Skip to content

Commit

Permalink
Merge pull request #58 from apple1417/master
Browse files Browse the repository at this point in the history
handle UStruct differing in size between bl2 + tps
  • Loading branch information
apple1417 authored Jan 1, 2025
2 parents 9d5303b + 4670120 commit dd818f6
Show file tree
Hide file tree
Showing 14 changed files with 200 additions and 45 deletions.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.25)

project(unrealsdk VERSION 1.5.1)
project(unrealsdk VERSION 1.6.0)

set(UNREALSDK_UE_VERSION "UE4" CACHE STRING "The unreal engine version to build the SDK for. One of 'UE3' or 'UE4'.")
set(UNREALSDK_ARCH "x64" CACHE STRING "The architecture to build the sdk for. One of 'x86' or 'x64'.")
Expand Down
11 changes: 10 additions & 1 deletion changelog.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
# Changelog

## v1.5.1
## v1.6.0

- Handled `UStruct` differing in size between BL2 and TPS.

This affects all members on it's subclasses - `UClass::ClassDefaultObject`, `UClass::Interfaces`,
`UFunction::FunctionFlags`, `UFunction::NumParams`, `UFunction::ParamsSize`,
`UFunction::ReturnValueOffset`, and `UScriptStruct::StructFlags` have all now changed to methods
which return a reference.

