merge upstream

This commit is contained in:
2025-07-23 16:55:13 +00:00
58 changed files with 2754 additions and 11 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
$(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: \
$(DOC_DIR)/config \
$(DOC_DIR)/chunihook.md \
@ -265,6 +281,7 @@ $(BUILD_DIR_ZIP)/segatools.zip: \
$(BUILD_DIR_ZIP)/tokyo.zip \
$(BUILD_DIR_ZIP)/fgo.zip \
$(BUILD_DIR_ZIP)/kemono.zip \
$(BUILD_DIR_ZIP)/apm3.zip \
CHANGELOG.md \
README.md \

View File

@ -1,6 +1,6 @@
# Segatools
Version: `2024-09-30`
Version: `2025-07-20`
Loaders and hardware emulators for SEGA games that run on the Nu and ALLS platforms.
@ -32,6 +32,8 @@ Loaders and hardware emulators for SEGA games that run on the Nu and ALLS platfo
* starting from WACCA
* Kemono Friends
* Kemono Friends 3: Planet Tours
* ALL.Net P-ras MULTI Version 3
* starting from ALL.Net P-ras MULTI Version 3 1.01
## End-users

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

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"
]
}
}

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

@ -0,0 +1,61 @@
@echo off
pushd %~dp0
set PATH=%~dp0lib;%~dp0;X:\;%PATH%
rem remove the reboot flag and show the copyright screen
if exist %tmp%\APMv3SystemReboot (
del %tmp%\APMv3SystemReboot
)
if exist %tmp%\SequenceSetting.json (
del %tmp%\SequenceSetting.json
)
:BEGIN
pushd %~dp0
qprocess amdaemon.exe > NUL
IF %ERRORLEVEL% NEQ 0 start /min "AM Daemon" inject -d -k apm3hook.dll amdaemon.exe -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

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

