diff --git a/Common/ChunkFile.h b/Common/ChunkFile.h index 83270c32ac..00d8593000 100644 --- a/Common/ChunkFile.h +++ b/Common/ChunkFile.h @@ -32,6 +32,7 @@ #include #include #include +#include #include "Common.h" #include "FileUtil.h" @@ -147,6 +148,7 @@ public: void Do(std::list &x, T &default_val) { u32 list_size = (u32)x.size(); + Do(list_size); x.resize(list_size, default_val); typename std::list::iterator itr, end; @@ -154,6 +156,41 @@ public: Do(*itr); } + // Store STL sets. + template + void Do(std::set &x) + { + unsigned int number = (unsigned int)x.size(); + Do(number); + + switch (mode) + { + case MODE_READ: + { + x.clear(); + while (number-- > 0) + { + T it; + Do(it); + x.insert(it); + } + } + break; + case MODE_WRITE: + case MODE_MEASURE: + case MODE_VERIFY: + { + typename std::set::iterator itr = x.begin(); + while (number-- > 0) + Do(*itr++); + } + break; + + default: + ERROR_LOG(COMMON, "Savestate error: invalid mode %d.", mode); + } + } + // Store strings. void Do(std::string &x) { diff --git a/Core/FileSystems/FileSystem.h b/Core/FileSystems/FileSystem.h index ab1c1e48d2..d14854803e 100644 --- a/Core/FileSystems/FileSystem.h +++ b/Core/FileSystems/FileSystem.h @@ -18,6 +18,7 @@ #pragma once #include "../../Globals.h" +#include "../../Common/ChunkFile.h" #include enum FileAccess @@ -56,6 +57,20 @@ struct PSPFileInfo PSPFileInfo() : size(0), access(0), exists(false), type(FILETYPE_NORMAL), isOnSectorSystem(false), startSector(0), numSectors(0) {} + void DoState(PointerWrap &p) + { + p.Do(name); + p.Do(size); + p.Do(access); + p.Do(exists); + p.Do(type); + p.Do(mtime); + p.Do(isOnSectorSystem); + p.Do(startSector); + p.Do(numSectors); + p.DoMarker("PSPFileInfo"); + } + std::string name; s64 size; u32 access; //unix 777 diff --git a/Core/HLE/sceIo.cpp b/Core/HLE/sceIo.cpp index 2523539a0a..801b4bf917 100644 --- a/Core/HLE/sceIo.cpp +++ b/Core/HLE/sceIo.cpp @@ -137,7 +137,18 @@ public: sprintf(ptr, "Seekpos: %08x", (u32)pspFileSystem.GetSeekPos(handle)); } static u32 GetMissingErrorCode() { return SCE_KERNEL_ERROR_BADF; } - int GetIDType() const { return 0; } + int GetIDType() const { return PPSSPP_KERNEL_TMID_File; } + + virtual void DoState(PointerWrap &p) { + p.Do(fullpath); + p.Do(handle); + p.Do(callbackID); + p.Do(callbackArg); + p.Do(asyncResult); + p.Do(pendingAsyncResult); + p.Do(sectorBlockMode); + p.DoMarker("File"); + } std::string fullpath; u32 handle; @@ -836,7 +847,18 @@ public: const char *GetName() {return name.c_str();} const char *GetTypeName() {return "DirListing";} static u32 GetMissingErrorCode() { return SCE_KERNEL_ERROR_BADF; } - int GetIDType() const { return 0; } + int GetIDType() const { return PPSSPP_KERNEL_TMID_DirList; } + + virtual void DoState(PointerWrap &p) { + p.Do(name); + + // TODO: Is this the right way for it to wake up? + size_t count = listing.size(); + for (size_t i = 0; i < count; ++i) { + listing[i].DoState(p); + } + p.DoMarker("DirListing"); + } std::string name; std::vector listing; @@ -920,6 +942,14 @@ u32 sceIoIoctl(u32 id, u32 cmd, u32 indataPtr, u32 inlen, u32 outdataPtr, u32 ou return 0; } +KernelObject *__KernelFileNodeObject() { + return new FileNode; +} + +KernelObject *__KernelDirListingObject() { + return new DirListing; +} + const HLEFunction IoFileMgrForUser[] = { { 0xb29ddf9c, &WrapU_C, "sceIoDopen" }, { 0xe3eb004c, &WrapU_IU, "sceIoDread" }, diff --git a/Core/HLE/sceIo.h b/Core/HLE/sceIo.h index 907fd6046b..8015bd93d9 100644 --- a/Core/HLE/sceIo.h +++ b/Core/HLE/sceIo.h @@ -19,9 +19,12 @@ #include #include "HLE.h" +#include "sceKernel.h" void __IoInit(); void __IoShutdown(); +KernelObject *__KernelFileNodeObject(); +KernelObject *__KernelDirListingObject(); void Register_IoFileMgrForUser(); void Register_StdioForUser(); diff --git a/Core/HLE/sceKernel.cpp b/Core/HLE/sceKernel.cpp index 1883e7b64b..947d6d3a48 100644 --- a/Core/HLE/sceKernel.cpp +++ b/Core/HLE/sceKernel.cpp @@ -320,25 +320,76 @@ void KernelObjectPool::DoState(PointerWrap &p) { int _maxCount = maxCount; p.Do(_maxCount); + + if (_maxCount != maxCount) + ERROR_LOG(HLE, "Unable to load state: different kernel object storage."); + + // Assumption: on load, we've already cleared. p.DoArray(occupied, maxCount); for (int i = 0; i < maxCount; ++i) { if (!occupied[i]) continue; - KernelObject *t = pool[i]; - if (!t) + int type; + if (p.mode == p.MODE_READ) { - // TODO: Pass down error? - ERROR_LOG(HLE, "Unable to save state: inconsistent kernel object pool."); - return; + p.Do(type); + pool[i] = CreateByIDType(type); } - - t->DoState(p); + else + { + type = pool[i]->GetIDType(); + p.Do(type); + } + pool[i]->DoState(p); } p.DoMarker("KernelObjectPool"); } +KernelObject *KernelObjectPool::CreateByIDType(int type) +{ + // Used for save states. This is ugly, but what other way is there? + switch (type) + { + case SCE_KERNEL_TMID_Alarm: + return __KernelAlarmObject(); + case SCE_KERNEL_TMID_EventFlag: + return __KernelEventFlagObject(); + case SCE_KERNEL_TMID_Mbox: + return __KernelMbxObject(); + case SCE_KERNEL_TMID_Fpl: + return __KernelMemoryFPLObject(); + case SCE_KERNEL_TMID_Vpl: + return __KernelMemoryVPLObject(); + case PPSSPP_KERNEL_TMID_PMB: + return __KernelMemoryPMBObject(); + case PPSSPP_KERNEL_TMID_Module: + return __KernelModuleObject(); + case SCE_KERNEL_TMID_Mpipe: + return __KernelMsgPipeObject(); + case SCE_KERNEL_TMID_Mutex: + return __KernelMutexObject(); + case SCE_KERNEL_TMID_LwMutex: + return __KernelLwMutexObject(); + case SCE_KERNEL_TMID_Semaphore: + return __KernelSemaphoreObject(); + case SCE_KERNEL_TMID_Callback: + return __KernelCallbackObject(); + case SCE_KERNEL_TMID_Thread: + return __KernelThreadObject(); + case SCE_KERNEL_TMID_VTimer: + return __KernelVTimerObject(); + case PPSSPP_KERNEL_TMID_File: + return __KernelFileNodeObject(); + case PPSSPP_KERNEL_TMID_DirList: + return __KernelDirListingObject(); + + default: + ERROR_LOG(COMMON, "Unable to load state: could not find object type %d.", type); + } +} + void sceKernelIcacheInvalidateAll() { DEBUG_LOG(CPU, "Icache invalidated - should clear JIT someday"); diff --git a/Core/HLE/sceKernel.h b/Core/HLE/sceKernel.h index 9ae451eb8e..90835442d1 100644 --- a/Core/HLE/sceKernel.h +++ b/Core/HLE/sceKernel.h @@ -224,6 +224,7 @@ enum SCE_KERNEL_ERROR_ERRORMAX = 0x8002044d, }; +// If you add to this, make sure to check KernelObjectPool::CreateByIDType(). enum TMIDPurpose { SCE_KERNEL_TMID_Thread = 1, @@ -243,6 +244,12 @@ enum TMIDPurpose SCE_KERNEL_TMID_DelayThread = 65, SCE_KERNEL_TMID_SuspendThread = 66, SCE_KERNEL_TMID_DormantThread = 67, + + // Not official, but need ids for save states. + PPSSPP_KERNEL_TMID_Module = 0x100001, + PPSSPP_KERNEL_TMID_PMB = 0x100002, + PPSSPP_KERNEL_TMID_File = 0x100003, + PPSSPP_KERNEL_TMID_DirList = 0x100004, }; typedef int SceUID; @@ -329,6 +336,7 @@ public: SceUID Create(KernelObject *obj, int rangeBottom = 16, int rangeTop = 0x7fffffff); void DoState(PointerWrap &p); + static KernelObject *CreateByIDType(int type); template u32 Destroy(SceUID handle) diff --git a/Core/HLE/sceKernelAlarm.cpp b/Core/HLE/sceKernelAlarm.cpp index 3a26a7788b..ea833860ec 100644 --- a/Core/HLE/sceKernelAlarm.cpp +++ b/Core/HLE/sceKernelAlarm.cpp @@ -111,6 +111,12 @@ void __KernelAlarmInit() alarmTimer = CoreTiming::RegisterEvent("Alarm", __KernelTriggerAlarm); } +KernelObject *__KernelAlarmObject() +{ + // Default object to load from state. + return new Alarm; +} + void __KernelScheduleAlarm(Alarm *alarm, u64 ticks) { alarm->alm.schedule = (CoreTiming::GetTicks() + ticks) / (u64) CoreTiming::GetClockFrequencyMHz(); diff --git a/Core/HLE/sceKernelAlarm.h b/Core/HLE/sceKernelAlarm.h index 09dd2e2f82..24cc866e37 100644 --- a/Core/HLE/sceKernelAlarm.h +++ b/Core/HLE/sceKernelAlarm.h @@ -23,3 +23,4 @@ int sceKernelCancelAlarm(SceUID uid); int sceKernelReferAlarmStatus(SceUID uid, u32 infoPtr); void __KernelAlarmInit(); +KernelObject *__KernelAlarmObject(); diff --git a/Core/HLE/sceKernelEventFlag.cpp b/Core/HLE/sceKernelEventFlag.cpp index 5a2c625117..f0e5e16034 100644 --- a/Core/HLE/sceKernelEventFlag.cpp +++ b/Core/HLE/sceKernelEventFlag.cpp @@ -104,6 +104,12 @@ void __KernelEventFlagInit() eventFlagWaitTimer = CoreTiming::RegisterEvent("EventFlagTimeout", &__KernelEventFlagTimeout); } +KernelObject *__KernelEventFlagObject() +{ + // Default object to load from state. + return new EventFlag; +} + bool __KernelEventFlagMatches(u32 *pattern, u32 bits, u8 wait, u32 outAddr) { if ((wait & PSP_EVENT_WAITOR) diff --git a/Core/HLE/sceKernelEventFlag.h b/Core/HLE/sceKernelEventFlag.h index 4243b8d897..2eb1c659ba 100644 --- a/Core/HLE/sceKernelEventFlag.h +++ b/Core/HLE/sceKernelEventFlag.h @@ -28,3 +28,4 @@ u32 sceKernelReferEventFlagStatus(SceUID id, u32 statusPtr); u32 sceKernelCancelEventFlag(SceUID uid, u32 pattern, u32 numWaitThreadsPtr); void __KernelEventFlagInit(); +KernelObject *__KernelEventFlagObject(); diff --git a/Core/HLE/sceKernelMbx.cpp b/Core/HLE/sceKernelMbx.cpp index 080226870e..3ae6ae4f04 100644 --- a/Core/HLE/sceKernelMbx.cpp +++ b/Core/HLE/sceKernelMbx.cpp @@ -170,6 +170,11 @@ void __KernelMbxInit() mbxWaitTimer = CoreTiming::RegisterEvent("MbxTimeout", &__KernelMbxTimeout); } +KernelObject *__KernelMbxObject() +{ + return new Mbx; +} + bool __KernelUnlockMbxForThread(Mbx *m, MbxWaitingThread &th, u32 &error, int result, bool &wokeThreads) { SceUID waitID = __KernelGetWaitID(th.first, WAITTYPE_MBX, error); diff --git a/Core/HLE/sceKernelMbx.h b/Core/HLE/sceKernelMbx.h index 7edf6309e9..b0fb25e952 100644 --- a/Core/HLE/sceKernelMbx.h +++ b/Core/HLE/sceKernelMbx.h @@ -34,3 +34,4 @@ int sceKernelCancelReceiveMbx(SceUID id, u32 numWaitingThreadsAddr); int sceKernelReferMbxStatus(SceUID id, u32 infoAddr); void __KernelMbxInit(); +KernelObject *__KernelMbxObject(); diff --git a/Core/HLE/sceKernelMemory.cpp b/Core/HLE/sceKernelMemory.cpp index 2b06e0108a..807e74a2e1 100644 --- a/Core/HLE/sceKernelMemory.cpp +++ b/Core/HLE/sceKernelMemory.cpp @@ -53,7 +53,7 @@ struct NativeFPL }; //FPL - Fixed Length Dynamic Memory Pool - every item has the same length -struct FPL : KernelObject +struct FPL : public KernelObject { const char *GetName() {return nf.name;} const char *GetTypeName() {return "FPL";} @@ -106,7 +106,7 @@ struct SceKernelVplInfo int numWaitThreads; }; -struct VPL : KernelObject +struct VPL : public KernelObject { const char *GetName() {return nv.name;} const char *GetTypeName() {return "VPL";} @@ -368,13 +368,18 @@ public: sprintf(ptr, "MemPart: %08x - %08x size: %08x", address, address + sz, sz); } static u32 GetMissingErrorCode() { return SCE_KERNEL_ERROR_UNKNOWN_MPPID; } /// ???? - int GetIDType() const { return 0; } + int GetIDType() const { return PPSSPP_KERNEL_TMID_PMB; } PartitionMemoryBlock(BlockAllocator *_alloc, u32 size, bool fromEnd) { alloc = _alloc; - address = alloc->Alloc(size, fromEnd, "PMB"); - alloc->ListBlocks(); + + // 0 is used for save states to wake up. + if (size != 0) + { + address = alloc->Alloc(size, fromEnd, "PMB"); + alloc->ListBlocks(); + } } ~PartitionMemoryBlock() { @@ -620,6 +625,22 @@ void sceKernelSetCompilerVersion(int version) flags_ |= SCE_KERNEL_HASCOMPILERVERSION; } +KernelObject *__KernelMemoryFPLObject() +{ + return new FPL; +} + +KernelObject *__KernelMemoryVPLObject() +{ + return new VPL; +} + +KernelObject *__KernelMemoryPMBObject() +{ + // TODO: We could theoretically handle kernelMemory too, but we don't support that now anyway. + return new PartitionMemoryBlock(&userMemory, 0, true); +} + // VPL = variable length memory pool diff --git a/Core/HLE/sceKernelMemory.h b/Core/HLE/sceKernelMemory.h index eaf2e23f4c..cc5334ae89 100644 --- a/Core/HLE/sceKernelMemory.h +++ b/Core/HLE/sceKernelMemory.h @@ -18,6 +18,7 @@ #pragma once #include "../Util/BlockAllocator.h" +#include "sceKernel.h" //todo: "real" memory block allocator, @@ -30,6 +31,9 @@ extern BlockAllocator kernelMemory; void __KernelMemoryInit(); void __KernelMemoryShutdown(); +KernelObject *__KernelMemoryFPLObject(); +KernelObject *__KernelMemoryVPLObject(); +KernelObject *__KernelMemoryPMBObject(); void sceKernelCreateVpl(); void sceKernelDeleteVpl(); diff --git a/Core/HLE/sceKernelModule.cpp b/Core/HLE/sceKernelModule.cpp index a1b045bc9f..1f7b0c050c 100644 --- a/Core/HLE/sceKernelModule.cpp +++ b/Core/HLE/sceKernelModule.cpp @@ -117,13 +117,25 @@ public: nm.entry_addr); } static u32 GetMissingErrorCode() { return SCE_KERNEL_ERROR_UNKNOWN_MODULE; } - int GetIDType() const { return 0; } + int GetIDType() const { return PPSSPP_KERNEL_TMID_Module; } + + virtual void DoState(PointerWrap &p) + { + p.Do(nm); + p.Do(memoryBlockAddr); + p.DoMarker("Module"); + } NativeModule nm; u32 memoryBlockAddr; }; +KernelObject *__KernelModuleObject() +{ + return new Module; +} + ////////////////////////////////////////////////////////////////////////// // MODULES ////////////////////////////////////////////////////////////////////////// @@ -642,7 +654,7 @@ u32 sceKernelLoadModule(const char *name, u32 flags) class AfterModuleEntryCall : public Action { public: AfterModuleEntryCall() {} - Module *module_; + SceUID moduleID_; u32 retValAddr; virtual void run(); }; diff --git a/Core/HLE/sceKernelModule.h b/Core/HLE/sceKernelModule.h index 2652e1e55f..18e3d5f036 100644 --- a/Core/HLE/sceKernelModule.h +++ b/Core/HLE/sceKernelModule.h @@ -20,6 +20,8 @@ #include "sceKernel.h" #include "HLE.h" +KernelObject *__KernelModuleObject(); + u32 __KernelGetModuleGP(SceUID module); bool __KernelLoadExec(const char *filename, SceKernelLoadExecParam *param, std::string *error_string); diff --git a/Core/HLE/sceKernelMsgPipe.cpp b/Core/HLE/sceKernelMsgPipe.cpp index e98786f366..8d60cccf48 100644 --- a/Core/HLE/sceKernelMsgPipe.cpp +++ b/Core/HLE/sceKernelMsgPipe.cpp @@ -162,6 +162,11 @@ struct MsgPipe : public KernelObject u8 *buffer; }; +KernelObject *__KernelMsgPipeObject() +{ + return new MsgPipe; +} + void sceKernelCreateMsgPipe() { const char *name = Memory::GetCharPointer(PARAM(0)); diff --git a/Core/HLE/sceKernelMsgPipe.h b/Core/HLE/sceKernelMsgPipe.h index 0f262038b8..46f812f4ee 100644 --- a/Core/HLE/sceKernelMsgPipe.h +++ b/Core/HLE/sceKernelMsgPipe.h @@ -26,4 +26,6 @@ void sceKernelReceiveMsgPipe(); void sceKernelReceiveMsgPipeCB(); void sceKernelTryReceiveMsgPipe(); void sceKernelCancelMsgPipe(); -void sceKernelReferMsgPipeStatus(); \ No newline at end of file +void sceKernelReferMsgPipeStatus(); + +KernelObject *__KernelMsgPipeObject(); diff --git a/Core/HLE/sceKernelMutex.cpp b/Core/HLE/sceKernelMutex.cpp index b5a78afdfe..c0778031c7 100644 --- a/Core/HLE/sceKernelMutex.cpp +++ b/Core/HLE/sceKernelMutex.cpp @@ -137,6 +137,16 @@ void __KernelMutexInit() __KernelListenThreadEnd(&__KernelMutexThreadEnd); } +KernelObject *__KernelMutexObject() +{ + return new Mutex; +} + +KernelObject *__KernelLwMutexObject() +{ + return new LwMutex; +} + void __KernelMutexShutdown() { mutexHeldLocks.clear(); diff --git a/Core/HLE/sceKernelMutex.h b/Core/HLE/sceKernelMutex.h index de54371948..5db1f3b4d5 100644 --- a/Core/HLE/sceKernelMutex.h +++ b/Core/HLE/sceKernelMutex.h @@ -38,3 +38,5 @@ void __KernelMutexThreadEnd(SceUID thread); void __KernelMutexInit(); void __KernelMutexShutdown(); +KernelObject *__KernelMutexObject(); +KernelObject *__KernelLwMutexObject(); diff --git a/Core/HLE/sceKernelSemaphore.cpp b/Core/HLE/sceKernelSemaphore.cpp index 54c30ae831..0572a8d70d 100644 --- a/Core/HLE/sceKernelSemaphore.cpp +++ b/Core/HLE/sceKernelSemaphore.cpp @@ -75,6 +75,11 @@ void __KernelSemaInit() semaWaitTimer = CoreTiming::RegisterEvent("SemaphoreTimeout", &__KernelSemaTimeout); } +KernelObject *__KernelSemaphoreObject() +{ + return new Semaphore; +} + // Returns whether the thread should be removed. bool __KernelUnlockSemaForThread(Semaphore *s, SceUID threadID, u32 &error, int result, bool &wokeThreads) { diff --git a/Core/HLE/sceKernelSemaphore.h b/Core/HLE/sceKernelSemaphore.h index 9a8b9f2640..f816f97e41 100644 --- a/Core/HLE/sceKernelSemaphore.h +++ b/Core/HLE/sceKernelSemaphore.h @@ -29,3 +29,4 @@ int sceKernelWaitSemaCB(SceUID semaid, int signal, u32 timeoutPtr); void __KernelSemaTimeout(u64 userdata, int cycleslate); void __KernelSemaInit(); +KernelObject *__KernelSemaphoreObject(); diff --git a/Core/HLE/sceKernelThread.cpp b/Core/HLE/sceKernelThread.cpp index 8d03a17f8a..816c3f4a2c 100644 --- a/Core/HLE/sceKernelThread.cpp +++ b/Core/HLE/sceKernelThread.cpp @@ -113,6 +113,18 @@ public: static u32 GetMissingErrorCode() { return SCE_KERNEL_ERROR_UNKNOWN_CBID; } int GetIDType() const { return SCE_KERNEL_TMID_Callback; } + virtual void DoState(PointerWrap &p) + { + p.Do(nc); + p.Do(savedPC); + p.Do(savedRA); + p.Do(savedV0); + p.Do(savedV1); + p.Do(savedIdRegister); + p.Do(forceDelete); + p.DoMarker("Callback"); + } + NativeCallback nc; u32 savedPC; @@ -297,6 +309,34 @@ public: bool isWaiting() const { return (nt.status & THREADSTATUS_WAIT) != 0; } bool isSuspended() const { return (nt.status & THREADSTATUS_SUSPEND) != 0; } + virtual void DoState(PointerWrap &p) + { + p.Do(nt); + p.Do(waitInfo); + p.Do(sleeping); + p.Do(moduleId); + p.Do(isProcessingCallbacks); + p.Do(currentCallbackId); + p.Do(context); + + u32 numCallbacks = THREAD_CALLBACK_NUM_TYPES; + p.Do(numCallbacks); + if (numCallbacks != THREAD_CALLBACK_NUM_TYPES) + ERROR_LOG(HLE, "Unable to load state: different kernel object storage."); + + for (size_t i = 0; i < THREAD_CALLBACK_NUM_TYPES; ++i) + { + std::set::iterator it, end; + p.Do(registeredCallbacks[i]); + p.Do(readyCallbacks[i]); + } + + p.Do(pendingMipsCalls); + p.Do(stackBlock); + + p.DoMarker("Thread"); + } + NativeThread nt; ThreadWaitInfo waitInfo; @@ -409,6 +449,16 @@ void __KernelThreadingInit() __KernelListenThreadEnd(__KernelCancelWakeup); } +KernelObject *__KernelThreadObject() +{ + return new Thread; +} + +KernelObject *__KernelCallbackObject() +{ + return new Callback; +} + void __KernelListenThreadEnd(ThreadCallback callback) { threadEndListeners.push_back(callback); diff --git a/Core/HLE/sceKernelThread.h b/Core/HLE/sceKernelThread.h index 913511fe65..d11a33c17c 100644 --- a/Core/HLE/sceKernelThread.h +++ b/Core/HLE/sceKernelThread.h @@ -98,6 +98,8 @@ struct ThreadContext void __KernelThreadingInit(); void __KernelThreadingShutdown(); +KernelObject *__KernelThreadObject(); +KernelObject *__KernelCallbackObject(); void __KernelScheduleWakeup(int usFromNow, int threadnumber); SceUID __KernelGetCurThread(); diff --git a/Core/HLE/sceKernelVTimer.cpp b/Core/HLE/sceKernelVTimer.cpp index ad3d383141..35174f34f1 100644 --- a/Core/HLE/sceKernelVTimer.cpp +++ b/Core/HLE/sceKernelVTimer.cpp @@ -51,6 +51,11 @@ struct VTimer : public KernelObject u32 argument; }; +KernelObject *__KernelVTimerObject() +{ + return new VTimer; +} + void sceKernelCreateVTimer() { DEBUG_LOG(HLE,"sceKernelCreateVTimer"); diff --git a/Core/HLE/sceKernelVTimer.h b/Core/HLE/sceKernelVTimer.h index 260341b214..3597dfeb18 100644 --- a/Core/HLE/sceKernelVTimer.h +++ b/Core/HLE/sceKernelVTimer.h @@ -23,3 +23,5 @@ void sceKernelSetVTimerHandler(); // TODO void _sceKernelReturnFromTimerHandler(); + +KernelObject *__KernelVTimerObject();