diff --git a/AsaApi/AsaApi.vcxproj b/AsaApi/AsaApi.vcxproj
index 2ec10f5..7192005 100644
--- a/AsaApi/AsaApi.vcxproj
+++ b/AsaApi/AsaApi.vcxproj
@@ -78,10 +78,11 @@
DynamicLibrary
false
- v143
+ v145
true
Unicode
- 14.39.33519
+
+
DynamicLibrary
@@ -267,11 +268,11 @@
true
true
true
- NDEBUG;ASAAPI_EXPORTS;ARK_EXPORTS;_WINDOWS;_USRDLL;POCO_STATIC;%(PreprocessorDefinitions)
+ NOMINMAX;_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR;_SILENCE_ALL_CXX17_DEPRECATION_WARNINGS;NDEBUG;ASAAPI_EXPORTS;ARK_EXPORTS;_WINDOWS;_USRDLL;POCO_STATIC;%(PreprocessorDefinitions)
true
NotUsing
pch.h
- $(SolutionDir)AsaApi\Core\Public\API\UE;$(SolutionDir)AsaApi\Core\Public;%(AdditionalIncludeDirectories)
+ $(SolutionDir)AsaApi\Core\Public\API\UE;$(SolutionDir)AsaApi\Core\Public;$(SolutionDir)Includes\raw_pdb\src;%(AdditionalIncludeDirectories)
stdcpp20
stdc17
true
@@ -371,6 +372,26 @@ copy "$(SolutionDir)$(PlatformName)\$(ConfigurationName)\$(ProjectName).pdb" "F:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/AsaApi/AsaApi.vcxproj.filters b/AsaApi/AsaApi.vcxproj.filters
index dcb3686..f4feb8c 100644
--- a/AsaApi/AsaApi.vcxproj.filters
+++ b/AsaApi/AsaApi.vcxproj.filters
@@ -109,6 +109,9 @@
{f1b840ad-6bee-4a69-b9ca-20346e129b27}
+
+ {358771c7-78e5-4c89-8d84-58b61dfd3d7d}
+
@@ -177,6 +180,66 @@
Source Files\Core\Private\Tools
+
+ Source Files\Core\Private\PDBReader\raw_pdb
+
+
+ Source Files\Core\Private\PDBReader\raw_pdb
+
+
+ Source Files\Core\Private\PDBReader\raw_pdb
+
+
+ Source Files\Core\Private\PDBReader\raw_pdb
+
+
+ Source Files\Core\Private\PDBReader\raw_pdb
+
+
+ Source Files\Core\Private\PDBReader\raw_pdb
+
+
+ Source Files\Core\Private\PDBReader\raw_pdb
+
+
+ Source Files\Core\Private\PDBReader\raw_pdb
+
+
+ Source Files\Core\Private\PDBReader\raw_pdb
+
+
+ Source Files\Core\Private\PDBReader\raw_pdb
+
+
+ Source Files\Core\Private\PDBReader\raw_pdb
+
+
+ Source Files\Core\Private\PDBReader\raw_pdb
+
+
+ Source Files\Core\Private\PDBReader\raw_pdb
+
+
+ Source Files\Core\Private\PDBReader\raw_pdb
+
+
+ Source Files\Core\Private\PDBReader\raw_pdb
+
+
+ Source Files\Core\Private\PDBReader\raw_pdb
+
+
+ Source Files\Core\Private\PDBReader\raw_pdb
+
+
+ Source Files\Core\Private\PDBReader\raw_pdb
+
+
+ Source Files\Core\Private\PDBReader\raw_pdb
+
+
+ Source Files\Core\Private\PDBReader\raw_pdb
+
diff --git a/AsaApi/Core/Private/Ark/ArkBaseApi.cpp b/AsaApi/Core/Private/Ark/ArkBaseApi.cpp
index 01ff8df..b5836d8 100644
--- a/AsaApi/Core/Private/Ark/ArkBaseApi.cpp
+++ b/AsaApi/Core/Private/Ark/ArkBaseApi.cpp
@@ -10,6 +10,8 @@
#include "HooksImpl.h"
#include "ApiUtils.h"
#include
+#include
+#include
#include "Requests.h"
#include
#include
@@ -42,6 +44,8 @@ namespace API
std::unordered_map offsets_dump;
std::unordered_map bitfields_dump;
+ std::unordered_map fields_dump;
+ std::unordered_map functions_dump;
try
{
@@ -64,11 +68,12 @@ namespace API
const fs::path keyCacheFile = fs::path(exe_path).append(ArkBaseApi::GetApiName()+"/Cache/cached_key.cache");
const fs::path offsetsCacheFile = fs::path(exe_path).append(ArkBaseApi::GetApiName()+"/Cache/cached_offsets.cache");
const fs::path bitfieldsCacheFile = fs::path(exe_path).append(ArkBaseApi::GetApiName()+"/Cache/cached_bitfields.cache");
+ const fs::path fieldsCacheFile = fs::path(exe_path).append(ArkBaseApi::GetApiName()+"/Cache/cached_fields.cache");
+ const fs::path functionsCacheFile = fs::path(exe_path).append(ArkBaseApi::GetApiName()+"/Cache/cached_functions.cache");
const fs::path offsetsCacheFilePlain = fs::path(exe_path).append(ArkBaseApi::GetApiName() + "/Cache/cached_offsets.txt");
const std::string fileHash = Cache::calculateSHA256(filepath);
std::string storedHash = Cache::readFromFile(keyCacheFile);
std::unordered_set pdbIgnoreSet = Cache::readFileIntoSet(pdbIgnoreFile);
- const std::string defaultCDNUrl = "https://cdn.pelayori.com/cache/";
const fs::path arkApiDir = fs::path(exe_path).append(ArkBaseApi::GetApiName());
@@ -99,32 +104,23 @@ namespace API
Log::GetLog()->info("Added DLL search directory: {}", std::filesystem::path(w).string());
}
- if (autoCacheConfig.value("Enable", true)
- && autoCacheConfig.value("DownloadCacheURL", defaultCDNUrl) != ""
- && (fileHash != storedHash || !fs::exists(offsetsCacheFile) || !fs::exists(bitfieldsCacheFile)))
- {
- const fs::path downloadFile = autoCacheConfig.value("DownloadCacheURL", defaultCDNUrl) + fileHash + ".zip";
- const fs::path localFile = fs::path(exe_path).append(ArkBaseApi::GetApiName() + "/Cache/" + fileHash + ".zip");
-
- if (ArkBaseApi::DownloadCacheFiles(downloadFile, localFile))
- storedHash = Cache::readFromFile(keyCacheFile);
- else
- Log::GetLog()->warn("Ooops you are early, the cache has not finished cooking yet! Cache files usually take 10 minutes to be ready after an update. If more time has passed please contact developers.");
-
- if (fs::exists(localFile))
- fs::remove(localFile);
- }
-
if (fileHash != storedHash || !fs::exists(offsetsCacheFile) || !fs::exists(bitfieldsCacheFile))
{
- Log::GetLog()->info("Cache refresh required this will take 10-20 minutes to complete");
- pdb_reader.Read(filepath, &offsets_dump, &bitfields_dump, pdbIgnoreSet);
+ Log::GetLog()->info("Cache refresh required this will take few seconds to complete");
+ pdb_reader.Read(filepath, &offsets_dump, &bitfields_dump, pdbIgnoreSet, &fields_dump, &functions_dump);
Log::GetLog()->info("Caching offsets for faster loading next time");
Cache::serializeMap(offsets_dump, offsetsCacheFile);
Log::GetLog()->info("Caching bitfields for faster loading next time");
Cache::serializeMap(bitfields_dump, bitfieldsCacheFile);
+
+ Log::GetLog()->info("Caching field type info for faster loading next time");
+ Cache::serializeMap(fields_dump, fieldsCacheFile);
+
+ Log::GetLog()->info("Caching function info for faster loading next time");
+ Cache::serializeMap(functions_dump, functionsCacheFile);
+
Cache::saveToFile(keyCacheFile, fileHash);
Cache::saveToFilePlain(offsetsCacheFilePlain, offsets_dump);
}
@@ -136,6 +132,18 @@ namespace API
Log::GetLog()->info("Reading cached bitfields");
bitfields_dump = Cache::deserializeMap(bitfieldsCacheFile);
+
+ if (fs::exists(fieldsCacheFile))
+ {
+ Log::GetLog()->info("Reading cached field types");
+ fields_dump = Cache::deserializeMap(fieldsCacheFile);
+ }
+
+ if (fs::exists(functionsCacheFile))
+ {
+ Log::GetLog()->info("Reading cached function info");
+ functions_dump = Cache::deserializeMap(functionsCacheFile);
+ }
}
}
catch (const std::exception& error)
@@ -144,7 +152,7 @@ namespace API
return false;
}
- Offsets::Get().Init(move(offsets_dump), move(bitfields_dump));
+ Offsets::Get().Init(move(offsets_dump), move(bitfields_dump), move(fields_dump), move(functions_dump));
Sleep(10);
AsaApi::InitHooks();
Log::GetLog()->info("API was successfully loaded");
@@ -282,9 +290,11 @@ namespace API
{
GetCommands()->AddConsoleCommand("plugins.load", &LoadPluginCmd);
GetCommands()->AddConsoleCommand("plugins.unload", &UnloadPluginCmd);
+ GetCommands()->AddConsoleCommand("dumpclass", &DumpClassCmd);
GetCommands()->AddRconCommand("plugins.load", &LoadPluginRcon);
GetCommands()->AddRconCommand("plugins.unload", &UnloadPluginRcon);
GetCommands()->AddRconCommand("map.setserverid", &SetServerID);
+ GetCommands()->AddRconCommand("dumpclass", &DumpClassRcon);
}
FString ArkBaseApi::LoadPlugin(FString* cmd)
@@ -411,4 +421,206 @@ namespace API
rcon_connection->SendMessageW(rcon_packet->Id, 0, &reply);
}
+
+ FString ArkBaseApi::DumpClass(FString* cmd) {
+ TArray parsed;
+ cmd->ParseIntoArray(parsed, L" ", true);
+
+ if (!parsed.IsValidIndex(1)) {
+ return L"Usage: dumpclass ";
+ }
+
+ const std::string className = parsed[1].ToString();
+ const bool isGlobal = (className == "Global");
+
+ try {
+ namespace fs = std::filesystem;
+
+ TCHAR buffer[MAX_PATH];
+ GetModuleFileName(NULL, buffer, sizeof(buffer));
+ fs::path exe_path = fs::path(buffer).parent_path();
+
+ const fs::path dumpDir = exe_path / "ArkApi" / "ClassDumps";
+ if (!fs::exists(dumpDir))
+ fs::create_directories(dumpDir);
+
+ const fs::path outputFile = dumpDir / (className + ".h");
+ std::ofstream file(outputFile);
+
+ if (!file.is_open()) {
+ return FString::Format("Failed to create output file: {}", outputFile.string().c_str());
+ }
+
+ auto fields = Offsets::Get().GetFieldsForClass(className);
+ auto bitfields = Offsets::Get().GetBitFieldsForClass(className);
+ auto functions = Offsets::Get().GetFunctionsForClass(className);
+
+ if (fields.empty() && bitfields.empty() && functions.empty()) {
+ file.close();
+ fs::remove(outputFile);
+ return FString::Format("No data found for class: {}", className.c_str());
+ }
+
+ std::sort(fields.begin(), fields.end(), [](const auto& a, const auto& b) { return a.second.offset < b.second.offset; });
+ std::sort(bitfields.begin(), bitfields.end(), [](const auto& a, const auto& b) { return a.second.offset < b.second.offset; });
+ std::sort(functions.begin(), functions.end(), [](const auto& a, const auto& b) { return a.second.signature < b.second.signature; });
+
+ if (isGlobal) {
+ file << "namespace " << className << "\n{\n";
+ }
+ else {
+ file << "struct " << className << "\n{\n";
+ }
+
+ if (!fields.empty()) {
+ file << "\t// Fields\n\n";
+ for (const auto& [key, info] : fields) {
+ size_t dotPos = key.rfind('.');
+ std::string memberName = (dotPos != std::string::npos) ? key.substr(dotPos + 1) : key;
+
+ if (isGlobal) {
+ file << "\tinline " << info.type << "& " << memberName << "Field() { return *GetNativeDataPointerField<" << info.type << "*>(nullptr, \"" << key << "\"); }\n";
+ }
+ else {
+ file << "\t" << info.type << "& " << memberName << "Field() { return *GetNativePointerField<" << info.type << "*>(this, \"" << key << "\"); }\n";
+ }
+ }
+ }
+
+ if (!bitfields.empty()) {
+ file << "\n\t// Bitfields\n\n";
+ for (const auto& [key, bf] : bitfields) {
+ size_t dotPos = key.rfind('.');
+ std::string memberName = (dotPos != std::string::npos) ? key.substr(dotPos + 1) : key;
+
+ if (isGlobal) {
+ file << "\tinline BitFieldValue " << memberName << "Field() { return { nullptr, \"" << key << "\" }; }\n";
+ }
+ else {
+ file << "\tBitFieldValue " << memberName << "Field() { return { this, \"" << key << "\" }; }\n";
+ }
+ }
+ }
+
+ if (!functions.empty()) {
+ file << "\n\t// Functions\n\n";
+ for (const auto& [key, info] : functions) {
+ if (info.signature.rfind("exec", 0) == 0)
+ continue;
+
+ std::string paramTypes = info.params;
+ std::vector paramNamesList;
+ if (!info.paramNames.empty()) {
+ std::string name;
+ for (char c : info.paramNames) {
+ if (c == ',') {
+ if (!name.empty()) paramNamesList.push_back(name);
+ name.clear();
+ }
+ else {
+ name += c;
+ }
+ }
+ if (!name.empty()) paramNamesList.push_back(name);
+ }
+
+ std::string paramDecl;
+ std::string paramCall;
+ if (!info.params.empty()) {
+ std::vector paramList;
+ std::string param;
+ int depth = 0;
+ for (char c : info.params) {
+ if (c == '<') depth++;
+ else if (c == '>') depth--;
+ else if (c == ',' && depth == 0) {
+ paramList.push_back(param);
+ param.clear();
+ continue;
+ }
+ param += c;
+ }
+ if (!param.empty()) paramList.push_back(param);
+
+ for (size_t i = 0; i < paramList.size(); i++) {
+ if (i > 0) {
+ paramDecl += ", ";
+ paramCall += ", ";
+ }
+
+ std::string paramName = (i < paramNamesList.size()) ? paramNamesList[i] : ("arg" + std::to_string(i));
+ paramDecl += paramList[i] + " " + paramName;
+ paramCall += paramName;
+ }
+ }
+
+ size_t parenPos = info.signature.find('(');
+ std::string funcName = (parenPos != std::string::npos) ? info.signature.substr(0, parenPos) : info.signature;
+
+ if (isGlobal) {
+ if (info.returnType == "void" || info.returnType.empty()) {
+ if (paramTypes.empty())
+ file << "\tinline void " << funcName << "() { NativeCall(nullptr, \"" << key << "\"); }\n";
+ else
+ file << "\tinline void " << funcName << "(" << paramDecl << ") { NativeCall(nullptr, \"" << key << "\", " << paramCall << "); }\n";
+ }
+ else {
+ if (paramTypes.empty())
+ file << "\tinline " << info.returnType << " " << funcName << "() { return NativeCall<" << info.returnType << ">(nullptr, \"" << key << "\"); }\n";
+ else
+ file << "\tinline " << info.returnType << " " << funcName << "(" << paramDecl << ") { return NativeCall<" << info.returnType << ", " << paramTypes << ">(nullptr, \"" << key << "\", " << paramCall << "); }\n";
+ }
+ }
+ else if (info.isStatic) {
+ if (info.returnType == "void" || info.returnType.empty()) {
+ if (paramTypes.empty())
+ file << "\tstatic void " << funcName << "() { NativeCall(nullptr, \"" << key << "\"); }\n";
+ else
+ file << "\tstatic void " << funcName << "(" << paramDecl << ") { NativeCall(nullptr, \"" << key << "\", " << paramCall << "); }\n";
+ }
+ else {
+ if (paramTypes.empty())
+ file << "\tstatic " << info.returnType << " " << funcName << "() { return NativeCall<" << info.returnType << ">(nullptr, \"" << key << "\"); }\n";
+ else
+ file << "\tstatic " << info.returnType << " " << funcName << "(" << paramDecl << ") { return NativeCall<" << info.returnType << ", " << paramTypes << ">(nullptr, \"" << key << "\", " << paramCall << "); }\n";
+ }
+ }
+ else {
+ if (info.returnType == "void" || info.returnType.empty()) {
+ if (paramTypes.empty())
+ file << "\tvoid " << funcName << "() { NativeCall(this, \"" << key << "\"); }\n";
+ else
+ file << "\tvoid " << funcName << "(" << paramDecl << ") { NativeCall(this, \"" << key << "\", " << paramCall << "); }\n";
+ }
+ else {
+ if (paramTypes.empty())
+ file << "\t" << info.returnType << " " << funcName << "() { return NativeCall<" << info.returnType << ">(this, \"" << key << "\"); }\n";
+ else
+ file << "\t" << info.returnType << " " << funcName << "(" << paramDecl << ") { return NativeCall<" << info.returnType << ", " << paramTypes << ">(this, \"" << key << "\", " << paramCall << "); }\n";
+ }
+ }
+ }
+ }
+
+ file << "};\n";
+ file.close();
+
+ Log::GetLog()->info("Class dump saved to: {}", outputFile.string());
+ return FString::Format("Class dump saved to: {}", outputFile.string().c_str());
+ }
+ catch (const std::exception& error) {
+ Log::GetLog()->warn("({}) {}", __FUNCTION__, error.what());
+ return FString::Format("Failed to dump class - {}", error.what());
+ }
+ }
+
+ void ArkBaseApi::DumpClassCmd(APlayerController* player_controller, FString* cmd, bool /*unused*/) {
+ auto* shooter_controller = static_cast(player_controller);
+ AsaApi::GetApiUtils().SendServerMessage(shooter_controller, FColorList::Green, *DumpClass(cmd));
+ }
+
+ void ArkBaseApi::DumpClassRcon(RCONClientConnection* rcon_connection, RCONPacket* rcon_packet, UWorld* /*unused*/) {
+ FString reply = DumpClass(&rcon_packet->Body);
+ rcon_connection->SendMessageW(rcon_packet->Id, 0, &reply);
+ }
} // namespace API
diff --git a/AsaApi/Core/Private/Ark/ArkBaseApi.h b/AsaApi/Core/Private/Ark/ArkBaseApi.h
index af6f69e..47c91dc 100644
--- a/AsaApi/Core/Private/Ark/ArkBaseApi.h
+++ b/AsaApi/Core/Private/Ark/ArkBaseApi.h
@@ -42,6 +42,12 @@ namespace API
static void SetServerID(RCONClientConnection* /*rcon_connection*/, RCONPacket* /*rcon_packet*/,
UWorld* /*unused*/);
+ // Class dump command
+ static FString DumpClass(FString* cmd);
+ static void DumpClassCmd(APlayerController* /*player_controller*/, FString* /*cmd*/, bool /*unused*/);
+ static void DumpClassRcon(RCONClientConnection* /*rcon_connection*/, RCONPacket* /*rcon_packet*/,
+ UWorld* /*unused*/);
+
std::unique_ptr commands_;
std::unique_ptr hooks_;
std::unique_ptr api_utils_;
diff --git a/AsaApi/Core/Private/Cache.h b/AsaApi/Core/Private/Cache.h
index 6035644..6525ee2 100644
--- a/AsaApi/Core/Private/Cache.h
+++ b/AsaApi/Core/Private/Cache.h
@@ -1,6 +1,7 @@
#pragma once
#include
#include "Logger/Logger.h"
+#include "PDBReader/PDBReader.h"
#include
#include
@@ -16,6 +17,24 @@ namespace Cache
std::string readFromFile(const std::filesystem::path& filename);
+ // Helper to write a string to binary file
+ inline void writeString(std::ofstream& file, const std::string& str)
+ {
+ std::size_t len = str.size();
+ file.write(reinterpret_cast(&len), sizeof(len));
+ file.write(str.data(), len);
+ }
+
+ // Helper to read a string from binary file
+ inline bool readString(std::ifstream& file, std::string& str)
+ {
+ std::size_t len;
+ if (!file.read(reinterpret_cast(&len), sizeof(len)))
+ return false;
+ str.resize(len);
+ return file.read(&str[0], len).good() || file.eof();
+ }
+
template
void serializeMap(const std::unordered_map& data, const std::filesystem::path& filename)
{
@@ -37,6 +56,53 @@ namespace Cache
file.close();
}
+ // Specialized serialization for FieldInfo
+ template <>
+ inline void serializeMap(const std::unordered_map& data, const std::filesystem::path& filename)
+ {
+ std::ofstream file(filename, std::ios::binary | std::ios::trunc);
+ if (!file.is_open())
+ {
+ Log::GetLog()->error("Error opening file for writing: " + filename.string());
+ return;
+ }
+
+ for (const auto& entry : data)
+ {
+ writeString(file, entry.first);
+ writeString(file, entry.second.type);
+ file.write(reinterpret_cast(&entry.second.offset), sizeof(entry.second.offset));
+ file.write(reinterpret_cast(&entry.second.isPointer), sizeof(entry.second.isPointer));
+ }
+
+ file.close();
+ }
+
+ // Specialized serialization for FunctionInfo
+ template <>
+ inline void serializeMap(const std::unordered_map& data, const std::filesystem::path& filename)
+ {
+ std::ofstream file(filename, std::ios::binary | std::ios::trunc);
+ if (!file.is_open())
+ {
+ Log::GetLog()->error("Error opening file for writing: " + filename.string());
+ return;
+ }
+
+ for (const auto& entry : data)
+ {
+ writeString(file, entry.first);
+ writeString(file, entry.second.returnType);
+ writeString(file, entry.second.signature);
+ writeString(file, entry.second.params);
+ writeString(file, entry.second.paramNames);
+ file.write(reinterpret_cast(&entry.second.offset), sizeof(entry.second.offset));
+ file.write(reinterpret_cast(&entry.second.isStatic), sizeof(entry.second.isStatic));
+ }
+
+ file.close();
+ }
+
template
std::unordered_map deserializeMap(const std::filesystem::path& filename)
{
@@ -79,6 +145,81 @@ namespace Cache
return data;
}
+ // Specialized deserialization for FieldInfo
+ template <>
+ inline std::unordered_map deserializeMap(const std::filesystem::path& filename)
+ {
+ std::unordered_map data;
+
+ if (!std::filesystem::exists(filename))
+ {
+ Log::GetLog()->error("File does not exist: " + filename.string());
+ return data;
+ }
+
+ std::ifstream file(filename, std::ios::binary);
+ if (!file.is_open())
+ {
+ Log::GetLog()->error("Error opening file for reading: " + filename.string());
+ return data;
+ }
+
+ data.reserve(300000);
+
+ std::string key;
+ while (readString(file, key))
+ {
+ API::FieldInfo info;
+ if (!readString(file, info.type)) break;
+ if (!file.read(reinterpret_cast(&info.offset), sizeof(info.offset))) break;
+ if (!file.read(reinterpret_cast(&info.isPointer), sizeof(info.isPointer))) break;
+ data[key] = info;
+ key.clear();
+ }
+
+ file.close();
+ return data;
+ }
+
+ // Specialized deserialization for FunctionInfo
+ template <>
+ inline std::unordered_map deserializeMap(const std::filesystem::path& filename)
+ {
+ std::unordered_map data;
+
+ if (!std::filesystem::exists(filename))
+ {
+ Log::GetLog()->error("File does not exist: " + filename.string());
+ return data;
+ }
+
+ std::ifstream file(filename, std::ios::binary);
+ if (!file.is_open())
+ {
+ Log::GetLog()->error("Error opening file for reading: " + filename.string());
+ return data;
+ }
+
+ data.reserve(250000);
+
+ std::string key;
+ while (readString(file, key))
+ {
+ API::FunctionInfo info;
+ if (!readString(file, info.returnType)) break;
+ if (!readString(file, info.signature)) break;
+ if (!readString(file, info.params)) break;
+ if (!readString(file, info.paramNames)) break;
+ if (!file.read(reinterpret_cast(&info.offset), sizeof(info.offset))) break;
+ if (!file.read(reinterpret_cast(&info.isStatic), sizeof(info.isStatic))) break;
+ data[key] = info;
+ key.clear();
+ }
+
+ file.close();
+ return data;
+ }
+
void saveToFilePlain(const std::filesystem::path& filename, const std::unordered_map& map);
std::unordered_set readFileIntoSet(const std::filesystem::path& filename);
diff --git a/AsaApi/Core/Private/Offsets.cpp b/AsaApi/Core/Private/Offsets.cpp
index 90f856f..a6e8bcb 100644
--- a/AsaApi/Core/Private/Offsets.cpp
+++ b/AsaApi/Core/Private/Offsets.cpp
@@ -41,16 +41,18 @@ namespace API
}
void Offsets::Init(std::unordered_map&& offsets_dump,
- std::unordered_map&& bitfields_dump)
+ std::unordered_map&& bitfields_dump,
+ std::unordered_map&& fields_dump,
+ std::unordered_map&& functions_dump)
{
offsets_dump_.swap(offsets_dump);
bitfields_dump_.swap(bitfields_dump);
+ fields_dump_.swap(fields_dump);
+ functions_dump_.swap(functions_dump);
}
- DWORD64 Offsets::GetAddress(const void* base, const std::string& name)
- {
- if (!offsets_dump_.contains(name))
- {
+ DWORD64 Offsets::GetAddress(const void* base, const std::string& name) {
+ if (!offsets_dump_.contains(name)) {
Log::GetLog()->critical("Failed to get the offset of {}.", name);
Log::GetLog()->flush();
Sleep(10000);
@@ -60,10 +62,8 @@ namespace API
return reinterpret_cast(base) + static_cast(offsets_dump_[name]);
}
- LPVOID Offsets::GetAddress(const std::string& name)
- {
- if (!offsets_dump_.contains(name))
- {
+ LPVOID Offsets::GetAddress(const std::string& name) {
+ if (!offsets_dump_.contains(name)) {
Log::GetLog()->critical("Failed to get the offset of {}.", name);
Log::GetLog()->flush();
Sleep(10000);
@@ -73,10 +73,8 @@ namespace API
return reinterpret_cast(module_base_ + static_cast(offsets_dump_[name]));
}
- LPVOID Offsets::GetDataAddress(const std::string& name)
- {
- if (!offsets_dump_.contains(name))
- {
+ LPVOID Offsets::GetDataAddress(const std::string& name) {
+ if (!offsets_dump_.contains(name)) {
Log::GetLog()->critical("Failed to get the offset of {}.", name);
Log::GetLog()->flush();
Sleep(10000);
@@ -86,18 +84,10 @@ namespace API
return reinterpret_cast(data_base_ + static_cast(offsets_dump_[name]));
}
- BitField Offsets::GetBitField(const void* base, const std::string& name)
- {
- return GetBitFieldInternal(base, name);
- }
-
- BitField Offsets::GetBitField(LPVOID base, const std::string& name)
- {
- return GetBitFieldInternal(base, name);
- }
+ BitField Offsets::GetBitField(const void* base, const std::string& name) { return GetBitFieldInternal(base, name); }
+ BitField Offsets::GetBitField(LPVOID base, const std::string& name) { return GetBitFieldInternal(base, name); }
- BitField Offsets::GetBitFieldInternal(const void* base, const std::string& name)
- {
+ BitField Offsets::GetBitFieldInternal(const void* base, const std::string& name) {
if (!bitfields_dump_.contains(name))
{
Log::GetLog()->critical("Failed to get the bitfield address of {}.", name);
@@ -115,4 +105,56 @@ namespace API
return cf;
}
+
+ std::vector> Offsets::GetOffsetsForClass(const std::string& className) const {
+ std::vector> result;
+ const std::string prefix = className + ".";
+
+ for (const auto& [key, value] : offsets_dump_) {
+ if (key.rfind(prefix, 0) == 0) {
+ result.emplace_back(key, value);
+ }
+ }
+
+ return result;
+ }
+
+ std::vector> Offsets::GetBitFieldsForClass(const std::string& className) const {
+ std::vector> result;
+ const std::string prefix = className + ".";
+
+ for (const auto& [key, value] : bitfields_dump_) {
+ if (key.rfind(prefix, 0) == 0) {
+ result.emplace_back(key, value);
+ }
+ }
+
+ return result;
+ }
+
+ std::vector> Offsets::GetFieldsForClass(const std::string& className) const {
+ std::vector> result;
+ const std::string prefix = className + ".";
+
+ for (const auto& [key, value] : fields_dump_) {
+ if (key.rfind(prefix, 0) == 0) {
+ result.emplace_back(key, value);
+ }
+ }
+
+ return result;
+ }
+
+ std::vector> Offsets::GetFunctionsForClass(const std::string& className) const {
+ std::vector> result;
+ const std::string prefix = className + ".";
+
+ for (const auto& [key, value] : functions_dump_) {
+ if (key.rfind(prefix, 0) == 0) {
+ result.emplace_back(key, value);
+ }
+ }
+
+ return result;
+ }
} // namespace API
diff --git a/AsaApi/Core/Private/Offsets.h b/AsaApi/Core/Private/Offsets.h
index bd98e24..c9098e3 100644
--- a/AsaApi/Core/Private/Offsets.h
+++ b/AsaApi/Core/Private/Offsets.h
@@ -1,8 +1,10 @@
#pragma once
#include
+#include "PDBReader/PDBReader.h"
#include
+#include
namespace API
{
@@ -17,7 +19,9 @@ namespace API
Offsets& operator=(Offsets&&) = delete;
void Init(std::unordered_map&& offsets_dump,
- std::unordered_map&& bitfields_dump);
+ std::unordered_map&& bitfields_dump,
+ std::unordered_map&& fields_dump = {},
+ std::unordered_map&& functions_dump = {});
DWORD64 GetAddress(const void* base, const std::string& name);
LPVOID GetAddress(const std::string& name);
@@ -27,6 +31,12 @@ namespace API
BitField GetBitField(const void* base, const std::string& name);
BitField GetBitField(LPVOID base, const std::string& name);
+ // Get all entries for a specific class
+ std::vector> GetOffsetsForClass(const std::string& className) const;
+ std::vector> GetBitFieldsForClass(const std::string& className) const;
+ std::vector> GetFieldsForClass(const std::string& className) const;
+ std::vector> GetFunctionsForClass(const std::string& className) const;
+
private:
Offsets();
~Offsets() = default;
@@ -38,5 +48,7 @@ namespace API
std::unordered_map offsets_dump_;
std::unordered_map bitfields_dump_;
+ std::unordered_map fields_dump_;
+ std::unordered_map functions_dump_;
};
} // namespace API
diff --git a/AsaApi/Core/Private/PDBReader/PDBReader.cpp b/AsaApi/Core/Private/PDBReader/PDBReader.cpp
index e064593..ce5eba6 100644
--- a/AsaApi/Core/Private/PDBReader/PDBReader.cpp
+++ b/AsaApi/Core/Private/PDBReader/PDBReader.cpp
@@ -1,6 +1,10 @@
#include "PDBReader.h"
#include
+#include
+#include
+#include
+#include
#include
#include
@@ -8,430 +12,1041 @@
#include "../Private/Helpers.h"
#include "../Private/Offsets.h"
+#include
+#pragma comment(lib, "dbghelp.lib")
+
+// raw_pdb includes
+#include "PDB.h"
+#include "PDB_RawFile.h"
+#include "PDB_InfoStream.h"
+#include "PDB_DBIStream.h"
+#include "PDB_TPIStream.h"
+#include "PDB_ModuleInfoStream.h"
+#include "PDB_ModuleSymbolStream.h"
+#include "PDB_ImageSectionStream.h"
+#include "PDB_GlobalSymbolStream.h"
+#include "PDB_PublicSymbolStream.h"
+#include "PDB_CoalescedMSFStream.h"
+#include "PDB_TPITypes.h"
+#include "PDB_DBITypes.h"
+
namespace API
{
- template
- class ScopedDiaType
+ struct MemoryMappedFile
{
- public:
- ScopedDiaType() : _sym(nullptr)
+ void* baseAddress = nullptr;
+ size_t length = 0;
+ HANDLE fileHandle = INVALID_HANDLE_VALUE;
+ HANDLE mappingHandle = nullptr;
+
+ static MemoryMappedFile Open(const std::wstring& path)
{
+ MemoryMappedFile file;
+
+ file.fileHandle = CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr,
+ OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
+
+ if (file.fileHandle == INVALID_HANDLE_VALUE)
+ return file;
+
+ LARGE_INTEGER fileSize;
+ if (!GetFileSizeEx(file.fileHandle, &fileSize)) {
+ CloseHandle(file.fileHandle);
+ file.fileHandle = INVALID_HANDLE_VALUE;
+ return file;
+ }
+
+ file.length = static_cast(fileSize.QuadPart);
+ file.mappingHandle = CreateFileMappingW(file.fileHandle, nullptr, PAGE_READONLY, 0, 0, nullptr);
+ if (!file.mappingHandle) {
+ CloseHandle(file.fileHandle);
+ file.fileHandle = INVALID_HANDLE_VALUE;
+ return file;
+ }
+
+ file.baseAddress = MapViewOfFile(file.mappingHandle, FILE_MAP_READ, 0, 0, 0);
+ if (!file.baseAddress) {
+ CloseHandle(file.mappingHandle);
+ CloseHandle(file.fileHandle);
+ file.fileHandle = INVALID_HANDLE_VALUE;
+ file.mappingHandle = nullptr;
+ }
+
+ return file;
}
- ScopedDiaType(T* sym) : _sym(sym)
- {
+ static void Close(MemoryMappedFile& file) {
+ if (file.baseAddress) {
+ UnmapViewOfFile(file.baseAddress);
+ file.baseAddress = nullptr;
+ }
+
+ if (file.mappingHandle) {
+ CloseHandle(file.mappingHandle);
+ file.mappingHandle = nullptr;
+ }
+
+ if (file.fileHandle != INVALID_HANDLE_VALUE) {
+ CloseHandle(file.fileHandle);
+ file.fileHandle = INVALID_HANDLE_VALUE;
+ }
}
+ };
- ~ScopedDiaType()
+ class TypeTable {
+ public:
+ TypeTable(const PDB::TPIStream& tpiStream)
+ : m_firstTypeIndex(tpiStream.GetFirstTypeIndex())
+ , m_lastTypeIndex(tpiStream.GetLastTypeIndex())
+ , m_recordCount(tpiStream.GetTypeRecordCount())
{
- if (_sym != nullptr)
- _sym->Release();
+ const PDB::DirectMSFStream& directStream = tpiStream.GetDirectMSFStream();
+ m_stream = PDB::CoalescedMSFStream(directStream, directStream.GetSize(), 0);
+
+ m_records.resize(m_recordCount);
+
+ uint32_t typeIndex = 0;
+ tpiStream.ForEachTypeRecordHeaderAndOffset([this, &typeIndex](const PDB::CodeView::TPI::RecordHeader& header, size_t offset) {
+ const PDB::CodeView::TPI::Record* record = m_stream.GetDataAtOffset(offset);
+ m_records[typeIndex] = record;
+ ++typeIndex;
+ });
+ }
+
+ const PDB::CodeView::TPI::Record* GetTypeRecord(uint32_t typeIndex) const {
+ if (typeIndex < m_firstTypeIndex || typeIndex > m_lastTypeIndex)
+ return nullptr;
+
+ const size_t index = typeIndex - m_firstTypeIndex;
+ if (index >= m_records.size())
+ return nullptr;
+
+ return m_records[index];
}
- T** ref() { return &_sym; }
- T** operator&() { return ref(); }
- T* operator->() { return _sym; }
- operator T*() { return _sym; }
- void Attach(T* sym) { _sym = sym; }
+ uint32_t GetFirstTypeIndex() const { return m_firstTypeIndex; }
+ uint32_t GetLastTypeIndex() const { return m_lastTypeIndex; }
+ const std::vector& GetTypeRecords() const { return m_records; }
private:
- T* _sym;
+ uint32_t m_firstTypeIndex;
+ uint32_t m_lastTypeIndex;
+ size_t m_recordCount;
+ std::vector m_records;
+ PDB::CoalescedMSFStream m_stream;
};
- template
- using CComPtr = ScopedDiaType;
+ void PdbReader::AddOffset(const std::string& key, intptr_t value) {
+ std::lock_guard lock(offsets_mutex_);
+ (*offsets_dump_)[key] = value;
+ }
- void PdbReader::Read(const std::wstring& path, std::unordered_map* offsets_dump,
- std::unordered_map* bitfields_dump, const std::unordered_set filter_set)
- {
- offsets_dump_ = offsets_dump;
- bitfields_dump_ = bitfields_dump;
- filter_set_ = filter_set;
+ void PdbReader::AddBitField(const std::string& key, const BitField& value) {
+ std::lock_guard lock(bitfields_mutex_);
+ (*bitfields_dump_)[key] = value;
+ }
- offsets_dump_->reserve(550000);
- bitfields_dump_->reserve(11000);
+ void PdbReader::AddFieldInfo(const std::string& key, const std::string& typeName, intptr_t offset, bool isPointer) {
+ if (!fields_dump_) return;
+ std::lock_guard lock(fields_mutex_);
+ FieldInfo info;
+ info.type = typeName;
+ info.offset = offset;
+ info.isPointer = isPointer;
+ (*fields_dump_)[key] = info;
+ }
- std::ifstream f{path};
- if (!f.good())
- throw std::runtime_error("Failed to open pdb file");
-
- IDiaDataSource* data_source;
- IDiaSession* dia_session;
- IDiaSymbol* symbol;
+ void PdbReader::AddFunctionInfo(const std::string& key, const std::string& returnType, const std::string& signature, const std::string& params, const std::string& paramNames, intptr_t offset, bool isStatic) {
+ if (!functions_dump_) return;
+ std::lock_guard lock(functions_mutex_);
+ FunctionInfo info;
+ info.returnType = returnType;
+ info.signature = signature;
+ info.params = params;
+ info.paramNames = paramNames;
+ info.offset = offset;
+ info.isStatic = isStatic;
+ (*functions_dump_)[key] = info;
+ }
- try
- {
- LoadDataFromPdb(path, &data_source, &dia_session, &symbol);
+ bool PdbReader::MarkVisited(uint32_t id) {
+ std::lock_guard lock(visited_mutex_);
+ if (visited_.find(id) != visited_.end())
+ return false;
+ visited_.insert(id);
+ return true;
+ }
+
+ bool PdbReader::FilterSymbols(const std::string& name) const {
+ if (name.empty())
+ return true;
+
+ for (const auto& filter : filter_set_) {
+ if (name.rfind(filter, 0) == 0 && name.rfind("UE::GC", 0) != 0)
+ return true;
}
- catch (const std::runtime_error&)
- {
- Log::GetLog()->error("Failed to load data from pdb file ");
- throw;
+
+ if (name.find('`') != std::string::npos)
+ return true;
+
+ return false;
+ }
+
+ static std::string ReplaceAll(std::string str, const std::string& from, const std::string& to) {
+ size_t pos = 0;
+ while ((pos = str.find(from, pos)) != std::string::npos) {
+ str.replace(pos, from.length(), to);
+ pos += to.length();
}
+ return str;
+ }
- Log::GetLog()->info("Dumping structures..");
- DumpStructs(symbol);
+ std::string UndecorateName(const char* decoratedName, DWORD flags) {
+ if (!decoratedName || decoratedName[0] != '?')
+ return decoratedName ? decoratedName : "";
- Log::GetLog()->info("Dumping functions..");
- DumpFunctions(symbol);
+ char undecoratedName[4096];
+ if (UnDecorateSymbolName(decoratedName, undecoratedName, sizeof(undecoratedName), flags) == 0)
+ return decoratedName;
- Log::GetLog()->info("Dumping globals..");
- DumpGlobalVariables(symbol);
+ return std::string(undecoratedName);
+ }
- Cleanup(symbol, dia_session, data_source);
+ std::string ExtractFunctionParams(const char* name) {
+ if (!name)
+ return "";
- Log::GetLog()->info("Successfully read information from PDB\n");
+ std::string result;
+
+ if (name[0] == '?')
+ result = UndecorateName(name, 0x20000);
+ else
+ result = name;
+
+ size_t start = result.find('(');
+ size_t end = result.rfind(')');
+
+ if (start == std::string::npos || end == std::string::npos || end <= start)
+ return "";
+
+ std::string params = result.substr(start + 1, end - start - 1);
+
+ params = ReplaceAll(params, "struct ", "");
+ params = ReplaceAll(params, "class ", "");
+ params = ReplaceAll(params, "enum ", "");
+ params = ReplaceAll(params, "const ", "");
+ params = ReplaceAll(params, " ", "");
+ params = ReplaceAll(params, "__ptr64", "");
+
+ if (params == "void")
+ params.clear();
+
+ return params;
}
- void PdbReader::LoadDataFromPdb(const std::wstring& path, IDiaDataSource** dia_source, IDiaSession** session,
- IDiaSymbol** symbol)
- {
- const std::string current_dir = Tools::GetCurrentDir();
+ std::string ExtractReturnType(const char* name) {
+ if (!name)
+ return "void";
- const std::string lib_path = current_dir + "\\msdia140.dll";
- const HMODULE h_module = LoadLibraryA(lib_path.c_str());
- if (h_module == nullptr)
- {
- throw std::runtime_error("Failed to load msdia140.dll. Error code - " + std::to_string(GetLastError()));
- }
+ std::string result;
+
+ if (name[0] == '?')
+ result = UndecorateName(name, 0x0);
+ else
+ result = name;
+
+ size_t parenPos = result.find('(');
+ if (parenPos == std::string::npos)
+ return "void";
+
+ size_t funcStart = result.rfind(' ', parenPos);
+ if (funcStart == std::string::npos)
+ return "void";
+
+ std::string beforeFunc = result.substr(0, funcStart);
+ beforeFunc = ReplaceAll(beforeFunc, "__cdecl", "");
+ beforeFunc = ReplaceAll(beforeFunc, "__stdcall", "");
+ beforeFunc = ReplaceAll(beforeFunc, "__fastcall", "");
+ beforeFunc = ReplaceAll(beforeFunc, "__thiscall", "");
+ beforeFunc = ReplaceAll(beforeFunc, "__vectorcall", "");
+ beforeFunc = ReplaceAll(beforeFunc, "public:", "");
+ beforeFunc = ReplaceAll(beforeFunc, "private:", "");
+ beforeFunc = ReplaceAll(beforeFunc, "protected:", "");
+ beforeFunc = ReplaceAll(beforeFunc, "virtual ", "");
+ beforeFunc = ReplaceAll(beforeFunc, "static ", "");
+ beforeFunc = ReplaceAll(beforeFunc, "struct ", "");
+ beforeFunc = ReplaceAll(beforeFunc, "class ", "");
+ beforeFunc = ReplaceAll(beforeFunc, "enum ", "");
+ beforeFunc = ReplaceAll(beforeFunc, "__ptr64", "");
+
+ size_t start = beforeFunc.find_first_not_of(" \t");
+ size_t end = beforeFunc.find_last_not_of(" \t");
+ if (start == std::string::npos)
+ return "void";
+
+ std::string returnType = beforeFunc.substr(start, end - start + 1);
+
+ if (returnType.empty() || returnType.find_first_not_of(" \t") == std::string::npos)
+ return "void";
+
+ returnType = ReplaceAll(returnType, " ", "");
+
+ return returnType;
+ }
- const auto dll_get_class_object = reinterpret_cast(GetProcAddress(
- h_module, "DllGetClassObject"));
- if (dll_get_class_object == nullptr)
- {
- throw std::runtime_error("Can't find DllGetClassObject. Error code - " + std::to_string(GetLastError()));
+ std::string ExtractFunctionName(const char* name) {
+ if (!name)
+ return "";
+
+ std::string result;
+
+ if (name[0] == '?')
+ result = UndecorateName(name, 0x1000);
+ else {
+ result = name;
+ size_t parenPos = result.find('(');
+ if (parenPos != std::string::npos) {
+ result = result.substr(0, parenPos);
+ }
}
- IClassFactory* class_factory;
- HRESULT hr = dll_get_class_object(__uuidof(DiaSource), IID_IClassFactory, &class_factory);
- if (FAILED(hr))
- {
- throw std::runtime_error("DllGetClassObject has failed. Error code - " + std::to_string(GetLastError()));
- }
+ return result;
+ }
- hr = class_factory->CreateInstance(nullptr, __uuidof(IDiaDataSource), reinterpret_cast(dia_source));
- if (FAILED(hr))
- {
- class_factory->Release();
- throw std::runtime_error("CreateInstance has failed. Error code - " + std::to_string(GetLastError()));
- }
+ void PdbReader::CollectFunctionParamNames(const PDB::RawFile& rawFile, const PDB::DBIStream& dbiStream) {
+ const PDB::ModuleInfoStream moduleInfoStream = dbiStream.CreateModuleInfoStream(rawFile);
+ const PDB::ArrayView modules = moduleInfoStream.GetModules();
- hr = (*dia_source)->loadDataFromPdb(path.c_str());
- if (FAILED(hr))
- {
- class_factory->Release();
- throw std::runtime_error("loadDataFromPdb has failed. HRESULT - " + std::to_string(hr));
- }
+ for (const PDB::ModuleInfoStream::Module& module : modules) {
+ if (!module.HasSymbolStream())
+ continue;
- // Open a session for querying symbols
+ const PDB::ModuleSymbolStream moduleSymbolStream = module.CreateSymbolStream(rawFile);
+
+ uint32_t currentFuncOffset = 0;
+ std::string currentFuncKey;
+ std::vector currentParams;
+ bool inFunction = false;
+ bool hasThisPointer = false;
+
+ moduleSymbolStream.ForEachSymbol([&](const PDB::CodeView::DBI::Record* record) {
+ switch (record->header.kind) {
+ case PDB::CodeView::DBI::SymbolRecordKind::S_GPROC32:
+ case PDB::CodeView::DBI::SymbolRecordKind::S_LPROC32:
+ case PDB::CodeView::DBI::SymbolRecordKind::S_GPROC32_ID:
+ case PDB::CodeView::DBI::SymbolRecordKind::S_LPROC32_ID: {
+ if (inFunction && currentFuncOffset != 0) {
+ std::lock_guard lock(param_names_mutex_);
+ if (!currentParams.empty()) {
+ std::string paramNamesStr;
+ for (size_t i = 0; i < currentParams.size(); i++)
+ {
+ if (i > 0) paramNamesStr += ",";
+ paramNamesStr += currentParams[i];
+ }
+ param_names_map_[currentFuncOffset] = paramNamesStr;
+ }
+ }
+ if (inFunction && !currentFuncKey.empty()) {
+ std::lock_guard lock(func_has_this_mutex_);
+ func_has_this_map_[currentFuncKey] = hasThisPointer;
+ }
+
+ currentFuncOffset = record->data.S_GPROC32.offset;
+ currentParams.clear();
+ inFunction = true;
+ hasThisPointer = false;
+
+ const char* name = record->data.S_GPROC32.name;
+ if (name) {
+ std::string funcName = ExtractFunctionName(name);
+ if (funcName.find("::") != std::string::npos)
+ currentFuncKey = ReplaceAll(funcName, "::", ".");
+ else
+ currentFuncKey = "Global." + funcName;
+ }
+ else
+ currentFuncKey.clear();
+
+ break;
+ }
+
+ case PDB::CodeView::DBI::SymbolRecordKind::S_REGREL32: {
+ if (inFunction) {
+ const char* paramName = record->data.S_REGREL32.name;
+ if (paramName && paramName[0] != '\0') {
+ std::string nameStr = paramName;
+
+ if (nameStr == "this" || nameStr == "_this")
+ hasThisPointer = true;
+ else
+ currentParams.push_back(paramName);
+ }
+ }
+
+ break;
+ }
+
+ case PDB::CodeView::DBI::SymbolRecordKind::S_BPREL32: {
+ if (inFunction) {
+ const char* paramName = record->data.S_BPRELSYM32.name;
+ if (paramName && paramName[0] != '\0') {
+ std::string nameStr = paramName;
+
+ if (nameStr == "this" || nameStr == "_this")
+ {
+ hasThisPointer = true;
+ }
+ else
+ {
+ currentParams.push_back(paramName);
+ }
+ }
+ }
+ break;
+ }
+
+ case PDB::CodeView::DBI::SymbolRecordKind::S_END:
+ case PDB::CodeView::DBI::SymbolRecordKind::S_PROC_ID_END: {
+ if (inFunction && currentFuncOffset != 0) {
+ std::lock_guard lock(param_names_mutex_);
+ if (!currentParams.empty()) {
+ std::string paramNamesStr;
+ for (size_t i = 0; i < currentParams.size(); i++) {
+ if (i > 0) paramNamesStr += ",";
+ paramNamesStr += currentParams[i];
+ }
+ param_names_map_[currentFuncOffset] = paramNamesStr;
+ }
+ }
+ if (inFunction && !currentFuncKey.empty()) {
+ std::lock_guard lock(func_has_this_mutex_);
+ func_has_this_map_[currentFuncKey] = hasThisPointer;
+ }
+
+ inFunction = false;
+ currentFuncOffset = 0;
+ currentFuncKey.clear();
+ currentParams.clear();
+ hasThisPointer = false;
+ break;
+ }
+
+ default:
+ break;
+ }
+ });
+
+ if (inFunction && currentFuncOffset != 0) {
+ std::lock_guard lock(param_names_mutex_);
+ if (!currentParams.empty()) {
+ std::string paramNamesStr;
+ for (size_t i = 0; i < currentParams.size(); i++) {
+ if (i > 0) paramNamesStr += ",";
+ paramNamesStr += currentParams[i];
+ }
+ param_names_map_[currentFuncOffset] = paramNamesStr;
+ }
+ }
- hr = (*dia_source)->openSession(session);
- if (FAILED(hr))
- {
- class_factory->Release();
- throw std::runtime_error("openSession has failed. HRESULT - " + std::to_string(hr));
+ if (inFunction && !currentFuncKey.empty()) {
+ std::lock_guard lock(func_has_this_mutex_);
+ func_has_this_map_[currentFuncKey] = hasThisPointer;
+ }
}
+ }
- // Retrieve a reference to the global scope
-
- hr = (*session)->get_globalScope(symbol);
- if (hr != S_OK)
- {
- class_factory->Release();
- throw std::runtime_error("get_globalScope has failed. HRESULT - " + std::to_string(hr));
+ bool PdbReader::FunctionHasThisPointer(const std::string& funcName) const {
+ std::string baseName = funcName;
+ size_t parenPos = funcName.find('(');
+ if (parenPos != std::string::npos) {
+ baseName = funcName.substr(0, parenPos);
+ }
+
+ auto it = func_has_this_map_.find(baseName);
+ if (it != func_has_this_map_.end()) {
+ return it->second;
}
- class_factory->Release();
+ return true;
}
- void PdbReader::DumpStructs(IDiaSymbol* g_symbol)
- {
- IDiaSymbol* symbol = nullptr;
+ void PdbReader::ProcessModuleFunctions(const PDB::RawFile& rawFile, const PDB::DBIStream& dbiStream) {
+ const PDB::ModuleInfoStream moduleInfoStream = dbiStream.CreateModuleInfoStream(rawFile);
+ const PDB::ArrayView modules = moduleInfoStream.GetModules();
- CComPtr enum_symbols;
- if (FAILED(g_symbol->findChildren(SymTagUDT, nullptr, nsNone, &enum_symbols)))
- throw std::runtime_error("Failed to find symbols");
+ for (const PDB::ModuleInfoStream::Module& module : modules) {
+ if (!module.HasSymbolStream())
+ continue;
- ULONG celt = 0;
- while (SUCCEEDED(enum_symbols->Next(1, &symbol, &celt)) && celt == 1)
- {
- CComPtr sym(symbol);
+ const PDB::ModuleSymbolStream moduleSymbolStream = module.CreateSymbolStream(rawFile);
- const uint32_t sym_id = GetSymbolId(symbol);
- if (visited_.find(sym_id) != visited_.end())
- return;
+ moduleSymbolStream.ForEachSymbol([&](const PDB::CodeView::DBI::Record* record) {
+ if (record->header.kind != PDB::CodeView::DBI::SymbolRecordKind::S_GPROC32 && record->header.kind != PDB::CodeView::DBI::SymbolRecordKind::S_LPROC32 && record->header.kind != PDB::CodeView::DBI::SymbolRecordKind::S_GPROC32_ID && record->header.kind != PDB::CodeView::DBI::SymbolRecordKind::S_LPROC32_ID) return;
- visited_.insert(sym_id);
+ const char* name = record->data.S_GPROC32.name;
+ uint32_t offset = record->data.S_GPROC32.offset;
- std::string str_name = GetSymbolNameString(sym);
+ if (!name || offset == 0)
+ return;
- if (FilterSymbols(str_name))
- continue;
+ std::string funcName = ExtractFunctionName(name);
+
+ if (FilterSymbols(funcName))
+ return;
+
+ std::string params = ExtractFunctionParams(name);
+
+ std::string fullName;
+ if (funcName.find("::") != std::string::npos)
+ fullName = ReplaceAll(funcName, "::", ".") + "(" + params + ")";
+ else
+ fullName = "Global." + funcName + "(" + params + ")";
- DumpType(sym, str_name, 0);
+ {
+ std::lock_guard lock(offsets_mutex_);
+ if (offsets_dump_->find(fullName) != offsets_dump_->end())
+ return;
+ }
+
+ AddOffset(fullName, static_cast(offset));
+
+ std::string returnType = ExtractReturnType(name);
+ std::string signature = funcName.substr(funcName.rfind("::") != std::string::npos ? funcName.rfind("::") + 2 : funcName.rfind('.') != std::string::npos ? funcName.rfind('.') + 1 : 0);
+ signature += "(" + params + ")";
+
+ std::string paramNames = GetParamNamesForOffset(offset);
+ bool isMemberFunction = (funcName.find("::") != std::string::npos);
+ bool isStatic = isMemberFunction && !FunctionHasThisPointer(fullName);
+
+ AddFunctionInfo(fullName, returnType, signature, params, paramNames, static_cast(offset), isStatic);
+ });
}
}
- void PdbReader::DumpFunctions(IDiaSymbol* g_symbol)
+ std::string PdbReader::GetParamNamesForOffset(uint32_t offset) const {
+ auto it = param_names_map_.find(offset);
+ if (it != param_names_map_.end())
+ {
+ return it->second;
+ }
+ return "";
+ }
+
+ static size_t GetLeafSize(PDB::CodeView::TPI::TypeRecordKind kind) {
+ if (kind < PDB::CodeView::TPI::TypeRecordKind::LF_NUMERIC)
+ return sizeof(uint16_t);
+
+ switch (kind) {
+ case PDB::CodeView::TPI::TypeRecordKind::LF_CHAR:
+ return sizeof(PDB::CodeView::TPI::TypeRecordKind) + sizeof(uint8_t);
+ case PDB::CodeView::TPI::TypeRecordKind::LF_SHORT:
+ case PDB::CodeView::TPI::TypeRecordKind::LF_USHORT:
+ return sizeof(PDB::CodeView::TPI::TypeRecordKind) + sizeof(uint16_t);
+ case PDB::CodeView::TPI::TypeRecordKind::LF_LONG:
+ case PDB::CodeView::TPI::TypeRecordKind::LF_ULONG:
+ return sizeof(PDB::CodeView::TPI::TypeRecordKind) + sizeof(uint32_t);
+ case PDB::CodeView::TPI::TypeRecordKind::LF_QUADWORD:
+ case PDB::CodeView::TPI::TypeRecordKind::LF_UQUADWORD:
+ return sizeof(PDB::CodeView::TPI::TypeRecordKind) + sizeof(uint64_t);
+ default:
+ return 0;
+ }
+ }
+
+ static uint64_t GetLeafValue(const char* data, PDB::CodeView::TPI::TypeRecordKind kind)
{
- IDiaSymbol* symbol;
+ if (kind < PDB::CodeView::TPI::TypeRecordKind::LF_NUMERIC)
+ return *reinterpret_cast(data);
+
+ const char* valueData = data + sizeof(PDB::CodeView::TPI::TypeRecordKind);
+
+ switch (kind) {
+ case PDB::CodeView::TPI::TypeRecordKind::LF_CHAR:
+ return static_cast(*reinterpret_cast(valueData));
+ case PDB::CodeView::TPI::TypeRecordKind::LF_SHORT:
+ return static_cast(*reinterpret_cast(valueData));
+ case PDB::CodeView::TPI::TypeRecordKind::LF_USHORT:
+ return static_cast(*reinterpret_cast(valueData));
+ case PDB::CodeView::TPI::TypeRecordKind::LF_LONG:
+ return static_cast(*reinterpret_cast(valueData));
+ case PDB::CodeView::TPI::TypeRecordKind::LF_ULONG:
+ return static_cast(*reinterpret_cast(valueData));
+ case PDB::CodeView::TPI::TypeRecordKind::LF_QUADWORD:
+ return static_cast(*reinterpret_cast(valueData));
+ case PDB::CodeView::TPI::TypeRecordKind::LF_UQUADWORD:
+ return *reinterpret_cast(valueData);
+ default:
+ return 0;
+ }
+ }
- CComPtr enum_symbols;
- if (FAILED(g_symbol->findChildren(SymTagFunction, nullptr, nsNone, &enum_symbols)))
- throw std::runtime_error("Failed to find symbols");
+ static const char* GetLeafName(const char* data, PDB::CodeView::TPI::TypeRecordKind kind) { return &data[GetLeafSize(kind)]; }
+
+ std::string PdbReader::GetTypeName(const TypeTable& typeTable, uint32_t typeIndex) const { return GetTypeNameInternal(typeTable, typeIndex, 0); }
+
+ std::string PdbReader::GetTypeNameInternal(const TypeTable& typeTable, uint32_t typeIndex, int depth) const {
+ if (depth > 50)
+ return "";
+
+ if (typeIndex < typeTable.GetFirstTypeIndex()) {
+ PDB::CodeView::TPI::TypeIndexKind type = (PDB::CodeView::TPI::TypeIndexKind)(typeIndex);
+ switch (type) {
+ case PDB::CodeView::TPI::TypeIndexKind::T_VOID: return "void";
+ case PDB::CodeView::TPI::TypeIndexKind::T_CHAR: return "char";
+ case PDB::CodeView::TPI::TypeIndexKind::T_UCHAR: return "unsigned char";
+ case PDB::CodeView::TPI::TypeIndexKind::T_SHORT: return "short";
+ case PDB::CodeView::TPI::TypeIndexKind::T_USHORT: return "unsigned short";
+ case PDB::CodeView::TPI::TypeIndexKind::T_LONG: return "long";
+ case PDB::CodeView::TPI::TypeIndexKind::T_ULONG: return "unsigned long";
+ case PDB::CodeView::TPI::TypeIndexKind::T_INT4: return "int";
+ case PDB::CodeView::TPI::TypeIndexKind::T_UINT4: return "unsigned int";
+ case PDB::CodeView::TPI::TypeIndexKind::T_QUAD: return "__int64";
+ case PDB::CodeView::TPI::TypeIndexKind::T_UQUAD: return "unsigned __int64";
+ case PDB::CodeView::TPI::TypeIndexKind::T_REAL32: return "float";
+ case PDB::CodeView::TPI::TypeIndexKind::T_REAL64: return "double";
+ case PDB::CodeView::TPI::TypeIndexKind::T_BOOL08: return "bool";
+ case PDB::CodeView::TPI::TypeIndexKind::T_WCHAR: return "wchar_t";
+ case PDB::CodeView::TPI::TypeIndexKind::T_32PVOID:
+ case PDB::CodeView::TPI::TypeIndexKind::T_64PVOID: return "void*";
+ default: return "";
+ }
+ }
- ULONG celt = 0;
- std::stringstream ss;
- while (SUCCEEDED(enum_symbols->Next(1, &symbol, &celt)) && celt == 1)
- {
- CComPtr sym(symbol);
+ const PDB::CodeView::TPI::Record* record = typeTable.GetTypeRecord(typeIndex);
+ if (!record)
+ return "";
+
+ switch (record->header.kind) {
+ case PDB::CodeView::TPI::TypeRecordKind::LF_CLASS:
+ case PDB::CodeView::TPI::TypeRecordKind::LF_STRUCTURE:
+ return GetLeafName(record->data.LF_CLASS.data, record->data.LF_CLASS.lfEasy.kind);
+ case PDB::CodeView::TPI::TypeRecordKind::LF_UNION:
+ return GetLeafName(record->data.LF_UNION.data, static_cast(0));
+ case PDB::CodeView::TPI::TypeRecordKind::LF_ENUM:
+ return record->data.LF_ENUM.name;
+ case PDB::CodeView::TPI::TypeRecordKind::LF_POINTER:
+ return GetTypeNameInternal(typeTable, record->data.LF_POINTER.utype, depth + 1) + "*";
+ case PDB::CodeView::TPI::TypeRecordKind::LF_MODIFIER:
+ return GetTypeNameInternal(typeTable, record->data.LF_MODIFIER.type, depth + 1);
+ case PDB::CodeView::TPI::TypeRecordKind::LF_ARRAY:
+ return GetTypeNameInternal(typeTable, record->data.LF_ARRAY.elemtype, depth + 1) + "[]";
+ default:
+ return "";
+ }
+ }
- DWORD sym_tag_type;
- if (sym->get_symTag(&sym_tag_type) != S_OK)
- continue;
+ void PdbReader::ProcessFieldList(const PDB::CodeView::TPI::Record* record, const std::string& structName, const TypeTable& typeTable) {
+ if (!record || record->header.kind != PDB::CodeView::TPI::TypeRecordKind::LF_FIELDLIST)
+ return;
- std::string str_name = GetSymbolNameString(sym);
+ const uint32_t maxSize = record->header.size - sizeof(uint16_t);
+ if (maxSize == 0 || maxSize > 1000000)
+ return;
- if (FilterSymbols(str_name))
- continue;
+ for (size_t i = 0; i < maxSize;) {
+ const uint8_t* rawData = reinterpret_cast(&record->data.LF_FIELD.list) + i;
+ if (*rawData >= 0xF0) {
+ size_t padBytes = (*rawData & 0x0F); i += (padBytes > 0) ? padBytes : 1;
+ continue;
+ }
- const uint32_t sym_id = GetSymbolId(sym);
+ if (i + sizeof(PDB::CodeView::TPI::TypeRecordKind) > maxSize)
+ break;
- if (visited_.find(sym_id) != visited_.end())
- continue;
+ const PDB::CodeView::TPI::FieldList* fieldRecord = reinterpret_cast(reinterpret_cast(&record->data.LF_FIELD.list) + i);
- visited_.insert(sym_id);
+ if (fieldRecord->kind == PDB::CodeView::TPI::TypeRecordKind::LF_MEMBER) {
+ uint64_t offset = GetLeafValue(fieldRecord->data.LF_MEMBER.offset, fieldRecord->data.LF_MEMBER.lfEasy.kind);
- if (str_name.empty())
- continue;
+ const char* memberName = GetLeafName(fieldRecord->data.LF_MEMBER.offset, fieldRecord->data.LF_MEMBER.lfEasy.kind);
+
+ if (!memberName || memberName < reinterpret_cast(record) || memberName >= reinterpret_cast(record) + record->header.size + sizeof(uint16_t)) {
+ i += 8;
+ i = (i + 3) & ~3;
+ continue;
+ }
- DWORD offset;
- if (sym->get_addressOffset(&offset) != S_OK)
- continue;
+ size_t nameLen = strnlen(memberName, maxSize - i);
+ if (nameLen > 0 && nameLen < 1000) {
+ const std::string fullName = structName + "." + memberName;
+
+ const PDB::CodeView::TPI::Record* memberType = typeTable.GetTypeRecord(fieldRecord->data.LF_MEMBER.index);
+ if (memberType && memberType->header.kind == PDB::CodeView::TPI::TypeRecordKind::LF_BITFIELD) {
+ BitField bitField;
+ bitField.offset = static_cast(offset);
+ bitField.bit_position = memberType->data.LF_BITFIELD.position;
+ bitField.num_bits = memberType->data.LF_BITFIELD.length;
+
+ uint32_t underlyingType = memberType->data.LF_BITFIELD.type;
+ if (underlyingType < typeTable.GetFirstTypeIndex()) {
+ uint32_t sizeIndicator = (underlyingType >> 4) & 0x7;
+ switch (sizeIndicator) {
+ case 0: bitField.length = 1; break;
+ case 1: bitField.length = 2; break;
+ case 2: bitField.length = 4; break;
+ case 3: bitField.length = 8; break;
+ default: bitField.length = 4; break;
+ }
+ }
+ else {
+ bitField.length = 4;
+ }
+
+ AddBitField(fullName, bitField);
+ }
+ else {
+ std::string typeName = GetTypeName(typeTable, fieldRecord->data.LF_MEMBER.index);
+ bool isPointer = typeName.back() == '*';
+
+ AddOffset(fullName, static_cast(offset));
+ AddFieldInfo(fullName, typeName, static_cast(offset), isPointer);
+ }
+ }
- ss.clear();
- ss.str(std::string());
+ i += static_cast(memberName - reinterpret_cast(fieldRecord));
+ i += nameLen + 1;
+ i = (i + 3) & ~3;
+ }
+ else if (fieldRecord->kind == PDB::CodeView::TPI::TypeRecordKind::LF_STMEMBER) {
+ const char* memberName = fieldRecord->data.LF_STMEMBER.name;
+ if (!memberName) {
+ i += 8;
+ i = (i + 3) & ~3;
+ continue;
+ }
- // Check if it's a member function
- if (str_name.find(':') != std::string::npos)
- {
- ss << ReplaceString(str_name, "::", ".") << "(" << GetFunctionSymbolParams(sym) << ")";
- (*offsets_dump_)[ss.str()] = offset;
- //const std::string new_str = ReplaceString(str_name, "::", ".") + "(" + GetFunctionSymbolParams(sym) + ")";
- //(*offsets_dump_)[new_str] = offset;
+ size_t nameLen = strnlen(memberName, maxSize - i);
+ i += static_cast(memberName - reinterpret_cast(fieldRecord));
+ i += nameLen + 1;
+ i = (i + 3) & ~3;
}
- else
- {
- ss << "Global." << str_name << "(" << GetFunctionSymbolParams(sym) << ")";
- (*offsets_dump_)[ss.str()] = offset;
- //(*offsets_dump_)["Global." + str_name + "(" + GetFunctionSymbolParams(sym) + ")"] = offset;
+ else if (fieldRecord->kind == PDB::CodeView::TPI::TypeRecordKind::LF_BCLASS) {
+ size_t leafSize = GetLeafSize(fieldRecord->data.LF_BCLASS.lfEasy.kind);
+ i += sizeof(PDB::CodeView::TPI::TypeRecordKind) + sizeof(PDB::CodeView::TPI::MemberAttributes) + sizeof(uint32_t) + leafSize;
+ i = (i + 3) & ~3;
}
- }
- }
-
- void PdbReader::DumpGlobalVariables(IDiaSymbol* g_symbol)
- {
- IDiaSymbol* symbol;
-
- CComPtr enum_symbols;
- if (FAILED(g_symbol->findChildren(SymTagData, nullptr, nsNone, &enum_symbols)))
- throw std::runtime_error("Failed to find symbols");
+ else if (fieldRecord->kind == PDB::CodeView::TPI::TypeRecordKind::LF_INDEX) {
+ const PDB::CodeView::TPI::Record* nextRecord = typeTable.GetTypeRecord(fieldRecord->data.LF_INDEX.type);
+ if (nextRecord)
+ ProcessFieldList(nextRecord, structName, typeTable);
+
+ i += sizeof(PDB::CodeView::TPI::FieldList::Data::LF_INDEX);
+ i = (i + 3) & ~3;
+ }
+ else if (fieldRecord->kind == PDB::CodeView::TPI::TypeRecordKind::LF_VFUNCTAB) {
+ i += sizeof(PDB::CodeView::TPI::FieldList::Data::LF_VFUNCTAB);
+ i = (i + 3) & ~3;
+ }
+ else if (fieldRecord->kind == PDB::CodeView::TPI::TypeRecordKind::LF_ONEMETHOD) {
+ auto methodProp = static_cast(fieldRecord->data.LF_ONEMETHOD.attributes.mprop);
+ const char* methodName = nullptr;
+
+ if (methodProp == PDB::CodeView::TPI::MethodProperty::Intro || methodProp == PDB::CodeView::TPI::MethodProperty::PureIntro)
+ methodName = &reinterpret_cast(fieldRecord->data.LF_ONEMETHOD.vbaseoff)[sizeof(uint32_t)];
+ else
+ methodName = &reinterpret_cast(fieldRecord->data.LF_ONEMETHOD.vbaseoff)[0];
+
+ if (!methodName || methodName < reinterpret_cast(record)) {
+ i += 8;
+ i = (i + 3) & ~3;
+ continue;
+ }
- ULONG celt = 0;
- while (SUCCEEDED(enum_symbols->Next(1, &symbol, &celt)) && celt == 1)
- {
- CComPtr sym(symbol);
+ size_t nameLen = strnlen(methodName, maxSize - i);
+ i += static_cast(methodName - reinterpret_cast(fieldRecord));
+ i += nameLen + 1;
+ i = (i + 3) & ~3;
+ }
+ else if (fieldRecord->kind == PDB::CodeView::TPI::TypeRecordKind::LF_METHOD) {
+ const char* methodName = fieldRecord->data.LF_METHOD.name;
+ if (!methodName) {
+ i += 8;
+ i = (i + 3) & ~3;
+ continue;
+ }
- const uint32_t sym_id = GetSymbolId(symbol);
- if (visited_.find(sym_id) != visited_.end())
- return;
+ size_t nameLen = strnlen(methodName, maxSize - i);
+ i += static_cast(methodName - reinterpret_cast(fieldRecord));
+ i += nameLen + 1;
+ i = (i + 3) & ~3;
+ }
+ else if (fieldRecord->kind == PDB::CodeView::TPI::TypeRecordKind::LF_NESTTYPE) {
+ const char* nestName = fieldRecord->data.LF_NESTTYPE.name;
+ if (!nestName) {
+ i += 8;
+ i = (i + 3) & ~3;
+ continue;
+ }
- visited_.insert(sym_id);
+ size_t nameLen = strnlen(nestName, maxSize - i);
+ i += static_cast(nestName - reinterpret_cast(fieldRecord));
+ i += nameLen + 1;
+ i = (i + 3) & ~3;
+ }
+ else if (fieldRecord->kind == PDB::CodeView::TPI::TypeRecordKind::LF_ENUMERATE) {
+ const char* enumName = GetLeafName(fieldRecord->data.LF_ENUMERATE.value, fieldRecord->data.LF_ENUMERATE.lfEasy.kind);
+ if (!enumName || enumName < reinterpret_cast(record)) {
+ i += 8;
+ i = (i + 3) & ~3;
+ continue;
+ }
- std::string str_name = GetSymbolNameString(sym);
- if (FilterSymbols(str_name))
- continue;
+ size_t nameLen = strnlen(enumName, maxSize - i);
+ i += static_cast(enumName - reinterpret_cast(fieldRecord));
+ i += nameLen + 1;
+ i = (i + 3) & ~3;
+ }
+ else if (fieldRecord->kind == PDB::CodeView::TPI::TypeRecordKind::LF_VBCLASS || fieldRecord->kind == PDB::CodeView::TPI::TypeRecordKind::LF_IVBCLASS) {
+ const uint8_t* basePtr = reinterpret_cast(fieldRecord);
+ const uint8_t* leafPtr = reinterpret_cast(&fieldRecord->data.LF_VBCLASS.vbpOffset);
- DWORD sym_tag;
- if (sym->get_symTag(&sym_tag) != S_OK)
- continue;
+ auto leaf1Kind = *reinterpret_cast(leafPtr);
+ size_t leaf1Size = GetLeafSize(leaf1Kind);
- DWORD offset;
- if (sym->get_addressOffset(&offset) != S_OK)
- continue;
+ const uint8_t* leaf2Ptr = leafPtr + leaf1Size;
+ auto leaf2Kind = *reinterpret_cast(leaf2Ptr);
+ size_t leaf2Size = GetLeafSize(leaf2Kind);
- (*offsets_dump_)["Global." + str_name] = offset;
+ i += static_cast(leaf2Ptr + leaf2Size - basePtr);
+ i = (i + 3) & ~3;
+ }
+ else if (fieldRecord->kind == PDB::CodeView::TPI::TypeRecordKind::LF_FRIENDCLS) {
+ i += sizeof(uint16_t) + sizeof(uint32_t);
+ i = (i + 3) & ~3;
+ }
+ else if (fieldRecord->kind == PDB::CodeView::TPI::TypeRecordKind::LF_VFUNCOFF) {
+ i += sizeof(uint16_t) + sizeof(uint32_t) + sizeof(uint32_t);
+ i = (i + 3) & ~3;
+ }
+ else {
+ // Unknown field type - skip the minimum amount and hope for the best
+ size_t oldI = i;
+ i += 4;
+ i = (i + 3) & ~3;
+
+ if (i <= oldI || i >= maxSize)
+ break;
+ }
}
}
- void PdbReader::DumpType(IDiaSymbol* symbol, const std::string& structure, int indent) const
- {
- CComPtr enum_children;
- IDiaSymbol* symbol_child;
- DWORD sym_tag;
- ULONG celt = 0;
+ void PdbReader::ProcessStructOrClass(const PDB::CodeView::TPI::Record* record, const TypeTable& typeTable) {
+ if (!record)
+ return;
- if (indent > 5)
+ if (record->header.kind != PDB::CodeView::TPI::TypeRecordKind::LF_CLASS && record->header.kind != PDB::CodeView::TPI::TypeRecordKind::LF_STRUCTURE)
return;
- if (symbol->get_symTag(&sym_tag) != S_OK)
+ if (record->data.LF_CLASS.property.fwdref)
return;
- switch (sym_tag)
- {
- case SymTagData:
- DumpData(symbol, structure);
- break;
- case SymTagEnum:
- case SymTagUDT:
- if (SUCCEEDED(symbol->findChildren(SymTagNull, nullptr, nsNone, &enum_children)))
- {
- while (SUCCEEDED(enum_children->Next(1, &symbol_child, &celt)) && celt == 1)
- {
- CComPtr sym_child(symbol_child);
+ const char* structName = GetLeafName(record->data.LF_CLASS.data, record->data.LF_CLASS.lfEasy.kind);
+ if (!structName || FilterSymbols(structName))
+ return;
- DumpType(sym_child, structure, indent + 2);
- }
+ const PDB::CodeView::TPI::Record* fieldRecord = typeTable.GetTypeRecord(record->data.LF_CLASS.field);
+ if (fieldRecord)
+ ProcessFieldList(fieldRecord, structName, typeTable);
+ }
+
+ void PdbReader::ProcessTypes(const PDB::TPIStream& tpiStream, const TypeTable& typeTable) {
+ for (const auto* record : typeTable.GetTypeRecords()) {
+ if (record->header.kind == PDB::CodeView::TPI::TypeRecordKind::LF_CLASS || record->header.kind == PDB::CodeView::TPI::TypeRecordKind::LF_STRUCTURE) {
+ ProcessStructOrClass(record, typeTable);
}
- break;
- default:
- break;
}
}
- void PdbReader::DumpData(IDiaSymbol* symbol, const std::string& structure) const
+ std::string PdbReader::GetFunctionParams(uint32_t typeIndex, const TypeTable& typeTable)
{
- DWORD loc_type;
- if (symbol->get_locationType(&loc_type) != S_OK)
- return;
-
- if (loc_type != LocIsThisRel && loc_type != LocIsBitField)
- return;
+ const PDB::CodeView::TPI::Record* record = typeTable.GetTypeRecord(typeIndex);
+ if (!record)
+ return "";
- CComPtr type;
- if (symbol->get_type(&type) != S_OK)
- return;
+ return "";
+ }
- if (type == nullptr)
- return;
+ void PdbReader::ProcessFunctions(const PDB::RawFile& rawFile, const PDB::DBIStream& dbiStream, const PDB::ImageSectionStream& imageSectionStream) {
+ // Use public symbol stream which contains mangled names with full signatures
+ const PDB::PublicSymbolStream publicSymbolStream = dbiStream.CreatePublicSymbolStream(rawFile);
+ const PDB::CoalescedMSFStream symbolRecordStream = dbiStream.CreateSymbolRecordStream(rawFile);
+ const PDB::ArrayView hashRecords = publicSymbolStream.GetRecords();
- LONG offset;
- if (symbol->get_offset(&offset) != S_OK)
- return;
+ for (const PDB::HashRecord& hashRecord : hashRecords) {
+ const PDB::CodeView::DBI::Record* record = publicSymbolStream.GetRecord(symbolRecordStream, hashRecord);
+
+ if (record->header.kind != PDB::CodeView::DBI::SymbolRecordKind::S_PUB32)
+ continue;
- std::string str_name = GetSymbolNameString(symbol);
- if (str_name.empty())
- return;
+ if (record->data.S_PUB32.flags != PDB::CodeView::DBI::PublicSymbolFlags::Function)
+ continue;
- if (loc_type == LocIsBitField)
- {
- DWORD bit_position;
- if (symbol->get_bitPosition(&bit_position) != S_OK)
- return;
+ const char* name = record->data.S_PUB32.name;
+ // Use raw offset within section, NOT RVA
+ // This matches DIA SDK's get_addressOffset behavior
+ uint32_t offset = record->data.S_PUB32.offset;
- ULONGLONG num_bits;
- if (symbol->get_length(&num_bits) != S_OK)
- return;
+ if (!name || offset == 0)
+ continue;
- ULONGLONG length;
- if (type->get_length(&length) != S_OK)
- return;
+ std::string funcName = ExtractFunctionName(name);
+
+ if (FilterSymbols(funcName))
+ continue;
- const BitField bit_field{static_cast(offset), bit_position, num_bits, length};
+ std::string params = ExtractFunctionParams(name);
- (*bitfields_dump_)[structure + "." + str_name] = bit_field;
- }
- else if (loc_type == LocIsThisRel)
- {
- (*offsets_dump_)[structure + "." + str_name] = offset;
+ std::string fullName;
+ if (funcName.find("::") != std::string::npos)
+ fullName = ReplaceAll(funcName, "::", ".") + "(" + params + ")";
+ else
+ fullName = "Global." + funcName + "(" + params + ")";
+
+ AddOffset(fullName, static_cast(offset));
+
+ std::string returnType = ExtractReturnType(name);
+ std::string signature = funcName.substr(funcName.rfind("::") != std::string::npos ? funcName.rfind("::") + 2 : funcName.rfind('.') != std::string::npos ? funcName.rfind('.') + 1 : 0);
+ signature += "(" + params + ")";
+
+ bool isMemberFunction = (funcName.find("::") != std::string::npos);
+ bool isStatic = isMemberFunction && !FunctionHasThisPointer(fullName);
+
+ std::string paramNames = GetParamNamesForOffset(offset);
+
+ AddFunctionInfo(fullName, returnType, signature, params, paramNames, static_cast(offset), isStatic);
}
}
- bool PdbReader::FilterSymbols(const std::string input)
- {
- if (input.empty())
- return true;
+ void PdbReader::ProcessGlobalVariables(const PDB::RawFile& rawFile, const PDB::DBIStream& dbiStream, const PDB::ImageSectionStream& imageSectionStream, const TypeTable& typeTable) {
+ const PDB::GlobalSymbolStream globalSymbolStream = dbiStream.CreateGlobalSymbolStream(rawFile);
+ const PDB::CoalescedMSFStream symbolRecordStream = dbiStream.CreateSymbolRecordStream(rawFile);
- for (const auto& filter : filter_set_)
- {
- if (input.starts_with(filter)
- && !input.starts_with("UE::GC"))
- return true;
- }
+ const PDB::ArrayView hashRecords = globalSymbolStream.GetRecords();
- if (input.find('`') != std::string::npos)
- return true;
+ for (const PDB::HashRecord& hashRecord : hashRecords) {
+ const PDB::CodeView::DBI::Record* record = globalSymbolStream.GetRecord(symbolRecordStream, hashRecord);
+
+ const char* name = nullptr;
+ uint32_t offset = 0;
+ uint32_t typeIndex = 0;
- return false;
+ if (record->header.kind == PDB::CodeView::DBI::SymbolRecordKind::S_GDATA32) {
+ name = record->data.S_GDATA32.name;
+ // Use raw section offset, not RVA like raw_pdb
+ offset = record->data.S_GDATA32.offset;
+ typeIndex = record->data.S_GDATA32.typeIndex;
+ }
+ else if (record->header.kind == PDB::CodeView::DBI::SymbolRecordKind::S_LDATA32) {
+ name = record->data.S_LDATA32.name;
+ // Use raw section offset, not RVA like raw_pdb
+ offset = record->data.S_LDATA32.offset;
+ typeIndex = record->data.S_LDATA32.typeIndex;
+ }
+
+ if (name && offset != 0 && !FilterSymbols(name)) {
+ std::string globalKey = "Global." + std::string(name);
+ AddOffset(globalKey, static_cast(offset));
+
+ // Get type name and add field info
+ if (typeIndex != 0) {
+ std::string typeName = GetTypeName(typeTable, typeIndex);
+ if (!typeName.empty()) {
+ bool isPointer = (typeName.back() == '*');
+ AddFieldInfo(globalKey, typeName, static_cast(offset), isPointer);
+ }
+ }
+ }
+ }
}
- std::string PdbReader::GetSymbolNameString(IDiaSymbol* symbol)
- {
- BSTR str = nullptr;
+ void PdbReader::Read(const std::wstring& path, std::unordered_map* offsets_dump, std::unordered_map* bitfields_dump, const std::unordered_set filter_set, std::unordered_map* fields_dump, std::unordered_map* functions_dump) {
+ offsets_dump_ = offsets_dump;
+ bitfields_dump_ = bitfields_dump;
+ fields_dump_ = fields_dump;
+ functions_dump_ = functions_dump;
+ filter_set_ = filter_set;
- std::string name;
+ offsets_dump_->reserve(550000);
+ bitfields_dump_->reserve(11000);
+ if (fields_dump_) fields_dump_->reserve(300000);
+ if (functions_dump_) functions_dump_->reserve(250000);
- HRESULT hr = symbol->get_name(&str);
- if (hr != S_OK)
- return name;
+ std::ifstream f{path};
+ if (!f.good())
+ throw std::runtime_error("Failed to open pdb file");
+ f.close();
- if (str != nullptr)
- {
- name = Tools::Utf8Encode(str);
+ MemoryMappedFile pdbFile = MemoryMappedFile::Open(path);
+ if (!pdbFile.baseAddress) {
+ Log::GetLog()->error("Cannot memory-map PDB file");
+ throw std::runtime_error("Cannot memory-map PDB file");
}
- SysFreeString(str);
-
- return name;
- }
+ PDB::ErrorCode errorCode = PDB::ValidateFile(pdbFile.baseAddress, pdbFile.length);
+ if (errorCode != PDB::ErrorCode::Success) {
+ MemoryMappedFile::Close(pdbFile);
+ Log::GetLog()->error("Invalid PDB file");
+ throw std::runtime_error("Invalid PDB file");
+ }
- uint32_t PdbReader::GetSymbolId(IDiaSymbol* symbol)
- {
- DWORD id;
- symbol->get_symIndexId(&id);
+ const PDB::RawFile rawPdbFile = PDB::CreateRawFile(pdbFile.baseAddress);
- return id;
- }
+ errorCode = PDB::HasValidDBIStream(rawPdbFile);
+ if (errorCode != PDB::ErrorCode::Success) {
+ MemoryMappedFile::Close(pdbFile);
+ Log::GetLog()->error("Invalid DBI stream");
+ throw std::runtime_error("Invalid DBI stream");
+ }
- void PdbReader::Cleanup(IDiaSymbol* symbol, IDiaSession* session, IDiaDataSource* source)
- {
- if (symbol != nullptr)
- symbol->Release();
- if (session != nullptr)
- session->Release();
- if (source != nullptr)
- source->Release();
-
- CoUninitialize();
- }
+ const PDB::InfoStream infoStream(rawPdbFile);
+ if (infoStream.UsesDebugFastLink()) {
+ MemoryMappedFile::Close(pdbFile);
+ Log::GetLog()->error("PDB was linked using unsupported option /DEBUG:FASTLINK");
+ throw std::runtime_error("PDB uses /DEBUG:FASTLINK");
+ }
- std::string PdbReader::GetFunctionSymbolParams(IDiaSymbol* pFunction)
- {
- std::string parameterTypes;
- BSTR undecorated = nullptr;
- if (SUCCEEDED(pFunction->get_undecoratedNameEx(0x20000, &undecorated)) // 0x20000 - Don't include __ptr64 in output (just on the func sig, but the params can still output it)
- && undecorated != nullptr)
- {
- parameterTypes = Tools::Utf8Encode(undecorated);
- const size_t start = parameterTypes.find('(');
- const size_t end = parameterTypes.find(')');
- if (start != std::string::npos && end != std::string::npos)
- {
- parameterTypes = parameterTypes.substr(start + 1, end - start - 1);
- parameterTypes = ReplaceString(parameterTypes, "struct ", "");
- parameterTypes = ReplaceString(parameterTypes, "class ", "");
- parameterTypes = ReplaceString(parameterTypes, "enum ", "");
- //parameterTypes = ReplaceString(parameterTypes, "& __ptr64", "*"); // pointers
- parameterTypes = ReplaceString(parameterTypes, "const ", "");
- parameterTypes = ReplaceString(parameterTypes, " ", "");
- parameterTypes = ReplaceString(parameterTypes, "__ptr64", "");
- if (parameterTypes == "void")
- parameterTypes.clear();
- }
+ const PDB::DBIStream dbiStream = PDB::CreateDBIStream(rawPdbFile);
+
+ errorCode = PDB::HasValidTPIStream(rawPdbFile);
+ if (errorCode != PDB::ErrorCode::Success) {
+ MemoryMappedFile::Close(pdbFile);
+ Log::GetLog()->error("Invalid TPI stream");
+ throw std::runtime_error("Invalid TPI stream");
}
- return parameterTypes;
- }
+ const PDB::TPIStream tpiStream = PDB::CreateTPIStream(rawPdbFile);
+ const PDB::ImageSectionStream imageSectionStream = dbiStream.CreateImageSectionStream(rawPdbFile);
+
+ Log::GetLog()->info("Creating type table...");
+ const TypeTable typeTable(tpiStream);
+
+ Log::GetLog()->info("Collecting function parameter names...");
+ CollectFunctionParamNames(rawPdbFile, dbiStream); // Do not put in task, must execute first
+ Log::GetLog()->info("Processing structures...");
+ auto typesTask = std::async(std::launch::async, [this, &tpiStream, &typeTable]() {
+ ProcessTypes(tpiStream, typeTable);
+ });
+ Log::GetLog()->info("Processing functions...");
+ auto functionsTask = std::async(std::launch::async, [this, &rawPdbFile, &dbiStream, &imageSectionStream]() {
+ ProcessFunctions(rawPdbFile, dbiStream, imageSectionStream);
+ });
+
+ Log::GetLog()->info("Processing global variables...");
+ auto globalsTask = std::async(std::launch::async, [this, &rawPdbFile, &dbiStream, &imageSectionStream, &typeTable]() {
+ ProcessGlobalVariables(rawPdbFile, dbiStream, imageSectionStream, typeTable);
+ });
+
+ // Wait for all tasks to complete
+ typesTask.wait();
+ functionsTask.wait();
+ globalsTask.wait();
+
+ // Cleanup
+ MemoryMappedFile::Close(pdbFile);
+
+ Log::GetLog()->info("Successfully read information from PDB\n");
+ }
} // namespace API
diff --git a/AsaApi/Core/Private/PDBReader/PDBReader.h b/AsaApi/Core/Private/PDBReader/PDBReader.h
index 8e4f383..51e2c43 100644
--- a/AsaApi/Core/Private/PDBReader/PDBReader.h
+++ b/AsaApi/Core/Private/PDBReader/PDBReader.h
@@ -1,14 +1,61 @@
#pragma once
-#include
#include
+#include
+#include
+#include
+#include
+#include
#include "json.hpp"
-
#include
+// Forward declarations for raw_pdb
+namespace PDB
+{
+ class RawFile;
+ class TPIStream;
+ class DBIStream;
+ class ModuleInfoStream;
+ class ImageSectionStream;
+
+ namespace CodeView
+ {
+ namespace TPI
+ {
+ struct Record;
+ enum class TypeRecordKind : uint16_t;
+ }
+ namespace DBI
+ {
+ struct Record;
+ }
+ }
+}
+
namespace API
{
+ // Structure to hold field type information
+ struct FieldInfo
+ {
+ std::string type; // The type name (e.g., "FString", "TArray")
+ intptr_t offset; // Offset within the class
+ bool isPointer; // Whether the type is a pointer
+ };
+
+ // Structure to hold function signature information
+ struct FunctionInfo
+ {
+ std::string returnType; // Return type
+ std::string signature; // Full signature with params (e.g., "FuncName(int,float)")
+ std::string params; // Just the parameter types (comma-separated)
+ std::string paramNames; // Just the parameter names (comma-separated, e.g., "_this,ForPC,bForced")
+ intptr_t offset; // Function offset
+ bool isStatic; // Whether function is static
+ };
+
+ class TypeTable;
+
class PdbReader
{
public:
@@ -16,28 +63,61 @@ namespace API
~PdbReader() = default;
void Read(const std::wstring& path, std::unordered_map* offsets_dump,
- std::unordered_map* bitfields_dump, const std::unordered_set filter_set);
+ std::unordered_map* bitfields_dump,
+ const std::unordered_set filter_set,
+ std::unordered_map* fields_dump = nullptr,
+ std::unordered_map* functions_dump = nullptr);
private:
- static void LoadDataFromPdb(const std::wstring& /*path*/, IDiaDataSource** /*dia_source*/, IDiaSession**
- /*session*/, IDiaSymbol** /*symbol*/);
+ // Main processing methods
+ void ProcessTypes(const PDB::TPIStream& tpiStream, const TypeTable& typeTable);
+ void ProcessFunctions(const PDB::RawFile& rawFile, const PDB::DBIStream& dbiStream, const PDB::ImageSectionStream& imageSectionStream);
+ void ProcessGlobalVariables(const PDB::RawFile& rawFile, const PDB::DBIStream& dbiStream, const PDB::ImageSectionStream& imageSectionStream, const TypeTable& typeTable);
- void DumpStructs(IDiaSymbol* /*g_symbol*/);
- void DumpFunctions(IDiaSymbol* /*g_symbol*/);
- void DumpGlobalVariables(IDiaSymbol* /*g_symbol*/);
- void DumpType(IDiaSymbol* /*symbol*/, const std::string& /*structure*/, int /*indent*/) const;
- void DumpData(IDiaSymbol* /*symbol*/, const std::string& /*structure*/) const;
+ // Type processing helpers
+ void ProcessStructOrClass(const PDB::CodeView::TPI::Record* record, const TypeTable& typeTable);
+ void ProcessFieldList(const PDB::CodeView::TPI::Record* record, const std::string& structName, const TypeTable& typeTable);
- bool FilterSymbols(const std::string input);
- static std::string GetSymbolNameString(IDiaSymbol* /*symbol*/);
- static uint32_t GetSymbolId(IDiaSymbol* /*symbol*/);
- static void Cleanup(IDiaSymbol* /*symbol*/, IDiaSession* /*session*/, IDiaDataSource* /*source*/);
- static std::string GetFunctionSymbolParams(IDiaSymbol* /*symbol*/);
+ std::string GetFunctionParams(uint32_t typeIndex, const TypeTable& typeTable);
+ // Utility methods
+ bool FilterSymbols(const std::string& name) const;
+ std::string GetTypeName(const TypeTable& typeTable, uint32_t typeIndex) const;
+ std::string GetTypeNameInternal(const TypeTable& typeTable, uint32_t typeIndex, int depth) const;
+
+ // Thread-safe data access
+ void AddOffset(const std::string& key, intptr_t value);
+ void AddBitField(const std::string& key, const BitField& value);
+ void AddFieldInfo(const std::string& key, const std::string& typeName, intptr_t offset, bool isPointer);
+ void AddFunctionInfo(const std::string& key, const std::string& returnType, const std::string& signature, const std::string& params, const std::string& paramNames, intptr_t offset, bool isStatic);
+ bool MarkVisited(uint32_t id);
+
+ // Parameter names extraction and module function processing
+ void CollectFunctionParamNames(const PDB::RawFile& rawFile, const PDB::DBIStream& dbiStream);
+ void ProcessModuleFunctions(const PDB::RawFile& rawFile, const PDB::DBIStream& dbiStream);
+ std::string GetParamNamesForOffset(uint32_t offset) const;
+ bool FunctionHasThisPointer(const std::string& funcName) const;
+
+ // Data members
std::unordered_map* offsets_dump_{nullptr};
std::unordered_map* bitfields_dump_{nullptr};
-
+ std::unordered_map* fields_dump_{nullptr};
+ std::unordered_map* functions_dump_{nullptr};
std::unordered_set filter_set_;
+
+ // Map from function offset to comma-separated parameter names
+ std::unordered_map param_names_map_;
+ // Map from function name key to whether it has 'this' pointer (non-static member function)
+ std::unordered_map func_has_this_map_;
+ std::mutex param_names_mutex_;
+ std::mutex func_has_this_mutex_;
+
+ // Thread synchronization
+ std::mutex offsets_mutex_;
+ std::mutex bitfields_mutex_;
+ std::mutex fields_mutex_;
+ std::mutex functions_mutex_;
+ std::mutex visited_mutex_;
std::unordered_set visited_;
};
} // namespace API
diff --git a/Includes/raw_pdb/LICENSE b/Includes/raw_pdb/LICENSE
new file mode 100644
index 0000000..d3fe23f
--- /dev/null
+++ b/Includes/raw_pdb/LICENSE
@@ -0,0 +1,25 @@
+BSD 2-Clause License
+
+Copyright 2011-2022, Molecular Matters GmbH
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Includes/raw_pdb/src/Foundation/PDB_ArrayView.h b/Includes/raw_pdb/src/Foundation/PDB_ArrayView.h
new file mode 100644
index 0000000..3c462ee
--- /dev/null
+++ b/Includes/raw_pdb/src/Foundation/PDB_ArrayView.h
@@ -0,0 +1,68 @@
+// Copyright 2011-2022, Molecular Matters GmbH
+// See LICENSE.txt for licensing details (2-clause BSD License: https://opensource.org/licenses/BSD-2-Clause)
+
+#pragma once
+
+#include "PDB_Macros.h"
+#include "PDB_Assert.h"
+
+
+namespace PDB
+{
+ // A read-only view into arrays of any type and length.
+ template
+ class PDB_NO_DISCARD ArrayView
+ {
+ public:
+ // Constructs an array view from a C array with explicit length.
+ inline constexpr explicit ArrayView(const T* const array, size_t length) PDB_NO_EXCEPT
+ : m_data(array)
+ , m_length(length)
+ {
+ }
+
+ PDB_DEFAULT_COPY_CONSTRUCTOR(ArrayView);
+ PDB_DEFAULT_MOVE_CONSTRUCTOR(ArrayView);
+
+ // Provides read-only access to the underlying array.
+ PDB_NO_DISCARD inline constexpr const T* Decay(void) const PDB_NO_EXCEPT
+ {
+ return m_data;
+ }
+
+ // Returns the length of the view.
+ PDB_NO_DISCARD inline constexpr size_t GetLength(void) const PDB_NO_EXCEPT
+ {
+ return m_length;
+ }
+
+ // Returns the i-th element.
+ PDB_NO_DISCARD inline const T& operator[](size_t i) const PDB_NO_EXCEPT
+ {
+ PDB_ASSERT(i < GetLength(), "Index %zu out of bounds [0, %zu).", i, GetLength());
+ return m_data[i];
+ }
+
+
+ // ------------------------------------------------------------------------------------------------
+ // Range-based for-loop support
+ // ------------------------------------------------------------------------------------------------
+
+ PDB_NO_DISCARD inline const T* begin(void) const PDB_NO_EXCEPT
+ {
+ return m_data;
+ }
+
+ PDB_NO_DISCARD inline const T* end(void) const PDB_NO_EXCEPT
+ {
+ return m_data + m_length;
+ }
+
+ private:
+ const T* const m_data;
+ const size_t m_length;
+
+ PDB_DISABLE_MOVE_ASSIGNMENT(ArrayView);
+ PDB_DISABLE_COPY_ASSIGNMENT(ArrayView);
+ };
+}
diff --git a/Includes/raw_pdb/src/Foundation/PDB_Assert.h b/Includes/raw_pdb/src/Foundation/PDB_Assert.h
new file mode 100644
index 0000000..6991e06
--- /dev/null
+++ b/Includes/raw_pdb/src/Foundation/PDB_Assert.h
@@ -0,0 +1,27 @@
+// Copyright 2011-2022, Molecular Matters GmbH
+// See LICENSE.txt for licensing details (2-clause BSD License: https://opensource.org/licenses/BSD-2-Clause)
+
+#pragma once
+
+#include "PDB_Macros.h"
+#include "PDB_Log.h"
+
+
+PDB_PUSH_WARNING_CLANG
+PDB_DISABLE_WARNING_CLANG("-Wgnu-zero-variadic-macro-arguments")
+PDB_DISABLE_WARNING_CLANG("-Wreserved-identifier")
+
+extern "C" void __cdecl __debugbreak(void);
+
+#if PDB_COMPILER_MSVC
+# pragma intrinsic(__debugbreak)
+#endif
+
+
+#ifdef _DEBUG
+# define PDB_ASSERT(_condition, _msg, ...) (_condition) ? (void)true : (PDB_LOG_ERROR(_msg, ##__VA_ARGS__), __debugbreak())
+#else
+# define PDB_ASSERT(_condition, _msg, ...) PDB_NOOP(_condition, _msg, ##__VA_ARGS__)
+#endif
+
+PDB_POP_WARNING_CLANG
diff --git a/Includes/raw_pdb/src/Foundation/PDB_BitOperators.h b/Includes/raw_pdb/src/Foundation/PDB_BitOperators.h
new file mode 100644
index 0000000..04f17a4
--- /dev/null
+++ b/Includes/raw_pdb/src/Foundation/PDB_BitOperators.h
@@ -0,0 +1,23 @@
+// Copyright 2011-2022, Molecular Matters GmbH
+// See LICENSE.txt for licensing details (2-clause BSD License: https://opensource.org/licenses/BSD-2-Clause)
+
+#pragma once
+
+#include "PDB_Macros.h"
+
+
+#define PDB_DEFINE_BIT_OPERATORS(_type) \
+ PDB_NO_DISCARD inline constexpr _type operator|(_type lhs, _type rhs) PDB_NO_EXCEPT \
+ { \
+ return static_cast<_type>(PDB_AS_UNDERLYING(lhs) | PDB_AS_UNDERLYING(rhs)); \
+ } \
+ \
+ PDB_NO_DISCARD inline constexpr _type operator&(_type lhs, _type rhs) PDB_NO_EXCEPT \
+ { \
+ return static_cast<_type>(PDB_AS_UNDERLYING(lhs) & PDB_AS_UNDERLYING(rhs)); \
+ } \
+ \
+ PDB_NO_DISCARD inline constexpr _type operator~(_type value) PDB_NO_EXCEPT \
+ { \
+ return static_cast<_type>(~PDB_AS_UNDERLYING(value)); \
+ }
diff --git a/Includes/raw_pdb/src/Foundation/PDB_BitUtil.h b/Includes/raw_pdb/src/Foundation/PDB_BitUtil.h
new file mode 100644
index 0000000..7dc5ee3
--- /dev/null
+++ b/Includes/raw_pdb/src/Foundation/PDB_BitUtil.h
@@ -0,0 +1,73 @@
+// Copyright 2011-2022, Molecular Matters GmbH
+// See LICENSE.txt for licensing details (2-clause BSD License: https://opensource.org/licenses/BSD-2-Clause)
+
+#pragma once
+
+#include "PDB_Assert.h"
+
+#ifdef _WIN32
+ PDB_PUSH_WARNING_CLANG
+ PDB_DISABLE_WARNING_CLANG("-Wreserved-identifier")
+
+ extern "C" unsigned char _BitScanForward(unsigned long* _Index, unsigned long _Mask);
+
+ PDB_POP_WARNING_CLANG
+
+# if PDB_COMPILER_MSVC
+# pragma intrinsic(_BitScanForward)
+# endif
+#endif
+
+
+namespace PDB
+{
+ namespace BitUtil
+ {
+ // Returns whether the given unsigned value is a power of two.
+ template
+ PDB_NO_DISCARD inline constexpr bool IsPowerOfTwo(T value) PDB_NO_EXCEPT
+ {
+ PDB_ASSERT(value != 0u, "Invalid value.");
+
+ return (value & (value - 1u)) == 0u;
+ }
+
+
+ // Rounds the given unsigned value up to the next multiple.
+ template
+ PDB_NO_DISCARD inline constexpr T RoundUpToMultiple(T numToRound, T multipleOf) PDB_NO_EXCEPT
+ {
+ PDB_ASSERT(IsPowerOfTwo(multipleOf), "Multiple must be a power-of-two.");
+
+ return (numToRound + (multipleOf - 1u)) & ~(multipleOf - 1u);
+ }
+
+
+ // Finds the position of the first set bit in the given value starting from the LSB, e.g. FindFirstSetBit(0b00000010) == 1.
+ // This operation is also known as CTZ (Count Trailing Zeros).
+ template
+ PDB_NO_DISCARD inline uint32_t FindFirstSetBit(T value) PDB_NO_EXCEPT;
+
+ template <>
+ PDB_NO_DISCARD inline uint32_t FindFirstSetBit(uint32_t value) PDB_NO_EXCEPT
+ {
+ PDB_ASSERT(value != 0u, "Invalid value.");
+
+#ifdef _WIN32
+ unsigned long result = 0ul;
+
+ _BitScanForward(&result, value);
+#else
+ unsigned int result = 0u;
+
+ result = static_cast(__builtin_ffs(static_cast(value)));
+ if (result)
+ {
+ --result;
+ }
+#endif
+
+ return result;
+ }
+ }
+}
diff --git a/Includes/raw_pdb/src/Foundation/PDB_CRT.h b/Includes/raw_pdb/src/Foundation/PDB_CRT.h
new file mode 100644
index 0000000..539dab3
--- /dev/null
+++ b/Includes/raw_pdb/src/Foundation/PDB_CRT.h
@@ -0,0 +1,14 @@
+// Copyright 2011-2022, Molecular Matters GmbH
+// See LICENSE.txt for licensing details (2-clause BSD License: https://opensource.org/licenses/BSD-2-Clause)
+
+#pragma once
+
+
+// avoid pulling in different headers just for a few declarations
+extern "C" int __cdecl printf(char const* const _Format, ...);
+
+extern "C" int __cdecl memcmp(void const* _Buf1, void const* _Buf2, size_t _Size);
+extern "C" void* __cdecl memcpy(void* _Dst, void const* _Src, size_t _Size);
+
+extern "C" size_t __cdecl strlen(char const* _Str);
+extern "C" int __cdecl strcmp(char const* _Str1, char const* _Str2);
diff --git a/Includes/raw_pdb/src/Foundation/PDB_Forward.h b/Includes/raw_pdb/src/Foundation/PDB_Forward.h
new file mode 100644
index 0000000..ba82dfe
--- /dev/null
+++ b/Includes/raw_pdb/src/Foundation/PDB_Forward.h
@@ -0,0 +1,9 @@
+// Copyright 2011-2022, Molecular Matters GmbH
+// See LICENSE.txt for licensing details (2-clause BSD License: https://opensource.org/licenses/BSD-2-Clause)
+
+#pragma once
+
+
+// See Jonathan Mueller's blog for replacing std::move and std::forward:
+// https://foonathan.net/2021/09/move-forward/
+#define PDB_FORWARD(...) static_cast(__VA_ARGS__)
diff --git a/Includes/raw_pdb/src/Foundation/PDB_Log.h b/Includes/raw_pdb/src/Foundation/PDB_Log.h
new file mode 100644
index 0000000..83a8518
--- /dev/null
+++ b/Includes/raw_pdb/src/Foundation/PDB_Log.h
@@ -0,0 +1,15 @@
+// Copyright 2011-2022, Molecular Matters GmbH
+// See LICENSE.txt for licensing details (2-clause BSD License: https://opensource.org/licenses/BSD-2-Clause)
+
+#pragma once
+
+#include "PDB_Macros.h"
+#include "PDB_CRT.h"
+
+
+PDB_PUSH_WARNING_CLANG
+PDB_DISABLE_WARNING_CLANG("-Wgnu-zero-variadic-macro-arguments")
+
+#define PDB_LOG_ERROR(_format, ...) printf(_format, ##__VA_ARGS__)
+
+PDB_POP_WARNING_CLANG
diff --git a/Includes/raw_pdb/src/Foundation/PDB_Macros.h b/Includes/raw_pdb/src/Foundation/PDB_Macros.h
new file mode 100644
index 0000000..fddcccf
--- /dev/null
+++ b/Includes/raw_pdb/src/Foundation/PDB_Macros.h
@@ -0,0 +1,126 @@
+// Copyright 2011-2022, Molecular Matters GmbH
+// See LICENSE.txt for licensing details (2-clause BSD License: https://opensource.org/licenses/BSD-2-Clause)
+
+#pragma once
+
+#include "PDB_Platform.h"
+#include "PDB_TypeTraits.h"
+
+
+// ------------------------------------------------------------------------------------------------
+// ATTRIBUTES
+// ------------------------------------------------------------------------------------------------
+
+// Indicates to the compiler that the return value of a function or class should not be ignored.
+#if PDB_CPP_17
+# define PDB_NO_DISCARD [[nodiscard]]
+#else
+# define PDB_NO_DISCARD
+#endif
+
+// Indicates to the compiler that a function does not throw an exception.
+#define PDB_NO_EXCEPT noexcept
+
+
+// ------------------------------------------------------------------------------------------------
+// SPECIAL MEMBER FUNCTIONS
+// ------------------------------------------------------------------------------------------------
+
+// Default special member functions.
+#define PDB_DEFAULT_COPY_CONSTRUCTOR(_name) _name(const _name&) PDB_NO_EXCEPT = default
+#define PDB_DEFAULT_COPY_ASSIGNMENT(_name) _name& operator=(const _name&) PDB_NO_EXCEPT = default
+#define PDB_DEFAULT_MOVE_CONSTRUCTOR(_name) _name(_name&&) PDB_NO_EXCEPT = default
+#define PDB_DEFAULT_MOVE_ASSIGNMENT(_name) _name& operator=(_name&&) PDB_NO_EXCEPT = default
+
+// Default copy member functions.
+#define PDB_DEFAULT_COPY(_name) PDB_DEFAULT_COPY_CONSTRUCTOR(_name); PDB_DEFAULT_COPY_ASSIGNMENT(_name)
+
+// Default move member functions.
+#define PDB_DEFAULT_MOVE(_name) PDB_DEFAULT_MOVE_CONSTRUCTOR(_name); PDB_DEFAULT_MOVE_ASSIGNMENT(_name)
+
+// Single macro to default all copy and move member functions.
+#define PDB_DEFAULT_COPY_MOVE(_name) PDB_DEFAULT_COPY(_name); PDB_DEFAULT_MOVE(_name)
+
+// Disable special member functions.
+#define PDB_DISABLE_COPY_CONSTRUCTOR(_name) _name(const _name&) PDB_NO_EXCEPT = delete
+#define PDB_DISABLE_COPY_ASSIGNMENT(_name) _name& operator=(const _name&) PDB_NO_EXCEPT = delete
+#define PDB_DISABLE_MOVE_CONSTRUCTOR(_name) _name(_name&&) PDB_NO_EXCEPT = delete
+#define PDB_DISABLE_MOVE_ASSIGNMENT(_name) _name& operator=(_name&&) PDB_NO_EXCEPT = delete
+
+// Disable copy member functions.
+#define PDB_DISABLE_COPY(_name) PDB_DISABLE_COPY_CONSTRUCTOR(_name); PDB_DISABLE_COPY_ASSIGNMENT(_name)
+
+// Disable move member functions.
+#define PDB_DISABLE_MOVE(_name) PDB_DISABLE_MOVE_CONSTRUCTOR(_name); PDB_DISABLE_MOVE_ASSIGNMENT(_name)
+
+// Single macro to disable all copy and move member functions.
+#define PDB_DISABLE_COPY_MOVE(_name) PDB_DISABLE_COPY(_name); PDB_DISABLE_MOVE(_name)
+
+
+// ------------------------------------------------------------------------------------------------
+// COMPILER WARNINGS
+// ------------------------------------------------------------------------------------------------
+
+#if PDB_COMPILER_MSVC
+# define PDB_PRAGMA(_x) __pragma(_x)
+
+# define PDB_PUSH_WARNING_MSVC PDB_PRAGMA(warning(push))
+# define PDB_SUPPRESS_WARNING_MSVC(_number) PDB_PRAGMA(warning(suppress : _number))
+# define PDB_DISABLE_WARNING_MSVC(_number) PDB_PRAGMA(warning(disable : _number))
+# define PDB_POP_WARNING_MSVC PDB_PRAGMA(warning(pop))
+
+# define PDB_PUSH_WARNING_CLANG
+# define PDB_DISABLE_WARNING_CLANG(_diagnostic)
+# define PDB_POP_WARNING_CLANG
+#elif PDB_COMPILER_CLANG
+# define PDB_PRAGMA(_x) _Pragma(#_x)
+
+# define PDB_PUSH_WARNING_MSVC
+# define PDB_SUPPRESS_WARNING_MSVC(_number)
+# define PDB_DISABLE_WARNING_MSVC(_number)
+# define PDB_POP_WARNING_MSVC
+
+# define PDB_PUSH_WARNING_CLANG PDB_PRAGMA(clang diagnostic push)
+# define PDB_DISABLE_WARNING_CLANG(_diagnostic) PDB_PRAGMA(clang diagnostic ignored _diagnostic)
+# define PDB_POP_WARNING_CLANG PDB_PRAGMA(clang diagnostic pop)
+#elif PDB_COMPILER_GCC
+# define PDB_PRAGMA(_x) _Pragma(#_x)
+
+# define PDB_PUSH_WARNING_MSVC
+# define PDB_SUPPRESS_WARNING_MSVC(_number)
+# define PDB_DISABLE_WARNING_MSVC(_number)
+# define PDB_POP_WARNING_MSVC
+
+# define PDB_PUSH_WARNING_CLANG
+# define PDB_DISABLE_WARNING_CLANG(_diagnostic)
+# define PDB_POP_WARNING_CLANG
+#endif
+
+
+// ------------------------------------------------------------------------------------------------
+// MISCELLANEOUS
+// ------------------------------------------------------------------------------------------------
+
+// Trick to make other macros require a semicolon at the end.
+#define PDB_REQUIRE_SEMICOLON static_assert(true, "")
+
+// Defines a C-like flexible array member.
+#define PDB_FLEXIBLE_ARRAY_MEMBER(_type, _name) \
+ PDB_PUSH_WARNING_MSVC \
+ PDB_PUSH_WARNING_CLANG \
+ PDB_DISABLE_WARNING_MSVC(4200) \
+ PDB_DISABLE_WARNING_CLANG("-Wzero-length-array") \
+ _type _name[0]; \
+ PDB_POP_WARNING_MSVC \
+ PDB_POP_WARNING_CLANG \
+ PDB_REQUIRE_SEMICOLON
+
+// Casts any value to the value of the underlying type.
+#define PDB_AS_UNDERLYING(_value) static_cast::type>(_value)
+
+// Signals to the compiler that a function should be ignored, but have its argument list parsed (and "used", so as to not generate "unused variable" warnings).
+#if PDB_COMPILER_MSVC
+# define PDB_NOOP __noop
+#else
+# define PDB_NOOP(...) (void)sizeof(__VA_ARGS__)
+#endif
diff --git a/Includes/raw_pdb/src/Foundation/PDB_Memory.h b/Includes/raw_pdb/src/Foundation/PDB_Memory.h
new file mode 100644
index 0000000..ccb7e86
--- /dev/null
+++ b/Includes/raw_pdb/src/Foundation/PDB_Memory.h
@@ -0,0 +1,11 @@
+// Copyright 2011-2022, Molecular Matters GmbH
+// See LICENSE.txt for licensing details (2-clause BSD License: https://opensource.org/licenses/BSD-2-Clause)
+
+#pragma once
+
+
+#define PDB_NEW(_type) new _type
+#define PDB_NEW_ARRAY(_type, _length) new _type[_length]
+
+#define PDB_DELETE(_ptr) delete _ptr
+#define PDB_DELETE_ARRAY(_ptr) delete[] _ptr
diff --git a/Includes/raw_pdb/src/Foundation/PDB_Move.h b/Includes/raw_pdb/src/Foundation/PDB_Move.h
new file mode 100644
index 0000000..04bf78b
--- /dev/null
+++ b/Includes/raw_pdb/src/Foundation/PDB_Move.h
@@ -0,0 +1,11 @@
+// Copyright 2011-2022, Molecular Matters GmbH
+// See LICENSE.txt for licensing details (2-clause BSD License: https://opensource.org/licenses/BSD-2-Clause)
+
+#pragma once
+
+#include "PDB_TypeTraits.h"
+
+
+// See Jonathan Mueller's blog for replacing std::move and std::forward:
+// https://foonathan.net/2020/09/move-forward/
+#define PDB_MOVE(...) static_cast::type&&>(__VA_ARGS__)
diff --git a/Includes/raw_pdb/src/Foundation/PDB_Platform.h b/Includes/raw_pdb/src/Foundation/PDB_Platform.h
new file mode 100644
index 0000000..8775a54
--- /dev/null
+++ b/Includes/raw_pdb/src/Foundation/PDB_Platform.h
@@ -0,0 +1,45 @@
+// Copyright 2011-2022, Molecular Matters GmbH
+// See LICENSE.txt for licensing details (2-clause BSD License: https://opensource.org/licenses/BSD-2-Clause)
+
+#pragma once
+
+
+// determine the compiler/toolchain used
+#if defined(__clang__)
+# define PDB_COMPILER_MSVC 0
+# define PDB_COMPILER_CLANG 1
+# define PDB_COMPILER_GCC 0
+#elif defined(_MSC_VER)
+# define PDB_COMPILER_MSVC 1
+# define PDB_COMPILER_CLANG 0
+# define PDB_COMPILER_GCC 0
+#elif defined(__GNUC__)
+# define PDB_COMPILER_MSVC 0
+# define PDB_COMPILER_CLANG 0
+# define PDB_COMPILER_GCC 1
+#else
+# error("Unknown compiler.");
+#endif
+
+// check whether C++17 is available
+#if __cplusplus >= 201703L
+# define PDB_CPP_17 1
+#else
+# define PDB_CPP_17 0
+#endif
+
+// define used standard types
+typedef decltype(sizeof(0)) size_t;
+static_assert(sizeof(sizeof(0)) == sizeof(size_t), "Wrong size.");
+
+typedef int int32_t;
+static_assert(sizeof(int32_t) == 4u, "Wrong size.");
+
+typedef unsigned char uint8_t;
+static_assert(sizeof(uint8_t) == 1u, "Wrong size.");
+
+typedef unsigned short uint16_t;
+static_assert(sizeof(uint16_t) == 2u, "Wrong size.");
+
+typedef unsigned int uint32_t;
+static_assert(sizeof(uint32_t) == 4u, "Wrong size.");
diff --git a/Includes/raw_pdb/src/Foundation/PDB_PointerUtil.h b/Includes/raw_pdb/src/Foundation/PDB_PointerUtil.h
new file mode 100644
index 0000000..014297d
--- /dev/null
+++ b/Includes/raw_pdb/src/Foundation/PDB_PointerUtil.h
@@ -0,0 +1,33 @@
+// Copyright 2011-2022, Molecular Matters GmbH
+// See LICENSE.txt for licensing details (2-clause BSD License: https://opensource.org/licenses/BSD-2-Clause)
+
+#pragma once
+
+#include "PDB_Macros.h"
+#include "PDB_TypeTraits.h"
+
+
+namespace PDB
+{
+ namespace Pointer
+ {
+ // Offsets any pointer by a given number of bytes.
+ template
+ PDB_NO_DISCARD inline T Offset(U* anyPointer, V howManyBytes) PDB_NO_EXCEPT
+ {
+ static_assert(PDB::is_pointer::value == true, "Type T must be a pointer type.");
+
+ union
+ {
+ T as_T;
+ U* as_U_ptr;
+ char* as_char_ptr;
+ };
+
+ as_U_ptr = anyPointer;
+ as_char_ptr += howManyBytes;
+
+ return as_T;
+ }
+ }
+}
diff --git a/Includes/raw_pdb/src/Foundation/PDB_TypeTraits.h b/Includes/raw_pdb/src/Foundation/PDB_TypeTraits.h
new file mode 100644
index 0000000..9286453
--- /dev/null
+++ b/Includes/raw_pdb/src/Foundation/PDB_TypeTraits.h
@@ -0,0 +1,65 @@
+// Copyright 2011-2022, Molecular Matters GmbH
+// See LICENSE.txt for licensing details (2-clause BSD License: https://opensource.org/licenses/BSD-2-Clause)
+
+#pragma once
+
+
+// provide our own type traits to avoid pulling in unnecessary includes
+namespace PDB
+{
+ template
+ struct is_pointer
+ {
+ static constexpr bool value = false;
+ };
+
+ template
+ struct is_pointer