Implement proc_ui.rpl + stub SYSSwitchToEManual() to avoid softlocks

- Full reimplementation of proc_ui.rpl with all 19 exports
- Foreground/Background messages now go to the coreinit system message queue as they should (instead of using a hack where proc_ui receives them directly)
- Add missing coreinit API needed by proc_ui: OSGetPFID(), OSGetUPID(), OSGetTitleID(), __OSCreateThreadType()
- Use big-endian types in OSMessage
- Flesh out the stubs for OSDriver_Register and OSDriver_Unregister a bit more since we need to call it from proc_ui. Similiar small tweaks to other coreinit API
- Stub sysapp SYSSwitchToEManual() and _SYSSwitchToEManual() in such a way that they will trigger the expected background/foreground transition, avoiding softlocks in games that call these functions
This commit is contained in:
Exzap 2024-04-30 23:09:00 +02:00
parent c038e758ae
commit 1c73dc9e1b
21 changed files with 1146 additions and 95 deletions

1
.gitignore vendored
View File

@ -39,6 +39,7 @@ bin/sdcard/*
bin/screenshots/*
bin/dump/*
bin/cafeLibs/*
bin/portable/*
bin/keys.txt
!bin/shaderCache/info.txt

View File

@ -221,5 +221,5 @@ void osLib_load()
nsyskbd::nsyskbd_load();
swkbd::load();
camera::load();
procui_load();
proc_ui::load();
}

View File

@ -179,27 +179,6 @@ void coreinitExport_OSGetSharedData(PPCInterpreter_t* hCPU)
osLib_returnFromFunction(hCPU, 1);
}
typedef struct
{
MPTR getDriverName;
MPTR ukn04;
MPTR onAcquiredForeground;
MPTR onReleaseForeground;
MPTR ukn10;
}OSDriverCallbacks_t;
void coreinitExport_OSDriver_Register(PPCInterpreter_t* hCPU)
{
#ifdef CEMU_DEBUG_ASSERT
cemuLog_log(LogType::Force, "OSDriver_Register(0x{:08x},0x{:08x},0x{:08x},0x{:08x},0x{:08x},0x{:08x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7], hCPU->gpr[8]);
#endif
OSDriverCallbacks_t* driverCallbacks = (OSDriverCallbacks_t*)memory_getPointerFromVirtualOffset(hCPU->gpr[5]);
// todo
osLib_returnFromFunction(hCPU, 0);
}
namespace coreinit
{
sint32 OSGetCoreId()
@ -379,7 +358,6 @@ void coreinit_load()
coreinit::miscInit();
osLib_addFunction("coreinit", "OSGetSharedData", coreinitExport_OSGetSharedData);
osLib_addFunction("coreinit", "UCReadSysConfig", coreinitExport_UCReadSysConfig);
osLib_addFunction("coreinit", "OSDriver_Register", coreinitExport_OSDriver_Register);
// async callbacks
InitializeAsyncCallback();

View File

@ -2,6 +2,12 @@
namespace coreinit
{
enum class RplEntryReason
{
Loaded = 1,
Unloaded = 2,
};
uint32 OSDynLoad_SetAllocator(MPTR allocFunc, MPTR freeFunc);
void OSDynLoad_SetTLSAllocator(MPTR allocFunc, MPTR freeFunc);
uint32 OSDynLoad_GetAllocator(betype<MPTR>* funcAlloc, betype<MPTR>* funcFree);

View File

@ -837,7 +837,7 @@ namespace coreinit
FSAsyncResult* FSGetAsyncResult(OSMessage* msg)
{
return (FSAsyncResult*)memory_getPointerFromVirtualOffset(_swapEndianU32(msg->message));
return (FSAsyncResult*)memory_getPointerFromVirtualOffset(msg->message);
}
sint32 __FSProcessAsyncResult(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, sint32 fsStatus, uint32 errHandling)

View File

@ -126,7 +126,7 @@ namespace coreinit
return physicalAddr;
}
void OSMemoryBarrier(PPCInterpreter_t* hCPU)
void OSMemoryBarrier()
{
// no-op
}

View File

@ -5,4 +5,9 @@ namespace coreinit
void InitializeMemory();
void OSGetMemBound(sint32 memType, MPTR* offsetOutput, uint32* sizeOutput);
void* OSBlockMove(MEMPTR<void> dst, MEMPTR<void> src, uint32 size, bool flushDC);
void* OSBlockSet(MEMPTR<void> dst, uint32 value, uint32 size);
void OSMemoryBarrier();
}

View File

@ -3,6 +3,8 @@
namespace coreinit
{
void UpdateSystemMessageQueue();
void HandleReceivedSystemMessage(OSMessage* msg);
SysAllocator<OSMessageQueue> g_systemMessageQueue;
SysAllocator<OSMessage, 16> _systemMessageQueueArray;
@ -27,6 +29,9 @@ namespace coreinit
bool OSReceiveMessage(OSMessageQueue* msgQueue, OSMessage* msg, uint32 flags)
{
bool isSystemMessageQueue = (msgQueue == g_systemMessageQueue);
if(isSystemMessageQueue)
UpdateSystemMessageQueue();
__OSLockScheduler(msgQueue);
while (msgQueue->usedCount == (uint32be)0)
{
@ -50,6 +55,8 @@ namespace coreinit
if (!msgQueue->threadQueueSend.isEmpty())
msgQueue->threadQueueSend.wakeupSingleThreadWaitQueue(true);
__OSUnlockScheduler(msgQueue);
if(isSystemMessageQueue)
HandleReceivedSystemMessage(msg);
return true;
}

View File

@ -3,12 +3,21 @@
namespace coreinit
{
enum class SysMessageId : uint32
{
MsgAcquireForeground = 0xFACEF000,
MsgReleaseForeground = 0xFACEBACC,
MsgExit = 0xD1E0D1E0,
HomeButtonDenied = 0xCCC0FFEE,
NetIoStartOrStop = 0xAAC0FFEE,
};
struct OSMessage
{
MPTR message;
uint32 data0;
uint32 data1;
uint32 data2;
uint32be message;
uint32be data0;
uint32be data1;
uint32be data2;
};
struct OSMessageQueue
@ -36,5 +45,7 @@ namespace coreinit
bool OSPeekMessage(OSMessageQueue* msgQueue, OSMessage* msg);
sint32 OSSendMessage(OSMessageQueue* msgQueue, OSMessage* msg, uint32 flags);
OSMessageQueue* OSGetSystemMessageQueue();
void InitializeMessageQueue();
};

View File

@ -1,5 +1,6 @@
#include "Cafe/OS/common/OSCommon.h"
#include "Cafe/OS/libs/coreinit/coreinit_Misc.h"
#include "Cafe/OS/libs/coreinit/coreinit_MessageQueue.h"
#include "Cafe/CafeSystem.h"
#include "Cafe/Filesystem/fsc.h"
#include <pugixml.hpp>
@ -371,6 +372,23 @@ namespace coreinit
return true;
}
uint32 OSGetPFID()
{
return 15; // hardcoded as game
}
uint32 OSGetUPID()
{
return OSGetPFID();
}
uint64 s_currentTitleId;
uint64 OSGetTitleID()
{
return s_currentTitleId;
}
uint32 s_sdkVersion;
uint32 __OSGetProcessSDKVersion()
@ -470,9 +488,78 @@ namespace coreinit
return 0;
}
void OSReleaseForeground()
{
cemuLog_logDebug(LogType::Force, "OSReleaseForeground not implemented");
}
bool s_transitionToBackground = false;
bool s_transitionToForeground = false;
void StartBackgroundForegroundTransition()
{
s_transitionToBackground = true;
s_transitionToForeground = true;
}
// called at the beginning of OSReceiveMessage if the queue is the system message queue
void UpdateSystemMessageQueue()
{
if(!OSIsInterruptEnabled())
return;
cemu_assert_debug(!__OSHasSchedulerLock());
// normally syscall 0x2E is used to get the next message
// for now we just have some preliminary logic here to allow a fake transition to background & foreground
if(s_transitionToBackground)
{
// add transition to background message
OSMessage msg{};
msg.data0 = stdx::to_underlying(SysMessageId::MsgReleaseForeground);
msg.data1 = 0; // 1 -> System is shutting down 0 -> Begin transitioning to background
OSMessageQueue* systemMessageQueue = coreinit::OSGetSystemMessageQueue();
if(OSSendMessage(systemMessageQueue, &msg, 0))
s_transitionToBackground = false;
return;
}
if(s_transitionToForeground)
{
// add transition to foreground message
OSMessage msg{};
msg.data0 = stdx::to_underlying(SysMessageId::MsgAcquireForeground);
msg.data1 = 1; // ?
msg.data2 = 1; // ?
OSMessageQueue* systemMessageQueue = coreinit::OSGetSystemMessageQueue();
if(OSSendMessage(systemMessageQueue, &msg, 0))
s_transitionToForeground = false;
return;
}
}
// called when OSReceiveMessage returns a message from the system message queue
void HandleReceivedSystemMessage(OSMessage* msg)
{
cemu_assert_debug(!__OSHasSchedulerLock());
cemuLog_log(LogType::Force, "Receiving message: {:08x}", (uint32)msg->data0);
}
uint32 OSDriver_Register(uint32 moduleHandle, sint32 priority, OSDriverInterface* driverCallbacks, sint32 driverId, uint32be* outUkn1, uint32be* outUkn2, uint32be* outUkn3)
{
cemuLog_logDebug(LogType::Force, "OSDriver_Register stubbed");
return 0;
}
uint32 OSDriver_Deregister(uint32 moduleHandle, sint32 driverId)
{
cemuLog_logDebug(LogType::Force, "OSDriver_Deregister stubbed");
return 0;
}
void miscInit()
{
s_currentTitleId = CafeSystem::GetForegroundTitleId();
s_sdkVersion = CafeSystem::GetForegroundTitleSDKVersion();
s_transitionToBackground = false;
s_transitionToForeground = false;
cafeExportRegister("coreinit", __os_snprintf, LogType::Placeholder);
cafeExportRegister("coreinit", OSReport, LogType::Placeholder);
@ -480,6 +567,10 @@ namespace coreinit
cafeExportRegister("coreinit", COSWarn, LogType::Placeholder);
cafeExportRegister("coreinit", OSLogPrintf, LogType::Placeholder);
cafeExportRegister("coreinit", OSConsoleWrite, LogType::Placeholder);
cafeExportRegister("coreinit", OSGetPFID, LogType::Placeholder);
cafeExportRegister("coreinit", OSGetUPID, LogType::Placeholder);
cafeExportRegister("coreinit", OSGetTitleID, LogType::Placeholder);
cafeExportRegister("coreinit", __OSGetProcessSDKVersion, LogType::Placeholder);
g_homeButtonMenuEnabled = true; // enabled by default
@ -489,6 +580,11 @@ namespace coreinit
cafeExportRegister("coreinit", OSLaunchTitleByPathl, LogType::Placeholder);
cafeExportRegister("coreinit", OSRestartGame, LogType::Placeholder);
cafeExportRegister("coreinit", OSReleaseForeground, LogType::Placeholder);
cafeExportRegister("coreinit", OSDriver_Register, LogType::Placeholder);
cafeExportRegister("coreinit", OSDriver_Deregister, LogType::Placeholder);
}
};

View File

@ -2,9 +2,29 @@
namespace coreinit
{
uint32 OSGetUPID();
uint32 OSGetPFID();
uint64 OSGetTitleID();
uint32 __OSGetProcessSDKVersion();
uint32 OSLaunchTitleByPathl(const char* path, uint32 pathLength, uint32 argc);
uint32 OSRestartGame(uint32 argc, MEMPTR<char>* argv);
void OSReleaseForeground();
void StartBackgroundForegroundTransition();
struct OSDriverInterface
{
MEMPTR<void> getDriverName;
MEMPTR<void> init;
MEMPTR<void> onAcquireForeground;
MEMPTR<void> onReleaseForeground;
MEMPTR<void> done;
};
static_assert(sizeof(OSDriverInterface) == 0x14);
uint32 OSDriver_Register(uint32 moduleHandle, sint32 priority, OSDriverInterface* driverCallbacks, sint32 driverId, uint32be* outUkn1, uint32be* outUkn2, uint32be* outUkn3);
uint32 OSDriver_Deregister(uint32 moduleHandle, sint32 driverId);
void miscInit();
};

View File

@ -294,9 +294,9 @@ namespace coreinit
__OSUnlockScheduler();
}
bool OSCreateThreadType(OSThread_t* thread, MPTR entryPoint, sint32 numParam, void* ptrParam, void* stackTop2, sint32 stackSize, sint32 priority, uint32 attr, OSThread_t::THREAD_TYPE threadType)
bool OSCreateThreadType(OSThread_t* thread, MPTR entryPoint, sint32 numParam, void* ptrParam, void* stackTop, sint32 stackSize, sint32 priority, uint32 attr, OSThread_t::THREAD_TYPE threadType)
{
OSCreateThreadInternal(thread, entryPoint, memory_getVirtualOffsetFromPointer(stackTop2) - stackSize, stackSize, attr, threadType);
OSCreateThreadInternal(thread, entryPoint, memory_getVirtualOffsetFromPointer(stackTop) - stackSize, stackSize, attr, threadType);
thread->context.gpr[3] = _swapEndianU32(numParam); // num arguments
thread->context.gpr[4] = _swapEndianU32(memory_getVirtualOffsetFromPointer(ptrParam)); // arguments pointer
__OSSetThreadBasePriority(thread, priority);
@ -317,9 +317,15 @@ namespace coreinit
return true;
}
bool OSCreateThread(OSThread_t* thread, MPTR entryPoint, sint32 numParam, void* ptrParam, void* stackTop2, sint32 stackSize, sint32 priority, uint32 attr)
bool OSCreateThread(OSThread_t* thread, MPTR entryPoint, sint32 numParam, void* ptrParam, void* stackTop, sint32 stackSize, sint32 priority, uint32 attr)
{
return OSCreateThreadType(thread, entryPoint, numParam, ptrParam, stackTop2, stackSize, priority, attr, OSThread_t::THREAD_TYPE::TYPE_APP);
return OSCreateThreadType(thread, entryPoint, numParam, ptrParam, stackTop, stackSize, priority, attr, OSThread_t::THREAD_TYPE::TYPE_APP);
}
// alias to OSCreateThreadType, similar to OSCreateThread, but with an additional parameter for the thread type
bool __OSCreateThreadType(OSThread_t* thread, MPTR entryPoint, sint32 numParam, void* ptrParam, void* stackTop, sint32 stackSize, sint32 priority, uint32 attr, OSThread_t::THREAD_TYPE threadType)
{
return OSCreateThreadType(thread, entryPoint, numParam, ptrParam, stackTop, stackSize, priority, attr, threadType);
}
bool OSRunThread(OSThread_t* thread, MPTR funcAddress, sint32 numParam, void* ptrParam)
@ -445,12 +451,12 @@ namespace coreinit
return currentThread->specificArray[index].GetPtr();
}
void OSSetThreadName(OSThread_t* thread, char* name)
void OSSetThreadName(OSThread_t* thread, const char* name)
{
thread->threadName = name;
}
char* OSGetThreadName(OSThread_t* thread)
const char* OSGetThreadName(OSThread_t* thread)
{
return thread->threadName.GetPtr();
}
@ -1371,6 +1377,7 @@ namespace coreinit
{
cafeExportRegister("coreinit", OSCreateThreadType, LogType::CoreinitThread);
cafeExportRegister("coreinit", OSCreateThread, LogType::CoreinitThread);
cafeExportRegister("coreinit", __OSCreateThreadType, LogType::CoreinitThread);
cafeExportRegister("coreinit", OSExitThread, LogType::CoreinitThread);
cafeExportRegister("coreinit", OSGetCurrentThread, LogType::CoreinitThread);

View File

@ -449,7 +449,7 @@ struct OSThread_t
/* +0x578 */ sint32 alarmRelatedUkn;
/* +0x57C */ std::array<MEMPTR<void>, 16> specificArray;
/* +0x5BC */ betype<THREAD_TYPE> type;
/* +0x5C0 */ MEMPTR<char> threadName;
/* +0x5C0 */ MEMPTR<const char> threadName;
/* +0x5C4 */ MPTR waitAlarm; // used only by OSWaitEventWithTimeout/OSSignalEvent ?
/* +0x5C8 */ uint32 userStackPointer;
@ -505,6 +505,7 @@ namespace coreinit
void* OSGetDefaultThreadStack(sint32 coreIndex, uint32& size);
bool OSCreateThreadType(OSThread_t* thread, MPTR entryPoint, sint32 numParam, void* ptrParam, void* stackTop2, sint32 stackSize, sint32 priority, uint32 attr, OSThread_t::THREAD_TYPE threadType);
bool __OSCreateThreadType(OSThread_t* thread, MPTR entryPoint, sint32 numParam, void* ptrParam, void* stackTop, sint32 stackSize, sint32 priority, uint32 attr, OSThread_t::THREAD_TYPE threadType);
void OSCreateThreadInternal(OSThread_t* thread, uint32 entryPoint, MPTR stackLowerBaseAddr, uint32 stackSize, uint8 affinityMask, OSThread_t::THREAD_TYPE threadType);
bool OSRunThread(OSThread_t* thread, MPTR funcAddress, sint32 numParam, void* ptrParam);
void OSExitThread(sint32 exitValue);
@ -519,8 +520,8 @@ namespace coreinit
bool OSSetThreadPriority(OSThread_t* thread, sint32 newPriority);
uint32 OSGetThreadAffinity(OSThread_t* thread);
void OSSetThreadName(OSThread_t* thread, char* name);
char* OSGetThreadName(OSThread_t* thread);
void OSSetThreadName(OSThread_t* thread, const char* name);
const char* OSGetThreadName(OSThread_t* thread);
sint32 __OSResumeThreadInternal(OSThread_t* thread, sint32 resumeCount);
sint32 OSResumeThread(OSThread_t* thread);
@ -530,6 +531,7 @@ namespace coreinit
void OSSuspendThread(OSThread_t* thread);
void OSSleepThread(OSThreadQueue* threadQueue);
void OSWakeupThread(OSThreadQueue* threadQueue);
bool OSJoinThread(OSThread_t* thread, uint32be* exitValue);
void OSTestThreadCancelInternal();

