From 6c5afcce144f272a636529efa9ae028ff7072e1b Mon Sep 17 00:00:00 2001 From: boykopovar Date: Fri, 11 Sep 2026 09:43:40 +0300 Subject: [PATCH] refactor(relinker): separate args parsing and autorun from main --- README.md | 4 +- core/CMakeLists.txt | 3 + core/cli/include/Cli.hpp | 24 +++++++ core/cli/src/Autorun.cpp | 33 +++++++++ core/cli/src/CliArgs.cpp | 56 +++++++++++++++ core/main.cpp | 148 +++++++-------------------------------- 6 files changed, 144 insertions(+), 124 deletions(-) create mode 100644 core/cli/include/Cli.hpp create mode 100644 core/cli/src/Autorun.cpp create mode 100644 core/cli/src/CliArgs.cpp diff --git a/README.md b/README.md index fce6a1078..eb3916e62 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # About -Converter for native execution of PlayStation 5 ELF binaries on Linux through binary format conversion and ABI compatibility. The relinker implementation uses only the C++20 standard library and performs deterministic binary transformation. +Converter for native execution of PlayStation 5 ELF binaries on Linux and Windows through binary format conversion and ABI compatibility. The relinker implementation uses only the C++20 standard library and performs deterministic transformation of executable binary. Implementations of system prx libraries suitable for dynamic linking: [core/libs/prx](core/libs/prx) @@ -13,6 +13,8 @@ Shader initialization via `sceAgcCreate*` passes. Now: `sceVideoOutOpen not implemented`. +On Windows, after printing an unhandled exception the process exits with STATUS_STACK_BUFFER_OVERRUN due to the difficulty of manually [implementing proper exception handling](core/libs/prx/libc/src/exception). + ## Disclaimer This project is intended for interoperability, research, preservation, and compatibility purposes. It does not include, distribute, or require copyrighted software, firmware, cryptographic keys, or proprietary libraries. Users are responsible for ensuring that any binaries used with this project are obtained and used in accordance with applicable laws and their respective license terms. diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index fd4ed8c25..9a51c9e63 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -1,5 +1,7 @@ add_executable(relinker ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/cli/src/CliArgs.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/cli/src/Autorun.cpp ${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/linux/LinuxElfPatcher.cpp ${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsPePatcher.cpp ${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsPeFormat.cpp @@ -43,6 +45,7 @@ add_executable(relinker ${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/pipeline/RelinkerPipeline.cpp ) target_include_directories(relinker PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/cli/include ${CMAKE_CURRENT_SOURCE_DIR}/domain/include ${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/include ${CMAKE_CURRENT_SOURCE_DIR}/io/include diff --git a/core/cli/include/Cli.hpp b/core/cli/include/Cli.hpp new file mode 100644 index 000000000..2d66c3390 --- /dev/null +++ b/core/cli/include/Cli.hpp @@ -0,0 +1,24 @@ +#pragma once +#include +#include + +namespace Cli { + +struct Args { + bool skipSyscallCheck = false; + bool toIntel = false; + bool writeRegistry = false; + bool toWindows = false; + bool lazyBinding = false; + bool autorun = false; + std::uint32_t unusedFilterLevel = 2; + std::string inputPath; + std::string outputPath; + std::string runPath = "$ORIGIN/libs"; +}; + +Args ParseArgs(int argc, char* argv[]); + +void Autorun(const std::string& absPath, bool toWindows); + +} diff --git a/core/cli/src/Autorun.cpp b/core/cli/src/Autorun.cpp new file mode 100644 index 000000000..c0e816545 --- /dev/null +++ b/core/cli/src/Autorun.cpp @@ -0,0 +1,33 @@ +#include +#include +#include +#include +#include +#include + +namespace Cli { + +void Autorun(const std::string& absPath, bool toWindows) { + if (!toWindows) { + std::filesystem::permissions(absPath, + std::filesystem::perms::owner_exec | + std::filesystem::perms::group_exec | + std::filesystem::perms::others_exec, + std::filesystem::perm_options::add); + } + + const std::string cmd = "\"" + absPath + "\""; + const int rawCode = std::system(cmd.c_str()); + + if (toWindows) { + std::cout << "Exit code: " << rawCode << '\n'; + } else { + std::cout << "Raw exit code: " << rawCode << "; Unpacked: " << (rawCode >> 8) << '\n'; + } + + std::cout << "\nPress Enter to exit...\n"; + std::cin.ignore(std::numeric_limits::max(), '\n'); + std::cin.get(); +} + +} diff --git a/core/cli/src/CliArgs.cpp b/core/cli/src/CliArgs.cpp new file mode 100644 index 000000000..9817d2257 --- /dev/null +++ b/core/cli/src/CliArgs.cpp @@ -0,0 +1,56 @@ +#include +#include +#include +#include + +namespace Cli { + +Args ParseArgs(int argc, char* argv[]) { + Args args; + bool unusedFilterSpecified = false; + + for (int i = 1; i < argc; ++i) { + const std::string arg = argv[i]; + if (arg == "--skip-syscall-check") { + args.skipSyscallCheck = true; + } else if (arg == "--to-intel") { + args.toIntel = true; + } else if (arg.rfind("unused-filter=", 0) == 0) { + const std::string value = arg.substr(14); + if (unusedFilterSpecified || value.size() != 1 || value[0] < '0' || value[0] > '2') + throw std::runtime_error("unused-filter must be specified once with a value of 0, 1 or 2"); + args.unusedFilterLevel = static_cast(value[0] - '0'); + unusedFilterSpecified = true; + } else if (arg == "--registry") { + args.writeRegistry = true; + } else if (arg == "--rpath") { + if (i + 1 >= argc) + throw std::runtime_error("--rpath requires a value"); + args.runPath = argv[++i]; + } else if (arg == "--windows") { + args.toWindows = true; + } else if (arg == "--lazy-binding") { + args.lazyBinding = true; + } else if (arg == "--autorun") { + args.autorun = true; + } else if (arg.rfind("--", 0) == 0 || arg == "unused-filter") { + throw std::runtime_error("unknown option: " + arg); + } else if (args.inputPath.empty()) { + args.inputPath = arg; + } else if (args.outputPath.empty()) { + args.outputPath = arg; + } else { + throw std::runtime_error("unexpected argument: " + arg); + } + } + + if (args.inputPath.empty() || args.outputPath.empty()) + throw std::runtime_error( + "Usage: relinker [--windows] [--skip-syscall-check] [--to-intel] [unused-filter=0|1|2] [--registry] [--rpath ] [--lazy-binding] [--autorun] \n" + "Example: relinker input.elf output.elf" + ); + + return args; +} + +} diff --git a/core/main.cpp b/core/main.cpp index 61b11b23c..1fdddcebd 100644 --- a/core/main.cpp +++ b/core/main.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -6,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -20,69 +22,13 @@ #include #include #include -#include -#include -#include -#include int main(const int argc, char* argv[]) { - bool skipSyscallCheck = false; - bool toIntel = false; - std::uint32_t unusedFilterLevel = 2; - bool unusedFilterSpecified = false; - bool writeRegistry = false; - bool toWindows = false; - bool lazyBinding = false; - bool autorun = false; - - std::string inputPath; - std::string outputPath; - std::string runPath = "$ORIGIN/libs"; - - for (int i = 1; i < argc; ++i) { - const std::string arg = argv[i]; - if (arg == "--skip-syscall-check") { - skipSyscallCheck = true; - } else if (arg == "--to-intel") { - toIntel = true; - } else if (arg.rfind("unused-filter=", 0) == 0) { - const std::string value = arg.substr(14); - if (unusedFilterSpecified || value.size() != 1 || value[0] < '0' || value[0] > '2') { - std::cerr << "FAIL: unused-filter must be specified once with a value of 0, 1 or 2\n"; - return 1; - } - unusedFilterLevel = static_cast(value[0] - '0'); - unusedFilterSpecified = true; - } else if (arg == "--registry") { - writeRegistry = true; - } else if (arg == "--rpath") { - if (i + 1 >= argc) { - std::cerr << "FAIL: --rpath requires a value\n"; - return 1; - } - runPath = argv[++i]; - } else if (arg == "--windows") { - toWindows = true; - } else if (arg == "--lazy-binding") { - lazyBinding = true; - } else if (arg == "--autorun") { - autorun = true; - } else if (arg.rfind("--", 0) == 0 || arg == "unused-filter") { - std::cerr << "FAIL: unknown option: " << arg << "\n"; - return 1; - } else if (inputPath.empty()) { - inputPath = arg; - } else if (outputPath.empty()) { - outputPath = arg; - } else { - std::cerr << "FAIL: unexpected argument: " << arg << "\n"; - return 1; - } - } - - if (inputPath.empty() || outputPath.empty()) { - std::cerr << "Usage: relinker [--windows] [--skip-syscall-check] [--to-intel] [unused-filter=0|1|2] [--registry] [--rpath ] [--lazy-binding] [--autorun] \n" - "Example: relinker input.elf output.elf\n"; + Cli::Args args; + try { + args = Cli::ParseArgs(argc, argv); + } catch (const std::exception& e) { + std::cerr << "FAIL: " << e.what() << "\n"; return 1; } @@ -90,50 +36,33 @@ int main(const int argc, char* argv[]) { Io::FileReader fileReader; Io::FileWriter fileWriter; - auto sourceBytes = fileReader.Read(inputPath); + auto sourceBytes = fileReader.Read(args.inputPath); + const std::string absPath = std::filesystem::absolute(args.outputPath).string(); - if (toIntel) { - std::cout << "Mode: Intel instruction conversion; system unchanged; unused-filter=" << unusedFilterLevel << " (not applied)\n"; + if (args.toIntel) { + std::cout << "Mode: Intel instruction conversion; system unchanged; unused-filter=" << args.unusedFilterLevel << " (not applied)\n"; const Relinker::ElfReader elfReader(sourceBytes); const auto converter = Codegen::MakeAmd64OnlyConverter(); auto result = converter->Convert(std::move(sourceBytes), elfReader.ReadCodeSegments()); - fileWriter.Write(outputPath, std::move(result.Bytes)); + fileWriter.Write(absPath, std::move(result.Bytes)); std::cout << "OK: " << result.ReplacedCount << " instructions replaced\n"; - if (autorun) { - if (!toWindows) { - std::filesystem::permissions(outputPath, - std::filesystem::perms::owner_exec | - std::filesystem::perms::group_exec | - std::filesystem::perms::others_exec, - std::filesystem::perm_options::add); - } - const std::string absPath = std::filesystem::absolute(outputPath).string(); - const std::string cmd = "\"" + absPath + "\""; - std::system(cmd.c_str()); - std::cout << "\nPress Enter to exit...\n"; - std::cin.ignore(std::numeric_limits::max(), '\n'); - std::cin.get(); - } + if (args.autorun) Cli::Autorun(absPath, args.toWindows); return 0; } auto elfReader = std::make_shared(sourceBytes); - auto syscallScanner = skipSyscallCheck - ? Relinker::MakeNullSyscallScanner() - : Relinker::MakeSyscallScanner(); - const auto pipeline = std::make_shared( elfReader, - std::move(syscallScanner), + args.skipSyscallCheck ? Relinker::MakeNullSyscallScanner() : Relinker::MakeSyscallScanner(), Relinker::MakeCallSiteResolver(), std::make_shared(), std::make_shared(), - unusedFilterLevel == 2 ? Relinker::MakeStrictUnusedNidFilter() : Relinker::MakeUnusedNidFilter(), - unusedFilterLevel + args.unusedFilterLevel == 2 ? Relinker::MakeStrictUnusedNidFilter() : Relinker::MakeUnusedNidFilter(), + args.unusedFilterLevel ); - std::cout << "System: " << (toWindows ? "Windows" : "Linux") << "; unused-filter=" << unusedFilterLevel << "\n"; + std::cout << "System: " << (args.toWindows ? "Windows" : "Linux") << "; unused-filter=" << args.unusedFilterLevel << "\n"; auto result = pipeline->Relink(sourceBytes); for (const auto& patch : result.Patches) { if (patch.Offset > sourceBytes.size() || patch.Bytes.size() > sourceBytes.size() - patch.Offset) @@ -141,18 +70,16 @@ int main(const int argc, char* argv[]) { for (std::size_t index = 0; index < patch.Bytes.size(); ++index) sourceBytes[patch.Offset + index] = patch.Bytes[index]; } - if (writeRegistry) { - const std::filesystem::path outFsPath(outputPath); + if (args.writeRegistry) { + const std::filesystem::path outFsPath(absPath); const std::string registryPath = (outFsPath.parent_path() / (outFsPath.stem().string() + ".registry.json")).string(); - auto callRegistryWriter = std::make_shared(); - fileWriter.Write(registryPath, callRegistryWriter->WriteCallRegistry(result.RegistryEntries)); + fileWriter.Write(registryPath, std::make_shared()->WriteCallRegistry(result.RegistryEntries)); } auto byteWriter = std::make_shared(); - const std::string absPath = std::filesystem::absolute(outputPath).string(); std::shared_ptr patcher; - if (toWindows) { + if (args.toWindows) { patcher = std::make_shared(); } else { patcher = std::make_shared( @@ -165,40 +92,15 @@ int main(const int argc, char* argv[]) { byteWriter ); } - auto patched = patcher->Patch(sourceBytes, result.OriginalHeaders, result.DynamicSection, result.OriginalPltGotVaddr, runPath, lazyBinding); - fileWriter.Write(absPath, patched); - std::cout << "External prx references: " << result.RegistryEntries.size() - << "\nOutput file: " << absPath << '\n'; - if (autorun) { - if (!toWindows) { - std::filesystem::permissions(outputPath, - std::filesystem::perms::owner_exec | - std::filesystem::perms::group_exec | - std::filesystem::perms::others_exec, - std::filesystem::perm_options::add); - } + fileWriter.Write(absPath, patcher->Patch(sourceBytes, result.OriginalHeaders, result.DynamicSection, result.OriginalPltGotVaddr, args.runPath, args.lazyBinding)); + std::cout << "External prx references: " << result.RegistryEntries.size() << "\nOutput file: " << absPath << '\n'; - const std::string cmd = "\"" + absPath + "\""; - const int rawCode = std::system(cmd.c_str()); - - if (toWindows) { - std::cout << "Exit code: " << rawCode << '\n'; - } - else { - std::cout << "Raw exit code: " << rawCode - << "; Unpacked: " << (rawCode >> 8) << '\n'; - } - std::cout << "\nPress Enter to exit...\n"; - - std::cin.ignore(std::numeric_limits::max(), '\n'); - std::cin.get(); - } + if (args.autorun) 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 << ")"; + if (e.FailureOffset != 0) std::cerr << " (offset 0x" << std::hex << e.FailureOffset << ")"; std::cerr << "\n"; return 2; } catch (const std::exception& e) {