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: #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

@ -240,6 +240,22 @@ $(BUILD_DIR_ZIP)/kemono.zip:
for x in exe dll; do strip $(BUILD_DIR_ZIP)/kemono/*.$$x; done for x in exe dll; do strip $(BUILD_DIR_ZIP)/kemono/*.$$x; done
$(V)cd $(BUILD_DIR_ZIP)/kemono ; zip -r ../kemono.zip * $(V)cd $(BUILD_DIR_ZIP)/kemono ; zip -r ../kemono.zip *
$(BUILD_DIR_ZIP)/apm3.zip:
$(V)echo ... $@
$(V)mkdir -p $(BUILD_DIR_ZIP)/apm3
$(V)mkdir -p $(BUILD_DIR_ZIP)/apm3/DEVICE
$(V)cp $(BUILD_DIR_64)/subprojects/capnhook/inject/inject.exe \
$(BUILD_DIR_GAMES_64)/apm3hook/apm3hook.dll \
$(DIST_DIR)/apm3/segatools.ini \
$(DIST_DIR)/apm3/launch.bat \
$(DIST_DIR)/apm3/config_hook.json \
$(BUILD_DIR_ZIP)/apm3
$(V)cp pki/billing.pub \
pki/ca.crt \
$(BUILD_DIR_ZIP)/apm3/DEVICE
$(V)strip $(BUILD_DIR_ZIP)/apm3/*.{exe,dll}
$(V)cd $(BUILD_DIR_ZIP)/apm3 ; zip -r ../apm3.zip *
$(BUILD_DIR_ZIP)/doc.zip: \ $(BUILD_DIR_ZIP)/doc.zip: \
$(DOC_DIR)/config \ $(DOC_DIR)/config \
$(DOC_DIR)/chunihook.md \ $(DOC_DIR)/chunihook.md \
@ -265,6 +281,7 @@ $(BUILD_DIR_ZIP)/segatools.zip: \
$(BUILD_DIR_ZIP)/tokyo.zip \ $(BUILD_DIR_ZIP)/tokyo.zip \
$(BUILD_DIR_ZIP)/fgo.zip \ $(BUILD_DIR_ZIP)/fgo.zip \
$(BUILD_DIR_ZIP)/kemono.zip \ $(BUILD_DIR_ZIP)/kemono.zip \
$(BUILD_DIR_ZIP)/apm3.zip \
CHANGELOG.md \ CHANGELOG.md \
README.md \ README.md \

View File

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

View File

@ -13,6 +13,7 @@ typedef HRESULT (*path_hook_t)(
HRESULT path_hook_push(path_hook_t hook); HRESULT path_hook_push(path_hook_t hook);
void path_hook_insert_hooks(HMODULE target); void path_hook_insert_hooks(HMODULE target);
int path_compare_w(const wchar_t *string1, const wchar_t *string2, size_t count); 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) static inline bool path_is_separator_w(wchar_t c)
{ {

View File

@ -5,6 +5,7 @@ platform_lib = static_library(
dependencies : [ dependencies : [
capnhook.get_variable('hook_dep'), capnhook.get_variable('hook_dep'),
shlwapi_lib, shlwapi_lib,
bcrypt_lib,
], ],
sources : [ sources : [
'amvideo.c', 'amvideo.c',
@ -31,6 +32,8 @@ platform_lib = static_library(
'pcbid.h', 'pcbid.h',
'platform.c', 'platform.c',
'platform.h', 'platform.h',
'security.c',
'security.h',
'vfs.c', 'vfs.c',
'vfs.h', 'vfs.h',
'system.c', '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, const wchar_t *src,
wchar_t *dest, wchar_t *dest,
size_t *count); 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_amfs(void *bytes, uint32_t *nbytes);
static HRESULT vfs_reg_read_appdata(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 wchar_t vfs_option[] = L"C:\\Mount\\Option";
static const size_t vfs_option_len = _countof(vfs_option) - 1; 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[] = { static const struct reg_hook_val vfs_reg_vals[] = {
{ {
.name = L"AMFS", .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') { if (vfs_config.option[0] != L'\0') {
hr = path_hook_push(vfs_path_hook_option); hr = path_hook_push(vfs_path_hook_option);
if (FAILED(hr)) {
return hr;
}
hr = path_hook_push(vfs_path_hook_apm);
if (FAILED(hr)) { if (FAILED(hr)) {
return hr; return hr;
} }
@ -514,6 +526,53 @@ static HRESULT vfs_path_hook_option(
return S_OK; 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) static HRESULT vfs_reg_read_amfs(void *bytes, uint32_t *nbytes)
{ {
return reg_hook_read_wstr(bytes, nbytes, L"E:\\"); 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"C300FWDLusb.dll",
L"apmled.dll", L"apmled.dll",
L"HKBSys_api.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); static const size_t target_modules_len = _countof(target_modules);

10
dist/apm3/config_hook.json vendored Normal file
View File

@ -0,0 +1,10 @@
{
"common": {
"language": "english"
},
"aime": {
"firmware_path": [
"aime_firm\\update_15396_6728_94.bin"
]
}
}

51
dist/apm3/launch.bat vendored Normal file
View File

@ -0,0 +1,51 @@
@echo off
cd /d %~dp0
set PATH=%~dp0lib;%~dp0;X:\;%PATH%
:BEGIN
cd /d %~dp0
qprocess amdaemon.exe > NUL
IF %ERRORLEVEL% NEQ 0 start /min cmd /C "inject -d -k apm3hook.dll amdaemon.exe -f -c daemon_config\common.json daemon_config\server.json config_hook.json"
inject -d -k apm3hook.dll APMV3System -screen-fullscreen 0 -screen-width 1920 -screen-height 1080 -popupWindow -logFile output_log.txt
if exist %tmp%\segaboot (
del %tmp%\segaboot
goto END
)
if exist %tmp%\app (
del %tmp%\app
goto APP
)
if exist %tmp%\apptest (
del %tmp%\apptest
goto APPTEST
)
goto END
:APP
call W:\game.bat
taskkill /f /im emoneyUI.exe > nul 2>&1
goto BEGIN
:APPTEST
call W:\gametest.bat
goto BEGIN
:END
taskkill /f /im emoneyUI.exe > nul 2>&1
taskkill /f /im amdaemon.exe > nul 2>&1
rundll32 apm3hook.dll,UnmountApmDrives
echo.
echo Game processes have terminated
pause

161
dist/apm3/segatools.ini vendored Normal file
View File

@ -0,0 +1,161 @@
; -----------------------------------------------------------------------------
; Path settings
; -----------------------------------------------------------------------------
[vfs]
; Insert the path to the game AMFS directory here (contains ICF1 and ICF2)
amfs=
; Insert the path to the game Option directory here (contains Axxx directories)
option=
; Create an empty directory somewhere and insert the path here.
; This directory may be shared between multiple SEGA games.
; NOTE: This has nothing to do with Windows %APPDATA%.
appdata=appdata
; -----------------------------------------------------------------------------
; Device settings
; -----------------------------------------------------------------------------
[aime]
; Enable Aime card reader assembly emulation. Disable to use a real SEGA Aime
; reader.
enable=1
aimePath=DEVICE\aime.txt
[vfd]
; Enable VFD emulation. Disable to use a real VFD
; GP1232A02A FUTABA assembly.
enable=1
[led15093]
; 837-15093-06 LED board emulation setting.
enable=1
; COM port number for the LED board.
portNo1=2
; -----------------------------------------------------------------------------
; Network settings
; -----------------------------------------------------------------------------
[dns]
; Insert the hostname or IP address of the server you wish to use here.
; Note that 127.0.0.1, localhost etc are specifically rejected.
default=127.0.0.1
[netenv]
; Simulate an ideal LAN environment. This may interfere with head-to-head play.
; SEGA games are somewhat picky about their LAN environment, so leaving this
; setting enabled is recommended.
enable=1
; The final octet of the local host's IP address on the virtualized subnet (so,
; if the keychip subnet is `192.168.32.0` and this value is set to `11`, then the
; local host's virtualized LAN IP is `192.168.32.11`).
addrSuffix=11
; -----------------------------------------------------------------------------
; Board settings
; -----------------------------------------------------------------------------
[keychip]
; The /24 LAN subnet that the emulated keychip will tell the game to expect.
; If you disable netenv then you must set this to your LAN's IP subnet, and
; that subnet must start with 192.168.
subnet=192.168.168.0
[system]
; Enable ALLS system settings.
enable=1
; Enable freeplay mode. This will disable the coin slot and set the game to
; freeplay. Keep in mind that some game modes (e.g. Freedom/Time Modes) will not
; allow you to start a game in freeplay mode.
freeplay=0
; -----------------------------------------------------------------------------
; Misc. hook settings
; -----------------------------------------------------------------------------
[touch]
; WinTouch emulation setting.
enable=1
remap=1
cursor=1
[unity]
; Enable Unity hook. This will allow you to run custom .NET code before the game
enable=1
; Path to a .NET DLL that should run before the game. Useful for loading
; modding frameworks such as BepInEx.
targetAssembly=
[mount]
; Enables W: drive mapping instead of using .vhd files
enable=1
; Delays the mount operation by a slight bit, so the launch sound effect isn't cut off.
delay=1
[epay]
; Enables the Thinca emulation. This will allow you to enable E-Money on compatible servers.
enable=0
; -----------------------------------------------------------------------------
; Custom IO settings
; -----------------------------------------------------------------------------
[aimeio]
; To use a custom card reader IO DLL enter its path here.
; Leave empty if you want to use Segatools built-in keyboard input.
path=
[apmio]
; To use a custom APM IO DLL enter its path here.
; Leave empty if you want to use Segatools built-in gamepad input.
path=
; -----------------------------------------------------------------------------
; Input settings
; -----------------------------------------------------------------------------
; Keyboard bindings are specified as hexadecimal (prefixed with 0x) or decimal
; (not prefixed with 0x) virtual-key codes, a list of which can be found here:
;
; https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
;
; This is, admittedly, not the most user-friendly configuration method in the
; world. An improved solution will be provided later.
[io4]
; Test button virtual-key code. Default is the 1 key.
test=0x31
; Service button virtual-key code. Default is the 2 key.
service=0x32
; Keyboard button to increment coin counter. Default is the 3 key.
coin=0x33
; START button (default: P)
start=0x50
; HOME button (default: O)
home=0x4F
; Buttons for the fight stick (default: arrows)
up=0x26
right=0x27
down=0x28
left=0x25
; Main buttons
; The arrangement is as following:
; 1 2 3 4
; 5 6 7 8
; Defaults:
; Q W E R
; A S D F
button1=0x51
button2=0x57
button3=0x45
button4=0x52
button5=0x41
button6=0x53
button7=0x44
button8=0x46

115
games/apm3hook/apm3-dll.c Normal file
View File

@ -0,0 +1,115 @@
#include <windows.h>
#include <assert.h>
#include <stdlib.h>
#include "apm3hook/apm3-dll.h"
#include "util/dll-bind.h"
#include "util/dprintf.h"
const struct dll_bind_sym apm3_dll_syms[] = {
{
.sym = "apm3_io_init",
.off = offsetof(struct apm3_dll, init),
}, {
.sym = "apm3_io_poll",
.off = offsetof(struct apm3_dll, poll),
}, {
.sym = "apm3_io_get_opbtns",
.off = offsetof(struct apm3_dll, get_opbtns),
}, {
.sym = "apm3_io_get_gamebtns",
.off = offsetof(struct apm3_dll, get_gamebtns),
}, {
.sym = "apm3_io_led_init",
.off = offsetof(struct apm3_dll, led_init),
}, {
.sym = "apm3_io_led_set_colors",
.off = offsetof(struct apm3_dll, led_set_leds),
}
};
struct apm3_dll apm3_dll;
// Copypasta DLL binding and diagnostic message boilerplate.
// Not much of this lends itself to being easily factored out. Also there
// will be a lot of API-specific branching code here eventually as new API
// versions get defined, so even though these functions all look the same
// now this won't remain the case forever.
HRESULT apm3_dll_init(const struct apm3_dll_config *cfg, HINSTANCE self)
{
uint16_t (*get_api_version)(void);
const struct dll_bind_sym *sym;
HINSTANCE owned;
HINSTANCE src;
HRESULT hr;
assert(cfg != NULL);
assert(self != NULL);
if (cfg->path[0] != L'\0') {
owned = LoadLibraryW(cfg->path);
if (owned == NULL) {
hr = HRESULT_FROM_WIN32(GetLastError());
dprintf("APM IO: Failed to load IO DLL: %lx: %S\n",
hr,
cfg->path);
goto end;
}
dprintf("APM IO: Using custom IO DLL: %S\n", cfg->path);
src = owned;
} else {
owned = NULL;
src = self;
}
get_api_version = (void *) GetProcAddress(src, "apm3_io_get_api_version");
if (get_api_version != NULL) {
apm3_dll.api_version = get_api_version();
} else {
apm3_dll.api_version = 0x0100;
dprintf("Custom IO DLL does not expose apm3_io_get_api_version, "
"assuming API version 1.0.\n"
"Please ask the developer to update their DLL.\n");
}
if (apm3_dll.api_version >= 0x0200) {
hr = E_NOTIMPL;
dprintf("APM IO: Custom IO DLL implements an unsupported "
"API version (%#04x). Please update Segatools.\n",
apm3_dll.api_version);
goto end;
}
sym = apm3_dll_syms;
hr = dll_bind(&apm3_dll, src, &sym, _countof(apm3_dll_syms));
if (FAILED(hr)) {
if (src != self) {
dprintf("APM IO: Custom IO DLL does not provide function "
"\"%s\". Please contact your IO DLL's developer for "
"further assistance.\n",
sym->sym);
goto end;
} else {
dprintf("Internal error: could not reflect \"%s\"\n", sym->sym);
}
}
owned = NULL;
end:
if (owned != NULL) {
FreeLibrary(owned);
}
return hr;
}

23
games/apm3hook/apm3-dll.h Normal file
View File

@ -0,0 +1,23 @@
#pragma once
#include <windows.h>
#include "apm3io/apm3io.h"
struct apm3_dll {
uint16_t api_version;
HRESULT (*init)(void);
HRESULT (*poll)(void);
void (*get_opbtns)(uint8_t *opbtn);
void (*get_gamebtns)(uint32_t *gamebtn);
HRESULT (*led_init)(void);
void (*led_set_leds)(uint8_t board, uint8_t *rgb);
};
struct apm3_dll_config {
wchar_t path[MAX_PATH];
};
extern struct apm3_dll apm3_dll;
HRESULT apm3_dll_init(const struct apm3_dll_config *cfg, HINSTANCE self);

View File

@ -0,0 +1,21 @@
LIBRARY apm3hook
EXPORTS
aime_io_get_api_version
aime_io_init
aime_io_led_set_color
aime_io_nfc_get_aime_id
aime_io_nfc_get_felica_id
aime_io_nfc_poll
amDllVideoClose @2
amDllVideoGetVBiosVersion @4
amDllVideoOpen @1
amDllVideoSetResolution @3
apm3_io_get_api_version
apm3_io_get_gamebtns
apm3_io_get_opbtns
apm3_io_init
apm3_io_poll
apm3_io_led_init
apm3_io_led_set_colors
UnmountApmDrives

113
games/apm3hook/config.c Normal file
View File

@ -0,0 +1,113 @@
#include <assert.h>
#include <stddef.h>
#include <stdlib.h>
#include "board/config.h"
#include "hooklib/config.h"
#include "hooklib/dvd.h"
#include "apm3hook/config.h"
#include "platform/config.h"
void apm3_dll_config_load(
struct apm3_dll_config *cfg,
const wchar_t *filename)
{
assert(cfg != NULL);
assert(filename != NULL);
GetPrivateProfileStringW(
L"apmio",
L"path",
L"",
cfg->path,
_countof(cfg->path),
filename);
}
void led15093_config_load(struct led15093_config *cfg, const wchar_t *filename)
{
assert(cfg != NULL);
assert(filename != NULL);
wchar_t tmpstr[16];
memset(cfg->board_number, ' ', sizeof(cfg->board_number));
memset(cfg->chip_number, ' ', sizeof(cfg->chip_number));
memset(cfg->boot_chip_number, ' ', sizeof(cfg->boot_chip_number));
cfg->enable = GetPrivateProfileIntW(L"led15093", L"enable", 1, filename);
cfg->port_no[0] = GetPrivateProfileIntW(L"led15093", L"portNo1", 0, filename);
cfg->port_no[1] = GetPrivateProfileIntW(L"led15093", L"portNo2", 0, filename);
cfg->high_baudrate = GetPrivateProfileIntW(L"led15093", L"highBaud", 0, filename);
cfg->fw_ver = GetPrivateProfileIntW(L"led15093", L"fwVer", 0xA0, filename);
cfg->fw_sum = GetPrivateProfileIntW(L"led15093", L"fwSum", 0xAA53, filename);
GetPrivateProfileStringW(
L"led15093",
L"boardNumber",
L"15093-06",
tmpstr,
_countof(tmpstr),
filename);
size_t n = wcstombs(cfg->board_number, tmpstr, sizeof(cfg->board_number));
for (int i = n; i < sizeof(cfg->board_number); i++)
{
cfg->board_number[i] = ' ';
}
GetPrivateProfileStringW(
L"led15093",
L"chipNumber",
L"6710A",
tmpstr,
_countof(tmpstr),
filename);
n = wcstombs(cfg->chip_number, tmpstr, sizeof(cfg->chip_number));
for (int i = n; i < sizeof(cfg->chip_number); i++)
{
cfg->chip_number[i] = ' ';
}
GetPrivateProfileStringW(
L"led15093",
L"bootChipNumber",
L"6709 ",
tmpstr,
_countof(tmpstr),
filename);
n = wcstombs(cfg->boot_chip_number, tmpstr, sizeof(cfg->boot_chip_number));
for (int i = n; i < sizeof(cfg->boot_chip_number); i++)
{
cfg->boot_chip_number[i] = ' ';
}
}
void mount_config_load(struct mount_config *cfg, const wchar_t *filename) {
cfg->enable = GetPrivateProfileIntW(L"mount", L"enable", 1, filename);
cfg->delay = GetPrivateProfileIntW(L"mount", L"delay", 1, filename);
}
void apm3_hook_config_load(
struct apm3_hook_config *cfg,
const wchar_t *filename)
{
assert(cfg != NULL);
assert(filename != NULL);
platform_config_load(&cfg->platform, filename);
aime_config_load(&cfg->aime, filename);
dvd_config_load(&cfg->dvd, filename);
io4_config_load(&cfg->io4, filename);
vfd_config_load(&cfg->vfd, filename);
touch_screen_config_load(&cfg->touch, filename);
led15093_config_load(&cfg->led15093, filename);
apm3_dll_config_load(&cfg->dll, filename);
unity_config_load(&cfg->unity, filename);
mount_config_load(&cfg->mount, filename);
}

43
games/apm3hook/config.h Normal file
View File

@ -0,0 +1,43 @@
#pragma once
#include <stddef.h>
#include "board/config.h"
#include "board/led15093.h"
#include "hooklib/dvd.h"
#include "hooklib/touch.h"
#include "hooklib/printer.h"
#include "gfxhook/config.h"
#include "apm3hook/apm3-dll.h"
#include "platform/config.h"
#include "unityhook/config.h"
struct mount_config {
bool enable;
bool delay;
};
struct apm3_hook_config {
struct platform_config platform;
struct aime_config aime;
struct dvd_config dvd;
struct io4_config io4;
struct vfd_config vfd;
struct touch_screen_config touch;
struct led15093_config led15093;
struct apm3_dll_config dll;
struct unity_config unity;
struct mount_config mount;
};
void apm3_dll_config_load(
struct apm3_dll_config *cfg,
const wchar_t *filename);
void apm3_hook_config_load(
struct apm3_hook_config *cfg,
const wchar_t *filename);

173
games/apm3hook/dllmain.c Normal file
View File

@ -0,0 +1,173 @@
/*
"ALL.Net P-ras multi Ver.3" (apm) hook
Devices
USB: 837-15257 "Type 4" I/O Board
COM1: 200-6275 VFD GP1232A02A FUTABA Board
COM2: 837-15093-06 LED Controller Board
COM3: 837-15396 "Gen 3" Aime Reader
*/
#include <windows.h>
#include <stdlib.h>
#include "config.h"
#include "io4.h"
#include "mount.h"
#include "amex/ds.h"
#include "hook/process.h"
#include "hooklib/serial.h"
#include "hooklib/spike.h"
#include "platform/clock.h"
#include "platform/config.h"
#include "platform/nusec.h"
#include "platform/security.h"
#include "unityhook/hook.h"
#include "util/dprintf.h"
#include "util/env.h"
static HMODULE apm3_hook_mod;
static process_entry_t apm3_startup;
static struct apm3_hook_config apm3_hook_cfg;
void unity_hook_callback(HMODULE hmodule, const wchar_t* p) {
dprintf("Unity: Hook callback: %ls\n", p);
serial_hook_apply_hooks(hmodule);
security_hook_insert_hooks(hmodule);
touch_hook_insert_hooks(hmodule);
//mount_hook_apply_hooks(&apm3_hook_cfg.mount, hmodule);
}
void apm3_extra_hooks_init(void) {
HMODULE module = LoadLibraryA("Apmv3System_Data/Plugins/x86_64/apmmount.dll"); // HACK??
if (module != NULL){
dprintf("APM: Successfully pre-loaded apmmount\n");
}
module = LoadLibraryA("Apmv3System_Data/Plugins/x86_64/apmled.dll"); // HACK??
if (module != NULL){
dprintf("APM: Successfully pre-loaded apmled\n");
}
}
static DWORD CALLBACK apm3_pre_startup(void)
{
HRESULT hr;
dprintf("--- Begin %s ---\n", __func__);
/* Load config */
apm3_hook_config_load(&apm3_hook_cfg, get_config_path());
/* Pin stuff */
apm3_extra_hooks_init();
/* Hook Win32 APIs */
dvd_hook_init(&apm3_hook_cfg.dvd, apm3_hook_mod);
touch_screen_hook_init(&apm3_hook_cfg.touch, apm3_hook_mod);
serial_hook_init();
/* Initialize emulation hooks */
hr = platform_hook_init(
&apm3_hook_cfg.platform,
"SDEM",
"ACA1",
apm3_hook_mod);
if (FAILED(hr)) {
goto fail;
}
hr = sg_reader_hook_init(&apm3_hook_cfg.aime, 3, 3, apm3_hook_mod);
if (FAILED(hr)) {
goto fail;
}
hr = vfd_hook_init(&apm3_hook_cfg.vfd, 1);
if (FAILED(hr)) {
goto fail;
}
hr = apm3_dll_init(&apm3_hook_cfg.dll, apm3_hook_mod);
if (FAILED(hr)) {
goto fail;
}
hr = apm3_io4_hook_init(&apm3_hook_cfg.io4);
if (FAILED(hr)) {
goto fail;
}
unsigned int led_port_no[2] = {2, 0};
hr = led15093_hook_init(&apm3_hook_cfg.led15093,
apm3_dll.led_init, apm3_dll.led_set_leds, led_port_no);
if (FAILED(hr)) {
goto fail;
}
hr = security_hook_init();
if (FAILED(hr)) {
goto fail;
}
touch_screen_hook_init(&apm3_hook_cfg.touch, apm3_hook_mod);
mount_hook_init(&apm3_hook_cfg.platform.vfs, &apm3_hook_cfg.mount);
security_hook_insert_hooks(NULL);
mount_hook_apply_hooks(NULL);
unity_hook_init(&apm3_hook_cfg.unity, apm3_hook_mod, unity_hook_callback);
/* Initialize debug helpers */
spike_hook_init(get_config_path());
dprintf("--- End %s ---\n", __func__);
return apm3_startup();
fail:
ExitProcess(EXIT_FAILURE);
}
BOOL WINAPI DllMain(HMODULE mod, DWORD cause, void *ctx)
{
HRESULT hr;
if (cause != DLL_PROCESS_ATTACH) {
return TRUE;
}
wchar_t szFileName[MAX_PATH];
GetModuleFileNameW(NULL, szFileName, MAX_PATH);
if (wcsstr(szFileName, L"rundll32") != NULL) {
return TRUE;
}
apm3_hook_mod = mod;
hr = process_hijack_startup(apm3_pre_startup, &apm3_startup);
if (!SUCCEEDED(hr)) {
dprintf("Failed to hijack process startup: %x\n", (int) hr);
}
return SUCCEEDED(hr);
}

