From 2da6e2b05621cc68b909bcc452da5ceaf3e0b739 Mon Sep 17 00:00:00 2001 From: Bottersnike Date: Tue, 28 Dec 2021 20:54:12 +0000 Subject: [PATCH] Flask --- .gitignore | 3 + __base.html | 2 +- docs.py | 79 ++ freeze.py | 7 + index.html | 106 --- packet.html | 896 ------------------ proto/apsmanager.html | 33 - proto/cardmng.html | 232 ----- proto/dlstatus.html | 54 -- proto/esign.html | 37 - proto/esoc.html | 50 - proto/eventlog.html | 58 -- proto/message.html | 35 - proto/package.html | 47 - proto/pcbevent.html | 41 - proto/pcbtracker.html | 39 - proto/services.html | 70 -- proto/sidmgr.html | 94 -- proto/traceroute.html | 40 - proto/userdata.html | 48 - protocol.html | 284 ------ templates/base.html | 25 + cardid.html => templates/pages/cardid.html | 411 ++++---- templates/pages/index.html | 84 ++ templates/pages/packet.html | 881 +++++++++++++++++ templates/pages/proto/apsmanager.html | 13 + templates/pages/proto/cardmng.html | 212 +++++ templates/pages/proto/dlstatus.html | 34 + {proto => templates/pages/proto}/eacoin.html | 128 ++- templates/pages/proto/esign.html | 17 + templates/pages/proto/esoc.html | 30 + templates/pages/proto/eventlog.html | 38 + .../pages/proto}/facility.html | 142 ++- .../pages/proto}/game/sv4.html | 290 +++--- .../pages/proto}/matching.html | 58 +- templates/pages/proto/message.html | 15 + templates/pages/proto/package.html | 27 + templates/pages/proto/pcbevent.html | 21 + templates/pages/proto/pcbtracker.html | 19 + .../pages/proto}/playerdata.html | 74 +- templates/pages/proto/services.html | 50 + templates/pages/proto/sidmgr.html | 74 ++ {proto => templates/pages/proto}/system.html | 68 +- templates/pages/proto/traceroute.html | 20 + templates/pages/proto/userdata.html | 28 + templates/pages/protocol.html | 263 +++++ templates/pages/transport.html | 52 + transport.html | 54 -- 48 files changed, 2508 insertions(+), 2875 deletions(-) create mode 100644 .gitignore create mode 100644 docs.py create mode 100644 freeze.py delete mode 100644 index.html delete mode 100644 packet.html delete mode 100644 proto/apsmanager.html delete mode 100644 proto/cardmng.html delete mode 100644 proto/dlstatus.html delete mode 100644 proto/esign.html delete mode 100644 proto/esoc.html delete mode 100644 proto/eventlog.html delete mode 100644 proto/message.html delete mode 100644 proto/package.html delete mode 100644 proto/pcbevent.html delete mode 100644 proto/pcbtracker.html delete mode 100644 proto/services.html delete mode 100644 proto/sidmgr.html delete mode 100644 proto/traceroute.html delete mode 100644 proto/userdata.html delete mode 100644 protocol.html create mode 100644 templates/base.html rename cardid.html => templates/pages/cardid.html (60%) create mode 100644 templates/pages/index.html create mode 100644 templates/pages/packet.html create mode 100644 templates/pages/proto/apsmanager.html create mode 100644 templates/pages/proto/cardmng.html create mode 100644 templates/pages/proto/dlstatus.html rename {proto => templates/pages/proto}/eacoin.html (62%) create mode 100644 templates/pages/proto/esign.html create mode 100644 templates/pages/proto/esoc.html create mode 100644 templates/pages/proto/eventlog.html rename {proto => templates/pages/proto}/facility.html (50%) rename {proto => templates/pages/proto}/game/sv4.html (53%) rename {proto => templates/pages/proto}/matching.html (65%) create mode 100644 templates/pages/proto/message.html create mode 100644 templates/pages/proto/package.html create mode 100644 templates/pages/proto/pcbevent.html create mode 100644 templates/pages/proto/pcbtracker.html rename {proto => templates/pages/proto}/playerdata.html (68%) create mode 100644 templates/pages/proto/services.html create mode 100644 templates/pages/proto/sidmgr.html rename {proto => templates/pages/proto}/system.html (66%) create mode 100644 templates/pages/proto/traceroute.html create mode 100644 templates/pages/proto/userdata.html create mode 100644 templates/pages/protocol.html create mode 100644 templates/pages/transport.html delete mode 100644 transport.html diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac16543 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.pyc +__pycache__/ +build/ diff --git a/__base.html b/__base.html index ce20100..c4ee1ac 100644 --- a/__base.html +++ b/__base.html @@ -21,4 +21,4 @@ - \ No newline at end of file +{% endblock %} \ No newline at end of file diff --git a/docs.py b/docs.py new file mode 100644 index 0000000..421a970 --- /dev/null +++ b/docs.py @@ -0,0 +1,79 @@ +from flask import Flask, send_from_directory, render_template +from livereload import Server +import os + +app = Flask(__name__) + +@app.route("/styles.css") +def styles(): + return send_from_directory(".", "styles.css") + + +for base, folders, files in os.walk("images"): + 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 = handler(base, name) + handler.__name__ == route + app.add_url_rule(route, route, handler) + +TEMPLATES = "templates" +PAGES_BASE = "pages" +for base, folders, files in os.walk(TEMPLATES + "/" + PAGES_BASE): + if ".git" in base: + continue + if base.startswith(TEMPLATES): + base = base[len(TEMPLATES):] + + for name in files: + if name.endswith(".html"): + def handler(base, name): + def handler(): + return render_template(os.path.join(base, name).strip("/").replace("\\", "/")) + 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 + + handler = handler(base, name) + handler.__name__ == route + app.add_url_rule(route, route, handler) + + +from flask import url_for +def has_no_empty_params(rule): + defaults = rule.defaults if rule.defaults is not None else () + arguments = rule.arguments if rule.arguments is not None else () + return len(defaults) >= len(arguments) +@app.route("/site-map") +def site_map(): + links = [] + for rule in app.url_map.iter_rules(): + # Filter out rules we can't navigate to in a browser + # and rules that require parameters + if "GET" in rule.methods and has_no_empty_params(rule): + url = url_for(rule.endpoint, **(rule.defaults or {})) + links.append((url, rule.endpoint)) + return str(links) + +if __name__ == '__main__': + app.config['TEMPLATES_AUTO_RELOAD'] = True + app.config['DEBUG'] = True + + server = Server(app.wsgi_app) + server.watch("templates") + server.serve(port=3000) \ No newline at end of file diff --git a/freeze.py b/freeze.py new file mode 100644 index 0000000..9ccb94b --- /dev/null +++ b/freeze.py @@ -0,0 +1,7 @@ +from flask_frozen import Freezer +from docs import app + +freezer = Freezer(app) + +if __name__ == '__main__': + freezer.freeze() \ No newline at end of file diff --git a/index.html b/index.html deleted file mode 100644 index f120af7..0000000 --- a/index.html +++ /dev/null @@ -1,106 +0,0 @@ - - - - - - - - e-Amusement API - - - - - - - - - - - - -
ContentsTransport layerPacket formatApplication Protocol
- -

Benami/Konami e-Amusement API

-

Why?

-

I was curious how these APIs work, yet could find little to nothing on Google. There are a number of - closed-source projects, with presumably similarly closed-source internal documentation, and a scattering of - implementations of things, yet I couldn't find a site that actually just documents how the API works. If I'm - going to have to reverse engineer an open source project (or a closed source one, for that matter), I might as - well just go reverse engineer an actual game (or it's stdlib, as most of my time has been spent currently).

-

For the sake of being lazy, I'll probably end up calling it eAmuse more than anything else throughout these - pages. Other names you may come across include httac and xrpc. The latter are the - suite of HTTP functions used in the Bemani stdlib, and the name of their communication protocol they implement - at the application layer, but whenever someone refers to any of them in the context of a rhythm game, they will - be referring to the things documented here.

-

These pages are very much a work in progress, and are being written as I reverse engineer parts of the - protocol. I've been asserting all my assumptions by writing my own implementation as I go, however it currently - isn't sharable quality code and, more importantly, the purpose of these pages is to make implementation of one's - own code hopefully trivial (teach a man to fish, and all that).

-

Sharing annotated sources for all of the games' stdlibs would be both impractical and unwise. Where relevant - however I try to include snippets to illustrate concepts, and have included their locations in the source for if - you feel like taking a dive too.

-

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.

- -

Code snippets

-

Across these pages there are a number of code snippets. They roughly break down into three categories:

- -

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?).

- -

Contents

-
    -
  1. Transport layer
  2. -
      -
    1. Packet structure
    2. -
    3. Types
    4. -
    -
  3. The inner packet structure
  4. -
      -
    1. XML packets
    2. -
    3. Binary packed packets
    4. -
    5. Binary schemas
    6. -
    7. Binary data
    8. -
    -
  5. Communication protocol details
  6. - -
  7. Misc pages
  8. -
      -
    1. Parsing and converting card IDs
    2. -
    -
- -

Getting started

-

My aim with these pages is to cover as much as possible, so you don't need to try and figure them out yourself. - That said, being able to follow along yourself will almost certainly help get more out of this. For following - along with source code, you're really going to want to grab yourself a dumped copy of a game (it's going to be a - lot easier, and cheeper, than dumping one yourself). I trust you can figure out where to find that.

-

For network related things, your options are a little broader. The ideal would be physical ownership of a - cabinet, and a subscription to genuine e-amusement. Odds are you don't have both of those :P. A connection to an - alternative network works just as well. In the more likely case that you don't have a physical cabinet, it's - time to crack out that dumped copy of a game and just run it on your own PC (or a VM, if you're not on Windows) - (odds are whatever you downloaded came with the program you'll need to start it pre-packaged. If not, it rhymes - with rice.).

-

You will also need a local e-amusement-emulating server. By the time I'm done with these pages, there will - hopefully be everything you need to be able to write your own. Unfortunately I'm not finished writing them; - depending on where you acquired your game, it may have shipped with one of said servers. If it didn't, Asphyxia - CORE will do the trick (yes, it's closed source).

-

If this all sounds like way too much work, and/or you're just here because of curiosity, I plan to prepare some - pcaps of network traffic to play around with without needing a running copy of a game or a network tap on a cab. -

- - Next page - -

This site intentionally looks not-great. I don't feel like changing that, and honestly quite like the - aesthetic.

- - - \ No newline at end of file diff --git a/packet.html b/packet.html deleted file mode 100644 index 3c266ea..0000000 --- a/packet.html +++ /dev/null @@ -1,896 +0,0 @@ - - - - - - - - Packet format | eAmuse API - - - - - - - - - - - - - -
ContentsTransport layerPacket formatApplication Protocol
- -

Packet format

- -

eAmuse uses XML for its application layer payloads*. This XML is either verbatim, or in a custom packed binary - format.
*Newer games use JSON, but this page is about XML.

- - -

The XML format

- -

Each tag that contains a value has a __type attribute that identifies what type it is. Array types - have a __count attribute indicating how many items are in the array. Binary blobs additionally have - a __size attribute indicating their length (this is notably not present on strings, however).

-

It is perhaps simpler to illustrate with an example, so:

-
<?xml version='1.0' encoding='UTF-8'?>
-<call model="KFC:J:A:A:2019020600" srcid="1000" tag="b0312077">
-    <eventlog method="write">
-        <retrycnt __type="u32" />
-        <data>
-            <eventid __type="str">G_CARDED</eventid>
-            <eventorder __type="s32">5</eventorder>
-            <pcbtime __type="u64">1639669516779</pcbtime>
-            <gamesession __type="s64">1</gamesession>
-            <strdata1 __type="str" />
-            <strdata2 __type="str" />
-            <numdata1 __type="s64">1</numdata1>
-            <numdata2 __type="s64" />
-            <locationid __type="str">ea</locationid>
-        </data>
-    </eventlog>
-</call>
-

Arrays are encoded by concatenating every value together, with spaces between them. Data types that have multiple - values, are serialized similarly.

-

Therefore, an element storing an array of 3u8 ([(1, 2, 3), (4, 5, 6)]) would look like - this

-
<demo __type="3u8" __count="2">1 2 3 4 5 6</demo>
-

Besides this, this is otherwise a rather standard XML.

- -

Packed binary overview

- -

Many packets, rather than using a string-based XML format, use a custom binary packed format instead. While it - can be a little confusing, remembering that this is encoding an XML tree can make it easier to parse.

-

To start with, let's take a look at the overall structure of the packets.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
0123456789101112131415
A0CE~EHead length
Schema definition
FFAlign
Data length
Payload
Align
-

Every packet starts with the magic byte 0xA0. Following this is the content byte, the encoding byte, - and then the 2's compliment of the encoding byte.

-

Currently known possible values for the content byte are:

- - - - - - - - - - - - - - - - - - - - - - - -
CContent
0x42Compressed data
0x43Compressed, no data
0x45Decompressed data
0x46Decompressed, no data
-

Decompressed packets contain an XML string. Compressed packets are what we're interested in here.

-

The encoding flag indicates the encoding for all string types in the packet (more on those later). Possible - values are:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
E~EEncoding name
0x200xDFASCII
0x400xBFISO-8859-1ISO_8859-1
0x600x9FEUC-JPEUCJPEUC_JP
0x800x7FSHIFT-JISSHIFT_JISSJIS
0xA00x5FUTF-8UTF8
-
- Source code details -

The full table for these values can be found in libavs.

-
- -
libavs-win32.dll:0x1006b960
-
-

A second table exists just before this on in the source, responsible for the - <?xml version='1.0' encoding='??'?> line in XML files. -

-
- -
libavs-win32.dll:0x1006b940
-
-

This is indexed using the following function, which maps the above encoding IDs to 1, 2, 3, 4 and 5 - respectively.

