mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-19 17:29:06 +02:00
Merge pull request #21951 from hrydgard/more-commandline-refactor
More commandline parsing refactoring
This commit is contained in:
8 files changed
+479
-308
No files matched your search
+265
-44
@@ -1,6 +1,7 @@
|
||||
#include "Core/Config.h"
|
||||
#include "Core/CmdLine.h"
|
||||
#include "Common/StringUtils.h"
|
||||
#include "Common/Log/LogManager.h"
|
||||
|
||||
#ifdef HAVE_LIBRETRO_VFS
|
||||
|
||||
@@ -16,7 +17,142 @@
|
||||
|
||||
#endif
|
||||
|
||||
static int printUsage(int argc, const char *argv[]) {
|
||||
enum class CmdParamType {
|
||||
Bool,
|
||||
BoolInverse,
|
||||
Int,
|
||||
String,
|
||||
};
|
||||
|
||||
struct CommandLineParam {
|
||||
size_t offsetInStruct; // calculate with offsetof(CommandLineOptions, member). NOTE: Must point to an optional<> of the correct type!
|
||||
CmdParamType type;
|
||||
const char *longName;
|
||||
char shortName; // can be 0 for no short name
|
||||
const char *docString;
|
||||
CmdLineMode mode;
|
||||
};
|
||||
|
||||
#define POFF(member) \
|
||||
{offsetof(CommandLineOptions, member)}
|
||||
|
||||
enum class ParseParamResult {
|
||||
Success,
|
||||
NoMatch,
|
||||
BadValue,
|
||||
};
|
||||
|
||||
ParseParamResult SetValue(CommandLineOptions *options, const CommandLineParam ¶m, const std::string &value) {
|
||||
switch (param.type) {
|
||||
case CmdParamType::Bool:
|
||||
case CmdParamType::BoolInverse:
|
||||
{
|
||||
bool v = true;
|
||||
if (value == "true" || value == "1") {
|
||||
v = true;
|
||||
} else if (value == "false" || value == "0") {
|
||||
v = false;
|
||||
} else {
|
||||
PRINT_STDERR("Error: Invalid value for boolean parameter --%s: '%s'. Expected 'true' or 'false'.\n", param.longName, value.c_str());
|
||||
return ParseParamResult::BadValue;
|
||||
}
|
||||
|
||||
if (param.type == CmdParamType::BoolInverse) {
|
||||
v = !v;
|
||||
}
|
||||
*reinterpret_cast<std::optional<bool> *>(reinterpret_cast<uint8_t *>(options) + param.offsetInStruct) = v;
|
||||
break;
|
||||
}
|
||||
case CmdParamType::Int:
|
||||
*reinterpret_cast<std::optional<int> *>(reinterpret_cast<uint8_t *>(options) + param.offsetInStruct) = std::stoi(value);
|
||||
break;
|
||||
case CmdParamType::String:
|
||||
*reinterpret_cast<std::optional<std::string> *>(reinterpret_cast<uint8_t *>(options) + param.offsetInStruct) = value;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return ParseParamResult::Success;
|
||||
}
|
||||
|
||||
// Helper to parse parameters that take an argument.
|
||||
// Supports: --param=value, --param value, -p value
|
||||
// Returns true if argument was consumed, false otherwise.
|
||||
// On success, 'value' contains the argument string and 'i' is incremented if needed.
|
||||
static ParseParamResult ParseParameterStr(int argc, const char *argv[], size_t &i, const CommandLineParam ¶m, CommandLineOptions *options) {
|
||||
const char *arg = argv[i];
|
||||
const size_t len = strlen(arg);
|
||||
|
||||
// Check for short form: -p value
|
||||
if (param.shortName && len == 2 && arg[0] == '-' && arg[1] == param.shortName) {
|
||||
if (i + 1 < argc) {
|
||||
ParseParamResult result = SetValue(options, param, argv[i + 1]);
|
||||
i += 2; // Skip both -p and value
|
||||
return result;
|
||||
}
|
||||
PRINT_STDERR("Error: -%c requires an argument.\n", param.shortName);
|
||||
return ParseParamResult::BadValue;
|
||||
}
|
||||
|
||||
// Check for long form with equals: --param=value
|
||||
if (param.longName) {
|
||||
std::string prefix = std::string("--") + param.longName + "=";
|
||||
if (startsWith(arg, prefix)) {
|
||||
ParseParamResult result = SetValue(options, param, arg + prefix.size());
|
||||
i++; // Skip --param=value
|
||||
return result;
|
||||
}
|
||||
|
||||
// Check for boolean/inverse-boolean parameter - these don't take a separate param value.
|
||||
if (param.type == CmdParamType::Bool && equals(arg, std::string("--") + param.longName)) {
|
||||
ParseParamResult result = SetValue(options, param, "true");
|
||||
i++; // Skip --param
|
||||
return result;
|
||||
} else if (param.type == CmdParamType::BoolInverse && equals(arg, std::string("--") + param.longName)) {
|
||||
ParseParamResult result = SetValue(options, param, "false");
|
||||
i++; // Skip --param
|
||||
return result;
|
||||
}
|
||||
|
||||
// Check for long form with space: --param value
|
||||
if (equals(arg, std::string("--") + param.longName)) {
|
||||
if (i + 1 < argc) {
|
||||
ParseParamResult result = SetValue(options, param, argv[i + 1]);
|
||||
i += 2; // Skip both --param and value
|
||||
return result;
|
||||
} else {
|
||||
PRINT_STDERR("Error: --%s requires an argument.\n", param.longName);
|
||||
return ParseParamResult::BadValue;
|
||||
}
|
||||
}
|
||||
|
||||
// Else no match, continue.
|
||||
}
|
||||
return ParseParamResult::NoMatch;
|
||||
}
|
||||
|
||||
static const CommandLineParam g_autoParams[] = {
|
||||
{POFF(fullscreen), CmdParamType::Bool, "fullscreen", 0, "Force full screen mode", CmdLineMode::Application},
|
||||
{POFF(fullscreen), CmdParamType::BoolInverse, "windowed", 0, "Force windowed mode", CmdLineMode::Application},
|
||||
{POFF(startScreen), CmdParamType::String, "start-screen", 0, "Start on a specific screen (e.g. 'gamesettings', 'touchscreentest')", CmdLineMode::Application},
|
||||
{POFF(escapeExit), CmdParamType::Bool, "escape-exit", 0, "Escape key exits the application", CmdLineMode::Application},
|
||||
{POFF(pauseMenuExit), CmdParamType::Bool, "pause-menu-exit", 0, "Change \"Exit to menu\" in pause menu to \"Exit\"", CmdLineMode::Application},
|
||||
{POFF(appendConfig), CmdParamType::String, "appendconfig", 0, "Merge config FILE into the current configuration"},
|
||||
{POFF(root), CmdParamType::String, "root", 'r', "Mount root directory"},
|
||||
{POFF(stateToLoad), CmdParamType::String, "state", 0, "Load state from specified file"},
|
||||
{POFF(compare), CmdParamType::Bool, "compare", 'c', "Enable comparison mode"},
|
||||
{POFF(bench), CmdParamType::Bool, "bench", 'b', "Enable benchmark mode"},
|
||||
{POFF(oldAtrac), CmdParamType::Bool, "old-atrac", 0, "Use old ATRAC decoder"},
|
||||
{POFF(log), CmdParamType::String, "log", 0, "Output log to FILE"},
|
||||
{POFF(screenshotFilename), CmdParamType::String, "screenshot", 0, "Take a screenshot and save to FILE"},
|
||||
{POFF(screenshotFilenameSave), CmdParamType::String, "screenshot-save", 0, "Save screenshot to specified path"},
|
||||
{POFF(timeout), CmdParamType::Int, "timeout", 0, "Set the timeout value"},
|
||||
};
|
||||
|
||||
// NOTE: On Windows this prints nothing unfortunately, since PPSSPP is not a "console app".
|
||||
// A fun trick we could do is AttachConsole(ATTACH_PARENT_PROCESS) which works at least from git bash and powershell, if we also use WriteConsole.
|
||||
// However it's not exactly ideal.
|
||||
static int printUsage(int argc, const char *argv[], CmdLineMode mode) {
|
||||
// NOTE: by convention, --help outputs to stdout,
|
||||
// not to stderr, since it is intended output in this
|
||||
// case (usage printed under different circumstances,
|
||||
@@ -25,7 +161,13 @@ static int printUsage(int argc, const char *argv[]) {
|
||||
const char *progname = argc > 0 ? argv[0] : "ppsspp";
|
||||
// NOTE: wording largely taken from
|
||||
// https://www.ppsspp.org/docs/reference/command-line/
|
||||
PRINT_STDOUT("PPSSPP - a PSP emulator (SDL build)\n");
|
||||
if (mode == CmdLineMode::Application) {
|
||||
PRINT_STDOUT("PPSSPP - a PSP emulator\n");
|
||||
} else {
|
||||
PRINT_STDOUT("PPSSPP Headless\n");
|
||||
PRINT_STDOUT("This is primarily meant as a non-interactive test tool.\n\n");
|
||||
}
|
||||
PRINT_STDOUT("PPSSPP - a PSP emulator\n");
|
||||
PRINT_STDOUT("Usage: %s [options] [FILE]\n\n", progname);
|
||||
PRINT_STDOUT("Launches FILE (e.g. ISO image) if present.\n");
|
||||
PRINT_STDOUT("Options (some of these are specific to SDL backend):\n");
|
||||
@@ -43,30 +185,33 @@ static int printUsage(int argc, const char *argv[]) {
|
||||
PRINT_STDOUT(" -j use JIT\n");
|
||||
PRINT_STDOUT(" -J use IR JIT\n");
|
||||
|
||||
PRINT_STDOUT(" --fullscreen force full screen mode, ignoring saved configuration\n");
|
||||
PRINT_STDOUT(" --windowed force windowed mode, ignoring saved configuration\n");
|
||||
PRINT_STDOUT(" --xres PIXELS set X resolution\n");
|
||||
PRINT_STDOUT(" --yres PIXELS set Y resolution\n");
|
||||
PRINT_STDOUT(" --dpi FACTOR set DPI\n");
|
||||
PRINT_STDOUT(" --scale FACTOR set scale\n");
|
||||
PRINT_STDOUT(" --ipad set resolution to 1024x768\n");
|
||||
PRINT_STDOUT(" --portrait portrait mode\n");
|
||||
for (const auto ¶m : g_autoParams) {
|
||||
if (param.mode != CmdLineMode::Both && param.mode != mode) {
|
||||
// Skip mode-irrelevant parameters in help.
|
||||
continue;
|
||||
}
|
||||
PRINT_STDOUT(" --%s%s%s\n", param.longName,
|
||||
param.shortName ? ", -" : "",
|
||||
param.shortName ? std::string(1, param.shortName).c_str() : "");
|
||||
PRINT_STDOUT(" %s\n", param.docString);
|
||||
}
|
||||
|
||||
// These are only available in SDL.
|
||||
if (mode == CmdLineMode::Application) {
|
||||
PRINT_STDOUT(" --xres PIXELS set X resolution\n");
|
||||
PRINT_STDOUT(" --yres PIXELS set Y resolution\n");
|
||||
PRINT_STDOUT(" --dpi DPI set DPI\n");
|
||||
PRINT_STDOUT(" --scale FACTOR set scale\n");
|
||||
}
|
||||
PRINT_STDOUT(" --graphics=BACKEND use a different gpu backend\n");
|
||||
PRINT_STDOUT(" options: gles, software, etc. (also opengl3.1, etc.)\n");
|
||||
|
||||
PRINT_STDOUT(" --pause-menu-exit change \"Exit to menu\" in pause menu to \"Exit\"\n");
|
||||
PRINT_STDOUT(" --escape-exit escape key exits the application\n");
|
||||
PRINT_STDOUT(" --gamesettings go directly to settings\n");
|
||||
PRINT_STDOUT(" --touchscreentest go directly to the touchscreentest screen\n");
|
||||
PRINT_STDOUT(" --appendconfig=FILE merge config FILE into the current configuration\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Logging should be done with plain printf here.
|
||||
// Error reporting is done with PRINT_STDERR(....).
|
||||
// Actually might want to reconsider given Android...
|
||||
CommandLineParseResult CommandLineOptions::Parse(int argc, const char *argv[]) {
|
||||
CommandLineParseResult CommandLineOptions::Parse(int argc, const char *argv[], CmdLineMode mode) {
|
||||
constexpr std::string_view gpuBackendStr = "--graphics=";
|
||||
constexpr std::string_view configOption = "--config=";
|
||||
constexpr std::string_view controlsOption = "--controlconfig=";
|
||||
@@ -81,16 +226,18 @@ CommandLineParseResult CommandLineOptions::Parse(int argc, const char *argv[]) {
|
||||
const size_t len = strlen(argv[i]);
|
||||
|
||||
if (len > 0 && argv[i][0] != '-') {
|
||||
// This is a filename to boot.
|
||||
if (!bootFilename.has_value()) {
|
||||
bootFilename = std::string(argv[i]);
|
||||
// This is a filename to boot. Headless supports multiple filenames.
|
||||
if (bootFilenames.size() == 0 || mode == CmdLineMode::Headless) {
|
||||
bootFilenames.emplace_back(argv[i]);
|
||||
} else {
|
||||
// Already have a filename.
|
||||
PRINT_STDERR("Warning: Ignoring extra boot filename '%s'.\n", argv[i]);
|
||||
// Already have a filename. Ignore.
|
||||
PRINT_STDERR("Ignoring extra boot filename '%s' (can only pass in one).\n", argv[i]);
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// single char commands, like -l, -s, -d
|
||||
// single char commands, like -l, -s
|
||||
if (len == 2) {
|
||||
switch (argv[i][1]) {
|
||||
case 'l':
|
||||
@@ -101,55 +248,97 @@ CommandLineParseResult CommandLineOptions::Parse(int argc, const char *argv[]) {
|
||||
optionS = true;
|
||||
break;
|
||||
case 'd':
|
||||
debugLogLevel = true;
|
||||
logLevel = LogLevel::LDEBUG;
|
||||
break;
|
||||
case 'v':
|
||||
logLevel = LogLevel::LVERBOSE;
|
||||
break;
|
||||
case 'h':
|
||||
printUsage(argc, argv);
|
||||
return CommandLineParseResult::Exit;
|
||||
case 'v':
|
||||
printf("%s\n", PPSSPP_GIT_VERSION);
|
||||
printUsage(argc, argv, mode);
|
||||
return CommandLineParseResult::Exit;
|
||||
// Legacy cpucore options.
|
||||
case 'j':
|
||||
cpuCore = CPUCore::JIT;
|
||||
break;
|
||||
case 'i':
|
||||
cpuCore = CPUCore::INTERPRETER;
|
||||
break;
|
||||
case 'r':
|
||||
cpuCore = CPUCore::IR_INTERPRETER;
|
||||
break;
|
||||
case 'J':
|
||||
cpuCore = CPUCore::JIT_IR;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(__APPLE__)
|
||||
// On Apple system debugged executable may get -NSDocumentRevisionsDebugMode YES in argv.
|
||||
if (equals(argv[i], "-NSDocumentRevisionsDebugMode")) {
|
||||
// Ignore
|
||||
// Ignore the arg and the YES
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
// Simple bool commands
|
||||
|
||||
// NOTE: We need to parse --fullscreen early, before we create the window.
|
||||
if (equals(argv[i], "--help")) {
|
||||
printUsage(argc, argv);
|
||||
bool parsedAutoParam = false;
|
||||
// Loop through all the auto commands, call ParseParameterWithArg to handle them.
|
||||
for (const auto ¶m : g_autoParams) {
|
||||
if (param.mode != CmdLineMode::Both && param.mode != mode) {
|
||||
continue;
|
||||
}
|
||||
ParseParamResult result = ParseParameterStr(argc, argv, i, param, this);
|
||||
if (result == ParseParamResult::Success) {
|
||||
parsedAutoParam = true;
|
||||
break;
|
||||
} else if (result == ParseParamResult::BadValue) {
|
||||
return CommandLineParseResult::Exit;
|
||||
} // else nomatch
|
||||
}
|
||||
|
||||
if (parsedAutoParam) {
|
||||
// We already incremented i.
|
||||
continue;
|
||||
} else if (equals(argv[i], "--help")) {
|
||||
printUsage(argc, argv, mode);
|
||||
return CommandLineParseResult::Exit;
|
||||
} else if (equals(argv[i], "--version")) {
|
||||
printf("%s\n", PPSSPP_GIT_VERSION);
|
||||
return CommandLineParseResult::Exit;
|
||||
} else if (equals(argv[i], "--fullscreen")) {
|
||||
fullscreen = true;
|
||||
} else if (equals(argv[i], "--windowed")) {
|
||||
fullscreen = false;
|
||||
// Commands with parameters. TODO: Should support both space and equals, like --config=foo.ini and --config foo.ini
|
||||
// Commands with parameters. TODO: Should support both space and equals, like --config=foo.ini and --config foo.ini
|
||||
} else if (equals(argv[i], "--jit-ir")) {
|
||||
// Headless legacy
|
||||
cpuCore = CPUCore::JIT_IR;
|
||||
} else if (equals(argv[i], "--ir")) {
|
||||
// Headless legacy
|
||||
cpuCore = CPUCore::IR_INTERPRETER;
|
||||
} else if (startsWith(argv[i], gpuBackendStr)) {
|
||||
const std::string_view restOfOption = argv[i] + gpuBackendStr.size();
|
||||
const std::string restOfOption = argv[i] + gpuBackendStr.size();
|
||||
// Force software rendering off, as picking gles implies HW acceleration.
|
||||
// We could add more options for software such as "software-gles",
|
||||
// "software-vulkan" and "software-d3d11", or something similar.
|
||||
// For now, software rendering force-activates OpenGL.
|
||||
double glVersionTemp = 0.0f;
|
||||
if (restOfOption == "directx11" || restOfOption == "d3d11") {
|
||||
gpuBackend = GPUBackend::DIRECT3D11;
|
||||
softwareRendering = false;
|
||||
} else if (restOfOption == "gles") {
|
||||
gpuBackend = GPUBackend::OPENGL;
|
||||
softwareRendering = false;
|
||||
} else if (restOfOption == "vulkan") {
|
||||
gpuBackend = GPUBackend::VULKAN;
|
||||
softwareRendering = false;
|
||||
} else if (restOfOption == "software") {
|
||||
gpuBackend = GPUBackend::OPENGL;
|
||||
softwareRendering = true;
|
||||
} else if (sscanf(restOfOption.c_str(), "gles%lg", &glVersionTemp) == 1 || sscanf(restOfOption.c_str(), "opengl%lg", &glVersionTemp) == 1) {
|
||||
g_Config.iGPUBackend = (int)GPUBackend::OPENGL;
|
||||
g_Config.bSoftwareRendering = false;
|
||||
force_gl_version = int(10.0 * glVersionTemp + 0.5);
|
||||
} else if (restOfOption == "gles") {
|
||||
gpuBackend = GPUBackend::OPENGL;
|
||||
softwareRendering = false;
|
||||
} else {
|
||||
// Bad value, report error and exit.
|
||||
PRINT_STDERR("Invalid value for --graphics=: %s", restOfOption.c_str());
|
||||
return CommandLineParseResult::Exit;
|
||||
}
|
||||
} else if (startsWith(argv[i], configOption)) {
|
||||
configFilename = std::string(argv[i] + configOption.size());
|
||||
@@ -158,10 +347,16 @@ CommandLineParseResult CommandLineOptions::Parse(int argc, const char *argv[]) {
|
||||
} else {
|
||||
// Report unknown argument later once this is complete.
|
||||
}
|
||||
|
||||
// To the next argument.
|
||||
i++;
|
||||
}
|
||||
|
||||
// Final adjustments to adjust for old inconsistent code
|
||||
if (log.has_value()) {
|
||||
enableLogging = true;
|
||||
showLogWindow = true;
|
||||
}
|
||||
|
||||
return CommandLineParseResult::Continue;
|
||||
}
|
||||
|
||||
@@ -186,4 +381,30 @@ void CommandLineOptions::ApplyToConfig() const {
|
||||
g_Config.bAutoRun = false;
|
||||
g_Config.bSaveSettings = false;
|
||||
}
|
||||
if (cpuCore.has_value()) {
|
||||
g_Config.iCpuCore = (int)cpuCore.value();
|
||||
}
|
||||
if (escapeExit.has_value()) {
|
||||
g_Config.bPauseExitsEmulator = escapeExit.value();
|
||||
}
|
||||
if (pauseMenuExit.has_value()) {
|
||||
g_Config.bPauseMenuExitsEmulator = pauseMenuExit.value();
|
||||
}
|
||||
|
||||
if (logLevel.has_value()) {
|
||||
g_logManager.SetAllLogLevels(logLevel.value());
|
||||
}
|
||||
|
||||
if (oldAtrac.has_value()) {
|
||||
g_Config.bUseOldAtrac = oldAtrac.value();
|
||||
g_Config.DoNotSaveSetting(&g_Config.bUseOldAtrac);
|
||||
}
|
||||
|
||||
if (root.has_value()) {
|
||||
g_Config.DoNotSaveSetting(&g_Config.mountRoot);
|
||||
g_Config.mountRoot = Path(root.value());
|
||||
}
|
||||
|
||||
// Note: dpi is not applied here - it's platform-specific.
|
||||
// Platforms should check cmdLineOptions.dpi.has_value() and handle accordingly.
|
||||
}
|
||||
+45
-5
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include "Common/Log.h"
|
||||
#include "Core/ConfigValues.h"
|
||||
|
||||
enum class CommandLineParseResult {
|
||||
@@ -9,27 +10,66 @@ enum class CommandLineParseResult {
|
||||
Error,
|
||||
};
|
||||
|
||||
enum class CmdLineMode {
|
||||
Both,
|
||||
Application,
|
||||
Headless,
|
||||
};
|
||||
|
||||
// We collect command line options in this struct, then we apply it to the config after it's been loaded.
|
||||
// This parser is shared between regular PPSSPP and headless, so there are some options that are only useful
|
||||
// in one of them.
|
||||
// When adding new options, don't forget to update g_autoParams in CmdLine.cpp (or write manual parsing).
|
||||
struct CommandLineOptions {
|
||||
// If returns CommandLineParseResult::Exit or ::Error, the program should exit immediately (with an error return code if Error).
|
||||
CommandLineParseResult Parse(int argc, const char *argv[], CmdLineMode mode = CmdLineMode::Application);
|
||||
void ApplyToConfig() const;
|
||||
|
||||
std::optional<bool> fullscreen;
|
||||
std::optional<GPUBackend> gpuBackend;
|
||||
std::optional<bool> softwareRendering;
|
||||
std::optional<bool> enableLogging;
|
||||
std::optional<LogLevel> logLevel; // Override log level with this.
|
||||
std::optional<std::string> log;
|
||||
std::vector<std::string> bootFilenames;
|
||||
|
||||
std::optional<std::string> bootFilename;
|
||||
std::optional<CPUCore> cpuCore;
|
||||
|
||||
std::optional<std::string> startScreen;
|
||||
|
||||
std::optional<bool> escapeExit;
|
||||
std::optional<bool> pauseMenuExit;
|
||||
|
||||
std::optional<std::string> appendConfig;
|
||||
std::optional<std::string> root; // mount root, needs more explanation
|
||||
std::optional<std::string> stateToLoad;
|
||||
|
||||
// SDL only: Option to force a specific OpenGL version (42="4.2",
|
||||
// etc.; -1 means "try them all").
|
||||
// Implemented as a workaround for https://github.com/hrydgard/ppsspp/issues/20687
|
||||
// NOTE: this is currently not persistent (doesn't
|
||||
// go to config), even though --graphics=openglX.Y
|
||||
// also sets the GPU backend which does persist.
|
||||
int force_gl_version = -1;
|
||||
|
||||
#ifndef _DEBUG
|
||||
bool showLogWindow = false;
|
||||
#else
|
||||
bool showLogWindow = true;
|
||||
#endif
|
||||
bool debugLogLevel = false;
|
||||
std::string configFilename = "";
|
||||
std::string controlsConfigFilename = "";
|
||||
|
||||
bool optionS = false; // a legacy option
|
||||
|
||||
// If returns CommandLineParseResult::Exit or ::Error, the program should exit immediately (with an error return code if Error).
|
||||
CommandLineParseResult Parse(int argc, const char *argv[]);
|
||||
void ApplyToConfig() const;
|
||||
std::optional<bool> oldAtrac;
|
||||
|
||||
// Headless options
|
||||
std::optional<bool> compare;
|
||||
std::optional<bool> bench;
|
||||
std::optional<bool> verbose;
|
||||
|
||||
std::optional<std::string> screenshotFilename;
|
||||
std::optional<std::string> screenshotFilenameSave;
|
||||
std::optional<int> timeout;
|
||||
};
|
||||
+13
-53
@@ -1441,8 +1441,6 @@ int main(int argc, char *argv[]) {
|
||||
const int linked = SDL_GetVersion();
|
||||
int set_xres = -1;
|
||||
int set_yres = -1;
|
||||
bool portrait = false;
|
||||
bool set_ipad = false;
|
||||
float set_dpi = 0.0f;
|
||||
float set_scale = 1.0f;
|
||||
|
||||
@@ -1451,20 +1449,9 @@ int main(int argc, char *argv[]) {
|
||||
const char *remain_argv[256] = { argv[0] };
|
||||
constexpr int remain_argv_cap = (int)(sizeof(remain_argv) / sizeof(remain_argv[0]));
|
||||
|
||||
// Option to force a specific OpenGL version (42="4.2",
|
||||
// etc.; -1 means "try them all").
|
||||
// Implemented as a workaround for https://github.com/hrydgard/ppsspp/issues/20687
|
||||
// NOTE: this is currently not persistent (doesn't
|
||||
// go to config), even though --graphics=openglX.Y
|
||||
// also sets the GPU backend which does persist.
|
||||
int force_gl_version = -1;
|
||||
|
||||
Uint32 mode = 0;
|
||||
for (int i = 1; i < argc; i++) {
|
||||
if (!strcmp(argv[i], "--fullscreen")) {
|
||||
mode |= SDL_WINDOW_FULLSCREEN;
|
||||
g_Config.DoNotSaveSetting(&g_Config.bFullScreen);
|
||||
} else if (set_xres == -2)
|
||||
if (set_xres == -2)
|
||||
set_xres = parseInt(argv[i]);
|
||||
else if (set_yres == -2)
|
||||
set_yres = parseInt(argv[i]);
|
||||
@@ -1480,31 +1467,7 @@ int main(int argc, char *argv[]) {
|
||||
set_dpi = -2;
|
||||
else if (!strcmp(argv[i], "--scale"))
|
||||
set_scale = -2;
|
||||
else if (!strcmp(argv[i], "--ipad"))
|
||||
set_ipad = true;
|
||||
else if (!strcmp(argv[i], "--portrait"))
|
||||
portrait = true;
|
||||
else if (!strncmp(argv[i], "--graphics=", strlen("--graphics="))) {
|
||||
const char *restOfOption = argv[i] + strlen("--graphics=");
|
||||
double val=-1.0; // Yes, floating point.
|
||||
if (!strcmp(restOfOption, "vulkan")) {
|
||||
g_Config.iGPUBackend = (int)GPUBackend::VULKAN;
|
||||
g_Config.bSoftwareRendering = false;
|
||||
} else if (!strcmp(restOfOption, "software")) {
|
||||
// Same as on Windows, software presently implies OpenGL.
|
||||
g_Config.iGPUBackend = (int)GPUBackend::OPENGL;
|
||||
g_Config.bSoftwareRendering = true;
|
||||
} else if (!strcmp(restOfOption, "gles") || !strcmp(restOfOption, "opengl")) {
|
||||
// NOTE: OpenGL and GLES are treated the same for
|
||||
// the purposes of option parsing.
|
||||
g_Config.iGPUBackend = (int)GPUBackend::OPENGL;
|
||||
g_Config.bSoftwareRendering = false;
|
||||
} else if (sscanf(restOfOption, "gles%lg", &val) == 1 || sscanf(restOfOption, "opengl%lg", &val) == 1) {
|
||||
g_Config.iGPUBackend = (int)GPUBackend::OPENGL;
|
||||
g_Config.bSoftwareRendering = false;
|
||||
force_gl_version = int(10.0 * val + 0.5);
|
||||
}
|
||||
} else {
|
||||
else {
|
||||
if (remain_argc < remain_argv_cap - 1) {
|
||||
remain_argv[remain_argc++] = argv[i];
|
||||
} else {
|
||||
@@ -1556,6 +1519,7 @@ int main(int argc, char *argv[]) {
|
||||
g_RefreshRate = displayMode->refresh_rate;
|
||||
SDL_free(displayIDs);
|
||||
|
||||
// TODO: Should only call this if we actually intend to use OpenGL.
|
||||
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
|
||||
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
|
||||
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
|
||||
@@ -1565,19 +1529,19 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
// Force fullscreen if the resolution is too low to run windowed.
|
||||
if (g_DesktopWidth < 480 * 2 && g_DesktopHeight < 272 * 2) {
|
||||
mode |= SDL_WINDOW_FULLSCREEN;
|
||||
cmdLineOptions.fullscreen = true;
|
||||
}
|
||||
|
||||
// If we're on mobile, don't try for windowed either.
|
||||
#if defined(MOBILE_DEVICE) && !PPSSPP_PLATFORM(SWITCH)
|
||||
mode |= SDL_WINDOW_FULLSCREEN;
|
||||
cmdLineOptions.fullscreen = true;
|
||||
#elif defined(USING_FBDEV) || PPSSPP_PLATFORM(SWITCH)
|
||||
mode |= SDL_WINDOW_FULLSCREEN;
|
||||
cmdLineOptions.fullscreen = true;
|
||||
#else
|
||||
mode |= SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY;
|
||||
#endif
|
||||
|
||||
if (mode & SDL_WINDOW_FULLSCREEN) {
|
||||
if (cmdLineOptions.fullscreen) {
|
||||
g_display.pixel_xres = g_DesktopWidth;
|
||||
g_display.pixel_yres = g_DesktopHeight;
|
||||
g_Config.bFullScreen = true;
|
||||
@@ -1585,16 +1549,9 @@ int main(int argc, char *argv[]) {
|
||||
// set a sensible default resolution (2x)
|
||||
g_display.pixel_xres = 480 * 2 * set_scale;
|
||||
g_display.pixel_yres = 272 * 2 * set_scale;
|
||||
if (portrait) {
|
||||
std::swap(g_display.pixel_xres, g_display.pixel_yres);
|
||||
}
|
||||
g_Config.bFullScreen = false;
|
||||
}
|
||||
|
||||
if (set_ipad) {
|
||||
g_display.pixel_xres = 1024;
|
||||
g_display.pixel_yres = 768;
|
||||
}
|
||||
if (!landscape) {
|
||||
std::swap(g_display.pixel_xres, g_display.pixel_yres);
|
||||
}
|
||||
@@ -1636,6 +1593,9 @@ int main(int argc, char *argv[]) {
|
||||
#else
|
||||
const char *external_dir = "/tmp";
|
||||
#endif
|
||||
|
||||
// After NativeInit, code should no longer look at cmdLineOptions, they should have been translated
|
||||
// into g_Config settings. This is because NativeInit may modify g_Config settings based on the command line options.
|
||||
NativeInit(remain_argc, (const char **)remain_argv, cmdLineOptions, path, external_dir, nullptr);
|
||||
|
||||
// Use the setting from the config when initing the window.
|
||||
@@ -1672,7 +1632,7 @@ int main(int argc, char *argv[]) {
|
||||
std::string error_message;
|
||||
if (g_Config.iGPUBackend == (int)GPUBackend::OPENGL) {
|
||||
SDLGLGraphicsContext *glctx = new SDLGLGraphicsContext();
|
||||
if (glctx->Init(window, x, y, w, h, mode, &error_message, force_gl_version) != 0) {
|
||||
if (glctx->Init(window, x, y, w, h, mode, &error_message, cmdLineOptions.force_gl_version) != 0) {
|
||||
// Let's try the fallback once per process run.
|
||||
fprintf(stderr, "GL init error '%s' - falling back to Vulkan\n", error_message.c_str());
|
||||
g_Config.iGPUBackend = (int)GPUBackend::VULKAN;
|
||||
@@ -1702,7 +1662,7 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
// NOTE : This should match the three lines above in the OpenGL case.
|
||||
SDLGLGraphicsContext *glctx = new SDLGLGraphicsContext();
|
||||
if (glctx->Init(window, x, y, w, h, mode, &error_message, force_gl_version) != 0) {
|
||||
if (glctx->Init(window, x, y, w, h, mode, &error_message, cmdLineOptions.force_gl_version) != 0) {
|
||||
fprintf(stderr, "GL fallback failed: %s\n", error_message.c_str());
|
||||
return 1;
|
||||
}
|
||||
@@ -1878,7 +1838,7 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
if (g_Config.iGPUBackend == (int)GPUBackend::OPENGL) {
|
||||
SDLGLGraphicsContext *ctx = (SDLGLGraphicsContext *)graphicsContext;
|
||||
if (!ctx->Init(window, x, y, w, h, mode, &error_message, force_gl_version)) {
|
||||
if (!ctx->Init(window, x, y, w, h, mode, &error_message, cmdLineOptions.force_gl_version)) {
|
||||
fprintf(stderr, "Failed to reinit graphics.\n");
|
||||
}
|
||||
}
|
||||
|
||||
+65
-118
@@ -543,7 +543,9 @@ void NativeInit(int argc, const char *argv[], const CommandLineOptions &cmdLineO
|
||||
#elif PPSSPP_PLATFORM(SWITCH)
|
||||
g_Config.memStickDirectory = g_Config.internalDataDirectory / "config/ppsspp";
|
||||
g_Config.flash0Directory = g_Config.internalDataDirectory / "assets/flash0";
|
||||
#elif !PPSSPP_PLATFORM(WINDOWS)
|
||||
#elif PPSSPP_PLATFORM(WINDOWS)
|
||||
// ...
|
||||
#else
|
||||
std::string config;
|
||||
if (getenv("XDG_CONFIG_HOME") != NULL)
|
||||
config = getenv("XDG_CONFIG_HOME");
|
||||
@@ -574,25 +576,19 @@ void NativeInit(int argc, const char *argv[], const CommandLineOptions &cmdLineO
|
||||
|
||||
g_logManager.Init(&g_Config.bEnableLogging);
|
||||
|
||||
#if !PPSSPP_PLATFORM(WINDOWS)
|
||||
g_Config.SetSearchPath(GetSysDirectory(DIRECTORY_SYSTEM));
|
||||
|
||||
// Note that if we don't have storage permission here, loading the config will
|
||||
// fail and it will be set to the default. Later, we load again when we get permission.
|
||||
g_Config.Load();
|
||||
g_Config.Load(cmdLineOptions.configFilename.c_str(), cmdLineOptions.controlsConfigFilename.c_str());
|
||||
System_Notify(SystemNotification::CONFIG_LOADED);
|
||||
#endif
|
||||
|
||||
// Apply parsed command line options to config.
|
||||
cmdLineOptions.ApplyToConfig();
|
||||
|
||||
const char *fileToLog = nullptr;
|
||||
Path stateToLoad;
|
||||
|
||||
bool gotBootFilename = false;
|
||||
bool gotoGameSettings = false;
|
||||
bool gotoTouchScreenTest = false;
|
||||
bool gotoDeveloperTools = false;
|
||||
boot_filename.clear();
|
||||
|
||||
// Parse command line
|
||||
@@ -603,7 +599,9 @@ void NativeInit(int argc, const char *argv[], const CommandLineOptions &cmdLineO
|
||||
forceLogLevel = true;
|
||||
};
|
||||
|
||||
// TODO: Need a much better command line argument parser.
|
||||
if (cmdLineOptions.logLevel.has_value()) {
|
||||
setLogLevel(cmdLineOptions.logLevel.value());
|
||||
}
|
||||
|
||||
for (int i = 1; i < argc; i++) {
|
||||
if (argv[i][0] == '-') {
|
||||
@@ -615,119 +613,64 @@ void NativeInit(int argc, const char *argv[], const CommandLineOptions &cmdLineO
|
||||
}
|
||||
#endif
|
||||
switch (argv[i][1]) {
|
||||
case 'd':
|
||||
// Enable debug logging
|
||||
// Note that you must also change the max log level in Log.h.
|
||||
setLogLevel(LogLevel::LDEBUG);
|
||||
break;
|
||||
case 'v':
|
||||
// Enable verbose logging
|
||||
// Note that you must also change the max log level in Log.h.
|
||||
setLogLevel(LogLevel::LVERBOSE);
|
||||
break;
|
||||
case 'j':
|
||||
g_Config.iCpuCore = (int)CPUCore::JIT;
|
||||
g_Config.DoNotSaveSetting(&g_Config.iCpuCore);
|
||||
break;
|
||||
case 'i':
|
||||
g_Config.iCpuCore = (int)CPUCore::INTERPRETER;
|
||||
g_Config.DoNotSaveSetting(&g_Config.iCpuCore);
|
||||
break;
|
||||
case 'r':
|
||||
g_Config.iCpuCore = (int)CPUCore::IR_INTERPRETER;
|
||||
g_Config.DoNotSaveSetting(&g_Config.iCpuCore);
|
||||
break;
|
||||
case 'J':
|
||||
g_Config.iCpuCore = (int)CPUCore::JIT_IR;
|
||||
g_Config.DoNotSaveSetting(&g_Config.iCpuCore);
|
||||
break;
|
||||
case '-':
|
||||
if (!strncmp(argv[i], "--loglevel=", strlen("--loglevel=")) && strlen(argv[i]) > strlen("--loglevel="))
|
||||
setLogLevel(static_cast<LogLevel>(std::atoi(argv[i] + strlen("--loglevel="))));
|
||||
if (!strncmp(argv[i], "--log=", strlen("--log=")) && strlen(argv[i]) > strlen("--log="))
|
||||
fileToLog = argv[i] + strlen("--log=");
|
||||
if (!strncmp(argv[i], "--state=", strlen("--state=")) && strlen(argv[i]) > strlen("--state="))
|
||||
stateToLoad = Path(argv[i] + strlen("--state="));
|
||||
if (!strncmp(argv[i], "--escape-exit", strlen("--escape-exit")))
|
||||
g_Config.bPauseExitsEmulator = true;
|
||||
if (!strncmp(argv[i], "--pause-menu-exit", strlen("--pause-menu-exit")))
|
||||
g_Config.bPauseMenuExitsEmulator = true;
|
||||
if (!strcmp(argv[i], "--fullscreen")) {
|
||||
g_Config.DoNotSaveSetting(&g_Config.bFullScreen);
|
||||
g_Config.bFullScreen = true;
|
||||
}
|
||||
if (!strncmp(argv[i], "--root=", strlen("--root=")) && strlen(argv[i]) > strlen("--root=")) {
|
||||
g_Config.DoNotSaveSetting(&g_Config.mountRoot);
|
||||
g_Config.mountRoot = Path(argv[i] + strlen("--root="));
|
||||
}
|
||||
if (!strcmp(argv[i], "--windowed")) {
|
||||
g_Config.DoNotSaveSetting(&g_Config.bFullScreen);
|
||||
g_Config.bFullScreen = false;
|
||||
}
|
||||
if (!strcmp(argv[i], "--touchscreentest"))
|
||||
gotoTouchScreenTest = true;
|
||||
if (!strcmp(argv[i], "--gamesettings"))
|
||||
gotoGameSettings = true;
|
||||
if (!strcmp(argv[i], "--developertools"))
|
||||
gotoDeveloperTools = true;
|
||||
if (!strncmp(argv[i], "--appendconfig=", strlen("--appendconfig=")) && strlen(argv[i]) > strlen("--appendconfig=")) {
|
||||
g_Config.SetAppendedConfigIni(Path(argv[i] + strlen("--appendconfig=")));
|
||||
g_Config.LoadAppendedConfig();
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// This parameter should be a boot filename. Only accept it if we
|
||||
// don't already have one.
|
||||
if (!gotBootFilename) {
|
||||
gotBootFilename = true;
|
||||
INFO_LOG(Log::System, "Boot filename found in args: '%s'", argv[i]);
|
||||
// Ignore. Boot filename is extracted in the previous step.
|
||||
}
|
||||
}
|
||||
|
||||
bool okToLoad = true;
|
||||
bool okToCheck = true;
|
||||
if (System_GetPropertyBool(SYSPROP_SUPPORTS_PERMISSIONS)) {
|
||||
PermissionStatus status = System_GetPermissionStatus(SYSTEM_PERMISSION_STORAGE);
|
||||
if (status == PERMISSION_STATUS_DENIED) {
|
||||
ERROR_LOG(Log::IO, "Storage permission denied. Launching without argument.");
|
||||
okToLoad = false;
|
||||
okToCheck = false;
|
||||
} else if (status != PERMISSION_STATUS_GRANTED) {
|
||||
ERROR_LOG(Log::IO, "Storage permission not granted. Launching without argument check.");
|
||||
okToCheck = false;
|
||||
} else {
|
||||
INFO_LOG(Log::IO, "Storage permission granted.");
|
||||
}
|
||||
}
|
||||
if (okToLoad) {
|
||||
std::string str = std::string(argv[i]);
|
||||
// Handle file:/// URIs, since you get those when creating shortcuts on some Android systems.
|
||||
if (startsWith(str, "file:///")) {
|
||||
str = UriDecode(str.substr(7));
|
||||
INFO_LOG(Log::IO, "Decoding '%s' to '%s'", argv[i], str.c_str());
|
||||
}
|
||||
if (cmdLineOptions.appendConfig.has_value()) {
|
||||
g_Config.SetAppendedConfigIni(Path(cmdLineOptions.appendConfig.value()));
|
||||
g_Config.LoadAppendedConfig();
|
||||
}
|
||||
|
||||
boot_filename = Path(str);
|
||||
skipLogo = true;
|
||||
}
|
||||
// This is needed on iOS, to fixup the path to match the current app directory, if it's stored in it.
|
||||
TryUpdateSavedPath(&boot_filename);
|
||||
if (okToLoad && okToCheck) {
|
||||
std::unique_ptr<FileLoader> fileLoader(ConstructFileLoader(boot_filename));
|
||||
if (!fileLoader->Exists()) {
|
||||
fprintf(stderr, "File not found: %s\n", boot_filename.c_str());
|
||||
// This parameter should be a boot filename. Only accept it if we
|
||||
// don't already have one.
|
||||
if (!cmdLineOptions.bootFilenames.empty()) {
|
||||
std::string bootFilename = cmdLineOptions.bootFilenames[0];
|
||||
gotBootFilename = true;
|
||||
INFO_LOG(Log::System, "Boot filename found in args: '%s'", bootFilename.c_str());
|
||||
|
||||
#if defined(_WIN32) || defined(__ANDROID__) || PPSSPP_PLATFORM(IOS)
|
||||
boot_filename.clear();
|
||||
#else
|
||||
// Bail.
|
||||
exit(1);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
bool okToLoad = true;
|
||||
bool okToCheck = true;
|
||||
if (System_GetPropertyBool(SYSPROP_SUPPORTS_PERMISSIONS)) {
|
||||
PermissionStatus status = System_GetPermissionStatus(SYSTEM_PERMISSION_STORAGE);
|
||||
if (status == PERMISSION_STATUS_DENIED) {
|
||||
ERROR_LOG(Log::IO, "Storage permission denied. Launching without argument.");
|
||||
okToLoad = false;
|
||||
okToCheck = false;
|
||||
} else if (status != PERMISSION_STATUS_GRANTED) {
|
||||
ERROR_LOG(Log::IO, "Storage permission not granted. Launching without argument check.");
|
||||
okToCheck = false;
|
||||
} else {
|
||||
fprintf(stderr, "Syntax error: Can only boot one file.\nNote: Many command line args need a =, like --appendconfig=FILENAME.ini.\n");
|
||||
INFO_LOG(Log::IO, "Storage permission granted.");
|
||||
}
|
||||
}
|
||||
if (okToLoad) {
|
||||
// Handle file:/// URIs, since you get those when creating shortcuts on some Android systems.
|
||||
if (startsWith(bootFilename, "file:///")) {
|
||||
bootFilename = UriDecode(bootFilename.substr(7));
|
||||
INFO_LOG(Log::IO, "Decoding '%s' to '%s'", cmdLineOptions.bootFilenames[0].c_str(), bootFilename.c_str());
|
||||
}
|
||||
|
||||
boot_filename = Path(bootFilename);
|
||||
skipLogo = true;
|
||||
}
|
||||
// This is needed on iOS, to fixup the path to match the current app directory, if it's stored in it.
|
||||
TryUpdateSavedPath(&boot_filename);
|
||||
if (okToLoad && okToCheck) {
|
||||
std::unique_ptr<FileLoader> fileLoader(ConstructFileLoader(boot_filename));
|
||||
if (!fileLoader->Exists()) {
|
||||
fprintf(stderr, "File not found: %s\n", boot_filename.c_str());
|
||||
|
||||
#if defined(_WIN32) || defined(__ANDROID__) || PPSSPP_PLATFORM(IOS)
|
||||
// Ignore and proceed.
|
||||
boot_filename.clear();
|
||||
#else
|
||||
// Bail.
|
||||
exit(1);
|
||||
@@ -770,8 +713,8 @@ void NativeInit(int argc, const char *argv[], const CommandLineOptions &cmdLineO
|
||||
|
||||
g_BackgroundAudio.SFX().Init();
|
||||
|
||||
if (!boot_filename.empty() && stateToLoad.Valid()) {
|
||||
SaveState::Load(stateToLoad, -1, &ShowMessageAfterSaveStateAction);
|
||||
if (!boot_filename.empty() && cmdLineOptions.stateToLoad.has_value()) {
|
||||
SaveState::Load(Path(cmdLineOptions.stateToLoad.value()), -1, &ShowMessageAfterSaveStateAction);
|
||||
}
|
||||
|
||||
if (g_Config.bAchievementsEnable) {
|
||||
@@ -791,14 +734,18 @@ void NativeInit(int argc, const char *argv[], const CommandLineOptions &cmdLineO
|
||||
if (g_Config.memStickDirectory.empty()) {
|
||||
INFO_LOG(Log::System, "No memstick directory! Asking for one to be configured.");
|
||||
g_screenManager->switchScreen(new LogoScreen(AfterLogoScreen::MEMSTICK_SCREEN_INITIAL_SETUP));
|
||||
} else if (gotoGameSettings) {
|
||||
g_screenManager->switchScreen(new LogoScreen(AfterLogoScreen::TO_GAME_SETTINGS));
|
||||
} else if (gotoTouchScreenTest) {
|
||||
g_screenManager->switchScreen(new MainScreen());
|
||||
} else if (cmdLineOptions.startScreen.has_value()) {
|
||||
// Launch into specified start screen. This is useful for testing UI, more screens can be easily added here.
|
||||
if (equals(cmdLineOptions.startScreen.value(), "touchscreentest")) {
|
||||
g_screenManager->switchScreen(new MainScreen());
|
||||
}
|
||||
g_screenManager->push(new TouchTestScreen(Path()));
|
||||
} else if (gotoDeveloperTools) {
|
||||
g_screenManager->switchScreen(new MainScreen());
|
||||
g_screenManager->push(new DeveloperToolsScreen(Path()));
|
||||
if (equals(cmdLineOptions.startScreen.value(), "gamesettings")) {
|
||||
g_screenManager->switchScreen(new LogoScreen(AfterLogoScreen::TO_GAME_SETTINGS));
|
||||
} else if (equals(cmdLineOptions.startScreen.value(), "developertools")) {
|
||||
g_screenManager->switchScreen(new MainScreen());
|
||||
g_screenManager->push(new DeveloperToolsScreen(Path()));
|
||||
}
|
||||
} else if (skipLogo && !boot_filename.empty()) {
|
||||
INFO_LOG(Log::System, "Launching EmuScreen with boot filename '%s'", boot_filename.c_str());
|
||||
g_screenManager->switchScreen(new EmuScreen(boot_filename));
|
||||
|
||||
@@ -43,8 +43,6 @@ static std::thread mainThread;
|
||||
static std::string g_error_message;
|
||||
static bool g_inLoop;
|
||||
|
||||
extern std::vector<std::wstring> GetWideCmdLine();
|
||||
|
||||
class GraphicsContext;
|
||||
static GraphicsContext *g_graphicsContext;
|
||||
|
||||
|
||||
+1
-11
@@ -1050,7 +1050,7 @@ int WINAPI WinMain(HINSTANCE _hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLin
|
||||
|
||||
// args provide argc and argv.
|
||||
CommandLineOptions cmdLineOptions;
|
||||
CommandLineParseResult parseResult = cmdLineOptions.Parse((int)args.size(), args.data());
|
||||
const CommandLineParseResult parseResult = cmdLineOptions.Parse((int)args.size(), args.data());
|
||||
switch (parseResult) {
|
||||
case CommandLineParseResult::Exit:
|
||||
return 0;
|
||||
@@ -1091,12 +1091,6 @@ int WINAPI WinMain(HINSTANCE _hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLin
|
||||
InitMemstickDirectory();
|
||||
CreateSysDirectories();
|
||||
|
||||
// Load config up here, because those changes below would be overwritten
|
||||
// if it's not loaded here first.
|
||||
g_Config.SetSearchPath(GetSysDirectory(DIRECTORY_SYSTEM));
|
||||
g_Config.Load(cmdLineOptions.configFilename.c_str(), cmdLineOptions.controlsConfigFilename.c_str());
|
||||
System_Notify(SystemNotification::CONFIG_LOADED);
|
||||
|
||||
#ifndef _DEBUG
|
||||
// See #11719 - too many Vulkan drivers crash on basic init.
|
||||
if (g_Config.IsBackendEnabled(GPUBackend::VULKAN)) {
|
||||
@@ -1119,10 +1113,6 @@ int WINAPI WinMain(HINSTANCE _hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLin
|
||||
SetWindowPos(console, NULL, g_Config.iConsoleWindowX, g_Config.iConsoleWindowY, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
|
||||
}
|
||||
|
||||
if (cmdLineOptions.debugLogLevel) {
|
||||
g_logManager.SetAllLogLevels(LogLevel::LDEBUG);
|
||||
}
|
||||
|
||||
// Windows Media Foundation - used for camera and (currently, but not later) microphone support (we'll move to WASAPI for audio input later)
|
||||
if (!RegisterCMPTMFApis()) {
|
||||
ERROR_LOG(Log::System, "Failed to load Media Foundation functions");
|
||||
|
||||
+50
-75
@@ -36,6 +36,7 @@
|
||||
#include "Common/TimeUtil.h"
|
||||
#include "Common/StringUtils.h"
|
||||
#include "Common/Thread/ThreadManager.h"
|
||||
#include "Core/CmdLine.h"
|
||||
#include "Core/Config.h"
|
||||
#include "Core/ConfigValues.h"
|
||||
#include "Core/Core.h"
|
||||
@@ -198,8 +199,9 @@ bool RunAutoTest(HeadlessHost *headlessHost, CoreParameter &coreParameter, const
|
||||
return false;
|
||||
}
|
||||
|
||||
if (opt.compare)
|
||||
if (opt.compare) {
|
||||
headlessHost->SetComparisonScreenshot(ExpectedScreenshotFromFilename(coreParameter.fileToStart), opt.maxScreenshotError);
|
||||
}
|
||||
|
||||
std::string error_string;
|
||||
while (PSP_InitUpdate(&error_string) == BootState::Booting) {
|
||||
@@ -226,8 +228,7 @@ bool RunAutoTest(HeadlessHost *headlessHost, CoreParameter &coreParameter, const
|
||||
bool passed = true;
|
||||
double deadline = time_now_d() + opt.timeout;
|
||||
coreState = coreParameter.startBreak ? CORE_STEPPING_CPU : CORE_RUNNING_CPU;
|
||||
while (coreState == CORE_RUNNING_CPU || coreState == CORE_STEPPING_CPU)
|
||||
{
|
||||
while (coreState == CORE_RUNNING_CPU || coreState == CORE_STEPPING_CPU) {
|
||||
int blockTicks = (int)usToCycles(1000000 / 10);
|
||||
PSP_RunLoopFor(blockTicks);
|
||||
|
||||
@@ -276,11 +277,13 @@ bool RunAutoTest(HeadlessHost *headlessHost, CoreParameter &coreParameter, const
|
||||
|
||||
PSP_Shutdown(true);
|
||||
|
||||
if (!opt.bench)
|
||||
if (!opt.bench) {
|
||||
headlessHost->FlushDebugOutput();
|
||||
}
|
||||
|
||||
if (opt.compare && passed)
|
||||
if (opt.compare && passed) {
|
||||
passed = CompareOutput(coreParameter.fileToStart, output, opt.verbose);
|
||||
}
|
||||
|
||||
return passed;
|
||||
}
|
||||
@@ -324,7 +327,7 @@ static void AddRecursively(std::vector<std::string> *tests, Path actualPath) {
|
||||
}
|
||||
}
|
||||
|
||||
static void AddTestsByPath(std::vector<std::string> *tests, std::string_view path) {
|
||||
static void AddToTestsByPath(std::vector<std::string> *tests, std::string_view path) {
|
||||
if (endsWith(path, "/...")) {
|
||||
path = path.substr(0, path.size() - 4);
|
||||
// Recurse for tests
|
||||
@@ -337,8 +340,7 @@ static void AddTestsByPath(std::vector<std::string> *tests, std::string_view pat
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, const char* argv[])
|
||||
{
|
||||
int main(int argc, const char* argv[]) {
|
||||
PROFILE_INIT();
|
||||
TimeInit();
|
||||
#if PPSSPP_PLATFORM(WINDOWS)
|
||||
@@ -356,8 +358,22 @@ int main(int argc, const char* argv[])
|
||||
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
|
||||
#endif
|
||||
|
||||
CommandLineOptions cmdLineOptions;
|
||||
CommandLineParseResult parseResult = cmdLineOptions.Parse(argc, argv, CmdLineMode::Headless);
|
||||
switch (parseResult) {
|
||||
case CommandLineParseResult::Exit:
|
||||
return 0;
|
||||
case CommandLineParseResult::Error:
|
||||
return 1;
|
||||
case CommandLineParseResult::Continue:
|
||||
break;
|
||||
}
|
||||
|
||||
AutoTestOptions testOptions{};
|
||||
testOptions.timeout = std::numeric_limits<double>::infinity();
|
||||
testOptions.compare = cmdLineOptions.compare.value_or(false);
|
||||
testOptions.bench = cmdLineOptions.bench.value_or(false);
|
||||
testOptions.timeout = cmdLineOptions.timeout.value_or(0);
|
||||
|
||||
bool fullLog = false;
|
||||
const char *stateToLoad = 0;
|
||||
GPUCore gpuCore = GPUCORE_SOFTWARE;
|
||||
@@ -368,73 +384,29 @@ int main(int argc, const char* argv[])
|
||||
|
||||
std::vector<std::string> testFilenames;
|
||||
std::vector<std::string> ignoredTests;
|
||||
const char *mountIso = nullptr;
|
||||
const char *mountRoot = nullptr;
|
||||
const char *screenshotFilename = nullptr;
|
||||
const char *screenshotSavePath = nullptr;
|
||||
std::string mountIso;
|
||||
std::string mountRoot;
|
||||
|
||||
for (int i = 1; i < argc; i++)
|
||||
{
|
||||
if (!strcmp(argv[i], "-m") || !strcmp(argv[i], "--mount"))
|
||||
{
|
||||
if (cmdLineOptions.cpuCore.has_value()) {
|
||||
cpuCore = cmdLineOptions.cpuCore.value();
|
||||
}
|
||||
|
||||
if (cmdLineOptions.root.has_value()) {
|
||||
mountRoot = cmdLineOptions.root.value().c_str();
|
||||
}
|
||||
|
||||
for (int i = 1; i < argc; i++) {
|
||||
if (!strcmp(argv[i], "-m") || !strcmp(argv[i], "--mount")) {
|
||||
if (++i >= argc)
|
||||
return printUsage(argv[0], "Missing argument after -m");
|
||||
mountIso = argv[i];
|
||||
}
|
||||
else if (!strcmp(argv[i], "-r") || !strcmp(argv[i], "--root"))
|
||||
{
|
||||
if (++i >= argc)
|
||||
return printUsage(argv[0], "Missing argument after -r");
|
||||
mountRoot = argv[i];
|
||||
}
|
||||
else if (!strcmp(argv[i], "-l") || !strcmp(argv[i], "--log"))
|
||||
fullLog = true;
|
||||
else if (!strcmp(argv[i], "-o") || !strcmp(argv[i], "--odslog"))
|
||||
outputDebugStringLog = true;
|
||||
else if (!strcmp(argv[i], "-i"))
|
||||
cpuCore = CPUCore::INTERPRETER;
|
||||
else if (!strcmp(argv[i], "-j"))
|
||||
cpuCore = CPUCore::JIT;
|
||||
else if (!strcmp(argv[i], "--jit-ir"))
|
||||
cpuCore = CPUCore::JIT_IR;
|
||||
else if (!strcmp(argv[i], "--ir"))
|
||||
cpuCore = CPUCore::IR_INTERPRETER;
|
||||
else if (!strcmp(argv[i], "-c") || !strcmp(argv[i], "--compare"))
|
||||
testOptions.compare = true;
|
||||
else if (!strcmp(argv[i], "--bench"))
|
||||
testOptions.bench = true;
|
||||
else if (!strcmp(argv[i], "-v") || !strcmp(argv[i], "--verbose"))
|
||||
testOptions.verbose = true;
|
||||
else if (!strcmp(argv[i], "--old-atrac"))
|
||||
oldAtrac = true;
|
||||
else if (!strncmp(argv[i], "--graphics=", strlen("--graphics=")) && strlen(argv[i]) > strlen("--graphics="))
|
||||
{
|
||||
const char *gpuName = argv[i] + strlen("--graphics=");
|
||||
if (!strcasecmp(gpuName, "gles"))
|
||||
gpuCore = GPUCORE_GLES;
|
||||
// There used to be a separate "null" rendering core - just use software.
|
||||
else if (!strcasecmp(gpuName, "software") || !strcasecmp(gpuName, "null"))
|
||||
gpuCore = GPUCORE_SOFTWARE;
|
||||
else if (!strcasecmp(gpuName, "directx11") || !strcasecmp(gpuName, "d3d11"))
|
||||
gpuCore = GPUCORE_DIRECTX11;
|
||||
else if (!strcasecmp(gpuName, "vulkan"))
|
||||
gpuCore = GPUCORE_VULKAN;
|
||||
else
|
||||
return printUsage(argv[0], "Unknown gpu backend specified after --graphics=. Allowed: software, directx9, directx11, vulkan, gles, null.");
|
||||
}
|
||||
// Default to GLES if no value selected.
|
||||
else if (!strcmp(argv[i], "--graphics")) {
|
||||
#if PPSSPP_API(ANY_GL)
|
||||
gpuCore = GPUCORE_GLES;
|
||||
#else
|
||||
gpuCore = GPUCORE_DIRECTX11;
|
||||
#endif
|
||||
} else if (!strncmp(argv[i], "--screenshot=", strlen("--screenshot=")) && strlen(argv[i]) > strlen("--screenshot="))
|
||||
screenshotFilename = argv[i] + strlen("--screenshot=");
|
||||
else if (!strncmp(argv[i], "--screenshot-save=", strlen("--screenshot-save=")) && strlen(argv[i]) > strlen("--screenshot-save="))
|
||||
screenshotSavePath = argv[i] + strlen("--screenshot-save=");
|
||||
else if (!strncmp(argv[i], "--timeout=", strlen("--timeout=")) && strlen(argv[i]) > strlen("--timeout="))
|
||||
testOptions.timeout = strtod(argv[i] + strlen("--timeout="), nullptr);
|
||||
else if (!strncmp(argv[i], "--max-mse=", strlen("--max-mse=")) && strlen(argv[i]) > strlen("--max-mse="))
|
||||
testOptions.maxScreenshotError = strtod(argv[i] + strlen("--max-mse="), nullptr);
|
||||
else if (!strncmp(argv[i], "--debugger=", strlen("--debugger=")) && strlen(argv[i]) > strlen("--debugger="))
|
||||
@@ -447,11 +419,14 @@ int main(int argc, const char* argv[])
|
||||
if (++i >= argc)
|
||||
return printUsage(argv[0], "Missing argument after --ignore");
|
||||
ignoredTests.push_back(argv[i]);
|
||||
} else {
|
||||
AddTestsByPath(&testFilenames, argv[i]);
|
||||
}
|
||||
}
|
||||
|
||||
for (const std::string &filename : cmdLineOptions.bootFilenames) {
|
||||
AddToTestsByPath(&testFilenames, filename);
|
||||
}
|
||||
|
||||
|
||||
if (testFilenames.size() == 1 && testFilenames[0][0] == '@')
|
||||
testFilenames = ReadFromListFile(testFilenames[0].substr(1));
|
||||
|
||||
@@ -481,6 +456,8 @@ int main(int argc, const char* argv[])
|
||||
g_logManager.EnableOutput(LogOutput::Printf);
|
||||
}
|
||||
|
||||
cmdLineOptions.ApplyToConfig();
|
||||
|
||||
// Needs to be after log so we don't interfere with test output.
|
||||
g_threadManager.Init(cpu_info.num_cores, cpu_info.logical_cpu_count);
|
||||
|
||||
@@ -496,8 +473,8 @@ int main(int argc, const char* argv[])
|
||||
coreParameter.gpuCore = glWorking ? gpuCore : GPUCORE_SOFTWARE;
|
||||
coreParameter.graphicsContext = graphicsContext;
|
||||
coreParameter.enableSound = false;
|
||||
coreParameter.mountIso = mountIso ? Path(mountIso) : Path();
|
||||
coreParameter.mountRoot = mountRoot ? Path(mountRoot) : Path();
|
||||
coreParameter.mountIso = mountIso.empty() ? Path() : Path(mountIso);
|
||||
coreParameter.mountRoot = mountRoot.empty() ? Path() : Path(mountRoot);
|
||||
coreParameter.startBreak = false;
|
||||
coreParameter.headLess = true;
|
||||
coreParameter.renderScaleFactor = 1;
|
||||
@@ -554,8 +531,6 @@ int main(int argc, const char* argv[])
|
||||
g_Config.bUseOldAtrac = oldAtrac;
|
||||
g_Config.iForceEnableHLE = 0xFFFFFFFF; // Run all modules as HLE. We don't have anything to load in this context.
|
||||
|
||||
// g_Config.bUseOldAtrac = true;
|
||||
|
||||
Path exePath = File::GetExeDirectory();
|
||||
g_Config.flash0Directory = exePath / "assets/flash0";
|
||||
|
||||
@@ -584,10 +559,10 @@ int main(int argc, const char* argv[])
|
||||
nextPath = nextPath.NavigateUp();
|
||||
}
|
||||
|
||||
if (screenshotFilename)
|
||||
headlessHost->SetComparisonScreenshot(Path(std::string(screenshotFilename)), testOptions.maxScreenshotError);
|
||||
if (screenshotSavePath)
|
||||
headlessHost->SetScreenshotSavePath(Path(std::string(screenshotSavePath)));
|
||||
if (cmdLineOptions.screenshotFilename.has_value())
|
||||
headlessHost->SetComparisonScreenshot(Path(std::string(cmdLineOptions.screenshotFilename.value())), testOptions.maxScreenshotError);
|
||||
if (cmdLineOptions.screenshotFilenameSave.has_value())
|
||||
headlessHost->SetScreenshotSavePath(Path(std::string(cmdLineOptions.screenshotFilenameSave.value())));
|
||||
headlessHost->SetWriteFailureScreenshot(!getenv("GITHUB_ACTIONS") && !testOptions.bench);
|
||||
headlessHost->SetWriteDebugOutput(!testOptions.compare && !testOptions.bench);
|
||||
|
||||
|
||||
@@ -84,6 +84,7 @@
|
||||
#include "Common/File/VFS/VFS.h"
|
||||
#include "Common/File/VFS/DirectoryReader.h"
|
||||
#include "Common/Math/fast/fast_matrix.h"
|
||||
#include "Core/CmdLine.h"
|
||||
#include "Core/FileSystems/ISOFileSystem.h"
|
||||
#include "Core/MemMap.h"
|
||||
#include "Core/KeyMap.h"
|
||||
@@ -1310,6 +1311,44 @@ bool TestFriendlyPath() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TestCmdLine() {
|
||||
{
|
||||
const char *argv[] = {
|
||||
"ppsspp",
|
||||
"--fullscreen",
|
||||
"--graphics=d3d11",
|
||||
"--pause-menu-exit",
|
||||
"--timeout=3",
|
||||
"My_Game.iso"
|
||||
};
|
||||
int argc = ARRAY_SIZE(argv);
|
||||
CommandLineOptions options;
|
||||
options.Parse(argc, argv, CmdLineMode::Application);
|
||||
EXPECT_TRUE(options.fullscreen.value_or(false));
|
||||
if (options.bootFilenames.empty()) {
|
||||
EXPECT_TRUE(false);
|
||||
return false;
|
||||
}
|
||||
EXPECT_EQ_STR(options.bootFilenames[0], std::string("My_Game.iso"));
|
||||
EXPECT_TRUE(options.gpuBackend.has_value());
|
||||
EXPECT_EQ_INT((int)options.gpuBackend.value_or((GPUBackend)-1), (int)GPUBackend::DIRECT3D11);
|
||||
EXPECT_EQ_INT(options.timeout.value_or(0), 3);
|
||||
EXPECT_TRUE(options.pauseMenuExit.value_or(false));
|
||||
}
|
||||
// Test GL version override
|
||||
{
|
||||
const char *argv[] = {
|
||||
"ppsspp",
|
||||
"--graphics=gles3.3",
|
||||
};
|
||||
int argc = ARRAY_SIZE(argv);
|
||||
CommandLineOptions options;
|
||||
options.Parse(argc, argv);
|
||||
EXPECT_EQ_INT(options.force_gl_version, 33);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check that RTTI is working.
|
||||
bool TestLang() {
|
||||
struct Base { virtual ~Base() = default; };
|
||||
@@ -1393,6 +1432,7 @@ TestItem availableTests[] = {
|
||||
TEST_ITEM(FriendlyPath),
|
||||
TEST_ITEM(LinAlg),
|
||||
TEST_ITEM(Lang),
|
||||
TEST_ITEM(CmdLine),
|
||||
};
|
||||
|
||||
int main(int argc, const char *argv[]) {
|
||||
|
||||
Reference in new issue
Block a user