diff --git a/plugins/grounded/grounded.cpp b/plugins/grounded/grounded.cpp index ffb37a66d5e..a96b61b1489 100644 --- a/plugins/grounded/grounded.cpp +++ b/plugins/grounded/grounded.cpp @@ -123,31 +123,54 @@ uint8_t mumble_initPositionalData(const char *const *programNames, const uint64_ continue; } - /* Look up camera positional data through pointer chain */ - - procptr_t p = iter->second.baseAddress(); - - if (!(p = proc.peekPtr(p + 0x614C3C8))) { + // An address pointing to the start our pointer chain is moved + // into r10. findPattern and peekRIP read executable regions of + // memory and give us the address that would be copied to r10. + // + // This address is in executable code and points to a data + // page in the executable. It's not moving around between + // program launches. Instead, we use findPattern to support + // different versions of the game, hopefully future versions. + // If a new executable is shipped with a game update, and the + // start of our pointer chain in the data page moves, we can + // find it again by finding this code pattern and seeing what + // it points to. + // + // 4C 8B 15 ?? ?? ?? ?? mov r10,qword ptr ds:[?? ?? ?? ??] + // 44 8D 4A FF lea r9d,qword ptr ds:[rdx-1] + // 49 63 C1 movsxd rax,r9d + // 49 8B 34 C2 mov rsi,qword ptr ds:[r10+rax*8] + const std::vector< uint8_t > pattern = { + 0x4C, 0x8B, 0x15, '?', '?', '?', '?', /**/ + 0x44, 0x8D, 0x4A, 0xFF, /**/ + 0x49, 0x63, 0xC1, /**/ + 0x49, 0x8B, 0x34, 0xC2, /**/ + }; + + procptr_t addr, ok; + + if (!(addr = proc.findPattern(pattern, iter->second))) { continue; } - if (!(p = proc.peekPtr(p + 0x0))) { + if (!(addr = proc.peekRIP(addr + 0x3))) { continue; } - if (!(p = proc.peekPtr(p + 0x8))) { - continue; - } + /* Only test that we can read the memory in the address we got. + * + * Normally, the memory at that address is another pointer to + * the start of our pointer chain. But, if we're at the main + * menu and haven't loaded in to any world yet, the pointer at + * this address will be null. We should be able to read this + * memory early on, but expect to read out a null pointer if we + * haven't loaded into the world yet. */ - p += 0x700; - - GroundedCam _cam; - - if (!(proc.peek(p, _cam))) { + if (!(proc.peek(addr, ok))) { continue; } - handle = std::make_unique< GroundedHandle >(std::move(proc), p); + handle = std::make_unique< GroundedHandle >(std::move(proc), addr); return MUMBLE_PDEC_OK; } @@ -159,44 +182,81 @@ void mumble_shutdownPositionalData() { handle.reset(); } +enum FollowChain { + CHAIN_OK = 0, + CHAIN_LATER, + CHAIN_BAD, +}; + +enum FollowChain followPointerChain(const ProcessWindows &proc, const procptr_t start, GroundedCam &cam) { + procptr_t chain; + + /* If we can't read the starting address, the program probably quit; + * we should shut down our positional audio */ + if (!proc.peek< procptr_t >(start, chain)) { + return CHAIN_BAD; + } + + if (chain == 0) { + /* Probably at the main menu. We expect this to point to a + * valid address later. */ + return CHAIN_LATER; + } + + if (!(chain = proc.peekPtr(chain))) { + return CHAIN_BAD; + } + + if (!(chain = proc.peekPtr(chain + 0x8))) { + return CHAIN_BAD; + } + + if (!proc.peek(chain + 0x700, cam)) { + return CHAIN_BAD; + } + + return CHAIN_OK; +} + bool mumble_fetchPositionalData(float *avatarPos, float *avatarDir, float *avatarAxis, float *cameraPos, float *cameraDir, float *cameraAxis, const char **contextPtr, const char **identityPtr) { *contextPtr = ""; *identityPtr = ""; - ProcessWindows &proc = std::get< 0 >(*handle); - procptr_t camAddr = std::get< 1 >(*handle); - + const ProcessWindows &proc = std::get< 0 >(*handle); + const procptr_t start = std::get< 1 >(*handle); GroundedCam cam; + auto result = followPointerChain(proc, start, cam); - if (!proc.peek< GroundedCam >(camAddr, cam)) { - std::fill_n(avatarPos, 3, 0.f); - std::fill_n(avatarDir, 3, 0.f); - std::fill_n(avatarAxis, 3, 0.f); + switch (result) { + case CHAIN_OK: + /* We expect top and front to be unit vectors in the game. */ + assert(float3_is_unit(cam.top)); + assert(float3_is_unit(cam.front)); - std::fill_n(cameraPos, 3, 0.f); - std::fill_n(cameraDir, 3, 0.f); - std::fill_n(cameraAxis, 3, 0.f); + avatarAxis[0] = cameraAxis[0] = -cam.top[0]; + avatarAxis[1] = cameraAxis[1] = cam.top[2]; + avatarAxis[2] = cameraAxis[2] = -cam.top[1]; - return false; - } - - /* We expect top and front to be unit vectors in the game. */ - assert(float3_is_unit(cam.top)); - assert(float3_is_unit(cam.front)); + avatarDir[0] = cameraDir[0] = -cam.front[0]; + avatarDir[1] = cameraDir[1] = cam.front[2]; + avatarDir[2] = cameraDir[2] = -cam.front[1]; - avatarAxis[0] = cameraAxis[0] = -cam.top[0]; - avatarAxis[1] = cameraAxis[1] = cam.top[2]; - avatarAxis[2] = cameraAxis[2] = -cam.top[1]; + avatarPos[0] = cameraPos[0] = unreal_to_mumble_units(cam.pos[0]); + avatarPos[1] = cameraPos[1] = unreal_to_mumble_units(cam.pos[2]); + avatarPos[2] = cameraPos[2] = unreal_to_mumble_units(cam.pos[1]); + return true; - avatarDir[0] = cameraDir[0] = -cam.front[0]; - avatarDir[1] = cameraDir[1] = cam.front[2]; - avatarDir[2] = cameraDir[2] = -cam.front[1]; + default: + std::fill_n(avatarPos, 3, 0.f); + std::fill_n(avatarDir, 3, 0.f); + std::fill_n(avatarAxis, 3, 0.f); - avatarPos[0] = cameraPos[0] = unreal_to_mumble_units(cam.pos[0]); - avatarPos[1] = cameraPos[1] = unreal_to_mumble_units(cam.pos[2]); - avatarPos[2] = cameraPos[2] = unreal_to_mumble_units(cam.pos[1]); + std::fill_n(cameraPos, 3, 0.f); + std::fill_n(cameraDir, 3, 0.f); + std::fill_n(cameraAxis, 3, 0.f); - return true; + return result == CHAIN_LATER; + } }