mirror of
https://gitea.tendokyu.moe/TeamTofuShop/segatools.git
synced 2026-10-12 03:28:09 +03:00
apm: Fix distribution server handling, abaas log symlinks, add extra buttons for dinput (#123)
Requires https://gitea.tendokyu.moe/TeamTofuShop/capnhook/pulls/8 This adds the following: 1. Fixes `am::abaas::FileSearchSuite::isAvailableAbsolutePath: Line259 ERROR: folder is unavailable (Y:\SDEM\log\\SDFU)` by hooking `DeviceIoControl` with `FSCTL_SET_REPARSE_POINT`. Sega decided to use the most bizzare way possible of creating symbolic links. These are created in the AbaaS log directory inside SDEM and point to the appdata directories for the sub-games. Magic numbers have been taken from amdaemon decompilation. That whole API is very sparsely documented on the Microsoft side. 2. Fixes distribution server settings not applying. Unlike most other games, the daemon config files do not have dipswitch filters in them. Instead the distribution setting is determined by running `hwvalue.exe` twice and checking it's exit code. This has been added to `launch.bat` and requires the linked addition for inject. 3. Implemented `get_opbtns` and added extra buttons for dinput. * `get_opbtns` existed but was never implemented. Added the ability to bind dinput buttons for test/service. * DirectInput is weird. The controller I'm using is sending directional keys as buttons rather than a D-Pad, so I was unable to bind it. These extra directional buttons can be bound in addition to the bindings that exist for a D-Pad. 4. Fixes audio in Rolling Gunner by setting the CWD to the root of the C:\ drive before starting a game. lol. lmao even. 5. Add a `DefineDosDevice` for Y:\ to fix Blazblue crashing They hardcoded the damn path to appdata instead of retrieving it from the apm.dll... Reviewed-on: https://gitea.tendokyu.moe/TeamTofuShop/segatools/pulls/123 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:
@@ -3,6 +3,7 @@
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
@@ -11,6 +12,8 @@
|
||||
|
||||
#include "hooklib/path.h"
|
||||
|
||||
#include <ntdef.h>
|
||||
#include <winioctl.h>
|
||||
#include <util/dprintf.h>
|
||||
|
||||
/* Helpers */
|
||||
@@ -208,6 +211,16 @@ static BOOL WINAPI hook_GetDiskFreeSpaceW(
|
||||
LPDWORD lpTotalNumberOfClusters
|
||||
);
|
||||
|
||||
static BOOL WINAPI hook_DeviceIoControl(
|
||||
HANDLE hFile,
|
||||
uint32_t dwIoControlCode,
|
||||
void *lpInBuffer,
|
||||
uint32_t nInBufferSize,
|
||||
void *lpOutBuffer,
|
||||
uint32_t nOutBufferSize,
|
||||
uint32_t *lpBytesReturned,
|
||||
OVERLAPPED *lpOverlapped);
|
||||
|
||||
/* Link pointers */
|
||||
|
||||
static BOOL (WINAPI *next_CreateDirectoryA)(
|
||||
@@ -411,6 +424,16 @@ static BOOL (WINAPI *next_GetDiskFreeSpaceW)(
|
||||
LPDWORD lpTotalNumberOfClusters
|
||||
);
|
||||
|
||||
static BOOL (WINAPI *next_DeviceIoControl)(
|
||||
HANDLE hFile,
|
||||
uint32_t dwIoControlCode,
|
||||
void *lpInBuffer,
|
||||
uint32_t nInBufferSize,
|
||||
void *lpOutBuffer,
|
||||
uint32_t nOutBufferSize,
|
||||
uint32_t *lpBytesReturned,
|
||||
OVERLAPPED *lpOverlapped);
|
||||
|
||||
/* Hook table */
|
||||
|
||||
static const struct hook_symbol path_hook_syms[] = {
|
||||
@@ -558,6 +581,10 @@ static const struct hook_symbol path_hook_syms[] = {
|
||||
.name = "GetDiskFreeSpaceW",
|
||||
.patch = hook_GetDiskFreeSpaceW,
|
||||
.link = (void **) &next_GetDiskFreeSpaceW,
|
||||
}, {
|
||||
.name = "DeviceIoControl",
|
||||
.patch = hook_DeviceIoControl,
|
||||
.link = (void **) &next_DeviceIoControl,
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1764,6 +1791,65 @@ static BOOL WINAPI hook_GetDiskFreeSpaceW(
|
||||
return result;
|
||||
}
|
||||
|
||||
static BOOL WINAPI hook_DeviceIoControl(
|
||||
HANDLE hFile,
|
||||
uint32_t dwIoControlCode,
|
||||
void *lpInBuffer,
|
||||
uint32_t nInBufferSize,
|
||||
void *lpOutBuffer,
|
||||
uint32_t nOutBufferSize,
|
||||
uint32_t *lpBytesReturned,
|
||||
OVERLAPPED *lpOverlapped) {
|
||||
|
||||
if (dwIoControlCode != FSCTL_SET_REPARSE_POINT) {
|
||||
return next_DeviceIoControl(hFile, dwIoControlCode, lpInBuffer, nInBufferSize, lpOutBuffer, nOutBufferSize, lpBytesReturned, lpOverlapped);
|
||||
}
|
||||
|
||||
if (hFile == INVALID_HANDLE_VALUE || nInBufferSize == 0 || nInBufferSize > MAXIMUM_REPARSE_DATA_BUFFER_SIZE) {
|
||||
SetLastError(ERROR_INVALID_PARAMETER);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
REPARSE_DATA_BUFFER* mount = lpInBuffer;
|
||||
|
||||
wchar_t *trans;
|
||||
wchar_t *src;
|
||||
|
||||
if (wcsstr(mount->SymbolicLinkReparseBuffer.PathBuffer, L"?\\") != NULL) { // strip prefix
|
||||
src = mount->SymbolicLinkReparseBuffer.PathBuffer + 2;
|
||||
} else {
|
||||
src = mount->SymbolicLinkReparseBuffer.PathBuffer;
|
||||
}
|
||||
|
||||
BOOL ok = path_transform_w(&trans, src);
|
||||
|
||||
if (!ok || trans == NULL) {
|
||||
return next_DeviceIoControl(hFile, dwIoControlCode, lpInBuffer, nInBufferSize, lpOutBuffer, nOutBufferSize, lpBytesReturned, lpOverlapped);
|
||||
}
|
||||
|
||||
wchar_t tmp[MAX_PATH];
|
||||
swprintf_s(tmp, MAX_PATH, L"\\??\\%ls", trans);
|
||||
|
||||
size_t pathLen = wcslen(tmp);
|
||||
size_t pathByteLen = pathLen * sizeof(wchar_t);
|
||||
|
||||
REPARSE_DATA_BUFFER* newMount = malloc(sizeof(REPARSE_DATA_BUFFER) + pathByteLen);
|
||||
newMount->Reserved = mount->Reserved;
|
||||
newMount->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT;
|
||||
newMount->ReparseDataLength = 12 + pathByteLen;
|
||||
newMount->MountPointReparseBuffer.SubstituteNameOffset = 0;
|
||||
newMount->MountPointReparseBuffer.SubstituteNameLength = pathByteLen;
|
||||
newMount->MountPointReparseBuffer.PrintNameOffset = pathByteLen + 2;
|
||||
newMount->MountPointReparseBuffer.PrintNameLength = 0;
|
||||
memcpy(newMount->MountPointReparseBuffer.PathBuffer, tmp, pathByteLen);
|
||||
|
||||
BOOL result = next_DeviceIoControl(hFile, dwIoControlCode, newMount, 8 + 12 + pathByteLen, lpOutBuffer, nOutBufferSize, lpBytesReturned, lpOverlapped);
|
||||
|
||||
free(trans);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
char** str_split_a(char* a_str, const char a_delim) {
|
||||
char** result = 0;
|
||||
size_t count = 0;
|
||||
|
||||
Vendored
+18
-1
@@ -14,12 +14,23 @@ if exist %tmp%\SequenceSetting.json (
|
||||
del %tmp%\SequenceSetting.json
|
||||
)
|
||||
|
||||
rem detect model and dipswitch for server/client
|
||||
|
||||
set AMDAEMON_CONFIG=standalone.json
|
||||
inject -w -k apm3hook.dll hwvalue.exe modeltype
|
||||
if %ERRORLEVEL% == 5 goto BEGIN
|
||||
|
||||
inject -w -k apm3hook.dll hwvalue.exe dipsw
|
||||
set AMDAEMON_CONFIG=server.json
|
||||
if %ERRORLEVEL% == 1 set AMDAEMON_CONFIG=client.json
|
||||
|
||||
:BEGIN
|
||||
|
||||
pushd %~dp0
|
||||
|
||||
set DOORSTOP_DISABLE=TRUE
|
||||
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
|
||||
IF %ERRORLEVEL% NEQ 0 start /min "AM Daemon" inject -d -k apm3hook.dll amdaemon.exe -c daemon_config\common.json daemon_config\%AMDAEMON_CONFIG% config_hook.json
|
||||
set DOORSTOP_DISABLE=
|
||||
|
||||
REM Add "-screen-fullscreen 0 -popupWindow" if you want to run in windowed mode
|
||||
@@ -44,12 +55,18 @@ goto END
|
||||
|
||||
:APP
|
||||
|
||||
pushd C:\
|
||||
call W:\game.bat
|
||||
popd
|
||||
|
||||
taskkill /f /im emoneyUI.exe > nul 2>&1
|
||||
goto BEGIN
|
||||
|
||||
:APPTEST
|
||||
pushd C:\
|
||||
call W:\gametest.bat
|
||||
popd
|
||||
|
||||
goto BEGIN
|
||||
|
||||
:END
|
||||
|
||||
Vendored
+10
@@ -238,6 +238,9 @@ deviceName=
|
||||
start=9
|
||||
home=10
|
||||
|
||||
service=11
|
||||
test=12
|
||||
|
||||
button1=1
|
||||
button2=4
|
||||
button3=6
|
||||
@@ -246,3 +249,10 @@ button5=2
|
||||
button6=3
|
||||
button7=8
|
||||
button8=7
|
||||
|
||||
; Additional directional keys. These only have to be set if your
|
||||
; controller reports them as buttons rather than a hat switch.
|
||||
up=13
|
||||
right=14
|
||||
down=15
|
||||
left=16
|
||||
|
||||
+19
-1
@@ -114,7 +114,7 @@ bool hook_ApmSys_mountVhd(const wchar_t* vhdOriginalPath, const wchar_t* vhdPatc
|
||||
|
||||
// Game drive
|
||||
wchar_t device[3];
|
||||
device[0] = mountDriveLetter;
|
||||
device[0] = (unsigned char) mountDriveLetter;
|
||||
device[1] = ':';
|
||||
device[2] = '\0';
|
||||
|
||||
@@ -136,9 +136,19 @@ bool hook_ApmSys_mountVhd(const wchar_t* vhdOriginalPath, const wchar_t* vhdPatc
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Appdata drive
|
||||
device[0] = 'Y';
|
||||
|
||||
dprintf("Mount: Mapping %ls to %ls\n", device, vcfg->appdata);
|
||||
if (!DefineDosDeviceW(0, device, vcfg->appdata)) {
|
||||
dprintf("DefineDosDevice failed: %lx\n", GetLastError());
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (mcfg->delay) {
|
||||
Sleep(1500);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
bool hook_ApmSys_unmountVhd(char mountDriveLetter) {
|
||||
@@ -161,12 +171,20 @@ bool hook_ApmSys_unmountVhd(char mountDriveLetter) {
|
||||
dprintf("DefineDosDevice failed: %lx\n", GetLastError());
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Appdata drive
|
||||
device[0] = 'Y';
|
||||
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');
|
||||
hook_ApmSys_unmountVhd('Y');
|
||||
}
|
||||
|
||||
bool hook_ApmSys_unmountFscrypt(const wchar_t* mountFolderPath) {
|
||||
|
||||
@@ -47,6 +47,14 @@ void apm3_di_config_load(struct apm3_di_config *cfg, const wchar_t *filename)
|
||||
_countof(cfg->device_name),
|
||||
filename);
|
||||
|
||||
cfg->test = GetPrivateProfileIntW(L"dinput", L"test", 0, filename);
|
||||
cfg->service = GetPrivateProfileIntW(L"dinput", L"service", 0, filename);
|
||||
|
||||
cfg->up = GetPrivateProfileIntW(L"dinput", L"up", 0, filename);
|
||||
cfg->right = GetPrivateProfileIntW(L"dinput", L"right", 0, filename);
|
||||
cfg->down = GetPrivateProfileIntW(L"dinput", L"down", 0, filename);
|
||||
cfg->left = GetPrivateProfileIntW(L"dinput", L"left", 0, filename);
|
||||
|
||||
cfg->home = GetPrivateProfileIntW(L"dinput", L"home", 0, filename);
|
||||
cfg->start = GetPrivateProfileIntW(L"dinput", L"start", 0, filename);
|
||||
|
||||
|
||||
@@ -8,9 +8,15 @@
|
||||
|
||||
struct apm3_di_config {
|
||||
wchar_t device_name[64];
|
||||
uint8_t test;
|
||||
uint8_t service;
|
||||
uint8_t start;
|
||||
uint8_t home;
|
||||
uint8_t button[8];
|
||||
uint8_t up;
|
||||
uint8_t right;
|
||||
uint8_t down;
|
||||
uint8_t left;
|
||||
};
|
||||
|
||||
struct apm3_xi_config {
|
||||
|
||||
+50
-2
@@ -21,11 +21,13 @@ static BOOL CALLBACK apm3_di_enum_callback(
|
||||
const DIDEVICEINSTANCEW *dev,
|
||||
void *ctx);
|
||||
static void apm3_di_get_gamebtns(uint16_t *gamebtn_out);
|
||||
static void apm3_di_get_opbtns(uint8_t *opbtn_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,
|
||||
.get_opbtns = apm3_di_get_opbtns
|
||||
};
|
||||
|
||||
static HWND apm3_di_wnd;
|
||||
@@ -34,8 +36,16 @@ 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_service;
|
||||
static uint8_t apm3_di_test;
|
||||
static uint8_t apm3_di_extra_up;
|
||||
static uint8_t apm3_di_extra_right;
|
||||
static uint8_t apm3_di_extra_down;
|
||||
static uint8_t apm3_di_extra_left;
|
||||
static uint8_t apm3_di_button[APM3_BUTTON_COUNT];
|
||||
|
||||
static union apm3_di_state state = {0};
|
||||
|
||||
HRESULT apm3_di_init(
|
||||
const struct apm3_di_config *cfg,
|
||||
HINSTANCE inst,
|
||||
@@ -136,6 +146,8 @@ static HRESULT apm3_di_config_apply(const struct apm3_di_config *cfg)
|
||||
cfg->device_name);
|
||||
dprintf("Stick: Home button . . . : %i\n", cfg->home);
|
||||
dprintf("Stick: Start button . . . : %i\n", cfg->start);
|
||||
dprintf("Stick: Service button . . : %i\n", cfg->service);
|
||||
dprintf("Stick: Test button . . . : %i\n", cfg->test);
|
||||
|
||||
/* Print the configuration for all 8 buttons */
|
||||
for (i = 0; i < APM3_BUTTON_COUNT; i++) {
|
||||
@@ -146,7 +158,14 @@ static HRESULT apm3_di_config_apply(const struct apm3_di_config *cfg)
|
||||
|
||||
apm3_di_start = cfg->start;
|
||||
apm3_di_home = cfg->home;
|
||||
|
||||
apm3_di_service = cfg->service;
|
||||
apm3_di_test = cfg->test;
|
||||
|
||||
apm3_di_extra_up = cfg->up;
|
||||
apm3_di_extra_right = cfg->right;
|
||||
apm3_di_extra_down = cfg->down;
|
||||
apm3_di_extra_left = cfg->left;
|
||||
|
||||
for (i = 0; i < APM3_BUTTON_COUNT; i++) {
|
||||
apm3_di_button[i] = cfg->button[i];
|
||||
}
|
||||
@@ -184,7 +203,6 @@ static BOOL CALLBACK apm3_di_enum_callback(
|
||||
|
||||
static void apm3_di_get_gamebtns(uint16_t *gamebtn_out)
|
||||
{
|
||||
union apm3_di_state state;
|
||||
uint16_t gamebtn;
|
||||
HRESULT hr;
|
||||
|
||||
@@ -198,6 +216,22 @@ static void apm3_di_get_gamebtns(uint16_t *gamebtn_out)
|
||||
|
||||
gamebtn = apm3_di_decode_pov(state.st.rgdwPOV[0]);
|
||||
|
||||
if (apm3_di_extra_up && state.st.rgbButtons[apm3_di_extra_up - 1]) {
|
||||
gamebtn |= APM3_IO_GAMEBTN_UP;
|
||||
}
|
||||
|
||||
if (apm3_di_extra_right && state.st.rgbButtons[apm3_di_extra_right - 1]) {
|
||||
gamebtn |= APM3_IO_GAMEBTN_RIGHT;
|
||||
}
|
||||
|
||||
if (apm3_di_extra_down && state.st.rgbButtons[apm3_di_extra_down - 1]) {
|
||||
gamebtn |= APM3_IO_GAMEBTN_DOWN;
|
||||
}
|
||||
|
||||
if (apm3_di_extra_left && state.st.rgbButtons[apm3_di_extra_left - 1]) {
|
||||
gamebtn |= APM3_IO_GAMEBTN_LEFT;
|
||||
}
|
||||
|
||||
if (apm3_di_start && state.st.rgbButtons[apm3_di_start - 1]) {
|
||||
gamebtn |= APM3_IO_GAMEBTN_START;
|
||||
}
|
||||
@@ -255,3 +289,17 @@ static uint8_t apm3_di_decode_pov(DWORD pov)
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void apm3_di_get_opbtns(uint8_t *opbtn_out) {
|
||||
|
||||
uint8_t opbtn = 0;
|
||||
|
||||
if (apm3_di_service && state.st.rgbButtons[apm3_di_service - 1]) {
|
||||
opbtn |= APM3_IO_OPBTN_SERVICE;
|
||||
}
|
||||
if (apm3_di_test && state.st.rgbButtons[apm3_di_test - 1]) {
|
||||
opbtn |= APM3_IO_OPBTN_TEST;
|
||||
}
|
||||
|
||||
*opbtn_out = opbtn;
|
||||
}
|
||||
@@ -83,6 +83,10 @@ void apm3_io_get_opbtns(uint8_t *opbtn_out)
|
||||
apm3_io_coin = false;
|
||||
}
|
||||
|
||||
if (apm3_io_backend->get_opbtns != NULL) {
|
||||
apm3_io_backend->get_opbtns(&opbtn);
|
||||
}
|
||||
|
||||
*opbtn_out = opbtn;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
[wrap-git]
|
||||
directory = capnhook
|
||||
url = https://gitea.tendokyu.moe/TeamTofuShop/capnhook
|
||||
revision = 35169a505788ad414f831fd707fffd0b6d025477
|
||||
revision = afece371fb046cf9c540512663c02fa77242d12c
|
||||
|
||||
Reference in New Issue
Block a user