├── reversing ├── .gitignore └── README.md ├── SCXLauncher ├── icon.ico ├── logo.bmp ├── Resource.rc ├── resource.h ├── Patcher.h ├── PEINFO.h ├── FileVersion.h ├── Message.h ├── DetourMaster.h ├── SCXLoader.h ├── GameData.h ├── Registry.h ├── notes.txt ├── GameVersionInfo.h ├── Registry.cpp ├── SCXLauncher.vcxproj.filters ├── FileVersion.cpp ├── Instructions.h ├── Settings.h ├── Patcher.cpp ├── GameVersion.h ├── SCXLauncher.vcxproj ├── SCXLauncher.cpp ├── SCXLoader.cpp └── GameData.cpp ├── SimCopterX.sln ├── .gitattributes └── .gitignore /reversing/.gitignore: -------------------------------------------------------------------------------- 1 | *.dll 2 | *.exe 3 | -------------------------------------------------------------------------------- /SCXLauncher/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alekasm/SimCopterX/HEAD/SCXLauncher/icon.ico -------------------------------------------------------------------------------- /SCXLauncher/logo.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alekasm/SimCopterX/HEAD/SCXLauncher/logo.bmp -------------------------------------------------------------------------------- /SCXLauncher/Resource.rc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alekasm/SimCopterX/HEAD/SCXLauncher/Resource.rc -------------------------------------------------------------------------------- /reversing/README.md: -------------------------------------------------------------------------------- 1 | # SimCopter Reverse Engineering 2 | 3 | IDA Freeware 7.7, SimCopter Classics Version 1.0.1.4. 4 | -------------------------------------------------------------------------------- /SCXLauncher/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by Resource.rc 4 | // 5 | #define IDB_BITMAP1 109 6 | #define IDI_ICON1 110 7 | 8 | // Next default values for new objects 9 | // 10 | #ifdef APSTUDIO_INVOKED 11 | #ifndef APSTUDIO_READONLY_SYMBOLS 12 | #define _APS_NEXT_RESOURCE_VALUE 111 13 | #define _APS_NEXT_COMMAND_VALUE 40001 14 | #define _APS_NEXT_CONTROL_VALUE 1001 15 | #define _APS_NEXT_SYMED_VALUE 101 16 | #endif 17 | #endif 18 | -------------------------------------------------------------------------------- /SCXLauncher/Patcher.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "PEINFO.h" 3 | #include "DetourMaster.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | 13 | class Patcher 14 | { 15 | public: 16 | static bool CreateDetourSection(const char* filepath, PEINFO* info); 17 | static bool Patch(PEINFO, std::vector, std::string exe_fname); 18 | static DWORD GetFileOffset(PEINFO info, DWORD address); 19 | 20 | private: 21 | static DWORD align(DWORD size, DWORD align, DWORD addr); 22 | }; 23 | -------------------------------------------------------------------------------- /SCXLauncher/PEINFO.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | static const DWORD WIN32_PE_ENTRY = 0x400000; 7 | struct PEINFO 8 | { 9 | struct PEDATA { 10 | DWORD RealVirtualAddress; 11 | DWORD VirtualAddress; 12 | DWORD RawDataPointer; 13 | DWORD VirtualSize; 14 | }; 15 | 16 | DWORD GetDetourVirtualAddress(DWORD offset = 0x0) 17 | { 18 | OutputDebugString(std::string("Detour Virtual Address: " + std::to_string(data_map[".detour"].RealVirtualAddress) + "\n").c_str()); 19 | return data_map[".detour"].RealVirtualAddress + offset; 20 | } 21 | 22 | std::map data_map; 23 | }; -------------------------------------------------------------------------------- /SCXLauncher/FileVersion.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "Message.h" 9 | 10 | typedef DWORD(CALLBACK* GetFileVersionInfoSizeExA2)(DWORD, LPCSTR, LPDWORD); 11 | typedef DWORD(CALLBACK* GetFileVersionInfoExA2)(DWORD, LPCSTR, DWORD, DWORD, LPVOID); 12 | typedef DWORD(CALLBACK* VerQueryValueA2)(LPCVOID, LPCSTR, LPVOID*, PUINT); 13 | typedef std::map StringFileInfoMap; 14 | 15 | class FileVersion 16 | { 17 | public: 18 | MessageValue GetSCFileVersionInfo(LPCSTR, StringFileInfoMap&); 19 | 20 | private: 21 | BOOL Initialize(); 22 | GetFileVersionInfoSizeExA2 _GetFileVersionInfoSizeExA2; 23 | GetFileVersionInfoExA2 _GetFileVersionInfoExA2; 24 | VerQueryValueA2 _VerQueryValueA2; 25 | HINSTANCE hInst = NULL; 26 | }; -------------------------------------------------------------------------------- /SCXLauncher/Message.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | static void ShowMessage(std::string title, std::string message) 6 | { 7 | MessageBox(NULL, message.c_str(), title.c_str(), MB_OK); 8 | OutputDebugString(message.c_str()); 9 | } 10 | 11 | static void ShowMessage(std::wstring title, std::wstring message) 12 | { 13 | MessageBoxW(NULL, message.c_str(), title.c_str(), MB_OK); 14 | OutputDebugStringW(message.c_str()); 15 | } 16 | 17 | static std::string LastErrorString() 18 | { 19 | return std::string("SimCopterX Error (" + std::to_string(GetLastError()) + ")"); 20 | } 21 | 22 | struct MessageValue 23 | { 24 | BOOL Value; 25 | std::string Message; 26 | MessageValue(BOOL value, std::string message) 27 | { 28 | Value = value; 29 | Message = message; 30 | } 31 | 32 | MessageValue(BOOL value) 33 | { 34 | Value = value; 35 | Message = ""; 36 | } 37 | 38 | MessageValue() 39 | { 40 | Value = FALSE; 41 | Message = ""; 42 | } 43 | }; 44 | 45 | -------------------------------------------------------------------------------- /SCXLauncher/DetourMaster.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Instructions.h" 3 | #include "PEINFO.h" 4 | 5 | //0x128 for general variables, 64 = size of cheat string * 32 cheats = 0x800 6 | //static const unsigned int DETOUR_RDATA_VAR_SIZE = 0x128; 7 | //static const unsigned int DETOUR_RDATA_CHEAT_SIZE = 0x800; 8 | //static const unsigned int DETOUR_RDATA_SIZE = DETOUR_RDATA_VAR_SIZE + DETOUR_RDATA_CHEAT_SIZE; 9 | 10 | struct DetourMaster 11 | { 12 | DWORD current_location; 13 | DWORD base_location; 14 | PEINFO info; 15 | 16 | DetourMaster(PEINFO info) 17 | { 18 | this->info = info; 19 | current_location = info.GetDetourVirtualAddress(); 20 | base_location = current_location; 21 | current_location += 0x128; //Reserve space for variables 22 | } 23 | 24 | DWORD GetNextDetour() 25 | { 26 | return current_location; 27 | } 28 | 29 | void SetLastDetourSize(size_t size) 30 | { 31 | //Technically this includes instructions outside of the detour but who cares 32 | current_location += (size % 4) + size; 33 | } 34 | std::vector instructions; 35 | }; -------------------------------------------------------------------------------- /SCXLauncher/SCXLoader.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "Shlwapi.h" 4 | #include "shellapi.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "Patcher.h" 13 | #include "FileVersion.h" 14 | #include "Message.h" 15 | #include "GameData.h" 16 | 17 | #pragma comment(lib, "Shlwapi.lib") 18 | #pragma comment(lib, "Shell32.lib") 19 | #pragma comment(lib, "Kernel32.lib") 20 | #pragma comment(lib, "Advapi32.lib" ) 21 | 22 | struct SCXParameters 23 | { 24 | unsigned int sleep_time; 25 | unsigned int resolution_mode; 26 | bool fullscreen; 27 | }; 28 | 29 | struct PatchInfo; 30 | class SCXLoader 31 | { 32 | public: 33 | static void LoadSettings(); 34 | static int GetPatchedSCXVersion(); 35 | static bool CreatePatchedGame(std::string, SCXParameters); 36 | static bool InstallGame(); 37 | static bool StartSCX(SCXParameters); 38 | static bool GetValidInstallation(); 39 | static bool FixMaxisHelpViewer(std::filesystem::path); 40 | static const PatchInfo& GetPatchInfo(); 41 | static constexpr unsigned int SCX_VERSION = 17; 42 | private: 43 | static bool GetFileCompatability(std::wstring); 44 | }; 45 | 46 | -------------------------------------------------------------------------------- /SCXLauncher/GameData.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include "Patcher.h" 6 | #include "GameVersion.h" 7 | 8 | 9 | class GameData 10 | { 11 | public: 12 | static std::vector GenerateData(PEINFO, GameVersions); 13 | private: 14 | static void CreateSleepFunction(DetourMaster*, GameVersions); 15 | static void CreateResolutionFunction(DetourMaster*, GameVersions); 16 | static void CreateGlobalInitFunction(DetourMaster*, GameVersions); 17 | static void CreateCDFunction(DetourMaster*, GameVersions); 18 | static void CreateChopperUIFunction(DetourMaster*, GameVersions); 19 | static void CreateFlapUIFunction(DetourMaster*, GameVersions); 20 | static void CreateChopperClipFunction(DetourMaster*, GameVersions); 21 | static void CreateScreenClipFunction(DetourMaster*, GameVersions); 22 | static void CreateDDrawPaletteFunction(DetourMaster*, GameVersions); 23 | static void CreateHangarMainFunction(DetourMaster*, GameVersions); 24 | static void CreateMapCheatFunction(DetourMaster*, GameVersions); 25 | static void RenderSimsFunction(DetourMaster*, GameVersions); 26 | static void PatchChopperDamageFunction(DetourMaster*, GameVersions); 27 | static void PatchEmergencyVehicleCrashOnRamp(DetourMaster*, GameVersions); 28 | static void PatchHUnlock(DetourMaster*, GameVersions); 29 | static void PatchAssertions(DetourMaster*, GameVersions); 30 | }; -------------------------------------------------------------------------------- /SimCopterX.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28307.168 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SCXLauncher", "SCXLauncher\SCXLauncher.vcxproj", "{92EB3627-F121-4FF9-8BEC-2101DD26A4D5}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Debug|x86 = Debug|x86 12 | Release|x64 = Release|x64 13 | Release|x86 = Release|x86 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {92EB3627-F121-4FF9-8BEC-2101DD26A4D5}.Debug|x64.ActiveCfg = Debug|x64 17 | {92EB3627-F121-4FF9-8BEC-2101DD26A4D5}.Debug|x64.Build.0 = Debug|x64 18 | {92EB3627-F121-4FF9-8BEC-2101DD26A4D5}.Debug|x86.ActiveCfg = Debug|Win32 19 | {92EB3627-F121-4FF9-8BEC-2101DD26A4D5}.Debug|x86.Build.0 = Debug|Win32 20 | {92EB3627-F121-4FF9-8BEC-2101DD26A4D5}.Release|x64.ActiveCfg = Release|x64 21 | {92EB3627-F121-4FF9-8BEC-2101DD26A4D5}.Release|x64.Build.0 = Release|x64 22 | {92EB3627-F121-4FF9-8BEC-2101DD26A4D5}.Release|x86.ActiveCfg = Release|Win32 23 | {92EB3627-F121-4FF9-8BEC-2101DD26A4D5}.Release|x86.Build.0 = Release|Win32 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {D3D58BDE-0139-40E9-AC9C-6856A25482AB} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /SCXLauncher/Registry.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | struct RegistryValue { 7 | const DWORD dwType; 8 | const DWORD Size; 9 | const std::wstring wstring; 10 | LPBYTE Data; 11 | 12 | explicit RegistryValue(const std::wstring& value) : 13 | dwType(REG_SZ), Size((value.size() + 1) * sizeof(wchar_t)), 14 | wstring(value) 15 | { 16 | Data = (LPBYTE)malloc(Size); 17 | if (Data == nullptr) throw std::bad_alloc(); 18 | memcpy(Data, value.c_str(), Size); 19 | } 20 | 21 | explicit RegistryValue(const DWORD& value) : 22 | dwType(REG_DWORD), Size(sizeof(DWORD)), 23 | wstring(std::to_wstring(value)) 24 | { 25 | Data = (LPBYTE)malloc(Size); 26 | if (Data == nullptr) throw std::bad_alloc(); 27 | memcpy(Data, &value, Size); 28 | } 29 | 30 | RegistryValue(const RegistryValue& v) : 31 | dwType(v.dwType), Size(v.Size), wstring(v.wstring) 32 | { 33 | Data = (LPBYTE)malloc(Size); 34 | if (Data == nullptr) throw std::bad_alloc(); 35 | memcpy(Data, v.Data, Size); 36 | } 37 | 38 | ~RegistryValue() 39 | { 40 | free(Data); 41 | Data = nullptr; 42 | } 43 | 44 | }; 45 | 46 | struct RegistryEntry { 47 | const std::wstring Name; 48 | RegistryValue* Value; 49 | RegistryEntry(std::wstring Name, RegistryValue* Value) : 50 | Name(Name), Value(Value) 51 | { 52 | } 53 | RegistryEntry(std::wstring Name) : 54 | Name(Name), Value(nullptr) 55 | { 56 | } 57 | RegistryEntry() = delete; 58 | ~RegistryEntry() 59 | { 60 | delete Value; 61 | Value = nullptr; 62 | } 63 | }; 64 | 65 | struct RegistryKey { 66 | HKEY hKey; 67 | std::wstring SubKey; 68 | RegistryKey(HKEY hKey, std::wstring SubKey) 69 | { 70 | this->hKey = hKey; 71 | this->SubKey = SubKey; 72 | } 73 | RegistryKey() = default; 74 | }; 75 | 76 | namespace Registry { 77 | BOOL SetValue(const RegistryKey, const RegistryEntry&); 78 | BOOL GetValue(const RegistryKey, RegistryEntry&); 79 | BOOL DeleteValue(const RegistryKey, const RegistryEntry&); 80 | } -------------------------------------------------------------------------------- /SCXLauncher/notes.txt: -------------------------------------------------------------------------------- 1 | All occurrences of hardcoded dimension values: 2 | 3 | (Instruction) 412277 = [0x280] Equivalent to Streets "CALL_INIT_SKYBOX", skybox width 4 | (Instruction) 41226C = [0xC8] Equivalent to Streets "CALL_INIT_SKYBOX", skybox height 5 | 6 | (Instruction) 46374E = [0x280] Equivalent to Streets "INITIALIZE_SKYBOX", skybox width 7 | (Instruction) 463758 = [0xC8] Equivalent to Streets "INITIALIZE_SKYBOX", skybox height 8 | 9 | Note: Skybox value must be 128K buffer (640*200=128K). For some reason the 10 | "RENDER_SKYBOX" function is missing in SimCopter, however the resolution can be modified 11 | and the skybox doesn't ghost or under/over buffer. 12 | 13 | (Instruction) 41250B = [0x280] Patched CHOPPER_UI 14 | 15 | (Instruction) 445E4F = [0x280] Unknown 16 | (Instruction) 445E54 = [0x1E0] Unknown 17 | 18 | (Instruction) 44FC5A = [0x118] Unknown 19 | (Instruction) 44FA20 = [0x280] Unknown 20 | 21 | (Instruction) 45E9C2 = [0x280] width < 640 check 22 | (Instruction) 45E9D4 = [0x1E0] height < 480 check 23 | (Instruction) 45E9E0 = [0x08] bit depth = 8 check 24 | 25 | (Instruction) 474B3E = [0x280] Unknown, analysis fails Skybox? 26 | (Instruction) 474B44 = [0xC8] Unknown, analysis fails Skybox? 27 | 28 | (Instruction) 480CAF = [0x280] Unknown, (repeated multiple times in switch case) 29 | 30 | (Data) 50179C = [0xC8] Unknown, potential skybox 31 | 32 | -------------------------- 33 | Languages (500708): 34 | 1 = English 35 | 2 = Unknown 36 | 3 = French 37 | 4 = German 38 | 5 = Italian 39 | 6 = Spanish 40 | 7 = Dutch 41 | 8 = Unknown 42 | 9 = Unknown 43 | 10 = Unknown 44 | 11 = Swedish 45 | ------------------------- 46 | 47 | Random notes (1.02 patch) 48 | chopper values initiated in function: sub_48C330 49 | 50 | 0x509E8C = health pointer 51 | 0x5DF770 = health 52 | 509C90 + 0xD0 = health (base = chopper?) 53 | + 0x4C = max health 54 | 55 | 464171 = address where variables are loaded from file 56 | 57 | 48BEC9 = division for damage, lower number = faster "sway damage" 58 | chopper with full health doesnt reach this 59 | 60 | 61 | -------------------------- 62 | 63 | 64 | -------------------------------------------------------------------------------- /SCXLauncher/GameVersionInfo.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include "GameVersion.h" 5 | 6 | 7 | struct GameVersionInfo 8 | { 9 | std::string md5sum; 10 | std::string friendlyName; 11 | std::string version; 12 | std::string date; 13 | }; 14 | 15 | static const std::unordered_map GameVersionInfoMap 16 | { 17 | { GameVersions::VCLASSICS,{ "6bc646d182ab8625a0d2394112334005", 18 | "Classics Version", "(1.0.1.4)", "1 April 1997" } 19 | }, 20 | { GameVersions::V11SC, { "90db54003aa9ba881543c9d2cd0dbfbf", 21 | "Version 1.1SC", "(1.0.1.0)", "8 December 1996" } 22 | }, 23 | { GameVersions::V102_PATCH, { "d2f5c5eca71075696964d0f91b1163bf", 24 | "Version 1.02 Patch", "(1.0.1.3)", "26 February 1997" } 25 | }, 26 | { GameVersions::V11SC_FR, { "b296b26e922bc43705b49f7414d7218f", 27 | "Version 1.1SC (French)", "(1.0.1.0)", "9 December 1996" } 28 | }, 29 | { GameVersions::ORIGINAL, { "17d5eba3e604229c4b87a68f20520b56", 30 | "Version 1.0", "(1.0.0.0)", "14 November 1996" } 31 | }, 32 | { GameVersions::V10_JP, { "1ea2ece4cf9b4e0ed3da217a31426795", 33 | "Version 1.0 (Japanese)", "(1.0.0.0)", "7 December 1996" } 34 | }, 35 | { GameVersions::DEBUG, { "2970cdb003869392d9bff3253b25e720", 36 | "Debug Version 1.0.0.1", "(1.0.0.0)", "13 May 1996" } 37 | } 38 | }; 39 | 40 | static bool GetGameVersion(std::string hash, GameVersions& version) 41 | { 42 | std::unordered_map::const_iterator it; 43 | for (it = GameVersionInfoMap.begin(); it != GameVersionInfoMap.end(); ++it) 44 | { 45 | if (it->second.md5sum.compare(hash.c_str()) == 0) 46 | { 47 | version = it->first; 48 | return true; 49 | } 50 | } 51 | return false; 52 | } 53 | 54 | static std::string CreateVersionString(GameVersions version) 55 | { 56 | std::unordered_map::const_iterator it; 57 | it = GameVersionInfoMap.find(version); 58 | if (it == GameVersionInfoMap.end()) 59 | return ""; 60 | char buffer[64]; 61 | snprintf(buffer, sizeof(buffer), "%s %s - %s", 62 | it->second.friendlyName.c_str(), 63 | it->second.version.c_str(), 64 | it->second.date.c_str()); 65 | return std::string(buffer); 66 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /SCXLauncher/Registry.cpp: -------------------------------------------------------------------------------- 1 | #include "Registry.h" 2 | #include 3 | 4 | BOOL Registry::GetValue(const RegistryKey key, RegistryEntry& entry) 5 | { 6 | HKEY hKey; 7 | DWORD disposition; 8 | LSTATUS status_createkeyex = RegCreateKeyExW(key.hKey, key.SubKey.c_str(), 9 | 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hKey, &disposition); 10 | if (status_createkeyex != ERROR_SUCCESS) 11 | { 12 | printf("[Registry::SetValues] RegCreateKeyExW (%ls) = %d\n", 13 | key.SubKey.c_str(), status_createkeyex); 14 | return FALSE; 15 | } 16 | 17 | DWORD queryType; 18 | WCHAR queryData[256] = { 0 }; 19 | DWORD cbData = sizeof(queryData) - 1; 20 | LSTATUS status_queryvalue = RegQueryValueExW(hKey, 21 | entry.Name.c_str(), NULL, &queryType, 22 | (LPBYTE)queryData, &cbData); 23 | if (status_queryvalue != ERROR_SUCCESS) return FALSE; 24 | 25 | const std::wstring wdata(queryData); 26 | if (entry.Value) 27 | { 28 | delete entry.Value; 29 | entry.Value = nullptr; 30 | } 31 | switch (queryType) 32 | { 33 | case REG_SZ: 34 | entry.Value = new RegistryValue(wdata); 35 | break; 36 | case REG_DWORD: 37 | DWORD value; 38 | memcpy(&value, queryData, sizeof(DWORD)); 39 | entry.Value = new RegistryValue(value); 40 | break; 41 | } 42 | 43 | RegCloseKey(hKey); 44 | return TRUE; 45 | } 46 | 47 | BOOL Registry::DeleteValue(const RegistryKey key, const RegistryEntry& value) 48 | { 49 | HKEY hKey; 50 | DWORD disposition; 51 | LSTATUS status_createkeyex = RegCreateKeyExW(key.hKey, key.SubKey.c_str(), 52 | 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hKey, &disposition); 53 | if (status_createkeyex != ERROR_SUCCESS) 54 | { 55 | printf("[Registry::SetValues] RegCreateKeyExW (%ls) = %d\n", 56 | key.SubKey.c_str(), status_createkeyex); 57 | return FALSE; 58 | } 59 | BOOL result = RegDeleteKeyW(hKey, value.Name.c_str()); 60 | RegCloseKey(hKey); 61 | return result; 62 | } 63 | 64 | 65 | BOOL Registry::SetValue(const RegistryKey key, const RegistryEntry& value) 66 | { 67 | HKEY hKey; 68 | DWORD disposition; 69 | LSTATUS status_createkeyex = RegCreateKeyExW(key.hKey, key.SubKey.c_str(), 70 | 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hKey, &disposition); 71 | if (status_createkeyex != ERROR_SUCCESS) 72 | { 73 | printf("[Registry::SetValues] RegCreateKeyExW (%ls) = %d\n", 74 | key.SubKey.c_str(), status_createkeyex); 75 | return FALSE; 76 | } 77 | 78 | LSTATUS status_setvalue = RegSetValueExW(hKey, 79 | value.Name.c_str(), NULL, 80 | value.Value->dwType, 81 | value.Value->Data, 82 | value.Value->Size); 83 | 84 | if (status_setvalue != ERROR_SUCCESS) 85 | { 86 | printf("Unable to set the registry key. LSTATUS=%d\n", status_setvalue); 87 | return FALSE; 88 | } 89 | RegCloseKey(hKey); 90 | return TRUE; 91 | } 92 | -------------------------------------------------------------------------------- /SCXLauncher/SCXLauncher.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | {4a5211e2-e468-4b9e-940b-f75d26cf4be4} 18 | 19 | 20 | 21 | 22 | Source Files 23 | 24 | 25 | Source Files 26 | 27 | 28 | Source Files 29 | 30 | 31 | Source Files 32 | 33 | 34 | Source Files 35 | 36 | 37 | Source Files 38 | 39 | 40 | 41 | 42 | Header Files 43 | 44 | 45 | Header Files 46 | 47 | 48 | Header Files 49 | 50 | 51 | Header Files 52 | 53 | 54 | Header Files 55 | 56 | 57 | Header Files 58 | 59 | 60 | Header Files 61 | 62 | 63 | Header Files 64 | 65 | 66 | Header Files 67 | 68 | 69 | Header Files 70 | 71 | 72 | Header Files 73 | 74 | 75 | Header Files 76 | 77 | 78 | Header Files 79 | 80 | 81 | 82 | 83 | Resource Files 84 | 85 | 86 | 87 | 88 | Resource Files 89 | 90 | 91 | Resource Files 92 | 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /SCXLauncher/FileVersion.cpp: -------------------------------------------------------------------------------- 1 | #include "FileVersion.h" 2 | 3 | BOOL FileVersion::Initialize() 4 | { 5 | if (hInst) 6 | return TRUE; 7 | 8 | //Version.lib does not contain GetFileVersionInfoSizeExA or GetFileVersionInfoExA, must be 9 | //retrieved from Version.dll. All functions are now instead loaded from dll, including 10 | //VerQueryValueA 11 | if ((hInst = LoadLibrary("Version.dll")) == NULL) 12 | { 13 | return FALSE; 14 | } 15 | 16 | FARPROC _GetFileVersionInfoSizeExA2Address = GetProcAddress(hInst, "GetFileVersionInfoSizeExA"); 17 | FARPROC _GetFileVersionInfoExA2Address = GetProcAddress(hInst, "GetFileVersionInfoExA"); 18 | FARPROC _VerQueryValueA2Address = GetProcAddress(hInst, "VerQueryValueA"); 19 | 20 | if (_GetFileVersionInfoSizeExA2Address == NULL || 21 | _GetFileVersionInfoExA2Address == NULL || 22 | _VerQueryValueA2Address == NULL) 23 | { 24 | return FALSE; 25 | } 26 | 27 | _GetFileVersionInfoSizeExA2 = (GetFileVersionInfoSizeExA2)_GetFileVersionInfoSizeExA2Address; 28 | _GetFileVersionInfoExA2 = (GetFileVersionInfoExA2)_GetFileVersionInfoExA2Address; 29 | _VerQueryValueA2 = (VerQueryValueA2)_VerQueryValueA2Address; 30 | 31 | return TRUE; 32 | } 33 | 34 | MessageValue FileVersion::GetSCFileVersionInfo(LPCSTR filename, StringFileInfoMap& out) 35 | { 36 | if (!Initialize()) 37 | return MessageValue(FALSE, "Could not load Version.dll (System32)!\n"); 38 | 39 | DWORD flags = FILE_VER_GET_NEUTRAL | FILE_VER_GET_LOCALISED; 40 | DWORD size = _GetFileVersionInfoSizeExA2(flags, filename, NULL); 41 | if (size == 0) 42 | { 43 | return MessageValue(FALSE, "Failed to get the file version info size! Error Code: " + std::to_string(GetLastError()) + "\n"); 44 | } 45 | 46 | LPVOID pVersionInfo = new BYTE[size]; 47 | BOOL result = _GetFileVersionInfoExA2(flags, filename, NULL, size, pVersionInfo); 48 | if (!result) 49 | { 50 | return MessageValue(FALSE, "Failed to get the file version info! Error Code: " + std::to_string(GetLastError()) + "\n"); 51 | } 52 | 53 | UINT puLen2; 54 | struct LANGANDCODEPAGE { 55 | WORD wLanguage; 56 | WORD wCodePage; 57 | } *lpTranslate; 58 | 59 | BOOL t_result = _VerQueryValueA2(pVersionInfo, "\\VarFileInfo\\Translation", (LPVOID*)&lpTranslate, &puLen2); 60 | if (!t_result) 61 | { 62 | return MessageValue(FALSE, "Failed to get the file translation! Error Code: " + std::to_string(GetLastError()) + "\n"); 63 | } 64 | printf("0x%x, 0x%x\n", lpTranslate->wLanguage, lpTranslate->wCodePage); 65 | 66 | std::map stringFileInfo = 67 | { 68 | {"FileVersion", ""}, 69 | {"CompanyName", ""}, 70 | {"FileDescription", ""}, 71 | {"OriginalFilename", ""} 72 | }; 73 | 74 | for (auto it = stringFileInfo.begin(); it != stringFileInfo.end(); it++) 75 | { 76 | UINT puLen; 77 | char buffer[256]; 78 | sprintf_s(buffer, sizeof(buffer), "\\StringFileInfo\\%04lx%04lx\\%s", lpTranslate->wLanguage, lpTranslate->wCodePage, it->first.c_str()); 79 | printf("Quering Value: %s\n", buffer); 80 | 81 | LPCSTR lpBuffer; 82 | BOOL result3 = _VerQueryValueA2(pVersionInfo, buffer, (LPVOID*)&lpBuffer, &puLen); 83 | if (!result3) 84 | { 85 | return MessageValue(FALSE, "Failed to get the StringFileInfo(" + it->first + ") + Error Code: " + std::to_string(GetLastError()) + "\n"); 86 | } 87 | stringFileInfo[it->first] = lpBuffer; 88 | printf("%s: %s\n", it->first.c_str(), it->second.c_str()); 89 | } 90 | 91 | out = stringFileInfo; 92 | return MessageValue(TRUE); 93 | } 94 | -------------------------------------------------------------------------------- /SCXLauncher/Instructions.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | typedef std::initializer_list ByteArray; 6 | 7 | 8 | struct Instruction { 9 | BYTE byte; 10 | DWORD address; 11 | Instruction(DWORD address, BYTE byte) 12 | { 13 | this->address = address; 14 | this->byte = byte; 15 | } 16 | }; 17 | 18 | struct StringValue { 19 | std::string string; 20 | unsigned int size_alignment; 21 | StringValue(std::string string, unsigned int size_alignment) 22 | { 23 | this->string = string; 24 | this->size_alignment = size_alignment; 25 | } 26 | }; 27 | 28 | class Instructions 29 | { 30 | public: 31 | 32 | void operator<<(BYTE b) 33 | { 34 | container.push_back(Instruction(current_location++, b)); 35 | } 36 | 37 | void operator<<(std::initializer_list bytes) 38 | { 39 | for (BYTE b : bytes) 40 | { 41 | container.push_back(Instruction(current_location++, b)); 42 | } 43 | } 44 | 45 | void operator<<(DWORD address) 46 | { 47 | BYTE byte[sizeof(DWORD)]; 48 | memcpy(byte, &address, sizeof(DWORD)); 49 | for (int i = 0; i < sizeof(DWORD); i++) 50 | { 51 | container.push_back(Instruction(current_location++, byte[i])); 52 | } 53 | } 54 | 55 | void operator<<(StringValue string_value) 56 | { 57 | unsigned int track_address = 0; 58 | for (char character : string_value.string) 59 | { 60 | operator<<(BYTE(character)); 61 | track_address++; 62 | } 63 | if (track_address < string_value.size_alignment) 64 | relocate(current_location + (string_value.size_alignment - track_address)); 65 | } 66 | 67 | Instructions(DWORD address) 68 | { 69 | current_location = address; 70 | } 71 | 72 | void relocate(DWORD address) 73 | { 74 | current_location = address; 75 | } 76 | 77 | void nop(size_t amount) 78 | { 79 | for (size_t i = 0; i < amount; i++) 80 | operator<<(BYTE(0x90)); 81 | } 82 | 83 | void jmp(DWORD address, BOOL change_location) 84 | { 85 | DWORD next_address = current_location + 0x5; 86 | DWORD encoding = address - next_address; 87 | operator<<(BYTE(0xE9)); 88 | operator<<(encoding); 89 | if (change_location) 90 | current_location = address; 91 | } 92 | 93 | void jmp(DWORD address) 94 | { 95 | jmp(address, TRUE); 96 | } 97 | 98 | void jnz(DWORD address) 99 | { 100 | DWORD next_address = current_location + 0x6; 101 | DWORD encoding = address - next_address; 102 | operator<<(BYTE(0x0F)); 103 | operator<<(BYTE(0x85)); 104 | operator<<(encoding); 105 | } 106 | 107 | void jz(DWORD address) 108 | { 109 | DWORD next_address = current_location + 0x6; 110 | DWORD encoding = address - next_address; 111 | operator<<(BYTE(0x0F)); 112 | operator<<(BYTE(0x84)); 113 | operator<<(encoding); 114 | } 115 | 116 | void jge(DWORD address) 117 | { 118 | DWORD next_address = current_location + 0x6; 119 | DWORD encoding = address - next_address; 120 | operator<<(BYTE(0x0F)); 121 | operator<<(BYTE(0x8D)); 122 | operator<<(encoding); 123 | } 124 | 125 | void cmp(DWORD address, BYTE value) 126 | { 127 | //operator<<(BYTE(0x3E)); //segment overload 128 | operator<<(ByteArray{ 0x83, 0x3D }); //cmp 129 | operator<<(address); 130 | operator<<(value); 131 | } 132 | 133 | void call(DWORD address) 134 | { 135 | DWORD next_address = current_location + 0x5; 136 | DWORD encoding = address - next_address; 137 | operator<<(BYTE(0xE8)); 138 | operator<<(encoding); 139 | } 140 | 141 | //FF /2 CALL r/m32 142 | //https://c9x.me/x86/html/file_module_x86_id_26.html 143 | //http://ref.x86asm.net/coder32.html#modrm_byte_32 144 | //https://stackoverflow.com/questions/15017659/how-to-read-the-intel-opcode-notation/41616657#41616657 145 | void call_disp32(DWORD address) 146 | { 147 | //3E = DS segment override prefix 148 | //FF 15 = call near absolute indirect, /2 disp32 = 0x15 149 | operator<<(ByteArray{ 0xFF, 0x15 }); 150 | operator<<(address); 151 | } 152 | 153 | void call_rm32(DWORD address) 154 | { 155 | //3E = DS segment override prefix (unused) 156 | //FF 15 = call near absolute indirect, /2 disp32 = 0x15 157 | operator<<(ByteArray{ 0xFF, 0x15 }); 158 | operator<<(address); 159 | } 160 | 161 | void push_rm32(DWORD address) 162 | { 163 | //3E = DS segment override prefix (unused) 164 | //FF 35 = push near absolute indirect, /6 disp32 = 0x35 165 | operator<<(ByteArray{ 0xFF, 0x35 }); 166 | operator<<(address); 167 | } 168 | 169 | std::vector GetInstructions() 170 | { 171 | return container; 172 | } 173 | 174 | DWORD GetCurrentLocation() 175 | { 176 | return current_location; 177 | } 178 | 179 | private: 180 | std::vector container; 181 | DWORD current_location; 182 | }; 183 | 184 | class DataValue : public Instructions 185 | { 186 | public: 187 | DataValue(DWORD address, BYTE value) : Instructions(address) 188 | { 189 | operator<<(value); 190 | } 191 | }; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc -------------------------------------------------------------------------------- /SCXLauncher/Settings.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Registry.h" 3 | #include "GameVersion.h" 4 | 5 | #define REGISTRY_SUBKEY L"Software\\SimCopterX" 6 | #define REGISTRY_PATCHEDHASH L"PatchedHash" 7 | #define REGISTRY_GAMELOCATION L"GameLocation" 8 | #define REGISTRY_PATCHERVER L"PatcherVersion" 9 | #define REGISTRY_INSTALLED L"Installed" 10 | #define REGISTRY_GAMEVER L"GameVersion" 11 | #define REGISTRY_SCREENMODE L"ScreenMode" 12 | #define REGISTRY_RESOLUTION L"Resolution" 13 | #define REGISTRY_SLEEPTIME L"SleepTime" 14 | 15 | 16 | struct PatchInfo 17 | { 18 | std::wstring PatchedGameLocation; 19 | std::wstring PatchedGameHash; 20 | GameVersions PatchedGameVersion; 21 | bool PatchedGameIsInstalled = false; 22 | int PatcherVersion = -1; 23 | const bool IsPatched() const { return PatcherVersion > 0; }; 24 | }; 25 | 26 | struct SettingsInfo 27 | { 28 | int ScreenMode = 0; 29 | int Resolution = 0; 30 | int SleepTime = 5; 31 | }; 32 | 33 | struct Settings 34 | { 35 | static const SettingsInfo GetSettingsInfo() 36 | { 37 | RegistryEntry re_screenmode(REGISTRY_SCREENMODE); 38 | RegistryEntry re_resolution(REGISTRY_RESOLUTION); 39 | RegistryEntry re_sleeptime(REGISTRY_SLEEPTIME); 40 | 41 | RegistryKey rkey; 42 | rkey.hKey = HKEY_CURRENT_USER; 43 | rkey.SubKey = REGISTRY_SUBKEY; 44 | SettingsInfo info; 45 | 46 | if (Registry::GetValue(rkey, re_screenmode)) 47 | { 48 | info.ScreenMode = std::stoi(re_screenmode.Value->wstring); 49 | } 50 | 51 | if (Registry::GetValue(rkey, re_resolution)) 52 | { 53 | info.Resolution = std::stoi(re_resolution.Value->wstring); 54 | } 55 | 56 | if (Registry::GetValue(rkey, re_sleeptime)) 57 | { 58 | info.SleepTime = std::stoi(re_sleeptime.Value->wstring); 59 | } 60 | return info; 61 | } 62 | 63 | static BOOL SetSettingsInfo(const SettingsInfo& info) 64 | { 65 | RegistryKey rkey; 66 | rkey.hKey = HKEY_CURRENT_USER; 67 | rkey.SubKey = REGISTRY_SUBKEY; 68 | 69 | BOOL resultScreenMode = 70 | Registry::SetValue(rkey, RegistryEntry(REGISTRY_SCREENMODE, 71 | new RegistryValue(info.ScreenMode))); 72 | 73 | BOOL resultResolution = 74 | Registry::SetValue(rkey, RegistryEntry(REGISTRY_RESOLUTION, 75 | new RegistryValue(info.Resolution))); 76 | 77 | BOOL resultSleepTime = 78 | Registry::SetValue(rkey, RegistryEntry(REGISTRY_SLEEPTIME, 79 | new RegistryValue(info.SleepTime))); 80 | 81 | return resultResolution && resultScreenMode && resultSleepTime; 82 | } 83 | 84 | 85 | static const PatchInfo GetPatchInfo() 86 | { 87 | RegistryEntry re_hash(REGISTRY_PATCHEDHASH); 88 | RegistryEntry re_location(REGISTRY_GAMELOCATION); 89 | RegistryEntry re_scxversion(REGISTRY_PATCHERVER); 90 | RegistryEntry re_installed(REGISTRY_INSTALLED); 91 | RegistryEntry re_gameversion(REGISTRY_GAMEVER); 92 | 93 | RegistryKey rkey; 94 | rkey.hKey = HKEY_CURRENT_USER; 95 | rkey.SubKey = REGISTRY_SUBKEY; 96 | PatchInfo info; 97 | 98 | if (Registry::GetValue(rkey, re_hash)) 99 | { 100 | info.PatchedGameHash = re_hash.Value->wstring; 101 | } 102 | 103 | if (Registry::GetValue(rkey, re_location)) 104 | { 105 | info.PatchedGameLocation = re_location.Value->wstring; 106 | } 107 | 108 | if (Registry::GetValue(rkey, re_scxversion)) 109 | { 110 | info.PatcherVersion = std::stoi(re_scxversion.Value->wstring); 111 | } 112 | 113 | if (Registry::GetValue(rkey, re_installed)) 114 | { 115 | info.PatchedGameIsInstalled = std::stoi(re_installed.Value->wstring); 116 | } 117 | 118 | if (Registry::GetValue(rkey, re_gameversion)) 119 | { 120 | info.PatchedGameVersion = (GameVersions)std::stoi(re_gameversion.Value->wstring); 121 | } 122 | return info; 123 | } 124 | 125 | static BOOL SetPatchInfo(const PatchInfo& info) 126 | { 127 | RegistryKey rkey; 128 | rkey.hKey = HKEY_CURRENT_USER; 129 | rkey.SubKey = REGISTRY_SUBKEY; 130 | 131 | BOOL resultPatchedHash = 132 | Registry::SetValue(rkey, RegistryEntry(REGISTRY_PATCHEDHASH, 133 | new RegistryValue(info.PatchedGameHash))); 134 | 135 | BOOL resultGameLocation = 136 | Registry::SetValue(rkey, RegistryEntry(REGISTRY_GAMELOCATION, 137 | new RegistryValue(info.PatchedGameLocation))); 138 | 139 | BOOL resultPatcherVersion = 140 | Registry::SetValue(rkey, RegistryEntry(REGISTRY_PATCHERVER, 141 | new RegistryValue(info.PatcherVersion))); 142 | 143 | BOOL resultInstalled = 144 | Registry::SetValue(rkey, RegistryEntry(REGISTRY_INSTALLED, 145 | new RegistryValue(info.PatchedGameIsInstalled))); 146 | 147 | BOOL resultGameVersion = 148 | Registry::SetValue(rkey, RegistryEntry(REGISTRY_GAMEVER, 149 | new RegistryValue(info.PatchedGameVersion))); 150 | 151 | return resultPatchedHash && resultGameLocation && 152 | resultPatcherVersion && resultInstalled && 153 | resultGameVersion; 154 | } 155 | 156 | static BOOL ClearPatchInfo() 157 | { 158 | RegistryKey rkey; 159 | rkey.hKey = HKEY_CURRENT_USER; 160 | rkey.SubKey = REGISTRY_SUBKEY; 161 | 162 | BOOL resultPatchedHash = Registry::DeleteValue(rkey, RegistryEntry(REGISTRY_PATCHEDHASH)); 163 | BOOL resultGameLocation = Registry::DeleteValue(rkey, RegistryEntry(REGISTRY_GAMELOCATION)); 164 | BOOL resultPatcherVersion = Registry::DeleteValue(rkey, RegistryEntry(REGISTRY_PATCHERVER)); 165 | BOOL resultInstalled = Registry::DeleteValue(rkey, RegistryEntry(REGISTRY_INSTALLED)); 166 | BOOL resultGameVersion = Registry::DeleteValue(rkey, RegistryEntry(REGISTRY_GAMEVER)); 167 | 168 | return resultPatchedHash && resultGameLocation && 169 | resultPatcherVersion && resultInstalled && 170 | resultGameVersion; 171 | } 172 | 173 | }; -------------------------------------------------------------------------------- /SCXLauncher/Patcher.cpp: -------------------------------------------------------------------------------- 1 | #include "Patcher.h" 2 | 3 | bool Patcher::Patch(PEINFO info, std::vector instructions, std::string exe_fname) 4 | { 5 | FILE* efile; 6 | int result = fopen_s(&efile, exe_fname.c_str(), "rb+"); 7 | 8 | if (efile == NULL) 9 | { 10 | OutputDebugString(std::string("Failed to load exe file: " + exe_fname + "\n").c_str()); 11 | OutputDebugString(std::string("Reason: fopen_s returns error code: " + std::to_string(result) + "\n").c_str()); 12 | return false; 13 | } 14 | 15 | unsigned int bytes_written = 0; 16 | for (Instructions is : instructions) 17 | { 18 | for (Instruction instruction : is.GetInstructions()) 19 | { 20 | DWORD address = GetFileOffset(info, instruction.address); 21 | /* 22 | char buffer[256]; 23 | sprintf_s(buffer, sizeof(buffer), "VA = %x, FO = %x, BYTE: %x \n", instruction.address, address, instruction.byte); 24 | OutputDebugString(buffer); 25 | */ 26 | fseek(efile, address, SEEK_SET); 27 | fprintf(efile, "%c", instruction.byte); 28 | bytes_written++; 29 | } 30 | } 31 | 32 | OutputDebugString(std::string("Total bytes patched: " + std::to_string(bytes_written) + "\n").c_str()); 33 | 34 | int close_result = fclose(efile); 35 | OutputDebugString(std::string("Patched file closed successfully: " + close_result == 0 ? "true\n" : "false\n").c_str()); 36 | return true; 37 | } 38 | 39 | DWORD Patcher::GetFileOffset(PEINFO info, DWORD address) 40 | { 41 | //TODO this function is very inefficient 42 | for (auto it = info.data_map.begin(); it != info.data_map.end(); ++it) 43 | { 44 | DWORD start_address = it->second.RealVirtualAddress; 45 | DWORD end_address = start_address + it->second.VirtualSize; 46 | if (address >= start_address && address <= end_address) 47 | { 48 | //printf("- %x is in %s\n", address, it->first.c_str()); 49 | return (address - start_address) + it->second.RawDataPointer; 50 | } 51 | } 52 | printf("Failed to find section where address %x belongs\n", address); 53 | return NULL; 54 | } 55 | 56 | DWORD Patcher::align(DWORD size, DWORD align, DWORD addr) 57 | { 58 | if (!(size % align)) 59 | return addr + size; 60 | return addr + (size / align + 1) * align; 61 | } 62 | 63 | bool Patcher::CreateDetourSection(const char* filepath, PEINFO* info) 64 | { 65 | const char* section_name = ".detour"; 66 | DWORD section_size = 2048; 67 | 68 | HANDLE file = CreateFile(filepath, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); 69 | if (file == INVALID_HANDLE_VALUE) 70 | { 71 | OutputDebugString(std::string("Invalid handle when attempting to check detour sections: \n" + std::string(filepath) + "\n").c_str()); 72 | OutputDebugString(std::string("Reason: " + std::to_string(GetLastError()) + "\n").c_str()); 73 | return false; 74 | } 75 | 76 | DWORD fileSize = GetFileSize(file, NULL); 77 | BYTE* pByte = new BYTE[fileSize]; 78 | DWORD dw; 79 | ReadFile(file, pByte, fileSize, &dw, NULL); 80 | 81 | PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)pByte; 82 | if (dos->e_magic != IMAGE_DOS_SIGNATURE) 83 | { 84 | OutputDebugString("Error with IMAGE_DOS_SIGNATURE when creating detour section\n"); 85 | return false; 86 | } 87 | 88 | PIMAGE_FILE_HEADER FH = (PIMAGE_FILE_HEADER)(pByte + dos->e_lfanew + sizeof(DWORD)); 89 | PIMAGE_OPTIONAL_HEADER OH = (PIMAGE_OPTIONAL_HEADER)(pByte + dos->e_lfanew + sizeof(DWORD) + sizeof(IMAGE_FILE_HEADER)); 90 | PIMAGE_SECTION_HEADER SH = (PIMAGE_SECTION_HEADER)(pByte + dos->e_lfanew + sizeof(IMAGE_NT_HEADERS)); 91 | 92 | bool detour_section_exists = false; 93 | OutputDebugString(std::string("Current total sections: " + std::to_string(FH->NumberOfSections) + "\n").c_str()); 94 | for (unsigned int i = 0; i < FH->NumberOfSections; i++) 95 | { 96 | std::string name(reinterpret_cast(SH[i].Name)); 97 | info->data_map[name].VirtualAddress = SH[i].VirtualAddress; 98 | info->data_map[name].RealVirtualAddress = SH[i].VirtualAddress + WIN32_PE_ENTRY; 99 | info->data_map[name].RawDataPointer = SH[i].PointerToRawData; 100 | info->data_map[name].VirtualSize = SH[i].Misc.VirtualSize; 101 | if (name.compare(section_name) == 0) 102 | detour_section_exists = true; 103 | } 104 | 105 | if (detour_section_exists) 106 | { 107 | char buffer[256]; 108 | snprintf(buffer, sizeof(buffer), ".detour section already exists at 0x%x\n", info->data_map[".detour"].VirtualAddress); 109 | OutputDebugString(std::string(buffer).c_str()); 110 | CloseHandle(file); 111 | return true; 112 | } 113 | 114 | ZeroMemory(&SH[FH->NumberOfSections], sizeof(IMAGE_SECTION_HEADER)); 115 | CopyMemory(&SH[FH->NumberOfSections].Name, section_name, 8); 116 | 117 | SH[FH->NumberOfSections].Misc.VirtualSize = align(section_size, OH->SectionAlignment, 0); 118 | SH[FH->NumberOfSections].VirtualAddress = align(SH[FH->NumberOfSections - 1].Misc.VirtualSize, OH->SectionAlignment, SH[FH->NumberOfSections - 1].VirtualAddress); 119 | SH[FH->NumberOfSections].SizeOfRawData = align(section_size, OH->FileAlignment, 0); 120 | SH[FH->NumberOfSections].PointerToRawData = align(SH[FH->NumberOfSections - 1].SizeOfRawData, OH->FileAlignment, SH[FH->NumberOfSections - 1].PointerToRawData);// right here ptr to raw data 121 | SH[FH->NumberOfSections].Characteristics = IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ; 122 | 123 | SetFilePointer(file, SH[FH->NumberOfSections].PointerToRawData + SH[FH->NumberOfSections].SizeOfRawData, NULL, FILE_BEGIN); 124 | SetEndOfFile(file); 125 | OH->SizeOfImage = SH[FH->NumberOfSections].VirtualAddress + SH[FH->NumberOfSections].Misc.VirtualSize; 126 | FH->NumberOfSections += 1; 127 | SetFilePointer(file, 0, NULL, FILE_BEGIN); 128 | WriteFile(file, pByte, fileSize, &dw, NULL); 129 | 130 | unsigned int new_size = FH->NumberOfSections - 1; //Size not length 131 | OutputDebugString(std::string("Added .detour section, total sections: " + std::to_string(FH->NumberOfSections) + "\n").c_str()); 132 | std::string name(reinterpret_cast(SH[new_size].Name)); 133 | info->data_map[name].VirtualAddress = SH[new_size].VirtualAddress; 134 | info->data_map[name].RealVirtualAddress = SH[new_size].VirtualAddress + WIN32_PE_ENTRY; 135 | info->data_map[name].RawDataPointer = SH[new_size].PointerToRawData; 136 | info->data_map[name].VirtualSize = SH[new_size].Misc.VirtualSize; 137 | 138 | char buffer[256]; 139 | sprintf_s(buffer, sizeof(buffer), "Created .detour section at 0x%x\n", info->data_map[".detour"].VirtualAddress); 140 | OutputDebugString(std::string(buffer).c_str()); 141 | 142 | BOOL result = CloseHandle(file); 143 | if (!result) 144 | { 145 | OutputDebugString(std::string("Error closing the file, error code: " + std::to_string(GetLastError()) + "\n").c_str()); 146 | } 147 | 148 | return true; 149 | } -------------------------------------------------------------------------------- /SCXLauncher/GameVersion.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | struct FunctionType 5 | { 6 | DWORD WINMAIN; 7 | DWORD GLOBAL_INIT; 8 | DWORD FLAP_UI; 9 | DWORD CHOPPER_UI; 10 | DWORD CD_CHECK; 11 | DWORD CHOPPER_CLIP; 12 | DWORD RES_LOOKUP; 13 | DWORD DS_SLEEP; 14 | DWORD SCREEN_CLIP; 15 | DWORD BITDEPTH_CHECK; 16 | DWORD VERSIONS; 17 | DWORD GRAPHICS_INIT; 18 | DWORD ARG_PARSER; 19 | DWORD GFX_SOUND_INIT; 20 | DWORD ADJUST_WINDOW; 21 | DWORD DDRAW_PALETTE; 22 | DWORD CHEAT; 23 | DWORD HANGAR_MAIN; 24 | DWORD UNK_RENDER_1; 25 | DWORD RENDER_SIMS; 26 | DWORD CHOPPER_RENDER_UNK1; 27 | DWORD SET_EMERGENCY_VEHICLE_AVAILABLE; //arg0 = vehicle index 28 | DWORD EMERGENCY_VEHICLE_RENDER_UNK1; 29 | DWORD HUNLOCK; //Only available in debug 30 | DWORD DOASSERT; //Only available in debug 31 | DWORD ASSERT; //Only available in debug 32 | DWORD DS_PEEKMESSAGE; //Only needed in debug 33 | }; 34 | 35 | struct DataType 36 | { 37 | DWORD RES_TYPE; 38 | }; 39 | 40 | struct DetourOffsetType 41 | { 42 | static const DWORD MY_SLEEP = 0x0; 43 | }; 44 | 45 | struct GameVersion 46 | { 47 | FunctionType functions; 48 | DataType data; 49 | }; 50 | 51 | const struct VersionClassics : GameVersion 52 | { 53 | VersionClassics() 54 | { 55 | functions.GLOBAL_INIT = 0x45DDB0; 56 | functions.WINMAIN = 0x4308B0; 57 | functions.DS_SLEEP = 0x62B624; 58 | functions.CD_CHECK = 0x435840; 59 | functions.CHOPPER_UI = 0x4124C0; 60 | functions.FLAP_UI = 0x412860; 61 | functions.CHOPPER_CLIP = 0x413170; 62 | functions.RES_LOOKUP = 0x4641C0; 63 | functions.SCREEN_CLIP = 0x430E40; 64 | functions.DDRAW_PALETTE = 0x41CD40; 65 | functions.HANGAR_MAIN = 0x43ECC0; 66 | functions.UNK_RENDER_1 = 0x44C550; 67 | functions.RENDER_SIMS = 0x4D68E0; 68 | functions.CHOPPER_RENDER_UNK1 = 0x48DD80; 69 | functions.EMERGENCY_VEHICLE_RENDER_UNK1 = 0x498040; 70 | 71 | functions.BITDEPTH_CHECK = 0x45E870; //+0x170 72 | functions.VERSIONS = 0x45F210; //Contains versions for everything (game, glide, os, etc) 73 | functions.GRAPHICS_INIT = 0x41C2E0; //Conditional on whether to initialize glide or DDraw = use to patch DDraw 74 | functions.ARG_PARSER = 0x45EB10; //All command-line arguments processed here 75 | functions.GFX_SOUND_INIT = 0x45DEC0; //+36F (45E22F) = if not windowed mode - calls DirectX fullscreen 76 | functions.ADJUST_WINDOW = 0x421400; //Positions the window (LPRECT) in the center of your screen 77 | functions.CHEAT = 0x438370; //This function will get rewritten, space: 0x240D (9229 bytes) 78 | 79 | data.RES_TYPE = 0x5017D0; 80 | } 81 | } version_classics; 82 | 83 | const struct Version11SC : GameVersion 84 | { 85 | Version11SC() 86 | { 87 | functions.GLOBAL_INIT = 0x45ADE0; 88 | functions.WINMAIN = 0x42DBB0; 89 | functions.DS_SLEEP = 0x61D594; 90 | functions.CD_CHECK = 0x432B20; 91 | functions.CHOPPER_UI = 0x412440; 92 | functions.FLAP_UI = 0x4127D0; 93 | functions.CHOPPER_CLIP = 0x413090; 94 | functions.RES_LOOKUP = 0x4610A0; 95 | functions.SCREEN_CLIP = 0x42E130; 96 | functions.DDRAW_PALETTE = 0x41C9E0; 97 | functions.HANGAR_MAIN = 0x43BFA0; 98 | functions.UNK_RENDER_1 = 0x449850; 99 | functions.RENDER_SIMS = 0x4CFB30; 100 | functions.CHOPPER_RENDER_UNK1 = 0x489800; 101 | functions.EMERGENCY_VEHICLE_RENDER_UNK1 = 0x492030; 102 | 103 | functions.VERSIONS = 0x45C0F0; 104 | 105 | data.RES_TYPE = 0x4F9798; 106 | } 107 | } version_11sc; 108 | 109 | const struct Version11SCFR : GameVersion 110 | { 111 | Version11SCFR() 112 | { 113 | functions.GLOBAL_INIT = 0x459B30; 114 | functions.WINMAIN = 0x42C900; 115 | functions.DS_SLEEP = 0x61D594; 116 | functions.CD_CHECK = 0x431870; 117 | functions.CHOPPER_UI = 0x411190; 118 | functions.FLAP_UI = 0x411520; 119 | functions.CHOPPER_CLIP = 0x411DE0; 120 | functions.RES_LOOKUP = 0x45FDF0; 121 | functions.SCREEN_CLIP = 0x42CE80; 122 | functions.DDRAW_PALETTE = 0x41B730; 123 | functions.HANGAR_MAIN = 0x43ACF0; 124 | functions.UNK_RENDER_1 = 0x4485A0; 125 | functions.RENDER_SIMS = 0x4D18B0; 126 | functions.CHOPPER_RENDER_UNK1 = 0x488800; 127 | functions.EMERGENCY_VEHICLE_RENDER_UNK1 = 0x491D20; 128 | 129 | data.RES_TYPE = 0x4F9778; 130 | } 131 | } version_11scfr; 132 | 133 | const struct Version102Patch : GameVersion 134 | { 135 | Version102Patch() 136 | { 137 | functions.GLOBAL_INIT = 0x45B540; 138 | functions.WINMAIN = 0x42E0A0; 139 | functions.DS_SLEEP = 0x62560C; 140 | functions.CD_CHECK = 0x433030; 141 | functions.CHOPPER_UI = 0x4124F0; 142 | functions.FLAP_UI = 0x412890; 143 | functions.CHOPPER_CLIP = 0x4131A0; 144 | functions.RES_LOOKUP = 0x461930; 145 | functions.SCREEN_CLIP = 0x42E630; 146 | functions.DDRAW_PALETTE = 0x41CD60; 147 | functions.HANGAR_MAIN = 0x43C4F0; 148 | functions.UNK_RENDER_1 = 0x449DA0; 149 | functions.RENDER_SIMS = 0x4D44B0; 150 | functions.CHOPPER_RENDER_UNK1 = 0x48BD10; 151 | functions.EMERGENCY_VEHICLE_RENDER_UNK1 = 0x495DF0; 152 | 153 | functions.BITDEPTH_CHECK = 0x45C000; //+0x170 154 | 155 | data.RES_TYPE = 0x4FE7A0; 156 | } 157 | } version_102patch; 158 | 159 | const struct Version10JP : GameVersion 160 | { 161 | Version10JP() 162 | { 163 | functions.GLOBAL_INIT = 0x401800; 164 | functions.WINMAIN = 0x435100; 165 | functions.DS_SLEEP = 0x61C598; 166 | functions.CD_CHECK = 0x43F9C0; 167 | functions.CHOPPER_UI = 0x419EE0; 168 | functions.FLAP_UI = 0x41A270; 169 | functions.CHOPPER_CLIP = 0x41AB30; 170 | functions.RES_LOOKUP = 0x4070A0; 171 | functions.SCREEN_CLIP = 0x437080; 172 | functions.DDRAW_PALETTE = 0x418E90; 173 | functions.HANGAR_MAIN = 0x40DB40; 174 | functions.UNK_RENDER_1 = 0x453250; 175 | functions.RENDER_SIMS = 0x4D0310; 176 | functions.CHOPPER_RENDER_UNK1 = 0x490680; 177 | functions.EMERGENCY_VEHICLE_RENDER_UNK1 = 0x497690; 178 | 179 | data.RES_TYPE = 0x4F9CD0; 180 | } 181 | } version_10jp; 182 | 183 | const struct VersionOriginal : GameVersion 184 | { 185 | VersionOriginal() 186 | { 187 | functions.GLOBAL_INIT = 0x45AC60; 188 | functions.WINMAIN = 0x42DA10; 189 | functions.DS_SLEEP = 0x61B588; 190 | functions.CD_CHECK = 0x432900; 191 | functions.CHOPPER_UI = 0x412440; 192 | functions.FLAP_UI = 0x4127D0; 193 | functions.CHOPPER_CLIP = 0x413090; 194 | functions.RES_LOOKUP = 0x460F20; 195 | functions.SCREEN_CLIP = 0x42DF90; 196 | functions.DDRAW_PALETTE = 0x41CA10; 197 | functions.HANGAR_MAIN = 0x43BD40; 198 | functions.UNK_RENDER_1 = 0x4496E0; 199 | functions.RENDER_SIMS = 0x4CF2C0; 200 | functions.CHOPPER_RENDER_UNK1 = 0x489480; 201 | functions.EMERGENCY_VEHICLE_RENDER_UNK1 = 0x491CB0; 202 | 203 | data.RES_TYPE = 0x4F8798; 204 | } 205 | } version_original; 206 | 207 | 208 | 209 | const struct VersionDeveloperDebug : GameVersion 210 | { 211 | VersionDeveloperDebug() 212 | { 213 | functions.GLOBAL_INIT = 0x4885ED; 214 | functions.WINMAIN = 0x41F870; 215 | functions.DS_SLEEP = 0x6C35D8; 216 | functions.RES_LOOKUP = 0x495B80; 217 | functions.SCREEN_CLIP = 0x432900; 218 | functions.DDRAW_PALETTE = 0x49F0FA; 219 | functions.HUNLOCK = 0x554A3D; 220 | functions.DS_PEEKMESSAGE = 0x6C3800; 221 | functions.DOASSERT = 0x554F30; 222 | functions.ASSERT = 0x56DA30; 223 | data.RES_TYPE = 0x598F00; 224 | } 225 | } version_debug; 226 | 227 | 228 | //The order of this matters 229 | enum GameVersions { VCLASSICS, V11SC, V102_PATCH, V11SC_FR, ORIGINAL, V10_JP, DEBUG }; 230 | static const GameVersion* const Versions[7] = 231 | { 232 | &version_classics, 233 | &version_11sc, 234 | &version_102patch, 235 | &version_11scfr, 236 | &version_original, 237 | &version_10jp, 238 | &version_debug 239 | }; -------------------------------------------------------------------------------- /SCXLauncher/SCXLauncher.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 15.0 23 | {92EB3627-F121-4FF9-8BEC-2101DD26A4D5} 24 | Win32Proj 25 | SCXLauncher 26 | 10.0 27 | SimCopterX 28 | 29 | 30 | 31 | Application 32 | true 33 | v143 34 | MultiByte 35 | 36 | 37 | Application 38 | false 39 | v143 40 | true 41 | MultiByte 42 | 43 | 44 | Application 45 | true 46 | v143 47 | Unicode 48 | 49 | 50 | Application 51 | false 52 | v143 53 | true 54 | Unicode 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | true 76 | 77 | 78 | true 79 | 80 | 81 | false 82 | 83 | 84 | false 85 | 86 | 87 | 88 | Level3 89 | Disabled 90 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 91 | true 92 | MultiThreadedDebug 93 | 94 | 95 | true 96 | Windows 97 | 98 | 99 | 100 | 101 | Level3 102 | Disabled 103 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 104 | true 105 | 106 | 107 | true 108 | Console 109 | 110 | 111 | 112 | 113 | Level3 114 | MaxSpeed 115 | true 116 | true 117 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 118 | true 119 | MultiThreaded 120 | stdcpp17 121 | 122 | 123 | true 124 | true 125 | true 126 | Windows 127 | RequireAdministrator 128 | 129 | 130 | 131 | 132 | Level3 133 | MaxSpeed 134 | true 135 | true 136 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 137 | true 138 | 139 | 140 | true 141 | true 142 | true 143 | Console 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | -------------------------------------------------------------------------------- /SCXLauncher/SCXLauncher.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include //Button_SetCheck macro 4 | #include //CommCtrl includes sliders 5 | #include 6 | #include "resource.h" 7 | #include "SCXLoader.h" 8 | #include "Settings.h" 9 | #include "GameVersionInfo.h" 10 | 11 | static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); 12 | void initialize(HINSTANCE hInstance); 13 | void enable_settings(bool, bool); 14 | void refresh(); 15 | void update_state(); 16 | void get_settings(); 17 | void update_sleep_bar(); 18 | SCXParameters GetParameters(); 19 | 20 | namespace 21 | { 22 | LPCSTR ResolutionOptions[4] = { "[4:3] 640x480 (Original)", "[4:3] 1024x768", "[16:9] 1280x720", "[16:10] 1280x800" }; 23 | unsigned int SpeedValues[9] = { 1, 4, 8, 12, 16, 20, 24, 32, 64 }; 24 | 25 | HWND PatchButton; 26 | HWND InstallButton; 27 | HWND StartButton; 28 | HWND settingsHwnd; 29 | 30 | HWND fsRadioButton; 31 | HWND wsRadioButton; 32 | 33 | HWND speedTextbox; 34 | HWND versionTextbox; 35 | HWND resolutionCombobox; 36 | 37 | SettingsInfo info; 38 | 39 | int resolutionValue = 0; 40 | bool fullscreenValue = true; 41 | 42 | HWND SleepBar; 43 | HWND resolutionTextbox; 44 | 45 | WNDCLASSEX SettingsClass; 46 | 47 | bool CanEnd = false; 48 | bool end_process = false; 49 | 50 | int speedMS = 16; 51 | HBITMAP hBitmap = NULL; 52 | } 53 | 54 | void update_sleep_bar() 55 | { 56 | if (info.SleepTime && info.SleepTime <= (sizeof(SpeedValues) / 4)) 57 | speedMS = SpeedValues[info.SleepTime - 1]; 58 | 59 | std::string speedText("Game Sleep: "); 60 | speedText.append(std::to_string(speedMS)).append("ms"); 61 | SetWindowText(speedTextbox, speedText.c_str()); 62 | UpdateWindow(speedTextbox); 63 | } 64 | 65 | void get_settings() 66 | { 67 | info = Settings::GetSettingsInfo(); 68 | SendMessage(SleepBar, TBM_SETPOS, WPARAM(TRUE), LPARAM(info.SleepTime)); 69 | SendMessage(resolutionCombobox, CB_SETCURSEL, (WPARAM)(info.Resolution), (LPARAM)0); 70 | resolutionValue = info.Resolution; 71 | if (info.ScreenMode) 72 | { 73 | Button_SetCheck(fsRadioButton, BST_CHECKED); 74 | Button_SetCheck(wsRadioButton, BST_UNCHECKED); 75 | } 76 | else 77 | { 78 | Button_SetCheck(fsRadioButton, BST_UNCHECKED); 79 | Button_SetCheck(wsRadioButton, BST_CHECKED); 80 | } 81 | update_sleep_bar(); 82 | refresh(); 83 | } 84 | 85 | SCXParameters GetParameters() 86 | { 87 | SCXParameters parameters; 88 | parameters.fullscreen = SendMessage(fsRadioButton, BM_GETCHECK, 0, 0) == BST_CHECKED; 89 | info.ScreenMode = parameters.fullscreen; 90 | 91 | switch (resolutionValue) 92 | { 93 | case 0: //640x480 94 | parameters.resolution_mode = 1; 95 | break; 96 | case 1: //1024x768 97 | parameters.resolution_mode = 3; 98 | break; 99 | case 2: //1280x720 100 | parameters.resolution_mode = 2; 101 | break; 102 | case 3: //1280x800 103 | parameters.resolution_mode = 0; 104 | break; 105 | default: 106 | printf("Error: Invalid resolution combobox selection \n"); 107 | parameters.resolution_mode = 1; 108 | break; 109 | } 110 | parameters.sleep_time = speedMS; 111 | return parameters; 112 | } 113 | 114 | void update_patch() 115 | { 116 | const PatchInfo& patchInfo = SCXLoader::GetPatchInfo(); 117 | if (!patchInfo.IsPatched()) 118 | { 119 | SetWindowText(versionTextbox, "Patch and Play Settings"); 120 | UpdateWindow(versionTextbox); 121 | return; 122 | } 123 | else if (patchInfo.PatchedGameVersion == GameVersions::DEBUG) 124 | { //Make this perhaps a little more intuitive 125 | SendMessage(resolutionCombobox, CB_SETCURSEL, (WPARAM)0, (LPARAM)0); 126 | ComboBox_Enable(resolutionCombobox, FALSE); 127 | resolutionValue = 0; 128 | info.Resolution = 0; 129 | } 130 | std::string versionText; 131 | std::string friendlyName = GameVersionInfoMap.at(patchInfo.PatchedGameVersion).friendlyName; 132 | if (patchInfo.PatchedGameIsInstalled) 133 | { 134 | versionText.append("Patch and Play Settings"); 135 | } 136 | else 137 | { 138 | versionText.append(friendlyName); 139 | versionText.append(" (Not Installed)"); 140 | } 141 | SetWindowText(versionTextbox, versionText.c_str()); 142 | UpdateWindow(versionTextbox); 143 | refresh(); 144 | } 145 | 146 | 147 | int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) 148 | { 149 | HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0); 150 | hBitmap = LoadBitmap(hInstance, MAKEINTRESOURCE(IDB_BITMAP1)); 151 | initialize(hInstance); 152 | PatchInfo info = Settings::GetPatchInfo(); 153 | 154 | SCXLoader::LoadSettings(); 155 | get_settings(); 156 | update_state(); 157 | update_patch(); 158 | 159 | MSG msg; 160 | while (!end_process && GetMessage(&msg, NULL, 0, 0)) 161 | { 162 | TranslateMessage(&msg); 163 | DispatchMessage(&msg); 164 | } 165 | return 0; 166 | } 167 | 168 | void destroy() 169 | { 170 | if (settingsHwnd != NULL) 171 | DestroyWindow(settingsHwnd); 172 | } 173 | 174 | void update_state() 175 | { 176 | if (SCXLoader::GetValidInstallation()) 177 | { //Patched and installed 178 | Button_Enable(StartButton, TRUE); 179 | enable_settings(true, true); 180 | Button_Enable(InstallButton, FALSE); 181 | } 182 | else if (SCXLoader::GetPatchedSCXVersion() > 0) 183 | { //Patched but not installed 184 | Button_Enable(StartButton, FALSE); 185 | Button_Enable(InstallButton, TRUE); 186 | enable_settings(false, false); 187 | } 188 | else 189 | { //Not patched or installed 190 | Button_Enable(StartButton, FALSE); 191 | Button_Enable(InstallButton, FALSE); 192 | enable_settings(true, false); 193 | } 194 | } 195 | 196 | void refresh() 197 | { 198 | UpdateWindow(wsRadioButton); 199 | UpdateWindow(PatchButton); 200 | UpdateWindow(speedTextbox); 201 | UpdateWindow(versionTextbox); 202 | UpdateWindow(resolutionCombobox); 203 | UpdateWindow(StartButton); 204 | UpdateWindow(fsRadioButton); 205 | UpdateWindow(InstallButton); 206 | UpdateWindow(SleepBar); 207 | } 208 | 209 | void enable_settings(bool enableMods, bool enableArgs) 210 | { 211 | Button_Enable(fsRadioButton, enableArgs); 212 | Button_Enable(wsRadioButton, enableArgs); 213 | ComboBox_Enable(resolutionCombobox, enableMods); 214 | Edit_Enable(SleepBar, enableMods); 215 | Edit_Enable(speedTextbox, enableMods); 216 | refresh(); 217 | } 218 | 219 | void initialize(HINSTANCE hInstance) 220 | { 221 | SettingsClass.cbClsExtra = NULL; 222 | SettingsClass.cbWndExtra = NULL; 223 | SettingsClass.lpszMenuName = NULL; 224 | SettingsClass.hCursor = LoadCursor(NULL, IDC_ARROW); 225 | SettingsClass.hbrBackground = CreateSolidBrush(COLORREF(0xf0f0f0)); 226 | SettingsClass.cbSize = sizeof(WNDCLASSEX); 227 | SettingsClass.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON1)); 228 | SettingsClass.hIconSm = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON1)); 229 | SettingsClass.hInstance = hInstance; 230 | SettingsClass.lpfnWndProc = WndProc; 231 | SettingsClass.lpszClassName = "SASETTINGS"; 232 | SettingsClass.style = CS_HREDRAW | CS_VREDRAW; 233 | RegisterClassEx(&SettingsClass); 234 | 235 | unsigned int window_width = 400; 236 | unsigned int window_height = 300; 237 | unsigned int window_x = (GetSystemMetrics(SM_CXSCREEN) - window_width) / 2; 238 | unsigned int window_y = (GetSystemMetrics(SM_CYSCREEN) - window_height) / 2; 239 | 240 | settingsHwnd = CreateWindowEx( 241 | WS_EX_STATICEDGE, 242 | SettingsClass.lpszClassName, 243 | std::string("SimCopterX - Version " + std::to_string(SCXLoader::SCX_VERSION)).c_str(), 244 | WS_VISIBLE | WS_CLIPCHILDREN | WS_BORDER | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX, 245 | window_x, window_y, window_width, window_height, NULL, NULL, NULL, NULL); 246 | 247 | PatchButton = CreateWindow( 248 | "Button", "Patch Game", WS_VISIBLE | WS_CHILDWINDOW | BS_PUSHBUTTON, 249 | 20, 65, 150, 25, settingsHwnd, NULL, 250 | NULL, NULL); 251 | 252 | InstallButton = CreateWindow( 253 | "Button", "Install Game", WS_VISIBLE | WS_CHILDWINDOW | BS_PUSHBUTTON, 254 | 210, 65, 150, 25, settingsHwnd, NULL, 255 | NULL, NULL); 256 | 257 | versionTextbox = CreateWindow("EDIT", "Select patch settings", 258 | WS_CHILD | WS_VISIBLE | ES_CENTER | ES_READONLY | ES_MULTILINE, 259 | 10, 105, 390, 20, settingsHwnd, NULL, NULL, NULL); 260 | 261 | resolutionCombobox = CreateWindow( 262 | "COMBOBOX", "", WS_VISIBLE | WS_CHILDWINDOW | CBS_DROPDOWNLIST | CBS_HASSTRINGS | WS_BORDER, 263 | 10, 132 + 10, 195, 100, settingsHwnd, NULL, NULL, NULL); 264 | 265 | SleepBar = CreateWindow( 266 | TRACKBAR_CLASS, "TEST", WS_VISIBLE | WS_CHILD | TBS_HORZ | TBS_AUTOTICKS, 267 | 220, 144 + 10, 150, 20, settingsHwnd, NULL, NULL, NULL); 268 | 269 | speedTextbox = CreateWindow("EDIT", "Game Sleep: 16ms", 270 | WS_CHILD | WS_VISIBLE | ES_LEFT | ES_READONLY | ES_MULTILINE, 271 | 230, 174 + 10, 150, 20, settingsHwnd, NULL, NULL, NULL); 272 | 273 | fsRadioButton = CreateWindow( 274 | "Button", "Fullscreen", WS_VISIBLE | WS_CHILDWINDOW | BS_AUTORADIOBUTTON, 275 | 10, 170 + 10, 100, 25, settingsHwnd, NULL, 276 | NULL, NULL); 277 | Button_SetCheck(fsRadioButton, BST_CHECKED); 278 | 279 | wsRadioButton = CreateWindow( 280 | "Button", "Windowed", WS_VISIBLE | WS_CHILDWINDOW | BS_AUTORADIOBUTTON, 281 | 115, 170 + 10, 100, 25, settingsHwnd, NULL, 282 | NULL, NULL); 283 | 284 | StartButton = CreateWindow( 285 | "Button", "Dispatch!", WS_VISIBLE | WS_CHILDWINDOW | BS_PUSHBUTTON, 286 | 110, 225, 150, 25, settingsHwnd, NULL, 287 | NULL, NULL); 288 | 289 | SendMessage(SleepBar, TBM_SETRANGEMIN, WPARAM(FALSE), LPARAM(1)); 290 | SendMessage(SleepBar, TBM_SETRANGEMAX, WPARAM(FALSE), LPARAM(9)); 291 | SendMessage(SleepBar, TBM_SETPOS, WPARAM(FALSE), LPARAM(5)); 292 | SendMessage(SleepBar, TBM_SETTICFREQ, WPARAM(1), LPARAM(0)); 293 | 294 | 295 | for (int i = 0; i < sizeof(ResolutionOptions) / sizeof(LPCSTR); i++) 296 | SendMessage(resolutionCombobox, (UINT)CB_ADDSTRING, (WPARAM)0, (LPARAM)ResolutionOptions[i]); 297 | 298 | SendMessage(resolutionCombobox, CB_SETCURSEL, (WPARAM)0, (LPARAM)0); 299 | refresh(); 300 | } 301 | 302 | LRESULT CALLBACK WndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) 303 | { 304 | switch (Msg) 305 | { 306 | case WM_PAINT: 307 | PAINTSTRUCT ps; 308 | HDC hdc; 309 | BITMAP bitmap; 310 | HDC hdcMem; 311 | HGDIOBJ oldBitmap; 312 | 313 | hdc = BeginPaint(hWnd, &ps); 314 | 315 | hdcMem = CreateCompatibleDC(hdc); 316 | oldBitmap = SelectObject(hdcMem, hBitmap); 317 | 318 | GetObject(hBitmap, sizeof(bitmap), &bitmap); 319 | BitBlt(hdc, 90, 10, bitmap.bmWidth, bitmap.bmHeight, hdcMem, 0, 0, SRCCOPY); 320 | 321 | SelectObject(hdcMem, oldBitmap); 322 | DeleteDC(hdcMem); 323 | 324 | EndPaint(hWnd, &ps); 325 | return 0; 326 | case WM_ACTIVATE: 327 | refresh(); 328 | return 0; 329 | 330 | case WM_HSCROLL: 331 | { 332 | info.SleepTime = SendMessage((HWND)lParam, (UINT)TBM_GETPOS, (WPARAM)0, (LPARAM)0); 333 | speedMS = 16; 334 | update_sleep_bar(); 335 | } 336 | return 0; 337 | case WM_COMMAND: 338 | 339 | if ((HWND)lParam == resolutionCombobox) 340 | { 341 | if (HIWORD(wParam) == CBN_SELCHANGE) 342 | { 343 | info.Resolution = SendMessage((HWND)lParam, (UINT)CB_GETCURSEL, (WPARAM)0, (LPARAM)0); 344 | char ListItem[256]; 345 | SendMessage((HWND)lParam, (UINT)CB_GETLBTEXT, (WPARAM)info.Resolution, (LPARAM)ListItem); 346 | for (int i = 0; i < sizeof(ResolutionOptions) / sizeof(LPCSTR); i++) 347 | { 348 | if (_stricmp(std::string(ListItem).c_str(), ResolutionOptions[i]) == 0) 349 | { 350 | resolutionValue = i; 351 | break; 352 | } 353 | } 354 | } 355 | } 356 | else if ((HWND)lParam == resolutionTextbox) 357 | { 358 | ::SetFocus(NULL); 359 | } 360 | else if ((HWND)lParam == speedTextbox) 361 | { 362 | ::SetFocus(NULL); 363 | } 364 | else 365 | { 366 | switch (HIWORD(wParam)) 367 | { 368 | case BN_CLICKED: 369 | if ((HWND)lParam == PatchButton) 370 | { 371 | Button_Enable(PatchButton, FALSE); 372 | ::SetFocus(NULL); 373 | char szFile[256]; 374 | OPENFILENAME ofn; 375 | ZeroMemory(&ofn, sizeof(ofn)); 376 | ofn.lStructSize = sizeof(ofn); 377 | ofn.hwndOwner = NULL; 378 | ofn.lpstrFile = szFile; 379 | ofn.lpstrFile[0] = '\0'; 380 | ofn.nMaxFile = sizeof(szFile); 381 | ofn.lpstrFilter = "SimCopter.exe\0SimCopter.exe;COPTER_D.EXE\0"; 382 | ofn.nFilterIndex = 1; 383 | ofn.lpstrFileTitle = NULL; 384 | ofn.nMaxFileTitle = 0; 385 | ofn.lpstrInitialDir = NULL; 386 | ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST; 387 | GetOpenFileName(&ofn); 388 | SCXParameters params = GetParameters(); //the window resolution is not working 389 | bool patchSuccess = SCXLoader::CreatePatchedGame(ofn.lpstrFile, params); 390 | update_patch(); 391 | if(patchSuccess) 392 | Settings::SetSettingsInfo(info); 393 | update_state(); 394 | Button_Enable(PatchButton, TRUE); 395 | refresh(); 396 | } 397 | else if ((HWND)lParam == InstallButton) 398 | { 399 | SCXLoader::InstallGame(); 400 | update_state(); 401 | update_patch(); 402 | } 403 | else if ((HWND)lParam == StartButton) 404 | { 405 | if (SCXLoader::StartSCX(GetParameters())) 406 | { 407 | Settings::SetSettingsInfo(info); 408 | end_process = true; 409 | } 410 | ::SetFocus(NULL); 411 | UpdateWindow(StartButton); 412 | } 413 | else if ((HWND)lParam == InstallButton) 414 | { 415 | ::SetFocus(NULL); 416 | UpdateWindow(InstallButton); 417 | } 418 | } 419 | } 420 | return 0; 421 | case WM_DESTROY: 422 | end_process = true; 423 | return 0; 424 | default: 425 | return DefWindowProc(hWnd, Msg, wParam, lParam); 426 | } 427 | } -------------------------------------------------------------------------------- /SCXLauncher/SCXLoader.cpp: -------------------------------------------------------------------------------- 1 | #include "SCXLoader.h" 2 | #include "Settings.h" 3 | #include "GameVersionInfo.h" 4 | 5 | std::string LastErrorString(); 6 | MessageValue CreateMD5Hash(std::wstring); 7 | MessageValue VerifyOriginalGame(std::string, GameVersions&); 8 | MessageValue VerifyInstallationDirectory(const PatchInfo&, std::filesystem::path&); 9 | MessageValue VerifyInstallation(GameVersions version, std::filesystem::path); 10 | MessageValue CopyFileSafe(std::string source, std::string destination); 11 | 12 | namespace 13 | { 14 | const std::string GAME_FILE = "SimCopter.exe"; 15 | const std::string BACKUP_FILE = "SimCopter(Backup).exe"; 16 | const std::string PATCH_NAME = "SimCopterX"; 17 | const std::wstring PATCH_NAMEW = L"SimCopterX"; 18 | const std::string GAME_NAME = "SimCopter"; 19 | FileVersion fileVersion; 20 | PatchInfo patch_info; 21 | } 22 | 23 | const PatchInfo& SCXLoader::GetPatchInfo() 24 | { 25 | return patch_info; 26 | } 27 | 28 | bool SCXLoader::GetValidInstallation() 29 | { 30 | return patch_info.PatchedGameIsInstalled; 31 | } 32 | 33 | int SCXLoader::GetPatchedSCXVersion() 34 | { 35 | return patch_info.PatcherVersion; 36 | } 37 | 38 | bool SCXLoader::GetFileCompatability(std::wstring game_location) 39 | { 40 | RegistryKey rkey; 41 | rkey.hKey = HKEY_CURRENT_USER; 42 | rkey.SubKey = L"Software\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\\Layers"; 43 | 44 | HKEY hKey; 45 | LONG lRes = RegOpenKeyExW(rkey.hKey, rkey.SubKey.c_str(), 0, KEY_READ, &hKey); 46 | WCHAR lpData[512]; 47 | DWORD lpcbData = sizeof(lpData); 48 | std::wstring format_location(game_location); 49 | std::replace(format_location.begin(), format_location.end(), '/', '\\'); 50 | LSTATUS status = RegQueryValueExW(hKey, format_location.c_str(), 0, NULL, (LPBYTE)lpData, &lpcbData); 51 | if (status != ERROR_SUCCESS) 52 | { 53 | OutputDebugString(std::string("Registry Error: " + std::to_string((int)status) + "\n").c_str()); 54 | OutputDebugStringW(std::wstring(format_location + L"\n").c_str()); 55 | return false; 56 | } 57 | 58 | if (std::wstring(lpData).find(L"~ 256COLOR") == std::wstring::npos) 59 | { 60 | return Registry::SetValue(rkey, RegistryEntry(format_location, 61 | new RegistryValue(L"~ 256COLOR"))); 62 | } 63 | return true; 64 | } 65 | 66 | MessageValue VerifyOriginalGame(std::string source, GameVersions& version) 67 | { 68 | if (!PathFileExistsA(source.c_str())) 69 | { 70 | return MessageValue(FALSE, "The game does not exist at " + source + "\n"); 71 | } 72 | 73 | StringFileInfoMap sfiMap; 74 | MessageValue result = fileVersion.GetSCFileVersionInfo(source.c_str(), sfiMap); 75 | std::string sfi_result = ""; 76 | if (result.Value) 77 | { 78 | sfi_result += "\nFile Information\n"; 79 | for (auto sfit = sfiMap.begin(); sfit != sfiMap.end(); sfit++) 80 | { 81 | sfi_result += sfit->first + ": " + sfit->second + "\n"; 82 | } 83 | } 84 | else 85 | { 86 | sfi_result += "\nUnable to query the SimCopter.exe File Information because: \n"; 87 | sfi_result += result.Message; 88 | } 89 | 90 | MessageValue hash_check = CreateMD5Hash(std::wstring(source.begin(), source.end())); 91 | if (!hash_check.Value || !GetGameVersion(hash_check.Message, version)) 92 | { 93 | std::string reason; 94 | if (hash_check.Value) 95 | { 96 | reason = PATCH_NAME + " does not have a matching hash stored in the database of patched versions.\n"; 97 | reason += "Hash: " + hash_check.Message + "\n"; 98 | } 99 | else 100 | { 101 | reason = hash_check.Message + "\n"; 102 | } 103 | reason += sfi_result; 104 | return MessageValue(FALSE, reason); 105 | } 106 | 107 | return MessageValue(TRUE, sfi_result); 108 | } 109 | 110 | bool CreatePatchFile() 111 | { 112 | MessageValue hash_check = CreateMD5Hash(patch_info.PatchedGameLocation); 113 | if (!hash_check.Value) 114 | { 115 | ShowMessage(PATCH_NAME + " Error", hash_check.Message); 116 | return false; 117 | } 118 | patch_info.PatchedGameHash = std::wstring(hash_check.Message.begin(), hash_check.Message.end()); 119 | return Settings::SetPatchInfo(patch_info); 120 | } 121 | 122 | bool SCXLoader::InstallGame() 123 | { 124 | if (patch_info.PatcherVersion < 1 || patch_info.PatchedGameLocation.empty()) 125 | { 126 | return false; 127 | } 128 | 129 | MessageValue msg_verify; 130 | std::filesystem::path install_dir; 131 | if (!(msg_verify = VerifyInstallationDirectory(patch_info, install_dir)).Value) 132 | { 133 | ShowMessage(PATCH_NAME + " Installation", msg_verify.Message); 134 | return false; 135 | } 136 | 137 | MessageValue msg_install; 138 | if (!(msg_install = VerifyInstallation(patch_info.PatchedGameVersion, install_dir)).Value) 139 | { 140 | ShowMessage(PATCH_NAME + " Installation", msg_install.Message); 141 | return false; 142 | } 143 | 144 | patch_info.PatchedGameIsInstalled = true; 145 | return Settings::SetPatchInfo(patch_info); 146 | } 147 | 148 | bool SCXLoader::CreatePatchedGame(std::string game_location, SCXParameters params) 149 | { 150 | 151 | if (game_location.empty()) //Means nothing was selected 152 | return false; 153 | 154 | /* 155 | If the game we selected is not an original, check to see if our directory has an original. 156 | If the game we selected is an original, copy it to our SimCopterX directory 157 | */ 158 | bool using_backup_copy = false; 159 | MessageValue verifyCurrentValue; 160 | GameVersions version; 161 | 162 | std::filesystem::path exe_path; 163 | std::filesystem::path backup_exe_path; 164 | std::filesystem::path exe_parent_path; 165 | 166 | 167 | exe_path = std::filesystem::path(game_location); 168 | try 169 | { 170 | exe_path = std::filesystem::canonical(exe_path); 171 | exe_parent_path = exe_path.parent_path(); 172 | backup_exe_path = exe_parent_path; 173 | backup_exe_path.append(BACKUP_FILE); 174 | } 175 | catch (const std::exception& e) 176 | { 177 | printf("%s\n", e.what()); 178 | return false; 179 | } 180 | 181 | if (!(verifyCurrentValue = VerifyOriginalGame(exe_path.string(), version)).Value) 182 | { 183 | if (!VerifyOriginalGame(backup_exe_path.string(), version).Value) 184 | { 185 | std::string message_body = "The SimCopter game you selected isn't supported or is already modified/patched. " + PATCH_NAME + " "; 186 | message_body += "could not find an original backup. If this is an official version of the game, please submit the following "; 187 | message_body += "file information so it can be supported.\n\n"; 188 | message_body += verifyCurrentValue.Message; 189 | ShowMessage(PATCH_NAME + " Error", message_body); 190 | return false; 191 | } 192 | MessageValue copy_result = CopyFileSafe(backup_exe_path.string(), exe_path.string()); 193 | if (!copy_result.Value) 194 | { 195 | ShowMessage(LastErrorString(), copy_result.Message); 196 | return false; 197 | } 198 | using_backup_copy = true; 199 | } 200 | else 201 | { 202 | MessageValue copy_result = CopyFileSafe(exe_path.string(), backup_exe_path.string()); 203 | if (!copy_result.Value) 204 | { 205 | ShowMessage(LastErrorString(), copy_result.Message); 206 | return false; 207 | } 208 | } 209 | 210 | PEINFO info; 211 | if (!Patcher::CreateDetourSection(exe_path.string().c_str(), &info)) 212 | { 213 | ShowMessage(PATCH_NAME + " Patch Failed", "Failed to modify the game's executable file.\n Make sure the game isn't running or opened in another application"); 214 | return false; 215 | } 216 | 217 | std::vector instructions = GameData::GenerateData(info, version); 218 | DWORD sleep_address = info.GetDetourVirtualAddress(DetourOffsetType::MY_SLEEP); 219 | instructions.push_back(DataValue(sleep_address, BYTE(params.sleep_time))); 220 | if (version != GameVersions::DEBUG) 221 | { 222 | BYTE resolutionMode = (params.resolution_mode); 223 | DWORD res_address = Versions[version]->data.RES_TYPE; 224 | instructions.push_back(DataValue(res_address, resolutionMode)); 225 | } 226 | 227 | if (!Patcher::Patch(info, instructions, exe_path.string())) 228 | { 229 | ShowMessage(PATCH_NAME + " Patch Failed", "Failed to patch the game file.\n Make sure the game isn't running or opened in another application"); 230 | return false; 231 | } 232 | 233 | MessageValue hash_check = CreateMD5Hash(exe_path.wstring()); 234 | if (!hash_check.Value) 235 | { 236 | ShowMessage(PATCH_NAME + " Error", hash_check.Message); 237 | return false; 238 | } 239 | 240 | patch_info.PatchedGameHash = std::wstring(hash_check.Message.begin(), hash_check.Message.end()); 241 | patch_info.PatchedGameLocation = exe_path.wstring(); 242 | patch_info.PatcherVersion = SCX_VERSION; 243 | patch_info.PatchedGameVersion = version; 244 | patch_info.PatchedGameIsInstalled = false; 245 | bool reg_result = Settings::SetPatchInfo(patch_info); 246 | 247 | std::string message = ""; 248 | if (using_backup_copy) 249 | message += "Used Backup:\n" + CreateVersionString(version) + "\n\n"; 250 | else 251 | message += "Detected Version:\n" + CreateVersionString(version) + "\n\n"; 252 | 253 | message += "Patch location: \n" + game_location + "\n\n"; 254 | if (reg_result) 255 | message += "Patch was successful!\nIf you move or modify this file, then you cannot install.\n"; 256 | else 257 | message += "Patch was successful!\nThe patch info couldn't be stored to your registry.\n" 258 | "You will not be able to install the game.\n"; 259 | 260 | ShowMessage(PATCH_NAME + " Patch Successful!", message); 261 | return true; 262 | } 263 | 264 | void ClearPatchInfo(std::wstring reason) 265 | { 266 | std::string title = PATCH_NAME + " Error"; 267 | ShowMessage(std::wstring(title.begin(), title.end()), reason); 268 | patch_info = PatchInfo(); 269 | Settings::ClearPatchInfo(); 270 | } 271 | 272 | MessageValue VerifyInstallationDirectory(const PatchInfo& patchInfo, std::filesystem::path& install_dir) 273 | { 274 | if (patchInfo.PatchedGameVersion == GameVersions::DEBUG) 275 | { //This game does not come from a CD-ROM 276 | return MessageValue(TRUE); 277 | } 278 | std::wstring game_location = patchInfo.PatchedGameLocation; 279 | std::filesystem::path exe_path; 280 | std::filesystem::path root_path; 281 | std::filesystem::path autorun_path; 282 | try 283 | { 284 | exe_path = std::filesystem::path(game_location); 285 | root_path = exe_path.parent_path().parent_path(); 286 | root_path = std::filesystem::canonical(root_path); 287 | autorun_path = root_path; 288 | autorun_path.append("autorun.inf"); 289 | } 290 | catch (const std::exception& e) 291 | { 292 | return MessageValue(FALSE, "The game does not appear to be on a valid extracted CD"); 293 | printf("%s\n", e.what()); 294 | } 295 | 296 | OutputDebugString(std::string("Checking: " + autorun_path.string() + "\n").c_str()); 297 | if (!PathFileExistsA(autorun_path.string().c_str())) 298 | { 299 | return MessageValue(FALSE, "The game does not appear to be on a valid extracted CD, missing autorun.inf"); 300 | } 301 | install_dir = root_path; 302 | return MessageValue(TRUE); 303 | } 304 | 305 | 306 | bool VerifyPatchedGame() 307 | { 308 | if (!PathFileExistsW(patch_info.PatchedGameLocation.c_str())) 309 | { 310 | ClearPatchInfo(std::wstring(L"The game no longer exists where we patched it:\n" + 311 | patch_info.PatchedGameLocation + L"\nPlease try repatching.").c_str()); 312 | return false; 313 | } 314 | 315 | MessageValue hash_check = CreateMD5Hash(patch_info.PatchedGameLocation); 316 | if (!hash_check.Value) 317 | { 318 | ShowMessage(PATCH_NAME + " Error", hash_check.Message); 319 | return false; 320 | } 321 | 322 | std::wstring hash_checkw(hash_check.Message.begin(), hash_check.Message.end()); 323 | 324 | if (hash_checkw.compare(patch_info.PatchedGameHash) != 0) 325 | { 326 | ClearPatchInfo(L"The patched game doesn't have a matching hash, this can happen " 327 | "if you modified the patched game or restored it back to the " 328 | "original game. Please try repatching."); 329 | return false; 330 | } 331 | 332 | if (patch_info.PatcherVersion != SCXLoader::SCX_VERSION) 333 | { 334 | ClearPatchInfo(std::wstring(L"You currently have " + PATCH_NAMEW + 335 | L" Version " + std::to_wstring(SCXLoader::SCX_VERSION) + 336 | L" however the game was \npreviously patched using Version " + 337 | std::to_wstring(patch_info.PatcherVersion) + L". Please repatch the game.")); 338 | return false; 339 | } 340 | 341 | return true; 342 | } 343 | 344 | bool SCXLoader::StartSCX(SCXParameters params) 345 | { 346 | 347 | if (patch_info.PatcherVersion < 0) 348 | { //This shouldn't happen because the button should not be enabled 349 | ShowMessage(PATCH_NAME + " Error", "You need to patch the game before starting."); 350 | return false; 351 | } 352 | 353 | if (!patch_info.PatchedGameIsInstalled) 354 | { //This shouldn't happen because the button should not be enabled 355 | ShowMessage(PATCH_NAME + " Error", "You need to patch the game using 'Verify Install'"); 356 | return false; 357 | } 358 | 359 | if (!VerifyPatchedGame()) 360 | { 361 | return false; 362 | } 363 | 364 | if (!params.fullscreen && !GetFileCompatability(patch_info.PatchedGameLocation)) 365 | { 366 | const char* message = 367 | "You must run the SimCopter.exe in 8-bit color to use Windowed mode!\n\n" 368 | "1. Right Click SimCopter.exe -> Properties\n" 369 | "2. Select the 'Compatibility' tab\n" 370 | "3. Enable 'Reduced color mode' and select 8-bit/256 color\n" 371 | "4. Ensure all other options are NOT selected\n" 372 | "5. Click apply, then try again"; 373 | ShowMessage(PATCH_NAME, std::string(message)); 374 | return false; 375 | } 376 | 377 | std::string game_location = std::filesystem::path(patch_info.PatchedGameLocation).string(); 378 | PEINFO info; 379 | if (!Patcher::CreateDetourSection(game_location.c_str(), &info)) 380 | { //Should grab detour section 381 | ShowMessage(PATCH_NAME + " Patch Failed", "Failed to modify the game's executable file.\n Make sure the game isn't running or opened in another application"); 382 | return false; 383 | } 384 | 385 | std::vector instructions; 386 | DWORD sleep_address = info.GetDetourVirtualAddress(DetourOffsetType::MY_SLEEP); 387 | instructions.push_back(DataValue(sleep_address, BYTE(params.sleep_time))); 388 | if (patch_info.PatchedGameVersion != GameVersions::DEBUG) 389 | { 390 | DWORD res_address = Versions[patch_info.PatchedGameVersion]->data.RES_TYPE; 391 | instructions.push_back(DataValue(res_address, BYTE(params.resolution_mode))); 392 | } 393 | 394 | if (!Patcher::Patch(info, instructions, game_location.c_str())) 395 | { 396 | ShowMessage(PATCH_NAME + " Patch Failed", "Failed to patch the game file.\n Make sure the game isn't running or opened in another application"); 397 | return false; 398 | } 399 | 400 | //Can only get the hash at this point as a reference, can't use it to check for complete validty 401 | //because changing sleep time and resolution mode dwords will change the hash 402 | 403 | CreatePatchFile(); 404 | 405 | std::string parameters = params.fullscreen ? "-f" : "-w"; 406 | HINSTANCE hInstance = ShellExecuteA(NULL, "open", game_location.c_str(), parameters.c_str(), NULL, SW_SHOWDEFAULT); 407 | int h_result = reinterpret_cast(hInstance); 408 | if (h_result <= 31) 409 | { 410 | ShowMessage(std::string(PATCH_NAME + " Error (" + std::to_string(h_result) + ")"), std::string("Failed to start the patched game at: \n" + game_location)); 411 | return false; 412 | } 413 | return true; 414 | } 415 | 416 | bool SCXLoader::FixMaxisHelpViewer(std::filesystem::path path) 417 | { 418 | std::string webste32 = path.string() + "/setup/webste32/"; 419 | std::vector webste_files = { "regsvr32.exe", "webster.ocx" }; 420 | for (std::string file : webste_files) 421 | { 422 | if (!PathFileExistsA(std::string(webste32 + file).c_str())) 423 | { 424 | return false; 425 | } 426 | } 427 | std::string executable = webste32 + "regsvr32.exe"; 428 | std::string parameters = "/s /i " + webste32 + "webster.ocx"; 429 | HINSTANCE hInstance = ShellExecuteA(NULL, "open", executable.c_str(), parameters.c_str(), NULL, SW_HIDE); 430 | return reinterpret_cast(hInstance) > 32; 431 | } 432 | 433 | void SCXLoader::LoadSettings() 434 | { 435 | patch_info = Settings::GetPatchInfo(); 436 | if (patch_info.IsPatched()) 437 | { 438 | VerifyPatchedGame(); 439 | } 440 | } 441 | 442 | MessageValue CreateMD5Hash(std::wstring filename_wstring) 443 | { 444 | //LPCWSTR filename = filename_wstring.c_str(); 445 | 446 | DWORD cbHash = 16; 447 | HCRYPTHASH hHash = 0; 448 | HCRYPTPROV hProv = 0; 449 | BYTE rgbHash[16]; 450 | CHAR rgbDigits[] = "0123456789abcdef"; 451 | HANDLE hFile = CreateFileW(filename_wstring.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, 452 | OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL); 453 | int v = GetLastError(); 454 | printf("%d\n", v); 455 | 456 | if (hFile == INVALID_HANDLE_VALUE) 457 | { 458 | std::string error_message = "Failed to retrieve the MD5 Hash of the program:\n"; 459 | error_message += "CreateFileW has an invalid handle.\n"; 460 | return MessageValue(FALSE, error_message); 461 | } 462 | 463 | CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT); 464 | CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash); 465 | 466 | BOOL bResult = FALSE; 467 | DWORD BUFSIZE = 4096; 468 | BYTE rgbFile[4096]; 469 | DWORD cbRead = 0; 470 | while (bResult = ReadFile(hFile, rgbFile, BUFSIZE, &cbRead, NULL)) 471 | { 472 | if (0 == cbRead) 473 | break; 474 | 475 | CryptHashData(hHash, rgbFile, cbRead, 0); 476 | } 477 | 478 | std::string md5_hash = ""; 479 | if (CryptGetHashParam(hHash, HP_HASHVAL, rgbHash, &cbHash, 0)) 480 | { 481 | for (DWORD i = 0; i < cbHash; i++) 482 | { 483 | char buffer[3]; //buffer needs terminating null 484 | sprintf_s(buffer, 3, "%c%c", rgbDigits[rgbHash[i] >> 4], rgbDigits[rgbHash[i] & 0xf]); 485 | md5_hash.append(buffer); 486 | } 487 | CryptDestroyHash(hHash); 488 | CryptReleaseContext(hProv, 0); 489 | CloseHandle(hFile); 490 | return MessageValue(TRUE, md5_hash); 491 | } 492 | else 493 | { 494 | CloseHandle(hFile); 495 | std::string error_message = "Failed to retrieve the MD5 Hash of the program:\n"; 496 | error_message += "CryptGetHashParam returned false.\n"; 497 | return MessageValue(FALSE, error_message); 498 | } 499 | } 500 | 501 | MessageValue VerifyInstallation(GameVersions version, std::filesystem::path install_dir) 502 | { 503 | if (version == GameVersions::DEBUG) 504 | { //This version was not installed from a CD-ROM 505 | return MessageValue(TRUE); 506 | } 507 | std::filesystem::path game_dir = std::filesystem::path(patch_info.PatchedGameLocation); 508 | try 509 | { 510 | game_dir = game_dir.parent_path(); 511 | game_dir = std::filesystem::canonical(game_dir); 512 | } 513 | catch (const std::exception& e) 514 | { 515 | return MessageValue(FALSE, std::string("Failed to copy:\n") + e.what()); 516 | } 517 | 518 | std::map dll_source = 519 | { 520 | {"smackw32.dll", "setup/smacker/"}, 521 | {"sst1init.dll", "setup/system/"}, 522 | {"glide.dll", "setup/system/"} 523 | }; 524 | 525 | std::map> dll_map = 526 | { 527 | { GameVersions::ORIGINAL, {"smackw32.dll"}}, 528 | { GameVersions::V10_JP, {"smackw32.dll"}}, 529 | { GameVersions::V11SC, {"smackw32.dll"}}, 530 | { GameVersions::V11SC_FR, {"smackw32.dll"}}, 531 | { GameVersions::V102_PATCH, {"sst1init.dll", "glide.dll", "smackw32.dll"}}, 532 | { GameVersions::VCLASSICS, {"sst1init.dll", "glide.dll", "smackw32.dll"}} 533 | }; 534 | 535 | for (std::string dll : dll_map[version]) 536 | { 537 | std::filesystem::path dll_path = install_dir; 538 | std::filesystem::path dll_dest = game_dir; 539 | try 540 | { 541 | dll_path.append(dll_source[dll] + dll); 542 | dll_dest.append(dll); 543 | dll_path = std::filesystem::canonical(dll_path); 544 | } 545 | catch (const std::exception& e) 546 | { 547 | return MessageValue(FALSE, std::string("Failed to copy:\n") + e.what()); 548 | } 549 | 550 | 551 | if (!PathFileExistsA(dll_path.string().c_str())) 552 | { 553 | std::string message = "Your game version is: \n" + CreateVersionString(version); 554 | message += "\n\nWe could not find the required dll: " + dll + "\nExpected location:\n"; 555 | message += dll_path.string() + "\n\n"; 556 | message += "This usually happens if you are trying to mismatch game versions with different CDs. "; 557 | message += "Try using the 'Classics CD', it should have all the dlls so you can use any game version.\n"; 558 | return MessageValue(FALSE, message); 559 | } 560 | 561 | MessageValue copy_result = CopyFileSafe(dll_path.string(), dll_dest.string()); 562 | if (!copy_result.Value) 563 | { 564 | return copy_result; 565 | } 566 | } 567 | SCXLoader::FixMaxisHelpViewer(install_dir); 568 | 569 | return MessageValue(TRUE); 570 | } 571 | 572 | MessageValue CopyFileSafe(std::string source, std::string destination) 573 | { 574 | DWORD attributes = GetFileAttributes(source.c_str()); 575 | if (attributes == INVALID_FILE_ATTRIBUTES) 576 | { 577 | return MessageValue(FALSE, "Failed to GET file attributes for:\n" + source + "\n"); 578 | } 579 | 580 | if (attributes & (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN)) 581 | { 582 | if (SetFileAttributes(source.c_str(), FILE_ATTRIBUTE_NORMAL) == 0) 583 | { 584 | return MessageValue(FALSE, "Failed to SET(" + std::to_string(GetLastError()) + 585 | ") file attributes for:\n" + source + "\n"); 586 | } 587 | OutputDebugString(std::string("Reset file attributes for: " + source + "\n").c_str()); 588 | } 589 | 590 | if (!CopyFileA(source.c_str(), destination.c_str(), FALSE)) 591 | { 592 | char buffer[512]; 593 | snprintf(buffer, sizeof(buffer), "Failed to copy:\n%s\n\nDestination:\n%s\n\nError Code: %d\n\n", 594 | source.c_str(), destination.c_str(), GetLastError()); 595 | return MessageValue(FALSE, std::string(buffer)); 596 | } 597 | OutputDebugString(std::string("Copied " + source + " to: " + destination + "\n").c_str()); 598 | return MessageValue(TRUE); 599 | } 600 | 601 | -------------------------------------------------------------------------------- /SCXLauncher/GameData.cpp: -------------------------------------------------------------------------------- 1 | #include "GameData.h" 2 | 3 | std::vector GameData::GenerateData(PEINFO info, GameVersions version) 4 | { 5 | DetourMaster* master = new DetourMaster(info); 6 | 7 | //PatchHUnlock(master, version); 8 | PatchAssertions(master, version); 9 | CreateSleepFunction(master, version); 10 | if (version != GameVersions::DEBUG) 11 | { 12 | CreateGlobalInitFunction(master, version); 13 | CreateResolutionFunction(master, version); 14 | CreateCDFunction(master, version); 15 | CreateChopperUIFunction(master, version); 16 | CreateFlapUIFunction(master, version); 17 | CreateChopperClipFunction(master, version); 18 | CreateScreenClipFunction(master, version); 19 | CreateDDrawPaletteFunction(master, version); 20 | CreateHangarMainFunction(master, version); 21 | CreateMapCheatFunction(master, version); 22 | RenderSimsFunction(master, version); 23 | PatchChopperDamageFunction(master, version); 24 | PatchEmergencyVehicleCrashOnRamp(master, version); 25 | } 26 | std::vector ret_ins(master->instructions); 27 | delete master; 28 | return ret_ins; 29 | } 30 | 31 | void GameData::CreateDDrawPaletteFunction(DetourMaster* master, GameVersions version) 32 | { 33 | //Always use GetSystemPaletteEntries flow instead of manually generating the palette entries 34 | //Previously chose flow based on windowed vs fullscreen (windowed manually generated?) 35 | DWORD function_entry = Versions[version]->functions.DDRAW_PALETTE; 36 | DWORD rewrite_start; 37 | switch (version) 38 | { 39 | case GameVersions::V11SC: 40 | case GameVersions::V11SC_FR: 41 | case GameVersions::ORIGINAL: 42 | case GameVersions::V10_JP: 43 | rewrite_start = function_entry + 0x32; 44 | break; 45 | case GameVersions::V102_PATCH: 46 | case GameVersions::VCLASSICS: 47 | rewrite_start = function_entry + 0x3F; 48 | break; 49 | } 50 | 51 | Instructions instructions(rewrite_start); 52 | instructions << ByteArray{ 0xEB, 0x07 }; //jmp short 0x07 bytes 53 | instructions << ByteArray{ 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90 }; //Clean up debugging view 54 | 55 | size_t is_size = instructions.GetInstructions().size(); 56 | printf("[DDraw Palette] Generated a total of %d bytes\n", is_size); 57 | master->instructions.push_back(instructions); 58 | } 59 | 60 | void GameData::CreateScreenClipFunction(DetourMaster* master, GameVersions version) 61 | { 62 | DWORD function_entry = Versions[version]->functions.SCREEN_CLIP; 63 | DWORD function_res = Versions[version]->functions.RES_LOOKUP; 64 | 65 | DWORD rewrite_start; 66 | switch (version) 67 | { 68 | case GameVersions::V11SC: 69 | case GameVersions::V11SC_FR: 70 | case GameVersions::ORIGINAL: 71 | case GameVersions::V10_JP: 72 | rewrite_start = function_entry + 0x151; 73 | break; 74 | case GameVersions::V102_PATCH: 75 | case GameVersions::VCLASSICS: 76 | rewrite_start = function_entry + 0x15E; 77 | break; 78 | } 79 | 80 | //To make this simple from my IDA conversion, just going to use segment overrides for now.. 81 | Instructions instructions(rewrite_start); //Start overwriting where the comparison is for res type 82 | instructions << ByteArray{ 0x36, 0x8D, 0x4C, 0x24, 0x14 }; //lea ecx, [esp+0x14] 83 | instructions << ByteArray{ 0x36, 0x8D, 0x44, 0x24, 0x18 }; //lea eax, [esp+0x18] 84 | instructions << BYTE(0x50); //push eax 85 | instructions << BYTE(0x51); //push ecx 86 | instructions.call(function_res); //call 87 | instructions << ByteArray{ 0x31, 0xC0 }; //xor eax, eax 88 | instructions << ByteArray{ 0x36, 0x89, 0x44, 0x24, 0x0C }; //mov [esp+0xC], eax 89 | instructions << ByteArray{ 0x36, 0x89, 0x44, 0x24, 0x10 }; //mov [esp+0x10], eax 90 | instructions << ByteArray{ 0xEB, 0x4E }; //jump back to control block 91 | 92 | size_t is_size = instructions.GetInstructions().size(); 93 | printf("[Screen Clip] Generated a total of %d bytes\n", is_size); 94 | master->instructions.push_back(instructions); 95 | } 96 | 97 | void GameData::CreateChopperClipFunction(DetourMaster* master, GameVersions version) 98 | { 99 | DWORD function_entry = Versions[version]->functions.CHOPPER_CLIP; 100 | Instructions instructions(function_entry + 0x25); 101 | instructions << BYTE(0x01); //Changes cmp , 0 to 1 (1 being original resolution) 102 | instructions << ByteArray{ 0x74, 0x7 }; //jz (short) 7 bytes 103 | 104 | //Not Resolution 1, don't subtract 105 | instructions << ByteArray{ 0x8D, 0x08 }; //lea ecx, [eax] 106 | instructions << ByteArray{ 0x89, 0x4E, 0x24 }; //mov [esi+0x24], ecx 107 | instructions << ByteArray{ 0xEB, 0x41 }; //jmp (short) 0x41 bytes jumps to a block after the subtraction 108 | 109 | //Resolution 1 (original), subtract like original (-80 from width and height) 110 | instructions << ByteArray{ 0x8D, 0x48, 0xB0 }; //lea ecx [eax-0x50] 111 | instructions << ByteArray{ 0x83, 0xEA, 0x50 }; //sub edx, 0x50 112 | instructions << ByteArray{ 0x89, 0x4E, 0x24 }; //mov [esi+24], ecx 113 | instructions << ByteArray{ 0xEB, 0x36 }; //jmp short 0x36 bytes jumps to a block after the subtraction 114 | 115 | size_t is_size = instructions.GetInstructions().size(); 116 | printf("[Chopper View Clip] Generated a total of %d bytes\n", is_size); 117 | master->instructions.push_back(instructions); 118 | } 119 | 120 | void GameData::CreateFlapUIFunction(DetourMaster* master, GameVersions version) 121 | { 122 | DWORD function_entry = Versions[version]->functions.FLAP_UI; 123 | DWORD rewrite_start; 124 | switch (version) 125 | { 126 | case GameVersions::V11SC: 127 | case GameVersions::V11SC_FR: 128 | case GameVersions::ORIGINAL: 129 | case GameVersions::V10_JP: 130 | rewrite_start = function_entry + 0xC; 131 | break; 132 | case GameVersions::V102_PATCH: 133 | case GameVersions::VCLASSICS: 134 | rewrite_start = function_entry + 0x19; 135 | break; 136 | } 137 | 138 | //We're going to be overwriting the instructions to compare resolution type and jnz 139 | 140 | //83 3D D0 17 50 00 01 , comparison 141 | //75 6A , jnz 142 | 143 | Instructions instructions(rewrite_start); 144 | instructions << ByteArray{ 0x8B, 0xF8 }; //mov edi, eax 145 | instructions << ByteArray{ 0x8B, 0x41, 0x1C }; //mov eax, [ecx+0x1C] loads screen width into eax 146 | instructions << ByteArray{ 0x2D, 0x8A, 0x00, 0x00, 0x00 }; //sub eax, 0x8A subtract 138px (flap img width) from width 147 | instructions << BYTE(0x90); //nop buffer 148 | instructions << ByteArray{ 0x90, 0x90, 0x90, 0x90, 0x90 }; //nops out the 'mov eax, 0x1F6' (original x value) 149 | 150 | size_t is_size = instructions.GetInstructions().size(); 151 | printf("[Chopper Flap UI] Generated a total of %d bytes\n", is_size); 152 | master->instructions.push_back(instructions); 153 | } 154 | 155 | void GameData::CreateMapCheatFunction(DetourMaster* master, GameVersions version) 156 | { 157 | DWORD res_dword = Versions[version]->data.RES_TYPE; 158 | DWORD function_entry = Versions[version]->functions.UNK_RENDER_1; 159 | 160 | DWORD rewrite_start = function_entry + 0x1FA; 161 | DWORD detour_return = function_entry + 0x204; 162 | if (version == GameVersions::V10_JP) 163 | { 164 | rewrite_start = function_entry + 0x202; 165 | detour_return = function_entry + 0x20C; 166 | } 167 | 168 | Instructions instructions(rewrite_start); 169 | instructions.jmp(master->GetNextDetour()); 170 | instructions.cmp(res_dword, 0x1); 171 | instructions << ByteArray{ 0x75, 0x0F }; //jnz 172 | instructions << BYTE(0x68); 173 | instructions << DWORD(0x144); 174 | instructions << BYTE(0x68); 175 | instructions << DWORD(0x200); 176 | instructions.jmp(detour_return, FALSE); 177 | instructions << ByteArray{ 0x8B, 0x8E, 0xD4, 0x00, 0x00, 0x00 }; // mov ecx, [esi+0xD4] //Screen height 178 | instructions << ByteArray{ 0x83, 0xE9, 0x68 }; //sub ecx, 102. Map = 124 x 98, create 4 px buffer 179 | instructions << BYTE(0x51); //push ecx 180 | instructions << ByteArray{ 0x6A, 0x04 }; //push 0x2 181 | instructions.jmp(detour_return, FALSE); 182 | 183 | size_t is_size = instructions.GetInstructions().size(); 184 | master->SetLastDetourSize(is_size); 185 | printf("[Map Cheat] Generated a total of %d bytes\n", is_size); 186 | master->instructions.push_back(instructions); 187 | } 188 | 189 | void GameData::CreateChopperUIFunction(DetourMaster* master, GameVersions version) 190 | { 191 | DWORD function_entry = Versions[version]->functions.CHOPPER_UI; 192 | DWORD res_dword = Versions[version]->data.RES_TYPE; 193 | DWORD detour_return; 194 | 195 | 196 | //Classics version has a resolution variable check which consistutes a 0xD size difference between the functions 197 | //Unfortunately the instructions are not after our patch. 198 | switch (version) 199 | { 200 | case GameVersions::V11SC: 201 | case GameVersions::V11SC_FR: 202 | case GameVersions::ORIGINAL: 203 | case GameVersions::V10_JP: 204 | detour_return = function_entry + 0xF2; 205 | break; 206 | case GameVersions::V102_PATCH: 207 | case GameVersions::VCLASSICS: 208 | detour_return = function_entry + 0xFF; 209 | break; 210 | } 211 | 212 | Instructions instructions(DWORD(function_entry + 0x13)); 213 | instructions << BYTE(0x90); //not necessary, just makes it look nicer in graph views 214 | instructions.jmp(master->GetNextDetour()); 215 | 216 | instructions << ByteArray{ 0xB8, 0x10, 0x00, 0x00, 0x00 }; //mov eax, 10 217 | instructions << ByteArray{ 0xB9, 0x3E, 0x00, 0x00, 0x00 }; //mov ecx, 3E 218 | instructions << ByteArray{ 0x89, 0x44, 0x24, 0x1C }; //mov [esp+0x1C], eax 219 | instructions << ByteArray{ 0x89, 0x44, 0x24, 0x20 }; //mov [esp+0x20], eax 220 | instructions << ByteArray{ 0xB8, 0x2C, 0x02, 0x00, 0x00 }; //mov eax, 22C 221 | instructions << ByteArray{ 0xBF, 0x80, 0x02, 0x00, 0x00 }; //mov edi, 280 222 | instructions << ByteArray{ 0x89, 0x44, 0x24, 0x2C }; //mov [esp+0x2C], eax 223 | instructions << ByteArray{ 0x89, 0x4C, 0x24, 0x30 }; //mov [esp+0x30], ecx 224 | instructions << ByteArray{ 0xBB, 0x24, 0x01, 0x00, 0x00 }; //mov ebx, 124 225 | instructions << ByteArray{ 0x89, 0x7C, 0x24, 0x3C }; //mov [esp+0x3C], edi 226 | instructions << ByteArray{ 0x89, 0x5C, 0x24, 0x40 }; //mov [esp+0x40], ebx 227 | instructions << ByteArray{ 0x89, 0x44, 0x24, 0x4C }; //mov [esp+0x4C], eax 228 | instructions << ByteArray{ 0x89, 0x5C, 0x24, 0x50 }; //mov [esp+0x50], ebx 229 | instructions << ByteArray{ 0xB8, 0xC8, 0x01, 0x00, 0x00 }; //mov eax, 1C8 230 | instructions << ByteArray{ 0xB9, 0x8E, 0x01, 0x00, 0x00 }; //mov ecx, 18E 231 | instructions << ByteArray{ 0x89, 0x44, 0x24, 0x5C }; //mov [esp+0x5C], eax 232 | instructions << ByteArray{ 0x89, 0x4C, 0x24, 0x60 }; //mov [esp+0x60], ecx 233 | instructions << ByteArray{ 0x89, 0x7C, 0x24, 0x6C }; //mov [esp+0x6C], edi 234 | instructions << ByteArray{ 0x89, 0x44, 0x24, 0x7C }; //mov [esp+0x7C], eax 235 | instructions << ByteArray{ 0x89, 0xBC, 0x24, 0x8C, 0x00, 0x00, 0x00 }; //mov [esp+0x8C], edi 236 | instructions << ByteArray{ 0xB8, 0xB6, 0x01, 0x00, 0x00 }; //mov eax, 1B6 237 | instructions << ByteArray{ 0x89, 0x44, 0x24, 0x70 }; //mov [esp+0x70], eax 238 | instructions << ByteArray{ 0xB8, 0xE0, 0x01, 0x00, 0x00 }; //mov eax, 1E0 239 | instructions << ByteArray{ 0x89, 0x84, 0x24, 0x80, 0x00, 0x00, 0x00 }; //mov [esp+0x80], eax 240 | instructions << ByteArray{ 0x89, 0x84, 0x24, 0x90, 0x00, 0x00, 0x00 }; //mov [esp+0x90], eax 241 | 242 | instructions.cmp(res_dword, 0x1); 243 | instructions.jnz(DWORD(instructions.GetCurrentLocation() + 0xA1)); //Size of original UI = 9B, Size of jnz instruction = 6 244 | //9B + 6 = A1 245 | //0x36 = SS segment override prefix 246 | //This is the original Chopper UI layout for 640x480 247 | instructions << ByteArray{ 0x36, 0xC7, 0x44, 0x24, 0x14, 0x00, 0x00, 0x00, 0x00 }; //mov dword ptr ss:[esp+14], 0x0 248 | instructions << ByteArray{ 0x36, 0xC7, 0x44, 0x24, 0x18, 0x00, 0x00, 0x00, 0x00 }; //mov dword ptr ss:[esp+18], 0x0 249 | instructions << ByteArray{ 0x36, 0xC7, 0x44, 0x24, 0x24, 0xEE, 0x01, 0x00, 0x00 }; //mov dword ptr ss:[esp+24], 0x1EE 250 | instructions << ByteArray{ 0x36, 0xC7, 0x44, 0x24, 0x28, 0x00, 0x00, 0x00, 0x00 }; //mov dword ptr ss:[esp+28], 0x0 251 | instructions << ByteArray{ 0x36, 0xC7, 0x44, 0x24, 0x34, 0x2C, 0x02, 0x00, 0x00 }; //mov dword ptr ss:[esp+34], 0x22C 252 | instructions << ByteArray{ 0x36, 0xC7, 0x44, 0x24, 0x38, 0x00, 0x00, 0x00, 0x00 }; //mov dword ptr ss:[esp+38], 0x0 253 | instructions << ByteArray{ 0x36, 0xC7, 0x44, 0x24, 0x44, 0x12, 0x02, 0x00, 0x00 }; //mov dword ptr ss:[esp+44], 0x212 254 | instructions << ByteArray{ 0x36, 0xC7, 0x44, 0x24, 0x48, 0x3E, 0x00, 0x00, 0x00 }; //mov dword ptr ss:[esp+48], 0x3E 255 | instructions << ByteArray{ 0x36, 0xC7, 0x44, 0x24, 0x54, 0x00, 0x00, 0x00, 0x00 }; //mov dword ptr ss:[esp+54], 0x0 256 | instructions << ByteArray{ 0x36, 0xC7, 0x44, 0x24, 0x58, 0x63, 0x01, 0x00, 0x00 }; //mov dword ptr ss:[esp+58], 0x163 257 | instructions << ByteArray{ 0x36, 0xC7, 0x44, 0x24, 0x64, 0xC7, 0x01, 0x00, 0x00 }; //mov dword ptr ss:[esp+64], 0x1C7 258 | instructions << ByteArray{ 0x36, 0xC7, 0x44, 0x24, 0x68, 0x22, 0x01, 0x00, 0x00 }; //mov dword ptr ss:[esp+68], 0x122 259 | instructions << ByteArray{ 0x36, 0xC7, 0x44, 0x24, 0x74, 0x00, 0x00, 0x00, 0x00 }; //mov dword ptr ss:[esp+74], 0x0 260 | instructions << ByteArray{ 0x36, 0xC7, 0x44, 0x24, 0x78, 0x8E, 0x01, 0x00, 0x00 }; //mov dword ptr ss:[esp+78], 0x18E 261 | instructions << ByteArray{ 0x36, 0xC7, 0x84, 0x24, 0x84, 0x00, 0x00, 0x00, 0xC6, 0x01, 0x00, 0x00 }; //mov dword ptr ss:[esp+84], 0x1C6 (+0x84, exceeds 8-bit signed) 262 | instructions << ByteArray{ 0x36, 0xC7, 0x84, 0x24, 0x88, 0x00, 0x00, 0x00, 0xB5, 0x01, 0x00, 0x00 }; //mov dword ptr ss:[esp+88], 0x1B5 263 | instructions.jmp(detour_return, FALSE); //Don't switch location yet 264 | 265 | //This is the 0x9B offset 266 | //If 5017D0 != 1 (not 640x480), we need to write a custom chopper UI layout 267 | instructions << ByteArray{ 0x36, 0x8B, 0x46, 0x1C }; //mov eax, dword ptr ss:[esi+1C] 268 | instructions << ByteArray{ 0x36, 0x8B, 0x56, 0x20 }; //mov edx, dword ptr ss:[esi+20] 269 | instructions << ByteArray{ 0x31, 0xC9 }; //xor ecx, ecx 270 | instructions << ByteArray{ 0x36, 0xC7, 0x44, 0x24, 0x14, 0x00, 0x00, 0x00, 0x00 }; //mov dword ptr ss:[esp+14], 0x0 271 | instructions << ByteArray{ 0x36, 0xC7, 0x44, 0x24, 0x18, 0x00, 0x00, 0x00, 0x00 }; //mov dword ptr ss:[esp+18], 0x0 272 | instructions << ByteArray{ 0x89, 0xC1 }; //mov ecx, eax 273 | instructions << ByteArray{ 0x83, 0xE9, 0x3E }; //sub ecx, 3E 274 | instructions << ByteArray{ 0x36, 0x89, 0x4C, 0x24, 0x24 }; //mov dword ptr ss:[esp+24], ecx 275 | instructions << ByteArray{ 0x36, 0xC7, 0x44, 0x24, 0x28, 0x00, 0x00, 0x00, 0x00 }; //mov dword ptr ss:[esp+28], 0 276 | instructions << ByteArray{ 0x89, 0xC1 }; //mov ecx, eax 277 | instructions << ByteArray{ 0x81, 0xE9, 0xC6, 0x01, 0x00, 0x00 }; //sub ecx, 1C6 278 | instructions << ByteArray{ 0x36, 0x89, 0x4C, 0x24, 0x74 }; //mov dword ptr ss:[esp+74], ecx 279 | instructions << ByteArray{ 0x36, 0x89, 0x4C, 0x24, 0x54 }; //mov dword ptr ss:[esp+54], ecx 280 | instructions << ByteArray{ 0x36, 0x89, 0x4C, 0x24, 0x34 }; //mov dword ptr ss:[esp+34], ecx 281 | instructions << ByteArray{ 0x83, 0xE9, 0x1A }; //sub ecx, 1A 282 | instructions << ByteArray{ 0x36, 0x89, 0x4C, 0x24, 0x44 }; //mov dword ptr ss:[esp+44], ecx 283 | instructions << ByteArray{ 0x81, 0xE9, 0xA0, 0x00, 0x00, 0x00 }; //sub ecx, A0 284 | instructions << ByteArray{ 0x36, 0x89, 0x8C, 0x24, 0x84, 0x00, 0x00, 0x00 }; //mov dword ptr ss:[esp+84], ecx 285 | instructions << ByteArray{ 0x89, 0xD1 }; //mov ecx, edx 286 | instructions << ByteArray{ 0x83, 0xE9, 0x52 }; //sub, ecx, 52 287 | instructions << ByteArray{ 0x36, 0x89, 0x4C, 0x24, 0x78 }; //mov dword ptr ss:[esp+78], ecx 288 | instructions << ByteArray{ 0x83, 0xE9, 0x2B }; //sub ecx, 2B 289 | instructions << ByteArray{ 0x36, 0x89, 0x4C, 0x24, 0x58 }; //mov dword ptr ss:[esp+58], ecx 290 | instructions << ByteArray{ 0x83, 0xC1, 0x11 }; //add ecx, 11 291 | instructions << ByteArray{ 0x36, 0x89, 0x4C, 0x24, 0x38 }; //mov dword ptr ss:[esp+38], ecx 292 | instructions << ByteArray{ 0x83, 0xE9, 0x04 }; //sub ecx, 4 293 | instructions << ByteArray{ 0x36, 0x89, 0x4C, 0x24, 0x48 }; //mov dword ptr ss:[esp+48], ecx 294 | instructions << ByteArray{ 0x89, 0xD1 }; //mov ecx, edx 295 | instructions << ByteArray{ 0x83, 0xE9, 0x2B }; //sub ecx, 2B 296 | instructions << ByteArray{ 0x36, 0x89, 0x8C, 0x24, 0x88, 0x00, 0x00, 0x00 }; //mov dword ptr ss:[esp+88], ecx 297 | instructions << ByteArray{ 0x36, 0xC7, 0x44, 0x24, 0x64, 0x00, 0x00, 0x00, 0x00 }; //mov dword ptr ss:[esp+64], 0 298 | instructions << ByteArray{ 0x89, 0xD1 }; //mov ecx, edx 299 | instructions << ByteArray{ 0x81, 0xE9, 0x93, 0x00, 0x00, 0x00 }; //sub ecx, 93 300 | instructions << ByteArray{ 0x36, 0x89, 0x4C, 0x24, 0x68 }; //mov dword ptr ss:[esp+68], ecx 301 | instructions.jmp(detour_return); //Now we can jump back 302 | 303 | size_t is_size = instructions.GetInstructions().size(); 304 | master->SetLastDetourSize(is_size); 305 | printf("[Chopper UI] Generated a total of %d bytes\n", is_size); 306 | master->instructions.push_back(instructions); 307 | } 308 | 309 | void GameData::CreateCDFunction(DetourMaster* master, GameVersions version) 310 | { 311 | DWORD function_entry = Versions[version]->functions.CD_CHECK; 312 | Instructions instructions(DWORD(function_entry + 0x171)); 313 | instructions.jmp(DWORD(function_entry + 0x23B)); //jmp (originally jnz) 314 | 315 | size_t is_size = instructions.GetInstructions().size(); 316 | printf("[CD Check Bypass] Generated a total of %d bytes\n", is_size); 317 | master->instructions.push_back(instructions); 318 | } 319 | 320 | void GameData::PatchHUnlock(DetourMaster* master, GameVersions version) 321 | { 322 | //LocalUnlock will sometimes return ERROR_NOT_LOCKED, failing an assert 323 | //This is not a problem, and it's safe to continue 324 | if (version != GameVersions::DEBUG) 325 | return; 326 | DWORD function_entry = Versions[version]->functions.HUNLOCK; 327 | DWORD rewrite_start = function_entry + 0x1B; 328 | Instructions instructions(rewrite_start); 329 | instructions << ByteArray{ 0x31, 0xC0 }; //xor eax, eax 330 | instructions.nop(4); 331 | size_t is_size = instructions.GetInstructions().size(); 332 | printf("[HUnlock Patch] Generated a total of %d bytes\n", is_size); 333 | master->instructions.push_back(instructions); 334 | } 335 | 336 | void GameData::PatchAssertions(DetourMaster* master, GameVersions version) 337 | { 338 | //Fixes the primary problem with assertions, initialization (HUnlock) 339 | //Lots of other game assertions fail, this makes the game faster and 340 | //playable without constantly crashing (from assertions) 341 | if (version != GameVersions::DEBUG) 342 | return; 343 | 344 | { 345 | DWORD function_entry = Versions[version]->functions.ASSERT; 346 | Instructions instructions(function_entry); 347 | instructions << ByteArray{ 0xC3 }; //retn 348 | size_t is_size = instructions.GetInstructions().size(); 349 | printf("[Assert Patch] Generated a total of %d bytes\n", is_size); 350 | master->instructions.push_back(instructions); 351 | } 352 | 353 | { 354 | DWORD function_entry = Versions[version]->functions.DOASSERT; 355 | Instructions instructions(function_entry); 356 | instructions << ByteArray{ 0xC3 }; //retn 357 | size_t is_size = instructions.GetInstructions().size(); 358 | printf("[DoAssert Patch] Generated a total of %d bytes\n", is_size); 359 | master->instructions.push_back(instructions); 360 | } 361 | } 362 | 363 | void GameData::CreateSleepFunction(DetourMaster* master, GameVersions version) 364 | { 365 | DWORD function_entry = Versions[version]->functions.WINMAIN; 366 | DWORD dsfunction_sleep = Versions[version]->functions.DS_SLEEP; 367 | DWORD sleep_param = master->info.GetDetourVirtualAddress(DetourOffsetType::MY_SLEEP); 368 | DWORD rewrite_start, jnz_detour_return, jmp_detour_return; 369 | 370 | switch (version) 371 | { 372 | case GameVersions::V10_JP: 373 | rewrite_start = function_entry + 0x143; 374 | jnz_detour_return = function_entry + 0x149; 375 | jmp_detour_return = function_entry + 0x16D; 376 | break; 377 | case GameVersions::DEBUG: 378 | //Debug uses jz, logical slightly changed 379 | rewrite_start = function_entry + 0x186; 380 | jnz_detour_return = function_entry + 0x194; 381 | jmp_detour_return = function_entry + 0x1D5; 382 | break; 383 | default: 384 | rewrite_start = function_entry + 0x11E; 385 | jnz_detour_return = function_entry + 0x124; 386 | jmp_detour_return = function_entry + 0x148; 387 | } 388 | 389 | Instructions instructions(rewrite_start); 390 | instructions.jmp(master->GetNextDetour()); //jmp 391 | if (version == GameVersions::DEBUG) 392 | { 393 | DWORD pPeekMessageA = Versions[version]->functions.DS_PEEKMESSAGE; 394 | instructions.call_disp32(pPeekMessageA); 395 | } 396 | else 397 | { 398 | instructions << ByteArray{ 0xFF, 0xD6 }; //call esi 399 | } 400 | instructions << BYTE(0x50); //push eax 401 | instructions.push_rm32(sleep_param); //push param millis 402 | instructions.call_rm32(dsfunction_sleep); //call Sleep 403 | instructions << BYTE(0x58); //pop eax 404 | instructions << ByteArray{ 0x85, 0xC0 }; //test eax, eax 405 | instructions.jnz(jnz_detour_return); //jnz 406 | instructions.jmp(jmp_detour_return); //jmp 407 | 408 | size_t is_size = instructions.GetInstructions().size(); 409 | master->SetLastDetourSize(is_size); 410 | printf("[Main Loop Sleep] Generated a total of %d bytes\n", is_size); 411 | //printf("DetourMaster now points to address starting at %x\n", master->current_location); 412 | master->instructions.push_back(instructions); 413 | 414 | } 415 | 416 | void GameData::RenderSimsFunction(DetourMaster* master, GameVersions version) 417 | { 418 | DWORD function_entry = Versions[version]->functions.RENDER_SIMS; 419 | 420 | //First remove all the instructions which try and store the address 421 | //of the element in the array. 422 | Instructions instructions(function_entry + 0x35); 423 | instructions.nop(7); // mov eax, [esp+AC] 424 | instructions.nop(2); // mov ebp, [ecx] 425 | instructions.nop(1); // push eax 426 | instructions.nop(3); //call [ebp+28] 427 | instructions.relocate(function_entry + 0x46); 428 | instructions.nop(4); //nop [esp+34], eax 429 | 430 | //Now rewrite the original call, but use a safety length check 431 | instructions.relocate(function_entry + 0x109); 432 | DWORD detour = master->GetNextDetour(); 433 | instructions.jmp(detour, FALSE); 434 | instructions.nop(2); //clean up for debugging 435 | instructions.relocate(detour); 436 | instructions << ByteArray{ 0x8B, 0x8C, 0x24, 0xA8, 0x00, 0x00, 0x00 }; //mov ecx, [esp+0xA8] 437 | instructions << ByteArray{ 0x8B, 0x49, 0x28 }; //mov ecx, [ecx+0x28] 438 | instructions << ByteArray{ 0x8B, 0x84, 0x24, 0xAC, 0x00, 0x00, 0x00 }; //mov eax, [esp+0xAC] (index arg) 439 | instructions << ByteArray{ 0x3B, 0x41, 0x14 }; //cmp eax, [ecx+0x14] (compare index arg against length) 440 | instructions.jge(function_entry + 0x2F0); //jump if outside bounds, dont render 441 | instructions << ByteArray{ 0x8B, 0x49, 0x04 }; //mov ecx, [ecx+0x4] (the array) 442 | instructions << ByteArray{ 0x8B, 0x04, 0x81 }; //mov eax, [ecx+eax*4] (the element in the array) 443 | instructions << ByteArray{ 0x8D, 0x2C, 0xD0 }; //lea ebp, [eax+edx*8] (the instructions we overwrote on detour) 444 | instructions.jmp(function_entry + 0x110); 445 | 446 | size_t is_size = instructions.GetInstructions().size(); 447 | master->SetLastDetourSize(is_size); 448 | printf("[Render Sims Fix] Generated a total of %d bytes\n", is_size); 449 | master->instructions.push_back(instructions); 450 | } 451 | 452 | void GameData::PatchEmergencyVehicleCrashOnRamp(DetourMaster* master, GameVersions version) 453 | { 454 | DWORD function_entry = Versions[version]->functions.EMERGENCY_VEHICLE_RENDER_UNK1; 455 | DWORD detour_entry = function_entry + 0x43; 456 | DWORD detour_return = function_entry + 0x4E; 457 | 458 | //Returning false will inherently set the station to having a vehicle available 459 | //and set the vehicle state to 0 (at station) 460 | DWORD function_return_false; 461 | switch (version) 462 | { 463 | case GameVersions::ORIGINAL: 464 | case GameVersions::V10_JP: 465 | function_return_false = function_entry + 0x1F7; 466 | break; 467 | default: 468 | function_return_false = function_entry + 0x1F8; 469 | break; 470 | } 471 | 472 | Instructions instructions(detour_entry); 473 | instructions.jmp(master->GetNextDetour()); 474 | 475 | //Preserve the original content 476 | instructions << ByteArray{ 0x8D, 0x47, 0x05 }; //lea eax, [edi+0x5] 477 | instructions << ByteArray{ 0x85, 0xC0 }; // test eax, eax 478 | instructions.jz(function_return_false); 479 | 480 | //Check if emergency vehicle is not supposed to render 481 | instructions << ByteArray{ 0x33, 0xF6 }; //xor esi, esi 482 | instructions << ByteArray{ 0x66, 0x8B, 0x77, 0x08 }; //mov si, [edi+0x8] 483 | instructions << ByteArray{ 0x85, 0xF6 }; //test esi, esi 484 | instructions.jnz(detour_return); //passed the "ramp crash" check 485 | 486 | //The x/y coordinates are stored as single bytes because maps are 128x128 487 | //edx+0xC = x, edx+0xD = y, esi at this point will be 0, overwrite both coordinates 488 | //at the same time by moving a WORD. 489 | instructions << ByteArray{ 0x89, 0x72, 0x0C }; //mov [edx+0xC], esi 490 | instructions.jmp(function_return_false); 491 | size_t is_size = instructions.GetInstructions().size(); 492 | master->SetLastDetourSize(is_size); 493 | printf("[Ramp Crash Fix] Generated a total of %d bytes\n", is_size); 494 | master->instructions.push_back(instructions); 495 | } 496 | 497 | void GameData::CreateHangarMainFunction(DetourMaster* master, GameVersions version) 498 | { 499 | 500 | DWORD function_entry = Versions[version]->functions.HANGAR_MAIN; 501 | 502 | DWORD detour_entry_2; 503 | DWORD detour_return_2; 504 | 505 | switch (version) 506 | { 507 | case GameVersions::V11SC: 508 | case GameVersions::V11SC_FR: 509 | case GameVersions::V102_PATCH: 510 | case GameVersions::VCLASSICS: 511 | detour_entry_2 = function_entry + 0x1D5; 512 | detour_return_2 = function_entry + 0x1E0; 513 | break; 514 | case GameVersions::ORIGINAL: 515 | case GameVersions::V10_JP: 516 | detour_entry_2 = function_entry + 0x1DB; 517 | detour_return_2 = function_entry + 0x1E6; 518 | break; 519 | } 520 | 521 | DWORD viewport_x_offset; 522 | DWORD offset_bg_ptr; 523 | switch (version) 524 | { 525 | case GameVersions::V11SC: 526 | case GameVersions::V11SC_FR: 527 | case GameVersions::V102_PATCH: 528 | case GameVersions::ORIGINAL: 529 | case GameVersions::V10_JP: 530 | viewport_x_offset = 0x14A; 531 | offset_bg_ptr = 0x13E; 532 | break; 533 | case GameVersions::VCLASSICS: 534 | viewport_x_offset = 0x152; 535 | offset_bg_ptr = 0x146; 536 | break; 537 | } 538 | 539 | 540 | 541 | Instructions instructions(DWORD(function_entry + 0x41)); 542 | instructions.jmp(master->GetNextDetour()); 543 | 544 | //Patches scrolling in the hangar for high resolution 545 | instructions << ByteArray{ 0x89, 0x86 }; 546 | instructions << viewport_x_offset; //mov [esi+0x14A], eax 547 | instructions << ByteArray{ 0x79, 0x0B }; //jns 0xC 548 | instructions.jmp(function_entry + 0x61, FALSE); 549 | instructions << ByteArray{ 0x3B, 0xF9 }; // cmp edi, ecx 550 | instructions << ByteArray{ 0x7F, 0x02 }; // jg 551 | instructions << ByteArray{ 0x8B, 0xF9 }; // mov edi, ecx 552 | instructions << ByteArray{ 0x2B, 0xCF }; // sub ecx, edi 553 | instructions << ByteArray{ 0x3B, 0xC8 }; // cmp ecx, eax 554 | instructions << ByteArray{ 0x7D, 0x06 }; // jge 555 | instructions << ByteArray{ 0x89, 0x8E }; 556 | instructions << viewport_x_offset; // mov [esi+152], ecx 557 | instructions.jmp(function_entry + 0x61, FALSE); 558 | size_t is_size1 = instructions.GetInstructions().size(); 559 | master->SetLastDetourSize(is_size1); 560 | 561 | /* 562 | End result: 563 | esi+152 = viewport.x (edx) 564 | esi+18 = screen width (eax) 565 | */ 566 | instructions.relocate(detour_entry_2); 567 | instructions.jmp(master->GetNextDetour()); 568 | 569 | instructions << BYTE(0x51); //push ecx 570 | instructions << ByteArray{ 0x8B, 0x8E }; 571 | instructions << offset_bg_ptr; //mov ecx, [esi+0x13E] 572 | instructions << ByteArray{ 0x8B, 0x49, 0x08 }; //mov ecx, [ecx+0x8] 573 | 574 | instructions << ByteArray{ 0x8B, 0x46, 0x18 }; //mov eax, [esi+0x18] 575 | instructions << ByteArray{ 0x2B, 0x46, 0x10 }; //sub eax, dword ptr ds:[esi+0x10] 576 | 577 | //Check if for some reason edx < 0 (shouldnt be), 578 | //should be verified above when we assign in [esi+0x152] 579 | instructions << ByteArray{ 0x83, 0xFA, 00 }; // cmp edx, 0 580 | instructions << ByteArray{ 0x7F, 0x02 }; // jg 2 581 | instructions << ByteArray{ 0x33, 0xD2 }; //xor edx, edx 582 | 583 | instructions << ByteArray{ 0x03, 0xC2 }; //add eax, edx 584 | 585 | instructions << ByteArray{ 0x3B, 0xC1 }; //cmp eax, ecx 586 | instructions << ByteArray{ 0x7E, 0x0A }; //jle if screen width < background width 587 | 588 | instructions << ByteArray{ 0x2B, 0xC8 }; //sub ecx, eax (creates negative) 589 | instructions << ByteArray{ 0x03, 0xC1 }; //add eax, ecx (adds negative) 590 | instructions << ByteArray{ 0x03, 0xD1 }; //add edx, ecx (adds negative) 591 | instructions << ByteArray{ 0x79, 0x02 }; //jns 592 | instructions << ByteArray{ 0x33, 0xD2 }; //xor edx, edx 593 | 594 | //Continue 595 | instructions << BYTE(0x59); //pop ecx 596 | instructions << ByteArray{ 0x8B, 0x5E, 0x20 }; //mov ebx, [esi + 20h] 597 | instructions.jmp(detour_return_2, FALSE); //continue back to push eax 598 | 599 | size_t is_size2 = instructions.GetInstructions().size(); 600 | master->SetLastDetourSize(is_size2 - is_size1); 601 | printf("[Hangar Main] Generated a total of %d bytes\n", instructions.GetInstructions().size()); 602 | master->instructions.push_back(instructions); 603 | } 604 | 605 | void GameData::PatchChopperDamageFunction(DetourMaster* master, GameVersions version) 606 | { 607 | /* 608 | This patch forces the chopper damage calculation to always happen. Previously if max health - current health = 0 (no damage), 609 | then this calculation would be skipped. If this calculation is skipped, then for some reason the chopper would incorrectly 610 | sway as if it had taken some damage. Additionally, a chopper is never repaired to exactly its full health - due to division 611 | its possible to be 1-2 health below max, therefore the "max health sway" bug would only appear after a chopper was purchased 612 | or after the level switched. This patch only ensures that the calculation is always called regardless of damage taken. 613 | 614 | AOB: B9 14 00 00 00 8B 40 4C 2B 83 D0 00 00 00 615 | */ 616 | 617 | DWORD function_offset; 618 | unsigned int nop_count = 6; //different encoding on comparison 619 | switch (version) 620 | { 621 | case V11SC: 622 | case V11SC_FR: 623 | function_offset = 0xDB; 624 | nop_count = 3; 625 | break; 626 | case VCLASSICS: 627 | case V102_PATCH: 628 | function_offset = 0xD4; 629 | break; 630 | case ORIGINAL: 631 | case V10_JP: 632 | function_offset = 0xD8; 633 | break; 634 | } 635 | DWORD function_entry = Versions[version]->functions.CHOPPER_RENDER_UNK1; 636 | Instructions instructions(DWORD(function_entry + function_offset)); 637 | instructions.nop(nop_count); // nops out comparison 638 | instructions << BYTE(0xEB); //Changes jnz to jmp 639 | size_t is_size = instructions.GetInstructions().size(); 640 | master->SetLastDetourSize(is_size); 641 | printf("[Max Health Patch] Generated a total of %d bytes\n", is_size); 642 | master->instructions.push_back(instructions); 643 | } 644 | 645 | void GameData::CreateResolutionFunction(DetourMaster* master, GameVersions version) 646 | { 647 | DWORD function_entry = Versions[version]->functions.RES_LOOKUP; 648 | 649 | Instructions instructions(DWORD(function_entry + 0x13)); 650 | instructions << DWORD(0x500); 651 | instructions.relocate(function_entry + 0x19); 652 | instructions << DWORD(0x320); 653 | 654 | instructions.relocate(function_entry + 0x53); 655 | instructions << DWORD(0x500); 656 | instructions.relocate(function_entry + 0x59); 657 | instructions << DWORD(0x2D0); 658 | 659 | instructions.relocate(function_entry + 0x73); 660 | instructions << DWORD(0x400); 661 | instructions.relocate(function_entry + 0x79); 662 | instructions << DWORD(0x300); 663 | 664 | size_t is_size = instructions.GetInstructions().size(); 665 | master->SetLastDetourSize(is_size); 666 | printf("[Resolution Lookup] Generated a total of %d bytes\n", is_size); 667 | //printf("DetourMaster now points to address starting at %x\n", master->current_location); 668 | master->instructions.push_back(instructions); 669 | } 670 | 671 | void GameData::CreateGlobalInitFunction(DetourMaster* master, GameVersions version) 672 | { 673 | //printf("DetourMaster starting at %x\n", master->current_location); 674 | DWORD function_entry = Versions[version]->functions.GLOBAL_INIT; 675 | DWORD function_res = Versions[version]->functions.RES_LOOKUP; 676 | 677 | /* 678 | lea edi, [esi+4040h] +0x66 <GetNextDetour()); //jmp 688 | instructions << ByteArray{ 0x3E, 0x8D, 0x86, 0x44, 0x40, 0x00, 0x00 }; //lea eax, [esi+4044h] 689 | instructions << BYTE(0x50); //push eax 690 | instructions << BYTE(0x57); //push edi 691 | instructions.call(function_res); //call 692 | instructions << ByteArray{ 0x3E, 0x8B, 0xBE, 0x40, 0x40, 0x00, 0x00 }; //mov edi, [esi+4040h] 693 | instructions << ByteArray{ 0x3E, 0x8B, 0x86, 0x44, 0x40, 0x00, 0x00 }; //mov eax, [esi+4044h] 694 | instructions << ByteArray{ 0x3E, 0x89, 0xBE, 0x4C, 0x40, 0x00, 0x00 }; //mov [esi+404Ch], edi 695 | instructions << ByteArray{ 0x3E, 0x89, 0x86, 0x50, 0x40, 0x00, 0x00 }; //mov [esi+4050h], eax 696 | instructions << ByteArray{ 0x3E, 0x8D, 0xBE, 0x40, 0x40, 0x00, 0x00 }; //lea edi, [esi+4040h] 697 | instructions << ByteArray{ 0xB9, 0x08, 0x00, 0x00, 0x00 }; //mov ecx, 8 698 | instructions << ByteArray{ 0x3E, 0x89, 0x8E, 0x48, 0x40, 0x00, 0x00 }; //mov [esi+4048h], ecx 699 | instructions.jmp(function_entry + 0xA0); //jmp 700 | //push ebp +0xA0 <SetLastDetourSize(is_size); 707 | printf("[Global Initialization] Generated a total of %d bytes\n", is_size); 708 | //printf("DetourMaster now points to address starting at %x\n", master->current_location); 709 | master->instructions.push_back(instructions); 710 | } --------------------------------------------------------------------------------