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;