diff --git a/Core/Config.cpp b/Core/Config.cpp index c4be6127c..2aa82500c 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -349,6 +349,8 @@ static ConfigSetting generalSettings[] = { ConfigSetting("RemoteISOPort", &g_Config.iRemoteISOPort, 0, true, false), ConfigSetting("LastRemoteISOServer", &g_Config.sLastRemoteISOServer, ""), ConfigSetting("LastRemoteISOPort", &g_Config.iLastRemoteISOPort, 0), + ConfigSetting("RemoteISOManualConfig", &g_Config.bRemoteISOManual, false), + ConfigSetting("RemoteISOSubdir", &g_Config.sRemoteISOSubdir, "/"), #ifdef __ANDROID__ ConfigSetting("ScreenRotation", &g_Config.iScreenRotation, 1), diff --git a/Core/Config.h b/Core/Config.h index f036404d0..67c406a12 100644 --- a/Core/Config.h +++ b/Core/Config.h @@ -144,6 +144,8 @@ public: int iRemoteISOPort; std::string sLastRemoteISOServer; int iLastRemoteISOPort; + bool bRemoteISOManual; + std::string sRemoteISOSubdir; bool bMemStickInserted; int iScreenRotation; // The rotation angle of the PPSSPP UI. Only supported on Android and possibly other mobile platforms. diff --git a/UI/RemoteISOScreen.cpp b/UI/RemoteISOScreen.cpp index 884e2eb12..d59145033 100644 --- a/UI/RemoteISOScreen.cpp +++ b/UI/RemoteISOScreen.cpp @@ -198,6 +198,11 @@ static bool FindServer(std::string &resultHost, int &resultPort) { return true; } + //don't scan if in manual mode + if (g_Config.bRemoteISOManual) { + return false; + } + // Start by requesting a list of recent local ips for this network. if (http.Resolve(REPORT_HOSTNAME, REPORT_PORT)) { if (http.Connect()) { @@ -251,11 +256,23 @@ static bool LoadGameList(const std::string &host, int port, std::vector responseHeaders; + std::string subdir ="/"; + size_t offset; - // Start by requesting a list of recent local ips for this network. + if (g_Config.bRemoteISOManual) { + subdir = g_Config.sRemoteISOSubdir; + offset=subdir.find_last_of("/"); + if (offset != subdir.length() - 1 && offset != subdir.npos) { + //truncate everything after last / + subdir.erase(offset + 1); + } + } + + // Start by requesting the list of games from the server. if (http.Resolve(host.c_str(), port)) { if (http.Connect()) { - code = http.GET("/", &result); + code = http.GET(subdir.c_str(), &result,responseHeaders); http.Disconnect(); } } @@ -268,19 +285,53 @@ static bool LoadGameList(const std::string &host, int port, std::vector items; result.TakeAll(&listing); - SplitString(listing, '\n', items); - for (const std::string &item : items) { - if (!endsWithNoCase(item, ".cso") && !endsWithNoCase(item, ".iso") && !endsWithNoCase(item, ".pbp")) { - continue; + std::string contentType; + for (const std::string &header : responseHeaders) { + if (startsWithNoCase(header, "Content-Type:")) { + contentType = header.substr(strlen("Content-Type:")); + // Strip any whitespace (TODO: maybe move this to stringutil?) + contentType.erase(0, contentType.find_first_not_of(" \t\r\n")); + contentType.erase(contentType.find_last_not_of(" \t\r\n") + 1); } - - char temp[1024] = {}; - snprintf(temp, sizeof(temp) - 1, "http://%s:%d%s", host.c_str(), port, item.c_str()); - games.push_back(temp); } - //save for next time - if (!games.empty()){ + // TODO: Technically, "TExt/hTml ; chaRSet = Utf8" should pass, but "text/htmlese" should not. + // But unlikely that'll be an issue. + bool parseHtml = startsWithNoCase(contentType, "text/html"); + bool parseText = startsWithNoCase(contentType, "text/plain"); + + if (parseText) { + //ppsspp server + SplitString(listing, '\n', items); + for (const std::string &item : items) { + if (!endsWithNoCase(item, ".cso") && !endsWithNoCase(item, ".iso") && !endsWithNoCase(item, ".pbp")) { + continue; + } + + char temp[1024] = {}; + snprintf(temp, sizeof(temp) - 1, "http://%s:%d%s", host.c_str(), port, item.c_str()); + games.push_back(temp); + } + } else if (parseHtml) { + //other webserver + GetQuotedStrings(listing, items); + for (const std::string &item : items) { + + if (!endsWithNoCase(item, ".cso") && !endsWithNoCase(item, ".iso") && !endsWithNoCase(item, ".pbp")) { + continue; + } + + char temp[1024] = {}; + snprintf(temp, sizeof(temp) - 1, "http://%s:%d%s%s", host.c_str(), port, subdir.c_str(),item.c_str()); + games.push_back(temp); + } + } else { + ERROR_LOG(FILESYS, "Unsupported Content-Type: %s", contentType.c_str()); + return false; + } + + //save for next time unless manual is true + if (!games.empty() && !g_Config.bRemoteISOManual){ g_Config.sLastRemoteISOServer = host; g_Config.iLastRemoteISOPort = port; } @@ -322,8 +373,6 @@ void RemoteISOScreen::CreateViews() { leftColumnItems->Add(new TextView(sy->T("RemoteISODesc", "Games in your recent list will be shared"), new LinearLayoutParams(Margins(12, 5, 0, 5)))); leftColumnItems->Add(new TextView(sy->T("RemoteISOWifi", "Note: Connect both devices to the same wifi"), new LinearLayoutParams(Margins(12, 5, 0, 5)))); - // TODO: Could display server address for manual entry. - rightColumnItems->SetSpacing(0.0f); Choice *browseChoice = new Choice(sy->T("Browse Games")); rightColumnItems->Add(browseChoice)->OnClick.Handle(this, &RemoteISOScreen::HandleBrowse); @@ -338,6 +387,8 @@ void RemoteISOScreen::CreateViews() { rightColumnItems->Add(new Choice(sy->T("Share Games (Server)")))->OnClick.Handle(this, &RemoteISOScreen::HandleStartServer); browseChoice->SetEnabled(true); } + Choice *settingsChoice = new Choice(sy->T("Settings")); + rightColumnItems->Add(settingsChoice)->OnClick.Handle(this, &RemoteISOScreen::HandleSettings); rightColumnItems->Add(new Spacer(25.0)); rightColumnItems->Add(new Choice(di->T("Back"), "", false, new AnchorLayoutParams(150, WRAP_CONTENT, 10, NONE, NONE, 10)))->OnClick.Handle(this, &UIScreen::OnBack); @@ -383,6 +434,11 @@ UI::EventReturn RemoteISOScreen::HandleBrowse(UI::EventParams &e) { return EVENT_DONE; } +UI::EventReturn RemoteISOScreen::HandleSettings(UI::EventParams &e) { + screenManager()->push(new RemoteISOSettingsScreen()); + return EVENT_DONE; +} + RemoteISOConnectScreen::RemoteISOConnectScreen() : status_(ScanStatus::SCANNING), nextRetry_(0.0) { scanCancelled = false; @@ -582,3 +638,42 @@ void RemoteISOBrowseScreen::CreateViews() { upgradeBar_ = 0; } + +void RemoteISOSettingsScreen::CreateViews() { + I18NCategory *di = GetI18NCategory("Dialog"); + I18NCategory *n = GetI18NCategory("Networking"); + I18NCategory *ms = GetI18NCategory("MainSettings"); + + ViewGroup *remoteisoSettingsScroll = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, FILL_PARENT)); + remoteisoSettingsScroll->SetTag("GameSettingsNetworking"); + LinearLayout *remoteisoSettings = new LinearLayout(ORIENT_VERTICAL); + remoteisoSettings->SetSpacing(0); + remoteisoSettingsScroll->Add(remoteisoSettings); + + remoteisoSettings->Add(new ItemHeader(ms->T("Remote Disc Streaming"))); + remoteisoSettings->Add(new CheckBox(&g_Config.bRemoteISOManual, n->T("Manual Mode Client", "Manual Mode Client"))); + PopupTextInputChoice *remoteServer = remoteisoSettings->Add(new PopupTextInputChoice(&g_Config.sLastRemoteISOServer, n->T("Remote Server"), "", 255, screenManager())); + remoteServer->SetEnabledPtr(&g_Config.bRemoteISOManual); + PopupSliderChoice *remotePort = remoteisoSettings->Add(new PopupSliderChoice(&g_Config.iLastRemoteISOPort, 0, 65535, n->T("Remote Port", "Remote Port"), 100, screenManager())); + remotePort->SetEnabledPtr(&g_Config.bRemoteISOManual); + PopupTextInputChoice *remoteSubdir = remoteisoSettings->Add(new PopupTextInputChoice(&g_Config.sRemoteISOSubdir, n->T("Remote Subdirectory"), "", 255, screenManager())); + remoteSubdir->SetEnabledPtr(&g_Config.bRemoteISOManual); + remoteSubdir->OnChange.Handle(this, &RemoteISOSettingsScreen::OnChangeRemoteISOSubdir); + remoteisoSettings->Add(new PopupSliderChoice(&g_Config.iRemoteISOPort, 0, 65535, n->T("Local Server Port", "Local Server Port"), 100, screenManager())); + remoteisoSettings->Add(new Spacer(25.0)); + remoteisoSettings->Add(new Choice(di->T("Back"), "", false, new AnchorLayoutParams(150, WRAP_CONTENT, 10, NONE, NONE, 10)))->OnClick.Handle(this, &UIScreen::OnBack); + + root_ = new AnchorLayout(new LayoutParams(FILL_PARENT, FILL_PARENT)); + root_->Add(remoteisoSettings); +} + +UI::EventReturn RemoteISOSettingsScreen::OnChangeRemoteISOSubdir(UI::EventParams &e) { + //Conform to HTTP standards + ReplaceAll(g_Config.sRemoteISOSubdir, " ", "%20"); + ReplaceAll(g_Config.sRemoteISOSubdir, "\\", "/"); + //Make sure it begins with / + if (g_Config.sRemoteISOSubdir[0] != '/') + g_Config.sRemoteISOSubdir = "/" + g_Config.sRemoteISOSubdir; + + return UI::EVENT_DONE; +} diff --git a/UI/RemoteISOScreen.h b/UI/RemoteISOScreen.h index d4bcd4074..9ceb5c087 100644 --- a/UI/RemoteISOScreen.h +++ b/UI/RemoteISOScreen.h @@ -36,6 +36,7 @@ protected: UI::EventReturn HandleStartServer(UI::EventParams &e); UI::EventReturn HandleStopServer(UI::EventParams &e); UI::EventReturn HandleBrowse(UI::EventParams &e); + UI::EventReturn HandleSettings(UI::EventParams &e); bool serverRunning_; bool serverStopping_; @@ -83,3 +84,13 @@ protected: std::vector games_; }; + +class RemoteISOSettingsScreen : public UIScreenWithBackground { +public: + +protected: + void CreateViews() override; + + UI::EventReturn OnChangeRemoteISOSubdir(UI::EventParams &e); + +}; \ No newline at end of file diff --git a/ext/native/base/stringutil.cpp b/ext/native/base/stringutil.cpp index 4c72959b3..0fadb197b 100644 --- a/ext/native/base/stringutil.cpp +++ b/ext/native/base/stringutil.cpp @@ -279,6 +279,26 @@ void SplitString(const std::string& str, const char delim, std::vector& output) +{ + size_t next = 0; + bool even = 0; + for (size_t pos = 0, len = str.length(); pos < len; ++pos) { + if (str[pos] == '\"' || str[pos] == '\'') { + if (even) { + //quoted text + output.push_back(str.substr(next, pos - next)); + even = 0; + } else { + //non quoted text + even = 1; + } + // Skip the delimiter itself. + next = pos + 1; + } + } +} + std::string ReplaceAll(std::string result, const std::string& src, const std::string& dest) { size_t pos = 0; diff --git a/ext/native/base/stringutil.h b/ext/native/base/stringutil.h index e001f4253..9205c009b 100644 --- a/ext/native/base/stringutil.h +++ b/ext/native/base/stringutil.h @@ -113,6 +113,8 @@ static bool TryParse(const std::string &str, N *const output) } void SplitString(const std::string& str, const char delim, std::vector& output); +void GetQuotedStrings(const std::string& str, std::vector& output); + std::string ReplaceAll(std::string input, const std::string& src, const std::string& dest); // Compare two strings, ignore the difference between the ignorestr1 and the ignorestr2 in str1 and str2. diff --git a/ext/native/net/http_client.cpp b/ext/native/net/http_client.cpp index a9ddf9bbf..a8836b257 100644 --- a/ext/native/net/http_client.cpp +++ b/ext/native/net/http_client.cpp @@ -180,7 +180,7 @@ void DeChunk(Buffer *inbuffer, Buffer *outbuffer, int contentLength, float *prog } } -int Client::GET(const char *resource, Buffer *output, float *progress, bool *cancelled) { +int Client::GET(const char *resource, Buffer *output, std::vector &responseHeaders, float *progress, bool *cancelled) { const char *otherHeaders = "Accept: */*\r\n" "Accept-Encoding: gzip\r\n"; @@ -190,7 +190,6 @@ int Client::GET(const char *resource, Buffer *output, float *progress, bool *can } Buffer readbuf; - std::vector responseHeaders; int code = ReadResponseHeaders(&readbuf, responseHeaders, progress); if (code < 0) { return code; @@ -203,6 +202,12 @@ int Client::GET(const char *resource, Buffer *output, float *progress, bool *can return code; } +int Client::GET(const char *resource, Buffer *output, float *progress, bool *cancelled) { + std::vector responseHeaders; + int code = GET(resource, output, responseHeaders, progress, cancelled); + return code; +} + int Client::POST(const char *resource, const std::string &data, const std::string &mime, Buffer *output, float *progress) { char otherHeaders[2048]; if (mime.empty()) { diff --git a/ext/native/net/http_client.h b/ext/native/net/http_client.h index ec2c40cb8..114754030 100644 --- a/ext/native/net/http_client.h +++ b/ext/native/net/http_client.h @@ -59,6 +59,7 @@ public: // Return value is the HTTP return code. 200 means OK. < 0 means some local error. int GET(const char *resource, Buffer *output, float *progress = nullptr, bool *cancelled = nullptr); + int GET(const char *resource, Buffer *output, std::vector &responseHeaders, float *progress = nullptr, bool *cancelled = nullptr); // Return value is the HTTP return code. int POST(const char *resource, const std::string &data, const std::string &mime, Buffer *output, float *progress = nullptr); diff --git a/ext/native/net/http_server.cpp b/ext/native/net/http_server.cpp index 57ceab984..9357d2fe6 100644 --- a/ext/native/net/http_server.cpp +++ b/ext/native/net/http_server.cpp @@ -86,7 +86,7 @@ void Request::WriteHttpResponseHeader(int status, int64_t size, const char *mime net::OutputSink *buffer = Out(); buffer->Printf("HTTP/1.0 %03d %s\r\n", status, statusStr); - buffer->Push("Server: SuperDuperServer v0.1\r\n"); + buffer->Push("Server: PPSSPPServer v0.1\r\n"); buffer->Printf("Content-Type: %s\r\n", mimeType ? mimeType : DEFAULT_MIME_TYPE); buffer->Push("Connection: close\r\n"); if (size >= 0) {