Styles stuff

This commit is contained in:
Bottersnike 2023-06-19 02:37:46 +01:00
parent 94a1b713e4
commit 12832ca73b
Signed by: Bottersnike
SSH Key Fingerprint: SHA256:3g0ghwd4dNX1k1RX8qazbiT+3RIYn/daeBevHZVCiU0
33 changed files with 1408 additions and 496 deletions

1
.gitignore vendored
View File

@ -2,3 +2,4 @@
__pycache__/
build/
kcf/
static/main.css

202
docs.py
View File

@ -1,11 +1,16 @@
from dataclasses import dataclass
from typing import Any
import datetime
import re
import os
import jinja_markdown
from flask import Flask, send_from_directory, render_template, make_response, url_for
from flask import (
Flask, send_from_directory, render_template, make_response, url_for,
render_template_string
)
import sass
from livereload import Server
# Importing performs monkeypatching
@ -49,6 +54,10 @@ EAMUSE_CONTENTS = {
}
SEGA_CONTENTS = {
"intro.html": ("Introduction to RingEdge 2", ()),
"network": ("Networking", {
"auth.html": "ALL.Net Authentication",
"billing.html": "ALL.Net Billing",
}),
"hardware": ("Hardware", {
"jvs.html": "JVS",
"touch.html": "Touchscreen",
@ -82,8 +91,8 @@ CONTENTS = {
class Part:
id: str
name: str
description: str = None
page: str = None
description: str | None = None
page: str | None = None
PARTS = {
@ -158,6 +167,8 @@ def generate_toc(base, name, route, start=1):
name, children = toc[url]
elif isinstance(toc[url], str):
name, children = toc[url], -1
else:
raise ValueError
out += "<li>"
if isinstance(url, str):
@ -206,10 +217,10 @@ def generate_toc(base, name, route, start=1):
return walk(toc, route, start)
def generate_footer(base, name, route):
def generate_footer_links(base, name, route):
parts = route.strip("/").split("/")
if not parts:
return ""
return {}
toc = CONTENTS
path = []
@ -229,10 +240,13 @@ def generate_footer(base, name, route):
break
if toc == CONTENTS and len(parts) == 1:
assert toc is not None
toc = toc[PAGES_BASE.partition("/")[2]]
if toc is None:
siblings = None
us_idx = -1
parent = ""
else:
siblings = [i for i in toc.keys() if isinstance(i, str)]
try:
@ -243,29 +257,24 @@ def generate_footer(base, name, route):
if not parent.endswith("/"):
parent += "/"
footer = "<footer><span>"
links: dict[str, Any] = dict(footer_previous="", footer_crumbs=[], footer_current="", footer_next="")
if siblings and us_idx > 0:
footer += f'<a href="{parent}{siblings[us_idx - 1]}">Previous page</a>'
footer += "</span><span>"
links["footer_previous"] = parent + siblings[us_idx - 1]
if parts:
crumbs = []
built = ROOT + "/"
for i in parts[:-1]:
built += f"{i}"
if not built.endswith((".html", "/")):
built += "/"
crumbs.append(f'<a href="{built}">{i}</a>')
crumbs.append(parts[-1])
footer += "/".join(crumbs)
footer += "</span><span>"
links["footer_crumbs"].append((built, i))
links["footer_current"] = parts[-1]
if siblings and us_idx < len(siblings) - 1 and us_idx != -1:
footer += f'<a href="{parent}{siblings[us_idx + 1]}">Next page</a>'
links["footer_next"] = parent + siblings[us_idx + 1]
footer += "</span></footer>"
return footer
return links
def ioctl(original):
@ -306,61 +315,106 @@ def header_script():
return send_from_directory(".", "headers.js")
for i in STATIC:
for base, _, files in os.walk(i):
def install_static():
for i in STATIC:
for base, _, files in os.walk(i):
for name in files:
def handler_factory(base, name):
def handler():
return send_from_directory(base, name)
return handler
local_base = base.replace("\\", "/").strip(".").strip("/")
route = local_base + "/" + name
if not route.startswith("/"):
route = "/" + route
app.add_url_rule(route, route, handler_factory(base, name))
def handler_factory_html(base, name, route):
def handler():
return render_template(
os.path.join(base, name).strip("/").replace("\\", "/"),
HOST=HOST,
ROOT=ROOT,
CANONICAL=ROOT + route,
**generate_footer_links(base, name, route),
generate_xrpc_list=generate_xrpc_list,
generate_toc=lambda start=1: generate_toc(base, name, route, start),
relative=lambda path: os.path.join(base, path).strip("/").replace("\\", "/"),
part=part,
ioctl=ioctl,
)
return handler
def handler_factory_markdown(base, name, route):
md_name = name[:-5] + ".md"
md_path = os.path.join(base, md_name).strip("/").replace("\\", "/")
title = "Markdown Page"
with open(os.path.join(TEMPLATES, md_path)) as md_f:
for line in md_f:
line = line.strip()
if line.startswith("#") and not line.startswith("##"):
title = line[1:].strip()
template = (
f"{{% extends \"sega.html\" %}}{{% block title %}}{title}{{% endblock %}}"
f"{{% block body %}}"
f"{{% markdown %}}{{% include \"{md_path}\" %}}{{% endmarkdown %}}"
f"{{% endblock %}}"
)
def handler():
return render_template_string(
template,
HOST=HOST,
ROOT=ROOT,
CANONICAL=ROOT + route,
**generate_footer_links(base, name, route),
generate_xrpc_list=generate_xrpc_list,
generate_toc=lambda start=1: generate_toc(base, name, route, start),
relative=lambda path: os.path.join(base, path).strip("/").replace("\\", "/"),
part=part,
ioctl=ioctl,
)
return handler
def install_pages():
for base, _, files in os.walk(TEMPLATES + "/" + PAGES_BASE):
if ".git" in base:
continue
if base.startswith(TEMPLATES):
base = base[len(TEMPLATES):]
for name in files:
def handler(base, name):
def handler():
return send_from_directory(base, name)
return handler
local_base = base.replace("\\", "/").strip(".").strip("/")
route = local_base + "/" + name
if not route.startswith("/"):
route = "/" + route
handler_factory = None
handler = handler(base, name)
handler.__name__ == route
app.add_url_rule(route, route, handler)
if name.endswith(".html"):
handler_factory = handler_factory_html
elif name.endswith(".md") and not name.startswith("~"):
handler_factory = handler_factory_markdown
name = name[:-3] + ".html"
if handler_factory is not None:
local_base = base.replace("\\", "/").strip(".").strip("/")
if local_base.startswith(PAGES_BASE):
local_base = local_base[len(PAGES_BASE):]
for base, _, files in os.walk(TEMPLATES + "/" + PAGES_BASE):
if ".git" in base:
continue
if base.startswith(TEMPLATES):
base = base[len(TEMPLATES):]
if name.endswith(".md"):
route = local_base + "/" + name[:-3] + ".html"
else:
route = local_base + "/" + name
if route.endswith("/index.html"):
route = route[:-10]
if not route.startswith("/"):
route = "/" + route
for name in files:
if name.endswith(".html"):
def handler(base, name, route):
def handler():
return render_template(
os.path.join(base, name).strip("/").replace("\\", "/"),
ROOT=ROOT,
CANONICAL=ROOT + route,
generate_xrpc_list=generate_xrpc_list,
generate_toc=lambda start=1: generate_toc(base, name, route, start),
generate_footer=lambda: generate_footer(base, name, route),
relative=lambda path: os.path.join(base, path).strip("/").replace("\\", "/"),
part=part,
ioctl=ioctl,
)
return handler
local_base = base.replace("\\", "/").strip(".").strip("/")
if local_base.startswith(PAGES_BASE):
local_base = local_base[len(PAGES_BASE):]
route = local_base + "/" + name
if route.endswith("/index.html"):
route = route[:-10]
if not route.startswith("/"):
route = "/" + route
print([route, base, name])
handler = handler(base, name, route)
handler.__name__ == route
app.add_url_rule(route, route, handler)
app.add_url_rule(route, route, handler_factory(base, name, route))
@app.route("/sitemap.xml")
@ -369,7 +423,7 @@ def sitemap():
links = []
for rule in app.url_map.iter_rules():
if "GET" in rule.methods and len(rule.arguments) == 0:
if (not rule.methods or "GET" in rule.methods) and len(rule.arguments) == 0:
url = url_for(rule.endpoint, **(rule.defaults or {}))
if not url.endswith(("/", ".html", ".png")):
continue
@ -388,6 +442,16 @@ def sitemap():
return response
def compile_sass():
with open("static/main.css", "w") as main_css:
main_css.write(sass.compile(filename="main.scss", output_style="compressed")) # type: ignore
install_static()
install_pages()
compile_sass()
def run_dev():
app.config['TEMPLATES_AUTO_RELOAD'] = True
app.config['DEBUG'] = True
@ -396,6 +460,8 @@ def run_dev():
server = Server(app.wsgi_app)
server.watch(".")
server.watch("main.scss", func=compile_sass)
server.watch("styles", func=compile_sass)
server.serve(port=3000)

View File

@ -1,4 +1,87 @@
for (const el of document.querySelectorAll("[id]")) {
const ROOT = document.getElementById("root");
const sidebar = document.createElement("div");
sidebar.classList.add("sidebar");
let contentsEl = document.createElement("ul");
sidebar.appendChild(contentsEl);
const contentsHeadings = [];
let cDepth = 1;
for (const el of ROOT.querySelectorAll(["h1", "h2", "h3", "h4"])) {
if (el.id.length == 0) {
el.id = el.innerText
.toLowerCase()
.replace(/[^a-zA-Z]+/g, " ")
.trim()
.replace(/ /g, "-");
}
let newLi = document.createElement("li");
const newA = document.createElement("a");
newA.setAttribute("href", "#" + el.id);
newA.innerHTML = el.innerHTML;
newLi.appendChild(newA);
contentsHeadings.push([el, newLi]);
const depth = parseInt(el.tagName[1]);
// We're in too deep
while (cDepth > depth) {
contentsEl = contentsEl.parentElement.parentElement;
cDepth--;
}
// We're _way_ too shallow
if (cDepth < depth - 1) {
while (cDepth < depth) {
const tempLi = document.createElement("li");
contentsEl.appendChild(tempLi);
const newUl = document.createElement("ul");
tempLi.appendChild(newUl);
contentsEl = newUl;
cDepth++;
}
}
// We're only one level too shallow
else if (cDepth < depth) {
const newUl = document.createElement("ul");
if (depth == 0) {
newLi.appendChild(newUl);
} else {
contentsEl.childNodes[contentsEl.childNodes.length - 1].appendChild(newUl);
}
contentsEl = newUl;
cDepth++;
}
contentsEl.appendChild(newLi);
}
document.body.appendChild(sidebar);
contentsHeadings.reverse();
const onScroll = () => {
for (const [hEl, sbLi] of contentsHeadings) {
sbLi.classList.remove("active");
}
let set = false;
for (const [hEl, sbLi] of contentsHeadings) {
if (hEl.offsetTop <= window.scrollY + 32) {
sbLi.classList.add("active");
set = true;
break;
}
}
if (!set && contentsHeadings.length != 0) {
contentsHeadings[contentsHeadings.length - 1][1].classList.add("active");
}
};
document.addEventListener("scroll", onScroll);
document.addEventListener("resize", onScroll);
onScroll();
for (const el of ROOT.querySelectorAll("[id]")) {
if (el.tagName === "marker") continue;
el.classList.add("haspara");
const pilcrow = document.createElement("a");
@ -84,4 +167,4 @@ const make_foldable = (root) => {
flush_header(level, end);
}
};
make_foldable(document.body);
make_foldable(ROOT);

91
main.scss Normal file
View File

@ -0,0 +1,91 @@
// * {
// margin: 0;
// padding: 0;
// }
@import "styles/normalise.scss";
* {
box-sizing: border-box;
}
html,
body {
width: 100%;
height: 100%;
}
body {
font-family: "Segoe UI", sans-serif;
line-height: 1.35;
color: #222;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
&::after {
content: "";
display: block;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 32px;
background: linear-gradient(#000, #0000);
z-index: 10;
}
}
#root {
max-width: #{960px + 16px * 2};
width: 100%;
padding: 16px;
}
#gap {
flex-grow: 1;
}
img {
max-width: 100%;
}
svg {
transform: translateZ(0);
}
// We can't fit equal margins either side, so try our best
@media (max-width: #{(960px + 16px * 2) + 260px * 2}) {
body {
padding-left: 260px;
}
#root {
max-width: #{800px + 16 * 2};
}
}
// We have no hope of showing the sidebar
@media (max-width: #{(800px + 16px * 2) + 260px * 1}) {
body {
padding-left: 0;
}
.sidebar {
display: none;
}
}
@media (prefers-color-scheme: dark) {
body {
background-color: #000;
color: #e6e6e6;
}
}
@import "styles/type.scss";
@import "styles/code.scss";
@import "styles/table.scss";
@import "styles/collapse.scss";
@import "styles/footer.scss";
@import "styles/sidebar.scss";
@import "styles/misc.scss";
@import "styles/nav.scss"; // TODO: Get rid of this
@import "styles/part.scss"; // TODO: Get rid of this

4
start_ea.cmd Normal file
View File

@ -0,0 +1,4 @@
@echo off
set EA_PROOT=pages/eamuse
set EA_HOST=127.0.0.1:3000
py docs.py

4
start_sega.cmd Normal file
View File

@ -0,0 +1,4 @@
@echo off
set EA_PROOT=pages/sega
set EA_HOST=127.0.0.1:3000
py docs.py

View File

@ -71,11 +71,12 @@ code {
vertical-align: middle;
letter-spacing: .02em;
padding: 2px 4px;
font-size: 90%;
/* font-size: 90%; */
color: #c7254e;
background-color: #f9f2f4;
border-radius: 4px;
word-break: break-word;
vertical-align: bottom;
}
dfn {
@ -266,6 +267,7 @@ mark {
.toggle-root {
cursor: pointer;
position: relative;
padding-left: 22px;
}
.toggle-root.closed {
@ -280,7 +282,7 @@ mark {
border-left-color: transparent;
border-top-color: transparent;
position: absolute;
left: -18px;
left: 2px;
top: 50%;
transform: translateY(-50%) rotate(45deg);
transition: transform 100ms ease-out, opacity 100ms ease-out;
@ -339,7 +341,7 @@ mark {
@media (prefers-color-scheme: dark) {
body {
background-color: #000;
color: #fff;
color: #e6e6e6;
}
footer {

61
styles/code.scss Normal file
View File

@ -0,0 +1,61 @@
code {
vertical-align: middle;
letter-spacing: 0.02em;
padding: 2px 4px;
font-size: 90%;
color: #c7254e;
background-color: #f9f2f4;
border-radius: 4px;
word-break: break-word;
vertical-align: middle;
& > a {
color: inherit;
}
@media (prefers-color-scheme: dark) {
background-color: #221115;
}
}
pre > code,
.highlight {
display: block;
word-break: normal;
border-radius: 4px;
background: #f8f8f8;
border: 1px solid #ccc;
padding: 4px;
color: #333;
padding: 9.5px;
line-height: 1.4;
width: min-content;
}
pre {
max-width: 100%;
overflow-x: auto;
display: block;
& > .highlight {
margin-bottom: -16px;
}
}
.highlight {
font-size: 95%;
& > pre {
margin: 0;
& > code {
border: none;
margin: 0;
padding: 0;
}
}
@media (prefers-color-scheme: dark) {
filter: invert(1);
}
}

99
styles/collapse.scss Normal file
View File

@ -0,0 +1,99 @@
// Paragraph markers
.pilcrow {
position: absolute;
right: calc(100%);
padding-right: 10px;
top: 0.1em;
font-size: 0.9em;
text-decoration: none;
opacity: 0;
color: #e68aa2;
&:hover {
opacity: 1;
color: #c7254e;
}
}
.haspara {
position: relative;
&:hover .pilcrow {
opacity: 1;
}
}
// Section collapsing
.toggle-root {
cursor: pointer;
position: relative;
padding-left: 22px;
&.closed {
user-select: none;
}
&::before {
opacity: 0.5;
content: "";
display: block;
border: 4px solid currentColor;
border-left-color: transparent;
border-top-color: transparent;
position: absolute;
left: 2px;
top: 50%;
transform: translateY(-50%) rotate(45deg);
transition: transform 100ms ease-out, opacity 100ms ease-out;
}
&:hover::before {
opacity: 1;
}
&.closed::before {
transform: translateY(-50%) rotate(-45deg);
}
}
.toggle-section {
opacity: 1;
height: auto;
transition: opacity 50ms ease-out;
overflow: visible;
&.closed {
opacity: 0;
overflow: hidden;
height: 0;
}
}
// Notes
summary {
user-select: none;
cursor: pointer;
color: #c7254e;
}
details {
background: #f9f2f4;
border: 1px solid #c7b3b8;
border-radius: 2px;
padding: 4px 8px;
margin: 4px 0;
overflow-x: auto;
max-width: 100%;
code {
background: #fff;
}
@media (prefers-color-scheme: dark) {
background: #1c0d11;
border-color: #3b2b2f;
& code {
background: #000;
}
}
}

31
styles/footer.scss Normal file
View File

@ -0,0 +1,31 @@
footer {
width: 100%;
margin-top: 32px;
padding-top: 8px;
background-color: #090a0c;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
.footer-inner {
display: flex;
flex-direction: column;
max-width: 1000px;
width: 100%;
padding: 24px 16px 32px;
.footer-links {
text-align: center;
& > *:first-child {
float: left;
}
& > *:last-child {
float: right;
}
}
}
}

19
styles/misc.scss Normal file
View File

@ -0,0 +1,19 @@
.client {
color: #f5417d;
}
.server {
color: #4171f5;
}
.ata-bad {
color: #f5417d;
}
.ata-good {
color: #4df541;
}
.ata-ignore {
color: #f5ad41;
}

16
styles/nav.scss Normal file
View File

@ -0,0 +1,16 @@
table.nav {
padding-right: 1px;
padding-bottom: 1px;
}
table.nav td,
table.nav th {
display: inline-block;
margin-right: -1px;
margin-bottom: -1px;
}
.nav a {
display: block;
padding: 4px 8px;
}

351
styles/normalise.scss Normal file
View File

@ -0,0 +1,351 @@
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
/* Document
========================================================================== */
/**
* 1. Correct the line height in all browsers.
* 2. Prevent adjustments of font size after orientation changes in iOS.
*/
html {
line-height: 1.15; /* 1 */
-webkit-text-size-adjust: 100%; /* 2 */
}
/* Sections
========================================================================== */
/**
* Remove the margin in all browsers.
*/
body {
margin: 0;
}
/**
* Render the `main` element consistently in IE.
*/
main {
display: block;
}
/**
* Correct the font size and margin on `h1` elements within `section` and
* `article` contexts in Chrome, Firefox, and Safari.
*/
h1 {
font-size: 2em;
margin: 0.67em 0;
}
/* Grouping content
========================================================================== */
/**
* 1. Add the correct box sizing in Firefox.
* 2. Show the overflow in Edge and IE.
*/
hr {
box-sizing: content-box; /* 1 */
height: 0; /* 1 */
overflow: visible; /* 2 */
}
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers.
*/
pre {
font-family: monospace, monospace; /* 1 */
font-size: 1em; /* 2 */
}
/* Text-level semantics
========================================================================== */
/**
* Remove the gray background on active links in IE 10.
*/
a {
background-color: transparent;
}
/**
* 1. Remove the bottom border in Chrome 57-
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
*/
abbr[title] {
border-bottom: none; /* 1 */
text-decoration: underline; /* 2 */
text-decoration: underline dotted; /* 2 */
}
/**
* Add the correct font weight in Chrome, Edge, and Safari.
*/
b,
strong {
font-weight: bolder;
}
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers.
*/
code,
kbd,
samp {
font-family: monospace, monospace; /* 1 */
font-size: 1em; /* 2 */
}
/**
* Add the correct font size in all browsers.
*/
small {
font-size: 80%;
}
/**
* Prevent `sub` and `sup` elements from affecting the line height in
* all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
/* Embedded content
========================================================================== */
/**
* Remove the border on images inside links in IE 10.
*/
img {
border-style: none;
}
/* Forms
========================================================================== */
/**
* 1. Change the font styles in all browsers.
* 2. Remove the margin in Firefox and Safari.
*/
button,
input,
optgroup,
select,
textarea {
font-family: inherit; /* 1 */
font-size: 100%; /* 1 */
line-height: 1.15; /* 1 */
margin: 0; /* 2 */
}
/**
* Show the overflow in IE.
* 1. Show the overflow in Edge.
*/
button,
input {
/* 1 */
overflow: visible;
}
/**
* Remove the inheritance of text transform in Edge, Firefox, and IE.
* 1. Remove the inheritance of text transform in Firefox.
*/
button,
select {
/* 1 */
text-transform: none;
}
/**
* Correct the inability to style clickable types in iOS and Safari.
*/
button,
[type="button"],
[type="reset"],
[type="submit"] {
-webkit-appearance: button;
}
/**
* Remove the inner border and padding in Firefox.
*/
button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
border-style: none;
padding: 0;
}
/**
* Restore the focus styles unset by the previous rule.
*/
button:-moz-focusring,
[type="button"]:-moz-focusring,
[type="reset"]:-moz-focusring,
[type="submit"]:-moz-focusring {
outline: 1px dotted ButtonText;
}
/**
* Correct the padding in Firefox.
*/
fieldset {
padding: 0.35em 0.75em 0.625em;
}
/**
* 1. Correct the text wrapping in Edge and IE.
* 2. Correct the color inheritance from `fieldset` elements in IE.
* 3. Remove the padding so developers are not caught out when they zero out
* `fieldset` elements in all browsers.
*/
legend {
box-sizing: border-box; /* 1 */
color: inherit; /* 2 */
display: table; /* 1 */
max-width: 100%; /* 1 */
padding: 0; /* 3 */
white-space: normal; /* 1 */
}
/**
* Add the correct vertical alignment in Chrome, Firefox, and Opera.
*/
progress {
vertical-align: baseline;
}
/**
* Remove the default vertical scrollbar in IE 10+.
*/
textarea {
overflow: auto;
}
/**
* 1. Add the correct box sizing in IE 10.
* 2. Remove the padding in IE 10.
*/
[type="checkbox"],
[type="radio"] {
box-sizing: border-box; /* 1 */
padding: 0; /* 2 */
}
/**
* Correct the cursor style of increment and decrement buttons in Chrome.
*/
[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
height: auto;
}
/**
* 1. Correct the odd appearance in Chrome and Safari.
* 2. Correct the outline style in Safari.
*/
[type="search"] {
-webkit-appearance: textfield; /* 1 */
outline-offset: -2px; /* 2 */
}
/**
* Remove the inner padding in Chrome and Safari on macOS.
*/
[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
/**
* 1. Correct the inability to style clickable types in iOS and Safari.
* 2. Change font properties to `inherit` in Safari.
*/
::-webkit-file-upload-button {
-webkit-appearance: button; /* 1 */
font: inherit; /* 2 */
}
/* Interactive
========================================================================== */
/*
* Add the correct display in Edge, IE 10+, and Firefox.
*/
details {
display: block;
}
/*
* Add the correct display in all browsers.
*/
summary {
display: list-item;
}
/* Misc
========================================================================== */
/**
* Add the correct display in IE 10+.
*/
template {
display: none;
}
/**
* Add the correct display in IE 10.
*/
[hidden] {
display: none;
}

42
styles/part.scss Normal file
View File

@ -0,0 +1,42 @@
.part {
text-decoration: underline dotted;
text-underline-offset: 2px;
position: relative;
display: inline;
cursor: help;
background-color: #fff;
& span {
display: block;
}
& > span {
display: none;
position: absolute;
left: 0;
width: 100%;
top: 100%;
margin: 4px;
border: 1px solid currentColor;
padding: 8px;
background-color: inherit;
}
& > span > span:nth-child(2n - 1) {
font-weight: 600;
}
& > span > span:nth-child(2n) {
margin-left: 1rem;
}
&:hover > span,
&:focus > span,
& > span:hover {
display: block;
}
@media (prefers-color-scheme: dark) {
background-color: #000;
}
}

52
styles/sidebar.scss Normal file
View File

@ -0,0 +1,52 @@
.sidebar {
position: fixed;
left: 0;
top: 0;
z-index: 100;
width: 260px;
font-size: 0.875rem;
max-height: 100%;
overflow-y: auto;
ul {
list-style: none;
margin-left: 4px;
border-left: 2px solid #aaa;
@media (prefers-color-scheme: dark) {
border-color: #555;
}
}
li {
padding-left: 6px;
margin-left: -1px;
color: #999;
@media (prefers-color-scheme: dark) {
color: #aaa;
}
&.active {
padding-left: 4px;
border-left: 2px solid #f5417d;
color: #f5417d;
}
}
a {
display: block;
padding: 4px 0 4px 6px;
font-weight: 500;
text-decoration: none;
&,
&:visited {
color: inherit;
}
&:hover {
color: #f5417d;
}
}
}
// @media (max-width: {}

54
styles/table.scss Normal file
View File

@ -0,0 +1,54 @@
table {
border-collapse: collapse;
// letter-spacing: 0.02em;
max-width: 100%;
overflow-x: auto;
display: block;
&.code {
font-family: monospace;
td,
th {
text-align: center;
}
}
& ~ table {
margin-top: 1rem;
}
}
thead {
font-weight: bold;
border-bottom: 2px solid currentColor;
}
td,
th {
border: 1px solid #111;
padding: 2px;
min-width: 32px;
vertical-align: top;
& > code {
word-break: normal;
}
}
table:not(.code) td,
table:not(.code) th {
padding: 2px 6px;
}
.pad-row {
border-top: 2px solid currentColor;
border-bottom: 2px solid currentColor;
height: 1px;
}
@media (prefers-color-scheme: dark) {
td,
th {
border: 1px solid #777;
}
}

89
styles/type.scss Normal file
View File

@ -0,0 +1,89 @@
p {
word-break: break-word;
}
// a {
// color: #f5417d;
// }
small.x-small {
font-size: 0.6em;
}
// Typography spacing
h1,
h2,
h3,
h4,
h5,
h6 {
padding: 0;
margin: 0;
}
h1,
h2 {
margin-top: 2.4rem;
&:first-child {
margin-top: 1rem;
}
}
h3,
h4,
h5,
h6 {
margin-top: 1.6rem;
}
h1 + h2,
h2 + h3,
h3 + h4,
h4 + h5,
h5 + h6,
table {
margin-top: 0.8rem;
}
p {
margin-top: 0.8rem;
margin-bottom: 0.8rem;
overflow-wrap: break-word;
}
ul {
margin: 0.8rem 0 0 1.6rem;
padding: 0;
}
ul ul {
margin: 0 0 0 1.6rem;
}
ol {
margin: 0.8rem 0 0 2.4rem;
padding: 0;
}
ol ol {
margin: 0 0 0 2.4rem;
}
dfn {
border-bottom: 1px dashed currentColor;
cursor: help;
}
@media (prefers-color-scheme: dark) {
a {
text-decoration: none;
&,
&:visited {
color: #f5417d;
}
&:hover {
color: #dc3264;
}
}
}

View File

@ -7,7 +7,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}{% endblock %}{% if self.title() %} | {% endif %}{% block roottitle %}Arcade Reverse Engineering{% endblock %}</title>
<link rel="stylesheet" href="{{ROOT}}/styles.css?ver=16">
<!-- <link rel="stylesheet" href="{{ROOT}}/styles.css?ver=16"> -->
<link rel="stylesheet" href="{{ROOT}}/static/main.css">
<link rel="stylesheet" href="{{ROOT}}/tango.css">
<link rel="canonical" href="{{CANONICAL}}" />
@ -22,8 +23,11 @@
</head>
<body>
{% block rootbody %}{% endblock %}
{{ generate_footer()|safe }}
<div id="root">
{% block rootbody %}{% endblock %}
</div>
<div id="gap"></div>
{% include "footer.html" %}
<script src="{{ROOT}}/headers.js?v=1"></script>
</body>

13
templates/footer.html Normal file
View File

@ -0,0 +1,13 @@
<footer>
<div class="footer-inner">
<div class="footer-links">
<span>{%if footer_previous %}<a href="{{footer_previous}}">Previous page</a>{% endif %}</span>
<span>
{% for crumb in footer_crumbs %}<a href="{{crumb[0]}}">{{crumb[1]}}</a>/{% endfor %}{{footer_current}}
</span>
<span>{%if footer_next %}<a href="{{footer_next}}">Next page</a>{% endif %}</span>
</div>
</div>
</footer>

View File

@ -1,3 +0,0 @@
{% extends "sega.html" %} {% block title %}Ring Keychip{% endblock %} {% block body %}
{% markdown %}{% include relative("~keychip.md") %}{% endmarkdown %}
{% endblock %}

View File

@ -1,44 +1,25 @@
{% extends "sega.html" %}
{% block title %}Touchscreen{% endblock %}
{% block body %}
<h1>The MaiMai Touchscreen</h1>
# The MaiMai Touchscreen
<p>The touchscreen for MaiMai, pre-DX, is powered by a {{part("838-15221")|safe}} board, connected to <code>COM3</code>.
</p>
<p>Unlike other IO boards, this board communicates using a custom, text-based, protocol.</p>
The touchscreen for MaiMai, pre-DX, is powered by a 838-15221 board, connected to `COM3`.
<h2 id="serial">Serial configuartion</h2>
<table>
<tr>
<td>Port</td>
<td>COM3</td>
</tr>
<tr>
<td>Baud rate</td>
<td>9600</td>
</tr>
<tr>
<td>Bits per byte</td>
<td>8</td>
</tr>
<tr>
<td>Stop bits</td>
<td>0</td>
</tr>
<tr>
<td>Parity bits</td>
<td>0</td>
</tr>
</table>
Unlike other IO boards, this board communicates using a custom, text-based, protocol.
<h2 id="packet">Packet format</h2>
<p>As previously mentioned, all packets are text. This means, for the most part, values will stay within a normal
printable range. More importantly, null bytes are not permitted as they will be interpted as the end of the string.
</p>
<p>Packets sent from the game to the board are surrounded in braces, <code>{like this}</code>. Packets sent from the
board to the game are surrounded in parentheses, <code>(like this)</code>.</p>
## Serial configuartion
| | |
| ------------- | ---- |
| Port | COM3 |
| Baud Rate | 9600 |
| Bits per byte | 8 |
| Stop bits | 0 |
| Parity bits | 0 |
## Packet format
As previously mentioned, all packets are text. This means, for the most part, values will stay within a normal printable range. More importantly, null bytes are not permitted as they will be interpted as the end of the string.
Packets sent from the game to the board are surrounded in braces, `{like this}`. Packets sent from the board to the game are surrounded in parentheses, `(like this)`.
### `{HALT}`
<h3 id="halt"><code>{HALT}</code></h3>
<table class="code">
<tr>
<td><code>{</code></td>
@ -49,8 +30,9 @@
<td><code>}</code></td>
</tr>
</table>
<p>This instructs the board to stop sending the state of the touchscreen. No response is expected.</p>
<h3 id="stat"><code>{STAT}</code></h3>
This instructs the board to stop sending the state of the touchscreen. No response is expected.
### `{STAT}`
<table class="code">
<tr>
<td><code>{</code></td>
@ -61,8 +43,9 @@
<td><code>}</code></td>
</tr>
</table>
<p>This instructs the board to begin sending the state of the touchscreen (detailed below). No response is expected.</p>
<h3 id="xxth"><code>{??th}</code></h3>
This instructs the board to begin sending the state of the touchscreen (detailed below). No response is expected.
### `{??th}`
<table class="code">
<tr>
<td><code>{</code></td>
@ -73,8 +56,7 @@
<td><code>}</code></td>
</tr>
</table>
<p>This requests the configured threshold value for a specific sensor from the board. The expected response is as
follows:</p>
This requests the configured threshold value for a specific sensor from the board. The expected response is as follows:
<table class="code">
<tr>
<td><code>(</code></td>
@ -85,7 +67,8 @@
<td><code>)</code></td>
</tr>
</table>
<h3 id="xxth"><code>{??k?}</code></h3>
### `{??k?}`
<table class="code">
<tr>
<td><code>{</code></td>
@ -96,7 +79,7 @@
<td><code>}</code></td>
</tr>
</table>
<p>This configures the threshold value for a specific sensor. The expected response is as follows:</p>
This configures the threshold value for a specific sensor. The expected response is as follows:
<table class="code">
<tr>
<td><code>(</code></td>
@ -109,8 +92,8 @@
</table>
<h2 id="push">Active mode</h2>
<p>After a <code>{STAT}</code> packet is received, the board enters a mode where it begins constantly transmitting the
state of the touchscreen. The data sent is in the following format:</p>
After a `{STAT}` packet is received, the board enters a mode where it begins constantly transmitting the
state of the touchscreen. The data sent is in the following format:
<table class="code">
<tr>
@ -141,16 +124,16 @@
</tr>
</table>
<p>Each data byte is a bit mask of the 5 values it contains, mapped to <code>1<sub>h</sub></code>,
<code>2<sub>h</sub></code>, <code>4<sub>h</sub></code>, <code>8<sub>h</sub></code>, and <code>10<sub>h</sub></code>
respectively. While sending <code>1f<sub>h</sub></code> would be a valid byte, it is recommended to make use of the
Each data byte is a bit mask of the 5 values it contains, mapped to `1`~h~,
`2`~h~, `4`~h~, `8`~h~, and `10`~h~
respectively. While sending `1f`~h~ would be a valid byte, it is recommended to make use of the
upper bits to keep the value within a printable range. I personally recommend masking with
<code>40<sub>h</sub></code> (<code>'@'</code>) for this purpose. Values will then range from <code>@</code> to
<code>_</code>. Bits indicated with <code>x</code> are unused. It is recommended, but not required, to leave them
`40`~h~ (`'@'`) for this purpose. Values will then range from `@` to
`_`. Bits indicated with `x` are unused. It is recommended, but not required, to leave them
unset.
</p>
<p>The four bytes marked as padding are unused, with the only requirement being that they are non-null. It is
recommended, but not required, to set them to <code>40<sub>h</sub></code> (<code>'@'</code>).</p>
The four bytes marked as padding are unused, with the only requirement being that they are non-null. It is
recommended, but not required, to set them to `40`~h~ (`'@'`).
<details>
<summary>Individual bit breakdown</summary>
@ -359,7 +342,5 @@
</table>
</details>
<p>An example may aid here. The following image is what transmitting <code>(FIBT@@@@@@@@)</code> is interpreted as:</p>
An example may aid here. The following image is what transmitting `(FIBT@@@@@@@@)` is interpreted as:
<img src="{{ROOT}}/images/maimai_FIBT.png" class="graphic">
{% endblock %}

View File

@ -0,0 +1,34 @@
# MaiMai API Reference
This API reference is currently accurate for versions:
- 1.97
- 1.98
Support for older versions is in the works.
## `POST /MaimaiServlet/UserLoginApi`
## `POST /MaimaiServlet/UserLogoutApi`
## `POST /MaimaiServlet/UpsertTransferApi`
## `POST /MaimaiServlet/UpsertUserAppApi`
## `POST /MaimaiServlet/GetUserActivityApi`
## `POST /MaimaiServlet/GetUserBossApi`
## `POST /MaimaiServlet/GetUserCharacterApi`
## `POST /MaimaiServlet/GetUserCourseApi`
## `POST /MaimaiServlet/GetUserDataApi`
## `POST /MaimaiServlet/GetTransferFriendApi`
## `POST /MaimaiServlet/GetUserItemApi`
## `POST /MaimaiServlet/GetUserMusicApi`
## `POST /MaimaiServlet/GetUserOptionApi`
## `POST /MaimaiServlet/GetUserPresentEventApi`
## `POST /MaimaiServlet/GetUserPreviewApi`
## `POST /MaimaiServlet/GetUserGradeApi`
## `POST /MaimaiServlet/GetUserRecentRatingApi`
## `POST /MaimaiServlet/GetUserSurvivalApi`
## `POST /MaimaiServlet/GetUserWebOptionApi`
## `POST /MaimaiServlet/UpsertClientBookkeepingApi`
## `POST /MaimaiServlet/UpsertClientSettingApi`
## `POST /MaimaiServlet/UpsertClientTestmodeApi`
## `POST /MaimaiServlet/GetGameSettingApi`
## `POST /MaimaiServlet/GetGameEventApi`
## `POST /MaimaiServlet/GetGameRankingApi`

View File

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Maimai API Documentation</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet" />
<style>
body {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<redoc spec-url="http://134.65.56.170/MaimaiServlet/spec.json"></redoc>
<script src="https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js"></script>
</body>
</html>

View File

@ -1,3 +0,0 @@
{% extends "sega.html" %} {% block title %}ALL.Net{% endblock %} {% block body %}
{% markdown %}{% include relative("~allnet.md") %}{% endmarkdown %}
{% endblock %}

View File

@ -1,197 +0,0 @@
{% extends "sega.html" %}
{% block title %}ALL.Net{% endblock %}
{% block body %}
<h1>ALL.Net</h1>
<p>A simple service that exposes two URLs. The hostname must be <code>http://naominet.jp</code>.</p>
<p>Requests should be made with a number of standard HTTP headers, and must be either HTTP version 1.0 or 1.1</p>
<table>
<tr>
<td><code>Connection</code></td>
<td><code>Close</code></td>
</tr>
<tr>
<td><code>Pragma</code></td>
<td><code>DFI</code></td>
</tr>
<tr>
<td><code>User-Agent</code></td>
<td><code>ALL.Net_PC_Win2/ver1.0</code></td>
</tr>
<tr>
<td><code>Content-Type</code></td>
<td><code>application/x-www-form-urlencoded</code></td>
</tr>
<tr>
<td><code>Content-Length</code></td>
<td><i>variable</i></td>
</tr>
</table>
<p>Note that the <code>Pragma</code> header is optional, and the <code>Content-Type</code> header is a lie.</p>
<p>Requests and responses should be <code>POST</code>s, and their body should be base64 encoded, zlib compressed,
<code>x-www-form-urlencoded</code> data. For example, <code>{key: "value", other: "another"}</code> should encode to
<code>eJwdxcEJACAMA8Bt3CLD5BEQFIXSFtw+4OuWHpq7NG5OBXi+BmwzCRo=</code>.
</p>
<p>Responses are expected to contain <code>stat</code> indicating status:</p>
<table>
<tr>
<td><code>1</code></td>
<td>Success</td>
</tr>
<tr>
<td><code>0</code></td>
<td>Failure</td>
</tr>
<tr>
<td><code>-1</code></td>
<td>Failure</td>
</tr>
<tr>
<td><code>-2</code></td>
<td>Failure</td>
</tr>
<tr>
<td><code>-3</code></td>
<td>Failure</td>
</tr>
</table>
<p>This service provides two endpoints, documented below:</p>
<h3><code>/sys/servlet/PowerOn</code></h3>
<h4>Request:</h4>
<!--
"game_id=%s&ver=%s&serial=%s&ip=%s&firm_ver=%01d%02d%02d&boot_ver=%02X%02X&encode=%s&format_ver=%s&hops=%d\r\n"
"game_id=%s&ver=%s&serial=%s&ip=%s&firm_ver=%01d%02d%02d&boot_ver=%02X%02X&format_ver=%s&hops=%d\r\n"
"game_id=%s&ver=%s&serial=%s\r\n"
"game_id=%s&ver=%s&serial=%s&encode=%s\r\n"
"keychipid=%s&functype=%u&gameid=%s&gamever=%s&boardid=%s&tenpoip=%s&libalibver=%s&datamax=%s&billingtype=%d&protocolver=%s&operatingfix=%d"
(libalibver = 1.110, protocolver = 1.000)
SDBT: Chunithm
SBXL: Maimai -->
<table>
<tr>
<td><code>game_id</code></td>
<td>4-character game ID</td>
</tr>
<tr>
<td><code>ver</code></td>
<td></td>
</tr>
<tr>
<td><code>serial</code></td>
<td></td>
</tr>
<tr>
<td><code>ip</code></td>
<td></td>
</tr>
<tr>
<td><code>firm_ver</code></td>
<td><code>%01d%02d%02d</code></td>
</tr>
<tr>
<td><code>boot_ver</code></td>
<td><code>%02X%02X</code></td>
</tr>
<tr>
<td><code>format_ver</code></td>
<td></td>
</tr>
<tr>
<td><code>hops</code></td>
<td></td>
</tr>
<tr>
<td><code>encode</code></td>
<td></td>
</tr>
</table>
<h4>Response:</h4>
<table>
<tr>
<td><code>stat</code></td>
<td>See above</td>
</tr>
<tr>
<td><code>uri</code></td>
<td></td>
</tr>
<tr>
<td><code>host</code></td>
<td></td>
</tr>
<tr>
<td><code>region0</code></td>
<td></td>
</tr>
<tr>
<td><code>region_name0</code></td>
<td></td>
</tr>
<tr>
<td><code>region_name1</code></td>
<td></td>
</tr>
<tr>
<td><code>region_name2</code></td>
<td></td>
</tr>
<tr>
<td><code>region_name3</code></td>
<td></td>
</tr>
<tr>
<td><code>year</code></td>
<td></td>
</tr>
<tr>
<td><code>month</code></td>
<td></td>
</tr>
<tr>
<td><code>day</code></td>
<td></td>
</tr>
<tr>
<td><code>hour</code></td>
<td></td>
</tr>
<tr>
<td><code>minute</code></td>
<td></td>
</tr>
<tr>
<td><code>second</code></td>
<td></td>
</tr>
<tr>
<td><code>place_id</code></td>
<td></td>
</tr>
<tr>
<td><code>setting</code></td>
<td></td>
</tr>
<tr>
<td><code>country</code></td>
<td></td>
</tr>
<tr>
<td><code>timezone</code></td>
<td></td>
</tr>
<tr>
<td><code>res_class</code></td>
<td></td>
</tr>
</table>
<h3><code>/sys/servlet/DownloadOrder</code></h3>
{% endblock %}

View File

@ -1,3 +0,0 @@
{% extends "sega.html" %} {% block title %}ALL.Net Service{% endblock %} {% block body %}
{% markdown %}{% include relative("~auth.md") %}{% endmarkdown %}
{% endblock %}

View File

@ -0,0 +1,149 @@
# ALL.Net Authentication
The ALL.Net authentication service is divided into four primary categories. They are ordered in decreasing importance on this page.
**This entire service is HTTP-only!**
## Auth API
These four endpoints handle the primary functions of the ALL.Net authentication service. Requests and responses are sent as a url-encoded query-value string.
Some endpoints make use of a mildly obfuscated version of this. When the `Pragma: DFI` header is present for a request or response, it indicates that the content is a base64 encoded, deflated, version of its real value. If a request was made with `Pragma: DFI`, the response will be too.
All responses have the `Content-Type` of `text/plain; charset=%s`, where the charset is `EUC-JP` if unspecified in the request body.
Requests can be made using both the `GET` and `POST` verbs, and function identically.
### `/sys/servlet/PowerOn`
All requests to this endpoint **MUST** be DFI-encoded. The request is as follows:
| Name | Required | Default | Meaning |
| ------------ | -------- | -------- | ---------------------------------------------------------------------------------------------------------------------- |
| `game_id` | Yes | | The four-digit game ID |
| `ver` | Yes | | The game version |
| `serial` | Yes | | The keychip serial number |
| `ip` | Yes | | The tenpo router IP address (%d.%d.%d.%d) |
| `firm_ver` | Yes | | The ALL.Net library version (a semantic version, formatted as %01d%02d%02d) |
| `boot_ver` | Yes | | Unknown. Just pass 0000. (%02X%02X) |
| `encode` | | `EUC-JP` | Request encoding. "EUC-JP", "Shift_JIS" and "UTF-8" are common, but be prepared to handle more. |
| `format_ver` | | `1.00` | Request format version. Parsed as a float, however "1.00", "2.00" and "3" are the only values that should be observed. |
| `hops` | | `-1` | |
| `token` | | | Added in format 3, this value is echoed in the response. |
Observed values for `firm_ver` are listed below, however other versions likely exist:
- 0.01.00
- 0.02.00
- 2.00.07
- 2.00.08
- 2.01.02
- 3.00.00
- 3.00.01
- 3.00.02
- 3.00.03
- 3.00.04
- 3.00.05
- 3.00.09
- 5.00.00
- 6.00.00
The server should then use the provided information to authenticate the machine as it sees fit, and will then return the information required for use of ALL.Net services. The response structure varies depending on the format version in use.
| Name | Required | Default | Version added | Final version present | Meaning |
| ----------------- | -------- | ------- | ------------- | --------------------- | ------------------------------------------------------------------------------- |
| `stat` | Yes | | | | Success: `1`, Game failure: `-1`, Machine failure: `-2`, Location failure: `-3` |
| `uri` | Yes | _empty_ | | | Title server URI<sup>1</sup>. Will be empty if stat<=0 |
| `host` | Yes | _empty_ | | | Title server hostname<sup>1</sup>. Will be empty if stat<=0 |
| `place_id` | | | | | ALL.Net location ID |
| `name` | | | | | ALL.Net location name |
| `nickname` | | | | | ALL.Net location nickname |
| `region0` | Yes | `0` | | | Region information. |
| `region_name0` | Yes | _empty_ | | | |
| `region_name1` | Yes | _empty_ | | | |
| `region_name2` | Yes | _empty_ | | | |
| `region_name3` | Yes | _empty_ | | | ^ |
| `country` | | | 2 | | ALL.Net 3-character country code |
| `allnet_id` | | | 3 | | |
| `client_timezone` | Yes | _empty_ | 3 | | Example `+0900` |
| `utc_time` | Yes | | 3 | | `yyyy-MM-dd'T'HH:mm:ss'Z'` |
| `res_ver` | Yes | | 3 | | Will always be the literal `3` |
| `token` | Yes | | 3 | | The token from the request |
| `year` | Yes | | | 2 | Current time |
| `month` | Yes | | | 2 | |
| `day` | Yes | | | 2 | |
| `hour` | Yes | | | 2 | |
| `minute` | Yes | | | 2 | ^ |
| `timezone` | Yes | | 2 | 2 | Will always be the literal `+09:00` |
| `res_class` | Yes | | 2 | 2 | Will always be the literal `PowerOnResponseVer2` |
| `setting` | Yes | | | | Machine setting. `1` indicates the machine is OK, and should always be set. |
1. The hostname (if) present in `uri` is used for name resolution. The value in `host` is passed to the title server in the `Host` header, and can be utilised as an extra authentication step.
### `/sys/servlet/Alive`
There is no request for this endpoint. The response is the two bytes `OK` (that is, `Content-Length: 2`).
### `/sys/servlet/DownloadOrder`
All requests to this endpoint **MAY** be DFI-encoded. The request is as follows:
| Name | Required | Default | Meaning |
| --------- | -------- | ------- | ----------------------------------------------------------------------------------------------- |
| `game_id` | Yes | | The four-digit game ID |
| `ver` | Yes | | The game version |
| `serial` | Yes | | The keychip serial number |
| `ip` | | | The tenpo router IP address (%d.%d.%d.%d) |
| `encode` | | | Request encoding. "EUC-JP", "Shift_JIS" and "UTF-8" are common, but be prepared to handle more. |
The response is:
| Name | Required | Default | Meaning |
| -------- | --------------- | ------- | ---------------------------------------------------------------------------------------------------------------- |
| `stat` | Yes | | Machine setting |
| `serial` | <sup>1</sup> | | `,`-seperated list of the serial numbers of machines in the same store with the same game and machine group IDs. |
| `uri` | Yes<sup>1</sup> | `null` | Download order URI(s) |
1. Omitted if `stat` is not `1`.
`uri` can contain both an app download order url, and an option image download order url. The two are concatinated, with the option image having `|` prefixed. That is, if only an option image is available, this will take the obscure-looking value of `|https://url.to/opt`.
### `/sys/servlet/LoaderStateRecorder`
All requests to this endpoint **MUST NOT** be DFI-encoded. The request is as follows:
| Name | Required | Meaning |
| ----------- | -------- | ---------------------------------- |
| `serial` | Yes | The keychip serial number |
| `dvd` | Yes | |
| `net` | Yes | |
| `work` | Yes | |
| `old_net` | Yes | |
| `deliver` | Yes | |
| `nb_ftd` | Yes | Number of files to download |
| `nb_dld` | Yes | Number of files downloaded |
| `last_sysa` | Yes | Last authentication unix timestamp |
| `sysa_st` | Yes | Last authentication state |
| `dld_st` | Yes | Download state |
The response is either `OK` if the request was formatted correctly, otherwise `NG`.
## Report API
Used to report download status for an ongoing network-based game delivery, this category contains just a single endpoint.
### `/sys/servlet/Report`
## Title API
These legacy endpoints are used to request title-specific information. I've not seen any games use these, and they're a low priority for documentation.
### `/sys/servlet/GetMachineList`
### `/sys/servlet/GetPlaceList`
### `/sys/servlet/GetRegionList`
### `/sys/servlet/MachineTable`
### `/sys/servlet/PlaceRegionTable`
### `/sys/servlet/PlaceRegionTableAll`
## Admin API
These endpoints are used for ALL.Net system administration and are not of interest. Their endpoints are recorded here for prosperity. Custom network authors should consider implementing their own administration functionality however best suits their architecture.
### `/sys/servlet/AdminRegister`
### `/sys/servlet/AdminView`

View File

@ -0,0 +1,7 @@
# ALL.Net Billing
The ALL.Net billing service is provided by `ib.naominet.jp`, and is a single HTTPS endpoint running on port `8443`. This port number is hardcoded in games.
## `POST /request/`, `POST /request`, `POST /request.php`
This endpoint is accessible at three URIs, which all function identically.

View File

@ -1,6 +0,0 @@
{% extends "sega.html" %}
{% block title %}Networking{% endblock %}
{% block body %}
<h1>Networking</h1>
{{ generate_toc()|safe }}
{% endblock %}

View File

@ -5,7 +5,7 @@ ALL.Net, short for Amusement Linkage Live Network, is SEGA's standadised arcade
The ALL.Net model is compresed of four networked services:
1. [Authentication. This is `naominet.jp`, and handles basic operations.](./auth)
2. The billing service. This is `ib.naominet.jp`, and handles tracking and updating play counters.
2. [The billing service. This is `ib.naominet.jp`, and handles tracking and updating play counters.](./billing)
3. AiMeDB. This is `aime.naominet.jp`, and handles user management.
4. The title server. The URL for this is provided by ALL.Net, as different games use different services for this.

View File

@ -1,149 +0,0 @@
# ALL.Net Authentication
The ALL.Net authentication service is divided into four primary categories. They are ordered in decreasing importance on this page.
**This entire service is HTTP-only!**
## Auth API
These four endpoints handle the primary functions of the ALL.Net authentication service. Requests and responses are sent as a url-encoded query-value string.
Some endpoints make use of a mildly obfuscated version of this. When the `Pragma: DFI` header is present for a request or response, it indicates that the content is a base64 encoded, deflated, version of its real value. If a request was made with `Pragma: DFI`, the response will be too.
All responses have the `Content-Type` of `text/plain; charset=%s`, where the charset is `EUC-JP` if unspecified in the request body.
Requests can be made using both the `GET` and `POST` verbs, and function identically.
### `/sys/servlet/PowerOn`
All requests to this endpoint **MUST** be DFI-encoded. The request is as follows:
| Name | Required | Default | Meaning |
| ---------- | -------- | ------- | ---------------------------------------------------------------------------------------------------------------------- |
| game_id | Yes | | The four-digit game ID |
| ver | Yes | | The game version |
| serial | Yes | | The keychip serial number |
| ip | Yes | | The tenpo router IP address (%d.%d.%d.%d) |
| firm_ver | Yes | | The ALL.Net library version (a semantic version, formatted as %01d%02d%02d) |
| boot_ver | Yes | | Unknown. Just pass 0000. (%02X%02X) |
| encode | | EUC-JP | Request encoding. "EUC-JP", "Shift_JIS" and "UTF-8" are common, but be prepared to handle more. |
| format_ver | | 1.00 | Request format version. Parsed as a float, however "1.00", "2.00" and "3" are the only values that should be observed. |
| hops | | -1 | |
| token | | | Added in format 3, this value is echoed in the response. |
Observed values for `firm_ver` are listed below, however other versions likely exist:
- 0.01.00
- 0.02.00
- 2.00.07
- 2.00.08
- 2.01.02
- 3.00.00
- 3.00.01
- 3.00.02
- 3.00.03
- 3.00.04
- 3.00.05
- 3.00.09
- 5.00.00
- 6.00.00
The server should then use the provided information to authenticate the machine as it sees fit, and will then return the information required for use of ALL.Net services. The response structure varies depending on the format version in use.
| Name | Required | Default | Version added | Final version present | Meaning |
| --------------- | -------- | ------- | ------------- | --------------------- | ------------------------------------------------------------------------------- |
| stat | Yes | | | | Success: `1`, Game failure: `-1`, Machine failure: `-2`, Location failure: `-3` |
| uri | Yes | _empty_ | | | Title server URI<sup>1</sup>. Will be empty if stat<=0 |
| host | Yes | _empty_ | | | Title server hostname<sup>1</sup>. Will be empty if stat<=0 |
| place_id | | | | | ALL.Net location ID |
| name | | | | | ALL.Net location name |
| nickname | | | | | ALL.Net location nickname |
| region0 | Yes | `0` | | | Region information. |
| region_name0 | Yes | _empty_ | | | |
| region_name1 | Yes | _empty_ | | | |
| region_name2 | Yes | _empty_ | | | |
| region_name3 | Yes | _empty_ | | | ^ |
| country | | | 2 | | ALL.Net 3-character country code |
| allnet_id | | | 3 | | |
| client_timezone | Yes | _empty_ | 3 | | Example `+0900` |
| utc_time | Yes | | 3 | | `yyyy-MM-dd'T'HH:mm:ss'Z'` |
| res_ver | Yes | | 3 | | Will always be the literal `3` |
| token | Yes | | 3 | | The token from the request |
| year | Yes | | | 2 | Current time |
| month | Yes | | | 2 | |
| day | Yes | | | 2 | |
| hour | Yes | | | 2 | |
| minute | Yes | | | 2 | ^ |
| timezone | Yes | | 2 | 2 | Will always be the literal `+09:00` |
| res_class | Yes | | 2 | 2 | Will always be the literal `PowerOnResponseVer2` |
| setting | Yes | | | | Machine setting. `1` indicates the machine is OK, and should always be set. |
1. The hostname (if) present in `uri` is used for name resolution. The value in `host` is passed to the title server in the `Host` header, and can be utilised as an extra authentication step.
### `/sys/servlet/Alive`
There is no request for this endpoint. The response is the two bytes `OK` (that is, `Content-Length: 2`).
### `/sys/servlet/DownloadOrder`
All requests to this endpoint **MAY** be DFI-encoded. The request is as follows:
| Name | Required | Default | Meaning |
| ------- | -------- | ------- | ----------------------------------------------------------------------------------------------- |
| game_id | Yes | | The four-digit game ID |
| ver | Yes | | The game version |
| serial | Yes | | The keychip serial number |
| ip | Yes | | The tenpo router IP address (%d.%d.%d.%d) |
| encode | | | Request encoding. "EUC-JP", "Shift_JIS" and "UTF-8" are common, but be prepared to handle more. |
The response is:
| Name | Required | Default | Meaning |
| ------ | --------------- | ------- | ---------------------------------------------------------------------------------------------------------------- |
| stat | Yes | | Machine setting |
| serial | <sup>1</sup> | | `,`-seperated list of the serial numbers of machines in the same store with the same game and machine group IDs. |
| uri | Yes<sup>1</sup> | `null` | Download order URI(s) |
1. Omitted if `stat` is not `1`.
`uri` can contain both an app download order url, and an option image download order url. The two are concatinated, with the option image having `|` prefixed. That is, if only an option image is available, this will take the obscure-looking value of `|https://url.to/opt`.
### `/sys/servlet/LoaderStateRecorder`
All requests to this endpoint **MUST NOT** be DFI-encoded. The request is as follows:
| Name | Required | Meaning |
| --------- | -------- | ---------------------------------- |
| serial | Yes | The keychip serial number |
| dvd | Yes | |
| net | Yes | |
| work | Yes | |
| old_net | Yes | |
| deliver | Yes | |
| nb_ftd | Yes | Number of files to download |
| nb_dld | Yes | Number of files downloaded |
| last_sysa | Yes | Last authentication unix timestamp |
| sysa_st | Yes | Last authentication state |
| dld_st | Yes | Download state |
The response is either `OK` if the request was formatted correctly, otherwise `NG`.
## Report API
Used to report download status for an ongoing network-based game delivery, this category contains just a single endpoint.
### `/sys/servlet/Report`
## Title API
These legacy endpoints are used to request title-specific information. I've not seen any games use these, and they're a low priority for documentation.
### `/sys/servlet/GetMachineList`
### `/sys/servlet/GetPlaceList`
### `/sys/servlet/GetRegionList`
### `/sys/servlet/MachineTable`
### `/sys/servlet/PlaceRegionTable`
### `/sys/servlet/PlaceRegionTableAll`
## Admin API
These endpoints are used for ALL.Net system administration and are not of interest. Their endpoints are recorded here for prosperity. Custom network authors should consider implementing their own administration functionality however best suits their architecture.
### `/sys/servlet/AdminRegister`
### `/sys/servlet/AdminView`

View File

@ -5,6 +5,7 @@
<tr>
<td><a href="{{ROOT}}/">Contents</a></td>
<td><a href="{{ROOT}}/intro">Intro</a></td>
<td><a href="{{ROOT}}/network/">Networking</a></td>
<td><a href="{{ROOT}}/software/">Software</a></td>
<td><a href="{{ROOT}}/hardware/">Hardware</a></td>
<td><a href="{{ROOT}}/manual/">Manual</a></td>