1- // Include necessary headers
2- #include " stdafx.h"
3- #include " helper.hpp"
1+ #include " ..\..\common\FixBase.hpp"
42
5- #include < spdlog/spdlog.h>
6- #include < spdlog/sinks/basic_file_sink.h>
7- #include < inipp/inipp.h>
8- #include < safetyhook.hpp>
9- #include < vector>
10- #include < map>
11- #include < windows.h>
12- #include < psapi.h> // For GetModuleInformation
13- #include < fstream>
14- #include < filesystem>
15- #include < cmath> // For atanf, tanf
16- #include < sstream>
17- #include < cstring>
18- #include < iomanip>
19- #include < cstdint>
20- #include < iostream>
21-
22- #define spdlog_confparse (var ) spdlog::info(" Config Parse: {}: {}" , #var, var)
23-
24- HMODULE exeModule = GetModuleHandle(NULL );
25- HMODULE thisModule;
26-
27- // Fix details
28- std::string sFixName = " SurvivalTheUltimateChallengeFOVFix" ;
29- std::string sFixVersion = " 1.0" ;
30- std::filesystem::path sFixPath ;
31-
32- // Ini
33- inipp::Ini<char > ini;
34- std::string sConfigFile = sFixName + " .ini" ;
35-
36- // Logger
37- std::shared_ptr<spdlog::logger> logger;
38- std::string sLogFile = sFixName + " .log" ;
39- std::filesystem::path sExePath ;
40- std::string sExeName ;
41-
42- // Constants
43- constexpr float fOldAspectRatio = 4 .0f / 3 .0f ;
44-
45- // Ini variables
46- bool bFixActive;
47-
48- // Variables
49- int iCurrentResX;
50- int iCurrentResY;
51- float fNewAspectRatio ;
52- float fFOVFactor ;
53- float fNewCameraFOV ;
54- float fAspectRatioScale ;
55-
56- // Game detection
57- enum class Game
58- {
59- STUC ,
60- Unknown
61- };
62-
63- struct GameInfo
64- {
65- std::string GameTitle;
66- std::string ExeName;
67- };
68-
69- const std::map<Game, GameInfo> kGames = {
70- {Game::STUC , {" Survival: The Ultimate Challenge" , " Survival.bum" }},
71- };
72-
73- const GameInfo* game = nullptr ;
74- Game eGameType = Game::Unknown;
75-
76- void Logging ()
3+ class SurvivalTheUltimateChallengeFix final : public FixBase
774{
78- // Get path to DLL
79- WCHAR dllPath[_MAX_PATH] = { 0 };
80- GetModuleFileNameW (thisModule, dllPath, MAX_PATH );
81- sFixPath = dllPath;
82- sFixPath = sFixPath .remove_filename ();
83-
84- // Get game name and exe path
85- WCHAR exePathW[_MAX_PATH] = { 0 };
86- GetModuleFileNameW (exeModule, exePathW, MAX_PATH );
87- sExePath = exePathW;
88- sExeName = sExePath .filename ().string ();
89- sExePath = sExePath .remove_filename ();
90-
91- // Spdlog initialization
92- try
5+ public:
6+ explicit SurvivalTheUltimateChallengeFix (HMODULE selfModule) : FixBase(selfModule)
937 {
94- logger = spdlog::basic_logger_st (sFixName .c_str (), sExePath .string () + " \\ " + sLogFile , true );
95- spdlog::set_default_logger (logger);
96- spdlog::flush_on (spdlog::level::debug);
97- spdlog::set_level (spdlog::level::debug); // Enable debug level logging
98-
99- spdlog::info (" ----------" );
100- spdlog::info (" {:s} v{:s} loaded." , sFixName .c_str (), sFixVersion .c_str ());
101- spdlog::info (" ----------" );
102- spdlog::info (" Log file: {}" , sExePath .string () + " \\ " + sLogFile );
103- spdlog::info (" ----------" );
104- spdlog::info (" Module Name: {0:s}" , sExeName .c_str ());
105- spdlog::info (" Module Path: {0:s}" , sExePath .string ());
106- spdlog::info (" Module Address: 0x{0:X}" , (uintptr_t )exeModule);
107- spdlog::info (" ----------" );
108- spdlog::info (" DLL has been successfully loaded." );
8+ s_instance_ = this ;
1099 }
110- catch (const spdlog::spdlog_ex& ex)
10+
11+ ~SurvivalTheUltimateChallengeFix () override
11112 {
112- AllocConsole ();
113- FILE * dummy;
114- freopen_s (&dummy, " CONOUT$" , " w" , stdout);
115- std::cout << " Log initialization failed: " << ex.what () << std::endl;
116- FreeLibraryAndExitThread (thisModule, 1 );
13+ if (s_instance_ == this )
14+ {
15+ s_instance_ = nullptr ;
16+ }
11717 }
118- }
11918
120- void Configuration ()
121- {
122- // Inipp initialization
123- std::ifstream iniFile (sFixPath .string () + " \\ " + sConfigFile );
124- if (!iniFile)
19+ protected:
20+ const char * FixName () const override
12521 {
126- AllocConsole ();
127- FILE * dummy;
128- freopen_s (&dummy, " CONOUT$" , " w" , stdout);
129- std::cout << sFixName .c_str () << " v" << sFixVersion .c_str () << " loaded." << std::endl;
130- std::cout << " ERROR: Could not locate config file." << std::endl;
131- std::cout << " ERROR: Make sure " << sConfigFile .c_str () << " is located in " << sFixPath .string ().c_str () << std::endl;
132- spdlog::shutdown ();
133- FreeLibraryAndExitThread (thisModule, 1 );
22+ return " SurvivalTheUltimateChallengeFOVFix" ;
13423 }
135- else
24+
25+ const char * FixVersion () const override
13626 {
137- spdlog::info (" Config file: {}" , sFixPath .string () + " \\ " + sConfigFile );
138- ini.parse (iniFile);
27+ return " 1.1" ;
13928 }
14029
141- // Parse config
142- ini.strip_trailing_comments ();
143- spdlog::info (" ----------" );
144-
145- // Load settings from ini
146- inipp::get_value (ini.sections [" FOVFix" ], " Enabled" , bFixActive);
147- spdlog_confparse (bFixActive);
148-
149- // Load resolution from ini
150- inipp::get_value (ini.sections [" Settings" ], " Width" , iCurrentResX);
151- inipp::get_value (ini.sections [" Settings" ], " Height" , iCurrentResY);
152- inipp::get_value (ini.sections [" Settings" ], " FOVFactor" , fFOVFactor );
153- spdlog_confparse (iCurrentResX);
154- spdlog_confparse (iCurrentResY);
155- spdlog_confparse (fFOVFactor );
156-
157- // If resolution not specified, use desktop resolution
158- if (iCurrentResX <= 0 || iCurrentResY <= 0 )
30+ const char * TargetName () const override
15931 {
160- spdlog::info (" Resolution not specified in ini file. Using desktop resolution." );
161- // Implement Util::GetPhysicalDesktopDimensions() accordingly
162- auto desktopDimensions = Util::GetPhysicalDesktopDimensions ();
163- iCurrentResX = desktopDimensions.first ;
164- iCurrentResY = desktopDimensions.second ;
165- spdlog_confparse (iCurrentResX);
166- spdlog_confparse (iCurrentResY);
32+ return " Survival: The Ultimate Challenge" ;
16733 }
16834
169- spdlog::info (" ----------" );
170- }
171-
172- bool DetectGame ()
173- {
174- for (const auto & [type, info] : kGames )
35+ InitMode GetInitMode () const override
17536 {
176- if (Util::stringcmp_caseless (info.ExeName , sExeName ))
177- {
178- spdlog::info (" Detected game: {:s} ({:s})" , info.GameTitle , sExeName );
179- spdlog::info (" ----------" );
180- eGameType = type;
181- game = &info;
182- return true ;
183- }
37+ // return InitMode::Direct;
38+ return InitMode::WorkerThread;
39+ // return InitMode::ExportedOnly;
18440 }
18541
186- spdlog::error (" Failed to detect supported game, {:s} isn't supported by the fix." , sExeName );
187- return false ;
188- }
189-
190- static SafetyHookMid CameraFOVInstructionHook{};
191-
192- void CameraFOVInstructionMidHook (SafetyHookContext& ctx)
193- {
194- float & fCurrentCameraFOV = Memory::ReadMem (ctx.esp + 0x4 );
195-
196- fNewCameraFOV = Maths::CalculateNewFOV_RadBased (fCurrentCameraFOV , fAspectRatioScale ) * fFOVFactor ;
197-
198- _asm
42+ bool IsCompatibleExecutable (const std::string& exeName) const override
19943 {
200- fld dword ptr ds:[ fNewCameraFOV ]
44+ return Util::stringcmp_caseless (exeName, " Survival.bum " );
20145 }
202- }
20346
204- void FOVFix ()
205- {
206- if (eGameType == Game::STUC && bFixActive == true )
47+ void ParseFixConfig (inipp::Ini<char >& ini) override
20748 {
208- fNewAspectRatio = static_cast <float >(iCurrentResX) / static_cast <float >(iCurrentResY);
49+ inipp::get_value (ini.sections [" Settings" ], " FOVFactor" , m_fovFactor);
50+ spdlog_confparse (m_fovFactor);
51+ }
20952
210- fAspectRatioScale = fNewAspectRatio / fOldAspectRatio ;
53+ void ApplyFix () override
54+ {
55+ auto ResolutionScanResult = Memory::PatternScan (ExeModule (), " 8B 4E ?? 8B 46 ?? 89 0D" );
56+ if (ResolutionScanResult)
57+ {
58+ spdlog::info (" Resolution Instructions Scan: Address is {:s}+{:x}" , ExeName ().c_str (), ResolutionScanResult - (std::uint8_t *)ExeModule ());
59+
60+ m_resolutionHook = safetyhook::create_mid (ResolutionScanResult, [](SafetyHookContext& ctx)
61+ {
62+ int & iCurrentWidth = Memory::ReadMem (ctx.esi + 0x64 );
63+ int & iCurrentHeight = Memory::ReadMem (ctx.esi + 0x68 );
64+ s_instance_->m_newAspectRatio = static_cast <float >(iCurrentWidth) / static_cast <float >(iCurrentHeight);
65+ s_instance_->m_aspectRatioScale = s_instance_->m_newAspectRatio / s_instance_->m_oldAspectRatio ;
66+ });
67+ }
68+ else
69+ {
70+ spdlog::error (" Failed to locate resolution instructions scan memory address." );
71+ return ;
72+ }
21173
212- std:: uint8_t * CameraFOVInstructionScanResult = Memory::PatternScan (exeModule , " D9 44 24 04 D8 0D ?? ?? ?? ?? 8A 81 44 02 00 00 84 C0 D9 F2 " );
213- if (CameraFOVInstructionScanResult )
74+ auto CameraFOVScanResult = Memory::PatternScan (ExeModule () , " D9 44 24 ?? D8 0D ?? ?? ?? ?? 8A 81" );
75+ if (CameraFOVScanResult )
21476 {
215- spdlog::info (" Camera FOV Instruction: Address is {:s}+{:x}" , sExeName .c_str (), CameraFOVInstructionScanResult - (std::uint8_t *)exeModule );
77+ spdlog::info (" Camera FOV Instruction: Address is {:s}+{:x}" , ExeName () .c_str (), CameraFOVScanResult - (std::uint8_t *)ExeModule () );
21678
217- Memory::PatchBytes (CameraFOVInstructionScanResult, " \x90\x90\x90\x90 " , 4 ); // NOP out the original instruction
79+ Memory::WriteNOPs (CameraFOVScanResult , 4 ); // NOP out the original instruction
21880
219- CameraFOVInstructionHook = safetyhook::create_mid (CameraFOVInstructionScanResult, CameraFOVInstructionMidHook);
81+ m_cameraFOVHook = safetyhook::create_mid (CameraFOVScanResult, [](SafetyHookContext& ctx)
82+ {
83+ float & fCurrentCameraFOV = Memory::ReadMem (ctx.esp + 0x4 );
84+ s_instance_->m_newCameraFOV = Maths::CalculateNewFOV_RadBased (fCurrentCameraFOV , s_instance_->m_aspectRatioScale ) * s_instance_->m_fovFactor ;
85+ FPU::FLD (s_instance_->m_newCameraFOV );
86+ });
22087 }
22188 else
22289 {
22390 spdlog::error (" Failed to locate camera FOV instruction memory address." );
22491 return ;
22592 }
22693 }
227- }
22894
229- DWORD __stdcall Main ( void *)
230- {
231- Logging ();
232- Configuration () ;
233- if ( DetectGame ())
234- {
235- FOVFix () ;
236- }
237- return TRUE ;
238- }
95+ private:
96+ static constexpr float m_oldAspectRatio = 4 . 0f / 3 . 0f ;
97+
98+ SafetyHookMid m_resolutionHook ;
99+ SafetyHookMid m_cameraFOVHook;
100+
101+ inline static SurvivalTheUltimateChallengeFix* s_instance_ = nullptr ;
102+ };
103+
104+ static std::unique_ptr<SurvivalTheUltimateChallengeFix> g_fix;
239105
240106BOOL APIENTRY DllMain (HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
241107{
242108 switch (ul_reason_for_call)
243109 {
244110 case DLL_PROCESS_ATTACH :
245111 {
246- thisModule = hModule;
247- HANDLE mainHandle = CreateThread (NULL , 0 , Main, 0 , NULL , 0 );
248- if (mainHandle)
249- {
250- SetThreadPriority (mainHandle, THREAD_PRIORITY_HIGHEST );
251- CloseHandle (mainHandle);
252- }
112+ g_fix = std::make_unique<SurvivalTheUltimateChallengeFix>(hModule);
113+ g_fix->Start ();
253114 break ;
254115 }
116+
117+ case DLL_PROCESS_DETACH :
118+ {
119+ g_fix->Shutdown ();
120+ g_fix.reset ();
121+ break ;
122+ }
123+
255124 case DLL_THREAD_ATTACH :
256125 case DLL_THREAD_DETACH :
257- case DLL_PROCESS_DETACH :
126+ default :
258127 break ;
259128 }
129+
260130 return TRUE ;
261131}
0 commit comments