From 2a522ca516053662ea1d762688b982b9be0229fe Mon Sep 17 00:00:00 2001 From: Medik Date: Wed, 2 Oct 2024 21:25:37 +0100 Subject: [PATCH] Add seamless Freecam integration --- M2TWEOP Code/M2TWEOP GUI/dataG.h | 2 +- M2TWEOP Code/M2TWEOP GUI/gameRunnerUI.cpp | 32 +- M2TWEOP Code/M2TWEOP GUI/mainUI.cpp | 2 +- M2TWEOP Code/M2TWEOP GUI/managerG.cpp | 5 - M2TWEOP Code/M2TWEOP GUI/modSettingsUI.cpp | 6 - M2TWEOP DataFiles/eopData/config/gameCfg.json | 1 - .../resources/tools/freecam/Freecam.exe | Bin 0 -> 136192 bytes .../resources/tools/freecam/README.txt | 119 ++++++ .../tools/freecam/assemblyLines_Camera.txt | 102 +++++ .../tools/freecam/assemblyLines_Target.txt | 98 +++++ .../tools/freecam/code/AssemblyLine.cpp | 46 +++ .../tools/freecam/code/AssemblyLine.h | 32 ++ .../resources/tools/freecam/code/Freecam.cpp | 379 ++++++++++++++++++ .../resources/tools/freecam/code/MouseDelta.h | 48 +++ .../resources/tools/freecam/code/Options.h | 281 +++++++++++++ .../resources/tools/freecam/code/RW.cpp | 113 ++++++ .../eopData/resources/tools/freecam/code/RW.h | 46 +++ .../resources/tools/freecam/code/codeKiller.h | 55 +++ .../resources/tools/freecam/code/resource.h | 16 + .../eopData/resources/tools/freecam/code/ss.h | 122 ++++++ .../resources/tools/freecam/config.txt | 87 ++++ .../tools/freecam/icon/Freecam Icon.ico | Bin 0 -> 4286 bytes .../releaseInfo/releaseDescription.md | 2 +- 23 files changed, 1567 insertions(+), 27 deletions(-) create mode 100644 M2TWEOP DataFiles/eopData/resources/tools/freecam/Freecam.exe create mode 100644 M2TWEOP DataFiles/eopData/resources/tools/freecam/README.txt create mode 100644 M2TWEOP DataFiles/eopData/resources/tools/freecam/assemblyLines_Camera.txt create mode 100644 M2TWEOP DataFiles/eopData/resources/tools/freecam/assemblyLines_Target.txt create mode 100644 M2TWEOP DataFiles/eopData/resources/tools/freecam/code/AssemblyLine.cpp create mode 100644 M2TWEOP DataFiles/eopData/resources/tools/freecam/code/AssemblyLine.h create mode 100644 M2TWEOP DataFiles/eopData/resources/tools/freecam/code/Freecam.cpp create mode 100644 M2TWEOP DataFiles/eopData/resources/tools/freecam/code/MouseDelta.h create mode 100644 M2TWEOP DataFiles/eopData/resources/tools/freecam/code/Options.h create mode 100644 M2TWEOP DataFiles/eopData/resources/tools/freecam/code/RW.cpp create mode 100644 M2TWEOP DataFiles/eopData/resources/tools/freecam/code/RW.h create mode 100644 M2TWEOP DataFiles/eopData/resources/tools/freecam/code/codeKiller.h create mode 100644 M2TWEOP DataFiles/eopData/resources/tools/freecam/code/resource.h create mode 100644 M2TWEOP DataFiles/eopData/resources/tools/freecam/code/ss.h create mode 100644 M2TWEOP DataFiles/eopData/resources/tools/freecam/config.txt create mode 100644 M2TWEOP DataFiles/eopData/resources/tools/freecam/icon/Freecam Icon.ico diff --git a/M2TWEOP Code/M2TWEOP GUI/dataG.h b/M2TWEOP Code/M2TWEOP GUI/dataG.h index 26788a69..1756cd4a 100644 --- a/M2TWEOP Code/M2TWEOP GUI/dataG.h +++ b/M2TWEOP Code/M2TWEOP GUI/dataG.h @@ -35,7 +35,7 @@ class dataG // Freecam Integration string freecamExeName = "Freecam.exe"; bool freecamIntegration = false; - string freecamFolder = ""; + bool freecamStarted = false; string exeName; string gameArgs; diff --git a/M2TWEOP Code/M2TWEOP GUI/gameRunnerUI.cpp b/M2TWEOP Code/M2TWEOP GUI/gameRunnerUI.cpp index f314c420..f31624e3 100644 --- a/M2TWEOP Code/M2TWEOP GUI/gameRunnerUI.cpp +++ b/M2TWEOP Code/M2TWEOP GUI/gameRunnerUI.cpp @@ -109,6 +109,26 @@ namespace gameRunnerUI exit(0); } } + // Open Freecam if we are using the integration after waiting a bit for the game to start + std::this_thread::sleep_for(std::chrono::seconds(5)); + if (dataG::data.gameData.freecamIntegration == true && dataG::data.gameData.freecamStarted == false) + { + string currentFolder; + string freecamFolder; + string exePath; + helpers::getCurrentPath(currentFolder); + + exePath = currentFolder + ".\\eopData\\resources\\tools\\freecam\\Freecam.exe"; + freecamFolder = currentFolder + ".\\eopData\\resources\\tools\\freecam"; + + std::wstring wideFolderPath = helpers::stringToWstring(freecamFolder); + std::wstring wideExePath = helpers::stringToWstring(exePath); + + LPSTR lpstr = helpers::ConvertWideStringToLPSTR(wideExePath); + LPSTR lpstr_folder = helpers::ConvertWideStringToLPSTR(wideFolderPath); + helpers::openProcess(lpstr, lpstr_folder); + dataG::data.gameData.freecamStarted = true; + } } int drawUI(bool *isOpen) @@ -165,18 +185,6 @@ namespace gameRunnerUI runGameThread, std::ref(startProcess.isRunStarted), std::ref(startProcess.isRunEnded), std::ref(startProcess.isGetResponse), std::ref(startProcess.exePath), std::ref(startProcess.exeArgs), std::ref(startProcess.eopArgs), startProcess.isEopNeeded); thrUrl.detach(); - // Open Freecam if we are using the integration - if (dataG::data.gameData.freecamIntegration == true) - { - std::string exePath = dataG::data.gameData.freecamFolder + "\\Freecam.exe"; - - std::wstring wideFolderPath = helpers::stringToWstring(dataG::data.gameData.freecamFolder); - std::wstring wideExePath = helpers::stringToWstring(exePath); - - LPSTR lpstr = helpers::ConvertWideStringToLPSTR(wideExePath); - LPSTR lpstr_folder = helpers::ConvertWideStringToLPSTR(wideFolderPath); - helpers::openProcess(lpstr, lpstr_folder); - } // Stop the launcher background music if Rich Presence is enabled and the launcher will stay open if (dataG::data.gameData.isDiscordRichPresenceEnabled == true && dataG::data.audio.bkgMusic.isMusicNeeded == true) { diff --git a/M2TWEOP Code/M2TWEOP GUI/mainUI.cpp b/M2TWEOP Code/M2TWEOP GUI/mainUI.cpp index fc69bd05..28b674d9 100644 --- a/M2TWEOP Code/M2TWEOP GUI/mainUI.cpp +++ b/M2TWEOP Code/M2TWEOP GUI/mainUI.cpp @@ -67,7 +67,7 @@ namespace mainUI } return 0; } - else if (childs.isDiscordUIOpen == true) + else if (childs.isDiscordUIOpen == true || dataG::data.gameData.freecamIntegration == true) { gameRunnerUI::maintainGUI(); return 0; diff --git a/M2TWEOP Code/M2TWEOP GUI/managerG.cpp b/M2TWEOP Code/M2TWEOP GUI/managerG.cpp index 5e18a62f..a5e8d463 100644 --- a/M2TWEOP Code/M2TWEOP GUI/managerG.cpp +++ b/M2TWEOP Code/M2TWEOP GUI/managerG.cpp @@ -107,10 +107,6 @@ namespace managerG { getJson(dataG::data.gameData.freecamIntegration, "isFreecamIntegrationEnabled"); } - if (json.contains("freecamFolder")) - { - getJson(dataG::data.gameData.freecamFolder, "freecamFolder"); - } } catch (jsn::json::type_error& e) { @@ -238,7 +234,6 @@ namespace managerG setJson("IsOverrideBattleCamera", dataG::data.gameData.IsOverrideBattleCamera); setJson("isDiscordRichPresenceEnabled", dataG::data.gameData.isDiscordRichPresenceEnabled); setJson("isFreecamIntegrationEnabled", dataG::data.gameData.freecamIntegration); - setJson("freecamFolder", dataG::data.gameData.freecamFolder); writeJsonToFile(fPath, json); json.clear(); diff --git a/M2TWEOP Code/M2TWEOP GUI/modSettingsUI.cpp b/M2TWEOP Code/M2TWEOP GUI/modSettingsUI.cpp index 2c8441a0..fb307bf2 100644 --- a/M2TWEOP Code/M2TWEOP GUI/modSettingsUI.cpp +++ b/M2TWEOP Code/M2TWEOP GUI/modSettingsUI.cpp @@ -245,12 +245,6 @@ namespace modSettingsUI // Freecam Settings ImGui::Checkbox("Freecam Integration", &dataG::data.gameData.freecamIntegration); if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { ImGui::SetTooltip("Automatically start and close the Freecam application when the game is launched");} - - ImGui::Text("Freecam Folder"); - if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { ImGui::SetTooltip("Path to where you have Freecam installed (e.g C:\\Users\\stead\\Documents\\Modding Tools\\Med2 Modding Tools\\Freecam)");} - ImGui::InputText("", &dataG::data.gameData.freecamFolder); - if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { ImGui::SetTooltip("Path to where you have Freecam installed (e.g C:\\Users\\stead\\Documents\\Modding Tools\\Med2 Modding Tools\\Freecam)");} - ImGui::NewLine(); } void drawModSettingsUI(bool* isOpen) diff --git a/M2TWEOP DataFiles/eopData/config/gameCfg.json b/M2TWEOP DataFiles/eopData/config/gameCfg.json index ea763af6..2b4e477e 100644 --- a/M2TWEOP DataFiles/eopData/config/gameCfg.json +++ b/M2TWEOP DataFiles/eopData/config/gameCfg.json @@ -2,7 +2,6 @@ "isBlockLaunchWithoutEop": false, "isDiscordRichPresenceEnabled": false, "isFreecamIntegrationEnabled": false, - "freecamFolder": "", "isDeveloperModeNeeded": true, "isTacticalMapViewerNeeded": true, "isContextMenuNeeded": true, diff --git a/M2TWEOP DataFiles/eopData/resources/tools/freecam/Freecam.exe b/M2TWEOP DataFiles/eopData/resources/tools/freecam/Freecam.exe new file mode 100644 index 0000000000000000000000000000000000000000..207368a0600c6d8ec1726ee7310fb7e02632dc83 GIT binary patch literal 136192 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 literal 0 HcmV?d00001 diff --git a/M2TWEOP DataFiles/eopData/resources/tools/freecam/README.txt b/M2TWEOP DataFiles/eopData/resources/tools/freecam/README.txt new file mode 100644 index 00000000..afd29d76 --- /dev/null +++ b/M2TWEOP DataFiles/eopData/resources/tools/freecam/README.txt @@ -0,0 +1,119 @@ + +FREECAM -Bugis_Duckis + This version of FREECAM most likely not perfect and weirdness may happen! I have included the source code for anyone who wants it. It's probably + not very good, but I'll add that I'm not very accustomed to C++. Use it as you wish, no need to credit! + + +What does this program actually do? + Freecam is for all intents and purposes a hack. It essentially hijacks game variables and writes its own values to them. With version 1.0 it + even edits the machine code of medieval 2 in real-time to stop jitteryness and make the program less CPU demanding. Due to the hacky nature + of the program, IT IS IMPORTANT THAT YOU ONLY RUN THIS FOR THE CORRECT VERSION OF THE GAME, or else it will crash. Have fun! + + +REQUIREMENTS: + Windows 10/11 64bit, other versions of windows have not been tested. + Medieval 2 total war (Definitive edition V1.5.2.0/1.52), as for now gold edition has not been tested. + Microsoft Visual c++ 2022. + + +How to use? + step 1: launch the game. + step 2: Make sure you use "total war" camera style ingame. + step 3: Go into conf.txt and make sure that the controls match those ingame. + step 4: tab out of the game and start Freecam.exe. + step 5: tab into the game and enjoy! + + Recommendations: + Turn off restrict camera. + Unbind alt-s in the ingame settings (It mutes sound). You can do this by binding alt-s to a useless bind such as getting rid of advisor. + + Notes: + If you want to turn off freecam, then it is very important that you do so through the exit bind (END by default) and not the X button + in the corner. Else camera will be frozen in place until you restart the game! + You do not need to place the executable in any specific place or mod folder, it can be run from anywhere! + If you momentarily want vanilla controls, freecam can be paused with the the pause key (INSERT by default) + Save often, crashing though rare may occur. + + +Questions: + Where should i place the program? Anywhere you want! It doesn't not be in any place or modfolder, just make sure that it's next to the files it comes with! + Does this work with the DLC:s? Yes! + Does this work with mods? Yes! + Does this work on multiplayer? Yes! + Does this work with any other total war games? No! + Can I use the code to develop my own projects/fix this one? Absolutely! + + +Known Issues: + Camera going below game when double clicking on units, causing the game to freeze. + There is a small chance of crashing currently (As for now it has only occurred when tabbing). + + +Troubleshooting: + General Issues. + Try starting freecam as administrator. + + +----------------------------------------------------------------------------------------------------------- +Changelog: + 1.2.2: + Additions: + Added an option for controlling the delay when the program gives or releases control of the camera. + 1.2.1: + Fixes: + Fixed an error with the config file. Where the program would mix up the binds for rotate_up and up. + + Changes: + Changed the name of the FORWARD, BACKWARD, etc... to MOVE_FORWARD, MOVE_BACKWARD, etc... + + 1.2: + Fixes: + VERTICAL_SPEED now functions properly. + Fixed a typo in the code that stopped ROTATION_CONTROLS from working. + + Additions: + Added UPS, allows you to set how many updates per second the program will be running at. for users with 60fps+ monitors. You will need to + tweak the variables in config.txt. As UPS simply changes how fast the program will run, camera will therefor appear to be twice as fast if you set it to 120 rather than the default 60 as an example. + Added an Icon for the executable. + + 1.1: + Fixes: + Fixed inconsitency in speed when pressing forward and touching the top of the screen. + Fixed EDGEPAN_SPEED so it has similar speeds as camera sensitivity. + + Changes: + Higher sensitivty values now actually makes the camera more sensitive rather than less. + + Additions: + Added OVERRIDE_MOVEMENT, which makes the movement and scrolling controls provided by freecam optional. + Added untested Gold Edition addresses. I have no way to test these as I dont own gold edition, only use + on custom battles until it has been confirmed that they infact do work. You might otherwise risk + losing progress on your save due to crashes. + Added keybinds for rotating camera. + + + 1.0: + Due to this version being completely seperate, earlier versions will not be included in this changelog as to avoid missinformation. + Due to reports of instability from many users, I have decided to rewrite the entire project. While the general idea remains the same, + some new features have been added to greatly improve stability and decrease CPU usage. The biggest of which is the fact that the + program now will delete lines of machine-code straight from the games process memory while it's running, which stops the game + from writing data to the camera position variables. In earlier versions, the program was essentially having a battle to death over + control of the variables, and for some systems the game was winning that battle. In this version the games hands got chopped off. + This only happens in RAM memory so you don't need to worry about game files, however the game might crash if you're running an + incompatible version of the game. + + Hopefully this version will run better! Sadly I cant make this work for gold edition as I dont own that version, if someone manages + to translate all of the addresses to gold edition then I'll be greatful! Otherwise I'll try to come with some solution in the future. + + Additions: + - Greatly improved stability. + - Decreased CPU usage. + - Smoother camera movements. + - More user friendly config file. + - Custom memory addresses. + - Pausing. + + Not included: + - Control of camera orientation with keyboard (Kinda defeats the point of having freecam). + - Gold Edition support, I will try do something for this in the future. + - Some configuration regarding camera speed. This can be readded if people want it. \ No newline at end of file diff --git a/M2TWEOP DataFiles/eopData/resources/tools/freecam/assemblyLines_Camera.txt b/M2TWEOP DataFiles/eopData/resources/tools/freecam/assemblyLines_Camera.txt new file mode 100644 index 00000000..4e4d9a21 --- /dev/null +++ b/M2TWEOP DataFiles/eopData/resources/tools/freecam/assemblyLines_Camera.txt @@ -0,0 +1,102 @@ +# Just put all the assembly memory addresses that write to camera coords in here, so that the program can remove them. + +#Definitive Edition addresses +#CAMERA_X +0x008F8E10 #Tabbing +0x008F8B50 #Cutscene +0x00E7EF6A #Cutscene2 +0x0094FCDC #Constant / restrictor +0x008FAC69 #restrictor +0x008F8C6C #Movement +0x008F9439 #Movement2 +0x0095B40E #Map click pan +0x0095B7F4 #Unit pan +0x008F8E8B #Unit zoom +0x008F6F29 #??? +0x0095B3B0 #??? +0x0094E996 #??? +0x008F9050 #??? + +#CAMERA_Y +0x008F8E1C #Tabbing +0x008F8B5C #Cutscene +0x00E7EF7F #Cutscene2 +0x0094FCE5 #Constant / restrictor +0x008FAC72 #restrictor +0x008F8C76 #Movement +0x008F9443 #Movement2 +0x0095B429 #Map click pan +0x0095B805 #Unit pan +0x008F8E97 #Unit zoom +0x008F6F39 #??? +0x0095B3BB #??? +0x0094E9DF #??? +0x008F905A #??? + +#CAMERA_Z +0x008F8E16 #Tabbing +0x008F8B56 #Cutscene +0x00E7EF74 #Cutscene2 +0x0094FCE0 #Constant / restrictor RTS +0x0094FD2D #Constant / restrictor2 RTS +0x008FAC6D #restrictor TWC +0x008F8C71 #Movement TWC +0x008F943E #Movement2 TWC +0x0095B41B #Map click pan +0x0095B499 #Map click pan2 +0x0095B7FC #Unit pan +0x008F8E91 #Unit zoom +0x008F6F2F #??? +0x0095B3B5 #??? +0x008F9011 #??? + +# Gold Edition addresses (These have not been tested, they might cause the game to crash!) +# Use a simple regex replace to quickly use these, replace "#0x" with "0x", make sure to remove or comment the previous defintive edition addresses! +#CAMERA_X +#0x00941f60 +#0x00941ca0 +#0x00ec80ba +#0x00998e2c +#0x00943db9 +#0x00941dbc +#0x00942589 +#0x009a455e +#0x009a4944 +#0x00941fdb +#0x00940079 +#0x009a4500 +#0x00997ae6 +#0x009421a0 + +#CAMERA_Y +#0x00941f6c +#0x00941cac +#0x00ec80cf +#0x00998e35 +#0x00943dc2 +#0x00941dc6 +#0x00942593 +#0x009a4579 +#0x009a4955 +#0x00941fe7 +#0x00940089 +#0x009a450b +#0x00997b2f +#0x009421aa + +#CAMERA_Z +#0x00941f66 +#0x00941ca6 +#0x00ec80c4 +#0x00998e30 +#0x00998e7d +#0x00943dbd +#0x00941dc1 +#0x0094258e +#0x009a456b +#0x009a45e9 +#0x009a494c +#0x00941fe1 +#0x0094007f +#0x009a4505 +#0x00942161 \ No newline at end of file diff --git a/M2TWEOP DataFiles/eopData/resources/tools/freecam/assemblyLines_Target.txt b/M2TWEOP DataFiles/eopData/resources/tools/freecam/assemblyLines_Target.txt new file mode 100644 index 00000000..62949b7a --- /dev/null +++ b/M2TWEOP DataFiles/eopData/resources/tools/freecam/assemblyLines_Target.txt @@ -0,0 +1,98 @@ +# Just put all the assembly memory addresses that write to camera coords in here, so that the program can remove them. + +#Definitive Edition addresses +#TARGET_X +0x008F8B78 +0x008F8E38 +0x008F8EB9 +0x00E7EF91 +0x008F6F5F +0x0095B5CB +0x0094FB90 +0x0095B828 +0x0095B828 +0x008F8CB6 +0x008F9480 +0x008F7056 +0x008FAC5B + +#TARGET_Y +0x008F8B84 +0x008F8E44 +0x008F8EC5 +0x00E7EFA6 +0x008F6F6B +0x0095B5D4 +0x0094FB9B +0x0095B831 +0x008F8CC0 +0x008F948A +0x008F7060 +0x008FAC63 + +#TARGET_Z +0x008F8B7E +0x008F8E3E +0x008F8EBF +0x00E7EF9B +0x008F6F65 +0x0095B5CF +0x0094FB95 +0x0094FBCE +0x0094FDCD +0x0095B82C +0x008F8CBB +0x008F9485 +0x008F705B +0x008FAC4E +0x0094E9BC +0x008F9055 + +# Gold Edition addresses (These have not been tested, they might cause the game to crash!) +# Use a simple regex replace to quickly use these, replace "#0x" with "0x" +#CAMERA_X +#0x00941cc8 +#0x00941f88 +#0x00942009 +#0x00ec80e1 +#0x009400af +#0x009a471b +#0x00998ce0 +#0x009a4978 +#0x009a4978 +#0x00941e06 +#0x009425d0 +#0x009401a6 +#0x00943dab + +#TARGET_Y +#0x00941cd4 +#0x00941f94 +#0x00942015 +#0x00ec80f6 +#0x009400bb +#0x009a4724 +#0x00998ceb +#0x009a4981 +#0x00941e10 +#0x009425da +#0x009401b0 +#0x00943db3 + +#TARGET_Z +#0x00941cce +#0x00941f8e +#0x0094200f +#0x00ec80eb +#0x009400b5 +#0x009a471f +#0x00998ce5 +#0x00998d1e +#0x00998f1d +#0x009a497c +#0x00941e0b +#0x009425d5 +#0x009401ab +#0x00943d9e +#0x00997b0c +#0x009421a5 \ No newline at end of file diff --git a/M2TWEOP DataFiles/eopData/resources/tools/freecam/code/AssemblyLine.cpp b/M2TWEOP DataFiles/eopData/resources/tools/freecam/code/AssemblyLine.cpp new file mode 100644 index 00000000..6b2ec27b --- /dev/null +++ b/M2TWEOP DataFiles/eopData/resources/tools/freecam/code/AssemblyLine.cpp @@ -0,0 +1,46 @@ +#include "AssemblyLine.h" + + + + +AssemblyLine::AssemblyLine(LPVOID addr) +{ + this->address = addr; + this->bytes = (int)RW::readMemoryByte(addr) == 243 ? 5 : 3; + //The 243 or F3 byte means that the operatation in total is 5 bytes long. + //Otherwise the operation is 3 bytes long. This works for this program as these are the only possibilities + this->original = new byte[this->bytes]; + + for (int i = 0; i < this->bytes; i++) + original[i] = RW::readMemoryByte((LPVOID)((int)(address)+i)); +} + +void AssemblyLine::nop() +{ + byte nop = (byte)144; + for (int i = 0; i < bytes; i++) + RW::writeMemory((LPVOID)((int)(address)+i), nop); +} + +void AssemblyLine::reset() +{ + for (int i = 0; i < bytes; i++) + RW::writeMemory((LPVOID)((int)(address)+i), original[i]); +} + +void AssemblyLine::printOriginal() +{ + std::cout << "Original = "; + for (int i = 0; i < bytes; i++) + std::cout << (int)original[i] << " "; + std::cout << "\n"; +} + +void AssemblyLine::printCurrent() +{ + std::cout << address << " is = "; + for (int i = 0; i < bytes; i++) + std::cout << (int)RW::readMemoryByte((LPVOID)((int)(address)+i)) << " "; + std::cout << "\n"; +} + diff --git a/M2TWEOP DataFiles/eopData/resources/tools/freecam/code/AssemblyLine.h b/M2TWEOP DataFiles/eopData/resources/tools/freecam/code/AssemblyLine.h new file mode 100644 index 00000000..25c5557e --- /dev/null +++ b/M2TWEOP DataFiles/eopData/resources/tools/freecam/code/AssemblyLine.h @@ -0,0 +1,32 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include "RW.h" + + +class AssemblyLine +{ +private: + LPVOID address; + int bytes; + byte *original; + +public: + AssemblyLine() {}; + AssemblyLine(LPVOID addr); + + void nop(); + + void reset(); + + void printOriginal(); + + void printCurrent(); +}; + diff --git a/M2TWEOP DataFiles/eopData/resources/tools/freecam/code/Freecam.cpp b/M2TWEOP DataFiles/eopData/resources/tools/freecam/code/Freecam.cpp new file mode 100644 index 00000000..4cda9bde --- /dev/null +++ b/M2TWEOP DataFiles/eopData/resources/tools/freecam/code/Freecam.cpp @@ -0,0 +1,379 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "AssemblyLine.h" +#include "codeKiller.h" +#include "Options.h" +#include "MouseDelta.h" + + +#define PI 3.14159265359 + +bool isBattle; +bool codeRemoved = false; +bool relieve_control = true; //This accounts for basically all camera pan related stuff ingame + +float camera_x = 0, camera_y = 0, camera_z = 0, camera_ground_z = 0; +float target_x = 1, target_y = 0, target_z = 0; + +float pitch = 0, yaw = 0; + +CodeKiller cameraCode; +CodeKiller targetCode; + +bool isPressed(unsigned int keycode) +{ + return GetKeyState(keycode) < 0; +} + +// These are the addresses that are only read from, this one should be run all the time +void readPassives() +{ + isBattle = RW::readMemoryInt(Options::BATTLE); +} + +// These are the addresses that are activly written too +void readActives() +{ + camera_x = RW::readMemoryFloat(Options::CAMERA_X); + camera_y = RW::readMemoryFloat(Options::CAMERA_Y); + camera_z = RW::readMemoryFloat(Options::CAMERA_Z); + + target_x = RW::readMemoryFloat(Options::TARGET_X); + target_y = RW::readMemoryFloat(Options::TARGET_Y); + target_z = RW::readMemoryFloat(Options::TARGET_Z); + + float length = sqrt(pow(target_x - camera_x, 2) + pow(target_y - camera_y, 2) + pow(target_z - camera_z, 2)); + pitch = asin((target_z - camera_z) / length); + yaw = atan2((target_y - camera_y) / length, (target_x - camera_x) / length); + if (isnan(pitch)) + pitch = 0; + if (isnan(yaw)) + yaw = 0; + +} + +//Writes to the active addresses, should never be run at the same time as readActives! +void writeActives() +{ + + //std::cout << yaw << " " << pitch << "\n"; + if (Options::OVERRIDE_MOVEMENT) + { + RW::writeMemory(Options::CAMERA_X, camera_x); + RW::writeMemory(Options::CAMERA_Y, camera_y); + RW::writeMemory(Options::CAMERA_Z, camera_z); + } + else + { + camera_x = RW::readMemoryFloat(Options::CAMERA_X); + camera_y = RW::readMemoryFloat(Options::CAMERA_Y); + camera_z = RW::readMemoryFloat(Options::CAMERA_Z); + } + + pitch = max(pitch, -(PI / 2.0f) * 0.9f); + pitch = min(pitch, (PI / 2.0f) * 0.9f); + target_x = (cos(yaw) * cos(pitch) * 1000.0f) + camera_x; + target_y = (sin(yaw) * cos(pitch) * 1000.0f) + camera_y; + target_z = (sin(pitch) * 1000.0f) + camera_z; + + RW::writeMemory(Options::TARGET_X, target_x); + RW::writeMemory(Options::TARGET_Y, target_y); + RW::writeMemory(Options::TARGET_Z, target_z); +} + +void killCode() +{ + if (!codeRemoved) + { + RW::suspend(); + Sleep(Options::RELIEVE_DELAY); + if (Options::OVERRIDE_MOVEMENT) + cameraCode.kill(); + targetCode.kill(); + Sleep(Options::RELIEVE_DELAY); + RW::resume(); + codeRemoved = true; + } +} + +void resurrectCode() +{ + if (codeRemoved) + { + RW::suspend(); + Sleep(Options::RELIEVE_DELAY); + if (Options::OVERRIDE_MOVEMENT) + cameraCode.resurrect(); + targetCode.resurrect(); + Sleep(Options::RELIEVE_DELAY); + RW::resume(); + codeRemoved = false; + } +} + +void relieve(bool set) +{ + relieve_control = set; + if (!set) + killCode(); + else + resurrectCode(); +} + +long scrollPos = 0; +void scroll_event() +{ + while (true) + scrollPos += GetScrollDelta(); +} + + +int main() +{ + std::cout << "Starting FREECAM!\n"; + std::cout << "Scroll event listener established!\n"; + thread se{ scroll_event }; + + Options::init("config.txt"); + RW::init(L"medieval 2"); + Sleep(100); + + cameraCode = CodeKiller(SS::readFile("assemblyLines_Camera.txt")); + std::cout << "successfully read assemblyLines_Camera.txt!\n"; + targetCode = CodeKiller(SS::readFile("assemblyLines_Target.txt")); + std::cout << "successfully read assemblyLines_Target.txt!\n"; + + + + readActives(); + + POINT p = { 0,0 }; + GetCursorPos(&p); + int oldX = p.x, oldY = p.y; + int oldClickX = -50000, oldClickY = -50000; + int oldScrollPos = 0; + long clickTime = 0, oldClickTime; + + bool lButtonHeld = false; + bool paused = false, pauseHeld; + bool fast_press, slow_press; + + + float x_vel = 0; + float y_vel = 0; + float z_vel = 0; + + float pitch_vel = 0; + float yaw_vel = 0; + + unsigned int sleepTime = (1000 / Options::UPS) / 2; + + HWND hwnd = FindWindow(0, L"medieval 2"); + std::cout << "Main loop started!\nFreecam 1.2.1 is now active!\n"; + + while (true) + { + RECT window; + GetWindowRect(hwnd, &window); + Sleep(sleepTime); + readPassives(); + + //Exit + if (isPressed(Options::EXIT)) + { + resurrectCode(); + RW::terminate("USER EXIT"); + } + + //PAUSE CLICKING + if (isPressed(Options::PAUSE)) + { + if (pauseHeld == false) + paused = !paused, std::cout << (paused ? "paused" : "unpaused") << "\n"; + pauseHeld = true; + } + else + pauseHeld = false; + + //Main camera code here. Only runs if you're tabbed in and when there is a battle + if (isBattle && !paused && (window.left > -32000 && window.top >= 0)) + { + GetCursorPos(&p); + + //Variables + fast_press = isPressed(Options::FAST); + slow_press = isPressed(Options::SLOW); + + float horizontal_speed = Options::HORIZONTAL_SPEED; + float vertical_speed = Options::VERTICAL_SPEED; + if (fast_press && slow_press) + horizontal_speed *= Options::BOTH_MOD, vertical_speed *= Options::BOTH_MOD; + else if (fast_press) + horizontal_speed *= Options::FAST_MOD, vertical_speed *= Options::FAST_MOD; + else if (slow_press) + horizontal_speed *= Options::SLOW_MOD, vertical_speed *= Options::SLOW_MOD; + + float x_acc = 0; + float y_acc = 0; + float z_acc = 0; + + float pitch_acc = 0; + float yaw_acc = 0; + + + //################# VANILLA FUNCTION RETENTION #################### + //Double click detection + if (isPressed(VK_LBUTTON)) + { + if (lButtonHeld == false) + { + oldClickTime = clickTime; + clickTime = chrono::duration_cast(chrono::time_point_cast(chrono::high_resolution_clock::now()).time_since_epoch()).count(); + if (clickTime - oldClickTime < GetDoubleClickTime() && abs(oldClickX - p.x) < 2 && abs(oldClickY - p.y) < 2) + relieve(true); + oldClickX = p.x, oldClickY = p.y; + } + lButtonHeld = true; + } + else + lButtonHeld = false; + + + //################# CAMERA POSITION #################### + if (isPressed(Options::FORWARD)) + y_acc += sin(0 + yaw), x_acc += cos(0 + yaw), + relieve(false); + + if (isPressed(Options::BACKWARD)) + y_acc += sin(PI + yaw), x_acc += cos(PI + yaw), + relieve(false); + + if (isPressed(Options::LEFT)) + y_acc += sin(PI / 2 + yaw), x_acc += cos(PI / 2 + yaw), + relieve(false); + + if (isPressed(Options::RIGHT)) + y_acc += sin(3 * (PI / 2) + yaw), x_acc += cos(3 * (PI / 2) + yaw), + relieve(false); + + //Up and down + float diff = (scrollPos - oldScrollPos); + diff = Options::INVERT_SCROLL ? -diff : diff; + int isNegative = (diff != 0 ? (abs(diff) / diff) : 1); + z_vel += (pow(diff, 2) * isNegative) * vertical_speed / 10.0f; //Kinda ugly + + if (isPressed(Options::UP)) + z_acc -= 1, + relieve(false); + + if (isPressed(Options::DOWN)) + z_acc -= -1, + relieve(false); + + + //################# CAMERA ORIENTATION #################### + //FREEEECAAAAAAAAM + float adjusted_sensitivty = (Options::SENSITIVITY * (1 - Options::CAMERA_SMOOTHENING)); + if (isPressed(Options::FREECAM)) + { + float invert = Options::INVERT_MOUSE ? -1.0 : 1.0; + pitch_acc -= (float)(invert * (p.y - oldY)) / (500.0f) * adjusted_sensitivty; + yaw_acc -= (float)(invert * (p.x - oldX)) / (500.0f) * adjusted_sensitivty; + relieve(false); + } + + //Edge Detection + float adjusted_panSpeed = (Options::PAN_PEED * (1 - Options::CAMERA_SMOOTHENING)); + if (Options::ENABLE_EDGEPAN) + { + if (p.x <= window.left + Options::EDGEPAN_MARGIN) + yaw_acc += (3.0f / 100.0f) * adjusted_panSpeed, + relieve(false); + + else if (p.x >= window.right - Options::EDGEPAN_MARGIN) + yaw_acc -= (3.0f / 100.0f) * adjusted_panSpeed, + relieve(false); + + if (p.y <= window.top + Options::EDGEPAN_MARGIN) + y_acc += sin(0 + yaw), x_acc += cos(0 + yaw), + relieve(false); + + else if (p.y >= window.bottom - Options::EDGEPAN_MARGIN) + y_acc += sin(PI + yaw), x_acc += cos(PI + yaw), + relieve(false); + } + + //Keycontrolled orientation + if (Options::ROTATION_CONTROLS) + { + if (isPressed(Options::ROTATE_UP)) + pitch_acc += (3.0f / 100.0f) * adjusted_panSpeed, + relieve(false); + + if (isPressed(Options::ROTATE_DOWN)) + pitch_acc -= (3.0f / 100.0f) * adjusted_panSpeed, + relieve(false); + + if (isPressed(Options::ROTATE_LEFT)) + yaw_acc += (3.0f / 100.0f) * adjusted_panSpeed, + relieve(false); + + if (isPressed(Options::ROTATE_RIGHT)) + yaw_acc -= (3.0f / 100.0f) * adjusted_panSpeed, + relieve(false); + } + + //For unit zooming + if (isPressed(Options::getVK_Key("VK_MBUTTON"))) + relieve(true); + + //################# ETC #################### + float length = sqrt(pow(x_acc, 2) + pow(y_acc, 2) + pow(z_acc, 2)); + if (length == 0) // 0/0 avoidance + length = 1; + x_vel += ((x_acc / length) * (horizontal_speed * (1 - Options::HORIZONTAL_SMOOTHENING))) / 2.0f; //Weird math is to account for smoothening + y_vel += ((y_acc / length) * (horizontal_speed * (1 - Options::HORIZONTAL_SMOOTHENING))) / 2.0f; + z_vel += ((z_acc / length) * (vertical_speed * (1 - Options::VERTICAL_SMOOTHENING))) / 2.0f; + camera_x += x_vel; + camera_y += y_vel; + camera_z += z_vel; + x_vel *= Options::HORIZONTAL_SMOOTHENING; + y_vel *= Options::HORIZONTAL_SMOOTHENING; + z_vel *= Options::VERTICAL_SMOOTHENING; + + pitch_vel += pitch_acc; + yaw_vel += yaw_acc; + pitch += pitch_vel; + yaw += yaw_vel; + pitch_vel *= Options::CAMERA_SMOOTHENING; + yaw_vel *= Options::CAMERA_SMOOTHENING; + + oldX = p.x, oldY = p.y; + + camera_x = min(max(camera_x, -900), 900); + camera_y = min(max(camera_y, -900), 900); + + //Writer / Reader + if (!relieve_control) + writeActives(); + else + readActives(); + } + else // When youre not tabbed in or in a battle + { + relieve(true); + readActives(); + } + + oldScrollPos = scrollPos; + } + std::cout << " done!"; +} diff --git a/M2TWEOP DataFiles/eopData/resources/tools/freecam/code/MouseDelta.h b/M2TWEOP DataFiles/eopData/resources/tools/freecam/code/MouseDelta.h new file mode 100644 index 00000000..94d3d1b9 --- /dev/null +++ b/M2TWEOP DataFiles/eopData/resources/tools/freecam/code/MouseDelta.h @@ -0,0 +1,48 @@ +#pragma once +#include +#include +//Scroll stuff from https://www.youtube.com/watch?v=Kp0fwR1lk8g +HHOOK mh; +int delta = 0; +LRESULT CALLBACK Mouse(int nCode, WPARAM wParam, LPARAM lParam) +{ + if (nCode < 0) + return CallNextHookEx(mh, nCode, wParam, lParam); + + MSLLHOOKSTRUCT* pMouseStruct = (MSLLHOOKSTRUCT*)lParam; + + if (pMouseStruct != NULL) + { + if (wParam == WM_MOUSEWHEEL) + if (HIWORD(pMouseStruct->mouseData) == 120) + delta = 1; + else + delta = -1; + else + delta = 0; + } + + return CallNextHookEx(mh, nCode, wParam, lParam); +} + +int GetScrollDelta() +{ + delta = 0; + if (!(mh = SetWindowsHookExA(WH_MOUSE_LL, Mouse, NULL, 0))) + delta = -404; + + MSG message; + bool peek = true; + long tm = time(0); + + while (peek) + { + PeekMessage(&message, NULL, 0, 0, PM_REMOVE); + if (!delta == 0) + peek = false; + } + + UnhookWindowsHookEx(mh); + + return delta; +} \ No newline at end of file diff --git a/M2TWEOP DataFiles/eopData/resources/tools/freecam/code/Options.h b/M2TWEOP DataFiles/eopData/resources/tools/freecam/code/Options.h new file mode 100644 index 00000000..5215daac --- /dev/null +++ b/M2TWEOP DataFiles/eopData/resources/tools/freecam/code/Options.h @@ -0,0 +1,281 @@ +#pragma once +#include +#include "ss.h" +#include +#include +#include + +namespace Options +{ + std::unordered_map keyFinder; + unsigned int UPS = 60; + unsigned int RELIEVE_DELAY = 2; + + bool OVERRIDE_MOVEMENT; + bool ROTATION_CONTROLS; + + unsigned int EXIT, PAUSE; + + unsigned int FORWARD, BACKWARD, LEFT, RIGHT, UP, DOWN; + unsigned int ROTATE_UP, ROTATE_DOWN, ROTATE_LEFT, ROTATE_RIGHT; + unsigned int FAST, SLOW; + unsigned int FREECAM; + + float SENSITIVITY; + float HORIZONTAL_SMOOTHENING; + float VERTICAL_SMOOTHENING; + float CAMERA_SMOOTHENING; + float HORIZONTAL_SPEED; + float VERTICAL_SPEED; + float PAN_PEED; + + float FAST_MOD, SLOW_MOD, BOTH_MOD; + + bool INVERT_MOUSE; + bool INVERT_SCROLL; + + bool ENABLE_EDGEPAN; + unsigned int EDGEPAN_MARGIN; + + LPVOID BATTLE; + LPVOID CAMERA_X, CAMERA_Y, CAMERA_Z; + LPVOID TARGET_X, TARGET_Y, TARGET_Z; + + std::string getVar(string text, string varName) + { + std::string out = std::regex_replace(text, std::regex("\n+"), " "); + out = std::regex_replace(out, std::regex(".*(\t| |^)" + varName + "="), ""); + out = std::regex_replace(out, std::regex(" .*"), ""); + //std::cout << "\t" << varName << " = " << out << "\n"; + return out; + } + + unsigned int getVK_Key(std::string key) + { + if (key.length() == 1) + return VkKeyScanExA(tolower(key[0]), GetKeyboardLayout(0)); + else if (key.length() > 1) + return keyFinder.at(key); + else + { + std::cerr << "key " << key << " does not exist!\n" ; + throw std::runtime_error("Key is empty"); + } + } + + void init(std::string file_path) + { + { + keyFinder.emplace("VK_LBUTTON", 0x01); + keyFinder.emplace("VK_RBUTTON", 0x02); + keyFinder.emplace("VK_CANCEL", 0x03); + keyFinder.emplace("VK_MBUTTON", 0x04); + keyFinder.emplace("VK_XBUTTON1", 0x05); + keyFinder.emplace("VK_XBUTTON2", 0x06); + keyFinder.emplace("VK_BACK", 0x08); + keyFinder.emplace("VK_TAB", 0x09); + keyFinder.emplace("VK_CLEAR", 0x0C); + keyFinder.emplace("VK_RETURN", 0x0D); + keyFinder.emplace("VK_SHIFT", 0x10); + keyFinder.emplace("VK_CONTROL", 0x11); + keyFinder.emplace("VK_MENU", 0x12); + keyFinder.emplace("VK_PAUSE", 0x13); + keyFinder.emplace("VK_CAPITAL", 0x14); + keyFinder.emplace("VK_KANA", 0x15); + keyFinder.emplace("VK_HANGUEL", 0x15); + keyFinder.emplace("VK_HANGUL", 0x15); + keyFinder.emplace("VK_IME_ON", 0x16); + keyFinder.emplace("VK_JUNJA", 0x17); + keyFinder.emplace("VK_FINAL", 0x18); + keyFinder.emplace("VK_HANJA", 0x19); + keyFinder.emplace("VK_KANJI", 0x19); + keyFinder.emplace("VK_IME_OFF", 0x1A); + keyFinder.emplace("VK_ESCAPE", 0x1B); + keyFinder.emplace("VK_CONVERT", 0x1C); + keyFinder.emplace("VK_NONCONVERT", 0x1D); + keyFinder.emplace("VK_ACCEPT", 0x1E); + keyFinder.emplace("VK_MODECHANGE", 0x1F); + keyFinder.emplace("VK_SPACE", 0x20); + keyFinder.emplace("VK_PRIOR", 0x21); + keyFinder.emplace("VK_NEXT", 0x22); + keyFinder.emplace("VK_END", 0x23); + keyFinder.emplace("VK_HOME", 0x24); + keyFinder.emplace("VK_LEFT", 0x25); + keyFinder.emplace("VK_UP", 0x26); + keyFinder.emplace("VK_RIGHT", 0x27); + keyFinder.emplace("VK_DOWN", 0x28); + keyFinder.emplace("VK_SELECT", 0x29); + keyFinder.emplace("VK_PRINT", 0x2A); + keyFinder.emplace("VK_EXECUTE", 0x2B); + keyFinder.emplace("VK_SNAPSHOT", 0x2C); + keyFinder.emplace("VK_INSERT", 0x2D); + keyFinder.emplace("VK_DELETE", 0x2E); + keyFinder.emplace("VK_HELP", 0x2F); + keyFinder.emplace("VK_LWIN", 0x5B); + keyFinder.emplace("VK_RWIN", 0x5C); + keyFinder.emplace("VK_APPS", 0x5D); + keyFinder.emplace("VK_SLEEP", 0x5F); + keyFinder.emplace("VK_NUMPAD0", 0x60); + keyFinder.emplace("VK_NUMPAD1", 0x61); + keyFinder.emplace("VK_NUMPAD2", 0x62); + keyFinder.emplace("VK_NUMPAD3", 0x63); + keyFinder.emplace("VK_NUMPAD4", 0x64); + keyFinder.emplace("VK_NUMPAD5", 0x65); + keyFinder.emplace("VK_NUMPAD6", 0x66); + keyFinder.emplace("VK_NUMPAD7", 0x67); + keyFinder.emplace("VK_NUMPAD8", 0x68); + keyFinder.emplace("VK_NUMPAD9", 0x69); + keyFinder.emplace("VK_MULTIPLY", 0x6A); + keyFinder.emplace("VK_ADD", 0x6B); + keyFinder.emplace("VK_SEPARATOR", 0x6C); + keyFinder.emplace("VK_SUBTRACT", 0x6D); + keyFinder.emplace("VK_DECIMAL", 0x6E); + keyFinder.emplace("VK_DIVIDE", 0x6F); + keyFinder.emplace("VK_F1", 0x70); + keyFinder.emplace("VK_F2", 0x71); + keyFinder.emplace("VK_F3", 0x72); + keyFinder.emplace("VK_F4", 0x73); + keyFinder.emplace("VK_F5", 0x74); + keyFinder.emplace("VK_F6", 0x75); + keyFinder.emplace("VK_F7", 0x76); + keyFinder.emplace("VK_F8", 0x77); + keyFinder.emplace("VK_F9", 0x78); + keyFinder.emplace("VK_F10", 0x79); + keyFinder.emplace("VK_F11", 0x7A); + keyFinder.emplace("VK_F12", 0x7B); + keyFinder.emplace("VK_F13", 0x7C); + keyFinder.emplace("VK_F14", 0x7D); + keyFinder.emplace("VK_F15", 0x7E); + keyFinder.emplace("VK_F16", 0x7F); + keyFinder.emplace("VK_F17", 0x80); + keyFinder.emplace("VK_F18", 0x81); + keyFinder.emplace("VK_F19", 0x82); + keyFinder.emplace("VK_F20", 0x83); + keyFinder.emplace("VK_F21", 0x84); + keyFinder.emplace("VK_F22", 0x85); + keyFinder.emplace("VK_F23", 0x86); + keyFinder.emplace("VK_F24", 0x87); + keyFinder.emplace("VK_NUMLOCK", 0x90); + keyFinder.emplace("VK_SCROLL", 0x91); + keyFinder.emplace("VK_LSHIFT", 0xA0); + keyFinder.emplace("VK_RSHIFT", 0xA1); + keyFinder.emplace("VK_LCONTROL", 0xA2); + keyFinder.emplace("VK_RCONTROL", 0xA3); + keyFinder.emplace("VK_LMENU", 0xA4); + keyFinder.emplace("VK_RMENU", 0xA5); + keyFinder.emplace("VK_BROWSER_BACK", 0xA6); + keyFinder.emplace("VK_BROWSER_FORWARD", 0xA7); + keyFinder.emplace("VK_BROWSER_REFRESH", 0xA8); + keyFinder.emplace("VK_BROWSER_STOP", 0xA9); + keyFinder.emplace("VK_BROWSER_SEARCH", 0xAA); + keyFinder.emplace("VK_BROWSER_FAVORITES", 0xAB); + keyFinder.emplace("VK_BROWSER_HOME", 0xAC); + keyFinder.emplace("VK_VOLUME_MUTE", 0xAD); + keyFinder.emplace("VK_VOLUME_DOWN", 0xAE); + keyFinder.emplace("VK_VOLUME_UP", 0xAF); + keyFinder.emplace("VK_MEDIA_NEXT_TRACK", 0xB0); + keyFinder.emplace("VK_MEDIA_PREV_TRACK", 0xB1); + keyFinder.emplace("VK_MEDIA_STOP", 0xB2); + keyFinder.emplace("VK_MEDIA_PLAY_PAUSE", 0xB3); + keyFinder.emplace("VK_LAUNCH_MAIL", 0xB4); + keyFinder.emplace("VK_LAUNCH_MEDIA_SELECT", 0xB5); + keyFinder.emplace("VK_LAUNCH_APP1", 0xB6); + keyFinder.emplace("VK_LAUNCH_APP2", 0xB7); + keyFinder.emplace("VK_OEM_1", 0xBA); + keyFinder.emplace("VK_OEM_PLUS", 0xBB); + keyFinder.emplace("VK_OEM_COMMA", 0xBC); + keyFinder.emplace("VK_OEM_MINUS", 0xBD); + keyFinder.emplace("VK_OEM_PERIOD", 0xBE); + keyFinder.emplace("VK_OEM_2", 0xBF); + keyFinder.emplace("VK_OEM_3", 0xC0); + keyFinder.emplace("VK_OEM_4", 0xDB); + keyFinder.emplace("VK_OEM_5", 0xDC); + keyFinder.emplace("VK_OEM_6", 0xDD); + keyFinder.emplace("VK_OEM_7", 0xDE); + keyFinder.emplace("VK_OEM_8", 0xDF); + keyFinder.emplace("VK_OEM_102", 0xE2); + keyFinder.emplace("VK_PROCESSKEY", 0xE5); + keyFinder.emplace("VK_PACKET", 0xE7); + keyFinder.emplace("VK_ATTN", 0xF6); + keyFinder.emplace("VK_CRSEL", 0xF7); + keyFinder.emplace("VK_EXSEL", 0xF8); + keyFinder.emplace("VK_EREOF", 0xF9); + keyFinder.emplace("VK_PLAY", 0xFA); + keyFinder.emplace("VK_ZOOM", 0xFB); + keyFinder.emplace("VK_PA1", 0xFD); + keyFinder.emplace("VK_OEM_CLEAR", 0xFE); + } + + try + { + std::string contents = SS::readFile(file_path); + + contents = std::regex_replace(contents, std::regex("#.*"), ""); + contents = std::regex_replace(contents, std::regex("\n+"), "\n"); + contents = std::regex_replace(contents, std::regex("^\n+"), ""); + contents = std::regex_replace(contents, std::regex("\n+$"), ""); + contents = std::regex_replace(contents, std::regex(" "), ""); + contents = std::regex_replace(contents, std::regex("\t"), ""); + + FORWARD = getVK_Key(getVar(contents, "MOVE_FORWARD")); + LEFT = getVK_Key(getVar(contents, "MOVE_LEFT")); + BACKWARD= getVK_Key(getVar(contents, "MOVE_BACKWARD")); + RIGHT = getVK_Key(getVar(contents, "MOVE_RIGHT")); + UP = getVK_Key(getVar(contents, "MOVE_UP")); + DOWN = getVK_Key(getVar(contents, "MOVE_DOWN")); + ROTATE_LEFT = getVK_Key(getVar(contents, "ROTATE_LEFT")); + ROTATE_RIGHT = getVK_Key(getVar(contents, "ROTATE_RIGHT")); + ROTATE_UP = getVK_Key(getVar(contents, "ROTATE_UP")); + ROTATE_DOWN = getVK_Key(getVar(contents, "ROTATE_DOWN")); + + EXIT = getVK_Key(getVar(contents, "EXIT")); + PAUSE = getVK_Key(getVar(contents, "PAUSE")); + FAST = getVK_Key(getVar(contents, "FAST")); + SLOW = getVK_Key(getVar(contents, "SLOW")); + FREECAM = getVK_Key(getVar(contents, "FREECAM")); + + SENSITIVITY = stof(getVar(contents, "SENSITIVITY")); + HORIZONTAL_SMOOTHENING = max(min(SS::percentageToFloat(getVar(contents, "HORIZONTAL_SMOOTHENING")),0.99999999f),0); + VERTICAL_SMOOTHENING = max(min(SS::percentageToFloat(getVar(contents, "VERTICAL_SMOOTHENING")), 0.99999999f), 0); + CAMERA_SMOOTHENING = max(min(SS::percentageToFloat(getVar(contents, "CAMERA_SMOOTHENING")), 0.99999999f), 0); + HORIZONTAL_SPEED = stof(getVar(contents, "HORIZONTAL_SPEED")); + VERTICAL_SPEED = stof(getVar(contents, "VERTICAL_SPEED")); + PAN_PEED = stof(getVar(contents, "PAN_PEED")); + + FAST_MOD = SS::percentageToFloat(getVar(contents, "FAST_MOD")); + SLOW_MOD = SS::percentageToFloat(getVar(contents, "SLOW_MOD")); + BOTH_MOD = SS::percentageToFloat(getVar(contents, "BOTH_MOD")); + + INVERT_MOUSE = getVar(contents, "INVERT_MOUSE") == "true" ? true : false; + INVERT_SCROLL = getVar(contents, "INVERT_SCROLL") == "true" ? true : false; + ENABLE_EDGEPAN = getVar(contents, "ENABLE_EDGEPAN") == "true" ? true : false; + ROTATION_CONTROLS= getVar(contents, "ROTATION_CONTROLS") == "true" ? true : false; + + EDGEPAN_MARGIN = stoi(getVar(contents, "EDGEPAN_MARGIN")); + + + OVERRIDE_MOVEMENT = getVar(contents, "OVERRIDE_MOVEMENT") == "true" ? true : false; + + //Addresses + BATTLE = (LPVOID)std::stoul(getVar(contents, "BATTLE"), nullptr, 16); + CAMERA_X = (LPVOID)std::stoul(getVar(contents, "CAMERA_X"), nullptr, 16); + CAMERA_Y = (LPVOID)std::stoul(getVar(contents, "CAMERA_Y"), nullptr, 16); + CAMERA_Z = (LPVOID)std::stoul(getVar(contents, "CAMERA_Z"), nullptr, 16); + TARGET_X = (LPVOID)std::stoul(getVar(contents, "TARGET_X"), nullptr, 16); + TARGET_Y = (LPVOID)std::stoul(getVar(contents, "TARGET_Y"), nullptr, 16); + TARGET_Z = (LPVOID)std::stoul(getVar(contents, "TARGET_Z"), nullptr, 16); + + UPS = std::stoi(getVar(contents, "UPS")); + RELIEVE_DELAY = std::stoi(getVar(contents, "RELIEVE_DELAY")); + + std::cout << "Options in " + file_path + " successfully read!\n"; + } + catch (...) + { + std::cerr << "Failed to read options " + file_path + "\n"; + Sleep(6000); + throw std::runtime_error("Failed to read options from " + file_path); + exit(0); + } + } +} \ No newline at end of file diff --git a/M2TWEOP DataFiles/eopData/resources/tools/freecam/code/RW.cpp b/M2TWEOP DataFiles/eopData/resources/tools/freecam/code/RW.cpp new file mode 100644 index 00000000..7b3659af --- /dev/null +++ b/M2TWEOP DataFiles/eopData/resources/tools/freecam/code/RW.cpp @@ -0,0 +1,113 @@ +#include "RW.h" + +void RW::terminate(std::string reason) +{ + CloseHandle(hProc); + for (int i = 0; i < hThread_length; i++) + CloseHandle(hThreads[i]); + std::cout << reason << "\n TERMINATING IN 6 SECONDS!"; + Sleep(6000); + exit(0); +} + +//READERS +byte RW::readMemoryByte(LPVOID address) +{ + byte var; + ReadProcessMemory(hProc, address, &var, (DWORD)sizeof(var), NULL); + return var; +} + +float RW::readMemoryFloat(LPVOID address) +{ + float var; + ReadProcessMemory(hProc, address, &var, (DWORD)sizeof(var), NULL); + return var; +} + +int RW::readMemoryInt(LPVOID address) +{ + int var; + ReadProcessMemory(hProc, address, &var, (DWORD)sizeof(var), NULL); + return var; +} + +bool RW::readMemoryBool(LPVOID address) +{ + bool var; + ReadProcessMemory(hProc, address, &var, (DWORD)sizeof(var), NULL); + return var; +} + +//WRITERS +bool RW::writeMemory(LPVOID address, byte data) +{ + return WriteProcessMemory(hProc, address, &data, (DWORD)sizeof(data), NULL); +} +bool RW::writeMemory(LPVOID address, float data) +{ + return WriteProcessMemory(hProc, address, &data, (DWORD)sizeof(data), NULL); +} +bool RW::writeMemory(LPVOID address, int data) +{ + return WriteProcessMemory(hProc, address, &data, (DWORD)sizeof(data), NULL); +} + +void RW::init(LPCWSTR proc) +{ + //hwnd = FindWindow(0, L"medieval 2"); + hwnd = FindWindow(0, proc); + if (hwnd == 0) + terminate("Cannot find window, is the game currently on?"); + else + { + std::cout << proc << " Window found!\n"; + GetWindowThreadProcessId(hwnd, &pId); + hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pId); + + if (!hProc) + terminate("Cannot open process"); + else + std::cout << "Process found!\n"; + + //Find game thread so it can be paused + HANDLE hThreadSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); + + THREADENTRY32 threadEntry; + threadEntry.dwSize = sizeof(THREADENTRY32); + + std::list handles{}; + Thread32First(hThreadSnapshot, &threadEntry); + do + { + if (threadEntry.th32OwnerProcessID == pId) + { + HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, + threadEntry.th32ThreadID); + handles.push_front(hThread); + hThread_length++; + } + } while (Thread32Next(hThreadSnapshot, &threadEntry)); + std::cout << "Process threads found!\n"; + + hThreads = new HANDLE[handles.size()]; + for (int i = 0; i < hThread_length; i++) + hThreads[i] = handles.front(), handles.pop_front(); + + std::cout << "RW init complete!\n"; + + CloseHandle(hThreadSnapshot); + } +} + +void RW::suspend() +{ + for (int i = 0; i < hThread_length; i++) + SuspendThread(hThreads[i]); +} + +void RW::resume() +{ + for (int i = 0; i < hThread_length; i++) + ResumeThread(hThreads[i]); +} diff --git a/M2TWEOP DataFiles/eopData/resources/tools/freecam/code/RW.h b/M2TWEOP DataFiles/eopData/resources/tools/freecam/code/RW.h new file mode 100644 index 00000000..8478e14c --- /dev/null +++ b/M2TWEOP DataFiles/eopData/resources/tools/freecam/code/RW.h @@ -0,0 +1,46 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include "assemblyLine.h" + +static HWND hwnd; +static DWORD pId; +static HANDLE hProc; +static HANDLE *hThreads; +static unsigned int hThread_length; + +class RW +{ +public: + // INIT + static void init(LPCWSTR proc); + + static void terminate(std::string reason); + + //readers and writers + static byte readMemoryByte(LPVOID address); + static float readMemoryFloat(LPVOID address); + static int readMemoryInt(LPVOID address); + static bool readMemoryBool(LPVOID address); + + static bool writeMemory(LPVOID address, byte data); + static bool writeMemory(LPVOID address, float data); + static bool writeMemory(LPVOID address, int data); + + static void suspend(); + static void resume(); + + //Preferable, but does not work :( so dont use + //template + //static T readMemory(LPVOID address); + //template + //static bool writeMemory(LPVOID address, G data); + + + +}; \ No newline at end of file diff --git a/M2TWEOP DataFiles/eopData/resources/tools/freecam/code/codeKiller.h b/M2TWEOP DataFiles/eopData/resources/tools/freecam/code/codeKiller.h new file mode 100644 index 00000000..7d35ef1f --- /dev/null +++ b/M2TWEOP DataFiles/eopData/resources/tools/freecam/code/codeKiller.h @@ -0,0 +1,55 @@ +#pragma once +#include +#include +#include "ss.h" +#include "AssemblyLine.h" + + +class CodeKiller +{ + AssemblyLine* code; + int length; + +public: + + CodeKiller() {}; + CodeKiller(std::string contents) + { + try + { + std::string fileContents = contents; + fileContents = std::regex_replace(fileContents, std::regex("#.*"), ""); + fileContents = std::regex_replace(fileContents, std::regex("\n+"), "\n"); + fileContents = std::regex_replace(fileContents, std::regex("^\n+"), ""); + fileContents = std::regex_replace(fileContents, std::regex("\n+$"), ""); + fileContents = std::regex_replace(fileContents, std::regex(" +"), ""); + fileContents = std::regex_replace(fileContents, std::regex("\t+"), ""); + std::string* s = SS::split(fileContents, '\n'); + + length = SS::count(fileContents, '\n') + 1; + code = new AssemblyLine[length]; + for (int i = 0; i < length; i++) + code[i] = AssemblyLine((LPVOID)std::stoul(s[i], nullptr, 16)); + + } + catch (...) + { + std::cerr <<"Failed to generate code killer " + contents + "\n"; + throw std::runtime_error("Failed to generate code killer " + contents); + Sleep(6000); + exit(0); + } + } + + void kill() + { + for (int i = 0; i < length; i++) + code[i].nop(); + } + + void resurrect() + { + for (int i = 0; i < length; i++) + code[i].reset(); + } +}; \ No newline at end of file diff --git a/M2TWEOP DataFiles/eopData/resources/tools/freecam/code/resource.h b/M2TWEOP DataFiles/eopData/resources/tools/freecam/code/resource.h new file mode 100644 index 00000000..63c040da --- /dev/null +++ b/M2TWEOP DataFiles/eopData/resources/tools/freecam/code/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Freecam2-2.rc +// +#define IDI_ICON1 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/M2TWEOP DataFiles/eopData/resources/tools/freecam/code/ss.h b/M2TWEOP DataFiles/eopData/resources/tools/freecam/code/ss.h new file mode 100644 index 00000000..32c57739 --- /dev/null +++ b/M2TWEOP DataFiles/eopData/resources/tools/freecam/code/ss.h @@ -0,0 +1,122 @@ +#pragma once +#include +#include +#include +#include +using namespace std; + +//String stuff (ss) I am aware that the name needs replacing. +namespace SS +{ + string removeCharacters(string str, char c) + { + string result; + for (size_t i = 0; i < str.size(); i++) + { + char currentChar = str[i]; + if (currentChar != c) + result += currentChar; + } + return result; + } + + string getLineWith(list text, string line) + { + for (string str : text) + if (str.find(line) != std::string::npos) + return str; + return ""; + } + + int find(string text, string marker) // Made due to some confusion :/ + { + for (int i = 0; i < text.length(); i++) + { + bool full_match = true; + if (text.at(i) == marker.at(0)) + { + for (int j = 1; j < marker.length(); j++) + if (text.at(i + j) != marker.at(j)) + { + full_match = false; + break; + } + } + else + full_match = false; + + if (full_match) + return i; + } + return -1; + } + + string getRightOfReg(string line, string seperator) + { + string out = std::regex_replace(line, std::regex(".*"+seperator), ""); + return line.substr(find(line, seperator) + seperator.length(), line.size()); + } + + string getRightOf(string line, string seperator) + { + return line.substr(find(line, seperator) + seperator.length(), line.size()); + } + + string getLeftOf(string line, string seperator) + { + return line.substr(0, find(line, seperator)); + } + + string readFile(string f) + { + ifstream file; + file.open(f); + + if (file.is_open()) + { + string line; + string text; + getline(file, text); + while (getline(file, line)) + text += "\n" + line; + + return text; + } + else + { + std::cerr << "Failed to read file " + f + "\n"; + throw std::runtime_error("Failed to open file " + f); + Sleep(6000); + exit(0); + } + } + + float percentageToFloat(string in) + { + float out = -1.0f; + if (find(in, "%") != -1) + out = stof(std::regex_replace(in, std::regex("\%"), "")) / 100.0f; + else + out = stof(in); + + return out; + } + + int count(string text, char symbol) + { + return (int)(std::count(text.begin(), text.end(), symbol)); + } + + string* split(string in, char seperator) + { + string* out = new string[count(in, seperator) + 1]; + int i = 0, s = 0; + string comp = in + ""; + while (i < count(in, seperator) + 1) + { + out[i++] = comp.substr(0, comp.find_first_of(seperator)); + comp = comp.substr(comp.find_first_of(seperator) + 1, comp.length()); + } + return out; + } +} \ No newline at end of file diff --git a/M2TWEOP DataFiles/eopData/resources/tools/freecam/config.txt b/M2TWEOP DataFiles/eopData/resources/tools/freecam/config.txt new file mode 100644 index 00000000..1b54841a --- /dev/null +++ b/M2TWEOP DataFiles/eopData/resources/tools/freecam/config.txt @@ -0,0 +1,87 @@ +# Welcome to the freecam configuration file. +# Advice: Do not use the middle mouse button for FREECAM, the game uses middle mouse button for zooming to units and that key is not rebindable sadly. +# VK_XBUTTON1 Also suffers from a similar issue. +# VK key codes can be found at - https://cherrytree.at/misc/vk.htm, Otherwise look up a VK_keycode map/list for your language. +# It is not neccesary but it is highly reccommended that the controls below match the controls ingame. +# Binds can either be a VK keycode or simply the character you desire. + + +UPS = 60 # Updates per second, If you play with a fps other than 60, you might want to change this to whatever fps you have. + # Camera Movements will appear to be 60fps even if game is running at 120 as an example. + # Other settings such as camera speed, smoothening and such do not scale with this value, you will need to tweak + # them if you change the UPS. + +RELIEVE_DELAY = 1 # How long the game is paused in ms when the program releases or gives back control over the camera. Higher values will cause + # a noticable stutter when double clicking, lower values might increase risk of crashing on some systems. + +#General Bindings and freecam +PAUSE = VK_INSERT +EXIT = VK_END +FAST = VK_SHIFT +SLOW = VK_MENU # VK_MENU is left-alt + +FAST_MOD = 250.0% # How FAST,SLOW or both pressed will affect the speed of the camera. +SLOW_MOD = 25.0% +BOTH_MOD = 10.0% + + +FREECAM = VK_XBUTTON2 # The key that makes mouse control control the camera orientation. + +INVERT_MOUSE = false +INVERT_SCROLL = true + +CAMERA_SMOOTHENING = 50.0% +SENSITIVITY = 1.0 + +PAN_PEED = 1.0 # Effects both edgepanning aswell as rotation controls +ENABLE_EDGEPAN = true + EDGEPAN_MARGIN = 5 # Pixel distance that triggers edgepanning + +ROTATION_CONTROLS = false + ROTATE_UP = r + ROTATE_DOWN = f + ROTATE_LEFT = q + ROTATE_RIGHT = e + + +# OVERRIDE_MOVEMENT gives control over the of the camera position to freecam. This allows for infinite zooming, more consistent camera speeds etc. +# One issue currently is that you can go below ground. +OVERRIDE_MOVEMENT = true + + HORIZONTAL_SMOOTHENING = 90.0% + VERTICAL_SMOOTHENING = 90.0% # Effects scrolling + + HORIZONTAL_SPEED = 1.0 + VERTICAL_SPEED = 1.0 + + MOVE_FORWARD = w + MOVE_LEFT = a + MOVE_BACKWARD = s + MOVE_RIGHT = d + MOVE_UP = VK_DOWN #Down arrow key + MOVE_DOWN = VK_UP #Up arrow key + + + + + + +################### ADDRESSES BELOW, DONT TOUCH UNLESS YOU KNOW WHAT YOU'RE DOING ################### + +#Definitive edition addresses +BATTLE = 0x0193D683 +TARGET_X = 0x0193D5DC +TARGET_Y = 0x0193D5E4 +TARGET_Z = 0x0193D5E0 +CAMERA_X = 0x0193D598 +CAMERA_Y = 0x0193D5A0 +CAMERA_Z = 0x0193D59C + +#Gold edition addresses (Make sure to change the addresses in assemblyLines_Camera and assemblyLines_Target) also! +#BATTLE = 0x019867D3 +#TARGET_X = 0x0198672C +#TARGET_Y = 0x01986734 +#TARGET_Z = 0x01986730 +#CAMERA_X = 0x019866E8 +#CAMERA_Y = 0x019866F0 +#CAMERA_Z = 0x019866EC \ No newline at end of file diff --git a/M2TWEOP DataFiles/eopData/resources/tools/freecam/icon/Freecam Icon.ico b/M2TWEOP DataFiles/eopData/resources/tools/freecam/icon/Freecam Icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..e904b8bee8288c1335433c64c55ae852bdfc76de GIT binary patch literal 4286 zcmc(iyH6EC6o&@^=|m|c#wZvqj3~Ymkw#3=7&Y=Y_-Zwx4V{Y`=`83?u8D;e6vh}W z$W{IVP3SBzu~1P#48B6Rejn_b&3c#TMGpDcxo6It-|RegXG5ssuemw+KRntP!sZad zZon0egZ^2gZfENpbB<;GuccKZv_M;oWqsDIhMAY1hi({v5qJ+1F_!gNXN<9x=u0(u zjzR%G!Ebm2kD(uWV=U{l&KP40U|Vx6#~Nr9!3X#Z1vmpv?>fkuKI@DrfNg9Wfw`7q zI|v?x85n~;I0q-;6r9e%I%ABr%||eYxy-p3Gj0eb!SS4iAMk65{3QMz+=p*q&V~Bm zIN8@JxB_>ev;szHy9=&@InA9n7tUVY{iZmVInC{QQY&pW{3yUUbU{?$-R%{_1gT$Aq0tVK9hL*Kk=s=e0wYd!vJz5dpE|FHyS zp9STfKRwh{oaV+aPUS{7654tZRjG&)+Th55u7t*XlFK(|=A~ zTO4bfy|5k