This commit is contained in:
Bottersnike 2023-03-27 20:47:33 +01:00
parent fed0270c34
commit 3c482a6168
No known key found for this signature in database
22 changed files with 743 additions and 460 deletions

13
docs.py
View File

@ -3,6 +3,8 @@ import datetime
import re import re
import os 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
from livereload import Server from livereload import Server
@ -12,7 +14,12 @@ import ini_lexer # NOQA: F401
app = Flask(__name__) app = Flask(__name__)
app.jinja_options.setdefault('extensions', []).append('jinja2_highlight.HighlightExtension') extensions = app.jinja_options.setdefault('extensions', [])
extensions.append('jinja2_highlight.HighlightExtension')
extensions.append('jinja_markdown.MarkdownExtension')
jinja_markdown.EXTENSIONS.append("mdx_spantables")
jinja_markdown.EXTENSIONS.append("toc")
HTAG = re.compile(r"<h(\d)[^>]*id=\"([^\"]+)\"[^>]*>([^<]*)</h") HTAG = re.compile(r"<h(\d)[^>]*id=\"([^\"]+)\"[^>]*>([^<]*)</h")
@ -105,6 +112,9 @@ def generate_xrpc_list():
if prefix: if prefix:
prefix = prefix.replace("/", ".") + "." prefix = prefix.replace("/", ".") + "."
for i in files: for i in files:
if i.startswith("~"):
continue
delim = "_" if prefix else "." delim = "_" if prefix else "."
href = f"{ROOT}/proto{base[len(proto):]}/{i}" href = f"{ROOT}/proto{base[len(proto):]}/{i}"
output += f"<li><code><a href=\"{href}\">" output += f"<li><code><a href=\"{href}\">"
@ -325,6 +335,7 @@ for base, _, files in os.walk(TEMPLATES + "/" + PAGES_BASE):
generate_xrpc_list=generate_xrpc_list, generate_xrpc_list=generate_xrpc_list,
generate_toc=lambda start=1: generate_toc(base, name, route, start), generate_toc=lambda start=1: generate_toc(base, name, route, start),
generate_footer=lambda: generate_footer(base, name, route), generate_footer=lambda: generate_footer(base, name, route),
relative=lambda path: os.path.join(base, path).strip("/").replace("\\", "/"),
part=part, part=part,
ioctl=ioctl, ioctl=ioctl,
) )

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
images/factorytest-sbr.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

BIN
images/factorytest-tc1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
images/factorytest-tc2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

BIN
images/factorytest-tc3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

BIN
images/ring/keychip-top.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

186
mdx_spantables.py Normal file
View File