129
games/apm3hook/io4.c Normal file
View File

@ -0,0 +1,129 @@
#include <windows.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include "board/io4.h"
#include "apm3hook/apm3-dll.h"
#include "util/dprintf.h"
static HRESULT apm3_io4_poll(void *ctx, struct io4_state *state);
static uint16_t coins;
static const struct io4_ops apm3_io4_ops = {
.poll = apm3_io4_poll,
};
HRESULT apm3_io4_hook_init(const struct io4_config *cfg)
{
HRESULT hr;
assert(apm3_dll.init != NULL);
hr = io4_hook_init(cfg, &apm3_io4_ops, NULL);
if (FAILED(hr)) {
return hr;
}
return apm3_dll.init();
}
static HRESULT apm3_io4_poll(void *ctx, struct io4_state *state)
{
uint8_t opbtn;
uint32_t gamebtn;
HRESULT hr;
assert(apm3_dll.poll != NULL);
assert(apm3_dll.get_opbtns != NULL);
assert(apm3_dll.get_gamebtns != NULL);
memset(state, 0, sizeof(*state));
hr = apm3_dll.poll();
if (FAILED(hr)) {
return hr;
}
opbtn = 0;
gamebtn = 0;
apm3_dll.get_opbtns(&opbtn);
apm3_dll.get_gamebtns(&gamebtn);
if (opbtn & APM3_IO_OPBTN_TEST) {
state->buttons[0] |= IO4_BUTTON_TEST;
}
if (opbtn & APM3_IO_OPBTN_SERVICE) {
state->buttons[0] |= IO4_BUTTON_SERVICE;
}
if (opbtn & APM3_IO_OPBTN_COIN) {
coins++;
}
state->chutes[0] = coins << 8;
if (gamebtn & APM3_IO_GAMEBTN_HOME) {
state->buttons[1] |= 1 << 12;
}
if (gamebtn & APM3_IO_GAMEBTN_START) {
state->buttons[0] |= 1 << 7;
}
if (gamebtn & APM3_IO_GAMEBTN_UP) {
state->buttons[0] |= 1 << 5;
}
if (gamebtn & APM3_IO_GAMEBTN_RIGHT) {
state->buttons[0] |= 1 << 2;
}
if (gamebtn & APM3_IO_GAMEBTN_DOWN) {
state->buttons[0] |= 1 << 4;
}
if (gamebtn & APM3_IO_GAMEBTN_LEFT) {
state->buttons[0] |= 1 << 3;
}
if (gamebtn & APM3_IO_GAMEBTN_B1) {
state->buttons[0] |= 1 << 1;
}
if (gamebtn & APM3_IO_GAMEBTN_B2) {
state->buttons[0] |= 1 << 0;
}
if (gamebtn & APM3_IO_GAMEBTN_B3) {
state->buttons[0] |= 1 << 15;
}
if (gamebtn & APM3_IO_GAMEBTN_B4) {
state->buttons[0] |= 1 << 14;
}
if (gamebtn & APM3_IO_GAMEBTN_B5) {
state->buttons[0] |= 1 << 13;
}
if (gamebtn & APM3_IO_GAMEBTN_B6) {
state->buttons[0] |= 1 << 12;
}
if (gamebtn & APM3_IO_GAMEBTN_B7) {
state->buttons[0] |= 1 << 11;
}
if (gamebtn & APM3_IO_GAMEBTN_B8) {
state->buttons[0] |= 1 << 10;
}
return S_OK;
}

