mirror of
https://github.com/InstanceBrick/nightcap.git
synced 2026-10-27 20:01:21 +01:00
fix(vr): read the pointer hand and HeadScale offsets per build and check them at start-up
This commit is contained in:
1 parent
7294a46e8a
commit
f9289d9947
3 files changed
+99
-6
No files matched your search
@@ -403,6 +403,8 @@ constexpr VrBuildContract kContracts[] = {
|
||||
0x3a8,
|
||||
0x73677a8,
|
||||
0x1d48b70,
|
||||
0x118,
|
||||
0x140,
|
||||
"2998 (2.736.1408)"},
|
||||
{"5f0704edd9064f566ee3d6df2bd2fabbcc709f03",
|
||||
{0x6d08108, 0x37f9fe4, 0x37fa3c6, 0x37fa3b6, 0x37fa3e8, 0x73f4690,
|
||||
@@ -413,6 +415,8 @@ constexpr VrBuildContract kContracts[] = {
|
||||
0x3a0,
|
||||
0x73f24c8,
|
||||
0x1d8f8c0,
|
||||
0x118,
|
||||
0x140,
|
||||
"3092 (2.738.1397)"},
|
||||
};
|
||||
|
||||
@@ -431,11 +435,26 @@ bool HasExpectedVrPointerContract(const std::uint8_t* code, std::size_t size,
|
||||
code[0x59] != 0xe8 || code[0x6d] != 0xff || code[0x6e] != 0x91) {
|
||||
return false;
|
||||
}
|
||||
std::uint32_t camera_slot;
|
||||
// movsxd rax,[rsi+selector] at +0x24; movss xmm0,[r14+HeadScale] at +0x13d.
|
||||
static constexpr std::array<std::uint8_t, 3> kSelectorLoad = {0x48, 0x63,
|
||||
0x86};
|
||||
static constexpr std::array<std::uint8_t, 5> kHeadScaleLoad = {
|
||||
0xf3, 0x41, 0x0f, 0x10, 0x86};
|
||||
if (std::memcmp(code + 0x24, kSelectorLoad.data(), kSelectorLoad.size()) !=
|
||||
0 ||
|
||||
std::memcmp(code + 0x13d, kHeadScaleLoad.data(),
|
||||
kHeadScaleLoad.size()) != 0) {
|
||||
return false;
|
||||
}
|
||||
std::uint32_t camera_slot, selector, head_scale;
|
||||
std::memcpy(&camera_slot, code + 0x6f, sizeof(camera_slot));
|
||||
std::memcpy(&selector, code + 0x27, sizeof(selector));
|
||||
std::memcpy(&head_scale, code + 0x142, sizeof(head_scale));
|
||||
return RelativeTarget(code + 0x5a, contract.pointer_frame_rva + 0x5e) ==
|
||||
contract.workspace_getter_rva &&
|
||||
camera_slot == contract.camera_vtable_offset;
|
||||
camera_slot == contract.camera_vtable_offset &&
|
||||
selector == contract.pointer_hand_selector_offset &&
|
||||
head_scale == contract.camera_head_scale_offset;
|
||||
}
|
||||
|
||||
EyeResourceState InspectEyeResources(const void *object) {
|
||||
@@ -676,6 +695,24 @@ std::string DescribeSupportedVrBuilds() {
|
||||
|
||||
namespace internal {
|
||||
|
||||
int ReadPointerHandIndex(const void* service, const VrBuildContract& contract) {
|
||||
int index = 0;
|
||||
std::memcpy(&index,
|
||||
static_cast<const unsigned char*>(service) +
|
||||
contract.pointer_hand_selector_offset,
|
||||
sizeof(index));
|
||||
return index;
|
||||
}
|
||||
|
||||
float ReadCameraHeadScale(const void* camera, const VrBuildContract& contract) {
|
||||
float scale = 0.f;
|
||||
std::memcpy(&scale,
|
||||
static_cast<const unsigned char*>(camera) +
|
||||
contract.camera_head_scale_offset,
|
||||
sizeof(scale));
|
||||
return scale;
|
||||
}
|
||||
|
||||
float GetHandPitchOffsetDeg() {
|
||||
static const float offset = [] {
|
||||
if (const char* env = std::getenv("MOCKTAIL_VR_HAND_PITCH_OFFSET")) {
|
||||
@@ -805,8 +842,7 @@ void RobloxVrDeviceBridge::OnPointerFrameCall(void* result, void* service) {
|
||||
if (!backend) return;
|
||||
const auto current = backend->PublishedHeadPose();
|
||||
if (!current.valid) return;
|
||||
int index = 0;
|
||||
std::memcpy(&index, static_cast<unsigned char*>(service) + 0x118, 4);
|
||||
const int index = internal::ReadPointerHandIndex(service, *contract_);
|
||||
if (index != 1 && index != 2) return;
|
||||
if (!current.hands[index - 1].aim_valid) return;
|
||||
const auto& hand = native_pose.hands[index - 1];
|
||||
@@ -821,9 +857,8 @@ void RobloxVrDeviceBridge::OnPointerFrameCall(void* result, void* service) {
|
||||
void* camera = reinterpret_cast<void* (*)(void*)>(
|
||||
vtable[contract_->camera_vtable_offset / sizeof(void*)])(workspace);
|
||||
if (!camera) return;
|
||||
float scale = 0;
|
||||
const float scale = internal::ReadCameraHeadScale(camera, *contract_);
|
||||
int degrees = 0;
|
||||
std::memcpy(&scale, static_cast<unsigned char*>(camera) + 0x140, 4);
|
||||
std::memcpy(°rees,
|
||||
reinterpret_cast<void*>(base + contract_->hand_pitch_storage_rva),
|
||||
4);
|
||||
|
||||
@@ -20,12 +20,20 @@ struct VrBuildContract {
|
||||
std::uintptr_t camera_vtable_offset;
|
||||
std::uintptr_t hand_pitch_storage_rva;
|
||||
std::uintptr_t haptic_sink_rva;
|
||||
// Field offsets the pointer hook reads: the hand index in the pointer
|
||||
// service and HeadScale in the workspace camera.
|
||||
std::uintptr_t pointer_hand_selector_offset;
|
||||
std::uintptr_t camera_head_scale_offset;
|
||||
// Roblox version code and name, for messages that list supported builds.
|
||||
std::string_view version;
|
||||
};
|
||||
|
||||
const VrBuildContract* FindVrBuildContract(std::string_view build_id);
|
||||
|
||||
// The hand the pointer service asks about: 1 left, 2 right.
|
||||
int ReadPointerHandIndex(const void* service, const VrBuildContract& contract);
|
||||
float ReadCameraHeadScale(const void* camera, const VrBuildContract& contract);
|
||||
|
||||
// Computes the record orientation for a hand pose: the aim pose (or, with
|
||||
// MOCKTAIL_VR_HAND_POSE_SOURCE=grip, the grip pose) pitched by the engine's
|
||||
// own hand pitch plus the configurable offset.
|
||||
|
||||
@@ -317,6 +317,15 @@ TEST(VrPointerContract, PinsWorkspaceAndCameraSlotForEachBuild) {
|
||||
"5f0704edd9064f566ee3d6df2bd2fabbcc709f03"}) {
|
||||
const auto& contract = *internal::FindVrBuildContract(id);
|
||||
std::array<std::uint8_t, 0x1b2> code{};
|
||||
// movsxd rax,[rsi+selector] and movss xmm0,[r14+HeadScale].
|
||||
const std::uint8_t selector_load[] = {0x48, 0x63, 0x86};
|
||||
std::memcpy(code.data() + 0x24, selector_load, sizeof(selector_load));
|
||||
const std::uint32_t selector = contract.pointer_hand_selector_offset;
|
||||
std::memcpy(code.data() + 0x27, &selector, sizeof(selector));
|
||||
const std::uint8_t scale_load[] = {0xf3, 0x41, 0x0f, 0x10, 0x86};
|
||||
std::memcpy(code.data() + 0x13d, scale_load, sizeof(scale_load));
|
||||
const std::uint32_t head_scale = contract.camera_head_scale_offset;
|
||||
std::memcpy(code.data() + 0x142, &head_scale, sizeof(head_scale));
|
||||
const std::uint8_t prologue[] = {0x55, 0x48, 0x89, 0xe5, 0x41,
|
||||
0x57, 0x41, 0x56, 0x41, 0x54,
|
||||
0x53, 0x48, 0x83, 0xec, 0x70};
|
||||
@@ -336,6 +345,14 @@ TEST(VrPointerContract, PinsWorkspaceAndCameraSlotForEachBuild) {
|
||||
EXPECT_FALSE(internal::HasExpectedVrPointerContract(code.data(),
|
||||
code.size(), contract));
|
||||
code[0x6f] ^= 8;
|
||||
code[0x27] ^= 0x10;
|
||||
EXPECT_FALSE(internal::HasExpectedVrPointerContract(code.data(),
|
||||
code.size(), contract));
|
||||
code[0x27] ^= 0x10;
|
||||
code[0x142] ^= 0x10;
|
||||
EXPECT_FALSE(internal::HasExpectedVrPointerContract(code.data(),
|
||||
code.size(), contract));
|
||||
code[0x142] ^= 0x10;
|
||||
code[0x5a] ^= 1;
|
||||
EXPECT_FALSE(internal::HasExpectedVrPointerContract(code.data(),
|
||||
code.size(), contract));
|
||||
@@ -940,3 +957,36 @@ TEST(VrHandOrientation, GripSourceKeepsTheGripRotation) {
|
||||
|
||||
} // namespace
|
||||
} // namespace mocktail::vr
|
||||
|
||||
namespace mocktail::vr {
|
||||
namespace {
|
||||
|
||||
TEST(VrPointerLayout, ReadsHandIndexAndHeadScaleAtTheBuildsOwnOffsets) {
|
||||
// Roblox moves these fields between builds; a fixed offset reads garbage.
|
||||
internal::VrBuildContract contract{};
|
||||
contract.pointer_hand_selector_offset = 0x108;
|
||||
contract.camera_head_scale_offset = 0x130;
|
||||
std::array<unsigned char, 0x200> service{}, camera{};
|
||||
const int hand = 2, stale_hand = 7;
|
||||
const float scale = 1.5f, stale_scale = 9.f;
|
||||
std::memcpy(service.data() + 0x108, &hand, 4);
|
||||
std::memcpy(service.data() + 0x118, &stale_hand, 4);
|
||||
std::memcpy(camera.data() + 0x130, &scale, 4);
|
||||
std::memcpy(camera.data() + 0x140, &stale_scale, 4);
|
||||
EXPECT_EQ(internal::ReadPointerHandIndex(service.data(), contract), 2);
|
||||
EXPECT_FLOAT_EQ(internal::ReadCameraHeadScale(camera.data(), contract),
|
||||
1.5f);
|
||||
}
|
||||
|
||||
TEST(VrPointerLayout, VerifiedBuildsKeepTheirOffsets) {
|
||||
for (const char* id : {"ade08266c67aee88ec9c1d00902150e1684dad3a",
|
||||
"5f0704edd9064f566ee3d6df2bd2fabbcc709f03"}) {
|
||||
const auto* contract = internal::FindVrBuildContract(id);
|
||||
ASSERT_NE(contract, nullptr);
|
||||
EXPECT_EQ(contract->pointer_hand_selector_offset, 0x118u) << id;
|
||||
EXPECT_EQ(contract->camera_head_scale_offset, 0x140u) << id;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace mocktail::vr
|
||||
Reference in new issue
Block a user