diff --git a/.gitignore b/.gitignore
index 9491a2f..2ef9cb1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -360,4 +360,6 @@ MigrationBackup/
.ionide/
# Fody - auto-generated XML schema
-FodyWeavers.xsd
\ No newline at end of file
+FodyWeavers.xsd
+
+enc_temp_folder/
\ No newline at end of file
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..eada14b
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,21 @@
+[submodule "cheat-base/vendor/simpleIni"]
+ path = cheat-base/vendor/simpleIni
+ url = https://github.com/brofield/simpleini.git
+[submodule "cheat-base/vendor/fmt"]
+ path = cheat-base/vendor/fmt
+ url = https://github.com/fmtlib/fmt.git
+[submodule "cheat-base/vendor/magic_enum"]
+ path = cheat-base/vendor/magic_enum
+ url = https://github.com/Neargye/magic_enum.git
+[submodule "cheat-library/vendor/protobuf"]
+ path = cheat-library/vendor/protobuf
+ url = https://github.com/protocolbuffers/protobuf.git
+[submodule "cheat-library/vendor/json"]
+ path = cheat-base/vendor/json
+ url = https://github.com/nlohmann/json.git
+[submodule "cheat-base/vendor/stb"]
+ path = cheat-base/vendor/stb
+ url = https://github.com/nothings/stb.git
+[submodule "cheat-base/vendor/imgui"]
+ path = cheat-base/vendor/imgui
+ url = https://github.com/CallowBlack/imgui-brightness-fix.git
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..f4f776a
--- /dev/null
+++ b/README.md
@@ -0,0 +1,137 @@
+
Akebi GC
+The great software for some game that exploiting anime girls (and boys).
+
+
+Getting Started
+
+### Building from source
+It is reccomended to use [Visual Studio 2022.](https://visualstudio.microsoft.com/)
+As well as setting up **`cheat-library`** as startup project.
+**The following is a recommended procedure, but others may be used.**
+1. Clone repository with `git clone --recurse-submodules https://github.com/Akebi-Group/Akebi-GC.git`
+1. Open `Akebi-GC/akebi-gc.sln`
+1. Build solution `akebi-gc.sln`.
+
+### Release
+1. Head over to the releases page
+1. Download the latest binaries
+
+### Usage
+(1-2 are optional if you didn't build from source)
+1. Open `/bin`
+1. Open Compiled version (debug, release)
+
+
+1. Insure that `CLibrary.dll` is in the same folder that `injector.exe`.
+1. Run `injector.exe`.
+
+Features
+
+#### General
+- Protection Bypass
+- In-Game GUI
+- Hotkeys
+
+#### Player
+- God Mode
+- Unlimited Stamina
+- Dumb Enemies (Enemies don't attack)
+- Player
+- Multiply Attacks
+- No Cooldown Skill/Ultimate
+- No Cooldown Sprint
+
+#### World
+- Auto Loot
+- Auto Talk
+- Killaura
+- Auto Tree Farm
+- Mob Vacuum
+- Auto Fish
+
+#### Teleport
+- Chest/Oculi Teleport (Teleports to nearest)
+- Map Teleport (Teleport to mark on map)
+
+#### Visuals
+- ESP
+- Interactive Map
+- Elemental Sight
+
+#### Debugging
+- Entity List
+- Position Info
+- FPS Graph
+- Packet Sniffer
+
+
+Demo
+
+
+ Map Teleportation
+
+
+
+ Noclip
+
+
+
+ TP to Oculi
+
+
+
+ TP to Chests
+
+
+
+ Rapid Fire
+
+
+
+ Auto Talk
+
+
+
+Roadmap
+
+- [ ] Cutscene Skipping
+- [ ] Create database for chests, oculi, etc.
+
+Contributing
+
+## Adding a feature
+1. Fork the Project
+1. Create your Feature Branch (`git checkout -b feature/AmazingFeature`)
+1. Commit your Changes (`git commit -m 'Add some AmazingFeature'`)
+1. Push to the Branch (`git push origin feature/AmazingFeature`)
+1. Open a Pull Request
+
+## Suggestions
+
+Open an issue with the title of the suggesstion you want to make.
+In the description, make sure it is descriptive enough so our devs can understand what you want and how you want it.
+
+## Bugs
+Welcome to the short explanation for bug reporting, as well as the bug report template.
+
+1. Find a bug and write down what happened, as well as your first thoughts on what you think caused it.
+
+2. Try to reproduce the bug. For this you need to understand what actually happened, leading up to the bug and when the actual bug happened. To make sure you get all this information correctly taking various forms of documentations, such as video, screenshots etc is essential. These steps makes it a lot easier to try and figure out what actually happened. Try to replicate the scenario where the bug appeared, as close to the original as possible. What we would recommend for this step is using the bug reporting template which can be found on page 2 and simply adding the information you have / find in there.
+
+3. can it be reproduced? Yes or no. If yes: Explain in as much detail as possible what happens when the bug occurs and why it occurs. Try and explain it as cleanly and as concise as possible to make sure that the coders don’t have to read an essay to understand what could be a simple bug with a simple fix. For this, remember that information is very subjective so it is much better to over communicate than to risk confusion. If no: Try to provide as much information about the bug as possible, so that the testers will be able to replicate the scenario in which the bug occurred more easily so we can try to reproduce the bug.
+
+4. Tell us which version you are using. Otherwise we would be getting bug reports on the same issue, that has been infact fixed in the latest commits. copy the SHA / Version Number of the latest commit when you built the mod. For example: `bd17a00ec388f3b93624280cde9e1c66e740edf9` / Release 0.7
+
+Notes: Please remember to always record your testing sessions on your local hard drive and then upload them unlisted to youtube to conserve memory space on your computer and to give us easy access to your replays. This is to ensure that the optimal amount of documentation is available for the bug testers and coders to use as a guideline for either replicating scenarios, reproducing bugs or fixing them.
+
+TL:DR Record all your stuff while playing the mod and report any bugs to the issues section of this repository.
+
+### Bug reporting template
+Title: e.g. “Instantly kill enemy with Shackles“
+Description: “Game crashed if x, y, z“
+
+-- Footer --
+Date Occured: 5 / 3 / 2022
+Is it reproducible: Yes / Occasionally / No
+Latest Commit used: `bd17a00ec388f3b93624280cde9e1c66e740edf9`
+Release Version: 0.7
diff --git a/akebi-gc.sln b/akebi-gc.sln
new file mode 100644
index 0000000..1915763
--- /dev/null
+++ b/akebi-gc.sln
@@ -0,0 +1,65 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.32014.148
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "injector", "injector\injector.vcxproj", "{F578B30C-8DE6-4741-99E4-1D30D2ACDAC4}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "cheat-base", "cheat-base\cheat-base.vcxproj", "{ADB35022-823B-4DC0-B495-3EFEFBD3A82F}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "cheat-library", "cheat-library\cheat-library.vcxproj", "{CE178784-CB96-45CA-AE16-FC0DA1126970}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release_WithScanner|x64 = Release_WithScanner|x64
+ Release_WithScanner|x86 = Release_WithScanner|x86
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {F578B30C-8DE6-4741-99E4-1D30D2ACDAC4}.Debug|x64.ActiveCfg = Debug|x64
+ {F578B30C-8DE6-4741-99E4-1D30D2ACDAC4}.Debug|x64.Build.0 = Debug|x64
+ {F578B30C-8DE6-4741-99E4-1D30D2ACDAC4}.Debug|x86.ActiveCfg = Debug|x64
+ {F578B30C-8DE6-4741-99E4-1D30D2ACDAC4}.Debug|x86.Build.0 = Debug|x64
+ {F578B30C-8DE6-4741-99E4-1D30D2ACDAC4}.Release_WithScanner|x64.ActiveCfg = Release_WS|x64
+ {F578B30C-8DE6-4741-99E4-1D30D2ACDAC4}.Release_WithScanner|x64.Build.0 = Release_WS|x64
+ {F578B30C-8DE6-4741-99E4-1D30D2ACDAC4}.Release_WithScanner|x86.ActiveCfg = Release_WithScanner|x64
+ {F578B30C-8DE6-4741-99E4-1D30D2ACDAC4}.Release_WithScanner|x86.Build.0 = Release_WithScanner|x64
+ {F578B30C-8DE6-4741-99E4-1D30D2ACDAC4}.Release|x64.ActiveCfg = Release|x64
+ {F578B30C-8DE6-4741-99E4-1D30D2ACDAC4}.Release|x64.Build.0 = Release|x64
+ {F578B30C-8DE6-4741-99E4-1D30D2ACDAC4}.Release|x86.ActiveCfg = Release|x64
+ {F578B30C-8DE6-4741-99E4-1D30D2ACDAC4}.Release|x86.Build.0 = Release|x64
+ {ADB35022-823B-4DC0-B495-3EFEFBD3A82F}.Debug|x64.ActiveCfg = Debug|x64
+ {ADB35022-823B-4DC0-B495-3EFEFBD3A82F}.Debug|x64.Build.0 = Debug|x64
+ {ADB35022-823B-4DC0-B495-3EFEFBD3A82F}.Debug|x86.ActiveCfg = Debug|x64
+ {ADB35022-823B-4DC0-B495-3EFEFBD3A82F}.Debug|x86.Build.0 = Debug|x64
+ {ADB35022-823B-4DC0-B495-3EFEFBD3A82F}.Release_WithScanner|x64.ActiveCfg = Release_WS|x64
+ {ADB35022-823B-4DC0-B495-3EFEFBD3A82F}.Release_WithScanner|x64.Build.0 = Release_WS|x64
+ {ADB35022-823B-4DC0-B495-3EFEFBD3A82F}.Release_WithScanner|x86.ActiveCfg = Release_WithScanner|x64
+ {ADB35022-823B-4DC0-B495-3EFEFBD3A82F}.Release_WithScanner|x86.Build.0 = Release_WithScanner|x64
+ {ADB35022-823B-4DC0-B495-3EFEFBD3A82F}.Release|x64.ActiveCfg = Release|x64
+ {ADB35022-823B-4DC0-B495-3EFEFBD3A82F}.Release|x64.Build.0 = Release|x64
+ {ADB35022-823B-4DC0-B495-3EFEFBD3A82F}.Release|x86.ActiveCfg = Release|x64
+ {ADB35022-823B-4DC0-B495-3EFEFBD3A82F}.Release|x86.Build.0 = Release|x64
+ {CE178784-CB96-45CA-AE16-FC0DA1126970}.Debug|x64.ActiveCfg = Debug|x64
+ {CE178784-CB96-45CA-AE16-FC0DA1126970}.Debug|x64.Build.0 = Debug|x64
+ {CE178784-CB96-45CA-AE16-FC0DA1126970}.Debug|x86.ActiveCfg = Debug|x64
+ {CE178784-CB96-45CA-AE16-FC0DA1126970}.Debug|x86.Build.0 = Debug|x64
+ {CE178784-CB96-45CA-AE16-FC0DA1126970}.Release_WithScanner|x64.ActiveCfg = Release_WS|x64
+ {CE178784-CB96-45CA-AE16-FC0DA1126970}.Release_WithScanner|x64.Build.0 = Release_WS|x64
+ {CE178784-CB96-45CA-AE16-FC0DA1126970}.Release_WithScanner|x86.ActiveCfg = Release_WithScanner|x64
+ {CE178784-CB96-45CA-AE16-FC0DA1126970}.Release_WithScanner|x86.Build.0 = Release_WithScanner|x64
+ {CE178784-CB96-45CA-AE16-FC0DA1126970}.Release|x64.ActiveCfg = Release|x64
+ {CE178784-CB96-45CA-AE16-FC0DA1126970}.Release|x64.Build.0 = Release|x64
+ {CE178784-CB96-45CA-AE16-FC0DA1126970}.Release|x86.ActiveCfg = Release|x64
+ {CE178784-CB96-45CA-AE16-FC0DA1126970}.Release|x86.Build.0 = Release|x64
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {0F485A89-A340-4742-97E7-923E14A929A1}
+ EndGlobalSection
+EndGlobal
diff --git a/cheat-base/cheat-base.vcxproj b/cheat-base/cheat-base.vcxproj
new file mode 100644
index 0000000..f1a225e
--- /dev/null
+++ b/cheat-base/cheat-base.vcxproj
@@ -0,0 +1,282 @@
+
+
+
+
+ Debug
+ x64
+
+
+ Release_WS
+ x64
+
+
+ Release
+ x64
+
+
+
+ 16.0
+ Win32Proj
+ {adb35022-823b-4dc0-b495-3efefbd3a82f}
+ cheat-base
+ 10.0
+ cheat-base
+
+
+
+ StaticLibrary
+ true
+ v143
+ MultiByte
+
+
+ StaticLibrary
+ false
+ v143
+ true
+ MultiByte
+
+
+ StaticLibrary
+ false
+ v143
+ true
+ MultiByte
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+ $(SolutionDir)bin\$(Configuration)\
+ $(SolutionDir)bin\$(Configuration)\obj\$(ProjectName)\
+
+
+ false
+ $(SolutionDir)bin\$(Configuration)\
+ $(SolutionDir)bin\$(Configuration)\obj\$(ProjectName)\
+
+
+ false
+ $(SolutionDir)bin\$(Configuration)\
+ $(SolutionDir)bin\$(Configuration)\obj\$(ProjectName)\
+
+
+
+ Level3
+ true
+ _AMD64_;_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING;_SILENCE_ALL_CXX17_DEPRECATION_WARNINGS;_CRT_SECURE_NO_WARNINGS;_DEBUG;_LIB;%(PreprocessorDefinitions)
+ true
+ Use
+ pch.h
+ stdcpp17
+ $(ProjectDir)framework\;$(ProjectDir)src\;$(ProjectDir)vendor\detours\;$(ProjectDir)vendor\fmt\include\;$(ProjectDir)vendor\imgui\;$(ProjectDir)vendor\magic_enum\include\;$(ProjectDir)vendor\simpleIni\;$(ProjectDir)vendor\json\single_include\;$(ProjectDir)vendor\imgui-notify-v2\;$(ProjectDir)vendor\stb\
+
+
+
+
+ true
+
+
+
+
+
+
+ detours-x64.lib;%(AdditionalDependencies)
+
+
+ $(ProjectDir)/vendor/detours/;%(AdditionalLibraryDirectories)
+
+
+
+
+ Level3
+ true
+ false
+ true
+ _AMD64_;_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING;_SILENCE_ALL_CXX17_DEPRECATION_WARNINGS;_CRT_SECURE_NO_WARNINGS;NDEBUG;_LIB;%(PreprocessorDefinitions)
+ true
+ Use
+ pch.h
+ stdcpp17
+ $(ProjectDir)framework\;$(ProjectDir)src\;$(ProjectDir)vendor\detours\;$(ProjectDir)vendor\fmt\include\;$(ProjectDir)vendor\imgui\;$(ProjectDir)vendor\magic_enum\include\;$(ProjectDir)vendor\simpleIni\;$(ProjectDir)vendor\json\single_include\;$(ProjectDir)vendor\imgui-notify-v2\;$(ProjectDir)vendor\stb\
+ Disabled
+ false
+
+
+
+
+ true
+ true
+ true
+
+
+
+
+
+
+ detours-x64.lib;%(AdditionalDependencies)
+
+
+ $(ProjectDir)/vendor/detours/;%(AdditionalLibraryDirectories)
+
+
+
+
+ Level3
+ true
+ false
+ true
+ _AMD64_;_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING;_SILENCE_ALL_CXX17_DEPRECATION_WARNINGS;_CRT_SECURE_NO_WARNINGS;NDEBUG;_LIB;%(PreprocessorDefinitions)
+ true
+ Use
+ pch.h
+ stdcpp17
+ $(ProjectDir)framework\;$(ProjectDir)src\;$(ProjectDir)vendor\detours\;$(ProjectDir)vendor\fmt\include\;$(ProjectDir)vendor\imgui\;$(ProjectDir)vendor\magic_enum\include\;$(ProjectDir)vendor\simpleIni\;$(ProjectDir)vendor\json\single_include\;$(ProjectDir)vendor\imgui-notify-v2\;$(ProjectDir)vendor\stb\
+ Disabled
+ false
+
+
+
+
+ true
+ true
+ true
+
+
+
+
+
+
+ detours-x64.lib;%(AdditionalDependencies)
+
+
+ $(ProjectDir)/vendor/detours/;%(AdditionalLibraryDirectories)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ NotUsing
+ NotUsing
+ NotUsing
+
+
+ NotUsing
+ NotUsing
+ NotUsing
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Create
+ Create
+ Create
+
+
+
+ NotUsing
+ NotUsing
+ NotUsing
+
+
+ NotUsing
+ NotUsing
+ NotUsing
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cheat-base/cheat-base.vcxproj.filters b/cheat-base/cheat-base.vcxproj.filters
new file mode 100644
index 0000000..55bc8c5
--- /dev/null
+++ b/cheat-base/cheat-base.vcxproj.filters
@@ -0,0 +1,279 @@
+
+
+
+
+ {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
+ cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx
+
+
+ {93995380-89BD-4b04-88EB-625FBE52EBFB}
+ h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd
+
+
+ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
+ rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
+
+
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+
\ No newline at end of file
diff --git a/cheat-base/framework/framework.h b/cheat-base/framework/framework.h
new file mode 100644
index 0000000..3209b4a
--- /dev/null
+++ b/cheat-base/framework/framework.h
@@ -0,0 +1,3 @@
+#pragma once
+
+#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
diff --git a/cheat-base/framework/pch.cpp b/cheat-base/framework/pch.cpp
new file mode 100644
index 0000000..64b7eef
--- /dev/null
+++ b/cheat-base/framework/pch.cpp
@@ -0,0 +1,5 @@
+// pch.cpp: source file corresponding to the pre-compiled header
+
+#include "pch.h"
+
+// When you are using pre-compiled headers, this source file is necessary for compilation to succeed.
diff --git a/cheat-base/framework/pch.h b/cheat-base/framework/pch.h
new file mode 100644
index 0000000..2cfc048
--- /dev/null
+++ b/cheat-base/framework/pch.h
@@ -0,0 +1,47 @@
+// pch.h: This is a precompiled header file.
+// Files listed below are compiled only once, improving build performance for future builds.
+// This also affects IntelliSense performance, including code completion and many code browsing features.
+// However, files listed here are ALL re-compiled if any one of them is updated between builds.
+// Do not add files here that you will be updating frequently as this negates the performance advantage.
+
+#ifndef PCH_H
+#define PCH_H
+
+// add headers that you want to pre-compile here
+#include "framework.h"
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+#endif //PCH_H
diff --git a/cheat-base/src/cheat-base/HookManager.h b/cheat-base/src/cheat-base/HookManager.h
new file mode 100644
index 0000000..41b3f85
--- /dev/null
+++ b/cheat-base/src/cheat-base/HookManager.h
@@ -0,0 +1,78 @@
+#pragma once
+
+#include
+#include
+
+#define CALL_ORIGIN(function, ...) \
+ HookManager::call(function, __func__, __VA_ARGS__)
+
+class HookManager
+{
+public:
+ template
+ static void install(Fn func, Fn handler)
+ {
+ enable(func, handler);
+ holderMap[reinterpret_cast(handler)] = reinterpret_cast(func);
+ }
+
+ template
+ [[nodiscard]] static Fn getOrigin(Fn handler, const char* callerName = nullptr) noexcept
+ {
+ if (holderMap.count(reinterpret_cast(handler)) == 0) {
+ LOG_WARNING("Origin not found for handler: %s. Maybe racing bug.", callerName == nullptr ? "" : callerName);
+ return nullptr;
+ }
+ return reinterpret_cast(holderMap[reinterpret_cast(handler)]);
+ }
+
+ template
+ [[nodiscard]] static void detach(Fn handler) noexcept
+ {
+ disable(handler);
+ holderMap.erase(reinterpret_cast(handler));
+ }
+
+ template
+ [[nodiscard]] static RType call(RType(*handler)(Params...), const char* callerName = nullptr, Params... params)
+ {
+ auto origin = getOrigin(handler, callerName);
+ if (origin != nullptr)
+ return origin(params...);
+
+ return RType();
+ }
+
+ static void detachAll() noexcept
+ {
+ for (const auto &[key, value] : holderMap)
+ {
+ disable(key);
+ }
+ holderMap.clear();
+ }
+
+private:
+ inline static std::map holderMap{};
+
+ template
+ static void disable(Fn handler)
+ {
+ Fn origin = getOrigin(handler);
+ DetourTransactionBegin();
+ DetourUpdateThread(GetCurrentThread());
+ DetourDetach(&(PVOID&)origin, handler);
+ DetourTransactionCommit();
+ }
+
+ template
+ static void enable(Fn& func, Fn handler)
+ {
+ DetourTransactionBegin();
+ DetourUpdateThread(GetCurrentThread());
+ DetourAttach(&(PVOID&)func, handler);
+ DetourTransactionCommit();
+ }
+};
+
+
diff --git a/cheat-base/src/cheat-base/Hotkey.cpp b/cheat-base/src/cheat-base/Hotkey.cpp
new file mode 100644
index 0000000..fbec295
--- /dev/null
+++ b/cheat-base/src/cheat-base/Hotkey.cpp
@@ -0,0 +1,395 @@
+#include
+#include "Hotkey.h"
+
+#define IM_VK_KEYPAD_ENTER (VK_RETURN + 256)
+
+static ImGuiKey LegacyToInput(short key)
+{
+ switch (key)
+ {
+ case VK_TAB: return ImGuiKey_Tab;
+ case VK_LEFT: return ImGuiKey_LeftArrow;
+ case VK_RIGHT: return ImGuiKey_RightArrow;
+ case VK_UP: return ImGuiKey_UpArrow;
+ case VK_DOWN: return ImGuiKey_DownArrow;
+ case VK_PRIOR: return ImGuiKey_PageUp;
+ case VK_NEXT: return ImGuiKey_PageDown;
+ case VK_HOME: return ImGuiKey_Home;
+ case VK_END: return ImGuiKey_End;
+ case VK_INSERT: return ImGuiKey_Insert;
+ case VK_DELETE: return ImGuiKey_Delete;
+ case VK_BACK: return ImGuiKey_Backspace;
+ case VK_SPACE: return ImGuiKey_Space;
+ case VK_RETURN: return ImGuiKey_Enter;
+ case VK_ESCAPE: return ImGuiKey_Escape;
+ case VK_OEM_7: return ImGuiKey_Apostrophe;
+ case VK_OEM_COMMA: return ImGuiKey_Comma;
+ case VK_OEM_MINUS: return ImGuiKey_Minus;
+ case VK_OEM_PERIOD: return ImGuiKey_Period;
+ case VK_OEM_2: return ImGuiKey_Slash;
+ case VK_OEM_1: return ImGuiKey_Semicolon;
+ case VK_OEM_PLUS: return ImGuiKey_Equal;
+ case VK_OEM_4: return ImGuiKey_LeftBracket;
+ case VK_OEM_5: return ImGuiKey_Backslash;
+ case VK_OEM_6: return ImGuiKey_RightBracket;
+ case VK_OEM_3: return ImGuiKey_GraveAccent;
+ case VK_CAPITAL: return ImGuiKey_CapsLock;
+ case VK_SCROLL: return ImGuiKey_ScrollLock;
+ case VK_NUMLOCK: return ImGuiKey_NumLock;
+ case VK_SNAPSHOT: return ImGuiKey_PrintScreen;
+ case VK_PAUSE: return ImGuiKey_Pause;
+ case VK_NUMPAD0: return ImGuiKey_Keypad0;
+ case VK_NUMPAD1: return ImGuiKey_Keypad1;
+ case VK_NUMPAD2: return ImGuiKey_Keypad2;
+ case VK_NUMPAD3: return ImGuiKey_Keypad3;
+ case VK_NUMPAD4: return ImGuiKey_Keypad4;
+ case VK_NUMPAD5: return ImGuiKey_Keypad5;
+ case VK_NUMPAD6: return ImGuiKey_Keypad6;
+ case VK_NUMPAD7: return ImGuiKey_Keypad7;
+ case VK_NUMPAD8: return ImGuiKey_Keypad8;
+ case VK_NUMPAD9: return ImGuiKey_Keypad9;
+ case VK_DECIMAL: return ImGuiKey_KeypadDecimal;
+ case VK_DIVIDE: return ImGuiKey_KeypadDivide;
+ case VK_MULTIPLY: return ImGuiKey_KeypadMultiply;
+ case VK_SUBTRACT: return ImGuiKey_KeypadSubtract;
+ case VK_ADD: return ImGuiKey_KeypadAdd;
+ case IM_VK_KEYPAD_ENTER: return ImGuiKey_KeypadEnter;
+ case VK_LSHIFT: return ImGuiKey_LeftShift;
+ case VK_LCONTROL: return ImGuiKey_LeftCtrl;
+ case VK_LMENU: return ImGuiKey_LeftAlt;
+ case VK_LWIN: return ImGuiKey_LeftSuper;
+ case VK_RSHIFT: return ImGuiKey_RightShift;
+ case VK_RCONTROL: return ImGuiKey_RightCtrl;
+ case VK_RMENU: return ImGuiKey_RightAlt;
+ case VK_RWIN: return ImGuiKey_RightSuper;
+ case VK_APPS: return ImGuiKey_Menu;
+ case '0': return ImGuiKey_0;
+ case '1': return ImGuiKey_1;
+ case '2': return ImGuiKey_2;
+ case '3': return ImGuiKey_3;
+ case '4': return ImGuiKey_4;
+ case '5': return ImGuiKey_5;
+ case '6': return ImGuiKey_6;
+ case '7': return ImGuiKey_7;
+ case '8': return ImGuiKey_8;
+ case '9': return ImGuiKey_9;
+ case 'A': return ImGuiKey_A;
+ case 'B': return ImGuiKey_B;
+ case 'C': return ImGuiKey_C;
+ case 'D': return ImGuiKey_D;
+ case 'E': return ImGuiKey_E;
+ case 'F': return ImGuiKey_F;
+ case 'G': return ImGuiKey_G;
+ case 'H': return ImGuiKey_H;
+ case 'I': return ImGuiKey_I;
+ case 'J': return ImGuiKey_J;
+ case 'K': return ImGuiKey_K;
+ case 'L': return ImGuiKey_L;
+ case 'M': return ImGuiKey_M;
+ case 'N': return ImGuiKey_N;
+ case 'O': return ImGuiKey_O;
+ case 'P': return ImGuiKey_P;
+ case 'Q': return ImGuiKey_Q;
+ case 'R': return ImGuiKey_R;
+ case 'S': return ImGuiKey_S;
+ case 'T': return ImGuiKey_T;
+ case 'U': return ImGuiKey_U;
+ case 'V': return ImGuiKey_V;
+ case 'W': return ImGuiKey_W;
+ case 'X': return ImGuiKey_X;
+ case 'Y': return ImGuiKey_Y;
+ case 'Z': return ImGuiKey_Z;
+ case VK_F1: return ImGuiKey_F1;
+ case VK_F2: return ImGuiKey_F2;
+ case VK_F3: return ImGuiKey_F3;
+ case VK_F4: return ImGuiKey_F4;
+ case VK_F5: return ImGuiKey_F5;
+ case VK_F6: return ImGuiKey_F6;
+ case VK_F7: return ImGuiKey_F7;
+ case VK_F8: return ImGuiKey_F8;
+ case VK_F9: return ImGuiKey_F9;
+ case VK_F10: return ImGuiKey_F10;
+ case VK_F11: return ImGuiKey_F11;
+ case VK_F12: return ImGuiKey_F12;
+ case VK_LBUTTON: return ImGuiMouseButton_Left;
+ case VK_RBUTTON: return ImGuiMouseButton_Right;
+ case VK_MBUTTON: return ImGuiMouseButton_Middle;
+ case VK_XBUTTON1: return 3;
+ case VK_XBUTTON2: return 4;
+ default: return ImGuiKey_None;
+ }
+}
+
+static short InputToLegacy(ImGuiKey inputkey)
+{
+ auto& io = ImGui::GetIO();
+ if (inputkey > 4)
+ return io.KeyMap[inputkey];
+
+ switch (inputkey)
+ {
+ case ImGuiMouseButton_Left:
+ return VK_LBUTTON;
+ case ImGuiMouseButton_Right:
+ return VK_RBUTTON;
+ case ImGuiMouseButton_Middle:
+ return VK_MBUTTON;
+ case 3:
+ return VK_XBUTTON1;
+ case 4:
+ return VK_XBUTTON2;
+ }
+
+ LOG_CRIT("Failed to find legacy input");
+ return -1;
+}
+
+static bool IsKeyDown(ImGuiKey key)
+{
+ if (key > 6)
+ return ImGui::IsKeyDown(key);
+
+ switch (key)
+ {
+ case 1:
+ case 2:
+ return ImGui::IsMouseDown(key - 1);
+ case 4:
+ case 5:
+ case 6:
+ return ImGui::IsMouseDown(key - 2);
+ }
+ return false;
+}
+
+static bool IsKeyReleased(ImGuiKey key)
+{
+ if (key > 6)
+ return ImGui::IsKeyReleased(key);
+
+ switch (key)
+ {
+ case 1:
+ case 2:
+ return ImGui::IsMouseReleased(key - 1);
+ case 4:
+ case 5:
+ case 6:
+ return ImGui::IsMouseReleased(key - 2);
+ }
+ return false;
+}
+
+void FixModKey(short& legacyKey)
+{
+ // Can cause incorrect input when both keys pressed, need to fix!
+ switch (legacyKey)
+ {
+
+ case VK_CONTROL:
+ {
+ if (IsKeyDown(ImGuiKey_LeftCtrl))
+ legacyKey = VK_LCONTROL;
+ else if (IsKeyDown(ImGuiKey_RightCtrl))
+ legacyKey = VK_RCONTROL;
+
+ return;
+ }
+ case VK_SHIFT:
+ {
+ if (IsKeyDown(ImGuiKey_LeftShift))
+ legacyKey = VK_LSHIFT;
+ else if (IsKeyDown(ImGuiKey_RightShift))
+ legacyKey = VK_RSHIFT;
+
+ return;
+ }
+
+ }
+}
+
+Hotkey::Hotkey() : PressedEvent(m_PressedEvent), m_PressedEvent()
+{
+ events::KeyUpEvent += MY_METHOD_HANDLER(Hotkey::OnKeyUp);
+}
+
+Hotkey::Hotkey(std::vector legacyKeys) : Hotkey()
+{
+ for (short legacyKey : legacyKeys)
+ {
+ this->m_Keys.insert(legacyKey);
+ }
+}
+
+Hotkey::Hotkey(short key) : Hotkey()
+{
+ this->m_Keys.insert(key);
+}
+
+Hotkey::Hotkey(const Hotkey& other) : Hotkey()
+{
+ m_Keys = {other.m_Keys};
+}
+
+Hotkey::~Hotkey()
+{
+ events::KeyUpEvent -= MY_METHOD_HANDLER(Hotkey::OnKeyUp);
+}
+
+Hotkey& Hotkey::operator=(Hotkey&& hotkey) noexcept
+{
+ m_Keys = std::move(hotkey.m_Keys);
+ return *this;
+}
+
+Hotkey& Hotkey::operator=(Hotkey& hotkey) noexcept
+{
+ m_Keys = hotkey.m_Keys;
+ return *this;
+}
+
+bool Hotkey::operator-(const Hotkey& c2)
+{
+ for (short key : m_Keys)
+ {
+ if (c2.m_Keys.count(key) == 0)
+ return true;
+ }
+ return false;
+}
+
+bool Hotkey::operator!=(const Hotkey& c2) const
+{
+ return !(*this == c2);
+}
+
+bool Hotkey::operator==(const Hotkey& c2) const
+{
+ return m_Keys == c2.m_Keys;
+}
+
+std::string GetKeyName(short key)
+{
+ if (key > 5)
+ return ImGui::GetKeyName(key);
+
+ switch (key)
+ {
+ case ImGuiMouseButton_Left:
+ return "LMB";
+ case ImGuiMouseButton_Right:
+ return "RMB";
+ case ImGuiMouseButton_Middle:
+ return "MMB";
+ case 3:
+ return "Mouse X1";
+ case 4:
+ return "Mouse X2";
+ }
+
+ return "Unknown";
+}
+
+Hotkey::operator std::string() const
+{
+ if (IsEmpty())
+ return "None";
+
+ std::stringstream hotkeyNameStream;
+
+ for (auto it = m_Keys.begin(); it != m_Keys.end(); it++)
+ {
+ if (it != m_Keys.begin())
+ hotkeyNameStream << " + ";
+
+ hotkeyNameStream << GetKeyName(LegacyToInput(*it));
+ }
+ return hotkeyNameStream.str();
+}
+
+bool Hotkey::IsPressed() const
+{
+ for (short key : m_Keys)
+ {
+ if (!IsKeyDown(key))
+ return false;
+ }
+
+ return true;
+}
+
+bool Hotkey::IsPressed(short legacyKey) const
+{
+ FixModKey(legacyKey);
+
+ if (m_Keys.count(legacyKey) == 0)
+ return false;
+
+ std::unordered_set keysClone = m_Keys;
+ keysClone.erase(legacyKey);
+
+ for (short key : keysClone)
+ {
+ bool result = IsKeyDown(key);
+ if (!result)
+ return false;
+ }
+
+ return true;
+}
+
+bool Hotkey::IsReleased() const
+{
+ bool released = false;
+ for (short key : m_Keys)
+ {
+ if (IsKeyReleased(key))
+ {
+ released = true;
+ continue;
+ }
+
+ if (!IsKeyDown(key))
+ return false;
+ }
+
+ return released;
+}
+
+bool Hotkey::IsEmpty() const
+{
+ return m_Keys.size() == 0;
+}
+
+std::vector Hotkey::GetKeys() const
+{
+ return std::vector(m_Keys.begin(), m_Keys.end());
+}
+
+Hotkey Hotkey::GetPressedHotkey()
+{
+ Hotkey hotkey{};
+
+ auto& io = ImGui::GetIO();
+
+ for (ImGuiKey i = ImGuiKey_NamedKey_BEGIN; i < ImGuiKey_NamedKey_END - 4; i++)
+ {
+ bool isKeyDown = io.KeysDown[i];
+ if (isKeyDown)
+ hotkey.m_Keys.insert(InputToLegacy(i));
+ }
+
+ for (ImGuiKey i = 0; i < ImGuiMouseButton_COUNT; i++)
+ {
+ bool isMouseButtonDown = io.MouseDown[i];
+ if (isMouseButtonDown)
+ hotkey.m_Keys.insert(InputToLegacy(i));
+ }
+ return hotkey;
+}
+
+void Hotkey::OnKeyUp(short key, bool& canceled)
+{
+ if (IsPressed(key))
+ m_PressedEvent();
+}
diff --git a/cheat-base/src/cheat-base/Hotkey.h b/cheat-base/src/cheat-base/Hotkey.h
new file mode 100644
index 0000000..d9cefb4
--- /dev/null
+++ b/cheat-base/src/cheat-base/Hotkey.h
@@ -0,0 +1,43 @@
+#pragma once
+
+#include
+#include
+
+#include
+
+class Hotkey
+{
+public:
+
+ Hotkey();
+ Hotkey(const Hotkey& other);
+ Hotkey(short key);
+ Hotkey(std::vector keys);
+ ~Hotkey();
+
+ Hotkey& operator=(Hotkey& hotkey) noexcept;
+ Hotkey& operator=(Hotkey&& hotkey) noexcept;
+
+ bool operator== (const Hotkey& c2) const;
+ bool operator!= (const Hotkey& c2) const;
+ bool operator-(const Hotkey& c2);
+
+ bool IsPressed() const;
+ bool IsPressed(short keyDown) const;
+ bool IsReleased() const;
+
+ bool IsEmpty() const;
+
+ std::vector GetKeys() const;
+
+ operator std::string() const;
+
+ static Hotkey GetPressedHotkey();
+
+ IEvent<>& PressedEvent;
+private:
+ TEvent<> m_PressedEvent;
+ std::unordered_set m_Keys;
+
+ void OnKeyUp(short key, bool& canceled);
+};
\ No newline at end of file
diff --git a/cheat-base/src/cheat-base/Logger.cpp b/cheat-base/src/cheat-base/Logger.cpp
new file mode 100644
index 0000000..a2ca152
--- /dev/null
+++ b/cheat-base/src/cheat-base/Logger.cpp
@@ -0,0 +1,143 @@
+#include
+#include "Logger.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+Logger::Level Logger::s_FileLogLevel = Logger::Level::None;
+Logger::Level Logger::s_ConsoleLogLevel = Logger::Level::None;
+
+std::string Logger::directory = "";
+std::string Logger::logfilepath = "";
+std::mutex Logger::_mutex{};
+
+void Logger::SetLevel(Level level, LoggerType type)
+{
+ switch (type)
+ {
+ case Logger::LoggerType::Any:
+ s_FileLogLevel = level;
+ s_ConsoleLogLevel = level;
+ break;
+ case Logger::LoggerType::ConsoleLogger:
+ s_ConsoleLogLevel = level;
+ break;
+ case Logger::LoggerType::FileLogger:
+ s_FileLogLevel = level;
+ break;
+ default:
+ break;
+ }
+}
+
+Logger::Level Logger::GetLevel(Logger::LoggerType type)
+{
+ switch (type)
+ {
+ case Logger::LoggerType::Any:
+ return s_FileLogLevel < s_ConsoleLogLevel ? s_FileLogLevel : s_ConsoleLogLevel;
+ case Logger::LoggerType::ConsoleLogger:
+ return s_ConsoleLogLevel;
+ case Logger::LoggerType::FileLogger:
+ return s_FileLogLevel;
+ default:
+ return Logger::Level::None;
+ }
+}
+
+void LogToFile(std::string& filepath, std::string& msg)
+{
+ std::ofstream myfile;
+ myfile.open(filepath, std::ios::out | std::ios::app | std::ios::binary);
+ myfile << msg << std::endl;
+ myfile.close();
+}
+
+struct Prefix
+{
+ char color;
+ const char* text;
+};
+
+Prefix GetLevelPrefix(Logger::Level level)
+{
+ switch (level)
+ {
+ case Logger::Level::Critical:
+ return { 0x04, "Critical" };
+ case Logger::Level::Error:
+ return { 0x0C, "Error" };
+ case Logger::Level::Warning:
+ return { 0x06, "Warning" };
+ case Logger::Level::Info:
+ return { 0x02, "Info" };
+ case Logger::Level::Debug:
+ return { 0x0B, "Debug" };
+ case Logger::Level::Trace:
+ return { 0x08, "Trace" };
+ default:
+ return { 0x00, "" };
+ }
+}
+
+void Logger::Log(Logger::Level logLevel, const char* filepath, int line, const char* fmt, ...)
+{
+ if (Logger::s_ConsoleLogLevel == Logger::Level::None && Logger::s_FileLogLevel == Logger::Level::None)
+ return;
+
+ auto filename = std::filesystem::path(filepath).filename().string();
+ auto prefix = GetLevelPrefix(logLevel);
+ char buffer[1024];
+
+ va_list args;
+ va_start(args, fmt);
+ vsprintf_s(buffer, fmt, args);
+ va_end(args);
+
+ if (Logger::s_ConsoleLogLevel != Logger::Level::None && Logger::s_ConsoleLogLevel >= logLevel)
+ {
+ const std::lock_guard lock(_mutex);
+
+ auto logLineConsole = util::string_format("[%s:%d] %s", filename.c_str(), line, buffer);
+ std::cout << "[";
+
+ HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
+ SetConsoleTextAttribute(hConsole, prefix.color);
+ std::cout << prefix.text;
+ SetConsoleTextAttribute(hConsole, 15);
+
+ std::cout << "] " << logLineConsole << std::endl;
+ }
+
+ if (Logger::s_FileLogLevel != Logger::Level::None && Logger::s_FileLogLevel >= logLevel)
+ {
+ const std::lock_guard lock(_mutex);
+
+ auto rawTime = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
+ struct tm gmtm;
+ gmtime_s(&gmtm, &rawTime);
+ auto logLineFile = util::string_format("[%02d:%02d:%02d] [%s] [%s:%d] %s", gmtm.tm_hour, gmtm.tm_min, gmtm.tm_sec,
+ prefix.text, filename.c_str(), line, buffer);
+ LogToFile(Logger::logfilepath, logLineFile);
+ }
+}
+
+void Logger::PrepareFileLogging(std::string directory)
+{
+ auto rawTime = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
+ struct tm gmtm;
+ gmtime_s(&gmtm, &rawTime);
+
+ Logger::directory = directory;
+ if (!std::filesystem::is_directory(directory))
+ std::filesystem::create_directories(directory);
+
+ Logger::logfilepath = util::string_format("%s\\log_%04d-%02d-%02d_%02d-%02d.txt", directory.c_str(),
+ 1900 + gmtm.tm_year, gmtm.tm_mon, gmtm.tm_mday, gmtm.tm_hour, gmtm.tm_min);
+}
diff --git a/cheat-base/src/cheat-base/Logger.h b/cheat-base/src/cheat-base/Logger.h
new file mode 100644
index 0000000..37fc6e9
--- /dev/null
+++ b/cheat-base/src/cheat-base/Logger.h
@@ -0,0 +1,48 @@
+#pragma once
+#include
+#include
+#define EXTLOG(level, fmt, ...) Logger::Log(level, __FILE__, __LINE__, fmt, __VA_ARGS__)
+#define LOG_CRIT(fmt, ...) EXTLOG(Logger::Level::Critical, fmt, __VA_ARGS__)
+#define LOG_ERROR(fmt, ...) EXTLOG(Logger::Level::Error, fmt, __VA_ARGS__)
+#define LOG_WARNING(fmt, ...) EXTLOG(Logger::Level::Warning, fmt, __VA_ARGS__)
+#define LOG_INFO(fmt, ...) EXTLOG(Logger::Level::Info, fmt, __VA_ARGS__)
+#define LOG_DEBUG(fmt, ...) EXTLOG(Logger::Level::Debug, fmt, __VA_ARGS__)
+#define LOG_TRACE(fmt, ...) EXTLOG(Logger::Level::Trace, fmt, __VA_ARGS__)
+
+class Logger
+{
+public:
+ enum class Level
+ {
+ None,
+ Critical,
+ Error,
+ Warning,
+ Info,
+ Debug,
+ Trace
+ };
+
+ enum class LoggerType
+ {
+ Any,
+ ConsoleLogger,
+ FileLogger
+ };
+
+ static void SetLevel(Level level, LoggerType type = LoggerType::Any);
+ static Level GetLevel(LoggerType type);
+
+ static void Log(Level logLevel, const char* filename, int line, const char* fmt, ...);
+
+ static void PrepareFileLogging(std::string directory);
+
+private:
+ static Level s_FileLogLevel;
+ static Level s_ConsoleLogLevel;
+
+ static std::mutex _mutex;
+
+ static std::string directory;
+ static std::string logfilepath;
+};
\ No newline at end of file
diff --git a/cheat-base/src/cheat-base/Patch.cpp b/cheat-base/src/cheat-base/Patch.cpp
new file mode 100644
index 0000000..7542493
--- /dev/null
+++ b/cheat-base/src/cheat-base/Patch.cpp
@@ -0,0 +1,95 @@
+#include
+#include "Patch.h"
+
+#include
+
+bool Patch::Install(uint64_t address, std::vector value)
+{
+ if (patches.count(address) > 0)
+ {
+ LOG_ERROR("Failed to install patch: patch already installed.");
+ return false;
+ }
+
+ auto oldValue = WriteMemory(address, value);
+ if (oldValue == nullptr)
+ return false;
+
+ patches[address] = oldValue;
+ return true;
+}
+
+bool Patch::Restore(uint64_t address)
+{
+ if (patches.count(address) == 0)
+ {
+ LOG_ERROR("Failed to restore patch: not found patch to target address 0x%016X", address);
+ return false;
+ }
+
+ auto restoreValue = patches[address];
+ auto oldValue = WriteMemory(address, *restoreValue);
+ if (oldValue == nullptr)
+ return false;
+
+ patches.erase(address);
+ delete restoreValue;
+ delete oldValue;
+
+ return true;
+}
+
+std::vector* Patch::WriteMemory(uint64_t address, std::vector value)
+{
+ MEMORY_BASIC_INFORMATION information{};
+ auto size = VirtualQuery(reinterpret_cast(address), &information, sizeof(information));
+ if (size < sizeof(information))
+ {
+ LOG_LAST_ERROR("Failed to get page information");
+ return nullptr;
+ }
+
+ if (information.State != MEM_COMMIT)
+ {
+ LOG_ERROR("Page at target address isn't MEM_COMMIT (0x%016X)", address);
+ return nullptr;
+ }
+
+
+ DWORD oldProtection = -1;
+ if ((information.AllocationProtect & PAGE_READWRITE) == 0 && (information.AllocationProtect & PAGE_EXECUTE_READWRITE) == 0)
+ {
+ if (VirtualProtect(reinterpret_cast(address), value.size(), PAGE_EXECUTE_READWRITE, &oldProtection) == FALSE)
+ {
+ LOG_LAST_ERROR("Failed to change page protection");
+ return nullptr;
+ }
+ }
+
+ auto oldValue = new std::vector(value.size());
+ auto errorCode = memcpy_s(oldValue->data(), value.size(), reinterpret_cast(address), value.size());
+ if (errorCode != 0)
+ {
+ LOG_ERROR("Failed to get origin value from memory at 0x%016X. Error code: %d", address, errorCode);
+ delete oldValue;
+ return nullptr;
+ }
+
+ errorCode = memcpy_s(reinterpret_cast(address), value.size(), value.data(), value.size());
+ if (errorCode != 0)
+ {
+ LOG_ERROR("Failed to rewrite target memory at 0x%016X. Error code: %d", address, errorCode);
+ delete oldValue;
+ return nullptr;
+ }
+
+ if (oldProtection != -1)
+ {
+ DWORD temp = 0;
+ if (VirtualProtect(reinterpret_cast(address), value.size(), oldProtection, &temp) == FALSE)
+ {
+ LOG_LAST_ERROR("Failed to restore page protection");
+ }
+ }
+ return oldValue;
+}
diff --git a/cheat-base/src/cheat-base/Patch.h b/cheat-base/src/cheat-base/Patch.h
new file mode 100644
index 0000000..41c1e99
--- /dev/null
+++ b/cheat-base/src/cheat-base/Patch.h
@@ -0,0 +1,31 @@
+#pragma once
+
+#include
+#include
+
+#define OPatch(offset, value) Patch::Install(il2cppi_get_base_address() + offset, value)
+#define OUnpatch(offset) Patch::Restore(il2cppi_get_base_address() + offset)
+#define TogglePatch(field, targetField, offset, patchBytes) if (field == &targetField) { if (targetField.GetValue()) OPatch(offset, patchBytes); else OUnpatch(offset); return; }
+
+class Patch
+{
+public:
+
+ // Installing patch to target address.
+ // In detail: replaces memory in target address with specified in 'value'
+ // saves old memory for future restore
+ // Return true if successfull.
+ static bool Install(uint64_t address, std::vector value);
+
+ // Restoring old memory in this address, if it was patched.
+ // Return true if successfull.
+ static bool Restore(uint64_t address);
+
+private:
+
+ inline static std::map*> patches{};
+
+ static std::vector* WriteMemory(uint64_t address, std::vector value);
+
+};
+
diff --git a/cheat-base/src/cheat-base/PatternScanner.cpp b/cheat-base/src/cheat-base/PatternScanner.cpp
new file mode 100644
index 0000000..c435b41
--- /dev/null
+++ b/cheat-base/src/cheat-base/PatternScanner.cpp
@@ -0,0 +1,908 @@
+#include "pch.h"
+#include "PatternScanner.h"
+
+#include
+
+#include
+#include
+#include
+
+PatternScanner::PatternScanner() :
+ m_CacheChanged(false)
+{
+}
+
+void PatternScanner::ParseSignatureFile(const std::string& signaturesContent)
+{
+ nlohmann::json siganturesJson;
+ try
+ {
+ siganturesJson = nlohmann::json::parse(signaturesContent);
+ }
+ catch (nlohmann::json::parse_error* e)
+ {
+ LOG_ERROR("Failed to parse siganature json content. Error byte %d", e->byte);
+ return;
+ }
+
+ ParseSignatureJson(&siganturesJson);
+}
+
+bool PatternScanner::IsUpdated()
+{
+ return m_CacheChanged;
+}
+
+void PatternScanner::ParseSignatureJson(void* signatureJson)
+{
+ nlohmann::json& jsonContent = *reinterpret_cast(signatureJson);
+ for (auto& moduleEntry : jsonContent.items())
+ {
+ std::string moduleName = moduleEntry.key();
+
+ if (m_ModulePatterns.count(moduleName) == 0)
+ m_ModulePatterns[moduleName] = {};
+
+ auto& functionPatterns = m_ModulePatterns[moduleName];
+ for (auto& functionEntry : moduleEntry.value().items())
+ {
+ std::string functionName = functionEntry.key();
+ functionPatterns[functionName] = {};
+
+ auto& container = functionPatterns[functionName];
+ auto& patternInfo = functionEntry.value();
+
+ for (auto& xref : patternInfo["xref"])
+ container.xrefs.push_back({ xref["sig"], xref["offset"] });
+
+ for (auto& pattern : patternInfo["signatures"])
+ container.signatures.push_back(pattern);
+ }
+ }
+}
+
+uintptr_t PatternScanner::GetOffsetInt(const nlohmann::json& value)
+{
+ std::uintptr_t offset = 0;
+ if (value.is_string())
+ {
+ std::string strValue = value;
+ offset = strtoul(strValue.c_str(), nullptr, 16);
+ }
+ else if (value.is_number_unsigned())
+ {
+ offset = value;
+ }
+ return offset;
+}
+
+
+std::string PatternScanner::GetOffsetStr(uintptr_t offset)
+{
+ std::stringstream ss;
+ ss << std::hex << offset;
+ return ss.str();
+}
+
+void PatternScanner::Save(const std::filesystem::path& filename)
+{
+ std::ofstream outputStream(filename, std::ios::out);
+ if (!outputStream.is_open())
+ {
+ LOG_ERROR("Failed to open file '%s' to save offsets.", filename.c_str());
+ return;
+ }
+
+ std::string output;
+ Save(output);
+ outputStream << output;
+ outputStream.close();
+}
+
+void PatternScanner::Save(std::string& outContent)
+{
+ if (!m_CacheChanged)
+ outContent = m_LoadCache;
+
+ nlohmann::json modulesInfo{};
+
+ SaveJson(modulesInfo);
+
+ outContent = modulesInfo.dump();
+}
+
+void PatternScanner::SaveJson(nlohmann::json& outObject)
+{
+ for (auto& [moduleName, functionsOffsets] : m_CacheOffsets)
+ {
+ SaveModuleHash(moduleName, outObject[moduleName]["hash"]);
+
+ auto& functionsObject = outObject[moduleName]["functions"];
+ for (auto& [functionName, offset] : functionsOffsets)
+ {
+ functionsObject[functionName] = GetOffsetStr(offset);
+ }
+ }
+}
+
+bool PatternScanner::Load(const std::filesystem::path& filename)
+{
+ std::ifstream inputStream(filename, std::ios::in);
+ if (!inputStream.is_open())
+ {
+ LOG_ERROR("Failed to open file '%s' for load offsets.", filename.c_str());
+ return false;
+ }
+
+ std::stringstream buffer;
+ buffer << inputStream.rdbuf();
+ return Load(buffer.str());
+}
+
+bool PatternScanner::Load(const std::string& content)
+{
+ nlohmann::json contentJson;
+ try
+ {
+ contentJson = nlohmann::json::parse(content);
+ }
+ catch (nlohmann::json::parse_error* e)
+ {
+ LOG_ERROR("Failed to parse siganature json content. Error byte %d", e->byte);
+ return false;
+ }
+
+ return LoadJson(contentJson);
+
+ m_LoadCache = content;
+}
+
+bool PatternScanner::LoadJson(const nlohmann::json& object)
+{
+ bool result = true;
+ for (auto& moduleEntry : object.items())
+ {
+ std::string moduleName = moduleEntry.key();
+ auto& moduleJson = moduleEntry.value();
+
+ if (!IsValidModuleHash(moduleName, moduleJson["hash"]))
+ {
+ LOG_WARNING("Module '%s' hash don't match with saved one. Seems module was updated.", moduleName.c_str());
+ result = false;
+ continue;
+ }
+
+ if (m_CacheOffsets.count(moduleName) == 0)
+ m_CacheOffsets[moduleName] = {};
+
+ auto& functionsOffsets = m_CacheOffsets[moduleName];
+ for (auto& funcOffsetEntry : moduleJson["functions"].items())
+ {
+ functionsOffsets[funcOffsetEntry.key()] = GetOffsetInt(funcOffsetEntry.value());
+ }
+ }
+ return result;
+}
+
+PatternScanner::ModuleInfo& PatternScanner::GetModuleInfo(const std::string& modulePath)
+{
+ static std::map s_ModuleInfoCache;
+ std::string moduleName = std::filesystem::path(modulePath).filename().string();
+
+ if (s_ModuleInfoCache.count(moduleName) > 0)
+ return s_ModuleInfoCache[moduleName];
+
+ HMODULE hModule = GetModuleHandle(moduleName.c_str());
+ if (hModule == NULL)
+ {
+ LOG_LAST_ERROR("Failed to find module '%s'.", moduleName.c_str());
+ std::system("pause");
+ exit(0);
+ }
+
+ s_ModuleInfoCache[moduleName] = GetModuleInfo(hModule);
+ return s_ModuleInfoCache[moduleName];
+}
+
+PatternScanner::ModuleInfo& PatternScanner::GetModuleInfo(HMODULE hModule)
+{
+ static std::map s_ModuleInfoCache;
+
+ if (hModule == NULL)
+ {
+ LOG_CRIT("hModule is NULL.");
+ std::system("pause");
+ exit(0);
+ }
+
+ if (s_ModuleInfoCache.count(hModule) > 0)
+ return s_ModuleInfoCache[hModule];
+
+ MODULEINFO nativeInfo{};
+ BOOL result = GetModuleInformation(GetCurrentProcess(), hModule, &nativeInfo, sizeof(nativeInfo));
+ if (result == FALSE)
+ {
+ LOG_LAST_ERROR("Failed get info about module at 0x%p.", hModule);
+ std::system("pause");
+ exit(0);
+ }
+
+ s_ModuleInfoCache[hModule] = {};
+ auto& moduleInfo = s_ModuleInfoCache[hModule];
+ moduleInfo.handle = hModule;
+ moduleInfo.base = (uintptr_t)hModule;
+ moduleInfo.size = (uintptr_t)nativeInfo.SizeOfImage;
+
+ char buffer[MAX_PATH] = {};
+ result = GetModuleFileNameA(hModule, buffer, sizeof(buffer));
+ if (result == FALSE)
+ {
+ LOG_LAST_ERROR("Failed get filename module at 0x%p.", hModule);
+ std::system("pause");
+ exit(0);
+ }
+
+ moduleInfo.filePath = buffer;
+
+ LOG_DEBUG("Module %s bound 0x%p-0x%p.", moduleInfo.filePath.c_str(),
+ moduleInfo.base, moduleInfo.base + moduleInfo.size);
+
+ uintptr_t currentAddress = moduleInfo.base;
+ uintptr_t endAddress = moduleInfo.base + moduleInfo.size;
+ while (currentAddress <= endAddress)
+ {
+ MEMORY_BASIC_INFORMATION memoryInfo{};
+ auto byteCount = VirtualQuery((LPCVOID)currentAddress, &memoryInfo, sizeof(memoryInfo));
+ if (byteCount == 0)
+ {
+ LOG_LAST_ERROR("Failed get memory info for address 0x%p.", currentAddress);
+ break;
+ }
+
+ currentAddress = (uintptr_t)memoryInfo.BaseAddress + memoryInfo.RegionSize;
+
+ auto protection = memoryInfo.AllocationProtect;
+
+ if (protection != PAGE_EXECUTE && protection != PAGE_EXECUTE_READ &&
+ protection != PAGE_EXECUTE_READWRITE && protection != PAGE_EXECUTE_WRITECOPY)
+ continue;
+
+ moduleInfo.execRegions.push_back({ (uintptr_t)memoryInfo.BaseAddress, memoryInfo.RegionSize });
+ }
+
+ return moduleInfo;
+}
+
+void PatternScanner::AddOffset(const std::string& moduleName, const std::string& name, uintptr_t offset)
+{
+ if (m_CacheOffsets.count(moduleName) == 0)
+ m_CacheOffsets[moduleName] = {};
+
+ m_CacheOffsets[moduleName][name] = offset;
+ m_CacheChanged = true;
+}
+
+size_t ComputeChecksum(const std::string& filename)
+{
+ std::ifstream file(filename, std::ios::in | std::ios::binary);
+ if (!file.is_open())
+ {
+ LOG_ERROR("Failed to compute file json: %s", filename.c_str());
+ return 0;
+ }
+
+ size_t sum = 0;
+ size_t word = 0;
+ while (file.read(reinterpret_cast(&word), sizeof(word))) {
+ sum += word;
+ }
+
+ if (file.gcount()) {
+ word &= (~0U >> ((sizeof(size_t) - file.gcount()) * 8));
+ sum += word;
+ }
+
+ return sum;
+}
+
+int64_t PatternScanner::GetModuleTimestamp(const std::string& moduleName)
+{
+ auto& moduleInfo = GetModuleInfo(moduleName);
+ auto write_time = std::filesystem::last_write_time(moduleInfo.filePath);
+ return write_time.time_since_epoch().count();
+}
+
+int64_t PatternScanner::GetModuleTimestamp(HMODULE hModule)
+{
+ auto& moduleInfo = GetModuleInfo(hModule);
+ auto write_time = std::filesystem::last_write_time(moduleInfo.filePath);
+ return write_time.time_since_epoch().count();
+}
+
+bool PatternScanner::IsValidModuleHash(const std::string& moduleName, const nlohmann::json& hashObject)
+{
+ auto& info = GetModuleInfo(moduleName);
+ return IsValidModuleHash(info.handle, hashObject);
+}
+
+bool PatternScanner::IsValidModuleHash(HMODULE hModule, const nlohmann::json& hashObject)
+{
+ if (!hashObject.contains("timestamp") || !hashObject.contains("checksum"))
+ return false;
+
+ int64_t currTimestamp = GetModuleTimestamp(hModule);
+
+ int64_t timestamp = hashObject["timestamp"];
+ size_t checksum = hashObject["checksum"];
+
+ // To increase speed, we don't check checksum if timestamp matches
+ if (timestamp == currTimestamp)
+ {
+ m_ComputedHashes[hModule] = checksum;
+ return true;
+ }
+
+ size_t currChecksum = m_ComputedHashes.count(hModule) > 0 ? m_ComputedHashes[hModule] : ComputeChecksum(GetModuleInfo(hModule).filePath);
+ m_ComputedHashes[hModule] = currChecksum;
+
+ return checksum == currChecksum;
+}
+
+void PatternScanner::SaveModuleHash(const std::string& moduleName, nlohmann::json& outObject)
+{
+ auto& info = GetModuleInfo(moduleName);
+ SaveModuleHash(info.handle, outObject);
+}
+
+void PatternScanner::SaveModuleHash(HMODULE hModule, nlohmann::json& outObject)
+{
+ auto& moduleInfo = GetModuleInfo(hModule);
+ auto write_time = std::filesystem::last_write_time(moduleInfo.filePath);
+ int64_t timestamp = write_time.time_since_epoch().count();
+
+ size_t checksum = m_ComputedHashes.count(hModule) > 0 ? m_ComputedHashes[hModule] : ComputeChecksum(moduleInfo.filePath);
+
+ outObject["timestamp"] = timestamp;
+ outObject["checksum"] = checksum;
+
+ m_ComputedHashes[hModule] = checksum;
+}
+
+void PatternScanner::SearchAll()
+{
+ for (auto& [moduleName, methodsSignatures] : m_ModulePatterns)
+ {
+ for (auto& [methodName, sigInfo] : methodsSignatures)
+ {
+ LOG_DEBUG("Searching %s::%s", moduleName.c_str(), methodName.c_str());
+ auto searchResult = Search(moduleName, methodName);
+ if (searchResult == 0)
+ LOG_WARNING("Not found");
+ else
+ LOG_DEBUG("Found function at %s + %p", moduleName.c_str(), searchResult);
+ }
+ }
+}
+
+uintptr_t PatternScanner::Search(const std::string& name)
+{
+ for (auto& [moduleName, modulePatternsData] : m_ModulePatterns)
+ {
+ if (modulePatternsData.count(name) > 0)
+ return Search(moduleName, name, modulePatternsData[name]);
+ }
+
+ return 0;
+}
+
+uintptr_t PatternScanner::Search(const std::string& moduleName, const std::string& name)
+{
+ uintptr_t moduleBase = GetModuleInfo(moduleName).base;
+
+ if (m_CacheOffsets.count(moduleName) > 0 && m_CacheOffsets[moduleName].count(name) > 0)
+ {
+ uintptr_t offset = m_CacheOffsets[moduleName][name];
+ return offset != 0 ? moduleBase + offset : 0;
+ }
+
+ if (m_ModulePatterns.count(moduleName) > 0 && m_ModulePatterns[moduleName].count(name) > 0)
+ return Search(moduleName, name, m_ModulePatterns[moduleName][name]);
+
+ AddOffset(moduleName, name, 0);
+ return 0;
+}
+
+uintptr_t PatternScanner::Search(const std::string& moduleName, const std::string& name, const PatternInfo& info)
+{
+ AddressCounter counter;
+ uintptr_t moduleBase = GetModuleInfo(moduleName).base;
+
+ for (auto& pattern : info.signatures)
+ {
+ auto address = SearchFunction(moduleName, pattern);
+ if (address == 0)
+ continue;
+
+ counter.Add(address);
+ }
+
+ for (auto& xrefPattern : info.xrefs)
+ {
+ auto address = SearchXref(moduleName, xrefPattern);
+ if (address == 0)
+ continue;
+
+ counter.Add(address);
+ }
+
+ uintptr_t address = counter.GetMax();
+ AddOffset(moduleName, name, address == 0 ? 0 : address - moduleBase);
+ return address;
+}
+
+uintptr_t PatternScanner::SearchFunction(const std::string& moduleName, const std::string& pattern)
+{
+ auto address = SearchInModule(moduleName, pattern);
+ if (address == 0)
+ return 0;
+
+ return FindFunctionEntry(address);
+}
+
+uintptr_t PatternScanner::SearchXref(const std::string& moduleName, const OffsetSignature& xrefPattern)
+{
+ HMODULE hModule = GetModuleInfo(moduleName).handle;
+ return SearchXref(hModule, xrefPattern);
+}
+
+uintptr_t PatternScanner::SearchXref(HMODULE hModule, const OffsetSignature& xrefPattern)
+{
+ auto address = SearchInModule(hModule, xrefPattern.pattern);
+ if (!address)
+ return 0;
+
+ uint8_t callOpcode = util::ReadMapped((void*)address, xrefPattern.offset, true);
+ int opcodeOffset = 0;
+ switch (callOpcode)
+ {
+ case 0xE8:
+ case 0xE9:
+ opcodeOffset = 1;
+ break;
+ case 0x48:
+ case 0x4C:
+ opcodeOffset = 3;
+ break;
+ default:
+ LOG_WARNING("Trying find xref to not supported long call (opcode 0x%x)", callOpcode);
+ return 0;
+ }
+
+ int callOffset = util::ReadMapped((void*)address, xrefPattern.offset + opcodeOffset, true);
+ uintptr_t dataAddress = address + xrefPattern.offset + 4 + opcodeOffset + callOffset;
+
+ //if (!IsFunctionEntry(functionAddress))
+ //{
+ // LOG_WARNING("Xref calc function address failed. There is no function at 0x%p.", functionAddress);
+ // return {};
+ //}
+
+ return dataAddress;
+}
+
+uintptr_t PatternScanner::SearchInModule(const std::string& moduleName, const std::string& pattern)
+{
+ auto& moduleInfo = GetModuleInfo(moduleName);
+
+ auto tokens = util::StringSplit(" ", pattern);
+
+ std::vector> bytePattern;
+ bytePattern.reserve(tokens.size());
+ for (auto& token : tokens)
+ {
+ std::optional value = token == "??" ? std::optional() : std::stoi(token, 0, 16);
+ bytePattern.push_back(value);
+ }
+
+ uint8_t countFound = 0;
+ uint64_t address = 0;
+ for (auto& region : moduleInfo.execRegions)
+ {
+ auto regionSearchResult = SearchInRange(region.base, region.base + region.size, bytePattern);
+ if (regionSearchResult.status == SRStatus::NotFound)
+ continue;
+
+ if (regionSearchResult.status == SRStatus::NotUnique)
+ {
+ LOG_WARNING("Pattern ununique '%s'.", pattern.c_str());
+ return {};
+ }
+
+ countFound++;
+ if (countFound > 1)
+ {
+ LOG_WARNING("Pattern ununique '%s'.", pattern.c_str());
+ return {};
+ }
+
+ address = regionSearchResult.value;
+ }
+
+ if (countFound == 0)
+ LOG_WARNING("Pattern not found '%s'.", pattern.c_str());
+
+ return address;
+}
+
+uintptr_t PatternScanner::SearchInModule(HMODULE hModule, const std::string& pattern)
+{
+ char buffer[MAX_PATH] = {};
+ auto count = GetModuleFileNameA(hModule, buffer, sizeof(buffer));
+ if (count == 0)
+ {
+ LOG_ERROR("Failed to get module name for handle 0x%p.", hModule);
+ return {};
+ }
+ return SearchInModule(buffer, pattern);
+}
+
+UINT32 get_first_bit_set(UINT32 x)
+{
+ // Generates a single BSF instruction
+ unsigned long ret;
+ _BitScanForward(&ret, x);
+ return (UINT32)ret;
+}
+
+UINT32 clear_leftmost_set(UINT32 value)
+{
+ // Generates a single BLSR instruction
+ return value & (value - 1);
+}
+
+int memcmp_mask(const BYTE* buffer1, const BYTE* buffer2, const BYTE* mask2, size_t count)
+{
+ while (count--)
+ {
+ if (*mask2)
+ {
+ if (*buffer1 != *buffer2)
+ return -1;
+ }
+
+ buffer1++, buffer2++, mask2++;
+ };
+ return 0;
+}
+
+struct Signature
+{
+ std::vector bytes;
+ std::vector mask;
+ bool hasWildcards = false;
+
+ Signature(const std::vector>& sig)
+ {
+ for (auto& item : sig)
+ {
+ if (!item)
+ {
+ bytes.push_back(0xCC);
+ mask.push_back(0x00);
+ hasWildcards = true;
+ continue;
+ }
+
+ bytes.push_back(*item);
+ mask.push_back(0xFF);
+ }
+ }
+};
+
+// Find signiture pattern in memory
+PBYTE FindSignatureAVX2(PBYTE data, size_t size, const Signature& sig)
+{
+ const auto* pat = sig.bytes.data();
+ size_t patLen = sig.bytes.size();
+ size_t patLen1 = (patLen - 1);
+ size_t patLen2 = (patLen - 2);
+
+ // Fill 'first' and 'last' with the first and last pattern byte respectively
+ const __m256i first = _mm256_set1_epi8(pat[0]);
+ const __m256i last = _mm256_set1_epi8(pat[patLen1]);
+
+ if (!sig.hasWildcards)
+ {
+ // A little faster without wildcards
+
+ // Scan 32 bytes at the time..
+ for (size_t i = 0; i + 32 + patLen1 < size; i += 32)
+ {
+ // Load in the next 32 bytes of input first and last
+ // Can use align 32 bit read for first since the input is page aligned
+ const __m256i block_first = _mm256_load_si256((const __m256i*) (data + i));
+ const __m256i block_last = _mm256_loadu_si256((const __m256i*) (data + i + patLen1));
+
+ // Compare first and last data to get 32byte masks
+ const __m256i eq_first = _mm256_cmpeq_epi8(first, block_first);
+ const __m256i eq_last = _mm256_cmpeq_epi8(last, block_last);
+
+ // AND the equality masks and into a 32 bit mask
+ UINT32 mask = _mm256_movemask_epi8(_mm256_and_si256(eq_first, eq_last));
+
+ // Do pattern compare between first and last position if we got our first and last at this data position
+ while (mask != 0)
+ {
+ UINT32 bitpos = get_first_bit_set(mask);
+ if (memcmp(data + i + bitpos + 1, pat + 1, patLen2) == 0)
+ return data + i + bitpos;
+
+ mask = clear_leftmost_set(mask);
+ };
+ }
+
+ }
+ else
+ {
+ // Pattern scan with wildcards mask
+ const BYTE* msk = sig.mask.data();
+
+ for (size_t i = 0; i + patLen1 + 32 < size; i += 32)
+ {
+ const __m256i block_first = _mm256_load_si256((const __m256i*) (data + i));
+ const __m256i block_last = _mm256_loadu_si256((const __m256i*) (data + i + patLen1));
+
+ const __m256i eq_first = _mm256_cmpeq_epi8(first, block_first);
+ const __m256i eq_last = _mm256_cmpeq_epi8(last, block_last);
+
+ UINT32 mask = _mm256_movemask_epi8(_mm256_and_si256(eq_first, eq_last));
+
+ // Do a byte pattern w/mask compare between first and last position if we got our first and last
+ while (mask != 0)
+ {
+ UINT32 bitpos = get_first_bit_set(mask);
+ if (memcmp_mask(data + i + bitpos + 1, pat + 1, msk + 1, patLen2) == 0)
+ return data + i + bitpos;
+
+ mask = clear_leftmost_set(mask);
+ };
+ }
+ }
+ return NULL;
+}
+
+PBYTE FindSignature(PBYTE input, size_t inputLen, const Signature& sig)
+{
+ if (!sig.hasWildcards)
+ {
+ // If no wildcards, faster to use a memcmp() type
+ const BYTE* pat = sig.bytes.data();
+ const BYTE* end = (input + inputLen);
+ const BYTE first = *pat;
+ size_t sigLen = sig.bytes.size();
+
+ // Setup last in the pattern length byte quick for rejection test
+ size_t lastIdx = (sigLen - 1);
+ BYTE last = pat[lastIdx];
+
+ for (PBYTE ptr = input; ptr < end; ++ptr)
+ {
+ if ((ptr[0] == first) && (ptr[lastIdx] == last))
+ {
+ if (memcmp(ptr + 1, pat + 1, sigLen - 2) == 0)
+ return ptr;
+ }
+ }
+ }
+ else
+ {
+ const BYTE* pat = sig.bytes.data();
+ const BYTE* msk = sig.mask.data();
+ const BYTE* end = (input + inputLen);
+ const BYTE first = *pat;
+ size_t sigLen = sig.bytes.size();
+ size_t lastIdx = (sigLen - 1);
+ BYTE last = pat[lastIdx];
+
+ for (PBYTE ptr = input; ptr < end; ++ptr)
+ {
+ if ((ptr[0] == first) && (ptr[lastIdx] == last))
+ {
+ const BYTE* patPtr = pat + 1;
+ const BYTE* mskPtr = msk + 1;
+ const BYTE* memPtr = ptr + 1;
+ BOOL found = TRUE;
+
+ for (int i = 0; (i < sigLen - 2) && (memPtr < end); ++mskPtr, ++patPtr, ++memPtr, i++)
+ {
+ if (!*mskPtr)
+ continue;
+
+ if (*memPtr != *patPtr)
+ {
+ found = FALSE;
+ break;
+ }
+ }
+
+ if (found)
+ return ptr;
+ }
+ }
+ }
+
+ return 0;
+}
+
+PatternScanner::SearchResult PatternScanner::SearchSignatureAVX2(PBYTE input, size_t inputLen, const std::vector>& pattern)
+{
+ Signature sig = Signature(pattern);
+
+ size_t sigSize = sig.bytes.size();
+ size_t len = inputLen;
+ size_t count = 0;
+
+ inputLen -= sigSize;
+
+ PBYTE match = FindSignatureAVX2(input, len, sig);
+ uintptr_t firstMatch = (uintptr_t)match;
+ while (match)
+ {
+ if (++count >= 2)
+ break;
+
+ ++match;
+ len = (inputLen - (int)(match - input));
+ if (len < sigSize)
+ break;
+
+ match = FindSignatureAVX2(match, len, sig);
+ };
+
+ SearchResult result {};
+ result.value = (uintptr_t)firstMatch;
+ switch (count)
+ {
+ case 0: result.status = SRStatus::NotFound; break;
+ case 1: result.status = SRStatus::Unique; break;
+ default: result.status = SRStatus::NotUnique; break;
+ };
+ return result;
+}
+
+PatternScanner::SearchResult PatternScanner::SearchSignature(PBYTE input, size_t inputLen, const std::vector>& pattern)
+{
+ Signature sig = Signature(pattern);
+
+ size_t sigSize = sig.bytes.size();
+ size_t len = inputLen;
+ size_t count = 0;
+
+ inputLen -= sigSize;
+
+ // Search for signature match..
+ PBYTE match = FindSignature(input, len, sig);
+ uintptr_t firstMatch = (uintptr_t)match;
+ while (match)
+ {
+ // Stop now if we've hit two matches
+ if (++count >= 2)
+ break;
+
+ ++match;
+ len = (inputLen - (int)(match - input));
+ if (len < sigSize)
+ break;
+
+ // Next search
+ match = FindSignature(match, len, sig);
+ };
+
+ SearchResult result{};
+ result.value = (uintptr_t)firstMatch;
+ switch (count)
+ {
+ case 0: result.status = SRStatus::NotFound; break;
+ case 1: result.status = SRStatus::Unique; break;
+ default: result.status = SRStatus::NotUnique; break;
+ };
+ return result;
+}
+
+bool TestAVX2Support()
+{
+ enum { EAX, EBX, ECX, EDX };
+ int regs[4];
+
+ // Highest Function Parameter
+ __cpuid(regs, 0);
+ if (regs[EAX] >= 7)
+ {
+ // Extended Features
+ __cpuid(regs, 7);
+ return (regs[EBX] & /*AVX2*/ (1 << 5)) != 0;
+ }
+ return false;
+}
+
+PatternScanner::SearchResult PatternScanner::SearchInRange(uintptr_t start, uintptr_t end, const std::vector>& pattern)
+{
+ bool hasAVX2 = TestAVX2Support();
+
+ if (hasAVX2)
+ {
+ auto result = SearchSignatureAVX2((PBYTE)start, end - start, pattern);
+ return result;
+ }
+
+ static bool warnOnce = true;
+ if (warnOnce)
+ {
+ warnOnce = false;
+ LOG_WARNING("Using non-AVX2 reference search *\n");
+ }
+
+ return SearchSignature((PBYTE)start, end - start, pattern);
+
+
+ /*const uint8_t* rStart = (const uint8_t*)start;
+ const uint8_t* rEnd = (const uint8_t*)end;
+
+ auto comparer = [](uint8_t val1, std::optional val2)
+ {
+ return (!val2 || val1 == *val2);
+ };
+
+ SearchResult sResult = { SRStatus::NotFound, 0 };
+ while (true)
+ {
+ const uint8_t* res = std::search(rStart, rEnd, pattern.begin(), pattern.end(), comparer);
+ if (res >= rEnd)
+ break;
+
+ if (sResult.status != SRStatus::NotFound)
+ return { SRStatus::NotUnique, 0 };
+
+ sResult = { SRStatus::Unique, (uint64_t)res };
+ rStart = res + pattern.size();
+ }
+ return sResult;*/
+}
+
+bool PatternScanner::IsFunctionEntry(uintptr_t functionAddress)
+{
+ auto address = FindFunctionEntry(functionAddress);
+ if (functionAddress == 0)
+ return false;
+ return functionAddress == functionAddress;
+}
+
+uintptr_t PatternScanner::FindFunctionEntry(uintptr_t address)
+{
+ // TODO: Implement function asm head find. Maybe with use Zydis.
+ return 0;
+}
+
+PatternScanner::AddressCounter::AddressCounter() {}
+
+void PatternScanner::AddressCounter::Add(uintptr_t address)
+{
+ if (m_Counts.count(address) == 0)
+ m_Counts[address] = 0;
+ m_Counts[address]++;
+}
+
+uintptr_t PatternScanner::AddressCounter::GetMax()
+{
+ if (m_Counts.size() == 0)
+ return 0;
+
+ using pair_type = decltype(m_Counts)::value_type;
+ auto maxEntry = std::max_element(m_Counts.begin(), m_Counts.end(),
+ [](const pair_type& a, const pair_type& b)
+ {
+ return a.second < b.second;
+ });
+
+ return maxEntry->first;
+}
diff --git a/cheat-base/src/cheat-base/PatternScanner.h b/cheat-base/src/cheat-base/PatternScanner.h
new file mode 100644
index 0000000..e6801bc
--- /dev/null
+++ b/cheat-base/src/cheat-base/PatternScanner.h
@@ -0,0 +1,138 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+class PatternScanner
+{
+public:
+ PatternScanner();
+
+ uintptr_t Search(const std::string& name);
+ virtual uintptr_t Search(const std::string& moduleName, const std::string& name);
+
+ virtual void SearchAll();
+
+ void Save(const std::filesystem::path& filename);
+ void Save(std::string& outContent);
+
+ bool Load(const std::filesystem::path& filename);
+ bool Load(const std::string& content);
+
+ virtual void SaveJson(nlohmann::json& outObject);
+ virtual bool LoadJson(const nlohmann::json& object);
+
+ void ParseSignatureFile(const std::string& signaturesContent);
+
+ bool IsUpdated();
+
+ bool IsValidModuleHash(HMODULE HMODULE, const nlohmann::json& hashObject);
+ bool IsValidModuleHash(const std::string& moduleName, const nlohmann::json& hashObject);
+
+ int64_t GetModuleTimestamp(const std::string& moduleName);
+ int64_t GetModuleTimestamp(HMODULE hModule);
+
+protected:
+
+ class AddressCounter
+ {
+ public:
+ AddressCounter();
+ void Add(uintptr_t address);
+ uintptr_t GetMax();
+
+ private:
+ std::map m_Counts;
+ };
+
+ struct OffsetSignature
+ {
+ std::string pattern;
+ uint32_t offset;
+ };
+
+ struct PatternInfo
+ {
+ std::vector signatures;
+ std::vector xrefs;
+ };
+
+ std::map> m_ModulePatterns;
+ std::map> m_CacheOffsets;
+ bool m_CacheChanged;
+ std::string m_LoadCache;
+
+ std::map m_ComputedHashes;
+
+
+ struct RegionInfo
+ {
+ uintptr_t base;
+ size_t size;
+ };
+
+ struct ModuleInfo
+ {
+ HMODULE handle = 0;
+ uintptr_t base = 0;
+ size_t size = 0;
+ std::string filePath;
+ std::vector execRegions;
+ };
+
+ void AddOffset(const std::string& moduleName, const std::string& name, uintptr_t offset);
+
+ void SaveModuleHash(HMODULE hModule, nlohmann::json& outObject);
+ void SaveModuleHash(const std::string& moduleName, nlohmann::json& outObject);
+
+ ModuleInfo& GetModuleInfo(HMODULE hModule);
+ ModuleInfo& GetModuleInfo(const std::string& moduleName);
+
+ virtual void ParseSignatureJson(void* signatureJson);
+
+ virtual uintptr_t Search(const std::string& moduleName, const std::string& name, const PatternInfo& info);
+ uintptr_t SearchFunction(const std::string& moduleName, const std::string& pattern);
+
+ uintptr_t SearchXref(const std::string& moduleName, const OffsetSignature& xrefPattern);
+ uintptr_t SearchXref(HMODULE hModule, const OffsetSignature& xrefPattern);
+
+ uintptr_t SearchInModule(const std::string& moduleName, const std::string& pattern);
+ uintptr_t SearchInModule(HMODULE hModule, const std::string& pattern);
+
+ uintptr_t GetOffsetInt(const nlohmann::json& value);
+ std::string GetOffsetStr(uintptr_t offset);
+
+ template
+ std::optional SearchValue(HMODULE hModule, const std::string& pattern, uint32_t codeOffset)
+ {
+ auto address = SearchInModule(hModule, pattern);
+ if (address == 0)
+ return {};
+
+ int offset = util::ReadMapped((void*)address, codeOffset, true);
+ return reinterpret_cast(address + offset + codeOffset + sizeof(int));
+ }
+
+ enum class SRStatus
+ {
+ Unique,
+ NotUnique,
+ NotFound
+ };
+
+ struct SearchResult
+ {
+ SRStatus status;
+ uintptr_t value;
+ };
+
+ SearchResult SearchSignature(PBYTE input, size_t inputLen, const std::vector>& sig);
+ SearchResult SearchSignatureAVX2(PBYTE input, size_t inputLen, const std::vector>& sig);
+ SearchResult SearchInRange(uintptr_t start, uintptr_t end, const std::vector>& pattern);
+
+ virtual bool IsFunctionEntry(uintptr_t address);
+ virtual uintptr_t FindFunctionEntry(uintptr_t address);
+};
+
diff --git a/cheat-base/src/cheat-base/PipeTransfer.cpp b/cheat-base/src/cheat-base/PipeTransfer.cpp
new file mode 100644
index 0000000..e4ef042
--- /dev/null
+++ b/cheat-base/src/cheat-base/PipeTransfer.cpp
@@ -0,0 +1,87 @@
+#include
+#include "PipeTransfer.h"
+
+#include
+#include
+
+#include
+
+PipeTransfer::PipeTransfer(const std::string& name)
+{
+ std::stringstream ss;
+ ss << "\\\\.\\pipe\\" << name;
+ this->m_Name = ss.str();
+ this->m_Pipe = 0;
+}
+
+PipeTransfer::~PipeTransfer()
+{
+ if (m_Pipe)
+ CloseHandle(m_Pipe);
+}
+
+bool PipeTransfer::Create()
+{
+ if (m_Pipe)
+ CloseHandle(m_Pipe);
+ m_Pipe = CreateNamedPipe(m_Name.c_str(), PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, 1, 256 * 1024, 16, INFINITE, NULL);
+ return IsPipeOpened();
+}
+
+bool PipeTransfer::IsPipeOpened()
+{
+ return m_Pipe && m_Pipe != INVALID_HANDLE_VALUE;
+}
+
+bool PipeTransfer::Connect()
+{
+ if (IsPipeOpened())
+ CloseHandle(m_Pipe);
+
+ m_Pipe = CreateFile(m_Name.c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
+
+ return IsPipeOpened();
+}
+
+bool PipeTransfer::WaitForConnection()
+{
+ return ConnectNamedPipe(m_Pipe, nullptr);
+}
+
+void PipeTransfer::ReadBytes(void* buffer, size_t size)
+{
+ if (size == 0 || !IsPipeOpened()) return;
+
+ DWORD readCount = 0;
+ auto result = ReadFile(m_Pipe, buffer, size, &readCount, nullptr);
+ if (!result || readCount < size)
+ {
+ LOG_LAST_ERROR("Failed read from pipe.");
+ CloseHandle(m_Pipe);
+ m_Pipe = 0;
+ }
+}
+
+void PipeTransfer::WriteBytes(void* buffer, size_t size)
+{
+ if (size == 0 || !IsPipeOpened()) return;
+
+ DWORD writenCount = 0;
+ auto result = WriteFile(m_Pipe, buffer, size, &writenCount, nullptr);
+ if (!result || writenCount < size)
+ {
+ LOG_LAST_ERROR("Failed write to pipe.");
+ CloseHandle(m_Pipe);
+ m_Pipe = 0;
+ }
+}
+
+void PipeTransfer::ReadObject(PipeSerializedObject& object)
+{
+ object.Read(this);
+}
+
+void PipeTransfer::WriteObject(PipeSerializedObject& object)
+{
+ object.Write(this);
+}
diff --git a/cheat-base/src/cheat-base/PipeTransfer.h b/cheat-base/src/cheat-base/PipeTransfer.h
new file mode 100644
index 0000000..8b91386
--- /dev/null
+++ b/cheat-base/src/cheat-base/PipeTransfer.h
@@ -0,0 +1,82 @@
+#pragma once
+
+#include
+#include
+
+typedef unsigned char byte;
+
+class PipeTransfer;
+class PipeSerializedObject
+{
+public:
+ virtual void Write(PipeTransfer* transfer) = 0;
+ virtual void Read(PipeTransfer* transfer) = 0;
+};
+
+class PipeTransfer
+{
+public:
+ PipeTransfer(const std::string& name);
+ ~PipeTransfer();
+
+ bool Create();
+ bool Connect();
+ bool WaitForConnection();
+ bool IsPipeOpened();
+
+ void ReadBytes(void* buffer, size_t size);
+ void WriteBytes(void* buffer, size_t size);
+
+ void ReadObject(PipeSerializedObject& object);
+ void WriteObject(PipeSerializedObject& object);
+
+ template
+ void Read(T& value)
+ {
+ ReadBytes(&value, sizeof(T));
+ }
+
+ template
+ void Write(const T& val)
+ {
+ WriteBytes(const_cast(&val), sizeof(T));
+ }
+
+ template<>
+ void Read(std::vector& vector)
+ {
+ size_t size; Read(size);
+ vector.clear();
+ vector.resize(size);
+ ReadBytes(vector.data(), size);
+ }
+
+ template<>
+ void Write(const std::vector& value)
+ {
+ Write(value.size());
+ WriteBytes(const_cast(value.data()), value.size());
+ }
+
+ template<>
+ void Read(std::string& value)
+ {
+ size_t size; Read(size);
+ value.clear();
+ value.resize(size);
+ ReadBytes(value.data(), size);
+ }
+
+ template<>
+ void Write(const std::string& value)
+ {
+ Write(value.length());
+ WriteBytes(const_cast(value.data()), value.length());
+ }
+
+private:
+
+ std::string m_Name;
+ HANDLE m_Pipe;
+};
+
diff --git a/cheat-base/src/cheat-base/ResourceLoader.cpp b/cheat-base/src/cheat-base/ResourceLoader.cpp
new file mode 100644
index 0000000..9f40d05
--- /dev/null
+++ b/cheat-base/src/cheat-base/ResourceLoader.cpp
@@ -0,0 +1,49 @@
+#include "pch.h"
+#include "ResourceLoader.h"
+#include "util.h"
+
+std::string ResourceLoader::Load(const char* name, const char* type)
+{
+ LPBYTE pData = nullptr;
+ DWORD size = 0;
+ if (!LoadEx(name, type, pData, size))
+ {
+ LOG_LAST_ERROR("Failed to load resource %s", name);
+ return {};
+ }
+
+ return std::string(reinterpret_cast(pData), size);
+}
+
+std::string ResourceLoader::Load(int resID, const char* type)
+{
+ return ResourceLoader::Load(MAKEINTRESOURCE(resID), type);
+}
+
+bool ResourceLoader::LoadEx(const char* name, const char* type, LPBYTE& pDest, DWORD& size)
+{
+ if (s_Handle == nullptr)
+ return false;
+
+ HRSRC hResource = FindResource(s_Handle, name, type);
+ if (hResource) {
+ HGLOBAL hGlob = LoadResource(s_Handle, hResource);
+ if (hGlob) {
+ size = SizeofResource(s_Handle, hResource);
+ pDest = static_cast(LockResource(hGlob));
+ if (size > 0 && pDest)
+ return true;
+ }
+ }
+ return false;
+}
+
+bool ResourceLoader::LoadEx(int resId, const char* type, LPBYTE& pDest, DWORD& size)
+{
+ return ResourceLoader::LoadEx(MAKEINTRESOURCE(resId), type, pDest, size);
+}
+
+void ResourceLoader::SetModuleHandle(HMODULE handle)
+{
+ s_Handle = handle;
+}
diff --git a/cheat-base/src/cheat-base/ResourceLoader.h b/cheat-base/src/cheat-base/ResourceLoader.h
new file mode 100644
index 0000000..bd41926
--- /dev/null
+++ b/cheat-base/src/cheat-base/ResourceLoader.h
@@ -0,0 +1,16 @@
+#pragma once
+class ResourceLoader
+{
+public:
+ static std::string Load(const char* name, const char* type);
+ static std::string Load(int resID, const char* type);
+
+ static bool LoadEx(const char* name, const char* type, LPBYTE& pDest, DWORD& size);
+ static bool LoadEx(int resId, const char* type, LPBYTE& pDest, DWORD& size);
+
+ static void SetModuleHandle(HMODULE handle);
+
+private:
+ inline static HMODULE s_Handle = nullptr;
+};
+
diff --git a/cheat-base/src/cheat-base/cheat/CheatManagerBase.cpp b/cheat-base/src/cheat-base/cheat/CheatManagerBase.cpp
new file mode 100644
index 0000000..aa55871
--- /dev/null
+++ b/cheat-base/src/cheat-base/cheat/CheatManagerBase.cpp
@@ -0,0 +1,551 @@
+#include
+#include "CheatManagerBase.h"
+
+#include
+
+#include
+#include
+#include
+
+namespace cheat
+{
+
+ void CheatManagerBase::Init(LPBYTE pFontData, DWORD dFontDataSize)
+ {
+ renderer::Init(pFontData, dFontDataSize);
+
+ events::RenderEvent += MY_METHOD_HANDLER(CheatManagerBase::OnRender);
+ events::KeyUpEvent += MY_METHOD_HANDLER(CheatManagerBase::OnKeyUp);
+ events::WndProcEvent += MY_METHOD_HANDLER(CheatManagerBase::OnWndProc);
+ }
+
+ CheatManagerBase::CheatManagerBase():
+ NF(m_SelectedSection, "", "General", 0),
+ m_IsBlockingInput(true),
+ m_IsPrevCursorActive(false)
+ {
+ }
+
+ void CheatManagerBase::DrawExternal() const
+ {
+ for (auto& feature : m_Features)
+ {
+ ImGui::PushID(&feature);
+ feature->DrawExternal();
+ ImGui::PopID();
+ }
+ }
+
+ void CheatManagerBase::DrawMenu()
+ {
+ if (m_ModuleOrder.empty())
+ return;
+
+ static std::string* current = &m_ModuleOrder[m_SelectedSection];
+
+ ImGui::SetNextWindowSize(ImVec2(600, 300), ImGuiCond_FirstUseEver);
+
+ if (!ImGui::Begin("CCGenshin (By Callow)"))
+ {
+ ImGui::End();
+ return;
+ }
+
+ ImGui::BeginGroup();
+
+ if (ImGui::Checkbox("Block key/mouse", &m_IsBlockingInput))
+ {
+ renderer::SetInputLock(this, m_IsBlockingInput);
+ }
+
+ if (ImGui::BeginListBox("##listbox 2", ImVec2(175, -FLT_MIN)))
+ {
+ size_t index = 0;
+ for (auto& moduleName : m_ModuleOrder)
+ {
+ const bool is_selected = (current == &moduleName);
+ if (ImGui::Selectable(moduleName.c_str(), is_selected))
+ {
+ current = &moduleName;
+ m_SelectedSection = index;
+ }
+
+ if (is_selected)
+ ImGui::SetItemDefaultFocus();
+ index++;
+ }
+ ImGui::EndListBox();
+ }
+
+ ImGui::EndGroup();
+
+ ImGui::SameLine();
+
+ ImGui::BeginGroup();
+
+ DrawProfileLine();
+
+ ImGuiWindowFlags window_flags = ImGuiWindowFlags_None;
+ ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 5.0f);
+ ImGui::BeginChild("ChildR", ImVec2(0, 0), true, window_flags);
+
+ auto& sections = m_FeatureMap[*current];
+ auto emptyName = std::string();
+ if (sections.count(emptyName) > 0)
+ DrawMenuSection(emptyName, sections[""]);
+
+ for (auto& [sectionName, features] : sections)
+ {
+ if (sectionName.empty())
+ continue;
+
+ DrawMenuSection(sectionName, features);
+ }
+
+ ImGui::EndChild();
+ ImGui::PopStyleVar();
+
+ ImGui::EndGroup();
+
+ ImGui::End();
+ }
+
+ void CheatManagerBase::DrawMenuSection(const std::string& sectionName, const std::vector& features) const
+ {
+ if (!sectionName.empty())
+ BeginGroupPanel(sectionName.c_str(), ImVec2(-1, 0));
+
+ for (auto& feature : features)
+ {
+ ImGui::PushID(&feature);
+ feature->DrawMain();
+ ImGui::PopID();
+ }
+
+ if (!sectionName.empty())
+ EndGroupPanel();
+ }
+
+ void CheatManagerBase::DrawProfileGlobalActivities()
+ {
+ if (ImGui::Button("Add new profile"))
+ {
+ std::unordered_set profileNameSet = { config::GetProfiles().begin(), config::GetProfiles().end() };
+ size_t index = 0;
+ std::string name {};
+ do
+ {
+ index++;
+ std::string newName = fmt::format("Profile #{}", index);
+ if (profileNameSet.count(newName) == 0)
+ name = newName;
+
+ } while (name.empty());
+
+ config::CreateProfile(name, false);
+ }
+ }
+
+ void CheatManagerBase::DrawProfileEntryActivities(const std::string& profileName)
+ {
+ bool isPopupOpen = ImGui::IsRenamePopupOpened();
+
+ if (isPopupOpen)
+ ImGui::BeginDisabled();
+
+ if (ImGui::SmallButton("Rnm"))
+ ImGui::OpenRenamePopup(profileName);
+ if (ImGui::IsItemHovered())
+ ImGui::SetTooltip("Rename");
+
+ if (isPopupOpen)
+ ImGui::EndDisabled();
+
+ std::string nameBuffer;
+ if (ImGui::DrawRenamePopup(nameBuffer))
+ {
+ config::RenameProfile(profileName, nameBuffer);
+ }
+
+ ImGui::SameLine();
+
+ if (ImGui::SmallButton("Del"))
+ config::RemoveProfile(profileName);
+ if (ImGui::IsItemHovered())
+ ImGui::SetTooltip("Delete");
+ }
+
+ void CheatManagerBase::DrawProfileEntry(const std::string& profileName)
+ {
+ ImGui::Text(profileName.c_str());
+ }
+
+ void CheatManagerBase::DrawProfileTableHeader()
+ {
+ ImGui::TableSetupColumn("Name");
+ }
+
+ int CheatManagerBase::GetProfileTableColumnCount()
+ {
+ return 1;
+ }
+
+ void CheatManagerBase::DrawProfileConfiguration()
+ {
+ static ImGuiTableFlags flags =
+ ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable
+ | ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_NoBordersInBody
+ | ImGuiTableFlags_ScrollY;
+ if (ImGui::BeginTable("ProfileTable", GetProfileTableColumnCount() + 1, flags,
+ ImVec2(0.0f, ImGui::GetTextLineHeightWithSpacing() * 10), 0.0f))
+ {
+ DrawProfileTableHeader();
+ ImGui::TableSetupColumn("Actions");
+ ImGui::TableSetupScrollFreeze(0, 1);
+ ImGui::TableHeadersRow();
+
+ // Copy profiles names
+ auto profiles = config::GetProfiles();
+ for (auto& profile : profiles)
+ {
+ ImGui::TableNextRow();
+ ImGui::TableNextColumn();
+
+ ImGui::PushID(profile.c_str());
+ DrawProfileEntry(profile);
+ ImGui::TableNextColumn();
+
+ DrawProfileEntryActivities(profile);
+ ImGui::PopID();
+ }
+
+ ImGui::EndTable();
+ }
+
+ DrawProfileGlobalActivities();
+ }
+
+ void CheatManagerBase::DrawProfileLine()
+ {
+ if (m_IsProfileConfigurationShowed)
+ ImGui::BeginDisabled();
+
+ bool buttonPressed = ImGui::Button("Configure...");
+
+ if (m_IsProfileConfigurationShowed)
+ ImGui::EndDisabled();
+
+ if (buttonPressed)
+ m_IsProfileConfigurationShowed = !m_IsProfileConfigurationShowed;
+
+ ImGui::SameLine();
+
+ auto& profiles = config::GetProfiles();
+ auto& currentProfile = config::CurrentProfileName();
+
+ constexpr float width = 200.0f;
+ ImGui::SetNextItemWidth(width);
+ if (ImGui::BeginCombo("Profile", currentProfile.c_str()))
+ {
+ for (auto& name : profiles)
+ {
+ bool is_selected = (currentProfile == name);
+ if (ImGui::Selectable(name.c_str(), is_selected))
+ config::ChangeProfile(name);
+
+ if (ImGui::IsItemHovered() && CalcWidth(name) > width)
+ ShowHelpText(name.c_str());
+
+ if (is_selected)
+ ImGui::SetItemDefaultFocus();
+ }
+ ImGui::EndCombo();
+ }
+ }
+
+ void CheatManagerBase::DrawStatus() const
+ {
+ // Drawing status window
+ ImGuiWindowFlags flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoBackground |
+ ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoBringToFrontOnFocus |
+ ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse;
+
+ auto& settings = feature::Settings::GetInstance();
+ if (!settings.f_StatusMove)
+ flags |= ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoMove;
+
+ ImGui::Begin("Cheat status", nullptr, flags);
+
+ static ImGuiTableFlags tabFlags = ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg;
+
+ if (ImGui::BeginTable("activesTable", 1, tabFlags))
+ {
+ ImGui::TableSetupColumn("Active features");
+ ImGui::TableHeadersRow();
+
+ int row = 0;
+
+ for (auto& feature : m_Features)
+ {
+ if (feature->NeedStatusDraw())
+ {
+ ImGui::TableNextRow();
+ ImGui::TableSetColumnIndex(0);
+
+ feature->DrawStatus();
+
+ ImU32 row_bg_color = ImGui::GetColorU32(
+ ImVec4(0.2f + row * 0.1f, 0.1f + row * 0.05f, 0.1f + row * 0.03f, 0.85f));
+ ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0, row_bg_color);
+ row++;
+ }
+ }
+ ImGui::EndTable();
+ }
+
+ ImGui::End();
+ }
+
+ void CheatManagerBase::DrawInfo()
+ {
+ auto& settings = feature::Settings::GetInstance();
+
+ // Drawing status window
+ ImGuiWindowFlags flags = ImGuiWindowFlags_NoDecoration |
+ ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoBringToFrontOnFocus |
+ ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse;
+
+ if (!settings.f_InfoMove)
+ flags |= ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoMove;
+
+ auto checkLambda = [](const Feature* feat) { return feat->NeedInfoDraw(); };
+ bool showAny = std::any_of(m_Features.begin(), m_Features.end(), checkLambda);
+ if (!showAny && !settings.f_StatusMove)
+ return;
+
+ ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.04f, 0.05f, 0.05f, 0.90f));
+ ImGui::Begin("Info window", nullptr, flags);
+ ImGui::PopStyleColor();
+
+ if (!showAny)
+ {
+ ImGui::Text("Nothing here");
+ ImGui::End();
+ return;
+ }
+
+ for (auto& moduleName : m_ModuleOrder)
+ {
+ auto& sections = m_FeatureMap[moduleName];
+ bool moduleShowAny = std::any_of(sections.begin(), sections.end(),
+ [](const auto& iter)
+ {
+ return std::any_of(iter.second.begin(), iter.second.end(),
+ [](const auto feat)
+ {
+ return feat->NeedInfoDraw();
+ });
+ }
+ );
+ if (!moduleShowAny)
+ continue;
+
+ BeginGroupPanel(moduleName.c_str(), ImVec2(-1, 0));
+
+ for (auto& [sectionName, features] : sections)
+ {
+ for (auto& feature : features)
+ {
+ if (!feature->NeedInfoDraw())
+ continue;
+
+ ImGui::PushID(&feature);
+ feature->DrawInfo();
+ ImGui::PopID();
+ }
+ }
+
+ EndGroupPanel();
+ }
+
+ ImGui::End();
+ }
+
+ void CheatManagerBase::DrawFps()
+ {
+ auto& settings = feature::Settings::GetInstance();
+
+ ImGuiWindowFlags flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoFocusOnAppearing
+ | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_AlwaysAutoResize;
+
+ if (!settings.f_FpsMove)
+ flags |= ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoMove;
+
+ if (ImGui::Begin("FPS", nullptr, flags))
+ {
+ ImGui::Text("FPS: %.1f", ImGui::GetIO().Framerate);
+ ImGui::End();
+ }
+ }
+
+ void CheatManagerBase::DrawNotifications()
+ {
+ ImGui::RenderNotifications();
+ }
+
+
+ void CheatManagerBase::OnRender()
+ {
+ auto& settings = feature::Settings::GetInstance();
+
+ DrawExternal();
+
+ if (s_IsMenuShowed)
+ DrawMenu();
+
+ if (m_IsProfileConfigurationShowed)
+ {
+ ImGui::SetNextWindowSize({ 0, ImGui::GetTextLineHeightWithSpacing() * 11 }, ImGuiCond_FirstUseEver);
+ if (ImGui::Begin("Config profile configuration", &m_IsProfileConfigurationShowed))
+ DrawProfileConfiguration();
+
+ ImGui::End();
+ }
+
+ if (settings.f_StatusShow)
+ DrawStatus();
+
+ if (settings.f_InfoShow)
+ DrawInfo();
+
+ if (settings.f_FpsShow)
+ DrawFps();
+
+ if (settings.f_NotificationsShow)
+ DrawNotifications();
+
+ if (settings.f_MenuKey.value().IsReleased() && !ImGui::IsAnyItemActive())
+ ToggleMenuShow();
+ }
+
+ void CheatManagerBase::CheckToggles(short key) const
+ {
+ if (s_IsMenuShowed || renderer::IsInputLocked())
+ return;
+
+ auto& settings = feature::Settings::GetInstance();
+ if (!settings.f_HotkeysEnabled)
+ return;
+
+ for (auto& field : config::GetFields>())
+ {
+ auto& toggle = field.value();
+ if (toggle.value.IsPressed(key))
+ {
+ toggle.enabled = !toggle.enabled;
+ field.FireChanged();
+
+ std::string title = fmt::format("{}: {}", field.friendName(), (toggle ? "Enabled" : "Disabled"));
+ ImGuiToast toast(ImGuiToastType_None, settings.f_NotificationsDelay);
+ toast.set_title(title.c_str());
+ ImGui::InsertNotification(toast);
+ }
+ }
+ }
+
+ bool menuToggled = false;
+
+ void CheatManagerBase::ToggleMenuShow()
+ {
+ s_IsMenuShowed = !s_IsMenuShowed;
+ renderer::SetInputLock(this, s_IsMenuShowed && m_IsBlockingInput);
+ menuToggled = true;
+ }
+
+ void CheatManagerBase::OnKeyUp(short key, bool& cancelled)
+ {
+ auto& settings = feature::Settings::GetInstance();
+ if (!settings.f_MenuKey.value().IsPressed(key))
+ {
+ CheckToggles(key);
+ return;
+ }
+ }
+
+ void CheatManagerBase::OnWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, bool& canceled)
+ {
+ if (!menuToggled)
+ return;
+
+ menuToggled = false;
+
+ if (s_IsMenuShowed)
+ {
+ m_IsPrevCursorActive = CursorGetVisibility();
+ if (!m_IsPrevCursorActive)
+ CursorSetVisibility(true);
+ }
+ else if (!m_IsPrevCursorActive)
+ CursorSetVisibility(false);
+ }
+
+ bool CheatManagerBase::IsMenuShowed()
+ {
+ return s_IsMenuShowed;
+ }
+
+ void CheatManagerBase::PushFeature(Feature* feature)
+ {
+ m_Features.push_back(feature);
+
+ auto& info = feature->GetGUIInfo();
+ if (m_FeatureMap.count(info.moduleName) == 0)
+ {
+ m_FeatureMap[info.moduleName] = {};
+ m_ModuleOrder.push_back(info.moduleName);
+ }
+
+ auto& sectionMap = m_FeatureMap[info.moduleName];
+ std::string sectionName = info.isGroup ? info.name : std::string();
+ if (sectionMap.count(sectionName) == 0)
+ sectionMap[sectionName] = {};
+
+ auto& featureList = sectionMap[sectionName];
+ featureList.push_back(feature);
+ }
+
+ void CheatManagerBase::AddFeature(Feature* feature)
+ {
+ PushFeature(feature);
+ }
+
+ void CheatManagerBase::AddFeatures(std::vector features)
+ {
+ for (auto& feature : features)
+ {
+ PushFeature(feature);
+ }
+ }
+
+ void CheatManagerBase::SetModuleOrder(std::vector moduleOrder)
+ {
+ std::unordered_set moduleSet;
+ moduleSet.insert(m_ModuleOrder.begin(), m_ModuleOrder.end());
+
+ m_ModuleOrder.clear();
+
+ for (auto& moduleName : moduleOrder)
+ {
+ if (m_FeatureMap.count(moduleName) == 0)
+ continue;
+
+ m_ModuleOrder.push_back(moduleName);
+ moduleSet.erase(moduleName);
+ }
+
+ for (auto& moduleName : moduleSet)
+ {
+ m_ModuleOrder.push_back(moduleName);
+ }
+ }
+}
diff --git a/cheat-base/src/cheat-base/cheat/CheatManagerBase.h b/cheat-base/src/cheat-base/cheat/CheatManagerBase.h
new file mode 100644
index 0000000..fd8f2dc
--- /dev/null
+++ b/cheat-base/src/cheat-base/cheat/CheatManagerBase.h
@@ -0,0 +1,76 @@
+#pragma once
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+#include
+
+namespace cheat
+{
+ class CheatManagerBase
+ {
+ public:
+
+ static bool IsMenuShowed();
+
+ //static CheatManagerBase& GetInstance();
+ CheatManagerBase(CheatManagerBase const&) = delete;
+ void operator=(CheatManagerBase const&) = delete;
+
+ void AddFeature(Feature* feature);
+ void AddFeatures(std::vector features);
+
+ void SetModuleOrder(std::vector moduleOrder);
+
+ void OnKeyUp(short key, bool& cancelled);
+ void OnWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, bool& cancelled);
+ void OnRender();
+
+ void Init(LPBYTE pFontData, DWORD dFontDataSize);
+
+ virtual void CursorSetVisibility(bool visibility) = 0;
+ virtual bool CursorGetVisibility() = 0;
+
+ protected:
+ config::Field m_SelectedSection;
+
+ std::vector m_Features;
+ std::vector m_ModuleOrder;
+ std::map>> m_FeatureMap;
+
+ inline static bool s_IsMenuShowed = false;
+ bool m_IsBlockingInput;
+ bool m_IsPrevCursorActive;
+ bool m_IsProfileConfigurationShowed;
+
+ explicit CheatManagerBase();
+
+ void DrawExternal() const;
+
+ void DrawMenu();
+ void DrawMenuSection(const std::string& sectionName, const std::vector& features) const;
+
+ virtual void DrawProfileGlobalActivities();
+ virtual void DrawProfileEntryActivities(const std::string&profileName);
+ virtual void DrawProfileEntry(const std::string& profileName);
+ virtual void DrawProfileTableHeader();
+ virtual int GetProfileTableColumnCount();
+ virtual void DrawProfileConfiguration();
+ virtual void DrawProfileLine();
+
+ virtual void DrawStatus() const;
+ virtual void DrawInfo();
+ void DrawFps();
+ static void DrawNotifications();
+ void PushFeature(Feature* feature);
+ void CheckToggles(short key) const;
+
+ void ToggleMenuShow();
+ };
+}
+
+
diff --git a/cheat-base/src/cheat-base/cheat/Feature.h b/cheat-base/src/cheat-base/cheat/Feature.h
new file mode 100644
index 0000000..44160ed
--- /dev/null
+++ b/cheat-base/src/cheat-base/cheat/Feature.h
@@ -0,0 +1,38 @@
+#pragma once
+
+#include
+
+namespace cheat
+{
+ struct FeatureGUIInfo
+ {
+ std::string name;
+ std::string moduleName;
+ bool isGroup;
+ };
+
+ class Feature
+ {
+ public:
+ Feature(Feature const&) = delete;
+ void operator=(Feature const&) = delete;
+
+ // GUI handlers
+ virtual const FeatureGUIInfo& GetGUIInfo() const = 0;
+
+ virtual void DrawMain() = 0;
+
+ virtual bool NeedStatusDraw() const { return false; };
+ virtual void DrawStatus() { };
+
+ virtual bool NeedInfoDraw() const { return false; };
+ virtual void DrawInfo() { };
+
+ virtual void DrawExternal() { };
+
+ protected:
+ Feature() { };
+ };
+}
+
+
diff --git a/cheat-base/src/cheat-base/cheat/misc/Settings.cpp b/cheat-base/src/cheat-base/cheat/misc/Settings.cpp
new file mode 100644
index 0000000..d44fcf0
--- /dev/null
+++ b/cheat-base/src/cheat-base/cheat/misc/Settings.cpp
@@ -0,0 +1,140 @@
+#include
+#include "Settings.h"
+
+#include
+#include
+#include
+
+namespace cheat::feature
+{
+ Settings::Settings() : Feature(),
+ NF(f_MenuKey, "Show Cheat Menu Key", "General", Hotkey(VK_F1)),
+ NF(f_HotkeysEnabled, "Hotkeys Enabled", "General", true),
+ NF(f_FontSize, "Font size", "General", 16.0f),
+
+ NF(f_StatusMove, "Move Status Window", "General::StatusWindow", true),
+ NF(f_StatusShow, "Show Status Window", "General::StatusWindow", true),
+
+ NF(f_InfoMove, "Move Info Window", "General::InfoWindow", true),
+ NF(f_InfoShow, "Show Info Window", "General::InfoWindow", true),
+
+ NF(f_FpsMove, "Move FPS Indicator", "General::FPS", false),
+ NF(f_FpsShow, "Show FPS Indicator", "General::FPS", true),
+
+ NF(f_NotificationsShow, "Show Notifications", "General::Notify", true),
+ NF(f_NotificationsDelay, "Notifications Delay", "General::Notify", 500),
+
+ NF(f_FileLogging, "File Logging", "General::Logging", false),
+ NF(f_ConsoleLogging, "Console Logging", "General::Logging", true),
+
+ NF(f_FastExitEnable, "Fast Exit", "General::FastExit", false),
+ NF(f_HotkeyExit, "Hotkeys", "General::FastExit", Hotkey(VK_F12))
+
+ {
+ renderer::SetGlobalFontSize(f_FontSize);
+ f_HotkeyExit.value().PressedEvent += MY_METHOD_HANDLER(Settings::OnExitKeyPressed);
+ }
+
+ const FeatureGUIInfo& Settings::GetGUIInfo() const
+ {
+ static const FeatureGUIInfo info{ "", "Settings", false };
+ return info;
+ }
+
+ void Settings::DrawMain()
+ {
+
+ BeginGroupPanel("General", ImVec2(-1, 0));
+ {
+ ConfigWidget(f_MenuKey, false,
+ "Key to toggle main menu visibility. Cannot be empty.\n"\
+ "If you forget this key, you can see or set it in your config file.");
+ ConfigWidget(f_HotkeysEnabled, "Enable hotkeys.");
+ if (ConfigWidget(f_FontSize, 1, 8, 64, "Font size for cheat interface."))
+ {
+ f_FontSize = std::clamp(f_FontSize.value(), 8, 64);
+ renderer::SetGlobalFontSize(f_FontSize);
+ }
+ }
+ EndGroupPanel();
+
+ BeginGroupPanel("Logging", ImVec2(-1, 0));
+ {
+ bool consoleChanged = ConfigWidget(f_ConsoleLogging,
+ "Enable console for logging information (changes will take effect after relaunch)");
+ if (consoleChanged && !f_ConsoleLogging)
+ {
+ Logger::SetLevel(Logger::Level::None, Logger::LoggerType::ConsoleLogger);
+ }
+
+ bool fileLogging = ConfigWidget(f_FileLogging,
+ "Enable file logging (changes will take effect after relaunch).\n" \
+ "A folder in the app directory will be created for logs.");
+ if (fileLogging && !f_FileLogging)
+ {
+ Logger::SetLevel(Logger::Level::None, Logger::LoggerType::FileLogger);
+ }
+ }
+ EndGroupPanel();
+
+ BeginGroupPanel("Status Window", ImVec2(-1, 0));
+ {
+ ConfigWidget(f_StatusShow);
+ ConfigWidget(f_StatusMove, "Allow moving of 'Status' window.");
+ }
+ EndGroupPanel();
+
+ BeginGroupPanel("Info Window", ImVec2(-1, 0));
+ {
+ ConfigWidget(f_InfoShow);
+ ConfigWidget(f_InfoMove, "Allow moving of 'Info' window.");
+ }
+ EndGroupPanel();
+
+ BeginGroupPanel("FPS indicator", ImVec2(-1, 0));
+ {
+ ConfigWidget(f_FpsShow);
+ ConfigWidget(f_FpsMove, "Allow moving of 'FPS Indicator' window.");
+ }
+ EndGroupPanel();
+
+ BeginGroupPanel("Show Notifications", ImVec2(-1, 0));
+ {
+ ConfigWidget(f_NotificationsShow, "Notifications on the bottom-right corner of the window will be displayed.");
+ ConfigWidget(f_NotificationsDelay, 1,1,10000, "Delay in milliseconds between notifications.");
+ }
+ EndGroupPanel();
+
+ BeginGroupPanel("Fast Exit", ImVec2(-1, 0));
+ {
+ ConfigWidget("Enabled",
+ f_FastExitEnable,
+ "Enable Fast Exit.\n"
+ );
+ if (!f_FastExitEnable)
+ ImGui::BeginDisabled();
+
+ ConfigWidget("Key", f_HotkeyExit, true,
+ "Key to exit the game.");
+
+ if (!f_FastExitEnable)
+ ImGui::EndDisabled();
+ }
+ EndGroupPanel();
+ }
+
+ Settings& Settings::GetInstance()
+ {
+ static Settings instance;
+ return instance;
+ }
+
+ void Settings::OnExitKeyPressed()
+ {
+ if (!f_FastExitEnable || CheatManagerBase::IsMenuShowed())
+ return;
+
+ ExitProcess(0);
+ }
+}
+
diff --git a/cheat-base/src/cheat-base/cheat/misc/Settings.h b/cheat-base/src/cheat-base/cheat/misc/Settings.h
new file mode 100644
index 0000000..1a00653
--- /dev/null
+++ b/cheat-base/src/cheat-base/cheat/misc/Settings.h
@@ -0,0 +1,44 @@
+#pragma once
+#include
+#include
+
+namespace cheat::feature
+{
+
+ class Settings : public Feature
+ {
+ public:
+ config::Field f_MenuKey;
+ config::Field f_HotkeysEnabled;
+ config::Field f_FontSize;
+
+ config::Field f_StatusMove;
+ config::Field f_StatusShow;
+
+ config::Field f_InfoMove;
+ config::Field f_InfoShow;
+
+ config::Field f_FpsShow;
+ config::Field f_FpsMove;
+
+ config::Field f_NotificationsShow;
+ config::Field f_NotificationsDelay;
+
+ config::Field f_ConsoleLogging;
+ config::Field f_FileLogging;
+
+ config::Field f_FastExitEnable;
+ config::Field f_HotkeyExit;
+
+ static Settings& GetInstance();
+
+ const FeatureGUIInfo& GetGUIInfo() const override;
+ void DrawMain() override;
+
+ private:
+
+ void OnExitKeyPressed();
+ Settings();
+ };
+}
+
diff --git a/cheat-base/src/cheat-base/config/Config.cpp b/cheat-base/src/cheat-base/config/Config.cpp
new file mode 100644
index 0000000..0b7b75b
--- /dev/null
+++ b/cheat-base/src/cheat-base/config/Config.cpp
@@ -0,0 +1,383 @@
+#include
+#include "config.h"
+
+#include
+#include
+
+#include
+#include
+
+namespace config
+{
+ TEvent<> ProfileChanged;
+
+ static std::filesystem::path s_Filepath;
+ static nlohmann::json s_ConfigRoot;
+ static nlohmann::json s_EmptyJObject = nlohmann::json::object();
+
+ // Little speed-up
+ static nlohmann::json* s_ProfileRoot = nullptr;
+ static nlohmann::json* s_Profiles = nullptr;
+ static nlohmann::json* s_SharedRoot = nullptr;
+
+ static std::mutex s_ProfileMutex;
+ static std::string s_ProfileName;
+ static std::vector s_ProfilesNames;
+
+ static const int c_SaveDelay = 2000;
+ static TEvent<>* s_UpdateEvent = nullptr;
+ static std::atomic s_NextSaveTimestamp = 0;
+
+ static std::vector> s_Entries;
+
+ void LoadFile()
+ {
+ std::ifstream fileInput(s_Filepath, std::ios::in);
+ if (!fileInput.is_open())
+ {
+ LOG_DEBUG("Failed to open config file, maybe it's first launch.");
+ return;
+ }
+
+ try
+ {
+ s_ConfigRoot = nlohmann::json::parse(fileInput);
+ }
+ catch (nlohmann::json::parse_error& ex)
+ {
+ LOG_ERROR("Parse error at byte %llu", ex.byte);
+ return;
+ }
+ }
+
+ void UpdateProfilesNames()
+ {
+ std::lock_guard _lock(s_ProfileMutex);
+ s_ProfilesNames.clear();
+ for (auto& [name, _] : s_Profiles->items())
+ {
+ s_ProfilesNames.push_back(name);
+ }
+ }
+
+ void Initialize(const std::string& filePath)
+ {
+ s_ConfigRoot = {};
+ s_Filepath = filePath;
+ LoadFile();
+
+ if (!s_ConfigRoot.contains("current_profile"))
+ {
+ s_ConfigRoot = {
+ { "shared", {} },
+ { "profiles", {} },
+ { "current_profile", ""}
+ };
+ }
+
+ s_Profiles = &s_ConfigRoot["profiles"];
+ s_SharedRoot = &s_ConfigRoot["shared"];
+
+ if (s_ConfigRoot["current_profile"] == "")
+ CreateProfile("default");
+ else
+ ChangeProfile(s_ConfigRoot["current_profile"]);
+
+ UpdateProfilesNames();
+ }
+
+ void OnUpdate();
+
+ void SetupUpdate(TEvent<>* updateEvent)
+ {
+ s_UpdateEvent = updateEvent;
+ (*s_UpdateEvent) += FUNCTION_HANDLER(OnUpdate);
+ }
+
+ void UpdateSaveTimestamp()
+ {
+ if (!s_UpdateEvent)
+ return;
+
+ if (s_NextSaveTimestamp != 0)
+ return;
+
+ s_NextSaveTimestamp = util::GetCurrentTimeMillisec() + c_SaveDelay;
+ }
+
+ void ResetNotShared()
+ {
+ for (auto& entry : s_Entries)
+ {
+ if (!entry->IsShared())
+ entry->Reset();
+ }
+ }
+
+ nlohmann::json& GetFieldJsonContainer(internal::FieldEntry* field, bool create = false)
+ {
+ if (field->GetContainer() != nullptr)
+ return *field->GetContainer();
+
+ nlohmann::json* rootContainer = s_ProfileRoot;
+ if (field->IsShared())
+ rootContainer = s_SharedRoot;
+
+ auto sectionParts = util::StringSplit("::", field->GetSection());
+ for (auto& part : sectionParts)
+ {
+ if (!rootContainer->contains(part))
+ {
+ if (!create)
+ return s_EmptyJObject;
+
+ (*rootContainer)[part] = {};
+ }
+
+ rootContainer = &(*rootContainer)[part];
+ }
+
+ auto& sectionContainer = *rootContainer;
+ if (!sectionContainer.contains(field->GetName()))
+ {
+ if (!create)
+ return s_EmptyJObject;
+
+ sectionContainer[field->GetName()] = {};
+ }
+
+ auto& fieldContainer = sectionContainer[field->GetName()];
+ field->SetContainer(&fieldContainer);
+ return fieldContainer;
+ }
+
+ void RemoveFieldContainer(internal::FieldEntry* field, const std::string& section, const std::string& name, bool shared)
+ {
+ field->SetContainer(nullptr);
+
+ nlohmann::json* rootContainer = s_ProfileRoot;
+ if (shared)
+ rootContainer = s_SharedRoot;
+
+ auto sectionParts = util::StringSplit("::", section);
+ std::list> nodePath;
+ for (auto& part : sectionParts)
+ {
+ if (!(*rootContainer).contains(part))
+ return;
+
+ nodePath.push_front({ part, rootContainer });
+ rootContainer = &(*rootContainer)[part];
+ }
+
+ if (!rootContainer->contains(name))
+ return;
+
+ rootContainer->erase(name);
+ for (auto& [key, node] : nodePath)
+ {
+ if (!(*node)[key].empty())
+ break;
+
+ node->erase(key);
+ }
+ }
+
+ void UpdateField(internal::FieldEntry* field)
+ {
+ auto& fieldContainer = GetFieldJsonContainer(field);
+ field->FromJson(fieldContainer);
+ }
+
+ void UpdateNotShared()
+ {
+ ResetNotShared();
+ for (auto& entry : s_Entries)
+ {
+ if (!entry->IsShared())
+ UpdateField(entry.get());
+ }
+ }
+
+ void LoadField(internal::FieldEntry* field)
+ {
+ auto& fieldContainer = GetFieldJsonContainer(field, true);
+
+ auto jObject = field->ToJson();
+ if (jObject.empty())
+ RemoveFieldContainer(field, field->GetSection(), field->GetName(), field->IsShared());
+ else
+ fieldContainer = jObject;
+ }
+
+ void LoadAll()
+ {
+ for (auto& entry : s_Entries)
+ {
+ LoadField(entry.get());
+ }
+ }
+
+ void OnFieldChanged(internal::FieldEntry* field)
+ {
+ LoadField(field);
+ Save();
+ }
+
+ void OnFieldMoved(internal::FieldEntry* field, const std::string& oldSection, bool oldShared)
+ {
+ RemoveFieldContainer(field, oldSection, field->GetName(), oldShared);
+ OnFieldChanged(field);
+ }
+
+ void OnFieldReposition(internal::FieldEntry* field, const std::string& oldSection, bool oldShared)
+ {
+ field->SetContainer(nullptr);
+ UpdateField(field);
+ }
+
+ void internal::AddField(std::shared_ptr field)
+ {
+ s_Entries.push_back(field);
+ UpdateField(field.get());
+ field->ChangedEvent += FUNCTION_HANDLER(OnFieldChanged);
+ field->MovedEvent += FUNCTION_HANDLER(OnFieldMoved);
+ field->RepositionEvent += FUNCTION_HANDLER(OnFieldReposition);
+ }
+
+ void Refresh()
+ {
+ LoadAll();
+ Save();
+ }
+
+ void SaveInternal()
+ {
+ std::ofstream fileOutput(s_Filepath, std::ios::out);
+ if (!fileOutput.is_open())
+ {
+ LOG_DEBUG("Failed to open config file for writing.");
+ UpdateSaveTimestamp();
+ return;
+ }
+
+ fileOutput << s_ConfigRoot.dump(4);
+ fileOutput.close();
+ }
+
+ void Save()
+ {
+ if (s_UpdateEvent)
+ {
+ UpdateSaveTimestamp();
+ return;
+ }
+ SaveInternal();
+ }
+
+ void OnUpdate()
+ {
+ if (s_NextSaveTimestamp > 0 && util::GetCurrentTimeMillisec() > s_NextSaveTimestamp)
+ {
+ s_NextSaveTimestamp = 0;
+ SaveInternal();
+ }
+ }
+
+ void CreateProfile(const std::string& profileName, bool moveAfterCreate)
+ {
+ if (s_Profiles->contains(profileName))
+ {
+ if (moveAfterCreate)
+ ChangeProfile(profileName);
+ return;
+ }
+
+ (*s_Profiles)[profileName] = {};
+ UpdateProfilesNames();
+
+ if (moveAfterCreate)
+ ChangeProfile(profileName);
+ Save();
+ }
+
+ void RemoveProfile(const std::string& profileName)
+ {
+ if (!s_Profiles->contains(profileName))
+ return;
+
+ if (s_Profiles->size() == 1)
+ return;
+
+ if (s_ProfileName == profileName)
+ {
+ for (auto& [name, value] : s_Profiles->items())
+ {
+ if (name != profileName)
+ {
+ ChangeProfile(name);
+ break;
+ }
+ }
+ }
+
+ s_Profiles->erase(profileName);
+ UpdateProfilesNames();
+ Save();
+ }
+
+ void RenameProfile(const std::string& oldProfileName, const std::string& newProfileName)
+ {
+ if (!s_Profiles->contains(oldProfileName) || s_Profiles->contains(newProfileName))
+ return;
+
+ if (s_ProfileName == oldProfileName)
+ s_ProfileRoot = nullptr;
+
+ (*s_Profiles)[newProfileName] = (*s_Profiles)[oldProfileName];
+ s_Profiles->erase(oldProfileName);
+
+ if (s_ProfileRoot == nullptr)
+ {
+ for (auto& entry : s_Entries)
+ {
+ if (!entry->IsShared())
+ entry->SetContainer(nullptr);
+ }
+
+ ChangeProfile(newProfileName);
+ }
+ UpdateProfilesNames();
+ Save();
+ }
+
+ void ChangeProfile(const std::string& profileName)
+ {
+ if (s_ProfileName == profileName)
+ return;
+
+ if (!s_Profiles->contains(profileName))
+ return;
+
+ std::lock_guard _lock(s_ProfileMutex);
+
+ s_ProfileRoot = &(*s_Profiles)[profileName];
+ s_ProfileName = profileName;
+
+ s_ConfigRoot["current_profile"] = profileName;
+ UpdateNotShared();
+ Save();
+
+ ProfileChanged();
+ }
+
+ std::vector const& GetProfiles()
+ {
+ return s_ProfilesNames;
+ }
+
+ std::string const& CurrentProfileName()
+ {
+ return s_ProfileName;
+ }
+}
\ No newline at end of file
diff --git a/cheat-base/src/cheat-base/config/Config.h b/cheat-base/src/cheat-base/config/Config.h
new file mode 100644
index 0000000..4b2a054
--- /dev/null
+++ b/cheat-base/src/cheat-base/config/Config.h
@@ -0,0 +1,66 @@
+#pragma once
+
+#include "Field.h"
+#include
+#include
+#include "fields/Toggle.h"
+#include "fields/Enum.h"
+
+#define NFEX(field, friendName, name, section, defaultValue, shared) field##(config::CreateField(friendName, name, section, shared, defaultValue))
+#define NFEXUP(field, friendName, name, section, shared, ...) field##(config::CreateField(friendName, name, section, shared, __VA_ARGS__))
+
+#define NFB(field, name, section, defaultValue, shared) NFEX(field, name, config::internal::FixFieldName(#field), section, defaultValue, shared)
+#define NFS(field, name, section, defaultValue) NFB(field, name, section, defaultValue, true)
+#define NF(field, name, section, defaultValue) NFB(field, name, section, defaultValue, false)
+
+#define NFPB(field, name, section, shared, ...) NFEXUP(field, name, config::internal::FixFieldName(#field), section, shared, __VA_ARGS__)
+#define NFPS(field, name, section, ...) NFPB(field, name, section, true, __VA_ARGS__)
+#define NFP(field, name, section, ...) NFPB(field, name, section, false, __VA_ARGS__)
+
+namespace config
+{
+ namespace internal
+ {
+ template
+ std::vector s_Fields;
+
+ void AddField(std::shared_ptr field);
+
+ inline std::string FixFieldName(const std::string& fieldName)
+ {
+ if (fieldName.substr(1, 1) == "_")
+ return fieldName.substr(2);
+ return fieldName;
+ }
+ }
+
+ template
+ Field CreateField(const std::string& friendName, const std::string& name, const std::string& section, bool multiProfile, Args... args)
+ {
+ auto newField = Field(friendName, name, section, T(args...), multiProfile);
+ internal::s_Fields>.push_back(newField);
+ internal::AddField(newField.entry());
+ return newField;
+ }
+
+ template
+ std::vector>& GetFields()
+ {
+ return internal::s_Fields>;
+ }
+
+ void Initialize(const std::string& filePath);
+ void SetupUpdate(TEvent<>*);
+
+ void Refresh();
+ void Save();
+
+ void CreateProfile(const std::string& profileName, bool moveAfterCreate = true);
+ void RemoveProfile(const std::string& profileName);
+ void RenameProfile(const std::string& oldProfileName, const std::string& newProfileName);
+ void ChangeProfile(const std::string& profileName);
+ std::vector const& GetProfiles();
+ std::string const& CurrentProfileName();
+
+ extern TEvent<> ProfileChanged;
+}
\ No newline at end of file
diff --git a/cheat-base/src/cheat-base/config/Field.h b/cheat-base/src/cheat-base/config/Field.h
new file mode 100644
index 0000000..2e48a6e
--- /dev/null
+++ b/cheat-base/src/cheat-base/config/Field.h
@@ -0,0 +1,15 @@
+#pragma once
+
+#include "internal/FieldSerialize.h"
+#include "internal/FieldBase.h"
+namespace config
+{
+ template
+ class Field : public internal::FieldBase
+ {
+ public:
+ using base = internal::FieldBase;
+ using base::operator=;
+ using base::base;
+ };
+}
\ No newline at end of file
diff --git a/cheat-base/src/cheat-base/config/converters.h b/cheat-base/src/cheat-base/config/converters.h
new file mode 100644
index 0000000..7882b20
--- /dev/null
+++ b/cheat-base/src/cheat-base/config/converters.h
@@ -0,0 +1,68 @@
+#pragma once
+
+#include
+#include
+#include
+
+namespace config::converters
+{
+
+ template
+ inline nlohmann::json ToJson(const T& value)
+ {
+ return nlohmann::json(value);
+ }
+
+ template
+ inline void FromJson(T& value, const nlohmann::json& jObject)
+ {
+ value = jObject.get();
+ }
+
+ // Here is storing all simple converters json<->class
+
+ // ImColor
+ template<>
+ inline nlohmann::json ToJson(const ImColor& value)
+ {
+ return nlohmann::json((ImU32)value);
+ }
+
+ template<>
+ inline void FromJson(ImColor& value, const nlohmann::json& jObject)
+ {
+ value = { (ImU32)jObject };
+ }
+
+ // Hotkey
+ template<>
+ inline nlohmann::json ToJson(const Hotkey& value)
+ {
+ auto keys = value.GetKeys();
+ if (keys.empty())
+ return {};
+
+ if (keys.size() == 1)
+ return keys[0];
+
+ return nlohmann::json(value.GetKeys());
+ }
+
+ template<>
+ inline void FromJson(Hotkey& value, const nlohmann::json& jObject)
+ {
+ if (jObject.is_null() || jObject.empty())
+ return;
+
+ if (jObject.is_number())
+ {
+ value = { jObject.get() };
+ return;
+ }
+
+ value = { jObject.get>() };
+ }
+
+ // Enum
+
+}
\ No newline at end of file
diff --git a/cheat-base/src/cheat-base/config/fields/Enum.h b/cheat-base/src/cheat-base/config/fields/Enum.h
new file mode 100644
index 0000000..96a29a4
--- /dev/null
+++ b/cheat-base/src/cheat-base/config/fields/Enum.h
@@ -0,0 +1,100 @@
+#pragma once
+
+#include
+
+namespace config
+{
+
+ template
+ class Enum
+ {
+ public:
+ Enum()
+ {
+ static_assert(std::is_enum::value, "Must be an enum type");
+ m_Value = T();
+ }
+
+ Enum(T enumValue)
+ {
+ static_assert(std::is_enum::value, "Must be an enum type");
+ m_Value = enumValue;
+ }
+
+ inline T value() const
+ {
+ return m_Value;
+ }
+
+ inline T* pointer() const
+ {
+ return const_cast(&m_Value);
+ }
+
+ inline operator T()
+ {
+ return value();
+ }
+
+ inline T* operator&()
+ {
+ return pointer();
+ }
+
+ inline uint32_t raw() const
+ {
+ return static_cast(m_Value);
+ }
+
+ inline Enum& operator=(const T& other)
+ {
+ static_assert(std::is_enum::value, "Must be an enum type");
+ m_Value = other;
+ return *this;
+ }
+
+ inline Enum& operator=(const uint32_t& other)
+ {
+ m_Value = static_cast(other);
+ return *this;
+ }
+
+ private:
+ T m_Value;
+ };
+
+
+ //// Okay, close your eyes and don't look at this mess. (Please)
+ //template
+ //class Field> : public internal::FieldBase>
+ //{
+ //public:
+ // using base = internal::FieldBase>;
+ // using base::operator=;
+ // using base::base;
+
+ // operator T() const
+ // {
+ // return base::value();
+ // }
+ //};
+}
+
+namespace nlohmann
+{
+ template
+ struct adl_serializer> {
+ static void to_json(json& j, const config::Enum& enumValue)
+ {
+ j = {
+ { "name", magic_enum::enum_name(enumValue.value()) },
+ { "value", enumValue.raw() }
+ };
+ }
+
+ static void from_json(const json& j, config::Enum& value)
+ {
+ value = j["value"].get();
+ }
+ };
+}
\ No newline at end of file
diff --git a/cheat-base/src/cheat-base/config/fields/Toggle.h b/cheat-base/src/cheat-base/config/fields/Toggle.h
new file mode 100644
index 0000000..558b8ab
--- /dev/null
+++ b/cheat-base/src/cheat-base/config/fields/Toggle.h
@@ -0,0 +1,83 @@
+#pragma once
+
+#include
+#include
+
+namespace config
+{
+ template
+ class Toggle
+ {
+ public:
+ bool enabled;
+ T value;
+
+ Toggle(const T& value) : enabled(false), value(value) { }
+
+ Toggle(bool enabled) : enabled(enabled), value() { }
+
+ Toggle() : enabled(false), value() { }
+
+ inline operator bool&()
+ {
+ return enabled;
+ }
+
+ inline operator T&()
+ {
+ return value;
+ }
+
+ inline bool operator==(const Toggle& rhs)
+ {
+ return rhs.enabled == enabled && rhs.value == value;
+ }
+ };
+
+ // Okay, close your eyes and don't look at this mess. (Please)
+ template
+ class Field> : public internal::FieldBase>
+ {
+ public:
+ using base = internal::FieldBase>;
+ using base::operator=;
+ using base::base;
+
+ operator bool() const
+ {
+ return base::value();
+ }
+
+ operator T&() const
+ {
+ return base::value().value;
+ }
+ };
+}
+
+namespace nlohmann
+{
+ template
+ struct adl_serializer> {
+ static void to_json(json& j, const config::Toggle& toggle)
+ {
+ j = {
+ { "toggled", toggle.enabled },
+ { "value", config::converters::ToJson(toggle.value) }
+ };
+ }
+
+ static void from_json(const json& j, config::Toggle& toggle)
+ {
+ if (j.is_boolean())
+ {
+ toggle.enabled = j;
+ toggle.value = {};
+ return;
+ }
+
+ toggle.enabled = j["toggled"].get();
+ config::converters::FromJson(toggle.value, j.contains("value") ? j["value"] : j["hotkey"]); // Support previously version
+ }
+ };
+}
\ No newline at end of file
diff --git a/cheat-base/src/cheat-base/config/internal/FieldBase.h b/cheat-base/src/cheat-base/config/internal/FieldBase.h
new file mode 100644
index 0000000..a1dfe90
--- /dev/null
+++ b/cheat-base/src/cheat-base/config/internal/FieldBase.h
@@ -0,0 +1,105 @@
+#pragma once
+
+#include "FieldEntry.h"
+#include "FieldSerialize.h"
+
+namespace config::internal
+{
+ template