7
games/apm3hook/io4.h Normal file
View File

@ -0,0 +1,7 @@
#pragma once
#include <windows.h>
#include "board/io4.h"
HRESULT apm3_io4_hook_init(const struct io4_config *cfg);

View File

@ -0,0 +1,32 @@
shared_library(
'apm3hook',
name_prefix : '',
include_directories : inc,
implicit_include_directories : false,
vs_module_defs : 'apm3hook.def',
dependencies : [
capnhook.get_variable('hook_dep'),
capnhook.get_variable('hooklib_dep'),
],
link_with : [
aimeio_lib,
apm3io_lib,
board_lib,
gfxhook_lib,
hooklib_lib,
platform_lib,
unityhook_lib,
util_lib,
],
sources : [
'apm3-dll.c',
'apm3-dll.h',
'config.c',
'config.h',
'dllmain.c',
'io4.c',
'io4.h',
'mount.c',
'mount.h',
],
)

174
games/apm3hook/mount.c Normal file
View File

@ -0,0 +1,174 @@
#include "board/aime-dll.h"
#include "config.h"
#include "mount.h"
#include <assert.h>
#include <pathcch.h>
#include <shlwapi.h>
#include <stdio.h>
#include <stdlib.h>
#include "hooklib/path.h"
#include "hook/procaddr.h"
#include "hook/table.h"
#include "util/dprintf.h"
/* API hooks */
static bool hook_ApmSys_mountVhd(const wchar_t* vhdOriginalPath, const wchar_t* vhdPatchPath, char mountDriveLetter);
static bool hook_ApmSys_mountFscrypt(const wchar_t* fscryptImagePath, const wchar_t* mountFolderPath, const wchar_t* subGameId);
static bool hook_ApmSys_unmountVhd(char mountDriveLetter);
static bool hook_ApmSys_unmountFscrypt(const wchar_t* mountFolderPath);
static const struct hook_symbol mount_hooks[] = {
{
.name = "ApmSys_mountVhd",
.patch = hook_ApmSys_mountVhd,
.ordinal = 2,
},
{
.name = "ApmSys_unmountVhd",
.patch = hook_ApmSys_unmountVhd,
.ordinal = 4,
},
{
.name = "ApmSys_mountFscrypt",
.patch = hook_ApmSys_mountFscrypt,
.ordinal = 1,
},
{
.name = "ApmSys_unmountFscrypt",
.patch = hook_ApmSys_unmountFscrypt,
.ordinal = 3,
},
};
static struct vfs_config* vcfg;
static struct mount_config* mcfg;
static wchar_t game_directory[MAX_PATH];
void mount_hook_init(struct vfs_config* vfs_cfg, struct mount_config* mount_cfg) {
assert(vfs_cfg != NULL);
assert(mount_cfg != NULL);
vcfg = vfs_cfg;
mcfg = mount_cfg;
if (!mcfg->enable) {
return;
}
dprintf("Mount: Hook enabled\n");
}
void mount_hook_apply_hooks(HMODULE module) {
if (!mcfg->enable) {
return;
}
proc_addr_table_push(module, "apmmount.dll", mount_hooks, _countof(mount_hooks));
}
bool hook_ApmSys_mountFscrypt(const wchar_t* fscryptImagePath, const wchar_t* mountFolderPath, const wchar_t* subGameId) {
dprintf("Mount: Mount FsCrypt (%ls, %ls, %ls)\n", fscryptImagePath, mountFolderPath, subGameId);
wcscpy_s(game_directory, MAX_PATH, fscryptImagePath);
PathCchRemoveFileSpec(game_directory, MAX_PATH);
wnsprintfW(game_directory, MAX_PATH, L"%s\\App", game_directory);
wchar_t *trans;
BOOL ok;
ok = path_transform_w(&trans, game_directory);
if (!ok) {
dprintf("Mount: Path transformation error\n");
return 1;
}
if (trans != NULL) {
wcscpy_s(game_directory, MAX_PATH, trans);
}
free(trans);
dprintf("Mount: Target Path: %ls\n", game_directory);
return 0;
}
bool hook_ApmSys_mountVhd(const wchar_t* vhdOriginalPath, const wchar_t* vhdPatchPath, char mountDriveLetter) {
dprintf("Mount: Mount VHD (%ls, %ls, %c)\n", vhdOriginalPath, vhdPatchPath, mountDriveLetter);
if (game_directory[0] == '\0') {
dprintf("Mount: Target directory is unset!\n");
return 1;
}
// Game drive
wchar_t device[3];
device[0] = mountDriveLetter;
device[1] = ':';
device[2] = '\0';
dprintf("Mount: Mapping %ls to %ls\n", device, game_directory);
if (!DefineDosDeviceW(0, device, game_directory)) {
dprintf("DefineDosDevice failed: %lx\n", GetLastError());
return 1;
}
// APM root drive
device[0] = 'X';
wchar_t self_directory[MAX_PATH];
GetCurrentDirectoryW(MAX_PATH, self_directory);
dprintf("Mount: Mapping %ls to %ls\n", device, self_directory);
if (!DefineDosDeviceW(0, device, self_directory)) {
dprintf("DefineDosDevice failed: %lx\n", GetLastError());
return 1;
}
if (mcfg->delay) {
Sleep(1500);
}
return 0;
}
bool hook_ApmSys_unmountVhd(char mountDriveLetter) {
dprintf("Mount: Unmount VHD (%c)\n", mountDriveLetter);
char device[3];
device[0] = mountDriveLetter;
device[1] = ':';
device[2] = '\0';
// Game drive
if (!DefineDosDevice(DDD_REMOVE_DEFINITION, device, NULL)) {
dprintf("DefineDosDevice failed: %lx\n", GetLastError());
return 1;
}
// APM root drive
device[0] = 'X';
if (!DefineDosDevice(DDD_REMOVE_DEFINITION, device, NULL)) {
dprintf("DefineDosDevice failed: %lx\n", GetLastError());
return 1;
}
return 0;
}
void CALLBACK UnmountApmDrives(HWND hwnd, HINSTANCE hinst, LPSTR lpszCmdLine, int nCmdShow) {
hook_ApmSys_unmountVhd('W');
hook_ApmSys_unmountVhd('X');
}
bool hook_ApmSys_unmountFscrypt(const wchar_t* mountFolderPath) {
dprintf("Mount: Unmount FsCrypt (%ls)\n", mountFolderPath);
memset(game_directory, 0, MAX_PATH * sizeof(wchar_t));
return 0;
}