-
char* xml_get_encoding_name(uint encoding_id) {
-    return ENCODING_NAME_TABLE[((encoding_id & 0xe0) >> 5) * 4];
-}
-
-

While validating ~E isn't technically required, it acts as a useful assertion that the packet being - parsed is valid.

- -

The packet schema header

-

Following the 4 byte header, is a 4 byte integer containing the length of the next part of the header (this is - technically made redundant as this structure is also terminated).

-

This part of the header defines the schema that the main payload uses.

- -

A tag definition looks like:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
0123456789101112131415
TypenlenTag name
Attributes and childrenFE
- -

Structure names are encoded as densely packed 6 bit values, length prefixed (nlen). The acceptable - alphabet is 0123456789:ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz, and the packed values - are indecies within this alphabet.

- -

The children can be a combination of either attribute names, or child tags. Attribute names are represented by - the byte 0x2E followed by a length prefixed name as defined above. Child tags follow the above - format. Type 0x2E must therefore be considered reserved as a possible structure type.

- -

Attributes (type 0x2E) represent a string attribute. Any other attribute must be defined as a child - tag. Is it notable that 0 children is allowable, which is how the majority of values are encoded.

-

All valid IDs, and their respective type, are listed in the following table. The bucket column here will be - used later when unpacking the main data, so we need not worry about it for now, but be warned it exists and is - possibly the least fun part of this format.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
IDBytesC typeBucketXML namesIDBytesC typeBucketXML names
0x010void-void0x2124uint64[3]int3u64
0x021int8bytes80x2212float[3]int3f
0x031uint8byteu80x2324double[3]int3d
0x042int16shorts160x244int8[4]int4s8
0x052uint16shorts160x254uint8[4]int4u8
0x064int32ints320x268int16[4]int4s16
0x074uint32intu320x278uint8[4]int4s16
0x088int64ints640x2816int32[4]int4s32vs32
0x098uint64intu640x2916uint32[4]int4u32vs32
0x0aprefixchar[]intbinbinary0x2a32int64[4]int4s64
0x0bprefixchar[]intstrstring0x2b32uint64[4]int4u64
0x0c4uint8[4]intip40x2c16float[4]int4fvf
0x0d4uint32inttime0x2d32double[4]int4d
0x0e4floatintfloatf0x2eprefixchar[]intattr
0x0f8doubleintdoubled0x2f0-array
0x102int8[2]short2s80x3016int8[16]intvs8
0x112uint8[2]short2u80x3116uint8[16]intvu8
0x124int16[2]int2s160x3216int8[8]intvs16
0x134uint16[2]int2s160x3316uint8[8]intvu16
0x148int32[2]int2s320x341boolbyteboolb
0x158uint32[2]int2u320x352bool[2]short2b
0x1616int16[2]int2s64vs640x363bool[3]int3b
0x1716uint16[2]int2u64vu640x374bool[4]int4b
0x188float[2]int2f0x3816bool[16]intvb
0x1916double[2]int2dvd0x38
0x1a3int8[3]int3s80x39
0x1b3uint8[3]int3u80x3a
0x1c6int16[3]int3s160x3b
0x1d6uint16[3]int3s160x3c
0x1e12int32[3]int3s320x3d
0x1f12uint32[3]int3u320x3e
0x2024int64[3]int3s640x3f
- -

Strings should be encoded and decoded according to the encoding specified in the packet header. Null termination is optional, however should be stripped during decoding.

-

All of these IDs are & 0x3F. Any value can be turned into an array by setting the 7th bit - high (| 0x40). Arrays of this form, in the data section, will be an aligned size: u32 - immediately followed by size bytes' worth of (unaligned!) values of the unmasked type.

- -
- Source code details -

The full table for these values can be found in libavs. This table contains the names of every tag, along - with additional information such as how many bytes that data type requires, and which parsing function - should be used.

-
- -
libavs-win32.dll:0x100782a8
-
-
-
- Note about the array type: -

While I'm not totally sure, I have a suspicion this type is used internally as a pseudo-type. Trying to - identify its function as a parsable type has some obvious blockers:

- -

All of the types have convenient printf-using helper functions that are used to emit them when - serializing XML. All except one.

- -

If we have a look inside the function that populates node sizes (libavs-win32.dll:0x1000cf00), - it has an explicit case, however is the same fallback as the default case.

- - -

In the same function, however, we can find a second (technically first) check for the array type.

- -

This seems to suggest that internally arrays are represented as a normal node, with the array - type, however when serializing it's converted into the array types we're used to (well, will be after the - next sections) by masking 0x40 onto the contained type.

-

Also of interest from this snippet is the fact that void, bin, str, - and attr cannot be arrays. void and attr make sense, however - str and bin are more interesting. I suspect this is because konami want to be able - to preallocate the memory, which wouldn't be possible with these variable length structures. -

-
- -

The data section

- -

This is where all the actual packet data is. For the most part, parsing this is the easy part. We traverse our - schema, and read values out of the packet according to the value indicated in the schema. Unfortunately, konami - decided all data should be aligned very specifically, and that gaps left during alignment should be backfilled - later. This makes both reading and writing somewhat more complicated, however the system can be fairly easily - understood.

-

Firstly, we divide the payload up into 4 byte chunks. Each chunk can be allocated to either store individual - bytes, shorts, or ints (these are the buckets in the table above). When reading or writing a value, we first - check if a chunk allocated to the desired type's bucket is available and has free/as-yet-unread space within it. - If so, we will store/read our data to/from there. If there is no such chunk, we claim the next unclaimed chunk - for our bucket.

-

For example, imagine we write the sequence byte, int, byte, short, byte, int, short. The final output should look like:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
0123456789101112131415
bytebytebyteintshortshortint
- -

While this might seem a silly system compared to just not aligning values, it is at least possible to intuit that it helps reduce wasted space. It should be noted that any variable-length structure, such as a string or an array, claims all chunks it encroaches on for the int bucket, disallowing the storage of bytes or shorts within them.

- -
- Implementing a packer -

While the intuitive way to understand the packing algorithm is via chunks and buckets, a far more efficient implementation can be made that uses three pointers. Rather than try to explain in words, hopefully this python implementation should suffice as explanation:

class Packer:
-    def __init__(self, offset=0):
-        self._word_cursor = offset
-        self._short_cursor = offset
-        self._byte_cursor = offset
-        self._boundary = offset % 4
-
-    def _next_block(self):
-        self._word_cursor += 4
-        return self._word_cursor - 4
-
-    def request_allocation(self, size):
-        if size == 0:
-            return self._word_cursor
-        elif size == 1:
-            if self._byte_cursor % 4 == self._boundary:
-                self._byte_cursor = self._next_block() + 1
-            else:
-                self._byte_cursor += 1
-            return self._byte_cursor - 1
-        elif size == 2:
-            if self._short_cursor % 4 == self._boundary:
-                self._short_cursor = self._next_block() + 2
-            else:
-                self._short_cursor += 2
-            return self._short_cursor - 2
-        else:
-            old_cursor = self._word_cursor
-            for _ in range(math.ceil(size / 4)):
-                self._word_cursor += 4
-            return old_cursor
-
-    def notify_skipped(self, no_bytes):
-        for _ in range(math.ceil(no_bytes / 4)):
-            self.request_allocation(4)

-
- - Prev page | Next page - - - - \ No newline at end of file diff --git a/proto/apsmanager.html b/proto/apsmanager.html deleted file mode 100644 index d794d41..0000000 --- a/proto/apsmanager.html +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - e-Amusement API - - - - - - - - - - - - -
ContentsTransport layerPacket formatApplication Protocol
- -

apsmanager

-

apsmanager.getstat

-

Request:

-
<call ...>
-    <apsmanager method="getstat" model*="" />
-</call>
-

Response:

-
<response>
-    <apsmanager status="status" />
-</response>
- \ No newline at end of file diff --git a/proto/cardmng.html b/proto/cardmng.html deleted file mode 100644 index 98c4a64..0000000 --- a/proto/cardmng.html +++ /dev/null @@ -1,232 +0,0 @@ - - - - - - - - e-Amusement API - - - - - - - - - - - - -
ContentsTransport layerPacket formatApplication Protocol
- -

cardmng

-

As the name might imply, this service is responsible for handling interactions with physical e-Amusement cards. - e-Amusement currently has two different types of cards in circulation. There are classic e-Amusement cards - making use of a magnetic stripe, and the newer RFID cards using FeliCa (these are probably what you have). They - are identified in requests using the cardtype attribute as in the below table. -

-

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. You can (and should) read about the algorithm used for - these IDs on the Card IDs page.

-

In the interest of not wasting space, cardid and cardtype will be omitted from - individual breakdowns where their meaning is obvious.

- -

Card types:

- - - - - - - - - - - - - - - -
cardtypeMeaning
1Old style magnetic stripe card
2FeliCa RFID card
- - - - -

cardmng.inquire

-

Request information about a card that has been inserted or touched against a reader.

- -

Request:

-
<call ...>
-    <cardmng method="inquire" cardid="" cardtype="" update="" model*="" />
-</call>
- - - - - -
updateShould the tracked last play time be updated by this inquiry? (Just a guess)
-

Response:

-
<response>
-    <cardmng status="status" refid="" dataid="" pcode=""  newflag="" binded="" expired=" ecflag="" useridflag="" extidflag="" lastupdate="" />
-</response>
-

To handle this request, we first must lookup if this cardid has ever been seen by our servers - before. If not, we abort with a 112 status. Otherwise, we proceeed to check if this card has been - 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.

- - - - - - - - - - - - - - - - - - - - - -
refidA reference to this card to be used in other requests
dataidAppears to be set the same as refid; presumably to allow different keys for game state vs - login details.
newflagInverse of binded
bindedHas a profile ever been created for this game (or an older version, requiring a migration) - (1 or 0)
expired? Just set to 0.
- -

cardmng.getrefid

-

Register a new card to this server.

-

Request:

