~zanneth/BishiLauncher

18fd1194ca199c310c13c61e183a2771f8e9e801 — Charles Magahern 3 years ago 90728f7
Initial working hack (always picks nekojya)
M CMakeLists.txt => CMakeLists.txt +1 -0
@@ 36,4 36,5 @@ add_library(bishiinject SHARED
    src/processmoduletable.cpp
    src/util.cpp
    src/gamecontroller.cpp
    src/x86isa.cpp
)

M include/gamecontroller.h => include/gamecontroller.h +16 -1
@@ 5,7 5,7 @@
#include "processmoduletable.h"
#include "util.h"

static DWORD __fastcall __patched_game_tick(void *p);
static char* __cdecl __patched_get_minigame_tex(char *, size_t, DWORD);

class GameController
{


@@ 15,9 15,19 @@ public:

    bool initialize();

private: // types
    using BishiGetMinigameTexFunc = char* __cdecl(*)(
        char *tex_name_buf,
        size_t tex_name_maxlen,
        DWORD minigame_uid);

private: // functions
    explicit GameController();

    char* _patched_get_minigame_tex(char *tex_name_buf,
                                    size_t tex_name_maxlen,
                                    DWORD minigame_uid);

    static void _patch_exe(void *dst, void *src, size_t len);
    static void _patch_exe_ptr(void **ptr, void *new_value);
    static bool _patch_function(const ProcessModuleTable &modtable,


@@ 25,4 35,9 @@ private: // functions
                                const char *sym_name,
                                void *new_funcptr,
                                void **out_orig_funcptr);

    friend char* __cdecl __patched_get_minigame_tex(char *, size_t, DWORD);

private: // variables
    BishiGetMinigameTexFunc _orig_get_minigame_tex_func_ptr;
};

M include/gameprocess.h => include/gameprocess.h +1 -0
@@ 30,6 30,7 @@ private:
    HANDLE                _game_proc_wait_handle;
    HANDLE                _game_thread_handle;
    HANDLE                _inject_thread_handle;
    HANDLE                _stdout_read_handle;

    std::function<void()> _did_exit_callback;
};

M src/gamecontroller.cpp => src/gamecontroller.cpp +47 -18
@@ 1,5 1,7 @@
#include "gamecontroller.h"
#include "processmoduletable.h"
#include "util.h"
#include "x86isa.h"

#include <winternl.h>
#include <cstring>


@@ 12,20 14,12 @@

namespace
{
    constexpr uint32_t GAME_LOOP_TICK_VTABLE_OFFSET = 0x000887f4;
    constexpr uint32_t ACIO_HBHI_GET_CSB_OFFSET = 0x00001490;
    constexpr uint32_t AVS_STDBOOT_HEAP_BOOT_CALL_OFFSET = 0x000291BA;
    constexpr uint32_t AVS_PBOOT_HEAP_BOOT_CALL_OFFSET = 0x0003D900;
    constexpr uint32_t AFP_LOGGING_FLAGS_OFFSET = 0x00081148;

    constexpr const char *ACIO_MODULE_NAME = "libacio.dll";
    constexpr const char *ACIO_HBHI_GET_CSB_SYM_NAME = "ac_io_hbhi_get_control_status_buffer";

    constexpr const char *AFP_MODULE_NAME = "libafp-win32.dll";
    constexpr const char *AFP_BOOT_SYM_NAME = "afp_boot";

    constexpr const char *AVS_MODULE_NAME = "libavs-win32.dll";
    constexpr const char *AVS_HEAP_BOOT_SYM_NAME = "heap_boot";
    constexpr uint32_t BISHI_GET_MINIGAME_TEX_FILENAME_OFFSET = 0x67f20;
    constexpr uint32_t BISHI_GET_MINIGAME_TEX_FILENAME_XREFS[] = {
        0x429ea,
        0x466d3
    };
    constexpr const char *BISHI_MODULE_NAME = "bishi09.exe";
}

GameController& GameController::instance()


@@ 44,17 38,47 @@ GameController::~GameController()