7
games/apm3hook/mount.h Normal file
View File

@ -0,0 +1,7 @@
#pragma once
#include "board/aime-dll.h"
#include "config.h"
void mount_hook_apply_hooks(HMODULE module);
void mount_hook_init(struct vfs_config* vfs_cfg, struct mount_config* mount_cfg);

128
games/apm3io/apm3io.c Normal file
View File

@ -0,0 +1,128 @@
#include <windows.h>
#include <xinput.h>
#include <math.h>
#include <limits.h>
#include <stdint.h>
#include "apm3io/apm3io.h"
#include "apm3io/config.h"
#include "util/dprintf.h"
#include "util/env.h"
static uint8_t apm3_opbtn;
static uint32_t apm3_gamebtn;
static int16_t apm3_stick_x;
static int16_t apm3_stick_y;
static struct apm3_io_config apm3_io_cfg;
static bool apm3_io_coin;
uint16_t apm3_io_get_api_version(void) {
return 0x0100;
}
HRESULT apm3_io_init(void) {
apm3_io_config_load(&apm3_io_cfg, get_config_path());
return S_OK;
}
HRESULT apm3_io_poll(void) {
apm3_opbtn = 0;
apm3_gamebtn = 0;
if (GetAsyncKeyState(apm3_io_cfg.vk_test) & 0x8000) {
apm3_opbtn |= APM3_IO_OPBTN_TEST;
}
if (GetAsyncKeyState(apm3_io_cfg.vk_service) & 0x8000) {
apm3_opbtn |= APM3_IO_OPBTN_SERVICE;
}
if (GetAsyncKeyState(apm3_io_cfg.vk_coin) & 0x8000) {
if (!apm3_io_coin) {
apm3_io_coin = true;
apm3_opbtn |= APM3_IO_OPBTN_COIN;
}
} else {
apm3_io_coin = false;
}
if (GetAsyncKeyState(apm3_io_cfg.vk_home) & 0x8000) {
apm3_gamebtn |= APM3_IO_GAMEBTN_HOME;
}
if (GetAsyncKeyState(apm3_io_cfg.vk_start) & 0x8000) {
apm3_gamebtn |= APM3_IO_GAMEBTN_START;
}
if (GetAsyncKeyState(apm3_io_cfg.vk_up) & 0x8000) {
apm3_gamebtn |= APM3_IO_GAMEBTN_UP;
}
if (GetAsyncKeyState(apm3_io_cfg.vk_right) & 0x8000) {
apm3_gamebtn |= APM3_IO_GAMEBTN_RIGHT;
}
if (GetAsyncKeyState(apm3_io_cfg.vk_down) & 0x8000) {
apm3_gamebtn |= APM3_IO_GAMEBTN_DOWN;
}
if (GetAsyncKeyState(apm3_io_cfg.vk_left) & 0x8000) {
apm3_gamebtn |= APM3_IO_GAMEBTN_LEFT;
}
if (GetAsyncKeyState(apm3_io_cfg.vk_buttons[0]) & 0x8000) {
apm3_gamebtn |= APM3_IO_GAMEBTN_B1;
}
if (GetAsyncKeyState(apm3_io_cfg.vk_buttons[1]) & 0x8000) {
apm3_gamebtn |= APM3_IO_GAMEBTN_B2;
}
if (GetAsyncKeyState(apm3_io_cfg.vk_buttons[2]) & 0x8000) {
apm3_gamebtn |= APM3_IO_GAMEBTN_B3;
}
if (GetAsyncKeyState(apm3_io_cfg.vk_buttons[3]) & 0x8000) {
apm3_gamebtn |= APM3_IO_GAMEBTN_B4;
}
if (GetAsyncKeyState(apm3_io_cfg.vk_buttons[4]) & 0x8000) {
apm3_gamebtn |= APM3_IO_GAMEBTN_B5;
}
if (GetAsyncKeyState(apm3_io_cfg.vk_buttons[5]) & 0x8000) {
apm3_gamebtn |= APM3_IO_GAMEBTN_B6;
}
if (GetAsyncKeyState(apm3_io_cfg.vk_buttons[6]) & 0x8000) {
apm3_gamebtn |= APM3_IO_GAMEBTN_B7;
}
if (GetAsyncKeyState(apm3_io_cfg.vk_buttons[7]) & 0x8000) {
apm3_gamebtn |= APM3_IO_GAMEBTN_B8;
}
return S_OK;
}
void apm3_io_get_opbtns(uint8_t* opbtn) {
if (opbtn != NULL) {
*opbtn = apm3_opbtn;
}
}
void apm3_io_get_gamebtns(uint32_t* btn) {
if (btn != NULL) {
*btn = apm3_gamebtn;
}
}
HRESULT apm3_io_led_init(void) {
return S_OK;
}
void apm3_io_led_set_colors(uint8_t board, uint8_t* rgb) {
return;
}

