From 58eb10c74ea9f301a9ab17556b3e6d7d1bf9a0bc Mon Sep 17 00:00:00 2001 From: Mgamerz Date: Sun, 12 Sep 2021 13:55:51 -0600 Subject: [PATCH] Add LE3 SLH --- LE3-ASI-Plugins.sln | 12 +- LE3-SDK/Common.h | 68 ++++++ LE3-SDK/Interface.h | 169 ++++++++++++++ LE3-SDK/LE3-SDK.vcxproj | 19 +- .../LE1StreamingLevelsHUD.vcxproj.filters | 22 ++ .../LE3StreamingLevelsHUD.cpp | 217 ++++++++++++++++++ .../LE3StreamingLevelsHUD.vcxproj | 99 ++++++++ 7 files changed, 594 insertions(+), 12 deletions(-) create mode 100644 LE3-SDK/Common.h create mode 100644 LE3-SDK/Interface.h create mode 100644 LE3StreamingLevelsHUD/LE1StreamingLevelsHUD.vcxproj.filters create mode 100644 LE3StreamingLevelsHUD/LE3StreamingLevelsHUD.cpp create mode 100644 LE3StreamingLevelsHUD/LE3StreamingLevelsHUD.vcxproj diff --git a/LE3-ASI-Plugins.sln b/LE3-ASI-Plugins.sln index 36ccd19..9e275fb 100644 --- a/LE3-ASI-Plugins.sln +++ b/LE3-ASI-Plugins.sln @@ -5,22 +5,22 @@ VisualStudioVersion = 16.0.31624.102 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LE3-SDK", "LE3-SDK\LE3-SDK.vcxproj", "{8DA34EDE-7D5C-4DA3-8841-78AB05E8C6D2}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LE3StreamingLevelsHUD", "LE3StreamingLevelsHUD\LE3StreamingLevelsHUD.vcxproj", "{24261DB1-E217-4234-B048-1ACEA2F67237}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 Release|x64 = Release|x64 - Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {8DA34EDE-7D5C-4DA3-8841-78AB05E8C6D2}.Debug|x64.ActiveCfg = Debug|x64 {8DA34EDE-7D5C-4DA3-8841-78AB05E8C6D2}.Debug|x64.Build.0 = Debug|x64 - {8DA34EDE-7D5C-4DA3-8841-78AB05E8C6D2}.Debug|x86.ActiveCfg = Debug|Win32 - {8DA34EDE-7D5C-4DA3-8841-78AB05E8C6D2}.Debug|x86.Build.0 = Debug|Win32 {8DA34EDE-7D5C-4DA3-8841-78AB05E8C6D2}.Release|x64.ActiveCfg = Release|x64 {8DA34EDE-7D5C-4DA3-8841-78AB05E8C6D2}.Release|x64.Build.0 = Release|x64 - {8DA34EDE-7D5C-4DA3-8841-78AB05E8C6D2}.Release|x86.ActiveCfg = Release|Win32 - {8DA34EDE-7D5C-4DA3-8841-78AB05E8C6D2}.Release|x86.Build.0 = Release|Win32 + {24261DB1-E217-4234-B048-1ACEA2F67237}.Debug|x64.ActiveCfg = Debug|x64 + {24261DB1-E217-4234-B048-1ACEA2F67237}.Debug|x64.Build.0 = Debug|x64 + {24261DB1-E217-4234-B048-1ACEA2F67237}.Release|x64.ActiveCfg = Release|x64 + {24261DB1-E217-4234-B048-1ACEA2F67237}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/LE3-SDK/Common.h b/LE3-SDK/Common.h new file mode 100644 index 0000000..a83724d --- /dev/null +++ b/LE3-SDK/Common.h @@ -0,0 +1,68 @@ +#pragma once +#include +#include + +#include +#include + + +typedef unsigned char byte; +typedef signed long int32; +typedef unsigned long uint32; +typedef signed long long int64; +typedef unsigned long long uint64; +typedef wchar_t wchar; + + +namespace Common +{ + void OpenConsole() + { + AllocConsole(); + + freopen_s((FILE**)stdout, "CONOUT$", "w", stdout); + freopen_s((FILE**)stderr, "CONOUT$", "w", stderr); + + HANDLE console = GetStdHandle(STD_OUTPUT_HANDLE); + CONSOLE_SCREEN_BUFFER_INFO lpConsoleScreenBufferInfo; + GetConsoleScreenBufferInfo(console, &lpConsoleScreenBufferInfo); + SetConsoleScreenBufferSize(console, { lpConsoleScreenBufferInfo.dwSize.X, 30000 }); + } + + void CloseConsole() + { + FreeConsole(); + } + + // loosely based on https://stackoverflow.com/q/26572459 + byte* GetModuleBaseAddress(wchar* moduleName) + { + auto pid = GetCurrentProcessId(); + + HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, pid); + byte* baseAddress = nullptr; + + if (INVALID_HANDLE_VALUE != snapshot) + { + MODULEENTRY32 moduleEntry = { 0 }; + moduleEntry.dwSize = sizeof(MODULEENTRY32); + + if (Module32First(snapshot, &moduleEntry)) + { + do + { + if (0 == wcscmp(moduleEntry.szModule, moduleName)) + { + baseAddress = moduleEntry.modBaseAddr; + break; + } + } while (Module32Next(snapshot, &moduleEntry)); + } + CloseHandle(snapshot); + } + + return baseAddress; + } +} + +#define writeln(msg,...) fwprintf_s(stdout, L"LENE::" msg "\n", __VA_ARGS__) diff --git a/LE3-SDK/Interface.h b/LE3-SDK/Interface.h new file mode 100644 index 0000000..86df86e --- /dev/null +++ b/LE3-SDK/Interface.h @@ -0,0 +1,169 @@ +#pragma once + + +#pragma region Plugin-side utilities + +/// Game versions for SpiSupportDecl. +/// Not convertible with LEGameVersion or SPIGameVersion!!! + +#define SPI_GAME_LEL (1 << 0) +#define SPI_GAME_LE1 (1 << 1) +#define SPI_GAME_LE2 (1 << 2) +#define SPI_GAME_LE3 (1 << 3) + + +/// SPI version macros. +/// Duplicate the stuff in version.h!!! + +#define SPI_VERSION_ANY 3 +#define SPI_VERSION_LATEST 3 + +/// Plugin-side definition which marks the dll as supporting SPI. +#define SPI_PLUGINSIDE_SUPPORT(NAME,AUTHOR,VERSION,GAME_FLAGS,SPIMINVER) \ +extern "C" __declspec(dllexport) void SpiSupportDecl(wchar_t** name, wchar_t** author, wchar_t** version, int* gameIndexFlags, int* spiMinVersion) \ +{ *name = NAME; *author = AUTHOR; *version = VERSION; *gameIndexFlags = GAME_FLAGS; *spiMinVersion = SPIMINVER; } + +/// Plugin-side definition which marks the dll for being loaded +/// at the earliest possible moment. +#define SPI_PLUGINSIDE_PRELOAD extern "C" __declspec(dllexport) bool SpiShouldPreload(void) { return true; } +/// Plugin-side definition which marks the dll for being loaded +/// only after DRM has finished decrypting the game. +#define SPI_PLUGINSIDE_POSTLOAD extern "C" __declspec(dllexport) bool SpiShouldPreload(void) { return false; } + +/// Plugin-side definition which marks the dll for running +/// its attach point sequentially during game startup. +#define SPI_PLUGINSIDE_SEQATTACH extern "C" __declspec(dllexport) bool SpiShouldSpawnThread(void) { return false; } +/// Plugin-side definition which marks the dll for running +/// its attach point asynchronously during game startup. +#define SPI_PLUGINSIDE_ASYNCATTACH extern "C" __declspec(dllexport) bool SpiShouldSpawnThread(void) { return true; } + +/// Plugin-side boilerplate macro for defining the plugin attach point. +/// This is run when the plugin is loaded by SPI (!), not when the DLL itself is loaded. +#define SPI_IMPLEMENT_ATTACH extern "C" __declspec(dllexport) bool SpiOnAttach(ISharedProxyInterface* InterfacePtr) +/// Plugin-side boilerplate macro for defining the plugin detach point. +/// This *should* run when the plugin is unloaded by SPI (!), not when the DLL itself is unloaded. +/// WARNING: DO NOT RELY ON THIS BEING RUN +#define SPI_IMPLEMENT_DETACH extern "C" __declspec(dllexport) bool SpiOnDetach(ISharedProxyInterface* InterfacePtr) + +#pragma endregion + + +#pragma region Error codes + +/// +/// Return value for all public SPI functions. +/// - 0 is illegal to return, +/// - 1 means success, +/// - [10; 100) mean failures, +/// - [100; ...) mean that normal operation is no longer possible. +/// +enum class SPIReturn : short +{ + // Adding stuff here requires adding stuff to MakeReturnCodeText() !!! + Undefined = 0, + Success = 1, + FailureGeneric = 10, + FailureDuplicacy = 11, + FailureHooking = 12, + FailureInvalidParam = 13, + FailureUnsupportedYet = 14, + FailureDeprecated = 15, + FailurePatternInvalid = 16, + FailurePatternTooLong = 17, + ErrorFatal = 100, + ErrorWinApi = 115, +}; + +/// +/// Get a string describing an SPIReturn code. +/// +/// A const wide string +const wchar_t* SPIReturnToString(SPIReturn code) +{ + switch (code) + { + case SPIReturn::Undefined: return L"Undefined - illegal return code"; + case SPIReturn::Success: return L"Success - yay!"; + case SPIReturn::FailureGeneric: return L"FailureGeneric - unspecified error"; + case SPIReturn::FailureDuplicacy: return L"FailureDuplicacy - something unique was not unique, or something that should have existed didn't"; + case SPIReturn::FailureHooking: return L"FailureHooking - injection code returned an error"; + case SPIReturn::FailureInvalidParam: return L"FailureInvalidParam - illegal parameter passed to an SPI method"; + case SPIReturn::FailureUnsupportedYet: return L"FailureUnsupportedYet - feature is defined in SPI but not yet provided"; + case SPIReturn::FailureDeprecated: return L"FailureDeprecated - feature is defined in SPI but must not be used"; + case SPIReturn::FailurePatternInvalid: return L"FailurePatternInvalid - provided pattern was ill-formed"; + case SPIReturn::FailurePatternTooLong: return L"FailurePatternTooLong - provided pattern is too long"; + case SPIReturn::ErrorFatal: return L"ErrorFatal - unspecified error AFTER WHICH EXECUTION CANNOT CONTINUE"; + case SPIReturn::ErrorWinApi: return L"ErrorWinApi - a call to Win API function failed, check GetLastError()"; + default: return L"UNRECOGNIZED RETURN CODE - CONTACT DEVELOPERS"; + } +} + +#pragma endregion + + +#pragma region The public-facing interface + +#define SPIDECL [[nodiscard]] __declspec(noinline) virtual SPIReturn +#define SPIDEFN virtual SPIReturn + +/// Game versions for GetHostGame. +/// Not interchangable with LEGameVersion!!! +enum class SPIGameVersion +{ + Launcher = 0, + LE1 = 1, + LE2 = 2, + LE3 = 3 +}; + +/// +/// SPI declaration for use in ASI mods. +/// +class ISharedProxyInterface +{ +public: + /// + /// Get version of the SPI implementation provided by the host proxy. + /// + /// Output value for the version. + /// An appropriate code. + SPIDECL GetVersion(unsigned long* outVersionPtr) = 0; + /// + /// Get if the host proxy was built in debug or release mode. + /// + /// Output value for the build mode. + /// An appropriate code. + SPIDECL GetBuildMode(bool* outIsRelease) = 0; + /// + /// Get the game this host proxy is attached to (1 - 3 or Launcher). + /// + /// Output value for the game. + /// An appropriate code. + SPIDECL GetHostGame(SPIGameVersion* outGameVersion) = 0; + + /// + /// Search the main game module for a PEiD-style pattern. + /// + /// Output value for the offset, set to NULL if not found. + /// PEiD-style pattern specifying 100 bytes (299 chars + \0) at most. + /// An appropriate code. + SPIDECL FindPattern(void** outOffsetPtr, char* combinedPattern) = 0; + + /// + /// Use bink proxy's built-in injection library to detour a procedure. + /// + /// Name of the hook used for logging purposes. + /// Pointer to detour. + /// Pointer to what to detour the target with. + /// Pointer to where to write out the original procedure. + /// An appropriate code. + SPIDECL InstallHook(const char* name, void* target, void* detour, void** original) = 0; + /// + /// Remove a hook installed by . + /// + /// Name of the hook to remove. + /// An appropriate code. + SPIDECL UninstallHook(const char* name) = 0; +}; + +#pragma endregion diff --git a/LE3-SDK/LE3-SDK.vcxproj b/LE3-SDK/LE3-SDK.vcxproj index bed799b..fed0f93 100644 --- a/LE3-SDK/LE3-SDK.vcxproj +++ b/LE3-SDK/LE3-SDK.vcxproj @@ -179,9 +179,12 @@ Level3 true _DEBUG;_LIB;%(PreprocessorDefinitions) - true - Use - pch.h + false + NotUsing + + + stdcpp17 + /bigobj %(AdditionalOptions) @@ -196,9 +199,13 @@ true true NDEBUG;_LIB;%(PreprocessorDefinitions) - true - Use - pch.h + false + NotUsing + + + /bigobj %(AdditionalOptions) + stdcpp17 + true diff --git a/LE3StreamingLevelsHUD/LE1StreamingLevelsHUD.vcxproj.filters b/LE3StreamingLevelsHUD/LE1StreamingLevelsHUD.vcxproj.filters new file mode 100644 index 0000000..ba5209f --- /dev/null +++ b/LE3StreamingLevelsHUD/LE1StreamingLevelsHUD.vcxproj.filters @@ -0,0 +1,22 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + \ No newline at end of file diff --git a/LE3StreamingLevelsHUD/LE3StreamingLevelsHUD.cpp b/LE3StreamingLevelsHUD/LE3StreamingLevelsHUD.cpp new file mode 100644 index 0000000..12854f0 --- /dev/null +++ b/LE3StreamingLevelsHUD/LE3StreamingLevelsHUD.cpp @@ -0,0 +1,217 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "../LE3-SDK/Interface.h" +#include "../LE3-SDK/Common.h" +#include "../LE3-SDK/SdkHeaders.h" + +#define SLHHOOK "LE3StreamingLevelsHUD_" + +SPI_PLUGINSIDE_SUPPORT(L"LE3StreamingLevelsHUD", L"Mgamerz", L"1.0.0", SPI_GAME_LE3, SPI_VERSION_LATEST); +SPI_PLUGINSIDE_POSTLOAD; +SPI_PLUGINSIDE_ASYNCATTACH; + + +// ====================================================================== + + +// ProcessEvent hook +// Renders the HUD for Streaming Levels +// ====================================================================== + + +typedef void (*tProcessEvent)(UObject* Context, UFunction* Function, void* Parms, void* Result); +tProcessEvent ProcessEvent = nullptr; +tProcessEvent ProcessEvent_orig = nullptr; + +size_t MaxMemoryHit = 0; +bool DrawSLH = true; +bool CanToggleDrawSLH = false; + +static void RenderTextSLH(std::wstring msg, const float x, const float y, const char r, const char g, const char b, const float alpha, UCanvas* can) +{ + can->SetDrawColor(r, g, b, alpha * 255); + can->SetPos(x, y + 64); //+ is Y start. To prevent overlay on top of the power bar thing + can->DrawTextW(FString{ const_cast(msg.c_str()) }, 1, 0.8f, 0.8f, nullptr); +} + +const char* FormatBytes(size_t bytes, char* keepInStackStr) +{ + const char* sizes[4] = { "B", "KB", "MB", "GB" }; + + int i; + double dblByte = bytes; + for (i = 0; i < 4 && bytes >= 1024; i++, bytes /= 1024) + dblByte = bytes / 1024.0; + + sprintf(keepInStackStr, "%.2f", dblByte); + + return strcat(strcat(keepInStackStr, " "), sizes[i]); +} + +int line = 0; +PROCESS_MEMORY_COUNTERS pmc; + + +void biohud_hook(UObject* Context, UFunction* Function, void* Parms, void* Result) +{ + if (!strcmp(Function->GetFullName(), "Function SFXGame.BioHUD.PostRender")) + { + line = 0; + auto hud = reinterpret_cast(Context); + if (hud != nullptr) + { + // Toggle drawing/not drawing + if ((GetKeyState('T') & 0x8000) && (GetKeyState(VK_CONTROL) & 0x8000)) { + if (CanToggleDrawSLH) { + CanToggleDrawSLH = false; // Will not activate combo again until you re-press combo + DrawSLH = !DrawSLH; + } + } + else + { + if (!(GetKeyState('T') & 0x8000) || !(GetKeyState(VK_CONTROL) & 0x8000)) { + CanToggleDrawSLH = true; // can press key combo again + } + } + + // Render mem usage + if (GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc))) + { + unsigned char r = 0; + unsigned char g = 255; + + // Flash if high crit + char str[18] = ""; //Allocate + float alpha = 1.0f; + + if (DrawSLH) + { + wchar_t objectName[512]; + swprintf_s(objectName, 512, L"Memory usage: %S (%llu bytes)", FormatBytes(pmc.PagefileUsage, str), pmc.PagefileUsage); + RenderTextSLH(objectName, 5.0f, line * 12.0f, r, g, 0, alpha, hud->Canvas); + } + line++; + + // Max Hit + if (pmc.PagefileUsage > MaxMemoryHit) + { + MaxMemoryHit = pmc.PagefileUsage; + } + + if (DrawSLH) { + wchar_t objectName[512]; + swprintf_s(objectName, 512, L"Max memory hit: %S (%llu bytes)", FormatBytes(MaxMemoryHit, str), MaxMemoryHit); + RenderTextSLH(objectName, 5.0f, line * 12.0f, r, g, 0, alpha, hud->Canvas); + } + + line++; + } + + if (DrawSLH && hud->WorldInfo) + { + //screenLogger->ClearMessages(); + //logger.writeToConsoleOnly(string_format("Number of streaming levels: %d\n", hud->WorldInfo->StreamingLevels.Count), true); + if (hud->WorldInfo->StreamingLevels.Count > 0) { + int yIndex = 3; //Start at line 3 (starting at 0) + for (int i = 0; i < hud->WorldInfo->StreamingLevels.Count; i++) { + std::wstringstream ss; + ULevelStreaming* sl = hud->WorldInfo->StreamingLevels.Data[i]; + if (sl->bShouldBeLoaded || sl->bIsVisible) { + unsigned char r = 255; + unsigned char g = 255; + unsigned char b = 255; + + ss << sl->PackageName.GetName(); + if (sl->PackageName.Number > 0) + { + ss << "_" << sl->PackageName.Number; + } + if (sl->bIsVisible) + { + ss << " Visible"; + r = 0; + g = 255; + b = 0; + } + else if (sl->bHasLoadRequestPending) + { + ss << " Loading"; + r = 255; + g = 229; + b = 0; + } + else if (sl->bHasUnloadRequestPending) + { + ss << " Unloading"; + r = 0; + g = 255; + b = 229; + } + else if (sl->bShouldBeLoaded && sl->LoadedLevel) + { + ss << " Loaded"; + r = 255; + g = 255; + b = 0; + } + else if (sl->bShouldBeLoaded) + { + ss << " Pending load"; + r = 255; + g = 175; + b = 0; + } + const std::wstring msg = ss.str(); + RenderTextSLH(msg, 5, yIndex * 12.0f, r, g, b, 1.0f, hud->Canvas); + yIndex++; + } + } + } + } + } + } + + ProcessEvent_orig(Context, Function, Parms, Result); +} + +// ====================================================================== + + +SPI_IMPLEMENT_ATTACH +{ + //Common::OpenConsole(); + + auto _ = SDKInitializer::Instance(); +/*writeln(L"Attach - names at 0x%p, objects at 0x%p", + SDKInitializer::Instance()->GetBioNamePools(), + SDKInitializer::Instance()->GetObjects());*/ + +if (auto rc = InterfacePtr->FindPattern((void**)&ProcessEvent, "40 55 41 56 41 57 48 81 EC 90 00 00 00 48 8D 6C 24 20"); + rc != SPIReturn::Success) +{ + //writeln(L"Attach - failed to find ProcessEvent pattern: %d / %s", rc, SPIReturnToString(rc)); + return false; +} + + +if (auto rc = InterfacePtr->InstallHook(SLHHOOK "ProcessEvent", ProcessEvent, biohud_hook, (void**)&ProcessEvent_orig); + rc != SPIReturn::Success) +{ + //writeln(L"Attach - failed to hook ProcessEvent: %d / %s", rc, SPIReturnToString(rc)); + return false; +} + +return true; +} + +SPI_IMPLEMENT_DETACH +{ + //Common::CloseConsole(); + return true; +} diff --git a/LE3StreamingLevelsHUD/LE3StreamingLevelsHUD.vcxproj b/LE3StreamingLevelsHUD/LE3StreamingLevelsHUD.vcxproj new file mode 100644 index 0000000..139b470 --- /dev/null +++ b/LE3StreamingLevelsHUD/LE3StreamingLevelsHUD.vcxproj @@ -0,0 +1,99 @@ + + + + + Debug + x64 + + + Release + x64 + + + + + + + + {8da34ede-7d5c-4da3-8841-78ab05e8c6d2} + + + + 16.0 + Win32Proj + {24261db1-e217-4234-b048-1acea2f67237} + LE1StreamingLevelsHUD + 10.0 + + + + DynamicLibrary + true + v142 + Unicode + + + DynamicLibrary + false + v142 + true + Unicode + + + + + + + + + + + + + + + true + .asi + + + false + .asi + + + + Level3 + true + ASI_DEBUG;_CRT_SECURE_NO_WARNINGS;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + false + stdcpp17 + stdc17 + /bigobj %(AdditionalOptions) + + + Console + true + + + + + Level3 + true + true + true + _CRT_SECURE_NO_WARNINGS;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + false + stdcpp17 + stdc17 + /bigobj %(AdditionalOptions) + + + Console + true + true + true + + + + + + \ No newline at end of file