├── .gitmodules ├── README.MD ├── premake5.bat ├── premake5.exe ├── premake5.lua └── source └── dllmain.cpp /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "external/Hooking.Patterns"] 2 | path = external/Hooking.Patterns 3 | url = https://github.com/ThirteenAG/Hooking.Patterns.git 4 | branch = master 5 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # MGSResolutionPatch 2 | 3 | **UPDATE 2023/10/29**: Fixed UI bugs in MGS2 (including codec calls) 4 | 5 | This is an experimental ASI Plugin for Steam releases of Metal Gear Solid 2 and Metal Gear Solid 3. 6 | It allows to change ingame resolution via ini config. 7 | 8 | Recommended for play at resolutions 1920x1080, 2560x1440 and 3840x2160. 9 | Resolutions other than above mentioned are not guaranteed to produce an adequate image (it might be stretched), but I didn't test every possible resolution that is out there, so you might as well try whatever resolution you want. 10 | 11 | ## Installation 12 | 13 | 1. Download the latest Ultimate ASI Loader from here: https://github.com/ThirteenAG/Ultimate-ASI-Loader/releases/download/x64-latest/d3d11-x64.zip 14 | 2. Put d3d11.dll from Ultimate ASI Loader into your MGS2/MGS3 directory 15 | 3. Download the latest MGSResolutionPatch from here: https://github.com/Sergeanur/MGSResolutionPatch/releases 16 | 4. Put MGSResolutionPatch.asi and MGSResolutionPatch.ini into your MGS2/MGS3 directory 17 | 5. Open MGSResolutionPatch.ini with a text editor to change the resolution to the one you desire 18 | -------------------------------------------------------------------------------- /premake5.bat: -------------------------------------------------------------------------------- 1 | premake5 vs2022 -------------------------------------------------------------------------------- /premake5.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sergeanur/MGSResolutionPatch/b9adc9af2c08bb6f5342bd5ce3f5729758ac4d49/premake5.exe -------------------------------------------------------------------------------- /premake5.lua: -------------------------------------------------------------------------------- 1 | workspace "MGSResolutionPatch" 2 | configurations { "Debug", "Release" } 3 | architecture "x64" 4 | location "build" 5 | buildoptions {"-std:c++latest"} 6 | 7 | defines { "X64" } 8 | 9 | project "MGSResolutionPatch" 10 | kind "SharedLib" 11 | language "C++" 12 | targetdir "bin/x64/%{cfg.buildcfg}" 13 | targetname "MGSResolutionPatch" 14 | targetextension ".asi" 15 | 16 | includedirs { "source" } 17 | includedirs { "external" } 18 | 19 | files { "source/dllmain.h", "source/dllmain.cpp", "external/Hooking.Patterns/Hooking.Patterns.cpp", "external/Hooking.Patterns/Hooking.Patterns.h" } 20 | 21 | characterset ("UNICODE") 22 | 23 | filter "configurations:Debug" 24 | defines { "DEBUG" } 25 | symbols "On" 26 | 27 | filter "configurations:Release" 28 | defines { "NDEBUG" } 29 | optimize "On" 30 | staticruntime "On" 31 | -------------------------------------------------------------------------------- /source/dllmain.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | struct ResEntry 6 | { 7 | int field_0 = 1; // widescreen? 8 | float fAspectRatio = 0; 9 | unsigned int WindowSizeX = 0, ResolutionX = 0; 10 | unsigned int WindowSizeY = 0, ResolutionY = 0; 11 | }; 12 | 13 | std::thread* tempThread = nullptr; 14 | 15 | void SetResolutionHook(hook::pattern& pattern, const ResEntry& newResolution) 16 | { 17 | DWORD protect[2]; 18 | if (VirtualProtect(pattern.get_first(0), 0x100, PAGE_EXECUTE_READWRITE, &protect[0])) 19 | { 20 | *(int*)pattern.get_first(3) = newResolution.field_0; 21 | *(float*)pattern.get_first(10) = newResolution.fAspectRatio; 22 | *(unsigned int*)pattern.get_first(17) = newResolution.WindowSizeX; 23 | *(unsigned int*)pattern.get_first(24) = newResolution.WindowSizeY; 24 | *(unsigned int*)pattern.get_first(31) = newResolution.ResolutionX; 25 | *(unsigned int*)pattern.get_first(38) = newResolution.ResolutionY; 26 | 27 | *(unsigned int*)pattern.get_first(252) = 1; 28 | 29 | VirtualProtect(pattern.get_first(0), 0x100, protect[0], &protect[1]); 30 | } 31 | 32 | // UI fix (MGS 2) 33 | pattern = hook::pattern("66 0F 6E 45 F8"); 34 | if (!pattern.empty()) 35 | { 36 | // X scale constant used in some other code too, so move it to the other place for safety reasons 37 | float* pXScale = (float*)((char*)pattern.get_first(9+4) + *(int*)pattern.get_first(9)); 38 | float XScale = *pXScale * ((float)newResolution.ResolutionX / 1280.0f); 39 | pXScale -= 35; // some align space after 'vector too long' 40 | 41 | if (VirtualProtect(pXScale, 4, PAGE_READWRITE, &protect[0])) 42 | { 43 | *pXScale = XScale; 44 | VirtualProtect(pXScale, 4, protect[0], &protect[1]); 45 | } 46 | 47 | if (VirtualProtect(pattern.get_first(9), 4, PAGE_READWRITE, &protect[0])) 48 | { 49 | *(int*)pattern.get_first(9) -= 0x8C; 50 | VirtualProtect(pattern.get_first(9), 4, protect[0], &protect[1]); 51 | } 52 | 53 | if (VirtualProtect(pattern.get_first(52), 4, PAGE_READWRITE, &protect[0])) 54 | { 55 | *(int*)pattern.get_first(52) -= 0x8C; 56 | VirtualProtect(pattern.get_first(52), 4, protect[0], &protect[1]); 57 | } 58 | 59 | // Y scale only used once, so modify in-place 60 | float* pYScale = (float*)((char*)pattern.get_first(0x64) + *(int*)pattern.get_first(0x60)); 61 | float YScale = *pYScale * ((float)newResolution.ResolutionY / 720.0f); 62 | if (VirtualProtect(pYScale, 4, PAGE_READWRITE, &protect[0])) 63 | { 64 | *pYScale = YScale; 65 | VirtualProtect(pYScale, 4, protect[0], &protect[1]); 66 | } 67 | } 68 | 69 | } 70 | 71 | ResEntry newResolution; 72 | 73 | void threadWaitingLoop(hook::pattern pattern) 74 | { 75 | while (*pattern.get_first(0) != 0x018745C7 && *pattern.get_first(252) != 6) 76 | { 77 | std::this_thread::yield(); 78 | } 79 | 80 | SetResolutionHook(pattern, newResolution); 81 | } 82 | 83 | std::wstring GetModulePath(HMODULE hModule) 84 | { 85 | std::wstring path; 86 | path.resize(MAX_PATH); 87 | 88 | while (true) 89 | { 90 | auto ret = GetModuleFileNameW(hModule, path.data(), static_cast(path.size())); 91 | if (ret == 0) 92 | return L""; 93 | if (ret == path.size()) 94 | path.resize(path.size() * 2); 95 | else 96 | return path; 97 | } 98 | 99 | return path; 100 | } 101 | 102 | BOOL APIENTRY DllMain(HMODULE hModule, DWORD reason, LPVOID lpReserved) 103 | { 104 | if (reason == DLL_PROCESS_ATTACH) 105 | { 106 | std::wstring ModulePath = GetModulePath(hModule); 107 | ModulePath.resize(ModulePath.find_last_of(L'.')); 108 | ModulePath += L".ini"; 109 | if (!ModulePath.empty()) 110 | { 111 | INT Value; 112 | 113 | Value = GetPrivateProfileInt(L"MGSResolutionPatch", L"WindowSizeX", 0, ModulePath.c_str()); 114 | if (Value > 0) newResolution.ResolutionX = Value; 115 | 116 | Value = GetPrivateProfileInt(L"MGSResolutionPatch", L"WindowSizeY", 0, ModulePath.c_str()); 117 | if (Value > 0) newResolution.ResolutionY = Value; 118 | 119 | Value = GetPrivateProfileInt(L"MGSResolutionPatch", L"ResolutionX", 0, ModulePath.c_str()); 120 | if (Value > 0) newResolution.WindowSizeX = Value; 121 | 122 | Value = GetPrivateProfileInt(L"MGSResolutionPatch", L"ResolutionY", 0, ModulePath.c_str()); 123 | if (Value > 0) newResolution.WindowSizeY = Value; 124 | 125 | Value = GetPrivateProfileInt(L"MGSResolutionPatch", L"SquishVertically", 1, ModulePath.c_str()); 126 | newResolution.field_0 = !!Value; 127 | 128 | if (newResolution.ResolutionX == 0 || newResolution.ResolutionY == 0 || newResolution.WindowSizeX == 0 || newResolution.WindowSizeY == 0) // no valid resolution 129 | return TRUE; 130 | 131 | newResolution.fAspectRatio = (float)newResolution.WindowSizeX / (float)newResolution.WindowSizeY; 132 | 133 | auto pattern = hook::pattern("C7 45 87 01 00 00 00"); 134 | 135 | if (!pattern.empty()) 136 | { 137 | SetResolutionHook(pattern, newResolution); 138 | } 139 | else 140 | { 141 | // look for encrypted patterns 142 | pattern = hook::pattern("4C DA 57 A8 25 E2"); // MGS3 143 | if (!pattern.empty()) 144 | tempThread = new std::thread(threadWaitingLoop, pattern); 145 | else 146 | { 147 | pattern = hook::pattern("90 89 26 34 BB 0E 63 7C"); // MGS2 148 | if (!pattern.empty()) 149 | tempThread = new std::thread(threadWaitingLoop, pattern); 150 | } 151 | } 152 | } 153 | 154 | } 155 | if (reason == DLL_PROCESS_DETACH) 156 | { 157 | delete tempThread; 158 | } 159 | return TRUE; 160 | } --------------------------------------------------------------------------------