diff --git a/.clang-format b/.clang-format index 2ad2ec208..fb7ca9cfe 100644 --- a/.clang-format +++ b/.clang-format @@ -7,7 +7,7 @@ AccessModifierOffset: -4 AlignTrailingComments: false AllowAllConstructorInitializersOnNextLine: false AllowAllParametersOfDeclarationOnNextLine: true -AllowShortFunctionsOnASingleLine: Empty +AllowShortFunctionsOnASingleLine: Inline AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterReturnType: None AlwaysBreakTemplateDeclarations: No @@ -35,5 +35,5 @@ IndentExternBlock: Indent PointerAlignment: Left SortIncludes: false NamespaceIndentation: All -PackConstructorInitializers: NextLine -BreakConstructorInitializersBeforeComma: true +PackConstructorInitializers: Never +BreakConstructorInitializers: BeforeComma diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 10640d70e..c104bb382 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -5,3 +5,9 @@ # Folder rename f9bc3c9d1834cb8bd5f872b749b057c33e8b0102 + +# Clang format change: one-line inline functions +5b2c608b22ba272e4ab1a45adc1f43b60b1aea79 + +# Clang format change: see PR #775 +6e9792f3651d1e0c7045c5d67312c10f91ce6962 diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..786ba2130 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,9 @@ +# Configures dependabot + +version: 2 +updates: + # GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 000000000..cc83d7cd3 --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,9 @@ +# Add 'needs code review' label to any changes within the entire repository +needs code review: +- changed-files: + - any-glob-to-any-file: '**' + +# Add 'needs testing' label to any changes within the entire repository +needs testing: +- changed-files: + - any-glob-to-any-file: '**' diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 727a30602..ba2b0cfb1 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -16,3 +16,11 @@ Note that commit messages in PRs will generally be squashed to keep commit histo --> Replace this line with a description of your change (and screenshots/screenrecordings if applicable). + +### Code review: + +Replace this line with anything specific to look out for during code reviews. + +### Testing: + +Replace this line with instructions on how to test your pull request. The more detailed, the easier it is for reviewers to test, the faster your PR gets merged. diff --git a/.github/workflows/auto-label-pr.yml b/.github/workflows/auto-label-pr.yml new file mode 100644 index 000000000..659ff351d --- /dev/null +++ b/.github/workflows/auto-label-pr.yml @@ -0,0 +1,14 @@ +name: Auto-Labeler +on: + pull_request_target: + types: + - opened + +jobs: + labeler: + permissions: + contents: read + pull-requests: write + runs-on: ubuntu-latest + steps: + - uses: actions/labeler@v5 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e562dc3bf..5f647faa0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,7 @@ jobs: - name: Setup msvc uses: ilammy/msvc-dev-cmd@v1 - name: Configure cmake - run: cmake -G "Ninja" -DCMAKE_BUILD_TYPE:STRING="${{ env.BUILD_PROFILE }}" + run: cmake -G "Ninja" -B build -DCMAKE_BUILD_TYPE:STRING="${{ env.BUILD_PROFILE }}" - name: Setup resource file version shell: bash run: | @@ -33,7 +33,7 @@ jobs: FILEVERSION=$(echo ${{ env.NORTHSTAR_VERSION }} | tr '.' ',' | sed -E 's/-rc[0-9]+//' | tr -d '[:alpha:]') sed -i "s/0,0,0,1/${FILEVERSION}/g" primedev/ns_version.h - name: Build - run: cmake --build . + run: cmake --build build/ - name: Extract Short Commit Hash id: extract shell: bash @@ -43,13 +43,13 @@ jobs: with: name: NorthstarLauncher-${{ matrix.config.name }}-${{ steps.extract.outputs.commit }} path: | - game/ + build/game/ format-check: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: DoozyX/clang-format-lint-action@v0.16.2 + - uses: DoozyX/clang-format-lint-action@v0.18.2 with: source: 'primedev' exclude: 'primedev/include primedev/thirdparty primedev/wsockproxy' diff --git a/.github/workflows/merge-conflict-auto-label.yml b/.github/workflows/merge-conflict-auto-label.yml index cf5598a6e..abb7cabde 100644 --- a/.github/workflows/merge-conflict-auto-label.yml +++ b/.github/workflows/merge-conflict-auto-label.yml @@ -1,10 +1,11 @@ name: Merge Conflict Auto Label on: + workflow_dispatch: # Manual run push: branches: - main schedule: - - cron: "10 21 * * *" + - cron: "10 21 * * *" # Runs at 21:10; time was chosen based on contributor activity and low GitHub Actions cron load. jobs: triage: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e6dd8cc38..a440aea33 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,7 +21,7 @@ jobs: - name: Setup msvc uses: ilammy/msvc-dev-cmd@v1 - name: Configure cmake - run: cmake -G "Ninja" -DCMAKE_BUILD_TYPE:STRING="Release" + run: cmake -G "Ninja" -B build -DCMAKE_BUILD_TYPE:STRING="Release" - name: Setup resource file version shell: bash run: | @@ -30,22 +30,22 @@ jobs: FILEVERSION=$(echo ${{ env.NORTHSTAR_VERSION }} | tr '.' ',' | sed -E 's/-rc[0-9]+//' | tr -d '[:alpha:]') sed -i "s/0,0,0,1/${FILEVERSION}/g" primedev/ns_version.h - name: Build - run: cmake --build . + run: cmake --build build/ - name: Upload launcher build as artifact uses: actions/upload-artifact@v3 with: name: northstar-launcher path: | - game/*.exe - game/*.dll - game/bin/x64_retail/*.dll + build/game/*.exe + build/game/*.dll + build/game/bin/x64_retail/*.dll - name: Upload debug build artifact uses: actions/upload-artifact@v3 with: name: launcher-debug-files path: | - game/*.pdb - game/bin/x64_retail/*.pdb + build/game/*.pdb + build/game/bin/x64_retail/*.pdb upload-launcher-to-release: if: startsWith(github.ref, 'refs/tags/v') diff --git a/.gitignore b/.gitignore index c3c50a405..5e881b2d1 100644 --- a/.gitignore +++ b/.gitignore @@ -18,7 +18,7 @@ mono_crash.* # CMake output out/ -game/ +build/game/ build/ CMakeFiles/ cmake_install.cmake diff --git a/BUILD.md b/BUILD.md index f0ed5e722..76f20eb6b 100644 --- a/BUILD.md +++ b/BUILD.md @@ -37,9 +37,9 @@ Developers who can work a command line may be interested in using [Visual Studio - Follow the same steps as above for Visual Studio Build Tools, but instead of opening in Visual Studio, run the Command Prompt for VS 2022 and navigate to the NorthstarLauncher. -- Run `cmake . -G "Ninja"` to generate build files. +- Run `cmake . -G "Ninja" -B build` to generate build files. -- Run `cmake --build .` to build the project. +- Run `cmake --build build/` to build the project. ## Linux ### Steps @@ -47,8 +47,8 @@ Developers who can work a command line may be interested in using [Visual Studio 2. Use `cd` to navigate to the cloned repo's directory 3. Then, run the following commands in order: * `docker build --rm -t northstar-build-fedora .` -* `docker run --rm -it -e CC=cl -e CXX=cl --mount type=bind,source="$(pwd)",destination=/build northstar-build-fedora cmake . -DCMAKE_BUILD_TYPE=Release -DCMAKE_SYSTEM_NAME=Windows -G "Ninja"` -* `docker run --rm -it -e CC=cl -e CXX=cl --mount type=bind,source="$(pwd)",destination=/build northstar-build-fedora cmake --build .` +* `docker run --rm -it -e CC=cl -e CXX=cl --mount type=bind,source="$(pwd)",destination=/build northstar-build-fedora cmake . -DCMAKE_BUILD_TYPE=Release -DCMAKE_SYSTEM_NAME=Windows -G "Ninja" -B build` +* `docker run --rm -it -e CC=cl -e CXX=cl --mount type=bind,source="$(pwd)",destination=/build northstar-build-fedora cmake --build build/` #### Podman @@ -57,5 +57,5 @@ When using [`podman`](https://podman.io/) instead of Docker on an SELinux enable As such the corresponding commands are * `podman build --rm -t northstar-build-fedora .` -* `podman run --rm -it -e CC=cl -e CXX=cl --mount type=bind,source="$(pwd)",destination=/build,z northstar-build-fedora cmake . -DCMAKE_BUILD_TYPE=Release -DCMAKE_SYSTEM_NAME=Windows -G "Ninja"` -* `podman run --rm -it -e CC=cl -e CXX=cl --mount type=bind,source="$(pwd)",destination=/build,z northstar-build-fedora cmake --build .` +* `podman run --rm -it -e CC=cl -e CXX=cl --mount type=bind,source="$(pwd)",destination=/build,z northstar-build-fedora cmake . -DCMAKE_BUILD_TYPE=Release -DCMAKE_SYSTEM_NAME=Windows -G "Ninja" -B build` +* `podman run --rm -it -e CC=cl -e CXX=cl --mount type=bind,source="$(pwd)",destination=/build,z northstar-build-fedora cmake --build build/` diff --git a/Dockerfile b/Dockerfile index 261d649fa..af1bf3414 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,18 +1,18 @@ FROM registry.fedoraproject.org/fedora-toolbox:38 RUN dnf update -y && \ dnf install -y \ - git \ - wine \ - wine-mono \ - python3 \ - msitools \ - python3-simplejson \ - python3-six \ - cmake \ - ninja-build \ - make \ - samba \ - libunwind && \ + git \ + wine \ + wine-mono \ + python3 \ + msitools \ + python3-simplejson \ + python3-six \ + cmake \ + ninja-build \ + make \ + samba \ + libunwind && \ dnf clean all && \ mkdir /opt/msvc/ /build diff --git a/README.md b/README.md index 80be8fd0f..648122010 100644 --- a/README.md +++ b/README.md @@ -9,4 +9,6 @@ Check [BUILD.md](BUILD.md) for instructions on how to compile, you can also down ## Format +For project coding standards check out [STANDARDS.md](STANDARDS.md). + This project uses [clang-format](https://clang.llvm.org/docs/ClangFormat.html), make sure you run `clang-format -i --style=file --exclude=primedev/include primedev/*.cpp primedev/*.h` when opening a Pull Request. Check the tool's website for instructions on how to integrate it with your IDE. diff --git a/STANDARDS.md b/STANDARDS.md new file mode 100644 index 000000000..89d365238 --- /dev/null +++ b/STANDARDS.md @@ -0,0 +1,53 @@ +# Code standards + +There are exceptions, ask for them! + +### Preamble + +You are more than welcome to reformat any existing files using these rules should they fail to match them but please SPLIT your formatting changes from the rest by making a separate PR! + +### General rules + +> Basic rules that apply all the time. + +Always assert your assumptions! + +Use PascalCase for all names. + +Suffix structs with `_t`. + +Prefix classes with `C` (class) and `I` (abstract class). + +Prefix all class member variables with `m_`. + +Prefixes `g_` for global variables and `s_` for static variables are welcome. + +For hooking we use `o_` for function pointers pointing to the original implementation and `h_` for functions we replace them with. + +Document all function implementations and their arguments (if the argument is self explanatory you don't need to document it) valve style: +``` +//----------------------------------------------------------------------------- +// Purpose: MH_MakeHook wrapper +// Input : *ppOriginal - Original function being detoured +// pDetour - Detour function +// Output : true on success, false otherwise +//----------------------------------------------------------------------------- +``` + +Don't overcomment your code unless nescessary, expect the reader to have limited knowledge. + +Use `FIXME` comments for possible improvements/issues, `NOTE` for important information one might want to look into. + +### Valve source files + +> Rules that apply to all files from original valve code base. + +When adding or just modifying a file that's present in valve source place it where valve put it. + +Always use hungarian notation in these files. + +### New files + +> Rules that apply to Respawn or our own files. + +When adding new files follow the general rules, you don't have to use hungarian notation. Put the file where you think it makes the most sense. diff --git a/primedev/Northstar.cmake b/primedev/Northstar.cmake index 40583d283..4e8ec973b 100644 --- a/primedev/Northstar.cmake +++ b/primedev/Northstar.cmake @@ -62,12 +62,14 @@ add_library( "dedicated/dedicatedlogtoclient.cpp" "dedicated/dedicatedlogtoclient.h" "dedicated/dedicatedmaterialsystem.cpp" + "engine/gl_matsysiface.cpp" "engine/host.cpp" "engine/hoststate.cpp" "engine/hoststate.h" "engine/r2engine.cpp" "engine/r2engine.h" "engine/runframe.cpp" + "game/client/clientmode_shared.cpp" "logging/crashhandler.cpp" "logging/crashhandler.h" "logging/logging.cpp" @@ -150,7 +152,6 @@ add_library( "squirrel/squirrelautobind.cpp" "squirrel/squirrelautobind.h" "squirrel/squirrelclasstypes.h" - "squirrel/squirreldatatypes.h" "util/printcommands.cpp" "util/printcommands.h" "util/printmaps.cpp" @@ -161,6 +162,25 @@ add_library( "util/version.h" "util/wininfo.cpp" "util/wininfo.h" + "vscript/languages/squirrel_re/include/squirrel.h" + "vscript/languages/squirrel_re/squirrel/sqarray.h" + "vscript/languages/squirrel_re/squirrel/sqclosure.h" + "vscript/languages/squirrel_re/squirrel/sqcompiler.h" + "vscript/languages/squirrel_re/squirrel/sqfunctionproto.h" + "vscript/languages/squirrel_re/squirrel/sqlexer.h" + "vscript/languages/squirrel_re/squirrel/sqobject.h" + "vscript/languages/squirrel_re/squirrel/sqopcodes.h" + "vscript/languages/squirrel_re/squirrel/sqstate.h" + "vscript/languages/squirrel_re/squirrel/sqstring.h" + "vscript/languages/squirrel_re/squirrel/sqstruct.h" + "vscript/languages/squirrel_re/squirrel/sqtable.h" + "vscript/languages/squirrel_re/squirrel/squserdata.h" + "vscript/languages/squirrel_re/squirrel/sqvector.h" + "vscript/languages/squirrel_re/squirrel/sqvm.h" + "vscript/languages/squirrel_re/vsquirrel.h" + "vscript/vscript.h" + "windows/libsys.cpp" + "windows/libsys.h" "dllmain.cpp" "ns_version.h" "Northstar.def" diff --git a/primedev/client/audio.cpp b/primedev/client/audio.cpp index 635014141..4a759cdab 100644 --- a/primedev/client/audio.cpp +++ b/primedev/client/audio.cpp @@ -11,8 +11,6 @@ namespace fs = std::filesystem; -AUTOHOOK_INIT() - static const char* pszAudioEventName; ConVar* Cvar_mileslog_enable; @@ -389,14 +387,12 @@ bool ShouldPlayAudioEvent(const char* eventName, const std::shared_ptrsecond; if (!ShouldPlayAudioEvent(eventName, overrideData)) - return LoadSampleMetadata(sample, audioBuffer, audioBufferLength, audioType); + return o_pLoadSampleMetadata(sample, audioBuffer, audioBufferLength, audioType); void* data = 0; unsigned int dataLength = 0; @@ -479,7 +475,7 @@ bool, __fastcall, (void* sample, void* audioBuffer, unsigned int audioBufferLeng if (!data) { spdlog::warn("Could not fetch override sample data for event {}! Using original data instead.", eventName); - return LoadSampleMetadata(sample, audioBuffer, audioBufferLength, audioType); + return o_pLoadSampleMetadata(sample, audioBuffer, audioBufferLength, audioType); } audioBuffer = data; @@ -490,26 +486,22 @@ bool, __fastcall, (void* sample, void* audioBuffer, unsigned int audioBufferLeng *(unsigned int*)((uintptr_t)sample + 0xF0) = audioBufferLength; // 64 - Auto-detect sample type - bool res = LoadSampleMetadata(sample, audioBuffer, audioBufferLength, 64); + bool res = o_pLoadSampleMetadata(sample, audioBuffer, audioBufferLength, 64); if (!res) spdlog::error("LoadSampleMetadata failed! The game will crash :("); return res; } -// clang-format off -AUTOHOOK(sub_1800294C0, mileswin64.dll + 0x294C0, -void*, __fastcall, (void* a1, void* a2)) -// clang-format on +static void*(__fastcall* o_pSub_1800294C0)(void* a1, void* a2) = nullptr; +static void* __fastcall h_Sub_1800294C0(void* a1, void* a2) { pszAudioEventName = reinterpret_cast((*((__int64*)a2 + 6))); - return sub_1800294C0(a1, a2); + return o_pSub_1800294C0(a1, a2); } -// clang-format off -AUTOHOOK(MilesLog, client.dll + 0x57DAD0, -void, __fastcall, (int level, const char* string)) -// clang-format on +static void(__fastcall* o_pMilesLog)(int level, const char* string) = nullptr; +static void __fastcall h_MilesLog(int level, const char* string) { if (!Cvar_mileslog_enable->GetBool()) return; @@ -517,6 +509,55 @@ void, __fastcall, (int level, const char* string)) spdlog::info("[MSS] {} - {}", level, string); } +static void(__fastcall* o_pSub_18003EBD0)(DWORD dwThreadID, const char* threadName) = nullptr; +static void __fastcall h_Sub_18003EBD0(DWORD dwThreadID, const char* threadName) +{ + HANDLE hThread = OpenThread(THREAD_SET_LIMITED_INFORMATION, FALSE, dwThreadID); + + if (hThread != NULL) + { + // TODO: This "method" of "charset conversion" from string to wstring is abhorrent. Change it to a proper one + // as soon as Northstar has some helper function to do proper charset conversions. + auto tmp = std::string(threadName); + HRESULT WINAPI _SetThreadDescription(HANDLE hThread, PCWSTR lpThreadDescription); + _SetThreadDescription(hThread, std::wstring(tmp.begin(), tmp.end()).c_str()); + + CloseHandle(hThread); + } + + o_pSub_18003EBD0(dwThreadID, threadName); +} + +static char*(__fastcall* o_pSub_18003BC10)(void* a1, void* a2, void* a3, void* a4, void* a5, int a6) = nullptr; +static char* __fastcall h_Sub_18003BC10(void* a1, void* a2, void* a3, void* a4, void* a5, int a6) +{ + HANDLE hThread; + char* ret = o_pSub_18003BC10(a1, a2, a3, a4, a5, a6); + + if (ret != NULL && (hThread = reinterpret_cast(*((uint64_t*)ret + 55))) != NULL) + { + HRESULT WINAPI _SetThreadDescription(HANDLE hThread, PCWSTR lpThreadDescription); + _SetThreadDescription(hThread, L"[Miles] WASAPI Service Thread"); + } + + return ret; +} + +ON_DLL_LOAD("mileswin64.dll", MilesWin64_Audio, (CModule module)) +{ + o_pLoadSampleMetadata = module.Offset(0xF110).RCast(); + HookAttach(&(PVOID&)o_pLoadSampleMetadata, (PVOID)h_LoadSampleMetadata); + + o_pSub_1800294C0 = module.Offset(0x294C0).RCast(); + HookAttach(&(PVOID&)o_pSub_1800294C0, (PVOID)h_Sub_1800294C0); + + o_pSub_18003EBD0 = module.Offset(0x3EBD0).RCast(); + HookAttach(&(PVOID&)o_pSub_18003EBD0, (PVOID)h_Sub_18003EBD0); + + o_pSub_18003BC10 = module.Offset(0x3BC10).RCast(); + HookAttach(&(PVOID&)o_pSub_18003BC10, (PVOID)h_Sub_18003BC10); +} + ON_DLL_LOAD_RELIESON("engine.dll", MilesLogFuncHooks, ConVar, (CModule module)) { Cvar_mileslog_enable = new ConVar("mileslog_enable", "0", FCVAR_NONE, "Enables/disables whether the mileslog func should be logged"); @@ -524,7 +565,8 @@ ON_DLL_LOAD_RELIESON("engine.dll", MilesLogFuncHooks, ConVar, (CModule module)) ON_DLL_LOAD_CLIENT_RELIESON("client.dll", AudioHooks, ConVar, (CModule module)) { - AUTOHOOK_DISPATCH() + o_pMilesLog = module.Offset(0x57DAD0).RCast(); + HookAttach(&(PVOID&)o_pMilesLog, (PVOID)h_MilesLog); Cvar_ns_print_played_sounds = new ConVar("ns_print_played_sounds", "0", FCVAR_NONE, ""); MilesStopAll = module.Offset(0x580850).RCast(); diff --git a/primedev/client/clientauthhooks.cpp b/primedev/client/clientauthhooks.cpp index 35ae3aa77..ceb648a53 100644 --- a/primedev/client/clientauthhooks.cpp +++ b/primedev/client/clientauthhooks.cpp @@ -3,8 +3,6 @@ #include "client/r2client.h" #include "core/vanilla.h" -AUTOHOOK_INIT() - ConVar* Cvar_ns_has_agreed_to_send_token; // mirrored in script @@ -12,16 +10,14 @@ const int NOT_DECIDED_TO_SEND_TOKEN = 0; const int AGREED_TO_SEND_TOKEN = 1; const int DISAGREED_TO_SEND_TOKEN = 2; -// clang-format off -AUTOHOOK(AuthWithStryder, engine.dll + 0x1843A0, -void, __fastcall, (void* a1)) -// clang-format on +static void (*__fastcall o_pAuthWithStryder)(void* a1) = nullptr; +static void __fastcall h_AuthWithStryder(void* a1) { // don't attempt to do Atlas auth if we are in vanilla compatibility mode // this prevents users from joining untrustworthy servers (unless they use a concommand or something) if (g_pVanillaCompatibility->GetVanillaCompatibility()) { - AuthWithStryder(a1); + o_pAuthWithStryder(a1); return; } @@ -38,15 +34,13 @@ void, __fastcall, (void* a1)) *g_pLocalPlayerOriginToken = 0; } - AuthWithStryder(a1); + o_pAuthWithStryder(a1); } char* p3PToken; -// clang-format off -AUTOHOOK(Auth3PToken, engine.dll + 0x183760, -char*, __fastcall, ()) -// clang-format on +static char* (*__fastcall o_pAuth3PToken)() = nullptr; +static char* __fastcall h_Auth3PToken() { if (!g_pVanillaCompatibility->GetVanillaCompatibility() && g_pMasterServerManager->m_sOwnClientAuthToken[0]) { @@ -54,12 +48,16 @@ char*, __fastcall, ()) strcpy(p3PToken, "Protocol 3: Protect the Pilot"); } - return Auth3PToken(); + return o_pAuth3PToken(); } ON_DLL_LOAD_CLIENT_RELIESON("engine.dll", ClientAuthHooks, ConVar, (CModule module)) { - AUTOHOOK_DISPATCH() + o_pAuthWithStryder = module.Offset(0x1843A0).RCast(); + HookAttach(&(PVOID&)o_pAuthWithStryder, (PVOID)h_AuthWithStryder); + + o_pAuth3PToken = module.Offset(0x183760).RCast(); + HookAttach(&(PVOID&)o_pAuth3PToken, (PVOID)h_Auth3PToken); p3PToken = module.Offset(0x13979D80).RCast(); diff --git a/primedev/client/clientruihooks.cpp b/primedev/client/clientruihooks.cpp index ad50d11a8..e49e6f63a 100644 --- a/primedev/client/clientruihooks.cpp +++ b/primedev/client/clientruihooks.cpp @@ -1,23 +1,20 @@ #include "core/convar/convar.h" -AUTOHOOK_INIT() - ConVar* Cvar_rui_drawEnable; -// clang-format off -AUTOHOOK(DrawRUIFunc, engine.dll + 0xFC500, -bool, __fastcall, (void* a1, float* a2)) -// clang-format on +static bool (*__fastcall o_pDrawRUIFunc)(void* a1, float* a2) = nullptr; +static bool __fastcall h_DrawRUIFunc(void* a1, float* a2) { if (!Cvar_rui_drawEnable->GetBool()) return 0; - return DrawRUIFunc(a1, a2); + return o_pDrawRUIFunc(a1, a2); } ON_DLL_LOAD_CLIENT_RELIESON("engine.dll", RUI, ConVar, (CModule module)) { - AUTOHOOK_DISPATCH() + o_pDrawRUIFunc = module.Offset(0xFC500).RCast(); + HookAttach(&(PVOID&)o_pDrawRUIFunc, (PVOID)h_DrawRUIFunc); Cvar_rui_drawEnable = new ConVar("rui_drawEnable", "1", FCVAR_CLIENTDLL, "Controls whether RUI should be drawn"); } diff --git a/primedev/client/clientvideooverrides.cpp b/primedev/client/clientvideooverrides.cpp index d8aa27541..54bb64697 100644 --- a/primedev/client/clientvideooverrides.cpp +++ b/primedev/client/clientvideooverrides.cpp @@ -1,11 +1,7 @@ #include "mods/modmanager.h" -AUTOHOOK_INIT() - -// clang-format off -AUTOHOOK_PROCADDRESS(BinkOpen, bink2w64.dll, BinkOpen, -void*, __fastcall, (const char* path, uint32_t flags)) -// clang-format on +static void* (*__fastcall o_pBinkOpen)(const char* path, uint32_t flags) = nullptr; +static void* __fastcall h_BinkOpen(const char* path, uint32_t flags) { std::string filename(fs::path(path).filename().string()); spdlog::info("BinkOpen {}", filename); @@ -25,16 +21,20 @@ void*, __fastcall, (const char* path, uint32_t flags)) { // create new path fs::path binkPath(fileOwner->m_ModDirectory / "media" / filename); - return BinkOpen(binkPath.string().c_str(), flags); + return o_pBinkOpen(binkPath.string().c_str(), flags); } else - return BinkOpen(path, flags); + return o_pBinkOpen(path, flags); } -ON_DLL_LOAD_CLIENT("engine.dll", BinkVideo, (CModule module)) +ON_DLL_LOAD_CLIENT("bink2w64.dll", BinkRead, (CModule module)) { - AUTOHOOK_DISPATCH() + o_pBinkOpen = module.GetExportedFunction("BinkOpen").RCast(); + HookAttach(&(PVOID&)o_pBinkOpen, (PVOID)h_BinkOpen); +} +ON_DLL_LOAD_CLIENT("engine.dll", BinkVideo, (CModule module)) +{ // remove engine check for whether the bik we're trying to load exists in r2/media, as this will fail for biks in mods // note: the check in engine is actually unnecessary, so it's just useless in practice and we lose nothing by removing it module.Offset(0x459AD).NOP(6); diff --git a/primedev/client/debugoverlay.cpp b/primedev/client/debugoverlay.cpp index a67b8355c..9cfc1612d 100644 --- a/primedev/client/debugoverlay.cpp +++ b/primedev/client/debugoverlay.cpp @@ -5,8 +5,6 @@ #include "core/math/vector.h" #include "server/ai_helper.h" -AUTOHOOK_INIT() - enum OverlayType_t { OVERLAY_BOX = 0, @@ -41,10 +39,7 @@ struct OverlayBase_t struct OverlayLine_t : public OverlayBase_t { - OverlayLine_t() - { - m_Type = OVERLAY_LINE; - } + OverlayLine_t() { m_Type = OVERLAY_LINE; } Vector3 origin; Vector3 dest; @@ -57,10 +52,7 @@ struct OverlayLine_t : public OverlayBase_t struct OverlayBox_t : public OverlayBase_t { - OverlayBox_t() - { - m_Type = OVERLAY_BOX; - } + OverlayBox_t() { m_Type = OVERLAY_BOX; } Vector3 origin; Vector3 mins; @@ -74,10 +66,7 @@ struct OverlayBox_t : public OverlayBase_t struct OverlayTriangle_t : public OverlayBase_t { - OverlayTriangle_t() - { - m_Type = OVERLAY_TRIANGLE; - } + OverlayTriangle_t() { m_Type = OVERLAY_TRIANGLE; } Vector3 p1; Vector3 p2; @@ -91,10 +80,7 @@ struct OverlayTriangle_t : public OverlayBase_t struct OverlaySweptBox_t : public OverlayBase_t { - OverlaySweptBox_t() - { - m_Type = OVERLAY_SWEPT_BOX; - } + OverlaySweptBox_t() { m_Type = OVERLAY_SWEPT_BOX; } Vector3 start; Vector3 end; @@ -109,10 +95,7 @@ struct OverlaySweptBox_t : public OverlayBase_t struct OverlaySphere_t : public OverlayBase_t { - OverlaySphere_t() - { - m_Type = OVERLAY_SPHERE; - } + OverlaySphere_t() { m_Type = OVERLAY_SPHERE; } Vector3 vOrigin; float flRadius; @@ -137,10 +120,8 @@ OverlayBase_t** s_pOverlays; int* g_nRenderTickCount; int* g_nOverlayTickCount; -// clang-format off -AUTOHOOK(DrawOverlay, engine.dll + 0xABCB0, -void, __fastcall, (OverlayBase_t * pOverlay)) -// clang-format on +static void(__fastcall* o_pDrawOverlay)(OverlayBase_t* pOverlay) = nullptr; +static void __fastcall h_DrawOverlay(OverlayBase_t* pOverlay) { EnterCriticalSection(s_OverlayMutex); @@ -220,10 +201,8 @@ void, __fastcall, (OverlayBase_t * pOverlay)) LeaveCriticalSection(s_OverlayMutex); } -// clang-format off -AUTOHOOK(DrawAllOverlays, engine.dll + 0xAB780, -void, __fastcall, (bool bRender)) -// clang-format on +static void(__fastcall* o_pDrawAllOverlays)(bool bRender) = nullptr; +static void __fastcall h_DrawAllOverlays(bool bRender) { EnterCriticalSection(s_OverlayMutex); @@ -274,10 +253,7 @@ void, __fastcall, (bool bRender)) if (bShouldDraw && bRender && (Cvar_enable_debug_overlays->GetBool() || pCurrOverlay->m_Type == OVERLAY_SMARTAMMO)) { - // call the new function, not the original - // note: if there is a beter way to call the hooked version of an - // autohook func then that would be better than this - __autohookfuncDrawOverlay(pCurrOverlay); + h_DrawOverlay(pCurrOverlay); } pPrevOverlay = pCurrOverlay; @@ -295,7 +271,11 @@ void, __fastcall, (bool bRender)) ON_DLL_LOAD_CLIENT_RELIESON("engine.dll", DebugOverlay, ConVar, (CModule module)) { - AUTOHOOK_DISPATCH() + o_pDrawOverlay = module.Offset(0xABCB0).RCast(); + HookAttach(&(PVOID&)o_pDrawOverlay, (PVOID)h_DrawOverlay); + + o_pDrawAllOverlays = module.Offset(0xAB780).RCast(); + HookAttach(&(PVOID&)o_pDrawAllOverlays, (PVOID)h_DrawAllOverlays); OverlayBase_t__IsDead = module.Offset(0xACAC0).RCast(); OverlayBase_t__DestroyOverlay = module.Offset(0xAB680).RCast(); diff --git a/primedev/client/languagehooks.cpp b/primedev/client/languagehooks.cpp index 36b5d5aed..80d3c31d9 100644 --- a/primedev/client/languagehooks.cpp +++ b/primedev/client/languagehooks.cpp @@ -5,8 +5,6 @@ namespace fs = std::filesystem; -AUTOHOOK_INIT() - typedef LANGID (*Tier0_DetectDefaultLanguageType)(); bool CheckLangAudioExists(char* lang) @@ -48,10 +46,8 @@ std::string GetAnyInstalledAudioLanguage() return "NO LANGUAGE DETECTED"; } -// clang-format off -AUTOHOOK(GetGameLanguage, tier0.dll + 0xF560, -char*, __fastcall, ()) -// clang-format on +static char*(__fastcall* o_pGetGameLanguage)() = nullptr; +static char* __fastcall h_GetGameLanguage() { auto tier0Handle = GetModuleHandleA("tier0.dll"); auto Tier0_DetectDefaultLanguageType = GetProcAddress(tier0Handle, "Tier0_DetectDefaultLanguage"); @@ -78,7 +74,7 @@ char*, __fastcall, ()) canOriginDictateLang = true; // let it try { - auto lang = GetGameLanguage(); + auto lang = o_pGetGameLanguage(); if (!CheckLangAudioExists(lang)) { if (strcmp(lang, "russian") != @@ -96,7 +92,7 @@ char*, __fastcall, ()) Tier0_DetectDefaultLanguageType(); // force the global in tier0 to be populated with language inferred from user's system rather than // defaulting to Russian canOriginDictateLang = false; // Origin has no say anymore, we will fallback to user's system setup language - auto lang = GetGameLanguage(); + auto lang = o_pGetGameLanguage(); spdlog::info("Detected system language: {}", lang); if (!CheckLangAudioExists(lang)) { @@ -113,5 +109,6 @@ char*, __fastcall, ()) ON_DLL_LOAD_CLIENT("tier0.dll", LanguageHooks, (CModule module)) { - AUTOHOOK_DISPATCH() + o_pGetGameLanguage = module.Offset(0xF560).RCast(); + HookAttach(&(PVOID&)o_pGetGameLanguage, (PVOID)h_GetGameLanguage); } diff --git a/primedev/client/latencyflex.cpp b/primedev/client/latencyflex.cpp index 395578709..be93fabdc 100644 --- a/primedev/client/latencyflex.cpp +++ b/primedev/client/latencyflex.cpp @@ -1,20 +1,16 @@ #include "core/convar/convar.h" -AUTOHOOK_INIT() - ConVar* Cvar_r_latencyflex; void (*m_winelfx_WaitAndBeginFrame)(); -// clang-format off -AUTOHOOK(OnRenderStart, client.dll + 0x1952C0, -void, __fastcall, ()) -// clang-format on +void(__fastcall* o_pOnRenderStart)() = nullptr; +void __fastcall h_OnRenderStart() { if (Cvar_r_latencyflex->GetBool() && m_winelfx_WaitAndBeginFrame) m_winelfx_WaitAndBeginFrame(); - OnRenderStart(); + o_pOnRenderStart(); } ON_DLL_LOAD_CLIENT_RELIESON("client.dll", LatencyFlex, ConVar, (CModule module)) @@ -36,7 +32,8 @@ ON_DLL_LOAD_CLIENT_RELIESON("client.dll", LatencyFlex, ConVar, (CModule module)) return; } - AUTOHOOK_DISPATCH() + o_pOnRenderStart = module.Offset(0x1952C0).RCast(); + HookAttach(&(PVOID&)o_pOnRenderStart, (PVOID)h_OnRenderStart); spdlog::info("LatencyFleX initialized."); Cvar_r_latencyflex = new ConVar("r_latencyflex", "1", FCVAR_ARCHIVE, "Whether or not to use LatencyFleX input latency reduction."); diff --git a/primedev/client/localchatwriter.cpp b/primedev/client/localchatwriter.cpp index 35cc065f4..042e77d9d 100644 --- a/primedev/client/localchatwriter.cpp +++ b/primedev/client/localchatwriter.cpp @@ -102,7 +102,10 @@ Color lightColors[8] = { class AnsiEscapeParser { public: - explicit AnsiEscapeParser(LocalChatWriter* writer) : m_writer(writer) {} + explicit AnsiEscapeParser(LocalChatWriter* writer) + : m_writer(writer) + { + } void HandleVal(unsigned long val) { @@ -257,7 +260,10 @@ class AnsiEscapeParser } }; -LocalChatWriter::LocalChatWriter(Context context) : m_context(context) {} +LocalChatWriter::LocalChatWriter(Context context) + : m_context(context) +{ +} void LocalChatWriter::Write(const char* str) { diff --git a/primedev/client/modlocalisation.cpp b/primedev/client/modlocalisation.cpp index 2b73876b3..e5efa0745 100644 --- a/primedev/client/modlocalisation.cpp +++ b/primedev/client/modlocalisation.cpp @@ -1,55 +1,58 @@ #include "mods/modmanager.h" -AUTOHOOK_INIT() - void* g_pVguiLocalize; -// clang-format off -AUTOHOOK(CLocalize__AddFile, localize.dll + 0x6D80, -bool, __fastcall, (void* pVguiLocalize, const char* path, const char* pathId, bool bIncludeFallbackSearchPaths)) -// clang-format on +static bool(__fastcall* o_pCLocalise__AddFile)( + void* pVguiLocalize, const char* path, const char* pathId, bool bIncludeFallbackSearchPaths) = nullptr; +static bool __fastcall h_CLocalise__AddFile(void* pVguiLocalize, const char* path, const char* pathId, bool bIncludeFallbackSearchPaths) { // save this for later g_pVguiLocalize = pVguiLocalize; - bool ret = CLocalize__AddFile(pVguiLocalize, path, pathId, bIncludeFallbackSearchPaths); + bool ret = o_pCLocalise__AddFile(pVguiLocalize, path, pathId, bIncludeFallbackSearchPaths); if (ret) spdlog::info("Loaded localisation file {} successfully", path); return true; } -// clang-format off -AUTOHOOK(CLocalize__ReloadLocalizationFiles, localize.dll + 0xB830, -void, __fastcall, (void* pVguiLocalize)) -// clang-format on +static void(__fastcall* o_pCLocalize__ReloadLocalizationFiles)(void* pVguiLocalize) = nullptr; +static void __fastcall h_CLocalize__ReloadLocalizationFiles(void* pVguiLocalize) { // load all mod localization manually, so we keep track of all files, not just previously loaded ones for (Mod mod : g_pModManager->m_LoadedMods) if (mod.m_bEnabled) for (std::string& localisationFile : mod.LocalisationFiles) - CLocalize__AddFile(g_pVguiLocalize, localisationFile.c_str(), nullptr, false); + o_pCLocalise__AddFile(g_pVguiLocalize, localisationFile.c_str(), nullptr, false); spdlog::info("reloading localization..."); - CLocalize__ReloadLocalizationFiles(pVguiLocalize); + o_pCLocalize__ReloadLocalizationFiles(pVguiLocalize); } -// clang-format off -AUTOHOOK(CEngineVGui__Init, engine.dll + 0x247E10, -void, __fastcall, (void* self)) -// clang-format on +static void(__fastcall* o_pCEngineVGui__Init)(void* self) = nullptr; +static void __fastcall h_CEngineVGui__Init(void* self) { - CEngineVGui__Init(self); // this loads r1_english, valve_english, dev_english + o_pCEngineVGui__Init(self); // this loads r1_english, valve_english, dev_english // previously we did this in CLocalize::AddFile, but for some reason it won't properly overwrite localization from // files loaded previously if done there, very weird but this works so whatever for (Mod mod : g_pModManager->m_LoadedMods) if (mod.m_bEnabled) for (std::string& localisationFile : mod.LocalisationFiles) - CLocalize__AddFile(g_pVguiLocalize, localisationFile.c_str(), nullptr, false); + o_pCLocalise__AddFile(g_pVguiLocalize, localisationFile.c_str(), nullptr, false); +} + +ON_DLL_LOAD_CLIENT("engine.dll", VGuiInit, (CModule module)) +{ + o_pCEngineVGui__Init = module.Offset(0x247E10).RCast(); + HookAttach(&(PVOID&)o_pCEngineVGui__Init, (PVOID)h_CEngineVGui__Init); } ON_DLL_LOAD_CLIENT("localize.dll", Localize, (CModule module)) { - AUTOHOOK_DISPATCH() + o_pCLocalise__AddFile = module.Offset(0x6D80).RCast(); + HookAttach(&(PVOID&)o_pCLocalise__AddFile, (PVOID)h_CLocalise__AddFile); + + o_pCLocalize__ReloadLocalizationFiles = module.Offset(0xB830).RCast(); + HookAttach(&(PVOID&)o_pCLocalize__ReloadLocalizationFiles, (PVOID)h_CLocalize__ReloadLocalizationFiles); } diff --git a/primedev/client/rejectconnectionfixes.cpp b/primedev/client/rejectconnectionfixes.cpp index 1b326a3c8..adfd772ce 100644 --- a/primedev/client/rejectconnectionfixes.cpp +++ b/primedev/client/rejectconnectionfixes.cpp @@ -1,12 +1,8 @@ #include "engine/r2engine.h" -AUTOHOOK_INIT() - // this is called from when our connection is rejected, this is the only case we're hooking this for -// clang-format off -AUTOHOOK(COM_ExplainDisconnection, engine.dll + 0x1342F0, -void,, (bool a1, const char* fmt, ...)) -// clang-format on +static void (*o_pCOM_ExplainDisconnection)(bool a1, const char* fmt, ...) = nullptr; +static void h_COM_ExplainDisconnection(bool a1, const char* fmt, ...) { va_list va; va_start(va, fmt); @@ -25,10 +21,11 @@ void,, (bool a1, const char* fmt, ...)) Cbuf_AddText(Cbuf_GetCurrentPlayer(), "disconnect", cmd_source_t::kCommandSrcCode); } - return COM_ExplainDisconnection(a1, "%s", buf); + return o_pCOM_ExplainDisconnection(a1, "%s", buf); } ON_DLL_LOAD_CLIENT("engine.dll", RejectConnectionFixes, (CModule module)) { - AUTOHOOK_DISPATCH() + o_pCOM_ExplainDisconnection = module.Offset(0x1342F0).RCast(); + HookAttach(&(PVOID&)o_pCOM_ExplainDisconnection, (PVOID)h_COM_ExplainDisconnection); } diff --git a/primedev/core/convar/convar.cpp b/primedev/core/convar/convar.cpp index 87a1e1591..802c088dc 100644 --- a/primedev/core/convar/convar.cpp +++ b/primedev/core/convar/convar.cpp @@ -1,7 +1,7 @@ #include "bits.h" #include "cvar.h" #include "convar.h" -#include "core/sourceinterface.h" +#include "core/tier1.h" #include @@ -35,8 +35,7 @@ ON_DLL_LOAD("engine.dll", ConVar, (CModule module)) g_pConVar_Vtable = module.Offset(0x67FD28); g_pIConVar_Vtable = module.Offset(0x67FDC8); - g_pCVarInterface = new SourceInterface("vstdlib.dll", "VEngineCvar007"); - g_pCVar = *g_pCVarInterface; + g_pCVar = Sys_GetFactoryPtr("vstdlib.dll", "VEngineCvar007").RCast(); } //----------------------------------------------------------------------------- diff --git a/primedev/core/convar/cvar.cpp b/primedev/core/convar/cvar.cpp index aa5f03653..78da1ad2e 100644 --- a/primedev/core/convar/cvar.cpp +++ b/primedev/core/convar/cvar.cpp @@ -22,5 +22,4 @@ std::unordered_map CCvar::DumpToMap() return allConVars; } -SourceInterface* g_pCVarInterface; CCvar* g_pCVar; diff --git a/primedev/core/convar/cvar.h b/primedev/core/convar/cvar.h index beaa84f47..024f2b879 100644 --- a/primedev/core/convar/cvar.h +++ b/primedev/core/convar/cvar.h @@ -34,5 +34,4 @@ class CCvar std::unordered_map DumpToMap(); }; -extern SourceInterface* g_pCVarInterface; extern CCvar* g_pCVar; diff --git a/primedev/core/filesystem/filesystem.cpp b/primedev/core/filesystem/filesystem.cpp index b39939e45..3c711e8e3 100644 --- a/primedev/core/filesystem/filesystem.cpp +++ b/primedev/core/filesystem/filesystem.cpp @@ -1,34 +1,32 @@ #include "filesystem.h" -#include "core/sourceinterface.h" +#include "core/tier1.h" #include "mods/modmanager.h" #include #include -AUTOHOOK_INIT() - bool bReadingOriginalFile = false; std::string sCurrentModPath; ConVar* Cvar_ns_fs_log_reads; -SourceInterface* g_pFilesystem; +IFileSystem* g_pFilesystem; std::string ReadVPKFile(const char* path) { // read scripts.rson file, todo: check if this can be overwritten - FileHandle_t fileHandle = (*g_pFilesystem)->m_vtable2->Open(&(*g_pFilesystem)->m_vtable2, path, "rb", "GAME", 0); + FileHandle_t fileHandle = g_pFilesystem->m_vtable2->Open(&g_pFilesystem->m_vtable2, path, "rb", "GAME", 0); std::stringstream fileStream; int bytesRead = 0; char data[4096]; do { - bytesRead = (*g_pFilesystem)->m_vtable2->Read(&(*g_pFilesystem)->m_vtable2, data, (int)std::size(data), fileHandle); + bytesRead = g_pFilesystem->m_vtable2->Read(&g_pFilesystem->m_vtable2, data, (int)std::size(data), fileHandle); fileStream.write(data, bytesRead); } while (bytesRead == std::size(data)); - (*g_pFilesystem)->m_vtable2->Close(*g_pFilesystem, fileHandle); + g_pFilesystem->m_vtable2->Close(g_pFilesystem, fileHandle); return fileStream.str(); } @@ -44,18 +42,17 @@ std::string ReadVPKOriginalFile(const char* path) return ret; } -// clang-format off -HOOK(AddSearchPathHook, AddSearchPath, -void, __fastcall, (IFileSystem * fileSystem, const char* pPath, const char* pathID, SearchPathAdd_t addType)) -// clang-format on +static void(__fastcall* o_pAddSearchPath)(IFileSystem* fileSystem, const char* pPath, const char* pathID, SearchPathAdd_t addType) = + nullptr; +static void __fastcall h_AddSearchPath(IFileSystem* fileSystem, const char* pPath, const char* pathID, SearchPathAdd_t addType) { - AddSearchPath(fileSystem, pPath, pathID, addType); + o_pAddSearchPath(fileSystem, pPath, pathID, addType); // make sure current mod paths are at head if (!strcmp(pathID, "GAME") && sCurrentModPath.compare(pPath) && addType == PATH_ADD_TO_HEAD) { - AddSearchPath(fileSystem, sCurrentModPath.c_str(), "GAME", PATH_ADD_TO_HEAD); - AddSearchPath(fileSystem, GetCompiledAssetsPath().string().c_str(), "GAME", PATH_ADD_TO_HEAD); + o_pAddSearchPath(fileSystem, sCurrentModPath.c_str(), "GAME", PATH_ADD_TO_HEAD); + o_pAddSearchPath(fileSystem, GetCompiledAssetsPath().string().c_str(), "GAME", PATH_ADD_TO_HEAD); } } @@ -67,13 +64,13 @@ void SetNewModSearchPaths(Mod* mod) { if ((fs::absolute(mod->m_ModDirectory) / MOD_OVERRIDE_DIR).string().compare(sCurrentModPath)) { - AddSearchPath( - &*(*g_pFilesystem), (fs::absolute(mod->m_ModDirectory) / MOD_OVERRIDE_DIR).string().c_str(), "GAME", PATH_ADD_TO_HEAD); + o_pAddSearchPath( + g_pFilesystem, (fs::absolute(mod->m_ModDirectory) / MOD_OVERRIDE_DIR).string().c_str(), "GAME", PATH_ADD_TO_HEAD); sCurrentModPath = (fs::absolute(mod->m_ModDirectory) / MOD_OVERRIDE_DIR).string(); } } else // push compiled to head - AddSearchPath(&*(*g_pFilesystem), fs::absolute(GetCompiledAssetsPath()).string().c_str(), "GAME", PATH_ADD_TO_HEAD); + o_pAddSearchPath(g_pFilesystem, fs::absolute(GetCompiledAssetsPath()).string().c_str(), "GAME", PATH_ADD_TO_HEAD); } bool TryReplaceFile(const char* pPath, bool shouldCompile) @@ -97,22 +94,17 @@ bool TryReplaceFile(const char* pPath, bool shouldCompile) } // force modded files to be read from mods, not cache -// clang-format off -HOOK(ReadFromCacheHook, ReadFromCache, -bool, __fastcall, (IFileSystem * filesystem, char* pPath, void* result)) -// clang-format off +static bool(__fastcall* o_pReadFromCache)(IFileSystem* filesystem, char* pPath, void* result) = nullptr; +static bool __fastcall h_ReadFromCache(IFileSystem* filesystem, char* pPath, void* result) { if (TryReplaceFile(pPath, true)) return false; - return ReadFromCache(filesystem, pPath, result); + return o_pReadFromCache(filesystem, pPath, result); } -// force modded files to be read from mods, not vpk -// clang-format off -AUTOHOOK(ReadFileFromVPK, filesystem_stdio.dll + 0x5CBA0, -FileHandle_t, __fastcall, (VPKData* vpkInfo, uint64_t* b, char* filename)) -// clang-format on +static FileHandle_t(__fastcall* o_pReadFileFromVPK)(VPKData* vpkInfo, uint64_t* b, char* filename) = nullptr; +static FileHandle_t __fastcall h_ReadFileFromVPK(VPKData* vpkInfo, uint64_t* b, char* filename) { // don't compile here because this is only ever called from OpenEx, which already compiles if (TryReplaceFile(filename, false)) @@ -121,22 +113,24 @@ FileHandle_t, __fastcall, (VPKData* vpkInfo, uint64_t* b, char* filename)) return b; } - return ReadFileFromVPK(vpkInfo, b, filename); + return o_pReadFileFromVPK(vpkInfo, b, filename); } -// clang-format off -AUTOHOOK(CBaseFileSystem__OpenEx, filesystem_stdio.dll + 0x15F50, -FileHandle_t, __fastcall, (IFileSystem* filesystem, const char* pPath, const char* pOptions, uint32_t flags, const char* pPathID, char **ppszResolvedFilename)) -// clang-format on +static FileHandle_t(__fastcall* o_pCBaseFileSystem__OpenEx)( + IFileSystem* filesystem, const char* pPath, const char* pOptions, uint32_t flags, const char* pPathID, char** ppszResolvedFilename) = + nullptr; +static FileHandle_t __fastcall h_CBaseFileSystem__OpenEx( + IFileSystem* filesystem, const char* pPath, const char* pOptions, uint32_t flags, const char* pPathID, char** ppszResolvedFilename) { TryReplaceFile(pPath, true); - return CBaseFileSystem__OpenEx(filesystem, pPath, pOptions, flags, pPathID, ppszResolvedFilename); + return o_pCBaseFileSystem__OpenEx(filesystem, pPath, pOptions, flags, pPathID, ppszResolvedFilename); } -HOOK(MountVPKHook, MountVPK, VPKData*, , (IFileSystem * fileSystem, const char* pVpkPath)) +static VPKData* (*o_pMountVPK)(IFileSystem* fileSystem, const char* pVpkPath) = nullptr; +static VPKData* h_MountVPK(IFileSystem* fileSystem, const char* pVpkPath) { NS::log::fs->info("MountVPK {}", pVpkPath); - VPKData* ret = MountVPK(fileSystem, pVpkPath); + VPKData* ret = o_pMountVPK(fileSystem, pVpkPath); for (Mod mod : g_pModManager->m_LoadedMods) { @@ -156,7 +150,7 @@ HOOK(MountVPKHook, MountVPK, VPKData*, , (IFileSystem * fileSystem, const char* continue; } - VPKData* loaded = MountVPK(fileSystem, vpkEntry.m_sVpkPath.c_str()); + VPKData* loaded = o_pMountVPK(fileSystem, vpkEntry.m_sVpkPath.c_str()); if (!ret) // this is primarily for map vpks and stuff, so the map's vpk is what gets returned from here ret = loaded; } @@ -167,11 +161,18 @@ HOOK(MountVPKHook, MountVPK, VPKData*, , (IFileSystem * fileSystem, const char* ON_DLL_LOAD("filesystem_stdio.dll", Filesystem, (CModule module)) { - AUTOHOOK_DISPATCH() + o_pReadFileFromVPK = module.Offset(0x5CBA0).RCast(); + HookAttach(&(PVOID&)o_pReadFileFromVPK, (PVOID)h_ReadFileFromVPK); + + o_pCBaseFileSystem__OpenEx = module.Offset(0x15F50).RCast(); + HookAttach(&(PVOID&)o_pCBaseFileSystem__OpenEx, (PVOID)h_CBaseFileSystem__OpenEx); - g_pFilesystem = new SourceInterface("filesystem_stdio.dll", "VFileSystem017"); + g_pFilesystem = Sys_GetFactoryPtr("filesystem_stdio.dll", "VFileSystem017").RCast(); - AddSearchPathHook.Dispatch((LPVOID)(*g_pFilesystem)->m_vtable->AddSearchPath); - ReadFromCacheHook.Dispatch((LPVOID)(*g_pFilesystem)->m_vtable->ReadFromCache); - MountVPKHook.Dispatch((LPVOID)(*g_pFilesystem)->m_vtable->MountVPK); + o_pAddSearchPath = reinterpret_cast(g_pFilesystem->m_vtable->AddSearchPath); + HookAttach(&(PVOID&)o_pAddSearchPath, (PVOID)h_AddSearchPath); + o_pReadFromCache = reinterpret_cast(g_pFilesystem->m_vtable->ReadFromCache); + HookAttach(&(PVOID&)o_pReadFromCache, (PVOID)h_ReadFromCache); + o_pMountVPK = reinterpret_cast(g_pFilesystem->m_vtable->MountVPK); + HookAttach(&(PVOID&)o_pMountVPK, (PVOID)h_MountVPK); } diff --git a/primedev/core/filesystem/filesystem.h b/primedev/core/filesystem/filesystem.h index 4e2c18d9b..2c592b3f6 100644 --- a/primedev/core/filesystem/filesystem.h +++ b/primedev/core/filesystem/filesystem.h @@ -1,5 +1,4 @@ #pragma once -#include "core/sourceinterface.h" // taken from ttf2sdk typedef void* FileHandle_t; @@ -48,7 +47,7 @@ class IFileSystem VTable2* m_vtable2; }; -extern SourceInterface* g_pFilesystem; +extern IFileSystem* g_pFilesystem; std::string ReadVPKFile(const char* path); std::string ReadVPKOriginalFile(const char* path); diff --git a/primedev/core/filesystem/rpakfilesystem.cpp b/primedev/core/filesystem/rpakfilesystem.cpp index da72646b9..ebb9085a7 100644 --- a/primedev/core/filesystem/rpakfilesystem.cpp +++ b/primedev/core/filesystem/rpakfilesystem.cpp @@ -2,206 +2,447 @@ #include "mods/modmanager.h" #include "dedicated/dedicated.h" #include "core/tier0.h" +#include "util/utils.h" -AUTOHOOK_INIT() - -// there are more i'm just too lazy to add +#pragma pack(push, 1) struct PakLoadFuncs { - void* unk0[3]; - int (*LoadPakAsync)(const char* pPath, void* unknownSingleton, int flags, void* callback0, void* callback1); - void* unk1[2]; - void* (*UnloadPak)(int iPakHandle, void* callback); - void* unk2[6]; - void* (*LoadFile)(const char* path); // unsure - void* unk3[10]; - void* (*ReadFileAsync)(const char* pPath, void* a2); + void (*InitRpakSystem)(); + void (*AddAssetLoaderWithJobDetails)(/*assetTypeHeader*/ void*, uint32_t, int); + void (*AddAssetLoader)(/*assetTypeHeader*/ void*); + PakHandle (*LoadRpakFileAsync)(const char* pPath, void* allocator, int flags); + void (*LoadRpakFile)(const char*, __int64(__fastcall*)(), __int64, void(__cdecl*)()); + __int64 qword28; + void (*UnloadPak)(PakHandle iPakHandle, void* callback); + __int64 qword38; + __int64 qword40; + __int64 qword48; + __int64 qword50; + FARPROC (*GetDllCallback)(__int16 a1, const CHAR* a2); + __int64 (*GetAssetByHash)(__int64 hash); + __int64 (*GetAssetByName)(const char* a1); + __int64 qword70; + __int64 qword78; + __int64 qword80; + __int64 qword88; + __int64 qword90; + __int64 qword98; + __int64 qwordA0; + __int64 qwordA8; + __int64 qwordB0; + __int64 qwordBB; + void* (*OpenFile)(const char* pPath); + __int64 CloseFile; + __int64 qwordD0; + __int64 FileReadAsync; + __int64 ComplexFileReadAsync; + __int64 GetReadJobState; + __int64 WaitForFileReadJobComplete; + __int64 CancelFileReadJob; + __int64 CancelFileReadJobAsync; + __int64 qword108; }; +static_assert(sizeof(PakLoadFuncs) == 0x110); +#pragma pack(pop) PakLoadFuncs* g_pakLoadApi; - PakLoadManager* g_pPakLoadManager; -void** pUnknownPakLoadSingleton; -int PakLoadManager::LoadPakAsync(const char* pPath, const ePakLoadSource nLoadSource) +static char* pszCurrentMapRpakPath = nullptr; +static PakHandle* piCurrentMapRpakHandle = nullptr; +static PakHandle* piCurrentMapPatchRpakHandle = nullptr; +static /*CModelLoader*/ void** ppModelLoader = nullptr; +static void** rpakMemoryAllocator = nullptr; + +static __int64 (*o_pLoadGametypeSpecificRpaks)(const char* levelName) = nullptr; +static __int64 (**o_pCleanMaterialSystemStuff)() = nullptr; +static __int64 (**o_pCModelLoader_UnreferenceAllModels)(/*CModelLoader*/ void* a1) = nullptr; +static char* (*o_pLoadlevelLoadscreen)(const char* levelName) = nullptr; +static unsigned int (*o_pGetPakPatchNumber)(const char* pPakPath) = nullptr; + +// Marks all mod Paks to be unloaded on next map load. +// Also cleans up any mod Paks that are already unloaded. +void PakLoadManager::UnloadAllModPaks() { - int nHandle = g_pakLoadApi->LoadPakAsync(pPath, *pUnknownPakLoadSingleton, 2, nullptr, nullptr); - - // set the load source of the pak we just loaded - if (nHandle != -1) - GetPakInfo(nHandle)->m_nLoadSource = nLoadSource; - - return nHandle; + NS::log::rpak->info("Reloading RPaks on next map load..."); + for (auto& modPak : m_modPaks) + { + modPak.m_markedForDelete = true; + } + // clean up any paks that are both marked for unload and already unloaded + CleanUpUnloadedPaks(); + SetForceReloadOnMapLoad(true); } -void PakLoadManager::UnloadPak(const int nPakHandle) +// Tracks all Paks related to a mod. +void PakLoadManager::TrackModPaks(Mod& mod) { - g_pakLoadApi->UnloadPak(nPakHandle, nullptr); + const fs::path modPakPath("./" / mod.m_ModDirectory / "paks"); + + for (auto& modRpakEntry : mod.Rpaks) + { + ModPak_t pak; + pak.m_modName = mod.Name; + pak.m_path = (modPakPath / modRpakEntry.m_pakName).string(); + pak.m_pathHash = STR_HASH(pak.m_path); + + pak.m_preload = modRpakEntry.m_preload; + pak.m_dependentPakHash = modRpakEntry.m_dependentPakHash; + pak.m_mapRegex = modRpakEntry.m_loadRegex; + + m_modPaks.push_back(pak); + } } -void PakLoadManager::UnloadMapPaks() +// Untracks all paks that aren't currently loaded and are marked for unload. +void PakLoadManager::CleanUpUnloadedPaks() { - for (auto& pair : m_vLoadedPaks) - if (pair.second.m_nLoadSource == ePakLoadSource::MAP) - UnloadPak(pair.first); + auto fnRemovePredicate = [](ModPak_t& pak) -> bool { return pak.m_markedForDelete && pak.m_handle == PakHandle::INVALID; }; + + m_modPaks.erase(std::remove_if(m_modPaks.begin(), m_modPaks.end(), fnRemovePredicate), m_modPaks.end()); } -LoadedPak* PakLoadManager::TrackLoadedPak(ePakLoadSource nLoadSource, int nPakHandle, size_t nPakNameHash) +// Unloads all paks that are marked for unload. +void PakLoadManager::UnloadMarkedPaks() { - LoadedPak pak; - pak.m_nLoadSource = nLoadSource; - pak.m_nPakHandle = nPakHandle; - pak.m_nPakNameHash = nPakNameHash; + ++m_reentranceCounter; + const ScopeGuard guard([&]() { --m_reentranceCounter; }); - m_vLoadedPaks.insert(std::make_pair(nPakHandle, pak)); - return &m_vLoadedPaks.at(nPakHandle); + (*o_pCModelLoader_UnreferenceAllModels)(*ppModelLoader); + (*o_pCleanMaterialSystemStuff)(); + + for (auto& modPak : m_modPaks) + { + if (modPak.m_handle == PakHandle::INVALID || !modPak.m_markedForDelete) + continue; + + g_pakLoadApi->UnloadPak(modPak.m_handle, *o_pCleanMaterialSystemStuff); + modPak.m_handle = PakHandle::INVALID; + } } -void PakLoadManager::RemoveLoadedPak(int nPakHandle) +// Loads all modded paks for the given map. +void PakLoadManager::LoadModPaksForMap(const char* mapName) { - m_vLoadedPaks.erase(nPakHandle); + ++m_reentranceCounter; + const ScopeGuard guard([&]() { --m_reentranceCounter; }); + + for (auto& modPak : m_modPaks) + { + // don't load paks that are already loaded + if (modPak.m_handle != PakHandle::INVALID) + continue; + std::cmatch matches; + if (!std::regex_match(mapName, matches, modPak.m_mapRegex)) + continue; + + modPak.m_handle = g_pakLoadApi->LoadRpakFileAsync(modPak.m_path.c_str(), *rpakMemoryAllocator, 7); + m_mapPaks.push_back(modPak.m_pathHash); + } } -LoadedPak* PakLoadManager::GetPakInfo(const int nPakHandle) +// Unloads all modded map paks. +void PakLoadManager::UnloadModPaks() { - return &m_vLoadedPaks.at(nPakHandle); + ++m_reentranceCounter; + const ScopeGuard guard([&]() { --m_reentranceCounter; }); + + (*o_pCModelLoader_UnreferenceAllModels)(*ppModelLoader); + (*o_pCleanMaterialSystemStuff)(); + + for (auto& modPak : m_modPaks) + { + for (auto it = m_mapPaks.begin(); it != m_mapPaks.end(); ++it) + { + if (*it != modPak.m_pathHash) + continue; + + m_mapPaks.erase(it, it + 1); + g_pakLoadApi->UnloadPak(modPak.m_handle, *o_pCleanMaterialSystemStuff); + modPak.m_handle = PakHandle::INVALID; + break; + } + } + + // If this has happened, we may have leaked a pak? + // It basically means that none of the entries in m_modPaks matched the hash in m_mapPaks so we didn't end up unloading it + assert_msg(m_mapPaks.size() == 0, "Not all map paks were unloaded?"); } -int PakLoadManager::GetPakHandle(const size_t nPakNameHash) +// Called after a Pak was loaded. +void PakLoadManager::OnPakLoaded(std::string& originalPath, std::string& resultingPath, PakHandle resultingHandle) { - for (auto& pair : m_vLoadedPaks) - if (pair.second.m_nPakNameHash == nPakNameHash) - return pair.first; + if (IsVanillaCall()) + { + // add entry to loaded vanilla rpaks + m_vanillaPaks.emplace_back(originalPath, resultingHandle); + } - return -1; + LoadDependentPaks(resultingPath, resultingHandle); } -int PakLoadManager::GetPakHandle(const char* pPath) +// Called before a Pak was unloaded. +void PakLoadManager::OnPakUnloading(PakHandle handle) { - return GetPakHandle(STR_HASH(pPath)); + UnloadDependentPaks(handle); + + if (IsVanillaCall()) + { + // remove entry from loaded vanilla rpaks + auto fnRemovePredicate = [handle](std::pair& pair) -> bool { return pair.second == handle; }; + + m_vanillaPaks.erase(std::remove_if(m_vanillaPaks.begin(), m_vanillaPaks.end(), fnRemovePredicate), m_vanillaPaks.end()); + + // no need to handle aliasing here, if vanilla wants it gone, it's gone + } + else + { + // note: aliasing is handled the old way, long term todo: move it over to the PakLoadManager + // handle the potential unloading of an aliased vanilla rpak (we aliased it, and we are now unloading the alias, so we should load + // the vanilla one again) + // for (auto& [path, resultingHandle] : m_vanillaPaks) + //{ + // if (resultingHandle != handle) + // continue; + + // // load vanilla rpak + //} + } + + // set handle of the mod pak (if any) that has this handle for proper tracking + for (auto& modPak : m_modPaks) + { + if (modPak.m_handle == handle) + modPak.m_handle = PakHandle::INVALID; + } } -void* PakLoadManager::LoadFile(const char* path) +// Whether the vanilla game has this rpak +static bool VanillaHasPak(const char* pakName) { - return g_pakLoadApi->LoadFile(path); + fs::path originalPath = fs::path("./r2/paks/Win64") / pakName; + unsigned int highestPatch = o_pGetPakPatchNumber(pakName); + if (highestPatch) + { + // add the patch path to the extension + char buf[16]; + snprintf(buf, sizeof(buf), "(%02u).rpak", highestPatch); + // remove the .rpak and add the new suffix + originalPath = originalPath.replace_extension().string() + buf; + } + else + { + originalPath /= pakName; + } + + return fs::exists(originalPath); } -void HandlePakAliases(char** map) +// If vanilla doesn't have an rpak for this path, tries to map it to a modded rpak of the same name. +void PakLoadManager::FixupPakPath(std::string& pakPath) { - // convert the pak being loaded to it's aliased one, e.g. aliasing mp_hub_timeshift => sp_hub_timeshift - for (int64_t i = g_pModManager->m_LoadedMods.size() - 1; i > -1; i--) + if (VanillaHasPak(pakPath.c_str())) + return; + + for (ModPak_t& modPak : m_modPaks) { - Mod* mod = &g_pModManager->m_LoadedMods[i]; - if (!mod->m_bEnabled) + if (modPak.m_markedForDelete) continue; - if (mod->RpakAliases.find(*map) != mod->RpakAliases.end()) + fs::path modPakFilename = fs::path(modPak.m_path).filename(); + if (pakPath == modPakFilename.string()) { - *map = &mod->RpakAliases[*map][0]; + pakPath = modPak.m_path; return; } } } -void LoadPreloadPaks() +// Loads all "Preload" Paks. todo: deprecate Preload. +void PakLoadManager::LoadPreloadPaks() { - // note, loading from ./ is necessary otherwise paks will load from gamedir/r2/paks - for (Mod& mod : g_pModManager->m_LoadedMods) + ++m_reentranceCounter; + const ScopeGuard guard([&]() { --m_reentranceCounter; }); + + for (auto& modPak : m_modPaks) { - if (!mod.m_bEnabled) + if (modPak.m_markedForDelete || modPak.m_handle != PakHandle::INVALID || !modPak.m_preload) continue; - // need to get a relative path of mod to mod folder - fs::path modPakPath("./" / mod.m_ModDirectory / "paks"); + modPak.m_handle = g_pakLoadApi->LoadRpakFileAsync(modPak.m_path.c_str(), *rpakMemoryAllocator, 7); + } +} - for (ModRpakEntry& pak : mod.Rpaks) - if (pak.m_bAutoLoad) - g_pPakLoadManager->LoadPakAsync((modPakPath / pak.m_sPakName).string().c_str(), ePakLoadSource::CONSTANT); +// Causes all "Postload" paks to be loaded again. +void PakLoadManager::ReloadPostloadPaks() +{ + ++m_reentranceCounter; + const ScopeGuard guard([&]() { --m_reentranceCounter; }); + + // pretend that we just loaded all of these vanilla paks + for (auto& [path, handle] : m_vanillaPaks) + { + LoadDependentPaks(path, handle); } } -void LoadPostloadPaks(const char* pPath) +// Wrapper for Pak load API. +void* PakLoadManager::OpenFile(const char* path) { - // note, loading from ./ is necessary otherwise paks will load from gamedir/r2/paks - for (Mod& mod : g_pModManager->m_LoadedMods) + return g_pakLoadApi->OpenFile(path); +} + +// Loads Paks that depend on this Pak. +void PakLoadManager::LoadDependentPaks(std::string& path, PakHandle handle) +{ + ++m_reentranceCounter; + const ScopeGuard guard([&]() { --m_reentranceCounter; }); + + const size_t hash = STR_HASH(path); + for (auto& modPak : m_modPaks) { - if (!mod.m_bEnabled) + if (modPak.m_handle != PakHandle::INVALID) + continue; + if (modPak.m_dependentPakHash != hash) continue; - // need to get a relative path of mod to mod folder - fs::path modPakPath("./" / mod.m_ModDirectory / "paks"); - - for (ModRpakEntry& pak : mod.Rpaks) - if (pak.m_sLoadAfterPak == pPath) - g_pPakLoadManager->LoadPakAsync((modPakPath / pak.m_sPakName).string().c_str(), ePakLoadSource::CONSTANT); + // load pak + modPak.m_handle = g_pakLoadApi->LoadRpakFileAsync(modPak.m_path.c_str(), *rpakMemoryAllocator, 7); + m_dependentPaks.emplace_back(handle, hash); } } -void LoadCustomMapPaks(char** pakName, bool* bNeedToFreePakName) +// Unloads Paks that depend on this Pak. +void PakLoadManager::UnloadDependentPaks(PakHandle handle) { - // whether the vanilla game has this rpak - bool bHasOriginalPak = fs::exists(fs::path("r2/paks/Win64/") / *pakName); + ++m_reentranceCounter; + const ScopeGuard guard([&]() { --m_reentranceCounter; }); - // note, loading from ./ is necessary otherwise paks will load from gamedir/r2/paks - for (Mod& mod : g_pModManager->m_LoadedMods) + auto fnRemovePredicate = [&](std::pair& pair) -> bool { - if (!mod.m_bEnabled) - continue; + if (pair.first != handle) + return false; - // need to get a relative path of mod to mod folder - fs::path modPakPath("./" / mod.m_ModDirectory / "paks"); + for (auto& modPak : m_modPaks) + { + if (modPak.m_pathHash != pair.second || modPak.m_handle == PakHandle::INVALID) + continue; - for (ModRpakEntry& pak : mod.Rpaks) + // unload pak + g_pakLoadApi->UnloadPak(modPak.m_handle, *o_pCleanMaterialSystemStuff); + modPak.m_handle = PakHandle::INVALID; + } + + return true; + }; + m_dependentPaks.erase(std::remove_if(m_dependentPaks.begin(), m_dependentPaks.end(), fnRemovePredicate), m_dependentPaks.end()); +} + +// Handles aliases for rpaks defined in rpak.json, effectively redirecting an rpak load to a different path. +static void HandlePakAliases(std::string& originalPath) +{ + // convert the pak being loaded to its aliased one, e.g. aliasing mp_hub_timeshift => sp_hub_timeshift + for (int64_t i = g_pModManager->m_LoadedMods.size() - 1; i > PakHandle::INVALID; i--) + { + Mod* mod = &g_pModManager->m_LoadedMods[i]; + if (!mod->m_bEnabled) + continue; + + if (mod->RpakAliases.find(originalPath) != mod->RpakAliases.end()) { - if (!pak.m_bAutoLoad && !pak.m_sPakName.compare(*pakName)) - { - // if the game doesn't have the original pak, let it handle loading this one as if it was the one it was loading originally - if (!bHasOriginalPak) - { - std::string path = (modPakPath / pak.m_sPakName).string(); - *pakName = new char[path.size() + 1]; - strcpy(*pakName, &path[0]); - (*pakName)[path.size()] = '\0'; - - bHasOriginalPak = true; - *bNeedToFreePakName = - true; // we can't free this memory until we're done with the pak, so let whatever's calling this deal with it - } - else - g_pPakLoadManager->LoadPakAsync((modPakPath / pak.m_sPakName).string().c_str(), ePakLoadSource::MAP); - } + originalPath = (mod->m_ModDirectory / "paks" / mod->RpakAliases[originalPath]).string(); + return; } } } -// clang-format off -HOOK(LoadPakAsyncHook, LoadPakAsync, -int, __fastcall, (char* pPath, void* unknownSingleton, int flags, void* pCallback0, void* pCallback1)) -// clang-format on +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +static bool (*o_pLoadMapRpaks)(char* mapPath) = nullptr; +static bool h_LoadMapRpaks(char* mapPath) { - HandlePakAliases(&pPath); + // unload all mod rpaks that are marked for unload + g_pPakLoadManager->UnloadMarkedPaks(); + g_pPakLoadManager->CleanUpUnloadedPaks(); - // dont load the pak if it's currently loaded already - size_t nPathHash = STR_HASH(pPath); - if (g_pPakLoadManager->GetPakHandle(nPathHash) != -1) - return -1; + // strip file extension + const std::string mapName = fs::path(mapPath).replace_extension().string(); - bool bNeedToFreePakName = false; + // load mp_common, sp_common etc. + o_pLoadGametypeSpecificRpaks(mapName.c_str()); - static bool bShouldLoadPaks = true; - if (bShouldLoadPaks) + // unload old modded map paks + g_pPakLoadManager->UnloadModPaks(); + // load modded map paks + g_pPakLoadManager->LoadModPaksForMap(mapName.c_str()); + + // don't load/unload anything when going to the lobby, presumably to save load times when going back to the same map + if (!g_pPakLoadManager->GetForceReloadOnMapLoad() && !strcmp("mp_lobby", mapName.c_str())) + return false; + + if (g_pPakLoadManager->GetForceReloadOnMapLoad()) { - // make a copy of the path for comparing to determine whether we should load this pak on dedi, before it could get overwritten by - // LoadCustomMapPaks - std::string originalPath(pPath); + g_pPakLoadManager->LoadPreloadPaks(); + g_pPakLoadManager->ReloadPostloadPaks(); + } - // disable preloading while we're doing this - bShouldLoadPaks = false; + char mapRpakStr[272]; + snprintf(mapRpakStr, 272, "%s.rpak", mapName.c_str()); - LoadPreloadPaks(); - LoadCustomMapPaks(&pPath, &bNeedToFreePakName); + // if level being loaded is the same as current level, do nothing + if (!g_pPakLoadManager->GetForceReloadOnMapLoad() && !strcmp(mapRpakStr, pszCurrentMapRpakPath)) + return true; - bShouldLoadPaks = true; + strcpy(pszCurrentMapRpakPath, mapRpakStr); + + (*o_pCleanMaterialSystemStuff)(); + o_pLoadlevelLoadscreen(mapName.c_str()); + + // unload old map rpaks + PakHandle curHandle = *piCurrentMapRpakHandle; + PakHandle curPatchHandle = *piCurrentMapPatchRpakHandle; + if (curHandle != PakHandle::INVALID) + { + (*o_pCModelLoader_UnreferenceAllModels)(*ppModelLoader); + (*o_pCleanMaterialSystemStuff)(); + g_pakLoadApi->UnloadPak(curHandle, *o_pCleanMaterialSystemStuff); + *piCurrentMapRpakHandle = PakHandle::INVALID; + } + if (curPatchHandle != PakHandle::INVALID) + { + (*o_pCModelLoader_UnreferenceAllModels)(*ppModelLoader); + (*o_pCleanMaterialSystemStuff)(); + g_pakLoadApi->UnloadPak(curPatchHandle, *o_pCleanMaterialSystemStuff); + *piCurrentMapPatchRpakHandle = PakHandle::INVALID; + } + + *piCurrentMapRpakHandle = g_pakLoadApi->LoadRpakFileAsync(mapRpakStr, *rpakMemoryAllocator, 7); + + // load special _patch rpak (seemingly used for dev things?) + char levelPatchRpakStr[272]; + snprintf(levelPatchRpakStr, 272, "%s_patch.rpak", mapName.c_str()); + *piCurrentMapPatchRpakHandle = g_pakLoadApi->LoadRpakFileAsync(levelPatchRpakStr, *rpakMemoryAllocator, 7); + + // we just reloaded the paks, so we don't need to force it again + g_pPakLoadManager->SetForceReloadOnMapLoad(false); + return true; +} + +// clang-format off +HOOK(LoadPakAsyncHook, LoadPakAsync, +PakHandle, __fastcall, (const char* pPath, void* memoryAllocator, int flags)) +// clang-format on +{ + // make a copy of the path for comparing to determine whether we should load this pak on dedi, before it could get overwritten + std::string svOriginalPath(pPath); + + std::string resultingPath(pPath); + HandlePakAliases(resultingPath); + + if (g_pPakLoadManager->IsVanillaCall()) + { + g_pPakLoadManager->LoadPreloadPaks(); + g_pPakLoadManager->FixupPakPath(resultingPath); // do this after custom paks load and in bShouldLoadPaks so we only ever call this on the root pakload call // todo: could probably add some way to flag custom paks to not be loaded on dedicated servers in rpak.json @@ -210,44 +451,27 @@ int, __fastcall, (char* pPath, void* unknownSingleton, int flags, void* pCallbac // sp_ rpaks contain tutorial ghost data // sucks to have to load the entire rpak for that but sp was never meant to be done on dedi if (IsDedicatedServer() && - (CommandLine()->CheckParm("-nopakdedi") || strncmp(&originalPath[0], "common", 6) && strncmp(&originalPath[0], "sp_", 3))) + (CommandLine()->CheckParm("-nopakdedi") || strncmp(&svOriginalPath[0], "common", 6) && strncmp(&svOriginalPath[0], "sp_", 3))) { - if (bNeedToFreePakName) - delete[] pPath; - - NS::log::rpak->info("Not loading pak {} for dedicated server", originalPath); - return -1; + NS::log::rpak->info("Not loading pak {} for dedicated server", svOriginalPath); + return PakHandle::INVALID; } } - int iPakHandle = LoadPakAsync(pPath, unknownSingleton, flags, pCallback0, pCallback1); - NS::log::rpak->info("LoadPakAsync {} {}", pPath, iPakHandle); - - // trak the pak - g_pPakLoadManager->TrackLoadedPak(ePakLoadSource::UNTRACKED, iPakHandle, nPathHash); - LoadPostloadPaks(pPath); + PakHandle iPakHandle = LoadPakAsync(resultingPath.c_str(), memoryAllocator, flags); + NS::log::rpak->info("LoadPakAsync {} {}", resultingPath, iPakHandle); - if (bNeedToFreePakName) - delete[] pPath; + g_pPakLoadManager->OnPakLoaded(svOriginalPath, resultingPath, iPakHandle); return iPakHandle; } // clang-format off HOOK(UnloadPakHook, UnloadPak, -void*, __fastcall, (int nPakHandle, void* pCallback)) +void*, __fastcall, (PakHandle nPakHandle, void* pCallback)) // clang-format on { - // stop tracking the pak - g_pPakLoadManager->RemoveLoadedPak(nPakHandle); - - static bool bShouldUnloadPaks = true; - if (bShouldUnloadPaks) - { - bShouldUnloadPaks = false; - g_pPakLoadManager->UnloadMapPaks(); - bShouldUnloadPaks = true; - } + g_pPakLoadManager->OnPakUnloading(nPakHandle); NS::log::rpak->info("UnloadPak {}", nPakHandle); return UnloadPak(nPakHandle, pCallback); @@ -256,7 +480,7 @@ void*, __fastcall, (int nPakHandle, void* pCallback)) // we hook this exclusively for resolving stbsp paths, but seemingly it's also used for other stuff like vpk, rpak, mprj and starpak loads // tbh this actually might be for memory mapped files or something, would make sense i think // clang-format off -HOOK(ReadFileAsyncHook, ReadFileAsync, +HOOK(OpenFileHook, o_pOpenFile, void*, __fastcall, (const char* pPath, void* pCallback)) // clang-format on { @@ -329,19 +553,34 @@ void*, __fastcall, (const char* pPath, void* pCallback)) NS::log::rpak->info("LoadStreamPak: {}", filename.string()); } - return ReadFileAsync(pPath, pCallback); + return o_pOpenFile(pPath, pCallback); } ON_DLL_LOAD("engine.dll", RpakFilesystem, (CModule module)) { - AUTOHOOK_DISPATCH(); - g_pPakLoadManager = new PakLoadManager; g_pakLoadApi = module.Offset(0x5BED78).Deref().RCast(); - pUnknownPakLoadSingleton = module.Offset(0x7C5E20).RCast(); - LoadPakAsyncHook.Dispatch((LPVOID*)g_pakLoadApi->LoadPakAsync); + LoadPakAsyncHook.Dispatch((LPVOID*)g_pakLoadApi->LoadRpakFileAsync); UnloadPakHook.Dispatch((LPVOID*)g_pakLoadApi->UnloadPak); - ReadFileAsyncHook.Dispatch((LPVOID*)g_pakLoadApi->ReadFileAsync); + OpenFileHook.Dispatch((LPVOID*)g_pakLoadApi->OpenFile); + + pszCurrentMapRpakPath = module.Offset(0x1315C3E0).RCast(); + piCurrentMapRpakHandle = module.Offset(0x7CB5A0).RCast(); + piCurrentMapPatchRpakHandle = module.Offset(0x7CB5A4).RCast(); + ppModelLoader = module.Offset(0x7C4AC0).RCast(); + rpakMemoryAllocator = module.Offset(0x7C5E20).RCast(); + + o_pLoadGametypeSpecificRpaks = module.Offset(0x15AD20).RCast(); + o_pCleanMaterialSystemStuff = module.Offset(0x12A11F00).RCast(); + o_pCModelLoader_UnreferenceAllModels = module.Offset(0x5ED580).RCast(); + o_pLoadlevelLoadscreen = module.Offset(0x15A810).RCast(); + + o_pLoadMapRpaks = module.Offset(0x15A8C0).RCast(); + HookAttach(&(PVOID&)o_pLoadMapRpaks, (PVOID)h_LoadMapRpaks); + + // kinda bad, doing things in rtech in an engine callback but it seems fine for now + CModule rtechModule(GetModuleHandleA("rtech_game.dll")); + o_pGetPakPatchNumber = rtechModule.Offset(0x9A00).RCast(); } diff --git a/primedev/core/filesystem/rpakfilesystem.h b/primedev/core/filesystem/rpakfilesystem.h index bcd57a731..87a41e7b1 100644 --- a/primedev/core/filesystem/rpakfilesystem.h +++ b/primedev/core/filesystem/rpakfilesystem.h @@ -1,39 +1,81 @@ #pragma once -enum class ePakLoadSource -{ - UNTRACKED = -1, // not a pak we loaded, we shouldn't touch this one +#include - CONSTANT, // should be loaded at all times - MAP // loaded from a map, should be unloaded when the map is unloaded +enum PakHandle : int +{ + INVALID = -1, }; -struct LoadedPak +struct ModPak_t { - ePakLoadSource m_nLoadSource; - int m_nPakHandle; - size_t m_nPakNameHash; + std::string m_modName; + + std::string m_path; + size_t m_pathHash = 0; + + // If the map being loaded matches this regex, this pak will be loaded. + std::regex m_mapRegex; + // If a pak with a hash matching this is loaded, this pak will be loaded. + size_t m_dependentPakHash = 0; + // If this is set, this pak will be loaded whenever any other pak is loaded. + bool m_preload = false; + + // If this is set, the Pak will be unloaded on next map load + bool m_markedForDelete = false; + // The current rpak handle associated with this Pak + PakHandle m_handle = PakHandle::INVALID; }; class PakLoadManager { -private: - std::map m_vLoadedPaks {}; - std::unordered_map m_HashToPakHandle {}; - public: - int LoadPakAsync(const char* pPath, const ePakLoadSource nLoadSource); - void UnloadPak(const int nPakHandle); - void UnloadMapPaks(); - void* LoadFile(const char* path); // this is a guess + void UnloadAllModPaks(); + void TrackModPaks(Mod& mod); + + void CleanUpUnloadedPaks(); + void UnloadMarkedPaks(); + + void LoadModPaksForMap(const char* mapName); + void UnloadModPaks(); + + // Whether the current context is a vanilla call to a function, or a modded one + bool IsVanillaCall() const { return m_reentranceCounter == 0; } + // Whether paks will be forced to reload on the next map load + bool GetForceReloadOnMapLoad() const { return m_forceReloadOnMapLoad; } + void SetForceReloadOnMapLoad(bool value) { m_forceReloadOnMapLoad = value; } + + void OnPakLoaded(std::string& originalPath, std::string& resultingPath, PakHandle resultingHandle); + void OnPakUnloading(PakHandle handle); + + void FixupPakPath(std::string& path); + + void LoadPreloadPaks(); + void ReloadPostloadPaks(); + + void* OpenFile(const char* path); + +private: + void LoadDependentPaks(std::string& path, PakHandle handle); + void UnloadDependentPaks(PakHandle handle); - LoadedPak* TrackLoadedPak(ePakLoadSource nLoadSource, int nPakHandle, size_t nPakNameHash); - void RemoveLoadedPak(int nPakHandle); + // All paks that vanilla has attempted to load. (they may have been aliased away) + // Also known as a list of rpaks that the vanilla game would have loaded at this point in time. + std::vector> m_vanillaPaks; - LoadedPak* GetPakInfo(const int nPakHandle); + // All mod Paks that are currently tracked + std::vector m_modPaks; + // Hashes of the currently loaded map mod paks + std::vector m_mapPaks; + // Currently loaded Pak path hashes that depend on a handle to remain loaded (Postload) + std::vector> m_dependentPaks; - int GetPakHandle(const size_t nPakNameHash); - int GetPakHandle(const char* pPath); + // Used to force rpaks to be unloaded and reloaded on the next map load. + // Vanilla behaviour is to not do this when loading into mp_lobby, or loading into the same map you were last in. + bool m_forceReloadOnMapLoad = false; + // Used to track if the current hook call is a vanilla call or not. + // When loading/unloading a mod Pak, increment this before doing so, and decrement afterwards. + int m_reentranceCounter = 0; }; extern PakLoadManager* g_pPakLoadManager; diff --git a/primedev/core/hooks.cpp b/primedev/core/hooks.cpp index fef8bbcf1..6146c93c6 100644 --- a/primedev/core/hooks.cpp +++ b/primedev/core/hooks.cpp @@ -10,12 +10,8 @@ #include #include -#define XINPUT1_3_DLL "XInput1_3.dll" - namespace fs = std::filesystem; -AUTOHOOK_INIT() - // called from the ON_DLL_LOAD macros __dllLoadCallback::__dllLoadCallback( eDllLoadCallbackSide side, const std::string dllName, DllLoadCallbackFuncType callback, std::string uniqueStr, std::string reliesOn) @@ -87,18 +83,18 @@ void __fileAutohook::DispatchForModule(const char* pModuleName) hook->Dispatch(); } -ManualHook::ManualHook(const char* funcName, LPVOID func) : pHookFunc(func), ppOrigFunc(nullptr) +ManualHook::ManualHook(const char* funcName, LPVOID func) + : svFuncName(funcName) + , pHookFunc(func) + , ppOrigFunc(nullptr) { - const size_t iFuncNameStrlen = strlen(funcName); - pFuncName = new char[iFuncNameStrlen]; - memcpy(pFuncName, funcName, iFuncNameStrlen); } -ManualHook::ManualHook(const char* funcName, LPVOID* orig, LPVOID func) : pHookFunc(func), ppOrigFunc(orig) +ManualHook::ManualHook(const char* funcName, LPVOID* orig, LPVOID func) + : svFuncName(funcName) + , pHookFunc(func) + , ppOrigFunc(orig) { - const size_t iFuncNameStrlen = strlen(funcName); - pFuncName = new char[iFuncNameStrlen]; - memcpy(pFuncName, funcName, iFuncNameStrlen); } bool ManualHook::Dispatch(LPVOID addr, LPVOID* orig) @@ -107,19 +103,19 @@ bool ManualHook::Dispatch(LPVOID addr, LPVOID* orig) ppOrigFunc = orig; if (!addr) - spdlog::error("Address for hook {} is invalid", pFuncName); + spdlog::error("Address for hook {} is invalid", svFuncName); else if (MH_CreateHook(addr, pHookFunc, ppOrigFunc) == MH_OK) { if (MH_EnableHook(addr) == MH_OK) { - spdlog::info("Enabling hook {}", pFuncName); + spdlog::info("Enabling hook {}", svFuncName); return true; } else - spdlog::error("MH_EnableHook failed for function {}", pFuncName); + spdlog::error("MH_EnableHook failed for function {}", svFuncName); } else - spdlog::error("MH_CreateHook failed for function {}", pFuncName); + spdlog::error("MH_CreateHook failed for function {}", svFuncName); return false; } @@ -225,14 +221,15 @@ void MakeHook(LPVOID pTarget, LPVOID pDetour, void* ppOriginal, const char* pFun spdlog::error("MH_CreateHook failed for function {}", pStrippedFuncName); } -AUTOHOOK_ABSOLUTEADDR(_GetCommandLineA, (LPVOID)GetCommandLineA, LPSTR, WINAPI, ()) +static LPSTR(WINAPI* o_pGetCommandLineA)() = nullptr; +static LPSTR WINAPI h_GetCommandLineA() { static char* cmdlineModified; static char* cmdlineOrg; if (cmdlineOrg == nullptr || cmdlineModified == nullptr) { - cmdlineOrg = _GetCommandLineA(); + cmdlineOrg = o_pGetCommandLineA(); bool isDedi = strstr(cmdlineOrg, "-dedicated"); // well, this one has to be a real argument bool ignoreStartupArgs = strstr(cmdlineOrg, "-nostartupargs"); @@ -330,8 +327,6 @@ void CallLoadLibraryACallbacks(LPCSTR lpLibFileName, HMODULE moduleAddress) void CallLoadLibraryWCallbacks(LPCWSTR lpLibFileName, HMODULE moduleAddress) { - CModule cModule(moduleAddress); - while (true) { bool bDoneCalling = true; @@ -392,87 +387,13 @@ void CallAllPendingDLLLoadCallbacks() } } -// clang-format off -AUTOHOOK_ABSOLUTEADDR(_LoadLibraryExA, (LPVOID)LoadLibraryExA, -HMODULE, WINAPI, (LPCSTR lpLibFileName, HANDLE hFile, DWORD dwFlags)) -// clang-format on -{ - HMODULE moduleAddress; - - LPCSTR lpLibFileNameEnd = lpLibFileName + strlen(lpLibFileName); - LPCSTR lpLibName = lpLibFileNameEnd - strlen(XINPUT1_3_DLL); - - // replace xinput dll with one that has ASLR - if (lpLibFileName <= lpLibName && !strncmp(lpLibName, XINPUT1_3_DLL, strlen(XINPUT1_3_DLL) + 1)) - { - moduleAddress = _LoadLibraryExA("XInput9_1_0.dll", hFile, dwFlags); - - if (!moduleAddress) - { - MessageBoxA(0, "Could not find XInput9_1_0.dll", "Northstar", MB_ICONERROR); - exit(EXIT_FAILURE); - - return nullptr; - } - } - else - moduleAddress = _LoadLibraryExA(lpLibFileName, hFile, dwFlags); - - if (moduleAddress) - { - CallLoadLibraryACallbacks(lpLibFileName, moduleAddress); - g_pPluginManager->InformDllLoad(moduleAddress, fs::path(lpLibFileName)); - } - - return moduleAddress; -} - -// clang-format off -AUTOHOOK_ABSOLUTEADDR(_LoadLibraryA, (LPVOID)LoadLibraryA, -HMODULE, WINAPI, (LPCSTR lpLibFileName)) -// clang-format on -{ - HMODULE moduleAddress = _LoadLibraryA(lpLibFileName); - - if (moduleAddress) - CallLoadLibraryACallbacks(lpLibFileName, moduleAddress); - - return moduleAddress; -} - -// clang-format off -AUTOHOOK_ABSOLUTEADDR(_LoadLibraryExW, (LPVOID)LoadLibraryExW, -HMODULE, WINAPI, (LPCWSTR lpLibFileName, HANDLE hFile, DWORD dwFlags)) -// clang-format on -{ - HMODULE moduleAddress = _LoadLibraryExW(lpLibFileName, hFile, dwFlags); - - if (moduleAddress) - CallLoadLibraryWCallbacks(lpLibFileName, moduleAddress); - - return moduleAddress; -} - -// clang-format off -AUTOHOOK_ABSOLUTEADDR(_LoadLibraryW, (LPVOID)LoadLibraryW, -HMODULE, WINAPI, (LPCWSTR lpLibFileName)) -// clang-format on -{ - HMODULE moduleAddress = _LoadLibraryW(lpLibFileName); - - if (moduleAddress) - { - CallLoadLibraryWCallbacks(lpLibFileName, moduleAddress); - g_pPluginManager->InformDllLoad(moduleAddress, fs::path(lpLibFileName)); - } - - return moduleAddress; -} - -void InstallInitialHooks() +void HookSys_Init() { if (MH_Initialize() != MH_OK) + { spdlog::error("MH_Initialize (minhook initialization) failed"); + } - AUTOHOOK_DISPATCH() + o_pGetCommandLineA = GetCommandLineA; + HookAttach(&(PVOID&)o_pGetCommandLineA, (PVOID)h_GetCommandLineA); } diff --git a/primedev/core/hooks.h b/primedev/core/hooks.h index e5a653549..26d9483cd 100644 --- a/primedev/core/hooks.h +++ b/primedev/core/hooks.h @@ -3,7 +3,33 @@ #include #include -void InstallInitialHooks(); +//----------------------------------------------------------------------------- +// Purpose: Init minhook +//----------------------------------------------------------------------------- +void HookSys_Init(); + +//----------------------------------------------------------------------------- +// Purpose: MH_MakeHook wrapper +// Input : *ppOriginal - Original function being detoured +// pDetour - Detour function +//----------------------------------------------------------------------------- +inline void HookAttach(PVOID* ppOriginal, PVOID pDetour) +{ + PVOID pAddr = *ppOriginal; + if (MH_CreateHook(pAddr, pDetour, ppOriginal) == MH_OK) + { + if (MH_EnableHook(pAddr) != MH_OK) + { + spdlog::error("Failed enabling a function hook!"); + } + } + else + { + spdlog::error("Failed creating a function hook!"); + } +} + +void CallLoadLibraryACallbacks(LPCSTR lpLibFileName, HMODULE moduleAddress); typedef void (*DllLoadCallbackFuncType)(CModule moduleAddress); void AddDllLoadCallback(std::string dll, DllLoadCallbackFuncType callback, std::string tag = "", std::vector reliesOn = {}); @@ -114,7 +140,9 @@ class __autohook __autohook() = delete; __autohook(__fileAutohook* autohook, const char* funcName, LPVOID absoluteAddress, LPVOID* orig, LPVOID func) - : pHookFunc(func), ppOrigFunc(orig), iAbsoluteAddress(absoluteAddress) + : pHookFunc(func) + , ppOrigFunc(orig) + , iAbsoluteAddress(absoluteAddress) { iAddressResolutionMode = ABSOLUTE_ADDR; @@ -126,7 +154,8 @@ class __autohook } __autohook(__fileAutohook* autohook, const char* funcName, const char* addrString, LPVOID* orig, LPVOID func) - : pHookFunc(func), ppOrigFunc(orig) + : pHookFunc(func) + , ppOrigFunc(orig) { iAddressResolutionMode = OFFSET_STRING; @@ -142,7 +171,8 @@ class __autohook } __autohook(__fileAutohook* autohook, const char* funcName, const char* moduleName, const char* procName, LPVOID* orig, LPVOID func) - : pHookFunc(func), ppOrigFunc(orig) + : pHookFunc(func) + , ppOrigFunc(orig) { iAddressResolutionMode = PROCADDRESS; @@ -252,7 +282,7 @@ class __autohook class ManualHook { public: - char* pFuncName; + std::string svFuncName; LPVOID pHookFunc; LPVOID* ppOrigFunc; @@ -301,10 +331,7 @@ class __autovar pAutohook->vars.push_back(this); } - void Dispatch() - { - *m_pTarget = (void*)ParseDLLOffsetString(m_pAddrString); - } + void Dispatch() { *m_pTarget = (void*)ParseDLLOffsetString(m_pAddrString); } }; // VAR_AT(engine.dll+0x404, ConVar*, Cvar_host_timescale) diff --git a/primedev/core/math/bitbuf.h b/primedev/core/math/bitbuf.h index a06dab17f..33056e63e 100644 --- a/primedev/core/math/bitbuf.h +++ b/primedev/core/math/bitbuf.h @@ -90,25 +90,13 @@ enum EBitCoordType class BitBufferBase { protected: - INLINE void SetName(const char* name) - { - m_BufferName = name; - } + INLINE void SetName(const char* name) { m_BufferName = name; } public: - INLINE bool IsOverflowed() - { - return m_Overflow; - } - INLINE void SetOverflowed() - { - m_Overflow = true; - } + INLINE bool IsOverflowed() { return m_Overflow; } + INLINE void SetOverflowed() { m_Overflow = true; } - INLINE const char* GetName() - { - return m_BufferName; - } + INLINE const char* GetName() { return m_BufferName; } private: const char* m_BufferName = ""; @@ -437,28 +425,13 @@ class BFRead : public BitBufferBase return fReturn; } - INLINE i32 ReadChar() - { - return ReadSBitLong(sizeof(char) << 3); - } - INLINE u32 ReadByte() - { - return ReadUBitLong(sizeof(unsigned char) << 3); - } + INLINE i32 ReadChar() { return ReadSBitLong(sizeof(char) << 3); } + INLINE u32 ReadByte() { return ReadUBitLong(sizeof(unsigned char) << 3); } - INLINE i32 ReadShort() - { - return ReadSBitLong(sizeof(short) << 3); - } - INLINE u32 ReadWord() - { - return ReadUBitLong(sizeof(unsigned short) << 3); - } + INLINE i32 ReadShort() { return ReadSBitLong(sizeof(short) << 3); } + INLINE u32 ReadWord() { return ReadUBitLong(sizeof(unsigned short) << 3); } - INLINE i32 ReadLong() - { - return (i32)(ReadUBitLong(sizeof(i32) << 3)); - } + INLINE i32 ReadLong() { return (i32)(ReadUBitLong(sizeof(i32) << 3)); } INLINE float ReadFloat() { u32 temp = ReadUBitLong(sizeof(float) << 3); @@ -687,24 +660,12 @@ class BFRead : public BitBufferBase return std::min(nCurOfs + nAdjust, m_DataBits); } - INLINE bool SeekRelative(size_t offset) - { - return Seek(GetNumBitsRead() + offset); - } + INLINE bool SeekRelative(size_t offset) { return Seek(GetNumBitsRead() + offset); } - INLINE size_t TotalBytesAvailable() - { - return m_DataBytes; - } + INLINE size_t TotalBytesAvailable() { return m_DataBytes; } - INLINE size_t GetNumBitsLeft() - { - return m_DataBits - GetNumBitsRead(); - } - INLINE size_t GetNumBytesLeft() - { - return GetNumBitsLeft() >> 3; - } + INLINE size_t GetNumBitsLeft() { return m_DataBits - GetNumBitsRead(); } + INLINE size_t GetNumBytesLeft() { return GetNumBitsLeft() >> 3; } private: size_t m_DataBits; // 0x0010 @@ -743,10 +704,7 @@ class BFWrite : public BitBufferBase m_DataEnd = reinterpret_cast(reinterpret_cast(m_Data) + m_DataBytes); } - INLINE int GetNumBitsLeft() - { - return m_OutBitsLeft + (32 * (m_DataEnd - m_DataOut - 1)); - } + INLINE int GetNumBitsLeft() { return m_OutBitsLeft + (32 * (m_DataEnd - m_DataOut - 1)); } INLINE void Reset() { @@ -775,10 +733,7 @@ class BFWrite : public BitBufferBase return reinterpret_cast(m_Data); } - INLINE u8* GetData() - { - return GetBasePointer(); - } + INLINE u8* GetData() { return GetBasePointer(); } INLINE void Finish() { @@ -851,10 +806,7 @@ class BFWrite : public BitBufferBase } } - INLINE void WriteSBitLong(i32 data, i32 numBits) - { - WriteUBitLong((u32)data, numBits, false); - } + INLINE void WriteSBitLong(i32 data, i32 numBits) { WriteUBitLong((u32)data, numBits, false); } INLINE void WriteUBitVar(u32 n) { @@ -911,40 +863,19 @@ class BFWrite : public BitBufferBase return !IsOverflowed(); } - INLINE bool WriteBytes(const uptr data, i32 numBytes) - { - return WriteBits(data, numBytes << 3); - } + INLINE bool WriteBytes(const uptr data, i32 numBytes) { return WriteBits(data, numBytes << 3); } - INLINE i32 GetNumBitsWritten() - { - return (32 - m_OutBitsLeft) + (32 * (m_DataOut - m_Data)); - } + INLINE i32 GetNumBitsWritten() { return (32 - m_OutBitsLeft) + (32 * (m_DataOut - m_Data)); } - INLINE i32 GetNumBytesWritten() - { - return (GetNumBitsWritten() + 7) >> 3; - } + INLINE i32 GetNumBytesWritten() { return (GetNumBitsWritten() + 7) >> 3; } - INLINE void WriteChar(i32 val) - { - WriteSBitLong(val, sizeof(char) << 3); - } + INLINE void WriteChar(i32 val) { WriteSBitLong(val, sizeof(char) << 3); } - INLINE void WriteByte(i32 val) - { - WriteUBitLong(val, sizeof(unsigned char) << 3, false); - } + INLINE void WriteByte(i32 val) { WriteUBitLong(val, sizeof(unsigned char) << 3, false); } - INLINE void WriteShort(i32 val) - { - WriteSBitLong(val, sizeof(short) << 3); - } + INLINE void WriteShort(i32 val) { WriteSBitLong(val, sizeof(short) << 3); } - INLINE void WriteWord(i32 val) - { - WriteUBitLong(val, sizeof(unsigned short) << 3); - } + INLINE void WriteWord(i32 val) { WriteUBitLong(val, sizeof(unsigned short) << 3); } INLINE bool WriteString(const char* str) { diff --git a/primedev/core/math/color.h b/primedev/core/math/color.h index 013c4e4ca..a46bc0893 100644 --- a/primedev/core/math/color.h +++ b/primedev/core/math/color.h @@ -7,22 +7,10 @@ struct color24 typedef struct color32_s { - bool operator!=(const struct color32_s& other) const - { - return r != other.r || g != other.g || b != other.b || a != other.a; - } - inline unsigned* asInt(void) - { - return reinterpret_cast(this); - } - inline const unsigned* asInt(void) const - { - return reinterpret_cast(this); - } - inline void Copy(const color32_s& rhs) - { - *asInt() = *rhs.asInt(); - } + bool operator!=(const struct color32_s& other) const { return r != other.r || g != other.g || b != other.b || a != other.a; } + inline unsigned* asInt(void) { return reinterpret_cast(this); } + inline const unsigned* asInt(void) const { return reinterpret_cast(this); } + inline void Copy(const color32_s& rhs) { *asInt() = *rhs.asInt(); } uint8_t r, g, b, a; } color32; @@ -79,55 +67,22 @@ class Color _b = _color[2]; _a = _color[3]; } - int GetValue(int index) const - { - return _color[index]; - } - void SetRawColor(int color32) - { - *((int*)this) = color32; - } - int GetRawColor(void) const - { - return *((int*)this); - } + int GetValue(int index) const { return _color[index]; } + void SetRawColor(int color32) { *((int*)this) = color32; } + int GetRawColor(void) const { return *((int*)this); } - inline int r() const - { - return _color[0]; - } - inline int g() const - { - return _color[1]; - } - inline int b() const - { - return _color[2]; - } - inline int a() const - { - return _color[3]; - } + inline int r() const { return _color[0]; } + inline int g() const { return _color[1]; } + inline int b() const { return _color[2]; } + inline int a() const { return _color[3]; } - unsigned char& operator[](int index) - { - return _color[index]; - } + unsigned char& operator[](int index) { return _color[index]; } - const unsigned char& operator[](int index) const - { - return _color[index]; - } + const unsigned char& operator[](int index) const { return _color[index]; } - bool operator==(const Color& rhs) const - { - return (*((int*)this) == *((int*)&rhs)); - } + bool operator==(const Color& rhs) const { return (*((int*)this) == *((int*)&rhs)); } - bool operator!=(const Color& rhs) const - { - return !(operator==(rhs)); - } + bool operator!=(const Color& rhs) const { return !(operator==(rhs)); } Color& operator=(const Color& rhs) { @@ -164,10 +119,7 @@ class Color return out; } - SourceColor ToSourceColor() - { - return SourceColor(_color[0], _color[1], _color[2], _color[3]); - } + SourceColor ToSourceColor() { return SourceColor(_color[0], _color[1], _color[2], _color[3]); } private: unsigned char _color[4]; diff --git a/primedev/core/math/vector.h b/primedev/core/math/vector.h index e62f2c93f..ea1de65ec 100644 --- a/primedev/core/math/vector.h +++ b/primedev/core/math/vector.h @@ -13,15 +13,27 @@ class Vector3 public: float x, y, z; - Vector3(float _x, float _y, float _z) : x(_x), y(_y), z(_z) {} - Vector3(float _f) : x(_f), y(_f), z(_f) {} - Vector3() : x(0.0f), y(0.0f), z(0.0f) {} - - inline bool IsValid() + Vector3(float _x, float _y, float _z) + : x(_x) + , y(_y) + , z(_z) + { + } + Vector3(float _f) + : x(_f) + , y(_f) + , z(_f) + { + } + Vector3() + : x(0.0f) + , y(0.0f) + , z(0.0f) { - return IsFinite(x) && IsFinite(y) && IsFinite(z); } + inline bool IsValid() { return IsFinite(x) && IsFinite(y) && IsFinite(z); } + inline void Init(float fX = 0.0f, float fY = 0.0f, float fZ = 0.0f) { x = fX; @@ -29,10 +41,7 @@ class Vector3 z = fZ; } - inline float Length() - { - return FastSqrt(x * x + y * y + z * z); - } + inline float Length() { return FastSqrt(x * x + y * y + z * z); } inline float DistTo(const Vector3& vOther) { @@ -44,10 +53,7 @@ class Vector3 return vDelta.Length(); } - float Dot(const Vector3& vOther) const - { - return x * vOther.x + y * vOther.y + z * vOther.z; - } + float Dot(const Vector3& vOther) const { return x * vOther.x + y * vOther.y + z * vOther.z; } // Cross product between two vectors. Vector3 Cross(const Vector3& vOther) const; @@ -311,17 +317,29 @@ class QAngle float y; float z; - QAngle(float _x, float _y, float _z) : x(_x), y(_y), z(_z) {} - QAngle(float _f) : x(_f), y(_f), z(_f) {} - QAngle() : x(0.0f), y(0.0f), z(0.0f) {} + QAngle(float _x, float _y, float _z) + : x(_x) + , y(_y) + , z(_z) + { + } + QAngle(float _f) + : x(_f) + , y(_f) + , z(_f) + { + } + QAngle() + : x(0.0f) + , y(0.0f) + , z(0.0f) + { + } Vector3 GetNormal() const; // todo: more operators maybe - bool operator==(const QAngle& other) - { - return x == other.x && y == other.y && z == other.z; - } + bool operator==(const QAngle& other) { return x == other.x && y == other.y && z == other.z; } }; inline Vector3 QAngle::GetNormal() const diff --git a/primedev/core/memalloc.h b/primedev/core/memalloc.h index 73e078f5f..a55ce361b 100644 --- a/primedev/core/memalloc.h +++ b/primedev/core/memalloc.h @@ -40,10 +40,7 @@ class SourceAllocator } return _realloc_base(originalPtr, newSize); } - static void Free(void* ptr) - { - _free_base(ptr); - } + static void Free(void* ptr) { _free_base(ptr); } }; typedef rapidjson::GenericDocument, rapidjson::MemoryPoolAllocator, SourceAllocator> rapidjson_document; diff --git a/primedev/core/sourceinterface.cpp b/primedev/core/sourceinterface.cpp index 5a72beb07..74e4a9963 100644 --- a/primedev/core/sourceinterface.cpp +++ b/primedev/core/sourceinterface.cpp @@ -1,16 +1,12 @@ #include "sourceinterface.h" #include "logging/sourceconsole.h" -AUTOHOOK_INIT() - // really wanted to do a modular callback system here but honestly couldn't be bothered so hardcoding stuff for now: todo later -// clang-format off -AUTOHOOK_PROCADDRESS(ClientCreateInterface, client.dll, CreateInterface, -void*, __fastcall, (const char* pName, const int* pReturnCode)) -// clang-format on +static void*(__fastcall* o_pClientCreateInterface)(const char* pName, const int* pReturnCode) = nullptr; +static void* __fastcall h_ClientCreateInterface(const char* pName, const int* pReturnCode) { - void* ret = ClientCreateInterface(pName, pReturnCode); + void* ret = o_pClientCreateInterface(pName, pReturnCode); spdlog::info("CreateInterface CLIENT {}", pName); if (!strcmp(pName, "GameClientExports001")) @@ -19,30 +15,38 @@ void*, __fastcall, (const char* pName, const int* pReturnCode)) return ret; } -// clang-format off -AUTOHOOK_PROCADDRESS(ServerCreateInterface, server.dll, CreateInterface, -void*, __fastcall, (const char* pName, const int* pReturnCode)) -// clang-format on +static void*(__fastcall* o_pServerCreateInterface)(const char* pName, const int* pReturnCode) = nullptr; +static void* __fastcall h_ServerCreateInterface(const char* pName, const int* pReturnCode) { - void* ret = ServerCreateInterface(pName, pReturnCode); + void* ret = o_pServerCreateInterface(pName, pReturnCode); spdlog::info("CreateInterface SERVER {}", pName); return ret; } -// clang-format off -AUTOHOOK_PROCADDRESS(EngineCreateInterface, engine.dll, CreateInterface, -void*, __fastcall, (const char* pName, const int* pReturnCode)) -// clang-format on +static void*(__fastcall* o_pEngineCreateInterface)(const char* pName, const int* pReturnCode) = nullptr; +static void* __fastcall h_EngineCreateInterface(const char* pName, const int* pReturnCode) { - void* ret = EngineCreateInterface(pName, pReturnCode); + void* ret = o_pEngineCreateInterface(pName, pReturnCode); spdlog::info("CreateInterface ENGINE {}", pName); return ret; } -// clang-format off -ON_DLL_LOAD("client.dll", ClientInterface, (CModule module)) {AUTOHOOK_DISPATCH_MODULE(client.dll)} -ON_DLL_LOAD("server.dll", ServerInterface, (CModule module)) {AUTOHOOK_DISPATCH_MODULE(server.dll)} -ON_DLL_LOAD("engine.dll", EngineInterface, (CModule module)) {AUTOHOOK_DISPATCH_MODULE(engine.dll)} -// clang-format on +ON_DLL_LOAD("client.dll", ClientInterface, (CModule module)) +{ + o_pClientCreateInterface = module.GetExportedFunction("CreateInterface").RCast(); + HookAttach(&(PVOID&)o_pClientCreateInterface, (PVOID)h_ClientCreateInterface); +} + +ON_DLL_LOAD("server.dll", ServerInterface, (CModule module)) +{ + o_pServerCreateInterface = module.GetExportedFunction("CreateInterface").RCast(); + HookAttach(&(PVOID&)o_pServerCreateInterface, (PVOID)h_ServerCreateInterface); +} + +ON_DLL_LOAD("engine.dll", EngineInterface, (CModule module)) +{ + o_pEngineCreateInterface = module.GetExportedFunction("CreateInterface").RCast(); + HookAttach(&(PVOID&)o_pEngineCreateInterface, (PVOID)h_EngineCreateInterface); +} diff --git a/primedev/core/sourceinterface.h b/primedev/core/sourceinterface.h index bbbbd3bc2..730339daa 100644 --- a/primedev/core/sourceinterface.h +++ b/primedev/core/sourceinterface.h @@ -26,13 +26,7 @@ template class SourceInterface spdlog::error("Failed to call CreateInterface for %s in %s", interfaceName, moduleName); } - T* operator->() const - { - return m_interface; - } + T* operator->() const { return m_interface; } - operator T*() const - { - return m_interface; - } + operator T*() const { return m_interface; } }; diff --git a/primedev/core/tier0.cpp b/primedev/core/tier0.cpp index dd5ac245e..639b3bf8c 100644 --- a/primedev/core/tier0.cpp +++ b/primedev/core/tier0.cpp @@ -18,11 +18,40 @@ void TryCreateGlobalMemAlloc() g_pMemAllocSingleton = CreateGlobalMemAlloc(); // if it already exists, this returns the preexisting IMemAlloc instance } +HRESULT WINAPI _SetThreadDescription(HANDLE hThread, PCWSTR lpThreadDescription) +{ + // need to grab it dynamically as this function was only introduced at some point in Windows 10 + static decltype(&SetThreadDescription) _SetThreadDescription = + CModule("KernelBase.dll").GetExportedFunction("SetThreadDescription").RCast(); + + if (_SetThreadDescription) + return _SetThreadDescription(hThread, lpThreadDescription); + + return ERROR_OLD_WIN_VERSION; +} + +static void(__fastcall* o_pThreadSetDebugName)(HANDLE threadHandle, const char* name) = nullptr; +static void __fastcall h_ThreadSetDebugName(HANDLE threadHandle, const char* name) +{ + if (threadHandle == 0) + threadHandle = GetCurrentThread(); + + // TODO: This "method" of "charset conversion" from string to wstring is abhorrent. Change it to a proper one + // as soon as Northstar has some helper function to do proper charset conversions. + auto tmp = std::string(name); + _SetThreadDescription(threadHandle, std::wstring(tmp.begin(), tmp.end()).c_str()); + + o_pThreadSetDebugName(threadHandle, name); +} + ON_DLL_LOAD("tier0.dll", Tier0GameFuncs, (CModule module)) { // shouldn't be necessary, but do this just in case TryCreateGlobalMemAlloc(); + o_pThreadSetDebugName = module.GetExportedFunction("ThreadSetDebugName").RCast(); + HookAttach(&(PVOID&)o_pThreadSetDebugName, (PVOID)h_ThreadSetDebugName); + // setup tier0 funcs CommandLine = module.GetExportedFunction("CommandLine").RCast(); Plat_FloatTime = module.GetExportedFunction("Plat_FloatTime").RCast(); diff --git a/primedev/core/vanilla.h b/primedev/core/vanilla.h index b0797803a..356495c87 100644 --- a/primedev/core/vanilla.h +++ b/primedev/core/vanilla.h @@ -17,10 +17,7 @@ class VanillaCompatibility m_bIsVanillaCompatible = isVanilla; } - bool GetVanillaCompatibility() - { - return m_bIsVanillaCompatible; - } + bool GetVanillaCompatibility() { return m_bIsVanillaCompatible; } private: bool m_bIsVanillaCompatible = false; diff --git a/primedev/dedicated/dedicated.cpp b/primedev/dedicated/dedicated.cpp index eca9b9f1f..8b0604fcf 100644 --- a/primedev/dedicated/dedicated.cpp +++ b/primedev/dedicated/dedicated.cpp @@ -8,8 +8,6 @@ #include "masterserver/masterserver.h" #include "util/printcommands.h" -AUTOHOOK_INIT() - bool IsDedicatedServer() { static bool result = strstr(GetCommandLineA(), "-dedicated"); @@ -114,10 +112,8 @@ DWORD WINAPI ConsoleInputThread(PVOID pThreadParameter) return 0; } -// clang-format off -AUTOHOOK(IsGameActiveWindow, engine.dll + 0x1CDC80, -bool,, ()) -// clang-format on +static bool (*o_pIsGameActiveWindow)() = nullptr; +static bool h_IsGameActiveWindow() { return true; } @@ -126,7 +122,8 @@ ON_DLL_LOAD_DEDI_RELIESON("engine.dll", DedicatedServer, ServerPresence, (CModul { spdlog::info("InitialiseDedicated"); - AUTOHOOK_DISPATCH_MODULE(engine.dll) + o_pIsGameActiveWindow = module.Offset(0x1CDC80).RCast(); + HookAttach(&(PVOID&)o_pIsGameActiveWindow, (PVOID)h_IsGameActiveWindow); // Host_Init // prevent a particle init that relies on client dll @@ -270,12 +267,10 @@ ON_DLL_LOAD_DEDI("tier0.dll", DedicatedServerOrigin, (CModule module)) module.GetExportedFunction("Tier0_InitOrigin").Patch("C3"); } -// clang-format off -AUTOHOOK(PrintSquirrelError, server.dll + 0x794D0, -void, __fastcall, (void* sqvm)) -// clang-format on +static void(__fastcall* o_pPrintSquirrelError)(void* sqvm) = nullptr; +static void __fastcall h_PrintSquirrelError(void* sqvm) { - PrintSquirrelError(sqvm); + o_pPrintSquirrelError(sqvm); // close dedicated server if a fatal error is hit // atm, this will crash if not aborted, so this just closes more gracefully @@ -289,7 +284,8 @@ void, __fastcall, (void* sqvm)) ON_DLL_LOAD_DEDI("server.dll", DedicatedServerGameDLL, (CModule module)) { - AUTOHOOK_DISPATCH_MODULE(server.dll) + o_pPrintSquirrelError = module.Offset(0x794D0).RCast(); + HookAttach(&(PVOID&)o_pPrintSquirrelError, (PVOID)h_PrintSquirrelError); if (CommandLine()->CheckParm("-nopakdedi")) { diff --git a/primedev/dedicated/dedicatedmaterialsystem.cpp b/primedev/dedicated/dedicatedmaterialsystem.cpp index 010780862..f74cbfe39 100644 --- a/primedev/dedicated/dedicatedmaterialsystem.cpp +++ b/primedev/dedicated/dedicatedmaterialsystem.cpp @@ -1,11 +1,18 @@ #include "dedicated.h" #include "core/tier0.h" -AUTOHOOK_INIT() - -// clang-format off -AUTOHOOK(D3D11CreateDevice, materialsystem_dx11.dll + 0xD9A0E, -HRESULT, __stdcall, ( +static HRESULT(__stdcall* o_pD3D11CreateDevice)( + void* pAdapter, + int DriverType, + HMODULE Software, + UINT Flags, + int* pFeatureLevels, + UINT FeatureLevels, + UINT SDKVersion, + void** ppDevice, + int* pFeatureLevel, + void** ppImmediateContext) = nullptr; +static HRESULT __stdcall h_D3D11CreateDevice( void* pAdapter, int DriverType, HMODULE Software, @@ -15,8 +22,7 @@ HRESULT, __stdcall, ( UINT SDKVersion, void** ppDevice, int* pFeatureLevel, - void** ppImmediateContext)) -// clang-format on + void** ppImmediateContext) { // note: this is super duper temp pretty much just messing around with it // does run surprisingly well on dedi for a software driver tho if you ignore the +1gb ram usage at times, seems like dedi doesn't @@ -26,13 +32,14 @@ HRESULT, __stdcall, ( if (CommandLine()->CheckParm("-softwared3d11")) DriverType = 5; // D3D_DRIVER_TYPE_WARP - return D3D11CreateDevice( + return o_pD3D11CreateDevice( pAdapter, DriverType, Software, Flags, pFeatureLevels, FeatureLevels, SDKVersion, ppDevice, pFeatureLevel, ppImmediateContext); } ON_DLL_LOAD_DEDI("materialsystem_dx11.dll", DedicatedServerMaterialSystem, (CModule module)) { - AUTOHOOK_DISPATCH() + o_pD3D11CreateDevice = module.Offset(0xD9A0E).RCast(); + HookAttach(&(PVOID&)o_pD3D11CreateDevice, (PVOID)h_D3D11CreateDevice); // CMaterialSystem::FindMaterial // make the game always use the error material diff --git a/primedev/dllmain.cpp b/primedev/dllmain.cpp index 1191307fb..95ea103f5 100644 --- a/primedev/dllmain.cpp +++ b/primedev/dllmain.cpp @@ -10,6 +10,8 @@ #include "squirrel/squirrel.h" #include "server/serverpresence.h" +#include "windows/libsys.h" + #include "rapidjson/document.h" #include "rapidjson/stringbuffer.h" #include "rapidjson/writer.h" @@ -64,7 +66,11 @@ bool InitialiseNorthstar() // Write launcher version to log StartupLog(); - InstallInitialHooks(); + // Init minhook + HookSys_Init(); + + // Init loadlibrary callbacks + LibSys_Init(); g_pServerPresence = new ServerPresenceManager(); diff --git a/primedev/engine/gl_matsysiface.cpp b/primedev/engine/gl_matsysiface.cpp new file mode 100644 index 000000000..075a56acc --- /dev/null +++ b/primedev/engine/gl_matsysiface.cpp @@ -0,0 +1,50 @@ +#include "materialsystem/cmaterialglue.h" + +CMaterialGlue* (*GetMaterialAtCrossHair)(); + +static void(__fastcall* o_pCC_mat_crosshair_printmaterial_f)(const CCommand& args) = nullptr; +static void __fastcall h_CC_mat_crosshair_printmaterial_f(const CCommand& args) +{ + CMaterialGlue* pMat = GetMaterialAtCrossHair(); + + if (!pMat) + { + spdlog::error("Not looking at a material!"); + return; + } + + std::function fnPrintGlue = [](CMaterialGlue* pGlue, const char* szName) + { + spdlog::info("|-----------------------------------------------"); + + if (!pGlue) + { + spdlog::info("|-- {} is NULL", szName); + return; + } + + spdlog::info("|-- Name: {}", szName); + spdlog::info("|-- GUID: {:#x}", pGlue->m_GUID); + spdlog::info("|-- Name: {}", pGlue->m_pszName); + spdlog::info("|-- Width : {}", pGlue->m_iWidth); + spdlog::info("|-- Height: {}", pGlue->m_iHeight); + }; + + spdlog::info("|- GUID: {:#x}", pMat->m_GUID); + spdlog::info("|- Name: {}", pMat->m_pszName); + spdlog::info("|- Width : {}", pMat->m_iWidth); + spdlog::info("|- Height: {}", pMat->m_iHeight); + + fnPrintGlue(pMat->m_pDepthShadow, "DepthShadow"); + fnPrintGlue(pMat->m_pDepthPrepass, "DepthPrepass"); + fnPrintGlue(pMat->m_pDepthVSM, "DepthVSM"); + fnPrintGlue(pMat->m_pColPass, "ColPass"); +} + +ON_DLL_LOAD("engine.dll", GlMatSysIFace, (CModule module)) +{ + o_pCC_mat_crosshair_printmaterial_f = module.Offset(0xB3C40).RCast(); + HookAttach(&(PVOID&)o_pCC_mat_crosshair_printmaterial_f, (PVOID)h_CC_mat_crosshair_printmaterial_f); + + GetMaterialAtCrossHair = module.Offset(0xB37D0).RCast(); +} diff --git a/primedev/engine/host.cpp b/primedev/engine/host.cpp index dacb8fc1c..e414908b2 100644 --- a/primedev/engine/host.cpp +++ b/primedev/engine/host.cpp @@ -6,15 +6,11 @@ #include "r2engine.h" #include "core/tier0.h" -AUTOHOOK_INIT() - -// clang-format off -AUTOHOOK(Host_Init, engine.dll + 0x155EA0, -void, __fastcall, (bool bDedicated)) -// clang-format on +static void(__fastcall* o_pHost_Init)(bool bDedicated) = nullptr; +static void __fastcall h_Host_Init(bool bDedicated) { spdlog::info("Host_Init()"); - Host_Init(bDedicated); + o_pHost_Init(bDedicated); FixupCvarFlags(); // need to initialise these after host_init since they do stuff to preexisting concommands/convars without being client/server specific InitialiseCommandPrint(); @@ -29,5 +25,6 @@ void, __fastcall, (bool bDedicated)) ON_DLL_LOAD("engine.dll", Host_Init, (CModule module)) { - AUTOHOOK_DISPATCH() + o_pHost_Init = module.Offset(0x155EA0).RCast(); + HookAttach(&(PVOID&)o_pHost_Init, (PVOID)h_Host_Init); } diff --git a/primedev/engine/hoststate.cpp b/primedev/engine/hoststate.cpp index d5942551a..4a4d909da 100644 --- a/primedev/engine/hoststate.cpp +++ b/primedev/engine/hoststate.cpp @@ -9,14 +9,12 @@ #include "squirrel/squirrel.h" #include "plugins/pluginmanager.h" -AUTOHOOK_INIT() - CHostState* g_pHostState; std::string sLastMode; -VAR_AT(engine.dll + 0x13FA6070, ConVar*, Cvar_hostport); -FUNCTION_AT(engine.dll + 0x1232C0, void, __fastcall, _Cmd_Exec_f, (const CCommand& arg, bool bOnlyIfExists, bool bUseWhitelists)); +static ConVar* Cvar_hostport = nullptr; +static void(__fastcall* _Cmd_Exec_f)(const CCommand& arg, bool bOnlyIfExists, bool bUseWhitelists) = nullptr; void ServerStartingOrChangingMap() { @@ -53,10 +51,8 @@ void ServerStartingOrChangingMap() g_pServerAuthentication->m_bStartingLocalSPGame = false; } -// clang-format off -AUTOHOOK(CHostState__State_NewGame, engine.dll + 0x16E7D0, -void, __fastcall, (CHostState* self)) -// clang-format on +static void(__fastcall* o_pCHostState__State_NewGame)(CHostState* self) = nullptr; +static void __fastcall h_CHostState__State_NewGame(CHostState* self) { spdlog::info("HostState: NewGame"); @@ -70,7 +66,7 @@ void, __fastcall, (CHostState* self)) ServerStartingOrChangingMap(); double dStartTime = Plat_FloatTime(); - CHostState__State_NewGame(self); + o_pCHostState__State_NewGame(self); spdlog::info("loading took {}s", Plat_FloatTime() - dStartTime); // setup server presence @@ -82,10 +78,8 @@ void, __fastcall, (CHostState* self)) g_pServerAuthentication->m_bNeedLocalAuthForNewgame = false; } -// clang-format off -AUTOHOOK(CHostState__State_LoadGame, engine.dll + 0x16E730, -void, __fastcall, (CHostState* self)) -// clang-format on +static void(__fastcall* o_pCHostState__State_LoadGame)(CHostState* self) = nullptr; +static void __fastcall h_CHostState__State_LoadGame(CHostState* self) { // singleplayer server starting // useless in 99% of cases but without it things could potentially break very much @@ -100,7 +94,7 @@ void, __fastcall, (CHostState* self)) g_pServerAuthentication->m_bStartingLocalSPGame = true; double dStartTime = Plat_FloatTime(); - CHostState__State_LoadGame(self); + o_pCHostState__State_LoadGame(self); spdlog::info("loading took {}s", Plat_FloatTime() - dStartTime); // no server presence, can't do it because no map name in hoststate @@ -109,32 +103,28 @@ void, __fastcall, (CHostState* self)) g_pServerAuthentication->m_bNeedLocalAuthForNewgame = false; } -// clang-format off -AUTOHOOK(CHostState__State_ChangeLevelMP, engine.dll + 0x16E520, -void, __fastcall, (CHostState* self)) -// clang-format on +static void(__fastcall* o_pCHostState__State_ChangeLevelMP)(CHostState* self) = nullptr; +static void __fastcall h_CHostState__State_ChangeLevelMP(CHostState* self) { spdlog::info("HostState: ChangeLevelMP"); ServerStartingOrChangingMap(); double dStartTime = Plat_FloatTime(); - CHostState__State_ChangeLevelMP(self); + o_pCHostState__State_ChangeLevelMP(self); spdlog::info("loading took {}s", Plat_FloatTime() - dStartTime); g_pServerPresence->SetMap(g_pHostState->m_levelName); } -// clang-format off -AUTOHOOK(CHostState__State_GameShutdown, engine.dll + 0x16E640, -void, __fastcall, (CHostState* self)) -// clang-format on +static void(__fastcall* o_pCHostState__State_GameShutdown)(CHostState* self) = nullptr; +static void __fastcall h_CHostState__State_GameShutdown(CHostState* self) { spdlog::info("HostState: GameShutdown"); g_pServerPresence->DestroyPresence(); - CHostState__State_GameShutdown(self); + o_pCHostState__State_GameShutdown(self); // run gamemode cleanup cfg now instead of when we start next map if (sLastMode.length()) @@ -153,12 +143,10 @@ void, __fastcall, (CHostState* self)) } } -// clang-format off -AUTOHOOK(CHostState__FrameUpdate, engine.dll + 0x16DB00, -void, __fastcall, (CHostState* self, double flCurrentTime, float flFrameTime)) -// clang-format on +static void(__fastcall* o_pCHostState__FrameUpdate)(CHostState* self, double flCurrentTime, float flFrameTime) = nullptr; +static void __fastcall h_CHostState__FrameUpdate(CHostState* self, double flCurrentTime, float flFrameTime) { - CHostState__FrameUpdate(self, flCurrentTime, flFrameTime); + o_pCHostState__FrameUpdate(self, flCurrentTime, flFrameTime); if (*g_pServerState == server_state_t::ss_active) { @@ -184,7 +172,23 @@ void, __fastcall, (CHostState* self, double flCurrentTime, float flFrameTime)) ON_DLL_LOAD_RELIESON("engine.dll", HostState, ConVar, (CModule module)) { - AUTOHOOK_DISPATCH() + o_pCHostState__State_NewGame = module.Offset(0x16E7D0).RCast(); + HookAttach(&(PVOID&)o_pCHostState__State_NewGame, (PVOID)h_CHostState__State_NewGame); + + o_pCHostState__State_LoadGame = module.Offset(0x16E730).RCast(); + HookAttach(&(PVOID&)o_pCHostState__State_LoadGame, (PVOID)h_CHostState__State_LoadGame); + + o_pCHostState__State_ChangeLevelMP = module.Offset(0x16E520).RCast(); + HookAttach(&(PVOID&)o_pCHostState__State_ChangeLevelMP, (PVOID)h_CHostState__State_ChangeLevelMP); + + o_pCHostState__State_GameShutdown = module.Offset(0x16E640).RCast(); + HookAttach(&(PVOID&)o_pCHostState__State_GameShutdown, (PVOID)h_CHostState__State_GameShutdown); + + o_pCHostState__FrameUpdate = module.Offset(0x16DB00).RCast(); + HookAttach(&(PVOID&)o_pCHostState__FrameUpdate, (PVOID)h_CHostState__FrameUpdate); + + Cvar_hostport = module.Offset(0x13FA6070).RCast(); + _Cmd_Exec_f = module.Offset(0x1232C0).RCast(); g_pHostState = module.Offset(0x7CF180).RCast(); } diff --git a/primedev/game/client/clientmode_shared.cpp b/primedev/game/client/clientmode_shared.cpp new file mode 100644 index 000000000..e5793261a --- /dev/null +++ b/primedev/game/client/clientmode_shared.cpp @@ -0,0 +1,66 @@ + +//----------------------------------------------------------------------------- +// Some explanation might be needed for this. The crash is caused by +// us calling a pure virtual function in the constructor. +// The order goes like this: +// ctor +// -> vftable = IPureCall::vftable +// -> IPureCall::Ok() +// -> IPureCall::CallMeIDareYou() +// -> purecall_handler +// -> crash :( +class IPureCall +{ +public: + IPureCall() { Ok(); } + + virtual void CallMeIDareYou() = 0; + + void Ok() { CallMeIDareYou(); } +}; + +class CPureCall : IPureCall +{ + virtual void CallMeIDareYou() {} +}; + +static void (*o_pCC_crash_test_f)(const CCommand& args); +static void h_CC_crash_test_f(const CCommand& args) +{ + int crashtype = 0; + int dummy; + if (args.ArgC() > 1) + { + crashtype = atoi(args.Arg(1)); + } + switch (crashtype) + { + case 0: + dummy = *((int*)NULL); + spdlog::info("Crashed! {}", dummy); + break; + case 1: + *((int*)NULL) = 24122021; + break; + case 2: + throw std::exception("Crashed!"); + break; + case 3: + RaiseException(7, 0, 0, NULL); + break; + case 4: + { + CPureCall PureCall; + break; + } + default: + spdlog::info("Unknown variety of crash. You have now failed to crash. I hope you're happy."); + break; + } +} + +ON_DLL_LOAD("engine.dll", ClientModeShared, (CModule module)) +{ + o_pCC_crash_test_f = module.Offset(0x15BEE0).RCast(); + HookAttach(&(PVOID&)o_pCC_crash_test_f, (PVOID)h_CC_crash_test_f); +} diff --git a/primedev/logging/crashhandler.h b/primedev/logging/crashhandler.h index c059a8ca4..992dd7d2e 100644 --- a/primedev/logging/crashhandler.h +++ b/primedev/logging/crashhandler.h @@ -14,35 +14,17 @@ class CCrashHandler void Init(); void Shutdown(); - void Lock() - { - m_Mutex.lock(); - } - - void Unlock() - { - m_Mutex.unlock(); - } - - void SetState(bool bState) - { - m_bState = bState; - } - - bool GetState() const - { - return m_bState; - } - - void SetAllFatal(bool bState) - { - m_bAllExceptionsFatal = bState; - } - - bool GetAllFatal() const - { - return m_bAllExceptionsFatal; - } + void Lock() { m_Mutex.lock(); } + + void Unlock() { m_Mutex.unlock(); } + + void SetState(bool bState) { m_bState = bState; } + + bool GetState() const { return m_bState; } + + void SetAllFatal(bool bState) { m_bAllExceptionsFatal = bState; } + + bool GetAllFatal() const { return m_bAllExceptionsFatal; } //----------------------------------------------------------------------------- // Exception helpers diff --git a/primedev/logging/logging.cpp b/primedev/logging/logging.cpp index 4367b384b..e1298553c 100644 --- a/primedev/logging/logging.cpp +++ b/primedev/logging/logging.cpp @@ -11,8 +11,6 @@ #include #include -AUTOHOOK_INIT() - std::vector> loggers {}; namespace NS::log @@ -124,11 +122,7 @@ void CustomSink::custom_log(const custom_log_msg& msg) void InitialiseConsole() { - if (GetConsoleWindow() == NULL && AllocConsole() == FALSE) - { - std::cout << "[*] Failed to create a console window" << std::endl; - } - else + if (AllocConsole() != FALSE) { freopen("CONOUT$", "w", stdout); freopen("CONOUT$", "w", stderr); diff --git a/primedev/logging/logging.h b/primedev/logging/logging.h index be41cb39d..edfa20dc1 100644 --- a/primedev/logging/logging.h +++ b/primedev/logging/logging.h @@ -14,7 +14,11 @@ class ColoredLogger; struct custom_log_msg : spdlog::details::log_msg { public: - custom_log_msg(ColoredLogger* origin, spdlog::details::log_msg msg) : origin(origin), spdlog::details::log_msg(msg) {} + custom_log_msg(ColoredLogger* origin, spdlog::details::log_msg msg) + : origin(origin) + , spdlog::details::log_msg(msg) + { + } ColoredLogger* origin; }; @@ -38,7 +42,8 @@ class ColoredLogger : public spdlog::logger std::vector> custom_sinks_; - ColoredLogger(std::string name, Color color, bool first = false) : spdlog::logger(*spdlog::default_logger()) + ColoredLogger(std::string name, Color color, bool first = false) + : spdlog::logger(*spdlog::default_logger()) { name_ = std::move(name); if (!first) diff --git a/primedev/logging/sourceconsole.cpp b/primedev/logging/sourceconsole.cpp index 55be47237..c9063d198 100644 --- a/primedev/logging/sourceconsole.cpp +++ b/primedev/logging/sourceconsole.cpp @@ -1,35 +1,35 @@ #include "core/convar/convar.h" #include "sourceconsole.h" -#include "core/sourceinterface.h" +#include "core/tier1.h" #include "core/convar/concommand.h" #include "util/printcommands.h" -SourceInterface* g_pSourceGameConsole; +CGameConsole* g_pGameConsole; void ConCommand_toggleconsole(const CCommand& arg) { NOTE_UNUSED(arg); - if ((*g_pSourceGameConsole)->IsConsoleVisible()) - (*g_pSourceGameConsole)->Hide(); + if (g_pGameConsole->IsConsoleVisible()) + g_pGameConsole->Hide(); else - (*g_pSourceGameConsole)->Activate(); + g_pGameConsole->Activate(); } void ConCommand_showconsole(const CCommand& arg) { NOTE_UNUSED(arg); - (*g_pSourceGameConsole)->Activate(); + g_pGameConsole->Activate(); } void ConCommand_hideconsole(const CCommand& arg) { NOTE_UNUSED(arg); - (*g_pSourceGameConsole)->Hide(); + g_pGameConsole->Hide(); } void SourceConsoleSink::custom_sink_it_(const custom_log_msg& msg) { - if (!(*g_pSourceGameConsole)->m_bInitialized) + if (!g_pGameConsole->m_bInitialized) return; spdlog::memory_buf_t formatted; @@ -41,11 +41,11 @@ void SourceConsoleSink::custom_sink_it_(const custom_log_msg& msg) SourceColor levelColor = m_LogColours[msg.level]; std::string name {msg.logger_name.begin(), msg.logger_name.end()}; - (*g_pSourceGameConsole)->m_pConsole->m_pConsolePanel->ColorPrint(msg.origin->SRCColor, ("[" + name + "]").c_str()); - (*g_pSourceGameConsole)->m_pConsole->m_pConsolePanel->Print(" "); - (*g_pSourceGameConsole)->m_pConsole->m_pConsolePanel->ColorPrint(levelColor, ("[" + std::string(level_names[msg.level]) + "]").c_str()); - (*g_pSourceGameConsole)->m_pConsole->m_pConsolePanel->Print(" "); - (*g_pSourceGameConsole)->m_pConsole->m_pConsolePanel->Print(fmt::to_string(formatted).c_str()); + g_pGameConsole->m_pConsole->m_pConsolePanel->ColorPrint(msg.origin->SRCColor, ("[" + name + "]").c_str()); + g_pGameConsole->m_pConsole->m_pConsolePanel->Print(" "); + g_pGameConsole->m_pConsole->m_pConsolePanel->ColorPrint(levelColor, ("[" + std::string(level_names[msg.level]) + "]").c_str()); + g_pGameConsole->m_pConsole->m_pConsolePanel->Print(" "); + g_pGameConsole->m_pConsole->m_pConsolePanel->Print(fmt::to_string(formatted).c_str()); } void SourceConsoleSink::sink_it_(const spdlog::details::log_msg& msg) @@ -73,9 +73,9 @@ void, __fastcall, (CConsoleDialog* consoleDialog, const char* pCommand)) // called from sourceinterface.cpp in client createinterface hooks, on GameClientExports001 void InitialiseConsoleOnInterfaceCreation() { - (*g_pSourceGameConsole)->Initialize(); + g_pGameConsole->Initialize(); // hook OnCommandSubmitted so we print inputted commands - OnCommandSubmittedHook.Dispatch((LPVOID)(*g_pSourceGameConsole)->m_pConsole->m_vtable->OnCommandSubmitted); + OnCommandSubmittedHook.Dispatch((LPVOID)g_pGameConsole->m_pConsole->m_vtable->OnCommandSubmitted); auto consoleSink = std::make_shared(); if (g_bSpdLog_UseAnsiColor) @@ -87,7 +87,7 @@ void InitialiseConsoleOnInterfaceCreation() ON_DLL_LOAD_CLIENT_RELIESON("client.dll", SourceConsole, ConCommand, (CModule module)) { - g_pSourceGameConsole = new SourceInterface("client.dll", "GameConsole004"); + g_pGameConsole = Sys_GetFactoryPtr("client.dll", "GameConsole004").RCast(); RegisterConCommand("toggleconsole", ConCommand_toggleconsole, "Show/hide the console.", FCVAR_DONTRECORD); RegisterConCommand("showconsole", ConCommand_showconsole, "Show the console.", FCVAR_DONTRECORD); diff --git a/primedev/logging/sourceconsole.h b/primedev/logging/sourceconsole.h index 215dae1a3..35cc1723c 100644 --- a/primedev/logging/sourceconsole.h +++ b/primedev/logging/sourceconsole.h @@ -61,8 +61,6 @@ class CGameConsole CConsoleDialog* m_pConsole; }; -extern SourceInterface* g_pSourceGameConsole; - // spdlog logger class SourceConsoleSink : public CustomSink { diff --git a/primedev/masterserver/masterserver.cpp b/primedev/masterserver/masterserver.cpp index e7bb11329..56ab608d2 100644 --- a/primedev/masterserver/masterserver.cpp +++ b/primedev/masterserver/masterserver.cpp @@ -992,7 +992,12 @@ void ConCommand_ns_fetchservers(const CCommand& args) g_pMasterServerManager->RequestServerList(); } -MasterServerManager::MasterServerManager() : m_pendingConnectionInfo {}, m_sOwnServerId {""}, m_sOwnClientAuthToken {""} {} +MasterServerManager::MasterServerManager() + : m_pendingConnectionInfo {} + , m_sOwnServerId {""} + , m_sOwnClientAuthToken {""} +{ +} ON_DLL_LOAD_RELIESON("engine.dll", MasterServer, (ConCommand, ServerPresence), (CModule module)) { diff --git a/primedev/materialsystem/cmaterialglue.h b/primedev/materialsystem/cmaterialglue.h new file mode 100644 index 000000000..1738a91ab --- /dev/null +++ b/primedev/materialsystem/cmaterialglue.h @@ -0,0 +1,47 @@ +#pragma once + +#include "materialsystem/cshaderglue.h" + +class CMaterialGlue +{ +public: + void* m_pVFTable; + char m_unk[8]; + + uint64_t m_GUID; + + const char* m_pszName; + const char* m_pszSurfaceProp; + const char* m_pszSurfaceProp2; + + CMaterialGlue* m_pDepthShadow; + CMaterialGlue* m_pDepthPrepass; + CMaterialGlue* m_pDepthVSM; + CMaterialGlue* m_pColPass; + + char gap_50[64]; + + CShaderGlue* m_pShaderGlue; + void** m_pTextureHandles; + void** m_pStreamingTextures; + int16_t m_iStreamingTextureCount; + uint8_t m_iSamplersIndices[4]; + int16_t m_iUnknown0; + char gap_B0[12]; + + int16_t aword_BC[2]; + int32_t flags2; + int32_t flags3; + int16_t m_iWidth; + int16_t m_iHeight; + int16_t m_iUnknown1; + int16_t m_iUnknown2; + + void** m_pUnkD3D11Ptr; + void* m_pD3D11Buffer; + void* qword_E0; + void* pointer_E8; + + int32_t dword_F0; + char gap_F4[12]; +}; diff --git a/primedev/materialsystem/cshaderglue.h b/primedev/materialsystem/cshaderglue.h new file mode 100644 index 000000000..8194f1fb7 --- /dev/null +++ b/primedev/materialsystem/cshaderglue.h @@ -0,0 +1,7 @@ +#pragma once + +class CShaderGlue +{ +public: + void* vftable; +}; diff --git a/primedev/mods/autodownload/moddownloader.cpp b/primedev/mods/autodownload/moddownloader.cpp index feaa08ea9..c20a3adbc 100644 --- a/primedev/mods/autodownload/moddownloader.cpp +++ b/primedev/mods/autodownload/moddownloader.cpp @@ -55,6 +55,8 @@ size_t WriteToString(void* ptr, size_t size, size_t count, void* stream) void ModDownloader::FetchModsListFromAPI() { + modState.state = MANIFESTO_FETCHING; + std::thread requestThread( [this]() { @@ -63,6 +65,9 @@ void ModDownloader::FetchModsListFromAPI() rapidjson::Document verifiedModsJson; std::string url = modsListUrl; + // Empty verified mods manifesto + verifiedMods = {}; + curl_global_init(CURL_GLOBAL_ALL); easyhandle = curl_easy_init(); std::string readBuffer; @@ -75,7 +80,12 @@ void ModDownloader::FetchModsListFromAPI() curl_easy_setopt(easyhandle, CURLOPT_WRITEDATA, &readBuffer); curl_easy_setopt(easyhandle, CURLOPT_WRITEFUNCTION, WriteToString); result = curl_easy_perform(easyhandle); - ScopeGuard cleanup([&] { curl_easy_cleanup(easyhandle); }); + ScopeGuard cleanup( + [&] + { + curl_easy_cleanup(easyhandle); + modState.state = DOWNLOADING; + }); if (result == CURLcode::CURLE_OK) { @@ -93,23 +103,23 @@ void ModDownloader::FetchModsListFromAPI() for (auto i = verifiedModsJson.MemberBegin(); i != verifiedModsJson.MemberEnd(); ++i) { // Format testing - if (!i->value.HasMember("DependencyPrefix") || !i->value.HasMember("Versions")) + if (!i->value.HasMember("Repository") || !i->value.HasMember("Versions")) { spdlog::warn("Verified mods manifesto format is unrecognized, skipping loading."); return; } std::string name = i->name.GetString(); - std::string dependency = i->value["DependencyPrefix"].GetString(); - std::unordered_map modVersions; + rapidjson::Value& versions = i->value["Versions"]; assert(versions.IsArray()); for (auto& attribute : versions.GetArray()) { assert(attribute.IsObject()); // Format testing - if (!attribute.HasMember("Version") || !attribute.HasMember("Checksum")) + if (!attribute.HasMember("Version") || !attribute.HasMember("Checksum") || !attribute.HasMember("DownloadLink") || + !attribute.HasMember("Platform")) { spdlog::warn("Verified mods manifesto format is unrecognized, skipping loading."); return; @@ -117,10 +127,14 @@ void ModDownloader::FetchModsListFromAPI() std::string version = attribute["Version"].GetString(); std::string checksum = attribute["Checksum"].GetString(); - modVersions.insert({version, {.checksum = checksum}}); + std::string downloadLink = attribute["DownloadLink"].GetString(); + std::string platformValue = attribute["Platform"].GetString(); + VerifiedModPlatform platform = + platformValue.compare("thunderstore") == 0 ? VerifiedModPlatform::Thunderstore : VerifiedModPlatform::Unknown; + modVersions.insert({version, {.checksum = checksum, .downloadLink = downloadLink, .platform = platform}}); } - VerifiedModDetails modConfig = {.dependencyPrefix = dependency, .versions = modVersions}; + VerifiedModDetails modConfig = {.versions = modVersions}; verifiedMods.insert({name, modConfig}); spdlog::info("==> Loaded configuration for mod \"" + name + "\""); } @@ -154,13 +168,10 @@ int ModDownloader::ModFetchingProgressCallback( return 0; } -std::optional ModDownloader::FetchModFromDistantStore(std::string_view modName, std::string_view modVersion) +std::optional ModDownloader::FetchModFromDistantStore(std::string_view modName, VerifiedModVersion version) { - // Retrieve mod prefix from local mods list, or use mod name as mod prefix if bypass flag is set - std::string modPrefix = strstr(GetCommandLineA(), VERIFICATION_FLAG) ? modName.data() : verifiedMods[modName.data()].dependencyPrefix; - // Build archive distant URI - std::string archiveName = std::format("{}-{}.zip", modPrefix, modVersion.data()); - std::string url = STORE_URL + archiveName; + std::string url = version.downloadLink; + std::string archiveName = fs::path(url).filename().generic_string(); spdlog::info(std::format("Fetching mod archive from {}", url)); // Download destination @@ -380,7 +391,7 @@ int GetModArchiveSize(unzFile file, unz_global_info64 info) return totalSize; } -void ModDownloader::ExtractMod(fs::path modPath) +void ModDownloader::ExtractMod(fs::path modPath, VerifiedModPlatform platform) { unzFile file; std::string name; @@ -418,6 +429,14 @@ void ModDownloader::ExtractMod(fs::path modPath) modState.total = GetModArchiveSize(file, gi); modState.progress = 0; + // Right now, we only know how to extract Thunderstore mods + if (platform != VerifiedModPlatform::Thunderstore) + { + spdlog::error("Failed extracting mod from unknown platform (value: {}).", platform); + modState.state = UNKNOWN_PLATFORM; + return; + } + // Mod directory name (removing the ".zip" fom the archive name) name = modPath.filename().string(); name = name.substr(0, name.length() - 4); @@ -588,8 +607,9 @@ void ModDownloader::DownloadMod(std::string modName, std::string modVersion) }); // Download mod archive - std::string expectedHash = verifiedMods[modName].versions[modVersion].checksum; - std::optional fetchingResult = FetchModFromDistantStore(std::string_view(modName), std::string_view(modVersion)); + VerifiedModVersion fullVersion = verifiedMods[modName].versions[modVersion]; + std::string expectedHash = fullVersion.checksum; + std::optional fetchingResult = FetchModFromDistantStore(std::string_view(modName), fullVersion); if (!fetchingResult.has_value()) { spdlog::error("Something went wrong while fetching archive, aborting."); @@ -605,7 +625,7 @@ void ModDownloader::DownloadMod(std::string modName, std::string modVersion) } // Extract downloaded mod archive - ExtractMod(archiveLocation); + ExtractMod(archiveLocation, fullVersion.platform); }); requestThread.detach(); @@ -614,7 +634,12 @@ void ModDownloader::DownloadMod(std::string modName, std::string modVersion) ON_DLL_LOAD_RELIESON("engine.dll", ModDownloader, (ConCommand), (CModule module)) { g_pModDownloader = new ModDownloader(); +} + +ADD_SQFUNC("void", NSFetchVerifiedModsManifesto, "", "", ScriptContext::SERVER | ScriptContext::CLIENT | ScriptContext::UI) +{ g_pModDownloader->FetchModsListFromAPI(); + return SQRESULT_NULL; } ADD_SQFUNC( diff --git a/primedev/mods/autodownload/moddownloader.h b/primedev/mods/autodownload/moddownloader.h index 0d851e2fe..c7a88c19a 100644 --- a/primedev/mods/autodownload/moddownloader.h +++ b/primedev/mods/autodownload/moddownloader.h @@ -5,17 +5,22 @@ class ModDownloader private: const char* VERIFICATION_FLAG = "-disablemodverification"; const char* CUSTOM_MODS_URL_FLAG = "-customverifiedurl="; - const char* STORE_URL = "https://gcdn.thunderstore.io/live/repository/packages/"; const char* DEFAULT_MODS_LIST_URL = "https://raw.githubusercontent.com/R2Northstar/VerifiedMods/main/verified-mods.json"; char* modsListUrl; + enum class VerifiedModPlatform + { + Unknown, + Thunderstore + }; struct VerifiedModVersion { std::string checksum; + std::string downloadLink; + VerifiedModPlatform platform; }; struct VerifiedModDetails { - std::string dependencyPrefix; std::unordered_map versions = {}; }; std::unordered_map verifiedMods = {}; @@ -45,7 +50,7 @@ class ModDownloader * @param modVersion version of the mod to be downloaded * @returns location of the downloaded archive */ - std::optional FetchModFromDistantStore(std::string_view modName, std::string_view modVersion); + std::optional FetchModFromDistantStore(std::string_view modName, VerifiedModVersion modVersion); /** * Tells if a mod archive has not been corrupted. @@ -65,12 +70,13 @@ class ModDownloader * Extracts a mod archive to the game folder. * * This extracts a downloaded mod archive from its original location to the - * current game profile, in the remote mods folder. + * current game profile; the install folder is defined by the platform parameter. * * @param modPath location of the downloaded archive + * @param platform origin of the downloaded archive * @returns nothing */ - void ExtractMod(fs::path modPath); + void ExtractMod(fs::path modPath, VerifiedModPlatform platform); public: ModDownloader(); @@ -116,6 +122,8 @@ class ModDownloader enum ModInstallState { + MANIFESTO_FETCHING, + // Normal installation process DOWNLOADING, CHECKSUMING, @@ -129,7 +137,8 @@ class ModDownloader MOD_FETCHING_FAILED, MOD_CORRUPTED, // Downloaded archive checksum does not match verified hash NO_DISK_SPACE_AVAILABLE, - NOT_FOUND // Mod is not currently being auto-downloaded + NOT_FOUND, // Mod is not currently being auto-downloaded + UNKNOWN_PLATFORM }; struct MOD_STATE diff --git a/primedev/mods/modmanager.cpp b/primedev/mods/modmanager.cpp index 68f9bd0f9..52fc6e8b1 100644 --- a/primedev/mods/modmanager.cpp +++ b/primedev/mods/modmanager.cpp @@ -819,7 +819,7 @@ void ModManager::LoadMods() modVpk.m_sVpkPath = (file.path().parent_path() / vpkName).string(); if (m_bHasLoadedMods && modVpk.m_bAutoLoad) - (*g_pFilesystem)->m_vtable->MountVPK(*g_pFilesystem, vpkName.c_str()); + g_pFilesystem->m_vtable->MountVPK(g_pFilesystem, vpkName.c_str()); } } } @@ -866,19 +866,57 @@ void ModManager::LoadMods() if (fs::is_regular_file(file) && file.path().extension() == ".rpak") { std::string pakName(file.path().filename().string()); + ModRpakEntry& modPak = mod.Rpaks.emplace_back(mod); - ModRpakEntry& modPak = mod.Rpaks.emplace_back(); - modPak.m_bAutoLoad = - !bUseRpakJson || (dRpakJson.HasMember("Preload") && dRpakJson["Preload"].IsObject() && - dRpakJson["Preload"].HasMember(pakName) && dRpakJson["Preload"][pakName].IsTrue()); + modPak.m_pakName = pakName; - // postload things - if (!bUseRpakJson || - (dRpakJson.HasMember("Postload") && dRpakJson["Postload"].IsObject() && dRpakJson["Postload"].HasMember(pakName))) - modPak.m_sLoadAfterPak = dRpakJson["Postload"][pakName].GetString(); + if (!bUseRpakJson) + { + spdlog::warn("Mod {} contains rpaks without valid rpak.json, rpaks might not be loaded", mod.Name); + } + else + { + modPak.m_preload = + (dRpakJson.HasMember("Preload") && dRpakJson["Preload"].IsObject() && dRpakJson["Preload"].HasMember(pakName) && + dRpakJson["Preload"][pakName].IsTrue()); + + // only one load method can be used for an rpak. + if (modPak.m_preload) + goto REGISTER_STARPAK; - modPak.m_sPakName = pakName; + // postload things + if (dRpakJson.HasMember("Postload") && dRpakJson["Postload"].IsObject() && dRpakJson["Postload"].HasMember(pakName)) + { + modPak.m_dependentPakHash = STR_HASH(dRpakJson["Postload"][pakName].GetString()); + // only one load method can be used for an rpak. + goto REGISTER_STARPAK; + } + + // this is the only bit of rpak.json that isn't really deprecated. Even so, it will be moved over to the mod.json + // eventually + if (dRpakJson.HasMember(pakName)) + { + if (!dRpakJson[pakName].IsString()) + { + spdlog::error("Mod {} has invalid rpak.json. Rpak entries must be strings.", mod.Name); + continue; + } + + std::string loadStr = dRpakJson[pakName].GetString(); + try + { + modPak.m_loadRegex = std::regex(loadStr); + } + catch (...) + { + spdlog::error("Mod {} has invalid rpak.json. Malformed regex \"{}\" for {}", mod.Name, loadStr, pakName); + return; + } + } + } + + REGISTER_STARPAK: // read header of file and get the starpak paths // this is done here as opposed to on starpak load because multiple rpaks can load a starpak // and there is seemingly no good way to tell which rpak is causing the load of a starpak :/ @@ -918,12 +956,11 @@ void ModManager::LoadMods() } } } - - // not using atm because we need to resolve path to rpak - // if (m_hasLoadedMods && modPak.m_bAutoLoad) - // g_pPakLoadManager->LoadPakAsync(pakName.c_str()); } } + + if (g_pPakLoadManager != nullptr) + g_pPakLoadManager->TrackModPaks(mod); } // read keyvalues paths @@ -1051,6 +1088,8 @@ void ModManager::UnloadMods() fs::remove_all(GetCompiledAssetsPath()); g_CustomAudioManager.ClearAudioOverrides(); + if (g_pPakLoadManager != nullptr) + g_pPakLoadManager->UnloadAllModPaks(); if (!m_bHasEnabledModsCfg) m_EnabledModsCfg.SetObject(); diff --git a/primedev/mods/modmanager.h b/primedev/mods/modmanager.h index 95a8fe121..7859d6184 100644 --- a/primedev/mods/modmanager.h +++ b/primedev/mods/modmanager.h @@ -8,6 +8,7 @@ #include #include #include +#include namespace fs = std::filesystem; @@ -19,6 +20,8 @@ const std::string COMPILED_ASSETS_SUFFIX = "\\runtime\\compiled"; const std::set MODS_BLACKLIST = {"Mod Settings"}; +class Mod; + struct ModConVar { public: @@ -71,9 +74,22 @@ struct ModVPKEntry struct ModRpakEntry { public: - bool m_bAutoLoad; - std::string m_sPakName; - std::string m_sLoadAfterPak; + ModRpakEntry(Mod& parent) + : m_parent(parent) + , m_loadRegex("^thisMatchesNothing^") // discord couldnt give me a funny string + { + } + + Mod& m_parent; + std::string m_pakName; + std::regex m_loadRegex; + + // these exist purely for backwards compatibility, i don't really like them anymore + + // Preload, loads before the first rpak is loaded + bool m_preload = false; + // Postload, this rpak depends on an rpak with this hash + size_t m_dependentPakHash; }; class Mod @@ -120,11 +136,12 @@ class Mod std::string Pdiff; // only need one per mod std::vector Rpaks; - std::unordered_map - RpakAliases; // paks we alias to other rpaks, e.g. to load sp_crashsite paks on the map mp_crashsite - std::vector StarpakPaths; // starpaks that this mod contains + // paks we alias to other rpaks, e.g. to load sp_crashsite paks on the map mp_crashsite + std::unordered_map RpakAliases; + // starpaks that this mod contains // there seems to be no nice way to get the rpak that is causing the load of a starpak? // hashed with STR_HASH + std::vector StarpakPaths; std::unordered_map DependencyConstants; std::vector PluginDependencyConstants; diff --git a/primedev/mods/modsavefiles.cpp b/primedev/mods/modsavefiles.cpp index 68e33864b..13239c99f 100644 --- a/primedev/mods/modsavefiles.cpp +++ b/primedev/mods/modsavefiles.cpp @@ -537,7 +537,7 @@ ADD_SQFUNC("int", NSGetTotalSpaceRemaining, "", "", ScriptContext::CLIENT | Scri // ok, I'm just gonna explain what the fuck is going on here because this // is the pinnacle of my stupidity and I do not want to touch this ever // again, yet someone will eventually have to maintain this. -template std::string EncodeJSON(HSquirrelVM* sqvm) +template std::string EncodeJSON(HSQUIRRELVM sqvm) { // new rapidjson rapidjson_document doc; diff --git a/primedev/plugins/interfaces/interface.cpp b/primedev/plugins/interfaces/interface.cpp index 4c006f2c7..bc9005421 100644 --- a/primedev/plugins/interfaces/interface.cpp +++ b/primedev/plugins/interfaces/interface.cpp @@ -3,7 +3,8 @@ InterfaceReg* s_pInterfaceRegs; -InterfaceReg::InterfaceReg(InstantiateInterfaceFn fn, const char* pName) : m_pName(pName) +InterfaceReg::InterfaceReg(InstantiateInterfaceFn fn, const char* pName) + : m_pName(pName) { m_CreateFn = fn; m_pNext = s_pInterfaceRegs; diff --git a/primedev/plugins/plugins.cpp b/primedev/plugins/plugins.cpp index 03dd2c9e2..92be9d5cf 100644 --- a/primedev/plugins/plugins.cpp +++ b/primedev/plugins/plugins.cpp @@ -22,7 +22,8 @@ bool isValidSquirrelIdentifier(std::string s) return true; } -Plugin::Plugin(std::string path) : m_location(path) +Plugin::Plugin(std::string path) + : m_location(path) { HMODULE pluginModule = GetModuleHandleA(path.c_str()); @@ -218,7 +219,6 @@ void Plugin::OnSqvmCreated(CSquirrelVM* sqvm) const void Plugin::OnSqvmDestroying(CSquirrelVM* sqvm) const { - NS::log::PLUGINSYS->info("destroying sqvm {}", sqvm->vmContext); m_callbacks->OnSqvmDestroying(sqvm); } diff --git a/primedev/scripts/scriptdatatables.cpp b/primedev/scripts/scriptdatatables.cpp index 5e685b48c..b3c599212 100644 --- a/primedev/scripts/scriptdatatables.cpp +++ b/primedev/scripts/scriptdatatables.cpp @@ -44,7 +44,7 @@ struct Datatable ConVar* Cvar_ns_prefer_datatable_from_disk; -template Datatable* (*SQ_GetDatatableInternal)(HSquirrelVM* sqvm); +template Datatable* (*SQ_GetDatatableInternal)(HSQUIRRELVM sqvm); struct CSVData { @@ -70,10 +70,11 @@ REPLACE_SQFUNC(GetDataTable, (ScriptContext::UI | ScriptContext::CLIENT | Script g_pSquirrel->raiseerror(sqvm, fmt::format("Asset \"{}\" doesn't start with \"datatable/\"", pAssetName).c_str()); return SQRESULT_ERROR; } - else if (!Cvar_ns_prefer_datatable_from_disk->GetBool() && g_pPakLoadManager->LoadFile(pAssetName)) + else if (!Cvar_ns_prefer_datatable_from_disk->GetBool() && g_pPakLoadManager->OpenFile(pAssetName)) + { return g_pSquirrel->m_funcOriginals["GetDataTable"](sqvm); - // either we prefer disk datatables, or we're loading a datatable that wasn't found in rpak - else + } + else // either we prefer disk datatables, or we're loading a datatable that wasn't found in rpak { std::string sAssetPath(fmt::format("scripts/{}", pAssetName)); @@ -96,7 +97,7 @@ REPLACE_SQFUNC(GetDataTable, (ScriptContext::UI | ScriptContext::CLIENT | Script diskAssetPath /= fs::path(pAssetName); std::string sDiskAssetPath(diskAssetPath.string()); - if ((*g_pFilesystem)->m_vtable2->FileExists(&(*g_pFilesystem)->m_vtable2, sDiskAssetPath.c_str(), "GAME")) + if (g_pFilesystem->m_vtable2->FileExists(&g_pFilesystem->m_vtable2, sDiskAssetPath.c_str(), "GAME")) { std::string sTableCSV = ReadVPKFile(sDiskAssetPath.c_str()); if (!sTableCSV.size()) @@ -223,7 +224,7 @@ REPLACE_SQFUNC(GetDataTable, (ScriptContext::UI | ScriptContext::CLIENT | Script return SQRESULT_NOTNULL; } // the file doesn't exist on disk, check rpak if we haven't already - else if (Cvar_ns_prefer_datatable_from_disk->GetBool() && g_pPakLoadManager->LoadFile(pAssetName)) + else if (Cvar_ns_prefer_datatable_from_disk->GetBool() && g_pPakLoadManager->OpenFile(pAssetName)) return g_pSquirrel->m_funcOriginals["GetDataTable"](sqvm); // the file doesn't exist at all, error else @@ -750,7 +751,7 @@ std::string DataTableToString(Datatable* datatable) void DumpDatatable(const char* pDatatablePath) { - Datatable* pDatatable = (Datatable*)g_pPakLoadManager->LoadFile(pDatatablePath); + Datatable* pDatatable = (Datatable*)g_pPakLoadManager->OpenFile(pDatatablePath); if (!pDatatable) { spdlog::error("couldn't load datatable {} (rpak containing it may not be loaded?)", pDatatablePath); @@ -852,12 +853,12 @@ void ConCommand_dump_datatables(const CCommand& args) ON_DLL_LOAD_RELIESON("server.dll", ServerScriptDatatables, ServerSquirrel, (CModule module)) { - SQ_GetDatatableInternal = module.Offset(0x1250f0).RCast(); + SQ_GetDatatableInternal = module.Offset(0x1250f0).RCast(); } ON_DLL_LOAD_RELIESON("client.dll", ClientScriptDatatables, ClientSquirrel, (CModule module)) { - SQ_GetDatatableInternal = module.Offset(0x1C9070).RCast(); + SQ_GetDatatableInternal = module.Offset(0x1C9070).RCast(); SQ_GetDatatableInternal = SQ_GetDatatableInternal; } diff --git a/primedev/scripts/scripthttprequesthandler.cpp b/primedev/scripts/scripthttprequesthandler.cpp index 69828a5a2..f45e83f0d 100644 --- a/primedev/scripts/scripthttprequesthandler.cpp +++ b/primedev/scripts/scripthttprequesthandler.cpp @@ -450,7 +450,7 @@ template int HttpRequestHandler::MakeHttpRequest(const H // int NS_InternalMakeHttpRequest(int method, string baseUrl, table headers, table queryParams, // string contentType, string body, int timeout, string userAgent) -template SQRESULT SQ_InternalMakeHttpRequest(HSquirrelVM* sqvm) +template SQRESULT SQ_InternalMakeHttpRequest(HSQUIRRELVM sqvm) { if (!g_httpRequestHandler || !g_httpRequestHandler->IsRunning()) { @@ -475,7 +475,7 @@ template SQRESULT SQ_InternalMakeHttpRequest(HSquirrelVM SQTable* headerTable = sqvm->_stackOfCurrentFunction[3]._VAL.asTable; for (int idx = 0; idx < headerTable->_numOfNodes; ++idx) { - tableNode* node = &headerTable->_nodes[idx]; + SQTable::_HashNode* node = &headerTable->_nodes[idx]; if (node->key._Type == OT_STRING && node->val._Type == OT_ARRAY) { @@ -497,7 +497,7 @@ template SQRESULT SQ_InternalMakeHttpRequest(HSquirrelVM SQTable* queryTable = sqvm->_stackOfCurrentFunction[4]._VAL.asTable; for (int idx = 0; idx < queryTable->_numOfNodes; ++idx) { - tableNode* node = &queryTable->_nodes[idx]; + SQTable::_HashNode* node = &queryTable->_nodes[idx]; if (node->key._Type == OT_STRING && node->val._Type == OT_ARRAY) { @@ -527,14 +527,14 @@ template SQRESULT SQ_InternalMakeHttpRequest(HSquirrelVM } // bool NSIsHttpEnabled() -template SQRESULT SQ_IsHttpEnabled(HSquirrelVM* sqvm) +template SQRESULT SQ_IsHttpEnabled(HSQUIRRELVM sqvm) { g_pSquirrel->pushbool(sqvm, !IsHttpDisabled()); return SQRESULT_NOTNULL; } // bool NSIsLocalHttpAllowed() -template SQRESULT SQ_IsLocalHttpAllowed(HSquirrelVM* sqvm) +template SQRESULT SQ_IsLocalHttpAllowed(HSQUIRRELVM sqvm) { g_pSquirrel->pushbool(sqvm, IsLocalHttpAllowed()); return SQRESULT_NOTNULL; diff --git a/primedev/scripts/scripthttprequesthandler.h b/primedev/scripts/scripthttprequesthandler.h index f3921f4e7..72f719ec3 100644 --- a/primedev/scripts/scripthttprequesthandler.h +++ b/primedev/scripts/scripthttprequesthandler.h @@ -107,10 +107,7 @@ class HttpRequestHandler void StopHttpRequestHandler(); // Whether or not this http request handler is currently running. - bool IsRunning() const - { - return m_bIsHttpRequestHandlerRunning; - } + bool IsRunning() const { return m_bIsHttpRequestHandlerRunning; } /** * Creates a new thread to execute an HTTP request. diff --git a/primedev/scripts/scriptjson.cpp b/primedev/scripts/scriptjson.cpp index 8959bf471..91553ae3d 100644 --- a/primedev/scripts/scriptjson.cpp +++ b/primedev/scripts/scriptjson.cpp @@ -9,8 +9,8 @@ #undef GetObject // fuck microsoft developers #endif -template void -DecodeJsonArray(HSquirrelVM* sqvm, rapidjson::GenericValue, rapidjson::MemoryPoolAllocator>* arr) +template +void DecodeJsonArray(HSQUIRRELVM sqvm, rapidjson::GenericValue, rapidjson::MemoryPoolAllocator>* arr) { g_pSquirrel->newarray(sqvm, 0); @@ -48,8 +48,8 @@ DecodeJsonArray(HSquirrelVM* sqvm, rapidjson::GenericValue } } -template void -DecodeJsonTable(HSquirrelVM* sqvm, rapidjson::GenericValue, rapidjson::MemoryPoolAllocator>* obj) +template +void DecodeJsonTable(HSQUIRRELVM sqvm, rapidjson::GenericValue, rapidjson::MemoryPoolAllocator>* obj) { g_pSquirrel->newtable(sqvm); @@ -107,7 +107,7 @@ template void EncodeJSONTable( { for (int i = 0; i < table->_numOfNodes; i++) { - tableNode* node = &table->_nodes[i]; + SQTable::_HashNode* node = &table->_nodes[i]; if (node->key._Type == OT_STRING) { rapidjson::GenericValue, rapidjson::MemoryPoolAllocator> newObj(rapidjson::kObjectType); @@ -240,7 +240,7 @@ ADD_SQFUNC( doc.SetObject(); // temp until this is just the func parameter type - HSquirrelVM* vm = (HSquirrelVM*)sqvm; + HSQUIRRELVM vm = (HSQUIRRELVM)sqvm; SQTable* table = vm->_stackOfCurrentFunction[1]._VAL.asTable; EncodeJSONTable(table, &doc, doc.GetAllocator()); diff --git a/primedev/scripts/scriptjson.h b/primedev/scripts/scriptjson.h index b747106b2..f766e3f00 100644 --- a/primedev/scripts/scriptjson.h +++ b/primedev/scripts/scriptjson.h @@ -10,4 +10,4 @@ template void EncodeJSONTable( rapidjson::MemoryPoolAllocator& allocator); template void -DecodeJsonTable(HSquirrelVM* sqvm, rapidjson::GenericValue, rapidjson::MemoryPoolAllocator>* obj); +DecodeJsonTable(HSQUIRRELVM sqvm, rapidjson::GenericValue, rapidjson::MemoryPoolAllocator>* obj); diff --git a/primedev/server/ai_navmesh.h b/primedev/server/ai_navmesh.h index 65529f7a2..c3339110a 100644 --- a/primedev/server/ai_navmesh.h +++ b/primedev/server/ai_navmesh.h @@ -207,28 +207,16 @@ struct dtPoly Vector3 org; // /// Sets the user defined area id. [Limit: < #DT_MAX_AREAS] - inline void setArea(unsigned char a) - { - areaAndtype = (areaAndtype & 0xc0) | (a & 0x3f); - } + inline void setArea(unsigned char a) { areaAndtype = (areaAndtype & 0xc0) | (a & 0x3f); } /// Sets the polygon type. (See: #dtPolyTypes.) - inline void setType(unsigned char t) - { - areaAndtype = (areaAndtype & 0x3f) | (t << 6); - } + inline void setType(unsigned char t) { areaAndtype = (areaAndtype & 0x3f) | (t << 6); } /// Gets the user defined area id. - inline unsigned char getArea() const - { - return areaAndtype & 0x3f; - } + inline unsigned char getArea() const { return areaAndtype & 0x3f; } /// Gets the polygon type. (See: #dtPolyTypes) - inline unsigned char getType() const - { - return areaAndtype >> 6; - } + inline unsigned char getType() const { return areaAndtype >> 6; } }; /// Defines a link between polygons. diff --git a/primedev/squirrel/squirrel.cpp b/primedev/squirrel/squirrel.cpp index f6df0c5f3..4141d04df 100644 --- a/primedev/squirrel/squirrel.cpp +++ b/primedev/squirrel/squirrel.cpp @@ -11,6 +11,8 @@ #include "ns_version.h" #include "core/vanilla.h" +#include "vscript/vscript.h" + #include AUTOHOOK_INIT() @@ -253,8 +255,8 @@ template void SquirrelManager::ExecuteCode(cons spdlog::info("Executing {} script code {} ", GetContextName(context), pCode); - std::string strCode(pCode); - CompileBufferState bufferState = CompileBufferState(strCode); + // NOTE: SQBufferState doesn't strdup pCode! + SQBufferState bufferState = SQBufferState(pCode); SQRESULT compileResult = compilebuffer(&bufferState, "console"); spdlog::info("sq_compilebuffer returned {}", PrintSQRESULT.at(compileResult)); @@ -309,14 +311,14 @@ template void SquirrelManager::AddFuncOverride( } // hooks -bool IsUIVM(ScriptContext context, HSquirrelVM* pSqvm) +bool IsUIVM(ScriptContext context, HSQUIRRELVM pSqvm) { NOTE_UNUSED(context); return ScriptContext(pSqvm->sharedState->cSquirrelVM->vmContext) == ScriptContext::UI; } -template void* (*sq_compiler_create)(HSquirrelVM* sqvm, void* a2, void* a3, SQBool bShouldThrowError); -template void* __fastcall sq_compiler_createHook(HSquirrelVM* sqvm, void* a2, void* a3, SQBool bShouldThrowError) +template void* (*sq_compiler_create)(HSQUIRRELVM sqvm, void* a2, void* a3, SQBool bShouldThrowError); +template void* __fastcall sq_compiler_createHook(HSQUIRRELVM sqvm, void* a2, void* a3, SQBool bShouldThrowError) { // store whether errors generated from this compile should be fatal if (IsUIVM(context, sqvm)) @@ -327,8 +329,8 @@ template void* __fastcall sq_compiler_createHook(HSquirr return sq_compiler_create(sqvm, a2, a3, bShouldThrowError); } -template SQInteger (*SQPrint)(HSquirrelVM* sqvm, const char* fmt); -template SQInteger SQPrintHook(HSquirrelVM* sqvm, const char* fmt, ...) +template SQInteger (*SQPrint)(HSQUIRRELVM sqvm, const char* fmt); +template SQInteger SQPrintHook(HSQUIRRELVM sqvm, const char* fmt, ...) { NOTE_UNUSED(sqvm); @@ -398,9 +400,9 @@ template void __fastcall DestroyVMHook(void* a1, CSquirr spdlog::info("DestroyVM {} {}", GetContextName(realContext), (void*)sqvm); } -template void (*SQCompileError)(HSquirrelVM* sqvm, const char* error, const char* file, int line, int column); +template void (*SQCompileError)(HSQUIRRELVM sqvm, const char* error, const char* file, int line, int column); template -void __fastcall ScriptCompileErrorHook(HSquirrelVM* sqvm, const char* error, const char* file, int line, int column) +void __fastcall ScriptCompileErrorHook(HSQUIRRELVM sqvm, const char* error, const char* file, int line, int column) { bool bIsFatalError = g_pSquirrel->m_bFatalCompilationErrors; ScriptContext realContext = context; // ui and client use the same function so we use this for prints @@ -544,7 +546,7 @@ template void ConCommand_script(const CCommand& args) g_pSquirrel->ExecuteCode(args.ArgS()); } -template SQRESULT SQ_StubbedFunc(HSquirrelVM* sqvm) +template SQRESULT SQ_StubbedFunc(HSQUIRRELVM sqvm) { SQStackInfos si; g_pSquirrel->sq_stackinfos(sqvm, 0, si); @@ -707,6 +709,7 @@ ON_DLL_LOAD_RELIESON("client.dll", ClientSquirrel, ConCommand, (CModule module)) g_pSquirrel->__sq_GetEntityConstant_CBaseEntity = module.Offset(0x3E49B0).RCast(); g_pSquirrel->__sq_getentityfrominstance = module.Offset(0x114F0).RCast(); + g_pSquirrel->__sq_createscriptinstance = module.Offset(0xC20E0).RCast(); g_pSquirrel->__sq_GetEntityConstant_CBaseEntity = g_pSquirrel->__sq_GetEntityConstant_CBaseEntity; g_pSquirrel->__sq_getentityfrominstance = g_pSquirrel->__sq_getentityfrominstance; @@ -800,6 +803,7 @@ ON_DLL_LOAD_RELIESON("server.dll", ServerSquirrel, ConCommand, (CModule module)) g_pSquirrel->__sq_GetEntityConstant_CBaseEntity = module.Offset(0x418AF0).RCast(); g_pSquirrel->__sq_getentityfrominstance = module.Offset(0x1E920).RCast(); + g_pSquirrel->__sq_createscriptinstance = module.Offset(0x43F2F0).RCast(); g_pSquirrel->logger = NS::log::SCRIPT_SV; // Message buffer stuff diff --git a/primedev/squirrel/squirrel.h b/primedev/squirrel/squirrel.h index d747bce39..ae6e80c93 100644 --- a/primedev/squirrel/squirrel.h +++ b/primedev/squirrel/squirrel.h @@ -117,6 +117,7 @@ class SquirrelManagerBase sq_getfunctionType __sq_getfunction; sq_getentityfrominstanceType __sq_getentityfrominstance; + sq_createscriptinstanceType __sq_createscriptinstance; sq_GetEntityConstantType __sq_GetEntityConstant_CBaseEntity; sq_pushnewstructinstanceType __sq_pushnewstructinstance; @@ -125,138 +126,75 @@ class SquirrelManagerBase #pragma endregion #pragma region SQVM func wrappers - inline void defconst(CSquirrelVM* sqvm, const SQChar* pName, int nValue) - { - __sq_defconst(sqvm, pName, nValue); - } + inline void defconst(CSquirrelVM* sqvm, const SQChar* pName, int nValue) { __sq_defconst(sqvm, pName, nValue); } inline SQRESULT - compilebuffer(CompileBufferState* bufferState, const SQChar* bufferName = "unnamedbuffer", const SQBool bShouldThrowError = false) + compilebuffer(SQBufferState* bufferState, const SQChar* bufferName = "unnamedbuffer", const SQBool bShouldThrowError = false) { return __sq_compilebuffer(m_pSQVM->sqvm, bufferState, bufferName, -1, bShouldThrowError); } - inline SQRESULT _call(HSquirrelVM* sqvm, const SQInteger args) - { - return __sq_call(sqvm, args + 1, false, true); - } + inline SQRESULT _call(HSQUIRRELVM sqvm, const SQInteger args) { return __sq_call(sqvm, args + 1, false, true); } - inline SQInteger raiseerror(HSquirrelVM* sqvm, const SQChar* sError) - { - return __sq_raiseerror(sqvm, sError); - } + inline SQInteger raiseerror(HSQUIRRELVM sqvm, const SQChar* sError) { return __sq_raiseerror(sqvm, sError); } inline bool compilefile(CSquirrelVM* sqvm, const char* path, const char* name, int a4) { return __sq_compilefile(sqvm, path, name, a4); } - inline void newarray(HSquirrelVM* sqvm, const SQInteger stackpos = 0) - { - __sq_newarray(sqvm, stackpos); - } + inline void newarray(HSQUIRRELVM sqvm, const SQInteger stackpos = 0) { __sq_newarray(sqvm, stackpos); } - inline SQRESULT arrayappend(HSquirrelVM* sqvm, const SQInteger stackpos) - { - return __sq_arrayappend(sqvm, stackpos); - } + inline SQRESULT arrayappend(HSQUIRRELVM sqvm, const SQInteger stackpos) { return __sq_arrayappend(sqvm, stackpos); } - inline SQRESULT newtable(HSquirrelVM* sqvm) - { - return __sq_newtable(sqvm); - } + inline SQRESULT newtable(HSQUIRRELVM sqvm) { return __sq_newtable(sqvm); } - inline SQRESULT newslot(HSquirrelVM* sqvm, SQInteger idx, SQBool bStatic) - { - return __sq_newslot(sqvm, idx, bStatic); - } + inline SQRESULT newslot(HSQUIRRELVM sqvm, SQInteger idx, SQBool bStatic) { return __sq_newslot(sqvm, idx, bStatic); } - inline void pushroottable(HSquirrelVM* sqvm) - { - __sq_pushroottable(sqvm); - } + inline void pushroottable(HSQUIRRELVM sqvm) { __sq_pushroottable(sqvm); } - inline void pushstring(HSquirrelVM* sqvm, const SQChar* sVal, int length = -1) - { - __sq_pushstring(sqvm, sVal, length); - } + inline void pushstring(HSQUIRRELVM sqvm, const SQChar* sVal, int length = -1) { __sq_pushstring(sqvm, sVal, length); } - inline void pushinteger(HSquirrelVM* sqvm, const SQInteger iVal) - { - __sq_pushinteger(sqvm, iVal); - } + inline void pushinteger(HSQUIRRELVM sqvm, const SQInteger iVal) { __sq_pushinteger(sqvm, iVal); } - inline void pushfloat(HSquirrelVM* sqvm, const SQFloat flVal) - { - __sq_pushfloat(sqvm, flVal); - } + inline void pushfloat(HSQUIRRELVM sqvm, const SQFloat flVal) { __sq_pushfloat(sqvm, flVal); } - inline void pushbool(HSquirrelVM* sqvm, const SQBool bVal) - { - __sq_pushbool(sqvm, bVal); - } + inline void pushbool(HSQUIRRELVM sqvm, const SQBool bVal) { __sq_pushbool(sqvm, bVal); } - inline void pushasset(HSquirrelVM* sqvm, const SQChar* sVal, int length = -1) - { - __sq_pushasset(sqvm, sVal, length); - } + inline void pushasset(HSQUIRRELVM sqvm, const SQChar* sVal, int length = -1) { __sq_pushasset(sqvm, sVal, length); } - inline void pushvector(HSquirrelVM* sqvm, const Vector3 pVal) - { - __sq_pushvector(sqvm, (float*)&pVal); - } + inline void pushvector(HSQUIRRELVM sqvm, const Vector3 pVal) { __sq_pushvector(sqvm, (float*)&pVal); } - inline void pushobject(HSquirrelVM* sqvm, SQObject* obj) - { - __sq_pushobject(sqvm, obj); - } + inline void pushobject(HSQUIRRELVM sqvm, SQObject* obj) { __sq_pushobject(sqvm, obj); } - inline const SQChar* getstring(HSquirrelVM* sqvm, const SQInteger stackpos) - { - return __sq_getstring(sqvm, stackpos); - } + inline const SQChar* getstring(HSQUIRRELVM sqvm, const SQInteger stackpos) { return __sq_getstring(sqvm, stackpos); } - inline SQInteger getinteger(HSquirrelVM* sqvm, const SQInteger stackpos) - { - return __sq_getinteger(sqvm, stackpos); - } + inline SQInteger getinteger(HSQUIRRELVM sqvm, const SQInteger stackpos) { return __sq_getinteger(sqvm, stackpos); } - inline SQFloat getfloat(HSquirrelVM* sqvm, const SQInteger stackpos) - { - return __sq_getfloat(sqvm, stackpos); - } + inline SQFloat getfloat(HSQUIRRELVM sqvm, const SQInteger stackpos) { return __sq_getfloat(sqvm, stackpos); } - inline SQBool getbool(HSquirrelVM* sqvm, const SQInteger stackpos) - { - return __sq_getbool(sqvm, stackpos); - } + inline SQBool getbool(HSQUIRRELVM sqvm, const SQInteger stackpos) { return __sq_getbool(sqvm, stackpos); } - inline SQRESULT get(HSquirrelVM* sqvm, const SQInteger stackpos) - { - return __sq_get(sqvm, stackpos); - } + inline SQRESULT get(HSQUIRRELVM sqvm, const SQInteger stackpos) { return __sq_get(sqvm, stackpos); } - inline Vector3 getvector(HSquirrelVM* sqvm, const SQInteger stackpos) - { - return *(Vector3*)__sq_getvector(sqvm, stackpos); - } + inline Vector3 getvector(HSQUIRRELVM sqvm, const SQInteger stackpos) { return *(Vector3*)__sq_getvector(sqvm, stackpos); } - inline int sq_getfunction(HSquirrelVM* sqvm, const char* name, SQObject* returnObj, const char* signature) + inline int sq_getfunction(HSQUIRRELVM sqvm, const char* name, SQObject* returnObj, const char* signature) { return __sq_getfunction(sqvm, name, returnObj, signature); } - inline SQRESULT getasset(HSquirrelVM* sqvm, const SQInteger stackpos, const char** result) + inline SQRESULT getasset(HSQUIRRELVM sqvm, const SQInteger stackpos, const char** result) { return __sq_getasset(sqvm, stackpos, result); } - inline long long sq_stackinfos(HSquirrelVM* sqvm, int level, SQStackInfos& out) + inline long long sq_stackinfos(HSQUIRRELVM sqvm, int level, SQStackInfos& out) { return __sq_stackinfos(sqvm, level, &out, sqvm->_callstacksize); } - inline Mod* getcallingmod(HSquirrelVM* sqvm, int depth = 0) + inline Mod* getcallingmod(HSQUIRRELVM sqvm, int depth = 0) { SQStackInfos stackInfo {}; if (1 + depth >= sqvm->_callstacksize) @@ -273,29 +211,26 @@ class SquirrelManagerBase } return nullptr; } - template inline SQRESULT getuserdata(HSquirrelVM* sqvm, const SQInteger stackpos, T* data, uint64_t* typeId) + template inline SQRESULT getuserdata(HSQUIRRELVM sqvm, const SQInteger stackpos, T* data, uint64_t* typeId) { return __sq_getuserdata(sqvm, stackpos, (void**)data, typeId); // this sometimes crashes idk } - template inline T* createuserdata(HSquirrelVM* sqvm, SQInteger size) + template inline T* createuserdata(HSQUIRRELVM sqvm, SQInteger size) { void* ret = __sq_createuserdata(sqvm, size); memset(ret, 0, size); return (T*)ret; } - inline SQRESULT setuserdatatypeid(HSquirrelVM* sqvm, const SQInteger stackpos, uint64_t typeId) + inline SQRESULT setuserdatatypeid(HSQUIRRELVM sqvm, const SQInteger stackpos, uint64_t typeId) { return __sq_setuserdatatypeid(sqvm, stackpos, typeId); } - template inline SQBool getthisentity(HSquirrelVM* sqvm, T* ppEntity) - { - return __sq_getthisentity(sqvm, (void**)ppEntity); - } + template inline SQBool getthisentity(HSQUIRRELVM sqvm, T* ppEntity) { return __sq_getthisentity(sqvm, (void**)ppEntity); } - template inline T* getentity(HSquirrelVM* sqvm, SQInteger iStackPos) + template inline T* getentity(HSQUIRRELVM sqvm, SQInteger iStackPos) { SQObject obj; __sq_getobject(sqvm, iStackPos, &obj); @@ -304,15 +239,9 @@ class SquirrelManagerBase return (T*)__sq_getentityfrominstance(m_pSQVM, &obj, __sq_GetEntityConstant_CBaseEntity()); } - inline SQRESULT pushnewstructinstance(HSquirrelVM* sqvm, const int fieldCount) - { - return __sq_pushnewstructinstance(sqvm, fieldCount); - } + inline SQRESULT pushnewstructinstance(HSQUIRRELVM sqvm, const int fieldCount) { return __sq_pushnewstructinstance(sqvm, fieldCount); } - inline SQRESULT sealstructslot(HSquirrelVM* sqvm, const int fieldIndex) - { - return __sq_sealstructslot(sqvm, fieldIndex); - } + inline SQRESULT sealstructslot(HSQUIRRELVM sqvm, const int fieldIndex) { return __sq_sealstructslot(sqvm, fieldIndex); } #pragma endregion }; @@ -404,10 +333,7 @@ template class SquirrelManager : public virtual Squirrel #pragma endregion public: - SquirrelManager() - { - m_pSQVM = nullptr; - } + SquirrelManager() { m_pSQVM = nullptr; } void VMCreated(CSquirrelVM* newSqvm); void VMDestroyed(); diff --git a/primedev/squirrel/squirrelautobind.h b/primedev/squirrel/squirrelautobind.h index 0fc599f3d..cb1d21087 100644 --- a/primedev/squirrel/squirrelautobind.h +++ b/primedev/squirrel/squirrelautobind.h @@ -15,7 +15,7 @@ extern SquirrelAutoBindContainer* g_pSqAutoBindContainer; class __squirrelautobind; #define ADD_SQFUNC(returnType, funcName, argTypes, helpText, runOnContext) \ - template SQRESULT CONCAT2(Script_, funcName)(HSquirrelVM * sqvm); \ + template SQRESULT CONCAT2(Script_, funcName)(HSQUIRRELVM sqvm); \ namespace \ { \ __squirrelautobind CONCAT2(__squirrelautobind, __LINE__)( \ @@ -35,10 +35,10 @@ class __squirrelautobind; returnType, __STR(funcName), argTypes, helpText, CONCAT2(Script_, funcName) < ScriptContext::SERVER >); \ }); \ } \ - template SQRESULT CONCAT2(Script_, funcName)(HSquirrelVM * sqvm) + template SQRESULT CONCAT2(Script_, funcName)(HSQUIRRELVM sqvm) #define REPLACE_SQFUNC(funcName, runOnContext) \ - template SQRESULT CONCAT2(Script_, funcName)(HSquirrelVM * sqvm); \ + template SQRESULT CONCAT2(Script_, funcName)(HSQUIRRELVM sqvm); \ namespace \ { \ __squirrelautobind CONCAT2(__squirrelautobind, __LINE__)( \ @@ -57,7 +57,7 @@ class __squirrelautobind; __STR(funcName), CONCAT2(Script_, funcName) < ScriptContext::SERVER >); \ }); \ } \ - template SQRESULT CONCAT2(Script_, funcName)(HSquirrelVM * sqvm) + template SQRESULT CONCAT2(Script_, funcName)(HSQUIRRELVM sqvm) class __squirrelautobind { diff --git a/primedev/squirrel/squirrelclasstypes.h b/primedev/squirrel/squirrelclasstypes.h index 91c3c4683..f53f6d183 100644 --- a/primedev/squirrel/squirrelclasstypes.h +++ b/primedev/squirrel/squirrelclasstypes.h @@ -1,83 +1,14 @@ #pragma once -#include "squirreldatatypes.h" -#include - -enum SQRESULT : SQInteger -{ - SQRESULT_ERROR = -1, - SQRESULT_NULL = 0, - SQRESULT_NOTNULL = 1, -}; - -typedef SQRESULT (*SQFunction)(HSquirrelVM* sqvm); +#include "vscript/vscript.h" -enum class eSQReturnType -{ - Float = 0x1, - Vector = 0x3, - Integer = 0x5, - Boolean = 0x6, - Entity = 0xD, - String = 0x21, - Default = 0x20, - Arrays = 0x25, - Asset = 0x28, - Table = 0x26, -}; +#include const std::map PrintSQRESULT = { {SQRESULT::SQRESULT_ERROR, "SQRESULT_ERROR"}, {SQRESULT::SQRESULT_NULL, "SQRESULT_NULL"}, {SQRESULT::SQRESULT_NOTNULL, "SQRESULT_NOTNULL"}}; -struct CompileBufferState -{ - const SQChar* buffer; - const SQChar* bufferPlusLength; - const SQChar* bufferAgain; - - CompileBufferState(const std::string& code) - { - buffer = code.c_str(); - bufferPlusLength = code.c_str() + code.size(); - bufferAgain = code.c_str(); - } -}; - -struct SQFuncRegistration -{ - const char* squirrelFuncName; - const char* cppFuncName; - const char* helpText; - const char* returnTypeString; - const char* argTypes; - uint32_t unknown1; - uint32_t devLevel; - const char* shortNameMaybe; - uint32_t unknown2; - eSQReturnType returnType; - uint32_t* externalBufferPointer; - uint64_t externalBufferSize; - uint64_t unknown3; - uint64_t unknown4; - SQFunction funcPtr; - - SQFuncRegistration() - { - memset(this, 0, sizeof(SQFuncRegistration)); - this->returnType = eSQReturnType::Default; - } -}; - -enum class ScriptContext : int -{ - INVALID = -1, - SERVER, - CLIENT, - UI, -}; - typedef std::vector> FunctionVector; typedef std::function VoidFunction; @@ -174,7 +105,8 @@ class SquirrelAsset { public: std::string path; - SquirrelAsset(std::string path) : path(path) {}; + SquirrelAsset(std::string path) + : path(path) {}; }; #pragma region TypeDefs @@ -184,55 +116,56 @@ typedef int64_t (*RegisterSquirrelFuncType)(CSquirrelVM* sqvm, SQFuncRegistratio typedef void (*sq_defconstType)(CSquirrelVM* sqvm, const SQChar* name, int value); typedef SQRESULT (*sq_compilebufferType)( - HSquirrelVM* sqvm, CompileBufferState* compileBuffer, const char* file, int a1, SQBool bShouldThrowError); -typedef SQRESULT (*sq_callType)(HSquirrelVM* sqvm, SQInteger iArgs, SQBool bShouldReturn, SQBool bThrowError); -typedef SQInteger (*sq_raiseerrorType)(HSquirrelVM* sqvm, const SQChar* pError); + HSQUIRRELVM sqvm, SQBufferState* compileBuffer, const char* file, int a1, SQBool bShouldThrowError); +typedef SQRESULT (*sq_callType)(HSQUIRRELVM sqvm, SQInteger iArgs, SQBool bShouldReturn, SQBool bThrowError); +typedef SQInteger (*sq_raiseerrorType)(HSQUIRRELVM sqvm, const SQChar* pError); typedef bool (*sq_compilefileType)(CSquirrelVM* sqvm, const char* path, const char* name, int a4); // sq stack array funcs -typedef void (*sq_newarrayType)(HSquirrelVM* sqvm, SQInteger iStackpos); -typedef SQRESULT (*sq_arrayappendType)(HSquirrelVM* sqvm, SQInteger iStackpos); +typedef void (*sq_newarrayType)(HSQUIRRELVM sqvm, SQInteger iStackpos); +typedef SQRESULT (*sq_arrayappendType)(HSQUIRRELVM sqvm, SQInteger iStackpos); // sq table funcs -typedef SQRESULT (*sq_newtableType)(HSquirrelVM* sqvm); -typedef SQRESULT (*sq_newslotType)(HSquirrelVM* sqvm, SQInteger idx, SQBool bStatic); +typedef SQRESULT (*sq_newtableType)(HSQUIRRELVM sqvm); +typedef SQRESULT (*sq_newslotType)(HSQUIRRELVM sqvm, SQInteger idx, SQBool bStatic); // sq stack push funcs -typedef void (*sq_pushroottableType)(HSquirrelVM* sqvm); -typedef void (*sq_pushstringType)(HSquirrelVM* sqvm, const SQChar* pStr, SQInteger iLength); -typedef void (*sq_pushintegerType)(HSquirrelVM* sqvm, SQInteger i); -typedef void (*sq_pushfloatType)(HSquirrelVM* sqvm, SQFloat f); -typedef void (*sq_pushboolType)(HSquirrelVM* sqvm, SQBool b); -typedef void (*sq_pushassetType)(HSquirrelVM* sqvm, const SQChar* str, SQInteger iLength); -typedef void (*sq_pushvectorType)(HSquirrelVM* sqvm, const SQFloat* pVec); -typedef void (*sq_pushobjectType)(HSquirrelVM* sqvm, SQObject* pVec); +typedef void (*sq_pushroottableType)(HSQUIRRELVM sqvm); +typedef void (*sq_pushstringType)(HSQUIRRELVM sqvm, const SQChar* pStr, SQInteger iLength); +typedef void (*sq_pushintegerType)(HSQUIRRELVM sqvm, SQInteger i); +typedef void (*sq_pushfloatType)(HSQUIRRELVM sqvm, SQFloat f); +typedef void (*sq_pushboolType)(HSQUIRRELVM sqvm, SQBool b); +typedef void (*sq_pushassetType)(HSQUIRRELVM sqvm, const SQChar* str, SQInteger iLength); +typedef void (*sq_pushvectorType)(HSQUIRRELVM sqvm, const SQFloat* pVec); +typedef void (*sq_pushobjectType)(HSQUIRRELVM sqvm, SQObject* pVec); // sq stack get funcs -typedef const SQChar* (*sq_getstringType)(HSquirrelVM* sqvm, SQInteger iStackpos); -typedef SQInteger (*sq_getintegerType)(HSquirrelVM* sqvm, SQInteger iStackpos); -typedef SQFloat (*sq_getfloatType)(HSquirrelVM*, SQInteger iStackpos); -typedef SQBool (*sq_getboolType)(HSquirrelVM*, SQInteger iStackpos); -typedef SQRESULT (*sq_getType)(HSquirrelVM* sqvm, SQInteger iStackpos); -typedef SQRESULT (*sq_getassetType)(HSquirrelVM* sqvm, SQInteger iStackpos, const char** pResult); -typedef SQRESULT (*sq_getuserdataType)(HSquirrelVM* sqvm, SQInteger iStackpos, void** pData, uint64_t* pTypeId); -typedef SQFloat* (*sq_getvectorType)(HSquirrelVM* sqvm, SQInteger iStackpos); -typedef SQBool (*sq_getthisentityType)(HSquirrelVM*, void** ppEntity); -typedef void (*sq_getobjectType)(HSquirrelVM*, SQInteger iStackPos, SQObject* pOutObj); - -typedef long long (*sq_stackinfosType)(HSquirrelVM* sqvm, int iLevel, SQStackInfos* pOutObj, int iCallStackSize); +typedef const SQChar* (*sq_getstringType)(HSQUIRRELVM sqvm, SQInteger iStackpos); +typedef SQInteger (*sq_getintegerType)(HSQUIRRELVM sqvm, SQInteger iStackpos); +typedef SQFloat (*sq_getfloatType)(HSQUIRRELVM, SQInteger iStackpos); +typedef SQBool (*sq_getboolType)(HSQUIRRELVM, SQInteger iStackpos); +typedef SQRESULT (*sq_getType)(HSQUIRRELVM sqvm, SQInteger iStackpos); +typedef SQRESULT (*sq_getassetType)(HSQUIRRELVM sqvm, SQInteger iStackpos, const char** pResult); +typedef SQRESULT (*sq_getuserdataType)(HSQUIRRELVM sqvm, SQInteger iStackpos, void** pData, uint64_t* pTypeId); +typedef SQFloat* (*sq_getvectorType)(HSQUIRRELVM sqvm, SQInteger iStackpos); +typedef SQBool (*sq_getthisentityType)(HSQUIRRELVM, void** ppEntity); +typedef void (*sq_getobjectType)(HSQUIRRELVM, SQInteger iStackPos, SQObject* pOutObj); + +typedef long long (*sq_stackinfosType)(HSQUIRRELVM sqvm, int iLevel, SQStackInfos* pOutObj, int iCallStackSize); // sq stack userpointer funcs -typedef void* (*sq_createuserdataType)(HSquirrelVM* sqvm, SQInteger iSize); -typedef SQRESULT (*sq_setuserdatatypeidType)(HSquirrelVM* sqvm, SQInteger iStackpos, uint64_t iTypeId); +typedef void* (*sq_createuserdataType)(HSQUIRRELVM sqvm, SQInteger iSize); +typedef SQRESULT (*sq_setuserdatatypeidType)(HSQUIRRELVM sqvm, SQInteger iStackpos, uint64_t iTypeId); // sq misc entity funcs typedef void* (*sq_getentityfrominstanceType)(CSquirrelVM* sqvm, SQObject* pInstance, char** ppEntityConstant); +typedef SQObject* (*sq_createscriptinstanceType)(void* ent); typedef char** (*sq_GetEntityConstantType)(); -typedef int (*sq_getfunctionType)(HSquirrelVM* sqvm, const char* name, SQObject* returnObj, const char* signature); +typedef int (*sq_getfunctionType)(HSQUIRRELVM sqvm, const char* name, SQObject* returnObj, const char* signature); // structs -typedef SQRESULT (*sq_pushnewstructinstanceType)(HSquirrelVM* sqvm, int fieldCount); -typedef SQRESULT (*sq_sealstructslotType)(HSquirrelVM* sqvm, int slotIndex); +typedef SQRESULT (*sq_pushnewstructinstanceType)(HSQUIRRELVM sqvm, int fieldCount); +typedef SQRESULT (*sq_sealstructslotType)(HSQUIRRELVM sqvm, int slotIndex); #pragma endregion diff --git a/primedev/squirrel/squirreldatatypes.h b/primedev/squirrel/squirreldatatypes.h deleted file mode 100644 index 84ab15ecc..000000000 --- a/primedev/squirrel/squirreldatatypes.h +++ /dev/null @@ -1,501 +0,0 @@ -#pragma once -/* - This file has been generated by IDA. - It contains local type definitions from - the type library 'server.dll' -*/ - -struct HSquirrelVM; -struct CallInfo; -struct SQTable; -struct SQString; -struct SQFunctionProto; -struct SQClosure; -struct SQSharedState; -struct StringTable; -struct SQStructInstance; -struct SQStructDef; -struct SQNativeClosure; -struct SQArray; -struct tableNode; -struct SQUserData; -struct CSquirrelVM; - -typedef void (*releasehookType)(void* val, int size); - -// stolen from ttf2sdk: sqvm types -typedef float SQFloat; -typedef long SQInteger; -typedef unsigned long SQUnsignedInteger; -typedef char SQChar; -typedef SQUnsignedInteger SQBool; - -/* 127 */ -enum SQObjectType : int -{ - _RT_NULL = 0x1, - _RT_INTEGER = 0x2, - _RT_FLOAT = 0x4, - _RT_BOOL = 0x8, - _RT_STRING = 0x10, - _RT_TABLE = 0x20, - _RT_ARRAY = 0x40, - _RT_USERDATA = 0x80, - _RT_CLOSURE = 0x100, - _RT_NATIVECLOSURE = 0x200, - _RT_GENERATOR = 0x400, - OT_USERPOINTER = 0x800, - _RT_USERPOINTER = 0x800, - _RT_THREAD = 0x1000, - _RT_FUNCPROTO = 0x2000, - _RT_CLASS = 0x4000, - _RT_INSTANCE = 0x8000, - _RT_WEAKREF = 0x10000, - OT_VECTOR = 0x40000, - SQOBJECT_CANBEFALSE = 0x1000000, - OT_NULL = 0x1000001, - OT_BOOL = 0x1000008, - SQOBJECT_DELEGABLE = 0x2000000, - SQOBJECT_NUMERIC = 0x4000000, - OT_INTEGER = 0x5000002, - OT_FLOAT = 0x5000004, - SQOBJECT_REF_COUNTED = 0x8000000, - OT_STRING = 0x8000010, - OT_ARRAY = 0x8000040, - OT_CLOSURE = 0x8000100, - OT_NATIVECLOSURE = 0x8000200, - OT_ASSET = 0x8000400, - OT_THREAD = 0x8001000, - OT_FUNCPROTO = 0x8002000, - OT_CLAAS = 0x8004000, - OT_STRUCT = 0x8200000, - OT_WEAKREF = 0x8010000, - OT_TABLE = 0xA000020, - OT_USERDATA = 0xA000080, - OT_INSTANCE = 0xA008000, - OT_ENTITY = 0xA400000, -}; - -/* 156 */ -union SQObjectValue -{ - SQString* asString; - SQTable* asTable; - SQClosure* asClosure; - SQFunctionProto* asFuncProto; - SQStructDef* asStructDef; - long long as64Integer; - SQNativeClosure* asNativeClosure; - SQArray* asArray; - HSquirrelVM* asThread; - float asFloat; - int asInteger; - SQUserData* asUserdata; - SQStructInstance* asStructInstance; -}; - -/* 160 */ -struct SQVector -{ - SQObjectType _Type; - float x; - float y; - float z; -}; - -/* 128 */ -struct SQObject -{ - SQObjectType _Type; - int structNumber; - SQObjectValue _VAL; -}; - -/* 138 */ -struct alignas(8) SQString -{ - void* vftable; - int uiRef; - int padding; - SQString* _next_maybe; - SQSharedState* sharedState; - int length; - unsigned char gap_24[4]; - char _hash[8]; - char _val[1]; -}; - -/* 137 */ -struct alignas(8) SQTable -{ - void* vftable; - unsigned char gap_08[4]; - int uiRef; - unsigned char gap_10[8]; - void* pointer_18; - void* pointer_20; - void* _sharedState; - long long field_30; - tableNode* _nodes; - int _numOfNodes; - int size; - int field_48; - int _usedNodes; - unsigned char _gap_50[20]; - int field_64; - unsigned char _gap_68[80]; -}; - -/* 140 */ -struct alignas(8) SQClosure -{ - void* vftable; - unsigned char gap_08[4]; - int uiRef; - void* pointer_10; - void* pointer_18; - void* pointer_20; - void* sharedState; - SQObject obj_30; - SQObject _function; - SQObject* _outervalues; - unsigned char gap_58[8]; - unsigned char gap_60[96]; - SQObject* objectPointer_C0; - unsigned char gap_C8[16]; -}; - -/* 139 */ -struct alignas(8) SQFunctionProto -{ - void* vftable; - unsigned char gap_08[4]; - int uiRef; - unsigned char gap_10[8]; - void* pointer_18; - void* pointer_20; - void* sharedState; - void* pointer_30; - SQObjectType _fileNameType; - SQString* _fileName; - SQObjectType _funcNameType; - SQString* _funcName; - SQObject obj_58; - unsigned char gap_68[12]; - int _stacksize; - unsigned char gap_78[48]; - int nParameters; - unsigned char gap_AC[60]; - int nDefaultParams; - unsigned char gap_EC[200]; -}; - -/* 152 */ -struct SQStructDef -{ - void* vtable; - int uiRef; - unsigned char padding_C[4]; - unsigned char unknown[24]; - SQSharedState* sharedState; - SQObjectType _nameType; - SQString* _name; - unsigned char gap_38[16]; - SQObjectType _variableNamesType; - SQTable* _variableNames; - unsigned char gap_[32]; -}; - -/* 157 */ -struct alignas(8) SQNativeClosure -{ - void* vftable; - int uiRef; - unsigned char gap_C[4]; - long long value_10; - long long value_18; - long long value_20; - SQSharedState* sharedState; - char unknown_30; - unsigned char padding_34[7]; - long long value_38; - long long value_40; - long long value_48; - long long value_50; - long long value_58; - SQObjectType _nameType; - SQString* _name; - long long value_70; - long long value_78; - unsigned char justInCaseGap_80[300]; -}; - -/* 162 */ -struct SQArray -{ - void* vftable; - int uiRef; - unsigned char gap_24[36]; - SQObject* _values; - int _usedSlots; - int _allocated; -}; - -/* 129 */ -struct alignas(8) HSquirrelVM -{ - void* vftable; - int uiRef; - unsigned char gap_8[12]; - void* _toString; - void* _roottable_pointer; - void* pointer_28; - CallInfo* ci; - CallInfo* _callstack; - int _callstacksize; - int _stackbase; - SQObject* _stackOfCurrentFunction; - SQSharedState* sharedState; - void* pointer_58; - void* pointer_60; - int _top; - SQObject* _stack; - unsigned char gap_78[8]; - SQObject* _vargvstack; - unsigned char gap_88[8]; - SQObject temp_reg; - unsigned char gapA0[8]; - void* pointer_A8; - unsigned char gap_B0[8]; - SQObject _roottable_object; - SQObject _lasterror; - SQObject _errorHandler; - long long field_E8; - int traps; - unsigned char gap_F4[12]; - int _nnativecalls; - int _suspended; - int _suspended_root; - int _unk; - int _suspended_target; - int trapAmount; - int _suspend_varargs; - int unknown_field_11C; - SQObject object_120; -}; - -/* 150 */ -struct SQStructInstance -{ - void* vftable; - __int32 uiRef; - BYTE gap_C[4]; - __int64 unknown_10; - void* pointer_18; - __int64 unknown_20; - SQSharedState* _sharedState; - unsigned int size; - BYTE gap_34[4]; - SQObject data[1]; // This struct is dynamically sized, so this size is unknown -}; - -/* 148 */ -struct SQSharedState -{ - unsigned char gap_0[72]; - void* unknown; - unsigned char gap_50[16344]; - SQObjectType _unknownTableType00; - long long _unknownTableValue00; - unsigned char gap_4038[16]; - StringTable* _stringTable; - unsigned char gap_4050[32]; - SQObjectType _unknownTableType0; - long long _unknownTableValue0; - SQObjectType _unknownObjectType1; - long long _unknownObjectValue1; - unsigned char gap_4090[8]; - SQObjectType _unknownArrayType2; - long long _unknownArrayValue2; - SQObjectType _gobalsArrayType; - SQStructInstance* _globalsArray; - unsigned char gap_40B8[16]; - SQObjectType _nativeClosuresType; - SQTable* _nativeClosures; - SQObjectType _typedConstantsType; - SQTable* _typedConstants; - SQObjectType _untypedConstantsType; - SQTable* _untypedConstants; - SQObjectType _globalsMaybeType; - SQTable* _globals; - SQObjectType _functionsType; - SQTable* _functions; - SQObjectType _structsType; - SQTable* _structs; - SQObjectType _typeDefsType; - SQTable* _typeDefs; - SQObjectType unknownTableType; - SQTable* unknownTable; - SQObjectType _squirrelFilesType; - SQTable* _squirrelFiles; - unsigned char gap_4158[80]; - SQObjectType _nativeClosures2Type; - SQTable* _nativeClosures2; - SQObjectType _entityTypesMaybeType; - SQTable* _entityTypesMaybe; - SQObjectType unknownTable2Type; - SQTable* unknownTable2; - unsigned char gap_41D8[72]; - SQObjectType _compilerKeywordsType; - SQTable* _compilerKeywords; - HSquirrelVM* _currentThreadMaybe; - unsigned char gap_4238[8]; - SQObjectType unknownTable3Type; - SQTable* unknownTable3; - unsigned char gap_4250[16]; - SQObjectType unknownThreadType; - SQTable* unknownThread; - SQObjectType _tableNativeFunctionsType; - SQTable* _tableNativeFunctions; - SQObjectType _unknownTableType4; - long long _unknownObjectValue4; - SQObjectType _unknownObjectType5; - long long _unknownObjectValue5; - SQObjectType _unknownObjectType6; - long long _unknownObjectValue6; - SQObjectType _unknownObjectType7; - long long _unknownObjectValue7; - SQObjectType _unknownObjectType8; - long long _unknownObjectValue8; - SQObjectType _unknownObjectType9; - long long _unknownObjectValue9; - SQObjectType _unknownObjectType10; - long long _unknownObjectValue10; - SQObjectType _unknownObjectType11; - long long _unknownObjectValue11; - SQObjectType _unknownObjectType12; - long long _unknownObjectValue12; - SQObjectType _unknownObjectType13; - long long _unknownObjectValue13; - SQObjectType _unknownObjectType14; - long long _unknownObjectValue14; - SQObjectType _unknownObjectType15; - long long _unknownObjectValue15; - unsigned char gap_4340[16]; - void* printFunction; - unsigned char gap_4358[16]; - void* logEntityFunction; - unsigned char gap_4370[40]; - SQObjectType _waitStringType; - SQString* _waitStringValue; - SQObjectType _SpinOffAndWaitForStringType; - SQString* _SpinOffAndWaitForStringValue; - SQObjectType _SpinOffAndWaitForSoloStringType; - SQString* _SpinOffAndWaitForSoloStringValue; - SQObjectType _SpinOffStringType; - SQString* _SpinOffStringValue; - SQObjectType _SpinOffDelayedStringType; - SQString* _SpinOffDelayedStringValue; - CSquirrelVM* cSquirrelVM; - bool enableDebugInfo; // functionality stripped - unsigned char gap_43F1[23]; -}; - -/* 165 */ -struct tableNode -{ - SQObject val; - SQObject key; - tableNode* next; -}; - -/* 136 */ -struct alignas(8) CallInfo -{ - long long ip; - SQObject* _literals; - SQObject obj10; - SQObject closure; - int _etraps[4]; - int _root; - short _vargs_size; - short _vargs_base; - unsigned char gap[16]; -}; - -/* 149 */ -struct StringTable -{ - unsigned char gap_0[12]; - int _numofslots; - unsigned char gap_10[200]; -}; - -/* 141 */ -struct alignas(8) SQStackInfos -{ - char* _name; - char* _sourceName; - int _line; -}; - -/* 151 */ -struct alignas(4) SQInstruction -{ - int op; - int arg1; - int output; - short arg2; - short arg3; -}; - -/* 154 */ -struct SQLexer -{ - unsigned char gap_0[112]; -}; - -/* 153 */ -struct SQCompiler -{ - unsigned char gap_0[4]; - int _token; - unsigned char gap_8[8]; - SQObject object_10; - SQLexer lexer; - unsigned char gap_90[752]; - HSquirrelVM* sqvm; - unsigned char gap_288[8]; -}; - -/* 155 */ -struct CSquirrelVM -{ - BYTE gap_0[8]; - HSquirrelVM* sqvm; - BYTE gap_10[8]; - SQObject unknownObject_18; - __int64 unknown_28; - BYTE gap_30[12]; - __int32 vmContext; - BYTE gap_40[648]; - char* (*formatString)(__int64 a1, const char* format, ...); - BYTE gap_2D0[24]; -}; - -struct SQUserData -{ - void* vftable; - int uiRef; - char gap_12[4]; - long long unknown_10; - long long unknown_18; - long long unknown_20; - long long sharedState; - long long unknown_30; - int size; - char padding1[4]; - releasehookType releaseHook; - long long typeId; - char data[1]; -}; diff --git a/primedev/thirdparty/silver-bun/module.cpp b/primedev/thirdparty/silver-bun/module.cpp index 84f4da9e2..26e9b6b65 100644 --- a/primedev/thirdparty/silver-bun/module.cpp +++ b/primedev/thirdparty/silver-bun/module.cpp @@ -66,6 +66,25 @@ void CModule::Init() m_ModuleSections.push_back(ModuleSections_t(reinterpret_cast(hCurrentSection.Name), static_cast(m_pModuleBase + hCurrentSection.VirtualAddress), hCurrentSection.SizeOfRawData)); // Push back a struct with the section data. } + + // Get the location of IMAGE_IMPORT_DESCRIPTOR for this module by adding the IMAGE_DIRECTORY_ENTRY_IMPORT relative virtual address onto our + // module base address. + + if (m_pNTHeaders->FileHeader.SizeOfOptionalHeader == 0) + return; + + IMAGE_DATA_DIRECTORY& imageDirectory = m_pNTHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]; + if (imageDirectory.Size == 0 || imageDirectory.VirtualAddress == 0) + return; + + IMAGE_IMPORT_DESCRIPTOR* pImageImportDescriptors = reinterpret_cast(m_pModuleBase + imageDirectory.VirtualAddress); + for (IMAGE_IMPORT_DESCRIPTOR* pIID = pImageImportDescriptors; pIID->Name != 0; pIID++) + { + // Get virtual relative Address of the imported module name. Then add module base Address to get the actual location. + const char* szImportedModuleName = reinterpret_cast(reinterpret_cast(m_pModuleBase + pIID->Name)); + + m_vImportedModules.push_back(szImportedModuleName); + } } //----------------------------------------------------------------------------- diff --git a/primedev/thirdparty/silver-bun/module.h b/primedev/thirdparty/silver-bun/module.h index 5683ee146..cc5130866 100644 --- a/primedev/thirdparty/silver-bun/module.h +++ b/primedev/thirdparty/silver-bun/module.h @@ -52,6 +52,7 @@ class CModule ModuleSections_t GetSectionByName(const char* szSectionName) const; inline const std::vector& GetSections() const { return m_ModuleSections; } + inline const std::vector& GetImportedModules() const { return m_vImportedModules; } inline uintptr_t GetModuleBase(void) const { return m_pModuleBase; } inline DWORD GetModuleSize(void) const { return m_nModuleSize; } inline const std::string& GetModuleName(void) const { return m_ModuleName; } @@ -73,4 +74,5 @@ class CModule uintptr_t m_pModuleBase; DWORD m_nModuleSize; std::vector m_ModuleSections; + std::vector m_vImportedModules; }; diff --git a/primedev/util/utils.h b/primedev/util/utils.h index c8cbc7e87..c613d16dd 100644 --- a/primedev/util/utils.h +++ b/primedev/util/utils.h @@ -8,16 +8,16 @@ template class ScopeGuard auto operator=(ScopeGuard&) = delete; ScopeGuard(ScopeGuard&) = delete; - ScopeGuard(T callback) : m_callback(callback) {} + ScopeGuard(T callback) + : m_callback(callback) + { + } ~ScopeGuard() { if (!m_dismissed) m_callback(); } - void Dismiss() - { - m_dismissed = true; - } + void Dismiss() { m_dismissed = true; } private: bool m_dismissed = false; diff --git a/primedev/vscript/languages/squirrel_re/include/squirrel.h b/primedev/vscript/languages/squirrel_re/include/squirrel.h new file mode 100644 index 000000000..b068ff06b --- /dev/null +++ b/primedev/vscript/languages/squirrel_re/include/squirrel.h @@ -0,0 +1,95 @@ +#pragma once + +class Mod; +struct SQBufferState; +class CBaseEntity; + +struct SQVM; +struct SQObject; +struct SQTable; +struct SQArray; +struct SQString; +struct SQClosure; +struct SQFunctionProto; +struct SQStructDef; +struct SQNativeClosure; +struct SQStructInstance; +struct SQUserData; +struct SQSharedState; + +typedef float SQFloat; +typedef long SQInteger; +typedef unsigned long SQUnsignedInteger; +typedef char SQChar; +typedef SQUnsignedInteger SQBool; + +typedef SQVM* HSQUIRRELVM; + +enum SQRESULT : SQInteger +{ + SQRESULT_ERROR = -1, + SQRESULT_NULL = 0, + SQRESULT_NOTNULL = 1, +}; + +enum class eSQReturnType +{ + Float = 0x1, + Vector = 0x3, + Integer = 0x5, + Boolean = 0x6, + Entity = 0xD, + String = 0x21, + Default = 0x20, + Arrays = 0x25, + Asset = 0x28, + Table = 0x26, +}; + +struct SQBufferState +{ + const SQChar* buffer; + const SQChar* bufferPlusLength; + const SQChar* bufferAgain; + + SQBufferState(const SQChar* pszCode) + { + buffer = pszCode; + bufferPlusLength = pszCode + strlen(pszCode); + bufferAgain = pszCode; + } +}; + +typedef SQRESULT (*SQFunction)(HSQUIRRELVM sqvm); + +struct SQFuncRegistration +{ + const char* squirrelFuncName; + const char* cppFuncName; + const char* helpText; + const char* returnTypeString; + const char* argTypes; + uint32_t unknown1; + uint32_t devLevel; + const char* shortNameMaybe; + uint32_t unknown2; + eSQReturnType returnType; + uint32_t* externalBufferPointer; + uint64_t externalBufferSize; + uint64_t unknown3; + uint64_t unknown4; + SQFunction funcPtr; + + SQFuncRegistration() + { + memset(this, 0, sizeof(SQFuncRegistration)); + this->returnType = eSQReturnType::Default; + } +}; + +struct alignas(8) SQStackInfos +{ + char* _name; + char* _sourceName; + int _line; +}; diff --git a/primedev/vscript/languages/squirrel_re/squirrel/sqarray.h b/primedev/vscript/languages/squirrel_re/squirrel/sqarray.h new file mode 100644 index 000000000..3214dc81d --- /dev/null +++ b/primedev/vscript/languages/squirrel_re/squirrel/sqarray.h @@ -0,0 +1,12 @@ +#pragma once + +#include "vscript/languages/squirrel_re/include/squirrel.h" +#include "vscript/languages/squirrel_re/squirrel/sqobject.h" + +struct SQArray : public SQCollectable +{ + SQObject* _values; + int _usedSlots; + int _allocated; +}; +static_assert(sizeof(SQArray) == 0x40); diff --git a/primedev/vscript/languages/squirrel_re/squirrel/sqclosure.h b/primedev/vscript/languages/squirrel_re/squirrel/sqclosure.h new file mode 100644 index 000000000..85c3adef0 --- /dev/null +++ b/primedev/vscript/languages/squirrel_re/squirrel/sqclosure.h @@ -0,0 +1,29 @@ +#pragma once + +#include "vscript/languages/squirrel_re/include/squirrel.h" +#include "vscript/languages/squirrel_re/squirrel/sqobject.h" + +struct alignas(8) SQClosure : public SQCollectable +{ + SQObject obj_30; + SQObject _function; + SQObject* _outervalues; + unsigned char gap_58[8]; +}; +static_assert(sizeof(SQClosure) == 96); + +struct alignas(8) SQNativeClosure : public SQCollectable +{ + char unknown_30; + unsigned char padding_34[7]; + long long value_38; + long long value_40; + long long value_48; + long long value_50; + long long value_58; + SQObjectType _nameType; + SQString* _name; + long long value_70; + long long value_78; +}; +static_assert(sizeof(SQNativeClosure) == 128); diff --git a/primedev/vscript/languages/squirrel_re/squirrel/sqcompiler.h b/primedev/vscript/languages/squirrel_re/squirrel/sqcompiler.h new file mode 100644 index 000000000..5a54751ca --- /dev/null +++ b/primedev/vscript/languages/squirrel_re/squirrel/sqcompiler.h @@ -0,0 +1,26 @@ +#pragma once + +#include "vscript/languages/squirrel_re/include/squirrel.h" +#include "vscript/languages/squirrel_re/squirrel/sqlexer.h" + +struct SQCompiler +{ + BYTE gap0[4]; + int _token; + BYTE gap_8[8]; + SQObject object_10; + SQLexer lexer; + int64_t qword90; + int64_t qword98; + BYTE gapA0[280]; + bool bFatalError; + BYTE gap1B9[143]; + int64_t qword248; + int64_t qword250; + int64_t qword258; + int64_t qword260; + BYTE gap268[280]; + HSQUIRRELVM pSQVM; + unsigned char gap_288[8]; +}; +static_assert(sizeof(SQCompiler) == 0x390); diff --git a/primedev/vscript/languages/squirrel_re/squirrel/sqfunctionproto.h b/primedev/vscript/languages/squirrel_re/squirrel/sqfunctionproto.h new file mode 100644 index 000000000..77bec7ebe --- /dev/null +++ b/primedev/vscript/languages/squirrel_re/squirrel/sqfunctionproto.h @@ -0,0 +1,24 @@ +#pragma once + +#include "vscript/languages/squirrel_re/include/squirrel.h" +#include "vscript/languages/squirrel_re/squirrel/sqobject.h" + +// NOTE [Fifty]: Variable sized struct +struct alignas(8) SQFunctionProto : public SQCollectable +{ + void* pointer_30; + SQObjectType _fileNameType; + SQString* _fileName; + SQObjectType _funcNameType; + SQString* _funcName; + SQObject obj_58; + unsigned char gap_68[12]; + int _stacksize; + unsigned char gap_78[48]; + int nParameters; + unsigned char gap_AC[60]; + int nDefaultParams; + unsigned char gap_EC[200]; +}; +// TODO [Fifty]: Find out the size of the base struct +static_assert(offsetof(SQFunctionProto, _fileName) == 0x40); // Sanity this check for now diff --git a/primedev/vscript/languages/squirrel_re/squirrel/sqlexer.h b/primedev/vscript/languages/squirrel_re/squirrel/sqlexer.h new file mode 100644 index 000000000..dc19bea8f --- /dev/null +++ b/primedev/vscript/languages/squirrel_re/squirrel/sqlexer.h @@ -0,0 +1,9 @@ +#pragma once + +#include "vscript/languages/squirrel_re/include/squirrel.h" + +// TODO [Fifty]: Verify size +struct SQLexer +{ + unsigned char gap_0[112]; +}; diff --git a/primedev/vscript/languages/squirrel_re/squirrel/sqobject.h b/primedev/vscript/languages/squirrel_re/squirrel/sqobject.h new file mode 100644 index 000000000..ea5c0da96 --- /dev/null +++ b/primedev/vscript/languages/squirrel_re/squirrel/sqobject.h @@ -0,0 +1,93 @@ +#pragma once + +#include "vscript/languages/squirrel_re/include/squirrel.h" + +struct SQTable; + +struct SQRefCounted +{ + void* vftable; + SQInteger uiRef; + void* weakRef; // Probably +}; + +struct SQCollectable : public SQRefCounted +{ + SQCollectable* _next; + SQCollectable* _prev; + SQSharedState* _sharedstate; +}; + +struct SQDelegable : public SQCollectable +{ + SQTable* _delegate; +}; + +enum SQObjectType : int +{ + _RT_NULL = 0x1, + _RT_INTEGER = 0x2, + _RT_FLOAT = 0x4, + _RT_BOOL = 0x8, + _RT_STRING = 0x10, + _RT_TABLE = 0x20, + _RT_ARRAY = 0x40, + _RT_USERDATA = 0x80, + _RT_CLOSURE = 0x100, + _RT_NATIVECLOSURE = 0x200, + _RT_GENERATOR = 0x400, + OT_USERPOINTER = 0x800, + _RT_USERPOINTER = 0x800, + _RT_THREAD = 0x1000, + _RT_FUNCPROTO = 0x2000, + _RT_CLASS = 0x4000, + _RT_INSTANCE = 0x8000, + _RT_WEAKREF = 0x10000, + OT_VECTOR = 0x40000, + SQOBJECT_CANBEFALSE = 0x1000000, + OT_NULL = 0x1000001, + OT_BOOL = 0x1000008, + SQOBJECT_DELEGABLE = 0x2000000, + SQOBJECT_NUMERIC = 0x4000000, + OT_INTEGER = 0x5000002, + OT_FLOAT = 0x5000004, + SQOBJECT_REF_COUNTED = 0x8000000, + OT_STRING = 0x8000010, + OT_ARRAY = 0x8000040, + OT_CLOSURE = 0x8000100, + OT_NATIVECLOSURE = 0x8000200, + OT_ASSET = 0x8000400, + OT_THREAD = 0x8001000, + OT_FUNCPROTO = 0x8002000, + OT_CLAAS = 0x8004000, + OT_STRUCT = 0x8200000, + OT_WEAKREF = 0x8010000, + OT_TABLE = 0xA000020, + OT_USERDATA = 0xA000080, + OT_INSTANCE = 0xA008000, + OT_ENTITY = 0xA400000, +}; + +union SQObjectValue +{ + SQString* asString; + SQTable* asTable; + SQClosure* asClosure; + SQFunctionProto* asFuncProto; + SQStructDef* asStructDef; + long long as64Integer; + SQNativeClosure* asNativeClosure; + SQArray* asArray; + HSQUIRRELVM asThread; + float asFloat; + int asInteger; + SQUserData* asUserdata; + SQStructInstance* asStructInstance; +}; + +struct SQObject +{ + SQObjectType _Type; + int structNumber; + SQObjectValue _VAL; +}; diff --git a/primedev/vscript/languages/squirrel_re/squirrel/sqopcodes.h b/primedev/vscript/languages/squirrel_re/squirrel/sqopcodes.h new file mode 100644 index 000000000..be0756c1d --- /dev/null +++ b/primedev/vscript/languages/squirrel_re/squirrel/sqopcodes.h @@ -0,0 +1,13 @@ +#pragma once + +#include "vscript/languages/squirrel_re/include/squirrel.h" + +// TODO [Fifty]: Verify size +struct alignas(4) SQInstruction +{ + int op; + int arg1; + int output; + short arg2; + short arg3; +}; diff --git a/primedev/vscript/languages/squirrel_re/squirrel/sqstate.h b/primedev/vscript/languages/squirrel_re/squirrel/sqstate.h new file mode 100644 index 000000000..d5282ac7f --- /dev/null +++ b/primedev/vscript/languages/squirrel_re/squirrel/sqstate.h @@ -0,0 +1,120 @@ +#pragma once + +#include "vscript/languages/squirrel_re/include/squirrel.h" + +class CSquirrelVM; +struct SQCompiler; + +// TODO [Fifty]: Verify size +struct StringTable +{ + unsigned char gap_0[12]; + int _numofslots; + unsigned char gap_10[200]; +}; + +struct SQSharedState +{ + unsigned char gap_0[72]; + void* unknown; + unsigned char gap_50[16344]; + SQObjectType _unknownTableType00; + long long _unknownTableValue00; + unsigned char gap_4038[16]; + StringTable* _stringTable; + unsigned char gap_4050[32]; + SQObjectType _unknownTableType0; + long long _unknownTableValue0; + SQObjectType _unknownObjectType1; + long long _unknownObjectValue1; + unsigned char gap_4090[8]; + SQObjectType _unknownArrayType2; + long long _unknownArrayValue2; + SQObjectType _gobalsArrayType; + SQStructInstance* _globalsArray; + unsigned char gap_40B8[16]; + SQObjectType _nativeClosuresType; + SQTable* _nativeClosures; + SQObjectType _typedConstantsType; + SQTable* _typedConstants; + SQObjectType _untypedConstantsType; + SQTable* _untypedConstants; + SQObjectType _globalsMaybeType; + SQTable* _globals; + SQObjectType _functionsType; + SQTable* _functions; + SQObjectType _structsType; + SQTable* _structs; + SQObjectType _typeDefsType; + SQTable* _typeDefs; + SQObjectType unknownTableType; + SQTable* unknownTable; + SQObjectType _squirrelFilesType; + SQTable* _squirrelFiles; + unsigned char gap_4158[80]; + SQObjectType _nativeClosures2Type; + SQTable* _nativeClosures2; + SQObjectType _entityTypesMaybeType; + SQTable* _entityTypesMaybe; + SQObjectType unknownTable2Type; + SQTable* unknownTable2; + unsigned char gap_41D8[64]; + SQCompiler* pCompiler; + SQObjectType _compilerKeywordsType; + SQTable* _compilerKeywords; + HSQUIRRELVM _currentThreadMaybe; + unsigned char gap_4238[8]; + SQObjectType unknownTable3Type; + SQTable* unknownTable3; + unsigned char gap_4250[16]; + SQObjectType unknownThreadType; + SQTable* unknownThread; + SQObjectType _tableNativeFunctionsType; + SQTable* _tableNativeFunctions; + SQObjectType _unknownTableType4; + long long _unknownObjectValue4; + SQObjectType _unknownObjectType5; + long long _unknownObjectValue5; + SQObjectType _unknownObjectType6; + long long _unknownObjectValue6; + SQObjectType _unknownObjectType7; + long long _unknownObjectValue7; + SQObjectType _unknownObjectType8; + long long _unknownObjectValue8; + SQObjectType _unknownObjectType9; + long long _unknownObjectValue9; + SQObjectType _unknownObjectType10; + long long _unknownObjectValue10; + SQObjectType _unknownObjectType11; + long long _unknownObjectValue11; + SQObjectType _unknownObjectType12; + long long _unknownObjectValue12; + SQObjectType _unknownObjectType13; + long long _unknownObjectValue13; + SQObjectType _unknownObjectType14; + long long _unknownObjectValue14; + SQObjectType _unknownObjectType15; + long long _unknownObjectValue15; + unsigned __int8 gap_4340[8]; + void* fnFatalErrorCallback; + void* fnPrintCallback; + unsigned __int8 gap_4358[16]; + void* logEntityFunction; + unsigned char gap_4370[1]; + SQChar szContextName[8]; + unsigned char gap[31]; + SQObjectType _waitStringType; + SQString* _waitStringValue; + SQObjectType _SpinOffAndWaitForStringType; + SQString* _SpinOffAndWaitForStringValue; + SQObjectType _SpinOffAndWaitForSoloStringType; + SQString* _SpinOffAndWaitForSoloStringValue; + SQObjectType _SpinOffStringType; + SQString* _SpinOffStringValue; + SQObjectType _SpinOffDelayedStringType; + SQString* _SpinOffDelayedStringValue; + CSquirrelVM* cSquirrelVM; + bool enableDebugInfo; // functionality stripped + unsigned char gap_43F1[23]; +}; +static_assert(sizeof(SQSharedState) == 17416); diff --git a/primedev/vscript/languages/squirrel_re/squirrel/sqstring.h b/primedev/vscript/languages/squirrel_re/squirrel/sqstring.h new file mode 100644 index 000000000..5b17f489c --- /dev/null +++ b/primedev/vscript/languages/squirrel_re/squirrel/sqstring.h @@ -0,0 +1,15 @@ +#pragma once + +#include "vscript/languages/squirrel_re/include/squirrel.h" +#include "vscript/languages/squirrel_re/squirrel/sqobject.h" + +// NOTE [Fifty]: Variable sized struct +struct alignas(8) SQString : public SQRefCounted +{ + SQSharedState* sharedState; + int length; + unsigned char gap_24[4]; + char _hash[8]; + char _val[1]; +}; +static_assert(sizeof(SQString) == 56); // [Fifty]: Game allocates 56 + strlen diff --git a/primedev/vscript/languages/squirrel_re/squirrel/sqstruct.h b/primedev/vscript/languages/squirrel_re/squirrel/sqstruct.h new file mode 100644 index 000000000..1e357df8a --- /dev/null +++ b/primedev/vscript/languages/squirrel_re/squirrel/sqstruct.h @@ -0,0 +1,24 @@ +#pragma once + +#include "vscript/languages/squirrel_re/include/squirrel.h" +#include "vscript/languages/squirrel_re/squirrel/sqobject.h" + +struct SQStructDef : public SQCollectable +{ + SQObjectType _nameType; + SQString* _name; + unsigned char gap_38[16]; + SQObjectType _variableNamesType; + SQTable* _variableNames; + unsigned char gap_[32]; +}; +static_assert(sizeof(SQStructDef) == 128); + +// NOTE [Fifty]: Variable sized struct +struct SQStructInstance : public SQCollectable +{ + unsigned int size; + BYTE gap_34[4]; + SQObject data[1]; +}; +static_assert(sizeof(SQStructInstance) == 72); diff --git a/primedev/vscript/languages/squirrel_re/squirrel/sqtable.h b/primedev/vscript/languages/squirrel_re/squirrel/sqtable.h new file mode 100644 index 000000000..e0ced4362 --- /dev/null +++ b/primedev/vscript/languages/squirrel_re/squirrel/sqtable.h @@ -0,0 +1,21 @@ +#pragma once + +#include "vscript/languages/squirrel_re/include/squirrel.h" +#include "vscript/languages/squirrel_re/squirrel/sqobject.h" + +struct alignas(8) SQTable : public SQDelegable +{ + struct _HashNode + { + SQObject val; + SQObject key; + _HashNode* next; + }; + + _HashNode* _nodes; + int _numOfNodes; + int size; + int field_48; + int _usedNodes; +}; +static_assert(sizeof(SQTable) == 80); diff --git a/primedev/vscript/languages/squirrel_re/squirrel/squserdata.h b/primedev/vscript/languages/squirrel_re/squirrel/squserdata.h new file mode 100644 index 000000000..98ede8878 --- /dev/null +++ b/primedev/vscript/languages/squirrel_re/squirrel/squserdata.h @@ -0,0 +1,15 @@ +#pragma once + +#include "vscript/languages/squirrel_re/include/squirrel.h" +#include "vscript/languages/squirrel_re/squirrel/sqobject.h" + +// NOTE [Fifty]: Variable sized struct +struct SQUserData : public SQDelegable +{ + int size; + char padding1[4]; + void* (*releasehook)(void* val, int size); + long long typeId; + char data[1]; +}; +static_assert(sizeof(SQUserData) == 88); // [Fifty]: Game allocates 87 + size (passed to the function) diff --git a/primedev/vscript/languages/squirrel_re/squirrel/sqvector.h b/primedev/vscript/languages/squirrel_re/squirrel/sqvector.h new file mode 100644 index 000000000..63984e909 --- /dev/null +++ b/primedev/vscript/languages/squirrel_re/squirrel/sqvector.h @@ -0,0 +1,12 @@ +#pragma once + +#include "vscript/languages/squirrel_re/include/squirrel.h" + +// TODO [Fifty]: Verify size +struct SQVector +{ + SQObjectType _Type; + float x; + float y; + float z; +}; diff --git a/primedev/vscript/languages/squirrel_re/squirrel/sqvm.h b/primedev/vscript/languages/squirrel_re/squirrel/sqvm.h new file mode 100644 index 000000000..d16092e7b --- /dev/null +++ b/primedev/vscript/languages/squirrel_re/squirrel/sqvm.h @@ -0,0 +1,69 @@ +#pragma once + +#include "vscript/languages/squirrel_re/include/squirrel.h" +#include "vscript/languages/squirrel_re/squirrel/sqstring.h" + +struct SQVM; + +enum class ScriptContext : int +{ + INVALID = -1, + SERVER, + CLIENT, + UI, +}; + +struct alignas(8) SQVM +{ + struct alignas(8) CallInfo + { + long long ip; + SQObject* _literals; + SQObject obj10; + SQObject closure; + int _etraps[4]; + int _root; + short _vargs_size; + short _vargs_base; + unsigned char gap[16]; + }; + + void* vftable; + int uiRef; + unsigned char gap_8[12]; + void* _toString; + void* _roottable_pointer; + void* pointer_28; + CallInfo* ci; + CallInfo* _callstack; + int _callstacksize; + int _stackbase; + SQObject* _stackOfCurrentFunction; + SQSharedState* sharedState; + void* pointer_58; + void* pointer_60; + int _top; + SQObject* _stack; + unsigned char gap_78[8]; + SQObject* _vargvstack; + unsigned char gap_88[8]; + SQObject temp_reg; + unsigned char gapA0[8]; + void* pointer_A8; + unsigned char gap_B0[8]; + SQObject _roottable_object; + SQObject _lasterror; + SQObject _errorHandler; + long long field_E8; + int traps; + unsigned char gap_F4[12]; + int _nnativecalls; + int _suspended; + int _suspended_root; + int _unk; + int _suspended_target; + int trapAmount; + int _suspend_varargs; + int unknown_field_11C; + SQObject object_120; +}; diff --git a/primedev/vscript/languages/squirrel_re/vsquirrel.h b/primedev/vscript/languages/squirrel_re/vsquirrel.h new file mode 100644 index 000000000..43be685ee --- /dev/null +++ b/primedev/vscript/languages/squirrel_re/vsquirrel.h @@ -0,0 +1,16 @@ +#pragma once + +struct CSquirrelVM +{ + BYTE gap_0[8]; + HSQUIRRELVM sqvm; + BYTE gap_10[8]; + SQObject unknownObject_18; + __int64 unknown_28; + BYTE gap_30[12]; + __int32 vmContext; + BYTE gap_40[648]; + char* (*formatString)(__int64 a1, const char* format, ...); + BYTE gap_2D0[24]; +}; +static_assert(sizeof(CSquirrelVM) == 744); diff --git a/primedev/vscript/vscript.h b/primedev/vscript/vscript.h new file mode 100644 index 000000000..4c9f072a9 --- /dev/null +++ b/primedev/vscript/vscript.h @@ -0,0 +1,20 @@ +#pragma once + +#include "vscript/languages/squirrel_re/include/squirrel.h" + +#include "vscript/languages/squirrel_re/squirrel/sqarray.h" +#include "vscript/languages/squirrel_re/squirrel/sqclosure.h" +#include "vscript/languages/squirrel_re/squirrel/sqcompiler.h" +#include "vscript/languages/squirrel_re/squirrel/sqfunctionproto.h" +#include "vscript/languages/squirrel_re/squirrel/sqlexer.h" +#include "vscript/languages/squirrel_re/squirrel/sqobject.h" +#include "vscript/languages/squirrel_re/squirrel/sqopcodes.h" +#include "vscript/languages/squirrel_re/squirrel/sqstate.h" +#include "vscript/languages/squirrel_re/squirrel/sqstring.h" +#include "vscript/languages/squirrel_re/squirrel/sqstruct.h" +#include "vscript/languages/squirrel_re/squirrel/sqtable.h" +#include "vscript/languages/squirrel_re/squirrel/squserdata.h" +#include "vscript/languages/squirrel_re/squirrel/sqvector.h" +#include "vscript/languages/squirrel_re/squirrel/sqvm.h" + +#include "vscript/languages/squirrel_re/vsquirrel.h" diff --git a/primedev/windows/libsys.cpp b/primedev/windows/libsys.cpp new file mode 100644 index 000000000..0aff820b7 --- /dev/null +++ b/primedev/windows/libsys.cpp @@ -0,0 +1,151 @@ +#include "libsys.h" +#include "plugins/pluginmanager.h" + +#define XINPUT1_3_DLL "XInput1_3.dll" + +typedef HMODULE (*WINAPI ILoadLibraryA)(LPCSTR lpLibFileName); +typedef HMODULE (*WINAPI ILoadLibraryExA)(LPCSTR lpLibFileName, HANDLE hFile, DWORD dwFlags); +typedef HMODULE (*WINAPI ILoadLibraryW)(LPCWSTR lpLibFileName); +typedef HMODULE (*WINAPI ILoadLibraryExW)(LPCWSTR lpLibFileName, HANDLE hFile, DWORD dwFlags); + +ILoadLibraryA o_LoadLibraryA = nullptr; +ILoadLibraryExA o_LoadLibraryExA = nullptr; +ILoadLibraryW o_LoadLibraryW = nullptr; +ILoadLibraryExW o_LoadLibraryExW = nullptr; + +//----------------------------------------------------------------------------- +// Purpose: Run detour callbacks for given HMODULE +//----------------------------------------------------------------------------- +void LibSys_RunModuleCallbacks(HMODULE hModule) +{ + // Modules that we have already ran callbacks for. + // Note: If we ever hook unloading modules, then this will need updating to handle removal etc. + static std::vector vCalledModules; + + if (!hModule) + { + return; + } + + // If we have already ran callbacks for this module, don't run them again. + if (std::find(vCalledModules.begin(), vCalledModules.end(), hModule) != vCalledModules.end()) + { + return; + } + vCalledModules.push_back(hModule); + + // Get module base name in ASCII as noone wants to deal with unicode + CHAR szModuleName[MAX_PATH]; + GetModuleBaseNameA(GetCurrentProcess(), hModule, szModuleName, MAX_PATH); + + // Run calllbacks for all imported modules + CModule cModule(hModule); + for (const std::string& svImport : cModule.GetImportedModules()) + LibSys_RunModuleCallbacks(GetModuleHandleA(svImport.c_str())); + + // DevMsg(eLog::NONE, "%s\n", szModuleName); + + // Call callbacks + CallLoadLibraryACallbacks(szModuleName, hModule); + g_pPluginManager->InformDllLoad(hModule, fs::path(szModuleName)); +} + +//----------------------------------------------------------------------------- +// Load library callbacks + +HMODULE WINAPI WLoadLibraryA(LPCSTR lpLibFileName) +{ + HMODULE hModule = o_LoadLibraryA(lpLibFileName); + + LibSys_RunModuleCallbacks(hModule); + + return hModule; +} + +HMODULE WINAPI WLoadLibraryExA(LPCSTR lpLibFileName, HANDLE hFile, DWORD dwFlags) +{ + HMODULE hModule; + + LPCSTR lpLibFileNameEnd = lpLibFileName + strlen(lpLibFileName); + LPCSTR lpLibName = lpLibFileNameEnd - strlen(XINPUT1_3_DLL); + + // replace xinput dll with one that has ASLR + if (lpLibFileName <= lpLibName && !strncmp(lpLibName, XINPUT1_3_DLL, strlen(XINPUT1_3_DLL) + 1)) + { + const char* pszReplacementDll = "XInput1_4.dll"; + hModule = o_LoadLibraryExA(pszReplacementDll, hFile, dwFlags); + + if (!hModule) + { + pszReplacementDll = "XInput9_1_0.dll"; + spdlog::warn("Couldn't load XInput1_4.dll. Will try XInput9_1_0.dll. If on Windows 7 this is expected"); + hModule = o_LoadLibraryExA(pszReplacementDll, hFile, dwFlags); + } + + if (!hModule) + { + spdlog::error("Couldn't load XInput9_1_0.dll"); + MessageBoxA( + 0, "Could not load a replacement for XInput1_3.dll\nTried: XInput1_4.dll and XInput9_1_0.dll", "Northstar", MB_ICONERROR); + exit(EXIT_FAILURE); + + return nullptr; + } + + spdlog::info("Successfully loaded {} as a replacement for XInput1_3.dll", pszReplacementDll); + } + else + { + hModule = o_LoadLibraryExA(lpLibFileName, hFile, dwFlags); + } + + bool bShouldRunCallbacks = + !(dwFlags & (LOAD_LIBRARY_AS_DATAFILE | LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE | LOAD_LIBRARY_AS_IMAGE_RESOURCE)); + if (bShouldRunCallbacks) + { + LibSys_RunModuleCallbacks(hModule); + } + + return hModule; +} + +HMODULE WINAPI WLoadLibraryW(LPCWSTR lpLibFileName) +{ + HMODULE hModule = o_LoadLibraryW(lpLibFileName); + + LibSys_RunModuleCallbacks(hModule); + + return hModule; +} + +HMODULE WINAPI WLoadLibraryExW(LPCWSTR lpLibFileName, HANDLE hFile, DWORD dwFlags) +{ + HMODULE hModule = o_LoadLibraryExW(lpLibFileName, hFile, dwFlags); + + bool bShouldRunCallbacks = + !(dwFlags & (LOAD_LIBRARY_AS_DATAFILE | LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE | LOAD_LIBRARY_AS_IMAGE_RESOURCE)); + if (bShouldRunCallbacks) + { + LibSys_RunModuleCallbacks(hModule); + } + + return hModule; +} + +//----------------------------------------------------------------------------- +// Purpose: Initilase dll load callbacks +//----------------------------------------------------------------------------- +void LibSys_Init() +{ + HMODULE hKernel = GetModuleHandleA("KERNEL32.DLL"); + + o_LoadLibraryA = reinterpret_cast(GetProcAddress(hKernel, "LoadLibraryA")); + o_LoadLibraryExA = reinterpret_cast(GetProcAddress(hKernel, "LoadLibraryExA")); + o_LoadLibraryW = reinterpret_cast(GetProcAddress(hKernel, "LoadLibraryW")); + o_LoadLibraryExW = reinterpret_cast(GetProcAddress(hKernel, "LoadLibraryExW")); + + HookAttach(&(PVOID&)o_LoadLibraryA, (PVOID)WLoadLibraryA); + HookAttach(&(PVOID&)o_LoadLibraryExA, (PVOID)WLoadLibraryExA); + HookAttach(&(PVOID&)o_LoadLibraryW, (PVOID)WLoadLibraryW); + HookAttach(&(PVOID&)o_LoadLibraryExW, (PVOID)WLoadLibraryExW); +} diff --git a/primedev/windows/libsys.h b/primedev/windows/libsys.h new file mode 100644 index 000000000..91345d8ff --- /dev/null +++ b/primedev/windows/libsys.h @@ -0,0 +1,3 @@ +#pragma once + +void LibSys_Init();