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 #include <vector>
19 
20 #include "Common/Serialize/Serializer.h"
21 #include "Common/Serialize/SerializeFuncs.h"
22 #include "Common/Serialize/SerializeMap.h"
23 #include "Core/Loaders.h"
24 #include "Core/MemMap.h"
25 #include "Core/System.h"
26 #include "Core/Host.h"
27 #include "Core/CoreTiming.h"
28 #include "Core/Reporting.h"
29 #include "Core/MIPS/MIPS.h"
30 #include "Core/HLE/HLE.h"
31 #include "Core/HLE/FunctionWrappers.h"
32 #include "Core/HLE/sceUmd.h"
33 #include "Core/HLE/sceKernelThread.h"
34 #include "Core/HLE/sceKernelInterrupt.h"
35 #include "Core/HLE/sceKernelMemory.h"
36 #include "Core/HLE/KernelWaitHelpers.h"
37 
38 #include "Core/FileSystems/BlockDevices.h"
39 #include "Core/FileSystems/MetaFileSystem.h"
40 #include "Core/FileSystems/ISOFileSystem.h"
41 #include "Core/FileSystems/VirtualDiscFileSystem.h"
42 
43 static constexpr u64 MICRO_DELAY_ACTIVATE = 4000;
44 // Does not include PSP_UMD_CHANGED.
45 static constexpr uint32_t UMD_STAT_ALLOW_WAIT = PSP_UMD_NOT_PRESENT | PSP_UMD_PRESENT | PSP_UMD_NOT_READY | PSP_UMD_READY | PSP_UMD_READABLE;
46 
47 static bool umdActivated = true;
48 static u32 umdStatus = 0;
49 static u32 umdErrorStat = 0;
50 static int driveCBId = 0;
51 static int umdStatTimeoutEvent = -1;
52 static int umdStatChangeEvent = -1;
53 static int umdInsertChangeEvent = -1;
54 static std::vector<SceUID> umdWaitingThreads;
55 static std::map<SceUID, u64> umdPausedWaits;
56 
57 bool UMDReplacePermit = false;
58 bool UMDInserted = true;
59 
60 struct PspUmdInfo {
61 	u32_le size;
62 	u32_le type;
63 };
64 
65 static void __UmdStatTimeout(u64 userdata, int cyclesLate);
66 static void __UmdStatChange(u64 userdata, int cyclesLate);
67 static void __UmdInsertChange(u64 userdata, int cyclesLate);
68 static void __UmdBeginCallback(SceUID threadID, SceUID prevCallbackId);
69 static void __UmdEndCallback(SceUID threadID, SceUID prevCallbackId);
70 
__UmdInit()71 void __UmdInit()
72 {
73 	umdStatTimeoutEvent = CoreTiming::RegisterEvent("UmdTimeout", __UmdStatTimeout);
74 	umdStatChangeEvent = CoreTiming::RegisterEvent("UmdChange", __UmdStatChange);
75 	umdInsertChangeEvent = CoreTiming::RegisterEvent("UmdInsertChange", __UmdInsertChange);
76 	umdActivated = true;
77 	umdStatus = 0;
78 	umdErrorStat = 0;
79 	driveCBId = 0;
80 	umdWaitingThreads.clear();
81 	umdPausedWaits.clear();
82 
83 	__KernelRegisterWaitTypeFuncs(WAITTYPE_UMD, __UmdBeginCallback, __UmdEndCallback);
84 }
85 
__UmdDoState(PointerWrap & p)86 void __UmdDoState(PointerWrap &p)
87 {
88 	auto s = p.Section("sceUmd", 1, 3);
89 	if (!s)
90 		return;
91 
92 	u8 activatedByte = umdActivated ? 1 : 0;
93 	Do(p, umdActivated);
94 	umdActivated = activatedByte != 0;
95 	Do(p, umdStatus);
96 	Do(p, umdErrorStat);
97 	Do(p, driveCBId);
98 	Do(p, umdStatTimeoutEvent);
99 	CoreTiming::RestoreRegisterEvent(umdStatTimeoutEvent, "UmdTimeout", __UmdStatTimeout);
100 	Do(p, umdStatChangeEvent);
101 	CoreTiming::RestoreRegisterEvent(umdStatChangeEvent, "UmdChange", __UmdStatChange);
102 	Do(p, umdWaitingThreads);
103 	Do(p, umdPausedWaits);
104 
105 	if (s > 1) {
106 		Do(p, UMDReplacePermit);
107 		if (UMDReplacePermit)
108 			host->UpdateUI();
109 	}
110 	if (s > 2) {
111 		Do(p, umdInsertChangeEvent);
112 		Do(p, UMDInserted);
113 	} else {
114 		umdInsertChangeEvent = -1;
115 		UMDInserted = true;
116 	}
117 	CoreTiming::RestoreRegisterEvent(umdInsertChangeEvent, "UmdInsertChange", __UmdInsertChange);
118 }
119 
__KernelUmdGetState()120 static u8 __KernelUmdGetState() {
121 	if (!UMDInserted) {
122 		return PSP_UMD_NOT_PRESENT;
123 	}
124 
125 	// Most games seem to expect the disc to be ready early on, active or not.
126 	// It seems like the PSP sets this state when the disc is "ready".
127 	u8 state = PSP_UMD_PRESENT | PSP_UMD_READY;
128 	if (umdActivated) {
129 		state |= PSP_UMD_READABLE;
130 	}
131 	return state;
132 }
133 
UmdWakeThreads()134 static void UmdWakeThreads() {
135 	// Wake anyone waiting on this.
136 	for (size_t i = 0; i < umdWaitingThreads.size(); ++i) {
137 		const SceUID threadID = umdWaitingThreads[i];
138 
139 		u32 error;
140 		u32 stat = __KernelGetWaitValue(threadID, error);
141 		bool keep = false;
142 		if (HLEKernel::VerifyWait(threadID, WAITTYPE_UMD, 1)) {
143 			// Only if they are still waiting do we keep them in the list.
144 			keep = (stat & __KernelUmdGetState()) == 0;
145 			if (!keep) {
146 				__KernelResumeThreadFromWait(threadID, 0);
147 			}
148 		}
149 
150 		if (!keep) {
151 			umdWaitingThreads.erase(umdWaitingThreads.begin() + i--);
152 		}
153 	}
154 }
155 
__UmdStatChange(u64 userdata,int cyclesLate)156 static void __UmdStatChange(u64 userdata, int cyclesLate) {
157 	umdActivated = userdata != 0;
158 
159 	UmdWakeThreads();
160 }
161 
__UmdInsertChange(u64 userdata,int cyclesLate)162 static void __UmdInsertChange(u64 userdata, int cyclesLate) {
163 	UMDInserted = true;
164 
165 	UmdWakeThreads();
166 }
167 
__KernelUmdActivate()168 static void __KernelUmdActivate()
169 {
170 	u32 notifyArg = PSP_UMD_PRESENT | PSP_UMD_READABLE;
171 	// PSP_UMD_READY will be returned when sceKernelGetCompiledSdkVersion() != 0
172 	if (sceKernelGetCompiledSdkVersion() != 0) {
173 		notifyArg |= PSP_UMD_READY;
174 	}
175 	if (driveCBId != 0)
176 		__KernelNotifyCallback(driveCBId, notifyArg);
177 
178 	// Don't activate immediately, take time to "spin up."
179 	CoreTiming::RemoveAllEvents(umdStatChangeEvent);
180 	CoreTiming::ScheduleEvent(usToCycles(MICRO_DELAY_ACTIVATE), umdStatChangeEvent, 1);
181 }
182 
__KernelUmdDeactivate()183 static void __KernelUmdDeactivate()
184 {
185 	u32 notifyArg = PSP_UMD_PRESENT | PSP_UMD_READY;
186 	if (driveCBId != 0)
187 		__KernelNotifyCallback(driveCBId, notifyArg);
188 
189 	CoreTiming::RemoveAllEvents(umdStatChangeEvent);
190 	__UmdStatChange(0, 0);
191 }
192 
__UmdBeginCallback(SceUID threadID,SceUID prevCallbackId)193 static void __UmdBeginCallback(SceUID threadID, SceUID prevCallbackId)
194 {
195 	SceUID pauseKey = prevCallbackId == 0 ? threadID : prevCallbackId;
196 
197 	if (HLEKernel::VerifyWait(threadID, WAITTYPE_UMD, 1))
198 	{
199 		// This means two callbacks in a row.  PSP crashes if the same callback runs inside itself.
200 		// TODO: Handle this better?
201 		if (umdPausedWaits.find(pauseKey) != umdPausedWaits.end())
202 			return;
203 
204 		_dbg_assert_msg_(umdStatTimeoutEvent != -1, "Must have a umd timer");
205 		s64 cyclesLeft = CoreTiming::UnscheduleEvent(umdStatTimeoutEvent, threadID);
206 		if (cyclesLeft != 0)
207 			umdPausedWaits[pauseKey] = CoreTiming::GetTicks() + cyclesLeft;
208 		else
209 			umdPausedWaits[pauseKey] = 0;
210 
211 		HLEKernel::RemoveWaitingThread(umdWaitingThreads, threadID);
212 
213 		DEBUG_LOG(SCEIO, "sceUmdWaitDriveStatCB: Suspending lock wait for callback");
214 	}
215 	else
216 		WARN_LOG_REPORT(SCEIO, "sceUmdWaitDriveStatCB: beginning callback with bad wait id?");
217 }
218 
__UmdEndCallback(SceUID threadID,SceUID prevCallbackId)219 static void __UmdEndCallback(SceUID threadID, SceUID prevCallbackId)
220 {
221 	SceUID pauseKey = prevCallbackId == 0 ? threadID : prevCallbackId;
222 
223 	u32 error;
224 	u32 stat = __KernelGetWaitValue(threadID, error);
225 	if (umdPausedWaits.find(pauseKey) == umdPausedWaits.end())
226 	{
227 		WARN_LOG_REPORT(SCEIO, "__UmdEndCallback(): UMD paused wait missing");
228 
229 		__KernelResumeThreadFromWait(threadID, 0);
230 		return;
231 	}
232 
233 	u64 waitDeadline = umdPausedWaits[pauseKey];
234 	umdPausedWaits.erase(pauseKey);
235 
236 	// TODO: Don't wake up if __KernelCurHasReadyCallbacks()?
237 
238 	if ((stat & __KernelUmdGetState()) != 0)
239 	{
240 		__KernelResumeThreadFromWait(threadID, 0);
241 		return;
242 	}
243 
244 	s64 cyclesLeft = waitDeadline - CoreTiming::GetTicks();
245 	if (cyclesLeft < 0 && waitDeadline != 0)
246 		__KernelResumeThreadFromWait(threadID, SCE_KERNEL_ERROR_WAIT_TIMEOUT);
247 	else
248 	{
249 		_dbg_assert_msg_(umdStatTimeoutEvent != -1, "Must have a umd timer");
250 		CoreTiming::ScheduleEvent(cyclesLeft, umdStatTimeoutEvent, __KernelGetCurThread());
251 
252 		umdWaitingThreads.push_back(threadID);
253 
254 		DEBUG_LOG(SCEIO, "sceUmdWaitDriveStatCB: Resuming lock wait for callback");
255 	}
256 }
257 
sceUmdCheckMedium()258 static int sceUmdCheckMedium()
259 {
260 	if (UMDInserted) {
261 		DEBUG_LOG(SCEIO, "1=sceUmdCheckMedium()");
262 		return 1; //non-zero: disc in drive
263 	}
264 	DEBUG_LOG(SCEIO, "0=sceUmdCheckMedium()");
265 	return 0;
266 }
267 
sceUmdGetDiscInfo(u32 infoAddr)268 static u32 sceUmdGetDiscInfo(u32 infoAddr)
269 {
270 	DEBUG_LOG(SCEIO, "sceUmdGetDiscInfo(%08x)", infoAddr);
271 
272 	if (Memory::IsValidAddress(infoAddr)) {
273 		auto info = PSPPointer<PspUmdInfo>::Create(infoAddr);
274 		if (info->size != 8)
275 			return PSP_ERROR_UMD_INVALID_PARAM;
276 
277 		info->type = PSP_UMD_TYPE_GAME;
278 		return 0;
279 	} else
280 		return PSP_ERROR_UMD_INVALID_PARAM;
281 }
282 
sceUmdActivate(u32 mode,const char * name)283 static int sceUmdActivate(u32 mode, const char *name) {
284 	if (mode < 1 || mode > 2)
285 		return hleLogWarning(SCEIO, PSP_ERROR_UMD_INVALID_PARAM);
286 
287 	__KernelUmdActivate();
288 
289 	if (mode != 1) {
290 		return hleLogError(SCEIO, 0, "UNTESTED");
291 	}
292 	return hleLogSuccessI(SCEIO, 0);
293 }
294 
sceUmdDeactivate(u32 mode,const char * name)295 static int sceUmdDeactivate(u32 mode, const char *name)
296 {
297 	// Why 18?  No idea.
298 	if (mode > 18)
299 		return PSP_ERROR_UMD_INVALID_PARAM;
300 
301 	__KernelUmdDeactivate();
302 
303 	if (mode == 1) {
304 		DEBUG_LOG(SCEIO, "0=sceUmdDeactivate(%d, %s)", mode, name);
305 	} else {
306 		ERROR_LOG(SCEIO, "UNTESTED 0=sceUmdDeactivate(%d, %s)", mode, name);
307 	}
308 
309 	return 0;
310 }
311 
sceUmdRegisterUMDCallBack(u32 cbId)312 static u32 sceUmdRegisterUMDCallBack(u32 cbId)
313 {
314 	int retVal = 0;
315 
316 	// TODO: If the callback is invalid, return PSP_ERROR_UMD_INVALID_PARAM.
317 	if (!kernelObjects.IsValid(cbId)) {
318 		retVal = PSP_ERROR_UMD_INVALID_PARAM;
319 	} else {
320 		// There's only ever one.
321 		driveCBId = cbId;
322 	}
323 	DEBUG_LOG(SCEIO, "%d=sceUmdRegisterUMDCallback(id=%08x)", retVal, cbId);
324 	return retVal;
325 }
326 
sceUmdUnRegisterUMDCallBack(int cbId)327 static int sceUmdUnRegisterUMDCallBack(int cbId)
328 {
329 	int retVal;
330 
331 	if (cbId != driveCBId) {
332 		retVal = PSP_ERROR_UMD_INVALID_PARAM;
333 	} else {
334 		if (sceKernelGetCompiledSdkVersion() > 0x3000000) {
335 			retVal = 0;
336 		} else {
337 			retVal = cbId;
338 		}
339 		driveCBId = 0;
340 	}
341 	DEBUG_LOG(SCEIO, "%08x=sceUmdUnRegisterUMDCallBack(id=%08x)", retVal, cbId);
342 	return retVal;
343 }
344 
sceUmdGetDriveStat()345 static u32 sceUmdGetDriveStat()
346 {
347 	if (!UMDInserted) {
348 		WARN_LOG(SCEIO, "sceUmdGetDriveStat: UMD is taking out for switch UMD");
349 		return PSP_UMD_NOT_PRESENT;
350 	}
351 	//u32 retVal = PSP_UMD_INITED | PSP_UMD_READY | PSP_UMD_PRESENT;
352 	u32 retVal = __KernelUmdGetState();
353 	// This one can be very spammy.
354 	VERBOSE_LOG(SCEIO,"0x%02x=sceUmdGetDriveStat()", retVal);
355 	return retVal;
356 }
357 
__UmdStatTimeout(u64 userdata,int cyclesLate)358 static void __UmdStatTimeout(u64 userdata, int cyclesLate)
359 {
360 	SceUID threadID = (SceUID)userdata;
361 
362 	u32 error;
363 	SceUID waitID = __KernelGetWaitID(threadID, WAITTYPE_UMD, error);
364 	// Assuming it's still waiting.
365 	if (waitID == 1)
366 		__KernelResumeThreadFromWait(threadID, SCE_KERNEL_ERROR_WAIT_TIMEOUT);
367 
368 	HLEKernel::RemoveWaitingThread(umdWaitingThreads, threadID);
369 }
370 
__UmdWaitStat(u32 timeout)371 static void __UmdWaitStat(u32 timeout)
372 {
373 	// This happens to be how the hardware seems to time things.
374 	if (timeout <= 4)
375 		timeout = 15;
376 	else if (timeout <= 215)
377 		timeout = 250;
378 
379 	CoreTiming::ScheduleEvent(usToCycles((int) timeout), umdStatTimeoutEvent, __KernelGetCurThread());
380 }
381 
382 /**
383 * Wait for a drive to reach a certain state
384 *
385 * @param stat - The drive stat to wait for.
386 * @return < 0 on error
387 *
388 */
sceUmdWaitDriveStat(u32 stat)389 static int sceUmdWaitDriveStat(u32 stat) {
390 	if ((stat & UMD_STAT_ALLOW_WAIT) == 0) {
391 		return hleLogDebug(SCEIO, SCE_KERNEL_ERROR_ERRNO_INVALID_ARGUMENT, "bad status");
392 	}
393 	if (!__KernelIsDispatchEnabled()) {
394 		return hleLogDebug(SCEIO, SCE_KERNEL_ERROR_CAN_NOT_WAIT, "dispatch disabled");
395 	}
396 	if (__IsInInterrupt()) {
397 		return hleLogDebug(SCEIO, SCE_KERNEL_ERROR_ILLEGAL_CONTEXT, "inside interrupt");
398 	}
399 
400 	hleEatCycles(520);
401 	if ((stat & __KernelUmdGetState()) == 0) {
402 		DEBUG_LOG(SCEIO, "sceUmdWaitDriveStat(stat = %08x): waiting", stat);
403 		umdWaitingThreads.push_back(__KernelGetCurThread());
404 		__KernelWaitCurThread(WAITTYPE_UMD, 1, stat, 0, 0, "umd stat waited");
405 		return 0;
406 	}
407 
408 	return hleLogSuccessI(SCEIO, 0);
409 }
410 
sceUmdWaitDriveStatWithTimer(u32 stat,u32 timeout)411 static int sceUmdWaitDriveStatWithTimer(u32 stat, u32 timeout) {
412 	if ((stat & UMD_STAT_ALLOW_WAIT) == 0) {
413 		return hleLogDebug(SCEIO, SCE_KERNEL_ERROR_ERRNO_INVALID_ARGUMENT, "bad status");
414 	}
415 	if (!__KernelIsDispatchEnabled()) {
416 		return hleLogDebug(SCEIO, SCE_KERNEL_ERROR_CAN_NOT_WAIT, "dispatch disabled");
417 	}
418 	if (__IsInInterrupt()) {
419 		return hleLogDebug(SCEIO, SCE_KERNEL_ERROR_ILLEGAL_CONTEXT, "inside interrupt");
420 	}
421 
422 	hleEatCycles(520);
423 	if ((stat & __KernelUmdGetState()) == 0) {
424 		DEBUG_LOG(SCEIO, "sceUmdWaitDriveStatWithTimer(stat = %08x, timeout = %d): waiting", stat, timeout);
425 		__UmdWaitStat(timeout);
426 		umdWaitingThreads.push_back(__KernelGetCurThread());
427 		__KernelWaitCurThread(WAITTYPE_UMD, 1, stat, 0, false, "umd stat waited with timer");
428 		return 0;
429 	} else {
430 		hleReSchedule("umd stat checked");
431 	}
432 
433 	return hleLogSuccessI(SCEIO, 0);
434 }
435 
sceUmdWaitDriveStatCB(u32 stat,u32 timeout)436 static int sceUmdWaitDriveStatCB(u32 stat, u32 timeout) {
437 	if ((stat & UMD_STAT_ALLOW_WAIT) == 0) {
438 		return hleLogDebug(SCEIO, SCE_KERNEL_ERROR_ERRNO_INVALID_ARGUMENT, "bad status");
439 	}
440 	if (!__KernelIsDispatchEnabled()) {
441 		return hleLogDebug(SCEIO, SCE_KERNEL_ERROR_CAN_NOT_WAIT, "dispatch disabled");
442 	}
443 	if (__IsInInterrupt()) {
444 		return hleLogDebug(SCEIO, SCE_KERNEL_ERROR_ILLEGAL_CONTEXT, "inside interrupt");
445 	}
446 
447 	hleEatCycles(520);
448 	hleCheckCurrentCallbacks();
449 	if ((stat & __KernelUmdGetState()) == 0) {
450 		DEBUG_LOG(SCEIO, "sceUmdWaitDriveStatCB(stat = %08x, timeout = %d): waiting", stat, timeout);
451 		if (timeout == 0) {
452 			timeout = 8000;
453 		}
454 
455 		__UmdWaitStat(timeout);
456 		umdWaitingThreads.push_back(__KernelGetCurThread());
457 		__KernelWaitCurThread(WAITTYPE_UMD, 1, stat, 0, true, "umd stat waited");
458 	} else {
459 		hleReSchedule("umd stat waited");
460 	}
461 
462 	return hleLogSuccessI(SCEIO, 0);
463 }
464 
sceUmdCancelWaitDriveStat()465 static u32 sceUmdCancelWaitDriveStat()
466 {
467 	DEBUG_LOG(SCEIO, "0=sceUmdCancelWaitDriveStat()");
468 
469 	for (size_t i = 0; i < umdWaitingThreads.size(); ++i) {
470 		const SceUID threadID = umdWaitingThreads[i];
471 		CoreTiming::UnscheduleEvent(umdStatTimeoutEvent, threadID);
472 		HLEKernel::ResumeFromWait(threadID, WAITTYPE_UMD, 1, (int)SCE_KERNEL_ERROR_WAIT_CANCEL);
473 	}
474 	umdWaitingThreads.clear();
475 
476 	return 0;
477 }
478 
sceUmdGetErrorStat()479 static u32 sceUmdGetErrorStat()
480 {
481 	DEBUG_LOG(SCEIO,"%i=sceUmdGetErrorStat()", umdErrorStat);
482 	return umdErrorStat;
483 }
484 
__UmdReplace(Path filepath)485 void __UmdReplace(Path filepath) {
486 	std::string error = "";
487 	if (!UmdReplace(filepath, error)) {
488 		ERROR_LOG(SCEIO, "UMD Replace failed: %s", error.c_str());
489 		return;
490 	}
491 
492 	UMDInserted = false;
493 	// Wake any threads waiting for the disc to be removed.
494 	UmdWakeThreads();
495 
496 	CoreTiming::ScheduleEvent(usToCycles(200*1000), umdInsertChangeEvent, 0); // Wait sceUmdCheckMedium call
497 	// TODO Is this always correct if UMD was not activated?
498 	u32 notifyArg = PSP_UMD_PRESENT | PSP_UMD_READABLE | PSP_UMD_CHANGED;
499 	if (driveCBId != 0)
500 		__KernelNotifyCallback(driveCBId, notifyArg);
501 }
502 
getUMDReplacePermit()503 bool getUMDReplacePermit() {
504 	return UMDReplacePermit;
505 }
506 
sceUmdReplaceProhibit()507 static u32 sceUmdReplaceProhibit()
508 {
509 	DEBUG_LOG(SCEIO,"sceUmdReplaceProhibit()");
510 	if (UMDReplacePermit) {
511 		UMDReplacePermit = false;
512 		host->NotifySwitchUMDUpdated();
513 	}
514 	return 0;
515 }
516 
sceUmdReplacePermit()517 static u32 sceUmdReplacePermit()
518 {
519 	DEBUG_LOG(SCEIO,"sceUmdReplacePermit()");
520 	if (!UMDReplacePermit) {
521 		UMDReplacePermit = true;
522 		host->NotifySwitchUMDUpdated();
523 	}
524 	return 0;
525 }
526 
527 const HLEFunction sceUmdUser[] =
528 {
529 	{0XC6183D47, &WrapI_UC<sceUmdActivate>,               "sceUmdActivate",               'i', "is"},
530 	{0X6B4A146C, &WrapU_V<sceUmdGetDriveStat>,            "sceUmdGetDriveStat",           'x', ""  },
531 	{0X46EBB729, &WrapI_V<sceUmdCheckMedium>,             "sceUmdCheckMedium",            'i', ""  },
532 	{0XE83742BA, &WrapI_UC<sceUmdDeactivate>,             "sceUmdDeactivate",             'i', "xs"},
533 	{0X8EF08FCE, &WrapI_U<sceUmdWaitDriveStat>,           "sceUmdWaitDriveStat",          'i', "x" },
534 	{0X56202973, &WrapI_UU<sceUmdWaitDriveStatWithTimer>, "sceUmdWaitDriveStatWithTimer", 'i', "xx"},
535 	{0X4A9E5E29, &WrapI_UU<sceUmdWaitDriveStatCB>,        "sceUmdWaitDriveStatCB",        'i', "xx"},
536 	{0X6AF9B50A, &WrapU_V<sceUmdCancelWaitDriveStat>,     "sceUmdCancelWaitDriveStat",    'x', ""  },
537 	{0X20628E6F, &WrapU_V<sceUmdGetErrorStat>,            "sceUmdGetErrorStat",           'x', ""  },
538 	{0X340B7686, &WrapU_U<sceUmdGetDiscInfo>,             "sceUmdGetDiscInfo",            'x', "x" },
539 	{0XAEE7404D, &WrapU_U<sceUmdRegisterUMDCallBack>,     "sceUmdRegisterUMDCallBack",    'x', "x" },
540 	{0XBD2BDE07, &WrapI_I<sceUmdUnRegisterUMDCallBack>,   "sceUmdUnRegisterUMDCallBack",  'i', "i" },
541 	{0X87533940, &WrapU_V<sceUmdReplaceProhibit>,         "sceUmdReplaceProhibit",        'x', ""  },
542 	{0XCBE9F02A, &WrapU_V<sceUmdReplacePermit>,           "sceUmdReplacePermit",          'x', ""  },
543 	{0X14C6C45C, nullptr,                                 "sceUmdUnuseUMDInMsUsbWlan",    '?', ""  },
544 	{0XB103FA38, nullptr,                                 "sceUmdUseUMDInMsUsbWlan",      '?', ""  },
545 };
546 
Register_sceUmdUser()547 void Register_sceUmdUser()
548 {
549 	RegisterModule("sceUmdUser", ARRAY_SIZE(sceUmdUser), sceUmdUser);
550 }
551