-
<call ...>
-    <cardmng method="getrefid" cardtype="" cardid=" newflag="" passwd="" model*="" />
-</call>
- - - - - - - - - -
newflag?
passwdThe pin for this new user. Should always be a four digit number (and that's worth validating), - but it's passed as a string so could feasibly be anything desired.
-

Response:

-
<response>
-    <cardmng status="status" refid="" dataid="" pcode="" />
-</response>
- - - - - - - - - - - - - -
refidA reference to this card to be used in other requests
dataidAppears to be set the same as refid; presumably to allow different keys for game state vs - login details.
pcode? Not present in captured data.
- -

cardmng.bindmodel

-

Request:

-
<call ...>
-    <cardmng method="bindmodel" refid="" newflag="" model*="" />
-</call>
-

Response:

-
<response>
-    <cardmng status="status" dataid="" />
-</response>
- -

cardmng.bindcard

-

Request:

-
<call ...>
-    <cardmng method="bindcard" cardtype="" newid="" refid="" model*="" />
-</call>
-

Response:

-
<response>
-    <cardmng status="status" />
-</response>
- -

cardmng.authpass

-

Test a pin for a card. This request notably uses the refid, so required a - cardmng.inquire call to be made first. -

-

Request:

-
<call ...>
-    <cardmng method="authpass" refid="" pass="" model*="" />
-</call>
- - - - - - - - - -
refidThe reference we received either during cardmng.inquire or cardmng.getrefid - (the latter for new cards)
passThe pin to test. See cardmng.getrefid.
-

Response:

-
<response>
-    <cardmng status="status" />
-</response>
-

If the pin is valid, status should be 0. Otherwise, 116.

- -

cardmng.getkeepspan

-

Request:

-
<call ...>
-    <cardmng method="getkeepspan" model*="" />
-</call>
-

Response:

-
<response>
-    <cardmng status="status" keepspan="" />
-</response>
- -

cardmng.getkeepremain

-

Request:

-
<call ...>
-    <cardmng method="getkeepremain" refid="" model*="" />
-</call>
-

Response:

-
<response>
-    <cardmng status="status" keepremain="" />
-</response>
- -

cardmng.getdatalist

-

Request:

-
<call ...>
-    <cardmng method="getdatalist" refid="" model*="" />
-</call>
-

Response:

-
<response>
-    <cardmng status="status">
-        <item[]>
-            <mcode __type="str" />
-            <dataid __type="str" />
-            <regtime __type="str" />
-            <lasttime __type="str" />
-            <exptime __type="str" />
-            <expflag __type="u8" />
-        </item[]>
-    </cardmng>
-</response>
- \ No newline at end of file diff --git a/proto/dlstatus.html b/proto/dlstatus.html deleted file mode 100644 index eb7cc51..0000000 --- a/proto/dlstatus.html +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - - - e-Amusement API - - - - - - - - - - - - -
ContentsTransport layerPacket formatApplication Protocol
- -

dlstatus

-

dlstatus.done

-

Request:

-
<call ...>
-    <dlstatus method="done">
-        <url>
-            <param __type="str" />
-        </url>
-        <name __type="str" />
-        <size __type="s32" />
-    </dlstatus>
-</call>
- -

Response:

-
<response>
-    <dlstatus status="status">
-        <progress __type="s32" />
-    </dlstatus>
-</response>
- -

dlstatus.progress

-

Request:

-
<call ...>
-    <dlstatus method="progress" />
-        <progress __type="s32" />
-    </dlstatus>
-</call>
-

Response:

-
<response>
-    <dlstatus status="status" />
-</response>
- \ No newline at end of file diff --git a/proto/esign.html b/proto/esign.html deleted file mode 100644 index b5c7364..0000000 --- a/proto/esign.html +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - e-Amusement API - - - - - - - - - - - - -
ContentsTransport layerPacket formatApplication Protocol
- -

esign

-

esign.request

-

Request:

-
<call ...>
-    <esign method="request">
-        placeholder
-    </esign>
-</call>
-

Response:

-
<response>
-    <esign status="status">
-        placeholder
-    </esign>
-</response>
- \ No newline at end of file diff --git a/proto/esoc.html b/proto/esoc.html deleted file mode 100644 index 1d061b1..0000000 --- a/proto/esoc.html +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - e-Amusement API - - - - - - - - - - - - -
ContentsTransport layerPacket formatApplication Protocol
- -

esoc

-

esoc.read

-

Request:

-
<call ...>
-    <esoc method="read">
-        <senddata />
-    </esoc>
-</call>
-

Response:

-
<response>
-    <esoc status="status">
-        <recvdata />
-    </esoc>
-</response>
-

Go figure.

- -

esoc.write

-

Request:

-
<call ...>
-    <esoc method="write">
-        <senddata />
-    </esoc>
-</call>
-

Response:

-
<response>
-    <esoc status="status" />
-</response>
- \ No newline at end of file diff --git a/proto/eventlog.html b/proto/eventlog.html deleted file mode 100644 index c1f9d54..0000000 --- a/proto/eventlog.html +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - - - e-Amusement API - - - - - - - - - - - - -
ContentsTransport layerPacket formatApplication Protocol
- -

eventlog

-

eventlog.write

-

Request:

-
<call ...>
-    <eventlog method="write">
-        <retrycnt __type="u32" />
-        <data>
-            <eventid __type="str" />
-            <eventorder __type="s32" />
-            <pcbtime __type="u64" />
-            <gamesession __type="s64" />
-            <strdata1 __type="str" />
-            <strdata2 __type="str" />
-            <numdata1 __type="s64" />
-            <numdata2 __type="s64" />
-            <locationid __type="str" />
-        </data>
-    </eventlog>
-</call>
-

Event ID list:

- -

Response:

-
<response>
-    <eventlog status="status">
-        <gamesession __type="s64" />
-        <logsendflg __type="s32" />
-        <logerrlevel __type="s32" />
-        <evtidnosendflg __type="s32" />
-    </eventlog>
-</response>
- \ No newline at end of file diff --git a/proto/message.html b/proto/message.html deleted file mode 100644 index dd7852e..0000000 --- a/proto/message.html +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - e-Amusement API - - - - - - - - - - - - -
ContentsTransport layerPacket formatApplication Protocol
- -

message

-

message.get

-

Request:

-
<call ...>
-    <message method="get" model*="" />
-</call>
-

Response:

-
<response>
-    <message expire="" status="status">
-        <item[] name="" start="" end="" data="" />
-    </message>
-</response>
- \ No newline at end of file diff --git a/proto/package.html b/proto/package.html deleted file mode 100644 index f8c6910..0000000 --- a/proto/package.html +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - - - e-Amusement API - - - - - - - - - - - - -
ContentsTransport layerPacket formatApplication Protocol
- -

package

-

package.list

-

Request:

-
<call ...>
-    <package method="list" pkgtype="pkgtype" model*="" />
-</call>
-

all is the only currently observed value for pkgtype

-

Response:

-
<response>
-    <package status="status">
-        <item[] url="" />
-    </package>
-</response>
-

A list of all packages available for download.

- -

package.intend

-

Request:

-
<call ...>
-    <package method="intend" url="" model*="" />
-</call>
-

Response:

-
<response>
-    <package status="status" />
-</response>
- \ No newline at end of file diff --git a/proto/pcbevent.html b/proto/pcbevent.html deleted file mode 100644 index 4f8009a..0000000 --- a/proto/pcbevent.html +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - e-Amusement API - - - - - - - - - - - - -
ContentsTransport layerPacket formatApplication Protocol
- -

pcbevent

-

pcbevent.put

-

Request:

-
<call ...>
-    <pcbevent method="put">
-        <time __type="time" />
-        <seq __type="u32" />
-        <item[]>
-            <name __type="str" />
-            <value __type="s32" />
-            <time __type="time" />
-        </item[]>
-    </pcbevent>
-</call>
-

Response:

-
<response>
-    <pcbevent status="status" />
-</response>
- \ No newline at end of file diff --git a/proto/pcbtracker.html b/proto/pcbtracker.html deleted file mode 100644 index a80aa6d..0000000 --- a/proto/pcbtracker.html +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - e-Amusement API - - - - - - - - - - - - -
ContentsTransport layerPacket formatApplication Protocol
- -

pcbtracker

-

pcbtracker.alive

-

Request:

-
<call ...>
-    <pcbtracker method="alive" model*="" hardid="" softid="" accountid="" agree="" ecflag="" />
-</call>
-

ecflag here is determining if the arcade operator allows the use of paseli on this machine.

-

agree@ and ecflag@ appear to either be totally non present, or present with a value of - "1", but then again I may be reading the code wrong, so take that with a pinch of salt. -

-

Response:

-
<response>
-    <pcbtracker status="" time="" limit="" ecenable="" eclimit="" >
-</response>
-

As you might guess, ecenable@ is therefore the flag to determine if paseli is enabled (i.e. the - arcade operator and the server both allow its use).

- \ No newline at end of file diff --git a/proto/services.html b/proto/services.html deleted file mode 100644 index fe8c7d3..0000000 --- a/proto/services.html +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - - - e-Amusement API - - - - - - - - - - - - -
ContentsTransport layerPacket formatApplication Protocol
- -

services

-

services.get

-

Request:

-
<call ...>
-    <services method="get" model*="" >
-        <info>
-            <AVS2 __type="str">AVS2 version</AVS2>
-        </info>
-    </services>
-</call>
-

Response:

-
<response>
-    <services expire="" method="get" mode="" status="status">
-        <item[] name="service" url="url" />
-    </services>
-</response>
-

Known services are:

- -

Most of these will usually just return the URL to the eAmuse server (or your fake one ;D). ntp is a - notable exception, unless you're planning on reimplementing NTP. keepalive will likely alsop be a - custom URL with query parameters pre-baked.

-

mode is one of operation, debug, test, or - factory. -

- \ No newline at end of file diff --git a/proto/sidmgr.html b/proto/sidmgr.html deleted file mode 100644 index 61e536b..0000000 --- a/proto/sidmgr.html +++ /dev/null @@ -1,94 +0,0 @@ - - - - - - - - e-Amusement API - - - - - - - - - - - - -
ContentsTransport layerPacket formatApplication Protocol
- -

sidmgr

-

sidmgr.create

-

Request:

-
<call ...>
-    <sidmgr method="create">
-        <cardtype __type="str" />
-        <cardid __type="str" />
-        <cardgid __type="str" />
-        <steal __type="u8" />
-    </sidmgr>
-</call>
-

Response:

-
<response>
-    <sidmgr status="status">
-        <state __type="u32" />
-        <e_count __type="u8" />
-        <last __type="time" />
-        <locked __type="time" />
-        <sid __type="str" />
-        <cardid_status __type="u8" />
-        <refid __type="str" />
-    </sidmgr>
-</response>
- -

sidmgr.open

-

Request:

-
<call ...>
-    <sidmgr method="open" sid="" >
-        <pass __type="str" />
-    </sidmgr>
-</call>
-

Response:

-
<response>
-    <sidmgr status="status">
-        <state __type="u32" />
-        <refid __type="str" />
-        <locked __type="time" />
-    </sidmgr>
-</response>
- -

sidmgr.touch

-

Request:

-
<call ...>
-    <sidmgr method="touch" sid="" />
-</call>
-

Response:

-
<response>
-    <sidmgr status="status" />
-</response>
- -

sidmgr.branch

-

Request:

-
<call ...>
-    <sidmgr method="branch" sid="" />
-</call>
-

Response:

-
<response>
-    <sidmgr status="status" />
-</response>
- -

sidmgr.close

-

Request:

-
<call ...>
-    <sidmgr method="close" sid="" />
-        <cause __type="u32" />
-    </sidmgr>
-</call>
-

Response:

-
<response>
-    <sidmgr status="status" />
-</response>
- \ No newline at end of file diff --git a/proto/traceroute.html b/proto/traceroute.html deleted file mode 100644 index f694510..0000000 --- a/proto/traceroute.html +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - e-Amusement API - - - - - - - - - - - - -
ContentsTransport layerPacket formatApplication Protocol
- -

traceroute

-

traceroute.send

-

Request:

-
<call ...>
-    <traceroute proto="" method="send">
-        <hop[]>
-            <valid __type="bool">
-            <addr __type="ip4">
-            <usec __type="u64">
-        </hop[]>
-    </traceroute>
-</call>
-

hop repeats for every hop (unsurprisingly)

-

Response:

-
<response>
-    <traceroute status="status" />
-</response>
- \ No newline at end of file diff --git a/proto/userdata.html b/proto/userdata.html deleted file mode 100644 index 502947e..0000000 --- a/proto/userdata.html +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - - e-Amusement API - - - - - - - - - - - - -
ContentsTransport layerPacket formatApplication Protocol
- -

userdata

-

userdata.read

-

Request:

-
<call ...>
-    <userdata method="read" card*="" model*="" label="" />
-</call>
-

Response:

-
<response>
-    <userdata status="status" time="">
-        <b[] __type="" />
-    </userdata>
-</response>
-

__type here can be either bin or str

- -

userdata.write

-

Request:

-
<call ...>
-    <userdata method="write" card="" time="" model*="" label*="" >
-        <b[] __type="str" />
-    </userdata>
-</call>
-

Response:

-
<response>
-    <userdata status="status" />
-</response>
- \ No newline at end of file diff --git a/protocol.html b/protocol.html deleted file mode 100644 index fb8b85b..0000000 --- a/protocol.html +++ /dev/null @@ -1,284 +0,0 @@ - - - - - - - - Packet format | eAmuse API - - - - - - - - - - - - - -
ContentsTransport layerPacket formatApplication Protocol
- -

Application Protocol

-

As the previous pages have eluded to (you did read them, didn't you?), eAmuse uses HTTP as its main way of - getting data around. This means we need an HTTP server running but, as we'll see, we don't need to think too - hard about that.

-

Every request made is a POST request, to //<model>/<module>/<method>, - with its body being encoded data as described in the previous sections. In addition to the - X-Compress: and X-Eamuse-Info: headers previously detailed, there is also a - X-PCB-ID: header. that can be set. Your machine's PCB ID uniquely defines the physical board. This - header is added in out-bound requests, and allows the server to identify you. Importantly, it's also the value - that the server uses to identify which machines are authorized to be on the network, and which are not. -

-

Every request is followed immediately by a response. Any response code other than 200 is considered - a failure.

- -
- Source code details -
- -
libavs-win32-ea3.dll:0x1000f8e7
-
-
- -

All requests follow a basic format:

-
<call model="model" srcid="srcid" tag="tag">
-    <module method="method" ...attributes>
-        children
-    </module>
-</call>
-

The responses follow a similar format:

-
<response>
-    <module status="status" ...attributes>
-        children
-    </module>
-</response>
-

With "0" being a successful status. Convention is to identify a specific method as - module.method, and we'll be following this convention in this document too. There are - a lot of possible methods, so the majority of this document is a big reference for them all. There are a - number of generic methods, and a number of game specific ones. If you haven't clocked yet, I've been working on - an SDVX 4 build for most of these pages, and each game also comes with its own set of game-specific methods. - These are namespaces under the game.%s module and, in the case of SDVX 4, are all - game.sv4_method. I may or may not document the SDVX 4 specific methods, but I've listed them - here anyway for completeness. -

-

Paths in the XML bodies are formatted using an XPath-like syntax. That is, status@/response gets the - status attribute from response, and response/eacoin/sequence would return - that node's value. -

-

NOTE: I am using the non-standard notation of <node* ... and - <node attr*="" ... to indicate that an attribute or node is not always present! Additionally, I - am going to use the notation of <node[]> to indicate that a node repeats. -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
StatusMeaning
0Success
109No profile
110Not allowed
112Card not found (cardmng.inquire)
116Card pin invalid (cardmng.authpass)
-
- How to reverse engineer these calls -

Turns out bemani have been quite sensible in how they implemented their code for creating structures, so it's - rather readable. That said, if you've been using Ghidra (like me!), this is the time to switch to IDA. I'll - let the below screenshots below speak for themselves: -

- -
- Ghidra - - -
-
- IDA Pro - - -
- -

I know which of these I'd rather use for reverse engineering (sorry, Ghidra)!

-
- -

Possible XRPC requests

- - - - Totally undocumented services (based on services.get): - -

I'll try and figure these out in due course, promise!

- \ No newline at end of file diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..4fc4436 --- /dev/null +++ b/templates/base.html @@ -0,0 +1,25 @@ + + + + + + + + {% block title %}{% endblock %}{% if self.title() %} | {% endif %}e-Amusement API + + + + + + + + + + + + +
ContentsTransport layerPacket formatApplication Protocol
+ {% block body %}{% endblock %} + + + \ No newline at end of file diff --git a/cardid.html b/templates/pages/cardid.html similarity index 60% rename from cardid.html rename to templates/pages/cardid.html index 399b36d..91781da 100644 --- a/cardid.html +++ b/templates/pages/cardid.html @@ -1,34 +1,14 @@ - - - - - - - - e-Amusement API - - - - - - - - - - - - -
ContentsTransport layerPacket formatApplication Protocol
- -

Card ID generation

-
- I'm just here for code. -

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.

-
import binascii
+{% extends "base.html" %}
+{% block body %}
+

Card ID generation

+
+ I'm just here for code. +

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.

+
import binascii
 from Crypto.Cipher import DES3
 
 
@@ -39,183 +19,183 @@ ALPHABET = "0123456789ABCDEFGHJKLMNPRSTUWXYZ"
 
 
 def enc_des(uid):
-    cipher = DES3.new(_KEY, DES3.MODE_CBC, iv=b'\0' * 8)
-    return cipher.encrypt(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)
+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))
+chk = sum(data[i] * (i % 3 + 1) for i in range(15))
 
-    while chk > 31:
-        chk = (chk >> 5) + (chk & 31)
+while chk > 31:
+    chk = (chk >> 5) + (chk & 31)
 
