From 82fb53f089404bc885e61740ebcb8384ed5c1c07 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Tue, 26 Aug 2025 16:51:02 +0200 Subject: [PATCH] enhancement(nodeps): remove pe-parse dependency --- .gitmodules | 3 - CMakeLists.txt | 2 - include/extract/dll.hpp | 22 +++++ src/extract/dll.cpp | 203 ++++++++++++++++++++++++++++++++++++++++ src/extract/extract.cpp | 50 ++++------ thirdparty/pe-parse | 1 - 6 files changed, 241 insertions(+), 40 deletions(-) create mode 100644 include/extract/dll.hpp create mode 100644 src/extract/dll.cpp delete mode 160000 thirdparty/pe-parse diff --git a/.gitmodules b/.gitmodules index 96897ed..e69de29 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +0,0 @@ -[submodule "thirdparty/pe-parse"] - path = thirdparty/pe-parse - url = https://github.com/trailofbits/pe-parse diff --git a/CMakeLists.txt b/CMakeLists.txt index df459a0..c14a71c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,7 +12,6 @@ add_compile_options(-fPIC -Wno-deprecated-declarations -Wno-unused-template) -add_subdirectory(thirdparty/pe-parse/pe-parser-library EXCLUDE_FROM_ALL) add_subdirectory(framegen) if(LSFGVK_EXCESS_DEBUG) @@ -44,7 +43,6 @@ target_include_directories(lsfg-vk SYSTEM target_include_directories(lsfg-vk PUBLIC include) target_link_libraries(lsfg-vk PUBLIC - pe-parse lsfg-vk-framegen) # diagnostics diff --git a/include/extract/dll.hpp b/include/extract/dll.hpp new file mode 100644 index 0000000..eff8d82 --- /dev/null +++ b/include/extract/dll.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include +#include +#include +#include + +namespace DLL { + + /// + /// Parse all resources from a DLL file. + /// + /// *Shouldn't* cause any segmentation faults. + /// + /// @param filename Path to the DLL file. + /// @return A map of resource IDs to their binary data. + /// + /// @throws std::runtime_error on various failure points. + /// + std::unordered_map> parse_dll(const std::string& filename); + +} diff --git a/src/extract/dll.cpp b/src/extract/dll.cpp new file mode 100644 index 0000000..893db5f --- /dev/null +++ b/src/extract/dll.cpp @@ -0,0 +1,203 @@ +#include "extract/dll.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/// DOS file header +struct DOSHeader { + uint16_t magic; // 0x5A4D + std::array pad; + int32_t pe_offset; // file offset +}; + +/// PE header +struct PEHeader { + uint32_t signature; // "PE\0\0" + std::array pad1; + uint16_t sect_count; + std::array pad2; + uint16_t opt_hdr_size; + std::array pad3; +}; + +/// (partial!) PE optional header +struct PEOptionalHeader { + uint16_t magic; // 0x20B + std::array pad4; + std::pair resource_table; // file offset/size +}; + +/// Section header +struct SectionHeader { + std::array pad1; + uint32_t vsize; // virtual + uint32_t vaddress; + uint32_t fsize; // raw + uint32_t foffset; + std::array pad2; +}; + +/// Resource directory +struct ResourceDirectory { + std::array pad; + uint16_t name_count; + uint16_t id_count; +}; + +/// Resource directory entry +struct ResourceDirectoryEntry { + uint32_t id; + uint32_t offset; // high bit = directory +}; + +/// Resource data entry +struct ResourceDataEntry { + uint32_t offset; + uint32_t size; + std::array pad; +}; + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-container" +namespace { + /// Safely cast a vector to a pointer of type T + template + const T* safe_cast(const std::vector& data, size_t offset) { + const size_t end = offset + sizeof(T); + if (end > data.size() || end < offset) + throw std::runtime_error("Buffer overflow during safe cast"); + return reinterpret_cast(&data.at(offset)); + } + + /// Safely cast a vector to a span of T + template + std::span span_cast(const std::vector& data, size_t offset, size_t count) { + const size_t end = offset + (count * sizeof(T)); + if (end > data.size() || end < offset) + throw std::runtime_error("Buffer overflow during safe cast"); + return std::span(reinterpret_cast(&data.at(offset)), count); + } +} +#pragma clang diagnostic pop + +std::unordered_map> DLL::parse_dll(const std::string& filename) { + std::ifstream file(filename, std::ios::binary | std::ios::ate); + if (!file.is_open()) + throw std::runtime_error("Failed to open Lossless.dll"); + + const auto size = file.tellg(); + file.seekg(0, std::ios::beg); + + std::vector data(static_cast(size)); + if (!file.read(reinterpret_cast(data.data()), size)) + throw std::runtime_error("Failed to read Lossless.dll"); + + // parse dos header + size_t fileOffset = 0; + const auto* dosHdr = safe_cast(data, 0); + if (dosHdr->magic != 0x5A4D) + throw std::runtime_error("Invalid DOS header magic number"); + + // parse pe header + fileOffset += static_cast(dosHdr->pe_offset); + const auto* peHdr = safe_cast(data, fileOffset); + if (peHdr->signature != 0x00004550) + throw std::runtime_error("Invalid PE header signature"); + + // parse optional pe header + fileOffset += sizeof(PEHeader); + const auto* peOptHdr = safe_cast(data, fileOffset); + if (peOptHdr->magic != 0x20B) + throw std::runtime_error("Unsupported PE format (not PE32+)"); + const auto& [rsrc_rva, rsrc_size] = peOptHdr->resource_table; + + // locate section containing resources + std::optional rsrc_offset; + fileOffset += peHdr->opt_hdr_size; + const auto sectHdrs = span_cast(data, fileOffset, peHdr->sect_count); + for (const auto& sectHdr : sectHdrs) { + if (rsrc_rva < sectHdr.vaddress || rsrc_rva > (sectHdr.vaddress + sectHdr.vsize)) + continue; + + rsrc_offset.emplace((rsrc_rva - sectHdr.vaddress) + sectHdr.foffset); + break; + } + if (!rsrc_offset) + throw std::runtime_error("Failed to locate resource section"); + + // parse resource directory + fileOffset = rsrc_offset.value(); + const auto* rsrcDir = safe_cast(data, fileOffset); + if (rsrcDir->id_count < 3) + throw std::runtime_error("Incorrect resource directory"); + + // find resource table with data type + std::optional rsrc_tbl_offset; + fileOffset = rsrc_offset.value() + sizeof(ResourceDirectory); + const auto rsrcDirEntries = span_cast( + data, fileOffset, rsrcDir->name_count + rsrcDir->id_count); + for (const auto& rsrcDirEntry : rsrcDirEntries) { + if (rsrcDirEntry.id != 10) // RT_RCDATA + continue; + if ((rsrcDirEntry.offset & 0x80000000) == 0) + throw std::runtime_error("Expected resource directory, but found data entry"); + + rsrc_tbl_offset.emplace(rsrcDirEntry.offset & 0x7FFFFFFF); + } + if (!rsrc_tbl_offset) + throw std::runtime_error("Failed to locate RT_RCDATA directory"); + + // parse data type resource directory + fileOffset = rsrc_offset.value() + rsrc_tbl_offset.value(); + const auto* rsrcTbl = safe_cast(data, fileOffset); + if (rsrcTbl->id_count < 1) + throw std::runtime_error("Incorrect RT_RCDATA directory"); + + // collect all resources + fileOffset += sizeof(ResourceDirectory); + const auto rsrcTblEntries = span_cast( + data, fileOffset, rsrcTbl->name_count + rsrcTbl->id_count); + std::unordered_map> resources; + for (const auto& rsrcTblEntry : rsrcTblEntries) { + if ((rsrcTblEntry.offset & 0x80000000) == 0) + throw std::runtime_error("Expected resource directory, but found data entry"); + + // skip over language directory + fileOffset = rsrc_offset.value() + (rsrcTblEntry.offset & 0x7FFFFFFF); + const auto* langDir = safe_cast(data, fileOffset); + if (langDir->id_count < 1) + throw std::runtime_error("Incorrect language directory"); + + fileOffset += sizeof(ResourceDirectory); + const auto* langDirEntry = safe_cast(data, fileOffset); + if ((langDirEntry->offset & 0x80000000) != 0) + throw std::runtime_error("Expected resource data entry, but found directory"); + + // parse resource data entry + fileOffset = rsrc_offset.value() + (langDirEntry->offset & 0x7FFFFFFF); + const auto* entry = safe_cast(data, fileOffset); + if (entry->offset < rsrc_rva || entry->offset > (rsrc_rva + rsrc_size)) + throw std::runtime_error("Resource data entry points outside resource section"); + + // extract resource + std::vector resource(entry->size); + fileOffset = (entry->offset - rsrc_rva) + rsrc_offset.value(); + if (fileOffset + entry->size > data.size()) + throw std::runtime_error("Resource data entry points outside file"); + std::copy_n(&data.at(fileOffset), entry->size, resource.data()); + resources.emplace(rsrcTblEntry.id, std::move(resource)); + } + + return resources; +} diff --git a/src/extract/extract.cpp b/src/extract/extract.cpp index 75869eb..c00c9eb 100644 --- a/src/extract/extract.cpp +++ b/src/extract/extract.cpp @@ -1,16 +1,14 @@ #include "extract/extract.hpp" #include "config/config.hpp" +#include "extract/dll.hpp" -#include - +#include #include #include -#include #include #include #include #include -#include #include #include @@ -73,22 +71,11 @@ const std::unordered_map nameIdxTable = {{ }}; namespace { - auto& pshaders() { + auto& shaders() { static std::unordered_map, 2>> shaderData; return shaderData; } - int on_resource(void* ptr, const peparse::resource& res) { - if (res.type != peparse::RT_RCDATA || res.buf == nullptr || res.buf->bufLen <= 0) - return 0; - std::vector resource_data(res.buf->bufLen); - std::copy_n(res.buf->buf, res.buf->bufLen, resource_data.data()); - - auto* shaders = reinterpret_cast>*>(ptr); - shaders->emplace(res.name, std::move(resource_data)); - return 0; - } - const std::vector PATHS{{ ".local/share/Steam/steamapps/common", ".steam/steam/steamapps/common", @@ -121,44 +108,39 @@ namespace { } void Extract::extractShaders() { - if (!pshaders().empty()) + if (!shaders().empty()) return; - std::unordered_map> shaders{}; - // parse the dll - peparse::parsed_pe* dll = peparse::ParsePEFromFile(getDllPath().c_str()); - if (!dll) - throw std::runtime_error("Unable to read Lossless.dll, is it installed?"); - peparse::IterRsrc(dll, on_resource, reinterpret_cast(&shaders)); - peparse::DestructParsedPE(dll); + const auto resources = DLL::parse_dll(getDllPath()); + std::cerr << "lsfg-vk: Extracted " << resources.size() << " resources from dll.\n"; // ensure all shaders are present for (const auto& [name, idx] : nameIdxTable) { - auto fp16 = shaders.find(idx); - if (fp16 == shaders.end()) + auto fp16 = resources.find(idx); + if (fp16 == resources.end()) throw std::runtime_error("Shader not found: " + name + " (FP16).\n- Is Lossless Scaling up to date?"); - auto fp32 = shaders.find(idx + FP); - if (fp32 == shaders.end()) + auto fp32 = resources.find(idx + FP); + if (fp32 == resources.end()) throw std::runtime_error("Shader not found: " + name + " (FP32).\n- Is Lossless Scaling up to date?"); - pshaders().emplace(idx, std::array, 2>{ - std::move(fp32->second), - std::move(fp16->second) + shaders().emplace(idx, std::array, 2>{ + fp32->second, + fp16->second }); } } std::vector Extract::getShader(const std::string& name, bool fp16) { - if (pshaders().empty()) + if (shaders().empty()) throw std::runtime_error("Shaders are not loaded."); auto hit = nameIdxTable.find(name); if (hit == nameIdxTable.end()) throw std::runtime_error("Shader hash not found: " + name); - auto sit = pshaders().find(hit->second); - if (sit == pshaders().end()) + auto sit = shaders().find(hit->second); + if (sit == shaders().end()) throw std::runtime_error("Shader not found: " + name); return fp16 ? sit->second.at(1) : sit->second.at(0); } diff --git a/thirdparty/pe-parse b/thirdparty/pe-parse deleted file mode 160000 index 31ac596..0000000 --- a/thirdparty/pe-parse +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 31ac5966503689d5693cd9fb520bd525a8710e17