From 37bf5e717f5cbc53d861a821e4703581b64a8c8f Mon Sep 17 00:00:00 2001 From: Jacob Babor Date: Fri, 19 Jun 2026 22:01:44 -0500 Subject: [PATCH] Scan executable for GPS route strings --- red4ext/EdgeWeightGPS/src/Main.cpp | 149 +++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) diff --git a/red4ext/EdgeWeightGPS/src/Main.cpp b/red4ext/EdgeWeightGPS/src/Main.cpp index cf123ca..d41ab99 100644 --- a/red4ext/EdgeWeightGPS/src/Main.cpp +++ b/red4ext/EdgeWeightGPS/src/Main.cpp @@ -8,7 +8,10 @@ #include +#include +#include #include +#include #include #include #include @@ -87,6 +90,7 @@ std::mutex gLogMutex; RED4ext::v1::PluginHandle gHandle = nullptr; const RED4ext::v1::Sdk* gSdk = nullptr; uint32_t gUpdateLogCount = 0; +bool gScannedExecutable = false; std::filesystem::path GetLogPath() { @@ -142,11 +146,156 @@ const char* ReasonToString(RED4ext::v1::EMainReason aReason) return "Unknown"; } +bool IsReadablePage(DWORD aProtect) +{ + if ((aProtect & PAGE_GUARD) != 0 || (aProtect & PAGE_NOACCESS) != 0) + { + return false; + } + + const auto baseProtect = aProtect & 0xFF; + return baseProtect == PAGE_READONLY || baseProtect == PAGE_READWRITE || baseProtect == PAGE_WRITECOPY || + baseProtect == PAGE_EXECUTE_READ || baseProtect == PAGE_EXECUTE_READWRITE || + baseProtect == PAGE_EXECUTE_WRITECOPY; +} + +void ScanRegionForString(const uint8_t* aRegionBegin, size_t aRegionSize, uintptr_t aImageBase, const char* aNeedle, + uint32_t& aMatches) +{ + constexpr uint32_t kMaxMatchesPerNeedle = 8; + + const auto needleSize = std::strlen(aNeedle); + if (needleSize == 0 || aRegionSize < needleSize || aMatches >= kMaxMatchesPerNeedle) + { + return; + } + + const auto* cursor = aRegionBegin; + const auto* regionEnd = aRegionBegin + aRegionSize; + + while (cursor + needleSize <= regionEnd && aMatches < kMaxMatchesPerNeedle) + { + if (std::memcmp(cursor, aNeedle, needleSize) == 0) + { + const auto rva = reinterpret_cast(cursor) - aImageBase; + std::ostringstream line; + line << "exe-string match needle=\"" << aNeedle << "\" rva=0x" << std::hex << rva << std::dec; + LogRed4ext(line.str()); + ++aMatches; + cursor += needleSize; + continue; + } + + ++cursor; + } +} + +void ScanExecutableStrings() +{ + if (gScannedExecutable) + { + return; + } + gScannedExecutable = true; + + const auto imageBase = reinterpret_cast(GetModuleHandleW(nullptr)); + if (!imageBase) + { + LogRed4ext("exe-string scan skipped: main module unavailable"); + return; + } + + const auto* dos = reinterpret_cast(imageBase); + if (dos->e_magic != IMAGE_DOS_SIGNATURE) + { + LogRed4ext("exe-string scan skipped: bad DOS header"); + return; + } + + const auto* nt = reinterpret_cast(imageBase + dos->e_lfanew); + if (nt->Signature != IMAGE_NT_SIGNATURE) + { + LogRed4ext("exe-string scan skipped: bad NT header"); + return; + } + + const auto imageSize = static_cast(nt->OptionalHeader.SizeOfImage); + std::ostringstream header; + header << "exe-string scan begin base=0x" << std::hex << imageBase << " size=0x" << imageSize << std::dec; + LogRed4ext(header.str()); + + constexpr std::array kNeedles{ + "GPSSystem/Tick", + "GPSSystem", + "GPSSettings", + "OnSetHasGPSPortal", + "IsGPSPortal", + "m_cookedGpsData", + "gameCookedGpsMappinData", + "GetNavigationFunctionalTests", + "TrafficSystem_Pathfinding", + "TrafficSystem_PathMap", + "Pathfinding Algorithm Failed", + "No Path Found in Traffic", + "PathFindingFailed", + "StartPathfinding", + "StopPathfinding", + "worldTrafficLanePlayerGPSInfo", + "worldTrafficPersistentResource", + "worldTrafficConnectivityOutLane", + }; + + for (const auto* needle : kNeedles) + { + uint32_t matches = 0; + uintptr_t cursor = imageBase; + const uintptr_t end = imageBase + imageSize; + + while (cursor < end) + { + MEMORY_BASIC_INFORMATION mbi{}; + if (!VirtualQuery(reinterpret_cast(cursor), &mbi, sizeof(mbi))) + { + break; + } + + const auto regionBegin = reinterpret_cast(mbi.BaseAddress); + const auto regionEnd = regionBegin + mbi.RegionSize; + if (mbi.State == MEM_COMMIT && mbi.Type == MEM_IMAGE && IsReadablePage(mbi.Protect)) + { + const auto scanBegin = std::max(regionBegin, imageBase); + const auto scanEnd = std::min(regionEnd, end); + if (scanBegin < scanEnd) + { + ScanRegionForString(reinterpret_cast(scanBegin), scanEnd - scanBegin, imageBase, + needle, matches); + } + } + + if (regionEnd <= cursor) + { + break; + } + cursor = regionEnd; + } + + if (matches == 0) + { + std::ostringstream line; + line << "exe-string no match needle=\"" << needle << "\""; + LogRed4ext(line.str()); + } + } + + LogRed4ext("exe-string scan end"); +} + bool OnRunningEnter(void* aApp) { std::ostringstream line; line << "GameState Running OnEnter app=" << aApp; LogRed4ext(line.str()); + ScanExecutableStrings(); return true; }