AnyPS5/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp

397 lines
27 KiB
C++

#include <relinker/guest/GuestImage.hpp>
#include <elfpatcher/general/GuestModuleWriter.hpp>
#include <codegen/IAmd64OnlyConverter.hpp>
#include <io/FileReader.hpp>
#include <io/NativePath.hpp>
#include <io/BufferUtils.hpp>
#include <algorithm>
#include <fstream>
#include <functional>
#include <iomanip>
#include <iostream>
#include <limits>
#include <map>
#include <set>
#include <sstream>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
namespace Relinker {
namespace {
std::vector<std::string> ReadNeededNames(const Domain::SysVDynamicSection& dynamic) {
if (dynamic.DynamicSegmentData.size() % 16 != 0) throw Domain::RelinkerException("Invalid executable dependency table");
std::vector<std::string> names;
for (std::size_t offset = 0; offset < dynamic.DynamicSegmentData.size(); offset += 16) {
if (Io::ReadU64(dynamic.DynamicSegmentData, offset) != 1) throw Domain::RelinkerException("Unexpected executable dependency tag");
const auto nameOffset = Io::ReadU64(dynamic.DynamicSegmentData, offset + 8);
if (nameOffset >= dynamic.DynStrData.size()) throw Domain::RelinkerException("Invalid dependency string offset");
const auto start = dynamic.DynStrData.begin() + static_cast<std::ptrdiff_t>(nameOffset);
const auto end = std::find(start, dynamic.DynStrData.end(), 0);
if (end == dynamic.DynStrData.end()) throw Domain::RelinkerException("Unterminated dependency string");
names.emplace_back(start, end);
}
return names;
}
std::string FoldFilename(std::string name) {
for (auto& character : name) if (character >= 'A' && character <= 'Z') character = static_cast<char>(character + ('a' - 'A'));
return name;
}
std::string ModuleStem(std::string name, const bool windows) {
if (windows) name = FoldFilename(std::move(name));
for (const std::string_view suffix : {".debug_prx", ".sprx", ".prx"}) {
if (name.size() > suffix.size() && name.ends_with(suffix)) return name.substr(0, name.size() - suffix.size());
}
return {};
}
}
std::vector<GuestArtifact> GuestModuleBuilder::Build(const std::filesystem::path& inputPath, const std::filesystem::path& outputPath, Domain::SysVDynamicSection& dynamic, const bool windows, const bool macos, const bool toIntel, const bool toRosetta, ISyscallScanner& syscallScanner, const bool lazyBinding, const std::string& runPath, const std::set<std::string>& excludedModules, const std::filesystem::path& sceModulePath, const std::vector<std::string>& moduleDirectories) const {
const auto root = std::filesystem::absolute(sceModulePath).lexically_normal();
if (!std::filesystem::exists(root)) throw Domain::RelinkerException("Guest module parent directory does not exist: " + Io::Utf8Path(root));
if (!std::filesystem::is_directory(root)) throw Domain::RelinkerException("Guest module parent path is not a directory: " + Io::Utf8Path(root));
const auto missingModuleMessage = std::string(GuestModulePattern) + " was not found in: " + Io::Utf8Path(root) + ". Use --skip-sce-module to disable guest module processing.";
const auto singular = root / "sce_module";
const auto plural = root / "sce_modules";
const auto prx = root / "prx";
const bool hasSingular = std::filesystem::exists(singular);
const bool hasPlural = std::filesystem::exists(plural);
const bool hasPrx = std::filesystem::exists(prx);
if (hasSingular && hasPlural) throw Domain::RelinkerException("Both sce_module and sce_modules exist in the guest module parent directory");
if (!hasSingular && !hasPlural && !hasPrx) throw Domain::RelinkerException(missingModuleMessage);
std::vector<std::filesystem::path> directories;
if (hasSingular || hasPlural) directories.push_back(hasSingular ? singular : plural);
if (hasPrx) directories.push_back(prx);
std::vector<std::filesystem::path> runtimeDirectories;
for (const auto& name : moduleDirectories) {
const auto relative = Io::NativePath(name).lexically_normal();
if (relative.empty() || relative.has_root_path() || *relative.begin() == "..")
throw Domain::RelinkerException("--module-dir must be a directory inside the guest module parent directory: " + name);
auto directory = (root / relative).lexically_normal();
if (!directory.has_filename()) directory = directory.parent_path();
if (!std::filesystem::is_directory(directory)) throw Domain::RelinkerException("Guest module path is not a directory: " + Io::Utf8Path(directory));
if (std::find(directories.begin(), directories.end(), directory) != directories.end()) throw Domain::RelinkerException("Duplicate guest module directory: " + Io::Utf8Path(directory));
directories.push_back(directory);
runtimeDirectories.push_back(directory);
}
std::vector<std::filesystem::path> paths;
std::set<std::string> unmatchedExclusions = excludedModules;
const auto isElf = [](const std::filesystem::path& path) {
std::ifstream stream(path, std::ios::binary);
if (!stream) throw Domain::RelinkerException("Cannot read guest candidate: " + Io::Utf8Path(path));
char magic[4]{};
stream.read(magic, 4);
if (stream.bad()) throw Domain::RelinkerException("Cannot read guest candidate magic: " + Io::Utf8Path(path));
if (stream.gcount() != 4) return false;
const auto byte = [&](const std::size_t index) { return static_cast<unsigned char>(magic[index]); };
const bool self = (byte(0) == 0x4f && byte(1) == 0x15 && byte(2) == 0x3d && byte(3) == 0x1d) || (byte(0) == 0x54 && byte(1) == 0x14 && byte(2) == 0xf5 && byte(3) == 0xee);
return byte(0) == 0x7f && magic[1] == 'E' && magic[2] == 'L' && magic[3] == 'F';
};
for (const auto& directory : directories) {
if (!std::filesystem::is_directory(directory)) throw Domain::RelinkerException("Guest module path is not a directory: " + Io::Utf8Path(directory));
for (const auto& entry : std::filesystem::directory_iterator(directory)) {
if (Io::Utf8Path(entry.path().filename()).ends_with(GuestModuleSuffix)) continue;
if (excludedModules.contains(Io::Utf8Path(entry.path().filename()))) {
unmatchedExclusions.erase(Io::Utf8Path(entry.path().filename()));
continue;
}
if (!entry.is_regular_file()) continue;
if (isElf(entry.path())) paths.push_back(entry.path());
}
}
const auto neededNames = ReadNeededNames(dynamic);
std::set<std::string> missingNeeded;
std::map<std::string, std::filesystem::path> neededAliases;
for (const auto& name : neededNames) {
if (excludedModules.contains(name)) continue;
if (std::any_of(paths.begin(), paths.end(), [&](const auto& path) { return Io::Utf8Path(path.filename()) == name || (windows && FoldFilename(Io::Utf8Path(path.filename())) == FoldFilename(name)); })) continue;
const auto stem = ModuleStem(name, windows);
const auto alias = stem.empty() ? paths.end() : std::find_if(paths.begin(), paths.end(), [&](const auto& path) { return ModuleStem(Io::Utf8Path(path.filename()), windows) == stem; });
if (alias != paths.end()) neededAliases.emplace(name, *alias);
else missingNeeded.insert(name);
}
Io::FileReader reader;
std::map<std::filesystem::path, GuestImage> discovered;
const auto matchesIdentity = [](const std::string& name, const std::vector<std::string>& identities) {
return std::any_of(identities.begin(), identities.end(), [&](const auto& identity) {
return name == identity || name == identity + ".prx" || name == identity + ".sprx" || name == identity + ".suprx";
});
};
for (const auto& path : paths) discovered.emplace(path, GuestImageReader().Read(path, reader.Read(Io::Utf8Path(path)), windows));
if (!unmatchedExclusions.empty()) throw Domain::RelinkerException("Excluded guest module file not found: " + *unmatchedExclusions.begin());
std::sort(paths.begin(), paths.end());
paths.erase(std::unique(paths.begin(), paths.end()), paths.end());
if (paths.empty()) return {};
if (lazyBinding) throw Domain::RelinkerException("Guest modules require eager binding; --lazy-binding is incompatible");
std::vector<GuestImage> images;
if (std::filesystem::exists(outputPath)) {
for (const auto& path : paths)
if (std::filesystem::equivalent(path, outputPath))
throw Domain::RelinkerException("Executable output would overwrite an input module: " + Io::Utf8Path(outputPath));
}
std::map<std::string, std::vector<std::size_t>> exports;
std::map<std::string, std::set<std::size_t>> sharedExports;
std::set<std::string> outputNames;
for (const auto& path : paths) {
auto image = std::move(discovered.at(path));
if (image.OutputName.find_first_of("$\r\n") != std::string::npos) throw Domain::RelinkerException("Unsupported guest filename: " + image.OutputName);
std::string folded = image.OutputName;
if (windows) {
for (auto& value : folded) {
if (static_cast<unsigned char>(value) >= 128 || value == ':' || value == '$') throw Domain::RelinkerException("Unsupported Windows guest filename: " + image.OutputName);
if (value >= 'A' && value <= 'Z') value = static_cast<char>(value + ('a' - 'A'));
}
}
if (!outputNames.insert(folded).second) throw Domain::RelinkerException("Conflicting guest output filename: " + image.OutputName);
for (const auto& symbol : image.Symbols) {
if (symbol.Section == 0 || symbol.Section == AbsoluteSection || (symbol.Info >> 4) == 0 || symbol.Visibility == 1 || symbol.Visibility == 2) continue;
auto& providers = exports[symbol.Name];
const bool repeated = std::find(providers.begin(), providers.end(), images.size()) != providers.end();
if (!windows && repeated) throw Domain::RelinkerException("Duplicate guest export after stripping #: " + symbol.Name + " in " + Io::Utf8Path(path) + " and " + Io::Utf8Path(images[providers.front()].SourcePath));
if (!repeated) providers.push_back(images.size());
if (!windows && providers.size() > 1) sharedExports[symbol.Name].insert(providers.begin(), providers.end());
}
images.push_back(std::move(image));
}
std::map<std::string, std::size_t> guestNames;
std::map<std::string, std::size_t> windowsGuestFiles;
for (std::size_t index = 0; index < images.size(); ++index) {
for (const auto& name : {Io::Utf8Path(images[index].SourcePath.filename()), images[index].Soname}) {
if (name.empty()) continue;
const auto [found, inserted] = guestNames.emplace(name, index);
if (!inserted && found->second != index) throw Domain::RelinkerException("Ambiguous guest dependency name: " + name);
}
if (windows) windowsGuestFiles.emplace(FoldFilename(Io::Utf8Path(images[index].SourcePath.filename())), index);
}
for (const auto& [name, path] : neededAliases) {
if (guestNames.contains(name)) continue;
const auto image = std::find_if(images.begin(), images.end(), [&](const auto& candidate) { return candidate.SourcePath == path; });
if (image == images.end()) throw Domain::RelinkerException("Needed module alias has no guest image: " + name);
const auto index = static_cast<std::size_t>(image - images.begin());
const auto [found, inserted] = guestNames.emplace(name, index);
if (!inserted && found->second != index) throw Domain::RelinkerException("Ambiguous guest dependency name: " + name);
}
const auto findGuest = [&](const std::string& name) {
const auto exact = guestNames.find(name);
if (exact != guestNames.end() || !windows) return exact;
const auto file = windowsGuestFiles.find(FoldFilename(name));
return file == windowsGuestFiles.end() ? guestNames.end() : guestNames.emplace(name, file->second).first;
};
const auto resolveIdentity = [&](const std::string& name) {
if (findGuest(name) != guestNames.end()) return;
std::size_t match = images.size();
for (std::size_t index = 0; index < images.size(); ++index) {
const auto& identities = images[index].ModuleNames;
if (!matchesIdentity(name, identities)) continue;
if (match != images.size()) throw Domain::RelinkerException("Ambiguous guest module identity: " + name);
match = index;
}
if (match != images.size()) guestNames.emplace(name, match);
};
for (const auto& name : missingNeeded) resolveIdentity(name);
for (const auto& image : images) for (const auto& name : image.Dependencies) resolveIdentity(name);
const auto rejectSharedImport = [&](const std::string& name, const std::string& importer) {
const auto shared = sharedExports.find(name);
if (shared == sharedExports.end()) return;
std::string providers;
for (const auto provider : shared->second) providers += " " + Io::Utf8Path(images[provider].SourcePath);
throw Domain::RelinkerException("Ambiguous guest import " + name + " in " + importer + ": exported by" + providers);
};
const auto rename = [](std::vector<std::uint8_t>& symbols, std::vector<std::uint8_t>& strings, std::size_t index, const std::string& name) {
if (strings.size() > std::numeric_limits<std::uint32_t>::max()) throw Domain::RelinkerException("Guest string table too large");
Io::WriteU32(symbols, index * 24, static_cast<std::uint32_t>(strings.size()));
Io::AppendString(strings, name + GuestSymbolSuffix);
};
const auto declaresHost = [&](const std::string& library) { return !windows && !library.empty() && findGuest(library) == guestNames.end(); };
if (dynamic.DynSymData.size() % 24 != 0) throw Domain::RelinkerException("Invalid executable symbol table");
std::vector<std::string> executableModules(dynamic.DynSymData.size() / 24);
for (const auto* table : {&dynamic.RelaData, &dynamic.RelaPltData}) {
if (table->size() % 24 != 0) throw Domain::RelinkerException("Invalid executable relocation table");
for (std::size_t offset = 0; offset < table->size(); offset += 24) {
const auto symbol = Io::ReadU64(*table, offset + 8) >> 32;
const auto module = dynamic.ImportModules.find(Io::ReadU64(*table, offset));
if (symbol != 0 && symbol < executableModules.size() && module != dynamic.ImportModules.end()) executableModules[symbol] = module->second;
}
}
for (std::size_t offset = 0; offset < dynamic.DynSymData.size(); offset += 24) {
if (Io::ReadU16(dynamic.DynSymData, offset + 6) != 0) continue;
const auto nameOffset = Io::ReadU32(dynamic.DynSymData, offset);
if (nameOffset >= dynamic.DynStrData.size()) throw Domain::RelinkerException("Invalid executable symbol name offset");
const auto start = dynamic.DynStrData.begin() + nameOffset;
const auto end = std::find(start, dynamic.DynStrData.end(), 0);
if (end == dynamic.DynStrData.end()) throw Domain::RelinkerException("Unterminated executable symbol name");
const std::string name(start, end);
if (declaresHost(executableModules[offset / 24])) continue;
const std::string base = name.substr(0, name.find('#'));
const auto shared = sharedExports.find(base);
std::size_t needed = 0;
if (shared != sharedExports.end()) {
for (const auto provider : shared->second) {
const auto& file = images[provider].SourcePath.filename().string();
const auto& soname = images[provider].Soname;
if (std::find(neededNames.begin(), neededNames.end(), file) == neededNames.end() &&
(soname.empty() || std::find(neededNames.begin(), neededNames.end(), soname) == neededNames.end())) continue;
if (++needed > 1) break;
}
}
if (needed != 1) rejectSharedImport(base, Io::Utf8Path(inputPath));
if (!windows && exports.contains(name)) rename(dynamic.DynSymData, dynamic.DynStrData, offset / 24, name);
}
std::vector<std::set<std::size_t>> dependencies(images.size());
std::vector<std::set<std::size_t>> systemImports(images.size());
for (auto& image : images) {
image.UsePlatformTlsResolver = !windows ? !exports.contains("vNe1w4diLCs") : std::none_of(image.Symbols.begin(), image.Symbols.end(), [](const auto& symbol) {
return symbol.Name == "vNe1w4diLCs" && symbol.Section != 0 && symbol.Section != AbsoluteSection && (symbol.Info >> 4) != 0 && symbol.Visibility != 1 && symbol.Visibility != 2;
});
}
for (std::size_t index = 0; index < images.size(); ++index) {
for (const auto& name : images[index].Dependencies) {
const auto found = findGuest(name);
if (found != guestNames.end() && found->second != index) dependencies[index].insert(found->second);
}
for (std::size_t symbolIndex = 0; symbolIndex < images[index].Symbols.size(); ++symbolIndex) {
const auto& symbol = images[index].Symbols[symbolIndex];
if (symbol.Section != 0 || symbol.Name.empty() || declaresHost(symbol.Library)) continue;
const auto declared = symbol.Library.empty() ? guestNames.end() : findGuest(symbol.Library);
const auto shared = sharedExports.find(symbol.Name);
if (shared == sharedExports.end() || declared == guestNames.end() || !shared->second.contains(declared->second))
rejectSharedImport(symbol.Name, Io::Utf8Path(images[index].SourcePath));
const auto found = exports.find(symbol.Name);
std::vector<std::size_t> providers;
if (found != exports.end()) {
for (const auto provider : found->second) {
if (symbol.Library.empty() || (declared != guestNames.end() && declared->second == provider)) providers.push_back(provider);
}
}
if (providers.size() > 1) throw Domain::RelinkerException("Ambiguous guest import after stripping #: " + symbol.Name);
if (!providers.empty()) {
const auto& provider = images[providers.front()];
const auto exported = std::find_if(provider.Symbols.begin(), provider.Symbols.end(), [&](const auto& candidate) { return candidate.Section != 0 && candidate.Section != AbsoluteSection && candidate.Name == symbol.Name && (candidate.Info >> 4) != 0 && candidate.Visibility != 1 && candidate.Visibility != 2; });
if (exported == provider.Symbols.end() || ((symbol.Info & 15) != 0 && (symbol.Info & 15) != (exported->Info & 15))) throw Domain::RelinkerException("Guest import/export type mismatch: " + symbol.Name);
if (providers.front() != index) dependencies[index].insert(providers.front());
} else if (windows && (symbol.Info & 15) == 6) throw Domain::RelinkerException("Windows guest TLS import requires a guest TLS export: " + symbol.Name);
if (providers.empty()) systemImports[index].insert(symbolIndex);
}
}
std::vector<bool> required(images.size());
for (const auto& name : neededNames) {
const auto found = findGuest(name);
if (found != guestNames.end()) required[found->second] = true;
}
for (const auto& needed : dependencies) for (const auto dependency : needed) required[dependency] = true;
const auto deferred = [&](const std::size_t index) {
return !required[index] && std::find(runtimeDirectories.begin(), runtimeDirectories.end(), images[index].SourcePath.parent_path().lexically_normal()) != runtimeDirectories.end();
};
if (!windows) {
for (auto& image : images) {
for (std::size_t index = 1; index < image.Symbols.size(); ++index) {
const auto& symbol = image.Symbols[index];
const bool exported = symbol.Section != 0 && (symbol.Info >> 4) != 0 && symbol.Visibility != 1 && symbol.Visibility != 2;
if (((symbol.Section == 0 && !declaresHost(symbol.Library)) || exported) && exports.contains(symbol.Name)) rename(image.Dynamic.DynSymData, image.Dynamic.DynStrData, index, symbol.Name);
}
}
}
std::vector<std::size_t> order;
std::vector<unsigned char> states(images.size());
const std::function<void(std::size_t)> visit = [&](std::size_t index) {
if (states[index] == 1) throw Domain::RelinkerException("Cyclic guest initialization dependency: " + Io::Utf8Path(images[index].SourcePath));
if (states[index] == 2) return;
states[index] = 1;
for (const auto dependency : dependencies[index]) visit(dependency);
states[index] = 2;
order.push_back(index);
};
for (std::size_t index = 0; index < images.size(); ++index) visit(index);
std::vector<std::string> hostLibraries;
std::set<std::string> uniqueHosts;
const auto addHost = [&](const std::string& name) {
if (findGuest(name) != guestNames.end()) return;
if (name.empty() || name.find_first_of("/\\:$") != std::string::npos) throw Domain::RelinkerException("Invalid host dependency: " + name);
if (uniqueHosts.insert(name).second) hostLibraries.push_back(name);
};
for (const auto& name : neededNames) addHost(name);
for (const auto& image : images) for (const auto& dependency : image.Dependencies) addHost(dependency);
if (uniqueHosts.contains("libSceLibcInternal.prx") && uniqueHosts.insert("libc.prx").second) hostLibraries.push_back("libc.prx");
dynamic.DynamicSegmentData.clear();
const auto addNeeded = [&](const std::string& name) {
Io::AppendU64(dynamic.DynamicSegmentData, 1);
Io::AppendU64(dynamic.DynamicSegmentData, dynamic.DynStrData.size());
Io::AppendString(dynamic.DynStrData, name);
};
for (const auto index : order) if (!windows && !deferred(index)) addNeeded("$ORIGIN/app0/" + images[index].SourcePath.parent_path().lexically_relative(root).generic_string() + "/" + images[index].OutputName);
for (const auto& name : hostLibraries) addNeeded(name);
if (!windows && runPath != "$ORIGIN" && !runPath.starts_with("$ORIGIN/") && !std::filesystem::path(runPath).is_absolute()) throw Domain::RelinkerException("Guest Linux run path must be absolute or begin with $ORIGIN");
const auto outputDirectory = std::filesystem::absolute(outputPath).parent_path().lexically_normal();
std::vector<GuestArtifact> artifacts;
for (const auto index : order) {
auto& image = images[index];
std::ostringstream moduleSize;
moduleSize << std::fixed << std::setprecision(2) << static_cast<double>(image.Bytes.size()) / 1000000.0;
std::cout << "Processing " << image.SourcePath.lexically_relative(root).generic_string() << " (" << moduleSize.str() << "MB)" << std::flush;
std::vector<Domain::ProgramHeader> codeHeaders;
for (const auto& header : image.Headers) if (header.Type == 1 && (header.Flags & 1) != 0) codeHeaders.push_back(header);
if (toIntel) {
auto converted = Codegen::MakeAmd64OnlyConverter(toRosetta ? Codegen::Amd64OnlyTarget::Rosetta : Codegen::Amd64OnlyTarget::Intel)->Convert(std::move(image.Bytes), codeHeaders);
image.Trampolines = std::move(converted.Trampolines);
image.Bytes = std::move(converted.Bytes);
}
for (const auto& header : codeHeaders) {
const std::vector<std::uint8_t> code(image.Bytes.begin() + header.Offset, image.Bytes.begin() + header.Offset + header.FileSize);
syscallScanner.ScanCodeSectionForSyscalls(code, header.MappedAddress, header.FileSize);
}
const auto relativeDirectory = "app0/" + image.SourcePath.parent_path().lexically_relative(root).generic_string();
const auto destination = outputDirectory / relativeDirectory;
const auto target = destination / image.OutputName;
if (target.lexically_normal() == std::filesystem::absolute(outputPath).lexically_normal()) throw Domain::RelinkerException("Guest output collides with the executable output");
for (const auto& source : paths) if (std::filesystem::exists(target) && std::filesystem::equivalent(source, target)) throw Domain::RelinkerException("Guest output would overwrite an input module: " + Io::Utf8Path(target));
if (std::filesystem::exists(target) && std::filesystem::equivalent(inputPath, target)) throw Domain::RelinkerException("Guest output would overwrite the input executable");
Domain::GuestRuntime runtime;
runtime.UsePlatformTlsResolver = image.UsePlatformTlsResolver;
runtime.Path = relativeDirectory + "/" + image.OutputName;
runtime.Names = {Io::Utf8Path(image.SourcePath.filename()), image.Soname};
runtime.DeferInitialization = deferred(index);
std::vector<std::uint8_t> output;
if (windows) output = Elfpatcher::GuestModuleWriter().WriteWindows(image, runtime);
else {
std::string guestRunPath = runPath;
if (runPath == "$ORIGIN" || runPath.starts_with("$ORIGIN/")) {
const auto relativeRoot = outputDirectory.lexically_relative(destination.lexically_normal()).generic_string();
guestRunPath = "$ORIGIN/" + relativeRoot + runPath.substr(7);
}
std::vector<std::string> needed;
for (const auto dependency : dependencies[index]) {
const auto dependencyPath = images[dependency].SourcePath.parent_path() / images[dependency].OutputName;
needed.push_back("$ORIGIN/" + dependencyPath.lexically_relative(image.SourcePath.parent_path()).generic_string());
}
needed.insert(needed.end(), hostLibraries.begin(), hostLibraries.end());
output = macos ? Elfpatcher::GuestModuleWriter().WriteMacOs(image, needed, guestRunPath) : Elfpatcher::GuestModuleWriter().WriteLinux(image, needed, guestRunPath);
}
if (windows) {
for (const auto& [name, provider] : guestNames) {
if (provider == index && std::find(runtime.Names.begin(), runtime.Names.end(), name) == runtime.Names.end()) runtime.Names.push_back(name);
}
}
std::vector<Domain::CallRegistryEntry> imports;
for (const auto* table : {&image.Dynamic.RelaData, &image.Dynamic.RelaPltData}) {
for (std::size_t position = 0; position < table->size(); position += 24) {
const auto symbolIndex = static_cast<std::size_t>(Io::ReadU64(*table, position + 8) >> 32);
if (!systemImports[index].contains(symbolIndex)) continue;
const auto& symbol = image.Symbols[symbolIndex];
imports.push_back({symbol.Name, symbol.Library, {}, 0, {}, Io::ReadU64(*table, position), {}, false});
}
}
dynamic.GuestModules.push_back(std::move(runtime));
artifacts.push_back({target, std::move(output), std::move(imports), image.SourcePath});
std::cout << " [DONE]" << std::endl;
}
return artifacts;
}
}