@ -0,0 +1,244 @@
; -----------------------------------------------------------------------------
; 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=
; -----------------------------------------------------------------------------
; Device settings
; -----------------------------------------------------------------------------
[aime]
; Enable Aime card reader assembly emulation. Disable to use a real SEGA Aime
; reader.
enable=1
aimePath=DEVICE\aime.txt
; Virtual-key code. If this button is **held** then the emulated IC card reader
; emulates an IC card in its proximity. A variety of different IC cards can be
; emulated; the exact choice of card that is emulated depends on the presence or
; absence of the configured card ID files. Default is the Return key.
scan=0x0D
[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]
; Keychip serial number. Keychip serials observed in the wild follow this
; pattern: `A\d{2}(E|X)-(01|20)[ABCDU]\d{8}`.
id=A69E-01A88888888
; 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
; Override the game's four-character platform code (e.g. `AAV2` for Nu 2). This
; is actually supposed to be a separate three-character `platformId` and
; integer `modelType` setting, but they are combined here for convenience.
; Use `ACA1` for the Server/Client, depending on the dipsw1
; Use `ACA5` for the Standalone mode
platformId=ACA1
[pcbid]
; Set the Windows host name. This should be an ALLS MAIN ID, without the
; hyphen (which is not a valid character in a Windows host name).
serialNo=ACAE01A99999999
[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
; LAN Install: If multiple machines are present on the same LAN then set
; this to 1 on exactly one machine and set this to 0 on all others.
dipsw1=1
; -----------------------------------------------------------------------------
; 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=1
; -----------------------------------------------------------------------------
; 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=
[apm3io]
; To use a custom APM3 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 F1 key.
test=0x70
; Service button virtual-key code. Default is the F2 key.
service=0x71
; Keyboard button to increment coin counter. Default is the F3 key.
coin=0x72
; Input API selection for IO4 input emulator.
; Set "keyboard" to use keyboard input, "xinput" to use a gamepad, "dinput" to
; use an arcade stick.
mode=keyboard
; Button mappings for the 8 vewlix arcade stick buttons.
;
; :=**#*+-. :+#@@@@#*- -*#@@@@#+:
; =%@@@@@@@@@#: -%@@@@@@@@@@%- -%@@@@@@@@@@%-
; #@@@@@@@@@@@@@+ +@@@@@@@@@@@@@@+ +@@@@@@@@@@@@@@=
; :::: *@@@@@ @@@@@- .@@@@@ @@@@@@. .@@@@@ @@@@@@
; :+%@@@@@@%*: %@@@@@ [2] @@@@@+ :@@@@@ [3] @@@@@@: :@@@@@ [4] @@@@@@
; #@@@@@@@@@@@@# +@@@@@ @@@@@: #@@@@ @@@@@# #@@@@ @@@@@*
; #@@@@ @@@@@# *@@@@@@@@@@@@@= *@@@@@@@@@@@@* *@@@@@@@@@@@@*
; @@@@@ [1] @@@@@@: -#@@@@@@@@@*. .+%@@@@@@%+. :*%@@@@@@%+.
; @@@@@ @@@@@@. :=++=-: .::. .::.
; =@@@@@@@@@@@@@@+
; -%@@@@@@@@@@%- =*%@@@%#=. :+#%@@@%*- .=#@@@@@%*-
; :+#%@@%#+- *@@@@@@@@@@@#. :#@@@@@@@@@@@+ +@@@@@@@@@@@%-
; %@@@@@@@@@@@@@@. -@@@@@@@@@@@@@@% #@@@@@@@@@@@@@@-
; .:::. +@@@@@ @@@@@# %@@@@@ @@@@@- -@@@@@@ @@@@%
; =#@@@@@@@#- +@@@@@ [6] @@@@@# @@@@@@ [7] @@@@@= -@@@@@@ [8] @@@@@
; =@@@@@@@@@@@@%. .@@@@@ @@@@@- =@@@@@ @@@@@. %@@@@@ @@@@=
; +@@@@@ @@@@@. :%@@@@@@@@@@@@- =@@@@@@@@@@@@#. *@@@@@@@@@@@@=
; @@@@@@ [5] @@@@@+ -*@@@@@@@#= =#@@@@@@%*: :+#@@@@@@*=
; %@@@@@ @@@@@= .:. ..:. ..
; :@@@@@@@@@@@@@@#
; .#@@@@@@@@@@@+
; .::.
[keyboard]
; 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
; 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
[xinput]
; Defaults:
; A B LB RB
; X Y LT RT
; Use the left thumbstick for directional controls on XInput controllers.
analogStickEnabled=1
[dinput]
; Name of the DirectInput stick to use (or any text that occurs in its name)
; Example: TE2+
;
; If this is left blank then the first DirectInput device will be used.
deviceName=
; DirectInput button numbers to map to menu inputs. Note that buttons are
; numbered from 1; some software numbers buttons from 0.
start=9
home=10
button1=1
button2=4
button3=6
button4=5
button5=2
button6=3
button7=8
button8=7

View File

@ -23,6 +23,12 @@ appdata=
enable=1
aimePath=DEVICE\aime.txt
; Virtual-key code. If this button is **held** then the emulated IC card reader
; emulates an IC card in its proximity. A variety of different IC cards can be
; emulated; the exact choice of card that is emulated depends on the presence or
; absence of the configured card ID files. Default is the Return key.
scan=0x0D
; -----------------------------------------------------------------------------
; Network settings
; -----------------------------------------------------------------------------

View File

@ -22,6 +22,12 @@ appdata=
enable=1
aimePath=DEVICE\aime.txt
; Virtual-key code. If this button is **held** then the emulated IC card reader
; emulates an IC card in its proximity. A variety of different IC cards can be
; emulated; the exact choice of card that is emulated depends on the presence or
; absence of the configured card ID files. Default is the Return key.
scan=0x0D
; -----------------------------------------------------------------------------
; Network settings
; -----------------------------------------------------------------------------

View File

@ -24,6 +24,12 @@ aimePath=DEVICE\aime.txt
; Enable high baud rate.
;highBaud=1
; Virtual-key code. If this button is **held** then the emulated IC card reader
; emulates an IC card in its proximity. A variety of different IC cards can be
; emulated; the exact choice of card that is emulated depends on the presence or
; absence of the configured card ID files. Default is the Return key.
scan=0x0D
[vfd]
; Enable VFD emulation. Disable to use a real VFD
; GP1232A02A FUTABA assembly.

View File

@ -22,6 +22,12 @@ appdata=
enable=1
aimePath=DEVICE\aime.txt
; Virtual-key code. If this button is **held** then the emulated IC card reader
; emulates an IC card in its proximity. A variety of different IC cards can be
; emulated; the exact choice of card that is emulated depends on the presence or
; absence of the configured card ID files. Default is the Return key.
scan=0x0D
[vfd]
; Enable VFD emulation. Disable to use a real VFD
; GP1232A02A FUTABA assembly.

View File

@ -24,6 +24,12 @@ enable=1
; will load the file from "resource\DEVICE\aime.txt".
aimePath=../DEVICE/aime.txt
; Virtual-key code. If this button is **held** then the emulated IC card reader
; emulates an IC card in its proximity. A variety of different IC cards can be
; emulated; the exact choice of card that is emulated depends on the presence or
; absence of the configured card ID files. Default is the Return key.
scan=0x0D
[led]
; Emulation for the LED board. Currently it's just dummy responses,
; but if somebody wants to make their keyboard or whatever light

View File

@ -22,6 +22,12 @@ appdata=
enable=1
aimePath=DEVICE\aime.txt
; Virtual-key code. If this button is **held** then the emulated IC card reader
; emulates an IC card in its proximity. A variety of different IC cards can be
; emulated; the exact choice of card that is emulated depends on the presence or
; absence of the configured card ID files. Default is the Return key.
scan=0x0D
; -----------------------------------------------------------------------------
; Network settings
; -----------------------------------------------------------------------------

View File

@ -22,6 +22,12 @@ appdata=
enable=1
aimePath=DEVICE\aime.txt
; Virtual-key code. If this button is **held** then the emulated IC card reader
; emulates an IC card in its proximity. A variety of different IC cards can be
; emulated; the exact choice of card that is emulated depends on the presence or
; absence of the configured card ID files. Default is the Return key.
scan=0x0D
[vfd]
; Enable VFD emulation. Disable to use a real VFD
; GP1232A02A FUTABA assembly.

View File

@ -21,6 +21,12 @@ appdata=
enable=1
aimePath=DEVICE\aime.txt
; Virtual-key code. If this button is **held** then the emulated IC card reader
; emulates an IC card in its proximity. A variety of different IC cards can be
; emulated; the exact choice of card that is emulated depends on the presence or
; absence of the configured card ID files. Default is the Return key.
scan=0x0D
; -----------------------------------------------------------------------------
; Network settings
; -----------------------------------------------------------------------------

5
dist/idz/launch.bat vendored
View File

@ -6,9 +6,10 @@ start "AM Daemon" /min inject -d -k idzhook.dll amdaemon.exe -c configDHCP_Final
rem Set dipsw1=0 and uncomment the ServerBox for in store battle?
rem inject -k idzhook.dll ServerBoxD8_Nu_x64.exe
inject -d -k idzhook.dll InitialD0_DX11_Nu.exe
inject -d -k idzhook.dll InitialD0_DX11_Nu.exe -m
taskkill /im ServerBoxD8_Nu_x64.exe > nul 2>&1
taskkill /f /im ServerBoxD8_Nu_x64.exe > nul 2>&1
taskkill /f /im amdaemon.exe > nul 2>&1
echo.
echo Game processes have terminated

View File

@ -24,6 +24,12 @@ aimePath=DEVICE\aime.txt
felicaGen=0
aimeGen=1
; Virtual-key code. If this button is **held** then the emulated IC card reader
; emulates an IC card in its proximity. A variety of different IC cards can be
; emulated; the exact choice of card that is emulated depends on the presence or
; absence of the configured card ID files. Default is the Return key.
scan=0x0D
; -----------------------------------------------------------------------------
; Network settings
; -----------------------------------------------------------------------------
@ -55,8 +61,10 @@ id=A69E-01A88888888
; 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.158.0
; that subnet must start with 192.168. The official keychip subnet is actually
; 192.168.158.0, but most dumps use 192.168.100.0 instead.
;subnet=192.168.158.0
subnet=192.168.100.0
[pcbid]
; Set the Windows host name. This should be an ALLS MAIN ID, without the

View File

@ -22,6 +22,12 @@ appdata=
enable=1
aimePath=DEVICE\aime.txt
; Virtual-key code. If this button is **held** then the emulated IC card reader
; emulates an IC card in its proximity. A variety of different IC cards can be
; emulated; the exact choice of card that is emulated depends on the presence or
; absence of the configured card ID files. Default is the Return key.
scan=0x0D
[vfd]
; Enable VFD emulation (currently just stubbed). Disable to use a real VFD
; GP1232A02A FUTABA assembly.

View File

@ -22,6 +22,12 @@ appdata=
enable=1
aimePath=DEVICE\aime.txt
; Virtual-key code. If this button is **held** then the emulated IC card reader
; emulates an IC card in its proximity. A variety of different IC cards can be
; emulated; the exact choice of card that is emulated depends on the presence or
; absence of the configured card ID files. Default is the Return key.
scan=0x0D
[vfd]
; Enable VFD emulation. Disable to use a real VFD
; GP1232A02A FUTABA assembly.

View File

@ -22,6 +22,12 @@ appdata=
enable=1
aimePath=DEVICE\aime.txt
; Virtual-key code. If this button is **held** then the emulated IC card reader
; emulates an IC card in its proximity. A variety of different IC cards can be
; emulated; the exact choice of card that is emulated depends on the presence or
; absence of the configured card ID files. Default is the Return key.
scan=0x0D
[vfd]
; Enable VFD emulation. Disable to use a real VFD
; GP1232A02A FUTABA assembly.

View File

@ -22,6 +22,12 @@ appdata=
enable=1
aimePath=DEVICE\aime.txt
; Virtual-key code. If this button is **held** then the emulated IC card reader
; emulates an IC card in its proximity. A variety of different IC cards can be
; emulated; the exact choice of card that is emulated depends on the presence or
; absence of the configured card ID files. Default is the Return key.
scan=0x0D
[vfd]
; Enable VFD emulation. Disable to use a real VFD
; GP1232A02A FUTABA assembly.

View File

@ -22,6 +22,12 @@ appdata=appdata
enable=1
aimePath=DEVICE\aime.txt
; Virtual-key code. If this button is **held** then the emulated IC card reader
; emulates an IC card in its proximity. A variety of different IC cards can be
; emulated; the exact choice of card that is emulated depends on the presence or
; absence of the configured card ID files. Default is the Return key.
scan=0x0D
[vfd]
; Enable VFD emulation. Disable to use a real VFD
; GP1232A02A FUTABA assembly.

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"apm3io",
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);

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

