It’s important to keep track of updates when you’re hacking on an actively maintained game. Admittedly, I’ve been slacking in this department as of late, and enabling push notifications on an official Twitter account probably isn’t the best way to be notified when an update comes out. I suppose it’s finally time for me to dive into the network side of things.

But before we begin, a little context: I run a score tracking website for beatmania IIDX INFINITAS. It uses a hook library, not too different to this one, to submit score data from the game. When an update comes out, I’m usually looking for two things:

Since the last time I wrote about this game was before the major 2020 update, here’s a quick recap of a few relevant changes:

Unlike the 2015 version, the launcher now has its own launcher. To be specific, the usual start menu and desktop shortcuts now simply open your browser to a web page with two buttons – one to start the game normally, and another to start it in trial mode.

If you’re not subscribed, you’ll only see the “トライアルモードを起動” button, which starts the game in trial mode. Fortunately, this now uses the same executables as the normal game, unlike the old version which had an entirely separate installer.

The login page for beatmania IIDX INFINITAS I’m not subscribed at the moment, so just imagine there were two buttons here.

Pressing one of these buttons starts up the familiar native launcher, which loads in a few links to news posts and checks for updates before letting you start the game. It’s still possible to open this directly from the launcher/modules directory. News, settings and update checks still work without a token – you just won’t be able to start the game.

My plan is to capture the requests made before the game launches, inspect the responses, then write a script to send identical requests and poll for changes. This can be extended further down the line to automate various tasks, such as sending push notifications, testing patterns against the latest binary, and processing new music without manual intervention.

Without further ado, let’s start the launcher up and take a look at some requests.

A list of captured network requests in Fiddler

The launcher, updater, and game process all send requests over HTTPS, which I’m capturing and decrypting with Fiddler.

The session list above shows all of the requests made after opening bm2dx_launcher.exe and clicking the ファイル確認 button to verify local game files. This in turn started bm2dx_updater.exe, which made requests to several of the same endpoints again.

At a glance, the only difference is that GetFile received a much larger response when requested from the updater. There’s not much we can do just yet, though, as all of the request and response bodies are encoded in some currently unknown format.

Let’s take the “GetServices” request as an example, which is the first request performed in both the launcher and updater.

Connection: Keep-Alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 253


The client_version is a bit old compared to the game, which is P2D:J:B:A:2021072800 at the time of writing, so it appears to be the version of the launcher process instead. This seems to be read from the ProductVersion attribute of the VERSIONINFO resource, as I noticed it changed in subsequent requests after I edited it in Resource Hacker.

As for the request component, I don’t know about you, but that sure looks like Base64 to me.

$ echo '76BCoF8AcCABA//UKkALBsqubf/SkP4BBtZt5v/LgAMFp6yy+K/+/v7/ArIUAvAM/2dldFNlcnZpB2NlcwTSAAA=' | base64 -d | xxd
00000000: efa0 42a0 5f00 7020 0103 ffd4 2a40 0b06  ..B._.p ....*@..
00000010: caae 6dff d290 fe01 06d6 6de6 ffcb 8003  ..m.......m.....
00000020: 05a7 acb2 f8af fefe feff 02b2 1402 f00c  ................
00000030: ff67 6574 5365 7276 6907 6365 7304 d200  .getServi.ces...
00000040: 00                                       .

Eh… that’s a little better, but decoding hasn’t exactly made it readable yet. Not sure why the “getServices” string is split up like that either. I guess it’s time to look at the binary for some assistance. Here are some strings I found while searching for “request”:

Eacnet request messages in the IDA strings view

Looking at this in order, we can assume requests are LZ77 compressed, then encoded to Base64, which is what we see in the request above. There’s something about a signature in there too, but don’t worry about that just yet, we’ll get there eventually.

Here’s an LZ77 library I had success with. I found implementations in JavaScript, C# and C++ too if Rust isn’t your thing.

