From 605f0abc7f4136e4acb3d3591a9bc31c5592df99 Mon Sep 17 00:00:00 2001 From: mayaqf Date: Sun, 23 Aug 2026 18:52:26 +0900 Subject: [PATCH 1/2] feat!: rename trigger config to Xbox LT/RT naming and add v1.0.1 version - Rename ini keys EnableL2/EnableR2 -> EnableLT/EnableRT and struct fields enableL2/enableR2 -> enableLT/enableRT across Config, XFireEngine, tests. Clean break: legacy EnableL2/EnableR2 keys are no longer accepted. - Add build version v1.0.1 via CMake project VERSION, injected as integer macros XFIRE_VERSION_MAJOR/MINOR/PATCH and stringified in new Version.h. Emitted in the [STICKYINIT] diagnostic log line. Integer macros avoid the VS generator's Release-config string-macro quoting breakage. - Add version comment to XInputXFire.ini header and README ini block. - Unify LT/RT trigger naming in code comments and test diagnostics (XFireEngine.h, Config.h, xfire_unit, TestHarness, xinput_probe). BREAKING CHANGE: ini keys EnableL2/EnableR2 renamed to EnableLT/EnableRT. Configs that set EnableL2=0 to disable a trigger revert to the default (enabled) and must use EnableLT=0 / EnableRT=0 instead. Co-Authored-By: Claude --- CMakeLists.txt | 12 +++++++--- README.md | 36 +++++++++++++++++++++------- assets/XInputXFire.ini | 12 +++++----- src/proxy/Config.cpp | 8 +++---- src/proxy/Config.h | 6 ++--- src/proxy/Version.h | 15 ++++++++++++ src/proxy/XFireEngine.cpp | 8 +++---- src/proxy/XFireEngine.h | 4 ++-- src/proxy/exports.cpp | 5 ++-- src/test/TestHarness.cpp | 4 ++-- src/test/xfire_unit.cpp | 50 +++++++++++++++++++-------------------- src/test/xinput_probe.cpp | 2 +- 12 files changed, 101 insertions(+), 61 deletions(-) create mode 100644 src/proxy/Version.h diff --git a/CMakeLists.txt b/CMakeLists.txt index a361728..a3dc1db 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ # XInputXFire Proxy DLL cmake_minimum_required(VERSION 3.20) -project(XInputXFireProxy LANGUAGES CXX) +project(XInputXFireProxy LANGUAGES CXX VERSION 1.0.1) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) @@ -23,7 +23,10 @@ add_library(XInputProxy SHARED ) target_include_directories(XInputProxy PRIVATE src/proxy) target_compile_definitions(XInputProxy PRIVATE - WIN32_LEAN_AND_MEAN _WIN32_WINNT=0x0602 NOMINMAX) + WIN32_LEAN_AND_MEAN _WIN32_WINNT=0x0602 NOMINMAX + XFIRE_VERSION_MAJOR=${PROJECT_VERSION_MAJOR} + XFIRE_VERSION_MINOR=${PROJECT_VERSION_MINOR} + XFIRE_VERSION_PATCH=${PROJECT_VERSION_PATCH}) if(MSVC) target_compile_options(XInputProxy PRIVATE /W4 /permissive- /utf-8) endif() @@ -76,7 +79,10 @@ add_executable(xfire_unit ) target_include_directories(xfire_unit PRIVATE src/proxy) target_compile_definitions(xfire_unit PRIVATE - XFIRE_TEST WIN32_LEAN_AND_MEAN _WIN32_WINNT=0x0602 NOMINMAX) + XFIRE_TEST WIN32_LEAN_AND_MEAN _WIN32_WINNT=0x0602 NOMINMAX + XFIRE_VERSION_MAJOR=${PROJECT_VERSION_MAJOR} + XFIRE_VERSION_MINOR=${PROJECT_VERSION_MINOR} + XFIRE_VERSION_PATCH=${PROJECT_VERSION_PATCH}) if(MSVC) target_compile_options(xfire_unit PRIVATE /W4 /permissive- /utf-8) endif() diff --git a/README.md b/README.md index 13b80db..7843fc4 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # XInputXFire Proxy DLL -Xboxコントローラ(XInput)の **R2 または L2 トリガーを押している間だけ、方向キー(DPAD)と ABXY ボタンを連射** するツール。XInput Plus のプロキシDLL方式を参考に自作。 +Xboxコントローラ(XInput)の **RT または LT トリガーを押している間だけ、方向キー(DPAD)と ABXY ボタンを連射** するツール。XInput Plus のプロキシDLL方式を参考に自作。 ## 動作方式(プロキシDLL方式) @@ -12,27 +12,37 @@ game.exe ─(暗黙DLL解決)─▶ ローカル xinput1_3.dll [プロキシ] ▼ 本物 System32\xinput1_3.dll (or 1_4) に転送 → 生state取得 ▼ - 連射エンジン: L2/R2判定 → QPC周期 → DPAD/ABXY 改変 + 連射エンジン: LT/RT判定 → QPC周期 → DPAD/ABXY 改変 ▼ (トリガ値は触らない) ゲームへ改変後 state を返す ``` ## 機能(連射ロジック) -- **発動条件**: L2 または R2 が押下閾値(既定150/255)以上。いずれかで発動。 +- **マスター切替(LB+A)**: 連射機能の有効/無効を **LB+A 同時押し(PS名 L1+×)** の立ち上がりでトグル。**起動時は OFF**(メニュー操作中の誤動作を防ぐため)。OFF 中は LT/RT を押しても連射せず入力をそのまま通す。 +- **発動条件**: LT または RT が押下閾値(既定150/255)以上。いずれかで発動。マスター ON のときのみ有効。 - **連射対象**: DPAD(上下左右) + ABXY(ini で個別指定可)。 - **周期**: QPC 高精度タイマベース。ON区間(既定50ms)=ボタン押下、OFF区間(既定50ms)=ボタン離。ポーリング間隔(60/120Hz等)に依存しない。 - **初回ON区間(FirstOnMs)**: 対象ボタン押下直後の最初のON区間だけ専用の長さを指定(既定200ms・0=無効=OnMsと同値)。位相クロックは対象ボタン押下基準で、ボタンを離すとリセットされ次回押下で再びFirstOnMsから。`FirstOnMs` を大きくすれば連射ON中でも「1回だけボタンを押す」がコントロール可能(長い初回ONの間に離せば1回押下で確定)。 - **ヒステリシス**: 押下閾値150 / 離上閾値140 で、トリガが閾値付近でチャタリングしても連射が暴走しない。 - **4コントローラ独立**: dwUserIndex 0-3 各々独立状態。 -- **トリガ透過**: L2/R2 のアナログ値はゲームへそのまま伝達(連射は DPAD/ABXY のみに作用)。 +- **トリガ透過**: LT/RT のアナログ値はゲームへそのまま伝達(連射は DPAD/ABXY のみに作用)。 - **物理押下のみ連射**: 物理的に押されている対象ボタンだけ連射。離せば停止。 +> **ボタン名対応**: 本ツールは XInput(Xbox API) ベースのため Xbox 名を基本表記します。PS コントローラでの呼び名との対応は以下の通り。 +> +> | Xbox | PS | XInput API | +> |---|---|---| +> | LT / RT | L2 / R2 | `bLeftTrigger` / `bRightTrigger`(連射発動トリガ・アナログ) | +> | LB / RB | L1 / R1 | `XINPUT_GAMEPAD_LEFT_SHOULDER` / `RIGHT_SHOULDER`(デジタル肩) | +> | A / B / X / Y | × / ○ / □ / △ | `XINPUT_GAMEPAD_A/B/X/Y`(顔ボタン・位置対応) | + ## 設定ファイル (XInputXFire.ini) ゲームexeと同フォルダに配置。無い場合はデフォルト値を使用。 ```ini +; XInputXFire v1.0.1 [XFire] OnMs=50 OffMs=50 @@ -41,11 +51,19 @@ FirstOnMs=200 TriggerThreshold=150 HysteresisLow=140 TargetButtons=DPAD_UP|DPAD_DOWN|DPAD_LEFT|DPAD_RIGHT|A|B|X|Y -EnableL2=1 -EnableR2=1 +EnableLT=1 +EnableRT=1 +; 連射マスター切替コンボ(|区切り・既定 LB|A=PS名 L1+A)。全キー同時押しの立ち上がりで ON/OFF 切替。空で無効(常時ON運用)。 +ToggleButtons=LB|A +; 起動時のマスター状態(1=ON / 0=OFF・既定0=OFF)。OFF中は連射せず素通し(メニュー操作安全)。 +DefaultEnabled=0 +; トグル切替時の音声アナウンス(1=有効 / 0=無効・既定1)。SAPI で "Enabled/Disabled cross fire." を再生。 +AnnounceEnabled=1 +; 起動音(1=再生 / 0=無効・既定1)。DLL 埋め込み WAVE リソースを再生しプロキシロード完了を通知。 +StartupSound=1 ``` -`TargetButtons` 対応名: `DPAD_UP` `DPAD_DOWN` `DPAD_LEFT` `DPAD_RIGHT` `A` `B` `X` `Y` `LB` `RB` `START` `BACK` `LSB` `RSB`(`|` 区切り) +`TargetButtons` / `ToggleButtons` 対応名: `DPAD_UP` `DPAD_DOWN` `DPAD_LEFT` `DPAD_RIGHT` `A` `B` `X` `Y` `LB` `RB` `START` `BACK` `LSB` `RSB`(`|` 区切り) ## プロキシDLLビルド @@ -81,7 +99,7 @@ ctest --test-dir build-x64 --output-on-failure ## 検証 1. **単体テスト** (`xfire_unit`): モックQPC時刻注入で連射ロジック(ON/OFF周期・ヒステリシス・トリガ透過・対象外保護・4コントローラ独立)を検証。コントローラ不要。 -2. **テストハーネス** (`test_harness`): プロキシDLLを同フォルダに `xinput1_3.dll` として配置し実行。実コントローラの R2 押下中に対象ボタンが周期トグルするか CSV ログで確認(60秒)。 +2. **テストハーネス** (`test_harness`): プロキシDLLを同フォルダに `xinput1_3.dll` として配置し実行。実コントローラの RT 押下中に対象ボタンが周期トグルするか CSV ログで確認(60秒)。 - **Smart App Control(SAC) 有効環境では test_harness.exe が起動できない場合があります**。SAC は未署名かつ Microsoft クラウドに実績のない新規バイナリを「未確認」としてブロックし(パスベースではないため Program Files 等へのコピーでも回避不可)、ユーザ側の例外設定もありません。イベントログ `Microsoft-Windows-CodeIntegrity/Operational` の ID 3118/3033 で「Smart App Control Block」を確認できます。 - なお**本番のプロキシDLLは SAC 下でもブロックされません**(署名済みゲーム exe が LoadLibrary で読み込む DLL は許容されるため)。SAC に引っかかるのは test_harness 等の**独立未署名 exe のみ**です。test_harness が起動できない場合は、SAC オフの別PC/VM で検証するか、本番ゲーム経路で実機確認してください。 3. **統合**: 許可されたオフラインゲームに配置し実動作確認。 @@ -101,7 +119,7 @@ ctest --test-dir build-x64 --output-on-failure コントローラが無反応・連射が効かない場合、プロキシDLLは起動時(初回エクスポート呼出時)の診断結果を **`%TEMP%\XInputXFire_xinput.log`** に1行ずつ追記します。このログで原因を切り分けられます。 -- `[STICKYINIT] LoadOnce=1 hDll=... GetState=...` → プロキシが正常にロードされ、本物DLLの関数ポインタを取得した(`hDll=0000000000000000` なら本物DLLロード失敗)。 +- `[STICKYINIT] version=1.0.1 LoadOnce=1 hDll=... GetState=...` → プロキシが正常にロードされ、本物DLLの関数ポインタを取得した(`version=` はビルドバージョン、`hDll=0000000000000000` なら本物DLLロード失敗)。 - `[LOADER] ...` → 本物DLLのロード失敗・必須エクスポート欠落等(フォールバック先DLLの切り替え状況)。 - `[CONFIG] ...` → ini の値が非数値・範囲外・不明トークンで既定値に置換された(意図しない挙動の原因特定に)。 - `[XFIRE] QueryPerformanceFrequency returned 0 ...` → 高精度タイマ取得失敗(連射機能が無効化・パススルーのみ動作)。 diff --git a/assets/XInputXFire.ini b/assets/XInputXFire.ini index 256d847..71b44e0 100644 --- a/assets/XInputXFire.ini +++ b/assets/XInputXFire.ini @@ -1,4 +1,4 @@ -; XInputXFire Proxy DLL 設定ファイル +; XInputXFire Proxy DLL 設定ファイル (v1.0.1) ; 対象ゲームのexeと同じフォルダに配置してください。 ; ファイルが無い場合はデフォルト値(下記)が使用されます。 @@ -12,18 +12,18 @@ OffMs=50 ; ボタンを離すとリセットされ、次回押下で再びこの長さから開始。 FirstOnMs=200 -; トリガ押下判定の閾値(0-255)。L2/R2 がこの値以上で連射発動。 +; トリガ押下判定の閾値(0-255)。LT/RT がこの値以上で連射発動。 TriggerThreshold=150 ; 離上判定の閾値(0-255)。連射中はこの値未満で解除(ヒステリシス=チャタリング防止)。 HysteresisLow=140 - + ; 連射対象ボタン(|区切り)。省略時は下記デフォルト。 ; 対応名: DPAD_UP DPAD_DOWN DPAD_LEFT DPAD_RIGHT A B X Y LB RB START BACK LSB RSB TargetButtons=DPAD_UP|DPAD_DOWN|DPAD_LEFT|DPAD_RIGHT|A|B|X|Y -; L2/R2 を連射発動トリガとして使用(1=有効 / 0=無効) -EnableL2=1 -EnableR2=1 +; LT/RT を連射発動トリガとして使用(1=有効 / 0=無効) +EnableLT=1 +EnableRT=1 ; ---- 連射マスタートグル(常時有効だとメニュー開閉ボタンが連射して誤動作するため) ---- ; 連射マスター有効/無効を切替えるコンボキー(|区切り)。この全キー同時押下の diff --git a/src/proxy/Config.cpp b/src/proxy/Config.cpp index cc74b85..1f94814 100644 --- a/src/proxy/Config.cpp +++ b/src/proxy/Config.cpp @@ -70,7 +70,7 @@ void Config::LoadOnce() { }; // bool 項目:1/0/true/false/yes/no/on/off を受理。空(キー不在/空)=既定、 // 不明値=既定+ログ。従来は GetPrivateProfileIntW で "true"/"yes" 等が 0(無効)に黙って変換 - // されていた(EnableL2=true で L2 連射が黙って OFF になる等)。 + // されていた(EnableLT=true で LT 連射が黙って OFF になる等)。 auto readBool = [sec, &ini](const wchar_t* key, const char* label, bool defv) -> bool { wchar_t b[32] = {0}; GetPrivateProfileStringW(sec, key, L"", b, 32, ini.c_str()); @@ -90,9 +90,9 @@ void Config::LoadOnce() { g_cfg.firstOnMs = readClampedDw(L"FirstOnMs", "FirstOnMs", g_cfg.firstOnMs, (INT)Config::kFirstOnMsMin, (INT)Config::kOnMsMax); g_cfg.triggerThreshold = readClampedByte(L"TriggerThreshold", "TriggerThreshold", g_cfg.triggerThreshold); g_cfg.hysteresisLow = readClampedByte(L"HysteresisLow", "HysteresisLow", g_cfg.hysteresisLow); - // EnableL2/R2 は 1/0/true/false/yes/no/on/off で記録(不明値=既定+ログ)。 - g_cfg.enableL2 = readBool(L"EnableL2", "EnableL2", g_cfg.enableL2); - g_cfg.enableR2 = readBool(L"EnableR2", "EnableR2", g_cfg.enableR2); + // EnableLT/RT は 1/0/true/false/yes/no/on/off で記録(不明値=既定+ログ)。 + g_cfg.enableLT = readBool(L"EnableLT", "EnableLT", g_cfg.enableLT); + g_cfg.enableRT = readBool(L"EnableRT", "EnableRT", g_cfg.enableRT); wchar_t buf[256] = {0}; GetPrivateProfileStringW(sec, L"TargetButtons", L"", buf, 256, ini.c_str()); int unk = 0; diff --git a/src/proxy/Config.h b/src/proxy/Config.h index 9d20d18..734a589 100644 --- a/src/proxy/Config.h +++ b/src/proxy/Config.h @@ -12,9 +12,9 @@ struct XFireConfig { | XINPUT_GAMEPAD_DPAD_LEFT | XINPUT_GAMEPAD_DPAD_RIGHT | XINPUT_GAMEPAD_A | XINPUT_GAMEPAD_B | XINPUT_GAMEPAD_X | XINPUT_GAMEPAD_Y; // 連射対象ビットマスク(既定=DPAD全方向+ABXY) - bool enableL2 = true; - bool enableR2 = true; - // --- 連射マスタートグル(L2/R2 ゲートの上掛け) --- + bool enableLT = true; + bool enableRT = true; + // --- 連射マスタートグル(LT/RT ゲートの上掛け) --- WORD toggleButtons = XINPUT_GAMEPAD_LEFT_SHOULDER | XINPUT_GAMEPAD_A; // トグル切替コンボ(LB|A)。全ビット同時押下の立ち上がりで ON/OFF 切替 bool defaultEnabled = false; // 起動時のマスター状態(false=OFF・メニュー操作安全) bool announceEnabled = true; // トグル切替時の SAPI 音声アナウンス有無 diff --git a/src/proxy/Version.h b/src/proxy/Version.h new file mode 100644 index 0000000..3ffc90e --- /dev/null +++ b/src/proxy/Version.h @@ -0,0 +1,15 @@ +// Version.h - ビルドバージョン(CMake から XFIRE_VERSION_MAJOR/MINOR/PATCH を数値マクロで注入)。 +// 数値を文字列化して "MAJOR.MINOR.PATCH" を構成。文字列マクロのジェネレータ依存の +// クォート問題を回避するため数値で渡してここで組み立てる。未注入時は 0.0.0 フォールバック。 +#pragma once +#ifndef XFIRE_VERSION_MAJOR + #define XFIRE_VERSION_MAJOR 0 + #define XFIRE_VERSION_MINOR 0 + #define XFIRE_VERSION_PATCH 0 +#endif +#define XFIRE_VERSION_STR_(a) #a +#define XFIRE_VERSION_STR(a) XFIRE_VERSION_STR_(a) +#define XFIRE_VERSION \ + XFIRE_VERSION_STR(XFIRE_VERSION_MAJOR) "." \ + XFIRE_VERSION_STR(XFIRE_VERSION_MINOR) "." \ + XFIRE_VERSION_STR(XFIRE_VERSION_PATCH) \ No newline at end of file diff --git a/src/proxy/XFireEngine.cpp b/src/proxy/XFireEngine.cpp index 72cffb7..e7cbe33 100644 --- a/src/proxy/XFireEngine.cpp +++ b/src/proxy/XFireEngine.cpp @@ -1,5 +1,5 @@ // XFireEngine.cpp - 連射タイミング制御コア -// 連射マスタートグル(既定OFF)の上に、L2/R2 押下中のみ連射対象ボタン(DPAD/ABXY)を +// 連射マスタートグル(既定OFF)の上に、LT/RT 押下中のみ連射対象ボタン(DPAD/ABXY)を // QPC ベース周期で ON/OFF 反転。トリガ値(bLeftTrigger/bRightTrigger)は触らない。 // 4コントローラ独立。コンボキー(LB+A)立ち上がりでマスターON/OFF切替 + SAPI 音声。 #include "XFireEngine.h" @@ -98,10 +98,10 @@ void XFireEngine::Apply(DWORD idx, XINPUT_STATE* p) { return; } - // 有効なトリガの最大値(L2/R2 いずれか押下で発動) + // 有効なトリガの最大値(LT/RT いずれか押下で発動) BYTE trig = 0; - if (cfg.enableR2 && g.bRightTrigger > trig) trig = g.bRightTrigger; - if (cfg.enableL2 && g.bLeftTrigger > trig) trig = g.bLeftTrigger; + if (cfg.enableRT && g.bRightTrigger > trig) trig = g.bRightTrigger; + if (cfg.enableLT && g.bLeftTrigger > trig) trig = g.bLeftTrigger; // ヒステリシス: 押下判定は高閾値、離上判定は低閾値 bool pressed = s.active ? (trig >= cfg.hysteresisLow) diff --git a/src/proxy/XFireEngine.h b/src/proxy/XFireEngine.h index 344960c..f43b0c4 100644 --- a/src/proxy/XFireEngine.h +++ b/src/proxy/XFireEngine.h @@ -1,5 +1,5 @@ // XFireEngine.h - 連射タイミング制御コア -// 連射マスタートグル(既定OFF)の上に L2/R2 押下中のみ、連射対象ボタン(DPAD/ABXY)を +// 連射マスタートグル(既定OFF)の上に LT/RT 押下中のみ、連射対象ボタン(DPAD/ABXY)を // QPC ベースの周期で ON/OFF 反転。トリガ値(bLeftTrigger/bRightTrigger)は触らない // (ゲームへそのまま伝達)。4コントローラ(dwUserIndex 0-3)は独立状態。 #pragma once @@ -9,7 +9,7 @@ namespace XFireEngine { // 初回エクスポート呼出時に1回だけ実行(QPC周波数取得・マスター既定状態設定)。スレッドセーフ。 void InitOnce(); // XInputGetState 転送後に呼ぶ。コンボキー(LB+A)立ち上がりでマスターON/OFF切替。 - // マスターONかつ L2/R2 押下中なら pState の DPAD/ABXY を連射改変。トリガ値は変更しない。 + // マスターONかつ LT/RT 押下中なら pState の DPAD/ABXY を連射改変。トリガ値は変更しない。 // 位相クロックは対象ボタン押下基準: 押下直後の最初のON区間は FirstOnMs(0=OnMsと同値)、 // その後 OnMs/OffMs サイクル。対象ボタンを離すと位相リセットされ次回押下でFirstOnMsから再開。 void Apply(DWORD dwUserIndex, XINPUT_STATE* pState); diff --git a/src/proxy/exports.cpp b/src/proxy/exports.cpp index 23ac023..a945697 100644 --- a/src/proxy/exports.cpp +++ b/src/proxy/exports.cpp @@ -7,6 +7,7 @@ #include "XFireEngine.h" #include "Announcer.h" #include "DiagLog.h" +#include "Version.h" #include #include @@ -27,8 +28,8 @@ void StickyInit() { g_stickyDone.store(1, std::memory_order_release); char b[256]; const RealXInput& r = RealXInputLoader::Get(); - sprintf_s(b, "[STICKYINIT] LoadOnce=%d hDll=%p GetState=%p SetState=%p Cap=%p Enable=%p", - ok ? 1 : 0, r.hDll, (void*)r.GetState, (void*)r.SetState, (void*)r.GetCapabilities, (void*)r.Enable); + sprintf_s(b, "[STICKYINIT] version=%s LoadOnce=%d hDll=%p GetState=%p SetState=%p Cap=%p Enable=%p", + XFIRE_VERSION, ok ? 1 : 0, r.hDll, (void*)r.GetState, (void*)r.SetState, (void*)r.GetCapabilities, (void*)r.Enable); DiagLog::Log(b); Announcer::StartupBeep(); // 初回エクスポート(プロキシロード)完了をビープで通知 } diff --git a/src/test/TestHarness.cpp b/src/test/TestHarness.cpp index 5a6162e..8a604e9 100644 --- a/src/test/TestHarness.cpp +++ b/src/test/TestHarness.cpp @@ -139,13 +139,13 @@ int main(int argc, char** argv) { } prevCombo = curCombo; - // 1秒サマリ(L2/R2 + 連射判定: 直近1秒の対象トグル回数 >= 6 で連射) + // 1秒サマリ(LT/RT + 連射判定: 直近1秒の対象トグル回数 >= 6 で連射) if (now - lastSummary >= 1000) { bool trig = (s.lt >= 128 || s.rt >= 128); int tc = (int)toggleTimes.size(); bool xfire = trig && tc >= 6; char bns[64]; ButtonNames(s.buttons, bns, sizeof(bns)); - printf("[%6.1fs] === L2=%3u R2=%3u | %-14s | トグル/秒=%-2d %s ===\n", + printf("[%6.1fs] === LT=%3u RT=%3u | %-14s | トグル/秒=%-2d %s ===\n", now/1000.0, s.lt, s.rt, bns, tc, xfire ? "XFIRE" : "idle"); lastSummary = now; } diff --git a/src/test/xfire_unit.cpp b/src/test/xfire_unit.cpp index b4602c9..fd0bc46 100644 --- a/src/test/xfire_unit.cpp +++ b/src/test/xfire_unit.cpp @@ -97,8 +97,8 @@ int main() { XFireEngine::SetTestClockNowMs(0.0); XINPUT_STATE s = MakeState(XINPUT_GAMEPAD_A, 150, 200); XFireEngine::Apply(0, &s); - Check(s.Gamepad.bLeftTrigger == 150, "T5: L2 unchanged"); - Check(s.Gamepad.bRightTrigger == 200, "T5: R2 unchanged"); + Check(s.Gamepad.bLeftTrigger == 150, "T5: LT unchanged"); + Check(s.Gamepad.bRightTrigger == 200, "T5: RT unchanged"); } // T6: 対象外ボタン(LB)は ON/OFF 両区間で触らない @@ -145,20 +145,20 @@ int main() { Check((s3.Gamepad.wButtons & XINPUT_GAMEPAD_A) != 0, "T8b: released -> physical A (no xfire)"); } - // T9: L2 単独でも発動 + // T9: LT 単独でも発動 { XFireEngine::ResetControllerState(); XFireEngine::SetTestClockNowMs(0.0); - XINPUT_STATE s = MakeState(XINPUT_GAMEPAD_A, 200, 0); // L2=200 + XINPUT_STATE s = MakeState(XINPUT_GAMEPAD_A, 200, 0); // LT=200 XFireEngine::Apply(0, &s); - Check((s.Gamepad.wButtons & XINPUT_GAMEPAD_A) != 0, "T9: L2 alone triggers xfire ON"); + Check((s.Gamepad.wButtons & XINPUT_GAMEPAD_A) != 0, "T9: LT alone triggers xfire ON"); } // T10: 押されていない対象ボタンは連射しない { XFireEngine::ResetControllerState(); XFireEngine::SetTestClockNowMs(0.0); - XINPUT_STATE s = MakeState(0, 0, 200); // ボタン無し、R2押下 + XINPUT_STATE s = MakeState(0, 0, 200); // ボタン無し、RT押下 XFireEngine::Apply(0, &s); // ON区間だが heldButtons=0 Check(s.Gamepad.wButtons == 0, "T10: no held target -> no buttons forced"); } @@ -218,13 +218,13 @@ int main() { Check((s4.Gamepad.wButtons & XINPUT_GAMEPAD_A) != 0, "T13c: ON after catch-up"); } - // T14: L2+R2 同時押下でも発動(max 値判定) + // T14: LT+RT 同時押下でも発動(max 値判定) { XFireEngine::ResetControllerState(); XFireEngine::SetTestClockNowMs(0.0); XINPUT_STATE s = MakeState(XINPUT_GAMEPAD_A, 130, 200); XFireEngine::Apply(0, &s); - Check((s.Gamepad.wButtons & XINPUT_GAMEPAD_A) != 0, "T14: L2+R2 both trigger ON"); + Check((s.Gamepad.wButtons & XINPUT_GAMEPAD_A) != 0, "T14: LT+RT both trigger ON"); } // T8c: OFF区間中にトリガ離上 -> 対象ボタンは物理状態に戻る(連射停止) @@ -256,7 +256,7 @@ int main() { XFireEngine::ResetControllerState(); XFireEngine::SetTestClockNowMs(0.0); - // OFF時: R2押下+A押下 -> 連射せず物理Aのまま + // OFF時: RT押下+A押下 -> 連射せず物理Aのまま XINPUT_STATE a = MakeState(XINPUT_GAMEPAD_A, 0, 200); XFireEngine::Apply(0, &a); Check(a.Gamepad.wButtons == XINPUT_GAMEPAD_A, "T15: master OFF -> no xfire (A stays physical)"); @@ -266,7 +266,7 @@ int main() { XFireEngine::Apply(0, &b); Check(b.Gamepad.wButtons == (XINPUT_GAMEPAD_LEFT_SHOULDER | XINPUT_GAMEPAD_A), "T15b: combo frame passthrough"); - // ON時: R2押下+A押下 -> ON区間でA押下維持(連射発動) + // ON時: RT押下+A押下 -> ON区間でA押下維持(連射発動) XFireEngine::ResetControllerState(); // フェーズ状態リセット(ON開始直後をON区間に) XFireEngine::SetTestClockNowMs(0.0); XINPUT_STATE c = MakeState(XINPUT_GAMEPAD_A, 0, 200); @@ -278,7 +278,7 @@ int main() { XFireEngine::Apply(0, &d); Check(d.Gamepad.wButtons == (XINPUT_GAMEPAD_LEFT_SHOULDER | XINPUT_GAMEPAD_A), "T15d: combo toggles OFF passthrough"); - // OFF時: R2押下+A押下 -> 再び連射せず物理Aのまま + // OFF時: RT押下+A押下 -> 再び連射せず物理Aのまま XFireEngine::ResetControllerState(); XFireEngine::SetTestClockNowMs(0.0); XINPUT_STATE e = MakeState(XINPUT_GAMEPAD_A, 0, 200); @@ -339,7 +339,7 @@ int main() { // t=50 でAを離す(トリガは押したまま) -> 位相リセット XFireEngine::SetTestClockNowMs(50.0); - XINPUT_STATE s1 = MakeState(0, 0, 200); // A離、R2押 + XINPUT_STATE s1 = MakeState(0, 0, 200); // A離、RT押 XFireEngine::Apply(0, &s1); Check(s1.Gamepad.wButtons == 0, "T17: t=50 A released -> no buttons"); @@ -530,7 +530,7 @@ int main() { Check(d.firstOnMs == 200, "T28c: default firstOnMs == 200"); Check(d.triggerThreshold == 150, "T28d: default triggerThreshold == 150"); Check(d.hysteresisLow == 140, "T28e: default hysteresisLow == 140"); - Check(d.enableL2 && d.enableR2, "T28f: default enableL2/R2 true"); + Check(d.enableLT && d.enableRT, "T28f: default enableLT/RT true"); Check(d.toggleButtons == (XINPUT_GAMEPAD_LEFT_SHOULDER | XINPUT_GAMEPAD_A), "T28g: default toggleButtons LB|A"); Check(d.defaultEnabled == false, "T28h: default defaultEnabled false"); Check(d.announceEnabled == true, "T28i: default announceEnabled true"); @@ -559,35 +559,35 @@ int main() { // T30: bool 項目は 1/0/true/false/yes/no を受理 { std::wstring path = tempIniPath(L"xfire_t30.ini"); - WritePrivateProfileStringW(L"XFire", L"EnableL2", L"0", path.c_str()); - WritePrivateProfileStringW(L"XFire", L"EnableR2", L"false", path.c_str()); + WritePrivateProfileStringW(L"XFire", L"EnableLT", L"0", path.c_str()); + WritePrivateProfileStringW(L"XFire", L"EnableRT", L"false", path.c_str()); WritePrivateProfileStringW(L"XFire", L"AnnounceEnabled", L"yes", path.c_str()); WritePrivateProfileStringW(L"XFire", L"StartupSound", L"no", path.c_str()); const XFireConfig& c = loadFromIni(path); - Check(c.enableL2 == false, "T30: EnableL2=0 -> false"); - Check(c.enableR2 == false, "T30b: EnableR2=false -> false"); + Check(c.enableLT == false, "T30: EnableLT=0 -> false"); + Check(c.enableRT == false, "T30b: EnableRT=false -> false"); Check(c.announceEnabled == true, "T30c: AnnounceEnabled=yes -> true"); Check(c.startupSound == false, "T30d: StartupSound=no -> false"); DeleteFileW(path.c_str()); } - // T30e: bool 不明値は既定(EnableL2 既定=true) + // T30e: bool 不明値は既定(EnableLT 既定=true) { std::wstring path = tempIniPath(L"xfire_t30e.ini"); - WritePrivateProfileStringW(L"XFire", L"EnableL2", L"maybe", path.c_str()); + WritePrivateProfileStringW(L"XFire", L"EnableLT", L"maybe", path.c_str()); const XFireConfig& c = loadFromIni(path); - Check(c.enableL2 == true, "T30e: EnableL2=maybe (unknown) -> default true"); + Check(c.enableLT == true, "T30e: EnableLT=maybe (unknown) -> default true"); DeleteFileW(path.c_str()); } - // T30f: bool true/1 受理(従来 EnableL2=true は 0->false になっていた silent failure の回帰防止) + // T30f: bool true/1 受理(従来 EnableLT=true は 0->false になっていた silent failure の回帰防止) { std::wstring path = tempIniPath(L"xfire_t30f.ini"); - WritePrivateProfileStringW(L"XFire", L"EnableL2", L"true", path.c_str()); - WritePrivateProfileStringW(L"XFire", L"EnableR2", L"1", path.c_str()); + WritePrivateProfileStringW(L"XFire", L"EnableLT", L"true", path.c_str()); + WritePrivateProfileStringW(L"XFire", L"EnableRT", L"1", path.c_str()); const XFireConfig& c = loadFromIni(path); - Check(c.enableL2 == true, "T30f: EnableL2=true -> true (was silently false)"); - Check(c.enableR2 == true, "T30g: EnableR2=1 -> true"); + Check(c.enableLT == true, "T30f: EnableLT=true -> true (was silently false)"); + Check(c.enableRT == true, "T30g: EnableRT=1 -> true"); DeleteFileW(path.c_str()); } diff --git a/src/test/xinput_probe.cpp b/src/test/xinput_probe.cpp index 549bad3..de32ad7 100644 --- a/src/test/xinput_probe.cpp +++ b/src/test/xinput_probe.cpp @@ -35,7 +35,7 @@ static void Probe(const wchar_t* name) { XINPUT_STATE st{}; DWORD hr = fn(i, &st); if (hr == 0) { // ERROR_SUCCESS - wprintf(L"[%s] idx=%lu CONNECTED pkt=%lu L2=%u R2=%u btn=0x%04x\n", + wprintf(L"[%s] idx=%lu CONNECTED pkt=%lu LT=%u RT=%u btn=0x%04x\n", name, i, st.dwPacketNumber, st.Gamepad.bLeftTrigger, st.Gamepad.bRightTrigger, st.Gamepad.wButtons); } From fb7ad77e5a96897f87d5f2e2e06966b720a3a805 Mon Sep 17 00:00:00 2001 From: mayaqf Date: Sun, 23 Aug 2026 19:03:05 +0900 Subject: [PATCH 2/2] fix: detect removed ini keys, harden version fallback, add gate tests Address review findings on the v1.0.1 LT/RT rename PR: - Config.cpp: detect removed EnableL2/EnableR2 keys and emit a [CONFIG] warning (value still ignored = clean break maintained). Prevents a user's "disabled" intent from silently flipping to the default (enabled=true) on upgrade with no diagnostic trace. - Version.h: guard MAJOR/MINOR/PATCH fallbacks individually so a partial macro injection cannot yield a malformed half-version; add trailing newline. - xfire_unit.cpp: add T31 (clean break: removed keys ignored -> default true), T32/T32b (engine respects enableLT/RT=false per-trigger gate), and extend T30e to also cover EnableRT unknown-value fallback. 98 checks, 0 failed. - README.md: full XINPUT_GAMEPAD_RIGHT_SHOULDER name, version placeholder in the [STICKYINIT] doc line, and mention removed-key detection in the [CONFIG] description. - assets/XInputXFire.ini: drop trailing whitespace on a blank line. Co-Authored-By: Claude --- README.md | 6 ++--- assets/XInputXFire.ini | 2 +- src/proxy/Config.cpp | 14 +++++++++++ src/proxy/Version.h | 6 +++++ src/test/xfire_unit.cpp | 55 ++++++++++++++++++++++++++++++++++++++++- 5 files changed, 78 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 7843fc4..9449452 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ game.exe ─(暗黙DLL解決)─▶ ローカル xinput1_3.dll [プロキシ] > | Xbox | PS | XInput API | > |---|---|---| > | LT / RT | L2 / R2 | `bLeftTrigger` / `bRightTrigger`(連射発動トリガ・アナログ) | -> | LB / RB | L1 / R1 | `XINPUT_GAMEPAD_LEFT_SHOULDER` / `RIGHT_SHOULDER`(デジタル肩) | +> | LB / RB | L1 / R1 | `XINPUT_GAMEPAD_LEFT_SHOULDER` / `XINPUT_GAMEPAD_RIGHT_SHOULDER`(デジタル肩) | > | A / B / X / Y | × / ○ / □ / △ | `XINPUT_GAMEPAD_A/B/X/Y`(顔ボタン・位置対応) | ## 設定ファイル (XInputXFire.ini) @@ -119,9 +119,9 @@ ctest --test-dir build-x64 --output-on-failure コントローラが無反応・連射が効かない場合、プロキシDLLは起動時(初回エクスポート呼出時)の診断結果を **`%TEMP%\XInputXFire_xinput.log`** に1行ずつ追記します。このログで原因を切り分けられます。 -- `[STICKYINIT] version=1.0.1 LoadOnce=1 hDll=... GetState=...` → プロキシが正常にロードされ、本物DLLの関数ポインタを取得した(`version=` はビルドバージョン、`hDll=0000000000000000` なら本物DLLロード失敗)。 +- `[STICKYINIT] version=<ビルドバージョン> LoadOnce=1 hDll=... GetState=...` → プロキシが正常にロードされ、本物DLLの関数ポインタを取得した(`version=` はビルドバージョン、`hDll=0000000000000000` なら本物DLLロード失敗)。 - `[LOADER] ...` → 本物DLLのロード失敗・必須エクスポート欠落等(フォールバック先DLLの切り替え状況)。 -- `[CONFIG] ...` → ini の値が非数値・範囲外・不明トークンで既定値に置換された(意図しない挙動の原因特定に)。 +- `[CONFIG] ...` → ini の値が非数値・範囲外・不明トークンで既定値に置換された、または廃止キー(`EnableL2`/`EnableR2`)が検出された(意図しない挙動の原因特定に)。 - `[XFIRE] QueryPerformanceFrequency returned 0 ...` → 高精度タイマ取得失敗(連射機能が無効化・パススルーのみ動作)。 **プレイ中はこのログは増えません**。診断行は起動時のみ書かれ、毎フレームの `XInputGetState` / 連射処理のホットパスはログを書きません(長時間プレイでも肥大化しません)。ログが `[STICKYINIT]` 1行だけで後が続かない場合はプロキシは正常に動いているので、コントローラ無反応は「ゲームが XInput 経由で入力を取得していない(非対応)」の可能性が高いです(→ [対応ゲームの条件](#対応ゲームの条件))。 diff --git a/assets/XInputXFire.ini b/assets/XInputXFire.ini index 71b44e0..783a147 100644 --- a/assets/XInputXFire.ini +++ b/assets/XInputXFire.ini @@ -16,7 +16,7 @@ FirstOnMs=200 TriggerThreshold=150 ; 離上判定の閾値(0-255)。連射中はこの値未満で解除(ヒステリシス=チャタリング防止)。 HysteresisLow=140 - + ; 連射対象ボタン(|区切り)。省略時は下記デフォルト。 ; 対応名: DPAD_UP DPAD_DOWN DPAD_LEFT DPAD_RIGHT A B X Y LB RB START BACK LSB RSB TargetButtons=DPAD_UP|DPAD_DOWN|DPAD_LEFT|DPAD_RIGHT|A|B|X|Y diff --git a/src/proxy/Config.cpp b/src/proxy/Config.cpp index 1f94814..96dd0fd 100644 --- a/src/proxy/Config.cpp +++ b/src/proxy/Config.cpp @@ -93,6 +93,20 @@ void Config::LoadOnce() { // EnableLT/RT は 1/0/true/false/yes/no/on/off で記録(不明値=既定+ログ)。 g_cfg.enableLT = readBool(L"EnableLT", "EnableLT", g_cfg.enableLT); g_cfg.enableRT = readBool(L"EnableRT", "EnableRT", g_cfg.enableRT); + // 廃止キー(EnableL2/EnableR2)の検出: クリーンブレイクで値は読まないが、 + // 移行時にユーザの意図が黙って反転(無効化->既定true=有効化)するのを防ぐため存在を警告する。 + struct LegacyKey { const wchar_t* oldName; const wchar_t* newName; }; + for (const LegacyKey& lk : { LegacyKey{L"EnableL2", L"EnableLT"}, LegacyKey{L"EnableR2", L"EnableRT"} }) { + wchar_t lb[32] = {0}; + GetPrivateProfileStringW(sec, lk.oldName, L"", lb, 32, ini.c_str()); + if (lb[0] != L'\0') { + char msg[160]; + sprintf_s(msg, sizeof(msg), + "[CONFIG] %ls is removed (renamed to %ls); ignored. Use the new key.", + lk.oldName, lk.newName); + DiagLog::Log(msg); + } + } wchar_t buf[256] = {0}; GetPrivateProfileStringW(sec, L"TargetButtons", L"", buf, 256, ini.c_str()); int unk = 0; diff --git a/src/proxy/Version.h b/src/proxy/Version.h index 3ffc90e..7af22f4 100644 --- a/src/proxy/Version.h +++ b/src/proxy/Version.h @@ -2,9 +2,15 @@ // 数値を文字列化して "MAJOR.MINOR.PATCH" を構成。文字列マクロのジェネレータ依存の // クォート問題を回避するため数値で渡してここで組み立てる。未注入時は 0.0.0 フォールバック。 #pragma once +// フォールバックは MAJOR/MINOR/PATCH 個別にガードする。1つだけ定義(部分注入)でも +// 残りが既定0になるため、異常な中途半端バージョンにならず安全。 #ifndef XFIRE_VERSION_MAJOR #define XFIRE_VERSION_MAJOR 0 +#endif +#ifndef XFIRE_VERSION_MINOR #define XFIRE_VERSION_MINOR 0 +#endif +#ifndef XFIRE_VERSION_PATCH #define XFIRE_VERSION_PATCH 0 #endif #define XFIRE_VERSION_STR_(a) #a diff --git a/src/test/xfire_unit.cpp b/src/test/xfire_unit.cpp index fd0bc46..074a0c4 100644 --- a/src/test/xfire_unit.cpp +++ b/src/test/xfire_unit.cpp @@ -571,15 +571,68 @@ int main() { DeleteFileW(path.c_str()); } - // T30e: bool 不明値は既定(EnableLT 既定=true) + // T30e: bool 不明値は既定(EnableLT/RT 既定=true)。EnableRT 側も対称的に検証。 { std::wstring path = tempIniPath(L"xfire_t30e.ini"); WritePrivateProfileStringW(L"XFire", L"EnableLT", L"maybe", path.c_str()); + WritePrivateProfileStringW(L"XFire", L"EnableRT", L"maybe", path.c_str()); const XFireConfig& c = loadFromIni(path); Check(c.enableLT == true, "T30e: EnableLT=maybe (unknown) -> default true"); + Check(c.enableRT == true, "T30e2: EnableRT=maybe (unknown) -> default true"); DeleteFileW(path.c_str()); } + // T31: クリーンブレイク — 廃止キー EnableL2/EnableR2 は値を読まず無視(新キー不在=>既定 true)。 + // 旧キーに 0(無効) を書いても新キーが不在なら既定(true)のまま。旧キーのフォールバック読込が + // 再導入されるとこのテストが失敗する(リネームの破壊的変更の回帰防止)。 + { + std::wstring path = tempIniPath(L"xfire_t31.ini"); + WritePrivateProfileStringW(L"XFire", L"EnableL2", L"0", path.c_str()); + WritePrivateProfileStringW(L"XFire", L"EnableR2", L"0", path.c_str()); + const XFireConfig& c = loadFromIni(path); + Check(c.enableLT == true, "T31: removed EnableL2=0 ignored -> enableLT default true"); + Check(c.enableRT == true, "T31b: removed EnableR2=0 ignored -> enableRT default true"); + DeleteFileW(path.c_str()); + } + + // T32: エンジンは enableLT/RT フラグでトリガを個別にゲートする(フラグ=false の側は発動しない)。 + // 既定config(両方true)で発動するのは既存テスト(T2/T3)で担保済み。ここでは抑制側を検証: + // フラグ=false のトリガを押しっ放しでも OFF 区間相当(t=60)で対象ボタン(A)が物理押下のまま + // トグルしないこと(=連射未発動)を確認する。フラグガードが削られると LT/RT が閾値を超えて + // 連射発動し A が OFF 区間で強制離されるため、このテストが失敗する。 + { + XFireConfig tcfg; + Config::ApplyDefaults(tcfg); + tcfg.onMs = 50; tcfg.offMs = 50; tcfg.firstOnMs = 0; + tcfg.triggerThreshold = 128; tcfg.hysteresisLow = 64; + tcfg.toggleButtons = 0; tcfg.announceEnabled = false; + XFireEngine::SetMasterEnabled(true); + + // enableLT=false -> LT=200+物理A で連射せず。t=60(OFF区間相当)でも A は物理押下のまま。 + tcfg.enableLT = false; tcfg.enableRT = true; + Config::SetForTest(tcfg); + XFireEngine::ResetControllerState(); + XFireEngine::SetTestClockNowMs(0.0); + XINPUT_STATE a0 = MakeState(XINPUT_GAMEPAD_A, 200, 0); + XFireEngine::Apply(0, &a0); // t=0 + XFireEngine::SetTestClockNowMs(60.0); + XINPUT_STATE a1 = MakeState(XINPUT_GAMEPAD_A, 200, 0); + XFireEngine::Apply(0, &a1); // t=60 + Check((a1.Gamepad.wButtons & XINPUT_GAMEPAD_A) != 0, "T32: enableLT=false -> LT suppressed, A stays physical at OFF phase"); + + // enableRT=false -> RT=200+物理A で連射せず。t=60(OFF区間相当)でも A は物理押下のまま。 + tcfg.enableLT = true; tcfg.enableRT = false; + Config::SetForTest(tcfg); + XFireEngine::ResetControllerState(); + XFireEngine::SetTestClockNowMs(0.0); + XINPUT_STATE b0 = MakeState(XINPUT_GAMEPAD_A, 0, 200); + XFireEngine::Apply(0, &b0); // t=0 + XFireEngine::SetTestClockNowMs(60.0); + XINPUT_STATE b1 = MakeState(XINPUT_GAMEPAD_A, 0, 200); + XFireEngine::Apply(0, &b1); // t=60 + Check((b1.Gamepad.wButtons & XINPUT_GAMEPAD_A) != 0, "T32b: enableRT=false -> RT suppressed, A stays physical at OFF phase"); + } + // T30f: bool true/1 受理(従来 EnableLT=true は 0->false になっていた silent failure の回帰防止) { std::wstring path = tempIniPath(L"xfire_t30f.ini");