CmdLine: Take integer parameters in hex too, and refuse trailing junk

They were read with sscanf("%d"), which stops at the first character it
can't use, so --disable-hle=0x110 was accepted as 0 and quietly ran
everything on our HLE. Now decimal or 0x hex, optionally signed, and the
whole value has to parse. Up to 0xFFFFFFFF is taken, for full masks.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Henrik RydgårdandClaude Opus 5.5 committed 2026-10-22 21:47:39 +02:00
1 parent 6c8c71a125
commit ddfd3bcfb9
2 files changed
+57 -1

No files matched your search

+30 -1
View File
@@ -1,5 +1,6 @@
#include <algorithm>
#include <cctype>
#include <cerrno>
#include <cstdlib>
#include <cstring>
@@ -51,6 +52,34 @@ enum class ParseParamResult {
BadValue,
};
// Decimal, or hex with 0x, optionally signed. Not base 0, which would read a leading 0 as octal.
// Up to 0xFFFFFFFF is accepted so a full 32-bit mask can be given, and wraps into the int.
static bool ParseIntParam(const std::string &value, int *out) {
const char *p = value.c_str();
bool negative = false;
if (*p == '-' || *p == '+') {
negative = *p == '-';
p++;
}
int base = 10;
if (p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) {
base = 16;
p += 2;
}
// strtoull would take another sign or leading spaces here.
if (!isxdigit((unsigned char)*p)) {
return false;
}
char *end = nullptr;
errno = 0;
const unsigned long long magnitude = strtoull(p, &end, base);
if (*end != '\0' || errno == ERANGE || magnitude > (negative ? 0x80000000ULL : 0xFFFFFFFFULL)) {
return false;
}
*out = negative ? (int)(0 - (uint32_t)magnitude) : (int)(uint32_t)magnitude;
return true;
}
ParseParamResult SetValue(CommandLineOptions *options, const CommandLineParam &param, const std::string &value) {
uint8_t *optionsPtr = reinterpret_cast<uint8_t *>(options);
switch (param.type) {
@@ -77,7 +106,7 @@ ParseParamResult SetValue(CommandLineOptions *options, const CommandLineParam &p
{
// Not std::stoi - that throws on junk, which takes the process down with no diagnostic.
int v;
if (sscanf(value.c_str(), "%d", &v) != 1) {
if (!ParseIntParam(value, &v)) {
PRINT_STDERR("Error: Invalid value for integer parameter --%s: '%s'.\n", param.longName, value.c_str());
return ParseParamResult::BadValue;
}
+27
View File
@@ -3391,6 +3391,33 @@ bool TestCmdLine() {
EXPECT_TRUE(options.memStickInserted.has_value());
EXPECT_FALSE(options.memStickInserted.value_or(true));
}
// Integers take decimal or 0x hex. A leading 0 is still decimal, and a full 32-bit mask wraps
// into the int rather than being refused.
{
const char *argv[] = {
"ppsspp",
"--disable-hle=0x110",
"--force-hle=0xFFFFFFFF",
"--cpu-mhz=0333",
"--resolution-scale=-2",
};
int argc = ARRAY_SIZE(argv);
CommandLineOptions options;
EXPECT_EQ_INT((int)options.Parse(argc, argv, CmdLineMode::Headless), (int)CommandLineParseResult::Continue);
EXPECT_EQ_INT(options.disableHLE.value_or(0), 0x110);
EXPECT_EQ_INT(options.forceHLE.value_or(0), -1);
EXPECT_EQ_INT(options.lockedCPUSpeed.value_or(0), 333);
EXPECT_EQ_INT(options.resolutionScale.value_or(0), -2);
}
// Trailing junk used to be ignored, so 0x110 quietly meant 0.
{
const char *bad[] = { "--disable-hle=0x110junk", "--disable-hle=12abc", "--disable-hle=0x", "--disable-hle= 5", "--disable-hle=0x100000000", "--disable-hle=--1" };
for (const char *arg : bad) {
const char *argv[] = { "ppsspp", arg };
CommandLineOptions options;
EXPECT_EQ_INT((int)options.Parse(ARRAY_SIZE(argv), argv, CmdLineMode::Headless), (int)CommandLineParseResult::Error);
}
}
// Test GL version override
{
const char *argv[] = {