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 <algorithm>
19 #include "Common/Serialize/Serializer.h"
20 #include "Common/Serialize/SerializeFuncs.h"
21 #include "Common/Serialize/SerializeMap.h"
22 #include "Core/HLE/HLE.h"
23 #include "Core/MIPS/MIPS.h"
24 #include "Core/CoreTiming.h"
25 #include "Core/MemMapHelpers.h"
26 #include "Core/Reporting.h"
27 #include "Core/HLE/sceKernel.h"
28 #include "Core/HLE/sceKernelThread.h"
29 #include "Core/HLE/sceKernelSemaphore.h"
30 #include "Core/HLE/KernelWaitHelpers.h"
31 #include "Core/HLE/FunctionWrappers.h"
32 
33 #define PSP_SEMA_ATTR_FIFO 0
34 #define PSP_SEMA_ATTR_PRIORITY 0x100
35 
36 /** Current state of a semaphore.
37 * @see sceKernelReferSemaStatus.
38 */
39 
40 struct NativeSemaphore
41 {
42 	/** Size of the ::SceKernelSemaInfo structure. */
43 	SceSize_le size;
44 	/** NUL-terminated name of the semaphore. */
45 	char name[KERNELOBJECT_MAX_NAME_LENGTH + 1];
46 	/** Attributes. */
47 	SceUInt_le attr;
48 	/** The initial count the semaphore was created with. */
49 	s32_le initCount;
50 	/** The current count. */
51 	s32_le currentCount;
52 	/** The maximum count. */
53 	s32_le maxCount;
54 	/** The number of threads waiting on the semaphore. */
55 	s32_le numWaitThreads;
56 };
57 
58 
59 struct PSPSemaphore : public KernelObject {
GetNamePSPSemaphore60 	const char *GetName() override { return ns.name; }
GetTypeNamePSPSemaphore61 	const char *GetTypeName() override { return GetStaticTypeName(); }
GetStaticTypeNamePSPSemaphore62 	static const char *GetStaticTypeName() { return "Semaphore"; }
63 
GetMissingErrorCodePSPSemaphore64 	static u32 GetMissingErrorCode() { return SCE_KERNEL_ERROR_UNKNOWN_SEMID; }
GetStaticIDTypePSPSemaphore65 	static int GetStaticIDType() { return SCE_KERNEL_TMID_Semaphore; }
GetIDTypePSPSemaphore66 	int GetIDType() const override { return SCE_KERNEL_TMID_Semaphore; }
67 
DoStatePSPSemaphore68 	void DoState(PointerWrap &p) override
69 	{
70 		auto s = p.Section("Semaphore", 1);
71 		if (!s)
72 			return;
73 
74 		Do(p, ns);
75 		SceUID dv = 0;
76 		Do(p, waitingThreads, dv);
77 		Do(p, pausedWaits);
78 	}
79 
80 	NativeSemaphore ns;
81 	std::vector<SceUID> waitingThreads;
82 	// Key is the callback id it was for, or if no callback, the thread id.
83 	std::map<SceUID, u64> pausedWaits;
84 };
85 
86 static int semaWaitTimer = -1;
87 
88 void __KernelSemaBeginCallback(SceUID threadID, SceUID prevCallbackId);
89 void __KernelSemaEndCallback(SceUID threadID, SceUID prevCallbackId);
90 
__KernelSemaInit()91 void __KernelSemaInit()
92 {
93 	semaWaitTimer = CoreTiming::RegisterEvent("SemaphoreTimeout", __KernelSemaTimeout);
94 	__KernelRegisterWaitTypeFuncs(WAITTYPE_SEMA, __KernelSemaBeginCallback, __KernelSemaEndCallback);
95 }
96 
__KernelSemaDoState(PointerWrap & p)97 void __KernelSemaDoState(PointerWrap &p)
98 {
99 	auto s = p.Section("sceKernelSema", 1);
100 	if (!s)
101 		return;
102 
103 	Do(p, semaWaitTimer);
104 	CoreTiming::RestoreRegisterEvent(semaWaitTimer, "SemaphoreTimeout", __KernelSemaTimeout);
105 }
106 
__KernelSemaphoreObject()107 KernelObject *__KernelSemaphoreObject()
108 {
109 	return new PSPSemaphore;
110 }
111 
112 // Returns whether the thread should be removed.
__KernelUnlockSemaForThread(PSPSemaphore * s,SceUID threadID,u32 & error,int result,bool & wokeThreads)113 static bool __KernelUnlockSemaForThread(PSPSemaphore *s, SceUID threadID, u32 &error, int result, bool &wokeThreads) {
114 	if (!HLEKernel::VerifyWait(threadID, WAITTYPE_SEMA, s->GetUID()))
115 		return true;
116 
117 	// If result is an error code, we're just letting it go.
118 	if (result == 0)
119 	{
120 		int wVal = (int) __KernelGetWaitValue(threadID, error);
121 		if (wVal > s->ns.currentCount)
122 			return false;
123 
124 		s->ns.currentCount -= wVal;
125 	}
126 
127 	u32 timeoutPtr = __KernelGetWaitTimeoutPtr(threadID, error);
128 	if (timeoutPtr != 0 && semaWaitTimer != -1)
129 	{
130 		// Remove any event for this thread.
131 		s64 cyclesLeft = CoreTiming::UnscheduleEvent(semaWaitTimer, threadID);
132 		if (cyclesLeft < 0)
133 			cyclesLeft = 0;
134 		Memory::Write_U32((u32) cyclesToUs(cyclesLeft), timeoutPtr);
135 	}
136 
137 	__KernelResumeThreadFromWait(threadID, result);
138 	wokeThreads = true;
139 	return true;
140 }
141 
__KernelSemaBeginCallback(SceUID threadID,SceUID prevCallbackId)142 void __KernelSemaBeginCallback(SceUID threadID, SceUID prevCallbackId)
143 {
144 	auto result = HLEKernel::WaitBeginCallback<PSPSemaphore, WAITTYPE_SEMA, SceUID>(threadID, prevCallbackId, semaWaitTimer);
145 	if (result == HLEKernel::WAIT_CB_SUCCESS)
146 		DEBUG_LOG(SCEKERNEL, "sceKernelWaitSemaCB: Suspending sema wait for callback");
147 	else
148 		WARN_LOG_REPORT(SCEKERNEL, "sceKernelWaitSemaCB: beginning callback with bad wait id?");
149 }
150 
__KernelSemaEndCallback(SceUID threadID,SceUID prevCallbackId)151 void __KernelSemaEndCallback(SceUID threadID, SceUID prevCallbackId)
152 {
153 	auto result = HLEKernel::WaitEndCallback<PSPSemaphore, WAITTYPE_SEMA, SceUID>(threadID, prevCallbackId, semaWaitTimer, __KernelUnlockSemaForThread);
154 	if (result == HLEKernel::WAIT_CB_RESUMED_WAIT)
155 		DEBUG_LOG(SCEKERNEL, "sceKernelWaitSemaCB: Resuming sema wait for callback");
156 }
157 
158 // Resume all waiting threads (for delete / cancel.)
159 // Returns true if it woke any threads.
__KernelClearSemaThreads(PSPSemaphore * s,int reason)160 static bool __KernelClearSemaThreads(PSPSemaphore *s, int reason) {
161 	u32 error;
162 	bool wokeThreads = false;
163 	std::vector<SceUID>::iterator iter, end;
164 	for (iter = s->waitingThreads.begin(), end = s->waitingThreads.end(); iter != end; ++iter)
165 		__KernelUnlockSemaForThread(s, *iter, error, reason, wokeThreads);
166 	s->waitingThreads.clear();
167 
168 	return wokeThreads;
169 }
170 
sceKernelCancelSema(SceUID id,int newCount,u32 numWaitThreadsPtr)171 int sceKernelCancelSema(SceUID id, int newCount, u32 numWaitThreadsPtr)
172 {
173 	u32 error;
174 	PSPSemaphore *s = kernelObjects.Get<PSPSemaphore>(id, error);
175 	if (s)
176 	{
177 		if (newCount > s->ns.maxCount)
178 		{
179 			DEBUG_LOG(SCEKERNEL, "sceKernelCancelSema(%i, %i, %08x): invalid count", id, newCount, numWaitThreadsPtr);
180 			return SCE_KERNEL_ERROR_ILLEGAL_COUNT;
181 		}
182 
183 		DEBUG_LOG(SCEKERNEL, "sceKernelCancelSema(%i, %i, %08x)", id, newCount, numWaitThreadsPtr);
184 
185 		s->ns.numWaitThreads = (int) s->waitingThreads.size();
186 		if (Memory::IsValidAddress(numWaitThreadsPtr))
187 			Memory::Write_U32(s->ns.numWaitThreads, numWaitThreadsPtr);
188 
189 		if (newCount < 0)
190 			s->ns.currentCount = s->ns.initCount;
191 		else
192 			s->ns.currentCount = newCount;
193 
194 		if (__KernelClearSemaThreads(s, SCE_KERNEL_ERROR_WAIT_CANCEL))
195 			hleReSchedule("semaphore canceled");
196 
197 		return 0;
198 	}
199 	else
200 	{
201 		DEBUG_LOG(SCEKERNEL, "sceKernelCancelSema(%i, %i, %08x): invalid semaphore", id, newCount, numWaitThreadsPtr);
202 		return error;
203 	}
204 }
205 
sceKernelCreateSema(const char * name,u32 attr,int initVal,int maxVal,u32 optionPtr)206 int sceKernelCreateSema(const char* name, u32 attr, int initVal, int maxVal, u32 optionPtr)
207 {
208 	if (!name)
209 	{
210 		WARN_LOG_REPORT(SCEKERNEL, "%08x=sceKernelCreateSema(): invalid name", SCE_KERNEL_ERROR_ERROR);
211 		return SCE_KERNEL_ERROR_ERROR;
212 	}
213 	if (attr >= 0x200)
214 	{
215 		WARN_LOG_REPORT(SCEKERNEL, "%08x=sceKernelCreateSema(): invalid attr parameter: %08x", SCE_KERNEL_ERROR_ILLEGAL_ATTR, attr);
216 		return SCE_KERNEL_ERROR_ILLEGAL_ATTR;
217 	}
218 
219 	PSPSemaphore *s = new PSPSemaphore();
220 	SceUID id = kernelObjects.Create(s);
221 
222 	s->ns.size = sizeof(NativeSemaphore);
223 	strncpy(s->ns.name, name, KERNELOBJECT_MAX_NAME_LENGTH);
224 	s->ns.name[KERNELOBJECT_MAX_NAME_LENGTH] = 0;
225 	s->ns.attr = attr;
226 	s->ns.initCount = initVal;
227 	s->ns.currentCount = s->ns.initCount;
228 	s->ns.maxCount = maxVal;
229 	s->ns.numWaitThreads = 0;
230 
231 	DEBUG_LOG(SCEKERNEL, "%i=sceKernelCreateSema(%s, %08x, %i, %i, %08x)", id, s->ns.name, s->ns.attr, s->ns.initCount, s->ns.maxCount, optionPtr);
232 
233 	if (optionPtr != 0)
234 	{
235 		u32 size = Memory::Read_U32(optionPtr);
236 		if (size > 4)
237 			WARN_LOG_REPORT(SCEKERNEL, "sceKernelCreateSema(%s) unsupported options parameter, size = %d", name, size);
238 	}
239 	if ((attr & ~PSP_SEMA_ATTR_PRIORITY) != 0)
240 		WARN_LOG_REPORT(SCEKERNEL, "sceKernelCreateSema(%s) unsupported attr parameter: %08x", name, attr);
241 
242 	return id;
243 }
244 
sceKernelDeleteSema(SceUID id)245 int sceKernelDeleteSema(SceUID id)
246 {
247 	u32 error;
248 	PSPSemaphore *s = kernelObjects.Get<PSPSemaphore>(id, error);
249 	if (s)
250 	{
251 		DEBUG_LOG(SCEKERNEL, "sceKernelDeleteSema(%i)", id);
252 
253 		bool wokeThreads = __KernelClearSemaThreads(s, SCE_KERNEL_ERROR_WAIT_DELETE);
254 		if (wokeThreads)
255 			hleReSchedule("semaphore deleted");
256 
257 		return kernelObjects.Destroy<PSPSemaphore>(id);
258 	}
259 	else
260 	{
261 		DEBUG_LOG(SCEKERNEL, "sceKernelDeleteSema(%i): invalid semaphore", id);
262 		return error;
263 	}
264 }
265 
sceKernelReferSemaStatus(SceUID id,u32 infoPtr)266 int sceKernelReferSemaStatus(SceUID id, u32 infoPtr)
267 {
268 	u32 error;
269 	PSPSemaphore *s = kernelObjects.Get<PSPSemaphore>(id, error);
270 	if (s)
271 	{
272 		DEBUG_LOG(SCEKERNEL, "sceKernelReferSemaStatus(%i, %08x)", id, infoPtr);
273 
274 		if (!Memory::IsValidAddress(infoPtr))
275 			return -1;
276 
277 		HLEKernel::CleanupWaitingThreads(WAITTYPE_SEMA, id, s->waitingThreads);
278 
279 		s->ns.numWaitThreads = (int) s->waitingThreads.size();
280 		if (Memory::Read_U32(infoPtr) != 0)
281 			Memory::WriteStruct(infoPtr, &s->ns);
282 		return 0;
283 	}
284 	else
285 	{
286 		ERROR_LOG(SCEKERNEL, "sceKernelReferSemaStatus: error %08x", error);
287 		return error;
288 	}
289 }
290 
sceKernelSignalSema(SceUID id,int signal)291 int sceKernelSignalSema(SceUID id, int signal)
292 {
293 	u32 error;
294 	PSPSemaphore *s = kernelObjects.Get<PSPSemaphore>(id, error);
295 	if (s)
296 	{
297 		if (s->ns.currentCount + signal - (int) s->waitingThreads.size() > s->ns.maxCount)
298 		{
299 			VERBOSE_LOG(SCEKERNEL, "sceKernelSignalSema(%i, %i): overflow (at %i)", id, signal, s->ns.currentCount);
300 			return SCE_KERNEL_ERROR_SEMA_OVF;
301 		}
302 
303 		int oldval = s->ns.currentCount;
304 		s->ns.currentCount += signal;
305 		DEBUG_LOG(SCEKERNEL, "sceKernelSignalSema(%i, %i) (count: %i -> %i)", id, signal, oldval, s->ns.currentCount);
306 
307 		if ((s->ns.attr & PSP_SEMA_ATTR_PRIORITY) != 0)
308 			std::stable_sort(s->waitingThreads.begin(), s->waitingThreads.end(), __KernelThreadSortPriority);
309 
310 		bool wokeThreads = false;
311 retry:
312 		for (auto iter = s->waitingThreads.begin(), end = s->waitingThreads.end(); iter != end; ++iter)
313 		{
314 			if (__KernelUnlockSemaForThread(s, *iter, error, 0, wokeThreads))
315 			{
316 				s->waitingThreads.erase(iter);
317 				goto retry;
318 			}
319 		}
320 
321 		if (wokeThreads)
322 			hleReSchedule("semaphore signaled");
323 
324 		hleEatCycles(900);
325 		return 0;
326 	}
327 	else
328 	{
329 		DEBUG_LOG(SCEKERNEL, "sceKernelSignalSema(%i, %i): invalid semaphore", id, signal);
330 		return error;
331 	}
332 }
333 
__KernelSemaTimeout(u64 userdata,int cycleslate)334 void __KernelSemaTimeout(u64 userdata, int cycleslate)
335 {
336 	SceUID threadID = (SceUID)userdata;
337 	u32 error;
338 	SceUID uid = __KernelGetWaitID(threadID, WAITTYPE_SEMA, error);
339 
340 	HLEKernel::WaitExecTimeout<PSPSemaphore, WAITTYPE_SEMA>(threadID);
341 
342 	// If in FIFO mode, that may have cleared another thread to wake up.
343 	PSPSemaphore *s = kernelObjects.Get<PSPSemaphore>(uid, error);
344 	if (s && (s->ns.attr & PSP_SEMA_ATTR_PRIORITY) == PSP_SEMA_ATTR_FIFO) {
345 		bool wokeThreads;
346 		std::vector<SceUID>::iterator iter = s->waitingThreads.begin();
347 		// Unlock every waiting thread until the first that must still wait.
348 		while (iter != s->waitingThreads.end() && __KernelUnlockSemaForThread(s, *iter, error, 0, wokeThreads)) {
349 			s->waitingThreads.erase(iter);
350 			iter = s->waitingThreads.begin();
351 		}
352 	}
353 }
354 
__KernelSetSemaTimeout(PSPSemaphore * s,u32 timeoutPtr)355 static void __KernelSetSemaTimeout(PSPSemaphore *s, u32 timeoutPtr) {
356 	if (timeoutPtr == 0 || semaWaitTimer == -1)
357 		return;
358 
359 	int micro = (int) Memory::Read_U32(timeoutPtr);
360 
361 	// This happens to be how the hardware seems to time things.
362 	if (micro <= 3)
363 		micro = 24;
364 	else if (micro <= 249)
365 		micro = 245;
366 
367 	// This should call __KernelSemaTimeout() later, unless we cancel it.
368 	CoreTiming::ScheduleEvent(usToCycles(micro), semaWaitTimer, __KernelGetCurThread());
369 }
370 
__KernelWaitSema(SceUID id,int wantedCount,u32 timeoutPtr,bool processCallbacks)371 static int __KernelWaitSema(SceUID id, int wantedCount, u32 timeoutPtr, bool processCallbacks)
372 {
373 	hleEatCycles(900);
374 
375 	if (wantedCount <= 0)
376 		return SCE_KERNEL_ERROR_ILLEGAL_COUNT;
377 
378 	hleEatCycles(500);
379 
380 	u32 error;
381 	PSPSemaphore *s = kernelObjects.Get<PSPSemaphore>(id, error);
382 	if (s)
383 	{
384 		if (wantedCount > s->ns.maxCount)
385 			return SCE_KERNEL_ERROR_ILLEGAL_COUNT;
386 
387 		// If there are any callbacks, we always wait, and wake after the callbacks.
388 		bool hasCallbacks = processCallbacks && __KernelCurHasReadyCallbacks();
389 		if (s->ns.currentCount >= wantedCount && s->waitingThreads.size() == 0 && !hasCallbacks)
390 			s->ns.currentCount -= wantedCount;
391 		else
392 		{
393 			SceUID threadID = __KernelGetCurThread();
394 			// May be in a tight loop timing out (where we don't remove from waitingThreads yet), don't want to add duplicates.
395 			if (std::find(s->waitingThreads.begin(), s->waitingThreads.end(), threadID) == s->waitingThreads.end())
396 				s->waitingThreads.push_back(threadID);
397 			__KernelSetSemaTimeout(s, timeoutPtr);
398 			__KernelWaitCurThread(WAITTYPE_SEMA, id, wantedCount, timeoutPtr, processCallbacks, "sema waited");
399 		}
400 
401 		return 0;
402 	}
403 	else
404 		return error;
405 }
406 
sceKernelWaitSema(SceUID id,int wantedCount,u32 timeoutPtr)407 int sceKernelWaitSema(SceUID id, int wantedCount, u32 timeoutPtr)
408 {
409 	int result = __KernelWaitSema(id, wantedCount, timeoutPtr, false);
410 	if (result == (int)SCE_KERNEL_ERROR_ILLEGAL_COUNT)
411 		DEBUG_LOG(SCEKERNEL, "SCE_KERNEL_ERROR_ILLEGAL_COUNT=sceKernelWaitSema(%i, %i, %i)", id, wantedCount, timeoutPtr);
412 	else if (result == 0)
413 		DEBUG_LOG(SCEKERNEL, "0=sceKernelWaitSema(%i, %i, %i)", id, wantedCount, timeoutPtr);
414 	else
415 		DEBUG_LOG(SCEKERNEL, "%08x=sceKernelWaitSema(%i, %i, %i)", result, id, wantedCount, timeoutPtr);
416 	return result;
417 }
418 
sceKernelWaitSemaCB(SceUID id,int wantedCount,u32 timeoutPtr)419 int sceKernelWaitSemaCB(SceUID id, int wantedCount, u32 timeoutPtr)
420 {
421 	int result = __KernelWaitSema(id, wantedCount, timeoutPtr, true);
422 	if (result == (int)SCE_KERNEL_ERROR_ILLEGAL_COUNT)
423 		DEBUG_LOG(SCEKERNEL, "SCE_KERNEL_ERROR_ILLEGAL_COUNT=sceKernelWaitSemaCB(%i, %i, %i)", id, wantedCount, timeoutPtr);
424 	else if (result == 0)
425 		DEBUG_LOG(SCEKERNEL, "0=sceKernelWaitSemaCB(%i, %i, %i)", id, wantedCount, timeoutPtr);
426 	else
427 		DEBUG_LOG(SCEKERNEL, "%08x=sceKernelWaitSemaCB(%i, %i, %i)", result, id, wantedCount, timeoutPtr);
428 	return result;
429 }
430 
431 // Should be same as WaitSema but without the wait, instead returning SCE_KERNEL_ERROR_SEMA_ZERO
sceKernelPollSema(SceUID id,int wantedCount)432 int sceKernelPollSema(SceUID id, int wantedCount)
433 {
434 	if (wantedCount <= 0)
435 	{
436 		DEBUG_LOG(SCEKERNEL, "SCE_KERNEL_ERROR_ILLEGAL_COUNT=sceKernelPollSema(%i, %i)", id, wantedCount);
437 		return (int)SCE_KERNEL_ERROR_ILLEGAL_COUNT;
438 	}
439 
440 	u32 error;
441 	PSPSemaphore *s = kernelObjects.Get<PSPSemaphore>(id, error);
442 	if (s)
443 	{
444 		if (s->ns.currentCount >= wantedCount && s->waitingThreads.size() == 0)
445 		{
446 			DEBUG_LOG(SCEKERNEL, "0=sceKernelPollSema(%i, %i)", id, wantedCount);
447 			s->ns.currentCount -= wantedCount;
448 			return 0;
449 		}
450 		else
451 		{
452 			DEBUG_LOG(SCEKERNEL, "SCE_KERNEL_ERROR_SEMA_ZERO=sceKernelPollSema(%i, %i)", id, wantedCount);
453 			return SCE_KERNEL_ERROR_SEMA_ZERO;
454 		}
455 	}
456 	else
457 	{
458 		DEBUG_LOG(SCEKERNEL, "sceKernelPollSema(%i, %i): invalid semaphore", id, wantedCount);
459 		return error;
460 	}
461 }
462 
463 // The below functions don't really belong to sceKernelSemaphore. They are the core crypto functionality,
464 // exposed through the confusingly named "sceUtilsBufferCopyWithRange" name, which Sony placed in the
465 // not-at-all-suspicious "semaphore" library, which has nothing to do with semaphores.
466 
sceUtilsBufferCopyWithRange(u32 outAddr,int outSize,u32 inAddr,int inSize,int cmd)467 static u32 sceUtilsBufferCopyWithRange(u32 outAddr, int outSize, u32 inAddr, int inSize, int cmd)
468 {
469 	u8 *outAddress = Memory::IsValidRange(outAddr, outSize) ? Memory::GetPointer(outAddr) : nullptr;
470 	const u8 *inAddress = Memory::IsValidRange(inAddr, inSize) ? Memory::GetPointer(inAddr) : nullptr;
471 	int temp = kirk_sceUtilsBufferCopyWithRange(outAddress, outSize, inAddress, inSize, cmd);
472 	if (temp != 0) {
473 		ERROR_LOG(SCEKERNEL, "hleUtilsBufferCopyWithRange: Failed with %d", temp);
474 	}
475 	return 0;
476 }
477 
478 // Note sure what difference there is between this and sceUtilsBufferCopyWithRange.
sceUtilsBufferCopyByPollingWithRange(u32 outAddr,int outSize,u32 inAddr,int inSize,int cmd)479 static int sceUtilsBufferCopyByPollingWithRange(u32 outAddr, int outSize, u32 inAddr, int inSize, int cmd)
480 {
481 	u8 *outAddress = Memory::IsValidRange(outAddr, outSize) ? Memory::GetPointer(outAddr) : nullptr;
482 	const u8 *inAddress = Memory::IsValidRange(inAddr, inSize) ? Memory::GetPointer(inAddr) : nullptr;
483 	return kirk_sceUtilsBufferCopyWithRange(outAddress, outSize, inAddress, inSize, cmd);
484 }
485 
486 const HLEFunction semaphore[] = {
487 	{0x4C537C72, &WrapU_UIUII<sceUtilsBufferCopyWithRange>,          "sceUtilsBufferCopyWithRange",                   'x', "xixii" },
488 	{0x77E97079, &WrapI_UIUII<sceUtilsBufferCopyByPollingWithRange>, "sceUtilsBufferCopyByPollingWithRange",          'i', "xixii"  },
489 };
490 
Register_semaphore()491 void Register_semaphore() {
492 	RegisterModule("semaphore", ARRAY_SIZE(semaphore), semaphore);
493 }
494