diff --git a/core/relinker/CMakeLists.txt b/core/relinker/CMakeLists.txt index 960edd54a..0e94c5cf4 100644 --- a/core/relinker/CMakeLists.txt +++ b/core/relinker/CMakeLists.txt @@ -198,6 +198,8 @@ if(BUILD_TESTING) find_package(Python3 QUIET COMPONENTS Interpreter) if(Python3_Interpreter_FOUND) + add_test(NAME cli_help COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/cli/tests/test_help.py $) add_test(NAME elf_offsets COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_elf_offsets.py $) add_test(NAME dynamic_segment COMMAND ${Python3_EXECUTABLE} diff --git a/core/relinker/cli/include/Cli.hpp b/core/relinker/cli/include/Cli.hpp index b80e4de9c..db0387434 100644 --- a/core/relinker/cli/include/Cli.hpp +++ b/core/relinker/cli/include/Cli.hpp @@ -8,6 +8,7 @@ namespace Cli { struct Args { + bool showHelp = false; bool skipSyscallCheck = false; bool skipSceModule = false; bool toIntel = false; @@ -27,6 +28,8 @@ struct Args { Args ParseArgs(int argc, char* argv[]); +const char* Usage(); + int Autorun(const std::string& absPath, bool toWindows); } diff --git a/core/relinker/cli/src/CliArgs.cpp b/core/relinker/cli/src/CliArgs.cpp index f354274b9..027ba7cc3 100644 --- a/core/relinker/cli/src/CliArgs.cpp +++ b/core/relinker/cli/src/CliArgs.cpp @@ -5,13 +5,20 @@ namespace Cli { +const char* Usage() { + return "Usage: relinker [--help] [--windows | --macos] [--windows-diagnostics] [--windows-gui] [--skip-syscall-check] [--skip-sce-module] [--exclude-sce-module ]... [--to-intel] [unused-filter=0|1|2] [--registry] [--rpath ] [--lazy-binding] [--autorun] \n" + "Example: relinker input.elf output.elf"; +} + 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") { + if (arg == "--help") { + args.showHelp = true; + } else if (arg == "--skip-syscall-check") { args.skipSyscallCheck = true; } else if (arg == "--skip-sce-module") { args.skipSceModule = true; @@ -67,11 +74,9 @@ Args ParseArgs(int argc, char* argv[]) { if (args.windowsGui && !args.toWindows) throw std::runtime_error("--windows-gui requires --windows"); - if (args.inputPath.empty() || args.outputPath.empty()) - throw std::runtime_error( - "Usage: relinker [--windows | --macos] [--windows-diagnostics] [--windows-gui] [--skip-syscall-check] [--skip-sce-module] [--exclude-sce-module ]... [--to-intel] [unused-filter=0|1|2] [--registry] [--rpath ] [--lazy-binding] [--autorun] \n" - "Example: relinker input.elf output.elf" - ); + if (!args.showHelp && (args.inputPath.empty() || args.outputPath.empty())) + throw std::runtime_error(Usage()); + return args; } diff --git a/core/relinker/cli/tests/test_help.py b/core/relinker/cli/tests/test_help.py new file mode 100644 index 000000000..8c9659aff --- /dev/null +++ b/core/relinker/cli/tests/test_help.py @@ -0,0 +1,44 @@ +from pathlib import Path +import subprocess +import sys +import tempfile + + +def main(): + relinker = Path(sys.argv[1]).resolve() + with tempfile.TemporaryDirectory(prefix="anyps5-help-") as directory: + work = Path(directory) + source = work / "missing.elf" + output = work / "existing.exe" + output.write_bytes(b"preserve this output") + + def run(*args): + return subprocess.run([str(relinker), *args], cwd=work, + capture_output=True, text=True, timeout=10) + + for args in [("--help",), ("--help", "--registry", "--autorun", str(source), str(output)), + (str(source), str(output), "--help")]: + result = run(*args) + assert result.returncode == 0, result.stderr + assert result.stdout.startswith("Usage: relinker "), result.stdout + assert "--help" in result.stdout and "Example:" in result.stdout + assert result.stderr == "", result.stderr + assert output.read_bytes() == b"preserve this output" + assert sorted(path.name for path in work.iterdir()) == [output.name] + + result = run() + assert result.returncode == 1 and "Usage: relinker" in result.stderr + for args in [("--help", "--unknown"), ("--unknown", "--help")]: + result = run(*args) + assert result.returncode == 1 and "unknown option" in result.stderr + result = run("--help", "--windows-gui") + assert result.returncode == 1 and "requires --windows" in result.stderr + for flag in ["--rpath", "--exclude-sce-module"]: + result = run(flag, "--help", str(source), str(output)) + assert result.returncode == 2 and "Cannot open file" in result.stderr + assert result.stdout == "", result.stdout + assert output.read_bytes() == b"preserve this output" + + +if __name__ == "__main__": + main() diff --git a/core/relinker/main.cpp b/core/relinker/main.cpp index 349cde69d..3a3cec579 100644 --- a/core/relinker/main.cpp +++ b/core/relinker/main.cpp @@ -37,6 +37,11 @@ int main(const int argc, char* argv[]) { return 1; } + if (args.showHelp) { + std::cout << Cli::Usage() << '\n'; + return 0; + } + 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'));