Engine Adapters¶
The
EngineAdapterabstraction is what makes QS-Bridge universal — each game engine gets its own adapter implementation while sharing the same bridge core.
Architecture¶
graph TD
EA[EngineAdapter — abstract]
EA --> UE4[UE4Adapter<br/>Production — UE4 4.27, vtable hooking]
EA --> UE5[UE5Adapter<br/>Planned]
EA --> UNITY[UnityAdapter<br/>Planned — IL2CPP function hooking]
EA --> GODOT[GodotAdapter<br/>Planned — GDExtension API]
EA --> MOCK[MockAdapter<br/>Testing — headless, no game engine]
Abstract Interface¶
// include/qsbridge/engine_adapter.h (164 lines)
class EngineAdapter {
public:
virtual ~EngineAdapter() = default;
// --- Lifecycle ---
virtual bool init() = 0;
virtual void shutdown() = 0;
virtual void tick(float delta_time) = 0;
// --- Identity ---
virtual std::string get_engine_name() const = 0;
virtual std::string get_engine_version() const = 0;
// --- Hook Management ---
virtual bool hook_function(const char* function_name, void* callback) = 0;
virtual bool unhook_function(const char* function_name) = 0;
virtual bool is_hooked(const char* function_name) const = 0;
// --- Player Tracking ---
virtual std::vector<PlayerInfo> get_connected_players() const = 0;
virtual std::string extract_player_id(void* player_controller) const = 0;
virtual void* find_player_controller(const std::string& player_id) const = 0;
// --- Memory Access ---
virtual bool read_memory(void* base, size_t offset, void* out, size_t size) const = 0;
virtual bool write_memory(void* base, size_t offset, const void* data, size_t size) = 0;
// --- Object System ---
virtual void* find_object_by_name(const char* class_name) const = 0;
virtual std::string get_object_class_name(void* object) const = 0;
};
UE4 Adapter (Production)¶
Location: lib/engines/ue4/ (4 source files)
| File | Purpose | Key Functions |
|---|---|---|
entry.cpp |
LD_PRELOAD constructor, bootstrap |
__attribute__((constructor)) |
introspection.cpp |
UE4 memory scanning, FProperty walks | find_property_offset() |
vtable_hook.cpp |
vtable-based function hooking | hook_virtual(), unhook_virtual() |
fname_cache.cpp |
FName string resolution cache | resolve_fname() |
Hooking Method¶
UE4 uses virtual function tables (vtables) for polymorphism. The adapter replaces vtable entries to intercept function calls:
// Simplified vtable hook
void hook_virtual(void* object, int vtable_index, void* hook_function) {
void** vtable = *(void***)object;
// Save original function pointer
original_functions_[vtable_index] = vtable[vtable_index];
// Make vtable writable
mprotect(page_align(vtable), PAGE_SIZE, PROT_READ | PROT_WRITE);
// Replace with hook
vtable[vtable_index] = hook_function;
// Restore protection
mprotect(page_align(vtable), PAGE_SIZE, PROT_READ);
}
Key Hooks¶
| UE4 Function | Hook Purpose |
|---|---|
UObject::ProcessEvent |
Intercept all RPC calls |
UWorld::SpawnActor |
Track actor creation |
AActor::Destroyed |
Track actor destruction |
UWorld::Tick |
Inject bridge tick into engine loop |
AGameMode::PostLogin |
Player connection |
AGameMode::Logout |
Player disconnection |
Mock Adapter (Testing)¶
Location: include/qsbridge/engines/mock_adapter.h
Headless adapter for unit and integration testing. No game engine required.
class MockAdapter : public EngineAdapter {
std::vector<MockPlayer> mock_players_;
public:
// Simulate player events
void add_mock_player(const std::string& id, const std::string& name);
void remove_mock_player(const std::string& id);
void simulate_rpc(const std::string& function_name, const void* params);
// EngineAdapter implementation (all functional, no game engine)
bool init() override { return true; }
void tick(float dt) override { /* update mock state */ }
// ...
};
Tests: 17 assertions in test_mock_adapter.cpp — verifying player tracking, RPC simulation, and adapter lifecycle.
Adding a New Engine Adapter¶
- Create
include/qsbridge/engines/my_engine.handlib/engines/my_engine/ - Implement
EngineAdapterinterface - Add to CMake build as a new static library layer
- Register in
entry.cppengine detection logic
Engine Detection¶
// Detect engine from the loaded process
EngineAdapter* detect_engine() {
if (is_ue4_process()) return new UE4Adapter();
if (is_ue5_process()) return new UE5Adapter();
if (is_unity_process()) return new UnityAdapter();
if (is_godot_process()) return new GodotAdapter();
return nullptr; // Unknown engine
}
Related Pages¶
- Bridge Internals — framework overview
- UE4 Implementation — UE4-specific details
- Memory Offsets — memory layout definitions