// Copyright (c) 2012- PPSSPP Project. // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, version 2.0 or later versions. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License 2.0 for more details. // A copy of the GPL 2.0 should have been included with the program. // If not, see http://www.gnu.org/licenses/ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. #include #include #include "Common/Serialize/Serializer.h" #include "Common/Serialize/SerializeFuncs.h" #include "Core/HLE/HLE.h" #include "Core/HLE/FunctionWrappers.h" #include "Core/CoreTiming.h" #include "Core/MemMap.h" #include "Core/Reporting.h" #include "Core/Config.h" #include "Core/HLE/scePower.h" #include "Core/HLE/sceKernelThread.h" #include "Core/HLE/sceKernelInterrupt.h" struct VolatileWaitingThread { SceUID threadID; u32 addrPtr; u32 sizePtr; }; const int PSP_POWER_ERROR_TAKEN_SLOT = 0x80000020; const int PSP_POWER_ERROR_SLOTS_FULL = 0x80000022; const int PSP_POWER_ERROR_EMPTY_SLOT = 0x80000025; const int PSP_POWER_ERROR_INVALID_CB = 0x80000100; const int PSP_POWER_ERROR_INVALID_SLOT = 0x80000102; const int PSP_POWER_CB_AC_POWER = 0x00001000; const int PSP_POWER_CB_BATTERY_EXIST = 0x00000080; const int PSP_POWER_CB_BATTERY_FULL = 0x00000064; const int POWER_CB_AUTO = -1; // These are the callback slots for user mode applications. const int numberOfCBPowerSlots = 16; // These are the callback slots for kernel mode applications. const int numberOfCBPowerSlotsPrivate = 32; static bool volatileMemLocked; static int powerCbSlots[numberOfCBPowerSlots]; static std::vector volatileWaitingThreads; // Should this belong here, or in CoreTiming? static int RealpllFreq = 222000000; static int RealbusFreq = 111000000; static int pllFreq = 222000000; static int busFreq = 111000000; // The CPU mhz can only be a multiple of the PLL divided by 511. int PowerCpuMhzToHz(int desired, int pllHz) { double maxfreq = desired * 1000000.0; double step = (double)pllHz / 511.0; // These values seem to be locked. if (pllHz >= 333000000 && desired == 333) { return 333000000; } else if (pllHz >= 222000000 && desired == 222) { return 222000000; } double freq = 0; while (freq + step < maxfreq) { freq += step; } // We match the PSP's HLE funcs better when we have the same float error, it seems. return (int)((float)(freq / 1000000.0f) * 1000000); } int PowerPllMhzToHz(int mhz) { // These seem to be the only steps it has. if (mhz <= 190) return 190285721; if (mhz <= 222) return 222000000; if (mhz <= 266) return 266399994; if (mhz <= 333) return 333000000; return mhz * 1000000; } int PowerBusMhzToHz(int mhz) { // These seem to be the only steps it has. if (mhz <= 95) return 95142860; if (mhz <= 111) return 111000000; if (mhz <= 133) return 133199997; if (mhz <= 166) return 166500000; return mhz * 1000000; } void __PowerInit() { memset(powerCbSlots, 0, sizeof(powerCbSlots)); volatileMemLocked = false; volatileWaitingThreads.clear(); if (g_Config.iLockedCPUSpeed > 0) { pllFreq = PowerPllMhzToHz(g_Config.iLockedCPUSpeed); busFreq = PowerBusMhzToHz(pllFreq / 2000000); CoreTiming::SetClockFrequencyHz(PowerCpuMhzToHz(g_Config.iLockedCPUSpeed, pllFreq)); } else { pllFreq = PowerPllMhzToHz(222); busFreq = PowerBusMhzToHz(111); } RealpllFreq = PowerPllMhzToHz(222); RealbusFreq = PowerBusMhzToHz(111); } void __PowerDoState(PointerWrap &p) { auto s = p.Section("scePower",1,2); if (!s) return; if (s >= 2) { Do(p, RealpllFreq); Do(p, RealbusFreq); if (RealpllFreq < 1000000) RealpllFreq = PowerPllMhzToHz(RealpllFreq); if (RealbusFreq < 1000000) RealbusFreq = PowerBusMhzToHz(RealbusFreq); } else { RealpllFreq = PowerPllMhzToHz(222); RealbusFreq = PowerBusMhzToHz(111); } if (g_Config.iLockedCPUSpeed > 0) { pllFreq = PowerPllMhzToHz(g_Config.iLockedCPUSpeed); busFreq = PowerBusMhzToHz(pllFreq / 2000000); CoreTiming::SetClockFrequencyHz(PowerCpuMhzToHz(g_Config.iLockedCPUSpeed, pllFreq)); } else { pllFreq = RealpllFreq; busFreq = RealbusFreq; } DoArray(p, powerCbSlots, ARRAY_SIZE(powerCbSlots)); Do(p, volatileMemLocked); Do(p, volatileWaitingThreads); } static int scePowerGetBatteryLifePercent() { DEBUG_LOG(HLE, "100=scePowerGetBatteryLifePercent"); return 100; } static int scePowerGetBatteryLifeTime() { DEBUG_LOG(HLE, "0=scePowerGetBatteryLifeTime()"); // 0 means we're on AC power. return 0; } static int scePowerGetBatteryTemp() { DEBUG_LOG(HLE, "0=scePowerGetBatteryTemp()"); // 0 means celsius temperature of the battery return 0; } static int scePowerIsPowerOnline() { DEBUG_LOG(HLE, "1=scePowerIsPowerOnline"); return 1; } static int scePowerIsBatteryExist() { DEBUG_LOG(HLE, "1=scePowerIsBatteryExist"); return 1; } static int scePowerIsBatteryCharging() { DEBUG_LOG(HLE, "0=scePowerIsBatteryCharging"); return 0; } static int scePowerGetBatteryChargingStatus() { DEBUG_LOG(HLE, "0=scePowerGetBatteryChargingStatus"); return 0; } static int scePowerIsLowBattery() { DEBUG_LOG(HLE, "0=scePowerIsLowBattery"); return 0; } static int scePowerRegisterCallback(int slot, int cbId) { DEBUG_LOG(HLE, "0=scePowerRegisterCallback(%i, %i)", slot, cbId); if (slot < -1 || slot >= numberOfCBPowerSlotsPrivate) { return PSP_POWER_ERROR_INVALID_SLOT; } if (slot >= numberOfCBPowerSlots) { return SCE_KERNEL_ERROR_PRIV_REQUIRED; } // TODO: If cbId is invalid return PSP_POWER_ERROR_INVALID_CB. if (cbId == 0) { return PSP_POWER_ERROR_INVALID_CB; } int retval = -1; if (slot == POWER_CB_AUTO) { // -1 signifies auto select of bank for (int i=0; i < numberOfCBPowerSlots; i++) { if (powerCbSlots[i] == 0 && retval == -1) { // found an empty slot powerCbSlots[i] = cbId; retval = i; } } if (retval == -1) { return PSP_POWER_ERROR_SLOTS_FULL; } } else { if (powerCbSlots[slot] == 0) { powerCbSlots[slot] = cbId; retval = 0; } else { return PSP_POWER_ERROR_TAKEN_SLOT; } } if (retval >= 0) { int arg = PSP_POWER_CB_AC_POWER | PSP_POWER_CB_BATTERY_EXIST | PSP_POWER_CB_BATTERY_FULL; __KernelNotifyCallback(cbId, arg); } return retval; } static int scePowerUnregisterCallback(int slotId) { DEBUG_LOG(HLE, "0=scePowerUnregisterCallback(%i)", slotId); if (slotId < 0 || slotId >= numberOfCBPowerSlotsPrivate) { return PSP_POWER_ERROR_INVALID_SLOT; } if (slotId >= numberOfCBPowerSlots) { return SCE_KERNEL_ERROR_PRIV_REQUIRED; } if (powerCbSlots[slotId] != 0) { int cbId = powerCbSlots[slotId]; DEBUG_LOG(HLE, "0=scePowerUnregisterCallback(%i) (cbid = %i)", slotId, cbId); powerCbSlots[slotId] = 0; } else { return PSP_POWER_ERROR_EMPTY_SLOT; } return 0; } static int sceKernelPowerLock(int lockType) { DEBUG_LOG(HLE, "0=sceKernelPowerLock(%i)", lockType); if (lockType == 0) { return 0; } else { return SCE_KERNEL_ERROR_INVALID_MODE; } } static int sceKernelPowerUnlock(int lockType) { DEBUG_LOG(HLE, "0=sceKernelPowerUnlock(%i)", lockType); if (lockType == 0) { return 0; } else { return SCE_KERNEL_ERROR_INVALID_MODE; } } static int sceKernelPowerTick(int flag) { DEBUG_LOG(HLE, "UNIMPL 0=sceKernelPowerTick(%i)", flag); return 0; } int KernelVolatileMemLock(int type, u32 paddr, u32 psize) { if (type != 0) { return SCE_KERNEL_ERROR_INVALID_MODE; } if (volatileMemLocked) { return SCE_KERNEL_ERROR_POWER_VMEM_IN_USE; } // Volatile RAM is always at 0x08400000 and is of size 0x00400000. // It's always available in the emu. // TODO: Should really reserve this properly! if (Memory::IsValidAddress(paddr)) { Memory::Write_U32(0x08400000, paddr); } if (Memory::IsValidAddress(psize)) { Memory::Write_U32(0x00400000, psize); } volatileMemLocked = true; return 0; } static int sceKernelVolatileMemTryLock(int type, u32 paddr, u32 psize) { u32 error = KernelVolatileMemLock(type, paddr, psize); switch (error) { case 0: // HACK: This fixes Crash Tag Team Racing. // Should only wait 1200 cycles though according to Unknown's testing, // and with that it's still broken. So it's not this, unfortunately. // Leaving it in for the 0.9.8 release anyway. hleEatCycles(500000); DEBUG_LOG(HLE, "sceKernelVolatileMemTryLock(%i, %08x, %08x) - success", type, paddr, psize); break; case SCE_KERNEL_ERROR_POWER_VMEM_IN_USE: ERROR_LOG(HLE, "sceKernelVolatileMemTryLock(%i, %08x, %08x) - already locked!", type, paddr, psize); break; default: ERROR_LOG_REPORT(HLE, "%08x=sceKernelVolatileMemTryLock(%i, %08x, %08x) - error", type, paddr, psize, error); break; } return error; } int KernelVolatileMemUnlock(int type) { if (type != 0) { return SCE_KERNEL_ERROR_INVALID_MODE; } if (!volatileMemLocked) { // I guess it must use a sema. return SCE_KERNEL_ERROR_SEMA_OVF; } volatileMemLocked = false; // Wake someone, always fifo. bool wokeThreads = false; u32 error; while (!volatileWaitingThreads.empty() && !volatileMemLocked) { VolatileWaitingThread waitInfo = volatileWaitingThreads.front(); volatileWaitingThreads.erase(volatileWaitingThreads.begin()); int waitID = __KernelGetWaitID(waitInfo.threadID, WAITTYPE_VMEM, error); // If they were force-released, just skip. if (waitID == 1 && KernelVolatileMemLock(0, waitInfo.addrPtr, waitInfo.sizePtr) == 0) { __KernelResumeThreadFromWait(waitInfo.threadID, 0); wokeThreads = true; } } if (wokeThreads) { INFO_LOG(HLE, "KernelVolatileMemUnlock(%i) handed over to another thread", type); hleReSchedule("volatile mem unlocked"); } return 0; } static int sceKernelVolatileMemUnlock(int type) { int error = KernelVolatileMemUnlock(type); if (error == SCE_KERNEL_ERROR_INVALID_MODE) { ERROR_LOG_REPORT(HLE, "sceKernelVolatileMemUnlock(%i) - invalid mode", type); return error; } else if (error == SCE_KERNEL_ERROR_SEMA_OVF) { ERROR_LOG_REPORT(HLE, "sceKernelVolatileMemUnlock(%i) FAILED - not locked", type); return error; } return hleLogSuccessI(HLE, 0); } static int sceKernelVolatileMemLock(int type, u32 paddr, u32 psize) { u32 error = 0; // If dispatch is disabled or in an interrupt, don't check, just return an error. // But still write the addr and size (some games require this to work, and it's testably true.) if (!__KernelIsDispatchEnabled()) { error = SCE_KERNEL_ERROR_CAN_NOT_WAIT; } else if (__IsInInterrupt()) { error = SCE_KERNEL_ERROR_ILLEGAL_CONTEXT; } else { error = KernelVolatileMemLock(type, paddr, psize); } switch (error) { case 0: // Should only wait 1200 cycles though according to Unknown's testing, hleEatCycles(1200); DEBUG_LOG(HLE, "sceKernelVolatileMemLock(%i, %08x, %08x) - success", type, paddr, psize); break; case SCE_KERNEL_ERROR_POWER_VMEM_IN_USE: { WARN_LOG(HLE, "sceKernelVolatileMemLock(%i, %08x, %08x) - already locked, waiting", type, paddr, psize); const VolatileWaitingThread waitInfo = { __KernelGetCurThread(), paddr, psize }; volatileWaitingThreads.push_back(waitInfo); __KernelWaitCurThread(WAITTYPE_VMEM, 1, 0, 0, false, "volatile mem waited"); } break; case SCE_KERNEL_ERROR_CAN_NOT_WAIT: { WARN_LOG(HLE, "sceKernelVolatileMemLock(%i, %08x, %08x): dispatch disabled", type, paddr, psize); Memory::Write_U32(0x08400000, paddr); Memory::Write_U32(0x00400000, psize); } break; case SCE_KERNEL_ERROR_ILLEGAL_CONTEXT: { WARN_LOG(HLE, "sceKernelVolatileMemLock(%i, %08x, %08x): in interrupt", type, paddr, psize); Memory::Write_U32(0x08400000, paddr); Memory::Write_U32(0x00400000, psize); } break; default: ERROR_LOG_REPORT(HLE, "%08x=sceKernelVolatileMemLock(%i, %08x, %08x) - error", type, paddr, psize, error); break; } return error; } static u32 scePowerSetClockFrequency(u32 pllfreq, u32 cpufreq, u32 busfreq) { // 190 might (probably) be a typo for 19, but it's what the actual PSP validates against. if (pllfreq < 19 || pllfreq < cpufreq || pllfreq > 333) { return hleLogWarning(SCEMISC, SCE_KERNEL_ERROR_INVALID_VALUE, "invalid pll frequency"); } if (cpufreq == 0 || cpufreq > 333) { return hleLogWarning(SCEMISC, SCE_KERNEL_ERROR_INVALID_VALUE, "invalid cpu frequency"); } if (busfreq == 0 || busfreq > 166) { return hleLogWarning(SCEMISC, SCE_KERNEL_ERROR_INVALID_VALUE, "invalid bus frequency"); } // TODO: More restrictions. if (g_Config.iLockedCPUSpeed > 0) { INFO_LOG(HLE, "scePowerSetClockFrequency(%i,%i,%i): locked by user config at %i, %i, %i", pllfreq, cpufreq, busfreq, g_Config.iLockedCPUSpeed, g_Config.iLockedCPUSpeed, busFreq); } else { INFO_LOG(HLE, "scePowerSetClockFrequency(%i,%i,%i)", pllfreq, cpufreq, busfreq); } // Only reschedules when the stepped PLL frequency changes. // It seems like the busfreq parameter has no effect (but can cause errors.) if (RealpllFreq != PowerPllMhzToHz(pllfreq)) { int oldPll = RealpllFreq / 1000000; RealpllFreq = PowerPllMhzToHz(pllfreq); RealbusFreq = PowerBusMhzToHz(RealpllFreq / 2000000); if (g_Config.iLockedCPUSpeed <= 0) { pllFreq = RealpllFreq; busFreq = RealbusFreq; CoreTiming::SetClockFrequencyHz(PowerCpuMhzToHz(cpufreq, pllFreq)); } // The delay depends on the source and destination frequency, most are 150ms. int newPll = RealpllFreq / 1000000; int usec = 150000; if ((newPll == 190 && oldPll == 222) || (newPll == 222 && oldPll == 190)) usec = 15700; else if ((newPll == 266 && oldPll == 333) || (newPll == 333 && oldPll == 266)) usec = 16600; return hleDelayResult(0, "scepower set clockFrequency", usec); } if (g_Config.iLockedCPUSpeed <= 0) CoreTiming::SetClockFrequencyHz(PowerCpuMhzToHz(cpufreq, pllFreq)); return 0; } static u32 scePowerSetCpuClockFrequency(u32 cpufreq) { if (cpufreq == 0 || cpufreq > 333) { return hleLogWarning(SCEMISC, SCE_KERNEL_ERROR_INVALID_VALUE, "invalid frequency"); } if (g_Config.iLockedCPUSpeed > 0) { return hleLogDebug(SCEMISC, 0, "locked by user config at %i", g_Config.iLockedCPUSpeed); } CoreTiming::SetClockFrequencyHz(PowerCpuMhzToHz(cpufreq, pllFreq)); return hleLogSuccessI(SCEMISC, 0); } static u32 scePowerSetBusClockFrequency(u32 busfreq) { if (busfreq == 0 || busfreq > 111) { return hleLogWarning(SCEMISC, SCE_KERNEL_ERROR_INVALID_VALUE, "invalid frequency"); } if (g_Config.iLockedCPUSpeed > 0) { return hleLogDebug(SCEMISC, 0, "locked by user config at %i", g_Config.iLockedCPUSpeed / 2); } // The value passed is validated, but then doesn't seem to matter for the result. // However, this sets a different hz than scePowerSetClockFrequency would have. if (pllFreq <= 190) busFreq = 94956673; else if (pllFreq <= 222) busFreq = 111000000; else if (pllFreq <= 266) busFreq = 132939331; else if (pllFreq <= 333) busFreq = 165848343; else busFreq = pllFreq / 2; return hleLogSuccessI(SCEMISC, 0); } static u32 scePowerGetCpuClockFrequencyInt() { int cpuFreq = CoreTiming::GetClockFrequencyHz() / 1000000; return hleLogSuccessI(SCEMISC, cpuFreq); } static u32 scePowerGetPllClockFrequencyInt() { return hleLogSuccessInfoI(SCEMISC, pllFreq / 1000000); } static u32 scePowerGetBusClockFrequencyInt() { return hleLogSuccessInfoI(SCEMISC, busFreq / 1000000); } static float scePowerGetCpuClockFrequencyFloat() { float cpuFreq = CoreTiming::GetClockFrequencyHz() / 1000000.0f; DEBUG_LOG(SCEMISC, "%f=scePowerGetCpuClockFrequencyFloat()", (float)cpuFreq); return cpuFreq; } static float scePowerGetPllClockFrequencyFloat() { INFO_LOG(SCEMISC, "%f=scePowerGetPllClockFrequencyFloat()", (float)pllFreq / 1000000.0f); return (float) pllFreq / 1000000.0f; } static float scePowerGetBusClockFrequencyFloat() { INFO_LOG(SCEMISC, "%f=scePowerGetBusClockFrequencyFloat()", (float)busFreq / 1000000.0f); return (float) busFreq / 1000000.0f; } static int scePowerTick() { DEBUG_LOG(SCEMISC, "scePowerTick()"); // Don't think we need to do anything. return 0; } static u32 IsPSPNonFat() { DEBUG_LOG(SCEMISC, "%d=scePower_a85880d0_IsPSPNonFat()", g_Config.iPSPModel); return g_Config.iPSPModel; } static const HLEFunction scePower[] = { {0X04B7766E, &WrapI_II, "scePowerRegisterCallback", 'i', "ii" }, {0X2B51FE2F, nullptr, "scePower_2B51FE2F", '?', "" }, {0X442BFBAC, nullptr, "scePowerGetBacklightMaximum", '?', "" }, {0XEFD3C963, &WrapI_V, "scePowerTick", 'i', "" }, {0XEDC13FE5, nullptr, "scePowerGetIdleTimer", '?', "" }, {0X7F30B3B1, nullptr, "scePowerIdleTimerEnable", '?', "" }, {0X972CE941, nullptr, "scePowerIdleTimerDisable", '?', "" }, {0X27F3292C, nullptr, "scePowerBatteryUpdateInfo", '?', "" }, {0XE8E4E204, nullptr, "scePowerGetForceSuspendCapacity", '?', "" }, {0XB999184C, nullptr, "scePowerGetLowBatteryCapacity", '?', "" }, {0X87440F5E, &WrapI_V, "scePowerIsPowerOnline", 'i', "" }, {0X0AFD0D8B, &WrapI_V, "scePowerIsBatteryExist", 'i', "" }, {0X1E490401, &WrapI_V, "scePowerIsBatteryCharging", 'i', "" }, {0XB4432BC8, &WrapI_V, "scePowerGetBatteryChargingStatus", 'i', "" }, {0XD3075926, &WrapI_V, "scePowerIsLowBattery", 'i', "" }, {0X78A1A796, nullptr, "scePowerIsSuspendRequired", '?', "" }, {0X94F5A53F, nullptr, "scePowerGetBatteryRemainCapacity", '?', "" }, {0XFD18A0FF, nullptr, "scePowerGetBatteryFullCapacity", '?', "" }, {0X2085D15D, &WrapI_V, "scePowerGetBatteryLifePercent", 'i', "" }, {0X8EFB3FA2, &WrapI_V, "scePowerGetBatteryLifeTime", 'i', "" }, {0X28E12023, &WrapI_V, "scePowerGetBatteryTemp", 'i', "" }, {0X862AE1A6, nullptr, "scePowerGetBatteryElec", '?', "" }, {0X483CE86B, nullptr, "scePowerGetBatteryVolt", '?', "" }, {0XCB49F5CE, nullptr, "scePowerGetBatteryChargeCycle", '?', "" }, {0X23436A4A, nullptr, "scePowerGetInnerTemp", '?', "" }, {0X0CD21B1F, nullptr, "scePowerSetPowerSwMode", '?', "" }, {0X165CE085, nullptr, "scePowerGetPowerSwMode", '?', "" }, {0XD6D016EF, nullptr, "scePowerLock", '?', "" }, {0XCA3D34C1, nullptr, "scePowerUnlock", '?', "" }, {0XDB62C9CF, nullptr, "scePowerCancelRequest", '?', "" }, {0X7FA406DD, nullptr, "scePowerIsRequest", '?', "" }, {0X2B7C7CF4, nullptr, "scePowerRequestStandby", '?', "" }, {0XAC32C9CC, nullptr, "scePowerRequestSuspend", '?', "" }, {0X2875994B, nullptr, "scePower_2875994B", '?', "" }, {0X0074EF9B, nullptr, "scePowerGetResumeCount", '?', "" }, {0XDFA8BAF8, &WrapI_I, "scePowerUnregisterCallback", 'i', "i" }, {0XDB9D28DD, &WrapI_I, "scePowerUnregitserCallback", 'i', "i" }, {0X843FBF43, &WrapU_U, "scePowerSetCpuClockFrequency", 'x', "x" }, {0XB8D7B3FB, &WrapU_U, "scePowerSetBusClockFrequency", 'x', "x" }, {0XFEE03A2F, &WrapU_V, "scePowerGetCpuClockFrequency", 'x', "" }, {0X478FE6F5, &WrapU_V, "scePowerGetBusClockFrequency", 'x', "" }, {0XFDB5BFE9, &WrapU_V, "scePowerGetCpuClockFrequencyInt", 'x', "" }, {0XBD681969, &WrapU_V, "scePowerGetBusClockFrequencyInt", 'x', "" }, {0XB1A52C83, &WrapF_V, "scePowerGetCpuClockFrequencyFloat", 'f', "" }, {0X9BADB3EB, &WrapF_V, "scePowerGetBusClockFrequencyFloat", 'f', "" }, {0X737486F2, &WrapU_UUU, "scePowerSetClockFrequency", 'x', "xxx"}, {0X34F9C463, &WrapU_V, "scePowerGetPllClockFrequencyInt", 'x', "" }, {0XEA382A27, &WrapF_V, "scePowerGetPllClockFrequencyFloat", 'f', "" }, {0XEBD177D6, &WrapU_UUU, "scePowerSetClockFrequency350", 'x', "xxx"}, // This is also the same as SetClockFrequency {0X469989AD, &WrapU_UUU, "scePower_469989ad", 'x', "xxx"}, // This is also the same as SetClockFrequency {0X545A7F3C, nullptr, "scePower_545A7F3C", '?', "" }, // TODO: Supposedly the same as SetClockFrequency also? {0XA4E93389, nullptr, "scePower_A4E93389", '?', "" }, // TODO: Supposedly the same as SetClockFrequency also? {0XA85880D0, &WrapU_V, "scePower_a85880d0_IsPSPNonFat", 'x', "" }, {0X3951AF53, nullptr, "scePowerWaitRequestCompletion", '?', "" }, {0X0442D852, nullptr, "scePowerRequestColdReset", '?', "" }, {0XBAFA3DF0, nullptr, "scePowerGetCallbackMode", '?', "" }, {0XA9D22232, nullptr, "scePowerSetCallbackMode", '?', "" }, // These seem to be aliases. {0X23C31FFE, &WrapI_IUU, "scePowerVolatileMemLock", 'i', "ixx"}, {0XFA97A599, &WrapI_IUU, "scePowerVolatileMemTryLock", 'i', "ixx"}, {0XB3EDD801, &WrapI_I, "scePowerVolatileMemUnlock", 'i', "i" }, }; //890129c in tyshooter looks bogus const HLEFunction sceSuspendForUser[] = { {0XEADB1BD7, &WrapI_I, "sceKernelPowerLock", 'i', "i" }, //(int param) set param to 0 {0X3AEE7261, &WrapI_I, "sceKernelPowerUnlock", 'i', "i" }, //(int param) set param to 0 {0X090CCB3F, &WrapI_I, "sceKernelPowerTick", 'i', "i" }, // There's an extra 4MB that can be allocated, which seems to be "volatile". These functions // let you grab it. {0XA14F40B2, &WrapI_IUU, "sceKernelVolatileMemTryLock", 'i', "ixx"}, {0XA569E425, &WrapI_I, "sceKernelVolatileMemUnlock", 'i', "i" }, {0X3E0271D3, &WrapI_IUU, "sceKernelVolatileMemLock", 'i', "ixx"}, }; void Register_scePower() { RegisterModule("scePower",ARRAY_SIZE(scePower),scePower); } void Register_sceSuspendForUser() { RegisterModule("sceSuspendForUser", ARRAY_SIZE(sceSuspendForUser), sceSuspendForUser); }