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);
}
...
参考