1 // Copyright (c) 2014- PPSSPP Project.
2 
3 // This program is free software: you can redistribute it and/or modify
4 // it under the terms of the GNU General Public License as published by
5 // the Free Software Foundation, version 2.0 or later versions.
6 
7 // This program is distributed in the hope that it will be useful,
8 // but WITHOUT ANY WARRANTY; without even the implied warranty of
9 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10 // GNU General Public License 2.0 for more details.
11 
12 // A copy of the GPL 2.0 should have been included with the program.
13 // If not, see http://www.gnu.org/licenses/
14 
15 // Official git repository and contact information can be found at
16 // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
17 
18 #include "ppsspp_config.h"
19 #include <algorithm>
20 #include <thread>
21 #include <mutex>
22 
23 #if PPSSPP_PLATFORM(WINDOWS) && !PPSSPP_PLATFORM(UWP)
24 #include "Common/CommonWindows.h"
25 #include <netfw.h>
26 #endif
27 
28 // TODO: For text align flags, probably shouldn't be in gfx_es2/...
29 #include "Common/Render/DrawBuffer.h"
30 #include "Common/Net/HTTPClient.h"
31 #include "Common/Net/Resolve.h"
32 #include "Common/Net/URL.h"
33 
34 #include "Common/File/PathBrowser.h"
35 #include "Common/Data/Format/JSONReader.h"
36 #include "Common/Data/Text/I18n.h"
37 #include "Common/Common.h"
38 #include "Common/TimeUtil.h"
39 #include "Common/StringUtils.h"
40 #include "Common/System/System.h"
41 #include "Core/Config.h"
42 #include "Core/WebServer.h"
43 #include "UI/RemoteISOScreen.h"
44 
45 using namespace UI;
46 
47 static const char *REPORT_HOSTNAME = "report.ppsspp.org";
48 static const int REPORT_PORT = 80;
49 
50 static bool scanCancelled = false;
51 static bool scanAborted = false;
52 
53 enum class ServerAllowStatus {
54 	NO,
55 	YES,
56 	UNKNOWN,
57 };
58 
IsServerAllowed(int port)59 static ServerAllowStatus IsServerAllowed(int port) {
60 #if PPSSPP_PLATFORM(WINDOWS) && !PPSSPP_PLATFORM(UWP)
61 	INetFwMgr *fwMgr = nullptr;
62 	HRESULT hr = CoCreateInstance(__uuidof(NetFwMgr), nullptr, CLSCTX_INPROC_SERVER, __uuidof(INetFwMgr), (void **)&fwMgr);
63 	if (FAILED(hr)) {
64 		return ServerAllowStatus::UNKNOWN;
65 	}
66 
67 	std::wstring app;
68 	size_t sz;
69 	do {
70 		app.resize(app.size() + MAX_PATH);
71 		// On failure, this will return the same value as passed in, but success will always be one lower.
72 		sz = GetModuleFileName(nullptr, &app[0], (DWORD)app.size());
73 	} while (sz >= app.size());
74 
75 	VARIANT allowedV, restrictedV;
76 	VariantInit(&allowedV);
77 	VariantInit(&restrictedV);
78 	hr = fwMgr->IsPortAllowed(&app[0], NET_FW_IP_VERSION_ANY, port, nullptr, NET_FW_IP_PROTOCOL_TCP, &allowedV, &restrictedV);
79 	fwMgr->Release();
80 
81 	if (FAILED(hr)) {
82 		return ServerAllowStatus::UNKNOWN;
83 	}
84 
85 	bool allowed = allowedV.vt == VT_BOOL && allowedV.boolVal != VARIANT_FALSE;
86 	bool restricted = restrictedV.vt == VT_BOOL && restrictedV.boolVal != VARIANT_FALSE;
87 	if (!allowed || restricted) {
88 		return ServerAllowStatus::NO;
89 	}
90 	return ServerAllowStatus::YES;
91 #else
92 	return ServerAllowStatus::UNKNOWN;
93 #endif
94 }
95 
RemoteSubdir()96 static std::string RemoteSubdir() {
97 	if (g_Config.bRemoteISOManual) {
98 		return g_Config.sRemoteISOSubdir;
99 	}
100 
101 	return "/";
102 }
103 
FindServer(std::string & resultHost,int & resultPort)104 bool RemoteISOConnectScreen::FindServer(std::string &resultHost, int &resultPort) {
105 	http::Client http;
106 	Buffer result;
107 	int code = 500;
108 	bool hadTimeouts = false;
109 
110 	std::string subdir = RemoteSubdir();
111 
112 	auto ri = GetI18NCategory("RemoteISO");
113 	auto SetStatus = [&](const std::string &key, const std::string &host, int port) {
114 		std::string formatted = ReplaceAll(ri->T(key), "[URL]", StringFromFormat("http://%s:%d/", host.c_str(), port));
115 
116 		std::lock_guard<std::mutex> guard(statusLock_);
117 		statusMessage_ = formatted;
118 	};
119 
120 	http.SetUserAgent(StringFromFormat("PPSSPP/%s", PPSSPP_GIT_VERSION));
121 
122 	auto TryServer = [&](const std::string &host, int port) {
123 		SetStatus("Resolving [URL]...", host, port);
124 		if (!http.Resolve(host.c_str(), port)) {
125 			SetStatus("Could not resolve [URL]", host, port);
126 			return false;
127 		}
128 
129 		SetStatus("Connecting to [URL]...", host, port);
130 		// Don't wait as long for a connect - we need a good connection for smooth streaming anyway.
131 		// This way if it's down, we'll find the right one faster.
132 		if (!http.Connect(1, 10.0, &scanCancelled)) {
133 			hadTimeouts = true;
134 			SetStatus("Could not connect to [URL]", host, port);
135 			return false;
136 		}
137 
138 		SetStatus("Loading game list from [URL]...", host, port);
139 		http::RequestProgress progress(&scanCancelled);
140 		code = http.GET(http::RequestParams(subdir.c_str()), &result, &progress);
141 		http.Disconnect();
142 
143 		if (code != 200) {
144 			if (code < 0) {
145 				hadTimeouts = true;
146 			}
147 			SetStatus("Game list failed from [URL]", host, port);
148 			return false;
149 		}
150 
151 		// Make sure this isn't just the debugger.  If so, move on.
152 		std::string listing;
153 		std::vector<std::string> items;
154 		result.TakeAll(&listing);
155 		SplitString(listing, '\n', items);
156 
157 		bool supported = false;
158 		for (const std::string &item : items) {
159 			if (!RemoteISOFileSupported(item)) {
160 				continue;
161 			}
162 			supported = true;
163 			break;
164 		}
165 
166 		if (supported) {
167 			resultHost = host;
168 			resultPort = port;
169 			SetStatus("Connected to [URL]", host, port);
170 			NOTICE_LOG(SYSTEM, "RemoteISO found: %s : %d", host.c_str(), port);
171 			return true;
172 		}
173 
174 		return false;
175 	};
176 
177 	// Try last server first, if it is set
178 	if (g_Config.iLastRemoteISOPort && g_Config.sLastRemoteISOServer != "") {
179 		if (TryServer(g_Config.sLastRemoteISOServer.c_str(), g_Config.iLastRemoteISOPort)) {
180 			return true;
181 		}
182 	}
183 
184 	// Don't scan if in manual mode.
185 	if (g_Config.bRemoteISOManual || scanCancelled) {
186 		return false;
187 	}
188 
189 	// Start by requesting a list of recent local ips for this network.
190 	SetStatus("Looking for peers...", "", 0);
191 	if (http.Resolve(REPORT_HOSTNAME, REPORT_PORT)) {
192 		if (http.Connect(2, 20.0, &scanCancelled)) {
193 			http::RequestProgress progress(&scanCancelled);
194 			code = http.GET(http::RequestParams("/match/list"), &result, &progress);
195 			http.Disconnect();
196 		}
197 	}
198 
199 	if (code != 200 || scanCancelled) {
200 		if (!scanCancelled) {
201 			SetStatus("Could not load peers, retrying soon...", "", 0);
202 		}
203 		return false;
204 	}
205 
206 	std::string json;
207 	result.TakeAll(&json);
208 
209 	using namespace json;
210 
211 	JsonReader reader(json.c_str(), json.size());
212 	if (!reader.ok()) {
213 		SetStatus("Could not load peers, retrying soon...", "", 0);
214 		return false;
215 	}
216 
217 	const JsonValue entries = reader.rootArray();
218 	if (entries.getTag() != JSON_ARRAY) {
219 		SetStatus("Could not load peers, retrying soon...", "", 0);
220 		return false;
221 	}
222 
223 	for (const auto pentry : entries) {
224 		JsonGet entry = pentry->value;
225 		if (scanCancelled)
226 			return false;
227 
228 		const char *host = entry.getString("ip", "");
229 		int port = entry.getInt("p", 0);
230 
231 		if (TryServer(host, port)) {
232 			return true;
233 		}
234 	}
235 
236 	// None of the local IPs were reachable.  We'll retry again.
237 	std::lock_guard<std::mutex> guard(statusLock_);
238 	if (hadTimeouts) {
239 		statusMessage_ = ri->T("RemoteISOScanningTimeout", "Scanning... check your desktop's firewall settings");
240 	} else {
241 		statusMessage_ = ri->T("RemoteISOScanning", "Scanning... click Share Games on your desktop");
242 	}
243 	return false;
244 }
245 
LoadGameList(const Path & url,std::vector<Path> & games)246 static bool LoadGameList(const Path &url, std::vector<Path> &games) {
247 	PathBrowser browser(url);
248 	std::vector<File::FileInfo> files;
249 	browser.GetListing(files, "iso:cso:pbp:elf:prx:ppdmp:", &scanCancelled);
250 	if (scanCancelled) {
251 		return false;
252 	}
253 	for (auto &file : files) {
254 		if (file.isDirectory || RemoteISOFileSupported(file.name)) {
255 			games.push_back(file.fullName);
256 		}
257 	}
258 
259 	return !games.empty();
260 }
261 
RemoteISOScreen()262 RemoteISOScreen::RemoteISOScreen() {
263 }
264 
update()265 void RemoteISOScreen::update() {
266 	UIScreenWithBackground::update();
267 
268 	if (!WebServerStopped(WebServerFlags::DISCS)) {
269 		auto result = IsServerAllowed(g_Config.iRemoteISOPort);
270 		if (result == ServerAllowStatus::NO) {
271 			firewallWarning_->SetVisibility(V_VISIBLE);
272 		} else if (result == ServerAllowStatus::YES) {
273 			firewallWarning_->SetVisibility(V_GONE);
274 		}
275 	}
276 
277 	bool nowRunning = !WebServerStopped(WebServerFlags::DISCS);
278 	if (serverStopping_ && !nowRunning) {
279 		serverStopping_ = false;
280 	}
281 
282 	if (serverRunning_ != nowRunning) {
283 		RecreateViews();
284 	}
285 	serverRunning_ = nowRunning;
286 }
287 
CreateViews()288 void RemoteISOScreen::CreateViews() {
289 	auto di = GetI18NCategory("Dialog");
290 	auto ri = GetI18NCategory("RemoteISO");
291 
292 	Margins actionMenuMargins(0, 20, 15, 0);
293 	Margins contentMargins(0, 20, 5, 5);
294 	ViewGroup *leftColumn = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, FILL_PARENT, 0.4f, contentMargins));
295 	LinearLayout *leftColumnItems = new LinearLayout(ORIENT_VERTICAL, new LayoutParams(WRAP_CONTENT, FILL_PARENT));
296 	ViewGroup *rightColumn = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(300, FILL_PARENT, actionMenuMargins));
297 	LinearLayout *rightColumnItems = new LinearLayout(ORIENT_VERTICAL);
298 
299 	leftColumnItems->Add(new TextView(ri->T("RemoteISODesc", "Games in your recent list will be shared"), new LinearLayoutParams(Margins(12, 5, 0, 5))));
300 	leftColumnItems->Add(new TextView(ri->T("RemoteISOWifi", "Note: Connect both devices to the same wifi"), new LinearLayoutParams(Margins(12, 5, 0, 5))));
301 	firewallWarning_ = leftColumnItems->Add(new TextView(ri->T("RemoteISOWinFirewall", "WARNING: Windows Firewall is blocking sharing"), new LinearLayoutParams(Margins(12, 5, 0, 5))));
302 	firewallWarning_->SetTextColor(0xFF0000FF);
303 	firewallWarning_->SetVisibility(V_GONE);
304 
305 	rightColumnItems->SetSpacing(0.0f);
306 	Choice *browseChoice = new Choice(ri->T("Browse Games"));
307 	rightColumnItems->Add(browseChoice)->OnClick.Handle(this, &RemoteISOScreen::HandleBrowse);
308 	if (WebServerStopping(WebServerFlags::DISCS)) {
309 		rightColumnItems->Add(new Choice(ri->T("Stopping..")))->SetDisabledPtr(&serverStopping_);
310 		browseChoice->SetEnabled(false);
311 	} else if (!WebServerStopped(WebServerFlags::DISCS)) {
312 		rightColumnItems->Add(new Choice(ri->T("Stop Sharing")))->OnClick.Handle(this, &RemoteISOScreen::HandleStopServer);
313 		browseChoice->SetEnabled(false);
314 	} else {
315 		rightColumnItems->Add(new Choice(ri->T("Share Games (Server)")))->OnClick.Handle(this, &RemoteISOScreen::HandleStartServer);
316 		browseChoice->SetEnabled(true);
317 	}
318 	Choice *settingsChoice = new Choice(ri->T("Settings"));
319 	rightColumnItems->Add(settingsChoice)->OnClick.Handle(this, &RemoteISOScreen::HandleSettings);
320 
321 	LinearLayout *beforeBack = new LinearLayout(ORIENT_HORIZONTAL, new AnchorLayoutParams(FILL_PARENT, FILL_PARENT));
322 	beforeBack->Add(leftColumn);
323 	beforeBack->Add(rightColumn);
324 	root_ = new AnchorLayout(new LayoutParams(FILL_PARENT, FILL_PARENT));
325 	root_->Add(beforeBack);
326 	root_->Add(new Choice(di->T("Back"), "", false, new AnchorLayoutParams(150, WRAP_CONTENT, 10, NONE, NONE, 10)))->OnClick.Handle<UIScreen>(this, &UIScreen::OnBack);
327 
328 	leftColumn->Add(leftColumnItems);
329 	rightColumn->Add(rightColumnItems);
330 }
331 
HandleStartServer(UI::EventParams & e)332 UI::EventReturn RemoteISOScreen::HandleStartServer(UI::EventParams &e) {
333 	if (!StartWebServer(WebServerFlags::DISCS)) {
334 		return EVENT_SKIPPED;
335 	}
336 
337 	return EVENT_DONE;
338 }
339 
HandleStopServer(UI::EventParams & e)340 UI::EventReturn RemoteISOScreen::HandleStopServer(UI::EventParams &e) {
341 	if (!StopWebServer(WebServerFlags::DISCS)) {
342 		return EVENT_SKIPPED;
343 	}
344 
345 	serverStopping_ = true;
346 	RecreateViews();
347 
348 	return EVENT_DONE;
349 }
350 
HandleBrowse(UI::EventParams & e)351 UI::EventReturn RemoteISOScreen::HandleBrowse(UI::EventParams &e) {
352 	screenManager()->push(new RemoteISOConnectScreen());
353 	return EVENT_DONE;
354 }
355 
HandleSettings(UI::EventParams & e)356 UI::EventReturn RemoteISOScreen::HandleSettings(UI::EventParams &e) {
357 	screenManager()->push(new RemoteISOSettingsScreen());
358 	return EVENT_DONE;
359 }
360 
RemoteISOConnectScreen()361 RemoteISOConnectScreen::RemoteISOConnectScreen() {
362 	scanCancelled = false;
363 	scanAborted = false;
364 
365 	scanThread_ = new std::thread([](RemoteISOConnectScreen *thiz) {
366 		thiz->ExecuteScan();
367 	}, this);
368 }
369 
~RemoteISOConnectScreen()370 RemoteISOConnectScreen::~RemoteISOConnectScreen() {
371 	int maxWait = 5000;
372 	scanCancelled = true;
373 	while (GetStatus() == ScanStatus::SCANNING || GetStatus() == ScanStatus::LOADING) {
374 		sleep_ms(1);
375 		if (--maxWait < 0) {
376 			// If it does ever wake up, it may crash... but better than hanging?
377 			scanAborted = true;
378 			break;
379 		}
380 	}
381 	if (scanThread_->joinable())
382 		scanThread_->join();
383 	delete scanThread_;
384 }
385 
CreateViews()386 void RemoteISOConnectScreen::CreateViews() {
387 	auto di = GetI18NCategory("Dialog");
388 	auto ri = GetI18NCategory("RemoteISO");
389 
390 	Margins actionMenuMargins(0, 20, 15, 0);
391 	Margins contentMargins(0, 20, 5, 5);
392 	ViewGroup *leftColumn = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(WRAP_CONTENT, FILL_PARENT, 0.4f, contentMargins));
393 	LinearLayout *leftColumnItems = new LinearLayout(ORIENT_VERTICAL, new LayoutParams(WRAP_CONTENT, FILL_PARENT));
394 	ViewGroup *rightColumn = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(300, FILL_PARENT, actionMenuMargins));
395 	LinearLayout *rightColumnItems = new LinearLayout(ORIENT_VERTICAL);
396 
397 	statusView_ = leftColumnItems->Add(new TextView(ri->T("RemoteISOScanning", "Scanning... click Share Games on your desktop"), FLAG_WRAP_TEXT, false, new LinearLayoutParams(Margins(12, 5, 0, 5))));
398 
399 	rightColumnItems->SetSpacing(0.0f);
400 	rightColumnItems->Add(new Choice(di->T("Cancel"), "", false, new AnchorLayoutParams(150, WRAP_CONTENT, 10, NONE, NONE, 10)))->OnClick.Handle<UIScreen>(this, &UIScreen::OnBack);
401 
402 	root_ = new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(FILL_PARENT, FILL_PARENT, 1.0f));
403 	root_->Add(leftColumn);
404 	root_->Add(rightColumn);
405 
406 	leftColumn->Add(leftColumnItems);
407 	rightColumn->Add(rightColumnItems);
408 }
409 
update()410 void RemoteISOConnectScreen::update() {
411 	auto ri = GetI18NCategory("RemoteISO");
412 
413 	UIScreenWithBackground::update();
414 
415 	ScanStatus s = GetStatus();
416 	switch (s) {
417 	case ScanStatus::SCANNING:
418 	case ScanStatus::LOADING:
419 		break;
420 
421 	case ScanStatus::FOUND:
422 		statusView_->SetText(ri->T("RemoteISOLoading", "Connected - loading game list"));
423 		status_ = ScanStatus::LOADING;
424 
425 		// Let's reuse scanThread_.
426 		if (scanThread_->joinable())
427 			scanThread_->join();
428 		delete scanThread_;
429 		statusMessage_.clear();
430 		scanThread_ = new std::thread([](RemoteISOConnectScreen *thiz) {
431 			thiz->ExecuteLoad();
432 		}, this);
433 		break;
434 
435 	case ScanStatus::FAILED:
436 		nextRetry_ = time_now_d() + 15.0;
437 		status_ = ScanStatus::RETRY_SCAN;
438 		break;
439 
440 	case ScanStatus::RETRY_SCAN:
441 		if (nextRetry_ < time_now_d()) {
442 			status_ = ScanStatus::SCANNING;
443 			nextRetry_ = 0.0;
444 
445 			if (scanThread_->joinable())
446 				scanThread_->join();
447 			delete scanThread_;
448 			statusMessage_.clear();
449 			scanThread_ = new std::thread([](RemoteISOConnectScreen *thiz) {
450 				thiz->ExecuteScan();
451 			}, this);
452 		}
453 		break;
454 
455 	case ScanStatus::LOADED:
456 		TriggerFinish(DR_OK);
457 		screenManager()->push(new RemoteISOBrowseScreen(url_, games_));
458 		break;
459 	}
460 
461 	std::lock_guard<std::mutex> guard(statusLock_);
462 	if (!statusMessage_.empty()) {
463 		statusView_->SetText(statusMessage_);
464 	}
465 }
466 
ExecuteScan()467 void RemoteISOConnectScreen::ExecuteScan() {
468 	FindServer(host_, port_);
469 	if (scanAborted) {
470 		return;
471 	}
472 
473 	std::lock_guard<std::mutex> guard(statusLock_);
474 	status_ = host_.empty() ? ScanStatus::FAILED : ScanStatus::FOUND;
475 }
476 
GetStatus()477 ScanStatus RemoteISOConnectScreen::GetStatus() {
478 	std::lock_guard<std::mutex> guard(statusLock_);
479 	return status_;
480 }
481 
ExecuteLoad()482 void RemoteISOConnectScreen::ExecuteLoad() {
483 	std::string subdir = RemoteSubdir();
484 	url_ = StringFromFormat("http://%s:%d%s", host_.c_str(), port_, subdir.c_str());
485 	bool result = LoadGameList(Path(url_), games_);
486 	if (scanAborted) {
487 		return;
488 	}
489 
490 	if (result && !games_.empty() && !g_Config.bRemoteISOManual) {
491 		g_Config.sLastRemoteISOServer = host_;
492 		g_Config.iLastRemoteISOPort = port_;
493 	}
494 
495 	std::lock_guard<std::mutex> guard(statusLock_);
496 	status_ = result ? ScanStatus::LOADED : ScanStatus::FAILED;
497 }
498 
499 class RemoteGameBrowser : public GameBrowser {
500 public:
RemoteGameBrowser(const Path & url,BrowseFlags browseFlags,bool * gridStyle_,ScreenManager * screenManager,std::string lastText,std::string lastLink,UI::LayoutParams * layoutParams=nullptr)501 	RemoteGameBrowser(const Path &url, BrowseFlags browseFlags, bool *gridStyle_, ScreenManager *screenManager, std::string lastText, std::string lastLink, UI::LayoutParams *layoutParams = nullptr)
502 		: GameBrowser(url, browseFlags, gridStyle_, screenManager, lastText, lastLink, layoutParams) {
503 		initialPath_ = url;
504 	}
505 
506 protected:
HomePath()507 	Path HomePath() override {
508 		return initialPath_;
509 	}
510 
511 	Path initialPath_;
512 };
513 
RemoteISOBrowseScreen(const std::string & url,const std::vector<Path> & games)514 RemoteISOBrowseScreen::RemoteISOBrowseScreen(const std::string &url, const std::vector<Path> &games)
515 	: url_(url), games_(games) {
516 }
517 
CreateViews()518 void RemoteISOBrowseScreen::CreateViews() {
519 	bool vertical = UseVerticalLayout();
520 
521 	auto di = GetI18NCategory("Dialog");
522 	auto ri = GetI18NCategory("RemoteISO");
523 
524 	Margins actionMenuMargins(0, 10, 10, 0);
525 
526 	TabHolder *leftColumn = new TabHolder(ORIENT_HORIZONTAL, 64, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT));
527 	tabHolder_ = leftColumn;
528 	tabHolder_->SetTag("RemoteGames");
529 	gameBrowsers_.clear();
530 
531 	leftColumn->SetClip(true);
532 
533 	ScrollView *scrollRecentGames = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT));
534 	scrollRecentGames->SetTag("RemoteGamesTab");
535 	GameBrowser *tabRemoteGames = new RemoteGameBrowser(
536 		Path(url_), BrowseFlags::PIN | BrowseFlags::NAVIGATE, &g_Config.bGridView1, screenManager(), "", "",
537 		new LinearLayoutParams(FILL_PARENT, FILL_PARENT));
538 	scrollRecentGames->Add(tabRemoteGames);
539 	gameBrowsers_.push_back(tabRemoteGames);
540 
541 	leftColumn->AddTab(ri->T("Remote Server"), scrollRecentGames);
542 	tabRemoteGames->OnChoice.Handle<MainScreen>(this, &MainScreen::OnGameSelectedInstant);
543 	tabRemoteGames->OnHoldChoice.Handle<MainScreen>(this, &MainScreen::OnGameSelected);
544 	tabRemoteGames->OnHighlight.Handle<MainScreen>(this, &MainScreen::OnGameHighlight);
545 
546 	ViewGroup *rightColumn = new ScrollView(ORIENT_VERTICAL);
547 	LinearLayout *rightColumnItems = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT));
548 	rightColumnItems->SetSpacing(0.0f);
549 	rightColumn->Add(rightColumnItems);
550 
551 	rightColumnItems->Add(new Choice(di->T("Back"), "", false, new AnchorLayoutParams(150, WRAP_CONTENT, 10, NONE, NONE, 10)))->OnClick.Handle<UIScreen>(this, &UIScreen::OnBack);
552 
553 	if (vertical) {
554 		root_ = new LinearLayout(ORIENT_VERTICAL);
555 		rightColumn->ReplaceLayoutParams(new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT));
556 		leftColumn->ReplaceLayoutParams(new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT, 1.0));
557 		root_->Add(rightColumn);
558 		root_->Add(leftColumn);
559 	} else {
560 		root_ = new LinearLayout(ORIENT_HORIZONTAL);
561 		leftColumn->ReplaceLayoutParams(new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT, 1.0));
562 		rightColumn->ReplaceLayoutParams(new LinearLayoutParams(300, FILL_PARENT, actionMenuMargins));
563 		root_->Add(leftColumn);
564 		root_->Add(rightColumn);
565 	}
566 
567 	root_->SetDefaultFocusView(tabHolder_);
568 
569 	upgradeBar_ = 0;
570 }
571 
RemoteISOSettingsScreen()572 RemoteISOSettingsScreen::RemoteISOSettingsScreen() {
573 	serverRunning_ = !WebServerStopped(WebServerFlags::DISCS);
574 }
575 
update()576 void RemoteISOSettingsScreen::update() {
577 	UIDialogScreenWithBackground::update();
578 
579 	bool nowRunning = !WebServerStopped(WebServerFlags::DISCS);
580 	if (serverRunning_ != nowRunning) {
581 		RecreateViews();
582 	}
583 	serverRunning_ = nowRunning;
584 }
585 
CreateViews()586 void RemoteISOSettingsScreen::CreateViews() {
587 	auto ri = GetI18NCategory("RemoteISO");
588 
589 	ViewGroup *remoteisoSettingsScroll = new ScrollView(ORIENT_VERTICAL, new LayoutParams(FILL_PARENT, FILL_PARENT));
590 	remoteisoSettingsScroll->SetTag("RemoteISOSettings");
591 	LinearLayout *remoteisoSettings = new LinearLayoutList(ORIENT_VERTICAL);
592 	remoteisoSettings->SetSpacing(0);
593 	remoteisoSettingsScroll->Add(remoteisoSettings);
594 
595 	remoteisoSettings->Add(new ItemHeader(ri->T("Remote disc streaming")));
596 	remoteisoSettings->Add(new CheckBox(&g_Config.bRemoteShareOnStartup, ri->T("Share on PPSSPP startup")));
597 	remoteisoSettings->Add(new CheckBox(&g_Config.bRemoteISOManual, ri->T("Manual Mode Client", "Manually configure client")));
598 #if !defined(MOBILE_DEVICE)
599 	PopupTextInputChoice *remoteServer = remoteisoSettings->Add(new PopupTextInputChoice(&g_Config.sLastRemoteISOServer, ri->T("Remote Server"), "", 255, screenManager()));
600 #else
601 	ChoiceWithValueDisplay *remoteServer = new ChoiceWithValueDisplay(&g_Config.sLastRemoteISOServer, ri->T("Remote Server"), (const char *)nullptr);
602 	remoteisoSettings->Add(remoteServer);
603 	remoteServer->OnClick.Handle(this, &RemoteISOSettingsScreen::OnClickRemoteServer);
604 #endif
605 	remoteServer->SetEnabledPtr(&g_Config.bRemoteISOManual);
606 	PopupSliderChoice *remotePort = remoteisoSettings->Add(new PopupSliderChoice(&g_Config.iLastRemoteISOPort, 0, 65535, ri->T("Remote Port", "Remote Port"), 100, screenManager()));
607 	remotePort->SetEnabledPtr(&g_Config.bRemoteISOManual);
608 #if !defined(MOBILE_DEVICE)
609 	PopupTextInputChoice *remoteSubdir = remoteisoSettings->Add(new PopupTextInputChoice(&g_Config.sRemoteISOSubdir, ri->T("Remote Subdirectory"), "", 255, screenManager()));
610 	remoteSubdir->OnChange.Handle(this, &RemoteISOSettingsScreen::OnChangeRemoteISOSubdir);
611 #else
612 	ChoiceWithValueDisplay *remoteSubdir = remoteisoSettings->Add(
613 			new ChoiceWithValueDisplay(&g_Config.sRemoteISOSubdir, ri->T("Remote Subdirectory"), (const char *)nullptr));
614 	remoteSubdir->OnClick.Handle(this, &RemoteISOSettingsScreen::OnClickRemoteISOSubdir);
615 #endif
616 	remoteSubdir->SetEnabledPtr(&g_Config.bRemoteISOManual);
617 
618 	PopupSliderChoice *portChoice = new PopupSliderChoice(&g_Config.iRemoteISOPort, 0, 65535, ri->T("Local Server Port", "Local Server Port"), 100, screenManager());
619 	remoteisoSettings->Add(portChoice);
620 	portChoice->SetDisabledPtr(&serverRunning_);
621 	remoteisoSettings->Add(new Spacer(25.0));
622 
623 	root_ = new AnchorLayout(new LayoutParams(FILL_PARENT, FILL_PARENT));
624 	root_->Add(remoteisoSettingsScroll);
625 	AddStandardBack(root_);
626 }
627 
OnClickRemoteServer(UI::EventParams & e)628 UI::EventReturn RemoteISOSettingsScreen::OnClickRemoteServer(UI::EventParams &e) {
629 #if PPSSPP_PLATFORM(WINDOWS) || defined(USING_QT_UI) || defined(__ANDROID__)
630 	auto ri = GetI18NCategory("RemoteISO");
631 	System_InputBoxGetString(ri->T("Remote Server"), g_Config.sLastRemoteISOServer, [](bool result, const std::string &value) {
632 		g_Config.sLastRemoteISOServer = value;
633 	});
634 #endif
635 	return UI::EVENT_DONE;
636 }
637 
OnClickRemoteISOSubdir(UI::EventParams & e)638 UI::EventReturn RemoteISOSettingsScreen::OnClickRemoteISOSubdir(UI::EventParams &e) {
639 #if PPSSPP_PLATFORM(WINDOWS) || defined(USING_QT_UI) || defined(__ANDROID__)
640 	auto ri = GetI18NCategory("RemoteISO");
641 	System_InputBoxGetString(ri->T("Remote Subdirectory"), g_Config.sRemoteISOSubdir, [](bool result, const std::string &value) {
642 		g_Config.sRemoteISOSubdir = value;
643 	});
644 #endif
645 	return UI::EVENT_DONE;
646 }
647 
OnChangeRemoteISOSubdir(UI::EventParams & e)648 UI::EventReturn RemoteISOSettingsScreen::OnChangeRemoteISOSubdir(UI::EventParams &e) {
649 	//Conform to HTTP standards
650 	ReplaceAll(g_Config.sRemoteISOSubdir, " ", "%20");
651 	ReplaceAll(g_Config.sRemoteISOSubdir, "\\", "/");
652 	//Make sure it begins with /
653 	if (g_Config.sRemoteISOSubdir.empty() || g_Config.sRemoteISOSubdir[0] != '/')
654 		g_Config.sRemoteISOSubdir = "/" + g_Config.sRemoteISOSubdir;
655 
656 	return UI::EVENT_DONE;
657 }
658