View File

@ -22,10 +22,9 @@ namespace coreinit
osLib_returnFromFunction(hCPU, (uint32)osTime);
}
void export_OSGetTime(PPCInterpreter_t* hCPU)
uint64 OSGetTime()
{
uint64 osTime = coreinit_getOSTime();
osLib_returnFromFunction64(hCPU, osTime);
return coreinit_getOSTime();
}
void export_OSGetSystemTime(PPCInterpreter_t* hCPU)
@ -360,7 +359,7 @@ namespace coreinit
void InitializeTimeAndCalendar()
{
osLib_addFunction("coreinit", "OSGetTime", export_OSGetTime);
cafeExportRegister("coreinit", OSGetTime, LogType::Placeholder);
osLib_addFunction("coreinit", "OSGetSystemTime", export_OSGetSystemTime);
osLib_addFunction("coreinit", "OSGetTick", export_OSGetTick);
osLib_addFunction("coreinit", "OSGetSystemTick", export_OSGetSystemTick);

View File

@ -45,7 +45,8 @@ namespace coreinit
};
void OSTicksToCalendarTime(uint64 ticks, OSCalendarTime_t* calenderStruct);
uint64 OSGetTime();
uint64 coreinit_getOSTime();
uint64 coreinit_getTimerTick();

View File

