Skip to content

Commit

Permalink
SDK: Bruteforce FBoolProperty offsets
Browse files Browse the repository at this point in the history
  • Loading branch information
praydog committed Oct 28, 2023
1 parent ea43fc7 commit 18abe44
Show file tree
Hide file tree
Showing 6 changed files with 179 additions and 5 deletions.
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,7 @@ list(APPEND sdk_SOURCES
"shared/sdk/ConsoleManager.cpp"
"shared/sdk/DynamicRHI.cpp"
"shared/sdk/EngineModule.cpp"
"shared/sdk/FBoolProperty.cpp"
"shared/sdk/FField.cpp"
"shared/sdk/FMalloc.cpp"
"shared/sdk/FName.cpp"
Expand Down Expand Up @@ -359,6 +360,7 @@ list(APPEND sdk_SOURCES
"shared/sdk/ConsoleManager.hpp"
"shared/sdk/DynamicRHI.hpp"
"shared/sdk/EngineModule.hpp"
"shared/sdk/FBoolProperty.hpp"
"shared/sdk/FField.hpp"
"shared/sdk/FFieldClass.hpp"
"shared/sdk/FMalloc.hpp"
Expand Down
116 changes: 116 additions & 0 deletions shared/sdk/FBoolProperty.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
#include <spdlog/spdlog.h>

#include "UObjectArray.hpp"
#include "UClass.hpp"

#include "FBoolProperty.hpp"

namespace sdk {
void FBoolProperty::update_offsets() {
if (s_updated_offsets) {
return;
}

s_updated_offsets = true;

SPDLOG_INFO("[FBoolProperty::update_offsets] Updating offsets");

const auto fhitresult = sdk::find_uobject<sdk::UScriptStruct>(L"ScriptStruct /Script/Engine.HitResult");

if (fhitresult == nullptr) {
SPDLOG_ERROR("[FBoolProperty::update_offsets] Failed to find FHitResult");
return;
}

// Need to iterate over FHItResult's properties to find the first bool property
// on <= 4.27 or so it's always the first property but not in the newer ones.
FBoolProperty* first_bool_property{nullptr};

for (auto prop = fhitresult->get_child_properties(); prop != nullptr; prop = prop->get_next()) {
const auto c = prop->get_class();

if (c == nullptr) {
continue;
}

if (c->get_name().to_string() == L"BoolProperty") {
first_bool_property = (FBoolProperty*)prop;
break;
}
}

if (first_bool_property == nullptr) {
SPDLOG_ERROR("[FBoolProperty::update_offsets] Failed to find first bool property");
return;
}

FBoolProperty* second_bool_property{nullptr};

auto next_prop = first_bool_property->get_next();

if (next_prop == nullptr) {
SPDLOG_ERROR("[FBoolProperty::update_offsets] Failed to find second bool property");
return;
}

second_bool_property = (FBoolProperty*)next_prop;

const auto second_c = next_prop->get_class();

if (second_c == nullptr || second_c->get_name().to_string() != L"BoolProperty") {
SPDLOG_ERROR("[FBoolProperty::update_offsets] Failed to find second bool property (class check)");
return;
}

// Start from FProperty's offset offset, and bruteforce until we find the correct offset
// Since there's two BoolProperties sequentially laid out, we should be able to figure out what the mask is
const auto initial_start = FProperty::s_offset_offset + 4 + sizeof(void*) + sizeof(void*);
// align up to sizeof(void*)
const auto start = (initial_start + sizeof(void*) - 1) & ~(sizeof(void*) - 1);
for (auto i = start; i < start + 0x100; ++i) try {
const auto potential_field_size_a = *(uint8_t*)((uintptr_t)first_bool_property + i);
const auto potential_field_size_b = *(uint8_t*)((uintptr_t)second_bool_property + i);
const auto potential_byte_offset_a = *(uint8_t*)((uintptr_t)first_bool_property + i + 1);
const auto potential_byte_offset_b = *(uint8_t*)((uintptr_t)second_bool_property + i + 1);
const auto potential_byte_mask_a = *(uint8_t*)((uintptr_t)first_bool_property + i + 2);
const auto potential_byte_mask_b = *(uint8_t*)((uintptr_t)second_bool_property + i + 2);
const auto potential_field_mask_a = *(uint8_t*)((uintptr_t)first_bool_property + i + 3);
const auto potential_field_mask_b = *(uint8_t*)((uintptr_t)second_bool_property + i + 3);

// Log all of these for debugging even if they arent right
/*SPDLOG_INFO("[FBoolProperty::update_offsets] Field size A: 0x{:X} ({:x})", potential_field_size_a, i);
SPDLOG_INFO("[FBoolProperty::update_offsets] Field size B: 0x{:X} ({:x})", potential_field_size_b, i);
SPDLOG_INFO("[FBoolProperty::update_offsets] Byte offset A: 0x{:X} ({:x})", potential_byte_offset_a, i + 1);
SPDLOG_INFO("[FBoolProperty::update_offsets] Byte offset B: 0x{:X} ({:x})", potential_byte_offset_b, i + 1);
SPDLOG_INFO("[FBoolProperty::update_offsets] Byte mask A: 0x{:X} ({:x})", potential_byte_mask_a, i + 2);
SPDLOG_INFO("[FBoolProperty::update_offsets] Byte mask B: 0x{:X} ({:x})", potential_byte_mask_b, i + 2);
SPDLOG_INFO("[FBoolProperty::update_offsets] Field mask A: 0x{:X} ({:x})", potential_field_mask_a, i + 3);
SPDLOG_INFO("[FBoolProperty::update_offsets] Field mask B: 0x{:X} ({:x})", potential_field_mask_b, i + 3);*/

if (potential_field_size_a == 1 && potential_field_size_b == 1 &&
potential_byte_offset_a == 0 && potential_byte_offset_b == 0 &&
potential_byte_mask_a == 1 && potential_byte_mask_b == 2 &&
potential_field_mask_a == 1 && potential_field_mask_b == 2)
{
SPDLOG_INFO("[FBoolProperty::update_offsets] Field size offset: 0x{:X}", i);
SPDLOG_INFO("[FBoolProperty::update_offsets] Byte offset offset: 0x{:X}", i + 1);
SPDLOG_INFO("[FBoolProperty::update_offsets] Byte mask offset: 0x{:X}", i + 2);
SPDLOG_INFO("[FBoolProperty::update_offsets] Field mask offset: 0x{:X}", i + 3);
s_field_size_offset = i;
s_byte_offset_offset = i + 1;
s_byte_mask_offset = i + 2;
s_field_mask_offset = i + 3;
break;
}
} catch(...) {
continue;
}

if (s_field_size_offset == 0) {
SPDLOG_ERROR("[FBoolProperty::update_offsets] Failed to find offsets");
return;
}

SPDLOG_INFO("[FBoolProperty::update_offsets] Found offsets");
}
}
52 changes: 52 additions & 0 deletions shared/sdk/FBoolProperty.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#pragma once

#include <cstdint>

#include "FProperty.hpp"

namespace sdk {
class FBoolProperty : public FProperty {
public:
static void update_offsets();

uint8_t get_field_size() const {
return *(uint8_t*)((uintptr_t)this + s_field_size_offset);
}

uint8_t get_byte_offset() const {
return *(uint8_t*)((uintptr_t)this + s_byte_offset_offset);
}

uint8_t get_byte_mask() const {
return *(uint8_t*)((uintptr_t)this + s_byte_mask_offset);
}

uint8_t get_field_mask() const {
return *(uint8_t*)((uintptr_t)this + s_field_mask_offset);
}

bool get_value_from_object(void* object) const {
return get_value_from_propbase((void*)((uintptr_t)object + get_offset()));
}

bool get_value_from_propbase(void* addr) const {
return (*(uint8_t*)((uintptr_t)addr + get_byte_offset()) & get_byte_mask()) != 0;
}

void set_value_in_object(void* object, bool value) const {
set_value_in_propbase((void*)((uintptr_t)object + get_offset()), value);
}

void set_value_in_propbase(void* addr, bool value) const {
const auto cur_value = *(uint8_t*)((uintptr_t)addr + get_byte_offset());
*(uint8_t*)((uintptr_t)addr + get_byte_offset()) = (cur_value & ~get_byte_mask()) | (value ? get_byte_mask() : 0);
}

private:
static inline bool s_updated_offsets{false};
static inline uint32_t s_field_size_offset{0x0}; // idk
static inline uint32_t s_byte_offset_offset{0x0}; // idk
static inline uint32_t s_byte_mask_offset{0x0}; // idk
static inline uint32_t s_field_mask_offset{0x0}; // idk
};
}
2 changes: 1 addition & 1 deletion shared/sdk/FProperty.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class FProperty : public FField {
// Given xyz props from FVector, find the offset which matches up with all of them
static void bruteforce_fproperty_offset(FProperty* x_prop, FProperty* y_prop, FProperty* z_prop);

private:
protected:
static inline uint32_t s_offset_offset{0x0}; // idk

friend class UStruct;
Expand Down
2 changes: 2 additions & 0 deletions shared/sdk/UObjectArray.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "FName.hpp"
#include "FField.hpp"
#include "FStructProperty.hpp"
#include "FBoolProperty.hpp"
#include "UObjectArray.hpp"

namespace sdk {
Expand Down Expand Up @@ -387,6 +388,7 @@ FUObjectArray* FUObjectArray::get() try {
sdk::UScriptStruct::update_offsets();
sdk::UProperty::update_offsets();
sdk::FStructProperty::update_offsets();
sdk::FBoolProperty::update_offsets();

#ifdef TESTING_GUOBJECTARRAY
try {
Expand Down
10 changes: 6 additions & 4 deletions src/mods/UObjectHook.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include <sdk/APlayerController.hpp>
#include <sdk/APawn.hpp>
#include <sdk/ScriptVector.hpp>
#include <sdk/FBoolProperty.hpp>

#include "VR.hpp"

Expand Down Expand Up @@ -1214,12 +1215,13 @@ void UObjectHook::ui_handle_properties(void* object, sdk::UStruct* uclass) {
ImGui::DragInt(utility::narrow(prop->get_field_name().to_string()).data(), &value, 1);
}
break;

// TODO: handle bitfields
case "BoolProperty"_fnv:
{
auto& value = *(bool*)((uintptr_t)object + ((sdk::FProperty*)prop)->get_offset());
ImGui::Checkbox(utility::narrow(prop->get_field_name().to_string()).data(), &value);
auto boolprop = (sdk::FBoolProperty*)prop;
auto value = boolprop->get_value_from_object(object);
if (ImGui::Checkbox(utility::narrow(prop->get_field_name().to_string()).data(), &value)) {
boolprop->set_value_in_object(object, value);
}
}
break;
case "ObjectProperty"_fnv:
Expand Down

0 comments on commit 18abe44

Please sign in to comment.