GitHub リポジトリ
フック対象:
https://github.com/James2022-rgb/direct_input_example
フックするやつ:
https://github.com/James2022-rgb/direct_input_hook_test
今回のフック対象
DirectInput で接続されているゲームコントローラを取得し, POV(ハットスイッチ), アナログ軸, ボタンの状態を表示するもの.
(direct_input_example.exe)
戦略
DirectInput を初期化して IDirectInput8 を生成するのに用いられる DirectInput8Create をフックする.
オリジナルの IDirectInput8 をラップし, こちらも IDirectInput8 インターフェースを実装するカスタムクラスを返す.
アプリケーションによっては DirectInput8Create ではなく CoCreateInstance を使っている場合があるので, こちらもフックするようにしておく.
よって文字通りにフックする関数は, DirectInput8Create と CoCreateInstance の2つだけであり,
IDirectInput8 の各メソッドの実装にて, ラップしているオリジナルのオブジェクトのそれを呼ぶ前後に, こちらが行いたい処理を入れることになる.
これは RenderDoc が DX11 や DX12 について行っていることにかなり近いはず.
IDirectInput8A と IDirectInput8W
文字列を扱う Windows API が基本的にそうであるように, IDirectInput8 は実際にはプリプロセッサ定義であり,
UNICODE の有無によって IDirectInput8W と IDirectInput8A に分けられている.
(ワイド文字列とそうでないもの)
子オブジェクトの IDirectInputDevice8 も同様.
対象となるプロセスがどちらを用いるかわからない場合, 基本的には両方に対応しておく必要があるが,
今回のコードではワイド文字列でない IDirectInput8A や IDirectInputDevice8A のみへの対応としている.
DirectInput8Create と CoCreateInstance のフック
DLL インジェクションを用いる.
ディレクトリ構成
./
├── direct_input_example.exe 👈 フック対象
├── hook.dll                 👈 インジェクトする DLL (フック対象関数のカスタム実装を含む)
├── hook_injector.exe        👈 DLL をインジェクトしてフック対象を起動するやつ
カスタムの IDirectInput8 オブジェクトや DirectInput8Create CoCreateInstance 関数の実装を含む hook.dll を同一ディレクトリに配置し,
さらに hook.dll をインジェクトして direct_input_example.exe を起動する hook_injector.exe を用意する.
hook_injector.exe
実際のインジェクト処理には Microsoft Detours を用いる.
DetourCreateProcessWithDllsA/DetourCreateProcessWithDllsW は Windows API の CreateProcess(CreateProcessA/CreateProcessW) を用いてプロセスをサスペンド状態(CREATE_SUSPENDED)で起動するが,
その際指定された DLL が一番最初にロードされるように, メモリ上のバイナリのイメージを書き換えてくれる.
#include <iostream>
#include <Windows.h>
#include <detours.h>
int main(int argc, char* argv[]) {
  LPCSTR kTargetPath = "direct_input_example.exe";
  LPCSTR kDllPath = "hook.dll";
  STARTUPINFOA si = { sizeof(STARTUPINFOA) };
  PROCESS_INFORMATION pi = { 0 };
  // Launch the target EXE in a suspended state, with our DLL injected.
  BOOL result = DetourCreateProcessWithDllsA(
    kTargetPath,      // lpApplicationName
    nullptr,          // lpCommandLine
    nullptr,          // lpProcessAttributes
    nullptr,          // lpThreadAttributes
    FALSE,            // bInheritHandles
    CREATE_SUSPENDED, // dwCreationFlags
    nullptr,          // lpEnvironment
    nullptr,          // lpCurrentDirectory
    &si,              // lpStartupInfo
    &pi,              // lpProcessInformation
    1,                // nDlls
    &kDllPath,        // rlpDlls
    nullptr           // pfCreateProcessA
  );
  if (!result) {
    std::cerr << "DetourCreateProcessWithDllsA failed: " << result << std::endl;
    return 1;
  }
  // Resume the target process, and wait for it to finish.
  ResumeThread(pi.hThread);
  WaitForSingleObject(pi.hProcess, INFINITE);
  return 0;
}
CREATE_SUSPENDED を指定しておけば, バイナリを書き換えたあと内部で ResumeThread しないでくれるので,
準備が終わればこちらで ResumeThread したあと終了を待機しておく.
hook.dll
こちらでも Microsoft Detours を用いる.
DllMain の DLL_PROCESS_ATTACH 時の処理
この DllMain は対象のプロセスのメモリ空間で, そのエントリポイントよりも先に, かつ元々インポートされるどの DLL よりも最初に, fdwReason == DLL_PROCESS_ATTACH で呼ばれる.
フック対象の関数のポインタの取得
まずはフックする対象の関数のポインタを入手しておく.
今回は g_target に格納する.
GetModuleHandle で取得したそれぞれのモジュールから, GetProcAddress で関数ポインタを取得する.
https://github.com/James2022-rgb/direct_input_hook_test/blob/master/hook/dllmain.cpp:
using FpDirectInput8Create = HRESULT(*)(HINSTANCE, DWORD, REFIID, LPVOID*, LPUNKNOWN);
using FpCoCreateInstance = HRESULT(*)(REFCLSID, LPUNKNOWN, DWORD, REFIID, LPVOID*);
// Function pointers to the original functions.
struct Functions {
  FpDirectInput8Create fpDirectInput8Create = nullptr;
  FpCoCreateInstance fpCoCreateInstance = nullptr;
};
static Functions g_target;
/// Function pointers to the original functions.
static Functions g_original;
...
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
  HMODULE hModuleDinput8 = nullptr;
  HMODULE hModuleOle32 = nullptr;
  if (fdwReason == DLL_PROCESS_ATTACH) {
    MessageBoxA(NULL, "DLL_PROCESS_ATTACH", "direct_input_hook_test", MB_OK);
    // Get the original pointers to the functions we want to hook.
    {
      hModuleDinput8 = GetModuleHandleA("dinput8.dll");
      if (hModuleDinput8 == NULL) {
        MessageBoxA(NULL, "Failed to load original dinput8.dll", "direct_input_hook_test", MB_ICONERROR | MB_OK);
        return FALSE;
      }
      g_target.fpDirectInput8Create = (FpDirectInput8Create)GetProcAddress(hModuleDinput8, "DirectInput8Create");
    }
    {
      hModuleOle32 = GetModuleHandleA("ole32.dll");
      if (hModuleOle32 == NULL) {
        MessageBoxA(NULL, "Failed to load original ole32.dll", "direct_input_hook_test", MB_ICONERROR | MB_OK);
        return FALSE;
      }
      g_target.fpCoCreateInstance = (FpCoCreateInstance)GetProcAddress(hModuleOle32, "CoCreateInstance");
    }
    // Detour the original functions.
    {
      ...
    }
  }
  else if (fdwReason == DLL_PROCESS_DETACH) {
    ...
  }
  return TRUE;
}
実際のフック処理
カスタムの DirectInput8Create, CoCreateInstance を用意して,
Microsoft Detours で元の関数ポインタの呼び出し先をこれらに書き換える:
フック先でオリジナルの関数を呼べるようにするため,
DetourAttach でなく DetourAttachEx を用いて,
オリジナルの関数を呼ぶための関数ポインタを受け取っておく.
今回は g_original に格納する.
HRESULT WINAPI Hooked_DirectInput8Create(
  HINSTANCE hinst,
  DWORD dwVersion,
  REFIID riidltf,
  LPVOID* ppvOut,
  LPUNKNOWN punkOuter
) {
  ... (後述)
}
HRESULT WINAPI Hooked_CoCreateInstance(
  REFCLSID  rclsid,
  LPUNKNOWN pUnkOuter,
  DWORD     dwClsContext,
  REFIID    riid,
  LPVOID* ppv
) {
  ... (後述)
}
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
  HMODULE hModuleDinput8 = nullptr;
  HMODULE hModuleOle32 = nullptr;
  if (fdwReason == DLL_PROCESS_ATTACH) {
    MessageBoxA(NULL, "DLL_PROCESS_ATTACH", "direct_input_hook_test", MB_OK);
    ...
    // Detour the original functions.
    {
      DetourTransactionBegin();
      DetourUpdateThread(GetCurrentThread());
      DetourAttachEx(
        reinterpret_cast<PVOID*>(&g_target.fpDirectInput8Create),
        Hooked_DirectInput8Create,
        reinterpret_cast<PDETOUR_TRAMPOLINE*>(&g_original.fpDirectInput8Create), // Receive the function pointer to call the original function.
        nullptr, nullptr
      );
      DetourAttachEx(
        reinterpret_cast<PVOID*>(&g_target.fpCoCreateInstance),
        Hooked_CoCreateInstance,
        reinterpret_cast<PDETOUR_TRAMPOLINE*>(&g_original.fpCoCreateInstance), // Receive the function pointer to call the original function.
        nullptr, nullptr
      );
      DetourTransactionCommit();
      DetourRestoreAfterWith();
    }
  }
  else if (fdwReason == DLL_PROCESS_DETACH) {
    ...
  }
  return TRUE;
}
DetourAttachEx の引数をいちおう説明しておくなら:
DetourAttachEx(
  // フックする対象の関数のポインタ(のポインタ)
  reinterpret_cast<PVOID*>(&g_target.fpDirectInput8Create),
  // 新たな呼び出し先とする関数のポインタ
  Hooked_DirectInput8Create,
  // オリジナルの関数を呼ぶための関数ポインタの受け取り先
  reinterpret_cast<PDETOUR_TRAMPOLINE*>(&g_original.fpDirectInput8Create),
  nullptr, nullptr
);
DllMain の DLL_PROCESS_DETACH 時の処理
順当に後始末を行う.
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
  HMODULE hModuleDinput8 = nullptr;
  HMODULE hModuleOle32 = nullptr;
  if (fdwReason == DLL_PROCESS_ATTACH) {
    ...
  }
  else if (fdwReason == DLL_PROCESS_DETACH) {
    MessageBoxA(NULL, "DLL_PROCESS_DETACH", "direct_input_hook_test", MB_OK);
    // Restore the original functions.
    {
      DetourTransactionBegin();
      DetourUpdateThread(GetCurrentThread());
      DetourDetach(
        reinterpret_cast<PVOID*>(&g_target.fpDirectInput8Create),
        Hooked_DirectInput8Create
      );
      DetourDetach(
        reinterpret_cast<PVOID*>(&g_target.fpCoCreateInstance),
        Hooked_CoCreateInstance
      );
      DetourTransactionCommit();
    }
    if (hModuleDinput8 != nullptr) {
      FreeLibrary(hModuleDinput8);
    }
    if (hModuleOle32 != nullptr) {
      FreeLibrary(hModuleOle32);
    }
  }
  return TRUE;
}
すると
hook_injector.exe を起動すると direct_input_example.exe が起動し,
そのプロセス内での DirectInput8Create, CoCreateInstance の呼び出しで,
こちらで用意した Hooked_DirectInput8Create, Hooked_CoCreateInstance が呼ばれるようになる.
注意点
- 
DetourCreateProcessWithDllsの仕様として, 対象 DLL は ordinal #1 に何らかの関数をエクスポートしている必要がある. - 
DetourAttach,DetourAttachExでフックした後にはDetourRestoreAfterWithを呼んでおく必要がある. 
Hooked_DirectInput8Create, Hooked_CoCreateInstance ですること
(上述のように, IDirectInput8A のみの対応とし, IDirectInput8W の場合はオリジナルの関数を呼ぶだけとする)
COM インターフェースの GUID として IID_IDirectInput8A ならば, 我々がフックしたいケースになる.
この場合に, オリジナルの関数を呼んで得た IDirectInput8A オブジェクトについて,
これをラップしたカスタムのオブジェクトを生成して,
オリジナルのものの代わりに ppvOut, ppv を通して返すようにする.
(Hooked_CoCreateInstance の場合には CLSID_DirectInput8 のチェックもある)
興味のないケースならば, 単純にオリジナルの関数を呼ぶようにしておく.
HRESULT WINAPI Hooked_DirectInput8Create(
  HINSTANCE hinst,
  DWORD dwVersion,
  REFIID riidltf,
  LPVOID* ppvOut,
  LPUNKNOWN punkOuter
) {
  if (riidltf == IID_IDirectInput8A) {
    // Something like:
    //  DirectInput8Create(hinst, dwVersion, &IID_IDirectInput8A, ppvOut, punkOuter);
    // Call the original function to get the original IDirectInput8A object.
    LPDIRECTINPUT8A original_direct_input = nullptr;
    HRESULT result = g_original.fpDirectInput8Create(hinst, dwVersion, riidltf, reinterpret_cast<LPVOID*>(&original_direct_input), punkOuter);
    if (FAILED(result)) {
      return result;
    }
    // Wrap the original IDirectInput8A object.
    LPDIRECTINPUT8A wrapped_direct_input = WrappedDirectInput8A::Create(original_direct_input);
    // Return the wrapped object.
    *ppvOut = wrapped_direct_input;
    return S_OK;
  }
  else if (riidltf == IID_IDirectInput8W) {
    // Something like:
    //  DirectInput8Create(hinst, dwVersion, &IID_IDirectInput8W, ppvOut, punkOuter);
    MessageBoxA(NULL, "IID_IDirectInput8W: Not supported.", "direct_input_hook_test", MB_ICONEXCLAMATION | MB_OK);
    // Not supported.
  }
  // Call the original function.
  return g_original.fpDirectInput8Create(hinst, dwVersion, riidltf, ppvOut, punkOuter);
}
HRESULT WINAPI Hooked_CoCreateInstance(
  REFCLSID  rclsid,
  LPUNKNOWN pUnkOuter,
  DWORD     dwClsContext,
  REFIID    riid,
  LPVOID* ppv
) {
  if (rclsid == CLSID_DirectInput8) {
    if (riid == IID_IDirectInput8A) {
      // Something like:
      //  CoCreateInstance(&CLSID_DirectInput8, pUnkOuter, dwClsContext, &IID_IDirectInput8A, ppv);
      // Call the original function to get the original IDirectInput8A object.
      LPDIRECTINPUT8A original_direct_input = nullptr;
      HRESULT result = g_original.fpCoCreateInstance(rclsid, pUnkOuter, dwClsContext, riid, reinterpret_cast<LPVOID*>(&original_direct_input));
      if (FAILED(result)) {
        return result;
      }
      // Wrap the original IDirectInput8A object.
      LPDIRECTINPUT8A wrapped_direct_input = WrappedDirectInput8A::Create(original_direct_input);
      // Return the wrapped object.
      *ppv = wrapped_direct_input;
    }
    else if (riid == IID_IDirectInput8W) {
      // Something like:
      //  CoCreateInstance(&CLSID_DirectInput8, pUnkOuter, dwClsContext, &IID_IDirectInput8W, ppv);
      MessageBoxA(NULL, "CLSID_DirectInput8 && IID_IDirectInput8W: Not supported.", "direct_input_hook_test", MB_ICONEXCLAMATION | MB_OK);
      // Not supported.
    }
  }
  // Call the original function.
  return g_original.fpCoCreateInstance(rclsid, pUnkOuter, dwClsContext, riid, ppv);
}
オリジナルをラップしたオブジェクト
IDirectInput8A インターフェースを実装し, メソッドの実装でオリジナルのそれを呼びながら,
その前後にこちらの処理を突っ込む.
共通
IUnknown インターフェースのメソッドの実装は単純にオリジナルのそれに移譲するが,
COM インターフェースのルールに従って, Release で参照カウンタがゼロになっていれば自身を削除する.
HRESULT QueryInterface(
  REFIID riid,
  void** ppvObject
) override {
  return original_->QueryInterface(riid, ppvObject);
}
ULONG AddRef() override {
  return original_->AddRef();
}
ULONG Release() override {
  ULONG const result = original_->Release();
  if (result == 0) {
    delete this;
  }
  return result;
}
IDirectInput8A
上記で返しているカスタムの IDirectInput8A オブジェクト:
// Wrap the original IDirectInput8A object.
LPDIRECTINPUT8A wrapped_direct_input = WrappedDirectInput8A::Create(original_direct_input);
// Return the wrapped object.
*ppvOut = wrapped_direct_input;
オリジナルの IDirectInput8A に移譲する.
ただし, IDirectInputDevice8A のメソッドもフックしたいので, IDirectInput8A::CreateDevice の実装では,
オリジナルの IDirectInputDevice8A をラップしたカスタムのオブジェクトを返す.
https://github.com/James2022-rgb/direct_input_hook_test/blob/master/hook/wrapped_directinput8.h:
...
class WrappedDirectInput8A : public IDirectInput8A {
public:
  ...
  //
  // IUnknown interface.
  //
  ...
  //
  // IDirectInput8A interface.
  //
  HRESULT ConfigureDevices(
    LPDICONFIGUREDEVICESCALLBACK lpdiCallback,
    LPDICONFIGUREDEVICESPARAMS lpdiCDParams,
    DWORD dwFlags,
    LPVOID pvRefData
  ) override {
    return original_->ConfigureDevices(lpdiCallback, lpdiCDParams, dwFlags, pvRefData);
  }
  HRESULT CreateDevice(
    REFGUID rguid,
    LPDIRECTINPUTDEVICE8A* lplpDirectInputDevice,
    LPUNKNOWN pUnkOuter
  ) override {
    MessageBoxA(NULL, "WrappedDirectInput8A::CreateDevice", "direct_input_hook_test", MB_OK);
    // Call the original method to actually create the device.
    LPDIRECTINPUTDEVICE8A original_device = nullptr;
    HRESULT hr = original_->CreateDevice(rguid, &original_device, pUnkOuter);
    if (SUCCEEDED(hr)) {
      // Wrap the original device in our custom implementation.
      *lplpDirectInputDevice = WrappedDirectInputDevice8A::Create(original_device);
    }
    return hr;
  }
  HRESULT EnumDevices(
    DWORD dwDevType,
    LPDIENUMDEVICESCALLBACKA lpCallback,
    LPVOID pvRef,
    DWORD dwFlags
  ) override {
    return original_->EnumDevices(dwDevType, lpCallback, pvRef, dwFlags);
  }
  HRESULT EnumDevicesBySemantics(
    LPCSTR ptszUserName,
    LPDIACTIONFORMATA lpdiActionFormat,
    LPDIENUMDEVICESBYSEMANTICSCBA lpCallback,
    LPVOID pvRef,
    DWORD dwFlags
  ) override {
    return original_->EnumDevicesBySemantics(ptszUserName, lpdiActionFormat, lpCallback, pvRef, dwFlags);
  }
  HRESULT FindDevice(
    REFGUID rguidClass,
    LPCTSTR ptszName,
    LPGUID pguidInstance
  ) override {
    return original_->FindDevice(rguidClass, ptszName, pguidInstance);
  }
  HRESULT GetDeviceStatus(
    REFGUID rguidInstance
  ) override {
    return original_->GetDeviceStatus(rguidInstance);
  }
  HRESULT Initialize(
    HINSTANCE hinst,
    DWORD dwVersion
  ) override {
    return original_->Initialize(hinst, dwVersion);
  }
  HRESULT RunControlPanel(
    HWND hwndOwner,
    DWORD dwFlags
  ) override {
    return original_->RunControlPanel(hwndOwner, dwFlags);
  }
private:
  explicit WrappedDirectInput8A(IDirectInput8A* original) : original_(original) {}
  IDirectInput8A* original_ = nullptr;;
};
IDirectInputDevice8A
IDirectInput8A の場合と殆ど変わらないので省略する.
(実装する必要のあるメソッドの数が多い)
実際に何かしてみる
プロセスの DirectInput の API コールのログを取る, などがこれで可能になるが,
これだけでもつまらないので, フック対象のプロセスの動きに影響を与えてみる.
プロセスから見えるデバイスの名前を変えてみる
元の名前の先頭に "RENAMED: " を付けたものを名前としてみる.
名前を扱うのは, IDirectInput8A::EnumDevices と, IDirectInputDevice8A::GetProperty で DIPROP_PRODUCTNAME のケースになる.
IDirectInput8A::EnumDevices では DIDEVICEINSTANCEA の名前の部分を書き換えたもので元のコールバックを呼んでやるようにする:
  ...
  HRESULT EnumDevices(
    DWORD dwDevType,
    LPDIENUMDEVICESCALLBACKA lpCallback,
    LPVOID pvRef,
    DWORD dwFlags
  ) override {
#if CONFIG_RENAME_DEVICES
    using FpCallback = BOOL(*)(LPCDIDEVICEINSTANCEA, LPVOID);
    struct Capture final {
      FpCallback original_callback = nullptr;
      LPVOID original_pvRef = nullptr;
    };
    auto DIEnumDevicesCallback = [](LPCDIDEVICEINSTANCEA lpddi, LPVOID pvRef) -> BOOL {
      Capture const& capture = *reinterpret_cast<Capture* const>(pvRef);
      char renamed_product_name[MAX_PATH];
      std::snprintf(renamed_product_name, sizeof(renamed_product_name), "RENAMED: %s", lpddi->tszProductName);
      DIDEVICEINSTANCE custom_instance = *lpddi;
      strcpy_s(custom_instance.tszProductName, renamed_product_name);
      return capture.original_callback(&custom_instance, capture.original_pvRef);
    };
    Capture capture = {
      .original_callback = lpCallback,
      .original_pvRef = pvRef,
    };
    return original_->EnumDevices(dwDevType, DIEnumDevicesCallback, &capture, dwFlags);
#endif
    return original_->EnumDevices(dwDevType, lpCallback, pvRef, dwFlags);
  }
  ...
