diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..568ff5a --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,20 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + + - package-ecosystem: "gitsubmodule" + directory: "/" + schedule: + interval: "daily" + groups: + submodules: + patterns: + - "*" \ No newline at end of file diff --git a/.github/funding.yml b/.github/funding.yml new file mode 100644 index 0000000..4df779f --- /dev/null +++ b/.github/funding.yml @@ -0,0 +1,6 @@ +# These are supported funding model platforms + +github: [ThirteenAG] +ko_fi: thirteenag +patreon: ThirteenAG +custom: [https://paypal.me/SergeyP13, https://boosty.to/thirteenag/donate] \ No newline at end of file diff --git a/.github/workflows/msvc_x86.yml b/.github/workflows/msvc_x86.yml index 1abc172..7e6c5a3 100644 --- a/.github/workflows/msvc_x86.yml +++ b/.github/workflows/msvc_x86.yml @@ -1,57 +1,97 @@ -name: Build +name: GitHub Actions Build on: - pull_request: push: - release: - types: published + paths-ignore: + - "**/*.md" + - '**/*.txt' + branches: + - '**' + pull_request: + paths-ignore: + - "**/*.md" + - '**/*.txt' + workflow_dispatch: + inputs: + release: + description: "Create a release" + type: choice + required: false + default: 'false' + options: + - 'true' + - 'false' + +concurrency: + group: ${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: write jobs: build: - runs-on: windows-2022 - strategy: - matrix: - platform: [Win32] - buildtype: [Release] + runs-on: windows-latest steps: + - name: Checkout Repository + uses: actions/checkout@v4 + with: + submodules: recursive + - name: Add msbuild to PATH - uses: microsoft/setup-msbuild@v1.0.2 - - uses: actions/checkout@v2 + uses: microsoft/setup-msbuild@main + + - name: Auto Increment Version + uses: MCKanpolat/auto-semver-action@v1 + id: versioning with: - submodules: 'true' + releaseType: minor + incrementPerCommit: false + github_token: ${{ secrets.GITHUB_TOKEN }} + - name: Configure build - run: | - ./premake5 vs2022 + run: ./premake5 vs2022 --with-version=${{ steps.versioning.outputs.version }} + - name: Build run: | - msbuild -m build/MaxPayne3.FusionFix.sln /property:Configuration=${{matrix.buildtype}} /property:Platform=${{matrix.platform}} - - name: Move binaries to data + msbuild -m build/MaxPayne3.FusionFix.sln /property:Configuration=Release /property:Platform=Win32 + + - name: Download Ultimate ASI Loader x86 + uses: robinraju/release-downloader@v1.8 + with: + repository: "ThirteenAG/Ultimate-ASI-Loader" + tag: "Win32-latest" + fileName: "dinput8-Win32.zip" + + - name: Unpack dependencies + run: | + 7z x dinput8-Win32.zip -odata/ -y + del dinput8-Win32.zip + del data\dinput8-Win32.SHA512 + + - name: Pack binaries run: | - cp "./bin/MaxPayne3.FusionFix.asi" "./data/plugins/MaxPayne3.FusionFix.asi" - 7z a MaxPayne3.FusionFix.zip ./data/* - - name: Upload artifact to actions - uses: actions/upload-artifact@v2 + ./release.bat + + - name: Upload artifact + uses: actions/upload-artifact@v4 with: name: MaxPayne3.FusionFix.zip - path: ./data/* - - name: Create Release - id: create_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: v1.${{ github.run_number }} - release_name: MaxPayne3.FusionFix v1.${{ github.run_number }} - draft: false - prerelease: false - - name: Upload Release Asset - id: upload-release-asset - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + path: data/* + + - name: Upload Release + if: | + github.event.inputs.release == 'true' && + github.ref_name == 'main' && + (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && + github.repository == 'ThirteenAG/MaxPayne3.FusionFix' + uses: ncipollo/release-action@main with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./MaxPayne3.FusionFix.zip - asset_name: MaxPayne3.FusionFix.zip - asset_content_type: application/zip + token: ${{ secrets.GITHUB_TOKEN }} + allowUpdates: false + name: MaxPayne3.FusionFix v${{ steps.versioning.outputs.version }} + bodyFile: "release.md" + tag: v${{ steps.versioning.outputs.version }} + artifacts: MaxPayne3.FusionFix.zip + diff --git a/.gitignore b/.gitignore index 6ef9495..64985ea 100644 --- a/.gitignore +++ b/.gitignore @@ -329,3 +329,9 @@ ASALocalRun/ # MFractors (Xamarin productivity tool) working folder .mfractor/ + +data/**/*.img +data/update/common/shaders/**/*.fxc + +data/plugins/MaxPayne3.FusionFix.asi +MaxPayne3.FusionFix.zip diff --git a/data/plugins/MaxPayne3.FusionFix.ini b/data/plugins/MaxPayne3.FusionFix.ini index 0f0d53f..c3f5635 100644 --- a/data/plugins/MaxPayne3.FusionFix.ini +++ b/data/plugins/MaxPayne3.FusionFix.ini @@ -2,3 +2,12 @@ HideSkipButton = 1 DisableGlobalLeaderboards = 1 // Fixes Hoboken Alleys map crash in coop OutlinesSizeMultiplier = 1.0f +BorderlessWindowed = 1 +GamepadIcons = 0 +; 0 - Xbox 360 +; 1 - Xbox One +; 2 - Playstation 3 +; 3 - Playstation 4 +; 4 - Playstation 5 +; 5 - Nintendo Switch +; 6 - Steam Deck diff --git a/data/update/platform/textures/buttons_pc.wtd b/data/update/platform/textures/buttons_pc.wtd new file mode 100644 index 0000000..4b18ea1 Binary files /dev/null and b/data/update/platform/textures/buttons_pc.wtd differ diff --git a/data/update/platform/textures/controller_360.wtd b/data/update/platform/textures/controller_360.wtd new file mode 100644 index 0000000..39e701f Binary files /dev/null and b/data/update/platform/textures/controller_360.wtd differ diff --git a/premake5.exe b/premake5.exe index c73da1f..1a637aa 100644 Binary files a/premake5.exe and b/premake5.exe differ diff --git a/premake5.lua b/premake5.lua index 787278d..8a62ab3 100644 --- a/premake5.lua +++ b/premake5.lua @@ -1,23 +1,60 @@ +newoption { + trigger = "with-version", + value = "STRING", + description = "Current version", + default = "1.0", +} + workspace "MaxPayne3.FusionFix" configurations { "Release", "Debug" } architecture "x86" location "build" - buildoptions {"-std:c++latest"} + cppdialect "C++latest" kind "SharedLib" language "C++" targetdir "bin/%{cfg.buildcfg}" targetextension ".asi" + buildoptions { "/dxifcInlineFunctions-" } defines { "rsc_CompanyName=\"MaxPayne3.FusionFix\"" } defines { "rsc_LegalCopyright=\"MaxPayne3.FusionFix\""} - defines { "rsc_FileVersion=\"1.0.0.0\"", "rsc_ProductVersion=\"1.0.0.0\"" } defines { "rsc_InternalName=\"%{prj.name}\"", "rsc_ProductName=\"%{prj.name}\"", "rsc_OriginalFilename=\"%{prj.name}.dll\"" } defines { "rsc_FileDescription=\"MaxPayne3.FusionFix\"" } defines { "rsc_UpdateUrl=\"https://github.com/ThirteenAG/MaxPayne3.FusionFix\"" } + local major = 1 + local minor = 0 + local build = 0 + local revision = 0 + if(_OPTIONS["with-version"]) then + local t = {} + for i in _OPTIONS["with-version"]:gmatch("([^.]+)") do + t[#t + 1], _ = i:gsub("%D+", "") + end + while #t < 4 do t[#t + 1] = 0 end + major = math.min(tonumber(t[1]), 255) + minor = math.min(tonumber(t[2]), 255) + build = math.min(tonumber(t[3]), 65535) + revision = math.min(tonumber(t[4]), 65535) + end + defines { "rsc_FileVersion_MAJOR=" .. major } + defines { "rsc_FileVersion_MINOR=" .. minor } + defines { "rsc_FileVersion_BUILD=" .. build } + defines { "rsc_FileVersion_REVISION=" .. revision } + defines { "rsc_FileVersion=\"" .. major .. "." .. minor .. "." .. build .. "\"" } + defines { "rsc_ProductVersion=\"" .. major .. "." .. minor .. "." .. build .. "\"" } + + defines { "_CRT_SECURE_NO_WARNINGS" } + includedirs { "source" } - files { "source/dllmain.cpp" } + includedirs { "source/includes" } + includedirs { "source/ledsdk" } + includedirs { "source/dxsdk" } + libdirs { "source/ledsdk" } + libdirs { "source/dxsdk" } + files { "source/*.h", "source/*.hpp", "source/*.cpp", "source/*.hxx", "source/*.ixx" } files { "source/resources/Versioninfo.rc" } + links { "LogitechLEDLib.lib" } includedirs { "external/hooking" } includedirs { "external/injector/include" } @@ -61,6 +98,6 @@ workspace "MaxPayne3.FusionFix" defines { "NDEBUG" } optimize "On" staticruntime "On" - + project "MaxPayne3.FusionFix" - setpaths("E:/Games/Steam/steamapps/common/Max Payne 3/Max Payne 3/", "MaxPayne3.exe", "plugins/") \ No newline at end of file + setpaths("Z:/WFP/Games/Max Payne/Max Payne 3/", "MaxPayne3.exe", "plugins/") \ No newline at end of file diff --git a/readme.md b/readme.md index 4f6d802..fa42c3c 100644 --- a/readme.md +++ b/readme.md @@ -1,4 +1,4 @@ -[![Actions Status: Release](https://github.com/ThirteenAG/MaxPayne3.FusionFix/actions/workflows/msvc_x86.yml/badge.svg)](https://github.com/ThirteenAG/MaxPayne3.FusionFix/actions) [![Discord](https://img.shields.io/badge/chat-on%20discord-7289da.svg?logo=discord)](https://discord.gg/RaZXpKExNj) +[![Actions Status: Release](https://github.com/ThirteenAG/MaxPayne3.FusionFix/actions/workflows/msvc_x86.yml/badge.svg)](https://github.com/ThirteenAG/MaxPayne3.FusionFix/actions)

