Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.7.0)
project(unrealsdk VERSION 1.8.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
13 changes: 13 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
# Changelog

## 1.8.0

- Added support for sending property changed events, via `UObject::post_edit_change_property` and
`UObject::post_edit_change_chain_property`.

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

- Made the error message when assigning incompatible array types more clear.

See also https://github.com/bl-sdk/unrealsdk/issues/60 .

[6222756c](https://github.com/bl-sdk/unrealsdk/commit/6222756c)

## 1.7.0

- `unrealsdk::unreal::cast` now copies the const-ness of its input object to its callbacks.
Expand Down
33 changes: 33 additions & 0 deletions src/unrealsdk/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,39 @@ std::optional<bool> get_bool(std::string_view path);
std::optional<int64_t> get_int(std::string_view path);
std::optional<std::string_view> get_str(std::string_view path);

/**
* @brief Gets an integer value from the config file, converting it to fit a specified type.
*
* @tparam T The integer type.
* @param path The path to the value to get - e.g. "unrealsdk.outerfield[1].innerfield".
* @return The returned value, or std::nullopt if not set, the incorrect type, or out of range.
*/
template <std::integral T>
std::optional<T> get_int(std::string_view path) {
auto result = get_int(path);
if (!result.has_value()) {
return std::nullopt;
}

if constexpr (std::is_signed_v<T>) {
auto val = *result;
if (std::numeric_limits<T>::min() <= val && val <= std::numeric_limits<T>::max()) {
return {static_cast<T>(val)};
}
} else {
if (*result < 0) {
return std::nullopt;
}

auto val = static_cast<uint64_t>(*result);
if (val <= std::numeric_limits<T>::max()) {
return {static_cast<T>(val)};
}
}

return std::nullopt;
}

/**
* @brief Gets the path of the config file, in case you want to do your own parsing.
*
Expand Down
1 change: 1 addition & 0 deletions src/unrealsdk/pch.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
#include <filesystem>
#include <fstream>
#include <functional>
#include <initializer_list>
#include <iostream>
#include <limits>
#include <memory>
Expand Down
5 changes: 3 additions & 2 deletions src/unrealsdk/unreal/classes/properties/uarrayproperty.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,9 @@ void PropTraits<UArrayProperty>::set(const UArrayProperty* prop,
"Array has static array inner property - unsure how to handle, aborting!");
}
if (value.type != inner) {
throw std::runtime_error("Array does not contain fields of type "
+ (std::string)inner->Name);
throw std::runtime_error(utils::narrow(
unrealsdk::fmt::format(L"Array fields have incompatible type, expected {}, got {}",
inner->get_path_name(), value.type->get_path_name())));
}

auto arr = reinterpret_cast<TArray<void>*>(addr);
Expand Down
43 changes: 43 additions & 0 deletions src/unrealsdk/unreal/classes/uobject.cpp
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
#include "unrealsdk/pch.h"

#include "unrealsdk/config.h"
#include "unrealsdk/unreal/classes/uclass.h"
#include "unrealsdk/unreal/classes/ufunction.h"
#include "unrealsdk/unreal/classes/uobject.h"
#include "unrealsdk/unreal/classes/uproperty.h"
#include "unrealsdk/unreal/classes/ustruct_funcs.h"
#include "unrealsdk/unreal/prop_traits.h"
#include "unrealsdk/unreal/structs/fname.h"
#include "unrealsdk/unreal/structs/fpropertychangeevent.h"
#include "unrealsdk/unreal/wrappers/bound_function.h"
#include "unrealsdk/unrealsdk.h"

Expand Down Expand Up @@ -80,4 +83,44 @@ BoundFunction UObject::get<UFunction, BoundFunction>(const FName& name, size_t i
return this->get<UFunction, BoundFunction>(this->Class->find_func_and_validate(name), idx);
}

void UObject::post_edit_change_property(const FName& name) const {
this->post_edit_change_property(this->Class->find_prop(name));
}
void UObject::post_edit_change_property(UProperty* prop) const {
FPropertyChangedEvent event{prop};

#ifdef UE3
constexpr auto default_idx = 19;
#else
constexpr auto default_idx = 78;
#endif
static auto idx =
config::get_int<size_t>("unrealsdk.uobject_post_edit_change_property_vf_index")
.value_or(default_idx);

this->call_virtual_function<void, FPropertyChangedEvent*>(idx, &event);
}

void UObject::post_edit_change_chain_property(UProperty* prop,
const std::vector<UProperty*>& chain) const {
FEditPropertyChain edit_chain{chain};
FPropertyChangedChainEvent event{prop, &edit_chain};

#ifdef UE3
constexpr auto default_idx = 18;
#else
constexpr auto default_idx = 77;
#endif
static auto idx =
config::get_int<size_t>("unrealsdk.uobject_post_edit_change_chain_property_vf_index")
.value_or(default_idx); // NOLINT(readability-magic-numbers)
this->call_virtual_function<void, FPropertyChangedEvent*>(idx, &event);
}

void UObject::post_edit_change_chain_property(UProperty* prop,
std::initializer_list<UProperty*> chain) const {
const std::vector<UProperty*> vector_chain{chain};
this->post_edit_change_chain_property(prop, vector_chain);
}

} // namespace unrealsdk::unreal
32 changes: 30 additions & 2 deletions src/unrealsdk/unreal/classes/uobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ namespace unrealsdk::unreal {

struct FImplementedInterface;
class UClass;
class UProperty;

class UObject {
public:
Expand Down Expand Up @@ -79,8 +80,21 @@ class UObject {
*/
template <typename R, typename... Args>
R call_virtual_function(size_t index, Args... args) const {
return reinterpret_cast<R (*)(const UObject*, Args...)>(this->vftable[index])(this,
args...);
#ifdef ARCH_X86
#if defined(__MINGW32__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wattributes" // thiscall on non-class
#endif
// NOLINTNEXTLINE(modernize-use-using) - need a typedef for the __thiscall
typedef R(__thiscall * func_ptr)(const UObject*, Args...);
#if defined(__MINGW32__)
#pragma GCC diagnostic pop
#endif
#else
using func_ptr = R (*)(const UObject*, Args...);
#endif

return reinterpret_cast<func_ptr>(this->vftable[index])(this, args...);
}

/**
Expand Down Expand Up @@ -146,6 +160,20 @@ class UObject {
*/
[[nodiscard]] bool is_implementation(const UClass* iface,
FImplementedInterface* impl_out = nullptr) const;

/**
* @brief Notifies the engine that we've made an external change to a property.
*
* @param name The name of the property which was changed.
* @param prop The property which was changed.
* @param chain The chain of properties to follow, if the change was within a struct.
*/
void post_edit_change_property(const FName& name) const;
void post_edit_change_property(UProperty* prop) const;
void post_edit_change_chain_property(UProperty* prop,
const std::vector<UProperty*>& chain) const;
void post_edit_change_chain_property(UProperty* prop,
std::initializer_list<UProperty*> chain) const;
};

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

namespace unrealsdk::unreal {

class UProperty;

FEditPropertyChain::FEditPropertyChain(const std::vector<UProperty*>& chain)
: size((uint32_t)chain.size()) {
this->nodes.resize(chain.size());

size_t idx = 0;
for (auto prop : chain) {
this->nodes[idx].value = prop;

if (idx > 0) {
this->nodes[idx].prev = &this->nodes[idx - 1];
}
if (idx < (chain.size() - 1)) {
this->nodes[idx].next = &this->nodes[idx + 1];
}

idx++;
}

if (!chain.empty()) {
this->head = &this->nodes.front();
this->tail = &this->nodes.back();
}
}

} // namespace unrealsdk::unreal
72 changes: 72 additions & 0 deletions src/unrealsdk/unreal/structs/fpropertychangeevent.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#ifndef UNREALSDK_UNREAL_STRUCTS_FPROPERTYCHANGEEVENT_H
#define UNREALSDK_UNREAL_STRUCTS_FPROPERTYCHANGEEVENT_H

#include "unrealsdk/pch.h"

namespace unrealsdk::unreal {

class UProperty;

#if defined(_MSC_VER) && defined(ARCH_X86)
#pragma pack(push, 0x4)
#endif

struct FPropertyChangedEvent {
static constexpr auto CHANGE_TYPE_UNSPECIFIED = 1;

/**
* @brief Creates a new property changed event.
*
* @param prop The property which was changed.
*/
FPropertyChangedEvent(UProperty* prop) : Property(prop), ChangeType(CHANGE_TYPE_UNSPECIFIED) {}

protected:
// NOLINTBEGIN(readability-identifier-naming)
UProperty* Property{};
UProperty* MemberProperty{};
uint32_t ChangeType{};
int32_t ObjectIteratorIndex{};
// NOLINTEND(readability-identifier-naming)
};

struct FEditPropertyChain {
public:
/**
* @brief Constructs a new edit chain pointing at the give properties.
*
* @param chain The chain of properties.
*/
FEditPropertyChain(const std::vector<UProperty*>& chain);

protected:
struct Node {
UProperty* value = nullptr;
Node* next = nullptr;
Node* prev = nullptr;
};
Node* head = nullptr;
Node* tail = nullptr;
uint32_t size = 0;

// I think there's some real fields here, but a zero-init seems to work well enough
// Add a bunch of padding before we add our own fields
// NOLINTNEXTLINE(readability-magic-numbers)
uint8_t dummy[64] = {0};

// Store all the nodes as part of the same object, so we don't need to deal with raw pointers
std::vector<Node> nodes;
};

struct FPropertyChangedChainEvent : public FPropertyChangedEvent {
// NOLINTNEXTLINE(readability-identifier-naming)
FEditPropertyChain* PropertyChain{};
};

#if defined(_MSC_VER) && defined(ARCH_X86)
#pragma pack(pop)
#endif

} // namespace unrealsdk::unreal

#endif /* UNREALSDK_UNREAL_STRUCTS_FPROPERTYCHANGEEVENT_H */
5 changes: 5 additions & 0 deletions supported_settings.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,8 @@ locking_function_calls = false

# After enabling `unrealsdk::hook_manager::log_all_calls`, the file to calls are logged to.
log_all_calls_file = "unrealsdk.calls.tsv"

# Overrides the virtual function index used when calling `UObject::PostEditChangeProperty`.
uobject_post_edit_change_property_vf_index = -1
# Overrides the virtual function index used when calling `UObject::PostEditChangeChainProperty`.
uobject_post_edit_change_chain property_vf_index = -1
Loading