From 9ac922c83473ebf6b5fd6ea8719c679c8b004e38 Mon Sep 17 00:00:00 2001 From: Masterkatze Date: Mon, 3 Nov 2025 05:24:10 +0300 Subject: [PATCH] Add class variable support, fix some issues --- src/formatter.cpp | 8 ++-- src/main.cpp | 2 +- src/parser.cpp | 22 ++++----- src/reader.cpp | 41 ++++++++++++++--- src/reader.hpp | 8 ++++ src/writer.cpp | 111 +++++++++++++++++++++++++++++++++++++--------- src/writer.hpp | 6 ++- 7 files changed, 149 insertions(+), 49 deletions(-) diff --git a/src/formatter.cpp b/src/formatter.cpp index cb1d777..902ebda 100644 --- a/src/formatter.cpp +++ b/src/formatter.cpp @@ -63,8 +63,8 @@ std::vector formatVTable(const ClassInfo &classInfo) while ((linuxIndex - (1 + previousOverloads)) >= 0) { - auto previousFunctionIndex = linuxIndex - (1 + previousOverloads); - auto previousFunctionInfo = vtableInfo.functions.at(previousFunctionIndex); + const auto previousFunctionIndex = linuxIndex - (1 + previousOverloads); + const auto previousFunctionInfo = vtableInfo.functions.at(previousFunctionIndex); if (functionInfo->symbol.name.empty() || shouldSkipWindowsFunction(classInfo, vtableIndex, previousFunctionIndex, *previousFunctionInfo)) { @@ -81,8 +81,8 @@ std::vector formatVTable(const ClassInfo &classInfo) while ((linuxIndex + 1 + remainingOverloads) < static_cast(vtableInfo.functions.size())) { - auto nextFunctionIndex = linuxIndex + 1 + remainingOverloads; - auto nextFunctionInfo = vtableInfo.functions.at(nextFunctionIndex); + const auto nextFunctionIndex = linuxIndex + 1 + remainingOverloads; + const auto nextFunctionInfo = vtableInfo.functions.at(nextFunctionIndex); if (functionInfo->symbol.name.empty() || shouldSkipWindowsFunction(classInfo, vtableIndex, nextFunctionIndex, *nextFunctionInfo)) { diff --git a/src/main.cpp b/src/main.cpp index ae52ed1..4196b22 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -246,5 +246,5 @@ int main(int argc, char *argv[]) } } - return writeGamedataFile(out.classes, inputFilePaths, outputDirectoryPaths); + return writeGamedataFile(out.classes, programInfo.vtableFieldDataEntries, inputFilePaths, outputDirectoryPaths); } diff --git a/src/parser.cpp b/src/parser.cpp index 8e704c3..07040f6 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -7,7 +7,7 @@ std::unique_ptr demangleSymbol(const char *abiName) { - int status; + int status = -4; char *ret = abi::__cxa_demangle(abiName, 0, 0, &status); DemangledSymbolDeallocator deallocator = [](char *mem) @@ -34,7 +34,7 @@ std::unique_ptr demangleSymbol(const char *abi std::span getDataForSymbol(const ProgramInfo &programInfo, const SymbolInfo &symbol) { LargeNumber dataStart; - const std::vector *dataChunks; + const std::vector *dataChunks = nullptr; if (symbol.section == 0) { @@ -149,12 +149,12 @@ Out parse(ProgramInfo &programInfo) functionAddress.low = symbolDataView[functionIndex]; functionAddress.isUnsigned = true; + // Note: Relocations not supported for 64-bit bins if (programInfo.addressSize > BYTES_PER_ELEMENT) { functionAddress.high = symbolDataView[++functionIndex]; } - - if (programInfo.addressSize == BYTES_PER_ELEMENT) + else if (programInfo.addressSize == BYTES_PER_ELEMENT) { LargeNumber localAddress; localAddress.high = 0; @@ -167,10 +167,6 @@ Out parse(ProgramInfo &programInfo) functionAddress = targetAddress; } } - else - { - std::cout << "Relocations not supported for 64-bit bins" << std::endl; - } auto functionSymbolsIterator = addressToSymbolMap.find(functionAddress); @@ -186,7 +182,7 @@ Out parse(ProgramInfo &programInfo) // This could be the end of the vtable, or it could just be a pure/deleted func. if (functionSymbolsIterator == addressToSymbolMap.end()) { - if (classInfo.vtables.size() == 0 || static_cast(functionAddress) != 0) + if (classInfo.vtables.empty() || static_cast(functionAddress) != 0) { auto& newClassVTable = classInfo.vtables.emplace_back(); classVTable = &newClassVTable; @@ -206,7 +202,7 @@ Out parse(ProgramInfo &programInfo) } auto functionSymbols = functionSymbolsIterator->second; - auto functionSymbol = functionSymbols.back(); + const auto& functionSymbol = functionSymbols.back(); auto functionSymbolName = functionSymbol.name; if (functionSymbolName == "__cxa_deleted_virtual" || functionSymbolName == "__cxa_pure_virtual") @@ -215,7 +211,7 @@ Out parse(ProgramInfo &programInfo) continue; } - FunctionInfo *functionInfoPtr; + FunctionInfo *functionInfoPtr = nullptr; auto functionInfoIterator = addressToFunctionMap.find(functionAddress); if (functionInfoIterator != addressToFunctionMap.end()) @@ -235,6 +231,7 @@ Out parse(ProgramInfo &programInfo) if (startOfName != std::string::npos) { name = name.substr(startOfName + 2); + nameSpace.resize(startOfName); } auto startOfArgs = demangledSymbol.rfind('('); @@ -243,9 +240,6 @@ Out parse(ProgramInfo &programInfo) shortName = shortName.substr(startOfName + 2, startOfArgs - startOfName - 2); } - - nameSpace = nameSpace.substr(0, startOfName); - FunctionInfo functionInfo; functionInfo.id = functionAddress; diff --git a/src/reader.cpp b/src/reader.cpp index f3efeab..b94bc18 100644 --- a/src/reader.cpp +++ b/src/reader.cpp @@ -19,6 +19,13 @@ #include #include +struct VTableFieldOffsetDataRaw +{ + uint64_t class_name_ptr; + uint64_t member_name_ptr; + uint64_t offset; +}; + LargeNumber::LargeNumber() : high{}, low{}, isUnsigned{} { @@ -92,14 +99,14 @@ ProgramInfo process(char *image, std::size_t size) return programInfo; } - size_t numberOfSections; + size_t numberOfSections = 0; if (elf_getshdrnum(elf, &numberOfSections) != 0) { programInfo.error = "Failed to get number of ELF sections. (" + std::string(elf_errmsg(-1)) + ")"; return programInfo; } - size_t sectionNameStringTableIndex; + size_t sectionNameStringTableIndex = 0; if (elf_getshdrstrndx(elf, §ionNameStringTableIndex) != 0) { programInfo.error = "Failed to get ELF section names. (" + std::string(elf_errmsg(-1)) + ")"; @@ -113,14 +120,14 @@ ProgramInfo process(char *image, std::size_t size) Elf_Scn *symbolTableScn = nullptr; size_t stringTableIndex = SHN_UNDEF; - Elf_Scn *stringTableScn = nullptr; + const Elf_Scn *stringTableScn = nullptr; size_t rodataIndex = SHN_UNDEF; - Elf64_Addr rodataOffset; + Elf64_Addr rodataOffset = 0; Elf_Scn *rodataScn = nullptr; size_t relRodataIndex = SHN_UNDEF; - Elf64_Addr relRodataOffset; + Elf64_Addr relRodataOffset = 0; Elf_Scn *relRodataScn = nullptr; for (size_t elfSectionIndex = 0; elfSectionIndex < numberOfSections; ++elfSectionIndex) @@ -175,6 +182,26 @@ ProgramInfo process(char *image, std::size_t size) relRodataOffset = elfSectionHeader.sh_addr; relRodataScn = elfScn; } + else if(elfSectionHeader.sh_type == SHT_PROGBITS && strcmp(name, ".member_offsets") == 0) + { + Elf_Data* data = elf_getdata(elfScn, nullptr); + if (data && data->d_size > 0) + { + size_t entry_count = data->d_size / sizeof(VTableFieldOffsetDataRaw); + auto entries = static_cast(data->d_buf); + + for (size_t i = 0; i < entry_count; ++i) + { +#if 0 + std::cout << "VTableFieldOffsetData entry " << i << ":\n"; + std::cout << " Class name: " << &image[entries[i].class_name_ptr] << "\n"; + std::cout << " Member name: " << &image[entries[i].member_name_ptr] << "\n"; + std::cout << " Offset: " << entries[i].offset << " (0x" << std::hex << entries[i].offset << std::dec << ")\n"; +#endif + programInfo.vtableFieldDataEntries.emplace_back(&image[entries[i].class_name_ptr], &image[entries[i].member_name_ptr], entries[i].offset); + } + } + } if (relocationTableScn && dynamicSymbolTableScn && symbolTableScn && stringTableScn && rodataScn && relRodataScn) { @@ -196,7 +223,7 @@ ProgramInfo process(char *image, std::size_t size) Elf_Data *relocationData = nullptr; while ((relocationData = elf_getdata(relocationTableScn, relocationData)) != nullptr) { - size_t relocationIndex = 0; + int relocationIndex = 0; GElf_Rel relocation; while (gelf_getrel(relocationData, relocationIndex++, &relocation) == &relocation) { @@ -210,7 +237,7 @@ ProgramInfo process(char *image, std::size_t size) while ((symbolData = elf_getdata(dynamicSymbolTableScn, symbolData)) != nullptr) { GElf_Sym symbol; - size_t symbolIndex = GELF_R_SYM(relocation.r_info); + int symbolIndex = GELF_R_SYM(relocation.r_info); if (gelf_getsym(symbolData, symbolIndex, &symbol) != &symbol) { continue; diff --git a/src/reader.hpp b/src/reader.hpp index fb6cf81..53b34cb 100644 --- a/src/reader.hpp +++ b/src/reader.hpp @@ -50,6 +50,13 @@ struct RelocationInfo LargeNumber target; }; +struct MemberOffset +{ + std::string className; + std::string memberName; + uint64_t offset; +}; + struct ProgramInfo { std::string error; @@ -62,6 +69,7 @@ struct ProgramInfo std::vector relRodataChunks; std::vector symbols; std::vector relocations; + std::vector vtableFieldDataEntries; }; ProgramInfo process(char *image, std::size_t size); diff --git a/src/writer.cpp b/src/writer.cpp index 64e8a09..992974e 100644 --- a/src/writer.cpp +++ b/src/writer.cpp @@ -16,7 +16,7 @@ using ClassNamespace = std::map; using ClassVTables = std::map; using Offsets = std::map; -Offsets prepareOffsets(std::list& classes) +Offsets prepareOffsets(const std::list& classes) { std::map offsets; @@ -31,13 +31,21 @@ Offsets prepareOffsets(std::list& classes) { if (!function.linuxIndex.has_value()) { - // std::cerr << fmt::format("Warning: function {} has no linuxIndex value", function.name) << std::endl; + if (!function.name.starts_with('~')) + { + std::cerr << fmt::format("Warning: function {} has no linuxIndex value", function.name) << std::endl; + } + continue; } if (!function.windowsIndex.has_value()) { - // std::cerr << fmt::format("Warning: function {} has no windowsIndex value", function.name) << std::endl; + if (!function.name.starts_with('~')) + { + std::cerr << fmt::format("Warning: function {} has no windowsIndex value", function.name) << std::endl; + } + continue; } @@ -50,33 +58,34 @@ Offsets prepareOffsets(std::list& classes) return offsets; } -std::optional getOffset(Offsets offsets, const std::string& symbol) +std::optional getVTableMethodOffset(Offsets offsets, const std::string& placeholder) { - auto functionNameStartPos = symbol.rfind("::"); + // placeholder example: CBasePlayer::CBaseEntity::AcceptInput(char const*, CBaseEntity*, CBaseEntity*, variant_t, int).windows + auto functionNameStartPos = placeholder.rfind("::"); if (functionNameStartPos == std::string::npos) { - std::cerr << fmt::format("Error: incorrect format of symbol {} (missing \'::\' separator)", symbol) << std::endl; + std::cerr << fmt::format("Error: incorrect format of symbol {} (missing \'::\' separator)", placeholder) << std::endl; return std::nullopt; } - auto systemNameStartPos = symbol.rfind('.'); + auto systemNameStartPos = placeholder.rfind('.'); if (systemNameStartPos == std::string::npos) { - std::cerr << fmt::format("Error: incorrect format of symbol {} (missing \'.\' separator)", symbol) << std::endl; + std::cerr << fmt::format("Error: incorrect format of symbol {} (missing \'.\' separator)", placeholder) << std::endl; return std::nullopt; } - auto namespaceStartPos = symbol.find("::"); + auto namespaceStartPos = placeholder.find("::"); if (namespaceStartPos == std::string::npos) { - std::cerr << fmt::format("Error: incorrect format of symbol {} (missing \'::\' separator)", symbol) << std::endl; + std::cerr << fmt::format("Error: incorrect format of symbol {} (missing \'::\' separator)", placeholder) << std::endl; return std::nullopt; } - auto className = symbol.substr(0, namespaceStartPos); - auto namespaceName = symbol.substr(namespaceStartPos + 2, functionNameStartPos - namespaceStartPos - 2); - auto functionName = symbol.substr(functionNameStartPos + 2, systemNameStartPos - functionNameStartPos - 2); - auto systemName = symbol.substr(systemNameStartPos + 1); + auto className = placeholder.substr(0, namespaceStartPos); + auto namespaceName = placeholder.substr(namespaceStartPos + 2, functionNameStartPos - namespaceStartPos - 2); + auto functionName = placeholder.substr(functionNameStartPos + 2, systemNameStartPos - functionNameStartPos - 2); + auto systemName = placeholder.substr(systemNameStartPos + 1); auto classVTablesIterator = offsets.find(className); if (classVTablesIterator == offsets.end()) @@ -107,7 +116,35 @@ std::optional getOffset(Offsets offsets, const std::string& symbol) return isLinux ? function.linuxIndex : function.windowsIndex; } -int writeGamedataFile(std::list& classes, const std::vector &inputFilePaths, const std::vector& outputDirectoryPaths) +std::optional getVTableFieldOffset(const std::vector& memberOffsets, const std::string& placeholder) +{ + // placeholder example: CGlobalEntityList::m_entityListeners + auto functionNameStartPos = placeholder.rfind("::"); + if (functionNameStartPos == std::string::npos) + { + std::cerr << fmt::format("Error: incorrect format of symbol {} (missing \'::\' separator)", placeholder) << std::endl; + return std::nullopt; + } + + auto className = placeholder.substr(0, functionNameStartPos); + auto memberName = placeholder.substr(functionNameStartPos + 2, placeholder.size()); + + for (const auto& memberOffset : memberOffsets) + { + if (memberOffset.className == className && memberOffset.memberName == memberName) + { + return memberOffset.offset; + } + } + + return std::nullopt; +} + +int writeGamedataFile( + const std::list& classes, + const std::vector& memberOffsets, + const std::vector& inputFilePaths, + const std::vector& outputDirectoryPaths) { auto offsets = prepareOffsets(classes); @@ -173,21 +210,51 @@ int writeGamedataFile(std::list& classes, const std::vector #include -int writeGamedataFile(std::list& classes, const std::vector &inputFilePaths, const std::vector &outputDirectoryPaths); +int writeGamedataFile( + const std::list& classes, + const std::vector& memberOffsets, + const std::vector& inputFilePaths, + const std::vector& outputDirectoryPaths);