/** * 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 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(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(); // Engine returns FString by value - our FString wrapper detects this via the // ownership flag (sign bit of m_Max) and won't try to delete[] engine memory. // Note: The engine-allocated buffer becomes a small one-time memory leak, which // is acceptable for version detection that runs once at startup. 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(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(Mapping.FortniteVersion); m_VersionInfo.FortniteSeasonMinor = static_cast((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; } }