@ -0,0 +1,170 @@
/*
"ALL.Net P-ras MULTI Version 3" (apm3) 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 "hook/iohook.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;
static const wchar_t *target_modules[] = {
L"apmled.dll",
};
static const size_t target_modules_len = _countof(target_modules);
void unity_hook_callback(HMODULE hmodule, const wchar_t* p) {
dprintf("Unity: Hook callback: %ls\n", p);
for (size_t i = 0; i < target_modules_len; i++) {
if (_wcsicmp(p, target_modules[i]) == 0) {
serial_hook_apply_hooks(hmodule);
iohook_apply_hooks(hmodule);
}
}
security_hook_insert_hooks(hmodule);
touch_hook_insert_hooks(hmodule);
// mount_hook_apply_hooks(&apm3_hook_cfg.mount, hmodule);
}
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());
/* 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);

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

@ -0,0 +1,85 @@
#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(uint16_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);

10
games/apm3io/backend.h Normal file
View File

@ -0,0 +1,10 @@
#pragma once
#include <stdint.h>
#include "apm3io/apm3io.h"
struct apm3_io_backend {
void (*get_opbtns)(uint8_t *opbtn);
void (*get_gamebtns)(uint16_t *gamebtn);
};

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

@ -0,0 +1,94 @@
#include <windows.h>
#include <assert.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include "apm3io/config.h"
const int BUTTON_DEFAULTS[] = {'Q','W','E','R','A','S','D','F'};
void apm3_kb_config_load(
struct apm3_kb_config *cfg,
const wchar_t *filename)
{
assert(cfg != NULL);
assert(filename != NULL);
cfg->vk_start = GetPrivateProfileIntW(L"keyboard", L"start", 'P', filename);
cfg->vk_home = GetPrivateProfileIntW(L"keyboard", L"home", 'O', filename);
cfg->vk_up = GetPrivateProfileIntW(L"keyboard", L"up", VK_UP, filename);
cfg->vk_right = GetPrivateProfileIntW(L"keyboard", L"right", VK_RIGHT, filename);
cfg->vk_down = GetPrivateProfileIntW(L"keyboard", L"down", VK_DOWN, filename);
cfg->vk_left = GetPrivateProfileIntW(L"keyboard", 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"keyboard", tmp, BUTTON_DEFAULTS[i], filename);
}
}
void apm3_di_config_load(struct apm3_di_config *cfg, const wchar_t *filename)
{
wchar_t key[8];
int i;
assert(cfg != NULL);
assert(filename != NULL);
GetPrivateProfileStringW(
L"dinput",
L"deviceName",
L"",
cfg->device_name,
_countof(cfg->device_name),
filename);
cfg->home = GetPrivateProfileIntW(L"dinput", L"home", 0, filename);
cfg->start = GetPrivateProfileIntW(L"dinput", L"start", 0, filename);
for (i = 0 ; i < 8 ; i++) {
swprintf_s(key, _countof(key), L"button%i", i + 1);
cfg->button[i] = GetPrivateProfileIntW(L"dinput", key, i + 1, filename);
}
}
void apm3_xi_config_load(struct apm3_xi_config *cfg, const wchar_t *filename)
{
assert(cfg != NULL);
assert(filename != NULL);
cfg->analog_stick_enabled = GetPrivateProfileIntW(
L"xinput",
L"analogStickEnabled",
1,
filename);
}
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", VK_F1, filename);
cfg->vk_service = GetPrivateProfileIntW(L"io4", L"service", VK_F2, filename);
cfg->vk_coin = GetPrivateProfileIntW(L"io4", L"coin", VK_F3, filename);
GetPrivateProfileStringW(
L"io4",
L"mode",
L"",
cfg->mode,
_countof(cfg->mode),
filename);
apm3_kb_config_load(&cfg->kb, filename);
apm3_di_config_load(&cfg->di, filename);
apm3_xi_config_load(&cfg->xi, filename);
}

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

@ -0,0 +1,48 @@
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#define APM3_BUTTON_COUNT 8
struct apm3_di_config {
wchar_t device_name[64];
uint8_t start;
uint8_t home;
uint8_t button[8];
};
struct apm3_xi_config {
bool analog_stick_enabled;
};
struct apm3_kb_config {
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];
};
struct apm3_io_config {
uint8_t vk_test;
uint8_t vk_service;
uint8_t vk_coin;
wchar_t mode[9];
struct apm3_kb_config kb;
struct apm3_di_config di;
struct apm3_xi_config xi;
};
void apm3_io_config_load(struct apm3_io_config *cfg, const wchar_t *filename);
void apm3_kb_config_load(struct apm3_kb_config *cfg, const wchar_t *filename);
void apm3_di_config_load(struct apm3_di_config *cfg, const wchar_t *filename);
void apm3_xi_config_load(struct apm3_xi_config *cfg, const wchar_t *filename);

83
games/apm3io/di-dev.c Normal file
View File

@ -0,0 +1,83 @@
#include <windows.h>
#include <dinput.h>
#include <assert.h>
#include "apm3io/di-dev.h"
#include "util/dprintf.h"
HRESULT apm3_di_dev_start(IDirectInputDevice8W *dev, HWND wnd)
{
HRESULT hr;
assert(dev != NULL);
assert(wnd != NULL);
hr = IDirectInputDevice8_SetCooperativeLevel(
dev,
wnd,
DISCL_BACKGROUND | DISCL_EXCLUSIVE);
if (FAILED(hr)) {
dprintf("DirectInput: SetCooperativeLevel failed: %08x\n", (int) hr);
return hr;
}
hr = IDirectInputDevice8_SetDataFormat(dev, &c_dfDIJoystick);
if (FAILED(hr)) {
dprintf("DirectInput: SetDataFormat failed: %08x\n", (int) hr);
return hr;
}
hr = IDirectInputDevice8_Acquire(dev);
if (FAILED(hr)) {
dprintf("DirectInput: Acquire failed: %08x\n", (int) hr);
return hr;
}
return hr;
}
HRESULT apm3_di_dev_poll(
IDirectInputDevice8W *dev,
HWND wnd,
union apm3_di_state *out)
{
HRESULT hr;
MSG msg;
assert(dev != NULL);
assert(wnd != NULL);
assert(out != NULL);
memset(out, 0, sizeof(*out));
/* Pump our dummy window's message queue just in case DirectInput or an
IHV DirectInput driver somehow relies on it */
while (PeekMessageW(&msg, wnd, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
hr = IDirectInputDevice8_GetDeviceState(
dev,
sizeof(out->st),
&out->st);
if (FAILED(hr)) {
dprintf("DirectInput: GetDeviceState error: %08x\n", (int) hr);
}
/* JVS lacks a protocol for reporting hardware errors from poll command
responses, so this ends up returning zeroed input state instead. */
return hr;
}

18
games/apm3io/di-dev.h Normal file
View File

@ -0,0 +1,18 @@
#pragma once
#include <windows.h>
#include <dinput.h>
#include <stdint.h>
union apm3_di_state {
DIJOYSTATE st;
uint8_t bytes[sizeof(DIJOYSTATE)];
};
HRESULT apm3_di_dev_start(IDirectInputDevice8W *dev, HWND wnd);
HRESULT apm3_di_dev_poll(
IDirectInputDevice8W *dev,
HWND wnd,
union apm3_di_state *out);

257
games/apm3io/di.c Normal file
View File

@ -0,0 +1,257 @@
#include <windows.h>
#include <dinput.h>
#include <stddef.h>
#include <stdint.h>
#include <wchar.h>
#include <assert.h>
#include "apm3io/backend.h"
#include "apm3io/config.h"
#include "apm3io/di.h"
#include "apm3io/di-dev.h"
#include "apm3io/apm3io.h"
#include "apm3io/wnd.h"
#include "util/dprintf.h"
#include "util/str.h"
struct apm3_di_axis {
wchar_t name[4];
size_t off;
};
static HRESULT apm3_di_config_apply(const struct apm3_di_config *cfg);
static BOOL CALLBACK apm3_di_enum_callback(
const DIDEVICEINSTANCEW *dev,
void *ctx);
static BOOL CALLBACK apm3_di_enum_callback_shifter(
const DIDEVICEINSTANCEW *dev,
void *ctx);
static void apm3_di_get_gamebtns(uint16_t *gamebtn_out);
static uint8_t apm3_di_decode_pov(DWORD pov);
static const struct apm3_io_backend apm3_di_backend = {
.get_gamebtns = apm3_di_get_gamebtns,
};
static HWND apm3_di_wnd;
static IDirectInput8W *apm3_di_api;
static IDirectInputDevice8W *apm3_di_dev;
static IDirectInputEffect *apm3_di_fx;
static uint8_t apm3_di_home;
static uint8_t apm3_di_start;
static uint8_t apm3_di_button[8];
HRESULT apm3_di_init(
const struct apm3_di_config *cfg,
HINSTANCE inst,
const struct apm3_io_backend **backend)
{
HRESULT hr;
HMODULE dinput8;
HRESULT (WINAPI *api_entry)(HINSTANCE,DWORD,REFIID,LPVOID *,LPUNKNOWN);
wchar_t dll_path[MAX_PATH];
UINT path_pos;
assert(cfg != NULL);
assert(backend != NULL);
*backend = NULL;
hr = apm3_di_config_apply(cfg);
if (FAILED(hr)) {
return hr;
}
hr = apm3_io_wnd_create(inst, &apm3_di_wnd);
if (FAILED(hr)) {
return hr;
}
hr = IDirectInput8_EnumDevices(
apm3_di_api,
DI8DEVCLASS_GAMECTRL,
apm3_di_enum_callback,
(void *) cfg,
DIEDFL_ATTACHEDONLY);
if (FAILED(hr)) {
dprintf("DirectInput: EnumDevices failed: %08x\n", (int) hr);
return hr;
}
if (apm3_di_dev == NULL) {
dprintf("Stick: Controller not found\n");
return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
}
hr = apm3_di_dev_start(apm3_di_dev, apm3_di_wnd);
if (FAILED(hr)) {
return hr;
}
dprintf("DirectInput: Controller initialized\n");
*backend = &apm3_di_backend;
return S_OK;
}
static HRESULT apm3_di_config_apply(const struct apm3_di_config *cfg)
{
int i;
if (cfg->start > 32) {
dprintf("Stick: Invalid start button: %i\n", cfg->start);
return E_INVALIDARG;
}
if (cfg->home > 32) {
dprintf("Stick: Invalid home button: %i\n", cfg->home);
return E_INVALIDARG;
}
/* Check all 8 defined buttons */
for (i = 0; i < 8; i++) {
if (cfg->button[i] > 32) {
dprintf("Stick: Invalid button %i: %i\n", i, cfg->button[i]);
return E_INVALIDARG;
}
}
/* Print some debug output to make sure config works... */
dprintf("Stick: --- Begin configuration ---\n");
dprintf("Stick: Device name . . . . : Contains \"%S\"\n",
cfg->device_name);
dprintf("Stick: Home button . . . : %i\n", cfg->home);
dprintf("Stick: Start button . . . : %i\n", cfg->start);
/* Print the configuration for all 8 buttons */
for (i = 0; i < 8; i++) {
dprintf("Stick: Button %i . . . . . : %i\n", i, cfg->button[i]);
}
dprintf("Stick: --- End configuration ---\n");
apm3_di_start = cfg->start;
apm3_di_home = cfg->home;
for (i = 0; i < 8; i++) {
apm3_di_button[i] = cfg->button[i];
}
return S_OK;
}
static BOOL CALLBACK apm3_di_enum_callback(
const DIDEVICEINSTANCEW *dev,
void *ctx)
{
const struct apm3_di_config *cfg;
HRESULT hr;
cfg = ctx;
if (wcsstr(dev->tszProductName, cfg->device_name) == NULL) {
return DIENUM_CONTINUE;
}
dprintf("Stick: Using DirectInput device \"%S\"\n", dev->tszProductName);
hr = IDirectInput8_CreateDevice(
apm3_di_api,
&dev->guidInstance,
&apm3_di_dev,
NULL);
if (FAILED(hr)) {
dprintf("Stick: CreateDevice failed: %08x\n", (int) hr);
}
return DIENUM_STOP;
}
static void apm3_di_get_gamebtns(uint16_t *gamebtn_out)
{
union apm3_di_state state;
uint16_t gamebtn;
HRESULT hr;
assert(gamebtn_out != NULL);
hr = apm3_di_dev_poll(apm3_di_dev, apm3_di_wnd, &state);
if (FAILED(hr)) {
return;
}
gamebtn = apm3_di_decode_pov(state.st.rgdwPOV[0]);
if (apm3_di_start && state.st.rgbButtons[apm3_di_start - 1]) {
gamebtn |= APM3_IO_GAMEBTN_START;
}
if (apm3_di_home && state.st.rgbButtons[apm3_di_home - 1]) {
gamebtn |= APM3_IO_GAMEBTN_HOME;
}
if (apm3_di_button[0] && state.st.rgbButtons[apm3_di_button[0] - 1]) {
gamebtn |= APM3_IO_GAMEBTN_B1;
}
if (apm3_di_button[1] && state.st.rgbButtons[apm3_di_button[1] - 1]) {
gamebtn |= APM3_IO_GAMEBTN_B2;
}
if (apm3_di_button[2] && state.st.rgbButtons[apm3_di_button[2] - 1]) {
gamebtn |= APM3_IO_GAMEBTN_B3;
}
if (apm3_di_button[3] && state.st.rgbButtons[apm3_di_button[3] - 1]) {
gamebtn |= APM3_IO_GAMEBTN_B4;
}
if (apm3_di_button[4] && state.st.rgbButtons[apm3_di_button[4] - 1]) {
gamebtn |= APM3_IO_GAMEBTN_B5;
}
if (apm3_di_button[5] && state.st.rgbButtons[apm3_di_button[5] - 1]) {
gamebtn |= APM3_IO_GAMEBTN_B6;
}
if (apm3_di_button[6] && state.st.rgbButtons[apm3_di_button[6] - 1]) {
gamebtn |= APM3_IO_GAMEBTN_B7;
}
if (apm3_di_button[7] && state.st.rgbButtons[apm3_di_button[7] - 1]) {
gamebtn |= APM3_IO_GAMEBTN_B8;
}
*gamebtn_out = gamebtn;
}
static uint8_t apm3_di_decode_pov(DWORD pov)
{
switch (pov) {
case 0: return APM3_IO_GAMEBTN_UP;
case 4500: return APM3_IO_GAMEBTN_UP | APM3_IO_GAMEBTN_RIGHT;
case 9000: return APM3_IO_GAMEBTN_RIGHT;
case 13500: return APM3_IO_GAMEBTN_RIGHT | APM3_IO_GAMEBTN_DOWN;
case 18000: return APM3_IO_GAMEBTN_DOWN;
case 22500: return APM3_IO_GAMEBTN_DOWN | APM3_IO_GAMEBTN_LEFT;
case 27000: return APM3_IO_GAMEBTN_LEFT;
case 31500: return APM3_IO_GAMEBTN_LEFT | APM3_IO_GAMEBTN_UP;
default: return 0;
}
}

7
games/apm3io/di.h Normal file
View File

@ -0,0 +1,7 @@
#pragma once
#include "apm3io/backend.h"
#include "apm3io/config.h"
HRESULT apm3_di_init(const struct apm3_di_config *cfg, HINSTANCE inst,
const struct apm3_io_backend **backend);

105
games/apm3io/dllmain.c Normal file
View File

@ -0,0 +1,105 @@
#include <windows.h>
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include "apm3io/backend.h"
#include "apm3io/config.h"
#include "apm3io/apm3io.h"
#include "apm3io/kb.h"
#include "apm3io/di.h"
#include "apm3io/xi.h"
#include "util/dprintf.h"
#include "util/str.h"
static struct apm3_io_config apm3_io_cfg;
static const struct apm3_io_backend *apm3_io_backend;
static bool apm3_io_coin;
uint16_t apm3_io_get_api_version(void)
{
return 0x0100;
}
HRESULT apm3_io_init(void)
{
HINSTANCE inst;
HRESULT hr;
assert(apm3_io_backend == NULL);
inst = GetModuleHandleW(NULL);
if (inst == NULL) {
hr = HRESULT_FROM_WIN32(GetLastError());
dprintf("GetModuleHandleW failed: %lx\n", hr);
return hr;
}
apm3_io_config_load(&apm3_io_cfg, L".\\segatools.ini");
if (wstr_ieq(apm3_io_cfg.mode, L"keyboard")) {
hr = apm3_kb_init(&apm3_io_cfg.kb, &apm3_io_backend);
} else if (wstr_ieq(apm3_io_cfg.mode, L"dinput")) {
hr = apm3_di_init(&apm3_io_cfg.di, inst, &apm3_io_backend);
} else if (wstr_ieq(apm3_io_cfg.mode, L"xinput")) {
hr = apm3_xi_init(&apm3_io_cfg.xi, &apm3_io_backend);
} else {
hr = E_INVALIDARG;
dprintf("APM3 IO: Invalid IO mode \"%S\", use keyboard, dinput or xinput\n",
apm3_io_cfg.mode);
}
return hr;
}
void apm3_io_get_opbtns(uint8_t *opbtn_out)
{
uint8_t opbtn;
assert(apm3_io_backend != NULL);
assert(opbtn_out != NULL);
opbtn = 0;
if (GetAsyncKeyState(apm3_io_cfg.vk_test) & 0x8000) {
opbtn |= APM3_IO_OPBTN_TEST;
}
if (GetAsyncKeyState(apm3_io_cfg.vk_service) & 0x8000) {
opbtn |= APM3_IO_OPBTN_SERVICE;
}
if (GetAsyncKeyState(apm3_io_cfg.vk_coin) & 0x8000) {
if (!apm3_io_coin) {
apm3_io_coin = true;
opbtn |= APM3_IO_OPBTN_COIN;
}
} else {
apm3_io_coin = false;
}
*opbtn_out = opbtn;
}
void apm3_io_get_gamebtns(uint16_t *gamebtn_out)
{
assert(apm3_io_backend != NULL);
assert(gamebtn_out != NULL);
apm3_io_backend->get_gamebtns(gamebtn_out);
}
HRESULT apm3_io_led_init(void)
{
return S_OK;
}
void apm3_io_led_set_colors(uint8_t board, uint8_t *rgb)
{
return;
}

184
games/apm3io/kb.c Normal file
View File

@ -0,0 +1,184 @@
#include <windows.h>
#include <xinput.h>
#include <math.h>
#include <assert.h>
#include <limits.h>
#include <stdint.h>
#include "apm3io/apm3io.h"
#include "apm3io/config.h"
#include "apm3io/kb.h"
#include "util/dprintf.h"
#include "util/env.h"
static uint8_t apm3_kb_home;
static uint8_t apm3_kb_start;
static uint8_t apm3_kb_up;
static uint8_t apm3_kb_right;
static uint8_t apm3_kb_down;
static uint8_t apm3_kb_left;
static uint8_t apm3_kb_button[8];
static void apm3_kb_get_gamebtns(uint16_t *gamebtn_out);
static HRESULT apm3_kb_config_apply(const struct apm3_kb_config *cfg);
static const struct apm3_io_backend apm3_kb_backend = {
.get_gamebtns = apm3_kb_get_gamebtns,
};
HRESULT apm3_kb_init(const struct apm3_kb_config *cfg,
const struct apm3_io_backend **backend) {
HRESULT hr;
assert(cfg != NULL);
assert(backend != NULL);
*backend = NULL;
hr = apm3_kb_config_apply(cfg);
if (FAILED(hr)) {
return hr;
}
dprintf("Keyboard: Using keyboard input\n");
*backend = &apm3_kb_backend;
return S_OK;
}
static HRESULT apm3_kb_config_apply(const struct apm3_kb_config *cfg) {
int i;
if (cfg->vk_start > 255) {
dprintf("Keyboard: Invalid start key configuration: %u\n", cfg->vk_start);
return E_INVALIDARG;
}
if (cfg->vk_home > 255) {
dprintf("Keyboard: Invalid home key configuration: %u\n", cfg->vk_home);
return E_INVALIDARG;
}
if (cfg->vk_up > 255) {
dprintf("Keyboard: Invalid up key configuration: %u\n", cfg->vk_up);
return E_INVALIDARG;
}
if (cfg->vk_right > 255) {
dprintf("Keyboard: Invalid right key configuration: %u\n", cfg->vk_right);
return E_INVALIDARG;
}
if (cfg->vk_down > 255) {
dprintf("Keyboard: Invalid down key configuration: %u\n", cfg->vk_down);
return E_INVALIDARG;
}
if (cfg->vk_left > 255) {
dprintf("Keyboard: Invalid left key configuration: %u\n", cfg->vk_left);
return E_INVALIDARG;
}
for (i = 0; i < APM3_BUTTON_COUNT; i++) {
if (cfg->vk_buttons[i] > 255) {
dprintf("Keyboard: Invalid button %i configuration\n", i);
return E_INVALIDARG;
}
apm3_kb_button[i] = cfg->vk_buttons[i];
}
/* Apply the configuration */
apm3_kb_home = cfg->vk_home;
apm3_kb_start = cfg->vk_start;
apm3_kb_up = cfg->vk_up;
apm3_kb_right = cfg->vk_right;
apm3_kb_down = cfg->vk_down;
apm3_kb_left = cfg->vk_left;
/* Print some debug output to make sure config works... */
dprintf("Keyboard: --- Begin configuration ---\n");
dprintf("Keyboard: Home key . . . . : %u\n", apm3_kb_home);
dprintf("Keyboard: Start key . . . : %u\n", apm3_kb_start);
dprintf("Keyboard: Down key . . . . : %u\n", cfg->vk_down);
dprintf("Keyboard: Left key . . . . : %u\n", cfg->vk_left);
/* Print the configuration for all 8 buttons */
for (i = 0; i < APM3_BUTTON_COUNT; i++) {
dprintf("Keyboard: Button %i . . . . : %u\n", i + 1, apm3_kb_button[i]);
}
dprintf("Keyboard: --- End configuration ---\n");
return S_OK;
}
static void apm3_kb_get_gamebtns(uint16_t* gamebtn_out) {
uint16_t gamebtn;
assert(gamebtn_out != NULL);
gamebtn = 0;
if (GetAsyncKeyState(apm3_kb_home) & 0x8000) {
gamebtn |= APM3_IO_GAMEBTN_HOME;
}
if (GetAsyncKeyState(apm3_kb_start) & 0x8000) {
gamebtn |= APM3_IO_GAMEBTN_START;
}
if (GetAsyncKeyState(apm3_kb_up) & 0x8000) {
gamebtn |= APM3_IO_GAMEBTN_UP;
}
if (GetAsyncKeyState(apm3_kb_right) & 0x8000) {
gamebtn |= APM3_IO_GAMEBTN_RIGHT;
}
if (GetAsyncKeyState(apm3_kb_down) & 0x8000) {
gamebtn |= APM3_IO_GAMEBTN_DOWN;
}
if (GetAsyncKeyState(apm3_kb_left) & 0x8000) {
gamebtn |= APM3_IO_GAMEBTN_LEFT;
}
if (GetAsyncKeyState(apm3_kb_button[0]) & 0x8000) {
gamebtn |= APM3_IO_GAMEBTN_B1;
}
if (GetAsyncKeyState(apm3_kb_button[1]) & 0x8000) {
gamebtn |= APM3_IO_GAMEBTN_B2;
}
if (GetAsyncKeyState(apm3_kb_button[2]) & 0x8000) {
gamebtn |= APM3_IO_GAMEBTN_B3;
}
if (GetAsyncKeyState(apm3_kb_button[3]) & 0x8000) {
gamebtn |= APM3_IO_GAMEBTN_B4;
}
if (GetAsyncKeyState(apm3_kb_button[4]) & 0x8000) {
gamebtn |= APM3_IO_GAMEBTN_B5;
}
if (GetAsyncKeyState(apm3_kb_button[5]) & 0x8000) {
gamebtn |= APM3_IO_GAMEBTN_B6;
}
if (GetAsyncKeyState(apm3_kb_button[6]) & 0x8000) {
gamebtn |= APM3_IO_GAMEBTN_B7;
}
if (GetAsyncKeyState(apm3_kb_button[7]) & 0x8000) {
gamebtn |= APM3_IO_GAMEBTN_B8;
}
*gamebtn_out = gamebtn;
}

9
games/apm3io/kb.h Normal file
View File

@ -0,0 +1,9 @@
#pragma once
#include <windows.h>
#include "apm3io/backend.h"
#include "apm3io/config.h"
HRESULT apm3_kb_init(const struct apm3_kb_config *cfg,
const struct apm3_io_backend **backend);

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

@ -0,0 +1,31 @@
apm3io_lib = static_library(
'apm3io',
name_prefix : '',
include_directories : inc,
implicit_include_directories : false,
dependencies : [
dinput8_lib,
dxguid_lib,
xinput_lib,
],
link_with : [
util_lib,
],
sources : [
'apm3io.h',
'backend.h',
'config.c',
'config.h',
'di.c',
'di.h',
'di-dev.c',
'di-dev.h',
'dllmain.c',
'kb.c',
'kb.h',
'wnd.c',
'wnd.h',
'xi.c',
'xi.h',
],
)

86
games/apm3io/wnd.c Normal file
View File

@ -0,0 +1,86 @@
#include <windows.h>
#include <assert.h>
#include <string.h>
#include "util/dprintf.h"
/* DirectInput requires a window for correct initialization (and also force
feedback), so this source file provides some utilities for creating a
generic message-only window. */
static LRESULT WINAPI apm3_io_wnd_proc(
HWND hwnd,
UINT msg,
WPARAM wparam,
LPARAM lparam);
HRESULT apm3_io_wnd_create(HINSTANCE inst, HWND *out)
{
HRESULT hr;
WNDCLASSEXW wcx;
ATOM atom;
HWND hwnd;
assert(inst != NULL); /* We are not an EXE */
assert(out != NULL);
*out = NULL;
memset(&wcx, 0, sizeof(wcx));
wcx.cbSize = sizeof(wcx);
wcx.lpfnWndProc = apm3_io_wnd_proc;
wcx.hInstance = inst;
wcx.lpszClassName = L"apm3IO";
atom = RegisterClassExW(&wcx);
if (atom == 0) {
hr = HRESULT_FROM_WIN32(GetLastError());
dprintf("apm3IO: RegisterClassExW failed: %08x\n", (int) hr);
goto fail;
}
hwnd = CreateWindowExW(
0,
(wchar_t *) (intptr_t) atom,
L"",
0,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
HWND_MESSAGE,
NULL,
inst,
NULL);
if (hwnd == NULL) {
hr = HRESULT_FROM_WIN32(GetLastError());
dprintf("apm3IO: CreateWindowExW failed: %08x\n", (int) hr);
goto fail;
}
*out = hwnd;
return S_OK;
fail:
UnregisterClassW((wchar_t *) (intptr_t) atom, inst);
return hr;
}
static LRESULT WINAPI apm3_io_wnd_proc(
HWND hwnd,
UINT msg,
WPARAM wparam,
LPARAM lparam)
{
switch (msg) {
default:
return DefWindowProcW(hwnd, msg, wparam, lparam);
}
}

5
games/apm3io/wnd.h Normal file
View File

@ -0,0 +1,5 @@
#pragma once
#include <windows.h>
HRESULT apm3_io_wnd_create(HINSTANCE inst, HWND *out);

167
games/apm3io/xi.c Normal file
View File

@ -0,0 +1,167 @@
#include <windows.h>
#include <xinput.h>
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include "apm3io/backend.h"
#include "apm3io/config.h"
#include "apm3io/apm3io.h"
#include "apm3io/xi.h"
#include "util/dprintf.h"
static void apm3_xi_get_gamebtns(uint16_t *gamebtn_out);
static HRESULT apm3_xi_config_apply(const struct apm3_xi_config *cfg);
static const struct apm3_io_backend apm3_xi_backend = {
.get_gamebtns = apm3_xi_get_gamebtns,
};
static bool apm3_xi_analog_stick_enabled;
HRESULT apm3_xi_init(const struct apm3_xi_config *cfg, const struct apm3_io_backend **backend)
{
HRESULT hr;
assert(cfg != NULL);
assert(backend != NULL);
hr = apm3_xi_config_apply(cfg);
if (FAILED(hr)) {
return hr;
}
dprintf("XInput: Using XInput controller\n");
*backend = &apm3_xi_backend;
return S_OK;
}
HRESULT apm3_io_poll(void)
{
return S_OK;
}
static HRESULT apm3_xi_config_apply(const struct apm3_xi_config *cfg)
{
dprintf("XInput: --- Begin configuration ---\n");
dprintf("XInput: Analog Stick : %i\n", cfg->analog_stick_enabled);
dprintf("XInput: --- End configuration ---\n");
apm3_xi_analog_stick_enabled = cfg->analog_stick_enabled;
return S_OK;
}
static void apm3_xi_get_gamebtns(uint16_t *gamebtn_out)
{
uint16_t gamebtn;
XINPUT_STATE xi;
WORD xb;
int left_x, left_y;
assert(gamebtn_out != NULL);
gamebtn = 0;
memset(&xi, 0, sizeof(xi));
XInputGetState(0, &xi);
xb = xi.Gamepad.wButtons;
/* Use the left analog stick as a D Pad if enabled */
if (apm3_xi_analog_stick_enabled) {
left_x = xi.Gamepad.sThumbLX;
left_y = xi.Gamepad.sThumbLY;
if (left_x < -XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE * 2) {
left_x += XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE * 2;
} else if (left_x > XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE * 2) {
left_x -= XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE * 2;
} else {
left_x = 0;
}
if (left_y < -XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE * 2) {
left_y += XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE * 2;
} else if (left_y > XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE * 2) {
left_y -= XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE * 2;
} else {
left_y = 0;
}
if (left_y < 0) {
gamebtn |= APM3_IO_GAMEBTN_DOWN;
} else if (left_y > 0) {
gamebtn |= APM3_IO_GAMEBTN_UP;
}
if (left_x < 0) {
gamebtn |= APM3_IO_GAMEBTN_LEFT;
} else if (left_x > 0) {
gamebtn |= APM3_IO_GAMEBTN_RIGHT;
}
}
/* Normal game button controls */
if (xb & XINPUT_GAMEPAD_DPAD_UP) {
gamebtn |= APM3_IO_GAMEBTN_UP;
}
if (xb & XINPUT_GAMEPAD_DPAD_DOWN) {
gamebtn |= APM3_IO_GAMEBTN_DOWN;
}
if (xb & XINPUT_GAMEPAD_DPAD_LEFT) {
gamebtn |= APM3_IO_GAMEBTN_LEFT;
}
if (xb & XINPUT_GAMEPAD_DPAD_RIGHT) {
gamebtn |= APM3_IO_GAMEBTN_RIGHT;
}
if (xb & XINPUT_GAMEPAD_START) {
gamebtn |= APM3_IO_GAMEBTN_START;
}
if (xb & XINPUT_GAMEPAD_BACK) {
gamebtn |= APM3_IO_GAMEBTN_HOME;
}
if (xb & XINPUT_GAMEPAD_A) {
gamebtn |= APM3_IO_GAMEBTN_B1;
}
if (xb & XINPUT_GAMEPAD_B) {
gamebtn |= APM3_IO_GAMEBTN_B2;
}
if (xb & XINPUT_GAMEPAD_X) {
gamebtn |= APM3_IO_GAMEBTN_B5;
}
if (xb & XINPUT_GAMEPAD_Y) {
gamebtn |= APM3_IO_GAMEBTN_B6;
}
if (xb & XINPUT_GAMEPAD_LEFT_SHOULDER) {
gamebtn |= APM3_IO_GAMEBTN_B3;
}
if (xb & XINPUT_GAMEPAD_RIGHT_SHOULDER) {
gamebtn |= APM3_IO_GAMEBTN_B4;
}
if (xi.Gamepad.bLeftTrigger > 64) {
gamebtn |= APM3_IO_GAMEBTN_B7;
}
if (xi.Gamepad.bRightTrigger > 64) {
gamebtn |= APM3_IO_GAMEBTN_B8;
}
*gamebtn_out = gamebtn;
}

11
games/apm3io/xi.h Normal file
View File

@ -0,0 +1,11 @@
#pragma once
/* Can't call this xinput.h or it will conflict with <xinput.h> */
#include <windows.h>
#include "apm3io/backend.h"
#include "apm3io/config.h"
HRESULT apm3_xi_init(const struct apm3_xi_config *cfg,
const struct apm3_io_backend **backend);

View File

@ -87,7 +87,7 @@ static DWORD CALLBACK cm_pre_startup(void)
goto fail;
}
hr = sg_reader_hook_init(&cm_hook_cfg.aime, 1, 1, cm_hook_mod);
hr = sg_reader_hook_init(&cm_hook_cfg.aime, 1, 3, cm_hook_mod);
if (FAILED(hr)) {
goto fail;

View File

@ -82,7 +82,7 @@ static DWORD CALLBACK mercury_pre_startup(void)
goto fail;
}
hr = sg_reader_hook_init(&mercury_hook_cfg.aime, 1, 1, mercury_hook_mod);
hr = sg_reader_hook_init(&mercury_hook_cfg.aime, 1, 3, mercury_hook_mod);
if (FAILED(hr)) {
goto fail;

View File

@ -97,7 +97,7 @@ static DWORD CALLBACK mu3_pre_startup(void)
return hr;
}
hr = sg_reader_hook_init(&mu3_hook_cfg.aime, 1, 1, mu3_hook_mod);
hr = sg_reader_hook_init(&mu3_hook_cfg.aime, 1, 3, mu3_hook_mod);
if (FAILED(hr)) {
goto fail;

View File

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