Merge pull request #21991 from hrydgard/cmdline-parse-improvements

Command line parse improvements (some new arguments), RAIntegration fixes
This commit is contained in:
Henrik Rydgård authored and GitHub committed 2026-07-27 23:37:01 +02:00
commit 16a38b090a
24 files changed
+1138 -513

No files matched your search

+20 -8
View File
@@ -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;
}
+1 -1
View File
@@ -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
View File
@@ -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;
}
}
+16
View File
@@ -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() {
+1
View File
@@ -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
View File
@@ -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 &param, 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
View File
@@ -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
View File
@@ -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.
+5
View File
@@ -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;
+7
View File
@@ -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
View File
@@ -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
View File
@@ -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.
+1 -1
View File
@@ -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:
+15 -10
View File
@@ -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
View File
File diff suppressed because it is too large. Load diff
+9 -7
View File
@@ -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
View File
@@ -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
View File
@@ -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() {
+1
View File
@@ -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
View File
@@ -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
View File
@@ -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();
+12
View File
@@ -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
View File
@@ -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();