diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d8f214b..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 @@ -137,7 +138,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 +148,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 +164,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..85a9fae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,9 +1,9 @@ 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_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/CMakePresets.json b/CMakePresets.json index efe923b..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" }, { @@ -110,7 +112,7 @@ "toolchainFile": "common_cmake/msvc.cmake" }, { - "name": "_x86", + "name": "_willow", "hidden": true, "architecture": { "value": "Win32", @@ -118,11 +120,12 @@ }, "cacheVariables": { "EXPLICIT_PYTHON_ARCH": "win32", + "UNREALSDK_FLAVOUR": "WILLOW", "UNREALSDK_ARCH": "x86" } }, { - "name": "_x64", + "name": "_oak", "hidden": true, "architecture": { "value": "x64", @@ -130,23 +133,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 +152,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/changelog.md b/changelog.md index 1fe1bbf..376e46d 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,39 @@ # 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. + +### 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/common_cmake b/common_cmake index c27d687..0b1c32d 160000 --- a/common_cmake +++ b/common_cmake @@ -1 +1 @@ -Subproject commit c27d68718f01ca2e40fc2627fa7461b552a0d6ce +Subproject commit 0b1c32de1ff98a7851f798d6b092edd1e89e5eba diff --git a/libs/unrealsdk b/libs/unrealsdk index 9621c36..c577cc5 160000 --- a/libs/unrealsdk +++ b/libs/unrealsdk @@ -1 +1 @@ -Subproject commit 9621c3643da2db05f3d8699d3646469135764420 +Subproject commit c577cc52e7a383071dc6f9917cfc039d14f4b991 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/commands.cpp b/src/pyunrealsdk/commands.cpp index f5f2cc5..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" @@ -107,8 +108,10 @@ void register_module(py::module_& mod) { commands.def( "add_command", [](const std::wstring& cmd, const py::object& 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, [callback](const wchar_t* line, size_t size, size_t cmd_len) { + cmd, [static_callback](const wchar_t* line, size_t size, size_t cmd_len) { try { const py::gil_scoped_acquire gil{}; debug_this_thread(); @@ -116,7 +119,7 @@ void register_module(py::module_& mod) { const py::str py_line{ PyUnicode_FromWideChar(line, static_cast(size))}; - callback(py_line, cmd_len); + static_callback(py_line, cmd_len); } catch (const std::exception& ex) { logging::log_python_exception(ex); } diff --git a/src/pyunrealsdk/hook.cpp b/src/pyunrealsdk/hook.cpp index 763d903..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" @@ -55,46 +56,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 @@ -173,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); 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..3965025 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 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. + * @param extra Any extra args to forward. + * @return A reference to the same class object. + */ + template + PyUEClass& def_member_prop(const char* name, D& (C::*getter)(void), const Extra&... extra) { + this->def_property( + name, [getter](C& self) { return (self.*getter)(); }, + [getter](C& self, D&& value) { (self.*getter)() = 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..ae4bd18 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; +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW + 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" @@ -235,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..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_property_readonly("MetaClass", &USoftClassProperty::get_meta_class); + .def_member_prop("MetaClass", &USoftClassProperty::MetaClass); } } // namespace pyunrealsdk::unreal diff --git a/src/pyunrealsdk/unreal_bindings/property_access.cpp b/src/pyunrealsdk/unreal_bindings/property_access.cpp index d224a85..1813a1f 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,27 +80,27 @@ 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 - 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; @@ -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..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()) { @@ -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..75e8bda 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..8fa9024 100644 --- a/src/pyunrealsdk/unreal_bindings/uobject_children.cpp +++ b/src/pyunrealsdk/unreal_bindings/uobject_children.cpp @@ -37,23 +37,28 @@ 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("PropertySize", &UStruct::PropertySize) + .def_member_prop("PropertyLink", &UStruct::PropertyLink) .def( "_fields", [](UStruct* self) { @@ -126,23 +131,21 @@ 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 ======== 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) .def( "_implements", [](UClass* self, UClass* interface) { return self->implements(interface, nullptr); }, @@ -153,9 +156,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()) { @@ -165,34 +165,26 @@ 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"); 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"); @@ -201,27 +193,25 @@ 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_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"); PyUEClass(mod, "UStructProperty") - .def_property_readonly("Struct", &UStructProperty::get_inner_struct); + .def_member_prop("Struct", &UStructProperty::Struct); PyUEClass(mod, "UTextProperty"); @@ -236,27 +226,22 @@ 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/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 diff --git a/stubs/unrealsdk/unreal/_uobject_children.pyi b/stubs/unrealsdk/unreal/_uobject_children.pyi index 1b85870..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]: @@ -100,12 +101,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 +126,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 +155,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 +173,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 +185,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