-    return chk
+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))
+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))
+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"
+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")
+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"
+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 = 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)
+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)
+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")
+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"
+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]
+for i in range(13, 0, -1):
+    card[i] ^= card[i - 1]
 
-    card[0] ^= card_type
+card[0] ^= card_type
 
-    card_id = dec_des(pack_5(card[:13])[:8])[::-1]
-    card_id = binascii.hexlify(card_id).decode().upper()
+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 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"
+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"
 
-
-

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.

-

KONAMI IDs have an alphabet of 0123456789ABCDEFGHJKLMNPRSTUWXYZ (note that IOQV are - absent), whereas e-A IDs (yeah I'm not typing that out every time) have an alphabet of - 0123456789ABCDEF (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. -

-

Converting KONAMI IDs to e-Amusement IDs

-

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:

- - - - - - - - - - - - - - - - - - - - - + +

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.

+

KONAMI IDs have an alphabet of 0123456789ABCDEFGHJKLMNPRSTUWXYZ (note that IOQV are + absent), whereas e-A IDs (yeah I'm not typing that out every time) have an alphabet of + 0123456789ABCDEF (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. +

+

Converting KONAMI IDs to e-Amusement IDs

+

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:

+
0123456789101112131415
+ - - - - + + + + + + + + + + + + + + + + -
e-Amusement IDCheck byteCard typeChecksum0123456789101112131415
+ + + e-Amusement ID + Check byte + Card type + Checksum + + -

Due to how IDs are constructed, there are a number of checks we can perform to validate an ID:

-
    -
  • Parity check: [11] % 2 == [12] % 2
  • -
  • Encoding check: [13] == [12] ^ 1
  • -
  • Checksum: [15] == checksum([0..14])
  • -
  • Post-decoding, FeliCa cards start with 0 and magnetic strip cards start with E004. -
  • -
  • Card type: [14] == 1 (magnetic stripe) or [14] == 2 (FeliCa)
  • -
+

Due to how IDs are constructed, there are a number of checks we can perform to validate an ID:

+
    +
  • Parity check: [11] % 2 == [12] % 2
  • +
  • Encoding check: [13] == [12] ^ 1
  • +
  • Checksum: [15] == checksum([0..14])
  • +
  • Post-decoding, FeliCa cards start with 0 and magnetic strip cards start with E004. +
  • +
  • Card type: [14] == 1 (magnetic stripe) or [14] == 2 (FeliCa)
  • +
-

To decrypt a KONAMI ID, at a high level we must:

-
    -
  • Remove the XOR encoding
  • -
  • 5-pack the ID
  • -
  • Decrypt the packed ID
  • -
  • Reverse the bytes
  • -
  • Convert to upper-case hex
  • -
-

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:

-
for i from 13 to 1 inclusive:
+

To decrypt a KONAMI ID, at a high level we must:

+
    +
  • Remove the XOR encoding
  • +
  • 5-pack the ID
  • +
  • Decrypt the packed ID
  • +
  • Reverse the bytes
  • +
  • Convert to upper-case hex
  • +
+

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:

+
for i from 13 to 1 inclusive:
     card[i] ^= card[i - 1]
 card[0] ^= card_type
-

The values in the e-Amusement ID field above will all maximally be 31 - (0b11111) therefore before we perform the decryption step we first densly pack these 5-bit - integers. That is, 0b11111 0b00000 0b11111 would be packed to 0b11111000 0b00111110. - This value will be 8 bytes long. We can now decrypt it, reverse it, and convert to hex. +

The values in the e-Amusement ID field above will all maximally be 31 + (0b11111) therefore before we perform the decryption step we first densly pack these 5-bit + integers. That is, 0b11111 0b00000 0b11111 would be packed to 0b11111000 0b00111110. + This value will be 8 bytes long. We can now decrypt it, reverse it, and convert to hex. +

+
+ Implementing 5-bit packing +

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.

- Implementing 5-bit packing -

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. -

-
- In most languages? -

Haha well you see we can actually cheat and use string manipulation. Wasteful? Incredibly. Efficient? Not - at all. Quick and easy? Yup!

-
def pack_5(data):
+        In most languages?
+        

Haha well you see we can actually cheat and use string manipulation. Wasteful? Incredibly. Efficient? Not + at all. Quick and easy? Yup!

+
def pack_5(data):
     data = "".join(f"{i:05b}" for i in data)
     if len(data) % 8 != 0:
         data += "0" * (8 - (len(data) % 8))
@@ -226,39 +206,39 @@ def unpack_5(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))
-

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.

-
+

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.

+
-

Converting e-Amusement IDs to KONAMI IDs

-

This is mostly the above process, in reverse, but we need to make sure to populate some of the extra check bytes. -

-

Before we start, we need to make sure we have a valid card! FeliCa cards (type 2) will begin with a - single null nibble, and magnetic stripe cards (type 1) with the word E004. We then - parse the entire ID as a hex string, giving us an 8-byte value.

-

This value is reversed, and encrypted. After encryption, we need to unpack it from it's - 5-packed format. This is the same process as unpacking, 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.

-

We pad the 13 bytes with 3 extra null bytes, then apply our checks to the ID:

-
card[0] ^= card_type
+

Converting e-Amusement IDs to KONAMI IDs

+

This is mostly the above process, in reverse, but we need to make sure to populate some of the extra check bytes. +

+

Before we start, we need to make sure we have a valid card! FeliCa cards (type 2) will begin with a + single null nibble, and magnetic stripe cards (type 1) with the word E004. We then + parse the entire ID as a hex string, giving us an 8-byte value.

+

This value is reversed, and encrypted. After encryption, we need to unpack it from it's + 5-packed format. This is the same process as unpacking, 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.

+

We pad the 13 bytes with 3 extra null bytes, then apply our checks to the ID:

+
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] = checksum(card)
-

This leaves us with 16 values ranging from 0 to 31, which we apply as indecies to our alphabet to produce the - final ID.

+

This leaves us with 16 values ranging from 0 to 31, which we apply as indecies to our alphabet to produce the + final ID.

-

Checksums

-

As if the encryption and XOR wasn't enough, card IDs also contain a checksum to make absolutely 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:

-
checksum(bytes):
+

Checksums

+

As if the encryption and XOR wasn't enough, card IDs also contain a checksum to make absolutely 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:

+
checksum(bytes):
     chk = 0
     for i from 0 to 14 inclusive:
         chk += bytes[i] * (i % 3 + 1)
@@ -268,31 +248,31 @@ card[15] = checksum(card)
return chk
-

The DES scheme used

-

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 IV. The key is quite easy to find if you hit the right binaries with - strings. Alternatively, check the source of this page. 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. -

- +
+ I'm curious how Bemani implemented this in their own code! +

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 soundvoltex.dll:0x1027316f + and you should see everything you need. +

+

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.

- I'm curious how Bemani implemented this in their own code! -

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 soundvoltex.dll:0x1027316f - and you should see everything you need. -

-

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.

-
- Show me that! -
DES_KEYMAP = [
+        Show me that!
+        
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],
@@ -482,7 +462,6 @@ def load_key(key):
     for i in range(24):
         key_data[i] = 2 * key[i % len(key)]
     des3_setkey(KEY_DATA, key_data)
-
- - \ No newline at end of file +
+{% endblock %} \ No newline at end of file diff --git a/templates/pages/index.html b/templates/pages/index.html new file mode 100644 index 0000000..a98cc9e --- /dev/null +++ b/templates/pages/index.html @@ -0,0 +1,84 @@ +{% extends "base.html" %} +{% block body %} +

Benami/Konami e-Amusement API

+

Why?

+

I was curious how these APIs work, yet could find little to nothing on Google. There are a number of + closed-source projects, with presumably similarly closed-source internal documentation, and a scattering of + implementations of things, yet I couldn't find a site that actually just documents how the API works. If I'm + going to have to reverse engineer an open source project (or a closed source one, for that matter), I might as + well just go reverse engineer an actual game (or it's stdlib, as most of my time has been spent currently).

+

For the sake of being lazy, I'll probably end up calling it eAmuse more than anything else throughout these + pages. Other names you may come across include httac and xrpc. The latter are the + suite of HTTP functions used in the Bemani stdlib, and the name of their communication protocol they implement + at the application layer, but whenever someone refers to any of them in the context of a rhythm game, they will + be referring to the things documented here.

+

These pages are very much a work in progress, and are being written as I reverse engineer parts of the + protocol. I've been asserting all my assumptions by writing my own implementation as I go, however it currently + isn't sharable quality code and, more importantly, the purpose of these pages is to make implementation of one's + own code hopefully trivial (teach a man to fish, and all that).

+

Sharing annotated sources for all of the games' stdlibs would be both impractical and unwise. Where relevant + however I try to include snippets to illustrate concepts, and have included their locations in the source for if + you feel like taking a dive too.

+

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.

+ +

Code snippets

+

Across these pages there are a number of code snippets. They roughly break down into three categories:

+
    +
  • Assembly: Directly disassembled code from game binaries
  • +
  • C: Either raw decompilation, or slightly cleaned up decompilation
  • +
  • Python: Snippets from my local testing implementations
  • +
  • Pseudocode: Used to illustrate some points. Note that it probably started life as Python before being + pseudo'd
  • +
+

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?).

+ +

Contents

+
    +
  1. Transport layer
  2. +
      +
    1. Packet structure
    2. +
    3. Types
    4. +
    +
  3. The inner packet structure
  4. +
      +
    1. XML packets
    2. +
    3. Binary packed packets
    4. +
    5. Binary schemas
    6. +
    7. Binary data
    8. +
    +
  5. Communication protocol details
  6. +
      +
    • There are a crazy number of sub pages here, so just go check the contents there.
    • +
    +
  7. Misc pages
  8. +
      +
    1. Parsing and converting card IDs
    2. +
    +
+ +

Getting started

+

My aim with these pages is to cover as much as possible, so you don't need to try and figure them out yourself. + That said, being able to follow along yourself will almost certainly help get more out of this. For following + along with source code, you're really going to want to grab yourself a dumped copy of a game (it's going to be a + lot easier, and cheeper, than dumping one yourself). I trust you can figure out where to find that.

+

For network related things, your options are a little broader. The ideal would be physical ownership of a + cabinet, and a subscription to genuine e-amusement. Odds are you don't have both of those :P. A connection to an + alternative network works just as well. In the more likely case that you don't have a physical cabinet, it's + time to crack out that dumped copy of a game and just run it on your own PC (or a VM, if you're not on Windows) + (odds are whatever you downloaded came with the program you'll need to start it pre-packaged. If not, it rhymes + with rice.).

+

You will also need a local e-amusement-emulating server. By the time I'm done with these pages, there will + hopefully be everything you need to be able to write your own. Unfortunately I'm not finished writing them; + depending on where you acquired your game, it may have shipped with one of said servers. If it didn't, Asphyxia + CORE will do the trick (yes, it's closed source).

+

If this all sounds like way too much work, and/or you're just here because of curiosity, I plan to prepare some + pcaps of network traffic to play around with without needing a running copy of a game or a network tap on a cab. +

+ +Next page + +

This site intentionally looks not-great. I don't feel like changing that, and honestly quite like the + aesthetic.

+{% endblock %} \ No newline at end of file diff --git a/templates/pages/packet.html b/templates/pages/packet.html new file mode 100644 index 0000000..051cbc9 --- /dev/null +++ b/templates/pages/packet.html @@ -0,0 +1,881 @@ +{% extends "base.html" %} +{% block body %} +

Packet format

+ +

eAmuse uses XML for its application layer payloads*. This XML is either verbatim, or in a custom packed binary + format.
*Newer games use JSON, but this page is about XML.

+ + +

The XML format

+ +

Each tag that contains a value has a __type attribute that identifies what type it is. Array types + have a __count attribute indicating how many items are in the array. Binary blobs additionally have + a __size attribute indicating their length (this is notably not present on strings, however).

+

It is perhaps simpler to illustrate with an example, so:

+
<?xml version='1.0' encoding='UTF-8'?>
+<call model="KFC:J:A:A:2019020600" srcid="1000" tag="b0312077">
+    <eventlog method="write">
+        <retrycnt __type="u32" />
+        <data>
+            <eventid __type="str">G_CARDED</eventid>
+            <eventorder __type="s32">5</eventorder>
+            <pcbtime __type="u64">1639669516779</pcbtime>
+            <gamesession __type="s64">1</gamesession>
+            <strdata1 __type="str" />
+            <strdata2 __type="str" />
+            <numdata1 __type="s64">1</numdata1>
+            <numdata2 __type="s64" />
+            <locationid __type="str">ea</locationid>
+        </data>
+    </eventlog>
+</call>
+

Arrays are encoded by concatenating every value together, with spaces between them. Data types that have multiple + values, are serialized similarly.

+

Therefore, an element storing an array of 3u8 ([(1, 2, 3), (4, 5, 6)]) would look like + this

+
<demo __type="3u8" __count="2">1 2 3 4 5 6</demo>
+

Besides this, this is otherwise a rather standard XML.

+ +

Packed binary overview

+ +

Many packets, rather than using a string-based XML format, use a custom binary packed format instead. While it + can be a little confusing, remembering that this is encoding an XML tree can make it easier to parse.

+

To start with, let's take a look at the overall structure of the packets.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
0123456789101112131415
A0CE~EHead length
Schema definition
FFAlign
Data length
Payload
Align
+

Every packet starts with the magic byte 0xA0. Following this is the content byte, the encoding byte, + and then the 2's compliment of the encoding byte.

+

Currently known possible values for the content byte are:

+ + + + + + + + + + + + + + + + + + + + + + + +
CContent
0x42Compressed data
0x43Compressed, no data
0x45Decompressed data
0x46Decompressed, no data
+

Decompressed packets contain an XML string. Compressed packets are what we're interested in here.

+

