Compare commits

...

14 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
Dniel97 097b74d849
cxb: server support added, bugfixes, thanks @Midorica 2024-03-13 21:40:25 +01:00
Dniel97 2590e749ca
config bugfixes 2024-03-07 15:46:43 +01:00
Dniel97 9c66488906
added Move/Replace/Delete hooks
- fixes FGO not correctly switching `START UP MODE`
- fixes SWDC not correctly switching `CABINET SETTING`
2024-02-27 16:54:12 +01:00
Dniel97 f570869946
fixed AMFS path redirect with no E:/ drive present 2024-02-27 16:49:27 +01:00
Dniel97 629ded4018 added AimePay, E-MONEY DNS redirects 2024-02-25 19:03:05 +01:00
Dniel97 ca36a879cb
updated README and added "AM Daemon" window name 2024-02-22 19:12:18 +01:00
63 changed files with 1119 additions and 383 deletions

View File

@ -1,33 +1,33 @@
# Segatools
Version: `2023-11-22`
Version: `2024-03-13`
Loaders and hardware emulators for SEGA games that run on the Nu and ALLS platforms.
## List of supported games
* Chunithm
* [Chunithm (Plus)](doc/chunihook.md)
* [Chunithm Air (Plus)](doc/chunihook.md)
* [Chunithm Star (Plus)](doc/chunihook.md)
* [Chunithm Amazon (Plus)](doc/chunihook.md)
* [Chunithm Crystal (Plus)](doc/chunihook.md)
* Chunithm SUN
* CHUNITHM
* up to [CHUNITHM PARADISE LOST](doc/chunihook.md)
* starting from CHUNITHM NEW!!
* crossbeats REV.
* up to crossbeats REV. SUNRISE
* Initial D
* [Initial D Arcade Stage Zero](doc/idzhook.md)
* Initial D THE ARCADE
* Hatsune Miku: Project DIVA Arcade
* up to Future Tone
* SEGA World Drivers Championship
* up to SEGA World Drivers Championship 2019
* SEGA World Drivers Championship 2019
* Fate/Grand Order
* Fate/Grand Order Arcade
* ONGEKI
* up to bright MEMORY
* O.N.G.E.K.I.
* starting from O.N.G.E.K.I.
* maimai DX
* up to maimai DX FESTiVAL PLUS
* starting from maimai DX
* Card Maker
* up to Card Maker 1.35
* Wacca
* up to WACCA Reverse
* starting from Card Maker
* WACCA
* starting from WACCA
## End-users

View File

@ -68,7 +68,6 @@ static void aime_io_config_read(
cfg->felica_path,
_countof(cfg->felica_path),
filename);
// dprintf("NFC: felicaPath GetLastError %lx\n", GetLastError());
cfg->felica_gen = GetPrivateProfileIntW(
L"aime",

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));
}
@ -154,7 +160,7 @@ static int my_cCommIo_GetTrigger()
out &= ~last_triggers;
dprintf("Revio: GetTrigger %X\n", out);
// dprintf("Revio: GetTrigger %X\n", out);
last_triggers = out;
return out;
}
@ -188,7 +194,7 @@ static int my_cCommIo_GetRelease()
out &= ~btns;
dprintf("Revio: GetRelease %X\n", out);
// dprintf("Revio: GetRelease %X\n", out);
last_triggers = btns;
return out;
}

View File

@ -2,8 +2,9 @@
pushd %~dp0
start /min inject_x64 -d -k chusanhook_x64.dll amdaemon.exe -c config_common.json config_server.json config_client.json config_cvt.json config_sp.json config_hook.json
start "AM Daemon" /min inject_x64 -d -k chusanhook_x64.dll amdaemon.exe -c config_common.json config_server.json config_client.json config_cvt.json config_sp.json config_hook.json
inject_x86 -d -k chusanhook_x86.dll chusanApp.exe
taskkill /f /im amdaemon.exe > nul 2>&1
echo.

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

2
dist/cm/start.bat vendored
View File

