diff --git a/.clang-tidy b/.clang-tidy index 9534cad..78808f9 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -51,4 +51,6 @@ CheckOptions: readability-identifier-naming.NamespaceCase: lower_case readability-identifier-naming.ParameterCase: lower_case readability-identifier-naming.VariableCase: lower_case + + readability-identifier-length.IgnoredVariableNames: _ --- diff --git a/.cruft.json b/.cruft.json index 83723c0..ab66239 100644 --- a/.cruft.json +++ b/.cruft.json @@ -1,6 +1,6 @@ { "template": "git@github.com:bl-sdk/common_dotfiles.git", - "commit": "6b31480199099e9957b18918373a75d979951919", + "commit": "cee5c9dbf5b95f57bb636e5138171aa6a4964cf1", "checkout": null, "context": { "cookiecutter": { @@ -15,7 +15,8 @@ "__project_slug": "unrealsdk", "include_cpp": true, "include_py": false, - "_template": "git@github.com:bl-sdk/common_dotfiles.git" + "_template": "git@github.com:bl-sdk/common_dotfiles.git", + "_commit": "cee5c9dbf5b95f57bb636e5138171aa6a4964cf1" } }, "directory": null diff --git a/.devcontainer/.gitignore b/.devcontainer/.gitignore new file mode 100644 index 0000000..670808b --- /dev/null +++ b/.devcontainer/.gitignore @@ -0,0 +1,6 @@ +# Ignore any custom folders outside of our predefined ones, to let you create your own +# One use might be using your own container mapping the install path onto your actual game folder +*/ +!clang-cross +!llvm-mingw +!mingw diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..a9aeee4 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,65 @@ +FROM alpine:latest AS clang-cross +CMD ["/bin/bash"] +RUN <> $GITHUB_PATH - - - name: Setup MinGW - if: startswith(matrix.preset, 'mingw') - uses: egor-tensin/setup-mingw@v2 - with: - platform: ${{ fromJSON('["x86", "x64"]')[contains(matrix.preset, 'x64')] }} - - - name: Setup Clang - if: startswith(matrix.preset, 'clang-cross') - uses: egor-tensin/setup-clang@v1 - - - name: Restore win sdk cache - if: startswith(matrix.preset, 'clang-cross') - uses: actions/cache@v4 - id: cache-win-sdk - with: - path: ~/win-sdk - key: ${{ runner.os }}-win-sdk - - - name: Setup msitools - if: startswith(matrix.preset, 'clang-cross') && steps.cache-win-sdk.outputs.cache-hit != 'true' - uses: awalsh128/cache-apt-pkgs-action@latest - with: - packages: msitools - version: ${{ runner.os }}-msitools - - - name: Setup win sdk - if: startswith(matrix.preset, 'clang-cross') && steps.cache-win-sdk.outputs.cache-hit != 'true' - run: | - git clone https://github.com/mstorsjo/msvc-wine.git - msvc-wine/vsdownload.py --accept-license --dest ~/win-sdk Microsoft.VisualStudio.Workload.VCTools - msvc-wine/install.sh ~/win-sdk - rm -r msvc-wine - - - name: Checkout repository and submodules - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Configure CMake - working-directory: ${{ env.GITHUB_WORKSPACE }} - # The extra msvc wine arg won't do anything if we're not cross compiling - run: > - cmake . - --preset ${{ matrix.preset }} - -G Ninja - -DMSVC_WINE_ENV_SCRIPT=$(readlink -f ~)/win-sdk/bin/${{ fromJSON('["x86", "x64"]')[contains(matrix.preset, 'x64')] }}/msvcenv.sh + - name: Checkout repository and submodules + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Build + uses: devcontainers/ci@v0.3 + with: + cacheFrom: ghcr.io/bl-sdk/${{ matrix.toolchain.container }}:latest + configFile: .devcontainer/${{ matrix.toolchain.container }}/devcontainer.json + push: never + # The git watcher cmake thinks something's unsafe? Doesn't happen to me locally. + runCmd: | + git config --global --add safe.directory `pwd` + cmake . --preset ${{ matrix.toolchain.preset }} -G Ninja + cmake --build out/build/${{ matrix.toolchain.preset }} - - name: Build - working-directory: ${{ env.GITHUB_WORKSPACE }} - run: cmake --build out/build/${{ matrix.preset }} # ============================================================================== diff --git a/.gitignore b/.gitignore index 1948346..eb698b0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .vs .vscode +.idea # C/C++ excludes .cache/clangd diff --git a/CMakePresets.json b/CMakePresets.json index 17affae..8e1c658 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -40,6 +40,9 @@ "lhs": "${hostSystemName}", "rhs": "Windows" }, + "environment": { + "MSVC_WINE_ENV_SCRIPT": "/win-sdk/bin/x86/msvcenv.sh" + }, "toolchainFile": "common_cmake/clang-cross-x86.cmake" }, { @@ -50,6 +53,9 @@ "lhs": "${hostSystemName}", "rhs": "Windows" }, + "environment": { + "MSVC_WINE_ENV_SCRIPT": "/win-sdk/bin/x64/msvcenv.sh" + }, "toolchainFile": "common_cmake/clang-cross-x64.cmake" }, { diff --git a/README.md b/README.md index dc83913..8df227a 100644 --- a/README.md +++ b/README.md @@ -142,13 +142,24 @@ This turns out to be a bit of a problem - MSVC and GNU have different exception both. Practically, this means when cross compiling, you should either compile everything from scratch, or setup Clang to build with the MSVC ABI. [See this blog post for more info](https://apple1417.dev/posts/2023-05-18-debugging-proton). -# Running the SDK by itself +# Building the SDK by itself The shared library is also useful when developing for the sdk itself, since it's the minimal -configuration to get it running. The CMake presets are set up to build this. - -Note that you will need to use some game specific plugin loader to get the dll loaded. It is not set -up to alias any system dlls (since when actually using it as a library you don't want that), you -can't just call it `d3d9.dll` and assume your game will load fine. +configuration to get it running. The CMake presets are set up to build this. There are currently +five supported toolchains, each of which have a few different sdk configurations: + +- MSVC +- Clang (Windows) +- Clang (Cross Compile) * +- MinGW * +- LLVM MinGW * + +The toolchains with an asterix are all cross compiling toolchains. These all also have an associated +dev container, which is the recommended way of building them. The `clang-cross-*` presets in +particular hardcode a path assuming they're running in the container. + +Note that you will need to use some game specific plugin loader to get the `unrealsdk.dll` loaded. +It is not set up to alias any system dlls (since when actually using it as a library you don't want +that), you can't just rename it to `d3d9.dll` and assume your game will load fine. To build: diff --git a/src/unrealsdk/game/bl2/antidebug.cpp b/src/unrealsdk/game/bl2/antidebug.cpp index 564dc6f..4bbe1bf 100644 --- a/src/unrealsdk/game/bl2/antidebug.cpp +++ b/src/unrealsdk/game/bl2/antidebug.cpp @@ -22,17 +22,9 @@ typedef NTSTATUS(WINAPI* NtQueryInformationProcess_func)(HANDLE ProcessHandle, ULONG ProcessInformationLength, PULONG ReturnLength); -#if defined(__clang__) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wenum-constexpr-conversion" -#endif - -constexpr auto ThreadHideFromDebugger = static_cast(17); -constexpr auto ProcessDebugObjectHandle = static_cast(30); - -#if defined(__clang__) -#pragma clang diagnostic pop -#endif +// 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) @@ -43,7 +35,7 @@ NTSTATUS NTAPI NtSetInformationThread_hook(HANDLE ThreadHandle, PVOID ThreadInformation, ULONG ThreadInformationLength) { // NOLINTEND(readability-identifier-naming) - if (ThreadInformationClass == ThreadHideFromDebugger) { + if (static_cast(ThreadInformationClass) == ThreadHideFromDebugger) { return STATUS_SUCCESS; } @@ -61,7 +53,7 @@ NTSTATUS WINAPI NtQueryInformationProcess_hook(HANDLE ProcessHandle, ULONG ProcessInformationLength, PULONG ReturnLength) { // NOLINTEND(readability-identifier-naming) - if (ProcessInformationClass == ProcessDebugObjectHandle) { + if (static_cast(ProcessInformationClass) == ProcessDebugObjectHandle) { return STATUS_PORT_NOT_SET; } diff --git a/src/unrealsdk/game/selector.cpp b/src/unrealsdk/game/selector.cpp index c9089bb..f33dbee 100644 --- a/src/unrealsdk/game/selector.cpp +++ b/src/unrealsdk/game/selector.cpp @@ -37,7 +37,7 @@ using all_known_games = std::tuple; * @tparam i Index of the game class being tested this iteration. Picked up automatically. * @param executable The executable name to match against. */ -template +template std::unique_ptr find_correct_hook(std::string_view executable) { if constexpr (i >= std::tuple_size_v) { throw std::runtime_error("Failed to find compatible game hook!"); diff --git a/src/unrealsdk/hook_manager.cpp b/src/unrealsdk/hook_manager.cpp index 38979db..eab14d0 100644 --- a/src/unrealsdk/hook_manager.cpp +++ b/src/unrealsdk/hook_manager.cpp @@ -89,7 +89,7 @@ namespace { thread_local bool should_inject_next_call = false; bool should_log_all_calls = false; -std::unique_ptr log_all_calls_stream; +std::wofstream log_all_calls_stream{}; std::mutex log_all_calls_stream_mutex{}; std::unordered_map> hooks{}; @@ -98,17 +98,17 @@ void log_all_calls(bool should_log) { // Only keep this file stream open while we need it if (should_log) { const std::lock_guard lock(log_all_calls_stream_mutex); - log_all_calls_stream = std::make_unique( + log_all_calls_stream.open( utils::get_this_dll().parent_path() / config::get_str("unrealsdk.log_all_calls_file").value_or("unrealsdk.calls.tsv"), - std::ofstream::trunc); + std::wofstream::trunc); } should_log_all_calls = should_log; if (!should_log) { const std::lock_guard lock(log_all_calls_stream_mutex); - log_all_calls_stream = nullptr; + log_all_calls_stream.close(); } } @@ -219,11 +219,14 @@ const List* preprocess_hook(std::wstring_view source, const UFunction* func, con std::wstring func_name{}; if (should_log_all_calls) { - func_name = func->get_path_name(); - auto obj_name = obj->get_path_name(); + // Extra safety check + if (log_all_calls_stream.is_open()) { + func_name = func->get_path_name(); + auto obj_name = obj->get_path_name(); - const std::lock_guard lock(log_all_calls_stream_mutex); - *log_all_calls_stream << source << L'\t' << func_name << L'\t' << obj_name << L'\n'; + const std::lock_guard lock(log_all_calls_stream_mutex); + log_all_calls_stream << source << L'\t' << func_name << L'\t' << obj_name << L'\n'; + } } // Check if anything matches the function FName diff --git a/src/unrealsdk/unreal/structs/fname.cpp b/src/unrealsdk/unreal/structs/fname.cpp index be9e15d..89e72f9 100644 --- a/src/unrealsdk/unreal/structs/fname.cpp +++ b/src/unrealsdk/unreal/structs/fname.cpp @@ -70,7 +70,7 @@ FName::operator std::wstring() const { return stream.str(); } -FName operator"" _fn(const wchar_t* str, size_t /*len*/) { +FName operator""_fn(const wchar_t* str, size_t /*len*/) { return FName{str}; } diff --git a/src/unrealsdk/unreal/structs/fname.h b/src/unrealsdk/unreal/structs/fname.h index 68f5810..ab7f83b 100644 --- a/src/unrealsdk/unreal/structs/fname.h +++ b/src/unrealsdk/unreal/structs/fname.h @@ -63,7 +63,7 @@ struct FName { * @param str The string to create a name of. * @param len The length of the string. */ -FName operator"" _fn(const wchar_t* str, size_t len); +FName operator""_fn(const wchar_t* str, size_t len); } // namespace unrealsdk::unreal @@ -84,7 +84,9 @@ struct hash { size_t operator()(const unrealsdk::unreal::FName& name) const { static_assert(sizeof(unrealsdk::unreal::FName) == sizeof(uint64_t), "FName is not same size as a uint64"); - return hash()(*reinterpret_cast(&name)); + uint64_t val{}; + memcpy(&val, &name, sizeof(name)); + return hash()(val); } }; diff --git a/src/unrealsdk/unreal/structs/gnames.cpp b/src/unrealsdk/unreal/structs/gnames.cpp index 265ac7c..807380b 100644 --- a/src/unrealsdk/unreal/structs/gnames.cpp +++ b/src/unrealsdk/unreal/structs/gnames.cpp @@ -11,7 +11,7 @@ bool FNameEntry::is_wide(void) const { #ifdef UE4 FNameEntry* TStaticIndirectArrayThreadSafeRead_FNameEntry::at(size_t idx) const { - if (idx >= (size_t)this->Count) { + if (std::cmp_greater_equal(idx, this->Count)) { throw std::out_of_range("FNameEntry index out of range"); } // NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic, diff --git a/src/unrealsdk/unreal/structs/gobjects.cpp b/src/unrealsdk/unreal/structs/gobjects.cpp index 13542bf..28ccc52 100644 --- a/src/unrealsdk/unreal/structs/gobjects.cpp +++ b/src/unrealsdk/unreal/structs/gobjects.cpp @@ -7,7 +7,7 @@ namespace unrealsdk::unreal { #ifdef UE4 FUObjectItem* FChunkedFixedUObjectArray::at(size_t idx) const { - if (idx >= (size_t)this->Count) { + if (std::cmp_greater_equal(idx, this->Count)) { throw std::out_of_range("FChunkedFixedUObjectArray index out of range"); } // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) diff --git a/src/unrealsdk/unreal/structs/tpersistentobjectptr.cpp b/src/unrealsdk/unreal/structs/tpersistentobjectptr.cpp index 9b84365..e6727bc 100644 --- a/src/unrealsdk/unreal/structs/tpersistentobjectptr.cpp +++ b/src/unrealsdk/unreal/structs/tpersistentobjectptr.cpp @@ -17,7 +17,7 @@ template const TPersistentObjectPtr* get_addr_from(const UObject* obj, const UObjectProperty* prop, size_t idx) { - if (idx >= (size_t)prop->ArrayDim) { + if (std::cmp_greater_equal(idx, prop->ArrayDim)) { throw std::out_of_range("Property index out of range"); } @@ -29,7 +29,7 @@ template const TPersistentObjectPtr* get_addr_from(const WrappedStruct& wrapped_struct, const UObjectProperty* prop, size_t idx) { - if (idx >= (size_t)prop->ArrayDim) { + if (std::cmp_greater_equal(idx, prop->ArrayDim)) { throw std::out_of_range("Property index out of range"); } @@ -44,7 +44,7 @@ const TPersistentObjectPtr* get_addr_from_array(const WrappedArray& array, si throw std::invalid_argument("WrappedArray property was of invalid type " + (std::string)array.type->Class->Name); } - if (idx >= (size_t)array.base->count) { + if (std::cmp_greater_equal(idx, array.base->count)) { throw std::out_of_range("WrappedArray index out of range"); } diff --git a/src/unrealsdk/unreal/wrappers/gnames.cpp b/src/unrealsdk/unreal/wrappers/gnames.cpp index 99dade9..7f9fa89 100644 --- a/src/unrealsdk/unreal/wrappers/gnames.cpp +++ b/src/unrealsdk/unreal/wrappers/gnames.cpp @@ -15,7 +15,7 @@ size_t GNames::size(void) const { } FNameEntry* GNames::at(size_t idx) const { - if (idx >= (size_t)this->internal->Count) { + if (std::cmp_greater_equal(idx, this->internal->Count)) { throw std::out_of_range("GObjects index out of range"); } return this->internal->at(idx); diff --git a/src/unrealsdk/unreal/wrappers/gobjects.cpp b/src/unrealsdk/unreal/wrappers/gobjects.cpp index 62f4fab..1fc4205 100644 --- a/src/unrealsdk/unreal/wrappers/gobjects.cpp +++ b/src/unrealsdk/unreal/wrappers/gobjects.cpp @@ -77,7 +77,7 @@ size_t GObjects::size(void) const { } UObject* GObjects::obj_at(size_t idx) const { - if (idx >= (size_t)this->internal->ObjObjects.Count) { + if (std::cmp_greater_equal(idx, this->internal->ObjObjects.Count)) { throw std::out_of_range("GObjects index out of range"); } return this->internal->ObjObjects.at(idx)->Object;