mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-02 14:50:41 -07:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8de7e5a9e4 | |||
| b8e99e75e5 | |||
| 46dc22dd63 | |||
| 69d78e0a87 | |||
| ab3d41432a | |||
| 73e10cac0c | |||
| c6242233a7 | |||
| 6b8335780f | |||
| 21f469b260 | |||
| 07a0df5fdc | |||
| d4f1768bbd | |||
| 96e7e4949f | |||
| 24d3062c66 | |||
| 6cd212aac2 | |||
| 064031bc12 | |||
| 4c8d4ebab6 | |||
| 462a07680b | |||
| 081def30db | |||
| 6776b08bce | |||
| 6cb507f4fa | |||
| de05af052f | |||
| c767918729 | |||
| fbc8bc2502 | |||
| c38f42cb19 | |||
| 42d8bbc92f | |||
| a09905792d | |||
| f113eb4f52 | |||
| f726748ebe |
@@ -49,6 +49,8 @@ Watch out for legacy OS compatibility. Currently, the minimum support floor is W
|
||||
|
||||
### Code style requirements
|
||||
* 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.
|
||||
|
||||
OK:
|
||||
|
||||
@@ -118,6 +118,9 @@ elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -static")
|
||||
else()
|
||||
|
||||
# warnings as errors
|
||||
set(CMAKE_COMPILE_WARNING_AS_ERROR ON)
|
||||
|
||||
# warnings
|
||||
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 :)
|
||||
@@ -398,6 +401,10 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
||||
games/nost/poke.cpp
|
||||
games/gitadora/gitadora.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/io.cpp
|
||||
games/mga/gunio.cpp
|
||||
@@ -470,10 +477,12 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
||||
hooks/audio/acm.cpp
|
||||
hooks/audio/audio.cpp
|
||||
hooks/audio/buffer.cpp
|
||||
hooks/audio/mme.cpp
|
||||
hooks/audio/util.cpp
|
||||
hooks/audio/backends/dsound/dsound_backend.cpp
|
||||
hooks/audio/backends/mmdevice/audio_endpoint_volume.cpp
|
||||
hooks/audio/backends/mmdevice/device.cpp
|
||||
hooks/audio/backends/mmdevice/device_collection.cpp
|
||||
hooks/audio/backends/mmdevice/device_enumerator.cpp
|
||||
hooks/audio/backends/wasapi/audio_client.cpp
|
||||
hooks/audio/backends/wasapi/audio_render_client.cpp
|
||||
@@ -526,9 +535,11 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
||||
misc/device.cpp
|
||||
misc/eamuse.cpp
|
||||
misc/extdev.cpp
|
||||
misc/nativetouchhook.cpp
|
||||
misc/sciunit.cpp
|
||||
misc/sde.cpp
|
||||
misc/wintouchemu.cpp
|
||||
misc/ami2000.cpp
|
||||
|
||||
# nvapi
|
||||
nvapi/nvapi.cpp
|
||||
|
||||
+168
-16
@@ -9,6 +9,15 @@
|
||||
#include "util/utils.h"
|
||||
#include <mutex>
|
||||
|
||||
#define MDFX_DEBUG_VERBOSE 0
|
||||
|
||||
#if MDFX_DEBUG_VERBOSE
|
||||
#define log_debug(module, format_str, ...) logger::push( \
|
||||
LOG_FORMAT("M", module, format_str, ## __VA_ARGS__), logger::Style::GREY)
|
||||
#else
|
||||
#define log_debug(module, format_str, ...)
|
||||
#endif
|
||||
|
||||
// constants
|
||||
const size_t STATUS_BUFFER_SIZE = 32;
|
||||
const size_t STATUS_BUFFER_NUM_ENTRIES = 16;
|
||||
@@ -30,6 +39,21 @@ static bool IS_MDXF_ACTIVE = false;
|
||||
static const uint8_t BACKFILL_INTERVAL_MS = 4;
|
||||
static const uint8_t BACKFILL_PADDING_MS = 2;
|
||||
|
||||
// These are used to determine if a thread needs to be spun up to keep pad state ring buffers populated with enough recent polls
|
||||
static uint64_t START_TIME = 0;
|
||||
static int CALL_COUNT = 0;
|
||||
static const int THRESHOLD_REFRESH_RATE = 120;
|
||||
|
||||
static std::atomic<bool> IS_REFRESH_RATE_MEASUREMENT_STARTED{false};
|
||||
static std::atomic<bool> IS_REFRESH_RATE_DETERMINED{false};
|
||||
static std::atomic<bool> IS_THREAD_NEEDED{false};
|
||||
static std::atomic<bool> MDXF_THREAD_RUNNING{false};
|
||||
|
||||
static std::thread MDXF_THREAD;
|
||||
|
||||
static constexpr int THREAD_REFRESH_RATE_HZ = 125;
|
||||
static constexpr auto THREAD_PERIOD = std::chrono::milliseconds(1000 / THREAD_REFRESH_RATE_HZ);
|
||||
|
||||
// buffers
|
||||
#pragma pack(push, 1)
|
||||
static struct {
|
||||
@@ -40,9 +64,20 @@ static struct {
|
||||
|
||||
static bool STATUS_BUFFER_FREEZE = false;
|
||||
|
||||
// Decides which method to use for populating ring buffer entries for "padding".
|
||||
// Overwritten in spicecfg using P4IO Buffer Algorithm option.
|
||||
// THREAD_MODE: Spins a thread running at THREAD_REFRESH_RATE_HZ which periodically fills the ring
|
||||
// buffer with auxiliary entries. Falls back on BACKFILL_MODE
|
||||
// BACKFILL_MODE: On every update cycle, fill the ring buffer with entries for the last known state
|
||||
// BACKFILL_INTERVAL_MS apart from each other from the time of the last entry to the
|
||||
// current time before adding the entry for the current state.
|
||||
// AUTO_MODE: thread mode if <120Hz, backfill if >=120Hz
|
||||
acio::MDXFBufferFillMode acio::MDXF_BUFFER_FILL_MODE = acio::MDXFBufferFillMode::AUTO_MODE;
|
||||
|
||||
typedef enum {
|
||||
ARKMDXP4_POLL = 0,
|
||||
EXTERNAL_POLL = 1
|
||||
INTERNAL_POLL = 1,
|
||||
EXTERNAL_POLL = 2
|
||||
} MDXFPollSource;
|
||||
|
||||
typedef uint64_t (__cdecl *ARK_GET_TICK_TIME64_T)();
|
||||
@@ -61,6 +96,106 @@ static uint64_t arkGetTickTime64() {
|
||||
return getTickTime64 ? getTickTime64() : timeGetTime();
|
||||
}
|
||||
|
||||
// Used to keep the ring buffer populated with steady updates. 60Hz interval is too slow
|
||||
static void mdxf_thread_start() {
|
||||
bool expected = false;
|
||||
if (!MDXF_THREAD_RUNNING.compare_exchange_strong(expected, true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
log_info("mdxf", "starting poll thread");
|
||||
|
||||
MDXF_THREAD = std::thread([] {
|
||||
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL);
|
||||
|
||||
while (MDXF_THREAD_RUNNING.load(std::memory_order_acquire)) {
|
||||
mdxf_poll(false);
|
||||
std::this_thread::sleep_for(THREAD_PERIOD);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static void mdxf_thread_stop() {
|
||||
if (!MDXF_THREAD_RUNNING.exchange(false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (MDXF_THREAD.joinable()) {
|
||||
MDXF_THREAD.join();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Snaps measured refresh rate to best fit
|
||||
static int snap_refresh_rate(int measured_hz) {
|
||||
static constexpr std::array<int, 6> rates = {
|
||||
60, 120, 144, 165, 180, 240
|
||||
};
|
||||
|
||||
int best = rates[0];
|
||||
int best_err = std::fabs(measured_hz - best);
|
||||
|
||||
for (int r : rates) {
|
||||
int err = std::fabs(measured_hz - r);
|
||||
if (err < best_err) {
|
||||
best = r;
|
||||
best_err = err;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
// Increments the number of times the update function was called,
|
||||
// then calculates the current refresh rate of the game
|
||||
// (20 seconds after the game starts, until 25 seconds)
|
||||
static void count_calls_from_game() {
|
||||
if (IS_REFRESH_RATE_DETERMINED) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uint64_t current_time = arkGetTickTime64();
|
||||
|
||||
if (!IS_REFRESH_RATE_MEASUREMENT_STARTED) {
|
||||
if (START_TIME == 0) {
|
||||
START_TIME = current_time;
|
||||
}
|
||||
|
||||
// boot screen takes about 10 seconds, so let's wait for double that
|
||||
if ((current_time - START_TIME) < 20000) {
|
||||
// too early, do nothing
|
||||
return;
|
||||
} else {
|
||||
// 20s has passed for the first time, start measuring on next call
|
||||
IS_REFRESH_RATE_MEASUREMENT_STARTED = true;
|
||||
START_TIME = current_time;
|
||||
log_debug("mdxf", "measurement begin");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const uint64_t elapsed_time = current_time - START_TIME;
|
||||
CALL_COUNT++;
|
||||
if (elapsed_time >= 5000) {
|
||||
double measured_hz = static_cast<double>(CALL_COUNT) * 1000.0 / static_cast<double>(elapsed_time);
|
||||
|
||||
// Account for the main loop calling this twice per iteration
|
||||
measured_hz *= 0.5;
|
||||
const int snapped_hz = snap_refresh_rate(static_cast<int>(measured_hz));
|
||||
|
||||
IS_REFRESH_RATE_DETERMINED = true;
|
||||
IS_THREAD_NEEDED = (snapped_hz < THRESHOLD_REFRESH_RATE);
|
||||
log_info(
|
||||
"mdxf",
|
||||
"detected: {} Hz, best fit: {} Hz",
|
||||
static_cast<int>(measured_hz),
|
||||
snapped_hz);
|
||||
|
||||
if (IS_THREAD_NEEDED) {
|
||||
mdxf_thread_start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Implementations
|
||||
*/
|
||||
@@ -92,22 +227,20 @@ static uint64_t __cdecl ac_io_mdxf_get_control_status_buffer(int node, void *out
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(*mutex);
|
||||
|
||||
const uint8_t start_index = (head_in == 0xFF) ? *head : head_in;
|
||||
|
||||
if (head_in != 0xFF) {
|
||||
*head = head_in;
|
||||
}
|
||||
|
||||
// Compute ring index: walk backwards from head as index increases
|
||||
// Compute ring index: walk backwards from start_index as index increases
|
||||
// Assumes ring buffer size is a power of two
|
||||
const size_t mask = size - 1;
|
||||
const size_t offset = static_cast<size_t>(index) & mask;
|
||||
const size_t i = (static_cast<size_t>(*head) - offset + size) & mask;
|
||||
const size_t i = (static_cast<size_t>(start_index) - offset + size) & mask;
|
||||
|
||||
// Copy the chosen entry
|
||||
memcpy(out, buffer[i], STATUS_BUFFER_SIZE);
|
||||
|
||||
// Return the head value actually used
|
||||
return static_cast<uint64_t>(*head);
|
||||
// Return the start value actually used
|
||||
return static_cast<uint64_t>(start_index);
|
||||
}
|
||||
|
||||
return error_ret;
|
||||
@@ -162,8 +295,18 @@ static bool __cdecl ac_io_mdxf_update_control_status_buffer_impl(int node, MDXFP
|
||||
// Dance Dance Revolution
|
||||
if (avs::game::is_model("MDX")) {
|
||||
// Marks this module as actively being used, allowing this function to be called from other sources
|
||||
if (!IS_MDXF_ACTIVE && source == ARKMDXP4_POLL) {
|
||||
IS_MDXF_ACTIVE = true;
|
||||
if (source == ARKMDXP4_POLL) {
|
||||
if (!IS_MDXF_ACTIVE) {
|
||||
log_debug("mdxf", "initializing mdxf I/O support");
|
||||
IS_MDXF_ACTIVE = true;
|
||||
if (acio::MDXF_BUFFER_FILL_MODE == acio::MDXFBufferFillMode::THREAD_MODE) {
|
||||
IS_THREAD_NEEDED = true;
|
||||
mdxf_thread_start();
|
||||
}
|
||||
}
|
||||
if (acio::MDXF_BUFFER_FILL_MODE == acio::MDXFBufferFillMode::AUTO_MODE) {
|
||||
count_calls_from_game();
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t (*buffer)[STATUS_BUFFER_SIZE];
|
||||
@@ -226,6 +369,8 @@ static bool __cdecl ac_io_mdxf_update_control_status_buffer_impl(int node, MDXFP
|
||||
}
|
||||
|
||||
uint16_t current_state;
|
||||
|
||||
// Only call getState() if called externally when actual input events happen, otherwise use previous known state
|
||||
if (source == EXTERNAL_POLL) {
|
||||
// get buttons
|
||||
auto &buttons = games::ddr::get_buttons();
|
||||
@@ -276,8 +421,8 @@ static bool __cdecl ac_io_mdxf_update_control_status_buffer_impl(int node, MDXFP
|
||||
start_time = min_time;
|
||||
}
|
||||
|
||||
// Only write one entry if called externally
|
||||
if (source == EXTERNAL_POLL) {
|
||||
// Don't backfill entries if called externally or if a separate thread is being used to fill auxiliary entries
|
||||
if (source == EXTERNAL_POLL || IS_THREAD_NEEDED) {
|
||||
start_time = stop_time - 1;
|
||||
}
|
||||
|
||||
@@ -323,11 +468,12 @@ static bool __cdecl ac_io_mdxf_update_control_status_buffer(int node) {
|
||||
}
|
||||
|
||||
// Used for triggering updates of the controller states from outside arkmdxp4.dll main refresh loop (i.e. within rawinput.cpp on controller events)
|
||||
void mdxf_poll() {
|
||||
void mdxf_poll(bool isExternal) {
|
||||
if (IS_MDXF_ACTIVE) {
|
||||
const MDXFPollSource source = isExternal ? EXTERNAL_POLL : INTERNAL_POLL;
|
||||
const uint64_t call_time_ms = arkGetTickTime64();
|
||||
ac_io_mdxf_update_control_status_buffer_impl(17, EXTERNAL_POLL, call_time_ms);
|
||||
ac_io_mdxf_update_control_status_buffer_impl(18, EXTERNAL_POLL, call_time_ms);
|
||||
ac_io_mdxf_update_control_status_buffer_impl(17, source, call_time_ms);
|
||||
ac_io_mdxf_update_control_status_buffer_impl(18, source, call_time_ms);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -348,4 +494,10 @@ void acio::MDXFModule::attach() {
|
||||
ACIO_MODULE_HOOK(ac_io_mdxf_get_control_status_buffer);
|
||||
ACIO_MODULE_HOOK(ac_io_mdxf_set_output_level);
|
||||
ACIO_MODULE_HOOK(ac_io_mdxf_update_control_status_buffer);
|
||||
}
|
||||
|
||||
acio::MDXFModule::~MDXFModule() {
|
||||
if (IS_THREAD_NEEDED) {
|
||||
mdxf_thread_stop();
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,24 @@
|
||||
|
||||
namespace acio {
|
||||
|
||||
enum class MDXFBufferFillMode {
|
||||
// backfill mode, but if <120Hz, enable poll thread
|
||||
AUTO_MODE,
|
||||
|
||||
// forces poll thread
|
||||
THREAD_MODE,
|
||||
|
||||
// forces backfill mode (no poll thread)
|
||||
BACKFILL_MODE
|
||||
};
|
||||
|
||||
extern MDXFBufferFillMode MDXF_BUFFER_FILL_MODE;
|
||||
|
||||
class MDXFModule : public ACIOModule {
|
||||
public:
|
||||
MDXFModule(HMODULE module, HookMode hookMode);
|
||||
|
||||
virtual void attach() override;
|
||||
~MDXFModule() override;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
#pragma once
|
||||
|
||||
// Called from rawinput thread whenever inputs have just been updated
|
||||
void mdxf_poll();
|
||||
void mdxf_poll(bool isExternal);
|
||||
@@ -84,7 +84,7 @@ static bool __cdecl ac_io_panb_start_auto_input() {
|
||||
return true;
|
||||
}
|
||||
|
||||
static uint8_t panb_get_button_velocity(Button button, Button button_soft, Button button_medium, Button button_hard) {
|
||||
static uint8_t panb_get_button_velocity(Button& button, Button& button_soft, Button& button_medium, Button& button_hard) {
|
||||
const auto velocity = Buttons::getVelocity(RI_MGR, button);
|
||||
const auto velocity_soft = Buttons::getVelocity(RI_MGR, button_soft);
|
||||
const auto velocity_medium = Buttons::getVelocity(RI_MGR, button_medium);
|
||||
|
||||
@@ -95,12 +95,19 @@ namespace acio2emu {
|
||||
break;
|
||||
|
||||
default:
|
||||
log_fatal("acio2emu", "cannot set step: unknown value: {}", s);
|
||||
log_fatal(
|
||||
"acio2emu",
|
||||
"cannot set step: unknown value: {}",
|
||||
static_cast<uint32_t>(s));
|
||||
break;
|
||||
}
|
||||
|
||||
if (!valid) {
|
||||
log_fatal("acio2emu", "illegal transition detected: {} -> {}", step_, s);
|
||||
log_fatal(
|
||||
"acio2emu",
|
||||
"illegal transition detected: {} -> {}",
|
||||
static_cast<uint32_t>(step_),
|
||||
static_cast<uint32_t>(s));
|
||||
}
|
||||
#endif
|
||||
step_ = s;
|
||||
|
||||
@@ -1108,6 +1108,8 @@ namespace avs {
|
||||
|
||||
// SOUND VOLTEX
|
||||
{"soundvoltex.dll", 0x10000000},
|
||||
// GITADORA
|
||||
{"gdxg.dll", 0x2000000},
|
||||
#endif
|
||||
// jubeat
|
||||
{"jubeat.dll", 0x2000000},
|
||||
|
||||
@@ -494,7 +494,9 @@ namespace avs {
|
||||
std::ostringstream init_code;
|
||||
init_code << EA3_MODEL;
|
||||
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 << "B";
|
||||
} else {
|
||||
@@ -529,7 +531,7 @@ namespace avs {
|
||||
auto app_param = avs::core::property_search_safe(app_config, nullptr, "/param");
|
||||
|
||||
// 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)) {
|
||||
log_fatal("avs-ea3", "entry init failed :(");
|
||||
}
|
||||
|
||||
@@ -253,28 +253,25 @@ namespace games::ddr {
|
||||
if(((dwIoControlCode>>16)&0xFFFF) == FILE_DEVICE_UNKNOWN) {
|
||||
switch((dwIoControlCode>>2)&0x3FFF) {
|
||||
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);
|
||||
auto controls = (uint32_t*) lpOutBuffer;
|
||||
|
||||
const std::pair<size_t, uint8_t> buttonMapping[] =
|
||||
{
|
||||
// Panel input comes from MDXF Control Status Buffer functions
|
||||
{Buttons::SERVICE, 25}, //Good
|
||||
{Buttons::TEST, 28}, //Good
|
||||
{Buttons::COIN_MECH, 24}, //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_DOWN, 2}, //Good
|
||||
{Buttons::P1_MENU_LEFT, 3}, //Good
|
||||
{Buttons::P1_MENU_RIGHT, 4}, //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_DOWN, 10}, //Good
|
||||
{Buttons::P2_MENU_LEFT, 11}, //Good
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
namespace games::gitadora {
|
||||
void bi2x_hook_init();
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include "acioemu/acioemu.h"
|
||||
|
||||
namespace games::gitadora {
|
||||
class GitadoraDevice : public acioemu::ACIODeviceEmu {
|
||||
public:
|
||||
bool is_ready = false;
|
||||
};
|
||||
}
|
||||
@@ -1,14 +1,16 @@
|
||||
#include "gitadora.h"
|
||||
|
||||
#include "handle.h"
|
||||
#include "bi2x_hook.h"
|
||||
#include <unordered_map>
|
||||
|
||||
#include "cfg/configurator.h"
|
||||
#include "hooks/audio/mme.h"
|
||||
#include "hooks/graphics/graphics.h"
|
||||
#include "util/cpuutils.h"
|
||||
#include "util/detour.h"
|
||||
#include "util/libutils.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/sigscan.h"
|
||||
#include "hooks/setupapihook.h"
|
||||
|
||||
namespace games::gitadora {
|
||||
|
||||
@@ -16,6 +18,32 @@ namespace games::gitadora {
|
||||
bool TWOCHANNEL = false;
|
||||
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.
|
||||
* We don't want this so we patch it out.
|
||||
@@ -214,6 +242,37 @@ namespace games::gitadora {
|
||||
system_module, "sys_debug_dip_set_param"));
|
||||
|
||||
#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");
|
||||
|
||||
|
||||
@@ -21,13 +21,20 @@ namespace games::gitadora {
|
||||
static inline bool is_drum() {
|
||||
return (
|
||||
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() {
|
||||
return (
|
||||
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')
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
};
|
||||
}
|
||||
@@ -10,6 +10,7 @@ std::vector<Button> &games::gitadora::get_buttons() {
|
||||
"Service",
|
||||
"Test",
|
||||
"Coin",
|
||||
"Headphone",
|
||||
"Guitar P1 Start",
|
||||
"Guitar P1 Up",
|
||||
"Guitar P1 Down",
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace games::gitadora {
|
||||
Service,
|
||||
Test,
|
||||
Coin,
|
||||
Headphone,
|
||||
GuitarP1Start,
|
||||
GuitarP1Up,
|
||||
GuitarP1Down,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -195,11 +195,11 @@ bool games::iidx::IIDXFMSerialHandle::open(LPCWSTR lpFileName) {
|
||||
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_emu.add_device(new FMSerialDevice());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -240,7 +240,7 @@ int games::iidx::IIDXFMSerialHandle::device_io(DWORD dwIoControlCode, LPVOID lpI
|
||||
}
|
||||
|
||||
bool games::iidx::IIDXFMSerialHandle::close() {
|
||||
log_info("iidx", "Closed COM2 (FM DEVICE)");
|
||||
log_info("iidx", "Closed COM2 (FM DEVICE / bi2a)");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -120,6 +120,11 @@ namespace games::iidx {
|
||||
|
||||
static AIO_IOB2_BI2X_TDJ* __fastcall aioIob2Bi2xTDJ_Create(AIO_NMGR_IOB2 *nmgr, int a2) {
|
||||
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;
|
||||
memset(&custom_node->data, 0, sizeof(custom_node->data));
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "hooks/sleephook.h"
|
||||
#include "launcher/options.h"
|
||||
#include "touch/touch.h"
|
||||
#include "misc/nativetouchhook.h"
|
||||
#include "misc/wintouchemu.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "util/detour.h"
|
||||
@@ -78,6 +79,10 @@ namespace games::iidx {
|
||||
char IIDXIO_LED_TICKER[10] = {' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '\x00'};
|
||||
bool IIDXIO_LED_TICKER_READONLY = false;
|
||||
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] = {
|
||||
{ 19, Lights::StageLeftAvgR, Lights::StageLeftAvgG, Lights::StageLeftAvgB, "Stage Left" },
|
||||
@@ -310,13 +315,14 @@ namespace games::iidx {
|
||||
memcpy(settings2.interface_detail, interface_detail2, sizeof(interface_detail2));
|
||||
setupapihook_add(settings2);
|
||||
|
||||
// IIDX 25-27 BIO2 BI2A input device
|
||||
// IIDX 25+ BIO2 BI2A input device
|
||||
devicehook_add(new IIDXFMSerialHandle());
|
||||
}
|
||||
|
||||
// check for new I/O DLL
|
||||
auto aio = libutils::try_library("libaio.dll");
|
||||
if (aio != nullptr) {
|
||||
HAS_LIBAIO = true;
|
||||
|
||||
// check TDJ mode
|
||||
TDJ_MODE |= fileutils::text_read("C:\\000rom.txt") == "TDJ-JA";
|
||||
@@ -332,7 +338,9 @@ namespace games::iidx {
|
||||
// need to hook `avs2-core.dll` so AVS win32fs operations go through rom hook
|
||||
devicehook_init(avs::core::DLL_INSTANCE);
|
||||
|
||||
if (!NATIVE_TOUCH) {
|
||||
if (NATIVE_TOUCH) {
|
||||
nativetouchhook::hook(avs::game::DLL_INSTANCE);
|
||||
} else {
|
||||
wintouchemu::FORCE = true;
|
||||
wintouchemu::INJECT_MOUSE_AS_WM_TOUCH = true;
|
||||
wintouchemu::hook_title_ends("beatmania IIDX", "main", avs::game::DLL_INSTANCE);
|
||||
@@ -468,9 +476,8 @@ namespace games::iidx {
|
||||
}
|
||||
|
||||
// check -monitor + TDJ mode
|
||||
if (!GRAPHICS_WINDOWED &&
|
||||
options->at(launcher::Options::DisplayAdapter).is_active() &&
|
||||
TDJ_MODE) {
|
||||
if (!GRAPHICS_WINDOWED && D3D9_ADAPTER.has_value() && TDJ_MODE) {
|
||||
IIDX_TDJ_MONITOR_WARNING = true;
|
||||
log_warning(
|
||||
"iidx",
|
||||
"\n\n!!! using -monitor option with TDJ is NOT recommended due to known !!!\n"
|
||||
@@ -892,4 +899,52 @@ namespace games::iidx {
|
||||
#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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ namespace games::iidx {
|
||||
extern char IIDXIO_LED_TICKER[10];
|
||||
extern bool IIDXIO_LED_TICKER_READONLY;
|
||||
extern std::mutex IIDX_LED_TICKER_LOCK;
|
||||
extern bool IIDX_TDJ_MONITOR_WARNING;
|
||||
|
||||
constexpr int IIDX_TAPELED_TOTAL = 17;
|
||||
// data mapping
|
||||
@@ -62,4 +63,10 @@ namespace games::iidx {
|
||||
const char* get_16seg();
|
||||
bool is_tdj_fhd();
|
||||
void apply_audio_hacks();
|
||||
|
||||
enum class iidx_aio_emulation_state {
|
||||
bi2a_com2,
|
||||
bi2x_hook
|
||||
};
|
||||
void update_io_emulation_state(iidx_aio_emulation_state state);
|
||||
}
|
||||
|
||||
@@ -48,21 +48,31 @@ namespace games::sdvx {
|
||||
SdvxOverlayPosition OVERLAY_POS = SDVX_OVERLAY_BOTTOM;
|
||||
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;
|
||||
|
||||
// states
|
||||
bool SHOW_VM_MONITOR_WARNING = false;
|
||||
static HKEY real_asio_reg_handle = nullptr;
|
||||
static HKEY real_asio_device_reg_handle = nullptr;
|
||||
|
||||
static LONG WINAPI RegOpenKeyA_hook(HKEY hKey, LPCSTR lpSubKey, PHKEY phkResult) {
|
||||
if (lpSubKey != nullptr && phkResult != nullptr) {
|
||||
if (hKey == HKEY_LOCAL_MACHINE &&
|
||||
ASIO_DRIVER.has_value() &&
|
||||
_stricmp(lpSubKey, "software\\asio") == 0)
|
||||
{
|
||||
*phkResult = PARENT_ASIO_REG_HANDLE;
|
||||
if (lpSubKey != nullptr &&
|
||||
phkResult != nullptr &&
|
||||
hKey == HKEY_LOCAL_MACHINE &&
|
||||
_stricmp(lpSubKey, "software\\asio") == 0) {
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
@@ -109,7 +119,7 @@ namespace games::sdvx {
|
||||
lpSubKey != nullptr && phkResult != nullptr &&
|
||||
_stricmp(lpSubKey, "HARDWARE\\DEVICEMAP\\SERIALCOMM") == 0) {
|
||||
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);
|
||||
@@ -229,6 +239,32 @@ namespace games::sdvx {
|
||||
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);
|
||||
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) {
|
||||
@@ -295,12 +331,10 @@ namespace games::sdvx {
|
||||
}
|
||||
|
||||
#ifdef SPICE64 // SDVX5+ specific code
|
||||
bool isValkyrieCabinetMode = avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H';
|
||||
auto options = games::get_options(eamuse_get_game());
|
||||
this->VALKYRIE_MODEL = avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H';
|
||||
// check -monitor + UFC mode
|
||||
if (!GRAPHICS_WINDOWED &&
|
||||
options->at(launcher::Options::DisplayAdapter).is_active() &&
|
||||
isValkyrieCabinetMode) {
|
||||
if (!GRAPHICS_WINDOWED && D3D9_ADAPTER.has_value() && this->VALKYRIE_MODEL) {
|
||||
SHOW_VM_MONITOR_WARNING = true;
|
||||
log_warning(
|
||||
"sdvx",
|
||||
"\n\n!!! using -monitor option with VM mode is NOT recommended due to !!!\n"
|
||||
@@ -324,16 +358,19 @@ namespace games::sdvx {
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef SPICE64
|
||||
this->detect_sound_output_device();
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
void SDVXGame::attach() {
|
||||
Game::attach();
|
||||
|
||||
#ifdef SPICE64 // SDVX5+ specific code
|
||||
bool isValkyrieCabinetMode = avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H';
|
||||
|
||||
// LCD handle
|
||||
if (!isValkyrieCabinetMode) {
|
||||
if (!this->VALKYRIE_MODEL) {
|
||||
devicehook_init();
|
||||
devicehook_add(new games::shared::LCDHandle());
|
||||
}
|
||||
@@ -372,8 +409,7 @@ namespace games::sdvx {
|
||||
nvapi_hook::initialize(avs::game::DLL_INSTANCE);
|
||||
}
|
||||
|
||||
// check for Valkyrie cabinet mode
|
||||
if (isValkyrieCabinetMode) {
|
||||
if (this->VALKYRIE_MODEL) {
|
||||
// hook touch window
|
||||
// in windowed mode, game can accept mouse input on the second screen
|
||||
if (!NATIVETOUCH && !GRAPHICS_WINDOWED) {
|
||||
|
||||
@@ -23,6 +23,9 @@ namespace games::sdvx {
|
||||
extern bool BI2X_INITIALIZED;
|
||||
extern SdvxOverlayPosition OVERLAY_POS;
|
||||
|
||||
// states
|
||||
extern bool SHOW_VM_MONITOR_WARNING;
|
||||
|
||||
class SDVXGame : public games::Game {
|
||||
public:
|
||||
SDVXGame();
|
||||
@@ -30,5 +33,12 @@ namespace games::sdvx {
|
||||
virtual void attach() override;
|
||||
virtual void post_attach() 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/backends/mmdevice/audio_endpoint_volume.h"
|
||||
#include "hooks/audio/backends/wasapi/audio_client.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
#define PRINT_FAILED_RESULT(name, ret) \
|
||||
do { \
|
||||
@@ -56,6 +57,7 @@ ULONG STDMETHODCALLTYPE WrappedIMMDevice::Release() {
|
||||
ULONG refs = pReal != nullptr ? pReal->Release() : 0;
|
||||
|
||||
if (refs == 0) {
|
||||
log_misc("audio::mmdevice", "WrappedIMMDevice::Release");
|
||||
delete this;
|
||||
}
|
||||
|
||||
@@ -69,7 +71,7 @@ HRESULT STDMETHODCALLTYPE WrappedIMMDevice::Activate(
|
||||
PROPVARIANT *pActivationParams,
|
||||
void **ppInterface)
|
||||
{
|
||||
log_misc("audio::mmdevice", "WrappedIMMDevice::Activate");
|
||||
log_misc("audio::mmdevice", "WrappedIMMDevice::Activate {}", guid2s(iid));
|
||||
|
||||
// call original
|
||||
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_collection.h"
|
||||
#include "device.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
@@ -42,7 +43,11 @@ HRESULT STDMETHODCALLTYPE WrappedIMMDeviceEnumerator::EnumAudioEndpoints(
|
||||
DWORD dwStateMask,
|
||||
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(
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
namespace hooks::audio::mme {
|
||||
void init(HINSTANCE module);
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
#include "avshook.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <mutex>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <external/robin_hood.h>
|
||||
|
||||
#include "avs/core.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::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 {
|
||||
bool DISABLE_VFS_DRIVE_REDIRECTION = false;
|
||||
bool DISABLE_CACHE = false;
|
||||
bool LOG = false;
|
||||
};
|
||||
|
||||
@@ -79,6 +86,74 @@ static bool is_spam_file(const char *file) {
|
||||
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) {
|
||||
if (is_fake_fd(fd) && ROM_FILE_CONTENTS) {
|
||||
if (st) {
|
||||
@@ -104,7 +179,12 @@ static int avs_fs_lstat(const char *name, struct avs::core::avs_stat *st) {
|
||||
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)) {
|
||||
WRAP_DEBUG_FMT("name: {}", name);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
namespace hooks::avs {
|
||||
namespace config {
|
||||
extern bool DISABLE_VFS_DRIVE_REDIRECTION;
|
||||
extern bool DISABLE_CACHE;
|
||||
extern bool LOG;
|
||||
};
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "games/gitadora/gitadora.h"
|
||||
#include "util/detour.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();
|
||||
}
|
||||
|
||||
// gitadora arena model needs this, or else
|
||||
// the game will keep spamming 0xAA
|
||||
if (games::gitadora::is_arena_model() && lpErrors) {
|
||||
*lpErrors = 0;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "cfg/icon.h"
|
||||
#include "cfg/screen_resize.h"
|
||||
#include "games/ddr/ddr.h"
|
||||
#include "games/gitadora/gitadora.h"
|
||||
#include "games/iidx/iidx.h"
|
||||
#include "hooks/graphics/backends/d3d9/d3d9_backend.h"
|
||||
#include "launcher/shutdown.h"
|
||||
@@ -33,6 +34,7 @@ struct CaptureData {
|
||||
|
||||
HWND TDJ_SUBSCREEN_WINDOW = nullptr;
|
||||
HWND SDVX_SUBSCREEN_WINDOW = nullptr;
|
||||
HWND GFDM_SUBSCREEN_WINDOW = nullptr;
|
||||
|
||||
// icon
|
||||
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_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
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -417,6 +424,11 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
|
||||
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);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@ extern bool GRAPHICS_WSUB_BORDERLESS;
|
||||
extern bool GRAPHICS_WSUB_ALWAYS_ON_TOP;
|
||||
extern HWND TDJ_SUBSCREEN_WINDOW;
|
||||
extern HWND SDVX_SUBSCREEN_WINDOW;
|
||||
extern HWND GFDM_SUBSCREEN_WINDOW;
|
||||
|
||||
extern bool SUBSCREEN_FORCE_REDRAW;
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "games/iidx/iidx.h"
|
||||
#include "games/gitadora/gitadora.h"
|
||||
#include "util/detour.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/utils.h"
|
||||
@@ -24,6 +25,7 @@ static decltype(GetLocaleInfoEx) *GetLocaleInfoEx_orig = nullptr;
|
||||
#ifdef SPICE64
|
||||
static decltype(GetSystemDefaultLCID) *GetSystemDefaultLCID_orig = nullptr;
|
||||
static decltype(IsDBCSLeadByte) *IsDBCSLeadByte_orig = nullptr;
|
||||
static decltype(WideCharToMultiByte) *WideCharToMultiByte_orig = nullptr;
|
||||
#endif
|
||||
|
||||
static NTSTATUS NTAPI RtlMultiByteToUnicodeN_hook(
|
||||
@@ -173,6 +175,41 @@ static BOOL WINAPI IsDBCSLeadByte_hook (
|
||||
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
|
||||
|
||||
void hooks::lang::early_init() {
|
||||
@@ -213,6 +250,15 @@ void hooks::lang::early_init() {
|
||||
IsDBCSLeadByte_hook,
|
||||
&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
|
||||
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "api/modules/capture.h"
|
||||
#include "acio/acio.h"
|
||||
#include "acio/icca/icca.h"
|
||||
#include "acio/mdxf/mdxf.h"
|
||||
#include "api/controller.h"
|
||||
#include "avs/automap.h"
|
||||
#include "avs/core.h"
|
||||
@@ -87,6 +88,7 @@
|
||||
#include "misc/device.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "misc/extdev.h"
|
||||
#include "misc/ami2000.h"
|
||||
#include "misc/sciunit.h"
|
||||
#include "misc/sde.h"
|
||||
#include "misc/wintouchemu.h"
|
||||
@@ -194,6 +196,7 @@ int main_implementation(int argc, char *argv[]) {
|
||||
bool attach_icca = false;
|
||||
bool attach_device = false;
|
||||
bool attach_extdev = false;
|
||||
bool attach_ami2000 = false;
|
||||
bool attach_sciunit = false;
|
||||
bool attach_cpusbxpkm_printer = false;
|
||||
bool attach_iidx = false;
|
||||
@@ -343,8 +346,13 @@ int main_implementation(int argc, char *argv[]) {
|
||||
GRAPHICS_FORCE_SINGLE_ADAPTER = 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();
|
||||
|
||||
// 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()) {
|
||||
GRAPHICS_CAPTURE_CURSOR = true;
|
||||
@@ -422,8 +430,8 @@ int main_implementation(int argc, char *argv[]) {
|
||||
if (options[launcher::Options::EnableEXTDEVModule].value_bool()) {
|
||||
attach_extdev = true;
|
||||
}
|
||||
if (options[launcher::Options::EnableSCIUNITModule].value_bool()) {
|
||||
attach_sciunit = true;
|
||||
if (options[launcher::Options::EnableAMI2000Module].value_bool()) {
|
||||
attach_ami2000 = true;
|
||||
}
|
||||
if (options[launcher::Options::EnableDevicePassthrough].value_bool()) {
|
||||
hooks::device::ENABLE = false;
|
||||
@@ -522,6 +530,9 @@ int main_implementation(int argc, char *argv[]) {
|
||||
overlay::windows::IIDX_SEGMENT_LOCATION =
|
||||
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()) {
|
||||
games::iidx::DISABLE_ESPEC_IO = true;
|
||||
}
|
||||
@@ -743,6 +754,9 @@ int main_implementation(int argc, char *argv[]) {
|
||||
if (options[launcher::Options::DisableAvsVfsDriveMountRedirection].is_active()) {
|
||||
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()) {
|
||||
cfg::SCREEN_RESIZE_CFG_PATH_OVERRIDE =
|
||||
options[launcher::Options::ScreenResizeConfigPath].value_text();
|
||||
@@ -1149,6 +1163,13 @@ int main_implementation(int argc, char *argv[]) {
|
||||
rawinput::RAWINPUT_REQUIRE_FOCUS = false;
|
||||
}
|
||||
}
|
||||
if (options[launcher::Options::DDRP4IOBufferMode].is_active()) {
|
||||
if (options[launcher::Options::DDRP4IOBufferMode].value_text() == "thread") {
|
||||
acio::MDXF_BUFFER_FILL_MODE = acio::MDXFBufferFillMode::THREAD_MODE;
|
||||
} else if (options[launcher::Options::DDRP4IOBufferMode].value_text() == "backfill") {
|
||||
acio::MDXF_BUFFER_FILL_MODE = acio::MDXFBufferFillMode::BACKFILL_MODE;
|
||||
}
|
||||
}
|
||||
|
||||
if (options[launcher::Options::MidiAlgoVer].is_active()) {
|
||||
if (options[launcher::Options::MidiAlgoVer].value_text() == "legacy") {
|
||||
@@ -1621,6 +1642,7 @@ int main_implementation(int argc, char *argv[]) {
|
||||
attach_io = true;
|
||||
attach_device = true;
|
||||
attach_extdev = true;
|
||||
attach_ami2000 = true;
|
||||
attach_gitadora = true;
|
||||
break;
|
||||
}
|
||||
@@ -2183,6 +2205,11 @@ int main_implementation(int argc, char *argv[]) {
|
||||
extdev_attach();
|
||||
}
|
||||
|
||||
// ami 2000 card reader attach
|
||||
if (attach_io || attach_ami2000) {
|
||||
ami2000_attach();
|
||||
}
|
||||
|
||||
// sci unit attach
|
||||
if (attach_io || attach_sciunit) {
|
||||
sciunit_attach();
|
||||
|
||||
@@ -207,7 +207,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.name = "monitor",
|
||||
.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"
|
||||
"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",
|
||||
.type = OptionType::Integer,
|
||||
.category = "Graphics (Full Screen)",
|
||||
@@ -590,7 +590,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.category = "Game Options (Advanced)",
|
||||
},
|
||||
{
|
||||
.title = "IIDX TDJ Mode",
|
||||
.title = "IIDX TDJ Mode (Lightning Model)",
|
||||
.name = "iidxtdj",
|
||||
.desc = "Enables TDJ cabinet mode. Ensure you also set -iidxsounddevice to desired option",
|
||||
.type = OptionType::Bool,
|
||||
@@ -679,6 +679,16 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
{"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",
|
||||
.name = "sdvx",
|
||||
@@ -1311,6 +1321,13 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.type = OptionType::Bool,
|
||||
.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",
|
||||
.name = "sciunit",
|
||||
@@ -1756,6 +1773,14 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.type = OptionType::Bool,
|
||||
.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",
|
||||
.name = "pebprint",
|
||||
@@ -2291,6 +2316,26 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.setting_name = "20",
|
||||
.category = "I/O Options",
|
||||
},
|
||||
{
|
||||
// DDRP4IOBufferMode
|
||||
.title = "DDR P4IO Buffer Algorithm",
|
||||
.name = "ddrp4iobuffer",
|
||||
.desc =
|
||||
"Remember to restart after changing this value.\n\n"
|
||||
"Sets the algorithm used to populate entries to the buffer of controller polls read by the game.\n\n"
|
||||
"auto (default): thread mode if <120Hz, backfill if >=120Hz\n\n"
|
||||
"thread: starts a thread to periodically insert polls into the buffer\n\n"
|
||||
"backfill: fills the buffer on each frame with last known state info at short regular intervals up to the current time, then writes the current state.\n\n"
|
||||
"Only has an effect when emulating P4IO (arkmdxp4.dll)",
|
||||
.type = OptionType::Enum,
|
||||
.game_name = "Dance Dance Revolution",
|
||||
.category = "Game Options (Advanced)",
|
||||
.elements = {
|
||||
{"auto", ""},
|
||||
{"thread", ""},
|
||||
{"backfill", ""},
|
||||
},
|
||||
},
|
||||
{
|
||||
// InputRequiresFocus
|
||||
.title = "Input Requires Focus",
|
||||
|
||||
@@ -71,6 +71,7 @@ namespace launcher {
|
||||
spice2x_IIDXLEDFontSize,
|
||||
spice2x_IIDXLEDColor,
|
||||
spice2x_IIDXLEDPos,
|
||||
IIDXLEDBorderless,
|
||||
LoadSoundVoltexModule,
|
||||
SDVXForce720p,
|
||||
SDVXPrinterEmulation,
|
||||
@@ -146,6 +147,7 @@ namespace launcher {
|
||||
EnableICCAModule,
|
||||
EnableDEVICEModule,
|
||||
EnableEXTDEVModule,
|
||||
EnableAMI2000Module,
|
||||
EnableSCIUNITModule,
|
||||
EnableDevicePassthrough,
|
||||
ForceWinTouch,
|
||||
@@ -195,6 +197,7 @@ namespace launcher {
|
||||
DisableSignalHandling,
|
||||
DisableDebugHooks,
|
||||
DisableAvsVfsDriveMountRedirection,
|
||||
DisableAvsCache,
|
||||
OutputPEB,
|
||||
DumpSystemInfo,
|
||||
QKSArgs,
|
||||
@@ -241,6 +244,7 @@ namespace launcher {
|
||||
IIDXRecDisable,
|
||||
MidiAlgoVer,
|
||||
MidiNoteSustain,
|
||||
DDRP4IOBufferMode,
|
||||
InputRequiresFocus,
|
||||
NostalgiaPoke,
|
||||
ForceBackBufferCount,
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
|
||||
void ami2000_attach();
|
||||
void ami2000_detach();
|
||||
@@ -0,0 +1,91 @@
|
||||
// enable touch functions - set version to windows 7
|
||||
// mingw otherwise doesn't load touch stuff
|
||||
#define _WIN32_WINNT 0x0601
|
||||
|
||||
#include "wintouchemu.h"
|
||||
|
||||
#include "util/detour.h"
|
||||
#include "util/logging.h"
|
||||
|
||||
#define TOUCH_SIMULATE_FAT_FINGERS 0
|
||||
#define TOUCH_DEBUG_VERBOSE 0
|
||||
|
||||
#if TOUCH_DEBUG_VERBOSE
|
||||
#define log_debug(module, format_str, ...) logger::push( \
|
||||
LOG_FORMAT("M", module, format_str, ## __VA_ARGS__), logger::Style::GREY)
|
||||
#else
|
||||
#define log_debug(module, format_str, ...)
|
||||
#endif
|
||||
|
||||
namespace nativetouchhook {
|
||||
|
||||
static decltype(GetTouchInputInfo) *GetTouchInputInfo_orig = nullptr;
|
||||
|
||||
static void strip_contact_size(PTOUCHINPUT point) {
|
||||
|
||||
#if TOUCH_SIMULATE_FAT_FINGERS
|
||||
point->dwMask |= 0x004;
|
||||
point->cxContact = 80 * 100;
|
||||
point->cyContact = 60 * 100;
|
||||
#endif
|
||||
|
||||
// most monitors do not set TOUCHEVENTFMASK_CONTACTAREA, but for
|
||||
// monitors that do set it, IIDX can get very confused (SDVX is not
|
||||
// affected)
|
||||
//
|
||||
// while the test menu and the touch "glow" seem to work properly,
|
||||
// interacting with subscreen menu items or entering PIN becomes
|
||||
// very unpredictable
|
||||
//
|
||||
// to fix this, simply remove the contact area width and height
|
||||
//
|
||||
// note: test menu > I/O > touch test gives 5 numbers:
|
||||
// n: x, y, w, h
|
||||
// where
|
||||
// n is the nth touch input since boot
|
||||
// x, y are coordinates (center of finger)
|
||||
// w, h are contact width and height
|
||||
//
|
||||
// when TOUCHEVENTFMASK_CONTACTAREA is not set, w/h will
|
||||
// automatically be seen as 1x1, which works perfectly fine
|
||||
|
||||
log_debug(
|
||||
"touch::native",
|
||||
"[{}, {}] dwMask = 0x{:x}, cxContact = {}, cyContact = {}",
|
||||
point->x / 100,
|
||||
point->y / 100,
|
||||
point->dwMask,
|
||||
point->cxContact,
|
||||
point->cyContact);
|
||||
|
||||
point->dwMask &= ~(0x004ul); // clear TOUCHEVENTFMASK_CONTACTAREA
|
||||
point->cxContact = 0;
|
||||
point->cyContact = 0;
|
||||
}
|
||||
|
||||
static BOOL WINAPI GetTouchInputInfoHook(
|
||||
HTOUCHINPUT hTouchInput, UINT cInputs, PTOUCHINPUT pInputs, int cbSize) {
|
||||
|
||||
// call the original fist
|
||||
const auto result = GetTouchInputInfo_orig(hTouchInput, cInputs, pInputs, cbSize);
|
||||
if (result == 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < cInputs; i++) {
|
||||
PTOUCHINPUT point = &pInputs[i];
|
||||
strip_contact_size(point);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void hook(HMODULE module) {
|
||||
GetTouchInputInfo_orig = detour::iat_try("GetTouchInputInfo", GetTouchInputInfoHook, module);
|
||||
if (GetTouchInputInfo_orig != nullptr) {
|
||||
log_misc("touch::native", "GetTouchInputInfo hooked");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
#include <windows.h>
|
||||
|
||||
namespace nativetouchhook {
|
||||
void hook(HMODULE module);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include "exitprompt.h"
|
||||
#include "avs/game.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "util/logging.h"
|
||||
#include "games/iidx/iidx.h"
|
||||
|
||||
namespace overlay::windows {
|
||||
|
||||
@@ -55,7 +57,21 @@ namespace overlay::windows {
|
||||
}
|
||||
ImGui::Spacing();
|
||||
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");
|
||||
build_button(this->overlay->window_camera, "Camera control", size, NextItem::NEW_LINE);
|
||||
|
||||
@@ -10,12 +10,13 @@ namespace overlay::windows {
|
||||
uint32_t IIDX_SEGMENT_FONT_SIZE = 64;
|
||||
std::optional<uint32_t> IIDX_SEGMENT_FONT_COLOR = std::nullopt;
|
||||
std::string IIDX_SEGMENT_LOCATION = "bottom";
|
||||
bool IIDX_SEGMENT_BORDERLESS = false;
|
||||
|
||||
static const size_t TICKER_SIZE = 9;
|
||||
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 int PADDING_Y = 8;
|
||||
static const int PADDING_X = 4;
|
||||
static const float PADDING_Y = 8.f;
|
||||
static const float PADDING_X = 4.f;
|
||||
|
||||
static const std::map<char, std::pair<char, char>> CHARMAP = {\
|
||||
// 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");
|
||||
}
|
||||
|
||||
this->title = "IIDX LED Segment Display";
|
||||
this->title = "IIDX LDJ LED Ticker";
|
||||
this->toggle_button = games::OverlayButtons::ToggleSubScreen;
|
||||
this->remove_window_padding = true;
|
||||
this->size_max = ImVec2(ImGui::GetIO().DisplaySize.x, ImGui::GetIO().DisplaySize.y);
|
||||
this->flags = ImGuiWindowFlags_NoTitleBar
|
||||
| ImGuiWindowFlags_NoScrollbar
|
||||
this->flags = ImGuiWindowFlags_NoScrollbar
|
||||
| ImGuiWindowFlags_NoResize
|
||||
| ImGuiWindowFlags_NoNavFocus
|
||||
| ImGuiWindowFlags_NoNavInputs
|
||||
| ImGuiWindowFlags_NoDocking;
|
||||
if (IIDX_SEGMENT_BORDERLESS) {
|
||||
this->flags |= ImGuiWindowFlags_NoTitleBar;
|
||||
}
|
||||
|
||||
if (IIDX_SEGMENT_FONT_COLOR.has_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() {
|
||||
// ImGui::CalcTextSize doesn't seem to work here, so manually calculate
|
||||
ImGui::PushFont(DSEG_FONT);
|
||||
this->init_size = ImGui::CalcTextSize("~.~.~.~.~.~.~.~.~.");
|
||||
ImGui::PopFont();
|
||||
this->init_size.x += PADDING_X * 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
|
||||
if (IIDX_SEGMENT_LOCATION.find("left") != std::string::npos) {
|
||||
@@ -127,9 +139,13 @@ namespace overlay::windows {
|
||||
}
|
||||
|
||||
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"
|
||||
ImGui::SetCursorPos(pos);
|
||||
|
||||
@@ -8,12 +8,14 @@ namespace overlay::windows {
|
||||
extern uint32_t IIDX_SEGMENT_FONT_SIZE;
|
||||
extern std::optional<uint32_t> IIDX_SEGMENT_FONT_COLOR;
|
||||
extern std::string IIDX_SEGMENT_LOCATION;
|
||||
extern bool IIDX_SEGMENT_BORDERLESS;
|
||||
|
||||
class IIDXSegmentDisplay : public Window {
|
||||
public:
|
||||
IIDXSegmentDisplay(SpiceOverlay *overlay);
|
||||
void calculate_initial_window() override;
|
||||
void build_content() override;
|
||||
float get_title_bar_height();
|
||||
private:
|
||||
ImVec4 color;
|
||||
void draw_ticker(char *ticker_string);
|
||||
|
||||
@@ -9,12 +9,14 @@
|
||||
namespace overlay::windows {
|
||||
|
||||
IIDXSubScreen::IIDXSubScreen(SpiceOverlay *overlay) : GenericSubScreen(overlay) {
|
||||
this->title = "IIDX Sub Screen";
|
||||
this->title = "IIDX TDJ Subscreen";
|
||||
|
||||
if (GRAPHICS_IIDX_WSUB) {
|
||||
this->disabled_message =
|
||||
"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.";
|
||||
} 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;
|
||||
|
||||
@@ -8,11 +8,13 @@
|
||||
namespace overlay::windows {
|
||||
|
||||
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';
|
||||
if (!isValkyrieCabinetMode) {
|
||||
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) {
|
||||
if (GRAPHICS_PREVENT_SECONDARY_WINDOW) {
|
||||
this->disabled_message = "Subscreen has been disabled by the user (-sdvxnosub).";
|
||||
|
||||
@@ -1850,7 +1850,7 @@ LRESULT CALLBACK rawinput::RawInputManager::input_wnd_proc(
|
||||
}
|
||||
|
||||
// update controller state ring buffers (DDR/MDXF)
|
||||
mdxf_poll();
|
||||
mdxf_poll(true);
|
||||
|
||||
// call the default window handler for cleanup
|
||||
DefWindowProc(hWnd, msg, wparam, lParam);
|
||||
@@ -2835,4 +2835,4 @@ void rawinput::RawInputManager::remove_callback_midi(void * data, const std::fun
|
||||
[data, callback](MidiCallback const &cb) {
|
||||
return cb.data == data && cb.f.target<void>() == callback.target<void>();
|
||||
}), this->callback_midi.end());
|
||||
}
|
||||
}
|
||||
@@ -241,7 +241,9 @@ static LRESULT CALLBACK SpiceTouchWndProc(HWND hWnd, UINT msg, WPARAM wParam, LP
|
||||
}
|
||||
|
||||
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 (is_windowed_sub) {
|
||||
|
||||
@@ -12,7 +12,8 @@ namespace deferredlogs {
|
||||
" this crash is most likely related to audio init failure",
|
||||
" * check if the default audio device has changed",
|
||||
" * 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;
|
||||
|
||||
@@ -350,4 +350,23 @@ static inline std::string wchar_to_u8(const wchar_t* wstr) {
|
||||
std::string out(size - 1, 0);
|
||||
WideCharToMultiByte(CP_UTF8, 0, wstr, -1, out.data(), size - 1, nullptr, nullptr);
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user