~zanneth/BishiLauncher

425571d82845f281db871ffc2a961835b6b066cb — Charles Magahern 4 years ago 18fd119
Complete hack for remapping broken minigames to hidden working ones
3 files changed, 129 insertions(+), 67 deletions(-)

M include/gamecontroller.h
A include/types.h
M src/gamecontroller.cpp
M include/gamecontroller.h => include/gamecontroller.h +12 -7
@@ 3,9 3,11 @@
#include <windows.h>

#include "processmoduletable.h"
#include "types.h"
#include "util.h"

static char* __cdecl __patched_get_minigame_tex(char *, size_t, DWORD);
static DWORD __cdecl __patched_test_minigame_38(BishiMinigame *);

class GameController
{


@@ 19,7 21,9 @@ private: // types
    using BishiGetMinigameTexFunc = char* __cdecl(*)(
        char *tex_name_buf,
        size_t tex_name_maxlen,
        DWORD minigame_uid);
        DWORD minigame_uid
    );
    using BishiTestMinigame38Func = DWORD __cdecl(*)(BishiMinigame *);

private: // functions
    explicit GameController();


@@ 27,17 31,18 @@ private: // functions
    char* _patched_get_minigame_tex(char *tex_name_buf,
                                    size_t tex_name_maxlen,
                                    DWORD minigame_uid);
    DWORD _patched_test_minigame_38(BishiMinigame *minigame);

    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,
                                const char *module_name,
                                const char *sym_name,
                                void *new_funcptr,
                                void **out_orig_funcptr);
    static void _patch_xrefs(const void *base,
                             const void *replacement,
                             const uint32_t *xrefs,
                             size_t xrefs_count);

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

private: // variables
    BishiGetMinigameTexFunc _orig_get_minigame_tex_func_ptr;
    BishiTestMinigame38Func _orig_test_minigame_38_func_ptr;
};

A include/types.h => include/types.h +23 -0
@@ 0,0 1,23 @@
#pragma once

#include <cstdint>

extern "C"
typedef struct
{
    uint32_t uid;
    uint32_t name1;
    uint32_t unk1;
    uint32_t name2;
    uint32_t game_type;
    uint32_t field_14;
    uint32_t field_18;
    uint32_t field_1C;
    uint32_t field_20;
    uint32_t field_24;
    uint32_t field_28;
    uint32_t field_2C;
    uint32_t field_30;
    uint32_t field_34;
    uint32_t field_38;
} BishiMinigame;

M src/gamecontroller.cpp => src/gamecontroller.cpp +94 -60
@@ 11,15 11,38 @@

#define DEBUG_LOG(...) printf("[BishiInject] " __VA_ARGS__)
#define ERROR_LOG(...) fprintf(stderr, "[BishiInject] " __VA_ARGS__)
#define COUNTOF(X) (sizeof(X) / sizeof(X[0]))

namespace
{
    constexpr const char *BISHI_MODULE_NAME = "bishi09.exe";

    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";

    constexpr uint32_t BISHI_TEST_MINIGAME_38_OFFSET = 0x680d0;
    constexpr uint32_t BISHI_TEST_MINIGAME_38_XREFS[] = {
        0x44998,
        0x51534
    };

    constexpr uint32_t BISHI_MINIGAME_PIROPIRO_UID = 0x33; // playable, hidden always
    constexpr uint32_t BISHI_MINIGAME_POPN_UID     = 0x3c; // playable, hidden always
    constexpr uint32_t BISHI_MINIGAME_LOVE_UID     = 0x3d; // playable, hidden always
    constexpr uint32_t BISHI_MINIGAME_MORSE_UID    = 0x0e; // playable, hidden by 38
    constexpr uint32_t BISHI_MINIGAME_USAGI_UID    = 0x08; // broken
    constexpr uint32_t BISHI_MINIGAME_STOP3_UID    = 0x0f; // broken
    constexpr uint32_t BISHI_MINIGAME_CLASS_UID    = 0x10; // broken

    constexpr uint32_t BISHI_MINIGAME_REMAP[] = {
        //        FROM             ->              TO
        BISHI_MINIGAME_USAGI_UID,       BISHI_MINIGAME_PIROPIRO_UID,
        BISHI_MINIGAME_STOP3_UID,       BISHI_MINIGAME_POPN_UID,
        BISHI_MINIGAME_CLASS_UID,       BISHI_MINIGAME_LOVE_UID,
    };
}

GameController& GameController::instance()


