Skip to content

Commit 8147906

Browse files
committed
SurvivalTheUltimateChallenge:
- Improved the fix's structure - Made the fix calculate field of view automatically from the in-game resolution
1 parent ba7792e commit 8147906

2 files changed

Lines changed: 82 additions & 212 deletions

File tree

project/SurvivalTheUltimateChallengeFOVFix.vcxproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@
105105
<LanguageStandard>stdcpplatest</LanguageStandard>
106106
<LanguageStandard_C>stdclatest</LanguageStandard_C>
107107
<DebugInformationFormat>None</DebugInformationFormat>
108-
<Optimization>Disabled</Optimization>
108+
<Optimization>MaxSpeed</Optimization>
109109
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
110110
<AdditionalOptions>/utf-8 %(AdditionalOptions)</AdditionalOptions>
111111
</ClCompile>
Lines changed: 81 additions & 211 deletions
Original file line numberDiff line numberDiff line change
@@ -1,261 +1,131 @@
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

240106
BOOL 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

Comments
 (0)