More cmdline work.

This commit is contained in:
Henrik Rydgård
2026-07-20 16:33:03 +02:00
parent cef7da49b3
commit e6875fa339
5 changed files with 139 additions and 54 deletions
+113 -30
View File
@@ -16,51 +16,121 @@
#endif
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;
};
#define POFF(member) \
{offsetof(CommandLineOptions, member)}
enum class ParseParamResult {
Success,
NoMatch,
BadValue,
};
ParseParamResult SetValue(CommandLineOptions *options, const CommandLineParam &param, 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 bool ParseParameterWithArg(int argc, const char *argv[], size_t &i,
const char *longName, char shortName,
const char **outValue) {
static ParseParamResult ParseParameterStr(int argc, const char *argv[], size_t &i, const CommandLineParam &param, CommandLineOptions *options) {
const char *arg = argv[i];
const size_t len = strlen(arg);
// Check for short form: -p value
if (shortName && len == 2 && arg[0] == '-' && arg[1] == shortName) {
if (param.shortName && len == 2 && arg[0] == '-' && arg[1] == param.shortName) {
if (i + 1 < argc) {
*outValue = argv[i + 1];
ParseParamResult result = SetValue(options, param, argv[i + 1]);
i += 2; // Skip both -p and value
return true;
return result;
}
PRINT_STDERR("Error: -%c requires an argument.\n", shortName);
return false;
PRINT_STDERR("Error: -%c requires an argument.\n", param.shortName);
return ParseParamResult::BadValue;
}
// Check for long form with equals: --param=value
if (longName) {
std::string prefix = std::string("--") + longName + "=";
if (param.longName) {
std::string prefix = std::string("--") + param.longName + "=";
if (startsWith(arg, prefix)) {
*outValue = arg + prefix.size();
ParseParamResult result = SetValue(options, param, arg + prefix.size());
i++; // Skip --param=value
return true;
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("--") + longName)) {
if (equals(arg, std::string("--") + param.longName)) {
if (i + 1 < argc) {
*outValue = argv[i + 1];
ParseParamResult result = SetValue(options, param, argv[i + 1]);
i += 2; // Skip both --param and value
return true;
return result;
}
PRINT_STDERR("Error: --%s requires an argument.\n", longName);
return false;
PRINT_STDERR("Error: --%s requires an argument.\n", param.longName);
return ParseParamResult::BadValue;
}
}
return false;
return ParseParamResult::NoMatch;
}
static const CommandLineParam g_autoParams[] = {
{POFF(fullscreen), CmdParamType::Bool, "fullscreen", 0, "Force full screen mode"},
{POFF(fullscreen), CmdParamType::BoolInverse, "windowed", 0, "Force windowed mode"},
};
static int printUsage(int argc, const char *argv[]) {
// NOTE: by convention, --help outputs to stdout,
// not to stderr, since it is intended output in this
@@ -88,13 +158,17 @@ 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");
for (const auto &param : g_autoParams) {
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);
}
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(" --ipad set resolution to 1024x768\n");
PRINT_STDOUT(" --portrait portrait mode\n");
PRINT_STDOUT(" --graphics=BACKEND use a different gpu backend\n");
PRINT_STDOUT(" options: gles, software, etc. (also opengl3.1, etc.)\n");
@@ -108,6 +182,7 @@ static int printUsage(int argc, const char *argv[]) {
return 0;
}
// Logging should be done with plain printf here.
// Error reporting is done with PRINT_STDERR(....).
// Actually might want to reconsider given Android...
@@ -163,19 +238,28 @@ CommandLineParseResult CommandLineOptions::Parse(int argc, const char *argv[]) {
// Ignore
}
#endif
// Simple bool commands
// NOTE: We need to parse --fullscreen early, before we create the window.
if (equals(argv[i], "--help")) {
bool parsedAutoParam = false;
// Loop through all the auto commands, call ParseParameterWithArg to handle them.
for (const auto &param : g_autoParams) {
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);
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
} else if (startsWith(argv[i], gpuBackendStr)) {
const std::string_view restOfOption = argv[i] + gpuBackendStr.size();
@@ -203,7 +287,6 @@ CommandLineParseResult CommandLineOptions::Parse(int argc, const char *argv[]) {
} else {
// Report unknown argument later once this is complete.
}
// To the next argument.
i++;
}
+9 -21
View File
@@ -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;
@@ -1461,10 +1459,7 @@ int main(int argc, char *argv[]) {
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,10 +1475,6 @@ 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.
@@ -1556,6 +1547,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 +1557,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;
cmdLineParams.fullscreen = true;
}
// If we're on mobile, don't try for windowed either.
#if defined(MOBILE_DEVICE) && !PPSSPP_PLATFORM(SWITCH)
mode |= SDL_WINDOW_FULLSCREEN;
cmdLineParams.fullscreen = true;
#elif defined(USING_FBDEV) || PPSSPP_PLATFORM(SWITCH)
mode |= SDL_WINDOW_FULLSCREEN;
cmdLineParams.fullscreen = true;
#else
mode |= SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY;
#endif
if (mode & SDL_WINDOW_FULLSCREEN) {
if (cmdLineParams.fullscreen) {
g_display.pixel_xres = g_DesktopWidth;
g_display.pixel_yres = g_DesktopHeight;
g_Config.bFullScreen = true;
@@ -1585,16 +1577,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 +1621,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.
-2
View File
@@ -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 -1
View File
@@ -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;
+16
View File
@@ -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,20 @@ bool TestFriendlyPath() {
return true;
}
bool TestCmdLine() {
const char *argv[] = {
"ppsspp",
"--fullscreen",
"My_Game.iso"
};
int argc = ARRAY_SIZE(argv);
CommandLineOptions options;
options.Parse(argc, argv);
EXPECT_TRUE(options.fullscreen.value_or(false));
EXPECT_EQ_STR(options.bootFilename.value_or(""), std::string("My_Game.iso"));
return true;
}
// Check that RTTI is working.
bool TestLang() {
struct Base { virtual ~Base() = default; };
@@ -1393,6 +1408,7 @@ TestItem availableTests[] = {
TEST_ITEM(FriendlyPath),
TEST_ITEM(LinAlg),
TEST_ITEM(Lang),
TEST_ITEM(CmdLine),
};
int main(int argc, const char *argv[]) {