@@ 51,20 74,19 @@ bool GameController::initialize()
        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);
    _orig_get_minigame_tex_func_ptr = (BishiGetMinigameTexFunc)
        ((uint8_t *)game_module->DllBase + BISHI_GET_MINIGAME_TEX_FILENAME_OFFSET);
    _patch_xrefs(game_module->DllBase,
                 (const void *)&__patched_get_minigame_tex,
                 BISHI_GET_MINIGAME_TEX_FILENAME_XREFS,
                 COUNTOF(BISHI_GET_MINIGAME_TEX_FILENAME_XREFS));

    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));
    }
    _orig_test_minigame_38_func_ptr = (BishiTestMinigame38Func)
        ((uint8_t *)game_module->DllBase + BISHI_TEST_MINIGAME_38_OFFSET);
    _patch_xrefs(game_module->DllBase,
                 (const void *)&__patched_test_minigame_38,
                 BISHI_TEST_MINIGAME_38_XREFS,
                 COUNTOF(BISHI_TEST_MINIGAME_38_XREFS));

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


@@ 76,7 98,46 @@ 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);
    // remap a broken minigame to a known working one
    constexpr size_t minigame_remap_size = COUNTOF(BISHI_MINIGAME_REMAP);
    uint32_t remap_result = 0;
    for (unsigned i = 0; i < minigame_remap_size; i += 2) {
        if (BISHI_MINIGAME_REMAP[i] == minigame_uid) {
            remap_result = BISHI_MINIGAME_REMAP[i + 1];
            break;
        }
    }

    DWORD new_minigame_uid = (remap_result ?: minigame_uid);
    return _orig_get_minigame_tex_func_ptr(tex_name_buf, tex_name_maxlen, new_minigame_uid);
}

DWORD GameController::_patched_test_minigame_38(BishiMinigame *minigame)
{
    DWORD result = 0;

    if (minigame) {
        const DWORD uid = minigame->uid;

        // we want all visible minigames to pass this test, even the broken ones.
        switch (uid) {
        case BISHI_MINIGAME_PIROPIRO_UID:
        case BISHI_MINIGAME_POPN_UID:
        case BISHI_MINIGAME_LOVE_UID:
        case BISHI_MINIGAME_MORSE_UID:
        case BISHI_MINIGAME_USAGI_UID:
        case BISHI_MINIGAME_STOP3_UID:
        case BISHI_MINIGAME_CLASS_UID:
            result = 1;
            break;

        default:
            result = _orig_test_minigame_38_func_ptr(minigame);
            break;
        }
    }

    return result;
}

// static


@@ 90,57 151,30 @@ void GameController::_patch_exe(void *dst, void *src, size_t len)
}

// static
void GameController::_patch_exe_ptr(void **ptr, void *new_value)
void GameController::_patch_xrefs(const void *base,
                                  const void *replacement,
                                  const uint32_t *xrefs,
                                  size_t xrefs_count)
{
    _patch_exe(ptr, &new_value, sizeof(void *));
}
    uint8_t patched_instr_data[5];

// static
bool GameController::_patch_function(const ProcessModuleTable &modtable,
                                     const char *module_name,
                                     const char *sym_name,
                                     void *new_funcptr,
                                     void **out_orig_funcptr)
{
    bool patched = false;

    for (const LDR_DATA_TABLE_ENTRY &module : modtable) {
        const uint8_t *base = (uint8_t *)module.DllBase;
        const IMAGE_DOS_HEADER *dosh = (IMAGE_DOS_HEADER *)base;
        const IMAGE_NT_HEADERS *hdrs = (IMAGE_NT_HEADERS *)(base + dosh->e_lfanew);
        const IMAGE_DATA_DIRECTORY *data_dir = &hdrs->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];

        IMAGE_IMPORT_DESCRIPTOR *iid;
        for (   iid = (IMAGE_IMPORT_DESCRIPTOR *)(base + data_dir->VirtualAddress);
                iid->Name != 0 && iid->Name != 0xffff;
                ++iid) {
            const char *iid_name = (char *)(base + iid->Name);
            if (strcmp(iid_name, module_name) != 0) { continue; }

            const uintptr_t *import_rvas = (uintptr_t *)(base + iid->OriginalFirstThunk);
            for (size_t i = 0; import_rvas[i] != 0; ++i) {
                const IMAGE_IMPORT_BY_NAME *import = (IMAGE_IMPORT_BY_NAME *)(base + import_rvas[i]);
                const char *import_name = (const char *)import->Name;
                if (strcmp(import_name, sym_name) != 0) { continue; }

                void **pointers = (void **)(base + iid->FirstThunk);
                void **ppointer = &pointers[i];
                void *new_value = new_funcptr;

                if (out_orig_funcptr) {
                    *out_orig_funcptr = *ppointer;
                }

                _patch_exe_ptr(ppointer, new_value);
                patched = true;
            }
        }
    }
    for (unsigned i = 0; i < xrefs_count; ++i) {
        const uint32_t xref = xrefs[i];
        void *dst = (void *)((uint8_t *)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);

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

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);
}

DWORD __cdecl __patched_test_minigame_38(BishiMinigame *minigame)
{
    return GameController::instance()._patched_test_minigame_38(minigame);
}