mirror of
https://gitea.tendokyu.moe/TeamTofuShop/segatools.git
synced 2026-10-14 04:19:02 +03:00
refactor all common parts and games
This commit is contained in:
@@ -0,0 +1,182 @@
|
||||
#include <windows.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "hook/table.h"
|
||||
|
||||
#include "hooklib/dll.h"
|
||||
#include "hooklib/reg.h"
|
||||
|
||||
#include "platform/amvideo.h"
|
||||
|
||||
#include "util/dprintf.h"
|
||||
|
||||
/* Hook functions */
|
||||
|
||||
static HRESULT amvideo_reg_read_name(void *bytes, uint32_t *nbytes);
|
||||
static HRESULT amvideo_reg_read_port_X(void *bytes, uint32_t *nbytes);
|
||||
static HRESULT amvideo_reg_read_resolution_1(void *bytes, uint32_t *nbytes);
|
||||
static HRESULT amvideo_reg_read_setting(void *bytes, uint32_t *nbytes);
|
||||
static HRESULT amvideo_reg_read_use_segatiming(void *bytes, uint32_t *nbytes);
|
||||
|
||||
static const wchar_t amvideo_dll_name[] = L"$amvideo";
|
||||
|
||||
static const struct reg_hook_val amvideo_reg_vals[] = {
|
||||
{
|
||||
.name = L"name",
|
||||
.read = amvideo_reg_read_name,
|
||||
.type = REG_SZ,
|
||||
},
|
||||
{
|
||||
.name = L"name_x86",
|
||||
.read = amvideo_reg_read_name,
|
||||
.type = REG_SZ,
|
||||
}
|
||||
};
|
||||
|
||||
static const struct reg_hook_val amvideo_reg_mode_vals[] = {
|
||||
{
|
||||
.name = L"monitor_setting_1",
|
||||
.read = amvideo_reg_read_setting,
|
||||
.type = REG_SZ,
|
||||
}, {
|
||||
.name = L"monitor_setting_2",
|
||||
.read = amvideo_reg_read_setting,
|
||||
.type = REG_SZ,
|
||||
}, {
|
||||
.name = L"port_1",
|
||||
.read = amvideo_reg_read_port_X,
|
||||
.type = REG_DWORD
|
||||
}, {
|
||||
.name = L"port_2",
|
||||
.read = amvideo_reg_read_port_X,
|
||||
.type = REG_DWORD
|
||||
}, {
|
||||
.name = L"port_3",
|
||||
.read = amvideo_reg_read_port_X,
|
||||
.type = REG_DWORD
|
||||
}, {
|
||||
.name = L"port_4",
|
||||
.read = amvideo_reg_read_port_X,
|
||||
.type = REG_DWORD
|
||||
}, {
|
||||
.name = L"port_5",
|
||||
.read = amvideo_reg_read_port_X,
|
||||
.type = REG_DWORD
|
||||
}, {
|
||||
.name = L"port_6",
|
||||
.read = amvideo_reg_read_port_X,
|
||||
.type = REG_DWORD
|
||||
}, {
|
||||
.name = L"port_7",
|
||||
.read = amvideo_reg_read_port_X,
|
||||
.type = REG_DWORD
|
||||
}, {
|
||||
.name = L"port_8",
|
||||
.read = amvideo_reg_read_port_X,
|
||||
.type = REG_DWORD
|
||||
}, {
|
||||
.name = L"resolution_1",
|
||||
.read = amvideo_reg_read_resolution_1,
|
||||
.type = REG_SZ,
|
||||
}, {
|
||||
.name = L"use_segatiming",
|
||||
.read = amvideo_reg_read_use_segatiming,
|
||||
.type = REG_DWORD,
|
||||
}
|
||||
};
|
||||
|
||||
HRESULT amvideo_hook_init(const struct amvideo_config *cfg, HMODULE redir_mod)
|
||||
{
|
||||
HRESULT hr;
|
||||
|
||||
assert(cfg != NULL);
|
||||
|
||||
if (!cfg->enable) {
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
hr = reg_hook_push_key(
|
||||
HKEY_LOCAL_MACHINE,
|
||||
L"SYSTEM\\SEGA\\SystemProperty\\amVideo",
|
||||
amvideo_reg_vals,
|
||||
_countof(amvideo_reg_vals));
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
hr = reg_hook_push_key(
|
||||
HKEY_LOCAL_MACHINE,
|
||||
L"SYSTEM\\SEGA\\SystemProperty\\sgsetdisplaysetting\\CurrentSetting",
|
||||
amvideo_reg_mode_vals,
|
||||
_countof(amvideo_reg_mode_vals));
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
hr = dll_hook_push(redir_mod, amvideo_dll_name);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
int amDllVideoOpen(void *ctx)
|
||||
{
|
||||
dprintf("AmVideo: %s\n", __func__);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int amDllVideoClose(void *ctx)
|
||||
{
|
||||
dprintf("AmVideo: %s\n", __func__);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int amDllVideoSetResolution(void *ctx, void *param)
|
||||
{
|
||||
dprintf("AmVideo: %s\n", __func__);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int amDllVideoGetVBiosVersion(void *ctx, char *dest, size_t nchars)
|
||||
{
|
||||
dprintf("AmVideo: %s\n", __func__);
|
||||
strcpy(dest, "01.02.03.04.05");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static HRESULT amvideo_reg_read_name(void *bytes, uint32_t *nbytes)
|
||||
{
|
||||
return reg_hook_read_wstr(bytes, nbytes, amvideo_dll_name);
|
||||
}
|
||||
|
||||
static HRESULT amvideo_reg_read_port_X(void *bytes, uint32_t *nbytes)
|
||||
{
|
||||
return reg_hook_read_u32(bytes, nbytes, 1);
|
||||
}
|
||||
|
||||
static HRESULT amvideo_reg_read_resolution_1(void *bytes, uint32_t *nbytes)
|
||||
{
|
||||
return reg_hook_read_wstr(bytes, nbytes, L"1920x1080");
|
||||
}
|
||||
|
||||
static HRESULT amvideo_reg_read_setting(void *bytes, uint32_t *nbytes)
|
||||
{
|
||||
return reg_hook_read_wstr(bytes, nbytes, L"0");
|
||||
}
|
||||
|
||||
static HRESULT amvideo_reg_read_use_segatiming(void *bytes, uint32_t *nbytes)
|
||||
{
|
||||
return reg_hook_read_u32(bytes, nbytes, 0);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
struct amvideo_config {
|
||||
bool enable;
|
||||
};
|
||||
|
||||
HRESULT amvideo_hook_init(const struct amvideo_config *cfg, HMODULE redir_mod);
|
||||
@@ -0,0 +1,269 @@
|
||||
#include <windows.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "hook/table.h"
|
||||
#include "hook/procaddr.h"
|
||||
|
||||
#include "platform/clock.h"
|
||||
|
||||
#include "util/dprintf.h"
|
||||
|
||||
static void WINAPI my_GetSystemTimeAsFileTime(FILETIME *out);
|
||||
static BOOL WINAPI my_GetLocalTime(SYSTEMTIME *out);
|
||||
static BOOL WINAPI my_GetSystemTime(SYSTEMTIME *out);
|
||||
static DWORD WINAPI my_GetTimeZoneInformation(TIME_ZONE_INFORMATION *tzinfo);
|
||||
static BOOL WINAPI my_SetLocalTime(SYSTEMTIME *in);
|
||||
static BOOL WINAPI my_SetSystemTime(SYSTEMTIME *in);
|
||||
static BOOL WINAPI my_SetTimeZoneInformation(TIME_ZONE_INFORMATION *tzinfo);
|
||||
|
||||
static BOOL (WINAPI * next_GetSystemTimeAsFileTime)(FILETIME *out);
|
||||
static int64_t clock_current_day;
|
||||
static bool clock_timezone;
|
||||
static bool clock_time_warp;
|
||||
static bool clock_writeable;
|
||||
|
||||
static const struct hook_symbol clock_base_hook_syms[] = {
|
||||
{
|
||||
.name = "GetSystemTimeAsFileTime",
|
||||
.patch = my_GetSystemTimeAsFileTime,
|
||||
.link = (void **) &next_GetSystemTimeAsFileTime,
|
||||
}
|
||||
};
|
||||
|
||||
static const struct hook_symbol clock_read_hook_syms[] = {
|
||||
{
|
||||
.name = "GetLocalTime",
|
||||
.patch = my_GetLocalTime,
|
||||
}, {
|
||||
.name = "GetSystemTime",
|
||||
.patch = my_GetSystemTime,
|
||||
}, {
|
||||
.name = "GetTimeZoneInformation",
|
||||
.patch = my_GetTimeZoneInformation,
|
||||
},
|
||||
};
|
||||
|
||||
static const struct hook_symbol clock_write_hook_syms[] = {
|
||||
{
|
||||
.name = "SetLocalTime",
|
||||
.patch = my_SetLocalTime,
|
||||
}, {
|
||||
.name = "SetSystemTime",
|
||||
.patch = my_SetSystemTime,
|
||||
}, {
|
||||
.name = "SetTimeZoneInformation",
|
||||
.patch = my_SetTimeZoneInformation,
|
||||
},
|
||||
};
|
||||
|
||||
/* FILETIME is expressed in 100ns i.e. 0.1us i.e. 10^-7 sec units.
|
||||
No official name for these units is given so let's call them "jiffies". */
|
||||
|
||||
#define jiffies_per_sec 10000000LL
|
||||
#define jiffies_per_hour (jiffies_per_sec * 3600LL)
|
||||
#define jiffies_per_day (jiffies_per_hour * 24LL)
|
||||
|
||||
static void WINAPI my_GetSystemTimeAsFileTime(FILETIME *out)
|
||||
{
|
||||
FILETIME in;
|
||||
int64_t day;
|
||||
int64_t real_jiffies;
|
||||
int64_t real_jiffies_biased;
|
||||
int64_t real_time;
|
||||
int64_t fake_time;
|
||||
int64_t fake_jiffies_biased;
|
||||
int64_t fake_jiffies;
|
||||
|
||||
if (!clock_time_warp) {
|
||||
next_GetSystemTimeAsFileTime(out);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (out == NULL) {
|
||||
SetLastError(ERROR_INVALID_PARAMETER);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* Get and convert real jiffies */
|
||||
|
||||
next_GetSystemTimeAsFileTime(&in);
|
||||
real_jiffies = (((int64_t) in.dwHighDateTime) << 32) | in.dwLowDateTime;
|
||||
|
||||
/* Keepout period is JST [02:00, 07:00), which is equivalent to
|
||||
UTC [17:00, 22:00). Bias UTC forward by 2 hours, changing this interval
|
||||
to [19:00, 00:00) to make the math easier. We revert this bias later. */
|
||||
|
||||
real_jiffies_biased = real_jiffies + 2LL * jiffies_per_hour;
|
||||
|
||||
/* Split date and time */
|
||||
|
||||
day = real_jiffies_biased / jiffies_per_day;
|
||||
real_time = real_jiffies_biased % jiffies_per_day;
|
||||
|
||||
/* Debug log */
|
||||
|
||||
if (clock_current_day != 0 && clock_current_day != day) {
|
||||
dprintf("\n*** CLOCK JUMP! ***\n\n");
|
||||
}
|
||||
|
||||
clock_current_day = day;
|
||||
|
||||
/* We want to skip the final five hours of our UTC+2 biased reference frame,
|
||||
so scale time-of-day by 19/24. */
|
||||
|
||||
fake_time = (real_time * 19LL) / 24LL;
|
||||
|
||||
/* Un-split date and time */
|
||||
|
||||
fake_jiffies_biased = day * jiffies_per_day + fake_time;
|
||||
|
||||
/* Revert bias */
|
||||
|
||||
fake_jiffies = fake_jiffies_biased - 2LL * jiffies_per_hour;
|
||||
|
||||
/* Return result */
|
||||
|
||||
out->dwLowDateTime = fake_jiffies;
|
||||
out->dwHighDateTime = fake_jiffies >> 32;
|
||||
}
|
||||
|
||||
static BOOL WINAPI my_GetLocalTime(SYSTEMTIME *out)
|
||||
{
|
||||
ULARGE_INTEGER arith;
|
||||
FILETIME linear;
|
||||
|
||||
/* Force JST */
|
||||
|
||||
my_GetSystemTimeAsFileTime(&linear);
|
||||
|
||||
arith.LowPart = linear.dwLowDateTime;
|
||||
arith.HighPart = linear.dwHighDateTime;
|
||||
arith.QuadPart += 9ULL * jiffies_per_hour;
|
||||
linear.dwLowDateTime = arith.LowPart;
|
||||
linear.dwHighDateTime = arith.HighPart;
|
||||
|
||||
return FileTimeToSystemTime(&linear, out);
|
||||
}
|
||||
|
||||
static BOOL WINAPI my_GetSystemTime(SYSTEMTIME *out)
|
||||
{
|
||||
FILETIME linear;
|
||||
BOOL ok;
|
||||
|
||||
my_GetSystemTimeAsFileTime(&linear);
|
||||
ok = FileTimeToSystemTime(&linear, out);
|
||||
|
||||
if (!ok) {
|
||||
return ok;
|
||||
}
|
||||
|
||||
#if defined(LOG_CLOCK)
|
||||
static int last_second;
|
||||
|
||||
if (out->wSecond != last_second) {
|
||||
dprintf("%04i/%02i/%02i %02i:%02i:%02i\n",
|
||||
out->wYear,
|
||||
out->wMonth,
|
||||
out->wDay,
|
||||
out->wHour,
|
||||
out->wMinute,
|
||||
out->wSecond);
|
||||
}
|
||||
|
||||
last_second = out->wSecond;
|
||||
#endif
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static DWORD WINAPI my_GetTimeZoneInformation(TIME_ZONE_INFORMATION *tzinfo)
|
||||
{
|
||||
dprintf("Clock: Returning JST timezone\n");
|
||||
|
||||
if (tzinfo == NULL) {
|
||||
SetLastError(ERROR_INVALID_PARAMETER);
|
||||
|
||||
return TIME_ZONE_ID_INVALID;
|
||||
}
|
||||
|
||||
/* Force JST (UTC+9), SEGA games malfunction in any other time zone.
|
||||
Strings and boundary times don't matter, we only set the offset. */
|
||||
|
||||
memset(tzinfo, 0, sizeof(*tzinfo));
|
||||
tzinfo->Bias = -9 * 60;
|
||||
|
||||
SetLastError(ERROR_SUCCESS);
|
||||
|
||||
/* "Unknown" here means that this region does not observe DST */
|
||||
|
||||
return TIME_ZONE_ID_UNKNOWN;
|
||||
}
|
||||
|
||||
static BOOL WINAPI my_SetLocalTime(SYSTEMTIME *in)
|
||||
{
|
||||
dprintf("Clock: Blocked local time update\n");
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static BOOL WINAPI my_SetSystemTime(SYSTEMTIME *in)
|
||||
{
|
||||
dprintf("Clock: Blocked system time update\n");
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static BOOL WINAPI my_SetTimeZoneInformation(TIME_ZONE_INFORMATION *in)
|
||||
{
|
||||
dprintf("Clock: Blocked timezone update\n");
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
HRESULT clock_hook_init(const struct clock_config *cfg)
|
||||
{
|
||||
assert(cfg != NULL);
|
||||
|
||||
clock_timezone = cfg->timezone;
|
||||
clock_time_warp = cfg->timewarp;
|
||||
clock_writeable = cfg->writeable;
|
||||
|
||||
clock_hook_insert_hooks(NULL);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
void clock_hook_insert_hooks(HMODULE target) {
|
||||
if (clock_timezone || clock_time_warp || !clock_writeable) {
|
||||
/* All the clock hooks require the core GSTAFT hook to be installed */
|
||||
/* Note the ! up there btw. */
|
||||
|
||||
hook_table_apply(
|
||||
target,
|
||||
"kernel32.dll",
|
||||
clock_base_hook_syms,
|
||||
_countof(clock_base_hook_syms));
|
||||
}
|
||||
|
||||
if (clock_timezone) {
|
||||
hook_table_apply(
|
||||
target,
|
||||
"kernel32.dll",
|
||||
clock_read_hook_syms,
|
||||
_countof(clock_read_hook_syms));
|
||||
}
|
||||
|
||||
if (!clock_writeable) {
|
||||
/* Install hook if this config parameter is FALSE! */
|
||||
hook_table_apply(
|
||||
target,
|
||||
"kernel32.dll",
|
||||
clock_write_hook_syms,
|
||||
_countof(clock_write_hook_syms));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
struct clock_config {
|
||||
bool timezone;
|
||||
bool timewarp;
|
||||
bool writeable;
|
||||
};
|
||||
|
||||
HRESULT clock_hook_init(const struct clock_config *cfg);
|
||||
void clock_hook_insert_hooks(HMODULE target);
|
||||
@@ -0,0 +1,364 @@
|
||||
#include <windows.h>
|
||||
#include <winsock2.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "platform/amvideo.h"
|
||||
#include "platform/clock.h"
|
||||
#include "platform/config.h"
|
||||
#include "platform/dns.h"
|
||||
#include "platform/epay.h"
|
||||
#include "platform/hwmon.h"
|
||||
#include "platform/hwreset.h"
|
||||
#include "platform/misc.h"
|
||||
#include "platform/netenv.h"
|
||||
#include "platform/nusec.h"
|
||||
#include "platform/pcbid.h"
|
||||
#include "platform/platform.h"
|
||||
#include "platform/vfs.h"
|
||||
#include "platform/system.h"
|
||||
|
||||
void platform_config_load(struct platform_config *cfg, const wchar_t *filename)
|
||||
{
|
||||
assert(cfg != NULL);
|
||||
assert(filename != NULL);
|
||||
|
||||
amvideo_config_load(&cfg->amvideo, filename);
|
||||
clock_config_load(&cfg->clock, filename);
|
||||
dns_config_load(&cfg->dns, filename);
|
||||
epay_config_load(&cfg->epay, filename);
|
||||
hwmon_config_load(&cfg->hwmon, filename);
|
||||
hwreset_config_load(&cfg->hwreset, filename);
|
||||
misc_config_load(&cfg->misc, filename);
|
||||
pcbid_config_load(&cfg->pcbid, filename);
|
||||
netenv_config_load(&cfg->netenv, filename);
|
||||
nusec_config_load(&cfg->nusec, filename);
|
||||
vfs_config_load(&cfg->vfs, filename);
|
||||
system_config_load(&cfg->system, filename);
|
||||
}
|
||||
|
||||
void amvideo_config_load(struct amvideo_config *cfg, const wchar_t *filename)
|
||||
{
|
||||
assert(cfg != NULL);
|
||||
assert(filename != NULL);
|
||||
|
||||
cfg->enable = GetPrivateProfileIntW(L"amvideo", L"enable", 1, filename);
|
||||
}
|
||||
|
||||
void clock_config_load(struct clock_config *cfg, const wchar_t *filename)
|
||||
{
|
||||
assert(cfg != NULL);
|
||||
assert(filename != NULL);
|
||||
|
||||
cfg->timezone = GetPrivateProfileIntW(L"clock", L"timezone", 1, filename);
|
||||
cfg->timewarp = GetPrivateProfileIntW(L"clock", L"timewarp", 0, filename);
|
||||
cfg->writeable = GetPrivateProfileIntW(
|
||||
L"clock",
|
||||
L"writeable",
|
||||
0,
|
||||
filename);
|
||||
}
|
||||
|
||||
void dns_config_load(struct dns_config *cfg, const wchar_t *filename)
|
||||
{
|
||||
wchar_t default_[128];
|
||||
|
||||
assert(cfg != NULL);
|
||||
assert(filename != NULL);
|
||||
|
||||
cfg->enable = GetPrivateProfileIntW(L"dns", L"enable", 1, filename);
|
||||
|
||||
GetPrivateProfileStringW(
|
||||
L"dns",
|
||||
L"default",
|
||||
L"localhost",
|
||||
default_,
|
||||
_countof(default_),
|
||||
filename);
|
||||
|
||||
GetPrivateProfileStringW(
|
||||
L"dns",
|
||||
L"router",
|
||||
default_,
|
||||
cfg->router,
|
||||
_countof(cfg->router),
|
||||
filename);
|
||||
|
||||
GetPrivateProfileStringW(
|
||||
L"dns",
|
||||
L"startup",
|
||||
default_,
|
||||
cfg->startup,
|
||||
_countof(cfg->startup),
|
||||
filename);
|
||||
|
||||
GetPrivateProfileStringW(
|
||||
L"dns",
|
||||
L"billing",
|
||||
default_,
|
||||
cfg->billing,
|
||||
_countof(cfg->billing),
|
||||
filename);
|
||||
|
||||
GetPrivateProfileStringW(
|
||||
L"dns",
|
||||
L"aimedb",
|
||||
default_,
|
||||
cfg->aimedb,
|
||||
_countof(cfg->aimedb),
|
||||
filename);
|
||||
|
||||
GetPrivateProfileStringW(
|
||||
L"dns",
|
||||
L"title",
|
||||
L"title",
|
||||
cfg->title,
|
||||
_countof(cfg->title),
|
||||
filename);
|
||||
|
||||
cfg->replaceHost = GetPrivateProfileIntW(L"dns", L"replaceHost", 0, filename);
|
||||
|
||||
cfg->startupPort = GetPrivateProfileIntW(L"dns", L"startupPort", 0, filename);
|
||||
cfg->billingPort = GetPrivateProfileIntW(L"dns", L"billingPort", 0, filename);
|
||||
cfg->aimedbPort = GetPrivateProfileIntW(L"dns", L"aimedbPort", 0, filename);
|
||||
}
|
||||
|
||||
void hwmon_config_load(struct hwmon_config *cfg, const wchar_t *filename)
|
||||
{
|
||||
assert(cfg != NULL);
|
||||
assert(filename != NULL);
|
||||
|
||||
cfg->enable = GetPrivateProfileIntW(L"hwmon", L"enable", 1, filename);
|
||||
}
|
||||
|
||||
void hwreset_config_load(struct hwreset_config *cfg, const wchar_t *filename)
|
||||
{
|
||||
assert(cfg != NULL);
|
||||
assert(filename != NULL);
|
||||
|
||||
cfg->enable = GetPrivateProfileIntW(L"hwreset", L"enable", 1, filename);
|
||||
}
|
||||
|
||||
void misc_config_load(struct misc_config *cfg, const wchar_t *filename)
|
||||
{
|
||||
assert(cfg != NULL);
|
||||
assert(filename != NULL);
|
||||
|
||||
cfg->enable = GetPrivateProfileIntW(L"misc", L"enable", 1, filename);
|
||||
}
|
||||
|
||||
void netenv_config_load(struct netenv_config *cfg, const wchar_t *filename)
|
||||
{
|
||||
wchar_t mac_addr[18];
|
||||
|
||||
assert(cfg != NULL);
|
||||
assert(filename != NULL);
|
||||
|
||||
memset(cfg, 0, sizeof(*cfg));
|
||||
|
||||
cfg->enable = GetPrivateProfileIntW(L"netenv", L"enable", 0, filename);
|
||||
|
||||
cfg->addr_suffix = GetPrivateProfileIntW(
|
||||
L"netenv",
|
||||
L"addrSuffix",
|
||||
11,
|
||||
filename);
|
||||
|
||||
cfg->router_suffix = GetPrivateProfileIntW(
|
||||
L"netenv",
|
||||
L"routerSuffix",
|
||||
254,
|
||||
filename);
|
||||
|
||||
GetPrivateProfileStringW(
|
||||
L"netenv",
|
||||
L"macAddr",
|
||||
L"01:02:03:04:05:06",
|
||||
mac_addr,
|
||||
_countof(mac_addr),
|
||||
filename);
|
||||
|
||||
swscanf(mac_addr,
|
||||
L"%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
|
||||
&cfg->mac_addr[0],
|
||||
&cfg->mac_addr[1],
|
||||
&cfg->mac_addr[2],
|
||||
&cfg->mac_addr[3],
|
||||
&cfg->mac_addr[4],
|
||||
&cfg->mac_addr[5],
|
||||
&cfg->mac_addr[6]);
|
||||
}
|
||||
|
||||
void nusec_config_load(struct nusec_config *cfg, const wchar_t *filename)
|
||||
{
|
||||
wchar_t keychip_id[17];
|
||||
wchar_t game_id[5];
|
||||
wchar_t platform_id[5];
|
||||
wchar_t subnet[16];
|
||||
unsigned int ip[4];
|
||||
size_t i;
|
||||
|
||||
assert(cfg != NULL);
|
||||
assert(filename != NULL);
|
||||
|
||||
memset(cfg, 0, sizeof(*cfg));
|
||||
memset(keychip_id, 0, sizeof(keychip_id));
|
||||
memset(game_id, 0, sizeof(game_id));
|
||||
memset(platform_id, 0, sizeof(platform_id));
|
||||
memset(subnet, 0, sizeof(subnet));
|
||||
|
||||
cfg->enable = GetPrivateProfileIntW(L"keychip", L"enable", 1, filename);
|
||||
|
||||
GetPrivateProfileStringW(
|
||||
L"keychip",
|
||||
L"id",
|
||||
L"A69E-01A88888888",
|
||||
keychip_id,
|
||||
_countof(keychip_id),
|
||||
filename);
|
||||
|
||||
GetPrivateProfileStringW(
|
||||
L"keychip",
|
||||
L"gameId",
|
||||
L"",
|
||||
game_id,
|
||||
_countof(game_id),
|
||||
filename);
|
||||
|
||||
GetPrivateProfileStringW(
|
||||
L"keychip",
|
||||
L"platformId",
|
||||
L"",
|
||||
platform_id,
|
||||
_countof(platform_id),
|
||||
filename);
|
||||
|
||||
cfg->region = GetPrivateProfileIntW(L"keychip", L"region", 1, filename);
|
||||
cfg->billing_type = GetPrivateProfileIntW(L"keychip", L"billingType", 1, filename);
|
||||
cfg->system_flag = GetPrivateProfileIntW(
|
||||
L"keychip",
|
||||
L"systemFlag",
|
||||
0x64,
|
||||
filename);
|
||||
|
||||
GetPrivateProfileStringW(
|
||||
L"keychip",
|
||||
L"subnet",
|
||||
L"192.168.100.0",
|
||||
subnet,
|
||||
_countof(subnet),
|
||||
filename);
|
||||
|
||||
for (i = 0 ; i < 16 ; i++) {
|
||||
cfg->keychip_id[i] = (char) keychip_id[i];
|
||||
}
|
||||
|
||||
for (i = 0 ; i < 4 ; i++) {
|
||||
cfg->game_id[i] = (char) game_id[i];
|
||||
}
|
||||
|
||||
for (i = 0 ; i < 4 ; i++) {
|
||||
cfg->platform_id[i] = (char) platform_id[i];
|
||||
}
|
||||
|
||||
swscanf(subnet, L"%u.%u.%u.%u", &ip[0], &ip[1], &ip[2], &ip[3]);
|
||||
cfg->subnet = (ip[0] << 24) | (ip[1] << 16) | (ip[2] << 8) | 0;
|
||||
|
||||
GetPrivateProfileStringW(
|
||||
L"keychip",
|
||||
L"billingCa",
|
||||
L"DEVICE\\ca.crt",
|
||||
cfg->billing_ca,
|
||||
_countof(cfg->billing_ca),
|
||||
filename);
|
||||
|
||||
GetPrivateProfileStringW(
|
||||
L"keychip",
|
||||
L"billingPub",
|
||||
L"DEVICE\\billing.pub",
|
||||
cfg->billing_pub,
|
||||
_countof(cfg->billing_pub),
|
||||
filename);
|
||||
}
|
||||
|
||||
void pcbid_config_load(struct pcbid_config *cfg, const wchar_t *filename)
|
||||
{
|
||||
assert(cfg != NULL);
|
||||
assert(filename != NULL);
|
||||
|
||||
cfg->enable = GetPrivateProfileIntW(L"pcbid", L"enable", 1, filename);
|
||||
|
||||
GetPrivateProfileStringW(
|
||||
L"pcbid",
|
||||
L"serialNo",
|
||||
L"ACAE01A99999999",
|
||||
cfg->serial_no,
|
||||
_countof(cfg->serial_no),
|
||||
filename);
|
||||
}
|
||||
|
||||
void vfs_config_load(struct vfs_config *cfg, const wchar_t *filename)
|
||||
{
|
||||
assert(cfg != NULL);
|
||||
assert(filename != NULL);
|
||||
|
||||
cfg->enable = GetPrivateProfileIntW(L"vfs", L"enable", 1, filename);
|
||||
|
||||
GetPrivateProfileStringW(
|
||||
L"vfs",
|
||||
L"amfs",
|
||||
L"",
|
||||
cfg->amfs,
|
||||
_countof(cfg->amfs),
|
||||
filename);
|
||||
|
||||
GetPrivateProfileStringW(
|
||||
L"vfs",
|
||||
L"appdata",
|
||||
L"",
|
||||
cfg->appdata,
|
||||
_countof(cfg->appdata),
|
||||
filename);
|
||||
|
||||
GetPrivateProfileStringW(
|
||||
L"vfs",
|
||||
L"option",
|
||||
L"",
|
||||
cfg->option,
|
||||
_countof(cfg->option),
|
||||
filename);
|
||||
}
|
||||
|
||||
void system_config_load(struct system_config *cfg, const wchar_t *filename)
|
||||
{
|
||||
wchar_t name[7];
|
||||
size_t i;
|
||||
|
||||
assert(cfg != NULL);
|
||||
assert(filename != NULL);
|
||||
|
||||
cfg->enable = GetPrivateProfileIntW(L"system", L"enable", 0, filename);
|
||||
cfg->freeplay = GetPrivateProfileIntW(L"system", L"freeplay", 0, filename);
|
||||
|
||||
wcscpy_s(name, _countof(name), L"dipsw0");
|
||||
|
||||
for (i = 0 ; i < 8 ; i++) {
|
||||
name[5] = L'1' + i;
|
||||
cfg->dipsw[i] = GetPrivateProfileIntW(L"system", name, 0, filename);
|
||||
}
|
||||
}
|
||||
|
||||
void epay_config_load(struct epay_config *cfg, const wchar_t *filename)
|
||||
{
|
||||
assert(cfg != NULL);
|
||||
assert(filename != NULL);
|
||||
|
||||
cfg->enable = GetPrivateProfileIntW(L"epay", L"enable", 1, filename);
|
||||
cfg->hook = GetPrivateProfileIntW(L"epay", L"hook", 0, filename);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "platform/amvideo.h"
|
||||
#include "platform/clock.h"
|
||||
#include "platform/dns.h"
|
||||
#include "platform/epay.h"
|
||||
#include "platform/hwmon.h"
|
||||
#include "platform/hwreset.h"
|
||||
#include "platform/misc.h"
|
||||
#include "platform/netenv.h"
|
||||
#include "platform/nusec.h"
|
||||
#include "platform/pcbid.h"
|
||||
#include "platform/platform.h"
|
||||
#include "platform/vfs.h"
|
||||
#include "platform/system.h"
|
||||
|
||||
void platform_config_load(
|
||||
struct platform_config *cfg,
|
||||
const wchar_t *filename);
|
||||
|
||||
void amvideo_config_load(struct amvideo_config *cfg, const wchar_t *filename);
|
||||
void clock_config_load(struct clock_config *cfg, const wchar_t *filename);
|
||||
void dns_config_load(struct dns_config *cfg, const wchar_t *filename);
|
||||
void epay_config_load(struct epay_config *cfg, const wchar_t *filename);
|
||||
void hwmon_config_load(struct hwmon_config *cfg, const wchar_t *filename);
|
||||
void hwreset_config_load(struct hwreset_config *cfg, const wchar_t *filename);
|
||||
void misc_config_load(struct misc_config *cfg, const wchar_t *filename);
|
||||
void netenv_config_load(struct netenv_config *cfg, const wchar_t *filename);
|
||||
void nusec_config_load(struct nusec_config *cfg, const wchar_t *filename);
|
||||
void pcbid_config_load(struct pcbid_config *cfg, const wchar_t *filename);
|
||||
void vfs_config_load(struct vfs_config *cfg, const wchar_t *filename);
|
||||
void system_config_load(struct system_config *cfg, const wchar_t *filename);
|
||||
@@ -0,0 +1,189 @@
|
||||
#include <windows.h>
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include "hooklib/dns.h"
|
||||
|
||||
#include "platform/dns.h"
|
||||
|
||||
HRESULT dns_platform_hook_init(const struct dns_config *cfg)
|
||||
{
|
||||
HRESULT hr;
|
||||
|
||||
assert(cfg != NULL);
|
||||
|
||||
if (!cfg->enable) {
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
if (cfg->replaceHost){
|
||||
http_hook_init();
|
||||
}
|
||||
|
||||
if (cfg->startupPort || cfg->billingPort || cfg->aimedbPort){
|
||||
port_hook_init(cfg->startupPort, cfg->billingPort, cfg->aimedbPort);
|
||||
}
|
||||
|
||||
hr = dns_hook_push(L"tenporouter.loc", cfg->router);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
hr = dns_hook_push(L"bbrouter.loc", cfg->router);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
hr = dns_hook_push(L"naominet.jp", cfg->startup);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
hr = dns_hook_push(L"anbzvarg.wc", cfg->startup);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
hr = dns_hook_push(L"op.auth.sys-all.net", cfg->startup);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
hr = dns_hook_push(L"at.auth.sys-all.net", cfg->startup);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
hr = dns_hook_push(L"at.sys-all.net", cfg->startup);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
hr = dns_hook_push(L"ib.naominet.jp", cfg->billing);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
hr = dns_hook_push(L"vo.anbzvarg.wc", cfg->billing);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
hr = dns_hook_push(L"aime.naominet.jp", cfg->aimedb);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
hr = dns_hook_push(L"nvzr.anbzvarg.wc", cfg->aimedb);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
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;
|
||||
}
|
||||
|
||||
// WAHLAP PowerOn
|
||||
hr = dns_hook_push(L"at.sys-all.cn", cfg->startup);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
hr = dns_hook_push(L"at.sys-allnet.cn", cfg->startup);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
// WAHLAP WeChat AimeDB Server
|
||||
hr = dns_hook_push(L"ai.sys-all.cn", cfg->aimedb);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
hr = dns_hook_push(L"ai.sys-allnet.cn", cfg->aimedb);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
// WAHLAP Billing
|
||||
hr = dns_hook_push(L"bl.sys-all.cn", cfg->billing);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
hr = dns_hook_push(L"bl.sys-allnet.cn", cfg->billing);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
// if your ISP resolves bad domains, it will kill the network. These 2
|
||||
// *cannot* resolve
|
||||
|
||||
hr = dns_hook_push(L"mobirouter.loc", NULL);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
hr = dns_hook_push(L"dslrouter.loc", NULL);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
// Disable api/polling to the original servers
|
||||
|
||||
hr = dns_hook_push(L"*.amlog.sys-all.net", NULL);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
hr = dns_hook_push(L"*.d-amlog.sys-all.net", NULL);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
struct dns_config {
|
||||
bool enable;
|
||||
wchar_t router[128];
|
||||
wchar_t startup[128];
|
||||
wchar_t billing[128];
|
||||
wchar_t aimedb[128];
|
||||
wchar_t title[128];
|
||||
bool replaceHost;
|
||||
unsigned short startupPort;
|
||||
unsigned short billingPort;
|
||||
unsigned short aimedbPort;
|
||||
};
|
||||
|
||||
HRESULT dns_platform_hook_init(const struct dns_config *cfg);
|
||||
@@ -0,0 +1,366 @@
|
||||
#include <windows.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "hook/table.h"
|
||||
|
||||
#include "hooklib/dns.h"
|
||||
#include "hooklib/reg.h"
|
||||
|
||||
#include "platform/epay.h"
|
||||
|
||||
#include <hooklib/path.h>
|
||||
|
||||
#include "util/dprintf.h"
|
||||
|
||||
static HRESULT misc_read_thinca_adapter(void *bytes, uint32_t *nbytes);
|
||||
static HRESULT misc_read_ca_loc(void *bytes, uint32_t *nbytes);
|
||||
static HRESULT misc_read_ca_client_loc(void *bytes, uint32_t *nbytes);
|
||||
static HRESULT misc_read_network_timeout(void *bytes, uint32_t *nbytes);
|
||||
static HRESULT misc_read_pattern0(void *bytes, uint32_t *nbytes);
|
||||
static HRESULT misc_read_network_timeout0(void *bytes, uint32_t *nbytes);
|
||||
static HRESULT misc_read_pattern1(void *bytes, uint32_t *nbytes);
|
||||
static HRESULT misc_read_network_timeout1(void *bytes, uint32_t *nbytes);
|
||||
|
||||
static uint64_t thinca_initialize(struct thinca_impl * self, uint64_t val);
|
||||
static uint64_t thinca_dispose(struct thinca_impl * self);
|
||||
static uint64_t thinca_set_resource(struct thinca_impl * self, char * res);
|
||||
static uint64_t thinca_set_pay_log(struct thinca_impl * self, uint64_t val, char * log, uint64_t val2, const char * size_lim);
|
||||
static uint64_t thinca_set_client_log(struct thinca_impl * self, uint64_t val, char * log);
|
||||
static uint64_t thinca_set_client_cfg(struct thinca_impl * self, char * log, uint64_t val);
|
||||
static uint64_t thinca_set_goods_code(struct thinca_impl * self, char * code);
|
||||
static uint64_t thinca_set_evt_handler(struct thinca_impl * self, void* handler);
|
||||
static uint64_t thinca_set_cert(struct thinca_impl * self, char * cert, uint64_t val);
|
||||
static uint64_t thinca_set_serial(struct thinca_impl * self, char * cert);
|
||||
static uint64_t thinca_check_deal(struct thinca_impl * self, void* deal);
|
||||
static uint64_t thinca_cancel(struct thinca_impl * self);
|
||||
static uint64_t thinca_select(struct thinca_impl * self);
|
||||
static uint64_t thinca_unk(struct thinca_impl * self, uint64_t val);
|
||||
static void thinca_unk8(struct thinca_impl * self);
|
||||
|
||||
static uint64_t my_ThincaPaymentGetVersion();
|
||||
static uint64_t (*next_ThincaPaymentGetVersion)();
|
||||
|
||||
static struct thinca_main* my_ThincaPaymentGetInstance(uint64_t ver);
|
||||
static struct thinca_main* (*next_ThincaPaymentGetInstance)(uint64_t ver);
|
||||
|
||||
static struct thinca_main* thinca_stub;
|
||||
|
||||
static const struct reg_hook_val epay_adapter_keys[] = {
|
||||
{
|
||||
.name = L"TfpsAimeRwAdapter",
|
||||
.read = misc_read_thinca_adapter,
|
||||
.type = REG_SZ,
|
||||
}
|
||||
};
|
||||
|
||||
static const struct reg_hook_val epay_tcap_keys[] = {
|
||||
{
|
||||
.name = L"CaLocation",
|
||||
.read = misc_read_ca_loc,
|
||||
.type = REG_SZ,
|
||||
},
|
||||
{
|
||||
.name = L"ThincaTcapClientPath",
|
||||
.read = misc_read_ca_client_loc,
|
||||
.type = REG_SZ,
|
||||
},
|
||||
{
|
||||
.name = L"ClientNetworkTimeout",
|
||||
.read = misc_read_network_timeout,
|
||||
.type = REG_DWORD,
|
||||
}
|
||||
};
|
||||
|
||||
static const struct reg_hook_val epay_tcap_url0_keys[] = {
|
||||
{
|
||||
.name = L"Pattern",
|
||||
.read = misc_read_pattern0,
|
||||
.type = REG_SZ,
|
||||
},
|
||||
{
|
||||
.name = L"ClientNetworkTimeout",
|
||||
.read = misc_read_network_timeout0,
|
||||
.type = REG_DWORD,
|
||||
}
|
||||
};
|
||||
|
||||
static const struct reg_hook_val epay_tcap_url1_keys[] = {
|
||||
{
|
||||
.name = L"Pattern",
|
||||
.read = misc_read_pattern1,
|
||||
.type = REG_SZ,
|
||||
},
|
||||
{
|
||||
.name = L"ClientNetworkTimeout",
|
||||
.read = misc_read_network_timeout1,
|
||||
.type = REG_DWORD,
|
||||
}
|
||||
};
|
||||
|
||||
static const struct hook_symbol epay_syms[] = {
|
||||
{
|
||||
.name = "ThincaPaymentGetVersion",
|
||||
.patch = my_ThincaPaymentGetVersion,
|
||||
.link = (void **) &next_ThincaPaymentGetVersion,
|
||||
.ordinal = 1,
|
||||
},
|
||||
{
|
||||
.name = "__imp_ThincaPaymentGetInstance",
|
||||
.patch = my_ThincaPaymentGetInstance,
|
||||
.link = (void **) &next_ThincaPaymentGetInstance,
|
||||
.ordinal = 2,
|
||||
},
|
||||
{
|
||||
.name = "ThincaPaymentGetInstance",
|
||||
.patch = my_ThincaPaymentGetInstance,
|
||||
.link = (void **) &next_ThincaPaymentGetInstance,
|
||||
.ordinal = 2,
|
||||
}
|
||||
};
|
||||
|
||||
HRESULT epay_apply_registry_hooks(){
|
||||
HRESULT hr = reg_hook_push_key(
|
||||
HKEY_LOCAL_MACHINE,
|
||||
L"SOFTWARE\\TFPaymentService\\ThincaRwAdapter",
|
||||
epay_adapter_keys,
|
||||
_countof(epay_adapter_keys));
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
hr = reg_hook_push_key(
|
||||
HKEY_LOCAL_MACHINE,
|
||||
L"SOFTWARE\\TFPaymentService\\ThincaTcapClient",
|
||||
epay_tcap_keys,
|
||||
_countof(epay_tcap_keys));
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
hr = reg_hook_push_key(
|
||||
HKEY_LOCAL_MACHINE,
|
||||
L"SOFTWARE\\TFPaymentService\\ThincaTcapClient\\URL0",
|
||||
epay_tcap_url0_keys,
|
||||
_countof(epay_tcap_url0_keys));
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
hr = reg_hook_push_key(
|
||||
HKEY_LOCAL_MACHINE,
|
||||
L"SOFTWARE\\TFPaymentService\\ThincaTcapClient\\URL1",
|
||||
epay_tcap_url1_keys,
|
||||
_countof(epay_tcap_url1_keys));
|
||||
|
||||
return hr;
|
||||
}
|
||||
|
||||
HRESULT epay_hook_init(const struct epay_config *cfg) {
|
||||
HRESULT hr;
|
||||
assert(cfg != NULL);
|
||||
|
||||
if (!cfg->enable) {
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
hr = epay_apply_registry_hooks();
|
||||
if (FAILED(hr)){
|
||||
return hr;
|
||||
}
|
||||
|
||||
dprintf("EPay: Registry initialized\n");
|
||||
|
||||
// HACK:(?) the DLLs are loaded dynamically so we just preload it and apply DNS and VFS hooks to it
|
||||
HMODULE thincahttpclient = LoadLibraryA("thincahttpclient.dll");
|
||||
if (thincahttpclient != NULL){
|
||||
dns_hook_apply_hooks(thincahttpclient);
|
||||
path_hook_insert_hooks(thincahttpclient);
|
||||
}
|
||||
HMODULE thincapayment = LoadLibraryA("ThincaPayment.dll");
|
||||
if (thincapayment != NULL){
|
||||
path_hook_insert_hooks(thincapayment);
|
||||
}
|
||||
HMODULE thincatcapclient = LoadLibraryA("thincatcapclient.dll");
|
||||
if (thincatcapclient != NULL){
|
||||
path_hook_insert_hooks(thincatcapclient);
|
||||
}
|
||||
|
||||
if (cfg->hook) {
|
||||
hook_table_apply(
|
||||
NULL,
|
||||
"ThincaPayment.dll",
|
||||
epay_syms,
|
||||
_countof(epay_syms));
|
||||
|
||||
thinca_stub = (struct thinca_main *) malloc(sizeof(struct thinca_main));
|
||||
thinca_stub->impl1 = (struct thinca_impl *) malloc(sizeof(struct thinca_impl));
|
||||
|
||||
thinca_stub->impl1->unk8 = thinca_unk8;
|
||||
thinca_stub->impl1->initialize = thinca_initialize;
|
||||
thinca_stub->impl1->dispose = thinca_dispose;
|
||||
thinca_stub->impl1->setResource = thinca_set_resource;
|
||||
thinca_stub->impl1->setThincaPaymentLog = thinca_set_pay_log;
|
||||
thinca_stub->impl1->setThincaEventInterface = thinca_set_evt_handler;
|
||||
thinca_stub->impl1->setIcasClientLog = thinca_set_client_log;
|
||||
thinca_stub->impl1->setIcasClientConfig = thinca_set_client_cfg;
|
||||
thinca_stub->impl1->setGoodsCode = thinca_set_goods_code;
|
||||
thinca_stub->impl1->setTerminalSerial = thinca_set_serial;
|
||||
thinca_stub->impl1->setClientCertificate = thinca_set_cert;
|
||||
thinca_stub->impl1->checkDeal = thinca_check_deal;
|
||||
thinca_stub->impl1->cancelRequest = thinca_cancel;
|
||||
thinca_stub->impl1->selectButton = thinca_select;
|
||||
thinca_stub->impl1->unk220 = thinca_unk;
|
||||
thinca_stub->impl1->unk228 = thinca_unk;
|
||||
|
||||
dprintf("Epay: Hooks initialized\n");
|
||||
}
|
||||
|
||||
return hr;
|
||||
}
|
||||
|
||||
static HRESULT misc_read_thinca_adapter(void *bytes, uint32_t *nbytes)
|
||||
{
|
||||
return reg_hook_read_wstr(bytes, nbytes, L"aime_rw_adapterMD.dll");
|
||||
}
|
||||
|
||||
static HRESULT misc_read_ca_loc(void *bytes, uint32_t *nbytes)
|
||||
{
|
||||
return reg_hook_read_wstr(bytes, nbytes, L"ca.pem");
|
||||
}
|
||||
|
||||
static HRESULT misc_read_ca_client_loc(void *bytes, uint32_t *nbytes)
|
||||
{
|
||||
return reg_hook_read_wstr(bytes, nbytes, L"thincatcapclient.dll");
|
||||
}
|
||||
|
||||
static HRESULT misc_read_network_timeout(void *bytes, uint32_t *nbytes)
|
||||
{
|
||||
return reg_hook_read_u32(bytes, nbytes, 20000);
|
||||
}
|
||||
|
||||
static HRESULT misc_read_pattern0(void *bytes, uint32_t *nbytes)
|
||||
{
|
||||
return reg_hook_read_wstr(bytes, nbytes, L".*\\.jsp");
|
||||
}
|
||||
|
||||
static HRESULT misc_read_network_timeout0(void *bytes, uint32_t *nbytes)
|
||||
{
|
||||
return reg_hook_read_u32(bytes, nbytes, 5000);
|
||||
}
|
||||
|
||||
static HRESULT misc_read_pattern1(void *bytes, uint32_t *nbytes)
|
||||
{
|
||||
return reg_hook_read_wstr(bytes, nbytes, L".*(closing|remove).*");
|
||||
}
|
||||
|
||||
static HRESULT misc_read_network_timeout1(void *bytes, uint32_t *nbytes)
|
||||
{
|
||||
return reg_hook_read_u32(bytes, nbytes, 60000);
|
||||
}
|
||||
|
||||
static uint64_t thinca_initialize(struct thinca_impl * self, uint64_t val)
|
||||
{
|
||||
dprintf("Epay: Thinca Initialize %lld\n", val);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint64_t thinca_dispose(struct thinca_impl * self)
|
||||
{
|
||||
dprintf("Epay: Thinca Dispose\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint64_t thinca_set_resource(struct thinca_impl * self, char * res)
|
||||
{
|
||||
dprintf("Epay: Thinca Set Resource %s\n", res);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint64_t thinca_set_pay_log(struct thinca_impl * self, uint64_t val, char * log, uint64_t val2, const char * size_lim)
|
||||
{
|
||||
dprintf("Epay: Thinca Set Paylog %lld | %s | %lld | %s\n", val, log, val2, size_lim);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint64_t thinca_set_client_log(struct thinca_impl * self, uint64_t val, char * log)
|
||||
{
|
||||
dprintf("Epay: Thinca Set ICAS Client log %lld | %s\n", val, log);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint64_t thinca_set_client_cfg(struct thinca_impl * self, char * log, uint64_t val)
|
||||
{
|
||||
dprintf("Epay: Thinca Set ICAS Client Config %s | %lld\n", log, val);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint64_t thinca_set_goods_code(struct thinca_impl * self, char * code)
|
||||
{
|
||||
dprintf("Epay: Thinca Set Goods Code %s\n", code);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint64_t thinca_set_evt_handler(struct thinca_impl * self, void* handler)
|
||||
{
|
||||
dprintf("Epay: Thinca Set Event Handler %p\n", handler);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint64_t thinca_set_cert(struct thinca_impl * self, char * cert, uint64_t val)
|
||||
{
|
||||
dprintf("Epay: Thinca Set Client Cert %s | %lld\n", cert, val);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint64_t thinca_set_serial(struct thinca_impl * self, char * cert)
|
||||
{
|
||||
dprintf("Epay: Thinca Set Terminal Serial %s\n", cert);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint64_t thinca_check_deal(struct thinca_impl * self, void* deal)
|
||||
{
|
||||
dprintf("Epay: Thinca Check Deal %p\n", deal);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint64_t thinca_cancel(struct thinca_impl * self)
|
||||
{
|
||||
dprintf("Epay: Thinca Cancel\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint64_t thinca_select(struct thinca_impl * self)
|
||||
{
|
||||
dprintf("Epay: Thinca Select\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint64_t thinca_unk(struct thinca_impl * self, uint64_t val)
|
||||
{
|
||||
dprintf("Epay: Thinca Unknown 220/228 %lld\n", val);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void thinca_unk8(struct thinca_impl * self)
|
||||
{
|
||||
dprintf("Epay: Thinca Unknown 8\n");
|
||||
}
|
||||
|
||||
static uint64_t my_ThincaPaymentGetVersion()
|
||||
{
|
||||
return 0x1040B00;
|
||||
}
|
||||
|
||||
static struct thinca_main* my_ThincaPaymentGetInstance(uint64_t ver)
|
||||
{
|
||||
dprintf("Epay: my_ThincaPaymentGetInstance hit!\n");
|
||||
return thinca_stub;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#pragma pack(push,1)
|
||||
struct epay_config {
|
||||
bool enable;
|
||||
bool hook;
|
||||
};
|
||||
|
||||
/* The functions in these structs are how clients like amdaemon interface
|
||||
* with thinca. We can simply replace these functions with our own stubs
|
||||
* to bypass errors and such. Currently this DOES NOT allow for epay, and
|
||||
* trying to do so will most likely just lead to misery. My goal isn't to
|
||||
* reimplement epay, just to give amdaemon SOMETHING so we can boot properly.
|
||||
*/
|
||||
struct thinca_impl {
|
||||
uint64_t* unk0;
|
||||
void (*unk8)(struct thinca_impl *);
|
||||
uint64_t (*initialize)(struct thinca_impl *, uint64_t);
|
||||
uint64_t (*dispose)(struct thinca_impl *);
|
||||
uint64_t (*setResource)(struct thinca_impl *, char *);
|
||||
uint64_t (*setThincaPaymentLog)(struct thinca_impl *, uint64_t, char *, uint64_t, const char *);
|
||||
uint64_t (*setIcasClientLog)(struct thinca_impl *, uint64_t, char *);
|
||||
uint64_t (*setIcasClientConfig)(struct thinca_impl *, char *, uint64_t);
|
||||
uint64_t* unk40;
|
||||
uint64_t* unk48;
|
||||
uint64_t (*setClientCertificate)(struct thinca_impl *, char *, uint64_t);
|
||||
uint64_t (*setTerminalSerial)(struct thinca_impl *, char *);
|
||||
uint64_t (*setGoodsCode)(struct thinca_impl *, char *);
|
||||
uint64_t unk68;
|
||||
uint64_t (*setThincaEventInterface)(struct thinca_impl *, void*); // probably a struct
|
||||
uint64_t unkGap78[7];
|
||||
uint64_t (*checkDeal)(struct thinca_impl *, void *); // probably a struct
|
||||
uint64_t unkGapB8[41];
|
||||
uint64_t (*cancelRequest)(struct thinca_impl *);
|
||||
uint64_t (*selectButton)(struct thinca_impl *);
|
||||
uint64_t unkGap210[2];
|
||||
uint64_t (*unk220)(struct thinca_impl *, uint64_t);
|
||||
uint64_t (*unk228)(struct thinca_impl *, uint64_t);
|
||||
};
|
||||
|
||||
/* I believe the actual struct is 0x310 bytes, so for now I'm just
|
||||
* implementing what I need and hoping the rest don't cause issues
|
||||
* later. AMDaemon seems to only care about impl1 and deal_thing,
|
||||
* at least from what I can tell
|
||||
*/
|
||||
struct thinca_main {
|
||||
struct thinca_impl* impl1;
|
||||
struct thinca_impl* impl2;
|
||||
HANDLE* mutex1;
|
||||
HANDLE* mutex2;
|
||||
HANDLE* mutex3;
|
||||
uint64_t* unk28;
|
||||
uint64_t* unk30;
|
||||
uint64_t* unk38;
|
||||
uint64_t* unk40;
|
||||
uint64_t* deal_thing;
|
||||
uint64_t filler[88];
|
||||
};
|
||||
|
||||
#pragma pack(pop)
|
||||
HRESULT epay_hook_init(const struct epay_config *cfg);
|
||||
HRESULT epay_apply_registry_hooks();
|
||||
@@ -0,0 +1,107 @@
|
||||
#include <windows.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <winioctl.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "hook/iohook.h"
|
||||
|
||||
#include "platform/hwmon.h"
|
||||
|
||||
#include "util/dprintf.h"
|
||||
#include "util/str.h"
|
||||
|
||||
#define HWMON_IOCTL_READ_CPU_TEMP CTL_CODE(0x8000, 0x800, METHOD_BUFFERED, FILE_READ_ACCESS)
|
||||
|
||||
static HRESULT hwmon_handle_irp(struct irp *irp);
|
||||
static HRESULT hwmon_handle_open(struct irp *irp);
|
||||
static HRESULT hwmon_handle_close(struct irp *irp);
|
||||
static HRESULT hwmon_handle_ioctl(struct irp *irp);
|
||||
|
||||
static HRESULT hwmon_ioctl_read_cpu_temp(struct irp *irp);
|
||||
|
||||
static HANDLE hwmon_fd;
|
||||
|
||||
HRESULT hwmon_hook_init(const struct hwmon_config *cfg)
|
||||
{
|
||||
HRESULT hr;
|
||||
|
||||
assert(cfg != NULL);
|
||||
|
||||
if (!cfg->enable) {
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
hr = iohook_open_nul_fd(&hwmon_fd);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
hr = iohook_push_handler(hwmon_handle_irp);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static HRESULT hwmon_handle_irp(struct irp *irp)
|
||||
{
|
||||
assert(irp != NULL);
|
||||
|
||||
if (irp->op != IRP_OP_OPEN && irp->fd != hwmon_fd) {
|
||||
return iohook_invoke_next(irp);
|
||||
}
|
||||
|
||||
switch (irp->op) {
|
||||
case IRP_OP_OPEN: return hwmon_handle_open(irp);
|
||||
case IRP_OP_CLOSE: return hwmon_handle_close(irp);
|
||||
case IRP_OP_IOCTL: return hwmon_handle_ioctl(irp);
|
||||
default: return HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION);
|
||||
}
|
||||
}
|
||||
|
||||
static HRESULT hwmon_handle_open(struct irp *irp)
|
||||
{
|
||||
if (!wstr_ieq(irp->open_filename, L"\\\\.\\sghwmonitor")) {
|
||||
return iohook_invoke_next(irp);
|
||||
}
|
||||
|
||||
dprintf("Hwmon: Opened device\n");
|
||||
irp->fd = hwmon_fd;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static HRESULT hwmon_handle_close(struct irp *irp)
|
||||
{
|
||||
dprintf("Hwmon: Closed device\n");
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static HRESULT hwmon_handle_ioctl(struct irp *irp)
|
||||
{
|
||||
switch (irp->ioctl) {
|
||||
case HWMON_IOCTL_READ_CPU_TEMP:
|
||||
return hwmon_ioctl_read_cpu_temp(irp);
|
||||
|
||||
default:
|
||||
dprintf("Hwmon: Unknown ioctl %08x, write %i read %i\n",
|
||||
irp->ioctl,
|
||||
(int) irp->write.nbytes,
|
||||
(int) irp->read.nbytes);
|
||||
|
||||
return HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION);
|
||||
}
|
||||
}
|
||||
|
||||
static HRESULT hwmon_ioctl_read_cpu_temp(struct irp *irp)
|
||||
{
|
||||
/* Assuming this is Celsius. It also seems to be biased by -23 (based on the
|
||||
caller code) despite being a 32-bit int. */
|
||||
|
||||
return iobuf_write_le32(&irp->read, 52);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
struct hwmon_config {
|
||||
bool enable;
|
||||
};
|
||||
|
||||
HRESULT hwmon_hook_init(const struct hwmon_config *cfg);
|
||||
@@ -0,0 +1,106 @@
|
||||
#include <windows.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <winioctl.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "hook/iohook.h"
|
||||
|
||||
#include "platform/hwreset.h"
|
||||
|
||||
#include "util/dprintf.h"
|
||||
#include "util/str.h"
|
||||
|
||||
#define HWRESET_IOCTL_RESTART CTL_CODE(0x8000, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||
|
||||
static HRESULT hwreset_handle_irp(struct irp *irp);
|
||||
static HRESULT hwreset_handle_open(struct irp *irp);
|
||||
static HRESULT hwreset_handle_close(struct irp *irp);
|
||||
static HRESULT hwreset_handle_ioctl(struct irp *irp);
|
||||
|
||||
static HRESULT hwreset_ioctl_restart(struct irp *irp);
|
||||
|
||||
static HANDLE hwreset_fd;
|
||||
|
||||
HRESULT hwreset_hook_init(const struct hwreset_config *cfg)
|
||||
{
|
||||
HRESULT hr;
|
||||
|
||||
assert(cfg != NULL);
|
||||
|
||||
if (!cfg->enable) {
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
hr = iohook_open_nul_fd(&hwreset_fd);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
hr = iohook_push_handler(hwreset_handle_irp);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static HRESULT hwreset_handle_irp(struct irp *irp)
|
||||
{
|
||||
assert(irp != NULL);
|
||||
|
||||
if (irp->op != IRP_OP_OPEN && irp->fd != hwreset_fd) {
|
||||
return iohook_invoke_next(irp);
|
||||
}
|
||||
|
||||
switch (irp->op) {
|
||||
case IRP_OP_OPEN: return hwreset_handle_open(irp);
|
||||
case IRP_OP_CLOSE: return hwreset_handle_close(irp);
|
||||
case IRP_OP_IOCTL: return hwreset_handle_ioctl(irp);
|
||||
default: return HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION);
|
||||
}
|
||||
}
|
||||
|
||||
static HRESULT hwreset_handle_open(struct irp *irp)
|
||||
{
|
||||
if (!wstr_ieq(irp->open_filename, L"\\\\.\\sghwreset")) {
|
||||
return iohook_invoke_next(irp);
|
||||
}
|
||||
|
||||
dprintf("Hwreset: Opened device\n");
|
||||
irp->fd = hwreset_fd;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static HRESULT hwreset_handle_close(struct irp *irp)
|
||||
{
|
||||
dprintf("Hwreset: Closed device\n");
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static HRESULT hwreset_handle_ioctl(struct irp *irp)
|
||||
{
|
||||
switch (irp->ioctl) {
|
||||
case HWRESET_IOCTL_RESTART:
|
||||
return hwreset_ioctl_restart(irp);
|
||||
|
||||
default:
|
||||
dprintf("Hwreset: Unknown ioctl %08x, write %i read %i\n",
|
||||
irp->ioctl,
|
||||
(int) irp->write.nbytes,
|
||||
(int) irp->read.nbytes);
|
||||
|
||||
return HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION);
|
||||
}
|
||||
}
|
||||
|
||||
static HRESULT hwreset_ioctl_restart(struct irp *irp)
|
||||
{
|
||||
dprintf("Hwreset: Reset requested\n");
|
||||
|
||||
return iobuf_write_le32(&irp->read, 1);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
struct hwreset_config {
|
||||
bool enable;
|
||||
};
|
||||
|
||||
HRESULT hwreset_hook_init(const struct hwreset_config *cfg);
|
||||
@@ -0,0 +1,39 @@
|
||||
platform_lib = static_library(
|
||||
'platform',
|
||||
include_directories : inc,
|
||||
implicit_include_directories : false,
|
||||
dependencies : [
|
||||
capnhook.get_variable('hook_dep'),
|
||||
shlwapi_lib,
|
||||
],
|
||||
sources : [
|
||||
'amvideo.c',
|
||||
'amvideo.h',
|
||||
'clock.c',
|
||||
'clock.h',
|
||||
'config.c',
|
||||
'config.h',
|
||||
'dns.c',
|
||||
'dns.h',
|
||||
'epay.c',
|
||||
'epay.h',
|
||||
'hwmon.c',
|
||||
'hwmon.h',
|
||||
'hwreset.c',
|
||||
'hwreset.h',
|
||||
'misc.c',
|
||||
'misc.h',
|
||||
'netenv.c',
|
||||
'netenv.h',
|
||||
'nusec.c',
|
||||
'nusec.h',
|
||||
'pcbid.c',
|
||||
'pcbid.h',
|
||||
'platform.c',
|
||||
'platform.h',
|
||||
'vfs.c',
|
||||
'vfs.h',
|
||||
'system.c',
|
||||
'system.h',
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,173 @@
|
||||
#include <windows.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "hook/table.h"
|
||||
|
||||
#include "hooklib/reg.h"
|
||||
|
||||
#include "platform/misc.h"
|
||||
|
||||
#include "util/dprintf.h"
|
||||
|
||||
static BOOL WINAPI misc_ExitWindowsEx(unsigned int flags, uint32_t reason);
|
||||
|
||||
static HRESULT misc_read_os_version(void *bytes, uint32_t *nbytes);
|
||||
static HRESULT misc_read_app_loader_count(void *bytes, uint32_t *nbytes);
|
||||
static HRESULT misc_read_cpu_temp_error(void *bytes, uint32_t *nbytes);
|
||||
static HRESULT misc_read_cpu_temp_warning(void *bytes, uint32_t *nbytes);
|
||||
static HRESULT misc_read_platform_id(void *bytes, uint32_t *nbytes);
|
||||
static HRESULT misc_read_platform_name(void *bytes, uint32_t *nbytes);
|
||||
|
||||
static const struct hook_symbol misc_syms[] = {
|
||||
{
|
||||
.name = "ExitWindowsEx",
|
||||
.patch = misc_ExitWindowsEx,
|
||||
}
|
||||
};
|
||||
|
||||
static const struct reg_hook_val misc_root_keys[] = {
|
||||
{
|
||||
.name = L"OSVersion",
|
||||
.read = misc_read_os_version,
|
||||
.type = REG_SZ,
|
||||
}
|
||||
};
|
||||
|
||||
static const struct reg_hook_val misc_master_keys[] = {
|
||||
{
|
||||
.name = L"AppLoaderCount",
|
||||
.read = misc_read_app_loader_count,
|
||||
.type = REG_DWORD,
|
||||
}, {
|
||||
/* Black-hole val, list it here so we don't get a warning msg */
|
||||
.name = L"NextProcess",
|
||||
.type = REG_SZ,
|
||||
}, {
|
||||
/* ditto */
|
||||
.name = L"SystemError",
|
||||
.type = REG_SZ,
|
||||
}
|
||||
};
|
||||
|
||||
static const struct reg_hook_val misc_static_keys[] = {
|
||||
{
|
||||
.name = L"CpuTempError",
|
||||
.read = misc_read_cpu_temp_error,
|
||||
.type = REG_DWORD,
|
||||
}, {
|
||||
.name = L"CpuTempWarning",
|
||||
.read = misc_read_cpu_temp_warning,
|
||||
.type = REG_DWORD,
|
||||
}, {
|
||||
.name = L"PlatformId",
|
||||
.read = misc_read_platform_id,
|
||||
.type = REG_SZ,
|
||||
}, {
|
||||
.name = L"PlatformName",
|
||||
.read = misc_read_platform_name,
|
||||
.type = REG_SZ,
|
||||
}
|
||||
};
|
||||
|
||||
static wchar_t misc_platform_id[5];
|
||||
|
||||
HRESULT misc_hook_init(const struct misc_config *cfg, const char *platform_id)
|
||||
{
|
||||
HRESULT hr;
|
||||
|
||||
assert(cfg != NULL);
|
||||
assert(platform_id != NULL && strlen(platform_id) == 4);
|
||||
|
||||
if (!cfg->enable) {
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
/* Set platform ID registry value */
|
||||
|
||||
mbstowcs_s(
|
||||
NULL,
|
||||
misc_platform_id,
|
||||
_countof(misc_platform_id),
|
||||
platform_id,
|
||||
_countof(misc_platform_id) - 1);
|
||||
|
||||
/* Add hardcoded dummy keys */
|
||||
|
||||
hr = reg_hook_push_key(
|
||||
HKEY_LOCAL_MACHINE,
|
||||
L"SYSTEM\\SEGA\\SystemProperty",
|
||||
misc_root_keys,
|
||||
_countof(misc_root_keys));
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
hr = reg_hook_push_key(
|
||||
HKEY_LOCAL_MACHINE,
|
||||
L"SYSTEM\\SEGA\\SystemProperty\\static",
|
||||
misc_static_keys,
|
||||
_countof(misc_static_keys));
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
hr = reg_hook_push_key(
|
||||
HKEY_LOCAL_MACHINE,
|
||||
L"SYSTEM\\SEGA\\SystemProperty\\Master",
|
||||
misc_master_keys,
|
||||
_countof(misc_master_keys));
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
/* Apply function hooks */
|
||||
|
||||
hook_table_apply(NULL, "user32.dll", misc_syms, _countof(misc_syms));
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static BOOL WINAPI misc_ExitWindowsEx(unsigned int flags, uint32_t reason)
|
||||
{
|
||||
dprintf("Misc: Blocked system reboot\n");
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static HRESULT misc_read_os_version(void *bytes, uint32_t *nbytes)
|
||||
{
|
||||
return reg_hook_read_wstr(bytes, nbytes, L"0_0_0");
|
||||
}
|
||||
|
||||
static HRESULT misc_read_app_loader_count(void *bytes, uint32_t *nbytes)
|
||||
{
|
||||
return reg_hook_read_u32(bytes, nbytes, 1);
|
||||
}
|
||||
|
||||
static HRESULT misc_read_cpu_temp_error(void *bytes, uint32_t *nbytes)
|
||||
{
|
||||
return reg_hook_read_u32(bytes, nbytes, 100);
|
||||
}
|
||||
|
||||
static HRESULT misc_read_cpu_temp_warning(void *bytes, uint32_t *nbytes)
|
||||
{
|
||||
return reg_hook_read_u32(bytes, nbytes, 95);
|
||||
}
|
||||
|
||||
static HRESULT misc_read_platform_id(void *bytes, uint32_t *nbytes)
|
||||
{
|
||||
return reg_hook_read_wstr(bytes, nbytes, misc_platform_id);
|
||||
}
|
||||
|
||||
static HRESULT misc_read_platform_name(void *bytes, uint32_t *nbytes)
|
||||
{
|
||||
return reg_hook_read_wstr(bytes, nbytes, L"ALLS MX2.1"); // TODO: Dynamic
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
struct misc_config {
|
||||
bool enable;
|
||||
};
|
||||
|
||||
HRESULT misc_hook_init(const struct misc_config *cfg, const char *platform_id);
|
||||
@@ -0,0 +1,508 @@
|
||||
#include <windows.h>
|
||||
#include <winsock2.h>
|
||||
#include <iphlpapi.h>
|
||||
#include <iptypes.h>
|
||||
#include <winternl.h>
|
||||
#include <assert.h>
|
||||
#include <icmpapi.h>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "hook/table.h"
|
||||
|
||||
#include "platform/netenv.h"
|
||||
#include "platform/nusec.h"
|
||||
|
||||
#include "util/dprintf.h"
|
||||
|
||||
struct netenv {
|
||||
IP_ADAPTER_ADDRESSES head;
|
||||
char name[64];
|
||||
wchar_t dns_suffix[64];
|
||||
wchar_t description[64];
|
||||
wchar_t friendly_name[64];
|
||||
IP_ADAPTER_PREFIX prefix;
|
||||
IP_ADAPTER_UNICAST_ADDRESS iface;
|
||||
IP_ADAPTER_GATEWAY_ADDRESS router;
|
||||
IP_ADAPTER_DNS_SERVER_ADDRESS dns;
|
||||
struct sockaddr_in prefix_sa;
|
||||
struct sockaddr_in iface_sa;
|
||||
struct sockaddr_in router_sa;
|
||||
struct sockaddr_in dns_sa;
|
||||
};
|
||||
|
||||
/* Hook functions */
|
||||
|
||||
static uint32_t WINAPI hook_GetAdaptersAddresses(
|
||||
uint32_t Family,
|
||||
uint32_t Flags,
|
||||
void *Reserved,
|
||||
IP_ADAPTER_ADDRESSES *AdapterAddresses,
|
||||
uint32_t *SizePointer);
|
||||
|
||||
static uint32_t WINAPI hook_GetAdaptersInfo(
|
||||
IP_ADAPTER_INFO *AdapterInfo,
|
||||
uint32_t *SizePointer);
|
||||
|
||||
static uint32_t WINAPI hook_GetBestRoute(
|
||||
uint32_t src_ip,
|
||||
uint32_t dest_ip,
|
||||
MIB_IPFORWARDROW *route);
|
||||
|
||||
static uint32_t WINAPI hook_GetIfTable(
|
||||
MIB_IFTABLE *pIfTable,
|
||||
uint32_t *pdwSize,
|
||||
BOOL bOrder);
|
||||
|
||||
static uint32_t WINAPI hook_IcmpSendEcho2(
|
||||
HANDLE IcmpHandle,
|
||||
HANDLE Event,
|
||||
PIO_APC_ROUTINE ApcRoutine,
|
||||
void *ApcContext,
|
||||
uint32_t DestinationAddress,
|
||||
void *RequestData,
|
||||
uint16_t RequestSize,
|
||||
IP_OPTION_INFORMATION *RequestOptions,
|
||||
void *ReplyBuffer,
|
||||
uint32_t ReplySize,
|
||||
uint32_t Timeout);
|
||||
|
||||
/* Link pointers */
|
||||
|
||||
static uint32_t (WINAPI *next_GetAdaptersAddresses)(
|
||||
uint32_t Family,
|
||||
uint32_t Flags,
|
||||
void *Reserved,
|
||||
IP_ADAPTER_ADDRESSES *AdapterAddresses,
|
||||
uint32_t *SizePointer);
|
||||
|
||||
static uint32_t (WINAPI *next_GetAdaptersInfo)(
|
||||
IP_ADAPTER_INFO *AdapterInfo,
|
||||
uint32_t *SizePointer);
|
||||
|
||||
static uint32_t (WINAPI *next_GetBestRoute)(
|
||||
uint32_t src_ip,
|
||||
uint32_t dest_ip,
|
||||
MIB_IPFORWARDROW *route);
|
||||
|
||||
static uint32_t (WINAPI *next_GetIfTable)(
|
||||
MIB_IFTABLE *pIfTable,
|
||||
uint32_t *pdwSize,
|
||||
BOOL bOrder);
|
||||
|
||||
static uint32_t (WINAPI *next_IcmpSendEcho2)(
|
||||
HANDLE IcmpHandle,
|
||||
HANDLE Event,
|
||||
PIO_APC_ROUTINE ApcRoutine,
|
||||
void *ApcContext,
|
||||
uint32_t DestinationAddress,
|
||||
void *RequestData,
|
||||
uint16_t RequestSize,
|
||||
IP_OPTION_INFORMATION *RequestOptions,
|
||||
void *ReplyBuffer,
|
||||
uint32_t ReplySize,
|
||||
uint32_t Timeout);
|
||||
|
||||
static const struct hook_symbol netenv_hook_syms[] = {
|
||||
{
|
||||
.name = "GetAdaptersAddresses",
|
||||
.patch = hook_GetAdaptersAddresses,
|
||||
.link = (void **) &next_GetAdaptersAddresses,
|
||||
}, {
|
||||
.name = "GetAdaptersInfo",
|
||||
.patch = hook_GetAdaptersInfo,
|
||||
.link = (void **) &next_GetAdaptersInfo,
|
||||
}, {
|
||||
.name = "GetBestRoute",
|
||||
.patch = hook_GetBestRoute,
|
||||
.link = (void **) &next_GetBestRoute,
|
||||
}, {
|
||||
.name = "GetIfTable",
|
||||
.patch = hook_GetIfTable,
|
||||
.link = (void **) &next_GetIfTable,
|
||||
}, {
|
||||
.name = "IcmpSendEcho2",
|
||||
.patch = hook_IcmpSendEcho2,
|
||||
.link = (void **) &next_IcmpSendEcho2,
|
||||
}
|
||||
};
|
||||
|
||||
static uint32_t netenv_ip_prefix;
|
||||
static uint32_t netenv_ip_iface;
|
||||
static uint32_t netenv_ip_router;
|
||||
static uint8_t netenv_mac_addr[6];
|
||||
|
||||
HRESULT netenv_hook_init(
|
||||
const struct netenv_config *cfg,
|
||||
const struct nusec_config *kc_cfg)
|
||||
{
|
||||
assert(cfg != NULL);
|
||||
assert(kc_cfg != NULL);
|
||||
|
||||
if (!cfg->enable) {
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
if (!kc_cfg->enable) {
|
||||
dprintf("Netenv: Keychip emu is off? Disabling Netenv emu.\n");
|
||||
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
netenv_ip_prefix = kc_cfg->subnet;
|
||||
netenv_ip_iface = kc_cfg->subnet | cfg->addr_suffix;
|
||||
netenv_ip_router = kc_cfg->subnet | cfg->router_suffix;
|
||||
memcpy(netenv_mac_addr, cfg->mac_addr, sizeof(netenv_mac_addr));
|
||||
|
||||
hook_table_apply(
|
||||
NULL,
|
||||
"iphlpapi.dll",
|
||||
netenv_hook_syms,
|
||||
_countof(netenv_hook_syms));
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static uint32_t WINAPI hook_GetAdaptersAddresses(
|
||||
uint32_t Family,
|
||||
uint32_t Flags,
|
||||
void *Reserved,
|
||||
IP_ADAPTER_ADDRESSES *AdapterAddresses,
|
||||
uint32_t *SizePointer)
|
||||
{
|
||||
/* This hook errs on the side of caution and returns a lot more
|
||||
information than the ALLNET lib cares about. MSVC mangles the main
|
||||
call site for this API quite aggressively, so by the time we decompile
|
||||
the code in question it's a little difficult to tell which pieces the
|
||||
ALLNET lib pays attention to. */
|
||||
|
||||
uint32_t nbytes;
|
||||
struct netenv *env;
|
||||
|
||||
if (Reserved != NULL || SizePointer == NULL) {
|
||||
return ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
nbytes = *SizePointer;
|
||||
*SizePointer = sizeof(*env);
|
||||
|
||||
if (AdapterAddresses == NULL || nbytes < sizeof(*env)) {
|
||||
return ERROR_BUFFER_OVERFLOW;
|
||||
}
|
||||
|
||||
env = CONTAINING_RECORD(AdapterAddresses, struct netenv, head);
|
||||
memset(env, 0, sizeof(*env));
|
||||
|
||||
env->head.Length = sizeof(env->head);
|
||||
env->head.IfIndex = 1;
|
||||
env->head.AdapterName = env->name;
|
||||
env->head.FirstUnicastAddress = &env->iface;
|
||||
env->head.FirstDnsServerAddress = &env->dns;
|
||||
env->head.DnsSuffix = env->dns_suffix;
|
||||
env->head.Description = env->description;
|
||||
env->head.FriendlyName = env->friendly_name;
|
||||
memcpy( env->head.PhysicalAddress,
|
||||
netenv_mac_addr,
|
||||
sizeof(netenv_mac_addr));
|
||||
env->head.PhysicalAddressLength = sizeof(netenv_mac_addr);
|
||||
env->head.Flags = IP_ADAPTER_DHCP_ENABLED | IP_ADAPTER_IPV4_ENABLED;
|
||||
env->head.Mtu = 4200; /* idk what's typical here */
|
||||
env->head.IfType = IF_TYPE_ETHERNET_CSMACD;
|
||||
env->head.OperStatus = IfOperStatusUp;
|
||||
env->head.FirstPrefix = &env->prefix;
|
||||
env->head.FirstGatewayAddress = &env->router;
|
||||
|
||||
strcpy_s(
|
||||
env->name,
|
||||
_countof(env->name),
|
||||
"{00000000-0000-0000-0000-000000000000}");
|
||||
|
||||
wcscpy_s(
|
||||
env->dns_suffix,
|
||||
_countof(env->dns_suffix),
|
||||
L"local");
|
||||
|
||||
wcscpy_s(
|
||||
env->description,
|
||||
_countof(env->description),
|
||||
L"Interface Description");
|
||||
|
||||
wcscpy_s(
|
||||
env->friendly_name,
|
||||
_countof(env->friendly_name),
|
||||
L"Fake Ethernet");
|
||||
|
||||
env->iface.Length = sizeof(env->iface);
|
||||
env->iface.Flags = 0;
|
||||
env->iface.Address.lpSockaddr = (struct sockaddr *) &env->iface_sa;
|
||||
env->iface.Address.iSockaddrLength = sizeof(env->iface_sa);
|
||||
env->iface.PrefixOrigin = IpPrefixOriginDhcp;
|
||||
env->iface.SuffixOrigin = IpSuffixOriginDhcp;
|
||||
env->iface.DadState = IpDadStatePreferred;
|
||||
env->iface.ValidLifetime = UINT32_MAX;
|
||||
env->iface.PreferredLifetime = UINT32_MAX;
|
||||
env->iface.LeaseLifetime = 86400;
|
||||
env->iface.OnLinkPrefixLength = 24;
|
||||
|
||||
env->prefix.Length = sizeof(env->prefix);
|
||||
env->prefix.Address.lpSockaddr = (struct sockaddr *) &env->prefix_sa;
|
||||
env->prefix.Address.iSockaddrLength = sizeof(env->prefix_sa);
|
||||
env->prefix.PrefixLength = 24;
|
||||
|
||||
env->router.Length = sizeof(env->router);
|
||||
env->router.Address.lpSockaddr = (struct sockaddr *) &env->router_sa;
|
||||
env->router.Address.iSockaddrLength = sizeof(env->router_sa);
|
||||
|
||||
env->dns.Length = sizeof(env->dns);
|
||||
env->dns.Address.lpSockaddr = (struct sockaddr *) &env->dns_sa;
|
||||
env->dns.Address.iSockaddrLength = sizeof(env->dns_sa);
|
||||
|
||||
env->prefix_sa.sin_family = AF_INET;
|
||||
env->prefix_sa.sin_addr.s_addr = _byteswap_ulong(netenv_ip_prefix);
|
||||
|
||||
env->iface_sa.sin_family = AF_INET;
|
||||
env->iface_sa.sin_addr.s_addr = _byteswap_ulong(netenv_ip_iface);
|
||||
|
||||
env->router_sa.sin_family = AF_INET;
|
||||
env->router_sa.sin_addr.s_addr = _byteswap_ulong(netenv_ip_router);
|
||||
|
||||
env->dns_sa.sin_family = AF_INET;
|
||||
env->dns_sa.sin_addr.s_addr = _byteswap_ulong(netenv_ip_router);
|
||||
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
static uint32_t WINAPI hook_GetAdaptersInfo(
|
||||
IP_ADAPTER_INFO *ai,
|
||||
uint32_t *nbytes_inout)
|
||||
{
|
||||
IP_ADDR_STRING iface;
|
||||
IP_ADDR_STRING router;
|
||||
uint32_t nbytes;
|
||||
|
||||
if (nbytes_inout == NULL) {
|
||||
return ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
nbytes = *nbytes_inout;
|
||||
*nbytes_inout = sizeof(*ai);
|
||||
|
||||
if (ai == NULL || nbytes < sizeof(*ai)) {
|
||||
return ERROR_BUFFER_OVERFLOW;
|
||||
}
|
||||
|
||||
dprintf("Netenv: GetAdaptersInfo: Virtualized LAN configuration:\n");
|
||||
dprintf("Netenv: Interface IP : %3i.%3i.%3i.%3i\n",
|
||||
(uint8_t) (netenv_ip_iface >> 24),
|
||||
(uint8_t) (netenv_ip_iface >> 16),
|
||||
(uint8_t) (netenv_ip_iface >> 8),
|
||||
(uint8_t) (netenv_ip_iface ));
|
||||
dprintf("Netenv: Router IP : %3i.%3i.%3i.%3i\n",
|
||||
(uint8_t) (netenv_ip_router >> 24),
|
||||
(uint8_t) (netenv_ip_router >> 16),
|
||||
(uint8_t) (netenv_ip_router >> 8),
|
||||
(uint8_t) (netenv_ip_router ));
|
||||
dprintf("Netenv: MAC Address : %02x:%02x:%02x:%02x:%02x:%02x\n",
|
||||
netenv_mac_addr[0],
|
||||
netenv_mac_addr[1],
|
||||
netenv_mac_addr[2],
|
||||
netenv_mac_addr[3],
|
||||
netenv_mac_addr[4],
|
||||
netenv_mac_addr[5]);
|
||||
|
||||
memset(&iface, 0, sizeof(iface));
|
||||
memset(&router, 0, sizeof(router));
|
||||
|
||||
sprintf_s(
|
||||
iface.IpAddress.String,
|
||||
_countof(iface.IpAddress.String),
|
||||
"%i.%i.%i.%i",
|
||||
(uint8_t) (netenv_ip_iface >> 24),
|
||||
(uint8_t) (netenv_ip_iface >> 16),
|
||||
(uint8_t) (netenv_ip_iface >> 8),
|
||||
(uint8_t) (netenv_ip_iface ));
|
||||
|
||||
strcpy_s(
|
||||
iface.IpMask.String,
|
||||
_countof(iface.IpMask.String),
|
||||
"255.255.255.0");
|
||||
|
||||
sprintf_s(
|
||||
router.IpAddress.String,
|
||||
_countof(iface.IpAddress.String),
|
||||
"%i.%i.%i.%i",
|
||||
(uint8_t) (netenv_ip_router >> 24),
|
||||
(uint8_t) (netenv_ip_router >> 16),
|
||||
(uint8_t) (netenv_ip_router >> 8),
|
||||
(uint8_t) (netenv_ip_router ));
|
||||
|
||||
strcpy_s(
|
||||
router.IpMask.String,
|
||||
_countof(router.IpMask.String),
|
||||
"255.255.255.0");
|
||||
|
||||
memset(ai, 0, sizeof(*ai));
|
||||
strcpy_s(
|
||||
ai->AdapterName,
|
||||
_countof(ai->AdapterName),
|
||||
"Fake Ethernet");
|
||||
strcpy_s(ai->Description,
|
||||
_countof(ai->Description),
|
||||
"Adapter Description");
|
||||
ai->AddressLength = sizeof(netenv_mac_addr);
|
||||
memcpy(ai->Address, netenv_mac_addr, sizeof(netenv_mac_addr));
|
||||
ai->Index = 1;
|
||||
ai->Type = MIB_IF_TYPE_ETHERNET;
|
||||
ai->DhcpEnabled = 1;
|
||||
memcpy(&ai->IpAddressList, &iface, sizeof(iface));
|
||||
memcpy(&ai->GatewayList, &router, sizeof(router));
|
||||
memcpy(&ai->DhcpServer, &router, sizeof(router));
|
||||
ai->LeaseObtained = time(NULL) - 3600;
|
||||
ai->LeaseExpires = time(NULL) + 86400;
|
||||
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
static uint32_t WINAPI hook_GetBestRoute(
|
||||
uint32_t src_ip,
|
||||
uint32_t dest_ip,
|
||||
MIB_IPFORWARDROW *route)
|
||||
{
|
||||
if (route == NULL) {
|
||||
return ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
uint32_t src_addr = _byteswap_ulong(src_ip);
|
||||
uint32_t dest_addr = _byteswap_ulong(dest_ip);
|
||||
|
||||
dprintf("Netenv: GetBestRoute ip4 %u.%u.%u.%u -> ip4 %u.%u.%u.%u\n",
|
||||
(src_addr >> 24) & 0xff, (src_addr >> 16) & 0xff, (src_addr >> 8) & 0xff, src_addr & 0xff,
|
||||
(dest_addr >> 24) & 0xff, (dest_addr >> 16) & 0xff, (dest_addr >> 8) & 0xff, dest_addr & 0xff);
|
||||
|
||||
memset(route, 0, sizeof(*route));
|
||||
|
||||
/* This doesn't seem to get read? It just needs to succeed. */
|
||||
|
||||
route->dwForwardDest = 0x00000000;
|
||||
route->dwForwardMask = 0xFFFFFFFF;
|
||||
route->dwForwardPolicy = 0; /* idk */
|
||||
route->dwForwardNextHop = _byteswap_ulong(netenv_ip_router);
|
||||
route->dwForwardIfIndex = 1;
|
||||
route->dwForwardType = MIB_IPROUTE_TYPE_INDIRECT;
|
||||
route->dwForwardProto = MIB_IPPROTO_NETMGMT;
|
||||
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
static uint32_t WINAPI hook_GetIfTable(
|
||||
MIB_IFTABLE *pIfTable,
|
||||
uint32_t *pdwSize,
|
||||
BOOL bOrder)
|
||||
{
|
||||
MIB_IFROW *row;
|
||||
uint32_t nbytes;
|
||||
|
||||
if (pdwSize == NULL) {
|
||||
return ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
nbytes = *pdwSize;
|
||||
*pdwSize = sizeof(*row) + sizeof(DWORD);
|
||||
|
||||
if (pIfTable == NULL || nbytes < sizeof(*row) + sizeof(DWORD)) {
|
||||
return ERROR_BUFFER_OVERFLOW;
|
||||
}
|
||||
|
||||
pIfTable->dwNumEntries = 1;
|
||||
|
||||
row = pIfTable->table;
|
||||
memset(row, 0, sizeof(*row));
|
||||
|
||||
wcscpy_s(row->wszName, _countof(row->wszName), L"Fake Ethernet");
|
||||
row->dwIndex = 1; /* Should match other IF_INDEX fields we return */
|
||||
row->dwType = IF_TYPE_ETHERNET_CSMACD;
|
||||
row->dwMtu = 4200; /* I guess? */
|
||||
row->dwSpeed = 1000000000;
|
||||
row->dwPhysAddrLen = sizeof(netenv_mac_addr);
|
||||
memcpy(row->bPhysAddr, netenv_mac_addr, sizeof(netenv_mac_addr));
|
||||
row->dwAdminStatus = 1;
|
||||
row->dwOperStatus = IF_OPER_STATUS_OPERATIONAL;
|
||||
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
static uint32_t WINAPI hook_IcmpSendEcho2(
|
||||
HANDLE IcmpHandle,
|
||||
HANDLE Event,
|
||||
PIO_APC_ROUTINE ApcRoutine,
|
||||
void *ApcContext,
|
||||
uint32_t DestinationAddress,
|
||||
void *RequestData,
|
||||
uint16_t RequestSize,
|
||||
IP_OPTION_INFORMATION *RequestOptions,
|
||||
void *ReplyBuffer,
|
||||
uint32_t ReplySize,
|
||||
uint32_t Timeout)
|
||||
{
|
||||
ICMP_ECHO_REPLY *pong;
|
||||
BOOL ok;
|
||||
|
||||
if (IcmpHandle == NULL || IcmpHandle == INVALID_HANDLE_VALUE) {
|
||||
SetLastError(ERROR_INVALID_PARAMETER);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (ApcRoutine != NULL) {
|
||||
dprintf("%s: Got APC routine...\n", __func__);
|
||||
SetLastError(ERROR_NOT_SUPPORTED);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (ReplyBuffer == NULL) {
|
||||
SetLastError(ERROR_INVALID_PARAMETER);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (ReplySize < sizeof(ICMP_ECHO_REPLY)) {
|
||||
SetLastError(IP_BUF_TOO_SMALL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t addr = _byteswap_ulong(DestinationAddress);
|
||||
|
||||
dprintf("Netenv: Virtualized ICMP Ping to ip4 %u.%u.%u.%u\n",
|
||||
(addr >> 24) & 0xff, (addr >> 16) & 0xff, (addr >> 8) & 0xff, addr & 0xff);
|
||||
|
||||
pong = (ICMP_ECHO_REPLY *) ReplyBuffer;
|
||||
memset(pong, 0, sizeof(*pong));
|
||||
pong->Address = DestinationAddress;
|
||||
pong->Status = IP_SUCCESS;
|
||||
pong->RoundTripTime = 1;
|
||||
pong->DataSize = 0;
|
||||
pong->Reserved = 1; /* Number of ICMP_ECHO_REPLY structs in ReplyBuffer */
|
||||
pong->Data = NULL;
|
||||
|
||||
if (Event != NULL) {
|
||||
ok = SetEvent(Event);
|
||||
|
||||
if (ok) {
|
||||
SetLastError(ERROR_IO_PENDING);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
dprintf("%s: Unexpected synchronous call...\n", __func__);
|
||||
SetLastError(ERROR_SUCCESS);
|
||||
|
||||
return 1;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "platform/nusec.h"
|
||||
|
||||
struct netenv_config {
|
||||
bool enable;
|
||||
uint8_t addr_suffix;
|
||||
uint8_t router_suffix;
|
||||
uint8_t mac_addr[6];
|
||||
};
|
||||
|
||||
HRESULT netenv_hook_init(
|
||||
const struct netenv_config *cfg,
|
||||
const struct nusec_config *kc_cfg);
|
||||
|
||||
@@ -0,0 +1,660 @@
|
||||
#include <windows.h>
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <winioctl.h>
|
||||
|
||||
#include "hook/iohook.h"
|
||||
|
||||
#include "hooklib/reg.h"
|
||||
|
||||
#include "platform/nusec.h"
|
||||
|
||||
#include "util/dprintf.h"
|
||||
#include "util/dump.h"
|
||||
#include "util/str.h"
|
||||
|
||||
#define NUSEC_IOCTL_PING CTL_CODE(0x22, 0x845, METHOD_BUFFERED, FILE_WRITE_ACCESS)
|
||||
#define NUSEC_IOCTL_GET_PLAY_COUNT CTL_CODE(0x22, 0x854, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
|
||||
#define NUSEC_IOCTL_ADD_PLAY_COUNT CTL_CODE(0x22, 0x855, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
|
||||
#define NUSEC_IOCTL_SET_IV CTL_CODE(0x22, 0x856, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
|
||||
#define NUSEC_IOCTL_ENCRYPT CTL_CODE(0x22, 0x857, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
|
||||
#define NUSEC_IOCTL_DECRYPT CTL_CODE(0x22, 0x858, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
|
||||
#define NUSEC_IOCTL_TD_RESTORE CTL_CODE(0x22, 0x861, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
|
||||
#define NUSEC_IOCTL_ERASE_TRACE_LOG CTL_CODE(0x22, 0x862, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
|
||||
#define NUSEC_IOCTL_TD_ERASE_USED CTL_CODE(0x22, 0x863, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
|
||||
#define NUSEC_IOCTL_PUT_TRACE_LOG_DATA CTL_CODE(0x22, 0x864, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
|
||||
#define NUSEC_IOCTL_GET_TRACE_LOG_DATA CTL_CODE(0x22, 0x865, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
|
||||
#define NUSEC_IOCTL_GET_TRACE_LOG_STATE CTL_CODE(0x22, 0x866, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
|
||||
#define NUSEC_IOCTL_GET_NVRAM_AVAILABLE CTL_CODE(0x22, 0x867, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
|
||||
#define NUSEC_IOCTL_TD_ERASE_ALL CTL_CODE(0x22, 0x869, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
|
||||
#define NUSEC_IOCTL_GET_BILLING_CA_CERT CTL_CODE(0x22, 0x871, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
|
||||
#define NUSEC_IOCTL_GET_BILLING_PUBKEY CTL_CODE(0x22, 0x872, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
|
||||
#define NUSEC_IOCTL_GET_PLAY_LIMIT CTL_CODE(0x22, 0x881, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
|
||||
#define NUSEC_IOCTL_PUT_PLAY_LIMIT CTL_CODE(0x22, 0x882, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
|
||||
#define NUSEC_IOCTL_GET_NEARFULL CTL_CODE(0x22, 0x883, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
|
||||
#define NUSEC_IOCTL_PUT_NEARFULL CTL_CODE(0x22, 0x884, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
|
||||
#define NUSEC_IOCTL_NVRAM_READ CTL_CODE(0x22, 0x891, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
|
||||
#define NUSEC_IOCTL_NVRAM_WRITE CTL_CODE(0x22, 0x892, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
|
||||
#define NUSEC_IOCTL_GET_NVRAM_GEOMETRY CTL_CODE(0x22, 0x893, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
|
||||
#define NUSEC_IOCTL_AL_AUTH_START CTL_CODE(0x22, 0x8B0, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
|
||||
#define NUSEC_IOCTL_AL_AUTH_PACKET CTL_CODE(0x22, 0x8B2, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
|
||||
|
||||
// Referenced in AMDaemon but seemingly not used, maybe leftovers?
|
||||
#define NUSEC_IOCTL_UNK_843 CTL_CODE(0x22, 0x843, METHOD_BUFFERED, FILE_WRITE_ACCESS)
|
||||
#define NUSEC_IOCTL_UNK_844 CTL_CODE(0x22, 0x844, METHOD_BUFFERED, FILE_WRITE_ACCESS)
|
||||
#define NUSEC_IOCTL_UNK_894 CTL_CODE(0x22, 0x894, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
|
||||
#define NUSEC_IOCTL_UNK_895 CTL_CODE(0x22, 0x895, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
|
||||
|
||||
struct nusec_log_record {
|
||||
uint8_t unknown[60];
|
||||
};
|
||||
|
||||
static HRESULT nusec_handle_irp(struct irp *irp);
|
||||
static HRESULT nusec_handle_open(struct irp *irp);
|
||||
static HRESULT nusec_handle_close(struct irp *irp);
|
||||
static HRESULT nusec_handle_ioctl(struct irp *irp);
|
||||
|
||||
static HRESULT nusec_ioctl_ping(struct irp *irp);
|
||||
static HRESULT nusec_ioctl_erase_trace_log(struct irp *irp);
|
||||
static HRESULT nusec_ioctl_td_erase_used(struct irp *irp);
|
||||
static HRESULT nusec_ioctl_add_play_count(struct irp *irp);
|
||||
static HRESULT nusec_ioctl_get_billing_ca_cert(struct irp *irp);
|
||||
static HRESULT nusec_ioctl_get_billing_pubkey(struct irp *irp);
|
||||
static HRESULT nusec_ioctl_get_nearfull(struct irp *irp);
|
||||
static HRESULT nusec_ioctl_get_nvram_available(struct irp *irp);
|
||||
static HRESULT nusec_ioctl_get_nvram_geometry(struct irp *irp);
|
||||
static HRESULT nusec_ioctl_get_play_count(struct irp *irp);
|
||||
static HRESULT nusec_ioctl_get_play_limit(struct irp *irp);
|
||||
static HRESULT nusec_ioctl_get_trace_log_data(struct irp *irp);
|
||||
static HRESULT nusec_ioctl_get_trace_log_state(struct irp *irp);
|
||||
static HRESULT nusec_ioctl_put_nearfull(struct irp *irp);
|
||||
static HRESULT nusec_ioctl_put_play_limit(struct irp *irp);
|
||||
static HRESULT nusec_ioctl_put_trace_log_data(struct irp *irp);
|
||||
|
||||
static HRESULT nusec_reg_read_game_id(void *bytes, uint32_t *nbytes);
|
||||
static HRESULT nusec_reg_read_keychip_id(void *bytes, uint32_t *nbytes);
|
||||
static HRESULT nusec_reg_read_model_type(void *bytes, uint32_t *nbytes);
|
||||
static HRESULT nusec_reg_read_platform_id(void *bytes, uint32_t *nbytes);
|
||||
static HRESULT nusec_reg_read_region(void *bytes, uint32_t *nbytes);
|
||||
static HRESULT nusec_reg_read_server_ip_ipv4(void *bytes, uint32_t *nbytes);
|
||||
static HRESULT nusec_reg_read_server_ip_ipv6(void *bytes, uint32_t *nbytes);
|
||||
static HRESULT nusec_reg_read_system_flag(void *bytes, uint32_t *nbytes);
|
||||
|
||||
static const struct reg_hook_val nusec_reg_vals[] = {
|
||||
{
|
||||
.name = L"gameId",
|
||||
.read = nusec_reg_read_game_id,
|
||||
.type = REG_BINARY,
|
||||
}, {
|
||||
.name = L"keychipId",
|
||||
.read = nusec_reg_read_keychip_id,
|
||||
.type = REG_BINARY,
|
||||
}, {
|
||||
.name = L"modelType",
|
||||
.read = nusec_reg_read_model_type,
|
||||
.type = REG_DWORD,
|
||||
}, {
|
||||
.name = L"platformId",
|
||||
.read = nusec_reg_read_platform_id,
|
||||
.type = REG_BINARY,
|
||||
}, {
|
||||
.name = L"region",
|
||||
.read = nusec_reg_read_region,
|
||||
.type = REG_DWORD,
|
||||
}, {
|
||||
.name = L"serverIpIpv4",
|
||||
.read = nusec_reg_read_server_ip_ipv4,
|
||||
.type = REG_BINARY,
|
||||
}, {
|
||||
.name = L"serverIpIpv6",
|
||||
.read = nusec_reg_read_server_ip_ipv6,
|
||||
.type = REG_BINARY,
|
||||
}, {
|
||||
.name = L"systemFlag",
|
||||
.read = nusec_reg_read_system_flag,
|
||||
.type = REG_DWORD,
|
||||
}
|
||||
};
|
||||
|
||||
static HANDLE nusec_fd;
|
||||
static uint32_t nusec_nearfull;
|
||||
static uint32_t nusec_play_count;
|
||||
static uint32_t nusec_play_limit;
|
||||
static struct nusec_log_record nusec_log[7154];
|
||||
static size_t nusec_log_head;
|
||||
static size_t nusec_log_tail;
|
||||
static struct nusec_config nusec_cfg;
|
||||
|
||||
HRESULT nusec_hook_init(
|
||||
const struct nusec_config *cfg,
|
||||
const char *game_id,
|
||||
const char *platform_id)
|
||||
{
|
||||
HRESULT hr;
|
||||
|
||||
assert(cfg != NULL);
|
||||
assert(game_id != NULL && strlen(game_id) == sizeof(cfg->game_id));
|
||||
assert(platform_id != NULL && strlen(platform_id) == sizeof(cfg->platform_id));
|
||||
|
||||
if (!cfg->enable) {
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
memcpy(&nusec_cfg, cfg, sizeof(*cfg));
|
||||
|
||||
if (nusec_cfg.game_id[0] == '\0') {
|
||||
memcpy(nusec_cfg.game_id, game_id, sizeof(nusec_cfg.game_id));
|
||||
}
|
||||
|
||||
if (nusec_cfg.platform_id[0] == '\0') {
|
||||
memcpy(nusec_cfg.platform_id, platform_id, sizeof(nusec_cfg.platform_id));
|
||||
}
|
||||
|
||||
// High 16 bits is billing type, low is actual playlimit
|
||||
nusec_nearfull = (nusec_cfg.billing_type << 16) + 512;
|
||||
nusec_play_count = 0;
|
||||
nusec_play_limit = 1024;
|
||||
|
||||
hr = iohook_open_nul_fd(&nusec_fd);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
hr = iohook_push_handler(nusec_handle_irp);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
hr = reg_hook_push_key(
|
||||
HKEY_LOCAL_MACHINE,
|
||||
L"SYSTEM\\SEGA\\SystemProperty\\keychip",
|
||||
nusec_reg_vals,
|
||||
_countof(nusec_reg_vals));
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static HRESULT nusec_handle_irp(struct irp *irp)
|
||||
{
|
||||
assert(irp != NULL);
|
||||
|
||||
if (irp->op != IRP_OP_OPEN && irp->fd != nusec_fd) {
|
||||
return iohook_invoke_next(irp);
|
||||
}
|
||||
|
||||
switch (irp->op) {
|
||||
case IRP_OP_OPEN: return nusec_handle_open(irp);
|
||||
case IRP_OP_CLOSE: return nusec_handle_close(irp);
|
||||
case IRP_OP_IOCTL: return nusec_handle_ioctl(irp);
|
||||
default: return HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION);
|
||||
}
|
||||
}
|
||||
|
||||
static HRESULT nusec_handle_open(struct irp *irp)
|
||||
{
|
||||
if (!wstr_ieq(irp->open_filename, L"\\??\\FddDriver")) {
|
||||
return iohook_invoke_next(irp);
|
||||
}
|
||||
|
||||
dprintf("Security: Opened handle\n");
|
||||
irp->fd = nusec_fd;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static HRESULT nusec_handle_close(struct irp *irp)
|
||||
{
|
||||
dprintf("Security: Closed handle\n");
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static HRESULT nusec_handle_ioctl(struct irp *irp)
|
||||
{
|
||||
switch (irp->ioctl) {
|
||||
case NUSEC_IOCTL_PING:
|
||||
return nusec_ioctl_ping(irp);
|
||||
|
||||
case NUSEC_IOCTL_ERASE_TRACE_LOG:
|
||||
return nusec_ioctl_erase_trace_log(irp);
|
||||
|
||||
case NUSEC_IOCTL_TD_ERASE_USED:
|
||||
return nusec_ioctl_td_erase_used(irp);
|
||||
|
||||
case NUSEC_IOCTL_ADD_PLAY_COUNT:
|
||||
return nusec_ioctl_add_play_count(irp);
|
||||
|
||||
case NUSEC_IOCTL_GET_BILLING_CA_CERT:
|
||||
return nusec_ioctl_get_billing_ca_cert(irp);
|
||||
|
||||
case NUSEC_IOCTL_GET_BILLING_PUBKEY:
|
||||
return nusec_ioctl_get_billing_pubkey(irp);
|
||||
|
||||
case NUSEC_IOCTL_GET_NEARFULL:
|
||||
return nusec_ioctl_get_nearfull(irp);
|
||||
|
||||
case NUSEC_IOCTL_GET_NVRAM_AVAILABLE:
|
||||
return nusec_ioctl_get_nvram_available(irp);
|
||||
|
||||
case NUSEC_IOCTL_GET_NVRAM_GEOMETRY:
|
||||
return nusec_ioctl_get_nvram_geometry(irp);
|
||||
|
||||
case NUSEC_IOCTL_GET_PLAY_COUNT:
|
||||
return nusec_ioctl_get_play_count(irp);
|
||||
|
||||
case NUSEC_IOCTL_GET_PLAY_LIMIT:
|
||||
return nusec_ioctl_get_play_limit(irp);
|
||||
|
||||
case NUSEC_IOCTL_GET_TRACE_LOG_DATA:
|
||||
return nusec_ioctl_get_trace_log_data(irp);
|
||||
|
||||
case NUSEC_IOCTL_GET_TRACE_LOG_STATE:
|
||||
return nusec_ioctl_get_trace_log_state(irp);
|
||||
|
||||
case NUSEC_IOCTL_PUT_NEARFULL:
|
||||
return nusec_ioctl_put_nearfull(irp);
|
||||
|
||||
case NUSEC_IOCTL_PUT_PLAY_LIMIT:
|
||||
return nusec_ioctl_put_play_limit(irp);
|
||||
|
||||
case NUSEC_IOCTL_PUT_TRACE_LOG_DATA:
|
||||
return nusec_ioctl_put_trace_log_data(irp);
|
||||
|
||||
default:
|
||||
dprintf("Security: Unknown ioctl %#08x, write %i read %i\n",
|
||||
irp->ioctl,
|
||||
(int) irp->write.nbytes,
|
||||
(int) irp->read.nbytes);
|
||||
|
||||
return HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION);
|
||||
}
|
||||
}
|
||||
|
||||
static HRESULT nusec_ioctl_ping(struct irp *irp)
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static HRESULT nusec_ioctl_erase_trace_log(struct irp *irp)
|
||||
{
|
||||
uint32_t count;
|
||||
size_t avail;
|
||||
HRESULT hr;
|
||||
|
||||
hr = iobuf_read_le32(&irp->write, &count);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
dprintf("Security: %s(count=%i)\n", __func__, count);
|
||||
|
||||
avail = nusec_log_head - nusec_log_tail;
|
||||
|
||||
if (count < avail) {
|
||||
count = avail;
|
||||
}
|
||||
|
||||
nusec_log_tail += count;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static HRESULT nusec_ioctl_td_erase_used(struct irp *irp)
|
||||
{
|
||||
dprintf("Security: %s\n", __func__);
|
||||
|
||||
nusec_log_head = 0;
|
||||
nusec_log_tail = 0;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static HRESULT nusec_ioctl_add_play_count(struct irp *irp)
|
||||
{
|
||||
uint32_t delta;
|
||||
HRESULT hr;
|
||||
|
||||
hr = iobuf_read_le32(&irp->write, &delta);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
dprintf("Security: Add play count: %i + %i = %i\n",
|
||||
nusec_play_count,
|
||||
delta,
|
||||
nusec_play_count + delta);
|
||||
|
||||
nusec_play_count += delta;
|
||||
|
||||
return iobuf_write_le32(&irp->read, nusec_play_count);
|
||||
}
|
||||
|
||||
static HRESULT nusec_ioctl_get_billing_ca_cert(struct irp *irp)
|
||||
{
|
||||
HANDLE fd;
|
||||
HRESULT hr;
|
||||
|
||||
dprintf("Security: %s\n", __func__);
|
||||
|
||||
fd = CreateFileW(
|
||||
nusec_cfg.billing_ca,
|
||||
GENERIC_READ,
|
||||
FILE_SHARE_READ,
|
||||
NULL,
|
||||
OPEN_EXISTING,
|
||||
FILE_ATTRIBUTE_NORMAL,
|
||||
NULL);
|
||||
|
||||
if (fd == INVALID_HANDLE_VALUE) {
|
||||
hr = HRESULT_FROM_WIN32(GetLastError());
|
||||
dprintf("Error opening CA cert file: %x\n", (int) hr);
|
||||
|
||||
return hr;
|
||||
}
|
||||
|
||||
/* Transform this ioctl into a read from that file and pass the IRP on */
|
||||
|
||||
irp->op = IRP_OP_READ;
|
||||
irp->fd = fd;
|
||||
|
||||
dprintf(">>> %p:%i/%i\n",
|
||||
irp->read.bytes,
|
||||
(int) irp->read.pos,
|
||||
(int) irp->read.nbytes);
|
||||
|
||||
hr = iohook_invoke_next(irp);
|
||||
|
||||
dprintf("<<< %p:%i/%i\n",
|
||||
irp->read.bytes,
|
||||
(int) irp->read.pos,
|
||||
(int) irp->read.nbytes);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
dprintf("ReadFile transformation failed: %x\n", (int) hr);
|
||||
}
|
||||
|
||||
CloseHandle(fd);
|
||||
|
||||
return hr;
|
||||
}
|
||||
|
||||
static HRESULT nusec_ioctl_get_billing_pubkey(struct irp *irp)
|
||||
{
|
||||
HANDLE fd;
|
||||
HRESULT hr;
|
||||
|
||||
dprintf("Security: %s\n", __func__);
|
||||
|
||||
fd = CreateFileW(
|
||||
nusec_cfg.billing_pub,
|
||||
GENERIC_READ,
|
||||
FILE_SHARE_READ,
|
||||
NULL,
|
||||
OPEN_EXISTING,
|
||||
FILE_ATTRIBUTE_NORMAL,
|
||||
NULL);
|
||||
|
||||
if (fd == INVALID_HANDLE_VALUE) {
|
||||
hr = HRESULT_FROM_WIN32(GetLastError());
|
||||
dprintf("Error opening billing pubkey file: %x\n", (int) hr);
|
||||
|
||||
return hr;
|
||||
}
|
||||
|
||||
irp->op = IRP_OP_READ;
|
||||
irp->fd = fd;
|
||||
|
||||
dprintf(">>> %p:%i/%i\n",
|
||||
irp->read.bytes,
|
||||
(int) irp->read.pos,
|
||||
(int) irp->read.nbytes);
|
||||
|
||||
hr = iohook_invoke_next(irp);
|
||||
|
||||
dprintf("<<< %p:%i/%i\n",
|
||||
irp->read.bytes,
|
||||
(int) irp->read.pos,
|
||||
(int) irp->read.nbytes);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
dprintf("ReadFile transformation failed: %x\n", (int) hr);
|
||||
}
|
||||
|
||||
CloseHandle(fd);
|
||||
|
||||
return hr;
|
||||
}
|
||||
|
||||
static HRESULT nusec_ioctl_get_nearfull(struct irp *irp)
|
||||
{
|
||||
dprintf("Security: %s\n", __func__);
|
||||
|
||||
return iobuf_write_le32(&irp->read, nusec_nearfull);
|
||||
}
|
||||
|
||||
static HRESULT nusec_ioctl_get_nvram_available(struct irp *irp)
|
||||
{
|
||||
size_t used;
|
||||
size_t avail;
|
||||
|
||||
used = nusec_log_head - nusec_log_tail;
|
||||
avail = _countof(nusec_log) - used;
|
||||
|
||||
dprintf("Security: %s: used=%i avail=%i\n", __func__,
|
||||
(int) used,
|
||||
(int) avail);
|
||||
|
||||
return iobuf_write_le32(&irp->read, avail);
|
||||
}
|
||||
|
||||
static HRESULT nusec_ioctl_get_nvram_geometry(struct irp *irp)
|
||||
{
|
||||
HRESULT hr;
|
||||
|
||||
dprintf("Security: %s\n", __func__);
|
||||
|
||||
iobuf_write_le32(&irp->read, 10); /* Num NVRAMs */
|
||||
hr = iobuf_write_le32(&irp->read, 4096); /* Num addresses (per NVRAM?) */
|
||||
|
||||
return hr;
|
||||
}
|
||||
|
||||
static HRESULT nusec_ioctl_get_play_count(struct irp *irp)
|
||||
{
|
||||
dprintf("Security: %s\n", __func__);
|
||||
|
||||
return iobuf_write_le32(&irp->read, nusec_play_count);
|
||||
}
|
||||
|
||||
static HRESULT nusec_ioctl_get_play_limit(struct irp *irp)
|
||||
{
|
||||
dprintf("Security: %s\n", __func__);
|
||||
|
||||
return iobuf_write_le32(&irp->read, nusec_play_limit);
|
||||
}
|
||||
|
||||
static HRESULT nusec_ioctl_get_trace_log_data(struct irp *irp)
|
||||
{
|
||||
uint32_t pos;
|
||||
uint32_t count;
|
||||
size_t avail;
|
||||
HRESULT hr;
|
||||
|
||||
dprintf("Security: %s\n", __func__);
|
||||
|
||||
hr = iobuf_read_le32(&irp->write, &pos);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
hr = iobuf_read_le32(&irp->write, &count);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
dprintf(" Params: %i %i Buf: %i\n", pos, count, (int) irp->read.nbytes);
|
||||
|
||||
avail = irp->read.nbytes - irp->read.pos;
|
||||
|
||||
if (avail < count * sizeof(struct nusec_log_record)) {
|
||||
dprintf("\tError: Insufficient buffer\n");
|
||||
|
||||
return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
|
||||
}
|
||||
|
||||
while (count > 0 && pos != nusec_log_head) {
|
||||
memcpy( &irp->read.bytes[irp->read.pos],
|
||||
&nusec_log[pos % _countof(nusec_log)],
|
||||
sizeof(struct nusec_log_record));
|
||||
|
||||
irp->read.pos += sizeof(struct nusec_log_record);
|
||||
pos++;
|
||||
count--;
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static HRESULT nusec_ioctl_get_trace_log_state(struct irp *irp)
|
||||
{
|
||||
HRESULT hr;
|
||||
|
||||
dprintf("Security: %s H: %i T: %i\n",
|
||||
__func__,
|
||||
(int) nusec_log_head,
|
||||
(int) nusec_log_tail);
|
||||
|
||||
iobuf_write_le32(&irp->read, nusec_log_head - nusec_log_tail);
|
||||
hr = iobuf_write_le32(&irp->read, nusec_log_tail);
|
||||
|
||||
return hr;
|
||||
}
|
||||
|
||||
static HRESULT nusec_ioctl_put_nearfull(struct irp *irp)
|
||||
{
|
||||
dprintf("Security: %s\n", __func__);
|
||||
dump_const_iobuf(&irp->write);
|
||||
|
||||
return iobuf_read_le32(&irp->write, &nusec_nearfull);
|
||||
}
|
||||
|
||||
static HRESULT nusec_ioctl_put_play_limit(struct irp *irp)
|
||||
{
|
||||
dprintf("Security: %s\n", __func__);
|
||||
dump_const_iobuf(&irp->write);
|
||||
|
||||
return iobuf_read_le32(&irp->write, &nusec_play_limit);
|
||||
}
|
||||
|
||||
static HRESULT nusec_ioctl_put_trace_log_data(struct irp *irp)
|
||||
{
|
||||
dprintf("Security: %s\n", __func__);
|
||||
dump_const_iobuf(&irp->write);
|
||||
|
||||
if (irp->write.nbytes != sizeof(struct nusec_log_record)) {
|
||||
dprintf(" Log record size is incorrect\n");
|
||||
|
||||
return E_INVALIDARG;
|
||||
}
|
||||
|
||||
if (nusec_log_head - nusec_log_tail >= _countof(nusec_log)) {
|
||||
dprintf(" Log buffer is full!\n");
|
||||
|
||||
return HRESULT_FROM_WIN32(ERROR_DISK_FULL);
|
||||
}
|
||||
|
||||
memcpy( &nusec_log[nusec_log_head % _countof(nusec_log)],
|
||||
irp->write.bytes,
|
||||
sizeof(struct nusec_log_record));
|
||||
|
||||
nusec_log_head++;
|
||||
|
||||
dprintf(" H: %i T: %i\n", (int) nusec_log_head, (int) nusec_log_tail);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static HRESULT nusec_reg_read_game_id(void *bytes, uint32_t *nbytes)
|
||||
{
|
||||
return reg_hook_read_bin(
|
||||
bytes,
|
||||
nbytes,
|
||||
&nusec_cfg.game_id,
|
||||
sizeof(nusec_cfg.game_id));
|
||||
}
|
||||
|
||||
static HRESULT nusec_reg_read_keychip_id(void *bytes, uint32_t *nbytes)
|
||||
{
|
||||
return reg_hook_read_bin(
|
||||
bytes,
|
||||
nbytes,
|
||||
&nusec_cfg.keychip_id,
|
||||
sizeof(nusec_cfg.keychip_id));
|
||||
}
|
||||
|
||||
static HRESULT nusec_reg_read_model_type(void *bytes, uint32_t *nbytes)
|
||||
{
|
||||
uint32_t u32;
|
||||
char c;
|
||||
|
||||
c = nusec_cfg.platform_id[3];
|
||||
|
||||
if (c >= '0' && c <= '9') {
|
||||
u32 = c - '0';
|
||||
} else {
|
||||
u32 = 0;
|
||||
}
|
||||
|
||||
return reg_hook_read_u32(bytes, nbytes, u32);
|
||||
}
|
||||
|
||||
static HRESULT nusec_reg_read_platform_id(void *bytes, uint32_t *nbytes)
|
||||
{
|
||||
/* Fourth byte is a separate registry val (modelType).
|
||||
We store it in the same config field for ease of configuration. */
|
||||
|
||||
return reg_hook_read_bin(
|
||||
bytes,
|
||||
nbytes,
|
||||
&nusec_cfg.platform_id,
|
||||
3);
|
||||
}
|
||||
|
||||
static HRESULT nusec_reg_read_region(void *bytes, uint32_t *nbytes)
|
||||
{
|
||||
return reg_hook_read_u32(bytes, nbytes, nusec_cfg.region);
|
||||
}
|
||||
|
||||
static HRESULT nusec_reg_read_server_ip_ipv4(void *bytes, uint32_t *nbytes)
|
||||
{
|
||||
uint32_t subnet;
|
||||
|
||||
subnet = _byteswap_ulong(nusec_cfg.subnet);
|
||||
|
||||
return reg_hook_read_bin(bytes, nbytes, &subnet, sizeof(subnet));
|
||||
}
|
||||
|
||||
static HRESULT nusec_reg_read_server_ip_ipv6(void *bytes, uint32_t *nbytes)
|
||||
{
|
||||
uint8_t subnet[16];
|
||||
|
||||
memset(subnet, 0, sizeof(subnet));
|
||||
|
||||
return reg_hook_read_bin(bytes, nbytes, subnet, sizeof(subnet));
|
||||
}
|
||||
|
||||
static HRESULT nusec_reg_read_system_flag(void *bytes, uint32_t *nbytes)
|
||||
{
|
||||
return reg_hook_read_u32(bytes, nbytes, nusec_cfg.system_flag);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
struct nusec_config {
|
||||
bool enable;
|
||||
char keychip_id[16];
|
||||
char game_id[4];
|
||||
char platform_id[4];
|
||||
uint8_t region;
|
||||
uint8_t system_flag;
|
||||
uint32_t subnet;
|
||||
uint16_t billing_type;
|
||||
wchar_t billing_ca[MAX_PATH];
|
||||
wchar_t billing_pub[MAX_PATH];
|
||||
};
|
||||
|
||||
HRESULT nusec_hook_init(
|
||||
const struct nusec_config *cfg,
|
||||
const char *game_id,
|
||||
const char *platform_id);
|
||||
@@ -0,0 +1,70 @@
|
||||
#include <windows.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "hook/table.h"
|
||||
|
||||
#include "platform/pcbid.h"
|
||||
|
||||
#include "util/dprintf.h"
|
||||
|
||||
static BOOL WINAPI pcbid_GetComputerNameA(char *dest, uint32_t *len);
|
||||
|
||||
static struct pcbid_config pcbid_cfg;
|
||||
|
||||
static const struct hook_symbol pcbid_syms[] = {
|
||||
{
|
||||
.name = "GetComputerNameA",
|
||||
.patch = pcbid_GetComputerNameA,
|
||||
}
|
||||
};
|
||||
|
||||
HRESULT pcbid_hook_init(const struct pcbid_config *cfg)
|
||||
{
|
||||
assert(cfg != NULL);
|
||||
|
||||
if (!cfg->enable) {
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
if (wcslen(cfg->serial_no) != 15) {
|
||||
dprintf("Pcbid: ERROR: Must be 15 chars! ex: ACAE01A99999999\n");
|
||||
|
||||
return E_INVALIDARG;
|
||||
}
|
||||
|
||||
memcpy(&pcbid_cfg, cfg, sizeof(*cfg));
|
||||
hook_table_apply(NULL, "kernel32.dll", pcbid_syms, _countof(pcbid_syms));
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static BOOL WINAPI pcbid_GetComputerNameA(char *dest, uint32_t *len)
|
||||
{
|
||||
size_t required;
|
||||
|
||||
if (dest == NULL || len == NULL) {
|
||||
SetLastError(ERROR_INVALID_PARAMETER);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
wcstombs_s(&required, NULL, 0, pcbid_cfg.serial_no, 0);
|
||||
|
||||
if (required > *len) {
|
||||
SetLastError(ERROR_INSUFFICIENT_BUFFER);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
dprintf("Pcbid: Get PCB serial\n");
|
||||
|
||||
wcstombs_s(NULL, dest, *len, pcbid_cfg.serial_no, *len - 1);
|
||||
SetLastError(ERROR_SUCCESS);
|
||||
*len = required - 1;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
struct pcbid_config {
|
||||
bool enable;
|
||||
wchar_t serial_no[17];
|
||||
};
|
||||
|
||||
HRESULT pcbid_hook_init(const struct pcbid_config *cfg);
|
||||
@@ -0,0 +1,98 @@
|
||||
#include <windows.h>
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include "platform/amvideo.h"
|
||||
#include "platform/clock.h"
|
||||
#include "platform/dns.h"
|
||||
#include "platform/epay.h"
|
||||
#include "platform/hwmon.h"
|
||||
#include "platform/misc.h"
|
||||
#include "platform/netenv.h"
|
||||
#include "platform/nusec.h"
|
||||
#include "platform/pcbid.h"
|
||||
#include "platform/platform.h"
|
||||
#include "platform/vfs.h"
|
||||
#include "platform/system.h"
|
||||
|
||||
HRESULT platform_hook_init(
|
||||
const struct platform_config *cfg,
|
||||
const char *game_id,
|
||||
const char *platform_id,
|
||||
HMODULE redir_mod)
|
||||
{
|
||||
HRESULT hr;
|
||||
|
||||
assert(cfg != NULL);
|
||||
assert(game_id != NULL);
|
||||
assert(platform_id != NULL);
|
||||
assert(redir_mod != NULL);
|
||||
|
||||
hr = amvideo_hook_init(&cfg->amvideo, redir_mod);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
hr = clock_hook_init(&cfg->clock);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
hr = dns_platform_hook_init(&cfg->dns);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
hr = hwmon_hook_init(&cfg->hwmon);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
hr = misc_hook_init(&cfg->misc, platform_id);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
hr = netenv_hook_init(&cfg->netenv, &cfg->nusec);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
hr = nusec_hook_init(&cfg->nusec, game_id, platform_id);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
hr = pcbid_hook_init(&cfg->pcbid);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
hr = vfs_hook_init(&cfg->vfs, game_id);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
hr = system_init(&cfg->system, &cfg->vfs);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
hr = epay_hook_init(&cfg->epay);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include "platform/amvideo.h"
|
||||
#include "platform/clock.h"
|
||||
#include "platform/dns.h"
|
||||
#include "platform/epay.h"
|
||||
#include "platform/hwmon.h"
|
||||
#include "platform/hwreset.h"
|
||||
#include "platform/misc.h"
|
||||
#include "platform/netenv.h"
|
||||
#include "platform/nusec.h"
|
||||
#include "platform/pcbid.h"
|
||||
#include "platform/vfs.h"
|
||||
#include "platform/system.h"
|
||||
|
||||
struct platform_config {
|
||||
struct amvideo_config amvideo;
|
||||
struct clock_config clock;
|
||||
struct dns_config dns;
|
||||
struct epay_config epay;
|
||||
struct hwmon_config hwmon;
|
||||
struct hwreset_config hwreset;
|
||||
struct misc_config misc;
|
||||
struct pcbid_config pcbid;
|
||||
struct netenv_config netenv;
|
||||
struct nusec_config nusec;
|
||||
struct vfs_config vfs;
|
||||
struct system_config system;
|
||||
};
|
||||
|
||||
HRESULT platform_hook_init(
|
||||
const struct platform_config *cfg,
|
||||
const char *game_id,
|
||||
const char *platform_id,
|
||||
HMODULE redir_mod);
|
||||
@@ -0,0 +1,183 @@
|
||||
#include "platform/system.h"
|
||||
|
||||
#include <windows.h>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "platform/vfs.h"
|
||||
#include "util/crc.h"
|
||||
#include "util/dprintf.h"
|
||||
#include "util/str.h"
|
||||
|
||||
#define DATA_SIZE 503
|
||||
#define BLOCK_SIZE (sizeof(uint32_t) + 4 + 1 + DATA_SIZE)
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
||||
typedef struct {
|
||||
uint32_t checksum;
|
||||
char padding[6];
|
||||
uint8_t freeplay;
|
||||
char data[DATA_SIZE - 2];
|
||||
} CreditBlock;
|
||||
|
||||
typedef struct {
|
||||
uint32_t checksum;
|
||||
char padding[4];
|
||||
uint8_t dip_switches;
|
||||
char data[DATA_SIZE];
|
||||
} DipSwBlock;
|
||||
|
||||
typedef struct {
|
||||
CreditBlock credit_block;
|
||||
DipSwBlock dip_switch_block;
|
||||
char *data;
|
||||
} SystemInfo;
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
static SystemInfo system_info;
|
||||
|
||||
static struct system_config system_config;
|
||||
static struct vfs_config vfs_config;
|
||||
|
||||
static void system_read_sysfile(const wchar_t *sys_file);
|
||||
static void system_save_sysfile(const wchar_t *sys_file);
|
||||
|
||||
HRESULT system_init(const struct system_config *cfg,
|
||||
const struct vfs_config *vfs_cfg) {
|
||||
HRESULT hr;
|
||||
wchar_t sys_file_path[MAX_PATH];
|
||||
|
||||
assert(cfg != NULL);
|
||||
assert(vfs_cfg != NULL);
|
||||
|
||||
if (!cfg->enable) {
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
memcpy(&system_config, cfg, sizeof(*cfg));
|
||||
|
||||
sys_file_path[0] = L'\0';
|
||||
// concatenate vfs_config.amfs with L"sysfile.dat"
|
||||
wcsncpy(sys_file_path, vfs_cfg->amfs, MAX_PATH);
|
||||
wcsncat(sys_file_path, L"\\sysfile.dat", MAX_PATH);
|
||||
|
||||
system_read_sysfile(sys_file_path);
|
||||
|
||||
// now write the system_config.system to the dip_switch_block
|
||||
system_save_sysfile(sys_file_path);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static void system_read_sysfile(const wchar_t *sys_file) {
|
||||
FILE *f = _wfopen(sys_file, L"r");
|
||||
|
||||
if (f == NULL) {
|
||||
dprintf(
|
||||
"System: First run detected, system settings can only be applied "
|
||||
"AFTER the first run\n");
|
||||
return;
|
||||
}
|
||||
|
||||
fseek(f, 0, SEEK_END);
|
||||
long file_size = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
|
||||
if (file_size != 0x6000) {
|
||||
dprintf("System: Invalid sysfile.dat file size\n");
|
||||
fclose(f);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
system_info.data = malloc(file_size);
|
||||
fread(system_info.data, 1, file_size, f);
|
||||
fclose(f);
|
||||
|
||||
// copy the credit_block and dip_switch_block from the sysfile.dat
|
||||
memcpy(&system_info.credit_block, system_info.data, BLOCK_SIZE);
|
||||
memcpy(&system_info.dip_switch_block, system_info.data + 0x2800,
|
||||
BLOCK_SIZE);
|
||||
}
|
||||
|
||||
static void system_save_sysfile(const wchar_t *sys_file) {
|
||||
char block[BLOCK_SIZE];
|
||||
DWORD sysfile_bytes_written = 0;
|
||||
uint8_t system = 0;
|
||||
uint8_t freeplay = 0;
|
||||
|
||||
HANDLE h_sysfile = CreateFileW(sys_file, GENERIC_READ | GENERIC_WRITE, 0, NULL, 0, 0, NULL);
|
||||
|
||||
if (h_sysfile == INVALID_HANDLE_VALUE) {
|
||||
return;
|
||||
}
|
||||
|
||||
// write the system_config.system to the dip_switch_block
|
||||
for (int i = 0; i < 8; i++) {
|
||||
// print the dip switch configuration with labels if present
|
||||
if (system_config.dipsw_config[i].label[0] != L'\0') {
|
||||
dprintf("System: %ls: %ls\n", system_config.dipsw_config[i].label,
|
||||
system_config.dipsw[i] ? system_config.dipsw_config[i].on
|
||||
: system_config.dipsw_config[i].off);
|
||||
} else if (system_config.dipsw[i]) {
|
||||
dprintf("System: DipSw%d=1 set\n", i + 1);
|
||||
}
|
||||
|
||||
// set the system variable to the dip switch configuration
|
||||
if (system_config.dipsw[i]) {
|
||||
system |= (1 << i);
|
||||
}
|
||||
}
|
||||
|
||||
if (system_config.freeplay) {
|
||||
// print that freeplay is enabled
|
||||
dprintf("System: Freeplay enabled\n");
|
||||
freeplay = 1;
|
||||
}
|
||||
|
||||
// set the new credit block
|
||||
system_info.credit_block.freeplay = freeplay;
|
||||
// set the new dip_switch_block
|
||||
system_info.dip_switch_block.dip_switches = system;
|
||||
|
||||
// calculate the new checksum, skip the old crc32 value
|
||||
// which is at the beginning of the block, thats's why the +4
|
||||
// conver the struct to chars in order for the crc32 calculation to work
|
||||
system_info.credit_block.checksum =
|
||||
crc32((char *)&system_info.credit_block + 4, BLOCK_SIZE - 4, 0);
|
||||
system_info.dip_switch_block.checksum =
|
||||
crc32((char *)&system_info.dip_switch_block + 4, BLOCK_SIZE - 4, 0);
|
||||
|
||||
// build the new credit block
|
||||
memcpy(block, (char *)&system_info.credit_block, BLOCK_SIZE);
|
||||
|
||||
memcpy(system_info.data, block, BLOCK_SIZE);
|
||||
memcpy(system_info.data + 0x3000, block, BLOCK_SIZE);
|
||||
|
||||
// build the new dip switch block
|
||||
memcpy(block, (char *)&system_info.dip_switch_block, BLOCK_SIZE);
|
||||
|
||||
memcpy(system_info.data + 0x2800, block, BLOCK_SIZE);
|
||||
memcpy(system_info.data + 0x5800, block, BLOCK_SIZE);
|
||||
|
||||
// print the dip_switch_block in hex
|
||||
/*
|
||||
dprintf("System Block: ");
|
||||
for (size_t i = 0; i < BLOCK_SIZE; i++)
|
||||
{
|
||||
dprintf("%02X ", ((uint8_t *)&system_info.dip_switch_block)[i]);
|
||||
}
|
||||
dprintf("\n");
|
||||
*/
|
||||
|
||||
WriteFile(h_sysfile, system_info.data, 0x6000, &sysfile_bytes_written, NULL);
|
||||
CloseHandle(h_sysfile);
|
||||
|
||||
if (sysfile_bytes_written != 0x6000) {
|
||||
dprintf("System: Only 0x%04lX bytes written out of 0x6000!\n", sysfile_bytes_written);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "platform/vfs.h"
|
||||
|
||||
|
||||
struct dipsw_config
|
||||
{
|
||||
wchar_t label[MAX_PATH];
|
||||
wchar_t on[MAX_PATH];
|
||||
wchar_t off[MAX_PATH];
|
||||
};
|
||||
|
||||
|
||||
struct system_config {
|
||||
bool enable;
|
||||
bool freeplay;
|
||||
bool dipsw[8];
|
||||
struct dipsw_config dipsw_config[8];
|
||||
};
|
||||
|
||||
HRESULT system_init(const struct system_config *cfg, const struct vfs_config *vfs_cfg);
|
||||
@@ -0,0 +1,543 @@
|
||||
#include <windows.h>
|
||||
#include <shlwapi.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "hooklib/path.h"
|
||||
#include "hooklib/reg.h"
|
||||
|
||||
#include "hook/procaddr.h"
|
||||
#include "hook/table.h"
|
||||
|
||||
#include "platform/vfs.h"
|
||||
|
||||
#include "util/dprintf.h"
|
||||
|
||||
static void vfs_fixup_path(wchar_t *path, size_t max_count);
|
||||
static HRESULT vfs_mkdir_rec(const wchar_t *path);
|
||||
static HRESULT vfs_path_hook(const wchar_t *src, wchar_t *dest, size_t *count);
|
||||
static HRESULT vfs_path_hook_nthome(
|
||||
const wchar_t *src,
|
||||
wchar_t *dest,
|
||||
size_t *count);
|
||||
static HRESULT vfs_path_hook_w10home(
|
||||
const wchar_t *src,
|
||||
wchar_t *dest,
|
||||
size_t *count);
|
||||
static HRESULT vfs_path_hook_option(
|
||||
const wchar_t *src,
|
||||
wchar_t *dest,
|
||||
size_t *count);
|
||||
static HRESULT vfs_reg_read_amfs(void *bytes, uint32_t *nbytes);
|
||||
static HRESULT vfs_reg_read_appdata(void *bytes, uint32_t *nbytes);
|
||||
|
||||
static wchar_t* hook_System_getAppRootPath();
|
||||
static wchar_t* (*next_System_getAppRootPath)();
|
||||
|
||||
static wchar_t* hook_AppImage_getOptionMountRootPath();
|
||||
static wchar_t* (*next_AppImage_getOptionMountRootPath)();
|
||||
|
||||
static const struct hook_symbol amdaemon_syms[] = {
|
||||
{
|
||||
.name = "System_getAppRootPath",
|
||||
.patch = hook_System_getAppRootPath,
|
||||
.link = (void **) &next_System_getAppRootPath,
|
||||
},
|
||||
{
|
||||
.name = "AppImage_getOptionMountRootPath",
|
||||
.patch = hook_AppImage_getOptionMountRootPath,
|
||||
.link = (void **) &next_AppImage_getOptionMountRootPath,
|
||||
},
|
||||
};
|
||||
|
||||
static wchar_t game[5] = {0};
|
||||
static wchar_t vfs_nthome_real[MAX_PATH];
|
||||
static const wchar_t vfs_nthome[] = L"C:\\Documents and Settings\\AppUser";
|
||||
static const size_t vfs_nthome_len = _countof(vfs_nthome) - 1;
|
||||
|
||||
static const wchar_t vfs_w10home[] = L"C:\\Users\\AppUser";
|
||||
static const size_t vfs_w10home_len = _countof(vfs_w10home) - 1;
|
||||
|
||||
static const wchar_t vfs_option[] = L"C:\\Mount\\Option";
|
||||
static const size_t vfs_option_len = _countof(vfs_option) - 1;
|
||||
|
||||
static const struct reg_hook_val vfs_reg_vals[] = {
|
||||
{
|
||||
.name = L"AMFS",
|
||||
.read = vfs_reg_read_amfs,
|
||||
.type = REG_SZ,
|
||||
}, {
|
||||
.name = L"APPDATA",
|
||||
.read = vfs_reg_read_appdata,
|
||||
.type = REG_SZ
|
||||
},
|
||||
};
|
||||
|
||||
static struct vfs_config vfs_config;
|
||||
|
||||
HRESULT vfs_hook_init(const struct vfs_config *config, const char* game_id)
|
||||
{
|
||||
wchar_t temp[MAX_PATH];
|
||||
size_t nthome_len;
|
||||
DWORD home_ok;
|
||||
HRESULT hr;
|
||||
|
||||
assert(config != NULL);
|
||||
|
||||
if (!config->enable) {
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
mbstowcs(game, game_id, 4);
|
||||
|
||||
if (config->amfs[0] == L'\0') {
|
||||
dprintf("Vfs: FATAL: AMFS path not specified in INI file\n");
|
||||
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
if (config->appdata[0] == L'\0') {
|
||||
dprintf("Vfs: FATAL: APPDATA path not specified in INI file\n");
|
||||
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
if (config->option[0] == L'\0') {
|
||||
dprintf("Vfs: WARNING: OPTION path not specified in INI file\n");
|
||||
} else if (!PathFileExistsW(config->option)) {
|
||||
dprintf("Vfs: FATAL: OPTION path does not exist\n");
|
||||
dprintf(" Configured: \"%ls\"\n", config->option);
|
||||
GetFullPathNameW(config->option, _countof(temp), temp, NULL);
|
||||
dprintf(" Expanded: \"%ls\"\n", temp);
|
||||
|
||||
return E_FAIL;
|
||||
} else if (!(GetFileAttributesW(config->option) & FILE_ATTRIBUTE_DIRECTORY)) {
|
||||
dprintf("Vfs: FATAL: OPTION path doesn't point to a directory\n");
|
||||
dprintf(" Configured: \"%ls\"\n", config->option);
|
||||
GetFullPathNameW(config->option, _countof(temp), temp, NULL);
|
||||
dprintf(" Expanded: \"%ls\"\n", temp);
|
||||
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
home_ok = GetEnvironmentVariableW(
|
||||
L"USERPROFILE",
|
||||
vfs_nthome_real,
|
||||
_countof(vfs_nthome_real));
|
||||
|
||||
if (!home_ok) {
|
||||
hr = HRESULT_FROM_WIN32(GetLastError());
|
||||
dprintf("Vfs: Failed to query %%USERPROFILE%% env var: %x\n",
|
||||
(int) hr);
|
||||
|
||||
return hr;
|
||||
}
|
||||
|
||||
memcpy(&vfs_config, config, sizeof(*config));
|
||||
|
||||
vfs_fixup_path(vfs_nthome_real, _countof(vfs_nthome_real));
|
||||
vfs_fixup_path(vfs_config.amfs, _countof(vfs_config.amfs));
|
||||
vfs_fixup_path(vfs_config.appdata, _countof(vfs_config.appdata));
|
||||
|
||||
if (vfs_config.option[0] != L'\0') {
|
||||
vfs_fixup_path(vfs_config.option, _countof(vfs_config.option));
|
||||
}
|
||||
|
||||
hr = vfs_mkdir_rec(vfs_config.amfs);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
dprintf("Vfs: Failed to create AMFS dir %S: %x\n",
|
||||
config->amfs,
|
||||
(int) hr);
|
||||
}
|
||||
|
||||
hr = vfs_mkdir_rec(vfs_config.appdata);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
dprintf("Vfs: Failed to create APPDATA dir %S: %x\n",
|
||||
config->appdata,
|
||||
(int) hr);
|
||||
|
||||
dprintf("Vfs: NOTE: SEGA Y: drive APPDATA, not Windows %%APPDATA%%.\n");
|
||||
}
|
||||
|
||||
/* Need to create the temp subdirectory, not just nthome itself */
|
||||
|
||||
nthome_len = wcslen(vfs_nthome_real);
|
||||
wcscpy_s(temp, _countof(temp), vfs_nthome_real);
|
||||
wcscpy_s(temp + nthome_len, _countof(temp) - nthome_len, L"temp");
|
||||
|
||||
hr = vfs_mkdir_rec(temp);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
dprintf("Vfs: Failed to create %S: %x\n", temp, (int) hr);
|
||||
}
|
||||
|
||||
/* Not auto-creating option directory as it is normally a read-only mount */
|
||||
|
||||
hr = path_hook_push(vfs_path_hook);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
hr = path_hook_push(vfs_path_hook_nthome);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
hr = path_hook_push(vfs_path_hook_w10home);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
if (vfs_config.option[0] != L'\0') {
|
||||
hr = path_hook_push(vfs_path_hook_option);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
}
|
||||
|
||||
hr = reg_hook_push_key(
|
||||
HKEY_LOCAL_MACHINE,
|
||||
L"SYSTEM\\SEGA\\SystemProperty\\mount",
|
||||
vfs_reg_vals,
|
||||
_countof(vfs_reg_vals));
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
proc_addr_table_push(
|
||||
NULL,
|
||||
"amdaemon_api.dll",
|
||||
amdaemon_syms,
|
||||
_countof(amdaemon_syms)
|
||||
);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static void vfs_fixup_path(wchar_t *path, size_t max_count)
|
||||
{
|
||||
size_t count;
|
||||
wchar_t abspath[MAX_PATH];
|
||||
|
||||
assert(path != NULL);
|
||||
/* Requirement for PathIsRelativeW */
|
||||
assert(max_count <= MAX_PATH);
|
||||
|
||||
if (PathIsRelativeW(path)) {
|
||||
count = GetFullPathNameW(path, _countof(abspath), abspath, NULL);
|
||||
|
||||
/* GetFullPathName's length return value is tricky, because it includes
|
||||
the NUL terminator on failure, but doesn't on success.
|
||||
Check if it fits the temp buf (else it's a failure and includes NUL
|
||||
anyway), then if it fits the target buf, NUL included. */
|
||||
if (count == 0 || count > _countof(abspath) || count >= max_count) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
wcscpy_s(path, max_count, abspath);
|
||||
} else {
|
||||
count = wcslen(path);
|
||||
}
|
||||
|
||||
if (path_is_separator_w(path[count - 1])) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (count + 2 > max_count) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
path[count + 0] = L'\\';
|
||||
path[count + 1] = L'\0';
|
||||
return;
|
||||
|
||||
fail:
|
||||
dprintf("Vfs: FATAL: Path too long: %S\n", path);
|
||||
abort();
|
||||
}
|
||||
|
||||
static HRESULT vfs_mkdir_rec(const wchar_t *path)
|
||||
{
|
||||
wchar_t *copy;
|
||||
wchar_t *pos;
|
||||
wchar_t wc;
|
||||
HRESULT hr;
|
||||
DWORD attr;
|
||||
BOOL ok;
|
||||
|
||||
assert(path != NULL);
|
||||
|
||||
copy = _wcsdup(path);
|
||||
|
||||
if (copy == NULL) {
|
||||
hr = HRESULT_FROM_WIN32(ERROR_OUTOFMEMORY);
|
||||
|
||||
goto end;
|
||||
}
|
||||
|
||||
pos = copy;
|
||||
|
||||
do {
|
||||
wc = *pos;
|
||||
|
||||
if (wc == L'\0' || wc == L'/' || wc == L'\\') {
|
||||
*pos = L'\0';
|
||||
attr = GetFileAttributesW(copy);
|
||||
|
||||
if (attr == INVALID_FILE_ATTRIBUTES) {
|
||||
ok = CreateDirectoryW(copy, NULL);
|
||||
|
||||
if (!ok) {
|
||||
hr = HRESULT_FROM_WIN32(GetLastError());
|
||||
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
|
||||
*pos = wc;
|
||||
}
|
||||
|
||||
pos++;
|
||||
} while (wc != L'\0');
|
||||
|
||||
hr = S_OK;
|
||||
|
||||
end:
|
||||
free(copy);
|
||||
|
||||
return hr;
|
||||
}
|
||||
|
||||
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':') {
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
switch (src[0]) {
|
||||
case L'e':
|
||||
case L'E':
|
||||
redir = vfs_config.amfs;
|
||||
|
||||
break;
|
||||
|
||||
case L'y':
|
||||
case L'Y':
|
||||
redir = vfs_config.appdata;
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
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) - src_len + redir_len + 1;
|
||||
|
||||
if (dest != NULL) {
|
||||
if (required > *count) {
|
||||
return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
|
||||
}
|
||||
|
||||
wcscpy_s(dest, *count, redir);
|
||||
wcscpy_s(dest + redir_len, *count - redir_len, src + src_len);
|
||||
}
|
||||
|
||||
*count = required;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static HRESULT vfs_path_hook_nthome(
|
||||
const wchar_t *src,
|
||||
wchar_t *dest,
|
||||
size_t *count)
|
||||
{
|
||||
size_t required;
|
||||
size_t redir_len;
|
||||
size_t shift;
|
||||
|
||||
assert(src != NULL);
|
||||
assert(count != NULL);
|
||||
|
||||
/* Case-insensitive check to see if src starts with vfs_nthome */
|
||||
|
||||
if (path_compare_w(src, vfs_nthome, vfs_nthome_len) != 0) {
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
/* Check if the character after vfs_nthome is a separator or the end of
|
||||
the string */
|
||||
|
||||
if (!path_is_separator_w(src[vfs_nthome_len]) &&
|
||||
src[vfs_nthome_len] != L'\0')
|
||||
{
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
/* Cut off the matched <prefix>\, add the replaced prefix, count NUL */
|
||||
|
||||
shift = path_is_separator_w(src[vfs_nthome_len]) ? 1 : 0;
|
||||
redir_len = wcslen(vfs_nthome_real);
|
||||
required = wcslen(src) - vfs_nthome_len - shift + redir_len + 1;
|
||||
|
||||
if (dest != NULL) {
|
||||
if (required > *count) {
|
||||
return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
|
||||
}
|
||||
|
||||
wcscpy_s(dest, *count, vfs_nthome_real);
|
||||
wcscpy_s(dest + redir_len, *count - redir_len, src + vfs_nthome_len + shift);
|
||||
}
|
||||
|
||||
*count = required;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static HRESULT vfs_path_hook_w10home(
|
||||
const wchar_t *src,
|
||||
wchar_t *dest,
|
||||
size_t *count)
|
||||
{
|
||||
size_t required;
|
||||
size_t redir_len;
|
||||
size_t shift;
|
||||
|
||||
assert(src != NULL);
|
||||
assert(count != NULL);
|
||||
|
||||
/* Case-insensitive check to see if src starts with vfs_w10home */
|
||||
|
||||
if (path_compare_w(src, vfs_w10home, vfs_w10home_len) != 0) {
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
/* Check if the character after vfs_w10home is a separator or the end of
|
||||
the string */
|
||||
|
||||
if (!path_is_separator_w(src[vfs_w10home_len]) &&
|
||||
src[vfs_w10home_len] != L'\0')
|
||||
{
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
/* Cut off the matched <prefix>\, add the replaced prefix, count NUL */
|
||||
|
||||
shift = path_is_separator_w(src[vfs_w10home_len]) ? 1 : 0;
|
||||
redir_len = wcslen(vfs_nthome_real);
|
||||
required = wcslen(src) - vfs_w10home_len - shift + redir_len + 1;
|
||||
|
||||
if (dest != NULL) {
|
||||
if (required > *count) {
|
||||
return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
|
||||
}
|
||||
|
||||
wcscpy_s(dest, *count, vfs_nthome_real);
|
||||
wcscpy_s(dest + redir_len, *count - redir_len, src + vfs_w10home_len + shift);
|
||||
}
|
||||
|
||||
*count = required;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static HRESULT vfs_path_hook_option(
|
||||
const wchar_t *src,
|
||||
wchar_t *dest,
|
||||
size_t *count)
|
||||
{
|
||||
size_t required;
|
||||
size_t redir_len;
|
||||
size_t shift;
|
||||
|
||||
assert(src != NULL);
|
||||
assert(count != NULL);
|
||||
|
||||
/* Case-insensitive check to see if src starts with vfs_option */
|
||||
|
||||
if (path_compare_w(src, vfs_option, vfs_option_len) != 0) {
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
/* Check if the character after vfs_nthome is a separator or the end of
|
||||
the string */
|
||||
|
||||
if (!path_is_separator_w(src[vfs_option_len]) &&
|
||||
src[vfs_option_len] != L'\0')
|
||||
{
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
/* Cut off the matched <prefix>\, add the replaced prefix, count NUL */
|
||||
|
||||
shift = path_is_separator_w(src[vfs_option_len]) ? 1 : 0;
|
||||
redir_len = wcslen(vfs_config.option);
|
||||
required = wcslen(src) - vfs_option_len - shift + redir_len + 1;
|
||||
|
||||
if (dest != NULL) {
|
||||
if (required > *count) {
|
||||
return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
|
||||
}
|
||||
|
||||
wcscpy_s(dest, *count, vfs_config.option);
|
||||
wcscpy_s(dest + redir_len, *count - redir_len, src + vfs_option_len + shift);
|
||||
}
|
||||
|
||||
*count = required;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static HRESULT vfs_reg_read_amfs(void *bytes, uint32_t *nbytes)
|
||||
{
|
||||
return reg_hook_read_wstr(bytes, nbytes, L"E:\\");
|
||||
}
|
||||
|
||||
static HRESULT vfs_reg_read_appdata(void *bytes, uint32_t *nbytes)
|
||||
{
|
||||
return reg_hook_read_wstr(bytes, nbytes, L"Y:\\");
|
||||
}
|
||||
|
||||
static wchar_t* hook_System_getAppRootPath()
|
||||
{
|
||||
wchar_t *path = malloc(sizeof(wchar_t) * MAX_PATH);
|
||||
wcscpy_s(path, MAX_PATH, vfs_config.appdata);
|
||||
wcscat_s(path, MAX_PATH, game);
|
||||
wcscat_s(path, MAX_PATH, L"\\");
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
static wchar_t* hook_AppImage_getOptionMountRootPath()
|
||||
{
|
||||
wchar_t *path = malloc(sizeof(wchar_t) * MAX_PATH);
|
||||
wcscpy_s(path, MAX_PATH, vfs_config.option);
|
||||
|
||||
return path;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
struct vfs_config {
|
||||
bool enable;
|
||||
wchar_t amfs[MAX_PATH];
|
||||
wchar_t appdata[MAX_PATH];
|
||||
wchar_t option[MAX_PATH];
|
||||
};
|
||||
|
||||
HRESULT vfs_hook_init(const struct vfs_config *config, const char* game_id);
|
||||
Reference in New Issue
Block a user