APMv3: add hook (#73)

This adds support for APMv3 I/O, menus and the launcher.

* Added a apm3hook dll and I/O based on the usual layout.
* Added C:\Mount\Apm to vfs.
* Added the relevant .dlls to unityhook.
* Added a hook for apmmount.dll that uses `CreateDosDevice` to mount decrypted data to the locations the launcher and games expect files to be. This will conflict with anything that is already at W:\ and X:\, but I do not have better solutions for this.
* `launch.bat` is a bit more involved as it simulates the launcher loop. It can be broken by alt+f4ing or closing the launcher with "X".
* An extra export was added, so rundll32 can be used to get rid of the dosdevices after the launcher was killed.
* Since all the games do everything via `X:\lib\apm.dll`, no game hooks were needed in testing, therefore, `game.bat` files can be used as is.
* Path hooks are applied correctly, so you can go correctly between games, launcher, sub system test mode and game test modes.

A setup guide (some stuff specific to my server) can be found here:
https://gmg.hopto.org:82/gmg/wiki/index.php/All.Net_P-ras_Multi_Menu

Tested with the 2 APM sample apps, Blazblue, Puyo, Guilty Gear and some weird unity puzzle game whose name I forgot.

![Apmv3System_yLRityJVpm.png](/attachments/3d645e71-81e6-42e6-acd4-63c537cda59e)
![puyoe_hJNhnJGFnd.png](/attachments/01664049-71fe-4c38-9c99-39649ab21e56)

Reviewed-on: TeamTofuShop/segatools#73
Co-authored-by: kyoubate-haruka <46010460+kyoubate-haruka@users.noreply.github.com>
Co-committed-by: kyoubate-haruka <46010460+kyoubate-haruka@users.noreply.github.com>
This commit is contained in:
2025-07-20 09:43:56 +00:00
committed by Dniel97
parent 03513e7b0c
commit e2e4b37e3f
28 changed files with 1658 additions and 3 deletions

View File

@ -17,7 +17,6 @@
static void path_hook_init(void);
static BOOL path_transform_a(char **out, const char *src);
static BOOL path_transform_w(wchar_t **out, const wchar_t *src);
/* API hooks */
@ -490,7 +489,7 @@ end:
return ok;
}
static BOOL path_transform_w(wchar_t **out, const wchar_t *src)
BOOL path_transform_w(wchar_t **out, const wchar_t *src)
{
BOOL ok;
HRESULT hr;

View File

@ -13,6 +13,7 @@ typedef HRESULT (*path_hook_t)(
HRESULT path_hook_push(path_hook_t hook);
void path_hook_insert_hooks(HMODULE target);
int path_compare_w(const wchar_t *string1, const wchar_t *string2, size_t count);
BOOL path_transform_w(wchar_t **out, const wchar_t *src);
static inline bool path_is_separator_w(wchar_t c)
{

View File

@ -5,6 +5,7 @@ platform_lib = static_library(
dependencies : [
capnhook.get_variable('hook_dep'),
shlwapi_lib,
bcrypt_lib,
],
sources : [
'amvideo.c',
@ -31,6 +32,8 @@ platform_lib = static_library(
'pcbid.h',
'platform.c',
'platform.h',
'security.c',
'security.h',
'vfs.c',
'vfs.h',
'system.c',

210
common/platform/security.c Normal file
View File

@ -0,0 +1,210 @@
#include <windows.h>
#include <bcrypt.h>
#define STATUS_SUCCESS ((NTSTATUS)0x00000000)
#include <stddef.h>
#include <stdlib.h>
#include "hook/table.h"
#include "platform/security.h"
#include "util/dprintf.h"
#include "util/dump.h"
static NTSTATUS __stdcall my_BCryptDecrypt(
BCRYPT_KEY_HANDLE hKey,
PUCHAR pbInput,
ULONG cbInput,
void* pPaddingInfo,
PUCHAR pbIV,
ULONG cbIV,
PUCHAR pbOutput,
ULONG cbOutput,
ULONG* pcbResult,
ULONG dwFlags);
static NTSTATUS (__stdcall *next_BCryptDecrypt)(
BCRYPT_KEY_HANDLE hKey,
PUCHAR pbInput,
ULONG cbInput,
void* pPaddingInfo,
PUCHAR pbIV,
ULONG cbIV,
PUCHAR pbOutput,
ULONG cbOutput,
ULONG* pcbResult,
ULONG dwFlags);
static NTSTATUS __stdcall my_BCryptEncrypt(
BCRYPT_KEY_HANDLE hKey,
PUCHAR pbInput,
ULONG cbInput,
void* pPaddingInfo,
PUCHAR pbIV,
ULONG cbIV,
PUCHAR pbOutput,
ULONG cbOutput,
ULONG* pcbResult,
ULONG dwFlags);
static NTSTATUS (__stdcall *next_BCryptEncrypt)(
BCRYPT_KEY_HANDLE hKey,
PUCHAR pbInput,
ULONG cbInput,
void* pPaddingInfo,
PUCHAR pbIV,
ULONG cbIV,
PUCHAR pbOutput,
ULONG cbOutput,
ULONG* pcbResult,
ULONG dwFlags);
static const struct hook_symbol security_hook_syms[] = {
{
.name = "BCryptDecrypt",
.patch = my_BCryptDecrypt,
.link = (void **) &next_BCryptDecrypt,
},
{
.name = "BCryptEncrypt",
.patch = my_BCryptEncrypt,
.link = (void **) &next_BCryptEncrypt,
}
};
void LogKeyProperties(BCRYPT_KEY_HANDLE hKey) {
NTSTATUS status;
ULONG cbData;
// Retrieve the algorithm name
WCHAR algorithmName[256];
ULONG cbAlgorithmName = sizeof(algorithmName);
status = BCryptGetProperty(hKey, BCRYPT_ALGORITHM_NAME, (PUCHAR) algorithmName, cbAlgorithmName * sizeof(WCHAR),
&cbData, 0);
if (status == STATUS_SUCCESS) {
dprintf("Algorithm: %ls\n", algorithmName);
} else {
dprintf("Algorithm: failed to retrieve algorithm name (0x%08lX)\n", status);
}
// Export the key value
DWORD keyBlobSize = 0;
status = BCryptExportKey(hKey, NULL, BCRYPT_KEY_DATA_BLOB, NULL, 0, &keyBlobSize, 0);
if (status != STATUS_SUCCESS) {
dprintf("KEY: failed to determine key blob size (0x%08lX)\n", status);
return;
}
PUCHAR keyBlob = (PUCHAR) malloc(keyBlobSize);
if (!keyBlob) {
dprintf("KEY: failed the memory allocation for key blob.\n");
return;
}
status = BCryptExportKey(hKey, NULL, BCRYPT_KEY_DATA_BLOB, keyBlob, keyBlobSize, &keyBlobSize, 0);
if (status == STATUS_SUCCESS) {
int headerSize = sizeof(BCRYPT_KEY_DATA_BLOB_HEADER);
dprintf("KEY (%lu bytes):\n", keyBlobSize - headerSize);
dump(keyBlob + headerSize, keyBlobSize - headerSize);
} else {
dprintf("KEY: failed to export key value (0x%08lX)\n", status);
}
free(keyBlob);
}
static NTSTATUS __stdcall my_BCryptDecrypt(
BCRYPT_KEY_HANDLE hKey,
PUCHAR pbInput,
ULONG cbInput,
void* pPaddingInfo,
PUCHAR pbIV,
ULONG cbIV,
PUCHAR pbOutput,
ULONG cbOutput,
ULONG* pcbResult,
ULONG dwFlags) {
dprintf("BCrypt: Decrypt\n");
dprintf("FLAGS: %lx\n", dwFlags);
LogKeyProperties(hKey);
dprintf("IV:\n");
dump(pbIV, cbIV);
dprintf("ENCRYPTED:\n");
dump(pbInput, cbInput);
NTSTATUS ret = next_BCryptDecrypt(hKey, pbInput, cbInput, pPaddingInfo, pbIV, cbIV, pbOutput, cbOutput, pcbResult,
dwFlags);
dprintf("Operation Result: %lx\n", ret);
if (ret == 0) {
dprintf("Decrypted (%ld, max buf: %ld):\n", *pcbResult, cbOutput);
if (pbOutput != NULL) {
dump(pbOutput, *pcbResult);
} else {
dprintf("pbOutput == NULL!\n");
}
}
return ret;
}
static NTSTATUS __stdcall my_BCryptEncrypt(
BCRYPT_KEY_HANDLE hKey,
PUCHAR pbInput,
ULONG cbInput,
void* pPaddingInfo,
PUCHAR pbIV,
ULONG cbIV,
PUCHAR pbOutput,
ULONG cbOutput,
ULONG* pcbResult,
ULONG dwFlags) {
dprintf("BCrypt: Encrypt\n");
dprintf("FLAGS: %lx\n", dwFlags);
LogKeyProperties(hKey);
dprintf("IV:\n");
dump(pbIV, cbIV);
dprintf("Input data (%ld):\n", cbInput);
dump(pbOutput, *pcbResult);
NTSTATUS ret = next_BCryptEncrypt(hKey, pbInput, cbInput, pPaddingInfo, pbIV, cbIV, pbOutput, cbOutput, pcbResult,
dwFlags);
dprintf("Operation Result: %lx\n", ret);
if (ret == 0) {
dprintf("First 16 bytes of encrypted data (%ld, max buf: %ld):\n", *pcbResult, cbOutput);
if (pbOutput != NULL) {
dump(pbOutput, min(16, *pcbResult));
} else {
dprintf("pbOutput == NULL!\n");
}
}
return ret;
}
HRESULT security_hook_init() {
security_hook_insert_hooks(NULL);
return S_OK;
}
void security_hook_insert_hooks(HMODULE mod) {
char value[8];
if (!GetEnvironmentVariableA("BCRYPT_DUMP_ENABLED", value, 8)) {
return;
}
if (value[0] != '1') {
return;
}
dprintf("BCrypt: dumping enabled\n");
hook_table_apply(
mod,
"BCrypt.dll",
security_hook_syms,
_countof(security_hook_syms));
}

View File

@ -0,0 +1,9 @@
#pragma once
#include <windows.h>
#include <stdbool.h>
#include <stddef.h>
HRESULT security_hook_init();
void security_hook_insert_hooks(HMODULE mod);

View File

@ -31,6 +31,10 @@ static HRESULT vfs_path_hook_option(
const wchar_t *src,
wchar_t *dest,
size_t *count);
static HRESULT vfs_path_hook_apm(
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);
@ -64,6 +68,9 @@ 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 wchar_t vfs_apm3[] = L"C:\\Mount\\Apm";
static const size_t vfs_apm3_len = _countof(vfs_apm3) - 1;
static const struct reg_hook_val vfs_reg_vals[] = {
{
.name = L"AMFS",
@ -199,6 +206,11 @@ HRESULT vfs_hook_init(const struct vfs_config *config, const char* game_id)
if (vfs_config.option[0] != L'\0') {
hr = path_hook_push(vfs_path_hook_option);
if (FAILED(hr)) {
return hr;
}
hr = path_hook_push(vfs_path_hook_apm);
if (FAILED(hr)) {
return hr;
}
@ -514,6 +526,53 @@ static HRESULT vfs_path_hook_option(
return S_OK;
}
static HRESULT vfs_path_hook_apm(
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_apm */
if (path_compare_w(src, vfs_apm3, vfs_apm3_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_apm3_len]) &&
src[vfs_apm3_len] != L'\0')
{
return S_FALSE;
}
/* Cut off the matched <prefix>\, add the replaced prefix, count NUL */
shift = path_is_separator_w(src[vfs_apm3_len]) ? 1 : 0;
redir_len = wcslen(vfs_config.option);
required = wcslen(src) - vfs_apm3_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_apm3_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:\\");

View File

@ -34,7 +34,11 @@ static const wchar_t *target_modules[] = {
L"C300FWDLusb.dll",
L"apmled.dll",
L"HKBSys_api.dll",
L"amptw.dll"
L"amptw.dll",
L"apmmount.dll",
L"abaasgs.dll",
L"AVProVideo.dll",
L"Audio360.dll",
};
static const size_t target_modules_len = _countof(target_modules);