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