From a50a9284aa296870da8980958472c58fd9b7d348 Mon Sep 17 00:00:00 2001 From: apple1417 Date: Sat, 22 Feb 2025 19:23:46 +1300 Subject: [PATCH 1/5] add helper to narrow ints from config --- src/unrealsdk/config.h | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/unrealsdk/config.h b/src/unrealsdk/config.h index ca88bff..78a0fb1 100644 --- a/src/unrealsdk/config.h +++ b/src/unrealsdk/config.h @@ -25,6 +25,39 @@ std::optional get_bool(std::string_view path); std::optional get_int(std::string_view path); std::optional 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::optional get_int(std::string_view path) { + auto result = get_int(path); + if (!result.has_value()) { + return std::nullopt; + } + + if constexpr (std::is_signed_v) { + auto val = *result; + if (std::numeric_limits::min() <= val && val <= std::numeric_limits::max()) { + return {static_cast(val)}; + } + } else { + if (*result < 0) { + return std::nullopt; + } + + auto val = static_cast(*result); + if (val <= std::numeric_limits::max()) { + return {static_cast(val)}; + } + } + + return std::nullopt; +} + /** * @brief Gets the path of the config file, in case you want to do your own parsing. * From a6040da4220bc6b0c38a68935ae95b07f6247911 Mon Sep 17 00:00:00 2001 From: apple1417 Date: Sat, 22 Feb 2025 19:26:39 +1300 Subject: [PATCH 2/5] support sending property change events --- src/unrealsdk/pch.h | 1 + src/unrealsdk/unreal/classes/uobject.cpp | 43 +++++++++++ src/unrealsdk/unreal/classes/uobject.h | 24 ++++++- .../unreal/structs/fpropertychangeevent.cpp | 32 +++++++++ .../unreal/structs/fpropertychangeevent.h | 72 +++++++++++++++++++ supported_settings.toml | 5 ++ 6 files changed, 175 insertions(+), 2 deletions(-) create mode 100644 src/unrealsdk/unreal/structs/fpropertychangeevent.cpp create mode 100644 src/unrealsdk/unreal/structs/fpropertychangeevent.h diff --git a/src/unrealsdk/pch.h b/src/unrealsdk/pch.h index d22d834..15e1e2d 100644 --- a/src/unrealsdk/pch.h +++ b/src/unrealsdk/pch.h @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include diff --git a/src/unrealsdk/unreal/classes/uobject.cpp b/src/unrealsdk/unreal/classes/uobject.cpp index e2f4221..46532b0 100644 --- a/src/unrealsdk/unreal/classes/uobject.cpp +++ b/src/unrealsdk/unreal/classes/uobject.cpp @@ -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" @@ -80,4 +83,44 @@ BoundFunction UObject::get(const FName& name, size_t i return this->get(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("unrealsdk.uobject_post_edit_change_property_vf_index") + .value_or(default_idx); + + this->call_virtual_function(idx, &event); +} + +void UObject::post_edit_change_chain_property(UProperty* prop, + const std::vector& 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("unrealsdk.uobject_post_edit_change_chain_property_vf_index") + .value_or(default_idx); // NOLINT(readability-magic-numbers) + this->call_virtual_function(idx, &event); +} + +void UObject::post_edit_change_chain_property(UProperty* prop, + std::initializer_list chain) const { + std::vector vector_chain{chain}; + this->post_edit_change_chain_property(prop, vector_chain); +} + } // namespace unrealsdk::unreal diff --git a/src/unrealsdk/unreal/classes/uobject.h b/src/unrealsdk/unreal/classes/uobject.h index e5bce0f..52abe3a 100644 --- a/src/unrealsdk/unreal/classes/uobject.h +++ b/src/unrealsdk/unreal/classes/uobject.h @@ -15,6 +15,7 @@ namespace unrealsdk::unreal { struct FImplementedInterface; class UClass; +class UProperty; class UObject { public: @@ -79,8 +80,13 @@ class UObject { */ template R call_virtual_function(size_t index, Args... args) const { - return reinterpret_cast(this->vftable[index])(this, - args...); +#ifdef ARCH_X86 + // NOLINTNEXTLINE(modernize-use-using) + typedef R(__thiscall * func_ptr)(const UObject*, Args...); +#else + using func_ptr = R (*)(const UObject*, Args...); +#endif + return reinterpret_cast(this->vftable[index])(this, args...); } /** @@ -146,6 +152,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& chain) const; + void post_edit_change_chain_property(UProperty* prop, + std::initializer_list chain) const; }; template <> diff --git a/src/unrealsdk/unreal/structs/fpropertychangeevent.cpp b/src/unrealsdk/unreal/structs/fpropertychangeevent.cpp new file mode 100644 index 0000000..6acb808 --- /dev/null +++ b/src/unrealsdk/unreal/structs/fpropertychangeevent.cpp @@ -0,0 +1,32 @@ +#include "unrealsdk/pch.h" +#include "unrealsdk/unreal/structs/fpropertychangeevent.h" + +namespace unrealsdk::unreal { + +class UProperty; + +FEditPropertyChain::FEditPropertyChain(const std::vector& 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.size() > 0) { + this->head = &this->nodes.front(); + this->tail = &this->nodes.back(); + } +} + +} // namespace unrealsdk::unreal diff --git a/src/unrealsdk/unreal/structs/fpropertychangeevent.h b/src/unrealsdk/unreal/structs/fpropertychangeevent.h new file mode 100644 index 0000000..a6911a0 --- /dev/null +++ b/src/unrealsdk/unreal/structs/fpropertychangeevent.h @@ -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& 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 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 */ diff --git a/supported_settings.toml b/supported_settings.toml index ee65984..e2a4e36 100644 --- a/supported_settings.toml +++ b/supported_settings.toml @@ -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 From 6222756ce39697b9722f09e06db0cc31686371d0 Mon Sep 17 00:00:00 2001 From: apple1417 Date: Sat, 22 Feb 2025 19:27:53 +1300 Subject: [PATCH 3/5] make array setter type error message clearer --- src/unrealsdk/unreal/classes/properties/uarrayproperty.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/unrealsdk/unreal/classes/properties/uarrayproperty.cpp b/src/unrealsdk/unreal/classes/properties/uarrayproperty.cpp index f016d67..7587c29 100644 --- a/src/unrealsdk/unreal/classes/properties/uarrayproperty.cpp +++ b/src/unrealsdk/unreal/classes/properties/uarrayproperty.cpp @@ -37,8 +37,9 @@ void PropTraits::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*>(addr); From 9faca9e5333d7a11630813b0fe21f4ef6a64c915 Mon Sep 17 00:00:00 2001 From: apple1417 Date: Sat, 22 Feb 2025 20:59:09 +1300 Subject: [PATCH 4/5] update changelog --- CMakeLists.txt | 2 +- changelog.md | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e9dd1da..dff7142 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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'.") diff --git a/changelog.md b/changelog.md index 123d574..3005358 100644 --- a/changelog.md +++ b/changelog.md @@ -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. From 5f465ccedce3ac0bb4f8f0652f93fa023bf1acd6 Mon Sep 17 00:00:00 2001 From: apple1417 Date: Sat, 22 Feb 2025 21:10:15 +1300 Subject: [PATCH 5/5] linting fixups --- src/unrealsdk/unreal/classes/uobject.cpp | 2 +- src/unrealsdk/unreal/classes/uobject.h | 10 +++++++++- src/unrealsdk/unreal/structs/fpropertychangeevent.cpp | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/unrealsdk/unreal/classes/uobject.cpp b/src/unrealsdk/unreal/classes/uobject.cpp index 46532b0..28a8242 100644 --- a/src/unrealsdk/unreal/classes/uobject.cpp +++ b/src/unrealsdk/unreal/classes/uobject.cpp @@ -119,7 +119,7 @@ void UObject::post_edit_change_chain_property(UProperty* prop, void UObject::post_edit_change_chain_property(UProperty* prop, std::initializer_list chain) const { - std::vector vector_chain{chain}; + const std::vector vector_chain{chain}; this->post_edit_change_chain_property(prop, vector_chain); } diff --git a/src/unrealsdk/unreal/classes/uobject.h b/src/unrealsdk/unreal/classes/uobject.h index 52abe3a..2e7325e 100644 --- a/src/unrealsdk/unreal/classes/uobject.h +++ b/src/unrealsdk/unreal/classes/uobject.h @@ -81,11 +81,19 @@ class UObject { template R call_virtual_function(size_t index, Args... args) const { #ifdef ARCH_X86 - // NOLINTNEXTLINE(modernize-use-using) +#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(this->vftable[index])(this, args...); } diff --git a/src/unrealsdk/unreal/structs/fpropertychangeevent.cpp b/src/unrealsdk/unreal/structs/fpropertychangeevent.cpp index 6acb808..3e2d361 100644 --- a/src/unrealsdk/unreal/structs/fpropertychangeevent.cpp +++ b/src/unrealsdk/unreal/structs/fpropertychangeevent.cpp @@ -23,7 +23,7 @@ FEditPropertyChain::FEditPropertyChain(const std::vector& chain) idx++; } - if (chain.size() > 0) { + if (!chain.empty()) { this->head = &this->nodes.front(); this->tail = &this->nodes.back(); }