1 // Copyright (c) 2012- 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 #if defined(_WIN32)
19 #include "Common/CommonWindows.h"
20 #endif
21 #include <TimeUtil.h>
22 #include "Common/Data/Text/I18n.h"
23 #include "Common/Serialize/Serializer.h"
24 #include "Common/Serialize/SerializeFuncs.h"
25 #include "Core/Config.h"
26 #include "Core/MemMapHelpers.h"
27 #include "Core/Util/PPGeDraw.h"
28 #include "Core/HLE/sceKernelMemory.h"
29 #include "Core/HLE/sceCtrl.h"
30 #include "Core/HLE/sceUtility.h"
31 #include "Core/HLE/sceNet.h"
32 #include "Core/HLE/sceNetAdhoc.h"
33 #include "Core/Dialog/PSPNetconfDialog.h"
34 #include "Common/Data/Encoding/Utf8.h"
35 
36 
37 #define NETCONF_CONNECT_APNET 0
38 #define NETCONF_STATUS_APNET 1
39 #define NETCONF_CONNECT_ADHOC 2
40 #define NETCONF_CONNECT_APNET_LAST 3
41 #define NETCONF_CREATE_ADHOC 4
42 #define NETCONF_JOIN_ADHOC 5
43 
44 static const float FONT_SCALE = 0.65f;
45 
46 // Needs testing.
47 const static int NET_INIT_DELAY_US = 200000;
48 const static int NET_SHUTDOWN_DELAY_US = 200000;
49 const static int NET_CONNECT_TIMEOUT = 15000000; // Using 15 secs to match the timeout on Adhoc Server side (SERVER_USER_TIMEOUT)
50 
51 struct ScanInfos {
52 	s32_le sz;
53 	SceNetAdhocctlScanInfoEmu si;
54 } PACK;
55 
56 
PSPNetconfDialog(UtilityDialogType type)57 PSPNetconfDialog::PSPNetconfDialog(UtilityDialogType type) : PSPDialog(type) {
58 }
59 
~PSPNetconfDialog()60 PSPNetconfDialog::~PSPNetconfDialog() {
61 }
62 
Init(u32 paramAddr)63 int PSPNetconfDialog::Init(u32 paramAddr) {
64 	// Already running
65 	if (ReadStatus() != SCE_UTILITY_STATUS_NONE)
66 		return SCE_ERROR_UTILITY_INVALID_STATUS;
67 
68 	requestAddr = paramAddr;
69 	int size = Memory::Read_U32(paramAddr);
70 	memset(&request, 0, sizeof(request));
71 	// Only copy the right size to support different request format
72 	Memory::Memcpy(&request, paramAddr, size);
73 
74 	ChangeStatusInit(NET_INIT_DELAY_US);
75 
76 	// Eat any keys pressed before the dialog inited.
77 	UpdateButtons();
78 	okButtonImg = ImageID("I_CIRCLE");
79 	cancelButtonImg = ImageID("I_CROSS");
80 	okButtonFlag = CTRL_CIRCLE;
81 	cancelButtonFlag = CTRL_CROSS;
82 	if (request.common.buttonSwap == 1)
83 	{
84 		okButtonImg = ImageID("I_CROSS");
85 		cancelButtonImg = ImageID("I_CIRCLE");
86 		okButtonFlag = CTRL_CROSS;
87 		cancelButtonFlag = CTRL_CIRCLE;
88 	}
89 
90 	connResult = -1;
91 	scanInfosAddr = 0;
92 	scanStep = 0;
93 	startTime = (u64)(time_now_d() * 1000000.0);
94 
95 	StartFade(true);
96 	return 0;
97 }
98 
DrawBanner()99 void PSPNetconfDialog::DrawBanner() {
100 
101 	PPGeDrawRect(0, 0, 480, 22, CalcFadedColor(0x65636358));
102 
103 	PPGeStyle textStyle = FadedStyle(PPGeAlign::BOX_VCENTER, 0.6f);
104 	textStyle.hasShadow = false;
105 
106 	// TODO: Draw a hexagon icon
107 	PPGeDrawImage(10, 5, 11.0f, 10.0f, 1, 10, 1, 10, 10, 10, FadedImageStyle());
108 	auto di = GetI18NCategory("Dialog");
109 	PPGeDrawText(di->T("Network Connection"), 31, 10, textStyle);
110 }
111 
DrawIndicator()112 void PSPNetconfDialog::DrawIndicator() {
113 	// TODO: Draw animated circle as processing indicator
114 	PPGeDrawImage(456, 248, 20.0f, 20.0f, 1, 10, 1, 10, 10, 10, FadedImageStyle());
115 }
116 
DisplayMessage(std::string text1,std::string text2a,std::string text2b,std::string text3a,std::string text3b,bool hasYesNo,bool hasOK)117 void PSPNetconfDialog::DisplayMessage(std::string text1, std::string text2a, std::string text2b, std::string text3a, std::string text3b, bool hasYesNo, bool hasOK) {
118 	auto di = GetI18NCategory("Dialog");
119 
120 	PPGeStyle buttonStyle = FadedStyle(PPGeAlign::BOX_CENTER, FONT_SCALE);
121 	PPGeStyle messageStyle = FadedStyle(PPGeAlign::BOX_HCENTER, FONT_SCALE);
122 	PPGeStyle messageStyleRight = FadedStyle(PPGeAlign::BOX_RIGHT, FONT_SCALE);
123 	PPGeStyle messageStyleLeft = FadedStyle(PPGeAlign::BOX_LEFT, FONT_SCALE);
124 
125 	std::string text2 = text2a + "  " + text2b;
126 	std::string text3 = text3a + "  " + text3b;
127 
128 	// Without the scrollbar, we have 350 total pixels.
129 	float WRAP_WIDTH = 300.0f;
130 	if (UTF8StringNonASCIICount(text1.c_str()) >= (int)text1.size() / 4) {
131 		WRAP_WIDTH = 336.0f;
132 		if (text1.size() > 12) {
133 			messageStyle.scale = 0.6f;
134 		}
135 	}
136 
137 	float totalHeight1 = 0.0f;
138 	PPGeMeasureText(nullptr, &totalHeight1, text1.c_str(), FONT_SCALE, PPGE_LINE_WRAP_WORD, WRAP_WIDTH);
139 	float totalHeight2 = 0.0f;
140 	if (text2 != "  ")
141 		PPGeMeasureText(nullptr, &totalHeight2, text2.c_str(), FONT_SCALE, PPGE_LINE_USE_ELLIPSIS, WRAP_WIDTH);
142 	float totalHeight3 = 0.0f;
143 	if (text3 != "  ")
144 		PPGeMeasureText(nullptr, &totalHeight3, text3.c_str(), FONT_SCALE, PPGE_LINE_USE_ELLIPSIS, WRAP_WIDTH);
145 	float marginTop = 0.0f;
146 	if (text2 != "  " || text3 != "  ")
147 		marginTop = 11.0f;
148 	float totalHeight = totalHeight1 + totalHeight2 + totalHeight3 + marginTop;
149 	// The PSP normally only shows about 8 lines at a time.
150 	// For improved UX, we intentionally show part of the next line.
151 	float visibleHeight = std::min(totalHeight, 175.0f);
152 	float h2 = visibleHeight / 2.0f;
153 
154 	float centerY = 135.0f;
155 	float sy = centerY - h2 - 15.0f;
156 	float ey = centerY + h2 + 20.0f;
157 	float buttonY = centerY + h2 + 5.0f;
158 
159 	auto drawSelectionBoxAndAdjust = [&](float x) {
160 		// Box has a fixed size.
161 		float w = 15.0f;
162 		float h = 8.0f;
163 		PPGeDrawRect(x - w, buttonY - h, x + w, buttonY + h, CalcFadedColor(0x6DCFCFCF));
164 
165 		centerY -= h + 5.0f;
166 		sy -= h + 5.0f;
167 		ey = buttonY + h * 2.0f + 5.0f;
168 	};
169 
170 	if (hasYesNo) {
171 		if (yesnoChoice == 1) {
172 			drawSelectionBoxAndAdjust(204.0f);
173 		}
174 		else {
175 			drawSelectionBoxAndAdjust(273.0f);
176 		}
177 
178 		PPGeDrawText(di->T("Yes"), 203.0f, buttonY - 1.0f, buttonStyle);
179 		PPGeDrawText(di->T("No"), 272.0f, buttonY - 1.0f, buttonStyle);
180 		if (IsButtonPressed(CTRL_LEFT) && yesnoChoice == 0) {
181 			yesnoChoice = 1;
182 		}
183 		else if (IsButtonPressed(CTRL_RIGHT) && yesnoChoice == 1) {
184 			yesnoChoice = 0;
185 		}
186 		buttonY += 8.0f + 5.0f;
187 	}
188 
189 	if (hasOK) {
190 		drawSelectionBoxAndAdjust(240.0f);
191 
192 		PPGeDrawText(di->T("OK"), 239.0f, buttonY - 1.0f, buttonStyle);
193 		buttonY += 8.0f + 5.0f;
194 	}
195 
196 	PPGeScissor(0, (int)(centerY - h2 - 2), 480, (int)(centerY + h2 + 2));
197 	PPGeDrawTextWrapped(text1.c_str(), 240.0f, centerY - h2 - scrollPos_, WRAP_WIDTH, 0, messageStyle);
198 	if (text2a != "") {
199 		if (text2b != "")
200 			PPGeDrawTextWrapped(text2a.c_str(), 240.0f - 5.0f, centerY - h2 - scrollPos_ + totalHeight1 + marginTop, WRAP_WIDTH, 0, messageStyleRight);
201 		else
202 			PPGeDrawTextWrapped(text2a.c_str(), 240.0f, centerY - h2 - scrollPos_ + totalHeight1 + marginTop, WRAP_WIDTH, 0, messageStyle);
203 	}
204 	if (text2b != "")
205 		PPGeDrawTextWrapped(text2b.c_str(), 240.0f + 5.0f, centerY - h2 - scrollPos_ + totalHeight1 + marginTop, WRAP_WIDTH, 0, messageStyleLeft);
206 	if (text3a != "") {
207 		if (text3b != "")
208 			PPGeDrawTextWrapped(text3a.c_str(), 240.0f - 5.0f, centerY - h2 - scrollPos_ + totalHeight1 + totalHeight2 + marginTop, WRAP_WIDTH, 0, messageStyleRight);
209 		else
210 			PPGeDrawTextWrapped(text3a.c_str(), 240.0f, centerY - h2 - scrollPos_ + totalHeight1 + totalHeight2 + marginTop, WRAP_WIDTH, 0, messageStyle);
211 	}
212 	if (text3b != "")
213 		PPGeDrawTextWrapped(text3b.c_str(), 240.0f + 5.0f, centerY - h2 - scrollPos_ + totalHeight1 + totalHeight2 + marginTop, WRAP_WIDTH, 0, messageStyleLeft);
214 	PPGeScissorReset();
215 
216 	// Do we need a scrollbar?
217 	if (visibleHeight < totalHeight) {
218 		float scrollSpeed = 5.0f;
219 		float scrollMax = totalHeight - visibleHeight;
220 
221 		float bobHeight = (visibleHeight / totalHeight) * visibleHeight;
222 		float bobOffset = (scrollPos_ / scrollMax) * (visibleHeight - bobHeight);
223 		float bobY1 = centerY - h2 + bobOffset;
224 		PPGeDrawRect(415.0f, bobY1, 420.0f, bobY1 + bobHeight, CalcFadedColor(0xFFCCCCCC));
225 
226 		auto buttonDown = [this](int btn, int& held) {
227 			if (IsButtonPressed(btn)) {
228 				held = 0;
229 				return true;
230 			}
231 			return IsButtonHeld(btn, held, 1, 1);
232 		};
233 		if (buttonDown(CTRL_DOWN, framesDownHeld_) && scrollPos_ < scrollMax) {
234 			scrollPos_ = std::min(scrollMax, scrollPos_ + scrollSpeed);
235 		}
236 		if (buttonDown(CTRL_UP, framesUpHeld_) && scrollPos_ > 0.0f) {
237 			scrollPos_ = std::max(0.0f, scrollPos_ - scrollSpeed);
238 		}
239 	}
240 
241 	PPGeDrawRect(60.0f, sy, 420.0f, sy + 1.0f, CalcFadedColor(0xFFFFFFFF));
242 	PPGeDrawRect(60.0f, ey, 420.0f, ey + 1.0f, CalcFadedColor(0xFFFFFFFF));
243 }
244 
Update(int animSpeed)245 int PSPNetconfDialog::Update(int animSpeed) {
246 	if (ReadStatus() != SCE_UTILITY_STATUS_RUNNING) {
247 		return SCE_ERROR_UTILITY_INVALID_STATUS;
248 	}
249 
250 	UpdateButtons();
251 	auto di = GetI18NCategory("Dialog");
252 	auto err = GetI18NCategory("Error");
253 	u64 now = (u64)(time_now_d() * 1000000.0);
254 
255 	// It seems JPCSP doesn't check for NETCONF_STATUS_APNET
256 	if (request.netAction == NETCONF_CONNECT_APNET || request.netAction == NETCONF_STATUS_APNET || request.netAction == NETCONF_CONNECT_APNET_LAST) {
257 		int state = NetApctl_GetState();
258 
259 		UpdateFade(animSpeed);
260 		StartDraw();
261 
262 		if (!hideNotice) {
263 			const float WRAP_WIDTH = 254.0f;
264 			const ImageID confirmBtnImage = g_Config.iButtonPreference == PSP_SYSTEMPARAM_BUTTON_CROSS ? ImageID("I_CROSS") : ImageID("I_CIRCLE");
265 			const int confirmBtn = g_Config.iButtonPreference == PSP_SYSTEMPARAM_BUTTON_CROSS ? CTRL_CROSS : CTRL_CIRCLE;
266 			const ImageID cancelBtnImage = g_Config.iButtonPreference == PSP_SYSTEMPARAM_BUTTON_CROSS ? ImageID("I_CIRCLE") : ImageID("I_CROSS");
267 			const int cancelBtn = g_Config.iButtonPreference == PSP_SYSTEMPARAM_BUTTON_CROSS ? CTRL_CIRCLE : CTRL_CROSS;
268 
269 			PPGeStyle textStyle = FadedStyle(PPGeAlign::BOX_CENTER, 0.5f);
270 			PPGeStyle buttonStyle = FadedStyle(PPGeAlign::BOX_LEFT, 0.5f);
271 
272 			PPGeDrawRect(0, 0, 480, 272, CalcFadedColor(0x63636363));
273 			DrawBanner();
274 			PPGeDrawTextWrapped(err->T("PPSSPPDoesNotSupportInternet", "PPSSPP currently does not support connecting to the Internet for DLC, PSN, or game updates.\nContinuing may cause unexpected behavior or freezes."), 241, 132, WRAP_WIDTH, 0, textStyle);
275 			PPGeDrawImage(confirmBtnImage, 185, 240, 20, 20, buttonStyle);
276 			PPGeDrawText(di->T("OK"), 215, 243, buttonStyle);
277 			PPGeDrawImage(cancelBtnImage, 255, 240, 20, 20, buttonStyle);
278 			PPGeDrawText(di->T("Cancel"), 285, 243, buttonStyle);
279 
280 			// Since we don't support Infrastructure API yet.. Let the Player read the message first and choose to continue or not (ie. for testing networks API)
281 			if (IsButtonPressed(cancelBtn)) {
282 				StartFade(false);
283 				ChangeStatus(SCE_UTILITY_STATUS_FINISHED, NET_SHUTDOWN_DELAY_US);
284 				// TODO: When the dialog is aborted, does it really set the result to this?
285 				// It seems to make Phantasy Star Portable 2 happy, so it should be okay for now.
286 				request.common.result = SCE_UTILITY_DIALOG_RESULT_ABORT;
287 			}
288 			else if (IsButtonPressed(confirmBtn)) {
289 				hideNotice = true;
290 				StartFade(true);
291 			}
292 		}
293 		else {
294 			PPGeDrawRect(0, 0, 480, 272, CalcFadedColor(0xC0C8B2AC));
295 			DrawBanner();
296 			DrawIndicator();
297 
298 			if (state == PSP_NET_APCTL_STATE_GOT_IP || state == PSP_NET_APCTL_STATE_GETTING_IP) {
299 				DisplayMessage(di->T("ObtainingIP", "Obtaining IP address.\nPlease wait..."), di->T("ConnectionName", "Connection Name"), netApctlInfo.name, di->T("SSID"), netApctlInfo.ssid);
300 			}
301 			else {
302 				// Skipping the Select Connection screen since we only have 1 fake profile
303 				DisplayMessage(di->T("ConnectingAP", "Connecting to the access point.\nPlease wait..."), di->T("ConnectionName", "Connection Name"), netApctlInfo.name, di->T("SSID"), netApctlInfo.ssid);
304 			}
305 			DisplayButtons(DS_BUTTON_CANCEL, di->T("Cancel"));
306 
307 			// The Netconf dialog stays visible until the network reaches the state PSP_NET_APCTL_STATE_GOT_IP.
308 			if (state == PSP_NET_APCTL_STATE_GOT_IP) {
309 				if (pendingStatus != SCE_UTILITY_STATUS_FINISHED) {
310 					StartFade(false);
311 					ChangeStatus(SCE_UTILITY_STATUS_FINISHED, NET_SHUTDOWN_DELAY_US);
312 				}
313 			}
314 
315 			else if (state == PSP_NET_APCTL_STATE_JOINING) {
316 				// Switch to the next message
317 				StartFade(true);
318 			}
319 
320 			else if (state == PSP_NET_APCTL_STATE_DISCONNECTED) {
321 				// When connecting with infrastructure, simulate a connection using the first network configuration entry.
322 				if (connResult < 0) {
323 					connResult = sceNetApctlConnect(1);
324 				}
325 			}
326 		}
327 
328 		EndDraw();
329 	}
330 	else if (request.netAction == NETCONF_CONNECT_ADHOC || request.netAction == NETCONF_CREATE_ADHOC || request.netAction == NETCONF_JOIN_ADHOC) {
331 		int state = NetAdhocctl_GetState();
332 		bool timedout = (state == ADHOCCTL_STATE_DISCONNECTED && now - startTime > NET_CONNECT_TIMEOUT);
333 
334 		UpdateFade(animSpeed);
335 		StartDraw();
336 		PPGeDrawRect(0, 0, 480, 272, CalcFadedColor(0xC0C8B2AC));
337 		DrawBanner();
338 		DrawIndicator();
339 
340 		if (timedout) {
341 			// FIXME: Do we need to show error message?
342 			DisplayMessage(di->T("InternalError", "An internal error has occurred.") + StringFromFormat("\n(%08X)", connResult));
343 			DisplayButtons(DS_BUTTON_CANCEL, di->T("Back"));
344 		}
345 		else {
346 			std::string channel = std::to_string(g_Config.iWlanAdhocChannel);
347 			if (g_Config.iWlanAdhocChannel == PSP_SYSTEMPARAM_ADHOC_CHANNEL_AUTOMATIC)
348 				channel = "Automatic";
349 
350 			DisplayMessage(di->T("ConnectingPleaseWait", "Connecting.\nPlease wait..."), di->T("Channel:") + std::string(" ") + di->T(channel));
351 
352 			// Only Join mode is showing Cancel button on KHBBS and the button will fade out before the dialog is fading out, probably because it's already connected thus can't be canceled anymore
353 			if (request.netAction == NETCONF_JOIN_ADHOC)
354 				DisplayButtons(DS_BUTTON_CANCEL, di->T("Cancel"));
355 
356 			// KHBBS will first enter the arena using NETCONF_CONNECT_ADHOC (auto-create group when not exist yet?), but when the event started the event's creator use NETCONF_CREATE_ADHOC while the joining players use NETCONF_JOIN_ADHOC
357 			if (request.NetconfData.IsValid()) {
358 				if (state == ADHOCCTL_STATE_DISCONNECTED) {
359 					switch (request.netAction)
360 					{
361 					case NETCONF_CREATE_ADHOC:
362 						if (connResult < 0) {
363 							connResult = sceNetAdhocctlCreate(request.NetconfData->groupName);
364 						}
365 						break;
366 					case NETCONF_JOIN_ADHOC:
367 						// FIXME: Should we Scan for a matching group first before Joining a Group (like adhoc games normally do)? Or Is it really allowed to join non-existing group?
368 						if (scanStep == 0) {
369 							if (sceNetAdhocctlScan() >= 0) {
370 								u32 structsz = sizeof(ScanInfos);
371 								if (Memory::IsValidAddress(scanInfosAddr))
372 									userMemory.Free(scanInfosAddr);
373 								scanInfosAddr = userMemory.Alloc(structsz, false, "NetconfScanInfo");
374 								Memory::Write_U32(sizeof(SceNetAdhocctlScanInfoEmu), scanInfosAddr);
375 								scanStep = 1;
376 							}
377 						}
378 						else if (scanStep == 1) {
379 							s32 sz = Memory::Read_U32(scanInfosAddr);
380 							// Get required buffer size
381 							if (sceNetAdhocctlGetScanInfo(scanInfosAddr, 0) >= 0) {
382 								s32 reqsz = Memory::Read_U32(scanInfosAddr);
383 								if (reqsz > sz) {
384 									sz = reqsz;
385 									if (Memory::IsValidAddress(scanInfosAddr))
386 										userMemory.Free(scanInfosAddr);
387 									u32 structsz = sz + sizeof(s32);
388 									scanInfosAddr = userMemory.Alloc(structsz, false, "NetconfScanInfo");
389 									Memory::Write_U32(sz, scanInfosAddr);
390 								}
391 								if (reqsz > 0) {
392 									if (sceNetAdhocctlGetScanInfo(scanInfosAddr, scanInfosAddr + sizeof(s32)) >= 0) {
393 										ScanInfos* scanInfos = (ScanInfos*)Memory::GetPointer(scanInfosAddr);
394 										int n = scanInfos->sz / sizeof(SceNetAdhocctlScanInfoEmu);
395 										// Assuming returned SceNetAdhocctlScanInfoEmu(s) are contagious where next is pointing to current addr + sizeof(SceNetAdhocctlScanInfoEmu)
396 										while (n > 0) {
397 											SceNetAdhocctlScanInfoEmu* si = (SceNetAdhocctlScanInfoEmu*)Memory::GetPointer(scanInfosAddr + sizeof(s32) + sizeof(SceNetAdhocctlScanInfoEmu) * (n - 1LL));
398 											if (memcmp(si->group_name.data, request.NetconfData->groupName, ADHOCCTL_GROUPNAME_LEN) == 0) {
399 												// Moving found group info to the front so we can use it on sceNetAdhocctlJoin easily
400 												memcpy((char*)scanInfos + sizeof(s32), si, sizeof(SceNetAdhocctlScanInfoEmu));
401 												scanStep = 2;
402 												break;
403 											}
404 											n--;
405 										}
406 										// Target group not found, try to scan again later
407 										if (n <= 0) {
408 											scanStep = 0;
409 										}
410 									}
411 								}
412 								// No group found, try to scan again later
413 								else {
414 									scanStep = 0;
415 								}
416 							}
417 						}
418 						else if (scanStep == 2) {
419 							if (connResult < 0) {
420 								connResult = sceNetAdhocctlJoin(scanInfosAddr + sizeof(s32));
421 								if (connResult >= 0) {
422 									// We are done!
423 									if (Memory::IsValidAddress(scanInfosAddr))
424 										userMemory.Free(scanInfosAddr);
425 									scanInfosAddr = 0;
426 								}
427 							}
428 						}
429 						break;
430 					default:
431 						if (connResult < 0) {
432 							connResult = sceNetAdhocctlConnect(request.NetconfData->groupName);
433 						}
434 						break;
435 					}
436 				}
437 			}
438 		}
439 
440 		// The Netconf dialog stays visible until the network reaches the state ADHOCCTL_STATE_CONNECTED.
441 		if (state == ADHOCCTL_STATE_CONNECTED) {
442 			// Checking pendingStatus to make sure ChangeStatus not to continously extending the delay ticks on every call for eternity
443 			if (pendingStatus != SCE_UTILITY_STATUS_FINISHED) {
444 				StartFade(false);
445 				ChangeStatus(SCE_UTILITY_STATUS_FINISHED, NET_SHUTDOWN_DELAY_US);
446 			}
447 
448 			// Let's not leaks any memory
449 			if (Memory::IsValidAddress(scanInfosAddr))
450 				userMemory.Free(scanInfosAddr);
451 			scanInfosAddr = 0;
452 		}
453 
454 		if ((request.netAction == NETCONF_JOIN_ADHOC || timedout) && IsButtonPressed(cancelButtonFlag)) {
455 			StartFade(false);
456 			ChangeStatus(SCE_UTILITY_STATUS_FINISHED, NET_SHUTDOWN_DELAY_US);
457 			request.common.result = SCE_UTILITY_DIALOG_RESULT_ABORT;
458 			// Let's not leaks any memory
459 			if (Memory::IsValidAddress(scanInfosAddr))
460 				userMemory.Free(scanInfosAddr);
461 			scanInfosAddr = 0;
462 		}
463 
464 		EndDraw();
465 	}
466 
467 	if (ReadStatus() == SCE_UTILITY_STATUS_FINISHED || pendingStatus == SCE_UTILITY_STATUS_FINISHED)
468 		Memory::Memcpy(requestAddr, &request, request.common.size, "NetConfDialogParam");
469 
470 	return 0;
471 }
472 
Shutdown(bool force)473 int PSPNetconfDialog::Shutdown(bool force) {
474 	if (ReadStatus() != SCE_UTILITY_STATUS_FINISHED && !force)
475 		return SCE_ERROR_UTILITY_INVALID_STATUS;
476 
477 	PSPDialog::Shutdown(force);
478 	if (!force) {
479 		ChangeStatusShutdown(NET_SHUTDOWN_DELAY_US);
480 	}
481 
482 	return 0;
483 }
484 
DoState(PointerWrap & p)485 void PSPNetconfDialog::DoState(PointerWrap &p) {
486 	PSPDialog::DoState(p);
487 
488 	auto s = p.Section("PSPNetconfigDialog", 0, 2);
489 	if (!s)
490 		return;
491 
492 	Do(p, request);
493 	if (s >= 2) {
494 		Do(p, scanInfosAddr);
495 		Do(p, scanStep);
496 		Do(p, connResult);
497 	}
498 	else {
499 		scanInfosAddr = 0;
500 		scanStep = 0;
501 		connResult = -1;
502 	}
503 
504 	if (p.mode == p.MODE_READ) {
505 		startTime = 0;
506 	}
507 }
508 
GetCommonParam()509 pspUtilityDialogCommon* PSPNetconfDialog::GetCommonParam()
510 {
511 	return &request.common;
512 }
513