84
games/apm3io/apm3io.h Normal file
View File

@ -0,0 +1,84 @@
#pragma once
#include <windows.h>
#include <stdint.h>
enum {
APM3_IO_OPBTN_TEST = 0x01,
APM3_IO_OPBTN_SERVICE = 0x02,
APM3_IO_OPBTN_COIN = 0x04,
};
enum {
APM3_IO_GAMEBTN_HOME = 0x01,
APM3_IO_GAMEBTN_START = 0x02,
APM3_IO_GAMEBTN_UP = 0x04,
APM3_IO_GAMEBTN_RIGHT = 0x08,
APM3_IO_GAMEBTN_DOWN = 0x10,
APM3_IO_GAMEBTN_LEFT = 0x20,
APM3_IO_GAMEBTN_B1 = 0x40,
APM3_IO_GAMEBTN_B2 = 0x80,
APM3_IO_GAMEBTN_B3 = 0x100,
APM3_IO_GAMEBTN_B4 = 0x200,
APM3_IO_GAMEBTN_B5 = 0x400,
APM3_IO_GAMEBTN_B6 = 0x800,
APM3_IO_GAMEBTN_B7 = 0x1000,
APM3_IO_GAMEBTN_B8 = 0x2000,
};
/* Get the version of the APMv3 IO API that this DLL supports. This
function should return a positive 16-bit integer, where the high byte is
the major version and the low byte is the minor version (as defined by the
Semantic Versioning standard).
The latest API version as of this writing is 0x0100. */
uint16_t apm3_io_get_api_version(void);
/* Initialize the IO DLL. This is the second function that will be called on
your DLL, after apm3_io_get_api_version.
All subsequent calls to this API may originate from arbitrary threads.
Minimum API version: 0x0100 */
HRESULT apm3_io_init(void);
/* Send any queued outputs (of which there are currently none, though this may
change in subsequent API versions) and retrieve any new inputs.
Minimum API version: 0x0100 */
HRESULT apm3_io_poll(void);
/* Get the state of the cabinet's operator buttons as of the last poll. See
APM3_IO_OPBTN enum above: this contains bit mask definitions for button
states returned in *opbtn. All buttons are active-high.
Minimum API version: 0x0100 */
void apm3_io_get_opbtns(uint8_t *opbtn);
/* Get the state of the cabinet's gameplay buttons as of the last poll. See
APM3_IO_GAMEBTN enum above: this contains bit mask definitions for button
states returned in *gamebtn. All buttons are active-high.
Minimum API version: 0x0100 */
void apm3_io_get_gamebtns(uint32_t *gamebtn);
/* Initialize LED emulation. This function will be called before any
other apm3_io_led_*() function calls.
All subsequent calls may originate from arbitrary threads and some may
overlap with each other. Ensuring synchronization inside your IO DLL is
your responsibility. */
HRESULT apm3_io_led_init(void);
/* Update the RGB LEDs.
Exact layout is TBD. */
void apm3_io_led_set_colors(uint8_t board, uint8_t *rgb);

