Update to spice2x-25-10-07 (pre-apply)

> broken commit
This commit is contained in:
[ ]
2025-11-11 04:57:26 +09:00
parent 359716b850
commit 652ad41845
97 changed files with 3164 additions and 1275 deletions
+1 -1
View File
@@ -14,7 +14,7 @@ namespace games::ccj {
bool MOUSE_TRACKBALL_USE_TOGGLE = false;
uint8_t TRACKBALL_SENSITIVITY = 10;
static HANDLE fakeHandle = (HANDLE)0xDEADBEEF;
static HANDLE fakeHandle = (HANDLE)0xDEADBEEFull;
static HWND hWnd = nullptr;
static WNDPROC wndProc = nullptr;
static std::thread *tbThread = nullptr;
+82
View File
@@ -27,6 +27,7 @@ namespace games::ddr {
// settings
bool SDMODE = false;
bool NO_CODEC_REGISTRATION = false;
uint8_t DDR_TAPELEDS[TAPELED_DEVICE_COUNT][50][3] {};
@@ -49,9 +50,79 @@ namespace games::ddr {
return SendMessage_real(hWnd, Msg, wParam, lParam);
}
bool contains_only_ascii(const std::string& str) {
for (auto c: str) {
if (static_cast<unsigned char>(c) > 127) {
return false;
}
}
return true;
}
DDRGame::DDRGame() : Game("Dance Dance Revolution") {
}
void DDRGame::register_codecs() {
// find where spice.exe / spice64.exe is located
const auto &spice_bin_path = libutils::module_file_name(nullptr).parent_path();
// find the com directory
std::filesystem::path dir = "";
if (MODULE_PATH == spice_bin_path) {
// try: \com
dir = spice_bin_path / "com";
} else {
// try: modules\..\com
dir = MODULE_PATH / ".." / "com";
}
if (fileutils::dir_exists(dir)) {
log_info("ddr", "looking for codecs in this directory: {}", dir.string());
} else {
log_info("ddr", "codecs directory not found: {}", dir.string());
return;
}
for (const auto &file : std::filesystem::directory_iterator(dir)) {
const auto &filename = file.path().filename();
const auto extension = strtolower(filename.extension().string());
if (extension != ".dll") {
continue;
}
log_info("ddr", "found DLL: {}, size: {} bytes", filename.string(), file.file_size());
if (filename == "k-clvsd.dll" || filename.string().find("xactengine") == 0) {
const std::wstring wcmd = L"regsvr32.exe /s \"" + file.path().wstring() + L"\"";
const std::string cmd = "regsvr32.exe /s \"" + file.path().string() + "\"";
int result = 0;
std::thread t([wcmd, &result]() {
result = _wsystem(wcmd.c_str());
});
t.join();
if (result == 0) {
log_info("ddr", "`{}` returned {}", cmd, result);
} else {
log_warning("ddr", "`{}` failed, returned {}", cmd, result);
}
if (!contains_only_ascii(file.path().string())) {
log_warning(
"ddr",
"BAD PATH ERROR\n\n\n"
"!!! !!!\n"
"!!! filesystem path to codec contains non-ASCII characters! !!!\n"
"!!! this may cause the game to crash! !!!\n"
"!!! !!!\n"
);
}
}
}
}
void DDRGame::pre_attach() {
if (!cfg::CONFIGURATOR_STANDALONE && avs::game::is_model("TDX")) {
log_fatal(
@@ -87,6 +158,17 @@ namespace games::ddr {
"!!! !!!\n\n\n"
);
}
if (!cfg::CONFIGURATOR_STANDALONE) {
if (!NO_CODEC_REGISTRATION) {
this->register_codecs();
} else {
log_warning(
"ddr",
"skipping codec registration (-ddrnocodec), "
"game may crash if you didn't register codecs before launching the game");
}
}
}
void DDRGame::attach() {
+4
View File
@@ -7,6 +7,7 @@ namespace games::ddr {
// settings
extern bool SDMODE;
extern bool NO_CODEC_REGISTRATION;
// Buffers to store RGB data for tape LEDs on gold cabinets
const size_t TAPELED_DEVICE_COUNT = 11;
@@ -18,5 +19,8 @@ namespace games::ddr {
virtual void pre_attach() override;
virtual void attach() override;
virtual void detach() override;
private:
void register_codecs();
};
}
+10 -33
View File
@@ -409,50 +409,27 @@ namespace games::iidx {
* 16 - side panel right - 183 bytes - 61 colors
*
* data is stored in RGB order, 3 bytes per color
*
* TODO: expose this data via API
*/
// data mapping
static struct TapeLedMapping {
size_t data_size;
int index_r, index_g, index_b;
TapeLedMapping(size_t data_size, int index_r, int index_g, int index_b)
: data_size(data_size), index_r(index_r), index_g(index_g), index_b(index_b) {}
} mapping[] = {
{ 19, Lights::StageLeftAvgR, Lights::StageLeftAvgG, Lights::StageLeftAvgB },
{ 19, Lights::StageRightAvgR, Lights::StageRightAvgG, Lights::StageRightAvgB },
{ 45, Lights::CabinetLeftAvgR, Lights::CabinetLeftAvgG, Lights::CabinetLeftAvgB },
{ 45, Lights::CabinetRightAvgR, Lights::CabinetRightAvgG, Lights::CabinetRightAvgB },
{ 21, Lights::ControlPanelUnderAvgR, Lights::ControlPanelUnderAvgG, Lights::ControlPanelUnderAvgB },
{ 54, Lights::CeilingLeftAvgR, Lights::CeilingLeftAvgG, Lights::CeilingLeftAvgB },
{ 11, Lights::TitleLeftAvgR, Lights::TitleLeftAvgG, Lights::TitleLeftAvgB },
{ 11, Lights::TitleRightAvgR, Lights::TitleRightAvgG, Lights::TitleRightAvgB },
{ 54, Lights::CeilingRightAvgR, Lights::CeilingRightAvgG, Lights::CeilingRightAvgB },
{ 17, Lights::TouchPanelLeftAvgR, Lights::TouchPanelLeftAvgG, Lights::TouchPanelLeftAvgB },
{ 17, Lights::TouchPanelRightAvgR, Lights::TouchPanelRightAvgG, Lights::TouchPanelRightAvgB },
{ 68, Lights::SidePanelLeftInnerAvgR, Lights::SidePanelLeftInnerAvgG, Lights::SidePanelLeftInnerAvgB },
{ 68, Lights::SidePanelLeftOuterAvgR, Lights::SidePanelLeftOuterAvgG, Lights::SidePanelLeftOuterAvgB },
{ 61, Lights::SidePanelLeftAvgR, Lights::SidePanelLeftAvgG, Lights::SidePanelLeftAvgB },
{ 68, Lights::SidePanelRightOuterAvgR, Lights::SidePanelRightOuterAvgG, Lights::SidePanelRightOuterAvgB },
{ 68, Lights::SidePanelRightInnerAvgR, Lights::SidePanelRightInnerAvgG, Lights::SidePanelRightInnerAvgB },
{ 61, Lights::SidePanelRightAvgR, Lights::SidePanelRightAvgG, Lights::SidePanelRightAvgB },
};
// check index bounds
if (tapeledutils::is_enabled() && index < std::size(mapping)) {
auto &map = mapping[index];
if (tapeledutils::is_enabled() && index < std::size(TAPELED_MAPPING)) {
auto &map = TAPELED_MAPPING[index];
const auto data_size = map.data.capacity();
// pick a color to use
const auto rgb = tapeledutils::pick_color_from_led_tape(data, map.data_size);
const auto rgb = tapeledutils::pick_color_from_led_tape(data, data_size);
// program the lights into API
auto &lights = get_lights();
GameAPI::Lights::writeLight(RI_MGR, lights[map.index_r], rgb.r);
GameAPI::Lights::writeLight(RI_MGR, lights[map.index_g], rgb.g);
GameAPI::Lights::writeLight(RI_MGR, lights[map.index_b], rgb.b);
for (unsigned int i = 0; i < data_size; ++i) {
map.data[i].r = data[i * 3];
map.data[i].g = data[i * 3 + 1];
map.data[i].b = data[i * 3 + 2];
}
}
if (This != custom_node) {
+81 -2
View File
@@ -8,6 +8,7 @@
#include "games/iidx/legacy_camera.h"
#include "hooks/audio/audio.h"
#include "hooks/avshook.h"
#include "hooks/cfgmgr32hook.h"
#include "hooks/devicehook.h"
@@ -22,11 +23,15 @@
#include "misc/wintouchemu.h"
#include "misc/eamuse.h"
#include "util/detour.h"
#include "util/deferlog.h"
#include "util/fileutils.h"
#include "util/libutils.h"
#include "util/memutils.h"
#include "util/sigscan.h"
#include "util/utils.h"
#include "launcher/signal.h"
#include "external/robin_hood.h"
#include "bi2a.h"
#include "bi2x_hook.h"
@@ -74,6 +79,26 @@ namespace games::iidx {
bool IIDXIO_LED_TICKER_READONLY = false;
std::mutex IIDX_LED_TICKER_LOCK;
tapeledutils::tape_led TAPELED_MAPPING[IIDX_TAPELED_TOTAL] = {
{ 19, Lights::StageLeftAvgR, Lights::StageLeftAvgG, Lights::StageLeftAvgB, "Stage Left" },
{ 19, Lights::StageRightAvgR, Lights::StageRightAvgG, Lights::StageRightAvgB, "Stage Right" },
{ 45, Lights::CabinetLeftAvgR, Lights::CabinetLeftAvgG, Lights::CabinetLeftAvgB, "Cabinet Left" },
{ 45, Lights::CabinetRightAvgR, Lights::CabinetRightAvgG, Lights::CabinetRightAvgB, "Cabinet Right" },
{ 21, Lights::ControlPanelUnderAvgR, Lights::ControlPanelUnderAvgG, Lights::ControlPanelUnderAvgB, "Control Panel Under" },
{ 54, Lights::CeilingLeftAvgR, Lights::CeilingLeftAvgG, Lights::CeilingLeftAvgB, "Ceiling Left" },
{ 11, Lights::TitleLeftAvgR, Lights::TitleLeftAvgG, Lights::TitleLeftAvgB, "Title Left" },
{ 11, Lights::TitleRightAvgR, Lights::TitleRightAvgG, Lights::TitleRightAvgB, "Title Right" },
{ 54, Lights::CeilingRightAvgR, Lights::CeilingRightAvgG, Lights::CeilingRightAvgB, "Ceiling Right" },
{ 17, Lights::TouchPanelLeftAvgR, Lights::TouchPanelLeftAvgG, Lights::TouchPanelLeftAvgB, "Touch Panel Left" },
{ 17, Lights::TouchPanelRightAvgR, Lights::TouchPanelRightAvgG, Lights::TouchPanelRightAvgB, "Touch Panel Right" },
{ 68, Lights::SidePanelLeftInnerAvgR, Lights::SidePanelLeftInnerAvgG, Lights::SidePanelLeftInnerAvgB, "Side Panel Left Inner" },
{ 68, Lights::SidePanelLeftOuterAvgR, Lights::SidePanelLeftOuterAvgG, Lights::SidePanelLeftOuterAvgB, "Side Panel Left Outer" },
{ 61, Lights::SidePanelLeftAvgR, Lights::SidePanelLeftAvgG, Lights::SidePanelLeftAvgB, "Side Panel Left" },
{ 68, Lights::SidePanelRightOuterAvgR, Lights::SidePanelRightOuterAvgG, Lights::SidePanelRightOuterAvgB, "Side Panel Right Outer" },
{ 68, Lights::SidePanelRightInnerAvgR, Lights::SidePanelRightInnerAvgG, Lights::SidePanelRightInnerAvgB, "Side Panel Right Inner" },
{ 61, Lights::SidePanelRightAvgR, Lights::SidePanelRightAvgG, Lights::SidePanelRightAvgB, "Side Panel Right" },
};
static LONG WINAPI RegOpenKeyA_hook(HKEY hKey, LPCSTR lpSubKey, PHKEY phkResult) {
if (lpSubKey != nullptr && phkResult != nullptr) {
if (hKey == HKEY_LOCAL_MACHINE &&
@@ -210,6 +235,9 @@ namespace games::iidx {
} else if (data.find(" W:touch: missing trigger:") != std::string::npos) {
out.clear();
return true;
} else if (data.find("SuperstepSound: Audio device is not available") != std::string::npos) {
deferredlogs::defer_error_messages(deferredlogs::SUPERSTEP_SOUND_ERROR_MESSAGE);
return false;
} else {
return false;
}
@@ -350,7 +378,11 @@ namespace games::iidx {
}
}
apply_audio_hacks();
if (hooks::audio::ENABLED) {
apply_audio_hacks();
} else {
log_warning("iidx", "skipping audio hooks; disabled by user");
}
// ASIO device hook
RegCloseKey_orig = detour::iat_try(
@@ -373,6 +405,14 @@ namespace games::iidx {
// init cfgmgr32 hooks
cfgmgr32hook_init(avs::game::DLL_INSTANCE);
// report common errors on iidx31 and above
if (avs::game::is_ext(2023091500, MAXINT) && GRAPHICS_9_ON_12_STATE == DX9ON12_FORCE_ON) {
deferredlogs::defer_error_messages({
"common incompatibility with DX 9on12 + IIDX 31 and above",
" IIDX31+ is known to be incompatible with DX 9on12, leading to blank screen or crashes"
});
}
}
void IIDXGame::pre_attach() {
@@ -740,26 +780,30 @@ namespace games::iidx {
0);
// attempt to detect ASIO support
// <=24 : 32-bit only
// 25-26: has neither (no patch needed - WASAPI Exclusive by default)
// 27-30: has both (envvar will be respected, ASIO or WASAPI)
// 31+: only has XONAR (ASIO by default, signature patch can be used to force WASAPI - for now)
if (!has_SOUND_OUTPUT_DEVICE && !has_XONAR_SOUND_CARD) {
// iidx 25-26
log_info("iidx", "This game only uses WASAPI audio engine");
return;
}
if (has_SOUND_OUTPUT_DEVICE && has_XONAR_SOUND_CARD) {
// iidx 27-30
log_info("iidx", "This game accepts SOUND_OUTPUT_DEVICE environment variable");
return;
}
log_info("iidx", "This game supports ASIO but does not accept SOUND_OUTPUT_DEVICE environment variable");
// patch game to force wasapi
if (SOUND_OUTPUT_DEVICE_IN_EFFECT.has_value() && SOUND_OUTPUT_DEVICE_IN_EFFECT.value() == "wasapi") {
intptr_t result = replace_pattern(
avs::game::DLL_INSTANCE,
"FF5008E8????ECFF83780803740D",
"FF5008E8??????FF83780803740D",
"??????BB00000000EB169090????",
0, 0);
@@ -774,6 +818,41 @@ namespace games::iidx {
"Successfully forced WASAPI as audio engine using signature matching @ 0x{:x}.",
result);
}
} else {
log_info("iidx", "Not applying force wasapi patch; game will use ASIO");
}
// patch iidx32+ for asio compatibility
// only do this if NOT wasapi (as opposed to checking if it's asio)
// the patch is only really needed for (some) non-XONAR devices but since people sometimes disguise
// other devices as a XONAR, don't check for the exact string (common ASIO workaround for INF)
if (avs::game::is_ext(2024090100, MAXINT) &&
!(SOUND_OUTPUT_DEVICE_IN_EFFECT.has_value() && SOUND_OUTPUT_DEVICE_IN_EFFECT.value() == "wasapi")) {
// in iidx32 final:
// ff 50 08 call QWORD PTR [rax+0x8] ; ASIO instance AddRef
// 48 8b 4b 08 mov rcx,QWORD PTR [rbx+0x8]
// 48 8b 01 mov rax,QWORD PTR [rcx]
// ff 50 08 call QWORD PTR [rax+0x8] ; ASIO instance AddRef
intptr_t result = replace_pattern(
avs::game::DLL_INSTANCE,
"FF50????????????????FF50??4533C94533C0418D51",
"????????????????????909090??????????????????",
0, 0);
if (result == 0) {
log_warning(
"iidx",
"Failed to apply ASIO compatibility fix for iidx32+. "
"Unless patches are applied, your ASIO device ({}) may hang or fail to work",
ASIO_DRIVER->c_str());
} else {
log_info(
"iidx",
"Successfully applied ASIO compatibility fix for iidx32+ using signature matching @ 0x{:x}.",
result);
}
}
#endif
+8
View File
@@ -2,9 +2,13 @@
#include <mutex>
#include <optional>
#include <vector>
#include "games/game.h"
#include "external/robin_hood.h"
#include "util/tapeled.h"
namespace games::iidx {
// settings
@@ -30,6 +34,10 @@ namespace games::iidx {
extern bool IIDXIO_LED_TICKER_READONLY;
extern std::mutex IIDX_LED_TICKER_LOCK;
constexpr int IIDX_TAPELED_TOTAL = 17;
// data mapping
extern tapeledutils::tape_led TAPELED_MAPPING[IIDX_TAPELED_TOTAL];
class IIDXGame : public games::Game {
public:
IIDXGame();
+38 -15
View File
@@ -85,12 +85,19 @@ namespace games::iidx {
&m_pwszSymbolicLink,
&m_cchSymbolicLink
);
if (FAILED(hr)) { goto done; }
if (FAILED(hr)) {
log_warning("iidx:camhook", "[{}] GetAllocatedString failed with {:#x}", m_name, (ULONG)hr);
goto done;
}
log_misc("iidx:camhook", "[{}] Symlink: {}", m_name, GetSymLink());
// Create the media source object.
hr = pActivate->ActivateObject(IID_PPV_ARGS(&m_pSource));
if (FAILED(hr)) { goto done; }
if (FAILED(hr)) {
log_warning("iidx:camhook", "[{}] ActivateObject failed with {:#x}", m_name, (ULONG)hr);
goto done;
}
// Retain reference to the camera
m_pSource->AddRef();
@@ -98,13 +105,22 @@ namespace games::iidx {
// Create an attribute store to hold initialization settings.
hr = WrappedMFCreateAttributes(&pAttributes, 2);
if (FAILED(hr)) { goto done; }
if (FAILED(hr)) {
log_warning("iidx:camhook", "[{}] MFCreateAttributes failed with {:#x}", m_name, (ULONG)hr);
goto done;
}
hr = pAttributes->SetUnknown(MF_SOURCE_READER_D3D_MANAGER, pD3DManager);
if (FAILED(hr)) { goto done; }
if (FAILED(hr)) {
log_warning("iidx:camhook", "[{}] SetUnknown(MF_SOURCE_READER_D3D_MANAGER) failed with {:#x}", m_name, (ULONG)hr);
goto done;
}
hr = pAttributes->SetUINT32(MF_SOURCE_READER_DISABLE_DXVA, FALSE);
if (FAILED(hr)) { goto done; }
if (FAILED(hr)) {
log_warning("iidx:camhook", "[{}] SetUINT32(MF_SOURCE_READER_DISABLE_DXVA) failed with {:#x}", m_name, (ULONG)hr);
goto done;
}
// TODO: Color space conversion
// if (SUCCEEDED(hr)) {
@@ -120,11 +136,18 @@ namespace games::iidx {
pAttributes,
&m_pSourceReader
);
if (FAILED(hr)) { goto done; }
if (FAILED(hr)) {
log_warning("iidx:camhook", "[{}] MFCreateSourceReaderFromMediaSource failed with {:#x}", m_name, (ULONG)hr);
goto done;
}
log_misc("iidx:camhook", "[{}] Created source reader", m_name);
hr = InitTargetTexture();
if (FAILED(hr)) { goto done; }
if (FAILED(hr)) {
log_warning("iidx:camhook", "[{}] InitTargetTexture failed with {:#x}", m_name, (ULONG)hr);
goto done;
}
// Camera should be still usable even if camera control is not supported
InitCameraControl();
@@ -134,7 +157,7 @@ namespace games::iidx {
m_initialized = true;
log_misc("iidx:camhook", "[{}] Initialized", m_name);
} else {
log_warning("iidx:camhook", "[{}] Failed to create camera: {}", m_name, hr);
log_warning("iidx:camhook", "[{}] Failed to create camera: {:#x}", m_name, (ULONG)hr);
}
SafeRelease(&pAttributes);
LeaveCriticalSection(&m_critsec);
@@ -165,7 +188,7 @@ namespace games::iidx {
if (FAILED(hr)) {
if (hr != MF_E_NO_MORE_TYPES) {
log_warning("iidx:camhook", "[{}] Cannot get media type {} {}", m_name, i, hr);
log_warning("iidx:camhook", "[{}] Cannot get media type {} {:#x}", m_name, i, (ULONG)hr);
}
break;
}
@@ -584,7 +607,7 @@ namespace games::iidx {
* Return values:
* S_OK: this is a "better" media type than the existing one
* S_FALSE: valid media type, but not "better"
* E_*: invalid meia type
* E_*: invalid media type
*/
HRESULT IIDXLocalCamera::TryMediaType(IMFMediaType *pType, UINT32 *pBestWidth, double *pBestFrameRate) {
HRESULT hr = S_OK;
@@ -595,13 +618,13 @@ namespace games::iidx {
hr = pType->GetGUID(MF_MT_SUBTYPE, &subtype);
if (FAILED(hr)) {
log_warning("iidx:camhook", "[{}] Failed to get subtype: {}", m_name, hr);
log_warning("iidx:camhook", "[{}] Failed to get subtype: {:#x}", m_name, (ULONG)hr);
return hr;
}
hr = MFGetAttributeSize(pType, MF_MT_FRAME_SIZE, &width, &height);
if (FAILED(hr)) {
log_warning("iidx:camhook", "[{}] Failed to get frame size: {}", m_name, hr);
log_warning("iidx:camhook", "[{}] Failed to get frame size: {:#x}", m_name, (ULONG)hr);
return hr;
}
@@ -619,7 +642,7 @@ namespace games::iidx {
(UINT32*)&frameRate.Denominator
);
if (FAILED(hr)) {
log_warning("iidx:camhook", "[{}] Failed to get frame rate: {}", m_name, hr);
log_warning("iidx:camhook", "[{}] Failed to get frame rate: {:#x}", m_name, (ULONG)hr);
return hr;
}
double frameRateValue = frameRate.Numerator / frameRate.Denominator;
@@ -696,7 +719,7 @@ namespace games::iidx {
if (SUCCEEDED(hr)) {
log_misc("iidx:camhook", "[{}] Created texture", m_name);
} else {
log_warning("iidx:camhook", "[{}] Failed to create texture: {}", m_name, hr);
log_warning("iidx:camhook", "[{}] Failed to create texture: {:#x}", m_name, (ULONG)hr);
}
return hr;
}
@@ -785,7 +808,7 @@ namespace games::iidx {
FlushDrawCommands();
if (FAILED(hr)) {
log_warning("iidx:camhook", "Error in DrawSample {}", hr);
log_warning("iidx:camhook", "Error in DrawSample {:#x}", (ULONG)hr);
}
SafeRelease(&pCameraSurf);
LeaveCriticalSection(&m_critsec);
+2 -2
View File
@@ -266,8 +266,8 @@ namespace games::iidx::poke {
y *= rawinput::TOUCHSCREEN_RANGE_Y;
} else if (GRAPHICS_IIDX_WSUB) {
// Scale to windowed subscreen
x *= GRAPHICS_IIDX_WSUB_WIDTH;
y *= GRAPHICS_IIDX_WSUB_HEIGHT;
x *= GRAPHICS_WSUB_WIDTH;
y *= GRAPHICS_WSUB_HEIGHT;
} else if (GENERIC_SUB_WINDOW_FULLSIZE || !overlay::OVERLAY->get_active()) {
// Overlay is not present, scale to main screen
if (GRAPHICS_WINDOWED) {
+10
View File
@@ -36,6 +36,7 @@
#include "ccj/io.h"
#include "qks/io.h"
#include "mfg/io.h"
#include "pc/io.h"
namespace games {
@@ -313,6 +314,15 @@ namespace games {
buttons.insert({ mfg, mfg::get_buttons() });
buttons_help.insert({ mfg, mfg::get_buttons_help() });
file_hints[mfg].emplace_back("game/MFGClient_Data");
// Polaris Chord
const std::string pc("Polaris Chord");
games.push_back(pc);
buttons.insert({ pc, pc::get_buttons() });
buttons_help.insert({ pc, pc::get_buttons_help() });
analogs.insert({ pc, pc::get_analogs() });
lights.insert({ pc, pc::get_lights() });
file_hints[pc].emplace_back("game/svm_Data");
}
const std::vector<std::string> &get_games() {
+34 -9
View File
@@ -13,10 +13,14 @@
namespace games::loveplus {
bool CAMERA_ENABLE = false;
// touch stuff
static bool TOUCH_ENABLE = false;
static bool TOUCH_ATTACHED = false;
static std::string lp_args = "-noWatchDog -noIOError -noIrda -notarget";
void touch_update() {
// check if touch enabled
@@ -28,16 +32,15 @@ namespace games::loveplus {
if (!TOUCH_ATTACHED) {
// Find the game window.
// We check the foreground window first, then fall back to searching for the window title
// All game versions seem to have their model first in the window title
HWND wnd = GetForegroundWindow();
if (!string_begins_with(GetActiveWindowTitle(), "LovePlus")) {
wnd = FindWindowBeginsWith(avs::game::MODEL);
}
HWND wnd = FindProcessWindowBeginsWith("LovePlusAC");
// attach touch hook
if (wnd) {
log_info("loveplus", "using window handle for touch: {}", fmt::ptr(wnd));
log_info(
"loveplus",
"using window handle for touch: {} ({})",
fmt::ptr(wnd),
get_window_title(wnd));
touch_create_wnd(wnd);
} else {
log_info("loveplus", "falling back to the DirectX window handle for touch");
@@ -117,12 +120,26 @@ namespace games::loveplus {
}
static LPSTR __stdcall GetCommandLineA_hook() {
static std::string lp_args = "-win -noCamera -noWatchDog -noIOError -noIrda -notarget";
return lp_args.data();
}
static bool lp_spam_remover(void *user, const std::string &data, logger::Style style, std::string &out) {
if (data.empty() || data[0] != '[') {
return false;
}
if (data.find("> [INFO] err_code: 0, err_title: , err_str:") != std::string::npos) {
out = "";
return true;
}
return false;
}
LovePlusGame::LovePlusGame() : Game("LovePlus") {
logger::hook_add(lp_spam_remover, nullptr);
}
LovePlusGame::~LovePlusGame() {
logger::hook_remove(lp_spam_remover, this);
}
void LovePlusGame::attach() {
@@ -157,9 +174,17 @@ namespace games::loveplus {
HMODULE seteq = libutils::try_library("ad_hd_seteq_dll.dll");
detour::inline_hook((void *) SetEqualizer, libutils::try_proc(seteq, "SetEqualizer"));
// set up command line args
if (!CAMERA_ENABLE) {
lp_args += " -noCamera";
}
if (GRAPHICS_WINDOWED) {
lp_args += " -win";
}
// get command line hook
HMODULE lpac = libutils::try_library("lpac.dll");
detour::iat("GetCommandLineA", GetCommandLineA_hook, lpac);
}
void LovePlusGame::detach() {
+3
View File
@@ -4,12 +4,15 @@
namespace games::loveplus {
extern bool CAMERA_ENABLE;
// touch stuff
void touch_update();
class LovePlusGame : public games::Game {
public:
LovePlusGame();
virtual ~LovePlusGame() override;
virtual void attach() override;
virtual void detach() override;
};
+1 -1
View File
@@ -28,7 +28,7 @@ namespace games::mfg {
SetEnvironmentVariableA("VFG_CABINET_TYPE", MFG_CABINET_TYPE.c_str());
// add card reader
portName = MFG_CABINET_TYPE == "UKS" ? L"\\\\.\\COM1" : L"\\\\.\\COM3";
portName = (MFG_CABINET_TYPE == "UKS") ? std::wstring(L"\\\\.\\COM1") : std::wstring(L"\\\\.\\COM3");
acioHandle = new acioemu::ACIOHandle(portName.c_str(), 1);
devicehook_init_trampoline();
devicehook_add(acioHandle);
+1 -1
View File
@@ -94,7 +94,7 @@ namespace games::otoca {
bool kb_insert_press = false;
// eamio keypress
kb_insert_press |= eamuse_get_keypad_state(0) & (1 << EAM_IO_INSERT);
kb_insert_press |= static_cast<bool>(eamuse_get_keypad_state(0) & (1 << EAM_IO_INSERT));
// check for card
if (CARD_DATA == nullptr && (eamuse_card_insert_consume(1, 0) || kb_insert_press)) {
+739
View File
@@ -0,0 +1,739 @@
#include "bi2x_hook.h"
#include "util/execexe.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 "pc.h"
#include "util/utils.h"
namespace games::pc {
bool PC_KNOB_MODE = false;
constexpr float RELATIVE_DECAY = 0.02f;
struct FADER_RELATIVE {
// ranges from 0.f to 1.f
float previous_raw = 0.5f;
// ranges from -1.f to +1.f
float effective_value = 0.f;
};
FADER_RELATIVE fader_l_relative;
FADER_RELATIVE fader_r_relative;
float calculate_analog_raw_delta(float old_value, float new_value) {
// assumed that values are between 0.f and 1.f
float delta = 0.f;
if ((old_value < 0.25f) && (0.75f < new_value)) {
// wrapped around: going left
delta = -((1.f - new_value) + old_value);
} else if ((new_value < 0.25f) && (0.75f < old_value)) {
// wrapped around: going right
delta = (1.f - old_value) + new_value;
} else {
delta = new_value - old_value;
}
return delta;
}
float apply_decay(float val) {
// return to center over time
if (RELATIVE_DECAY < val) {
return val - RELATIVE_DECAY;
} else if (val < -RELATIVE_DECAY) {
return val + RELATIVE_DECAY;
} else {
return 0.f;
}
}
float get_fader_knob_mode_value(float raw_analog, FADER_RELATIVE &fader) {
// add new value...
const float delta = calculate_analog_raw_delta(fader.previous_raw, raw_analog);
fader.effective_value += (delta * 20.f);
fader.previous_raw = raw_analog;
// capture the result after adding new input value
fader.effective_value = CLAMP(fader.effective_value, -1.f, 1.f);
const float result = fader.effective_value;
// post-process for next iteration: apply decay (return to center over time)
fader.effective_value = apply_decay(fader.effective_value);
return result;
}
/*
* class definitions
*/
struct AIO_SCI_COMM {
};
struct AIO_NMGR_IOB2 {
};
struct AIO_IOB2_BI2X_AC1 {
};
struct AIO_IOB2_BI2X_AC1__SETTING {
};
struct AIO_IOB2_BI2X_WRFIRM {
};
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];
};
/*
* 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_SetTapeLedDataGroup_t)(AIO_IOB2_BI2X_AC1* i_pNodeCtl, uint32_t i_bfGroup);
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 AIO_SCI_COMM* (__fastcall *aioIob2Bi2x_OpenSciUsbCdc_t)(uint32_t i_SerialNumber);
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);
typedef bool (__fastcall *aioIob2Bi2x_WriteFirmIsError_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);
// libaio.dll
typedef void (__fastcall *aioSci_Destroy_t)(AIO_SCI_COMM *i_pNodeMgr);
typedef void (__fastcall *aioNodeMgr_Destroy_t)(AIO_NMGR_IOB2 *i_pNodeMgr);
typedef int32_t (__fastcall *aioNodeMgr_GetState_t)(AIO_NMGR_IOB2 *i_pNodeMgr);
typedef bool (__fastcall *aioNodeMgr_IsReady_t)(AIO_NMGR_IOB2 *i_pNodeMgr, int32_t i_State);
typedef bool (__fastcall *aioNodeMgr_IsError_t)(AIO_NMGR_IOB2 *i_pNodeMgr, int32_t i_State);
typedef void (__fastcall *aioNodeCtl_Destroy_t)(AIO_IOB2_BI2X_AC1 *i_pNodeCtl);
typedef int32_t (__fastcall *aioNodeCtl_GetState_t)(AIO_IOB2_BI2X_AC1 *i_pNodeCtl);
typedef bool (__fastcall *aioNodeCtl_IsReady_t)(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, int32_t i_State);
typedef bool (__fastcall *aioNodeCtl_IsError_t)(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, int32_t i_State);
/*
* 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_SetTapeLedDataGroup_t aioIob2Bi2x_SetTapeLedDataGroup_orig = nullptr;
static aioIob2Bi2x_SetTapeLedDataLimit_t aioIob2Bi2x_SetTapeLedDataLimit_orig = nullptr;
static aioIob2Bi2x_OpenSciUsbCdc_t aioIob2Bi2x_OpenSciUsbCdc_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;
static aioIob2Bi2x_WriteFirmIsError_t aioIob2Bi2x_WriteFirmIsError_orig = nullptr;
// libaio-iob.dll
static aioNMgrIob2_Create_t aioNMgrIob2_Create_orig = nullptr;
static aioNMgrIob_BeginManage_t aioNMgrIob_BeginManage_orig = nullptr;
// libaio.dll
static aioSci_Destroy_t aioSci_Destroy_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;
/*
* variables
*/
static AIO_IOB2_BI2X_AC1 *aioIob2Bi2xAc1;
static AIO_SCI_COMM *aioSciComm;
static AIO_NMGR_IOB2 *aioNmgrIob2;
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) {
log_info("bi2x_hook", "aioIob2Bi2xAC1_Create hook hit");
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();
o_DevStatus->Input.CN8_8 = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Test]) ? 0 : 0xFF;
o_DevStatus->Input.CN8_9 = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Service]) ? 0 : 0xFF;
o_DevStatus->Input.CN8_10 = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::CoinMech]) ? 0 : 0xFF;
o_DevStatus->Input.CN15_10 = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Headphone]) ? 0 : 0xFF;
o_DevStatus->Input.CN15_12 = 0xFF; // Recorder off; present in I/O test menu but unused in game
o_DevStatus->Input.CN12_11 = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button1]) ? 0 : 0xFF;
o_DevStatus->Input.CN12_12 = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button2]) ? 0 : 0xFF;
o_DevStatus->Input.CN12_13 = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button3]) ? 0 : 0xFF;
o_DevStatus->Input.CN12_14 = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button4]) ? 0 : 0xFF;
o_DevStatus->Input.CN12_15 = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button5]) ? 0 : 0xFF;
o_DevStatus->Input.CN12_16 = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button6]) ? 0 : 0xFF;
o_DevStatus->Input.CN12_17 = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button7]) ? 0 : 0xFF;
o_DevStatus->Input.CN12_18 = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button8]) ? 0 : 0xFF;
o_DevStatus->Input.CN12_19 = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button9]) ? 0 : 0xFF;
o_DevStatus->Input.CN12_20 = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button10]) ? 0 : 0xFF;
o_DevStatus->Input.CN12_21 = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button11]) ? 0 : 0xFF;
o_DevStatus->Input.CN12_22 = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button12]) ? 0 : 0xFF;
auto &analogs = get_analogs();
// FADER-L
float val = 0.f;
if (analogs[Analogs::FaderL].isSet()) {
if (PC_KNOB_MODE) {
val = get_fader_knob_mode_value(
GameAPI::Analogs::getState(RI_MGR, analogs[Analogs::FaderL]),
fader_l_relative);
} else {
val = (GameAPI::Analogs::getState(RI_MGR, analogs[Analogs::FaderL]) - 0.5f) * 2;
}
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderL_Left]) &&
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderL_Right])) {
val = 0.f;
} else if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderL_Left])) {
val = -1.0f;
} else if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderL_Right])) {
val = +1.0f;
}
o_DevStatus->Input.CN15_7 = (val < 0.2f) ? 0xFF : 0;
o_DevStatus->Input.CN15_6 = (val > -0.85f && val < 0.35f) ? 0xFF : 0;
o_DevStatus->Input.CN15_5 = (val > -0.6f && val < 0.6f) ? 0xFF : 0;
o_DevStatus->Input.CN15_4 = (val > -0.35f && val < 0.85f) ? 0xFF : 0;
o_DevStatus->Input.CN15_3 = (val > -0.2f) ? 0xFF : 0;
// FADER-R
val = 0.f;
if (analogs[Analogs::FaderR].isSet()) {
if (PC_KNOB_MODE) {
val = get_fader_knob_mode_value(
GameAPI::Analogs::getState(RI_MGR, analogs[Analogs::FaderR]),
fader_r_relative);
} else {
val = (GameAPI::Analogs::getState(RI_MGR, analogs[Analogs::FaderR]) - 0.5f) * 2;
}
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderR_Left]) &&
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderR_Right])) {
val = 0.f;
} else if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderR_Left])) {
val = -1.0f;
} else if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderR_Right])) {
val = +1.0f;
}
o_DevStatus->Input.CN11_20 = (val < 0.2f) ? 0xFF : 0;
o_DevStatus->Input.CN11_19 = (val > -0.85f && val < 0.35f) ? 0xFF : 0;
o_DevStatus->Input.CN9_10 = (val > -0.6f && val < 0.6f) ? 0xFF : 0;
o_DevStatus->Input.CN9_9 = (val > -0.35f && val < 0.85f) ? 0xFF : 0;
o_DevStatus->Input.CN9_8 = (val > -0.2f) ? 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);
}
if (i_CnPin == 0x1) {
eamuse_coin_set_block(i_Data == 0xFF);
return;
}
auto &lights = get_lights();
if (i_CnPin == 0x11) {
GameAPI::Lights::writeLight(RI_MGR, lights.at(Lights::IC_READER_R), i_Data / 255.0f);
} else if (i_CnPin == 0x12) {
GameAPI::Lights::writeLight(RI_MGR, lights.at(Lights::IC_READER_G), i_Data / 255.0f);
} else if (i_CnPin == 0x13) {
GameAPI::Lights::writeLight(RI_MGR, lights.at(Lights::IC_READER_B), i_Data / 255.0f);
}
}
struct PolarisChordLight {
int data_index;
games::pc::Lights::pc_lights_t light;
PolarisChordLight(
int data_index, games::pc::Lights::pc_lights_t light) :
data_index(data_index), light(light) {}
};
static void set_led_value(games::pc::Lights::pc_lights_t light, uint8_t value) {
auto &lights = games::pc::get_lights();
GameAPI::Lights::writeLight(RI_MGR, lights.at(light), value / 255.f);
}
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);
}
// log_info(
// "pc",
// "lamp [{}] [{}] = {},{},{}, cnt={}",
// i_TapeLedCh,
// i_Offset,
// i_pData[0], i_pData[1], i_pData[2],
// i_cntTapeLed);
// [channel 0]
// these are button lamps; there are 12 buttons (columns) with this layout:
//
// 0 5 8 11 14 16 | 18 20 22 25 28 31
// 1 6 9 12 15 17 | 19 21 23 26 29 32
// 2 7 10 13 | 24 27 30 33
// 3 | 34
// 4 | 35
//
// 36 RGB lamps * 3 = 108 LEDs
// each value ranges from [0, 255]
// (we only care about the top row)
static PolarisChordLight button_lights_mapping[] = {
{(0 * 3) + 0, games::pc::Lights::pc_lights_t::Button1_R},
{(0 * 3) + 1, games::pc::Lights::pc_lights_t::Button1_G},
{(0 * 3) + 2, games::pc::Lights::pc_lights_t::Button1_B},
{(5 * 3) + 0, games::pc::Lights::pc_lights_t::Button2_R},
{(5 * 3) + 1, games::pc::Lights::pc_lights_t::Button2_G},
{(5 * 3) + 2, games::pc::Lights::pc_lights_t::Button2_B},
{(8 * 3) + 0, games::pc::Lights::pc_lights_t::Button3_R},
{(8 * 3) + 1, games::pc::Lights::pc_lights_t::Button3_G},
{(8 * 3) + 2, games::pc::Lights::pc_lights_t::Button3_B},
{(11 * 3) + 0, games::pc::Lights::pc_lights_t::Button4_R},
{(11 * 3) + 1, games::pc::Lights::pc_lights_t::Button4_G},
{(11 * 3) + 2, games::pc::Lights::pc_lights_t::Button4_B},
{(14 * 3) + 0, games::pc::Lights::pc_lights_t::Button5_R},
{(14 * 3) + 1, games::pc::Lights::pc_lights_t::Button5_G},
{(14 * 3) + 2, games::pc::Lights::pc_lights_t::Button5_B},
{(16 * 3) + 0, games::pc::Lights::pc_lights_t::Button6_R},
{(16 * 3) + 1, games::pc::Lights::pc_lights_t::Button6_G},
{(16 * 3) + 2, games::pc::Lights::pc_lights_t::Button6_B},
{(18 * 3) + 0, games::pc::Lights::pc_lights_t::Button7_R},
{(18 * 3) + 1, games::pc::Lights::pc_lights_t::Button7_G},
{(18 * 3) + 2, games::pc::Lights::pc_lights_t::Button7_B},
{(20 * 3) + 0, games::pc::Lights::pc_lights_t::Button8_R},
{(20 * 3) + 1, games::pc::Lights::pc_lights_t::Button8_G},
{(20 * 3) + 2, games::pc::Lights::pc_lights_t::Button8_B},
{(22 * 3) + 0, games::pc::Lights::pc_lights_t::Button9_R},
{(22 * 3) + 1, games::pc::Lights::pc_lights_t::Button9_G},
{(22 * 3) + 2, games::pc::Lights::pc_lights_t::Button9_B},
{(25 * 3) + 0, games::pc::Lights::pc_lights_t::Button10_R},
{(25 * 3) + 1, games::pc::Lights::pc_lights_t::Button10_G},
{(25 * 3) + 2, games::pc::Lights::pc_lights_t::Button10_B},
{(28 * 3) + 0, games::pc::Lights::pc_lights_t::Button11_R},
{(28 * 3) + 1, games::pc::Lights::pc_lights_t::Button11_G},
{(28 * 3) + 2, games::pc::Lights::pc_lights_t::Button11_B},
{(31 * 3) + 0, games::pc::Lights::pc_lights_t::Button12_R},
{(31 * 3) + 1, games::pc::Lights::pc_lights_t::Button12_G},
{(31 * 3) + 2, games::pc::Lights::pc_lights_t::Button12_B},
};
if (i_TapeLedCh == 0 && i_Offset == 0 && i_cntTapeLed == 108) {
for (const auto &map : button_lights_mapping) {
set_led_value(map.light, i_pData[map.data_index]);
}
}
}
void __fastcall aioIob2Bi2x_SetTapeLedDataGroup(AIO_IOB2_BI2X_AC1* i_pNodeCtl, uint32_t i_bfGroup) {
if (i_pNodeCtl != aioIob2Bi2xAc1) {
return aioIob2Bi2x_SetTapeLedDataGroup_orig(i_pNodeCtl, i_bfGroup);
}
}
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_SCI_COMM *__fastcall aioIob2Bi2x_OpenSciUsbCdc(uint32_t i_SerialNumber) {
aioSciComm = new AIO_SCI_COMM;
return aioSciComm;
}
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);
}
}
static bool __fastcall aioIob2Bi2x_WriteFirmIsError(int32_t i_State) {
if (aioIob2Bi2xWrfirm != nullptr) {
return false;
} else {
return aioIob2Bi2x_WriteFirmIsError_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) {
return aioNMgrIob_BeginManage_orig(i_pNodeMgr);
}
}
// libaio.dll
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_IOB2 *i_pNodeMgr) {
if (i_pNodeMgr == aioNmgrIob2) {
delete aioNmgrIob2;
aioNmgrIob2 = nullptr;
} else {
return aioNodeMgr_Destroy_orig(i_pNodeMgr);
}
}
static int32_t __fastcall aioNodeMgr_GetState(AIO_NMGR_IOB2 *i_pNodeMgr) {
if (i_pNodeMgr == aioNmgrIob2) {
return 1;
} else {
return aioNodeMgr_GetState_orig(i_pNodeMgr);
}
}
static bool __fastcall aioNodeMgr_IsReady(AIO_NMGR_IOB2 *i_pNodeMgr, int32_t i_State) {
if (i_pNodeMgr == aioNmgrIob2) {
return true;
} else {
return aioNodeMgr_IsReady_orig(i_pNodeMgr, i_State);
}
}
static bool __fastcall aioNodeMgr_IsError(AIO_NMGR_IOB2 *i_pNodeMgr, int32_t i_State) {
if (i_pNodeMgr == aioNmgrIob2) {
return false;
} else {
return aioNodeMgr_IsError_orig(i_pNodeMgr, i_State);
}
}
static void __fastcall aioNodeCtl_Destroy(AIO_IOB2_BI2X_AC1 *i_pNodeCtl) {
if (i_pNodeCtl == aioIob2Bi2xAc1) {
delete aioIob2Bi2xAc1;
aioIob2Bi2xAc1 = nullptr;
} else {
return aioNodeCtl_Destroy_orig(i_pNodeCtl);
}
}
static int32_t __fastcall aioNodeCtl_GetState(AIO_IOB2_BI2X_AC1 *i_pNodeCtl) {
if (i_pNodeCtl == aioIob2Bi2xAc1) {
return 1;
} else {
return aioNodeCtl_GetState_orig(i_pNodeCtl);
}
}
static bool __fastcall aioNodeCtl_IsReady(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, int32_t i_State) {
if (i_pNodeCtl == aioIob2Bi2xAc1) {
return true;
} else {
return aioNodeCtl_IsReady_orig(i_pNodeCtl, i_State);
}
}
static bool __fastcall aioNodeCtl_IsError(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, int32_t i_State) {
if (i_pNodeCtl == aioIob2Bi2xAc1) {
return false;
} else {
return aioNodeCtl_IsError_orig(i_pNodeCtl, i_State);
}
}
void bi2x_hook_init() {
// avoid double init
static bool initialized = false;
if (initialized) {
return;
} else {
initialized = true;
}
// announce
log_info("bi2x_hook", "init");
// libaio-iob2_video.dll
const auto libaioIob2VideoDll = "libaio-iob2_video.dll";
execexe::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xAC1_Create",
aioIob2Bi2xAC1_Create, &aioIob2Bi2xAC1_Create_orig);
execexe::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xAC1_GetDeviceStatus",
aioIob2Bi2xAC1_GetDeviceStatus, &aioIob2Bi2xAC1_GetDeviceStatus_orig);
execexe::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xAC1_SetWatchDogTimer",
aioIob2Bi2xAC1_SetWatchDogTimer, &aioIob2Bi2xAC1_SetWatchDogTimer_orig);
execexe::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xAC1_AddCounter",
aioIob2Bi2xAC1_AddCounter, &aioIob2Bi2xAC1_AddCounter_orig);
execexe::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xAC1_SetOutputData",
aioIob2Bi2xAC1_SetOutputData, &aioIob2Bi2xAC1_SetOutputData_orig);
execexe::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xAC1_SetTapeLedDataPart",
aioIob2Bi2xAC1_SetTapeLedDataPart, &aioIob2Bi2xAC1_SetTapeLedDataPart_orig);
execexe::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2x_SetTapeLedDataGroup",
aioIob2Bi2x_SetTapeLedDataGroup, &aioIob2Bi2x_SetTapeLedDataGroup_orig);
execexe::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2x_SetTapeLedDataLimit",
aioIob2Bi2x_SetTapeLedDataLimit, &aioIob2Bi2x_SetTapeLedDataLimit_orig);
execexe::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2x_OpenSciUsbCdc",
aioIob2Bi2x_OpenSciUsbCdc, &aioIob2Bi2x_OpenSciUsbCdc_orig);
execexe::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2x_CreateWriteFirmContext",
aioIob2Bi2x_CreateWriteFirmContext, &aioIob2Bi2x_CreateWriteFirmContext_orig);
execexe::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2x_DestroyWriteFirmContext",
aioIob2Bi2x_DestroyWriteFirmContext, &aioIob2Bi2x_DestroyWriteFirmContext_orig);
execexe::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2x_WriteFirmGetState",
aioIob2Bi2x_WriteFirmGetState, &aioIob2Bi2x_WriteFirmGetState_orig);
execexe::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2x_WriteFirmIsCompleted",
aioIob2Bi2x_WriteFirmIsCompleted, &aioIob2Bi2x_WriteFirmIsCompleted_orig);
execexe::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2x_WriteFirmIsError",
aioIob2Bi2x_WriteFirmIsError, &aioIob2Bi2x_WriteFirmIsError_orig);
// libaio-iob.dll
const auto libaioIobDll = "libaio-iob.dll";
execexe::trampoline_try(libaioIobDll, "aioNMgrIob2_Create",
aioNMgrIob2_Create, &aioNMgrIob2_Create_orig);
execexe::trampoline_try(libaioIobDll, "aioNMgrIob_BeginManage",
aioNMgrIob_BeginManage, &aioNMgrIob_BeginManage_orig);
// libaio.dll
const auto libaioDll = "libaio.dll";
execexe::trampoline_try(libaioDll, "aioSci_Destroy",
aioSci_Destroy, &aioSci_Destroy_orig);
execexe::trampoline_try(libaioDll, "aioNodeMgr_Destroy",
aioNodeMgr_Destroy, &aioNodeMgr_Destroy_orig);
execexe::trampoline_try(libaioDll, "aioNodeMgr_GetState",
aioNodeMgr_GetState, &aioNodeMgr_GetState_orig);
execexe::trampoline_try(libaioDll, "aioNodeMgr_IsReady",
aioNodeMgr_IsReady, &aioNodeMgr_IsReady_orig);
execexe::trampoline_try(libaioDll, "aioNodeMgr_IsError",
aioNodeMgr_IsError, &aioNodeMgr_IsError_orig);
execexe::trampoline_try(libaioDll, "aioNodeCtl_Destroy",
aioNodeCtl_Destroy, &aioNodeCtl_Destroy_orig);
execexe::trampoline_try(libaioDll, "aioNodeCtl_GetState",
aioNodeCtl_GetState, &aioNodeCtl_GetState_orig);
execexe::trampoline_try(libaioDll, "aioNodeCtl_IsReady",
aioNodeCtl_IsReady, &aioNodeCtl_IsReady_orig);
execexe::trampoline_try(libaioDll, "aioNodeCtl_IsError",
aioNodeCtl_IsError, &aioNodeCtl_IsError_orig);
}
}
+5
View File
@@ -0,0 +1,5 @@
#pragma once
namespace games::pc {
void bi2x_hook_init();
}
+127
View File
@@ -0,0 +1,127 @@
#include "io.h"
std::vector<Button> &games::pc::get_buttons() {
static std::vector<Button> buttons;
if (buttons.empty()) {
buttons = GameAPI::Buttons::getButtons("Polaris Chord");
GameAPI::Buttons::sortButtons(
&buttons,
"Service",
"Test",
"Coin Mech",
"Button 1",
"Button 2",
"Button 3",
"Button 4",
"Button 5",
"Button 6",
"Button 7",
"Button 8",
"Button 9",
"Button 10",
"Button 11",
"Button 12",
"Fader-L Left",
"Fader-L Right",
"Fader-R Left",
"Fader-R Right",
"Headphone"
);
}
return buttons;
}
std::string games::pc::get_buttons_help() {
// keep to max 100 characters wide
return
" FADER-L FADER-R \n"
"\n"
" B1 B2 B3 ... B11 B12\n"
"\n"
" Most menu interactions are on the touch screen; you can use your mouse.\n"
;
}
std::vector<Analog> &games::pc::get_analogs() {
static std::vector<Analog> analogs;
if (analogs.empty()) {
analogs = GameAPI::Analogs::getAnalogs("Polaris Chord");
GameAPI::Analogs::sortAnalogs(
&analogs,
"Fader-L",
"Fader-R"
);
}
return analogs;
}
std::vector<Light> &games::pc::get_lights() {
static std::vector<Light> lights;
if (lights.empty()) {
lights = GameAPI::Lights::getLights("Polaris Chord");
GameAPI::Lights::sortLights(
&lights,
"IC Reader R",
"IC Reader G",
"IC Reader B",
"Button 1 R",
"Button 1 G",
"Button 1 B",
"Button 2 R",
"Button 2 G",
"Button 2 B",
"Button 3 R",
"Button 3 G",
"Button 3 B",
"Button 4 R",
"Button 4 G",
"Button 4 B",
"Button 5 R",
"Button 5 G",
"Button 5 B",
"Button 6 R",
"Button 6 G",
"Button 6 B",
"Button 7 R",
"Button 7 G",
"Button 7 B",
"Button 8 R",
"Button 8 G",
"Button 8 B",
"Button 9 R",
"Button 9 G",
"Button 9 B",
"Button 10 R",
"Button 10 G",
"Button 10 B",
"Button 11 R",
"Button 11 G",
"Button 11 B",
"Button 12 R",
"Button 12 G",
"Button 12 B"
);
}
return lights;
}
+101
View File
@@ -0,0 +1,101 @@
#pragma once
#include <vector>
#include "cfg/api.h"
namespace games::pc {
namespace Buttons {
enum {
Service,
Test,
CoinMech,
Button1,
Button2,
Button3,
Button4,
Button5,
Button6,
Button7,
Button8,
Button9,
Button10,
Button11,
Button12,
FaderL_Left,
FaderL_Right,
FaderR_Left,
FaderR_Right,
Headphone
};
}
namespace Analogs {
enum {
FaderL,
FaderR,
};
}
namespace Lights {
typedef enum {
IC_READER_R,
IC_READER_G,
IC_READER_B,
Button1_R,
Button1_G,
Button1_B,
Button2_R,
Button2_G,
Button2_B,
Button3_R,
Button3_G,
Button3_B,
Button4_R,
Button4_G,
Button4_B,
Button5_R,
Button5_G,
Button5_B,
Button6_R,
Button6_G,
Button6_B,
Button7_R,
Button7_G,
Button7_B,
Button8_R,
Button8_G,
Button8_B,
Button9_R,
Button9_G,
Button9_B,
Button10_R,
Button10_G,
Button10_B,
Button11_R,
Button11_G,
Button11_B,
Button12_R,
Button12_G,
Button12_B
} pc_lights_t;
}
// getters
std::vector<Button> &get_buttons();
std::string get_buttons_help();
std::vector<Analog> &get_analogs();
std::vector<Light> &get_lights();
}
+110
View File
@@ -0,0 +1,110 @@
#include "pc.h"
#include <format>
#include "bi2x_hook.h"
#include "util/detour.h"
#include "util/logging.h"
#include "util/fileutils.h"
#include "util/unity_player.h"
#include "util/execexe.h"
#include "acioemu/handle.h"
#include "misc/wintouchemu.h"
#include "hooks/graphics/graphics.h"
#include "rawinput/rawinput.h"
namespace games::pc {
std::string PC_INJECT_ARGS = "";
bool PC_NO_IO = false;
static acioemu::ACIOHandle *acioHandle = nullptr;
static std::wstring portName = L"COM1";
static decltype(RegisterRawInputDevices) *RegisterRawInputDevices_orig = nullptr;
static decltype(EnumDisplaySettingsW) *EnumDisplaySettingsW_orig = nullptr;
static decltype(QueryDisplayConfig) *QueryDisplayConfig_orig = nullptr;
static BOOL WINAPI RegisterRawInputDevices_hook(PCRAWINPUTDEVICE pRawInputDevices, UINT uiNumDevices, UINT cbSize) {
// if the caller is spice itself, then pass through.
if (pRawInputDevices &&
(uiNumDevices > 0) &&
(pRawInputDevices[0].hwndTarget == RI_MGR->input_hwnd)) {
return RegisterRawInputDevices_orig(pRawInputDevices, uiNumDevices, cbSize);
}
// otherwise, it must be the game; prevent the game from registering for raw input
// and hijacking WM_INPUT messages.
SetLastError(0xDEADBEEF);
return FALSE;
}
static BOOL WINAPI EnumDisplaySettingsW_hook(LPCWSTR lpszDeviceName, DWORD iModeNum, DEVMODEW* lpDevMode) {
auto result = EnumDisplaySettingsW_orig(lpszDeviceName, iModeNum, lpDevMode);
// https://docs.unity3d.com/2022.3/Documentation/ScriptReference/Screen-currentResolution.html
// "If the player is running in windowed mode, this returns the current dimensions of the game window in pixels and the display refresh rate."
// Override it to 1920x1080 to avoid broken UI under Windowed mode
if (result && iModeNum == ENUM_CURRENT_SETTINGS) {
// Actually make it returns size of the game window, Can I?
lpDevMode->dmPelsWidth = 1920;
lpDevMode->dmPelsHeight = 1080;
}
return result;
}
LONG WINAPI QueryDisplayConfig_hook(UINT32, UINT32*, DISPLAYCONFIG_PATH_INFO*, UINT32*, DISPLAYCONFIG_MODE_INFO*, DISPLAYCONFIG_TOPOLOGY_ID*) {
// make unity fallback to EnumDisplaySettingsW as I don't
// want to deal with this api which is way more complex
return ERROR_NOT_SUPPORTED;
}
void PCGame::attach() {
Game::attach();
// add card reader
acioHandle = new acioemu::ACIOHandle(portName.c_str(), 1);
devicehook_init_trampoline();
devicehook_add(acioHandle);
execexe::init();
execexe::init_deferred([] () {
execexe::init_port_hook(portName, acioHandle);
execexe::load_library("libaio.dll");
execexe::load_library("libaio-iob.dll");
execexe::load_library("libaio-iob_video.dll");
execexe::load_library("libaio-iob2_video.dll");
execexe::load_library("win10actlog.dll");
if (!PC_NO_IO) {
bi2x_hook_init();
}
});
const auto user32Dll = "user32.dll";
detour::trampoline_try(user32Dll, "RegisterRawInputDevices",
RegisterRawInputDevices_hook, &RegisterRawInputDevices_orig);
detour::trampoline_try(user32Dll, "QueryDisplayConfig",
QueryDisplayConfig_hook, &QueryDisplayConfig_orig);
detour::trampoline_try(user32Dll, "EnumDisplaySettingsW",
EnumDisplaySettingsW_hook, &EnumDisplaySettingsW_orig);
if (GRAPHICS_SHOW_CURSOR) {
unity_utils::force_show_cursor(true);
}
unity_utils::set_args(
std::format("{} {}{}",
GetCommandLineA(),
PC_INJECT_ARGS,
unity_utils::get_unity_player_args()));
}
void PCGame::detach() {
Game::detach();
devicehook_dispose();
}
}
+17
View File
@@ -0,0 +1,17 @@
#pragma once
#include "games/game.h"
namespace games::pc {
extern std::string PC_INJECT_ARGS;
extern bool PC_NO_IO;
extern bool PC_KNOB_MODE;
class PCGame : public games::Game {
public:
PCGame() : Game("Polaris Chord") {}
virtual void attach() override;
virtual void detach() override;
};
}
+17 -1
View File
@@ -14,6 +14,7 @@ namespace games::pcm {
static double BILL_DELAY = 2.0;
static int bill_kind = 0;
static int accept_bill_kind = 0;
static int __cdecl BillVali_GetEscrowBillKind() {
return bill_kind;
@@ -74,6 +75,12 @@ namespace games::pcm {
BILL_IN_TIME = get_performance_seconds();
} else if ((BILL_IN_TIME != 0) && ((BILL_IN_TIME + BILL_DELAY) <= get_performance_seconds())) {
BILL_IN_TIME = 0;
if (accept_bill_kind != 0 && accept_bill_kind != bill_kind) {
bill_kind = 0;
return 0;
}
switch (bill_kind) {
case 1:
return 1;
@@ -98,7 +105,8 @@ namespace games::pcm {
return true;
}
static bool __cdecl BillVali_SetAcceptBill(int) {
static bool __cdecl BillVali_SetAcceptBill(int a1) {
accept_bill_kind = a1;
return true;
}
@@ -148,5 +156,13 @@ namespace games::pcm {
system, "?GetEscrowBillKind@GsBillVali@@SAHXZ"));
detour::inline_hook(BillVali_SetAcceptBill, libutils::try_proc(
system, "?SetAcceptBill@GsBillVali@@SA_NH@Z"));
// NOTE: for 2024091200 or newer
detour::inline_hook(BillVali_ReceiveBill, libutils::try_proc(
system, "?ReceiveBill@GsBillVali@@SA_NXZ"));
detour::inline_hook(BillVali_GetEscrowBillKind, libutils::try_proc(
system, "?GetEscrowBillKind@GsBillVali@@SA?AW4E_BILLKIND@@XZ"));
detour::inline_hook(BillVali_SetAcceptBill, libutils::try_proc(
system, "?SetAcceptBill@GsBillVali@@SA_NW4E_BILLKIND@@@Z"));
}
}
+6 -116
View File
@@ -14,134 +14,24 @@
#include <mfidl.h>
#include "avs/game.h"
#include "hooks/cfgmgr32hook.h"
#include "util/detour.h"
#include "util/memutils.h"
#include "util/utils.h"
static VTBL_TYPE(IMFActivate, GetAllocatedString) GetAllocatedString_orig = nullptr;
static decltype(MFEnumDeviceSources) *MFEnumDeviceSources_orig = nullptr;
namespace games::sdvx {
static std::wstring CAMERA0_ID;
static HRESULT WINAPI GetAllocatedString_hook(IMFActivate* This, REFGUID guidKey, LPWSTR *ppwszValue,
UINT32 *pcchLength) {
// call the original
HRESULT result = GetAllocatedString_orig(This, guidKey, ppwszValue, pcchLength);
// try first camera
wchar_t *pwc = nullptr;
if (CAMERA0_ID.length() == 23)
pwc = wcsstr(*ppwszValue, CAMERA0_ID.c_str());
// check if camera could be identified
if (pwc) {
// fake the USB IDs
pwc[4] = L'2';
pwc[5] = L'8';
pwc[6] = L'8';
pwc[7] = L'c';
pwc[13] = L'0';
pwc[14] = L'0';
pwc[15] = L'0';
pwc[16] = L'2';
pwc[21] = L'0';
pwc[22] = L'0';
}
// return original result
return result;
}
static void hook_camera(IMFActivate* camera, size_t no, std::wstring camera_id, std::string camera_instance) {
// don't hook if camera 0 is already hooked
if (CAMERA0_ID.length() > 0)
return;
// save the camera ID
CAMERA0_ID = camera_id;
// cfgmgr hook
CFGMGR32_HOOK_SETTING camera_setting;
camera_setting.device_instance = 0xDEADBEEF;
camera_setting.parent_instance = ~camera_setting.device_instance;
camera_setting.device_id = "USB\\VEN_1022&DEV_7908";
camera_setting.device_node_id = "USB\\VID_288C&PID_0002&MI_00\\?&????????&?&????";
if (camera_instance.length() == 17) {
for (int i = 0; i < 17; i++) {
camera_setting.device_node_id[28 + i] = camera_instance[i];
}
}
cfgmgr32hook_add(camera_setting);
// save original method for later use
if (GetAllocatedString_orig == nullptr) {
GetAllocatedString_orig = camera->lpVtbl->GetAllocatedString;
}
// hook allocated string method for camera identification
memutils::VProtectGuard camera_guard(camera->lpVtbl);
camera->lpVtbl->GetAllocatedString = GetAllocatedString_hook;
}
static HRESULT WINAPI MFEnumDeviceSources_hook(IMFAttributes *pAttributes, IMFActivate ***pppSourceActivate,
UINT32 *pcSourceActivate) {
// call original function
HRESULT result_orig = MFEnumDeviceSources_orig(pAttributes, pppSourceActivate, pcSourceActivate);
// check for capture devices
if (FAILED(result_orig) || !*pcSourceActivate) {
return result_orig;
}
// iterate cameras
size_t cam_hook_num = 0;
for (size_t cam_num = 0; cam_num < *pcSourceActivate && cam_hook_num < 1; cam_num++) {
// flip
size_t cam_num_flipped = cam_num;
// get camera link
IMFActivate *camera = (*pppSourceActivate)[cam_num_flipped];
LPWSTR camera_link_lpwstr;
UINT32 camera_link_length;
if (SUCCEEDED(camera->lpVtbl->GetAllocatedString(
camera,
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK,
&camera_link_lpwstr,
&camera_link_length))) {
// cut name to make ID
std::wstring camera_link_ws = std::wstring(camera_link_lpwstr);
std::wstring camera_id = camera_link_ws.substr(8, 23);
// get camera instance
std::string camera_link = ws2s(camera_link_ws);
std::string camera_instance = camera_link.substr(32, 17);
// hook the camera
hook_camera(camera, cam_hook_num, camera_id, camera_instance);
// increase camera hook number
cam_hook_num++;
}
}
// return result
return result_orig;
*pppSourceActivate = nullptr;
*pcSourceActivate = 0;
log_misc("sdvx", "MFEnumDeviceSources_hook called, returning 0 cameras");
return S_OK;
}
void camera_init() {
// camera media framework hook
MFEnumDeviceSources_orig = detour::iat_try(
"MFEnumDeviceSources", MFEnumDeviceSources_hook, avs::game::DLL_INSTANCE);
log_info("sdvx", "installing camera hooks...");
detour::iat_try("MFEnumDeviceSources", MFEnumDeviceSources_hook, avs::game::DLL_INSTANCE);
}
}
+12 -7
View File
@@ -13,6 +13,7 @@
#include "hooks/sleephook.h"
#include "hooks/winuser.h"
#include "touch/touch.h"
#include "util/deferlog.h"
#include "util/detour.h"
#include "util/logging.h"
#include "util/sigscan.h"
@@ -24,6 +25,7 @@
#include "io.h"
#include "acioemu/handle.h"
#include "cfg/configurator.h"
#include "launcher/signal.h"
static decltype(RegCloseKey) *RegCloseKey_orig = nullptr;
static decltype(RegEnumKeyA) *RegEnumKeyA_orig = nullptr;
@@ -39,7 +41,6 @@ namespace games::sdvx {
const char *ORIGINAL_ASIO_DEVICE_NAME = "XONAR SOUND CARD(64)";
// settings
bool DISABLECAMS = false;
bool NATIVETOUCH = false;
uint8_t DIGITAL_KNOB_SENS = 16;
SdvxOverlayPosition OVERLAY_POS = SDVX_OVERLAY_BOTTOM;
@@ -210,6 +211,10 @@ namespace games::sdvx {
out = "";
return true;
}
if (data.find("SuperstepSound: Audio device is not available") != std::string::npos) {
deferredlogs::defer_error_messages(deferredlogs::SUPERSTEP_SOUND_ERROR_MESSAGE);
return false;
}
return false;
}
@@ -369,17 +374,17 @@ namespace games::sdvx {
winuser_hook_init(avs::game::DLL_INSTANCE);
// hook camera
if (!DISABLECAMS) {
camera_init();
}
camera_init();
// RGB CAMERA error ignore
if (!replace_pattern(
// RGB CAMERA error ignore for SDVX5
// SDVX5: boot sequence triggers camera error if camera is not detected
// SDVX6: boots fine, but game title screen in attract loop will have camera error (cosmetic only)
if (replace_pattern(
avs::game::DLL_INSTANCE,
"418D480484C074218D51FD",
"????????????9090??????",
0, 0)) {
log_info("sdvx", "did not find matching signature for camera error patch");
log_info("sdvx", "applied camera error patch (sdvx5)");
}
// remove log spam
-1
View File
@@ -17,7 +17,6 @@ namespace games::sdvx {
};
// settings
extern bool DISABLECAMS;
extern bool NATIVETOUCH;
extern uint8_t DIGITAL_KNOB_SENS;
extern std::optional<std::string> ASIO_DRIVER;
+37 -17
View File
@@ -294,7 +294,7 @@ namespace games::shared {
// error
if (!clear) {
log_fatal("sdvx", "could not find path");
log_fatal("printer", "could not find path");
}
return "DUMMY";
@@ -326,7 +326,7 @@ namespace games::shared {
memcpy(image_data, pBandImage->baseAddr, (size_t) (image_width * image_height * 3));
// convert BGR to RGB
log_info("printer", "converting BGR to RGB...");
log_misc("printer", "converting BGR to RGB...");
for (int pixel = 0; pixel < image_width * image_height; pixel++) {
int index = pixel * 3;
uint8_t tmp = image_data[index];
@@ -334,25 +334,45 @@ namespace games::shared {
image_data[index + 2] = tmp;
}
// flip horizontally
for (int x = 0; x < image_width / 2; x++) {
for (int y = 0; y < image_height; y++) {
int index1 = (y * image_width + x) * 3;
int index2 = (y * image_width + image_width - x - 1) * 3;
uint8_t r = image_data[index1 + 0];
uint8_t g = image_data[index1 + 1];
uint8_t b = image_data[index1 + 2];
image_data[index1 + 0] = image_data[index2 + 0];
image_data[index1 + 1] = image_data[index2 + 1];
image_data[index1 + 2] = image_data[index2 + 2];
image_data[index2 + 0] = r;
image_data[index2 + 1] = g;
image_data[index2 + 2] = b;
if (avs::game::is_model({"KLP", "KFC"})) {
// rotate image clockwise
log_misc("printer", "rotate clockwise...");
auto rotated = new uint8_t[image_width * image_height * 3];
for (int x = 0; x < image_width; x++) {
for (int y = 0; y < image_height; y++) {
int index1 = (y * image_width + x) * 3;
int index2 = (x * image_height + y) * 3;
rotated[index2 + 0] = image_data[index1 + 0];
rotated[index2 + 1] = image_data[index1 + 1];
rotated[index2 + 2] = image_data[index1 + 2];
}
}
delete[] image_data;
image_data = rotated;
std::swap(image_width, image_height);
} else {
// flip horizontally
log_misc("printer", "flip horizontally...");
for (int x = 0; x < image_width / 2; x++) {
for (int y = 0; y < image_height; y++) {
int index1 = (y * image_width + x) * 3;
int index2 = (y * image_width + image_width - x - 1) * 3;
uint8_t r = image_data[index1 + 0];
uint8_t g = image_data[index1 + 1];
uint8_t b = image_data[index1 + 2];
image_data[index1 + 0] = image_data[index2 + 0];
image_data[index1 + 1] = image_data[index2 + 1];
image_data[index1 + 2] = image_data[index2 + 2];
image_data[index2 + 0] = r;
image_data[index2 + 1] = g;
image_data[index2 + 2] = b;
}
}
}
// iterate folders
log_info("printer", "writing files...");
log_misc("printer", "writing files...");
for (const auto &path : PRINTER_PATH) {
for (const auto &format : PRINTER_FORMAT) {