@ -0,0 +1,186 @@
"""
SpanTables Extension for Python-Markdown
========================================
This is a slightly modified version of the tables extension that comes with
python-markdown.
To span cells across multiple columns make sure the cells end with multiple
consecutive vertical bars. To span cells across rows fill the cell on the last
row with at least one underscore at the start or end of its content and no
other characters than spaces or underscores.
For example:
| head1 | head2 |
|-----------------|-------|
| span two cols ||
| span two rows | |
|_ | |
See <https://pythonhosted.org/Markdown/extensions/tables.html>
for documentation of the original extension.
Original code Copyright 2009 [Waylan Limberg](http://achinghead.com)
SpanTables changes Copyright 2016 [Maurice van der Pot](griffon26@kfk4ever.com)
License: [BSD](http://www.opensource.org/licenses/bsd-license.php)
"""
from __future__ import unicode_literals
from markdown.extensions.tables import TableProcessor
from markdown.extensions import Extension
import xml.etree.ElementTree as etree
class SpanTableProcessor(TableProcessor):
""" Process Tables. """
def test(self, parent, block):
rows = block.split('\n')
return (len(rows) > 1 and '|' in rows[0] and
'|' in rows[1] and '-' in rows[1] and
rows[1].strip()[0] in ['|', ':', '-'])
def is_end_of_rowspan(self, td):
return ((td is not None) and
(td.text.startswith('^') or td.text.endswith('^')) and
(td.text.strip('^ ') == ''))
def apply_rowspans(self, tbody):
table_cells = {}
rows = tbody.findall('tr')
max_cols = 0
max_rows = len(rows)
for y, tr in enumerate(rows):
cols = tr.findall('td')
x = 0
for td in cols:
colspan_str = td.get('colspan')
colspan = int(colspan_str) if colspan_str else 1
# Insert the td together with its parent
table_cells[(x, y)] = (tr, td)
x += colspan
max_cols = max(max_cols, x)
for x in range(max_cols):
possible_cells_in_rowspan = 0
current_colspan = None
for y in range(max_rows):
_, td = table_cells.get((x, y), (None, None))
if td is None:
possible_cells_in_rowspan = 0
else:
colspan = td.get('colspan')
if colspan != current_colspan:
current_colspan = colspan
possible_cells_in_rowspan = 0
if not td.text:
possible_cells_in_rowspan += 1
elif self.is_end_of_rowspan(td):
td.text = ''
possible_cells_in_rowspan += 1
first_cell_of_rowspan_y = y - (possible_cells_in_rowspan - 1)
for del_y in range(y, first_cell_of_rowspan_y, -1):
tr, td = table_cells.get((x, del_y))
tr.remove(td)
_, first_cell = table_cells.get((x, first_cell_of_rowspan_y))
first_cell.set('rowspan', str(possible_cells_in_rowspan))
possible_cells_in_rowspan = 0
else:
possible_cells_in_rowspan = 1
def run(self, parent, blocks):
""" Parse a table block and build table. """
block = blocks.pop(0).split('\n')
header = block[0].strip()
seperator = block[1].strip()
rows = [] if len(block) < 3 else block[2:]
# Get format type (bordered by pipes or not)
border = False
if header.startswith('|'):
border = True
# Get alignment of columns
align = []
for c in self._split_row(seperator, border):
if c.startswith(':') and c.endswith(':'):
align.append('center')
elif c.startswith(':'):
align.append('left')
elif c.endswith(':'):
align.append('right')
else:
align.append(None)
# Build table
table = etree.SubElement(parent, 'table')
thead = etree.SubElement(table, 'thead')
self._build_row(header, thead, align, border)
tbody = etree.SubElement(table, 'tbody')
for row in rows:
self._build_row(row.strip(), tbody, align, border)
self.apply_rowspans(tbody)
def _build_row(self, row, parent, align, border):
""" Given a row of text, build table cells. """
tr = etree.SubElement(parent, 'tr')
tag = 'td'
if parent.tag == 'thead':
tag = 'th'
cells = self._split_row(row, border)
c = None
# We use align here rather than cells to ensure every row
# contains the same number of columns.
for i, a in enumerate(align):
# After this None indicates that the cell before it should span
# this column and '' indicates an cell without content
try:
text = cells[i]
if text == '':
text = None
except IndexError: # pragma: no cover
text = ''
# No text after split indicates colspan
if text is None or text.strip() == "<":
if c is not None:
colspan_str = c.get('colspan')
colspan = int(colspan_str) if colspan_str else 1
c.set('colspan', str(colspan + 1))
else:
# if this is the first cell, then fall back to creating an empty cell
text = ''
else:
c = etree.SubElement(tr, tag)
c.text = text.strip()
if a:
c.set('align', a)
def _split_row(self, row, border):
""" split a row of text into list of cells. """
if border:
if row.startswith('|'):
row = row[1:]
if row.endswith('|'):
row = row[:-1]
return self._split(row)
class TableExtension(Extension):
""" Add tables to Markdown. """
def extendMarkdown(self, md):
""" Add an instance of SpanTableProcessor to BlockParser. """
md.parser.blockprocessors.register(SpanTableProcessor(md.parser, {}), 'spantable', 1000)
def makeExtension(*args, **kwargs):
return TableExtension(*args, **kwargs)

1
static/SBSA-00.key Normal file
View File

@ -0,0 +1 @@
<EFBFBD>и5цA3б?mК џ<C2A0>Ќ<>

View File