@ -19,5 +19,7 @@ namespace GX2
void GX2SetTVBuffer(void* imageBuffePtr, uint32 imageBufferSize, E_TVRES tvResolutionMode, uint32 surfaceFormat, E_TVBUFFERMODE bufferMode);
void GX2SetTVGamma(float gamma);
void GX2Invalidate(uint32 invalidationFlags, MPTR invalidationAddr, uint32 invalidationSize);
void GX2MiscInit();
};

View File

@ -9,6 +9,7 @@
#include "Cafe/OS/libs/proc_ui/proc_ui.h"
#include "Cafe/OS/libs/coreinit/coreinit_Time.h"
#include "Cafe/OS/libs/coreinit/coreinit_Misc.h"
namespace nn
{
@ -37,7 +38,7 @@ namespace nn
void StubPostAppReleaseBackground(PPCInterpreter_t* hCPU)
{
coreinit::OSSleepTicks(ESPRESSO_TIMER_CLOCK * 2); // Sleep 2s
ProcUI_SendForegroundMessage();
coreinit::StartBackgroundForegroundTransition();
}
sint32 StubPostApp(void* pAnyPostParam)

View File

@ -1,57 +1,905 @@
#include "Cafe/OS/common/OSCommon.h"
#include "Cafe/OS/libs/coreinit/coreinit_Alarm.h"
#include "Cafe/OS/libs/coreinit/coreinit_Thread.h"
#include "Cafe/OS/libs/coreinit/coreinit_MessageQueue.h"
#include "Cafe/OS/libs/coreinit/coreinit_Misc.h"
#include "Cafe/OS/libs/coreinit/coreinit_Memory.h"
#include "Cafe/OS/libs/coreinit/coreinit_MEM_ExpHeap.h"
#include "Cafe/OS/libs/coreinit/coreinit_Time.h"
#include "Cafe/OS/libs/coreinit/coreinit_FG.h"
#include "Cafe/OS/libs/coreinit/coreinit_DynLoad.h"
#include "Cafe/OS/libs/gx2/GX2_Misc.h"
#include "Cafe/OS/RPL/rpl.h"
#include "Common/CafeString.h"
#include "proc_ui.h"
#define PROCUI_STATUS_FOREGROUND 0
#define PROCUI_STATUS_BACKGROUND 1
#define PROCUI_STATUS_RELEASING 2
#define PROCUI_STATUS_EXIT 3
// proc_ui is a utility wrapper to help apps with the transition between foreground and background
// some games (like Xenoblades Chronicles X) bypass proc_ui.rpl and listen to OSGetSystemMessageQueue() directly
uint32 ProcUIProcessMessages()
using namespace coreinit;
namespace proc_ui
{
return PROCUI_STATUS_FOREGROUND;
}
enum class ProcUICoreThreadCommand
{
AcquireForeground = 0x0,
ReleaseForeground = 0x1,
Exit = 0x2,
NetIoStart = 0x3,
NetIoStop = 0x4,
HomeButtonDenied = 0x5,
Initial = 0x6
};
struct ProcUIInternalCallbackEntry
{
coreinit::OSAlarm_t alarm;
uint64be tickDelay;
MEMPTR<void> funcPtr;
MEMPTR<void> userParam;
sint32be priority;
MEMPTR<ProcUIInternalCallbackEntry> next;
};
static_assert(sizeof(ProcUIInternalCallbackEntry) == 0x70);
struct ProcUICallbackList
{
MEMPTR<ProcUIInternalCallbackEntry> first;
};
static_assert(sizeof(ProcUICallbackList) == 0x4);
std::atomic_bool s_isInitialized;
bool s_isInForeground;
bool s_isInShutdown;
bool s_isForegroundProcess;
bool s_previouslyWasBlocking;
ProcUIStatus s_currentProcUIStatus;
MEMPTR<void> s_saveCallback; // no param and no return value, set by ProcUIInit()
MEMPTR<void> s_saveCallbackEx; // with custom param and return value, set by ProcUIInitEx()
MEMPTR<void> s_saveCallbackExUserParam;
MEMPTR<coreinit::OSMessageQueue> s_systemMessageQueuePtr;
SysAllocator<coreinit::OSEvent> s_eventStateMessageReceived;
SysAllocator<coreinit::OSEvent> s_eventWaitingBeforeReleaseForeground;
SysAllocator<coreinit::OSEvent> s_eventBackgroundThreadGotMessage;
// procUI core threads
uint32 s_coreThreadStackSize;
bool s_coreThreadsCreated;
std::atomic<ProcUICoreThreadCommand> s_commandForCoreThread;
SysAllocator<OSThread_t> s_coreThreadArray[Espresso::CORE_COUNT];
MEMPTR<void> s_coreThreadStackPerCore[Espresso::CORE_COUNT];
SysAllocator<CafeString<22>> s_coreThread0NameBuffer;
SysAllocator<CafeString<22>> s_coreThread1NameBuffer;
SysAllocator<CafeString<22>> s_coreThread2NameBuffer;
SysAllocator<coreinit::OSEvent> s_eventCoreThreadsNewCommandReady;
SysAllocator<coreinit::OSEvent> s_eventCoreThreadsCommandDone;
SysAllocator<coreinit::OSRendezvous> s_coreThreadRendezvousA;
SysAllocator<coreinit::OSRendezvous> s_coreThreadRendezvousB;
SysAllocator<coreinit::OSRendezvous> s_coreThreadRendezvousC;
// background thread
MEMPTR<void> s_backgroundThreadStack;
SysAllocator<OSThread_t> s_backgroundThread;
// user defined heap
MEMPTR<void> s_memoryPoolHeapPtr;
MEMPTR<void> s_memAllocPtr;
MEMPTR<void> s_memFreePtr;
// draw done release
bool s_drawDoneReleaseCalled;
// memory storage
MEMPTR<void> s_bucketStorageBasePtr;
MEMPTR<void> s_mem1StorageBasePtr;
// callbacks
ProcUICallbackList s_callbacksType0_AcquireForeground[Espresso::CORE_COUNT];
ProcUICallbackList s_callbacksType1_ReleaseForeground[Espresso::CORE_COUNT];
ProcUICallbackList s_callbacksType2_Exit[Espresso::CORE_COUNT];
ProcUICallbackList s_callbacksType3_NetIoStart[Espresso::CORE_COUNT];
ProcUICallbackList s_callbacksType4_NetIoStop[Espresso::CORE_COUNT];
ProcUICallbackList s_callbacksType5_HomeButtonDenied[Espresso::CORE_COUNT];
ProcUICallbackList* const s_CallbackTables[stdx::to_underlying(ProcUICallbackId::COUNT)] =
{s_callbacksType0_AcquireForeground, s_callbacksType1_ReleaseForeground, s_callbacksType2_Exit, s_callbacksType3_NetIoStart, s_callbacksType4_NetIoStop, s_callbacksType5_HomeButtonDenied};
ProcUICallbackList s_backgroundCallbackList;
// driver
bool s_driverIsActive;
uint32be s_driverArgUkn1;
uint32be s_driverArgUkn2;
bool s_driverInBackground;
SysAllocator<OSDriverInterface> s_ProcUIDriver;
SysAllocator<CafeString<16>> s_ProcUIDriverName;
uint32 ProcUIInForeground(PPCInterpreter_t* hCPU)
{
return 1; // true means application is in foreground
}
void* _AllocMem(uint32 size)
{
MEMPTR<void> r{PPCCoreCallback(s_memAllocPtr, size)};
return r.GetPtr();
}
struct ProcUICallback
{
MPTR callback;
void* data;
sint32 priority;
void _FreeMem(void* ptr)
{
PPCCoreCallback(s_memFreePtr.GetMPTR(), ptr);
}
void ClearCallbacksWithoutMemFree()
{
for (sint32 coreIndex = 0; coreIndex < Espresso::CORE_COUNT; coreIndex++)
{
for (sint32 i = 0; i < stdx::to_underlying(ProcUICallbackId::COUNT); i++)
s_CallbackTables[i][coreIndex].first = nullptr;
}
s_backgroundCallbackList.first = nullptr;
}
void ShutdownThreads()
{
if ( !s_coreThreadsCreated)
return;
s_commandForCoreThread = ProcUICoreThreadCommand::Initial;
coreinit::OSMemoryBarrier();
OSSignalEvent(&s_eventCoreThreadsNewCommandReady);
for (sint32 coreIndex = 0; coreIndex < Espresso::CORE_COUNT; coreIndex++)
{
coreinit::OSJoinThread(&s_coreThreadArray[coreIndex], nullptr);
for (sint32 i = 0; i < stdx::to_underlying(ProcUICallbackId::COUNT); i++)
{
s_CallbackTables[i][coreIndex].first = nullptr; // memory is not cleanly released?
}
}
OSResetEvent(&s_eventCoreThreadsNewCommandReady);
for (sint32 coreIndex = 0; coreIndex < Espresso::CORE_COUNT; coreIndex++)
{
_FreeMem(s_coreThreadStackPerCore[coreIndex]);
s_coreThreadStackPerCore[coreIndex] = nullptr;
}
_FreeMem(s_backgroundThreadStack);
s_backgroundThreadStack = nullptr;
s_backgroundCallbackList.first = nullptr; // memory is not cleanly released?
s_coreThreadsCreated = false;
}
void DoCallbackChain(ProcUIInternalCallbackEntry* entry)
{
while (entry)
{
uint32 r = PPCCoreCallback(entry->funcPtr, entry->userParam);
if ( r )
cemuLog_log(LogType::APIErrors, "ProcUI: Callback returned error {}\n", r);
entry = entry->next;
}
}
void AlarmDoBackgroundCallback(PPCInterpreter_t* hCPU)
{
coreinit::OSAlarm_t* arg = MEMPTR<coreinit::OSAlarm_t>(hCPU->gpr[3]);
ProcUIInternalCallbackEntry* entry = (ProcUIInternalCallbackEntry*)arg;
uint32 r = PPCCoreCallback(entry->funcPtr, entry->userParam);
if ( r )
cemuLog_log(LogType::APIErrors, "ProcUI: Background callback returned error {}\n", r);
osLib_returnFromFunction(hCPU, 0); // return type is void
}
void StartBackgroundAlarms()
{
ProcUIInternalCallbackEntry* cb = s_backgroundCallbackList.first;
while(cb)
{
coreinit::OSCreateAlarm(&cb->alarm);
uint64 currentTime = coreinit::OSGetTime();
coreinit::OSSetPeriodicAlarm(&cb->alarm, currentTime, cb->tickDelay, RPLLoader_MakePPCCallable(AlarmDoBackgroundCallback));
cb = cb->next;
}
}
void CancelBackgroundAlarms()
{
ProcUIInternalCallbackEntry* entry = s_backgroundCallbackList.first;
while (entry)
{
OSCancelAlarm(&entry->alarm);
entry = entry->next;
}
}
void ProcUICoreThread(PPCInterpreter_t* hCPU)
{
uint32 coreIndex = hCPU->gpr[3];
cemu_assert_debug(coreIndex == OSGetCoreId());
while (true)
{
OSWaitEvent(&s_eventCoreThreadsNewCommandReady);
ProcUIInternalCallbackEntry* cbChain = nullptr;
cemuLog_logDebug(LogType::Force, "ProcUI: Core {} got command {}", coreIndex, (uint32)s_commandForCoreThread.load());
auto cmd = s_commandForCoreThread.load();
switch(cmd)
{
case ProcUICoreThreadCommand::Initial:
{
// signal to shut down thread
osLib_returnFromFunction(hCPU, 0);
return;
}
case ProcUICoreThreadCommand::AcquireForeground:
cbChain = s_callbacksType0_AcquireForeground[coreIndex].first;
break;
case ProcUICoreThreadCommand::ReleaseForeground:
cbChain = s_callbacksType1_ReleaseForeground[coreIndex].first;
break;
case ProcUICoreThreadCommand::Exit:
cbChain = s_callbacksType2_Exit[coreIndex].first;
break;
case ProcUICoreThreadCommand::NetIoStart:
cbChain = s_callbacksType3_NetIoStart[coreIndex].first;
break;
case ProcUICoreThreadCommand::NetIoStop:
cbChain = s_callbacksType4_NetIoStop[coreIndex].first;
break;
case ProcUICoreThreadCommand::HomeButtonDenied:
cbChain = s_callbacksType5_HomeButtonDenied[coreIndex].first;
break;
default:
cemu_assert_suspicious(); // invalid command
}
if(cmd == ProcUICoreThreadCommand::AcquireForeground)
{
if (coreIndex == 2)
CancelBackgroundAlarms();
cbChain = s_callbacksType0_AcquireForeground[coreIndex].first;
}
else if(cmd == ProcUICoreThreadCommand::ReleaseForeground)
{
if (coreIndex == 2)
StartBackgroundAlarms();
cbChain = s_callbacksType1_ReleaseForeground[coreIndex].first;
}
DoCallbackChain(cbChain);
OSWaitRendezvous(&s_coreThreadRendezvousA, 7);
if ( !coreIndex )
{
OSInitRendezvous(&s_coreThreadRendezvousC);
OSResetEvent(&s_eventCoreThreadsNewCommandReady);
}
OSWaitRendezvous(&s_coreThreadRendezvousB, 7);
if ( !coreIndex )
{
OSInitRendezvous(&s_coreThreadRendezvousA);
OSSignalEvent(&s_eventCoreThreadsCommandDone);
}
OSWaitRendezvous(&s_coreThreadRendezvousC, 7);
if ( !coreIndex )
OSInitRendezvous(&s_coreThreadRendezvousB);
if (cmd == ProcUICoreThreadCommand::ReleaseForeground)
{
OSWaitEvent(&s_eventWaitingBeforeReleaseForeground);
OSReleaseForeground();
}
}
osLib_returnFromFunction(hCPU, 0);
}
void RecreateProcUICoreThreads()
{
ShutdownThreads();
for (sint32 coreIndex = 0; coreIndex < Espresso::CORE_COUNT; coreIndex++)
{
s_coreThreadStackPerCore[coreIndex] = _AllocMem(s_coreThreadStackSize);
}
s_backgroundThreadStack = _AllocMem(s_coreThreadStackSize);
for (sint32 coreIndex = 0; coreIndex < Espresso::CORE_COUNT; coreIndex++)
{
__OSCreateThreadType(&s_coreThreadArray[coreIndex], RPLLoader_MakePPCCallable(ProcUICoreThread), coreIndex, nullptr,
(uint8*)s_coreThreadStackPerCore[coreIndex].GetPtr() + s_coreThreadStackSize, s_coreThreadStackSize, 16,
(1<<coreIndex), OSThread_t::THREAD_TYPE::TYPE_DRIVER);
OSResumeThread(&s_coreThreadArray[coreIndex]);
}
s_coreThread0NameBuffer->assign("{SYS ProcUI Core 0}");
s_coreThread1NameBuffer->assign("{SYS ProcUI Core 1}");
s_coreThread2NameBuffer->assign("{SYS ProcUI Core 2}");
OSSetThreadName(&s_coreThreadArray[0], s_coreThread0NameBuffer->c_str());
OSSetThreadName(&s_coreThreadArray[1], s_coreThread1NameBuffer->c_str());
OSSetThreadName(&s_coreThreadArray[2], s_coreThread2NameBuffer->c_str());
s_coreThreadsCreated = true;
}
void _SubmitCommandToCoreThreads(ProcUICoreThreadCommand cmd)
{
s_commandForCoreThread = cmd;
OSMemoryBarrier();
OSResetEvent(&s_eventCoreThreadsCommandDone);
OSSignalEvent(&s_eventCoreThreadsNewCommandReady);
OSWaitEvent(&s_eventCoreThreadsCommandDone);
}
void ProcUIInitInternal()
{
if( s_isInitialized.exchange(true) )
return;
if (!s_memoryPoolHeapPtr)
{
// user didn't specify a custom heap, use default heap instead
s_memAllocPtr = gCoreinitData->MEMAllocFromDefaultHeap.GetMPTR();
s_memFreePtr = gCoreinitData->MEMFreeToDefaultHeap.GetMPTR();
}
OSInitEvent(&s_eventStateMessageReceived, OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, OSEvent::EVENT_MODE::MODE_MANUAL);
OSInitEvent(&s_eventCoreThreadsNewCommandReady, OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, OSEvent::EVENT_MODE::MODE_MANUAL);
OSInitEvent(&s_eventWaitingBeforeReleaseForeground, OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, OSEvent::EVENT_MODE::MODE_MANUAL);
OSInitEvent(&s_eventCoreThreadsCommandDone, OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, OSEvent::EVENT_MODE::MODE_MANUAL);
OSInitRendezvous(&s_coreThreadRendezvousA);
OSInitRendezvous(&s_coreThreadRendezvousB);
OSInitRendezvous(&s_coreThreadRendezvousC);
OSInitEvent(&s_eventBackgroundThreadGotMessage, OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, OSEvent::EVENT_MODE::MODE_MANUAL);
s_currentProcUIStatus = ProcUIStatus::Foreground;
s_drawDoneReleaseCalled = false;
s_isInForeground = true;
s_coreThreadStackSize = 0x2000;
s_systemMessageQueuePtr = coreinit::OSGetSystemMessageQueue();
uint32 upid = coreinit::OSGetUPID();
s_isForegroundProcess = upid == 2 || upid == 15; // either Wii U Menu or game title, both are RAMPID 7 (foreground process)
RecreateProcUICoreThreads();
ClearCallbacksWithoutMemFree();
}
void ProcUIInit(MEMPTR<void> callbackReadyToRelease)
{
s_saveCallback = callbackReadyToRelease;
s_saveCallbackEx = nullptr;
s_saveCallbackExUserParam = nullptr;
ProcUIInitInternal();
}
void ProcUIInitEx(MEMPTR<void> callbackReadyToReleaseEx, MEMPTR<void> userParam)
{
s_saveCallback = nullptr;
s_saveCallbackEx = callbackReadyToReleaseEx;
s_saveCallbackExUserParam = userParam;
ProcUIInitInternal();
}
void ProcUIShutdown()
{
if (!s_isInitialized.exchange(false))
return;
if ( !s_isInForeground )
CancelBackgroundAlarms();
for (sint32 i = 0; i < Espresso::CORE_COUNT; i++)
OSSetThreadPriority(&s_coreThreadArray[i], 0);
_SubmitCommandToCoreThreads(ProcUICoreThreadCommand::Exit);
ProcUIClearCallbacks();
ShutdownThreads();
}
bool ProcUIIsRunning()
{
return s_isInitialized;
}
bool ProcUIInForeground()
{
return s_isInForeground;
}
bool ProcUIInShutdown()
{
return s_isInShutdown;
}
void AddCallbackInternal(void* funcPtr, void* userParam, uint64 tickDelay, sint32 priority, ProcUICallbackList& callbackList)
{
if ( __OSGetProcessSDKVersion() < 21102 )
{
// in earlier COS versions it was possible/allowed to register a callback before initializing ProcUI
s_memAllocPtr = gCoreinitData->MEMAllocFromDefaultHeap.GetMPTR();
s_memFreePtr = gCoreinitData->MEMFreeToDefaultHeap.GetMPTR();
}
else if ( !s_isInitialized )
{
cemuLog_log(LogType::Force, "ProcUI: Trying to register callback before init");
cemu_assert_suspicious();
}
ProcUIInternalCallbackEntry* entry = (ProcUIInternalCallbackEntry*)_AllocMem(sizeof(ProcUIInternalCallbackEntry));
entry->funcPtr = funcPtr;
entry->userParam = userParam;
entry->tickDelay = tickDelay;
entry->priority = priority;
ProcUIInternalCallbackEntry* cur = callbackList.first;
cur = callbackList.first;
if (!cur || cur->priority > priority)
{
// insert as the first element
entry->next = cur;
callbackList.first = entry;
}
else
{
// find the correct position to insert
while (cur->next && cur->next->priority < priority)
cur = cur->next;
entry->next = cur->next;
cur->next = entry;
}
}
void ProcUIRegisterCallbackCore(ProcUICallbackId callbackType, void* funcPtr, void* userParam, sint32 priority, uint32 coreIndex)
{
if(callbackType >= ProcUICallbackId::COUNT)
{
cemuLog_log(LogType::Force, "ProcUIRegisterCallback: Invalid callback type {}", stdx::to_underlying(callbackType));
return;
}
if(callbackType != ProcUICallbackId::AcquireForeground)
priority = -priority;
AddCallbackInternal(funcPtr, userParam, priority, 0, s_CallbackTables[stdx::to_underlying(callbackType)][coreIndex]);
}
void ProcUIRegisterCallback(ProcUICallbackId callbackType, void* funcPtr, void* userParam, sint32 priority)
{
ProcUIRegisterCallbackCore(callbackType, funcPtr, userParam, priority, OSGetCoreId());
}
void ProcUIRegisterBackgroundCallback(void* funcPtr, void* userParam, uint64 tickDelay)
{
AddCallbackInternal(funcPtr, userParam, 0, tickDelay, s_backgroundCallbackList);
}
void FreeCallbackChain(ProcUICallbackList& callbackList)
{
ProcUIInternalCallbackEntry* entry = callbackList.first;
while (entry)
{
ProcUIInternalCallbackEntry* next = entry->next;
_FreeMem(entry);
entry = next;
}
callbackList.first = nullptr;
}
void ProcUIClearCallbacks()
{
for (sint32 coreIndex = 0; coreIndex < Espresso::CORE_COUNT; coreIndex++)
{
for (sint32 i = 0; i < stdx::to_underlying(ProcUICallbackId::COUNT); i++)
{
FreeCallbackChain(s_CallbackTables[i][coreIndex]);
}
}
if (!s_isInForeground)
CancelBackgroundAlarms();
FreeCallbackChain(s_backgroundCallbackList);
}
void ProcUISetSaveCallback(void* funcPtr, void* userParam)
{
s_saveCallback = nullptr;
s_saveCallbackEx = funcPtr;
s_saveCallbackExUserParam = userParam;
}
void ProcUISetCallbackStackSize(uint32 newStackSize)
{
s_coreThreadStackSize = newStackSize;
if( s_isInitialized )
RecreateProcUICoreThreads();
}
uint32 ProcUICalcMemorySize(uint32 numCallbacks)
{
// each callback entry is 0x70 bytes with 0x14 bytes of allocator overhead (for ExpHeap). But for some reason proc_ui on 5.5.5 seems to reserve 0x8C0 bytes per callback?
uint32 stackReserveSize = (Espresso::CORE_COUNT + 1) * s_coreThreadStackSize; // 3 core threads + 1 message receive thread
uint32 callbackReserveSize = 0x8C0 * numCallbacks;
return stackReserveSize + callbackReserveSize + 100;
}
void _MemAllocFromMemoryPool(PPCInterpreter_t* hCPU)
{
uint32 size = hCPU->gpr[3];
MEMPTR<void> r = MEMAllocFromExpHeapEx((MEMHeapHandle)s_memoryPoolHeapPtr.GetPtr(), size, 4);
osLib_returnFromFunction(hCPU, r.GetMPTR());
}
void _FreeToMemoryPoolExpHeap(PPCInterpreter_t* hCPU)
{
MEMPTR<void> mem{hCPU->gpr[3]};
MEMFreeToExpHeap((MEMHeapHandle)s_memoryPoolHeapPtr.GetPtr(), mem.GetPtr());
osLib_returnFromFunction(hCPU, 0);
}
sint32 ProcUISetMemoryPool(void* memBase, uint32 size)
{
s_memAllocPtr = RPLLoader_MakePPCCallable(_MemAllocFromMemoryPool);
s_memFreePtr = RPLLoader_MakePPCCallable(_FreeToMemoryPoolExpHeap);
s_memoryPoolHeapPtr = MEMCreateExpHeapEx(memBase, size, MEM_HEAP_OPTION_THREADSAFE);
return s_memoryPoolHeapPtr ? 0 : -1;
}
void ProcUISetBucketStorage(void* memBase, uint32 size)
{
MEMPTR<void> fgBase;
uint32be fgFreeSize;
OSGetForegroundBucketFreeArea((MPTR*)&fgBase, (MPTR*)&fgFreeSize);
if(fgFreeSize < size)
cemuLog_log(LogType::Force, "ProcUISetBucketStorage: Buffer size too small");
s_bucketStorageBasePtr = memBase;
}
void ProcUISetMEM1Storage(void* memBase, uint32 size)
{
MEMPTR<void> memBound;
uint32be memBoundSize;
OSGetMemBound(1, (MPTR*)memBound.GetBEPtr(), (uint32*)&memBoundSize);
if(memBoundSize < size)
cemuLog_log(LogType::Force, "ProcUISetMEM1Storage: Buffer size too small");
s_mem1StorageBasePtr = memBase;
}
void ProcUIDrawDoneRelease()
{
s_drawDoneReleaseCalled = true;
}
OSMessage g_lastMsg;
void ProcUI_BackgroundThread_ReceiveSingleMessage(PPCInterpreter_t* hCPU)
{
// the background thread receives messages in a loop until the title is either exited or foreground is acquired
while ( true )
{
OSReceiveMessage(s_systemMessageQueuePtr, &g_lastMsg, OS_MESSAGE_BLOCK); // blocking receive
SysMessageId lastMsgId = static_cast<SysMessageId>((uint32)g_lastMsg.data0);
if(lastMsgId == SysMessageId::MsgExit || lastMsgId == SysMessageId::MsgAcquireForeground)
break;
else if (lastMsgId == SysMessageId::HomeButtonDenied)
{
cemu_assert_suspicious(); // Home button denied should not be sent to background app
}
else if ( lastMsgId == SysMessageId::NetIoStartOrStop )
{
if (g_lastMsg.data1 )
{
// NetIo start message
for (sint32 i = 0; i < Espresso::CORE_COUNT; i++)
DoCallbackChain(s_callbacksType3_NetIoStart[i].first);
}
else
{
// NetIo stop message
for (sint32 i = 0; i < Espresso::CORE_COUNT; i++)
DoCallbackChain(s_callbacksType4_NetIoStop[i].first);
}
}
else
{
cemuLog_log(LogType::Force, "ProcUI: BackgroundThread received invalid message 0x{:08x}", lastMsgId);
}
}
OSSignalEvent(&s_eventBackgroundThreadGotMessage);
osLib_returnFromFunction(hCPU, 0);
}
// handle received message
// if the message is Exit this function returns false, in all other cases it returns true
bool ProcessSysMessage(OSMessage* msg)
{
SysMessageId lastMsgId = static_cast<SysMessageId>((uint32)msg->data0);
if ( lastMsgId == SysMessageId::MsgAcquireForeground )
{
cemuLog_logDebug(LogType::Force, "ProcUI: Received Acquire Foreground message");
s_isInShutdown = false;
_SubmitCommandToCoreThreads(ProcUICoreThreadCommand::AcquireForeground);
s_currentProcUIStatus = ProcUIStatus::Foreground;
s_isInForeground = true;
OSMemoryBarrier();
OSSignalEvent(&s_eventStateMessageReceived);
return true;
}
else if (lastMsgId == SysMessageId::MsgExit)
{
cemuLog_logDebug(LogType::Force, "ProcUI: Received Exit message");
s_isInShutdown = true;
_SubmitCommandToCoreThreads(ProcUICoreThreadCommand::Exit);
for (sint32 i = 0; i < Espresso::CORE_COUNT; i++)
FreeCallbackChain(s_callbacksType2_Exit[i]);
s_currentProcUIStatus = ProcUIStatus::Exit;
OSMemoryBarrier();
OSSignalEvent(&s_eventStateMessageReceived);
return 0;
}
if (lastMsgId == SysMessageId::MsgReleaseForeground)
{
if (msg->data1 != 0)
{
cemuLog_logDebug(LogType::Force, "ProcUI: Received Release Foreground message as part of shutdown initiation");
s_isInShutdown = true;
}
else
{
cemuLog_logDebug(LogType::Force, "ProcUI: Received Release Foreground message");
}
s_currentProcUIStatus = ProcUIStatus::Releasing;
OSResetEvent(&s_eventStateMessageReceived);
// dont submit a command for the core threads yet, we need to wait for ProcUIDrawDoneRelease()
}
else if (lastMsgId == SysMessageId::HomeButtonDenied)
{
cemuLog_logDebug(LogType::Force, "ProcUI: Received Home Button Denied message");
_SubmitCommandToCoreThreads(ProcUICoreThreadCommand::HomeButtonDenied);
}
else if ( lastMsgId == SysMessageId::NetIoStartOrStop )
{
if (msg->data1 != 0)
{
cemuLog_logDebug(LogType::Force, "ProcUI: Received Net IO Start message");
_SubmitCommandToCoreThreads(ProcUICoreThreadCommand::NetIoStart);
}
else
{
cemuLog_logDebug(LogType::Force, "ProcUI: Received Net IO Stop message");
_SubmitCommandToCoreThreads(ProcUICoreThreadCommand::NetIoStop);
}
}
else
{
cemuLog_log(LogType::Force, "ProcUI: Received unknown message 0x{:08x}", (uint32)lastMsgId);
}
return true;
}
ProcUIStatus ProcUIProcessMessages(bool isBlockingInBackground)
{
OSMessage msg;
if (!s_isInitialized)
{
cemuLog_logOnce(LogType::Force, "ProcUIProcessMessages: ProcUI not initialized");
cemu_assert_suspicious();
return ProcUIStatus::Foreground;
}
if ( !isBlockingInBackground && OSGetCoreId() != 2 )
{
cemuLog_logOnce(LogType::Force, "ProcUIProcessMessages: Non-blocking call must run on core 2");
}
if (s_previouslyWasBlocking && isBlockingInBackground )
{
cemuLog_logOnce(LogType::Force, "ProcUIProcessMessages: Cannot switch to blocking mode when in background");
}
s_currentProcUIStatus = s_isInForeground ? ProcUIStatus::Foreground : ProcUIStatus::Background;
if (s_drawDoneReleaseCalled)
{
s_isInForeground = false;
s_currentProcUIStatus = ProcUIStatus::Background;
_SubmitCommandToCoreThreads(ProcUICoreThreadCommand::ReleaseForeground);
OSResetEvent(&s_eventWaitingBeforeReleaseForeground);
if(s_saveCallback)
PPCCoreCallback(s_saveCallback);
if(s_saveCallbackEx)
PPCCoreCallback(s_saveCallbackEx, s_saveCallbackExUserParam);
if (s_isForegroundProcess && isBlockingInBackground)
{
// start background thread
__OSCreateThreadType(&s_backgroundThread, RPLLoader_MakePPCCallable(ProcUI_BackgroundThread_ReceiveSingleMessage),
0, nullptr, (uint8*)s_backgroundThreadStack.GetPtr() + s_coreThreadStackSize, s_coreThreadStackSize,
16, (1<<2), OSThread_t::THREAD_TYPE::TYPE_DRIVER);
OSResumeThread(&s_backgroundThread);
s_previouslyWasBlocking = true;
}
cemuLog_logDebug(LogType::Force, "ProcUI: Releasing foreground");
OSSignalEvent(&s_eventWaitingBeforeReleaseForeground);
s_drawDoneReleaseCalled = false;
}
if (s_isInForeground || !isBlockingInBackground)
{
// non-blocking mode
if ( OSReceiveMessage(s_systemMessageQueuePtr, &msg, 0) )
{
s_previouslyWasBlocking = false;
if ( !ProcessSysMessage(&msg) )
return s_currentProcUIStatus;
// continue below, if we are now in background then ProcUIProcessMessages enters blocking mode
}
}
// blocking mode (if in background and param is true)
while (!s_isInForeground && isBlockingInBackground)
{
if ( !s_isForegroundProcess)
{
OSReceiveMessage(s_systemMessageQueuePtr, &msg, OS_MESSAGE_BLOCK);
s_previouslyWasBlocking = false;
if ( !ProcessSysMessage(&msg) )
return s_currentProcUIStatus;
}
// this code should only run if the background thread was started? Maybe rearrange the code to make this more clear
OSWaitEvent(&s_eventBackgroundThreadGotMessage);
OSResetEvent(&s_eventBackgroundThreadGotMessage);
OSJoinThread(&s_backgroundThread, nullptr);
msg = g_lastMsg; // g_lastMsg is set by the background thread
s_previouslyWasBlocking = false;
if ( !ProcessSysMessage(&msg) )
return s_currentProcUIStatus;
}
return s_currentProcUIStatus;
}
ProcUIStatus ProcUISubProcessMessages(bool isBlockingInBackground)
{
if (isBlockingInBackground)
{
while (s_currentProcUIStatus == ProcUIStatus::Background)
OSWaitEvent(&s_eventStateMessageReceived);
}
return s_currentProcUIStatus;
}
const char* ProcUIDriver_GetName()
{
s_ProcUIDriverName->assign("ProcUI");
return s_ProcUIDriverName->c_str();
}
void ProcUIDriver_Init(/* parameters unknown */)
{
s_driverIsActive = true;
OSMemoryBarrier();
}
void ProcUIDriver_OnDone(/* parameters unknown */)
{
if (s_driverIsActive)
{
ProcUIShutdown();
s_driverIsActive = false;
OSMemoryBarrier();
}
}
void StoreMEM1AndFGBucket()
{
if (s_mem1StorageBasePtr)
{
MEMPTR<void> memBound;
uint32be memBoundSize;
OSGetMemBound(1, (MPTR*)memBound.GetBEPtr(), (uint32*)&memBoundSize);
OSBlockMove(s_mem1StorageBasePtr.GetPtr(), memBound.GetPtr(), memBoundSize, true);
}
if (s_bucketStorageBasePtr)
{
MEMPTR<void> memBound;
uint32be memBoundSize;
OSGetForegroundBucketFreeArea((MPTR*)memBound.GetBEPtr(), (MPTR*)&memBoundSize);
OSBlockMove(s_bucketStorageBasePtr.GetPtr(), memBound.GetPtr(), memBoundSize, true);
}
}
void RestoreMEM1AndFGBucket()
{
if (s_mem1StorageBasePtr)
{
MEMPTR<void> memBound;
uint32be memBoundSize;
OSGetMemBound(1, (MPTR*)memBound.GetBEPtr(), (uint32*)&memBoundSize);
OSBlockMove(memBound.GetPtr(), s_mem1StorageBasePtr, memBoundSize, true);
GX2::GX2Invalidate(0x40, s_mem1StorageBasePtr.GetMPTR(), memBoundSize);
}
if (s_bucketStorageBasePtr)
{
MEMPTR<void> memBound;
uint32be memBoundSize;
OSGetForegroundBucketFreeArea((MPTR*)memBound.GetBEPtr(), (MPTR*)&memBoundSize);
OSBlockMove(memBound.GetPtr(), s_bucketStorageBasePtr, memBoundSize, true);
GX2::GX2Invalidate(0x40, memBound.GetMPTR(), memBoundSize);
}
}
void ProcUIDriver_OnAcquiredForeground(/* parameters unknown */)
{
if (s_driverInBackground)
{
ProcUIDriver_Init();
s_driverInBackground = false;
}
else
{
RestoreMEM1AndFGBucket();
s_driverIsActive = true;
OSMemoryBarrier();
}
}
void ProcUIDriver_OnReleaseForeground(/* parameters unknown */)
{
StoreMEM1AndFGBucket();
s_driverIsActive = false;
OSMemoryBarrier();
}
sint32 rpl_entry(uint32 moduleHandle, RplEntryReason reason)
{
if ( reason == RplEntryReason::Loaded )
{
s_ProcUIDriver->getDriverName = RPLLoader_MakePPCCallable([](PPCInterpreter_t* hCPU) {MEMPTR<const char> namePtr(ProcUIDriver_GetName()); osLib_returnFromFunction(hCPU, namePtr.GetMPTR()); });
s_ProcUIDriver->init = RPLLoader_MakePPCCallable([](PPCInterpreter_t* hCPU) {ProcUIDriver_Init(); osLib_returnFromFunction(hCPU, 0); });
s_ProcUIDriver->onAcquireForeground = RPLLoader_MakePPCCallable([](PPCInterpreter_t* hCPU) {ProcUIDriver_OnAcquiredForeground(); osLib_returnFromFunction(hCPU, 0); });
s_ProcUIDriver->onReleaseForeground = RPLLoader_MakePPCCallable([](PPCInterpreter_t* hCPU) {ProcUIDriver_OnReleaseForeground(); osLib_returnFromFunction(hCPU, 0); });
s_ProcUIDriver->done = RPLLoader_MakePPCCallable([](PPCInterpreter_t* hCPU) {ProcUIDriver_OnDone(); osLib_returnFromFunction(hCPU, 0); });
s_driverIsActive = false;
s_driverArgUkn1 = 0;
s_driverArgUkn2 = 0;
s_driverInBackground = false;
uint32be ukn3;
OSDriver_Register(moduleHandle, 200, &s_ProcUIDriver, 0, &s_driverArgUkn1, &s_driverArgUkn2, &ukn3);
if ( ukn3 )
{
if ( OSGetForegroundBucket(nullptr, nullptr) )
{
ProcUIDriver_Init();
OSMemoryBarrier();
return 0;
}
s_driverInBackground = true;
}
OSMemoryBarrier();
}
else if ( reason == RplEntryReason::Unloaded )
{
ProcUIDriver_OnDone();
OSDriver_Deregister(moduleHandle, 0);
}
return 0;
}
void reset()
{
// set variables to their initial state as if the RPL was just loaded
s_isInitialized = false;
s_isInShutdown = false;
s_isInForeground = false; // ProcUIInForeground returns false until ProcUIInit(Ex) is called
s_isForegroundProcess = true;
s_saveCallback = nullptr;
s_saveCallbackEx = nullptr;
s_systemMessageQueuePtr = nullptr;
ClearCallbacksWithoutMemFree();
s_currentProcUIStatus = ProcUIStatus::Foreground;
s_bucketStorageBasePtr = nullptr;
s_mem1StorageBasePtr = nullptr;
s_drawDoneReleaseCalled = false;
s_previouslyWasBlocking = false;
// core threads
s_coreThreadStackSize = 0;
s_coreThreadsCreated = false;
s_commandForCoreThread = ProcUICoreThreadCommand::Initial;
// background thread
s_backgroundThreadStack = nullptr;
// user defined heap
s_memoryPoolHeapPtr = nullptr;
s_memAllocPtr = nullptr;
s_memFreePtr = nullptr;
// driver
s_driverIsActive = false;
s_driverInBackground = false;
}
void load()
{
reset();
cafeExportRegister("proc_ui", ProcUIInit, LogType::ProcUi);
cafeExportRegister("proc_ui", ProcUIInitEx, LogType::ProcUi);
cafeExportRegister("proc_ui", ProcUIShutdown, LogType::ProcUi);
cafeExportRegister("proc_ui", ProcUIIsRunning, LogType::ProcUi);
cafeExportRegister("proc_ui", ProcUIInForeground, LogType::ProcUi);
cafeExportRegister("proc_ui", ProcUIInShutdown, LogType::ProcUi);
cafeExportRegister("proc_ui", ProcUIRegisterCallbackCore, LogType::ProcUi);
cafeExportRegister("proc_ui", ProcUIRegisterCallback, LogType::ProcUi);
cafeExportRegister("proc_ui", ProcUIRegisterBackgroundCallback, LogType::ProcUi);
cafeExportRegister("proc_ui", ProcUIClearCallbacks, LogType::ProcUi);
cafeExportRegister("proc_ui", ProcUISetSaveCallback, LogType::ProcUi);
cafeExportRegister("proc_ui", ProcUISetCallbackStackSize, LogType::ProcUi);
cafeExportRegister("proc_ui", ProcUICalcMemorySize, LogType::ProcUi);
cafeExportRegister("proc_ui", ProcUISetMemoryPool, LogType::ProcUi);
cafeExportRegister("proc_ui", ProcUISetBucketStorage, LogType::ProcUi);
cafeExportRegister("proc_ui", ProcUISetMEM1Storage, LogType::ProcUi);
cafeExportRegister("proc_ui", ProcUIDrawDoneRelease, LogType::ProcUi);
cafeExportRegister("proc_ui", ProcUIProcessMessages, LogType::ProcUi);
cafeExportRegister("proc_ui", ProcUISubProcessMessages, LogType::ProcUi);
// manually call rpl_entry for now
rpl_entry(-1, RplEntryReason::Loaded);
}
};
std::unordered_map<uint32, ProcUICallback> g_Callbacks;
uint32 ProcUIRegisterCallback(uint32 message, MPTR callback, void* data, sint32 priority)
{
g_Callbacks.insert_or_assign(message, ProcUICallback{ .callback = callback, .data = data, .priority = priority });
return 0;
}
void ProcUI_SendBackgroundMessage()
{
if (g_Callbacks.contains(PROCUI_STATUS_BACKGROUND))
{
ProcUICallback& callback = g_Callbacks[PROCUI_STATUS_BACKGROUND];
PPCCoreCallback(callback.callback, callback.data);
}
}
void ProcUI_SendForegroundMessage()
{
if (g_Callbacks.contains(PROCUI_STATUS_FOREGROUND))
{
ProcUICallback& callback = g_Callbacks[PROCUI_STATUS_FOREGROUND];
PPCCoreCallback(callback.callback, callback.data);
}
}
void procui_load()
{
cafeExportRegister("proc_ui", ProcUIRegisterCallback, LogType::ProcUi);
cafeExportRegister("proc_ui", ProcUIProcessMessages, LogType::ProcUi);
cafeExportRegister("proc_ui", ProcUIInForeground, LogType::ProcUi);
}

View File

@ -1,5 +1,44 @@
void procui_load();
namespace proc_ui
{
enum class ProcUIStatus
{
Foreground = 0,
Background = 1,
Releasing = 2,
Exit = 3
};
void ProcUI_SendForegroundMessage();
void ProcUI_SendBackgroundMessage();
enum class ProcUICallbackId
{
AcquireForeground = 0,
ReleaseForeground = 1,
Exit = 2,
NetIoStart = 3,
NetIoStop = 4,
HomeButtonDenied = 5,
COUNT = 6
};
void ProcUIInit(MEMPTR<void> callbackReadyToRelease);
void ProcUIInitEx(MEMPTR<void> callbackReadyToReleaseEx, MEMPTR<void> userParam);
void ProcUIShutdown();
bool ProcUIIsRunning();
bool ProcUIInForeground();
bool ProcUIInShutdown();
void ProcUIRegisterCallback(ProcUICallbackId callbackType, void* funcPtr, void* userParam, sint32 priority);
void ProcUIRegisterCallbackCore(ProcUICallbackId callbackType, void* funcPtr, void* userParam, sint32 priority, uint32 coreIndex);
void ProcUIRegisterBackgroundCallback(void* funcPtr, void* userParam, uint64 tickDelay);
void ProcUIClearCallbacks();
void ProcUISetSaveCallback(void* funcPtr, void* userParam);
void ProcUISetCallbackStackSize(uint32 newStackSize);
uint32 ProcUICalcMemorySize(uint32 numCallbacks);
sint32 ProcUISetMemoryPool(void* memBase, uint32 size);
void ProcUISetBucketStorage(void* memBase, uint32 size);
void ProcUISetMEM1Storage(void* memBase, uint32 size);
void ProcUIDrawDoneRelease();
ProcUIStatus ProcUIProcessMessages(bool isBlockingInBackground);
ProcUIStatus ProcUISubProcessMessages(bool isBlockingInBackground);
void load();
}

View File

@ -639,11 +639,34 @@ namespace sysapp
return coreinit::OSRestartGame(argc, argv);
}
struct EManualArgs
{
sysStandardArguments_t stdArgs;
uint64be titleId;
};
static_assert(sizeof(EManualArgs) == 0x10);
void _SYSSwitchToEManual(EManualArgs* args)
{
// the struct has the titleId at offset 8 and standard args at 0 (total size is most likely 0x10)
cemuLog_log(LogType::Force, "SYSSwitchToEManual called. Opening the manual is not supported");
coreinit::StartBackgroundForegroundTransition();
}
void SYSSwitchToEManual()
{
EManualArgs args{};
args.titleId = coreinit::OSGetTitleID();
_SYSSwitchToEManual(&args);
}
void load()
{
cafeExportRegisterFunc(SYSClearSysArgs, "sysapp", "SYSClearSysArgs", LogType::Placeholder);
cafeExportRegisterFunc(_SYSLaunchTitleByPathFromLauncher, "sysapp", "_SYSLaunchTitleByPathFromLauncher", LogType::Placeholder);
cafeExportRegisterFunc(SYSRelaunchTitle, "sysapp", "SYSRelaunchTitle", LogType::Placeholder);
cafeExportRegister("sysapp", _SYSSwitchToEManual, LogType::Placeholder);
cafeExportRegister("sysapp", SYSSwitchToEManual, LogType::Placeholder);
}
}

View File

@ -20,6 +20,11 @@ class CafeString // fixed buffer size, null-terminated, PPC char
return true;
}
const char* c_str()
{
return (const char*)data;
}
uint8be data[N];
};