Now that we know what requests look like, let’s try creating and posting some of our own. We’ve already captured one that gives us versions, checksums, and links to various game files. Now it’s just a matter of periodically re-issuing it and checking for changes...

...right? Hah, if only it were that simple. Remember that signature in the request query string that I totally ignored last time? Yeah, turns out it’s quite important. Take a look at what happened when I tried to re-submit a captured request a few hours later:

Reissuing the GetFile request in Fiddler some time later Replay → Reissue Unconditionally

That first session was captured from the launcher a long time ago. The second is the same, but re-issued just now.

Misleading status code aside, the response to the second request only contained an error message. After a bit more testing, it became clear that the signatures were “expiring”, and I’d have to re-sign the requests for them to be considered valid.

Here’s a quick demo you can try at home. What happens if you open the updater, close it, wait a minute, then open it again?

Showing a few requests with their signatures in Fiddler One of these is not like the others.

While the contents of all these requests are identical, the signature in the query string is constantly changing. This is true for all but the 5th and 6th requests, which occur within the same minute, and thus, share the same signature.

Something worth mentioning was that the GetServerClock request and everything before it could be re-issued, even though a few hours had passed since the original request. Everything following that request resulted in the same ‘status 9’ error.

From this, let’s assume that the GetServerClock request is used to “synchronize” your local system time with a time value provided by the server, which is what ensures all subsequent requests will have valid signatures. That’s another thing we’ll have to keep in mind, but for now, let’s just focus on generating some signatures programmatically.

First things first, let’s see how the updater does it. If you have a really good memory, you’ll remember seeing a few mentions of makeRequestStringAndSignature in the previous post, which showed up while searching for strings containing “request”.

Ignore those and search for “failed to makeRequestStringAndSignature“ instead. If you look a bit above where the string is referenced, you should see a call, immediately followed by a test instruction. Set a breakpoint on the call.

The breakpoint will hit on the initial getServices request, so the address in the rcx register should be pointing to an instance of EacnetRequestServices, which inherits from EacnetRequestPost and EacnetRequestBase. I’ve partially mapped out the class structure below by looking through various virtual functions.

As this is before the call occurs, we can see the ‘request’ and ‘signature’ members are currently empty:

Before and after the call to makeRequestStringAndSignature.

Stepping over the call, both request and signature variables are now populated. Honestly, that’s… pretty much all there is to it. If you copied these strings out from the class and (quickly) sent off a request, you’d get a valid response back.

Time to step through that function, reverse engineer and reimplement it, right? Nah… I mean, you could, but I think it’d be more fun, but mostly a lot easier, to hijack the game process and turn it into a server for encoding and decoding messages instead.

Okay, with my thinly veiled excuse to try out some REST libraries out of the way, let’s waste no time and get straight into it. We’ll start simple and work our way up, beginning with creating a request, and using the aforementioned function to do all the signing stuff for us. This will be compiled as a dynamic-link library and, for now, manually injected into the game process.

That said, creating a request and calling makeRequestStringAndSignature from the entrypoint won’t work. Instead, we’ll have to create a new thread using an exported function from avs2-core.dll and move our code into there. If you’ve looked around the directories of any Konaste game, you’ve probably seen this library (or its 32-bit variant under a different name) before.

What does AVS stand for? Who knows? I’ve just noticed that all the Konaste games use it, and it provides stuff like configuration, filesystem, networking, threading, memory management, and so on. All the export names are obfuscated, but you can get a general idea of what they do by looking at the error strings. I found these thread functions without too much hassle:

#define thread_create                XCgsqzn0000004
#define thread_join                  XCgsqzn000000b
#define thread_destroy               XCgsqzn0000006

extern "C"
    std::int32_t thread_create(void* proc, void* param, std::uint32_t sz_stack, std::uint32_t priority);
    void* thread_join(std::int32_t thread, void* = nullptr);
    void* thread_destroy(std::int32_t thread);

You could resolve these with GetModuleHandle and GetProcAddress, but I’ve recently started using gendef and dlltool instead, which can be installed in MSYS2 via the mingw-w64-x86_64-tools and mingw-w64-x86_64-binutils packages.

These tools allow you to generate a .lib file from avs2-core.dll, which you can link against when building your library.

$ gendef avs2-core.dll
 * [avs2-core.dll] Found PE+ image
$ dlltool --input-def=avs2-core.def --dllname=avs2-core.dll --output-lib=avs2-core.lib

Here’s how you can use it with CMake. Just make sure to copy avs2-core.lib into the “lib” directory after generating it.

target_link_directories(${PROJECT_NAME} PRIVATE lib)
target_link_libraries(${PROJECT_NAME} avs2-core)

Now we’ve got the preparation stuff out of the way, let’s continue writing our library code.

Here’s an extremely simplified structure for EacnetRequestBase based on what I found in ReClass earlier:

