diff --git a/.cruft.json b/.cruft.json index ab66239..3da4c80 100644 --- a/.cruft.json +++ b/.cruft.json @@ -1,6 +1,6 @@ { "template": "git@github.com:bl-sdk/common_dotfiles.git", - "commit": "cee5c9dbf5b95f57bb636e5138171aa6a4964cf1", + "commit": "597ec422d3b5692927f325b9b5c2ae42288e8cc5", "checkout": null, "context": { "cookiecutter": { @@ -16,7 +16,7 @@ "include_cpp": true, "include_py": false, "_template": "git@github.com:bl-sdk/common_dotfiles.git", - "_commit": "cee5c9dbf5b95f57bb636e5138171aa6a4964cf1" + "_commit": "597ec422d3b5692927f325b9b5c2ae42288e8cc5" } }, "directory": null diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8e8557b..f546805 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,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: @@ -29,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 @@ -52,17 +52,17 @@ jobs: 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: @@ -93,7 +93,6 @@ jobs: cmake . --preset ${{ matrix.toolchain.preset }} -G Ninja cmake --build out/build/${{ matrix.toolchain.preset }} - # ============================================================================== clang-tidy: @@ -103,8 +102,8 @@ jobs: fail-fast: false matrix: preset: [ - "clang-ue3-x86-release", - "clang-ue4-x64-release", + "clang-willow-release", + "clang-oak-release", ] steps: diff --git a/.gitignore b/.gitignore index eb698b0..50e5591 100644 --- a/.gitignore +++ b/.gitignore @@ -7,10 +7,6 @@ .gdbinit out -# User scripts with templates -postbuild -postbuild.bat - # CMake (from https://github.com/github/gitignore/blob/main/CMake.gitignore) CMakeLists.txt.user CMakeCache.txt diff --git a/.gitmodules b/.gitmodules index ac1c13a..1b42c38 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,6 @@ [submodule "libs/minhook"] path = libs/minhook url = https://github.com/TsudaKageyu/minhook.git -[submodule "libs/fmt"] - path = libs/fmt - url = https://github.com/fmtlib/fmt.git [submodule "libs/tomlplusplus"] path = libs/tomlplusplus url = https://github.com/marzer/tomlplusplus.git diff --git a/CMakeLists.txt b/CMakeLists.txt index dff7142..ed64090 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,9 +1,13 @@ cmake_minimum_required(VERSION 3.25) -project(unrealsdk VERSION 1.8.0) +project(unrealsdk VERSION 2.0.0) + +set(UNREALSDK_FLAVOUR "" CACHE STRING "Which \"flavour\" of the SDK to build.") +set_property(CACHE UNREALSDK_FLAVOUR PROPERTY STRINGS "WILLOW" "OAK") +if (NOT UNREALSDK_FLAVOUR MATCHES "(WILLOW|OAK)") + message(FATAL_ERROR "Got invalid sdk flavour '${UNREALSDK_FLAVOUR}'") +endif() -set(UNREALSDK_UE_VERSION "UE4" CACHE STRING "The unreal engine version to build the SDK for. One of 'UE3' or 'UE4'.") -set(UNREALSDK_ARCH "x64" CACHE STRING "The architecture to build the sdk for. One of 'x86' or 'x64'.") set(UNREALSDK_SHARED False CACHE BOOL "If set, compiles as a shared library instead of as an object.") add_library(_unrealsdk_interface INTERFACE) @@ -32,17 +36,6 @@ if(MINGW) target_compile_options(_unrealsdk_interface INTERFACE -Werror) endif() -set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) -try_compile(supports_format - SOURCE_FROM_CONTENT test.cpp " \ - #include \n \ - #ifndef __cpp_lib_format\n \ - #error\n \ - #endif \ - " - CXX_STANDARD 20 -) - set(CONFIGURE_FILES_DIR "${CMAKE_CURRENT_BINARY_DIR}/configure") configure_file( @@ -56,29 +49,12 @@ include(common_cmake/git_watcher.cmake) add_subdirectory(libs/minhook) add_subdirectory(libs/tomlplusplus) -if(NOT supports_format) - add_subdirectory(libs/fmt) -endif() - -# If using the clang windows cross compilation toolchain -if((NOT CMAKE_HOST_WIN32) - AND (CMAKE_SYSTEM_NAME STREQUAL "Windows") - AND (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")) - # Disable some intrinsics - MSVC implicitly defines intrinsics, but Clang does not, so we get - # a linking error otherwise - target_compile_definitions(minhook PRIVATE MINHOOK_DISABLE_INTRINSICS) - target_compile_definitions(_unrealsdk_interface INTERFACE TOML_ENABLE_SIMD=0) -endif() target_include_directories(_unrealsdk_interface INTERFACE "src" ${CONFIGURE_FILES_DIR}) target_link_libraries(_unrealsdk_interface INTERFACE minhook tomlplusplus::tomlplusplus) -if(NOT ${supports_format}) - target_link_libraries(_unrealsdk_interface INTERFACE fmt) -endif() target_compile_definitions(_unrealsdk_interface INTERFACE - "${UNREALSDK_UE_VERSION}" - "ARCH_$" + "UNREALSDK_FLAVOUR=UNREALSDK_FLAVOUR_${UNREALSDK_FLAVOUR}" "$<$:UNREALSDK_SHARED>" ) @@ -123,25 +99,4 @@ else() # Unconditionally add the exporting flag # Add it privately, so it doesn't appear in anything linking against this target_compile_definitions(unrealsdk PRIVATE "UNREALSDK_EXPORTING") - - # Postbuild - if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) - set(POSTBUILD_SCRIPT "postbuild") - if(CMAKE_HOST_WIN32) - set(POSTBUILD_SCRIPT "${POSTBUILD_SCRIPT}.bat") - endif() - if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${POSTBUILD_SCRIPT}") - add_custom_command( - TARGET unrealsdk - POST_BUILD - COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/${POSTBUILD_SCRIPT}" - ARGS - "$>" - "${UNREALSDK_UE_VERSION}" - "${UNREALSDK_ARCH}" - "$,DEBUG,RELEASE>" - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - ) - endif() - endif() endif() diff --git a/CMakePresets.json b/CMakePresets.json index 8e1c658..718359f 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,39 +112,25 @@ "toolchainFile": "common_cmake/msvc.cmake" }, { - "name": "_x86", + "name": "_willow", "hidden": true, "architecture": { "value": "Win32", "strategy": "external" }, "cacheVariables": { - "UNREALSDK_ARCH": "x86" + "UNREALSDK_FLAVOUR": "WILLOW" } }, { - "name": "_x64", + "name": "_oak", "hidden": true, "architecture": { "value": "x64", "strategy": "external" }, "cacheVariables": { - "UNREALSDK_ARCH": "x64" - } - }, - { - "name": "_ue3", - "hidden": true, - "cacheVariables": { - "UNREALSDK_UE_VERSION": "UE3" - } - }, - { - "name": "_ue4", - "hidden": true, - "cacheVariables": { - "UNREALSDK_UE_VERSION": "UE4" + "UNREALSDK_FLAVOUR": "OAK" } }, { @@ -160,222 +148,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/README.md b/README.md index 8df227a..228859a 100644 --- a/README.md +++ b/README.md @@ -51,9 +51,7 @@ auto op_string = hook.obj->get(L"BuildOverpowerPromptS So there are a few extra steps to integrate the sdk into your project before you can start using hooks like above. -The SDK requires at least C++20. This is primarily for templated lambdas - you may still be able to -use it if your compiler doesn't yet fully support it (e.g. it falls back to fmtlib if `std::format` -is not available). +The SDK requires at least C++20. The recommended way to link against the sdk is as a submodule. @@ -61,20 +59,28 @@ The recommended way to link against the sdk is as a submodule. git clone --recursive https://github.com/bl-sdk/unrealsdk.git ``` ```cmake +set(UNREALSDK_FLAVOUR WILLOW) +# set(UNREALSDK_SHARED True) add_submodule(path/to/unrealsdk) target_link_libraries(MyProject PRIVATE unrealsdk) ``` -You can configure the sdk by setting a few variables before including it: -- `UNREALSDK_UE_VERSION` - The unreal engine version to build the SDK for, one of `UE3` or `UE4`. - These versions are different enough that supporting them from a single binary is difficult. -- `UNREALSDK_ARCH` - The architecture to build the sdk for. One of `x86` or `x64`. Will be double - checked at compile time. -- `UNREALSDK_SHARED` - If set, compiles as a shared library instead of as an object. +You must define the build "flavour" before including the sdk. Each flavour corresponds to a set of +games running on a similar (but not necessarily the exact same) unreal engine versions. Essentially, +if two games are similar enough to use the same mod manager, and support mostly the same mods, they +should work under the same build flavour. Some level of runtime adaptation is supported (e.g. core +types may slightly change size or layout), but accounting for large engine internal differences +(e.g. the different GNames data structures) at runtime is a non-goal. -If you want to be able to run multiple projects using the sdk in the same game process, you *must* -compile it as a shared library, there's a decent amount of internal state preventing initializing it -twice. +The currently supported flavours are: +- `WILLOW`: Borderlands 1, 2, TPS, and AoDK standalone. Named for Gearbox's codename. 32-bit UE3. +- `OAK`: Borderlands 3 and Wonderlands. Named for Gearbox's codename. 64-bit UE 4.21ish - there's + some backports making exact versioning awkward. + +Additionally, you can optionally define the `UNREALSDK_SHARED` variable, to compile as a shared +library instead of as an object one. If you want to be able to run multiple projects using the sdk +in the same game process, you *must* compile it as a shared library, there's a decent amount of +internal state preventing initializing it twice. If you're linking against a static library, the easiest way to initialize it is: ```cpp @@ -168,15 +174,13 @@ To build: git clone --recursive https://github.com/bl-sdk/unrealsdk.git ``` -2. (OPTIONAL) Copy `postbuild.template`, and edit it to copy files to your game install directories. - -3. Choose a preset, and run CMake. Most IDEs will be able to do this for you, +2. Choose a preset, and run CMake. Most IDEs will be able to do this for you, ``` cmake . --preset msvc-ue4-x64-debug cmake --build out/build/msvc-ue4-x64-debug ``` -4. (OPTIONAL) If you're debugging a game on Steam, add a `steam_appid.txt` in the same folder as the +3. (OPTIONAL) If you're debugging a game on Steam, add a `steam_appid.txt` in the same folder as the executable, containing the game's Steam App Id. Normally, games compiled with Steamworks will call diff --git a/changelog.md b/changelog.md index 33fe361..8013571 100644 --- a/changelog.md +++ b/changelog.md @@ -1,12 +1,29 @@ # Changelog -## Upcoming +## 2.0.0 (Upcoming) +- 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. - [275bbc8b](https://github.com/bl-sdk/unrealsdk/commit/275bbc8b) +- Made `unrealsdk::memory::get_exe_range` public. ## 1.8.0 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/fmt b/libs/fmt deleted file mode 160000 index 0c9fce2..0000000 --- a/libs/fmt +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0c9fce2ffefecfdce794e1859584e25877b7b592 diff --git a/postbuild.template b/postbuild.template deleted file mode 100644 index 81c1efc..0000000 --- a/postbuild.template +++ /dev/null @@ -1,20 +0,0 @@ -REM This script is run after a build completes, intended for advanced customization of install dir. -REM On Windows, it must be renamed to `postbuild.bat`, and thus should be a batch file. On other -REM platforms, it should just be called `postbuild`, and can be any executable program. -REM If the script exists, it will be called with 4 arguments: The path to the built target and the -REM build's UE version, architecture, and type (`DEBUG` or `RELEASE`). - -REM Example (batch) implementation: - -@echo off - -set "target=%~1" -set "ue_version=%~2" -set "architecture=%~3" -set "build_type=%~4" - -if /i "%ue_version%" == "UE4" ( - if "%architecture%" == "x64" ( - copy /Y "%target%" "C:\Program Files (x86)\Steam\steamapps\common\Borderlands 3\OakGame\Binaries\Win64\Plugins\unrealsdk.dll" - ) -) diff --git a/src/unrealsdk/commands.cpp b/src/unrealsdk/commands.cpp index 7d787bd..c0c24b1 100644 --- a/src/unrealsdk/commands.cpp +++ b/src/unrealsdk/commands.cpp @@ -82,17 +82,39 @@ namespace impl { #ifndef UNREALSDK_IMPORTING -std::pair find_matching_command(std::wstring_view line) { +bool is_command_valid(std::wstring_view line, bool direct_user_input) { + if (direct_user_input && commands.find(NEXT_LINE) != commands.end()) { + return true; + } + auto non_space = std::ranges::find_if_not(line, &std::iswspace); + if (non_space == line.end()) { + return false; + } + + auto cmd_end = std::find_if(non_space, line.end(), &std::iswspace); + + std::wstring cmd(cmd_end - non_space, '\0'); + std::transform(non_space, cmd_end, cmd.begin(), &std::towlower); + return commands.contains(cmd); +} + +void run_command(std::wstring_view line) { auto iter = commands.find(NEXT_LINE); if (iter != commands.end()) { auto callback = iter->second; commands.erase(iter); - return {callback, 0}; + + callback->operator()(line.data(), line.size(), 0); + + callback->destroy(); + return; } + // I realize we're redoing a bunch of work from above here, but meh. + // Hopefully LTO gets it auto non_space = std::ranges::find_if_not(line, &std::iswspace); if (non_space == line.end()) { - return {nullptr, 0}; + return; } auto cmd_end = std::find_if(non_space, line.end(), &std::iswspace); @@ -100,8 +122,7 @@ std::pair find_matching_command(std::wstring_view line std::wstring cmd(cmd_end - non_space, '\0'); std::transform(non_space, cmd_end, cmd.begin(), &std::towlower); - return commands.contains(cmd) ? std::pair{commands[cmd], cmd_end - line.begin()} - : std::pair{nullptr, 0}; + commands.at(cmd)->operator()(line.data(), line.size(), cmd_end - line.begin()); } #endif diff --git a/src/unrealsdk/commands.h b/src/unrealsdk/commands.h index 4310cca..5393577 100644 --- a/src/unrealsdk/commands.h +++ b/src/unrealsdk/commands.h @@ -12,16 +12,18 @@ You can use this module to register custom console commands, in a single unified To register a command, you simply provide it's name. Commands are matched by comparing the first block of non-whitespace characters in a line submitted to console against all registered names. -As a special case, if you register the special `NEXT_LINE` command, it will always match the very -next line, in place of anything else which might have been matched otherwise. It will then -immediately be removed (though before the callback is run, so you can re-register it if needed), to -allow normal command processing to continue afterwards. +To create interactive menus, rather than registering separate commands for every input, use the +special `NEXT_LINE` command, which always matches the very next line, with higher priority than +anything else. These commands are only matched once, and are automatically removed before running +the callback. They also try to only match direct user inputs in console, and try ignore commands +sent via automated means - e.g. if the game actually sends an `open` command to load a map. */ /** * @brief A special value used to register a command which will always match the very next line. * @note Only one next line command can be registered at a time. * @note The next line command is automatically removed after it gets matched. + * @note The next line command *should not* be matched by automated commands. */ extern const std::wstring NEXT_LINE; @@ -67,13 +69,21 @@ namespace impl { // These functions are only relevant when implementing a game #ifndef UNREALSDK_IMPORTING /** - * @brief Finds the command which matches the given line. + * @brief Checks if a command line contains a valid command to be run. * - * @param line The line which was submitted. - * @return A pair of the callback to run and the offset to pass to it, or of nullptr and 0 if there - * was no match. + * @param line The line to check. + * @param direct_user_input True if this command was directly input to console by a user. + * @return True if this contains a command we should run. */ -std::pair find_matching_command(std::wstring_view line); +bool is_command_valid(std::wstring_view line, bool direct_user_input); + +/** + * @brief Runs the command associated with the given line. + * @note Assumes is_command_valid previously returned true on the same line. + * + * @param line The line holding the command to be run. + */ +void run_command(std::wstring_view line); #endif } // namespace impl diff --git a/src/unrealsdk/format.h b/src/unrealsdk/format.h deleted file mode 100644 index c29c8cb..0000000 --- a/src/unrealsdk/format.h +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef UNREALSDK_FORMAT_H -#define UNREALSDK_FORMAT_H - -// Header to switch between std::format and fmtlib, and alias them under `unrealsdk::fmt` - -// NOLINTBEGIN(misc-unused-using-decls) - -#if __cpp_lib_format - -#include - -namespace unrealsdk::fmt { - -using std::format; -using std::format_context; -using std::formatter; - -} // namespace unrealsdk::fmt - -#else - -#include -#include -#include -#include - -namespace unrealsdk::fmt { - -using ::fmt::format; -using ::fmt::format_context; -using ::fmt::formatter; - -} // namespace unrealsdk::fmt - -#endif - -// NOLINTEND(misc-unused-using-decls) - -#endif /* UNREALSDK_FORMAT_H */ diff --git a/src/unrealsdk/game/abstract_hook.h b/src/unrealsdk/game/abstract_hook.h index 3b62763..04d4d4f 100644 --- a/src/unrealsdk/game/abstract_hook.h +++ b/src/unrealsdk/game/abstract_hook.h @@ -3,7 +3,6 @@ #include "unrealsdk/pch.h" -#include "unrealsdk/unreal/classes/uobject.h" #include "unrealsdk/unreal/structs/fname.h" #ifndef UNREALSDK_IMPORTING @@ -14,6 +13,7 @@ class GNames; class GObjects; class UClass; class UFunction; +class UObject; struct FFrame; struct FLazyObjectPtr; struct FSoftObjectPtr; @@ -22,6 +22,12 @@ struct TemporaryFString; } // namespace unrealsdk::unreal +namespace unrealsdk::unreal::offsets { + +struct OffsetList; + +} // namespace unrealsdk::unreal::offsets + namespace unrealsdk::game { #pragma region Hook Classes @@ -61,7 +67,7 @@ struct AbstractHook { unreal::UClass* cls, unreal::UObject* outer, const unreal::FName& name, - decltype(unreal::UObject::ObjectFlags) flags, + uint64_t flags, unreal::UObject* template_obj) const = 0; [[nodiscard]] virtual unreal::UObject* find_object(unreal::UClass* cls, const std::wstring& name) const = 0; @@ -81,6 +87,7 @@ struct AbstractHook { const unreal::UObject* obj) const = 0; virtual void flazyobjectptr_assign(unreal::FLazyObjectPtr* ptr, const unreal::UObject* obj) const = 0; + [[nodiscard]] virtual const unreal::offsets::OffsetList& get_offsets(void) const = 0; }; #pragma endregion diff --git a/src/unrealsdk/game/bl1/antidebug.cpp b/src/unrealsdk/game/bl1/antidebug.cpp new file mode 100644 index 0000000..c8db461 --- /dev/null +++ b/src/unrealsdk/game/bl1/antidebug.cpp @@ -0,0 +1,108 @@ + +// - NOTE - +// Copied from bl2/antidebug.cpp +// + +#include "unrealsdk/pch.h" + +#include "unrealsdk/game/bl1/bl1.h" + +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW && !defined(UNREALSDK_IMPORTING) + +namespace unrealsdk::game { + +namespace { + +// NOLINTBEGIN(readability-identifier-naming) +// NOLINTNEXTLINE(modernize-use-using) - need a typedef for calling conventions in msvc +typedef NTSTATUS(WINAPI* NtSetInformationThread_func)( + HANDLE ThreadHandle, + THREAD_INFORMATION_CLASS ThreadInformationClass, + PVOID ThreadInformation, + ULONG ThreadInformationLength); +// NOLINTNEXTLINE(modernize-use-using) +typedef NTSTATUS(WINAPI* NtQueryInformationProcess_func)(HANDLE ProcessHandle, + PROCESSINFOCLASS ProcessInformationClass, + PVOID ProcessInformation, + ULONG ProcessInformationLength, + PULONG ReturnLength); + +// These are undocumented values, not in the header, treat as size_t to avoid enum conversion errors +constexpr size_t ThreadHideFromDebugger = 17; +constexpr size_t ProcessDebugObjectHandle = 30; +// NOLINTEND(readability-identifier-naming) + +// NOLINTBEGIN(readability-identifier-naming) +NtSetInformationThread_func NtSetInformationThread_ptr; +NTSTATUS NTAPI NtSetInformationThread_hook(HANDLE ThreadHandle, + THREAD_INFORMATION_CLASS ThreadInformationClass, + PVOID ThreadInformation, + ULONG ThreadInformationLength) { + // NOLINTEND(readability-identifier-naming) + if (static_cast(ThreadInformationClass) == ThreadHideFromDebugger) { + return STATUS_SUCCESS; + } + + return NtSetInformationThread_ptr(ThreadHandle, ThreadInformationClass, ThreadInformation, + ThreadInformationLength); +} +static_assert(std::is_same_v, + "NtSetInformationThread signature is incorrect"); + +// NOLINTBEGIN(readability-identifier-naming) +NtQueryInformationProcess_func NtQueryInformationProcess_ptr; +NTSTATUS WINAPI NtQueryInformationProcess_hook(HANDLE ProcessHandle, + PROCESSINFOCLASS ProcessInformationClass, + PVOID ProcessInformation, + ULONG ProcessInformationLength, + PULONG ReturnLength) { + // NOLINTEND(readability-identifier-naming) + if (static_cast(ProcessInformationClass) == ProcessDebugObjectHandle) { + return STATUS_PORT_NOT_SET; + } + + return NtQueryInformationProcess_ptr(ProcessHandle, ProcessInformationClass, ProcessInformation, + ProcessInformationLength, ReturnLength); +} +static_assert( + std::is_same_v, + "NtQueryInformationProcess signature is incorrect"); + +} // namespace + +void BL1Hook::hook_antidebug(void) { + MH_STATUS status = MH_OK; + + LPVOID target = nullptr; + status = MH_CreateHookApiEx(L"ntdll", "NtSetInformationThread", + reinterpret_cast(NtSetInformationThread_hook), + reinterpret_cast(&NtSetInformationThread_ptr), &target); + if (status != MH_OK) { + LOG(ERROR, "Failed to create NtSetInformationThread hook: {:x}", + static_cast(status)); + } else { + status = MH_EnableHook(target); + if (status != MH_OK) { + LOG(ERROR, "Failed to enable NtSetInformationThread hook: {:x}", + static_cast(status)); + } + } + + status = MH_CreateHookApiEx(L"ntdll", "NtQueryInformationProcess", + reinterpret_cast(NtQueryInformationProcess_hook), + reinterpret_cast(&NtQueryInformationProcess_ptr), &target); + if (status != MH_OK) { + LOG(ERROR, "Failed to create NtQueryInformationProcess hook: {:x}", + static_cast(status)); + } else { + status = MH_EnableHook(target); + if (status != MH_OK) { + LOG(ERROR, "Failed to enable NtQueryInformationProcess hook: {:x}", + static_cast(status)); + } + } +} + +} // namespace unrealsdk::game + +#endif diff --git a/src/unrealsdk/game/bl1/bl1.cpp b/src/unrealsdk/game/bl1/bl1.cpp new file mode 100644 index 0000000..c5e20dd --- /dev/null +++ b/src/unrealsdk/game/bl1/bl1.cpp @@ -0,0 +1,135 @@ +#include "unrealsdk/pch.h" +#include "unrealsdk/game/bl1/bl1.h" + +#include "unrealsdk/hook_manager.h" +#include "unrealsdk/memory.h" +#include "unrealsdk/unreal/structs/fframe.h" +#include "unrealsdk/version_error.h" + +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW && !defined(UNREALSDK_IMPORTING) + +using namespace unrealsdk::memory; +using namespace unrealsdk::unreal; + +namespace unrealsdk::game { + +void BL1Hook::hook(void) { + hook_antidebug(); + + hook_process_event(); + hook_call_function(); + + find_gobjects(); + find_gnames(); + find_fname_init(); + find_fframe_step(); + find_gmalloc(); + find_construct_object(); + find_get_path_name(); + find_static_find_object(); + find_load_package(); + + hexedit_set_command(); + hexedit_array_limit(); +} + +void BL1Hook::post_init(void) { + inject_console(); +} + +#pragma region FFrame::Step + +namespace { + +// FFrame::Step is inlined, so instead we manually re-implement it using GNatives. +const constinit Pattern<11> GNATIVES_SIG{ + "8B 14 95 {????????}" // mov edx, [edx*4+01F942C0] + "57" // push edi + "8D 4D ??" // lea ecx, [ebp-44] +}; + +// NOLINTNEXTLINE(modernize-use-using) +typedef void(__stdcall* fframe_step_func)(FFrame*, void*); +fframe_step_func** fframe_step_gnatives; + +} // namespace + +void BL1Hook::find_fframe_step(void) { + fframe_step_gnatives = GNATIVES_SIG.sigscan_nullable(); + LOG(MISC, "GNatives: {:p}", reinterpret_cast(fframe_step_gnatives)); +} + +void BL1Hook::fframe_step(unreal::FFrame* frame, unreal::UObject* /*obj*/, void* param) const { + ((*fframe_step_gnatives)[*frame->Code++])(frame, param); +} + +#pragma region FName::Init + +namespace { + +#if defined(__MINGW32__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wattributes" // thiscall on non-class +#endif + +const constinit Pattern<72> FNAME_INIT_SIG{ + "6A FF" // push -01 + "68 ????????" // push 018E73CB + "64 A1 ????????" // mov eax, fs:[00000000] + "50" // push eax + "81 EC ????????" // sub esp, 00000C98 + "A1 ????????" // mov eax, [01F131C0] + "33 C4" // xor eax, esp + "89 84 24 ????????" // mov [esp+00000C94], eax + "53" // push ebx + "55" // push ebp + "56" // push esi + "57" // push edi + "A1 ????????" // mov eax, [01F131C0] + "33 C4" // xor eax, esp + "50" // push eax + "8D 84 24 ????????" // lea eax, [esp+00000CAC] + "64 A3 ????????" // mov fs:[00000000], eax + "8B BC 24 ????????" // mov edi, [esp+00000CBC] + "8B E9" // mov ebp, ecx + "89 6C 24 ??" // mov [esp+1C], ebp +}; + +// NOLINTNEXTLINE(modernize-use-using) +typedef void(__thiscall* fname_init_func)(FName* name, + const wchar_t* str, + int32_t number, + int32_t find_type, + int32_t split_name); + +fname_init_func fname_init_ptr = nullptr; + +#if defined(__MINGW32__) +#pragma GCC diagnostic pop +#endif + +} // namespace + +void BL1Hook::find_fname_init(void) { + fname_init_ptr = FNAME_INIT_SIG.sigscan_nullable(); + LOG(MISC, "FName::Init: {:p}", (void*)fname_init_ptr); +} + +void BL1Hook::fname_init(unreal::FName* name, const wchar_t* str, int32_t number) const { + fname_init_ptr(name, str, number, 1, 1); +} + +#pragma endregion + +#pragma region FText::AsCultureInvariant + +void BL1Hook::ftext_as_culture_invariant(unreal::FText* /*text*/, + TemporaryFString&& /*str*/) const { + throw_version_error("FTexts are not implemented in UE3"); +} + +#pragma endregion + +} // namespace unrealsdk::game + +#endif diff --git a/src/unrealsdk/game/bl1/bl1.h b/src/unrealsdk/game/bl1/bl1.h new file mode 100644 index 0000000..3fe259c --- /dev/null +++ b/src/unrealsdk/game/bl1/bl1.h @@ -0,0 +1,140 @@ +#ifndef UNREALSDK_GAME_BL1_BL1_H +#define UNREALSDK_GAME_BL1_BL1_H + +#include "unrealsdk/pch.h" + +#include "unrealsdk/game/abstract_hook.h" +#include "unrealsdk/game/selector.h" + +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW && !defined(UNREALSDK_IMPORTING) + +namespace unrealsdk::game { + +class BL1Hook : public AbstractHook { + protected: + /** + * @brief Finds `FName::Init`, and sets up such that `fname_init` may be called. + */ + static void find_fname_init(void); + + /** + * @brief Hooks the antidebug functions and disables them. + */ + static void hook_antidebug(void); + + /** + * @brief Hex edits out the protection on the set command. + */ + static void hexedit_set_command(void); + + /** + * @brief Hex edits out the `obj dump` array limit. + */ + static void hexedit_array_limit(void); + + /** + * @brief Hooks `UObject::ProcessEvent` and points it at the hook manager. + */ + static void hook_process_event(void); + + /** + * @brief Hooks `UObject::CallFunction` and points it at the hook manager. + */ + static void hook_call_function(void); + + /** + * @brief Finds GObjects, and populates the wrapper member. + */ + static void find_gobjects(void); + + /** + * @brief Finds GNames, and sets up such that `gnames` may be called. + */ + static void find_gnames(void); + + /** + * @brief Finds `FFrame::Step`, and sets up such that `fframe_step` may be called. + */ + static void find_fframe_step(void); + + /** + * @brief Finds `GMalloc`, and sets up such that `malloc`, `realloc`, and `free` may be called. + */ + static void find_gmalloc(void); + + /** + * @brief Finds `StaticConstructObject`, and sets up such that `construct_object` may be called. + */ + static void find_construct_object(void); + + /** + * @brief Finds `UObject::GetPathName`, and sets up such that `uobject_path_name` may be called. + */ + static void find_get_path_name(void); + + /** + * @brief Finds `StaticFindObject`, and sets up such that `find_object` may be called. + */ + static void find_static_find_object(void); + + /** + * @brief Finds `LoadPackage`, and sets up such that `load_package` may be called. + */ + static void find_load_package(void); + + /** + * @brief Creates a console and sets the bind (if required), and hooks logging onto it. + */ + static void inject_console(void); + + public: + void hook(void) override; + void post_init(void) override; + + [[nodiscard]] bool is_console_ready(void) const override; + + [[nodiscard]] const unreal::GObjects& gobjects(void) const override; + [[nodiscard]] const unreal::GNames& gnames(void) const override; + [[nodiscard]] void* u_malloc(size_t len) const override; + [[nodiscard]] void* u_realloc(void* original, size_t len) const override; + void u_free(void* data) const override; + [[nodiscard]] unreal::UObject* construct_object(unreal::UClass* cls, + unreal::UObject* outer, + const unreal::FName& name, + uint64_t flags, + unreal::UObject* template_obj) const override; + [[nodiscard]] unreal::UObject* find_object(unreal::UClass* cls, + const std::wstring& name) const override; + [[nodiscard]] unreal::UObject* load_package(const std::wstring& name, + uint32_t flags) const override; + + void fname_init(unreal::FName* name, const wchar_t* str, int32_t number) const override; + void fframe_step(unreal::FFrame* frame, unreal::UObject* obj, void* param) const override; + void process_event(unreal::UObject* object, + unreal::UFunction* func, + void* params) const override; + void uconsole_output_text(const std::wstring& str) const override; + [[nodiscard]] std::wstring uobject_path_name(const unreal::UObject* obj) const override; + void ftext_as_culture_invariant(unreal::FText* text, + unreal::TemporaryFString&& str) const override; + void fsoftobjectptr_assign(unreal::FSoftObjectPtr* ptr, + const unreal::UObject* obj) const override; + void flazyobjectptr_assign(unreal::FLazyObjectPtr* ptr, + const unreal::UObject* obj) const override; + [[nodiscard]] const unreal::offsets::OffsetList& get_offsets(void) const override; +}; + +template <> +struct GameTraits { + static constexpr auto NAME = "Borderlands"; + + static bool matches_executable(std::string_view executable) { + return executable == "Borderlands.exe" || executable == "borderlands.exe"; + } +}; + +} // namespace unrealsdk::game + +#endif + +#endif diff --git a/src/unrealsdk/game/bl1/console.cpp b/src/unrealsdk/game/bl1/console.cpp new file mode 100644 index 0000000..0b990d4 --- /dev/null +++ b/src/unrealsdk/game/bl1/console.cpp @@ -0,0 +1,215 @@ +#include "unrealsdk/pch.h" + +#include "unrealsdk/game/bl1/bl1.h" + +#include "unrealsdk/commands.h" +#include "unrealsdk/config.h" +#include "unrealsdk/hook_manager.h" +#include "unrealsdk/unreal/classes/properties/copyable_property.h" +#include "unrealsdk/unreal/classes/properties/uboolproperty.h" +#include "unrealsdk/unreal/classes/properties/uobjectproperty.h" +#include "unrealsdk/unreal/classes/properties/ustrproperty.h" +#include "unrealsdk/unreal/classes/uobject.h" +#include "unrealsdk/unreal/classes/uobject_funcs.h" +#include "unrealsdk/unreal/find_class.h" +#include "unrealsdk/unreal/wrappers/bound_function.h" + +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW && !defined(UNREALSDK_IMPORTING) + +using namespace unrealsdk::unreal; + +namespace unrealsdk::game { + +namespace { + +// This is an extra hook, which we don't strictly need for the interface, but is really handy. By +// default the game prepends 'say ' to every command as a primitive way to disable console. Bypass +// it so you can actually use it. + +const std::wstring SAY_BYPASS_FUNC = L"Engine.Console:ShippingConsoleCommand"; +const constexpr auto SAY_BYPASS_TYPE = hook_manager::Type::PRE; +const std::wstring SAY_BYPASS_ID = L"unrealsdk_bl1_say_bypass"; + +// We could combine this with the say bypass, but by keeping them separate it'll let users disable +// one if they really want to +const std::wstring CONSOLE_COMMAND_FUNC = L"Engine.Console:ConsoleCommand"; +// This is the actual end point of all console commands, the above function normally calls through +// into this one - but we needed to hook it to be able to manage the console history. If something +/// directly calls `PC.ConsoleCommand("my_cmd")`, we need this hook to be able to catch it. +const std::wstring PC_CONSOLE_COMMAND_FUNC = L"Engine.PlayerController:ConsoleCommand"; + +const constexpr auto CONSOLE_COMMAND_TYPE = hook_manager::Type::PRE; +const std::wstring CONSOLE_COMMAND_ID = L"unrealsdk_bl1_console_command"; + +const std::wstring INJECT_CONSOLE_FUNC = L"WillowGame.WillowGameViewportClient:PostRender"; +const constexpr auto INJECT_CONSOLE_TYPE = hook_manager::Type::PRE; +const std::wstring INJECT_CONSOLE_ID = L"unrealsdk_bl1_inject_console"; + +BoundFunction console_output_text{}; + +bool say_bypass_hook(const hook_manager::Details& hook) { + static const auto console_command_func = + hook.obj->Class()->find_func_and_validate(L"ConsoleCommand"_fn); + static const auto command_property = + hook.args->type->find_prop_and_validate(L"Command"_fn); + + hook.obj->get(console_command_func) + .call(hook.args->get(command_property)); + return true; +} + +bool console_command_hook(const hook_manager::Details& hook) { + static const auto command_property = + hook.args->type->find_prop_and_validate(L"Command"_fn); + + static const auto history_prop = + hook.obj->Class()->find_prop_and_validate(L"History"_fn); + static const auto history_top_prop = + hook.obj->Class()->find_prop_and_validate(L"HistoryTop"_fn); + static const auto history_bot_prop = + hook.obj->Class()->find_prop_and_validate(L"HistoryBot"_fn); + static const auto history_cur_prop = + hook.obj->Class()->find_prop_and_validate(L"HistoryCur"_fn); + + static const UFunction* purge_command_func = + hook.obj->Class()->find_func_and_validate(L"PurgeCommandFromHistory"_fn); + static const UFunction* save_config_func = + hook.obj->Class()->find_func_and_validate(L"SaveConfig"_fn); + + auto line = hook.args->get(command_property); + + // This hook only runs when input via console, it is direct user input + if (!commands::impl::is_command_valid(line, true)) { + return false; + } + + // Add to the history buffer + { + // History is a ring buffer of recent commands + // HistoryBot points to the oldest entry, or -1 if history is empty. + // HistoryTop points to the next entry to fill. If history is empty it's 0. The value it + // points at is *not* shown in the history if we've wrapped. + // HistoryCur is always set to the same as top after running a command - presumably it + // changes while scrolling, but we can't really check that + + // First remove it from history + hook.obj->get(purge_command_func).call(line); + + // Insert this line at top + auto history_top = hook.obj->get(history_top_prop); + hook.obj->set(history_prop, history_top, line); + + // Increment top + history_top = (history_top + 1) % history_prop->ArrayDim(); + hook.obj->set(history_top_prop, history_top); + // And set current + hook.obj->set(history_cur_prop, history_top); + + // Increment bottom if needed + auto history_bot = hook.obj->get(history_bot_prop); + if ((history_bot == -1) || history_bot == history_top) { + hook.obj->set(history_bot_prop, + (history_bot + 1) % history_prop->ArrayDim()); + } + + hook.obj->get(save_config_func).call(); + } + + /* + * This is a little awkward. + * Since we can't let execution though to the unreal function, we're responsible for printing + * the executed command line. + * + * We do this via output text directly, rather than the LOG macro, so that it's not affected by + * the console log level, and so that it happens immediately (the LOG macro is queued, and can + * get out of order with respect to native engine messages). + * + * However, for custom console commands it's also nice to see what the command was in the log + * file, since you'll see all their output too. + * + * We don't really expose a "write to log file only", since it's not usually something useful, + * so as a compromise just use the LOG macro on the lowest possible log level, and assume the + * lowest people practically set their console log level to is dev warning. + */ + auto msg = format(L">>> {} <<<", line); + console_output_text.call(msg); + LOG(MIN, L"{}", msg); + + try { + commands::impl::run_command(line); + } catch (const std::exception& ex) { + LOG(ERROR, "An exception occurred while running a console command: {}", ex.what()); + } + + return true; +} + +bool pc_console_command_hook(const hook_manager::Details& hook) { + static const auto command_property = + hook.args->type->find_prop_and_validate(L"Command"_fn); + + auto line = hook.args->get(command_property); + + // Conversely, this hook is explicitly not from console + if (!commands::impl::is_command_valid(line, false)) { + return false; + } + + // This hook does not go to console, so there's no extra processing to be done, we can just run + // the callback immediately + try { + commands::impl::run_command(line); + } catch (const std::exception& ex) { + LOG(ERROR, "An exception occurred while running a console command: {}", ex.what()); + } + return true; +} + +bool inject_console_hook(const hook_manager::Details& hook) { + remove_hook(INJECT_CONSOLE_FUNC, INJECT_CONSOLE_TYPE, INJECT_CONSOLE_ID); + + auto console = hook.obj->get(L"ViewportConsole"_fn); + + // Grab this reference ASAP + // Actually using OutputTextLine because it handles an empty string - OutputText does nothing + console_output_text = console->get(L"OutputTextLine"_fn); + + auto existing_console_key = console->get(L"ConsoleKey"_fn); + if (existing_console_key != L"None"_fn || existing_console_key == L"Undefine"_fn) { + LOG(MISC, "Console key is already set to '{}'", existing_console_key); + } else { + std::string wanted_console_key{config::get_str("unrealsdk.console_key").value_or("Tilde")}; + console->set(L"ConsoleKey"_fn, FName{wanted_console_key}); + + LOG(MISC, "Set console key to '{}'", wanted_console_key); + } + + return false; +} + +} // namespace + +void BL1Hook::inject_console(void) { + add_hook(SAY_BYPASS_FUNC, SAY_BYPASS_TYPE, SAY_BYPASS_ID, &say_bypass_hook); + + add_hook(CONSOLE_COMMAND_FUNC, CONSOLE_COMMAND_TYPE, CONSOLE_COMMAND_ID, &console_command_hook); + add_hook(PC_CONSOLE_COMMAND_FUNC, CONSOLE_COMMAND_TYPE, CONSOLE_COMMAND_ID, + &pc_console_command_hook); + + add_hook(INJECT_CONSOLE_FUNC, INJECT_CONSOLE_TYPE, INJECT_CONSOLE_ID, &inject_console_hook); +} + +void BL1Hook::uconsole_output_text(const std::wstring& str) const { + if (console_output_text.func == nullptr) { + return; + } + console_output_text.call(str); +} + +bool BL1Hook::is_console_ready(void) const { + return console_output_text.func != nullptr; +} + +} // namespace unrealsdk::game + +#endif diff --git a/src/unrealsdk/game/bl1/globals.cpp b/src/unrealsdk/game/bl1/globals.cpp new file mode 100644 index 0000000..fd93f9d --- /dev/null +++ b/src/unrealsdk/game/bl1/globals.cpp @@ -0,0 +1,69 @@ +#include "unrealsdk/pch.h" + +#include "unrealsdk/game/bl1/bl1.h" + +#include "unrealsdk/memory.h" +#include "unrealsdk/unreal/wrappers/gnames.h" +#include "unrealsdk/unreal/wrappers/gobjects.h" + +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW && !defined(UNREALSDK_IMPORTING) + +using namespace unrealsdk::memory; +using namespace unrealsdk::unreal; + +namespace unrealsdk::game { + +namespace { + +GObjects gobjects_wrapper{}; + +const constinit Pattern<18> GOBJECTS_SIG{ + "8B 0D {????????}" // mov ecx, [01FB4DD8] + "8B 04 ??" // mov eax, [ecx+esi*4] + "8B 50 ??" // mov edx, [eax+0C] + "21 58 ??" // and [eax+08], ebx + "89 50 ??" // mov [eax+0C], edx +}; + +} // namespace + +void BL1Hook::find_gobjects(void) { + auto gobjects_ptr = read_offset(GOBJECTS_SIG.sigscan_nullable()); + LOG(MISC, "GObjects: {:p}", reinterpret_cast(gobjects_ptr)); + + gobjects_wrapper = GObjects(gobjects_ptr); +} + +const GObjects& BL1Hook::gobjects(void) const { + return gobjects_wrapper; +}; + +namespace { + +GNames gnames_wrapper{}; + +const constinit Pattern<21> GNAMES_SIG{ + "A1 {????????}" // mov eax, [01FB4DA8] + "8B 0C ??" // mov ecx, [eax+esi*4] + "68 ????????" // push 00001000 + "6A ??" // push 00 + "E8 ????????" // call 005C21F0 + "5E" // pop esi +}; + +} // namespace + +void BL1Hook::find_gnames(void) { + auto gnames_ptr = read_offset(GNAMES_SIG.sigscan_nullable()); + LOG(MISC, "GNames: {:p}", reinterpret_cast(gnames_ptr)); + + gnames_wrapper = GNames(gnames_ptr); +} + +const GNames& BL1Hook::gnames(void) const { + return gnames_wrapper; +} + +} // namespace unrealsdk::game + +#endif diff --git a/src/unrealsdk/game/bl1/hexedits.cpp b/src/unrealsdk/game/bl1/hexedits.cpp new file mode 100644 index 0000000..5978bee --- /dev/null +++ b/src/unrealsdk/game/bl1/hexedits.cpp @@ -0,0 +1,94 @@ +#include "unrealsdk/pch.h" + +#include "unrealsdk/game/bl1/bl1.h" +#include "unrealsdk/memory.h" + +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW && !defined(UNREALSDK_IMPORTING) + +using namespace unrealsdk::memory; + +namespace unrealsdk::game { + +namespace { + +const constinit Pattern<32> SET_COMMAND_SIG{ + "85 C0" // test eax, eax + "{?? ??}" // jne 0087EC57 + "8D 4C 24 ??" // lea ecx, [esp+18] + "68 ????????" // push 01B167C0 + "51" // push ecx + "E8 ????????" // call 005C2FD0 + "83 C4 08" // add esp, 08 + "85 C0" // test eax, eax + "74 ??" // je 0087EC80 + "39 9E ????????" // cmp [esi+000003E0], ebx +}; + +const constinit Pattern<32> ARRAY_LIMIT_SIG{ + "6A 64" // push 64 + "50" // push eax + "46" // inc esi + "{?? ????????}" // call MIN <--- + "83 C4 08" // add esp, 08 + "3B F0" // cmp esi, eax + "0F8C ????????" // jl 005E8F03 + "8B 7F ??" // mov edi, [edi+04] + "83 FF 64" // cmp edi, 64 + "{???? ????????}" // jl DONT_PRINT_MSG <--- +}; +const constexpr auto ARRAY_LIMIT_MESSAGE_OFFSET_FROM_MIN = 5 + 3 + 2 + 6 + 3 + 3; +const constexpr auto ARRAY_LIMIT_UNLOCK_SIZE = ARRAY_LIMIT_MESSAGE_OFFSET_FROM_MIN + 2; + +} // namespace + +void BL1Hook::hexedit_set_command(void) { + auto* set_command_msg = SET_COMMAND_SIG.sigscan_nullable(); + + if (set_command_msg == nullptr) { + LOG(ERROR, "Failed to find set command message signature."); + return; + } + + LOG(INFO, "Set Command: {:p}", reinterpret_cast(set_command_msg)); + + // NOLINTBEGIN(readability-magic-numbers) + unlock_range(set_command_msg, 2); + set_command_msg[0] = 0x90; + set_command_msg[1] = 0x90; + // NOLINTEND(readability-magic-numbers) +} + +void BL1Hook::hexedit_array_limit(void) { + auto array_limit = ARRAY_LIMIT_SIG.sigscan_nullable(); + if (array_limit == nullptr) { + LOG(ERROR, "Couldn't find array limit signature"); + return; + } + + LOG(MISC, "Array Limit: {:p}", reinterpret_cast(array_limit)); + unlock_range(array_limit, ARRAY_LIMIT_UNLOCK_SIZE); + + // To patch the array limit, we simply NOP out the call to min, so it always uses the full array + // size. Luckily, this means we don't need to do any stack work. + + // NOLINTBEGIN(readability-magic-numbers) + array_limit[0] = 0x90; + array_limit[1] = 0x90; + array_limit[2] = 0x90; + array_limit[3] = 0x90; + array_limit[4] = 0x90; + // NOLINTEND(readability-magic-numbers) + + // Then for the message, we're patching the JL with a NOP then a JMP w/ 4 byte offset + // Doing it this way means we can reuse the offset from the original JL + + auto array_limit_msg = array_limit + ARRAY_LIMIT_MESSAGE_OFFSET_FROM_MIN; + // NOLINTBEGIN(readability-magic-numbers) + array_limit_msg[0] = 0x90; + array_limit_msg[1] = 0xE9; + // NOLINTEND(readability-magic-numbers) +} + +} // namespace unrealsdk::game + +#endif diff --git a/src/unrealsdk/game/bl1/hooks.cpp b/src/unrealsdk/game/bl1/hooks.cpp new file mode 100644 index 0000000..7650c21 --- /dev/null +++ b/src/unrealsdk/game/bl1/hooks.cpp @@ -0,0 +1,222 @@ +#include "unrealsdk/pch.h" + +#include "unrealsdk/game/bl1/bl1.h" + +#include "unrealsdk/hook_manager.h" +#include "unrealsdk/locks.h" +#include "unrealsdk/memory.h" +#include "unrealsdk/unreal/classes/ufunction.h" +#include "unrealsdk/unreal/structs/fframe.h" +#include "unrealsdk/unreal/wrappers/wrapped_struct.h" + +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW && !defined(UNREALSDK_IMPORTING) + +using namespace unrealsdk::memory; +using namespace unrealsdk::unreal; + +namespace unrealsdk::game { + +namespace { + +// NOLINTNEXTLINE(modernize-use-using) +typedef void(__fastcall* process_event_func)(UObject* obj, + void* /*edx*/, + UFunction* func, + void* params, + void* /*null*/); +process_event_func process_event_ptr; + +const constinit Pattern<30> PROCESS_EVENT_SIG{ + "55" // push ebp + "8B EC" // mov ebp, esp + "6A FF" // push -01 + "68 ????????" // push 018E55E8 + "64 A1 ????????" // mov eax, fs:[00000000] + "50" // push eax + "83 EC 40" // sub esp, 40 + "A1 ????????" // mov eax, [01F131C0] + "33 C5" // xor eax, ebp + "89 45 ??" // mov [ebp-10], eax +}; + +void __fastcall process_event_hook(UObject* obj, + void* edx, + UFunction* func, + void* params, + void* null) { + try { + // This arg seems to be in the process of being deprecated, no usage in ghidra, always seems + // to be null, and it's gone in later ue versions. Gathering some extra info just in case. + if (null != nullptr) { + LOG(DEV_WARNING, L"Null param had a value in process event during func {} on obj {}", + func->get_path_name(), obj->get_path_name()); + } + + auto data = hook_manager::impl::preprocess_hook(L"ProcessEvent", func, obj); + if (data != nullptr) { + // Copy args so that hooks can't modify them, for parity with call function + const WrappedStruct args_base{func, params}; + WrappedStruct args = args_base.copy_params_only(); + hook_manager::Details hook{.obj = obj, + .args = &args, + .ret = {func->find_return_param()}, + .func = {.func = func, .object = obj}}; + + const bool block_execution = run_hooks_of_type(data, hook_manager::Type::PRE, hook); + + if (!block_execution) { + process_event_ptr(obj, edx, func, params, null); + } + + if (hook.ret.has_value()) { + hook.ret.copy_to(reinterpret_cast(params)); + } + + if (!has_post_hooks(data)) { + return; + } + + if (hook.ret.prop != nullptr && !hook.ret.has_value() && !block_execution) { + hook.ret.copy_from(reinterpret_cast(params)); + } + + if (!block_execution) { + run_hooks_of_type(data, hook_manager::Type::POST, hook); + } + + run_hooks_of_type(data, hook_manager::Type::POST_UNCONDITIONAL, hook); + + return; + } + } catch (const std::exception& ex) { + LOG(ERROR, "An exception occurred during the ProcessEvent hook: {}", ex.what()); + } + + process_event_ptr(obj, edx, func, params, null); +} + +void __fastcall locking_process_event_hook(UObject* obj, + void* edx, + UFunction* func, + void* params, + void* null) { + const locks::FunctionCall lock{}; + process_event_hook(obj, edx, func, params, null); +} + +static_assert(std::is_same_v, + "process_event signature is incorrect"); +static_assert(std::is_same_v, + "process_event signature is incorrect"); + +} // namespace + +void BL1Hook::hook_process_event(void) { + detour(PROCESS_EVENT_SIG, + locks::FunctionCall::enabled() ? locking_process_event_hook : process_event_hook, + &process_event_ptr, "ProcessEvent"); +} + +void BL1Hook::process_event(UObject* object, UFunction* func, void* params) const { + locking_process_event_hook(object, nullptr, func, params, nullptr); +} + +namespace { + +// NOLINTNEXTLINE(modernize-use-using) +typedef void(__fastcall* call_function_func)(UObject* obj, + void* /*edx*/, + FFrame* stack, + void* params, + UFunction* func); +call_function_func call_function_ptr; + +const constinit Pattern<31> CALL_FUNCTION_SIG{ + "55" // push ebp + "8D AC 24 ????????" // lea ebp, [esp-00000404] + "81 EC ????????" // sub esp, 00000404 + "6A FF" // push -01 + "68 ????????" // push 018E55B8 + "64 A1 ????????" // mov eax, fs:[00000000] + "50" // push eax + "83 EC 40" // sub esp, 40 +}; + +void __fastcall call_function_hook(UObject* obj, + void* edx, + FFrame* stack, + void* result, + UFunction* func) { + try { + auto data = hook_manager::impl::preprocess_hook(L"CallFunction", func, obj); + if (data != nullptr) { + WrappedStruct args{func}; + auto original_code = stack->extract_current_args(args); + + hook_manager::Details hook{.obj = obj, + .args = &args, + .ret = {func->find_return_param()}, + .func = {.func = func, .object = obj}}; + + const bool block_execution = run_hooks_of_type(data, hook_manager::Type::PRE, hook); + + if (block_execution) { + stack->Code++; + } else { + stack->Code = original_code; + call_function_ptr(obj, edx, stack, result, func); + } + + if (hook.ret.has_value()) { + // Result is a pointer directly to where the property should go, remove the offset + hook.ret.copy_to(reinterpret_cast(result) + - hook.ret.prop->Offset_Internal()); + } + + if (!has_post_hooks(data)) { + return; + } + + if (hook.ret.prop != nullptr && !hook.ret.has_value() && !block_execution) { + hook.ret.copy_from(reinterpret_cast(result) + - hook.ret.prop->Offset_Internal()); + } + + if (!block_execution) { + run_hooks_of_type(data, hook_manager::Type::POST, hook); + } + + run_hooks_of_type(data, hook_manager::Type::POST_UNCONDITIONAL, hook); + + return; + } + } catch (const std::exception& ex) { + LOG(ERROR, "An exception occurred during the CallFunction hook: {}", ex.what()); + } + + call_function_ptr(obj, edx, stack, result, func); +} + +void __fastcall locking_call_function_hook(UObject* obj, + void* edx, + FFrame* stack, + void* result, + UFunction* func) { + const locks::FunctionCall lock{}; + call_function_hook(obj, edx, stack, result, func); +} + +static_assert(std::is_same_v, + "call_function signature is incorrect"); + +} // namespace + +void BL1Hook::hook_call_function(void) { + detour(CALL_FUNCTION_SIG, + locks::FunctionCall::enabled() ? locking_call_function_hook : call_function_hook, + &call_function_ptr, "CallFunction"); +} + +} // namespace unrealsdk::game + +#endif diff --git a/src/unrealsdk/game/bl1/memory.cpp b/src/unrealsdk/game/bl1/memory.cpp new file mode 100644 index 0000000..5eabfad --- /dev/null +++ b/src/unrealsdk/game/bl1/memory.cpp @@ -0,0 +1,85 @@ +#include "unrealsdk/pch.h" + +#include "unrealsdk/game/bl1/bl1.h" +#include "unrealsdk/memory.h" +#include "unrealsdk/unreal/alignment.h" + +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW && !defined(UNREALSDK_IMPORTING) + +using namespace unrealsdk::memory; +using namespace unrealsdk::unreal; + +namespace unrealsdk::game { + +namespace { + +#if defined(__MINGW32__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wattributes" // thiscall on non-class +#endif + +struct FMalloc; +struct FMallocVFtable { + void* exec; + void*(__thiscall* u_malloc)(FMalloc* self, uint32_t len, uint32_t align); + void*(__thiscall* u_realloc)(FMalloc* self, void* original, uint32_t len, uint32_t align); + void*(__thiscall* u_free)(FMalloc* self, void* data); +}; +struct FMalloc { + FMallocVFtable* vftable; +}; + +#if defined(__MINGW32__) +#pragma GCC diagnostic pop +#endif + +FMalloc** gmalloc_ptr; + +const constinit Pattern<8> GMALLOC_PATTERN{ + "89 0D {????????}" // mov [01F703F4],ecx { (05AA6980) } + "8B 11" // mov edx,[ecx] +}; + +} // namespace + +void BL1Hook::find_gmalloc(void) { + gmalloc_ptr = read_offset(GMALLOC_PATTERN.sigscan_nullable()); + LOG(MISC, "GMalloc*: {:p}", reinterpret_cast(gmalloc_ptr)); + + if (gmalloc_ptr != nullptr) { + LOG(MISC, "GMalloc: {:p}", reinterpret_cast(*gmalloc_ptr)); + } +} + +void* BL1Hook::u_malloc(size_t len) const { + if (gmalloc_ptr == nullptr) { + throw std::runtime_error("tried allocating memory while gmalloc was still null!"); + } + auto gmalloc = *gmalloc_ptr; + + auto ret = gmalloc->vftable->u_malloc(gmalloc, len, get_malloc_alignment(len)); + memset(ret, 0, len); + return ret; +} + +void* BL1Hook::u_realloc(void* original, size_t len) const { + if (gmalloc_ptr == nullptr) { + throw std::runtime_error("tried allocating memory while gmalloc was still null!"); + } + auto gmalloc = *gmalloc_ptr; + + return gmalloc->vftable->u_realloc(gmalloc, original, len, get_malloc_alignment(len)); +} + +void BL1Hook::u_free(void* data) const { + if (gmalloc_ptr == nullptr) { + throw std::runtime_error("tried allocating memory while gmalloc was still null!"); + } + auto gmalloc = *gmalloc_ptr; + + gmalloc->vftable->u_free(gmalloc, data); +} + +} // namespace unrealsdk::game + +#endif diff --git a/src/unrealsdk/game/bl1/object.cpp b/src/unrealsdk/game/bl1/object.cpp new file mode 100644 index 0000000..2d0d54a --- /dev/null +++ b/src/unrealsdk/game/bl1/object.cpp @@ -0,0 +1,187 @@ +#include "unrealsdk/pch.h" + +#include "unrealsdk/game/bl1/bl1.h" +#include "unrealsdk/logging.h" +#include "unrealsdk/memory.h" +#include "unrealsdk/unreal/structs/fstring.h" + +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW && !defined(UNREALSDK_IMPORTING) + +using namespace unrealsdk::unreal; +using namespace unrealsdk::memory; + +namespace unrealsdk::game { + +#pragma region ConstructObject + +namespace { + +// NOLINTNEXTLINE(modernize-use-using) +typedef UObject*(__cdecl* construct_obj_func)(UClass* cls, + UObject* outer, + FName name, + uint64_t flags, + UObject* template_obj, + void* error_output_device, + void* instance_graph, + uint32_t assume_template_is_archetype); +construct_obj_func construct_obj_ptr; + +const constinit Pattern<47> CONSTRUCT_OBJECT_PATTERN{ + "6A FF" // push -01 + "68 ????????" // push 018E9573 + "64 A1 ????????" // mov eax, fs:[00000000] + "50" // push eax + "83 EC 0C" // sub esp, 0C + "53" // push ebx + "55" // push ebp + "56" // push esi + "57" // push edi + "A1 ????????" // mov eax, [01F131C0] + "33 C4" // xor eax, esp + "50" // push eax + "8D 44 24 ??" // lea eax, [esp+20] + "64 A3 ????????" // mov fs:[00000000], eax + "8B 6C 24 ??" // mov ebp, [esp+54] + "89 6C 24 ??" // mov [esp+14], ebp +}; + +} // namespace + +void BL1Hook::find_construct_object(void) { + construct_obj_ptr = CONSTRUCT_OBJECT_PATTERN.sigscan_nullable(); + LOG(MISC, "StaticConstructObject: {:p}", reinterpret_cast(construct_obj_ptr)); +} + +UObject* BL1Hook::construct_object(UClass* cls, + UObject* outer, + const FName& name, + uint64_t flags, + UObject* template_obj) const { + return construct_obj_ptr(cls, outer, name, flags, template_obj, nullptr, nullptr, + 0 /* false */); +} + +#pragma endregion + +#pragma region PathName + +namespace { + +#if defined(__MINGW32__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wattributes" // thiscall on non-class +#endif + +// NOLINTNEXTLINE(modernize-use-using) +typedef void(__thiscall* get_path_name_func)(const UObject* self, + const UObject* stop_outer, + ManagedFString* str); + +#if defined(__MINGW32__) +#pragma GCC diagnostic pop +#endif + +get_path_name_func get_path_name_ptr; + +const constinit Pattern<13> GET_PATH_NAME_PATTERN{ + "8B 44 24 ??" // mov eax, [esp+04] + "56" // push esi + "8B F1" // mov esi, ecx + "3B F0" // cmp esi, eax + "74 ??" // je 005D09D1 + "85 F6" // test esi, esi +}; + +} // namespace + +void BL1Hook::find_get_path_name(void) { + get_path_name_ptr = GET_PATH_NAME_PATTERN.sigscan_nullable(); + LOG(MISC, "GetPathName: {:p}", reinterpret_cast(get_path_name_ptr)); +} + +std::wstring BL1Hook::uobject_path_name(const UObject* obj) const { + ManagedFString str{}; + get_path_name_ptr(obj, nullptr, &str); + return str; +} + +#pragma endregion + +#pragma region FindObject + +namespace { + +// NOLINTNEXTLINE(modernize-use-using) +typedef UObject*(__cdecl* static_find_object_func)(const UClass* cls, + const UObject* package, + const wchar_t* str, + uint32_t exact_class); + +static_find_object_func static_find_object_ptr; +const constinit Pattern<47> STATIC_FIND_OBJECT_PATTERN{ + "6A FF" // push -01 + "68 ????????" // push 018E7FF0 + "64 A1 ????????" // mov eax, fs:[00000000] + "50" // push eax + "83 EC 24" // sub esp, 24 + "53" // push ebx + "55" // push ebp + "56" // push esi + "57" // push edi + "A1 ????????" // mov eax, [01F131C0] + "33 C4" // xor eax, esp + "50" // push eax + "8D 44 24 ??" // lea eax, [esp+38] + "64 A3 ????????" // mov fs:[00000000], eax + "8B 74 24 ??" // mov esi, [esp+4C] + "8B 7C 24 ??" // mov edi, [esp+50] +}; + +} // namespace + +void BL1Hook::find_static_find_object(void) { + static_find_object_ptr = STATIC_FIND_OBJECT_PATTERN.sigscan_nullable(); + LOG(MISC, "StaticFindObject: {:p}", reinterpret_cast(static_find_object_ptr)); +} + +UObject* BL1Hook::find_object(UClass* cls, const std::wstring& name) const { + return static_find_object_ptr(cls, nullptr, name.c_str(), 0 /* false */); +} + +#pragma endregion + +#pragma region LoadPackage + +namespace { + +using load_package_func = UObject* (*)(const UObject* outer, const wchar_t* name, uint32_t flags); +load_package_func load_package_ptr; + +const constinit Pattern<21> LOAD_PACKAGE_PATTERN{ + "55" // push ebp + "8B EC" // mov ebp, esp + "6A FF" // push -01 + "68 ????????" // push 018E8C90 + "64 A1 ????????" // mov eax, fs:[00000000] + "50" // push eax + "83 EC 28" // sub esp, 28 + "53" // push ebx +}; + +} // namespace + +void BL1Hook::find_load_package(void) { + load_package_ptr = LOAD_PACKAGE_PATTERN.sigscan_nullable(); + LOG(MISC, "LoadPackage: {:p}", reinterpret_cast(load_package_ptr)); +} + +[[nodiscard]] UObject* BL1Hook::load_package(const std::wstring& name, uint32_t flags) const { + return load_package_ptr(nullptr, name.data(), flags); +} + +#pragma endregion + +} // namespace unrealsdk::game + +#endif diff --git a/src/unrealsdk/game/bl1/offsets.cpp b/src/unrealsdk/game/bl1/offsets.cpp new file mode 100644 index 0000000..96282b3 --- /dev/null +++ b/src/unrealsdk/game/bl1/offsets.cpp @@ -0,0 +1,25 @@ +#include "unrealsdk/pch.h" +#include "unrealsdk/game/bl1/offsets.h" +#include "unrealsdk/game/bl1/bl1.h" +#include "unrealsdk/unreal/classes/ufield.h" +#include "unrealsdk/unreal/offset_list.h" + +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW && !defined(UNREALSDK_IMPORTING) + +using namespace unrealsdk::unreal::offsets; + +namespace unrealsdk::game { +namespace bl1 { +namespace { + +constexpr auto OFFSETS = OFFSET_LIST_FROM_NAMESPACE(); + +} +} // namespace bl1 + +[[nodiscard]] const unreal::offsets::OffsetList& BL1Hook::get_offsets(void) const { + return bl1::OFFSETS; +} + +} // namespace unrealsdk::game +#endif diff --git a/src/unrealsdk/game/bl1/offsets.h b/src/unrealsdk/game/bl1/offsets.h new file mode 100644 index 0000000..85f4579 --- /dev/null +++ b/src/unrealsdk/game/bl1/offsets.h @@ -0,0 +1,169 @@ +#ifndef UNREALSDK_GAME_BL1_OFFSETS_H +#define UNREALSDK_GAME_BL1_OFFSETS_H + +#include "unrealsdk/pch.h" +#include "unrealsdk/game/bl2/offsets.h" +#include "unrealsdk/unreal/classes/properties/attribute_property.h" +#include "unrealsdk/unreal/classes/properties/persistent_object_ptr_property.h" +#include "unrealsdk/unreal/classes/properties/uarrayproperty.h" +#include "unrealsdk/unreal/classes/properties/ubyteproperty.h" +#include "unrealsdk/unreal/classes/properties/uclassproperty.h" +#include "unrealsdk/unreal/classes/properties/udelegateproperty.h" +#include "unrealsdk/unreal/classes/properties/uenumproperty.h" +#include "unrealsdk/unreal/classes/properties/uinterfaceproperty.h" +#include "unrealsdk/unreal/classes/properties/umulticastdelegateproperty.h" +#include "unrealsdk/unreal/classes/properties/uobjectproperty.h" +#include "unrealsdk/unreal/classes/properties/ustructproperty.h" +#include "unrealsdk/unreal/classes/uconst.h" +#include "unrealsdk/unreal/classes/uscriptstruct.h" +#include "unrealsdk/unreal/offsets.h" +#include "unrealsdk/unreal/structs/fname.h" + +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW + +namespace unrealsdk::unreal { + +struct FImplementedInterface; + +} + +namespace unrealsdk::game::bl1 { + +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW +#pragma pack(push, 0x4) +#endif +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-private-field" +#endif +// NOLINTBEGIN(cppcoreguidelines-pro-type-member-init, +// readability-identifier-naming, +// readability-magic-numbers) + +class UStruct; +class UClass; + +class UObject { + private: + uintptr_t* vftable; + + public: + int32_t InternalIndex; + uint64_t ObjectFlags; + + private: + void* HashNext; + void* HashOuterNext; + void* StateFrame; + UObject* _Linker; + void* _LinkerIndex; + int32_t NetIndex; + + public: + UObject* Outer; + unreal::FName Name; + UClass* Class; + UObject* ObjectArchetype; +}; + +class UField : public UObject { + public: + // Superfield is considered to be part of UStruct in the rest of the sdk + // Luckily, we can just expose it here and it'll all get picked up right + UStruct* SuperField; + UField* Next; +}; + +class UProperty : public UField { + public: + int32_t ArrayDim; + int32_t ElementSize; + uint32_t PropertyFlags; + + private: + uint8_t UnknownData00[0x14]; + + public: + int32_t Offset_Internal; + UProperty* PropertyLinkNext; + + private: + uint8_t UnknownData01[0x20]; +}; + +class UStruct : public UField { + private: + uint8_t UnknownData00[0x08]; + + public: + UField* Children; + uint16_t PropertySize; + + private: + uint8_t UnknownData01[0x1C + 0x02]; + + public: + UProperty* PropertyLink; + + private: + uint8_t UnknownData02[0x10]; + + public: + unreal::TArray ScriptObjectReferences; + + private: + uint8_t UnknownData03[0x04]; +}; + +class UClass : public UStruct { + private: + uint8_t UnknownData00[0xC0]; + + public: + UObject* ClassDefaultObject; + + private: + uint8_t UnknownData01[0x48]; + + public: + unreal::TArray Interfaces; +}; + +using UScriptStruct = unreal::offsets::generic::UScriptStruct; +using UFunction = bl2::generic::UFunction; +using UConst = unreal::offsets::generic::UConst; +using UEnum = bl2::generic::UEnum; + +using UArrayProperty = unreal::offsets::generic::UArrayProperty; +using UBoolProperty = bl2::generic::UBoolProperty; +using UByteProperty = unreal::offsets::generic::UByteProperty; +using UDelegateProperty = unreal::offsets::generic::UDelegateProperty; +using UEnumProperty = unreal::offsets::generic::UEnumProperty; +class UFloatProperty : public UProperty {}; +using UInterfaceProperty = unreal::offsets::generic::UInterfaceProperty; +class UIntProperty : public UProperty {}; +using UMulticastDelegateProperty = unreal::offsets::generic::UMulticastDelegateProperty; +using UObjectProperty = unreal::offsets::generic::UObjectProperty; +using UStructProperty = unreal::offsets::generic::UStructProperty; + +using UByteAttributeProperty = unreal::offsets::generic::GenericAttributeProperty; +using UClassProperty = unreal::offsets::generic::UClassProperty; +using UFloatAttributeProperty = unreal::offsets::generic::GenericAttributeProperty; +using UIntAttributeProperty = unreal::offsets::generic::GenericAttributeProperty; +using USoftClassProperty = unreal::offsets::generic::USoftClassProperty; + +// NOLINTEND(cppcoreguidelines-pro-type-member-init, +// readability-identifier-naming, +// readability-magic-numbers) +#if defined(__clang__) +#pragma clang diagnostic pop +#endif +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW +#pragma pack(pop) +#endif + +} // namespace unrealsdk::game::bl1 + +#endif + +#endif /* UNREALSDK_GAME_BL1_OFFSETS_H */ diff --git a/src/unrealsdk/game/bl1/persistentobjectptr.cpp b/src/unrealsdk/game/bl1/persistentobjectptr.cpp new file mode 100644 index 0000000..ed3021b --- /dev/null +++ b/src/unrealsdk/game/bl1/persistentobjectptr.cpp @@ -0,0 +1,22 @@ +#include "unrealsdk/pch.h" + +#include "unrealsdk/game/bl1/bl1.h" +#include "unrealsdk/version_error.h" + +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW && !defined(UNREALSDK_IMPORTING) + +namespace unrealsdk::game { + +void BL1Hook::fsoftobjectptr_assign(unreal::FSoftObjectPtr* /* ptr */, + const unreal::UObject* /* obj */) const { + throw_version_error("Soft object pointers are not implemented in UE3"); +} + +void BL1Hook::flazyobjectptr_assign(unreal::FLazyObjectPtr* /* ptr */, + const unreal::UObject* /* obj */) const { + throw_version_error("Lazy object pointers are not implemented in UE3"); +} + +} // namespace unrealsdk::game + +#endif diff --git a/src/unrealsdk/game/bl2/antidebug.cpp b/src/unrealsdk/game/bl2/antidebug.cpp index 4bbe1bf..563ccf3 100644 --- a/src/unrealsdk/game/bl2/antidebug.cpp +++ b/src/unrealsdk/game/bl2/antidebug.cpp @@ -2,7 +2,7 @@ #include "unrealsdk/game/bl2/bl2.h" -#if defined(UE3) && defined(ARCH_X86) && !defined(UNREALSDK_IMPORTING) +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW && !defined(UNREALSDK_IMPORTING) namespace unrealsdk::game { diff --git a/src/unrealsdk/game/bl2/bl2.cpp b/src/unrealsdk/game/bl2/bl2.cpp index 38a5afb..6ea61fc 100644 --- a/src/unrealsdk/game/bl2/bl2.cpp +++ b/src/unrealsdk/game/bl2/bl2.cpp @@ -9,7 +9,7 @@ #include "unrealsdk/unreal/structs/ftext.h" #include "unrealsdk/version_error.h" -#if defined(UE3) && defined(ARCH_X86) && !defined(UNREALSDK_IMPORTING) +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW && !defined(UNREALSDK_IMPORTING) using namespace unrealsdk::memory; using namespace unrealsdk::unreal; diff --git a/src/unrealsdk/game/bl2/bl2.h b/src/unrealsdk/game/bl2/bl2.h index 2e0ac4a..43eb892 100644 --- a/src/unrealsdk/game/bl2/bl2.h +++ b/src/unrealsdk/game/bl2/bl2.h @@ -6,7 +6,7 @@ #include "unrealsdk/game/abstract_hook.h" #include "unrealsdk/game/selector.h" -#if defined(UE3) && defined(ARCH_X86) && !defined(UNREALSDK_IMPORTING) +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW && !defined(UNREALSDK_IMPORTING) namespace unrealsdk::game { @@ -109,7 +109,7 @@ class BL2Hook : public AbstractHook { [[nodiscard]] unreal::UObject* construct_object(unreal::UClass* cls, unreal::UObject* outer, const unreal::FName& name, - decltype(unreal::UObject::ObjectFlags) flags, + uint64_t flags, unreal::UObject* template_obj) const override; [[nodiscard]] unreal::UObject* find_object(unreal::UClass* cls, const std::wstring& name) const override; @@ -129,6 +129,7 @@ class BL2Hook : public AbstractHook { const unreal::UObject* obj) const override; void flazyobjectptr_assign(unreal::FLazyObjectPtr* ptr, const unreal::UObject* obj) const override; + [[nodiscard]] const unreal::offsets::OffsetList& get_offsets(void) const override; }; template <> diff --git a/src/unrealsdk/game/bl2/console.cpp b/src/unrealsdk/game/bl2/console.cpp index c661417..aa1fb76 100644 --- a/src/unrealsdk/game/bl2/console.cpp +++ b/src/unrealsdk/game/bl2/console.cpp @@ -21,7 +21,7 @@ #include "unrealsdk/unreal/wrappers/wrapped_struct.h" #include "unrealsdk/unrealsdk.h" -#if defined(UE3) && defined(ARCH_X86) && !defined(UNREALSDK_IMPORTING) +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW && !defined(UNREALSDK_IMPORTING) using namespace unrealsdk::unreal; @@ -74,7 +74,7 @@ bool say_bypass_hook(hook_manager::Details& hook) { */ static const auto console_command_func = - hook.obj->Class->find_func_and_validate(L"ConsoleCommand"_fn); + hook.obj->Class()->find_func_and_validate(L"ConsoleCommand"_fn); static const auto command_property = hook.args->type->find_prop_and_validate(L"Command"_fn); @@ -111,9 +111,9 @@ bool say_crash_fix_hook(hook_manager::Details& hook) { static const auto engine = unrealsdk::find_object(L"WillowGameEngine", L"Transient.WillowGameEngine_0"); static const auto spark_interface_prop = - engine->Class->find_prop_and_validate(L"SparkInterface"_fn); + engine->Class()->find_prop_and_validate(L"SparkInterface"_fn); static const auto is_spark_enabled_func = - spark_interface_prop->get_interface_class()->find_func_and_validate(L"IsSparkEnabled"_fn); + spark_interface_prop->InterfaceClass()->find_func_and_validate(L"IsSparkEnabled"_fn); // Check if we're online, if so allow normal processing if (BoundFunction{.func = is_spark_enabled_func, @@ -123,11 +123,11 @@ bool say_crash_fix_hook(hook_manager::Details& hook) { } static const auto get_timestamp_string_func = - hook.obj->Class->find_func_and_validate(L"GetTimestampString"_fn); + hook.obj->Class()->find_func_and_validate(L"GetTimestampString"_fn); static const auto default_save_game_manager = find_class(L"WillowSaveGameManager"_fn)->ClassDefaultObject(); static const auto time_format_prop = - default_save_game_manager->Class->find_prop_and_validate(L"TimeFormat"_fn); + default_save_game_manager->Class()->find_prop_and_validate(L"TimeFormat"_fn); auto timestamp = BoundFunction{.func = get_timestamp_string_func, .object = hook.obj} .call( @@ -136,7 +136,7 @@ bool say_crash_fix_hook(hook_manager::Details& hook) { static const auto pri_prop = hook.args->type->find_prop_and_validate(L"PRI"_fn); static const auto player_name_prop = - pri_prop->get_property_class()->find_prop_and_validate(L"PlayerName"_fn); + pri_prop->PropertyClass()->find_prop_and_validate(L"PlayerName"_fn); auto player_name = hook.args->get(pri_prop)->get(player_name_prop); @@ -145,7 +145,7 @@ bool say_crash_fix_hook(hook_manager::Details& hook) { player_name += timestamp; static const auto add_chat_message_internal_func = - hook.obj->Class->find_func_and_validate(L"AddChatMessageInternal"_fn); + hook.obj->Class()->find_func_and_validate(L"AddChatMessageInternal"_fn); static const auto msg_prop = hook.args->type->find_prop_and_validate(L"msg"_fn); BoundFunction{.func = add_chat_message_internal_func, .object = hook.obj} @@ -159,23 +159,23 @@ bool console_command_hook(hook_manager::Details& hook) { hook.args->type->find_prop_and_validate(L"Command"_fn); static const auto history_prop = - hook.obj->Class->find_prop_and_validate(L"History"_fn); + hook.obj->Class()->find_prop_and_validate(L"History"_fn); static const auto history_top_prop = - hook.obj->Class->find_prop_and_validate(L"HistoryTop"_fn); + hook.obj->Class()->find_prop_and_validate(L"HistoryTop"_fn); static const auto history_bot_prop = - hook.obj->Class->find_prop_and_validate(L"HistoryBot"_fn); + hook.obj->Class()->find_prop_and_validate(L"HistoryBot"_fn); static const auto history_cur_prop = - hook.obj->Class->find_prop_and_validate(L"HistoryCur"_fn); + hook.obj->Class()->find_prop_and_validate(L"HistoryCur"_fn); static const UFunction* purge_command_func = - hook.obj->Class->find_func_and_validate(L"PurgeCommandFromHistory"_fn); + hook.obj->Class()->find_func_and_validate(L"PurgeCommandFromHistory"_fn); static const UFunction* save_config_func = - hook.obj->Class->find_func_and_validate(L"SaveConfig"_fn); + hook.obj->Class()->find_func_and_validate(L"SaveConfig"_fn); auto line = hook.args->get(command_property); - auto [callback, cmd_len] = commands::impl::find_matching_command(line); - if (callback == nullptr) { + // This hook only runs when input via console, it is direct user input + if (!commands::impl::is_command_valid(line, true)) { return false; } @@ -196,7 +196,7 @@ bool console_command_hook(hook_manager::Details& hook) { hook.obj->set(history_prop, history_top, line); // Increment top - history_top = (history_top + 1) % history_prop->ArrayDim; + history_top = (history_top + 1) % history_prop->ArrayDim(); hook.obj->set(history_top_prop, history_top); // And set current hook.obj->set(history_cur_prop, history_top); @@ -205,7 +205,7 @@ bool console_command_hook(hook_manager::Details& hook) { auto history_bot = hook.obj->get(history_bot_prop); if ((history_bot == -1) || history_bot == history_top) { hook.obj->set(history_bot_prop, - (history_bot + 1) % history_prop->ArrayDim); + (history_bot + 1) % history_prop->ArrayDim()); } hook.obj->get(save_config_func).call(); @@ -227,12 +227,12 @@ bool console_command_hook(hook_manager::Details& hook) { as a compromise just use the LOG macro on the lowest possible log level, and assume the lowest people practically set their console log level to is dev warning. */ - auto msg = unrealsdk::fmt::format(L">>> {} <<<", line); + auto msg = std::format(L">>> {} <<<", line); console_output_text.call(msg); LOG(MIN, L"{}", msg); try { - callback->operator()(line.c_str(), line.size(), cmd_len); + commands::impl::run_command(line); } catch (const std::exception& ex) { LOG(ERROR, "An exception occurred while running a console command: {}", ex.what()); } @@ -246,15 +246,15 @@ bool pc_console_command_hook(hook_manager::Details& hook) { auto line = hook.args->get(command_property); - auto [callback, cmd_len] = commands::impl::find_matching_command(line); - if (callback == nullptr) { + // Conversely, this hook is explicitly not from console + if (!commands::impl::is_command_valid(line, false)) { return false; } // This hook does not go to console, so there's no extra processing to be done, we can just run // the callback immediately try { - callback->operator()(line.c_str(), line.size(), cmd_len); + commands::impl::run_command(line); } catch (const std::exception& ex) { LOG(ERROR, "An exception occurred while running a console command: {}", ex.what()); } diff --git a/src/unrealsdk/game/bl2/globals.cpp b/src/unrealsdk/game/bl2/globals.cpp index 076f863..dc911cd 100644 --- a/src/unrealsdk/game/bl2/globals.cpp +++ b/src/unrealsdk/game/bl2/globals.cpp @@ -5,7 +5,7 @@ #include "unrealsdk/unreal/wrappers/gnames.h" #include "unrealsdk/unreal/wrappers/gobjects.h" -#if defined(UE3) && defined(ARCH_X86) && !defined(UNREALSDK_IMPORTING) +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW && !defined(UNREALSDK_IMPORTING) using namespace unrealsdk::memory; using namespace unrealsdk::unreal; diff --git a/src/unrealsdk/game/bl2/hexedits.cpp b/src/unrealsdk/game/bl2/hexedits.cpp index 947970f..eb4c6b6 100644 --- a/src/unrealsdk/game/bl2/hexedits.cpp +++ b/src/unrealsdk/game/bl2/hexedits.cpp @@ -3,7 +3,7 @@ #include "unrealsdk/game/bl2/bl2.h" #include "unrealsdk/memory.h" -#if defined(UE3) && defined(ARCH_X86) && !defined(UNREALSDK_IMPORTING) +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW && !defined(UNREALSDK_IMPORTING) using namespace unrealsdk::memory; diff --git a/src/unrealsdk/game/bl2/hooks.cpp b/src/unrealsdk/game/bl2/hooks.cpp index 75ab223..cbaf5e3 100644 --- a/src/unrealsdk/game/bl2/hooks.cpp +++ b/src/unrealsdk/game/bl2/hooks.cpp @@ -16,7 +16,7 @@ #include "unrealsdk/unreal/wrappers/wrapped_struct.h" #include "unrealsdk/unrealsdk.h" -#if defined(UE3) && defined(ARCH_X86) && !defined(UNREALSDK_IMPORTING) +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW && !defined(UNREALSDK_IMPORTING) using namespace unrealsdk::memory; using namespace unrealsdk::unreal; @@ -79,7 +79,7 @@ void __fastcall process_event_hook(UObject* obj, .func = {.func = func, .object = obj}}; const bool block_execution = - hook_manager::impl::run_hooks_of_type(*data, hook_manager::Type::PRE, hook); + hook_manager::impl::run_hooks_of_type(data, hook_manager::Type::PRE, hook); if (!block_execution) { process_event_ptr(obj, edx, func, params, null); @@ -89,7 +89,7 @@ void __fastcall process_event_hook(UObject* obj, hook.ret.copy_to(reinterpret_cast(params)); } - if (!hook_manager::impl::has_post_hooks(*data)) { + if (!hook_manager::impl::has_post_hooks(data)) { return; } @@ -98,10 +98,10 @@ void __fastcall process_event_hook(UObject* obj, } if (!block_execution) { - hook_manager::impl::run_hooks_of_type(*data, hook_manager::Type::POST, hook); + hook_manager::impl::run_hooks_of_type(data, hook_manager::Type::POST, hook); } - hook_manager::impl::run_hooks_of_type(*data, hook_manager::Type::POST_UNCONDITIONAL, + hook_manager::impl::run_hooks_of_type(data, hook_manager::Type::POST_UNCONDITIONAL, hook); return; @@ -191,7 +191,7 @@ void __fastcall call_function_hook(UObject* obj, .func = {.func = func, .object = obj}}; const bool block_execution = - hook_manager::impl::run_hooks_of_type(*data, hook_manager::Type::PRE, hook); + hook_manager::impl::run_hooks_of_type(data, hook_manager::Type::PRE, hook); if (block_execution) { stack->Code++; @@ -203,23 +203,23 @@ void __fastcall call_function_hook(UObject* obj, if (hook.ret.has_value()) { // Result is a pointer directly to where the property should go, remove the offset hook.ret.copy_to(reinterpret_cast(result) - - hook.ret.prop->Offset_Internal); + - hook.ret.prop->Offset_Internal()); } - if (!hook_manager::impl::has_post_hooks(*data)) { + if (!hook_manager::impl::has_post_hooks(data)) { return; } if (hook.ret.prop != nullptr && !hook.ret.has_value() && !block_execution) { hook.ret.copy_from(reinterpret_cast(result) - - hook.ret.prop->Offset_Internal); + - hook.ret.prop->Offset_Internal()); } if (!block_execution) { - hook_manager::impl::run_hooks_of_type(*data, hook_manager::Type::POST, hook); + hook_manager::impl::run_hooks_of_type(data, hook_manager::Type::POST, hook); } - hook_manager::impl::run_hooks_of_type(*data, hook_manager::Type::POST_UNCONDITIONAL, + hook_manager::impl::run_hooks_of_type(data, hook_manager::Type::POST_UNCONDITIONAL, hook); return; diff --git a/src/unrealsdk/game/bl2/memory.cpp b/src/unrealsdk/game/bl2/memory.cpp index 14c2540..e15b7c9 100644 --- a/src/unrealsdk/game/bl2/memory.cpp +++ b/src/unrealsdk/game/bl2/memory.cpp @@ -4,7 +4,7 @@ #include "unrealsdk/memory.h" #include "unrealsdk/unreal/alignment.h" -#if defined(UE3) && defined(ARCH_X86) && !defined(UNREALSDK_IMPORTING) +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW && !defined(UNREALSDK_IMPORTING) using namespace unrealsdk::memory; using namespace unrealsdk::unreal; diff --git a/src/unrealsdk/game/bl2/object.cpp b/src/unrealsdk/game/bl2/object.cpp index 419aa63..e52de5a 100644 --- a/src/unrealsdk/game/bl2/object.cpp +++ b/src/unrealsdk/game/bl2/object.cpp @@ -11,7 +11,7 @@ #include "unrealsdk/unreal/structs/fstring.h" #include "unrealsdk/unreal/wrappers/bound_function.h" -#if defined(UE3) && defined(ARCH_X86) && !defined(UNREALSDK_IMPORTING) +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW && !defined(UNREALSDK_IMPORTING) using namespace unrealsdk::unreal; using namespace unrealsdk::memory; @@ -63,7 +63,7 @@ void BL2Hook::find_construct_object(void) { UObject* BL2Hook::construct_object(UClass* cls, UObject* outer, const FName& name, - decltype(UObject::ObjectFlags) flags, + uint64_t flags, UObject* template_obj) const { return construct_obj_ptr(cls, outer, name, flags, template_obj, nullptr, nullptr, 0 /* false */); diff --git a/src/unrealsdk/game/bl2/offsets.cpp b/src/unrealsdk/game/bl2/offsets.cpp new file mode 100644 index 0000000..bedc88c --- /dev/null +++ b/src/unrealsdk/game/bl2/offsets.cpp @@ -0,0 +1,25 @@ +#include "unrealsdk/pch.h" +#include "unrealsdk/game/bl2/offsets.h" +#include "unrealsdk/game/bl2/bl2.h" +#include "unrealsdk/unreal/classes/ufield.h" +#include "unrealsdk/unreal/offset_list.h" + +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW && !defined(UNREALSDK_IMPORTING) + +using namespace unrealsdk::unreal::offsets; + +namespace unrealsdk::game { +namespace bl2 { +namespace { + +constexpr auto OFFSETS = OFFSET_LIST_FROM_NAMESPACE(); + +} +} // namespace bl2 + +[[nodiscard]] const unreal::offsets::OffsetList& BL2Hook::get_offsets(void) const { + return bl2::OFFSETS; +} + +} // namespace unrealsdk::game +#endif diff --git a/src/unrealsdk/game/bl2/offsets.h b/src/unrealsdk/game/bl2/offsets.h new file mode 100644 index 0000000..74ef1c7 --- /dev/null +++ b/src/unrealsdk/game/bl2/offsets.h @@ -0,0 +1,215 @@ +#ifndef UNREALSDK_GAME_BL2_OFFSETS_H +#define UNREALSDK_GAME_BL2_OFFSETS_H + +#include "unrealsdk/pch.h" +#include "unrealsdk/unreal/classes/properties/attribute_property.h" +#include "unrealsdk/unreal/classes/properties/persistent_object_ptr_property.h" +#include "unrealsdk/unreal/classes/properties/uarrayproperty.h" +#include "unrealsdk/unreal/classes/properties/ubyteproperty.h" +#include "unrealsdk/unreal/classes/properties/uclassproperty.h" +#include "unrealsdk/unreal/classes/properties/udelegateproperty.h" +#include "unrealsdk/unreal/classes/properties/uenumproperty.h" +#include "unrealsdk/unreal/classes/properties/uinterfaceproperty.h" +#include "unrealsdk/unreal/classes/properties/umulticastdelegateproperty.h" +#include "unrealsdk/unreal/classes/properties/uobjectproperty.h" +#include "unrealsdk/unreal/classes/properties/ustructproperty.h" +#include "unrealsdk/unreal/classes/uconst.h" +#include "unrealsdk/unreal/classes/uenum.h" +#include "unrealsdk/unreal/classes/ufield.h" +#include "unrealsdk/unreal/classes/uscriptstruct.h" +#include "unrealsdk/unreal/offsets.h" +#include "unrealsdk/unreal/structs/fname.h" +#include "unrealsdk/unreal/structs/tarray.h" + +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW + +namespace unrealsdk::unreal { + +struct FImplementedInterface; + +} + +namespace unrealsdk::game::bl2 { + +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW +#pragma pack(push, 0x4) +#endif +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-private-field" +#endif +// NOLINTBEGIN(cppcoreguidelines-pro-type-member-init, +// readability-identifier-naming, +// readability-magic-numbers) + +class UClass; +class UProperty; + +namespace generic { + +// This is generic just so that we can reuse it in bl1+tps, but swap out the type of Class +template +class UObject { + private: + uintptr_t* vftable; + void* HashNext; + + public: + uint64_t ObjectFlags; + + private: + void* HashOuterNext; + void* StateFrame; + UObject* _Linker; + void* _LinkerIndex; + + public: + int32_t InternalIndex; + + private: + int32_t NetIndex; + + public: + UObject* Outer; + unreal::FName Name; + UClass* Class; + + private: + UObject* ObjectArchetype; +}; + +template +class UFunction : public T { + public: + uint32_t FunctionFlags; + + private: + uint16_t iNative; + uint16_t RepOffset; + unreal::FName FriendlyName; + uint8_t OperPrecedence; + + public: + uint8_t NumParams; + uint16_t ParamsSize; + uint16_t ReturnValueOffset; + + private: + uint8_t UnknownData00[0x6]; + void* Func; +}; + +template +class UEnum : public T { + public: + unreal::TArray Names; +}; + +template +class UBoolProperty : public T { + public: + uint32_t FieldMask; +}; + +} // namespace generic + +using UObject = bl2::generic::UObject; +using UField = unreal::offsets::generic::UField; + +class UProperty : public UField { + public: + int32_t ArrayDim; + int32_t ElementSize; + uint32_t PropertyFlags; + + private: + uint8_t UnknownData00[0x14]; + + public: + int32_t Offset_Internal; + unreal::UProperty* PropertyLinkNext; + + private: + uint8_t UnknownData01[0x18]; +}; + +class UStruct : public UField { + private: + uint8_t UnknownData00[0x8]; + + public: + UStruct* SuperField; + UField* Children; + uint16_t PropertySize; + + private: + uint8_t UnknownData01[0x1A]; + + public: + UProperty* PropertyLink; + + private: + uint8_t UnknownData02[0x10]; + + unreal::TArray ScriptObjectReferences; +}; + +class UClass : public UStruct { + // Misc Fields I found within this block in BL2, but which we really don't care about + + // 0xE8: TArray ClassReps; + // 0xF4: TArray NetFields; + // 0x100: TArray HideCategories; + // 0x10C: TArray AutoExpandCategories; + + private: + uint8_t UnknownData00[0xCC]; + + public: + UObject* ClassDefaultObject; + + private: + uint8_t UnknownData01[0x48]; + + public: + unreal::TArray Interfaces; +}; + +using UScriptStruct = unreal::offsets::generic::UScriptStruct; +using UFunction = bl2::generic::UFunction; +using UConst = unreal::offsets::generic::UConst; +using UEnum = bl2::generic::UEnum; + +using UArrayProperty = unreal::offsets::generic::UArrayProperty; +using UBoolProperty = bl2::generic::UBoolProperty; +using UByteProperty = unreal::offsets::generic::UByteProperty; +using UDelegateProperty = unreal::offsets::generic::UDelegateProperty; +using UEnumProperty = unreal::offsets::generic::UEnumProperty; +class UFloatProperty : public UProperty {}; +using UInterfaceProperty = unreal::offsets::generic::UInterfaceProperty; +class UIntProperty : public UProperty {}; +using UMulticastDelegateProperty = unreal::offsets::generic::UMulticastDelegateProperty; +using UObjectProperty = unreal::offsets::generic::UObjectProperty; +using UStructProperty = unreal::offsets::generic::UStructProperty; + +using UByteAttributeProperty = unreal::offsets::generic::GenericAttributeProperty; +using UClassProperty = unreal::offsets::generic::UClassProperty; +using UFloatAttributeProperty = unreal::offsets::generic::GenericAttributeProperty; +using UIntAttributeProperty = unreal::offsets::generic::GenericAttributeProperty; +using USoftClassProperty = unreal::offsets::generic::USoftClassProperty; + +// NOLINTEND(cppcoreguidelines-pro-type-member-init, +// readability-identifier-naming, +// readability-magic-numbers) +#if defined(__clang__) +#pragma clang diagnostic pop +#endif +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW +#pragma pack(pop) +#endif + +} // namespace unrealsdk::game::bl2 + +#endif + +#endif /* UNREALSDK_GAME_BL2_OFFSETS_H */ diff --git a/src/unrealsdk/game/bl2/persistentobjectptr.cpp b/src/unrealsdk/game/bl2/persistentobjectptr.cpp index e453e24..6140ce5 100644 --- a/src/unrealsdk/game/bl2/persistentobjectptr.cpp +++ b/src/unrealsdk/game/bl2/persistentobjectptr.cpp @@ -2,7 +2,7 @@ #include "unrealsdk/game/bl2/bl2.h" #include "unrealsdk/version_error.h" -#if defined(UE3) && defined(ARCH_X86) && !defined(UNREALSDK_IMPORTING) +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW && !defined(UNREALSDK_IMPORTING) namespace unrealsdk::game { diff --git a/src/unrealsdk/game/bl3/bl3.cpp b/src/unrealsdk/game/bl3/bl3.cpp index a1cf683..c1de42d 100644 --- a/src/unrealsdk/game/bl3/bl3.cpp +++ b/src/unrealsdk/game/bl3/bl3.cpp @@ -7,7 +7,7 @@ #include "unrealsdk/unreal/structs/fstring.h" #include "unrealsdk/unreal/structs/ftext.h" -#if defined(UE4) && defined(ARCH_X64) && !defined(UNREALSDK_IMPORTING) +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_OAK && !defined(UNREALSDK_IMPORTING) using namespace unrealsdk::unreal; using namespace unrealsdk::memory; diff --git a/src/unrealsdk/game/bl3/bl3.h b/src/unrealsdk/game/bl3/bl3.h index ceb6762..04ca85c 100644 --- a/src/unrealsdk/game/bl3/bl3.h +++ b/src/unrealsdk/game/bl3/bl3.h @@ -6,7 +6,7 @@ #include "unrealsdk/game/abstract_hook.h" #include "unrealsdk/game/selector.h" -#if defined(UE4) && defined(ARCH_X64) && !defined(UNREALSDK_IMPORTING) +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_OAK && !defined(UNREALSDK_IMPORTING) namespace unrealsdk::game { @@ -98,7 +98,7 @@ class BL3Hook : public AbstractHook { [[nodiscard]] unreal::UObject* construct_object(unreal::UClass* cls, unreal::UObject* outer, const unreal::FName& name, - decltype(unreal::UObject::ObjectFlags) flags, + uint64_t flags, unreal::UObject* template_obj) const override; [[nodiscard]] unreal::UObject* find_object(unreal::UClass* cls, const std::wstring& name) const override; @@ -118,6 +118,7 @@ class BL3Hook : public AbstractHook { const unreal::UObject* obj) const override; void flazyobjectptr_assign(unreal::FLazyObjectPtr* ptr, const unreal::UObject* obj) const override; + [[nodiscard]] const unreal::offsets::OffsetList& get_offsets(void) const override; }; template <> diff --git a/src/unrealsdk/game/bl3/console.cpp b/src/unrealsdk/game/bl3/console.cpp index d773bfa..74a0ad2 100644 --- a/src/unrealsdk/game/bl3/console.cpp +++ b/src/unrealsdk/game/bl3/console.cpp @@ -22,7 +22,7 @@ #include "unrealsdk/unreal/wrappers/wrapped_struct.h" #include "unrealsdk/unrealsdk.h" -#if defined(UE4) && defined(ARCH_X64) && !defined(UNREALSDK_IMPORTING) +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_OAK && !defined(UNREALSDK_IMPORTING) using namespace unrealsdk::unreal; @@ -61,8 +61,10 @@ void console_command_hook(UObject* console_obj, UnmanagedFString* raw_line) { try { std::wstring line = *raw_line; - auto [callback, cmd_len] = commands::impl::find_matching_command(line); - if (callback != nullptr) { + // Since this is a hook on UConsole, and since the game doesn't seem to be running it's own + // commands through it, just always consider it direct user input + // I don't really know what interface automated commands would come through, if any + if (commands::impl::is_command_valid(line, true)) { // Update the history buffer { // History buffer is oldest at index 0, newest at count @@ -103,7 +105,7 @@ void console_command_hook(UObject* console_obj, UnmanagedFString* raw_line) { // anything, just lower the count if (dropped_idx != (history_size - 1)) { auto data = reinterpret_cast(history_buffer.base->data); - auto element_size = history_buffer.type->ElementSize; + auto element_size = history_buffer.type->ElementSize(); auto dest = data + (dropped_idx * element_size); auto remaining_size = (history_size - dropped_idx) * element_size; @@ -137,12 +139,12 @@ void console_command_hook(UObject* console_obj, UnmanagedFString* raw_line) { useful, so as a compromise just use the LOG macro on the lowest possible log level, and assume the lowest people practically set their console log level to is dev warning. */ - auto msg = unrealsdk::fmt::format(L">>> {} <<<", line); + auto msg = std::format(L">>> {} <<<", line); static_uconsole_output_text(msg); LOG(MIN, L"{}", msg); try { - callback->operator()(line.c_str(), line.size(), cmd_len); + commands::impl::run_command(line); } catch (const std::exception& ex) { LOG(ERROR, "An exception occurred while running a console command: {}", ex.what()); } @@ -163,12 +165,12 @@ bool inject_console_hook(hook_manager::Details& hook) { auto local_player = hook.obj->get(L"Player"_fn); auto viewport = local_player->get(L"ViewportClient"_fn); auto console_property = - viewport->Class->find_prop_and_validate(L"ViewportConsole"_fn); + viewport->Class()->find_prop_and_validate(L"ViewportConsole"_fn); console = viewport->get(console_property); if (console == nullptr) { - auto default_console = console_property->get_property_class()->ClassDefaultObject(); - console = unrealsdk::construct_object(default_console->Class, default_console->Outer); + auto default_console = console_property->PropertyClass()->ClassDefaultObject(); + console = unrealsdk::construct_object(default_console->Class(), default_console->Outer()); viewport->set(L"ViewportConsole"_fn, console); } @@ -187,7 +189,7 @@ bool inject_console_hook(hook_manager::Details& hook) { // just search through gobjects for the default object ¯\_(ツ)_/¯ auto input_settings_fn = L"InputSettings"_fn; for (const auto& inner_obj : gobjects()) { - if (inner_obj->Class->Name != input_settings_fn) { + if (inner_obj->Class()->Name() != input_settings_fn) { continue; } diff --git a/src/unrealsdk/game/bl3/globals.cpp b/src/unrealsdk/game/bl3/globals.cpp index 4491a73..47cc8f9 100644 --- a/src/unrealsdk/game/bl3/globals.cpp +++ b/src/unrealsdk/game/bl3/globals.cpp @@ -5,7 +5,7 @@ #include "unrealsdk/unreal/wrappers/gnames.h" #include "unrealsdk/unreal/wrappers/gobjects.h" -#if defined(UE4) && defined(ARCH_X64) && !defined(UNREALSDK_IMPORTING) +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_OAK && !defined(UNREALSDK_IMPORTING) using namespace unrealsdk::unreal; using namespace unrealsdk::memory; diff --git a/src/unrealsdk/game/bl3/hooks.cpp b/src/unrealsdk/game/bl3/hooks.cpp index 7797540..c66233e 100644 --- a/src/unrealsdk/game/bl3/hooks.cpp +++ b/src/unrealsdk/game/bl3/hooks.cpp @@ -12,7 +12,7 @@ #include "unrealsdk/unreal/wrappers/unreal_pointer_funcs.h" #include "unrealsdk/unreal/wrappers/wrapped_struct.h" -#if defined(UE4) && defined(ARCH_X64) && !defined(UNREALSDK_IMPORTING) +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_OAK && !defined(UNREALSDK_IMPORTING) using namespace unrealsdk::unreal; using namespace unrealsdk::memory; @@ -48,7 +48,7 @@ void process_event_hook(UObject* obj, UFunction* func, void* params) { .func = {.func = func, .object = obj}}; const bool block_execution = - hook_manager::impl::run_hooks_of_type(*data, hook_manager::Type::PRE, hook); + hook_manager::impl::run_hooks_of_type(data, hook_manager::Type::PRE, hook); if (!block_execution) { process_event_ptr(obj, func, params); @@ -58,7 +58,7 @@ void process_event_hook(UObject* obj, UFunction* func, void* params) { hook.ret.copy_to(reinterpret_cast(params)); } - if (!hook_manager::impl::has_post_hooks(*data)) { + if (!hook_manager::impl::has_post_hooks(data)) { return; } @@ -67,10 +67,10 @@ void process_event_hook(UObject* obj, UFunction* func, void* params) { } if (!block_execution) { - hook_manager::impl::run_hooks_of_type(*data, hook_manager::Type::POST, hook); + hook_manager::impl::run_hooks_of_type(data, hook_manager::Type::POST, hook); } - hook_manager::impl::run_hooks_of_type(*data, hook_manager::Type::POST_UNCONDITIONAL, + hook_manager::impl::run_hooks_of_type(data, hook_manager::Type::POST_UNCONDITIONAL, hook); return; @@ -161,7 +161,7 @@ void call_function_hook(UObject* obj, FFrame* stack, void* result, UFunction* fu .func = {.func = func, .object = obj}}; const bool block_execution = - hook_manager::impl::run_hooks_of_type(*data, hook_manager::Type::PRE, hook); + hook_manager::impl::run_hooks_of_type(data, hook_manager::Type::PRE, hook); if (block_execution) { stack->Code++; @@ -173,23 +173,23 @@ void call_function_hook(UObject* obj, FFrame* stack, void* result, UFunction* fu if (hook.ret.has_value()) { // Result is a pointer directly to where the property should go, remove the offset hook.ret.copy_to(reinterpret_cast(result) - - hook.ret.prop->Offset_Internal); + - hook.ret.prop->Offset_Internal()); } - if (!hook_manager::impl::has_post_hooks(*data)) { + if (!hook_manager::impl::has_post_hooks(data)) { return; } if (hook.ret.prop != nullptr && !hook.ret.has_value() && !block_execution) { hook.ret.copy_from(reinterpret_cast(result) - - hook.ret.prop->Offset_Internal); + - hook.ret.prop->Offset_Internal()); } if (!block_execution) { - hook_manager::impl::run_hooks_of_type(*data, hook_manager::Type::POST, hook); + hook_manager::impl::run_hooks_of_type(data, hook_manager::Type::POST, hook); } - hook_manager::impl::run_hooks_of_type(*data, hook_manager::Type::POST_UNCONDITIONAL, + hook_manager::impl::run_hooks_of_type(data, hook_manager::Type::POST_UNCONDITIONAL, hook); return; diff --git a/src/unrealsdk/game/bl3/memory.cpp b/src/unrealsdk/game/bl3/memory.cpp index 6b7f1a0..c92c119 100644 --- a/src/unrealsdk/game/bl3/memory.cpp +++ b/src/unrealsdk/game/bl3/memory.cpp @@ -4,7 +4,7 @@ #include "unrealsdk/memory.h" #include "unrealsdk/unreal/alignment.h" -#if defined(UE4) && defined(ARCH_X64) && !defined(UNREALSDK_IMPORTING) +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_OAK && !defined(UNREALSDK_IMPORTING) using namespace unrealsdk::memory; using namespace unrealsdk::unreal; diff --git a/src/unrealsdk/game/bl3/object.cpp b/src/unrealsdk/game/bl3/object.cpp index 3f26aaf..99c8888 100644 --- a/src/unrealsdk/game/bl3/object.cpp +++ b/src/unrealsdk/game/bl3/object.cpp @@ -6,7 +6,7 @@ #include "unrealsdk/unreal/structs/fname.h" #include "unrealsdk/unreal/structs/fstring.h" -#if defined(UE4) && defined(ARCH_X64) && !defined(UNREALSDK_IMPORTING) +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_OAK && !defined(UNREALSDK_IMPORTING) using namespace unrealsdk::memory; using namespace unrealsdk::unreal; @@ -55,8 +55,11 @@ void BL3Hook::find_construct_object(void) { UObject* BL3Hook::construct_object(UClass* cls, UObject* outer, const FName& name, - decltype(UObject::ObjectFlags) flags, + uint64_t flags, UObject* template_obj) const { + if (flags > std::numeric_limits::max()) { + throw std::out_of_range("construct_object flags out of range, only 32-bits are supported"); + } return construct_obj_ptr(cls, outer, name, flags, 0, template_obj, 0 /* false */, nullptr, 0 /* false */); } diff --git a/src/unrealsdk/game/bl3/offsets.cpp b/src/unrealsdk/game/bl3/offsets.cpp new file mode 100644 index 0000000..af0b0e0 --- /dev/null +++ b/src/unrealsdk/game/bl3/offsets.cpp @@ -0,0 +1,26 @@ +#include "unrealsdk/pch.h" +#include "unrealsdk/game/bl3/offsets.h" +#include "unrealsdk/game/bl3/bl3.h" +#include "unrealsdk/unreal/classes/ufield.h" +#include "unrealsdk/unreal/offset_list.h" + +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_OAK && !defined(UNREALSDK_IMPORTING) + +using namespace unrealsdk::unreal::offsets; + +namespace unrealsdk::game { +namespace bl3 { +namespace { + +constexpr auto OFFSETS = OFFSET_LIST_FROM_NAMESPACE(); + +} + +} // namespace bl3 + +[[nodiscard]] const unreal::offsets::OffsetList& BL3Hook::get_offsets(void) const { + return bl3::OFFSETS; +} + +} // namespace unrealsdk::game +#endif diff --git a/src/unrealsdk/game/bl3/offsets.h b/src/unrealsdk/game/bl3/offsets.h new file mode 100644 index 0000000..eb9d77e --- /dev/null +++ b/src/unrealsdk/game/bl3/offsets.h @@ -0,0 +1,190 @@ +#ifndef UNREALSDK_GAME_BL3_OFFSETS_H +#define UNREALSDK_GAME_BL3_OFFSETS_H + +#include "unrealsdk/pch.h" +#include "unrealsdk/unreal/classes/properties/attribute_property.h" +#include "unrealsdk/unreal/classes/properties/persistent_object_ptr_property.h" +#include "unrealsdk/unreal/classes/properties/uarrayproperty.h" +#include "unrealsdk/unreal/classes/properties/ubyteproperty.h" +#include "unrealsdk/unreal/classes/properties/uclassproperty.h" +#include "unrealsdk/unreal/classes/properties/udelegateproperty.h" +#include "unrealsdk/unreal/classes/properties/uenumproperty.h" +#include "unrealsdk/unreal/classes/properties/uinterfaceproperty.h" +#include "unrealsdk/unreal/classes/properties/umulticastdelegateproperty.h" +#include "unrealsdk/unreal/classes/properties/uobjectproperty.h" +#include "unrealsdk/unreal/classes/properties/ustructproperty.h" +#include "unrealsdk/unreal/classes/uconst.h" +#include "unrealsdk/unreal/classes/uenum.h" +#include "unrealsdk/unreal/classes/ufield.h" +#include "unrealsdk/unreal/classes/uscriptstruct.h" +#include "unrealsdk/unreal/offsets.h" +#include "unrealsdk/unreal/structs/fname.h" +#include "unrealsdk/unreal/structs/fstring.h" +#include "unrealsdk/unreal/structs/tarray.h" +#include "unrealsdk/unreal/structs/tpair.h" + +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_OAK + +namespace unrealsdk::unreal { + +struct FImplementedInterface; + +} // namespace unrealsdk::unreal + +namespace unrealsdk::game::bl3 { + +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-private-field" +#endif +// NOLINTBEGIN(cppcoreguidelines-pro-type-member-init, +// readability-identifier-naming, +// readability-magic-numbers) + +class UClass; + +class UObject { + private: + uintptr_t* vftable; + + public: + int32_t ObjectFlags; + int32_t InternalIndex; + UClass* Class; + unreal::FName Name; + UObject* Outer; +}; + +using UField = unreal::offsets::generic::UField; + +class UProperty : public UField { + public: + int32_t ArrayDim; + int32_t ElementSize; + uint64_t PropertyFlags; + + private: + uint16_t RepIndex; + uint8_t BlueprintReplicationCondition; + + public: + int32_t Offset_Internal; + + private: + unreal::FName RepNotifyFunc; + + public: + UProperty* PropertyLinkNext; + + private: + UProperty* NextRef; + UProperty* DestructorLinkNext; + UProperty* PostConstructLinkNext; +}; + +class UStruct : public UField { + friend class unreal::UStruct; + + public: + UStruct* SuperField; + UField* Children; + int32_t PropertySize; + + private: + int32_t MinAlignment; + unreal::TArray Script; + + public: + UProperty* PropertyLink; + + private: + UProperty* RefLink; + UProperty* DestructorLink; + UProperty* PostConstructLink; + unreal::TArray ScriptObjectReferences; +}; + +class UClass : public UStruct { + private: + uint8_t UnknownData00[0x70]; + + public: + UObject* ClassDefaultObject; + + private: + uint8_t UnknownData01[0xA0]; + + public: + unreal::TArray Interfaces; +}; + +using UScriptStruct = unreal::offsets::generic::UScriptStruct; + +class UFunction : public UStruct { + public: + uint32_t FunctionFlags; + uint8_t NumParams; + uint16_t ParamsSize; + uint16_t ReturnValueOffset; + + private: + uint16_t RPCId; + uint16_t RPCResponseId; + UProperty* FirstPropertyToInit; + UFunction* EventGraphFunction; + int32_t EventGraphCallOffset; + void* Func; +}; + +using UConst = unreal::offsets::generic::UConst; + +class UEnum : public UField { + private: + unreal::UnmanagedFString CppType; + + public: + unreal::TArray> Names; + + private: + int64_t CppForm; +}; + +using UArrayProperty = unreal::offsets::generic::UArrayProperty; +using UByteProperty = unreal::offsets::generic::UByteProperty; +using UDelegateProperty = unreal::offsets::generic::UDelegateProperty; +using UEnumProperty = unreal::offsets::generic::UEnumProperty; +class UFloatProperty : public UProperty {}; +using UInterfaceProperty = unreal::offsets::generic::UInterfaceProperty; +class UIntProperty : public UProperty {}; +using UMulticastDelegateProperty = unreal::offsets::generic::UMulticastDelegateProperty; +using UObjectProperty = unreal::offsets::generic::UObjectProperty; +using UStructProperty = unreal::offsets::generic::UStructProperty; + +using UByteAttributeProperty = unreal::offsets::generic::GenericAttributeProperty; +using UClassProperty = unreal::offsets::generic::UClassProperty; +using UFloatAttributeProperty = unreal::offsets::generic::GenericAttributeProperty; +using UIntAttributeProperty = unreal::offsets::generic::GenericAttributeProperty; +using USoftClassProperty = unreal::offsets::generic::USoftClassProperty; + +class UBoolProperty : public UProperty { + private: + uint8_t FieldSize; + uint8_t ByteOffset; + uint8_t ByteMask; + + public: + uint8_t FieldMask; +}; + +// NOLINTEND(cppcoreguidelines-pro-type-member-init, +// readability-identifier-naming, +// readability-magic-numbers) +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + +} // namespace unrealsdk::game::bl3 + +#endif + +#endif /* UNREALSDK_GAME_BL3_OFFSETS_H */ diff --git a/src/unrealsdk/game/bl3/persistentobjectptr.cpp b/src/unrealsdk/game/bl3/persistentobjectptr.cpp index 0f029be..ec7146d 100644 --- a/src/unrealsdk/game/bl3/persistentobjectptr.cpp +++ b/src/unrealsdk/game/bl3/persistentobjectptr.cpp @@ -10,7 +10,7 @@ #include "unrealsdk/unreal/wrappers/gobjects.h" #include "unrealsdk/unrealsdk.h" -#if defined(UE4) && defined(ARCH_X64) && !defined(UNREALSDK_IMPORTING) +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_OAK && !defined(UNREALSDK_IMPORTING) using namespace unrealsdk::unreal; using namespace unrealsdk::memory; diff --git a/src/unrealsdk/game/selector.cpp b/src/unrealsdk/game/selector.cpp index 0c9a609..a063d22 100644 --- a/src/unrealsdk/game/selector.cpp +++ b/src/unrealsdk/game/selector.cpp @@ -2,6 +2,7 @@ #include "unrealsdk/config.h" #include "unrealsdk/game/abstract_hook.h" +#include "unrealsdk/game/bl1/bl1.h" #include "unrealsdk/game/bl2/bl2.h" #include "unrealsdk/game/bl3/bl3.h" #include "unrealsdk/game/tps/tps.h" @@ -17,18 +18,12 @@ namespace { // Tuple of all hook types to consider. // The first matching hook will be used, order matters. -#ifdef ARCH_X64 -#ifdef UE4 +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW +using all_known_games = std::tuple; +#elif UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_OAK using all_known_games = std::tuple; #else -#error No known games for UE3 x64 -#endif -#else -#ifdef UE4 -#error No known games for UE4 x86 -#else -using all_known_games = std::tuple; -#endif +#error Unknown SDK flavour #endif /** diff --git a/src/unrealsdk/game/tps/hexedits.cpp b/src/unrealsdk/game/tps/hexedits.cpp index c8f658f..9ccde24 100644 --- a/src/unrealsdk/game/tps/hexedits.cpp +++ b/src/unrealsdk/game/tps/hexedits.cpp @@ -3,7 +3,7 @@ #include "unrealsdk/game/tps/tps.h" #include "unrealsdk/memory.h" -#if defined(UE3) && defined(ARCH_X86) && !defined(UNREALSDK_IMPORTING) +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW && !defined(UNREALSDK_IMPORTING) using namespace unrealsdk::memory; diff --git a/src/unrealsdk/game/tps/offsets.cpp b/src/unrealsdk/game/tps/offsets.cpp new file mode 100644 index 0000000..742abad --- /dev/null +++ b/src/unrealsdk/game/tps/offsets.cpp @@ -0,0 +1,24 @@ +#include "unrealsdk/pch.h" +#include "unrealsdk/game/tps/offsets.h" +#include "unrealsdk/game/tps/tps.h" +#include "unrealsdk/unreal/offset_list.h" + +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW && !defined(UNREALSDK_IMPORTING) + +using namespace unrealsdk::unreal::offsets; + +namespace unrealsdk::game { +namespace tps { +namespace { + +constexpr auto OFFSETS = OFFSET_LIST_FROM_NAMESPACE(); + +} +} // namespace tps + +[[nodiscard]] const unreal::offsets::OffsetList& TPSHook::get_offsets(void) const { + return tps::OFFSETS; +} + +} // namespace unrealsdk::game +#endif diff --git a/src/unrealsdk/game/tps/offsets.h b/src/unrealsdk/game/tps/offsets.h new file mode 100644 index 0000000..8e38102 --- /dev/null +++ b/src/unrealsdk/game/tps/offsets.h @@ -0,0 +1,117 @@ +#ifndef UNREALSDK_GAME_TPS_OFFSETS_H +#define UNREALSDK_GAME_TPS_OFFSETS_H + +#include "unrealsdk/pch.h" +#include "unrealsdk/game/bl2/offsets.h" +#include "unrealsdk/unreal/offsets.h" + +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW + +namespace unrealsdk::game::tps { + +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW +#pragma pack(push, 0x4) +#endif +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-private-field" +#endif +// NOLINTBEGIN(cppcoreguidelines-pro-type-member-init, +// readability-identifier-naming, +// readability-magic-numbers) + +class UClass; + +using UObject = bl2::generic::UObject; +class UField : public bl2::UField {}; + +class UProperty : public UField { + public: + int32_t ArrayDim; + int32_t ElementSize; + uint32_t PropertyFlags; + + private: + uint8_t UnknownData00[0x14]; + + public: + int32_t Offset_Internal; + unreal::UProperty* PropertyLinkNext; + + private: + uint8_t UnknownData01[0xC]; +}; + +class UStruct : public UField { + private: + uint8_t UnknownData00[0x8]; + + public: + UStruct* SuperField; + UField* Children; + uint16_t PropertySize; + + private: + uint8_t UnknownData01[0x1A]; + + public: + UProperty* PropertyLink; + + private: + uint8_t UnknownData02[0x4]; + + unreal::TArray ScriptObjectReferences; +}; + +class UClass : public UStruct { + private: + uint8_t UnknownData00[0xCC]; + + public: + UObject* ClassDefaultObject; + + private: + uint8_t UnknownData01[0x14]; + + public: + unreal::TArray Interfaces; +}; + +using UScriptStruct = unreal::offsets::generic::UScriptStruct; +using UFunction = bl2::generic::UFunction; +class UConst : public bl2::UConst {}; +class UEnum : public bl2::UEnum {}; + +using UArrayProperty = unreal::offsets::generic::UArrayProperty; +using UBoolProperty = bl2::generic::UBoolProperty; +using UByteProperty = unreal::offsets::generic::UByteProperty; +using UDelegateProperty = unreal::offsets::generic::UDelegateProperty; +using UEnumProperty = unreal::offsets::generic::UEnumProperty; +class UFloatProperty : public UProperty {}; +using UInterfaceProperty = unreal::offsets::generic::UInterfaceProperty; +class UIntProperty : public UProperty {}; +using UMulticastDelegateProperty = unreal::offsets::generic::UMulticastDelegateProperty; +using UObjectProperty = unreal::offsets::generic::UObjectProperty; +using UStructProperty = unreal::offsets::generic::UStructProperty; + +using UByteAttributeProperty = unreal::offsets::generic::GenericAttributeProperty; +using UClassProperty = unreal::offsets::generic::UClassProperty; +using UFloatAttributeProperty = unreal::offsets::generic::GenericAttributeProperty; +using UIntAttributeProperty = unreal::offsets::generic::GenericAttributeProperty; +using USoftClassProperty = unreal::offsets::generic::USoftClassProperty; + +// NOLINTEND(cppcoreguidelines-pro-type-member-init, +// readability-identifier-naming, +// readability-magic-numbers) +#if defined(__clang__) +#pragma clang diagnostic pop +#endif +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW +#pragma pack(pop) +#endif + +} // namespace unrealsdk::game::tps + +#endif + +#endif /* UNREALSDK_GAME_TPS_OFFSETS_H */ diff --git a/src/unrealsdk/game/tps/tps.cpp b/src/unrealsdk/game/tps/tps.cpp index 4b823ef..69531dd 100644 --- a/src/unrealsdk/game/tps/tps.cpp +++ b/src/unrealsdk/game/tps/tps.cpp @@ -3,7 +3,7 @@ #include "unrealsdk/game/tps/tps.h" #include "unrealsdk/unreal/structs/fname.h" -#if defined(UE3) && defined(ARCH_X86) && !defined(UNREALSDK_IMPORTING) +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW && !defined(UNREALSDK_IMPORTING) using namespace unrealsdk::unreal; diff --git a/src/unrealsdk/game/tps/tps.h b/src/unrealsdk/game/tps/tps.h index 4d2c3c2..c597155 100644 --- a/src/unrealsdk/game/tps/tps.h +++ b/src/unrealsdk/game/tps/tps.h @@ -3,7 +3,7 @@ #include "unrealsdk/pch.h" -#if defined(UE3) && defined(ARCH_X86) && !defined(UNREALSDK_IMPORTING) +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW && !defined(UNREALSDK_IMPORTING) #include "unrealsdk/game/bl2/bl2.h" #include "unrealsdk/game/selector.h" @@ -16,6 +16,7 @@ class TPSHook : public BL2Hook { public: void fname_init(unreal::FName* name, const wchar_t* str, int32_t number) const override; + [[nodiscard]] const unreal::offsets::OffsetList& get_offsets(void) const override; }; template <> diff --git a/src/unrealsdk/hook_manager.cpp b/src/unrealsdk/hook_manager.cpp index eab14d0..46577ad 100644 --- a/src/unrealsdk/hook_manager.cpp +++ b/src/unrealsdk/hook_manager.cpp @@ -16,84 +16,130 @@ using namespace unrealsdk::unreal; #ifndef UNREALSDK_IMPORTING namespace unrealsdk::hook_manager::impl { -namespace { - /* -We store all hooks in a multi layered mapping. - -The outermost level is the FName of the function, which lets us do a very quick compare to discard -most calls. The second level is the full path name string, what was passed in, which we use to -confirm we've got the right hook. - -`preprocess_hook` looks through these first two levels to determine if the called function has any -hooks, and returns the "List" of all hooks which apply to that function at any stage. - -The list then maps each hook type into "Group"s. Each group is a full set to hooks to run on a -specific stage. +The fact that hooks run arbitrary user provided callbacks means our data structure can get modified +in a number of awkward ways while we're in the middle of iterating over it. There's the obvious case +of a hook can remove itself, but also tricker ones like a hook can invoke a nested hook on itself, +and only remove itself there, or the upper layer hook could remove itself and the nested one could +re-add it, etc. It gets even messier if we consider threading. + +So the number one concern behind this design is robustness - we need to support essentially +arbitrary modification while we're in the middle of iterating through it. This means performance is +a secondary concern - though I still tried keeping it mind - I haven't done any benchmarking. + +The easiest way to keep our iterators valid is to use linked lists. However, using a bunch of +`std::list`s isn't the best for performance, as we'll discuss in a bit, since need several layers +of them, so instead we use a bunch of intrusive linked lists on our own type. + + +The most basic form of the data structure we want is essentially a: + map>>>> + +Matching FNames first lets us discard function calls far quicker than needing to do a string +comparison on the full name. After matching both of them, we then need to split by hook type, which +finally gets us the collection of callbacks to run. The identifiers have no influence when matching +hooks, they're only used when adding/removing them. + +The smallest unit we have to iterate over is the hooks of the same type on the same function - the +one labeled just "collection" above. This is the `next_in_collection` linked list. + +Then we need to be able to jump between the collections on the same hook, but of different type. The +heads of the collection linked lists form into a second `next_type` linked list. We use an intrusive +linked list, so the exact same node from the above list also holds the pointers for this one. + +Above that, we need to jump between groups of hooks which share the same fname, but have different +full function names. The heads of the type linked lists form the third `next_function` linked list. + +Finally, we need to iterate through FNames. We actually do this using a hash table - but we still +need to deal with collisions. The heads of the function linked lists form our fourth and final +`next_collision` linked list. + +Trying to roughly diagram an example, this is what might be in a single hash bucket: + +Collision | [A] -----------------------------> [F] + | : : +Function | [A] ---------------> [D] [F] -> [G] + | : : : : +Type | [A] --------> [C] : : : + | : : : : : +Collection | [A] -> [B] [C] [D] -> [E] [F] [G] + +[A] Class::Func, post-hook +[B] Class::Func, post-hook +[C] Class::Func, pre-hook +[D] OtherClass::Func +[E] OtherClass::Func +[F] ThirdClass::SomeOtherFunc, where `SomeOtherFunc` and `Func` happen to get a hash collision +[G] FourthClass::SomeOtherFunc + +Each column is a single node, a node may be in multiple linked lists. */ -using Group = utils::StringViewMap; - -} // namespace - -struct List { - Group pre; - Group post; - Group post_unconditional; - - /** - * @brief Checks if all groups in the list are empty. - * - * @return True if all groups are empty - */ - [[nodiscard]] bool empty(void) const { - return this->pre.empty() && this->post.empty() && this->post_unconditional.empty(); - } - - /** - * @brief Gets a hook group on this list from it's type. - * - * @param type The hook type to get. - * @return A reference to the selected hook group on this object, or nullptr if calling the safe - * version and the type's invalid. - */ - [[nodiscard]] Group& get_group_by_type(Type type) { - switch (type) { - case Type::PRE: - return this->pre; - case Type::POST: - return this->post; - case Type::POST_UNCONDITIONAL: - return this->post_unconditional; - default: - throw std::invalid_argument("Invalid hook type " + std::to_string((uint8_t)type)); - } - } - // We only need the const version in practice - [[nodiscard]] const Group* get_safe_group_by_type(Type type) const noexcept { - switch (type) { - case Type::PRE: - return &this->pre; - case Type::POST: - return &this->post; - case Type::POST_UNCONDITIONAL: - return &this->post_unconditional; - default: - return nullptr; +struct Node { + public: + FName fname; + std::wstring full_name; + Type type; + std::wstring identifier; + DLLSafeCallback* callback; + + // Using shared pointers because it's easy + // Since we use std::make_shared, we're not really wasting allocations, but as a future + // optimization we could use an intrusive reference count to avoid repeating all the control + // block pointers, and save a little memory. + std::shared_ptr next_collision = nullptr; + std::shared_ptr next_function = nullptr; + std::shared_ptr next_type = nullptr; + std::shared_ptr next_in_collection = nullptr; + + Node(FName fname, + std::wstring_view full_name, + Type type, + std::wstring_view identifier, + DLLSafeCallback* callback) + : fname(fname), + full_name(full_name), + type(type), + identifier(identifier), + callback(callback) {} + Node(const Node&) = default; + Node(Node&&) noexcept = default; + Node& operator=(const Node&) = default; + Node& operator=(Node&&) noexcept = default; + ~Node() { + if (this->callback != nullptr) { + this->callback->destroy(); + this->callback = nullptr; } } }; namespace { -thread_local bool should_inject_next_call = false; +const constexpr auto HASH_TABLE_SIZE = 0x1000; +std::array, HASH_TABLE_SIZE> hooks_hash_table; + +/** + * @brief Hashes the given fname, and returns which index of the table it goes in. + * + * @param name The name to check. + * @return The hash table index. + */ +size_t get_table_index(FName fname) { + static_assert(sizeof(unrealsdk::unreal::FName) == sizeof(uint64_t), + "FName is not same size as a uint64"); + uint64_t val{}; + memcpy(&val, &fname, sizeof(fname)); + + // Identity seems mostly good enough - FNames already are just a relatively small integer value. + // This throws away the number field, but that's very rarely used on functions to begin with. + return val % HASH_TABLE_SIZE; +} bool should_log_all_calls = false; std::wofstream log_all_calls_stream{}; std::mutex log_all_calls_stream_mutex{}; -std::unordered_map> hooks{}; - void log_all_calls(bool should_log) { // Only keep this file stream open while we need it if (should_log) { @@ -112,6 +158,8 @@ void log_all_calls(bool should_log) { } } +thread_local bool should_inject_next_call = false; + void inject_next_call(void) { should_inject_next_call = true; } @@ -136,80 +184,243 @@ bool add_hook(std::wstring_view func, DLLSafeCallback* callback) { auto fname = extract_func_obj_name(func); - auto& path_name_map = hooks[fname]; + auto hash_idx = get_table_index(fname); + auto node = hooks_hash_table.at(hash_idx); + if (node == nullptr) { + // This function isn't in the hash table, can just add directly. + hooks_hash_table.at(hash_idx) = + std::make_shared(fname, func, type, identifier, callback); + return true; + } - // Doing this a bit weirdly to try avoid allocating a new string - we can get via string view, - // but setting requires converting to a full string first - auto iter = path_name_map.find(func); - if (iter == path_name_map.end()) { - iter = path_name_map.emplace(func, List{}).first; + // Look through hash collisions + while (node->fname != fname) { + if (node->next_collision == nullptr) { + // We found a collision, but nothing matched our name, so add it to the end + node->next_collision = std::make_shared(fname, func, type, identifier, callback); + return true; + } + node = node->next_collision; } - auto& group = iter->second.get_group_by_type(type); - if (group.contains(identifier)) { - return false; + // Look though full function names + while (node->full_name != func) { + if (node->next_function == nullptr) { + // We found another function with the same fname, but nothing matches the full name + node->next_function = std::make_shared(fname, func, type, identifier, callback); + return true; + } + node = node->next_function; } - group.emplace(identifier, callback); - return true; + // Look through hook types + while (node->type != type) { + if (node->next_type == nullptr) { + // We found the right function, but it doesn't have any hooks of this type yet + node->next_type = std::make_shared(fname, func, type, identifier, callback); + return true; + } + node = node->next_type; + } + + // Look through all remaining hooks to see if we can match the identifier + while (node->identifier != identifier) { + if (node->next_in_collection == nullptr) { + // Didn't find a matching identifier, add our new hook at the end + node->next_in_collection = + std::make_shared(fname, func, type, identifier, callback); + } + node = node->next_in_collection; + } + + // We already have this identifier, can't insert + return false; } bool has_hook(std::wstring_view func, Type type, std::wstring_view identifier) { auto fname = extract_func_obj_name(func); - auto fname_iter = hooks.find(fname); - if (fname_iter == hooks.end()) { + auto hash_idx = get_table_index(fname); + auto node = hooks_hash_table.at(hash_idx); + if (node == nullptr) { + // This function isn't even in the hash table return false; } - auto& path_name_map = fname_iter->second; - auto path_name_iter = path_name_map.find(func); - if (path_name_iter == path_name_map.end()) { - return false; + // Look through hash collisions + while (node->fname != fname) { + if (node->next_collision == nullptr) { + // We found a collision, but nothing matched our name + return false; + } + node = node->next_collision; + } + + // Look though full function names + while (node->full_name != func) { + if (node->next_function == nullptr) { + // We found another function with the same fname, but nothing matches the full name + return false; + } + node = node->next_function; + } + + // Look through hook types + while (node->type != type) { + if (node->next_type == nullptr) { + // We found the right function, but it doesn't have any hooks of this type + return false; + } + node = node->next_type; } - return path_name_iter->second.get_group_by_type(type).contains(identifier); + // Look through all remaining hooks to see if we can match the identifier + while (node->identifier != identifier) { + if (node->next_in_collection == nullptr) { + // Didn't find a matching identifier + return false; + } + node = node->next_in_collection; + } + + return true; } bool remove_hook(std::wstring_view func, Type type, std::wstring_view identifier) { auto fname = extract_func_obj_name(func); - auto fname_iter = hooks.find(fname); - if (fname_iter == hooks.end()) { + auto hash_idx = get_table_index(fname); + auto node = hooks_hash_table.at(hash_idx); + if (node == nullptr) { + // This function isn't even in the hash table return false; } - auto& path_name_map = fname_iter->second; - auto path_name_iter = path_name_map.find(func); - if (path_name_iter == path_name_map.end()) { - return false; + // Look through hash collisions + decltype(node) prev_collision = nullptr; + while (node->fname != fname) { + if (node->next_collision == nullptr) { + // We found a collision, but nothing matched our name + return false; + } + prev_collision = node; + node = node->next_collision; } - auto& group = path_name_iter->second.get_group_by_type(type); - auto group_iter = group.find(identifier); - if (group_iter == group.end()) { - return false; + // Look though full function names + decltype(node) prev_function = nullptr; + while (node->full_name != func) { + if (node->next_function == nullptr) { + // We found another function with the same fname, but nothing matches the full name + return false; + } + prev_function = node; + node = node->next_function; } - group_iter->second->destroy(); - group.erase(group_iter); + // Look through hook types + decltype(node) prev_type = nullptr; + while (node->type != type) { + if (node->next_type == nullptr) { + // We found the right function, but it doesn't have any hooks of this type + return false; + } + prev_type = node; + node = node->next_type; + } + + // Look through all remaining hooks to see if we can match the identifier + decltype(node) prev_in_collection = nullptr; + while (node->identifier != identifier) { + if (node->next_in_collection == nullptr) { + // Didn't find a matching identifier + return false; + } + prev_in_collection = node; + node = node->next_in_collection; + } /* - Important Note: While it's tempting, we can't also erase the higher levels here if they're - empty, because we may be being called from inside a hook, since this may cause a use after free. + We found a matching hook - 'node' is pointing at it. + + Consider the following diagram the diagram from above: + + Collision | [A] -----------------------------> [F] + | : : + Function | [A] ---------------> [D] [F] + | : : : + Type | [A] : [F] --------> [H] + | : : : : + Collection | [A] -> [B] -> [C] [D] -> [E] [F] -> [G] [H] + + Lets say we want to remove B, D, and F. The new layout we need is: + + Collision | [A] ------------------------------------> [G] + | : : + Function | [A] ----------------------> [E] : + | : : : + Type | [A] : [G] -> [H] + | : : : : + Collection | [A] --------> [C] [E] [G] [H] + + Removing B is easy, we simply set A->next_in_collection = C to bypass it. + + For D, we need to work bottom up. If our node is the head of the list, we need to promote our + next node up a layer. If our node is also the head of the above layer, we need to recurse + another layer up. So since D is the head of both the collection and type linked lists, we move + up to the function linked list, and need to set A->next_function = E. + + F exposes a further complication on top of D, since it was pointing at multiple other nodes. As + part of promoting a node up a layer, we need to insert it into that layer's linked list - that's + how we keep then link to H. In D's case, these were all null. + + Since only the heads of the lower-layer lists are used in the higher layer ones, as soon as we + come across a layer where we're not the head, we must be done, the lower layer nodes cannot have + other references to upper ones. + */ - We make sure to take a copy of the group in `run_hooks_of_type`, so invalidating iterators - within it is not a concern. + if (prev_in_collection != nullptr) { + prev_in_collection->next_in_collection = node->next_in_collection; + return true; + } + if (node->next_in_collection) { + node->next_in_collection->next_type = node->next_type; + node->next_type = node->next_in_collection; + } - Instead, we clean up during `preprocess_hook` - */ + if (prev_type != nullptr) { + prev_type->next_type = node->next_type; + return true; + } + if (node->next_type != nullptr) { + node->next_type->next_function = node->next_function; + node->next_function = node->next_type; + } + if (prev_function != nullptr) { + prev_function->next_function = node->next_function; + return true; + } + if (node->next_function != nullptr) { + node->next_function->next_collision = node->next_collision; + node->next_collision = node->next_function; + } + + if (prev_collision != nullptr) { + prev_collision->next_collision = node->next_collision; + return true; + } + // There's no higher layer linked list left. If we have following collision entries, set the + // hash table to them. If we don't, we want to null it anyway. + hooks_hash_table.at(hash_idx) = node->next_collision; return true; } } // namespace -const List* preprocess_hook(std::wstring_view source, const UFunction* func, const UObject* obj) { +std::shared_ptr preprocess_hook(std::wstring_view source, + const UFunction* func, + const UObject* obj) { if (should_inject_next_call) { should_inject_next_call = false; return nullptr; @@ -229,53 +440,70 @@ const List* preprocess_hook(std::wstring_view source, const UFunction* func, con } } - // Check if anything matches the function FName - auto fname_iter = hooks.find(func->Name); - if (fname_iter == hooks.end()) { + auto fname = func->Name(); + + auto hash_idx = get_table_index(fname); + auto node = hooks_hash_table.at(hash_idx); + if (node == nullptr) { + // This function isn't even in the hash table return nullptr; } - auto& path_name_map = fname_iter->second; - if (path_name_map.empty()) { - hooks.erase(func->Name); - return nullptr; + + // Look through hash collisions + while (node->fname != fname) { + if (node->next_collision == nullptr) { + // We found a collision, but nothing matched our name + return nullptr; + } + node = node->next_collision; } - // Now check the full path name + // At this point we need the full path name if (!should_log_all_calls) { func_name = func->get_path_name(); } - auto path_name_iter = path_name_map.find(func_name); - if (path_name_iter == path_name_map.end()) { - return nullptr; - } - auto& list = path_name_iter->second; - if (list.empty()) { - path_name_map.erase(func_name); - return nullptr; + + // Look though full function names + while (node->full_name != func_name) { + if (node->next_function == nullptr) { + // We found another function with the same fname, but nothing matches the full name + return nullptr; + } + node = node->next_function; } - return &list; + // Break off at this point - we know we have hooks on this function, so the hook processing will + // need to start extracting args. + return node; } -bool has_post_hooks(const List& list) { - return !list.post.empty() || !list.post_unconditional.empty(); +bool has_post_hooks(std::shared_ptr node) { + // We got the node from preprocess_hook, it's pointing to the start of the types linked list + for (; node != nullptr; node = node->next_type) { + if (node->type == Type::POST || node->type == Type::POST_UNCONDITIONAL) { + return true; + } + } + return false; } -bool run_hooks_of_type(const List& list, Type type, Details& hook) { - const Group* group_ptr = list.get_safe_group_by_type(type); - if (group_ptr == nullptr) { - LOG(ERROR, "Tried to run hooks of invalid type {}", (uint8_t)type); - return false; - } +bool run_hooks_of_type(std::shared_ptr node, Type type, Details& hook) { + // We got the node from preprocess_hook, it's pointing to the start of the types linked list - // Grab a copy of the revelevant hook group, in case the hook removes itself (which would - // invalidate the iterator) - const Group group = *group_ptr; + // Look through hook types + while (node->type != type) { + if (node->next_type == nullptr) { + // No hooks of this type - return false to not block + return false; + } + node = node->next_type; + } + // We've got the final list of hooks, run them all bool ret = false; - for (const auto& [_, hook_function] : group) { + for (; node != nullptr; node = node->next_in_collection) { try { - ret |= hook_function->operator()(hook); + ret |= node->callback->operator()(hook); } catch (const std::exception& ex) { LOG(ERROR, "An exception occurred during hook processing"); LOG(ERROR, L"Function: {}", hook.func.func->get_path_name()); diff --git a/src/unrealsdk/hook_manager.h b/src/unrealsdk/hook_manager.h index 8cca28e..a45fa67 100644 --- a/src/unrealsdk/hook_manager.h +++ b/src/unrealsdk/hook_manager.h @@ -109,7 +109,7 @@ bool remove_hook(std::wstring_view func, Type type, std::wstring_view identifier #ifndef UNREALSDK_IMPORTING namespace impl { // These functions are only relevant when implementing a game hook -struct List; +struct Node; /* Processing hooks needs to be very optimized, thousands if not tens of thousands of functions calls @@ -140,29 +140,29 @@ to work out if to early exit again. If it does, it can spend a bit longer extrac * @param source The source of the call, used for logging. * @param func The function which was called. * @param obj The object which called the function. - * @return A pointer to the relevant hook list, or nullptr if no hooks match. + * @return A node pointer to pass into the following functions, or nullptr if no hooks match. */ -const List* preprocess_hook(std::wstring_view source, - const unreal::UFunction* func, - const unreal::UObject* obj); +std::shared_ptr preprocess_hook(std::wstring_view source, + const unreal::UFunction* func, + const unreal::UObject* obj); /** * @brief Checks if a hook list contains any post hooks. * - * @param list The hook list, retrieved from `preprocess_hook`. + * @param node The node previously retrieved from `preprocess_hook`. * @return True if the list contains post hooks. */ -bool has_post_hooks(const List& list); +bool has_post_hooks(std::shared_ptr node); /** * @brief Runs all the hooks in a list which match the given type. * - * @param list The hook list, retrieved from `preprocess_hook`. + * @param node The node previously retrieved from `preprocess_hook`. * @param type The type of hooks to run. * @param hook The hook details. * @return The logical or of the hooks' return values. */ -bool run_hooks_of_type(const List& list, Type type, Details& hook); +bool run_hooks_of_type(std::shared_ptr node, Type type, Details& hook); } // namespace impl #endif diff --git a/src/unrealsdk/logging.cpp b/src/unrealsdk/logging.cpp index f26e654..7d0e9c6 100644 --- a/src/unrealsdk/logging.cpp +++ b/src/unrealsdk/logging.cpp @@ -224,7 +224,7 @@ constexpr auto LEVEL_WIDTH = 4; * @return The formatted message */ std::string format_message(const LogMessage& msg) { - return unrealsdk::fmt::format( + return std::format( "{1:>{0}%F %T}Z {3:>{2}}@{5:<{4}d} {7:>{6}}| {8}\n", DATE_WIDTH + sizeof(' ') + TIME_WIDTH, time_from_unix_ms(msg.unix_time_ms), LOCATION_WIDTH, truncate_leading_chunks(msg.location, "\\/:", LOCATION_WIDTH), LINE_WIDTH, msg.line, @@ -237,9 +237,9 @@ std::string format_message(const LogMessage& msg) { * @return The header. */ std::string get_header(void) { - return unrealsdk::fmt::format("{1:<{0}} {3:<{2}} {5:>{4}}@{7:<{6}} {9:>{8}}| \n", DATE_WIDTH, - "date", TIME_WIDTH + sizeof('Z'), "time", LOCATION_WIDTH, - "location", LINE_WIDTH, "line", LEVEL_WIDTH, "v"); + return std::format("{1:<{0}} {3:<{2}} {5:>{4}}@{7:<{6}} {9:>{8}}| \n", DATE_WIDTH, "date", + TIME_WIDTH + sizeof('Z'), "time", LOCATION_WIDTH, "location", LINE_WIDTH, + "line", LEVEL_WIDTH, "v"); } #endif diff --git a/src/unrealsdk/logging.h b/src/unrealsdk/logging.h index cc186f7..67c8dcc 100644 --- a/src/unrealsdk/logging.h +++ b/src/unrealsdk/logging.h @@ -2,7 +2,7 @@ #define UNREALSDK_LOGGING_H // Because this file in included in the pch, we can't include the pch here instead of these -#include "unrealsdk/format.h" +#include namespace unrealsdk::logging { @@ -92,9 +92,8 @@ void remove_callback(log_callback callback); * @param ... The format string + it's contents. */ // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) -#define LOG(level, ...) \ - unrealsdk::logging::log((unrealsdk::logging::Level::level), \ - unrealsdk::fmt::format(__VA_ARGS__), \ +#define LOG(level, ...) \ + unrealsdk::logging::log((unrealsdk::logging::Level::level), std::format(__VA_ARGS__), \ {(const char*)(__FUNCTION__), sizeof(__FUNCTION__) - 1}, (__LINE__)) #endif /* UNREALSDK_LOGGING_H */ diff --git a/src/unrealsdk/memory.cpp b/src/unrealsdk/memory.cpp index fb90881..b294877 100644 --- a/src/unrealsdk/memory.cpp +++ b/src/unrealsdk/memory.cpp @@ -4,15 +4,8 @@ namespace unrealsdk::memory { -namespace { - -/** - * @brief Gets the address range covered by the exe's module. - * - * @return A tuple of the exe start address and it's length. - */ -std::tuple get_exe_range(void) { - static std::optional> range = std::nullopt; +std::pair get_exe_range(void) { + static std::optional> range = std::nullopt; if (range) { return *range; } @@ -38,8 +31,6 @@ std::tuple get_exe_range(void) { return *range; } -} // namespace - uintptr_t sigscan(const uint8_t* bytes, const uint8_t* mask, size_t pattern_size) { auto [start, size] = get_exe_range(); return sigscan(bytes, mask, pattern_size, start, size); @@ -93,13 +84,15 @@ bool detour(uintptr_t addr, void* detour_func, void** original_func, std::string status = MH_CreateHook(reinterpret_cast(addr), detour_func, original_func); if (status != MH_OK) { - LOG(ERROR, "Failed to create detour for {}", name); + const char* error = MH_StatusToString(status); + LOG(ERROR, "Failed to create detour for '{}'; With error: '{}'", name, error); return false; } status = MH_EnableHook(reinterpret_cast(addr)); if (status != MH_OK) { - LOG(ERROR, "Failed to enable detour for {}", name); + const char* error = MH_StatusToString(status); + LOG(ERROR, "Failed to enable detour for '{}'; With error: '{}'", name, error); return false; } @@ -124,11 +117,12 @@ uintptr_t read_offset(uintptr_t address) { LOG(ERROR, "Attempted to read a null offset!"); return 0; } -#ifdef ARCH_X64 - return address + *reinterpret_cast(address) + 4; -#else - return *reinterpret_cast(address); -#endif + + if constexpr (sizeof(uintptr_t) == sizeof(uint64_t)) { + return address + *reinterpret_cast(address) + 4; + } else { + return *reinterpret_cast(address); + } } void unlock_range(uintptr_t start, size_t size) { diff --git a/src/unrealsdk/memory.h b/src/unrealsdk/memory.h index 2dbac55..0eb26bd 100644 --- a/src/unrealsdk/memory.h +++ b/src/unrealsdk/memory.h @@ -241,6 +241,13 @@ struct Pattern { } }; +/** + * @brief Gets the address range covered by the exe's module. + * + * @return A tuple of the exe start address and it's length. + */ +std::pair get_exe_range(void); + } // namespace unrealsdk::memory #endif /* UNREALSDK_MEMORY_H */ diff --git a/src/unrealsdk/pch.h b/src/unrealsdk/pch.h index 15e1e2d..43259f7 100644 --- a/src/unrealsdk/pch.h +++ b/src/unrealsdk/pch.h @@ -5,6 +5,13 @@ // This file is purely macros, it doesn't rely on anything else #include "unrealsdk/exports.h" +// The flavour macros are used in ifdefs everywhere. +// `UNREALSDK_FLAVOUR` should be defined to be equal to one of them. +// NOLINTBEGIN(cppcoreguidelines-macro-usage) +#define UNREALSDK_FLAVOUR_WILLOW 1 +#define UNREALSDK_FLAVOUR_OAK 2 +// NOLINTEND(cppcoreguidelines-macro-usage) + #define WIN32_LEAN_AND_MEAN #define WIN32_NO_STATUS #define NOGDI @@ -36,6 +43,7 @@ #include #include #include +#include #include #include #include @@ -57,10 +65,6 @@ #include #include -// This file is just a forwarder for whichever formatting library is configured, it doesn't define -// anything itself, so is fine to include here -#include "unrealsdk/format.h" - // This file is mostly just here so that the `LOG` macro is automatically available everywhere // It only includes library headers, so is also ok to include #include "unrealsdk/logging.h" @@ -74,11 +78,7 @@ using std::uint32_t; using std::uint64_t; using std::uint8_t; -#if __cplusplus > 202002L -using std::float32_t; -using std::float64_t; -#else - +// Awaiting better compiler support to replace these with std::float32_t // NOLINTBEGIN(readability-magic-numbers) static_assert(std::numeric_limits::is_iec559 && std::numeric_limits::digits == 24, "float is not ieee 32-bit"); @@ -89,23 +89,16 @@ static_assert(std::numeric_limits::is_iec559 && std::numeric_limitsClass->Name); + throw std::runtime_error("Unknown object type " + (std::string)obj->Class()->Name()); } #ifdef __clang__ @@ -133,11 +133,11 @@ void cast_impl(InputType* obj, if constexpr (i >= std::tuple_size_v) { // But we're supposed to check inherited types, and we have a super field if constexpr (check_inherited_types) { - if (working_class->SuperField != nullptr) { + if (working_class->SuperField() != nullptr) { // Jump back to the start of the tuple, but use the super field return cast_impl( - obj, working_class->SuperField, func, fallback); + obj, working_class->SuperField(), func, fallback); } } @@ -153,7 +153,7 @@ void cast_impl(InputType* obj, && (include_input_type || !std::is_same_v, cls>)) { // If the class name matches - if (working_class->Name == cls_fname()) { + if (working_class->Name() == cls_fname()) { // Run the callback if constexpr (std::is_const_v) { return func.template operator()(reinterpret_cast(obj)); @@ -249,7 +249,7 @@ void cast(InputType* obj, const Function& func, const Fallback& fallback = defau return cast_impl( - obj, obj->Class, func, fallback); + obj, obj->Class(), func, fallback); } } // namespace unrealsdk::unreal diff --git a/src/unrealsdk/unreal/class_name.h b/src/unrealsdk/unreal/class_name.h index 1790aca..42acb4f 100644 --- a/src/unrealsdk/unreal/class_name.h +++ b/src/unrealsdk/unreal/class_name.h @@ -37,7 +37,7 @@ void throw_on_wrong_type(const UObject* obj) { throw std::invalid_argument("Tried to validate type of null object!"); } static const auto expected_cls_name = cls_fname(); - auto cls_name = obj->Class->Name; + auto cls_name = obj->Class()->Name(); if (cls_name != expected_cls_name) { throw std::invalid_argument("Object was of unexpected type " + (std::string)cls_name); } diff --git a/src/unrealsdk/unreal/classes/properties/attribute_property.cpp b/src/unrealsdk/unreal/classes/properties/attribute_property.cpp new file mode 100644 index 0000000..b209b32 --- /dev/null +++ b/src/unrealsdk/unreal/classes/properties/attribute_property.cpp @@ -0,0 +1,15 @@ +#include "unrealsdk/pch.h" +#include "unrealsdk/unreal/classes/properties/attribute_property.h" +#include "unrealsdk/unreal/offset_list.h" +#include "unrealsdk/unreal/offsets.h" +#include "unrealsdk/unrealsdk.h" + +namespace unrealsdk::unreal { + +UNREALSDK_DEFINE_FIELDS_SOURCE_FILE(UByteAttributeProperty, + UNREALSDK_UBYTEATTRIBUTEPROPERTY_FIELDS); +UNREALSDK_DEFINE_FIELDS_SOURCE_FILE(UFloatAttributeProperty, + UNREALSDK_UFLOATATTRIBUTEPROPERTY_FIELDS); +UNREALSDK_DEFINE_FIELDS_SOURCE_FILE(UIntAttributeProperty, UNREALSDK_UINTATTRIBUTEPROPERTY_FIELDS); + +} // namespace unrealsdk::unreal diff --git a/src/unrealsdk/unreal/classes/properties/attribute_property.h b/src/unrealsdk/unreal/classes/properties/attribute_property.h index 2c3245f..29c7f42 100644 --- a/src/unrealsdk/unreal/classes/properties/attribute_property.h +++ b/src/unrealsdk/unreal/classes/properties/attribute_property.h @@ -10,56 +10,66 @@ namespace unrealsdk::unreal { +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW +#pragma pack(push, 0x4) +#endif + class UArrayProperty; class UProperty; -#if defined(_MSC_VER) && defined(ARCH_X86) -#pragma pack(push, 0x4) -#endif +namespace offsets::generic { -template >> -class AttributeProperty : public T { +template +class GenericAttributeProperty : public T { public: - AttributeProperty() = delete; - AttributeProperty(const AttributeProperty&) = delete; - AttributeProperty(AttributeProperty&&) = delete; - AttributeProperty& operator=(const AttributeProperty&) = delete; - AttributeProperty& operator=(AttributeProperty&&) = delete; - ~AttributeProperty() = delete; + // NOLINTBEGIN(readability-identifier-naming) - // NOLINTBEGIN(readability-magic-numbers, readability-identifier-naming) - - private: UArrayProperty* ModifierStackProperty; - AttributeProperty* OtherAttributeProperty; + GenericAttributeProperty* OtherAttributeProperty; - // NOLINTEND(readability-magic-numbers, readability-identifier-naming) - public: - /** - * @brief Gets the property used for this attribute's modifier stack. - * - * @return The modifier stack property. - */ - [[nodiscard]] UArrayProperty* get_modifier_stack_prop(void) const { - return this->read_field(&AttributeProperty::ModifierStackProperty); - } - - /** - * @brief Gets the other attribute property used for this attribute. - * - * @return The other attribute property. - */ - [[nodiscard]] AttributeProperty* get_other_attribute_property(void) const { - return this->read_field(&AttributeProperty::OtherAttributeProperty); - } + // NOLINTEND(readability-identifier-naming) }; -template -struct PropTraits> : public PropTraits {}; - -using UByteAttributeProperty = AttributeProperty; -using UFloatAttributeProperty = AttributeProperty; -using UIntAttributeProperty = AttributeProperty; +} // namespace offsets::generic + +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define DEFINE_ATTRIBUTE_PROPERTY(class_name, base_class, fields) \ + class class_name : public base_class { \ + public: \ + class_name() = delete; \ + class_name(const class_name&) = delete; \ + class_name(class_name&&) = delete; \ + class_name& operator=(const class_name&) = delete; \ + class_name& operator=(class_name&&) = delete; \ + ~class_name() = delete; \ + UNREALSDK_DEFINE_FIELDS_HEADER(class_name, fields); \ + }; \ + template <> \ + struct PropTraits : public PropTraits {}; + +// These fields become member functions, returning a reference into the object. +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define UNREALSDK_UBYTEATTRIBUTEPROPERTY_FIELDS(X) \ + X(UArrayProperty*, ModifierStackProperty) \ + X(UByteAttributeProperty*, OtherAttributeProperty) +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define UNREALSDK_UFLOATATTRIBUTEPROPERTY_FIELDS(X) \ + X(UArrayProperty*, ModifierStackProperty) \ + X(UFloatAttributeProperty*, OtherAttributeProperty) +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define UNREALSDK_UINTATTRIBUTEPROPERTY_FIELDS(X) \ + X(UArrayProperty*, ModifierStackProperty) \ + X(UIntAttributeProperty*, OtherAttributeProperty) + +DEFINE_ATTRIBUTE_PROPERTY(UByteAttributeProperty, + UByteProperty, + UNREALSDK_UBYTEATTRIBUTEPROPERTY_FIELDS); +DEFINE_ATTRIBUTE_PROPERTY(UFloatAttributeProperty, + UFloatProperty, + UNREALSDK_UFLOATATTRIBUTEPROPERTY_FIELDS); +DEFINE_ATTRIBUTE_PROPERTY(UIntAttributeProperty, + UIntProperty, + UNREALSDK_UINTATTRIBUTEPROPERTY_FIELDS); template <> inline const wchar_t* const ClassTraits::NAME = L"ByteAttributeProperty"; @@ -68,7 +78,9 @@ inline const wchar_t* const ClassTraits::NAME = L"Float template <> inline const wchar_t* const ClassTraits::NAME = L"IntAttributeProperty"; -#if defined(_MSC_VER) && defined(ARCH_X86) +#undef DEFINE_ATTRIBUTE_PROPERTY + +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(pop) #endif diff --git a/src/unrealsdk/unreal/classes/properties/copyable_property.h b/src/unrealsdk/unreal/classes/properties/copyable_property.h index 50a5898..697e733 100644 --- a/src/unrealsdk/unreal/classes/properties/copyable_property.h +++ b/src/unrealsdk/unreal/classes/properties/copyable_property.h @@ -15,7 +15,7 @@ This file describes all properties which can be read/written by a simple copy, a namespace unrealsdk::unreal { -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(push, 0x4) #endif @@ -49,7 +49,7 @@ using UInt16Property = CopyableProperty; using UIntProperty = CopyableProperty; using UInt64Property = CopyableProperty; -// UByteProperty has an extra field in UE3, handled in a different file +// UByteProperty has an extra field we care about, it's handled in a different file using UUInt16Property = CopyableProperty; using UUInt32Property = CopyableProperty; using UUInt64Property = CopyableProperty; @@ -83,7 +83,7 @@ inline const wchar_t* const ClassTraits::NAME = L"DoublePropert template <> inline const wchar_t* const ClassTraits::NAME = L"NameProperty"; -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(pop) #endif diff --git a/src/unrealsdk/unreal/classes/properties/persistent_object_ptr_property.cpp b/src/unrealsdk/unreal/classes/properties/persistent_object_ptr_property.cpp index b67e0a6..329824e 100644 --- a/src/unrealsdk/unreal/classes/properties/persistent_object_ptr_property.cpp +++ b/src/unrealsdk/unreal/classes/properties/persistent_object_ptr_property.cpp @@ -2,6 +2,8 @@ #include "unrealsdk/unreal/classes/properties/persistent_object_ptr_property.h" #include "unrealsdk/unreal/classes/uclass.h" +#include "unrealsdk/unreal/offset_list.h" +#include "unrealsdk/unreal/offsets.h" #include "unrealsdk/unreal/structs/fstring.h" #include "unrealsdk/unreal/structs/tarray.h" #include "unrealsdk/unreal/structs/tarray_funcs.h" @@ -12,9 +14,7 @@ namespace unrealsdk::unreal { -UClass* USoftClassProperty::get_meta_class(void) const { - return this->read_field(&USoftClassProperty::MetaClass); -} +UNREALSDK_DEFINE_FIELDS_SOURCE_FILE(USoftClassProperty, UNREALSDK_USOFTCLASSPROPERTY_FIELDS); PropTraits::Value PropTraits::get( const ULazyObjectProperty* /*prop*/, @@ -44,9 +44,9 @@ void PropTraits::set(const ULazyObjectProperty* prop, const Value& value) { // Ensure the object is of a valid class if (value != nullptr) { - auto prop_cls = prop->get_property_class(); + auto prop_cls = prop->PropertyClass(); if (!value->is_instance(prop_cls)) { - throw std::runtime_error("Object is not instance of " + (std::string)prop_cls->Name); + throw std::runtime_error("Object is not instance of " + (std::string)prop_cls->Name()); } } @@ -57,9 +57,9 @@ void PropTraits::set(const USoftObjectProperty* prop, const Value& value) { // Ensure the object is of a valid class if (value != nullptr) { - auto prop_cls = prop->get_property_class(); + auto prop_cls = prop->PropertyClass(); if (!value->is_instance(prop_cls)) { - throw std::runtime_error("Object is not instance of " + (std::string)prop_cls->Name); + throw std::runtime_error("Object is not instance of " + (std::string)prop_cls->Name()); } } @@ -70,13 +70,14 @@ void PropTraits::set(const USoftClassProperty* prop, const Value& value) { // Ensure the object is of a valid class if (value != nullptr) { - auto prop_cls = prop->get_property_class(); + auto prop_cls = prop->PropertyClass(); if (!value->is_instance(prop_cls)) { - throw std::runtime_error("Object is not instance of " + (std::string)prop_cls->Name); + throw std::runtime_error("Object is not instance of " + (std::string)prop_cls->Name()); } - auto meta_cls = prop->get_meta_class(); + auto meta_cls = prop->MetaClass(); if (!value->inherits(meta_cls)) { - throw std::runtime_error("Class does not inherit from " + (std::string)meta_cls->Name); + throw std::runtime_error("Class does not inherit from " + + (std::string)meta_cls->Name()); } } diff --git a/src/unrealsdk/unreal/classes/properties/persistent_object_ptr_property.h b/src/unrealsdk/unreal/classes/properties/persistent_object_ptr_property.h index 21d8188..541be17 100644 --- a/src/unrealsdk/unreal/classes/properties/persistent_object_ptr_property.h +++ b/src/unrealsdk/unreal/classes/properties/persistent_object_ptr_property.h @@ -5,18 +5,30 @@ #include "unrealsdk/unreal/class_traits.h" #include "unrealsdk/unreal/classes/properties/uobjectproperty.h" +#include "unrealsdk/unreal/offsets.h" #include "unrealsdk/unreal/prop_traits.h" #include "unrealsdk/unreal/wrappers/unreal_pointer.h" namespace unrealsdk::unreal { +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW +#pragma pack(push, 0x4) +#endif + struct FLazyObjectPath; struct FSoftObjectPath; class UObject; -#if defined(_MSC_VER) && defined(ARCH_X86) -#pragma pack(push, 0x4) -#endif +namespace offsets::generic { + +template +class USoftClassProperty : public T { + public: + // NOLINTNEXTLINE(readability-identifier-naming) + UClass* MetaClass; +}; + +} // namespace offsets::generic class ULazyObjectProperty : public UObjectProperty { public: @@ -49,16 +61,11 @@ class USoftClassProperty : public USoftObjectProperty { USoftClassProperty& operator=(USoftClassProperty&&) = delete; ~USoftClassProperty() = delete; - /** - * @brief Get the meta class of this property, which values must be a subclass of. - * - * @return This property's meta class. - */ - [[nodiscard]] UClass* get_meta_class(void) const; + // These fields become member functions, returning a reference into the object. +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define UNREALSDK_USOFTCLASSPROPERTY_FIELDS(X) X(UClass*, MetaClass) - private: - // NOLINTNEXTLINE(readability-identifier-naming) - UClass* MetaClass; + UNREALSDK_DEFINE_FIELDS_HEADER(USoftClassProperty, UNREALSDK_USOFTCLASSPROPERTY_FIELDS); }; template <> @@ -118,7 +125,7 @@ struct ClassTraits { static inline const wchar_t* const NAME = L"SoftClassProperty"; }; -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(pop) #endif diff --git a/src/unrealsdk/unreal/classes/properties/uarrayproperty.cpp b/src/unrealsdk/unreal/classes/properties/uarrayproperty.cpp index 0ca373d..263cfe4 100644 --- a/src/unrealsdk/unreal/classes/properties/uarrayproperty.cpp +++ b/src/unrealsdk/unreal/classes/properties/uarrayproperty.cpp @@ -2,25 +2,26 @@ #include "unrealsdk/unreal/cast.h" #include "unrealsdk/unreal/classes/properties/uarrayproperty.h" +#include "unrealsdk/unreal/offset_list.h" +#include "unrealsdk/unreal/offsets.h" #include "unrealsdk/unreal/prop_traits.h" #include "unrealsdk/unreal/structs/tarray.h" #include "unrealsdk/unreal/structs/tarray_funcs.h" #include "unrealsdk/unreal/wrappers/unreal_pointer.h" #include "unrealsdk/unreal/wrappers/unreal_pointer_funcs.h" #include "unrealsdk/unreal/wrappers/wrapped_array.h" +#include "unrealsdk/unrealsdk.h" namespace unrealsdk::unreal { -UProperty* UArrayProperty::get_inner(void) const { - return this->read_field(&UArrayProperty::Inner); -} +UNREALSDK_DEFINE_FIELDS_SOURCE_FILE(UArrayProperty, UNREALSDK_UARRAYPROPERTY_FIELDS); PropTraits::Value PropTraits::get( const UArrayProperty* prop, uintptr_t addr, const UnrealPointer& parent) { - auto inner = prop->get_inner(); - if (prop->ArrayDim > 1) { + auto inner = prop->Inner(); + if (prop->ArrayDim() > 1) { throw std::runtime_error( "Array has static array inner property - unsure how to handle, aborting!"); } @@ -31,15 +32,15 @@ PropTraits::Value PropTraits::get( void PropTraits::set(const UArrayProperty* prop, uintptr_t addr, const Value& value) { - auto inner = prop->get_inner(); - if (prop->ArrayDim > 1) { + auto inner = prop->Inner(); + if (prop->ArrayDim() > 1) { throw std::runtime_error( "Array has static array inner property - unsure how to handle, aborting!"); } if (value.type != inner) { - throw std::runtime_error(utils::narrow( - unrealsdk::fmt::format(L"Array fields have incompatible type, expected {}, got {}", - inner->get_path_name(), value.type->get_path_name()))); + throw std::runtime_error( + utils::narrow(std::format(L"Array fields have incompatible type, expected {}, got {}", + inner->get_path_name(), value.type->get_path_name()))); } auto arr = reinterpret_cast*>(addr); @@ -51,19 +52,19 @@ void PropTraits::set(const UArrayProperty* prop, cast(inner, [&arr, &value](const T* inner) { auto new_size = value.size(); - arr->resize(new_size, inner->ElementSize); + arr->resize(new_size, inner->ElementSize()); for (size_t i = 0; i < new_size; i++) { set_property(inner, 0, - reinterpret_cast(arr->data) + (inner->ElementSize * i), + reinterpret_cast(arr->data) + (inner->ElementSize() * i), value.get_at(i)); } }); } void PropTraits::destroy(const UArrayProperty* prop, uintptr_t addr) { - auto inner = prop->get_inner(); - if (prop->ArrayDim > 1) { + auto inner = prop->Inner(); + if (prop->ArrayDim() > 1) { throw std::runtime_error( "Array has static array inner property - unsure how to handle, aborting!"); } @@ -72,8 +73,8 @@ void PropTraits::destroy(const UArrayProperty* prop, uintptr_t a cast(inner, [arr](const T* inner) { for (size_t i = 0; i < arr->size(); i++) { - destroy_property(inner, 0, - reinterpret_cast(arr->data) + (inner->ElementSize * i)); + destroy_property( + inner, 0, reinterpret_cast(arr->data) + (inner->ElementSize() * i)); } }); diff --git a/src/unrealsdk/unreal/classes/properties/uarrayproperty.h b/src/unrealsdk/unreal/classes/properties/uarrayproperty.h index fd1fcac..44b4fde 100644 --- a/src/unrealsdk/unreal/classes/properties/uarrayproperty.h +++ b/src/unrealsdk/unreal/classes/properties/uarrayproperty.h @@ -5,16 +5,28 @@ #include "unrealsdk/unreal/class_traits.h" #include "unrealsdk/unreal/classes/uproperty.h" +#include "unrealsdk/unreal/offsets.h" #include "unrealsdk/unreal/prop_traits.h" #include "unrealsdk/unreal/wrappers/unreal_pointer.h" #include "unrealsdk/unreal/wrappers/wrapped_array.h" namespace unrealsdk::unreal { -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(push, 0x4) #endif +namespace offsets::generic { + +template +class UArrayProperty : public T { + public: + // NOLINTNEXTLINE(readability-identifier-naming) + UProperty* Inner; +}; + +} // namespace offsets::generic + class UArrayProperty : public UProperty { public: UArrayProperty() = delete; @@ -24,16 +36,11 @@ class UArrayProperty : public UProperty { UArrayProperty& operator=(UArrayProperty&&) = delete; ~UArrayProperty() = delete; - /** - * @brief Get the inner property, which the array entries are instances of. - * - * @return This property's inner property. - */ - [[nodiscard]] UProperty* get_inner(void) const; + // These fields become member functions, returning a reference into the object. +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define UNREALSDK_UARRAYPROPERTY_FIELDS(X) X(UProperty*, Inner) - private: - // NOLINTNEXTLINE(readability-identifier-naming) - UProperty* Inner; + UNREALSDK_DEFINE_FIELDS_HEADER(UArrayProperty, UNREALSDK_UARRAYPROPERTY_FIELDS); }; template <> @@ -50,7 +57,7 @@ struct ClassTraits { static inline const wchar_t* const NAME = L"ArrayProperty"; }; -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(pop) #endif diff --git a/src/unrealsdk/unreal/classes/properties/uboolproperty.cpp b/src/unrealsdk/unreal/classes/properties/uboolproperty.cpp index d8a4ed1..1ce95a5 100644 --- a/src/unrealsdk/unreal/classes/properties/uboolproperty.cpp +++ b/src/unrealsdk/unreal/classes/properties/uboolproperty.cpp @@ -1,26 +1,27 @@ #include "unrealsdk/pch.h" #include "unrealsdk/unreal/classes/properties/uboolproperty.h" +#include "unrealsdk/unreal/offset_list.h" +#include "unrealsdk/unreal/offsets.h" #include "unrealsdk/unreal/wrappers/unreal_pointer.h" +#include "unrealsdk/unrealsdk.h" namespace unrealsdk::unreal { -decltype(UBoolProperty::FieldMask) UBoolProperty::get_field_mask(void) const { - return this->read_field(&UBoolProperty::FieldMask); -} +UNREALSDK_DEFINE_FIELDS_SOURCE_FILE(UBoolProperty, UNREALSDK_UBOOLPROPERTY_FIELDS); PropTraits::Value PropTraits::get( const UBoolProperty* prop, uintptr_t addr, const UnrealPointer& /*parent*/) { - auto mask = prop->get_field_mask(); + auto mask = prop->FieldMask(); auto* bitfield = reinterpret_cast(addr); return (*bitfield & mask) != 0; } void PropTraits::set(const UBoolProperty* prop, uintptr_t addr, const Value& value) { - auto mask = prop->get_field_mask(); + auto mask = prop->FieldMask(); auto* bitfield = reinterpret_cast(addr); if (value) { diff --git a/src/unrealsdk/unreal/classes/properties/uboolproperty.h b/src/unrealsdk/unreal/classes/properties/uboolproperty.h index cad31ac..8d8a39a 100644 --- a/src/unrealsdk/unreal/classes/properties/uboolproperty.h +++ b/src/unrealsdk/unreal/classes/properties/uboolproperty.h @@ -10,7 +10,7 @@ namespace unrealsdk::unreal { -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(push, 0x4) #endif @@ -28,26 +28,19 @@ class UBoolProperty : public UProperty { UBoolProperty& operator=(UBoolProperty&&) = delete; ~UBoolProperty() = delete; - private: - // NOLINTBEGIN(readability-identifier-naming) - -#ifdef UE4 - uint8_t FieldSize; - uint8_t ByteOffset; - uint8_t ByteMask; - uint8_t FieldMask; +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_OAK + using field_mask_type = uint8_t; +#elif UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW + using field_mask_type = uint32_t; #else - uint32_t FieldMask; +#error Unknown SDK flavour #endif - // NOLINTEND(readability-identifier-naming) - public: - /** - * @brief Get the bool field mask of this property. - * - * @return The field mask. - */ - [[nodiscard]] decltype(FieldMask) get_field_mask(void) const; + // These fields become member functions, returning a reference into the object. +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define UNREALSDK_UBOOLPROPERTY_FIELDS(X) X(field_mask_type, FieldMask) + + UNREALSDK_DEFINE_FIELDS_HEADER(UBoolProperty, UNREALSDK_UBOOLPROPERTY_FIELDS); }; template <> @@ -67,7 +60,7 @@ struct ClassTraits { #pragma GCC diagnostic pop #endif -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(pop) #endif diff --git a/src/unrealsdk/unreal/classes/properties/ubyteproperty.cpp b/src/unrealsdk/unreal/classes/properties/ubyteproperty.cpp index eabae20..716f7ea 100644 --- a/src/unrealsdk/unreal/classes/properties/ubyteproperty.cpp +++ b/src/unrealsdk/unreal/classes/properties/ubyteproperty.cpp @@ -1,10 +1,11 @@ #include "unrealsdk/pch.h" #include "unrealsdk/unreal/classes/properties/ubyteproperty.h" +#include "unrealsdk/unreal/offset_list.h" +#include "unrealsdk/unreal/offsets.h" +#include "unrealsdk/unrealsdk.h" namespace unrealsdk::unreal { -UEnum* UByteProperty::get_enum(void) const { - return this->read_field(&UByteProperty::Enum); -} +UNREALSDK_DEFINE_FIELDS_SOURCE_FILE(UByteProperty, UNREALSDK_UBYTEPROPERTY_FIELDS); } // namespace unrealsdk::unreal diff --git a/src/unrealsdk/unreal/classes/properties/ubyteproperty.h b/src/unrealsdk/unreal/classes/properties/ubyteproperty.h index 37038b8..b18e923 100644 --- a/src/unrealsdk/unreal/classes/properties/ubyteproperty.h +++ b/src/unrealsdk/unreal/classes/properties/ubyteproperty.h @@ -10,12 +10,23 @@ namespace unrealsdk::unreal { -class UEnum; - -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(push, 0x4) #endif +class UEnum; + +namespace offsets::generic { + +template +class UByteProperty : public T { + public: + // NOLINTNEXTLINE(readability-identifier-naming) + UEnum* Enum; +}; + +} // namespace offsets::generic + class UByteProperty : public CopyableProperty { public: UByteProperty() = delete; @@ -25,20 +36,11 @@ class UByteProperty : public CopyableProperty { UByteProperty& operator=(UByteProperty&&) = delete; ~UByteProperty() = delete; - private: - // NOLINTBEGIN(readability-identifier-naming) + // These fields become member functions, returning a reference into the object. +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define UNREALSDK_UBYTEPROPERTY_FIELDS(X) X(UEnum*, Enum) - UEnum* Enum; - - // NOLINTEND(readability-identifier-naming) - - public: - /** - * @brief Get the enum associated with this property, if any. - * - * @return The enum. - */ - [[nodiscard]] UEnum* get_enum(void) const; + UNREALSDK_DEFINE_FIELDS_HEADER(UByteProperty, UNREALSDK_UBYTEPROPERTY_FIELDS); }; template <> @@ -49,7 +51,7 @@ struct ClassTraits { static inline const wchar_t* const NAME = L"ByteProperty"; }; -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(pop) #endif diff --git a/src/unrealsdk/unreal/classes/properties/uclassproperty.cpp b/src/unrealsdk/unreal/classes/properties/uclassproperty.cpp index ba6abd2..ec8bcde 100644 --- a/src/unrealsdk/unreal/classes/properties/uclassproperty.cpp +++ b/src/unrealsdk/unreal/classes/properties/uclassproperty.cpp @@ -2,13 +2,14 @@ #include "unrealsdk/unreal/classes/properties/uclassproperty.h" #include "unrealsdk/unreal/classes/uclass.h" +#include "unrealsdk/unreal/offset_list.h" +#include "unrealsdk/unreal/offsets.h" #include "unrealsdk/unreal/wrappers/unreal_pointer.h" +#include "unrealsdk/unrealsdk.h" namespace unrealsdk::unreal { -UClass* UClassProperty::get_meta_class(void) const { - return this->read_field(&UClassProperty::MetaClass); -} +UNREALSDK_DEFINE_FIELDS_SOURCE_FILE(UClassProperty, UNREALSDK_UCLASSPROPERTY_FIELDS); PropTraits::Value PropTraits::get( const UClassProperty* /*prop*/, @@ -22,13 +23,14 @@ void PropTraits::set(const UClassProperty* prop, const Value& value) { // Ensure the object is of a valid class if (value != nullptr) { - auto prop_cls = prop->get_property_class(); + auto prop_cls = prop->PropertyClass(); if (!value->is_instance(prop_cls)) { - throw std::runtime_error("Object is not instance of " + (std::string)prop_cls->Name); + throw std::runtime_error("Object is not instance of " + (std::string)prop_cls->Name()); } - auto meta_cls = prop->get_meta_class(); + auto meta_cls = prop->MetaClass(); if (!value->inherits(meta_cls)) { - throw std::runtime_error("Class does not inherit from " + (std::string)meta_cls->Name); + throw std::runtime_error("Class does not inherit from " + + (std::string)meta_cls->Name()); } } diff --git a/src/unrealsdk/unreal/classes/properties/uclassproperty.h b/src/unrealsdk/unreal/classes/properties/uclassproperty.h index 509cfa8..c1dcc78 100644 --- a/src/unrealsdk/unreal/classes/properties/uclassproperty.h +++ b/src/unrealsdk/unreal/classes/properties/uclassproperty.h @@ -1,5 +1,5 @@ -#ifndef UNREALSDK_UNREAL_CLASSES_PROPERTIES_UCLASSPROPERTY_H -#define UNREALSDK_UNREAL_CLASSES_PROPERTIES_UCLASSPROPERTY_H +#ifndef PYUNREALSDK_LIBS_UNREALSDK_SRC_UNREALSDK_UNREAL_CLASSES_PROPERTIES_UCLASSPROPERTY_H +#define PYUNREALSDK_LIBS_UNREALSDK_SRC_UNREALSDK_UNREAL_CLASSES_PROPERTIES_UCLASSPROPERTY_H #include "unrealsdk/pch.h" @@ -10,12 +10,23 @@ namespace unrealsdk::unreal { -class UClass; - -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(push, 0x4) #endif +class UClass; + +namespace offsets::generic { + +template +class UClassProperty : public T { + public: + // NOLINTNEXTLINE(readability-identifier-naming) + UClass* MetaClass; +}; + +} // namespace offsets::generic + class UClassProperty : public UObjectProperty { public: UClassProperty() = delete; @@ -25,16 +36,11 @@ class UClassProperty : public UObjectProperty { UClassProperty& operator=(UClassProperty&&) = delete; ~UClassProperty() = delete; - /** - * @brief Get the meta class of this property, which values must be a subclass of. - * - * @return This property's meta class. - */ - [[nodiscard]] UClass* get_meta_class(void) const; + // These fields become member functions, returning a reference into the object. +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define UNREALSDK_UCLASSPROPERTY_FIELDS(X) X(UClass*, MetaClass) - private: - // NOLINTNEXTLINE(readability-identifier-naming) - UClass* MetaClass; + UNREALSDK_DEFINE_FIELDS_HEADER(UClassProperty, UNREALSDK_UCLASSPROPERTY_FIELDS); }; template <> @@ -50,10 +56,10 @@ struct ClassTraits { static inline const wchar_t* const NAME = L"ClassProperty"; }; -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(pop) #endif } // namespace unrealsdk::unreal -#endif /* UNREALSDK_UNREAL_CLASSES_PROPERTIES_UCLASSPROPERTY_H */ +#endif /* PYUNREALSDK_LIBS_UNREALSDK_SRC_UNREALSDK_UNREAL_CLASSES_PROPERTIES_UCLASSPROPERTY_H */ diff --git a/src/unrealsdk/unreal/classes/properties/ucomponentproperty.h b/src/unrealsdk/unreal/classes/properties/ucomponentproperty.h index bd9a0f5..16ff0f5 100644 --- a/src/unrealsdk/unreal/classes/properties/ucomponentproperty.h +++ b/src/unrealsdk/unreal/classes/properties/ucomponentproperty.h @@ -9,7 +9,7 @@ namespace unrealsdk::unreal { -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(push, 0x4) #endif @@ -31,7 +31,7 @@ struct ClassTraits { static inline const wchar_t* const NAME = L"ComponentProperty"; }; -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(pop) #endif diff --git a/src/unrealsdk/unreal/classes/properties/udelegateproperty.cpp b/src/unrealsdk/unreal/classes/properties/udelegateproperty.cpp index 8719387..97513cd 100644 --- a/src/unrealsdk/unreal/classes/properties/udelegateproperty.cpp +++ b/src/unrealsdk/unreal/classes/properties/udelegateproperty.cpp @@ -1,14 +1,15 @@ #include "unrealsdk/pch.h" #include "unrealsdk/unreal/classes/properties/udelegateproperty.h" +#include "unrealsdk/unreal/offset_list.h" +#include "unrealsdk/unreal/offsets.h" #include "unrealsdk/unreal/prop_traits.h" #include "unrealsdk/unreal/structs/fscriptdelegate.h" #include "unrealsdk/unreal/wrappers/unreal_pointer.h" +#include "unrealsdk/unrealsdk.h" namespace unrealsdk::unreal { -UFunction* UDelegateProperty::get_signature(void) const { - return this->read_field(&UDelegateProperty::Signature); -} +UNREALSDK_DEFINE_FIELDS_SOURCE_FILE(UDelegateProperty, UNREALSDK_UDELEGATEPROPERTY_FIELDS); PropTraits::Value PropTraits::get( const UDelegateProperty* /*prop*/, @@ -20,7 +21,7 @@ PropTraits::Value PropTraits::get( void PropTraits::set(const UDelegateProperty* prop, uintptr_t addr, const Value& value) { - FScriptDelegate::validate_signature(value, prop->get_signature()); + FScriptDelegate::validate_signature(value, prop->Signature()); reinterpret_cast(addr)->bind(value); } diff --git a/src/unrealsdk/unreal/classes/properties/udelegateproperty.h b/src/unrealsdk/unreal/classes/properties/udelegateproperty.h index 0beb613..eb46806 100644 --- a/src/unrealsdk/unreal/classes/properties/udelegateproperty.h +++ b/src/unrealsdk/unreal/classes/properties/udelegateproperty.h @@ -11,12 +11,23 @@ namespace unrealsdk::unreal { -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(push, 0x4) #endif class UFunction; +namespace offsets::generic { + +template +class UDelegateProperty : public T { + public: + // NOLINTNEXTLINE(readability-identifier-naming) + UFunction* Signature; +}; + +} // namespace offsets::generic + class UDelegateProperty : public UProperty { public: UDelegateProperty() = delete; @@ -26,17 +37,11 @@ class UDelegateProperty : public UProperty { UDelegateProperty& operator=(UDelegateProperty&&) = delete; ~UDelegateProperty() = delete; - private: - // NOLINTNEXTLINE(readability-identifier-naming) - UFunction* Signature; + // These fields become member functions, returning a reference into the object. +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define UNREALSDK_UDELEGATEPROPERTY_FIELDS(X) X(UFunction*, Signature) - public: - /** - * @brief Get the function holding this delegate's signature. - * - * @return The signature function. - */ - [[nodiscard]] UFunction* get_signature(void) const; + UNREALSDK_DEFINE_FIELDS_HEADER(UDelegateProperty, UNREALSDK_UDELEGATEPROPERTY_FIELDS); }; template <> @@ -54,7 +59,7 @@ struct ClassTraits { static inline const wchar_t* const NAME = L"DelegateProperty"; }; -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(pop) #endif diff --git a/src/unrealsdk/unreal/classes/properties/uenumproperty.cpp b/src/unrealsdk/unreal/classes/properties/uenumproperty.cpp index 19d35e3..0c31183 100644 --- a/src/unrealsdk/unreal/classes/properties/uenumproperty.cpp +++ b/src/unrealsdk/unreal/classes/properties/uenumproperty.cpp @@ -3,8 +3,11 @@ #include "unrealsdk/unreal/cast.h" #include "unrealsdk/unreal/classes/properties/copyable_property.h" #include "unrealsdk/unreal/classes/properties/ubyteproperty.h" +#include "unrealsdk/unreal/offset_list.h" +#include "unrealsdk/unreal/offsets.h" #include "unrealsdk/unreal/prop_traits.h" #include "unrealsdk/unreal/wrappers/unreal_pointer.h" +#include "unrealsdk/unrealsdk.h" namespace unrealsdk::unreal { @@ -23,13 +26,7 @@ using valid_underlying_types = std::tupleread_field(&UEnumProperty::UnderlyingProp); -} - -UEnum* UEnumProperty::get_enum(void) const { - return this->read_field(&UEnumProperty::Enum); -} +UNREALSDK_DEFINE_FIELDS_SOURCE_FILE(UEnumProperty, UNREALSDK_UENUMPROPERTY_FIELDS); PropTraits::Value PropTraits::get(const UEnumProperty* prop, uintptr_t addr, @@ -37,7 +34,7 @@ PropTraits::Value PropTraits::get(const UEnumPrope PropTraits::Value value{}; cast::with_classes>( - prop->get_underlying_prop(), [addr, parent, &value](const T* underlying) { + prop->UnderlyingProp(), [addr, parent, &value](const T* underlying) { static_assert(std::is_integral_v::Value>); using underlying_limits = std::numeric_limits::Value>; @@ -53,7 +50,7 @@ PropTraits::Value PropTraits::get(const UEnumPrope void PropTraits::set(const UEnumProperty* prop, uintptr_t addr, const Value& value) { cast::with_classes>( - prop->get_underlying_prop(), [addr, value](const T* underlying) { + prop->UnderlyingProp(), [addr, value](const T* underlying) { static_assert(std::is_integral_v::Value>); using underlying_limits = std::numeric_limits::Value>; diff --git a/src/unrealsdk/unreal/classes/properties/uenumproperty.h b/src/unrealsdk/unreal/classes/properties/uenumproperty.h index 8f573df..a96757a 100644 --- a/src/unrealsdk/unreal/classes/properties/uenumproperty.h +++ b/src/unrealsdk/unreal/classes/properties/uenumproperty.h @@ -10,12 +10,27 @@ namespace unrealsdk::unreal { -class UEnum; - -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(push, 0x4) #endif +class UEnum; + +namespace offsets::generic { + +template +class UEnumProperty : public T { + public: + // NOLINTBEGIN(readability-identifier-naming) + + UProperty* UnderlyingProp; + UEnum* Enum; + + // NOLINTEND(readability-identifier-naming) +}; + +} // namespace offsets::generic + class UEnumProperty : public UProperty { public: UEnumProperty() = delete; @@ -25,27 +40,13 @@ class UEnumProperty : public UProperty { UEnumProperty& operator=(UEnumProperty&&) = delete; ~UEnumProperty() = delete; - private: - // NOLINTBEGIN(readability-identifier-naming) - - UProperty* UnderlyingProp; - UEnum* Enum; + // These fields become member functions, returning a reference into the object. +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define UNREALSDK_UENUMPROPERTY_FIELDS(X) \ + X(UProperty*, UnderlyingProp) \ + X(UEnum*, Enum) - // NOLINTEND(readability-identifier-naming) - public: - /** - * @brief Get the underlying numeric property of this enum property. - * - * @return The underlying property. - */ - [[nodiscard]] UProperty* get_underlying_prop(void) const; - - /** - * @brief Get the enum associated with this property. - * - * @return The enum. - */ - [[nodiscard]] UEnum* get_enum(void) const; + UNREALSDK_DEFINE_FIELDS_HEADER(UEnumProperty, UNREALSDK_UENUMPROPERTY_FIELDS); }; template <> @@ -61,7 +62,7 @@ struct ClassTraits { static inline const wchar_t* const NAME = L"EnumProperty"; }; -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(pop) #endif diff --git a/src/unrealsdk/unreal/classes/properties/uinterfaceproperty.cpp b/src/unrealsdk/unreal/classes/properties/uinterfaceproperty.cpp index dcac46e..104b67f 100644 --- a/src/unrealsdk/unreal/classes/properties/uinterfaceproperty.cpp +++ b/src/unrealsdk/unreal/classes/properties/uinterfaceproperty.cpp @@ -3,21 +3,22 @@ #include "unrealsdk/unreal/classes/properties/uinterfaceproperty.h" #include "unrealsdk/unreal/classes/uclass.h" #include "unrealsdk/unreal/classes/uobject.h" +#include "unrealsdk/unreal/offset_list.h" +#include "unrealsdk/unreal/offsets.h" #include "unrealsdk/unreal/structs/fimplementedinterface.h" #include "unrealsdk/unreal/wrappers/unreal_pointer.h" +#include "unrealsdk/unrealsdk.h" namespace unrealsdk::unreal { +UNREALSDK_DEFINE_FIELDS_SOURCE_FILE(UInterfaceProperty, UNREALSDK_UINTERFACEPROPERTY_FIELDS); + struct FScriptInterface { UObject* obj; // A pointer to a UObject that implements a native interface. void* iface_ptr; // Pointer to the location of the interface object within the UObject // referenced by ObjectPointer. }; -UClass* UInterfaceProperty::get_interface_class(void) const { - return this->read_field(&UInterfaceProperty::InterfaceClass); -} - PropTraits::Value PropTraits::get( const UInterfaceProperty* /*prop*/, uintptr_t addr, @@ -28,7 +29,7 @@ PropTraits::Value PropTraits::get( void PropTraits::set(const UInterfaceProperty* prop, uintptr_t addr, const Value& value) { - auto prop_iface = prop->get_interface_class(); + auto prop_iface = prop->InterfaceClass(); size_t pointer_offset = 0; @@ -37,7 +38,7 @@ void PropTraits::set(const UInterfaceProperty* prop, FImplementedInterface impl{}; if (!value->is_implementation(prop_iface, &impl)) { throw std::runtime_error("Object is not implementation of " - + (std::string)prop_iface->Name); + + (std::string)prop_iface->Name()); } pointer_offset = impl.get_pointer_offset(); } diff --git a/src/unrealsdk/unreal/classes/properties/uinterfaceproperty.h b/src/unrealsdk/unreal/classes/properties/uinterfaceproperty.h index 16fff68..ece87c3 100644 --- a/src/unrealsdk/unreal/classes/properties/uinterfaceproperty.h +++ b/src/unrealsdk/unreal/classes/properties/uinterfaceproperty.h @@ -10,12 +10,23 @@ namespace unrealsdk::unreal { +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW +#pragma pack(push, 0x4) +#endif + class UClass; class UObject; -#if defined(_MSC_VER) && defined(ARCH_X86) -#pragma pack(push, 0x4) -#endif +namespace offsets::generic { + +template +class UInterfaceProperty : public T { + public: + // NOLINTNEXTLINE(readability-identifier-naming) + UClass* InterfaceClass; +}; + +} // namespace offsets::generic class UInterfaceProperty : public UProperty { public: @@ -26,16 +37,11 @@ class UInterfaceProperty : public UProperty { UInterfaceProperty& operator=(UInterfaceProperty&&) = delete; ~UInterfaceProperty() = delete; - /** - * @brief Get the interface class of this property, which values must be an implementation of. - * - * @return This property's interface class. - */ - [[nodiscard]] UClass* get_interface_class(void) const; + // These fields become member functions, returning a reference into the object. +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define UNREALSDK_UINTERFACEPROPERTY_FIELDS(X) X(UClass*, InterfaceClass) - private: - // NOLINTNEXTLINE(readability-identifier-naming) - UClass* InterfaceClass; + UNREALSDK_DEFINE_FIELDS_HEADER(UInterfaceProperty, UNREALSDK_UINTERFACEPROPERTY_FIELDS); }; template <> @@ -53,7 +59,7 @@ struct ClassTraits { static inline const wchar_t* const NAME = L"InterfaceProperty"; }; -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(pop) #endif diff --git a/src/unrealsdk/unreal/classes/properties/umulticastdelegateproperty.cpp b/src/unrealsdk/unreal/classes/properties/umulticastdelegateproperty.cpp index b11c4a0..995896f 100644 --- a/src/unrealsdk/unreal/classes/properties/umulticastdelegateproperty.cpp +++ b/src/unrealsdk/unreal/classes/properties/umulticastdelegateproperty.cpp @@ -1,31 +1,33 @@ #include "unrealsdk/pch.h" #include "unrealsdk/unreal/classes/properties/umulticastdelegateproperty.h" +#include "unrealsdk/unreal/offset_list.h" +#include "unrealsdk/unreal/offsets.h" #include "unrealsdk/unreal/prop_traits.h" #include "unrealsdk/unreal/structs/fscriptdelegate.h" #include "unrealsdk/unreal/structs/tarray.h" #include "unrealsdk/unreal/structs/tarray_funcs.h" #include "unrealsdk/unreal/wrappers/unreal_pointer.h" #include "unrealsdk/unreal/wrappers/wrapped_multicast_delegate.h" +#include "unrealsdk/unrealsdk.h" namespace unrealsdk::unreal { -UFunction* UMulticastDelegateProperty::get_signature(void) const { - return this->read_field(&UMulticastDelegateProperty::Signature); -} +UNREALSDK_DEFINE_FIELDS_SOURCE_FILE(UMulticastDelegateProperty, + UNREALSDK_UMULTICASTDELEGATEPROPERTY_FIELDS); PropTraits::Value PropTraits::get( const UMulticastDelegateProperty* prop, uintptr_t addr, const UnrealPointer& parent) { - return {prop->get_signature(), reinterpret_cast*>(addr), parent}; + return {prop->Signature(), reinterpret_cast*>(addr), parent}; } void PropTraits::set(const UMulticastDelegateProperty* prop, uintptr_t addr, const Value& value) { - if (value.signature != prop->get_signature()) { + if (value.signature != prop->Signature()) { throw std::runtime_error("Multicast delegate signature doesn't match existing signature of " - + (std::string)prop->get_signature()->Name); + + (std::string)prop->Signature()->Name()); } auto arr = reinterpret_cast*>(addr); diff --git a/src/unrealsdk/unreal/classes/properties/umulticastdelegateproperty.h b/src/unrealsdk/unreal/classes/properties/umulticastdelegateproperty.h index 0ed7f03..485bd3a 100644 --- a/src/unrealsdk/unreal/classes/properties/umulticastdelegateproperty.h +++ b/src/unrealsdk/unreal/classes/properties/umulticastdelegateproperty.h @@ -12,12 +12,23 @@ namespace unrealsdk::unreal { -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(push, 0x4) #endif class UFunction; +namespace offsets::generic { + +template +class UMulticastDelegateProperty : public T { + public: + // NOLINTNEXTLINE(readability-identifier-naming) + UFunction* Signature; +}; + +} // namespace offsets::generic + class UMulticastDelegateProperty : public UProperty { public: UMulticastDelegateProperty() = delete; @@ -27,17 +38,12 @@ class UMulticastDelegateProperty : public UProperty { UMulticastDelegateProperty& operator=(UMulticastDelegateProperty&&) = delete; ~UMulticastDelegateProperty() = delete; - private: - // NOLINTNEXTLINE(readability-identifier-naming) - UFunction* Signature; + // These fields become member functions, returning a reference into the object. +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define UNREALSDK_UMULTICASTDELEGATEPROPERTY_FIELDS(X) X(UFunction*, Signature) - public: - /** - * @brief Get the function holding this delegate's signature. - * - * @return The signature function. - */ - [[nodiscard]] UFunction* get_signature(void) const; + UNREALSDK_DEFINE_FIELDS_HEADER(UMulticastDelegateProperty, + UNREALSDK_UMULTICASTDELEGATEPROPERTY_FIELDS); }; template <> @@ -57,7 +63,7 @@ struct ClassTraits { static inline const wchar_t* const NAME = L"MulticastDelegateProperty"; }; -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(pop) #endif diff --git a/src/unrealsdk/unreal/classes/properties/uobjectproperty.cpp b/src/unrealsdk/unreal/classes/properties/uobjectproperty.cpp index 5121bdd..67f1ea4 100644 --- a/src/unrealsdk/unreal/classes/properties/uobjectproperty.cpp +++ b/src/unrealsdk/unreal/classes/properties/uobjectproperty.cpp @@ -2,13 +2,14 @@ #include "unrealsdk/unreal/classes/properties/uobjectproperty.h" #include "unrealsdk/unreal/classes/uclass.h" +#include "unrealsdk/unreal/offset_list.h" +#include "unrealsdk/unreal/offsets.h" #include "unrealsdk/unreal/wrappers/unreal_pointer.h" +#include "unrealsdk/unrealsdk.h" namespace unrealsdk::unreal { -UClass* UObjectProperty::get_property_class(void) const { - return this->read_field(&UObjectProperty::PropertyClass); -} +UNREALSDK_DEFINE_FIELDS_SOURCE_FILE(UObjectProperty, UNREALSDK_UOBJECTPROPERTY_FIELDS); PropTraits::Value PropTraits::get( const UObjectProperty* /*prop*/, @@ -22,9 +23,9 @@ void PropTraits::set(const UObjectProperty* prop, const Value& value) { // Ensure the object is of a valid class if (value != nullptr) { - auto prop_cls = prop->get_property_class(); + auto prop_cls = prop->PropertyClass(); if (!value->is_instance(prop_cls)) { - throw std::runtime_error("Object is not instance of " + (std::string)prop_cls->Name); + throw std::runtime_error("Object is not instance of " + (std::string)prop_cls->Name()); } } diff --git a/src/unrealsdk/unreal/classes/properties/uobjectproperty.h b/src/unrealsdk/unreal/classes/properties/uobjectproperty.h index 5208c2f..3157486 100644 --- a/src/unrealsdk/unreal/classes/properties/uobjectproperty.h +++ b/src/unrealsdk/unreal/classes/properties/uobjectproperty.h @@ -10,12 +10,23 @@ namespace unrealsdk::unreal { +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW +#pragma pack(push, 0x4) +#endif + class UClass; class UObject; -#if defined(_MSC_VER) && defined(ARCH_X86) -#pragma pack(push, 0x4) -#endif +namespace offsets::generic { + +template +class UObjectProperty : public T { + public: + // NOLINTNEXTLINE(readability-identifier-naming) + UClass* PropertyClass; +}; + +} // namespace offsets::generic class UObjectProperty : public UProperty { public: @@ -26,16 +37,11 @@ class UObjectProperty : public UProperty { UObjectProperty& operator=(UObjectProperty&&) = delete; ~UObjectProperty() = delete; - /** - * @brief Get the class of this property, which values must be an instance of. - * - * @return This property's class. - */ - [[nodiscard]] UClass* get_property_class(void) const; + // These fields become member functions, returning a reference into the object. +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define UNREALSDK_UOBJECTPROPERTY_FIELDS(X) X(UClass*, PropertyClass) - private: - // NOLINTNEXTLINE(readability-identifier-naming) - UClass* PropertyClass; + UNREALSDK_DEFINE_FIELDS_HEADER(UObject, UNREALSDK_UOBJECTPROPERTY_FIELDS); }; template <> @@ -53,7 +59,7 @@ struct ClassTraits { static inline const wchar_t* const NAME = L"ObjectProperty"; }; -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(pop) #endif diff --git a/src/unrealsdk/unreal/classes/properties/ustrproperty.h b/src/unrealsdk/unreal/classes/properties/ustrproperty.h index e42eace..2bd6630 100644 --- a/src/unrealsdk/unreal/classes/properties/ustrproperty.h +++ b/src/unrealsdk/unreal/classes/properties/ustrproperty.h @@ -10,7 +10,7 @@ namespace unrealsdk::unreal { -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(push, 0x4) #endif @@ -38,7 +38,7 @@ struct ClassTraits { static inline const wchar_t* const NAME = L"StrProperty"; }; -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(pop) #endif diff --git a/src/unrealsdk/unreal/classes/properties/ustructproperty.cpp b/src/unrealsdk/unreal/classes/properties/ustructproperty.cpp index 7413e30..32776db 100644 --- a/src/unrealsdk/unreal/classes/properties/ustructproperty.cpp +++ b/src/unrealsdk/unreal/classes/properties/ustructproperty.cpp @@ -2,36 +2,38 @@ #include "unrealsdk/unreal/classes/properties/ustructproperty.h" #include "unrealsdk/unreal/classes/uscriptstruct.h" +#include "unrealsdk/unreal/offset_list.h" +#include "unrealsdk/unreal/offsets.h" #include "unrealsdk/unreal/prop_traits.h" #include "unrealsdk/unreal/wrappers/unreal_pointer.h" #include "unrealsdk/unreal/wrappers/wrapped_struct.h" +#include "unrealsdk/unrealsdk.h" namespace unrealsdk::unreal { -[[nodiscard]] UScriptStruct* UStructProperty::get_inner_struct(void) const { - return this->read_field(&UStructProperty::Struct); -} +UNREALSDK_DEFINE_FIELDS_SOURCE_FILE(UStructProperty, UNREALSDK_USTRUCTPROPERTY_FIELDS); PropTraits::Value PropTraits::get( const UStructProperty* prop, uintptr_t addr, const UnrealPointer& parent) { - auto this_struct = prop->get_inner_struct(); + auto this_struct = prop->Struct(); return {this_struct, reinterpret_cast(addr), parent}; } void PropTraits::set(const UStructProperty* prop, uintptr_t addr, const Value& value) { - auto this_struct = prop->get_inner_struct(); + auto this_struct = prop->Struct(); if (value.type != this_struct) { - throw std::runtime_error("Struct is not an instance of " + (std::string)this_struct->Name); + throw std::runtime_error("Struct is not an instance of " + + (std::string)this_struct->Name()); } copy_struct(addr, value); } void PropTraits::destroy(const UStructProperty* prop, uintptr_t addr) { - destroy_struct(prop->get_inner_struct(), addr); + destroy_struct(prop->Struct(), addr); } } // namespace unrealsdk::unreal diff --git a/src/unrealsdk/unreal/classes/properties/ustructproperty.h b/src/unrealsdk/unreal/classes/properties/ustructproperty.h index 9cad83f..ef8cd16 100644 --- a/src/unrealsdk/unreal/classes/properties/ustructproperty.h +++ b/src/unrealsdk/unreal/classes/properties/ustructproperty.h @@ -1,5 +1,5 @@ -#ifndef UNREALSDK_UNREAL_CLASSES_PROPERTIES_USTRUCTPROPERTY_H -#define UNREALSDK_UNREAL_CLASSES_PROPERTIES_USTRUCTPROPERTY_H +#ifndef PYUNREALSDK_LIBS_UNREALSDK_SRC_UNREALSDK_UNREAL_CLASSES_PROPERTIES_USTRUCTPROPERTY_H +#define PYUNREALSDK_LIBS_UNREALSDK_SRC_UNREALSDK_UNREAL_CLASSES_PROPERTIES_USTRUCTPROPERTY_H #include "unrealsdk/pch.h" @@ -11,12 +11,23 @@ namespace unrealsdk::unreal { -class UScriptStruct; - -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(push, 0x4) #endif +class UScriptStruct; + +namespace offsets::generic { + +template +class UStructProperty : public T { + public: + // NOLINTNEXTLINE(readability-identifier-naming) + unreal::UScriptStruct* Struct; +}; + +} // namespace offsets::generic + class UStructProperty : public UProperty { public: UStructProperty() = delete; @@ -26,16 +37,11 @@ class UStructProperty : public UProperty { UStructProperty& operator=(UStructProperty&&) = delete; ~UStructProperty() = delete; - /** - * @brief Get the struct type of this property, which values must be an instance of. - * - * @return This property's struct type. - */ - [[nodiscard]] UScriptStruct* get_inner_struct(void) const; + // These fields become member functions, returning a reference into the object. +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define UNREALSDK_USTRUCTPROPERTY_FIELDS(X) X(UScriptStruct*, Struct) - private: - // NOLINTNEXTLINE(readability-identifier-naming) - UScriptStruct* Struct; + UNREALSDK_DEFINE_FIELDS_HEADER(UStructProperty, UNREALSDK_USTRUCTPROPERTY_FIELDS); }; template <> @@ -54,10 +60,10 @@ struct ClassTraits { static inline const wchar_t* const NAME = L"StructProperty"; }; -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(pop) #endif } // namespace unrealsdk::unreal -#endif /* UNREALSDK_UNREAL_CLASSES_PROPERTIES_USTRUCTPROPERTY_H */ +#endif /* PYUNREALSDK_LIBS_UNREALSDK_SRC_UNREALSDK_UNREAL_CLASSES_PROPERTIES_USTRUCTPROPERTY_H */ diff --git a/src/unrealsdk/unreal/classes/properties/utextproperty.h b/src/unrealsdk/unreal/classes/properties/utextproperty.h index a0ebaab..38947e3 100644 --- a/src/unrealsdk/unreal/classes/properties/utextproperty.h +++ b/src/unrealsdk/unreal/classes/properties/utextproperty.h @@ -10,7 +10,7 @@ namespace unrealsdk::unreal { -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(push, 0x4) #endif @@ -38,7 +38,7 @@ struct ClassTraits { static inline const wchar_t* const NAME = L"TextProperty"; }; -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(pop) #endif diff --git a/src/unrealsdk/unreal/classes/properties/uweakobjectproperty.cpp b/src/unrealsdk/unreal/classes/properties/uweakobjectproperty.cpp index 1defd65..54e5bc1 100644 --- a/src/unrealsdk/unreal/classes/properties/uweakobjectproperty.cpp +++ b/src/unrealsdk/unreal/classes/properties/uweakobjectproperty.cpp @@ -22,9 +22,9 @@ void PropTraits::set(const UWeakObjectProperty* prop, const Value& value) { // Ensure the object is of a valid class if (value != nullptr) { - auto prop_cls = prop->get_property_class(); + auto prop_cls = prop->PropertyClass(); if (!value->is_instance(prop_cls)) { - throw std::runtime_error("Object is not instance of " + (std::string)prop_cls->Name); + throw std::runtime_error("Object is not instance of " + (std::string)prop_cls->Name()); } } diff --git a/src/unrealsdk/unreal/classes/properties/uweakobjectproperty.h b/src/unrealsdk/unreal/classes/properties/uweakobjectproperty.h index 149b973..a38fe99 100644 --- a/src/unrealsdk/unreal/classes/properties/uweakobjectproperty.h +++ b/src/unrealsdk/unreal/classes/properties/uweakobjectproperty.h @@ -12,7 +12,7 @@ namespace unrealsdk::unreal { class UObject; -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(push, 0x4) #endif @@ -41,7 +41,7 @@ struct ClassTraits { static inline const wchar_t* const NAME = L"WeakObjectProperty"; }; -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(pop) #endif diff --git a/src/unrealsdk/unreal/classes/uclass.cpp b/src/unrealsdk/unreal/classes/uclass.cpp index 184220a..87ff5af 100644 --- a/src/unrealsdk/unreal/classes/uclass.cpp +++ b/src/unrealsdk/unreal/classes/uclass.cpp @@ -1,59 +1,15 @@ #include "unrealsdk/pch.h" -#include "unrealsdk/config.h" #include "unrealsdk/unreal/class_name.h" #include "unrealsdk/unreal/classes/uclass.h" #include "unrealsdk/unreal/find_class.h" +#include "unrealsdk/unreal/offset_list.h" +#include "unrealsdk/unreal/offsets.h" +#include "unrealsdk/unrealsdk.h" namespace unrealsdk::unreal { -decltype(UClass::ClassDefaultObject_internal)& UClass::ClassDefaultObject(void) { - return this->get_field(&UClass::ClassDefaultObject_internal); -} -[[nodiscard]] const decltype(UClass::ClassDefaultObject_internal)& UClass::ClassDefaultObject( - void) const { - return this->get_field(&UClass::ClassDefaultObject_internal); -} - -// To further complicate things, UClass::Interfaces also shifts between BL2 + TPS -#ifdef UE3 - -// This is awful hacky code to get a working release out sooner, the whole system needs a rework. -// Check if the size of UClass is that we've observed in TPS, and if so use it's hardcoded offset. -namespace { - -const constexpr auto UCLASS_SIZE_TPS = 0x18C; -const constexpr auto UCLASS_INTERFACES_OFFSET_TPS = 0x160; - -} // namespace - -decltype(UClass::Interfaces_internal)& UClass::Interfaces(void) { - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) - return const_cast( - const_cast(this)->Interfaces()); -} -[[nodiscard]] const decltype(UClass::Interfaces_internal)& UClass::Interfaces(void) const { - static const auto use_tps_offset = - unrealsdk::config::get_bool("unrealsdk.__force_uclass_interfaces_tps_offset") - .value_or(this->Class->get_struct_size() == UCLASS_SIZE_TPS); - - if (use_tps_offset) { - return *reinterpret_cast( - reinterpret_cast(this) + UCLASS_INTERFACES_OFFSET_TPS); - } - - return this->get_field(&UClass::Interfaces_internal); -} -#else - -decltype(UClass::Interfaces_internal)& UClass::Interfaces(void) { - return this->get_field(&UClass::Interfaces_internal); -} -[[nodiscard]] const decltype(UClass::Interfaces_internal)& UClass::Interfaces(void) const { - return this->get_field(&UClass::Interfaces_internal); -} - -#endif +UNREALSDK_DEFINE_FIELDS_SOURCE_FILE(UClass, UNREALSDK_UCLASS_FIELDS); bool UClass::implements(const UClass* iface, FImplementedInterface* impl_out) const { // For each class in the inheritance chain diff --git a/src/unrealsdk/unreal/classes/uclass.h b/src/unrealsdk/unreal/classes/uclass.h index 5beb0aa..194243c 100644 --- a/src/unrealsdk/unreal/classes/uclass.h +++ b/src/unrealsdk/unreal/classes/uclass.h @@ -10,7 +10,7 @@ namespace unrealsdk::unreal { struct FImplementedInterface; -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(push, 0x4) #endif @@ -28,36 +28,13 @@ class UClass : public UStruct { UClass& operator=(UClass&&) = delete; ~UClass() = delete; - // NOLINTBEGIN(readability-magic-numbers, readability-identifier-naming) + // These fields become member functions, returning a reference into the object. +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define UNREALSDK_UCLASS_FIELDS(X) \ + X(UObject*, ClassDefaultObject) \ + X(TArray, Interfaces) - private: -#ifdef UE4 - uint8_t UnknownData00[0x70]; - UObject* ClassDefaultObject_internal; - uint8_t UnknownData01[0xA0]; - TArray Interfaces_internal; -#else - // Misc Fields I found within this block in BL2, but which we don't care about enough for me to - // find in UE4, or to want to increase the compile times by including - - // 0xE8: TArray ClassReps; - // 0xF4: TArray NetFields; - // 0x100: TArray HideCategories; - // 0x10C: TArray AutoExpandCategories; - - uint8_t UnknownData00[0xCC]; - UObject* ClassDefaultObject_internal; - uint8_t UnknownData01[0x48]; - TArray Interfaces_internal; -#endif - - public: - decltype(ClassDefaultObject_internal)& ClassDefaultObject(void); - [[nodiscard]] const decltype(ClassDefaultObject_internal)& ClassDefaultObject(void) const; - decltype(Interfaces_internal)& Interfaces(void); - [[nodiscard]] const decltype(Interfaces_internal)& Interfaces(void) const; - - // NOLINTEND(readability-magic-numbers, readability-identifier-naming) + UNREALSDK_DEFINE_FIELDS_HEADER(UClass, UNREALSDK_UCLASS_FIELDS); /** * @brief Checks if this class implements an interface. @@ -80,7 +57,7 @@ struct ClassTraits { #pragma GCC diagnostic pop #endif -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(pop) #endif diff --git a/src/unrealsdk/unreal/classes/uconst.cpp b/src/unrealsdk/unreal/classes/uconst.cpp new file mode 100644 index 0000000..692d55e --- /dev/null +++ b/src/unrealsdk/unreal/classes/uconst.cpp @@ -0,0 +1,10 @@ +#include "unrealsdk/unreal/classes/uconst.h" +#include "unrealsdk/unreal/offset_list.h" +#include "unrealsdk/unreal/offsets.h" +#include "unrealsdk/unrealsdk.h" + +namespace unrealsdk::unreal { + +UNREALSDK_DEFINE_FIELDS_SOURCE_FILE(UConst, UNREALSDK_UCONST_FIELDS); + +} diff --git a/src/unrealsdk/unreal/classes/uconst.h b/src/unrealsdk/unreal/classes/uconst.h index 863bddc..f64b285 100644 --- a/src/unrealsdk/unreal/classes/uconst.h +++ b/src/unrealsdk/unreal/classes/uconst.h @@ -7,6 +7,17 @@ namespace unrealsdk::unreal { +namespace offsets::generic { + +template +class UConst : public T { + public: + // NOLINTNEXTLINE(readability-identifier-naming) + UnmanagedFString Value; +}; + +} // namespace offsets::generic + class UConst : public UField { public: UConst() = delete; @@ -16,8 +27,11 @@ class UConst : public UField { UConst& operator=(UConst&&) = delete; ~UConst() = delete; - // NOLINTNEXTLINE(readability-identifier-naming) - UnmanagedFString Value; + // These fields become member functions, returning a reference into the object. +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define UNREALSDK_UCONST_FIELDS(X) X(UnmanagedFString, Value) + + UNREALSDK_DEFINE_FIELDS_HEADER(UConst, UNREALSDK_UCONST_FIELDS); }; template <> diff --git a/src/unrealsdk/unreal/classes/uenum.cpp b/src/unrealsdk/unreal/classes/uenum.cpp index 3c2479a..c469df8 100644 --- a/src/unrealsdk/unreal/classes/uenum.cpp +++ b/src/unrealsdk/unreal/classes/uenum.cpp @@ -1,36 +1,49 @@ #include "unrealsdk/pch.h" #include "unrealsdk/unreal/classes/uenum.h" -#include "unrealsdk/unreal/classes/properties/copyable_property.h" -#include "unrealsdk/unreal/classes/properties/uobjectproperty.h" -#include "unrealsdk/unreal/classes/ufield.h" -#include "unrealsdk/unreal/classes/ufunction.h" -#include "unrealsdk/unreal/classes/uobject.h" -#include "unrealsdk/unreal/classes/uobject_funcs.h" +#include "unrealsdk/game/bl2/offsets.h" +#include "unrealsdk/game/bl3/offsets.h" +#include "unrealsdk/unreal/offset_list.h" +#include "unrealsdk/unreal/offsets.h" #include "unrealsdk/unreal/structs/fname.h" -#include "unrealsdk/unreal/wrappers/bound_function.h" +#include "unrealsdk/unrealsdk.h" namespace unrealsdk::unreal { -#ifdef UE4 +UNREALSDK_DEFINE_FIELDS_SOURCE_FILE(UEnum, UNREALSDK_UENUM_FIELDS); + +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_OAK + std::unordered_map UEnum::get_names(void) const { std::unordered_map output; - for (size_t i = 0; i < this->Names.size(); i++) { - auto pair = this->Names.at(i); - output.emplace(pair.key, pair.value); + auto names = this->Names(); + for (size_t i = 0; i < names.size(); i++) { + auto pair = names.at(i); + + // Oak enums include the enum class name and a namespace separator before the value's name + // If we see it, strip it + const std::wstring str_key{pair.key}; + auto after_colons = str_key.find_first_not_of(L':', str_key.find_first_of(L':')); + + output.emplace(after_colons == std::string::npos ? pair.key : str_key.substr(after_colons), + pair.value); } return output; } -#else +#elif UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW std::unordered_map UEnum::get_names(void) const { std::unordered_map output; - for (size_t i = 0; i < this->Names.size(); i++) { - output.emplace(this->Names.at(i), i); + auto names = this->Names(); + for (size_t i = 0; i < names.size(); i++) { + // Willow enums just use the raw name, and are always stored in order + output.emplace(names.at(i), i); } return output; } +#else +#error Unknown SDK flavour #endif } // namespace unrealsdk::unreal diff --git a/src/unrealsdk/unreal/classes/uenum.h b/src/unrealsdk/unreal/classes/uenum.h index 3796554..fd6584c 100644 --- a/src/unrealsdk/unreal/classes/uenum.h +++ b/src/unrealsdk/unreal/classes/uenum.h @@ -11,7 +11,7 @@ namespace unrealsdk::unreal { -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(push, 0x4) #endif @@ -29,24 +29,25 @@ class UEnum : public UField { UEnum& operator=(UEnum&&) = delete; ~UEnum() = delete; - private: - // NOLINTBEGIN(readability-identifier-naming) - -#ifdef UE4 - UnmanagedFString CppType; - TArray> Names; - int64_t CppForm; +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW + using names_type = TArray; +#elif UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_OAK + using names_type = TArray >; #else - TArray Names; +#error Unknown sdk flavour #endif - // NOLINTEND(readability-identifier-naming) + // These fields become member functions, returning a reference into the object. +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define UNREALSDK_UENUM_FIELDS(X) X(names_type, Names) + + UNREALSDK_DEFINE_FIELDS_HEADER(UEnum, UNREALSDK_UENUM_FIELDS); - public: /** - * @brief Get the enum values which have assigned names. + * @brief Converts the enum names into a more usable name to value map. + * @note Keys are always the raw enum names, rather than `MyEnum::Entry` it's always `Entry`. * - * @return A map of integer values to their associated name. + * @return A map of names to their associated integer values. */ [[nodiscard]] std::unordered_map get_names(void) const; }; @@ -60,10 +61,10 @@ struct ClassTraits { #pragma GCC diagnostic pop #endif -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(pop) #endif } // namespace unrealsdk::unreal -#endif /* UNREALSDK_UNREAL_CLASSES_UENUM_H */ +#endif /* PYUNREALSDK_LIBS_UNREALSDK_SRC_UNREALSDK_UNREAL_CLASSES_UENUM_H */ diff --git a/src/unrealsdk/unreal/classes/ufield.cpp b/src/unrealsdk/unreal/classes/ufield.cpp new file mode 100644 index 0000000..9bccedf --- /dev/null +++ b/src/unrealsdk/unreal/classes/ufield.cpp @@ -0,0 +1,10 @@ +#include "unrealsdk/unreal/classes/ufield.h" +#include "unrealsdk/unreal/offset_list.h" +#include "unrealsdk/unreal/offsets.h" +#include "unrealsdk/unrealsdk.h" + +namespace unrealsdk::unreal { + +UNREALSDK_DEFINE_FIELDS_SOURCE_FILE(UField, UNREALSDK_UFIELD_FIELDS); + +} diff --git a/src/unrealsdk/unreal/classes/ufield.h b/src/unrealsdk/unreal/classes/ufield.h index 11a20d8..9229555 100644 --- a/src/unrealsdk/unreal/classes/ufield.h +++ b/src/unrealsdk/unreal/classes/ufield.h @@ -6,10 +6,26 @@ namespace unrealsdk::unreal { -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(push, 0x4) #endif +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-private-field" +#endif + +namespace offsets::generic { + +template +class UField : public T { + public: + // NOLINTNEXTLINE(readability-identifier-naming) + UField* Next; +}; + +} // namespace offsets::generic + class UField : public UObject { public: UField() = delete; @@ -19,8 +35,11 @@ class UField : public UObject { UField& operator=(UField&&) = delete; ~UField() = delete; - // NOLINTNEXTLINE(readability-identifier-naming) - UField* Next; + // These fields become member functions, returning a reference into the object. +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define UNREALSDK_UFIELD_FIELDS(X) X(UField*, Next) + + UNREALSDK_DEFINE_FIELDS_HEADER(UField, UNREALSDK_UFIELD_FIELDS); }; template <> @@ -28,7 +47,10 @@ struct ClassTraits { static inline const wchar_t* const NAME = L"Field"; }; -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(__clang__) +#pragma clang diagnostic pop +#endif +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(pop) #endif diff --git a/src/unrealsdk/unreal/classes/ufunction.cpp b/src/unrealsdk/unreal/classes/ufunction.cpp index 8202260..18549ff 100644 --- a/src/unrealsdk/unreal/classes/ufunction.cpp +++ b/src/unrealsdk/unreal/classes/ufunction.cpp @@ -2,39 +2,17 @@ #include "unrealsdk/unreal/classes/ufunction.h" #include "unrealsdk/unreal/classes/uproperty.h" +#include "unrealsdk/unreal/offset_list.h" +#include "unrealsdk/unreal/offsets.h" +#include "unrealsdk/unrealsdk.h" namespace unrealsdk::unreal { -decltype(UFunction::FunctionFlags_internal)& UFunction::FunctionFlags(void) { - return this->get_field(&UFunction::FunctionFlags_internal); -} -[[nodiscard]] const decltype(UFunction::FunctionFlags_internal)& UFunction::FunctionFlags( - void) const { - return this->get_field(&UFunction::FunctionFlags_internal); -} -decltype(UFunction::NumParams_internal)& UFunction::NumParams(void) { - return this->get_field(&UFunction::NumParams_internal); -} -[[nodiscard]] const decltype(UFunction::NumParams_internal)& UFunction::NumParams(void) const { - return this->get_field(&UFunction::NumParams_internal); -} -decltype(UFunction::ParamsSize_internal)& UFunction::ParamsSize(void) { - return this->get_field(&UFunction::ParamsSize_internal); -} -[[nodiscard]] const decltype(UFunction::ParamsSize_internal)& UFunction::ParamsSize(void) const { - return this->get_field(&UFunction::ParamsSize_internal); -} -decltype(UFunction::ReturnValueOffset_internal)& UFunction::ReturnValueOffset(void) { - return this->get_field(&UFunction::ReturnValueOffset_internal); -} -[[nodiscard]] const decltype(UFunction::ReturnValueOffset_internal)& UFunction::ReturnValueOffset( - void) const { - return this->get_field(&UFunction::ReturnValueOffset_internal); -} +UNREALSDK_DEFINE_FIELDS_SOURCE_FILE(UFunction, UNREALSDK_UFUNCTION_FIELDS); UProperty* UFunction::find_return_param(void) const { for (auto prop : this->properties()) { - if ((prop->PropertyFlags & UProperty::PROP_FLAG_RETURN) != 0) { + if ((prop->PropertyFlags() & UProperty::PROP_FLAG_RETURN) != 0) { return prop; } } diff --git a/src/unrealsdk/unreal/classes/ufunction.h b/src/unrealsdk/unreal/classes/ufunction.h index e412822..3f7bb7a 100644 --- a/src/unrealsdk/unreal/classes/ufunction.h +++ b/src/unrealsdk/unreal/classes/ufunction.h @@ -7,7 +7,7 @@ namespace unrealsdk::unreal { -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(push, 0x4) #endif @@ -27,41 +27,15 @@ class UFunction : public UStruct { UFunction& operator=(UFunction&&) = delete; ~UFunction() = delete; - // NOLINTBEGIN(readability-magic-numbers, readability-identifier-naming) + // These fields become member functions, returning a reference into the object. +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define UNREALSDK_UFUNCTION_FIELDS(X) \ + X(uint32_t, FunctionFlags) \ + X(uint8_t, NumParams) \ + X(uint16_t, ParamsSize) \ + X(uint16_t, ReturnValueOffset) - private: -#ifdef UE4 - uint32_t FunctionFlags_internal; - uint8_t NumParams_internal; - uint16_t ParamsSize_internal; - uint16_t ReturnValueOffset_internal; - uint16_t RPCId; - uint16_t RPCResponseId; - UProperty* FirstPropertyToInit; - UFunction* EventGraphFunction; - int32_t EventGraphCallOffset; - void* Func; -#else - uint32_t FunctionFlags_internal; - uint16_t iNative; - uint16_t RepOffset; - FName FriendlyName; - uint8_t OperPrecedence; - uint8_t NumParams_internal; - uint16_t ParamsSize_internal; - uint16_t ReturnValueOffset_internal; - uint8_t UnknownData00[0x6]; - void* Func; -#endif - public: - decltype(FunctionFlags_internal)& FunctionFlags(void); - [[nodiscard]] const decltype(FunctionFlags_internal)& FunctionFlags(void) const; - decltype(NumParams_internal)& NumParams(void); - [[nodiscard]] const decltype(NumParams_internal)& NumParams(void) const; - decltype(ParamsSize_internal)& ParamsSize(void); - [[nodiscard]] const decltype(ParamsSize_internal)& ParamsSize(void) const; - decltype(ReturnValueOffset_internal)& ReturnValueOffset(void); - [[nodiscard]] const decltype(ReturnValueOffset_internal)& ReturnValueOffset(void) const; + UNREALSDK_DEFINE_FIELDS_HEADER(UFunction, UNREALSDK_UFUNCTION_FIELDS); /** * @brief Finds the return param for this function (if it exists). @@ -69,8 +43,6 @@ class UFunction : public UStruct { * @return The return param, or `nullptr` if none exists. */ [[nodiscard]] UProperty* find_return_param(void) const; - - // NOLINTEND(readability-magic-numbers, readability-identifier-naming) }; template <> @@ -82,7 +54,7 @@ struct ClassTraits { #pragma GCC diagnostic pop #endif -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(pop) #endif diff --git a/src/unrealsdk/unreal/classes/uobject.cpp b/src/unrealsdk/unreal/classes/uobject.cpp index 28a8242..14bff53 100644 --- a/src/unrealsdk/unreal/classes/uobject.cpp +++ b/src/unrealsdk/unreal/classes/uobject.cpp @@ -6,6 +6,8 @@ #include "unrealsdk/unreal/classes/uobject.h" #include "unrealsdk/unreal/classes/uproperty.h" #include "unrealsdk/unreal/classes/ustruct_funcs.h" +#include "unrealsdk/unreal/offset_list.h" +#include "unrealsdk/unreal/offsets.h" #include "unrealsdk/unreal/prop_traits.h" #include "unrealsdk/unreal/structs/fname.h" #include "unrealsdk/unreal/structs/fpropertychangeevent.h" @@ -14,6 +16,8 @@ namespace unrealsdk::unreal { +UNREALSDK_DEFINE_FIELDS_SOURCE_FILE(UObject, UNREALSDK_UOBJECT_FIELDS); + #ifdef UNREALSDK_INTERNAL_PATH_NAME /** @@ -53,11 +57,11 @@ std::wstring UObject::get_path_name(void) const { #endif bool UObject::is_instance(const UClass* cls) const { - return this->Class->inherits(cls); + return this->Class()->inherits(cls); } bool UObject::is_implementation(const UClass* iface, FImplementedInterface* impl_out) const { - return this->Class->implements(iface, impl_out); + return this->Class()->implements(iface, impl_out); } template <> @@ -80,20 +84,23 @@ BoundFunction UObject::get(const UFunction* prop, size } template <> BoundFunction UObject::get(const FName& name, size_t idx) const { - return this->get(this->Class->find_func_and_validate(name), idx); + return this->get(this->Class()->find_func_and_validate(name), idx); } void UObject::post_edit_change_property(const FName& name) const { - this->post_edit_change_property(this->Class->find_prop(name)); + this->post_edit_change_property(this->Class()->find_prop(name)); } void UObject::post_edit_change_property(UProperty* prop) const { FPropertyChangedEvent event{prop}; -#ifdef UE3 +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW constexpr auto default_idx = 19; -#else +#elif UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_OAK constexpr auto default_idx = 78; +#else +#error Unknown SDK flavour #endif + static auto idx = config::get_int("unrealsdk.uobject_post_edit_change_property_vf_index") .value_or(default_idx); @@ -106,11 +113,14 @@ void UObject::post_edit_change_chain_property(UProperty* prop, FEditPropertyChain edit_chain{chain}; FPropertyChangedChainEvent event{prop, &edit_chain}; -#ifdef UE3 +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW constexpr auto default_idx = 18; -#else +#elif UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_OAK constexpr auto default_idx = 77; +#else +#error Unknown SDK flavour #endif + static auto idx = config::get_int("unrealsdk.uobject_post_edit_change_chain_property_vf_index") .value_or(default_idx); // NOLINT(readability-magic-numbers) diff --git a/src/unrealsdk/unreal/classes/uobject.h b/src/unrealsdk/unreal/classes/uobject.h index 2e7325e..af706b2 100644 --- a/src/unrealsdk/unreal/classes/uobject.h +++ b/src/unrealsdk/unreal/classes/uobject.h @@ -4,12 +4,13 @@ #include "unrealsdk/pch.h" #include "unrealsdk/unreal/class_traits.h" +#include "unrealsdk/unreal/offsets.h" #include "unrealsdk/unreal/prop_traits.h" #include "unrealsdk/unreal/structs/fname.h" namespace unrealsdk::unreal { -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(push, 0x4) #endif @@ -29,45 +30,24 @@ class UObject { uintptr_t* vftable; - // NOLINTBEGIN(readability-identifier-naming) - -#if UE4 - int32_t ObjectFlags; - int32_t InternalIndex; - UClass* Class; - FName Name; - UObject* Outer; +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_OAK + using object_flags_type = uint32_t; +#elif UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW + using object_flags_type = uint64_t; #else - private: - void* HashNext; - - public: - uint64_t ObjectFlags; - - private: - void* HashOuterNext; - void* StateFrame; - class UObject* _Linker; - void* _LinkerIndex; - - public: - int InternalIndex; - - private: - int NetIndex; - - public: - UObject* Outer; - FName Name; - UClass* Class; - - private: - UObject* ObjectArchetype; - - public: +#error Unknown SDK flavour #endif - // NOLINTEND(readability-identifier-naming) + // These fields become member functions, returning a reference into the object. +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define UNREALSDK_UOBJECT_FIELDS(X) \ + X(object_flags_type, ObjectFlags) \ + X(int32_t, InternalIndex) \ + X(UClass*, Class) \ + X(FName, Name) \ + X(UObject*, Outer) + + UNREALSDK_DEFINE_FIELDS_HEADER(UObject, UNREALSDK_UOBJECT_FIELDS); /** * @brief Calls a virtual function on this object. @@ -80,21 +60,21 @@ class UObject { */ template R call_virtual_function(size_t index, Args... args) const { -#ifdef ARCH_X86 + if constexpr (sizeof(void*) == sizeof(uint32_t)) { #if defined(__MINGW32__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wattributes" // thiscall on non-class #endif - // NOLINTNEXTLINE(modernize-use-using) - need a typedef for the __thiscall - typedef R(__thiscall * func_ptr)(const UObject*, Args...); + // NOLINTNEXTLINE(modernize-use-using) - need a typedef for the __thiscall + typedef R(__thiscall * func_ptr)(const UObject*, Args...); #if defined(__MINGW32__) #pragma GCC diagnostic pop #endif -#else - using func_ptr = R (*)(const UObject*, Args...); -#endif - - return reinterpret_cast(this->vftable[index])(this, args...); + return reinterpret_cast(this->vftable[index])(this, args...); + } else { + using func_ptr = R (*)(const UObject*, Args...); + return reinterpret_cast(this->vftable[index])(this, args...); + } } /** @@ -181,7 +161,7 @@ struct ClassTraits { static inline const wchar_t* const NAME = L"Object"; }; -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(pop) #endif diff --git a/src/unrealsdk/unreal/classes/uobject_funcs.h b/src/unrealsdk/unreal/classes/uobject_funcs.h index 9f8557f..9a15e39 100644 --- a/src/unrealsdk/unreal/classes/uobject_funcs.h +++ b/src/unrealsdk/unreal/classes/uobject_funcs.h @@ -22,7 +22,7 @@ template <> template [[nodiscard]] V UObject::get(const FName& name, size_t idx) const { - return this->get(this->Class->find_prop_and_validate(name), idx); + return this->get(this->Class()->find_prop_and_validate(name), idx); } template [[nodiscard]] V UObject::get(const T* prop, size_t idx) const { @@ -31,7 +31,7 @@ template template void UObject::set(const FName& name, size_t idx, const typename PropTraits::Value& value) { - this->set(this->Class->find_prop_and_validate(name), idx, value); + this->set(this->Class()->find_prop_and_validate(name), idx, value); } template void UObject::set(const T* prop, size_t idx, const typename PropTraits::Value& value) { diff --git a/src/unrealsdk/unreal/classes/uproperty.cpp b/src/unrealsdk/unreal/classes/uproperty.cpp index 2168813..938b429 100644 --- a/src/unrealsdk/unreal/classes/uproperty.cpp +++ b/src/unrealsdk/unreal/classes/uproperty.cpp @@ -3,50 +3,14 @@ #include "unrealsdk/config.h" #include "unrealsdk/unreal/classes/uclass.h" #include "unrealsdk/unreal/classes/uproperty.h" +#include "unrealsdk/unreal/offset_list.h" +#include "unrealsdk/unreal/offsets.h" #include "unrealsdk/unreal/structs/fname.h" #include "unrealsdk/unreal/wrappers/gobjects.h" #include "unrealsdk/unrealsdk.h" namespace unrealsdk::unreal { -#ifdef UE3 - -size_t UProperty::class_size(void) { - static size_t size = 0; - if (size != 0) { - return size; - } - - auto config_size = config::get_int("unrealsdk.uproperty_size"); - if (config_size.has_value()) { - size = (size_t)*config_size; - return size; - } - - // Rather than bother with a findobject call, we can recover UProperty from any arbitrary object - // UObject always has properties on it, we don't need to worry about what class we get - auto obj = *unrealsdk::gobjects().begin(); - auto prop = obj->Class->PropertyLink; - - const UStruct* cls = nullptr; - for (auto superfield : prop->Class->superfields()) { - if (superfield->Name == L"Property"_fn) { - cls = superfield; - break; - } - } - - // If we couldn't find the class, default to our actual size - if (cls == nullptr) { - size = sizeof(UProperty); - LOG(WARNING, "Couldn't find UProperty class size, defaulting to: {:#x}", size); - } else { - size = cls->get_struct_size(); - LOG(MISC, "UProperty class size: {:#x}", size); - } - return size; -} - -#endif +UNREALSDK_DEFINE_FIELDS_SOURCE_FILE(UProperty, UNREALSDK_UPROPERTY_FIELDS); } // namespace unrealsdk::unreal diff --git a/src/unrealsdk/unreal/classes/uproperty.h b/src/unrealsdk/unreal/classes/uproperty.h index 645d443..6fb303b 100644 --- a/src/unrealsdk/unreal/classes/uproperty.h +++ b/src/unrealsdk/unreal/classes/uproperty.h @@ -9,7 +9,7 @@ namespace unrealsdk::unreal { -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(push, 0x4) #endif @@ -18,27 +18,9 @@ namespace unrealsdk::unreal { #pragma clang diagnostic ignored "-Wunused-private-field" #endif -/* -There is a massive issue with `UProperty`. - -We've observed that it's 0x80 bytes big in BL2, but it's only 0x74 in TPS -These are both 32-bit UE3 games, so we want to support them both from the same dll. - -The extra 0xC bytes are placed right at the end, which makes accesses within `UProperty` itself - simple enough, especially since we don't care about whatever fields they are. -The issue is subclasses. We very much do care about fields on subclasses, which will be placed - after the variable offset. -Luckily, we can read the size off of `Core.Property` at runtime, and do some maths to work out the - actual offsets of the fields we want, using the helper function `read_field`. - -This means: -- All accesses to UProperty subclasses' fields **MUST** be done through `read_field`. -- All accesses to base UProperty fields **MUST** be direct, and **MUST NOT** use `read_field` -*/ - class UProperty : public UField { public: -#ifdef UE3 +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW static constexpr auto PROP_FLAG_OPTIONAL = 0x10; #endif static constexpr auto PROP_FLAG_PARAM = 0x80; @@ -52,84 +34,24 @@ class UProperty : public UField { UProperty& operator=(UProperty&&) = delete; ~UProperty() = delete; - // NOLINTBEGIN(readability-magic-numbers, readability-identifier-naming) - -#ifdef UE4 - int32_t ArrayDim; - int32_t ElementSize; - uint64_t PropertyFlags; - - private: - uint16_t RepIndex; - uint8_t BlueprintReplicationCondition; - - public: - int32_t Offset_Internal; - - private: - FName RepNotifyFunc; - - public: - /** In memory only: Linked list of properties from most-derived to base **/ - UProperty* PropertyLinkNext; - - private: - /** In memory only: Linked list of object reference properties from most-derived to base **/ - UProperty* NextRef; - /** In memory only: Linked list of properties requiring destruction. Note this does not include - * things that will be destroyed by the native destructor **/ - UProperty* DestructorLinkNext; - /** In memory only: Linked list of properties requiring post constructor initialization.**/ - UProperty* PostConstructLinkNext; // 0x0030(0x0040) MISSED OFFSET +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_OAK + using property_flags_type = uint64_t; +#elif UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW + using property_flags_type = uint32_t; #else - - int32_t ArrayDim; - int32_t ElementSize; - uint32_t PropertyFlags; - - private: - uint8_t UnknownData00[0x14]; - - public: - int32_t Offset_Internal; - UProperty* PropertyLinkNext; - - private: - uint8_t UnknownData01[0x18]; - - /** - * @brief Gets the size of this class. - * - * @return The size of this class. - */ - [[nodiscard]] static size_t class_size(void); - +#error Unknown SDK flavour #endif - public: - /** - * @brief Reads a field on a UProperty subclass, taking into account it's variable length. - * - * @tparam PropertyType The subclass of UProperty to read the field off of (should be picked up - * automatically). - * @tparam FieldType The type of the field being read (should be picked up automatically). - * @param prop The property to read the field off of. - * @param field Pointer to member of the field to read. - * @return The field's value. - */ - template >> - FieldType read_field(FieldType PropertyType::* field) const { -#ifdef UE4 - return reinterpret_cast(this)->*field; -#else - return *reinterpret_cast( - reinterpret_cast(&(reinterpret_cast(this)->*field)) - - sizeof(UProperty) + UProperty::class_size()); -#endif - } - // NOLINTEND(readability-magic-numbers, readability-identifier-naming) + // These fields become member functions, returning a reference into the object. +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define UNREALSDK_UPROPERTY_FIELDS(X) \ + X(int32_t, ArrayDim) \ + X(int32_t, ElementSize) \ + X(property_flags_type, PropertyFlags) \ + X(int32_t, Offset_Internal) \ + X(UProperty*, PropertyLinkNext) + + UNREALSDK_DEFINE_FIELDS_HEADER(UProperty, UNREALSDK_UPROPERTY_FIELDS); }; template <> @@ -141,7 +63,7 @@ struct ClassTraits { #pragma GCC diagnostic pop #endif -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(pop) #endif diff --git a/src/unrealsdk/unreal/classes/uscriptstruct.cpp b/src/unrealsdk/unreal/classes/uscriptstruct.cpp index 496ff01..9036cc6 100644 --- a/src/unrealsdk/unreal/classes/uscriptstruct.cpp +++ b/src/unrealsdk/unreal/classes/uscriptstruct.cpp @@ -1,15 +1,10 @@ -#include "unrealsdk/pch.h" #include "unrealsdk/unreal/classes/uscriptstruct.h" -#include "unrealsdk/unreal/classes/ustruct.h" +#include "unrealsdk/unreal/offset_list.h" +#include "unrealsdk/unreal/offsets.h" +#include "unrealsdk/unrealsdk.h" namespace unrealsdk::unreal { -decltype(UScriptStruct::StructFlags_internal)& UScriptStruct::StructFlags(void) { - return this->get_field(&UScriptStruct::StructFlags_internal); -} -[[nodiscard]] const decltype(UScriptStruct::StructFlags_internal)& UScriptStruct::StructFlags( - void) const { - return this->get_field(&UScriptStruct::StructFlags_internal); -} +UNREALSDK_DEFINE_FIELDS_SOURCE_FILE(UScriptStruct, UNREALSDK_USCRIPTSTRUCT_FIELDS); -} // namespace unrealsdk::unreal +} diff --git a/src/unrealsdk/unreal/classes/uscriptstruct.h b/src/unrealsdk/unreal/classes/uscriptstruct.h index b1503a0..0b7ca29 100644 --- a/src/unrealsdk/unreal/classes/uscriptstruct.h +++ b/src/unrealsdk/unreal/classes/uscriptstruct.h @@ -6,9 +6,24 @@ namespace unrealsdk::unreal { -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(push, 0x4) #endif +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-private-field" +#endif + +namespace offsets::generic { + +template +class UScriptStruct : public T { + public: + // NOLINTNEXTLINE(readability-identifier-naming) + uint32_t StructFlags; +}; + +} // namespace offsets::generic class UScriptStruct : public UStruct { public: @@ -19,16 +34,11 @@ class UScriptStruct : public UStruct { UScriptStruct& operator=(UScriptStruct&&) = delete; ~UScriptStruct() = delete; - // NOLINTBEGIN(readability-identifier-naming) - - private: - uint32_t StructFlags_internal; + // These fields become member functions, returning a reference into the object. +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define UNREALSDK_USCRIPTSTRUCT_FIELDS(X) X(uint32_t, StructFlags) - public: - decltype(StructFlags_internal)& StructFlags(void); - [[nodiscard]] const decltype(StructFlags_internal)& StructFlags(void) const; - - // NOLINTEND(readability-identifier-naming) + UNREALSDK_DEFINE_FIELDS_HEADER(UScriptStruct, UNREALSDK_USCRIPTSTRUCT_FIELDS); }; template <> @@ -36,7 +46,10 @@ struct ClassTraits { static inline const wchar_t* const NAME = L"ScriptStruct"; }; -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(__clang__) +#pragma clang diagnostic pop +#endif +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(pop) #endif diff --git a/src/unrealsdk/unreal/classes/ustruct.cpp b/src/unrealsdk/unreal/classes/ustruct.cpp index a708444..e7d336d 100644 --- a/src/unrealsdk/unreal/classes/ustruct.cpp +++ b/src/unrealsdk/unreal/classes/ustruct.cpp @@ -1,61 +1,21 @@ #include "unrealsdk/pch.h" #include "unrealsdk/config.h" +#include "unrealsdk/game/bl3/offsets.h" #include "unrealsdk/unreal/class_name.h" #include "unrealsdk/unreal/classes/ufield.h" #include "unrealsdk/unreal/classes/ufunction.h" #include "unrealsdk/unreal/classes/uproperty.h" #include "unrealsdk/unreal/classes/ustruct.h" +#include "unrealsdk/unreal/offset_list.h" +#include "unrealsdk/unreal/offsets.h" #include "unrealsdk/unreal/wrappers/bound_function.h" #include "unrealsdk/unreal/wrappers/gobjects.h" #include "unrealsdk/utils.h" namespace unrealsdk::unreal { -#ifdef UE3 - -size_t UStruct::class_size(void) { - static size_t size = 0; - if (size != 0) { - return size; - } - - auto config_size = config::get_int("unrealsdk.ustruct_size"); - if (config_size.has_value()) { - size = (size_t)*config_size; - return size; - } - - // Rather than bother with a find object/class, we can recover UStruct from any arbitrary object - // This just avoids extra dependencies, especially since theoretically find class might depend - // on this - - // First, find UClass - auto obj = *unrealsdk::gobjects().begin(); - const UClass* class_cls = obj->Class; - for (; class_cls->Class != class_cls; class_cls = class_cls->Class) {} - - // Then look through it's superfields for UStruct - const UStruct* struct_cls = nullptr; - for (auto superfield : class_cls->superfields()) { - if (superfield->Name == L"Struct"_fn) { - struct_cls = superfield; - break; - } - } - - // If we couldn't find the class, default to our actual size - if (struct_cls == nullptr) { - size = sizeof(UStruct); - LOG(WARNING, "Couldn't find UStruct class size, defaulting to: {:#x}", size); - } else { - size = struct_cls->get_struct_size(); - LOG(MISC, "UStruct class size: {:#x}", size); - } - return size; -} - -#endif +UNREALSDK_DEFINE_FIELDS_SOURCE_FILE(UStruct, UNREALSDK_USTRUCT_FIELDS); #pragma region Field Iterator @@ -69,13 +29,13 @@ UStruct::FieldIterator::reference UStruct::FieldIterator::operator*() const { UStruct::FieldIterator& UStruct::FieldIterator::operator++() { if (this->field != nullptr) { - this->field = this->field->Next; + this->field = this->field->Next(); } while (this->field == nullptr && this->this_struct != nullptr) { - this->this_struct = this->this_struct->SuperField; + this->this_struct = this->this_struct->SuperField(); if (this->this_struct != nullptr) { - this->field = this->this_struct->Children; + this->field = this->this_struct->Children(); } } @@ -95,7 +55,7 @@ bool UStruct::FieldIterator::operator!=(const UStruct::FieldIterator& rhs) const }; utils::IteratorProxy UStruct::fields(void) const { - FieldIterator begin{this, this->Children}; + FieldIterator begin{this, this->Children()}; // If we start out pointing at null (because this struct has no direct children), increment once // to find the actual first field @@ -118,7 +78,7 @@ UStruct::PropertyIterator::reference UStruct::PropertyIterator::operator*() cons } UStruct::PropertyIterator& UStruct::PropertyIterator::operator++() { - prop = prop->PropertyLinkNext; + prop = prop->PropertyLinkNext(); return *this; } UStruct::PropertyIterator UStruct::PropertyIterator::operator++(int) { @@ -135,7 +95,7 @@ bool UStruct::PropertyIterator::operator!=(const UStruct::PropertyIterator& rhs) }; utils::IteratorProxy UStruct::properties(void) const { - return {{this->PropertyLink}, {}}; + return {{this->PropertyLink()}, {}}; } #pragma endregion @@ -151,7 +111,7 @@ UStruct::SuperFieldIterator::reference UStruct::SuperFieldIterator::operator*() } UStruct::SuperFieldIterator& UStruct::SuperFieldIterator::operator++() { - this->this_struct = this->this_struct->SuperField; + this->this_struct = this->this_struct->SuperField(); return *this; } UStruct::SuperFieldIterator UStruct::SuperFieldIterator::operator++(int) { @@ -174,16 +134,20 @@ utils::IteratorProxy UStruct::superfields(void) con #pragma endregion size_t UStruct::get_struct_size(void) const { -#ifdef UE4 - return (this->PropertySize + this->MinAlignment - 1) & ~(this->MinAlignment - 1); +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_OAK + auto bl3_struct = reinterpret_cast(this); + return (bl3_struct->PropertySize + bl3_struct->MinAlignment - 1) + & ~(bl3_struct->MinAlignment - 1); +#elif UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW + return this->PropertySize(); #else - return this->PropertySize; +#error Unknown SDK flavour #endif } UField* UStruct::find(const FName& name) const { for (auto field : this->fields()) { - if (field->Name == name) { + if (field->Name() == name) { return field; } } @@ -193,7 +157,7 @@ UField* UStruct::find(const FName& name) const { UProperty* UStruct::find_prop(const FName& name) const { for (auto prop : this->properties()) { - if (prop->Name == name) { + if (prop->Name() == name) { return prop; } } diff --git a/src/unrealsdk/unreal/classes/ustruct.h b/src/unrealsdk/unreal/classes/ustruct.h index b37580d..4c6545d 100644 --- a/src/unrealsdk/unreal/classes/ustruct.h +++ b/src/unrealsdk/unreal/classes/ustruct.h @@ -9,7 +9,7 @@ namespace unrealsdk::unreal { -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(push, 0x4) #endif @@ -30,99 +30,23 @@ class UStruct : public UField { UStruct& operator=(UStruct&&) = delete; ~UStruct() = delete; - // NOLINTBEGIN(readability-magic-numbers, readability-identifier-naming) - -#ifdef UE4 - /* Struct this inherits from, may be null */ - UStruct* SuperField; - /* Pointer to start of linked list of child fields */ - UField* Children; - - private: - /* Total size of all UProperties, the allocated structure may be larger due to alignment */ - int32_t PropertySize; - /* Alignment of structure in memory, structure will be at least this large */ - int32_t MinAlignment; - /* Script bytecode associated with this object */ - TArray Script; - - public: - /* In memory only: Linked list of properties from most-derived to base */ - UProperty* PropertyLink; - - private: - /* In memory only: Linked list of object reference properties from most-derived to base */ - UProperty* RefLink; - /* In memory only: Linked list of properties requiring destruction. Note this does not include - * things that will be destroyed by the native destructor */ - UProperty* DestructorLink; - /** In memory only: Linked list of properties requiring post constructor initialization */ - UProperty* PostConstructLink; - /* Array of object references embedded in script code. Mirrored for easy access by realtime - * garbage collection code */ - TArray ScriptObjectReferences; -#else - private: - uint8_t UnknownData00[0x8]; - - public: - UStruct* SuperField; - UField* Children; - - private: - uint16_t PropertySize; - uint8_t UnknownData01[0x1A]; - - public: - UProperty* PropertyLink; - - private: - uint8_t UnknownData02[0x10]; - - TArray ScriptObjectReferences; - - // See the description in 'uproperty.h', we have the same issue here. `UnknownData02` is 0x10 in - // BL2, but 0x4 in TPS. Since we need it this time, we also make provisions for setters. - - /** - * @brief Gets the size of this class. - * - * @return The size of this class. - */ - [[nodiscard]] static size_t class_size(void); - -#endif - protected: - /** - * @brief Reads a field on a UStruct subclass, taking into account it's variable length. - * - * @tparam SubType The subclass of UStruct to read the field off of (should be picked up - * automatically). - * @tparam FieldType The type of the field being read (should be picked up automatically). - * @param field Pointer to member of the field to read. - * @return A reference to the field. - */ - template >> - [[nodiscard]] const FieldType& get_field(FieldType SubType::* field) const { -#ifdef UE4 - return reinterpret_cast(this)->*field; +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_OAK + using property_size_type = int32_t; +#elif UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW + using property_size_type = uint16_t; #else - return *reinterpret_cast( - reinterpret_cast(&(reinterpret_cast(this)->*field)) - - sizeof(UStruct) + UStruct::class_size()); +#error Unknown SDK flavour #endif - } - template >> - FieldType& get_field(FieldType SubType::* field) { - return const_cast(const_cast(this)->get_field(field)); - } - public: - // NOLINTEND(readability-magic-numbers, readability-identifier-naming) + // These fields become member functions, returning a reference into the object. +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define UNREALSDK_USTRUCT_FIELDS(X) \ + X(UStruct*, SuperField) \ + X(UField*, Children) \ + X(property_size_type, PropertySize) \ + X(UProperty*, PropertyLink) + + UNREALSDK_DEFINE_FIELDS_HEADER(UStruct, UNREALSDK_USTRUCT_FIELDS); #pragma region Iterators struct FieldIterator { @@ -267,7 +191,7 @@ struct ClassTraits { #pragma GCC diagnostic pop #endif -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(pop) #endif diff --git a/src/unrealsdk/unreal/debug_casters.cpp b/src/unrealsdk/unreal/debug_casters.cpp new file mode 100644 index 0000000..eff9be7 --- /dev/null +++ b/src/unrealsdk/unreal/debug_casters.cpp @@ -0,0 +1,76 @@ +#include "unrealsdk/pch.h" +#include "unrealsdk/game/bl1/offsets.h" +#include "unrealsdk/game/bl2/offsets.h" +#include "unrealsdk/game/bl3/offsets.h" +#include "unrealsdk/game/tps/offsets.h" +#include "unrealsdk/unreal/offset_list.h" + +#ifndef NDEBUG +/* +For debug builds, we define a bunch of helper functions you may call to transform the "generic" +unreal types into "concrete" ones. These are intended for use only in a debugger. + +These creates a few series of global functions, named after each supported game, and "ue_generic()". +These functions have overloads for every dynamic unreal type, they take a pointer to one and return +it cast to their concrete, game specific type (or to the generic one for "ue_generic()"). These +functions all also support being run as a no-op, so you don't need to double check if you have a +generic or concrete type. +*/ + +// NOLINTBEGIN(cppcoreguidelines-macro-usage) +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW + +#define DEBUG_CASTER_GAMES(X, ...) \ + X(bl1, __VA_ARGS__) \ + X(bl2, __VA_ARGS__) \ + X(tps, __VA_ARGS__) + +#elif UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_OAK + +#define DEBUG_CASTER_GAMES(X, ...) X(bl3, __VA_ARGS__) + +#else +#error Unknown sdk flavour +#endif + +#define DEFINE_DEBUG_CASTERS_FOR_GAME_AND_TYPE(game_name, type_name) \ + unrealsdk::game::game_name::type_name* game_name(unrealsdk::unreal::type_name* obj) { \ + return reinterpret_cast(obj); \ + } \ + const unrealsdk::game::game_name::type_name* game_name( \ + const unrealsdk::unreal::type_name* obj) { \ + return reinterpret_cast(obj); \ + } \ + unrealsdk::game::game_name::type_name* game_name(unrealsdk::game::game_name::type_name* obj) { \ + return obj; \ + } \ + const unrealsdk::game::game_name::type_name* game_name( \ + const unrealsdk::game::game_name::type_name* obj) { \ + return obj; \ + } \ + unrealsdk::unreal::type_name* ue_generic(unrealsdk::game::game_name::type_name* obj) { \ + return reinterpret_cast(obj); \ + } \ + const unrealsdk::unreal::type_name* ue_generic( \ + const unrealsdk::game::game_name::type_name* obj) { \ + return reinterpret_cast(obj); \ + } + +#define DEFINE_DEBUG_GENERIC_NOOP_CASTER(type_name) \ + unrealsdk::unreal::type_name* ue_generic(unrealsdk::unreal::type_name* obj) { \ + return obj; \ + } \ + const unrealsdk::unreal::type_name* ue_generic(const unrealsdk::unreal::type_name* obj) { \ + return obj; \ + } + +#define DEFINE_DEBUG_CASTERS_FOR_TYPE(type_name) \ + DEBUG_CASTER_GAMES(DEFINE_DEBUG_CASTERS_FOR_GAME_AND_TYPE, type_name) + +// NOLINTBEGIN(misc-use-internal-linkage) +UNREALSDK__DYNAMIC_OFFSET_TYPES(DEFINE_DEBUG_CASTERS_FOR_TYPE) +UNREALSDK__DYNAMIC_OFFSET_TYPES(DEFINE_DEBUG_GENERIC_NOOP_CASTER) +// NOLINTEND(misc-use-internal-linkage) + +// NOLINTEND(cppcoreguidelines-macro-usage) +#endif diff --git a/src/unrealsdk/unreal/find_class.cpp b/src/unrealsdk/unreal/find_class.cpp index 45daba2..90be583 100644 --- a/src/unrealsdk/unreal/find_class.cpp +++ b/src/unrealsdk/unreal/find_class.cpp @@ -12,8 +12,8 @@ class NamedClassCache : public NamedObjectCache { // We can't exactly do a find class lookup here // Instead, just follow the class chain off of an arbitrary object // UClass is the only object whose class is itself - auto cls = (*unrealsdk::gobjects().begin())->Class; - for (; cls->Class != cls; cls = cls->Class) {} + auto cls = (*unrealsdk::gobjects().begin())->Class(); + for (; cls->Class() != cls; cls = cls->Class()) {} return cls; } }; diff --git a/src/unrealsdk/unreal/namedobjectcache.h b/src/unrealsdk/unreal/namedobjectcache.h index cf1960c..58b8d6d 100644 --- a/src/unrealsdk/unreal/namedobjectcache.h +++ b/src/unrealsdk/unreal/namedobjectcache.h @@ -2,7 +2,6 @@ #define UNREALSDK_UNREAL_NAMEDOBJECTCACHE_H #include "unrealsdk/pch.h" -#include "unrealsdk/format.h" #include "unrealsdk/unreal/class_name.h" #include "unrealsdk/unreal/find_class.h" #include "unrealsdk/unreal/structs/fname.h" @@ -46,7 +45,7 @@ class NamedObjectCache { * @return The cached value. */ virtual CacheType add_to_cache(ObjectType* obj) { - cache[obj->Name] = obj; + cache[obj->Name()] = obj; return obj; } @@ -103,9 +102,9 @@ class NamedObjectCache { } // This should never really happen, but double check if (!obj->is_instance(this->uclass)) { - throw std::invalid_argument(unrealsdk::utils::narrow(unrealsdk::fmt::format( - L"Found object of unexpected class when searching for '{}': {}", name, - obj->get_path_name()))); + throw std::invalid_argument(unrealsdk::utils::narrow( + std::format(L"Found object of unexpected class when searching for '{}': {}", name, + obj->get_path_name()))); } return add_to_cache(reinterpret_cast(obj)); diff --git a/src/unrealsdk/unreal/offset_list.h b/src/unrealsdk/unreal/offset_list.h new file mode 100644 index 0000000..2cd1724 --- /dev/null +++ b/src/unrealsdk/unreal/offset_list.h @@ -0,0 +1,107 @@ +#ifndef UNREALSDK_UNREAL_OFFSET_LIST_H +#define UNREALSDK_UNREAL_OFFSET_LIST_H + +#include "unrealsdk/pch.h" + +// Note: this header needs to pull in almost all unreal classes, and their offset definitions +// Refrain from including it in other headers +#include "unrealsdk/unreal/classes/properties/attribute_property.h" +#include "unrealsdk/unreal/classes/properties/persistent_object_ptr_property.h" +#include "unrealsdk/unreal/classes/properties/uarrayproperty.h" +#include "unrealsdk/unreal/classes/properties/uboolproperty.h" +#include "unrealsdk/unreal/classes/properties/ubyteproperty.h" +#include "unrealsdk/unreal/classes/properties/uclassproperty.h" +#include "unrealsdk/unreal/classes/properties/udelegateproperty.h" +#include "unrealsdk/unreal/classes/properties/uenumproperty.h" +#include "unrealsdk/unreal/classes/properties/uinterfaceproperty.h" +#include "unrealsdk/unreal/classes/properties/umulticastdelegateproperty.h" +#include "unrealsdk/unreal/classes/properties/uobjectproperty.h" +#include "unrealsdk/unreal/classes/properties/ustructproperty.h" +#include "unrealsdk/unreal/classes/uclass.h" +#include "unrealsdk/unreal/classes/uconst.h" +#include "unrealsdk/unreal/classes/uenum.h" +#include "unrealsdk/unreal/classes/ufield.h" +#include "unrealsdk/unreal/classes/ufunction.h" +#include "unrealsdk/unreal/classes/uobject.h" +#include "unrealsdk/unreal/classes/uproperty.h" +#include "unrealsdk/unreal/classes/uscriptstruct.h" +#include "unrealsdk/unreal/classes/ustruct.h" + +namespace unrealsdk::unreal::offsets { + +// Since this type is shared between dlls, also force consistent padding between compilers, even +// though this isn't an unreal type +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW +#pragma pack(push, 0x4) +#endif + +// NOLINTBEGIN(cppcoreguidelines-macro-usage) +#define UNREALSDK__DYNAMIC_OFFSET_TYPES(X) \ + X(UArrayProperty) \ + X(UBoolProperty) \ + X(UByteAttributeProperty) \ + X(UByteProperty) \ + X(UClass) \ + X(UClassProperty) \ + X(UConst) \ + X(UDelegateProperty) \ + X(UEnum) \ + X(UEnumProperty) \ + X(UField) \ + X(UFloatAttributeProperty) \ + X(UFunction) \ + X(UIntAttributeProperty) \ + X(UInterfaceProperty) \ + X(UMulticastDelegateProperty) \ + X(UObject) \ + X(UObjectProperty) \ + X(UProperty) \ + X(UScriptStruct) \ + X(USoftClassProperty) \ + X(UStruct) \ + X(UStructProperty) + +#define UNREALSDK_OFFSETS__DEFINE_OFFSET_LIST_MEMBERS(name) unrealsdk::unreal::name::Offsets name; +#define UNREALSDK_OFFSETS__NESTED_FROM_TYPE(name) \ + unrealsdk::unreal::name::Offsets::from(), +#define UNREALSDK_OFFSETS__NESTED_FROM_NAMESPACE(name) \ + unrealsdk::unreal::name::Offsets::from(), + +// NOLINTEND(cppcoreguidelines-macro-usage) + +struct OffsetList { + // NOLINTNEXTLINE(readability-identifier-naming) + UNREALSDK__DYNAMIC_OFFSET_TYPES(UNREALSDK_OFFSETS__DEFINE_OFFSET_LIST_MEMBERS) + + /** + * @brief Creates a full offset list based off of the templated type. + * @note Assumes all the game specific types are subtypes - i.e. `T::UObject` is valid. + * + * @tparam T The templated type holding all the game-specific types. + * @return A new offset list. + */ + template + static constexpr OffsetList from(void) { + return {UNREALSDK__DYNAMIC_OFFSET_TYPES(UNREALSDK_OFFSETS__NESTED_FROM_TYPE)}; + } +}; + +/** + * @brief Macro to create a full offset list within the current namespace. + * @note Assumes all the game specific types are directly accessible - i.e just `UObject` is valid. + * + * @return A new offset list. + */ +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define OFFSET_LIST_FROM_NAMESPACE() \ + unrealsdk::unreal::offsets::OffsetList { \ + UNREALSDK__DYNAMIC_OFFSET_TYPES(UNREALSDK_OFFSETS__NESTED_FROM_NAMESPACE) \ + } + +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW +#pragma pack(pop) +#endif + +} // namespace unrealsdk::unreal::offsets + +#endif /* UNREALSDK_UNREAL_OFFSET_LIST_H */ diff --git a/src/unrealsdk/unreal/offsets.h b/src/unrealsdk/unreal/offsets.h new file mode 100644 index 0000000..f0ba7d3 --- /dev/null +++ b/src/unrealsdk/unreal/offsets.h @@ -0,0 +1,141 @@ +#ifndef UNREALSDK_UNREAL_OFFSETS_H +#define UNREALSDK_UNREAL_OFFSETS_H + +#include "unrealsdk/pch.h" + +namespace unrealsdk::unreal::offsets { + +using offset_type = uint16_t; + +// NOLINTBEGIN(cppcoreguidelines-macro-usage) + +// Internal macros +#define UNREALSDK_OFFSETS__DEFINE_OFFSET_MEMBERS(type, name) \ + unrealsdk::unreal::offsets::offset_type name; +#define UNREALSDK_OFFSETS__OFFSETOF_ASSERTS(type, name) \ + static_assert(offsetof(T, name) \ + < std::numeric_limits::max()); +#define UNREALSDK_OFFSETS__OFFSETOF(type, name) \ + static_cast(offsetof(T, name)), +#define UNREALSDK_OFFSETS__DEFINE_GETTER(type, name) \ + private: \ + using _type__##name = type; \ + \ + public: \ + [[nodiscard]] _type__##name& name(void) { \ + return *reinterpret_cast<_type__##name*>(reinterpret_cast(this) \ + + Offsets::get(&Offsets::name)); \ + } \ + [[nodiscard]] const _type__##name& name(void) const { \ + return *reinterpret_cast(reinterpret_cast(this) \ + + Offsets::get(&Offsets::name)); \ + } + +#if defined(__MINGW32__) || defined(__clang__) +#define UNREALSDK_OFFSETS__OFFSETOF_PRAGMA_PUSH \ + _Pragma("GCC diagnostic push") _Pragma("GCC diagnostic ignored \"-Winvalid-offsetof\"") +#define UNREALSDK_OFFSETS__OFFSETOF_PRAGMA_POP _Pragma("GCC diagnostic pop") + +#else +#define UNREALSDK_OFFSETS__OFFSETOF_PRAGMA_PUSH +#define UNREALSDK_OFFSETS__OFFSETOF_PRAGMA_POP +#endif + +/** + * @brief Header file macro to defines all the machinery for variable offset fields. + * @note Should be placed within the class definition. + * + * @param ClassName The name of the class these fields are being defined for. + * @param X_MACRO An X macro list of the fields to define. See the example below. + */ +#define UNREALSDK_DEFINE_FIELDS_HEADER(ClassName, X_MACRO) \ + public: \ + struct Offsets; \ + /* NOLINTNEXTLINE(readability-identifier-naming) */ \ + X_MACRO(UNREALSDK_OFFSETS__DEFINE_GETTER) \ + struct Offsets { \ + /* NOLINTNEXTLINE(readability-identifier-naming) */ \ + X_MACRO(UNREALSDK_OFFSETS__DEFINE_OFFSET_MEMBERS) \ + template \ + static constexpr Offsets from() { \ + UNREALSDK_OFFSETS__OFFSETOF_PRAGMA_PUSH \ + X_MACRO(UNREALSDK_OFFSETS__OFFSETOF_ASSERTS); \ + return {X_MACRO(UNREALSDK_OFFSETS__OFFSETOF)}; \ + UNREALSDK_OFFSETS__OFFSETOF_PRAGMA_POP \ + } \ + static unrealsdk::unreal::offsets::offset_type get( \ + unrealsdk::unreal::offsets::offset_type Offsets::* field); \ + } // deliberately no semicolon - forward declared earlier so that we could put this last + +/** + * @brief Source file macro to defines all the machinery for variable offset fields. + * @note Should be placed at the top level (but within the relevant namespace). + * + * @param ClassName The name of the class these fields are being defined for. + * @param X_MACRO An X macro list of the fields to define. See the example below. + */ +#define UNREALSDK_DEFINE_FIELDS_SOURCE_FILE(ClassName, X_MACRO) \ + unrealsdk::unreal::offsets::offset_type ClassName::Offsets::get( \ + unrealsdk::unreal::offsets::offset_type ClassName::Offsets::* field) { \ + return unrealsdk::internal::get_offsets().ClassName.*field; \ + } + +#if 0 // NOLINT(readability-avoid-unconditional-preprocessor-if) + +// The above macros take an X macro field list. This is a macro function, which takes another +// macro function, and calls it for each field + +#define UOBJECT_FIELDS(X) \ + X(UClass*, Class) \ + X(UObject*, Outer) + +// The inner macro always takes the following args: +// - The field's type +// - The field's name + +// Calling the macro +UNREALSDK_DEFINE_FIELDS_HEADER(UObject, UOBJECT_FIELDS); + +// Creates a class approximately like the following: +struct Offsets { + offset_type Class; + offset_type Outer; + + template + static constexpr Offsets from() { + return { + offsetof(T, Class), + offsetof(T, Outer), + }; + } +}; + +// You can use a one-liner to fill this with all the relevant offsets from another type +auto bl2_offsets = Offsets::from(); + +// The macro also creates a bunch of getters like the following +using _type__Class = UClass*; + +[[nodiscard]] _type__Class& Class(void) { + return *reinterpret_cast<_type__Class*>(reinterpret_cast(&self) + + Offsets::get(&Offsets::Class)); +} +[[nodiscard]] const _type__Class& Class(void) const { + return *reinterpret_cast<_type__Class*>(reinterpret_cast(&self) + + Offsets::get(&Offsets::Class)); +} + +// Since these return a reference, they can be used pretty much the exact same way as a member. +auto cls = obj->Class()->find(L"MyField"_fn); +obj->Class() = some_other_class; + +// Storing the reference returned from these functions has the exact same semantics as taking a +// member reference - i.e. don't do it, it's only valid for the parent object's lifetime. + +#endif + +// NOLINTEND(cppcoreguidelines-macro-usage) + +} // namespace unrealsdk::unreal::offsets + +#endif /* UNREALSDK_UNREAL_OFFSETS_H */ diff --git a/src/unrealsdk/unreal/prop_traits.h b/src/unrealsdk/unreal/prop_traits.h index dc08ec5..ee644a3 100644 --- a/src/unrealsdk/unreal/prop_traits.h +++ b/src/unrealsdk/unreal/prop_traits.h @@ -67,11 +67,11 @@ template uintptr_t base_addr, const UnrealPointer& parent = { nullptr}) { - if (idx >= (size_t)prop->ArrayDim) { + if (idx >= (size_t)prop->ArrayDim()) { throw std::out_of_range("Property index out of range"); } - return PropTraits::get(prop, base_addr + prop->Offset_Internal + (idx * prop->ElementSize), - parent); + return PropTraits::get( + prop, base_addr + prop->Offset_Internal() + (idx * prop->ElementSize()), parent); } /** @@ -88,11 +88,11 @@ void set_property(const T* prop, size_t idx, uintptr_t base_addr, const typename PropTraits::Value& value) { - if (idx >= (size_t)prop->ArrayDim) { + if (idx >= (size_t)prop->ArrayDim()) { throw std::out_of_range("Property index out of range"); } - return PropTraits::set(prop, base_addr + prop->Offset_Internal + (idx * prop->ElementSize), - value); + return PropTraits::set( + prop, base_addr + prop->Offset_Internal() + (idx * prop->ElementSize()), value); } /** @@ -105,11 +105,11 @@ void set_property(const T* prop, */ template void destroy_property(const T* prop, size_t idx, uintptr_t base_addr) { - if (idx >= (size_t)prop->ArrayDim) { + if (idx >= (size_t)prop->ArrayDim()) { throw std::out_of_range("Property index out of range"); } - return PropTraits::destroy(prop, - base_addr + prop->Offset_Internal + (idx * prop->ElementSize)); + return PropTraits::destroy( + prop, base_addr + prop->Offset_Internal() + (idx * prop->ElementSize())); } } // namespace unrealsdk::unreal diff --git a/src/unrealsdk/unreal/structs/fframe.cpp b/src/unrealsdk/unreal/structs/fframe.cpp index b9def65..215ec74 100644 --- a/src/unrealsdk/unreal/structs/fframe.cpp +++ b/src/unrealsdk/unreal/structs/fframe.cpp @@ -11,15 +11,15 @@ uint8_t* FFrame::extract_current_args(WrappedStruct& args) { auto args_addr = reinterpret_cast(args.base.get()); uint8_t* original_code = this->Code; - for (auto prop = reinterpret_cast(args.type->Children); + for (auto prop = reinterpret_cast(args.type->Children()); *this->Code != FFrame::EXPR_TOKEN_END_FUNCTION_PARAMS; - prop = reinterpret_cast(prop->Next)) { - if ((prop->PropertyFlags & UProperty::PROP_FLAG_RETURN) != 0) { + prop = reinterpret_cast(prop->Next())) { + if ((prop->PropertyFlags() & UProperty::PROP_FLAG_RETURN) != 0) { continue; } unrealsdk::internal::fframe_step( - this, this->Object, reinterpret_cast(args_addr + prop->Offset_Internal)); + this, this->Object, reinterpret_cast(args_addr + prop->Offset_Internal())); } return original_code; diff --git a/src/unrealsdk/unreal/structs/fframe.h b/src/unrealsdk/unreal/structs/fframe.h index a46ff38..8546757 100644 --- a/src/unrealsdk/unreal/structs/fframe.h +++ b/src/unrealsdk/unreal/structs/fframe.h @@ -3,7 +3,7 @@ namespace unrealsdk::unreal { -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(push, 0x4) #endif @@ -29,11 +29,15 @@ struct FOutParamRec { struct FOutputDevice { void* VfTable; -#ifdef UE3 +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW private: uint32_t bAllowSuppression; public: +#elif UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_OAK + // Intentionally empty +#else +#error Unknown SDK flavour #endif uint32_t bSuppressEventTag; @@ -48,12 +52,16 @@ struct FFrame : public FOutputDevice { uint8_t* Code; void* Locals; -#ifdef UE4 +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_OAK private: UProperty* LastProperty; void* LastPropertyAddress; public: +#elif UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW + // Intentionally empty +#else +#error Unknown SDK flavour #endif FFrame* PreviousFrame; @@ -75,7 +83,7 @@ struct FFrame : public FOutputDevice { #pragma GCC diagnostic pop #endif -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(pop) #endif diff --git a/src/unrealsdk/unreal/structs/fimplementedinterface.cpp b/src/unrealsdk/unreal/structs/fimplementedinterface.cpp index d49b6aa..2407c2a 100644 --- a/src/unrealsdk/unreal/structs/fimplementedinterface.cpp +++ b/src/unrealsdk/unreal/structs/fimplementedinterface.cpp @@ -6,10 +6,12 @@ namespace unrealsdk::unreal { size_t FImplementedInterface::get_pointer_offset() const { -#if UE4 +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_OAK return this->isNative ? 0 : this->PointerOffset; +#elif UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW + return this->VFTableProperty == nullptr ? 0 : this->VFTableProperty->Offset_Internal(); #else - return this->VFTableProperty == nullptr ? 0 : this->VFTableProperty->Offset_Internal; +#error Unknown SDK flavour #endif } diff --git a/src/unrealsdk/unreal/structs/fimplementedinterface.h b/src/unrealsdk/unreal/structs/fimplementedinterface.h index 703b86d..2825525 100644 --- a/src/unrealsdk/unreal/structs/fimplementedinterface.h +++ b/src/unrealsdk/unreal/structs/fimplementedinterface.h @@ -5,7 +5,7 @@ namespace unrealsdk::unreal { -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(push, 0x4) #endif @@ -18,11 +18,13 @@ struct FImplementedInterface { UClass* Class; private: -#ifdef UE4 +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_OAK int32_t PointerOffset; bool isNative; -#else +#elif UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW UStructProperty* VFTableProperty; // May be null (if native?) +#else +#error Unknown SDK flavour #endif // NOLINTEND(readability-identifier-naming) @@ -36,7 +38,7 @@ struct FImplementedInterface { [[nodiscard]] size_t get_pointer_offset(void) const; }; -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(pop) #endif diff --git a/src/unrealsdk/unreal/structs/fname.h b/src/unrealsdk/unreal/structs/fname.h index ab7f83b..a826175 100644 --- a/src/unrealsdk/unreal/structs/fname.h +++ b/src/unrealsdk/unreal/structs/fname.h @@ -5,7 +5,7 @@ namespace unrealsdk::unreal { -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(push, 0x4) #endif @@ -53,7 +53,7 @@ struct FName { operator std::wstring() const; }; -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(pop) #endif @@ -69,9 +69,8 @@ FName operator""_fn(const wchar_t* str, size_t len); // Custom FName formatter, which just casts to a string first template <> -struct unrealsdk::fmt::formatter - : unrealsdk::fmt::formatter { - auto format(unrealsdk::unreal::FName name, unrealsdk::fmt::format_context& ctx) const { +struct std::formatter : std::formatter { + auto format(unrealsdk::unreal::FName name, std::format_context& ctx) const { return formatter::format((std::string)name, ctx); } }; diff --git a/src/unrealsdk/unreal/structs/fpropertychangeevent.h b/src/unrealsdk/unreal/structs/fpropertychangeevent.h index a6911a0..fbd1858 100644 --- a/src/unrealsdk/unreal/structs/fpropertychangeevent.h +++ b/src/unrealsdk/unreal/structs/fpropertychangeevent.h @@ -7,7 +7,7 @@ namespace unrealsdk::unreal { class UProperty; -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(push, 0x4) #endif @@ -63,7 +63,7 @@ struct FPropertyChangedChainEvent : public FPropertyChangedEvent { FEditPropertyChain* PropertyChain{}; }; -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(pop) #endif diff --git a/src/unrealsdk/unreal/structs/fscriptdelegate.cpp b/src/unrealsdk/unreal/structs/fscriptdelegate.cpp index 994a4db..9cb3b71 100644 --- a/src/unrealsdk/unreal/structs/fscriptdelegate.cpp +++ b/src/unrealsdk/unreal/structs/fscriptdelegate.cpp @@ -9,7 +9,7 @@ namespace unrealsdk::unreal { -#ifdef UE3 +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW UObject* FScriptDelegate::get_object(void) const { return this->object; @@ -19,7 +19,7 @@ void FScriptDelegate::set_object(UObject* obj) { this->object = obj; } -#else +#elif UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_OAK UObject* FScriptDelegate::get_object(void) const { return unrealsdk::gobjects().get_weak_object(&this->object); @@ -28,6 +28,8 @@ UObject* FScriptDelegate::get_object(void) const { void FScriptDelegate::set_object(UObject* obj) { unrealsdk::gobjects().set_weak_object(&this->object, obj); } +#else +#error Unknown SDK flavour #endif [[nodiscard]] std::optional FScriptDelegate::as_function(void) const { @@ -37,7 +39,7 @@ void FScriptDelegate::set_object(UObject* obj) { return std::nullopt; } - return BoundFunction{.func = obj->Class->find_func_and_validate(this->func_name), + return BoundFunction{.func = obj->Class()->find_func_and_validate(this->func_name), .object = obj}; } @@ -49,7 +51,7 @@ void FScriptDelegate::bind(const std::optional& func) { } this->set_object(func->object); - this->func_name = func->func->Name; + this->func_name = func->func->Name(); } void FScriptDelegate::validate_signature(const std::optional& func, @@ -64,14 +66,14 @@ void FScriptDelegate::validate_signature(const std::optional& fun { UFunction* func_from_find = nullptr; try { - func_from_find = func->object->Class->find_func_and_validate(func->func->Name); + func_from_find = func->object->Class()->find_func_and_validate(func->func->Name()); } catch (const std::invalid_argument&) { - throw std::invalid_argument(unrealsdk::fmt::format( + throw std::invalid_argument(std::format( "Could not convert function to delegate: could not find function with name '{}'", - func->func->Name)); + func->func->Name())); } if (func_from_find != func->func) { - throw std::invalid_argument(utils::narrow(unrealsdk::fmt::format( + throw std::invalid_argument(utils::narrow(std::format( L"Could not convert function to delegate: got another function with the same name," "{} instead of {}", func_from_find->get_path_name(), func->func->get_path_name()))); @@ -97,13 +99,13 @@ void FScriptDelegate::validate_signature(const std::optional& fun auto [func_diff, sig_diff] = std::ranges::mismatch( func_props, sig_props, - [](UProperty* func, UProperty* sig) { return func->Class == sig->Class; }); + [](UProperty* func, UProperty* sig) { return func->Class() == sig->Class(); }); if (func_diff != func_props.end() && sig_diff != sig_props.end()) { - throw std::invalid_argument(unrealsdk::fmt::format( + throw std::invalid_argument(std::format( "Function signature does not match delegate: function's {} {} != delegate's {} {}", - (*func_diff)->Class->Name, (*func_diff)->Name, (*sig_diff)->Class->Name, - (*sig_diff)->Name)); + (*func_diff)->Class()->Name(), (*func_diff)->Name(), (*sig_diff)->Class()->Name(), + (*sig_diff)->Name())); } if (func_diff != func_props.end() && sig_diff == sig_props.end()) { throw std::invalid_argument( diff --git a/src/unrealsdk/unreal/structs/fscriptdelegate.h b/src/unrealsdk/unreal/structs/fscriptdelegate.h index 44ffbfc..63f404d 100644 --- a/src/unrealsdk/unreal/structs/fscriptdelegate.h +++ b/src/unrealsdk/unreal/structs/fscriptdelegate.h @@ -8,7 +8,7 @@ namespace unrealsdk::unreal { -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(push, 0x4) #endif @@ -17,10 +17,12 @@ class UFunction; struct FScriptDelegate { private: -#ifdef UE3 +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW UObject* object = nullptr; -#else +#elif UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_OAK FWeakObjectPtr object; +#else +#error Unknown SDK flavour #endif public: @@ -66,7 +68,7 @@ struct FScriptDelegate { const UFunction* signature); }; -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(pop) #endif diff --git a/src/unrealsdk/unreal/structs/ftext.cpp b/src/unrealsdk/unreal/structs/ftext.cpp index c48b1f1..04b8cc9 100644 --- a/src/unrealsdk/unreal/structs/ftext.cpp +++ b/src/unrealsdk/unreal/structs/ftext.cpp @@ -9,7 +9,7 @@ namespace unrealsdk::unreal { -#ifdef UE4 +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_OAK FText::FText(std::string_view str) : FText(utils::widen(str)) {} FText::FText(std::wstring_view str) : data(), flags() { @@ -53,7 +53,7 @@ FText::~FText() { } } -#else +#elif UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW FText::FText(std::string_view /* str */) : data(), flags() { (void)this; @@ -95,6 +95,8 @@ FText::~FText() { (void)this; } +#else +#error Unknown SDK flavour #endif } // namespace unrealsdk::unreal diff --git a/src/unrealsdk/unreal/structs/ftext.h b/src/unrealsdk/unreal/structs/ftext.h index cd1cb93..6e1f19f 100644 --- a/src/unrealsdk/unreal/structs/ftext.h +++ b/src/unrealsdk/unreal/structs/ftext.h @@ -6,7 +6,7 @@ namespace unrealsdk::unreal { -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(push, 0x4) #endif @@ -66,7 +66,7 @@ struct FText { FText& operator=(FText&&) = delete; }; -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(pop) #endif @@ -74,9 +74,8 @@ struct FText { // Custom FText formatter, which just casts to a string first template <> -struct unrealsdk::fmt::formatter - : unrealsdk::fmt::formatter { - auto format(unrealsdk::unreal::FText text, unrealsdk::fmt::format_context& ctx) const { +struct std::formatter : std::formatter { + auto format(unrealsdk::unreal::FText text, std::format_context& ctx) const { return formatter::format((std::string)text, ctx); } }; diff --git a/src/unrealsdk/unreal/structs/fweakobjectptr.h b/src/unrealsdk/unreal/structs/fweakobjectptr.h index a85d441..d607a03 100644 --- a/src/unrealsdk/unreal/structs/fweakobjectptr.h +++ b/src/unrealsdk/unreal/structs/fweakobjectptr.h @@ -5,7 +5,7 @@ namespace unrealsdk::unreal { -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(push, 0x4) #endif @@ -21,7 +21,7 @@ struct FWeakObjectPtr { int32_t object_serial_number = 0; }; -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(pop) #endif diff --git a/src/unrealsdk/unreal/structs/gnames.cpp b/src/unrealsdk/unreal/structs/gnames.cpp index 807380b..a5c36be 100644 --- a/src/unrealsdk/unreal/structs/gnames.cpp +++ b/src/unrealsdk/unreal/structs/gnames.cpp @@ -8,7 +8,7 @@ bool FNameEntry::is_wide(void) const { return (this->Index & NAME_WIDE_MASK) != 0; } -#ifdef UE4 +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_OAK FNameEntry* TStaticIndirectArrayThreadSafeRead_FNameEntry::at(size_t idx) const { if (std::cmp_greater_equal(idx, this->Count)) { diff --git a/src/unrealsdk/unreal/structs/gnames.h b/src/unrealsdk/unreal/structs/gnames.h index 36aac27..1c09074 100644 --- a/src/unrealsdk/unreal/structs/gnames.h +++ b/src/unrealsdk/unreal/structs/gnames.h @@ -3,7 +3,7 @@ namespace unrealsdk::unreal { -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(push, 0x4) #endif @@ -19,7 +19,7 @@ struct FNameEntry { // NOLINTBEGIN(readability-magic-numbers, readability-identifier-naming) -#ifdef UE4 +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_OAK int32_t Index; private: @@ -27,7 +27,7 @@ struct FNameEntry { public: FNameEntry* HashNext; -#else +#elif UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW private: uint8_t UnknownData00[0x08]; @@ -38,6 +38,8 @@ struct FNameEntry { uint8_t UnknownData01[0x04]; public: +#else +#error Unknown SDK flavour #endif union { @@ -55,7 +57,7 @@ struct FNameEntry { [[nodiscard]] bool is_wide(void) const; }; -#ifdef UE4 +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_OAK // NOLINTNEXTLINE(readability-identifier-naming) struct TStaticIndirectArrayThreadSafeRead_FNameEntry { @@ -87,7 +89,7 @@ struct TStaticIndirectArrayThreadSafeRead_FNameEntry { #pragma GCC diagnostic pop #endif -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(pop) #endif diff --git a/src/unrealsdk/unreal/structs/gobjects.cpp b/src/unrealsdk/unreal/structs/gobjects.cpp index 28ccc52..f63f5bd 100644 --- a/src/unrealsdk/unreal/structs/gobjects.cpp +++ b/src/unrealsdk/unreal/structs/gobjects.cpp @@ -4,7 +4,7 @@ namespace unrealsdk::unreal { -#ifdef UE4 +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_OAK FUObjectItem* FChunkedFixedUObjectArray::at(size_t idx) const { if (std::cmp_greater_equal(idx, this->Count)) { diff --git a/src/unrealsdk/unreal/structs/gobjects.h b/src/unrealsdk/unreal/structs/gobjects.h index 7d9b37e..a815713 100644 --- a/src/unrealsdk/unreal/structs/gobjects.h +++ b/src/unrealsdk/unreal/structs/gobjects.h @@ -5,7 +5,7 @@ namespace unrealsdk::unreal { class UObject; -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(push, 0x4) #endif @@ -14,7 +14,7 @@ class UObject; #pragma clang diagnostic ignored "-Wunused-private-field" #endif -#if defined(UE4) +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_OAK // There are a few fields we need to modify to deal with weak pointers. To be thread safe we need // them to be atomic fields - as unreal itself does. The fields are all aligned as normal, we expect @@ -95,7 +95,7 @@ struct FUObjectArray { #pragma GCC diagnostic pop #endif -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(pop) #endif diff --git a/src/unrealsdk/unreal/structs/tarray.h b/src/unrealsdk/unreal/structs/tarray.h index a6e5738..ba65e37 100644 --- a/src/unrealsdk/unreal/structs/tarray.h +++ b/src/unrealsdk/unreal/structs/tarray.h @@ -5,7 +5,7 @@ namespace unrealsdk::unreal { -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(push, 0x4) #endif @@ -190,7 +190,7 @@ struct TArray { #pragma endregion }; -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(pop) #endif diff --git a/src/unrealsdk/unreal/structs/tpair.h b/src/unrealsdk/unreal/structs/tpair.h index df77885..5874de4 100644 --- a/src/unrealsdk/unreal/structs/tpair.h +++ b/src/unrealsdk/unreal/structs/tpair.h @@ -3,7 +3,7 @@ namespace unrealsdk::unreal { -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(push, 0x4) #endif @@ -13,7 +13,7 @@ struct TPair { ValueType value; }; -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(pop) #endif diff --git a/src/unrealsdk/unreal/structs/tpersistentobjectptr.cpp b/src/unrealsdk/unreal/structs/tpersistentobjectptr.cpp index e6727bc..e7f1c4f 100644 --- a/src/unrealsdk/unreal/structs/tpersistentobjectptr.cpp +++ b/src/unrealsdk/unreal/structs/tpersistentobjectptr.cpp @@ -17,45 +17,45 @@ template const TPersistentObjectPtr* get_addr_from(const UObject* obj, const UObjectProperty* prop, size_t idx) { - if (std::cmp_greater_equal(idx, prop->ArrayDim)) { + if (std::cmp_greater_equal(idx, prop->ArrayDim())) { throw std::out_of_range("Property index out of range"); } auto addr = - reinterpret_cast(obj) + prop->Offset_Internal + (idx * prop->ElementSize); + reinterpret_cast(obj) + prop->Offset_Internal() + (idx * prop->ElementSize()); return reinterpret_cast*>(addr); } template const TPersistentObjectPtr* get_addr_from(const WrappedStruct& wrapped_struct, const UObjectProperty* prop, size_t idx) { - if (std::cmp_greater_equal(idx, prop->ArrayDim)) { + if (std::cmp_greater_equal(idx, prop->ArrayDim())) { throw std::out_of_range("Property index out of range"); } - auto addr = reinterpret_cast(wrapped_struct.base.get()) + prop->Offset_Internal - + (idx * prop->ElementSize); + auto addr = reinterpret_cast(wrapped_struct.base.get()) + prop->Offset_Internal() + + (idx * prop->ElementSize()); return reinterpret_cast*>(addr); } template const TPersistentObjectPtr* get_addr_from_array(const WrappedArray& array, size_t idx) { - if (!array.type->Class->inherits(find_class())) { + if (!array.type->Class()->inherits(find_class())) { throw std::invalid_argument("WrappedArray property was of invalid type " - + (std::string)array.type->Class->Name); + + (std::string)array.type->Class()->Name()); } if (std::cmp_greater_equal(idx, array.base->count)) { throw std::out_of_range("WrappedArray index out of range"); } - auto addr = reinterpret_cast(array.base->data) + (array.type->ElementSize * idx); + auto addr = reinterpret_cast(array.base->data) + (array.type->ElementSize() * idx); return reinterpret_cast*>(addr); } } // namespace const FSoftObjectPath* FSoftObjectPath::get_from(const UObject* obj, FName name, size_t idx) { - return get_from(obj, obj->Class->find_prop_and_validate(name), idx); + return get_from(obj, obj->Class()->find_prop_and_validate(name), idx); } const FSoftObjectPath* FSoftObjectPath::get_from(const WrappedStruct& wrapped_struct, FName name, @@ -79,7 +79,7 @@ const FSoftObjectPath* FSoftObjectPath::get_from_array(const WrappedArray& array } const FLazyObjectPath* FLazyObjectPath::get_from(const UObject* obj, FName name, size_t idx) { - return get_from(obj, obj->Class->find_prop_and_validate(name), idx); + return get_from(obj, obj->Class()->find_prop_and_validate(name), idx); } const FLazyObjectPath* FLazyObjectPath::get_from(const WrappedStruct& wrapped_struct, FName name, diff --git a/src/unrealsdk/unreal/structs/tpersistentobjectptr.h b/src/unrealsdk/unreal/structs/tpersistentobjectptr.h index c720f9f..9ba85bf 100644 --- a/src/unrealsdk/unreal/structs/tpersistentobjectptr.h +++ b/src/unrealsdk/unreal/structs/tpersistentobjectptr.h @@ -14,7 +14,7 @@ class USoftObjectProperty; class WrappedStruct; class WrappedArray; -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(push, 0x4) #endif @@ -100,7 +100,7 @@ struct FLazyObjectPath { struct FSoftObjectPtr : public TPersistentObjectPtr {}; struct FLazyObjectPtr : public TPersistentObjectPtr {}; -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(pop) #endif diff --git a/src/unrealsdk/unreal/structs/tsharedpointer.h b/src/unrealsdk/unreal/structs/tsharedpointer.h index 45e2301..7f5ac50 100644 --- a/src/unrealsdk/unreal/structs/tsharedpointer.h +++ b/src/unrealsdk/unreal/structs/tsharedpointer.h @@ -5,7 +5,7 @@ namespace unrealsdk::unreal { -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(push, 0x4) #endif @@ -40,7 +40,7 @@ struct TSharedPointer { TReferenceController* controller; }; -#if defined(_MSC_VER) && defined(ARCH_X86) +#if defined(_MSC_VER) && UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #pragma pack(pop) #endif diff --git a/src/unrealsdk/unreal/wrappers/bound_function.cpp b/src/unrealsdk/unreal/wrappers/bound_function.cpp index 7a95a5a..7ede84a 100644 --- a/src/unrealsdk/unreal/wrappers/bound_function.cpp +++ b/src/unrealsdk/unreal/wrappers/bound_function.cpp @@ -13,26 +13,27 @@ namespace unrealsdk::unreal { namespace func_params::impl { UProperty* get_next_param(UProperty* prop) { - prop = prop->PropertyLinkNext; - while (prop != nullptr && (prop->PropertyFlags & UProperty::PROP_FLAG_PARAM) == 0) { - prop = prop->PropertyLinkNext; + prop = prop->PropertyLinkNext(); + while (prop != nullptr && (prop->PropertyFlags() & UProperty::PROP_FLAG_PARAM) == 0) { + prop = prop->PropertyLinkNext(); } return prop; } void validate_no_more_params(UProperty* prop) { - for (; prop != nullptr; prop = prop->PropertyLinkNext) { - if ((prop->PropertyFlags & UProperty::PROP_FLAG_PARAM) == 0) { + for (; prop != nullptr; prop = prop->PropertyLinkNext()) { + 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) { continue; } -#ifdef UE3 - if ((prop->PropertyFlags & UProperty::PROP_FLAG_OPTIONAL) != 0) { +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW + if ((prop->PropertyFlags() & UProperty::PROP_FLAG_OPTIONAL) != 0) { continue; } #endif + throw std::runtime_error("Too few parameters to function call!"); } } diff --git a/src/unrealsdk/unreal/wrappers/bound_function.h b/src/unrealsdk/unreal/wrappers/bound_function.h index eac5037..853c2f4 100644 --- a/src/unrealsdk/unreal/wrappers/bound_function.h +++ b/src/unrealsdk/unreal/wrappers/bound_function.h @@ -52,7 +52,7 @@ void set_param(WrappedStruct& params, if (prop == nullptr) { throw std::runtime_error("Too many parameters to function call!"); } - if (prop->ArrayDim > 1) { + if (prop->ArrayDim() > 1) { throw std::runtime_error( "Function has static array argument - unsure how to handle, aborting!"); } @@ -78,8 +78,8 @@ void set_param(WrappedStruct& params, */ template void write_params(WrappedStruct& params, const typename PropTraits::Value&... args) { - UProperty* prop = params.type->PropertyLink; - if (prop != nullptr && (prop->PropertyFlags & UProperty::PROP_FLAG_PARAM) == 0) { + UProperty* prop = params.type->PropertyLink(); + if (prop != nullptr && (prop->PropertyFlags() & UProperty::PROP_FLAG_PARAM) == 0) { prop = impl::get_next_param(prop); } @@ -112,7 +112,7 @@ return_type get_return_value(const UFunction* func, const WrappedStruct& para if (ret == nullptr) { throw std::runtime_error("Couldn't find return param!"); } - if (ret->ArrayDim > 1) { + if (ret->ArrayDim() > 1) { throw std::runtime_error( "Function has static array return param - unsure how to handle, aborting!"); } @@ -162,7 +162,7 @@ class BoundFunction { if (params.type != this->func) { throw std::runtime_error( "Tried to call function with pre-filled parameters of incorrect type: " - + (std::string)params.type->Name); + + (std::string)params.type->Name()); } this->call_with_params(params.base.get()); diff --git a/src/unrealsdk/unreal/wrappers/gnames.cpp b/src/unrealsdk/unreal/wrappers/gnames.cpp index 7f9fa89..8968179 100644 --- a/src/unrealsdk/unreal/wrappers/gnames.cpp +++ b/src/unrealsdk/unreal/wrappers/gnames.cpp @@ -8,7 +8,7 @@ namespace unrealsdk::unreal { GNames::GNames(void) : internal(nullptr) {} GNames::GNames(internal_type internal) : internal(internal) {} -#if defined(UE4) +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_OAK size_t GNames::size(void) const { return this->internal->Count; @@ -21,7 +21,7 @@ FNameEntry* GNames::at(size_t idx) const { return this->internal->at(idx); } -#elif defined(UE3) +#elif UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW size_t GNames::size(void) const { return this->internal->size(); @@ -32,7 +32,7 @@ FNameEntry* GNames::at(size_t idx) const { } #else -#error Unknown UE version +#error Unknown SDK flavour #endif } // namespace unrealsdk::unreal diff --git a/src/unrealsdk/unreal/wrappers/gnames.h b/src/unrealsdk/unreal/wrappers/gnames.h index de8a80d..f4c2cb9 100644 --- a/src/unrealsdk/unreal/wrappers/gnames.h +++ b/src/unrealsdk/unreal/wrappers/gnames.h @@ -5,7 +5,7 @@ #include "unrealsdk/unreal/structs/gnames.h" -#ifdef UE3 +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #include "unrealsdk/unreal/structs/tarray.h" #endif @@ -13,11 +13,14 @@ namespace unrealsdk::unreal { class GNames { public: -#ifdef UE4 +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_OAK using internal_type = TStaticIndirectArrayThreadSafeRead_FNameEntry*; -#else +#elif UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW using internal_type = TArray*; +#else +#error Unknown SDK flavour #endif + private: internal_type internal; diff --git a/src/unrealsdk/unreal/wrappers/gobjects.cpp b/src/unrealsdk/unreal/wrappers/gobjects.cpp index 1fc4205..f5122b1 100644 --- a/src/unrealsdk/unreal/wrappers/gobjects.cpp +++ b/src/unrealsdk/unreal/wrappers/gobjects.cpp @@ -4,11 +4,13 @@ #include "unrealsdk/unreal/structs/fweakobjectptr.h" #include "unrealsdk/unreal/wrappers/gobjects.h" -#ifdef UE4 +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_OAK #include "unrealsdk/unreal/structs/gobjects.h" -#else +#elif UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #include "unrealsdk/unreal/structs/tarray.h" #include "unrealsdk/version_error.h" +#else +#error Unknown SDK flavour #endif namespace unrealsdk::unreal { @@ -70,7 +72,7 @@ GObjects::Iterator GObjects::end(void) { GObjects::GObjects(void) : internal(nullptr) {} GObjects::GObjects(internal_type internal) : internal(internal) {} -#if defined(UE4) +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_OAK size_t GObjects::size(void) const { return this->internal->ObjObjects.Count; @@ -103,7 +105,7 @@ void GObjects::set_weak_object(FWeakObjectPtr* ptr, const UObject* obj) const { if (obj == nullptr) { *ptr = {}; } else { - ptr->object_index = obj->InternalIndex; + ptr->object_index = obj->InternalIndex(); auto obj_item = this->internal->ObjObjects.at(ptr->object_index); auto serial_number = obj_item->SerialNumber.load(); @@ -125,7 +127,7 @@ void GObjects::set_weak_object(FWeakObjectPtr* ptr, const UObject* obj) const { } } -#elif defined(UE3) +#elif UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW size_t GObjects::size(void) const { return this->internal->size(); @@ -147,7 +149,7 @@ void GObjects::set_weak_object(FWeakObjectPtr* /* ptr */, const UObject* /* obj } #else -#error Unknown UE version +#error Unknown SDK flavour #endif } // namespace unrealsdk::unreal diff --git a/src/unrealsdk/unreal/wrappers/gobjects.h b/src/unrealsdk/unreal/wrappers/gobjects.h index cbf543d..f62565a 100644 --- a/src/unrealsdk/unreal/wrappers/gobjects.h +++ b/src/unrealsdk/unreal/wrappers/gobjects.h @@ -3,10 +3,12 @@ #include "unrealsdk/pch.h" -#ifdef UE4 +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_OAK #include "unrealsdk/unreal/structs/gobjects.h" -#else +#elif UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #include "unrealsdk/unreal/structs/tarray.h" +#else +#error Unknown SDK flavour #endif namespace unrealsdk::unreal { @@ -16,11 +18,14 @@ class UObject; class GObjects { public: -#ifdef UE4 +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_OAK using internal_type = FUObjectArray*; -#else +#elif UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW using internal_type = TArray*; +#else +#error Unknown SDK flavour #endif + private: internal_type internal; diff --git a/src/unrealsdk/unreal/wrappers/property_proxy.cpp b/src/unrealsdk/unreal/wrappers/property_proxy.cpp index 8f1be51..58c7d69 100644 --- a/src/unrealsdk/unreal/wrappers/property_proxy.cpp +++ b/src/unrealsdk/unreal/wrappers/property_proxy.cpp @@ -14,7 +14,7 @@ PropertyProxy::PropertyProxy(UProperty* prop) : prop(prop), ptr(nullptr) {} PropertyProxy::PropertyProxy(const PropertyProxy& other) : prop(other.prop), ptr(nullptr) { if (this->prop != nullptr && other.has_value()) { cast(this->prop, [this, &other](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++) { this->set(i, other.get(i)); } }); @@ -23,11 +23,11 @@ PropertyProxy::PropertyProxy(const PropertyProxy& other) : prop(other.prop), ptr PropertyProxy& PropertyProxy::operator=(const PropertyProxy& other) { if (other.prop != this->prop) { throw std::runtime_error("Property proxy is not instance of " - + (std::string)this->prop->Name); + + (std::string)this->prop->Name()); } if (this->prop != nullptr) { cast(this->prop, [this, &other](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++) { this->set(i, other.get(i)); } }); @@ -49,7 +49,7 @@ void PropertyProxy::copy_to(uintptr_t addr) const { } if (this->prop != nullptr) { cast(this->prop, [this, addr](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++) { set_property(prop, i, addr, this->get(i)); } }); @@ -62,7 +62,7 @@ void PropertyProxy::copy_from(uintptr_t addr) { } cast(this->prop, [this, addr](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++) { this->set(i, get_property(prop, i, addr)); } }); diff --git a/src/unrealsdk/unreal/wrappers/unreal_pointer.cpp b/src/unrealsdk/unreal/wrappers/unreal_pointer.cpp index ba06e1f..dccf3eb 100644 --- a/src/unrealsdk/unreal/wrappers/unreal_pointer.cpp +++ b/src/unrealsdk/unreal/wrappers/unreal_pointer.cpp @@ -40,10 +40,10 @@ void UnrealPointerControl::destroy_object(void) { if (prop != nullptr) { // Need to reconstruct the base address, since our pointer may be elsewhere auto prop_base = reinterpret_cast(this) - + sizeof(impl::UnrealPointerControl) - prop->Offset_Internal; + + sizeof(impl::UnrealPointerControl) - prop->Offset_Internal(); cast(prop, [prop_base](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++) { destroy_property(prop, i, prop_base); } }); diff --git a/src/unrealsdk/unreal/wrappers/unreal_pointer_funcs.h b/src/unrealsdk/unreal/wrappers/unreal_pointer_funcs.h index fc51fac..a4d7241 100644 --- a/src/unrealsdk/unreal/wrappers/unreal_pointer_funcs.h +++ b/src/unrealsdk/unreal/wrappers/unreal_pointer_funcs.h @@ -74,7 +74,7 @@ UnrealPointer::UnrealPointer(const UProperty* prop) requires std::is_void_v : control(nullptr), ptr(nullptr) { // If malloc throws, it should have handled freeing memory if required - auto buf = unrealsdk::u_malloc((prop->ElementSize * prop->ArrayDim) + auto buf = unrealsdk::u_malloc((prop->ElementSize() * prop->ArrayDim()) + sizeof(impl::UnrealPointerControl)); // Otherwise, if we throw during initialization we need to free manually @@ -83,7 +83,7 @@ UnrealPointer::UnrealPointer(const UProperty* prop) this->control = new (buf) impl::UnrealPointerControl(prop); this->ptr = reinterpret_cast(reinterpret_cast(this->control + 1) - - prop->Offset_Internal); + - prop->Offset_Internal()); } catch (const std::exception& ex) { unrealsdk::u_free(buf); LOG(ERROR, "Exception in unreal pointer constructor: {}", ex.what()); diff --git a/src/unrealsdk/unreal/wrappers/weak_pointer.cpp b/src/unrealsdk/unreal/wrappers/weak_pointer.cpp index 3307aed..8f175e7 100644 --- a/src/unrealsdk/unreal/wrappers/weak_pointer.cpp +++ b/src/unrealsdk/unreal/wrappers/weak_pointer.cpp @@ -14,12 +14,12 @@ WeakPointer::WeakPointer(const UObject* obj) : WeakPointer() { WeakPointer& WeakPointer::operator=(const UObject* obj) { this->obj_addr = reinterpret_cast(obj); if (obj != nullptr) { - this->index = obj->InternalIndex; + this->index = obj->InternalIndex(); - this->outer_addr = reinterpret_cast(obj->Outer); + this->outer_addr = reinterpret_cast(obj->Outer()); this->vftable_addr = reinterpret_cast(obj->vftable); - this->class_addr = reinterpret_cast(obj->Class); - this->name = obj->Name; + this->class_addr = reinterpret_cast(obj->Class()); + this->name = obj->Name(); } return *this; } @@ -37,9 +37,10 @@ UObject* WeakPointer::operator*(void) const { return nullptr; } - if (this->outer_addr != reinterpret_cast(obj->Outer) + if (this->outer_addr != reinterpret_cast(obj->Outer()) || this->vftable_addr != reinterpret_cast(obj->vftable) - || this->class_addr != reinterpret_cast(obj->Class) || this->name != obj->Name) { + || this->class_addr != reinterpret_cast(obj->Class()) + || this->name != obj->Name()) { return nullptr; } diff --git a/src/unrealsdk/unreal/wrappers/weak_pointer.h b/src/unrealsdk/unreal/wrappers/weak_pointer.h index 6cdf7a4..f492f1c 100644 --- a/src/unrealsdk/unreal/wrappers/weak_pointer.h +++ b/src/unrealsdk/unreal/wrappers/weak_pointer.h @@ -3,7 +3,7 @@ #include "unrealsdk/pch.h" -#ifdef UE3 +#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW #define UNREALSDK_EMULATED_WEAK_POINTER #endif diff --git a/src/unrealsdk/unreal/wrappers/wrapped_array.cpp b/src/unrealsdk/unreal/wrappers/wrapped_array.cpp index 61e5590..d94d330 100644 --- a/src/unrealsdk/unreal/wrappers/wrapped_array.cpp +++ b/src/unrealsdk/unreal/wrappers/wrapped_array.cpp @@ -12,7 +12,7 @@ WrappedArray::WrappedArray(const UProperty* type, TArray* base, const UnrealPointer& parent) : type(type), base(parent, base) { - if (type->ArrayDim > 1) { + if (type->ArrayDim() > 1) { throw std::runtime_error( "Array has static array inner property - unsure how to handle, aborting!"); } @@ -30,7 +30,7 @@ void WrappedArray::reserve(size_t new_cap) const { if (new_cap < this->size()) { throw std::invalid_argument("Can't decrease array capacity below it's current size."); } - this->base->reserve(new_cap, this->type->ElementSize); + this->base->reserve(new_cap, this->type->ElementSize()); } void WrappedArray::resize(size_t new_size) { @@ -42,13 +42,13 @@ void WrappedArray::resize(size_t new_size) { } }); - this->base->resize(new_size, this->type->ElementSize); + this->base->resize(new_size, this->type->ElementSize()); // 0-initialize any new entries if (new_size > old_size) { auto data_ptr = reinterpret_cast(this->base->data); - auto start = data_ptr + (old_size * this->type->ElementSize); - auto length = (new_size - old_size) * this->type->ElementSize; + auto start = data_ptr + (old_size * this->type->ElementSize()); + auto length = (new_size - old_size) * this->type->ElementSize(); memset(reinterpret_cast(start), 0, length); } } diff --git a/src/unrealsdk/unreal/wrappers/wrapped_array.h b/src/unrealsdk/unreal/wrappers/wrapped_array.h index ed4bd11..381b1df 100644 --- a/src/unrealsdk/unreal/wrappers/wrapped_array.h +++ b/src/unrealsdk/unreal/wrappers/wrapped_array.h @@ -70,7 +70,7 @@ class WrappedArray { */ template void validate_access(size_t idx) const { - auto property_class = this->type->Class->Name; + auto property_class = this->type->Class()->Name(); if (property_class != cls_fname()) { throw std::invalid_argument("WrappedArray property was of invalid type " + (std::string)property_class); @@ -94,7 +94,7 @@ class WrappedArray { this->validate_access(idx); return get_property( reinterpret_cast(this->type), 0, - reinterpret_cast(this->base->data) + (this->type->ElementSize * idx), + reinterpret_cast(this->base->data) + (this->type->ElementSize() * idx), this->base); } @@ -110,7 +110,8 @@ class WrappedArray { this->validate_access(idx); set_property( reinterpret_cast(this->type), 0, - reinterpret_cast(this->base->data) + (this->type->ElementSize * idx), value); + reinterpret_cast(this->base->data) + (this->type->ElementSize() * idx), + value); } /** @@ -124,7 +125,7 @@ class WrappedArray { this->validate_access(idx); destroy_property( reinterpret_cast(this->type), 0, - reinterpret_cast(this->base->data) + (this->type->ElementSize * idx)); + reinterpret_cast(this->base->data) + (this->type->ElementSize() * idx)); } }; diff --git a/src/unrealsdk/unreal/wrappers/wrapped_multicast_delegate.cpp b/src/unrealsdk/unreal/wrappers/wrapped_multicast_delegate.cpp index 5c1139d..89c5de3 100644 --- a/src/unrealsdk/unreal/wrappers/wrapped_multicast_delegate.cpp +++ b/src/unrealsdk/unreal/wrappers/wrapped_multicast_delegate.cpp @@ -37,7 +37,7 @@ WrappedMulticastDelegate::WrappedMulticastDelegate(const UFunction* signature, void WrappedMulticastDelegate::call(WrappedStruct& params) const { if (params.type != this->signature) { throw std::runtime_error("Tried to call delegate with parameters of incorrect type: " - + (std::string)params.type->Name); + + (std::string)params.type->Name()); } if (this->base->size() == 0) { diff --git a/src/unrealsdk/unreal/wrappers/wrapped_struct.cpp b/src/unrealsdk/unreal/wrappers/wrapped_struct.cpp index 02dcf1b..b41c1d7 100644 --- a/src/unrealsdk/unreal/wrappers/wrapped_struct.cpp +++ b/src/unrealsdk/unreal/wrappers/wrapped_struct.cpp @@ -13,13 +13,13 @@ namespace unrealsdk::unreal { void copy_struct(uintptr_t dest, const WrappedStruct& src) { if (dest == reinterpret_cast(src.base.get())) { LOG(DEV_WARNING, "Refusing to copy struct of type {} to itself, at address {:p}", - src.type->Name, src.base.get()); + src.type->Name(), src.base.get()); return; } for (const auto& prop : src.type->properties()) { cast(prop, [dest, &src](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++) { set_property(prop, i, dest, src.get(prop, i)); } }); @@ -30,7 +30,7 @@ void destroy_struct(const UStruct* type, uintptr_t addr) { for (const auto& prop : type->properties()) { try { cast(prop, [addr](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++) { destroy_property(prop, i, addr); } }); @@ -39,7 +39,7 @@ void destroy_struct(const UStruct* type, uintptr_t addr) { // on trying to destroy the rest // Log it to dev warning - don't want to use error to not freak out casusal users, this // should only really happen if a dev is messing with unsupported property types. - LOG(DEV_WARNING, "Error while destroying '{}' struct: {}", type->Name, ex.what()); + LOG(DEV_WARNING, "Error while destroying '{}' struct: {}", type->Name(), ex.what()); } } } @@ -60,7 +60,7 @@ WrappedStruct::WrappedStruct(WrappedStruct&& other) noexcept WrappedStruct& WrappedStruct::operator=(const WrappedStruct& other) { if (other.type != this->type) { - throw std::runtime_error("Struct is not an instance of " + (std::string)this->type->Name); + throw std::runtime_error("Struct is not an instance of " + (std::string)this->type->Name()); } if (this->base != nullptr && other.base != nullptr) { copy_struct(reinterpret_cast(this->base.get()), other); @@ -81,12 +81,12 @@ WrappedStruct WrappedStruct::copy_params_only(void) const { auto dest = reinterpret_cast(new_struct.base.get()); for (const auto& prop : this->type->properties()) { - if ((prop->PropertyFlags & UProperty::PROP_FLAG_PARAM) == 0) { + if ((prop->PropertyFlags() & UProperty::PROP_FLAG_PARAM) == 0) { continue; } cast(prop, [dest, this](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++) { set_property(prop, i, dest, this->get(prop, i)); } }); diff --git a/src/unrealsdk/unrealsdk.h b/src/unrealsdk/unrealsdk.h index 1c3612c..23d0205 100644 --- a/src/unrealsdk/unrealsdk.h +++ b/src/unrealsdk/unrealsdk.h @@ -3,7 +3,6 @@ #include "unrealsdk/pch.h" -#include "unrealsdk/unreal/classes/uobject.h" #include "unrealsdk/unreal/structs/fname.h" namespace unrealsdk::game { @@ -18,6 +17,7 @@ class GNames; class GObjects; class UClass; class UFunction; +class UObject; struct FFrame; struct FLazyObjectPtr; struct FSoftObjectPtr; @@ -26,6 +26,12 @@ struct TemporaryFString; } // namespace unrealsdk::unreal +namespace unrealsdk::unreal::offsets { + +struct OffsetList; + +} + namespace unrealsdk { #ifndef UNREALSDK_IMPORTING @@ -123,7 +129,7 @@ void u_free(void* data); [[nodiscard]] unreal::UObject* construct_object(unreal::UClass* cls, unreal::UObject* outer, const unreal::FName& name = {0, 0}, - decltype(unreal::UObject::ObjectFlags) flags = 0, + uint64_t flags = 0, unreal::UObject* template_obj = nullptr); /** @@ -214,6 +220,14 @@ void ftext_as_culture_invariant(unreal::FText* text, unreal::TemporaryFString&& void fsoftobjectptr_assign(unreal::FSoftObjectPtr* ptr, const unreal::UObject* obj); void flazyobjectptr_assign(unreal::FLazyObjectPtr* ptr, const unreal::UObject* obj); +/** + * @brief Get the offsets list for the currently hooked game. + * @note This is valid to call during initialization. + * + * @return A reference to the offsets list. + */ +[[nodiscard]] const unreal::offsets::OffsetList& get_offsets(void); + } // namespace internal } // namespace unrealsdk diff --git a/src/unrealsdk/unrealsdk_fw.inl b/src/unrealsdk/unrealsdk_fw.inl index 69a1de1..da04215 100644 --- a/src/unrealsdk/unrealsdk_fw.inl +++ b/src/unrealsdk/unrealsdk_fw.inl @@ -9,7 +9,7 @@ namespace. It is included directly in the other two files. #include "unrealsdk/pch.h" -#include "unrealsdk/unreal/classes/uobject.h" +#include "unrealsdk/unreal/offset_list.h" namespace unrealsdk::unreal { @@ -41,7 +41,7 @@ UNREALSDK_CAPI([[nodiscard]] UObject*, UClass* cls, UObject* outer, const FName* name = nullptr, - decltype(UObject::ObjectFlags) flags = 0, + uint64_t flags = 0, UObject* template_obj = nullptr); UNREALSDK_CAPI([[nodiscard]] UObject*, find_object, @@ -64,6 +64,7 @@ UNREALSDK_CAPI([[nodiscard]] wchar_t*, uobject_path_name, const UObject* obj, si UNREALSDK_CAPI(void, ftext_as_culture_invariant, FText* text, TemporaryFString&& str); UNREALSDK_CAPI(void, fsoftobjectptr_assign, FSoftObjectPtr* ptr, const unreal::UObject* obj); UNREALSDK_CAPI(void, flazyobjectptr_assign, FLazyObjectPtr* ptr, const unreal::UObject* obj); +UNREALSDK_CAPI([[nodiscard]] const offsets::OffsetList*, get_offsets); } // namespace internal diff --git a/src/unrealsdk/unrealsdk_main.cpp b/src/unrealsdk/unrealsdk_main.cpp index 59b3dec..ddfe88a 100644 --- a/src/unrealsdk/unrealsdk_main.cpp +++ b/src/unrealsdk/unrealsdk_main.cpp @@ -123,7 +123,7 @@ UNREALSDK_CAPI([[nodiscard]] UObject*, UClass* cls, UObject* outer, const FName* name, - decltype(UObject::ObjectFlags) flags, + uint64_t flags, UObject* template_obj) { FName local_name{0, 0}; if (name != nullptr) { @@ -196,6 +196,10 @@ UNREALSDK_CAPI(void, flazyobjectptr_assign, FLazyObjectPtr* ptr, const unreal::U hook_instance->flazyobjectptr_assign(ptr, obj); } +UNREALSDK_CAPI([[nodiscard]] const offsets::OffsetList*, get_offsets) { + return &hook_instance->get_offsets(); +} + } // namespace internal } // namespace unrealsdk diff --git a/src/unrealsdk/unrealsdk_wrappers.cpp b/src/unrealsdk/unrealsdk_wrappers.cpp index f0f9cae..1d2c080 100644 --- a/src/unrealsdk/unrealsdk_wrappers.cpp +++ b/src/unrealsdk/unrealsdk_wrappers.cpp @@ -43,7 +43,7 @@ void u_free(void* data) { UObject* construct_object(UClass* cls, UObject* outer, const FName& name, - decltype(UObject::ObjectFlags) flags, + uint64_t flags, UObject* template_obj) { return UNREALSDK_MANGLE(construct_object)(cls, outer, &name, flags, template_obj); } @@ -104,6 +104,10 @@ void flazyobjectptr_assign(unreal::FLazyObjectPtr* ptr, const unreal::UObject* o UNREALSDK_MANGLE(flazyobjectptr_assign)(ptr, obj); } +[[nodiscard]] const offsets::OffsetList& get_offsets() { + return *UNREALSDK_MANGLE(get_offsets)(); +} + } // namespace internal } // namespace unrealsdk diff --git a/src/unrealsdk/utils.h b/src/unrealsdk/utils.h index f60be74..b20a3de 100644 --- a/src/unrealsdk/utils.h +++ b/src/unrealsdk/utils.h @@ -160,8 +160,8 @@ using StringViewMap = std::unordered_map, std::equal // Custom wstring formatter, which calls narrow template <> -struct unrealsdk::fmt::formatter : unrealsdk::fmt::formatter { - auto format(const std::wstring& str, unrealsdk::fmt::format_context& ctx) const { +struct std::formatter : std::formatter { + auto format(const std::wstring& str, std::format_context& ctx) const { return formatter::format(unrealsdk::utils::narrow(str), ctx); } }; diff --git a/src/unrealsdk/version.cpp b/src/unrealsdk/version.cpp index e492c60..261d032 100644 --- a/src/unrealsdk/version.cpp +++ b/src/unrealsdk/version.cpp @@ -22,13 +22,12 @@ namespace { #include "unrealsdk/git.inl" const constexpr auto GIT_HASH_CHARS = 8; -const std::string VERSION_STR = - unrealsdk::fmt::format("unrealsdk 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("unrealsdk 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/supported_settings.toml b/supported_settings.toml index e2a4e36..2178122 100644 --- a/supported_settings.toml +++ b/supported_settings.toml @@ -5,8 +5,8 @@ # crashes if used. # You are not expected to provide all settings, you should only set those you need. Some settings -# will change behaviour simply by being defined. For example, if `uproperty_size` is set, the sdk -# will not attempt to auto detect it, and will instead assume the provided value is accurate. +# will change behaviour simply by being defined. For example, if `alloc_alignment` is set, the sdk +# will not attempt to dynamically adjust it, and will always use the provided value. # Not all settings are used under all build configurations - for example UE3 doesn't support FText, # so `ftext_get_display_string_vf_index` is of course unused. @@ -24,10 +24,6 @@ console_log_level = "INFO" # If set, overrides the executable name used for game detection in the shared module. exe_override = "" -# Changes the size the `UProperty` class is assumed to have. -uproperty_size = -1 -# Changes the size the `UStruct` class is assumed to have. -ustruct_size = -1 # Changes the alignment used when calling the unreal memory allocation functions. alloc_alignment = -1 @@ -60,4 +56,4 @@ log_all_calls_file = "unrealsdk.calls.tsv" # Overrides the virtual function index used when calling `UObject::PostEditChangeProperty`. uobject_post_edit_change_property_vf_index = -1 # Overrides the virtual function index used when calling `UObject::PostEditChangeChainProperty`. -uobject_post_edit_change_chain property_vf_index = -1 +uobject_post_edit_change_chain_property_vf_index = -1