mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2026-01-31 00:55:19 +01:00
added new cli parser using CLI11 (#3950)
* added new cli parser using CLI11 * pff * fixed repo * fix game autodetection * clear unessecary comments * added a check * fixed? * parse extras * one more try * readded -g * fixed ignore_game_patches flag * some rewrite improvements
This commit is contained in:
4
.gitmodules
vendored
4
.gitmodules
vendored
@@ -123,3 +123,7 @@
|
||||
[submodule "externals/aacdec/fdk-aac"]
|
||||
path = externals/aacdec/fdk-aac
|
||||
url = https://android.googlesource.com/platform/external/aac
|
||||
[submodule "externals/ext-CLI11"]
|
||||
path = externals/ext-CLI11
|
||||
url = https://github.com/shadexternals/ext-CLI11.git
|
||||
branch = main
|
||||
|
||||
@@ -1097,7 +1097,7 @@ create_target_directory_groups(shadps4)
|
||||
|
||||
target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak::xbyak Tracy::TracyClient RenderDoc::API FFmpeg::ffmpeg Dear_ImGui gcn half::half ZLIB::ZLIB PNG::PNG)
|
||||
target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator LibAtrac9 sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::glslang SDL3::SDL3 SDL3_mixer::SDL3_mixer pugixml::pugixml)
|
||||
target_link_libraries(shadps4 PRIVATE stb::headers libusb::usb lfreist-hwinfo::hwinfo nlohmann_json::nlohmann_json miniz fdk-aac)
|
||||
target_link_libraries(shadps4 PRIVATE stb::headers libusb::usb lfreist-hwinfo::hwinfo nlohmann_json::nlohmann_json miniz fdk-aac CLI11::CLI11)
|
||||
|
||||
target_compile_definitions(shadps4 PRIVATE IMGUI_USER_CONFIG="imgui/imgui_config.h")
|
||||
target_compile_definitions(Dear_ImGui PRIVATE IMGUI_USER_CONFIG="${PROJECT_SOURCE_DIR}/src/imgui/imgui_config.h")
|
||||
|
||||
8
externals/CMakeLists.txt
vendored
8
externals/CMakeLists.txt
vendored
@@ -1,4 +1,4 @@
|
||||
# SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
# SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
set(BUILD_SHARED_LIBS OFF)
|
||||
@@ -268,3 +268,9 @@ add_subdirectory(json)
|
||||
|
||||
# miniz
|
||||
add_subdirectory(miniz)
|
||||
|
||||
# cli11
|
||||
set(CLI11_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
||||
set(CLI11_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
|
||||
|
||||
add_subdirectory(ext-CLI11)
|
||||
1
externals/ext-CLI11
vendored
Submodule
1
externals/ext-CLI11
vendored
Submodule
Submodule externals/ext-CLI11 added at 1cce148334
378
src/main.cpp
378
src/main.cpp
@@ -1,16 +1,17 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
#include <CLI/CLI.hpp>
|
||||
#include <SDL3/SDL_messagebox.h>
|
||||
#include "functional"
|
||||
#include "iostream"
|
||||
#include "string"
|
||||
#include "system_error"
|
||||
#include "unordered_map"
|
||||
|
||||
#include <core/emulator_state.h>
|
||||
#include <fmt/core.h>
|
||||
#include "common/config.h"
|
||||
#include "common/key_manager.h"
|
||||
#include "common/logging/backend.h"
|
||||
#include "common/memory_patcher.h"
|
||||
#include "common/path_util.h"
|
||||
@@ -22,265 +23,176 @@
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#endif
|
||||
#include <common/key_manager.h>
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
#ifdef _WIN32
|
||||
SetConsoleOutputCP(CP_UTF8);
|
||||
#endif
|
||||
|
||||
IPC::Instance().Init();
|
||||
// Init emulator state
|
||||
std::shared_ptr<EmulatorState> m_emu_state = std::make_shared<EmulatorState>();
|
||||
EmulatorState::SetInstance(m_emu_state);
|
||||
// Load configurations
|
||||
|
||||
auto emu_state = std::make_shared<EmulatorState>();
|
||||
EmulatorState::SetInstance(emu_state);
|
||||
|
||||
const auto user_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);
|
||||
Config::load(user_dir / "config.toml");
|
||||
// temp copy the trophy key from old config to key manager if exists
|
||||
|
||||
// ---- Trophy key migration ----
|
||||
auto key_manager = KeyManager::GetInstance();
|
||||
if (key_manager->GetAllKeys().TrophyKeySet.ReleaseTrophyKey.empty()) {
|
||||
if (!Config::getTrophyKey().empty()) {
|
||||
|
||||
key_manager->SetAllKeys(
|
||||
{.TrophyKeySet = {.ReleaseTrophyKey =
|
||||
KeyManager::HexStringToBytes(Config::getTrophyKey())}});
|
||||
key_manager->SaveToFile();
|
||||
}
|
||||
if (key_manager->GetAllKeys().TrophyKeySet.ReleaseTrophyKey.empty() &&
|
||||
!Config::getTrophyKey().empty()) {
|
||||
key_manager->SetAllKeys({.TrophyKeySet = {.ReleaseTrophyKey = KeyManager::HexStringToBytes(
|
||||
Config::getTrophyKey())}});
|
||||
key_manager->SaveToFile();
|
||||
}
|
||||
bool has_game_argument = false;
|
||||
std::string game_path;
|
||||
std::vector<std::string> game_args{};
|
||||
std::optional<std::filesystem::path> game_folder;
|
||||
|
||||
bool waitForDebugger = false;
|
||||
CLI::App app{"shadPS4 Emulator CLI"};
|
||||
|
||||
// ---- CLI state ----
|
||||
std::optional<std::string> gamePath;
|
||||
std::vector<std::string> gameArgs;
|
||||
std::optional<std::filesystem::path> overrideRoot;
|
||||
std::optional<int> waitPid;
|
||||
bool waitForDebugger = false;
|
||||
|
||||
// Map of argument strings to lambda functions
|
||||
std::unordered_map<std::string, std::function<void(int&)>> arg_map = {
|
||||
{"-h",
|
||||
[&](int&) {
|
||||
std::cout
|
||||
<< "Usage: shadps4 [options] <elf or eboot.bin path>\n"
|
||||
"Options:\n"
|
||||
" -g, --game <path|ID> Specify game path to launch\n"
|
||||
" -- ... Parameters passed to the game ELF. "
|
||||
"Needs to be at the end of the line, and everything after \"--\" is a "
|
||||
"game argument.\n"
|
||||
" -p, --patch <patch_file> Apply specified patch file\n"
|
||||
" -i, --ignore-game-patch Disable automatic loading of game patch\n"
|
||||
" -f, --fullscreen <true|false> Specify window initial fullscreen "
|
||||
"state. Does not overwrite the config file.\n"
|
||||
" --add-game-folder <folder> Adds a new game folder to the config.\n"
|
||||
" --set-addon-folder <folder> Sets the addon folder to the config.\n"
|
||||
" --log-append Append log output to file instead of "
|
||||
"overwriting it.\n"
|
||||
" --override-root <folder> Override the game root folder. Default is the "
|
||||
"parent of game path\n"
|
||||
" --wait-for-debugger Wait for debugger to attach\n"
|
||||
" --wait-for-pid <pid> Wait for process with specified PID to stop\n"
|
||||
" --config-clean Run the emulator with the default config "
|
||||
"values, ignores the config file(s) entirely.\n"
|
||||
" --config-global Run the emulator with the base config file "
|
||||
"only, ignores game specific configs.\n"
|
||||
" --show-fps Enable FPS counter display at startup\n"
|
||||
" -h, --help Display this help message\n";
|
||||
exit(0);
|
||||
}},
|
||||
{"--help", [&](int& i) { arg_map["-h"](i); }},
|
||||
std::optional<std::string> fullscreenStr;
|
||||
bool ignoreGamePatch = false;
|
||||
bool showFps = false;
|
||||
bool configClean = false;
|
||||
bool configGlobal = false;
|
||||
bool logAppend = false;
|
||||
|
||||
{"-g",
|
||||
[&](int& i) {
|
||||
if (i + 1 < argc) {
|
||||
game_path = argv[++i];
|
||||
has_game_argument = true;
|
||||
} else {
|
||||
std::cerr << "Error: Missing argument for -g/--game\n";
|
||||
exit(1);
|
||||
}
|
||||
}},
|
||||
{"--game", [&](int& i) { arg_map["-g"](i); }},
|
||||
std::optional<std::filesystem::path> addGameFolder;
|
||||
std::optional<std::filesystem::path> setAddonFolder;
|
||||
std::optional<std::string> patchFile;
|
||||
|
||||
{"-p",
|
||||
[&](int& i) {
|
||||
if (i + 1 < argc) {
|
||||
MemoryPatcher::patch_file = argv[++i];
|
||||
} else {
|
||||
std::cerr << "Error: Missing argument for -p/--patch\n";
|
||||
exit(1);
|
||||
}
|
||||
}},
|
||||
{"--patch", [&](int& i) { arg_map["-p"](i); }},
|
||||
// ---- Options ----
|
||||
app.add_option("-g,--game", gamePath, "Game path or ID");
|
||||
app.add_option("-p,--patch", patchFile, "Patch file to apply");
|
||||
app.add_flag("-i,--ignore-game-patch", ignoreGamePatch,
|
||||
"Disable automatic loading of game patches");
|
||||
|
||||
{"-i", [&](int&) { Core::FileSys::MntPoints::ignore_game_patches = true; }},
|
||||
{"--ignore-game-patch", [&](int& i) { arg_map["-i"](i); }},
|
||||
{"-f",
|
||||
[&](int& i) {
|
||||
if (++i >= argc) {
|
||||
std::cerr << "Error: Missing argument for -f/--fullscreen\n";
|
||||
exit(1);
|
||||
}
|
||||
std::string f_param(argv[i]);
|
||||
bool is_fullscreen;
|
||||
if (f_param == "true") {
|
||||
is_fullscreen = true;
|
||||
} else if (f_param == "false") {
|
||||
is_fullscreen = false;
|
||||
} else {
|
||||
std::cerr
|
||||
<< "Error: Invalid argument for -f/--fullscreen. Use 'true' or 'false'.\n";
|
||||
exit(1);
|
||||
}
|
||||
// Set fullscreen mode without saving it to config file
|
||||
Config::setIsFullscreen(is_fullscreen);
|
||||
}},
|
||||
{"--fullscreen", [&](int& i) { arg_map["-f"](i); }},
|
||||
{"--add-game-folder",
|
||||
[&](int& i) {
|
||||
if (++i >= argc) {
|
||||
std::cerr << "Error: Missing argument for --add-game-folder\n";
|
||||
exit(1);
|
||||
}
|
||||
std::string config_dir(argv[i]);
|
||||
std::filesystem::path config_path = std::filesystem::path(config_dir);
|
||||
std::error_code discard;
|
||||
if (!std::filesystem::exists(config_path, discard)) {
|
||||
std::cerr << "Error: File does not exist: " << config_path << "\n";
|
||||
exit(1);
|
||||
}
|
||||
// FULLSCREEN: behavior-identical
|
||||
app.add_option("-f,--fullscreen", fullscreenStr, "Fullscreen mode (true|false)");
|
||||
|
||||
Config::addGameInstallDir(config_path);
|
||||
Config::save(Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "config.toml");
|
||||
std::cout << "Game folder successfully saved.\n";
|
||||
exit(0);
|
||||
}},
|
||||
{"--set-addon-folder",
|
||||
[&](int& i) {
|
||||
if (++i >= argc) {
|
||||
std::cerr << "Error: Missing argument for --add-addon-folder\n";
|
||||
exit(1);
|
||||
}
|
||||
std::string config_dir(argv[i]);
|
||||
std::filesystem::path config_path = std::filesystem::path(config_dir);
|
||||
std::error_code discard;
|
||||
if (!std::filesystem::exists(config_path, discard)) {
|
||||
std::cerr << "Error: File does not exist: " << config_path << "\n";
|
||||
exit(1);
|
||||
}
|
||||
app.add_option("--override-root", overrideRoot)->check(CLI::ExistingDirectory);
|
||||
|
||||
Config::setAddonInstallDir(config_path);
|
||||
Config::save(Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "config.toml");
|
||||
std::cout << "Addon folder successfully saved.\n";
|
||||
exit(0);
|
||||
}},
|
||||
{"--log-append", [&](int& i) { Common::Log::SetAppend(); }},
|
||||
{"--config-clean", [&](int& i) { Config::setConfigMode(Config::ConfigMode::Clean); }},
|
||||
{"--config-global", [&](int& i) { Config::setConfigMode(Config::ConfigMode::Global); }},
|
||||
{"--override-root",
|
||||
[&](int& i) {
|
||||
if (++i >= argc) {
|
||||
std::cerr << "Error: Missing argument for --override-root\n";
|
||||
exit(1);
|
||||
}
|
||||
std::string folder_str{argv[i]};
|
||||
std::filesystem::path folder{folder_str};
|
||||
if (!std::filesystem::exists(folder) || !std::filesystem::is_directory(folder)) {
|
||||
std::cerr << "Error: Folder does not exist: " << folder_str << "\n";
|
||||
exit(1);
|
||||
}
|
||||
game_folder = folder;
|
||||
}},
|
||||
{"--wait-for-debugger", [&](int& i) { waitForDebugger = true; }},
|
||||
{"--wait-for-pid",
|
||||
[&](int& i) {
|
||||
if (++i >= argc) {
|
||||
std::cerr << "Error: Missing argument for --wait-for-pid\n";
|
||||
exit(1);
|
||||
}
|
||||
waitPid = std::stoi(argv[i]);
|
||||
}},
|
||||
{"--show-fps", [&](int& i) { Config::setShowFpsCounter(true); }}};
|
||||
app.add_flag("--wait-for-debugger", waitForDebugger);
|
||||
app.add_option("--wait-for-pid", waitPid);
|
||||
|
||||
app.add_flag("--show-fps", showFps);
|
||||
app.add_flag("--config-clean", configClean);
|
||||
app.add_flag("--config-global", configGlobal);
|
||||
app.add_flag("--log-append", logAppend);
|
||||
|
||||
app.add_option("--add-game-folder", addGameFolder)->check(CLI::ExistingDirectory);
|
||||
app.add_option("--set-addon-folder", setAddonFolder)->check(CLI::ExistingDirectory);
|
||||
|
||||
// ---- Capture args after `--` verbatim ----
|
||||
app.allow_extras();
|
||||
app.parse_complete_callback([&]() {
|
||||
const auto& extras = app.remaining();
|
||||
if (!extras.empty()) {
|
||||
gameArgs = extras;
|
||||
}
|
||||
});
|
||||
|
||||
// ---- No-args behavior ----
|
||||
if (argc == 1) {
|
||||
if (!SDL_ShowSimpleMessageBox(
|
||||
SDL_MESSAGEBOX_INFORMATION, "shadPS4",
|
||||
"This is a CLI application. Please use the QTLauncher for a GUI: "
|
||||
"https://github.com/shadps4-emu/shadps4-qtlauncher/releases",
|
||||
nullptr))
|
||||
std::cerr << "Could not display SDL message box! Error: " << SDL_GetError() << "\n";
|
||||
int dummy = 0; // one does not simply pass 0 directly
|
||||
arg_map.at("-h")(dummy);
|
||||
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_INFORMATION, "shadPS4",
|
||||
"This is a CLI application. Please use the QTLauncher for a GUI:\n"
|
||||
"https://github.com/shadps4-emu/shadps4-qtlauncher/releases",
|
||||
nullptr);
|
||||
std::cout << app.help();
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Parse command-line arguments using the map
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
std::string cur_arg = argv[i];
|
||||
auto it = arg_map.find(cur_arg);
|
||||
if (it != arg_map.end()) {
|
||||
it->second(i); // Call the associated lambda function
|
||||
} else if (i == argc - 1 && !has_game_argument) {
|
||||
// Assume the last argument is the game file if not specified via -g/--game
|
||||
game_path = argv[i];
|
||||
has_game_argument = true;
|
||||
} else if (std::string(argv[i]) == "--") {
|
||||
if (i + 1 == argc) {
|
||||
std::cerr << "Warning: -- is set, but no game arguments are added!\n";
|
||||
break;
|
||||
}
|
||||
for (int j = i + 1; j < argc; j++) {
|
||||
game_args.push_back(argv[j]);
|
||||
}
|
||||
break;
|
||||
} else if (i + 1 < argc && std::string(argv[i + 1]) == "--") {
|
||||
if (!has_game_argument) {
|
||||
game_path = argv[i];
|
||||
has_game_argument = true;
|
||||
}
|
||||
try {
|
||||
app.parse(argc, argv);
|
||||
} catch (const CLI::ParseError& e) {
|
||||
return app.exit(e);
|
||||
}
|
||||
|
||||
// ---- Utility commands ----
|
||||
if (addGameFolder) {
|
||||
Config::addGameInstallDir(*addGameFolder);
|
||||
Config::save(user_dir / "config.toml");
|
||||
std::cout << "Game folder successfully saved.\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (setAddonFolder) {
|
||||
Config::setAddonInstallDir(*setAddonFolder);
|
||||
Config::save(user_dir / "config.toml");
|
||||
std::cout << "Addon folder successfully saved.\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!gamePath.has_value()) {
|
||||
if (!gameArgs.empty()) {
|
||||
gamePath = gameArgs.front();
|
||||
gameArgs.erase(gameArgs.begin());
|
||||
} else {
|
||||
std::cerr << "Unknown argument: " << cur_arg << ", see --help for info.\n";
|
||||
}
|
||||
}
|
||||
|
||||
// If no game directory is set and no command line argument, prompt for it
|
||||
if (Config::getGameInstallDirs().empty()) {
|
||||
std::cerr << "Warning: No game folder set, please set it by calling shadps4"
|
||||
" with the --add-game-folder <folder_name> argument\n";
|
||||
}
|
||||
|
||||
if (!has_game_argument) {
|
||||
std::cerr << "Error: Please provide a game path or ID.\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Check if the game path or ID exists
|
||||
std::filesystem::path eboot_path(game_path);
|
||||
|
||||
// Check if the provided path is a valid file
|
||||
if (!std::filesystem::exists(eboot_path)) {
|
||||
// If not a file, treat it as a game ID and search in install directories recursively
|
||||
bool game_found = false;
|
||||
const int max_depth = 5;
|
||||
for (const auto& install_dir : Config::getGameInstallDirs()) {
|
||||
if (auto found_path = Common::FS::FindGameByID(install_dir, game_path, max_depth)) {
|
||||
eboot_path = *found_path;
|
||||
game_found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!game_found) {
|
||||
std::cerr << "Error: Game ID or file path not found: " << game_path << std::endl;
|
||||
std::cerr << "Error: Please provide a game path or ID.\n";
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (waitPid.has_value()) {
|
||||
Core::Debugger::WaitForPid(waitPid.value());
|
||||
// ---- Apply flags ----
|
||||
if (patchFile)
|
||||
MemoryPatcher::patch_file = *patchFile;
|
||||
|
||||
if (ignoreGamePatch)
|
||||
Core::FileSys::MntPoints::ignore_game_patches = true;
|
||||
|
||||
if (fullscreenStr) {
|
||||
if (*fullscreenStr == "true") {
|
||||
Config::setIsFullscreen(true);
|
||||
} else if (*fullscreenStr == "false") {
|
||||
Config::setIsFullscreen(false);
|
||||
} else {
|
||||
std::cerr << "Error: Invalid argument for --fullscreen (use true|false)\n";
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Run the emulator with the resolved eboot path
|
||||
Core::Emulator* emulator = Common::Singleton<Core::Emulator>::Instance();
|
||||
if (showFps)
|
||||
Config::setShowFpsCounter(true);
|
||||
|
||||
if (configClean)
|
||||
Config::setConfigMode(Config::ConfigMode::Clean);
|
||||
|
||||
if (configGlobal)
|
||||
Config::setConfigMode(Config::ConfigMode::Global);
|
||||
|
||||
if (logAppend)
|
||||
Common::Log::SetAppend();
|
||||
|
||||
// ---- Resolve game path or ID ----
|
||||
std::filesystem::path ebootPath(*gamePath);
|
||||
if (!std::filesystem::exists(ebootPath)) {
|
||||
bool found = false;
|
||||
constexpr int maxDepth = 5;
|
||||
for (const auto& installDir : Config::getGameInstallDirs()) {
|
||||
if (auto foundPath = Common::FS::FindGameByID(installDir, *gamePath, maxDepth)) {
|
||||
ebootPath = *foundPath;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
std::cerr << "Error: Game ID or file path not found: " << *gamePath << "\n";
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (waitPid)
|
||||
Core::Debugger::WaitForPid(*waitPid);
|
||||
|
||||
auto* emulator = Common::Singleton<Core::Emulator>::Instance();
|
||||
emulator->executableName = argv[0];
|
||||
emulator->waitForDebuggerBeforeRun = waitForDebugger;
|
||||
emulator->Run(eboot_path, game_args, game_folder);
|
||||
emulator->Run(ebootPath, gameArgs, overrideRoot);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user