@ -28,21 +28,29 @@ svg {
transform: translateZ(0); transform: translateZ(0);
} }
td { td,
th {
border: 1px solid #111; border: 1px solid #111;
padding: 2px; padding: 2px;
min-width: 32px; min-width: 32px;
vertical-align: top;
} }
table:not(.code) td { table~table {
margin-top: 1rem;
}
table:not(.code) td,
table:not(.code) th {
padding: 2px 6px; padding: 2px 6px;
} }
table.code td { table.code td,
table.code th {
text-align: center; text-align: center;
} }
td a { .nav a {
display: block; display: block;
padding: 4px 8px; padding: 4px 8px;
} }
@ -71,7 +79,8 @@ dfn {
cursor: help; cursor: help;
} }
td>code { td>code,
th>code {
word-break: normal; word-break: normal;
} }
@ -79,7 +88,8 @@ code>a {
color: inherit; color: inherit;
} }
pre>code, .highlight { pre>code,
.highlight {
display: block; display: block;
word-break: normal; word-break: normal;
border-radius: 4px; border-radius: 4px;
@ -100,6 +110,12 @@ pre>.highlight {
margin: 0; margin: 0;
} }
.highlight>pre>code {
border: none;
margin: 0;
padding: 0;
}
pre { pre {
max-width: 100%; max-width: 100%;
overflow-x: auto; overflow-x: auto;
@ -131,7 +147,8 @@ table.nav {
padding-bottom: 1px; padding-bottom: 1px;
} }
table.nav td { table.nav td,
table.nav th {
display: inline-block; display: inline-block;
margin-right: -1px; margin-right: -1px;
margin-bottom: -1px; margin-bottom: -1px;
@ -211,7 +228,9 @@ footer>*:last-child {
margin-left: 1rem; margin-left: 1rem;
} }
.part:hover>span, .part:focus>span, .part>span:hover { .part:hover>span,
.part:focus>span,
.part>span:hover {
display: block; display: block;
} }
@ -303,9 +322,11 @@ mark {
.ata-bad { .ata-bad {
color: #f5417d; color: #f5417d;
} }
.ata-good { .ata-good {
color: #4df541; color: #4df541;
} }
.ata-ignore { .ata-ignore {
color: #f5ad41; color: #f5ad41;
} }
@ -321,7 +342,8 @@ mark {
border-top: 1px solid #fff5; border-top: 1px solid #fff5;
} }
td { td,
th {
border: 1px solid #777; border: 1px solid #777;
} }
@ -333,11 +355,12 @@ mark {
background-color: #000; background-color: #000;
} }
.highlight, img:not(.graphic) { .highlight {
filter: invert(1); filter: invert(1);
} }
a, a:visited { a,
a:visited {
text-decoration: none; text-decoration: none;
color: #f5417d color: #f5417d
} }

View File

@ -1,441 +1,3 @@
{% extends "sega.html" %} {% block title %}Ring Keychip{% endblock %} {% block body %} {% extends "sega.html" %} {% block title %}Ring Keychip{% endblock %} {% block body %}
<h1>Ring Keychip</h1> {% markdown %}{% include relative("~keychip.md") %}{% endmarkdown %}
<p> {% endblock %}
The Ring keychip is arguably simultaniously one of the most overkill while least utilised parts of the system.
On-board is a PIC microcontroller, a dedicated cryptography chip, a hardware SHA engine for authentication, and
flash storage.
</p>
<h2>Protocol</h2>
<p>
The PIC communicates with the system using a parallel bus. This bus is exposed physically on the keychip connector,
and in software can be accessed using <code>\\.\mxparallel</code>. All bus communication is encrypted using AES 128
ECB, using a different key for each data direction. Send/receive is defined from the perspective of the Ring system.
That is, the "Send" key handles data from the Ring to the keychip, and the "Receive" key handles data from the
keychip to the Ring. The initial key values are:
</p>
<h3>Initial receive key:</h3>
<pre><code>75 6f 72 61 74 6e 65 6b 61 6d 69 68 73 75 6b 75</code></pre>
<h3>Initial send key:</h3>
<pre><code>66 6E 65 6B 65 72 61 77 64 72 61 68 61 67 65 73</code></pre>
<p>
All packets are first prefixed by a command ordinal (see below), then command-specific information. The base unit of
transfer is 16 bytes due to AES 128. Unused bytes can contain anything, however mxkeychip chooses to pad using
random bytes derrived from the current system time.
</p>
<h3>Command Ordinals</h3>
<table>
<thead>
<tr>
<td>Ordinal</td>
<td>Command</td>
</tr>
</thead>
<tbody>
<tr>
<td><code>0</code></td>
<td>SetKeyS</td>
</tr>
<tr>
<td><code>1</code></td>
<td>SetKeyR</td>
</tr>
<tr>
<td><code>2</code></td>
<td>SetIv</td>
</tr>
<tr>
<td><code>3</code></td>
<td>Decrypt</td>
</tr>
<tr>
<td><code>4</code></td>
<td>Encrypt</td>
</tr>
<tr>
<td><code>5</code></td>
<td>GetAppBootInfo</td>
</tr>
<tr>
<td><code>6</code></td>
<td>EepromWrite</td>
</tr>
<tr>
<td><code>7</code></td>
<td>EepromRead</td>
</tr>
<tr>
<td><code>8</code></td>
<td>NvramWrite</td>
</tr>
<tr>
<td><code>9</code></td>
<td>NvramRead</td>
</tr>
<tr>
<td><code>10</code></td>
<td>AddPlayCount</td>
</tr>
<tr>
<td><code>11</code></td>
<td>FlashRead</td>
</tr>
<tr>
<td><code>12</code></td>
<td>FlashErase</td>
</tr>
<tr>
<td><code>13</code></td>
<td></td>
</tr>
<tr>
<td><code>14</code></td>
<td>FlashWrite</td>
</tr>
<tr>
<td><code>15</code></td>
<td></td>
</tr>
<tr>
<td><code>16</code></td>
<td></td>
</tr>
<tr>
<td><code>17</code></td>
<td></td>
</tr>
<tr>
<td><code>18</code></td>
<td></td>
</tr>
<tr>
<td><code>19</code></td>
<td></td>
</tr>
<tr>
<td><code>20</code></td>
<td>KcGetVersion</td>
</tr>
<tr>
<td><code>21</code></td>
<td>SetMainId</td>
</tr>
<tr>
<td><code>22</code></td>
<td>GetMainId</td>
</tr>
<tr>
<td><code>23</code></td>
<td>SetKeyId</td>
</tr>
<tr>
<td><code>24</code></td>
<td>GetKeyId</td>
</tr>
<tr>
<td><code>25</code></td>
<td>GetPlayCounter</td>
</tr>
</tbody>
</table>
<h3>SetKeyS</h3>
<p>Sets the "send" encryption key. The key is changed before communication of the reply.</p>
<h4>Request</h4>
<table>
<thead>
<tr>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td>A</td>
<td>B</td>
<td>C</td>
<td>D</td>
<td>E</td>
<td>F</td>
</tr>
</thead>
<tr>
<td>0</td>
<td colspan="15"><i>unused</i></td>
</tr>
<tr>
<td colspan="16">"send" encryption key</td>
</tr>
</table>
<h4>Response</h4>
<table>
<thead>
<tr>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td>A</td>
<td>B</td>
<td>C</td>
<td>D</td>
<td>E</td>
<td>F</td>
</tr>
</thead>
<tr>
<td>0</td>
<td colspan="15"><i>unused</i></td>
</tr>
</table>
<h3>SetKeyR</h3>
<p>Sets the "receive" encryption key. The key is changed before communication of the reply.</p>
<h4>Request</h4>
<table>
<thead>
<tr>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td>A</td>
<td>B</td>
<td>C</td>
<td>D</td>
<td>E</td>
<td>F</td>
</tr>
</thead>
<tr>
<td>1</td>
<td colspan="15"><i>unused</i></td>
</tr>
<tr>
<td colspan="16">"receive" encryption key</td>
</tr>
</table>
<h4>Response</h4>
<table>
<thead>
<tr>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td>A</td>
<td>B</td>
<td>C</td>
<td>D</td>
<td>E</td>
<td>F</td>
</tr>
</thead>
<tr>
<td>1</td>
<td colspan="15"><i>unused</i></td>
</tr>
</table>
<h3>SetIv</h3>
<p>Reset the game key IV to its initial value</p>
<h4>Request</h4>
<table>
<thead>
<tr>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td>A</td>
<td>B</td>
<td>C</td>
<td>D</td>
<td>E</td>
<td>F</td>
</tr>
</thead>
<tr>
<td>2</td>
<td colspan="15"><i>unused</i></td>
</tr>
</table>
<h4>Response</h4>
<table>
<thead>
<tr>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td>A</td>
<td>B</td>
<td>C</td>
<td>D</td>
<td>E</td>
<td>F</td>
</tr>
</thead>
<tr>
<td>2</td>
<td colspan="15"><i>unused</i></td>
</tr>
</table>
<h3>Decrypt</h3>
<p>Decrypt a block of data using the game key</p>
<h4>Request</h4>
<table>
<thead>
<tr>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td>A</td>
<td>B</td>
<td>C</td>
<td>D</td>
<td>E</td>
<td>F</td>
</tr>
</thead>
<tr>
<td>3</td>
<td colspan="15"><i>unused</i></td>
</tr>
<tr>
<td colspan="16">ciphertext to decrypt</td>
</tr>
</table>
<h4>Request</h4>
<table>
<thead>
<tr>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td>A</td>
<td>B</td>
<td>C</td>
<td>D</td>
<td>E</td>
<td>F</td>
</tr>
</thead>
<tr>
<td>3</td>
<td colspan="15"><i>unused</i></td>
</tr>
<tr>
<td colspan="16">decrypted plaintext</td>
</tr>
</table>
<h3>Encrypt</h3>
<p>Encrypt a block of data using the game key</p>
<h4>Request</h4>
<table>
<thead>
<tr>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td>A</td>
<td>B</td>
<td>C</td>
<td>D</td>
<td>E</td>
<td>F</td>
</tr>
</thead>
<tr>
<td>4</td>
<td colspan="15"><i>unused</i></td>
</tr>
<tr>
<td colspan="16">plaintext to encrypt</td>
</tr>
</table>
<h4>Request</h4>
<table>
<thead>
<tr>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td>A</td>
<td>B</td>
<td>C</td>
<td>D</td>
<td>E</td>
<td>F</td>
</tr>
</thead>
<tr>
<td>4</td>
<td colspan="15"><i>unused</i></td>
</tr>
<tr>
<td colspan="16">encrypted ciphertext</td>
</tr>
</table>
{% endblock %}

View File

@ -0,0 +1,367 @@
# Ring Keychip
The Ring keychip is arguably simultaniously one of the most overkill while least utilised parts of the system. On-board is a PIC microcontroller, a dedicated cryptography chip, a hardware SHA engine for authentication, and flash storage.
## Protocol
The PIC communicates with the system using a parallel bus. This bus is exposed physically on the keychip connector, and in software can be accessed using `\\.\mxparallel`. All bus communication is encrypted using AES 128 ECB, using a different key for each data direction. Send/receive is defined from the perspective of the Ring system. That is, the "Send" key handles data from the Ring to the keychip, and the "Receive" key handles data from the keychip to the Ring. The initial key values are:
### Initial receive key:
```
75 6f 72 61 74 6e 65 6b 61 6d 69 68 73 75 6b 75
```
### Initial send key:
```
66 6E 65 6B 65 72 61 77 64 72 61 68 61 67 65 73
```
All packets are first prefixed by a command ordinal (see below), then command-specific information. The base unit of transfer is 16 bytes due to AES 128. Unused bytes can contain anything, however mxkeychip chooses to pad using random bytes derrived from the current system time.
## Storage locations
### EEPROM
Stores entries detailing the tracedata metadata. There is a copy of the structure at 0x000 and a duplicate at 0x100. Each structure is comprised of 16 entries, where each entry first has a 4 byte CRC, then 12 bytes currently unknown.
### NVRAM
| Start | End | Content |
| ---------------- | ---------------- | ------------------ |
| 1800<sub>h</sub> | 1bff<sub>h</sub> | billing public key |
| 1c00<sub>h</sub> | 1fff<sub>h</sub> | ca certificate |
Each block begins with a 4 byte integer indicating the length of the data following it (up to 1020 bytes).
### Flash
| Start | End | Content |
| ----------------- | ----------------- | ------------------ |
| 00000<sub>h</sub> | 0ffff<sub>h</sub> | tracedata sector 1 |
| 10000<sub>h</sub> | 1ffff<sub>h</sub> | tracedata sector 2 |
| 20000<sub>h</sub> | 2ffff<sub>h</sub> | tracedata sector 3 |
| 30000<sub>h</sub> | 3ffff<sub>h</sub> | tracedata sector 4 |
| 40000<sub>h</sub> | 4ffff<sub>h</sub> | tracedata sector 5 |
| 50000<sub>h</sub> | 5ffff<sub>h</sub> | tracedata sector 6 |
| 60000<sub>h</sub> | 6ffff<sub>h</sub> | tracedata sector 7 |
| 70000<sub>h</sub> | 70fff<sub>h</sub> | nvram0 |
| 71000<sub>h</sub> | 71fff<sub>h</sub> | nvram1 |
| 72000<sub>h</sub> | 72fff<sub>h</sub> | nvram2 |
| 73000<sub>h</sub> | 73fff<sub>h</sub> | nvram3 |
| 74000<sub>h</sub> | 74fff<sub>h</sub> | nvram4 |
| 75000<sub>h</sub> | 75fff<sub>h</sub> | nvram5 |
| 76000<sub>h</sub> | 76fff<sub>h</sub> | nvram6 |
| 77000<sub>h</sub> | 77fff<sub>h</sub> | nvram7 |
| 78000<sub>h</sub> | 78fff<sub>h</sub> | nvram8 |
| 79000<sub>h</sub> | 79fff<sub>h</sub> | nvram9 |
| 7a000<sub>h</sub> | 7a10b<sub>h</sub> | billing info |
| 7b000<sub>h</sub> | 7b10b<sub>h</sub> | billing info dup |
Each tracedata sector begins with a 128-byte bitfield (1024 bits) indicating which blocks in the sector have been populated. Following this are 1022 blocks, 64 bytes each. (the last two of the theorhetical 1024 would overlap into the next sector). Each slot is individually encrypted with DES ECB, using key `4D77F1748D6D1094`.
This is implemented via the `AT25DF041A` 4Mbit flash chip on the keychip PCB. The maximum address is therefore 7ffff<sub>h</sub>.
## Command Ordinals
| Ordinal | Command |
| ------- | --------------------------------- |
| `0` | [SetKeyS](#setkeys) |
| `1` | [SetKeyR](#setkeyr) |
| `2` | [SetIv](#setiv) |
| `3` | [Decrypt](#decrypt) |
| `4` | [Encrypt](#encrypt) |
| `5` | [GetAppBootInfo](#getappbootinfo) |
| `6` | [EepromWrite](#eepromwrite) |
| `7` | [EepromRead](#eepromread) |
| `8` | [NvramWrite](#nvramwrite) |
| `9` | [NvramRead](#nvramread) |
| `10` | [AddPlayCount](#addplaycount) |
| `11` | [FlashRead](#flashread) |
| `12` | [FlashErase](#flasherase) |
| `13` | ? |
| `14` | [FlashWrite](#flashwrite) |
| `15` | ? |
| `16` | ? |
| `17` | ? |
| `18` | ? |
| `19` | ? |
| `20` | [KcGetVersion](#kcgetversion) |
| `21` | [SetMainId](#setmainid) |
| `22` | [GetMainId](#getmainid) |
| `23` | [SetKeyId](#setkeyid) |
| `24` | [GetKeyId](#getkeyid) |
| `25` | [GetPlayCounter](#getplaycounter) |
## SetKeyS
Sets the "send" encryption key. The key is changed before communication of the reply.
| | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F |
| ------- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| Send | 0 | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * |
| ^ | key | < | < | < | < | < | < | < | < | < | < | < | < | < | < | < |
| |
| Receive | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * |
| Variable | |
| -------- | ------------ |
| key | The send key |
## SetKeyR
Sets the "receive" encryption key. The key is changed before communication of the reply.
| | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F |
| ------- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| Send | 1 | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * |
| ^ | key | < | < | < | < | < | < | < | < | < | < | < | < | < | < | < |
| |
| Receive | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * |
| Variable | |
| -------- | --------------- |
| key | The receive key |
## SetIv
Reset the game key IV to its initial value
| | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F |
| ------- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| Send | 2 | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * |
| |
| Receive | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * |
## Decrypt
Decrypt a block of data using the game key
| | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F |
| ------- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| Send | 3 | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * |
| | ct | < | < | < | < | < | < | < | < | < | < | < | < | < | < | < |
| |
| Receive | pt | < | < | < | < | < | < | < | < | < | < | < | < | < | < | < |
| Variable | |
| -------- | ------------------------ |
| ct | The encrypted ciptertext |
| pt | The decrypted plaintext |
## Encrypt
Encrypt a block of data using the game key
| | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F |
| ------- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| Send | 4 | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * |
| ^ | pt | < | < | < | < | < | < | < | < | < | < | < | < | < | < | < |
| |
| Receive | ct | < | < | < | < | < | < | < | < | < | < | < | < | < | < | < |
| Variable | |
| -------- | ------------------------ |
| pt | The decrypted plaintext |
| ct | The encrypted ciptertext |
## GetAppBootInfo
Request the AppBoot structure from the keychip. The AppBoot structure is 256 bytes, thus requires 16 packets.
| | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F |
| ------- | ------------------- | --- | --- | -------- | --------------- | --- | --- | --- | ------- | --- | --- | --- | ------ | ---------- | ----------- | --- |
| Send | 5 | 0 | * | * | * | * | * | * | * | * | * | * | * | * | * | * |
| |
| Receive | CRC | < | < | < | Format | < | < | < | Game ID | < | < | < | Region | Model type | System flag | ? |
| | Platform ID | < | < | DVD flag | Network address | < | < | < | | | | | | | | |
| | | | | | | | | | | | | | | | | |
| | _206 bytes padding_ | < | < | < | < | < | < | < | < | < | < | < | < | < | < | < |
| | | | | | | | | | | | | | | | | |
| ^ | Seed | < | < | < | < | < | < | < | < | < | < | < | < | < | < | < |
| Variable | |
| --------------- | ----------------------------------------------------- |
| CRC | CRC32 checksum of the reamining 252 bytes |
| Format | AppBoot format type. The described type is format `1` |
| Game ID | Four character game ID. For example, `SBTR` |
| Region | Region bitmask |
| Model type | Model type this keychip is for |
| System flag | System flag bitmask |
| DVD flag | Is update from DVD allowed |
| Network address | Permitted subnet. Fourth octet is null |
**Note:** I'm not sure what the `0` is for here. Surprises await for anything other than `0` :D
## EepromWrite
| | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F |
| ------- | ---- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| Send | 6 | reg | * | * | * | * | * | * | * | * | * | * | * | * | * | * |
| ^ | data | < | < | < | < | < | < | < | < | < | < | < | < | < | < | < |
| |
| Receive | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * |
| Variable | |
| -------- | --------------------------------------------------------------------- |
| reg | EEPROM register to write. EEPROM is divided into 16x16 byte registers |
| data | The data to write |
## EepromRead
| | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F |
| ------- | ---- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| Send | 7 | reg | * | * | * | * | * | * | * | * | * | * | * | * | * | * |
| |
| Receive | data | < | < | < | < | < | < | < | < | < | < | < | < | < | < | < |
| Variable | |
| -------- | -------------------------------------------------------------------- |
| reg | EEPROM register to read. EEPROM is divided into 16x16 byte registers |
| data | The data read |
## NvramWrite
| | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F |
| ------- | ---- | ---- | --- | ------ | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| Send | 8 | addr | < | blocks | * | * | * | * | * | * | * | * | * | * | * | * |
| ^ | data | < | < | < | < | < | < | < | < | < | < | < | < | < | < | < |
| |
| Receive | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * |
| Variable | |
| -------- | ----------------------------------------------------- |
| addr | Absolute offset in NVRAM to write at |
| blocks | The number of 16 bytes blocks to write |
| data | The data to write. This will require `blocks` packets |
## NvramRead
| | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F |
| ------- | ---- | ---- | --- | ------ | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| Send | 8 | addr | < | blocks | * | * | * | * | * | * | * | * | * | * | * | * |
| |
| Receive | data | < | < | < | < | < | < | < | < | < | < | < | < | < | < | < |
| Variable | |
| -------- | ------------------------------------------------- |
| addr | Absolute offset in NVRAM to read at |
| blocks | The number of 16 bytes blocks to read |
| data | The read data. This will require `blocks` packets |
## AddPlayCount
Increment the playcount value
| | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F |
| ------- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| Send | 10 | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * |
| |
| Receive | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * |
## FlashRead
| | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F |
| ---- | --- | ---- | --- | --- | ------- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| Send | 11 | addr | < | < | # bytes | < | < | * | * | * | * | * | * | * | * | * |
| Variable | |
| -------- | ----------------------------------- |
| addr | Absolute offset in flash to read at |
| # bytes | The number of bytes to read |
**Important:** Data is returned as raw bytes transmitted over parallel, without encryption or packet encapsulation.
## FlashErase
Honestly not actually sure if this is an erase or what. Still working on it!
| | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F |
| ------- | --- | ---- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| Send | 12 | addr | < | < | * | * | * | * | * | * | * | * | * | * | * | * |
| |
| Receive | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * |
## FlashWrite
| | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F |
| ------- | --- | ---- | --- | --- | ------- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| Send | 14 | addr | < | < | # bytes | < | < | * | * | * | * | * | * | * | * | * |
| |
| Receive | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * |
| Variable | |
| -------- | ------------------------------------ |
| addr | Absolute offset in flash to write at |
| # bytes | The number of bytes to write |
**Important:** Data is transmitted immediately following transmission of the first packet. Is it transmitted as raw bytes over parallel, without encryption or packet encapsulation. The response packet is transmitted after # bytes have been received in this manner.
## KcGetVersion
| | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F |
| ------- | ------- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| Send | 20 | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * |
| |
| Receive | version | < | * | * | * | * | * | * | * | * | * | * | * | * | * | * |
| Variable | |
| -------- | --------------------------------------------------------------- |
| version | Two-byte version number. Keychips are observed to return 01,04. |
## SetMainId
Set the Main ID on the keychip, if not already set.
| | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F |
| ------- | ------- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| Send | 21 | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * |
| ^ | Main ID | < | < | < | < | < | < | < | < | < | < | < | < | < | < | < |
| |
| Receive | err | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * |
| Variable | |
| -------- | ------------------------------------------------------------------ |
| Main ID | The new keychip Main ID. No validation is performed of this value. |
| err | ? (0?) when succesful, FF<sub>h</sub> otherwise |
## GetMainId
Get the Main ID on the keychip.
| | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F |
| ------- | ------- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| Send | 22 | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * |
| |
| Receive | Main ID | < | < | < | < | < | < | < | < | < | < | < | < | < | < | < |
| Variable | |
| -------- | ------------------- |
| Main ID | The keychip Main ID |
## SetKeyId
Set the Key ID on the keychip, if not already set.
| | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F |
| ------- | ------ | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| Send | 23 | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * |
| ^ | Key ID | < | < | < | < | < | < | < | < | < | < | < | < | < | < | < |
| |
| Receive | err | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * |
| Variable | |
| -------- | ------------------------------------------------------------- |
| Key ID | The new keychip ID. No validation is performed of this value. |
| err | ? (0?) when succesful, FF<sub>h</sub> otherwise |
## GetKeyId
Get the Key ID on the keychip.
| | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F |
| ------- | ------ | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| Send | 24 | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * |
| |
| Receive | Key ID | < | < | < | < | < | < | < | < | < | < | < | < | < | < | < |
| Variable | |
| -------- | -------------- |
| Key ID | The keychip ID |
## GetPlayCounter
Get the current playcount value
| | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F |
| ------- | --------- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| Send | 24 | * | * | * | * | * | * | * | * | * | * | * | * | * | * | * |
| |
| Receive | playcount | < | < | < | * | * | * | * | * | * | * | * | * | * | * | * |
| Variable | |
| --------- | --------------------------- |
| playcount | The billing playcount value |

View File

@ -410,26 +410,26 @@
<tr class="pad-row"></tr> <tr class="pad-row"></tr>
<tr> <tr>
<td>0800</td> <td>0800</td>
<td>Unknown Error.</td> <td>Unexpected Failure</td>
<td>Generic error code. Check the event log for details</td> <td>Generic error code. Check the event log for details</td>
<td>ALLNetProc</td> <td>ALLNetProc</td>
</tr> </tr>
<tr> <tr>
<td>0801</td> <td>0801</td>
<td>Unknown Error.</td> <td>Main board Malfunctioning</td>
<td>Generic error code. Check the event log for details</td> <td>RTC access failed</td>
<td>ALLNetProc</td> <td>ALLNetProc</td>
</tr> </tr>
<tr> <tr>
<td>0802</td> <td>0802</td>
<td>Unknown Error.</td> <td>Main board Malfunctioning</td>
<td>Generic error code. Check the event log for details</td> <td>File access failed</td>
<td>ALLNetProc</td> <td>ALLNetProc</td>
</tr> </tr>
<tr> <tr>
<td>0803</td> <td>0803</td>
<td>Unknown Error.</td> <td>Unknown Error.</td>
<td>Generic error code. Check the event log for details</td> <td>Invalid billing config file</td>
<td>ALLNetProc</td> <td>ALLNetProc</td>
</tr> </tr>
<tr class="pad-row"></tr> <tr class="pad-row"></tr>
@ -665,7 +665,7 @@
<tr> <tr>
<td>8111</td> <td>8111</td>
<td>ALL.Net System error(REG)</td> <td>ALL.Net System error(REG)</td>
<td></td> <td>ALL.Net rejected the keychip</td>
<td>ALLNetProc</td> <td>ALLNetProc</td>
</tr> </tr>
<tr> <tr>
@ -1458,4 +1458,29 @@
</tbody> </tbody>
</table> </table>
<!-- From nxAuth -->
0390 - nxAuth is not working
0391 - Not Set Valid App Property
0392 - Not Found Valid System File
0393 - Not Found Aime Device Firmware File
0700 - Wrong Coin Setting
0701 - Wrong Cabinet Setting
0702 - Unexpected SelectMenu Failure
0703 - Storage Device Malfunctioning
0704 - Storage Device Malfunctioning
0705 - Game Program Not Acceptable
0706 - Storage Device Malfunctioning
0707 - Game Program Not Found
0708 - Storage Device Malfunctioning
0912 - Unexpected Game Program Failure
0913 - Game Program Not Found on Storage Device
6501 - Aime Card Reader Not Found
6502 - Aime Card Reader Unknown Error
6503 - Aime Read failed
6504 - Illegal Card Found
6505 - Occupied Aime Found
6506 - Aime Card Reader Firmware Update failed
6507 - Aime Card Reader Initialize failed
6508 - Aime DB Access failed
{% endblock %} {% endblock %}

View File

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

View File

@ -0,0 +1,47 @@
{% extends "sega.html" %}
{% block title %}geminifs{% endblock %}
{% block body %}
<h1>geminifs</h1>
<p>geminifs is an incredibly interesting driver for merging two volumes into a single mounted drive. It is comprised of
<code>geminifs.exe</code> and <code>geminifs.sys</code>. The former is the interface programs are expected to use,
and the latter is the driver itself.</p>
<h2><code>geminifs.exe</code> help</h2>
<pre><code>
Usage: geminifs.exe <original-image-path> <patch-image-path> <mount-drive> [cache=on|off] [reparse=on|off]
geminifs.exe -u <dismount-drive>
ex1: mount drive
> geminifs.exe O:\ P:\ X:
> geminifs.exe O:\ P:\ X: cache=off
> geminifs.exe O:\ P:\ X: cache=on reparse=on
ex2: dismount drive
> geminifs.exe -u X:
Options:
cache=on
Enable geminifs file-cache.
cache=off
Disable geminifs file-cache. File access memory consumption is a
minimum, have to sacrifice access speed.
reparse=on
Enable the open file as a 'Reparse Point' to the original path name.
reparse=off
When you open a file mount, the geminifs driver open the file and
read access indirectly.
The geminifs driver that can change the value of these options for each
volume to be mounted (by using these command-line). If no value is specified,
each of the following registry value is used.
[Service-Key]\Parameters\FsCache REG_DWORD 0:OFF 1:ON
[Service-Key]\Parameters\FsOpenFileAsReparse REG_DWORD 0:OFF 1:ON
If these are not specified, the default value (cache=on, reparse=on) will be
used.
</code></pre>
{% endblock %}

View File

@ -105,7 +105,7 @@ mxshellexecute.exe -->
stroke="currentColor"></rect> stroke="currentColor"></rect>
<text x="250" y="300" fill="currentColor" dominant-baseline="middle" text-anchor="middle" <text x="250" y="300" fill="currentColor" dominant-baseline="middle" text-anchor="middle"
font-family="monospace"> font-family="monospace">
s:\mxinstaller.exe -cmdport 40102 -bindport 40103 s:\mxinstaller.exe -cmdport 40102 -binport 40103
</text> </text>
</a> </a>

View File

@ -137,4 +137,11 @@
</table> </table>
<p>These errors are also reported into the Application event log, under the source <code>mxstartup</code>.</p> <p>These errors are also reported into the Application event log, under the source <code>mxstartup</code>.</p>
<h2>TrueCrypt Commands</h2>
<li>
<ul><code>TrueCrypt /k Z:\UpdateKeyFile /v [...] \Device\Harddisk%d\Partition%d /l W: /s /q</code></ul>
<ul><code>TrueCrypt /k Z:\UpdateKeyFile /v Z:\Minint\System32\DEFAULT_DRIVERS /l V: /s /q</code></ul>
<ul><code>TrueCrypt /p segahardpassword /k Z:\SystemKeyFile /v C:\System\Execute\System /l S: /w /s /q</code></ul>
</li>
{% endblock %} {% endblock %}

View File

@ -0,0 +1,51 @@
# Ring Series ASSY TEST
## What?
This is the testing tool used for self-checking RingWide, RingEdge and RingEdge2 PC systems. The automatic operation
of the tool is controlled by a number of configuration files stored on a locally attached USB storage device.
## Known Versions
| Version | Dumped? | Notes |
| ------- | ------- | ------------------------------- |
| 1.02 | No | |
| 1.04 | Yes | Only prefactorytest.exe changed |
| 1.10 | Yes | |
| 1.13 | Yes | |
| 1.16 | Yes | |
## How can I help?
The factory testing tool is installed into the `patch1` partition. Very few Ring games utilise this partition, and the test tool is not uninstalled for some reason, meaning we can extract it from the drive!
To start with, we need to locate the [SEGA Boot Record]({{ROOT}}/sega/misc/partition.html) and check if we have SBSA present in `patch1`. While this could be done totally manually, it's a lot easier to use disk forensics software as it will handle partition identification for us. Pictured is FTK Imager (free), however Autopsy (free) and X-Ways (not free) are all viable options.
![]({{ROOT}}/images/factorytest-sbr.png)
Starting at 0x5c0, we can begin to read out the partition information. First is the game ID, `SBSA`. Following that, is the install timestamp. This is
```c
struct {
uint16_t year;
uint8_t month;
uint8_t day;
uint8_t hour;
uint8_t minute;
uint8_t second;
uint8_t rsv;
}
```
The pictured example is `02 Apr 2010, 18:26:26`. The next four bytes are the version number. `04 00 01 00` here indicates version `1.04`.
If the version here is marked as dumped in the above table, then thank you for checking, but there's nothing new to find here! If it's not, then congratulations you've just found a new version! Let's get to dumping it!
The easiest way to proceed at this point is the extract the entire partition as a file. The partition we want is the second 2GiB partition. This is partition 7, however your software may have numbered it as partition 5.
The file we have created is a TrueCrypt volume. There is no password, and the keyfile is [`88D835E64133D13F6DBAA0FF9413AC9F`]({{ROOT}}/static/SBSA-00.key) (click to download).
![]({{ROOT}}/images/factorytest-tc1.png)
![]({{ROOT}}/images/factorytest-tc2.png)
![]({{ROOT}}/images/factorytest-tc3.png)
The files can now be copied from the chosen drive letter. **Please share them!**