Merge branch 'develop' into feature/netenv-broadcast-toggle

This commit is contained in:
Gl0w1amp
2026-06-19 17:16:08 +00:00
15 changed files with 296 additions and 39 deletions
+44
View File
@@ -200,6 +200,14 @@ static UINT WINAPI hook_GetDriveTypeW(
LPCWSTR lpRootPathName
);
static BOOL WINAPI hook_GetDiskFreeSpaceW(
LPCWSTR lpRootPathName,
LPDWORD lpSectorsPerCluster,
LPDWORD lpBytesPerSector,
LPDWORD lpNumberOfFreeClusters,
LPDWORD lpTotalNumberOfClusters
);
/* Link pointers */
static BOOL (WINAPI *next_CreateDirectoryA)(
@@ -395,6 +403,14 @@ static UINT (WINAPI *next_GetDriveTypeA)(
LPCSTR lpRootPathName
);
static BOOL (WINAPI *next_GetDiskFreeSpaceW)(
LPCWSTR lpRootPathName,
LPDWORD lpSectorsPerCluster,
LPDWORD lpBytesPerSector,
LPDWORD lpNumberOfFreeClusters,
LPDWORD lpTotalNumberOfClusters
);
/* Hook table */
static const struct hook_symbol path_hook_syms[] = {
@@ -538,6 +554,10 @@ static const struct hook_symbol path_hook_syms[] = {
.name = "GetDriveTypeW",
.patch = hook_GetDriveTypeW,
.link = (void **) &next_GetDriveTypeW,
}, {
.name = "GetDiskFreeSpaceW",
.patch = hook_GetDiskFreeSpaceW,
.link = (void **) &next_GetDiskFreeSpaceW,
}
};
@@ -1720,6 +1740,30 @@ static UINT WINAPI hook_GetDriveTypeW(
return result;
}
static BOOL WINAPI hook_GetDiskFreeSpaceW(
LPCWSTR lpRootPathName,
LPDWORD lpSectorsPerCluster,
LPDWORD lpBytesPerSector,
LPDWORD lpNumberOfFreeClusters,
LPDWORD lpTotalNumberOfClusters
) {
wchar_t *trans;
UINT result;
BOOL ok;
ok = path_transform_w(&trans, lpRootPathName);
if (!ok) {
return FALSE;
}
result = next_GetDiskFreeSpaceW(trans ? trans : lpRootPathName, lpSectorsPerCluster, lpBytesPerSector, lpNumberOfFreeClusters, lpTotalNumberOfClusters);
free(trans);
return result;
}
char** str_split_a(char* a_str, const char a_delim) {
char** result = 0;
size_t count = 0;
+21 -4
View File
@@ -60,6 +60,7 @@ static int32_t MTF[9];
/* Printer status */
static uint8_t STATUS = 0;
static uint8_t printSide = 0;
static ULONGLONG finishTime = 0;
/* C3XXFWDLusb API hooks */
@@ -150,7 +151,7 @@ int WINAPI chcusb_getEEPROM(uint8_t index, uint8_t *rData, uint16_t *rResult);
int WINAPI chcusb_setParameter(uint8_t a1, uint32_t a2, uint16_t *rResult);
int WINAPI chcusb_getParameter(uint8_t a1, uint8_t *a2, uint16_t *rResult);
int WINAPI chcusb_universal_command(int32_t a1, uint8_t a2, int32_t a3, uint8_t *a4, uint16_t *rResult);
int WINAPI chcusb_writeIred(uint8_t* a1, uint8_t* a2, uint16_t* rResult);
int WINAPI chcusb_writeIred(uint8_t* data, uint32_t* len, uint16_t* rResult);
/* PrintDLL API hooks */
@@ -2375,6 +2376,7 @@ int WINAPI chcusb_endpage(uint16_t *rResult) {
dprintf("Printer: Waiting for %dms...\n", printer_config.wait_time);
}
awaitingCardExit = true;
printSide = 0;
*rResult = 0;
return 1;
@@ -2388,8 +2390,8 @@ int WINAPI chcusb_write(uint8_t *data, uint32_t *writeSize, uint16_t *rResult) {
uint8_t metadata[44];
swprintf_s(
dumpPath, MAX_PATH,
L"%s\\C3XX_%04d%02d%02d_%02d%02d%02d.bmp",
printer_out_path, t.wYear, t.wMonth, t.wDay, t.wHour, t.wMinute, t.wSecond);
L"%s\\C3XX_%04d%02d%02d_%02d%02d%02d_%d.bmp",
printer_out_path, t.wYear, t.wMonth, t.wDay, t.wHour, t.wMinute, t.wSecond, printSide);
memcpy(metadata, cardRFID, 12);
memcpy(metadata + 12, cardDataBuffer, 32);
@@ -2399,6 +2401,8 @@ int WINAPI chcusb_write(uint8_t *data, uint32_t *writeSize, uint16_t *rResult) {
dprintf("Printer: C3XXusb: %s\n", __func__);
dwprintf(L"Printer: File written: %s\n", dumpPath);
printSide++;
*rResult = 0;
return 1;
@@ -3074,8 +3078,21 @@ void printer_set_dimensions(int width, int height){
HEIGHT = height;
}
int WINAPI chcusb_writeIred(uint8_t* a1, uint8_t* a2, uint16_t* rResult) {
int WINAPI chcusb_writeIred(uint8_t* data, uint32_t* len, uint16_t* rResult) {
dprintf("Printer: C3XXusb: %s(%d)\n", __func__, *len);
SYSTEMTIME t;
GetLocalTime(&t);
wchar_t dumpPath[MAX_PATH];
swprintf_s(
dumpPath, MAX_PATH,
L"%s\\C3XX_%04d%02d%02d_%02d%02d%02d_ir.bmp",
printer_out_path, t.wYear, t.wMonth, t.wDay, t.wHour, t.wMinute, t.wSecond);
WriteDataToBitmapFile(dumpPath, 8, WIDTH, HEIGHT, data, HOLO_SIZE, NULL, 0, rotate180);
dprintf("Printer: C3XXusb: %s\n", __func__);
*rResult = 0;
return 1;
}
+22 -11
View File
@@ -12,12 +12,15 @@
#include "util/dprintf.h"
#include "util/str.h"
#define MAX_CONCURRENT_REG_HANDLES 4
struct reg_hook_key {
HKEY root;
const wchar_t *name;
const struct reg_hook_val *vals;
size_t nvals;
HKEY handle;
HKEY handles[MAX_CONCURRENT_REG_HANDLES];
size_t nhandles;
};
/* Helper functions */
@@ -344,7 +347,7 @@ static LRESULT reg_hook_propagate_hr(HRESULT hr)
static struct reg_hook_key *reg_hook_match_key_locked(HKEY handle)
{
struct reg_hook_key *key;
size_t i;
size_t i, j;
if (handle == NULL || handle == INVALID_HANDLE_VALUE) {
return NULL;
@@ -353,8 +356,10 @@ static struct reg_hook_key *reg_hook_match_key_locked(HKEY handle)
for (i = 0 ; i < reg_hook_nkeys ; i++) {
key = &reg_hook_keys[i];
if (key->handle == handle) {
return key;
for (j = 0; j < key->nhandles; j++) {
if (key->handles[j] == handle) {
return key;
}
}
}
@@ -414,9 +419,9 @@ static LSTATUS reg_hook_open_locked(
return ERROR_SUCCESS;
}
/* Assume only one handle will be open at a time */
/* Assume only MAX_CONCURRENT_REG_HANDLES handles will be open at a time */
if (key->handle != NULL) {
if (key->nhandles > MAX_CONCURRENT_REG_HANDLES) {
return ERROR_SHARING_VIOLATION;
}
@@ -433,7 +438,7 @@ static LSTATUS reg_hook_open_locked(
out);
if (err == ERROR_SUCCESS) {
key->handle = *out;
key->handles[key->nhandles++] = *out;
}
return err;
@@ -511,16 +516,22 @@ static LSTATUS WINAPI hook_RegCreateKeyExW(
static LSTATUS WINAPI hook_RegCloseKey(HKEY handle)
{
struct reg_hook_key *key;
size_t i;
size_t i, j, k;
EnterCriticalSection(&reg_hook_lock);
for (i = 0 ; i < reg_hook_nkeys ; i++) {
key = &reg_hook_keys[i];
if (key->handle == handle) {
//dprintf("Registry: Closed virtual key %S\n", key->name);
key->handle = NULL;
for (j = 0; j < key->nhandles; j++) {
if (key->handles[j] == handle) {
for (k = j; k < MAX_CONCURRENT_REG_HANDLES - 1; k++) {
key->handles[k] = key->handles[k + 1];
}
key->handles[MAX_CONCURRENT_REG_HANDLES - 1] = NULL;
key->nhandles--;
//dprintf("Registry: Closed virtual key %S\n", key->name);
}
}
}
+8
View File
@@ -170,6 +170,13 @@ void misc_config_load(struct misc_config *cfg, const wchar_t *filename)
cfg->enable = GetPrivateProfileIntW(L"misc", L"enable", 1, filename);
cfg->allowMasterKeyWrite = GetPrivateProfileIntW(L"misc", L"allowMasterKeyWrite", 0, filename);
cfg->allowReboot = GetPrivateProfileIntW(L"misc", L"allowReboot", 0, filename);
GetPrivateProfileStringW(
L"misc",
L"nextProcessFilePath",
L"DEVICE\\NextProcess.txt",
cfg->nextProcessFile,
_countof(cfg->nextProcessFile),
filename);
}
void netenv_config_load(struct netenv_config *cfg, const wchar_t *filename)
@@ -346,6 +353,7 @@ void vfs_config_load(struct vfs_config *cfg, const wchar_t *filename)
assert(filename != NULL);
cfg->enable = GetPrivateProfileIntW(L"vfs", L"enable", 1, filename);
cfg->allowAmfsDownloads = GetPrivateProfileIntW(L"vfs", L"allowAmfsDownloads", 0, filename);
GetPrivateProfileStringW(
L"vfs",
+3 -3
View File
@@ -177,15 +177,15 @@ HRESULT dns_platform_hook_init(const struct dns_config *cfg)
return hr;
}
// Disable api/polling to the original servers
// ABaaS log server, receives system and error logs stored in appdata
hr = dns_hook_push(L"*.amlog.sys-all.net", NULL);
hr = dns_hook_push(L"*.amlog.sys-all.net", cfg->startup);
if (FAILED(hr)) {
return hr;
}
hr = dns_hook_push(L"*.d-amlog.sys-all.net", NULL);
hr = dns_hook_push(L"*.d-amlog.sys-all.net", cfg->startup);
if (FAILED(hr)) {
return hr;
+102 -2
View File
@@ -12,6 +12,8 @@
#include "platform/misc.h"
#include <shlwapi.h>
#include "util/dprintf.h"
static BOOL WINAPI misc_ExitWindowsEx(unsigned int flags, uint32_t reason);
@@ -22,6 +24,11 @@ static HRESULT misc_read_cpu_temp_error(void *bytes, uint32_t *nbytes);
static HRESULT misc_read_cpu_temp_warning(void *bytes, uint32_t *nbytes);
static HRESULT misc_read_platform_id(void *bytes, uint32_t *nbytes);
static HRESULT misc_read_platform_name(void *bytes, uint32_t *nbytes);
static HRESULT misc_read_main_nic(void *bytes, uint32_t *nbytes);
static HRESULT misc_read_extend_nic(void *bytes, uint32_t *nbytes);
static HRESULT misc_read_downloadui_done(void *bytes, uint32_t *nbytes);
static HRESULT misc_read_next_process(void *bytes, uint32_t *nbytes);
static HRESULT misc_write_next_process(const void *bytes, uint32_t nbytes);
static const struct hook_symbol misc_syms[] = {
{
@@ -44,11 +51,12 @@ static const struct reg_hook_val misc_master_keys[] = {
.read = misc_read_app_loader_count,
.type = REG_DWORD,
}, {
/* Black-hole val, list it here so we don't get a warning msg */
.name = L"NextProcess",
.read = misc_read_next_process,
.write = misc_write_next_process,
.type = REG_SZ,
}, {
/* ditto */
/* Black-hole val for reading, list it here so we don't get a warning msg */
.name = L"SystemError",
.type = REG_SZ,
}
@@ -74,7 +82,28 @@ static const struct reg_hook_val misc_static_keys[] = {
}
};
static const struct reg_hook_val misc_wireless_keys[] = {
{
.name = L"main_nic",
.read = misc_read_main_nic,
.type = REG_DWORD,
}, {
.name = L"extend_nic",
.read = misc_read_extend_nic,
.type = REG_DWORD,
}
};
static const struct reg_hook_val misc_downloadui_keys[] = {
{
.name = L"IsDone",
.read = misc_read_downloadui_done,
.type = REG_DWORD,
},
};
static wchar_t misc_platform_id[5];
static const struct misc_config *config;
HRESULT misc_hook_init(const struct misc_config *cfg, const char *platform_id)
{
@@ -83,6 +112,8 @@ HRESULT misc_hook_init(const struct misc_config *cfg, const char *platform_id)
assert(cfg != NULL);
assert(platform_id != NULL && strlen(platform_id) == 4);
config = cfg;
if (!cfg->enable) {
return S_FALSE;
}
@@ -124,8 +155,29 @@ HRESULT misc_hook_init(const struct misc_config *cfg, const char *platform_id)
L"SYSTEM\\SEGA\\SystemProperty\\Master",
misc_master_keys,
_countof(misc_master_keys));
if (FAILED(hr)) {
return hr;
}
}
hr = reg_hook_push_key(
HKEY_LOCAL_MACHINE,
L"SYSTEM\\SEGA\\SystemProperty\\wirelessNetwork",
misc_wireless_keys,
_countof(misc_wireless_keys));
if (FAILED(hr)) {
return hr;
}
hr = reg_hook_push_key(
HKEY_LOCAL_MACHINE,
L"SYSTEM\\SEGA\\SystemProperty\\downloadui",
misc_downloadui_keys,
_countof(misc_downloadui_keys));
if (FAILED(hr)) {
return hr;
}
@@ -136,6 +188,10 @@ HRESULT misc_hook_init(const struct misc_config *cfg, const char *platform_id)
hook_table_apply(NULL, "user32.dll", misc_syms, _countof(misc_syms));
}
if (PathFileExistsW(cfg->nextProcessFile)) {
DeleteFileW(cfg->nextProcessFile);
}
return S_OK;
}
@@ -175,3 +231,47 @@ static HRESULT misc_read_platform_name(void *bytes, uint32_t *nbytes)
{
return reg_hook_read_wstr(bytes, nbytes, L"ALLS MX2.1"); // TODO: Dynamic
}
static HRESULT misc_read_main_nic(void *bytes, uint32_t *nbytes)
{
return reg_hook_read_wstr(bytes, nbytes, L"");
}
static HRESULT misc_read_extend_nic(void *bytes, uint32_t *nbytes)
{
return reg_hook_read_wstr(bytes, nbytes, L"");
}
static HRESULT misc_read_downloadui_done(void *bytes, uint32_t *nbytes)
{
return reg_hook_read_u32(bytes, nbytes, 1);
}
static HRESULT misc_read_next_process(void *bytes, uint32_t *nbytes)
{
return reg_hook_read_wstr(bytes, nbytes, L"");
}
static HRESULT misc_write_next_process(const void *bytes, uint32_t nbytes)
{
HRESULT hr;
DWORD dwBytesWritten;
wchar_t* nextProcess = malloc(nbytes);
memcpy(nextProcess, bytes, nbytes);
dprintf("Misc: Next Process: %ls\n", nextProcess);
HANDLE hFile = CreateFileW(config->nextProcessFile, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
hr = HRESULT_FROM_WIN32(GetLastError());
dprintf("Misc: Error opening %ls for writing: %x\n", config->nextProcessFile, (int) hr);
return hr;
}
WriteFile(hFile, nextProcess, nbytes, &dwBytesWritten, NULL);
CloseHandle(hFile);
return S_OK;
}
+1
View File
@@ -8,6 +8,7 @@ struct misc_config {
bool enable;
bool allowReboot;
bool allowMasterKeyWrite;
wchar_t nextProcessFile[MAX_PATH];
};
HRESULT misc_hook_init(const struct misc_config *cfg, const char *platform_id);
+36
View File
@@ -41,6 +41,10 @@ static HRESULT vfs_custom_path_hook(
const wchar_t *src,
wchar_t *dest,
size_t *count);
static HRESULT vfs_path_hook_tmp_icf(
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);
@@ -77,6 +81,9 @@ 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 wchar_t vfs_tmp_icf[] = L"E:\\tmpIcf.icf";
static const size_t vfs_tmp_icf_len = _countof(vfs_tmp_icf) - 1;
static const struct reg_hook_val vfs_reg_vals[] = {
{
.name = L"AMFS",
@@ -168,6 +175,12 @@ HRESULT vfs_hook_init(const struct vfs_config *config, const char* game_id)
vfs_fixup_path(vfs_config.option, _countof(vfs_config.option), true);
}
hr = path_hook_push(vfs_path_hook_tmp_icf);
if (FAILED(hr)) {
return hr;
}
hr = vfs_mkdir_rec(vfs_config.amfs);
if (FAILED(hr)) {
@@ -601,6 +614,29 @@ static HRESULT vfs_path_hook_apm(
return S_OK;
}
// Block writing of E:\tmpIcf.icf to intentionally break the download process if the user has not enabled it
static HRESULT vfs_path_hook_tmp_icf(
const wchar_t *src,
wchar_t *dest,
size_t *count)
{
assert(src != NULL);
assert(count != NULL);
/* Case-insensitive check to see if src starts with vfs_tmp_icf */
if (path_compare_w(src, vfs_tmp_icf, vfs_tmp_icf_len) != 0) {
return S_FALSE;
}
if (vfs_config.allowAmfsDownloads) {
return S_FALSE;
}
dprintf("Vfs: AMFS downloads are blocked\n");
return E_FAIL;
}
static HRESULT vfs_reg_read_amfs(void *bytes, uint32_t *nbytes)
{
return reg_hook_read_wstr(bytes, nbytes, L"E:\\");
+1
View File
@@ -15,6 +15,7 @@ struct vfs_config {
wchar_t redirections_from[MAX_REDIRECTIONS][MAX_PATH];
int redirections_from_len[MAX_REDIRECTIONS];
wchar_t redirections_to[MAX_REDIRECTIONS][MAX_PATH];
bool allowAmfsDownloads;
};
HRESULT vfs_hook_init(const struct vfs_config *config, const char* game_id);
+6 -5
View File
@@ -111,8 +111,9 @@ framed=0
dpiAware=1
[touch]
; WinTouch emulation setting.
; WinTouch emulation setting. (set to 0 for native touchscreens)
enable=1
; Enable coordinate remapping (set to 0 if windowed mode cursor offset occurs)
remap=1
cursor=1
@@ -178,7 +179,7 @@ coin=0x72
mode=xinput
[xinput]
; XInput bindings
; XInput bindings
;
; Left Stick Joystick
; Left Stick Click Reset Camera
@@ -201,13 +202,13 @@ down=0x53
left=0x41
right=0x44
; Dash button. Default is the Left Shift,
; Dash button. Default is the Left Shift,
dash=0xA0
; Cycle Target button. Default is the F key.
target=0x46
; Attack button, Default is the Right Mouse Button.
; Attack button, Default is the Right Mouse Button.
attack=0x02
; Noble Phantasm button. Default is the Spacebar
; Noble Phantasm button. Default is the Spacebar
np=0x20
; Center Camera, Default is the C key
camera=0x43
+45 -7
View File
@@ -59,7 +59,7 @@ This is required for some games (e.g. Chunithm) but not others (e.g. WACCA).
Default: `1`
Changes the Aime card reader generation, this will also change the LED info
Changes the Aime card reader generation, this will also change the LED info
provided for the game.
- `1`: TN32MSEC003S H/W Ver3.0 / TN32MSEC003S F/W Ver1.2
@@ -198,7 +198,7 @@ resolves to one).
Default: `title`
Leave it as `title` to use the title server returned by ALL.Net. Rewrites
Leave it as `title` to use the title server returned by ALL.Net. Rewrites
the title server hostname for certain games, such as crossbeats REV.
### `router`
@@ -485,9 +485,9 @@ integer `modelType` setting, but they are combined here for convenience.
- `ACA`: ALLS UX/HX/MX
`modelType` is one of the following:
- `1`: Server (SV)
- `2`: Satalite (ST)
- `3`: Live (LV)
- `1`: Server (SV)
- `2`: Satalite (ST)
- `3`: Live (LV)
- `4`: Terminal (TN)
It's safe to assume that every game you'll be playing with these tools will be a Satalite.
@@ -513,7 +513,7 @@ Values are:
Default: `DEVICE\\ca.crt`
Set the billing certificate path. This has to match the one used for the
Set the billing certificate path. This has to match the one used for the
SSL billing server. The DER certificate must fit in 1024 bytes so it must be
small.
@@ -643,6 +643,28 @@ Default `DEVICE\sram.bin`
Path to the storage file for SRAM emulation.
### `[touch]`
Configure WinTouch emulation for mouse input.
#### `enable`
Default: `1`
Enable WinTouch touchscreen emulation for the mouse. Disable to use a native WinTouch-compatible touchscreen.
#### `remap`
Default: `1`
Enable coordinate remapping. Disable this if you running in windowed mode and find the touch position shifted when moving the window.
#### `cursor`
Default: `1`
Display a cursor to indicate touch position.
## `[vfs]`
Configure Windows path redirection hooks.
@@ -693,6 +715,12 @@ redirection0from=\\.\COM5
redirection0to=\\.\COM10
```
### `allowAmfsDownloads`
Default: `0`
Allows network services to download arbitrary files to the AMFS directory specified above. This has security implications, do not enable this, unless you trust your server operator.
## `[epay]`
Configure Thinca Payment (E-Money) emulation and hooks.
@@ -756,7 +784,7 @@ The Windows directory is always excluded from virtualization.
## `[misc]`
Configure miscellaneous hooks and features.
Configure miscellaneous hooks and features.
### `allowMasterKeyWrite`
@@ -769,3 +797,13 @@ Allows the game to write to specific registry keys relevant for the boot process
Default: `0`
Allows the game to reboot the computer. Only intended for owners of real hardware.
### `nextProcessFilePath`
Default: `DEVICE\NextProcess.txt`
This is a file that will be set to the content of what would be written to the `NextProcess` registry key when the game is terminated.
This allows whatever executed the game process to react what should happen next (System Test Mode selected, network delivery completed, ...) without requiring admin permissions.
The file is deleted on startup of segatools.
+1 -1
View File
@@ -12,7 +12,7 @@ if ERRORLEVEL 1 (
goto failure
)
docker run -it --rm -v %~dp0:/segatools --name %CONTAINER_NAME% %IMAGE_NAME%
docker run -it --rm -v "%~dp0:/segatools" --name %CONTAINER_NAME% %IMAGE_NAME%
if ERRORLEVEL 1 (
goto failure
+2 -2
View File
@@ -32,8 +32,8 @@ static uint8_t idac_di_rumble_duration;
static uint8_t idac_di_base_damper;
static uint8_t idac_di_deadband;
/* Fixed spring boost multiplier (1.5x) */
static const float idac_di_spring_boost = 1.5f;
/* Fixed spring boost multiplier */
static const float idac_di_spring_boost = 1.0f;
HRESULT idac_di_dev_init(const struct idac_di_config* cfg,
IDirectInputDevice8W* dev, HWND wnd) {
+2 -2
View File
@@ -31,8 +31,8 @@ static uint8_t idz_di_rumble_duration;
static uint8_t idz_di_base_damper;
static uint8_t idz_di_deadband;
/* Fixed spring boost multiplier (1.5x) */
static const float idz_di_spring_boost = 1.5f;
/* Fixed spring boost multiplier */
static const float idz_di_spring_boost = 1.0f;
HRESULT idz_di_dev_init(const struct idz_di_config* cfg,
IDirectInputDevice8W* dev, HWND wnd) {
+2 -2
View File
@@ -32,8 +32,8 @@ static uint8_t swdc_di_rumble_duration;
static uint8_t swdc_di_base_damper;
static uint8_t swdc_di_deadband;
/* Fixed spring boost multiplier (1.5x) */
static const float swdc_di_spring_boost = 1.5f;
/* Fixed spring boost multiplier */
static const float swdc_di_spring_boost = 1.0f;
HRESULT swdc_di_dev_init(const struct swdc_di_config* cfg,
IDirectInputDevice8W* dev, HWND wnd) {