Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions src/games/ExampleUE/ExampleUECameraModule.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#include "ExampleUECameraModule.h"
#include "memory/offsets.h"
#include "aer/ConstantsPool.h"
#include "mods/VR.hpp"

void ExampleUECameraModule::installHooks() {
auto calcViewFn = memory::calcViewAddr();
if (calcViewFn != 0) {
m_calcViewHook = safetyhook::create_inline(
reinterpret_cast<void*>(calcViewFn),
reinterpret_cast<void*>(&onCalcView)
);
}

auto getProjectionFn = memory::getProjectionAddr();
if (getProjectionFn != 0) {
m_getProjectionHook = safetyhook::create_inline(
reinterpret_cast<void*>(getProjectionFn),
reinterpret_cast<void*>(&onGetProjection)
);
}
}

void ExampleUECameraModule::onCalcView(sdk::APlayerCameraManager* camMgr, float dt, sdk::FMinimalViewInfo* outView) {
auto inst = get();
inst->m_calcViewHook.call<void>(camMgr, dt, outView);

auto vr = VR::get();
if (vr->is_hmd_active() && outView != nullptr) {
// Get VR transforms for applying to camera view
auto eye = vr->get_current_eye_transform();
auto hmd = vr->get_transform(0);
auto offset = vr->get_transform_offset();

// TODO: Apply VR transform to outView->Location/Rotation
// This requires game-specific SDK structure knowledge:
// - Extract position from hmd matrix
// - Extract rotation from eye matrix
// - Apply offset compensation
// - Write to outView->Location and outView->Rotation
//
// Example implementation (requires actual SDK structures):
// glm::vec3 hmdPos = glm::vec3(hmd[3]);
// outView->Location = FVector(hmdPos.x, hmdPos.y, hmdPos.z);
(void)eye;
(void)hmd;
(void)offset;
}
}

void ExampleUECameraModule::onGetProjection(glm::mat4* outProj, float fov, float aspect, float nearZ, float farZ) {
auto inst = get();
inst->m_getProjectionHook.call<void>(outProj, fov, aspect, nearZ, farZ);

auto vr = VR::get();
if (vr->is_hmd_active() && outProj != nullptr) {
GlobalPool::submit_projection(*outProj, vr->m_render_frame_count);
}
}
31 changes: 31 additions & 0 deletions src/games/ExampleUE/ExampleUECameraModule.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#pragma once

#include <safetyhook/inline_hook.hpp>
#include <glm/glm.hpp>
#include <cstdint>

// Forward declarations for SDK types
namespace sdk {
struct FMinimalViewInfo;
struct APlayerCameraManager;
}

class ExampleUECameraModule {
public:
static ExampleUECameraModule* get() {
static auto inst = new ExampleUECameraModule();
return inst;
}

void installHooks();

private:
ExampleUECameraModule() = default;
~ExampleUECameraModule() = default;

safetyhook::InlineHook m_calcViewHook{};
safetyhook::InlineHook m_getProjectionHook{};

static void onCalcView(sdk::APlayerCameraManager* camMgr, float dt, sdk::FMinimalViewInfo* outView);
static void onGetProjection(glm::mat4* outProj, float fov, float aspect, float nearZ, float farZ);
};
19 changes: 19 additions & 0 deletions src/games/ExampleUE/ExampleUEEntry.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#include "ExampleUEEntry.h"
#include "ExampleUERendererModule.h"
#include "ExampleUECameraModule.h"
#include <imgui.h>

std::optional<std::string> ExampleUEEntry::on_initialize() {
ExampleUERendererModule::get()->installHooks();
ExampleUECameraModule::get()->installHooks();
return std::nullopt;
}

void ExampleUEEntry::on_draw_ui() {
if (!ImGui::CollapsingHeader(get_name().data())) {
return;
}

m_hudScale->draw("HUD Scale");
m_decoupledPitch->draw("Decoupled Pitch");
}
20 changes: 20 additions & 0 deletions src/games/ExampleUE/ExampleUEEntry.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#pragma once

#include "Mod.hpp"
#include <memory>

class ExampleUEEntry : public Mod {
public:
static std::shared_ptr<ExampleUEEntry>& get() {
static auto inst = std::make_shared<ExampleUEEntry>();
return inst;
}

std::string_view get_name() const override { return "ExampleUE VR"; }
std::optional<std::string> on_initialize() override;
void on_draw_ui() override;

private:
ModSlider::Ptr m_hudScale{ModSlider::create("HudScale", 0.1f, 1.0f, 0.5f)};
ModToggle::Ptr m_decoupledPitch{ModToggle::create("DecoupledPitch", false)};
};
70 changes: 70 additions & 0 deletions src/games/ExampleUE/ExampleUERendererModule.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#include "ExampleUERendererModule.h"
#include "memory/offsets.h"
#include "mods/VR.hpp"
#include "Framework.hpp"
#include <atomic>

// Cached pointer to engine's frame counter (initialized during hook installation)
// Using atomic for thread-safe access
static std::atomic<int*> g_engineFrameCounterPtr{nullptr};

