Skip to content

Commit

Permalink
SDK: Add <= 4.10 support for GUObjectArray (inlined variant)
Browse files Browse the repository at this point in the history
  • Loading branch information
praydog committed Jul 6, 2023
1 parent cf73015 commit e4ce0ba
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 28 deletions.
109 changes: 81 additions & 28 deletions shared/sdk/UObjectArray.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,33 +75,8 @@ FUObjectArray* FUObjectArray::get() {
int32_t& potential_max_chunks = *(int32_t*)(*displacement + 0x28);
int32_t& potential_num_chunks = *(int32_t*)(*displacement + 0x2C);

if (potential_obj_first_gc_index < 0 || potential_obj_last_non_gc_index < 0 || potential_obj_max_objects_not_consid_by_gc < 0) {
SPDLOG_INFO("Skipping potential GUObjectArray at 0x{:x} due to negative values", *displacement);
return utility::ExhaustionResult::CONTINUE;
}

if (!IsBadReadPtr(*(void**)&potential_obj_first_gc_index, sizeof(void*)) && (*(int64_t*)&potential_obj_first_gc_index & 1) == 0) {
SPDLOG_INFO("Skipping potential GUObjectArray at 0x{:x} due to valid pointer", *displacement);
return utility::ExhaustionResult::CONTINUE;
}

if (!IsBadReadPtr(*(void**)&potential_obj_max_objects_not_consid_by_gc, sizeof(void*)) && (*(int64_t*)&potential_obj_max_objects_not_consid_by_gc & 1) == 0) {
SPDLOG_INFO("Skipping potential GUObjectArray at 0x{:x} due to valid pointer", *displacement);
return utility::ExhaustionResult::CONTINUE;
}

if (*(uint8_t*)&potential_obj_is_open_for_disregard_for_gc > 1) {
SPDLOG_INFO("Skipping potential GUObjectArray at 0x{:x} due to invalid boolean", *displacement);
return utility::ExhaustionResult::CONTINUE;
}

if (potential_obj_obj_objects == nullptr || IsBadReadPtr(potential_obj_obj_objects, sizeof(void*))) {
SPDLOG_INFO("Skipping potential GUObjectArray at 0x{:x} due to invalid pointer", *displacement);
return utility::ExhaustionResult::CONTINUE;
}

// Verify that the first object in the list is valid
{
if (potential_obj_obj_objects != nullptr && !IsBadReadPtr(potential_obj_obj_objects, sizeof(void*))) {
const auto first_obj = *(void**)potential_obj_obj_objects;

if (first_obj == nullptr || IsBadReadPtr(first_obj, sizeof(void*))) {
Expand All @@ -123,9 +98,87 @@ FUObjectArray* FUObjectArray::get() {
return utility::ExhaustionResult::CONTINUE;
}
}

// Do an initial check to see if we're operating on <= 4.10,
// which means the array is inlined and the size and chunk amount is stored at 0x1010 (512 * 8 + OBJECTS_OFFSET)
auto check_inlined_array = [&]() -> bool {
constexpr auto inlined_count_offs = OBJECTS_OFFSET + (MAX_INLINED_CHUNKS * sizeof(void*));

if (IsBadReadPtr((void*)*displacement, inlined_count_offs + 8)) {
return false;
}

const auto count = *(int32_t*)(*displacement + inlined_count_offs);
const auto chunk_count = *(int32_t*)(*displacement + inlined_count_offs + 4);

if (count > 0 && chunk_count > 0 && chunk_count < MAX_INLINED_CHUNKS) {
SPDLOG_INFO("Checking potential GUObjectArray at 0x{:x} with inlined count of {} and chunk count of {}", *displacement, count, chunk_count);

// Now that this has passed, we need to verify that the pointers in the inlined array match up with these counts
for (int32_t i = 0; i < chunk_count; ++i) {
const auto potential_chunk = *(void**)(*displacement + OBJECTS_OFFSET + (i * sizeof(void*)));

if (potential_chunk == nullptr || IsBadReadPtr(potential_chunk, sizeof(void*))) {
return false;
}

const auto potential_obj = *(void**)potential_chunk;

if (potential_obj == nullptr || IsBadReadPtr(potential_obj, sizeof(void*))) {
return false;
}
}

// Now check the ones ahead of the chunk count and make sure they are nullptr
for (int32_t i = chunk_count; i < MAX_INLINED_CHUNKS; ++i) {
const auto potential_chunk = *(void**)(*displacement + OBJECTS_OFFSET + (i * sizeof(void*)));

if (potential_chunk != nullptr) {
return false;
}
}

return true;
}

return false;
};

s_is_inlined_array = check_inlined_array();

if (s_is_inlined_array) {
SPDLOG_INFO("GUObjectArray appears to be inlined (<= 4.10)");
}

if (!s_is_inlined_array) {
if (potential_obj_first_gc_index < 0 || potential_obj_last_non_gc_index < 0 || potential_obj_max_objects_not_consid_by_gc < 0) {
SPDLOG_INFO("Skipping potential GUObjectArray at 0x{:x} due to negative values", *displacement);
return utility::ExhaustionResult::CONTINUE;
}

if (!IsBadReadPtr(*(void**)&potential_obj_first_gc_index, sizeof(void*)) && (*(int64_t*)&potential_obj_first_gc_index & 1) == 0) {
SPDLOG_INFO("Skipping potential GUObjectArray at 0x{:x} due to valid pointer", *displacement);
return utility::ExhaustionResult::CONTINUE;
}

if (!IsBadReadPtr(*(void**)&potential_obj_max_objects_not_consid_by_gc, sizeof(void*)) && (*(int64_t*)&potential_obj_max_objects_not_consid_by_gc & 1) == 0) {
SPDLOG_INFO("Skipping potential GUObjectArray at 0x{:x} due to valid pointer", *displacement);
return utility::ExhaustionResult::CONTINUE;
}

if (*(uint8_t*)&potential_obj_is_open_for_disregard_for_gc > 1) {
SPDLOG_INFO("Skipping potential GUObjectArray at 0x{:x} due to invalid boolean", *displacement);
return utility::ExhaustionResult::CONTINUE;
}

if (potential_obj_obj_objects == nullptr || IsBadReadPtr(potential_obj_obj_objects, sizeof(void*))) {
SPDLOG_INFO("Skipping potential GUObjectArray at 0x{:x} due to invalid pointer", *displacement);
return utility::ExhaustionResult::CONTINUE;
}
}

// At this point we've found it, check if it's a chunked array or not, and set a static variable
if (potential_max_chunks > 0 && potential_num_chunks > 0 && potential_max_chunks < 1000 && potential_num_chunks <= potential_max_chunks) {
if (!s_is_inlined_array && potential_max_chunks > 0 && potential_num_chunks > 0 && potential_max_chunks < 1000 && potential_num_chunks <= potential_max_chunks) {
try {
SPDLOG_INFO("max_chunks: {}, num_chunks: {}", potential_max_chunks, potential_num_chunks);

Expand All @@ -150,7 +203,7 @@ FUObjectArray* FUObjectArray::get() {
} catch(...) {
SPDLOG_ERROR("Failed to check if GUObjectArray is chunked");
}
} else {
} else if (!s_is_inlined_array) {
SPDLOG_INFO("GUObjectArray appears to not be chunked (max_chunks: {}, num_chunks: {})", potential_max_chunks, potential_num_chunks);
}

Expand Down
30 changes: 30 additions & 0 deletions shared/sdk/UObjectArray.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ struct FUObjectArray {
}

int32_t get_object_count() {
if (s_is_inlined_array) {
constexpr auto offs = OBJECTS_OFFSET + (MAX_INLINED_CHUNKS * sizeof(void*));
return *(int32_t*)((uintptr_t)this + offs);
}

if (s_is_chunked) {
constexpr auto offs = OBJECTS_OFFSET + sizeof(void*) + sizeof(void*) + 0x4;
return *(int32_t*)((uintptr_t)this + offs);
Expand All @@ -33,6 +38,18 @@ struct FUObjectArray {
return nullptr;
}

if (s_is_inlined_array) {
const auto chunk_index = index / OBJECTS_PER_CHUNK_INLINED;
const auto chunk_offset = index % OBJECTS_PER_CHUNK_INLINED;
const auto chunk = *(void**)((uintptr_t)get_objects_ptr() + (chunk_index * sizeof(void*)));

if (chunk == nullptr) {
return nullptr;
}

return (FUObjectItem*)((uintptr_t)chunk + (chunk_offset * s_item_distance));
}

if (s_is_chunked) {
const auto chunk_index = index / OBJECTS_PER_CHUNK;
const auto chunk_offset = index % OBJECTS_PER_CHUNK;
Expand All @@ -49,13 +66,26 @@ struct FUObjectArray {
}

void* get_objects_ptr() {
if (s_is_inlined_array) {
return (void*)((uintptr_t)this + OBJECTS_OFFSET);
}

return *(void**)((uintptr_t)this + OBJECTS_OFFSET);
}

private:
// for <= 4.10
constexpr static inline auto MAX_INLINED_CHUNKS = ((8 * 1024 * 1024) + 16384 - 1) / 16384;
constexpr static inline auto OBJECTS_PER_CHUNK_INLINED = 16384;

// for some newer versions
constexpr static inline auto OBJECTS_PER_CHUNK = 64 * 1024;

// has remained true for a long time
constexpr static inline auto OBJECTS_OFFSET = 0x10;

static inline bool s_is_chunked{false};
static inline bool s_is_inlined_array{false};
static inline int32_t s_item_distance{sizeof(FUObjectItem)}; // bruteforced later for older versions
};
}

0 comments on commit e4ce0ba

Please sign in to comment.