Compare commits

..

21 Commits

Author SHA1 Message Date
bicarus 8de7e5a9e4 sdvx: prevent the game from trying a random ASIO driver (#488)
## Link to GitHub Issue, if one exists
n/a

## Description of change
SDVX EG final started to pick an ASIO driver at random and tries to open
it, which causes crashes / hangs if the driver is not compatible
(ASIO4ALL in particular)

Only allow the game to access `HKLM\SOFTWARE\ASIO` if the user has a
Xonar AE, or if ASIO was configured using -sdvxasio.

## Testing
Tested year 1 data and EG final.
2025-12-30 17:58:28 -08:00
bicarus b8e99e75e5 gitadora: (arena model) prevent the small subscreen from being closed (#487)
## Link to GitHub Issue, if one exists
#477 

## Description of change
Ensure the small subscreen can't be resized or closed by accident.
2025-12-30 14:30:59 -08:00
bicarus 46dc22dd63 gitadora: (arena model) headphones i/o (#485)
## Link to GitHub Issue, if one exists
#477 

## Description of change
Plumb through headphone jack detection. Touch screen menu for headphone
volume lights up.

## Testing
Tested guitar.
2025-12-30 14:30:33 -08:00
oleg238948234 69d78e0a87 gitadora: (arena model) fix j32d encoding & use velocity (#483)
## Link to GitHub Issue, if one exists
N/A

## Description of change
in current implementation j32d analog values are not encoded properly,
values are supposed to go up to 1023, but they show up as 771 in game

## Testing
tested with M32-007-2025092400, used DM PAD CALIBRATION menu
2025-12-30 00:29:43 -08:00
oleg238948234 ab3d41432a gitadora: (arena model) F: drive fix (#484)
## Link to GitHub Issue, if one exists
N/A

## Description of change
prevents the game from creating folders on F: drive

## Testing
tested with M32-007-2025092400
2025-12-30 00:26:47 -08:00
bicarus 73e10cac0c gitadora: (arena model) text encoding hook (#482)
## Link to GitHub Issue, if one exists
#477 

## Description of change
Fix Japanese text broken in the game UI when system ACP is not
SHIFT-JIS.

## Testing
Checked song select and in-game title display.
2025-12-29 23:27:15 -08:00
bicarus c6242233a7 gitadora: (arena model) implement wailing, improve picking (#481)
## Link to GitHub Issue, if one exists
#477 

## Description of change
Add analog and digital wail for the arena model guitar.

Fix the picking code to be the same algorithm as extdev - detect rising
edges and insert 1 frame of input.

## Testing
Tested x/y/z and digital y up/down. Only Y gets used in game though.
2025-12-29 23:26:44 -08:00
bicarus 6b8335780f ddr: fix up init code when using old I/O with gold cab mode (#480)
## Link to GitHub Issue, if one exists
Fixes #479 

## Description of change
When p3io / p4io is in use with Gold Cab spec, use the old init code.

## Testing
Verified p3io / p4io / bio2 booting with 2024040200.
2025-12-29 16:26:30 -08:00
bicarus-dev 21f469b260 gitadora: arena cab volume change prevention (#478)
## Link to GitHub Issue, if one exists
#477 

## Description of change
Arena cab has two sound cards and it uses `WrappedIMMDeviceCollection`
to set the volume to 100% on boot on both, instead of the typical
`GetDefaultAudioEndpoint`.

## Testing
Testing Delta, testing other games as well as they are affected.
2025-12-29 03:23:58 -08:00
zxcwdta 07a0df5fdc gitadora: Added support for GITADORA Delta (#476)
## Link to GitHub Issue, if one exists
None

## Description of change
- Added bi2x_hook
- Added J32D (drum) and J33I (guitar) emulations
- AMI2000 card reader (this game uses this instead of ICCA)

## Testing
Played through a 15-minute session on Drummania.
Guitar mode works with keyboard input.
2025-12-28 16:17:06 -08:00
zxcwdta d4f1768bbd avs: Increased heap size for later gitadora games (#475)
## Link to GitHub Issue, if one exists
None

## Description of change
Increase the heap size for GITADORA games.

Later releases of GITADORA attempt to allocate a block of memory (size
of 0x7ECF78) for notes_info.xml but fail due to heap size being too
small, leading to HDD ERROR 5-1502-0008.

## Testing
Tested with current and previous GITADORA releases.
2025-12-26 23:00:57 -08:00
bicarus-dev 96e7e4949f iidx, linux: cache lstat result for movie thumbnails (#474)
## Link to GitHub Issue, if one exists
https://codeberg.org/nixac/spicetools/issues/4

## Description of change
Inspired by the Linux issue above in the fork, cache lstat calls for
thumbnail. IIDX 32 and 33 make a call for this every frame which can
cause significant overhead on Linux.

This is being enabled for all versions and not just the Linux variant of
the executables since it provides some marginal performance gain on
Windows.

## Testing
Tested IIDX 32/33, with the option on/off, and with AVS verbose logging
on/off.

Tested with ifs_layeredfs hook as well just to verify functionality.
2025-12-26 20:17:20 -08:00
bicarus-dev 24d3062c66 update superstep audio error troubleshooter message 2025-12-24 21:10:26 -08:00
bicarus-dev 6cd212aac2 iidx,sdvx: disable subscreen overlay if -monitor is in use (#471)
## Link to GitHub Issue, if one exists
#345 

## Description of change
If TDJ / VM mode is active AND user is enabled `-monitor` option (and
the value isn't 0, the primary monitor)

then disable the subscreen overlay, show an error message instead.

We're doing this because both games cannot accept emulated touch input
if `-monitor` option is in use. There is not much point in displaying
the second screen's image, other than to REALLY confuse people when
nothing happens when they click on it.

## Testing
Tested both games with `-monitor`.
2025-12-24 17:35:15 -08:00
bicarus-dev 064031bc12 Update CONTRIBUTING.md 2025-12-22 19:20:47 -08:00
bicarus-dev 4c8d4ebab6 iidx: detect ldj/tdj I/O mismatch (#470)
## Link to GitHub Issue, if one exists
#345 

## Description of change
For IIDX31+, detect if BI2A or BI2X I/O emulation becomes active, and
see if there is a mismatch between the -iidxtdj setting.

Right now a mismatch results in warning in log + deferred error log, but
in the future we could turn this into a fatal error and crash the game
to make it more obvious.

## Testing
Tested 4 combinations:

1. DLL in LDJ mode + -iidxtdj off (pass)
2. DLL in LDJ mode + -iidxtdj on (error)
3. DLL in TDJ mode + -iidxtdj off (error)
4. DLL in TDJ mode + -iidxtdj on (pass)
2025-12-22 18:52:37 -08:00
bicarus-dev 462a07680b Update CONTRIBUTING.md 2025-12-22 18:51:15 -08:00
bicarus-dev 081def30db overlay: clarify subscreen types (#469)
## Link to GitHub Issue, if one exists
#345 

## Description of change
In the main menu (Esc key), instead of just saying `Show subscreen`,
detect game and say `Show TDJ Subscreen` `Show LDJ LED Ticker` etc.

For the LED ticker window, show the title bar by default, and add an
option to hide it back.

This is to help with cases where people get stuck with being in LDJ mode
when they meant to turn on TDJ.

## Testing
Tested all configurations (IIDX LDJ/TDJ, SDVX UFC, DRS)
2025-12-22 15:59:31 -08:00
cchike 6776b08bce MDX: Refactored p4io.cpp (#467)
## Link to GitHub Issue, if one exists
#452 

## Description of change
Added `Sleep(4)` call to regulate how fast the `device_io` function will
call `getState` for each button. The panel buttons were commented out
because those are handled by the MDXF Control Status Buffer functions
anyways, so the work was redundant. It's also why the timing precision
from this function doesn't matter, so it can afford a longer sleep

## Testing
- Verified IO still functions properly
- Verified decrease in overall CPU usage
2025-12-21 20:49:39 -08:00
bicarus-dev 6cb507f4fa Update CONTRIBUTING.md 2025-12-21 18:57:42 -08:00
bicarus-dev de05af052f build: turn warnings into errors for gcc (#466)
for CI
2025-12-21 03:52:30 -08:00
49 changed files with 1831 additions and 62 deletions
+2
View File
@@ -49,6 +49,8 @@ Watch out for legacy OS compatibility. Currently, the minimum support floor is W
### Code style requirements ### Code style requirements
* Indents are four spaces. * Indents are four spaces.
* Try to keep under 100 characters per line. This includes comments and string literals.
* Avoid making unnecessary formatting or whitespace changes in a PR; e.g., watch out for text editor automatically stripping trailing whitespace. It pollutes the diff and slows down code review.
* Always use \{ curly braces \} when appropriate; do not omit them even when it's optional; such as `for` `if` `else`, etc. * Always use \{ curly braces \} when appropriate; do not omit them even when it's optional; such as `for` `if` `else`, etc.
OK: OK:
+10
View File
@@ -118,6 +118,9 @@ elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -static") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -static")
else() else()
# warnings as errors
set(CMAKE_COMPILE_WARNING_AS_ERROR ON)
# warnings # warnings
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra") # enable stuff set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra") # enable stuff
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-pointer-arith") # but we love pointer arithmetic :) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-pointer-arith") # but we love pointer arithmetic :)
@@ -398,6 +401,10 @@ set(SOURCE_FILES ${SOURCE_FILES}
games/nost/poke.cpp games/nost/poke.cpp
games/gitadora/gitadora.cpp games/gitadora/gitadora.cpp
games/gitadora/io.cpp games/gitadora/io.cpp
games/gitadora/handle.cpp
games/gitadora/j32d.cpp
games/gitadora/j33i.cpp
games/gitadora/bi2x_hook.cpp
games/mga/mga.cpp games/mga/mga.cpp
games/mga/io.cpp games/mga/io.cpp
games/mga/gunio.cpp games/mga/gunio.cpp
@@ -470,10 +477,12 @@ set(SOURCE_FILES ${SOURCE_FILES}
hooks/audio/acm.cpp hooks/audio/acm.cpp
hooks/audio/audio.cpp hooks/audio/audio.cpp
hooks/audio/buffer.cpp hooks/audio/buffer.cpp
hooks/audio/mme.cpp
hooks/audio/util.cpp hooks/audio/util.cpp
hooks/audio/backends/dsound/dsound_backend.cpp hooks/audio/backends/dsound/dsound_backend.cpp
hooks/audio/backends/mmdevice/audio_endpoint_volume.cpp hooks/audio/backends/mmdevice/audio_endpoint_volume.cpp
hooks/audio/backends/mmdevice/device.cpp hooks/audio/backends/mmdevice/device.cpp
hooks/audio/backends/mmdevice/device_collection.cpp
hooks/audio/backends/mmdevice/device_enumerator.cpp hooks/audio/backends/mmdevice/device_enumerator.cpp
hooks/audio/backends/wasapi/audio_client.cpp hooks/audio/backends/wasapi/audio_client.cpp
hooks/audio/backends/wasapi/audio_render_client.cpp hooks/audio/backends/wasapi/audio_render_client.cpp
@@ -530,6 +539,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
misc/sciunit.cpp misc/sciunit.cpp
misc/sde.cpp misc/sde.cpp
misc/wintouchemu.cpp misc/wintouchemu.cpp
misc/ami2000.cpp
# nvapi # nvapi
nvapi/nvapi.cpp nvapi/nvapi.cpp
+2
View File
@@ -1108,6 +1108,8 @@ namespace avs {
// SOUND VOLTEX // SOUND VOLTEX
{"soundvoltex.dll", 0x10000000}, {"soundvoltex.dll", 0x10000000},
// GITADORA
{"gdxg.dll", 0x2000000},
#endif #endif
// jubeat // jubeat
{"jubeat.dll", 0x2000000}, {"jubeat.dll", 0x2000000},
+4 -2
View File
@@ -494,7 +494,9 @@ namespace avs {
std::ostringstream init_code; std::ostringstream init_code;
init_code << EA3_MODEL; init_code << EA3_MODEL;
init_code << EA3_DEST; init_code << EA3_DEST;
if (strcmp(EA3_MODEL, "MDX") == 0 && strcmp(EA3_SPEC, "I") == 0) { if (avs::game::DLL_NAME == "arkmdxbio2.dll" &&
strcmp(EA3_MODEL, "MDX") == 0 && strcmp(EA3_SPEC, "I") == 0) {
// ddr gold cab
init_code << "G"; init_code << "G";
init_code << "B"; init_code << "B";
} else { } else {
@@ -529,7 +531,7 @@ namespace avs {
auto app_param = avs::core::property_search_safe(app_config, nullptr, "/param"); auto app_param = avs::core::property_search_safe(app_config, nullptr, "/param");
// call the game init // call the game init
log_info("avs-ea3", "calling entry init"); log_info("avs-ea3", "calling entry init (init code = {})", init_code_str);
if (!avs::game::entry_init(init_code_str.data(), app_param)) { if (!avs::game::entry_init(init_code_str.data(), app_param)) {
log_fatal("avs-ea3", "entry init failed :("); log_fatal("avs-ea3", "entry init failed :(");
} }
+5 -8
View File
@@ -253,28 +253,25 @@ namespace games::ddr {
if(((dwIoControlCode>>16)&0xFFFF) == FILE_DEVICE_UNKNOWN) { if(((dwIoControlCode>>16)&0xFFFF) == FILE_DEVICE_UNKNOWN) {
switch((dwIoControlCode>>2)&0x3FFF) { switch((dwIoControlCode>>2)&0x3FFF) {
case P4IO_IOCTL_GET_INPUTS: { case P4IO_IOCTL_GET_INPUTS: {
// Prevents this function from being called at its normal 2000-2500 kHz cadence and overloading the CPU, instead reduces it to 250 Hz
Sleep(4);
memset(lpOutBuffer, 0, 16); memset(lpOutBuffer, 0, 16);
auto controls = (uint32_t*) lpOutBuffer; auto controls = (uint32_t*) lpOutBuffer;
const std::pair<size_t, uint8_t> buttonMapping[] = const std::pair<size_t, uint8_t> buttonMapping[] =
{ {
// Panel input comes from MDXF Control Status Buffer functions
{Buttons::SERVICE, 25}, //Good {Buttons::SERVICE, 25}, //Good
{Buttons::TEST, 28}, //Good {Buttons::TEST, 28}, //Good
{Buttons::COIN_MECH, 24}, //Good {Buttons::COIN_MECH, 24}, //Good
{Buttons::P1_START, 0}, //Good {Buttons::P1_START, 0}, //Good
{Buttons::P1_PANEL_UP, 5}, //Good
{Buttons::P1_PANEL_DOWN, 6}, //Good
{Buttons::P1_PANEL_LEFT, 7}, //Goood
{Buttons::P1_PANEL_RIGHT, 16}, //Good
{Buttons::P1_MENU_UP, 1}, //Good {Buttons::P1_MENU_UP, 1}, //Good
{Buttons::P1_MENU_DOWN, 2}, //Good {Buttons::P1_MENU_DOWN, 2}, //Good
{Buttons::P1_MENU_LEFT, 3}, //Good {Buttons::P1_MENU_LEFT, 3}, //Good
{Buttons::P1_MENU_RIGHT, 4}, //Good {Buttons::P1_MENU_RIGHT, 4}, //Good
{Buttons::P2_START, 8}, //Good {Buttons::P2_START, 8}, //Good
{Buttons::P2_PANEL_UP, 13}, //Good
{Buttons::P2_PANEL_DOWN, 14}, //Good
{Buttons::P2_PANEL_LEFT, 15}, //Good
{Buttons::P2_PANEL_RIGHT, 20}, //Good
{Buttons::P2_MENU_UP, 9}, //Good {Buttons::P2_MENU_UP, 9}, //Good
{Buttons::P2_MENU_DOWN, 10}, //Good {Buttons::P2_MENU_DOWN, 10}, //Good
{Buttons::P2_MENU_LEFT, 11}, //Good {Buttons::P2_MENU_LEFT, 11}, //Good
+664
View File
@@ -0,0 +1,664 @@
#include "bi2x_hook.h"
#include <cstdint>
#include "util/detour.h"
#include "util/logging.h"
#include "rawinput/rawinput.h"
#include "misc/eamuse.h"
#include "games/io.h"
#include "io.h"
#include "util/tapeled.h"
#include <typeinfo>
namespace games::gitadora {
// /*
// * class definitions
// */
struct AIO_NMGR_IOB2 {
};
struct AIO_IOB2_BI2X_AC1__SETTING {
};
struct AIO_NMGR_IOB5 {
};
struct AIO_SCI_COMM {
};
struct AIO_IOB5_Y32D {
};
// struct AIO_IOB5_Y33I {
// };
struct AIO_IOB2_BI2X_AC1 {
};
struct AIO_NMGR_IOB__NODEINFO {
uint8_t data[0xA8];
};
struct AIO_IOB2_BI2X_WRFIRM {
};
struct AIO_SCI_COMM_T {
uint64_t unk_0;
struct AIO_SCI_COMM *aioSciComm;
};
struct AIO_COMM_STATUS {
uint64_t unk[5];
};
struct AIO_IOB2_BI2X_AC1__INPUT {
uint8_t DevIoCounter;
uint8_t bExIoAErr;
uint8_t bExIoBErr;
uint8_t bPcPowerOn;
uint8_t bPcPowerCheck;
uint8_t bCoin1Jam;
uint8_t bCoin2Jam;
uint8_t bCoin3Jam;
uint8_t bCoin4Jam;
uint8_t Coin1Count;
uint8_t Coin2Count;
uint8_t Coin3Count;
uint8_t Coin4Count;
uint16_t AnalogCh1;
uint16_t AnalogCh2;
uint16_t AnalogCh3;
uint16_t AnalogCh4;
uint8_t CN8_8;
uint8_t CN8_9;
uint8_t CN8_10;
uint8_t CN9_8;
uint8_t CN9_9;
uint8_t CN9_10;
uint8_t CN11_11;
uint8_t CN11_12;
uint8_t CN11_13;
uint8_t CN11_14;
uint8_t CN11_15;
uint8_t CN11_16;
uint8_t CN11_17;
uint8_t CN11_18;
uint8_t CN11_19;
uint8_t CN11_20;
uint8_t CN12_11;
uint8_t CN12_12;
uint8_t CN12_13;
uint8_t CN12_14;
uint8_t CN12_15;
uint8_t CN12_16;
uint8_t CN12_17;
uint8_t CN12_18;
uint8_t CN12_19;
uint8_t CN12_20;
uint8_t CN12_21;
uint8_t CN12_22;
uint8_t CN12_23;
uint8_t CN12_24;
uint8_t CN15_3;
uint8_t CN15_4;
uint8_t CN15_5;
uint8_t CN15_6;
uint8_t CN15_7;
uint8_t CN15_8;
uint8_t CN15_9;
uint8_t CN15_10;
uint8_t CN15_11;
uint8_t CN15_12;
uint8_t CN15_13;
uint8_t CN15_14;
uint8_t CN15_15;
uint8_t CN15_16;
uint8_t CN15_17;
uint8_t CN15_18;
uint8_t CN15_19;
uint8_t CN15_20;
uint8_t CN19_8;
uint8_t CN19_9;
uint8_t CN19_10;
uint8_t CN19_11;
uint8_t CN19_12;
uint8_t CN19_13;
uint8_t CN19_14;
uint8_t CN19_15;
};
struct AIO_IOB2_BI2X_AC1__INPUTDATA {
uint8_t data[247];
};
struct AIO_IOB2_BI2X_AC1__OUTPUTDATA {
uint8_t data[48];
};
struct AIO_IOB2_BI2X_AC1__ICNPIN {
uint16_t Ain[4];
uint64_t CnPin;
};
struct AIO_IOB2_BI2X_AC1__DEVSTATUS {
uint8_t InputCounter;
uint8_t OutputCounter;
uint8_t IoResetCounter;
uint8_t TapeLedCounter;
uint8_t TapeLedRate[8];
AIO_IOB2_BI2X_AC1__INPUT Input;
AIO_IOB2_BI2X_AC1__INPUTDATA InputData;
AIO_IOB2_BI2X_AC1__OUTPUTDATA OutputData;
AIO_IOB2_BI2X_AC1__ICNPIN ICnPinHist[20];
};
struct AIO_IOB5_Y32D__DEVSTATUS {
uint8_t unk[0xB1];
};
/*
* typedefs
*/
// libaio-iob2_video.dll
typedef AIO_IOB2_BI2X_AC1 *(__fastcall *aioIob2Bi2xAC1_Create_t)(AIO_NMGR_IOB2 *i_pNodeMgr, uint32_t i_DevId,
AIO_IOB2_BI2X_AC1__SETTING *i_Setting);
typedef void(__fastcall *aioIob2Bi2xAC1_GetDeviceStatus_t)(AIO_IOB2_BI2X_AC1 *i_pNodeCtl,
AIO_IOB2_BI2X_AC1__DEVSTATUS *o_DevStatus);
typedef void(__fastcall *aioIob2Bi2xAC1_SetWatchDogTimer_t)(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint8_t i_Count);
typedef void(__fastcall *aioIob2Bi2xAC1_AddCounter_t)(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint32_t i_Counter,
uint32_t i_Count);
typedef void(__fastcall *aioIob2Bi2xAC1_SetOutputData_t)(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint32_t i_CnPin,
uint8_t i_Data);
typedef void(__fastcall *aioIob2Bi2xAC1_SetTapeLedDataPart_t)(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint32_t i_TapeLedCh,
uint32_t i_Offset, uint8_t *i_pData,
uint32_t i_cntTapeLed, bool i_bReverse);
typedef void(__fastcall *aioIob2Bi2x_SetTapeLedDataLimit_t)(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint32_t i_Channel,
uint8_t i_Scale, uint8_t i_Limit);
typedef AIO_IOB2_BI2X_WRFIRM *(__fastcall *aioIob2Bi2x_CreateWriteFirmContext_t)(uint32_t i_SerialNumber,
uint32_t i_bfIob);
typedef void(__fastcall *aioIob2Bi2x_DestroyWriteFirmContext_t)(AIO_IOB2_BI2X_WRFIRM *i_pWrFirm);
typedef int32_t(__fastcall *aioIob2Bi2x_WriteFirmGetState_t)(AIO_IOB2_BI2X_WRFIRM *i_pWrFirm);
typedef bool(__fastcall *aioIob2Bi2x_WriteFirmIsCompleted_t)(int32_t i_State);
// libaio-iob.dll
typedef AIO_NMGR_IOB2 *(__fastcall *aioNMgrIob2_Create_t)(AIO_SCI_COMM *i_pSci, uint32_t i_bfMode);
typedef void(__fastcall *aioNMgrIob_BeginManage_t)(AIO_NMGR_IOB2 *i_pNodeMgr);
typedef void(__fastcall *aioNCtlIob_GetNodeInfo_t)(AIO_IOB5_Y32D *i_pNodeCtl,
AIO_NMGR_IOB__NODEINFO *o_NodeInfo);
// libaio.dll
typedef AIO_SCI_COMM_T *(__fastcall *aioSciComm_Open_t)(AIO_SCI_COMM_T *unk);
typedef void(__fastcall *aioSci_Destroy_t)(AIO_SCI_COMM *i_pNodeMgr);
typedef void(__fastcall *aioSci_GetCommStatus_t)(AIO_SCI_COMM *i_pNodeMgr, AIO_COMM_STATUS *i_Status);
typedef void(__fastcall *aioNodeMgr_Destroy_t)(AIO_NMGR_IOB5 *i_pNodeMgr);
typedef int32_t(__fastcall *aioNodeMgr_GetState_t)(AIO_NMGR_IOB5 *i_pNodeMgr);
typedef bool(__fastcall *aioNodeMgr_IsReady_t)(AIO_NMGR_IOB5 *i_pNodeMgr, int32_t i_State);
typedef bool(__fastcall *aioNodeMgr_IsError_t)(AIO_NMGR_IOB5 *i_pNodeMgr, int32_t i_State);
typedef void(__fastcall *aioNodeCtl_Destroy_t)(AIO_IOB5_Y32D *i_pNodeCtl);
typedef int32_t(__fastcall *aioNodeCtl_GetState_t)(AIO_IOB5_Y32D *i_pNodeCtl);
typedef bool(__fastcall *aioNodeCtl_IsReady_t)(AIO_IOB5_Y32D *i_pNodeCtl, int32_t i_State);
typedef bool(__fastcall *aioNodeCtl_IsError_t)(AIO_IOB5_Y32D *i_pNodeCtl, int32_t i_State);
typedef void(__fastcall *aioNodeCtl_UpdateDevicesStatus_t)();
// libaio-iob5.dll
typedef AIO_NMGR_IOB5 *(__fastcall *aioNMgrIob5_Create_t)(AIO_SCI_COMM *i_pSci, uint32_t i_bfMode);
// libaio-iob5_y32.dll
typedef AIO_IOB5_Y32D *(__fastcall *aioIob5Y32d_Create_t)(AIO_NMGR_IOB5 *i_pSci, uint32_t i_bfMode);
// typedef AIO_IOB5_Y33I *(__fastcall *aioIob5Y33i_Create_t)(AIO_NMGR_IOB5 *i_pSci, uint32_t i_bfMode);
typedef void(__fastcall *aioIob5Y32d_GetDeviceStatus_t)(AIO_IOB5_Y32D *i_pNodeCtl,
AIO_IOB5_Y32D__DEVSTATUS *o_DevStatus);
// typedef void(__fastcall *aioIob5Y33i_GetDeviceStatus_t)(AIO_IOB5_Y33I *i_pNodeCtl,
// AIO_IOB2_BI2X_AC1__DEVSTATUS *o_DevStatus);
/*
* function pointers
*/
// libaio-iob2_video.dll
static aioIob2Bi2xAC1_Create_t aioIob2Bi2xAC1_Create_orig = nullptr;
static aioIob2Bi2xAC1_GetDeviceStatus_t aioIob2Bi2xAC1_GetDeviceStatus_orig = nullptr;
static aioIob2Bi2xAC1_SetWatchDogTimer_t aioIob2Bi2xAC1_SetWatchDogTimer_orig = nullptr;
static aioIob2Bi2xAC1_AddCounter_t aioIob2Bi2xAC1_AddCounter_orig = nullptr;
static aioIob2Bi2xAC1_SetOutputData_t aioIob2Bi2xAC1_SetOutputData_orig = nullptr;
static aioIob2Bi2xAC1_SetTapeLedDataPart_t aioIob2Bi2xAC1_SetTapeLedDataPart_orig = nullptr;
static aioIob2Bi2x_SetTapeLedDataLimit_t aioIob2Bi2x_SetTapeLedDataLimit_orig = nullptr;
static aioIob2Bi2x_CreateWriteFirmContext_t aioIob2Bi2x_CreateWriteFirmContext_orig = nullptr;
static aioIob2Bi2x_DestroyWriteFirmContext_t aioIob2Bi2x_DestroyWriteFirmContext_orig = nullptr;
static aioIob2Bi2x_WriteFirmGetState_t aioIob2Bi2x_WriteFirmGetState_orig = nullptr;
static aioIob2Bi2x_WriteFirmIsCompleted_t aioIob2Bi2x_WriteFirmIsCompleted_orig = nullptr;
// libaio.dll
static aioSciComm_Open_t aioSciComm_Open_orig = nullptr;
static aioSci_Destroy_t aioSci_Destroy_orig = nullptr;
static aioSci_GetCommStatus_t aioSci_GetCommStatus_orig = nullptr;
static aioNodeMgr_Destroy_t aioNodeMgr_Destroy_orig = nullptr;
static aioNodeMgr_GetState_t aioNodeMgr_GetState_orig = nullptr;
static aioNodeMgr_IsReady_t aioNodeMgr_IsReady_orig = nullptr;
static aioNodeMgr_IsError_t aioNodeMgr_IsError_orig = nullptr;
static aioNodeCtl_Destroy_t aioNodeCtl_Destroy_orig = nullptr;
static aioNodeCtl_GetState_t aioNodeCtl_GetState_orig = nullptr;
static aioNodeCtl_IsReady_t aioNodeCtl_IsReady_orig = nullptr;
static aioNodeCtl_IsError_t aioNodeCtl_IsError_orig = nullptr;
static aioNodeCtl_UpdateDevicesStatus_t aioNodeCtl_UpdateDevicesStatus_orig = nullptr;
// libaio-iob5.dll
static aioNMgrIob5_Create_t aioNMgrIob5_Create_orig = nullptr;
// libaio-iob.dll
static aioNMgrIob2_Create_t aioNMgrIob2_Create_orig = nullptr;
static aioNMgrIob_BeginManage_t aioNMgrIob_BeginManage_orig = nullptr;
static aioNCtlIob_GetNodeInfo_t aioNCtlIob_GetNodeInfo_orig = nullptr;
// libaio-iob5_y32.dll
static aioIob5Y32d_Create_t aioIob5Y32d_Create_orig = nullptr;
static aioIob5Y32d_GetDeviceStatus_t aioIob5Y32d_GetDeviceStatus_orig = nullptr;
/*
* variables
*/
static AIO_IOB2_BI2X_AC1 *aioIob2Bi2xAc1;
static AIO_NMGR_IOB2 *aioNmgrIob2;
static AIO_NMGR_IOB5 *aioNmgrIob5;
static AIO_IOB5_Y32D *aioIob5Y32d;
static AIO_SCI_COMM *aioSciComm;
static AIO_IOB2_BI2X_WRFIRM *aioIob2Bi2xWrfirm;
/*
* implementations
*/
// libaio-iob2_video.dll
static AIO_IOB2_BI2X_AC1 *__fastcall aioIob2Bi2xAC1_Create(AIO_NMGR_IOB2 *i_pNodeMgr, uint32_t i_DevId,
AIO_IOB2_BI2X_AC1__SETTING *i_Setting) {
if (i_pNodeMgr == aioNmgrIob2) {
aioIob2Bi2xAc1 = new AIO_IOB2_BI2X_AC1;
return aioIob2Bi2xAc1;
} else {
return aioIob2Bi2xAC1_Create_orig(i_pNodeMgr, i_DevId, i_Setting);
}
}
void __fastcall aioIob2Bi2xAC1_GetDeviceStatus(AIO_IOB2_BI2X_AC1 *i_pNodeCtl,
AIO_IOB2_BI2X_AC1__DEVSTATUS *o_DevStatus) {
RI_MGR->devices_flush_output();
if (i_pNodeCtl != aioIob2Bi2xAc1) {
return aioIob2Bi2xAC1_GetDeviceStatus_orig(i_pNodeCtl, o_DevStatus);
}
memset(o_DevStatus, 0x00, sizeof(AIO_IOB2_BI2X_AC1__DEVSTATUS));
auto &buttons = get_buttons();
// struct may be misaligned
o_DevStatus->Input.CN8_10 = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Test]) ? 0 : 0xFF;
o_DevStatus->Input.CN9_8 = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Service]) ? 0 : 0xFF;
o_DevStatus->Input.CN9_9 = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Coin]) ? 0 : 0xFF;
o_DevStatus->Input.CN12_14 = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Headphone]) ? 0xFF : 0;
// coin
o_DevStatus->Input.Coin1Count = eamuse_coin_get_stock();
}
void __fastcall aioIob2Bi2xAC1_SetWatchDogTimer(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint8_t i_Count) {
if (i_pNodeCtl != aioIob2Bi2xAc1) {
return aioIob2Bi2xAC1_SetWatchDogTimer_orig(i_pNodeCtl, i_Count);
}
}
void __fastcall aioIob2Bi2xAC1_AddCounter(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint32_t i_Counter, uint32_t i_Count) {
if (i_pNodeCtl == aioIob2Bi2xAc1 && i_Count == 0) {
eamuse_coin_set_stock((uint16_t)i_Count);
} else {
return aioIob2Bi2xAC1_AddCounter_orig(i_pNodeCtl, i_Counter, i_Count);
}
}
void __fastcall aioIob2Bi2xAC1_SetOutputData(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint32_t i_CnPin, uint8_t i_Data) {
if (i_pNodeCtl != aioIob2Bi2xAc1) {
return aioIob2Bi2xAC1_SetOutputData_orig(i_pNodeCtl, i_CnPin, i_Data);
}
}
void __fastcall aioIob2Bi2xAC1_SetTapeLedDataPart(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint32_t i_TapeLedCh,
uint32_t i_Offset, uint8_t *i_pData,
uint32_t i_cntTapeLed, bool i_bReverse) {
if (i_pNodeCtl != aioIob2Bi2xAc1) {
return aioIob2Bi2xAC1_SetTapeLedDataPart_orig(i_pNodeCtl, i_TapeLedCh, i_Offset, i_pData, i_cntTapeLed, i_bReverse);
}
}
void __fastcall aioIob2Bi2x_SetTapeLedDataLimit(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint32_t i_Channel,
uint8_t i_Scale, uint8_t i_Limit) {
if (i_pNodeCtl != aioIob2Bi2xAc1) {
return aioIob2Bi2x_SetTapeLedDataLimit_orig(i_pNodeCtl, i_Channel, i_Scale, i_Limit);
}
}
static AIO_IOB2_BI2X_WRFIRM *__fastcall aioIob2Bi2x_CreateWriteFirmContext(
uint32_t i_SerialNumber, uint32_t i_bfIob) {
aioIob2Bi2xWrfirm = new AIO_IOB2_BI2X_WRFIRM;
return aioIob2Bi2xWrfirm;
}
static void __fastcall aioIob2Bi2x_DestroyWriteFirmContext(AIO_IOB2_BI2X_WRFIRM *i_pWrFirm) {
if (i_pWrFirm == aioIob2Bi2xWrfirm) {
delete aioIob2Bi2xWrfirm;
aioIob2Bi2xWrfirm = nullptr;
} else {
return aioIob2Bi2x_DestroyWriteFirmContext_orig(i_pWrFirm);
}
}
static int32_t __fastcall aioIob2Bi2x_WriteFirmGetState(AIO_IOB2_BI2X_WRFIRM *i_pWrFirm) {
if (i_pWrFirm == aioIob2Bi2xWrfirm) {
return 8;
} else {
return aioIob2Bi2x_WriteFirmGetState_orig(i_pWrFirm);
}
}
static bool __fastcall aioIob2Bi2x_WriteFirmIsCompleted(int32_t i_State) {
if (aioIob2Bi2xWrfirm != nullptr) {
return true;
} else {
return aioIob2Bi2x_WriteFirmIsCompleted_orig(i_State);
}
}
// libaio-iob.dll
static AIO_NMGR_IOB2 *__fastcall aioNMgrIob2_Create(AIO_SCI_COMM *i_pSci, uint32_t i_bfMode) {
if (i_pSci == aioSciComm) {
aioNmgrIob2 = new AIO_NMGR_IOB2;
return aioNmgrIob2;
} else {
return aioNMgrIob2_Create_orig(i_pSci, i_bfMode);
}
}
static void __fastcall aioNMgrIob_BeginManage(AIO_NMGR_IOB2 *i_pNodeMgr) {
if (i_pNodeMgr == aioNmgrIob2) {
} else {
return aioNMgrIob_BeginManage_orig(i_pNodeMgr);
}
}
static void __fastcall aioNCtlIob_GetNodeInfo(
AIO_IOB5_Y32D *i_pNodeCtl, AIO_NMGR_IOB__NODEINFO *o_NodeInfo) {
if (i_pNodeCtl == aioIob5Y32d) {
memset(o_NodeInfo, 0, sizeof(AIO_NMGR_IOB__NODEINFO));
} else {
return aioNCtlIob_GetNodeInfo_orig(i_pNodeCtl, o_NodeInfo);
}
}
// libaio.dll
static AIO_SCI_COMM_T *__fastcall aioSciComm_Open(AIO_SCI_COMM_T *unk) {
aioSciComm = new AIO_SCI_COMM;
memset(unk, 0, sizeof(AIO_SCI_COMM_T));
unk->aioSciComm = aioSciComm;
return unk;
}
static void __fastcall aioSci_GetCommStatus(AIO_SCI_COMM *i_pNodeMgr, AIO_COMM_STATUS *i_Status) {
if (i_pNodeMgr == aioSciComm) {
memset(i_Status, 0, sizeof(AIO_COMM_STATUS));
} else {
return aioSci_GetCommStatus_orig(i_pNodeMgr, i_Status);
}
}
static void __fastcall aioSci_Destroy(AIO_SCI_COMM *i_pNodeMgr) {
if (i_pNodeMgr == aioSciComm) {
delete aioSciComm;
} else {
return aioSci_Destroy_orig(i_pNodeMgr);
}
}
static void __fastcall aioNodeMgr_Destroy(AIO_NMGR_IOB5 *i_pNodeMgr) {
if (i_pNodeMgr == aioNmgrIob5 ||
i_pNodeMgr == (AIO_NMGR_IOB5 *)aioIob2Bi2xAc1 ||
i_pNodeMgr == (AIO_NMGR_IOB5 *)aioNmgrIob2) {
delete aioNmgrIob5;
aioNmgrIob5 = nullptr;
} else {
return aioNodeMgr_Destroy_orig(i_pNodeMgr);
}
}
static int32_t __fastcall aioNodeMgr_GetState(AIO_NMGR_IOB5 *i_pNodeMgr) {
if (i_pNodeMgr == aioNmgrIob5 ||
i_pNodeMgr == (AIO_NMGR_IOB5 *)aioIob2Bi2xAc1 ||
i_pNodeMgr == (AIO_NMGR_IOB5 *)aioNmgrIob2) {
return 1;
} else {
return aioNodeMgr_GetState_orig(i_pNodeMgr);
}
}
static bool __fastcall aioNodeMgr_IsReady(AIO_NMGR_IOB5 *i_pNodeMgr, int32_t i_State) {
if (i_pNodeMgr == aioNmgrIob5 ||
i_pNodeMgr == (AIO_NMGR_IOB5 *)aioIob2Bi2xAc1 ||
i_pNodeMgr == (AIO_NMGR_IOB5 *)aioNmgrIob2) {
return true;
} else {
return aioNodeMgr_IsReady_orig(i_pNodeMgr, i_State);
}
}
static bool __fastcall aioNodeMgr_IsError(AIO_NMGR_IOB5 *i_pNodeMgr, int32_t i_State) {
if (i_pNodeMgr == aioNmgrIob5 ||
i_pNodeMgr == (AIO_NMGR_IOB5 *)aioIob2Bi2xAc1 ||
i_pNodeMgr == (AIO_NMGR_IOB5 *)aioNmgrIob2) {
return false;
} else {
return aioNodeMgr_IsError_orig(i_pNodeMgr, i_State);
}
}
static void __fastcall aioNodeCtl_Destroy(AIO_IOB5_Y32D *i_pNodeCtl) {
if (i_pNodeCtl == aioIob5Y32d ||
i_pNodeCtl == (AIO_IOB5_Y32D *)aioIob2Bi2xAc1 ||
i_pNodeCtl == (AIO_IOB5_Y32D *)aioNmgrIob2) {
delete aioIob5Y32d;
aioIob5Y32d = nullptr;
} else {
return aioNodeCtl_Destroy_orig(i_pNodeCtl);
}
}
static int32_t __fastcall aioNodeCtl_GetState(AIO_IOB5_Y32D *i_pNodeCtl) {
if (i_pNodeCtl == aioIob5Y32d ||
i_pNodeCtl == (AIO_IOB5_Y32D *)aioIob2Bi2xAc1 ||
i_pNodeCtl == (AIO_IOB5_Y32D *)aioNmgrIob2) {
return 1;
} else {
return aioNodeCtl_GetState_orig(i_pNodeCtl);
}
}
static bool __fastcall aioNodeCtl_IsReady(AIO_IOB5_Y32D *i_pNodeCtl, int32_t i_State) {
if (i_pNodeCtl == aioIob5Y32d ||
i_pNodeCtl == (AIO_IOB5_Y32D *)aioIob2Bi2xAc1 ||
i_pNodeCtl == (AIO_IOB5_Y32D *)aioNmgrIob2) {
return true;
} else {
return aioNodeCtl_IsReady_orig(i_pNodeCtl, i_State);
}
}
static bool __fastcall aioNodeCtl_IsError(AIO_IOB5_Y32D *i_pNodeCtl, int32_t i_State) {
if (i_pNodeCtl == aioIob5Y32d ||
i_pNodeCtl == (AIO_IOB5_Y32D *)aioIob2Bi2xAc1 ||
i_pNodeCtl == (AIO_IOB5_Y32D *)aioNmgrIob2) {
return false;
} else {
return aioNodeCtl_IsError_orig(i_pNodeCtl, i_State);
}
}
static void __fastcall aioNodeCtl_UpdateDevicesStatus() {
}
// libaio-iob5.dll
static AIO_NMGR_IOB5 *__fastcall aioNMgrIob5_Create(AIO_SCI_COMM *i_pSci, uint32_t i_bfMode) {
if (i_pSci == aioSciComm) {
aioNmgrIob5 = new AIO_NMGR_IOB5;
return aioNmgrIob5;
} else {
return aioNMgrIob5_Create_orig(i_pSci, i_bfMode);
}
}
// libaio-iob5_y32.dll
static AIO_IOB5_Y32D *__fastcall aioIob5Y32d_Create(AIO_NMGR_IOB5 *i_pSci, uint32_t i_bfMode) {
if (i_pSci == aioNmgrIob5) {
aioIob5Y32d = new AIO_IOB5_Y32D;
return aioIob5Y32d;
} else {
return aioIob5Y32d_Create_orig(i_pSci, i_bfMode);
}
}
void __fastcall aioIob5Y32d_GetDeviceStatus(AIO_IOB5_Y32D *i_pNodeCtl,
AIO_IOB5_Y32D__DEVSTATUS *o_DevStatus) {
if (i_pNodeCtl != aioIob5Y32d) {
return aioIob5Y32d_GetDeviceStatus_orig(i_pNodeCtl, o_DevStatus);
}
memset(o_DevStatus, 0, sizeof(AIO_IOB5_Y32D__DEVSTATUS));
}
void bi2x_hook_init() {
// avoid double init
static bool initialized = false;
if (initialized) {
return;
} else {
initialized = true;
}
// announce
log_info("bi2x_hook", "init");
// libaio-iob5_y32.dll
const auto libaioIob5Y32Dll = "libaio-iob5_y32.dll";
detour::trampoline_try(libaioIob5Y32Dll, "aioIob5Y32d_Create",
aioIob5Y32d_Create, &aioIob5Y32d_Create_orig);
detour::trampoline_try(libaioIob5Y32Dll, "aioIob5Y32i_Create",
aioIob5Y32d_Create, &aioIob5Y32d_Create_orig);
detour::trampoline_try(libaioIob5Y32Dll, "aioIob5Y32d_GetDeviceStatus",
aioIob5Y32d_GetDeviceStatus, &aioIob5Y32d_GetDeviceStatus_orig);
detour::trampoline_try(libaioIob5Y32Dll, "aioIob5Y32i_GetDeviceStatus",
aioIob5Y32d_GetDeviceStatus, &aioIob5Y32d_GetDeviceStatus_orig);
// libaio-iob5.dll
const auto libaioIob5Dll = "libaio-iob5.dll";
detour::trampoline_try(libaioIob5Dll, "aioNMgrIob5_Create",
aioNMgrIob5_Create, &aioNMgrIob5_Create_orig);
// libaio-iob2_video.dll
const auto libaioIob2VideoDll = "libaio-iob2_video.dll";
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xAC1_Create",
aioIob2Bi2xAC1_Create, &aioIob2Bi2xAC1_Create_orig);
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xAC1_GetDeviceStatus",
aioIob2Bi2xAC1_GetDeviceStatus, &aioIob2Bi2xAC1_GetDeviceStatus_orig);
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xAC1_SetWatchDogTimer",
aioIob2Bi2xAC1_SetWatchDogTimer, &aioIob2Bi2xAC1_SetWatchDogTimer_orig);
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xAC1_AddCounter",
aioIob2Bi2xAC1_AddCounter, &aioIob2Bi2xAC1_AddCounter_orig);
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xAC1_SetOutputData",
aioIob2Bi2xAC1_SetOutputData, &aioIob2Bi2xAC1_SetOutputData_orig);
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xAC1_SetTapeLedDataPart",
aioIob2Bi2xAC1_SetTapeLedDataPart, &aioIob2Bi2xAC1_SetTapeLedDataPart_orig);
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2x_SetTapeLedDataLimit",
aioIob2Bi2x_SetTapeLedDataLimit, &aioIob2Bi2x_SetTapeLedDataLimit_orig);
detour::trampoline_try(libaioIob2VideoDll, "?CreateWriteFirmContext@AIO_IOB2_BI2X@@SAPEAUWRFIRM@1@II@Z",
aioIob2Bi2x_CreateWriteFirmContext, &aioIob2Bi2x_CreateWriteFirmContext_orig);
detour::trampoline_try(libaioIob2VideoDll, "?DestroyWriteFirmContext@AIO_IOB2_BI2X@@SAXPEAUWRFIRM@1@@Z",
aioIob2Bi2x_DestroyWriteFirmContext, &aioIob2Bi2x_DestroyWriteFirmContext_orig);
detour::trampoline_try(libaioIob2VideoDll, "?WriteFirmGetState@AIO_IOB2_BI2X@@SAHPEAUWRFIRM@1@@Z",
aioIob2Bi2x_WriteFirmGetState, &aioIob2Bi2x_WriteFirmGetState_orig);
detour::trampoline_try(libaioIob2VideoDll, "?WriteFirmIsCompleted@AIO_IOB2_BI2X@@SA_NH@Z",
aioIob2Bi2x_WriteFirmIsCompleted, &aioIob2Bi2x_WriteFirmIsCompleted_orig);
// libaio-iob.dll
const auto libaioIobDll = "libaio-iob.dll";
detour::trampoline_try(libaioIobDll, "aioNMgrIob2_Create",
aioNMgrIob2_Create, &aioNMgrIob2_Create_orig);
detour::trampoline_try(libaioIobDll, "aioNMgrIob_BeginManage",
aioNMgrIob_BeginManage, &aioNMgrIob_BeginManage_orig);
detour::trampoline_try(libaioIobDll, "aioNCtlIob_GetNodeInfo",
aioNCtlIob_GetNodeInfo, &aioNCtlIob_GetNodeInfo_orig);
// libaio.dll
const auto libaioDll = "libaio.dll";
detour::trampoline_try(libaioDll, "?Open@AIO_SCI_COMM@@SA?AU?$AC_ERESULT_T@PEAVAIO_SCI_COMM@@@@PEBD0AEBUSETTING@1@@Z",
aioSciComm_Open, &aioSciComm_Open_orig);
detour::trampoline_try(libaioDll, "aioSci_GetCommStatus",
aioSci_GetCommStatus, &aioSci_GetCommStatus_orig);
detour::trampoline_try(libaioDll, "aioSci_Destroy",
aioSci_Destroy, &aioSci_Destroy_orig);
detour::trampoline_try(libaioDll, "aioNodeMgr_Destroy",
aioNodeMgr_Destroy, &aioNodeMgr_Destroy_orig);
detour::trampoline_try(libaioDll, "aioNodeMgr_GetState",
aioNodeMgr_GetState, &aioNodeMgr_GetState_orig);
detour::trampoline_try(libaioDll, "aioNodeMgr_IsReady",
aioNodeMgr_IsReady, &aioNodeMgr_IsReady_orig);
detour::trampoline_try(libaioDll, "aioNodeMgr_IsError",
aioNodeMgr_IsError, &aioNodeMgr_IsError_orig);
detour::trampoline_try(libaioDll, "aioNodeCtl_Destroy",
aioNodeCtl_Destroy, &aioNodeCtl_Destroy_orig);
detour::trampoline_try(libaioDll, "aioNodeCtl_GetState",
aioNodeCtl_GetState, &aioNodeCtl_GetState_orig);
detour::trampoline_try(libaioDll, "aioNodeCtl_IsReady",
aioNodeCtl_IsReady, &aioNodeCtl_IsReady_orig);
detour::trampoline_try(libaioDll, "aioNodeCtl_IsError",
aioNodeCtl_IsError, &aioNodeCtl_IsError_orig);
detour::trampoline_try(libaioDll, "aioNodeCtl_UpdateDevicesStatus",
aioNodeCtl_UpdateDevicesStatus, &aioNodeCtl_UpdateDevicesStatus_orig);
}
}
+5
View File
@@ -0,0 +1,5 @@
#pragma once
namespace games::gitadora {
void bi2x_hook_init();
}
+10
View File
@@ -0,0 +1,10 @@
#pragma once
#include "acioemu/acioemu.h"
namespace games::gitadora {
class GitadoraDevice : public acioemu::ACIODeviceEmu {
public:
bool is_ready = false;
};
}
+61 -2
View File
@@ -1,14 +1,16 @@
#include "gitadora.h" #include "gitadora.h"
#include "handle.h"
#include "bi2x_hook.h"
#include <unordered_map> #include <unordered_map>
#include "cfg/configurator.h" #include "cfg/configurator.h"
#include "hooks/audio/mme.h"
#include "hooks/graphics/graphics.h" #include "hooks/graphics/graphics.h"
#include "util/cpuutils.h" #include "util/cpuutils.h"
#include "util/detour.h" #include "util/detour.h"
#include "util/libutils.h" #include "util/libutils.h"
#include "util/logging.h" #include "util/logging.h"
#include "util/sigscan.h" #include "util/sigscan.h"
#include "hooks/setupapihook.h"
namespace games::gitadora { namespace games::gitadora {
@@ -16,6 +18,32 @@ namespace games::gitadora {
bool TWOCHANNEL = false; bool TWOCHANNEL = false;
std::optional<unsigned int> CAB_TYPE = std::nullopt; std::optional<unsigned int> CAB_TYPE = std::nullopt;
/*
* Prevent GitaDora from creating folders on F drive
*/
#ifdef SPICE64
static DWORD WINAPI GetLogicalDrives_hook() {
return GetLogicalDrives() | 0x20;
}
static UINT WINAPI GetDriveTypeA_hook(LPCSTR lpRootPathName) {
if (!strcmp(lpRootPathName, "F:\\")) {
return DRIVE_FIXED;
}
return GetDriveTypeA(lpRootPathName);
}
static BOOL WINAPI CreateDirectoryA_hook(LPCSTR lpPathName, LPSECURITY_ATTRIBUTES lpSecurityAttributes) {
if (!strncmp(lpPathName, "F:/", 3)) {
return TRUE;
}
return CreateDirectoryA(lpPathName, lpSecurityAttributes);
}
#endif
/* /*
* GitaDora checks if the IP address has changed, and if it has it throws 5-1506-0000 like jubeat. * GitaDora checks if the IP address has changed, and if it has it throws 5-1506-0000 like jubeat.
* We don't want this so we patch it out. * We don't want this so we patch it out.
@@ -214,6 +242,37 @@ namespace games::gitadora {
system_module, "sys_debug_dip_set_param")); system_module, "sys_debug_dip_set_param"));
#ifdef SPICE64 #ifdef SPICE64
// gitadora arena model
auto aio = libutils::try_library("libaio.dll");
if (aio != nullptr) {
SETUPAPI_SETTINGS settings{};
settings.class_guid[0] = 0x86E0D1E0;
settings.class_guid[1] = 0x11D08089;
settings.class_guid[2] = 0x0008E49C;
settings.class_guid[3] = 0x731F303E;
const char property[] = "1CCF(8050)_000";
const char property_hardwareid[] = "USB\\VID_1CCF&PID_8050&MI_00\\000";
memcpy(settings.property_devicedesc, property, sizeof(property));
memcpy(settings.property_hardwareid, property_hardwareid, sizeof(property_hardwareid));
setupapihook_init(avs::game::DLL_INSTANCE);
setupapihook_add(settings);
// Gitadora IO(J32D/J33I) board emulation
devicehook_init(avs::game::DLL_INSTANCE);
devicehook_add(new GitaDoraSerialHandle());
// test/service/coin buttons
bi2x_hook_init();
// f: drive hook
detour::iat_try("GetLogicalDrives", GetLogicalDrives_hook, avs::game::DLL_INSTANCE);
detour::iat_try("GetDriveTypeA", GetDriveTypeA_hook, avs::game::DLL_INSTANCE);
detour::iat_try("CreateDirectoryA", CreateDirectoryA_hook, avs::game::DLL_INSTANCE);
// volume change prevention
hooks::audio::mme::init(avs::game::DLL_INSTANCE);
return;
}
HMODULE gdme_module = libutils::try_module("libgdme.dll"); HMODULE gdme_module = libutils::try_module("libgdme.dll");
+9 -2
View File
@@ -21,13 +21,20 @@ namespace games::gitadora {
static inline bool is_drum() { static inline bool is_drum() {
return ( return (
avs::game::is_model({ "J32", "K32", "L32" }) || avs::game::is_model({ "J32", "K32", "L32" }) ||
(avs::game::is_model("M32") && avs::game::SPEC[0] == 'B') (avs::game::is_model("M32") && (avs::game::SPEC[0] == 'B' || avs::game::SPEC[0] == 'D'))
); );
} }
static inline bool is_guitar() { static inline bool is_guitar() {
return ( return (
avs::game::is_model({ "J33", "K33", "L33" }) || avs::game::is_model({ "J33", "K33", "L33" }) ||
(avs::game::is_model("M32") && avs::game::SPEC[0] == 'A') (avs::game::is_model("M32") && (avs::game::SPEC[0] == 'A' || avs::game::SPEC[0] == 'C'))
);
}
static inline bool is_arena_model() {
return (
avs::game::is_model("M32") &&
(avs::game::SPEC[0] == 'C' || avs::game::SPEC[0] == 'D')
); );
} }
+110
View File
@@ -0,0 +1,110 @@
#include "handle.h"
#include "misc/eamuse.h"
#include "rawinput/rawinput.h"
#include "util/utils.h"
#include "util/time.h"
#include "io.h"
#include "gitadora.h"
#include "j32d.h"
#include "j33i.h"
#include "device.h"
using namespace acioemu;
static games::gitadora::GitadoraDevice *device;
bool games::gitadora::GitaDoraSerialHandle::open(LPCWSTR lpFileName) {
if (wcscmp(lpFileName, L"COM1") != 0) {
return false;
}
bool is_drum = games::gitadora::is_drum();
if (is_drum) {
device = new J32DSerialDevice();
} else {
device = new J33ISerialDevice();
}
acio_emu.add_device(device);
log_info("gitadora", "Opened COM1 ({} DEVICE)", is_drum ? "J32D" : "J33I");
return true;
}
int games::gitadora::GitaDoraSerialHandle::read(LPVOID lpBuffer, DWORD nNumberOfBytesToRead) {
auto buffer = reinterpret_cast<uint8_t *>(lpBuffer);
// the game does not send cmd 0x12F to the IO
// but it is expected to receive 0x12F for user input
// so i manually created a payload so that parse_msg can be used
// to write to the response_buffer
if (device->is_ready) {
uint8_t chk = 0;
MessageData* msg = ACIODeviceEmu::create_msg(0x1, 0x12F, 0, 0);
msg->addr = 0x1;
auto write_buffer = reinterpret_cast<uint8_t *>(msg);
while (acio_emu.read() != std::nullopt); // there may be old stuffs in the buffer
acio_emu.write(0xAA);
acio_emu.write(0xAA);
for(DWORD i = 0; i < 5; i++) {
chk += write_buffer[i];
acio_emu.write(write_buffer[i]);
}
// direct copy and paste from write_msg
if (chk == ACIO_SOF || chk == ACIO_ESCAPE) {
acio_emu.write(ACIO_ESCAPE);
acio_emu.write(~chk);
} else {
acio_emu.write(chk);
}
}
// read from emu
DWORD bytes_read = 0;
while (bytes_read < nNumberOfBytesToRead) {
auto cur_byte = acio_emu.read();
if (cur_byte.has_value()) {
buffer[bytes_read++] = cur_byte.value();
} else {
break;
}
}
#ifdef ACIOEMU_LOG
log_info("gitadora", "Read IO Data: {}", bin2hex(buffer, bytes_read));
#endif
// return amount of bytes read
return (int)bytes_read;
}
int games::gitadora::GitaDoraSerialHandle::write(LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite)
{
auto buffer = reinterpret_cast<const uint8_t *>(lpBuffer);
// write to emu
for (DWORD i = 0; i < nNumberOfBytesToWrite; i++) {
acio_emu.write(buffer[i]);
}
#ifdef ACIOEMU_LOG
log_info("gitadora", "Write IO Data: {}", bin2hex(buffer, nNumberOfBytesToWrite));
#endif
// return all data written
return (int)nNumberOfBytesToWrite;
}
int games::gitadora::GitaDoraSerialHandle::device_io(DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize,
LPVOID lpOutBuffer, DWORD nOutBufferSize) {
return -1;
}
bool games::gitadora::GitaDoraSerialHandle::close() {
log_info("gitadora", "Closed COM1");
return true;
}
+24
View File
@@ -0,0 +1,24 @@
#pragma once
#include "acioemu/acioemu.h"
#include "hooks/devicehook.h"
namespace games::gitadora {
class GitaDoraSerialHandle : public CustomHandle {
private:
acioemu::ACIOEmu acio_emu;
public:
bool open(LPCWSTR lpFileName) override;
int read(LPVOID lpBuffer, DWORD nNumberOfBytesToRead) override;
int write(LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite) override;
int device_io(DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer,
DWORD nOutBufferSize) override;
bool close() override;
};
}
+1
View File
@@ -10,6 +10,7 @@ std::vector<Button> &games::gitadora::get_buttons() {
"Service", "Service",
"Test", "Test",
"Coin", "Coin",
"Headphone",
"Guitar P1 Start", "Guitar P1 Start",
"Guitar P1 Up", "Guitar P1 Up",
"Guitar P1 Down", "Guitar P1 Down",
+1
View File
@@ -11,6 +11,7 @@ namespace games::gitadora {
Service, Service,
Test, Test,
Coin, Coin,
Headphone,
GuitarP1Start, GuitarP1Start,
GuitarP1Up, GuitarP1Up,
GuitarP1Down, GuitarP1Down,
+100
View File
@@ -0,0 +1,100 @@
#include "misc/eamuse.h"
#include "util/utils.h"
#include "io.h"
#include "j32d.h"
using namespace acioemu;
games::gitadora::J32DSerialDevice::J32DSerialDevice() {
this->node_count = 1;
log_info("gitadora", "J32D device created.");
}
bool games::gitadora::J32DSerialDevice::parse_msg(
MessageData *msg_in,
circular_buffer<uint8_t> *response_buffer) {
#ifdef ACIOEMU_LOG
log_info("gitadora", "GTDR ADDR: {}, CMD: 0x{:04x}", msg_in->addr, msg_in->cmd.code);
#endif
static uint8_t pid = 0;
// check command
switch (msg_in->cmd.code) {
case ACIO_CMD_GET_VERSION: {
// send version data
auto msg = this->create_msg(msg_in, MSG_VERSION_SIZE);
this->set_version(msg, 0x20a, 0, 0, 1, 0, "J32D");
write_msg(msg, response_buffer);
delete msg;
break;
}
case ACIO_CMD_STARTUP:
case ACIO_CMD_CLEAR:
case 0x0120: {
if (msg_in->cmd.code == 0x0120) {
pid = msg_in->cmd.pid;
this->is_ready = true;
}
// send status 0
auto msg = this->create_msg_status(msg_in, 0);
write_msg(msg, response_buffer);
delete msg;
break;
}
// IO
case 0x012F: {
auto msg = this->create_msg(msg_in, 0x11);
msg->cmd.pid = pid;
// don't know if it's needed/correct,
// without it the game seems to work just fine
pid += 1;
J32D_IO_STATUS payload;
memset(&payload, 0, sizeof(J32D_IO_STATUS));
auto &buttons = get_buttons();
auto high_tom = (uint16_t) (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::DrumHiTom]) * 1023);
auto low_tom = (uint16_t) (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::DrumLowTom]) * 1023);
auto snare = (uint16_t) (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::DrumSnare]) * 1023);
auto floor_tom = (uint16_t) (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::DrumFloorTom]) * 1023);
auto left_cymbal = (uint16_t) (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::DrumLeftCymbal]) * 1023);
auto right_cymbal = (uint16_t) (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::DrumRightCymbal]) * 1023);
auto hihat = (uint16_t) ((GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::DrumHiHat]) ||
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::DrumHiHatClosed]) ||
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::DrumHiHatHalfOpen])) * 1023);
#define ENCODE_ANALOG(__val) (((__val & 0x3f) << 10) | ((__val & 0x3c0) >> 6))
payload.high_tom = ENCODE_ANALOG(high_tom);
payload.low_tom = ENCODE_ANALOG(low_tom);
payload.snare = ENCODE_ANALOG(snare);
payload.floor_tom = ENCODE_ANALOG(floor_tom);
payload.left_cymbal = ENCODE_ANALOG(left_cymbal);
payload.right_cymbal = ENCODE_ANALOG(right_cymbal);
payload.hihat = ENCODE_ANALOG(hihat);
#undef ENCODE_ANALOG
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::DrumBassPedal])) {
payload.pedals |= 1;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::DrumLeftPedal])) {
payload.pedals |= 2;
}
memcpy(msg->cmd.raw, &payload, sizeof(J32D_IO_STATUS));
write_msg(msg, response_buffer);
delete msg;
break;
}
default:
#ifdef ACIOEMU_LOG
log_info("gitadora", "Unhandled cmd {} received.", msg_in->cmd.code);
#endif
return false;
}
// mark as handled
return true;
}
+25
View File
@@ -0,0 +1,25 @@
#pragma once
#include "acioemu/device.h"
#include "device.h"
namespace games::gitadora {
class J32DSerialDevice : public GitadoraDevice {
private:
uint8_t coin_counter = 0;
public:
J32DSerialDevice();
bool parse_msg(acioemu::MessageData *msg_in, circular_buffer<uint8_t> *response_buffer) override;
struct J32D_IO_STATUS {
uint16_t high_tom;
uint16_t low_tom;
uint16_t snare;
uint16_t floor_tom;
uint16_t left_cymbal;
uint16_t right_cymbal;
uint16_t hihat;
uint16_t unk7;
uint8_t pedals;
};
};
}
+160
View File
@@ -0,0 +1,160 @@
#include "misc/eamuse.h"
#include "util/utils.h"
#include "util/time.h"
#include "io.h"
#include "j33i.h"
#define GUITAR_BTN_R 0x7
#define GUITAR_BTN_G 0x6
#define GUITAR_BTN_B 0x5
#define GUITAR_BTN_Y 0x4
#define GUITAR_BTN_P 0x3
#define GUITAR_PICK_DOWN 0xC
#define GUITAR_PICK_UP 0xD
using namespace acioemu;
// why this number?? no clue, but this is what the test menu shows as the max
// value in the green box with the red dot; the value display itself can go
// higher but the red dot will reach the edge at +-189
#define ARENA_MODEL_WAIL_MAX 189
static bool GFDM_GF_PICK_STATE_UP;
static bool GFDM_GF_PICK_STATE_DOWN;
games::gitadora::J33ISerialDevice::J33ISerialDevice() {
this->node_count = 1;
log_info("gitadora", "J33I device created.");
}
bool games::gitadora::J33ISerialDevice::parse_msg(
MessageData *msg_in,
circular_buffer<uint8_t> *response_buffer) {
#ifdef ACIOEMU_LOG
log_info("gitadora", "GTDR ADDR: {}, CMD: 0x{:04x}", msg_in->addr, msg_in->cmd.code);
#endif
static uint8_t pid = 0;
// check command
switch (msg_in->cmd.code)
{
case ACIO_CMD_GET_VERSION: {
// send version data
auto msg = this->create_msg(msg_in, MSG_VERSION_SIZE);
this->set_version(msg, 0x10a, 0, 1, 0, 2, "J33I");
write_msg(msg, response_buffer);
delete msg;
break;
}
case ACIO_CMD_STARTUP:
case ACIO_CMD_CLEAR:
case 0x0120: {
if (msg_in->cmd.code == 0x0120) {
pid = msg_in->cmd.pid;
this->is_ready = true;
}
// send status 0
auto msg = this->create_msg_status(msg_in, 0);
write_msg(msg, response_buffer);
delete msg;
break;
}
// IO
case 0x012F: {
auto msg = this->create_msg(msg_in, 0x8);
msg->cmd.pid = pid;
pid += 1;
J33I_IO_STATUS payload;
memset(&payload, 0, sizeof(J33I_IO_STATUS));
auto &buttons = get_buttons();
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1R])) {
payload.buttons |= 1 << GUITAR_BTN_R;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1G])) {
payload.buttons |= 1 << GUITAR_BTN_G;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1B])) {
payload.buttons |= 1 << GUITAR_BTN_B;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1Y])) {
payload.buttons |= 1 << GUITAR_BTN_Y;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1P])) {
payload.buttons |= 1 << GUITAR_BTN_P;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1PickUp])) {
if (!GFDM_GF_PICK_STATE_UP) {
GFDM_GF_PICK_STATE_UP = true;
payload.buttons |= 1 << GUITAR_PICK_UP;
}
} else {
GFDM_GF_PICK_STATE_UP = false;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1PickDown])) {
if (!GFDM_GF_PICK_STATE_DOWN) {
GFDM_GF_PICK_STATE_DOWN = true;
payload.buttons |= 1 << GUITAR_PICK_DOWN;
}
} else {
GFDM_GF_PICK_STATE_DOWN = false;
}
auto &analogs = get_analogs();
// get x,y,z analog values [-0.5f, 0.5f], centered at 0.f
float x = 0.f;
auto x_analog = analogs[Analogs::GuitarP1WailX];
if (x_analog.isSet()) {
x = GameAPI::Analogs::getState(RI_MGR, x_analog) - 0.5f;
}
float y = 0.f;
auto y_analog = analogs[Analogs::GuitarP1WailY];
if (y_analog.isSet()) {
y = GameAPI::Analogs::getState(RI_MGR, y_analog) - 0.5f;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1WailUp])) {
y = -0.5f;
} else if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1WailDown])) {
y = 0.5f;
}
float z = 0.f;
auto z_analog = analogs[Analogs::GuitarP1WailZ];
if (z_analog.isSet()) {
z = GameAPI::Analogs::getState(RI_MGR, z_analog) - 0.5f;
}
// convert x,y,z to unsigned integer values that the game expects
const uint16_t x_int = x * ARENA_MODEL_WAIL_MAX * 2;
const uint16_t y_int = y * ARENA_MODEL_WAIL_MAX * 2;
const uint16_t z_int = z * ARENA_MODEL_WAIL_MAX * 2;
payload.gyroscope_x = static_cast<uint16_t>(((x_int >> 3) & (0xFF)) | ((x_int & 0x07) << 12));
payload.gyroscope_y = static_cast<uint16_t>(((y_int >> 3) & (0xFF)) | ((y_int & 0x07) << 12));
payload.gyroscope_z = static_cast<uint16_t>(((z_int >> 3) & (0xFF)) | ((z_int & 0x07) << 12));
memcpy(msg->cmd.raw, &payload, sizeof(J33I_IO_STATUS));
write_msg(msg, response_buffer);
delete msg;
break;
}
default:
#ifdef ACIOEMU_LOG
log_info("gitadora", "Unhandled cmd {} received.", msg_in->cmd.code);
#endif
return false;
}
// mark as handled
return true;
}
+24
View File
@@ -0,0 +1,24 @@
#pragma once
#include "acioemu/device.h"
#include "device.h"
namespace games::gitadora {
class J33ISerialDevice : public GitadoraDevice {
private:
uint8_t coin_counter = 0;
public:
J33ISerialDevice();
bool parse_msg(acioemu::MessageData *msg_in, circular_buffer<uint8_t> *response_buffer) override;
// there may be more, but the game doesn't seem to care
// TODO: add support for gyroscope, knob and wail
struct J33I_IO_STATUS {
uint16_t gyroscope_x;
uint16_t gyroscope_y;
uint16_t gyroscope_z;
uint16_t buttons;
};
};
}
+3 -3
View File
@@ -195,11 +195,11 @@ bool games::iidx::IIDXFMSerialHandle::open(LPCWSTR lpFileName) {
return false; return false;
} }
log_info("iidx", "Opened COM2 (FM DEVICE)"); log_info("iidx", "Opened COM2 (FM DEVICE / bi2a)");
games::iidx::update_io_emulation_state(games::iidx::iidx_aio_emulation_state::bi2a_com2);
// ACIO device // ACIO device
acio_emu.add_device(new FMSerialDevice()); acio_emu.add_device(new FMSerialDevice());
return true; return true;
} }
@@ -240,7 +240,7 @@ int games::iidx::IIDXFMSerialHandle::device_io(DWORD dwIoControlCode, LPVOID lpI
} }
bool games::iidx::IIDXFMSerialHandle::close() { bool games::iidx::IIDXFMSerialHandle::close() {
log_info("iidx", "Closed COM2 (FM DEVICE)"); log_info("iidx", "Closed COM2 (FM DEVICE / bi2a)");
return true; return true;
} }
+5
View File
@@ -120,6 +120,11 @@ namespace games::iidx {
static AIO_IOB2_BI2X_TDJ* __fastcall aioIob2Bi2xTDJ_Create(AIO_NMGR_IOB2 *nmgr, int a2) { static AIO_IOB2_BI2X_TDJ* __fastcall aioIob2Bi2xTDJ_Create(AIO_NMGR_IOB2 *nmgr, int a2) {
if (!BI2X_PASSTHROUGH) { if (!BI2X_PASSTHROUGH) {
log_info("bi2x_hook", "aioIob2Bi2xTDJ_Create called (TDJ I/O emulation active)");
games::iidx::update_io_emulation_state(
games::iidx::iidx_aio_emulation_state::bi2x_hook);
custom_node = new AIO_IOB2_BI2X_TDJ; custom_node = new AIO_IOB2_BI2X_TDJ;
memset(&custom_node->data, 0, sizeof(custom_node->data)); memset(&custom_node->data, 0, sizeof(custom_node->data));
+56 -4
View File
@@ -79,6 +79,10 @@ namespace games::iidx {
char IIDXIO_LED_TICKER[10] = {' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '\x00'}; char IIDXIO_LED_TICKER[10] = {' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '\x00'};
bool IIDXIO_LED_TICKER_READONLY = false; bool IIDXIO_LED_TICKER_READONLY = false;
std::mutex IIDX_LED_TICKER_LOCK; std::mutex IIDX_LED_TICKER_LOCK;
bool IIDX_TDJ_MONITOR_WARNING = false;
// io
static bool HAS_LIBAIO; // this is how we detect iidx27+
tapeledutils::tape_led TAPELED_MAPPING[IIDX_TAPELED_TOTAL] = { tapeledutils::tape_led TAPELED_MAPPING[IIDX_TAPELED_TOTAL] = {
{ 19, Lights::StageLeftAvgR, Lights::StageLeftAvgG, Lights::StageLeftAvgB, "Stage Left" }, { 19, Lights::StageLeftAvgR, Lights::StageLeftAvgG, Lights::StageLeftAvgB, "Stage Left" },
@@ -311,13 +315,14 @@ namespace games::iidx {
memcpy(settings2.interface_detail, interface_detail2, sizeof(interface_detail2)); memcpy(settings2.interface_detail, interface_detail2, sizeof(interface_detail2));
setupapihook_add(settings2); setupapihook_add(settings2);
// IIDX 25-27 BIO2 BI2A input device // IIDX 25+ BIO2 BI2A input device
devicehook_add(new IIDXFMSerialHandle()); devicehook_add(new IIDXFMSerialHandle());
} }
// check for new I/O DLL // check for new I/O DLL
auto aio = libutils::try_library("libaio.dll"); auto aio = libutils::try_library("libaio.dll");
if (aio != nullptr) { if (aio != nullptr) {
HAS_LIBAIO = true;
// check TDJ mode // check TDJ mode
TDJ_MODE |= fileutils::text_read("C:\\000rom.txt") == "TDJ-JA"; TDJ_MODE |= fileutils::text_read("C:\\000rom.txt") == "TDJ-JA";
@@ -471,9 +476,8 @@ namespace games::iidx {
} }
// check -monitor + TDJ mode // check -monitor + TDJ mode
if (!GRAPHICS_WINDOWED && if (!GRAPHICS_WINDOWED && D3D9_ADAPTER.has_value() && TDJ_MODE) {
options->at(launcher::Options::DisplayAdapter).is_active() && IIDX_TDJ_MONITOR_WARNING = true;
TDJ_MODE) {
log_warning( log_warning(
"iidx", "iidx",
"\n\n!!! using -monitor option with TDJ is NOT recommended due to known !!!\n" "\n\n!!! using -monitor option with TDJ is NOT recommended due to known !!!\n"
@@ -895,4 +899,52 @@ namespace games::iidx {
#endif #endif
} }
void update_io_emulation_state(iidx_aio_emulation_state state) {
// <iidx27, don't care, since tdj wasn't a thing before
// this is also how we detect that iidx module is attached
if (!HAS_LIBAIO) {
return;
}
// in iidx27-30, most DLL types can handle both, caching things in nvram and possibly end
// up with wrong i/o (temporarily until nvram is initialized again)
// to avoid false positives, only do this for iidx31+, since we know for sure that iidx31
// has a clean 010/012 split
if (avs::game::is_ext(0, 2023101000)) {
return;
}
// log_warning below could be turned into log_fatal in the future once we gain confidence
// that this isn't triggering when it's not supposed to
if (state == iidx_aio_emulation_state::bi2a_com2 && TDJ_MODE) {
deferredlogs::defer_error_messages({
"IIDX I/O mode conflict",
" game is currently configured to use LDJ I/O;",
" however, you turned on IIDX TDJ Mode (-iidxtdj)",
" two options to fix this:",
" For TDJ mode (120Hz): import patches and enable Force TDJ Mode patch",
" For LDJ mode (60Hz): turn off IIDX TDJ Mode option",
});
log_warning(
"iidx",
"game is using LDJ I/O but spice TDJ mode is turned on! "
"enable Force TDJ I/O patch, OR turn off -iidxtdj");
} else if (state == iidx_aio_emulation_state::bi2x_hook && !TDJ_MODE) {
deferredlogs::defer_error_messages({
"IIDX I/O mode conflict",
" game is currently configured to use TDJ I/O;",
" however, you turned off IIDX TDJ Mode (-iidxtdj)",
" two options to fix this:",
" For TDJ mode (120Hz): turn on IIDX TDJ Mode option",
" For LDJ mode (60Hz): import patches and enable Force LDJ Mode patch",
});
log_warning(
"iidx",
"game is using TDJ I/O but spice TDJ mode is turned off! "
"turn on -iidxtdj, OR enable Force LDJ I/O patch");
} else {
log_misc("iidx", "i/o mode validation passed");
}
}
} }
+7
View File
@@ -33,6 +33,7 @@ namespace games::iidx {
extern char IIDXIO_LED_TICKER[10]; extern char IIDXIO_LED_TICKER[10];
extern bool IIDXIO_LED_TICKER_READONLY; extern bool IIDXIO_LED_TICKER_READONLY;
extern std::mutex IIDX_LED_TICKER_LOCK; extern std::mutex IIDX_LED_TICKER_LOCK;
extern bool IIDX_TDJ_MONITOR_WARNING;
constexpr int IIDX_TAPELED_TOTAL = 17; constexpr int IIDX_TAPELED_TOTAL = 17;
// data mapping // data mapping
@@ -62,4 +63,10 @@ namespace games::iidx {
const char* get_16seg(); const char* get_16seg();
bool is_tdj_fhd(); bool is_tdj_fhd();
void apply_audio_hacks(); void apply_audio_hacks();
enum class iidx_aio_emulation_state {
bi2a_com2,
bi2x_hook
};
void update_io_emulation_state(iidx_aio_emulation_state state);
} }
+53 -17
View File
@@ -48,21 +48,31 @@ namespace games::sdvx {
SdvxOverlayPosition OVERLAY_POS = SDVX_OVERLAY_BOTTOM; SdvxOverlayPosition OVERLAY_POS = SDVX_OVERLAY_BOTTOM;
bool ENABLE_COM_PORT_SCAN_HOOK = false; bool ENABLE_COM_PORT_SCAN_HOOK = false;
std::optional<std::string> SOUND_OUTPUT_DEVICE = std::nullopt; bool USE_ASIO = false;
std::optional<std::string> ASIO_DRIVER = std::nullopt; std::optional<std::string> ASIO_DRIVER = std::nullopt;
// states // states
bool SHOW_VM_MONITOR_WARNING = false;
static HKEY real_asio_reg_handle = nullptr; static HKEY real_asio_reg_handle = nullptr;
static HKEY real_asio_device_reg_handle = nullptr; static HKEY real_asio_device_reg_handle = nullptr;
static LONG WINAPI RegOpenKeyA_hook(HKEY hKey, LPCSTR lpSubKey, PHKEY phkResult) { static LONG WINAPI RegOpenKeyA_hook(HKEY hKey, LPCSTR lpSubKey, PHKEY phkResult) {
if (lpSubKey != nullptr && phkResult != nullptr) { if (lpSubKey != nullptr &&
if (hKey == HKEY_LOCAL_MACHINE && phkResult != nullptr &&
ASIO_DRIVER.has_value() && hKey == HKEY_LOCAL_MACHINE &&
_stricmp(lpSubKey, "software\\asio") == 0) _stricmp(lpSubKey, "software\\asio") == 0) {
{
*phkResult = PARENT_ASIO_REG_HANDLE;
// prevent the game from randomly picking an ASIO device
if (!USE_ASIO) {
log_info(
"sdvx::asio",
"game tried to access HKLM\\SOFTWARE\\ASIO but ASIO is disabled; "
"access prevented, ASIO init will fail");
return ERROR_FILE_NOT_FOUND;
}
// convince the game to use our hook (so we can swap in the preferred device)
if (ASIO_DRIVER.has_value()) {
*phkResult = PARENT_ASIO_REG_HANDLE;
return RegOpenKeyA_orig(hKey, lpSubKey, &real_asio_reg_handle); return RegOpenKeyA_orig(hKey, lpSubKey, &real_asio_reg_handle);
} }
} }
@@ -109,7 +119,7 @@ namespace games::sdvx {
lpSubKey != nullptr && phkResult != nullptr && lpSubKey != nullptr && phkResult != nullptr &&
_stricmp(lpSubKey, "HARDWARE\\DEVICEMAP\\SERIALCOMM") == 0) { _stricmp(lpSubKey, "HARDWARE\\DEVICEMAP\\SERIALCOMM") == 0) {
log_info("sdvx::io", "hook access to HKLM\\HARDWARE\\DEVICEMAP\\SERIALCOMM to force COM1 ICCA"); log_info("sdvx::io", "hook access to HKLM\\HARDWARE\\DEVICEMAP\\SERIALCOMM to force COM1 ICCA");
return 2; // ERROR_FILE_NOT_FOUND return ERROR_FILE_NOT_FOUND;
} }
return RegOpenKeyExA_orig(hKey, lpSubKey, ulOptions, samDesired, phkResult); return RegOpenKeyExA_orig(hKey, lpSubKey, ulOptions, samDesired, phkResult);
@@ -229,6 +239,32 @@ namespace games::sdvx {
return false; return false;
} }
void SDVXGame::detect_sound_output_device() {
USE_ASIO = false;
// if the user specified a value for -sdvxasio, use asio
if (ASIO_DRIVER.has_value()) {
log_misc(
"sdvx",
"-sdvxasio is set to \"{}\"", ASIO_DRIVER.value());
USE_ASIO = true;
} else {
// see if XONAR AE is present; if so, use that
HKEY subkey;
LSTATUS result;
result = RegOpenKey(HKEY_LOCAL_MACHINE, "SOFTWARE\\ASIO\\XONAR SOUND CARD(64)", &subkey);
if (result == ERROR_SUCCESS) {
RegCloseKey(subkey);
USE_ASIO = true;
log_misc(
"sdvx",
"found HKLM\\SOFTWARE\\ASIO\\XONAR SOUND CARD(64)");
}
}
if (!USE_ASIO) {
log_misc("sdvx", "asio not configured");
}
}
typedef void **(__fastcall *volume_set_t)(uint64_t, uint64_t, uint64_t); typedef void **(__fastcall *volume_set_t)(uint64_t, uint64_t, uint64_t);
static volume_set_t volume_set_orig = nullptr; static volume_set_t volume_set_orig = nullptr;
static void **__fastcall volume_set_hook(uint64_t vol_sound, uint64_t vol_woofer, uint64_t vol_headphone) { static void **__fastcall volume_set_hook(uint64_t vol_sound, uint64_t vol_woofer, uint64_t vol_headphone) {
@@ -295,12 +331,10 @@ namespace games::sdvx {
} }
#ifdef SPICE64 // SDVX5+ specific code #ifdef SPICE64 // SDVX5+ specific code
bool isValkyrieCabinetMode = avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H'; this->VALKYRIE_MODEL = avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H';
auto options = games::get_options(eamuse_get_game());
// check -monitor + UFC mode // check -monitor + UFC mode
if (!GRAPHICS_WINDOWED && if (!GRAPHICS_WINDOWED && D3D9_ADAPTER.has_value() && this->VALKYRIE_MODEL) {
options->at(launcher::Options::DisplayAdapter).is_active() && SHOW_VM_MONITOR_WARNING = true;
isValkyrieCabinetMode) {
log_warning( log_warning(
"sdvx", "sdvx",
"\n\n!!! using -monitor option with VM mode is NOT recommended due to !!!\n" "\n\n!!! using -monitor option with VM mode is NOT recommended due to !!!\n"
@@ -324,16 +358,19 @@ namespace games::sdvx {
} }
#endif #endif
#ifdef SPICE64
this->detect_sound_output_device();
#endif
} }
void SDVXGame::attach() { void SDVXGame::attach() {
Game::attach(); Game::attach();
#ifdef SPICE64 // SDVX5+ specific code #ifdef SPICE64 // SDVX5+ specific code
bool isValkyrieCabinetMode = avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H';
// LCD handle // LCD handle
if (!isValkyrieCabinetMode) { if (!this->VALKYRIE_MODEL) {
devicehook_init(); devicehook_init();
devicehook_add(new games::shared::LCDHandle()); devicehook_add(new games::shared::LCDHandle());
} }
@@ -372,8 +409,7 @@ namespace games::sdvx {
nvapi_hook::initialize(avs::game::DLL_INSTANCE); nvapi_hook::initialize(avs::game::DLL_INSTANCE);
} }
// check for Valkyrie cabinet mode if (this->VALKYRIE_MODEL) {
if (isValkyrieCabinetMode) {
// hook touch window // hook touch window
// in windowed mode, game can accept mouse input on the second screen // in windowed mode, game can accept mouse input on the second screen
if (!NATIVETOUCH && !GRAPHICS_WINDOWED) { if (!NATIVETOUCH && !GRAPHICS_WINDOWED) {
+10
View File
@@ -23,6 +23,9 @@ namespace games::sdvx {
extern bool BI2X_INITIALIZED; extern bool BI2X_INITIALIZED;
extern SdvxOverlayPosition OVERLAY_POS; extern SdvxOverlayPosition OVERLAY_POS;
// states
extern bool SHOW_VM_MONITOR_WARNING;
class SDVXGame : public games::Game { class SDVXGame : public games::Game {
public: public:
SDVXGame(); SDVXGame();
@@ -30,5 +33,12 @@ namespace games::sdvx {
virtual void attach() override; virtual void attach() override;
virtual void post_attach() override; virtual void post_attach() override;
virtual void detach() override; virtual void detach() override;
private:
// don't be tempted to make this into a public or a global variable
// various modules need to check if VM mode is active and they should
// not depend on the fact that SDVX module is active (since it can be
// inactive on cabs or partial I/O setup)
bool VALKYRIE_MODEL = false;
void detect_sound_output_device();
}; };
} }
@@ -8,6 +8,7 @@
#include "hooks/audio/audio_private.h" #include "hooks/audio/audio_private.h"
#include "hooks/audio/backends/mmdevice/audio_endpoint_volume.h" #include "hooks/audio/backends/mmdevice/audio_endpoint_volume.h"
#include "hooks/audio/backends/wasapi/audio_client.h" #include "hooks/audio/backends/wasapi/audio_client.h"
#include "util/utils.h"
#define PRINT_FAILED_RESULT(name, ret) \ #define PRINT_FAILED_RESULT(name, ret) \
do { \ do { \
@@ -56,6 +57,7 @@ ULONG STDMETHODCALLTYPE WrappedIMMDevice::Release() {
ULONG refs = pReal != nullptr ? pReal->Release() : 0; ULONG refs = pReal != nullptr ? pReal->Release() : 0;
if (refs == 0) { if (refs == 0) {
log_misc("audio::mmdevice", "WrappedIMMDevice::Release");
delete this; delete this;
} }
@@ -69,7 +71,7 @@ HRESULT STDMETHODCALLTYPE WrappedIMMDevice::Activate(
PROPVARIANT *pActivationParams, PROPVARIANT *pActivationParams,
void **ppInterface) void **ppInterface)
{ {
log_misc("audio::mmdevice", "WrappedIMMDevice::Activate"); log_misc("audio::mmdevice", "WrappedIMMDevice::Activate {}", guid2s(iid));
// call original // call original
HRESULT ret = pReal->Activate(iid, dwClsCtx, pActivationParams, ppInterface); HRESULT ret = pReal->Activate(iid, dwClsCtx, pActivationParams, ppInterface);
@@ -0,0 +1,44 @@
#include "device_collection.h"
#include "device.h"
#include "util/utils.h"
#include "util/logging.h"
HRESULT STDMETHODCALLTYPE WrappedIMMDeviceCollection::QueryInterface(REFIID riid, void **ppvObj) {
if (ppvObj == nullptr) {
return E_POINTER;
}
if (riid == __uuidof(IMMDeviceCollection)) {
this->AddRef();
*ppvObj = this;
return S_OK;
}
return pReal->QueryInterface(riid, ppvObj);
}
ULONG STDMETHODCALLTYPE WrappedIMMDeviceCollection::AddRef() {
return pReal->AddRef();
}
ULONG STDMETHODCALLTYPE WrappedIMMDeviceCollection::Release() {
// get reference count of underlying interface
ULONG refs = pReal != nullptr ? pReal->Release() : 0;
if (refs == 0) {
delete this;
}
return refs;
}
HRESULT STDMETHODCALLTYPE WrappedIMMDeviceCollection::GetCount(UINT *pcDevices) {
return pReal->GetCount(pcDevices);
}
HRESULT STDMETHODCALLTYPE WrappedIMMDeviceCollection::Item(UINT nDevice, IMMDevice **ppDevice) {
log_info("audio", "WrappedIMMDeviceCollection::Item[{}]", nDevice);
// call original
const auto hr = pReal->Item(nDevice, ppDevice);
// wrap interface
*ppDevice = new WrappedIMMDevice(*ppDevice);
return hr;
}
@@ -0,0 +1,28 @@
#pragma once
#include <initguid.h>
#include <mmdeviceapi.h>
struct WrappedIMMDeviceCollection : IMMDeviceCollection {
explicit WrappedIMMDeviceCollection(IMMDeviceCollection *orig) : pReal(orig) {
}
WrappedIMMDeviceCollection(const WrappedIMMDeviceCollection &) = delete;
WrappedIMMDeviceCollection &operator=(const WrappedIMMDeviceCollection &) = delete;
virtual ~WrappedIMMDeviceCollection() = default;
#pragma region IUnknown
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObj) override;
virtual ULONG STDMETHODCALLTYPE AddRef() override;
virtual ULONG STDMETHODCALLTYPE Release() override;
#pragma endregion
#pragma region IMMDeviceCollection
virtual HRESULT STDMETHODCALLTYPE GetCount(UINT *pcDevices) override;
virtual HRESULT STDMETHODCALLTYPE Item(UINT nDevice, IMMDevice **ppDevice) override;
#pragma endregion
private:
IMMDeviceCollection *const pReal;
};
@@ -1,4 +1,5 @@
#include "device_enumerator.h" #include "device_enumerator.h"
#include "device_collection.h"
#include "device.h" #include "device.h"
#include "util/utils.h" #include "util/utils.h"
@@ -42,7 +43,11 @@ HRESULT STDMETHODCALLTYPE WrappedIMMDeviceEnumerator::EnumAudioEndpoints(
DWORD dwStateMask, DWORD dwStateMask,
IMMDeviceCollection **ppDevices) IMMDeviceCollection **ppDevices)
{ {
return pReal->EnumAudioEndpoints(dataFlow, dwStateMask, ppDevices); const auto hr = pReal->EnumAudioEndpoints(dataFlow, dwStateMask, ppDevices);
if (SUCCEEDED(hr) && (ppDevices != nullptr) && (*ppDevices != nullptr)) {
*ppDevices = new WrappedIMMDeviceCollection(*ppDevices);
}
return hr;
} }
HRESULT STDMETHODCALLTYPE WrappedIMMDeviceEnumerator::GetDefaultAudioEndpoint( HRESULT STDMETHODCALLTYPE WrappedIMMDeviceEnumerator::GetDefaultAudioEndpoint(
+27
View File
@@ -0,0 +1,27 @@
#include "mme.h"
#include <mmeapi.h>
#include "util/detour.h"
#include "util/logging.h"
namespace hooks::audio::mme {
static decltype(mixerSetControlDetails) *mixerSetControlDetails_orig = nullptr;
MMRESULT
WINAPI
mixerSetControlDetails_hook(
HMIXEROBJ hmxobj, LPMIXERCONTROLDETAILS pmxcd, DWORD fdwDetails) {
log_misc("audio::mme", "mixerSetControlDetails_hook called; ignoring volume change");
return MMSYSERR_NOERROR;
}
void init(HINSTANCE module) {
mixerSetControlDetails_orig =
detour::iat_try("mixerSetControlDetails", mixerSetControlDetails_hook, module);
if (mixerSetControlDetails_orig != nullptr) {
log_misc("audio::mme", "mixerSetControlDetails hooked");
}
}
}
+7
View File
@@ -0,0 +1,7 @@
#pragma once
#include <windows.h>
namespace hooks::audio::mme {
void init(HINSTANCE module);
}
+81 -1
View File
@@ -1,7 +1,10 @@
#include "avshook.h" #include "avshook.h"
#include <cassert>
#include <mutex>
#include <map> #include <map>
#include <optional> #include <optional>
#include <external/robin_hood.h>
#include "avs/core.h" #include "avs/core.h"
#include "avs/ea3.h" #include "avs/ea3.h"
@@ -20,8 +23,12 @@ static bool FAKE_FILE_OPEN = false;
static std::map<std::string, std::string> ROM_FILE_MAP; static std::map<std::string, std::string> ROM_FILE_MAP;
static std::string *ROM_FILE_CONTENTS = nullptr; static std::string *ROM_FILE_CONTENTS = nullptr;
static std::mutex lstat_cache_mutex;
static robin_hood::unordered_map<std::string, std::pair<int, struct avs::core::avs_stat>> lstat_cache;
namespace hooks::avs::config { namespace hooks::avs::config {
bool DISABLE_VFS_DRIVE_REDIRECTION = false; bool DISABLE_VFS_DRIVE_REDIRECTION = false;
bool DISABLE_CACHE = false;
bool LOG = false; bool LOG = false;
}; };
@@ -79,6 +86,74 @@ static bool is_spam_file(const char *file) {
return false; return false;
} }
static bool is_ldj_cacheable_file(const char *file) {
static const char *spam_prefixes[] = {
// iidx33: profile background customization, spams on every frame during card entry
"/data/graphic/entry_card",
// iidx32+: movie thumbnails, spams on every frame during song select
"/data/graphic/thumbnail/",
"/data/graphic/movie_thumbnail/",
};
for (auto &spam : spam_prefixes) {
if (string_begins_with(file, spam)) {
return true;
}
}
return false;
}
static int cached_avs_fs_lstat(const char *name, struct avs::core::avs_stat *st) {
assert(name != nullptr);
assert(st != nullptr);
const std::string filename(name);
int value = 0;
std::lock_guard<std::mutex> lock(lstat_cache_mutex);
// if this was already seen, return cached result
if (lstat_cache.contains(filename)) {
const auto &result = lstat_cache.at(filename);
value = result.first;
memcpy(st, &result.second, sizeof(*st));
WRAP_DEBUG_FMT(
"(cached) file={}, a={}, m={}, c={}, size={}, u={}",
name,
st->st_atime,
st->st_mtime,
st->st_ctime,
st->filesize,
st->unk1);
return value;
}
// if not, call the original avs function
// this returns 1 on success with valid values in st
// return 0 on failure, with filesize set to 0
// maybe this could be used for future optimization
value = avs::core::avs_fs_lstat(name, st);
WRAP_DEBUG_FMT(
"file={} a={}, m={}, c={}, size={}, u={}",
name,
st->st_atime,
st->st_mtime,
st->st_ctime,
st->filesize,
st->unk1);
// and cache it if there is room (prevent unconstrained growth)
if (lstat_cache.size() <= 4096) {
// c++ was a mistake
lstat_cache.emplace(
std::piecewise_construct,
std::forward_as_tuple(filename),
std::forward_as_tuple(value, *st));
}
// done
return value;
}
static int avs_fs_fstat(avs::core::avs_file_t fd, struct avs::core::avs_stat *st) { static int avs_fs_fstat(avs::core::avs_file_t fd, struct avs::core::avs_stat *st) {
if (is_fake_fd(fd) && ROM_FILE_CONTENTS) { if (is_fake_fd(fd) && ROM_FILE_CONTENTS) {
if (st) { if (st) {
@@ -104,7 +179,12 @@ static int avs_fs_lstat(const char *name, struct avs::core::avs_stat *st) {
return 1; return 1;
} }
auto value = avs::core::avs_fs_lstat(name, st); if (!hooks::avs::config::DISABLE_CACHE && (st != nullptr) &&
avs::game::is_model("LDJ") && is_ldj_cacheable_file(name)) {
return cached_avs_fs_lstat(name, st);
}
const auto value = avs::core::avs_fs_lstat(name, st);
if (!is_spam_file(name)) { if (!is_spam_file(name)) {
WRAP_DEBUG_FMT("name: {}", name); WRAP_DEBUG_FMT("name: {}", name);
} }
+1
View File
@@ -9,6 +9,7 @@
namespace hooks::avs { namespace hooks::avs {
namespace config { namespace config {
extern bool DISABLE_VFS_DRIVE_REDIRECTION; extern bool DISABLE_VFS_DRIVE_REDIRECTION;
extern bool DISABLE_CACHE;
extern bool LOG; extern bool LOG;
}; };
+7
View File
@@ -3,6 +3,7 @@
#include <vector> #include <vector>
#include "avs/game.h" #include "avs/game.h"
#include "games/gitadora/gitadora.h"
#include "util/detour.h" #include "util/detour.h"
#include "util/utils.h" #include "util/utils.h"
@@ -399,6 +400,12 @@ static BOOL WINAPI ClearCommError_hook(HANDLE hFile, LPDWORD lpErrors, LPCOMSTAT
lpStat->cbInQue = custom_handle->bytes_available(); lpStat->cbInQue = custom_handle->bytes_available();
} }
// gitadora arena model needs this, or else
// the game will keep spamming 0xAA
if (games::gitadora::is_arena_model() && lpErrors) {
*lpErrors = 0;
}
return TRUE; return TRUE;
} }
+14 -2
View File
@@ -11,6 +11,7 @@
#include "cfg/icon.h" #include "cfg/icon.h"
#include "cfg/screen_resize.h" #include "cfg/screen_resize.h"
#include "games/ddr/ddr.h" #include "games/ddr/ddr.h"
#include "games/gitadora/gitadora.h"
#include "games/iidx/iidx.h" #include "games/iidx/iidx.h"
#include "hooks/graphics/backends/d3d9/d3d9_backend.h" #include "hooks/graphics/backends/d3d9/d3d9_backend.h"
#include "launcher/shutdown.h" #include "launcher/shutdown.h"
@@ -33,6 +34,7 @@ struct CaptureData {
HWND TDJ_SUBSCREEN_WINDOW = nullptr; HWND TDJ_SUBSCREEN_WINDOW = nullptr;
HWND SDVX_SUBSCREEN_WINDOW = nullptr; HWND SDVX_SUBSCREEN_WINDOW = nullptr;
HWND GFDM_SUBSCREEN_WINDOW = nullptr;
// icon // icon
static HICON WINDOW_ICON = LoadIcon(GetModuleHandle(nullptr), MAKEINTRESOURCE(MAINICON)); static HICON WINDOW_ICON = LoadIcon(GetModuleHandle(nullptr), MAKEINTRESOURCE(MAINICON));
@@ -369,12 +371,17 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
bool is_tdj_sub_window = avs::game::is_model("LDJ") && window_name.ends_with(" sub"); bool is_tdj_sub_window = avs::game::is_model("LDJ") && window_name.ends_with(" sub");
bool is_sdvx_sub_window = avs::game::is_model("KFC") && window_name.ends_with(" Sub Screen"); bool is_sdvx_sub_window = avs::game::is_model("KFC") && window_name.ends_with(" Sub Screen");
bool is_gfdm_sub_window = games::gitadora::is_arena_model() && window_name.ends_with("SMALL");
// TDJ windowed mode with subscreen: hide maximize button // TDJ windowed mode with subscreen: hide maximize button
if ((is_tdj_sub_window && GRAPHICS_IIDX_WSUB) || is_sdvx_sub_window) { if ((is_tdj_sub_window && GRAPHICS_IIDX_WSUB) || is_sdvx_sub_window || is_gfdm_sub_window) {
dwStyle &= ~(WS_MAXIMIZEBOX); dwStyle &= ~(WS_MAXIMIZEBOX);
} }
if ((is_tdj_sub_window || is_sdvx_sub_window) && GRAPHICS_WSUB_BORDERLESS) { // mouse clicks become misaligned when resized
if (is_gfdm_sub_window) {
dwStyle &= ~(WS_SIZEBOX);
}
if ((is_tdj_sub_window || is_sdvx_sub_window || is_gfdm_sub_window) && GRAPHICS_WSUB_BORDERLESS) {
dwStyle &= ~(WS_OVERLAPPEDWINDOW); dwStyle &= ~(WS_OVERLAPPEDWINDOW);
} }
@@ -417,6 +424,11 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
graphics_hook_subscreen_window(SDVX_SUBSCREEN_WINDOW); graphics_hook_subscreen_window(SDVX_SUBSCREEN_WINDOW);
} }
if (is_gfdm_sub_window) {
GFDM_SUBSCREEN_WINDOW = result;
graphics_hook_subscreen_window(GFDM_SUBSCREEN_WINDOW);
}
disable_touch_indicators(result); disable_touch_indicators(result);
return result; return result;
} }
+1
View File
@@ -60,6 +60,7 @@ extern bool GRAPHICS_WSUB_BORDERLESS;
extern bool GRAPHICS_WSUB_ALWAYS_ON_TOP; extern bool GRAPHICS_WSUB_ALWAYS_ON_TOP;
extern HWND TDJ_SUBSCREEN_WINDOW; extern HWND TDJ_SUBSCREEN_WINDOW;
extern HWND SDVX_SUBSCREEN_WINDOW; extern HWND SDVX_SUBSCREEN_WINDOW;
extern HWND GFDM_SUBSCREEN_WINDOW;
extern bool SUBSCREEN_FORCE_REDRAW; extern bool SUBSCREEN_FORCE_REDRAW;
+46
View File
@@ -9,6 +9,7 @@
#include "avs/game.h" #include "avs/game.h"
#include "games/iidx/iidx.h" #include "games/iidx/iidx.h"
#include "games/gitadora/gitadora.h"
#include "util/detour.h" #include "util/detour.h"
#include "util/logging.h" #include "util/logging.h"
#include "util/utils.h" #include "util/utils.h"
@@ -24,6 +25,7 @@ static decltype(GetLocaleInfoEx) *GetLocaleInfoEx_orig = nullptr;
#ifdef SPICE64 #ifdef SPICE64
static decltype(GetSystemDefaultLCID) *GetSystemDefaultLCID_orig = nullptr; static decltype(GetSystemDefaultLCID) *GetSystemDefaultLCID_orig = nullptr;
static decltype(IsDBCSLeadByte) *IsDBCSLeadByte_orig = nullptr; static decltype(IsDBCSLeadByte) *IsDBCSLeadByte_orig = nullptr;
static decltype(WideCharToMultiByte) *WideCharToMultiByte_orig = nullptr;
#endif #endif
static NTSTATUS NTAPI RtlMultiByteToUnicodeN_hook( static NTSTATUS NTAPI RtlMultiByteToUnicodeN_hook(
@@ -173,6 +175,41 @@ static BOOL WINAPI IsDBCSLeadByte_hook (
return IsDBCSLeadByteEx(CODEPAGE_SHIFT_JIS, TestChar); return IsDBCSLeadByteEx(CODEPAGE_SHIFT_JIS, TestChar);
} }
static
int
WINAPI
WideCharToMultiByte_hook(
UINT CodePage,
DWORD dwFlags,
LPCWCH lpWideCharStr,
int cchWideChar,
LPSTR lpMultiByteStr,
int cbMultiByte,
LPCCH lpDefaultChar,
LPBOOL lpUsedDefaultChar
)
{
switch (CodePage) {
case CP_ACP:
case CP_THREAD_ACP:
SetThreadLocale(MAKELANGID(LANG_JAPANESE, SUBLANG_JAPANESE_JAPAN));
CodePage = CODEPAGE_SHIFT_JIS;
break;
default:
break;
}
return WideCharToMultiByte_orig(
CodePage,
dwFlags,
lpWideCharStr,
cchWideChar,
lpMultiByteStr,
cbMultiByte,
lpDefaultChar,
lpUsedDefaultChar);
}
#endif #endif
void hooks::lang::early_init() { void hooks::lang::early_init() {
@@ -213,6 +250,15 @@ void hooks::lang::early_init() {
IsDBCSLeadByte_hook, IsDBCSLeadByte_hook,
&IsDBCSLeadByte_orig); &IsDBCSLeadByte_orig);
} }
if (games::gitadora::is_arena_model()) {
log_info("hooks::lang", "hooking WideCharToMultiByte");
detour::trampoline_try(
"kernel32.dll",
"WideCharToMultiByte",
WideCharToMultiByte_hook,
&WideCharToMultiByte_orig);
}
#endif #endif
} }
+22 -3
View File
@@ -88,6 +88,7 @@
#include "misc/device.h" #include "misc/device.h"
#include "misc/eamuse.h" #include "misc/eamuse.h"
#include "misc/extdev.h" #include "misc/extdev.h"
#include "misc/ami2000.h"
#include "misc/sciunit.h" #include "misc/sciunit.h"
#include "misc/sde.h" #include "misc/sde.h"
#include "misc/wintouchemu.h" #include "misc/wintouchemu.h"
@@ -195,6 +196,7 @@ int main_implementation(int argc, char *argv[]) {
bool attach_icca = false; bool attach_icca = false;
bool attach_device = false; bool attach_device = false;
bool attach_extdev = false; bool attach_extdev = false;
bool attach_ami2000 = false;
bool attach_sciunit = false; bool attach_sciunit = false;
bool attach_cpusbxpkm_printer = false; bool attach_cpusbxpkm_printer = false;
bool attach_iidx = false; bool attach_iidx = false;
@@ -344,8 +346,13 @@ int main_implementation(int argc, char *argv[]) {
GRAPHICS_FORCE_SINGLE_ADAPTER = true; GRAPHICS_FORCE_SINGLE_ADAPTER = true;
GRAPHICS_PREVENT_SECONDARY_WINDOW = true; GRAPHICS_PREVENT_SECONDARY_WINDOW = true;
} }
if (options[launcher::Options::DisplayAdapter].is_active()) { if (options[launcher::Options::DisplayAdapter].is_active() &&
options[launcher::Options::DisplayAdapter].value_uint32() != D3DADAPTER_DEFAULT) {
D3D9_ADAPTER = options[launcher::Options::DisplayAdapter].value_uint32(); D3D9_ADAPTER = options[launcher::Options::DisplayAdapter].value_uint32();
// when we fix up adapter numbers, we only fix the first adapter, and not any subsequent
// adapters, so we can't deal with multi-monitor games
GRAPHICS_FORCE_SINGLE_ADAPTER = true;
} }
if (options[launcher::Options::CaptureCursor].value_bool()) { if (options[launcher::Options::CaptureCursor].value_bool()) {
GRAPHICS_CAPTURE_CURSOR = true; GRAPHICS_CAPTURE_CURSOR = true;
@@ -423,8 +430,8 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::EnableEXTDEVModule].value_bool()) { if (options[launcher::Options::EnableEXTDEVModule].value_bool()) {
attach_extdev = true; attach_extdev = true;
} }
if (options[launcher::Options::EnableSCIUNITModule].value_bool()) { if (options[launcher::Options::EnableAMI2000Module].value_bool()) {
attach_sciunit = true; attach_ami2000 = true;
} }
if (options[launcher::Options::EnableDevicePassthrough].value_bool()) { if (options[launcher::Options::EnableDevicePassthrough].value_bool()) {
hooks::device::ENABLE = false; hooks::device::ENABLE = false;
@@ -523,6 +530,9 @@ int main_implementation(int argc, char *argv[]) {
overlay::windows::IIDX_SEGMENT_LOCATION = overlay::windows::IIDX_SEGMENT_LOCATION =
options[launcher::Options::spice2x_IIDXLEDPos].value_text(); options[launcher::Options::spice2x_IIDXLEDPos].value_text();
} }
if (options[launcher::Options::IIDXLEDBorderless].value_bool()) {
overlay::windows::IIDX_SEGMENT_BORDERLESS = true;
}
if (options[launcher::Options::spice2x_IIDXNoESpec].value_bool()) { if (options[launcher::Options::spice2x_IIDXNoESpec].value_bool()) {
games::iidx::DISABLE_ESPEC_IO = true; games::iidx::DISABLE_ESPEC_IO = true;
} }
@@ -744,6 +754,9 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::DisableAvsVfsDriveMountRedirection].is_active()) { if (options[launcher::Options::DisableAvsVfsDriveMountRedirection].is_active()) {
hooks::avs::config::DISABLE_VFS_DRIVE_REDIRECTION = true; hooks::avs::config::DISABLE_VFS_DRIVE_REDIRECTION = true;
} }
if (options[launcher::Options::DisableAvsCache].is_active()) {
hooks::avs::config::DISABLE_CACHE = true;
}
if (options[launcher::Options::ScreenResizeConfigPath].is_active()) { if (options[launcher::Options::ScreenResizeConfigPath].is_active()) {
cfg::SCREEN_RESIZE_CFG_PATH_OVERRIDE = cfg::SCREEN_RESIZE_CFG_PATH_OVERRIDE =
options[launcher::Options::ScreenResizeConfigPath].value_text(); options[launcher::Options::ScreenResizeConfigPath].value_text();
@@ -1629,6 +1642,7 @@ int main_implementation(int argc, char *argv[]) {
attach_io = true; attach_io = true;
attach_device = true; attach_device = true;
attach_extdev = true; attach_extdev = true;
attach_ami2000 = true;
attach_gitadora = true; attach_gitadora = true;
break; break;
} }
@@ -2191,6 +2205,11 @@ int main_implementation(int argc, char *argv[]) {
extdev_attach(); extdev_attach();
} }
// ami 2000 card reader attach
if (attach_io || attach_ami2000) {
ami2000_attach();
}
// sci unit attach // sci unit attach
if (attach_io || attach_sciunit) { if (attach_io || attach_sciunit) {
sciunit_attach(); sciunit_attach();
+27 -2
View File
@@ -207,7 +207,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.name = "monitor", .name = "monitor",
.desc = "Sets the display that the game will be opened in, for multiple monitors.\n\n" .desc = "Sets the display that the game will be opened in, for multiple monitors.\n\n"
"0 is the primary monitor, 1 is the second monitor, and so on.\n\n" "0 is the primary monitor, 1 is the second monitor, and so on.\n\n"
"Not all games will respect this. Does not work at all with multi-monitor games like TDJ/UFC. " "Not all games will respect this. Not recommended for multi-monitor games like Lightning Model / Valkyrie Model modes. "
"Disable Full Screen Optimizations for best results", "Disable Full Screen Optimizations for best results",
.type = OptionType::Integer, .type = OptionType::Integer,
.category = "Graphics (Full Screen)", .category = "Graphics (Full Screen)",
@@ -590,7 +590,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.category = "Game Options (Advanced)", .category = "Game Options (Advanced)",
}, },
{ {
.title = "IIDX TDJ Mode", .title = "IIDX TDJ Mode (Lightning Model)",
.name = "iidxtdj", .name = "iidxtdj",
.desc = "Enables TDJ cabinet mode. Ensure you also set -iidxsounddevice to desired option", .desc = "Enables TDJ cabinet mode. Ensure you also set -iidxsounddevice to desired option",
.type = OptionType::Bool, .type = OptionType::Bool,
@@ -679,6 +679,16 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
{"bottomright", ""}, {"bottomright", ""},
}, },
}, },
{
// IIDXLEDBorderless
.title = "IIDX LED Ticker Borderless",
.name = "iidxledborderless",
.aliases= "iidxledborderless",
.desc = "Make LED ticker overlay window borderless (remove window decoration)",
.type = OptionType::Bool,
.game_name = "Beatmania IIDX",
.category = "Overlay",
},
{ {
.title = "Force Load Sound Voltex Module", .title = "Force Load Sound Voltex Module",
.name = "sdvx", .name = "sdvx",
@@ -1311,6 +1321,13 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.type = OptionType::Bool, .type = OptionType::Bool,
.category = "I/O Modules", .category = "I/O Modules",
}, },
{
.title = "Enable AMI2000 Module",
.name = "ami2000",
.desc = "Manually enable AMI2000 emulation",
.type = OptionType::Bool,
.category = "I/O Modules",
},
{ {
.title = "Enable SCIUNIT Module", .title = "Enable SCIUNIT Module",
.name = "sciunit", .name = "sciunit",
@@ -1756,6 +1773,14 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.type = OptionType::Bool, .type = OptionType::Bool,
.category = "Development", .category = "Development",
}, },
{
// DisableAvsCache
.title = "Disable AVS Cache",
.name = "avscachedisable",
.desc = "Disable optimization used for some games to cache data file info (only for IIDX32+ for now)",
.type = OptionType::Bool,
.category = "Development",
},
{ {
.title = "Output PEB", .title = "Output PEB",
.name = "pebprint", .name = "pebprint",
+3
View File
@@ -71,6 +71,7 @@ namespace launcher {
spice2x_IIDXLEDFontSize, spice2x_IIDXLEDFontSize,
spice2x_IIDXLEDColor, spice2x_IIDXLEDColor,
spice2x_IIDXLEDPos, spice2x_IIDXLEDPos,
IIDXLEDBorderless,
LoadSoundVoltexModule, LoadSoundVoltexModule,
SDVXForce720p, SDVXForce720p,
SDVXPrinterEmulation, SDVXPrinterEmulation,
@@ -146,6 +147,7 @@ namespace launcher {
EnableICCAModule, EnableICCAModule,
EnableDEVICEModule, EnableDEVICEModule,
EnableEXTDEVModule, EnableEXTDEVModule,
EnableAMI2000Module,
EnableSCIUNITModule, EnableSCIUNITModule,
EnableDevicePassthrough, EnableDevicePassthrough,
ForceWinTouch, ForceWinTouch,
@@ -195,6 +197,7 @@ namespace launcher {
DisableSignalHandling, DisableSignalHandling,
DisableDebugHooks, DisableDebugHooks,
DisableAvsVfsDriveMountRedirection, DisableAvsVfsDriveMountRedirection,
DisableAvsCache,
OutputPEB, OutputPEB,
DumpSystemInfo, DumpSystemInfo,
QKSArgs, QKSArgs,
+85
View File
@@ -0,0 +1,85 @@
#include "ami2000.h"
#include "util/utils.h"
#include "util/detour.h"
#include "util/libutils.h"
#include "misc/eamuse.h"
#include "acio/icca/icca.h"
static int CARD_TYPE = 0;
static uint8_t CARD_UID[8];
static void __fastcall AMI2000_Initialize() {
log_info("ami2000", "ami2000 initialize.");
}
static void __fastcall AMI2000_Startup() {
log_info("ami2000", "ami2000 startup.");
}
static void __fastcall AMI2000_Cleanup() {
log_info("ami2000", "ami2000 cleanup.");
}
static int __fastcall AMI2000_IsDeviceError() {
return 0;
}
static int __fastcall AMI2000_ReadCtrl() {
return 0;
}
static const char *__fastcall AMI2000_GetRomVersion() {
return "DUMMY";
}
static int __fastcall AMI2000_GetCardType() {
return CARD_TYPE;
}
static uint64_t __fastcall AMI2000_GetCardIdentifier() {
if (eamuse_card_insert_consume(1, 0)) {
eamuse_get_card(1, 0, CARD_UID);
CARD_TYPE = is_card_uid_felica(CARD_UID) ? 2 : 1;
log_info("ami2000", "AMI2000_GetCardIdentifier: {:X}", bswap64(*(uint64_t*)CARD_UID));
} else {
CARD_TYPE = 0;
return 0;
}
return bswap64(*(uint64_t *)CARD_UID);
}
void ami2000_attach() {
auto ami2000 = libutils::try_module("iccr-ami2000-api.dll");
if (!ami2000) {
log_warning("ami2000", "module not found, skipping.");
return;
}
// card unit
detour::inline_hook((void *)AMI2000_Initialize, libutils::try_proc(
ami2000, "AMI2000_Initialize"));
detour::inline_hook((void *)AMI2000_Startup, libutils::try_proc(
ami2000, "AMI2000_Startup"));
detour::inline_hook((void *)AMI2000_IsDeviceError, libutils::try_proc(
ami2000, "AMI2000_IsDeviceError"));
detour::inline_hook((void *)AMI2000_ReadCtrl, libutils::try_proc(
ami2000, "AMI2000_ReadCtrl"));
detour::inline_hook((void *)AMI2000_Cleanup, libutils::try_proc(
ami2000, "AMI2000_Cleanup"));
detour::inline_hook((void *)AMI2000_GetRomVersion, libutils::try_proc(
ami2000, "AMI2000_GetRomVersion"));
detour::inline_hook((void *)AMI2000_GetCardType, libutils::try_proc(
ami2000, "AMI2000_GetCardType"));
detour::inline_hook((void *)AMI2000_GetCardIdentifier, libutils::try_proc(
ami2000, "AMI2000_GetCardIdentifier"));
log_info("ami2000", "attached");
}
void ami2000_detach()
{
}
+4
View File
@@ -0,0 +1,4 @@
#pragma once
void ami2000_attach();
void ami2000_detach();
+17 -1
View File
@@ -1,6 +1,8 @@
#include "exitprompt.h" #include "exitprompt.h"
#include "avs/game.h"
#include "misc/eamuse.h" #include "misc/eamuse.h"
#include "util/logging.h" #include "util/logging.h"
#include "games/iidx/iidx.h"
namespace overlay::windows { namespace overlay::windows {
@@ -55,7 +57,21 @@ namespace overlay::windows {
} }
ImGui::Spacing(); ImGui::Spacing();
build_button(this->overlay->window_config, "Show Config", size, NextItem::NEW_LINE, false); build_button(this->overlay->window_config, "Show Config", size, NextItem::NEW_LINE, false);
build_button(this->overlay->window_sub, "Show Subscreen", size, NextItem::NEW_LINE, false);
std::string sub = "Show Subscreen";
if (avs::game::is_model("LDJ")) {
if (games::iidx::TDJ_MODE) {
sub = "Show TDJ Subscreen";
} else {
sub = "Show LDJ LED Ticker";
}
} else if (avs::game::is_model("KFC")) {
sub = "Show Valkyrie Subscreen";
} else if (avs::game::is_model("REC")) {
sub = "Show DRS Dance Floor";
}
build_button(this->overlay->window_sub, sub, size, NextItem::NEW_LINE, false);
ImGui::TextDisabled("Graphics"); ImGui::TextDisabled("Graphics");
build_button(this->overlay->window_camera, "Camera control", size, NextItem::NEW_LINE); build_button(this->overlay->window_camera, "Camera control", size, NextItem::NEW_LINE);
+24 -8
View File
@@ -10,12 +10,13 @@ namespace overlay::windows {
uint32_t IIDX_SEGMENT_FONT_SIZE = 64; uint32_t IIDX_SEGMENT_FONT_SIZE = 64;
std::optional<uint32_t> IIDX_SEGMENT_FONT_COLOR = std::nullopt; std::optional<uint32_t> IIDX_SEGMENT_FONT_COLOR = std::nullopt;
std::string IIDX_SEGMENT_LOCATION = "bottom"; std::string IIDX_SEGMENT_LOCATION = "bottom";
bool IIDX_SEGMENT_BORDERLESS = false;
static const size_t TICKER_SIZE = 9; static const size_t TICKER_SIZE = 9;
static const ImVec4 DARK_GRAY(0.1f, 0.1f, 0.1f, 1.f); static const ImVec4 DARK_GRAY(0.1f, 0.1f, 0.1f, 1.f);
static const ImVec4 RED(1.f, 0.f, 0.f, 1.f); static const ImVec4 RED(1.f, 0.f, 0.f, 1.f);
static const int PADDING_Y = 8; static const float PADDING_Y = 8.f;
static const int PADDING_X = 4; static const float PADDING_X = 4.f;
static const std::map<char, std::pair<char, char>> CHARMAP = {\ static const std::map<char, std::pair<char, char>> CHARMAP = {\
// period - add a space afterwards (game sends 'm' for period) // period - add a space afterwards (game sends 'm' for period)
@@ -31,16 +32,18 @@ namespace overlay::windows {
log_fatal("iidx_seg", "DSEG_FONT is null"); log_fatal("iidx_seg", "DSEG_FONT is null");
} }
this->title = "IIDX LED Segment Display"; this->title = "IIDX LDJ LED Ticker";
this->toggle_button = games::OverlayButtons::ToggleSubScreen; this->toggle_button = games::OverlayButtons::ToggleSubScreen;
this->remove_window_padding = true; this->remove_window_padding = true;
this->size_max = ImVec2(ImGui::GetIO().DisplaySize.x, ImGui::GetIO().DisplaySize.y); this->size_max = ImVec2(ImGui::GetIO().DisplaySize.x, ImGui::GetIO().DisplaySize.y);
this->flags = ImGuiWindowFlags_NoTitleBar this->flags = ImGuiWindowFlags_NoScrollbar
| ImGuiWindowFlags_NoScrollbar
| ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoResize
| ImGuiWindowFlags_NoNavFocus | ImGuiWindowFlags_NoNavFocus
| ImGuiWindowFlags_NoNavInputs | ImGuiWindowFlags_NoNavInputs
| ImGuiWindowFlags_NoDocking; | ImGuiWindowFlags_NoDocking;
if (IIDX_SEGMENT_BORDERLESS) {
this->flags |= ImGuiWindowFlags_NoTitleBar;
}
if (IIDX_SEGMENT_FONT_COLOR.has_value()) { if (IIDX_SEGMENT_FONT_COLOR.has_value()) {
const auto rgb = IIDX_SEGMENT_FONT_COLOR.value(); const auto rgb = IIDX_SEGMENT_FONT_COLOR.value();
@@ -51,13 +54,22 @@ namespace overlay::windows {
} }
} }
float IIDXSegmentDisplay::get_title_bar_height() {
// make sure you don't call this within PushFont/PopFont block
// since text line height calculation is dependent on current font size
return ImGui::GetTextLineHeight() + ImGui::GetStyle().FramePadding.y * 2.0f;
}
void IIDXSegmentDisplay::calculate_initial_window() { void IIDXSegmentDisplay::calculate_initial_window() {
// ImGui::CalcTextSize doesn't seem to work here, so manually calculate // ImGui::CalcTextSize doesn't seem to work here, so manually calculate
ImGui::PushFont(DSEG_FONT); ImGui::PushFont(DSEG_FONT);
this->init_size = ImGui::CalcTextSize("~.~.~.~.~.~.~.~.~."); this->init_size = ImGui::CalcTextSize("~.~.~.~.~.~.~.~.~.");
ImGui::PopFont();
this->init_size.x += PADDING_X * 2; this->init_size.x += PADDING_X * 2;
this->init_size.y += PADDING_Y * 2; this->init_size.y += PADDING_Y * 2;
ImGui::PopFont(); if (!IIDX_SEGMENT_BORDERLESS) {
this->init_size.y += get_title_bar_height();
}
// initial horizontal position // initial horizontal position
if (IIDX_SEGMENT_LOCATION.find("left") != std::string::npos) { if (IIDX_SEGMENT_LOCATION.find("left") != std::string::npos) {
@@ -127,9 +139,13 @@ namespace overlay::windows {
} }
void IIDXSegmentDisplay::draw_ticker(char *ticker_string) { void IIDXSegmentDisplay::draw_ticker(char *ticker_string) {
ImGui::PushFont(DSEG_FONT); float pad_y = PADDING_Y;
if (!IIDX_SEGMENT_BORDERLESS) {
pad_y += get_title_bar_height();
}
const auto pos = ImVec2(PADDING_X, pad_y);
const auto pos = ImVec2(PADDING_X, PADDING_Y); ImGui::PushFont(DSEG_FONT);
// to imitate LED "off" // to imitate LED "off"
ImGui::SetCursorPos(pos); ImGui::SetCursorPos(pos);
+2
View File
@@ -8,12 +8,14 @@ namespace overlay::windows {
extern uint32_t IIDX_SEGMENT_FONT_SIZE; extern uint32_t IIDX_SEGMENT_FONT_SIZE;
extern std::optional<uint32_t> IIDX_SEGMENT_FONT_COLOR; extern std::optional<uint32_t> IIDX_SEGMENT_FONT_COLOR;
extern std::string IIDX_SEGMENT_LOCATION; extern std::string IIDX_SEGMENT_LOCATION;
extern bool IIDX_SEGMENT_BORDERLESS;
class IIDXSegmentDisplay : public Window { class IIDXSegmentDisplay : public Window {
public: public:
IIDXSegmentDisplay(SpiceOverlay *overlay); IIDXSegmentDisplay(SpiceOverlay *overlay);
void calculate_initial_window() override; void calculate_initial_window() override;
void build_content() override; void build_content() override;
float get_title_bar_height();
private: private:
ImVec4 color; ImVec4 color;
void draw_ticker(char *ticker_string); void draw_ticker(char *ticker_string);
+3 -1
View File
@@ -9,12 +9,14 @@
namespace overlay::windows { namespace overlay::windows {
IIDXSubScreen::IIDXSubScreen(SpiceOverlay *overlay) : GenericSubScreen(overlay) { IIDXSubScreen::IIDXSubScreen(SpiceOverlay *overlay) : GenericSubScreen(overlay) {
this->title = "IIDX Sub Screen"; this->title = "IIDX TDJ Subscreen";
if (GRAPHICS_IIDX_WSUB) { if (GRAPHICS_IIDX_WSUB) {
this->disabled_message = this->disabled_message =
"Close this overlay and use the second window.\n" "Close this overlay and use the second window.\n"
"If you don't see the window, double check your DLL type and apply TDJ I/O patches as needed."; "If you don't see the window, double check your DLL type and apply TDJ I/O patches as needed.";
} else if (games::iidx::IIDX_TDJ_MONITOR_WARNING) {
this->disabled_message = "TDJ mode subscreen overlay is not compatible with -monitor option";
} }
float size = 0.5f; float size = 0.5f;
+3 -1
View File
@@ -8,11 +8,13 @@
namespace overlay::windows { namespace overlay::windows {
SDVXSubScreen::SDVXSubScreen(SpiceOverlay *overlay) : GenericSubScreen(overlay) { SDVXSubScreen::SDVXSubScreen(SpiceOverlay *overlay) : GenericSubScreen(overlay) {
this->title = "SDVX Sub Screen"; this->title = "SDVX Subscreen";
bool isValkyrieCabinetMode = avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H'; bool isValkyrieCabinetMode = avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H';
if (!isValkyrieCabinetMode) { if (!isValkyrieCabinetMode) {
this->disabled_message = "Valkyrie Model mode is not enabled!"; this->disabled_message = "Valkyrie Model mode is not enabled!";
} else if (games::sdvx::SHOW_VM_MONITOR_WARNING) {
this->disabled_message = "VM mode subscreen overlay is not compatible with -monitor option";
} else if (GRAPHICS_WINDOWED) { } else if (GRAPHICS_WINDOWED) {
if (GRAPHICS_PREVENT_SECONDARY_WINDOW) { if (GRAPHICS_PREVENT_SECONDARY_WINDOW) {
this->disabled_message = "Subscreen has been disabled by the user (-sdvxnosub)."; this->disabled_message = "Subscreen has been disabled by the user (-sdvxnosub).";
+3 -1
View File
@@ -241,7 +241,9 @@ static LRESULT CALLBACK SpiceTouchWndProc(HWND hWnd, UINT msg, WPARAM wParam, LP
} }
const auto is_windowed_sub = const auto is_windowed_sub =
(GRAPHICS_IIDX_WSUB && hWnd == TDJ_SUBSCREEN_WINDOW) || (hWnd == SDVX_SUBSCREEN_WINDOW); (GRAPHICS_IIDX_WSUB && hWnd == TDJ_SUBSCREEN_WINDOW) ||
(hWnd == SDVX_SUBSCREEN_WINDOW) ||
(hWnd == GFDM_SUBSCREEN_WINDOW);
if (msg == WM_CLOSE) { if (msg == WM_CLOSE) {
if (is_windowed_sub) { if (is_windowed_sub) {
+2 -1
View File
@@ -12,7 +12,8 @@ namespace deferredlogs {
" this crash is most likely related to audio init failure", " this crash is most likely related to audio init failure",
" * check if the default audio device has changed", " * check if the default audio device has changed",
" * fix your audio device settings (e.g., sample rate)", " * fix your audio device settings (e.g., sample rate)",
" * double check your spice audio options and patches" " * double check your spice audio options and patches",
" * ensure no other application is using the device in exclusive mode"
}; };
std::mutex deferred_errors_mutex; std::mutex deferred_errors_mutex;
+20 -1
View File
@@ -350,4 +350,23 @@ static inline std::string wchar_to_u8(const wchar_t* wstr) {
std::string out(size - 1, 0); std::string out(size - 1, 0);
WideCharToMultiByte(CP_UTF8, 0, wstr, -1, out.data(), size - 1, nullptr, nullptr); WideCharToMultiByte(CP_UTF8, 0, wstr, -1, out.data(), size - 1, nullptr, nullptr);
return out; return out;
} }
// convert litte vs big endian
inline uint64_t bswap64(uint64_t x) {
#if defined(__GNUC__) || defined(__clang__)
return __builtin_bswap64(x);
#endif
#ifdef _MSC_VER
return _byteswap_uint64(x);
#endif
return (x >> 56) |
((x >> 40) & 0x000000000000FF00ULL) |
((x >> 24) & 0x0000000000FF0000ULL) |
((x >> 8) & 0x00000000FF000000ULL) |
((x << 8) & 0x000000FF00000000ULL) |
((x << 24) & 0x0000FF0000000000ULL) |
((x << 40) & 0x00FF000000000000ULL) |
(x << 56);
}