Compare commits

...

8 Commits

Author SHA1 Message Date
Dniel97 25e954fb55 Merge pull request 'print vfd message instead of dump hex' (#13) from Sucareto/segatools:develop into develop
Reviewed-on: #13
2024-04-29 19:48:35 +00:00
Dniel97 a8c6ac70e4 Merge pull request '[unity] Fix Unity config path' (#12) from beerpsi/segatools:fix/unity/wrong-config-path into develop
Reviewed-on: #12
2024-04-29 19:41:53 +00:00
Dniel97 eb1ec0e261
idac: updated start.bat script 2024-04-29 21:34:30 +02:00
Sucareto 482a6e530a print vfd message 2024-04-30 02:19:10 +08:00
beerpsi 65173e1fa6 [unity] Fix Unity config path 2024-04-29 23:12:10 +07:00
beerpsi 4041844ea9 mai2/mu3/cm: Integrate UnityDoorstop with segatools (#11)
[UnityDoorstop](https://github.com/NeighTools/UnityDoorstop) is a tool to execute managed code (.NET DLLs) before Unity does, useful for modding frameworks such as BepInEx.

This PR integrates parts of its code into segatools, so loading BepInEx is as simple as adding 2 lines to `segatools.ini`:
```ini
[unity]
targetAssembly=BepInEx\core\BepInEx.Preloader.dll
```

This PR also factors out the Unity path redirection hooks to its own module.

Reviewed-on: #11
Co-authored-by: beerpsi <beerpsi@duck.com>
Co-committed-by: beerpsi <beerpsi@duck.com>
2024-04-15 19:30:28 +00:00
Dniel97 47a65e5e51
fixed aime LED firmware 2024-03-17 14:20:13 +01:00
Dniel97 774a639bb7
cxb: fixed configs 2024-03-14 00:14:51 +01:00
45 changed files with 559 additions and 321 deletions

View File

@ -25,6 +25,13 @@ struct sg_res_header {
uint8_t payload_len;
};
/* struct to save the version string with its length
to fix NUL terminator issues */
struct version_info {
const char *version;
uint8_t length;
};
typedef HRESULT (*sg_dispatch_fn_t)(
void *ctx,
const void *req,

View File

@ -27,11 +27,11 @@ static HRESULT sg_led_cmd_set_color(
const struct sg_led *led,
const struct sg_led_req_set_color *req);
const char *sg_led_info[] = {
"15084\xFF\x10\x00\x12",
"000-00000\xFF\x11\x40",
static const struct version_info led_version[] = {
{"15084\xFF\x10\x00\x12", 9},
{"000-00000\xFF\x11\x40", 12},
// maybe the same?
"000-00000\xFF\x11\x40"
{"000-00000\xFF\x11\x40", 12}
};
void sg_led_init(
@ -156,10 +156,10 @@ static HRESULT sg_led_cmd_get_info(
{
sg_led_dprintf(led, "Get info\n");
unsigned int len = strlen(sg_led_info[led->gen - 1]);
const struct version_info *fw = &led_version[led->gen - 1];
sg_res_init(&res->res, req, len);
memcpy(res->payload, sg_led_info[led->gen - 1], len);
sg_res_init(&res->res, req, fw->length);
memcpy(res->payload, fw->version, fw->length);
return S_OK;
}

View File

@ -65,16 +65,16 @@ static HRESULT sg_nfc_cmd_dummy(
const struct sg_req_header *req,
struct sg_res_header *res);
static const char *hw_version[] = {
"TN32MSEC003S H/W Ver3.0",
"837-15286",
"837-15396"
static const struct version_info hw_version[] = {
{"TN32MSEC003S H/W Ver3.0", 23},
{"837-15286", 9},
{"837-15396", 9}
};
static const char *fw_version[] = {
"TN32MSEC003S F/W Ver1.2",
"\x94",
"\x94"
static const struct version_info fw_version[] = {
{"TN32MSEC003S F/W Ver1.2", 23},
{"\x94", 1},
{"\x94", 1}
};
void sg_nfc_init(
@ -217,11 +217,11 @@ static HRESULT sg_nfc_cmd_get_fw_version(
const struct sg_req_header *req,
struct sg_nfc_res_get_fw_version *res)
{
unsigned int len = strlen(fw_version[nfc->gen - 1]);
const struct version_info *fw = &fw_version[nfc->gen - 1];
/* Dest version is not NUL terminated, this is intentional */
sg_res_init(&res->res, req, len);
memcpy(res->version, fw_version[nfc->gen - 1], len);
sg_res_init(&res->res, req, fw->length);
memcpy(res->version, fw->version, fw->length);
return S_OK;
}
@ -231,11 +231,11 @@ static HRESULT sg_nfc_cmd_get_hw_version(
const struct sg_req_header *req,
struct sg_nfc_res_get_hw_version *res)
{
unsigned int len = strlen(hw_version[nfc->gen - 1]);
const struct version_info *hw = &hw_version[nfc->gen - 1];
/* Dest version is not NUL terminated, this is intentional */
sg_res_init(&res->res, req, len);
memcpy(res->version, hw_version[nfc->gen - 1], len);
sg_res_init(&res->res, req, hw->length);
memcpy(res->version, hw->version, hw->length);
return S_OK;
}

View File

@ -26,6 +26,7 @@ static HRESULT vfd_handle_irp(struct irp *irp);
static struct uart vfd_uart;
static uint8_t vfd_written[512];
static uint8_t vfd_readable[512];
UINT codepage;
HRESULT vfd_hook_init(const struct vfd_config *cfg, unsigned int port_no)
{
@ -41,6 +42,7 @@ HRESULT vfd_hook_init(const struct vfd_config *cfg, unsigned int port_no)
vfd_uart.readable.bytes = vfd_readable;
vfd_uart.readable.nbytes = sizeof(vfd_readable);
codepage = GetACP();
dprintf("VFD: hook enabled.\n");
return iohook_push_handler(vfd_handle_irp);
@ -62,8 +64,60 @@ static HRESULT vfd_handle_irp(struct irp *irp)
return hr;
}
dprintf("VFD TX:\n");
dump_iobuf(&vfd_uart.written);
uint8_t cmd = 0;
uint8_t str_1[512];
uint8_t str_2[512];
uint8_t str_1_len = 0;
uint8_t str_2_len = 0;
for (size_t i = 0; i < vfd_uart.written.pos; i++) {
if (vfd_uart.written.bytes[i] == 0x1B) {
i++;
cmd = vfd_uart.written.bytes[i];
if (cmd == 0x30) {
i += 3;
}
else if (cmd == 0x50) {
i++;
}
continue;
}
if (cmd == 0x30) {
str_1[str_1_len++] = vfd_uart.written.bytes[i];
}
else if (cmd == 0x50) {
str_2[str_2_len++] = vfd_uart.written.bytes[i];
}
}
if (str_1_len) {
str_1[str_1_len++] = '\0';
if (codepage != 932) {
WCHAR buffer[512];
MultiByteToWideChar(932, 0, (LPCSTR)str_1, str_1_len, buffer, str_1_len);
char str_recode[str_1_len * 3];
WideCharToMultiByte(codepage, 0, buffer, str_1_len, str_recode, str_1_len * 3, NULL, NULL);
dprintf("VFD: %s\n", str_recode);
}
else {
dprintf("VFD: %s\n", str_1);
}
}
if (str_2_len) {
str_2[str_2_len++] = '\0';
if (codepage != 932) {
WCHAR buffer[512];
MultiByteToWideChar(932, 0, (LPCSTR)str_2, str_2_len, buffer, str_2_len);
char str_recode[str_2_len * 3];
WideCharToMultiByte(codepage, 0, buffer, str_2_len, str_recode, str_2_len * 3, NULL, NULL);
dprintf("VFD: %s\n", str_recode);
} else {
dprintf("VFD: %s\n", str_2);
}
}
// dprintf("VFD TX:\n");
// dump_iobuf(&vfd_uart.written);
vfd_uart.written.pos = 0;
return hr;

View File

@ -40,4 +40,5 @@ void cm_hook_config_load(
vfd_config_load(&cfg->vfd, filename);
touch_screen_config_load(&cfg->touch, filename);
cm_dll_config_load(&cfg->dll, filename);
unity_config_load(&cfg->unity, filename);
}

View File

@ -11,6 +11,8 @@
#include "platform/config.h"
#include "unityhook/config.h"
struct cm_hook_config {
struct platform_config platform;
struct aime_config aime;
@ -19,6 +21,7 @@ struct cm_hook_config {
struct vfd_config vfd;
struct cm_dll_config dll;
struct touch_screen_config touch;
struct unity_config unity;
};
void cm_dll_config_load(

View File

@ -16,10 +16,11 @@
#include "cmhook/config.h"
#include "cmhook/io4.h"
#include "cmhook/cm-dll.h"
#include "cmhook/unity.h"
#include "platform/platform.h"
#include "unityhook/hook.h"
#include "util/dprintf.h"
static HMODULE cm_hook_mod;
@ -83,7 +84,7 @@ static DWORD CALLBACK cm_pre_startup(void)
There seems to be an issue with other DLL hooks if `LoadLibraryW` is
hooked earlier in the `cmhook` initialization. */
unity_hook_init();
unity_hook_init(&cm_hook_cfg.unity, cm_hook_mod);
/* Initialize debug helpers */

View File

@ -16,6 +16,7 @@ shared_library(
hooklib_lib,
cmio_lib,
platform_lib,
unityhook_lib,
util_lib,
],
sources : [
@ -26,7 +27,5 @@ shared_library(
'io4.h',
'cm-dll.c',
'cm-dll.h',
'unity.h',
'unity.c',
],
)

View File

@ -1,95 +0,0 @@
#include <stdbool.h>
#include <windows.h>
#include "hook/table.h"
#include "hooklib/dll.h"
#include "hooklib/path.h"
#include "util/dprintf.h"
static void dll_hook_insert_hooks(HMODULE target);
static HMODULE WINAPI my_LoadLibraryW(const wchar_t *name);
static HMODULE (WINAPI *next_LoadLibraryW)(const wchar_t *name);
static const struct hook_symbol unity_kernel32_syms[] = {
{
.name = "LoadLibraryW",
.patch = my_LoadLibraryW,
.link = (void **) &next_LoadLibraryW,
},
};
static const wchar_t *target_modules[] = {
L"mono.dll",
L"cri_ware_unity.dll",
};
static const size_t target_modules_len = _countof(target_modules);
void unity_hook_init(void)
{
dll_hook_insert_hooks(NULL);
}
static void dll_hook_insert_hooks(HMODULE target)
{
hook_table_apply(
target,
"kernel32.dll",
unity_kernel32_syms,
_countof(unity_kernel32_syms));
}
static HMODULE WINAPI my_LoadLibraryW(const wchar_t *name)
{
const wchar_t *name_end;
const wchar_t *target_module;
bool already_loaded;
HMODULE result;
size_t name_len;
size_t target_module_len;
if (name == NULL) {
SetLastError(ERROR_INVALID_PARAMETER);
return NULL;
}
// Check if the module is already loaded
already_loaded = GetModuleHandleW(name) != NULL;
// Must call the next handler so the DLL reference count is incremented
result = next_LoadLibraryW(name);
if (!already_loaded && result != NULL) {
name_len = wcslen(name);
for (size_t i = 0; i < target_modules_len; i++) {
target_module = target_modules[i];
target_module_len = wcslen(target_module);
// Check if the newly loaded library is at least the length of
// the name of the target module
if (name_len < target_module_len) {
continue;
}
name_end = &name[name_len - target_module_len];
// Check if the name of the newly loaded library is one of the
// modules the path hooks should be injected into
if (_wcsicmp(name_end, target_module) != 0) {
continue;
}
dprintf("Unity: Loaded %S\n", target_module);
dll_hook_insert_hooks(result);
path_hook_insert_hooks(result);
}
}
return result;
}

View File

@ -1,3 +0,0 @@
#pragma once
void unity_hook_init(void);

View File

@ -23,22 +23,32 @@ void cxb_dll_config_load(
struct cxb_dll_config *cfg,
const wchar_t *filename)
{
assert(cfg != NULL);
assert(filename != NULL);
GetPrivateProfileStringW(
L"cxbio",
L"path",
L"",
cfg->path,
_countof(cfg->path),
filename);
}
void revio_config_load(struct revio_config *cfg, const wchar_t *filename)
{
assert(cfg != NULL);
assert(filename != NULL);
}
void network_config_load(struct network_config *cfg, const wchar_t *filename)
{
cfg->enable = GetPrivateProfileIntW(L"revio", L"enable", 1, filename);
}
void led_config_load(struct led_config *cfg, const wchar_t *filename)
{
assert(cfg != NULL);
assert(filename != NULL);
cfg->enable = GetPrivateProfileIntW(L"led", L"enable", 1, filename);
}
void cxb_hook_config_load(
@ -56,6 +66,5 @@ void cxb_hook_config_load(
gfx_config_load(&cfg->gfx, filename);
cxb_dll_config_load(&cfg->dll, filename);
revio_config_load(&cfg->revio, filename);
network_config_load(&cfg->network, filename);
led_config_load(&cfg->led, filename);
}

View File

@ -10,7 +10,6 @@
#include "cxbhook/cxb-dll.h"
#include "cxbhook/revio.h"
#include "cxbhook/led.h"
#include "cxbhook/network.h"
#include "gfxhook/gfx.h"
@ -23,7 +22,6 @@ struct cxb_hook_config {
struct gfx_config gfx;
struct cxb_dll_config dll;
struct revio_config revio;
struct network_config network;
struct led_config led;
};
@ -32,7 +30,6 @@ void cxb_dll_config_load(
const wchar_t *filename);
void revio_config_load(struct revio_config *cfg, const wchar_t *filename);
void network_config_load(struct network_config *cfg, const wchar_t *filename);
void led_config_load(struct led_config *cfg, const wchar_t *filename);
void cxb_hook_config_load(

View File

@ -9,7 +9,6 @@
#include "cxbhook/config.h"
#include "cxbhook/revio.h"
#include "cxbhook/led.h"
#include "cxbhook/network.h"
#include "cxbio/cxbio.h"
@ -103,12 +102,6 @@ static DWORD CALLBACK cxb_pre_startup(void)
goto fail;
}
hr = network_hook_init(&cxb_hook_cfg.network);
if (FAILED(hr)) {
goto fail;
}
hr = led_hook_init(&cxb_hook_cfg.led);
if (FAILED(hr)) {

View File

@ -49,7 +49,13 @@ static struct hook_symbol lamp_syms[] = {
HRESULT led_hook_init(struct led_config *cfg)
{
dprintf("LED: Init\n");
assert(cfg != NULL);
if (!cfg->enable) {
return S_FALSE;
}
dprintf("LED: Hook enabled.\n");
return proc_addr_table_push("CommLamp.dll", lamp_syms, _countof(lamp_syms));
}

View File

@ -30,7 +30,5 @@ shared_library(
'revio.h',
'led.c',
'led.h',
'network.c',
'network.h',
],
)

View File

@ -1,13 +0,0 @@
#include <windows.h>
#include <stdbool.h>
#include <stdint.h>
#include "cxbhook/network.h"
#include "util/dprintf.h"
HRESULT network_hook_init(struct network_config *cfg)
{
dprintf("Network: Init\n");
return S_OK;
}

View File

@ -1,13 +0,0 @@
#pragma once
#include <windows.h>
#include <stdbool.h>
#include <stdint.h>
struct network_config {
bool enable;
bool disable_ssl;
char title_server[PATH_MAX];
};
HRESULT network_hook_init(struct network_config *cfg);

View File

@ -82,7 +82,13 @@ static struct hook_symbol revio_syms[] = {
HRESULT revio_hook_init(struct revio_config *cfg)
{
dprintf("Revio: Init\n");
assert(cfg != NULL);
if (!cfg->enable) {
return S_FALSE;
}
dprintf("Revio: Hook enabled.\n");
return proc_addr_table_push("CommIo.dll", revio_syms, _countof(revio_syms));
}

View File

@ -68,6 +68,11 @@ dipsw1=0
; Enable/Disable WinTouch emulation
enable=0
[unity]
; Path to a .NET DLL that should run before the game. Useful for loading
; modding frameworks such as BepInEx.
targetAssembly=
; -----------------------------------------------------------------------------
; Custom IO settings
; -----------------------------------------------------------------------------

View File

@ -110,7 +110,7 @@ path=
[revio]
; Enable emulation of the rev IO board
enabe=1
enable=1
; Test button virtual-key code. Default is the F1 key.
test=0x70
; Service button virtual-key code. Default is the F2 key.

25
dist/idac/start.bat vendored
View File

@ -2,20 +2,6 @@
pushd %~dp0
REM set the APP_DIR to the Y drive
set APP_DIR=Y:\SDGT
REM create the APP_DIR if it doesn't exist and redirect it to the TEMP folder
if not exist "%APP_DIR%" (
subst Y: %TEMP%
REM timeout /t 1
if not exist "%APP_DIR%" (
mkdir "%APP_DIR%"
)
)
echo Mounted the Y:\ drive to the %TEMP%\SDGT folder
set AMDAEMON_CFG=config_common.json ^
config_ex.json ^
config_jp.json ^
@ -40,11 +26,14 @@ config_seat_single_jp.json ^
config_hook.json
start "AM Daemon" /min inject -d -k idachook.dll amdaemon.exe -c %AMDAEMON_CFG%
inject -d -k idachook.dll ..\WindowsNoEditor\GameProject.exe -culture=en launch=Cabinet ABSLOG="..\..\..\..\..\Userdata\GameProject.log" -Master -UserDir="..\..\..\Userdata" -NotInstalled -UNATTENDED
taskkill /f /im amdaemon.exe > nul 2>&1
REM unmount the APP_DIR
subst Y: /d > nul 2>&1
rem JP
rem inject -d -k idachook.dll ..\WindowsNoEditor\GameProject\Binaries\Win64\GameProject-Win64-Shipping.exe -culture=ja launch=Cabinet ABSLOG="..\..\..\..\..\Userdata\GameProject.log" -Master -UserDir="..\..\..\Userdata" -NotInstalled -UNATTENDED
rem EXP
inject -d -k idachook.dll ..\WindowsNoEditor\GameProject\Binaries\Win64\GameProject-Win64-Shipping.exe -culture=en launch=Cabinet ABSLOG="..\..\..\..\..\Userdata\GameProject.log" -Master -UserDir="..\..\..\Userdata" -NotInstalled -UNATTENDED
taskkill /f /im amdaemon.exe > nul 2>&1
echo.
echo Game processes have terminated

View File

@ -83,6 +83,15 @@ path=
; Leave empty if you want to use Segatools built-in keyboard input.
path=
; -----------------------------------------------------------------------------
; Misc. hook settings
; -----------------------------------------------------------------------------
[unity]
; Path to a .NET DLL that should run before the game. Useful for loading
; modding frameworks such as BepInEx.
targetAssembly=
; -----------------------------------------------------------------------------
; Input settings
; -----------------------------------------------------------------------------

View File

@ -72,6 +72,11 @@ dipsw1=1
[gfx]
enable=1
[unity]
; Path to a .NET DLL that should run before the game. Useful for loading
; modding frameworks such as BepInEx.
targetAssembly=
; -----------------------------------------------------------------------------
; Custom IO settings
; -----------------------------------------------------------------------------

View File

@ -39,4 +39,5 @@ void mai2_hook_config_load(
io4_config_load(&cfg->io4, filename);
vfd_config_load(&cfg->vfd, filename);
mai2_dll_config_load(&cfg->dll, filename);
unity_config_load(&cfg->unity, filename);
}

View File

@ -10,6 +10,8 @@
#include "platform/config.h"
#include "unityhook/config.h"
struct mai2_hook_config {
struct platform_config platform;
struct aime_config aime;
@ -17,6 +19,7 @@ struct mai2_hook_config {
struct io4_config io4;
struct vfd_config vfd;
struct mai2_dll_config dll;
struct unity_config unity;
};
void mai2_dll_config_load(

View File

@ -12,10 +12,11 @@
#include "mai2hook/config.h"
#include "mai2hook/io4.h"
#include "mai2hook/mai2-dll.h"
#include "mai2hook/unity.h"
#include "platform/platform.h"
#include "unityhook/hook.h"
#include "util/dprintf.h"
static HMODULE mai2_hook_mod;
@ -80,7 +81,7 @@ static DWORD CALLBACK mai2_pre_startup(void)
There seems to be an issue with other DLL hooks if `LoadLibraryW` is
hooked earlier in the `mai2hook` initialization. */
unity_hook_init();
unity_hook_init(&mai2_hook_cfg.unity, mai2_hook_mod);
/* Initialize debug helpers */

View File

@ -15,6 +15,7 @@ shared_library(
hooklib_lib,
mai2io_lib,
platform_lib,
unityhook_lib,
util_lib,
],
sources : [
@ -25,7 +26,5 @@ shared_library(
'io4.h',
'mai2-dll.c',
'mai2-dll.h',
'unity.h',
'unity.c',
],
)

View File

@ -1,3 +0,0 @@
#pragma once
void unity_hook_init(void);

View File

@ -42,6 +42,7 @@ shlwapi_lib = cc.find_library('shlwapi')
dinput8_lib = cc.find_library('dinput8')
dxguid_lib = cc.find_library('dxguid')
xinput_lib = cc.find_library('xinput')
pathcch_lib = cc.find_library('pathcch')
inc = include_directories('.')
capnhook = subproject('capnhook')
@ -55,6 +56,7 @@ subdir('platform')
subdir('util')
subdir('gfxhook')
subdir('unityhook')
subdir('aimeio')
subdir('chuniio')

View File

@ -42,4 +42,5 @@ void mu3_hook_config_load(
gfx_config_load(&cfg->gfx, filename);
vfd_config_load(&cfg->vfd, filename);
mu3_dll_config_load(&cfg->dll, filename);
unity_config_load(&cfg->unity, filename);
}

View File

@ -13,6 +13,8 @@
#include "platform/config.h"
#include "unityhook/config.h"
struct mu3_hook_config {
struct platform_config platform;
struct aime_config aime;
@ -22,6 +24,7 @@ struct mu3_hook_config {
// struct led15093_config led15093;
struct vfd_config vfd;
struct mu3_dll_config dll;
struct unity_config unity;
};
void mu3_dll_config_load(

View File

@ -2,7 +2,6 @@
#include <stdlib.h>
#include "board/io4.h"
#include "board/sg-reader.h"
#include "board/vfd.h"
@ -20,10 +19,12 @@
#include "mu3hook/config.h"
#include "mu3hook/io4.h"
#include "mu3hook/mu3-dll.h"
#include "mu3hook/unity.h"
#include "platform/platform.h"
#include "unityhook/config.h"
#include "unityhook/hook.h"
#include "util/dprintf.h"
static HMODULE mu3_hook_mod;
@ -99,7 +100,7 @@ static DWORD CALLBACK mu3_pre_startup(void)
There seems to be an issue with other DLL hooks if `LoadLibraryW` is
hooked earlier in the `mu3hook` initialization. */
unity_hook_init();
unity_hook_init(&mu3_hook_cfg.unity, mu3_hook_mod);
/* Initialize debug helpers */

View File

@ -17,6 +17,7 @@ shared_library(
hooklib_lib,
mu3io_lib,
platform_lib,
unityhook_lib,
util_lib,
],
sources : [
@ -27,7 +28,5 @@ shared_library(
'io4.h',
'mu3-dll.c',
'mu3-dll.h',
'unity.h',
'unity.c',
],
)

View File

@ -1,95 +0,0 @@
#include <stdbool.h>
#include <windows.h>
#include "hook/table.h"
#include "hooklib/dll.h"
#include "hooklib/path.h"
#include "util/dprintf.h"
static void dll_hook_insert_hooks(HMODULE target);
static HMODULE WINAPI my_LoadLibraryW(const wchar_t *name);
static HMODULE (WINAPI *next_LoadLibraryW)(const wchar_t *name);
static const struct hook_symbol unity_kernel32_syms[] = {
{
.name = "LoadLibraryW",
.patch = my_LoadLibraryW,
.link = (void **) &next_LoadLibraryW,
},
};
static const wchar_t *target_modules[] = {
L"mono.dll",
L"cri_ware_unity.dll",
};
static const size_t target_modules_len = _countof(target_modules);
void unity_hook_init(void)
{
dll_hook_insert_hooks(NULL);
}
static void dll_hook_insert_hooks(HMODULE target)
{
hook_table_apply(
target,
"kernel32.dll",
unity_kernel32_syms,
_countof(unity_kernel32_syms));
}
static HMODULE WINAPI my_LoadLibraryW(const wchar_t *name)
{
const wchar_t *name_end;
const wchar_t *target_module;
bool already_loaded;
HMODULE result;
size_t name_len;
size_t target_module_len;
if (name == NULL) {
SetLastError(ERROR_INVALID_PARAMETER);
return NULL;
}
// Check if the module is already loaded
already_loaded = GetModuleHandleW(name) != NULL;
// Must call the next handler so the DLL reference count is incremented
result = next_LoadLibraryW(name);
if (!already_loaded && result != NULL) {
name_len = wcslen(name);
for (size_t i = 0; i < target_modules_len; i++) {
target_module = target_modules[i];
target_module_len = wcslen(target_module);
// Check if the newly loaded library is at least the length of
// the name of the target module
if (name_len < target_module_len) {
continue;
}
name_end = &name[name_len - target_module_len];
// Check if the name of the newly loaded library is one of the
// modules the path hooks should be injected into
if (_wcsicmp(name_end, target_module) != 0) {
continue;
}
dprintf("Unity: Loaded %S\n", target_module);
dll_hook_insert_hooks(result);
path_hook_insert_hooks(result);
}
}
return result;
}

View File

@ -1,3 +0,0 @@
#pragma once
void unity_hook_init(void);

View File

@ -82,7 +82,7 @@ HRESULT dns_platform_hook_init(const struct dns_config *cfg)
return hr;
}
// croosbeats REV.
// crossbeats REV.
hr = dns_hook_push(L"https://rev-ent.ac.capcom.jp:443", cfg->title);
if (FAILED(hr)) {

14
unityhook/config.c Normal file
View File

@ -0,0 +1,14 @@
#include "config.h"
void unity_config_load(struct unity_config *cfg, const wchar_t *filename) {
cfg->enable = GetPrivateProfileIntW(L"unity", L"enable", 1, filename);
GetPrivateProfileStringW(
L"unity",
L"targetAssembly",
L"",
cfg->target_assembly,
_countof(cfg->target_assembly),
filename
);
}

12
unityhook/config.h Normal file
View File

@ -0,0 +1,12 @@
#pragma once
#include <stdbool.h>
#include <windows.h>
struct unity_config {
bool enable;
wchar_t target_assembly[MAX_PATH];
};
void unity_config_load(struct unity_config *cfg, const wchar_t *filename);

174
unityhook/doorstop.c Normal file
View File

@ -0,0 +1,174 @@
// A simplified version of NeighTools' UnityDoorstop, allowing mod loaders
// like BepInEx to be loaded into Unity games.
//
// SPDX-License-Identifier: CC0
// https://github.com/NeighTools/UnityDoorstop
#include <stdbool.h>
#include <pathcch.h>
#include <psapi.h>
#include "hooklib/procaddr.h"
#include "util/dprintf.h"
#include "doorstop.h"
#include "mono.h"
#include "util.h"
static void * my_mono_jit_init_version(const char *root_domain_name, const char *runtime_version);
void doorstop_invoke(void *domain);
static char module_name[MAX_PATH];
static bool doorstop_hook_initted;
static struct unity_config unity_config;
static struct hook_symbol unity_mono_syms[] = {
{
.name = "mono_jit_init_version",
.patch = my_mono_jit_init_version,
}
};
void doorstop_mono_hook_init(const struct unity_config *cfg, HINSTANCE module) {
if (doorstop_hook_initted || cfg->target_assembly[0] == 0) {
return;
}
GetModuleBaseNameA(GetCurrentProcess(), module, module_name, MAX_PATH);
memcpy(&unity_config, cfg, sizeof(*cfg));
load_mono_functions(module);
proc_addr_table_push(module_name, unity_mono_syms, _countof(unity_mono_syms));
doorstop_hook_initted = true;
}
static void * my_mono_jit_init_version(const char *root_domain_name, const char *runtime_version) {
dprintf("Unity: Starting Mono domain \"%s\"\n", root_domain_name);
SetEnvironmentVariableW(L"DOORSTOP_DLL_SEARCH_DIRS", widen(mono_assembly_getrootdir()));
void* domain = mono_jit_init_version(root_domain_name, runtime_version);
doorstop_invoke(domain);
return domain;
}
void doorstop_invoke(void* domain) {
if (GetEnvironmentVariableW(L"DOORSTOP_INITIALIZED", NULL, 0) != 0) {
dprintf("Unity: Doorstop is already initialized.\n");
return;
}
SetEnvironmentVariableW(L"DOORSTOP_INITIALIZED", L"TRUE");
mono_thread_set_main(mono_thread_current());
if (mono_domain_set_config) {
#define CONFIG_EXT L".config"
wchar_t config_path[MAX_PATH];
size_t config_path_len = GetModuleFileNameW(NULL, config_path, MAX_PATH);
wchar_t *folder_name = wcsdup(config_path);
PathCchRemoveFileSpec(folder_name, config_path_len + 1);
wmemcpy(config_path + config_path_len, CONFIG_EXT, sizeof(CONFIG_EXT) / sizeof(CONFIG_EXT[0]));
char *config_path_n = narrow(config_path);
char *folder_name_n = narrow(folder_name);
dprintf("Unity: Setting config paths: base dir: %s; config path: %s\n", folder_name_n, config_path_n);
mono_domain_set_config(domain, folder_name_n, config_path_n);
free(folder_name);
free(config_path_n);
free(folder_name_n);
#undef CONFIG_EXT
}
SetEnvironmentVariableW(L"DOORSTOP_INVOKE_DLL_PATH", unity_config.target_assembly);
char *assembly_dir = mono_assembly_getrootdir();
dprintf("Unity: Assembly directory: %s\n", assembly_dir);
SetEnvironmentVariableA("DOORSTOP_MANAGED_FOLDER_DIR", assembly_dir);
wchar_t app_path[MAX_PATH];
GetModuleFileNameW(NULL, app_path, MAX_PATH);
SetEnvironmentVariableW(L"DOORSTOP_PROCESS_PATH", app_path);
char* dll_path = narrow(unity_config.target_assembly);
dprintf("Unity: Loading assembly: %s\n", dll_path);
void* assembly = mono_domain_assembly_open(domain, dll_path);
if (!assembly) {
dprintf("Unity: Failed to load assembly\n");
free(dll_path);
return;
}
void *image = mono_assembly_get_image(assembly);
if (!image) {
dprintf("Unity: Assembly image doesn't exist\n");
free(dll_path);
return;
}
void *desc = mono_method_desc_new("*:Main", FALSE);
void *method = mono_method_desc_search_in_image(desc, image);
if (!method) {
dprintf("Unity: Assembly does not have a valid entrypoint.\n");
free(dll_path);
return;
}
void *signature = mono_method_signature(method);
UINT32 params = mono_signature_get_param_count(signature);
void **args = NULL;
if (params == 1) {
// If there is a parameter, it's most likely a string[].
void *args_array = mono_array_new(domain, mono_get_string_class(), 0);
args = malloc(sizeof(void*) * 1);
args[0] = args_array;
}
dprintf("Unity: Invoking method %p\n", method);
void *exc = NULL;
mono_runtime_invoke(method, NULL, args, &exc);
if (exc) {
dprintf("Unity: Error invoking method!\n");
void *ex_class = mono_get_exception_class();
void *to_string_desc = mono_method_desc_new("*:ToString()", FALSE);
void* to_string_method = mono_method_desc_search_in_class(to_string_desc, ex_class);
mono_method_desc_free(to_string_desc);
if (to_string_method) {
void* real_to_string_method = mono_object_get_virtual_method(exc, to_string_method);
void* exc2 = NULL;
void* str = mono_runtime_invoke(real_to_string_method, exc, NULL, &exc2);
if (!exc2) {
char* exc_str = mono_string_to_utf8(str);
dprintf("Unity: Error message: %s\n", exc_str);
}
}
}
mono_method_desc_free(desc);
free(dll_path);
if (args) {
free(args);
args = NULL;
}
}

5
unityhook/doorstop.h Normal file
View File

@ -0,0 +1,5 @@
#pragma once
#include "config.h"
void doorstop_mono_hook_init(const struct unity_config *cfg, HINSTANCE module);

View File

@ -1,14 +1,23 @@
#include <assert.h>
#include <stdbool.h>
#include <windows.h>
#include "hook/table.h"
#include "hooklib/dll.h"
#include "hooklib/path.h"
#include "util/dprintf.h"
#include "doorstop.h"
#include "hook.h"
static bool unity_hook_initted;
static struct unity_config unity_config;
static const wchar_t *target_modules[] = {
L"mono.dll",
L"mono-2.0-bdwgc.dll",
L"cri_ware_unity.dll",
};
static const size_t target_modules_len = _countof(target_modules);
static void dll_hook_insert_hooks(HMODULE target);
static HMODULE WINAPI my_LoadLibraryW(const wchar_t *name);
@ -22,28 +31,33 @@ static const struct hook_symbol unity_kernel32_syms[] = {
},
};
static const wchar_t *target_modules[] = {
L"mono-2.0-bdwgc.dll",
L"cri_ware_unity.dll",
};
static const size_t target_modules_len = _countof(target_modules);
void unity_hook_init(const struct unity_config *cfg, HINSTANCE self) {
assert(cfg != NULL);
void unity_hook_init(void)
{
if (!cfg->enable) {
return;
}
if (unity_hook_initted) {
return;
}
memcpy(&unity_config, cfg, sizeof(*cfg));
dll_hook_insert_hooks(NULL);
unity_hook_initted = true;
dprintf("Unity: Hook enabled.\n");
}
static void dll_hook_insert_hooks(HMODULE target)
{
static void dll_hook_insert_hooks(HMODULE target) {
hook_table_apply(
target,
"kernel32.dll",
unity_kernel32_syms,
_countof(unity_kernel32_syms));
target,
"kernel32.dll",
unity_kernel32_syms,
_countof(unity_kernel32_syms));
}
static HMODULE WINAPI my_LoadLibraryW(const wchar_t *name)
{
static HMODULE WINAPI my_LoadLibraryW(const wchar_t *name) {
const wchar_t *name_end;
const wchar_t *target_module;
bool already_loaded;
@ -66,6 +80,11 @@ static HMODULE WINAPI my_LoadLibraryW(const wchar_t *name)
if (!already_loaded && result != NULL) {
name_len = wcslen(name);
// mono entrypoint for injecting target_assembly
if (GetProcAddress(result, "mono_jit_init_version")) {
doorstop_mono_hook_init(&unity_config, result);
}
for (size_t i = 0; i < target_modules_len; i++) {
target_module = target_modules[i];
target_module_len = wcslen(target_module);

7
unityhook/hook.h Normal file
View File

@ -0,0 +1,7 @@
#pragma once
#include <windows.h>
#include "config.h"
void unity_hook_init(const struct unity_config *cfg, HINSTANCE self);

20
unityhook/meson.build Normal file
View File

@ -0,0 +1,20 @@
unityhook_lib = static_library(
'unityhook',
include_directories: inc,
implicit_include_directories: false,
c_pch: '../precompiled.h',
dependencies: [
capnhook.get_variable('hook_dep'),
pathcch_lib
],
sources: [
'mono.h',
'config.c',
'config.h',
'doorstop.c',
'doorstop.h',
'hook.c',
'hook.h',
'util.h'
],
)

100
unityhook/mono.h Normal file
View File

@ -0,0 +1,100 @@
// SPDX-License-Identifier: CC0
// https://github.com/NeighTools/UnityDoorstop
#pragma once
#include <windows.h>
// Here we define the pointers to some functions within mono.dll
// Note to C learners: these are not signature definitions, but rather "variable"
// definitions with the function pointer type.
// Note: we use void* instead of the real intented structs defined in mono API
// This way we don't need to include or define any of Mono's structs, which saves space
// This, obviously, comes with a drawback of not being able to easily access the contents of the structs
void * (*mono_thread_current)();
void (*mono_thread_set_main)(void *);
void *(*mono_jit_init_version)(const char *root_domain_name, const char *runtime_version);
void *(*mono_domain_assembly_open)(void *domain, const char *name);
void *(*mono_assembly_get_image)(void *assembly);
void *(*mono_runtime_invoke)(void *method, void *obj, void **params, void **exc);
void *(*mono_method_desc_new)(const char *name, int include_namespace);
void* (*mono_method_desc_search_in_image)(void* desc, void* image);
void *(*mono_method_desc_search_in_class)(void *desc, void *klass);
void (*mono_method_desc_free)(void *desc);
void *(*mono_method_signature)(void *method);
UINT32 (*mono_signature_get_param_count)(void *sig);
void (*mono_domain_set_config)(void *domain, char *base_dir, char *config_file_name);
void *(*mono_array_new)(void *domain, void *eclass, uintptr_t n);
void *(*mono_get_string_class)();
char *(*mono_assembly_getrootdir)();
// Additional funcs to bootstrap custom MONO
void (*mono_set_dirs)(const char* assembly_dir, const char* config_dir);
void (*mono_config_parse)(const char* filename);
void (*mono_set_assemblies_path)(const char* path);
void *(*mono_object_to_string)(void* obj, void** exc);
char *(*mono_string_to_utf8)(void* s);
void *(*mono_image_open_from_data_with_name)(void *data, DWORD data_len, int need_copy, void *status, int refonly,
const char *name);
void* (*mono_get_exception_class)();
void* (*mono_object_get_virtual_method)(void* obj_raw, void* method);
void* (*mono_jit_parse_options)(int argc, const char** argv);
typedef enum {
MONO_DEBUG_FORMAT_NONE,
MONO_DEBUG_FORMAT_MONO,
/* Deprecated, the mdb debugger is not longer supported. */
MONO_DEBUG_FORMAT_DEBUGGER
} MonoDebugFormat;
void* (*mono_debug_init)(MonoDebugFormat format);
void* (*mono_debug_domain_create)(void* domain);
/**
* \brief Loads Mono C API function pointers so that the above definitions can be called.
* \param mono_lib Mono.dll module.
*/
void load_mono_functions(HMODULE mono_lib) {
// Enjoy the fact that C allows such sloppy casting
// In C++ you would have to cast to the precise function pointer type
#define GET_MONO_PROC(name) name = (void*)GetProcAddress(mono_lib, #name)
// Find and assign all our functions that we are going to use
GET_MONO_PROC(mono_domain_assembly_open);
GET_MONO_PROC(mono_assembly_get_image);
GET_MONO_PROC(mono_runtime_invoke);
GET_MONO_PROC(mono_jit_init_version);
GET_MONO_PROC(mono_method_desc_new);
GET_MONO_PROC(mono_method_desc_search_in_class);
GET_MONO_PROC(mono_method_desc_search_in_image);
GET_MONO_PROC(mono_method_desc_free);
GET_MONO_PROC(mono_method_signature);
GET_MONO_PROC(mono_signature_get_param_count);
GET_MONO_PROC(mono_array_new);
GET_MONO_PROC(mono_get_string_class);
GET_MONO_PROC(mono_assembly_getrootdir);
GET_MONO_PROC(mono_thread_current);
GET_MONO_PROC(mono_thread_set_main);
GET_MONO_PROC(mono_domain_set_config);
GET_MONO_PROC(mono_set_dirs);
GET_MONO_PROC(mono_config_parse);
GET_MONO_PROC(mono_set_assemblies_path);
GET_MONO_PROC(mono_object_to_string);
GET_MONO_PROC(mono_string_to_utf8);
GET_MONO_PROC(mono_image_open_from_data_with_name);
GET_MONO_PROC(mono_get_exception_class);
GET_MONO_PROC(mono_object_get_virtual_method);
GET_MONO_PROC(mono_jit_parse_options);
GET_MONO_PROC(mono_debug_init);
GET_MONO_PROC(mono_debug_domain_create);
#undef GET_MONO_PROC
}

20
unityhook/util.h Normal file
View File

@ -0,0 +1,20 @@
#pragma once
#include <windows.h>
wchar_t *widen(const char *str) {
const int reqsz = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);
wchar_t *result = malloc(reqsz * sizeof(wchar_t));
MultiByteToWideChar(CP_UTF8, 0, str, -1, result, reqsz);
return result;
}
char *narrow(const wchar_t *str) {
const int reqsz = WideCharToMultiByte(CP_UTF8, 0, str, -1, NULL, 0, NULL, NULL);
char *result = malloc(reqsz * sizeof(char));
WideCharToMultiByte(CP_UTF8, 0, str, -1, result, reqsz, NULL, NULL);
return result;
}