diff --git a/release.bat b/release.bat new file mode 100644 index 0000000..c33b59f --- /dev/null +++ b/release.bat @@ -0,0 +1,3 @@ +copy bin\MaxPayne3.FusionFix.asi data\plugins\MaxPayne3.FusionFix.asi + +7z a "MaxPayne3.FusionFix.zip" ".\data\*" diff --git a/release.md b/release.md new file mode 100644 index 0000000..59b011b --- /dev/null +++ b/release.md @@ -0,0 +1 @@ +[README](https://github.com/ThirteenAG/MaxPayne3.FusionFix#readme) \ No newline at end of file diff --git a/source/buttons.ixx b/source/buttons.ixx new file mode 100644 index 0000000..bddb765 --- /dev/null +++ b/source/buttons.ixx @@ -0,0 +1,189 @@ +module; + +#include +#include + +export module buttons; + +import common; +import settings; + +class Buttons +{ +private: + static inline std::vector btnPrefix = { + "", //XBOX360 + "XBONE_", + "PS3_", + "PS4_", + "PS5_", + "SWITCH_", + "SD_", + }; + + static inline std::vector buttons = { + "UP_ARROW", "", "", "DOWN_ARROW", "", "", "LEFT_ARROW", "", "", "RIGHT_ARROW", "", "", "DPAD_UP", "", "", + "DPAD_DOWN", "", "", "DPAD_LEFT", "", "", "DPAD_RIGHT", "", "", "DPAD_NONE", "", "", "DPAD_ALL", "", "", + "DPAD_UPDOWN", "", "", "DPAD_LEFTRIGHT", "", "", "LSTICK_UP", "", "", "LSTICK_DOWN", "", "", "LSTICK_LEFT", + "", "", "LSTICK_RIGHT", "", "", "LSTICK_NONE", "", "", "LSTICK_ALL", "", "", "LSTICK_UPDOWN", "", "", + "LSTICK_LEFTRIGHT", "", "", "RSTICK_UP", "", "", "RSTICK_DOWN", "", "", "RSTICK_LEFT", "", "", "RSTICK_RIGHT", + "", "", "RSTICK_NONE", "", "", "RSTICK_ALL", "", "", "RSTICK_UPDOWN", "", "", "RSTICK_LEFTRIGHT", "", "", "A_BUTT", + "", "", "B_BUTT", "", "", "X_BUTT", "", "", "Y_BUTT", "", "", "LB_BUTT", "", "", "LT_BUTT", "", "", "RB_BUTT", "", + "", "RT_BUTT", "", "", "START_BUTT", "", "", "BACK_BUTT", "", "", "A_BUTT", "", "", "B_BUTT" + }; + + static inline std::vector controllerTexPtrs; + static inline std::vector controllerTexHashes; + static inline std::vector> buttonTexPtrs; + static inline void** gameButtonPtrs = nullptr; + static inline void** gameControllerPtrs = nullptr; + static inline void** controllerDstTexPtr = nullptr; + static void ButtonsCallback() + { + auto prefvalueindex = FusionFixSettings.GetInt("PREF_BUTTONS"); + if (gameButtonPtrs) + { + for (auto b = buttons.begin(); b < buttons.end(); b++) + { + if (!b->empty()) + { + auto i = std::distance(std::begin(buttons), b); + auto ptr = buttonTexPtrs[prefvalueindex][i]; + if (ptr) + gameButtonPtrs[i] = ptr; + } + } + } + } + + static inline injector::hook_back CTxdStore__LoadTexture; + static void __fastcall LoadCustomButtons(void* dst, void* edx, const char* name) + { + CTxdStore__LoadTexture.fun(dst, edx, name); + + buttonTexPtrs.clear(); + buttonTexPtrs.resize(btnPrefix.size()); + + for (auto& v : buttonTexPtrs) + v.resize(buttons.size()); + + for (auto prefix = btnPrefix.begin(); prefix < btnPrefix.end(); prefix++) + { + for (auto b = buttons.begin(); b < buttons.end(); b++) + { + if (!b->empty()) + { + auto texName = *prefix + *b; + CTxdStore__LoadTexture.fun(&buttonTexPtrs[std::distance(std::begin(btnPrefix), prefix)][std::distance(std::begin(buttons), b)], edx, texName.c_str()); + } + } + } + + ButtonsCallback(); + } + + static inline bool bIsController = false; + static inline injector::hook_back hbsub_DCFB30; + static uint32_t __cdecl sub_DCFB30(const char* name, int a2) + { + if (iequals(std::string(name), "controller")) + { + bIsController = true; + + controllerTexHashes.clear(); + controllerTexHashes.resize(btnPrefix.size()); + controllerTexPtrs.clear(); + controllerTexPtrs.resize(btnPrefix.size()); + + for (auto prefix = btnPrefix.begin(); prefix < btnPrefix.end(); prefix++) + { + auto texName = *prefix + "CONTROLLER"; + controllerTexHashes[std::distance(std::begin(btnPrefix), prefix)] = hbsub_DCFB30.fun(texName.c_str(), a2); + } + } + return hbsub_DCFB30.fun(name, a2); + } + + static inline injector::hook_back hbsub_CE8060; + static void* __fastcall sub_CE8060(void* _this, void* edx, uint32_t hash) + { + if (bIsController) + { + for (auto prefix = btnPrefix.begin(); prefix < btnPrefix.end(); prefix++) + { + auto texName = *prefix + "CONTROLLER"; + controllerTexPtrs[std::distance(std::begin(btnPrefix), prefix)] = hbsub_CE8060.fun(_this, edx, controllerTexHashes[std::distance(std::begin(btnPrefix), prefix)]); + } + } + return hbsub_CE8060.fun(_this, edx, hash); + } + +public: + Buttons() + { + FusionFix::onInitEvent() += []() + { + auto pattern = hook::pattern("B9 ? ? ? ? E8 ? ? ? ? 68 ? ? ? ? B9 ? ? ? ? E8 ? ? ? ? 68 ? ? ? ? B9 ? ? ? ? E8 ? ? ? ? 68 ? ? ? ? B9 ? ? ? ? E8 ? ? ? ? 68 ? ? ? ? B9 ? ? ? ? E8 ? ? ? ? 68 ? ? ? ? B9 ? ? ? ? E8 ? ? ? ? 68 ? ? ? ? B9 ? ? ? ? E8 ? ? ? ? 68 ? ? ? ? B9 ? ? ? ? E8 ? ? ? ? 68 ? ? ? ? B9 ? ? ? ? E8 ? ? ? ? 68 ? ? ? ? B9 ? ? ? ? E8 ? ? ? ? 68 ? ? ? ? B9 ? ? ? ? E8 ? ? ? ? 68 ? ? ? ? B9 ? ? ? ? E8 ? ? ? ? 68 ? ? ? ? B9 ? ? ? ? E8 ? ? ? ? 68 ? ? ? ? B9 ? ? ? ? E8 ? ? ? ? 68 ? ? ? ? B9 ? ? ? ? E8 ? ? ? ? 68 ? ? ? ? B9 ? ? ? ? E8 ? ? ? ? 68 ? ? ? ? B9 ? ? ? ? E8 ? ? ? ? 68 ? ? ? ? B9 ? ? ? ? E8 ? ? ? ? 68 ? ? ? ? B9 ? ? ? ? E8 ? ? ? ? 68 ? ? ? ? B9 ? ? ? ? E8 ? ? ? ? 68 ? ? ? ? B9 ? ? ? ? E8 ? ? ? ? 68 ? ? ? ? B9 ? ? ? ? E8 ? ? ? ? 68 ? ? ? ? B9 ? ? ? ? E8 ? ? ? ? 68 ? ? ? ? B9 ? ? ? ? E8 ? ? ? ? 68 ? ? ? ? B9 ? ? ? ? E8 ? ? ? ? 68 ? ? ? ? B9 ? ? ? ? E8 ? ? ? ? 68 ? ? ? ? B9 ? ? ? ? E8 ? ? ? ? 68 ? ? ? ? B9 ? ? ? ? E8 ? ? ? ? 68 ? ? ? ? B9 ? ? ? ? E8 ? ? ? ? 68 ? ? ? ? B9 ? ? ? ? E8 ? ? ? ? 68 ? ? ? ? B9 ? ? ? ? E8 ? ? ? ? 68 ? ? ? ? B9 ? ? ? ? E8 ? ? ? ? 68 ? ? ? ? B9 ? ? ? ? E8 ? ? ? ? 68 ? ? ? ? B9 ? ? ? ? E8 ? ? ? ? 68 ? ? ? ? B9 ? ? ? ? E8 ? ? ? ? 68 ? ? ? ? B9 ? ? ? ? E8 ? ? ? ? 68 ? ? ? ? B9 ? ? ? ? E8 ? ? ? ? 68 ? ? ? ? B9 ? ? ? ? E8 ? ? ? ? 68 ? ? ? ? B9 ? ? ? ? E8 ? ? ? ? 68 ? ? ? ? B9 ? ? ? ? E8 ? ? ? ? 68 ? ? ? ? B9 ? ? ? ? E8 ? ? ? ? 68 ? ? ? ? B9 ? ? ? ? E8 ? ? ? ? B9"); + gameButtonPtrs = *pattern.get_first(1); + + pattern = hook::pattern("E8 ? ? ? ? 83 C4 08 EB 02 33 C0 8B 4C 24 08 8B 16"); + hbsub_DCFB30.fun = injector::MakeCALL(pattern.get_first(0), sub_DCFB30).get(); + + pattern = hook::pattern("E8 ? ? ? ? 89 86 ? ? ? ? 85 C0 74 1B"); + hbsub_CE8060.fun = injector::MakeCALL(pattern.get_first(0), sub_CE8060).get(); + + pattern = hook::pattern("E8 ? ? ? ? B9 ? ? ? ? E8 ? ? ? ? C6 05 ? ? ? ? ? 5E"); + CTxdStore__LoadTexture.fun = injector::MakeCALL(pattern.get_first(), LoadCustomButtons).get(); + + pattern = hook::pattern("89 86 ? ? ? ? 85 C0 74 1B"); + struct LoadControllerTexture + { + void operator()(injector::reg_pack& regs) + { + *(uint32_t*)(regs.esi + 0x104) = regs.eax; + if (bIsController) + { + controllerDstTexPtr = (void**)(regs.esi + 0x104); + auto v = FusionFixSettings.GetInt("PREF_BUTTONS"); + if (v > 6) v = 0; else if (v < 0) v = 6; + if (controllerTexPtrs[v]) + *controllerDstTexPtr = controllerTexPtrs[v]; + } + bIsController = false; + } + }; injector::MakeInline(pattern.get_first(0), pattern.get_first(6)); + + FusionFix::onIniFileChange() += []() + { + ButtonsCallback(); + }; + + FusionFix::onMenuOptionChange() += [](std::string_view name, int32_t oldVal, int32_t curVal) + { + if (name == "MS_Control.ConfigurationList") + { + if (oldVal == 3 && curVal == 4) + { + auto v = FusionFixSettings.GetInt("PREF_BUTTONS") + 1; + if (v > 6) v = 0; else if (v < 0) v = 6; + + CIniReader iniWriter(""); + iniWriter.WriteInteger("MAIN", "GamepadIcons", v); + if (controllerDstTexPtr && controllerTexPtrs[v]) + *controllerDstTexPtr = controllerTexPtrs[v]; + } + else if (oldVal == 4 && curVal == 3) + { + auto v = FusionFixSettings.GetInt("PREF_BUTTONS") - 1; + if (v > 6) v = 0; else if (v < 0) v = 6; + + CIniReader iniWriter(""); + iniWriter.WriteInteger("MAIN", "GamepadIcons", v); + if (controllerDstTexPtr && controllerTexPtrs[v]) + *controllerDstTexPtr = controllerTexPtrs[v]; + } + } + }; + }; + } +} Buttons; \ No newline at end of file diff --git a/source/common.hxx b/source/common.hxx new file mode 100644 index 0000000..0c32eb1 --- /dev/null +++ b/source/common.hxx @@ -0,0 +1,22 @@ +#pragma once +#define WIN32_LEAN_AND_MEAN +#include +#include +#include "IniReader.h" +#include "injector/injector.hpp" +#include "injector/calling.hpp" +#include "injector/hooking.hpp" +#include "injector/assembly.hpp" +#include "injector/utility.hpp" +#include "Hooking.Patterns.h" +#include "ModuleList.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/source/common.ixx b/source/common.ixx new file mode 100644 index 0000000..2ad1abf --- /dev/null +++ b/source/common.ixx @@ -0,0 +1,806 @@ +module; + +#include + +export module common; + +import ; + +export class FusionFix +{ +public: + template + class Event : public std::function + { + public: + using std::function::function; + + private: + std::vector> handlers; + + public: + void operator+=(std::function&& handler) + { + handlers.push_back(handler); + } + + void executeAll(Args... args) const + { + if (!handlers.empty()) + { + for (auto& handler : handlers) + { + handler(args...); + } + } + } + + std::reference_wrapper>> executeAllAsync(Args... args) const + { + static std::vector> pendingFutures; + if (!handlers.empty()) + { + for (auto& handler : handlers) + { + pendingFutures.emplace_back(std::async(std::launch::async, std::cref(handler), args...)); + } + } + return std::ref(pendingFutures); + } + }; + +public: + static Event<>& onInitEvent() { + static Event<> InitEvent; + return InitEvent; + } + static Event<>& onInitEventAsync() { + static Event<> InitEventAsync; + return InitEventAsync; + } + static Event<>& onAfterUALRestoredIATEvent() { + static Event<> AfterUALRestoredIATEvent; + return AfterUALRestoredIATEvent; + } + static Event<>& onShutdownEvent() { + static Event<> ShutdownEvent; + return ShutdownEvent; + } + static Event<>& onGameInitEvent() { + static Event<> GameInitEvent; + return GameInitEvent; + } + static Event<>& onGameProcessEvent() { + static Event<> GameProcessEvent; + return GameProcessEvent; + } + static Event<>& onMenuDrawingEvent() { + static Event<> MenuDrawingEvent; + return MenuDrawingEvent; + } + static Event<>& onMenuEnterEvent() { + static Event<> MenuEnterEvent; + return MenuEnterEvent; + } + static Event<>& onMenuExitEvent() { + static Event<> MenuExitEvent; + return MenuExitEvent; + } + static Event<>& onBeforeReset() { + static Event<> BeforeReset; + return BeforeReset; + } + static Event<>& onIniFileChange() { + static Event<> IniFileChange; + return IniFileChange; + } + static Event& onMenuOptionChange() { + static Event MenuOptionChange; + return MenuOptionChange; + } + + struct D3D9 { + static Event& onBeforeCreateDevice() { + static Event BeforeCreateDevice; + return BeforeCreateDevice; + } + static Event& onAfterCreateDevice() { + static Event AfterCreateDevice; + return AfterCreateDevice; + } + static Event& onBeginScene() { + static Event BeginScene; + return BeginScene; + } + static Event& onEndScene() { + static Event EndScene; + return EndScene; + } + static Event& onBeforeReset() { + static Event BeforeReset; + return BeforeReset; + } + static Event& onAfterReset() { + static Event AfterReset; + return AfterReset; + } + static Event& onSetVertexShaderConstantF() { + static Event SetVertexShaderConstantF; + return SetVertexShaderConstantF; + } + static Event& onSetPixelShaderConstantF() { + static Event SetPixelShaderConstantF; + return SetPixelShaderConstantF; + } + static Event& onBeforeCreateTexture() { + static Event BeforeCreateTexture; + return BeforeCreateTexture; + } + static Event& onAfterCreateTexture() { + static Event AfterCreateTexture; + return AfterCreateTexture; + } + static Event& onSetTexture() { + static Event SetTexture; + return SetTexture; + } + }; +}; + +export template +T GetModulePath(HMODULE hModule) +{ + static constexpr auto INITIAL_BUFFER_SIZE = MAX_PATH; + static constexpr auto MAX_ITERATIONS = 7; + + if constexpr (std::is_same_v) + { + std::u16string ret; + std::filesystem::path pathret; + auto bufferSize = INITIAL_BUFFER_SIZE; + for (size_t iterations = 0; iterations < MAX_ITERATIONS; ++iterations) + { + ret.resize(bufferSize); + size_t charsReturned = 0; + charsReturned = GetModuleFileNameW(hModule, (LPWSTR)&ret[0], bufferSize); + if (charsReturned < ret.length()) + { + ret.resize(charsReturned); + pathret = ret; + return pathret; + } + else + { + bufferSize *= 2; + } + } + } + else + { + T ret; + auto bufferSize = INITIAL_BUFFER_SIZE; + for (size_t iterations = 0; iterations < MAX_ITERATIONS; ++iterations) + { + ret.resize(bufferSize); + size_t charsReturned = 0; + if constexpr (std::is_same_v) + charsReturned = GetModuleFileNameA(hModule, &ret[0], bufferSize); + else + charsReturned = GetModuleFileNameW(hModule, &ret[0], bufferSize); + if (charsReturned < ret.length()) + { + ret.resize(charsReturned); + return ret; + } + else + { + bufferSize *= 2; + } + } + } + return T(); +} + +export template +T GetThisModulePath() +{ + HMODULE hm = NULL; + GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (LPCWSTR)&FusionFix::onInitEvent, &hm); + T r = GetModulePath(hm); + if constexpr (std::is_same_v) + return r.parent_path(); + else if constexpr (std::is_same_v) + r = r.substr(0, r.find_last_of("/\\") + 1); + else + r = r.substr(0, r.find_last_of(L"/\\") + 1); + return r; +} + +export template +T GetThisModuleName() +{ + HMODULE hm = NULL; + GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (LPCWSTR)&FusionFix::onInitEvent, &hm); + const T moduleFileName = GetModulePath(hm); + + if constexpr (std::is_same_v) + return moduleFileName.filename(); + else if constexpr (std::is_same_v) + return moduleFileName.substr(moduleFileName.find_last_of("/\\") + 1); + else + return moduleFileName.substr(moduleFileName.find_last_of(L"/\\") + 1); +} + +export template +T GetExeModulePath() +{ + T r = GetModulePath(NULL); + + if constexpr (std::is_same_v) + return r.parent_path(); + else if constexpr (std::is_same_v) + r = r.substr(0, r.find_last_of("/\\") + 1); + else + r = r.substr(0, r.find_last_of(L"/\\") + 1); + return r; +} + +export template +T GetExeModuleName() +{ + const T moduleFileName = GetModulePath(NULL); + if constexpr (std::is_same_v) + return moduleFileName.filename(); + else if constexpr (std::is_same_v) + return moduleFileName.substr(moduleFileName.find_last_of("/\\") + 1); + else + return moduleFileName.substr(moduleFileName.find_last_of(L"/\\") + 1); +} + +export template +bool iequals(const T& s1, const V& s2) +{ + T str1(s1); T str2(s2); + std::transform(str1.begin(), str1.end(), str1.begin(), ::tolower); + std::transform(str2.begin(), str2.end(), str2.begin(), ::tolower); + return (str1 == str2); +} + +export inline void CreateThreadAutoClose(LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId) +{ + CloseHandle(CreateThread(lpThreadAttributes, dwStackSize, lpStartAddress, lpParameter, dwCreationFlags, lpThreadId)); +} + +export inline bool IsModuleUAL(HMODULE mod) +{ + if (GetProcAddress(mod, "IsUltimateASILoader") != NULL) + return true; + return false; +} + +export bool IsUALPresent() { + for (const auto& entry : std::stacktrace::current()) { + HMODULE hModule = NULL; + if (GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (LPCSTR)entry.native_handle(), &hModule)) { + if (IsModuleUAL(hModule)) + return true; + } + } + return false; +} + +export class CallbackHandler +{ +public: + static inline void RegisterCallback(std::function&& fn) + { + fn(); + } + + static inline void RegisterCallback(std::wstring_view module_name, std::function&& fn, bool bOnUnload = false) + { + if (!bOnUnload && (module_name.empty() || GetModuleHandleW(module_name.data()) != NULL)) + { + fn(); + } + else + { + RegisterDllNotification(); + if (!bOnUnload) + GetOnModuleLoadCallbackList().emplace(module_name, std::forward>(fn)); + else + GetOnModuleUnloadCallbackList().emplace(module_name, std::forward>(fn)); + } + } + + static inline void RegisterCallback(std::function&& fn) + { + RegisterDllNotification(); + GetOnAnyModuleLoadCallbackList().emplace_back(std::forward>(fn)); + } + + static inline void RegisterCallback(std::function&& fn, bool bPatternNotFound, ptrdiff_t offset = 0x1100, uint32_t* ptr = nullptr) + { + if (!bPatternNotFound) + { + fn(); + } + else + { + auto mh = GetModuleHandle(NULL); + IMAGE_NT_HEADERS* ntHeader = (IMAGE_NT_HEADERS*)((DWORD)mh + ((IMAGE_DOS_HEADER*)mh)->e_lfanew); + if (ptr == nullptr) + ptr = (uint32_t*)((DWORD)mh + ntHeader->OptionalHeader.BaseOfCode + ntHeader->OptionalHeader.SizeOfCode - offset); + std::thread([](std::function&& fn, uint32_t* ptr, uint32_t val) + { + while (*ptr == val) + std::this_thread::yield(); + + fn(); + }, fn, ptr, *ptr).detach(); + } + } + + static inline void RegisterCallback(std::function&& fn, hook::pattern pattern) + { + if (!pattern.empty()) + { + fn(); + } + else + { + auto* ptr = new ThreadParams{ fn, pattern }; + CreateThreadAutoClose(0, 0, (LPTHREAD_START_ROUTINE)&ThreadProc, (LPVOID)ptr, 0, NULL); + } + } + +private: + static inline void invokeOnModuleLoad(std::wstring_view module_name) + { + if (GetOnModuleLoadCallbackList().count(module_name.data())) + { + GetOnModuleLoadCallbackList().at(module_name.data())(); + } + } + + static inline void invokeOnUnload(std::wstring_view module_name) + { + if (GetOnModuleUnloadCallbackList().count(module_name.data())) + { + GetOnModuleUnloadCallbackList().at(module_name.data())(); + } + } + + static inline void invokeOnAnyModuleLoad(HMODULE mod) + { + if (!GetOnAnyModuleLoadCallbackList().empty()) + { + for (auto& f : GetOnAnyModuleLoadCallbackList()) + { + f(mod); + } + } + } + + static inline void invokeOnAnyModuleUnload(HMODULE mod) + { + if (!GetOnAnyModuleUnloadCallbackList().empty()) + { + for (auto& f : GetOnAnyModuleUnloadCallbackList()) + { + f(mod); + } + } + } + + static inline void InvokeAll() + { + for (auto&& fn : GetOnModuleLoadCallbackList()) + fn.second(); + } + +private: + struct Comparator + { + bool operator() (const std::wstring& s1, const std::wstring& s2) const + { + std::wstring str1(s1.length(), ' '); + std::wstring str2(s2.length(), ' '); + std::transform(s1.begin(), s1.end(), str1.begin(), tolower); + std::transform(s2.begin(), s2.end(), str2.begin(), tolower); + return str1 < str2; + } + }; + + static inline std::map, Comparator>& GetOnModuleLoadCallbackList() + { + static std::map, Comparator> onModuleLoad; + return onModuleLoad; + } + + static inline std::map, Comparator>& GetOnModuleUnloadCallbackList() + { + static std::map, Comparator> onModuleUnload; + return onModuleUnload; + } + + static inline std::vector>& GetOnAnyModuleLoadCallbackList() + { + return onAnyModuleLoad; + } + + static inline std::vector>& GetOnAnyModuleUnloadCallbackList() + { + return onAnyModuleUnload; + } + + struct ThreadParams + { + std::function fn; + hook::pattern pattern; + }; + + typedef NTSTATUS(NTAPI* _LdrRegisterDllNotification) (ULONG, PVOID, PVOID, PVOID); + typedef NTSTATUS(NTAPI* _LdrUnregisterDllNotification) (PVOID); + + typedef struct _LDR_DLL_LOADED_NOTIFICATION_DATA + { + ULONG Flags; //Reserved. + PUNICODE_STRING FullDllName; //The full path name of the DLL module. + PUNICODE_STRING BaseDllName; //The base file name of the DLL module. + PVOID DllBase; //A pointer to the base address for the DLL in memory. + ULONG SizeOfImage; //The size of the DLL image, in bytes. + } LDR_DLL_LOADED_NOTIFICATION_DATA, LDR_DLL_UNLOADED_NOTIFICATION_DATA, * PLDR_DLL_LOADED_NOTIFICATION_DATA, * PLDR_DLL_UNLOADED_NOTIFICATION_DATA; + + typedef union _LDR_DLL_NOTIFICATION_DATA + { + LDR_DLL_LOADED_NOTIFICATION_DATA Loaded; + LDR_DLL_UNLOADED_NOTIFICATION_DATA Unloaded; + } LDR_DLL_NOTIFICATION_DATA, * PLDR_DLL_NOTIFICATION_DATA; + + typedef NTSTATUS(NTAPI* PLDR_MANIFEST_PROBER_ROUTINE) + ( + IN HMODULE DllBase, + IN PCWSTR FullDllPath, + OUT PHANDLE ActivationContext + ); + + typedef NTSTATUS(NTAPI* PLDR_ACTX_LANGUAGE_ROURINE) + ( + IN HANDLE Unk, + IN USHORT LangID, + OUT PHANDLE ActivationContext + ); + + typedef void(NTAPI* PLDR_RELEASE_ACT_ROUTINE) + ( + IN HANDLE ActivationContext + ); + + typedef VOID(NTAPI* fnLdrSetDllManifestProber) + ( + IN PLDR_MANIFEST_PROBER_ROUTINE ManifestProberRoutine, + IN PLDR_ACTX_LANGUAGE_ROURINE CreateActCtxLanguageRoutine, + IN PLDR_RELEASE_ACT_ROUTINE ReleaseActCtxRoutine + ); + +private: + static inline void CALLBACK LdrDllNotification(ULONG NotificationReason, PLDR_DLL_NOTIFICATION_DATA NotificationData, PVOID Context) + { + static constexpr auto LDR_DLL_NOTIFICATION_REASON_LOADED = 1; + static constexpr auto LDR_DLL_NOTIFICATION_REASON_UNLOADED = 2; + if (NotificationReason == LDR_DLL_NOTIFICATION_REASON_LOADED) + { + invokeOnModuleLoad(NotificationData->Loaded.BaseDllName->Buffer); + invokeOnAnyModuleLoad((HMODULE)NotificationData->Loaded.DllBase); + } + else if (NotificationReason == LDR_DLL_NOTIFICATION_REASON_UNLOADED) + { + invokeOnUnload(NotificationData->Loaded.BaseDllName->Buffer); + invokeOnAnyModuleUnload((HMODULE)NotificationData->Loaded.DllBase); + } + } + + static inline NTSTATUS NTAPI ProbeCallback(IN HMODULE DllBase, IN PCWSTR FullDllPath, OUT PHANDLE ActivationContext) + { + //wprintf(L"ProbeCallback: Base %p, path '%ls', context %p\r\n", DllBase, FullDllPath, *ActivationContext); + + std::wstring str(FullDllPath); + invokeOnModuleLoad(str.substr(str.find_last_of(L"/\\") + 1)); + invokeOnAnyModuleLoad(DllBase); + + //if (!*ActivationContext) + // return STATUS_INVALID_PARAMETER; // breaks on xp + + HANDLE actx = NULL; + ACTCTXW act = { 0 }; + + act.cbSize = sizeof(act); + act.dwFlags = ACTCTX_FLAG_RESOURCE_NAME_VALID | ACTCTX_FLAG_HMODULE_VALID; + act.lpSource = FullDllPath; + act.hModule = DllBase; + act.lpResourceName = ISOLATIONAWARE_MANIFEST_RESOURCE_ID; + + // Reset pointer, crucial for x64 version + *ActivationContext = 0; + + actx = CreateActCtxW(&act); + + // Report no manifest is present + if (actx == INVALID_HANDLE_VALUE) + return 0xC000008B; //STATUS_RESOURCE_NAME_NOT_FOUND; + + *ActivationContext = actx; + + return STATUS_SUCCESS; + } + + static inline void RegisterDllNotification() + { + LdrRegisterDllNotification = (_LdrRegisterDllNotification)GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "LdrRegisterDllNotification"); + if (LdrRegisterDllNotification) + { + if (!cookie) + LdrRegisterDllNotification(0, LdrDllNotification, 0, &cookie); + } + else + { + LdrSetDllManifestProber = (fnLdrSetDllManifestProber)GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "LdrSetDllManifestProber"); + if (LdrSetDllManifestProber) + { + LdrSetDllManifestProber(&ProbeCallback, NULL, &ReleaseActCtx); + } + } + } + + static inline void UnRegisterDllNotification() + { + LdrUnregisterDllNotification = (_LdrUnregisterDllNotification)GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "LdrUnregisterDllNotification"); + if (LdrUnregisterDllNotification && cookie) + LdrUnregisterDllNotification(cookie); + } + +private: + static inline DWORD WINAPI ThreadProc(LPVOID ptr) + { + auto paramsPtr = static_cast(ptr); + auto params = *paramsPtr; + delete paramsPtr; + + HANDLE hTimer = NULL; + LARGE_INTEGER liDueTime; + liDueTime.QuadPart = -30 * 10000000LL; + hTimer = CreateWaitableTimer(NULL, TRUE, NULL); + SetWaitableTimer(hTimer, &liDueTime, 0, NULL, NULL, 0); + + while (params.pattern.clear().empty()) + { + Sleep(0); + + if (WaitForSingleObject(hTimer, 0) == WAIT_OBJECT_0) + { + CloseHandle(hTimer); + return 0; + } + }; + + params.fn(); + + return 0; + } +private: + static inline _LdrRegisterDllNotification LdrRegisterDllNotification; + static inline _LdrUnregisterDllNotification LdrUnregisterDllNotification; + static inline void* cookie; + static inline fnLdrSetDllManifestProber LdrSetDllManifestProber; +public: + static inline std::once_flag flag; + static inline std::vector> onAnyModuleLoad; + static inline std::vector> onAnyModuleUnload; +}; + +export template +hook::pattern find_pattern(Args... args) +{ + hook::pattern pattern; + ((pattern = hook::pattern(args), !pattern.count_hint(count).empty()) || ...); + return pattern; +} + +std::string format(const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + std::vector v(1024); + while (true) + { + va_list args2; + va_copy(args2, args); + int res = vsnprintf(v.data(), v.size(), fmt, args2); + if ((res >= 0) && (res < static_cast(v.size()))) + { + va_end(args); + va_end(args2); + return std::string(v.data()); + } + size_t size; + if (res < 0) + size = v.size() * 2; + else + size = static_cast(res) + 1; + v.clear(); + v.resize(size); + va_end(args2); + } +} + +export template +std::array to_bytes(const T& object) +{ + std::array bytes; + const uint8_t* begin = reinterpret_cast(std::addressof(object)); + const uint8_t* end = begin + sizeof(T); + std::copy(begin, end, std::begin(bytes)); + return bytes; +} + +export template +T& from_bytes(const std::array& bytes, T& object) +{ + static_assert(std::is_trivially_copyable::value, "not a TriviallyCopyable type"); + uint8_t* begin_object = reinterpret_cast(std::addressof(object)); + std::copy(std::begin(bytes), std::end(bytes), begin_object); + return object; +} + +export template +T from_bytes(const T1& bytes) +{ + static_assert(std::is_trivially_copyable::value, "not a TriviallyCopyable type"); + T object; + uint8_t* begin_object = reinterpret_cast(std::addressof(object)); + std::copy(std::begin(bytes), std::end(bytes) - (sizeof(T1) - sizeof(T)), begin_object); + return object; +} + +export template +std::string pattern_str(const std::array bytes) +{ + std::string result; + for (size_t i = 0; i < n; i++) + { + result += format("%02X ", bytes[i]); + } + return result; +} + +export template +std::string pattern_str(T t) +{ + return std::string((std::is_same::value ? format("%c ", t) : format("%02X ", t))); +} + +export template +std::string pattern_str(T t, Rest... rest) +{ + return std::string((std::is_same::value ? format("%c ", t) : format("%02X ", t)) + pattern_str(rest...)); +} + +export std::string pattern_str(std::string_view str) { + std::stringstream str_stream; + for (const auto& item : str) { + str_stream << std::uppercase << std::hex << std::setw(2) << std::setfill('0') << +uint8_t(item) << " "; + } + return str_stream.str(); +} + +export class IATHook +{ +public: + template + static void Replace(HMODULE target_module, std::string_view dll_name, Ts&& ... inputs) + { + auto hExecutableInstance = (size_t)target_module; + IMAGE_NT_HEADERS* ntHeader = (IMAGE_NT_HEADERS*)(hExecutableInstance + ((IMAGE_DOS_HEADER*)hExecutableInstance)->e_lfanew); + IMAGE_IMPORT_DESCRIPTOR* pImports = (IMAGE_IMPORT_DESCRIPTOR*)(hExecutableInstance + ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress); + size_t nNumImports = ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size / sizeof(IMAGE_IMPORT_DESCRIPTOR) - 1; + + auto PatchIAT = [&](size_t start, size_t end, size_t exe_end) + { + for (size_t i = 0; i < nNumImports; i++) + { + if (hExecutableInstance + (pImports + i)->FirstThunk > start && !(end && hExecutableInstance + (pImports + i)->FirstThunk > end)) + end = hExecutableInstance + (pImports + i)->FirstThunk; + } + + if (!end) { end = start + 0x100; } + if (end > exe_end) + { + start = hExecutableInstance; + end = exe_end; + } + + for (auto i = start; i < end; i += sizeof(size_t)) + { + DWORD dwProtect[2]; + VirtualProtect((size_t*)i, sizeof(size_t), PAGE_EXECUTE_READWRITE, &dwProtect[0]); + + auto ptr = *(size_t*)i; + if (!ptr) + continue; + + ([&] + { + auto func_name = std::get<0>(inputs); + auto func_hook = std::get<1>(inputs); + if (func_hook && ptr == (size_t)GetProcAddress(GetModuleHandleA(dll_name.data()), func_name)) + *(size_t*)i = (size_t)func_hook; + } (), ...); + + VirtualProtect((size_t*)i, sizeof(size_t), dwProtect[0], &dwProtect[1]); + } + }; + + static auto getSection = [](const PIMAGE_NT_HEADERS nt_headers, unsigned section) -> PIMAGE_SECTION_HEADER + { + return reinterpret_cast( + (UCHAR*)nt_headers->OptionalHeader.DataDirectory + + nt_headers->OptionalHeader.NumberOfRvaAndSizes * sizeof(IMAGE_DATA_DIRECTORY) + + section * sizeof(IMAGE_SECTION_HEADER)); + }; + + static auto getSectionEnd = [](IMAGE_NT_HEADERS* ntHeader, size_t inst) -> auto + { + auto sec = getSection(ntHeader, ntHeader->FileHeader.NumberOfSections - 1); + while (sec->Misc.VirtualSize == 0) sec--; + + auto secSize = max(sec->SizeOfRawData, sec->Misc.VirtualSize); + auto end = inst + max(sec->PointerToRawData, sec->VirtualAddress) + secSize; + return end; + }; + + auto hExecutableInstance_end = getSectionEnd(ntHeader, hExecutableInstance); + + // Find DLL + for (size_t i = 0; i < nNumImports; i++) + { + if ((size_t)(hExecutableInstance + (pImports + i)->Name) < hExecutableInstance_end) + { + if (!_stricmp((const char*)(hExecutableInstance + (pImports + i)->Name), dll_name.data())) + PatchIAT(hExecutableInstance + (pImports + i)->FirstThunk, 0, hExecutableInstance_end); + } + } + } +}; + +export class raw_mem +{ +public: + raw_mem(injector::memory_pointer_tr addr, std::initializer_list bytes, bool offset_back = false) + { + ptr = addr.as_int() - (offset_back ? bytes.size() : 0); + new_code.assign(std::move(bytes)); + old_code.resize(new_code.size()); + ReadMemoryRaw(ptr, old_code.data(), old_code.size(), true); + } + + void Write() + { + WriteMemoryRaw(ptr, new_code.data(), new_code.size(), true); + } + + void Restore() + { + WriteMemoryRaw(ptr, old_code.data(), old_code.size(), true); + } + + size_t Size() + { + return old_code.size(); + } + +private: + injector::memory_pointer ptr; + std::vector old_code; + std::vector new_code; +}; \ No newline at end of file diff --git a/source/comvars.ixx b/source/comvars.ixx new file mode 100644 index 0000000..896b6e1 --- /dev/null +++ b/source/comvars.ixx @@ -0,0 +1,22 @@ +module; + +#include + +export module comvars; + +import common; + +export HWND gWnd; +export RECT gRect; + +class Common +{ +public: + Common() + { + FusionFix::onInitEvent() += []() + { + + }; + } +} Common; \ No newline at end of file diff --git a/source/contributing.ixx b/source/contributing.ixx new file mode 100644 index 0000000..8eb7dfc --- /dev/null +++ b/source/contributing.ixx @@ -0,0 +1,19 @@ +module; + +#include + +export module contributing; + +import common; + +class Contributing +{ +public: + Contributing() + { + FusionFix::onInitEvent() += []() + { + // Add your code here + }; + } +} Contributing; \ No newline at end of file diff --git a/source/dllmain.cpp b/source/dllmain.cpp index 4431ecd..5eb94c3 100644 --- a/source/dllmain.cpp +++ b/source/dllmain.cpp @@ -1,74 +1,120 @@ -#include +#include +#include +#include +#pragma comment(lib,"Comctl32.lib") +#pragma comment(linker,"\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") -float fOutlinesSizeMultiplier; -float* dword_1FCA9A8; -void __fastcall sub_010AFD30(float* _this, void* edx, float a2) -{ - a2 *= fOutlinesSizeMultiplier; +import common; +import comvars; +import settings; - if (a2 <= 0.0) - { - _this[8] = 0.0; - _this[13] = 0.0; - } - else - { - _this[8] = a2 / *dword_1FCA9A8; - _this[13] = a2 / *dword_1FCA9A8; - } -} +//injector::hook_back hbCGameProcess; +//void __cdecl CGameProcessHook(int a1) +//{ +// static std::once_flag of; +// std::call_once(of, []() +// { +// FusionFix::onGameInitEvent().executeAll(); +// }); +// +// if (CTimer__m_UserPause && CTimer__m_CodePause) +// { +// static auto oldMenuState = 0; +// +// if (!*CTimer__m_UserPause && !*CTimer__m_CodePause) +// { +// uint32_t curMenuState = false; +// if (curMenuState != oldMenuState) { +// FusionFix::onMenuExitEvent().executeAll(); +// } +// oldMenuState = curMenuState; +// FusionFix::onGameProcessEvent().executeAll(); +// } +// else +// { +// uint32_t curMenuState = true; +// if (curMenuState != oldMenuState) { +// FusionFix::onMenuEnterEvent().executeAll(); +// } +// oldMenuState = curMenuState; +// FusionFix::onMenuDrawingEvent().executeAll(); +// } +// } +// +// return hbCGameProcess.fun(a1); +//} void Init() { - CIniReader iniReader(""); - bool bHideSkipButton = iniReader.ReadInteger("MAIN", "HideSkipButton", 1) != 0; - bool bDisableGlobalLeaderboards = iniReader.ReadInteger("MAIN", "DisableGlobalLeaderboards", 1) != 0; - fOutlinesSizeMultiplier = iniReader.ReadFloat("MAIN", "OutlinesSizeMultiplier", 0.0f); + FusionFixSettings.ReadIniSettings(); - if (bHideSkipButton) - { - auto pattern = hook::pattern("8B C8 89 86 ? ? ? ? E8 ? ? ? ? D9 EE 8B 86 ? ? ? ? 8B 38 83 EC 08 D9 54 24 04 8D 4C 24 3C D9 1C 24 E8 ? ? ? ? F3 0F 7E 00 8B 97 ? ? ? ? 83 EC 18 8B CC 66 0F D6 01 F3 0F 7E 40 ? 66 0F D6 41 ? F3 0F 7E 40 ? 6A 18 68"); - injector::MakeNOP(pattern.get_first(8), 5, true); - pattern = hook::pattern("8B C8 89 86 ? ? ? ? E8 ? ? ? ? D9 EE 8B 86 ? ? ? ? 8B 38 83 EC 08 D9 54 24 04 8D 4C 24 20 D9 1C 24 E8 ? ? ? ? F3 0F 7E 00 8B 97 ? ? ? ? 83 EC 18 8B CC 66 0F D6 01 F3 0F 7E 40 ? 66 0F D6 41 ? F3 0F 7E 40 ? 6A 18 68 ? ? ? ? 66 0F D6 41 ? 8B 8E ? ? ? ? 6A 18"); - injector::MakeNOP(pattern.get_first(8), 5, true); - } - - if (fOutlinesSizeMultiplier) - { - dword_1FCA9A8 = *hook::get_pattern("F3 0F 10 86 ? ? ? ? F3 0F 59 05 ? ? ? ? 51 8D 8C 24", 12); - - auto pattern = hook::pattern("E8 ? ? ? ? 68 ? ? ? ? 8D 94 24 ? ? ? ? 6A 02"); - injector::MakeCALL(pattern.get_first(0), sub_010AFD30, true); + //auto pattern = hook::pattern("E8 ? ? ? ? E8 ? ? ? ? E8 ? ? ? ? B9 ? ? ? ? E8 ? ? ? ? E8 ? ? ? ? E8 ? ? ? ? E8 ? ? ? ? B9"); + //hbCGameProcess.fun = injector::MakeCALL(pattern.get_first(0), CGameProcessHook, true).get(); - pattern = hook::pattern("D9 1C 24 E8 ? ? ? ? 68 ? ? ? ? 68"); - injector::MakeCALL(pattern.get_first(3), sub_010AFD30, true); + //static auto futures = FusionFix::onInitEventAsync().executeAllAsync(); - pattern = hook::pattern("E8 ? ? ? ? 68 ? ? ? ? 8D 44 24 20 6A 03 50 88 1D ? ? ? ? C6 05 ? ? ? ? ? 88 1D ? ? ? ? E8 ? ? ? ? 8B 08"); - injector::MakeCALL(pattern.get_first(0), sub_010AFD30, true); + //FusionFix::onGameInitEvent() += []() + //{ + // for (auto& f : futures.get()) + // f.wait(); + // futures.get().clear(); + //}; - pattern = hook::pattern("E8 ? ? ? ? F3 0F 10 44 24 ? 8D 4C 24 30 F3 0F 11 05 ? ? ? ? F3 0F 10 44 24 ? 51 B9 ? ? ? ? F3 0F 11 05 ? ? ? ? E8 ? ? ? ? 8A 5C 24 0F 80 FB FF 75 0C"); - injector::MakeCALL(pattern.get_first(0), sub_010AFD30, true); - } + FusionFix::onInitEvent().executeAll(); +} - if (bDisableGlobalLeaderboards) +HRESULT CALLBACK TaskDialogCallbackProc(HWND hwnd, UINT uNotification, WPARAM wParam, LPARAM lParam, LONG_PTR dwRefData) +{ + switch (uNotification) { - //auto pattern = hook::pattern("6A 00 68 ? ? ? ? E8 ? ? ? ? 8B 0F"); - //injector::WriteMemory(pattern.get_first(1), 0x01, true); - //pattern = hook::pattern("6A 02 68 ? ? ? ? E8 ? ? ? ? 8B 0D"); - //injector::WriteMemory(pattern.get_first(1), 0x00, true); - //pattern = hook::pattern("83 F8 01 B8 ? ? ? ? 74 05 B8 ? ? ? ? C3"); - //injector::MakeNOP(pattern.get_first(8), 7, true); - auto pattern = hook::pattern("8B 81 ? ? ? ? 83 F8 06"); - injector::MakeRET(pattern.get_first(0)); + case TDN_HYPERLINK_CLICKED: + ShellExecuteW(hwnd, L"open", (LPCWSTR)lParam, NULL, NULL, SW_SHOW); + break; } + + return S_OK; } -CEXP void InitializeASI() +void UALCompat() { - std::call_once(CallbackHandler::flag, []() + if (IsUALPresent()) + return; + + TASKDIALOGCONFIG tdc = { sizeof(TASKDIALOGCONFIG) }; + int nClickedBtn; + BOOL bCheckboxChecked; + LPCWSTR + szTitle = L"MaxPayne3.FusionFix", + szHeader = L"You are running Max Payne 3 Fusion Fix with an incompatible version of ASI Loader", + szContent = L"It requires the latest version of " \ + L"Ultimate ASI Loader\n\n" \ + L"https://github.com/ThirteenAG/Ultimate-ASI-Loader/releases/latest"; + TASKDIALOG_BUTTON aCustomButtons[] = { { 1000, L"Close the program" } }; + + tdc.hwndParent = gWnd; + tdc.dwFlags = TDF_USE_COMMAND_LINKS | TDF_ENABLE_HYPERLINKS | TDF_SIZE_TO_CONTENT | TDF_CAN_BE_MINIMIZED; + tdc.pButtons = aCustomButtons; + tdc.cButtons = _countof(aCustomButtons); + tdc.pszWindowTitle = szTitle; + tdc.pszMainIcon = TD_INFORMATION_ICON; + tdc.pszMainInstruction = szHeader; + tdc.pszContent = szContent; + tdc.pfCallback = TaskDialogCallbackProc; + tdc.lpCallbackData = 0; + + auto hr = TaskDialogIndirect(&tdc, &nClickedBtn, NULL, &bCheckboxChecked); + TerminateProcess(GetCurrentProcess(), 0); +} + +extern "C" +{ + void __declspec(dllexport) InitializeASI() { - CallbackHandler::RegisterCallback(Init, hook::pattern("C6 44 24 ? ? F3 0F 11 44 24 ? F3 0F 11 44 24 ? 89 4C 24 54")); - }); + std::call_once(CallbackHandler::flag, []() + { + CallbackHandler::RegisterCallback(Init, hook::pattern("C6 44 24 ? ? F3 0F 11 44 24 ? F3 0F 11 44 24 ? 89 4C 24 54")); + }); + } } BOOL APIENTRY DllMain(HMODULE hModule, DWORD reason, LPVOID lpReserved) @@ -77,5 +123,9 @@ BOOL APIENTRY DllMain(HMODULE hModule, DWORD reason, LPVOID lpReserved) { if (!IsUALPresent()) { InitializeASI(); } } + if (reason == DLL_PROCESS_DETACH) + { + FusionFix::onShutdownEvent().executeAll(); + } return TRUE; -} \ No newline at end of file +} diff --git a/source/hideskip.ixx b/source/hideskip.ixx new file mode 100644 index 0000000..8abd3cf --- /dev/null +++ b/source/hideskip.ixx @@ -0,0 +1,37 @@ +module; + +#include + +export module hideskip; + +import common; +import settings; + +class HideSkip +{ +public: + HideSkip() + { + FusionFix::onInitEvent() += []() + { + auto pattern = hook::pattern("8B C8 89 86 ? ? ? ? E8 ? ? ? ? D9 EE 8B 86 ? ? ? ? 8B 38 83 EC 08 D9 54 24 04 8D 4C 24 3C D9 1C 24 E8 ? ? ? ? F3 0F 7E 00 8B 97 ? ? ? ? 83 EC 18 8B CC 66 0F D6 01 F3 0F 7E 40 ? 66 0F D6 41 ? F3 0F 7E 40 ? 6A 18 68"); + static raw_mem fnHideSkip1(pattern.get_first(8), { 0x90, 0x90, 0x90, 0x90, 0x90 }); // nop x5 + pattern = hook::pattern("8B C8 89 86 ? ? ? ? E8 ? ? ? ? D9 EE 8B 86 ? ? ? ? 8B 38 83 EC 08 D9 54 24 04 8D 4C 24 20 D9 1C 24 E8 ? ? ? ? F3 0F 7E 00 8B 97 ? ? ? ? 83 EC 18 8B CC 66 0F D6 01 F3 0F 7E 40 ? 66 0F D6 41 ? F3 0F 7E 40 ? 6A 18 68 ? ? ? ? 66 0F D6 41 ? 8B 8E ? ? ? ? 6A 18"); + static raw_mem fnHideSkip2(pattern.get_first(8), { 0x90, 0x90, 0x90, 0x90, 0x90 }); // nop x5 + + FusionFix::onIniFileChange() += []() + { + if (FusionFixSettings.GetInt("PREF_HIDESKIP")) + { + fnHideSkip1.Write(); + fnHideSkip2.Write(); + } + else + { + fnHideSkip1.Restore(); + fnHideSkip2.Restore(); + } + }; + }; + } +} HideSkip; \ No newline at end of file diff --git a/source/includes/FileWatch.hpp b/source/includes/FileWatch.hpp new file mode 100644 index 0000000..4eba08b --- /dev/null +++ b/source/includes/FileWatch.hpp @@ -0,0 +1,1246 @@ +// MIT License +// +// Copyright(c) 2017 Thomas Monkman +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#ifndef FILEWATCHER_H +#define FILEWATCHER_H + +#include +#include +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#include +#include +#include +#include +#include +#endif // WIN32 + +#if __unix__ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif // __unix__ + +#ifdef __linux__ +#include +#endif + +#if defined(__APPLE__) || defined(__MACH__) +#include +#include +#include +#include +#include +#include +#define FILEWATCH_PLATFORM_MAC 1 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef FILEWATCH_PLATFORM_MAC +extern "C" int __getdirentries64(int, char *, int, long *); +#endif // FILEWATCH_PLATFORM_MAC + +namespace filewatch { + enum class Event { + added, + removed, + modified, + renamed_old, + renamed_new + }; + + template + struct IsWChar { + static constexpr bool value = false; + }; + + template<> + struct IsWChar { + static constexpr bool value = true; + }; + + template + struct Invokable { + static Fn make() { + return (Fn*)0; + } + + template + static T defaultValue() { + return *(T*)0; + } + + static void call(int) { + make()(defaultValue()); + } + + static int call(long value); + + static constexpr bool value = std::is_same::value; + }; + +#define _FILEWATCH_TO_STRING(x) #x +#define FILEWATCH_TO_STRING(x) _FILEWATCH_TO_STRING(x) + + [[maybe_unused]] static const char* event_to_string(Event event) { + switch (event) { + case Event::added: + return FILEWATCH_TO_STRING(Event::added); + case Event::removed: + return FILEWATCH_TO_STRING(Event::removed); + case Event::modified: + return FILEWATCH_TO_STRING(Event::modified); + case Event::renamed_old: + return FILEWATCH_TO_STRING(Event:renamed_old); + case Event::renamed_new: + return FILEWATCH_TO_STRING(Event::renamed_new); + } + assert(false); + } + + template + static typename std::enable_if::value, bool>::type + isParentOrSelfDirectory(const StringType& path) { + return path == L"." || path == L".."; + } + + template + static typename std::enable_if::value, bool>::type + isParentOrSelfDirectory(const StringType& path) { + return path == "." || path == ".."; + } + + /** + * \class FileWatch + * + * \brief Watches a folder or file, and will notify of changes via function callback. + * + * \author Thomas Monkman + * + */ + template + class FileWatch + { + typedef typename StringType::value_type C; + typedef std::basic_string> UnderpinningString; + typedef std::basic_regex> UnderpinningRegex; + + public: + + FileWatch(StringType path, UnderpinningRegex pattern, std::function callback) : + _path(absolute_path_of(path)), + _pattern(pattern), + _callback(callback), + _directory(get_directory(path)) + { + init(); + } + + FileWatch(StringType path, std::function callback) : + FileWatch(path, UnderpinningRegex(_regex_all), callback) {} + + ~FileWatch() { + destroy(); + } + + FileWatch(const FileWatch& other) : FileWatch(other._path, other._callback) {} + + FileWatch& operator=(const FileWatch& other) + { + if (this == &other) { return *this; } + + destroy(); + _path = other._path; + _callback = other._callback; + _directory = get_directory(other._path); + init(); + return *this; + } + + // Const memeber varibles don't let me implent moves nicely, if moves are really wanted std::unique_ptr should be used and move that. + FileWatch(FileWatch&&) = delete; + FileWatch& operator=(FileWatch&&) & = delete; + + private: + static constexpr C _regex_all[] = { '.', '*', '\0' }; + static constexpr C _this_directory[] = { '.', '/', '\0' }; + + struct PathParts + { + PathParts(StringType directory, StringType filename) : directory(directory), filename(filename) {} + StringType directory; + StringType filename; + }; + const StringType _path; + + UnderpinningRegex _pattern; + + static constexpr std::size_t _buffer_size = { 1024 * 256 }; + + // only used if watch a single file + StringType _filename; + + std::function _callback; + + std::thread _watch_thread; + + std::condition_variable _cv; + std::mutex _callback_mutex; + std::vector> _callback_information; + std::thread _callback_thread; + + std::promise _running; + std::atomic _destory = { false }; + bool _watching_single_file = { false }; + +#pragma mark "Platform specific data" +#ifdef _WIN32 + HANDLE _directory = { nullptr }; + HANDLE _close_event = { nullptr }; + + const DWORD _listen_filters = + FILE_NOTIFY_CHANGE_SECURITY | + FILE_NOTIFY_CHANGE_CREATION | + FILE_NOTIFY_CHANGE_LAST_ACCESS | + FILE_NOTIFY_CHANGE_LAST_WRITE | + FILE_NOTIFY_CHANGE_SIZE | + FILE_NOTIFY_CHANGE_ATTRIBUTES | + FILE_NOTIFY_CHANGE_DIR_NAME | + FILE_NOTIFY_CHANGE_FILE_NAME; + + const std::unordered_map _event_type_mapping = { + { FILE_ACTION_ADDED, Event::added }, + { FILE_ACTION_REMOVED, Event::removed }, + { FILE_ACTION_MODIFIED, Event::modified }, + { FILE_ACTION_RENAMED_OLD_NAME, Event::renamed_old }, + { FILE_ACTION_RENAMED_NEW_NAME, Event::renamed_new } + }; +#endif // WIN32 + +#if __unix__ + struct FolderInfo { + int folder; + int watch; + }; + + FolderInfo _directory; + + const std::uint32_t _listen_filters = IN_MODIFY | IN_CREATE | IN_DELETE; + + const static std::size_t event_size = (sizeof(struct inotify_event)); +#endif // __unix__ + +#if FILEWATCH_PLATFORM_MAC + struct FileState + { + int fd; + uint32_t nlink; + time_t last_modification; + + FileState(int fd, uint32_t nlink, time_t lt) + : fd(fd), nlink(nlink), + last_modification(lt) + { + + } + FileState(const FileState&) = delete; + FileState& operator=(const FileState&) = delete; + FileState(FileState&& other) : fd(other.fd), nlink(other.nlink), last_modification(other.last_modification) + { + other.fd = -1; + } + + FileState invalidate_and_clone() { + int fd = this->fd; + + this->fd = -1; + return FileState {fd, nlink, last_modification}; + } + + ~FileState() + { + if (fd != -1) { + close(fd); + } + } + }; + std::unordered_map _directory_snapshot{}; + bool _previous_event_is_rename = false; + CFRunLoopRef _run_loop = nullptr; + int _file_fd = -1; + struct timespec _last_modification_time = {}; + FSEventStreamRef _directory; + // fd for single file +#endif // FILEWATCH_PLATFORM_MAC + + void init() + { +#ifdef _WIN32 + _close_event = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!_close_event) { + throw std::system_error(GetLastError(), std::system_category()); + } +#endif // WIN32 + + _callback_thread = std::thread([this]() { + try { + callback_thread(); + } catch (...) { + try { + _running.set_exception(std::current_exception()); + } + catch (...) {} // set_exception() may throw too + } + }); + + _watch_thread = std::thread([this]() { + try { + monitor_directory(); + } catch (...) { + try { + _running.set_exception(std::current_exception()); + } + catch (...) {} // set_exception() may throw too + } + }); + + std::future future = _running.get_future(); + future.get(); //block until the monitor_directory is up and running + } + + void destroy() + { + _destory = true; + _running = std::promise(); + +#ifdef _WIN32 + SetEvent(_close_event); +#elif __unix__ + inotify_rm_watch(_directory.folder, _directory.watch); +#elif FILEWATCH_PLATFORM_MAC + if (_run_loop) { + CFRunLoopStop(_run_loop); + } +#endif // __unix__ + + _cv.notify_all(); + _watch_thread.join(); + _callback_thread.join(); + +#ifdef _WIN32 + CloseHandle(_directory); +#elif __unix__ + close(_directory.folder); +#elif FILEWATCH_PLATFORM_MAC + FSEventStreamStop(_directory); + FSEventStreamInvalidate(_directory); + FSEventStreamRelease(_directory); + _directory = nullptr; +#endif // FILEWATCH_PLATFORM_MAC + } + + const PathParts split_directory_and_file(const StringType& path) const + { + const auto predict = [](C character) { +#ifdef _WIN32 + return character == C('\\') || character == C('/'); +#elif __unix__ || FILEWATCH_PLATFORM_MAC + return character == C('/'); +#endif // __unix__ + }; + + UnderpinningString path_string = path; + const auto pivot = std::find_if(path_string.rbegin(), path_string.rend(), predict).base(); + //if the path is something like "test.txt" there will be no directory part, however we still need one, so insert './' + const StringType directory = [&]() { + const auto extracted_directory = UnderpinningString(path_string.begin(), pivot); + return (extracted_directory.size() > 0) ? extracted_directory : UnderpinningString(_this_directory); + }(); + const StringType filename = UnderpinningString(pivot, path_string.end()); + return PathParts(directory, filename); + } + + bool pass_filter(const UnderpinningString& file_path) + { + if (_watching_single_file) { + const UnderpinningString extracted_filename = { split_directory_and_file(file_path).filename }; + //if we are watching a single file, only that file should trigger action + return extracted_filename == _filename; + } + return std::regex_match(file_path, _pattern); + } + +#ifdef _WIN32 + template DWORD GetFileAttributesX(const char* lpFileName, Args... args) { + return GetFileAttributesA(lpFileName, args...); + } + template DWORD GetFileAttributesX(const wchar_t* lpFileName, Args... args) { + return GetFileAttributesW(lpFileName, args...); + } + + template HANDLE CreateFileX(const char* lpFileName, Args... args) { + return CreateFileA(lpFileName, args...); + } + template HANDLE CreateFileX(const wchar_t* lpFileName, Args... args) { + return CreateFileW(lpFileName, args...); + } + + HANDLE get_directory(const StringType& path) + { + auto file_info = GetFileAttributesX(path.c_str()); + + if (file_info == INVALID_FILE_ATTRIBUTES) + { + throw std::system_error(GetLastError(), std::system_category()); + } + _watching_single_file = (file_info & FILE_ATTRIBUTE_DIRECTORY) == false; + + const StringType watch_path = [this, &path]() { + if (_watching_single_file) + { + const auto parsed_path = split_directory_and_file(path); + _filename = parsed_path.filename; + return parsed_path.directory; + } + else + { + return path; + } + }(); + + HANDLE directory = CreateFileX( + watch_path.c_str(), // pointer to the file name + FILE_LIST_DIRECTORY, // access (read/write) mode + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, // share mode + nullptr, // security descriptor + OPEN_EXISTING, // how to create + FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, // file attributes + HANDLE(0)); // file with attributes to copy + + if (directory == INVALID_HANDLE_VALUE) + { + throw std::system_error(GetLastError(), std::system_category()); + } + return directory; + } + + void convert_wstring(const std::wstring& wstr, std::string& out) + { + int size_needed = WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), NULL, 0, NULL, NULL); + out.resize(size_needed, '\0'); + WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), &out[0], size_needed, NULL, NULL); + } + + void convert_wstring(const std::wstring& wstr, std::wstring& out) + { + out = wstr; + } + + void monitor_directory() + { + std::vector buffer(_buffer_size); + DWORD bytes_returned = 0; + OVERLAPPED overlapped_buffer{ 0 }; + + overlapped_buffer.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!overlapped_buffer.hEvent) { + std::cerr << "Error creating monitor event" << std::endl; + } + + std::array handles{ overlapped_buffer.hEvent, _close_event }; + + auto async_pending = false; + _running.set_value(); + do { + std::vector> parsed_information; + ReadDirectoryChangesW( + _directory, + buffer.data(), static_cast(buffer.size()), + TRUE, + _listen_filters, + &bytes_returned, + &overlapped_buffer, NULL); + + async_pending = true; + + switch (WaitForMultipleObjects(2, handles.data(), FALSE, INFINITE)) + { + case WAIT_OBJECT_0: + { + if (!GetOverlappedResult(_directory, &overlapped_buffer, &bytes_returned, TRUE)) { + throw std::system_error(GetLastError(), std::system_category()); + } + async_pending = false; + + if (bytes_returned == 0) { + break; + } + + FILE_NOTIFY_INFORMATION *file_information = reinterpret_cast(&buffer[0]); + do + { + std::wstring changed_file_w{ file_information->FileName, file_information->FileNameLength / sizeof(file_information->FileName[0]) }; + UnderpinningString changed_file; + convert_wstring(changed_file_w, changed_file); + if (pass_filter(changed_file)) + { + parsed_information.emplace_back(StringType{ changed_file }, _event_type_mapping.at(file_information->Action)); + } + + if (file_information->NextEntryOffset == 0) { + break; + } + + file_information = reinterpret_cast(reinterpret_cast(file_information) + file_information->NextEntryOffset); + } while (true); + break; + } + case WAIT_OBJECT_0 + 1: + // quit + break; + case WAIT_FAILED: + break; + } + //dispatch callbacks + { + std::lock_guard lock(_callback_mutex); + _callback_information.insert(_callback_information.end(), parsed_information.begin(), parsed_information.end()); + } + _cv.notify_all(); + } while (_destory == false); + + if (async_pending) + { + //clean up running async io + CancelIo(_directory); + GetOverlappedResult(_directory, &overlapped_buffer, &bytes_returned, TRUE); + } + } +#endif // WIN32 + +#if __unix__ + + bool is_file(const StringType& path) const + { + struct stat statbuf = {}; + if (stat(path.c_str(), &statbuf) != 0) + { + throw std::system_error(errno, std::system_category()); + } + return S_ISREG(statbuf.st_mode); + } + + FolderInfo get_directory(const StringType& path) + { + const auto folder = inotify_init(); + if (folder < 0) + { + throw std::system_error(errno, std::system_category()); + } + + _watching_single_file = is_file(path); + + const StringType watch_path = [this, &path]() { + if (_watching_single_file) + { + const auto parsed_path = split_directory_and_file(path); + _filename = parsed_path.filename; + return parsed_path.directory; + } + else + { + return path; + } + }(); + + const auto watch = inotify_add_watch(folder, watch_path.c_str(), IN_MODIFY | IN_CREATE | IN_DELETE); + if (watch < 0) + { + throw std::system_error(errno, std::system_category()); + } + return { folder, watch }; + } + + void monitor_directory() + { + std::vector buffer(_buffer_size); + + _running.set_value(); + while (_destory == false) + { + const auto length = read(_directory.folder, static_cast(buffer.data()), buffer.size()); + if (length > 0) + { + int i = 0; + std::vector> parsed_information; + while (i < length) + { + struct inotify_event *event = reinterpret_cast(&buffer[i]); // NOLINT + if (event->len) + { + const UnderpinningString changed_file{ event->name }; + if (pass_filter(changed_file)) + { + if (event->mask & IN_CREATE) + { + parsed_information.emplace_back(StringType{ changed_file }, Event::added); + } + else if (event->mask & IN_DELETE) + { + parsed_information.emplace_back(StringType{ changed_file }, Event::removed); + } + else if (event->mask & IN_MODIFY) + { + parsed_information.emplace_back(StringType{ changed_file }, Event::modified); + } + } + } + i += event_size + event->len; + } + //dispatch callbacks + { + std::lock_guard lock(_callback_mutex); + _callback_information.insert(_callback_information.end(), parsed_information.begin(), parsed_information.end()); + } + _cv.notify_all(); + } + } + } +#endif // __unix__ + +#if FILEWATCH_PLATFORM_MAC + static StringType absolute_path_of(const StringType& path) { + char buf[PATH_MAX]; + int fd = open((const char*)path.c_str(), O_RDONLY); + const char* str = buf; + struct stat stat; + mbstate_t state; + + assert(fd != -1); + fcntl(fd, F_GETPATH, buf); + fstat(fd, &stat); + + if (stat.st_mode & S_IFREG || stat.st_mode & S_IFLNK) { + size_t len = strlen(buf); + + for (size_t i = len - 1; i >= 0; i--) { + if (buf[i] == '/') { + buf[i] = '\0'; + break; + } + } + } + close(fd); + + if (IsWChar::value) { + size_t needed = mbsrtowcs(nullptr, &str, 0, &state) + 1; + StringType s; + + s.reserve(needed); + mbsrtowcs((wchar_t*)&s[0], &str, s.size(), &state); + return s; + } + return StringType {buf}; + } +#elif defined(__unix__) + static StringType absolute_path_of(const StringType& path) { + char buf[PATH_MAX]; + const char* str = buf; + struct stat stat; + mbstate_t state; + + realpath((const char*)path.c_str(), buf); + ::stat((const char*)path.c_str(), &stat); + + if (stat.st_mode & S_IFREG || stat.st_mode & S_IFLNK) { + size_t len = strlen(buf); + + for (size_t i = len - 1; i >= 0; i--) { + if (buf[i] == '/') { + buf[i] = '\0'; + break; + } + } + } + + if (IsWChar::value) { + size_t needed = mbsrtowcs(nullptr, &str, 0, &state) + 1; + StringType s; + + s.reserve(needed); + mbsrtowcs((wchar_t*)&s[0], &str, s.size(), &state); + return s; + } + return StringType {buf}; + } +#elif _WIN32 + static StringType absolute_path_of(const StringType& path) { + constexpr size_t size = IsWChar::value? MAX_PATH : 32767 * sizeof(wchar_t); + char buf[size]; + + DWORD length = IsWChar::value? + GetFullPathNameW((LPCWSTR)path.c_str(), + size / sizeof(TCHAR), + (LPWSTR)buf, + nullptr) : + GetFullPathNameA((LPCSTR)path.c_str(), + size / sizeof(TCHAR), + buf, + nullptr); + return StringType{(C*)buf, length}; + } +#endif + +#if FILEWATCH_PLATFORM_MAC + static StringType utf8StringToUtf32String(const char* buffer) { + mbstate_t state{}; + StringType s{}; + + size_t needed = mbsrtowcs(nullptr, &buffer, 0, &state) + 1; + s.reserve(needed); + mbsrtowcs((wchar_t*)&s[0], &buffer, s.size(), &state); + return s; + } + + template::value>> + static void walkDirectory(const StringType& path, Fn callback) { + int fd = open(path.c_str(), O_RDONLY); + char buf[1024]; + long basep = 0; + + if (fd == -1) { + return; + } + + int ret = __getdirentries64(fd, buf, sizeof(buf), &basep); + + while (ret > 0) { + char* current = buf; + int offset = 0; + + while (offset < ret) { + struct dirent* dirent = (struct dirent*)current; + StringType name = IsWChar::value? + utf8StringToUtf32String(dirent->d_name) + : StringType(dirent->d_name); + + callback(std::move(name)); + current += dirent->d_reclen; + offset += dirent->d_reclen; + } + ret = __getdirentries64(fd, buf, sizeof(buf), &basep); + } + close(fd); + } + + static StringType nameofFd(int fd) { + size_t len = 0; + char buf[MAXPATHLEN]; + + if (fcntl(fd, F_GETPATH, buf) == -1) { + return StringType{}; + } + if (IsWChar::value) { + return utf8StringToUtf32String(buf); + } + + len = strnlen(buf, MAXPATHLEN); + for (int i = len - 1; i >= 0; i--) { + if(buf[i] == '/') { + return StringType{buf + i + 1, len - i - 1}; + } + } + return StringType{buf, len}; + } + + static StringType fullPathOfFd(int fd) { + char buf[MAXPATHLEN]; + + if (fcntl(fd, F_GETPATH, buf) == -1) { + return StringType{}; + } + if (IsWChar::value) { + return utf8StringToUtf32String(buf); + } + return StringType{(C*)buf}; + } + + static StringType pathOfFd(int fd) { + size_t len = 0; + char buf[MAXPATHLEN]; + + if (fcntl(fd, F_GETPATH, buf) == -1) { + return StringType{}; + } + if (IsWChar::value) { + return utf8StringToUtf32String(buf); + } + + len = strnlen(buf, MAXPATHLEN); + for (int i = len - 1; i >= 0; i--) { + if(buf[i] == '/') { + return StringType{buf, static_cast(i)}; + } + } + return StringType{buf, len}; + } + + static bool fdIsRemoved(int fd) { + char buf[MAXPATHLEN]; + return fcntl(fd, F_GETPATH, buf) == -1; + } + + FileState makeFileState(const StringType& path) { + int fd = openFile(path); + struct stat stat; + + fstat(fd, &stat); + + return FileState { + openFile(path), + stat.st_nlink, + stat.st_mtimespec.tv_sec + }; + } + + static StringType filenameOf(const StringType& file) { + for (int i = file.size() - 1; i >= 0; i--) { + if(file[i] == '/') { + return file.substr(i + 1); + } + } + return file; + } + + static bool isInDirectory(const StringType& file, const StringType& path) { + if (file.size() < path.size()) { + return false; + } + return strncmp(file.data(), path.data(), path.size()) == 0; + } + + PathParts splitPath(const StringType& path) { + PathParts split = split_directory_and_file(path); + + if (split.directory.size() > 0 && split.directory[split.directory.size() - 1] == '/') { + split.directory.erase(split.directory.size() - 1); + } + return split; + } + + StringType fullPathOf(const StringType& file) { + return _path + '/' + file; + } + + int openFile(const StringType& file) { + int fd = open(fullPathOf(file).c_str(), O_RDONLY); + assert(fd != -1); + return fd; + } + + void walkAndSeeChanges() { + struct RenamedPair { + StringType old; + StringType current; + }; + struct EventInfo { + StringType file; + struct timespec time; + Event event; + }; + std::unordered_map newSnapshot{}; + std::vector events{}; + + for (auto& entry : _directory_snapshot) { + struct stat stat; + + fstat(entry.second.fd, &stat); + if (fdIsRemoved(entry.second.fd)) { + events.push_back(EventInfo { + .event = Event::removed, + .file = entry.first, + .time = stat.st_ctimespec + }); + continue; + } + + StringType fullPath = fullPathOfFd(entry.second.fd); + PathParts pathPair = splitPath(fullPath); + + if (pathPair.directory != _path) { + events.push_back(EventInfo { + .event = Event::removed, + .file = entry.first, + .time = stat.st_ctimespec + }); + continue; + } + if (entry.first != pathPair.filename) { + events.push_back(EventInfo { + .event = Event::renamed_old, + .file = entry.first, + .time = stat.st_ctimespec + }); + events.push_back(EventInfo { + .event = Event::renamed_new, + .file = pathPair.filename, + .time = stat.st_ctimespec + }); + } + else { + if (stat.st_mtimespec.tv_sec > entry.second.last_modification) { + entry.second.last_modification = stat.st_mtimespec.tv_sec; + events.push_back(EventInfo { + .event = Event::modified, + .file = pathPair.filename, + .time = stat.st_mtimespec + }); + } + } + newSnapshot.insert(std::make_pair(std::move(pathPair.filename), + std::move(entry.second.invalidate_and_clone()))); + } + + walkDirectory(_path, [&](StringType file) { + if (isParentOrSelfDirectory(file) || !std::regex_match(file, _pattern)) { + return; + } + if (newSnapshot.count(file) == 0) { + FileState state = makeFileState(file); + struct stat stat; + + fstat(state.fd, &stat); + events.push_back(EventInfo { + .event = Event::added, + .file = file, + .time = stat.st_mtimespec + }); + newSnapshot.insert(std::make_pair(file, std::move(state))); + } + }); + + std::swap(_directory_snapshot, newSnapshot); + + std::sort(events.begin(), events.end(), [] (const EventInfo& a, EventInfo& b) { + if (a.time.tv_sec == b.time.tv_sec) { + return a.time.tv_nsec < b.time.tv_nsec; + } + return a.time.tv_sec < b.time.tv_sec; + }); + + { + std::lock_guard lock(_callback_mutex); + + for (const auto& event : events) { + _callback_information.push_back(std::make_pair(event.file, event.event)); + } + } + _cv.notify_all(); + } + + void seeSingleFileChanges() { + struct EventInfo { + StringType file; + Event event; + }; + + int eventCount = 1; + EventInfo eventInfos[2]; + + if (fdIsRemoved(_file_fd)) { + eventInfos[0].event = Event::removed; + eventInfos[0].file = _filename; + } + else { + StringType absPath = pathOfFd(_file_fd); + PathParts split = splitPath(absPath); + + if (split.directory != _path) { + eventInfos[0].event = Event::removed; + eventInfos[0].file = _filename; + } + else if (split.filename != _filename) { + eventInfos[0].event = Event::renamed_old; + eventInfos[0].file = std::move(_filename); + eventInfos[1].event = Event::renamed_new; + eventInfos[1].file = split.filename; + eventCount = 2; + _filename = std::move(split.filename); + } + else { + struct stat stat; + + fstat(_file_fd, &stat); + + if (stat.st_mtimespec.tv_sec > _last_modification_time.tv_sec) { + eventInfos[0].event = Event::modified; + eventInfos[0].file = _filename; + _last_modification_time = stat.st_mtimespec; + } + else if (stat.st_mtimespec.tv_nsec > _last_modification_time.tv_nsec) { + eventInfos[0].event = Event::modified; + eventInfos[0].file = _filename; + _last_modification_time = stat.st_mtimespec; + } + else { + return; + } + } + } + + { + std::lock_guard lock(_callback_mutex); + for (int i = 0; i < eventCount; i++) { + _callback_information.push_back( + std::make_pair(eventInfos[i].file, eventInfos[i].event)); + } + } + _cv.notify_all(); + } + + void notify(CFStringRef path, const FSEventStreamEventFlags flags) { + CFIndex pathLength = CFStringGetLength(path); + CFIndex written = 0; + char buffer[PATH_MAX + 1]; + + CFStringGetBytes(path, + CFRange { + .location = 0, + .length = pathLength, + }, + IsWChar::value? kCFStringEncodingUTF32 : kCFStringEncodingUTF8, + 0, + false, + (UInt8*)buffer, + PATH_MAX, + &written); + + buffer[written] = 0; + + StringType absolutePath{(const C*)buffer, static_cast(pathLength)}; + PathParts pathPair = splitPath(absolutePath); + + if (_watching_single_file && pathPair.filename != _filename) { + return; + } + if (pathPair.directory != _path || !std::regex_match(pathPair.filename, _pattern)) { + return; + } + + Event event = Event::modified; + if (_previous_event_is_rename) { + event = Event::renamed_new; + _directory_snapshot.insert(std::make_pair(pathPair.filename, + std::move(makeFileState(pathPair.filename)))); + _previous_event_is_rename = false; + } + else if (flags & kFSEventStreamEventFlagItemRenamed) { + const auto state = _directory_snapshot.find(pathPair.filename); + assert(state != _directory_snapshot.end()); + StringType fdPath = pathOfFd(state->second.fd); + + // moved/delete to Trash folder + if (!isInDirectory(absolutePath, fdPath)) { + event = Event::removed; + _directory_snapshot.erase(pathPair.filename); + } + else { + event = Event::renamed_old; + _previous_event_is_rename = true; + } + } + else if (flags & kFSEventStreamEventFlagItemCreated) { + _directory_snapshot.insert(std::make_pair(pathPair.filename, + std::move(makeFileState(pathPair.filename)))); + event = Event::added; + } + else if (flags & kFSEventStreamEventFlagItemRemoved) { + _directory_snapshot.erase(pathPair.filename); + event = Event::removed; + } + + { + std::lock_guard lock(_callback_mutex); + _callback_information.push_back(std::make_pair(std::move(pathPair.filename), event)); + } + _cv.notify_all(); + } + + static void handleFsEvent(__attribute__((unused)) ConstFSEventStreamRef streamFef, + void* clientCallBackInfo, + size_t numEvents, + CFArrayRef eventPaths, + const FSEventStreamEventFlags* eventFlags, + __attribute__((unused)) const FSEventStreamEventId* eventIds) { + FileWatch* self = (FileWatch*)clientCallBackInfo; + + for (size_t i = 0; i < numEvents; i++) { + FSEventStreamEventFlags flag = eventFlags[i]; + CFStringRef path = (CFStringRef)CFArrayGetValueAtIndex(eventPaths, i); + + if (self->_watching_single_file) { + self->seeSingleFileChanges(); + } + else if (flag & kFSEventStreamEventFlagMustScanSubDirs) { + self->walkAndSeeChanges(); + } + else { + self->notify(path, flag); + } + } + } + + FSEventStreamRef openStream(const StringType& directory) { + CFStringEncoding encoding = IsWChar::value? + kCFStringEncodingUTF32 : kCFStringEncodingASCII; + CFStringRef path = CFStringCreateWithBytes(kCFAllocatorDefault, + (const UInt8*)directory.data(), + directory.size(), + encoding, + false); + CFArrayRef paths = CFArrayCreate( + kCFAllocatorDefault, + (const void**)&path, + 1, + nullptr); + FSEventStreamContext context { + .info = (void*)this + }; + FSEventStreamRef event = FSEventStreamCreate( + kCFAllocatorDefault, + (FSEventStreamCallback)handleFsEvent, + &context, + paths, + kFSEventStreamEventIdSinceNow, + 0, + kFSEventStreamCreateFlagNoDefer | kFSEventStreamCreateFlagFileEvents | + kFSEventStreamCreateFlagUseCFTypes); + + CFRelease(path); + CFRelease(paths); + return event; + } + + FSEventStreamRef openStreamForDirectory(const StringType& directory) { + FSEventStreamRef stream = openStream(directory); + walkDirectory(directory, [this] (StringType path) mutable { + if (!isParentOrSelfDirectory(path) && std::regex_match(path, _pattern)) { + _directory_snapshot.insert(std::make_pair(std::move(path), + std::move(makeFileState(path)))); + } + }); + return stream; + } + + FSEventStreamRef openStreamForFile(const StringType& file) { + PathParts split = splitPath(file); + + _watching_single_file = true; + _filename = std::move(split.filename); + _file_fd = openFile(file); + return openStreamForDirectory(split.directory); + } + + FSEventStreamRef get_directory(const StringType& directory) { + struct stat stat; + + ::stat((const char*)directory.c_str(), &stat); + if (stat.st_mode & S_IFDIR) { + return openStreamForDirectory(directory); + } + _last_modification_time = stat.st_mtimespec; + return openStreamForFile(directory); + } + + void monitor_directory() { + _run_loop = CFRunLoopGetCurrent(); + FSEventStreamScheduleWithRunLoop(_directory, + _run_loop, + kCFRunLoopDefaultMode); + FSEventStreamStart(_directory); + _running.set_value(); + CFRunLoopRun(); + } +#endif // FILEWATCH_PLATFORM_MAC + + void callback_thread() + { + while (_destory == false) { + std::unique_lock lock(_callback_mutex); + if (_callback_information.empty() && _destory == false) { + _cv.wait(lock, [this] { return _callback_information.size() > 0 || _destory; }); + } + decltype(_callback_information) callback_information = {}; + std::swap(callback_information, _callback_information); + lock.unlock(); + + for (const auto& file : callback_information) { + if (_callback) { + try + { + _callback(file.first, file.second); + } + catch (const std::exception&) + { + } + } + } + } + } + }; + + template constexpr typename FileWatch::C FileWatch::_regex_all[]; + template constexpr typename FileWatch::C FileWatch::_this_directory[]; +} +#endif diff --git a/source/ModuleList.hpp b/source/includes/ModuleList.hpp similarity index 100% rename from source/ModuleList.hpp rename to source/includes/ModuleList.hpp diff --git a/source/leaderboards.ixx b/source/leaderboards.ixx new file mode 100644 index 0000000..e1fd80c --- /dev/null +++ b/source/leaderboards.ixx @@ -0,0 +1,29 @@ +module; + +#include + +export module leaderboards; + +import common; +import settings; + +class Leaderboards +{ +public: + Leaderboards() + { + FusionFix::onInitEvent() += []() + { + auto pattern = hook::pattern("8B 81 ? ? ? ? 83 F8 06"); + static raw_mem fnLeaderboards(pattern.get_first(0), { 0xC3 }); // ret + + FusionFix::onIniFileChange() += []() + { + if (FusionFixSettings.GetInt("PREF_DISABLELEADERBOARDS")) + fnLeaderboards.Write(); + else + fnLeaderboards.Restore(); + }; + }; + } +} Leaderboards; \ No newline at end of file diff --git a/source/ledsdk/LogitechLEDLib.h b/source/ledsdk/LogitechLEDLib.h new file mode 100644 index 0000000..d73d86d --- /dev/null +++ b/source/ledsdk/LogitechLEDLib.h @@ -0,0 +1,202 @@ +// +// Logitech Gaming LED SDK +// +// Copyright (C) 2011-2014 Logitech. All rights reserved. +// Author: Tiziano Pigliucci +// Email: devtechsupport@logitech.com + +#pragma once + +#define LOGI_LED_BITMAP_WIDTH 21 +#define LOGI_LED_BITMAP_HEIGHT 6 +#define LOGI_LED_BITMAP_BYTES_PER_KEY 4 + +#define LOGI_LED_BITMAP_SIZE (LOGI_LED_BITMAP_WIDTH*LOGI_LED_BITMAP_HEIGHT*LOGI_LED_BITMAP_BYTES_PER_KEY) + +#define LOGI_LED_DURATION_INFINITE 0 + +#define LOGI_DEVICETYPE_MONOCHROME_ORD 0 +#define LOGI_DEVICETYPE_RGB_ORD 1 +#define LOGI_DEVICETYPE_PERKEY_RGB_ORD 2 + +#define LOGI_DEVICETYPE_MONOCHROME (1 << LOGI_DEVICETYPE_MONOCHROME_ORD) +#define LOGI_DEVICETYPE_RGB (1 << LOGI_DEVICETYPE_RGB_ORD) +#define LOGI_DEVICETYPE_PERKEY_RGB (1 << LOGI_DEVICETYPE_PERKEY_RGB_ORD) + +#define LOGI_DEVICETYPE_ALL (LOGI_DEVICETYPE_MONOCHROME | LOGI_DEVICETYPE_RGB | LOGI_DEVICETYPE_PERKEY_RGB) + + +namespace LogiLed +{ + typedef enum + { + ESC = 0x01, + F1 = 0x3b, + F2 = 0x3c, + F3 = 0x3d, + F4 = 0x3e, + F5 = 0x3f, + F6 = 0x40, + F7 = 0x41, + F8 = 0x42, + F9 = 0x43, + F10 = 0x44, + F11 = 0x57, + F12 = 0x58, + PRINT_SCREEN = 0x137, + SCROLL_LOCK = 0x46, + PAUSE_BREAK = 0x145, + TILDE = 0x29, + ONE = 0x02, + TWO = 0x03, + THREE = 0x04, + FOUR = 0x05, + FIVE = 0x06, + SIX = 0x07, + SEVEN = 0x08, + EIGHT = 0x09, + NINE = 0x0A, + ZERO = 0x0B, + MINUS = 0x0C, + EQUALS = 0x0D, + BACKSPACE = 0x0E, + INSERT = 0x152, + HOME = 0x147, + PAGE_UP = 0x149, + NUM_LOCK = 0x45, + NUM_SLASH = 0x135, + NUM_ASTERISK = 0x37, + NUM_MINUS = 0x4A, + TAB = 0x0F, + Q = 0x10, + W = 0x11, + E = 0x12, + R = 0x13, + T = 0x14, + Y = 0x15, + U = 0x16, + I = 0x17, + O = 0x18, + P = 0x19, + OPEN_BRACKET = 0x1A, + CLOSE_BRACKET = 0x1B, + BACKSLASH = 0x2B, + KEYBOARD_DELETE = 0x153, + END = 0x14F, + PAGE_DOWN = 0x151, + NUM_SEVEN = 0x47, + NUM_EIGHT = 0x48, + NUM_NINE = 0x49, + NUM_PLUS = 0x4E, + CAPS_LOCK = 0x3A, + A = 0x1E, + S = 0x1F, + D = 0x20, + F = 0x21, + G = 0x22, + H = 0x23, + J = 0x24, + K = 0x25, + L = 0x26, + SEMICOLON = 0x27, + APOSTROPHE = 0x28, + ENTER = 0x1C, + NUM_FOUR = 0x4B, + NUM_FIVE = 0x4C, + NUM_SIX = 0x4D, + LEFT_SHIFT = 0x2A, + Z = 0x2C, + X = 0x2D, + C = 0x2E, + V = 0x2F, + B = 0x30, + N = 0x31, + M = 0x32, + COMMA = 0x33, + PERIOD = 0x34, + FORWARD_SLASH = 0x35, + RIGHT_SHIFT = 0x36, + ARROW_UP = 0x148, + NUM_ONE = 0x4F, + NUM_TWO = 0x50, + NUM_THREE = 0x51, + NUM_ENTER = 0x11C, + LEFT_CONTROL = 0x1D, + LEFT_WINDOWS = 0x15B, + LEFT_ALT = 0x38, + SPACE = 0x39, + RIGHT_ALT = 0x138, + RIGHT_WINDOWS = 0x15C, + APPLICATION_SELECT = 0x15D, + RIGHT_CONTROL = 0x11D, + ARROW_LEFT = 0x14B, + ARROW_DOWN = 0x150, + ARROW_RIGHT = 0x14D, + NUM_ZERO = 0x52, + NUM_PERIOD = 0x53, + G_1 = 0xFFF1, + G_2 = 0xFFF2, + G_3 = 0xFFF3, + G_4 = 0xFFF4, + G_5 = 0xFFF5, + G_6 = 0xFFF6, + G_7 = 0xFFF7, + G_8 = 0xFFF8, + G_9 = 0xFFF9, + G_LOGO = 0xFFFF1, + G_BADGE = 0xFFFF2 + + }KeyName; + + typedef enum + { + Keyboard = 0x0, + Mouse = 0x3, + Mousemat = 0x4, + Headset = 0x8, + Speaker = 0xe + }DeviceType; +} + +bool LogiLedInit(); +bool LogiLedInitWithName(const char name[]); + +bool LogiLedGetSdkVersion(int *majorNum, int *minorNum, int *buildNum); +bool LogiLedGetConfigOptionNumber(const wchar_t *configPath, double *defaultValue); +bool LogiLedGetConfigOptionBool(const wchar_t *configPath, bool *defaultValue); +bool LogiLedGetConfigOptionColor(const wchar_t *configPath, int *defaultRed, int *defaultGreen, int *defaultBlue); +bool LogiLedGetConfigOptionRect(const wchar_t *configPath, int *defaultX, int *defaultY, int *defaultWidth, int *defaultHeight); +bool LogiLedGetConfigOptionString(const wchar_t *configPath, wchar_t *defaultValue, int bufferSize); +bool LogiLedGetConfigOptionKeyInput(const wchar_t *configPath, wchar_t *defaultValue, int bufferSize); +bool LogiLedGetConfigOptionSelect(const wchar_t *configPath, wchar_t *defaultValue, int *valueSize, const wchar_t *values, int bufferSize); +bool LogiLedGetConfigOptionRange(const wchar_t *configPath, int *defaultValue, int min, int max); +bool LogiLedSetConfigOptionLabel(const wchar_t *configPath, wchar_t *label); + +//Generic functions => Apply to any device type. +bool LogiLedSetTargetDevice(int targetDevice); +bool LogiLedSaveCurrentLighting(); +bool LogiLedSetLighting(int redPercentage, int greenPercentage, int bluePercentage); +bool LogiLedRestoreLighting(); +bool LogiLedFlashLighting(int redPercentage, int greenPercentage, int bluePercentage, int milliSecondsDuration, int milliSecondsInterval); +bool LogiLedPulseLighting(int redPercentage, int greenPercentage, int bluePercentage, int milliSecondsDuration, int milliSecondsInterval); +bool LogiLedStopEffects(); + +//Per-key functions => only apply to LOGI_DEVICETYPE_PERKEY_RGB devices. +bool LogiLedSetLightingFromBitmap(unsigned char bitmap[]); +bool LogiLedSetLightingForKeyWithScanCode(int keyCode, int redPercentage, int greenPercentage, int bluePercentage); +bool LogiLedSetLightingForKeyWithHidCode(int keyCode, int redPercentage, int greenPercentage, int bluePercentage); +bool LogiLedSetLightingForKeyWithQuartzCode(int keyCode, int redPercentage, int greenPercentage, int bluePercentage); +bool LogiLedSetLightingForKeyWithKeyName(LogiLed::KeyName keyName, int redPercentage, int greenPercentage, int bluePercentage); +bool LogiLedSaveLightingForKey(LogiLed::KeyName keyName); +bool LogiLedRestoreLightingForKey(LogiLed::KeyName keyName); +bool LogiLedExcludeKeysFromBitmap(LogiLed::KeyName *keyList, int listCount); + +//Per-key effects => only apply to LOGI_DEVICETYPE_PERKEY_RGB devices. +bool LogiLedFlashSingleKey(LogiLed::KeyName keyName, int redPercentage, int greenPercentage, int bluePercentage, int msDuration, int msInterval); +bool LogiLedPulseSingleKey(LogiLed::KeyName keyName, int startRedPercentage, int startGreenPercentage, int startBluePercentage, int finishRedPercentage, int finishGreenPercentage, int finishBluePercentage, int msDuration, bool isInfinite); +bool LogiLedStopEffectsOnKey(LogiLed::KeyName keyName); + +//Zonal functions => only apply to devices with zones. +bool LogiLedSetLightingForTargetZone(LogiLed::DeviceType deviceType, int zone, int redPercentage, int greenPercentage, int bluePercentage); + +void LogiLedShutdown(); diff --git a/source/ledsdk/LogitechLEDLib.lib b/source/ledsdk/LogitechLEDLib.lib new file mode 100644 index 0000000..4f8afef Binary files /dev/null and b/source/ledsdk/LogitechLEDLib.lib differ diff --git a/source/misc.h b/source/misc.h deleted file mode 100644 index 2a7f128..0000000 --- a/source/misc.h +++ /dev/null @@ -1,215 +0,0 @@ -#pragma once -#define WIN32_LEAN_AND_MEAN -#include -#include -#include -#include -#include -#include -#include -#include "IniReader.h" -#include "injector\injector.hpp" -#include "injector\calling.hpp" -#include "injector\hooking.hpp" -#include "injector\assembly.hpp" -#include "injector\utility.hpp" -#include "Hooking.Patterns.h" -#include "ModuleList.hpp" - -#ifndef CEXP -#define CEXP extern "C" __declspec(dllexport) -#endif - -void CreateThreadAutoClose(LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId) -{ - CloseHandle(CreateThread(lpThreadAttributes, dwStackSize, lpStartAddress, lpParameter, dwCreationFlags, lpThreadId)); -} - -bool IsUALPresent() -{ - ModuleList dlls; - dlls.Enumerate(ModuleList::SearchLocation::LocalOnly); - for (auto& e : dlls.m_moduleList) - { - if (GetProcAddress(std::get(e), "DirectInput8Create") != NULL && GetProcAddress(std::get(e), "DirectSoundCreate8") != NULL && GetProcAddress(std::get(e), "InternetOpenA") != NULL) - return true; - } - return false; -} - -class CallbackHandler -{ -public: - static inline void RegisterCallback(std::function&& fn) - { - fn(); - } - - static inline void RegisterCallback(std::wstring_view module_name, std::function&& fn) - { - if (module_name.empty() || GetModuleHandleW(module_name.data()) != NULL) - { - fn(); - } - else - { - RegisterDllNotification(); - GetCallbackList().emplace(module_name, std::forward>(fn)); - } - } - - static inline void RegisterCallback(std::function&& fn, bool bPatternNotFound, ptrdiff_t offset = 0x1100, uint32_t * ptr = nullptr) - { - if (!bPatternNotFound) - { - fn(); - } - else - { - auto mh = GetModuleHandle(NULL); - IMAGE_NT_HEADERS* ntHeader = (IMAGE_NT_HEADERS*)((DWORD)mh + ((IMAGE_DOS_HEADER*)mh)->e_lfanew); - if (ptr == nullptr) - ptr = (uint32_t*)((DWORD)mh + ntHeader->OptionalHeader.BaseOfCode + ntHeader->OptionalHeader.SizeOfCode - offset); - std::thread([](std::function&& fn, uint32_t* ptr, uint32_t val) - { - while (*ptr == val) - std::this_thread::yield(); - - fn(); - }, fn, ptr, *ptr).detach(); - } - } - - static inline void RegisterCallback(std::function&& fn, hook::pattern pattern) - { - if (!pattern.empty()) - { - fn(); - } - else - { - auto* ptr = new ThreadParams{ fn, pattern }; - CreateThreadAutoClose(0, 0, (LPTHREAD_START_ROUTINE)& ThreadProc, (LPVOID)ptr, 0, NULL); - } - } - -private: - static inline void call(std::wstring_view module_name) - { - if (GetCallbackList().count(module_name.data())) - { - GetCallbackList().at(module_name.data())(); - GetCallbackList().erase(module_name.data()); - } - - //if (GetCallbackList().empty()) //win7 crash in splinter cell - // UnRegisterDllNotification(); - } - - static inline void invoke_all() - { - for (auto&& fn : GetCallbackList()) - fn.second(); - } - -private: - struct Comparator - { - bool operator() (const std::wstring& s1, const std::wstring& s2) const - { - std::wstring str1(s1.length(), ' '); - std::wstring str2(s2.length(), ' '); - std::transform(s1.begin(), s1.end(), str1.begin(), tolower); - std::transform(s2.begin(), s2.end(), str2.begin(), tolower); - return str1 < str2; - } - }; - - static std::map, Comparator>& GetCallbackList() - { - static std::map, Comparator> functions; - return functions; - } - - struct ThreadParams - { - std::function fn; - hook::pattern pattern; - }; - - typedef NTSTATUS(NTAPI* _LdrRegisterDllNotification) (ULONG, PVOID, PVOID, PVOID); - typedef NTSTATUS(NTAPI* _LdrUnregisterDllNotification) (PVOID); - - typedef struct _LDR_DLL_LOADED_NOTIFICATION_DATA - { - ULONG Flags; //Reserved. - PUNICODE_STRING FullDllName; //The full path name of the DLL module. - PUNICODE_STRING BaseDllName; //The base file name of the DLL module. - PVOID DllBase; //A pointer to the base address for the DLL in memory. - ULONG SizeOfImage; //The size of the DLL image, in bytes. - } LDR_DLL_LOADED_NOTIFICATION_DATA, LDR_DLL_UNLOADED_NOTIFICATION_DATA, * PLDR_DLL_LOADED_NOTIFICATION_DATA, * PLDR_DLL_UNLOADED_NOTIFICATION_DATA; - - typedef union _LDR_DLL_NOTIFICATION_DATA - { - LDR_DLL_LOADED_NOTIFICATION_DATA Loaded; - LDR_DLL_UNLOADED_NOTIFICATION_DATA Unloaded; - } LDR_DLL_NOTIFICATION_DATA, * PLDR_DLL_NOTIFICATION_DATA; - -private: - static inline void CALLBACK LdrDllNotification(ULONG NotificationReason, PLDR_DLL_NOTIFICATION_DATA NotificationData, PVOID Context) - { - static constexpr auto LDR_DLL_NOTIFICATION_REASON_LOADED = 1; - if (NotificationReason == LDR_DLL_NOTIFICATION_REASON_LOADED) - { - call(NotificationData->Loaded.BaseDllName->Buffer); - } - } - - static inline void RegisterDllNotification() - { - LdrRegisterDllNotification = (_LdrRegisterDllNotification)GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "LdrRegisterDllNotification"); - if (LdrRegisterDllNotification && !cookie) - LdrRegisterDllNotification(0, LdrDllNotification, 0, &cookie); - } - - static inline void UnRegisterDllNotification() - { - LdrUnregisterDllNotification = (_LdrUnregisterDllNotification)GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "LdrUnregisterDllNotification"); - if (LdrUnregisterDllNotification && cookie) - LdrUnregisterDllNotification(cookie); - } - - static inline DWORD WINAPI ThreadProc(LPVOID ptr) - { - auto paramsPtr = static_cast(ptr); - auto params = *paramsPtr; - delete paramsPtr; - - HANDLE hTimer = NULL; - LARGE_INTEGER liDueTime; - liDueTime.QuadPart = -30 * 10000000LL; - hTimer = CreateWaitableTimer(NULL, TRUE, NULL); - SetWaitableTimer(hTimer, &liDueTime, 0, NULL, NULL, 0); - - while (params.pattern.clear().empty()) - { - Sleep(0); - - if (WaitForSingleObject(hTimer, 0) == WAIT_OBJECT_0) - { - CloseHandle(hTimer); - return 0; - } - }; - - params.fn(); - - return 0; - } -private: - static inline _LdrRegisterDllNotification LdrRegisterDllNotification; - static inline _LdrUnregisterDllNotification LdrUnregisterDllNotification; - static inline void* cookie; -public: - static inline std::once_flag flag; -}; \ No newline at end of file diff --git a/source/outlines.ixx b/source/outlines.ixx new file mode 100644 index 0000000..9df832f --- /dev/null +++ b/source/outlines.ixx @@ -0,0 +1,50 @@ +module; + +#include + +export module outlines; + +import common; +import settings; + +float fOutlinesSizeMultiplier; +float* dword_1FCA9A8; +void __fastcall sub_010AFD30(float* _this, void* edx, float a2) +{ + a2 *= FusionFixSettings.GetFloat("PREF_OUTLINESIZE"); + + if (a2 <= 0.0) + { + _this[8] = 0.0; + _this[13] = 0.0; + } + else + { + _this[8] = a2 / *dword_1FCA9A8; + _this[13] = a2 / *dword_1FCA9A8; + } +} + +class Outlines +{ +public: + Outlines() + { + FusionFix::onInitEvent() += []() + { + dword_1FCA9A8 = *hook::get_pattern("F3 0F 10 86 ? ? ? ? F3 0F 59 05 ? ? ? ? 51 8D 8C 24", 12); + + auto pattern = hook::pattern("E8 ? ? ? ? 68 ? ? ? ? 8D 94 24 ? ? ? ? 6A 02"); + injector::MakeCALL(pattern.get_first(0), sub_010AFD30, true); + + pattern = hook::pattern("D9 1C 24 E8 ? ? ? ? 68 ? ? ? ? 68"); + injector::MakeCALL(pattern.get_first(3), sub_010AFD30, true); + + pattern = hook::pattern("E8 ? ? ? ? 68 ? ? ? ? 8D 44 24 20 6A 03 50 88 1D ? ? ? ? C6 05 ? ? ? ? ? 88 1D ? ? ? ? E8 ? ? ? ? 8B 08"); + injector::MakeCALL(pattern.get_first(0), sub_010AFD30, true); + + pattern = hook::pattern("E8 ? ? ? ? F3 0F 10 44 24 ? 8D 4C 24 30 F3 0F 11 05 ? ? ? ? F3 0F 10 44 24 ? 51 B9 ? ? ? ? F3 0F 11 05 ? ? ? ? E8 ? ? ? ? 8A 5C 24 0F 80 FB FF 75 0C"); + injector::MakeCALL(pattern.get_first(0), sub_010AFD30, true); + }; + } +} Outlines; \ No newline at end of file diff --git a/source/resources/Versioninfo.rc b/source/resources/Versioninfo.rc index fdc9b79..6f5d6c6 100644 Binary files a/source/resources/Versioninfo.rc and b/source/resources/Versioninfo.rc differ diff --git a/source/settings.ixx b/source/settings.ixx new file mode 100644 index 0000000..b195ee2 --- /dev/null +++ b/source/settings.ixx @@ -0,0 +1,90 @@ +module; + +#include +#include +#include + +export module settings; + +import common; +import comvars; + + + +export class CSettings +{ +private: + static inline std::map> mFusionPrefs; + +public: + static inline void ReadIniSettings() + { + CIniReader iniReader(""); + mFusionPrefs["PREF_HIDESKIP"] = std::clamp(iniReader.ReadInteger("MAIN", "HideSkipButton", 1), 0, 1); + mFusionPrefs["PREF_DISABLELEADERBOARDS"] = std::clamp(iniReader.ReadInteger("MAIN", "DisableGlobalLeaderboards", 1), 0, 1); + mFusionPrefs["PREF_OUTLINESIZE"] = std::clamp(iniReader.ReadFloat("MAIN", "OutlinesSizeMultiplier", 1.0f), 0.0f, 10.0f); + mFusionPrefs["PREF_BORDERLESS"] = std::clamp(iniReader.ReadInteger("MAIN", "BorderlessWindowed", 1), 0, 1); + mFusionPrefs["PREF_BUTTONS"] = std::clamp(iniReader.ReadInteger("MAIN", "GamepadIcons", 0), 0, 6); + + static std::once_flag flag; + std::call_once(flag, [&]() + { + if (std::filesystem::exists(iniReader.GetIniPath())) + { + static filewatch::FileWatch watch(iniReader.GetIniPath(), [&](const std::string& path, const filewatch::Event change_type) + { + if (change_type == filewatch::Event::modified) + { + ReadIniSettings(); + FusionFix::onIniFileChange().executeAll(); + } + }); + } + }); + } + + CSettings() + { + auto pattern = hook::pattern("FF 86 ? ? ? ? 0F B7 8E ? ? ? ? 8B 86 ? ? ? ? 3B C1 7C 0A"); + struct Inc + { + void operator()(injector::reg_pack& regs) + { + auto name = std::string_view((const char*)regs.esi + 0x4); + auto oldVal = *(int32_t*)(regs.esi + 0xE4); + *(int32_t*)(regs.esi + 0xE4) += 1; + FusionFix::onMenuOptionChange().executeAll(name, oldVal, *(int32_t*)(regs.esi + 0xE4)); + } + }; injector::MakeInline(pattern.get_first(0), pattern.get_first(6)); + + pattern = hook::pattern("FF 8E ? ? ? ? 8B 86 ? ? ? ? 8B 8E"); + struct Dec + { + void operator()(injector::reg_pack& regs) + { + auto name = std::string_view((const char*)regs.esi + 0x4); + auto oldVal = *(int32_t*)(regs.esi + 0xE4); + *(int32_t*)(regs.esi + 0xE4) -= 1; + FusionFix::onMenuOptionChange().executeAll(name, oldVal, *(int32_t*)(regs.esi + 0xE4)); + } + }; injector::MakeInline(pattern.get_first(0), pattern.get_first(6)); + + pattern = hook::pattern("C7 86 ? ? ? ? ? ? ? ? 8B 86 ? ? ? ? 8B 8E ? ? ? ? 8B 11"); + struct Zero + { + void operator()(injector::reg_pack& regs) + { + auto name = std::string_view((const char*)regs.esi + 0x4); + auto oldVal = *(int32_t*)(regs.esi + 0xE4); + *(int32_t*)(regs.esi + 0xE4) = 0; + FusionFix::onMenuOptionChange().executeAll(name, oldVal, *(int32_t*)(regs.esi + 0xE4)); + } + }; injector::MakeInline(pattern.get_first(0), pattern.get_first(10)); + } + +public: + int32_t GetInt(std::string_view name) { return std::get(mFusionPrefs[name]); } + float GetFloat(std::string_view name) { return std::get(mFusionPrefs[name]); } + std::string GetString(std::string_view name) { return std::get(mFusionPrefs[name]); } + void SetInt(std::string_view name, int32_t value) { mFusionPrefs[name] = value; } +} FusionFixSettings; \ No newline at end of file diff --git a/source/windowed.ixx b/source/windowed.ixx new file mode 100644 index 0000000..cbf6b34 --- /dev/null +++ b/source/windowed.ixx @@ -0,0 +1,190 @@ +module; + +#include + +export module windowed; + +import common; +import comvars; +import settings; + +BOOL WINAPI SetRect_Hook(LPRECT lprc, int xLeft, int yTop, int xRight, int yBottom) +{ + gRect = { xLeft, yTop, xRight, yBottom }; + return SetRect(lprc, xLeft, yTop, xRight, yBottom); +} + +BOOL WINAPI MoveWindow_Hook(HWND hWnd, int X, int Y, int nWidth, int nHeight, BOOL bRepaint) +{ + RECT rect = { X, Y, nWidth, nHeight }; + HMONITOR monitor = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST); + MONITORINFO info = {}; + info.cbSize = sizeof(MONITORINFO); + GetMonitorInfo(monitor, &info); + int32_t DesktopResW = info.rcMonitor.right - info.rcMonitor.left; + int32_t DesktopResH = info.rcMonitor.bottom - info.rcMonitor.top; + if ((rect.right - rect.left >= DesktopResW) || (rect.bottom - rect.top >= DesktopResH)) + rect = gRect; + rect.left = (LONG)(((float)DesktopResW / 2.0f) - ((float)rect.right / 2.0f)); + rect.top = (LONG)(((float)DesktopResH / 2.0f) - ((float)rect.bottom / 2.0f)); + return MoveWindow(hWnd, rect.left, rect.top, rect.right, rect.bottom, bRepaint); +} + +void SwitchWindowStyle() +{ + if (gWnd) + { + RECT rect = gRect; + LONG lStyle = GetWindowLong(gWnd, GWL_STYLE); + if (FusionFixSettings.GetInt("PREF_BORDERLESS")) + { + lStyle &= ~(WS_CAPTION | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SYSMENU); + } + else + { + GetWindowRect(gWnd, &rect); + lStyle |= (WS_CAPTION | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SYSMENU); + } + AdjustWindowRect(&rect, lStyle, FALSE); + SetWindowLong(gWnd, GWL_STYLE, lStyle); + MoveWindow_Hook(gWnd, 0, 0, rect.right - rect.left, rect.bottom - rect.top, TRUE); + } +} + +HWND WINAPI CreateWindowExA_Hook(DWORD dwExStyle, LPCSTR lpClassName, LPCSTR lpWindowName, DWORD dwStyle, int X, int Y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, LPVOID lpParam) +{ + gWnd = CreateWindowExA(dwExStyle, lpClassName, lpWindowName, 0, X, Y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam); + SwitchWindowStyle(); + return gWnd; +} + +HWND WINAPI CreateWindowExW_Hook(DWORD dwExStyle, LPCWSTR lpClassName, LPCWSTR lpWindowName, DWORD dwStyle, int X, int Y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, LPVOID lpParam) +{ + gWnd = CreateWindowExW(dwExStyle, lpClassName, lpWindowName, 0, X, Y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam); + SwitchWindowStyle(); + return gWnd; +} + +BOOL WINAPI CenterWindowPosition(int nWidth, int nHeight) +{ + // fix the window to open at the center of the screen... + HMONITOR monitor = MonitorFromWindow(GetDesktopWindow(), MONITOR_DEFAULTTONEAREST); + MONITORINFOEX info = { sizeof(MONITORINFOEX) }; + GetMonitorInfo(monitor, &info); + DEVMODE devmode = {}; + devmode.dmSize = sizeof(DEVMODE); + EnumDisplaySettings(info.szDevice, ENUM_CURRENT_SETTINGS, &devmode); + DWORD DesktopX = devmode.dmPelsWidth; + DWORD DesktopY = devmode.dmPelsHeight; + + int newWidth = nWidth; + int newHeight = nHeight; + + int WindowPosX = (int)(((float)DesktopX / 2.0f) - ((float)newWidth / 2.0f)); + int WindowPosY = (int)(((float)DesktopY / 2.0f) - ((float)newHeight / 2.0f)); + + return SetWindowPos(gWnd, 0, WindowPosX, WindowPosY, newWidth, newHeight, SWP_NOZORDER | SWP_FRAMECHANGED); +} + +BOOL WINAPI AdjustWindowRect_Hook(LPRECT lpRect, DWORD dwStyle, BOOL bMenu) +{ + if (FusionFixSettings.GetInt("PREF_BORDERLESS")) + dwStyle &= ~(WS_CAPTION | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SYSMENU); + else + dwStyle |= (WS_CAPTION | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SYSMENU); + + return AdjustWindowRect(lpRect, dwStyle, bMenu); +} + +static void afterCreateWindow() +{ + LONG lStyle = GetWindowLong(gWnd, GWL_STYLE); + + if (FusionFixSettings.GetInt("PREF_BORDERLESS")) + lStyle &= ~(WS_CAPTION | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SYSMENU); + else + { + lStyle |= (WS_MINIMIZEBOX | WS_SYSMENU); + } + + SetWindowLong(gWnd, GWL_STYLE, lStyle); + SetWindowPos(gWnd, 0, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_FRAMECHANGED); +} + +BOOL WINAPI SetWindowPos_Hook(HWND hWnd, HWND hWndInsertAfter, int X, int Y, int cx, int cy, UINT uFlags) +{ + BOOL res = SetWindowPos(hWnd, hWndInsertAfter, X, Y, cx, cy, uFlags); + if (FusionFixSettings.GetInt("PREF_BORDERLESS")) + { + afterCreateWindow(); + RECT rect; + GetClientRect(hWnd, &rect); + CenterWindowPosition(rect.right, rect.bottom); + return TRUE; + } + return res; +} + +LONG WINAPI SetWindowLongA_Hook(HWND hWnd, int nIndex, LONG dwNewLong) +{ + if (FusionFixSettings.GetInt("PREF_BORDERLESS")) + { + afterCreateWindow(); + RECT rect; + GetClientRect(hWnd, &rect); + CenterWindowPosition(rect.right, rect.bottom); + return dwNewLong; + } + else + { + dwNewLong |= (WS_MINIMIZEBOX | WS_SYSMENU); + } + + return SetWindowLongA(hWnd, GWL_STYLE, dwNewLong); +} + +class Windowed +{ +public: + Windowed() + { + FusionFix::onInitEvent() += []() + { + IATHook::Replace(GetModuleHandleA(NULL), "USER32.DLL", + std::forward_as_tuple("CreateWindowExA", CreateWindowExA_Hook), + std::forward_as_tuple("CreateWindowExW", CreateWindowExW_Hook), + std::forward_as_tuple("MoveWindow", MoveWindow_Hook), + std::forward_as_tuple("AdjustWindowRect", AdjustWindowRect_Hook), + std::forward_as_tuple("SetRect", SetRect_Hook), + std::forward_as_tuple("SetWindowLongA", SetWindowLongA_Hook), + std::forward_as_tuple("SetWindowPos", SetWindowPos_Hook) + ); + + FusionFix::onIniFileChange() += []() + { + SwitchWindowStyle(); + }; + + FusionFix::onMenuOptionChange() += [](std::string_view name, int32_t oldVal, int32_t curVal) + { + if (name == "MS_Graphics.FullScreenList") + { + if (curVal == 0) // windowed + { + static auto counter = 0; + + CIniReader iniWriter(""); + iniWriter.WriteInteger("MAIN", "BorderlessWindowed", counter); + + counter++; + + if (counter > 1) + counter = 0; + else if (counter < 0) + counter = 1; + } + } + }; + }; + } +} Windowed; \ No newline at end of file