IDirectInputDevice8A::GetProperty では DIPROP_PRODUCTNAME の場合で,
オリジナルの実装を呼んで得たものを書き換えた名前を返すようにする:
  ...
  HRESULT GetProperty(
    REFGUID rguidProp,
    LPDIPROPHEADER pdiph
  ) override {
#if CONFIG_RENAME_DEVICES
    if (&rguidProp == &DIPROP_PRODUCTNAME) {
      HRESULT hr = original_->GetProperty(rguidProp, pdiph);
      if (FAILED(hr)) {
        return hr;
      }
      DIPROPSTRING* pdipstr = reinterpret_cast<DIPROPSTRING*>(pdiph);
      char product_name[MAX_PATH];
      WideCharToMultiByte(CP_ACP, 0, pdipstr->wsz, -1, product_name, sizeof(product_name), nullptr, nullptr);
      char renamed_product_name[MAX_PATH];
      std::snprintf(renamed_product_name, sizeof(renamed_product_name), "RENAMED: %s", product_name);
      
      MultiByteToWideChar(CP_ACP, 0, renamed_product_name, -1, pdipstr->wsz, sizeof(pdipstr->wsz) / sizeof(WCHAR));
      return S_OK;
    }
#endif
    return original_->GetProperty(rguidProp, pdiph);
  }
  ...
