Skip to content

Commit

Permalink
SDK: Possible fix for inlined FName::ToString in some games
Browse files Browse the repository at this point in the history
  • Loading branch information
praydog committed Jul 5, 2023
1 parent 0f15e05 commit aa8bd2d
Show file tree
Hide file tree
Showing 3 changed files with 147 additions and 61 deletions.
193 changes: 137 additions & 56 deletions shared/sdk/FName.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -163,85 +163,163 @@ std::optional<FName::ConstructorFn> FName::get_constructor() {
return result;
}

std::optional<FName::ToStringFn> FName::get_to_string() {
static auto result = []() -> std::optional<FName::ToStringFn> {
SPDLOG_INFO("FName::get_to_string");
namespace detail {
// Alternative for ToString if a majority of the calls are inlined
// not all of them are though.
std::optional<FName::ToStringFn> inlined_find_to_string() {
SPDLOG_INFO("FName::get_to_string: inlined_find_to_string");

const auto module = sdk::get_ue_module(L"Engine");
const auto module = sdk::get_ue_module(L"Engine");

if (module == nullptr) {
SPDLOG_ERROR("FName::get_to_string: Failed to get module");
return std::nullopt;
}
if (module == nullptr) {
SPDLOG_ERROR("FName::get_to_string: Failed to get module");
return std::nullopt;
}

const auto str_data = utility::scan_string(module, L"TAutoWeakObjectPtr<%s%s>");
if (!str_data) {
SPDLOG_ERROR("FName::get_to_string: Failed to get string data");
return std::nullopt;
}

SPDLOG_INFO("FName::get_to_string: str_data={:x}", *str_data);
const auto str_data = utility::scan_string(module, L"Commandlet %s finished execution (result %d)");

const auto str_ref = utility::scan_displacement_reference(module, *str_data);
if (!str_data) {
SPDLOG_ERROR("FName::get_to_string: Failed to get string data");
return std::nullopt;
}

SPDLOG_INFO("FName::get_to_string: str_data={:x}", *str_data);

if (!str_ref) {
SPDLOG_ERROR("FName::get_to_string: Failed to get string reference");
return std::nullopt;
const auto str_ref = utility::scan_displacement_reference(module, *str_data);

if (!str_ref) {
SPDLOG_ERROR("FName::get_to_string: Failed to get string reference");
return std::nullopt;
}

SPDLOG_INFO("FName::get_to_string: str_ref={:x}", *str_ref);

auto instructions = utility::get_disassembly_behind(*str_ref);

if (instructions.empty()) {
SPDLOG_ERROR("FName::get_to_string: Failed to get instructions");
return std::nullopt;
}

std::optional<FName::ToStringFn> result{};

std::reverse(instructions.begin(), instructions.end());
for (auto& instr : instructions) {
if (std::string_view{instr.instrux.Mnemonic}.starts_with("CALL")) {
const auto displacement = utility::resolve_displacement(instr.addr);

if (instr.instrux.BranchInfo.IsIndirect) {
if (!IsBadReadPtr((void*)*displacement, sizeof(void*))) {
result = *(FName::ToStringFn*)*displacement;
}
} else {
result = (FName::ToStringFn)*displacement;
}

break;
}
}

SPDLOG_INFO("FName::get_to_string: str_ref={:x}", *str_ref);
if (result) {
SPDLOG_INFO("FName::get_to_string (inlined alternative): result={:x}", (uintptr_t)*result);
}

const auto containing_func = utility::find_function_start(*str_ref);
return result;
}

if (!containing_func) {
SPDLOG_ERROR("FName::get_to_string: Failed to find containing function");
return std::nullopt;
std::optional<FName::ToStringFn> standard_find_to_string() {
SPDLOG_INFO("FName::get_to_string: standard_find_to_string");

const auto module = sdk::get_ue_module(L"Engine");

if (module == nullptr) {
SPDLOG_ERROR("FName::get_to_string: Failed to get module");
return std::nullopt;
}

const auto str_data = utility::scan_string(module, L"TAutoWeakObjectPtr<%s%s>");

if (!str_data) {
SPDLOG_ERROR("FName::get_to_string: Failed to get string data");
return std::nullopt;
}

SPDLOG_INFO("FName::get_to_string: str_data={:x}", *str_data);

const auto str_ref = utility::scan_displacement_reference(module, *str_data);

if (!str_ref) {
SPDLOG_ERROR("FName::get_to_string: Failed to get string reference");
return std::nullopt;
}

SPDLOG_INFO("FName::get_to_string: str_ref={:x}", *str_ref);

const auto containing_func = utility::find_function_start(*str_ref);

if (!containing_func) {
SPDLOG_ERROR("FName::get_to_string: Failed to find containing function");
return std::nullopt;
}

SPDLOG_INFO("FName::get_to_string: containing_func={:x}", *containing_func);

std::optional<uintptr_t> last_direct_call{};
std::optional<FName::ToStringFn> result{};

// It's known that FName::ToString precedes a string reference to TAutoWeakObjectPtr<%s%s>.
// So we must keep track of each direct call that precedes it
// because there's an indirect call that also precedes it.
// TODO: Possibly fix this for modular builds.
utility::exhaustive_decode((uint8_t*)*containing_func, 100, [&](INSTRUX& ix, uintptr_t ip) -> utility::ExhaustionResult {
if (result) {
return utility::ExhaustionResult::BREAK;
}

SPDLOG_INFO("FName::get_to_string: containing_func={:x}", *containing_func);
if (std::string_view{ix.Mnemonic}.starts_with("CALL") && !ix.BranchInfo.IsIndirect) {
last_direct_call = utility::calculate_absolute((ip + ix.Length) - 4);
SPDLOG_INFO("Found a direct call to {:x}", *last_direct_call);
}

std::optional<uintptr_t> last_direct_call{};
std::optional<FName::ToStringFn> result{};
if (std::string_view{ix.Mnemonic}.starts_with("CALL")) {
return utility::ExhaustionResult::STEP_OVER;
}

// It's known that FName::ToString precedes a string reference to TAutoWeakObjectPtr<%s%s>.
// So we must keep track of each direct call that precedes it
// because there's an indirect call that also precedes it.
// TODO: Possibly fix this for modular builds.
utility::exhaustive_decode((uint8_t*)*containing_func, 100, [&](INSTRUX& ix, uintptr_t ip) -> utility::ExhaustionResult {
if (result) {
return utility::ExhaustionResult::BREAK;
}
const auto displacement = utility::resolve_displacement(ip);

if (std::string_view{ix.Mnemonic}.starts_with("CALL") && !ix.BranchInfo.IsIndirect) {
last_direct_call = utility::calculate_absolute((ip + ix.Length) - 4);
SPDLOG_INFO("Found a direct call to {:x}", *last_direct_call);
if (displacement && *displacement == str_data) {
if (last_direct_call) {
result = (FName::ToStringFn)*last_direct_call;
}

if (std::string_view{ix.Mnemonic}.starts_with("CALL")) {
return utility::ExhaustionResult::STEP_OVER;
}
return utility::ExhaustionResult::BREAK;
}

const auto displacement = utility::resolve_displacement(ip);
return utility::ExhaustionResult::CONTINUE;
});

if (displacement && *displacement == str_data) {
if (last_direct_call) {
result = (FName::ToStringFn)*last_direct_call;
}
if (!result) {
SPDLOG_ERROR("FName::get_to_string: Failed to find ToString function");
return std::nullopt;
}

return utility::ExhaustionResult::BREAK;
}
SPDLOG_INFO("FName::get_to_string: result={:x}", (uintptr_t)*result);

return utility::ExhaustionResult::CONTINUE;
});
return result;
}
}

if (!result) {
SPDLOG_ERROR("FName::get_to_string: Failed to find ToString function");
return std::nullopt;
}
std::optional<FName::ToStringFn> FName::get_to_string() {
static auto result = []() -> std::optional<FName::ToStringFn> {
SPDLOG_INFO("FName::get_to_string");

SPDLOG_INFO("FName::get_to_string: result={:x}", (uintptr_t)*result);
const auto inlined_result = detail::inlined_find_to_string();

if (inlined_result) {
return inlined_result;
}

return result;
return detail::standard_find_to_string();
}();

return result;
Expand All @@ -268,6 +346,9 @@ std::wstring FName::to_string() const {

const auto fn = *to_string;
TArray<wchar_t> buffer{};
/*wchar_t scratch[512]{};
buffer.data = scratch;
buffer.capacity = 512 * sizeof(wchar_t);*/

fn(this, &buffer);

Expand Down
10 changes: 6 additions & 4 deletions shared/sdk/UObjectArray.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ FUObjectArray* FUObjectArray::get() {
}

// At this point we've found it, check if it's a chunked array or not, and set a static variable
if (IsBadReadPtr(*(void**)&potential_max_chunks, sizeof(void*)) && potential_max_chunks > 0 && potential_num_chunks > 0) {
if (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 Down Expand Up @@ -141,7 +141,9 @@ FUObjectArray* FUObjectArray::get() {
}

// do an initial first pass as a test
/*for (auto i = 0; i < result->get_object_count(); ++i) {
SPDLOG_INFO("[FUObjectArray::get] {} objects", result->get_object_count());

for (auto i = 0; i < result->get_object_count(); ++i) {
auto item = result->get_object(i);
if (item == nullptr) {
continue;
Expand All @@ -159,9 +161,9 @@ FUObjectArray* FUObjectArray::get() {

SPDLOG_INFO("{} {}", i, utility::narrow(name));
} catch(...) {
SPDLOG_ERROR("Failed to get name {}", i);
}
}*/
}

return result;
}();
Expand Down
5 changes: 4 additions & 1 deletion shared/sdk/UObjectArray.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#pragma once

#include <cstdint>

namespace sdk {
class UObjectBase;

Expand All @@ -19,7 +21,8 @@ struct FUObjectArray {

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

return *(int32_t*)((uintptr_t)this + OBJECTS_OFFSET + 0x8);
Expand Down

0 comments on commit aa8bd2d

Please sign in to comment.