Files
UniversalSlashingSimulator/Core/Versioning/VersionResolver.cpp
T
2026-02-01 21:20:51 -06:00

567 lines
23 KiB
C++

/**
* UniversalSlashingSimulator - Version Resolver Implementation
*
* Based on version-scanner-example.cpp patterns and comprehensive CL research.
* Complete CL-to-version mapping for Fortnite 1.2 through 19.40.
*/
#include "VersionResolver.h"
#include "../Memory/Memory.h"
#include "../Memory/PatternScanner.h"
#include "../Logging/Log.h"
#include "../../Engine/CoreTypes/FString.h"
#include <cstring>
namespace USS
{
// Complete CL to version mapping table
const FVersionResolver::FCLMapping FVersionResolver::s_CLMappings[] = {
// ========================================================================
// Chapter 1 - Season 1 (UE 4.16)
// ========================================================================
{ 3541083, 3681159, 4, 16, 1.20 }, // 1.2
{ 3681159, 3700114, 4, 16, 1.50 }, // 1.5
{ 3700114, 3709086, 4, 16, 1.72 }, // 1.7.2
{ 3709086, 3724489, 4, 16, 1.80 }, // 1.8
{ 3724489, 3757339, 4, 16, 1.82 }, // 1.8.2
{ 3757339, 3775276, 4, 16, 1.90 }, // 1.9
{ 3775276, 3790078, 4, 16, 1.91 }, // 1.9.1
// ========================================================================
// Chapter 1 - Season 2 (UE 4.19)
// ========================================================================
{ 3790078, 3807424, 4, 19, 1.10 }, // 1.10
{ 3807424, 3821117, 4, 19, 1.11 }, // 1.11
{ 3821117, 3841827, 4, 19, 2.00 }, // 2.0
{ 3841827, 3847564, 4, 19, 2.10 }, // 2.1
{ 3847564, 3858292, 4, 19, 2.20 }, // 2.2
{ 3858292, 3870737, 4, 19, 2.30 }, // 2.3
{ 3870737, 3889387, 4, 19, 2.40 }, // 2.4
{ 3889387, 3901517, 4, 19, 2.41 }, // 2.4.1
{ 3901517, 3913157, 4, 19, 2.42 }, // 2.4.2
{ 3913157, 3922182, 4, 19, 2.50 }, // 2.5
// ========================================================================
// Chapter 1 - Season 3 (UE 4.20)
// ========================================================================
{ 3922182, 3935073, 4, 20, 3.00 }, // 3.0
{ 3935073, 3942182, 4, 20, 3.10 }, // 3.1
{ 3942182, 3948073, 4, 20, 3.20 }, // 3.2
{ 3948073, 3968866, 4, 20, 3.30 }, // 3.3
{ 3968866, 3989614, 4, 20, 3.40 }, // 3.4
{ 3989614, 4008490, 4, 20, 3.50 }, // 3.5
{ 4008490, 4019403, 4, 20, 3.51 }, // 3.5.1
{ 4019403, 4039451, 4, 20, 3.52 }, // 3.5.2
{ 4039451, 4072250, 4, 20, 3.60 }, // 3.6
// ========================================================================
// Chapter 1 - Season 4 (UE 4.20)
// ========================================================================
{ 4072250, 4117433, 4, 20, 4.00 }, // 4.0
{ 4117433, 4127312, 4, 20, 4.10 }, // 4.1
{ 4127312, 4166199, 4, 20, 4.20 }, // 4.2
{ 4166199, 4205896, 4, 20, 4.30 }, // 4.3
{ 4205896, 4240749, 4, 20, 4.40 }, // 4.4
{ 4240749, 4276938, 4, 20, 4.50 }, // 4.5
// ========================================================================
// Chapter 1 - Season 5 (UE 4.21)
// ========================================================================
{ 4276938, 4336496, 4, 21, 5.00 }, // 5.0
{ 4336496, 4352937, 4, 21, 5.01 }, // 5.0.1
{ 4352937, 4378021, 4, 21, 5.10 }, // 5.1
{ 4378021, 4395664, 4, 21, 5.20 }, // 5.2
{ 4395664, 4417689, 4, 21, 5.21 }, // 5.2.1
{ 4417689, 4442095, 4, 21, 5.30 }, // 5.3
{ 4442095, 4461277, 4, 21, 5.40 }, // 5.4
{ 4461277, 4476098, 4, 21, 5.41 }, // 5.4.1
// ========================================================================
// Chapter 1 - Season 6 (UE 4.21)
// ========================================================================
{ 4476098, 4526925, 4, 21, 6.00 }, // 6.0
{ 4526925, 4541578, 4, 21, 6.01 }, // 6.0.1
{ 4541578, 4573279, 4, 21, 6.10 }, // 6.1
{ 4573279, 4612618, 4, 21, 6.20 }, // 6.2
{ 4612618, 4629139, 4, 21, 6.21 }, // 6.2.1
{ 4629139, 4667333, 4, 21, 6.30 }, // 6.3
{ 4667333, 4683176, 4, 21, 6.31 }, // 6.3.1
// ========================================================================
// Chapter 1 - Season 7 (UE 4.22)
// ========================================================================
{ 4683176, 4741202, 4, 22, 7.00 }, // 7.0
{ 4741202, 4775217, 4, 22, 7.10 }, // 7.1
{ 4775217, 4801627, 4, 22, 7.20 }, // 7.2
{ 4801627, 4834550, 4, 22, 7.30 }, // 7.3
{ 4834550, 4869070, 4, 22, 7.40 }, // 7.4
// ========================================================================
// Chapter 1 - Season 8 (UE 4.22)
// NOTE: 8.30 introduces new FastArraySerializer
// ========================================================================
{ 4869070, 4900175, 4, 22, 8.00 }, // 8.0
{ 4900175, 4937820, 4, 22, 8.10 }, // 8.1
{ 4937820, 4975227, 4, 22, 8.20 }, // 8.2
{ 4975227, 5027463, 4, 22, 8.30 }, // 8.3 (NEW FAS!)
{ 5027463, 5046157, 4, 22, 8.40 }, // 8.4
{ 5046157, 5076327, 4, 22, 8.50 }, // 8.5
{ 5076327, 5110300, 4, 22, 8.51 }, // 8.5.1
// ========================================================================
// Chapter 1 - Season 9 (UE 4.23 - FNamePool introduced)
// ========================================================================
{ 5110300, 5176700, 4, 23, 9.00 }, // 9.0
{ 5176700, 5216303, 4, 23, 9.10 }, // 9.1
{ 5216303, 5268528, 4, 23, 9.20 }, // 9.2
{ 5268528, 5332994, 4, 23, 9.30 }, // 9.3
{ 5332994, 5372160, 4, 23, 9.40 }, // 9.4
{ 5372160, 5423082, 4, 23, 9.41 }, // 9.4.1
// ========================================================================
// Chapter 1 - Season 10 (X) (UE 4.23)
// ========================================================================
{ 5423082, 5492370, 4, 23, 10.00 }, // 10.0
{ 5492370, 5545976, 4, 23, 10.10 }, // 10.1
{ 5545976, 5633945, 4, 23, 10.20 }, // 10.2
{ 5633945, 5704620, 4, 23, 10.30 }, // 10.3
{ 5704620, 5826396, 4, 23, 10.31 }, // 10.3.1
{ 5826396, 5878874, 4, 23, 10.40 }, // 10.4
// ========================================================================
// Chapter 2 - Season 1 (UE 4.24)
// ========================================================================
{ 5878874, 6058028, 4, 24, 11.00 }, // 11.0
{ 6058028, 6113816, 4, 24, 11.01 }, // 11.0.1
{ 6113816, 6195466, 4, 24, 11.10 }, // 11.1
{ 6195466, 6316943, 4, 24, 11.20 }, // 11.2
{ 6316943, 6394056, 4, 24, 11.21 }, // 11.2.1
{ 6394056, 6522018, 4, 24, 11.30 }, // 11.3
{ 6522018, 6639283, 4, 24, 11.31 }, // 11.3.1
{ 6639283, 6755567, 4, 24, 11.40 }, // 11.4
{ 6755567, 6870595, 4, 24, 11.50 }, // 11.5
// ========================================================================
// Chapter 2 - Season 2 (UE 4.24)
// ========================================================================
{ 6870595, 7037963, 4, 24, 12.00 }, // 12.0
{ 7037963, 7095426, 4, 24, 12.10 }, // 12.1
{ 7095426, 7190182, 4, 24, 12.20 }, // 12.2
{ 7190182, 7251970, 4, 24, 12.21 }, // 12.2.1
{ 7251970, 7351410, 4, 24, 12.30 }, // 12.3
{ 7351410, 7421103, 4, 24, 12.40 }, // 12.4
{ 7421103, 7499902, 4, 24, 12.41 }, // 12.4.1
{ 7499902, 7609292, 4, 24, 12.50 }, // 12.5
{ 7609292, 7704104, 4, 24, 12.60 }, // 12.6
{ 7704104, 7834553, 4, 24, 12.61 }, // 12.6.1
// ========================================================================
// Chapter 2 - Season 3 (UE 4.24)
// ========================================================================
{ 7834553, 8008725, 4, 24, 13.00 }, // 13.0
{ 8008725, 8090709, 4, 24, 13.20 }, // 13.2
{ 8090709, 8154316, 4, 24, 13.30 }, // 13.3
{ 8154316, 8297117, 4, 24, 13.40 }, // 13.4
// ========================================================================
// Chapter 2 - Season 4 (UE 4.24)
// ========================================================================
{ 8297117, 8490514, 4, 24, 14.00 }, // 14.0
{ 8490514, 8606188, 4, 24, 14.10 }, // 14.1
{ 8606188, 8723043, 4, 24, 14.20 }, // 14.2
{ 8723043, 8775446, 4, 24, 14.30 }, // 14.3
{ 8775446, 8870917, 4, 24, 14.40 }, // 14.4
{ 8870917, 9034168, 4, 24, 14.50 }, // 14.5
{ 9034168, 9141206, 4, 24, 14.60 }, // 14.6
// ========================================================================
// Chapter 2 - Season 5 (UE 4.25 - FField introduced)
// ========================================================================
{ 9141206, 9449003, 4, 25, 15.00 }, // 15.0
{ 9449003, 9562734, 4, 25, 15.10 }, // 15.1
{ 9562734, 9685607, 4, 25, 15.20 }, // 15.2
{ 9685607, 9822221, 4, 25, 15.21 }, // 15.2.1
{ 9822221, 9926083, 4, 25, 15.30 }, // 15.3
{ 9926083, 10033985, 4, 25, 15.40 }, // 15.4
{ 10033985, 10127509, 4, 25, 15.50 }, // 15.5
// ========================================================================
// Chapter 2 - Season 6 (UE 4.26)
// ========================================================================
{ 10127509, 10466661, 4, 26, 16.00 }, // 16.0
{ 10466661, 10639200, 4, 26, 16.10 }, // 16.1
{ 10639200, 10800459, 4, 26, 16.20 }, // 16.2
{ 10800459, 10951243, 4, 26, 16.30 }, // 16.3
{ 10951243, 11100825, 4, 26, 16.40 }, // 16.4
{ 11100825, 11203632, 4, 26, 16.50 }, // 16.5
// ========================================================================
// Chapter 2 - Season 7 (UE 4.26)
// ========================================================================
{ 11203632, 11556442, 4, 26, 17.00 }, // 17.0
{ 11556442, 11724923, 4, 26, 17.10 }, // 17.1
{ 11724923, 11883027, 4, 26, 17.20 }, // 17.2
{ 11883027, 12058785, 4, 26, 17.21 }, // 17.2.1
{ 12058785, 12186007, 4, 26, 17.30 }, // 17.3
{ 12186007, 12343911, 4, 26, 17.40 }, // 17.4
{ 12343911, 12493209, 4, 26, 17.50 }, // 17.5
// ========================================================================
// Chapter 2 - Season 8 (UE 4.26)
// ========================================================================
{ 12493209, 12905909, 4, 26, 18.00 }, // 18.0
{ 12905909, 13039508, 4, 26, 18.10 }, // 18.1
{ 13039508, 13206842, 4, 26, 18.20 }, // 18.2
{ 13206842, 13383027, 4, 26, 18.21 }, // 18.2.1
{ 13383027, 13498980, 4, 26, 18.30 }, // 18.3
{ 13498980, 13692932, 4, 26, 18.40 }, // 18.4
// ========================================================================
// Chapter 3 - Season 1 (UE 4.27)
// ========================================================================
{ 13692932, 14211857, 5, 0, 19.00 }, // 19.0
{ 14211857, 14422223, 5, 0, 19.01 }, // 19.0.1
{ 14422223, 14550713, 5, 0, 19.10 }, // 19.1
{ 14550713, 14786821, 5, 0, 19.20 }, // 19.2
{ 14786821, 14899505, 5, 0, 19.30 }, // 19.3
{ 14899505, 19215531, 5, 0, 19.40 }, // 19.4
};
const size_t FVersionResolver::s_CLMappingCount = sizeof(s_CLMappings) / sizeof(s_CLMappings[0]);
FVersionResolver::FVersionResolver()
: m_bDetected(false)
{
}
FVersionResolver& FVersionResolver::Get()
{
static FVersionResolver Instance;
return Instance;
}
EResult FVersionResolver::DetectVersion()
{
if (m_bDetected)
return EResult::AlreadyInitialized;
USS_LOG("Starting version detection...");
if (TryDetectFromVersionInfo())
{
USS_LOG("Version detected from embedded version info");
}
else if (TryDetectFromCL())
{
USS_LOG("Version detected from CL mapping");
}
else if (TryDetectFromPatterns())
{
USS_LOG("Version detected from memory patterns");
}
else
{
USS_ERROR("Failed to detect version");
// Show error message and exit for unsupported version
MessageBoxA(
nullptr,
"UniversalSlashingSimulator could not detect the Fortnite version.\n\n"
"This build supports Fortnite versions 1.2 through 19.40.\n\n"
"Please ensure you are running a supported version.",
"Unsupported Version",
MB_OK | MB_ICONERROR
);
return EResult::InvalidVersion;
}
DetermineGeneration();
ComputeFeatureFlags();
if (!SupportsVersion(m_VersionInfo))
{
USS_ERROR("Detected version is not supported: FN %.2f", m_VersionInfo.FortniteVersion);
char ErrorMsg[512];
snprintf(ErrorMsg, sizeof(ErrorMsg),
"UniversalSlashingSimulator detected Fortnite %.2f (CL %u)\n\n"
"This version is not supported.\n\n"
"Supported versions: 1.2 - 19.40",
m_VersionInfo.FortniteVersion,
m_VersionInfo.FortniteCL);
MessageBoxA(nullptr, ErrorMsg, "Unsupported Version", MB_OK | MB_ICONERROR);
return EResult::InvalidVersion;
}
USS_LOG("Detected version: Engine %s, Fortnite %.2f (CL %u)",
m_VersionInfo.GetEngineVersionString().c_str(),
m_VersionInfo.FortniteVersion,
m_VersionInfo.FortniteCL);
USS_LOG("Generation: %s", m_VersionInfo.GetGenerationName());
USS_LOG("Features: FNamePool=%d, FField=%d, ChunkedObjects=%d, NewFAS=%d, TObjectPtr=%d",
m_VersionInfo.bUseFNamePool,
m_VersionInfo.bUseFField,
m_VersionInfo.bUseChunkedObjects,
m_VersionInfo.bUseNewFastArraySerializer,
m_VersionInfo.bUseTObjectPtr);
m_bDetected = true;
return EResult::Success;
}
const FVersionInfo& FVersionResolver::GetVersionInfo() const
{
return m_VersionInfo;
}
bool FVersionResolver::SupportsVersion(const FVersionInfo& Info) const
{
if (Info.EngineVersionMajor == 4)
{
return Info.EngineVersionMinor >= 16 && Info.EngineVersionMinor <= 27;
}
else if (Info.EngineVersionMajor == 5)
{
return Info.EngineVersionMinor <= 2;
}
return false;
}
bool FVersionResolver::IsVersionDetected() const
{
return m_bDetected;
}
bool FVersionResolver::TryDetectFromVersionInfo()
{
// Try to find version string in memory
// Pattern: "++Fortnite+Release-XX.XX" or similar
const char* VersionPattern = "2B 2B 46 6F 72 74 6E 69 74 65 2B 52 65 6C 65 61 73 65"; // "++Fortnite+Release"
auto Result = Memory::FindPatternIDA(VersionPattern);
if (!Result)
{
USS_LOG("Version string pattern not found");
return false;
}
// Parse version from string after pattern
// Format: "++Fortnite+Release-XX.XX-CL-XXXXXXX"
char VersionStr[64] = {};
for (int i = 0; i < 63; ++i)
{
char c = 0;
Memory::Read<char>(Result.Address + i, c);
if (c == 0 || c == '-' && i > 30)
break;
VersionStr[i] = c;
}
USS_LOG("Found version string: %s", VersionStr);
const char* CLStart = strstr(VersionStr, "CL-");
if (CLStart)
{
uint32 CL = 0;
if (sscanf(CLStart, "CL-%u", &CL) == 1 && CL > 0)
{
return MapCLToVersion(CL);
}
}
return false;
}
bool FVersionResolver::TryDetectFromCL()
{
// Try to find CL using TimmiesAwesomeOffsetFinder
// This uses pattern scanning to find the GetEngineVersion function
// Pattern for GetEngineVersion (varies by version)
// @timmie
uintptr_t CLAddr = PatternScanner::Get()->FindGetEngineVersion();
if (CLAddr == 0)
{
USS_LOG("CL detection via pattern failed");
return false;
}
static FString(*GetEngineVersion)() = decltype(GetEngineVersion)(CLAddr);
FString EngineVersion = GetEngineVersion();
std::string EngineVer = EngineVersion.ToString();
size_t DashPos = EngineVer.find("-");
size_t PlusPos = EngineVer.find("+++");
if (DashPos == std::string::npos || PlusPos == std::string::npos || PlusPos <= DashPos)
{
USS_LOG("Failed to parse CL from EngineVersion: %s", EngineVer.c_str());
return false;
}
std::string CLStr = EngineVer.substr(DashPos + 1, PlusPos - (DashPos + 1));
uint32 CL = static_cast<uint32>(std::strtoul(CLStr.c_str(), nullptr, 10));
USS_LOG("Found CL from version function: %u", CL);
return MapCLToVersion(CL);
}
bool FVersionResolver::TryDetectFromPatterns()
{
// Try to detect version from characteristic patterns
// @timmie
// Check for FField (UE 4.25+)
uintptr_t FFieldAddr = TAOF::FindPattern(TAOF::Patterns::ProcessEvent_423Plus);
// Check for FNamePool (UE 4.23+)
uintptr_t FNamePoolAddr = TAOF::FindPattern(TAOF::Patterns::GNames_FNamePool);
// Check for older GNames (Pre-4.23)
uintptr_t OldGNamesAddr = TAOF::FindPattern(TAOF::Patterns::GNames_Pre423);
if (FFieldAddr && FNamePoolAddr)
{
// UE 4.25+
m_VersionInfo.EngineVersionMajor = 4;
m_VersionInfo.EngineVersionMinor = 25;
m_VersionInfo.FortniteVersion = 15.00;
m_VersionInfo.FortniteCL = 9500000;
USS_LOG("Detected UE 4.25+ from FField pattern");
return true;
}
else if (FNamePoolAddr)
{
// UE 4.23-4.24
m_VersionInfo.EngineVersionMajor = 4;
m_VersionInfo.EngineVersionMinor = 23;
m_VersionInfo.FortniteVersion = 9.00;
m_VersionInfo.FortniteCL = 5200000;
USS_LOG("Detected UE 4.23+ from FNamePool pattern");
return true;
}
else if (OldGNamesAddr)
{
// Pre-4.23 - check for chunked objects (4.21+)
uintptr_t ChunkedAddr = TAOF::FindPattern(TAOF::Patterns::GObjects_421Plus);
if (ChunkedAddr)
{
// UE 4.21-4.22
m_VersionInfo.EngineVersionMajor = 4;
m_VersionInfo.EngineVersionMinor = 21;
m_VersionInfo.FortniteVersion = 5.00;
m_VersionInfo.FortniteCL = 4300000;
USS_LOG("Detected UE 4.21+ from chunked GObjects pattern");
}
else
{
// UE 4.16-4.20
m_VersionInfo.EngineVersionMajor = 4;
m_VersionInfo.EngineVersionMinor = 16;
m_VersionInfo.FortniteVersion = 1.80;
m_VersionInfo.FortniteCL = 3724489;
USS_LOG("Detected UE 4.16-4.20 from old GNames pattern");
}
return true;
}
USS_WARN("Pattern-based version detection failed, using fallback...");
// Default to baseline version for development/testing
m_VersionInfo.EngineVersionMajor = 4;
m_VersionInfo.EngineVersionMinor = 16;
m_VersionInfo.FortniteVersion = 1.80;
m_VersionInfo.FortniteCL = 3724489;
return true;
}
bool FVersionResolver::MapCLToVersion(uint32 CL)
{
for (size_t i = 0; i < s_CLMappingCount; ++i)
{
const auto& Mapping = s_CLMappings[i];
if (CL >= Mapping.CLMin && CL < Mapping.CLMax)
{
m_VersionInfo.FortniteCL = CL;
m_VersionInfo.EngineVersionMajor = Mapping.EngineMajor;
m_VersionInfo.EngineVersionMinor = Mapping.EngineMinor;
m_VersionInfo.FortniteVersion = Mapping.FortniteVersion;
m_VersionInfo.FortniteSeasonMajor = static_cast<uint32>(Mapping.FortniteVersion);
m_VersionInfo.FortniteSeasonMinor = static_cast<uint32>((Mapping.FortniteVersion - m_VersionInfo.FortniteSeasonMajor) * 100);
USS_LOG("Mapped CL %u to Fortnite %.2f (UE %u.%u)",
CL, Mapping.FortniteVersion, Mapping.EngineMajor, Mapping.EngineMinor);
return true;
}
}
USS_WARN("Unknown CL: %u - not in mapping table", CL);
return false;
}
void FVersionResolver::DetermineGeneration()
{
uint32 Major = m_VersionInfo.EngineVersionMajor;
uint32 Minor = m_VersionInfo.EngineVersionMinor;
if (Major == 4)
{
if (Minor <= 19)
m_VersionInfo.Generation = EEngineGeneration::UE4_16_19;
else if (Minor <= 22)
m_VersionInfo.Generation = EEngineGeneration::UE4_20_22;
else if (Minor <= 24)
m_VersionInfo.Generation = EEngineGeneration::UE4_23_24;
else if (Minor == 25)
m_VersionInfo.Generation = EEngineGeneration::UE4_25;
else
m_VersionInfo.Generation = EEngineGeneration::UE4_26_27;
}
else if (Major == 5)
{
if (Minor == 0)
m_VersionInfo.Generation = EEngineGeneration::UE5_0;
else
m_VersionInfo.Generation = EEngineGeneration::UE5_1_Plus;
}
else
{
m_VersionInfo.Generation = EEngineGeneration::Unknown;
}
}
void FVersionResolver::ComputeFeatureFlags()
{
uint32 Major = m_VersionInfo.EngineVersionMajor;
uint32 Minor = m_VersionInfo.EngineVersionMinor;
double FN = m_VersionInfo.FortniteVersion;
// FNamePool introduced in UE 4.23 (FN 9.0)
m_VersionInfo.bUseFNamePool = (Major == 4 && Minor >= 23) || Major >= 5;
// FField introduced in UE 4.25 (FN 15.0)
m_VersionInfo.bUseFField = (Major == 4 && Minor >= 25) || Major >= 5;
// Chunked UObject array in UE 4.21+ (FN 5.0+)
m_VersionInfo.bUseChunkedObjects = (Major == 4 && Minor >= 21) || Major >= 5;
// New FastArraySerializer in FN 8.30+
m_VersionInfo.bUseNewFastArraySerializer = FN >= 8.30;
// TObjectPtr in UE 5.0+
m_VersionInfo.bUseTObjectPtr = Major >= 5;
// STW support (check version range)
m_VersionInfo.bSupportsSTW = FN <= 20.00;
}
}