プロセスから見えるアナログ軸の値を常に最大にしてみる
つまり IDirectInputDevice8A::GetDeviceState を呼び出して得たアナログ軸の値を, 常に最大のものとする.
IDirectInputDevice8A::SetDataFormat されたデータフォーマットが c_dfDIJoystick2 であった場合のみ i.e. DIJOYSTATE2 のみ, の対応とする.
まずはこれをチェック:
  HRESULT SetDataFormat(
    LPCDIDATAFORMAT lpdf
  ) override {
#if CONFIG_MAX_AXIS_VALUES
    // See if the data format is `c_dfDIJoystick2`, which is the data format we support.
    if (lpdf->dwSize == c_dfDIJoystick2.dwSize &&
      lpdf->dwObjSize == c_dfDIJoystick2.dwObjSize &&
      lpdf->dwFlags == c_dfDIJoystick2.dwFlags &&
      lpdf->dwDataSize == c_dfDIJoystick2.dwDataSize &&
      lpdf->dwNumObjs == c_dfDIJoystick2.dwNumObjs)
    {
      bool equal = true;
      for (DWORD i = 0; i < lpdf->dwNumObjs; ++i) {
        DIOBJECTDATAFORMAT const& lhs = lpdf->rgodf[i];
        DIOBJECTDATAFORMAT const& rhs = c_dfDIJoystick2.rgodf[i];
        
        if ((lhs.pguid == nullptr) != (rhs.pguid == nullptr)) {
          equal = false;
          break;
        }
        if ((lhs.pguid != nullptr && *lhs.pguid != *rhs.pguid) ||
          lhs.dwOfs != rhs.dwOfs ||
          lhs.dwType != rhs.dwType ||
          lhs.dwFlags != rhs.dwFlags)
        {
          equal = false;
          break;
        }
      }
      data_format_is_joystick2_ = equal;
    }
#endif
    return original_->SetDataFormat(lpdf);
  }
