実行ファイルの DirectInput の API コールをフックして何かする


## GitHub リポジトリ

フック対象:
https://github.com/James2022-rgb/direct_input_example

フックするやつ:
https://github.com/James2022-rgb/direct_input_hook_test

## 今回のフック対象

DirectInput で接続されているゲームコントローラを取得し, POV(ハットスイッチ), アナログ軸, ボタンの状態を表示するもの.
(`direct_input_example.exe`)

![direct_input_example_usFiZl4mjX.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100016/b0bb0534-ce17-462f-9e5e-98dfa1e2d519.png)

https://github.com/James2022-rgb/direct_input_example

## 戦略

DirectInput を初期化して `IDirectInput8` を生成するのに用いられる [`DirectInput8Create`](https://learn.microsoft.com/en-us/previous-versions/windows/desktop/ee416756(v=vs.85)) をフックする.
オリジナルの `IDirectInput8` をラップし, こちらも `IDirectInput8` インターフェースを実装するカスタムクラスを返す.

アプリケーションによっては `DirectInput8Create` ではなく [`CoCreateInstance`](https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-cocreateinstance) を使っている場合があるので, こちらもフックするようにしておく.

よって文字通りにフックする関数は, `DirectInput8Create` と `CoCreateInstance` の2つだけであり,
`IDirectInput8` の各メソッドの実装にて, ラップしているオリジナルのオブジェクトのそれを呼ぶ前後に, こちらが行いたい処理を入れることになる.

:::note
これは RenderDoc が DX11 や DX12 について行っていることにかなり近いはず.
:::

https://github.com/James2022-rgb/direct_input_hook_test

## `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](https://github.com/microsoft/Detours) を用いる.

[`DetourCreateProcessWithDllsA`/`DetourCreateProcessWithDllsW`](https://github.com/microsoft/detours/wiki/DetourCreateProcessWithDlls) は Windows API の `CreateProcess`(`CreateProcessA`/`CreateProcessW`) を用いてプロセスをサスペンド状態(`CREATE_SUSPENDED`)で起動するが,
その際指定された DLL が一番最初にロードされるように, メモリ上のバイナリのイメージを書き換えてくれる.



https://github.com/James2022-rgb/direct_input_hook_test/blob/master/hook_injector/main.cpp
```cpp:hook_injector.exe : main.cpp
#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:
```cpp:hook.dll : 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 で元の関数ポインタの呼び出し先をこれらに書き換える:

:::note warn
フック先でオリジナルの関数を呼べるようにするため,
[`DetourAttach`](https://github.com/microsoft/detours/wiki/DetourAttach) でなく [`DetourAttachEx`](https://github.com/microsoft/detours/wiki/DetourAttachEx) を用いて,
オリジナルの関数を呼ぶための関数ポインタを受け取っておく.

今回は `g_original` に格納する.
:::

```cpp:hook.dll : dllmain.cpp

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` の引数をいちおう説明しておくなら:
```cpp
DetourAttachEx(
  // フックする対象の関数のポインタ(のポインタ)
  reinterpret_cast<PVOID*>(&g_target.fpDirectInput8Create),
  // 新たな呼び出し先とする関数のポインタ
  Hooked_DirectInput8Create,
  // オリジナルの関数を呼ぶための関数ポインタの受け取り先
  reinterpret_cast<PDETOUR_TRAMPOLINE*>(&g_original.fpDirectInput8Create),
  nullptr, nullptr
);
```

#### `DllMain` の `DLL_PROCESS_DETACH` 時の処理

順当に後始末を行う.

```cpp:hook.dll : dllmain.cpp
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` が呼ばれるようになる.

### 注意点

:::note warn
- `DetourCreateProcessWithDlls` の仕様として, 対象 DLL は ordinal #1 に何らかの関数をエクスポートしている必要がある.
- `DetourAttach`, `DetourAttachEx` でフックした後には `DetourRestoreAfterWith` を呼んでおく必要がある.
:::

## `Hooked_DirectInput8Create`, `Hooked_CoCreateInstance` ですること

(上述のように, `IDirectInput8A` のみの対応とし, `IDirectInput8W` の場合はオリジナルの関数を呼ぶだけとする)

COM インターフェースの GUID として `IID_IDirectInput8A` ならば, 我々がフックしたいケースになる.

この場合に, オリジナルの関数を呼んで得た `IDirectInput8A` オブジェクトについて,
これをラップしたカスタムのオブジェクトを生成して,
オリジナルのものの代わりに `ppvOut`, `ppv` を通して返すようにする.
(`Hooked_CoCreateInstance` の場合には `CLSID_DirectInput8` のチェックもある)

:::note info
興味のないケースならば, 単純にオリジナルの関数を呼ぶようにしておく.
:::

```cpp:hook.dll : dllmain.cpp
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` で参照カウンタがゼロになっていれば自身を削除する.

```cpp
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` オブジェクト:
```cpp
// 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:
```cpp:hook.dll : 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` の場合と殆ど変わらないので省略する.
(実装する必要のあるメソッドの数が多い)

https://github.com/James2022-rgb/direct_input_hook_test/blob/master/hook/wrapped_directinputdevice8.h

## 実際に何かしてみる

プロセスの DirectInput の API コールのログを取る, などがこれで可能になるが,
これだけでもつまらないので, フック対象のプロセスの動きに影響を与えてみる.

### プロセスから見えるデバイスの名前を変えてみる

元の名前の先頭に "RENAMED: " を付けたものを名前としてみる.

![direct_input_example_xAg66o7Oju.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100016/ed7f07a5-cc51-4f56-8211-4ba8167dc1f2.png)

名前を扱うのは, `IDirectInput8A::EnumDevices` と, `IDirectInputDevice8A::GetProperty` で `DIPROP_PRODUCTNAME` のケースになる.

`IDirectInput8A::EnumDevices` では `DIDEVICEINSTANCEA` の名前の部分を書き換えたもので元のコールバックを呼んでやるようにする:

https://github.com/James2022-rgb/direct_input_hook_test/blob/20bad169ac59077a40ce9f9b0a43be11320f1737/hook/wrapped_directinput8.h#L78-L113

```cpp:hook.dll : wrapped_directinput8.h
  ...

  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` の場合で,
オリジナルの実装を呼んで得たものを書き換えた名前を返すようにする:

https://github.com/James2022-rgb/direct_input_hook_test/blob/20bad169ac59077a40ce9f9b0a43be11320f1737/hook/wrapped_directinputdevice8.h#L209-L235

```cpp:hook.dll : wrapped_directinputdevice8.h
  ...
  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` を呼び出して得たアナログ軸の値を, 常に最大のものとする.

![direct_input_example_Wh4kdQLuWi.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100016/f36aff69-ddb8-4a4e-a685-01ddd1ff92a9.png)
(実機の操作状態に依らずこうなる)

`IDirectInputDevice8A::SetDataFormat` されたデータフォーマットが `c_dfDIJoystick2` であった場合のみ i.e. `DIJOYSTATE2` のみ, の対応とする.
まずはこれをチェック:

https://github.com/James2022-rgb/direct_input_hook_test/blob/20bad169ac59077a40ce9f9b0a43be11320f1737/hook/wrapped_directinputdevice8.h#L286-L323

```cpp:hook.dll : wrapped_directinputdevice8.h
  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` して得た最大値を用いる:

https://github.com/James2022-rgb/direct_input_hook_test/blob/20bad169ac59077a40ce9f9b0a43be11320f1737/hook/wrapped_directinputdevice8.h#L135-L180

```cpp
  ...

  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);
  }

  ...
```

## 参考

https://qiita.com/msmania/items/ad34e5e210720f42998b

https://openpunk.com/journal/preinject-dlls/