WELCOME TO INFOCHEATS.NET

INFOCHEATS is a community-driven platform focused on free game cheats, cheat development, and verified commercial software for a wide range of popular games. We provide a large collection of free cheats shared by the community. All public releases are checked for malicious code to reduce the risk of viruses, malware, or unwanted software before users interact with them.

Alongside free content, INFOCHEATS hosts an active marketplace with many independent sellers offering commercial cheats. Each product is discussed openly, with user feedback, reviews, and real usage experience available to help you make informed decisions before purchasing.

Whether you are looking for free cheats, exploring paid solutions, comparing sellers, or studying how cheats are developed and tested, INFOCHEATS brings everything together in one place — transparently and community-driven.

Source HELLDIVERS 2 — Super Earth Tech: Crypto & API Request Base

byte_corvus

Newbie
Newbie
Newbie
Newbie
Status
Offline
Joined
Mar 3, 2026
Messages
667
Reaction score
457
If you're tired of guessing how HELLDIVERS 2 handles its session signing and mission activity, here's the "Super Earth Technology" source. No hand-holding, no "vtable for dummies" basics, and no explanations on how to inject a DLL—just the technical meat. This is a framework for intercepting and rebuilding the game's communication with its backend.

Architecture Overview
The setup is split into three core components:
  1. server/hd2_api.h — The heart of the project. Contains the crypto logic, MurmurHash64A implementation, XOR stream handling, and the signature builder for Mission/Activity bodies.
  2. server/HD2BlueCoin.cpp — The injected internal module. It leverages a DXGI Present hook to stay in sync with the game loop and handles the heavy lifting of pointer dereferencing.
  3. client/main.cpp — A TUI frontend that tails logs and manages the WinHTTP communication with the DLL.

Cryptographic Primitive & Signatures
The game uses a specific combination of hashing and encryption to validate requests. The source provides a clean implementation of the MurmurHash64A used for object IDs and key name hashes. It also demonstrates how to invoke the game's own internal SHA256 services by hitting the vtables for the signing context.

Code:
#pragma once
#include <windows.h>
#include <cstdint>
#include <cstring>
#include <string>
#include <vector>
 
namespace hd2api {
 
constexpr uint64_t MURMUR_M  = 0xC6A4A7935BD1E995ULL;
constexpr uint64_t EPOCH_1601_TO_1970 = 0x019DB1DED53E8000ULL;
constexpr uint32_t ACTIVITY_ID = 2140130854u;
 
namespace vtoff {
    constexpr size_t SIGN_SVC   = 0x10;
    constexpr size_t FINAL_SVC  = 0x128;
    constexpr size_t UPDATE_SVC = 0x138;
 
    constexpr size_t GET_DIGEST      = 0x38;
    constexpr size_t CREATE_CTX      = 0x5B8;
    constexpr size_t GET_KEY_ID      = 0x5E0;
 
    constexpr size_t SHA256_FINAL    = 0xA0;
 
    constexpr size_t SHA256_UPDATE   = 0x40;
    constexpr size_t GET_KEY_NAME    = 0x70;
}
 
struct HdSession {
    uint64_t crypto_ctx;
    uint64_t signing_ctx;
    const char* key_id;
    uint64_t key_name_hash;
    uint64_t ct;
    uint64_t _pad28;
    uint64_t xor_key;
    char key_name_buf[16];
    uint64_t key_name_size;
    uint64_t key_name_cap;
};
static_assert(sizeof(HdSession) == 0x58, "HdSession size mismatch");
 
class GameCrypto {
public:
    using FnCreateCtx    = void*       (__fastcall*)(void* session);
    using FnSha256Update = void        (__fastcall*)(void* ctx, const void* data, uint64_t len);
    using FnSha256Final  = void        (__fastcall*)(void* ctx, uint64_t mode);
    using FnGetDigest    = void        (__fastcall*)(void* ctx, void* out32);
    using FnGetKeyId     = const char* (__fastcall*)(int slot);
    using FnGetKeyName   = const char* (__fastcall*)();
 
private:
    uintptr_t crypto_ctx_ = 0;
 
    FnCreateCtx    fn_create_ctx_    = nullptr;
    FnSha256Update fn_sha256_update_ = nullptr;
    FnSha256Final  fn_sha256_final_  = nullptr;
    FnGetDigest    fn_get_digest_    = nullptr;
    FnGetKeyId     fn_get_key_id_    = nullptr;
    FnGetKeyName   fn_get_key_name_  = nullptr;
 
    static uintptr_t read_ptr(uintptr_t addr)
    {
        return *reinterpret_cast<uintptr_t*>(addr);
    }
 
    static bool valid(uintptr_t p)
    {
        return p > 0x10000 && p < 0x00007FFFFFFFFFFFULL;
    }
 
public:
    bool init(uintptr_t crypto_ctx)
    {
        crypto_ctx_ = crypto_ctx;
        if (!valid(crypto_ctx))
            return false;
 
        uintptr_t svc_sign  = read_ptr(crypto_ctx + vtoff::SIGN_SVC);
        uintptr_t svc_final = read_ptr(crypto_ctx + vtoff::FINAL_SVC);
        uintptr_t svc_upd   = read_ptr(crypto_ctx + vtoff::UPDATE_SVC);
 
        if (!valid(svc_sign))
            return false;
 
        fn_create_ctx_    = reinterpret_cast<FnCreateCtx>   (read_ptr(svc_sign  + vtoff::CREATE_CTX));
        fn_get_digest_    = reinterpret_cast<FnGetDigest>   (read_ptr(svc_sign  + vtoff::GET_DIGEST));
        fn_get_key_id_    = reinterpret_cast<FnGetKeyId>    (read_ptr(svc_sign  + vtoff::GET_KEY_ID));
        fn_sha256_final_  = svc_final ? reinterpret_cast<FnSha256Final> (read_ptr(svc_final + vtoff::SHA256_FINAL))  : nullptr;
        fn_sha256_update_ = svc_upd   ? reinterpret_cast<FnSha256Update>(read_ptr(svc_upd   + vtoff::SHA256_UPDATE)) : nullptr;
        fn_get_key_name_  = svc_upd   ? reinterpret_cast<FnGetKeyName>  (read_ptr(svc_upd   + vtoff::GET_KEY_NAME))  : nullptr;
 
        return fn_create_ctx_ && fn_sha256_update_ && fn_sha256_final_ &&
               fn_get_digest_ && fn_get_key_id_    && fn_get_key_name_;
    }
 
    uintptr_t ctx() const { return crypto_ctx_; }
 
    void* create_signing_ctx()
    {
        if (!fn_create_ctx_)
            return nullptr;
        uintptr_t svc_sign = read_ptr(crypto_ctx_ + vtoff::SIGN_SVC);
        if (!valid(svc_sign))
            return nullptr;
        return fn_create_ctx_(reinterpret_cast<void*>(svc_sign));
    }
 
    void sha256_update(void* ctx, const void* data, size_t len)
    {
        if (fn_sha256_update_ && ctx)
            fn_sha256_update_(ctx, data, static_cast<uint64_t>(len));
    }
 
    void sha256_final(void* ctx)
    {
        if (fn_sha256_final_ && ctx)
            fn_sha256_final_(ctx, 7);
    }
 
    bool get_signature(void* ctx, uint8_t out[32])
    {
        if (!fn_get_digest_ || !ctx)
            return false;
        std::memset(out, 0, 32);
        fn_get_digest_(ctx, out);
        return *reinterpret_cast<uint32_t*>(out) != 0;
    }
 
    std::string get_key_id()
    {
        if (!fn_get_key_id_)
            return {};
        const char* p = fn_get_key_id_(7);
        return p ? std::string(p) : std::string();
    }
 
    std::string get_key_name()
    {
        if (!fn_get_key_name_)
            return {};
        const char* p = fn_get_key_name_();
        return p ? std::string(p) : std::string();
    }
};
 
inline uint64_t read_u64_le(const uint8_t* p)
{
    uint64_t v = 0;
    std::memcpy(&v, p, 8);
    return v;
}
 
inline uint64_t murmur64a_body(const void* key, size_t len, uint64_t h)
{
    const auto* data = static_cast<const uint8_t*>(key);
    const size_t nblocks = len / 8;
 
    for (size_t i = 0; i < nblocks; ++i) {
        uint64_t k = read_u64_le(data + i * 8);
        k *= MURMUR_M;
        k ^= k >> 47;
        k *= MURMUR_M;
        h ^= k;
        h *= MURMUR_M;
    }
 
    const uint8_t* tail = data + nblocks * 8;
    switch (len & 7) {
    case 7: h ^= uint64_t(tail[6]) << 48; [[fallthrough]];
    case 6: h ^= uint64_t(tail[5]) << 40; [[fallthrough]];
    case 5: h ^= uint64_t(tail[4]) << 32; [[fallthrough]];
    case 4: h ^= uint64_t(tail[3]) << 24; [[fallthrough]];
    case 3: h ^= uint64_t(tail[2]) << 16; [[fallthrough]];
    case 2: h ^= uint64_t(tail[1]) << 8;  [[fallthrough]];
    case 1: h ^= uint64_t(tail[0]);
            h *= MURMUR_M;
    default: break;
    }
    return h;
}
 
inline uint64_t murmur64a_finalize(uint64_t h)
{
    h ^= h >> 47;
    h *= MURMUR_M;
    return h;
}
 
inline uint32_t compute_object_id(const std::string& uuid_str)
{
    uint64_t h = murmur64a_body(uuid_str.data(), uuid_str.size(), 0);
    h = murmur64a_finalize(h);
    return static_cast<uint32_t>(h >> 32);
}
 
inline uint64_t compute_key_name_hash(const std::string& key_name)
{
    uint64_t h_init = key_name.size() * MURMUR_M;
    uint64_t h = murmur64a_body(key_name.data(), key_name.size(), h_init);
    return murmur64a_finalize(h);
}
 
inline uint64_t filetime_to_unix_us()
{
    FILETIME ft{};
    GetSystemTimeAsFileTime(&ft);
    uint64_t t = (uint64_t(ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
    if (t < EPOCH_1601_TO_1970)
        return 0;
    return (t - EPOCH_1601_TO_1970) / 10;
}
 
inline uint64_t compute_xor_key(uint64_t ct, uint64_t key_name_hash)
{
    uint64_t h = murmur64a_finalize(key_name_hash);
    h = murmur64a_finalize(h);
    uint64_t time_bucket = (ct / 1000000ULL) / 30ULL;
    return MURMUR_M * (time_bucket ^ h);
}
 
inline void xor_encrypt_inplace(uint8_t* data, size_t len, uint64_t xor_key)
{
    uint8_t kb[8];
    std::memcpy(kb, &xor_key, 8);
    for (size_t i = 0; i < len; ++i)
        data[i] ^= kb[i & 7];
}
 
inline std::string xor_encrypt_copy(const std::string& body, uint64_t xor_key)
{
    std::string out = body;
    xor_encrypt_inplace(reinterpret_cast<uint8_t*>(&out[0]), out.size(), xor_key);
    return out;
}
 
inline std::string base64_encode(const uint8_t* data, size_t len)
{
    static const char tbl[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    std::string out;
    out.reserve(((len + 2) / 3) * 4);
    for (size_t i = 0; i < len; i += 3) {
        uint32_t n = uint32_t(data[i]) << 16;
        if (i + 1 < len) n |= uint32_t(data[i + 1]) << 8;
        if (i + 2 < len) n |= uint32_t(data[i + 2]);
        out.push_back(tbl[(n >> 18) & 63]);
        out.push_back(tbl[(n >> 12) & 63]);
        out.push_back((i + 1 < len) ? tbl[(n >> 6) & 63] : '=');
        out.push_back((i + 2 < len) ? tbl[n & 63] : '=');
    }
    return out;
}
 
inline std::string generate_uuid()
{
    uint8_t b[16];
    HMODULE advapi = GetModuleHandleA("advapi32.dll");
    using RtlGenRandomFn = BOOLEAN(APIENTRY*)(PVOID, ULONG);
    auto rtl = advapi ? reinterpret_cast<RtlGenRandomFn>(GetProcAddress(advapi, "SystemFunction036")) : nullptr;
    if (rtl)
        rtl(b, sizeof(b));
    else
        for (auto& x : b) x = static_cast<uint8_t>(rand() & 0xFF);
    b[6] = (b[6] & 0x0F) | 0x40;
    b[8] = (b[8] & 0x3F) | 0x80;
 
    char buf[64];
    snprintf(buf, sizeof(buf),
        "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
        b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7],
        b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15]);
    return buf;
}
 
inline std::string json_escape(const std::string& s)
{
    std::string out;
    out.reserve(s.size() + 8);
    for (char c : s) {
        if (c == '"' || c == '\\') out.push_back('\\');
        out.push_back(c);
    }
    return out;
}
 
inline std::string build_json_body(
    uint32_t activity_id,
    const std::string& gmi_str,
    const std::string& mission_id,
    uint32_t object_id,
    const std::vector<std::string>& participants)
{
    std::string j;
    j.reserve(512);
    j += "{\"activityId32\":";
    j += std::to_string(activity_id);
    j += ",\"gmiStr\":\"";
    j += json_escape(gmi_str);
    j += "\",\"missionId\":\"";
    j += json_escape(mission_id);
    j += "\",\"objectId\":";
    j += std::to_string(object_id);
    j += ",\"participants\":[";
    for (size_t i = 0; i < participants.size(); ++i) {
        if (i) j += ",";
        j += "\"" + json_escape(participants[i]) + "\"";
    }
    j += "]}";
    return j;
}
 
struct SignResult {
    std::string signature_b64;
    uint64_t ct         = 0;
    uint64_t xor_key    = 0;
    bool ok             = false;
};
 
inline SignResult sign_body(
    GameCrypto& crypto,
    void* signing_ctx,
    const std::string& key_id,
    const std::string& key_name,
    const std::string& json_body)
{
    SignResult r;
    if (!signing_ctx || key_id.empty() || key_name.empty() || json_body.empty())
        return r;
 
    r.ct = filetime_to_unix_us();
    uint64_t knh = compute_key_name_hash(key_name);
    r.xor_key = compute_xor_key(r.ct, knh);
 
    std::string encrypted = xor_encrypt_copy(json_body, r.xor_key);
 
    uint8_t xk_bytes[8];
    std::memcpy(xk_bytes, &r.xor_key, 8);
 
    crypto.sha256_update(signing_ctx, key_id.data(), key_id.size());
    crypto.sha256_update(signing_ctx, xk_bytes, 8);
    crypto.sha256_update(signing_ctx, encrypted.data(), encrypted.size());
 
    crypto.sha256_final(signing_ctx);
 
    uint8_t digest[32]{};
    r.ok = crypto.get_signature(signing_ctx, digest);
    if (r.ok)
        r.signature_b64 = base64_encode(digest, sizeof(digest));
 
    return r;
}
 
struct ApiRequest {
    std::string url;
    std::string body;
    std::string host;
    std::string path;
    std::string user_agent;
    std::string authorization;
    std::string key_id;
    std::string x_signature;
    uint64_t ct = 0;
};
 
inline ApiRequest build_mission_activity_request(
    GameCrypto& crypto,
    void* signing_ctx,
    const std::string& gmi_str,
    const std::string& auth_token,
    const std::vector<std::string>& participants)
{
    ApiRequest req;
    req.host = "api.live.prod.thehelldiversgame.com";
    req.path = "/api/Operation/Mission/Activity";
    req.url  = "https://" + req.host + req.path;
 
    std::string key_id   = crypto.get_key_id();
    std::string key_name = crypto.get_key_name();
    req.key_id       = key_id;
    req.user_agent   = key_name;
    req.authorization = auth_token;
 
    std::string mission_id = generate_uuid();
    uint32_t object_id = compute_object_id(mission_id);
 
    req.body = build_json_body(ACTIVITY_ID, gmi_str, mission_id, object_id, participants);
 
    SignResult sig = sign_body(crypto, signing_ctx, key_id, key_name, req.body);
    req.x_signature = sig.signature_b64;
    req.ct          = sig.ct;
 
    return req;
}
 
inline std::string hex_encode(const void* data, size_t len)
{
    static const char kHex[] = "0123456789ABCDEF";
    const auto* p = static_cast<const uint8_t*>(data);
    std::string out(len * 2, '\0');
    for (size_t i = 0; i < len; ++i) {
        out[i * 2 + 0] = kHex[(p[i] >> 4) & 0xF];
        out[i * 2 + 1] = kHex[p[i] & 0xF];
    }
    return out;
}
 
}

Offset Management & AOB Scanning
One of the better features here is the robust offset resolution system. It doesn't just rely on hardcoded RVA addresses that break every Tuesday. It implements a snapshot/cache system using AOB (Array of Bytes) signatures. If the hardcoded offset fails validation (e.g., checking if the pointer leads to a valid vtable), it triggers a signature scan and saves the new result to a local cache file in AppData.

Technical Notes & Safety
Crypto Health: The module includes a smoke test for the crypto context before attempting to sign requests. If your voffsets are stale, it will catch the access violation and log the error rather than just BSODing your rig.
Networking: Uses libcurl for the actual delivery of rebuilt JSON bodies.
Anti-Cheat: This is an internal approach via DXGI hooks. While it bypasses some client-side hurdles, remember that server-side telemetry is always watching what you're reporting for mission stats.

Code:
#define NOMINMAX
#define WIN32_LEAN_AND_MEAN
#include <winsock2.h>
#include <ws2tcpip.h>
#include <windows.h>
#include <psapi.h>
#include <shlobj.h>
#include <d3d11.h>
#include <dxgi1_2.h>
#include <winhttp.h>
 
#include <atomic>
#include <cstdarg>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <mutex>
#include <string>
#include <vector>
#include <algorithm>
#include <unordered_map>
 
#include "hd2_api.h"
 
#pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "dxgi.lib")
#pragma comment(lib, "psapi.lib")
#pragma comment(lib, "user32.lib")
#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "shell32.lib")
#pragma comment(lib, "winhttp.lib")
 
namespace hd2 {
 
constexpr uint16_t HTTP_PORT = 19880;
constexpr uint16_t LOG_PORT  = 19881;
 
constexpr uintptr_t OFF_CRYPTO_CTX_PTR = 0x19CDF98;
constexpr uintptr_t OFF_AUTH_TOKEN_PTR = 0x19E0450;
constexpr uintptr_t OFF_GMISTR_BUF     = 0x1928430;
constexpr uintptr_t OFF_GAME_ROOM_SCAN = 0x972280;
 
using CurlEasyInit      = void*  (__cdecl*)();
using CurlEasyCleanup   = void   (__cdecl*)(void*);
using CurlEasySetopt    = int    (__cdecl*)(void*, int, ...);
using CurlEasyGetinfo   = int    (__cdecl*)(void*, int, ...);
using CurlEasyPerform   = int    (__cdecl*)(void*);
using CurlSlistAppend   = void*  (__cdecl*)(void*, const char*);
using CurlSlistFreeAll  = void   (__cdecl*)(void*);
using CurlMultiInit     = void*  (__cdecl*)();
using CurlMultiAddHandle= int    (__cdecl*)(void*, void*);
using CurlMultiPerform  = int    (__cdecl*)(void*, int*);
using CurlMultiInfoRead = void*  (__cdecl*)(void*, int*);
using CurlMultiRemove   = int    (__cdecl*)(void*, void*);
using CurlMultiCleanup  = int    (__cdecl*)(void*);
using CurlEasyStrerror  = const char* (__cdecl*)(int);
 
struct CurlFns {
    CurlEasyInit       easy_init       = nullptr;
    CurlEasySetopt     easy_setopt     = nullptr;
    CurlEasyCleanup    easy_cleanup    = nullptr;
    CurlEasyGetinfo    easy_getinfo    = nullptr;
    CurlEasyPerform    easy_perform    = nullptr;
    CurlSlistAppend    slist_append    = nullptr;
    CurlSlistFreeAll   slist_free_all  = nullptr;
    CurlMultiInit      multi_init      = nullptr;
    CurlMultiAddHandle multi_add       = nullptr;
    CurlMultiPerform   multi_perform   = nullptr;
    CurlMultiInfoRead  multi_info_read = nullptr;
    CurlMultiRemove    multi_remove    = nullptr;
    CurlMultiCleanup   multi_cleanup   = nullptr;
    CurlEasyStrerror   easy_strerror   = nullptr;
    bool ready() const {
        return easy_init && easy_setopt && easy_cleanup && easy_getinfo &&
               easy_perform && slist_append && slist_free_all &&
               multi_init && multi_add && multi_perform &&
               multi_info_read && multi_remove && multi_cleanup;
    }
};
 
struct ResponseBuffer {
    char* data = nullptr;
    size_t used = 0;
    size_t cap  = 0;
};
 
struct PatchEntry {
    void** slot     = nullptr;
    void*  original = nullptr;
};
 
struct CurlMsg {
    int msg;
    void* easy_handle;
    union { void* whatever; int result; } data;
};
 
enum class SendState : uint32_t {
    Idle = 0,
    Building,
    Sending,
    Done,
};
 
struct SendContext {
    SendState state = SendState::Idle;
    void*  curl_easy   = nullptr;
    void*  curl_multi  = nullptr;
    void*  slist       = nullptr;
    char*  resp_mem    = nullptr;
    ResponseBuffer resp{};
    std::string body_keep;
    DWORD  result_code = 0xFFFFFFFFu;
    DWORD  trigger_tick = 0;
 
