mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-01 14:58:14 +00:00
Merge pull request #21991 from hrydgard/cmdline-parse-improvements
Command line parse improvements (some new arguments), RAIntegration fixes
This commit is contained in:
24 files changed
+1138
-513
No files matched your search
@@ -630,7 +630,7 @@ bool Delete(const Path &filename, bool quiet) {
|
||||
}
|
||||
|
||||
// Returns true if successful, or path already exists.
|
||||
bool CreateDir(const Path &path) {
|
||||
bool CreateDir(const Path &path, bool quiet) {
|
||||
if (SIMULATE_SLOW_IO) {
|
||||
sleep_ms(100, "slow-io-sim");
|
||||
INFO_LOG(Log::IO, "CreateDir %s", path.c_str());
|
||||
@@ -651,11 +651,15 @@ bool CreateDir(const Path &path) {
|
||||
AndroidContentURI uri(path.ToString());
|
||||
std::string newDirName = uri.GetLastPart();
|
||||
if (uri.NavigateUp()) {
|
||||
INFO_LOG(Log::IO, "Calling Android_CreateDirectory(%s, %s)", uri.ToString().c_str(), newDirName.c_str());
|
||||
if (!quiet) {
|
||||
INFO_LOG(Log::IO, "Calling Android_CreateDirectory(%s, %s)", uri.ToString().c_str(), newDirName.c_str());
|
||||
}
|
||||
return Android_CreateDirectory(uri.ToString(), newDirName) == StorageError::SUCCESS;
|
||||
} else {
|
||||
// Bad path - can't create this directory.
|
||||
WARN_LOG(Log::IO, "CreateDir failed: '%s'", path.c_str());
|
||||
if (!quiet) {
|
||||
WARN_LOG(Log::IO, "CreateDir failed: '%s'", path.c_str());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
@@ -686,10 +690,14 @@ bool CreateDir(const Path &path) {
|
||||
|
||||
DWORD error = GetLastError();
|
||||
if (error == ERROR_ALREADY_EXISTS) {
|
||||
DEBUG_LOG(Log::IO, "CreateDir: CreateDirectory failed on %s: already exists", path.c_str());
|
||||
if (!quiet) {
|
||||
DEBUG_LOG(Log::IO, "CreateDir: CreateDirectory failed on %s: already exists", path.c_str());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
ERROR_LOG(Log::IO, "CreateDir: CreateDirectory failed on %s: %08x %s", path.c_str(), (uint32_t)error, GetStringErrorMsg(error).c_str());
|
||||
if (!quiet) {
|
||||
ERROR_LOG(Log::IO, "CreateDir: CreateDirectory failed on %s: %08x %s", path.c_str(), (uint32_t)error, GetStringErrorMsg(error).c_str());
|
||||
}
|
||||
return false;
|
||||
#else
|
||||
if (mkdir(path.ToString().c_str(), 0755) == 0) {
|
||||
@@ -698,11 +706,15 @@ bool CreateDir(const Path &path) {
|
||||
|
||||
int err = errno;
|
||||
if (err == EEXIST) {
|
||||
DEBUG_LOG(Log::IO, "CreateDir: mkdir failed on %s: already exists", path.c_str());
|
||||
if (!quiet) {
|
||||
DEBUG_LOG(Log::IO, "CreateDir: mkdir failed on %s: already exists", path.c_str());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
ERROR_LOG(Log::IO, "CreateDir: mkdir failed on %s: %s", path.c_str(), strerror(err));
|
||||
if (!quiet) {
|
||||
ERROR_LOG(Log::IO, "CreateDir: mkdir failed on %s: %s", path.c_str(), strerror(err));
|
||||
}
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
@@ -710,7 +722,7 @@ bool CreateDir(const Path &path) {
|
||||
// Creates the full path of fullPath returns true on success
|
||||
bool CreateFullPath(const Path &path) {
|
||||
if (File::Exists(path)) {
|
||||
DEBUG_LOG(Log::IO, "CreateFullPath: path exists %s", path.ToVisualString().c_str());
|
||||
VERBOSE_LOG(Log::IO, "CreateFullPath: path exists %s", path.ToVisualString().c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ uint64_t GetFileSize(FILE *f);
|
||||
uint64_t ComputeRecursiveDirectorySize(const Path &path);
|
||||
|
||||
// Returns true if successful, or path already exists.
|
||||
bool CreateDir(const Path &filename);
|
||||
bool CreateDir(const Path &filename, bool quiet = false);
|
||||
|
||||
void ChangeMTime(const Path &path, time_t mtime);
|
||||
|
||||
|
||||
+44
-43
@@ -27,7 +27,6 @@
|
||||
#include <cstring>
|
||||
|
||||
#include "Common/Data/Encoding/Utf8.h"
|
||||
|
||||
#include "Common/Log/LogManager.h"
|
||||
|
||||
#if PPSSPP_PLATFORM(WINDOWS)
|
||||
@@ -70,50 +69,50 @@ void GenericLog(Log type, LogLevel level, const char *file, int line, const char
|
||||
|
||||
// NOTE: Needs to be kept in sync with the Log enum.
|
||||
static const char * const g_logTypeNames[] = {
|
||||
"SYSTEM",
|
||||
"BOOT",
|
||||
"COMMON",
|
||||
"System",
|
||||
"Boot",
|
||||
"Common",
|
||||
"CPU",
|
||||
"FILESYS",
|
||||
"FileSystem",
|
||||
"G3D",
|
||||
"TEXCACHE",
|
||||
"TexCache",
|
||||
"HLE",
|
||||
"JIT",
|
||||
"LOADER",
|
||||
"MPEG",
|
||||
"ATRAC",
|
||||
"ME", // Rest of the media Engine
|
||||
"MEMMAP",
|
||||
"SASMIX",
|
||||
"SAVESTATE",
|
||||
"FRAMEBUF",
|
||||
"AUDIO",
|
||||
"Loader",
|
||||
"Mpeg",
|
||||
"Atrac",
|
||||
"ME",
|
||||
"MemMap",
|
||||
"SasMix",
|
||||
"SaveState",
|
||||
"FrameBuf",
|
||||
"Audio",
|
||||
"IO",
|
||||
"ACHIEVEMENTS",
|
||||
"Achievements",
|
||||
"HTTP",
|
||||
"PRINTF",
|
||||
"TEXREPLACE",
|
||||
"DEBUGGER",
|
||||
"GEDEBUGGER",
|
||||
"Printf",
|
||||
"TexReplacement",
|
||||
"Debugger",
|
||||
"GeDebugger",
|
||||
"UI",
|
||||
"IAP",
|
||||
"CWCHEATS",
|
||||
"NET",
|
||||
"SCEAUDIO",
|
||||
"SCECTRL",
|
||||
"SCEDISP",
|
||||
"SCEFONT",
|
||||
"SCEGE",
|
||||
"SCEINTC",
|
||||
"SCEIO",
|
||||
"SCEKERNEL",
|
||||
"SCEMODULE",
|
||||
"SCENET",
|
||||
"SCERTC",
|
||||
"SCESAS",
|
||||
"SCEUTIL",
|
||||
"SCEMISC",
|
||||
"SCEREG",
|
||||
"CwCheats",
|
||||
"Net",
|
||||
"sceAudio",
|
||||
"sceCtrl",
|
||||
"sceDisplay",
|
||||
"sceFont",
|
||||
"sceGe",
|
||||
"sceIntc",
|
||||
"sceIo",
|
||||
"sceKernel",
|
||||
"sceModule",
|
||||
"sceNet",
|
||||
"sceRtc",
|
||||
"sceSas",
|
||||
"sceUtility",
|
||||
"sceMisc",
|
||||
"sceReg",
|
||||
};
|
||||
|
||||
const char *LogManager::GetLogTypeName(Log type) {
|
||||
@@ -469,25 +468,27 @@ void LogManager::StdioLog(const LogMessage &message) {
|
||||
}
|
||||
|
||||
void PrintfLog(const LogMessage &message) {
|
||||
const char *category = message.log;
|
||||
|
||||
switch (message.level) {
|
||||
case LogLevel::LVERBOSE:
|
||||
fprintf(stderr, "V %s", message.msg.c_str());
|
||||
fprintf(stderr, "V %s: %s", category, message.msg.c_str());
|
||||
break;
|
||||
case LogLevel::LDEBUG:
|
||||
fprintf(stderr, "D %s", message.msg.c_str());
|
||||
fprintf(stderr, "D %s: %s", category, message.msg.c_str());
|
||||
break;
|
||||
case LogLevel::LINFO:
|
||||
fprintf(stderr, "I %s", message.msg.c_str());
|
||||
fprintf(stderr, "I %s: %s", category, message.msg.c_str());
|
||||
break;
|
||||
case LogLevel::LERROR:
|
||||
fprintf(stderr, "E %s", message.msg.c_str());
|
||||
fprintf(stderr, "E %s: %s", category, message.msg.c_str());
|
||||
break;
|
||||
case LogLevel::LWARNING:
|
||||
fprintf(stderr, "W %s", message.msg.c_str());
|
||||
fprintf(stderr, "W %s: %s", category, message.msg.c_str());
|
||||
break;
|
||||
case LogLevel::LNOTICE:
|
||||
default:
|
||||
fprintf(stderr, "N %s", message.msg.c_str());
|
||||
fprintf(stderr, "N %s: %s", category, message.msg.c_str());
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -65,6 +65,8 @@ void TimeInit() {
|
||||
|
||||
// The timer will be automatically deleted on process destruction. Don't need to CloseHandle.
|
||||
Timer = CreateWaitableTimerExW(NULL, NULL, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS);
|
||||
|
||||
// TODO: We probably don't need this anymore if we are using the high res waitable timers?
|
||||
#if !PPSSPP_PLATFORM(UWP)
|
||||
TIMECAPS caps;
|
||||
timeGetDevCaps(&caps, sizeof caps);
|
||||
@@ -73,6 +75,12 @@ void TimeInit() {
|
||||
#endif
|
||||
}
|
||||
|
||||
void TimeShutdown() {
|
||||
#if PPSSPP_PLATFORM(WINDOWS) && !PPSSPP_PLATFORM(UWP)
|
||||
timeEndPeriod(1);
|
||||
#endif
|
||||
}
|
||||
|
||||
double time_now_d() {
|
||||
LARGE_INTEGER time;
|
||||
QueryPerformanceCounter(&time);
|
||||
@@ -141,6 +149,10 @@ void TimeInit() {
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
void TimeShutdown() {
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
// The only intended use is to match the timings in VK_GOOGLE_display_timing
|
||||
uint64_t time_now_raw() {
|
||||
struct timespec tp;
|
||||
@@ -210,6 +222,10 @@ void TimeInit() {
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
void TimeShutdown() {
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
static time_t start;
|
||||
|
||||
double time_now_d() {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include <cstdint>
|
||||
|
||||
void TimeInit();
|
||||
void TimeShutdown(); // Not really necessary to call, just for completeness.
|
||||
|
||||
// Seconds.
|
||||
double time_now_d();
|
||||
|
||||
+97
-19
@@ -23,6 +23,7 @@ enum class CmdParamType {
|
||||
Int,
|
||||
Double,
|
||||
String,
|
||||
Enum,
|
||||
};
|
||||
|
||||
struct CommandLineParam {
|
||||
@@ -32,6 +33,8 @@ struct CommandLineParam {
|
||||
char shortName; // can be 0 for no short name
|
||||
const char *docString;
|
||||
CmdLineMode mode;
|
||||
const char **enumValues;
|
||||
int enumCount;
|
||||
};
|
||||
|
||||
enum class ParseParamResult {
|
||||
@@ -41,6 +44,7 @@ enum class ParseParamResult {
|
||||
};
|
||||
|
||||
ParseParamResult SetValue(CommandLineOptions *options, const CommandLineParam ¶m, const std::string &value) {
|
||||
uint8_t *optionsPtr = reinterpret_cast<uint8_t *>(options);
|
||||
switch (param.type) {
|
||||
case CmdParamType::Bool:
|
||||
case CmdParamType::BoolInverse:
|
||||
@@ -58,18 +62,37 @@ ParseParamResult SetValue(CommandLineOptions *options, const CommandLineParam &p
|
||||
if (param.type == CmdParamType::BoolInverse) {
|
||||
v = !v;
|
||||
}
|
||||
*reinterpret_cast<std::optional<bool> *>(reinterpret_cast<uint8_t *>(options) + param.offsetInStruct) = v;
|
||||
*reinterpret_cast<std::optional<bool> *>(optionsPtr + param.offsetInStruct) = v;
|
||||
break;
|
||||
}
|
||||
case CmdParamType::Int:
|
||||
*reinterpret_cast<std::optional<int> *>(reinterpret_cast<uint8_t *>(options) + param.offsetInStruct) = std::stoi(value);
|
||||
*reinterpret_cast<std::optional<int> *>(optionsPtr + param.offsetInStruct) = std::stoi(value);
|
||||
break;
|
||||
case CmdParamType::Double:
|
||||
*reinterpret_cast<std::optional<double> *>(reinterpret_cast<uint8_t *>(options) + param.offsetInStruct) = std::stod(value);
|
||||
*reinterpret_cast<std::optional<double> *>(optionsPtr + param.offsetInStruct) = std::stod(value);
|
||||
break;
|
||||
case CmdParamType::String:
|
||||
*reinterpret_cast<std::optional<std::string> *>(reinterpret_cast<uint8_t *>(options) + param.offsetInStruct) = value;
|
||||
*reinterpret_cast<std::optional<std::string> *>(optionsPtr + param.offsetInStruct) = value;
|
||||
break;
|
||||
case CmdParamType::Enum:
|
||||
{
|
||||
int enumIndex = -1;
|
||||
for (int i = 0; i < param.enumCount; ++i) {
|
||||
if (value == param.enumValues[i]) {
|
||||
enumIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (enumIndex == -1) {
|
||||
PRINT_STDERR("Error: Invalid value for parameter --%s: '%s'. Expected one of: ", param.longName, value.c_str());
|
||||
for (int i = 0; i < param.enumCount; ++i) {
|
||||
PRINT_STDERR("%s%s", param.enumValues[i], (i + 1 < param.enumCount) ? ", " : "\n");
|
||||
}
|
||||
return ParseParamResult::BadValue;
|
||||
}
|
||||
*reinterpret_cast<std::optional<int> *>(optionsPtr + param.offsetInStruct) = enumIndex;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -132,6 +155,14 @@ static ParseParamResult ParseParameterStr(int argc, const char *argv[], size_t &
|
||||
return ParseParamResult::NoMatch;
|
||||
}
|
||||
|
||||
// Enum ExceptionAction
|
||||
const char *g_ExceptionActionValues[] = {
|
||||
"default",
|
||||
"ignore",
|
||||
"log",
|
||||
"break",
|
||||
};
|
||||
|
||||
#define POFF(member) offsetof(CommandLineOptions, member)
|
||||
static const CommandLineParam g_autoParams[] = {
|
||||
{POFF(fullscreen), CmdParamType::Bool, "fullscreen", '\0', "Force full screen mode", CmdLineMode::Application},
|
||||
@@ -151,31 +182,46 @@ static const CommandLineParam g_autoParams[] = {
|
||||
{POFF(timeout), CmdParamType::Double, "timeout", '\0', "Set the timeout value"},
|
||||
{POFF(resolutionScale), CmdParamType::Int, "resolution-scale", '\0', "Set the resolution scale factor"},
|
||||
{POFF(debuggerPort), CmdParamType::Int, "debugger", '\0', "Enable the WebSocket debugger on this port (0 = pick automatically); see docs/WebSocketDebugger.md"},
|
||||
{POFF(bootVSH), CmdParamType::Bool, "vsh", '\0', "Boot the VSH (requires files dumped from a PSP in the flash0 directory)"},
|
||||
{POFF(memReadAction), CmdParamType::Enum, "memread", '\0', "Set the action for memory read exceptions", CmdLineMode::Both, g_ExceptionActionValues, ARRAY_SIZE(g_ExceptionActionValues)},
|
||||
{POFF(memWriteAction), CmdParamType::Enum, "memwrite", '\0', "Set the action for memory write exceptions", CmdLineMode::Both, g_ExceptionActionValues, ARRAY_SIZE(g_ExceptionActionValues)},
|
||||
{POFF(breakAction), CmdParamType::Enum, "break", '\0', "Set the action for break exceptions", CmdLineMode::Both, g_ExceptionActionValues, ARRAY_SIZE(g_ExceptionActionValues)},
|
||||
{POFF(verbose), CmdParamType::Bool, "verbose", '\0', "Enable verbose output", CmdLineMode::Both},
|
||||
|
||||
// TODO: At some point we should maybe simply expose all config settings to be set directly from the command line automatically?
|
||||
};
|
||||
|
||||
// NOTE: On Windows this prints nothing unfortunately, since PPSSPP is not a "console app".
|
||||
// NOTE: On Windows this prints nothing unfortunately, since PPSSPP is not a "console app" (PPSSPPHeadless is though, there it works).
|
||||
// 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) {
|
||||
int CommandLineOptions::PrintUsage(const char *progname, const char *situationText) const {
|
||||
// NOTE: by convention, --help outputs to stdout,
|
||||
// not to stderr, since it is intended output in this
|
||||
// case (usage printed under different circumstances,
|
||||
// say in response to error during parsing commandline,
|
||||
// may go to stderr).
|
||||
const char *progname = argc > 0 ? argv[0] : "ppsspp";
|
||||
// NOTE: wording largely taken from
|
||||
// https://www.ppsspp.org/docs/reference/command-line/
|
||||
if (mode == CmdLineMode::Application) {
|
||||
PRINT_STDOUT("PPSSPP - a PSP emulator\n");
|
||||
if (situationText) {
|
||||
PRINT_STDOUT("%s\n\n", situationText);
|
||||
} else {
|
||||
PRINT_STDOUT("PPSSPP Headless\n");
|
||||
PRINT_STDOUT("This is primarily meant as a non-interactive test tool.\n\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");
|
||||
|
||||
if (mode == CmdLineMode::Application) {
|
||||
PRINT_STDOUT("Usage: %s [options] [FILE]\n\n", progname);
|
||||
} else {
|
||||
PRINT_STDOUT("Usage: %s [options] [FILE1] [FILE2] [FILE3]...\n\n", progname);
|
||||
}
|
||||
|
||||
PRINT_STDOUT("Launches FILE (for example, an ISO image) if present.\n");
|
||||
PRINT_STDOUT("See headless/README.md for more details.\n\n");
|
||||
PRINT_STDOUT("Options:\n");
|
||||
PRINT_STDOUT(" -h, --help show this message and exit\n");
|
||||
PRINT_STDOUT(" --version show version information and exit\n");
|
||||
|
||||
@@ -195,10 +241,24 @@ static int printUsage(int argc, const char *argv[], CmdLineMode mode) {
|
||||
// Skip mode-irrelevant parameters in help.
|
||||
continue;
|
||||
}
|
||||
PRINT_STDOUT(" --%s%s%s\n", param.longName,
|
||||
char key[25]{};
|
||||
snprintf(key, ARRAY_SIZE(key), " --%s%s%s", param.longName,
|
||||
param.shortName ? ", -" : "",
|
||||
param.shortName ? std::string(1, param.shortName).c_str() : "");
|
||||
PRINT_STDOUT(" %s\n", param.docString);
|
||||
// Fill key with spacing.
|
||||
for (size_t j = strlen(key); j < ARRAY_SIZE(key) - 1; ++j) {
|
||||
key[j] = ' ';
|
||||
}
|
||||
if (param.type == CmdParamType::Enum) {
|
||||
PRINT_STDOUT("%s%s\n", key, param.docString);
|
||||
PRINT_STDOUT(" options: ");
|
||||
for (int i = 0; i < param.enumCount; ++i) {
|
||||
PRINT_STDOUT("%s%s", param.enumValues[i], i + 1 < param.enumCount ? ", " : "");
|
||||
}
|
||||
PRINT_STDOUT("\n");
|
||||
} else {
|
||||
PRINT_STDOUT("%s%s\n", key, param.docString);
|
||||
}
|
||||
}
|
||||
|
||||
// These are only available in SDL.
|
||||
@@ -217,6 +277,7 @@ static int printUsage(int argc, const char *argv[], CmdLineMode mode) {
|
||||
// Error reporting is done with PRINT_STDERR(....).
|
||||
// Actually might want to reconsider given Android...
|
||||
CommandLineParseResult CommandLineOptions::Parse(int argc, const char *argv[], CmdLineMode mode) {
|
||||
this->mode = mode;
|
||||
constexpr std::string_view gpuBackendStr = "--graphics=";
|
||||
constexpr std::string_view configOption = "--config=";
|
||||
constexpr std::string_view controlsOption = "--controlconfig=";
|
||||
@@ -259,7 +320,7 @@ CommandLineParseResult CommandLineOptions::Parse(int argc, const char *argv[], C
|
||||
logLevel = LogLevel::LVERBOSE;
|
||||
break;
|
||||
case 'h':
|
||||
printUsage(argc, argv, mode);
|
||||
PrintUsage(argv[0], nullptr);
|
||||
return CommandLineParseResult::Exit;
|
||||
// Legacy cpucore options.
|
||||
case 'j':
|
||||
@@ -305,7 +366,7 @@ CommandLineParseResult CommandLineOptions::Parse(int argc, const char *argv[], C
|
||||
// We already incremented i.
|
||||
continue;
|
||||
} else if (equals(argv[i], "--help")) {
|
||||
printUsage(argc, argv, mode);
|
||||
PrintUsage(argv[0], nullptr);
|
||||
return CommandLineParseResult::Exit;
|
||||
} else if (equals(argv[i], "--version")) {
|
||||
printf("%s\n", PPSSPP_GIT_VERSION);
|
||||
@@ -422,6 +483,23 @@ void CommandLineOptions::ApplyToConfig() const {
|
||||
g_Config.DoNotSaveSetting(&g_Config.iInternalResolution);
|
||||
}
|
||||
|
||||
if (memReadAction.has_value()) {
|
||||
g_Config.iExceptionActionMemRead = memReadAction.value();
|
||||
g_Config.DoNotSaveSetting(&g_Config.iExceptionActionMemRead);
|
||||
}
|
||||
|
||||
if (memWriteAction.has_value()) {
|
||||
g_Config.iExceptionActionMemWrite = memWriteAction.value();
|
||||
g_Config.DoNotSaveSetting(&g_Config.iExceptionActionMemWrite);
|
||||
}
|
||||
|
||||
if (breakAction.has_value()) {
|
||||
g_Config.iExceptionActionBreak = breakAction.value();
|
||||
g_Config.DoNotSaveSetting(&g_Config.iExceptionActionBreak);
|
||||
}
|
||||
|
||||
// --vsh is applied by the caller (by setting their boot file name variable).
|
||||
|
||||
// Note: dpi is not applied here - it's platform-specific.
|
||||
// Platforms should check cmdLineOptions.dpi.has_value() and handle accordingly.
|
||||
}
|
||||
+12
-1
@@ -24,6 +24,9 @@ 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;
|
||||
int PrintUsage(const char *progname, const char *situationText) const;
|
||||
|
||||
CmdLineMode mode;
|
||||
|
||||
std::optional<bool> fullscreen;
|
||||
std::optional<GPUBackend> gpuBackend;
|
||||
@@ -44,10 +47,18 @@ struct CommandLineOptions {
|
||||
// Also breaks the CPU at start in the headless build. See docs/WebSocketDebugger.md.
|
||||
std::optional<int> debuggerPort;
|
||||
|
||||
// Attempts to boot the vsh, which will only work if the correct files are present in the flash
|
||||
// and once we've fixed all the bugs. This is just here to allow testing.
|
||||
std::optional<bool> bootVSH;
|
||||
|
||||
std::optional<std::string> appendConfig;
|
||||
std::optional<std::string> root; // mount root, needs more explanation
|
||||
std::optional<std::string> stateToLoad;
|
||||
|
||||
std::optional<int> memReadAction;
|
||||
std::optional<int> memWriteAction;
|
||||
std::optional<int> breakAction;
|
||||
|
||||
// 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
|
||||
@@ -76,8 +87,8 @@ struct CommandLineOptions {
|
||||
std::optional<bool> compare;
|
||||
std::optional<bool> bench;
|
||||
std::optional<bool> verbose;
|
||||
std::optional<double> timeout;
|
||||
|
||||
std::optional<std::string> screenshotFilename;
|
||||
std::optional<std::string> screenshotFilenameSave;
|
||||
std::optional<double> timeout;
|
||||
};
|
||||
+1
-1
@@ -99,7 +99,7 @@ std::string DefaultLangRegion() {
|
||||
// Unfortunate default. There's no need to use bFirstRun, since this is only a default.
|
||||
static std::string defaultLangRegion = "en_US";
|
||||
std::string langRegion = System_GetProperty(SYSPROP_LANGREGION);
|
||||
if (g_i18nrepo.IniExists(langRegion)) {
|
||||
if (!langRegion.empty() && g_i18nrepo.IniExists(langRegion)) {
|
||||
defaultLangRegion = langRegion;
|
||||
} else if (langRegion.length() >= 3) {
|
||||
// Don't give up. Let's try a fuzzy match - so nl_BE can match nl_NL.
|
||||
|
||||
@@ -213,6 +213,11 @@ public:
|
||||
// Core
|
||||
bool bIgnoreBadMemAccess;
|
||||
|
||||
// ExceptionAction enum: 0 = default (obey bIgnoreBadMemAccess), 1 = log, 2 = break, 3 = exit
|
||||
int iExceptionActionMemRead; // this also includes alignment and other odd memory exceptions.
|
||||
int iExceptionActionMemWrite;
|
||||
int iExceptionActionBreak;
|
||||
|
||||
bool bFastMemory;
|
||||
int iCpuCore;
|
||||
bool bCheckForNewVersion;
|
||||
|
||||
@@ -180,6 +180,13 @@ enum IOTimingMethods {
|
||||
IOTIMING_UMDSLOWREALISTIC = 3,
|
||||
};
|
||||
|
||||
enum class ExceptionAction {
|
||||
Default = 0, // Obey the value of "Ignore bad memory accesses" (that corresponds to Ignore vs Crash)
|
||||
Ignore = 1,
|
||||
Log = 2, // Ignore, but log details
|
||||
Break = 3, // Break execution in the PPSSPP debuggers
|
||||
};
|
||||
|
||||
enum class AutoLoadSaveState {
|
||||
OFF = 0,
|
||||
OLDEST = 1,
|
||||
|
||||
+46
-42
@@ -550,21 +550,46 @@ const char *ExecExceptionTypeAsString(ExecExceptionType type) {
|
||||
}
|
||||
}
|
||||
|
||||
void Core_MemoryException(u32 address, u32 accessSize, u32 pc, MemoryExceptionType type) {
|
||||
static ExceptionAction ResolveExceptionAction(ExceptionAction action) {
|
||||
if (action == ExceptionAction::Default) {
|
||||
return g_Config.bIgnoreBadMemAccess ? ExceptionAction::Ignore : ExceptionAction::Break;
|
||||
}
|
||||
return action;
|
||||
}
|
||||
|
||||
void Core_MemoryException(u32 address, u32 accessSize, u32 pc, MemoryExceptionType type, std::string_view additionalInfo, bool forceReport) {
|
||||
const char *desc = MemoryExceptionTypeAsString(type);
|
||||
// In jit, we only flush PC when bIgnoreBadMemAccess is off.
|
||||
if ((g_Config.iCpuCore == (int)CPUCore::JIT || g_Config.iCpuCore == (int)CPUCore::JIT_IR) && g_Config.bIgnoreBadMemAccess) {
|
||||
WARN_LOG(Log::MemMap, "%s: Invalid access at %08x (size %08x)", desc, address, accessSize);
|
||||
} else {
|
||||
WARN_LOG(Log::MemMap, "%s: Invalid access at %08x (size %08x) PC %08x LR %08x", desc, address, accessSize, currentMIPS->pc, currentMIPS->r[MIPS_REG_RA]);
|
||||
|
||||
char pcDetails[64];
|
||||
pcDetails[0] = 0;
|
||||
if ((CPUCore)g_Config.iCpuCore == CPUCore::INTERPRETER) {
|
||||
snprintf(pcDetails, sizeof(pcDetails), " PC %08x LR %08x", currentMIPS->pc, currentMIPS->r[MIPS_REG_RA]);
|
||||
}
|
||||
|
||||
if (!g_Config.bIgnoreBadMemAccess) {
|
||||
// Try to fetch a call stack, to start with.
|
||||
std::vector<MIPSStackWalk::StackFrame> stackFrames = WalkCurrentStack(-1);
|
||||
std::string stackTrace = FormatStackTrace(stackFrames);
|
||||
WARN_LOG(Log::MemMap, "\n%s", stackTrace.c_str());
|
||||
ExceptionAction action;
|
||||
switch (type) {
|
||||
case MemoryExceptionType::WRITE_WORD:
|
||||
case MemoryExceptionType::WRITE_BLOCK:
|
||||
action = ResolveExceptionAction((ExceptionAction)g_Config.iExceptionActionMemWrite);
|
||||
break;
|
||||
case MemoryExceptionType::READ_WORD:
|
||||
case MemoryExceptionType::READ_BLOCK:
|
||||
default:
|
||||
action = ResolveExceptionAction((ExceptionAction)g_Config.iExceptionActionMemRead);
|
||||
break;
|
||||
}
|
||||
|
||||
if (action == ExceptionAction::Ignore) {
|
||||
// Simplest logging and continue.
|
||||
WARN_LOG(Log::MemMap, "%s: Invalid access at %08x (size %08x) %s%.*s", desc, address, accessSize, pcDetails, (int)additionalInfo.length(), additionalInfo.data());
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string stackTrace = FormatStackTrace(WalkCurrentStack(-1));
|
||||
// Do the most detailed logging we can.
|
||||
ERROR_LOG(Log::MemMap, "%s: Invalid access at %08x (size %08x) %s%.*s\n%s", desc, address, accessSize, pcDetails, (int)additionalInfo.length(), additionalInfo.data(), stackTrace.c_str());
|
||||
if (action == ExceptionAction::Break) {
|
||||
MIPSExceptionInfo &e = g_exceptionInfo;
|
||||
e = {};
|
||||
e.type = MIPSExceptionType::MEMORY;
|
||||
@@ -578,35 +603,7 @@ void Core_MemoryException(u32 address, u32 accessSize, u32 pc, MemoryExceptionTy
|
||||
}
|
||||
}
|
||||
|
||||
void Core_MemoryExceptionInfo(u32 address, u32 accessSize, u32 pc, MemoryExceptionType type, std::string_view additionalInfo, bool forceReport) {
|
||||
const char *desc = MemoryExceptionTypeAsString(type);
|
||||
// In jit, we only flush PC when bIgnoreBadMemAccess is off.
|
||||
if ((g_Config.iCpuCore == (int)CPUCore::JIT || g_Config.iCpuCore == (int)CPUCore::JIT_IR) && g_Config.bIgnoreBadMemAccess) {
|
||||
WARN_LOG(Log::MemMap, "%s: Invalid access at %08x (size %08x). %.*s", desc, address, accessSize, (int)additionalInfo.length(), additionalInfo.data());
|
||||
} else {
|
||||
WARN_LOG(Log::MemMap, "%s: Invalid access at %08x (size %08x) PC %08x LR %08x %.*s", desc, address, accessSize, currentMIPS->pc, currentMIPS->r[MIPS_REG_RA], (int)additionalInfo.length(), additionalInfo.data());
|
||||
}
|
||||
|
||||
if (!g_Config.bIgnoreBadMemAccess || forceReport) {
|
||||
// Try to fetch a call stack, to start with.
|
||||
std::vector<MIPSStackWalk::StackFrame> stackFrames = WalkCurrentStack(-1);
|
||||
std::string stackTrace = FormatStackTrace(stackFrames);
|
||||
WARN_LOG(Log::MemMap, "\n%s", stackTrace.c_str());
|
||||
|
||||
MIPSExceptionInfo &e = g_exceptionInfo;
|
||||
e = {};
|
||||
e.type = MIPSExceptionType::MEMORY;
|
||||
e.info = additionalInfo;
|
||||
e.memory_type = type;
|
||||
e.address = address;
|
||||
e.accessSize = accessSize;
|
||||
e.stackTrace = stackTrace;
|
||||
e.pc = pc;
|
||||
Core_Break(BreakReason::MemoryException, address);
|
||||
}
|
||||
}
|
||||
|
||||
// Can't be ignored
|
||||
// Can't be ignored, must break. Not sure we can get a meaningful stack trace here (since the PC is invalid).
|
||||
void Core_ExecException(u32 address, u32 pc, ExecExceptionType type) {
|
||||
const char *desc = ExecExceptionTypeAsString(type);
|
||||
WARN_LOG(Log::MemMap, "%s: Invalid exec address %08x pc=%08x ra=%08x", desc, address, pc, currentMIPS->r[MIPS_REG_RA]);
|
||||
@@ -625,15 +622,22 @@ void Core_ExecException(u32 address, u32 pc, ExecExceptionType type) {
|
||||
}
|
||||
|
||||
void Core_BreakException(u32 pc) {
|
||||
ERROR_LOG(Log::CPU, "BREAK!");
|
||||
|
||||
MIPSExceptionInfo &e = g_exceptionInfo;
|
||||
e = {};
|
||||
e.type = MIPSExceptionType::BREAK;
|
||||
e.info.clear();
|
||||
e.pc = pc;
|
||||
|
||||
if (!g_Config.bIgnoreBadMemAccess) {
|
||||
const ExceptionAction action = ResolveExceptionAction((ExceptionAction)g_Config.iExceptionActionBreak);
|
||||
if (action == ExceptionAction::Ignore) {
|
||||
// Simplest logging and continue.
|
||||
WARN_LOG(Log::CPU, "CPU exception: break instruction hit at %08x. Ignoring (use --break=log for more details or --break=break to break)", pc);
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string stackTrace = FormatStackTrace(WalkCurrentStack(-1));
|
||||
ERROR_LOG(Log::CPU, "CPU exception: break instruction hit at %08x (ra=%08x)\n%s", pc, currentMIPS->r[MIPS_REG_RA], stackTrace.c_str());
|
||||
if (action == ExceptionAction::Break) {
|
||||
Core_Break(BreakReason::BreakInstruction, currentMIPS->pc);
|
||||
}
|
||||
}
|
||||
|
||||
+2
-5
@@ -22,6 +22,7 @@
|
||||
#include <string_view>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Core/ConfigValues.h"
|
||||
|
||||
class GraphicsContext;
|
||||
|
||||
@@ -166,11 +167,7 @@ enum class ExecExceptionType {
|
||||
THREAD,
|
||||
};
|
||||
|
||||
// Separate one for without info, to avoid having to allocate a string
|
||||
void Core_MemoryException(u32 address, u32 accessSize, u32 pc, MemoryExceptionType type);
|
||||
|
||||
void Core_MemoryExceptionInfo(u32 address, u32 accessSize, u32 pc, MemoryExceptionType type, std::string_view additionalInfo, bool forceReport);
|
||||
|
||||
void Core_MemoryException(u32 address, u32 accessSize, u32 pc, MemoryExceptionType type, std::string_view additionalInfo = "", bool forceReport = false);
|
||||
void Core_ExecException(u32 address, u32 pc, ExecExceptionType type);
|
||||
void Core_BreakException(u32 pc);
|
||||
// Call when loading save states, etc.
|
||||
|
||||
@@ -362,7 +362,7 @@ void ElfReader::LoadRelocations2(int rel_seg)
|
||||
}
|
||||
|
||||
op = Memory::Read_Instruction(rel_offset, true).encoding;
|
||||
DEBUG_LOG(Log::Loader, "Rel2: %5d: CMD=0x%04X flag=%x type=%d off_seg=%d offset=%08x addr_seg=%d op=%08x\n", rcount, cmd, flag, type, off_seg, rel_base, addr_seg, op);
|
||||
VERBOSE_LOG(Log::Loader, "Rel2: %5d: CMD=0x%04X flag=%x type=%d off_seg=%d offset=%08x addr_seg=%d op=%08x", rcount, cmd, flag, type, off_seg, rel_base, addr_seg, op);
|
||||
|
||||
switch(type){
|
||||
case 0:
|
||||
|
||||
@@ -68,18 +68,23 @@ DirectoryFileSystem::DirectoryFileSystem(IHandleAllocator *_hAlloc, const Path &
|
||||
static const std::string_view mixedCase = "wJpCzSBNnZfxSgoS";
|
||||
static const std::string_view upperCase = "WJPCZSBNNZFXSGOS";
|
||||
|
||||
// Check for case sensitivity
|
||||
bool checkSucceeded = false;
|
||||
File::CreateEmptyFile(basePath / mixedCase);
|
||||
if (File::Exists(basePath / mixedCase)) {
|
||||
checkSucceeded = true;
|
||||
if (!File::Exists(basePath / upperCase)) {
|
||||
flags |= FileSystemFlags::CASE_SENSITIVE;
|
||||
// Check for case sensitivity. TODO: Add more special cases to avoid the file creation dance.
|
||||
#if !PPSSPP_PLATFORM(WINDOWS)
|
||||
if (basePath.Type() == PathType::CONTENT_URI) {
|
||||
// Android: Is not case sensitive.
|
||||
} else {
|
||||
bool checkSucceeded = false;
|
||||
File::CreateEmptyFile(basePath / mixedCase);
|
||||
if (File::Exists(basePath / mixedCase)) {
|
||||
checkSucceeded = true;
|
||||
if (!File::Exists(basePath / upperCase)) {
|
||||
flags |= FileSystemFlags::CASE_SENSITIVE;
|
||||
}
|
||||
}
|
||||
File::Delete(basePath / mixedCase);
|
||||
INFO_LOG(Log::IO, "Is file system case sensitive? %s (base: '%s') (checkOK: %d)", (flags & FileSystemFlags::CASE_SENSITIVE) ? "yes" : "no", _basePath.c_str(), checkSucceeded);
|
||||
}
|
||||
File::Delete(basePath / mixedCase);
|
||||
|
||||
INFO_LOG(Log::IO, "Is file system case sensitive? %s (base: '%s') (checkOK: %d)", (flags & FileSystemFlags::CASE_SENSITIVE) ? "yes" : "no", _basePath.c_str(), checkSucceeded);
|
||||
#endif
|
||||
|
||||
hAlloc = _hAlloc;
|
||||
}
|
||||
|
||||
+780
-316
File diff suppressed because it is too large.
Load diff
@@ -328,13 +328,15 @@ void Register_sceUsbCam()
|
||||
}
|
||||
|
||||
std::vector<std::string> Camera::getDeviceList() {
|
||||
#ifdef HAVE_WIN32_CAMERA
|
||||
if (winCamera) {
|
||||
return winCamera->getDeviceList();
|
||||
}
|
||||
#else
|
||||
return System_GetCameraDeviceList();
|
||||
#endif
|
||||
#ifdef HAVE_WIN32_CAMERA
|
||||
if (winCamera) {
|
||||
return winCamera->getDeviceList();
|
||||
} else {
|
||||
return std::vector<std::string>();
|
||||
}
|
||||
#else
|
||||
return System_GetCameraDeviceList();
|
||||
#endif
|
||||
}
|
||||
|
||||
int Camera::startCapture() {
|
||||
|
||||
+1
-1
@@ -319,7 +319,7 @@ bool HandleFault(uintptr_t hostAddress, void *ctx) {
|
||||
uint32_t approximatePC = currentMIPS->pc;
|
||||
// TODO: Determine access size from the disassembled native instruction. We have some partial info already,
|
||||
// just need to clean it up.
|
||||
Core_MemoryExceptionInfo(guestAddress, 0, approximatePC, type, infoString, true);
|
||||
Core_MemoryException(guestAddress, 0, approximatePC, type, infoString, true);
|
||||
|
||||
// There's a small chance we can resume from this type of crash.
|
||||
g_lastCrashAddress = codePtr;
|
||||
|
||||
+30
-12
@@ -12,6 +12,9 @@
|
||||
// md5_hash(PSP_GAME/EBOOT.BIN)
|
||||
// hash = md5_finalize()
|
||||
|
||||
// To test RAIntegration, get the DLL here: https://github.com/RetroAchievements/RAIntegration/releases
|
||||
// Then just place it next to PPSSPP and enable RAIntegration in PPSSPP achivement settings, then restart it.
|
||||
|
||||
#include <algorithm>
|
||||
#include <set>
|
||||
#include <string>
|
||||
@@ -253,11 +256,15 @@ bool IsActive() {
|
||||
|
||||
static void raintegration_write_memory_handler(uint32_t address, uint8_t *buffer, uint32_t num_bytes, rc_client_t *client) {
|
||||
// convert_retroachievements_address_to_real_address
|
||||
uint32_t realAddress = address + PSP_MEMORY_OFFSET;
|
||||
uint8_t *writePtr = Memory::GetPointerWriteRange(realAddress, num_bytes);
|
||||
if (writePtr) {
|
||||
memcpy(writePtr, buffer, num_bytes);
|
||||
const uint32_t realAddress = address + PSP_MEMORY_OFFSET;
|
||||
if (!Memory::IsValidRange(realAddress, num_bytes)) {
|
||||
ERROR_LOG(Log::Achievements, "RAIntegration write memory: Bad address range %08x-%08x (%d bytes) (%08x was passed in)", realAddress, realAddress + num_bytes, num_bytes, address);
|
||||
return;
|
||||
}
|
||||
|
||||
// We checked the pointer above, this is ok.
|
||||
uint8_t *writePtr = Memory::GetPointerWriteUnchecked(realAddress);
|
||||
memcpy(writePtr, buffer, num_bytes);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -265,22 +272,20 @@ static void raintegration_write_memory_handler(uint32_t address, uint8_t *buffer
|
||||
static uint32_t read_memory_callback(uint32_t address, uint8_t *buffer, uint32_t num_bytes, rc_client_t *client) {
|
||||
// Achievements are traditionally defined relative to the base of main memory of the emulated console.
|
||||
// This is some kind of RetroArch-related legacy. In the PSP's case, this is simply a straight offset of 0x08000000.
|
||||
uint32_t orig_address = address;
|
||||
address += PSP_MEMORY_OFFSET;
|
||||
const uint32_t realAddress = address + PSP_MEMORY_OFFSET;
|
||||
|
||||
if (!Memory::IsValidRange(address, num_bytes)) {
|
||||
if (!Memory::IsValidRange(realAddress, num_bytes)) {
|
||||
// Some achievement packs are really, really spammy.
|
||||
// So we'll just count the bad accesses.
|
||||
Achievements::g_stats.badMemoryAccessCount++;
|
||||
if (g_Config.bAchievementsLogBadMemReads) {
|
||||
WARN_LOG(Log::G3D, "RetroAchievements PeekMemory: Bad address %08x (%d bytes) (%08x was passed in)", address, num_bytes, orig_address);
|
||||
WARN_LOG(Log::G3D, "RetroAchievements PeekMemory: Bad address %08x (%d bytes) (%08x was passed in)", realAddress, num_bytes, address);
|
||||
}
|
||||
|
||||
// This tells rcheevos that the access was bad, which should now be handled properly.
|
||||
return 0;
|
||||
}
|
||||
|
||||
Memory::MemcpyUnchecked(buffer, address, num_bytes);
|
||||
Memory::MemcpyUnchecked(buffer, realAddress, num_bytes);
|
||||
return num_bytes;
|
||||
}
|
||||
|
||||
@@ -674,6 +679,16 @@ void Initialize() {
|
||||
rc_client_set_encore_mode_enabled(g_rcClient, g_Config.bAchievementsEncoreMode ? 1 : 0);
|
||||
rc_client_set_unofficial_enabled(g_rcClient, g_Config.bAchievementsUnofficial ? 1 : 0);
|
||||
|
||||
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
|
||||
if (!g_Config.bAchievementsEnableRAIntegration) {
|
||||
TryLoginByToken(true);
|
||||
}
|
||||
#else
|
||||
TryLoginByToken(true);
|
||||
#endif
|
||||
}
|
||||
|
||||
void InitializeRAIntegration(void *windowHandle) {
|
||||
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
|
||||
if (g_Config.bAchievementsEnableRAIntegration) {
|
||||
wchar_t szFilePath[MAX_PATH];
|
||||
@@ -684,12 +699,15 @@ void Initialize() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
HWND hWnd = (HWND)System_GetPropertyInt(SYSPROP_MAIN_WINDOW_HANDLE);
|
||||
HWND hWnd = (HWND)windowHandle;
|
||||
if (!hWnd) {
|
||||
ERROR_LOG(Log::Achievements, "RAIntegration is enabled, but no main window handle was found.");
|
||||
return;
|
||||
}
|
||||
rc_client_begin_load_raintegration(g_rcClient, szFilePath, hWnd, "PPSSPP", PPSSPP_GIT_VERSION, &load_integration_callback, hWnd);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
TryLoginByToken(true);
|
||||
}
|
||||
|
||||
bool HasToken() {
|
||||
|
||||
@@ -71,6 +71,7 @@ bool RAIntegrationDirty();
|
||||
rc_client_t *GetClient();
|
||||
|
||||
void Initialize();
|
||||
void InitializeRAIntegration(void *windowHandle); // This must be call after the main window exists.
|
||||
void UpdateSettings();
|
||||
|
||||
bool LoginProblems(std::string *errorString);
|
||||
|
||||
+6
-3
@@ -579,11 +579,13 @@ void NativeInit(int argc, const char *argv[], const CommandLineOptions &cmdLineO
|
||||
// Apply parsed command line options to config.
|
||||
cmdLineOptions.ApplyToConfig();
|
||||
|
||||
const char *fileToLog = nullptr;
|
||||
|
||||
bool gotBootFilename = false;
|
||||
boot_filename.clear();
|
||||
|
||||
if (boot_filename.empty() && cmdLineOptions.bootVSH.has_value() && cmdLineOptions.bootVSH.value()) {
|
||||
boot_filename = g_Config.flash0Directory / "vsh/module/vshmain.prx";
|
||||
}
|
||||
|
||||
// Parse command line
|
||||
LogLevel logLevel = LogLevel::LINFO;
|
||||
bool forceLogLevel = false;
|
||||
@@ -596,6 +598,7 @@ void NativeInit(int argc, const char *argv[], const CommandLineOptions &cmdLineO
|
||||
setLogLevel(cmdLineOptions.logLevel.value());
|
||||
}
|
||||
|
||||
std::string fileToLog;
|
||||
for (int i = 1; i < argc; i++) {
|
||||
if (argv[i][0] == '-') {
|
||||
#if defined(__APPLE__)
|
||||
@@ -672,7 +675,7 @@ void NativeInit(int argc, const char *argv[], const CommandLineOptions &cmdLineO
|
||||
}
|
||||
}
|
||||
|
||||
if (fileToLog) {
|
||||
if (!fileToLog.empty()) {
|
||||
// Start logging immediately.
|
||||
g_logManager.EnableOutput(LogOutput::File);
|
||||
g_logManager.SetFileLogPath(Path(fileToLog));
|
||||
|
||||
+3
-1
@@ -1205,6 +1205,7 @@ int WINAPI WinMain(HINSTANCE _hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLin
|
||||
MainWindow::Show(_hInstance);
|
||||
|
||||
HWND hwndMain = MainWindow::GetHWND();
|
||||
Achievements::InitializeRAIntegration(hwndMain);
|
||||
|
||||
//initialize custom controls
|
||||
CtrlDisAsmView::init();
|
||||
@@ -1310,7 +1311,8 @@ int WINAPI WinMain(HINSTANCE _hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLin
|
||||
|
||||
MainWindow::DestroyDebugWindows();
|
||||
DialogManager::DestroyAll();
|
||||
timeEndPeriod(1);
|
||||
|
||||
TimeShutdown();
|
||||
|
||||
UnRegisterCMPTMFApis();
|
||||
|
||||
|
||||
@@ -199,6 +199,18 @@
|
||||
"description": "Dedicated regional relay server providing low-latency matchmaking for MENA players.",
|
||||
"data_mode": "AemuPostoffice",
|
||||
"status_data_json": "http://jeddah-relay.ddns.net/data.json"
|
||||
},
|
||||
{
|
||||
"name": "Alpha Adhoc Server",
|
||||
"host": "140.245.237.12",
|
||||
"discord": "https://discord.gg/fenYRERNRy",
|
||||
"web": "http://140.245.237.12:8080/",
|
||||
"location": "India",
|
||||
"location-emoji": "🇮🇳",
|
||||
"description": "Indian adhoc relay from alpha community",
|
||||
"data_mode": "AemuPostoffice",
|
||||
"status_xml": "",
|
||||
"icon": "https://raw.githubusercontent.com/MainPiyushHoon/Alpha-Community-Webpage/refs/heads/main/src/images/ALPHA.webp"
|
||||
}
|
||||
]
|
||||
}
|
||||
+27
-41
@@ -1,9 +1,13 @@
|
||||
|
||||
// Headless version of PPSSPP, for testing using http://code.google.com/p/pspautotests/ .
|
||||
// See headless.txt.
|
||||
// See headless/README.md.
|
||||
// To build on non-windows systems, just run CMake in the SDL directory, it will build both a normal ppsspp and the headless version.
|
||||
//
|
||||
// Example command line to run a test in the VS debugger (useful to debug failures):
|
||||
// > --root pspautotests/tests/../ --compare --timeout=5 --graphics=software pspautotests/tests/cpu/cpu_alu/cpu_alu.prx
|
||||
// Example command line for taking screenshots from a frame dump:
|
||||
// > -l --graphics=vulkan --screenshot-save=vt_ref.bmp "D:\PSP ISO\dump\Depth\11578 Virtua Tennis pause menu ULES00126_0002.zip" --resolution-scale=2
|
||||
//
|
||||
// NOTE: In MSVC, don't forget to set the working directory to $ProjectDir\.. in debug settings.
|
||||
|
||||
#include "ppsspp_config.h"
|
||||
@@ -17,13 +21,11 @@
|
||||
#include <algorithm>
|
||||
|
||||
#include "Common/Profiler/Profiler.h"
|
||||
#include "Common/System/NativeApp.h"
|
||||
#include "Common/System/Request.h"
|
||||
#include "Common/System/System.h"
|
||||
|
||||
#include "Common/CommonWindows.h"
|
||||
#if PPSSPP_PLATFORM(WINDOWS)
|
||||
#include <timeapi.h>
|
||||
#else
|
||||
#include <csignal>
|
||||
#endif
|
||||
@@ -121,33 +123,17 @@ std::string NativeLoadSecret(std::string_view nameOfSecret) {
|
||||
return "";
|
||||
}
|
||||
|
||||
int printUsage(const char *progname, const char *reason) {
|
||||
if (reason != NULL)
|
||||
fprintf(stderr, "Error: %s\n\n", reason);
|
||||
fprintf(stderr, "PPSSPP Headless\n");
|
||||
fprintf(stderr, "This is primarily meant as a non-interactive test tool.\n\n");
|
||||
fprintf(stderr, "Usage: %s file.elf... [options]\n\n", progname);
|
||||
fprintf(stderr, "Options:\n");
|
||||
int printUsage(const CommandLineOptions &options, const char *progname, const char *reason) {
|
||||
options.PrintUsage(progname, reason);
|
||||
fprintf(stderr, " -m, --mount umd.cso mount iso on umd1:\n");
|
||||
fprintf(stderr, " -r, --root some/path mount path on host0: (elfs must be in here)\n");
|
||||
fprintf(stderr, " -l, --log full log output, not just emulated printfs\n");
|
||||
fprintf(stderr, " --debugger=PORT enable websocket debugger and break at start\n");
|
||||
|
||||
fprintf(stderr, " --graphics=BACKEND use a different gpu backend\n");
|
||||
fprintf(stderr, " options: gles, software, directx9, etc.\n");
|
||||
fprintf(stderr, " --screenshot=FILE compare against a screenshot\n");
|
||||
fprintf(stderr, " --screenshot-save=FILE save rendered screenshot to a BMP file\n");
|
||||
fprintf(stderr, " --max-mse=NUMBER maximum allowed MSE error for screenshot\n");
|
||||
fprintf(stderr, " --timeout=SECONDS abort test it if takes longer than SECONDS\n");
|
||||
|
||||
fprintf(stderr, " -v, --verbose show the full passed/failed result\n");
|
||||
fprintf(stderr, " -i use the interpreter\n");
|
||||
fprintf(stderr, " --ir use ir interpreter\n");
|
||||
fprintf(stderr, " -j use jit (default)\n");
|
||||
fprintf(stderr, " -c, --compare compare with output in file.expected\n");
|
||||
fprintf(stderr, " --bench run multiple times and output speed\n");
|
||||
fprintf(stderr, "\nSee headless.txt for details.\n");
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -260,9 +246,9 @@ void System_SendDebugScreenshot(const uint8_t *data, int width, int height) {
|
||||
struct AutoTestOptions {
|
||||
double timeout;
|
||||
double maxScreenshotError;
|
||||
bool compare : 1;
|
||||
bool verbose : 1;
|
||||
bool bench : 1;
|
||||
bool compare;
|
||||
bool verbose;
|
||||
bool bench;
|
||||
};
|
||||
|
||||
bool RunAutoTest(HeadlessHost *headlessHost, CoreParameter &coreParameter, const AutoTestOptions &opt) {
|
||||
@@ -464,6 +450,7 @@ int main(int argc, const char* argv[]) {
|
||||
testOptions.compare = cmdLineOptions.compare.value_or(false);
|
||||
testOptions.bench = cmdLineOptions.bench.value_or(false);
|
||||
testOptions.timeout = cmdLineOptions.timeout.value_or(std::numeric_limits<double>::infinity());
|
||||
testOptions.verbose = cmdLineOptions.verbose.value_or(false);
|
||||
|
||||
bool fullLog = false;
|
||||
const char *stateToLoad = 0;
|
||||
@@ -488,24 +475,22 @@ int main(int argc, const char* argv[]) {
|
||||
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");
|
||||
return printUsage(cmdLineOptions, argv[0], "Missing argument after -m");
|
||||
mountIso = 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], "-v") || !strcmp(argv[i], "--verbose"))
|
||||
testOptions.verbose = true;
|
||||
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], "--state=", strlen("--state=")) && strlen(argv[i]) > strlen("--state="))
|
||||
stateToLoad = argv[i] + strlen("--state=");
|
||||
else if (!strcmp(argv[i], "--help") || !strcmp(argv[i], "-h"))
|
||||
return printUsage(argv[0], NULL);
|
||||
return printUsage(cmdLineOptions, argv[0], NULL);
|
||||
else if (!strcmp(argv[i], "--ignore")) {
|
||||
if (++i >= argc)
|
||||
return printUsage(argv[0], "Missing argument after --ignore");
|
||||
return printUsage(cmdLineOptions, argv[0], "Missing argument after --ignore");
|
||||
ignoredTests.push_back(argv[i]);
|
||||
}
|
||||
}
|
||||
@@ -514,7 +499,6 @@ int main(int argc, const char* argv[]) {
|
||||
AddToTestsByPath(&testFilenames, filename);
|
||||
}
|
||||
|
||||
|
||||
if (testFilenames.size() == 1 && testFilenames[0][0] == '@')
|
||||
testFilenames = ReadFromListFile(testFilenames[0].substr(1));
|
||||
|
||||
@@ -528,23 +512,20 @@ int main(int argc, const char* argv[]) {
|
||||
testFilenames.end()
|
||||
);
|
||||
|
||||
if (testFilenames.empty())
|
||||
return printUsage(argv[0], argc <= 1 ? NULL : "No executables specified");
|
||||
|
||||
g_Config.bEnableLogging = (fullLog || outputDebugStringLog);
|
||||
g_logManager.Init(&g_Config.bEnableLogging, outputDebugStringLog);
|
||||
|
||||
for (int i = 0; i < (int)Log::NUMBER_OF_LOGS; i++) {
|
||||
Log type = (Log)i;
|
||||
g_logManager.SetEnabled(type, (fullLog || outputDebugStringLog));
|
||||
g_logManager.SetLogLevel(type, LogLevel::LDEBUG);
|
||||
g_logManager.SetLogLevel(type, LogLevel::LDEBUG); // TODO: Make the level configurable.
|
||||
}
|
||||
if (fullLog) {
|
||||
// Only with --log, add the printfLogger.
|
||||
g_logManager.EnableOutput(LogOutput::Printf);
|
||||
}
|
||||
|
||||
g_Config.RestoreDefaults(RestoreSettingsBits::SETTINGS | RestoreSettingsBits::CONTROLS | RestoreSettingsBits::RECENT, true);
|
||||
g_Config.RestoreDefaults(RestoreSettingsBits::SETTINGS | RestoreSettingsBits::CONTROLS | RestoreSettingsBits::RECENT, false);
|
||||
|
||||
// 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);
|
||||
@@ -633,7 +614,7 @@ int main(int argc, const char* argv[]) {
|
||||
#if PPSSPP_PLATFORM(WINDOWS)
|
||||
// Mount a filesystem
|
||||
g_Config.memStickDirectory = exePath / "memstick";
|
||||
File::CreateDir(g_Config.memStickDirectory);
|
||||
File::CreateDir(g_Config.memStickDirectory, true);
|
||||
CreateSysDirectories();
|
||||
#elif !PPSSPP_PLATFORM(ANDROID)
|
||||
g_Config.memStickDirectory = Path(std::string(getenv("HOME"))) / ".ppsspp";
|
||||
@@ -679,6 +660,13 @@ int main(int argc, const char* argv[]) {
|
||||
|
||||
UpdateUIState(UISTATE_INGAME);
|
||||
|
||||
if (cmdLineOptions.bootVSH.has_value() && cmdLineOptions.bootVSH.value()) {
|
||||
AddToTestsByPath(&testFilenames, (g_Config.flash0Directory / "vsh/module/vshmain.prx").ToString());
|
||||
}
|
||||
if (testFilenames.empty()) {
|
||||
return printUsage(cmdLineOptions, argv[0], argc <= 1 ? NULL : "No executables specified");
|
||||
}
|
||||
|
||||
if (cmdLineOptions.debuggerPort.has_value()) {
|
||||
coreParameter.startBreak = true;
|
||||
StartWebServer(WebServerFlags::DEBUGGER);
|
||||
@@ -747,12 +735,10 @@ int main(int argc, const char* argv[]) {
|
||||
|
||||
g_VFS.Clear();
|
||||
g_logManager.Shutdown();
|
||||
if (cmdLineOptions.debuggerPort.has_value())
|
||||
if (cmdLineOptions.debuggerPort.has_value()) {
|
||||
net::Shutdown();
|
||||
|
||||
#if PPSSPP_PLATFORM(WINDOWS)
|
||||
timeEndPeriod(1);
|
||||
#endif
|
||||
}
|
||||
TimeShutdown();
|
||||
|
||||
g_threadManager.Teardown();
|
||||
|
||||
|
||||
+1
-1
Submodule pspautotests updated: 4374348477...3b281cf05e.
Reference in new issue
Block a user