Card ID docs

This commit is contained in:
Bottersnike 2021-12-28 20:21:53 +00:00
parent c8e7fc5d86
commit 20affcfa63
4 changed files with 517 additions and 9 deletions

488
cardid.html Normal file
View File

@ -0,0 +1,488 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>e-Amusement API</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<table>
<tr>
<td><a href=".">Contents</a></td>
<td><a href="transport.html">Transport layer</a></td>
<td><a href="packet.html">Packet format</a></td>
<td><a href="protocol.html">Application Protocol</a></td>
</tr>
</table>
<h1>Card ID generation</h1>
<details>
<summary>I'm just here for code.</summary>
<p>Fair. My intent with these pages is to describe things in enough detail that they should be simple to
implement yourself, but this is one of those things that's quite easy to just drop in some pre-made code
for. My local implementation is in python, so that's all you're getting :). As a free bonus, have some test
cases too. It's not great code by any stretch, and it liberally uses assertions rather than proper
exceptions, but it should be a good enough starting point for your own version.</p>
<pre><code>import binascii
from Crypto.Cipher import DES3
KEY = b"" # Check the <a href="#des">DES section</a> for this
_KEY = bytes(i * 2 for i in KEY) # Preprocess the key
ALPHABET = "0123456789ABCDEFGHJKLMNPRSTUWXYZ"
def enc_des(uid):
cipher = DES3.new(_KEY, DES3.MODE_CBC, iv=b'\0' * 8)
return cipher.encrypt(uid)
def dec_des(uid):
cipher = DES3.new(_KEY, DES3.MODE_CBC, iv=b'\0' * 8)
return cipher.decrypt(uid)
def checksum(data):
chk = sum(data[i] * (i % 3 + 1) for i in range(15))
while chk > 31:
chk = (chk >> 5) + (chk & 31)
return chk
def pack_5(data):
data = "".join(f"{i:05b}" for i in data)
if len(data) % 8 != 0:
data += "0" * (8 - (len(data) % 8))
return bytes(int(data[i:i+8], 2) for i in range(0, len(data), 8))
def unpack_5(data):
data = "".join(f"{i:08b}" for i in data)
if len(data) % 5 != 0:
data += "0" * (5 - (len(data) % 5))
return bytes(int(data[i:i+5], 2) for i in range(0, len(data), 5))
def to_konami_id(uid):
assert len(uid) == 16, "UID must be 16 bytes"
if uid.upper().startswith("E004"):
card_type = 1
elif uid.upper().startswith("0"):
card_type = 2
else:
raise ValueError("Invalid UID prefix")
kid = binascii.unhexlify(uid)
assert len(kid) == 8, "ID must be 8 bytes"
out = bytearray(unpack_5(enc_des(kid[::-1]))[:13]) + b'\0\0\0'
out[0] ^= card_type
out[13] = 1
for i in range(1, 14):
out[i] ^= out[i - 1]
out[14] = card_type
out[15] = checksum(out)
return "".join(ALPHABET[i] for i in out)
def to_uid(konami_id):
if konami_id[14] == "1":
card_type = 1
elif konami_id[14] == "2":
card_type = 2
else:
raise ValueError("Invalid ID")
assert len(konami_id) == 16, f"ID must be 16 characters"
assert all(i in ALPHABET for i in konami_id), "ID contains invalid characters"
card = [ALPHABET.index(i) for i in konami_id]
assert card[11] % 2 == card[12] % 2, "Parity check failed"
assert card[13] == card[12] ^ 1, "Card invalid"
assert card[15] == checksum(card), "Checksum failed"
for i in range(13, 0, -1):
card[i] ^= card[i - 1]
card[0] ^= card_type
card_id = dec_des(pack_5(card[:13])[:8])[::-1]
card_id = binascii.hexlify(card_id).decode().upper()
if card_type == 1:
assert card_id[:4] == "E004", "Invalid card type"
elif card_type == 2:
assert card_id[0] == "0", "Invalid card type"
return card_id
if __name__ == "__main__":
assert to_konami_id("0000000000000000") == "007TUT8XJNSSPN2P", "To KID failed"
assert to_uid("007TUT8XJNSSPN2P") == "0000000000000000", "From KID failed"
assert to_uid(to_konami_id("000000100200F000")) == "000000100200F000", "Roundtrip failed"
</code></pre>
</details>
<p>e-Amusement cards use 16 digit IDs. KONAMI IDs are also 16 digits. Are they related? Yes! In fact, KONAMI IDs are
derived from the ID stored on the e-Amusement card.</p>
<p>KONAMI IDs have an alphabet of <code>0123456789ABCDEFGHJKLMNPRSTUWXYZ</code> (note that <code>IOQV</code> are
absent), whereas e-A IDs (yeah I'm not typing that out every time) have an alphabet of
<code>0123456789ABCDEF</code> (hex). It stands to reason then that there's additional information present in
KONAMI IDs, as they are the same length, but can hold a greater density of information. That intuition would be
correct.
</p>
<h2 id="konami">Converting KONAMI IDs to e-Amusement IDs</h2>
<p>Let's take a look at the format of KONAMI IDs. The first step before we can do anything is to convert it from a
string to a series of integers. Each byte is replaced with its index in the alphabet, giving us 16 values
ranging from 0 through 31. These bytes has the following meanings:</p>
<table class="code">
<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>10</td>
<td>11</td>
<td>12</td>
<td>13</td>
<td>14</td>
<td>15</td>
</tr>
</thead>
<tr>
<td colspan="13">e-Amusement ID</td>
<td>Check byte</td>
<td>Card type</td>
<td>Checksum</td>
</tr>
</table>
<p>Due to how IDs are constructed, there are a number of checks we can perform to validate an ID:</p>
<ul>
<li>Parity check: <code>[11] % 2 == [12] % 2</code></li>
<li>Encoding check: <code>[13] == [12] ^ 1</code></li>
<li>Checksum: <code>[15] == <a href="#checksum">checksum([0..14])</a></code></li>
<li>Post-decoding, FeliCa cards start with <code>0</code> and magnetic strip cards start with <code>E004</code>.
</li>
<li>Card type: <code>[14] == 1</code> (magnetic stripe) or <code>[14] == 2</code> (FeliCa)</li>
</ul>
<p>To decrypt a KONAMI ID, at a high level we must:</p>
<ul>
<li>Remove the XOR encoding</li>
<li>5-pack the ID</li>
<li>Decrypt the packed ID</li>
<li>Reverse the bytes</li>
<li>Convert to upper-case hex</li>
</ul>
<p>As we'll see in the next section, card IDs have an XOR pass performed. This is what allows for the above encoding
check, but we must remove it before we can begin decoding. A line of code speaks a thousand words, so have
three:</p>
<pre><code>for i from 13 to 1 inclusive:
card[i] ^= card[i - 1]
card[0] ^= card_type</code></pre>
<p id="packing">The values in the <code>e-Amusement ID</code> field above will all maximally be 31
(<code>0b11111</code>) therefore before we perform the decryption step we first densly pack these 5-bit
integers. That is, <code>0b11111 0b00000 0b11111</code> would be packed to <code>0b11111000 0b00111110</code>.
This value will be 8 bytes long. We can now <a href="#des">decrypt it</a>, reverse it, and convert to hex.
</p>
<details>
<summary>Implementing 5-bit packing</summary>
<p>In most languages, implementing a packer can be done one of two ways. The approach chosen by Bemani is to
first create a table of every individual bit (each stored in a whole byte!), then iterate through the bits
ORing them together. This is a simple but wasteful implementation. The other approach is to use a buffer
byte and slowly shift values in, tracking how many bits are stored in the buffer byte, and performing
different actions depending on how many bits this is. For both packing and unpacking this requires three
cases. It's somewhat more complex, but less wasteful in terms of memory usage. Pick your poison I suppose.
</p>
<details>
<summary>In <i>most</i> languages?</summary>
<p>Haha well you see we can actually cheat and use string manipulation. Wasteful? Incredibly. Efficient? Not
at all. Quick and easy? Yup!</p>
<pre><code>def pack_5(data):
data = "".join(f"{i:05b}" for i in data)
if len(data) % 8 != 0:
data += "0" * (8 - (len(data) % 8))
return bytes(int(data[i:i+8], 2) for i in range(0, len(data), 8))
def unpack_5(data):
data = "".join(f"{i:08b}" for i in data)
if len(data) % 5 != 0:
data += "0" * (5 - (len(data) % 5))
return bytes(int(data[i:i+5], 2) for i in range(0, len(data), 5))</code></pre>
<p>If your language of choice allows this, and you don't care for efficiency, this can be a great time-saver
towards get something working. Truth be told my local implementation originally used the Bemani method
(it was a line-for-line port, after all), switched to the second method, then I opted for this hacky
string method in the name of code clarity.</p>
</details>
</details>
<h2 id="eaid">Converting e-Amusement IDs to KONAMI IDs</h2>
<p>This is mostly the above process, in reverse, but we need to make sure to populate some of the extra check bytes.
</p>
<p>Before we start, we need to make sure we have a valid card! FeliCa cards (type <code>2</code>) will begin with a
single null nibble, and magnetic stripe cards (type <code>1</code>) with the word <code>E004</code>. We then
parse the entire ID as a hex string, giving us an 8-byte value.</p>
<p>This value is reversed, and <a href="#des">encrypted</a>. After encryption, we need to unpack it from it's
5-packed format. This is the same process as <a href="#packing">unpacking</a>, but reversed. The unpacked data
can be ambiguous in length. It's 13 bytes. If your unpacker produces a 14th byte, it'll be null and can be
discarded.</p>
<p>We pad the 13 bytes with 3 extra null bytes, then apply our checks to the ID:</p>
<pre><code>card[0] ^= card_type
card[13] = 1
for i from 0 to 13 inclusive:
card[i + 1] ^= card[i]
card[14] = card_type
card[15] = <a href="#checksum">checksum(card)</a></code></pre>
<p>This leaves us with 16 values ranging from 0 to 31, which we apply as indecies to our alphabet to produce the
final ID.</p>
<h2 id="checksum">Checksums</h2>
<p>As if the encryption and XOR wasn't enough, card IDs also contain a checksum to make <i>absolutely</i> sure the
card is valid. I could explain in words how the checksum works, but that's probably not very useful. Have a
pseudocode snippet instead:</p>
<pre><code>checksum(bytes):
chk = 0
for i from 0 to 14 inclusive:
chk += bytes[i] * (i % 3 + 1)
while chk > 31:
chk = (chk >> 5) + (chk & 31)
return chk</code></pre>
<h2 id="des">The DES scheme used</h2>
<p>For whatever reason, Bemani decided that IDs should be encrypted. Thankfully however they used triple DES, which
almost certainly has an existing implementation in your language of choice. It is triple DES, in CBC mode, with
a totally null <code>IV</code>. The key is quite easy to find if you hit the right binaries with
<code>strings</code>. <span style="color: white">Alternatively, check the source of this page.</span> The key
contains characters that are all within the ASCII range. Before we can use it with DES, the value of every byte
needs doubled. This was presumably done to give the values more range, but I sincerely doubt it adds any
additional security.
</p>
<!-- Oh hello there. "?I'llB2c.YouXXXeMeHaYpy!" (without the quotes). Don't forget to double every byte before using
the key, giving us an actual key of 7e924ed8d88464c65cb2deeab0b0b0ca9aca90c2b2e0f242. -->
<details>
<summary>I'm curious how Bemani implemented this in their own code!</summary>
<p>Curiosity is a great thing. Unfortunately, this is code that is implement within the game specific DLL files.
If you happen to have SDXV 4 in front of you too, head over over to <code>soundvoltex.dll:0x1027316f</code>
and you should see everything you need.
</p>
<p>As part of breaking down how this all works, I produced a more or less line-for-line Python port of the game
code, for testing, validation, etc.. It's not especially pretty, but should give you an idea of how it works
under the hood. One interesting observation is that it looks like the initial and final permutation steps
were inlined. It's also possible that they did the whole thing with macros rather than inline functions.
Either way, my python port didn't do any cleaning up, because we can just use a DES library.</p>
<details>
<summary>Show me that!</summary>
<pre><code>DES_KEYMAP = [
[0x02080008, 0x02082000, 0x00002008, 0x00000000, 0x02002000, 0x00080008, 0x02080000, 0x02082008, 0x00000008, 0x02000000, 0x00082000, 0x00002008, 0x00082008, 0x02002008, 0x02000008, 0x02080000, 0x00002000, 0x00082008, 0x00080008, 0x02002000, 0x02082008, 0x02000008, 0x00000000, 0x00082000, 0x02000000, 0x00080000, 0x02002008, 0x02080008, 0x00080000, 0x00002000, 0x02082000, 0x00000008, 0x00080000, 0x00002000, 0x02000008, 0x02082008, 0x00002008, 0x02000000, 0x00000000, 0x00082000, 0x02080008, 0x02002008, 0x02002000, 0x00080008, 0x02082000, 0x00000008, 0x00080008, 0x02002000, 0x02082008, 0x00080000, 0x02080000, 0x02000008, 0x00082000, 0x00002008, 0x02002008, 0x02080000, 0x00000008, 0x02082000, 0x00082008, 0x00000000, 0x02000000, 0x02080008, 0x00002000, 0x00082008],
[0x08000004, 0x00020004, 0x00000000, 0x08020200, 0x00020004, 0x00000200, 0x08000204, 0x00020000, 0x00000204, 0x08020204, 0x00020200, 0x08000000, 0x08000200, 0x08000004, 0x08020000, 0x00020204, 0x00020000, 0x08000204, 0x08020004, 0x00000000, 0x00000200, 0x00000004, 0x08020200, 0x08020004, 0x08020204, 0x08020000, 0x08000000, 0x00000204, 0x00000004, 0x00020200, 0x00020204, 0x08000200, 0x00000204, 0x08000000, 0x08000200, 0x00020204, 0x08020200, 0x00020004, 0x00000000, 0x08000200, 0x08000000, 0x00000200, 0x08020004, 0x00020000, 0x00020004, 0x08020204, 0x00020200, 0x00000004, 0x08020204, 0x00020200, 0x00020000, 0x08000204, 0x08000004, 0x08020000, 0x00020204, 0x00000000, 0x00000200, 0x08000004, 0x08000204, 0x08020200, 0x08020000, 0x00000204, 0x00000004, 0x08020004],
[0x80040100, 0x01000100, 0x80000000, 0x81040100, 0x00000000, 0x01040000, 0x81000100, 0x80040000, 0x01040100, 0x81000000, 0x01000000, 0x80000100, 0x81000000, 0x80040100, 0x00040000, 0x01000000, 0x81040000, 0x00040100, 0x00000100, 0x80000000, 0x00040100, 0x81000100, 0x01040000, 0x00000100, 0x80000100, 0x00000000, 0x80040000, 0x01040100, 0x01000100, 0x81040000, 0x81040100, 0x00040000, 0x81040000, 0x80000100, 0x00040000, 0x81000000, 0x00040100, 0x01000100, 0x80000000, 0x01040000, 0x81000100, 0x00000000, 0x00000100, 0x80040000, 0x00000000, 0x81040000, 0x01040100, 0x00000100, 0x01000000, 0x81040100, 0x80040100, 0x00040000, 0x81040100, 0x80000000, 0x01000100, 0x80040100, 0x80040000, 0x00040100, 0x01040000, 0x81000100, 0x80000100, 0x01000000, 0x81000000, 0x01040100],
[0x04010801, 0x00000000, 0x00010800, 0x04010000, 0x04000001, 0x00000801, 0x04000800, 0x00010800, 0x00000800, 0x04010001, 0x00000001, 0x04000800, 0x00010001, 0x04010800, 0x04010000, 0x00000001, 0x00010000, 0x04000801, 0x04010001, 0x00000800, 0x00010801, 0x04000000, 0x00000000, 0x00010001, 0x04000801, 0x00010801, 0x04010800, 0x04000001, 0x04000000, 0x00010000, 0x00000801, 0x04010801, 0x00010001, 0x04010800, 0x04000800, 0x00010801, 0x04010801, 0x00010001, 0x04000001, 0x00000000, 0x04000000, 0x00000801, 0x00010000, 0x04010001, 0x00000800, 0x04000000, 0x00010801, 0x04000801, 0x04010800, 0x00000800, 0x00000000, 0x04000001, 0x00000001, 0x04010801, 0x00010800, 0x04010000, 0x04010001, 0x00010000, 0x00000801, 0x04000800, 0x04000801, 0x00000001, 0x04010000, 0x00010800],
[0x00000400, 0x00000020, 0x00100020, 0x40100000, 0x40100420, 0x40000400, 0x00000420, 0x00000000, 0x00100000, 0x40100020, 0x40000020, 0x00100400, 0x40000000, 0x00100420, 0x00100400, 0x40000020, 0x40100020, 0x00000400, 0x40000400, 0x40100420, 0x00000000, 0x00100020, 0x40100000, 0x00000420, 0x40100400, 0x40000420, 0x00100420, 0x40000000, 0x40000420, 0x40100400, 0x00000020, 0x00100000, 0x40000420, 0x00100400, 0x40100400, 0x40000020, 0x00000400, 0x00000020, 0x00100000, 0x40100400, 0x40100020, 0x40000420, 0x00000420, 0x00000000, 0x00000020, 0x40100000, 0x40000000, 0x00100020, 0x00000000, 0x40100020, 0x00100020, 0x00000420, 0x40000020, 0x00000400, 0x40100420, 0x00100000, 0x00100420, 0x40000000, 0x40000400, 0x40100420, 0x40100000, 0x00100420, 0x00100400, 0x40000400],
[0x00800000, 0x00001000, 0x00000040, 0x00801042, 0x00801002, 0x00800040, 0x00001042, 0x00801000, 0x00001000, 0x00000002, 0x00800002, 0x00001040, 0x00800042, 0x00801002, 0x00801040, 0x00000000, 0x00001040, 0x00800000, 0x00001002, 0x00000042, 0x00800040, 0x00001042, 0x00000000, 0x00800002, 0x00000002, 0x00800042, 0x00801042, 0x00001002, 0x00801000, 0x00000040, 0x00000042, 0x00801040, 0x00801040, 0x00800042, 0x00001002, 0x00801000, 0x00001000, 0x00000002, 0x00800002, 0x00800040, 0x00800000, 0x00001040, 0x00801042, 0x00000000, 0x00001042, 0x00800000, 0x00000040, 0x00001002, 0x00800042, 0x00000040, 0x00000000, 0x00801042, 0x00801002, 0x00801040, 0x00000042, 0x00001000, 0x00001040, 0x00801002, 0x00800040, 0x00000042, 0x00000002, 0x00001042, 0x00801000, 0x00800002],
[0x10400000, 0x00404010, 0x00000010, 0x10400010, 0x10004000, 0x00400000, 0x10400010, 0x00004010, 0x00400010, 0x00004000, 0x00404000, 0x10000000, 0x10404010, 0x10000010, 0x10000000, 0x10404000, 0x00000000, 0x10004000, 0x00404010, 0x00000010, 0x10000010, 0x10404010, 0x00004000, 0x10400000, 0x10404000, 0x00400010, 0x10004010, 0x00404000, 0x00004010, 0x00000000, 0x00400000, 0x10004010, 0x00404010, 0x00000010, 0x10000000, 0x00004000, 0x10000010, 0x10004000, 0x00404000, 0x10400010, 0x00000000, 0x00404010, 0x00004010, 0x10404000, 0x10004000, 0x00400000, 0x10404010, 0x10000000, 0x10004010, 0x10400000, 0x00400000, 0x10404010, 0x00004000, 0x00400010, 0x10400010, 0x00004010, 0x00400010, 0x00000000, 0x10404000, 0x10000010, 0x10400000, 0x10004010, 0x00000010, 0x00404000],
[0x00208080, 0x00008000, 0x20200000, 0x20208080, 0x00200000, 0x20008080, 0x20008000, 0x20200000, 0x20008080, 0x00208080, 0x00208000, 0x20000080, 0x20200080, 0x00200000, 0x00000000, 0x20008000, 0x00008000, 0x20000000, 0x00200080, 0x00008080, 0x20208080, 0x00208000, 0x20000080, 0x00200080, 0x20000000, 0x00000080, 0x00008080, 0x20208000, 0x00000080, 0x20200080, 0x20208000, 0x00000000, 0x00000000, 0x20208080, 0x00200080, 0x20008000, 0x00208080, 0x00008000, 0x20000080, 0x00200080, 0x20208000, 0x00000080, 0x00008080, 0x20200000, 0x20008080, 0x20000000, 0x20200000, 0x00208000, 0x20208080, 0x00008080, 0x00208000, 0x20200080, 0x00200000, 0x20000080, 0x20008000, 0x00000000, 0x00008000, 0x00200000, 0x20200080, 0x00208080, 0x20000000, 0x20208000, 0x00000080, 0x20008080]
]
DES_ROTORS = [0x22, 0x0D, 0x05, 0x2E, 0x2F, 0x12, 0x20, 0x29, 0x0B, 0x35, 0x21, 0x14, 0x0E, 0x24, 0x1E, 0x18, 0x31, 0x02, 0x0F, 0x25, 0x2A, 0x32, 0x00, 0x15, 0x26, 0x30, 0x06, 0x1A, 0x27, 0x04, 0x34, 0x19, 0x0C, 0x1B, 0x1F, 0x28, 0x01, 0x11, 0x1C, 0x1D, 0x17, 0x33, 0x23, 0x07, 0x03, 0x16, 0x09, 0x2B, 0x29, 0x14, 0x0C, 0x35, 0x36, 0x19, 0x27, 0x30, 0x12, 0x1F, 0x28, 0x1B, 0x15, 0x2B, 0x25, 0x00, 0x01, 0x09, 0x16, 0x2C, 0x31, 0x02, 0x07, 0x1C, 0x2D, 0x37, 0x0D, 0x21, 0x2E, 0x0B, 0x06, 0x20, 0x13, 0x22, 0x26, 0x2F, 0x08, 0x18, 0x23, 0x24, 0x1E, 0x03, 0x2A, 0x0E, 0x0A, 0x1D, 0x10, 0x32, 0x37, 0x22, 0x1A, 0x26, 0x0B, 0x27, 0x35, 0x05, 0x20, 0x2D, 0x36, 0x29, 0x23, 0x02, 0x33, 0x0E, 0x0F, 0x17, 0x24, 0x03, 0x08, 0x10, 0x15, 0x2A, 0x06, 0x0C, 0x1B, 0x2F, 0x1F, 0x19, 0x14, 0x2E, 0x21, 0x30, 0x34, 0x04, 0x16, 0x07, 0x31, 0x32, 0x2C, 0x11, 0x01, 0x1C, 0x18, 0x2B, 0x1E, 0x09, 0x0C, 0x30, 0x28, 0x34, 0x19, 0x35, 0x26, 0x13, 0x2E, 0x06, 0x0B, 0x37, 0x31, 0x10, 0x0A, 0x1C, 0x1D, 0x25, 0x32, 0x11, 0x16, 0x1E, 0x23, 0x01, 0x14, 0x1A, 0x29, 0x04, 0x2D, 0x27, 0x22, 0x1F, 0x2F, 0x05, 0x0D, 0x12, 0x24, 0x15, 0x08, 0x09, 0x03, 0x00, 0x0F, 0x2A, 0x07, 0x02, 0x2C, 0x17, 0x1A, 0x05, 0x36, 0x0D, 0x27, 0x26, 0x34, 0x21, 0x1F, 0x14, 0x19, 0x0C, 0x08, 0x1E, 0x18, 0x2A, 0x2B, 0x33, 0x09, 0x00, 0x24, 0x2C, 0x31, 0x0F, 0x22, 0x28, 0x37, 0x12, 0x06, 0x35, 0x30, 0x2D, 0x04, 0x13, 0x1B, 0x20, 0x32, 0x23, 0x16, 0x17, 0x11, 0x0E, 0x1D, 0x01, 0x15, 0x10, 0x03, 0x25, 0x28, 0x13, 0x0B, 0x1B, 0x35, 0x34, 0x0D, 0x2F, 0x2D, 0x22, 0x27, 0x1A, 0x16, 0x2C, 0x07, 0x01, 0x02, 0x0A, 0x17, 0x0E, 0x32, 0x03, 0x08, 0x1D, 0x30, 0x36, 0x0C, 0x20, 0x14, 0x26, 0x05, 0x06, 0x12, 0x21, 0x29, 0x2E, 0x09, 0x31, 0x24, 0x25, 0x00, 0x1C, 0x2B, 0x0F, 0x23, 0x1E, 0x11, 0x33, 0x36, 0x21, 0x19, 0x29, 0x26, 0x0D, 0x1B, 0x04, 0x06, 0x30, 0x35, 0x28, 0x24, 0x03, 0x15, 0x0F, 0x10, 0x18, 0x25, 0x1C, 0x09, 0x11, 0x16, 0x2B, 0x05, 0x0B, 0x1A, 0x2E, 0x22, 0x34, 0x13, 0x14, 0x20, 0x2F, 0x37, 0x1F, 0x17, 0x08, 0x32, 0x33, 0x0E, 0x2A, 0x02, 0x1D, 0x31, 0x2C, 0x00, 0x0A, 0x0B, 0x2F, 0x27, 0x37, 0x34, 0x1B, 0x29, 0x12, 0x14, 0x05, 0x26, 0x36, 0x32, 0x11, 0x23, 0x1D, 0x1E, 0x07, 0x33, 0x2A, 0x17, 0x00, 0x24, 0x02, 0x13, 0x19, 0x28, 0x1F, 0x30, 0x0D, 0x21, 0x22, 0x2E, 0x04, 0x0C, 0x2D, 0x25, 0x16, 0x09, 0x0A, 0x1C, 0x01, 0x10, 0x2B, 0x08, 0x03, 0x0E, 0x18, 0x12, 0x36, 0x2E, 0x05, 0x06, 0x22, 0x30, 0x19, 0x1B, 0x0C, 0x2D, 0x04, 0x02, 0x18, 0x2A, 0x24, 0x25, 0x0E, 0x03, 0x31, 0x1E, 0x07, 0x2B, 0x09, 0x1A, 0x20, 0x2F, 0x26, 0x37, 0x14, 0x28, 0x29, 0x35, 0x0B, 0x13, 0x34, 0x2C, 0x1D, 0x10, 0x11, 0x23, 0x08, 0x17, 0x32, 0x0F, 0x0A, 0x15, 0x00, 0x20, 0x0B, 0x1F, 0x13, 0x14, 0x30, 0x05, 0x27, 0x29, 0x1A, 0x06, 0x12, 0x10, 0x07, 0x01, 0x32, 0x33, 0x1C, 0x11, 0x08, 0x2C, 0x15, 0x02, 0x17, 0x28, 0x2E, 0x04, 0x34, 0x0C, 0x22, 0x36, 0x37, 0x26, 0x19, 0x21, 0x0D, 0x03, 0x2B, 0x1E, 0x00, 0x31, 0x16, 0x25, 0x09, 0x1D, 0x18, 0x23, 0x0E, 0x2E, 0x19, 0x2D, 0x21, 0x22, 0x05, 0x13, 0x35, 0x37, 0x28, 0x14, 0x20, 0x1E, 0x15, 0x0F, 0x09, 0x0A, 0x2A, 0x00, 0x16, 0x03, 0x23, 0x10, 0x25, 0x36, 0x1F, 0x12, 0x0D, 0x1A, 0x30, 0x0B, 0x0C, 0x34, 0x27, 0x2F, 0x1B, 0x11, 0x02, 0x2C, 0x0E, 0x08, 0x24, 0x33, 0x17, 0x2B, 0x07, 0x31, 0x1C, 0x1F, 0x27, 0x06, 0x2F, 0x30, 0x13, 0x21, 0x26, 0x0C, 0x36, 0x22, 0x2E, 0x2C, 0x23, 0x1D, 0x17, 0x18, 0x01, 0x0E, 0x24, 0x11, 0x31, 0x1E, 0x33, 0x0B, 0x2D, 0x20, 0x1B, 0x28, 0x05, 0x19, 0x1A, 0x0D, 0x35, 0x04, 0x29, 0x00, 0x10, 0x03, 0x1C, 0x16, 0x32, 0x0A, 0x25, 0x02, 0x15, 0x08, 0x2A, 0x2D, 0x35, 0x14, 0x04, 0x05, 0x21, 0x2F, 0x34, 0x1A, 0x0B, 0x30, 0x1F, 0x03, 0x31, 0x2B, 0x25, 0x07, 0x0F, 0x1C, 0x32, 0x00, 0x08, 0x2C, 0x0A, 0x19, 0x06, 0x2E, 0x29, 0x36, 0x13, 0x27, 0x28, 0x1B, 0x26, 0x12, 0x37, 0x0E, 0x1E, 0x11, 0x2A, 0x24, 0x09, 0x18, 0x33, 0x10, 0x23, 0x16, 0x01, 0x06, 0x26, 0x22, 0x12, 0x13, 0x2F, 0x04, 0x0D, 0x28, 0x19, 0x05, 0x2D, 0x11, 0x08, 0x02, 0x33, 0x15, 0x1D, 0x2A, 0x09, 0x0E, 0x16, 0x03, 0x18, 0x27, 0x14, 0x1F, 0x37, 0x0B, 0x21, 0x35, 0x36, 0x29, 0x34, 0x20, 0x0C, 0x1C, 0x2C, 0x00, 0x01, 0x32, 0x17, 0x07, 0x0A, 0x1E, 0x31, 0x24, 0x0F, 0x14, 0x34, 0x30, 0x20, 0x21, 0x04, 0x12, 0x1B, 0x36, 0x27, 0x13, 0x06, 0x00, 0x16, 0x10, 0x0A, 0x23, 0x2B, 0x01, 0x17, 0x1C, 0x24, 0x11, 0x07, 0x35, 0x22, 0x2D, 0x0C, 0x19, 0x2F, 0x26, 0x0B, 0x37, 0x0D, 0x2E, 0x1A, 0x2A, 0x03, 0x0E, 0x0F, 0x09, 0x25, 0x15, 0x18, 0x2C, 0x08, 0x32, 0x1D, 0x1B, 0x06, 0x37, 0x27, 0x28, 0x0B, 0x19, 0x22, 0x04, 0x2E, 0x1A, 0x0D, 0x07, 0x1D, 0x17, 0x11, 0x2A, 0x32, 0x08, 0x1E, 0x23, 0x2B, 0x18, 0x0E, 0x1F, 0x29, 0x34, 0x13, 0x20, 0x36, 0x2D, 0x12, 0x05, 0x14, 0x35, 0x21, 0x31, 0x0A, 0x15, 0x16, 0x10, 0x2C, 0x1C, 0x00, 0x33, 0x0F, 0x02, 0x24]
byte_102D4AA0 = [0x1F, 0x1E, 0x1D, 0x1C, 0x1B, 0x1A, 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x1B, 0x1A, 0x19, 0x18, 0x17, 0x16, 0x13, 0x12, 0x11, 0x10, 0x0F, 0x0E, 0x0B, 0x0A, 0x09, 0x08, 0x07, 0x06, 0x03, 0x02, 0x01, 0x00, 0x1F, 0x1E]
KEY_DATA = [0] * 96
def roll_right(arg2, arg3):
return (arg2 << 32 - arg3 | (arg2 >> arg3)) & 0xffffffff
def des_small_flips_encrypt(key, output, uid):
v3 = uid[0] | (uid[1] << 8) | (uid[2] << 16) | (uid[3] << 24)
v0 = uid[4] | (uid[5] << 8) | (uid[6] << 16) | (uid[7] << 24)
v4 = (16 * ((v3 ^ (v0 >> 4)) & 0xF0F0F0F)) ^ v0
v5 = (v3 ^ (v0 >> 4)) & 0xF0F0F0F ^ v3
v6 = (v4 ^ (v5 >> 16)) & 0xffff
v7 = (v6 << 16) ^ v5
v8 = v6 ^ v4
v9 = (v7 ^ (v8 >> 2)) & 0x33333333
v10 = (4 * v9) ^ v8
v11 = v9 ^ v7
v12 = (v10 ^ (v11 >> 8)) & 0xFF00FF
v13 = (v12 << 8) ^ v11
v14 = roll_right(v12 ^ v10, 1)
v15 = (v13 ^ v14) & 0x55555555
v16 = v15 ^ v14
v17 = roll_right(v15 ^ v13, 1)
for i in range(0, 32, 4):
v19 = roll_right(v17 ^ key[i + 1], 28)
v16 ^= (0
^ DES_KEYMAP[0][((v17 ^ key[i]) >> 26) & 0x3f]
^ DES_KEYMAP[1][((v17 ^ key[i]) >> 18) & 0x3f]
^ DES_KEYMAP[2][(((v17 ^ key[i]) >> 8) >> 2) & 0x3f]
^ DES_KEYMAP[3][((v17 ^ key[i]) >> 2) & 0x3f]
^ DES_KEYMAP[4][(v19 >> 26) & 0x3f]
^ DES_KEYMAP[5][(v19 >> 18) & 0x3f]
^ DES_KEYMAP[6][(v19 >> 10) & 0x3f]
^ DES_KEYMAP[7][(v19 >> 2) & 0x3f]
)
v20 = roll_right(v16 ^ key[i + 3], 28)
v17 ^= (0
^ DES_KEYMAP[0][((v16 ^ key[i + 2]) >> 26) & 0x3f]
^ DES_KEYMAP[1][((v16 ^ key[i + 2]) >> 18) & 0x3F]
^ DES_KEYMAP[2][(((v16 ^ key[i + 2]) >> 8) >> 2) & 0x3f]
^ DES_KEYMAP[3][((v16 ^ key[i + 2]) >> 2) & 0x3F]
^ DES_KEYMAP[4][(v20 >> 26) & 0x3f]
^ DES_KEYMAP[5][(v20 >> 18) & 0x3F]
^ DES_KEYMAP[6][(v20 >> 10) & 0x3f]
^ DES_KEYMAP[7][(v20 >> 2) & 0x3f]
)
v21 = roll_right(v16, 31)
v22 = (v17 ^ v21) & 0x55555555
v23 = v22 ^ v21
v24 = roll_right(v22 ^ v17, 31)
v25 = (v24 ^ (v23 >> 8)) & 0xFF00FF
v26 = (v25 << 8) ^ v23
v27 = v25 ^ v24
v28 = (v26 ^ ((v25 ^ v24) >> 2)) & 0x33333333
v29 = (4 * v28) ^ v27
v30 = v28 ^ v26
v31 = (v29 ^ (v30 >> 16)) & 0xffff
v32 = ((v31 << 16) & 0xffffffff) ^ v30
v33 = v31 ^ v29
v34 = (v32 ^ (v33 >> 4)) & 0xF0F0F0F
v35 = (v34 << 4) ^ v33
v34 = v34 ^ v32
output[0] = v34 & 0xff
output[1] = (v34 >> 8) & 0xff
output[2] = (v34 >> 16) & 0xff
output[3] = (v34 >> 24) & 0xff
output[4] = v35 & 0xff
output[5] = (v35 >> 8) & 0xff
output[6] = (v35 >> 16) & 0xff
output[7] = (v35 >> 24) & 0xff
def des_small_flips_decrypt(key, output, uid):
v3 = uid[0] | (uid[1] << 8) | (uid[2] << 16) | (uid[3] << 24)
v0 = uid[4] | (uid[5] << 8) | (uid[6] << 16) | (uid[7] << 24)
v4 = (16 * ((v3 ^ (v0 >> 4)) & 0xF0F0F0F)) ^ v0
v5 = (v3 ^ (v0 >> 4)) & 0xF0F0F0F ^ v3
v6 = (v4 ^ (v5 >> 16)) & 0xffff
v7 = (v6 << 16) ^ v5
v8 = v6 ^ v4
v9 = (v7 ^ (v8 >> 2)) & 0x33333333
v10 = (4 * v9) ^ v8
v11 = v9 ^ v7
v12 = (v10 ^ (v11 >> 8)) & 0xFF00FF
v13 = (v12 << 8) ^ v11
v14 = roll_right(v12 ^ v10, 1)
v15 = (v13 ^ v14) & 0x55555555
v16 = v15 ^ v14
v17 = roll_right(v15 ^ v13, 1)
for i in range(0, 32, 4):
v19 = roll_right(v17 ^ key[(31 - i)], 28)
v16 ^= (0
^ DES_KEYMAP[0][((v17 ^ key[(30 - i)]) >> 26) & 0x3f]
^ DES_KEYMAP[1][((v17 ^ key[(30 - i)]) >> 18) & 0x3F]
^ DES_KEYMAP[2][(((v17 ^ key[(30 - i)]) >> 8) >> 2) & 0x3f]
^ DES_KEYMAP[3][((v17 ^ key[(30 - i)]) >> 2) & 0x3F]
^ DES_KEYMAP[4][(v19 >> 26) & 0x3f]
^ DES_KEYMAP[5][(v19 >> 18) & 0x3F]
^ DES_KEYMAP[6][(v19 >> 10) & 0x3f]
^ DES_KEYMAP[7][(v19 >> 2) & 0x3f]
)
v20 = roll_right(v16 ^ key[(29 - i)], 28)
v17 ^= (0
^ DES_KEYMAP[0][((v16 ^ key[(28 - i)]) >> 26) & 0x3f]
^ DES_KEYMAP[1][((v16 ^ key[(28 - i)]) >> 18) & 0x3F]
^ DES_KEYMAP[2][(((v16 ^ key[(28 - i)]) >> 8) >> 2) & 0x3f]
^ DES_KEYMAP[3][((v16 ^ key[(28 - i)]) >> 2) & 0x3F]
^ DES_KEYMAP[4][(v20 >> 26) & 0x3f]
^ DES_KEYMAP[5][(v20 >> 18) & 0x3f]
^ DES_KEYMAP[6][(v20 >> 10) & 0x3f]
^ DES_KEYMAP[7][(v20 >> 2) & 0x3f]
)
v21 = roll_right(v16, 31)
v22 = (v17 ^ v21) & 0x55555555
v23 = v22 ^ v21
v24 = roll_right(v22 ^ v17, 31)
v25 = (v24 ^ (v23 >> 8)) & 0xFF00FF
v26 = (v25 << 8) ^ v23
v27 = v25 ^ v24
v28 = (v26 ^ ((v25 ^ v24) >> 2)) & 0x33333333
v29 = (4 * v28) ^ v27
v30 = v28 ^ v26
v31 = (v29 ^ (v30 >> 16)) & 0xffff
v32 = (v31 << 16) ^ v30
v33 = v31 ^ v29
v34 = (v32 ^ (v33 >> 4)) & 0xF0F0F0F
v35 = (v34 << 4) ^ v33
v34 = v34 ^ v32
output[0] = v34 & 0xff
output[1] = (v34 >> 8) & 0xff
output[2] = (v34 >> 16) & 0xff
output[3] = (v34 >> 24) & 0xff
output[4] = v35 & 0xff
output[5] = (v35 >> 8) & 0xff
output[6] = (v35 >> 16) & 0xff
output[7] = (v35 >> 24) & 0xff
def des3_encryption(key, kid_out, uid):
des_small_flips_encrypt(key, kid_out, uid)
des_small_flips_decrypt(key[32:], kid_out, kid_out)
des_small_flips_encrypt(key[64:], kid_out, kid_out)
def des_setkey(key, offset, out):
b1 = bytearray(56);
for i in range(8):
v3 = out[i]
for j in range(7):
b1[-7 * i - j + 55] = (v3 >> (j + 1)) & 1
for i in range(32):
v5 = 0
for j in range(24):
v5 |= b1[DES_ROTORS[24 * i + j]] << byte_102D4AA0[24 * (i & 1) + j]
key[offset + i] = v5
def des3_setkey(key, out):
for i in range(3):
des_setkey(key, 32 * i, out[8 * i:])
def load_key(key):
key_data = bytearray(24)
for i in range(24):
key_data[i] = 2 * key[i % len(key)]
des3_setkey(KEY_DATA, key_data)</code></pre>
</details>
</details>
</body>

View File

@ -42,6 +42,18 @@
<p>If you're here because you work on one of those aforementioned closed source projects, hello! Feel free to share
knowledge with the rest of the world, or point out corrections. Or don't; you do you.</p>
<h3>Code snippets</h3>
<p>Across these pages there are a number of code snippets. They roughly break down into three categories:</p>
<ul>
<li>Assembly: Directly disassembled code from game binaries</li>
<li>C: Either raw decompilation, or slightly cleaned up decompilation</li>
<li>Python: Snippets from my local testing implementations</li>
<li>Pseudocode: Used to illustrate some points. Note that it probably started life as Python before being
pseudo'd</li>
</ul>
<p>If you yoink chunks of Python code, attribution is always appreciated, but consider it under CC0 (just don't be
that person who tries to take credit for it, yeah?).</p>
<h2>Contents</h2>
<ol>
<li><a href="./transport.html">Transport layer</a></li>
@ -56,6 +68,14 @@
<li><a href="./packet.html#schema">Binary schemas</a></li>
<li><a href="./packet.html#data">Binary data</a></li>
</ol>
<li><a href="./protocol.html">Communication protocol details</a></li>
<ul>
<li>There are a crazy number of sub pages here, so just go check the contents there.</li>
</ul>
<li>Misc pages</li>
<ol>
<li><a href="./cardid.html">Parsing and converting card IDs</a></li>
</ol>
</ol>
<h2>Getting started</h2>

View File

@ -159,11 +159,11 @@
<td></td>
</tr>
<tr>
<td><code>0x40</code></td>
<td><code>0xBF</code></td>
<td><code>ISO-8859-1</code></td>
<td><code>ISO_8859-1</code></td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>0x60</code></td>

View File

@ -27,13 +27,13 @@
are identified in requests using the <code>cardtype</code> attribute as in the below table.
</p>
<p>e-Amusement cards have a "card number" and a "card id". Confusingly, neither is a number. The card number is the
one printed on your card. The card ID is your KONAMI ID. The number is derrived from your ID using an algorithm
that I'll detail here once I get round to it.</p>
one printed on your card. The card ID is your KONAMI ID. You can (and should) read about the algorithm used for
these IDs on <a href="../cardid.html">the Card IDs page</a>.</p>
<p>In the interest of not wasting space, <code>cardid</code> and <code>cardtype</code> will be omitted from
individual breakdowns where their meaning is obvious.</p>
<h4>Card types:</h4>
<table class="nocode">
<table>
<thead>
<tr>
<td><code>cardtype</code></td>
@ -69,7 +69,7 @@
<pre><code>&lt;call <i>...</i>&gt;
&lt;cardmng method="inquire" cardid="" cardtype="" update="" model*="" /&gt;
&lt;/call&gt;</code></pre>
<table class="nocode">
<table>
<tr>
<td><code>update</code></td>
<td>Should the tracked last play time be updated by this inquiry? (Just a guess)</td>
@ -84,7 +84,7 @@
seen for this specific game. If we have never seen this card used on this game, it is possible this card was
used with an older version of this game, and migration is supported, in which case we report as if we had found
a profile for this game.</p>
<table class="nocode">
<table>
<tr>
<td><code>refid</code></td>
<td>A reference to this card to be used in other requests</td>
@ -115,7 +115,7 @@
<pre><code>&lt;call <i>...</i>&gt;
&lt;cardmng method="getrefid" cardtype="" cardid=" newflag="" passwd="" model*="" /&gt;
&lt;/call&gt;</code></pre>
<table class="nocode">
<table>
<tr>
<td><code>newflag</code></td>
<td>?</td>
@ -130,7 +130,7 @@
<pre><code>&lt;response&gt;
&lt;cardmng status="<i>status</i>" refid="" dataid="" pcode="" /&gt;
&lt;/response&gt;</code></pre>
<table class="nocode">
<table>
<tr>
<td><code>refid</code></td>
<td>A reference to this card to be used in other requests</td>
@ -174,7 +174,7 @@
<pre><code>&lt;call <i>...</i>&gt;
&lt;cardmng method="authpass" refid="" pass="" model*="" /&gt;
&lt;/call&gt;</code></pre>
<table class="nocode">
<table>
<tr>
<td><code>refid</code></td>
<td>The reference we received either during <code>cardmng.inquire</code> or <code>cardmng.getrefid</code>