diff --git a/CMakeLists.txt b/CMakeLists.txt index a3f94cf692..1826721d7a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1580,6 +1580,8 @@ add_library(${CoreLibName} ${CoreLinkType} Core/Debugger/WebSocket/MemoryInfoSubscriber.h Core/Debugger/WebSocket/MemorySubscriber.cpp Core/Debugger/WebSocket/MemorySubscriber.h + Core/Debugger/WebSocket/ReplaySubscriber.cpp + Core/Debugger/WebSocket/ReplaySubscriber.h Core/Debugger/WebSocket/SteppingBroadcaster.cpp Core/Debugger/WebSocket/SteppingBroadcaster.h Core/Debugger/WebSocket/SteppingSubscriber.cpp diff --git a/Common/Common.vcxproj b/Common/Common.vcxproj index 5e7647b71a..476039ff0f 100644 --- a/Common/Common.vcxproj +++ b/Common/Common.vcxproj @@ -551,6 +551,12 @@ NotUsing NotUsing NotUsing + + + + + + NotUsing @@ -561,6 +567,12 @@ NotUsing NotUsing NotUsing + + + + + + NotUsing @@ -571,6 +583,12 @@ NotUsing NotUsing NotUsing + + + + + + NotUsing @@ -581,6 +599,12 @@ NotUsing NotUsing NotUsing + + + + + + NotUsing @@ -591,8 +615,21 @@ NotUsing NotUsing NotUsing + + + + + + + + + + + + + + - NotUsing NotUsing @@ -602,6 +639,12 @@ NotUsing NotUsing NotUsing + + + + + + NotUsing @@ -612,6 +655,12 @@ NotUsing NotUsing NotUsing + + + + + + NotUsing @@ -622,6 +671,12 @@ NotUsing NotUsing NotUsing + + + + + + NotUsing @@ -632,6 +687,12 @@ NotUsing NotUsing NotUsing + + + + + + NotUsing @@ -642,6 +703,12 @@ NotUsing NotUsing NotUsing + + + + + + NotUsing @@ -652,6 +719,12 @@ NotUsing NotUsing NotUsing + + + + + + NotUsing @@ -662,6 +735,12 @@ NotUsing NotUsing NotUsing + + + + + + NotUsing @@ -672,6 +751,12 @@ NotUsing NotUsing NotUsing + + + + + + NotUsing @@ -682,6 +767,12 @@ NotUsing NotUsing NotUsing + + + + + + NotUsing @@ -692,6 +783,12 @@ NotUsing NotUsing NotUsing + + + + + + diff --git a/Common/File/FileUtil.cpp b/Common/File/FileUtil.cpp index b528d21b38..2536d4bdf7 100644 --- a/Common/File/FileUtil.cpp +++ b/Common/File/FileUtil.cpp @@ -364,10 +364,10 @@ bool CreateFullPath(const Path &path) { std::string fullPath = path.ToString(); int panicCounter = 100; - VERBOSE_LOG(COMMON, "CreateFullPath: path %s", fullPath.c_str()); + VERBOSE_LOG(COMMON, "CreateFullPath: '%s'", fullPath.c_str()); if (File::Exists(fullPath)) { - DEBUG_LOG(COMMON, "CreateFullPath: path exists %s", fullPath.c_str()); + DEBUG_LOG(COMMON, "CreateFullPath: path already exists: '%s'", fullPath.c_str()); return true; } @@ -752,7 +752,7 @@ const Path &GetExeDirectory() { ExePath = Path(program_path); #elif (defined(__APPLE__) && !PPSSPP_PLATFORM(IOS)) || defined(__linux__) || defined(KERN_PROC_PATHNAME) - char program_path[4096]; + char program_path[4096]{}; uint32_t program_path_size = sizeof(program_path) - 1; #if defined(__linux__) diff --git a/Common/Net/HTTPServer.cpp b/Common/Net/HTTPServer.cpp index 882d49a965..c787c3f829 100644 --- a/Common/Net/HTTPServer.cpp +++ b/Common/Net/HTTPServer.cpp @@ -72,7 +72,9 @@ Request::Request(int fd) Request::~Request() { Close(); - _assert_(in_->Empty()); + if (!in_->Empty()) { + ERROR_LOG(IO, "Input not empty - invalid request?"); + } delete in_; if (!out_->Empty()) { ERROR_LOG(IO, "Output not empty - connection abort?"); @@ -330,6 +332,10 @@ void Server::HandleRequest(const Request &request) { } void Server::HandleRequestDefault(const Request &request) { + if (request.resource() == nullptr) { + fallback_(request); + return; + } // First, look through all handlers. If we got one, use it. auto handler = handlers_.find(request.resource()); if (handler != handlers_.end()) { diff --git a/Common/Net/WebsocketServer.cpp b/Common/Net/WebsocketServer.cpp index 3b5449b489..3b6f98ed27 100644 --- a/Common/Net/WebsocketServer.cpp +++ b/Common/Net/WebsocketServer.cpp @@ -341,8 +341,8 @@ bool WebSocketServer::ReadFrame() { mask = &header[10]; // Read from big endian. - uint64_t high = (header[2] << 24) | (header[3] << 16) || (header[4] << 8) | (header[5] << 0); - uint64_t low = (header[6] << 24) | (header[7] << 16) || (header[8] << 8) | (header[9] << 0); + uint64_t high = (header[2] << 24) | (header[3] << 16) | (header[4] << 8) | (header[5] << 0); + uint64_t low = (header[6] << 24) | (header[7] << 16) | (header[8] << 8) | (header[9] << 0); sz = (high << 32) | low; if ((sz & 0x8000000000000000ULL) != 0) { diff --git a/Core/Config.cpp b/Core/Config.cpp index 8ba3633dbd..848137003c 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -71,13 +71,15 @@ struct ConfigSetting { TYPE_FLOAT, TYPE_STRING, TYPE_TOUCH_POS, + TYPE_PATH, }; - union Value { + union DefaultValue { bool b; int i; uint32_t u; float f; const char *s; + const char *p; // not sure how much point.. ConfigTouchPos touchPos; }; union SettingPtr { @@ -86,6 +88,7 @@ struct ConfigSetting { uint32_t *u; float *f; std::string *s; + Path *p; ConfigTouchPos *touchPos; }; @@ -94,7 +97,8 @@ struct ConfigSetting { typedef uint32_t (*Uint32DefaultCallback)(); typedef float (*FloatDefaultCallback)(); typedef const char *(*StringDefaultCallback)(); - typedef ConfigTouchPos (*TouchPosDefaultCallback)(); + typedef ConfigTouchPos(*TouchPosDefaultCallback)(); + typedef const char *(*PathDefaultCallback)(); union Callback { BoolDefaultCallback b; @@ -102,102 +106,110 @@ struct ConfigSetting { Uint32DefaultCallback u; FloatDefaultCallback f; StringDefaultCallback s; + PathDefaultCallback p; TouchPosDefaultCallback touchPos; }; ConfigSetting(bool v) - : ini_(""), type_(TYPE_TERMINATOR), report_(false), save_(false), perGame_(false) { + : iniKey_(""), type_(TYPE_TERMINATOR), report_(false), save_(false), perGame_(false) { ptr_.b = nullptr; cb_.b = nullptr; } ConfigSetting(const char *ini, bool *v, bool def, bool save = true, bool perGame = false) - : ini_(ini), type_(TYPE_BOOL), report_(false), save_(save), perGame_(perGame) { + : iniKey_(ini), type_(TYPE_BOOL), report_(false), save_(save), perGame_(perGame) { ptr_.b = v; cb_.b = nullptr; default_.b = def; } ConfigSetting(const char *ini, int *v, int def, bool save = true, bool perGame = false) - : ini_(ini), type_(TYPE_INT), report_(false), save_(save), perGame_(perGame) { + : iniKey_(ini), type_(TYPE_INT), report_(false), save_(save), perGame_(perGame) { ptr_.i = v; cb_.i = nullptr; default_.i = def; } ConfigSetting(const char *ini, int *v, int def, std::function transTo, std::function transFrom, bool save = true, bool perGame = false) - : ini_(ini), type_(TYPE_INT), report_(false), save_(save), perGame_(perGame), translateTo_(transTo), translateFrom_(transFrom) { + : iniKey_(ini), type_(TYPE_INT), report_(false), save_(save), perGame_(perGame), translateTo_(transTo), translateFrom_(transFrom) { ptr_.i = v; cb_.i = nullptr; default_.i = def; } ConfigSetting(const char *ini, uint32_t *v, uint32_t def, bool save = true, bool perGame = false) - : ini_(ini), type_(TYPE_UINT32), report_(false), save_(save), perGame_(perGame) { + : iniKey_(ini), type_(TYPE_UINT32), report_(false), save_(save), perGame_(perGame) { ptr_.u = v; cb_.u = nullptr; default_.u = def; } ConfigSetting(const char *ini, float *v, float def, bool save = true, bool perGame = false) - : ini_(ini), type_(TYPE_FLOAT), report_(false), save_(save), perGame_(perGame) { + : iniKey_(ini), type_(TYPE_FLOAT), report_(false), save_(save), perGame_(perGame) { ptr_.f = v; cb_.f = nullptr; default_.f = def; } ConfigSetting(const char *ini, std::string *v, const char *def, bool save = true, bool perGame = false) - : ini_(ini), type_(TYPE_STRING), report_(false), save_(save), perGame_(perGame) { + : iniKey_(ini), type_(TYPE_STRING), report_(false), save_(save), perGame_(perGame) { ptr_.s = v; cb_.s = nullptr; default_.s = def; } + ConfigSetting(const char *ini, Path *p, const char *def, bool save = true, bool perGame = false) + : iniKey_(ini), type_(TYPE_PATH), report_(false), save_(save), perGame_(perGame) { + ptr_.p = p; + cb_.p = nullptr; + default_.p = def; + } + ConfigSetting(const char *iniX, const char *iniY, const char *iniScale, const char *iniShow, ConfigTouchPos *v, ConfigTouchPos def, bool save = true, bool perGame = false) - : ini_(iniX), ini2_(iniY), ini3_(iniScale), ini4_(iniShow), type_(TYPE_TOUCH_POS), report_(false), save_(save), perGame_(perGame) { + : iniKey_(iniX), ini2_(iniY), ini3_(iniScale), ini4_(iniShow), type_(TYPE_TOUCH_POS), report_(false), save_(save), perGame_(perGame) { ptr_.touchPos = v; cb_.touchPos = nullptr; default_.touchPos = def; } ConfigSetting(const char *ini, bool *v, BoolDefaultCallback def, bool save = true, bool perGame = false) - : ini_(ini), type_(TYPE_BOOL), report_(false), save_(save), perGame_(perGame) { + : iniKey_(ini), type_(TYPE_BOOL), report_(false), save_(save), perGame_(perGame) { ptr_.b = v; cb_.b = def; } ConfigSetting(const char *ini, int *v, IntDefaultCallback def, bool save = true, bool perGame = false) - : ini_(ini), type_(TYPE_INT), report_(false), save_(save), perGame_(perGame) { + : iniKey_(ini), type_(TYPE_INT), report_(false), save_(save), perGame_(perGame) { ptr_ .i = v; cb_.i = def; } ConfigSetting(const char *ini, int *v, IntDefaultCallback def, std::function transTo, std::function transFrom, bool save = true, bool perGame = false) - : ini_(ini), type_(TYPE_INT), report_(false), save_(save), perGame_(perGame), translateTo_(transTo), translateFrom_(transFrom) { + : iniKey_(ini), type_(TYPE_INT), report_(false), save_(save), perGame_(perGame), translateTo_(transTo), translateFrom_(transFrom) { ptr_.i = v; cb_.i = def; } ConfigSetting(const char *ini, uint32_t *v, Uint32DefaultCallback def, bool save = true, bool perGame = false) - : ini_(ini), type_(TYPE_UINT32), report_(false), save_(save), perGame_(perGame) { + : iniKey_(ini), type_(TYPE_UINT32), report_(false), save_(save), perGame_(perGame) { ptr_ .u = v; cb_.u = def; } ConfigSetting(const char *ini, float *v, FloatDefaultCallback def, bool save = true, bool perGame = false) - : ini_(ini), type_(TYPE_FLOAT), report_(false), save_(save), perGame_(perGame) { + : iniKey_(ini), type_(TYPE_FLOAT), report_(false), save_(save), perGame_(perGame) { ptr_.f = v; cb_.f = def; } ConfigSetting(const char *ini, std::string *v, StringDefaultCallback def, bool save = true, bool perGame = false) - : ini_(ini), type_(TYPE_STRING), report_(false), save_(save), perGame_(perGame) { + : iniKey_(ini), type_(TYPE_STRING), report_(false), save_(save), perGame_(perGame) { ptr_.s = v; cb_.s = def; } ConfigSetting(const char *iniX, const char *iniY, const char *iniScale, const char *iniShow, ConfigTouchPos *v, TouchPosDefaultCallback def, bool save = true, bool perGame = false) - : ini_(iniX), ini2_(iniY), ini3_(iniScale), ini4_(iniShow), type_(TYPE_TOUCH_POS), report_(false), save_(save), perGame_(perGame) { + : iniKey_(iniX), ini2_(iniY), ini3_(iniScale), ini4_(iniShow), type_(TYPE_TOUCH_POS), report_(false), save_(save), perGame_(perGame) { ptr_.touchPos = v; cb_.touchPos = def; } @@ -212,39 +224,39 @@ struct ConfigSetting { if (cb_.b) { default_.b = cb_.b(); } - return section->Get(ini_, ptr_.b, default_.b); + return section->Get(iniKey_, ptr_.b, default_.b); case TYPE_INT: if (cb_.i) { default_.i = cb_.i(); } if (translateFrom_) { std::string value; - if (section->Get(ini_, &value, nullptr)) { + if (section->Get(iniKey_, &value, nullptr)) { *ptr_.i = translateFrom_(value); return true; } } - return section->Get(ini_, ptr_.i, default_.i); + return section->Get(iniKey_, ptr_.i, default_.i); case TYPE_UINT32: if (cb_.u) { default_.u = cb_.u(); } - return section->Get(ini_, ptr_.u, default_.u); + return section->Get(iniKey_, ptr_.u, default_.u); case TYPE_FLOAT: if (cb_.f) { default_.f = cb_.f(); } - return section->Get(ini_, ptr_.f, default_.f); + return section->Get(iniKey_, ptr_.f, default_.f); case TYPE_STRING: if (cb_.s) { default_.s = cb_.s(); } - return section->Get(ini_, ptr_.s, default_.s); + return section->Get(iniKey_, ptr_.s, default_.s); case TYPE_TOUCH_POS: if (cb_.touchPos) { default_.touchPos = cb_.touchPos(); } - section->Get(ini_, &ptr_.touchPos->x, default_.touchPos.x); + section->Get(iniKey_, &ptr_.touchPos->x, default_.touchPos.x); section->Get(ini2_, &ptr_.touchPos->y, default_.touchPos.y); section->Get(ini3_, &ptr_.touchPos->scale, default_.touchPos.scale); if (ini4_) { @@ -253,6 +265,18 @@ struct ConfigSetting { ptr_.touchPos->show = default_.touchPos.show; } return true; + case TYPE_PATH: + { + std::string tmp; + if (cb_.p) { + default_.p = cb_.p(); + } + bool result = section->Get(iniKey_, &tmp, default_.p); + if (result) { + *ptr_.p = Path(tmp); + } + return result; + } default: _dbg_assert_msg_(false, "Unexpected ini setting type"); return false; @@ -265,21 +289,23 @@ struct ConfigSetting { switch (type_) { case TYPE_BOOL: - return section->Set(ini_, *ptr_.b); + return section->Set(iniKey_, *ptr_.b); case TYPE_INT: if (translateTo_) { std::string value = translateTo_(*ptr_.i); - return section->Set(ini_, value); + return section->Set(iniKey_, value); } - return section->Set(ini_, *ptr_.i); + return section->Set(iniKey_, *ptr_.i); case TYPE_UINT32: - return section->Set(ini_, *ptr_.u); + return section->Set(iniKey_, *ptr_.u); case TYPE_FLOAT: - return section->Set(ini_, *ptr_.f); + return section->Set(iniKey_, *ptr_.f); case TYPE_STRING: - return section->Set(ini_, *ptr_.s); + return section->Set(iniKey_, *ptr_.s); + case TYPE_PATH: + return section->Set(iniKey_, ptr_.p->ToString()); case TYPE_TOUCH_POS: - section->Set(ini_, ptr_.touchPos->x); + section->Set(iniKey_, ptr_.touchPos->x); section->Set(ini2_, ptr_.touchPos->y); section->Set(ini3_, ptr_.touchPos->scale); if (ini4_) { @@ -298,15 +324,17 @@ struct ConfigSetting { switch (type_) { case TYPE_BOOL: - return data.Add(prefix + ini_, *ptr_.b); + return data.Add(prefix + iniKey_, *ptr_.b); case TYPE_INT: - return data.Add(prefix + ini_, *ptr_.i); + return data.Add(prefix + iniKey_, *ptr_.i); case TYPE_UINT32: - return data.Add(prefix + ini_, *ptr_.u); + return data.Add(prefix + iniKey_, *ptr_.u); case TYPE_FLOAT: - return data.Add(prefix + ini_, *ptr_.f); + return data.Add(prefix + iniKey_, *ptr_.f); case TYPE_STRING: - return data.Add(prefix + ini_, *ptr_.s); + return data.Add(prefix + iniKey_, *ptr_.s); + case TYPE_PATH: + return data.Add(prefix + iniKey_, ptr_.p->ToString()); case TYPE_TOUCH_POS: // Doesn't report. return; @@ -316,7 +344,7 @@ struct ConfigSetting { } } - const char *ini_; + const char *iniKey_; const char *ini2_; const char *ini3_; const char *ini4_; @@ -325,7 +353,7 @@ struct ConfigSetting { bool save_; bool perGame_; SettingPtr ptr_; - Value default_; + DefaultValue default_; Callback cb_; // We only support transform for ints. @@ -1228,7 +1256,7 @@ void Config::Load(const char *iniFileName, const char *controllerIniFilename) { iRunCount++; if (!File::Exists(Path(currentDirectory))) - currentDirectory = ""; + currentDirectory = defaultCurrentDirectory; Section *log = iniFile.GetOrCreateSection(logSectionName); @@ -1564,12 +1592,8 @@ void Config::CleanRecent() { recentIsos = cleanedRecent; } -void Config::SetDefaultPath(const Path &defaultPath) { - defaultPath_ = defaultPath; -} - -void Config::AddSearchPath(const Path &path) { - searchPath_.push_back(path); +void Config::SetSearchPath(const Path &searchPath) { + searchPath_ = searchPath; } const Path Config::FindConfigFile(const std::string &baseFilename) { @@ -1583,21 +1607,16 @@ const Path Config::FindConfigFile(const std::string &baseFilename) { } #endif - for (size_t i = 0; i < searchPath_.size(); ++i) { - Path filename = searchPath_[i] / baseFilename; - if (File::Exists(filename)) { - return filename; - } + Path filename = searchPath_ / baseFilename; + if (File::Exists(filename)) { + return filename; } - const Path filename = defaultPath_ / baseFilename; - if (!File::Exists(filename)) { - // Make sure at least the directory it's supposed to be in exists. - Path path = filename.NavigateUp(); - if (createdPath_ != path) { - File::CreateFullPath(path); - createdPath_ = path; - } + // Make sure at least the directory it's supposed to be in exists. + Path path = filename.NavigateUp(); + // This check is just to avoid logging. + if (!File::Exists(path)) { + File::CreateFullPath(path); } return filename; } @@ -1610,7 +1629,7 @@ void Config::RestoreDefaults() { if (File::Exists(iniFilename_)) File::Delete(iniFilename_); recentIsos.clear(); - currentDirectory = ""; + currentDirectory = defaultCurrentDirectory; } Load(); } diff --git a/Core/Config.h b/Core/Config.h index 81b9bfe8bd..ff9d0309ea 100644 --- a/Core/Config.h +++ b/Core/Config.h @@ -493,8 +493,9 @@ public: bool bSimpleFrameStats; // Various directories. Autoconfigured, not read from ini. - std::string currentDirectory; - Path externalDirectory; + Path currentDirectory; // The directory selected in the game browsing window. + Path defaultCurrentDirectory; // Platform dependent, initialized at startup. + Path memStickDirectory; Path flash0Directory; Path internalDataDirectory; @@ -520,10 +521,7 @@ public: Path getGameConfigFile(const std::string &gameId); bool hasGameConfig(const std::string &game_id); - // Used when the file is not found in the search path. Trailing slash. - void SetDefaultPath(const Path &defaultPath); - // Use a trailing slash. - void AddSearchPath(const Path &path); + void SetSearchPath(const Path &path); const Path FindConfigFile(const std::string &baseFilename); // Utility functions for "recent" management @@ -551,9 +549,7 @@ private: std::string gameIdTitle_; Path iniFilename_; Path controllerIniFilename_; - std::vector searchPath_; - Path defaultPath_; - Path createdPath_; + Path searchPath_; }; std::map> GetLangValuesMapping(); diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index 68e918a7f0..ececc51b5f 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -525,6 +525,7 @@ + @@ -1076,6 +1077,7 @@ + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index c6a59d5d51..43a36fa022 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -1172,6 +1172,9 @@ Ext\libzip + + Debugger\WebSocket + @@ -1886,6 +1889,9 @@ Ext\libzip + + Debugger\WebSocket + diff --git a/Core/Debugger/WebSocket.cpp b/Core/Debugger/WebSocket.cpp index f3f83254af..3d85f4b6fd 100644 --- a/Core/Debugger/WebSocket.cpp +++ b/Core/Debugger/WebSocket.cpp @@ -59,6 +59,7 @@ #include "Core/Debugger/WebSocket/InputSubscriber.h" #include "Core/Debugger/WebSocket/MemoryInfoSubscriber.h" #include "Core/Debugger/WebSocket/MemorySubscriber.h" +#include "Core/Debugger/WebSocket/ReplaySubscriber.h" #include "Core/Debugger/WebSocket/SteppingSubscriber.h" typedef DebuggerSubscriber *(*SubscriberInit)(DebuggerEventHandlerMap &map); @@ -73,6 +74,7 @@ static const std::vector subscribers({ &WebSocketInputInit, &WebSocketMemoryInfoInit, &WebSocketMemoryInit, + &WebSocketReplayInit, &WebSocketSteppingInit, }); diff --git a/Core/Debugger/WebSocket/GameSubscriber.cpp b/Core/Debugger/WebSocket/GameSubscriber.cpp index 0acd4a9b3e..2d8bb1a116 100644 --- a/Core/Debugger/WebSocket/GameSubscriber.cpp +++ b/Core/Debugger/WebSocket/GameSubscriber.cpp @@ -18,15 +18,48 @@ #include "Core/Debugger/WebSocket/GameSubscriber.h" #include "Core/Debugger/WebSocket/WebSocketUtils.h" #include "Core/ELF/ParamSFO.h" +#include "Core/Host.h" #include "Core/System.h" DebuggerSubscriber *WebSocketGameInit(DebuggerEventHandlerMap &map) { + map["game.reset"] = &WebSocketGameReset; map["game.status"] = &WebSocketGameStatus; map["version"] = &WebSocketVersion; return nullptr; } +// Reset emulation (game.reset) +// +// Use this if you need to break on start and do something before the game starts. +// +// Parameters: +// - break: optional boolean, true to break CPU on start. Use cpu.resume afterward. +// +// Response (same event name) with no extra data or error. +void WebSocketGameReset(DebuggerRequest &req) { + if (!PSP_IsInited()) + return req.Fail("Game not running"); + + bool needBreak = false; + if (!req.ParamBool("break", &needBreak, DebuggerParamType::OPTIONAL)) + return; + + if (needBreak) + PSP_CoreParameter().startBreak = true; + + PSP_Shutdown(); + std::string resetError; + if (!PSP_Init(PSP_CoreParameter(), &resetError)) { + ERROR_LOG(BOOT, "Error resetting: %s", resetError.c_str()); + return req.Fail("Could not reset"); + } + host->BootDone(); + host->UpdateDisassembly(); + + req.Respond(); +} + // Check game status (game.status) // // No parameters. diff --git a/Core/Debugger/WebSocket/GameSubscriber.h b/Core/Debugger/WebSocket/GameSubscriber.h index e3d08f4522..af43c692d0 100644 --- a/Core/Debugger/WebSocket/GameSubscriber.h +++ b/Core/Debugger/WebSocket/GameSubscriber.h @@ -21,5 +21,6 @@ DebuggerSubscriber *WebSocketGameInit(DebuggerEventHandlerMap &map); +void WebSocketGameReset(DebuggerRequest &req); void WebSocketGameStatus(DebuggerRequest &req); void WebSocketVersion(DebuggerRequest &req); diff --git a/Core/Debugger/WebSocket/ReplaySubscriber.cpp b/Core/Debugger/WebSocket/ReplaySubscriber.cpp new file mode 100644 index 0000000000..3b548a25e5 --- /dev/null +++ b/Core/Debugger/WebSocket/ReplaySubscriber.cpp @@ -0,0 +1,157 @@ +// Copyright (c) 2021- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#include +#include "Common/Data/Encoding/Base64.h" +#include "Common/Swap.h" +#include "Core/HLE/sceRtc.h" +#include "Core/Replay.h" +#include "Core/System.h" +#include "Core/Debugger/WebSocket/ReplaySubscriber.h" + +DebuggerSubscriber *WebSocketReplayInit(DebuggerEventHandlerMap &map) { + // No need to bind or alloc state, these are all global. + map["replay.begin"] = &WebSocketReplayBegin; + map["replay.abort"] = &WebSocketReplayAbort; + map["replay.flush"] = &WebSocketReplayFlush; + map["replay.execute"] = &WebSocketReplayExecute; + map["replay.status"] = &WebSocketReplayStatus; + map["replay.time.get"] = &WebSocketReplayTimeGet; + map["replay.time.set"] = &WebSocketReplayTimeSet; + + return nullptr; +} + +// Begin or resume recording of replay data (replay.begin) +// +// If a replay was previously being played back, this will keep any executed replay data up to +// this point for the next flush. To discard, break the CPU, abort, and then begin. +// +// No parameters. +// +// Empty response. +void WebSocketReplayBegin(DebuggerRequest &req) { + ReplayBeginSave(); + req.Respond(); +} + +// Abort any replay execution or recording (replay.abort) +// +// This stops executing any replay and discards any in progress recording. +// +// No parameters. +// +// Response (same event name) with no extra data. +void WebSocketReplayAbort(DebuggerRequest &req) { + ReplayAbort(); + req.Respond(); +} + +// Flush current recording data (replay.flush) +// +// Flushes event data and returns it. Note when combining, you must decode first. +// +// No parameters. +// +// Response (same event name): +// - version: unsigned integer, version number of data. +// - base64: base64 encode of binary data. +void WebSocketReplayFlush(DebuggerRequest &req) { + if (!PSP_IsInited()) + return req.Fail("Game not running"); + + std::vector data; + ReplayFlushBlob(&data); + + JsonWriter &json = req.Respond(); + json.writeInt("version", ReplayVersion()); + json.writeString("base64", Base64Encode(data.data(), data.size())); +} + +// Begin executing a replay (replay.execute) +// +// Parameters: +// - version: unsigned integer, same version from replay.flush. +// - base64: base64 encoded replay data. +// +// Response (same event name) with no extra data. +void WebSocketReplayExecute(DebuggerRequest &req) { + if (!PSP_IsInited()) + return req.Fail("Game not running"); + + uint32_t version = -1; + if (!req.ParamU32("version", &version)) + return; + std::string encoded; + if (!req.ParamString("base64", &encoded)) + return; + + std::vector data = Base64Decode(encoded.data(), encoded.size()); + if (!ReplayExecuteBlob(version, data)) + return req.Fail("Invalid replay data or version"); + + req.Respond(); +} + +// Get replay status (replay.status) +// +// No parameters. +// +// Response (same event name): +// - executing: boolean if a replay is being executed. +// - saving: boolean if a replay is being recorded. +void WebSocketReplayStatus(DebuggerRequest &req) { + JsonWriter &json = req.Respond(); + json.writeBool("executing", ReplayIsExecuting()); + json.writeBool("saving", ReplayIsSaving()); +} + +// Get the base RTC (real time clock) time for replay data (replay.time.get) +// +// The base time is constant during a game session, and represents the "power on" time of the +// emulated PSP. +// +// No parameters. +// +// Response (same event name): +// - value: unsigned integer, may have more than 32 integer bits. +void WebSocketReplayTimeGet(DebuggerRequest &req) { + if (!PSP_IsInited()) + return req.Fail("Game not running"); + + JsonWriter &json = req.Respond(); + json.writeUint("value", RtcBaseTime()); +} + +// Overwrite the base RTC time (replay.time.set) +// +// Parameters: +// - value: unsigned integer. +// +// Empty response. +void WebSocketReplayTimeSet(DebuggerRequest &req) { + if (!PSP_IsInited()) + return req.Fail("Game not running"); + + uint32_t value; + if (!req.ParamU32("value", &value, false)) { + return; + } + + RtcSetBaseTime((int32_t)value); + req.Respond(); +} diff --git a/Core/Debugger/WebSocket/ReplaySubscriber.h b/Core/Debugger/WebSocket/ReplaySubscriber.h new file mode 100644 index 0000000000..901e54f60e --- /dev/null +++ b/Core/Debugger/WebSocket/ReplaySubscriber.h @@ -0,0 +1,30 @@ +// Copyright (c) 2021- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#pragma once + +#include "Core/Debugger/WebSocket/WebSocketUtils.h" + +DebuggerSubscriber *WebSocketReplayInit(DebuggerEventHandlerMap &map); + +void WebSocketReplayBegin(DebuggerRequest &req); +void WebSocketReplayAbort(DebuggerRequest &req); +void WebSocketReplayFlush(DebuggerRequest &req); +void WebSocketReplayExecute(DebuggerRequest &req); +void WebSocketReplayStatus(DebuggerRequest &req); +void WebSocketReplayTimeGet(DebuggerRequest &req); +void WebSocketReplayTimeSet(DebuggerRequest &req); diff --git a/Core/Replay.cpp b/Core/Replay.cpp index 93b0968c25..e94eebe04b 100644 --- a/Core/Replay.cpp +++ b/Core/Replay.cpp @@ -116,7 +116,7 @@ struct ReplayFileInfo { struct ReplayItem { ReplayItemHeader info; - std::vector data; + std::vector data; ReplayItem(ReplayItemHeader h) : info(h) { } @@ -136,7 +136,16 @@ static uint8_t lastAnalog[2][2]{}; static size_t replayDiskPos = 0; static bool diskFailed = false; -void ReplayExecuteBlob(const std::vector &data) { +bool ReplayExecuteBlob(int version, const std::vector &data) { + if (version < REPLAY_VERSION_MIN || version > REPLAY_VERSION_CURRENT) { + ERROR_LOG(SYSTEM, "Bad replay data version: %d", version); + return false; + } + if (data.size() == 0) { + ERROR_LOG(SYSTEM, "Empty replay data"); + return false; + } + ReplayAbort(); // Rough estimate. @@ -167,6 +176,7 @@ void ReplayExecuteBlob(const std::vector &data) { replayState = ReplayState::EXECUTE; INFO_LOG(SYSTEM, "Executing replay with %lld items", (long long)replayItems.size()); + return true; } bool ReplayExecuteFile(const Path &filename) { @@ -178,7 +188,8 @@ bool ReplayExecuteFile(const Path &filename) { return false; } - std::vector data; + int version = -1; + std::vector data; auto loadData = [&]() { // TODO: Maybe stream instead. size_t sz = File::GetFileSize(fp); @@ -206,6 +217,9 @@ bool ReplayExecuteFile(const Path &filename) { WARN_LOG(SYSTEM, "Replay version %d scary and futuristic, trying anyway", fh.version); } + RtcSetBaseTime((int32_t)fh.rtcBaseSeconds, 0); + version = fh.version; + data.resize(sz); if (fread(&data[0], sz, 1, fp) != 1) { @@ -218,7 +232,7 @@ bool ReplayExecuteFile(const Path &filename) { if (loadData()) { fclose(fp); - ReplayExecuteBlob(data); + ReplayExecuteBlob(version, data); return true; } @@ -243,7 +257,7 @@ void ReplayBeginSave() { replayState = ReplayState::SAVE; } -void ReplayFlushBlob(std::vector *data) { +void ReplayFlushBlob(std::vector *data) { size_t sz = replayItems.size() * sizeof(ReplayItemHeader); // Add in any side data. for (const auto &item : replayItems) { @@ -289,7 +303,7 @@ bool ReplayFlushFile(const Path &filename) { size_t c = replayItems.size(); if (success && c != 0) { // TODO: Maybe stream instead. - std::vector data; + std::vector data; ReplayFlushBlob(&data); success = fwrite(&data[0], data.size(), 1, fp) == 1; @@ -304,6 +318,10 @@ bool ReplayFlushFile(const Path &filename) { return success; } +int ReplayVersion() { + return REPLAY_VERSION_CURRENT; +} + void ReplayAbort() { replayItems.clear(); replayExecPos = 0; @@ -319,6 +337,14 @@ void ReplayAbort() { diskFailed = false; } +bool ReplayIsExecuting() { + return replayState == ReplayState::EXECUTE; +} + +bool ReplayIsSaving() { + return replayState == ReplayState::SAVE; +} + static void ReplaySaveCtrl(uint32_t &buttons, uint8_t analog[2][2], uint64_t t) { if (lastButtons != buttons) { replayItems.push_back(ReplayItemHeader(ReplayAction::BUTTONS, t, buttons)); diff --git a/Core/Replay.h b/Core/Replay.h index 2229a6dee8..8ed514108c 100644 --- a/Core/Replay.h +++ b/Core/Replay.h @@ -49,24 +49,30 @@ enum class ReplayAction : uint8_t { struct PSPFileInfo; // Replay from data in memory. Does not manipulate base time / RNG state. -void ReplayExecuteBlob(const std::vector &data); +bool ReplayExecuteBlob(int version, const std::vector &data); // Replay from data in a file. Returns false if invalid. bool ReplayExecuteFile(const Path &filename); -// Returns whether there are unexected events to replay. +// Returns whether there are unexecuted events to replay. bool ReplayHasMoreEvents(); // Begin recording. If currently executing, discards unexecuted events. void ReplayBeginSave(); // Flush buffered events to memory. Continues recording (next call will receive new events only.) // No header is flushed with this operation - don't mix with ReplayFlushFile(). -void ReplayFlushBlob(std::vector *data); +void ReplayFlushBlob(std::vector *data); // Flush buffered events to file. Continues recording (next call will receive new events only.) // Do not call with a different filename before ReplayAbort(). bool ReplayFlushFile(const Path &filename); +// Get current replay data version. +int ReplayVersion(); // Abort any execute or record operation in progress. void ReplayAbort(); +// Check if replay data is being executed or saved. +bool ReplayIsExecuting(); +bool ReplayIsSaving(); + void ReplayApplyCtrl(uint32_t &buttons, uint8_t analog[2][2], uint64_t t); uint32_t ReplayApplyDisk(ReplayAction action, uint32_t result, uint64_t t); uint64_t ReplayApplyDisk64(ReplayAction action, uint64_t result, uint64_t t); diff --git a/Core/System.cpp b/Core/System.cpp index c6e510ed88..d47407f6d0 100644 --- a/Core/System.cpp +++ b/Core/System.cpp @@ -468,7 +468,8 @@ bool PSP_InitUpdate(std::string *error_string) { } bool PSP_Init(const CoreParameter &coreParam, std::string *error_string) { - PSP_InitStart(coreParam, error_string); + if (!PSP_InitStart(coreParam, error_string)) + return false; while (!PSP_InitUpdate(error_string)) sleep_ms(10); @@ -697,7 +698,7 @@ void InitSysDirectories() { File::CreateDir(GetSysDirectory(DIRECTORY_SAVESTATE)); if (g_Config.currentDirectory.empty()) { - g_Config.currentDirectory = GetSysDirectory(DIRECTORY_GAME).ToString(); + g_Config.currentDirectory = GetSysDirectory(DIRECTORY_GAME); } } #endif diff --git a/Core/System.h b/Core/System.h index c66e45ab67..d9e3cdb3a1 100644 --- a/Core/System.h +++ b/Core/System.h @@ -97,7 +97,10 @@ bool IsAudioInitialised(); void UpdateLoadedFile(FileLoader *fileLoader); +// NOTE: These are almost all derived from g_Config.memStickDirectory directly - +// they are not stored anywhere. Path GetSysDirectory(PSPDirectories directoryType); + #ifdef _WIN32 void InitSysDirectories(); #endif diff --git a/Qt/QtMain.cpp b/Qt/QtMain.cpp index 0e7916b4bc..b642f74421 100644 --- a/Qt/QtMain.cpp +++ b/Qt/QtMain.cpp @@ -528,7 +528,7 @@ bool MainUI::event(QEvent *e) { QString fileName = QFileDialog::getOpenFileName(nullptr, "Load ROM", g_Config.currentDirectory.c_str(), "PSP ROMs (*.iso *.cso *.pbp *.elf *.zip *.ppdmp)"); if (QFile::exists(fileName)) { QDir newPath; - g_Config.currentDirectory = newPath.filePath(fileName).toStdString(); + g_Config.currentDirectory = Path(newPath.filePath(fileName).toStdString()); g_Config.Save("browseFileEvent"); NativeMessageReceived("boot", fileName.toStdString().c_str()); diff --git a/Qt/mainwindow.cpp b/Qt/mainwindow.cpp index 53e18c23d4..8b766110db 100644 --- a/Qt/mainwindow.cpp +++ b/Qt/mainwindow.cpp @@ -122,7 +122,7 @@ void MainWindow::loadAct() if (QFile::exists(filename)) { QFileInfo info(filename); - g_Config.currentDirectory = info.absolutePath().toStdString(); + g_Config.currentDirectory = Path(info.absolutePath().toStdString()); NativeMessageReceived("boot", filename.toStdString().c_str()); } } @@ -252,7 +252,7 @@ void MainWindow::switchUMDAct() if (QFile::exists(filename)) { QFileInfo info(filename); - g_Config.currentDirectory = info.absolutePath().toStdString(); + g_Config.currentDirectory = Path(info.absolutePath().toStdString()); __UmdReplace(Path(filename.toStdString())); } } diff --git a/UI/DevScreens.cpp b/UI/DevScreens.cpp index 0fb6b27ee2..4827a2500c 100644 --- a/UI/DevScreens.cpp +++ b/UI/DevScreens.cpp @@ -592,7 +592,7 @@ void SystemInfoScreen::CreateViews() { storage->Add(new InfoItem("MemStickDirectory", g_Config.memStickDirectory.ToVisualString())); storage->Add(new InfoItem("InternalDataDirectory", g_Config.internalDataDirectory.ToVisualString())); storage->Add(new InfoItem("AppCacheDir", g_Config.appCacheDirectory.ToVisualString())); - storage->Add(new InfoItem("ExtStorageDir", g_Config.externalDirectory.ToVisualString())); + storage->Add(new InfoItem("DefaultCurrentDir", g_Config.defaultCurrentDirectory.ToVisualString())); #if PPSSPP_PLATFORM(ANDROID) storage->Add(new InfoItem("ExtFilesDir", g_extFilesDir)); diff --git a/UI/MainScreen.cpp b/UI/MainScreen.cpp index 34f5a2e820..f31f20e5dd 100644 --- a/UI/MainScreen.cpp +++ b/UI/MainScreen.cpp @@ -505,7 +505,7 @@ void GameBrowser::FocusGame(const Path &gamePath) { void GameBrowser::SetPath(const Path &path) { path_.SetPath(path); - g_Config.currentDirectory = path_.GetPath().ToString(); + g_Config.currentDirectory = path_.GetPath(); Refresh(); } @@ -905,7 +905,7 @@ UI::EventReturn GameBrowser::NavigateClick(UI::EventParams &e) { } else { path_.Navigate(text.ToString()); } - g_Config.currentDirectory = path_.GetPath().ToString(); + g_Config.currentDirectory = path_.GetPath(); Refresh(); return UI::EVENT_DONE; } diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp index 8246730eba..175e159c90 100644 --- a/UI/NativeApp.cpp +++ b/UI/NativeApp.cpp @@ -318,20 +318,9 @@ static bool CheckFontIsUsable(const wchar_t *fontFace) { static void PostLoadConfig() { // On Windows, we deal with currentDirectory in InitSysDirectories(). -#ifndef _WIN32 +#if !PPSSPP_PLATFORM(WINDOWS) if (g_Config.currentDirectory.empty()) { -#if defined(__ANDROID__) - g_Config.currentDirectory = g_Config.externalDirectory.ToString(); -#elif PPSSPP_PLATFORM(IOS) - g_Config.currentDirectory = g_Config.internalDataDirectory.ToString(); -#elif PPSSPP_PLATFORM(SWITCH) - g_Config.currentDirectory = "/"; -#else - if (getenv("HOME") != nullptr) - g_Config.currentDirectory = getenv("HOME"); - else - g_Config.currentDirectory = "./"; -#endif + g_Config.currentDirectory = g_Config.defaultCurrentDirectory; } #endif @@ -457,6 +446,9 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch user_data_path += "/"; #endif + // external_dir has all kinds of meanings depending on platform. + // on iOS it's even the path to bundled app assets. It's a mess. + // We want this to be FIRST. #if PPSSPP_PLATFORM(IOS) // Packed assets are included in app @@ -487,8 +479,8 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch } #endif + g_Config.defaultCurrentDirectory = Path("/"); g_Config.internalDataDirectory = Path(savegame_dir); - g_Config.externalDirectory = Path(external_dir); #if defined(__ANDROID__) // TODO: This needs to change in Android 12. @@ -496,6 +488,7 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch // Maybe there should be an option to use internal memory instead, but I think // that for most people, using external memory (SDCard/USB Storage) makes the // most sense. + g_Config.defaultCurrentDirectory = Path(external_dir); g_Config.memStickDirectory = Path(external_dir); g_Config.flash0Directory = Path(external_dir) / "flash0"; @@ -511,6 +504,7 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch } } #elif PPSSPP_PLATFORM(IOS) + g_Config.defaultCurrentDirectory = g_Config.internalDataDirectory; g_Config.memStickDirectory = Path(user_data_path); g_Config.flash0Directory = Path(std::string(external_dir)) / "flash0"; #elif PPSSPP_PLATFORM(SWITCH) @@ -527,6 +521,13 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch g_Config.memStickDirectory = Path(config) / "ppsspp"; g_Config.flash0Directory = File::GetExeDirectory() / "assets/flash0"; + if (getenv("HOME") != nullptr) { + g_Config.defaultCurrentDirectory = Path(getenv("HOME")); + } else { + // Hm, should probably actually explicitly set the current directory.. + // Though it's not many platforms that'll land us here. + g_Config.currentDirectory = Path("."); + } #endif if (cache_dir && strlen(cache_dir)) { @@ -538,14 +539,13 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch LogManager::Init(&g_Config.bEnableLogging); #if !PPSSPP_PLATFORM(WINDOWS) - g_Config.AddSearchPath(Path(user_data_path)); - g_Config.AddSearchPath(GetSysDirectory(DIRECTORY_SYSTEM)); - g_Config.SetDefaultPath(GetSysDirectory(DIRECTORY_SYSTEM)); + g_Config.SetSearchPath(GetSysDirectory(DIRECTORY_SYSTEM)); // Note that if we don't have storage permission here, loading the config will // fail and it will be set to the default. Later, we load again when we get permission. g_Config.Load(); #endif + LogManager *logman = LogManager::GetInstance(); #if PPSSPP_PLATFORM(ANDROID) @@ -1017,9 +1017,9 @@ void TakeScreenshot() { Path filename; while (i < 10000){ if (g_Config.bScreenshotsAsPNG) - filename = path / StringFromFormat("%s_%05d.png", path.c_str(), gameId.c_str(), i); + filename = path / StringFromFormat("%s_%05d.png", gameId.c_str(), i); else - filename = path / StringFromFormat("%s_%05d.jpg", path.c_str(), gameId.c_str(), i); + filename = path / StringFromFormat("%s_%05d.jpg", gameId.c_str(), i); File::FileInfo info; if (!File::Exists(filename)) break; diff --git a/UWP/CoreUWP/CoreUWP.vcxproj b/UWP/CoreUWP/CoreUWP.vcxproj index bd5f53edeb..f3f026c4d6 100644 --- a/UWP/CoreUWP/CoreUWP.vcxproj +++ b/UWP/CoreUWP/CoreUWP.vcxproj @@ -405,6 +405,7 @@ + @@ -636,6 +637,7 @@ + diff --git a/UWP/CoreUWP/CoreUWP.vcxproj.filters b/UWP/CoreUWP/CoreUWP.vcxproj.filters index 7d70fe9b42..89b790f3c4 100644 --- a/UWP/CoreUWP/CoreUWP.vcxproj.filters +++ b/UWP/CoreUWP/CoreUWP.vcxproj.filters @@ -700,6 +700,9 @@ Debugger\WebSocket + + Debugger\WebSocket + Debugger\WebSocket @@ -1701,6 +1704,9 @@ Debugger\WebSocket + + Debugger\WebSocket + Debugger\WebSocket diff --git a/UWP/PPSSPP_UWPMain.cpp b/UWP/PPSSPP_UWPMain.cpp index 949eaf0df1..29afda806e 100644 --- a/UWP/PPSSPP_UWPMain.cpp +++ b/UWP/PPSSPP_UWPMain.cpp @@ -92,9 +92,6 @@ PPSSPP_UWPMain::PPSSPP_UWPMain(App ^app, const std::shared_ptrLocalFolder->Path->Data(); g_Config.memStickDirectory = Path(memstickFolderW); @@ -106,9 +103,8 @@ PPSSPP_UWPMain::PPSSPP_UWPMain(App ^app, const std::shared_ptrpath).NavigateUp(); - g_Config.currentDirectory = retro_base_dir.ToString(); - g_Config.externalDirectory = retro_base_dir; + g_Config.currentDirectory = retro_base_dir; + g_Config.defaultCurrentDirectory = retro_base_dir; g_Config.memStickDirectory = retro_save_dir; g_Config.flash0Directory = retro_base_dir / "flash0"; g_Config.internalDataDirectory = retro_base_dir;