struct EacnetRequestBase
    virtual ~EacnetRequestBase() = 0;
    virtual void* setup(bool*) = 0;

    auto get_request() { return *reinterpret_cast<const char**>(std::uintptr_t(this) + 0x88); }
    auto get_signature() { return *reinterpret_cast<const char**>(std::uintptr_t(this) + 0xA8); }

There are a few more important fields, such as the request property, but we’ll add those as the need arises. In the meantime, we’ll need to call the setup virtual function after creating the request object, but before making the request string and signature.

To create a request object, we simply need to call createRequestObject – a function that can be found by… you guessed it, searching for the “createRequestObject” string. If you see a huge switch statement, you’ve found the right function.

Decompiled view of EacnetCom::createRequestObject

The first case in the switch corresponds to EacnetRequestServices, so we pass 0 as the second argument. The first argument, a1, doesn’t appear to be used for anything, so it’s fine to just pass nullptr there. Here’s what all this looks like so far:

    if (reason != DLL_PROCESS_ATTACH)
        return FALSE;


    freopen_s(reinterpret_cast<FILE**>(stderr), "CONOUT$", "w", stderr);
    freopen_s(reinterpret_cast<FILE**>(stdout), "CONOUT$", "w", stdout);

    auto thread = thread_create(reinterpret_cast<void*>(+[] ()
        auto const createRequestObject = reinterpret_cast<void* (*) (void*, std::int32_t)>(0x1402A6760);
        auto const makeRequestStringAndSignature = reinterpret_cast<bool (*) (void*)>(0x1402C51E0);

        auto request = reinterpret_cast<EacnetRequestBase*>(createRequestObject(nullptr, 0));
        auto param = true;


        printf("request:   %s\n", request->get_request());
        printf("signature: %s\n", request->get_signature());

    }), nullptr, 0x1000, 1);



    return FALSE;
Console window displaying a request string and signature

One down, four to go. As a quick reminder, here’s the full list of requests we’re going to be making:

In case you were wondering, yes, you could definitely skip most of these. I just thought it would be a bit underwhelming if I only created one request, but by all means, feel free to go straight to EacnetRequestResourceInfo if you want.

Moving on, the next request is EacnetRequestServerState... but there’s a problem.

So far I’ve been targeting the game process, bm2dx.exe, but after looking through all the cases in createRequestObject, it turns out you can’t actually get a EacnetRequestResourceInfo request from it. Likewise, it doesn’t show up in Class Informer either.

EacnetCom::createRequestObject differs depending on the binary.

As shown above, an ID of 7 can yield two completely different requests depending on which binary you’re loaded into. It makes sense that the game wouldn’t have requests exclusive to the launcher and vice versa, but it’s pretty inconvenient for us.

…or it would be, but we’ve already got all we need with our single instance of EacnetRequestServices!

As it turns out, the request body is determined by the request property, which can be found 0x50 bytes in to the class.

It’s nullptr until the setup virtual function is called, which is what we did for EacnetRequestServices, but this isn’t necessary if we create a custom property, swap the pointer in the class to use it, then call makeRequestStringAndSignature.

So that’s exactly what we’ll do. All the necessary functions are provided by AVS2, so we’ll need to expand our definitions a bit.

#define property_create              XCgsqzn0000090
#define property_destroy             XCgsqzn0000091
#define property_set_flag            XCgsqzn000009a
#define property_mem_write           XCgsqzn00000b8
#define property_query_size          XCgsqzn000009f
#define property_insert_read         XCgsqzn0000094
#define property_read_query_memsize  XCgsqzn00000b0

enum property_flag
    PROP_XML    = 0x00,
    PROP_READ   = 0x01,
    PROP_WRITE  = 0x02,
    PROP_CREATE = 0x04,
    PROP_BINARY = 0x08,
    PROP_APPEND = 0x10,

extern "C"
    void* property_create(std::uint32_t flags, void* buffer, std::uint32_t size);
    std::int32_t property_destroy(void* property);
    std::int32_t property_set_flag(void* property, std::uint32_t set_flags, std::uint32_t clear_flags);
    std::int32_t property_mem_write(void* property, void* buffer, std::uint32_t size);
    std::int32_t property_query_size(void* property);
    std::int32_t property_insert_read(void* property, void* node, void* reader, std::uint32_t file);
    std::int32_t property_read_query_memsize(void* fn, std::int32_t file, void* = nullptr, void* = nullptr);

We’ll also need to add another function to EacnetRequestBase for when we’re ready to swap the property:

auto set_request_property(void* property) { *reinterpret_cast<void**>(std::uintptr_t(this) + 0x50) = property; }

And now for the star of the show, we’ve got this helper class to handle property creation:

struct xml_property
    explicit xml_property(std::string_view src): body(src)
        auto const index = TlsAlloc();
        TlsSetValue(index, this);

        auto static reader = +[] (std::uint32_t param, void* dst, std::size_t nbytes) -> std::size_t
            auto property = reinterpret_cast<xml_property*>(TlsGetValue(param));
            auto start = property-> + property->position;

            if (property->position + nbytes > property->body.size())
                nbytes = property->body.size() - property->position;

            if (nbytes == 0)
                return 0;

            std::memcpy(dst, start, nbytes);
            property->position += nbytes;

            return nbytes;

        auto const size = property_read_query_memsize(reader, index, nullptr, nullptr);
        auto const flags = PROP_XML | PROP_READ | PROP_WRITE | PROP_CREATE | PROP_APPEND;

        buffer = std::make_unique<std::uint8_t[]>(size);
        instance = property_create(flags, buffer.get(), size);
        position = 0;

        property_insert_read(instance, nullptr, reader, index);
        property_set_flag(instance, PROP_BINARY, PROP_XML);


    ~xml_property() { property_destroy(instance); }
    xml_property(const xml_property&) = delete;
    xml_property& operator=(const xml_property&) = delete;

    void* instance = nullptr;
    std::string_view body = {};
    std::size_t position = 0;
    std::unique_ptr<std::uint8_t[]> buffer = nullptr;

To give a quick rundown of how this works, we call property_read_query_memsize to determine how much memory we’ll need to allocate for the property. The reader function only accepts a 32-bit value (probably because AVS2 file handles are 32-bit), so we copy the address of our xml_property to a TLS slot and pass the index of that instead.

Next, we allocate the memory and create the property. It’ll begin as XML, so we set PROP_XML when creating it. The position was previously advanced by property_read_query_memsize, so we need to reset it before calling property_insert_read.

Finally, we convert from PROP_XML to PROP_BINARY instead, allowing us to call makeRequestStringAndSignature.

With that out of the way, it’s time to make some more requests. How about all of them in one go?

auto const samples = std::vector<std::tuple<std::string, std::string>> {
    {"EacnetRequestServices",    "<p2d>"
                                 "  <method __type=\"str\">getServices</method>"
                                 "  <params>"
                                 "    <dummy __type=\"u8\">0</dummy>"
                                 "  </params>"
    {"EacnetRequestServerState", "<p2d>\n"
                                 "  <method __type=\"str\">getServerState</method>\n"
    {"EacnetRequestServerClock", "<p2d>\n"
                                  "  <method __type=\"str\">getServerClock</method>\n"
                                  "  <params>\n"
                                  "    <dummy __type=\"u8\">0</dummy>\n"
                                  "  </params>\n"
    {"EacnetRequestLogCheck",     "<p2d>\n"
                                  "  <method __type=\"str\">checkSendLogAvailable</method>\n"
                                  "  <params>\n"
                                  "    <dummy __type=\"u8\">0</dummy>\n"
                                  "  </params>\n"
    {"EacnetRequestResourceInfo", "<p2d>\n"
                                  "  <method __type=\"str\">getResourceInfo</method>\n"
                                  "  <params>\n"
                                  "    <full __type=\"bool\">1</full>\n"
                                  "  </params>\n"

I captured these requests from the updater process, then decoded them to XML with my Fiddler inspector plugin.

We can re-use our existing instance from createRequestObject by swapping out the request property between each call to makeRequestStringAndSignature. Just be careful to set this back to nullptr before calling the virtual destructor!

auto thread = thread_create(reinterpret_cast<void*>(+[] ()
    auto const createRequestObject = reinterpret_cast<void* (*) (void*, std::int32_t)>(0x1402A6760);
    auto const makeRequestStringAndSignature = reinterpret_cast<bool (*) (void*)>(0x1402C51E0);

    auto request = reinterpret_cast<EacnetRequestBase*>(createRequestObject(nullptr, 0));

    for (auto const& [name, body]: samples)
        auto property = xml_property(body);


        printf("[%s]\n", name.c_str());
        printf("request:   %s\n", request->get_request());
        printf("signature: %s\n\n", request->get_signature());

}), nullptr, 0x1000, 1);
Console window displaying several request string and signatures

Just one thing left now before we can actually start submitting these – setting the Eacnet clock.

Once again, it’s... a little underwhelming. Search for the “eacne_clock initialized” string, follow the reference, copy the address of the function, then call it with the 64-bit value you got from EacnetRequestServerClock and, yeah, that’s it.

auto const initializeClock = reinterpret_cast<void* (*) (std::uint64_t)>(0x140297D80);

For example, this capture from January returned a server clock of 1642772720510. If we pass that to initializeClock, the signature for the newly generated EacnetRequestResourceInfo should match the one captured back then.

Matching signatures from past and present requests Generating a request with the same signature 6 months later.

I suppose that’s everything I wanted to cover in this post. If you’re looking for a more ‘preassembled’ version of the above, check out my simple REST service “infapi” over on GitHub. Some things worth checking out in that repository are:

Lastly, there’s also a Python script which performs the requests above and produces an aria2 download list as a result.

Last updated Monday, 11 July 2022 at 12:16 PM.