36
games/apm3io/config.c Normal file
View File

@ -0,0 +1,36 @@
#include <windows.h>
#include <assert.h>
#include <stddef.h>
#include <stdio.h>
#include "apm3io/config.h"
const int BUTTON_DEFAULTS[] = {'Q','W','E','R','A','S','D','F'};
void apm3_io_config_load(
struct apm3_io_config *cfg,
const wchar_t *filename)
{
assert(cfg != NULL);
assert(filename != NULL);
cfg->vk_test = GetPrivateProfileIntW(L"io4", L"test", '1', filename);
cfg->vk_service = GetPrivateProfileIntW(L"io4", L"service", '2', filename);
cfg->vk_coin = GetPrivateProfileIntW(L"io4", L"coin", '3', filename);
cfg->vk_start = GetPrivateProfileIntW(L"io4", L"start", 'P', filename);
cfg->vk_home = GetPrivateProfileIntW(L"io4", L"home", 'O', filename);
cfg->vk_up = GetPrivateProfileIntW(L"io4", L"up", VK_UP, filename);
cfg->vk_right = GetPrivateProfileIntW(L"io4", L"right", VK_RIGHT, filename);
cfg->vk_down = GetPrivateProfileIntW(L"io4", L"down", VK_DOWN, filename);
cfg->vk_left = GetPrivateProfileIntW(L"io4", L"left", VK_LEFT, filename);
wchar_t tmp[16];
for (int i = 0; i < APM3_BUTTON_COUNT; i++) {
swprintf_s(tmp, 32, L"button%d", i + 1);
cfg->vk_buttons[i] = GetPrivateProfileIntW(L"io4", tmp, BUTTON_DEFAULTS[i], filename);
}
}

