Wrap D3D9 COM objects instead of MinHook for the cold path#41
Open
3vcloud wants to merge 2 commits into
Open
Conversation
added 2 commits
July 7, 2026 10:17
CreateDevice(Ex)/CreateTexture/CreateVolumeTexture/CreateCubeTexture/ UpdateTexture/BeginScene/SetTexture/GetTexture/Release are now intercepted via ordinary compiled virtual dispatch on wrapper objects returned from Direct3DCreate9(Ex), instead of MinHook patching the real object's vtable slots. Nothing is ever patched in this path, so it sidesteps every self-modifying-code/JIT-cache concern the ARM64 emulator (Windows-on-ARM's xtajit, or Wine on FEX-Emu/box64) has with inline code patching. The late-attach path (SetDevice/RegisterExistingDevice/D3D9Hooks.cpp) is unchanged: wrapping only works when gMod controls object creation from the start, so late-attach still needs MinHook and remains gated off under ARM64 emulation.
RegisterExistingDevice deduped against D3D9Hooks.cpp's own g_devices map, but the cold Direct3DCreate9(Ex) path (D3D9Wrappers.cpp) never touches that map. If gMod is early-injected and something (e.g. GWToolbox adopting a pre-loaded gMod) also calls SetDevice on the resulting wrapper device, RegisterExistingDevice would treat it as new: MinHook-patch the wrapper's own vtable on top, and construct a second TextureClient, hitting its single-instance ASSERT. Check TextureClient::CurrentClient() first so any already-active device (via either path) is a no-op.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
InstallD3D9Hooks' MinHook vtable-slot patching with COM wrapper/proxy objects for the normal load-before-launch path.Direct3DCreate9/Direct3DCreate9Exnow return aWrappedDirect3D9/WrappedDirect3D9Exinstead of the real object; itsCreateDevice/CreateDeviceExwraps the resulting device inWrappedDirect3DDevice9/WrappedDirect3DDevice9Ex, andCreateTexture/CreateVolumeTexture/CreateCubeTexturewrap their results inWrappedTexture9/WrappedVolumeTexture9/WrappedCubeTexture9.CreateTexture,UpdateTexture,BeginScene,SetTexture,GetTexture,Release) is intercepted the same way, replicating the exact semantics of the currenth_*MinHook detours inD3D9Hooks.cpp— just via ordinary dispatch instead of a patched vtable slot. Nothing is ever patched, so this sidesteps every self-modifying-code/JIT-cache concern the ARM64 emulator (Windows-on-ARM's xtajit, or Wine on FEX-Emu/box64) has with inline code patching.SetDevice→RegisterExistingDevice,D3D9Hooks.cpp) is untouched: wrapping only works when gMod controls object creation from the start (the game already holds a pointer to the real object by the time late-attach happens), so that path still needs MinHook and remains gated off under ARM64 emulation (see Refuse late device-attach under ARM64 emulation #40).TextureClientis constructed with the wrapper device pointer (not the real one), soDirectX::CreateDDSTextureFromMemoryEx's internalCreateTexturecall re-enters the wrapper's interceptedCreateTexture, which is what makesloading_fake-gated fake-texture registration work at all. A small real-texture→wrapper side map (D3D9TextureWrappers.cpp) recovers wrapper identity for the one call (GetTexture) that only ever hands back real, unwrapped pointers.CMakeLists.txtglobsheader/*.h/source/*.cpp.Verified full method coverage of all 7 wrapped interfaces (
IDirect3D9,IDirect3D9Ex,IDirect3DDevice9,IDirect3DDevice9Ex,IDirect3DTexture9,IDirect3DVolumeTexture9,IDirect3DCubeTexture9) programmatically against the Microsoft DirectX SDK header declarations (cross-referenced via wine-mirror/wine and apitrace/dxsdk on GitHub) — every interface method has a matching override, and every declared override has a matching implementation. No Windows/MSVC toolchain was available to compile-check this, so please build before merging.Known limitation
QueryInterfaceon any wrapper only recognizes its own interface family (IUnknown,IDirect3D9/Ex,IDirect3DDevice9/Ex, or the specific texture interface); an unrecognized-but-successfulQueryInterfacecall falls through to the real object and hands back an unwrapped pointer. Not expected to matter in practice — noted inline where it applies.Test plan
AddFile/modlist-driven replacement) still works end to endFreeLibrary) still cleans upTextureClientcorrectly