Refactor IIDX camhook texture replacement (#424)

## Link to GitHub Issue, if one exists
Fixes #393 

## Description of change
- Fixes compatibility with IIDX 33
- Adjusts the pattern scan for "hook A" to be compatible with IIDX 27
- Refactors texture replacement to use Camera::CCameraManager2, removing
the need for a second hook

Related: https://github.com/aixxe/2dxcamhook/compare/0.4.0.0...0.5.0.0

## Testing
Booted with `-iidxcamhook` on IIDX 27, 28, 29, 30, 31, 32 & 33
Confirmed both cameras were visible in I/O test, music select settings &
gameplay
This commit is contained in:
aixxe
2025-11-17 09:21:39 +00:00
committed by GitHub
parent 052698afa3
commit 4c87078807
4 changed files with 112 additions and 83 deletions
+72 -70
View File
@@ -22,35 +22,31 @@
static std::filesystem::path dll_path; static std::filesystem::path dll_path;
static HMODULE iidx_module; static HMODULE iidx_module;
static uintptr_t addr_hook_a = 0; static uintptr_t addr_hook_a = 0;
static uintptr_t addr_hook_b = 0;
static uintptr_t addr_textures = 0; static uintptr_t addr_textures = 0;
static uintptr_t addr_camera_manager = 0;
static uintptr_t addr_device_offset = 0; static uintptr_t addr_device_offset = 0;
static uintptr_t addr_afp_texture_offset = 0;
typedef void **(__fastcall *camera_hook_a_t)(PBYTE); typedef void **(__fastcall *camera_hook_a_t)(PBYTE);
typedef LPDIRECT3DTEXTURE9 (*camera_hook_b_t)(void*, int);
static camera_hook_a_t camera_hook_a_orig = nullptr; static camera_hook_a_t camera_hook_a_orig = nullptr;
static camera_hook_b_t camera_hook_b_orig = nullptr;
auto static hook_a_init = std::once_flag {}; auto static hook_a_init = std::once_flag {};
static LPDIRECT3DDEVICE9EX device; static LPDIRECT3DDEVICE9EX device;
static LPDIRECT3DTEXTURE9 *texture_a = nullptr; static LPDIRECT3DTEXTURE9 *camera_texture_a = nullptr;
static LPDIRECT3DTEXTURE9 *texture_b = nullptr; static LPDIRECT3DTEXTURE9 *camera_texture_b = nullptr;
static LPDIRECT3DTEXTURE9 *preview_texture_a = nullptr;
static LPDIRECT3DTEXTURE9 *preview_texture_b = nullptr;
struct PredefinedHook { struct PredefinedHook {
std::string pe_identifier; std::string pe_identifier;
uintptr_t hook_a; uintptr_t hook_a;
uintptr_t hook_b;
uintptr_t hook_textures; uintptr_t hook_textures;
uintptr_t hook_camera_manager;
uintptr_t hook_device_offset; uintptr_t hook_device_offset;
uintptr_t hook_afp_texture_offset;
}; };
PredefinedHook g_predefinedHooks[] = PredefinedHook g_predefinedHooks[] = {};
{
// 27 003
{ "5f713b52_6d4090", 0x005971b0, 0x005fb2c0, 0x04e49898, 0x000000d0 },
// 27 010
{ "5f713946_7f52b0", 0x006b89e0, 0x0071c950, 0x04fd08b8, 0x000000d0 },
};
const DWORD g_predefinedHooksLength = ARRAYSIZE(g_predefinedHooks); const DWORD g_predefinedHooksLength = ARRAYSIZE(g_predefinedHooks);
@@ -75,8 +71,8 @@ namespace games::iidx {
if (sscanf( if (sscanf(
s.c_str(), s.c_str(),
"%i,%i,%i,%i", "%i,%i,%i,%i,%i",
(int*)&addr_hook_a, (int*)&addr_hook_b, (int*)&addr_textures, (int*)&addr_device_offset) == 4) { (int*)&addr_hook_a, (int*)&addr_textures, (int*)&addr_camera_manager, (int*)&addr_device_offset, (int*)&addr_afp_texture_offset) == 5) {
return true; return true;
@@ -101,8 +97,8 @@ namespace games::iidx {
// there are three different ways we try to hook the camera entry points // there are three different ways we try to hook the camera entry points
// 1) user-provided override via cmd line // 1) user-provided override via cmd line
// 2) predefined offsets using PE header matching (needed for iidx27 and few others) // 2) predefined offsets using PE header matching
// 3) signature match (should work for most iidx28-31) // 3) signature match (should work for most iidx27-33)
// method 1: user provided // method 1: user provided
if (TDJ_CAMERA_OVERRIDE.has_value() && parse_cmd_params()) { if (TDJ_CAMERA_OVERRIDE.has_value() && parse_cmd_params()) {
@@ -117,10 +113,11 @@ namespace games::iidx {
for (DWORD i = 0; i < g_predefinedHooksLength; i++) { for (DWORD i = 0; i < g_predefinedHooksLength; i++) {
if (pe.compare(g_predefinedHooks[i].pe_identifier) == 0) { if (pe.compare(g_predefinedHooks[i].pe_identifier) == 0) {
log_misc("iidx:camhook", "Found predefined addresses"); log_misc("iidx:camhook", "Found predefined addresses");
addr_hook_a = g_predefinedHooks[i].hook_a; addr_hook_a = g_predefinedHooks[i].hook_a;
addr_hook_b = g_predefinedHooks[i].hook_b; addr_textures = g_predefinedHooks[i].hook_textures;
addr_textures = g_predefinedHooks[i].hook_textures; addr_camera_manager = g_predefinedHooks[i].hook_camera_manager;
addr_device_offset = g_predefinedHooks[i].hook_device_offset; addr_device_offset = g_predefinedHooks[i].hook_device_offset;
addr_afp_texture_offset = g_predefinedHooks[i].hook_afp_texture_offset;
return TRUE; return TRUE;
} }
} }
@@ -131,8 +128,8 @@ namespace games::iidx {
// --- addr_hook_a --- // --- addr_hook_a ---
uint8_t *addr_hook_a_ptr = reinterpret_cast<uint8_t *>(find_pattern( uint8_t *addr_hook_a_ptr = reinterpret_cast<uint8_t *>(find_pattern(
iidx_module, iidx_module,
"488BC4565741564883EC5048C740D8FEFFFFFF", "E800000000488B8F0000000048894D",
"XXXXXXXXXXXXXXXXXXX", "X????XXX????XXX",
0, 0)); 0, 0));
if (addr_hook_a_ptr == nullptr) { if (addr_hook_a_ptr == nullptr) {
@@ -141,24 +138,10 @@ namespace games::iidx {
return FALSE; return FALSE;
} }
addr_hook_a = (uint64_t) (addr_hook_a_ptr - (uint8_t*) iidx_module);
// addr_hook_b
uint8_t* addr_hook_b_ptr = reinterpret_cast<uint8_t *>(find_pattern(
iidx_module,
"E8000000004885C07439E8",
"X????XXXXXX",
0, 0));
if (addr_hook_b_ptr == nullptr) {
log_warning("iidx:camhook", "failed to find hook: addr_hook_b");
return FALSE;
}
// displace with the content of wildcard bytes // displace with the content of wildcard bytes
int32_t disp_b = *((int32_t *) (addr_hook_b_ptr + 1)); int32_t disp_a = *((int32_t *) (addr_hook_a_ptr + 1));
addr_hook_b = (addr_hook_b_ptr - (uint8_t*) iidx_module) + disp_b + 5; addr_hook_a = (addr_hook_a_ptr - (uint8_t*) iidx_module) + disp_a + 5;
// --- addr_textures --- // --- addr_textures ---
uint8_t* addr_textures_ptr = reinterpret_cast<uint8_t *>(find_pattern( uint8_t* addr_textures_ptr = reinterpret_cast<uint8_t *>(find_pattern(
@@ -194,6 +177,28 @@ namespace games::iidx {
addr_textures = (addr_textures_ptr - (uint8_t*) iidx_module) + disp_textures + 7; addr_textures = (addr_textures_ptr - (uint8_t*) iidx_module) + disp_textures + 7;
// --- addr_camera_manager ---
uint8_t *addr_camera_manager_ptr = reinterpret_cast<uint8_t *>(find_pattern(
iidx_module,
"E80000000033FF488B58",
"X????XXXXX",
0, 0));
if (addr_camera_manager_ptr == nullptr) {
log_warning("iidx:camhook", "failed to find hook: addr_get_camera_manager");
return FALSE;
}
// displace with the content of wildcard bytes
int32_t disp_camera_manager = *((int32_t *) (addr_camera_manager_ptr + 1));
addr_camera_manager_ptr = addr_camera_manager_ptr + disp_camera_manager + 5;
// once more to get the final address
disp_camera_manager = *((int32_t *) (addr_camera_manager_ptr + 3));
addr_camera_manager = (addr_camera_manager_ptr - (uint8_t*) iidx_module) + disp_camera_manager + 7;
// addr_device_offset // addr_device_offset
search_from = (addr_hook_a_ptr - (uint8_t*) iidx_module); search_from = (addr_hook_a_ptr - (uint8_t*) iidx_module);
uint8_t *addr_device_ptr = reinterpret_cast<uint8_t *>(find_pattern_from( uint8_t *addr_device_ptr = reinterpret_cast<uint8_t *>(find_pattern_from(
@@ -201,39 +206,47 @@ namespace games::iidx {
"488B89", "488B89",
"XXX", "XXX",
3, 0, search_from)); 3, 0, search_from));
addr_device_offset = *addr_device_ptr;
if (addr_textures_ptr == nullptr) { if (addr_device_ptr == nullptr) {
log_warning("iidx:camhook", "failed to find hook: addr_textures (part 3)"); log_warning("iidx:camhook", "failed to find hook: addr_device_ptr");
return FALSE; return FALSE;
} }
addr_device_offset = *addr_device_ptr;
// --- addr_afp_texture_offset ---
uint8_t *addr_afp_texture_ptr = reinterpret_cast<uint8_t *>(find_pattern(
iidx_module,
"488B41004885C07400488D5424",
"XXX?XXXX?XXXX",
0, 0));
if (addr_afp_texture_ptr == nullptr) {
log_warning("iidx:camhook", "failed to find hook: addr_afp_texture_offset");
return FALSE;
}
addr_afp_texture_offset = *(addr_afp_texture_ptr + 3);
return TRUE; return TRUE;
} }
static void **__fastcall camera_hook_a(PBYTE a1) { static void **__fastcall camera_hook_a(PBYTE a1) {
std::call_once(hook_a_init, [&]{ std::call_once(hook_a_init, [&]{
device = *reinterpret_cast<LPDIRECT3DDEVICE9EX*>(a1 + addr_device_offset); device = *reinterpret_cast<LPDIRECT3DDEVICE9EX*>(a1 + addr_device_offset);
auto const textures = *reinterpret_cast<LPDIRECT3DTEXTURE9**>((uint8_t*)iidx_module + addr_textures); auto const preview = *reinterpret_cast<LPDIRECT3DTEXTURE9**>((uint8_t*)iidx_module + addr_textures);
auto const manager = reinterpret_cast<Camera::CCameraManager2*>((uint8_t*)iidx_module + addr_camera_manager);
texture_a = textures; camera_texture_a = manager->cameras->a->d3d9_texture(addr_afp_texture_offset);
texture_b = textures + 2; camera_texture_b = manager->cameras->b->d3d9_texture(addr_afp_texture_offset);
preview_texture_a = preview;
preview_texture_b = preview + 2;
init_local_camera(); init_local_camera();
}); });
return camera_hook_a_orig(a1); return camera_hook_a_orig(a1);
} }
static LPDIRECT3DTEXTURE9 camera_hook_b(void* a1, int idx) {
if (idx == 0 && top_camera) {
return top_camera->GetTexture();
}
if (idx == 1 && front_camera) {
return front_camera->GetTexture();
}
return camera_hook_b_orig(a1, idx);
}
bool create_hooks() { bool create_hooks() {
auto *hook_a_ptr = reinterpret_cast<uint16_t *>( auto *hook_a_ptr = reinterpret_cast<uint16_t *>(
reinterpret_cast<intptr_t>(iidx_module) + addr_hook_a); reinterpret_cast<intptr_t>(iidx_module) + addr_hook_a);
@@ -246,17 +259,6 @@ namespace games::iidx {
return FALSE; return FALSE;
} }
auto *hook_b_ptr = reinterpret_cast<uint16_t *>(
reinterpret_cast<intptr_t>(iidx_module) + addr_hook_b);
if (!detour::trampoline(
reinterpret_cast<camera_hook_b_t>(hook_b_ptr),
camera_hook_b,
&camera_hook_b_orig)) {
log_warning("iidx:camhook", "failed to trampoline hook_b");
return FALSE;
}
return TRUE; return TRUE;
} }
@@ -443,7 +445,7 @@ namespace games::iidx {
} }
IIDXLocalCamera* add_top_camera(IMFActivate* pActivate) { IIDXLocalCamera* add_top_camera(IMFActivate* pActivate) {
auto top_camera = new IIDXLocalCamera("top", TDJ_CAMERA_PREFER_16_9, pActivate, s_pD3DManager, device, texture_a); auto top_camera = new IIDXLocalCamera("top", TDJ_CAMERA_PREFER_16_9, pActivate, s_pD3DManager, device, camera_texture_a, preview_texture_a);
if (top_camera->m_initialized) { if (top_camera->m_initialized) {
LOCAL_CAMERA_LIST.push_back(top_camera); LOCAL_CAMERA_LIST.push_back(top_camera);
} else { } else {
@@ -454,7 +456,7 @@ namespace games::iidx {
} }
IIDXLocalCamera* add_front_camera(IMFActivate* pActivate) { IIDXLocalCamera* add_front_camera(IMFActivate* pActivate) {
auto front_camera = new IIDXLocalCamera("front", TDJ_CAMERA_PREFER_16_9, pActivate, s_pD3DManager, device, texture_b); auto front_camera = new IIDXLocalCamera("front", TDJ_CAMERA_PREFER_16_9, pActivate, s_pD3DManager, device, camera_texture_b, preview_texture_b);
if (front_camera->m_initialized) { if (front_camera->m_initialized) {
LOCAL_CAMERA_LIST.push_back(front_camera); LOCAL_CAMERA_LIST.push_back(front_camera);
} else { } else {
@@ -469,8 +471,8 @@ namespace games::iidx {
if (result) { if (result) {
log_misc( log_misc(
"iidx:camhook", "iidx:camhook",
"found hooks:\n hook_a 0x{:x}\n hook_b 0x{:x}\n textures 0x{:x}\n device_offset 0x{:x}", "found hooks:\n hook_a 0x{:x}\n textures 0x{:x}\n camera_manager 0x{:x}\n device_offset 0x{:x}\n afp_texture_offset 0x{:x}",
addr_hook_a, addr_hook_b, addr_textures, addr_device_offset); addr_hook_a, addr_textures, addr_camera_manager, addr_device_offset, addr_afp_texture_offset);
result = create_hooks(); result = create_hooks();
if (result) { if (result) {
init_mf_library(); init_mf_library();
+10 -5
View File
@@ -46,14 +46,17 @@ namespace games::iidx {
IMFActivate *pActivate, IMFActivate *pActivate,
IDirect3DDeviceManager9 *pD3DManager, IDirect3DDeviceManager9 *pD3DManager,
LPDIRECT3DDEVICE9EX device, LPDIRECT3DDEVICE9EX device,
LPDIRECT3DTEXTURE9 *texture_target LPDIRECT3DTEXTURE9 *camera_texture_target,
LPDIRECT3DTEXTURE9 *preview_texture_target
): ):
m_nRefCount(1), m_nRefCount(1),
m_name(name), m_name(name),
m_prefer_16_by_9(prefer_16_by_9), m_prefer_16_by_9(prefer_16_by_9),
m_device(device), m_device(device),
m_texture_target(texture_target), m_camera_texture_target(camera_texture_target),
m_texture_original(*texture_target) m_preview_texture_target(preview_texture_target),
m_camera_texture_original(*camera_texture_target),
m_preview_texture_original(*preview_texture_target)
{ {
InitializeCriticalSection(&m_critsec); InitializeCriticalSection(&m_critsec);
@@ -691,7 +694,8 @@ namespace games::iidx {
if (FAILED(hr)) { goto done; } if (FAILED(hr)) { goto done; }
// Make the game use this new texture as camera stream source // Make the game use this new texture as camera stream source
*m_texture_target = m_texture; *m_camera_texture_target = m_texture;
*m_preview_texture_target = m_texture;
m_active = TRUE; m_active = TRUE;
// Create texture for colour conversion // Create texture for colour conversion
@@ -860,7 +864,8 @@ namespace games::iidx {
ULONG uCount = InterlockedDecrement(&m_nRefCount); ULONG uCount = InterlockedDecrement(&m_nRefCount);
*m_texture_target = m_texture_original; *m_camera_texture_target = m_camera_texture_original;
*m_preview_texture_target = m_preview_texture_original;
for (size_t i = 0; i < m_mediaTypeInfos.size(); i++) { for (size_t i = 0; i < m_mediaTypeInfos.size(); i++) {
SafeRelease(&(m_mediaTypeInfos.at(i).p_mediaType)); SafeRelease(&(m_mediaTypeInfos.at(i).p_mediaType));
+27 -5
View File
@@ -66,6 +66,25 @@ extern std::string CAMERA_CONTROL_LABELS[];
extern std::string DRAW_MODE_LABELS[]; extern std::string DRAW_MODE_LABELS[];
namespace games::iidx { namespace games::iidx {
namespace Camera {
struct PlayVideoCamera {
IDirect3DTexture9** d3d9_texture(const uintptr_t offset) {
auto const afp_texture = *reinterpret_cast<uint8_t**>
(reinterpret_cast<uint8_t*>(this) + offset);
return reinterpret_cast<IDirect3DTexture9**>(afp_texture + 0x8);
}
};
struct CCameraManager2 {
using camera_pointers = struct {
PlayVideoCamera* a;
PlayVideoCamera* b;
};
void* vftbl;
camera_pointers* cameras;
};
}
class IIDXLocalCamera { class IIDXLocalCamera {
protected: protected:
virtual ~IIDXLocalCamera() {}; virtual ~IIDXLocalCamera() {};
@@ -98,8 +117,9 @@ namespace games::iidx {
// DirectX9 DeviceEx // DirectX9 DeviceEx
LPDIRECT3DDEVICE9EX m_device; LPDIRECT3DDEVICE9EX m_device;
// Address to hook camera texture onto the game // Address to hook camera textures onto the game
LPDIRECT3DTEXTURE9 *m_texture_target = nullptr; LPDIRECT3DTEXTURE9 *m_camera_texture_target = nullptr;
LPDIRECT3DTEXTURE9 *m_preview_texture_target = nullptr;
// Target texture (to be shown in the game) // Target texture (to be shown in the game)
LPDIRECT3DTEXTURE9 m_texture = nullptr; LPDIRECT3DTEXTURE9 m_texture = nullptr;
@@ -117,8 +137,9 @@ namespace games::iidx {
LPDIRECT3DTEXTURE9 m_transformResultTexture = nullptr; LPDIRECT3DTEXTURE9 m_transformResultTexture = nullptr;
IDirect3DSurface9 *m_pTransformResultSurf = nullptr; IDirect3DSurface9 *m_pTransformResultSurf = nullptr;
// Storing original texture for clean up // Storing original textures for clean up
LPDIRECT3DTEXTURE9 m_texture_original = nullptr; LPDIRECT3DTEXTURE9 m_camera_texture_original = nullptr;
LPDIRECT3DTEXTURE9 m_preview_texture_original = nullptr;
IAMCameraControl *m_pCameraControl = nullptr; IAMCameraControl *m_pCameraControl = nullptr;
@@ -154,7 +175,8 @@ namespace games::iidx {
IMFActivate *pActivate, IMFActivate *pActivate,
IDirect3DDeviceManager9 *pD3DManager, IDirect3DDeviceManager9 *pD3DManager,
LPDIRECT3DDEVICE9EX device, LPDIRECT3DDEVICE9EX device,
LPDIRECT3DTEXTURE9 *texture_target LPDIRECT3DTEXTURE9 *camera_texture_target,
LPDIRECT3DTEXTURE9 *preview_texture_target
); );
LPDIRECT3DTEXTURE9 GetTexture(); LPDIRECT3DTEXTURE9 GetTexture();
ULONG Release(); ULONG Release();
+3 -3
View File
@@ -497,10 +497,10 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.display_name = "iidxcamhookoffset", .display_name = "iidxcamhookoffset",
.aliases = "iidxcamhookoffset", .aliases = "iidxcamhookoffset",
.desc = "Override DLL offsets for camera hook; " .desc = "Override DLL offsets for camera hook; "
"format: 4 hex values separated by commas " "format: 5 hex values separated by commas "
"(offset_hook_a, offset_hook_b, offset_textures, offset_d3d_device)", "(offset_hook_a, offset_textures, offset_camera_manager, offset_d3d_device, offset_afp_texture)",
.type = OptionType::Text, .type = OptionType::Text,
.setting_name = "0x5817a0,0x5ea3b0,0x6fffbd8,0xe0", .setting_name = "0x5817a0,0x6fffbd8,0xbbae40,0xe0,0x30",
.game_name = "Beatmania IIDX", .game_name = "Beatmania IIDX",
.category = "Game Options (Advanced)", .category = "Game Options (Advanced)",
}, },