#include #include #include #include "get-graphics-offsets.h" typedef HRESULT(WINAPI *d3d9createex_t)(UINT, IDirect3D9Ex **); struct d3d9_info { HMODULE module; HWND hwnd; IDirect3D9Ex *d3d9ex; IDirect3DDevice9Ex *device; IDirect3DSwapChain9 *swap; }; static inline bool d3d9_init(d3d9_info &info) { d3d9createex_t create; HRESULT hr; info.hwnd = CreateWindowExA(0, DUMMY_WNDCLASS, "d3d9 get-offset window", WS_POPUP, 0, 0, 1, 1, nullptr, nullptr, GetModuleHandleA(nullptr), nullptr); if (!info.hwnd) { return false; } info.module = LoadLibraryA("d3d9.dll"); if (!info.module) { return false; } create = (d3d9createex_t)GetProcAddress(info.module, "Direct3DCreate9Ex"); if (!create) { return false; } hr = create(D3D_SDK_VERSION, &info.d3d9ex); if (FAILED(hr)) { return false; } D3DPRESENT_PARAMETERS pp = {}; pp.Windowed = true; pp.SwapEffect = D3DSWAPEFFECT_FLIP; pp.BackBufferFormat = D3DFMT_A8R8G8B8; pp.BackBufferWidth = 2; pp.BackBufferHeight = 2; pp.BackBufferCount = 1; pp.hDeviceWindow = info.hwnd; pp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; hr = info.d3d9ex->CreateDeviceEx( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, info.hwnd, D3DCREATE_HARDWARE_VERTEXPROCESSING | D3DCREATE_NOWINDOWCHANGES, &pp, nullptr, &info.device); if (FAILED(hr)) { return false; } hr = info.device->GetSwapChain(0, &info.swap); if (FAILED(hr)) { return false; } return true; } static inline void d3d9_free(d3d9_info &info) { if (info.swap) info.swap->Release(); if (info.device) info.device->Release(); if (info.d3d9ex) info.d3d9ex->Release(); if (info.hwnd) DestroyWindow(info.hwnd); } #ifdef _WIN64 #define MAX_CMP_SIZE 22 // clang-format off static const uint8_t mask[][MAX_CMP_SIZE] = { { 0xF8, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xF8, 0xF8, 0x00, 0x00, 0x00, 0x00 }, { 0xF8, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xF8, 0xF8, 0x00, 0x00, 0x00, 0x00 }, { 0xF8, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00 }, { 0xF8, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00 }, }; static const uint8_t mask_cmp[][MAX_CMP_SIZE] = { /* * Windows 7 * 48 8B 83 B8 3D 00 00 mov rax, [rbx+3DB8h] * 44 39 B8 68 50 00 00 cmp [rax+5068h], r15d * 75 12 jnz short loc_7FF7AA90530 * 41 B8 F9 19 00 00 mov r8d, 19F9h */ { 0x48, 0x8B, 0x80, 0x00, 0x00, 0x00, 0x00, 0x44, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x75, 0x00, 0x40, 0xB8, 0x00, 0x00, 0x00, 0x00 }, /* * Windows ???+ * 49 8B 87 78 41 00 00 mov rax, [r15+4178h] * 39 98 E0 51 00 00 cmp [rax+51E0h], ebx * 75 12 jnz short loc_1800AEC9C * 41 B9 C3 1A 00 00 mov r9d, 1AC3h */ { 0x48, 0x8B, 0x80, 0x00, 0x00, 0x00, 0x00, 0x39, 0x80, 0x00, 0x00, 0x00, 0x00, 0x75, 0x00, 0x40, 0xB8, 0x00, 0x00, 0x00, 0x00 }, /* * Windows 10 April 2018 * 49 8B 87 58 40 00 00 mov rax, [r15+4058h] * 39 98 C0 53 00 00 cmp [rax+53C0h], ebx * 75 12 jnz short loc_1800A7FEC * 48 8D 15 7F B6 09 00 lea rdx, addrErrorMsg * * Note: different instructions, last byte skipped due to MAX_CMP_SIZE */ { 0x48, 0x8B, 0x80, 0x00, 0x00, 0x00, 0x00, 0x39, 0x80, 0x00, 0x00, 0x00, 0x00, 0x75, 0x00, 0x48, 0x8D, 0x00, 0x00, 0x00, 0x00 }, /* * Windows 11 22H2 * 49 8b 86 30 40 00 00 MOV RAX,qword ptr [R14 + 0x4030] * 83 b8 a0 55 00 00 00 CMP dword ptr [RAX + 0x55a0],0x0 * 75 12 JNZ LAB_1800b8662 * 48 8d 15 b9 24 0a 00 LEA param_2,[s_To_use...] * * Note: different instructions, last byte skipped due to MAX_CMP_SIZE */ { 0x48, 0x8B, 0x80, 0x00, 0x00, 0x00, 0x00, 0x83, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x75, 0x00, 0x48, 0x8D, 0x00, 0x00, 0x00 }, }; // clang-format on // Offset into the code for the numbers we're interested in static const uint32_t code_offsets[][2] = { {3, 10}, {3, 9}, {3, 9}, {3, 9}, }; #else #define MAX_CMP_SIZE 20 // clang-format off static const uint8_t mask[][MAX_CMP_SIZE] = { { 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00 }, { 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00 }, { 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00 }, { 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00 }, }; static const uint8_t mask_cmp[][MAX_CMP_SIZE] = { /* * Windows 7+ * 8B 83 E8 29 00 00 mov eax, [ebx+29E8h] * 39 B0 80 4B 00 00 cmp [eax+4B80h], esi * 75 14 jnz short loc_754CD9E1 * 68 F9 19 00 00 push 19F9h */ { 0x8B, 0x80, 0x00, 0x00, 0x00, 0x00, 0x39, 0x80, 0x00, 0x00, 0x00, 0x00, 0x75, 0x00, 0x68, 0x00, 0x00, 0x00, 0x00 }, /* Windows 10 Creator's Update+ * 8B 86 F8 2B 00 00 mov eax, [esi+2BF8h] * 83 B8 00 4D 00 00 00 cmp dword ptr [eax+4D00h], 0 * 75 0F jnz short loc_100D793C * 68 C3 1A 00 00 push 1AC3h */ { 0x8B, 0x80, 0x00, 0x00, 0x00, 0x00, 0x83, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x75, 0x00, 0x68, 0x00, 0x00, 0x00, 0x00 }, /* * Windows 10 April 2018 Update * 8B 86 68 2B 00 00 mov eax, [esi+2B68h] * 83 B8 F4 4D 00 00 00 cmp dword ptr [eax+4DF4h], 0 * 75 0F jnz short loc_100D9A9C * BA 08 71 01 10 mov edx, offset errMsg */ { 0x8B, 0x80, 0x00, 0x00, 0x00, 0x00, 0x83, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x75, 0x00, 0xBA, 0x00, 0x00, 0x00, 0x00 }, /* * Windows 11 22H2 * 8b 83 3c 2b 00 00 MOV EAX,dword ptr [EBX + 0x2b3c] * 39 b8 44 4f 00 00 CMP dword ptr [EAX + 0x4f44],EDI * 75 0f JNZ LAB_100d79a0 * ba d0 c6 00 10 MOV EDX,s_To_use_... */ { 0x8B, 0x80, 0x00, 0x00, 0x00, 0x00, 0x39, 0x80, 0x00, 0x00, 0x00, 0x00, 0x75, 0x00, 0xBA, 0x00, 0x00, 0x00, 0x00, 0x00 }, }; // clang-format on // Offset into the code for the numbers we're interested in static const uint32_t code_offsets[][2] = { {2, 8}, {2, 8}, {2, 8}, {2, 8}, }; #endif #define MAX_FUNC_SCAN_BYTES 200 static inline bool pattern_matches(uint8_t *byte, uint32_t *offset1, uint32_t *offset2) { for (size_t j = 0; j < sizeof(mask) / sizeof(mask[0]); j++) { for (size_t i = 0; i < MAX_CMP_SIZE; i++) { if ((byte[i] & mask[j][i]) != mask_cmp[j][i]) goto next_signature; } *offset1 = code_offsets[j][0]; *offset2 = code_offsets[j][1]; return true; next_signature:; } return false; } void get_d3d9_offsets(struct d3d9_offsets *offsets) { d3d9_info info = {}; bool success = d3d9_init(info); if (success) { uint8_t **vt = *(uint8_t ***)info.device; /* Pointer to CheckResourceResidency */ uint8_t *crr = vt[125]; offsets->present = vtable_offset(info.module, info.device, 17); offsets->present_ex = vtable_offset(info.module, info.device, 121); offsets->present_swap = vtable_offset(info.module, info.swap, 3); uint32_t offset1, offset2; for (size_t i = 0; i < MAX_FUNC_SCAN_BYTES; i++) { if (pattern_matches(&crr[i], &offset1, &offset2)) { #define get_offset(x) *(uint32_t *)&crr[i + x] uint32_t off1 = get_offset(offset1); uint32_t off2 = get_offset(offset2); /* check to make sure offsets are within * expected values */ if (off1 > 0xFFFF || off2 > 0xFFFF) break; #ifdef _MSC_VER /* check to make sure offsets actually point * toward expected data */ __try { uint8_t *ptr = (uint8_t *)(info.device); uint8_t *d3d9_ptr = *(uint8_t **)(ptr + off1); if (d3d9_ptr != (uint8_t *)info.d3d9ex) break; BOOL &is_d3d9ex = *(BOOL *)(d3d9_ptr + off2); if (is_d3d9ex != TRUE) break; } __except (EXCEPTION_EXECUTE_HANDLER) { break; } #endif offsets->d3d9_clsoff = off1; offsets->is_d3d9ex_clsoff = off2; break; } } } d3d9_free(info); }