@ -2,7 +2,7 @@
pushd %~dp0
start /min inject -d -k cmhook.dll amdaemon.exe -c config_common.json config_server.json config_client.json config_hook.json
start "AM Daemon" /min inject -d -k cmhook.dll amdaemon.exe -c config_common.json config_server.json config_client.json config_hook.json
inject -d -k cmhook.dll CardMaker.exe -screen-fullscreen 0 -popupwindow -screen-width 1080 -screen-height 1920
taskkill /f /im amdaemon.exe > nul 2>&1

View File

@ -19,10 +19,10 @@ appdata=
[aime]
; Aime reader emulation
; CXB is stupid, so we have to make the paths go back one
enable=1
; CXB is stupid, so we have to make the paths go back two directories. This
; will load the file from "resource\DEVICE\aime.txt".
aimePath=../DEVICE/aime.txt
felicaPath=../DEVICE/felica.txt
[led]
; Emulation for the LED board. Currently it's just dummy responses,
@ -39,6 +39,10 @@ enable=1
; Note that 127.0.0.1, localhost etc are specifically rejected.
default=127.0.0.1
; Set the title server hostname or IP address here, as the title server
; is hardcoded in the game.
title=https://127.0.0.1:9002
[netenv]
; Simulate an ideal LAN environment. This may interfere with head-to-head play.
; Crossbeats is extremely picky about its LAN environment, so leaving this
@ -106,13 +110,14 @@ 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.
service=0x71
; Keyboard button to increment coin counter. Default is the F3 key.
coin=0x72
; Menu up key. Default is up arrow.
up=0x26
; Menu down key. Default is down arrow.

View File