bool GameController::initialize()
{
    DEBUG_LOG("Attempting to patch game code...\n");
    bool success = false;
    // disable buffering on output streams
    std::setvbuf(stdout, NULL, _IONBF, 0);
    std::setvbuf(stderr, NULL, _IONBF, 0);

    show_err("initialize called");
    DEBUG_LOG("Attempting to patch game code...\n");

    ProcessModuleTable table;
    return success;
    const LDR_DATA_TABLE_ENTRY *game_module = table.entry_named(BISHI_MODULE_NAME);
    if (!game_module) {
        ERROR_LOG("Failed to find game module.\n");
        return false;
    }

    uint8_t *base = (uint8_t *)game_module->DllBase;
    const void *replacement = (const void *)&__patched_get_minigame_tex;
    uint8_t patched_instr_data[5];

    _orig_get_minigame_tex_func_ptr = (BishiGetMinigameTexFunc)(base + BISHI_GET_MINIGAME_TEX_FILENAME_OFFSET);

    for (const uint32_t xref : BISHI_GET_MINIGAME_TEX_FILENAME_XREFS) {
        void *dst = (void *)(base + xref);
        const uint32_t call_rel = (uintptr_t)replacement - (uintptr_t)dst - sizeof(patched_instr_data);
        X86ISA::call_rel_32(call_rel, patched_instr_data);

        DEBUG_LOG("Patching minigame tex getter xref at 0x%p\n", dst);
        _patch_exe(dst, patched_instr_data, sizeof(patched_instr_data));
    }

    DEBUG_LOG("Successfully patched game code.\n");
    return true;
}

#pragma mark - Internal

char* GameController::_patched_get_minigame_tex(char *tex_name_buf,
                                                size_t tex_name_maxlen,
                                                DWORD minigame_uid)
{
    return _orig_get_minigame_tex_func_ptr(tex_name_buf, tex_name_maxlen, 1);
}

// static
void GameController::_patch_exe(void *dst, void *src, size_t len)
{


@@ 115,3 139,8 @@ bool GameController::_patch_function(const ProcessModuleTable &modtable,

    return patched;
}

char* __cdecl __patched_get_minigame_tex(char *tex_name_buf, size_t tex_name_maxlen, DWORD minigame_uid)
{
    return GameController::instance()._patched_get_minigame_tex(tex_name_buf, tex_name_maxlen, minigame_uid);
}

M src/gameprocess.cpp => src/gameprocess.cpp +72 -25
@@ 4,12 4,15 @@
#include <iostream>
#include <sstream>

static void __safe_close_handle(HANDLE &h);

GameProcess::GameProcess(const std::string &game_exe_path)
    : _game_exe_path(game_exe_path),
      _game_proc_handle(INVALID_HANDLE_VALUE),
      _game_proc_wait_handle(INVALID_HANDLE_VALUE),
      _game_thread_handle(INVALID_HANDLE_VALUE),
      _inject_thread_handle(INVALID_HANDLE_VALUE)
      _inject_thread_handle(INVALID_HANDLE_VALUE),
      _stdout_read_handle(INVALID_HANDLE_VALUE)
{}

GameProcess::~GameProcess()


@@ 23,6 26,7 @@ bool GameProcess::launch()
{
    STARTUPINFO         si = {0};
    PROCESS_INFORMATION pi = {0};
    HANDLE              stdout_write_handle = INVALID_HANDLE_VALUE;
    BOOL                success = false;

    do {


@@ 40,13 44,36 @@ bool GameProcess::launch()
            break;
        }

        // redirect stdout and stderr to parent process
        SECURITY_ATTRIBUTES secattr = {0};
        secattr.nLength = sizeof(secattr);
        secattr.bInheritHandle = TRUE;

        success = CreatePipe(&_stdout_read_handle, &stdout_write_handle, &secattr, 0);
        if (!success) {
            fprintf(stderr, "Failed to create stdout pipe for child process.\n");
            break;
        }

        // ensure read handle for stdout is not inherited
        success = SetHandleInformation(_stdout_read_handle, HANDLE_FLAG_INHERIT, 0);
        if (!success) {
            fprintf(stderr, "Failed to disable inherit flag on stdout handle.\n");
            break;
        }

        si.cb = sizeof(si);
        si.hStdOutput = stdout_write_handle;
        si.hStdError = stdout_write_handle;
        si.dwFlags |= STARTF_USESTDHANDLES;

        // create the process in a suspended state
        success = CreateProcess(
            NULL,                           // lpApplicationName
            (LPSTR)_game_exe_path.c_str(),  // lpCommandLine
            NULL,                           // lpProcessAttributes
            NULL,                           // lpThreadAttributes
            FALSE,                          // bInheritHandles
            TRUE,                           // bInheritHandles
            CREATE_SUSPENDED,               // dwCreationFlags
            NULL,                           // lpEnvironment
            NULL,                           // lpCurrentDirectory


@@ 72,7 99,7 @@ bool GameProcess::launch()
        success = RegisterWaitForSingleObject(
            &_game_proc_wait_handle,        // phNewWaitObject
            _game_proc_handle,              // hObject
            &GameProcess::_wait_callback,  // callback
            &GameProcess::_wait_callback,   // callback
            (PVOID)this,                    // context
            INFINITE,                       // dwMilliseconds
            WT_EXECUTEONLYONCE              // dwFlags


@@ 90,6 117,11 @@ bool GameProcess::launch()
        }
    } while (0);

    // cleanup
    if (stdout_write_handle != INVALID_HANDLE_VALUE) {
        CloseHandle(stdout_write_handle);
    }

    return success;
}



@@ 106,9 138,30 @@ bool GameProcess::is_running() const

bool GameProcess::wait() const
{
    // register wait for process handle
    DWORD result = WaitForSingleObject(_game_proc_handle, INFINITE);
    return (result == WAIT_OBJECT_0);
    constexpr const DWORD buf_size = 4096;

    DWORD bytes_read, bytes_written;
    CHAR buf[buf_size];
    HANDLE parent_stdout = GetStdHandle(STD_OUTPUT_HANDLE);
    BOOL success = FALSE;

    while (true) {
        if (_stdout_read_handle == INVALID_HANDLE_VALUE) {
            break;
        }

        success = ReadFile(_stdout_read_handle, buf, buf_size, &bytes_read, nullptr);
        if (!success || bytes_read == 0) {
            break;
        }

        success = WriteFile(parent_stdout, buf, bytes_read, &bytes_written, nullptr);
        if (!success || bytes_written == 0) {
            break;
        }
    }

    return true;
}

void GameProcess::terminate()


@@ 200,25 253,11 @@ bool GameProcess::_inject()

void GameProcess::_close()
{
    if (_game_proc_handle != INVALID_HANDLE_VALUE) {
        CloseHandle(_game_proc_handle);
        _game_proc_handle = INVALID_HANDLE_VALUE;
    }

    if (_game_proc_wait_handle != INVALID_HANDLE_VALUE) {
        CloseHandle(_game_proc_wait_handle);
        _game_proc_wait_handle = INVALID_HANDLE_VALUE;
    }

    if (_game_thread_handle != INVALID_HANDLE_VALUE) {
        CloseHandle(_game_thread_handle);
        _game_thread_handle = INVALID_HANDLE_VALUE;
    }

    if (_inject_thread_handle != INVALID_HANDLE_VALUE) {
        CloseHandle(_inject_thread_handle);
        _inject_thread_handle = INVALID_HANDLE_VALUE;
    }
    __safe_close_handle(_game_proc_handle);
    __safe_close_handle(_game_proc_wait_handle);
    __safe_close_handle(_game_thread_handle);
    __safe_close_handle(_inject_thread_handle);
    __safe_close_handle(_stdout_read_handle);
}

void CALLBACK GameProcess::_wait_callback(PVOID param, BOOLEAN timer_or_wait_fired)


@@ 240,3 279,11 @@ std::wstring GameProcess::_exe_dir()
    FilePath exe_splitpath(exe_path);
    return exe_splitpath.drive + exe_splitpath.directory;
}

