#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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(character + ('a' - 'A')); if (!args.toWindows && !args.toMacos && 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); if (std::filesystem::exists(args.outputPath) && std::filesystem::equivalent(args.inputPath, args.outputPath)) throw Domain::RelinkerException("Executable output would overwrite the input executable"); const std::string absPath = std::filesystem::absolute(args.outputPath).string(); std::vector 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 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(sourceBytes); const std::shared_ptr syscallScanner = args.skipSyscallCheck ? Relinker::MakeNullSyscallScanner() : Relinker::MakeSyscallScanner(); const auto pipeline = std::make_shared( elfReader, syscallScanner, Relinker::MakeCallSiteResolver(), std::make_shared(), std::make_shared(), args.unusedFilterLevel == 2 ? Relinker::MakeStrictUnusedNidFilter() : Relinker::MakeUnusedNidFilter(), args.unusedFilterLevel ); std::cout << "System: " << (args.toWindows ? "Windows" : args.toMacos ? "macOS" : "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 guestArtifacts; if (!args.skipSceModule) { guestArtifacts = Relinker::GuestModuleBuilder().Build(args.inputPath, absPath, result.DynamicSection, args.toWindows, args.toMacos, 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(); 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(); std::shared_ptr patcher; if (args.toMacos) { patcher = std::make_shared(); } else if (args.toWindows) { patcher = std::make_shared(args.windowsGui, std::filesystem::path(args.inputPath).parent_path() / "sce_sys" / "icon0.png"); } else { patcher = std::make_shared( std::make_shared(), std::make_shared( std::make_shared(), byteWriter ), std::make_shared(byteWriter), byteWriter ); } std::vector 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 \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; }