The encoding flag indicates the encoding for all string types in the packet (more on those later). Possible + values are:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
E~EEncoding name
0x200xDFASCII
0x400xBFISO-8859-1ISO_8859-1
0x600x9FEUC-JPEUCJPEUC_JP
0x800x7FSHIFT-JISSHIFT_JISSJIS
0xA00x5FUTF-8UTF8
+
+ Source code details +

The full table for these values can be found in libavs.

+
+ +
libavs-win32.dll:0x1006b960
+
+

A second table exists just before this on in the source, responsible for the + <?xml version='1.0' encoding='??'?> line in XML files. +

+
+ +
libavs-win32.dll:0x1006b940
+
+

This is indexed using the following function, which maps the above encoding IDs to 1, 2, 3, 4 and 5 + respectively.

+
char* xml_get_encoding_name(uint encoding_id) {
+    return ENCODING_NAME_TABLE[((encoding_id & 0xe0) >> 5) * 4];
+}
+
+

While validating ~E isn't technically required, it acts as a useful assertion that the packet being + parsed is valid.

+ +

The packet schema header

+

Following the 4 byte header, is a 4 byte integer containing the length of the next part of the header (this is + technically made redundant as this structure is also terminated).

+

This part of the header defines the schema that the main payload uses.

+ +

A tag definition looks like:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
0123456789101112131415
TypenlenTag name
Attributes and childrenFE
+ +

Structure names are encoded as densely packed 6 bit values, length prefixed (nlen). The acceptable + alphabet is 0123456789:ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz, and the packed values + are indecies within this alphabet.

+ +

The children can be a combination of either attribute names, or child tags. Attribute names are represented by + the byte 0x2E followed by a length prefixed name as defined above. Child tags follow the above + format. Type 0x2E must therefore be considered reserved as a possible structure type.

+ +

Attributes (type 0x2E) represent a string attribute. Any other attribute must be defined as a child + tag. Is it notable that 0 children is allowable, which is how the majority of values are encoded.

+

All valid IDs, and their respective type, are listed in the following table. The bucket column here will be + used later when unpacking the main data, so we need not worry about it for now, but be warned it exists and is + possibly the least fun part of this format.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IDBytesC typeBucketXML namesIDBytesC typeBucketXML names
0x010void-void0x2124uint64[3]int3u64
0x021int8bytes80x2212float[3]int3f
0x031uint8byteu80x2324double[3]int3d
0x042int16shorts160x244int8[4]int4s8
0x052uint16shorts160x254uint8[4]int4u8
0x064int32ints320x268int16[4]int4s16
0x074uint32intu320x278uint8[4]int4s16
0x088int64ints640x2816int32[4]int4s32vs32
0x098uint64intu640x2916uint32[4]int4u32vs32
0x0aprefixchar[]intbinbinary0x2a32int64[4]int4s64
0x0bprefixchar[]intstrstring0x2b32uint64[4]int4u64
0x0c4uint8[4]intip40x2c16float[4]int4fvf
0x0d4uint32inttime0x2d32double[4]int4d
0x0e4floatintfloatf0x2eprefixchar[]intattr
0x0f8doubleintdoubled0x2f0-array
0x102int8[2]short2s80x3016int8[16]intvs8
0x112uint8[2]short2u80x3116uint8[16]intvu8
0x124int16[2]int2s160x3216int8[8]intvs16
0x134uint16[2]int2s160x3316uint8[8]intvu16
0x148int32[2]int2s320x341boolbyteboolb
0x158uint32[2]int2u320x352bool[2]short2b
0x1616int16[2]int2s64vs640x363bool[3]int3b
0x1716uint16[2]int2u64vu640x374bool[4]int4b
0x188float[2]int2f0x3816bool[16]intvb
0x1916double[2]int2dvd0x38
0x1a3int8[3]int3s80x39
0x1b3uint8[3]int3u80x3a
0x1c6int16[3]int3s160x3b
0x1d6uint16[3]int3s160x3c
0x1e12int32[3]int3s320x3d
0x1f12uint32[3]int3u320x3e
0x2024int64[3]int3s640x3f
+ +

Strings should be encoded and decoded according to the encoding specified in the packet header. Null termination is + optional, however should be stripped during decoding.

+

All of these IDs are & 0x3F. Any value can be turned into an array by setting the 7th bit + high (| 0x40). Arrays of this form, in the data section, will be an aligned size: u32 + immediately followed by size bytes' worth of (unaligned!) values of the unmasked type.

+ +
+ Source code details +

The full table for these values can be found in libavs. This table contains the names of every tag, along + with additional information such as how many bytes that data type requires, and which parsing function + should be used.

+
+ +
libavs-win32.dll:0x100782a8
+
+
+
+ Note about the array type: +

While I'm not totally sure, I have a suspicion this type is used internally as a pseudo-type. Trying to + identify its function as a parsable type has some obvious blockers:

+ +

All of the types have convenient printf-using helper functions that are used to emit them when + serializing XML. All except one.

+ +

If we have a look inside the function that populates node sizes (libavs-win32.dll:0x1000cf00), + it has an explicit case, however is the same fallback as the default case.

+ + +

In the same function, however, we can find a second (technically first) check for the array type.

+ +

This seems to suggest that internally arrays are represented as a normal node, with the array + type, however when serializing it's converted into the array types we're used to (well, will be after the + next sections) by masking 0x40 onto the contained type.

+

Also of interest from this snippet is the fact that void, bin, str, + and attr cannot be arrays. void and attr make sense, however + str and bin are more interesting. I suspect this is because konami want to be able + to preallocate the memory, which wouldn't be possible with these variable length structures. +

+
+ +

The data section

+ +

This is where all the actual packet data is. For the most part, parsing this is the easy part. We traverse our + schema, and read values out of the packet according to the value indicated in the schema. Unfortunately, konami + decided all data should be aligned very specifically, and that gaps left during alignment should be backfilled + later. This makes both reading and writing somewhat more complicated, however the system can be fairly easily + understood.

+

Firstly, we divide the payload up into 4 byte chunks. Each chunk can be allocated to either store individual + bytes, shorts, or ints (these are the buckets in the table above). When reading or writing a value, we first + check if a chunk allocated to the desired type's bucket is available and has free/as-yet-unread space within it. + If so, we will store/read our data to/from there. If there is no such chunk, we claim the next unclaimed chunk + for our bucket.

+

For example, imagine we write the sequence byte, int, byte, short, byte, int, short. The final output + should look like:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
0123456789101112131415
bytebytebyteintshortshortint
+ +

While this might seem a silly system compared to just not aligning values, it is at least possible to intuit that it + helps reduce wasted space. It should be noted that any variable-length structure, such as a string or an array, + claims all chunks it encroaches on for the int bucket, disallowing the storage of bytes or shorts + within them.

+ +
+ Implementing a packer +

While the intuitive way to understand the packing algorithm is via chunks and buckets, a far more efficient + implementation can be made that uses three pointers. Rather than try to explain in words, hopefully this python + implementation should suffice as explanation: +

class Packer:
+    def __init__(self, offset=0):
+        self._word_cursor = offset
+        self._short_cursor = offset
+        self._byte_cursor = offset
+        self._boundary = offset % 4
+
+    def _next_block(self):
+        self._word_cursor += 4
+        return self._word_cursor - 4
+
+    def request_allocation(self, size):
+        if size == 0:
+            return self._word_cursor
+        elif size == 1:
+            if self._byte_cursor % 4 == self._boundary:
+                self._byte_cursor = self._next_block() + 1
+            else:
+                self._byte_cursor += 1
+            return self._byte_cursor - 1
+        elif size == 2:
+            if self._short_cursor % 4 == self._boundary:
+                self._short_cursor = self._next_block() + 2
+            else:
+                self._short_cursor += 2
+            return self._short_cursor - 2
+        else:
+            old_cursor = self._word_cursor
+            for _ in range(math.ceil(size / 4)):
+                self._word_cursor += 4
+            return old_cursor
+
+    def notify_skipped(self, no_bytes):
+        for _ in range(math.ceil(no_bytes / 4)):
+            self.request_allocation(4)
+

+
+ +Prev page | Next page +{% endblock %} \ No newline at end of file diff --git a/templates/pages/proto/apsmanager.html b/templates/pages/proto/apsmanager.html new file mode 100644 index 0000000..b753aba --- /dev/null +++ b/templates/pages/proto/apsmanager.html @@ -0,0 +1,13 @@ +{% extends "base.html" %} +{% block body %} +

apsmanager

+

apsmanager.getstat

+

Request:

+
<call ...>
+    <apsmanager method="getstat" model*="" />
+</call>
+

Response:

+
<response>
+    <apsmanager status="status" />
+</response>
+{% endblock %} \ No newline at end of file diff --git a/templates/pages/proto/cardmng.html b/templates/pages/proto/cardmng.html new file mode 100644 index 0000000..ea7af74 --- /dev/null +++ b/templates/pages/proto/cardmng.html @@ -0,0 +1,212 @@ +{% extends "base.html" %} +{% block body %} +

cardmng

+

As the name might imply, this service is responsible for handling interactions with physical e-Amusement cards. + e-Amusement currently has two different types of cards in circulation. There are classic e-Amusement cards + making use of a magnetic stripe, and the newer RFID cards using FeliCa (these are probably what you have). They + are identified in requests using the cardtype attribute as in the below table. +

+

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. You can (and should) read about the algorithm used for + these IDs on the Card IDs page.

+

In the interest of not wasting space, cardid and cardtype will be omitted from + individual breakdowns where their meaning is obvious.

+ +

Card types:

+ + + + + + + + + + + + + + + +
cardtypeMeaning
1Old style magnetic stripe card
2FeliCa RFID card
+ + + + +

cardmng.inquire

+

Request information about a card that has been inserted or touched against a reader.

+ +

Request:

+
<call ...>
+    <cardmng method="inquire" cardid="" cardtype="" update="" model*="" />
+</call>
+ + + + + +
updateShould the tracked last play time be updated by this inquiry? (Just a guess)
+

Response:

+
<response>
+    <cardmng status="status" refid="" dataid="" pcode=""  newflag="" binded="" expired=" ecflag="" useridflag="" extidflag="" lastupdate="" />
+</response>
+

To handle this request, we first must lookup if this cardid has ever been seen by our servers + before. If not, we abort with a 112 status. Otherwise, we proceeed to check if this card has been + 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.

+ + + + + + + + + + + + + + + + + + + + + +
refidA reference to this card to be used in other requests
dataidAppears to be set the same as refid; presumably to allow different keys for game state vs + login details.
newflagInverse of binded
bindedHas a profile ever been created for this game (or an older version, requiring a migration) + (1 or 0)
expired? Just set to 0.
+ +

cardmng.getrefid

+

Register a new card to this server.

+

Request:

