mirror of
https://github.com/PancakeTAS/lsfg-vk.git
synced 2025-10-30 07:01:10 +00:00
enhancement(nodeps): remove pe-parse dependency
This commit is contained in:
parent
77c9cc632d
commit
82fb53f089
6 changed files with 241 additions and 40 deletions
3
.gitmodules
vendored
3
.gitmodules
vendored
|
|
@ -1,3 +0,0 @@
|
|||
[submodule "thirdparty/pe-parse"]
|
||||
path = thirdparty/pe-parse
|
||||
url = https://github.com/trailofbits/pe-parse
|
||||
|
|
@ -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
|
||||
|
|
|
|||
22
include/extract/dll.hpp
Normal file
22
include/extract/dll.hpp
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
#pragma once
|
||||
|
||||
#include <unordered_map>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
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<uint32_t, std::vector<uint8_t>> parse_dll(const std::string& filename);
|
||||
|
||||
}
|
||||
203
src/extract/dll.cpp
Normal file
203
src/extract/dll.cpp
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
#include "extract/dll.hpp"
|
||||
|
||||
#include <unordered_map>
|
||||
#include <stdexcept>
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <fstream>
|
||||
#include <utility>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include <span>
|
||||
|
||||
/// DOS file header
|
||||
struct DOSHeader {
|
||||
uint16_t magic; // 0x5A4D
|
||||
std::array<uint16_t, 29> pad;
|
||||
int32_t pe_offset; // file offset
|
||||
};
|
||||
|
||||
/// PE header
|
||||
struct PEHeader {
|
||||
uint32_t signature; // "PE\0\0"
|
||||
std::array<uint16_t, 1> pad1;
|
||||
uint16_t sect_count;
|
||||
std::array<uint16_t, 6> pad2;
|
||||
uint16_t opt_hdr_size;
|
||||
std::array<uint16_t, 1> pad3;
|
||||
};
|
||||
|
||||
/// (partial!) PE optional header
|
||||
struct PEOptionalHeader {
|
||||
uint16_t magic; // 0x20B
|
||||
std::array<uint16_t, 63> pad4;
|
||||
std::pair<uint32_t, uint32_t> resource_table; // file offset/size
|
||||
};
|
||||
|
||||
/// Section header
|
||||
struct SectionHeader {
|
||||
std::array<uint16_t, 4> pad1;
|
||||
uint32_t vsize; // virtual
|
||||
uint32_t vaddress;
|
||||
uint32_t fsize; // raw
|
||||
uint32_t foffset;
|
||||
std::array<uint16_t, 8> pad2;
|
||||
};
|
||||
|
||||
/// Resource directory
|
||||
struct ResourceDirectory {
|
||||
std::array<uint16_t, 6> 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<uint32_t, 2> 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<typename T>
|
||||
const T* safe_cast(const std::vector<uint8_t>& 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<const T*>(&data.at(offset));
|
||||
}
|
||||
|
||||
/// Safely cast a vector to a span of T
|
||||
template<typename T>
|
||||
std::span<const T> span_cast(const std::vector<uint8_t>& 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<const T>(reinterpret_cast<const T*>(&data.at(offset)), count);
|
||||
}
|
||||
}
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
std::unordered_map<uint32_t, std::vector<uint8_t>> 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<uint8_t> data(static_cast<size_t>(size));
|
||||
if (!file.read(reinterpret_cast<char*>(data.data()), size))
|
||||
throw std::runtime_error("Failed to read Lossless.dll");
|
||||
|
||||
// parse dos header
|
||||
size_t fileOffset = 0;
|
||||
const auto* dosHdr = safe_cast<const DOSHeader>(data, 0);
|
||||
if (dosHdr->magic != 0x5A4D)
|
||||
throw std::runtime_error("Invalid DOS header magic number");
|
||||
|
||||
// parse pe header
|
||||
fileOffset += static_cast<size_t>(dosHdr->pe_offset);
|
||||
const auto* peHdr = safe_cast<const PEHeader>(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<const PEOptionalHeader>(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<size_t> rsrc_offset;
|
||||
fileOffset += peHdr->opt_hdr_size;
|
||||
const auto sectHdrs = span_cast<const SectionHeader>(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<const ResourceDirectory>(data, fileOffset);
|
||||
if (rsrcDir->id_count < 3)
|
||||
throw std::runtime_error("Incorrect resource directory");
|
||||
|
||||
// find resource table with data type
|
||||
std::optional<size_t> rsrc_tbl_offset;
|
||||
fileOffset = rsrc_offset.value() + sizeof(ResourceDirectory);
|
||||
const auto rsrcDirEntries = span_cast<const ResourceDirectoryEntry>(
|
||||
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<const ResourceDirectory>(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<const ResourceDirectoryEntry>(
|
||||
data, fileOffset, rsrcTbl->name_count + rsrcTbl->id_count);
|
||||
std::unordered_map<uint32_t, std::vector<uint8_t>> 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<const ResourceDirectory>(data, fileOffset);
|
||||
if (langDir->id_count < 1)
|
||||
throw std::runtime_error("Incorrect language directory");
|
||||
|
||||
fileOffset += sizeof(ResourceDirectory);
|
||||
const auto* langDirEntry = safe_cast<const ResourceDirectoryEntry>(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<const ResourceDataEntry>(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<uint8_t> 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;
|
||||
}
|
||||
|
|
@ -1,16 +1,14 @@
|
|||
#include "extract/extract.hpp"
|
||||
#include "config/config.hpp"
|
||||
#include "extract/dll.hpp"
|
||||
|
||||
#include <pe-parse/parse.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <unordered_map>
|
||||
#include <filesystem>
|
||||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
|
||||
|
|
@ -73,22 +71,11 @@ const std::unordered_map<std::string, uint32_t> nameIdxTable = {{
|
|||
}};
|
||||
|
||||
namespace {
|
||||
auto& pshaders() {
|
||||
auto& shaders() {
|
||||
static std::unordered_map<uint32_t, std::array<std::vector<uint8_t>, 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<uint8_t> resource_data(res.buf->bufLen);
|
||||
std::copy_n(res.buf->buf, res.buf->bufLen, resource_data.data());
|
||||
|
||||
auto* shaders = reinterpret_cast<std::unordered_map<uint32_t, std::vector<uint8_t>>*>(ptr);
|
||||
shaders->emplace(res.name, std::move(resource_data));
|
||||
return 0;
|
||||
}
|
||||
|
||||
const std::vector<std::filesystem::path> 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<uint32_t, std::vector<uint8_t>> 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<void*>(&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<std::vector<uint8_t>, 2>{
|
||||
std::move(fp32->second),
|
||||
std::move(fp16->second)
|
||||
shaders().emplace(idx, std::array<std::vector<uint8_t>, 2>{
|
||||
fp32->second,
|
||||
fp16->second
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<uint8_t> 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);
|
||||
}
|
||||
|
|
|
|||
1
thirdparty/pe-parse
vendored
1
thirdparty/pe-parse
vendored
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 31ac5966503689d5693cd9fb520bd525a8710e17
|
||||
Loading…
Add table
Reference in a new issue