void ExampleUERendererModule::installHooks() {
auto beginFrameFn = memory::beginFrameAddr();
if (beginFrameFn != 0) {
m_beginFrameHook = safetyhook::create_inline(
reinterpret_cast<void*>(beginFrameFn),
reinterpret_cast<void*>(&onBeginFrame)
);
}

auto beginRenderFn = memory::beginRenderAddr();
if (beginRenderFn != 0) {
m_beginRenderHook = safetyhook::create_inline(
reinterpret_cast<void*>(beginRenderFn),
reinterpret_cast<void*>(&onBeginRender)
);
}

// Cache pointer to engine's frame counter for direct access
auto frameCounterAddr = memory::engineFrameCounterAddr();
if (frameCounterAddr != 0) {
g_engineFrameCounterPtr.store(reinterpret_cast<int*>(frameCounterAddr));
}
}

uintptr_t ExampleUERendererModule::onBeginFrame() {
auto inst = get();

// Cache the engine frame count before the frame begins
// This reads directly from the engine's GFrameNumber or equivalent
int* framePtr = g_engineFrameCounterPtr.load();
if (framePtr != nullptr) {
// Validate pointer is within expected memory range before dereferencing
if (reinterpret_cast<uintptr_t>(framePtr) > 0x10000) {
inst->cacheEngineFrameCount(*framePtr);
}
}

return inst->m_beginFrameHook.call<uintptr_t>();
}

uintptr_t ExampleUERendererModule::onBeginRender(void* ctx) {
auto inst = get();

// Call the original function first to let the engine update its frame state
auto result = inst->m_beginRenderHook.call<uintptr_t>(ctx);

if (g_framework->is_ready()) {
auto vr = VR::get();

// Use the engine's frame count directly - avoids sync issues
int engineFrame = inst->getEngineFrameCount();

g_framework->enable_engine_thread();
g_framework->run_imgui_frame(false);
vr->on_begin_rendering(engineFrame);
vr->update_hmd_state(engineFrame);
}

return result;
}
32 changes: 32 additions & 0 deletions src/games/ExampleUE/ExampleUERendererModule.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#pragma once

#include <safetyhook/inline_hook.hpp>
#include <cstdint>

class ExampleUERendererModule {
public:
static ExampleUERendererModule* get() {
static auto inst = new ExampleUERendererModule();
return inst;
}

void installHooks();

// Get the frame count directly from the engine
// Override this to read from the game's frame counter (e.g., GFrameNumber)
int getEngineFrameCount() const { return m_cachedEngineFrame; }
void cacheEngineFrameCount(int frame) { m_cachedEngineFrame = frame; }

private:
ExampleUERendererModule() = default;
~ExampleUERendererModule() = default;

safetyhook::InlineHook m_beginFrameHook{};
safetyhook::InlineHook m_beginRenderHook{};

// Cached engine frame - should be set by reading from engine's frame counter
int m_cachedEngineFrame{0};

static uintptr_t onBeginFrame();
static uintptr_t onBeginRender(void* context);
};
62 changes: 62 additions & 0 deletions src/games/ExampleUE/memory/offsets.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#pragma once

#include "memory/memory_mul.h"
#include <cstdint>

namespace memory {

/**
* Game-specific pattern/offset definitions for ExampleUE
*
* IMPORTANT: These byte patterns are fragile and may break with game updates.
*
* Recommended approaches for production use:
* 1. Implement version detection to select patterns based on game version
* 2. Use multiple fallback patterns for each function
* 3. Consider using RTTI-based lookup when available (e.g., VTable method)
* 4. Store static offsets for known game versions as fallback
*
* Pattern format uses '?' for wildcard bytes that may vary between versions.
* The FuncRelocation function will use signature scanning when SIGNATURE_SCAN
* is defined, otherwise falls back to the static offset parameter.
*/

inline uintptr_t beginFrameAddr() {
// Pattern for UE BeginFrame function
// This pattern targets the function prologue which is more stable
return FuncRelocation("BeginFrame", "48 89 5C 24 ? 57 48 83 EC 20 48 8B D9 E8", 0x0);
}

inline uintptr_t beginRenderAddr() {
// Pattern for UE BeginRender function
// Uses standard x64 calling convention prologue
return FuncRelocation("BeginRender", "48 89 5C 24 ? 48 89 74 24 ? 57 48 83 EC 30", 0x0);
}

inline uintptr_t calcViewAddr() {
// Pattern for APlayerCameraManager::CalcView
// Alternative: Use VTable("APlayerCameraManager", offset) if RTTI available
return FuncRelocation("CalcView", "40 53 48 83 EC 40 48 8B DA 48 8B D1", 0x0);
}

inline uintptr_t getProjectionAddr() {
// Pattern for projection matrix calculation
// Look for SSE register saves as they're common in matrix functions
return FuncRelocation("GetProjection", "48 83 EC 48 0F 29 74 24 ?", 0x0);
}

/**
* Engine frame counter address (GFrameNumber or similar)
* Reading directly from the engine's frame counter ensures proper synchronization
* and eliminates frame lag issues compared to manually incrementing.
*
* For UE4/UE5: Look for GFrameNumber global variable
* Pattern typically references the frame counter increment location
*/
inline uintptr_t engineFrameCounterAddr() {
// Pattern that references GFrameNumber - adjust for specific game
// This is typically found near frame begin/end logic
return InstructionRelocation("EngineFrameCounter", "8B 05 ? ? ? ? 89 05 ? ? ? ?", 2, 6, 0x0);
}

} // namespace memory
Loading