@ -129,12 +129,12 @@ path=
[io4]
; Input API selection for JVS input emulator.
; Test button virtual-key code. Default is the 1 key.
test=0x31
; Service button virtual-key code. Default is the 2 key.
service=0x32
; Keyboard button to increment coin counter. Default is the 3 key.
coin=0x33
; Test button virtual-key code. Default is the F1 key.
test=0x70
; Service button virtual-key code. Default is the F2 key.
service=0x71
; Keyboard button to increment coin counter. Default is the F3 key.
coin=0x72
; .·:'''''''''''''''''''''''''''''''''''''''''''''':·.
; : : ______ / \ [] : :
@ -151,8 +151,8 @@ coin=0x33
;
; Only XInput is currently supported.
; Controller Button
; -------------------------------------------------------
; XInput bindings
;
; Left Stick Joystick
; Left Stick Click Reset Camera
; Left Trigger Dash

27
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 ^
@ -39,12 +25,15 @@ config_seat_single_ex.json ^
config_seat_single_jp.json ^
config_hook.json
start /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
start "AM Daemon" /min inject -d -k idachook.dll amdaemon.exe -c %AMDAEMON_CFG%
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
; -----------------------------------------------------------------------------

5
dist/mai2/start.bat vendored
View File

@ -2,8 +2,9 @@
pushd %~dp0
start /min inject -d -k mai2hook.dll amdaemon.exe -f -c config_common.json config_server.json config_client.json
inject -d -k mai2hook.dll sinmai -screen-fullscreen 0
start "AM Daemon" /min inject -d -k mai2hook.dll amdaemon.exe -f -c config_common.json config_server.json config_client.json
inject -d -k mai2hook.dll sinmai -screen-fullscreen 0 -popupwindow -screen-width 2160 -screen-height 1920 -silent-crashes
taskkill /f /im amdaemon.exe > nul 2>&1
echo.

View File

@ -4,10 +4,10 @@ pushd %~dp0
taskkill /f /im amdaemon.exe > nul 2>&1
REM USA
REM start inject -d -k mercuryhook.dll amdaemon.exe -f -c config.json config_lan_install_client.json config_lan_install_server.json config_video_clone.json config_video_dual.json config_video_clone_flip.json config_video_dual_flip.json config_region_exp.json config_region_chn.json config_region_usa.json
start "AM Daemon" /min inject -d -k mercuryhook.dll amdaemon.exe -f -c config.json config_lan_install_client.json config_lan_install_server.json config_video_clone.json config_video_dual.json config_video_clone_flip.json config_video_dual_flip.json config_region_exp.json config_region_chn.json config_region_usa.json
REM JP
start inject -d -k mercuryhook.dll amdaemon.exe -f -c config.json config_lan_install_client.json config_lan_install_server.json config_video_clone.json config_video_dual.json config_video_clone_flip.json config_video_dual_flip.json config_region_exp.json config_region_chn.json config_region_jpn.json
start "AM Daemon" /min inject -d -k mercuryhook.dll amdaemon.exe -f -c config.json config_lan_install_client.json config_lan_install_server.json config_video_clone.json config_video_dual.json config_video_clone_flip.json config_video_dual_flip.json config_region_exp.json config_region_chn.json config_region_jpn.json
inject -d -k mercuryhook.dll ../WindowsNoEditor/Mercury/Binaries/Win64/Mercury-Win64-Shipping.exe
taskkill /f /im amdaemon.exe > nul 2>&1

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
; -----------------------------------------------------------------------------
@ -81,7 +86,7 @@ enable=1
; Leave empty if you want to use Segatools built-in keyboard input.
path=
[fgoio]
[mu3io]
; To use a custom O.N.G.E.K.I. IO DLL enter its path here.
; Leave empty if you want to use Segatools built-in keyboard/gamepad input.
path=

2
dist/mu3/start.bat vendored
View File

@ -2,7 +2,7 @@
pushd %~dp0
start /min inject -d -k mu3hook.dll amdaemon.exe -f -c config_common.json config_server.json config_client.json
start "AM Daemon" /min inject -d -k mu3hook.dll amdaemon.exe -f -c config_common.json config_server.json config_client.json
inject -d -k mu3hook.dll mu3 -screen-fullscreen 0 -popupwindow -screen-width 1080 -screen-height 1920
taskkill /f /im amdaemon.exe > nul 2>&1

View File

@ -63,7 +63,7 @@ enable=1
; Enable freeplay mode. This will disable the coin slot and set the game to
; freeplay. Keep in mind that some game modes (e.g. Freedom/Time Modes) will not
; allow you to start a game in freeplay mode.
freeplay=0´
freeplay=0
; -----------------------------------------------------------------------------
; Custom IO settings

View File

@ -153,6 +153,13 @@ setting. Also, loopback addresses are specifically checked for and rejected by
the games themselves; this needs to be a LAN or WAN IP (or a hostname that
resolves to one).
### `title`
Default: `title`
Leave it as `title` to use the title server returned by ALL.Net. Rewrites
the title server hostname for certain games, such as crossbeats REV.
### `router`
Default: Empty string (i.e. use value from `default` setting)
@ -388,13 +395,29 @@ Bit values are:
- 3: EXP: Export (for Asian markets)
- 4: CHS: China (Simplified Chinese?)
### `billingCa`
Default: `DEVICE\\ca.crt`
Set the billing certificate path. This has to match the one used for the
SSL billing server. The DER certificate must fit in 1024 bytes so it must be
small.
### `billingPub`
Default: `DEVICE\\billing.pub`
Set the actual keychip RSA public key path. This public key has to match the
private key `billing.key` of the billing server in order to decrypt/encrypt
the billing transactions.
### `billingType`
Default: `1`
Set the billing "type" for the keychip. The type determins what kind of revenue share,
if any, the game maker has with SEGA. Some games may be picky and require types other
then 1 (ex. Crossbeats requires billing type 2), so this option is provided if this
then 1 (ex. crossbeats REV. requires billing type 2), so this option is provided if this
is an issue. Billing types are:
- 0: No billing?

View File

@ -3,6 +3,7 @@
#include <windows.h>
#include <windns.h>
#include <ws2tcpip.h>
#include <winhttp.h>
#include <assert.h>
#include <stdbool.h>
@ -12,6 +13,8 @@
#include "hook/hr.h"
#include "hook/table.h"
#include "util/dprintf.h"
#include "hooklib/dns.h"
/* Latest w32headers does not include DnsQueryEx, so we'll have to "polyfill"
@ -66,6 +69,18 @@ static int WSAAPI hook_getaddrinfo(
const ADDRINFOA *pHints,
ADDRINFOA **ppResult);
static HINTERNET WINAPI hook_WinHttpConnect(
HINTERNET hSession,
const wchar_t *pwszServerName,
INTERNET_PORT nServerPort,
DWORD dwReserved);
static bool WINAPI hook_WinHttpCrackUrl(
const wchar_t *pwszUrl,
DWORD dwUrlLength,
DWORD dwFlags,
LPURL_COMPONENTS lpUrlComponents);
/* Link pointers */
static DNS_STATUS (WINAPI *next_DnsQuery_A)(
@ -95,6 +110,18 @@ static int (WSAAPI *next_getaddrinfo)(
const ADDRINFOA *pHints,
ADDRINFOA **ppResult);
static HINTERNET (WINAPI *next_WinHttpConnect)(
HINTERNET hSession,
const wchar_t *pwszServerName,
INTERNET_PORT nServerPort,
DWORD dwReserved);
static bool (WINAPI *next_WinHttpCrackUrl)(
const wchar_t *pwszUrl,
DWORD dwUrlLength,
DWORD dwFlags,
LPURL_COMPONENTS lpUrlComponents);
static const struct hook_symbol dns_hook_syms_dnsapi[] = {
{
.name = "DnsQuery_A",
@ -120,10 +147,24 @@ static const struct hook_symbol dns_hook_syms_ws2[] = {
}
};
static const struct hook_symbol dns_hook_syms_winhttp[] = {
{
.name = "WinHttpConnect",
.patch = hook_WinHttpConnect,
.link = (void **) &next_WinHttpConnect,
}, {
.name = "WinHttpCrackUrl",
.patch = hook_WinHttpCrackUrl,
.link = (void **) &next_WinHttpCrackUrl,
}
};
static bool dns_hook_initted;
static CRITICAL_SECTION dns_hook_lock;
static struct dns_hook_entry *dns_hook_entries;
static size_t dns_hook_nentries;
static char received_title_url[255];
static void dns_hook_init(void)
{
@ -145,6 +186,12 @@ static void dns_hook_init(void)
"ws2_32.dll",
dns_hook_syms_ws2,
_countof(dns_hook_syms_ws2));
hook_table_apply(
NULL,
"winhttp.dll",
dns_hook_syms_winhttp,
_countof(dns_hook_syms_winhttp));
}
HRESULT dns_hook_push(const wchar_t *from_src, const wchar_t *to_src)
@ -460,3 +507,83 @@ end:
return result;
}
static HINTERNET WINAPI hook_WinHttpConnect(
HINTERNET hSession,
const wchar_t *pwszServerName,
INTERNET_PORT nServerPort,
DWORD dwReserved)
{
const struct dns_hook_entry *pos;
size_t i;
if (pwszServerName == NULL) {
return NULL;
}
EnterCriticalSection(&dns_hook_lock);
for (i = 0 ; i < dns_hook_nentries ; i++) {
pos = &dns_hook_entries[i];
if (_wcsicmp(pwszServerName, pos->from) == 0) {
if(pos->to == NULL) {
LeaveCriticalSection(&dns_hook_lock);
return NULL;
}
pwszServerName = pos->to;
break;
}
}
LeaveCriticalSection(&dns_hook_lock);
return next_WinHttpConnect(hSession, pwszServerName, nServerPort, dwReserved);
}
// Hook to replace CXB title url
static bool WINAPI hook_WinHttpCrackUrl(
const wchar_t *pwszUrl,
DWORD dwUrlLength,
DWORD dwFlags,
LPURL_COMPONENTS lpUrlComponents)
{
const struct dns_hook_entry *pos;
size_t i;
EnterCriticalSection(&dns_hook_lock);
for (i = 0 ; i < dns_hook_nentries ; i++) {
pos = &dns_hook_entries[i];
if (_wcsicmp(pwszUrl, pos->from) == 0) {
wchar_t* toAddr = pos->to;
wchar_t titleBuffer[255];
if(wcscmp(toAddr, L"title") == 0) {
size_t wstr_c;
mbstowcs_s(&wstr_c, titleBuffer, 255, received_title_url, strlen(received_title_url));
toAddr = titleBuffer;
}
bool result = next_WinHttpCrackUrl(
toAddr,
wcslen(toAddr),
dwFlags,
lpUrlComponents
);
LeaveCriticalSection(&dns_hook_lock);
return result;
}
}
LeaveCriticalSection(&dns_hook_lock);
return next_WinHttpCrackUrl(
pwszUrl,
dwUrlLength,
dwFlags,
lpUrlComponents
);
}

View File

@ -101,6 +101,40 @@ static BOOL WINAPI hook_PathFileExistsA(LPCSTR pszPath);
static BOOL WINAPI hook_PathFileExistsW(LPCWSTR pszPath);
static BOOL WINAPI hook_MoveFileA(
const char *lpExistingFileName,
const char *lpNewFileName);
static BOOL WINAPI hook_MoveFileW(
const wchar_t *lpExistingFileName,
const wchar_t *lpNewFileName);
static BOOL WINAPI hook_MoveFileExA(
const char *lpExistingFileName,
const char *lpNewFileName,
uint32_t dwFlags);
static BOOL WINAPI hook_ReplaceFileA(
const char *lpReplacedFileName,
const char *lpReplacementFileName,
const char *lpBackupFileName,
uint32_t dwReplaceFlags,
void *lpExclude,
void *lpReserved);
static BOOL WINAPI hook_ReplaceFileW(
const wchar_t *lpReplacedFileName,
const wchar_t *lpReplacementFileName,
const wchar_t *lpBackupFileName,
uint32_t dwReplaceFlags,
void *lpExclude,
void *lpReserved);
static BOOL WINAPI hook_DeleteFileA(const char *lpFileName);
static BOOL WINAPI hook_DeleteFileW(const wchar_t *lpFileName);
/* Link pointers */
static BOOL (WINAPI *next_CreateDirectoryA)(
@ -185,6 +219,39 @@ static BOOL (WINAPI *next_PathFileExistsA)(LPCSTR pszPath);
static BOOL (WINAPI *next_PathFileExistsW)(LPCWSTR pszPath);
static BOOL (WINAPI *next_MoveFileA)(
const char *lpExistingFileName,
const char *lpNewFileName);
static BOOL (WINAPI *next_MoveFileW)(
const wchar_t *lpExistingFileName,
const wchar_t *lpNewFileName);
static BOOL (WINAPI *next_MoveFileExA)(
const char *lpExistingFileName,
const char *lpNewFileName,
uint32_t dwFlags);
static BOOL (WINAPI *next_ReplaceFileA)(
const char *lpReplacedFileName,
const char *lpReplacementFileName,
const char *lpBackupFileName,
uint32_t dwReplaceFlags,
void *lpExclude,
void *lpReserved);
static BOOL (WINAPI *next_ReplaceFileW)(
const wchar_t *lpReplacedFileName,
const wchar_t *lpReplacementFileName,
const wchar_t *lpBackupFileName,
uint32_t dwReplaceFlags,
void *lpExclude,
void *lpReserved);
static BOOL (WINAPI *next_DeleteFileA)(const char *lpFileName);
static BOOL (WINAPI *next_DeleteFileW)(const wchar_t *lpFileName);
/* Hook table */
static const struct hook_symbol path_hook_syms[] = {
@ -260,6 +327,34 @@ static const struct hook_symbol path_hook_syms[] = {
.name = "PathFileExistsW",
.patch = hook_PathFileExistsW,
.link = (void **) &next_PathFileExistsW,
}, {
.name = "MoveFileA",
.patch = hook_MoveFileA,
.link = (void **) &next_MoveFileA,
}, {
.name = "MoveFileW",
.patch = hook_MoveFileW,
.link = (void **) &next_MoveFileW,
}, {
.name = "MoveFileExA",
.patch = hook_MoveFileExA,
.link = (void **) &next_MoveFileExA,
}, {
.name = "ReplaceFileA",
.patch = hook_ReplaceFileA,
.link = (void **) &next_ReplaceFileA,
}, {
.name = "ReplaceFileW",
.patch = hook_ReplaceFileW,
.link = (void **) &next_ReplaceFileW,
}, {
.name = "DeleteFileA",
.patch = hook_DeleteFileA,
.link = (void **) &next_DeleteFileA,
}, {
.name = "DeleteFileW",
.patch = hook_DeleteFileW,
.link = (void **) &next_DeleteFileW,
}
};
@ -906,3 +1001,213 @@ static BOOL WINAPI hook_PathFileExistsW(LPCWSTR pszPath)
return ok;
}
static BOOL WINAPI hook_MoveFileA(
const char *lpExistingFileName,
const char *lpNewFileName)
{
char *oldTrans;
char *newTrans;
BOOL ok;
ok = path_transform_a(&oldTrans, lpExistingFileName);
if (!ok) {
return FALSE;
}
ok = path_transform_a(&newTrans, lpNewFileName);
if (!ok) {
free(oldTrans);
return FALSE;
}
ok = next_MoveFileA(
oldTrans ? oldTrans : lpExistingFileName,
newTrans ? newTrans : lpNewFileName);
free(oldTrans);
free(newTrans);
return ok;
}
static BOOL WINAPI hook_MoveFileW(
const wchar_t *lpExistingFileName,
const wchar_t *lpNewFileName)
{
wchar_t *oldTrans;
wchar_t *newTrans;
BOOL ok;
ok = path_transform_w(&oldTrans, lpExistingFileName);
if (!ok) {
return FALSE;
}
ok = path_transform_w(&newTrans, lpNewFileName);
if (!ok) {
free(oldTrans);
return FALSE;
}
ok = next_MoveFileW(
oldTrans ? oldTrans : lpExistingFileName,
newTrans ? newTrans : lpNewFileName);
free(oldTrans);
free(newTrans);
return ok;
}
static BOOL WINAPI hook_MoveFileExA(
const char *lpExistingFileName,
const char *lpNewFileName,
uint32_t dwFlags)
{
char *oldTrans;
char *newTrans;
BOOL ok;
ok = path_transform_a(&oldTrans, lpExistingFileName);
if (!ok) {
return FALSE;
}
ok = path_transform_a(&newTrans, lpNewFileName);
if (!ok) {
free(oldTrans);
return FALSE;
}
ok = next_MoveFileExA(
oldTrans ? oldTrans : lpExistingFileName,
newTrans ? newTrans : lpNewFileName,
dwFlags);
free(oldTrans);
free(newTrans);
return ok;
}
static BOOL WINAPI hook_ReplaceFileA(
const char *lpReplacedFileName,
const char *lpReplacementFileName,
const char *lpBackupFileName,
uint32_t dwReplaceFlags,
void *lpExclude,
void *lpReserved)
{
char *oldTrans;
char *newTrans;
BOOL ok;
ok = path_transform_a(&oldTrans, lpReplacedFileName);
if (!ok) {
return FALSE;
}
ok = path_transform_a(&newTrans, lpReplacementFileName);
if (!ok) {
free(oldTrans);
return FALSE;
}
ok = next_ReplaceFileA(
oldTrans ? oldTrans : lpReplacedFileName,
newTrans ? newTrans : lpReplacementFileName,
lpBackupFileName,
dwReplaceFlags,
lpExclude,
lpReserved);
free(oldTrans);
free(newTrans);
return ok;
}
static BOOL WINAPI hook_ReplaceFileW(
const wchar_t *lpReplacedFileName,
const wchar_t *lpReplacementFileName,
const wchar_t *lpBackupFileName,
uint32_t dwReplaceFlags,
void *lpExclude,
void *lpReserved)
{
wchar_t *oldTrans;
wchar_t *newTrans;
BOOL ok;
ok = path_transform_w(&oldTrans, lpReplacedFileName);
if (!ok) {
return FALSE;
}
ok = path_transform_w(&newTrans, lpReplacementFileName);
if (!ok) {
free(oldTrans);
return FALSE;
}
ok = next_ReplaceFileW(
oldTrans ? oldTrans : lpReplacedFileName,
newTrans ? newTrans : lpReplacementFileName,
lpBackupFileName,
dwReplaceFlags,
lpExclude,
lpReserved);
free(oldTrans);
free(newTrans);
return ok;
}
static BOOL WINAPI hook_DeleteFileA(const char *lpFileName)
{
char *trans;
BOOL ok;
ok = path_transform_a(&trans, lpFileName);
if (!ok) {
return FALSE;
}
ok = next_DeleteFileA(trans ? trans: lpFileName);
return ok;
}
static BOOL WINAPI hook_DeleteFileW(const wchar_t *lpFileName)
{
wchar_t *trans;
BOOL ok;
ok = path_transform_w(&trans, lpFileName);
if (!ok) {
return FALSE;
}
ok = next_DeleteFileW(trans ? trans: lpFileName);
return ok;
}

View File

@ -13,7 +13,6 @@ EXPORTS
amDllVideoSetResolution @3
idac_io_get_api_version
idac_io_init
idac_io_poll
idac_io_get_opbtns
idac_io_get_gamebtns
idac_io_get_shifter

View File

@ -52,10 +52,6 @@ HRESULT idac_xi_init(const struct idac_xi_config *cfg, const struct idac_io_back
return S_OK;
}
HRESULT idac_io_poll(void) {
return S_OK;
}
static HRESULT idac_xi_config_apply(const struct idac_xi_config *cfg) {
/* Deadzones check */
if (cfg->left_stick_deadzone > 32767 || cfg->left_stick_deadzone < 0) {

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

@ -113,6 +113,14 @@ void dns_config_load(struct dns_config *cfg, const wchar_t *filename)
cfg->aimedb,
_countof(cfg->aimedb),
filename);
GetPrivateProfileStringW(
L"dns",
L"title",
L"title",
cfg->title,
_countof(cfg->title),
filename);
}
void hwmon_config_load(struct hwmon_config *cfg, const wchar_t *filename)

View File

@ -82,6 +82,33 @@ HRESULT dns_platform_hook_init(const struct dns_config *cfg)
return hr;
}
// crossbeats REV.
hr = dns_hook_push(L"https://rev-ent.ac.capcom.jp:443", cfg->title);
if (FAILED(hr)) {
return hr;
}
// AimePay
hr = dns_hook_push(L"api-aime.am-all.net", cfg->startup);
if (FAILED(hr)) {
return hr;
}
// E-MONEY
hr = dns_hook_push(L"tasms-api-basis.thincacloud.com", cfg->startup);
if (FAILED(hr)) {
return hr;
}
hr = dns_hook_push(L"shop.tfps.thincacloud.com", cfg->startup);
if (FAILED(hr)) {
return hr;
}
// if your ISP resolves bad domains, it will kill the network. These 2
// *cannot* resolve

View File

@ -11,6 +11,7 @@ struct dns_config {
wchar_t startup[128];
wchar_t billing[128];
wchar_t aimedb[128];
wchar_t title[128];
};
HRESULT dns_platform_hook_init(const struct dns_config *cfg);

View File

@ -14,22 +14,22 @@
#include "util/str.h"
enum {
NUSEC_IOCTL_PING = 0x22A114,
NUSEC_IOCTL_ERASE_TRACE_LOG = 0x22E188,
NUSEC_IOCTL_TD_ERASE_USED = 0x22E18C,
NUSEC_IOCTL_ADD_PLAY_COUNT = 0x22E154,
NUSEC_IOCTL_GET_BILLING_CA_CERT = 0x22E1C4,
NUSEC_IOCTL_GET_BILLING_PUBKEY = 0x22E1C8,
NUSEC_IOCTL_GET_NEARFULL = 0x22E20C,
NUSEC_IOCTL_GET_NVRAM_AVAILABLE = 0x22E19C,
NUSEC_IOCTL_GET_NVRAM_GEOMETRY = 0x22E24C,
NUSEC_IOCTL_GET_PLAY_COUNT = 0x22E150,
NUSEC_IOCTL_GET_PLAY_LIMIT = 0x22E204,
NUSEC_IOCTL_GET_TRACE_LOG_DATA = 0x22E194,
NUSEC_IOCTL_GET_TRACE_LOG_STATE = 0x22E198,
NUSEC_IOCTL_PUT_NEARFULL = 0x22E210,
NUSEC_IOCTL_PUT_PLAY_LIMIT = 0x22E208,
NUSEC_IOCTL_PUT_TRACE_LOG_DATA = 0x22E190,
NUSEC_IOCTL_PING = CTL_CODE(0x22, 0x845, METHOD_BUFFERED, FILE_WRITE_ACCESS),
NUSEC_IOCTL_GET_PLAY_COUNT = CTL_CODE(0x22, 0x854, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS),
NUSEC_IOCTL_ADD_PLAY_COUNT = CTL_CODE(0x22, 0x855, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS),
NUSEC_IOCTL_ERASE_TRACE_LOG = CTL_CODE(0x22, 0x862, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS),
NUSEC_IOCTL_TD_ERASE_USED = CTL_CODE(0x22, 0x863, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS),
NUSEC_IOCTL_PUT_TRACE_LOG_DATA = CTL_CODE(0x22, 0x864, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS),
NUSEC_IOCTL_GET_TRACE_LOG_DATA = CTL_CODE(0x22, 0x865, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS),
NUSEC_IOCTL_GET_TRACE_LOG_STATE = CTL_CODE(0x22, 0x866, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS),
NUSEC_IOCTL_GET_NVRAM_AVAILABLE = CTL_CODE(0x22, 0x867, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS),
NUSEC_IOCTL_GET_BILLING_CA_CERT = CTL_CODE(0x22, 0x871, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS),
NUSEC_IOCTL_GET_BILLING_PUBKEY = CTL_CODE(0x22, 0x872, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS),
NUSEC_IOCTL_GET_PLAY_LIMIT = CTL_CODE(0x22, 0x881, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS),
NUSEC_IOCTL_PUT_PLAY_LIMIT = CTL_CODE(0x22, 0x882, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS),
NUSEC_IOCTL_GET_NEARFULL = CTL_CODE(0x22, 0x883, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS),
NUSEC_IOCTL_PUT_NEARFULL = CTL_CODE(0x22, 0x884, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS),
NUSEC_IOCTL_GET_NVRAM_GEOMETRY = CTL_CODE(0x22, 0x893, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS),
};
struct nusec_log_record {

View File

@ -277,11 +277,12 @@ static HRESULT vfs_path_hook(const wchar_t *src, wchar_t *dest, size_t *count)
const wchar_t *redir;
size_t required;
size_t redir_len;
size_t src_len;
assert(src != NULL);
assert(count != NULL);
if (src[0] == L'\0' || src[1] != L':' || !path_is_separator_w(src[2])) {
if (src[0] == L'\0' || src[1] != L':') {
return S_FALSE;
}
@ -304,10 +305,15 @@ static HRESULT vfs_path_hook(const wchar_t *src, wchar_t *dest, size_t *count)
return S_FALSE;
}
/* GetFileAttributesW would request the src "E:", so fix the src_len in
in order to redirect the drive letter successfully */
src_len = path_is_separator_w(src[2]) ? 3 : 2;
/* Cut off <prefix>\, replace with redir path, count NUL terminator */
redir_len = wcslen(redir);
required = wcslen(src) - 3 + redir_len + 1;
required = wcslen(src) - src_len + redir_len + 1;
if (dest != NULL) {
if (required > *count) {
@ -315,7 +321,7 @@ static HRESULT vfs_path_hook(const wchar_t *src, wchar_t *dest, size_t *count)
}
wcscpy_s(dest, *count, redir);
wcscpy_s(dest + redir_len, *count - redir_len, src + 3);
wcscpy_s(dest + redir_len, *count - redir_len, src + src_len);
}
*count = required;

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