Scan executable for GPS route strings

This commit is contained in:
2026-06-19 22:01:44 -05:00
parent 4fb1ba6912
commit 37bf5e717f
+149
View File
@@ -8,7 +8,10 @@
#include <windows.h> #include <windows.h>
#include <algorithm>
#include <array>
#include <chrono> #include <chrono>
#include <cstring>
#include <cstdint> #include <cstdint>
#include <filesystem> #include <filesystem>
#include <fstream> #include <fstream>
@@ -87,6 +90,7 @@ std::mutex gLogMutex;
RED4ext::v1::PluginHandle gHandle = nullptr; RED4ext::v1::PluginHandle gHandle = nullptr;
const RED4ext::v1::Sdk* gSdk = nullptr; const RED4ext::v1::Sdk* gSdk = nullptr;
uint32_t gUpdateLogCount = 0; uint32_t gUpdateLogCount = 0;
bool gScannedExecutable = false;
std::filesystem::path GetLogPath() std::filesystem::path GetLogPath()
{ {
@@ -142,11 +146,156 @@ const char* ReasonToString(RED4ext::v1::EMainReason aReason)
return "Unknown"; 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<uintptr_t>(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<uintptr_t>(GetModuleHandleW(nullptr));
if (!imageBase)
{
LogRed4ext("exe-string scan skipped: main module unavailable");
return;
}
const auto* dos = reinterpret_cast<const IMAGE_DOS_HEADER*>(imageBase);
if (dos->e_magic != IMAGE_DOS_SIGNATURE)
{
LogRed4ext("exe-string scan skipped: bad DOS header");
return;
}
const auto* nt = reinterpret_cast<const IMAGE_NT_HEADERS*>(imageBase + dos->e_lfanew);
if (nt->Signature != IMAGE_NT_SIGNATURE)
{
LogRed4ext("exe-string scan skipped: bad NT header");
return;
}
const auto imageSize = static_cast<size_t>(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<const char*, 18> 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<const void*>(cursor), &mbi, sizeof(mbi)))
{
break;
}
const auto regionBegin = reinterpret_cast<uintptr_t>(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<const uint8_t*>(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) bool OnRunningEnter(void* aApp)
{ {
std::ostringstream line; std::ostringstream line;
line << "GameState Running OnEnter app=" << aApp; line << "GameState Running OnEnter app=" << aApp;
LogRed4ext(line.str()); LogRed4ext(line.str());
ScanExecutableStrings();
return true; return true;
} }