[70854d65](https://github.com/bl-sdk/unrealsdk/commit/70854d65)

- Fixed all BL3 console output being treated as console commands instead.

Expand Down
2 changes: 1 addition & 1 deletion src/unrealsdk/game/bl2/console.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ bool say_crash_fix_hook(hook_manager::Details& hook) {
static const auto get_timestamp_string_func =
hook.obj->Class->find_func_and_validate(L"GetTimestampString"_fn);
static const auto default_save_game_manager =
find_class(L"WillowSaveGameManager"_fn)->ClassDefaultObject;
find_class(L"WillowSaveGameManager"_fn)->ClassDefaultObject();
static const auto time_format_prop =
default_save_game_manager->Class->find_prop_and_validate<UStrProperty>(L"TimeFormat"_fn);

Expand Down
2 changes: 1 addition & 1 deletion src/unrealsdk/game/bl3/console.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ bool inject_console_hook(hook_manager::Details& hook) {
console = viewport->get(console_property);

if (console == nullptr) {
auto default_console = console_property->get_property_class()->ClassDefaultObject;
auto default_console = console_property->get_property_class()->ClassDefaultObject();
console = unrealsdk::construct_object(default_console->Class, default_console->Outer);
viewport->set<UObjectProperty>(L"ViewportConsole"_fn, console);
}
Expand Down
16 changes: 15 additions & 1 deletion src/unrealsdk/unreal/classes/uclass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,20 @@

namespace unrealsdk::unreal {

decltype(UClass::ClassDefaultObject_internal)& UClass::ClassDefaultObject(void) {
return this->get_field(&UClass::ClassDefaultObject_internal);
}
[[nodiscard]] const decltype(UClass::ClassDefaultObject_internal)& UClass::ClassDefaultObject(
void) const {
return this->get_field(&UClass::ClassDefaultObject_internal);
}
decltype(UClass::Interfaces_internal)& UClass::Interfaces(void) {
return this->get_field(&UClass::Interfaces_internal);
}
[[nodiscard]] const decltype(UClass::Interfaces_internal)& UClass::Interfaces(void) const {
return this->get_field(&UClass::Interfaces_internal);
}

bool UClass::implements(const UClass* iface, FImplementedInterface* impl_out) const {
// For each class in the inheritance chain
for (const UObject* superfield : this->superfields()) {
Expand All @@ -16,7 +30,7 @@ bool UClass::implements(const UClass* iface, FImplementedInterface* impl_out) co
auto cls = reinterpret_cast<const UClass*>(superfield);

// For each interface on that class
for (auto our_iface : cls->Interfaces) {
for (auto our_iface : cls->Interfaces()) {
// If the interface matches
if (our_iface.Class == iface) {
// Output the implementation, if necessary
Expand Down
27 changes: 10 additions & 17 deletions src/unrealsdk/unreal/classes/uclass.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,13 @@ class UClass : public UStruct {

// NOLINTBEGIN(readability-magic-numbers, readability-identifier-naming)

#ifdef UE4
private:
#ifdef UE4
uint8_t UnknownData00[0x70];

public:
UObject* ClassDefaultObject;

private:
UObject* ClassDefaultObject_internal;
uint8_t UnknownData01[0xA0];

public:
TArray<FImplementedInterface> Interfaces;
TArray<FImplementedInterface> Interfaces_internal;
#else
private:
// Misc Fields I found within this block in BL2, but which we don't care about enough for me to
// find in UE4, or to want to increase the compile times by including

Expand All @@ -53,16 +46,16 @@ class UClass : public UStruct {
// 0x10C: TArray<FName> AutoExpandCategories;

uint8_t UnknownData00[0xCC];

public:
UObject* ClassDefaultObject;

private:
UObject* ClassDefaultObject_internal;
uint8_t UnknownData01[0x48];
TArray<FImplementedInterface> Interfaces_internal;
#endif

public:
TArray<FImplementedInterface> Interfaces;
#endif
decltype(ClassDefaultObject_internal)& ClassDefaultObject(void);
[[nodiscard]] const decltype(ClassDefaultObject_internal)& ClassDefaultObject(void) const;
decltype(Interfaces_internal)& Interfaces(void);
[[nodiscard]] const decltype(Interfaces_internal)& Interfaces(void) const;

// NOLINTEND(readability-magic-numbers, readability-identifier-naming)

Expand Down
27 changes: 27 additions & 0 deletions src/unrealsdk/unreal/classes/ufunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,33 @@

namespace unrealsdk::unreal {

decltype(UFunction::FunctionFlags_internal)& UFunction::FunctionFlags(void) {
return this->get_field(&UFunction::FunctionFlags_internal);
}
[[nodiscard]] const decltype(UFunction::FunctionFlags_internal)& UFunction::FunctionFlags(
void) const {
return this->get_field(&UFunction::FunctionFlags_internal);
}
decltype(UFunction::NumParams_internal)& UFunction::NumParams(void) {
return this->get_field(&UFunction::NumParams_internal);
}
[[nodiscard]] const decltype(UFunction::NumParams_internal)& UFunction::NumParams(void) const {
return this->get_field(&UFunction::NumParams_internal);
}
decltype(UFunction::ParamsSize_internal)& UFunction::ParamsSize(void) {
return this->get_field(&UFunction::ParamsSize_internal);
}
[[nodiscard]] const decltype(UFunction::ParamsSize_internal)& UFunction::ParamsSize(void) const {
return this->get_field(&UFunction::ParamsSize_internal);
}
decltype(UFunction::ReturnValueOffset_internal)& UFunction::ReturnValueOffset(void) {
return this->get_field(&UFunction::ReturnValueOffset_internal);
}
[[nodiscard]] const decltype(UFunction::ReturnValueOffset_internal)& UFunction::ReturnValueOffset(
void) const {
return this->get_field(&UFunction::ReturnValueOffset_internal);
}

UProperty* UFunction::find_return_param(void) const {
for (auto prop : this->properties()) {
if ((prop->PropertyFlags & UProperty::PROP_FLAG_RETURN) != 0) {
Expand Down
35 changes: 18 additions & 17 deletions src/unrealsdk/unreal/classes/ufunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,39 +29,40 @@ class UFunction : public UStruct {

// NOLINTBEGIN(readability-magic-numbers, readability-identifier-naming)

#ifdef UE4
uint32_t FunctionFlags;
uint8_t NumParams;
uint16_t ParamsSize;
uint16_t ReturnValueOffset;

private:
#ifdef UE4
uint32_t FunctionFlags_internal;
uint8_t NumParams_internal;
uint16_t ParamsSize_internal;
uint16_t ReturnValueOffset_internal;
uint16_t RPCId;
uint16_t RPCResponseId;
UProperty* FirstPropertyToInit;
UFunction* EventGraphFunction;
int32_t EventGraphCallOffset;
void* Func;
#else
uint32_t FunctionFlags;

private:
uint32_t FunctionFlags_internal;
uint16_t iNative;
uint16_t RepOffset;
FName FriendlyName;
uint8_t OperPrecedence;

public:
uint8_t NumParams;
uint16_t ParamsSize;
uint16_t ReturnValueOffset;

private:
uint8_t NumParams_internal;
uint16_t ParamsSize_internal;
uint16_t ReturnValueOffset_internal;
uint8_t UnknownData00[0x6];
void* Func;
#endif

public:
decltype(FunctionFlags_internal)& FunctionFlags(void);
[[nodiscard]] const decltype(FunctionFlags_internal)& FunctionFlags(void) const;
decltype(NumParams_internal)& NumParams(void);
[[nodiscard]] const decltype(NumParams_internal)& NumParams(void) const;
decltype(ParamsSize_internal)& ParamsSize(void);
[[nodiscard]] const decltype(ParamsSize_internal)& ParamsSize(void) const;
decltype(ReturnValueOffset_internal)& ReturnValueOffset(void);
[[nodiscard]] const decltype(ReturnValueOffset_internal)& ReturnValueOffset(void) const;

/**
* @brief Finds the return param for this function (if it exists).
*
Expand Down
15 changes: 15 additions & 0 deletions src/unrealsdk/unreal/classes/uscriptstruct.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#include "unrealsdk/pch.h"
#include "unrealsdk/unreal/classes/uscriptstruct.h"
#include "unrealsdk/unreal/classes/ustruct.h"

namespace unrealsdk::unreal {

decltype(UScriptStruct::StructFlags_internal)& UScriptStruct::StructFlags(void) {
return this->get_field(&UScriptStruct::StructFlags_internal);
}
[[nodiscard]] const decltype(UScriptStruct::StructFlags_internal)& UScriptStruct::StructFlags(
void) const {
return this->get_field(&UScriptStruct::StructFlags_internal);
}

} // namespace unrealsdk::unreal
12 changes: 10 additions & 2 deletions src/unrealsdk/unreal/classes/uscriptstruct.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,16 @@ class UScriptStruct : public UStruct {
UScriptStruct& operator=(UScriptStruct&&) = delete;
~UScriptStruct() = delete;

// NOLINTNEXTLINE(readability-identifier-naming)
uint32_t StructFlags;
// NOLINTBEGIN(readability-identifier-naming)

private:
uint32_t StructFlags_internal;

public:
decltype(StructFlags_internal)& StructFlags(void);
[[nodiscard]] const decltype(StructFlags_internal)& StructFlags(void) const;

// NOLINTEND(readability-identifier-naming)
};

template <>
Expand Down
47 changes: 47 additions & 0 deletions src/unrealsdk/unreal/classes/ustruct.cpp
Original file line number Diff line number Diff line change
@@ -1,15 +1,62 @@
#include "unrealsdk/pch.h"

#include "unrealsdk/config.h"
#include "unrealsdk/unreal/class_name.h"
#include "unrealsdk/unreal/classes/ufield.h"
#include "unrealsdk/unreal/classes/ufunction.h"
#include "unrealsdk/unreal/classes/uproperty.h"
#include "unrealsdk/unreal/classes/ustruct.h"
#include "unrealsdk/unreal/wrappers/bound_function.h"
#include "unrealsdk/unreal/wrappers/gobjects.h"
#include "unrealsdk/utils.h"

namespace unrealsdk::unreal {

#ifdef UE3

size_t UStruct::class_size(void) {
static size_t size = 0;
if (size != 0) {
return size;
}

auto config_size = config::get_int("unrealsdk.ustruct_size");
if (config_size.has_value()) {
size = (size_t)*config_size;
return size;
}

// Rather than bother with a find object/class, we can recover UStruct from any arbitrary object
// This just avoids extra dependencies, especially since theoretically find class might depend
// on this

// First, find UClass
auto obj = *unrealsdk::gobjects().begin();
const UClass* class_cls = obj->Class;
for (; class_cls->Class != class_cls; class_cls = class_cls->Class) {}

// Then look through it's superfields for UStruct
const UStruct* struct_cls = nullptr;
for (auto superfield : class_cls->superfields()) {
if (superfield->Name == L"Struct"_fn) {
struct_cls = superfield;
break;
}
}

// If we couldn't find the class, default to our actual size
if (struct_cls == nullptr) {
size = sizeof(UStruct);
LOG(WARNING, "Couldn't find UStruct class size, defaulting to: {:#x}", size);
} else {
size = struct_cls->get_struct_size();
LOG(MISC, "UStruct class size: {:#x}", size);
}
return size;
}

#endif

#pragma region Field Iterator

UStruct::FieldIterator::FieldIterator(void) : this_struct(nullptr), field(nullptr) {}
Expand Down
41 changes: 40 additions & 1 deletion src/unrealsdk/unreal/classes/ustruct.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,50 @@ class UStruct : public UField {
uint8_t UnknownData02[0x10];

TArray<UObject*> ScriptObjectReferences;

// See the description in 'uproperty.h', we have the same issue here. `UnknownData02` is 0x10 in
// BL2, but 0x4 in TPS. Since we need it this time, we also make provisions for setters.

/**
* @brief Gets the size of this class.
*
* @return The size of this class.
*/
[[nodiscard]] static size_t class_size(void);

#endif
protected:
/**
* @brief Reads a field on a UStruct subclass, taking into account it's variable length.
*
* @tparam SubType The subclass of UStruct to read the field off of (should be picked up
* automatically).
* @tparam FieldType The type of the field being read (should be picked up automatically).
* @param field Pointer to member of the field to read.
* @return A reference to the field.
*/
template <typename SubType,
typename FieldType,
typename = std::enable_if_t<std::is_base_of_v<UStruct, SubType>>>
[[nodiscard]] const FieldType& get_field(FieldType SubType::*field) const {
#ifdef UE4
return reinterpret_cast<const SubType*>(this)->*field;
#else
return *reinterpret_cast<FieldType*>(
reinterpret_cast<uintptr_t>(&(reinterpret_cast<const SubType*>(this)->*field))
- sizeof(UStruct) + UStruct::class_size());
#endif
}
template <typename SubType,
typename FieldType,
typename = std::enable_if_t<std::is_base_of_v<UStruct, SubType>>>
FieldType& get_field(FieldType SubType::*field) {
return const_cast<FieldType&>(const_cast<const UStruct*>(this)->get_field(field));
}

public:
// NOLINTEND(readability-magic-numbers, readability-identifier-naming)

public:
#pragma region Iterators
struct FieldIterator {
using iterator_category = std::forward_iterator_tag;
Expand Down
6 changes: 3 additions & 3 deletions src/unrealsdk/unreal/wrappers/bound_function.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,14 @@ UNREALSDK_CAPI(void, bound_function_call_with_params, const BoundFunction* self,
UNREALSDK_CAPI(void, bound_function_call_with_params, const BoundFunction* self, void* params) {
const locks::FunctionCall lock{};

auto original_flags = self->func->FunctionFlags;
self->func->FunctionFlags |= UFunction::FUNC_NATIVE;
auto original_flags = self->func->FunctionFlags();
self->func->FunctionFlags() |= UFunction::FUNC_NATIVE;

// Calling process event itself does hold the lock, but we also need to guard messing with the
// function flags
unrealsdk::internal::process_event(self->object, self->func, params);

self->func->FunctionFlags = original_flags;
self->func->FunctionFlags() = original_flags;
}

#endif
Expand Down
2 changes: 2 additions & 0 deletions supported_settings.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ exe_override = ""

# Changes the size the `UProperty` class is assumed to have.
uproperty_size = -1
# Changes the size the `UStruct` class is assumed to have.
ustruct_size = -1
# Changes the alignment used when calling the unreal memory allocation functions.
alloc_alignment = -1

Expand Down

0 comments on commit dd818f6

Please sign in to comment.