+
<call ...>
+    <cardmng method="getrefid" cardtype="" cardid=" newflag="" passwd="" model*="" />
+</call>
+ + + + + + + + + +
newflag?
passwdThe pin for this new user. Should always be a four digit number (and that's worth validating), + but it's passed as a string so could feasibly be anything desired.
+

Response:

+
<response>
+    <cardmng status="status" refid="" dataid="" pcode="" />
+</response>
+ + + + + + + + + + + + + +
refidA reference to this card to be used in other requests
dataidAppears to be set the same as refid; presumably to allow different keys for game state vs + login details.
pcode? Not present in captured data.
+ +

cardmng.bindmodel

+

Request:

+
<call ...>
+    <cardmng method="bindmodel" refid="" newflag="" model*="" />
+</call>
+

Response:

+
<response>
+    <cardmng status="status" dataid="" />
+</response>
+ +

cardmng.bindcard

+

Request:

+
<call ...>
+    <cardmng method="bindcard" cardtype="" newid="" refid="" model*="" />
+</call>
+

Response:

+
<response>
+    <cardmng status="status" />
+</response>
+ +

cardmng.authpass

+

Test a pin for a card. This request notably uses the refid, so required a + cardmng.inquire call to be made first. +

+

Request:

+
<call ...>
+    <cardmng method="authpass" refid="" pass="" model*="" />
+</call>
+ + + + + + + + + +
refidThe reference we received either during cardmng.inquire or cardmng.getrefid + (the latter for new cards)
passThe pin to test. See cardmng.getrefid.
+

Response:

+
<response>
+    <cardmng status="status" />
+</response>
+

If the pin is valid, status should be 0. Otherwise, 116.

+ +

cardmng.getkeepspan

+

Request:

+
<call ...>
+    <cardmng method="getkeepspan" model*="" />
+</call>
+

Response:

+
<response>
+    <cardmng status="status" keepspan="" />
+</response>
+ +

cardmng.getkeepremain

+

Request:

+
<call ...>
+    <cardmng method="getkeepremain" refid="" model*="" />
+</call>
+

Response:

+
<response>
+    <cardmng status="status" keepremain="" />
+</response>
+ +

cardmng.getdatalist

+

Request:

+
<call ...>
+    <cardmng method="getdatalist" refid="" model*="" />
+</call>
+

Response:

+
<response>
+    <cardmng status="status">
+        <item[]>
+            <mcode __type="str" />
+            <dataid __type="str" />
+            <regtime __type="str" />
+            <lasttime __type="str" />
+            <exptime __type="str" />
+            <expflag __type="u8" />
+        </item[]>
+    </cardmng>
+</response>
+{% endblock %} \ No newline at end of file diff --git a/templates/pages/proto/dlstatus.html b/templates/pages/proto/dlstatus.html new file mode 100644 index 0000000..9ea8144 --- /dev/null +++ b/templates/pages/proto/dlstatus.html @@ -0,0 +1,34 @@ +{% extends "base.html" %} +{% block body %} +

dlstatus

+

dlstatus.done

+

Request:

+
<call ...>
+    <dlstatus method="done">
+        <url>
+            <param __type="str" />
+        </url>
+        <name __type="str" />
+        <size __type="s32" />
+    </dlstatus>
+</call>
+ +

Response:

+
<response>
+    <dlstatus status="status">
+        <progress __type="s32" />
+    </dlstatus>
+</response>
+ +

dlstatus.progress

+

Request:

+
<call ...>
+    <dlstatus method="progress" />
+        <progress __type="s32" />
+    </dlstatus>
+</call>
+

Response:

+
<response>
+    <dlstatus status="status" />
+</response>
+{% endblock %} \ No newline at end of file diff --git a/proto/eacoin.html b/templates/pages/proto/eacoin.html similarity index 62% rename from proto/eacoin.html rename to templates/pages/proto/eacoin.html index 1fb9767..ac90e1d 100644 --- a/proto/eacoin.html +++ b/templates/pages/proto/eacoin.html @@ -1,29 +1,9 @@ - - - - - - - - e-Amusement API - - - - - - - - - - - - -
ContentsTransport layerPacket formatApplication Protocol
- -

eacoin

-

eacoin.checkin

-

Request:

-
<call ...>
+{% extends "base.html" %}
+{% block body %}
+

eacoin

+

eacoin.checkin

+

Request:

+
<call ...>
     <eacoin method="checkin">
         <cardtype __type="str" />
         <cardid __type="str" />
@@ -31,8 +11,8 @@
         <ectype __type="str" />
     </eacoin>
 </call>
-

Response:

-
<response>
+

Response:

+
<response>
     <eacoin status="status">
         <sequence __type="s16" />
         <acstatus __type="u8" />
@@ -43,21 +23,21 @@
     </eacoin>
 </response>
-

eacoin.checkout

-

Request:

-
<call ...>
+

eacoin.checkout

+

Request:

+
<call ...>
     <eacoin method="checkout">
         <sessid __type="str" />
     </eacoin>
 </call>
-

Response:

-
<response>
+

Response:

+
<response>
     <eacoin status="status" />
 </response>
-

eacoin.consume

-

Request:

-
<call ...>
+

eacoin.consume

+

Request:

+
<call ...>
     <eacoin method="consume" esid="">
         <sessid __type="str" />
         <sequence __type="s16" />
@@ -67,8 +47,8 @@
         <detail __type="str" />
     </eacoin>
 </call>
-

Response:

-
<response>
+

Response:

+
<response>
     <eacoin status="status">
         <acstatus __type="u8" />
         <autocharge __type="u8" />
@@ -76,88 +56,88 @@
     </eacoin>
 </response>
-

eacoin.getbalance

-

Request:

-
<call ...>
+

eacoin.getbalance

+

Request:

+
<call ...>
     <eacoin method="getbalance">
       <sessid __type="str" />
     </eacoin>
 </call>
-

Response:

-
<response>
+

Response:

+
<response>
     <eacoin status="status">
         <acstatus __type="u8" />
         <balance __type="s32" />
     </eacoin>
 </response>
-

eacoin.getecstatus

-

Request:

-
<call ...>
+

eacoin.getecstatus

+

Request:

+
<call ...>
     <eacoin method="getecstatus" />
 </call>
-

Response:

-
<response>
+

Response:

+
<response>
     <eacoin status="status">
         <ectype __type="str" />
         <ecstatus __type="u8" />
     </eacoin>
 </response>
-

eacoin.touch

-

Request:

-
<call ...>
+

eacoin.touch

+

Request:

+
<call ...>
     <eacoin method="touch">
         <sessid __type="str" />
     </eacoin>
 </call>
-

Response:

-
<response>
+

Response:

+
<response>
     <eacoin status="status" />
 </response>
-

eacoin.opchpass

-

Request:

-
<call ...>
+

eacoin.opchpass

+

Request:

+
<call ...>
     <eacoin method="opchpass">
         <passwd __type="str" />
         <newpasswd __type="str" />
     </eacoin>
 </call>
-

Response:

-
<response>
+

Response:

+
<response>
     <eacoin status="status" />
 </response>
-

eacoin.opcheckin

-

Request:

-
<call ...>
+

eacoin.opcheckin

+

Request:

+
<call ...>
     <eacoin method="opcheckin">
         <passwd __type="str" />
     </eacoin>
 </call>
-

Response:

-
<response>
+

Response:

+
<response>
     <eacoin status="status">
         <sessid __type="str" />
     </eacoin>
 </response>
-

eacoin.opcheckout

-

Request:

-
<call ...>
+

eacoin.opcheckout

+

Request:

+
<call ...>
     <eacoin method="opcheckout">
         <sessid __type="str" />
     </eacoin>
 </call>
-

Response:

-
<response>
+

Response:

+
<response>
     <eacoin status="status" />
 </response>
-

eacoin.getlog

-

Request:

-
<call ...>
+

eacoin.getlog

+

Request:

+
<call ...>
     <eacoin method="getlog">
         <sessid __type="str" />
         <logtype __type="str" />
@@ -168,8 +148,8 @@
         <sesstype __type="str" />
     </eacoin>
 </call>
-

Response:

-
<response>
+

Response:

+
<response>
     <eacoin status="status">
         <processing __type="u8" />
         <topic>
@@ -197,4 +177,4 @@
         </history>
     </eacoin>
 </response>
- \ No newline at end of file +{% endblock %} \ No newline at end of file diff --git a/templates/pages/proto/esign.html b/templates/pages/proto/esign.html new file mode 100644 index 0000000..88d2b03 --- /dev/null +++ b/templates/pages/proto/esign.html @@ -0,0 +1,17 @@ +{% extends "base.html" %} +{% block body %} +

esign

+

esign.request

+

Request:

+
<call ...>
+    <esign method="request">
+        placeholder
+    </esign>
+</call>
+

Response:

+
<response>
+    <esign status="status">
+        placeholder
+    </esign>
+</response>
+{% endblock %} \ No newline at end of file diff --git a/templates/pages/proto/esoc.html b/templates/pages/proto/esoc.html new file mode 100644 index 0000000..e38cc9c --- /dev/null +++ b/templates/pages/proto/esoc.html @@ -0,0 +1,30 @@ +{% extends "base.html" %} +{% block body %} +

esoc

+

esoc.read

+

Request:

+
<call ...>
+    <esoc method="read">
+        <senddata />
+    </esoc>
+</call>
+

Response:

+
<response>
+    <esoc status="status">
+        <recvdata />
+    </esoc>
+</response>
+

Go figure.

+ +

esoc.write

+

Request:

+
<call ...>
+    <esoc method="write">
+        <senddata />
+    </esoc>
+</call>
+

Response:

+
<response>
+    <esoc status="status" />
+</response>
+{% endblock %} \ No newline at end of file diff --git a/templates/pages/proto/eventlog.html b/templates/pages/proto/eventlog.html new file mode 100644 index 0000000..2c67e21 --- /dev/null +++ b/templates/pages/proto/eventlog.html @@ -0,0 +1,38 @@ +{% extends "base.html" %} +{% block body %} +

eventlog

+

eventlog.write

+

Request:

+
<call ...>
+    <eventlog method="write">
+        <retrycnt __type="u32" />
+        <data>
+            <eventid __type="str" />
+            <eventorder __type="s32" />
+            <pcbtime __type="u64" />
+            <gamesession __type="s64" />
+            <strdata1 __type="str" />
+            <strdata2 __type="str" />
+            <numdata1 __type="s64" />
+            <numdata2 __type="s64" />
+            <locationid __type="str" />
+        </data>
+    </eventlog>
+</call>
+

Event ID list:

+
    +
  • G_GAMED
  • +
  • S_ERROR
  • +
  • S_PWRON TODO: find more!
  • +
  • T_OTDEMO
  • +
+

Response:

+
<response>
+    <eventlog status="status">
+        <gamesession __type="s64" />
+        <logsendflg __type="s32" />
+        <logerrlevel __type="s32" />
+        <evtidnosendflg __type="s32" />
+    </eventlog>
+</response>
+{% endblock %} \ No newline at end of file diff --git a/proto/facility.html b/templates/pages/proto/facility.html similarity index 50% rename from proto/facility.html rename to templates/pages/proto/facility.html index ec74198..6536380 100644 --- a/proto/facility.html +++ b/templates/pages/proto/facility.html @@ -1,33 +1,13 @@ - - - - - - - - e-Amusement API - - - - - - - - - - - - -
ContentsTransport layerPacket formatApplication Protocol
- -

facility

-

facility.get

-

Request:

-
<call ...>
+{% extends "base.html" %}
+{% block body %}
+

facility

+

facility.get

+

Request:

+
<call ...>
     <facility method="get" privateip*="" encoding*="" />
 </call>
-

Response:

-
<response>
+

Response:

+
<response>
     <facility expire=""\ status="status">
         <calendar*>
             <year __type="s16" />
@@ -83,58 +63,58 @@
         </share>
     </facility>
 </response>
-

I'm not totally sure what type share/eapass/valid is meant to be, but it's optional, so I'd - suggest just not bothering and leaving it out :).

- - - - - - - +

I'm not totally sure what type share/eapass/valid is meant to be, but it's optional, so I'd + suggest just not bothering and leaving it out :).

+
CountryCode
+ - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Hong KongHKCountryCode
TaiwanTW
KoreaKR
USAUS
ThailandTH
IndonesiaID
SingaporeSG
PhillipinesPH
MacaoMO
JapanJP
-

globalip (and associated ports) shold be the IP:port of the cabinet.

-

region is used for Japan, and has the value JP-[prefecture] where prefecture ranges - from 1 through 47.

-

TODO: Compile the list of regions

- \ No newline at end of file + + + Hong Kong + HK + + + Taiwan + TW + + + Korea + KR + + + USA + US + + + Thailand + TH + + + Indonesia + ID + + + Singapore + SG + + + Phillipines + PH + + + Macao + MO + + + Japan + JP + + +

globalip (and associated ports) shold be the IP:port of the cabinet.

+

region is used for Japan, and has the value JP-[prefecture] where prefecture ranges + from 1 through 47.

+

TODO: Compile the list of regions

+{% endblock %} \ No newline at end of file diff --git a/proto/game/sv4.html b/templates/pages/proto/game/sv4.html similarity index 53% rename from proto/game/sv4.html rename to templates/pages/proto/game/sv4.html index afd231d..6e2928c 100644 --- a/proto/game/sv4.html +++ b/templates/pages/proto/game/sv4.html @@ -1,387 +1,367 @@ - - - - - - - - e-Amusement API - - - - - - - - - - - - -
ContentsTransport layerPacket formatApplication Protocol
- -

game

-

game.sv4_sample

-

Request:

-
<call ...>
+{% extends "base.html" %}
+{% block body %}
+

game

+

game.sv4_sample

+

Request:

+
<call ...>
     <game method="sv4_sample">
         placeholder
     </game>
 </call>
-

Response:

-
<response>
+

Response:

+
<response>
     <game status="status">
         placeholder
     </game>
 </response>
-

game.sv4_new

-

Request:

-
<call ...>
+

game.sv4_new

+

Request:

+
<call ...>
     <game method="sv4_new">
         placeholder
     </game>
 </call>
-

Response:

-
<response>
+

Response:

+
<response>
     <game status="status">
         placeholder
     </game>
 </response>
-

game.sv4_load

-

Request:

-
<call ...>
+

game.sv4_load

+

Request:

+
<call ...>
     <game method="sv4_load">
         placeholder
     </game>
 </call>
-

Response:

-
<response>
+

Response:

+
<response>
     <game status="status">
         placeholder
     </game>
 </response>
-

game.sv4_load_m

-

Request:

-
<call ...>
+

game.sv4_load_m

+

Request:

+
<call ...>
     <game method="sv4_load_m">
         placeholder
     </game>
 </call>
-

Response:

-
<response>
+

Response:

+
<response>
     <game status="status">
         placeholder
     </game>
 </response>
-

game.sv4_save

-

Request:

-
<call ...>
+

game.sv4_save

+

Request:

+
<call ...>
     <game method="sv4_save">
         placeholder
     </game>
 </call>
-

Response:

-
<response>
+

Response:

+
<response>
     <game status="status">
         placeholder
     </game>
 </response>
-

game.sv4_save_m

-

Request:

-
<call ...>
+

game.sv4_save_m

+

Request:

+
<call ...>
     <game method="sv4_save_m">
         placeholder
     </game>
 </call>
-

Response:

-
<response>
+

Response:

+
<response>
     <game status="status">
         placeholder
     </game>
 </response>
-

game.sv4_common

-

Request:

-
<call ...>
+

game.sv4_common

+

Request:

+
<call ...>
     <game method="sv4_common">
         placeholder
     </game>
 </call>
-

Response:

-
<response>
+

Response:

+
<response>
     <game status="status">
         placeholder
     </game>
 </response>
-

game.sv4_shop

-

Request:

-
<call ...>
+

game.sv4_shop

+

Request:

+
<call ...>
     <game method="sv4_shop">
         placeholder
     </game>
 </call>
-

Response:

-
<response>
+

Response:

+
<response>
     <game status="status">
         placeholder
     </game>
 </response>
-

game.sv4_hiscore

-

Request:

-
<call ...>
+

game.sv4_hiscore

+

Request:

+
<call ...>
     <game method="sv4_hiscore">
         placeholder
     </game>
 </call>
-

Response:

-
<response>
+

Response:

+
<response>
     <game status="status">
         placeholder
     </game>
 </response>
-

game.sv4_buy

-

Request:

-
<call ...>
+

game.sv4_buy

+

Request:

+
<call ...>
     <game method="sv4_buy">
         placeholder
     </game>
 </call>
-

Response:

-
<response>
+

Response:

+
<response>
     <game status="status">
         placeholder
     </game>
 </response>
-

game.sv4_exception

-

Request:

-
<call ...>
+

game.sv4_exception

+

Request:

+
<call ...>
     <game method="sv4_exception">
         placeholder
     </game>
 </call>
-

Response:

-
<response>
+

Response:

+
<response>
     <game status="status">
         placeholder
     </game>
 </response>
-

game.sv4_entry_s

-

Request:

-
<call ...>
+

game.sv4_entry_s

+

Request:

+
<call ...>
     <game method="sv4_entry_s">
         placeholder
     </game>
 </call>
-

Response:

-
<response>
+

Response:

+
<response>
     <game status="status">
         placeholder
     </game>
 </response>
-

game.sv4_entry_e

-

Request:

-
<call ...>
+

game.sv4_entry_e

+

Request:

+
<call ...>
     <game method="sv4_entry_e">
         placeholder
     </game>
 </call>
-

Response:

-
<response>
+

Response:

+
<response>
     <game status="status">
         placeholder
     </game>
 </response>
-

game.sv4_frozen

-

Request:

-
<call ...>
+

game.sv4_frozen

+

Request:

+
<call ...>
     <game method="sv4_frozen">
         placeholder
     </game>
 </call>
-

Response:

-
<response>
+

Response:

+
<response>
     <game status="status">
         placeholder
     </game>
 </response>
-

game.sv4_lounce

-

Request:

-
<call ...>
-    <game method="sv4_lounce">
+

game.sv4_lounge

+

Request:

+
<call ...>
+    <game method="sv4_lounge">
         placeholder
     </game>
 </call>
-

Response:

-
<response>
+

Response:

+
<response>
     <game status="status">
         placeholder
     </game>
 </response>
-

game.sv4_save_e

-

Request:

-
<call ...>
+

game.sv4_save_e

+

Request:

+
<call ...>
     <game method="sv4_save_e">
         placeholder
     </game>
 </call>
-

Response:

-
<response>
+

Response:

+
<response>
     <game status="status">
         placeholder
     </game>
 </response>
-

game.sv4_save_pb

-

Request:

-
<call ...>
+

game.sv4_save_pb

+

Request:

+
<call ...>
     <game method="sv4_save_pb">
         placeholder
     </game>
 </call>
-

Response:

-
<response>
+

Response:

+
<response>
     <game status="status">
         placeholder
     </game>
 </response>
-

game.sv4_save_c

-

Request:

-
<call ...>
+

game.sv4_save_c

+

Request:

+
<call ...>
     <game method="sv4_save_c">
         placeholder
     </game>
 </call>
-

Response:

-
<response>
+

Response:

+
<response>
     <game status="status">
         placeholder
     </game>
 </response>
-

game.sv4_play_s

-

Request:

-
<call ...>
+

game.sv4_play_s

+

Request:

+
<call ...>
     <game method="sv4_play_s">
         placeholder
     </game>
 </call>
-

Response:

-
<response>
+

Response:

+
<response>
     <game status="status">
         placeholder
     </game>
 </response>
-

game.sv4_play_e

-

Request:

-
<call ...>
+

game.sv4_play_e

+

Request:

+
<call ...>
     <game method="sv4_play_e">
         placeholder
     </game>
 </call>
-

Response:

-
<response>
+

Response:

+
<response>
     <game status="status">
         placeholder
     </game>
 </response>
-

game.sv4_serial

-

Request:

-
<call ...>
+

game.sv4_serial

+

Request:

+
<call ...>
     <game method="sv4_serial">
         placeholder
     </game>
 </call>
-

Response:

-
<response>
+

Response:

+
<response>
     <game status="status">
         placeholder
     </game>
 </response>
-

game.sv4_save_fi

-

Request:

-
<call ...>
+

game.sv4_save_fi

+

Request:

+
<call ...>
     <game method="sv4_save_fi">
         placeholder
     </game>
 </call>
-

Response:

-
<response>
+

Response:

+
<response>
     <game status="status">
         placeholder
     </game>
 </response>
-

game.sv4_print

-

Request:

-
<call ...>
+

game.sv4_print

+

Request:

+
<call ...>
     <game method="sv4_print">
         placeholder
     </game>
 </call>
-

Response:

-
<response>
+

Response:

+
<response>
     <game status="status">
         placeholder
     </game>
 </response>
- -

Request:

-
<call ...>
+
+

Request:

+
<call ...>
     <game method="sv4_print_h">
         placeholder
     </game>
 </call>
-

Response:

-
<response>
+

Response:

+
<response>
     <game status="status">
         placeholder
     </game>
 </response>
-

game.sv4_load_r

-

Request:

-
<call ...>
+

game.sv4_load_r

+

Request:

+
<call ...>
     <game method="sv4_load_r">
         placeholder
     </game>
 </call>
-

Response:

-
<response>
+

Response:

+
<response>
     <game status="status">
         placeholder
     </game>
 </response>
-

game.sv4_save_campaign

-

Request:

-
<call ...>
+

game.sv4_save_campaign

+

Request:

+
<call ...>
     <game method="sv4_save_campaign">
         placeholder
     </game>
 </call>
-

Response:

-
<response>
+

Response:

+
<response>
     <game status="status">
         placeholder
     </game>
 </response>
- \ No newline at end of file +{% endblock %} \ No newline at end of file diff --git a/proto/matching.html b/templates/pages/proto/matching.html similarity index 65% rename from proto/matching.html rename to templates/pages/proto/matching.html index 9a1a707..e185c2f 100644 --- a/proto/matching.html +++ b/templates/pages/proto/matching.html @@ -1,29 +1,9 @@ - - - - - - - - e-Amusement API - - - - - - - - - - - - -
ContentsTransport layerPacket formatApplication Protocol
- -

matching

-

matching.request

-

Request:

-
<call ...>
+{% extends "base.html" %}
+{% block body %}
+

matching

+

matching.request

+

Request:

+
<call ...>
     <matching method="request">
         <info>
             <version __type="s32" />
@@ -46,8 +26,8 @@
         </data>
     </matching>
 </call>
-

Response:

-
<response>
+

Response:

+
<response>
     <matching status="status">
         <hostid __type="s64" />
         <result __type="s32" />
@@ -58,9 +38,9 @@
     </matching>
 </response>
-

matching.wait

-

Request:

-
<call ...>
+

matching.wait

+

Request:

+
<call ...>
     <matching method="wait">
         <info>
             <version __type="s32" />
@@ -72,17 +52,17 @@
         </data>
     </matching>
 </call>
-

Response:

-
<response>
+

Response:

+
<response>
     <matching status="status">
         <result __type="s32" />
         <prwtime __type="s32" />
     </matching>
 </response>
-

matching.finish

-

Request:

-
<call ...>
+

matching.finish

+

Request:

+
<call ...>
     <matching method="finish">
         <info>
             <version __type="s32" />
@@ -94,10 +74,10 @@
         </data>
     </matching>
 </call>
-

Response:

-
<response>
+

Response:

+
<response>
     <matching status="status">
         <result __type="s32" />
     </matching>
 </response>
- \ No newline at end of file +{% endblock %} \ No newline at end of file diff --git a/templates/pages/proto/message.html b/templates/pages/proto/message.html new file mode 100644 index 0000000..c2ae346 --- /dev/null +++ b/templates/pages/proto/message.html @@ -0,0 +1,15 @@ +{% extends "base.html" %} +{% block body %} +

message

+

message.get

+

Request:

+
<call ...>
+    <message method="get" model*="" />
+</call>
+

Response:

+
<response>
+    <message expire="" status="status">
+        <item[] name="" start="" end="" data="" />
+    </message>
+</response>
+{% endblock %} \ No newline at end of file diff --git a/templates/pages/proto/package.html b/templates/pages/proto/package.html new file mode 100644 index 0000000..bf13885 --- /dev/null +++ b/templates/pages/proto/package.html @@ -0,0 +1,27 @@ +{% extends "base.html" %} +{% block body %} +

package

+

package.list

+

Request:

+
<call ...>
+    <package method="list" pkgtype="pkgtype" model*="" />
+</call>
+

all is the only currently observed value for pkgtype

+

Response:

+
<response>
+    <package status="status">
+        <item[] url="" />
+    </package>
+</response>
+

A list of all packages available for download.

+ +

package.intend

+

Request:

+
<call ...>
+    <package method="intend" url="" model*="" />
+</call>
+

Response:

+
<response>
+    <package status="status" />
+</response>
+{% endblock %} \ No newline at end of file diff --git a/templates/pages/proto/pcbevent.html b/templates/pages/proto/pcbevent.html new file mode 100644 index 0000000..89898d7 --- /dev/null +++ b/templates/pages/proto/pcbevent.html @@ -0,0 +1,21 @@ +{% extends "base.html" %} +{% block body %} +

pcbevent

+

pcbevent.put

+

Request:

+
<call ...>
+    <pcbevent method="put">
+        <time __type="time" />
+        <seq __type="u32" />
+        <item[]>
+            <name __type="str" />
+            <value __type="s32" />
+            <time __type="time" />
+        </item[]>
+    </pcbevent>
+</call>
+

Response:

+
<response>
+    <pcbevent status="status" />
+</response>
+{% endblock %} \ No newline at end of file diff --git a/templates/pages/proto/pcbtracker.html b/templates/pages/proto/pcbtracker.html new file mode 100644 index 0000000..5f226bb --- /dev/null +++ b/templates/pages/proto/pcbtracker.html @@ -0,0 +1,19 @@ +{% extends "base.html" %} +{% block body %} +

pcbtracker

+

pcbtracker.alive

+

Request:

+
<call ...>
+    <pcbtracker method="alive" model*="" hardid="" softid="" accountid="" agree="" ecflag="" />
+</call>
+

ecflag here is determining if the arcade operator allows the use of paseli on this machine.

+

agree@ and ecflag@ appear to either be totally non present, or present with a value of + "1", but then again I may be reading the code wrong, so take that with a pinch of salt. +

+

Response:

+
<response>
+    <pcbtracker status="" time="" limit="" ecenable="" eclimit="" >
+</response>
+

As you might guess, ecenable@ is therefore the flag to determine if paseli is enabled (i.e. the + arcade operator and the server both allow its use).

+{% endblock %} \ No newline at end of file diff --git a/proto/playerdata.html b/templates/pages/proto/playerdata.html similarity index 68% rename from proto/playerdata.html rename to templates/pages/proto/playerdata.html index 1bf5acf..8c13228 100644 --- a/proto/playerdata.html +++ b/templates/pages/proto/playerdata.html @@ -1,29 +1,9 @@ - - - - - - - - e-Amusement API - - - - - - - - - - - - -
ContentsTransport layerPacket formatApplication Protocol
- -

playerdata

-

playerdata.usergamedata_send

-

Request:

-
<call ...>
+{% extends "base.html" %}
+{% block body %}
+

playerdata

+

playerdata.usergamedata_send

+

Request:

+
<call ...>
     <playerdata method="usergamedata_send">
         <retrycnt __type="u32" />
             <info>
@@ -40,16 +20,16 @@
             </data>
     </playerdata>
 </call>
-

Response:

-
<response>
+

Response:

+
<response>
     <playerdata status="status">
         <result __type="s32" />
     </playerdata>
 </response>
-

playerdata.usergamedata_recv

-

Request:

-
<call ...>
+

playerdata.usergamedata_recv

+

Request:

+
<call ...>
     <playerdata method="usergamedata_recv">
         <info>
             <version __type="u32" />
@@ -62,7 +42,7 @@
         </data>
     </playerdata>
 </call>
-
<call ...>
+
<call ...>
     <playerdata method="usergamedata_recv">
         <data>
             <refid __type="str">
@@ -72,8 +52,8 @@
         </data>
     </playerdata>
 </call>
-

Response:

-
<response>
+

Response:

+
<response>
     <playerdata status="status">
         <player>
             <result>
@@ -88,12 +68,12 @@
     </playerdata>
 </response>
-

playerdata.usergamedata_inheritance

-

See: playerdata.usergamedata_recv

+

playerdata.usergamedata_inheritance

+

See: playerdata.usergamedata_recv

-

playerdata.usergamedata_condrecv

-

Request:

-
<call ...>
+

playerdata.usergamedata_condrecv

+

Request:

+
<call ...>
     <playerdata method="usergamedata_condrecv">
         <info>
             <version __type="s32" />
@@ -112,8 +92,8 @@
         </info>
     </playerdata>
 </call>
-

Response:

-
<response>
+

Response:

+
<response>
     <playerdata status="status">
         <player>
             <result __type="s32" />
@@ -127,9 +107,9 @@
     </playerdata>
 </response>
-

playerdata.usergamedata_scorerank

-

Request:

-
<call ...>
+

playerdata.usergamedata_scorerank

+

Request:

+
<call ...>
     <playerdata method="usergamedata_scorerank">
         <info>
             <version __type="s32" />
@@ -143,8 +123,8 @@
         </data>
     </playerdata>
 </call>
-

Response:

-
<response>
+

Response:

+
<response>
     <playerdata status="status">
         <rank>
             <result __type="s32" />
@@ -153,4 +133,4 @@
         </rank>
     </playerdata>
 </response>
- \ No newline at end of file +{% endblock %} \ No newline at end of file diff --git a/templates/pages/proto/services.html b/templates/pages/proto/services.html new file mode 100644 index 0000000..b752256 --- /dev/null +++ b/templates/pages/proto/services.html @@ -0,0 +1,50 @@ +{% extends "base.html" %} +{% block body %} +

services

+

services.get

+

Request:

+
<call ...>
+    <services method="get" model*="" >
+        <info>
+            <AVS2 __type="str">AVS2 version</AVS2>
+        </info>
+    </services>
+</call>
+

Response:

+
<response>
+    <services expire="" method="get" mode="" status="status">
+        <item[] name="service" url="url" />
+    </services>
+</response>
+

Known services are:

+
    +
  • ntp
  • +
  • keepalive
  • +
  • cardmng
  • +
  • facility
  • +
  • message
  • +
  • numbering
  • +
  • package
  • +
  • pcbevent
  • +
  • pcbtracker
  • +
  • pkglist
  • +
  • posevent
  • +
  • userdata
  • +
  • userid
  • +
  • eacoin
  • +
  • local
  • +
  • local2
  • +
  • lobby
  • +
  • lobby2
  • +
  • dlstatus
  • +
  • netlog
  • +
  • sidmgr
  • +
  • globby
  • +
+

Most of these will usually just return the URL to the eAmuse server (or your fake one ;D). ntp is a + notable exception, unless you're planning on reimplementing NTP. keepalive will likely alsop be a + custom URL with query parameters pre-baked.

+

mode is one of operation, debug, test, or + factory. +

+{% endblock %} \ No newline at end of file diff --git a/templates/pages/proto/sidmgr.html b/templates/pages/proto/sidmgr.html new file mode 100644 index 0000000..99a4ee5 --- /dev/null +++ b/templates/pages/proto/sidmgr.html @@ -0,0 +1,74 @@ +{% extends "base.html" %} +{% block body %} +

sidmgr

+

sidmgr.create

+

Request:

+
<call ...>
+    <sidmgr method="create">
+        <cardtype __type="str" />
+        <cardid __type="str" />
+        <cardgid __type="str" />
+        <steal __type="u8" />
+    </sidmgr>
+</call>
+

Response:

+
<response>
+    <sidmgr status="status">
+        <state __type="u32" />
+        <e_count __type="u8" />
+        <last __type="time" />
+        <locked __type="time" />
+        <sid __type="str" />
+        <cardid_status __type="u8" />
+        <refid __type="str" />
+    </sidmgr>
+</response>
+ +

sidmgr.open

+

Request:

+
<call ...>
+    <sidmgr method="open" sid="" >
+        <pass __type="str" />
+    </sidmgr>
+</call>
+

Response:

+
<response>
+    <sidmgr status="status">
+        <state __type="u32" />
+        <refid __type="str" />
+        <locked __type="time" />
+    </sidmgr>
+</response>
+ +

sidmgr.touch

+

Request:

+
<call ...>
+    <sidmgr method="touch" sid="" />
+</call>
+

Response:

+
<response>
+    <sidmgr status="status" />
+</response>
+ +

sidmgr.branch

+

Request:

+
<call ...>
+    <sidmgr method="branch" sid="" />
+</call>
+

Response:

+
<response>
+    <sidmgr status="status" />
+</response>
+ +

sidmgr.close

+

Request:

+
<call ...>
+    <sidmgr method="close" sid="" />
+        <cause __type="u32" />
+    </sidmgr>
+</call>
+

Response:

+
<response>
+    <sidmgr status="status" />
+</response>
+{% endblock %} \ No newline at end of file diff --git a/proto/system.html b/templates/pages/proto/system.html similarity index 66% rename from proto/system.html rename to templates/pages/proto/system.html index 426f9b9..c910690 100644 --- a/proto/system.html +++ b/templates/pages/proto/system.html @@ -1,29 +1,9 @@ - - - - - - - - e-Amusement API - - - - - - - - - - - - -
ContentsTransport layerPacket formatApplication Protocol
- -

system

-

system.getmaster

-

Request:

-
<call ...>
+{% extends "base.html" %}
+{% block body %}
+

system

+

system.getmaster

+

Request:

+
<call ...>
     <system method="getmaster">
         <data>
             <gamekind __type="str" />
@@ -32,8 +12,8 @@
         </data>
     </system>
 </call>
-

Response:

-
<response>
+

Response:

+
<response>
     <system status="status">
         <result __type="s32" />
         <strdata1 __type="str" />
@@ -42,9 +22,9 @@
     </system>
 </response>
-

system.getlocationiplist

-

Request:

-
<call ...>
+

system.getlocationiplist

+

Request:

+
<call ...>
     <system method="getlocationiplist">
         <data>
             <locationid __type="str" />
@@ -52,8 +32,8 @@
         </data>
     </system>
 </call>
-

Response:

-
<response>
+

Response:

+
<response>
     <system status="status">
         <result __type="s32" />
         <iplist>
@@ -65,9 +45,9 @@
     </system>
 </response>
-

system.xrpcproxy

-

Request:

-
<call ...>
+

system.xrpcproxy

+

Request:

+
<call ...>
     <system method="xrpcproxy">
         <info>
             <version __type="s32" />
@@ -79,8 +59,8 @@
         </data>
     </system>
 </call>
-

Response:

-
<response>
+

Response:

+
<response>
     <system status="status">
         <result __type="s32" />
         <pwrtime __type="s32" />
@@ -101,9 +81,9 @@
     </system>
 </response>
-

system.convcardnumber

-

Request:

-
<call ...>
+

system.convcardnumber

+

Request:

+
<call ...>
     <system method="convcardnumber">
         <info>
             <version __type="s32" />
@@ -114,8 +94,8 @@
         </data>
     </system>
 </call>
-

Response:

-
<response>
+

Response:

+
<response>
     <system status="status">
         <result __type="s32" />
         <data>
@@ -123,4 +103,4 @@
         </data>
     </system>
 </response>
- \ No newline at end of file +{% endblock %} \ No newline at end of file diff --git a/templates/pages/proto/traceroute.html b/templates/pages/proto/traceroute.html new file mode 100644 index 0000000..f592b80 --- /dev/null +++ b/templates/pages/proto/traceroute.html @@ -0,0 +1,20 @@ +{% extends "base.html" %} +{% block body %} +

traceroute

+

traceroute.send

+

Request:

+
<call ...>
+    <traceroute proto="" method="send">
+        <hop[]>
+            <valid __type="bool">
+            <addr __type="ip4">
+            <usec __type="u64">
+        </hop[]>
+    </traceroute>
+</call>
+

hop repeats for every hop (unsurprisingly)

+

Response:

+
<response>
+    <traceroute status="status" />
+</response>
+{% endblock %} \ No newline at end of file diff --git a/templates/pages/proto/userdata.html b/templates/pages/proto/userdata.html new file mode 100644 index 0000000..f3da43b --- /dev/null +++ b/templates/pages/proto/userdata.html @@ -0,0 +1,28 @@ +{% extends "base.html" %} +{% block body %} +

userdata

+

userdata.read

+

Request:

+
<call ...>
+    <userdata method="read" card*="" model*="" label="" />
+</call>
+

Response:

+
<response>
+    <userdata status="status" time="">
+        <b[] __type="" />
+    </userdata>
+</response>
+

__type here can be either bin or str

+ +

userdata.write

+

Request:

+
<call ...>
+    <userdata method="write" card="" time="" model*="" label*="" >
+        <b[] __type="str" />
+    </userdata>
+</call>
+

Response:

+
<response>
+    <userdata status="status" />
+</response>
+{% endblock %} \ No newline at end of file diff --git a/templates/pages/protocol.html b/templates/pages/protocol.html new file mode 100644 index 0000000..403e7e9 --- /dev/null +++ b/templates/pages/protocol.html @@ -0,0 +1,263 @@ +{% extends "base.html" %} +{% block body %} +

Application Protocol

+

As the previous pages have eluded to (you did read them, didn't you?), eAmuse uses HTTP as its main way of + getting data around. This means we need an HTTP server running but, as we'll see, we don't need to think too + hard about that.

+

Every request made is a POST request, to //<model>/<module>/<method>, + with its body being encoded data as described in the previous sections. In addition to the + X-Compress: and X-Eamuse-Info: headers previously detailed, there is also a + X-PCB-ID: header. that can be set. Your machine's PCB ID uniquely defines the physical board. This + header is added in out-bound requests, and allows the server to identify you. Importantly, it's also the value + that the server uses to identify which machines are authorized to be on the network, and which are not. +

+

Every request is followed immediately by a response. Any response code other than 200 is considered + a failure.

+ +
+ Source code details +
+ +
libavs-win32-ea3.dll:0x1000f8e7
+
+
+ +

All requests follow a basic format:

+
<call model="model" srcid="srcid" tag="tag">
+    <module method="method" ...attributes>
+        children
+    </module>
+</call>
+

The responses follow a similar format:

+
<response>
+    <module status="status" ...attributes>
+        children
+    </module>
+</response>
+

With "0" being a successful status. Convention is to identify a specific method as + module.method, and we'll be following this convention in this document too. There are + a lot of possible methods, so the majority of this document is a big reference for them all. There are a + number of generic methods, and a number of game specific ones. If you haven't clocked yet, I've been working on + an SDVX 4 build for most of these pages, and each game also comes with its own set of game-specific methods. + These are namespaces under the game.%s module and, in the case of SDVX 4, are all + game.sv4_method. I may or may not document the SDVX 4 specific methods, but I've listed them + here anyway for completeness. +

+

Paths in the XML bodies are formatted using an XPath-like syntax. That is, status@/response gets the + status attribute from response, and response/eacoin/sequence would return + that node's value. +

+

NOTE: I am using the non-standard notation of <node* ... and + <node attr*="" ... to indicate that an attribute or node is not always present! Additionally, I + am going to use the notation of <node[]> to indicate that a node repeats. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
StatusMeaning
0Success
109No profile
110Not allowed
112Card not found (cardmng.inquire)
116Card pin invalid (cardmng.authpass)
+
+ How to reverse engineer these calls +

Turns out bemani have been quite sensible in how they implemented their code for creating structures, so it's + rather readable. That said, if you've been using Ghidra (like me!), this is the time to switch to IDA. I'll + let the below screenshots below speak for themselves: +

+ +
+ Ghidra + + +
+
+ IDA Pro + + +
+ +

I know which of these I'd rather use for reverse engineering (sorry, Ghidra)!

+
+ +

Possible XRPC requests

+ + + +Totally undocumented services (based on services.get): +
    +
  • numbering
  • +
  • pkglist
  • +
  • userid
  • +
  • local
  • +
  • local2
  • +
  • lobby
  • +
  • lobby2
  • +
  • netlog
  • +
  • globby
  • +
+

I'll try and figure these out in due course, promise!

+{% endblock %} \ No newline at end of file diff --git a/templates/pages/transport.html b/templates/pages/transport.html new file mode 100644 index 0000000..54e14de --- /dev/null +++ b/templates/pages/transport.html @@ -0,0 +1,52 @@ +{% extends "base.html" %} +{% block body %} +

Network format

+ +

eAmuse packets are sent and received over HTTP (no S), with requests being in the body of POST requests, + and replies being in the, well, reply.

+

The packets are typically both encrypted and compressed. The compression format used is indicated by the + X-Compress header, and valid values are +

+
    +
  • none
  • +
  • lz77
  • +
+
+ Source code details +
+ +
libavs-win32-ea3.dll:0x1000fa29
+
+
+

Encryption is performed after compression, and uses RC4. RC4 is symmetric, so decryption is performed the same + as encryption. That is, packet = encrypt(compress(data)) and + data = decompress(decrypt(data)). +

+ +

Encryption keys

+

Encryption is not performed using a single static key. Instead, each request and response has its own key that is + generated.

+

These keys are generated baesd on the X-Eamuse-Info header.

+

This header loosely follows the format 1-[0-9a-f]{8}-[0-9a-f]{4}. This corresponds to + [version]-[serial]-[salt]. TODO: Confirm this +

+

Our per-packet key is then generated using md5(serial | salt | KEY). Identifying KEY is + left as an exercise for the reader, however should not be especially challenging. Check + the page source if you're stuck.

+ + +

LZ77

+

Packets are compressed using lzss. The compressed data structure is a repeating cycle of an 8 bit flags byte, + followed by 8 values. Each value is either a single literal byte, if the corresponding bit in the preceeding flag is + high, or is a two byte lookup into the window.

+

The lookup bytes are structured as pppppppp ppppllll where p is a 12 bit index in the + window, and l is a 4 bit integer that determines how many times to repeat the value located at that + index in the window.

+ +

The exact algorithm used for compression is not especially important, as long as it follows this format. One can + feasibly perform no compression at all, and instead insert 0xFF every 8 bytes (starting at index 0), to + indicate that all values are literals. While obviously poor for compression, this is an easy way to test without + first implementing a compressor.

+ +Prev page | Next page +{% endblock %} \ No newline at end of file diff --git a/transport.html b/transport.html deleted file mode 100644 index 9cefb75..0000000 --- a/transport.html +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - - Transport | eAmuse API - - - - - - - - - - - -
ContentsTransport layerPacket formatApplication Protocol
- -

Network format

- -

eAmuse packets are sent and received over HTTP (no S), with requests being in the body of POST requests, and replies being in the, well, reply.

-

The packets are typically both encrypted and compressed. The compression format used is indicated by the X-Compress header, and valid values are

-
    -
  • none
  • -
  • lz77
  • -
-
- Source code details -
- -
libavs-win32-ea3.dll:0x1000fa29
-
-
-

Encryption is performed after compression, and uses RC4. RC4 is symmetric, so decryption is performed the same as encryption. That is, packet = encrypt(compress(data)) and data = decompress(decrypt(data)).

- -

Encryption keys

-

Encryption is not performed using a single static key. Instead, each request and response has its own key that is generated.

-

These keys are generated baesd on the X-Eamuse-Info header.

-

This header loosely follows the format 1-[0-9a-f]{8}-[0-9a-f]{4}. This corresponds to [version]-[serial]-[salt]. TODO: Confirm this

-

Our per-packet key is then generated using md5(serial | salt | KEY). Identifying KEY is left as an exercise for the reader, however should not be especially challenging. Check the page source if you're stuck.

- - -

LZ77

-

Packets are compressed using lzss. The compressed data structure is a repeating cycle of an 8 bit flags byte, followed by 8 values. Each value is either a single literal byte, if the corresponding bit in the preceeding flag is high, or is a two byte lookup into the window.

-

The lookup bytes are structured as pppppppp ppppllll where p is a 12 bit index in the window, and l is a 4 bit integer that determines how many times to repeat the value located at that index in the window.

- -

The exact algorithm used for compression is not especially important, as long as it follows this format. One can feasibly perform no compression at all, and instead insert 0xFF every 8 bytes (starting at index 0), to indicate that all values are literals. While obviously poor for compression, this is an easy way to test without first implementing a compressor.

- - Prev page | Next page - - - \ No newline at end of file