From 6e70d170be19b996d33a10ae736e1eeaa22a427d Mon Sep 17 00:00:00 2001 From: apple1417 Date: Sun, 2 Mar 2025 11:28:55 +1300 Subject: [PATCH 01/11] unrealsdk v2 support --- .github/workflows/build.yml | 3 -- CMakeLists.txt | 2 +- common_cmake | 2 +- libs/unrealsdk | 2 +- src/pyunrealsdk/base_bindings.cpp | 16 +++--- src/pyunrealsdk/logging.cpp | 3 +- src/pyunrealsdk/pch.h | 9 ++++ src/pyunrealsdk/static_py_object.h | 10 ---- src/pyunrealsdk/unreal_bindings/bindings.h | 27 +++++++++- .../unreal_bindings/bound_function.cpp | 42 +++++++-------- .../unreal_bindings/property_access.cpp | 46 ++++++++--------- src/pyunrealsdk/unreal_bindings/uenum.cpp | 2 +- src/pyunrealsdk/unreal_bindings/uobject.cpp | 23 ++++----- .../unreal_bindings/uobject_children.cpp | 51 ++++++++----------- .../unreal_bindings/wrapped_array_helpers.cpp | 14 ++--- .../wrapped_array_magic_methods.cpp | 6 +-- .../unreal_bindings/wrapped_array_methods.cpp | 7 ++- .../wrapped_multicast_delegate.cpp | 6 +-- .../unreal_bindings/wrapped_struct.cpp | 17 +++---- src/pyunrealsdk/version.cpp | 13 +++-- 20 files changed, 151 insertions(+), 150 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d8f214b..f2c703d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -137,7 +137,6 @@ jobs: submodules: recursive - name: Configure CMake - working-directory: ${{ env.GITHUB_WORKSPACE }} run: cmake . --preset ${{ matrix.preset }} -DCMAKE_DISABLE_PRECOMPILE_HEADERS=On - name: Remove `.modmap`s from compile commands @@ -148,7 +147,6 @@ jobs: -Path "out\build\${{ matrix.preset }}\compile_commands.json" - name: Run clang-tidy - working-directory: ${{ env.GITHUB_WORKSPACE }} run: | python (Get-Command run-clang-tidy).Source ` -p "out\build\${{ matrix.preset }}" ` @@ -165,7 +163,6 @@ jobs: steps: - name: Setup Clang - if: startswith(matrix.preset, 'clang') uses: egor-tensin/setup-clang@v1 - name: Checkout repository diff --git a/CMakeLists.txt b/CMakeLists.txt index 4b14294..cd6d69e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,7 @@ project(pyunrealsdk VERSION 1.7.0) function(_pyunrealsdk_add_base_target_args target_name) - target_compile_features(${target_name} PUBLIC cxx_std_20) + target_compile_features(${target_name} PUBLIC cxx_std_23) set_target_properties(${target_name} PROPERTIES COMPILE_WARNING_AS_ERROR True INTERPROCEDURAL_OPTIMIZATION True diff --git a/common_cmake b/common_cmake index c27d687..92175e1 160000 --- a/common_cmake +++ b/common_cmake @@ -1 +1 @@ -Subproject commit c27d68718f01ca2e40fc2627fa7461b552a0d6ce +Subproject commit 92175e1c74b78bbb6746a589793cf54a9e7c5465 diff --git a/libs/unrealsdk b/libs/unrealsdk index 9621c36..c2a918d 160000 --- a/libs/unrealsdk +++ b/libs/unrealsdk @@ -1 +1 @@ -Subproject commit 9621c3643da2db05f3d8699d3646469135764420 +Subproject commit c2a918d3bb8edc2225d6aab7b5ebb80d25bc4349 diff --git a/src/pyunrealsdk/base_bindings.cpp b/src/pyunrealsdk/base_bindings.cpp index 1b2fe98..f0d10ed 100644 --- a/src/pyunrealsdk/base_bindings.cpp +++ b/src/pyunrealsdk/base_bindings.cpp @@ -54,8 +54,8 @@ CachedType find_cached_potentially_qualified(const std::wstring& name, auto val = fully_qualified.value() ? str_getter(name) : fname_getter(FName{name}); if (val == nullptr) { - throw std::invalid_argument(unrealsdk::fmt::format("Couldn't find {} '{}'", error_type_name, - unrealsdk::utils::narrow(name))); + throw std::invalid_argument( + std::format("Couldn't find {} '{}'", error_type_name, unrealsdk::utils::narrow(name))); } return val; @@ -245,8 +245,8 @@ void register_base_bindings(py::module_& mod) { [](const std::variant& cls_arg, const std::wstring& name) { auto val = unrealsdk::find_object(evaluate_class_arg(cls_arg), name); if (val == nullptr) { - throw std::invalid_argument(unrealsdk::fmt::format("Couldn't find object '{}'", - unrealsdk::utils::narrow(name))); + throw std::invalid_argument( + std::format("Couldn't find object '{}'", unrealsdk::utils::narrow(name))); } return val; }, @@ -282,7 +282,7 @@ void register_base_bindings(py::module_& mod) { std::vector results{}; if (exact) { std::copy_if(gobjects.begin(), GObjects::end(), std::back_inserter(results), - [cls_ptr](UObject* obj) { return obj->Class == cls_ptr; }); + [cls_ptr](UObject* obj) { return obj->Class() == cls_ptr; }); } else { std::copy_if(gobjects.begin(), GObjects::end(), std::back_inserter(results), [cls_ptr](UObject* obj) { return obj->is_instance(cls_ptr); }); @@ -305,13 +305,13 @@ void register_base_bindings(py::module_& mod) { mod.def( "construct_object", [](const std::variant& cls_arg, UObject* outer, const FName& name, - decltype(UObject::ObjectFlags) flags, UObject* template_obj) { + uint64_t flags, UObject* template_obj) { auto cls = evaluate_class_arg(cls_arg); auto val = unrealsdk::construct_object(cls, outer, name, flags, template_obj); if (val == nullptr) { - throw std::runtime_error(unrealsdk::fmt::format( - "Failed to construct object! cls: {}, outer: {}, name: {}", cls->Name, + throw std::runtime_error(std::format( + "Failed to construct object! cls: {}, outer: {}, name: {}", cls->Name(), unrealsdk::utils::narrow(outer->get_path_name()), name)); } return val; diff --git a/src/pyunrealsdk/logging.cpp b/src/pyunrealsdk/logging.cpp index 487122d..3d59844 100644 --- a/src/pyunrealsdk/logging.cpp +++ b/src/pyunrealsdk/logging.cpp @@ -1,6 +1,5 @@ #include "pyunrealsdk/pch.h" #include "pyunrealsdk/logging.h" -#include "unrealsdk/format.h" #include "unrealsdk/logging.h" #include "unrealsdk/unrealsdk.h" @@ -113,7 +112,7 @@ template void register_per_log_level_printer(py::module_& logging, const char* func_name, std::string_view docstring_name) { - const auto docstring = unrealsdk::fmt::format( + const auto docstring = std::format( "Wrapper around print(), which uses a custom file at the {} log level.\n" "\n" "Args:\n" diff --git a/src/pyunrealsdk/pch.h b/src/pyunrealsdk/pch.h index 35b58dc..2661336 100644 --- a/src/pyunrealsdk/pch.h +++ b/src/pyunrealsdk/pch.h @@ -31,6 +31,15 @@ namespace py = pybind11; using namespace pybind11::literals; +// By default, pybind tries to compile with visibility hidden. This macro copies it, to avoid +// warnings when we have a type holding/inheriting from python objects, due to our types having +// greater visibility. +#if defined(__MINGW32__) +#define PY_OBJECT_VISIBILITY __attribute__((visibility("hidden"))) +#else +#define PY_OBJECT_VISIBILITY +#endif + #include // Type casters need to be defined the same way in every file, so best to put here diff --git a/src/pyunrealsdk/static_py_object.h b/src/pyunrealsdk/static_py_object.h index 5b07a91..78b2d3d 100644 --- a/src/pyunrealsdk/static_py_object.h +++ b/src/pyunrealsdk/static_py_object.h @@ -5,16 +5,6 @@ namespace pyunrealsdk { -// By default, pybind tries to compile with visibility hidden -// If we have default visibility in a type holding pybind objects as members, this may cause a -// warning, since our type has greater visibility than it's members -// This macro sets the right visibility -#if defined(__MINGW32__) -#define PY_OBJECT_VISIBILITY __attribute__((visibility("hidden"))) -#else -#define PY_OBJECT_VISIBILITY -#endif - /* A pybind object wrapper which can safely be stored statically. diff --git a/src/pyunrealsdk/unreal_bindings/bindings.h b/src/pyunrealsdk/unreal_bindings/bindings.h index 3e10b4c..ee61d6a 100644 --- a/src/pyunrealsdk/unreal_bindings/bindings.h +++ b/src/pyunrealsdk/unreal_bindings/bindings.h @@ -23,7 +23,32 @@ void register_module(py::module_& mod); * @tparam Options Extra options for the class definition. */ template -using PyUEClass = py::class_>; +class PY_OBJECT_VISIBILITY PyUEClass + : public py::class_> { + public: + using py::class_>::class_; + + /** + * @brief Helper type to define one of our C++ "properties" as a python property. + * @note These must be first calls, as the standard def functions return a base py::class_, + * rendering this function inaccessible. + * + * @tparam C The type of this class, should be picked up automatically. + * @tparam D The type of the property, should be picked up automatically. + * @tparam Extra Any extra template args to forward. + * @param name The name of the property. + * @param getter The getter function. Needs to be specialized, should be non-cost. + * @param extra Any extra args to forward. + * @return A reference to the same class object. + */ + template + PyUEClass& def_member_prop(const char* name, D& (*getter)(C&), const Extra&... extra) { + this->def_property( + name, [getter](C& self) { return getter(self); }, + [getter](C& self, D&& value) { getter(self) = std::move(value); }, extra...); + return *this; + } +}; } // namespace pyunrealsdk::unreal diff --git a/src/pyunrealsdk/unreal_bindings/bound_function.cpp b/src/pyunrealsdk/unreal_bindings/bound_function.cpp index 2cd1c17..e80c39d 100644 --- a/src/pyunrealsdk/unreal_bindings/bound_function.cpp +++ b/src/pyunrealsdk/unreal_bindings/bound_function.cpp @@ -2,7 +2,6 @@ #include "pyunrealsdk/unreal_bindings/bound_function.h" #include "pyunrealsdk/hooks.h" #include "pyunrealsdk/unreal_bindings/property_access.h" -#include "unrealsdk/format.h" #include "unrealsdk/hook_manager.h" #include "unrealsdk/unreal/classes/ufunction.h" #include "unrealsdk/unreal/classes/uobject.h" @@ -64,15 +63,15 @@ void fill_py_params(impl::PyCallInfo& info, const py::args& args, const py::kwar std::vector missing_required_args{}; for (auto prop : info.params.type->properties()) { - if ((prop->PropertyFlags & UProperty::PROP_FLAG_PARAM) == 0) { + if ((prop->PropertyFlags() & UProperty::PROP_FLAG_PARAM) == 0) { continue; } - if ((prop->PropertyFlags & UProperty::PROP_FLAG_RETURN) != 0 + if ((prop->PropertyFlags() & UProperty::PROP_FLAG_RETURN) != 0 && info.return_param == nullptr) { info.return_param = prop; continue; } - if ((prop->PropertyFlags & UProperty::PROP_FLAG_OUT) != 0) { + if ((prop->PropertyFlags() & UProperty::PROP_FLAG_OUT) != 0) { info.out_params.push_back(prop); } @@ -81,45 +80,44 @@ void fill_py_params(impl::PyCallInfo& info, const py::args& args, const py::kwar py_setattr_direct(prop, reinterpret_cast(info.params.base.get()), args[arg_idx++]); - if (kwargs.contains(prop->Name)) { - throw py::type_error( - unrealsdk::fmt::format("{}() got multiple values for argument '{}'", - info.params.type->Name, prop->Name)); + if (kwargs.contains(prop->Name())) { + throw py::type_error(std::format("{}() got multiple values for argument '{}'", + info.params.type->Name(), prop->Name())); } continue; } // If we're on to just kwargs - if (kwargs.contains(prop->Name)) { + if (kwargs.contains(prop->Name())) { // Extract the value with pop, so we can check that kwargs are empty at the // end py_setattr_direct(prop, reinterpret_cast(info.params.base.get()), - kwargs.attr("pop")(prop->Name)); + kwargs.attr("pop")(prop->Name())); continue; } // NOLINTNEXTLINE(misc-const-correctness) bool optional = false; #ifdef UE3 - optional = (prop->PropertyFlags & UProperty::PROP_FLAG_OPTIONAL) != 0; + optional = (prop->PropertyFlags() & UProperty::PROP_FLAG_OPTIONAL) != 0; #endif // If not given, and not optional, record for error later if (!optional) { - missing_required_args.push_back(prop->Name); + missing_required_args.push_back(prop->Name()); } } if (!missing_required_args.empty()) { - throw_missing_required_args(info.params.type->Name, missing_required_args); + throw_missing_required_args(info.params.type->Name(), missing_required_args); } if (!kwargs.empty()) { // Copying python, we only need to warn about one extra kwarg std::string bad_kwarg = py::str(kwargs.begin()->first); - throw py::type_error(unrealsdk::fmt::format("{}() got an unexpected keyword argument '{}'", - info.params.type->Name, bad_kwarg)); + throw py::type_error(std::format("{}() got an unexpected keyword argument '{}'", + info.params.type->Name(), bad_kwarg)); } } @@ -131,9 +129,8 @@ PyCallInfo::PyCallInfo(const UFunction* func, const py::args& args, const py::kw // Start by initializing a null struct, to avoid allocations : params(func, nullptr) { if (func->NumParams() < args.size()) { - throw py::type_error( - unrealsdk::fmt::format("{}() takes {} positional args, but {} were given", func->Name, - func->NumParams(), args.size())); + throw py::type_error(std::format("{}() takes {} positional args, but {} were given", + func->Name(), func->NumParams(), args.size())); } // If we're given exactly one arg, and it's a wrapped struct of our function type, take it as @@ -145,12 +142,12 @@ PyCallInfo::PyCallInfo(const UFunction* func, const py::args& args, const py::kw // Manually gather the return value and out params for (auto prop : func->properties()) { - if ((prop->PropertyFlags & UProperty::PROP_FLAG_RETURN) != 0 + if ((prop->PropertyFlags() & UProperty::PROP_FLAG_RETURN) != 0 && return_param == nullptr) { this->return_param = prop; continue; } - if ((prop->PropertyFlags & UProperty::PROP_FLAG_OUT) != 0) { + if ((prop->PropertyFlags() & UProperty::PROP_FLAG_OUT) != 0) { this->out_params.push_back(prop); } } @@ -200,9 +197,8 @@ void register_bound_function(py::module_& mod) { .def( "__repr__", [](BoundFunction& self) { - return unrealsdk::fmt::format( - "", self.func->Name, - unrealsdk::utils::narrow(self.object->get_path_name())); + return std::format("", self.func->Name(), + unrealsdk::utils::narrow(self.object->get_path_name())); }, "Gets a string representation of this function and the object it's bound to.\n" "\n" diff --git a/src/pyunrealsdk/unreal_bindings/property_access.cpp b/src/pyunrealsdk/unreal_bindings/property_access.cpp index d224a85..fed5e69 100644 --- a/src/pyunrealsdk/unreal_bindings/property_access.cpp +++ b/src/pyunrealsdk/unreal_bindings/property_access.cpp @@ -38,7 +38,7 @@ UField* py_find_field(const FName& name, const UStruct* type) { return type->find(name); } catch (const std::invalid_argument&) { throw py::attribute_error( - unrealsdk::fmt::format("'{}' object has no attribute '{}'", type->Name, name)); + std::format("'{}' object has no attribute '{}'", type->Name(), name)); } } @@ -68,7 +68,7 @@ std::vector py_dir(const py::object& self, const UStruct* type) { // Append our fields auto fields = type->fields(); std::ranges::transform(fields, std::back_inserter(names), - [](auto obj) { return obj->Name; }); + [](auto obj) { return obj->Name(); }); } return names; @@ -80,17 +80,17 @@ py::object py_getattr(UField* field, UObject* func_obj) { if (field->is_instance(find_class())) { auto prop = reinterpret_cast(field); - if (prop->ArrayDim < 1) { - throw py::attribute_error(unrealsdk::fmt::format("attribute '{}' has size of {}", - prop->Name, prop->ArrayDim)); + if (prop->ArrayDim() < 1) { + throw py::attribute_error( + std::format("attribute '{}' has size of {}", prop->Name(), prop->ArrayDim())); } // If we have a static array, return it as a tuple. // Store in a list for now so we can still append. - py::list ret{prop->ArrayDim}; + py::list ret{prop->ArrayDim()}; cast(prop, [base_addr, &ret, &parent](const T* prop) { - for (size_t i = 0; i < (size_t)prop->ArrayDim; i++) { + for (size_t i = 0; i < (size_t)prop->ArrayDim(); i++) { auto val = get_property(prop, i, base_addr, parent); // Multiple property types expose a get enum method @@ -111,7 +111,7 @@ py::object py_getattr(UField* field, ret[i] = std::move(val); } }); - if (prop->ArrayDim == 1) { + if (prop->ArrayDim() == 1) { return ret[0]; } return py::tuple(ret); @@ -119,7 +119,7 @@ py::object py_getattr(UField* field, if (field->is_instance(find_class())) { if (func_obj == nullptr) { throw py::attribute_error( - unrealsdk::fmt::format("cannot bind function '{}' with null object", field->Name)); + std::format("cannot bind function '{}' with null object", field->Name())); } return py::cast( @@ -131,15 +131,15 @@ py::object py_getattr(UField* field, } if (field->is_instance(find_class())) { - return py::cast((std::string) reinterpret_cast(field)->Value); + return py::cast((std::string) reinterpret_cast(field)->Value()); } if (field->is_instance(find_class())) { return enum_as_py_enum(reinterpret_cast(field)); } - throw py::attribute_error(unrealsdk::fmt::format("attribute '{}' has unknown type '{}'", - field->Name, field->Class->Name)); + throw py::attribute_error( + std::format("attribute '{}' has unknown type '{}'", field->Name(), field->Class()->Name())); } // The templated lambda and all the if constexprs make everything have a really high penalty @@ -147,25 +147,25 @@ py::object py_getattr(UField* field, // NOLINTNEXTLINE(readability-function-cognitive-complexity) void py_setattr_direct(UField* field, uintptr_t base_addr, const py::object& value) { if (!field->is_instance(find_class())) { - throw py::attribute_error(unrealsdk::fmt::format( - "attribute '{}' is not a property, and thus cannot be set", field->Name)); + throw py::attribute_error( + std::format("attribute '{}' is not a property, and thus cannot be set", field->Name())); } auto prop = reinterpret_cast(field); py::sequence value_seq; - if (prop->ArrayDim > 1) { + if (prop->ArrayDim() > 1) { if (!py::isinstance(value)) { std::string value_type_name = py::str(py::type::of(value).attr("__name__")); - throw py::type_error(unrealsdk::fmt::format( + throw py::type_error(std::format( "attribute value has unexpected type '{}', expected a sequence", value_type_name)); } value_seq = value; - if (value_seq.size() > static_cast(prop->ArrayDim)) { - throw py::type_error(unrealsdk::fmt::format( - "attribute value is too long, {} supports a maximum of {} values", prop->Name, - prop->ArrayDim)); + if (value_seq.size() > static_cast(prop->ArrayDim())) { + throw py::type_error( + std::format("attribute value is too long, {} supports a maximum of {} values", + prop->Name(), prop->ArrayDim())); } } else { value_seq = py::make_tuple(value); @@ -175,7 +175,7 @@ void py_setattr_direct(UField* field, uintptr_t base_addr, const py::object& val using value_type = typename PropTraits::Value; const size_t seq_size = value_seq.size(); - const size_t prop_size = prop->ArrayDim; + const size_t prop_size = prop->ArrayDim(); // As a special case, if we have an array property, allow assigning non-wrapped array // sequences @@ -201,10 +201,10 @@ void py_setattr_direct(UField* field, uintptr_t base_addr, const py::object& val } } else { if (seq_size != prop_size) { - throw py::type_error(unrealsdk::fmt::format( + throw py::type_error(std::format( "attribute value is too short, {} must be given as exactly {} values (no known " "default to use when less are given)", - prop->Name, prop->ArrayDim)); + prop->Name(), prop->ArrayDim())); } } diff --git a/src/pyunrealsdk/unreal_bindings/uenum.cpp b/src/pyunrealsdk/unreal_bindings/uenum.cpp index ea0569b..8057b0a 100644 --- a/src/pyunrealsdk/unreal_bindings/uenum.cpp +++ b/src/pyunrealsdk/unreal_bindings/uenum.cpp @@ -52,7 +52,7 @@ py::object enum_as_py_enum(const UEnum* enum_obj) { enum_names = enum_obj->get_names(); #endif - auto py_enum = intflag(enum_obj->Name, enum_names); + auto py_enum = intflag(enum_obj->Name(), enum_names); py_enum.attr("_unreal") = enum_obj; enum_cache.emplace(enum_obj, py_enum); diff --git a/src/pyunrealsdk/unreal_bindings/uobject.cpp b/src/pyunrealsdk/unreal_bindings/uobject.cpp index f9ad258..ddc75bb 100644 --- a/src/pyunrealsdk/unreal_bindings/uobject.cpp +++ b/src/pyunrealsdk/unreal_bindings/uobject.cpp @@ -3,7 +3,6 @@ #include "pyunrealsdk/static_py_object.h" #include "pyunrealsdk/unreal_bindings/bindings.h" #include "pyunrealsdk/unreal_bindings/property_access.h" -#include "unrealsdk/format.h" #include "unrealsdk/unreal/classes/uclass.h" #include "unrealsdk/unreal/classes/uobject.h" #include "unrealsdk/unreal/classes/uproperty.h" @@ -45,6 +44,11 @@ void register_uobject(py::module_& mod) { "\n" "Most objects you interact with will be this type in python, even if their unreal\n" "class is something different.") + .def_member_prop("ObjectFlags", &UObject::ObjectFlags) + .def_member_prop("InternalIndex", &UObject::InternalIndex) + .def_member_prop("Class", &UObject::Class) + .def_member_prop("Name", &UObject::Name) + .def_member_prop("Outer", &UObject::Outer) .def("__new__", [](const py::args&, const py::kwargs&) { throw py::type_error("Cannot create new instances of unreal objects."); @@ -53,8 +57,8 @@ void register_uobject(py::module_& mod) { .def( "__repr__", [](UObject* self) { - return unrealsdk::fmt::format("{}'{}'", self->Class->Name, - unrealsdk::utils::narrow(self->get_path_name())); + return std::format("{}'{}'", self->Class()->Name(), + unrealsdk::utils::narrow(self->get_path_name())); }, "Gets this object's full name.\n" "\n" @@ -67,7 +71,7 @@ void register_uobject(py::module_& mod) { " This object's name.") .def( "__dir__", - [](const py::object& self) { return py_dir(self, py::cast(self)->Class); }, + [](const py::object& self) { return py_dir(self, py::cast(self)->Class()); }, "Gets the attributes which exist on this object.\n" "\n" "Includes both python attributes and unreal fields. This can be changed to only\n" @@ -78,7 +82,7 @@ void register_uobject(py::module_& mod) { .def( "__getattr__", [](UObject* self, const FName& name) { - return py_getattr(py_find_field(name, self->Class), + return py_getattr(py_find_field(name, self->Class()), reinterpret_cast(self), nullptr, self); }, "Reads an unreal field off of the object.\n" @@ -133,7 +137,7 @@ void register_uobject(py::module_& mod) { } } - auto field = py_find_field(py::cast(name), self->Class); + auto field = py_find_field(py::cast(name), self->Class()); py_setattr_direct(field, reinterpret_cast(self), value); if (should_notify_counter > 0 && field->is_instance(find_class())) { @@ -209,12 +213,7 @@ void register_uobject(py::module_& mod) { "Args:\n" " prop: The property which was changed.\n" " *chain: The chain of properties to follow.", - "prop"_a) - .def_readwrite("ObjectFlags", &UObject::ObjectFlags) - .def_readwrite("InternalIndex", &UObject::InternalIndex) - .def_readwrite("Class", &UObject::Class) - .def_readwrite("Name", &UObject::Name) - .def_readwrite("Outer", &UObject::Outer); + "prop"_a); // Create under an empty handle to prevent this type being normally accessible py::class_(py::handle(), "context_manager", pybind11::module_local()) diff --git a/src/pyunrealsdk/unreal_bindings/uobject_children.cpp b/src/pyunrealsdk/unreal_bindings/uobject_children.cpp index 29dcbef..39adffc 100644 --- a/src/pyunrealsdk/unreal_bindings/uobject_children.cpp +++ b/src/pyunrealsdk/unreal_bindings/uobject_children.cpp @@ -37,23 +37,27 @@ namespace pyunrealsdk::unreal { void register_uobject_children(py::module_& mod) { // ======== First Layer Subclasses ======== - PyUEClass(mod, "UField").def_readwrite("Next", &UField::Next); + PyUEClass(mod, "UField").def_member_prop("Next", &UField::Next); // ======== Second Layer Subclasses ======== PyUEClass(mod, "UConst") + // Deliberately not using def_member_prop, since we need to do extra string conversions .def_property( - "Value", [](const UConst* self) { return (std::string)self->Value; }, - [](UConst* self, const std::string& new_value) { self->Value = new_value; }); + "Value", [](const UConst* self) { return (std::string)self->Value(); }, + [](UConst* self, const std::string& new_value) { self->Value() = new_value; }); PyUEClass(mod, "UProperty") - .def_readwrite("ArrayDim", &UProperty::ArrayDim) - .def_readwrite("ElementSize", &UProperty::ElementSize) - .def_readwrite("PropertyFlags", &UProperty::PropertyFlags) - .def_readwrite("Offset_Internal", &UProperty::Offset_Internal) - .def_readwrite("PropertyLinkNext", &UProperty::PropertyLinkNext); + .def_member_prop("ArrayDim", &UProperty::ArrayDim) + .def_member_prop("ElementSize", &UProperty::ElementSize) + .def_member_prop("PropertyFlags", &UProperty::PropertyFlags) + .def_member_prop("Offset_Internal", &UProperty::Offset_Internal) + .def_member_prop("PropertyLinkNext", &UProperty::PropertyLinkNext); PyUEClass(mod, "UStruct") + .def_member_prop("SuperField", &UStruct::SuperField) + .def_member_prop("Children", &UStruct::Children) + .def_member_prop("PropertyLink", &UStruct::PropertyLink) .def( "_fields", [](UStruct* self) { @@ -126,10 +130,7 @@ void register_uobject_children(py::module_& mod) { " name: The name of the child property.\n" "Returns:\n" " The found child property.", - "name"_a) - .def_readwrite("SuperField", &UStruct::SuperField) - .def_readwrite("Children", &UStruct::Children) - .def_readwrite("PropertyLink", &UStruct::PropertyLink); + "name"_a); // ======== Third Layer Subclasses ======== @@ -143,6 +144,7 @@ void register_uobject_children(py::module_& mod) { .def_property_readonly("Enum", &UByteProperty::get_enum); PyUEClass(mod, "UClass") + .def_member_prop("ClassDefaultObject", &UClass::ClassDefaultObject) .def( "_implements", [](UClass* self, UClass* interface) { return self->implements(interface, nullptr); }, @@ -153,9 +155,6 @@ void register_uobject_children(py::module_& mod) { "Returns:\n" " True if this class implements the interface, false otherwise.", "interface"_a) - .def_property( - "ClassDefaultObject", [](const UClass* self) { return self->ClassDefaultObject(); }, - [](UClass* self, UObject* value) { self->ClassDefaultObject() = value; }) .def_property_readonly("Interfaces", [](const UClass* self) { std::vector interfaces{}; for (const auto& iface : self->Interfaces()) { @@ -176,23 +175,15 @@ void register_uobject_children(py::module_& mod) { PyUEClass(mod, "UFloatProperty"); PyUEClass(mod, "UFunction") + .def_member_prop("FunctionFlags", &UFunction::FunctionFlags) + .def_member_prop("NumParams", &UFunction::NumParams) + .def_member_prop("ParamsSize", &UFunction::ParamsSize) + .def_member_prop("ReturnValueOffset", &UFunction::ReturnValueOffset) .def("_find_return_param", &UFunction::find_return_param, "Finds the return param for this function (if it exists).\n" "\n" "Returns:\n" - " The return param, or None if it doesn't exist.") - .def_property( - "FunctionFlags", [](const UFunction* self) { return self->FunctionFlags(); }, - [](UFunction* self, uint32_t value) { self->FunctionFlags() = value; }) - .def_property( - "NumParams", [](const UFunction* self) { return self->NumParams(); }, - [](UFunction* self, uint8_t value) { self->NumParams() = value; }) - .def_property( - "ParamsSize", [](const UFunction* self) { return self->ParamsSize(); }, - [](UFunction* self, uint16_t value) { self->ParamsSize() = value; }) - .def_property( - "ReturnValueOffset", [](const UFunction* self) { return self->ReturnValueOffset(); }, - [](UFunction* self, uint16_t value) { self->ReturnValueOffset() = value; }); + " The return param, or None if it doesn't exist."); PyUEClass(mod, "UInt8Property"); @@ -214,9 +205,7 @@ void register_uobject_children(py::module_& mod) { .def_property_readonly("PropertyClass", &UObjectProperty::get_property_class); PyUEClass(mod, "UScriptStruct") - .def_property( - "StructFlags", [](const UScriptStruct* self) { return self->StructFlags(); }, - [](UScriptStruct* self, uint32_t value) { self->StructFlags() = value; }); + .def_member_prop("StructFlags", &UScriptStruct::StructFlags); PyUEClass(mod, "UStrProperty"); diff --git a/src/pyunrealsdk/unreal_bindings/wrapped_array_helpers.cpp b/src/pyunrealsdk/unreal_bindings/wrapped_array_helpers.cpp index 8b6b8e7..5bfc6f6 100644 --- a/src/pyunrealsdk/unreal_bindings/wrapped_array_helpers.cpp +++ b/src/pyunrealsdk/unreal_bindings/wrapped_array_helpers.cpp @@ -23,11 +23,11 @@ size_t convert_py_idx(const WrappedArray& arr, py::ssize_t idx) { } py::object array_get(const WrappedArray& arr, size_t idx) { - if (arr.type->Offset_Internal != 0) { + if (arr.type->Offset_Internal() != 0) { throw std::runtime_error( "array inner property has non-zero offset, unsure how to handle, aborting!"); } - if (arr.type->ArrayDim != 1) { + if (arr.type->ArrayDim() != 1) { throw std::runtime_error( "array inner property is fixed array, unsure how to handle, aborting!"); } @@ -35,23 +35,23 @@ py::object array_get(const WrappedArray& arr, size_t idx) { // Const cast is slightly naughty, but we know the internals aren't going to modify properties return py_getattr( const_cast(arr.type), // NOLINT(cppcoreguidelines-pro-type-const-cast) - reinterpret_cast(arr.base.get()->data) + (arr.type->ElementSize * idx), + reinterpret_cast(arr.base.get()->data) + (arr.type->ElementSize() * idx), arr.base); } void array_set(WrappedArray& arr, size_t idx, const py::object& value) { - if (arr.type->Offset_Internal != 0) { + if (arr.type->Offset_Internal() != 0) { throw std::runtime_error( "array inner property has non-zero offset, unsure how to handle, aborting!"); } - if (arr.type->ArrayDim != 1) { + if (arr.type->ArrayDim() != 1) { throw std::runtime_error( "array inner property is fixed array, unsure how to handle, aborting!"); } py_setattr_direct( const_cast(arr.type), // NOLINT(cppcoreguidelines-pro-type-const-cast) - reinterpret_cast(arr.base.get()->data) + (arr.type->ElementSize * idx), value); + reinterpret_cast(arr.base.get()->data) + (arr.type->ElementSize() * idx), value); } void array_delete_range(WrappedArray& arr, size_t start, size_t stop) { @@ -67,7 +67,7 @@ void array_delete_range(WrappedArray& arr, size_t start, size_t stop) { if (stop != size) { auto data = reinterpret_cast(arr.base->data); - auto element_size = arr.type->ElementSize; + auto element_size = arr.type->ElementSize(); auto dest = data + (start * element_size); auto src = dest + (num_deleted * element_size); diff --git a/src/pyunrealsdk/unreal_bindings/wrapped_array_magic_methods.cpp b/src/pyunrealsdk/unreal_bindings/wrapped_array_magic_methods.cpp index 56eee4a..37468d0 100644 --- a/src/pyunrealsdk/unreal_bindings/wrapped_array_magic_methods.cpp +++ b/src/pyunrealsdk/unreal_bindings/wrapped_array_magic_methods.cpp @@ -84,9 +84,9 @@ void array_py_setitem_slice(WrappedArray& self, const py::slice& slice, const py // This logic sounds backwards, but it lets our simpler code early exit, and it works the same // way as list if (step != 1 && step != -1) { - throw py::value_error(unrealsdk::fmt::format( - "attempt to assign sequence of size {} to extended slice of size {}", value.size(), - slicelength)); + throw py::value_error( + std::format("attempt to assign sequence of size {} to extended slice of size {}", + value.size(), slicelength)); } if (step < 0) { diff --git a/src/pyunrealsdk/unreal_bindings/wrapped_array_methods.cpp b/src/pyunrealsdk/unreal_bindings/wrapped_array_methods.cpp index 8c4eae5..a7a121e 100644 --- a/src/pyunrealsdk/unreal_bindings/wrapped_array_methods.cpp +++ b/src/pyunrealsdk/unreal_bindings/wrapped_array_methods.cpp @@ -98,8 +98,7 @@ size_t array_py_index(const WrappedArray& self, } } - throw py::value_error( - unrealsdk::fmt::format("{} is not in array", std::string(py::repr(value)))); + throw py::value_error(std::format("{} is not in array", std::string(py::repr(value)))); } void array_py_insert(WrappedArray& self, py::ssize_t py_idx, const py::object& value) { @@ -116,7 +115,7 @@ void array_py_insert(WrappedArray& self, py::ssize_t py_idx, const py::object& v self.resize(size + 1); auto data = reinterpret_cast(self.base->data); - auto element_size = self.type->ElementSize; + auto element_size = self.type->ElementSize(); auto src = data + (idx * element_size); auto remaining_size = (size - idx) * element_size; @@ -214,7 +213,7 @@ void array_py_emplace_struct(WrappedArray& self, self.resize(size + 1); auto data = reinterpret_cast(self.base->data); - auto element_size = self.type->ElementSize; + auto element_size = self.type->ElementSize(); auto src = data + (idx * element_size); auto remaining_size = (size - idx) * element_size; diff --git a/src/pyunrealsdk/unreal_bindings/wrapped_multicast_delegate.cpp b/src/pyunrealsdk/unreal_bindings/wrapped_multicast_delegate.cpp index e608afa..ec94bcc 100644 --- a/src/pyunrealsdk/unreal_bindings/wrapped_multicast_delegate.cpp +++ b/src/pyunrealsdk/unreal_bindings/wrapped_multicast_delegate.cpp @@ -76,15 +76,15 @@ FScriptDelegate* find_matching_delegate(WrappedMulticastDelegate& self, auto ptr = std::find_if(begin, end, [&value](FScriptDelegate other) { // Do the cheap checks first - if (value.object != other.get_object() || value.func->Name != other.func_name) { + if (value.object != other.get_object() || value.func->Name() != other.func_name) { return false; } // Now check it resolves to the same function, which is a more expensive check // Manually iterate through fields to avoid the exceptions if we can't find what we're // looking for - for (auto field : value.object->Class->fields()) { - if (field->Name == other.func_name) { + for (auto field : value.object->Class()->fields()) { + if (field->Name() == other.func_name) { // Return immediately on the first name match return field == value.func; } diff --git a/src/pyunrealsdk/unreal_bindings/wrapped_struct.cpp b/src/pyunrealsdk/unreal_bindings/wrapped_struct.cpp index 8e6b196..c937534 100644 --- a/src/pyunrealsdk/unreal_bindings/wrapped_struct.cpp +++ b/src/pyunrealsdk/unreal_bindings/wrapped_struct.cpp @@ -67,17 +67,17 @@ void make_struct(unrealsdk::unreal::WrappedStruct& out_struct, py_setattr_direct(prop, reinterpret_cast(out_struct.base.get()), args[arg_idx++]); - if (converted_kwargs.contains(prop->Name)) { + if (converted_kwargs.contains(prop->Name())) { throw py::type_error( - unrealsdk::fmt::format("{}.__init__() got multiple values for argument '{}'", - out_struct.type->Name, prop->Name)); + std::format("{}.__init__() got multiple values for argument '{}'", + out_struct.type->Name(), prop->Name())); } continue; } // If we're on to just kwargs - auto iter = converted_kwargs.find(prop->Name); + auto iter = converted_kwargs.find(prop->Name()); if (iter != converted_kwargs.end()) { // Use extract to also remove the value from the map, so we can ensure it's empty later py_setattr_direct(prop, reinterpret_cast(out_struct.base.get()), @@ -88,9 +88,8 @@ void make_struct(unrealsdk::unreal::WrappedStruct& out_struct, if (!converted_kwargs.empty()) { // Copying python, we only need to warn about one extra kwarg - throw py::type_error( - unrealsdk::fmt::format("{}.__init__() got an unexpected keyword argument '{}'", - out_struct.type->Name, converted_kwargs.begin()->first)); + throw py::type_error(std::format("{}.__init__() got an unexpected keyword argument '{}'", + out_struct.type->Name(), converted_kwargs.begin()->first)); } } @@ -119,14 +118,14 @@ void register_wrapped_struct(py::module_& mod) { output << ", "; } first = false; - output << prop->Name << ": "; + output << prop->Name() << ": "; try { auto value = py_getattr(prop, reinterpret_cast(self.base.get()), self.base); output << py::repr(value); } catch (...) { - output << "Class->Name << ">"; + output << "Class()->Name() << ">"; } } diff --git a/src/pyunrealsdk/version.cpp b/src/pyunrealsdk/version.cpp index 7f21975..4a49400 100644 --- a/src/pyunrealsdk/version.cpp +++ b/src/pyunrealsdk/version.cpp @@ -20,13 +20,12 @@ namespace { #include "pyunrealsdk/git.inl" const constexpr auto GIT_HASH_CHARS = 8; -const std::string VERSION_STR = - unrealsdk::fmt::format("pyunrealsdk v{}.{}.{} ({}{})", - VERSION_MAJOR, - VERSION_MINOR, - VERSION_PATCH, - std::string(GIT_HEAD_SHA1).substr(0, GIT_HASH_CHARS), - GIT_IS_DIRTY ? ", dirty" : ""); +const std::string VERSION_STR = std::format("pyunrealsdk v{}.{}.{} ({}{})", + VERSION_MAJOR, + VERSION_MINOR, + VERSION_PATCH, + std::string(GIT_HEAD_SHA1).substr(0, GIT_HASH_CHARS), + GIT_IS_DIRTY ? ", dirty" : ""); #endif } // namespace From 133834b0c6affc9fad57506166a485aaa7083cbe Mon Sep 17 00:00:00 2001 From: apple1417 Date: Wed, 14 May 2025 20:59:00 +1200 Subject: [PATCH 02/11] upgrade to include latest v2 tweaks --- CMakePresets.json | 160 +++++++----------- libs/unrealsdk | 2 +- .../unreal_bindings/bound_function.cpp | 4 +- .../persistent_object_ptr_property.cpp | 2 +- src/pyunrealsdk/unreal_bindings/uenum.cpp | 2 +- .../unreal_bindings/uobject_children.cpp | 47 ++--- stubs/unrealsdk/unreal/_uobject_children.pyi | 48 ++---- 7 files changed, 109 insertions(+), 156 deletions(-) diff --git a/CMakePresets.json b/CMakePresets.json index efe923b..3aa18ea 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -110,7 +110,7 @@ "toolchainFile": "common_cmake/msvc.cmake" }, { - "name": "_x86", + "name": "_willow", "hidden": true, "architecture": { "value": "Win32", @@ -118,11 +118,12 @@ }, "cacheVariables": { "EXPLICIT_PYTHON_ARCH": "win32", + "UNREALSDK_FLAVOUR": "WILLOW", "UNREALSDK_ARCH": "x86" } }, { - "name": "_x64", + "name": "_oak", "hidden": true, "architecture": { "value": "x64", @@ -130,23 +131,10 @@ }, "cacheVariables": { "EXPLICIT_PYTHON_ARCH": "amd64", + "UNREALSDK_FLAVOUR": "OAK", "UNREALSDK_ARCH": "x64" } }, - { - "name": "_ue3", - "hidden": true, - "cacheVariables": { - "UNREALSDK_UE_VERSION": "UE3" - } - }, - { - "name": "_ue4", - "hidden": true, - "cacheVariables": { - "UNREALSDK_UE_VERSION": "UE4" - } - }, { "name": "_debug", "hidden": true, @@ -162,222 +150,202 @@ } }, { - "name": "clang-ue3-x86-debug", - "displayName": "UE3 x86 Debug (Clang)", + "name": "clang-willow-debug", + "displayName": "Willow Debug (Clang)", "inherits": [ "_base", "_clang_x86", - "_ue3", - "_x86", + "_willow", "_debug" ] }, { - "name": "clang-ue3-x86-release", - "displayName": "UE3 x86 Release (Clang)", + "name": "clang-willow-release", + "displayName": "Willow Release (Clang)", "inherits": [ "_base", "_clang_x86", - "_ue3", - "_x86", + "_willow", "_release" ] }, { - "name": "clang-ue4-x64-debug", - "displayName": "UE4 x64 Debug (Clang)", + "name": "clang-oak-debug", + "displayName": "Oak Debug (Clang)", "inherits": [ "_base", "_clang_x64", - "_ue4", - "_x64", + "_oak", "_debug" ] }, { - "name": "clang-ue4-x64-release", - "displayName": "UE4 x64 Release (Clang)", + "name": "clang-oak-release", + "displayName": "Oak Release (Clang)", "inherits": [ "_base", "_clang_x64", - "_ue4", - "_x64", + "_oak", "_release" ] }, { - "name": "clang-cross-ue3-x86-debug", - "displayName": "UE3 x86 Debug (Clang Cross Compiler)", + "name": "clang-cross-willow-debug", + "displayName": "Willow Debug (Clang Cross Compiler)", "inherits": [ "_base", "_clang_cross_x86", - "_ue3", - "_x86", + "_willow", "_debug" ] }, { - "name": "clang-cross-ue3-x86-release", - "displayName": "UE3 x86 Release (Clang Cross Compiler)", + "name": "clang-cross-willow-release", + "displayName": "Willow Release (Clang Cross Compiler)", "inherits": [ "_base", "_clang_cross_x86", - "_ue3", - "_x86", + "_willow", "_release" ] }, { - "name": "clang-cross-ue4-x64-debug", - "displayName": "UE4 x64 Debug (Clang Cross Compiler)", + "name": "clang-cross-oak-debug", + "displayName": "Oak Debug (Clang Cross Compiler)", "inherits": [ "_base", "_clang_cross_x64", - "_ue4", - "_x64", + "_oak", "_debug" ] }, { - "name": "clang-cross-ue4-x64-release", - "displayName": "UE4 x64 Release (Clang Cross Compiler)", + "name": "clang-cross-oak-release", + "displayName": "Oak Release (Clang Cross Compiler)", "inherits": [ "_base", "_clang_cross_x64", - "_ue4", - "_x64", + "_oak", "_release" ] }, { - "name": "llvm-mingw-ue3-x86-debug", - "displayName": "UE3 x86 Debug (LLVM MinGW)", + "name": "llvm-mingw-willow-debug", + "displayName": "Willow Debug (LLVM MinGW)", "inherits": [ "_base", "_llvm_mingw_x86", - "_ue3", - "_x86", + "_willow", "_debug" ] }, { - "name": "llvm-mingw-ue3-x86-release", - "displayName": "UE3 x86 Release (LLVM MinGW)", + "name": "llvm-mingw-willow-release", + "displayName": "Willow Release (LLVM MinGW)", "inherits": [ "_base", "_llvm_mingw_x86", - "_ue3", - "_x86", + "_willow", "_release" ] }, { - "name": "llvm-mingw-ue4-x64-debug", - "displayName": "UE4 x64 Debug (LLVM MinGW)", + "name": "llvm-mingw-oak-debug", + "displayName": "Oak Debug (LLVM MinGW)", "inherits": [ "_base", "_llvm_mingw_x64", - "_ue4", - "_x64", + "_oak", "_debug" ] }, { - "name": "llvm-mingw-ue4-x64-release", - "displayName": "UE4 x64 Release (LLVM MinGW)", + "name": "llvm-mingw-oak-release", + "displayName": "Oak Release (LLVM MinGW)", "inherits": [ "_base", "_llvm_mingw_x64", - "_ue4", - "_x64", + "_oak", "_release" ] }, { - "name": "mingw-ue3-x86-debug", - "displayName": "UE3 x86 Debug (MinGW)", + "name": "mingw-willow-debug", + "displayName": "Willow Debug (MinGW)", "inherits": [ "_base", "_mingw_x86", - "_ue3", - "_x86", + "_willow", "_debug" ] }, { - "name": "mingw-ue3-x86-release", - "displayName": "UE3 x86 Release (MinGW)", + "name": "mingw-willow-release", + "displayName": "Willow Release (MinGW)", "inherits": [ "_base", "_mingw_x86", - "_ue3", - "_x86", + "_willow", "_release" ] }, { - "name": "mingw-ue4-x64-debug", - "displayName": "UE4 x64 Debug (MinGW)", + "name": "mingw-oak-debug", + "displayName": "Oak Debug (MinGW)", "inherits": [ "_base", "_mingw_x64", - "_ue4", - "_x64", + "_oak", "_debug" ] }, { - "name": "mingw-ue4-x64-release", - "displayName": "UE4 x64 Release (MinGW)", + "name": "mingw-oak-release", + "displayName": "Oak Release (MinGW)", "inherits": [ "_base", "_mingw_x64", - "_ue4", - "_x64", + "_oak", "_release" ] }, { - "name": "msvc-ue3-x86-debug", - "displayName": "UE3 x86 Debug (MSVC)", + "name": "msvc-willow-debug", + "displayName": "Willow Debug (MSVC)", "inherits": [ "_base", "_msvc", - "_ue3", - "_x86", + "_willow", "_debug" ] }, { - "name": "msvc-ue3-x86-release", - "displayName": "UE3 x86 Release (MSVC)", + "name": "msvc-willow-release", + "displayName": "Willow Release (MSVC)", "inherits": [ "_base", "_msvc", - "_ue3", - "_x86", + "_willow", "_release" ] }, { - "name": "msvc-ue4-x64-debug", - "displayName": "UE4 x64 Debug (MSVC)", + "name": "msvc-oak-debug", + "displayName": "Oak Debug (MSVC)", "inherits": [ "_base", "_msvc", - "_ue4", - "_x64", + "_oak", "_debug" ] }, { - "name": "msvc-ue4-x64-release", - "displayName": "UE4 x64 Release (MSVC)", + "name": "msvc-oak-release", + "displayName": "Oak Release (MSVC)", "inherits": [ "_base", "_msvc", - "_ue4", - "_x64", + "_oak", "_release" ] } diff --git a/libs/unrealsdk b/libs/unrealsdk index c2a918d..38a5191 160000 --- a/libs/unrealsdk +++ b/libs/unrealsdk @@ -1 +1 @@ -Subproject commit c2a918d3bb8edc2225d6aab7b5ebb80d25bc4349 +Subproject commit 38a5191cc7c98a58ab123cc13884ef8e61baf74c diff --git a/src/pyunrealsdk/unreal_bindings/bound_function.cpp b/src/pyunrealsdk/unreal_bindings/bound_function.cpp index e80c39d..ae4bd18 100644 --- a/src/pyunrealsdk/unreal_bindings/bound_function.cpp +++ b/src/pyunrealsdk/unreal_bindings/bound_function.cpp @@ -99,7 +99,7 @@ void fill_py_params(impl::PyCallInfo& info, const py::args& args, const py::kwar // NOLINTNEXTLINE(misc-const-correctness) bool optional = false; -#ifdef UE3 +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW optional = (prop->PropertyFlags() & UProperty::PROP_FLAG_OPTIONAL) != 0; #endif @@ -231,7 +231,7 @@ void register_bound_function(py::module_& mod) { " The unreal function's args. Out params will be used to initialized the\n" " unreal value, but the python value is not modified in place. Kwargs are\n" " supported.\n" -#ifdef UE3 +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW " Optional params should also be optional.\n" #endif " Alternatively, may call with a single positional WrappedStruct which matches\n" diff --git a/src/pyunrealsdk/unreal_bindings/persistent_object_ptr_property.cpp b/src/pyunrealsdk/unreal_bindings/persistent_object_ptr_property.cpp index 0ee69ef..b6402ec 100644 --- a/src/pyunrealsdk/unreal_bindings/persistent_object_ptr_property.cpp +++ b/src/pyunrealsdk/unreal_bindings/persistent_object_ptr_property.cpp @@ -179,7 +179,7 @@ void register_persistent_object_properties(py::module_& mod) { "source"_a, "idx"_a); PyUEClass(mod, "USoftClassProperty") - .def_property_readonly("MetaClass", &USoftClassProperty::get_meta_class); + .def_member_prop("MetaClass", &USoftClassProperty::MetaClass); } } // namespace pyunrealsdk::unreal diff --git a/src/pyunrealsdk/unreal_bindings/uenum.cpp b/src/pyunrealsdk/unreal_bindings/uenum.cpp index 8057b0a..1f119c6 100644 --- a/src/pyunrealsdk/unreal_bindings/uenum.cpp +++ b/src/pyunrealsdk/unreal_bindings/uenum.cpp @@ -37,7 +37,7 @@ py::object enum_as_py_enum(const UEnum* enum_obj) { if (!enum_cache.contains(enum_obj)) { std::unordered_map enum_names{}; -#ifdef UE4 +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_OAK // UE4 enums include the enum name and a namespace separator before the name - strip them for (const auto& [key, value] : enum_obj->get_names()) { diff --git a/src/pyunrealsdk/unreal_bindings/uobject_children.cpp b/src/pyunrealsdk/unreal_bindings/uobject_children.cpp index 39adffc..074afb5 100644 --- a/src/pyunrealsdk/unreal_bindings/uobject_children.cpp +++ b/src/pyunrealsdk/unreal_bindings/uobject_children.cpp @@ -135,13 +135,13 @@ void register_uobject_children(py::module_& mod) { // ======== Third Layer Subclasses ======== PyUEClass(mod, "UArrayProperty") - .def_property_readonly("Inner", &UArrayProperty::get_inner); + .def_member_prop("Inner", &UArrayProperty::Inner); PyUEClass(mod, "UBoolProperty") - .def_property_readonly("FieldMask", &UBoolProperty::get_field_mask); + .def_member_prop("FieldMask", &UBoolProperty::FieldMask); PyUEClass(mod, "UByteProperty") - .def_property_readonly("Enum", &UByteProperty::get_enum); + .def_member_prop("Enum", &UByteProperty::Enum); PyUEClass(mod, "UClass") .def_member_prop("ClassDefaultObject", &UClass::ClassDefaultObject) @@ -164,13 +164,13 @@ void register_uobject_children(py::module_& mod) { }); PyUEClass(mod, "UDelegateProperty") - .def_property_readonly("Signature", &UDelegateProperty::get_signature); + .def_member_prop("Signature", &UDelegateProperty::Signature); PyUEClass(mod, "UDoubleProperty"); PyUEClass(mod, "UEnumProperty") - .def_property_readonly("UnderlyingProp", &UEnumProperty::get_underlying_prop) - .def_property_readonly("Enum", &UEnumProperty::get_enum); + .def_member_prop("UnderlyingProp", &UEnumProperty::UnderlyingProp) + .def_member_prop("Enum", &UEnumProperty::Enum); PyUEClass(mod, "UFloatProperty"); @@ -192,17 +192,18 @@ void register_uobject_children(py::module_& mod) { PyUEClass(mod, "UInt64Property"); PyUEClass(mod, "UInterfaceProperty") - .def_property_readonly("InterfaceClass", &UInterfaceProperty::get_interface_class); + .def_member_prop("InterfaceClass", &UInterfaceProperty::InterfaceClass); PyUEClass(mod, "UIntProperty"); PyUEClass(mod, "UMulticastDelegateProperty") - .def_property_readonly("Signature", &UMulticastDelegateProperty::get_signature); + .def_member_prop("Signature", + &UMulticastDelegateProperty::Signature); PyUEClass(mod, "UNameProperty"); PyUEClass(mod, "UObjectProperty") - .def_property_readonly("PropertyClass", &UObjectProperty::get_property_class); + .def_member_prop("PropertyClass", &UObjectProperty::PropertyClass); PyUEClass(mod, "UScriptStruct") .def_member_prop("StructFlags", &UScriptStruct::StructFlags); @@ -210,7 +211,7 @@ void register_uobject_children(py::module_& mod) { PyUEClass(mod, "UStrProperty"); PyUEClass(mod, "UStructProperty") - .def_property_readonly("Struct", &UStructProperty::get_inner_struct); + .def_member_prop("Struct", &UStructProperty::Struct); PyUEClass(mod, "UTextProperty"); @@ -225,27 +226,27 @@ void register_uobject_children(py::module_& mod) { PyUEClass(mod, "UBlueprintGeneratedClass"); PyUEClass(mod, "UByteAttributeProperty") - .def_property_readonly("ModifierStackProperty", - &UByteAttributeProperty::get_modifier_stack_prop) - .def_property_readonly("OtherAttributeProperty", - &UByteAttributeProperty::get_other_attribute_property); + .def_member_prop("ModifierStackProperty", + &UByteAttributeProperty::ModifierStackProperty) + .def_member_prop("OtherAttributeProperty", + &UByteAttributeProperty::OtherAttributeProperty); PyUEClass(mod, "UClassProperty") - .def_property_readonly("MetaClass", &UClassProperty::get_meta_class); + .def_member_prop("MetaClass", &UClassProperty::MetaClass); PyUEClass(mod, "UComponentProperty"); PyUEClass(mod, "UFloatAttributeProperty") - .def_property_readonly("ModifierStackProperty", - &UFloatAttributeProperty::get_modifier_stack_prop) - .def_property_readonly("OtherAttributeProperty", - &UFloatAttributeProperty::get_other_attribute_property); + .def_member_prop("ModifierStackProperty", + &UFloatAttributeProperty::ModifierStackProperty) + .def_member_prop("OtherAttributeProperty", + &UFloatAttributeProperty::OtherAttributeProperty); PyUEClass(mod, "UIntAttributeProperty") - .def_property_readonly("ModifierStackProperty", - &UIntAttributeProperty::get_modifier_stack_prop) - .def_property_readonly("OtherAttributeProperty", - &UIntAttributeProperty::get_other_attribute_property); + .def_member_prop("ModifierStackProperty", + &UIntAttributeProperty::ModifierStackProperty) + .def_member_prop("OtherAttributeProperty", + &UIntAttributeProperty::OtherAttributeProperty); // ULazyObjectProperty - registered elsewhere // USoftObjectProperty - registered elsewhere diff --git a/stubs/unrealsdk/unreal/_uobject_children.pyi b/stubs/unrealsdk/unreal/_uobject_children.pyi index 1b85870..02d8851 100644 --- a/stubs/unrealsdk/unreal/_uobject_children.pyi +++ b/stubs/unrealsdk/unreal/_uobject_children.pyi @@ -100,12 +100,10 @@ class UStruct(UField): # ======== Third Layer Subclasses ======== class UArrayProperty(UProperty): - @property - def Inner(self) -> UProperty: ... + Inner: UProperty class UBoolProperty(UProperty): - @property - def FieldMask(self) -> int: ... + FieldMask: int class UByteProperty(UProperty): @property @@ -127,16 +125,13 @@ class UClass(UStruct): """ class UDelegateProperty(UProperty): - @property - def Signature(self) -> UFunction: ... + Signature: UFunction class UDoubleProperty(UProperty): ... class UEnumProperty(UProperty): - @property - def Enum(self) -> UEnum: ... - @property - def UnderlyingProp(self) -> UProperty: ... + Enum: UEnum + UnderlyingProp: UProperty class UFloatProperty(UProperty): ... @@ -159,20 +154,17 @@ class UInt16Property(UProperty): ... class UInt64Property(UProperty): ... class UInterfaceProperty(UProperty): - @property - def InterfaceClass(self) -> UClass: ... + InterfaceClass: UClass class UIntProperty(UProperty): ... class UMulticastDelegateProperty(UProperty): - @property - def Signature(self) -> UFunction: ... + Signature: UFunction class UNameProperty(UProperty): ... class UObjectProperty(UProperty): - @property - def PropertyClass(self) -> UClass: ... + PropertyClass: UClass class UScriptStruct(UStruct): StructFlags: int @@ -180,8 +172,7 @@ class UScriptStruct(UStruct): class UStrProperty(UProperty): ... class UStructProperty(UProperty): - @property - def Struct(self) -> UScriptStruct: ... + Struct: UScriptStruct class UTextProperty(UProperty): ... class UUInt16Property(UProperty): ... @@ -193,28 +184,21 @@ class UUInt64Property(UProperty): ... class UBlueprintGeneratedClass(UClass): ... class UByteAttributeProperty(UByteProperty): - @property - def ModifierStackProperty(self) -> UArrayProperty | None: ... - @property - def OtherAttributeProperty(self) -> UByteAttributeProperty | None: ... + ModifierStackProperty: UArrayProperty + OtherAttributeProperty: UByteAttributeProperty class UClassProperty(UObjectProperty): - @property - def MetaClass(self) -> UClass: ... + MetaClass: UClass class UComponentProperty(UObjectProperty): ... class UFloatAttributeProperty(UByteProperty): - @property - def ModifierStackProperty(self) -> UArrayProperty | None: ... - @property - def OtherAttributeProperty(self) -> UByteAttributeProperty | None: ... + ModifierStackProperty: UArrayProperty + OtherAttributeProperty: UByteAttributeProperty class UIntAttributeProperty(UByteProperty): - @property - def ModifierStackProperty(self) -> UArrayProperty | None: ... - @property - def OtherAttributeProperty(self) -> UByteAttributeProperty | None: ... + ModifierStackProperty: UArrayProperty + OtherAttributeProperty: UByteAttributeProperty class ULazyObjectProperty(UObjectProperty): @staticmethod From 1e12de544244a34baaac5c71d155c455d60999b7 Mon Sep 17 00:00:00 2001 From: apple1417 Date: Thu, 15 May 2025 21:04:19 +1200 Subject: [PATCH 03/11] don't try overwrite hook return value on void functions also clean up this function a bit --- src/pyunrealsdk/hook.cpp | 87 ++++++++++++++++++++++++---------------- 1 file changed, 52 insertions(+), 35 deletions(-) diff --git a/src/pyunrealsdk/hook.cpp b/src/pyunrealsdk/hook.cpp index 763d903..8a8be64 100644 --- a/src/pyunrealsdk/hook.cpp +++ b/src/pyunrealsdk/hook.cpp @@ -55,46 +55,63 @@ bool handle_py_hook(Details& hook, const py::object& callback) { ret_arg = py::type::of(); } - auto should_block = callback(hook.obj, hook.args, ret_arg, hook.func); - - if (py::isinstance(should_block)) { - auto ret_tuple = py::cast(should_block); - auto size = ret_tuple.size(); - - if (size == 1) { - LOG(WARNING, - "Return value of hook was tuple of size 1, expected 2. Not overwriting return " - "value."); - } else { - if (size != 2) { - LOG(WARNING, - "Return value of hook was tuple of size {}, expected 2. Extra values will be " - "ignored.", - size); - } + auto py_ret = callback(hook.obj, hook.args, ret_arg, hook.func); + + if (!py::isinstance(py_ret)) { + // If not a tuple, the value we got is always the first field, if to block + return is_block_sentinel(py_ret); + } + + auto ret_tuple = py::cast(py_ret); + auto size = ret_tuple.size(); + + if (size == 0) { + LOG(DEV_WARNING, + "Hook returned empty tuple. Not blocking execution, not overwriting return."); + LOG(DEV_WARNING, "Hooked function: {}", hook.func.func->get_path_name()); + return false; + } - auto ret_override = ret_tuple[1]; - - if (py::type::of().is(ret_override) || py::isinstance(ret_override)) { - hook.ret.destroy(); - } else if (!py::ellipsis{}.equal(ret_override)) { - cast(hook.ret.prop, [&hook, &ret_override](T* prop) { - // Need to replicate PropertyProxy::set ourselves a bit, since we want to use - // our custom python setter - if (hook.ret.ptr.get() == nullptr) { - hook.ret.ptr = UnrealPointer(hook.ret.prop); - } - - pyunrealsdk::unreal::py_setattr_direct( - prop, reinterpret_cast(hook.ret.ptr.get()), ret_override); - }); + auto should_block = is_block_sentinel(ret_tuple[0]); + if (size < 2) { + return should_block; + } + + auto ret_override = ret_tuple[1]; + if (py::type::of().is(ret_override) || py::isinstance(ret_override)) { + // If unset, destroy whatever was there before + hook.ret.destroy(); + } else if (py::ellipsis{}.equal(ret_override)) { + // If ellipsis, keep whatever there was before - intentionally empty + } else if (hook.ret.prop == nullptr) { + // Try overwrite the return value - except we can't, this is a void function + LOG(DEV_WARNING, + "Hook of void function tried to overwrite return value. This will be " + "ignored."); + LOG(DEV_WARNING, "Hooked function: {}", hook.func.func->get_path_name()); + } else { + // Overwrite the return value + cast(hook.ret.prop, [&hook, &ret_override](T* prop) { + // Need to replicate PropertyProxy::set ourselves a bit, since we want to + // use our custom python setter + if (hook.ret.ptr.get() == nullptr) { + hook.ret.ptr = UnrealPointer(hook.ret.prop); } - } - should_block = std::move(ret_tuple[0]); + pyunrealsdk::unreal::py_setattr_direct( + prop, reinterpret_cast(hook.ret.ptr.get()), ret_override); + }); + } + if (size < 3) { + return should_block; } - return is_block_sentinel(should_block); + LOG(DEV_WARNING, + "Hook returned tuple of size {}, which is greater than the maximum used size of 2." + " Extra values will be ignored.", + size); + LOG(DEV_WARNING, "Hooked function: {}", hook.func.func->get_path_name()); + return should_block; } } // namespace From 632d8a2fe88c9966cd44d577c1a0662362d3baaa Mon Sep 17 00:00:00 2001 From: apple1417 Date: Sat, 17 May 2025 22:55:20 +1200 Subject: [PATCH 04/11] fix enum conversion --- src/pyunrealsdk/unreal_bindings/property_access.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pyunrealsdk/unreal_bindings/property_access.cpp b/src/pyunrealsdk/unreal_bindings/property_access.cpp index fed5e69..1813a1f 100644 --- a/src/pyunrealsdk/unreal_bindings/property_access.cpp +++ b/src/pyunrealsdk/unreal_bindings/property_access.cpp @@ -94,13 +94,13 @@ py::object py_getattr(UField* field, auto val = get_property(prop, i, base_addr, parent); // Multiple property types expose a get enum method - constexpr bool is_enum = requires(const T* type) { - { type->get_enum() } -> std::same_as; + constexpr bool is_enum = requires(T* type) { + { type->Enum() } -> std::convertible_to; }; // If the value we're reading is an enum, convert it to a python enum if constexpr (is_enum) { - auto ue_enum = prop->get_enum(); + auto ue_enum = prop->Enum(); if (ue_enum != nullptr) { ret[i] = enum_as_py_enum(ue_enum)(val); continue; From 305358cbebc218e9a8ddf045775c7d3b6ddfbbcf Mon Sep 17 00:00:00 2001 From: apple1417 Date: Sat, 17 May 2025 22:59:32 +1200 Subject: [PATCH 05/11] upgrade unrealsdk --- CMakePresets.json | 6 +- common_cmake | 2 +- libs/unrealsdk | 2 +- src/pyunrealsdk/unreal_bindings/bindings.h | 10 +-- .../persistent_object_ptr_property.cpp | 2 +- src/pyunrealsdk/unreal_bindings/uobject.cpp | 10 +-- .../unreal_bindings/uobject_children.cpp | 70 +++++++++---------- 7 files changed, 49 insertions(+), 53 deletions(-) diff --git a/CMakePresets.json b/CMakePresets.json index 3aa18ea..087b9ab 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -40,9 +40,10 @@ "lhs": "${hostSystemName}", "rhs": "Windows" }, - "environment": { + "cacheVariables": { "MSVC_WINE_ENV_SCRIPT": "/win-sdk/bin/x86/msvcenv.sh" }, + "generator": "Ninja", "toolchainFile": "common_cmake/clang-cross-x86.cmake" }, { @@ -53,9 +54,10 @@ "lhs": "${hostSystemName}", "rhs": "Windows" }, - "environment": { + "cacheVariables": { "MSVC_WINE_ENV_SCRIPT": "/win-sdk/bin/x64/msvcenv.sh" }, + "generator": "Ninja", "toolchainFile": "common_cmake/clang-cross-x64.cmake" }, { diff --git a/common_cmake b/common_cmake index 92175e1..0b1c32d 160000 --- a/common_cmake +++ b/common_cmake @@ -1 +1 @@ -Subproject commit 92175e1c74b78bbb6746a589793cf54a9e7c5465 +Subproject commit 0b1c32de1ff98a7851f798d6b092edd1e89e5eba diff --git a/libs/unrealsdk b/libs/unrealsdk index 38a5191..bc3a5a9 160000 --- a/libs/unrealsdk +++ b/libs/unrealsdk @@ -1 +1 @@ -Subproject commit 38a5191cc7c98a58ab123cc13884ef8e61baf74c +Subproject commit bc3a5a9857b431e0015ceb6ef258670144f8f152 diff --git a/src/pyunrealsdk/unreal_bindings/bindings.h b/src/pyunrealsdk/unreal_bindings/bindings.h index ee61d6a..3965025 100644 --- a/src/pyunrealsdk/unreal_bindings/bindings.h +++ b/src/pyunrealsdk/unreal_bindings/bindings.h @@ -30,22 +30,22 @@ class PY_OBJECT_VISIBILITY PyUEClass /** * @brief Helper type to define one of our C++ "properties" as a python property. - * @note These must be first calls, as the standard def functions return a base py::class_, + * @note These must be the first calls, as the standard def functions return a base py::class_, * rendering this function inaccessible. * * @tparam C The type of this class, should be picked up automatically. * @tparam D The type of the property, should be picked up automatically. * @tparam Extra Any extra template args to forward. * @param name The name of the property. - * @param getter The getter function. Needs to be specialized, should be non-cost. + * @param getter The getter function. * @param extra Any extra args to forward. * @return A reference to the same class object. */ template - PyUEClass& def_member_prop(const char* name, D& (*getter)(C&), const Extra&... extra) { + PyUEClass& def_member_prop(const char* name, D& (C::*getter)(void), const Extra&... extra) { this->def_property( - name, [getter](C& self) { return getter(self); }, - [getter](C& self, D&& value) { getter(self) = std::move(value); }, extra...); + name, [getter](C& self) { return (self.*getter)(); }, + [getter](C& self, D&& value) { (self.*getter)() = std::move(value); }, extra...); return *this; } }; diff --git a/src/pyunrealsdk/unreal_bindings/persistent_object_ptr_property.cpp b/src/pyunrealsdk/unreal_bindings/persistent_object_ptr_property.cpp index b6402ec..9f7d260 100644 --- a/src/pyunrealsdk/unreal_bindings/persistent_object_ptr_property.cpp +++ b/src/pyunrealsdk/unreal_bindings/persistent_object_ptr_property.cpp @@ -179,7 +179,7 @@ void register_persistent_object_properties(py::module_& mod) { "source"_a, "idx"_a); PyUEClass(mod, "USoftClassProperty") - .def_member_prop("MetaClass", &USoftClassProperty::MetaClass); + .def_member_prop("MetaClass", &USoftClassProperty::MetaClass); } } // namespace pyunrealsdk::unreal diff --git a/src/pyunrealsdk/unreal_bindings/uobject.cpp b/src/pyunrealsdk/unreal_bindings/uobject.cpp index ddc75bb..75e8bda 100644 --- a/src/pyunrealsdk/unreal_bindings/uobject.cpp +++ b/src/pyunrealsdk/unreal_bindings/uobject.cpp @@ -44,11 +44,11 @@ void register_uobject(py::module_& mod) { "\n" "Most objects you interact with will be this type in python, even if their unreal\n" "class is something different.") - .def_member_prop("ObjectFlags", &UObject::ObjectFlags) - .def_member_prop("InternalIndex", &UObject::InternalIndex) - .def_member_prop("Class", &UObject::Class) - .def_member_prop("Name", &UObject::Name) - .def_member_prop("Outer", &UObject::Outer) + .def_member_prop("ObjectFlags", &UObject::ObjectFlags) + .def_member_prop("InternalIndex", &UObject::InternalIndex) + .def_member_prop("Class", &UObject::Class) + .def_member_prop("Name", &UObject::Name) + .def_member_prop("Outer", &UObject::Outer) .def("__new__", [](const py::args&, const py::kwargs&) { throw py::type_error("Cannot create new instances of unreal objects."); diff --git a/src/pyunrealsdk/unreal_bindings/uobject_children.cpp b/src/pyunrealsdk/unreal_bindings/uobject_children.cpp index 074afb5..82d110d 100644 --- a/src/pyunrealsdk/unreal_bindings/uobject_children.cpp +++ b/src/pyunrealsdk/unreal_bindings/uobject_children.cpp @@ -37,7 +37,7 @@ namespace pyunrealsdk::unreal { void register_uobject_children(py::module_& mod) { // ======== First Layer Subclasses ======== - PyUEClass(mod, "UField").def_member_prop("Next", &UField::Next); + PyUEClass(mod, "UField").def_member_prop("Next", &UField::Next); // ======== Second Layer Subclasses ======== @@ -48,16 +48,16 @@ void register_uobject_children(py::module_& mod) { [](UConst* self, const std::string& new_value) { self->Value() = new_value; }); PyUEClass(mod, "UProperty") - .def_member_prop("ArrayDim", &UProperty::ArrayDim) - .def_member_prop("ElementSize", &UProperty::ElementSize) - .def_member_prop("PropertyFlags", &UProperty::PropertyFlags) - .def_member_prop("Offset_Internal", &UProperty::Offset_Internal) - .def_member_prop("PropertyLinkNext", &UProperty::PropertyLinkNext); + .def_member_prop("ArrayDim", &UProperty::ArrayDim) + .def_member_prop("ElementSize", &UProperty::ElementSize) + .def_member_prop("PropertyFlags", &UProperty::PropertyFlags) + .def_member_prop("Offset_Internal", &UProperty::Offset_Internal) + .def_member_prop("PropertyLinkNext", &UProperty::PropertyLinkNext); PyUEClass(mod, "UStruct") - .def_member_prop("SuperField", &UStruct::SuperField) - .def_member_prop("Children", &UStruct::Children) - .def_member_prop("PropertyLink", &UStruct::PropertyLink) + .def_member_prop("SuperField", &UStruct::SuperField) + .def_member_prop("Children", &UStruct::Children) + .def_member_prop("PropertyLink", &UStruct::PropertyLink) .def( "_fields", [](UStruct* self) { @@ -135,16 +135,16 @@ void register_uobject_children(py::module_& mod) { // ======== Third Layer Subclasses ======== PyUEClass(mod, "UArrayProperty") - .def_member_prop("Inner", &UArrayProperty::Inner); + .def_member_prop("Inner", &UArrayProperty::Inner); PyUEClass(mod, "UBoolProperty") - .def_member_prop("FieldMask", &UBoolProperty::FieldMask); + .def_member_prop("FieldMask", &UBoolProperty::FieldMask); PyUEClass(mod, "UByteProperty") - .def_member_prop("Enum", &UByteProperty::Enum); + .def_member_prop("Enum", &UByteProperty::Enum); PyUEClass(mod, "UClass") - .def_member_prop("ClassDefaultObject", &UClass::ClassDefaultObject) + .def_member_prop("ClassDefaultObject", &UClass::ClassDefaultObject) .def( "_implements", [](UClass* self, UClass* interface) { return self->implements(interface, nullptr); }, @@ -164,21 +164,21 @@ void register_uobject_children(py::module_& mod) { }); PyUEClass(mod, "UDelegateProperty") - .def_member_prop("Signature", &UDelegateProperty::Signature); + .def_member_prop("Signature", &UDelegateProperty::Signature); PyUEClass(mod, "UDoubleProperty"); PyUEClass(mod, "UEnumProperty") - .def_member_prop("UnderlyingProp", &UEnumProperty::UnderlyingProp) - .def_member_prop("Enum", &UEnumProperty::Enum); + .def_member_prop("UnderlyingProp", &UEnumProperty::UnderlyingProp) + .def_member_prop("Enum", &UEnumProperty::Enum); PyUEClass(mod, "UFloatProperty"); PyUEClass(mod, "UFunction") - .def_member_prop("FunctionFlags", &UFunction::FunctionFlags) - .def_member_prop("NumParams", &UFunction::NumParams) - .def_member_prop("ParamsSize", &UFunction::ParamsSize) - .def_member_prop("ReturnValueOffset", &UFunction::ReturnValueOffset) + .def_member_prop("FunctionFlags", &UFunction::FunctionFlags) + .def_member_prop("NumParams", &UFunction::NumParams) + .def_member_prop("ParamsSize", &UFunction::ParamsSize) + .def_member_prop("ReturnValueOffset", &UFunction::ReturnValueOffset) .def("_find_return_param", &UFunction::find_return_param, "Finds the return param for this function (if it exists).\n" "\n" @@ -192,26 +192,25 @@ void register_uobject_children(py::module_& mod) { PyUEClass(mod, "UInt64Property"); PyUEClass(mod, "UInterfaceProperty") - .def_member_prop("InterfaceClass", &UInterfaceProperty::InterfaceClass); + .def_member_prop("InterfaceClass", &UInterfaceProperty::InterfaceClass); PyUEClass(mod, "UIntProperty"); PyUEClass(mod, "UMulticastDelegateProperty") - .def_member_prop("Signature", - &UMulticastDelegateProperty::Signature); + .def_member_prop("Signature", &UMulticastDelegateProperty::Signature); PyUEClass(mod, "UNameProperty"); PyUEClass(mod, "UObjectProperty") - .def_member_prop("PropertyClass", &UObjectProperty::PropertyClass); + .def_member_prop("PropertyClass", &UObjectProperty::PropertyClass); PyUEClass(mod, "UScriptStruct") - .def_member_prop("StructFlags", &UScriptStruct::StructFlags); + .def_member_prop("StructFlags", &UScriptStruct::StructFlags); PyUEClass(mod, "UStrProperty"); PyUEClass(mod, "UStructProperty") - .def_member_prop("Struct", &UStructProperty::Struct); + .def_member_prop("Struct", &UStructProperty::Struct); PyUEClass(mod, "UTextProperty"); @@ -226,27 +225,22 @@ void register_uobject_children(py::module_& mod) { PyUEClass(mod, "UBlueprintGeneratedClass"); PyUEClass(mod, "UByteAttributeProperty") - .def_member_prop("ModifierStackProperty", - &UByteAttributeProperty::ModifierStackProperty) - .def_member_prop("OtherAttributeProperty", - &UByteAttributeProperty::OtherAttributeProperty); + .def_member_prop("ModifierStackProperty", &UByteAttributeProperty::ModifierStackProperty) + .def_member_prop("OtherAttributeProperty", &UByteAttributeProperty::OtherAttributeProperty); PyUEClass(mod, "UClassProperty") - .def_member_prop("MetaClass", &UClassProperty::MetaClass); + .def_member_prop("MetaClass", &UClassProperty::MetaClass); PyUEClass(mod, "UComponentProperty"); PyUEClass(mod, "UFloatAttributeProperty") - .def_member_prop("ModifierStackProperty", - &UFloatAttributeProperty::ModifierStackProperty) + .def_member_prop("ModifierStackProperty", &UFloatAttributeProperty::ModifierStackProperty) .def_member_prop("OtherAttributeProperty", - &UFloatAttributeProperty::OtherAttributeProperty); + &UFloatAttributeProperty::OtherAttributeProperty); PyUEClass(mod, "UIntAttributeProperty") - .def_member_prop("ModifierStackProperty", - &UIntAttributeProperty::ModifierStackProperty) - .def_member_prop("OtherAttributeProperty", - &UIntAttributeProperty::OtherAttributeProperty); + .def_member_prop("ModifierStackProperty", &UIntAttributeProperty::ModifierStackProperty) + .def_member_prop("OtherAttributeProperty", &UIntAttributeProperty::OtherAttributeProperty); // ULazyObjectProperty - registered elsewhere // USoftObjectProperty - registered elsewhere From 7d4cab888886b6a1b6651840183961f6fe78504e Mon Sep 17 00:00:00 2001 From: apple1417 Date: Sat, 17 May 2025 22:59:41 +1200 Subject: [PATCH 06/11] expose new property size field --- src/pyunrealsdk/unreal_bindings/uobject_children.cpp | 1 + stubs/unrealsdk/unreal/_uobject_children.pyi | 1 + 2 files changed, 2 insertions(+) diff --git a/src/pyunrealsdk/unreal_bindings/uobject_children.cpp b/src/pyunrealsdk/unreal_bindings/uobject_children.cpp index 82d110d..8fa9024 100644 --- a/src/pyunrealsdk/unreal_bindings/uobject_children.cpp +++ b/src/pyunrealsdk/unreal_bindings/uobject_children.cpp @@ -57,6 +57,7 @@ void register_uobject_children(py::module_& mod) { PyUEClass(mod, "UStruct") .def_member_prop("SuperField", &UStruct::SuperField) .def_member_prop("Children", &UStruct::Children) + .def_member_prop("PropertySize", &UStruct::PropertySize) .def_member_prop("PropertyLink", &UStruct::PropertyLink) .def( "_fields", diff --git a/stubs/unrealsdk/unreal/_uobject_children.pyi b/stubs/unrealsdk/unreal/_uobject_children.pyi index 02d8851..1cbe652 100644 --- a/stubs/unrealsdk/unreal/_uobject_children.pyi +++ b/stubs/unrealsdk/unreal/_uobject_children.pyi @@ -29,6 +29,7 @@ class UProperty(UField): class UStruct(UField): Children: UField | None PropertyLink: UProperty | None + PropertySize: int SuperField: UStruct | None def _fields(self) -> Iterator[UField]: From 8a9a56ee912be84ba408bbf2c69cbe591edd5a94 Mon Sep 17 00:00:00 2001 From: apple1417 Date: Sun, 18 May 2025 18:52:26 +1200 Subject: [PATCH 07/11] update ci --- .github/workflows/build.yml | 41 +++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f2c703d..5cfd869 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,10 +6,6 @@ on: [ workflow_dispatch ] -env: - LLVM_MINGW_VERSION: llvm-mingw-20240619-msvcrt-ubuntu-20.04-x86_64 - LLVM_MINGW_DOWNLOAD: https://github.com/mstorsjo/llvm-mingw/releases/download/20240619/llvm-mingw-20240619-msvcrt-ubuntu-20.04-x86_64.tar.xz - jobs: build-windows: runs-on: windows-latest @@ -18,10 +14,10 @@ jobs: fail-fast: false matrix: preset: [ - "clang-ue3-x86-release", - "clang-ue4-x64-release", - "msvc-ue3-x86-release", - "msvc-ue4-x64-release", + "clang-willow-release", + "clang-oak-release", + "msvc-willow-release", + "msvc-oak-release", ] steps: @@ -33,7 +29,7 @@ jobs: if: startswith(matrix.preset, 'msvc') uses: TheMrMilchmann/setup-msvc-dev@v3 with: - arch: ${{ fromJSON('["x86", "x64"]')[contains(matrix.preset, 'x64')] }} + arch: ${{ fromJSON('["x86", "x64"]')[contains(matrix.preset, 'oak')] }} - name: Setup CMake and Ninja uses: lukka/get-cmake@latest @@ -60,24 +56,23 @@ jobs: run: cmake --build out/build/${{ matrix.preset }} build-ubuntu: - # Require at least 24 for the mingw build - runs-on: ubuntu-24.04 + runs-on: ubuntu-latest strategy: fail-fast: false matrix: toolchain: - - preset: clang-cross-ue3-x86-release + - preset: clang-cross-willow-release container: clang-cross - - preset: clang-cross-ue4-x64-release + - preset: clang-cross-oak-release container: clang-cross - - preset: llvm-mingw-ue3-x86-release + - preset: llvm-mingw-willow-release container: llvm-mingw - - preset: llvm-mingw-ue4-x64-release + - preset: llvm-mingw-oak-release container: llvm-mingw - - preset: mingw-ue3-x86-release + - preset: mingw-willow-release container: mingw - - preset: mingw-ue4-x64-release + - preset: mingw-oak-release container: mingw steps: @@ -86,6 +81,13 @@ jobs: with: submodules: recursive + - name: Login to GitHub Container Registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Build uses: devcontainers/ci@v0.3 with: @@ -109,13 +111,12 @@ jobs: fail-fast: false matrix: preset: [ - "clang-ue3-x86-release", - "clang-ue4-x64-release", + "clang-willow-release", + "clang-oak-release", ] steps: - name: Setup Clang - if: startswith(matrix.preset, 'clang') uses: egor-tensin/setup-clang@v1 - name: Setup CMake and Ninja From 002cfcab42c916b4fda8126d1e8b733c7e3c7d9b Mon Sep 17 00:00:00 2001 From: apple1417 Date: Sat, 24 May 2025 18:38:28 +1200 Subject: [PATCH 08/11] fix crash when commands are deleted asynchronusly previously they were accidentally leaked, so we never ran into this --- libs/unrealsdk | 2 +- src/pyunrealsdk/commands.cpp | 53 ++++++++++++++++++++++++++---------- 2 files changed, 40 insertions(+), 15 deletions(-) diff --git a/libs/unrealsdk b/libs/unrealsdk index bc3a5a9..e5bad41 160000 --- a/libs/unrealsdk +++ b/libs/unrealsdk @@ -1 +1 @@ -Subproject commit bc3a5a9857b431e0015ceb6ef258670144f8f152 +Subproject commit e5bad41c67050a77cb1bc59062b4d74bb5d43ffb diff --git a/src/pyunrealsdk/commands.cpp b/src/pyunrealsdk/commands.cpp index f5f2cc5..924a3f8 100644 --- a/src/pyunrealsdk/commands.cpp +++ b/src/pyunrealsdk/commands.cpp @@ -99,6 +99,44 @@ void py_cmd_handler(const wchar_t* line, size_t size, size_t cmd_len) { } } +// Since next line commands may be removed asynchronusly, we need a custom type to make sure we hold +// the GIL when it's destroyed +struct PythonCommandCallback { + py::object callback; + + PythonCommandCallback(py::object callback) : callback(std::move(callback)) {} + PythonCommandCallback(const PythonCommandCallback&) = default; + PythonCommandCallback(PythonCommandCallback&&) noexcept = default; + PythonCommandCallback& operator=(const PythonCommandCallback&) = default; + PythonCommandCallback& operator=(PythonCommandCallback&&) noexcept = default; + + ~PythonCommandCallback() { + if (callback) { + const py::gil_scoped_acquire gil{}; + callback.release().dec_ref(); + } + } + + /** + * @brief Runs the command callback. + * + * @param line The line which triggered the command. + * @param size The size of the line which triggered the command. + * @param cmd_len The length of the matched command at the start of the line. + */ + void operator()(const wchar_t* line, size_t size, size_t cmd_len) { + try { + const py::gil_scoped_acquire gil{}; + debug_this_thread(); + + const py::str py_line{PyUnicode_FromWideChar(line, static_cast(size))}; + callback(py_line, cmd_len); + } catch (const std::exception& ex) { + logging::log_python_exception(ex); + } + } +}; + } // namespace void register_module(py::module_& mod) { @@ -107,20 +145,7 @@ void register_module(py::module_& mod) { commands.def( "add_command", [](const std::wstring& cmd, const py::object& callback) { - unrealsdk::commands::add_command( - cmd, [callback](const wchar_t* line, size_t size, size_t cmd_len) { - try { - const py::gil_scoped_acquire gil{}; - debug_this_thread(); - - const py::str py_line{ - PyUnicode_FromWideChar(line, static_cast(size))}; - - callback(py_line, cmd_len); - } catch (const std::exception& ex) { - logging::log_python_exception(ex); - } - }); + unrealsdk::commands::add_command(cmd, PythonCommandCallback{callback}); }, "Adds a custom console command.\n" "\n" From af44f31923eed728202fd58d442e88c6272da518 Mon Sep 17 00:00:00 2001 From: apple1417 Date: Sat, 24 May 2025 18:39:59 +1200 Subject: [PATCH 09/11] update changelog, bump version number as with unrealsdk, going to drop the commit hashes, since they take annoyingly much work for little gain --- CMakeLists.txt | 2 +- changelog.md | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cd6d69e..85a9fae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.25) -project(pyunrealsdk VERSION 1.7.0) +project(pyunrealsdk VERSION 1.8.0) function(_pyunrealsdk_add_base_target_args target_name) target_compile_features(${target_name} PUBLIC cxx_std_23) diff --git a/changelog.md b/changelog.md index 1fe1bbf..0495578 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,12 @@ # Changelog +## v1.8.0 +- Trying to overwrite the return value of a void function will now return a more appropriate error. + +- Upgraded to support unrealsdk v2 - native modules can expect some breakage. The most notable + effect this has on Python code is a number of formerly read-only fields on core unreal types have + become read-write. + ## v1.7.0 - Added `WrappedArray.emplace_struct`, to construct structs in place. This is more efficient than From 4f8bbababc576ce9bf54ef0f9f69b4191688fdd8 Mon Sep 17 00:00:00 2001 From: apple1417 Date: Sun, 25 May 2025 16:22:39 +1200 Subject: [PATCH 10/11] fix crash when hooks are deleted asynchronusly, redo fix for commands --- libs/unrealsdk | 2 +- src/pyunrealsdk/commands.cpp | 56 +++++++++++------------------------- src/pyunrealsdk/hook.cpp | 7 +++-- 3 files changed, 23 insertions(+), 42 deletions(-) diff --git a/libs/unrealsdk b/libs/unrealsdk index e5bad41..e3a8847 160000 --- a/libs/unrealsdk +++ b/libs/unrealsdk @@ -1 +1 @@ -Subproject commit e5bad41c67050a77cb1bc59062b4d74bb5d43ffb +Subproject commit e3a8847bd341dca6e89591ff1119d231476fe2ab diff --git a/src/pyunrealsdk/commands.cpp b/src/pyunrealsdk/commands.cpp index 924a3f8..2aa1a06 100644 --- a/src/pyunrealsdk/commands.cpp +++ b/src/pyunrealsdk/commands.cpp @@ -2,6 +2,7 @@ #include "pyunrealsdk/commands.h" #include "pyunrealsdk/debugging.h" #include "pyunrealsdk/logging.h" +#include "pyunrealsdk/static_py_object.h" #include "unrealsdk/commands.h" #include "unrealsdk/config.h" #include "unrealsdk/utils.h" @@ -99,44 +100,6 @@ void py_cmd_handler(const wchar_t* line, size_t size, size_t cmd_len) { } } -// Since next line commands may be removed asynchronusly, we need a custom type to make sure we hold -// the GIL when it's destroyed -struct PythonCommandCallback { - py::object callback; - - PythonCommandCallback(py::object callback) : callback(std::move(callback)) {} - PythonCommandCallback(const PythonCommandCallback&) = default; - PythonCommandCallback(PythonCommandCallback&&) noexcept = default; - PythonCommandCallback& operator=(const PythonCommandCallback&) = default; - PythonCommandCallback& operator=(PythonCommandCallback&&) noexcept = default; - - ~PythonCommandCallback() { - if (callback) { - const py::gil_scoped_acquire gil{}; - callback.release().dec_ref(); - } - } - - /** - * @brief Runs the command callback. - * - * @param line The line which triggered the command. - * @param size The size of the line which triggered the command. - * @param cmd_len The length of the matched command at the start of the line. - */ - void operator()(const wchar_t* line, size_t size, size_t cmd_len) { - try { - const py::gil_scoped_acquire gil{}; - debug_this_thread(); - - const py::str py_line{PyUnicode_FromWideChar(line, static_cast(size))}; - callback(py_line, cmd_len); - } catch (const std::exception& ex) { - logging::log_python_exception(ex); - } - } -}; - } // namespace void register_module(py::module_& mod) { @@ -145,7 +108,22 @@ void register_module(py::module_& mod) { commands.def( "add_command", [](const std::wstring& cmd, const py::object& callback) { - unrealsdk::commands::add_command(cmd, PythonCommandCallback{callback}); + // Convert to a static py object, so the lambda can safely get destroyed whenever + const StaticPyObject static_callback{callback}; + unrealsdk::commands::add_command( + cmd, [static_callback](const wchar_t* line, size_t size, size_t cmd_len) { + try { + const py::gil_scoped_acquire gil{}; + debug_this_thread(); + + const py::str py_line{ + PyUnicode_FromWideChar(line, static_cast(size))}; + + static_callback(py_line, cmd_len); + } catch (const std::exception& ex) { + logging::log_python_exception(ex); + } + }); }, "Adds a custom console command.\n" "\n" diff --git a/src/pyunrealsdk/hook.cpp b/src/pyunrealsdk/hook.cpp index 8a8be64..1cbccc5 100644 --- a/src/pyunrealsdk/hook.cpp +++ b/src/pyunrealsdk/hook.cpp @@ -3,6 +3,7 @@ #include "pyunrealsdk/exports.h" #include "pyunrealsdk/hooks.h" #include "pyunrealsdk/logging.h" +#include "pyunrealsdk/static_py_object.h" #include "pyunrealsdk/unreal_bindings/property_access.h" #include "unrealsdk/hook_manager.h" #include "unrealsdk/unreal/cast.h" @@ -190,12 +191,14 @@ void register_module(py::module_& mod) { "add_hook", [](const std::wstring& func, Type type, const std::wstring& identifier, const py::object& callback) { - add_hook(func, type, identifier, [callback](Details& hook) { + // Convert to a static py object, so the lambda can safely get destroyed whenever + const StaticPyObject static_callback{callback}; + add_hook(func, type, identifier, [static_callback](Details& hook) { try { const py::gil_scoped_acquire gil{}; debug_this_thread(); - return handle_py_hook(hook, callback); + return handle_py_hook(hook, static_callback); } catch (const std::exception& ex) { logging::log_python_exception(ex); From 7a3f28b2bd49574167d7b36ce492327887801362 Mon Sep 17 00:00:00 2001 From: apple1417 Date: Wed, 28 May 2025 19:08:51 +1200 Subject: [PATCH 11/11] pull in unrealsdk update, update changelog --- changelog.md | 27 +++++++++++++++++++++++++++ libs/unrealsdk | 2 +- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index 0495578..376e46d 100644 --- a/changelog.md +++ b/changelog.md @@ -7,6 +7,33 @@ effect this has on Python code is a number of formerly read-only fields on core unreal types have become read-write. +### unrealsdk v2.0.0 +For reference, the unrealsdk v2.0.0 changes this includes are: + +> - Now supports Borderlands 1. Big thanks to Ry for doing basically all the reverse engineering. +> +> - Major refactor of the core unreal types, to cleanly allow them to change layouts at runtime. All +> core fields have changed from members to zero-arg methods, which return a reference to the +> member. A few classes (e.g. `UProperty` subclasses) previous had existing methods to deal with +> the same problem, these have all been moved to the new system. +> +> Clang is able to detect this change, and gives a nice error recommending inserting brackets at +> the right spot. +> +> - Removed the `UNREALSDK_UE_VERSION` and `UNREALSDK_ARCH` CMake variables, in favour a new merged +> `UNREALSDK_FLAVOUR` variable. +> +> - Removed the (optional) dependency on libfmt, `std::format` support is now required. +> +> - Console commands registered using `unrealsdk::commands::NEXT_LINE` now (try to) only fire on +> direct user input, and ignore commands send via automated means. +> +> - Fixed that assigning an entire array, rather than getting the array and setting it's elements, +> would likely cause memory corruption. This was most common when using an array of large structs, +> and when assigning to one which was previously empty. +> +> - Made `unrealsdk::memory::get_exe_range` public. + ## v1.7.0 - Added `WrappedArray.emplace_struct`, to construct structs in place. This is more efficient than diff --git a/libs/unrealsdk b/libs/unrealsdk index e3a8847..c577cc5 160000 --- a/libs/unrealsdk +++ b/libs/unrealsdk @@ -1 +1 @@ -Subproject commit e3a8847bd341dca6e89591ff1119d231476fe2ab +Subproject commit c577cc52e7a383071dc6f9917cfc039d14f4b991