void __safe_close_handle(HANDLE &h)
{
    if (h != INVALID_HANDLE_VALUE) {
        CloseHandle(h);
        h = INVALID_HANDLE_VALUE;
    }
}

M src/main.cpp => src/main.cpp +10 -0
@@ 14,6 14,16 @@ namespace

int main(int argc, const char **argv)
{
    std::cout <<
        "================================================================================\n"
        "================================================================================\n"
        "============================== bishilauncher v0.1 ==============================\n"
        "================================== by zanneth ==================================\n"
        "================================================================================\n"
        "================================================================================\n"
        << std::endl;
    Sleep(1000);

    std::wstring exe_path_str(MAX_PATH, L'\0');
    DWORD result = GetModuleFileNameW(nullptr, exe_path_str.data(), exe_path_str.size());


A src/x86isa.cpp => src/x86isa.cpp +33 -0
@@ 0,0 1,33 @@
#include "x86isa.h"

union Addr32
{
    uint32_t addr;

    struct
    {
        uint8_t b1, b2, b3, b4;
    };
};

// static
void X86ISA::call_far_32(uint32_t iaddr, uint8_t out_instr[5])
{
    Addr32 addr { .addr = iaddr };
    out_instr[0] = 0x9a;
    out_instr[1] = addr.b1;
    out_instr[2] = addr.b2;
    out_instr[3] = addr.b3;
    out_instr[4] = addr.b4;
}

// static
void X86ISA::call_rel_32(uint32_t irel, uint8_t out_instr[5])
{
    Addr32 rel { .addr = irel };
    out_instr[0] = 0xe8;
    out_instr[1] = rel.b1;
    out_instr[2] = rel.b2;
    out_instr[3] = rel.b3;
    out_instr[4] = rel.b4;
}

A src/x86isa.h => src/x86isa.h +13 -0
@@ 0,0 1,13 @@
#ifndef X86ISA_H
#define X86ISA_H

#include <cstdint>

class X86ISA
{
public:
    static void call_far_32(uint32_t addr, uint8_t out_instr[5]);
    static void call_rel_32(uint32_t rel, uint8_t out_instr[5]);
};

#endif // X86ISA_H