問題なければ, IDirectInputDevice8A::GetDeviceState の実装において,
オリジナルのメソッドを呼んで得た DIJOYSTATE2 について, アナログ軸の値を最大値で書き換える.
DIPROP_RANGE で GetProperty して得た最大値を用いる:
  ...
  HRESULT GetDeviceState(
    DWORD cbData,
    LPVOID lpvData
  ) override {
#if CONFIG_MAX_AXIS_VALUES
    if (data_format_is_joystick2_ && cbData == sizeof(DIJOYSTATE2)) {
      HRESULT hr = original_->GetDeviceState(cbData, lpvData);
      if (FAILED(hr)) {
        return hr;
      }
      DIJOYSTATE2* dst_state = reinterpret_cast<DIJOYSTATE2*>(lpvData);
      auto GetAxisMaxValue = [device = original_](DWORD offset, LONG* pResult) -> HRESULT {
        DIPROPRANGE diprg {};
        diprg.diph.dwSize = sizeof(DIPROPRANGE);
        diprg.diph.dwHeaderSize = sizeof(DIPROPHEADER);
        diprg.diph.dwObj = offset;
        diprg.diph.dwHow = DIPH_BYOFFSET;
        diprg.lMin = 0;
        diprg.lMax = 0;
        HRESULT hr = device->GetProperty(DIPROP_RANGE, &diprg.diph);
        if (FAILED(hr)) {
          return hr;
        }
        *pResult = diprg.lMax;
        return S_OK;
      };
      GetAxisMaxValue(DIJOFS_X, &dst_state->lX);
      GetAxisMaxValue(DIJOFS_Y, &dst_state->lY);
      GetAxisMaxValue(DIJOFS_Z, &dst_state->lZ);
      GetAxisMaxValue(DIJOFS_RX, &dst_state->lRx);
      GetAxisMaxValue(DIJOFS_RY, &dst_state->lRy);
      GetAxisMaxValue(DIJOFS_RZ, &dst_state->lRz);
      GetAxisMaxValue(DIJOFS_SLIDER(0), &dst_state->rglSlider[0]);
      GetAxisMaxValue(DIJOFS_SLIDER(1), &dst_state->rglSlider[1]);
      return S_OK;
    }
#endif
    return original_->GetDeviceState(cbData, lpvData);
  }
  ...
参考