00000000: a042 a05f 0000 0020 0103 d42a 400b 06ca  .B._... ...*@...
00000010: ae6d d290 fe01 06d6 6de6 cb80 0305 a7ac  .m......m.......
00000020: b2f8 fefe feff 0000 0000 0014 0000 000c  ................
00000030: 6765 7453 6572 7669 6365 7300 0000 0000  getServices.....

Kinda funny that decompressing actually made it smaller. In any case, it’s still a lot more binary than text. To be more specific, this is a binary XML format that appears in various Konami games. It’s a fairly well-known format, and there a few open-source implementations available on GitHub. For example, here’s one written in Rust, Python, and another in C#.

If we take that original request string, decode it as Base64, decompress it using LZ77, then finally convert it from binary XML to regular XML using one of the libraries above, we finally get something readable:

  <method __type="str">getServices</method>
    <dummy __type="u8">0</dummy>

Fortunately, most of this process can be applied to the response data too. Here’s an example:

00000000: 5032 443a 3230 3135 3039 3138 3030 f2f5  P2D:2015091800..
00000010: d55c 05c4 b0e6 84e8 aeee a879 c5cf 8a36  .\.........y...6
00000020: 0b94 5df4 c925 5e6b d48e 3873 2181 efa0  ..]..%^k..8s!...
00000030: 42a0 5f00 704c 0103 ffd4 2a40 0606 e399  B._.pL....*@....
00000040: b9ff eb80 fe06 05ab 7df4 ffdc fe01 06de  ........}.......
00000050: ae3a c7ff 9006 0ce2 adfb ab79 ff78 e66e  .:.........y.x.n
00000060: 6afe 0912 caff 6bb3 e6a9 78e6 6df9 ff96  j.....k...x.m...
00000070: 8c74 a300 fe09 10fe 0112 6ace 9968 c74a  .t........j..h.J
00000080: 30af fefe feff 0572 1c06 3801 0007 6d00  0......r..8...m.
00000090: 00                                       .

Unlike the request, the response is already binary, so we can skip the Base64 stage and go straight to decompression, which brings us to the next difference. Before the compressed data begins, you’ll notice a protocol version (always P2D:2015091800 for this game), and a signature, which the client uses to verify the response body before any attempt is made to decode it.

Essentially, just skip the header entirely, then decompress and decode as before.

  <status __type="s32">0</status>
  <error __type="s32">0</error>
    <server_state __type="s32">1</server_state>
    <mainte_start_clock __type="u64">0</mainte_start_clock>
    <mainte_end_clock __type="u64">0</mainte_end_clock>

For your convenience, I’ve haphazardly strung a few of the aforementioned projects together to make a custom Fiddler inspector.

It adds an “Eacnet” tab to both requests and responses, which will automatically decode and print out nicely formatted XML.

As a bonus, it should also work with some of the other e-amusement cloud games. I only booted the launchers for GITADORA, SOUND VOLTEX III, pop’n music Lively and NOSTALGIA to test, but it was able to decode everything as expected.

If you’re wondering, the main differences I noticed between INFINITAS and those other games were certain characters in the Base64 request string being replaced (specifically “+” and “/”), and the trailing = being stripped out.

Decrypted Eacnet sessions inspected in Fiddler

Anyway, back to those captured sessions. After reviewing the decoded contents, I noticed that the GetFile response, specifically the one requested from the updater, contained links to a bunch of files. A ton of these had obfuscated filenames, but luckily the binaries for the game, and even the launcher and updater, were readily available in plain text.

I guess that’s half of my update tracker finished already then. I’ll just need to perpetually re-issue this request on a schedule and check the response for changes. What could go wrong? Well… that’s a subject for another day.

That’s all for now. I’ve been having a lot of fun messing around with this game and I’ve got a lot of things I want to write about eventually. In the next post, we’ll take a look at sending requests and why that might be more difficult than it sounds.

Last updated Saturday, 8 January 2022 at 01:15 PM.