mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-18 23:07:07 -05:00
--registry only described the imports of the executable, so a title whose own modules import system functions (PSN.prx, libc.prx and others) could not be audited completely. With --registry the relinker now also writes <output-stem>.<module>.registry.json for every converted guest module. An entry is written for each relocation against an undefined symbol that no other bundled module exports, the same test the module builder uses to decide that a dependency is satisfied by a sibling. Entries carry nid, library and targetOffset; the symbol name no longer has the # suffix by the time the module is read, so the NID is the bare 11 characters. The executable's registry is unchanged. The test converts two modules where one is provided by its sibling, for Linux and Windows output, covers the DT_RELA and DT_JMPREL tables, checks that the same module imports the sibling's function once the sibling is absent, and that nothing is written without --registry. Registered in CTest as guest_registry.
167 lines
9.1 KiB
C++
167 lines
9.1 KiB
C++
#include <Cli.hpp>
|
|
#include <domain/Types.hpp>
|
|
#include <io/FileReader.hpp>
|
|
#include <io/FileWriter.hpp>
|
|
#include <elfpatcher/linux/LinuxElfPatcher.hpp>
|
|
#include <elfpatcher/general/SegmentFilter.hpp>
|
|
#include <elfpatcher/general/EntryStubBuilder.hpp>
|
|
#include <elfpatcher/general/ProgramHeaderLayoutBuilder.hpp>
|
|
#include <elfpatcher/general/SectionHeaderTableBuilder.hpp>
|
|
#include <elfpatcher/windows/WindowsElfPatcher.hpp>
|
|
#include <io/ByteWriter.hpp>
|
|
#include <relinker/parsing/ElfReader.hpp>
|
|
#include <relinker/analysis/ValidationPolicy.hpp>
|
|
#include <relinker/analysis/SyscallScanner.hpp>
|
|
#include <relinker/analysis/CallSiteResolver.hpp>
|
|
#include <relinker/analysis/UnusedNidFilter.hpp>
|
|
#include <relinker/output/SysVDynamicSectionBuilder.hpp>
|
|
#include <relinker/output/CallRegistryWriter.hpp>
|
|
#include <relinker/pipeline/RelinkerPipeline.hpp>
|
|
#include <relinker/guest/GuestImage.hpp>
|
|
#include <codegen/IAmd64OnlyConverter.hpp>
|
|
#include <map>
|
|
#include <codegen/CodegenException.hpp>
|
|
#include <filesystem>
|
|
#include <iostream>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
int main(const int argc, char* argv[]) {
|
|
Cli::Args args;
|
|
try {
|
|
args = Cli::ParseArgs(argc, argv);
|
|
} catch (const std::exception& e) {
|
|
std::cerr << "FAIL: " << e.what() << "\n";
|
|
return 1;
|
|
}
|
|
|
|
try {
|
|
auto extension = std::filesystem::path(args.outputPath).extension().string();
|
|
for (auto& character : extension) if (character >= 'A' && character <= 'Z') character = static_cast<char>(character + ('a' - 'A'));
|
|
if (!args.toWindows && extension == ".exe") std::cerr << "WARNING: Output filename ends with .exe, but --windows was not specified. The output will be a Linux ELF executable.\n";
|
|
Io::FileReader fileReader;
|
|
Io::FileWriter fileWriter;
|
|
|
|
auto sourceBytes = fileReader.Read(args.inputPath);
|
|
const std::string absPath = std::filesystem::absolute(args.outputPath).string();
|
|
|
|
std::vector<Codegen::TrampolineSite> trampolines;
|
|
if (args.toIntel) {
|
|
const auto codeSegments = Relinker::ElfReader(sourceBytes).ReadCodeSegments();
|
|
auto converted = Codegen::MakeAmd64OnlyConverter()->Convert(std::move(sourceBytes), codeSegments);
|
|
sourceBytes = std::move(converted.Bytes);
|
|
trampolines = std::move(converted.Trampolines);
|
|
std::map<std::string, std::size_t> stubsByName;
|
|
for (const auto& report : converted.Reports) {
|
|
if (report.Lowering == Codegen::Amd64OnlyLowering::Kept)
|
|
std::cout << "Intel substitution: " << report.InstructionName << " at 0x" << std::hex << report.Offset << std::dec << " (" << report.OriginalLength << " bytes) kept: no room for a jump\n";
|
|
else if (report.InstructionName == "VRSQRTPS" || report.InstructionName == "VRCPPS")
|
|
++stubsByName[report.InstructionName];
|
|
else
|
|
std::cout << "Intel substitution: " << report.InstructionName << " at 0x" << std::hex << report.Offset << std::dec << " (" << report.OriginalLength << " bytes) -> " << (report.Lowering == Codegen::Amd64OnlyLowering::InPlace ? "in place " : "stub ") << report.ReplacementLength << " bytes\n";
|
|
}
|
|
for (const auto& [name, count] : stubsByName)
|
|
std::cout << "Intel substitution: " << name << " -> stub at " << count << " sites\n";
|
|
std::cout << "Intel conversion: " << converted.ReplacedCount << " in place, " << trampolines.size() << " stubs, " << converted.KeptCount << " kept\n";
|
|
}
|
|
|
|
auto elfReader = std::make_shared<Relinker::ElfReader>(sourceBytes);
|
|
const std::shared_ptr<Relinker::ISyscallScanner> syscallScanner = args.skipSyscallCheck ? Relinker::MakeNullSyscallScanner() : Relinker::MakeSyscallScanner();
|
|
|
|
const auto pipeline = std::make_shared<Relinker::RelinkerPipeline>(
|
|
elfReader,
|
|
syscallScanner,
|
|
Relinker::MakeCallSiteResolver(),
|
|
std::make_shared<Relinker::ValidationPolicy>(),
|
|
std::make_shared<Relinker::SysVDynamicSectionBuilder>(),
|
|
args.unusedFilterLevel == 2 ? Relinker::MakeStrictUnusedNidFilter() : Relinker::MakeUnusedNidFilter(),
|
|
args.unusedFilterLevel
|
|
);
|
|
|
|
std::cout << "System: " << (args.toWindows ? "Windows" : "Linux") << "; unused-filter=" << args.unusedFilterLevel << "\n";
|
|
std::cout << "sce_module/sce_modules/prx processing: " << (args.skipSceModule ? "disabled (--skip-sce-module)" : "enabled") << '\n';
|
|
for (const auto& name : args.excludedSceModules) std::cout << "Guest module excluded: " << name << '\n';
|
|
auto result = pipeline->Relink(sourceBytes);
|
|
for (const auto& patch : result.Patches) {
|
|
if (patch.Offset > sourceBytes.size() || patch.Bytes.size() > sourceBytes.size() - patch.Offset)
|
|
throw Domain::RelinkerException("Relinker patch exceeds source image", patch.Offset);
|
|
for (std::size_t index = 0; index < patch.Bytes.size(); ++index) sourceBytes[patch.Offset + index] = patch.Bytes[index];
|
|
}
|
|
|
|
std::vector<Relinker::GuestArtifact> guestArtifacts;
|
|
if (!args.skipSceModule) {
|
|
guestArtifacts = Relinker::GuestModuleBuilder().Build(args.inputPath, absPath, result.DynamicSection, args.toWindows, args.toIntel, *syscallScanner, args.lazyBinding, args.runPath, args.excludedSceModules);
|
|
}
|
|
|
|
if (args.writeRegistry) {
|
|
const std::filesystem::path outFsPath(absPath);
|
|
const std::string registryPath = (outFsPath.parent_path() / (outFsPath.stem().string() + ".registry.json")).string();
|
|
const auto registryWriter = std::make_shared<Relinker::CallRegistryWriter>();
|
|
fileWriter.Write(registryPath, registryWriter->WriteCallRegistry(result.RegistryEntries));
|
|
for (const auto& artifact : guestArtifacts) {
|
|
const auto modulePath = outFsPath.parent_path() / (outFsPath.stem().string() + "." + artifact.Path.filename().string() + ".registry.json");
|
|
fileWriter.Write(modulePath.string(), registryWriter->WriteModuleImports(artifact.Imports));
|
|
}
|
|
}
|
|
|
|
auto byteWriter = std::make_shared<Io::ByteWriter>();
|
|
|
|
std::shared_ptr<Elfpatcher::IElfPatcher> patcher;
|
|
if (args.toWindows) {
|
|
patcher = std::make_shared<Elfpatcher::Windows::WindowsPePatcher>(args.windowsGui, std::filesystem::path(args.inputPath).parent_path() / "sce_sys" / "icon0.png");
|
|
} else {
|
|
patcher = std::make_shared<Elfpatcher::Linux::LinuxElfPatcher>(
|
|
std::make_shared<Elfpatcher::EntryStubBuilder>(),
|
|
std::make_shared<Elfpatcher::ProgramHeaderLayoutBuilder>(
|
|
std::make_shared<Elfpatcher::SegmentFilter>(),
|
|
byteWriter
|
|
),
|
|
std::make_shared<Elfpatcher::SectionHeaderTableBuilder>(byteWriter),
|
|
byteWriter
|
|
);
|
|
}
|
|
|
|
std::vector<std::uint8_t> executableBytes;
|
|
try {
|
|
executableBytes = patcher->Patch(sourceBytes, result.OriginalHeaders, result.DynamicSection, result.OriginalPltGotVaddr, args.runPath, args.lazyBinding, args.windowsDiagnostics, trampolines);
|
|
} catch (Domain::RelinkerException& error) {
|
|
error.InputPath = args.inputPath;
|
|
throw;
|
|
}
|
|
for (const auto& artifact : guestArtifacts) {
|
|
std::filesystem::create_directories(artifact.Path.parent_path());
|
|
fileWriter.Write(artifact.Path.string(), artifact.Bytes);
|
|
std::cout << "Guest module: " << artifact.Path.string() << '\n';
|
|
}
|
|
fileWriter.Write(absPath, executableBytes);
|
|
std::cout << "External prx references: " << result.RegistryEntries.size() << "\nOutput file: " << absPath << '\n';
|
|
std::cout << "Expected runtime layout (relative to the output executable):\n"
|
|
<< std::filesystem::path(absPath).filename().string() << "\n"
|
|
<< "libs/\n *.prx\napp0/\n <game resources>\n";
|
|
for (const auto& artifact : guestArtifacts)
|
|
std::cout << " " << artifact.Path.lexically_relative(std::filesystem::path(absPath).parent_path() / "app0").generic_string() << '\n';
|
|
std::cout << "Game resources and system libraries must be placed in this layout separately.\n";
|
|
if (args.runPath != "$ORIGIN/libs") std::cout << "Custom library search path (--rpath): " << args.runPath << '\n';
|
|
|
|
if (args.autorun) return Cli::Autorun(absPath, args.toWindows);
|
|
|
|
} catch (const Domain::RelinkerException& e) {
|
|
std::cerr << "FAIL: " << e.what();
|
|
if (e.FailureOffset != 0) std::cerr << " (offset 0x" << std::hex << e.FailureOffset << ")";
|
|
std::cerr << "\n";
|
|
if (!e.InputPath.empty()) std::cerr << "Input: " << e.InputPath << '\n';
|
|
return 2;
|
|
} catch (const Codegen::CodegenException& e) {
|
|
std::cerr << "FAIL: " << e.what();
|
|
if (e.FailureOffset != 0) std::cerr << " (offset 0x" << std::hex << e.FailureOffset << ")";
|
|
std::cerr << "\n";
|
|
return 2;
|
|
} catch (const std::exception& e) {
|
|
std::cerr << "FAIL: " << e.what() << "\n";
|
|
return 2;
|
|
}
|
|
|
|
return 0;
|
|
}
|