    std::vector<std::string> override_participants;
    uint32_t job_id     = 0;
    uint32_t shot_index = 0;
 
    std::atomic<bool> result_recorded{false};
};
 
struct ShotResult {
    uint32_t i = 0;
    bool     ok = false;
    uint32_t http_code = 0;
    int      curl_code = 0;
    std::string account_id;
    std::string amount;
    std::string message;
};
 
struct Job {
    std::vector<ShotResult> shots;
    std::atomic<uint32_t>   ok_count{0};
    std::atomic<uint32_t>   fail_count{0};
};
 
static std::atomic<DWORD>    g_last_http_code{0xFFFFFFFFu};
static std::atomic<bool>     g_unloading{false};
static std::atomic<bool>     g_hook_installed{false};
static std::atomic<uint32_t> g_send_count{0};
static std::atomic<uint32_t> g_send_ok{0};
static std::atomic<uint32_t> g_send_fail{0};
 
static uintptr_t g_hd2_base  = 0;
static uintptr_t g_game_base = 0;
static uintptr_t g_room_ctx  = 0;
static uintptr_t g_cached_game_room_account = 0;
static HMODULE   g_module    = nullptr;
static HANDLE    g_task_done_event = nullptr;
static HWND      g_hwnd         = nullptr;
static HWND      g_helper_hwnd  = nullptr;
static void*     g_present_orig  = nullptr;
static void*     g_present1_orig = nullptr;
static std::vector<PatchEntry> g_patches;
static std::string g_last_error;
static hd2api::GameCrypto g_crypto;
static CurlFns g_curl;
static SendContext g_send;
 
static HRESULT (STDMETHODCALLTYPE* g_real_present)(IDXGISwapChain*, UINT, UINT) = nullptr;
static HRESULT (STDMETHODCALLTYPE* g_real_present1)(IDXGISwapChain1*, UINT, UINT, const DXGI_PRESENT_PARAMETERS*) = nullptr;
 
static const wchar_t* kHelperClassName = L"ShikinamiAsuka_HD2BC";
 
static std::mutex              g_log_clients_mutex;
static std::vector<SOCKET>     g_log_clients;
static HANDLE                  g_log_thread   = nullptr;
static HANDLE                  g_http_thread  = nullptr;
 
static std::mutex              g_jobs_mutex;
static std::unordered_map<uint32_t, std::shared_ptr<Job>> g_jobs;
static std::atomic<uint32_t>   g_next_job_id{1};
 
static std::mutex  g_log_file_mu;
static HANDLE      g_log_file = INVALID_HANDLE_VALUE;
static bool        g_log_file_tried = false;
 
static void ensure_log_file()
{
    if (g_log_file != INVALID_HANDLE_VALUE || g_log_file_tried) return;
    g_log_file_tried = true;
    wchar_t buf[MAX_PATH] = {};
    if (SHGetFolderPathW(nullptr, CSIDL_LOCAL_APPDATA, nullptr, 0, buf) != S_OK) return;
    std::wstring d = buf; d += L"\\HD2BlueCoin";
    CreateDirectoryW(d.c_str(), nullptr);
    std::wstring p = d + L"\\bluecoin.log";
    g_log_file = CreateFileW(p.c_str(),
                             FILE_APPEND_DATA,
                             FILE_SHARE_READ | FILE_SHARE_WRITE,
                             nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
}
 
static void log_file_write(const char* data, int len)
{
    std::lock_guard<std::mutex> lock(g_log_file_mu);
    ensure_log_file();
    if (g_log_file == INVALID_HANDLE_VALUE) return;
    DWORD wr = 0;
    WriteFile(g_log_file, data, (DWORD)len, &wr, nullptr);
    FlushFileBuffers(g_log_file);
}
 
static void push_one_line(const char* msg, int total)
{
    std::lock_guard<std::mutex> lock(g_log_clients_mutex);
    for (auto it = g_log_clients.begin(); it != g_log_clients.end(); ) {
        int ret = ::send(*it, msg, total, 0);
        if (ret == SOCKET_ERROR) {
            closesocket(*it);
            it = g_log_clients.erase(it);
        } else {
            ++it;
        }
    }
}
 
static void logf(const char* fmt, ...)
{
    char msg[4096];
    va_list ap;
    va_start(ap, fmt);
    int len = vsnprintf(msg, sizeof(msg) - 2, fmt, ap);
    va_end(ap);
    if (len < 0) len = 0;
 
    {
 
        char buf[4100];
        int nbuf = (len < (int)sizeof(buf) - 2) ? len : (int)sizeof(buf) - 2;
        memcpy(buf, msg, nbuf);
        buf[nbuf]   = '\n';
        buf[nbuf+1] = '\0';
        log_file_write(buf, nbuf + 1);
    }
 
    constexpr int CHUNK = 120;
    if (len <= CHUNK) {
        msg[len]   = '\n';
        msg[len+1] = '\0';
        push_one_line(msg, len + 1);
        return;
    }
 
    char line[CHUNK + 8];
    int off = 0;
    bool first = true;
    while (off < len) {
        int take = (len - off > CHUNK) ? CHUNK : (len - off);
        int n = 0;
        if (!first) { line[n++] = ' '; line[n++] = '.'; line[n++] = '.'; line[n++] = '.'; line[n++] = ' '; }
        memcpy(line + n, msg + off, take);
        n += take;
        line[n++] = '\n';
        line[n]   = '\0';
        push_one_line(line, n);
        off += take;
        first = false;
    }
}
 
static size_t seh_read_ascii(const char* p, char* dst, size_t max_len)
{
    size_t n = 0;
    __try {
        for (; n < max_len; ++n) {
            unsigned char c = static_cast<unsigned char>(p[n]);
            if (!c) break;
            if (c < 32 || c >= 127) break;
            dst[n] = static_cast<char>(c);
        }
    } __except (EXCEPTION_EXECUTE_HANDLER) {
        return 0;
    }
    return n;
}
 
static size_t seh_read_wide_as_ascii(const wchar_t* p, char* dst, size_t max_chars)
{
    size_t n = 0;
    __try {
        for (; n < max_chars; ++n) {
            wchar_t c = p[n];
            if (!c) break;
            if (c < 32 || c >= 127) break;
            dst[n] = static_cast<char>(c);
        }
    } __except (EXCEPTION_EXECUTE_HANDLER) {
        return 0;
    }
    return n;
}
 
static std::string read_ascii_ptr(uintptr_t addr, size_t max_len)
{
    if (!addr) return {};
    if (max_len > 4096) max_len = 4096;
    char buf[4096];
    size_t n = seh_read_ascii(reinterpret_cast<const char*>(addr), buf, max_len);
    return std::string(buf, n);
}
 
static std::string read_wide_ascii_ptr(uintptr_t addr, size_t max_chars)
{
    if (!addr) return {};
    if (max_chars > 1024) max_chars = 1024;
    char buf[1024];
    size_t n = seh_read_wide_as_ascii(reinterpret_cast<const wchar_t*>(addr), buf, max_chars);
    return std::string(buf, n);
}
 
static bool is_valid_ptr(uintptr_t p)
{
    return p > 0x10000 && p < 0x00007FFFFFFFFFFFULL;
}
 
static bool try_read(uintptr_t va, void* dst, size_t n)
{
    if (!is_valid_ptr(va)) return false;
    __try {
        std::memcpy(dst, reinterpret_cast<const void*>(va), n);
        return true;
    } __except (EXCEPTION_EXECUTE_HANDLER) {
        return false;
    }
}
 
namespace offsets {
 
struct Resolved {
    uintptr_t crypto_ctx_ptr = 0;
    uintptr_t auth_token_ptr = 0;
    uintptr_t gmistr_buf     = 0;
    uintptr_t game_room_scan = 0;
    bool crypto_ok   = false;
    bool auth_ok     = false;
    bool gmi_ok      = false;
    bool scan_ok     = false;
    bool crypto_from_cache  = false;
    bool auth_from_cache    = false;
    bool gmi_from_cache     = false;
    bool scan_from_cache    = false;
};
 
static Resolved g_resolved;
 
static bool verify_crypto_ctx(uintptr_t va) {
    uintptr_t ctx = 0;
    if (!try_read(va, &ctx, sizeof(ctx))) return false;
    if (!is_valid_ptr(ctx)) return false;
    uintptr_t vt = 0;
    if (!try_read(ctx + hd2api::vtoff::SIGN_SVC, &vt, sizeof(vt))) return false;
    return is_valid_ptr(vt);
}
 
static bool verify_auth_token(uintptr_t va) {
    uintptr_t p = 0;
    if (!try_read(va, &p, sizeof(p))) return false;
    if (!is_valid_ptr(p)) return false;
    char buf[40]{};
    if (!try_read(p + 4, buf, sizeof(buf) - 1)) return false;
    for (int i = 0; i < 36; ++i) {
        char c = buf[i];
        bool hex = (c>='0'&&c<='9')||(c>='a'&&c<='f')||(c>='A'&&c<='F');
        if (i==8||i==13||i==18||i==23) { if (c != '-') return false; }
        else                            { if (!hex)     return false; }
    }
    return true;
}
 
static bool verify_gmistr(uintptr_t va) {
    char buf[33]{};
    if (!try_read(va, buf, 32)) return false;
    int hex = 0;
    for (int i = 0; i < 32; ++i) {
        char c = buf[i];
        if (!((c>='0'&&c<='9')||(c>='A'&&c<='F')||(c>='a'&&c<='f'))) break;
        ++hex;
    }
    return hex >= 16;
}
 
static bool verify_scan_insn(uintptr_t va) {
    uint8_t p[16]{};
    if (!try_read(va, p, sizeof(p))) return false;
    if (p[0] != 0x48) return false;
    if (p[1] != 0x8B && p[1] != 0x8D) return false;
    uint8_t modrm = p[2];
    if ((modrm & 0xC7) != 0x05) return false;
    int32_t disp = 0;
    std::memcpy(&disp, p + 3, 4);
    uintptr_t target = va + 7 + disp;
    (void)target;
    return true;
}
 
static constexpr uint32_t CACHE_MAGIC   = 0x4F324448;
static constexpr uint32_t CACHE_VERSION = 1;
 
struct CacheRecord {
    char     key[16];
    char     module[16];
    uint32_t rva;
    uint16_t pre_len;
    uint16_t post_len;
    uint8_t  pre[32];
    uint8_t  post[32];
};
static_assert(sizeof(CacheRecord) == 16 + 16 + 4 + 2 + 2 + 32 + 32, "cache record layout");
 
static std::wstring cache_dir()
{
    wchar_t buf[MAX_PATH]{};
    if (SHGetFolderPathW(nullptr, CSIDL_LOCAL_APPDATA, nullptr, 0, buf) != S_OK)
        return L"";
    std::wstring d = buf;
    d += L"\\HD2BlueCoin";
    CreateDirectoryW(d.c_str(), nullptr);
    return d;
}
 
static std::wstring cache_path()
{
    auto d = cache_dir();
    if (d.empty()) return L"";
    return d + L"\\offsets.cache";
}
 
static bool load_cache(std::vector<CacheRecord>& out)
{
    out.clear();
    auto path = cache_path();
    if (path.empty()) return false;
    HANDLE h = CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr,
                           OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
    if (h == INVALID_HANDLE_VALUE) return false;
    uint32_t magic = 0, ver = 0, count = 0;
    DWORD rd = 0;
    if (!ReadFile(h, &magic, 4, &rd, nullptr) || rd != 4 || magic != CACHE_MAGIC) { CloseHandle(h); return false; }
    if (!ReadFile(h, &ver,   4, &rd, nullptr) || rd != 4 || ver != CACHE_VERSION)   { CloseHandle(h); return false; }
    if (!ReadFile(h, &count, 4, &rd, nullptr) || rd != 4 || count > 64)             { CloseHandle(h); return false; }
    out.resize(count);
    for (uint32_t i = 0; i < count; ++i) {
        if (!ReadFile(h, &out[i], sizeof(CacheRecord), &rd, nullptr) || rd != sizeof(CacheRecord)) {
            CloseHandle(h); out.clear(); return false;
        }
    }
    CloseHandle(h);
    return true;
}
 
static bool save_cache(const std::vector<CacheRecord>& recs)
{
    auto path = cache_path();
    if (path.empty()) return false;
    HANDLE h = CreateFileW(path.c_str(), GENERIC_WRITE, 0, nullptr,
                           CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
    if (h == INVALID_HANDLE_VALUE) return false;
    uint32_t magic = CACHE_MAGIC, ver = CACHE_VERSION, count = (uint32_t)recs.size();
    DWORD wr = 0;
    WriteFile(h, &magic, 4, &wr, nullptr);
    WriteFile(h, &ver,   4, &wr, nullptr);
    WriteFile(h, &count, 4, &wr, nullptr);
    for (const auto& r : recs)
        WriteFile(h, &r, sizeof(CacheRecord), &wr, nullptr);
    CloseHandle(h);
    return true;
}
 
static void fill_key(CacheRecord& r, const char* key, const char* module) {
    std::memset(r.key, 0, sizeof(r.key));
    std::memset(r.module, 0, sizeof(r.module));
    strncpy_s(r.key, key, sizeof(r.key) - 1);
    strncpy_s(r.module, module, sizeof(r.module) - 1);
}
 
static bool snapshot(CacheRecord& r, const char* key, const char* module, uint32_t rva, uintptr_t abs_va)
{
    fill_key(r, key, module);
    r.rva = rva;
    r.pre_len = 32;
    r.post_len = 32;
    std::memset(r.pre, 0, 32);
    std::memset(r.post, 0, 32);
    if (!try_read(abs_va - 32, r.pre, 32))  return false;
    if (!try_read(abs_va,      r.post, 32)) return false;
    return true;
}
 
static uintptr_t scan_by_signature(HMODULE mod, const uint8_t pre[32], const uint8_t post[32])
{
    if (!mod) return 0;
    MODULEINFO mi{};
    if (!GetModuleInformation(GetCurrentProcess(), mod, &mi, sizeof(mi))) return 0;
    const uint8_t* base = static_cast<const uint8_t*>(mi.lpBaseOfDll);
    const size_t size = mi.SizeOfImage;
    if (size < 64) return 0;
 
    uintptr_t hit = 0;
    size_t hits  = 0;
    __try {
        for (size_t i = 32; i + 32 <= size; ++i) {
 
            if (base[i - 32] != pre[0]) continue;
            if (std::memcmp(base + i - 32, pre, 32) != 0) continue;
            if (std::memcmp(base + i,      post, 32) != 0) continue;
            ++hits;
            hit = reinterpret_cast<uintptr_t>(base + i);
            if (hits > 1) return 0;
        }
    } __except (EXCEPTION_EXECUTE_HANDLER) {
        return 0;
    }
    return hits == 1 ? hit : 0;
}
 
static HMODULE module_by_name(const char* name)
{
    return GetModuleHandleA(name);
}
 
struct Target {
    const char* key;
    const char* module_name;
    uintptr_t   base;
    uintptr_t   hardcoded_rva;
    bool (*verify)(uintptr_t va);
    bool* ok_flag;
    bool* from_cache_flag;
    uintptr_t* out_abs_va;
};
 
static Resolved resolve(uintptr_t hd2_base, uintptr_t game_base)
{
    Resolved r{};
 
    Target targets[] = {
        { "crypto_ctx_ptr", "game.dll",        game_base, OFF_CRYPTO_CTX_PTR, &verify_crypto_ctx, &r.crypto_ok, &r.crypto_from_cache, &r.crypto_ctx_ptr },
        { "auth_token_ptr", "game.dll",        game_base, OFF_AUTH_TOKEN_PTR, &verify_auth_token, &r.auth_ok,   &r.auth_from_cache,   &r.auth_token_ptr },
        { "gmistr_buf",     "helldivers2.exe", hd2_base,  OFF_GMISTR_BUF,     &verify_gmistr,     &r.gmi_ok,    &r.gmi_from_cache,    &r.gmistr_buf     },
        { "game_room_scan", "game.dll",        game_base, OFF_GAME_ROOM_SCAN, &verify_scan_insn,  &r.scan_ok,   &r.scan_from_cache,   &r.game_room_scan },
    };
 
    bool all_pass = true;
    for (auto& t : targets) {
        uintptr_t abs_va = t.base + t.hardcoded_rva;
        *t.out_abs_va = abs_va;
        *t.ok_flag = (t.base != 0) && t.verify(abs_va);
        if (!*t.ok_flag) all_pass = false;
        logf("offset[%s] hardcoded abs=0x%p verify=%d", t.key, (void*)abs_va, *t.ok_flag ? 1 : 0);
    }
 
    if (all_pass) {
 
        std::vector<CacheRecord> recs;
        recs.reserve(4);
        for (auto& t : targets) {
            CacheRecord rec{};
            if (snapshot(rec, t.key, t.module_name, (uint32_t)t.hardcoded_rva, *t.out_abs_va))
                recs.push_back(rec);
        }
        if (save_cache(recs))
            logf("offsets: all hardcoded offsets verified, cache refreshed (%zu records)", recs.size());
        else
            logf("offsets: verified but failed to save cache");
        return r;
    }
 
    std::vector<CacheRecord> recs;
    bool have_cache = load_cache(recs);
    if (!have_cache) {
        logf("offsets: sanity failed and no cache available — falling back to hardcoded (WARN: may crash)");
        return r;
    }
 
    for (auto& t : targets) {
        if (*t.ok_flag) continue;
        const CacheRecord* found = nullptr;
        for (const auto& rec : recs) {
            if (std::strncmp(rec.key, t.key, sizeof(rec.key)) == 0) { found = &rec; break; }
        }
        if (!found) { logf("offsets[%s]: no cache record", t.key); continue; }
        HMODULE mod = module_by_name(found->module);
        if (!mod) { logf("offsets[%s]: module '%s' not loaded", t.key, found->module); continue; }
 
        uintptr_t hit = scan_by_signature(mod, found->pre, found->post);
        if (!hit) { logf("offsets[%s]: AOB scan miss/ambiguous — falling back to hardcoded", t.key); continue; }
 
        *t.out_abs_va = hit;
        *t.from_cache_flag = true;
        *t.ok_flag = t.verify(hit);
        logf("offsets[%s]: AOB hit abs=0x%p (cache), verify=%d", t.key, (void*)hit, *t.ok_flag ? 1 : 0);
 
        if (*t.ok_flag) {
            CacheRecord upd{};
            MODULEINFO mi{};
            GetModuleInformation(GetCurrentProcess(), mod, &mi, sizeof(mi));
            uint32_t new_rva = (uint32_t)(hit - reinterpret_cast<uintptr_t>(mi.lpBaseOfDll));
            if (snapshot(upd, t.key, t.module_name, new_rva, hit)) {
                for (auto& rec : recs) {
                    if (std::strncmp(rec.key, t.key, sizeof(rec.key)) == 0) { rec = upd; break; }
                }
                save_cache(recs);
            }
        }
    }
 
    return r;
}
 
}
 
static void resolve_game_offsets()
{
    if (!g_game_base) return;
    uintptr_t scan = offsets::g_resolved.game_room_scan;
    if (!is_valid_ptr(scan)) return;
 
    int32_t rel = 0;
    if (!try_read(scan + 3, &rel, sizeof(rel))) return;
    g_room_ctx = scan + rel;
 
    uintptr_t acct = 0;
    if (try_read(g_room_ctx + 0x77, &acct, sizeof(acct)) && is_valid_ptr(acct))
        g_cached_game_room_account = acct + 0x5F744;
 
    uintptr_t arr_now = 0;
    try_read(g_room_ctx + 0xD7, &arr_now, sizeof(arr_now));
    logf("resolve_offsets: room_ctx=0x%p participants_arr(now)=0x%p game_room_acct=0x%p",
         reinterpret_cast<void*>(g_room_ctx),
         reinterpret_cast<void*>(arr_now),
         reinterpret_cast<void*>(g_cached_game_room_account));
}
 
static std::string get_gmi_str()
{
    return read_ascii_ptr(offsets::g_resolved.gmistr_buf, 300);
}
 
static std::string get_auth_token()
{
    uintptr_t ptr = 0;
    if (!try_read(offsets::g_resolved.auth_token_ptr, &ptr, sizeof(ptr))) return {};
    if (!is_valid_ptr(ptr)) return {};
    return read_ascii_ptr(ptr + 4, 128);
}
 
static uintptr_t get_crypto_ctx()
{
    uintptr_t v = 0;
    if (!try_read(offsets::g_resolved.crypto_ctx_ptr, &v, sizeof(v))) return 0;
    return v;
}
 
static std::vector<std::string> get_participants()
{
    std::vector<std::string> result;
    if (!g_room_ctx) return result;
 
    uintptr_t arr_ptr = 0;
    if (!try_read(g_room_ctx + 0xD7, &arr_ptr, sizeof(arr_ptr))) return result;
    if (!is_valid_ptr(arr_ptr)) return result;
 
    for (int i = 0; i < 4; ++i) {
        uint64_t val = 0;
        if (!try_read(arr_ptr + i * 0x20, &val, sizeof(val))) break;
        if (!val) break;
 
        if (is_valid_ptr(val)) {
            auto s = read_wide_ascii_ptr(val, 64);
            if (!s.empty()) { result.push_back(std::move(s)); continue; }
        }
        result.push_back(std::to_string(val));
    }
    return result;
}
 
static std::string get_account_id()
{
    if (g_cached_game_room_account && is_valid_ptr(g_cached_game_room_account)) {
        auto s = read_ascii_ptr(g_cached_game_room_account, 128);
        if (!s.empty()) return s;
    }
    auto parts = get_participants();
    if (!parts.empty()) return parts[0];
    return {};
}
 
static bool seh_call_crypto_init(uintptr_t ctx)
{
    __try {
        return g_crypto.init(ctx);
    } __except (EXCEPTION_EXECUTE_HANDLER) {
        return false;
    }
}
 
static bool ensure_crypto_init()
{
    if (g_crypto.ctx()) return true;
    uintptr_t ctx = get_crypto_ctx();
    if (!seh_call_crypto_init(ctx)) {
        logf("ensure_crypto_init: failed ctx=0x%p", reinterpret_cast<void*>(ctx));
        return false;
    }
    logf("ensure_crypto_init: ok ctx=0x%p", reinterpret_cast<void*>(ctx));
    return true;
}
 
static std::atomic<bool> g_crypto_healthy{false};
 
static void* seh_create_signing_ctx()
{
    __try {
        return g_crypto.create_signing_ctx();
    } __except (EXCEPTION_EXECUTE_HANDLER) {
        return nullptr;
    }
}
 
static void fill_key_id_inner(char* out, size_t cap)
{
    auto s = g_crypto.get_key_id();
    strncpy_s(out, cap, s.c_str(), _TRUNCATE);
}
 
static bool seh_fill_key_id(char* out, size_t cap)
{
    if (!out || cap == 0) return false;
    out[0] = 0;
    __try {
        fill_key_id_inner(out, cap);
        return out[0] != 0;
    } __except (EXCEPTION_EXECUTE_HANDLER) {
        out[0] = 0;
        return false;
    }
}
 
static void fill_key_name_inner(char* out, size_t cap)
{
    auto s = g_crypto.get_key_name();
    strncpy_s(out, cap, s.c_str(), _TRUNCATE);
}
 
static bool seh_fill_key_name(char* out, size_t cap)
{
    if (!out || cap == 0) return false;
    out[0] = 0;
    __try {
        fill_key_name_inner(out, cap);
        return out[0] != 0;
    } __except (EXCEPTION_EXECUTE_HANDLER) {
        out[0] = 0;
        return false;
    }
}
 
static void crypto_smoke_test()
{
    g_crypto_healthy.store(false);
    if (!ensure_crypto_init()) return;
    void* sctx = seh_create_signing_ctx();
    if (!sctx) {
        logf("WARN: create_signing_ctx null/faulted - vtoff::* may be stale");
        return;
    }
    g_crypto_healthy.store(true);
    logf("crypto smoke test: ok (signing_ctx=%p)", sctx);
}
 
static void resolve_curl()
{
    if (g_curl.ready()) return;
    HMODULE hCurl = GetModuleHandleA("libcurl.dll");
    if (!hCurl) hCurl = GetModuleHandleA("libcurl-x64.dll");
    if (!hCurl) return;
 
    auto G = [&](const char* n) { return GetProcAddress(hCurl, n); };
    g_curl.easy_init       = reinterpret_cast<CurlEasyInit>      (G("curl_easy_init"));
    g_curl.easy_setopt     = reinterpret_cast<CurlEasySetopt>    (G("curl_easy_setopt"));
    g_curl.easy_cleanup    = reinterpret_cast<CurlEasyCleanup>   (G("curl_easy_cleanup"));
    g_curl.easy_getinfo    = reinterpret_cast<CurlEasyGetinfo>   (G("curl_easy_getinfo"));
    g_curl.easy_perform    = reinterpret_cast<CurlEasyPerform>   (G("curl_easy_perform"));
    g_curl.slist_append    = reinterpret_cast<CurlSlistAppend>   (G("curl_slist_append"));
    g_curl.slist_free_all  = reinterpret_cast<CurlSlistFreeAll>  (G("curl_slist_free_all"));
    g_curl.multi_init      = reinterpret_cast<CurlMultiInit>     (G("curl_multi_init"));
    g_curl.multi_add       = reinterpret_cast<CurlMultiAddHandle>(G("curl_multi_add_handle"));
    g_curl.multi_perform   = reinterpret_cast<CurlMultiPerform>  (G("curl_multi_perform"));
    g_curl.multi_info_read = reinterpret_cast<CurlMultiInfoRead> (G("curl_multi_info_read"));
    g_curl.multi_remove    = reinterpret_cast<CurlMultiRemove>   (G("curl_multi_remove_handle"));
    g_curl.multi_cleanup   = reinterpret_cast<CurlMultiCleanup>  (G("curl_multi_cleanup"));
    g_curl.easy_strerror   = reinterpret_cast<CurlEasyStrerror>  (G("curl_easy_strerror"));
}
 
static size_t __cdecl curl_write_cb(char* ptr, size_t size, size_t nmemb, void* userdata)
{
    auto* rb = static_cast<ResponseBuffer*>(userdata);
    size_t n = size * nmemb;
    if (!rb || !rb->data || rb->used + n >= rb->cap) return 0;
    std::memcpy(rb->data + rb->used, ptr, n);
    rb->used += n;
    rb->data[rb->used] = 0;
    return n;
}
 
static void send_cleanup(SendContext& s)
{
    if (s.curl_multi && s.curl_easy)
        g_curl.multi_remove(s.curl_multi, s.curl_easy);
    if (s.curl_easy)
        g_curl.easy_cleanup(s.curl_easy);
    if (s.curl_multi)
        g_curl.multi_cleanup(s.curl_multi);
    if (s.slist)
        g_curl.slist_free_all(s.slist);
    if (s.resp_mem)
        HeapFree(GetProcessHeap(), 0, s.resp_mem);
    s.curl_easy  = nullptr;
    s.curl_multi = nullptr;
    s.slist      = nullptr;
    s.resp_mem   = nullptr;
    s.resp       = {};
    s.body_keep.clear();
}
 
static std::string extract_json_string(const std::string& str, const char* key)
{
    auto pos = str.find(key);
    if (pos == std::string::npos) return {};
    pos += std::strlen(key);
    while (pos < str.size() && (str[pos] == ' ' || str[pos] == ':')) ++pos;
    if (pos >= str.size()) return {};
    if (str[pos] == '"') {
        ++pos;
        auto end = str.find('"', pos);
        return end != std::string::npos ? str.substr(pos, end - pos) : std::string();
    }
    auto end = str.find_first_of(",}]", pos);
    return end != std::string::npos ? str.substr(pos, end - pos) : str.substr(pos);
}
 
static bool try_claim_shot_result(SendContext& s)
{
    bool expected = false;
    return s.result_recorded.compare_exchange_strong(expected, true);
}
 
static void push_shot_result(uint32_t job_id, const ShotResult& sr)
{
    if (!job_id) return;
    std::shared_ptr<Job> j;
    {
        std::lock_guard<std::mutex> lock(g_jobs_mutex);
        auto it = g_jobs.find(job_id);
        if (it != g_jobs.end()) j = it->second;
    }
    if (!j) return;
    j->shots.push_back(sr);
    if (sr.ok) ++j->ok_count;
    else        ++j->fail_count;
}
 
static void send_process_response(SendContext& s, int curl_code)
{
    long code = 0;
    g_curl.easy_getinfo(s.curl_easy, 0x200002  , &code);
    s.result_code = static_cast<DWORD>(code);
    g_last_http_code.store(s.result_code);
 
    uint32_t n = ++g_send_count;
    std::string resp_str = s.resp.data ? std::string(s.resp.data) : std::string();
 
    ShotResult sr;
    sr.i = s.shot_index;
    sr.http_code = (uint32_t)code;
    sr.curl_code = curl_code;
 
    const char* curl_err = (curl_code != 0 && g_curl.easy_strerror)
                               ? g_curl.easy_strerror(curl_code)
                               : nullptr;
 
    if (curl_code == 0 && code == 200) {
        ++g_send_ok;
        sr.ok = true;
        sr.account_id = extract_json_string(resp_str, "\"platformAccountId\":");
        sr.amount     = extract_json_string(resp_str, "\"amount\":");
        if (!sr.account_id.empty() && !sr.amount.empty()) {
            logf("[shot %u job=%u #%u] OK 200 account=%s amount=%s",
                 s.shot_index, s.job_id, n, sr.account_id.c_str(), sr.amount.c_str());
        } else {
            logf("[shot %u job=%u #%u] HTTP 200 | %s",
                 s.shot_index, s.job_id, n, resp_str.c_str());
        }
    } else if (curl_code != 0) {
 
        ++g_send_fail;
        sr.ok = false;
        char buf[256];
        snprintf(buf, sizeof(buf), "curl error %d (%s)", curl_code,
                 curl_err ? curl_err : "unknown");
        sr.message = buf;
        logf("[shot %u job=%u #%u] CURL %d (%s) | http=%ld body=%s",
             s.shot_index, s.job_id, n, curl_code,
             curl_err ? curl_err : "?", code, resp_str.c_str());
    } else {
 
        ++g_send_fail;
        sr.ok = false;
        sr.message = resp_str;
        logf("[shot %u job=%u #%u] HTTP %ld FAIL | %s",
             s.shot_index, s.job_id, n, code, resp_str.c_str());
    }
 
    if (try_claim_shot_result(s)) {
        push_shot_result(s.job_id, sr);
    } else {
        logf("[shot %u job=%u #%u] late result dropped (timeout already recorded)",
             s.shot_index, s.job_id, n);
    }
}
 
static void send_tick(SendContext& s)
{
    if (g_unloading.load()) return;
 
    switch (s.state) {
 
    case SendState::Idle:
        break;
 
    case SendState::Building: {
        g_last_error.clear();
        if (!ensure_crypto_init())             { g_last_error = "crypto init failed"; goto fail; }
        {
            auto gmi  = get_gmi_str();
            auto auth = get_auth_token();
            if (gmi.empty() || auth.empty())   { g_last_error = "gmi/auth empty"; goto fail; }
 
            char kid_buf[96] = {};
            if (!seh_fill_key_id(kid_buf, sizeof(kid_buf)) || !kid_buf[0]) {
                g_last_error = "key_id unavailable (crypto not ready?)"; goto fail;
            }
            std::string key_id(kid_buf);
 
            void* sctx = seh_create_signing_ctx();
            if (!sctx)                         { g_last_error = "create_signing_ctx null/faulted"; goto fail; }
 
            std::vector<std::string> parts = s.override_participants.empty()
                ? get_participants()
                : s.override_participants;
 
            {
                auto self_id = get_account_id();
                if (!self_id.empty()) {
                    bool has = false;
                    for (auto& p : parts) if (p == self_id) { has = true; break; }
                    if (!has) parts.insert(parts.begin(), self_id);
                }
            }
 
            if (parts.empty())                 { g_last_error = "participants empty"; goto fail; }
 
            {
                std::string pj;
                for (size_t ii = 0; ii < parts.size(); ++ii) {
                    if (ii) pj += ",";
                    pj += parts[ii];
                }
                logf("[send_build] participants(%zu)=[%s]", parts.size(), pj.c_str());
            }
 
            hd2api::ApiRequest req = hd2api::build_mission_activity_request(
                g_crypto, sctx, gmi, auth, parts);
 
            resolve_curl();
            if (!g_curl.ready())               { g_last_error = "curl not ready"; goto fail; }
 
            s.curl_easy  = g_curl.easy_init();
            s.curl_multi = g_curl.multi_init();
            if (!s.curl_easy || !s.curl_multi) { g_last_error = "curl init failed"; goto fail; }
 
            s.resp_mem = static_cast<char*>(HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 65536));
            s.resp = {s.resp_mem, 0, 65535};
 
            s.slist = nullptr;
            auto ah = [&](const std::string& h) { s.slist = g_curl.slist_append(s.slist, h.c_str()); };
            ah("Accept: */*");
            ah("Authorization: " + req.authorization);
            ah("Key_ID: " + req.key_id);
            ah("X-Signature: " + req.x_signature);
            ah("ct: " + std::to_string(req.ct));
            ah("Content-Type: application/json");
            ah("User-Agent: " + req.user_agent);
 
            s.body_keep = req.body;
 
            constexpr int CURLOPT_URL           = 10002;
            constexpr int CURLOPT_POSTFIELDS    = 10015;
            constexpr int CURLOPT_POSTFIELDSIZE = 60;
            constexpr int CURLOPT_HTTPHEADER    = 10023;
            constexpr int CURLOPT_WRITEFUNCTION = 20011;
            constexpr int CURLOPT_WRITEDATA     = 10001;
            constexpr int CURLOPT_NOSIGNAL      = 99;
 
            g_curl.easy_setopt(s.curl_easy, CURLOPT_URL,           req.url.c_str());
            g_curl.easy_setopt(s.curl_easy, CURLOPT_POSTFIELDS,    s.body_keep.c_str());
            g_curl.easy_setopt(s.curl_easy, CURLOPT_POSTFIELDSIZE, static_cast<long>(s.body_keep.size()));
            g_curl.easy_setopt(s.curl_easy, CURLOPT_HTTPHEADER,    s.slist);
            g_curl.easy_setopt(s.curl_easy, CURLOPT_WRITEFUNCTION, &curl_write_cb);
            g_curl.easy_setopt(s.curl_easy, CURLOPT_WRITEDATA,     &s.resp);
            g_curl.easy_setopt(s.curl_easy, CURLOPT_NOSIGNAL,      1L);
 
            g_curl.multi_add(s.curl_multi, s.curl_easy);
        }
        s.state = SendState::Sending;
        break;
 
    fail:
        logf("send_build: %s", g_last_error.c_str());
        if (try_claim_shot_result(s)) {
            ShotResult sr;
            sr.i = s.shot_index;
            sr.http_code = 0;
            sr.ok = false;
            sr.message = g_last_error;
            push_shot_result(s.job_id, sr);
        }
        send_cleanup(s);
        s.result_code = 0xFFFFFFFFu;
        g_last_http_code.store(s.result_code);
        s.state = SendState::Done;
        if (g_task_done_event) SetEvent(g_task_done_event);
        break;
    }
 
    case SendState::Sending: {
        int running = 0;
        g_curl.multi_perform(s.curl_multi, &running);
        if (running > 0) break;
 
        int curl_code = 0;
        int msgs = 0;
        while (auto* m = reinterpret_cast<CurlMsg*>(
                   g_curl.multi_info_read(s.curl_multi, &msgs))) {
            if (m->msg == 1   && m->easy_handle == s.curl_easy) {
                curl_code = m->data.result;
            }
        }
 
        send_process_response(s, curl_code);
        send_cleanup(s);
        s.state = SendState::Done;
        if (g_task_done_event) SetEvent(g_task_done_event);
        break;
    }
 
    case SendState::Done:
        s.override_participants.clear();
        s.job_id = 0;
        s.shot_index = 0;
        s.state = SendState::Idle;
        break;
    }
}
 
static void record_seh_failure(SendContext* s);
static void seh_send_tick(SendContext* s)
{
    __try {
        send_tick(*s);
    } __except (EXCEPTION_EXECUTE_HANDLER) {
 
        record_seh_failure(s);
    }
}
 
static void record_seh_failure(SendContext* s)
{
 
    logf("[send_tick] access violation caught - offsets/crypto likely stale");
    if (try_claim_shot_result(*s)) {
        ShotResult sr;
        sr.i         = s->shot_index;
        sr.http_code = 0;
        sr.ok        = false;
        sr.message   = "access violation in send_tick (stale offsets?)";
        push_shot_result(s->job_id, sr);
    }
    s->override_participants.clear();
    s->job_id      = 0;
    s->shot_index  = 0;
    s->state       = SendState::Idle;
    s->result_code = 0xFFFFFFFFu;
    g_last_http_code.store(s->result_code);
    if (g_task_done_event) SetEvent(g_task_done_event);
}
 
static void process_present()
{
    if (g_unloading.load()) return;
    seh_send_tick(&g_send);
}
 
static HRESULT STDMETHODCALLTYPE hook_present(IDXGISwapChain* swap, UINT sync, UINT flags)
{
    process_present();
    return g_real_present ? g_real_present(swap, sync, flags) : S_OK;
}
 
static HRESULT STDMETHODCALLTYPE hook_present1(IDXGISwapChain1* swap, UINT sync, UINT flags, const DXGI_PRESENT_PARAMETERS* params)
{
    process_present();
    return g_real_present1 ? g_real_present1(swap, sync, flags, params) : S_OK;
}
 
static HWND find_game_window()
{
    const wchar_t* titles[] = { L"HELLDIVERS\u2122 2", L"HELLDIVERS 2", L"Helldivers 2" };
    for (auto* t : titles) { HWND h = FindWindowW(nullptr, t); if (h) return h; }
    struct Ctx { DWORD pid = GetCurrentProcessId(); HWND hwnd = nullptr; } ctx;
    EnumWindows([](HWND h, LPARAM lp) -> BOOL {
        auto* c = reinterpret_cast<Ctx*>(lp);
        DWORD pid = 0; GetWindowThreadProcessId(h, &pid);
        if (pid != c->pid || !IsWindowVisible(h) || GetWindow(h, GW_OWNER)) return TRUE;
        c->hwnd = h; return FALSE;
    }, reinterpret_cast<LPARAM>(&ctx));
    return ctx.hwnd;
}
 
static HWND ensure_helper_window()
{
    if (g_helper_hwnd && IsWindow(g_helper_hwnd)) return g_helper_hwnd;
    WNDCLASSEXW wc{}; wc.cbSize = sizeof(wc); wc.lpfnWndProc = DefWindowProcW;
    wc.hInstance = GetModuleHandleW(nullptr); wc.lpszClassName = kHelperClassName;
    RegisterClassExW(&wc);
    g_helper_hwnd = CreateWindowExW(0, kHelperClassName, L"HD2BC Helper", WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, 320, 240, nullptr, nullptr, GetModuleHandleW(nullptr), nullptr);
    if (g_helper_hwnd) ShowWindow(g_helper_hwnd, SW_HIDE);
    return g_helper_hwnd;
}
 
static bool resolve_present_targets(void** out_p, void** out_p1)
{
    *out_p = nullptr; *out_p1 = nullptr;
    g_hwnd = find_game_window();
    if (!g_hwnd) { g_hwnd = ensure_helper_window(); if (!g_hwnd) return false; }
 
    DXGI_SWAP_CHAIN_DESC desc{};
    desc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    desc.SampleDesc.Count = 1; desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    desc.BufferCount = 1; desc.OutputWindow = g_hwnd; desc.Windowed = TRUE;
    desc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
 
    IDXGISwapChain* swap = nullptr; ID3D11Device* dev = nullptr;
    ID3D11DeviceContext* ctx = nullptr; D3D_FEATURE_LEVEL fl = D3D_FEATURE_LEVEL_11_0, ofl{};
    HRESULT hr = D3D11CreateDeviceAndSwapChain(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0,
        &fl, 1, D3D11_SDK_VERSION, &desc, &swap, &dev, &ofl, &ctx);
    if (FAILED(hr))
        hr = D3D11CreateDeviceAndSwapChain(nullptr, D3D_DRIVER_TYPE_WARP, nullptr, 0,
            &fl, 1, D3D11_SDK_VERSION, &desc, &swap, &dev, &ofl, &ctx);
    if (FAILED(hr) || !swap) { if (ctx) ctx->Release(); if (dev) dev->Release(); if (swap) swap->Release(); return false; }
 
    void** vtbl = *reinterpret_cast<void***>(swap);
    *out_p = vtbl[8];
    IDXGISwapChain1* swap1 = nullptr;
    if (SUCCEEDED(swap->QueryInterface(__uuidof(IDXGISwapChain1), reinterpret_cast<void**>(&swap1))) && swap1) {
        *out_p1 = (*reinterpret_cast<void***>(swap1))[22];
        swap1->Release();
    }
    ctx->Release(); dev->Release(); swap->Release();
    return *out_p != nullptr;
}
 
static void patch_slots(HMODULE mod, void* target, void* replacement)
{
    if (!mod || !target || !replacement) return;
    MODULEINFO mi{}; if (!GetModuleInformation(GetCurrentProcess(), mod, &mi, sizeof(mi))) return;
    uint8_t* base = static_cast<uint8_t*>(mi.lpBaseOfDll);
    for (size_t off = 0; off + sizeof(void*) <= mi.SizeOfImage; off += sizeof(void*)) {
        void** slot = reinterpret_cast<void**>(base + off);
        if (*slot != target) continue;
        DWORD old = 0;
        if (VirtualProtect(slot, sizeof(void*), PAGE_EXECUTE_READWRITE, &old)) {
            g_patches.push_back({slot, *slot});
            *slot = replacement;
            DWORD tmp = 0; VirtualProtect(slot, sizeof(void*), old, &tmp);
        }
    }
}
 
static bool install_hook()
{
    if (g_hook_installed.load()) return true;
    void *p = nullptr, *p1 = nullptr;
    if (!resolve_present_targets(&p, &p1)) return false;
    g_present_orig = p; g_present1_orig = p1;
    g_real_present  = reinterpret_cast<decltype(g_real_present)>(p);
    g_real_present1 = reinterpret_cast<decltype(g_real_present1)>(p1);
    HMODULE dxgi = GetModuleHandleA("dxgi.dll"); if (!dxgi) return false;
    g_patches.clear();
    patch_slots(dxgi, p, reinterpret_cast<void*>(&hook_present));
    if (p1) patch_slots(dxgi, p1, reinterpret_cast<void*>(&hook_present1));
    resolve_curl();
    g_hook_installed.store(!g_patches.empty());
    logf("install_hook: patched %zu slot(s)", g_patches.size());
    return g_hook_installed.load();
}
 
static void uninstall_hook()
{
    for (auto& e : g_patches) {
        DWORD old = 0;
        if (VirtualProtect(e.slot, sizeof(void*), PAGE_EXECUTE_READWRITE, &old)) {
            *e.slot = e.original;
            DWORD tmp = 0; VirtualProtect(e.slot, sizeof(void*), old, &tmp);
        }
    }
    g_patches.clear();
    g_hook_installed.store(false);
}
 
static std::string json_escape(const std::string& s)
{
    std::string out;
    out.reserve(s.size() + 8);
    for (char c : s) {
        switch (c) {
        case '"':  out += "\\\""; break;
        case '\\': out += "\\\\"; break;
        case '\n': out += "\\n";  break;
        case '\r': out += "\\r";  break;
        case '\t': out += "\\t";  break;
        default:
            if ((unsigned char)c < 0x20) {
                char buf[8]; snprintf(buf, sizeof(buf), "\\u%04x", c & 0xFF);
                out += buf;
            } else {
                out += c;
            }
            break;
        }
    }
    return out;
}
 
static int json_get_int(const std::string& json, const char* key, int def_val)
{
    std::string needle = std::string("\"") + key + "\"";
    auto pos = json.find(needle);
    if (pos == std::string::npos) return def_val;
    pos += needle.size();
    while (pos < json.size() && (json[pos] == ' ' || json[pos] == ':')) ++pos;
    if (pos >= json.size()) return def_val;
    return std::atoi(json.c_str() + pos);
}
 
static bool json_get_bool(const std::string& json, const char* key, bool def_val)
{
    std::string needle = std::string("\"") + key + "\"";
    auto pos = json.find(needle);
    if (pos == std::string::npos) return def_val;
    pos += needle.size();
    while (pos < json.size() && (json[pos] == ' ' || json[pos] == ':')) ++pos;
    if (pos + 4 <= json.size() && json.compare(pos, 4, "true") == 0)  return true;
    if (pos + 5 <= json.size() && json.compare(pos, 5, "false") == 0) return false;
    return def_val;
}
 
static std::string json_get_str(const std::string& json, const char* key)
{
    std::string needle = std::string("\"") + key + "\"";
    auto pos = json.find(needle);
    if (pos == std::string::npos) return {};
    pos += needle.size();
    while (pos < json.size() && (json[pos] == ' ' || json[pos] == ':')) ++pos;
    if (pos >= json.size() || json[pos] != '"') return {};
    ++pos;
    auto end = json.find('"', pos);
    while (end != std::string::npos && end > 0 && json[end - 1] == '\\') {
        end = json.find('"', end + 1);
    }
    if (end == std::string::npos) return {};
    return json.substr(pos, end - pos);
}
 
static std::vector<std::string> json_get_str_array(const std::string& json, const char* key)
{
    std::vector<std::string> out;
    std::string needle = std::string("\"") + key + "\"";
    auto pos = json.find(needle);
    if (pos == std::string::npos) return out;
    pos += needle.size();
    while (pos < json.size() && (json[pos] == ' ' || json[pos] == ':')) ++pos;
    if (pos >= json.size() || json[pos] != '[') return out;
    ++pos;
    while (pos < json.size()) {
        while (pos < json.size() && (json[pos] == ' ' || json[pos] == ',')) ++pos;
        if (pos >= json.size() || json[pos] == ']') break;
        if (json[pos] != '"') break;
        ++pos;
        auto end = json.find('"', pos);
        if (end == std::string::npos) break;
        out.push_back(json.substr(pos, end - pos));
        pos = end + 1;
    }
    return out;
}
 
struct LookupResult {
    bool ok = false;
    long http_code = 0;
    std::string friend_code;
    std::string account_id;
    std::string name;
    std::string raw;
    std::string error;
};
 
static std::wstring widen(const std::string& s)
{
    if (s.empty()) return {};
    int n = MultiByteToWideChar(CP_UTF8, 0, s.data(), (int)s.size(), nullptr, 0);
    std::wstring w(n, L'\0');
    MultiByteToWideChar(CP_UTF8, 0, s.data(), (int)s.size(), w.data(), n);
    return w;
}
 
static LookupResult lookup_sync(const std::string& friend_code)
{
    LookupResult r;
    r.friend_code = friend_code;
 
    auto auth = get_auth_token();
    if (auth.empty()) { r.error = "auth token empty"; return r; }
 
    FILETIME ft; GetSystemTimeAsFileTime(&ft);
    uint64_t hn = ((uint64_t)ft.dwHighDateTime << 32) | ft.dwLowDateTime;
 
    uint64_t us = (hn - 116444736000000000ULL) / 10ULL;
 
    HINTERNET hs = WinHttpOpen(L"Stingray WIN64",
                               WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY,
                               WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
    if (!hs) { r.error = "WinHttpOpen failed"; return r; }
 
    HINTERNET hc = WinHttpConnect(hs, L"api.live.prod.thehelldiversgame.com",
                                   INTERNET_DEFAULT_HTTPS_PORT, 0);
    if (!hc) { WinHttpCloseHandle(hs); r.error = "WinHttpConnect failed"; return r; }
 
    std::wstring path = L"/api/FriendsV2/FriendCode/" + widen(friend_code);
    HINTERNET hr = WinHttpOpenRequest(hc, L"GET", path.c_str(), nullptr,
                                       WINHTTP_NO_REFERER,
                                       WINHTTP_DEFAULT_ACCEPT_TYPES,
                                       WINHTTP_FLAG_SECURE);
    if (!hr) {
        WinHttpCloseHandle(hc); WinHttpCloseHandle(hs);
        r.error = "WinHttpOpenRequest failed"; return r;
    }
 
    DWORD tls = WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2 | 0x00002000;
    WinHttpSetOption(hs, WINHTTP_OPTION_SECURE_PROTOCOLS, &tls, sizeof(tls));
 
    WinHttpSetTimeouts(hr, 15000, 15000, 15000, 15000);
 
    DWORD dec = WINHTTP_DECOMPRESSION_FLAG_ALL;
    WinHttpSetOption(hr, WINHTTP_OPTION_DECOMPRESSION, &dec, sizeof(dec));
 
    std::wstring headers;
    headers += L"Accept: application/json\r\n";
    headers += L"Content-Type: application/json\r\n";
    headers += L"User-Agent: Stingray WIN64\r\n";
    headers += L"Authorization: " + widen(auth) + L"\r\n";
    {
        wchar_t ctbuf[64];
        swprintf(ctbuf, 64, L"ct: %llu\r\n", (unsigned long long)us);
        headers += ctbuf;
    }
 
    long http_code = 0;
    std::string body;
 
    BOOL ok = WinHttpSendRequest(hr, headers.c_str(), (DWORD)-1L,
                                 WINHTTP_NO_REQUEST_DATA, 0, 0, 0);
    if (!ok) {
        DWORD err = GetLastError();
        char buf[64]; snprintf(buf, sizeof(buf), "WinHttpSendRequest err=%lu", err);
        r.error = buf;
        goto cleanup;
    }
    if (!WinHttpReceiveResponse(hr, nullptr)) {
        DWORD err = GetLastError();
        char buf[64]; snprintf(buf, sizeof(buf), "WinHttpReceiveResponse err=%lu", err);
        r.error = buf;
        goto cleanup;
    }
 
    {
        DWORD sc = 0; DWORD cb = sizeof(sc);
        WinHttpQueryHeaders(hr, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
                            WINHTTP_HEADER_NAME_BY_INDEX, &sc, &cb, WINHTTP_NO_HEADER_INDEX);
        http_code = (long)sc;
    }
 
    for (;;) {
        DWORD avail = 0;
        if (!WinHttpQueryDataAvailable(hr, &avail)) break;
        if (avail == 0) break;
        std::string chunk(avail, '\0');
        DWORD rd = 0;
        if (!WinHttpReadData(hr, chunk.data(), avail, &rd)) break;
        chunk.resize(rd);
        body += chunk;
        if (rd == 0) break;
    }
 
    r.http_code = http_code;
    r.raw       = body;
 
    if (http_code == 200) {
        r.ok = true;
        r.account_id = json_get_str(body, "platformAccountId");
        if (r.account_id.empty()) r.account_id = json_get_str(body, "accountId");
        r.name       = json_get_str(body, "name");
    } else if (r.error.empty()) {
        char buf[64]; snprintf(buf, sizeof(buf), "http=%ld", http_code);
        r.error = buf;
    }
 
cleanup:
    WinHttpCloseHandle(hr);
    WinHttpCloseHandle(hc);
    WinHttpCloseHandle(hs);
    return r;
}
 
struct HttpRequest {
    std::string method;
    std::string path;
    std::string query;
    std::string body;
    int content_length = 0;
};
 
static std::string url_decode(const std::string& s)
{
    std::string out;
    out.reserve(s.size());
    for (size_t i = 0; i < s.size(); ++i) {
        if (s[i] == '+') out.push_back(' ');
        else if (s[i] == '%' && i + 2 < s.size()) {
            auto hex = [](char c) -> int {
                if (c >= '0' && c <= '9') return c - '0';
                if (c >= 'a' && c <= 'f') return c - 'a' + 10;
                if (c >= 'A' && c <= 'F') return c - 'A' + 10;
                return -1;
            };
            int hi = hex(s[i + 1]), lo = hex(s[i + 2]);
            if (hi >= 0 && lo >= 0) { out.push_back((char)((hi << 4) | lo)); i += 2; }
            else out.push_back(s[i]);
        }
        else out.push_back(s[i]);
    }
    return out;
}
 
static std::string query_get(const std::string& query, const std::string& key)
{
    size_t i = 0;
    while (i < query.size()) {
        size_t eq = query.find('=', i);
        if (eq == std::string::npos) break;
        size_t amp = query.find('&', eq + 1);
        if (amp == std::string::npos) amp = query.size();
        if (query.compare(i, eq - i, key) == 0)
            return url_decode(query.substr(eq + 1, amp - eq - 1));
        i = amp + 1;
    }
    return {};
}
 
static bool parse_http_request(const std::string& raw, HttpRequest& req)
{
    auto line_end = raw.find("\r\n");
    if (line_end == std::string::npos) return false;
    std::string req_line = raw.substr(0, line_end);
 
    auto sp1 = req_line.find(' ');
    if (sp1 == std::string::npos) return false;
    req.method = req_line.substr(0, sp1);
 
    auto sp2 = req_line.find(' ', sp1 + 1);
    if (sp2 == std::string::npos) return false;
    std::string full_path = req_line.substr(sp1 + 1, sp2 - sp1 - 1);
 
    auto qm = full_path.find('?');
    if (qm == std::string::npos) {
        req.path = full_path;
    } else {
        req.path = full_path.substr(0, qm);
        req.query = full_path.substr(qm + 1);
    }
 
    req.content_length = 0;
    std::string lower_raw = raw;
    for (auto& c : lower_raw) c = static_cast<char>(::tolower(static_cast<unsigned char>(c)));
    auto cl_pos = lower_raw.find("content-length:");
    if (cl_pos != std::string::npos) {
        cl_pos += 15;
        while (cl_pos < lower_raw.size() && lower_raw[cl_pos] == ' ') ++cl_pos;
        req.content_length = std::atoi(lower_raw.c_str() + cl_pos);
    }
 
    auto body_start = raw.find("\r\n\r\n");
    if (body_start != std::string::npos)
        req.body = raw.substr(body_start + 4);
 
    return true;
}
 
static std::string build_http_response(int status_code, const char* status_text, const std::string& body)
{
    char header[512];
    snprintf(header, sizeof(header),
        "HTTP/1.1 %d %s\r\n"
        "Content-Type: application/json; charset=utf-8\r\n"
        "Content-Length: %zu\r\n"
        "Connection: close\r\n"
        "Access-Control-Allow-Origin: *\r\n"
        "\r\n",
        status_code, status_text, body.size());
    return std::string(header) + body;
}
 
static std::string handle_api_status()
{
    auto parts = get_participants();
    std::string parts_json = "[";
    for (size_t i = 0; i < parts.size(); ++i) {
        if (i > 0) parts_json += ",";
        parts_json += "\"" + json_escape(parts[i]) + "\"";
    }
    parts_json += "]";
 
    auto auth = get_auth_token();
    std::string session_prefix;
    if (auth.size() > 20) session_prefix = auth.substr(0, 20) + "...";
    else                   session_prefix = auth;
 
    const auto& r = offsets::g_resolved;
 
    char buf[2048];
    snprintf(buf, sizeof(buf),
        "{"
        "\"ok\":true,"
        "\"hook_installed\":%s,"
        "\"curl_ready\":%s,"
        "\"crypto_healthy\":%s,"
        "\"account_id\":\"%s\","
        "\"participants\":%s,"
        "\"session_prefix\":\"%s\","
        "\"total_sent\":%u,"
        "\"total_ok\":%u,"
        "\"total_fail\":%u,"
        "\"offsets\":{"
            "\"crypto_ok\":%s,\"auth_ok\":%s,\"gmi_ok\":%s,\"scan_ok\":%s,"
            "\"crypto_from_cache\":%s,\"auth_from_cache\":%s,\"gmi_from_cache\":%s,\"scan_from_cache\":%s,"
            "\"crypto_ctx_ptr\":\"0x%llx\",\"auth_token_ptr\":\"0x%llx\","
            "\"gmistr_buf\":\"0x%llx\",\"game_room_scan\":\"0x%llx\""
        "}"
        "}",
        g_hook_installed.load() ? "true" : "false",
        g_curl.ready() ? "true" : "false",
        g_crypto_healthy ? "true" : "false",
        json_escape(get_account_id()).c_str(),
        parts_json.c_str(),
        json_escape(session_prefix).c_str(),
        g_send_count.load(), g_send_ok.load(), g_send_fail.load(),
        r.crypto_ok ? "true":"false", r.auth_ok ? "true":"false",
        r.gmi_ok    ? "true":"false", r.scan_ok ? "true":"false",
        r.crypto_from_cache ? "true":"false", r.auth_from_cache ? "true":"false",
        r.gmi_from_cache    ? "true":"false", r.scan_from_cache ? "true":"false",
        (unsigned long long)r.crypto_ctx_ptr, (unsigned long long)r.auth_token_ptr,
        (unsigned long long)r.gmistr_buf,     (unsigned long long)r.game_room_scan);
 
    return build_http_response(200, "OK", buf);
}
 
static std::string handle_api_lookup(const HttpRequest& req)
{
    std::string code = query_get(req.query, "code");
    if (code.empty()) {
        return build_http_response(400, "Bad Request",
            "{\"ok\":false,\"error\":\"missing code parameter\"}");
    }
 
    logf("lookup: friend_code=%s", code.c_str());
    auto r = lookup_sync(code);
 
    char buf[4096];
    snprintf(buf, sizeof(buf),
        "{"
        "\"ok\":%s,"
        "\"http_code\":%ld,"
        "\"friend_code\":\"%s\","
        "\"account_id\":\"%s\","
        "\"name\":\"%s\","
        "\"error\":\"%s\""
        "}",
        r.ok ? "true" : "false",
        r.http_code,
        json_escape(r.friend_code).c_str(),
        json_escape(r.account_id).c_str(),
        json_escape(r.name).c_str(),
        json_escape(r.error).c_str());
 
    logf("lookup: code=%s http=%ld ok=%d id=%s name=%s",
         code.c_str(), r.http_code, r.ok ? 1 : 0,
         r.account_id.c_str(), r.name.c_str());
 
    return build_http_response(200, "OK", std::string(buf));
}
 
static std::string handle_api_shoot(const HttpRequest& req)
{
 
    std::vector<std::string> raw_targets;
    std::string single = json_get_str(req.body, "target");
    if (!single.empty()) raw_targets.push_back(single);
    auto arr = json_get_str_array(req.body, "targets");
    for (auto& s : arr) if (!s.empty()) raw_targets.push_back(s);
 
    bool is_friend_code = json_get_bool(req.body, "is_friend_code", false);
    int count           = json_get_int(req.body,  "count", 1);
    int interval_ms     = json_get_int(req.body,  "interval_ms", 3000);
    int timeout_ms      = json_get_int(req.body,  "timeout_ms", 30000);
 
    if (raw_targets.empty()) {
        return build_http_response(400, "Bad Request",
            "{\"ok\":false,\"error\":\"missing target/targets\"}");
    }
    if (count < 1) count = 1;
    if (count > 10000) count = 10000;
    if (interval_ms < 100) interval_ms = 100;
    if (timeout_ms  < 1000)   timeout_ms = 1000;
    if (timeout_ms  > 600000) timeout_ms = 600000;
 
    std::vector<std::string> participants;
    std::vector<std::string> target_names;
    participants.reserve(raw_targets.size());
    target_names.reserve(raw_targets.size());
 
    if (is_friend_code) {
        for (auto& fc : raw_targets) {
            auto lr = lookup_sync(fc);
            if (!lr.ok || lr.account_id.empty()) {
                char err[256];
                snprintf(err, sizeof(err),
                    "{\"ok\":false,\"error\":\"lookup failed for %s (http=%ld)\"}",
                    json_escape(fc).c_str(), lr.http_code);
                return build_http_response(400, "Bad Request", err);
            }
            participants.push_back(lr.account_id);
            target_names.push_back(lr.name.empty() ? lr.account_id : lr.name);
            logf("shoot: resolved %s -> %s (%s)", fc.c_str(), lr.account_id.c_str(), lr.name.c_str());
        }
    } else {
        participants = raw_targets;
        target_names = raw_targets;
    }
 
    if (!g_hook_installed.load() && !install_hook()) {
        return build_http_response(500, "Internal Server Error",
            "{\"ok\":false,\"error\":\"hook not installed\"}");
    }
 
    uint32_t job_id = g_next_job_id.fetch_add(1);
    auto job = std::make_shared<Job>();
    {
        std::lock_guard<std::mutex> lock(g_jobs_mutex);
        g_jobs[job_id] = job;
    }
 
    logf("shoot[job=%u]: starting count=%d targets=%zu (friend_code=%d) interval=%dms timeout=%dms",
         job_id, count, participants.size(), is_friend_code ? 1 : 0, interval_ms, timeout_ms);
 
    const int wait_idle_max = timeout_ms / 50;
 
    for (int i = 0; i < count; ++i) {
        if (g_unloading.load()) break;
 
        int wait_idle = 0;
        while (g_send.state != SendState::Idle && wait_idle < wait_idle_max) {
            Sleep(50);
            ++wait_idle;
        }
        if (g_send.state != SendState::Idle) {
            logf("shoot[job=%u shot=%d]: timeout waiting idle (>%dms)", job_id, i + 1, timeout_ms);
            ShotResult sr; sr.i = (uint32_t)(i + 1); sr.ok = false;
            sr.message = "timeout waiting idle";
            job->shots.push_back(sr); ++job->fail_count;
            continue;
        }
 
        g_send.override_participants = participants;
        g_send.job_id     = job_id;
        g_send.shot_index = (uint32_t)(i + 1);
        g_send.result_recorded.store(false);
        ResetEvent(g_task_done_event);
        g_send.state = SendState::Building;
 
        DWORD wr = WaitForSingleObject(g_task_done_event, (DWORD)timeout_ms);
 
        if (wr == WAIT_TIMEOUT) {
            logf("shoot[job=%u shot=%d]: timeout sending (>%dms)", job_id, i + 1, timeout_ms);
            if (try_claim_shot_result(g_send)) {
                ShotResult sr;
                sr.i = (uint32_t)(i + 1);
                sr.ok = false;
                sr.http_code = 0;
                sr.curl_code = 28  ;
                sr.message = "timeout sending";
                push_shot_result(job_id, sr);
            }
 
        }
 
        if (i < count - 1 && !g_unloading.load()) {
            Sleep((DWORD)interval_ms);
        }
    }
 
    std::string shots_json = "[";
    for (size_t k = 0; k < job->shots.size(); ++k) {
        if (k) shots_json += ",";
        const auto& s = job->shots[k];
        char one[1536];
        snprintf(one, sizeof(one),
            "{\"i\":%u,\"ok\":%s,\"http_code\":%u,\"curl_code\":%d,"
            "\"account_id\":\"%s\",\"amount\":\"%s\",\"message\":\"%s\"}",
            s.i,
            s.ok ? "true" : "false",
            s.http_code,
            s.curl_code,
            json_escape(s.account_id).c_str(),
            json_escape(s.amount).c_str(),
            json_escape(s.message).c_str());
        shots_json += one;
    }
    shots_json += "]";
 
    std::string targets_json = "[";
    for (size_t k = 0; k < target_names.size(); ++k) {
        if (k) targets_json += ",";
        targets_json += "\"" + json_escape(target_names[k]) + "\"";
    }
    targets_json += "]";
 
    char head[512];
    snprintf(head, sizeof(head),
        "{\"ok\":true,\"job_id\":%u,\"count\":%d,\"ok_count\":%u,\"fail_count\":%u,"
        "\"targets\":",
        job_id, count, job->ok_count.load(), job->fail_count.load());
    std::string body = head;
    body += targets_json;
    body += ",\"shots\":";
    body += shots_json;
    body += "}";
 
    logf("shoot[job=%u]: done ok=%u fail=%u",
         job_id, job->ok_count.load(), job->fail_count.load());
 
    return build_http_response(200, "OK", body);
}
 
static DWORD WINAPI http_server_thread(void*)
{
    SOCKET srv = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (srv == INVALID_SOCKET) return 1;
 
    int opt = 1;
    setsockopt(srv, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<const char*>(&opt), sizeof(opt));
 
    sockaddr_in addr{};
    addr.sin_family      = AF_INET;
    addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
    addr.sin_port        = htons(HTTP_PORT);
 
    if (bind(srv, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) == SOCKET_ERROR) {
        logf("http_server: bind failed port %d err=%d", HTTP_PORT, WSAGetLastError());
        closesocket(srv); return 1;
    }
    listen(srv, 8);
    logf("http_server: listening on 127.0.0.1:%d", HTTP_PORT);
 
    u_long nb = 1; ioctlsocket(srv, FIONBIO, &nb);
 
    while (!g_unloading.load()) {
        fd_set rfds; FD_ZERO(&rfds); FD_SET(srv, &rfds);
        timeval tv = { 0, 200000 };
        int sel = select(0, &rfds, nullptr, nullptr, &tv);
        if (sel <= 0) continue;
 
        sockaddr_in cli_addr{}; int cli_len = sizeof(cli_addr);
        SOCKET cli = accept(srv, reinterpret_cast<sockaddr*>(&cli_addr), &cli_len);
        if (cli == INVALID_SOCKET) continue;
 
        u_long blocking = 0; ioctlsocket(cli, FIONBIO, &blocking);
        DWORD timeout = 10000;
        setsockopt(cli, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast<const char*>(&timeout), sizeof(timeout));
 
        std::string raw; raw.reserve(4096);
        char buf[4096];
        while (true) {
            int n = recv(cli, buf, sizeof(buf), 0);
            if (n <= 0) break;
            raw.append(buf, n);
            if (raw.find("\r\n\r\n") != std::string::npos) {
                HttpRequest tmp;
                if (parse_http_request(raw, tmp) && tmp.content_length > 0) {
                    size_t header_end = raw.find("\r\n\r\n") + 4;
                    size_t body_received = raw.size() - header_end;
                    while (static_cast<int>(body_received) < tmp.content_length) {
                        n = recv(cli, buf, sizeof(buf), 0);
                        if (n <= 0) break;
                        raw.append(buf, n);
                        body_received += n;
                    }
                }
                break;
            }
        }
 
        if (raw.empty()) { closesocket(cli); continue; }
 
        HttpRequest req;
        if (!parse_http_request(raw, req)) {
            std::string resp = build_http_response(400, "Bad Request", "{\"ok\":false,\"error\":\"bad request\"}");
            send(cli, resp.c_str(), (int)resp.size(), 0);
            closesocket(cli); continue;
        }
 
        std::string response;
 
        if (req.path == "/api/status" && req.method == "GET") {
            response = handle_api_status();
        }
        else if (req.path == "/api/lookup" && req.method == "GET") {
            response = handle_api_lookup(req);
        }
        else if (req.path == "/api/shoot" && req.method == "POST") {
            response = handle_api_shoot(req);
        }
        else {
            std::string body = "{\"ok\":false,\"error\":\"not found\","
                               "\"endpoints\":[\"/api/status\",\"/api/lookup?code=\",\"/api/shoot\"]}";
            response = build_http_response(404, "Not Found", body);
        }
 
        send(cli, response.c_str(), (int)response.size(), 0);
        closesocket(cli);
    }
 
    closesocket(srv);
    return 0;
}
 
static DWORD WINAPI log_push_thread(void*)
{
    SOCKET srv = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (srv == INVALID_SOCKET) return 1;
    int opt = 1; setsockopt(srv, SOL_SOCKET, SO_REUSEADDR, (const char*)&opt, sizeof(opt));
 
    sockaddr_in addr{};
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
    addr.sin_port = htons(LOG_PORT);
 
    if (bind(srv, (sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) { closesocket(srv); return 1; }
    listen(srv, 4);
 
    u_long nb = 1; ioctlsocket(srv, FIONBIO, &nb);
    while (!g_unloading.load()) {
        fd_set rfds; FD_ZERO(&rfds); FD_SET(srv, &rfds);
        timeval tv = { 0, 200000 };
        int sel = select(0, &rfds, nullptr, nullptr, &tv);
        if (sel <= 0) continue;
 
        sockaddr_in cli_addr{}; int cli_len = sizeof(cli_addr);
        SOCKET cli = accept(srv, (sockaddr*)&cli_addr, &cli_len);
        if (cli == INVALID_SOCKET) continue;
 
        u_long cli_nb = 1; ioctlsocket(cli, FIONBIO, &cli_nb);
        {
            std::lock_guard<std::mutex> lock(g_log_clients_mutex);
            g_log_clients.push_back(cli);
        }
        logf("log_push: client connected (total=%zu)", g_log_clients.size());
    }
 
    {
        std::lock_guard<std::mutex> lock(g_log_clients_mutex);
        for (auto s : g_log_clients) closesocket(s);
        g_log_clients.clear();
    }
    closesocket(srv);
    return 0;
}
 
static DWORD WINAPI unload_thread(void*)
{
    if (g_unloading.exchange(true)) return 0;
    logf("unloading...");
    send_cleanup(g_send);
    g_send.state = SendState::Idle;
    if (g_task_done_event) SetEvent(g_task_done_event);
    uninstall_hook();
 
    {
        std::lock_guard<std::mutex> lock(g_log_clients_mutex);
        for (auto s : g_log_clients) closesocket(s);
        g_log_clients.clear();
    }
    if (g_http_thread) { WaitForSingleObject(g_http_thread, 2000); CloseHandle(g_http_thread); g_http_thread = nullptr; }
    if (g_log_thread)  { WaitForSingleObject(g_log_thread, 2000);  CloseHandle(g_log_thread);  g_log_thread = nullptr; }
    if (g_helper_hwnd && IsWindow(g_helper_hwnd)) { DestroyWindow(g_helper_hwnd); g_helper_hwnd = nullptr; }
    Sleep(200);
    WSACleanup();
 
    if (g_module) FreeLibraryAndExitThread(g_module, 0);
    return 0;
}
 
static DWORD WINAPI init_thread(void*)
{
    WSADATA wsa; WSAStartup(MAKEWORD(2, 2), &wsa);
 
    g_hd2_base  = reinterpret_cast<uintptr_t>(GetModuleHandleA("helldivers2.exe"));
    g_game_base = reinterpret_cast<uintptr_t>(GetModuleHandleA("game.dll"));
 
    g_log_thread = CreateThread(nullptr, 0, &log_push_thread, nullptr, 0, nullptr);
    Sleep(100);
 
    logf("init: hd2=0x%p game=0x%p", reinterpret_cast<void*>(g_hd2_base), reinterpret_cast<void*>(g_game_base));
 
    for (int i = 0; i < 60 && !g_game_base; ++i) {
        Sleep(500);
        g_game_base = reinterpret_cast<uintptr_t>(GetModuleHandleA("game.dll"));
    }
    if (!g_game_base) logf("init: WARN game.dll never appeared");
 
    offsets::g_resolved = offsets::resolve(g_hd2_base, g_game_base);
    const auto& r = offsets::g_resolved;
    logf("offsets: crypto=%d auth=%d gmi=%d scan=%d (cache: %d/%d/%d/%d)",
         r.crypto_ok, r.auth_ok, r.gmi_ok, r.scan_ok,
         r.crypto_from_cache, r.auth_from_cache, r.gmi_from_cache, r.scan_from_cache);
 
    resolve_game_offsets();
    resolve_curl();
 
    bool ok = install_hook();
    logf("init: hook=%d curl=%s", ok ? 1 : 0, g_curl.ready() ? "ok" : "FAIL");
 
    g_http_thread = CreateThread(nullptr, 0, &http_server_thread, nullptr, 0, nullptr);
    logf("init: HTTP API on %d, log push on %d", HTTP_PORT, LOG_PORT);
 
    MessageBoxA(nullptr,
        "HD2BlueCoin injected!\n\n"
        "HTTP API: 127.0.0.1:19880\n"
        "Log Push: 127.0.0.1:19881\n\n"
        "Endpoints:\n"
        "  GET  /api/status\n"
        "  GET  /api/lookup?code=<friend_code>\n"
        "  POST /api/shoot\n\n"
        "Start HD2Client.exe to drive it.",
        "HD2BlueCoin", MB_OK | MB_ICONINFORMATION);
    return 0;
}
 
}
 
extern "C" __declspec(dllexport) BOOL __stdcall HD2_Install()
{
    return hd2::g_hook_installed.load() ? TRUE : FALSE;
}
 
extern "C" __declspec(dllexport) void __stdcall HD2_Uninstall()
{
    CreateThread(nullptr, 0, &hd2::unload_thread, nullptr, 0, nullptr);
}
 
extern "C" __declspec(dllexport) DWORD __stdcall HD2_GetLastHttpCode()
{
    return hd2::g_last_http_code.load();
}
 
BOOL WINAPI DllMain(HINSTANCE hinst, DWORD reason, LPVOID)
{
    if (reason == DLL_PROCESS_ATTACH) {
        DisableThreadLibraryCalls(hinst);
        hd2::g_module = hinst;
        hd2::g_task_done_event = CreateEventW(nullptr, TRUE, FALSE, nullptr);
        HANDLE th = CreateThread(nullptr, 0, &hd2::init_thread, nullptr, 0, nullptr);
        if (th) CloseHandle(th);
    } else if (reason == DLL_PROCESS_DETACH) {
        hd2::uninstall_hook();
        if (hd2::g_helper_hwnd && IsWindow(hd2::g_helper_hwnd)) DestroyWindow(hd2::g_helper_hwnd);
        if (hd2::g_task_done_event) CloseHandle(hd2::g_task_done_event);
    }
    return TRUE;
}


Code:
#define NOMINMAX
#define WIN32_LEAN_AND_MEAN
#include <winsock2.h>
#include <ws2tcpip.h>
#include <windows.h>
#include <winhttp.h>
 
#include <algorithm>
#include <atomic>
#include <cstdarg>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <deque>
#include <mutex>
#include <string>
#include <thread>
#include <vector>
 
#pragma comment(lib, "winhttp.lib")
#pragma comment(lib, "ws2_32.lib")
 
static constexpr const wchar_t* DLL_HOST = L"127.0.0.1";
static constexpr INTERNET_PORT  DLL_PORT = 19880;
static constexpr const char*    LOG_HOST = "127.0.0.1";
static constexpr uint16_t       LOG_PORT = 19881;
 
static constexpr int LOG_ROWS_DEFAULT = 14;
 
static std::mutex       g_console_mu;
static HANDLE           g_hout = INVALID_HANDLE_VALUE;
static int              g_cols = 120;
static int              g_rows = 40;
static int              g_log_rows  = LOG_ROWS_DEFAULT;
static int              g_log_top_row    = 0;
static int              g_log_bottom_row = 0;
static int              g_upper_rows     = 0;
static int              g_menu_prompt_row = 0;
 
static std::atomic<bool> g_running{true};
 
static void vt_write(const char* s)
{
    DWORD w = 0;
    WriteFile(g_hout, s, (DWORD)std::strlen(s), &w, nullptr);
}
 
static void vt_writef(const char* fmt, ...)
{
    char buf[2048];
    va_list ap; va_start(ap, fmt);
    int n = vsnprintf(buf, sizeof(buf), fmt, ap);
    va_end(ap);
    if (n < 0) return;
    DWORD w = 0;
    WriteFile(g_hout, buf, (DWORD)n, &w, nullptr);
}
 
static bool init_console()
{
    SetConsoleOutputCP(CP_UTF8);
    SetConsoleCP(CP_UTF8);
 
    g_hout = GetStdHandle(STD_OUTPUT_HANDLE);
    DWORD mode = 0;
    if (!GetConsoleMode(g_hout, &mode)) return false;
    if (!SetConsoleMode(g_hout, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING | ENABLE_PROCESSED_OUTPUT)) return false;
 
    HANDLE hin = GetStdHandle(STD_INPUT_HANDLE);
    DWORD in_mode = 0;
    if (GetConsoleMode(hin, &in_mode))
        SetConsoleMode(hin, (in_mode | ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT));
 
    CONSOLE_SCREEN_BUFFER_INFO info{};
    if (GetConsoleScreenBufferInfo(g_hout, &info)) {
        g_cols = info.srWindow.Right - info.srWindow.Left + 1;
        g_rows = info.srWindow.Bottom - info.srWindow.Top + 1;
    }
    if (g_cols < 60)  g_cols = 80;
    if (g_rows < 24)  g_rows = 30;
 
    g_log_rows      = (g_rows > 28) ? LOG_ROWS_DEFAULT : std::max(8, g_rows / 3);
    g_upper_rows    = g_rows - g_log_rows - 1;
    g_log_top_row   = g_upper_rows + 2;
    g_log_bottom_row= g_rows;
 
    vt_write("\x1b[2J\x1b[H");
    return true;
}
 
static std::string utf8_from_wide(const std::wstring& w)
{
    if (w.empty()) return {};
    int n = WideCharToMultiByte(CP_UTF8, 0, w.data(), (int)w.size(), nullptr, 0, nullptr, nullptr);
    std::string out(n, '\0');
    WideCharToMultiByte(CP_UTF8, 0, w.data(), (int)w.size(), out.data(), n, nullptr, nullptr);
    return out;
}
 
static std::wstring wide_from_utf8(const std::string& s)
{
    if (s.empty()) return {};
    int n = MultiByteToWideChar(CP_UTF8, 0, s.data(), (int)s.size(), nullptr, 0);
    std::wstring out(n, L'\0');
    MultiByteToWideChar(CP_UTF8, 0, s.data(), (int)s.size(), out.data(), n);
    return out;
}
 
static void draw_frame()
{
    std::lock_guard<std::mutex> lock(g_console_mu);
    vt_write("\x1b[2J\x1b[H");
 
    vt_write("\x1b[1;1H");
    vt_write("\x1b[1;36m=== HD2 BlueCoin Client ===\x1b[0m");
    vt_writef("\x1b[2;1H target: http://127.0.0.1:%u    log: tcp://127.0.0.1:%u",
              (unsigned)DLL_PORT, (unsigned)LOG_PORT);
 
    vt_writef("\x1b[%d;1H", g_upper_rows + 1);
    for (int i = 0; i < g_cols; ++i) vt_write("─");
 
    vt_writef("\x1b[%d;1H\x1b[2K\x1b[90m[ live log stream ]\x1b[0m", g_log_top_row);
    for (int r = g_log_top_row + 1; r <= g_log_bottom_row; ++r)
        vt_writef("\x1b[%d;1H\x1b[2K", r);
 
    vt_writef("\x1b[%d;%dr", g_log_top_row + 1, g_log_bottom_row);
 
    vt_writef("\x1b[%d;1H", 4);
}
 
static void goto_menu_area()
{
    vt_writef("\x1b[%d;1H", g_menu_prompt_row > 0 ? g_menu_prompt_row : 4);
}
 
static void clear_upper()
{
    std::lock_guard<std::mutex> lock(g_console_mu);
    for (int r = 4; r <= g_upper_rows; ++r)
        vt_writef("\x1b[%d;1H\x1b[2K", r);
    vt_writef("\x1b[%d;1H", 4);
}
 
static void print_upper(const std::string& text)
{
 
    std::lock_guard<std::mutex> lock(g_console_mu);
    int r = 4;
    size_t pos = 0;
    while (pos < text.size() && r <= g_upper_rows - 2) {
        size_t nl = text.find('\n', pos);
        std::string line = (nl == std::string::npos) ? text.substr(pos) : text.substr(pos, nl - pos);
        if ((int)line.size() > g_cols - 1) line.resize(g_cols - 1);
        vt_writef("\x1b[%d;1H\x1b[2K%s", r, line.c_str());
        if (nl == std::string::npos) break;
        pos = nl + 1;
        ++r;
    }
    vt_writef("\x1b[%d;1H", g_upper_rows);
}
 
static void append_log_line(const std::string& line)
{
    std::lock_guard<std::mutex> lock(g_console_mu);
    vt_write("\x1b[s");
    vt_writef("\x1b[%d;1H", g_log_bottom_row);
    vt_write("\x1b[2K");
 
    std::string l = line;
    if ((int)l.size() > g_cols - 1) l.resize(g_cols - 1);
    DWORD w = 0;
    WriteFile(g_hout, l.data(), (DWORD)l.size(), &w, nullptr);
    vt_write("\n");
    vt_write("\x1b[u");
}
 
struct HttpResp {
    DWORD status = 0;
    std::string body;
    std::string headers;
};
 
static HttpResp http_do(const wchar_t* verb, const wchar_t* path, const std::string& body_utf8)
{
    HttpResp r;
    HINTERNET hs = WinHttpOpen(L"HD2Client/1.0",
                               WINHTTP_ACCESS_TYPE_NO_PROXY,
                               WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
    if (!hs) return r;
 
    HINTERNET hc = WinHttpConnect(hs, DLL_HOST, DLL_PORT, 0);
    if (!hc) { WinHttpCloseHandle(hs); return r; }
 
    HINTERNET hr = WinHttpOpenRequest(hc, verb, path, nullptr, WINHTTP_NO_REFERER,
                                       WINHTTP_DEFAULT_ACCEPT_TYPES, 0  );
    if (!hr) { WinHttpCloseHandle(hc); WinHttpCloseHandle(hs); return r; }
 
    const wchar_t* extra = L"Content-Type: application/json\r\nAccept: application/json\r\nConnection: close";
    BOOL ok = WinHttpSendRequest(hr, extra, (DWORD)-1L,
                                 body_utf8.empty() ? WINHTTP_NO_REQUEST_DATA : (LPVOID)body_utf8.data(),
                                 (DWORD)body_utf8.size(),
                                 (DWORD)body_utf8.size(), 0);
    if (!ok) goto cleanup;
    if (!WinHttpReceiveResponse(hr, nullptr)) goto cleanup;
 
    {
        DWORD sc = 0; DWORD cb = sizeof(sc);
        WinHttpQueryHeaders(hr, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
                            WINHTTP_HEADER_NAME_BY_INDEX, &sc, &cb, WINHTTP_NO_HEADER_INDEX);
        r.status = sc;
    }
 
    for (;;) {
        DWORD avail = 0;
        if (!WinHttpQueryDataAvailable(hr, &avail)) break;
        if (avail == 0) break;
        std::string chunk(avail, '\0');
        DWORD rd = 0;
        if (!WinHttpReadData(hr, chunk.data(), avail, &rd)) break;
        chunk.resize(rd);
        r.body += chunk;
        if (rd == 0) break;
    }
 
cleanup:
    WinHttpCloseHandle(hr);
    WinHttpCloseHandle(hc);
    WinHttpCloseHandle(hs);
    return r;
}
 
static HttpResp api_get(const std::wstring& path)       { return http_do(L"GET",  path.c_str(), ""); }
static HttpResp api_post(const std::wstring& path, const std::string& body) { return http_do(L"POST", path.c_str(), body); }
 
static void log_stream_worker()
{
    WSADATA wsa; WSAStartup(MAKEWORD(2, 2), &wsa);
 
    while (g_running.load()) {
        SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
        if (s == INVALID_SOCKET) { Sleep(1000); continue; }
 
        sockaddr_in addr{};
        addr.sin_family = AF_INET;
        addr.sin_port   = htons(LOG_PORT);
        inet_pton(AF_INET, LOG_HOST, &addr.sin_addr);
 
        if (connect(s, (sockaddr*)&addr, sizeof(addr)) != 0) {
            closesocket(s);
            Sleep(1500);
            continue;
        }
        append_log_line("[client] log stream connected");
 
        std::string acc;
        char buf[2048];
        while (g_running.load()) {
            int n = recv(s, buf, sizeof(buf), 0);
            if (n <= 0) break;
            acc.append(buf, n);
            size_t pos;
            while ((pos = acc.find('\n')) != std::string::npos) {
                std::string line = acc.substr(0, pos);
                if (!line.empty() && line.back() == '\r') line.pop_back();
                append_log_line(line);
                acc.erase(0, pos + 1);
            }
        }
        append_log_line("[client] log stream disconnected; reconnecting...");
        closesocket(s);
        Sleep(1500);
    }
 
    WSACleanup();
}
 
static std::string json_find_str(const std::string& j, const char* key)
{
    std::string needle = std::string("\"") + key + "\"";
    auto p = j.find(needle);
    if (p == std::string::npos) return {};
    p += needle.size();
    while (p < j.size() && (j[p] == ' ' || j[p] == ':')) ++p;
    if (p >= j.size() || j[p] != '"') return {};
    ++p;
    auto e = j.find('"', p);
    if (e == std::string::npos) return {};
    return j.substr(p, e - p);
}
 
static std::string json_find_scalar(const std::string& j, const char* key)
{
    std::string needle = std::string("\"") + key + "\"";
    auto p = j.find(needle);
    if (p == std::string::npos) return {};
    p += needle.size();
    while (p < j.size() && (j[p] == ' ' || j[p] == ':')) ++p;
    if (p >= j.size()) return {};
    if (j[p] == '"') {
        ++p;
        auto e = j.find('"', p);
        return e == std::string::npos ? std::string() : j.substr(p, e - p);
    }
    auto e = j.find_first_of(",}]", p);
    return e == std::string::npos ? j.substr(p) : j.substr(p, e - p);
}
 
static std::string trim(const std::string& s)
{
    size_t a = 0, b = s.size();
    while (a < b && (s[a] == ' ' || s[a] == '\t' || s[a] == '\r' || s[a] == '\n')) ++a;
    while (b > a && (s[b-1] == ' ' || s[b-1] == '\t' || s[b-1] == '\r' || s[b-1] == '\n')) --b;
    return s.substr(a, b - a);
}
 
static std::string prompt_line(const char* label)
{
    {
        std::lock_guard<std::mutex> lock(g_console_mu);
        vt_writef("%s", label);
    }
    char buf[512]{};
    if (!fgets(buf, sizeof(buf), stdin)) return {};
    return trim(buf);
}
 
static std::vector<std::string> split_csv(const std::string& s)
{
    std::vector<std::string> out;
    std::string cur;
    for (char c : s) {
        if (c == ',') { cur = trim(cur); if (!cur.empty()) out.push_back(cur); cur.clear(); }
        else cur.push_back(c);
    }
    cur = trim(cur);
    if (!cur.empty()) out.push_back(cur);
    return out;
}
 
static std::string json_escape(const std::string& s)
{
    std::string o; o.reserve(s.size()+4);
    for (char c : s) {
        if (c == '"')  { o += "\\\""; }
        else if (c == '\\') { o += "\\\\"; }
        else o.push_back(c);
    }
    return o;
}
 
static void action_status()
{
    clear_upper();
    auto r = api_get(L"/api/status");
    std::string msg;
    msg += "GET /api/status -> HTTP " + std::to_string(r.status) + "\n";
    if (r.status != 200) {
        msg += "(no body or non-200; raw: " + r.body + ")";
        print_upper(msg);
        return;
    }
    msg += "hook_installed  : " + json_find_scalar(r.body, "hook_installed") + "\n";
    msg += "curl_ready      : " + json_find_scalar(r.body, "curl_ready") + "\n";
    msg += "crypto_healthy  : " + json_find_scalar(r.body, "crypto_healthy") + "\n";
    msg += "account_id      : " + json_find_str(r.body, "account_id") + "\n";
    msg += "session_prefix  : " + json_find_str(r.body, "session_prefix") + "\n";
    msg += "total_sent/ok/fail : " +
           json_find_scalar(r.body, "total_sent") + " / " +
           json_find_scalar(r.body, "total_ok")   + " / " +
           json_find_scalar(r.body, "total_fail") + "\n";
    msg += "offsets.crypto_ok/auth/gmi/scan : " +
           json_find_scalar(r.body, "crypto_ok") + "/" +
           json_find_scalar(r.body, "auth_ok")   + "/" +
           json_find_scalar(r.body, "gmi_ok")    + "/" +
           json_find_scalar(r.body, "scan_ok")   + "\n";
    msg += "from_cache (c/a/g/s)            : " +
           json_find_scalar(r.body, "crypto_from_cache") + "/" +
           json_find_scalar(r.body, "auth_from_cache")   + "/" +
           json_find_scalar(r.body, "gmi_from_cache")    + "/" +
           json_find_scalar(r.body, "scan_from_cache")   + "\n";
    print_upper(msg);
}
 
static void action_lookup()
{
    clear_upper();
    std::string code = prompt_line("Friend code: ");
    if (code.empty()) return;
 
    std::wstring path = L"/api/lookup?code=" + wide_from_utf8(code);
    auto r = api_get(path);
 
    std::string msg = "GET /api/lookup?code=" + code + " -> HTTP " + std::to_string(r.status) + "\n";
    if (r.status != 200) { msg += r.body; print_upper(msg); return; }
 
    std::string ok_s      = json_find_scalar(r.body, "ok");
    std::string http_code = json_find_scalar(r.body, "http_code");
    std::string acct      = json_find_str(r.body, "account_id");
    std::string name      = json_find_str(r.body, "name");
    std::string err       = json_find_str(r.body, "error");
 
    msg += "ok           : " + ok_s + "\n";
    msg += "http_code    : " + http_code + "\n";
    msg += "account_id   : " + acct + "\n";
    msg += "name         : " + name + "\n";
    if (!err.empty()) msg += "error        : " + err + "\n";
    print_upper(msg);
}
 
static void action_shoot()
{
    clear_upper();
    std::string is_fc   = prompt_line("Is friend code? (y/N): ");
    bool friend_code    = (!is_fc.empty() && (is_fc[0] == 'y' || is_fc[0] == 'Y'));
 
    std::string targets = prompt_line("Target(s) (comma-separated allowed): ");
    if (targets.empty()) return;
    auto list = split_csv(targets);
    if (list.empty()) return;
 
    std::string count_s    = prompt_line("Count (default 1): ");
    std::string interval_s = prompt_line("Interval ms (default 3000): ");
    int count    = count_s.empty()    ? 1    : std::atoi(count_s.c_str());
    int interval = interval_s.empty() ? 3000 : std::atoi(interval_s.c_str());
    if (count < 1) count = 1;
    if (interval < 100) interval = 100;
 
    std::string body = "{";
    body += "\"is_friend_code\":";
    body += friend_code ? "true" : "false";
    body += ",\"count\":" + std::to_string(count);
    body += ",\"interval_ms\":" + std::to_string(interval);
    if (list.size() == 1) {
        body += ",\"target\":\"" + json_escape(list[0]) + "\"";
    } else {
        body += ",\"targets\":[";
        for (size_t i = 0; i < list.size(); ++i) {
            if (i) body += ",";
            body += "\"" + json_escape(list[i]) + "\"";
        }
        body += "]";
    }
    body += "}";
 
    {
        std::lock_guard<std::mutex> lock(g_console_mu);
        vt_writef("\x1b[%d;1H\x1b[2K(sending, see live log below...)", g_upper_rows);
    }
 
    auto r = api_post(L"/api/shoot", body);
 
    std::string msg = "POST /api/shoot -> HTTP " + std::to_string(r.status) + "\n";
    if (r.status != 200) { msg += r.body; print_upper(msg); return; }
 
    msg += "job_id      : " + json_find_scalar(r.body, "job_id") + "\n";
    msg += "count       : " + json_find_scalar(r.body, "count") + "\n";
    msg += "ok_count    : " + json_find_scalar(r.body, "ok_count") + "\n";
    msg += "fail_count  : " + json_find_scalar(r.body, "fail_count") + "\n";
    msg += "retries     : " + json_find_scalar(r.body, "retries") + "\n";
 
    size_t p = r.body.find("\"shots\":[");
    if (p != std::string::npos) {
        p += 9;
        size_t end = r.body.find(']', p);
        std::string shots = (end == std::string::npos) ? std::string() : r.body.substr(p, end - p);
        size_t i = 0; int shown = 0;
        while (i < shots.size() && shown < 6) {
            size_t a = shots.find('{', i);
            if (a == std::string::npos) break;
            size_t b = shots.find('}', a);
            if (b == std::string::npos) break;
            std::string one = shots.substr(a, b - a + 1);
            std::string idx   = json_find_scalar(one, "i");
            std::string ok    = json_find_scalar(one, "ok");
            std::string http  = json_find_scalar(one, "http_code");
            std::string acct  = json_find_str   (one, "account_id");
            std::string amt   = json_find_str   (one, "amount");
            msg += "  #" + idx + " ok=" + ok + " http=" + http +
                   " account=" + acct + " amount=" + amt + "\n";
            i = b + 1;
            ++shown;
        }
    }
 
    print_upper(msg);
}
 
int main()
{
    if (!init_console()) {
        std::fprintf(stderr, "failed to enable VT console mode — requires Windows 10+\n");
        return 1;
    }
 
    draw_frame();
    std::thread log_th(log_stream_worker);
 
    bool quit = false;
    while (!quit) {
        clear_upper();
        {
            std::lock_guard<std::mutex> lock(g_console_mu);
            vt_writef("\x1b[4;1H [1] Lookup by friend code");
            vt_writef("\x1b[5;1H [2] Shoot blue coins");
            vt_writef("\x1b[6;1H [3] Status");
            vt_writef("\x1b[7;1H [Q] Quit");
            vt_writef("\x1b[9;1H> ");
            g_menu_prompt_row = 9;
        }
 
        std::string c = prompt_line("");
        if (c.empty()) continue;
        char ch = c[0];
 
        switch (ch) {
        case '1': action_lookup(); break;
        case '2': action_shoot();  break;
        case '3': action_status(); break;
        case 'q': case 'Q': quit = true; break;
        default: break;
        }
 
        if (!quit) {
            std::lock_guard<std::mutex> lock(g_console_mu);
            vt_writef("\x1b[%d;1H[press enter to continue]", g_upper_rows - 1);
            (void)getchar();
        }
    }
 
    g_running.store(false);
 
    {
        SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
        sockaddr_in a{}; a.sin_family = AF_INET; a.sin_port = htons(LOG_PORT);
        inet_pton(AF_INET, LOG_HOST, &a.sin_addr);
        connect(s, (sockaddr*)&a, sizeof(a));
        closesocket(s);
    }
    if (log_th.joinable()) log_th.join();
 
    vt_write("\x1b[r");
    vt_writef("\x1b[%d;1H\n", g_rows);
    return 0;
}

If you're planning to research other API endpoints like the Galactic War or Stratagem unlocks, this base should save you a few nights of reversing. Drop your findings or crash logs if you decide to port this to a newer build.

Anyone tested this on the latest patch yet?
 
Top