28
games/apm3io/config.h Normal file
View File

@ -0,0 +1,28 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#define APM3_BUTTON_COUNT 8
struct apm3_io_config {
uint8_t vk_test;
uint8_t vk_service;
uint8_t vk_coin;
uint8_t vk_start;
uint8_t vk_home;
uint8_t vk_up;
uint8_t vk_right;
uint8_t vk_down;
uint8_t vk_left;
uint8_t vk_buttons[APM3_BUTTON_COUNT];
};
void apm3_io_config_load(
struct apm3_io_config *cfg,
const wchar_t *filename);

15
games/apm3io/meson.build Normal file
View File

@ -0,0 +1,15 @@
apm3io_lib = static_library(
'apm3io',
name_prefix : '',
include_directories : inc,
implicit_include_directories : false,
dependencies : [
xinput_lib,
],
sources : [
'apm3io.c',
'apm3io.h',
'config.c',
'config.h',
],
)

View File

@ -93,6 +93,7 @@ xinput_lib = cc.find_library('xinput')
pathcch_lib = cc.find_library('pathcch') pathcch_lib = cc.find_library('pathcch')
imagehlp_lib = cc.find_library('imagehlp') imagehlp_lib = cc.find_library('imagehlp')
ws2_32_lib = cc.find_library('ws2_32') ws2_32_lib = cc.find_library('ws2_32')
bcrypt_lib = cc.find_library('bcrypt')
inc = include_directories('common', 'games') inc = include_directories('common', 'games')
capnhook = subproject('capnhook') capnhook = subproject('capnhook')
@ -123,6 +124,7 @@ subdir('games/cxbio')
subdir('games/tokyoio') subdir('games/tokyoio')
subdir('games/fgoio') subdir('games/fgoio')
subdir('games/kemonoio') subdir('games/kemonoio')
subdir('games/apm3io')
subdir('games/chunihook') subdir('games/chunihook')
subdir('games/divahook') subdir('games/divahook')
@ -139,3 +141,4 @@ subdir('games/cxbhook')
subdir('games/tokyohook') subdir('games/tokyohook')
subdir('games/fgohook') subdir('games/fgohook')
subdir('games/kemonohook') subdir('games/kemonohook')
subdir('games/apm3hook')