Add all the files

This commit is contained in:
Exzap 2022-08-22 22:21:23 +02:00
parent e3db07a16a
commit d60742f52b
1445 changed files with 430238 additions and 0 deletions

View file

@ -0,0 +1,28 @@
#include "Cafe/OS/common/OSCommon.h"
#include "Cafe/OS/libs/TCL/TCL.h"
namespace TCL
{
enum class TCL_SUBMISSION_FLAG : uint32
{
SURFACE_SYNC = 0x400000, // submit surface sync packet before cmd
TRIGGER_INTERRUPT = 0x200000, // probably
UKN_20000000 = 0x20000000,
};
int TCLSubmitToRing(uint32be* cmd, uint32 cmdLen, uint32be* controlFlags, uint64* submissionTimestamp)
{
// todo - figure out all the bits of *controlFlags
// if submissionTimestamp != nullptr then set it to the timestamp of the submission. Note: We should make sure that uint64's are written atomically by the GPU command processor
cemu_assert_debug(false);
return 0;
}
void Initialize()
{
cafeExportRegister("TCL", TCLSubmitToRing, LogType::Placeholder);
}
}

View file

@ -0,0 +1,4 @@
namespace TCL
{
void Initialize();
}

View file

@ -0,0 +1,40 @@
#include "Cafe/OS/common/OSCommon.h"
#include "avm.h"
namespace avm
{
bool AVMIsHDCPAvailable()
{
return true;
}
bool AVMIsHDCPOn()
{
return true;
}
bool AVMGetAnalogContentsProtectionEnable(uint32be* isEnable)
{
*isEnable = 1;
return false;
}
bool AVMIsAnalogContentsProtectionOn()
{
return true;
}
bool AVMSetAnalogContentsProtectionEnable(sint32 newState)
{
return true; // returns 1 (true) if new state was applied successfully?
}
void Initialize()
{
cafeExportRegister("avm", AVMIsHDCPAvailable, LogType::Placeholder);
cafeExportRegister("avm", AVMIsHDCPOn, LogType::Placeholder);
cafeExportRegister("avm", AVMGetAnalogContentsProtectionEnable, LogType::Placeholder);
cafeExportRegister("avm", AVMIsAnalogContentsProtectionOn, LogType::Placeholder);
cafeExportRegister("avm", AVMSetAnalogContentsProtectionEnable, LogType::Placeholder);
}
}

View file

@ -0,0 +1,5 @@
namespace avm
{
void Initialize();
}

View file

@ -0,0 +1,257 @@
#include "Common/precompiled.h"
#include "Cafe/OS/common/OSCommon.h"
#include "camera.h"
#include "Cafe/OS/RPL/rpl.h"
#include "Cafe/OS/libs/coreinit/coreinit_Alarm.h"
#include "Cafe/OS/libs/coreinit/coreinit_Time.h"
#include "Cafe/HW/Espresso/PPCCallback.h"
namespace camera
{
struct CAMInitInfo_t
{
/* +0x00 */ uint32be ukn00;
/* +0x04 */ uint32be width;
/* +0x08 */ uint32be height;
/* +0x0C */ uint32be workMemorySize;
/* +0x10 */ MEMPTR<void> workMemory;
/* +0x14 */ uint32be handlerFuncPtr;
/* +0x18 */ uint32be ukn18;
/* +0x1C */ uint32be fps;
/* +0x20 */ uint32be ukn20;
};
struct CAMTargetSurface
{
/* +0x00 */ uint32be surfaceSize;
/* +0x04 */ MEMPTR<void> surfacePtr;
/* +0x08 */ uint32be ukn08;
/* +0x0C */ uint32be ukn0C;
/* +0x10 */ uint32be ukn10;
/* +0x14 */ uint32be ukn14;
/* +0x18 */ uint32be ukn18;
/* +0x1C */ uint32be ukn1C;
};
struct CAMCallbackParam
{
// type 0 - frame decoded | field1 - imagePtr, field2 - imageSize, field3 - ukn (0)
// type 1 - ???
/* +0x0 */ uint32be type; // 0 -> Frame decoded
/* +0x4 */ uint32be field1;
/* +0x8 */ uint32be field2;
/* +0xC */ uint32be field3;
};
#define CAM_ERROR_SUCCESS 0
#define CAM_ERROR_INVALID_HANDLE -8
std::vector<struct CameraInstance*> g_table_cameraHandles;
std::vector<struct CameraInstance*> g_activeCameraInstances;
std::recursive_mutex g_mutex_camera;
std::atomic_int g_cameraCounter{ 0 };
SysAllocator<coreinit::OSAlarm_t, 1> g_alarm_camera;
SysAllocator<CAMCallbackParam, 1> g_cameraHandlerParam;
CameraInstance* GetCameraInstanceByHandle(sint32 camHandle)
{
std::unique_lock<std::recursive_mutex> _lock(g_mutex_camera);
if (camHandle <= 0)
return nullptr;
camHandle -= 1;
if (camHandle >= g_table_cameraHandles.size())
return nullptr;
return g_table_cameraHandles[camHandle];
}
struct CameraInstance
{
CameraInstance(uint32 frameWidth, uint32 frameHeight, MPTR handlerFunc) : width(frameWidth), height(frameHeight), handlerFunc(handlerFunc) { AcquireHandle(); };
~CameraInstance() { if (isOpen) { CloseCam(); } ReleaseHandle(); };
sint32 handle{ 0 };
uint32 width;
uint32 height;
bool isOpen{false};
std::queue<CAMTargetSurface> queue_targetSurfaces;
MPTR handlerFunc;
bool OpenCam()
{
if (isOpen)
return false;
isOpen = true;
g_activeCameraInstances.push_back(this);
return true;
}
bool CloseCam()
{
if (!isOpen)
return false;
isOpen = false;
vectorRemoveByValue(g_activeCameraInstances, this);
return true;
}
void QueueTargetSurface(CAMTargetSurface* targetSurface)
{
std::unique_lock<std::recursive_mutex> _lock(g_mutex_camera);
cemu_assert_debug(queue_targetSurfaces.size() < 100); // check for sane queue length
queue_targetSurfaces.push(*targetSurface);
}
private:
void AcquireHandle()
{
std::unique_lock<std::recursive_mutex> _lock(g_mutex_camera);
for (uint32 i = 0; i < g_table_cameraHandles.size(); i++)
{
if (g_table_cameraHandles[i] == nullptr)
{
g_table_cameraHandles[i] = this;
this->handle = i + 1;
return;
}
}
this->handle = (sint32)(g_table_cameraHandles.size() + 1);
g_table_cameraHandles.push_back(this);
}
void ReleaseHandle()
{
for (uint32 i = 0; i < g_table_cameraHandles.size(); i++)
{
if (g_table_cameraHandles[i] == this)
{
g_table_cameraHandles[i] = nullptr;
return;
}
}
cemu_assert_debug(false);
}
};
sint32 CAMGetMemReq(void* ukn)
{
return 1 * 1024; // always return 1KB
}
sint32 CAMCheckMemSegmentation(void* base, uint32 size)
{
return CAM_ERROR_SUCCESS; // always return success
}
void ppcCAMUpdate60(PPCInterpreter_t* hCPU)
{
// update all open camera instances
size_t numCamInstances = g_activeCameraInstances.size();
//for (auto& itr : g_activeCameraInstances)
for(size_t i=0; i<numCamInstances; i++)
{
std::unique_lock<std::recursive_mutex> _lock(g_mutex_camera);
if (i >= g_activeCameraInstances.size())
break;
CameraInstance* camInstance = g_activeCameraInstances[i];
// todo - handle 30 / 60 FPS
if (camInstance->queue_targetSurfaces.empty())
continue;
auto& targetSurface = camInstance->queue_targetSurfaces.front();
g_cameraHandlerParam->type = 0;
g_cameraHandlerParam->field1 = targetSurface.surfacePtr.GetMPTR();
g_cameraHandlerParam->field2 = targetSurface.surfaceSize;
g_cameraHandlerParam->field3 = 0;
cemu_assert_debug(camInstance->handlerFunc != MPTR_NULL);
camInstance->queue_targetSurfaces.pop();
_lock.unlock();
PPCCoreCallback(camInstance->handlerFunc, g_cameraHandlerParam.GetPtr());
}
osLib_returnFromFunction(hCPU, 0);
}
sint32 CAMInit(uint32 cameraId, CAMInitInfo_t* camInitInfo, uint32be* error)
{
CameraInstance* camInstance = new CameraInstance(camInitInfo->width, camInitInfo->height, camInitInfo->handlerFuncPtr);
std::unique_lock<std::recursive_mutex> _lock(g_mutex_camera);
if (g_cameraCounter == 0)
{
coreinit::OSCreateAlarm(g_alarm_camera.GetPtr());
coreinit::OSSetPeriodicAlarm(g_alarm_camera.GetPtr(), coreinit::coreinit_getOSTime(), (uint64)ESPRESSO_TIMER_CLOCK / 60ull, RPLLoader_MakePPCCallable(ppcCAMUpdate60));
}
g_cameraCounter++;
return camInstance->handle;
}
sint32 CAMExit(sint32 camHandle)
{
CameraInstance* camInstance = GetCameraInstanceByHandle(camHandle);
if (!camInstance)
return CAM_ERROR_INVALID_HANDLE;
CAMClose(camHandle);
delete camInstance;
std::unique_lock<std::recursive_mutex> _lock(g_mutex_camera);
g_cameraCounter--;
if (g_cameraCounter == 0)
coreinit::OSCancelAlarm(g_alarm_camera.GetPtr());
return CAM_ERROR_SUCCESS;
}
sint32 CAMOpen(sint32 camHandle)
{
CameraInstance* camInstance = GetCameraInstanceByHandle(camHandle);
if (!camInstance)
return CAM_ERROR_INVALID_HANDLE;
camInstance->OpenCam();
return CAM_ERROR_SUCCESS;
}
sint32 CAMClose(sint32 camHandle)
{
CameraInstance* camInstance = GetCameraInstanceByHandle(camHandle);
if (!camInstance)
return CAM_ERROR_INVALID_HANDLE;
camInstance->CloseCam();
return CAM_ERROR_SUCCESS;
}
sint32 CAMSubmitTargetSurface(sint32 camHandle, CAMTargetSurface* targetSurface)
{
CameraInstance* camInstance = GetCameraInstanceByHandle(camHandle);
if (!camInstance)
return CAM_ERROR_INVALID_HANDLE;
camInstance->QueueTargetSurface(targetSurface);
return CAM_ERROR_SUCCESS;
}
void reset()
{
g_cameraCounter = 0;
}
void load()
{
reset();
cafeExportRegister("camera", CAMGetMemReq, LogType::Placeholder);
cafeExportRegister("camera", CAMCheckMemSegmentation, LogType::Placeholder);
cafeExportRegister("camera", CAMInit, LogType::Placeholder);
cafeExportRegister("camera", CAMExit, LogType::Placeholder);
cafeExportRegister("camera", CAMOpen, LogType::Placeholder);
cafeExportRegister("camera", CAMClose, LogType::Placeholder);
cafeExportRegister("camera", CAMSubmitTargetSurface, LogType::Placeholder);
}
}

View file

@ -0,0 +1,10 @@
#pragma once
namespace camera
{
sint32 CAMOpen(sint32 camHandle);
sint32 CAMClose(sint32 camHandle);
void load();
};

View file

@ -0,0 +1,378 @@
#include "Cafe/OS/common/OSCommon.h"
#include "Common/SysAllocator.h"
#include "Cafe/OS/RPL/rpl.h"
#include "Cafe/OS/libs/coreinit/coreinit_Misc.h"
// includes for Initialize coreinit submodules
#include "Cafe/OS/libs/coreinit/coreinit_BSP.h"
#include "Cafe/OS/libs/coreinit/coreinit_Scheduler.h"
#include "Cafe/OS/libs/coreinit/coreinit_Atomic.h"
#include "Cafe/OS/libs/coreinit/coreinit_OverlayArena.h"
#include "Cafe/OS/libs/coreinit/coreinit_DynLoad.h"
#include "Cafe/OS/libs/coreinit/coreinit_GHS.h"
#include "Cafe/OS/libs/coreinit/coreinit_HWInterface.h"
#include "Cafe/OS/libs/coreinit/coreinit_Memory.h"
#include "Cafe/OS/libs/coreinit/coreinit_IM.h"
#include "Cafe/OS/libs/coreinit/coreinit_LockedCache.h"
#include "Cafe/OS/libs/coreinit/coreinit_MemoryMapping.h"
#include "Cafe/OS/libs/coreinit/coreinit_IPC.h"
#include "Cafe/OS/libs/coreinit/coreinit_IPCBuf.h"
#include "Cafe/OS/libs/coreinit/coreinit_Coroutine.h"
#include "Cafe/OS/libs/coreinit/coreinit_OSScreen.h"
#include "Cafe/OS/libs/coreinit/coreinit_FG.h"
#include "Cafe/OS/libs/coreinit/coreinit_SystemInfo.h"
#include "Cafe/OS/libs/coreinit/coreinit_SysHeap.h"
#include "Cafe/OS/libs/coreinit/coreinit_MCP.h"
#include "Cafe/OS/libs/coreinit/coreinit_Time.h"
#include "Cafe/OS/libs/coreinit/coreinit_Alarm.h"
#include "Cafe/OS/libs/coreinit/coreinit_CodeGen.h"
#include "Cafe/OS/libs/coreinit/coreinit_Thread.h"
#include "Cafe/OS/libs/coreinit/coreinit_MPQueue.h"
#include "Cafe/OS/libs/coreinit/coreinit_FS.h"
#include "Cafe/OS/libs/coreinit/coreinit_MEM_UnitHeap.h"
#include "Cafe/OS/libs/coreinit/coreinit_MEM_FrmHeap.h"
#include "Cafe/OS/libs/coreinit/coreinit_MEM_BlockHeap.h"
#include "Cafe/OS/libs/coreinit/coreinit_MEM_ExpHeap.h"
coreinitData_t* gCoreinitData = NULL;
sint32 ScoreStackTrace(OSThread_t* thread, MPTR sp)
{
uint32 stackMinAddr = _swapEndianU32(thread->stackEnd);
uint32 stackMaxAddr = _swapEndianU32(thread->stackBase);
sint32 score = 0;
uint32 currentStackPtr = sp;
for (sint32 i = 0; i < 50; i++)
{
uint32 nextStackPtr = memory_readU32(currentStackPtr);
if (nextStackPtr < currentStackPtr)
break;
if (nextStackPtr < stackMinAddr || nextStackPtr > stackMaxAddr)
break;
if ((nextStackPtr & 3) != 0)
break;
score += 10;
uint32 returnAddress = 0;
returnAddress = memory_readU32(nextStackPtr + 4);
//cemuLog_log(LogType::Force, fmt::format("SP {0:08x} ReturnAddress {1:08x}", nextStackPtr, returnAddress));
if (returnAddress > 0 && returnAddress < 0x10000000 && (returnAddress&3) == 0)
score += 5; // within code region
else
score -= 5;
currentStackPtr = nextStackPtr;
}
return score;
}
void DebugLogStackTrace(OSThread_t* thread, MPTR sp)
{
// sp might not point to a valid stackframe
// scan stack and evaluate which sp is most likely the beginning of the stackframe
// scan 0x400 bytes
sint32 highestScore = -1;
uint32 highestScoreSP = sp;
for (sint32 i = 0; i < 0x100; i++)
{
uint32 sampleSP = sp + i * 4;
sint32 score = ScoreStackTrace(thread, sampleSP);
if (score > highestScore)
{
highestScore = score;
highestScoreSP = sampleSP;
}
}
if (highestScoreSP != sp)
cemuLog_log(LogType::Force, fmt::format("Trace starting at SP {0:08x} r1 = {1:08x}", highestScoreSP, sp));
else
cemuLog_log(LogType::Force, fmt::format("Trace starting at SP/r1 {0:08x}", highestScoreSP));
// print stack trace
uint32 currentStackPtr = highestScoreSP;
uint32 stackMinAddr = _swapEndianU32(thread->stackEnd);
uint32 stackMaxAddr = _swapEndianU32(thread->stackBase);
for (sint32 i = 0; i < 20; i++)
{
uint32 nextStackPtr = memory_readU32(currentStackPtr);
if (nextStackPtr < currentStackPtr)
break;
if (nextStackPtr < stackMinAddr || nextStackPtr > stackMaxAddr)
break;
uint32 returnAddress = 0;
returnAddress = memory_readU32(nextStackPtr + 4);
cemuLog_log(LogType::Force, fmt::format("SP {0:08x} ReturnAddr {1:08x}", nextStackPtr, returnAddress));
currentStackPtr = nextStackPtr;
}
}
void coreinitExport_OSPanic(PPCInterpreter_t* hCPU)
{
debug_printf("OSPanic!\n");
debug_printf("File: %s:%d\n", memory_getPointerFromVirtualOffset(hCPU->gpr[3]), hCPU->gpr[4]);
debug_printf("Msg: %s\n", memory_getPointerFromVirtualOffset(hCPU->gpr[5]));
DebugLogStackTrace(coreinit::OSGetCurrentThread(), coreinit::OSGetStackPointer());
#ifndef PUBLIC_RELEASE
assert_dbg();
while (true) std::this_thread::sleep_for(std::chrono::milliseconds(100));
#endif
osLib_returnFromFunction(hCPU, 0);
}
typedef struct
{
/* +0x00 */ uint32be name;
/* +0x04 */ uint32be fileType; // 2 = font
/* +0x08 */ uint32be kernelFilenamePtr;
/* +0x0C */ MEMPTR<void> data;
/* +0x10 */ uint32be size;
/* +0x14 */ uint32be ukn14;
/* +0x18 */ uint32be ukn18;
}coreinitShareddataEntry_t;
static_assert(sizeof(coreinitShareddataEntry_t) == 0x1C, "");
uint8* extractCafeDefaultFont(sint32* size);
MPTR placeholderFont = MPTR_NULL;
sint32 placeholderFontSize = 0;
void coreinitExport_OSGetSharedData(PPCInterpreter_t* hCPU)
{
// parameters:
// r3 sharedAreaId
// r4 flags
// r5 areaPtrPtr
// r6 areaSizePtr
// on real Wii U hw/sw there is a list of shared area entries starting at offset +0xF8000000
// properly formated (each entry is 0x1C bytes) it looks like this:
// FF CA FE 01 00 00 00 02 FF E8 47 AC F8 00 00 70 00 C8 0D 4C 00 00 00 00 FF FF FF FC
// FF CA FE 02 00 00 00 02 FF E8 47 B7 F8 C8 0D C0 00 22 7E B4 00 00 00 00 00 00 11 D5
// FF CA FE 03 00 00 00 02 FF E8 47 A0 F8 EA 8C 80 00 25 44 E0 00 00 00 00 FF A0 00 00
// FF CA FE 04 00 00 00 02 FF E8 47 C2 F9 0F D1 60 00 7D 93 5C 00 00 00 00 FF FF FF FC
uint32 sharedAreaId = hCPU->gpr[3];
coreinitShareddataEntry_t* shareddataTable = (coreinitShareddataEntry_t*)memory_getPointerFromVirtualOffset(MEMORY_SHAREDDATA_AREA_ADDR);
uint32 name = 0xFFCAFE01 + sharedAreaId;
for (sint32 i = 0; i < 4; i++)
{
if ((uint32)shareddataTable[i].name == name)
{
memory_writeU32(hCPU->gpr[5], shareddataTable[i].data.GetMPTR());
memory_writeU32(hCPU->gpr[6], (uint32)shareddataTable[i].size);
osLib_returnFromFunction(hCPU, 1);
return;
}
}
// some games require a valid result or they will crash, return a pointer to our placeholder font
forceLog_printf("OSGetSharedData() called by game but no shareddata fonts loaded. Use placeholder font");
if (placeholderFont == MPTR_NULL)
{
// load and then return placeholder font
uint8* placeholderFontPtr = extractCafeDefaultFont(&placeholderFontSize);
placeholderFont = coreinit_allocFromSysArea(placeholderFontSize, 256);
if (placeholderFont == MPTR_NULL)
forceLog_printf("Failed to alloc placeholder font sys memory");
memcpy(memory_getPointerFromVirtualOffset(placeholderFont), placeholderFontPtr, placeholderFontSize);
free(placeholderFontPtr);
}
// return placeholder font
memory_writeU32(hCPU->gpr[5], placeholderFont);
memory_writeU32(hCPU->gpr[6], placeholderFontSize);
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)
{
#ifndef PUBLIC_RELEASE
forceLog_printf("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()
{
return PPCInterpreter_getCoreIndex(ppcInterpreterCurrentInstance);
}
uint32 OSGetCoreCount()
{
return Espresso::CORE_COUNT;
}
uint32 OSIsDebuggerInitialized()
{
return 0;
}
uint32 OSGetConsoleType()
{
return 0x03000050;
}
uint32 OSGetMainCoreId()
{
return 1;
}
bool OSIsMainCore()
{
return OSGetCoreId() == OSGetMainCoreId();
}
uint32 OSGetStackPointer()
{
return ppcInterpreterCurrentInstance->gpr[1];
}
void coreinitExport_ENVGetEnvironmentVariable(PPCInterpreter_t* hCPU)
{
forceLogDebug_printf("ENVGetEnvironmentVariable(\"%s\",0x08x,0x%x)\n", (char*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]), hCPU->gpr[4], hCPU->gpr[5]);
char* envKeyStr = (char*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]);
char* outputString = (char*)memory_getPointerFromVirtualOffset(hCPU->gpr[4]);
sint32 outputStringMaxLen = (sint32)hCPU->gpr[5];
// also return the string "" just in case
if (outputStringMaxLen > 0)
{
outputString[0] = '\0';
}
osLib_returnFromFunction(hCPU, 1);
}
void coreinit_exit(uint32 r)
{
forceLog_printf("coreinit.exit(%d)", r);
cemu_assert_debug(false);
// never return
while (true) std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
bool OSIsOffBoot()
{
return true;
}
uint32 OSGetBootPMFlags()
{
forceLogDebug_printf("OSGetBootPMFlags() - placeholder");
return 0;
}
uint32 OSGetSystemMode()
{
forceLogDebug_printf("OSGetSystemMode() - placeholder");
// if this returns 2, barista softlocks shortly after boot
return 0;
}
void InitializeCore()
{
cafeExportRegister("coreinit", OSGetCoreId, LogType::CoreinitThread);
cafeExportRegister("coreinit", OSGetCoreCount, LogType::CoreinitThread);
cafeExportRegister("coreinit", OSIsDebuggerInitialized, LogType::CoreinitThread);
cafeExportRegister("coreinit", OSGetConsoleType, LogType::CoreinitThread);
cafeExportRegister("coreinit", OSGetMainCoreId, LogType::CoreinitThread);
cafeExportRegister("coreinit", OSIsMainCore, LogType::CoreinitThread);
cafeExportRegister("coreinit", OSGetStackPointer, LogType::CoreinitThread);
osLib_addFunction("coreinit", "ENVGetEnvironmentVariable", coreinitExport_ENVGetEnvironmentVariable);
cafeExportRegisterFunc(coreinit_exit, "coreinit", "exit", LogType::CoreinitThread);
cafeExportRegister("coreinit", OSIsOffBoot, LogType::CoreinitThread);
cafeExportRegister("coreinit", OSGetBootPMFlags, LogType::CoreinitThread);
cafeExportRegister("coreinit", OSGetSystemMode, LogType::CoreinitThread);
}
};
void coreinit_load()
{
coreinit::InitializeCore();
coreinit::InitializeSchedulerLock();
coreinit::InitializeSysHeap();
// allocate coreinit global data
gCoreinitData = (coreinitData_t*)memory_getPointerFromVirtualOffset(coreinit_allocFromSysArea(sizeof(coreinitData_t), 32));
memset(gCoreinitData, 0x00, sizeof(coreinitData_t));
// coreinit weak links
osLib_addVirtualPointer("coreinit", "MEMAllocFromDefaultHeap", memory_getVirtualOffsetFromPointer(&gCoreinitData->MEMAllocFromDefaultHeap));
osLib_addVirtualPointer("coreinit", "MEMAllocFromDefaultHeapEx", memory_getVirtualOffsetFromPointer(&gCoreinitData->MEMAllocFromDefaultHeapEx));
osLib_addVirtualPointer("coreinit", "MEMFreeToDefaultHeap", memory_getVirtualOffsetFromPointer(&gCoreinitData->MEMFreeToDefaultHeap));
osLib_addVirtualPointer("coreinit", "__atexit_cleanup", memory_getVirtualOffsetFromPointer(&gCoreinitData->__atexit_cleanup));
osLib_addVirtualPointer("coreinit", "__stdio_cleanup", memory_getVirtualOffsetFromPointer(&gCoreinitData->__stdio_cleanup));
osLib_addVirtualPointer("coreinit", "__cpp_exception_cleanup_ptr", memory_getVirtualOffsetFromPointer(&gCoreinitData->__cpp_exception_cleanup_ptr));
osLib_addVirtualPointer("coreinit", "__cpp_exception_init_ptr", memory_getVirtualOffsetFromPointer(&gCoreinitData->__cpp_exception_init_ptr));
// init GHS and threads
coreinit::PrepareGHSRuntime();
coreinit::InitializeThread();
// reset threads
activeThreadCount = 0;
// init submodules
coreinit::InitializeMEM();
coreinit::InitializeMEMFrmHeap();
coreinit::InitializeMEMUnitHeap();
coreinit::InitializeMEMBlockHeap();
coreinit::InitializeFG();
coreinit::InitializeBSP();
coreinit::InitializeMCP();
coreinit::InitializeOverlayArena();
coreinit::InitializeDynLoad();
coreinit::InitializeGHS();
coreinit::InitializeHWInterface();
coreinit::InitializeAtomic();
coreinit::InitializeMemory();
coreinit::InitializeIM();
coreinit::InitializeLC();
coreinit::InitializeMP();
coreinit::InitializeTimeAndCalendar();
coreinit::InitializeAlarm();
coreinit::InitializeFS();
coreinit::InitializeSystemInfo();
coreinit::InitializeConcurrency();
coreinit::InitializeSpinlock();
coreinit::InitializeMessageQueue();
coreinit::InitializeIPC();
coreinit::InitializeIPCBuf();
coreinit::InitializeCodeGen();
coreinit::InitializeCoroutine();
coreinit::InitializeOSScreen();
// legacy mem stuff
coreinit::expheap_load();
// misc exports
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

@ -0,0 +1,48 @@
#pragma once
#include "Cafe/HW/Espresso/Const.h"
#define PPC_CORE_COUNT (Espresso::CORE_COUNT)
#include "Cafe/OS/libs/coreinit/coreinit_MessageQueue.h"
// async callback helper
void InitializeAsyncCallback();
void coreinitAsyncCallback_add(MPTR functionMPTR, uint32 numParameters, uint32 r3 = 0, uint32 r4 = 0, uint32 r5 = 0, uint32 r6 = 0, uint32 r7 = 0, uint32 r8 = 0, uint32 r9 = 0, uint32 r10 = 0);
void coreinitAsyncCallback_addWithLock(MPTR functionMPTR, uint32 numParameters, uint32 r3 = 0, uint32 r4 = 0, uint32 r5 = 0, uint32 r6 = 0, uint32 r7 = 0, uint32 r8 = 0, uint32 r9 = 0, uint32 r10 = 0);
// misc
void coreinit_load();
// coreinit shared memory
typedef struct
{
MEMPTR<void> MEMAllocFromDefaultHeap;
MEMPTR<void> MEMAllocFromDefaultHeapEx;
MEMPTR<void> MEMFreeToDefaultHeap;
MPTR __atexit_cleanup;
MPTR __cpp_exception_init_ptr;
MPTR __cpp_exception_cleanup_ptr;
MPTR __stdio_cleanup;
}coreinitData_t;
extern coreinitData_t* gCoreinitData;
#include "Cafe/OS/libs/coreinit/coreinit_Spinlock.h"
// coreinit init
void coreinit_start(PPCInterpreter_t* hCPU);
MPTR OSAllocFromSystem(uint32 size, uint32 alignment);
void OSFreeToSystem(MPTR mem);
// above is all the legacy stuff. New code uses namespaces
namespace coreinit
{
sint32 OSGetCoreId();
uint32 OSGetCoreCount();
uint32 OSGetStackPointer();
};

View file

@ -0,0 +1,359 @@
#include "Cafe/OS/common/OSCommon.h"
#include "Cafe/HW/Espresso/PPCCallback.h"
#include "Cafe/OS/libs/coreinit/coreinit_Thread.h"
#include "Cafe/OS/libs/coreinit/coreinit_Time.h"
#include "Cafe/OS/libs/coreinit/coreinit_Alarm.h"
#include "Cafe/HW/Espresso/Recompiler/PPCRecompiler.h"
#include "Cafe/OS/RPL/rpl.h"
// #define ALARM_LOGGING
namespace coreinit
{
SysAllocator<OSEvent> g_alarmEvent;
SysAllocator<OSThread_t> g_alarmThread;
SysAllocator<uint8, 1024 * 128> _g_alarmThreadStack;
SysAllocator<char, 32> _g_alarmThreadName;
class OSHostAlarm
{
public:
OSHostAlarm(uint64 nextFire, uint64 period, void(*callbackFunc)(uint64 currentTick, void* context), void* context) : m_nextFire(nextFire), m_period(period), m_callbackFunc(callbackFunc), m_context(context)
{
cemu_assert_debug(__OSHasSchedulerLock()); // must hold lock
auto r = g_activeAlarmList.emplace(this);
cemu_assert_debug(r.second); // check if insertion was successful
m_isActive = true;
updateEarliestAlarmAtomic();
}
~OSHostAlarm()
{
cemu_assert_debug(__OSHasSchedulerLock()); // must hold lock
if (m_isActive)
{
g_activeAlarmList.erase(g_activeAlarmList.find(this));
updateEarliestAlarmAtomic();
}
}
uint64 getFireTick() const
{
return m_nextFire;
}
void triggerAlarm(uint64 currentTick)
{
m_callbackFunc(currentTick, m_context);
}
static void updateEarliestAlarmAtomic()
{
cemu_assert_debug(__OSHasSchedulerLock());
if (!g_activeAlarmList.empty())
{
auto firstAlarm = g_activeAlarmList.begin();
g_soonestAlarm = (*firstAlarm)->m_nextFire;
}
else
{
g_soonestAlarm = std::numeric_limits<uint64>::max();
}
}
static void updateAlarms(uint64 currentTick)
{
cemu_assert_debug(__OSHasSchedulerLock());
if (g_activeAlarmList.empty())
return;
// debug begin
#ifndef PUBLIC_RELEASE
uint64 prevTick = 0;
auto itr = g_activeAlarmList.begin();
while (itr != g_activeAlarmList.end())
{
uint64 t = (*itr)->m_nextFire;
if (t < prevTick)
cemu_assert_suspicious();
prevTick = t;
++itr;
}
#endif
// debug end
while (true)
{
auto firstAlarm = g_activeAlarmList.begin();
if (currentTick >= (*firstAlarm)->m_nextFire)
{
OSHostAlarm* alarm = *firstAlarm;
g_activeAlarmList.erase(firstAlarm);
alarm->triggerAlarm(currentTick);
// if periodic alarm then requeue
if (alarm->m_period > 0)
{
alarm->m_nextFire += alarm->m_period;
g_activeAlarmList.emplace(alarm);
}
else
alarm->m_isActive = false;
updateEarliestAlarmAtomic();
}
else
break;
}
}
uint64 getNextFire() const
{
return m_nextFire;
}
static bool quickCheckForAlarm(uint64 currentTick)
{
// fast way to check if any alarm was triggered without requiring scheduler lock
return currentTick >= g_soonestAlarm;
}
public:
struct ComparatorFireTime
{
bool operator ()(OSHostAlarm* const & p1, OSHostAlarm* const & p2) const
{
auto p1Fire = p1->getNextFire();
auto p2Fire = p2->getNextFire();
if (p1Fire == p2Fire)
return (uintptr_t)p1 < (uintptr_t)p2; // if time is equal, differ by pointer (to allow storing multiple alarms with same firing time)
return p1Fire < p2Fire;
}
};
private:
uint64 m_nextFire;
uint64 m_period; // if zero then repeat is disabled
bool m_isActive{ false };
void (*m_callbackFunc)(uint64 currentTick, void* context);
void* m_context;
static std::set<class OSHostAlarm*, ComparatorFireTime> g_activeAlarmList;
static std::atomic_uint64_t g_soonestAlarm;
};
std::set<class OSHostAlarm*, OSHostAlarm::ComparatorFireTime> OSHostAlarm::g_activeAlarmList;
std::atomic_uint64_t OSHostAlarm::g_soonestAlarm{};
OSHostAlarm* OSHostAlarmCreate(uint64 nextFire, uint64 period, void(*callbackFunc)(uint64 currentTick, void* context), void* context)
{
OSHostAlarm* hostAlarm = new OSHostAlarm(nextFire, period, callbackFunc, context);
return hostAlarm;
}
void OSHostAlarmDestroy(OSHostAlarm* hostAlarm)
{
delete hostAlarm;
}
void alarm_update()
{
cemu_assert_debug(!__OSHasSchedulerLock());
uint64 currentTick = coreinit::coreinit_getOSTime();
if (!OSHostAlarm::quickCheckForAlarm(currentTick))
return;
__OSLockScheduler();
OSHostAlarm::updateAlarms(currentTick);
__OSUnlockScheduler();
}
/* alarm API */
void OSCreateAlarm(OSAlarm_t* alarm)
{
memset(alarm, 0, sizeof(OSAlarm_t));
alarm->setMagic();
}
void coreinitExport_OSCreateAlarmEx(PPCInterpreter_t* hCPU)
{
OSAlarm_t* OSAlarm = (OSAlarm_t*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]);
OSCreateAlarm(OSAlarm);
OSAlarm->name = _swapEndianU32(hCPU->gpr[4]);
osLib_returnFromFunction(hCPU, 0);
}
std::unordered_map<OSAlarm_t*, OSHostAlarm*> g_activeAlarms;
bool OSCancelAlarm(OSAlarm_t* alarm)
{
__OSLockScheduler();
bool alarmWasActive = false;
auto itr = g_activeAlarms.find(alarm);
if (itr != g_activeAlarms.end())
{
OSHostAlarmDestroy(itr->second);
g_activeAlarms.erase(itr);
alarmWasActive = true;
}
__OSUnlockScheduler();
return alarmWasActive;
}
void __OSHostAlarmTriggered(uint64 currentTick, void* context)
{
#ifdef ALARM_LOGGING
cemuLog_log(LogType::Force, "[Alarm] Alarm ready and alarm thread signalled. Current tick: {}", currentTick);
#endif
OSSignalEventInternal(g_alarmEvent.GetPtr());
}
void __OSInitiateAlarm(OSAlarm_t* alarm, uint64 startTime, uint64 period, MPTR handlerFunc, bool isPeriodic)
{
cemu_assert_debug(__OSHasSchedulerLock());
uint64 nextTime = startTime;
#ifdef ALARM_LOGGING
double periodInMS = (double)period * 1000.0 / (double)EspressoTime::GetTimerClock();
cemuLog_log(LogType::Force, "[Alarm] Start alarm 0x{:08x}. Func 0x{:08x}. Period: {}ms", MEMPTR(alarm).GetMPTR(), handlerFunc, periodInMS);
#endif
if (isPeriodic)
{
cemu_assert_debug(period != 0);
if (period == 0)
return;
uint64 currentTime = coreinit_getOSTime();
uint64 ticksSinceStart = currentTime - startTime;
uint64 numPeriods = ticksSinceStart / period;
nextTime = startTime + (numPeriods + 1ull) * period;
alarm->startTime = _swapEndianU64(startTime);
alarm->nextTime = _swapEndianU64(nextTime);
alarm->period = _swapEndianU64(period);
alarm->handler = _swapEndianU32(handlerFunc);
}
else
{
alarm->nextTime = _swapEndianU64(startTime);
alarm->period = 0;
alarm->handler = _swapEndianU32(handlerFunc);
}
auto existingAlarmItr = g_activeAlarms.find(alarm);
if (existingAlarmItr != g_activeAlarms.end())
{
// delete existing alarm
forceLogDebug_printf("__OSInitiateAlarm() called on alarm which was already active");
OSHostAlarmDestroy(existingAlarmItr->second);
g_activeAlarms.erase(existingAlarmItr);
}
g_activeAlarms[alarm] = OSHostAlarmCreate(nextTime, period, __OSHostAlarmTriggered, nullptr);
}
void OSSetAlarm(OSAlarm_t* alarm, uint64 delayInTicks, MPTR handlerFunc)
{
__OSLockScheduler();
__OSInitiateAlarm(alarm, coreinit_getOSTime() + delayInTicks, 0, handlerFunc, false);
__OSUnlockScheduler();
}
void OSSetPeriodicAlarm(OSAlarm_t* alarm, uint64 nextFire, uint64 period, MPTR handlerFunc)
{
__OSLockScheduler();
__OSInitiateAlarm(alarm, nextFire, period, handlerFunc, true);
__OSUnlockScheduler();
}
void OSSetAlarmUserData(OSAlarm_t* alarm, uint32 userData)
{
alarm->userData = _swapEndianU32(userData);
}
void coreinitExport_OSGetAlarmUserData(PPCInterpreter_t* hCPU)
{
OSAlarm_t* OSAlarmBE = (OSAlarm_t*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]);
MPTR userData = _swapEndianU32(OSAlarmBE->userData);
osLib_returnFromFunction(hCPU, userData);
}
void OSAlarm_resetAll()
{
cemu_assert_debug(g_activeAlarms.empty());
cemu_assert_debug(false);
}
void _OSAlarmThread(PPCInterpreter_t* hCPU)
{
while( true )
{
OSWaitEvent(g_alarmEvent.GetPtr());
uint64 currentTick = coreinit_getOSTime();
while (true)
{
// get alarm to fire
OSAlarm_t* alarm = nullptr;
__OSLockScheduler();
auto itr = g_activeAlarms.begin();
while(itr != g_activeAlarms.end())
{
if (currentTick >= _swapEndianU64(itr->first->nextTime))
{
alarm = itr->first;
if (alarm->period == 0)
{
// end alarm
g_activeAlarms.erase(itr);
break;
}
else
{
alarm->nextTime = _swapEndianU64(_swapEndianU64(alarm->nextTime) + _swapEndianU64(alarm->period));
}
break;
}
++itr;
}
__OSUnlockScheduler();
if (!alarm)
break;
// do callback for alarm
#ifdef ALARM_LOGGING
double periodInMS = (double)_swapEndianU64(alarm->period) * 1000.0 / (double)EspressoTime::GetTimerClock();
cemuLog_log(LogType::Force, "[Alarm] Callback 0x{:08x} for alarm 0x{:08x}. Current tick: {}. Period: {}ms", _swapEndianU32(alarm->handler), MEMPTR(alarm).GetMPTR(), currentTick, periodInMS);
#endif
PPCCoreCallback(_swapEndianU32(alarm->handler), alarm, &(g_alarmThread.GetPtr()->context));
}
}
}
void InitializeAlarm()
{
cafeExportRegister("coreinit", OSCreateAlarm, LogType::Placeholder);
cafeExportRegister("coreinit", OSCancelAlarm, LogType::Placeholder);
cafeExportRegister("coreinit", OSSetAlarm, LogType::Placeholder);
cafeExportRegister("coreinit", OSSetPeriodicAlarm, LogType::Placeholder);
cafeExportRegister("coreinit", OSSetAlarmUserData, LogType::Placeholder);
osLib_addFunction("coreinit", "OSCreateAlarmEx", coreinitExport_OSCreateAlarmEx);
osLib_addFunction("coreinit", "OSGetAlarmUserData", coreinitExport_OSGetAlarmUserData);
// init event
OSInitEvent(g_alarmEvent.GetPtr(), OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, OSEvent::EVENT_MODE::MODE_AUTO);
// create alarm callback handler thread
coreinit::OSCreateThreadType(g_alarmThread.GetPtr(), RPLLoader_MakePPCCallable(_OSAlarmThread), 0, nullptr, _g_alarmThreadStack.GetPtr() + _g_alarmThreadStack.GetByteSize(), (sint32)_g_alarmThreadStack.GetByteSize(), 0, 0x7, OSThread_t::THREAD_TYPE::TYPE_IO);
OSResumeThread(g_alarmThread.GetPtr());
strcpy(_g_alarmThreadName.GetPtr(), "Alarm Thread");
coreinit::OSSetThreadName(g_alarmThread.GetPtr(), _g_alarmThreadName.GetPtr());
}
}

View file

@ -0,0 +1,54 @@
#pragma once
#include "Cafe/OS/libs/coreinit/coreinit_Thread.h"
namespace coreinit
{
class OSHostAlarm;
OSHostAlarm* OSHostAlarmCreate(uint64 nextFire, uint64 period, void(*callbackFunc)(uint64 currentTick, void* context), void* context);
void OSHostAlarmDestroy(OSHostAlarm* hostAlarm);
struct OSAlarm_t
{
/* +0x00 */ betype<uint32> magic;
/* +0x04 */ MPTR_UINT8 name;
/* +0x08 */ uint32 ukn08;
/* +0x0C */ MPTR handler;
/* +0x10 */ uint32 ukn10;
/* +0x14 */ uint32 padding14;
/* +0x18 */ uint64 nextTime; // next fire time
/* +0x20 */ MPTR prev; // pointer to OSAlarm
/* +0x24 */ MPTR next; // pointer to OSAlarm
/* +0x28 */ uint64 period; // period (zero for non-periodic timer)
/* +0x30 */ uint64 startTime; // period start
/* +0x38 */ MPTR userData;
/* +0x3C */ uint32 ukn3C;
/* +0x40 */ OSThreadQueue uknThreadQueue;
/* +0x50 */ MPTR alarmQueue;
/* +0x54 */ MPTR ukn54;
void setMagic()
{
magic = (uint32)'aLrM';
}
bool checkMagic()
{
return magic == (uint32)'aLrM';
}
};
static_assert(sizeof(OSAlarm_t) == 0x58);
void OSCreateAlarm(OSAlarm_t* alarm);
bool OSCancelAlarm(OSAlarm_t* alarm);
void OSSetAlarm(OSAlarm_t* alarm, uint64 time, MPTR handlerFunc);
void OSSetAlarmUserData(OSAlarm_t* alarm, uint32 userData);
void OSSetPeriodicAlarm(OSAlarm_t* OSAlarm, uint64 startTick, uint64 periodTick, MPTR OSAlarmHandler);
void OSAlarm_resetAll();
void alarm_update();
void InitializeAlarm();
}

View file

@ -0,0 +1,139 @@
#include "Cafe/OS/common/OSCommon.h"
#include <atomic>
#include "coreinit_Atomic.h"
namespace coreinit
{
/* 32bit atomic operations */
uint32 OSSwapAtomic(std::atomic<uint32be>* mem, uint32 newValue)
{
uint32be _newValue = newValue;
uint32be previousValue = mem->exchange(_newValue);
return previousValue;
}
bool OSCompareAndSwapAtomic(std::atomic<uint32be>* mem, uint32 compareValue, uint32 swapValue)
{
// seen in GTA3 homebrew port
uint32be _compareValue = compareValue;
uint32be _swapValue = swapValue;
return mem->compare_exchange_strong(_compareValue, _swapValue);
}
bool OSCompareAndSwapAtomicEx(std::atomic<uint32be>* mem, uint32 compareValue, uint32 swapValue, uint32be* previousValue)
{
// seen in GTA3 homebrew port
uint32be _compareValue = compareValue;
uint32be _swapValue = swapValue;
bool r = mem->compare_exchange_strong(_compareValue, _swapValue);
*previousValue = _compareValue;
return r;
}
uint32 OSAddAtomic(std::atomic<uint32be>* mem, uint32 adder)
{
uint32be knownValue;
while (true)
{
uint32be knownValue = mem->load();
uint32be newValue = knownValue + adder;
if (mem->compare_exchange_strong(knownValue, newValue))
break;
}
return knownValue;
}
/* 64bit atomic operations */
uint64 OSSwapAtomic64(std::atomic<uint64be>* mem, uint64 newValue)
{
uint64be _newValue = newValue;
uint64be previousValue = mem->exchange(_newValue);
return previousValue;
}
uint64 OSSetAtomic64(std::atomic<uint64be>* mem, uint64 newValue)
{
return OSSwapAtomic64(mem, newValue);
}
uint64 OSGetAtomic64(std::atomic<uint64be>* mem)
{
return mem->load();
}
uint64 OSAddAtomic64(std::atomic<uint64be>* mem, uint64 adder)
{
uint64be knownValue;
while (true)
{
uint64be knownValue = mem->load();
uint64be newValue = knownValue + adder;
if (mem->compare_exchange_strong(knownValue, newValue))
break;
}
return knownValue;
}
uint64 OSAndAtomic64(std::atomic<uint64be>* mem, uint64 val)
{
uint64be knownValue;
while (true)
{
uint64be knownValue = mem->load();
uint64be newValue = knownValue & val;
if (mem->compare_exchange_strong(knownValue, newValue))
break;
}
return knownValue;
}
uint64 OSOrAtomic64(std::atomic<uint64be>* mem, uint64 val)
{
uint64be knownValue;
while (true)
{
uint64be knownValue = mem->load();
uint64be newValue = knownValue | val;
if (mem->compare_exchange_strong(knownValue, newValue))
break;
}
return knownValue;
}
bool OSCompareAndSwapAtomic64(std::atomic<uint64be>* mem, uint64 compareValue, uint64 swapValue)
{
uint64be _compareValue = compareValue;
uint64be _swapValue = swapValue;
return mem->compare_exchange_strong(_compareValue, _swapValue);
}
bool OSCompareAndSwapAtomicEx64(std::atomic<uint64be>* mem, uint64 compareValue, uint64 swapValue, uint64be* previousValue)
{
uint64be _compareValue = compareValue;
uint64be _swapValue = swapValue;
bool r = mem->compare_exchange_strong(_compareValue, _swapValue);
*previousValue = _compareValue;
return r;
}
void InitializeAtomic()
{
// 32bit atomic operations
cafeExportRegister("coreinit", OSSwapAtomic, LogType::Placeholder);
cafeExportRegister("coreinit", OSCompareAndSwapAtomic, LogType::Placeholder);
cafeExportRegister("coreinit", OSCompareAndSwapAtomicEx, LogType::Placeholder);
cafeExportRegister("coreinit", OSAddAtomic, LogType::Placeholder);
// 64bit atomic operations
cafeExportRegister("coreinit", OSSetAtomic64, LogType::Placeholder);
cafeExportRegister("coreinit", OSGetAtomic64, LogType::Placeholder);
cafeExportRegister("coreinit", OSSwapAtomic64, LogType::Placeholder);
cafeExportRegister("coreinit", OSAddAtomic64, LogType::Placeholder);
cafeExportRegister("coreinit", OSAndAtomic64, LogType::Placeholder);
cafeExportRegister("coreinit", OSOrAtomic64, LogType::Placeholder);
cafeExportRegister("coreinit", OSCompareAndSwapAtomic64, LogType::Placeholder);
cafeExportRegister("coreinit", OSCompareAndSwapAtomicEx64, LogType::Placeholder);
}
}

View file

@ -0,0 +1,21 @@
#pragma once
#include <atomic>
namespace coreinit
{
uint32 OSSwapAtomic(std::atomic<uint32be>* mem, uint32 newValue);
bool OSCompareAndSwapAtomic(std::atomic<uint32be>* mem, uint32 compareValue, uint32 swapValue);
bool OSCompareAndSwapAtomicEx(std::atomic<uint32be>* mem, uint32 compareValue, uint32 swapValue, uint32be* previousValue);
uint32 OSAddAtomic(std::atomic<uint32be>* mem, uint32 adder);
uint64 OSSwapAtomic64(std::atomic<uint64be>* mem, uint64 newValue);
uint64 OSSetAtomic64(std::atomic<uint64be>* mem, uint64 newValue);
uint64 OSGetAtomic64(std::atomic<uint64be>* mem);
uint64 OSAddAtomic64(std::atomic<uint64be>* mem, uint64 adder);
uint64 OSAndAtomic64(std::atomic<uint64be>* mem, uint64 val);
uint64 OSOrAtomic64(std::atomic<uint64be>* mem, uint64 val);
bool OSCompareAndSwapAtomic64(std::atomic<uint64be>* mem, uint64 compareValue, uint64 swapValue);
bool OSCompareAndSwapAtomicEx64(std::atomic<uint64be>* mem, uint64 compareValue, uint64 swapValue, uint64be* previousValue);
void InitializeAtomic();
}

View file

@ -0,0 +1,19 @@
#include "Cafe/OS/common/OSCommon.h"
#include "coreinit_BSP.h"
namespace coreinit
{
bool bspGetHardwareVersion(uint32be* version)
{
uint8 highVersion = 0x11; // anything below 0x11 will be considered as Hollywood
// todo: Check version returned on console
uint32 tempVers = highVersion << 24;
*version = tempVers;
return true;
}
void InitializeBSP()
{
cafeExportRegister("coreinit", bspGetHardwareVersion, LogType::Placeholder);
}
}

View file

@ -0,0 +1,4 @@
namespace coreinit
{
void InitializeBSP();
};

View file

@ -0,0 +1,118 @@
#pragma once
#include "Cafe/OS/libs/coreinit/coreinit_Thread.h"
#include "util/helpers/fspinlock.h"
struct CoreinitAsyncCallback
{
CoreinitAsyncCallback(MPTR functionMPTR, uint32 numParameters, uint32 r3, uint32 r4, uint32 r5, uint32 r6, uint32 r7, uint32 r8, uint32 r9, uint32 r10) :
m_functionMPTR(functionMPTR), m_numParameters(numParameters), m_gprParam{ r3, r4, r5, r6, r7, r8, r9, r10 } {};
static void queue(MPTR functionMPTR, uint32 numParameters, uint32 r3, uint32 r4, uint32 r5, uint32 r6, uint32 r7, uint32 r8, uint32 r9, uint32 r10)
{
s_asyncCallbackSpinlock.acquire();
s_asyncCallbackQueue.emplace_back(allocateAndInitFromPool(functionMPTR, numParameters, r3, r4, r5, r6, r7, r8, r9, r10));
s_asyncCallbackSpinlock.release();
}
static void callNextFromQueue()
{
s_asyncCallbackSpinlock.acquire();
if (s_asyncCallbackQueue.empty())
{
cemuLog_log(LogType::Force, "AsyncCallbackQueue is empty. Unexpected behavior");
s_asyncCallbackSpinlock.release();
return;
}
CoreinitAsyncCallback* cb = s_asyncCallbackQueue[0];
s_asyncCallbackQueue.erase(s_asyncCallbackQueue.begin());
s_asyncCallbackSpinlock.release();
cb->doCall();
s_asyncCallbackSpinlock.acquire();
releaseToPool(cb);
s_asyncCallbackSpinlock.release();
}
private:
void doCall()
{
PPCCoreCallback(m_functionMPTR, m_gprParam[0], m_gprParam[1], m_gprParam[2], m_gprParam[3], m_gprParam[4], m_gprParam[5], m_gprParam[6], m_gprParam[7]);
}
static CoreinitAsyncCallback* allocateAndInitFromPool(MPTR functionMPTR, uint32 numParameters, uint32 r3, uint32 r4, uint32 r5, uint32 r6, uint32 r7, uint32 r8, uint32 r9, uint32 r10)
{
cemu_assert_debug(s_asyncCallbackSpinlock.isHolding());
if (s_asyncCallbackPool.empty())
{
CoreinitAsyncCallback* cb = new CoreinitAsyncCallback(functionMPTR, numParameters, r3, r4, r5, r6, r7, r8, r9, r10);
return cb;
}
CoreinitAsyncCallback* cb = s_asyncCallbackPool[0];
s_asyncCallbackPool.erase(s_asyncCallbackPool.begin());
*cb = CoreinitAsyncCallback(functionMPTR, numParameters, r3, r4, r5, r6, r7, r8, r9, r10);
return cb;
}
static void releaseToPool(CoreinitAsyncCallback* cb)
{
cemu_assert_debug(s_asyncCallbackSpinlock.isHolding());
s_asyncCallbackPool.emplace_back(cb);
}
static std::vector<struct CoreinitAsyncCallback*> s_asyncCallbackPool;
static std::vector<struct CoreinitAsyncCallback*> s_asyncCallbackQueue;
static FSpinlock s_asyncCallbackSpinlock;
sint32 m_numParameters;
uint32 m_gprParam[9];
MPTR m_functionMPTR;
};
std::vector<struct CoreinitAsyncCallback*> CoreinitAsyncCallback::s_asyncCallbackPool;
std::vector<struct CoreinitAsyncCallback*> CoreinitAsyncCallback::s_asyncCallbackQueue;
FSpinlock CoreinitAsyncCallback::s_asyncCallbackSpinlock;
SysAllocator<OSThread_t> g_coreinitCallbackThread;
SysAllocator<uint8, 1024*64> _g_coreinitCallbackThreadStack;
SysAllocator<coreinit::OSSemaphore> g_asyncCallbackAsync;
SysAllocator<char, 32> _g_coreinitCBThreadName;
void _coreinitCallbackThread(PPCInterpreter_t* hCPU)
{
while (coreinit::OSWaitSemaphore(g_asyncCallbackAsync.GetPtr()))
{
CoreinitAsyncCallback::callNextFromQueue();
}
}
void coreinitAsyncCallback_addWithLock(MPTR functionMPTR, uint32 numParameters, uint32 r3, uint32 r4, uint32 r5, uint32 r6, uint32 r7, uint32 r8, uint32 r9, uint32 r10)
{
cemu_assert_debug(numParameters <= 8);
if (functionMPTR >= 0x10000000)
{
cemuLog_log(LogType::Force, fmt::format("Suspicious callback address {0:08x} params: {1:08x} {2:08x} {3:08x} {4:08x}", functionMPTR, r3, r4, r5, r6));
cemuLog_waitForFlush();
}
CoreinitAsyncCallback::queue(functionMPTR, numParameters, r3, r4, r5, r6, r7, r8, r9, r10);
__OSLockScheduler();
coreinit::OSSignalSemaphoreInternal(g_asyncCallbackAsync.GetPtr(), false);
__OSUnlockScheduler();
}
void coreinitAsyncCallback_add(MPTR functionMPTR, uint32 numParameters, uint32 r3, uint32 r4, uint32 r5, uint32 r6, uint32 r7, uint32 r8, uint32 r9, uint32 r10)
{
cemu_assert_debug(__OSHasSchedulerLock() == false); // do not call when holding scheduler lock
coreinitAsyncCallback_addWithLock(functionMPTR, numParameters, r3, r4, r5, r6, r7, r8, r9, r10);
}
void InitializeAsyncCallback()
{
coreinit::OSInitSemaphore(g_asyncCallbackAsync.GetPtr(), 0);
coreinit::OSCreateThreadType(g_coreinitCallbackThread.GetPtr(), PPCInterpreter_makeCallableExportDepr(_coreinitCallbackThread), 0, nullptr, _g_coreinitCallbackThreadStack.GetPtr() + _g_coreinitCallbackThreadStack.GetByteSize(), (sint32)_g_coreinitCallbackThreadStack.GetByteSize(), 0, 7, OSThread_t::THREAD_TYPE::TYPE_IO);
coreinit::OSResumeThread(g_coreinitCallbackThread.GetPtr());
strcpy(_g_coreinitCBThreadName.GetPtr(), "Callback Thread");
coreinit::OSSetThreadName(g_coreinitCallbackThread.GetPtr(), _g_coreinitCBThreadName.GetPtr());
}

View file

@ -0,0 +1,148 @@
#include "Cafe/OS/common/OSCommon.h"
#include "Cafe/OS/RPL/rpl.h"
#include "Cafe/OS/libs/coreinit/coreinit_CodeGen.h"
#include "Cafe/HW/Espresso/Recompiler/PPCRecompiler.h"
#include "Common/ExceptionHandler/ExceptionHandler.h"
namespace coreinit
{
struct
{
bool rangeIsAllocated;
MPTR rangeStart;
uint32 rangeSize;
uint8* cacheStateCopy; // holds a copy of the entire range, simulates icache state (updated via ICBI)
}coreinitCodeGen = {0};
void codeGenArea_memoryWriteCallback(void* pageStart, size_t size)
{
uint32 ea = memory_getVirtualOffsetFromPointer(pageStart);
uint32 eaEnd = ea + (uint32)size;
while (ea <= eaEnd)
{
codeGenHandleICBI(ea);
ea += 0x20;
}
}
void OSGetCodegenVirtAddrRange(betype<uint32>* rangeStart, betype<uint32>* rangeSize)
{
uint32 codegenSize = 0x01000000; // todo: Read from cos.xml
//debug_printf("OSGetCodegenVirtAddrRange(0x%08x,0x%08x)\n", hCPU->gpr[3], hCPU->gpr[4]);
// on first call, allocate range
if( coreinitCodeGen.rangeIsAllocated == false )
{
coreinitCodeGen.rangeStart = RPLLoader_AllocateCodeSpace(codegenSize, 0x1000);
coreinitCodeGen.rangeSize = codegenSize;
coreinitCodeGen.cacheStateCopy = new uint8[codegenSize];
memset(coreinitCodeGen.cacheStateCopy, 0, codegenSize);
coreinitCodeGen.rangeIsAllocated = true;
}
*rangeStart = coreinitCodeGen.rangeStart;
*rangeSize = coreinitCodeGen.rangeSize;
}
void OSGetCodegenVirtAddrRangeInternal(uint32& rangeStart, uint32& rangeSize)
{
if (coreinitCodeGen.rangeIsAllocated == 0)
{
rangeStart = 0;
rangeSize = 0;
return;
}
rangeStart = coreinitCodeGen.rangeStart;
rangeSize = coreinitCodeGen.rangeSize;
}
void ICInvalidateRange(uint32 startAddress, uint32 size)
{
uint32 ea = startAddress & ~0x1F;
uint32 eaEnd = (startAddress + size + 0x1F)&~0x1F;
while (ea <= eaEnd)
{
codeGenHandleICBI(ea);
ea += 0x20;
}
}
void coreinitExport_OSCodegenCopy(PPCInterpreter_t* hCPU)
{
if( coreinitCodeGen.rangeIsAllocated == false )
assert_dbg();
uint32 codeAddrDest = hCPU->gpr[3];
uint32 memAddrSrc = hCPU->gpr[4];
uint32 size = hCPU->gpr[5];
if( codeAddrDest < coreinitCodeGen.rangeStart || codeAddrDest >= (coreinitCodeGen.rangeStart+coreinitCodeGen.rangeSize) )
assert_dbg();
if( (codeAddrDest+size) < coreinitCodeGen.rangeStart || (codeAddrDest+size) > (coreinitCodeGen.rangeStart+coreinitCodeGen.rangeSize) )
assert_dbg();
memcpy(memory_getPointerFromVirtualOffset(codeAddrDest), memory_getPointerFromVirtualOffset(memAddrSrc), size);
// invalidate recompiler range
uint32 ea = codeAddrDest & ~0x1F;
uint32 eaEnd = (codeAddrDest + size + 0x1F)&~0x1F;
while (ea <= eaEnd)
{
codeGenHandleICBI(ea);
ea += 0x20;
}
osLib_returnFromFunction(hCPU, 0);
}
void codeGenHandleICBI(uint32 ea)
{
cemu_assert_debug((ea & 0x1F) == 0);
if (coreinitCodeGen.rangeIsAllocated == false)
return;
cemu_assert_debug((coreinitCodeGen.rangeStart & 0x1F) == 0);
cemu_assert_debug((coreinitCodeGen.rangeSize & 0x1F) == 0);
if (ea >= coreinitCodeGen.rangeStart && ea < (coreinitCodeGen.rangeStart + coreinitCodeGen.rangeSize))
{
uint8* cacheCopy = coreinitCodeGen.cacheStateCopy + (ea - coreinitCodeGen.rangeStart);
uint8* currentState = memory_getPointerFromVirtualOffset(ea);
if (memcmp(currentState, cacheCopy, 32) != 0)
{
// instructions changed
// flush cache
PPCRecompiler_invalidateRange(ea, ea+0x20);
// update icache copy
memcpy(cacheCopy, currentState, 32);
}
}
}
bool _avoidCodeGenJIT = false;
// currently we dont handle code invalidation well for direct write access
// therefore if we detect attempts to write we will disable JITing the area
bool codeGenShouldAvoid()
{
return _avoidCodeGenJIT;
}
bool OSSwitchSecCodeGenMode(bool isRXOnly)
{
if (!_avoidCodeGenJIT)
{
forceLog_printf("Disable JIT on dynamic code area");
}
_avoidCodeGenJIT = true; // this function getting called is usually a sign that
// does this have a return value?
return true;
}
void InitializeCodeGen()
{
cafeExportRegister("coreinit", OSGetCodegenVirtAddrRange, LogType::Placeholder);
cafeExportRegister("coreinit", ICInvalidateRange, LogType::Placeholder);
osLib_addFunction("coreinit", "OSCodegenCopy", coreinitExport_OSCodegenCopy);
cafeExportRegister("coreinit", OSSwitchSecCodeGenMode, LogType::Placeholder);
}
}

View file

@ -0,0 +1,10 @@
#pragma once
namespace coreinit
{
void OSGetCodegenVirtAddrRangeInternal(uint32& rangeStart, uint32& rangeSize);
void codeGenHandleICBI(uint32 ea);
bool codeGenShouldAvoid();
void InitializeCodeGen();
}

View file

@ -0,0 +1,100 @@
#include "Cafe/OS/common/OSCommon.h"
#include "Cafe/OS/libs/coreinit/coreinit_Coroutine.h"
#include "Cafe/HW/Espresso/PPCState.h"
#include "Cafe/HW/Espresso/Interpreter/PPCInterpreterInternal.h"
#include "Cafe/HW/MMU/MMU.h"
namespace coreinit
{
void coreinitExport_OSInitCoroutine(PPCInterpreter_t* hCPU)
{
OSCoroutine* coroutine = (OSCoroutine*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]);
coroutine->lr = _swapEndianU32(hCPU->gpr[4]);
coroutine->r1 = _swapEndianU32(hCPU->gpr[5]);
osLib_returnFromFunction(hCPU, 0);
}
void coreinitCoroutine_OSSaveCoroutine(OSCoroutine* coroutine, PPCInterpreter_t* hCPU)
{
coroutine->lr = _swapEndianU32(hCPU->spr.LR);
coroutine->cr = _swapEndianU32(ppc_getCR(hCPU));
coroutine->gqr1 = _swapEndianU32(hCPU->spr.UGQR[1]);
coroutine->r1 = _swapEndianU32(hCPU->gpr[1]);
coroutine->r2 = _swapEndianU32(hCPU->gpr[2]);
coroutine->r13 = _swapEndianU32(hCPU->gpr[13]);
for (sint32 i = 14; i < 32; i++)
coroutine->gpr[i - 14] = _swapEndianU32(hCPU->gpr[i]);
for (sint32 i = 14; i < 32; i++)
{
coroutine->fpr[i - 14] = _swapEndianU64(hCPU->fpr[i].fp0int);
}
for (sint32 i = 14; i < 32; i++)
{
coroutine->psr[i - 14] = _swapEndianU64(hCPU->fpr[i].fp1int);
}
}
void coreinitCoroutine_OSLoadCoroutine(OSCoroutine* coroutine, PPCInterpreter_t* hCPU)
{
hCPU->spr.LR = _swapEndianU32(coroutine->lr);
ppc_setCR(hCPU, _swapEndianU32(coroutine->cr));
hCPU->spr.UGQR[1] = _swapEndianU32(coroutine->gqr1);
hCPU->gpr[1] = _swapEndianU32(coroutine->r1);
hCPU->gpr[2] = _swapEndianU32(coroutine->r2);
hCPU->gpr[13] = _swapEndianU32(coroutine->r13);
for (sint32 i = 14; i < 32; i++)
hCPU->gpr[i] = _swapEndianU32(coroutine->gpr[i - 14]);
for (sint32 i = 14; i < 32; i++)
{
hCPU->fpr[i].fp0int = _swapEndianU64(coroutine->fpr[i - 14]);
}
for (sint32 i = 14; i < 32; i++)
{
hCPU->fpr[i].fp1int = _swapEndianU64(coroutine->psr[i - 14]);
}
}
void coreinitExport_OSSwitchCoroutine(PPCInterpreter_t* hCPU)
{
OSCoroutine* coroutineCurrent = (OSCoroutine*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]);
OSCoroutine* coroutineNext = (OSCoroutine*)memory_getPointerFromVirtualOffsetAllowNull(hCPU->gpr[4]);
coreinitCoroutine_OSSaveCoroutine(coroutineCurrent, hCPU);
if (coroutineNext != NULL)
{
coreinitCoroutine_OSLoadCoroutine(coroutineNext, hCPU);
}
osLib_returnFromFunction(hCPU, 0);
}
void coreinitExport_OSSwitchFiberEx(PPCInterpreter_t* hCPU)
{
ppcDefineParamU32(param0, 0);
ppcDefineParamU32(param1, 1);
ppcDefineParamU32(param2, 2);
ppcDefineParamU32(param3, 3);
ppcDefineParamMPTR(newInstructionPointer, 4);
ppcDefineParamMPTR(newStackPointer, 5);
MPTR prevStackpointer = hCPU->gpr[1];
hCPU->gpr[1] = newStackPointer;
hCPU->gpr[3] = param0;
hCPU->gpr[4] = param1;
hCPU->gpr[5] = param2;
hCPU->gpr[6] = param3;
PPCCore_executeCallbackInternal(newInstructionPointer);
uint32 returnValue = hCPU->gpr[3];
hCPU->gpr[1] = prevStackpointer;
osLib_returnFromFunction(hCPU, returnValue);
}
void InitializeCoroutine()
{
osLib_addFunction("coreinit", "OSInitCoroutine", coreinitExport_OSInitCoroutine);
osLib_addFunction("coreinit", "OSSwitchCoroutine", coreinitExport_OSSwitchCoroutine);
osLib_addFunction("coreinit", "OSSwitchFiberEx", coreinitExport_OSSwitchFiberEx);
}
}

View file

@ -0,0 +1,21 @@
#pragma once
namespace coreinit
{
struct OSCoroutine
{
/* +0x00 */ uint32 lr;
/* +0x04 */ uint32 cr;
/* +0x08 */ uint32 gqr1;
/* +0x0C */ uint32 r1; // stack pointer
/* +0x10 */ uint32 r2;
/* +0x14 */ uint32 r13;
/* +0x18 */ uint32 gpr[18];
uint64 fpr[18];
uint64 psr[18];
};
static_assert(sizeof(OSCoroutine) == 0x180);
void InitializeCoroutine();
}

View file

@ -0,0 +1,147 @@
#include "Cafe/OS/common/OSCommon.h"
#include "Cafe/HW/Espresso/PPCCallback.h"
#include "Cafe/OS/RPL/rpl.h"
#include "Cafe/OS/libs/coreinit/coreinit_DynLoad.h"
#include "Cafe/OS/libs/coreinit/coreinit_MEM.h"
namespace coreinit
{
MPTR _osDynLoadFuncAlloc = MPTR_NULL;
MPTR _osDynLoadFuncFree = MPTR_NULL;
MPTR _osDynLoadTLSFuncAlloc = MPTR_NULL;
MPTR _osDynLoadTLSFuncFree = MPTR_NULL;
uint32 OSDynLoad_SetAllocator(MPTR allocFunc, MPTR freeFunc)
{
_osDynLoadFuncAlloc = allocFunc;
_osDynLoadFuncFree = freeFunc;
return 0;
}
void OSDynLoad_SetTLSAllocator(MPTR allocFunc, MPTR freeFunc)
{
_osDynLoadTLSFuncAlloc = allocFunc;
_osDynLoadTLSFuncFree = freeFunc;
}
uint32 OSDynLoad_GetAllocator(betype<MPTR>* funcAlloc, betype<MPTR>* funcFree)
{
*funcAlloc = _osDynLoadFuncAlloc;
*funcFree = _osDynLoadFuncFree;
return 0;
}
void OSDynLoad_GetTLSAllocator(betype<MPTR>* funcAlloc, betype<MPTR>* funcFree)
{
*funcAlloc = _osDynLoadTLSFuncAlloc;
*funcFree = _osDynLoadTLSFuncFree;
}
void* OSDynLoad_AllocatorAlloc(sint32 size, sint32 alignment)
{
if (_osDynLoadFuncAlloc == MPTR_NULL)
return MPTR_NULL;
StackAllocator<MEMPTR<void>> _ptrStorage;
int r = PPCCoreCallback(_osDynLoadFuncAlloc, size, alignment, _ptrStorage.GetMPTR());
if (r != 0)
{
cemu_assert_debug(false);
return MPTR_NULL;
}
return _ptrStorage->GetPtr();
}
void OSDynLoad_AllocatorFree(void* mem)
{
if (_osDynLoadFuncFree == MPTR_NULL)
return;
MEMPTR<void> _mem = mem;
PPCCoreCallback(_osDynLoadFuncFree, _mem);
}
uint32 OSDynLoad_Acquire(const char* libName, uint32be* moduleHandleOut)
{
// truncate path
sint32 fileNameStartIndex = 0;
sint32 tempLen = (sint32)strlen(libName);
for (sint32 i = tempLen - 1; i >= 0; i--)
{
if (libName[i] == '/')
{
fileNameStartIndex = i + 1;
break;
}
}
// truncate file extension
char tempLibName[512];
strcpy(tempLibName, libName + fileNameStartIndex);
tempLen = (sint32)strlen(tempLibName);
for (sint32 i = tempLen - 1; i >= 0; i--)
{
if (tempLibName[i] == '.')
{
tempLibName[i] = '\0';
break;
}
}
// search for loaded modules with matching name
uint32 rplHandle = RPLLoader_GetHandleByModuleName(libName);
if (rplHandle == RPL_INVALID_HANDLE)
{
RPLLoader_AddDependency(libName);
RPLLoader_UpdateDependencies();
RPLLoader_Link();
RPLLoader_CallEntrypoints();
rplHandle = RPLLoader_GetHandleByModuleName(libName);
if (rplHandle == RPL_INVALID_HANDLE)
rplHandle = 0;
}
*moduleHandleOut = rplHandle;
// return module not found error code
if (rplHandle == RPL_INVALID_HANDLE)
return 0xFFFCFFE9;
return 0;
}
uint32 OSDynLoad_Release(uint32 moduleHandle)
{
if (moduleHandle == RPL_INVALID_HANDLE)
return 0;
RPLLoader_RemoveDependency(moduleHandle);
RPLLoader_UpdateDependencies();
// this function isn't supposed to return anything, but early versions of Cemu did and Cemuhook (up to 0.5.7.6) now relies on it. We still keep the return value around for compatibility
return 0;
}
uint32 OSDynLoad_FindExport(uint32 moduleHandle, uint32 isData, const char* exportName, betype<MPTR>* addrOut)
{
if (moduleHandle == 0xFFFFFFFF)
{
// main module
// Assassins Creed 4 has this handle hardcoded
moduleHandle = RPLLoader_GetMainModuleHandle();
}
MPTR exportResult = RPLLoader_FindModuleOrHLEExport(moduleHandle, isData, exportName);
*addrOut = exportResult;
if (exportResult == MPTR_NULL)
return 0xFFFFFFFF;
return 0;
}
void InitializeDynLoad()
{
cafeExportRegister("coreinit", OSDynLoad_SetAllocator, LogType::Placeholder);
cafeExportRegister("coreinit", OSDynLoad_SetTLSAllocator, LogType::Placeholder);
cafeExportRegister("coreinit", OSDynLoad_GetAllocator, LogType::Placeholder);
cafeExportRegister("coreinit", OSDynLoad_GetTLSAllocator, LogType::Placeholder);
cafeExportRegister("coreinit", OSDynLoad_Acquire, LogType::Placeholder);
cafeExportRegister("coreinit", OSDynLoad_Release, LogType::Placeholder);
cafeExportRegister("coreinit", OSDynLoad_FindExport, LogType::Placeholder);
}
}

View file

@ -0,0 +1,18 @@
#pragma once
namespace coreinit
{
uint32 OSDynLoad_SetAllocator(MPTR allocFunc, MPTR freeFunc);
void OSDynLoad_SetTLSAllocator(MPTR allocFunc, MPTR freeFunc);
uint32 OSDynLoad_GetAllocator(betype<MPTR>* funcAlloc, betype<MPTR>* funcFree);
void OSDynLoad_GetTLSAllocator(betype<MPTR>* funcAlloc, betype<MPTR>* funcFree);
void* OSDynLoad_AllocatorAlloc(sint32 size, sint32 alignment);
void OSDynLoad_AllocatorFree(void* mem);
uint32 OSDynLoad_Acquire(const char* libName, uint32be* moduleHandleOut);
uint32 OSDynLoad_Release(uint32 moduleHandle);
uint32 OSDynLoad_FindExport(uint32 moduleHandle, uint32 isData, const char* exportName, betype<MPTR>* addrOut);
void InitializeDynLoad();
}

View file

@ -0,0 +1,200 @@
#include "Cafe/OS/common/OSCommon.h"
#include <memory>
#define FG_BUCKET_AREA_FREE 0 // free area available game
#define FG_BUCKET_AREA_AUDIO_TRANSITION 1 // transition audio buffer
#define FG_BUCKET_AREA2 2 // frame storage? TV?
#define FG_BUCKET_AREA3 3 // frame storage? DRC?
#define FG_BUCKET_AREA4 4 // frame storage? TV?
#define FG_BUCKET_AREA5 5 // frame storage? DRC?
#define FG_BUCKET_AREA_SAVE 6
#define FG_BUCKET_AREA_COPY 7 // for OS copy data (clipboard and title switch parameters)
#define FG_BUCKET_AREA_FREE_SIZE 0x2800000
#define FG_BUCKET_AREA_SAVE_SIZE 0x1000
#define FG_BUCKET_AREA_COPY_SIZE 0x400000
#define FG_BUCKET_AREA_COUNT 8
namespace coreinit
{
MEMPTR<void> fgAddr = nullptr; // NULL if not in foreground
MEMPTR<uint8> fgSaveAreaAddr = nullptr;
struct
{
uint32 id;
uint32 startOffset;
uint32 size;
}fgAreaEntries[FG_BUCKET_AREA_COUNT] =
{
{ 0, 0, 0x2800000 },
{ 7, 0x2800000, 0x400000 },
{ 1, 0x2C00000, 0x900000 },
{ 2, 0x3500000, 0x3C0000 },
{ 3, 0x38C0000, 0x1C0000 },
{ 4, 0x3A80000, 0x3C0000 },
{ 5, 0x3E40000, 0x1BF000 },
{ 6, 0x3FFF000, 0x1000 }
};
MEMPTR<uint8> GetFGMemByArea(uint32 areaId)
{
if (fgAddr == nullptr)
return nullptr;
for (sint32 i = 0; i < FG_BUCKET_AREA_COUNT; i++)
{
if (fgAreaEntries[i].id == areaId)
return MEMPTR<uint8>(fgAddr.GetPtr<uint8>() + fgAreaEntries[i].startOffset);
}
return nullptr;
}
bool OSGetForegroundBucket(MEMPTR<void>* offset, uint32be* size)
{
// return full size of foreground bucket area
if (offset)
*offset = MEMPTR<void>{ (uint32)MEMORY_FGBUCKET_AREA_ADDR };
if (size)
*size = MEMORY_FGBUCKET_AREA_SIZE;
// return true if in foreground
return true;
}
bool OSGetForegroundBucketFreeArea(MPTR* offset, MPTR* size)
{
uint8* freeAreaAddr = GetFGMemByArea(FG_BUCKET_AREA_FREE).GetPtr();
*offset = _swapEndianU32(memory_getVirtualOffsetFromPointer(freeAreaAddr));
*size = _swapEndianU32(FG_BUCKET_AREA_FREE_SIZE);
// return true if in foreground
return (fgAddr != nullptr);
}
void coreinitExport_OSGetForegroundBucket(PPCInterpreter_t* hCPU)
{
//debug_printf("OSGetForegroundBucket(0x%x,0x%x)\n", hCPU->gpr[3], hCPU->gpr[4]);
// returns the whole FG bucket area (if the current process is in the foreground)
ppcDefineParamMPTR(areaOutput, 0);
ppcDefineParamMPTR(areaSize, 1);
bool r = OSGetForegroundBucket((MEMPTR<void>*)memory_getPointerFromVirtualOffsetAllowNull(areaOutput), (uint32be*)memory_getPointerFromVirtualOffsetAllowNull(areaSize));
osLib_returnFromFunction(hCPU, r ? 1 : 0);
}
void coreinitExport_OSGetForegroundBucketFreeArea(PPCInterpreter_t* hCPU)
{
debug_printf("OSGetForegroundBucketFreeArea(0x%x,0x%x)\n", hCPU->gpr[3], hCPU->gpr[4]);
ppcDefineParamMPTR(areaOutput, 0);
ppcDefineParamMPTR(areaSize, 1);
bool r = OSGetForegroundBucketFreeArea((MPTR*)memory_getPointerFromVirtualOffsetAllowNull(areaOutput), (MPTR*)memory_getPointerFromVirtualOffsetAllowNull(areaSize));
osLib_returnFromFunction(hCPU, r ? 1 : 0);
}
void InitForegroundBucket()
{
uint32be fgSize;
OSGetForegroundBucket(&fgAddr, &fgSize);
uint8* freeAreaPtr = GetFGMemByArea(FG_BUCKET_AREA_FREE).GetPtr();
memset(freeAreaPtr, 0, FG_BUCKET_AREA_FREE_SIZE);
uint8* saveAreaPtr = GetFGMemByArea(FG_BUCKET_AREA_SAVE).GetPtr();
fgSaveAreaAddr = saveAreaPtr;
if (*(uint32be*)saveAreaPtr != (uint32be)'Save')
{
// clear save area
memset(saveAreaPtr, 0, FG_BUCKET_AREA_SAVE_SIZE);
// clear copy area
memset(GetFGMemByArea(FG_BUCKET_AREA_COPY).GetPtr(), 0, FG_BUCKET_AREA_COPY_SIZE);
// init save area
*(uint32be*)(saveAreaPtr + 0x00) = 'Save';
*(uint32be*)(saveAreaPtr + 0x08) |= 0x300;
}
}
void __OSClearCopyData()
{
uint8* fgCopyArea = GetFGMemByArea(FG_BUCKET_AREA_COPY).GetPtr();
if (fgCopyArea)
{
*(uint32be*)(fgCopyArea + 0x00) = 0;
}
}
uint32 __OSGetCopyDataSize()
{
uint8* fgCopyArea = GetFGMemByArea(FG_BUCKET_AREA_COPY).GetPtr();
if (fgCopyArea)
{
return *(uint32be*)(fgCopyArea + 0x00);
}
return 0;
}
uint8* __OSGetCopyDataPtr()
{
uint8* fgCopyArea = GetFGMemByArea(FG_BUCKET_AREA_COPY).GetPtr();
if (fgCopyArea)
return fgCopyArea + 4;
return nullptr;
}
bool __OSAppendCopyData(uint8* data, sint32 length)
{
uint8* fgCopyArea = GetFGMemByArea(FG_BUCKET_AREA_COPY).GetPtr();
if (fgCopyArea == nullptr)
return false;
uint32 currentOffset = *(uint32be*)(fgCopyArea + 0x00);
if ((currentOffset + length) > FG_BUCKET_AREA_COPY_SIZE - 4)
{
return false;
}
memcpy(fgCopyArea + currentOffset + 4, data, length);
*(uint32be*)(fgCopyArea + 0x00) += length;
return true;
}
bool __OSResizeCopyData(sint32 length)
{
uint8* fgCopyArea = GetFGMemByArea(FG_BUCKET_AREA_COPY).GetPtr();
if (fgCopyArea == nullptr)
return false;
sint32 currentOffset = (sint32) * (uint32be*)(fgCopyArea + 0x00);
if (length < currentOffset)
{
// can only make copy data smaller
*(uint32be*)(fgCopyArea + 0x00) = length;
return true;
}
return false;
}
bool OSCopyFromClipboard(void* data, uint32be* size)
{
uint32 cSize = *size;
if (cSize == 0 && data == nullptr)
return false;
if (OSGetForegroundBucket(nullptr, nullptr) == false)
return false;
// todo
*size = 0;
return true;
}
void coreinitExport_OSCopyFromClipboard(PPCInterpreter_t* hCPU)
{
forceLogDebug_printf("OSCopyFromClipboard(0x%x,0x%x)\n", hCPU->gpr[3], hCPU->gpr[4]);
ppcDefineParamMEMPTR(buffer, void, 0);
ppcDefineParamMEMPTR(size, uint32be, 1);
bool r = OSCopyFromClipboard(buffer.GetPtr(), size.GetPtr());
osLib_returnFromFunction(hCPU, r ? 1 : 0);
}
void InitializeFG()
{
osLib_addFunction("coreinit", "OSGetForegroundBucket", coreinitExport_OSGetForegroundBucket);
osLib_addFunction("coreinit", "OSGetForegroundBucketFreeArea", coreinitExport_OSGetForegroundBucketFreeArea);
osLib_addFunction("coreinit", "OSCopyFromClipboard", coreinitExport_OSCopyFromClipboard);
}
}

View file

@ -0,0 +1,17 @@
#pragma once
namespace coreinit
{
void __OSClearCopyData();
uint32 __OSGetCopyDataSize();
uint8* __OSGetCopyDataPtr();
bool __OSAppendCopyData(uint8* data, sint32 length);
bool __OSResizeCopyData(sint32 length);
bool OSGetForegroundBucket(MEMPTR<void>* offset, uint32be* size);
bool OSGetForegroundBucketFreeArea(MPTR* offset, MPTR* size);
void InitForegroundBucket();
void InitializeFG();
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,325 @@
#pragma once
#include "Cafe/OS/libs/coreinit/coreinit_Thread.h"
#include "Cafe/IOSU/iosu_ipc_common.h"
#include "Cafe/IOSU/fsa/fsa_types.h"
#include "Cafe/IOSU/fsa/iosu_fsa.h"
#include "coreinit_MessageQueue.h"
typedef struct
{
uint32be fileHandle;
}FSFileHandleDepr_t;
typedef MEMPTR<betype<FSDirHandle2>> FSDirHandlePtr;
typedef struct
{
MEMPTR<void> userCallback;
MEMPTR<void> userContext;
MEMPTR<coreinit::OSMessageQueue> ioMsgQueue;
}FSAsyncParamsNew_t;
static_assert(sizeof(FSAsyncParamsNew_t) == 0xC);
typedef struct
{
MPTR userCallback; // 0x96C
MPTR userContext;
MPTR ioMsgQueue;
}FSAsyncParams_t; // legacy struct. Replace with FSAsyncParamsNew_t
namespace coreinit
{
struct FSCmdQueue
{
enum class QUEUE_FLAG : uint32
{
IS_FULL = (1 << 0), // waiting for Ioctl(v) result
CANCEL_ALL = (1 << 4),
};
/* +0x00 */ MPTR firstMPTR;
/* +0x04 */ MPTR lastMPTR;
/* +0x08 */ OSMutex mutex;
/* +0x34 */ MPTR dequeueHandlerFuncMPTR;
/* +0x38 */ uint32be numCommandsInFlight;
/* +0x3C */ uint32 numMaxCommandsInFlight;
/* +0x40 */ betype<QUEUE_FLAG> queueFlags;
};
DEFINE_ENUM_FLAG_OPERATORS(FSCmdQueue::QUEUE_FLAG);
#define FS_CLIENT_BUFFER_SIZE (5888)
#define FS_CMD_BLOCK_SIZE (2688)
struct FSClient_t
{
uint8 buffer[FS_CLIENT_BUFFER_SIZE];
};
struct FSCmdBlock_t
{
union
{
uint8 buffer[FS_CMD_BLOCK_SIZE];
struct
{
uint32 mount_it;
}data;
};
};
static_assert(sizeof(FSCmdBlock_t) == FS_CMD_BLOCK_SIZE);
struct FSClientBody_t
{
uint8 ukn0000[0x100];
uint8 ukn0100[0x100];
uint8 ukn0200[0x100];
uint8 ukn0300[0x100];
uint8 ukn0400[0x100];
uint8 ukn0500[0x100];
uint8 ukn0600[0x100];
uint8 ukn0700[0x100];
uint8 ukn0800[0x100];
uint8 ukn0900[0x100];
uint8 ukn0A00[0x100];
uint8 ukn0B00[0x100];
uint8 ukn0C00[0x100];
uint8 ukn0D00[0x100];
uint8 ukn0E00[0x100];
uint8 ukn0F00[0x100];
uint8 ukn1000[0x100];
uint8 ukn1100[0x100];
uint8 ukn1200[0x100];
uint8 ukn1300[0x100];
uint8 ukn1400[0x10];
uint8 ukn1410[0x10];
uint8 ukn1420[0x10];
uint8 ukn1430[0x10];
uint32 ukn1440;
betype<IOSDevHandle> iosuFSAHandle;
uint32 ukn1448;
uint32 ukn144C;
uint8 ukn1450[0x10];
uint8 ukn1460[0x10];
uint8 ukn1470[0x10];
FSCmdQueue fsCmdQueue;
/* +0x14C4 */ MEMPTR<struct FSCmdBlockBody_t> currentCmdBlockBody; // set to currently active cmd
uint32 ukn14C8;
uint32 ukn14CC;
uint8 ukn14D0[0x10];
uint8 ukn14E0[0x10];
uint8 ukn14F0[0x10];
uint8 ukn1500[0x100];
uint32 ukn1600;
uint32 ukn1604;
uint32 ukn1608;
uint32 ukn160C;
uint32 ukn1610;
MEMPTR<FSClientBody_t> fsClientBodyNext; // next FSClientBody_t* in list of registered clients (list is circular, the last element points to the first element)
uint32 ukn1618;
/* +0x161C */ MEMPTR<FSClient_t> selfClient; // pointer to FSClient struct which holds this FSClientBody
uint32 ukn1620;
};
struct FSAsyncResult
{
/* +0x00 */ FSAsyncParamsNew_t fsAsyncParamsNew;
// fs message storage
struct FSMessage
{
/* +0x0C / 0x0978 */ MEMPTR<FSAsyncResult> fsAsyncResult;
/* +0x10 */ MPTR fsClientMPTR2; // 0x097C
/* +0x14 */ MPTR fsCmdBlockMPTR; // 0x0980
/* +0x18 */ MPTR commandType; // 0x0984
};
union
{
OSMessage osMsg;
FSMessage fsMsg;
}msgUnion;
/* +0x1C */ MEMPTR<FSClient_t> fsClient; // 0x0988
/* +0x20 */ MEMPTR<FSCmdBlock_t> fsCmdBlock; // 0x98C
/* +0x24 */ uint32be fsStatusNew; // 0x990
};
static_assert(sizeof(FSAsyncResult) == 0x28);
struct FSCmdBlockBody_t
{
iosu::fsa::FSAIpcCommand ipcData;
uint8 ukn0820[0x10];
uint8 ukn0830[0x10];
uint8 ukn0840[0x10];
uint8 ukn0850[0x10];
uint8 ukn0860[0x10];
uint8 ukn0870[0x10];
MPTR fsCmdBlockBodyMPTR;
uint32 ukn0884;
uint32 ukn0888;
uint32 destBuffer88CMPTR;
uint32 ukn0890;
uint32 ukn0894;
uint32 ukn0898;
uint32 ukn089C;
uint32 ukn08A0;
uint32 ukn08A4;
uint32 ukn08A8;
uint32 ukn08AC;
uint8 ukn08B0[0x10];
uint8 ukn08C0[0x10];
uint8 ukn08D0[0x10];
uint8 ukn08E0[0x10];
uint8 ukn08F0[0x10];
/* +0x0900 */ uint32be operationType;
betype<IOSDevHandle> fsaDevHandle;
/* +0x0908 */ uint16be ipcReqType; // 0 -> IoctlAsync, 1 -> IoctlvAsync
uint8 ukn090A;
uint8 ukn090B;
uint32 ukn090C;
uint32 ukn0910;
uint32 ukn0914;
uint32 ukn0918;
uint32 ukn091C;
uint32 ukn0920;
uint32 ukn0924;
uint32 ukn0928;
uint32 ukn092C;
uint32 ukn0930;
uint32 ukn0934;
/* +0x0938 */ MEMPTR<FSClientBody_t> fsClientBody;
/* +0x093C */ uint32 statusCode; // not a status code but rather the state? Uses weird values for some reason
/* +0x0940 */ uint32be cancelState; // bitmask. Bit 0 -> If set command has been canceled
// return values
/* +0x0944 */ uint32 returnValueMPTR; // returnedFilePos (used to store pointer to variable that holds return value?), also used by QUERYINFO to store pointer for result. Also used for GetCwd() to hold the pointer for the returned dir path. Also used by OPENFILE to hold returned fileHandle
/* +0x0948 */ uint32 transferSize; // number of bytes to transfer
// transfer control?
uint32 uknVal094C;
uint32 transferElemSize; // number of bytes of a single transferred element (count of elements can be calculated via count = transferSize/transferElemSize)
uint32 uknVal0954; // this is set to max(0x10, transferSize) for reads and to min(0x40000, transferSize) for writes?
// link for cmd queue
MPTR nextMPTR; // points towards FSCmdQueue->first
MPTR previousMPTR; // points towards FSCmdQueue->last
/* +0x960 */ betype<FSA_RESULT> lastFSAStatus;
uint32 ukn0964;
/* +0x0968 */ uint8 errHandling; // return error flag mask
/* +0x096C */ FSAsyncResult asyncResult;
/* +0x0994 */ MEMPTR<void> userData;
/* +0x0998 */ OSMessageQueue syncTaskMsgQueue; // this message queue is used when mapping asynchronous tasks to synchronous API
/* +0x09D4 */ OSMessage _syncTaskMsg[1];
/* +0x09E4 */ MPTR cmdFinishFuncMPTR;
/* +0x09E8 */ uint8 priority;
uint8 uknStatusGuessed09E9;
uint8 ukn09EA;
uint8 ukn09EB;
uint32 ukn09EC;
uint32 ukn9F0;
uint32be ukn9F4_lastErrorRelated;
/* +0x9F8 */ MEMPTR<FSCmdBlock_t> selfCmdBlock;
uint32 ukn9FC;
};
static_assert(sizeof(FSAsyncParams_t) == 0xC);
static_assert(sizeof(FSCmdBlock_t) == 0xA80);
#define FSA_CMD_FLAG_SET_POS (1<<0)
#define FSA_CMD_OPERATION_TYPE_CHANGEDIR (0x5)
#define FSA_CMD_OPERATION_TYPE_GETCWD (0x6)
#define FSA_CMD_OPERATION_TYPE_MAKEDIR (0x7)
#define FSA_CMD_OPERATION_TYPE_REMOVE (0x8)
#define FSA_CMD_OPERATION_TYPE_RENAME (0x9)
#define FSA_CMD_OPERATION_TYPE_OPENDIR (0xA)
#define FSA_CMD_OPERATION_TYPE_READDIR (0xB)
#define FSA_CMD_OPERATION_TYPE_CLOSEDIR (0xD)
#define FSA_CMD_OPERATION_TYPE_OPENFILE (0xE)
#define FSA_CMD_OPERATION_TYPE_READ (0xF)
#define FSA_CMD_OPERATION_TYPE_WRITE (0x10)
#define FSA_CMD_OPERATION_TYPE_GETPOS (0x11)
#define FSA_CMD_OPERATION_TYPE_SETPOS (0x12)
#define FSA_CMD_OPERATION_TYPE_ISEOF (0x13)
#define FSA_CMD_OPERATION_TYPE_GETSTATFILE (0x14)
#define FSA_CMD_OPERATION_TYPE_CLOSEFILE (0x15)
#define FSA_CMD_OPERATION_TYPE_QUERYINFO (0x18)
#define FSA_CMD_OPERATION_TYPE_APPENDFILE (0x19)
#define FSA_CMD_OPERATION_TYPE_TRUNCATEFILE (0x1A)
#define FSA_CMD_OPERATION_TYPE_FLUSHQUOTA (0x1E)
#define FSA_CMD_STATUS_CODE_D900A21 0xD900A21 // cmd block is initialized
#define FSA_CMD_STATUS_CODE_D900A22 0xD900A22 // cmd block is queued
#define FSA_CMD_STATUS_CODE_D900A24 0xD900A24 // cmd block was processed and is available again
#define FSA_CMD_STATUS_CODE_D900A26 0xD900A26 // cmd block result is being processed
enum FS_VOLSTATE : sint32
{
FS_VOLSTATE_READY = 1,
};
// internal interface
sint32 __FSQueryInfoAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint8* queryString, uint32 queryType, void* queryResult, uint32 errHandling, FSAsyncParamsNew_t* fsAsyncParams);
// coreinit exports
FS_RESULT FSAddClientEx(FSClient_t* fsClient, uint32 uknR4, uint32 errHandling);
FS_RESULT FSAddClient(FSClient_t* fsClient, uint32 errHandling);
FS_RESULT FSDelClient(FSClient_t* fsClient, uint32 errHandling);
void FSInitCmdBlock(FSCmdBlock_t* fsCmdBlock);
sint32 FSOpenFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, char* mode, FSFileHandleDepr_t* fileHandle, uint32 errHandling, FSAsyncParamsNew_t* asyncParams);
sint32 FSOpenFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, char* mode, FSFileHandleDepr_t* fileHandle, uint32 errHandling);
sint32 FSReadFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* dst, uint32 size, uint32 count, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams);
sint32 FSReadFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* dst, uint32 size, uint32 count, uint32 fileHandle, uint32 flag, uint32 errorMask);
sint32 FSReadFileWithPosAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* dst, uint32 size, uint32 count, uint32 filePos, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams);
sint32 FSReadFileWithPos(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* dst, uint32 size, uint32 count, uint32 filePos, uint32 fileHandle, uint32 flag, uint32 errorMask);
sint32 FSWriteFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* src, uint32 size, uint32 count, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams);
sint32 FSWriteFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* src, uint32 size, uint32 count, uint32 fileHandle, uint32 flag, uint32 errorMask);
sint32 FSWriteFileWithPosAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* src, uint32 size, uint32 count, uint32 filePos, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams);
sint32 FSWriteFileWithPos(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* src, uint32 size, uint32 count, uint32 filePos, uint32 fileHandle, uint32 flag, uint32 errorMask);
sint32 FSSetPosFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 filePos, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams);
sint32 FSSetPosFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 filePos, uint32 errorMask);
sint32 FSGetPosFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32be* returnedFilePos, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams);
sint32 FSGetPosFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32be* returnedFilePos, uint32 errorMask);
sint32 FSAppendFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 size, uint32 count, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams);
sint32 FSAppendFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 size, uint32 count, uint32 errorMask);
sint32 FSIsEofAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams);
sint32 FSIsEof(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 errorMask);
sint32 FSRenameAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* srcPath, char* dstPath, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams);
sint32 FSRename(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* srcPath, char* dstPath, uint32 errorMask);
sint32 FSRemoveAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint8* filePath, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams);
sint32 FSRemove(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint8* filePath, uint32 errorMask);
sint32 FSMakeDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const uint8* dirPath, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams);
sint32 FSMakeDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const uint8* path, uint32 errorMask);
sint32 FSChangeDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams);
sint32 FSChangeDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, uint32 errorMask);
sint32 FSGetCwdAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* dirPathOut, sint32 dirPathMaxLen, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams);
sint32 FSGetCwd(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* dirPathOut, sint32 dirPathMaxLen, uint32 errorMask);
sint32 FSGetFreeSpaceSizeAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const char* path, FSLargeSize* returnedFreeSize, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams);
sint32 FSGetFreeSpaceSize(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const char* path, FSLargeSize* returnedFreeSize, uint32 errorMask);
sint32 FSOpenDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, FSDirHandlePtr dirHandleOut, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams);
sint32 FSOpenDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, FSDirHandlePtr dirHandleOut, uint32 errorMask);
sint32 FSReadDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, FSDirEntry_t* dirEntryOut, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams);
sint32 FSReadDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, FSDirEntry_t* dirEntryOut, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams);
sint32 FSCloseDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams);
sint32 FSCloseDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, uint32 errorMask);
sint32 FSFlushQuotaAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams);
sint32 FSFlushQuota(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, uint32 errorMask);
FS_VOLSTATE FSGetVolumeState(FSClient_t* fsClient);
void InitializeFS();
};

View file

@ -0,0 +1,285 @@
#include "Cafe/OS/common/OSCommon.h"
#include "Cafe/OS/libs/coreinit/coreinit_GHS.h"
#include "Cafe/OS/libs/coreinit/coreinit_MEM.h"
#include "Cafe/OS/libs/coreinit/coreinit_Thread.h"
#include "Cafe/OS/RPL/rpl.h"
namespace coreinit
{
struct iobbuf
{
uint32be ukn00; // lock index?
uint32be ukn04; // ?
uint32be ukn08; // ?
uint32be flags; // permissions and channel
};
#define GHS_FOPEN_MAX 100
struct GHSAccessibleData
{
iobbuf _iob[GHS_FOPEN_MAX];
MPTR _iob_lock[GHS_FOPEN_MAX];
uint16be __gh_FOPEN_MAX;
MEMPTR<void> ghs_environ;
uint32 ghs_Errno; // exposed by __gh_errno_ptr() or via 'errno' data export
};
SysAllocator<GHSAccessibleData> g_ghs_data;
struct ghs_flock
{
uint32be mutexIndex;
};
void __ghs_flock_create(ghs_flock* flock);
ghs_flock* __ghs_flock_ptr(iobbuf* iob);
std::recursive_mutex g_ghsLock;
std::recursive_mutex g_ghsLockFlock;
SysAllocator<coreinit::OSMutex, GHS_FOPEN_MAX> _flockMutexArray;
bool _flockMutexMask[GHS_FOPEN_MAX]; // if set, mutex in _flockMutexArray is reserved
#define IOB_FLAG_IN (0x1)
#define IOB_FLAG_OUT (0x2)
#define IOB_FLAG_CHANNEL(__x) ((__x)<<18)
void PrepareGHSRuntime()
{
g_ghs_data->ghs_environ = nullptr;
g_ghs_data->__gh_FOPEN_MAX = GHS_FOPEN_MAX;
g_ghs_data->ghs_Errno = 0;
for (sint32 i = 0; i < GHS_FOPEN_MAX; i++)
_flockMutexMask[i] = false;
// init stdin/stdout/stderr
g_ghs_data->_iob[0].flags = IOB_FLAG_IN;
g_ghs_data->_iob[1].flags = IOB_FLAG_OUT;
g_ghs_data->_iob[1].flags = IOB_FLAG_OUT;
g_ghs_data->_iob[0].flags |= IOB_FLAG_CHANNEL(0);
g_ghs_data->_iob[1].flags |= IOB_FLAG_CHANNEL(1);
g_ghs_data->_iob[2].flags |= IOB_FLAG_CHANNEL(2);
__ghs_flock_create(__ghs_flock_ptr(g_ghs_data->_iob + 0));
__ghs_flock_create(__ghs_flock_ptr(g_ghs_data->_iob + 1));
__ghs_flock_create(__ghs_flock_ptr(g_ghs_data->_iob + 2));
osLib_addVirtualPointer("coreinit", "__gh_FOPEN_MAX", memory_getVirtualOffsetFromPointer(&g_ghs_data->__gh_FOPEN_MAX));
osLib_addVirtualPointer("coreinit", "_iob", memory_getVirtualOffsetFromPointer(g_ghs_data->_iob));
osLib_addVirtualPointer("coreinit", "environ", memory_getVirtualOffsetFromPointer(&g_ghs_data->ghs_environ));
osLib_addVirtualPointer("coreinit", "errno", memory_getVirtualOffsetFromPointer(&g_ghs_data->ghs_Errno));
}
void __ghs_flock_create(ghs_flock* flock)
{
g_ghsLockFlock.lock();
// find available mutex
sint32 mutexIndex = -1;
for (sint32 i = 0; i < GHS_FOPEN_MAX; i++)
{
if (!_flockMutexMask[i])
{
mutexIndex = i;
break;
}
}
if (mutexIndex == -1)
{
forceLog_printf("__ghs_flock_create(): No flock available");
cemu_assert(false); // no available mutex
}
// mark mutex as reserved
_flockMutexMask[mutexIndex] = true;
// init mutex
coreinit::OSInitMutexEx(_flockMutexArray.GetPtr() + mutexIndex, NULL);
// update flock to point to the reserved mutex
flock->mutexIndex = mutexIndex;
g_ghsLockFlock.unlock();
}
void __ghs_flock_destroy(uint32 index)
{
g_ghsLockFlock.lock();
cemu_assert_debug(index > 2); // stdin/stdout/stderr should never be released?
cemu_assert(index < GHS_FOPEN_MAX);
cemu_assert_debug(_flockMutexMask[index]);
_flockMutexMask[index] = false;
g_ghsLockFlock.unlock();
}
ghs_flock* __ghs_flock_ptr(iobbuf* iob)
{
size_t streamIndex = iob - g_ghs_data->_iob;
return (ghs_flock*)&(g_ghs_data->_iob_lock[streamIndex]);
}
void __ghs_flock_file(uint32 index)
{
cemu_assert(index < GHS_FOPEN_MAX);
OSLockMutex(_flockMutexArray.GetPtr() + index);
}
void __ghs_funlock_file(uint32 index)
{
cemu_assert(index < GHS_FOPEN_MAX);
OSUnlockMutex(_flockMutexArray.GetPtr() + index);
}
void __ghsLock()
{
while (!g_ghsLock.try_lock())
{
PPCCore_switchToScheduler();
}
}
void __ghsUnlock()
{
g_ghsLock.unlock();
}
void* __get_eh_init_block()
{
return nullptr;
}
void* __get_eh_globals()
{
OSThread_t* currentThread = coreinit::OSGetCurrentThread();
return currentThread->crt.eh_globals.GetPtr();
}
void* __get_eh_mem_manage()
{
OSThread_t* currentThread = coreinit::OSGetCurrentThread();
return &currentThread->crt.eh_mem_manage;
}
void* __gh_errno_ptr()
{
OSThread_t* currentThread = coreinit::OSGetCurrentThread();
return &currentThread->context.error;
}
void* __get_eh_store_globals()
{
OSThread_t* currentThread = coreinit::OSGetCurrentThread();
return &currentThread->crt.eh_store_globals;
}
void* __get_eh_store_globals_tdeh()
{
OSThread_t* currentThread = coreinit::OSGetCurrentThread();
return &currentThread->crt.eh_store_globals_tdeh;
}
struct ghs_mtx_t
{
MEMPTR<coreinit::OSMutex> mutexPtr;
};
void __ghs_mtx_init(ghs_mtx_t* mtx)
{
mtx->mutexPtr = (coreinit::OSMutex*)coreinit::_weak_MEMAllocFromDefaultHeapEx(ppcsizeof<coreinit::OSMutex>(), 8);
coreinit::OSInitMutex(mtx->mutexPtr.GetPtr());
}
void __ghs_mtx_dst(ghs_mtx_t* mtx)
{
coreinit::_weak_MEMFreeToDefaultHeap(mtx->mutexPtr.GetPtr());
mtx->mutexPtr = nullptr;
}
void __ghs_mtx_lock(ghs_mtx_t* mtx)
{
coreinit::OSLockMutex(mtx->mutexPtr.GetPtr());
}
void __ghs_mtx_unlock(ghs_mtx_t* mtx)
{
coreinit::OSUnlockMutex(mtx->mutexPtr.GetPtr());
}
struct OSTLSBlock
{
MPTR addr;
uint32 ukn04;
};
static_assert(sizeof(OSTLSBlock) == 8);
struct TLS_Index
{
uint16 ukn00;
uint16 tlsModuleIndex;
MPTR ukn04;
};
void* __tls_get_addr(TLS_Index* tlsIndex)
{
OSThread_t* currentThread = coreinit::OSGetCurrentThread();
if (_swapEndianU16(tlsIndex->tlsModuleIndex) == 0)
assert_dbg();
// check if we need to allocate additional TLS blocks for this thread
if (_swapEndianU16(tlsIndex->tlsModuleIndex) >= _swapEndianU32(currentThread->numAllocatedTLSBlocks))
{
uint32 allocSize = (RPLLoader_GetMaxTLSModuleIndex() + 1) * sizeof(OSTLSBlock); // __OSDynLoad_gTLSHeader.ukn00 * 8;
MPTR allocMem = coreinit_allocFromSysArea(allocSize, 4);
memset(memory_getPointerFromVirtualOffset(allocMem), 0, allocSize);
if (_swapEndianU32(currentThread->numAllocatedTLSBlocks) != 0)
{
// keep previously allocated blocks
memcpy(memory_getPointerFromVirtualOffset(allocMem), memory_getPointerFromVirtualOffset(_swapEndianU32(currentThread->tlsBlocksMPTR)), _swapEndianU32(currentThread->numAllocatedTLSBlocks) * 8);
}
currentThread->tlsBlocksMPTR = _swapEndianU32(allocMem);
currentThread->numAllocatedTLSBlocks = _swapEndianU16(RPLLoader_GetMaxTLSModuleIndex() + 1);
}
// look up TLS address based on moduleIndex
OSTLSBlock* tlsBlock = (OSTLSBlock*)memory_getPointerFromVirtualOffsetAllowNull(_swapEndianU32(currentThread->tlsBlocksMPTR) + sizeof(OSTLSBlock) * (uint32)_swapEndianU16(tlsIndex->tlsModuleIndex));
if (tlsBlock->addr != _swapEndianU32(MPTR_NULL))
{
//osLib_returnFromFunction(hCPU, _swapEndianU32(tlsBlock->addr)+_swapEndianU32(tlsIndex->ukn04));
return memory_getPointerFromVirtualOffset(_swapEndianU32(tlsBlock->addr) + _swapEndianU32(tlsIndex->ukn04));
}
// alloc data for TLS block
uint8* tlsSectionData = nullptr;
sint32 tlsSize = 0;
bool r = RPLLoader_GetTLSDataByTLSIndex((sint16)_swapEndianU16(tlsIndex->tlsModuleIndex), &tlsSectionData, &tlsSize);
cemu_assert(r);
cemu_assert(tlsSize != 0);
MPTR tlsData = coreinit_allocFromSysArea(tlsSize, 32);
memcpy(memory_getPointerFromVirtualOffset(tlsData), tlsSectionData, tlsSize);
tlsBlock->addr = _swapEndianU32(tlsData);
return memory_getPointerFromVirtualOffset(_swapEndianU32(tlsBlock->addr) + _swapEndianU32(tlsIndex->ukn04));
}
void InitializeGHS()
{
cafeExportRegister("coreinit", __ghs_flock_create, LogType::Placeholder);
cafeExportRegister("coreinit", __ghs_flock_destroy, LogType::Placeholder);
cafeExportRegister("coreinit", __ghs_flock_ptr, LogType::Placeholder);
cafeExportRegister("coreinit", __ghs_flock_file, LogType::Placeholder);
cafeExportRegister("coreinit", __ghs_funlock_file, LogType::Placeholder);
cafeExportRegister("coreinit", __ghsLock, LogType::Placeholder);
cafeExportRegister("coreinit", __ghsUnlock, LogType::Placeholder);
cafeExportRegister("coreinit", __get_eh_init_block, LogType::Placeholder);
cafeExportRegister("coreinit", __get_eh_globals, LogType::Placeholder);
cafeExportRegister("coreinit", __get_eh_mem_manage, LogType::Placeholder);
cafeExportRegister("coreinit", __gh_errno_ptr, LogType::Placeholder);
cafeExportRegister("coreinit", __get_eh_store_globals, LogType::Placeholder);
cafeExportRegister("coreinit", __get_eh_store_globals_tdeh, LogType::Placeholder);
cafeExportRegister("coreinit", __ghs_mtx_init, LogType::Placeholder);
cafeExportRegister("coreinit", __ghs_mtx_dst, LogType::Placeholder);
cafeExportRegister("coreinit", __ghs_mtx_lock, LogType::Placeholder);
cafeExportRegister("coreinit", __ghs_mtx_unlock, LogType::Placeholder);
cafeExportRegister("coreinit", __tls_get_addr, LogType::Placeholder);
}
};

View file

@ -0,0 +1,8 @@
#pragma once
namespace coreinit
{
void PrepareGHSRuntime();
void InitializeGHS();
};

View file

@ -0,0 +1,138 @@
#include "Cafe/OS/common/OSCommon.h"
#include "coreinit_HWInterface.h"
namespace coreinit
{
enum class RegisterInterfaceId : uint32 // for __OSRead/__OSWrite API (register access in userspace)
{
INTERFACE_VI_UKN = 0, // 0x0C1E0000
INTERFACE_VI2_UKN = 3, // might also be some other interface?
};
enum class SysRegisterInterfaceId : uint32 // for __OSRead/__OSWriteRegister (register access via kernel systemcall)
{
INTERFACE_UKN = 0,
INTERFACE_3_ACR_VI = 3, // 0x0D00021C
INTERFACE_6_SI = 6, // 0x0D006400
INTERFACE_7_AI_PROBABLY = 7, // 0x0D046C00 // AI or some secondary AI interface?
};
PAddr _GetRegisterPhysicalAddress(RegisterInterfaceId interfaceId, uint32 offset)
{
PAddr base = 0;
switch (interfaceId)
{
case RegisterInterfaceId::INTERFACE_VI_UKN:
base = 0x0C1E0000;
break;
default:
cemu_assert_debug(false); // todo
return 0;
}
return base + offset;
}
PAddr _GetSysRegisterPhysicalAddress(SysRegisterInterfaceId interfaceId, uint32 offset)
{
PAddr base = 0;
switch (interfaceId)
{
case SysRegisterInterfaceId::INTERFACE_3_ACR_VI:
base = 0x0D00021C;
break;
case SysRegisterInterfaceId::INTERFACE_6_SI:
base = 0x0D006400;
break;
default:
cemu_assert_debug(false); // todo
return 0;
}
return base + offset;
}
/* Userspace register interface */
uint32 OSReadRegister32(RegisterInterfaceId interfaceId, uint32 offset)
{
PAddr regAddr = _GetRegisterPhysicalAddress(interfaceId, offset);
cemu_assert_debug(regAddr);
return MMU::ReadMMIO_32(regAddr);
}
uint16 OSReadRegister16(RegisterInterfaceId interfaceId, uint32 offset)
{
PAddr regAddr = _GetRegisterPhysicalAddress(interfaceId, offset);
cemu_assert_debug(regAddr);
return MMU::ReadMMIO_16(regAddr);
}
void OSWriteRegister16(uint16 newValue, RegisterInterfaceId interfaceId, uint32 offset)
{
static bool s_dbg = false;
if (!s_dbg)
{
cemu_assert_debug(false);
s_dbg = true;
}
}
void OSWriteRegister32(uint16 newValue, RegisterInterfaceId interfaceId, uint32 offset)
{
static bool s_dbg = false;
if (!s_dbg)
{
cemu_assert_debug(false);
s_dbg = true;
}
}
void OSModifyRegister16(RegisterInterfaceId interfaceId, uint32 uknR4, uint32 uknR5, uint32 uknR6)
{
static bool s_dbg = false;
if (!s_dbg)
{
cemu_assert_debug(false);
s_dbg = true;
}
}
/* Kernel register interface */
uint32 __OSReadRegister32Ex(SysRegisterInterfaceId interfaceId, uint32 registerId)
{
uint32 offset = registerId * 4;
cemu_assert_debug(offset < 0x40);
PAddr regAddr = _GetSysRegisterPhysicalAddress(interfaceId, offset);
cemu_assert_debug(regAddr);
return MMU::ReadMMIO_32(regAddr);
}
void __OSWriteRegister32Ex(SysRegisterInterfaceId interfaceId, uint32 registerId, uint32 newValue)
{
uint32 offset = registerId * 4;
cemu_assert_debug(offset < 0x40);
PAddr regAddr = _GetSysRegisterPhysicalAddress(interfaceId, offset);
cemu_assert_debug(regAddr);
MMU::WriteMMIO_32(regAddr, newValue);
}
void InitializeHWInterface()
{
cafeExportRegister("coreinit", OSReadRegister32, LogType::Placeholder);
cafeExportRegister("coreinit", OSReadRegister16, LogType::Placeholder);
cafeExportRegister("coreinit", OSWriteRegister16, LogType::Placeholder);
cafeExportRegister("coreinit", OSWriteRegister32, LogType::Placeholder);
cafeExportRegister("coreinit", OSModifyRegister16, LogType::Placeholder);
cafeExportRegister("coreinit", __OSReadRegister32Ex, LogType::Placeholder);
cafeExportRegister("coreinit", __OSWriteRegister32Ex, LogType::Placeholder);
};
};

View file

@ -0,0 +1,4 @@
namespace coreinit
{
void InitializeHWInterface();
};

View file

@ -0,0 +1,137 @@
#include "Cafe/OS/common/OSCommon.h"
// APD = Automatic Power Down
namespace coreinit
{
#define IM_ERROR_NONE 0
void coreinitExport_IMIsAPDEnabledBySysSettings(PPCInterpreter_t* hCPU)
{
debug_printf("IMIsAPDEnabledBySysSettings(0x%08x)\n", hCPU->gpr[3]);
ppcDefineParamTypePtr(isAPDEnabled, uint32be, 0);
*isAPDEnabled = 0;
osLib_returnFromFunction(hCPU, IM_ERROR_NONE);
}
void coreinitExport_IMGetTimeBeforeAPD(PPCInterpreter_t* hCPU)
{
// parameters:
// r3 uint32* returns the remaining number of seconds until auto-shutdown
memory_writeU32(hCPU->gpr[3], 60 * 30); // 30 minutes
osLib_returnFromFunction(hCPU, IM_ERROR_NONE);
}
void coreinitExport_IMGetTimeBeforeDimming(PPCInterpreter_t* hCPU)
{
// parameters:
// r3 uint32* returns the remaining number of seconds until dimming
memory_writeU32(hCPU->gpr[3], 60 * 30); // 30 minutes
osLib_returnFromFunction(hCPU, IM_ERROR_NONE);
}
bool imDimIsEnabled = true;
void coreinitExport_IMEnableDim(PPCInterpreter_t* hCPU)
{
imDimIsEnabled = true;
osLib_returnFromFunction(hCPU, IM_ERROR_NONE);
}
void coreinitExport_IMIsDimEnabled(PPCInterpreter_t* hCPU)
{
// parameters:
// r3 uint32* returns the remaining number of seconds until auto-shutdown
memory_writeU32(hCPU->gpr[3], imDimIsEnabled ? 1 : 0); // enabled
osLib_returnFromFunction(hCPU, IM_ERROR_NONE);
}
void coreinitExport_IMGetAPDPeriod(PPCInterpreter_t* hCPU)
{
forceLogDebug_printf("IMGetAPDPeriod(0x%08x)\n", hCPU->gpr[3]);
// parameters:
// r3 uint32* returns the number of seconds until auto-shutdown occurs
memory_writeU32(hCPU->gpr[3], 600);
osLib_returnFromFunction(hCPU, IM_ERROR_NONE);
}
void coreinitExport_IM_GetParameter(PPCInterpreter_t* hCPU)
{
forceLogDebug_printf("IM_GetParameter()");
ppcDefineParamS32(imHandle, 0); // handle from IM_Open()
ppcDefineParamS32(uknR4, 1);
ppcDefineParamS32(parameterId, 2);
ppcDefineParamStructPtr(output, void, 3);
ppcDefineParamS32(uknR7, 4);
ppcDefineParamS32(uknR8, 5);
if (parameterId == 0)
{
// inactive seconds
*(uint32be*)output = 600;
}
else
{
cemu_assert_unimplemented();
}
osLib_returnFromFunction(hCPU, IM_ERROR_NONE);
}
void coreinitExport_IM_GetRuntimeParameter(PPCInterpreter_t* hCPU)
{
forceLogDebug_printf("IM_GetRuntimeParameter()");
ppcDefineParamS32(parameterId, 0);
ppcDefineParamStructPtr(output, void, 1);
if (parameterId == 8)
{
// indicates if last session was ended due to auto-power-down
*(uint32be*)output = 0;
}
else
{
cemu_assert_unimplemented();
}
osLib_returnFromFunction(hCPU, IM_ERROR_NONE);
}
void coreinitExport_IM_GetHomeButtonParams(PPCInterpreter_t* hCPU)
{
debug_printf("IM_GetHomeButtonParams(...)\n");
ppcDefineParamS32(imObj, 0);
ppcDefineParamMPTR(ipcBuf, 1);
ppcDefineParamMPTR(paramOut, 2);
ppcDefineParamS32(uknR6, 3);
ppcDefineParamS32(uknR7, 4);
// todo
// note: No idea what these values mean. But they were chosen so that the Browser (surf.rpx) does not OSPanic()
memory_writeU32(paramOut + 0x0, 0);
memory_writeU32(paramOut + 0x4, 0);
// for scope.rpx (Download Manager)
//memory_writeU32(paramOut + 0x0, 1);
//memory_writeU32(paramOut + 0x4, 2); // some sort of index (starting at 1?)
osLib_returnFromFunction(hCPU, IM_ERROR_NONE);
}
void InitializeIM()
{
osLib_addFunction("coreinit", "IMIsAPDEnabledBySysSettings", coreinitExport_IMIsAPDEnabledBySysSettings);
osLib_addFunction("coreinit", "IMGetTimeBeforeAPD", coreinitExport_IMGetTimeBeforeAPD);
osLib_addFunction("coreinit", "IMGetTimeBeforeDimming", coreinitExport_IMGetTimeBeforeDimming);
osLib_addFunction("coreinit", "IMEnableDim", coreinitExport_IMEnableDim);
osLib_addFunction("coreinit", "IMIsDimEnabled", coreinitExport_IMIsDimEnabled);
osLib_addFunction("coreinit", "IMGetAPDPeriod", coreinitExport_IMGetAPDPeriod);
osLib_addFunction("coreinit", "IM_GetHomeButtonParams", coreinitExport_IM_GetHomeButtonParams);
osLib_addFunction("coreinit", "IM_GetParameter", coreinitExport_IM_GetParameter);
osLib_addFunction("coreinit", "IM_GetRuntimeParameter", coreinitExport_IM_GetRuntimeParameter);
}
};

View file

@ -0,0 +1,6 @@
#pragma once
namespace coreinit
{
void InitializeIM();
};

View file

@ -0,0 +1,91 @@
#include "Cafe/OS/libs/coreinit/coreinit_IOS.h"
#include "Cafe/IOSU/legacy/iosu_ioctl.h"
// superseded by coreinit_IPC.cpp/h
sint32 __depr__IOS_Open(char* path, uint32 mode)
{
sint32 iosDevice = 0;
if (path == nullptr)
{
iosDevice = 0;
}
else
{
if (strcmp(path, IOS_PATH_ODM) == 0)
iosDevice = IOS_DEVICE_ODM;
else if (strcmp(path, IOS_PATH_SOCKET) == 0)
iosDevice = IOS_DEVICE_SOCKET;
else if (strcmp(path, IOS_PATH_ACT) == 0)
iosDevice = IOS_DEVICE_ACT;
else if (strcmp(path, IOS_PATH_FPD) == 0)
iosDevice = IOS_DEVICE_FPD;
else if (strcmp(path, IOS_PATH_ACP_MAIN) == 0)
iosDevice = IOS_DEVICE_ACP_MAIN;
else if (strcmp(path, IOS_PATH_MCP) == 0)
iosDevice = IOS_DEVICE_MCP;
else if (strcmp(path, IOS_PATH_BOSS) == 0)
iosDevice = IOS_DEVICE_BOSS;
else if (strcmp(path, IOS_PATH_NIM) == 0)
iosDevice = IOS_DEVICE_NIM;
else if (strcmp(path, IOS_PATH_IOSUHAX) == 0)
return -1;
else
iosDevice = IOS_DEVICE_UKN;
}
return iosDevice;
}
sint32 __depr__IOS_Ioctl(uint32 fd, uint32 request, void* inBuffer, uint32 inSize, void* outBuffer, uint32 outSize)
{
switch (fd)
{
case IOS_DEVICE_ODM:
{
// Home Menu uses ioctl cmd 5 on startup and then repeats cmd 4 every frame
if (request == 4)
{
// check drive state
debug_printf("checkDriveState()\n");
*(uint32be*)outBuffer = 0xA;
}
else
{
debug_printf("odm unsupported ioctl %d\n", request);
}
break;
}
default:
{
// todo
forceLogDebug_printf("Unsupported Ioctl command");
}
}
return 0;
}
sint32 __depr__IOS_Ioctlv(uint32 fd, uint32 request, uint32 countIn, uint32 countOut, ioBufferVector_t* ioBufferVectors)
{
StackAllocator<ioQueueEntry_t> _queueEntryBuf;
ioQueueEntry_t* queueEntry = _queueEntryBuf.GetPointer();
queueEntry->isIoctlv = true;
queueEntry->isAsync = false;
queueEntry->request = request;
queueEntry->countIn = countIn;
queueEntry->countOut = countOut;
queueEntry->bufferVectors = ioBufferVectors;
queueEntry->ppcThread = nullptr;
queueEntry->returnValue = 0;
queueEntry->isCompleted = false;
sint32 r = iosuIoctl_pushAndWait(fd, queueEntry);
return r;
}
sint32 __depr__IOS_Close(uint32 fd)
{
return 0;
}

View file

@ -0,0 +1,9 @@
// IOS
typedef struct _ioBufferVector_t ioBufferVector_t;
sint32 __depr__IOS_Open(char* path, uint32 mode);
sint32 __depr__IOS_Ioctl(uint32 fd, uint32 request, void* inBuffer, uint32 inSize, void* outBuffer, uint32 outSize);
sint32 __depr__IOS_Ioctlv(uint32 fd, uint32 request, uint32 countIn, uint32 countOut, ioBufferVector_t* ioBufferVectors);
sint32 __depr__IOS_Close(uint32 fd);
// superseded by coreinit_IPC.h

View file

@ -0,0 +1,482 @@
#include "Cafe/OS/common/OSCommon.h"
#include "Cafe/IOSU/kernel/iosu_kernel.h"
#include "Cafe/HW/Espresso/Const.h"
#include "Cafe/HW/Espresso/PPCCallback.h"
#include "coreinit_MessageQueue.h"
#include "coreinit_IPC.h"
namespace coreinit
{
static constexpr inline size_t IPC_NUM_RESOURCE_BUFFERS = 0x30;
struct IPCResourceBuffer
{
IPCCommandBody commandBody;
uint8 bufferData[0x80 - 0x48];
};
static_assert(sizeof(IPCCommandBody) == 0x48);
static_assert(sizeof(IPCResourceBuffer) == 0x80);
struct IPCResourceBufferDescriptor
{
/* +0x00 */ uint32be IsAllocated;
/* +0x04 */ MEMPTR<OSMessageQueue> asyncMsgQueue; // optional, if set a message will be sent to this queue...
/* +0x08 */ MEMPTR<void> asyncResultFunc; // ...otherwise this is checked and delegated to the IPC threads. If false, only eventSynchronousIPC will be signaled. If true, a message will be sent to the per-core ipc queue
/* +0x0C */ MEMPTR<void> asyncResultUserParam;
/* +0x10 */ uint32 ukn10;
/* +0x14 */ MEMPTR<IPCResourceBuffer> resourcePtr;
/* +0x18 */ OSEvent eventSynchronousIPC;
};
static_assert(sizeof(IPCResourceBufferDescriptor) == 0x3C);
struct IPCBufferFIFO
{
sint32be writeIndex;
sint32be readIndex;
sint32be numQueuedEntries;
sint32be mostQueuedEntries;
MEMPTR<IPCResourceBufferDescriptor> ringbufferArray[IPC_NUM_RESOURCE_BUFFERS];
void Init()
{
writeIndex = 0;
readIndex = -1;
numQueuedEntries = 0;
mostQueuedEntries = 0;
for (size_t i = 0; i < IPC_NUM_RESOURCE_BUFFERS; i++)
ringbufferArray[i] = nullptr;
}
void Push(IPCResourceBufferDescriptor* descriptor)
{
cemu_assert(readIndex != writeIndex); // if equal, fifo is full (should not happen as there not more buffers than ringbuffer entries)
ringbufferArray[writeIndex] = descriptor;
if (readIndex < 0)
readIndex = writeIndex;
writeIndex = (writeIndex + 1) % IPC_NUM_RESOURCE_BUFFERS;
++numQueuedEntries;
if (numQueuedEntries > mostQueuedEntries)
mostQueuedEntries = numQueuedEntries;
}
IPCResourceBufferDescriptor* Pop()
{
if (numQueuedEntries == 0)
return nullptr;
IPCResourceBufferDescriptor* r = ringbufferArray[readIndex].GetPtr();
--numQueuedEntries;
if (numQueuedEntries == 0)
readIndex = -1;
else
readIndex = (readIndex + 1) % IPC_NUM_RESOURCE_BUFFERS;
return r;
}
};
struct IPCDriverCOSKernelCommunicationArea
{
uint32be numAvailableResponses;
MEMPTR<IPCCommandBody> responseArray[11];
};
static_assert(sizeof(IPCDriverCOSKernelCommunicationArea) == 0x30);
struct alignas(64) IPCDriver
{
betype<IPCDriverState> state;
uint32 ukn04;
uint32 coreIndex;
uint32 writeIndexCmd020;
MEMPTR<IPCResourceBuffer> resourceBuffers;
/* 0x16C */ IPCBufferFIFO fifoFreeBuffers;
/* 0x23C */ IPCBufferFIFO fifoBuffersInFlight;
/* 0x334 */ uint32 resourceBuffersInitialized;
/* 0x338 */ IPCDriverCOSKernelCommunicationArea kernelSharedArea; // this is passed to system call 0x1E00 (IPCOpen)
/* 0x3FC */ IPCResourceBufferDescriptor resBufferDescriptor[IPC_NUM_RESOURCE_BUFFERS];
};
//static_assert(sizeof(IPCDriverInstance) == 0x1740);
SysAllocator<IPCResourceBuffer, IPC_NUM_RESOURCE_BUFFERS * Espresso::CORE_COUNT, 0x40> s_ipcResourceBuffers;
SysAllocator<IPCDriver, Espresso::CORE_COUNT, 0x40> s_ipcDriver;
IPCDriver& IPCDriver_GetByCore(uint32 coreIndex)
{
cemu_assert_debug(coreIndex >= 0 && coreIndex < (uint32)Espresso::CORE_COUNT);
return s_ipcDriver[coreIndex];
}
void IPCDriver_InitForCore(uint32 coreIndex)
{
IPCDriver& ipcDriver = IPCDriver_GetByCore(coreIndex);
ipcDriver.coreIndex = coreIndex;
ipcDriver.state = IPCDriverState::INITIALIZED;
ipcDriver.resourceBuffers = s_ipcResourceBuffers.GetPtr() + IPC_NUM_RESOURCE_BUFFERS * coreIndex;
ipcDriver.resourceBuffersInitialized = 0;
// setup resource descriptors
for (size_t i = 0; i < IPC_NUM_RESOURCE_BUFFERS; i++)
{
ipcDriver.resBufferDescriptor[i].resourcePtr = ipcDriver.resourceBuffers.GetPtr() + i;
ipcDriver.resBufferDescriptor[i].asyncResultFunc = nullptr;
ipcDriver.resBufferDescriptor[i].asyncResultUserParam = nullptr;
}
ipcDriver.resourceBuffersInitialized = 1;
// setup resource buffer FIFOs
ipcDriver.fifoFreeBuffers.Init();
ipcDriver.fifoBuffersInFlight.Init();
for (size_t i = 0; i < IPC_NUM_RESOURCE_BUFFERS; i++)
ipcDriver.fifoFreeBuffers.Push(ipcDriver.resBufferDescriptor + i);
}
IPCResourceBufferDescriptor* IPCDriver_AllocateResource(IPCDriver* ipcDriver, IOSDevHandle devHandle, IPCCommandId cmdId, OSMessageQueue* asyncMessageQueue, MEMPTR<void> asyncResultFunc, MEMPTR<void> asyncResultUserParam)
{
cemu_assert_debug(ipcDriver->coreIndex == OSGetCoreId());
IPCResourceBufferDescriptor* descriptor = nullptr;
while (true)
{
descriptor = ipcDriver->fifoFreeBuffers.Pop();
if (!descriptor)
{
cemuLog_log(LogType::Force, "IPCDriver: Exceeded free resources");
OSYieldThread();
cemu_assert_unimplemented(); // we should wait for an event instead of busylooping
}
else
break;
}
cemu_assert_debug(descriptor >= ipcDriver->resBufferDescriptor && descriptor < ipcDriver->resBufferDescriptor + IPC_NUM_RESOURCE_BUFFERS);
cemu_assert_debug(descriptor->resourcePtr.GetPtr() >= ipcDriver->resourceBuffers.GetPtr() && descriptor->resourcePtr.GetPtr() < (ipcDriver->resourceBuffers.GetPtr() + IPC_NUM_RESOURCE_BUFFERS));
IPCResourceBuffer* res = descriptor->resourcePtr;
IPCCommandBody& cmdBody = res->commandBody;
descriptor->IsAllocated = 1;
descriptor->asyncMsgQueue = asyncMessageQueue;
descriptor->asyncResultFunc = asyncResultFunc;
descriptor->asyncResultUserParam = asyncResultUserParam;
cmdBody.cmdId = cmdId;
cmdBody.ukn0C = 0;
cmdBody.ukn14 = 0;
cmdBody.result = 0;
cmdBody.devHandle = devHandle;
return descriptor;
}
void IPCDriver_ReleaseResource(IPCDriver* ipcDriver, IPCResourceBufferDescriptor* requestDescriptor)
{
requestDescriptor->IsAllocated = 0;
ipcDriver->fifoFreeBuffers.Push(requestDescriptor);
}
/* IPC threads */
SysAllocator<OSThread_t, Espresso::CORE_COUNT> gIPCThread;
SysAllocator<uint8, 0x4000 * Espresso::CORE_COUNT> _gIPCThreadStack;
SysAllocator<uint8, 0x18 * Espresso::CORE_COUNT> _gIPCThreadNameStorage;
SysAllocator<OSMessageQueue, Espresso::CORE_COUNT> gIPCThreadMsgQueue;
SysAllocator<OSMessage, Espresso::CORE_COUNT * IPC_NUM_RESOURCE_BUFFERS> _gIPCThreadSemaphoreStorage;
// handler thread for asynchronous callbacks for IPC responses
void __IPCDriverThreadFunc(PPCInterpreter_t* hCPU)
{
uint32 coreIndex = OSGetCoreId();
while (true)
{
OSMessage msg;
OSReceiveMessage(gIPCThreadMsgQueue.GetPtr() + coreIndex, &msg, OS_MESSAGE_BLOCK);
cemu_assert(msg.data2 == 1); // type must be callback
MEMPTR<void> cbFunc{ msg.message };
cemu_assert(cbFunc != nullptr);
PPCCoreCallback(cbFunc.GetPtr(), (uint32)msg.data0, (uint32)msg.data1);
}
osLib_returnFromFunction(hCPU, 0);
}
void IPCDriver_InitIPCThread(uint32 coreIndex)
{
// create a thread with 0x4000 stack space
// and a message queue large enough to hold the maximum number of commands (IPC_NUM_RESOURCE_BUFFERS)
OSInitMessageQueue(gIPCThreadMsgQueue.GetPtr() + coreIndex, _gIPCThreadSemaphoreStorage.GetPtr() + coreIndex * IPC_NUM_RESOURCE_BUFFERS, IPC_NUM_RESOURCE_BUFFERS);
OSThread_t* ipcThread = gIPCThread.GetPtr() + coreIndex;
OSCreateThreadType(ipcThread, PPCInterpreter_makeCallableExportDepr(__IPCDriverThreadFunc), 0, nullptr, _gIPCThreadStack.GetPtr() + 0x4000 * coreIndex + 0x4000, 0x4000, 15, (1 << coreIndex), OSThread_t::THREAD_TYPE::TYPE_DRIVER);
sprintf((char*)_gIPCThreadNameStorage.GetPtr()+coreIndex*0x18, "{SYS IPC Core %d}", coreIndex);
OSSetThreadName(ipcThread, (char*)_gIPCThreadNameStorage.GetPtr() + coreIndex * 0x18);
OSResumeThread(ipcThread);
}
/* coreinit IOS_* API */
void _IPCDriver_SubmitCmdAllQueued(IPCDriver& ipcDriver)
{
// on COS, submitted commands first go to the COS kernel via syscall 0x2000, where they are processed, copied and queued again
// we skip all of this and just pass our IPC commands directly to the IOSU kernel handler HLE function
// important: IOSU needs to know which PPC core sent the command, so that it can also notify the same core about the result
ipcDriver.state = IPCDriverState::SUBMITTING;
while (true)
{
IPCResourceBufferDescriptor* res = ipcDriver.fifoBuffersInFlight.Pop();
if (!res)
break;
// resolve pointers
switch (res->resourcePtr->commandBody.cmdId)
{
case IPCCommandId::IOS_OPEN:
res->resourcePtr->commandBody.args[0] = res->resourcePtr->commandBody.ppcVirt0.GetMPTR();
break;
case IPCCommandId::IOS_CLOSE:
break;
case IPCCommandId::IOS_IOCTL:
res->resourcePtr->commandBody.args[1] = res->resourcePtr->commandBody.ppcVirt0.GetMPTR();
res->resourcePtr->commandBody.args[3] = res->resourcePtr->commandBody.ppcVirt1.GetMPTR();
break;
case IPCCommandId::IOS_IOCTLV:
res->resourcePtr->commandBody.args[3] = res->resourcePtr->commandBody.ppcVirt0.GetMPTR();
break;
default:
cemu_assert_unimplemented();
break;
}
iosu::kernel::IPCSubmitFromCOS(ipcDriver.coreIndex, &res->resourcePtr->commandBody);
}
ipcDriver.state = IPCDriverState::READY;
}
void _IPCDriver_SubmitCmd(IPCDriver& ipcDriver, IPCResourceBufferDescriptor* requestDescriptor)
{
if (requestDescriptor->asyncResultFunc == nullptr)
OSInitEvent(&requestDescriptor->eventSynchronousIPC, OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, OSEvent::EVENT_MODE::MODE_AUTO);
ipcDriver.fifoBuffersInFlight.Push(requestDescriptor);
cemu_assert_debug(ipcDriver.state == IPCDriverState::READY || ipcDriver.state == IPCDriverState::INITIALIZED);
_IPCDriver_SubmitCmdAllQueued(ipcDriver);
}
uint32 _IPCDriver_WaitForResultAndRelease(IPCDriver& ipcDriver, IPCResourceBufferDescriptor* requestDescriptor)
{
OSWaitEvent(&requestDescriptor->eventSynchronousIPC);
uint32 r = requestDescriptor->resourcePtr->commandBody.result;
IPCDriver_ReleaseResource(&ipcDriver, requestDescriptor);
return r;
}
void IPCDriver_HandleResponse(IPCDriver& ipcDriver, IPCCommandBody* res, uint32 ppcCoreIndex)
{
size_t index = (IPCResourceBuffer*)res - ipcDriver.resourceBuffers.GetPtr();
cemu_assert(index < IPC_NUM_RESOURCE_BUFFERS);
IPCResourceBufferDescriptor* descriptor = ipcDriver.resBufferDescriptor + index;
cemu_assert(descriptor->IsAllocated != 0);
if (descriptor->asyncMsgQueue != nullptr)
{
OSMessage msg;
msg.message = 0;
msg.data0 = res->result;
msg.data1 = descriptor->asyncResultUserParam.GetMPTR();
msg.data2 = 0;
sint32 r = OSSendMessage(descriptor->asyncMsgQueue.GetPtr(), &msg, 0);
cemu_assert(r != 0);
IPCDriver_ReleaseResource(&ipcDriver, descriptor);
return;
}
if (descriptor->asyncResultFunc != nullptr)
{
OSMessage msg;
msg.message = descriptor->asyncResultFunc.GetMPTR();
msg.data0 = res->result;
msg.data1 = descriptor->asyncResultUserParam.GetMPTR();
msg.data2 = 1;
sint32 r = OSSendMessage(gIPCThreadMsgQueue.GetPtr() + ppcCoreIndex, &msg, 0);
cemu_assert(r != 0);
IPCDriver_ReleaseResource(&ipcDriver, descriptor);
return;
}
// signal event for synchronous IPC
OSSignalEvent(&descriptor->eventSynchronousIPC);
}
// handles responses queued in shared region
void IPCDriver_KernelCallback(IPCDriver& ipcDriver)
{
cemu_assert_debug(ipcDriver.kernelSharedArea.numAvailableResponses != 0);
for (uint32 i = 0; i < ipcDriver.kernelSharedArea.numAvailableResponses; i++)
IPCDriver_HandleResponse(ipcDriver, ipcDriver.kernelSharedArea.responseArray[i], ipcDriver.coreIndex);
ipcDriver.kernelSharedArea.numAvailableResponses = 0;
}
// called by our HLE'd IOSU directly
void IPCDriver_NotifyResponses(uint32 ppcCoreIndex, IPCCommandBody** responseArray, uint32 numResponses)
{
cemu_assert(numResponses < 11);
IPCDriver& ipcDriver = IPCDriver_GetByCore(ppcCoreIndex);
ipcDriver.kernelSharedArea.numAvailableResponses = numResponses;
for (uint32 i = 0; i < numResponses; i++)
ipcDriver.kernelSharedArea.responseArray[i] = responseArray[i];
IPCDriver_KernelCallback(ipcDriver);
}
void _IPCDriver_SetupCmd_IOSOpen(IPCDriver& ipcDriver, IPCResourceBufferDescriptor* requestDescriptor, const char* devicePath, uint32 flags)
{
// store the path in the buffer after the command body
IPCResourceBuffer* resBuffer = requestDescriptor->resourcePtr;
IPCCommandBody& cmdBody = resBuffer->commandBody;
uint8* buffer = resBuffer->bufferData;
size_t pathLen = strlen(devicePath);
if (pathLen > 31)
{
cemuLog_log(LogType::Force, "IOS_Open(): Device path must not exceed 31 characters");
cemu_assert_error();
}
memcpy(buffer, devicePath, pathLen + 1);
cmdBody.ppcVirt0 = MEMPTR<void>(buffer).GetMPTR();
cmdBody.args[0] = 0;
cmdBody.args[1] = (uint32)(pathLen + 1);
cmdBody.args[2] = flags;
}
IOS_ERROR _IPCDriver_SetupCmd_IOSIoctl(IPCDriver& ipcDriver, IPCResourceBufferDescriptor* requestDescriptor, uint32 requestId, void* ptrIn, uint32 sizeIn, void* ptrOut, uint32 sizeOut)
{
IPCCommandBody& cmdBody = requestDescriptor->resourcePtr->commandBody;
cmdBody.args[0] = requestId;
cmdBody.args[1] = MPTR_NULL; // set to ppcVirt0 later
cmdBody.args[2] = sizeIn;
cmdBody.args[3] = MPTR_NULL; // set to ppcVirt1 later
cmdBody.args[4] = sizeOut;
cmdBody.ppcVirt0 = MEMPTR<void>(ptrIn).GetMPTR();
cmdBody.ppcVirt1 = MEMPTR<void>(ptrOut).GetMPTR();
return IOS_ERROR_OK;
}
IOS_ERROR _IPCDriver_SetupCmd_IOSIoctlv(IPCDriver& ipcDriver, IPCResourceBufferDescriptor* requestDescriptor, uint32 requestId, uint32 numIn, uint32 numOut, IPCIoctlVector* vec)
{
IPCCommandBody& cmdBody = requestDescriptor->resourcePtr->commandBody;
// verify input and output vectors
IPCIoctlVector* vecIn = vec;
IPCIoctlVector* vecOut = vec + numIn;
for (uint32 i = 0; i < numIn; i++)
{
if (vecIn[i].baseVirt == nullptr && vecIn[i].size != 0)
return IOS_ERROR_INVALID_ARG;
vecIn[i].basePhys = vecIn[i].baseVirt;
vecIn[i].baseVirt = nullptr;
}
for (uint32 i = 0; i < numOut; i++)
{
if (vecOut[i].baseVirt == nullptr && vecOut[i].size != 0)
return IOS_ERROR_INVALID_ARG;
vecOut[i].basePhys = vecOut[i].baseVirt;
vecOut[i].baseVirt = nullptr;
}
// set args
cmdBody.ppcVirt0 = MEMPTR<void>(vec).GetMPTR();
cmdBody.args[0] = requestId;
cmdBody.args[1] = numIn;
cmdBody.args[2] = numOut;
cmdBody.args[3] = 0; // set to ppcVirt0 later
return IOS_ERROR_OK;
}
IOSDevHandle IOS_Open(const char* devicePath, uint32 flags)
{
IPCDriver& ipcDriver = IPCDriver_GetByCore(OSGetCoreId());
IPCResourceBufferDescriptor* ipcDescriptor = IPCDriver_AllocateResource(&ipcDriver, 0, IPCCommandId::IOS_OPEN, nullptr, nullptr, nullptr);
_IPCDriver_SetupCmd_IOSOpen(ipcDriver, ipcDescriptor, devicePath, flags);
_IPCDriver_SubmitCmd(ipcDriver, ipcDescriptor);
uint32 r = _IPCDriver_WaitForResultAndRelease(ipcDriver, ipcDescriptor);
return r;
}
IOS_ERROR IOS_Close(IOSDevHandle devHandle)
{
IPCDriver& ipcDriver = IPCDriver_GetByCore(OSGetCoreId());
IPCResourceBufferDescriptor* ipcDescriptor = IPCDriver_AllocateResource(&ipcDriver, devHandle, IPCCommandId::IOS_CLOSE, nullptr, nullptr, nullptr);
_IPCDriver_SubmitCmd(ipcDriver, ipcDescriptor);
IOS_ERROR r = (IOS_ERROR)_IPCDriver_WaitForResultAndRelease(ipcDriver, ipcDescriptor);
return r;
}
IOS_ERROR IOS_Ioctl(IOSDevHandle devHandle, uint32 requestId, void* ptrIn, uint32 sizeIn, void* ptrOut, uint32 sizeOut)
{
IPCDriver& ipcDriver = IPCDriver_GetByCore(OSGetCoreId());
IPCResourceBufferDescriptor* ipcDescriptor = IPCDriver_AllocateResource(&ipcDriver, devHandle, IPCCommandId::IOS_IOCTL, nullptr, nullptr, nullptr);
IOS_ERROR r = _IPCDriver_SetupCmd_IOSIoctl(ipcDriver, ipcDescriptor, requestId, ptrIn, sizeIn, ptrOut, sizeOut);
if (r != IOS_ERROR_OK)
{
cemuLog_log(LogType::Force, "IOS_Ioctl failed due to bad parameters");
IPCDriver_ReleaseResource(&ipcDriver, ipcDescriptor);
return r;
}
_IPCDriver_SubmitCmd(ipcDriver, ipcDescriptor);
r = (IOS_ERROR)_IPCDriver_WaitForResultAndRelease(ipcDriver, ipcDescriptor);
return r;
}
IOS_ERROR IOS_IoctlAsync(IOSDevHandle devHandle, uint32 requestId, void* ptrIn, uint32 sizeIn, void* ptrOut, uint32 sizeOut, MEMPTR<void> asyncResultFunc, MEMPTR<void> asyncResultUserParam)
{
IPCDriver& ipcDriver = IPCDriver_GetByCore(OSGetCoreId());
IPCResourceBufferDescriptor* ipcDescriptor = IPCDriver_AllocateResource(&ipcDriver, devHandle, IPCCommandId::IOS_IOCTL, nullptr, asyncResultFunc, asyncResultUserParam);
IOS_ERROR r = _IPCDriver_SetupCmd_IOSIoctl(ipcDriver, ipcDescriptor, requestId, ptrIn, sizeIn, ptrOut, sizeOut);
if (r != IOS_ERROR_OK)
{
cemuLog_log(LogType::Force, "IOS_Ioctl failed due to bad parameters");
IPCDriver_ReleaseResource(&ipcDriver, ipcDescriptor);
return r;
}
_IPCDriver_SubmitCmd(ipcDriver, ipcDescriptor);
return r;
}
IOS_ERROR IOS_Ioctlv(IOSDevHandle devHandle, uint32 requestId, uint32 numIn, uint32 numOut, IPCIoctlVector* vec)
{
IPCDriver& ipcDriver = IPCDriver_GetByCore(OSGetCoreId());
IPCResourceBufferDescriptor* ipcDescriptor = IPCDriver_AllocateResource(&ipcDriver, devHandle, IPCCommandId::IOS_IOCTLV, nullptr, nullptr, nullptr);
IOS_ERROR r = _IPCDriver_SetupCmd_IOSIoctlv(ipcDriver, ipcDescriptor, requestId, numIn, numOut, vec);
if (r != IOS_ERROR_OK)
{
cemuLog_log(LogType::Force, "IOS_Ioctlv failed due to bad parameters");
IPCDriver_ReleaseResource(&ipcDriver, ipcDescriptor);
return r;
}
_IPCDriver_SubmitCmd(ipcDriver, ipcDescriptor);
r = (IOS_ERROR)_IPCDriver_WaitForResultAndRelease(ipcDriver, ipcDescriptor);
return r;
}
IOS_ERROR IOS_IoctlvAsync(IOSDevHandle devHandle, uint32 requestId, uint32 numIn, uint32 numOut, IPCIoctlVector* vec, MEMPTR<void> asyncResultFunc, MEMPTR<void> asyncResultUserParam)
{
IPCDriver& ipcDriver = IPCDriver_GetByCore(OSGetCoreId());
IPCResourceBufferDescriptor* ipcDescriptor = IPCDriver_AllocateResource(&ipcDriver, devHandle, IPCCommandId::IOS_IOCTLV, nullptr, asyncResultFunc, asyncResultUserParam);
IOS_ERROR r = _IPCDriver_SetupCmd_IOSIoctlv(ipcDriver, ipcDescriptor, requestId, numIn, numOut, vec);
if (r != IOS_ERROR_OK)
{
cemuLog_log(LogType::Force, "IOS_Ioctlv failed due to bad parameters");
IPCDriver_ReleaseResource(&ipcDriver, ipcDescriptor);
return r;
}
_IPCDriver_SubmitCmd(ipcDriver, ipcDescriptor);
return r;
}
void InitializeIPC()
{
for (uint32 i = 0; i < Espresso::CORE_COUNT; i++)
{
IPCDriver_InitForCore(i);
IPCDriver_InitIPCThread(i);
}
// register API
cafeExportRegister("coreinit", IOS_Open, LogType::PPC_IPC);
cafeExportRegister("coreinit", IOS_Close, LogType::PPC_IPC);
cafeExportRegister("coreinit", IOS_Ioctl, LogType::PPC_IPC);
cafeExportRegister("coreinit", IOS_IoctlAsync, LogType::PPC_IPC);
cafeExportRegister("coreinit", IOS_Ioctlv, LogType::PPC_IPC);
cafeExportRegister("coreinit", IOS_IoctlvAsync, LogType::PPC_IPC);
}
};

View file

@ -0,0 +1,16 @@
#include "Cafe/IOSU/iosu_ipc_common.h"
#include "Cafe/IOSU/iosu_types_common.h"
namespace coreinit
{
void IPCDriver_NotifyResponses(uint32 ppcCoreIndex, IPCCommandBody** responseArray, uint32 numResponses);
IOSDevHandle IOS_Open(const char* devicePath, uint32 flags);
IOS_ERROR IOS_Close(IOSDevHandle devHandle);
IOS_ERROR IOS_Ioctl(IOSDevHandle devHandle, uint32 requestId, void* ptrIn, uint32 sizeIn, void* ptrOut, uint32 sizeOut);
IOS_ERROR IOS_IoctlAsync(IOSDevHandle devHandle, uint32 requestId, void* ptrIn, uint32 sizeIn, void* ptrOut, uint32 sizeOut, MEMPTR<void> asyncResultFunc, MEMPTR<void> asyncResultUserParam);
IOS_ERROR IOS_Ioctlv(IOSDevHandle devHandle, uint32 requestId, uint32 numIn, uint32 numOut, IPCIoctlVector* vec);
IOS_ERROR IOS_IoctlvAsync(IOSDevHandle devHandle, uint32 requestId, uint32 numIn, uint32 numOut, IPCIoctlVector* vec, MEMPTR<void> asyncResultFunc, MEMPTR<void> asyncResultUserParam);
void InitializeIPC();
};

View file

@ -0,0 +1,233 @@
#include "Cafe/OS/common/OSCommon.h"
#include "Cafe/OS/libs/coreinit/coreinit_Thread.h"
namespace coreinit
{
struct FIFOEntry_t
{
MEMPTR<uint8> p;
};
struct IPCFifo_t
{
uint32be writeIndex;
uint32be readIndex;
uint32be availableEntries; // number of available entries
uint32be entryCount;
MEMPTR<FIFOEntry_t> entryArray;
};
struct IPCBufPool_t
{
/* +0x00 */ uint32be magic;
/* +0x04 */ MEMPTR<void> fullBufferPtr;
/* +0x08 */ uint32be fullBufferSize;
/* +0x0C */ uint32be uknFromParamR7; // boolean?
/* +0x10 */ uint32be ukn10; // set to zero on init
/* +0x14 */ uint32be entrySize1;
/* +0x18 */ uint32be entrySize2; // set to same value as entrySize1
/* +0x1C */ uint32be entryCount; // actual number of used entries
/* +0x20 */ MEMPTR<uint8> entryStartPtr;
/* +0x24 */ uint32be entryCountMul4;
/* +0x28 */ IPCFifo_t fifo;
/* +0x3C */ coreinit::OSMutex mutex;
// full size is 0x68
};
void FIFOInit(IPCFifo_t* fifo, uint32 entryCount, void* entryArray)
{
fifo->entryCount = entryCount;
fifo->entryArray = (FIFOEntry_t*)entryArray;
fifo->writeIndex = 0;
fifo->readIndex = -1;
fifo->availableEntries = 0;
}
sint32 FIFOPush(IPCFifo_t* fifo, void* entry)
{
if (fifo->readIndex == fifo->writeIndex)
{
assert_dbg();
return -8;
}
fifo->entryArray[(uint32)fifo->writeIndex].p = (uint8*)entry;
if ((sint32)fifo->readIndex < 0)
{
// set readIndex to valid value when fifo was empty
fifo->readIndex = fifo->writeIndex;
}
fifo->availableEntries = (uint32)fifo->availableEntries + 1;
fifo->writeIndex = ((uint32)fifo->writeIndex + 1) % (uint32)fifo->entryCount;
return 0;
}
sint32 FIFOPop(IPCFifo_t* fifo, uint8** entry)
{
*entry = nullptr;
if ((sint32)fifo->readIndex < 0)
return -7;
fifo->availableEntries = (uint32)fifo->availableEntries - 1;
*entry = fifo->entryArray[(uint32)fifo->readIndex].p.GetPtr();
fifo->readIndex = ((uint32)fifo->readIndex + 1) % (uint32)fifo->entryCount;
if (fifo->availableEntries == (uint32be)0)
{
fifo->readIndex = -1;
}
return 0;
}
void* getUpwardsAlignedAddr(void* addr, uint32 alignment)
{
uint64 v = ((uint64)addr + alignment - 1) & ~(uint64)(alignment - 1);
return (uint8*)v;
}
void* getDownwardsAlignedAddr(void* addr, uint32 alignment)
{
uint64 v = ((uint64)addr) & ~(uint64)(alignment - 1);
return (uint8*)v;
}
static_assert(sizeof(IPCBufPool_t) == 0x68);
IPCBufPool_t* IPCBufPoolCreate(uint8* bufferArea, uint32 bufferSize, uint32 entrySize, uint32be* entryCountOutput, uint32 uknR7)
{
memset(bufferArea, 0, bufferSize);
IPCBufPool_t* ipcBufPool = (IPCBufPool_t*)getUpwardsAlignedAddr(bufferArea, 4);
uint8* alignedEnd = (uint8*)getDownwardsAlignedAddr(bufferArea + bufferSize, 4);
uint8* dataStart = (uint8*)getUpwardsAlignedAddr(ipcBufPool + 1, 0x4);
*entryCountOutput = 0;
// todo: Validate parameters
OSInitMutexEx(&ipcBufPool->mutex, NULL);
ipcBufPool->fullBufferPtr = bufferArea;
ipcBufPool->fullBufferSize = bufferSize;
ipcBufPool->uknFromParamR7 = uknR7;
uint32 paddedEntrySize = (entrySize + 0x3F) & ~0x3F;
ipcBufPool->ukn10 = 0;
ipcBufPool->magic = 0xBADF00D;
ipcBufPool->entrySize1 = paddedEntrySize;
ipcBufPool->entrySize2 = paddedEntrySize;
uint32 remainingSize = (uint32)((bufferArea + bufferSize) - dataStart);
uint32 entryCount = remainingSize / paddedEntrySize;
if (entryCount <= 1)
assert_dbg();
ipcBufPool->entryCountMul4 = entryCount * 4;
if ((uint32)ipcBufPool->entryCountMul4 >= paddedEntrySize)
{
// special handling required (need to adjust entry count?)
assert_dbg();
}
else
{
entryCount--; // remove one entry to make room for entry pointer array
ipcBufPool->entryCount = entryCount;
*entryCountOutput = entryCount;
ipcBufPool->entryStartPtr = (dataStart + (uint32)ipcBufPool->entryCountMul4);
FIFOInit(&ipcBufPool->fifo, (uint32)ipcBufPool->entryCount, dataStart);
}
// add all entries to the fifo
for (sint32 i = 0; i < (sint32)ipcBufPool->entryCount; i++)
{
uint8* entry = ipcBufPool->entryStartPtr.GetPtr() + i * (uint32)ipcBufPool->entrySize2;
if (FIFOPush(&ipcBufPool->fifo, entry) != 0)
return nullptr;
}
return ipcBufPool;
}
uint8* IPCBufPoolAllocate(IPCBufPool_t* ipcBufPool, uint32 size)
{
uint8* entry = nullptr;
OSLockMutex(&ipcBufPool->mutex);
if (ipcBufPool->magic == (uint32be)0xBADF00D && size <= (uint32)ipcBufPool->entrySize1)
{
FIFOPop(&ipcBufPool->fifo, &entry);
}
else
{
assert_dbg();
}
OSUnlockMutex(&ipcBufPool->mutex);
return entry;
}
sint32 IPCBufPoolFree(IPCBufPool_t* ipcBufPool, uint8* entry)
{
OSLockMutex(&ipcBufPool->mutex);
sint32 res = 0;
if (ipcBufPool->magic == (uint32be)0xBADF00D)
{
// check if entry is actually part of the pool
uint32 offset = (uint32)(entry - (uint8*)ipcBufPool->entryStartPtr.GetPtr());
if ((offset % (uint32)ipcBufPool->entrySize2) != 0)
assert_dbg();
uint32 index = offset / (uint32)ipcBufPool->entrySize2;
if ((index >= (uint32)ipcBufPool->entryCount))
assert_dbg();
FIFOPush(&ipcBufPool->fifo, entry);
}
else
{
assert_dbg();
res = -4;
}
OSUnlockMutex(&ipcBufPool->mutex);
return res;
}
void coreinitExport_IPCBufPoolCreate(PPCInterpreter_t* hCPU)
{
ppcDefineParamTypePtr(bufferArea, uint8, 0);
ppcDefineParamU32(bufferSize, 1);
ppcDefineParamU32(entrySize, 2);
ppcDefineParamU32BEPtr(entryCountOutput, 3);
ppcDefineParamU32(uknR7, 4);
IPCBufPool_t* ipcBufPool = IPCBufPoolCreate(bufferArea, bufferSize, entrySize, entryCountOutput, uknR7);
osLib_returnFromFunction(hCPU, memory_getVirtualOffsetFromPointer(ipcBufPool));
return;
// example dump of IPC buffer (at 0x1011FF40):
// 0000 0B AD F0 0D 10 11 FF 40 00 00 53 01 00 00 00 01 00 00 00 00 00 00 02 C0 00 00 02 C0 00 00 00 1D
// 0020 10 12 00 40 00 00 00 78 00 00 00 00 00 00 00 00 00 00 00 1D 00 00 00 1D 10 11 FF A8 6D 55 74 58
// 0040 10 00 87 2C 00 00 00 00 00 00 00 00 00 00 00 00 10 11 FF 7C 00 00 00 00 00 00 00 00 00 00 00 00
// 0060 00 00 00 00 00 00 00 00 10 12 00 40 10 12 03 00 10 12 05 C0 10 12 08 80 10 12 0B 40 10 12 0E 00
// 0080 10 12 10 C0 10 12 13 80 10 12 16 40 10 12 19 00 10 12 1B C0 10 12 1E 80 10 12 21 40 10 12 24 00
// 00A0 10 12 26 C0 10 12 29 80 10 12 2C 40 10 12 2F 00 10 12 31 C0 10 12 34 80 10 12 37 40 10 12 3A 00
// 00C0 10 12 3C C0 10 12 3F 80 10 12 42 40 10 12 45 00 10 12 47 C0 10 12 4A 80 10 12 4D 40 00 00 00 00
// 00E0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
}
void coreinitExport_IPCBufPoolAllocate(PPCInterpreter_t* hCPU)
{
debug_printf("IPCBufPoolAllocate(0x%08x,0x%x)\n", hCPU->gpr[3], hCPU->gpr[4]);
ppcDefineParamStructPtr(ipcBufPool, IPCBufPool_t, 0);
ppcDefineParamU32(size, 1);
uint8* entry = IPCBufPoolAllocate(ipcBufPool, size);
osLib_returnFromFunction(hCPU, memory_getVirtualOffsetFromPointer(entry));
}
void coreinitExport_IPCBufPoolFree(PPCInterpreter_t* hCPU)
{
debug_printf("IPCBufPoolFree(0x%08x,0x%08x)\n", hCPU->gpr[3], hCPU->gpr[4]);
ppcDefineParamStructPtr(ipcBufPool, IPCBufPool_t, 0);
ppcDefineParamTypePtr(entry, uint8, 1);
sint32 res = IPCBufPoolFree(ipcBufPool, entry);
osLib_returnFromFunction(hCPU, res);
}
void InitializeIPCBuf()
{
osLib_addFunction("coreinit", "IPCBufPoolCreate", coreinitExport_IPCBufPoolCreate);
osLib_addFunction("coreinit", "IPCBufPoolAllocate", coreinitExport_IPCBufPoolAllocate);
osLib_addFunction("coreinit", "IPCBufPoolFree", coreinitExport_IPCBufPoolFree);
}
}

View file

@ -0,0 +1,6 @@
#pragma once
namespace coreinit
{
void InitializeIPCBuf();
}

View file

@ -0,0 +1,198 @@
#include "Cafe/OS/common/OSCommon.h"
#include "Cafe/HW/Espresso/PPCCallback.h"
#include "Cafe/OS/libs/coreinit/coreinit.h"
#include "Cafe/OS/RPL/rpl.h"
#include "Cafe/OS/libs/padscore/padscore.h"
#include "Cafe/OS/libs/coreinit/coreinit_SysHeap.h"
#include "Cafe/OS/libs/coreinit/coreinit_Alarm.h"
#include "Cafe/OS/libs/vpad/vpad.h"
#include "Cafe/OS/libs/coreinit/coreinit_GHS.h"
#include "Cafe/OS/libs/coreinit/coreinit_DynLoad.h"
#include "Cafe/OS/libs/coreinit/coreinit_MEM.h"
#include "Cafe/OS/libs/coreinit/coreinit_FG.h"
#include "Cafe/CafeSystem.h"
extern MPTR _entryPoint;
extern RPLModule* applicationRPX;
typedef struct
{
MPTR argv[32];
uint32be argc;
char argStorage[0x1000];
}coreinitInit_t;
coreinitInit_t* _coreinitInfo = nullptr;
MPTR OSAllocFromSystem(uint32 size, uint32 alignment)
{
return coreinit_allocFromSysArea(size, alignment);
}
void OSFreeToSystem(MPTR mem)
{
coreinit_freeToSysArea(mem);
}
extern std::string _pathToExecutable;
sint32 argStorageIndex;
void _AddArg(const char* arg, sint32 len)
{
uint32 argc = _coreinitInfo->argc;
char* argStorageStr = _coreinitInfo->argStorage + argStorageIndex;
memcpy(argStorageStr, arg, len);
argStorageStr[len] = '\0';
argStorageIndex += (sint32)strlen(arg) + 1;
_coreinitInfo->argv[argc] = _swapEndianU32(memory_getVirtualOffsetFromPointer(argStorageStr));
_coreinitInfo->argc = argc+1;
}
sint32 _GetArgLength(const char* arg)
{
sint32 c = 0;
while (*arg)
{
if (*arg == ' ')
break; // end at whitespace
cemu_assert_debug(*arg != '\"' && *arg != '\''); // todo
arg++;
c++;
}
return c;
}
void CafeInit()
{
// extract executable filename
sint32 rpxPathStart = (sint32)_pathToExecutable.size() - 1;
if (rpxPathStart > 0)
{
while (rpxPathStart > 0 && _pathToExecutable[rpxPathStart-1] != '/')
rpxPathStart--;
}
else
{
rpxPathStart = 0;
}
std::string_view rpxFileName = std::basic_string_view<char>(_pathToExecutable.data() + rpxPathStart, _pathToExecutable.data() + _pathToExecutable.size());
argStorageIndex = 0;
_coreinitInfo->argc = 0;
_AddArg(rpxFileName.data(), rpxFileName.size());
strcpy((char*)_coreinitInfo->argStorage, std::string(rpxFileName).c_str());
std::string _argStr = CafeSystem::GetForegroundTitleArgStr();
const char* argString = _argStr.c_str();
// attach parameters from arg string
if (argString && argString[0] != '\0')
{
const char* t = strstr(argString, ".rpx");
if (t)
{
t += 4;
while (*t)
{
// skip all whitespace
while (*t)
{
if (*t == ' ')
{
t++;
}
else
break;
}
// get length of arg
sint32 argLength = _GetArgLength(t);
if (argLength > 0)
{
// add arg
_AddArg(t, argLength);
// next
t += argLength;
}
}
}
else
{
forceLogDebug_printf("Unable to find end of rpx file name in arg string");
}
}
// setup UGQR
ppcInterpreterCurrentInstance->spr.UGQR[0 + 2] = 0x00040004;
ppcInterpreterCurrentInstance->spr.UGQR[0 + 3] = 0x00050005;
ppcInterpreterCurrentInstance->spr.UGQR[0 + 4] = 0x00060006;
ppcInterpreterCurrentInstance->spr.UGQR[0 + 5] = 0x00070007;
coreinit::InitForegroundBucket();
coreinit::InitSysHeap();
}
struct PreinitUserHeapStruct
{
MEMPTR<coreinit::MEMHeapBase> heapTempMEM1;
MEMPTR<coreinit::MEMHeapBase> heapTempFG;
MEMPTR<coreinit::MEMHeapBase> heapTempMEM2;
};
SysAllocator<PreinitUserHeapStruct> g_preinitUserParam;
void InitCafeHeaps()
{
// init default heaps
g_preinitUserParam->heapTempMEM1 = nullptr;
g_preinitUserParam->heapTempFG = nullptr;
g_preinitUserParam->heapTempMEM2 = nullptr;
coreinit::InitDefaultHeaps(g_preinitUserParam->heapTempMEM1, g_preinitUserParam->heapTempFG, g_preinitUserParam->heapTempMEM2);
// if __preinit_user export exists in main executable, run it and pass our heaps
MPTR exportAddr = applicationRPX ? RPLLoader_FindRPLExport(applicationRPX, "__preinit_user", false) : MPTR_NULL;
if (exportAddr != MPTR_NULL)
{
PPCCoreCallback(exportAddr, &g_preinitUserParam->heapTempMEM1, &g_preinitUserParam->heapTempFG, &g_preinitUserParam->heapTempMEM2);
}
// setup heaps
if (g_preinitUserParam->heapTempMEM1 != nullptr)
coreinit::MEMSetBaseHeapHandle(0, g_preinitUserParam->heapTempMEM1);
if (g_preinitUserParam->heapTempFG != nullptr)
coreinit::MEMSetBaseHeapHandle(8, g_preinitUserParam->heapTempFG);
if (g_preinitUserParam->heapTempMEM2 != nullptr)
coreinit::MEMSetBaseHeapHandle(1, g_preinitUserParam->heapTempMEM2);
}
MPTR CoreInitEntry(sint32 argc, MPTR argv)
{
const char* rpxPath = (char*)memory_getPointerFromVirtualOffset(memory_readU32(argv + 0));
InitCafeHeaps();
// do a dummy allocation via the OSDynLoad allocator
// Watch Dogs relies on this to correctly set up its malloc() allocator
// must be larger than 0x190 to trigger creation of a new memory segment. But also must not be close to page alignment (0x1000) or else the bug will trigger
void* dummyAlloc = coreinit::OSDynLoad_AllocatorAlloc(0x500, 0x4);
if (dummyAlloc)
coreinit::OSDynLoad_AllocatorFree(dummyAlloc);
return _entryPoint;
}
sint32 _coreinitTitleEntryPoint;
void coreinit_start(PPCInterpreter_t* hCPU)
{
_coreinitInfo = (coreinitInit_t*)memory_getPointerFromVirtualOffset(coreinit_allocFromSysArea(sizeof(coreinitInit_t), 32));
memset(_coreinitInfo, 0, sizeof(coreinitInit_t));
CafeInit();
_coreinitTitleEntryPoint = CoreInitEntry(_coreinitInfo->argc, memory_getVirtualOffsetFromPointer(_coreinitInfo->argv));
RPLLoader_CallEntrypoints();
// init vpadbase (todo - simulate entrypoints for HLE modules)
padscore::start();
vpad::start();
// continue at main executable entrypoint
hCPU->gpr[4] = memory_getVirtualOffsetFromPointer(_coreinitInfo->argv);
hCPU->gpr[3] = _coreinitInfo->argc;
hCPU->instructionPointer = _coreinitTitleEntryPoint;
}

View file

@ -0,0 +1,299 @@
#include "Cafe/OS/common/OSCommon.h"
#include "Cafe/OS/libs/coreinit/coreinit.h"
#include "Cafe/HW/Latte/Core/LatteBufferCache.h"
/*
Locked cache is mapped to the following memory regions:
offset size
core0: 0xFFC00000 0x4000
core1: 0xFFC40000 0x4000
core2: 0xFFC80000 0x4000
*/
#define LC_LOCKED_CACHE_GRANULARITY (0x200) // 512B
#define LC_LOCKED_CACHE_SIZE (0x4000) // 16KB
#define LC_MASK_FREE (0)
#define LC_MASK_RESERVED (1)
#define LC_MASK_RESERVED_END (2) // indicates end of reserved block
namespace coreinit
{
uint8 lcCacheMask[PPC_CORE_COUNT][(LC_LOCKED_CACHE_SIZE + LC_LOCKED_CACHE_GRANULARITY - 1) / LC_LOCKED_CACHE_GRANULARITY] = { 0 };
uint32 lcAllocatedBlocks[PPC_CORE_COUNT] = { 0 };
MPTR lcAddr[] =
{
0xFFC00000, // core 0
0xFFC40000, // core 1
0xFFC80000 // core 2
};
void coreinitExport_LCGetMaxSize(PPCInterpreter_t* hCPU)
{
osLib_returnFromFunction(hCPU, LC_LOCKED_CACHE_SIZE);
}
void coreinitExport_LCAlloc(PPCInterpreter_t* hCPU)
{
//debug_printf("LCAlloc(0x%04x) Thread %08x Core %d", hCPU->gpr[3], coreinitThread_getCurrentThread(hCPU), PPCInterpreter_getCoreIndex(hCPU));
uint32 size = hCPU->gpr[3];
if (size == 0 || size > LC_LOCKED_CACHE_SIZE)
{
debug_printf("LCAlloc: Invalid alloc size %d\n", size);
osLib_returnFromFunction(hCPU, MPTR_NULL);
return;
}
if ((size % LC_LOCKED_CACHE_GRANULARITY) != 0)
{
debug_printf("LCAlloc: Unaligned alloc size 0x%04x\n", size);
size += (LC_LOCKED_CACHE_GRANULARITY - 1);
size &= ~(LC_LOCKED_CACHE_GRANULARITY - 1);
}
uint32 coreIndex = PPCInterpreter_getCoreIndex(hCPU);
uint8* cacheMask = lcCacheMask[coreIndex];
uint32 entryMaskLength = size / LC_LOCKED_CACHE_GRANULARITY;
for (uint32 i = 0; i <= (LC_LOCKED_CACHE_SIZE / LC_LOCKED_CACHE_GRANULARITY) - entryMaskLength; i++)
{
// check if range starting at i is free
bool rangeIsFree = true;
for (uint32 f = 0; f < entryMaskLength; f++)
{
if (cacheMask[i + f] != LC_MASK_FREE)
{
rangeIsFree = false;
break;
}
}
if (rangeIsFree)
{
// found space for allocation
MPTR allocAddr = lcAddr[coreIndex] + i * LC_LOCKED_CACHE_GRANULARITY;
// mark range as allocated
for (uint32 f = 0; f < entryMaskLength - 1; f++)
{
cacheMask[i + f] = LC_MASK_RESERVED;
}
cacheMask[i + entryMaskLength - 1] = LC_MASK_RESERVED_END;
// update allocation counter
lcAllocatedBlocks[coreIndex] += entryMaskLength;
// return allocAddr
//debug_printf("LCAlloc result %08x Thread %08x Core %d", (uint32)allocAddr, coreinitThread_getCurrentThread(hCPU), PPCInterpreter_getCoreIndex(hCPU));
osLib_returnFromFunction(hCPU, allocAddr);
return;
}
}
// not enough space left
//debug_printf("LCAlloc failed Thread %08x Core %d", coreinitThread_getCurrentThread(hCPU), PPCInterpreter_getCoreIndex(hCPU));
osLib_returnFromFunction(hCPU, MPTR_NULL);
}
void coreinitExport_LCDealloc(PPCInterpreter_t* hCPU)
{
uint32 coreIndex = PPCInterpreter_getCoreIndex(hCPU);
uint8* cacheMask = lcCacheMask[coreIndex];
//printf("LCDealloc(0x%08x)\n", hCPU->gpr[3]);
MPTR deallocAddr = hCPU->gpr[3];
if (deallocAddr < lcAddr[coreIndex] || deallocAddr >= (lcAddr[coreIndex] + LC_LOCKED_CACHE_SIZE))
{
// out of bounds
#ifndef PUBLIC_RELEASE
forceLog_printf("LCDealloc(): Out of bounds");
#endif
osLib_returnFromFunction(hCPU, 0);
return;
}
uint32 relativeOffset = (uint32)deallocAddr - lcAddr[coreIndex];
if ((relativeOffset % LC_LOCKED_CACHE_GRANULARITY) != 0)
{
debug_printf("LCDealloc: Offset alignment is invalid (0x%08x / 0x%08x)\n", deallocAddr, relativeOffset);
osLib_returnFromFunction(hCPU, 0);
return;
}
uint32 startIndex = relativeOffset / LC_LOCKED_CACHE_GRANULARITY;
// check this is really the beginning of an allocated block
if (startIndex > 0 && (cacheMask[startIndex - 1] != LC_MASK_RESERVED_END && cacheMask[startIndex - 1] != LC_MASK_FREE))
{
debug_printf("LCDealloc: Offset is invalid (0x%08x / 0x%08x)\n", deallocAddr, relativeOffset);
osLib_returnFromFunction(hCPU, 0);
return;
}
// free range by reseting mask in allocated region
for (uint32 i = startIndex; i < (LC_LOCKED_CACHE_SIZE / LC_LOCKED_CACHE_GRANULARITY); i++)
{
if (cacheMask[i] == LC_MASK_RESERVED_END)
{
// end of allocated block reached
cacheMask[i] = LC_MASK_FREE;
// update allocation counter
lcAllocatedBlocks[coreIndex]--;
break;
}
else if (cacheMask[i] == LC_MASK_FREE)
{
debug_printf("LCDealloc: Allocation mask error detected\n");
break;
}
cacheMask[i] = LC_MASK_FREE;
// update allocation counter
lcAllocatedBlocks[coreIndex]--;
}
osLib_returnFromFunction(hCPU, 0);
}
void coreinitExport_LCGetUnallocated(PPCInterpreter_t* hCPU)
{
uint32 unallocatedBytes = 0;
uint32 coreIndex = PPCInterpreter_getCoreIndex(hCPU);
unallocatedBytes = LC_LOCKED_CACHE_SIZE - (lcAllocatedBlocks[coreIndex] * LC_LOCKED_CACHE_GRANULARITY);
//debug_printf("LCGetUnallocated() Result: 0x%x\n", unallocatedBytes);
osLib_returnFromFunction(hCPU, unallocatedBytes);
}
void coreinitExport_LCGetAllocatableSize(PPCInterpreter_t* hCPU)
{
debug_printf("LCGetAllocatableSize()\n");
// no parameters, returns largest allocatable block size
// get core LC parameters
uint32 coreIndex = PPCInterpreter_getCoreIndex(hCPU);
uint8* cacheMask = lcCacheMask[coreIndex];
// scan for largest range of available blocks
uint32 largestFreeRange = 0;
uint32 currentRangeSize = 0;
for (uint32 i = 0; i < LC_LOCKED_CACHE_SIZE / LC_LOCKED_CACHE_GRANULARITY; i++)
{
if (cacheMask[i] == LC_MASK_FREE)
{
currentRangeSize++;
}
else
{
largestFreeRange = std::max(largestFreeRange, currentRangeSize);
currentRangeSize = 0;
}
}
largestFreeRange = std::max(largestFreeRange, currentRangeSize);
osLib_returnFromFunction(hCPU, largestFreeRange * LC_LOCKED_CACHE_GRANULARITY);
}
uint32 LCIsEnabled[PPC_CORE_COUNT] = { 0 };
void coreinitExport_LCEnableDMA(PPCInterpreter_t* hCPU)
{
//debug_printf("LCEnableDMA()\n");
LCIsEnabled[PPCInterpreter_getCoreIndex(hCPU)]++;
osLib_returnFromFunction(hCPU, 1);
}
sint32 _lcDisableErrorCounter = 0;
void coreinitExport_LCDisableDMA(PPCInterpreter_t* hCPU)
{
//debug_printf("LCDisableDMA()\n");
#ifndef PUBLIC_RELASE
if (LCIsEnabled[PPCInterpreter_getCoreIndex(hCPU)] == 0)
assert_dbg();
#endif
LCIsEnabled[PPCInterpreter_getCoreIndex(hCPU)]--;
#ifndef PUBLIC_RELEASE
if (LCIsEnabled[PPCInterpreter_getCoreIndex(hCPU)] == 0)
{
uint32 coreIndex = PPCInterpreter_getCoreIndex(hCPU);
uint8* cacheMask = lcCacheMask[coreIndex];
for (uint32 i = 0; i <= (LC_LOCKED_CACHE_SIZE / LC_LOCKED_CACHE_GRANULARITY); i++)
{
if (cacheMask[i] != LC_MASK_FREE)
{
if (_lcDisableErrorCounter < 15)
{
forceLog_printf("LC disabled but there is still memory allocated");
_lcDisableErrorCounter++;
}
}
}
}
#endif
osLib_returnFromFunction(hCPU, 1);
}
void coreinitExport_LCIsDMAEnabled(PPCInterpreter_t* hCPU)
{
osLib_returnFromFunction(hCPU, LCIsEnabled[PPCInterpreter_getCoreIndex(hCPU)] ? 1 : 0);
}
void coreinitExport_LCHardwareIsAvailable(PPCInterpreter_t* hCPU)
{
// on the real HW, LC is shared between processes and not all processes can access it. In the emulator we don't care and always return true.
osLib_returnFromFunction(hCPU, 1);
}
void coreinitExport_LCLoadDMABlocks(PPCInterpreter_t* hCPU)
{
//printf("LCLoadDMABlocks(0x%08x, 0x%08x, 0x%08x)\n", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]);
uint32 numBlocks = hCPU->gpr[5];
if (numBlocks == 0)
numBlocks = 128;
uint32 transferSize = numBlocks * 32;
uint8* destPtr = memory_getPointerFromVirtualOffset(hCPU->gpr[3]);
uint8* srcPtr = memory_getPointerFromVirtualOffset(hCPU->gpr[4]);
// copy right away, we don't emulate the DMAQueue currently
memcpy(destPtr, srcPtr, transferSize);
osLib_returnFromFunction(hCPU, 0);
}
void coreinitExport_LCStoreDMABlocks(PPCInterpreter_t* hCPU)
{
//printf("LCStoreDMABlocks(0x%08x, 0x%08x, 0x%08x)\n", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]);
uint32 numBlocks = hCPU->gpr[5];
if (numBlocks == 0)
numBlocks = 128;
//uint32 transferSize = numBlocks*32;
uint8* destPtr = memory_getPointerFromVirtualOffset(hCPU->gpr[3]);
uint8* srcPtr = memory_getPointerFromVirtualOffset(hCPU->gpr[4]);
// copy right away, we don't emulate the DMAQueue currently
memcpy_qwords(destPtr, srcPtr, numBlocks * (32 / sizeof(uint64)));
LatteBufferCache_notifyDCFlush(hCPU->gpr[3], numBlocks * 32);
osLib_returnFromFunction(hCPU, 0);
}
void coreinitExport_LCWaitDMAQueue(PPCInterpreter_t* hCPU)
{
//printf("LCWaitDMAQueue(%d)\n", hCPU->gpr[3]);
uint32 len = hCPU->gpr[3];
// todo: Implement (be aware DMA queue is per core)
osLib_returnFromFunction(hCPU, 0);
}
void InitializeLC()
{
for (sint32 f = 0; f < PPC_CORE_COUNT; f++)
{
memset(lcCacheMask[f], LC_MASK_FREE, (LC_LOCKED_CACHE_SIZE + LC_LOCKED_CACHE_GRANULARITY - 1) / LC_LOCKED_CACHE_GRANULARITY);
}
osLib_addFunction("coreinit", "LCGetMaxSize", coreinitExport_LCGetMaxSize);
osLib_addFunction("coreinit", "LCAlloc", coreinitExport_LCAlloc);
osLib_addFunction("coreinit", "LCDealloc", coreinitExport_LCDealloc);
osLib_addFunction("coreinit", "LCGetUnallocated", coreinitExport_LCGetUnallocated);
osLib_addFunction("coreinit", "LCGetAllocatableSize", coreinitExport_LCGetAllocatableSize);
osLib_addFunction("coreinit", "LCEnableDMA", coreinitExport_LCEnableDMA);
osLib_addFunction("coreinit", "LCDisableDMA", coreinitExport_LCDisableDMA);
osLib_addFunction("coreinit", "LCIsDMAEnabled", coreinitExport_LCIsDMAEnabled);
osLib_addFunction("coreinit", "LCHardwareIsAvailable", coreinitExport_LCHardwareIsAvailable);
osLib_addFunction("coreinit", "LCLoadDMABlocks", coreinitExport_LCLoadDMABlocks);
osLib_addFunction("coreinit", "LCStoreDMABlocks", coreinitExport_LCStoreDMABlocks);
osLib_addFunction("coreinit", "LCWaitDMAQueue", coreinitExport_LCWaitDMAQueue);
}
}

View file

@ -0,0 +1,6 @@
#pragma once
namespace coreinit
{
void InitializeLC();
}

View file

@ -0,0 +1,561 @@
#include "Cafe/OS/common/OSCommon.h"
#include "Cafe/IOSU/legacy/iosu_ioctl.h"
#include "Cafe/IOSU/legacy/iosu_mcp.h"
#include "Cafe/OS/libs/coreinit/coreinit_IOS.h"
#include "Cafe/OS/libs/coreinit/coreinit_MCP.h"
#include "Cafe/OS/libs/nn_common.h"
#include "Cafe/OS/libs/nn_act/nn_act.h"
#include "config/CemuConfig.h"
#include "Cafe/CafeSystem.h"
#define mcpPrepareRequest() \
StackAllocator<iosuMcpCemuRequest_t> _buf_mcpRequest; \
StackAllocator<ioBufferVector_t> _buf_bufferVector; \
iosuMcpCemuRequest_t* mcpRequest = _buf_mcpRequest.GetPointer(); \
ioBufferVector_t* mcpBufferVector = _buf_bufferVector.GetPointer(); \
memset(mcpRequest, 0, sizeof(iosuMcpCemuRequest_t)); \
memset(mcpBufferVector, 0, sizeof(ioBufferVector_t)); \
mcpBufferVector->buffer = (uint8*)mcpRequest;
#define MCP_FAKE_HANDLE (0x00000010)
typedef struct
{
uint32be n0;
uint32be n1;
uint32be n2;
}mcpSystemVersion_t;
static_assert(sizeof(MCPTitleInfo) == 0x61, "title entry size is invalid");
static_assert(offsetof(MCPTitleInfo, appPath) == 0xC, "title entry appPath has invalid offset");
static_assert(offsetof(MCPTitleInfo, titleVersion) == 0x48, "title entry titleVersion has invalid offset");
static_assert(offsetof(MCPTitleInfo, osVersion) == 0x4A, "title entry osVersion has invalid offset");
MCPHANDLE MCP_Open()
{
// placeholder
return MCP_FAKE_HANDLE;
}
void MCP_Close(MCPHANDLE mcpHandle)
{
// placeholder
}
void coreinitExport_MCP_Open(PPCInterpreter_t* hCPU)
{
// placeholder
osLib_returnFromFunction(hCPU, MCP_Open());
}
void coreinitExport_MCP_Close(PPCInterpreter_t* hCPU)
{
// placeholder
osLib_returnFromFunction(hCPU, 0);
}
sint32 MCP_GetSysProdSettings(MCPHANDLE mcpHandle, SysProdSettings* sysProdSettings)
{
memset(sysProdSettings, 0x00, sizeof(SysProdSettings));
// todo: Other fields are currently unknown
sysProdSettings->gameRegion = (uint8)CafeSystem::GetForegroundTitleRegion();
sysProdSettings->platformRegion = (uint8)CafeSystem::GetPlatformRegion();
// contains Wii U serial parts at +0x1A and +0x22?
return 0;
}
void coreinitExport_MCP_GetSysProdSettings(PPCInterpreter_t* hCPU)
{
forceLogDebug_printf("MCP_GetSysProdSettings(0x%08x,0x%08x)", hCPU->gpr[3], hCPU->gpr[4]);
sint32 result = MCP_GetSysProdSettings(hCPU->gpr[3], (SysProdSettings*)memory_getPointerFromVirtualOffset(hCPU->gpr[4]));
osLib_returnFromFunction(hCPU, result);
}
void coreinitExport_MCP_TitleListByAppType(PPCInterpreter_t* hCPU)
{
forceLogDebug_printf("MCP_TitleListByAppType(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]);
ppcDefineParamU32(mcpHandle, 0);
ppcDefineParamU32(appType, 1);
ppcDefineParamU32BEPtr(countOutput, 2);
ppcDefineParamStructPtr(titleList, MCPTitleInfo, 3);
ppcDefineParamU32(titleListSize, 4);
sint32 maxCount = titleListSize / sizeof(MCPTitleInfo);
mcpPrepareRequest();
mcpRequest->requestCode = IOSU_MCP_GET_TITLE_LIST_BY_APP_TYPE;
mcpRequest->titleListRequest.titleCount = maxCount;
mcpRequest->titleListRequest.titleList = titleList;
mcpRequest->titleListRequest.titleListBufferSize = sizeof(MCPTitleInfo) * 1;
mcpRequest->titleListRequest.appType = appType;
__depr__IOS_Ioctlv(IOS_DEVICE_MCP, IOSU_MCP_REQUEST_CEMU, 1, 1, mcpBufferVector);
*countOutput = mcpRequest->titleListRequest.titleCount;
osLib_returnFromFunction(hCPU, 0);
}
void coreinitExport_MCP_TitleList(PPCInterpreter_t* hCPU)
{
forceLogDebug_printf("MCP_TitleList(...) unimplemented");
ppcDefineParamU32(mcpHandle, 0);
ppcDefineParamU32BEPtr(countOutput, 1);
ppcDefineParamStructPtr(titleList, MCPTitleInfo, 2);
ppcDefineParamU32(titleListBufferSize, 3);
// todo -> Other parameters
mcpPrepareRequest();
mcpRequest->requestCode = IOSU_MCP_GET_TITLE_LIST;
mcpRequest->titleListRequest.titleCount = *countOutput;
mcpRequest->titleListRequest.titleList = titleList;
mcpRequest->titleListRequest.titleListBufferSize = titleListBufferSize;
__depr__IOS_Ioctlv(IOS_DEVICE_MCP, IOSU_MCP_REQUEST_CEMU, 1, 1, mcpBufferVector);
*countOutput = mcpRequest->titleListRequest.titleCount;
osLib_returnFromFunction(hCPU, mcpRequest->returnCode);
}
void coreinitExport_MCP_TitleCount(PPCInterpreter_t* hCPU)
{
forceLogDebug_printf("MCP_TitleCount(): Untested");
ppcDefineParamU32(mcpHandle, 0);
mcpPrepareRequest();
mcpRequest->requestCode = IOSU_MCP_GET_TITLE_COUNT;
mcpRequest->titleListRequest.titleCount = 0;
__depr__IOS_Ioctlv(IOS_DEVICE_MCP, IOSU_MCP_REQUEST_CEMU, 1, 1, mcpBufferVector);
osLib_returnFromFunction(hCPU, mcpRequest->titleListRequest.titleCount);
}
void coreinitExport_MCP_GetTitleInfo(PPCInterpreter_t* hCPU)
{
ppcDefineParamU32(mcpHandle, 0);
ppcDefineParamU64(titleId, 2);
ppcDefineParamStructPtr(titleList, MCPTitleInfo, 4);
forceLogDebug_printf("MCP_GetTitleInfo() called");
mcpPrepareRequest();
mcpRequest->requestCode = IOSU_MCP_GET_TITLE_INFO;
mcpRequest->titleListRequest.titleCount = 1;
mcpRequest->titleListRequest.titleList = titleList;
mcpRequest->titleListRequest.titleListBufferSize = sizeof(MCPTitleInfo);
mcpRequest->titleListRequest.titleId = titleId;
__depr__IOS_Ioctlv(IOS_DEVICE_MCP, IOSU_MCP_REQUEST_CEMU, 1, 1, mcpBufferVector);
if (mcpRequest->titleListRequest.titleCount == 0)
{
forceLog_printf("MCP_GetTitleInfo() failed to get title info");
}
osLib_returnFromFunction(hCPU, mcpRequest->returnCode);
}
void coreinitExport_MCP_GetTitleInfoByTitleAndDevice(PPCInterpreter_t* hCPU)
{
ppcDefineParamU32(mcpHandle, 0);
ppcDefineParamU64(titleId, 2);
ppcDefineParamStr(device, 4); // e.g. "odd"
ppcDefineParamStructPtr(titleList, MCPTitleInfo, 5);
forceLogDebug_printf("MCP_GetTitleInfoByTitleAndDevice() called (todo - device type support)");
mcpPrepareRequest();
mcpRequest->requestCode = IOSU_MCP_GET_TITLE_INFO;
mcpRequest->titleListRequest.titleCount = 1;
mcpRequest->titleListRequest.titleList = titleList;
mcpRequest->titleListRequest.titleListBufferSize = sizeof(MCPTitleInfo);
mcpRequest->titleListRequest.titleId = titleId;
__depr__IOS_Ioctlv(IOS_DEVICE_MCP, IOSU_MCP_REQUEST_CEMU, 1, 1, mcpBufferVector);
if (mcpRequest->titleListRequest.titleCount == 0)
{
forceLogDebug_printf("MCP_GetTitleInfoByTitleAndDevice() no title found");
osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_MCP, 0)); // E-Shop/nn_vctl.rpl expects error to be returned when no title is found
return;
}
osLib_returnFromFunction(hCPU, mcpRequest->returnCode);
}
namespace coreinit
{
void export_MCP_GetSystemVersion(PPCInterpreter_t* hCPU)
{
forceLogDebug_printf("MCP_GetSystemVersion(%d,0x%08x)", hCPU->gpr[3], hCPU->gpr[4]);
ppcDefineParamU32(mcpHandle, 0);
ppcDefineParamStructPtr(systemVersion, mcpSystemVersion_t, 1);
systemVersion->n0 = 0x5;
systemVersion->n1 = 0x5;
systemVersion->n2 = 0x2;
// todo: Load this from \sys\title\00050010\10041200\content\version.bin
osLib_returnFromFunction(hCPU, 0);
}
void export_MCP_Get4SecondOffStatus(PPCInterpreter_t* hCPU)
{
// r3 = mcpHandle
forceLogDebug_printf("MCP_Get4SecondOffStatus(...) placeholder");
// if this returns 1 then Barista will display the warning about cold-shutdown ('Holding the POWER button for at least four seconds...')
osLib_returnFromFunction(hCPU, 0);
}
void export_MCP_TitleListByDevice(PPCInterpreter_t* hCPU)
{
ppcDefineParamU32(mcpHandle, 0);
ppcDefineParamStr(deviceName, 1);
ppcDefineParamU32BEPtr(titleCount, 2);
ppcDefineParamStructPtr(titleList, MCPTitleInfo, 3); // type guessed
// purpose of r7 parameter is unknown
cemu_assert_unimplemented();
titleList[0].titleIdHigh = 0x00050000;
titleList[0].titleIdLow = 0x11223344;
strcpy(titleList[0].appPath, "titlePathTest");
*titleCount = 1;
forceLogDebug_printf("MCP_TitleListByDevice() - placeholder");
osLib_returnFromFunction(hCPU, 0);
}
#pragma pack(1)
typedef struct
{
/* +0x000 */ char storageName[0x90]; // the name in the storage path
/* +0x090 */ char storagePath[0x280 - 1]; // /vol/storage_%s%02x
/* +0x30F */ uint32be storageSubindexOrMask; // the id in the storage path, but this might also be a MASK of indices (e.g. 1 -> Only device 1, 7 -> Device 1,2,3) men.rpx expects 0xF (or 0x7?) to be set for MLC, SLC and USB for MLC_FullDeviceList
uint8 ukn313[4];
uint8 ukn317[4];
}MCPDevice_t;
#pragma pack()
static_assert(sizeof(MCPDevice_t) == 0x31B, "MCPDevice_t has invalid size");
static_assert(offsetof(MCPDevice_t, storagePath) == 0x090, "MCPDevice_t.storagePath has invalid offset");
static_assert(offsetof(MCPDevice_t, storageSubindexOrMask) == 0x30F, "MCPDevice_t.storageSubindex has invalid offset");
static_assert(offsetof(MCPDevice_t, ukn313) == 0x313, "MCPDevice_t.ukn313 has invalid offset");
static_assert(offsetof(MCPDevice_t, ukn317) == 0x317, "MCPDevice_t.ukn317 has invalid offset");
void MCP_DeviceListEx(uint32 mcpHandle, uint32be* deviceCount, MCPDevice_t* deviceList, uint32 deviceListSize, bool returnFullList)
{
sint32 maxDeviceCount = deviceListSize / sizeof(MCPDevice_t);
if (maxDeviceCount < 3*3)
assert_dbg();
// if this doesnt return both MLC and SLC friendlist (frd.rpx) will softlock during boot
memset(deviceList, 0, sizeof(MCPDevice_t) * 1);
sint32 index = 0;
for (sint32 f = 0; f < 1; f++)
{
// 0
strcpy(deviceList[index].storageName, "mlc");
deviceList[index].storageSubindexOrMask = 0xF; // bitmask?
sprintf(deviceList[index].storagePath, "/vol/storage_%s%02x", deviceList[index].storageName, (sint32)deviceList[index].storageSubindexOrMask);
index++;
// 1
strcpy(deviceList[index].storageName, "slc");
deviceList[index].storageSubindexOrMask = 0xF; // bitmask?
sprintf(deviceList[index].storagePath, "/vol/storage_%s%02x", deviceList[index].storageName, (sint32)deviceList[index].storageSubindexOrMask);
index++;
// 2
strcpy(deviceList[index].storageName, "usb");
deviceList[index].storageSubindexOrMask = 0xF;
sprintf(deviceList[index].storagePath, "/vol/storage_%s%02x", deviceList[index].storageName, (sint32)deviceList[index].storageSubindexOrMask);
index++;
}
*deviceCount = index;
}
void export_MCP_DeviceList(PPCInterpreter_t* hCPU)
{
ppcDefineParamU32(mcpHandle, 0);
ppcDefineParamU32BEPtr(deviceCount, 1);
ppcDefineParamStructPtr(deviceList, MCPDevice_t, 2);
ppcDefineParamU32(deviceListSize, 3);
forceLogDebug_printf("MCP_DeviceList() - placeholder LR %08x", hCPU->spr.LR);
sint32 maxDeviceCount = deviceListSize / sizeof(MCPDevice_t);
if (maxDeviceCount < 2)
assert_dbg();
// if this doesnt return both MLC and SLC friendlist (frd.rpx) will softlock during boot
memset(deviceList, 0, sizeof(MCPDevice_t) * 1);
// 0
strcpy(deviceList[0].storageName, "mlc");
deviceList[0].storageSubindexOrMask = (0x01); // bitmask?
sprintf(deviceList[0].storagePath, "/vol/storage_%s%02x", deviceList[0].storageName, (sint32)deviceList[0].storageSubindexOrMask);
// 1
strcpy(deviceList[1].storageName, "slc");
deviceList[1].storageSubindexOrMask = (0x01); // bitmask?
sprintf(deviceList[1].storagePath, "/vol/storage_%s%02x", deviceList[1].storageName, (sint32)deviceList[1].storageSubindexOrMask);
// 2
//strcpy(deviceList[2].storageName, "usb");
//deviceList[2].storageSubindex = 1;
//sprintf(deviceList[2].storagePath, "/vol/storage_%s%02x", deviceList[2].storageName, (sint32)deviceList[2].storageSubindex);
*deviceCount = 2;
osLib_returnFromFunction(hCPU, 0);
}
void export_MCP_FullDeviceList(PPCInterpreter_t* hCPU)
{
ppcDefineParamU32(mcpHandle, 0);
ppcDefineParamU32BEPtr(deviceCount, 1);
ppcDefineParamStructPtr(deviceList, MCPDevice_t, 2);
ppcDefineParamU32(deviceListSize, 3);
forceLogDebug_printf("MCP_FullDeviceList() - placeholder LR %08x", hCPU->spr.LR);
MCP_DeviceListEx(mcpHandle, deviceCount, deviceList, deviceListSize, true);
osLib_returnFromFunction(hCPU, 0);
}
void export_MCP_UpdateCheckContext(PPCInterpreter_t* hCPU)
{
ppcDefineParamU32(mcpHandle, 0);
ppcDefineParamU32BEPtr(unknownParam, 1);
forceLogDebug_printf("MCP_UpdateCheckContext() - placeholder (might be wrong)");
*unknownParam = 1;
osLib_returnFromFunction(hCPU, 0);
}
void export_MCP_TitleListUpdateGetNext(PPCInterpreter_t* hCPU)
{
ppcDefineParamU32(mcpHandle, 0);
ppcDefineParamMPTR(callbackMPTR, 1);
forceLogDebug_printf("MCP_TitleListUpdateGetNext() - placeholder/unimplemented");
// this callback is to let the app know when the title list changed?
osLib_returnFromFunction(hCPU, 0);
}
void export_MCP_GetOverlayAppInfo(PPCInterpreter_t* hCPU)
{
forceLogDebug_printf("MCP_GetOverlayAppInfo(...) placeholder");
ppcDefineParamU32(mcpHandle, 0);
ppcDefineParamU32(appType, 1);
ppcDefineParamU32(uknR5, 2);
ppcDefineParamStructPtr(titleList, MCPTitleInfo, 3);
// hacky
mcpPrepareRequest();
mcpRequest->requestCode = IOSU_MCP_GET_TITLE_LIST_BY_APP_TYPE;
mcpRequest->titleListRequest.titleCount = 1;
mcpRequest->titleListRequest.titleList = titleList;
mcpRequest->titleListRequest.titleListBufferSize = sizeof(MCPTitleInfo)*1;
mcpRequest->titleListRequest.appType = appType;
__depr__IOS_Ioctlv(IOS_DEVICE_MCP, IOSU_MCP_REQUEST_CEMU, 1, 1, mcpBufferVector);
if (mcpRequest->titleListRequest.titleCount != 1)
assert_dbg();
osLib_returnFromFunction(hCPU, 0);
}
void InitializeMCP()
{
osLib_addFunction("coreinit", "MCP_Open", coreinitExport_MCP_Open);
osLib_addFunction("coreinit", "MCP_Close", coreinitExport_MCP_Close);
osLib_addFunction("coreinit", "MCP_GetSysProdSettings", coreinitExport_MCP_GetSysProdSettings);
osLib_addFunction("coreinit", "MCP_TitleListByAppType", coreinitExport_MCP_TitleListByAppType);
osLib_addFunction("coreinit", "MCP_TitleList", coreinitExport_MCP_TitleList);
osLib_addFunction("coreinit", "MCP_TitleCount", coreinitExport_MCP_TitleCount);
osLib_addFunction("coreinit", "MCP_GetTitleInfo", coreinitExport_MCP_GetTitleInfo);
osLib_addFunction("coreinit", "MCP_GetTitleInfoByTitleAndDevice", coreinitExport_MCP_GetTitleInfoByTitleAndDevice);
osLib_addFunction("coreinit", "MCP_TitleListByDevice", export_MCP_TitleListByDevice);
osLib_addFunction("coreinit", "MCP_GetSystemVersion", export_MCP_GetSystemVersion);
osLib_addFunction("coreinit", "MCP_Get4SecondOffStatus", export_MCP_Get4SecondOffStatus);
osLib_addFunction("coreinit", "MCP_DeviceList", export_MCP_DeviceList);
osLib_addFunction("coreinit", "MCP_FullDeviceList", export_MCP_FullDeviceList);
osLib_addFunction("coreinit", "MCP_UpdateCheckContext", export_MCP_UpdateCheckContext);
osLib_addFunction("coreinit", "MCP_TitleListUpdateGetNext", export_MCP_TitleListUpdateGetNext);
osLib_addFunction("coreinit", "MCP_GetOverlayAppInfo", export_MCP_GetOverlayAppInfo);
}
}
typedef struct
{
char settingName[0x40];
uint32 ukn1;
uint32 ukn2;
uint32 ukn3;
uint32 ukn4_size; // size guessed
MPTR resultPtr; // pointer to output value
}UCParamStruct_t;
static_assert(sizeof(UCParamStruct_t) == 0x54); // unsure
#if BOOST_OS_LINUX
#define _strcmpi strcasecmp
#endif
void coreinitExport_UCReadSysConfig(PPCInterpreter_t* hCPU)
{
// parameters:
// r3 = UC handle?
// r4 = Number of values to read (count of UCParamStruct_t entries)
// r5 = UCParamStruct_t*
UCParamStruct_t* ucParamBase = (UCParamStruct_t*)memory_getPointerFromVirtualOffset(hCPU->gpr[5]);
uint32 ucParamCount = hCPU->gpr[4];
for(uint32 i=0; i<ucParamCount; i++)
{
UCParamStruct_t* ucParam = ucParamBase+i;
if(_strcmpi(ucParam->settingName, "cafe.cntry_reg") == 0)
{
// country code
// get country from simple address
uint32be simpleAddress = 0;
nn::act::GetSimpleAddressIdEx(&simpleAddress, nn::act::ACT_SLOT_CURRENT);
uint32 countryCode = nn::act::getCountryCodeFromSimpleAddress(simpleAddress);
if( ucParam->resultPtr != _swapEndianU32(MPTR_NULL) )
memory_writeU32(_swapEndianU32(ucParam->resultPtr), countryCode);
}
else if (_strcmpi(ucParam->settingName, "cafe.language") == 0)
{
// language
// 0 -> Japanese
// 1 -> English
// ...
uint32 languageId = (uint32)GetConfig().console_language.GetValue();
if (ucParam->resultPtr != _swapEndianU32(MPTR_NULL))
memory_writeU32(_swapEndianU32(ucParam->resultPtr), languageId);
}
else if (_strcmpi(ucParam->settingName, "cafe.initial_launch") == 0)
{
// initial launch
// possible ids:
// 0xFF Set by SCIFormatSystem (default?)
// 0x01 Inited but no user created yet (setting this will make the home menu go into user creation dialog)
// 0x02 Inited, with user
// other values are unknown
memory_writeU8(_swapEndianU32(ucParam->resultPtr), 2);
}
else if (_strcmpi(ucParam->settingName, "cafe.eula_version") == 0)
{
// todo - investigate values
memory_writeU32(_swapEndianU32(ucParam->resultPtr), 0);
}
else if (_strcmpi(ucParam->settingName, "cafe.eula_agree") == 0)
{
// todo - investigate values
memory_writeU8(_swapEndianU32(ucParam->resultPtr), 0);
}
else if (_strcmpi(ucParam->settingName, "cafe.version") == 0)
{
// todo - investigate values
memory_writeU16(_swapEndianU32(ucParam->resultPtr), 0);
}
else if (_strcmpi(ucParam->settingName, "cafe.eco") == 0)
{
// todo - investigate values
memory_writeU8(_swapEndianU32(ucParam->resultPtr), 0);
}
else if (_strcmpi(ucParam->settingName, "cafe.fast_boot") == 0)
{
// todo - investigate values
memory_writeU8(_swapEndianU32(ucParam->resultPtr), 0);
}
else if (_strcmpi(ucParam->settingName, "parent.enable") == 0)
{
// parental feature enabled
if (ucParam->resultPtr != _swapEndianU32(MPTR_NULL))
memory_writeU32(_swapEndianU32(ucParam->resultPtr), 0);
}
else if (_strcmpi(ucParam->settingName, "nn.act.account_repaired") == 0)
{
if (ucParam->resultPtr != _swapEndianU32(MPTR_NULL))
memory_writeU8(_swapEndianU32(ucParam->resultPtr), 0);
}
else if (_strcmpi(ucParam->settingName, "p_acct1.net_communication_on_game") == 0)
{
// get parental online control for online features
// note: This option is account-bound, the p_acct1 prefix indicates that the account in slot 1 is used
// account in slot 1
if (ucParam->resultPtr != _swapEndianU32(MPTR_NULL))
memory_writeU8(_swapEndianU32(ucParam->resultPtr), 0); // data type is guessed
}
else if (_strcmpi(ucParam->settingName, "p_acct1.int_movie") == 0)
{
// get parental online control for movies?
// used by Pikmin HD movies
if (ucParam->resultPtr != _swapEndianU32(MPTR_NULL))
memory_writeU8(_swapEndianU32(ucParam->resultPtr), 0); // 0 -> allowed
}
else if (_strcmpi(ucParam->settingName, "p_acct1.network_launcher") == 0)
{
// miiverse restrictions
if (ucParam->resultPtr != _swapEndianU32(MPTR_NULL))
memory_writeU8(_swapEndianU32(ucParam->resultPtr), 0); // data type is guessed (0 -> no restrictions, 1 -> read only?, 2 -> no access?)
}
else if (_strcmpi(ucParam->settingName, "s_acct01.uuid") == 0)
{
if (ucParam->resultPtr != _swapEndianU32(MPTR_NULL) && ucParam->ukn4_size >= 37)
{
StackAllocator<uint8, 16> _uuid;
uint8* uuid = _uuid.GetPointer();
nn::act::GetUuidEx(uuid, 1, 0);
char tempStr[64];
sprintf(tempStr, "%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X", uuid[0], uuid[1], uuid[2], uuid[3], uuid[4], uuid[5], uuid[6], uuid[7],
uuid[8], uuid[9], uuid[10], uuid[11], uuid[12], uuid[13], uuid[14], uuid[15]);
strcpy((char*)memory_getPointerFromVirtualOffset(_swapEndianU32(ucParam->resultPtr)), tempStr);
}
}
else if (_strcmpi(ucParam->settingName, "s_acct01.nn.ec.eshop_initialized") == 0)
{
// E-Shop welcome screen
if (ucParam->resultPtr != _swapEndianU32(MPTR_NULL))
memory_writeU8(_swapEndianU32(ucParam->resultPtr), 1); // data type is guessed
}
else if (_strcmpi(ucParam->settingName, "p_acct1.int_browser") == 0)
{
if (ucParam->resultPtr != _swapEndianU32(MPTR_NULL))
memory_writeU8(_swapEndianU32(ucParam->resultPtr), 0);
}
else
{
forceLogDebug_printf("Unsupported SCI value: %s Size %08x\n", ucParam->settingName, ucParam->ukn4_size);
}
}
osLib_returnFromFunction(hCPU, 0); // 0 -> success
}

View file

@ -0,0 +1,54 @@
#pragma once
struct SysProdSettings
{
uint8 ukn00; // 0x00
uint8 ukn01; // 0x01
uint8 ukn02; // 0x02
uint8 platformRegion; // 0x03
uint8 ukn04;
uint8 ukn05;
uint8 ukn06;
uint8 ukn07;
uint8 prevBlock;
uint8 ukn09;
uint8 ukn0A;
uint8 gameRegion; // game region?
uint8 nextBlock;
uint8 ukn0D;
uint8 ukn0E;
uint8 ukn0F;
uint8 ukn10;
uint8 ukn11;
uint8 ukn12;
uint8 ukn13;
uint8 ukn14[4];
uint8 ukn18[4];
uint8 ukn1C[4];
uint8 ukn20[4];
uint8 ukn24[4];
uint8 ukn28[4];
uint8 ukn2C[4];
uint8 ukn30[4];
uint8 ukn34[4];
uint8 ukn38[4];
uint8 ukn3C[4];
uint8 ukn40[4];
uint8 ukn44;
uint8 ukn45;
};
static_assert(sizeof(SysProdSettings) == 0x46);
typedef uint32 MCPHANDLE;
MCPHANDLE MCP_Open();
void MCP_Close(MCPHANDLE mcpHandle);
sint32 MCP_GetSysProdSettings(MCPHANDLE mcpHandle, SysProdSettings* sysProdSettings);
void coreinitExport_UCReadSysConfig(PPCInterpreter_t* hCPU);
namespace coreinit
{
void InitializeMCP();
}

View file

@ -0,0 +1,651 @@
#include "Cafe/OS/common/OSCommon.h"
#include "Cafe/OS/libs/coreinit/coreinit_FG.h"
#include "Cafe/OS/libs/coreinit/coreinit_MEM.h"
#include "Cafe/OS/libs/coreinit/coreinit_MEM_ExpHeap.h"
#include "Cafe/OS/libs/coreinit/coreinit_Memory.h"
#include "Cafe/OS/RPL/rpl.h"
#include "Cafe/OS/libs/coreinit/coreinit_MEM_FrmHeap.h"
#include "coreinit_DynLoad.h"
// the system area is a block of memory that exists only in the emulator. It is used to simplify dynamic memory allocation for system data
// this partially overlaps with the system heap from coreinit_SysHeap.cpp -> Use SysHeap for everything
MPTR sysAreaAllocatorOffset = 0;
MPTR coreinit_allocFromSysArea(uint32 size, uint32 alignment)
{
cemu_assert_debug(mmuRange_CEMU_AREA.isMapped());
// deprecated, use OSAllocFromSystem instead (for everything but SysAllocator which probably should use its own allocator?)
static std::mutex s_allocator_mutex;
s_allocator_mutex.lock();
sysAreaAllocatorOffset = (sysAreaAllocatorOffset+alignment-1);
sysAreaAllocatorOffset -= (sysAreaAllocatorOffset%alignment);
uint32 newMemOffset = mmuRange_CEMU_AREA.getBase() + sysAreaAllocatorOffset;
sysAreaAllocatorOffset += (size+3)&~3;
if( sysAreaAllocatorOffset >= mmuRange_CEMU_AREA.getSize() )
{
forceLog_printf("Ran out of system memory");
cemu_assert(false); // out of bounds
}
s_allocator_mutex.unlock();
return newMemOffset;
}
void coreinit_freeToSysArea(MPTR mem)
{
// todo
}
namespace coreinit
{
#define MEM_MAX_HEAP_TABLE (0x20)
sint32 g_heapTableCount = 0;
MEMHeapBase* g_heapTable[MEM_MAX_HEAP_TABLE] = {};
bool g_slockInitialized = false;
bool g_listsInitialized = false;
MEMList g_list1;
MEMList g_list2;
MEMList g_list3;
std::array<uint32, 3> gHeapFillValues{ 0xC3C3C3C3, 0xF3F3F3F3, 0xD3D3D3D3 };
OSSpinLock gHeapGlobalLock;
MEMHeapBase* gDefaultHeap;
bool MEMHeapTable_Add(MEMHeapBase* heap)
{
if (g_heapTableCount >= MEM_MAX_HEAP_TABLE)
return false;
g_heapTable[g_heapTableCount] = heap;
g_heapTableCount++;
return true;
}
bool MEMHeapTable_Remove(MEMHeapBase* heap)
{
if (g_heapTableCount == 0)
return false;
if (g_heapTableCount > MEM_MAX_HEAP_TABLE)
return false;
for (sint32 i = 0; i < g_heapTableCount; ++i)
{
if (g_heapTable[i] == heap)
{
g_heapTable[i] = nullptr;
g_heapTableCount--;
if (g_heapTableCount == 0)
return true;
if (i >= g_heapTableCount)
return true;
cemu_assert_debug(i < MEM_MAX_HEAP_TABLE);
for (; i < g_heapTableCount; ++i)
{
g_heapTable[i] = g_heapTable[i + 1];
}
cemu_assert_debug(i < MEM_MAX_HEAP_TABLE);
g_heapTable[i] = nullptr;
return true;
}
}
return false;
}
MEMHeapBase* _MEMList_FindContainingHeap(MEMList* list, MEMHeapBase* heap)
{
for (MEMHeapBase* obj = (MEMHeapBase*)MEMGetFirstListObject(list); obj; obj = (MEMHeapBase*)MEMGetNextListObject(list, obj))
{
if (obj->heapStart.GetPtr() <= heap && heap < obj->heapEnd.GetPtr())
{
const MEMHeapHandle containHeap = _MEMList_FindContainingHeap(&obj->childList, heap);
return containHeap ? containHeap : obj;
}
}
return nullptr;
}
bool MEMList_ContainsHeap(MEMList* list, MEMHeapBase* heap)
{
for (MEMHeapBase* obj = (MEMHeapBase*)MEMGetFirstListObject(list); obj; obj = (MEMHeapBase*)MEMGetNextListObject(list, obj))
{
if (obj == heap)
return true;
}
return false;
}
MEMList* MEMList_FindContainingHeap(MEMHeapBase* head)
{
MEMPTR<void> memBound;
uint32be memBoundSize;
OSGetMemBound(1, (MPTR*)memBound.GetBEPtr(), (uint32*)&memBoundSize);
MEMPTR<void> bucket;
uint32be bucketSize;
coreinit::OSGetForegroundBucket(&bucket, &bucketSize);
if ((uintptr_t)memBound.GetPtr() > (uintptr_t)head || (uintptr_t)head >= (uintptr_t)memBound.GetPtr() + (uint32)memBoundSize)
{
if ((uintptr_t)bucket.GetPtr() > (uintptr_t)head || (uintptr_t)head >= (uintptr_t)bucket.GetPtr() + (uint32)bucketSize)
{
MEMHeapBase* containHeap = _MEMList_FindContainingHeap(&g_list1, head);
if (containHeap)
return &containHeap->childList;
return &g_list1;
}
MEMHeapBase* containHeap = _MEMList_FindContainingHeap(&g_list3, head);
if (containHeap)
return &containHeap->childList;
return &g_list3;
}
MEMHeapBase* containHeap = _MEMList_FindContainingHeap(&g_list2, head);
if (containHeap)
return &containHeap->childList;
return &g_list2;
}
void MEMInitHeapBase(MEMHeapBase* heap, MEMHeapMagic magic, void* heapStart, void* heapEnd, uint32 createFlags)
{
memset(heap, 0, sizeof(MEMHeapBase));
heap->magic = magic;
heap->heapStart = heapStart;
heap->heapEnd = heapEnd;
heap->flags = (uint8)createFlags;
MEMInitList(&heap->childList, 4);
if (!g_slockInitialized)
{
OSInitSpinLock(&gHeapGlobalLock);
g_slockInitialized = true;
}
if (!g_listsInitialized)
{
MEMInitList(&g_list1, offsetof(MEMHeapBase, link));
MEMInitList(&g_list2, offsetof(MEMHeapBase, link));
MEMInitList(&g_list3, offsetof(MEMHeapBase, link));
g_listsInitialized = true;
}
OSInitSpinLock(&heap->spinlock);
OSUninterruptibleSpinLock_Acquire(&gHeapGlobalLock);
MEMList* list = MEMList_FindContainingHeap(heap);
MEMAppendListObject(list, heap);
OSUninterruptibleSpinLock_Release(&gHeapGlobalLock);
}
void MEMBaseDestroyHeap(MEMHeapBase* heap)
{
OSUninterruptibleSpinLock_Acquire(&gHeapGlobalLock);
if (HAS_FLAG(heap->flags, MEM_HEAP_OPTION_THREADSAFE))
OSUninterruptibleSpinLock_Acquire(&heap->spinlock);
MEMList* containHeap = MEMList_FindContainingHeap(heap);
cemu_assert_debug(MEMList_ContainsHeap(containHeap, heap));
MEMRemoveListObject(containHeap, heap);
if (HAS_FLAG(heap->flags, MEM_HEAP_OPTION_THREADSAFE))
OSUninterruptibleSpinLock_Release(&heap->spinlock);
OSUninterruptibleSpinLock_Release(&gHeapGlobalLock);
}
std::array<MEMHeapBase*, 9> sHeapBaseHandle{};
MEMHeapBase* MEMGetBaseHeapHandle(uint32 index)
{
if (index >= sHeapBaseHandle.size())
return nullptr;
return sHeapBaseHandle[index];
}
MEMHeapBase* MEMSetBaseHeapHandle(uint32 index, MEMHeapBase* heapBase)
{
if (index >= sHeapBaseHandle.size())
return nullptr;
if (sHeapBaseHandle[index] != nullptr)
{
cemuLog_log(LogType::Force, "MEMSetBaseHeapHandle(): Trying to assign heap to non-empty slot");
return sHeapBaseHandle[index];
}
sHeapBaseHandle[index] = heapBase;
return nullptr;
}
uint32 MEMSetFillValForHeap(HEAP_FILL_TYPE type, uint32 value)
{
uint32 idx = (uint32)type;
cemu_assert(idx < gHeapFillValues.size());
OSUninterruptibleSpinLock_Acquire(&gHeapGlobalLock);
const uint32 oldValue = gHeapFillValues[idx];
gHeapFillValues[idx] = value;
OSUninterruptibleSpinLock_Release(&gHeapGlobalLock);
return oldValue;
}
uint32 MEMGetFillValForHeap(HEAP_FILL_TYPE type)
{
uint32 idx = (uint32)type;
cemu_assert(idx < gHeapFillValues.size());
OSUninterruptibleSpinLock_Acquire(&gHeapGlobalLock);
const uint32 value = gHeapFillValues[idx];
OSUninterruptibleSpinLock_Release(&gHeapGlobalLock);
return value;
}
MEMHeapBase* MEMFindContainHeap(const void* memBlock)
{
MEMPTR<void> memBound;
uint32be memBoundSize;
OSGetMemBound(1, (MPTR*)memBound.GetBEPtr(), (uint32*)&memBoundSize);
MEMPTR<void> bucket;
uint32be bucketSize;
coreinit::OSGetForegroundBucket(&bucket, &bucketSize);
OSUninterruptibleSpinLock_Acquire(&gHeapGlobalLock);
MEMHeapBase* result;
if ((uintptr_t)memBound.GetPtr() > (uintptr_t)memBlock || (uintptr_t)memBlock >= (uintptr_t)memBound.GetPtr() + (uint32)memBoundSize)
{
if ((uintptr_t)bucket.GetPtr() > (uintptr_t)memBlock || (uintptr_t)memBlock >= (uintptr_t)bucket.GetPtr() + (uint32)bucketSize)
{
result = _MEMList_FindContainingHeap(&g_list1, (MEMHeapBase*)memBlock);
}
else
{
if (coreinit::OSGetForegroundBucket(nullptr, nullptr) == 0)
{
cemu_assert_unimplemented(); // foreground required
result = nullptr;
}
else
{
result = _MEMList_FindContainingHeap(&g_list3, (MEMHeapBase*)memBlock);
}
}
}
else
{
if (coreinit::OSGetForegroundBucket(nullptr, nullptr) == 0)
{
cemu_assert_unimplemented(); // foreground required
result = nullptr;
}
else
{
result = _MEMList_FindContainingHeap(&g_list2, (MEMHeapBase*)memBlock);
}
}
OSUninterruptibleSpinLock_Release(&gHeapGlobalLock);
return result;
}
void* MEMCreateUserHeapHandle(void* heapAddress, uint32 heapSize)
{
MEMInitHeapBase((MEMHeapBase*)heapAddress, MEMHeapMagic::USER_HEAP, (uint8*)heapAddress + 0x40, (uint8*)heapAddress + heapSize, 0);
return heapAddress;
}
/* MEM Heap list */
#define GET_MEM_LINK(__obj__,__offset__) ((MEMLink*)((uintptr_t)__obj__ + (uint16)__offset__))
void* MEMGetFirstListObject(MEMList* list)
{
return MEMGetNextListObject(list, nullptr);
}
void MEMList_SetSingleObject(MEMList* list, void* object)
{
cemu_assert_debug(list != nullptr);
cemu_assert_debug(object != nullptr);
MEMLink* link = GET_MEM_LINK(object, list->offset);
link->prev = nullptr;
link->next = nullptr;
list->numObjects = list->numObjects + 1;
list->head = object;
list->tail = object;
}
void MEMInitList(MEMList* list, uint32 offset)
{
cemu_assert_debug(list != nullptr);
cemu_assert(offset <= 0xFFFF);
memset(list, 0x00, sizeof(MEMLink));
list->offset = (uint16)offset;
}
void MEMAppendListObject(MEMList* list, void* object)
{
cemu_assert_debug(list != nullptr);
cemu_assert_debug(object != nullptr);
if (list->head)
{
list->numObjects = list->numObjects + 1;
MEMLink* link = GET_MEM_LINK(object, list->offset);
link->prev = list->tail;
link->next = nullptr;
MEMLink* tailLink = GET_MEM_LINK(list->tail.GetPtr(), list->offset);
tailLink->next = object;
list->tail = object;
}
else
MEMList_SetSingleObject(list, object);
}
void MEMPrependListObject(MEMList* list, void* object)
{
cemu_assert_debug(list != nullptr);
cemu_assert_debug(object != nullptr);
MEMPTR<void> headObject = list->head;
if (headObject)
{
list->numObjects = list->numObjects + 1;
MEMLink* link = GET_MEM_LINK(object, list->offset);
link->prev = nullptr;
link->next = headObject;
MEMLink* headLink = GET_MEM_LINK(headObject.GetPtr(), list->offset);
headLink->prev = object;
list->head = object;
}
else
MEMList_SetSingleObject(list, object);
}
void MEMRemoveListObject(MEMList* list, void* object)
{
cemu_assert_debug(list != nullptr);
cemu_assert_debug(object != nullptr);
MEMLink* link = GET_MEM_LINK(object, list->offset);
MEMPTR<void> prevObject = link->prev;
if (prevObject)
{
MEMLink* prevLink = GET_MEM_LINK(prevObject.GetPtr(), list->offset);
prevLink->next = link->next;
}
else
list->head = link->next;
MEMPTR<void> nextObject = link->next;
if (nextObject)
{
MEMLink* nextLink = GET_MEM_LINK(nextObject.GetPtr(), list->offset);
nextLink->prev = prevObject;
}
else
list->tail = prevObject;
link->prev = nullptr;
link->next = nullptr;
list->numObjects = list->numObjects - 1;
}
void* MEMGetNextListObject(MEMList* list, void* object)
{
cemu_assert_debug(list != nullptr);
if (!object)
return list->head.GetPtr();
MEMLink* result = GET_MEM_LINK(object, list->offset);
return result->next.GetPtr();
}
void* MEMGetPrevListObject(MEMList* list, void* object)
{
cemu_assert_debug(list != nullptr);
if (!object)
return list->tail.GetPtr();
MEMLink* result = GET_MEM_LINK(object, list->offset);
return result->prev.GetPtr();
}
void* MEMGetNthListObject(MEMList* list, uint32 index)
{
cemu_assert_debug(list != nullptr);
void* result = MEMGetNextListObject(list, nullptr);
for (uint32 i = 0; i != index; ++i)
{
result = MEMGetNextListObject(list, result);
}
return result;
}
/* Default allocators */
MEMHeapBase* MEMDefaultHeap_Init(void* mem, uint32 size)
{
MEMHeapBase* heap = MEMCreateExpHeapEx(mem, size, 4);
gDefaultHeap = heap;
cemu_assert_debug(heap);
return heap;
}
void* default_MEMAllocFromDefaultHeap(uint32 size)
{
void* mem = MEMAllocFromExpHeapEx(gDefaultHeap, size, 0x40);
coreinitMemLog_printf("MEMAllocFromDefaultHeap(0x%08x) Result: 0x%08x", size, memory_getVirtualOffsetFromPointer(mem));
return mem;
}
void export_default_MEMAllocFromDefaultHeap(PPCInterpreter_t* hCPU)
{
ppcDefineParamU32(size, 0);
MEMPTR<void> mem = default_MEMAllocFromDefaultHeap(size);
osLib_returnFromFunction(hCPU, mem.GetMPTR());
}
void* default_MEMAllocFromDefaultHeapEx(uint32 size, sint32 alignment)
{
void* mem = MEMAllocFromExpHeapEx(gDefaultHeap, size, alignment);
coreinitMemLog_printf("MEMAllocFromDefaultHeap(0x%08x,%d) Result: 0x%08x", size, alignment, memory_getVirtualOffsetFromPointer(mem));
return mem;
}
void export_default_MEMAllocFromDefaultHeapEx(PPCInterpreter_t* hCPU)
{
ppcDefineParamU32(size, 0);
ppcDefineParamS32(alignment, 1);
MEMPTR<void> mem = default_MEMAllocFromDefaultHeapEx(size, alignment);
osLib_returnFromFunction(hCPU, mem.GetMPTR());
}
void default_MEMFreeToDefaultHeap(void* mem)
{
MEMFreeToExpHeap(gDefaultHeap, mem);
}
void export_default_MEMFreeToDefaultHeap(PPCInterpreter_t* hCPU)
{
ppcDefineParamMEMPTR(mem, void, 0);
default_MEMFreeToDefaultHeap(mem.GetPtr());
osLib_returnFromFunction(hCPU, 0);
}
void default_DynLoadAlloc(PPCInterpreter_t* hCPU)
{
ppcDefineParamS32(size, 0);
ppcDefineParamS32(alignment, 1);
ppcDefineParamMEMPTR(memResultPtr, uint32be, 2);
MPTR mem = PPCCoreCallback(gCoreinitData->MEMAllocFromDefaultHeapEx.GetMPTR(), size, alignment);
*memResultPtr = mem;
osLib_returnFromFunction(hCPU, 0);
}
void default_DynLoadFree(PPCInterpreter_t* hCPU)
{
ppcDefineParamMEMPTR(mem, void, 0);
PPCCoreCallback(gCoreinitData->MEMFreeToDefaultHeap.GetMPTR(), mem);
osLib_returnFromFunction(hCPU, 0);
}
void* _weak_MEMAllocFromDefaultHeapEx(uint32 size, sint32 alignment)
{
MEMPTR<void> r{ PPCCoreCallback(gCoreinitData->MEMAllocFromDefaultHeapEx.GetMPTR(), size, alignment) };
return r.GetPtr();
}
void* _weak_MEMAllocFromDefaultHeap(uint32 size)
{
MEMPTR<void> r{ PPCCoreCallback(gCoreinitData->MEMAllocFromDefaultHeap.GetMPTR(), size) };
return r.GetPtr();
}
void _weak_MEMFreeToDefaultHeap(void* ptr)
{
PPCCoreCallback(gCoreinitData->MEMFreeToDefaultHeap.GetMPTR(), ptr);
}
void coreinitExport_MEMAllocFromAllocator(PPCInterpreter_t* hCPU)
{
debug_printf("MEMAllocFromAllocator(0x%x, 0x%x)\n", hCPU->gpr[3], hCPU->gpr[4]);
MEMAllocator* memAllocator = (MEMAllocator*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]);
// redirect execution to allocator alloc callback
hCPU->instructionPointer = memAllocator->func->funcAlloc.GetMPTR();
}
void coreinitExport_MEMFreeToAllocator(PPCInterpreter_t* hCPU)
{
debug_printf("MEMFreeToAllocator(0x%x, 0x%08x)\n", hCPU->gpr[3], hCPU->gpr[4]);
MEMAllocator* memAllocator = (MEMAllocator*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]);
// redirect execution to allocator free callback
hCPU->instructionPointer = memAllocator->func->funcFree.GetMPTR();
}
void _DefaultHeapAllocator_Alloc(PPCInterpreter_t* hCPU)
{
hCPU->gpr[3] = hCPU->gpr[4];
hCPU->instructionPointer = gCoreinitData->MEMAllocFromDefaultHeap.GetMPTR();
}
void _DefaultHeapAllocator_Free(PPCInterpreter_t* hCPU)
{
hCPU->gpr[3] = hCPU->gpr[4];
hCPU->instructionPointer = gCoreinitData->MEMFreeToDefaultHeap.GetMPTR();
}
SysAllocator<MEMAllocatorFunc> gDefaultHeapAllocator;
void coreinitExport_MEMInitAllocatorForDefaultHeap(PPCInterpreter_t* hCPU)
{
debug_printf("MEMInitAllocatorForDefaultHeap(0x%08x)", hCPU->gpr[3]);
ppcDefineParamStructPtr(memAllocator, MEMAllocator, 0);
gDefaultHeapAllocator->funcAlloc = _swapEndianU32(PPCInterpreter_makeCallableExportDepr(_DefaultHeapAllocator_Alloc));
gDefaultHeapAllocator->funcFree = _swapEndianU32(PPCInterpreter_makeCallableExportDepr(_DefaultHeapAllocator_Free));
memAllocator->func = gDefaultHeapAllocator.GetPtr();
memAllocator->heap = MEMPTR<void>(MEMGetBaseHeapHandle(1));
memAllocator->param1 = 0;
memAllocator->param2 = 0;
osLib_returnFromFunction(hCPU, 0);
}
void InitDefaultHeaps(MEMPTR<MEMHeapBase>& mem1Heap, MEMPTR<MEMHeapBase>& memFGHeap, MEMPTR<MEMHeapBase>& mem2Heap)
{
mem1Heap = nullptr;
memFGHeap = nullptr;
mem2Heap = nullptr;
gCoreinitData->MEMAllocFromDefaultHeap = RPLLoader_MakePPCCallable(export_default_MEMAllocFromDefaultHeap);
gCoreinitData->MEMAllocFromDefaultHeapEx = RPLLoader_MakePPCCallable(export_default_MEMAllocFromDefaultHeapEx);
gCoreinitData->MEMFreeToDefaultHeap = RPLLoader_MakePPCCallable(export_default_MEMFreeToDefaultHeap);
if (OSGetForegroundBucket(nullptr, nullptr))
{
MEMPTR<void> memBound;
uint32be memBoundSize;
OSGetMemBound(1, (MPTR*)memBound.GetBEPtr(), (uint32*)&memBoundSize);
mem1Heap = MEMCreateFrmHeapEx(memBound.GetPtr(), (uint32)memBoundSize, 0);
OSGetForegroundBucketFreeArea((MPTR*)memBound.GetBEPtr(), (MPTR*)&memBoundSize);
memFGHeap = MEMCreateFrmHeapEx(memBound.GetPtr(), (uint32)memBoundSize, 0);
}
MEMPTR<void> memBound;
uint32be memBoundSize;
OSGetMemBound(2, (MPTR*)memBound.GetBEPtr(), (uint32*)&memBoundSize);
mem2Heap = MEMDefaultHeap_Init(memBound.GetPtr(), (uint32)memBoundSize);
// set DynLoad allocators
OSDynLoad_SetAllocator(RPLLoader_MakePPCCallable(default_DynLoadAlloc), RPLLoader_MakePPCCallable(default_DynLoadFree));
OSDynLoad_SetTLSAllocator(RPLLoader_MakePPCCallable(default_DynLoadAlloc), RPLLoader_MakePPCCallable(default_DynLoadFree));
}
void CoreInitDefaultHeap(/* ukn */)
{
cemu_assert_unimplemented();
}
void InitializeMEM()
{
for (auto& it : sHeapBaseHandle)
it = nullptr;
cafeExportRegister("coreinit", CoreInitDefaultHeap, LogType::CoreinitMem);
osLib_addFunction("coreinit", "MEMInitAllocatorForDefaultHeap", coreinitExport_MEMInitAllocatorForDefaultHeap);
osLib_addFunction("coreinit", "MEMAllocFromAllocator", coreinitExport_MEMAllocFromAllocator);
osLib_addFunction("coreinit", "MEMFreeToAllocator", coreinitExport_MEMFreeToAllocator);
cafeExportRegister("coreinit", MEMGetBaseHeapHandle, LogType::CoreinitMem);
cafeExportRegister("coreinit", MEMSetBaseHeapHandle, LogType::CoreinitMem);
cafeExportRegister("coreinit", MEMFindContainHeap, LogType::CoreinitMem);
cafeExportRegister("coreinit", MEMGetFillValForHeap, LogType::CoreinitMem);
cafeExportRegister("coreinit", MEMSetFillValForHeap, LogType::CoreinitMem);
cafeExportRegister("coreinit", MEMCreateUserHeapHandle, LogType::CoreinitMem);
cafeExportRegister("coreinit", MEMInitList, LogType::CoreinitMem);
cafeExportRegister("coreinit", MEMPrependListObject, LogType::CoreinitMem);
cafeExportRegister("coreinit", MEMAppendListObject, LogType::CoreinitMem);
cafeExportRegister("coreinit", MEMRemoveListObject, LogType::CoreinitMem);
cafeExportRegister("coreinit", MEMGetNextListObject, LogType::CoreinitMem);
cafeExportRegister("coreinit", MEMGetNthListObject, LogType::CoreinitMem);
cafeExportRegister("coreinit", MEMGetPrevListObject, LogType::CoreinitMem);
}
}

View file

@ -0,0 +1,183 @@
#pragma once
#include "Cafe/OS/libs/coreinit/coreinit_Spinlock.h"
struct MEMLink_t
{
MPTR prevObject;
MPTR nextObject;
};
static_assert(sizeof(MEMLink_t) == 8);
struct MEMList_t
{
MPTR headObject;
MPTR tailObject;
uint16 numObjects;
uint16 offset;
};
static_assert(sizeof(MEMList_t) == 0xC);
struct MEMAllocatorFunc
{
MEMPTR<void> funcAlloc;
MEMPTR<void> funcFree;
};
static_assert(sizeof(MEMAllocatorFunc) == 8);
struct MEMAllocator
{
/* +0x000 */ MEMPTR<MEMAllocatorFunc> func;
/* +0x004 */ MEMPTR<void> heap;
/* +0x00C */ uint32be param1;
/* +0x010 */ uint32be param2;
};
static_assert(sizeof(MEMAllocator) == 0x10);
MPTR coreinit_allocFromSysArea(uint32 size, uint32 alignment);
void coreinit_freeToSysArea(MPTR mem);
// mem exports
void coreinitExport_MEMInitAllocatorForDefaultHeap(PPCInterpreter_t* hCPU);
void coreinitExport_MEMGetBaseHeapHandle(PPCInterpreter_t* hCPU);
/* legacy stuff above */
namespace coreinit
{
#define MEM_HEAP_INVALID_HANDLE (nullptr)
#define MEM_HEAP_DEFAULT_ALIGNMENT 4
#define MIN_ALIGNMENT 4
#define MIN_ALIGNMENT_MINUS_ONE (MIN_ALIGNMENT-1)
#define MEM_HEAP_OPTION_NONE (0)
#define MEM_HEAP_OPTION_CLEAR (1 << 0)
#define MEM_HEAP_OPTION_FILL (1 << 1)
#define MEM_HEAP_OPTION_THREADSAFE (1 << 2)
enum class MEMHeapMagic : uint32
{
UNIT_HEAP = 'UNTH',
BLOCK_HEAP = 'BLKH',
FRAME_HEAP = 'FRMH',
EXP_HEAP = 'EXPH',
USER_HEAP = 'USRH',
};
struct MEMLink
{
MEMPTR<void> prev;
MEMPTR<void> next;
};
static_assert(sizeof(MEMLink) == 0x8);
struct MEMList
{
/* 0x00 */ MEMPTR<void> head;
/* 0x04 */ MEMPTR<void> tail;
/* 0x08 */ uint16be numObjects;
/* 0x0A */ uint16be offset;
};
static_assert(sizeof(MEMList) == 0xC);
void MEMInitList(MEMList* list, uint32 offset);
void MEMAppendListObject(MEMList* list, void* object);
void MEMRemoveListObject(MEMList* list, void* object);
void* MEMGetFirstListObject(MEMList* list);
void* MEMGetNextListObject(MEMList* list, void* object);
struct MEMHeapBase
{
/* +0x00 */ betype<MEMHeapMagic> magic;
/* +0x04 */ MEMLink link;
/* +0x0C */ MEMList childList;
/* +0x18 */ MEMPTR<void> heapStart;
/* +0x1C */ MEMPTR<void> heapEnd; // heap end + 1
/* +0x20 */ OSSpinLock spinlock;
/* +0x30 */ uint8 _ukn[3];
/* +0x33 */ uint8 flags;
void AcquireLock()
{
if (flags & MEM_HEAP_OPTION_THREADSAFE)
OSUninterruptibleSpinLock_Acquire(&spinlock);
}
void ReleaseLock()
{
if (flags & MEM_HEAP_OPTION_THREADSAFE)
OSUninterruptibleSpinLock_Release(&spinlock);
}
// if set, memset allocations to zero
bool HasOptionClear() const
{
return (flags & MEM_HEAP_OPTION_CLEAR) != 0;
}
// if set, memset allocations/releases to specific fill values
bool HasOptionFill() const
{
return (flags & MEM_HEAP_OPTION_FILL) != 0;
}
};
static_assert(offsetof(MEMHeapBase, childList) == 0xC);
static_assert(offsetof(MEMHeapBase, spinlock) == 0x20);
static_assert(offsetof(MEMHeapBase, flags) == 0x33);
static_assert(sizeof(MEMHeapBase) == 0x34); // heap base is actually 0x40 but bytes 0x34 to 0x40 are padding?
typedef MEMHeapBase* MEMHeapHandle;
/* Heap base */
void MEMInitHeapBase(MEMHeapBase* heap, MEMHeapMagic magic, void* heapStart, void* heapEnd, uint32 createFlags);
void MEMBaseDestroyHeap(MEMHeapBase* heap);
MEMHeapBase* MEMGetBaseHeapHandle(uint32 index);
MEMHeapBase* MEMSetBaseHeapHandle(uint32 index, MEMHeapBase* heapBase);
/* Heap list */
bool MEMHeapTable_Add(MEMHeapBase* heap);
bool MEMHeapTable_Remove(MEMHeapBase* heap);
MEMHeapBase* _MEMList_FindContainingHeap(MEMList* list, MEMHeapBase* heap);
bool MEMList_ContainsHeap(MEMList* list, MEMHeapBase* heap);
MEMList* MEMList_FindContainingHeap(MEMHeapBase* head);
/* Heap settings */
enum class HEAP_FILL_TYPE : uint32
{
ON_HEAP_CREATE = 0,
ON_ALLOC = 1,
ON_FREE = 2,
};
uint32 MEMGetFillValForHeap(HEAP_FILL_TYPE type);
uint32 MEMSetFillValForHeap(HEAP_FILL_TYPE type, uint32 value);
MEMHeapHandle MEMFindContainHeap(const void* memBlock);
/* Heap default allocators */
void InitDefaultHeaps(MEMPTR<MEMHeapBase>& mem1Heap, MEMPTR<MEMHeapBase>& memFGHeap, MEMPTR<MEMHeapBase>& mem2Heap);
void* default_MEMAllocFromDefaultHeap(uint32 size);
void* default_MEMAllocFromDefaultHeapEx(uint32 size, sint32 alignment);
void default_MEMFreeToDefaultHeap(void* mem);
void* _weak_MEMAllocFromDefaultHeapEx(uint32 size, sint32 alignment);
void* _weak_MEMAllocFromDefaultHeap(uint32 size);
void _weak_MEMFreeToDefaultHeap(void* ptr);
/* Unit heap */
void InitializeMEMUnitHeap();
void InitializeMEM();
}

View file

@ -0,0 +1,596 @@
#include "Cafe/OS/common/OSCommon.h"
#include "Cafe/OS/libs/coreinit/coreinit_MEM.h"
#include "Cafe/OS/libs/coreinit/coreinit_MEM_BlockHeap.h"
namespace coreinit
{
MEMHeapHandle MEMInitBlockHeap(MEMBlockHeap2_t* memStart, void* startAddr, void* endAddr, void* initTrackMem, uint32 initTrackMemSize, uint32 createFlags)
{
if (memStart == nullptr || startAddr == nullptr || endAddr == nullptr || (uintptr_t)startAddr >= (uintptr_t)endAddr)
return nullptr;
if (initTrackMemSize == 0)
initTrackMem = nullptr;
else if (initTrackMem == nullptr)
initTrackMemSize = 0;
MEMInitHeapBase(memStart, MEMHeapMagic::BLOCK_HEAP, startAddr, endAddr, createFlags);
memStart->track.addrStart = startAddr;
memStart->track.addrEnd = (void*)((uintptr_t)endAddr - 1);
memStart->track.isFree = 1;
memStart->track.previousBlock = nullptr;
memStart->track.nextBlock = nullptr;
memStart->headBlock = &memStart->track;
memStart->tailBlock = &memStart->track;
memStart->nextFreeBlock = nullptr;
memStart->freeBlocksLeft = 0;
uint8 flags = memStart->flags;
if(HAS_FLAG(flags, MEM_HEAP_OPTION_FILL))
{
cemu_assert_unimplemented();
}
if(initTrackMemSize != 0)
{
sint32 result = MEMAddBlockHeapTracking(MEMPTR<void>(memStart).GetMPTR(), MEMPTR<void>(initTrackMem).GetMPTR(), initTrackMemSize);
if(result != 0)
{
MEMBaseDestroyHeap(memStart);
return nullptr;
}
}
MEMHeapTable_Add(memStart);
return memStart;
}
void* MEMDestroyBlockHeap(MEMHeapHandle hHeap)
{
if (!hHeap)
return nullptr;
cemu_assert_debug(hHeap->magic == MEMHeapMagic::BLOCK_HEAP);
MEMBaseDestroyHeap(hHeap);
MEMHeapTable_Remove(hHeap);
memset(hHeap, 0x00, sizeof(MEMBlockHeap2_t));
return hHeap;
}
sint32 MEMGetAllocatableSizeForBlockHeapEx(MEMBlockHeap2_t* blockHeap, sint32 alignment)
{
if (!blockHeap || blockHeap->magic != MEMHeapMagic::BLOCK_HEAP)
{
cemuLog_log(LogType::Force, "MEMGetAllocatableSizeForBlockHeapEx(): Not a valid block heap");
return 0;
}
if (alignment < 0)
alignment = -alignment;
else if (alignment == 0)
alignment = 4;
if (HAS_FLAG(blockHeap->flags, MEM_HEAP_OPTION_THREADSAFE))
OSUninterruptibleSpinLock_Acquire(&blockHeap->spinlock);
MEMBlockHeapTrack2_t* track = (MEMBlockHeapTrack2_t*)blockHeap->headBlock.GetPtr();
uint32 allocatableSize = 0;
while (track)
{
if (track->isFree != 0)
{
uint32 addrStart = track->addrStart.GetMPTR();
uint32 addrEnd = track->addrEnd.GetMPTR();
uint32 alignmentMinusOne = alignment - 1;
uint32 alignedAddrStart = ((addrStart + alignmentMinusOne) / alignment) * alignment;
if (alignedAddrStart <= addrEnd)
{
uint32 size = (addrEnd + 1) - alignedAddrStart;
allocatableSize = std::max(allocatableSize, size);
}
}
// next
track = (MEMBlockHeapTrack2_t*)track->nextBlock.GetPtr();
}
if (HAS_FLAG(blockHeap->flags, MEM_HEAP_OPTION_THREADSAFE))
OSUninterruptibleSpinLock_Release(&blockHeap->spinlock);
return allocatableSize;
}
void MEMDumpBlockHeap(MPTR heap);
typedef struct
{
/* +0x00 */ uint32 addrStart;
/* +0x04 */ uint32 addrEnd;
/* +0x08 */ uint32 isFree; // if 0 -> block is used
/* +0x0C */ MPTR previousBlock;
/* +0x10 */ MPTR nextBlock;
}MEMBlockHeapTrackDEPR;
typedef struct
{
/* +0x00 */ uint32 ukn00;
/* +0x04 */ uint32 ukn04;
/* +0x08 */ uint32 trackArray;
/* +0x0C */ uint32 trackCount;
}MEMBlockHeapGroupDEPR;
typedef struct
{
/* +0x000 */ betype<MEMHeapMagic> magic;
/* +0x004 */ MEMLink_t link;
/* +0x00C */ MEMList_t childList;
/* +0x018 */ MPTR heapStart;
/* +0x01C */ MPTR heapEnd;
/* +0x020 */ coreinit::OSSpinLock spinlock;
/* +0x030 */
union
{
uint32 val;
uint32 flags;
}
attribute;
// start of block heap header
/* +0x034 */ uint8 ukn034[0x50 - 0x34]; // ?
/* +0x050 */ MEMBlockHeapTrackDEPR nullBlockTrack;
/* +0x064 */ MPTR headBlock;
/* +0x068 */ MPTR tailBlock;
/* +0x06C */ MPTR nextFreeBlock;
/* +0x070 */ uint32 freeBlocksLeft;
}MEMBlockHeapDEPR;
void _blockHeapDebugVerifyIfBlockIsLinked(MEMBlockHeapDEPR* blockHeapHead, MEMBlockHeapTrackDEPR* track)
{
//MEMBlockHeapTrack_t* trackItr = (MEMBlockHeapTrack_t*)memory_getPointerFromVirtualOffsetAllowNull(_swapEndianU32(blockHeapHead->firstBlock));
//MEMBlockHeapTrack_t* prevHistory[4];
//while( trackItr )
//{
// if( trackItr == track )
// {
// debug_printf("PrevBlock3 %08x\n", memory_getVirtualOffsetFromPointer(prevHistory[0]));
// debug_printf("PrevBlock2 %08x\n", memory_getVirtualOffsetFromPointer(prevHistory[1]));
// debug_printf("PrevBlock1 %08x\n", memory_getVirtualOffsetFromPointer(prevHistory[2]));
// debug_printf("PrevBlock0 %08x\n", memory_getVirtualOffsetFromPointer(prevHistory[3]));
// assert_dbg();
// }
// prevHistory[3] = prevHistory[2];
// prevHistory[2] = prevHistory[1];
// prevHistory[1] = prevHistory[0];
// prevHistory[0] = trackItr;
// // next
// trackItr = (MEMBlockHeapTrack_t*)memory_getPointerFromVirtualOffsetAllowNull(_swapEndianU32(trackItr->nextBlock));
//}
}
void _blockHeapDebugVerifyLinkOrder(MEMBlockHeapDEPR* blockHeapHead)
{
//MEMBlockHeapTrack_t* trackItr = (MEMBlockHeapTrack_t*)memory_getPointerFromVirtualOffsetAllowNull(_swapEndianU32(blockHeapHead->firstBlock));
//MEMBlockHeapTrack_t* prev = NULL;
//while( trackItr )
//{
// MPTR prevMPTR = memory_getVirtualOffsetFromPointer(prev);
// if( _swapEndianU32(trackItr->previousBlock) != prevMPTR )
// assert_dbg();
// // next
// prev = trackItr;
// trackItr = (MEMBlockHeapTrack_t*)memory_getPointerFromVirtualOffsetAllowNull(_swapEndianU32(trackItr->nextBlock));
//}
}
sint32 MEMAddBlockHeapTracking(MPTR heap, MPTR trackMem, uint32 trackMemSize)
{
MEMBlockHeapDEPR* blockHeapHead = (MEMBlockHeapDEPR*)memory_getPointerFromVirtualOffset(heap);
if (blockHeapHead->magic != MEMHeapMagic::BLOCK_HEAP)
{
return 0;
}
if (trackMem == MPTR_NULL || trackMemSize <= (sizeof(MEMBlockHeapGroupDEPR) + sizeof(MEMBlockHeapTrackDEPR)))
{
return -4;
}
uint32 trackCount = (trackMemSize - sizeof(MEMBlockHeapGroupDEPR)) / sizeof(MEMBlockHeapTrackDEPR);
MEMBlockHeapGroupDEPR* group = (MEMBlockHeapGroupDEPR*)memory_getPointerFromVirtualOffset(trackMem);
group->ukn00 = _swapEndianU32(0);
group->ukn04 = _swapEndianU32(0);
group->trackArray = _swapEndianU32(trackMem + sizeof(MEMBlockHeapGroupDEPR));
group->trackCount = _swapEndianU32(trackCount);
MEMBlockHeapTrackDEPR* track = (MEMBlockHeapTrackDEPR*)(group + 1);
track[0].previousBlock = _swapEndianU32(0);
if (trackCount > 1)
{
for (uint32 i = 0; i < trackCount - 1; i++)
{
track[i].nextBlock = _swapEndianU32(memory_getVirtualOffsetFromPointer(track + i + 1));
track[i + 1].previousBlock = _swapEndianU32(0);
}
}
// todo: Use spinlock from heap (and only use it if threadsafe heap flag is set)
__OSLockScheduler();
track[trackCount - 1].nextBlock = blockHeapHead->nextFreeBlock;
blockHeapHead->nextFreeBlock = _swapEndianU32(memory_getVirtualOffsetFromPointer(track));
blockHeapHead->freeBlocksLeft = _swapEndianU32(_swapEndianU32(blockHeapHead->freeBlocksLeft) + trackCount);
__OSUnlockScheduler();
return 0;
}
uint32 MEMGetTrackingLeftInBlockHeap(MPTR heap)
{
MEMBlockHeapDEPR* blockHeapHead = (MEMBlockHeapDEPR*)memory_getPointerFromVirtualOffset(heap);
if (blockHeapHead->magic != MEMHeapMagic::BLOCK_HEAP)
{
return 0;
}
return _swapEndianU32(blockHeapHead->freeBlocksLeft);
}
MPTR _MEMBlockHeap_GetFreeBlockTrack(MEMBlockHeapDEPR* blockHeapHead)
{
MPTR trackMPTR = _swapEndianU32(blockHeapHead->nextFreeBlock);
MEMBlockHeapTrackDEPR* track = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffset(trackMPTR);
MPTR nextFreeBlockMPTR = _swapEndianU32(track->nextBlock); // for unreserved tracks, nextBlock holds the next unreserved block
track->nextBlock = _swapEndianU32(MPTR_NULL);
blockHeapHead->nextFreeBlock = _swapEndianU32(nextFreeBlockMPTR);
if (_swapEndianU32(blockHeapHead->freeBlocksLeft) == 0)
{
forceLog_printf("BlockHeap: No free blocks left\n");
assert_dbg();
}
blockHeapHead->freeBlocksLeft = _swapEndianU32(_swapEndianU32(blockHeapHead->freeBlocksLeft) - 1);
return trackMPTR;
}
/*
* Release MEMBlockHeapTrack_t struct for block heap
*/
void _MEMBlockHeap_ReleaseBlockTrack(MEMBlockHeapDEPR* blockHeapHead, MPTR trackMPTR)
{
MEMBlockHeapTrackDEPR* track = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffset(trackMPTR);
track->nextBlock = blockHeapHead->nextFreeBlock;
blockHeapHead->nextFreeBlock = _swapEndianU32(trackMPTR);
blockHeapHead->freeBlocksLeft = _swapEndianU32(_swapEndianU32(blockHeapHead->freeBlocksLeft) + 1);
}
sint32 _MEMBlockHeap_AllocAtBlock(MEMBlockHeapDEPR* blockHeapHead, MEMBlockHeapTrackDEPR* track, MPTR allocationAddress, uint32 size)
{
MPTR trackMPTR = memory_getVirtualOffsetFromPointer(track);
uint32 trackEndAddr = _swapEndianU32(track->addrEnd);
uint32 prefixBlockSize = allocationAddress - _swapEndianU32(track->addrStart);
uint32 suffixBlockSize = (_swapEndianU32(track->addrEnd) + 1) - (allocationAddress + size);
if (prefixBlockSize > 0 && suffixBlockSize > 0)
{
// requires two free blocks
if (_swapEndianU32(blockHeapHead->freeBlocksLeft) < 2)
return -1;
}
else if (prefixBlockSize > 0 || suffixBlockSize > 0)
{
// requires one free block
if (_swapEndianU32(blockHeapHead->freeBlocksLeft) < 1)
return -2;
}
MPTR currentPreviousTrack = _swapEndianU32(track->previousBlock);
// remove used block from chain of free blocks (debug)
if (track->isFree != _swapEndianU32(0))
{
// check if block is in list of free blocks (it shouldnt be)
MEMBlockHeapTrackDEPR* trackItr = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffsetAllowNull(_swapEndianU32(blockHeapHead->nextFreeBlock));
while (trackItr)
{
if (trackItr == track)
assert_dbg();
// next
trackItr = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffsetAllowNull(_swapEndianU32(trackItr->nextBlock));
}
}
_blockHeapDebugVerifyLinkOrder(blockHeapHead);
// create prefix block
if (prefixBlockSize > 0)
{
uint32 blockRangeStart = _swapEndianU32(track->addrStart);
uint32 blockRangeEnd = allocationAddress - 1;
MPTR prefixTrackMPTR = _MEMBlockHeap_GetFreeBlockTrack(blockHeapHead);
MEMBlockHeapTrackDEPR* prefixTrack = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffset(prefixTrackMPTR);
prefixTrack->isFree = _swapEndianU32(1);
prefixTrack->addrStart = _swapEndianU32(blockRangeStart);
prefixTrack->addrEnd = _swapEndianU32(blockRangeEnd);
// register new firstBlock
if (blockHeapHead->headBlock == _swapEndianU32(trackMPTR))
blockHeapHead->headBlock = _swapEndianU32(prefixTrackMPTR);
// update link in original previous block
if (_swapEndianU32(track->previousBlock) != MPTR_NULL)
{
MEMBlockHeapTrackDEPR* tempTrack = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffset(_swapEndianU32(track->previousBlock));
tempTrack->nextBlock = _swapEndianU32(prefixTrackMPTR);
}
// update previous/next track link
prefixTrack->nextBlock = _swapEndianU32(trackMPTR);
prefixTrack->previousBlock = _swapEndianU32(currentPreviousTrack);
// set prefix track as current previous track
currentPreviousTrack = prefixTrackMPTR;
}
// update used block
track->isFree = _swapEndianU32(0);
track->addrStart = _swapEndianU32(allocationAddress);
track->addrEnd = _swapEndianU32(allocationAddress + size - 1);
track->previousBlock = _swapEndianU32(currentPreviousTrack);
currentPreviousTrack = trackMPTR;
// create suffix block
if (suffixBlockSize > 0)
{
uint32 blockRangeStart = allocationAddress + size;
uint32 blockRangeEnd = trackEndAddr;
// get suffix track
MPTR suffixTrackMPTR = _MEMBlockHeap_GetFreeBlockTrack(blockHeapHead);
MEMBlockHeapTrackDEPR* suffixTrack = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffset(suffixTrackMPTR);
suffixTrack->isFree = _swapEndianU32(1);
suffixTrack->addrStart = _swapEndianU32(blockRangeStart);
suffixTrack->addrEnd = _swapEndianU32(blockRangeEnd);
// update previous/next track link
suffixTrack->previousBlock = _swapEndianU32(currentPreviousTrack);
suffixTrack->nextBlock = track->nextBlock;
// update last block of heap
if (_swapEndianU32(blockHeapHead->tailBlock) == trackMPTR)
blockHeapHead->tailBlock = _swapEndianU32(suffixTrackMPTR);
// update link in original next block
if (_swapEndianU32(track->nextBlock) != MPTR_NULL)
{
MEMBlockHeapTrackDEPR* tempTrack = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffset(_swapEndianU32(track->nextBlock));
tempTrack->previousBlock = _swapEndianU32(suffixTrackMPTR);
}
// update next block
track->nextBlock = _swapEndianU32(suffixTrackMPTR);
}
_blockHeapDebugVerifyLinkOrder(blockHeapHead);
// todo: Get fill value via MEMGetFillValForHeap
memset(memory_getPointerFromVirtualOffset(allocationAddress), 0x00, size);
return 0;
}
MEMBlockHeapTrackDEPR* _MEMBlockHeap_FindBlockContaining(MEMBlockHeapDEPR* blockHeapHead, MPTR memAddr)
{
MPTR heapStart = _swapEndianU32(blockHeapHead->heapStart);
MPTR heapEnd = _swapEndianU32(blockHeapHead->heapEnd);
if (memAddr < heapStart)
return NULL;
if (memAddr > heapEnd)
return NULL;
uint32 distanceToStart = memAddr - heapStart;
uint32 distanceToEnd = heapEnd - memAddr;
// todo: If distanceToStart < distanceToEnd -> Iterate starting from firstBlock, else iterate starting from lastBlock (and continue via track->previousBlock)
MEMBlockHeapTrackDEPR* track = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffsetAllowNull(_swapEndianU32(blockHeapHead->headBlock));
if (track == NULL)
return NULL;
while (track)
{
if (_swapEndianU32(track->addrStart) == memAddr)
return track;
// next
// todo: Should this be ->previousBlock ?
track = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffsetAllowNull(_swapEndianU32(track->nextBlock));
}
return NULL;
}
MPTR MEMAllocFromBlockHeapEx(MPTR heap, uint32 size, sint32 alignment)
{
MEMBlockHeapDEPR* blockHeapHead = (MEMBlockHeapDEPR*)memory_getPointerFromVirtualOffset(heap);
if (blockHeapHead->magic != MEMHeapMagic::BLOCK_HEAP)
{
return MPTR_NULL;
}
// find free block which can hold the data (including alignment)
__OSLockScheduler(); // todo: replace with spinlock from heap
if (alignment == 0)
alignment = 4;
bool allocateAtEndOfBlock = false;
if (alignment < 0)
{
allocateAtEndOfBlock = true;
alignment = -alignment;
}
MEMBlockHeapTrackDEPR* track;
if (allocateAtEndOfBlock)
track = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffsetAllowNull(_swapEndianU32(blockHeapHead->tailBlock));
else
track = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffsetAllowNull(_swapEndianU32(blockHeapHead->headBlock));
cemu_assert_debug(__popcnt(alignment) == 1); // not a supported alignment value
while (track)
{
if (track->isFree != _swapEndianU32(0))
{
uint32 blockRangeStart = _swapEndianU32(track->addrStart);
uint32 blockRangeEnd = _swapEndianU32(track->addrEnd) + 1;
if (allocateAtEndOfBlock == false)
{
// calculate remaining size with proper alignment
uint32 alignedBlockRangeStart = (blockRangeStart + alignment - 1) & ~(alignment - 1);
if (alignedBlockRangeStart < blockRangeEnd)
{
uint32 allocRange = blockRangeEnd - alignedBlockRangeStart;
if (allocRange >= size)
{
sint32 allocError = _MEMBlockHeap_AllocAtBlock(blockHeapHead, track, alignedBlockRangeStart, size);
_blockHeapDebugVerifyLinkOrder(blockHeapHead);
__OSUnlockScheduler();
if (allocError == 0)
return alignedBlockRangeStart;
return MPTR_NULL;
}
}
}
else
{
uint32 alignedBlockRangeStart = (blockRangeEnd + 1 - size) & ~(alignment - 1);
if (alignedBlockRangeStart >= blockRangeStart)
{
sint32 allocError = _MEMBlockHeap_AllocAtBlock(blockHeapHead, track, alignedBlockRangeStart, size);
_blockHeapDebugVerifyLinkOrder(blockHeapHead);
__OSUnlockScheduler();
if (allocError == 0)
return alignedBlockRangeStart;
return MPTR_NULL;
}
}
}
// next
if (allocateAtEndOfBlock)
track = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffsetAllowNull(_swapEndianU32(track->previousBlock));
else
track = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffsetAllowNull(_swapEndianU32(track->nextBlock));
if (track == nullptr)
break;
}
__OSUnlockScheduler();
return MPTR_NULL;
}
void MEMFreeToBlockHeap(MPTR heap, MPTR memAddr)
{
MEMBlockHeapDEPR* blockHeapHead = (MEMBlockHeapDEPR*)memory_getPointerFromVirtualOffset(heap);
if (blockHeapHead->magic != MEMHeapMagic::BLOCK_HEAP)
{
return;
}
__OSLockScheduler(); // todo: replace with spinlock from heap (if heap threadsafe flag is set)
_blockHeapDebugVerifyLinkOrder(blockHeapHead);
MEMBlockHeapTrackDEPR* block = _MEMBlockHeap_FindBlockContaining(blockHeapHead, memAddr);
if (block != NULL)
{
MPTR blockMPTR = memory_getVirtualOffsetFromPointer(block);
#ifndef PUBLIC_RELEASE
if (block->isFree != 0)
assert_dbg();
_blockHeapDebugVerifyLinkOrder(blockHeapHead);
#endif
// mark current block as free
block->isFree = _swapEndianU32(1);
// attempt to merge with previous block
if (_swapEndianU32(block->previousBlock) != NULL)
{
MPTR previousBlockMPTR = _swapEndianU32(block->previousBlock);
MEMBlockHeapTrackDEPR* previousBlock = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffset(previousBlockMPTR);
if (_swapEndianU32(previousBlock->isFree) != 0)
{
// merge
previousBlock->addrEnd = block->addrEnd;
previousBlock->nextBlock = block->nextBlock;
if (_swapEndianU32(block->nextBlock) != MPTR_NULL)
{
MEMBlockHeapTrackDEPR* tempNextBlock = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffset(_swapEndianU32(block->nextBlock));
tempNextBlock->previousBlock = _swapEndianU32(previousBlockMPTR);
}
// release removed block
_MEMBlockHeap_ReleaseBlockTrack(blockHeapHead, blockMPTR);
// debug
_blockHeapDebugVerifyIfBlockIsLinked(blockHeapHead, (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffset(blockMPTR));
// merged block becomes the new current block
blockMPTR = previousBlockMPTR;
block = previousBlock;
}
}
// attempt to merge with next block
if (_swapEndianU32(block->nextBlock) != NULL)
{
MPTR nextBlockMPTR = _swapEndianU32(block->nextBlock);
MEMBlockHeapTrackDEPR* nextBlock = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffset(nextBlockMPTR);
if (_swapEndianU32(nextBlock->isFree) != 0)
{
// merge
block->addrEnd = nextBlock->addrEnd;
block->nextBlock = nextBlock->nextBlock;
if (_swapEndianU32(nextBlock->nextBlock) != MPTR_NULL)
{
MEMBlockHeapTrackDEPR* tempNextBlock = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffset(_swapEndianU32(nextBlock->nextBlock));
tempNextBlock->previousBlock = _swapEndianU32(blockMPTR);
}
//// merged block becomes the new current block
//blockMPTR = previousBlockMPTR;
//block = previousBlock;
// update last block
if (_swapEndianU32(blockHeapHead->tailBlock) == nextBlockMPTR)
{
blockHeapHead->tailBlock = _swapEndianU32(blockMPTR);
}
// release removed block
_MEMBlockHeap_ReleaseBlockTrack(blockHeapHead, nextBlockMPTR);
// debug
_blockHeapDebugVerifyIfBlockIsLinked(blockHeapHead, (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffset(nextBlockMPTR));
}
}
}
_blockHeapDebugVerifyLinkOrder(blockHeapHead);
__OSUnlockScheduler();
}
uint32 MEMGetTotalFreeSizeForBlockHeap(MEMBlockHeapDEPR* blockHeap)
{
if (!blockHeap || blockHeap->magic != MEMHeapMagic::BLOCK_HEAP)
{
cemu_assert_suspicious();
return 0;
}
__OSLockScheduler(); // todo: replace with spinlock from heap (if heap threadsafe flag is set)
uint32 totalSize = 0;
// sum up all free blocks
MPTR blockMPTR = _swapEndianU32(blockHeap->headBlock);
while (blockMPTR)
{
MEMBlockHeapTrackDEPR* track = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffset(blockMPTR);
if (track->isFree != _swapEndianU32(0))
{
// get block size
uint32 blockSize = _swapEndianU32(track->addrEnd) - _swapEndianU32(track->addrStart) + 1;
// add to totalSize
totalSize += blockSize;
}
// next
blockMPTR = _swapEndianU32(track->nextBlock);
}
__OSUnlockScheduler(); // todo: replace with spinlock from heap (if heap threadsafe flag is set)
return totalSize;
}
void MEMDumpBlockHeap(MPTR heap)
{
MEMBlockHeapDEPR* blockHeapHead = (MEMBlockHeapDEPR*)memory_getPointerFromVirtualOffset(heap);
if (blockHeapHead->magic != MEMHeapMagic::BLOCK_HEAP)
{
cemu_assert_suspicious();
return;
}
// iterate reserved/sized blocks
debug_printf("### MEMDumpBlockHeap ###\n");
__OSLockScheduler(); // todo: replace with spinlock from heap
MEMBlockHeapTrackDEPR* track = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffsetAllowNull(_swapEndianU32(blockHeapHead->headBlock));
while (track)
{
uint32 blockRangeStart = _swapEndianU32(track->addrStart);
uint32 blockRangeEnd = _swapEndianU32(track->addrEnd) + 1;
debug_printf(" %08x %08x - %08x prev/next %08x %08x isFree: %d\n", memory_getVirtualOffsetFromPointer(track), blockRangeStart, blockRangeEnd, _swapEndianU32(track->previousBlock), _swapEndianU32(track->nextBlock), _swapEndianU32(track->isFree));
// next
track = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffsetAllowNull(_swapEndianU32(track->nextBlock));
}
debug_printf("\n");
__OSUnlockScheduler();
}
void InitializeMEMBlockHeap()
{
cafeExportRegister("coreinit", MEMInitBlockHeap, LogType::CoreinitMem);
cafeExportRegister("coreinit", MEMDestroyBlockHeap, LogType::CoreinitMem);
cafeExportRegister("coreinit", MEMGetAllocatableSizeForBlockHeapEx, LogType::CoreinitMem);
cafeExportRegister("coreinit", MEMAddBlockHeapTracking, LogType::CoreinitMem);
cafeExportRegister("coreinit", MEMGetTrackingLeftInBlockHeap, LogType::CoreinitMem);
cafeExportRegister("coreinit", MEMAllocFromBlockHeapEx, LogType::CoreinitMem);
cafeExportRegister("coreinit", MEMFreeToBlockHeap, LogType::CoreinitMem);
cafeExportRegister("coreinit", MEMGetTotalFreeSizeForBlockHeap, LogType::CoreinitMem);
}
}

View file

@ -0,0 +1,34 @@
#pragma once
#include "Cafe/OS/libs/coreinit/coreinit_MEM.h"
namespace coreinit
{
struct MEMBlockHeapTrack2_t
{
/* +0x00 */ MEMPTR<void> addrStart;
/* +0x04 */ MEMPTR<void> addrEnd;
/* +0x08 */ uint32be isFree; // if 0 -> block is used
/* +0x0C */ MEMPTR<void> previousBlock;
/* +0x10 */ MEMPTR<void> nextBlock;
};
static_assert(sizeof(MEMBlockHeapTrack2_t) == 0x14);
struct MEMBlockHeap2_t : MEMHeapBase
{
/* +0x34 */ uint8 ukn[0x50 - 0x34];
/* +0x50 */ MEMBlockHeapTrack2_t track;
/* +0x64 */ MEMPTR<void> headBlock;
/* +0x68 */ MEMPTR<void> tailBlock;
/* +0x6C */ MEMPTR<void> nextFreeBlock;
/* +0x70 */ uint32be freeBlocksLeft;
/* +0x74 */ uint8 padding[0x80 - 0x74];
};
static_assert(sizeof(MEMBlockHeap2_t) == 0x80);
MEMHeapHandle MEMInitBlockHeap(MEMBlockHeap2_t* memStart, void* startAddr, void* endAddr, void* initTrackMem, uint32 initTrackMemSize, uint32 createFlags);
void* MEMDestroyBlockHeap(MEMHeapHandle hHeap);
sint32 MEMAddBlockHeapTracking(MPTR heap, MPTR trackMem, uint32 trackMemSize);
void InitializeMEMBlockHeap();
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,52 @@
#pragma once
#include "Cafe/OS/libs/coreinit/coreinit_Spinlock.h"
#include "Cafe/OS/libs/coreinit/coreinit_MEM.h"
namespace coreinit
{
void expheap_load();
#define MEM_EXPHEAP_ALLOC_MODE_FIRST (0)
#define MEM_EXPHEAP_ALLOC_MODE_NEAR (1)
#define MEM_EXPHEAP_USE_ALIGN_MARGIN (2)
enum class MEMExpHeapAllocDirection : uint32
{
HEAD = 0,
TAIL = 1
};
struct MBlockChain2_t
{
MEMPTR<struct MBlock2_t> headMBlock; // 0x00
MEMPTR<struct MBlock2_t> tailMBlock; // 0x04
};
static_assert(sizeof(MBlockChain2_t) == 8);
struct MEMExpHeapHead40_t
{
/* +0x00 */ MBlockChain2_t chainFreeBlocks; // 0x00
/* +0x08 */ MBlockChain2_t chainUsedBlocks; // 0x08
/* +0x10 */ uint16 groupID;
/* +0x12 */ uint16 fields; // Bit 0 -> Alloc mode, Bit 1 -> Allocate within alignment (create free blocks inside alignment padding)
};
static_assert(sizeof(MEMExpHeapHead40_t) == 0x14);
struct MEMExpHeapHead2 : MEMHeapBase
{
// Base
/* +0x34 */ uint32be ukn34;
/* +0x38 */ uint32be ukn38;
/* +0x3C */ uint32be ukn3C;
/* +0x40 */ MEMExpHeapHead40_t expHeapHead;
};
static_assert(sizeof(MEMExpHeapHead2) == 0x54);
MEMHeapHandle MEMCreateExpHeapEx(void* startAddress, uint32 size, uint32 createFlags);
void* MEMAllocFromExpHeapEx(MEMHeapHandle heap, uint32 size, sint32 alignment);
void MEMFreeToExpHeap(MEMHeapHandle heap, void* mem);
uint32 MEMGetAllocatableSizeForExpHeapEx(MEMHeapHandle heap, sint32 alignment);
}

View file

@ -0,0 +1,246 @@
#include "Cafe/OS/common/OSCommon.h"
#include "Cafe/OS/libs/coreinit/coreinit_MEM.h"
#include "Cafe/OS/libs/coreinit/coreinit_MEM_FrmHeap.h"
namespace coreinit
{
bool __FrmHeapDebug_IsValid(MEMFrmHeap* frmHeap, const char* funcName)
{
if (!frmHeap)
{
cemuLog_log(LogType::APIErrors, "{}: Heap is nullptr", funcName);
return false;
}
if (frmHeap->magic != MEMHeapMagic::FRAME_HEAP)
{
cemuLog_log(LogType::APIErrors, "{}: Heap has bad magic. Not initialized?", funcName);
return false;
}
return true;
}
MEMFrmHeap* MEMCreateFrmHeapEx(void* memStart, uint32 size, uint32 createFlags)
{
cemu_assert_debug(memStart);
uintptr_t startAddr = (uintptr_t)memStart;
uintptr_t endAddr = startAddr + size;
// align and pad address
startAddr = (startAddr + 3) & ~3;
endAddr &= ~3;
if (startAddr == 0)
return nullptr;
if (startAddr > endAddr || (endAddr - startAddr) < sizeof(MEMFrmHeap))
return nullptr;
MEMFrmHeap* frmHeap = (MEMFrmHeap*)startAddr;
MEMInitHeapBase(frmHeap, MEMHeapMagic::FRAME_HEAP, (void*)((uintptr_t)startAddr + sizeof(MEMFrmHeap)), (void*)endAddr, createFlags);
frmHeap->allocationHead = frmHeap->heapStart;
frmHeap->allocationTail = frmHeap->heapEnd;
frmHeap->recordedStates = nullptr;
MEMHeapTable_Add(frmHeap);
return frmHeap;
}
void* MEMDestroyFrmHeap(MEMFrmHeap* frmHeap)
{
if (!__FrmHeapDebug_IsValid(frmHeap, "MEMDestroyFrmHeap"))
return nullptr;
MEMBaseDestroyHeap(frmHeap);
MEMHeapTable_Remove(frmHeap);
return frmHeap;
}
uint32 MEMGetAllocatableSizeForFrmHeapEx(MEMFrmHeap* frmHeap, sint32 alignment)
{
if (!__FrmHeapDebug_IsValid(frmHeap, "MEMGetAllocatableSizeForFrmHeapEx"))
return 0;
frmHeap->AcquireLock();
uint32 allocatableSize = 0;
bool negativeAlignment = alignment < 0;
if (negativeAlignment)
alignment = -alignment;
if (!std::has_single_bit<uint32>((uint32)alignment))
{
cemuLog_log(LogType::APIErrors, "MEMGetAllocatableSizeForFrmHeapEx(): Invalid alignment");
return 0;
}
if (negativeAlignment)
{
cemu_assert_unimplemented();
}
else
{
uint32 headAllocator = frmHeap->allocationHead.GetMPTR();
uint32 tailAllocator = frmHeap->allocationTail.GetMPTR();
uint32 allocStart = (headAllocator + alignment - 1) & ~(alignment - 1);
if (allocStart <= tailAllocator)
{
allocatableSize = tailAllocator - allocStart;
}
}
frmHeap->ReleaseLock();
return allocatableSize;
}
void* MEMiGetFreeStartForFrmHeap(MEMFrmHeap* heap)
{
if (!__FrmHeapDebug_IsValid(heap, "MEMiGetFreeStartForFrmHeap"))
return nullptr;
return heap->allocationHead;
}
void* MEMiGetFreeEndForFrmHeap(MEMFrmHeap* heap)
{
if (!__FrmHeapDebug_IsValid(heap, "MEMiGetFreeEndForFrmHeap"))
return nullptr;
return heap->allocationTail;
}
void* __FrmHeap_AllocFromHead(MEMFrmHeap* frmHeap, uint32 size, sint32 alignment)
{
uint32 head = frmHeap->allocationHead.GetMPTR();
uint32 tail = frmHeap->allocationTail.GetMPTR();
uint32 allocStart = (head + alignment - 1) & ~(alignment - 1);
if ((allocStart + size) <= tail)
{
auto prevHead = frmHeap->allocationHead;
frmHeap->allocationHead = allocStart + size;
if (frmHeap->HasOptionClear())
memset(prevHead.GetPtr(), 0, frmHeap->allocationHead.GetMPTR() - prevHead.GetMPTR());
return MEMPTR<void>(allocStart).GetPtr();
}
return nullptr;
}
void* __FrmHeap_AllocFromTail(MEMFrmHeap* frmHeap, uint32 size, sint32 alignment)
{
uint32 head = frmHeap->allocationHead.GetMPTR();
uint32 tail = frmHeap->allocationTail.GetMPTR();
uint32 allocStart = (tail - size) & ~(alignment - 1);
if (allocStart >= head)
{
auto prevTail = frmHeap->allocationTail;
frmHeap->allocationTail = allocStart;
if (frmHeap->HasOptionClear())
memset(frmHeap->allocationTail.GetPtr(), 0, prevTail.GetMPTR() - frmHeap->allocationTail.GetMPTR());
return MEMPTR<void>(allocStart).GetPtr();
}
return nullptr;
}
void* MEMAllocFromFrmHeapEx(MEMFrmHeap* frmHeap, uint32 size, sint32 alignment)
{
if (!__FrmHeapDebug_IsValid(frmHeap, "MEMAllocFromFrmHeapEx"))
return nullptr;
if (size == 0)
size = 4;
size = (size + 3) & ~3; // pad to 4 byte alignment
frmHeap->AcquireLock();
void* mem;
if (alignment >= 0)
mem = __FrmHeap_AllocFromHead(frmHeap, size, alignment);
else
mem = __FrmHeap_AllocFromTail(frmHeap, size, -alignment);
frmHeap->ReleaseLock();
return mem;
}
void __FrmHeap_FreeFromHead(MEMFrmHeap* frmHeap)
{
cemu_assert_debug(frmHeap->recordedStates.IsNull());
frmHeap->recordedStates = nullptr;
frmHeap->allocationHead = frmHeap->heapStart;
}
void __FrmHeap_FreeFromTail(MEMFrmHeap* frmHeap)
{
cemu_assert_debug(frmHeap->recordedStates.IsNull());
frmHeap->recordedStates = nullptr;
void* heapEnd = frmHeap->heapEnd.GetPtr();
frmHeap->allocationTail = heapEnd;
}
void MEMFreeToFrmHeap(MEMFrmHeap* frmHeap, FrmHeapMode mode)
{
if (!__FrmHeapDebug_IsValid(frmHeap, "MEMFreeToFrmHeap"))
return;
frmHeap->AcquireLock();
if ((mode & FrmHeapMode::Head) != 0)
__FrmHeap_FreeFromHead(frmHeap);
if ((mode & FrmHeapMode::Tail) != 0)
__FrmHeap_FreeFromTail(frmHeap);
frmHeap->ReleaseLock();
}
bool MEMRecordStateForFrmHeap(MEMFrmHeap* frmHeap, uint32 id)
{
if (!__FrmHeapDebug_IsValid(frmHeap, "MEMRecordStateForFrmHeap"))
return false;
frmHeap->AcquireLock();
auto allocationHead = frmHeap->allocationHead;
auto allocationTail = frmHeap->allocationTail;
MEMFrmHeapRecordedState* rState = (MEMFrmHeapRecordedState*)__FrmHeap_AllocFromHead(frmHeap, sizeof(MEMFrmHeapRecordedState), 4); // modifies memHeap->allocationHead
cemu_assert_debug(rState);
if (!rState)
{
frmHeap->ReleaseLock();
return false;
}
rState->id = id;
rState->allocationHead = allocationHead;
rState->allocationTail = allocationTail;
rState->prevRecordedState = frmHeap->recordedStates;
frmHeap->recordedStates = rState;
frmHeap->ReleaseLock();
return true;
}
bool MEMFreeByStateToFrmHeap(MEMFrmHeap* frmHeap, uint32 id)
{
if (!__FrmHeapDebug_IsValid(frmHeap, "MEMFreeByStateToFrmHeap"))
return false;
frmHeap->AcquireLock();
// find matching state
MEMFrmHeapRecordedState* rState = frmHeap->recordedStates.GetPtr();
while (rState)
{
if (id == 0)
break;
if (rState->id == id)
break;
rState = rState->prevRecordedState.GetPtr();
}
if (!rState)
{
frmHeap->ReleaseLock();
return false;
}
// apply state
frmHeap->allocationHead = rState->allocationHead;
frmHeap->allocationTail = rState->allocationTail;
frmHeap->recordedStates = rState->prevRecordedState;
frmHeap->ReleaseLock();
return true;
}
void InitializeMEMFrmHeap()
{
cafeExportRegister("coreinit", MEMCreateFrmHeapEx, LogType::CoreinitMem);
cafeExportRegister("coreinit", MEMDestroyFrmHeap, LogType::CoreinitMem);
cafeExportRegister("coreinit", MEMGetAllocatableSizeForFrmHeapEx, LogType::CoreinitMem);
cafeExportRegister("coreinit", MEMiGetFreeStartForFrmHeap, LogType::CoreinitMem);
cafeExportRegister("coreinit", MEMiGetFreeEndForFrmHeap, LogType::CoreinitMem);
cafeExportRegister("coreinit", MEMAllocFromFrmHeapEx, LogType::CoreinitMem);
cafeExportRegister("coreinit", MEMFreeToFrmHeap, LogType::CoreinitMem);
cafeExportRegister("coreinit", MEMFreeToFrmHeap, LogType::CoreinitMem);
cafeExportRegister("coreinit", MEMRecordStateForFrmHeap, LogType::CoreinitMem);
cafeExportRegister("coreinit", MEMFreeByStateToFrmHeap, LogType::CoreinitMem);
}
}

View file

@ -0,0 +1,41 @@
#pragma once
#include "Cafe/OS/libs/coreinit/coreinit_MEM.h"
namespace coreinit
{
struct MEMFrmHeapRecordedState
{
uint32be id;
MEMPTR<void> allocationHead;
MEMPTR<void> allocationTail;
MEMPTR<MEMFrmHeapRecordedState> prevRecordedState;
};
static_assert(sizeof(MEMFrmHeapRecordedState) == 0x10);
struct MEMFrmHeap : MEMHeapBase
{
/* +0x34 */ uint32be ukn34;
/* +0x38 */ uint32be ukn38;
/* +0x3C */ uint32be ukn3C;
/* +0x40 */ MEMPTR<void> allocationHead;
/* +0x44 */ MEMPTR<void> allocationTail;
/* +0x48 */ MEMPTR<MEMFrmHeapRecordedState> recordedStates;
};
static_assert(sizeof(MEMFrmHeap) == 0x4C);
enum class FrmHeapMode : uint32
{
Head = (1 << 0),
Tail = (1 << 1),
All = (Head | Tail),
};
MEMFrmHeap* MEMCreateFrmHeapEx(void* memStart, uint32 size, uint32 createFlags);
void* MEMDestroyFrmHeap(MEMFrmHeap* frmHeap);
void* MEMAllocFromFrmHeapEx(MEMFrmHeap* frmHeap, uint32 size, sint32 alignment);
void MEMFreeToFrmHeap(MEMFrmHeap* frmHeap, FrmHeapMode mode);
void InitializeMEMFrmHeap();
}
ENABLE_BITMASK_OPERATORS(coreinit::FrmHeapMode);

View file

@ -0,0 +1,146 @@
#include "Cafe/OS/common/OSCommon.h"
#include "Cafe/OS/libs/coreinit/coreinit_MEM.h"
#include "Cafe/OS/libs/coreinit/coreinit_MEM_UnitHeap.h"
#include "Cafe/OS/libs/coreinit/coreinit_Spinlock.h"
namespace coreinit
{
void _MEMUnitHeap_IsValidHeap(MEMHeapHandle heap)
{
cemu_assert(heap != MEM_HEAP_INVALID_HANDLE);
cemu_assert(heap->magic == MEMHeapMagic::UNIT_HEAP);
}
MEMHeapBase* MEMCreateUnitHeapEx(void* memStart, uint32 heapSize, uint32 blockSize, uint32 alignment, uint32 createFlags)
{
cemu_assert_debug(memStart != nullptr);
cemu_assert_debug(alignment % MIN_ALIGNMENT == 0);
cemu_assert_debug(MIN_ALIGNMENT <= alignment && alignment <= 32);
uintptr_t startAddr = (uintptr_t)memStart;
uintptr_t endAddr = startAddr + heapSize;
startAddr = (startAddr + MIN_ALIGNMENT_MINUS_ONE) & (~MIN_ALIGNMENT_MINUS_ONE);
endAddr &= (~MIN_ALIGNMENT_MINUS_ONE);
if (startAddr > endAddr)
return nullptr;
const uint32 alignmentMinusOne = alignment - 1;
MEMUnitHeap* unitHeap = (MEMUnitHeap*)startAddr;
uintptr_t alignedStart = startAddr + sizeof(MEMUnitHeap) + alignmentMinusOne & ~((uintptr_t)alignmentMinusOne);
if (alignedStart > endAddr)
return nullptr;
blockSize = blockSize + alignmentMinusOne & ~alignmentMinusOne;
uint32 totalBlockSize = (uint32)(endAddr - alignedStart);
uint32 numBlocks = totalBlockSize / blockSize;
if (numBlocks == 0)
return nullptr;
MEMInitHeapBase(unitHeap, MEMHeapMagic::UNIT_HEAP, (void*)alignedStart, (void*)(alignedStart + (numBlocks * blockSize)), createFlags);
unitHeap->firstFreeBlock = (MEMUnitHeapBlock*)alignedStart;
unitHeap->blockSize = blockSize;
MEMUnitHeapBlock* currentBlock = (MEMUnitHeapBlock*)alignedStart;
for (uint32 i = 0; i < numBlocks - 1; ++i)
{
MEMUnitHeapBlock* nextBlock = (MEMUnitHeapBlock*)((uintptr_t)currentBlock + blockSize);
currentBlock->nextBlock = nextBlock;
currentBlock = nextBlock;
}
currentBlock->nextBlock = nullptr;
if ((MEMHeapHandle*)startAddr != nullptr)
{
MEMHeapTable_Add((MEMHeapHandle)startAddr);
}
return (MEMHeapBase*)startAddr;
}
void* MEMDestroyUnitHeap(MEMHeapHandle heap)
{
_MEMUnitHeap_IsValidHeap(heap);
MEMBaseDestroyHeap(heap);
MEMHeapTable_Remove(heap);
return heap;
}
uint32 MEMCalcHeapSizeForUnitHeap(uint32 blockSize, uint32 blockCount, uint32 alignment)
{
uint32 alignedBlockSize = (blockSize + (alignment - 1)) & ~(alignment - 1);
uint32 blockTotalSize = blockCount * alignedBlockSize;
uint32 heapSize = blockTotalSize + (alignment - 4) + sizeof(MEMUnitHeap);
return heapSize;
}
uint32 MEMCountFreeBlockForUnitHeap(coreinit::MEMUnitHeap* heap)
{
_MEMUnitHeap_IsValidHeap(heap);
heap->AcquireLock();
MEMUnitHeapBlock* currentBlock = heap->firstFreeBlock;
uint32 blockCount = 0;
while (currentBlock)
{
blockCount++;
currentBlock = currentBlock->nextBlock;
}
heap->ReleaseLock();
return blockCount;
}
void* MEMAllocFromUnitHeap(MEMUnitHeap* heap)
{
_MEMUnitHeap_IsValidHeap(heap);
heap->AcquireLock();
MEMUnitHeapBlock* currentBlock = heap->firstFreeBlock;
if (!currentBlock)
{
heap->ReleaseLock();
return nullptr;
}
// remove from list of free blocks
heap->firstFreeBlock = currentBlock->nextBlock;
// fill block
if (heap->HasOptionClear())
{
memset(currentBlock, 0, heap->blockSize);
}
else if (heap->HasOptionFill())
{
memset(currentBlock, coreinit::MEMGetFillValForHeap(coreinit::HEAP_FILL_TYPE::ON_ALLOC), heap->blockSize);
}
heap->ReleaseLock();
return currentBlock;
}
void MEMFreeToUnitHeap(MEMUnitHeap* heap, void* mem)
{
_MEMUnitHeap_IsValidHeap(heap);
if (!mem)
return;
heap->AcquireLock();
cemu_assert_debug(mem >= heap->heapStart.GetPtr() && mem < heap->heapEnd.GetPtr());
// add to list of free blocks
MEMUnitHeapBlock* releasedBlock = (MEMUnitHeapBlock*)mem;
releasedBlock->nextBlock = heap->firstFreeBlock;
heap->firstFreeBlock = releasedBlock;
if (heap->HasOptionFill())
memset(releasedBlock, coreinit::MEMGetFillValForHeap(coreinit::HEAP_FILL_TYPE::ON_FREE), heap->blockSize);
heap->ReleaseLock();
}
void InitializeMEMUnitHeap()
{
cafeExportRegister("coreinit", MEMCreateUnitHeapEx, LogType::CoreinitMem);
cafeExportRegister("coreinit", MEMDestroyUnitHeap, LogType::CoreinitMem);
cafeExportRegister("coreinit", MEMCalcHeapSizeForUnitHeap, LogType::CoreinitMem);
cafeExportRegister("coreinit", MEMCountFreeBlockForUnitHeap, LogType::CoreinitMem);
cafeExportRegister("coreinit", MEMAllocFromUnitHeap, LogType::CoreinitMem);
cafeExportRegister("coreinit", MEMFreeToUnitHeap, LogType::CoreinitMem);
}
}

View file

@ -0,0 +1,24 @@
#pragma once
#include "Cafe/OS/libs/coreinit/coreinit_MEM.h"
namespace coreinit
{
struct MEMUnitHeapBlock
{
MEMPTR<MEMUnitHeapBlock> nextBlock;
};
static_assert(sizeof(MEMUnitHeapBlock) == 4);
struct MEMUnitHeap : public MEMHeapBase
{
/* +0x34 */ uint32 padding034;
/* +0x38 */ uint32 padding038;
/* +0x3C */ uint32 padding03C;
/* +0x40 */ MEMPTR<MEMUnitHeapBlock> firstFreeBlock;
/* +0x44 */ uint32be blockSize;
};
static_assert(sizeof(MEMUnitHeap) == 0x48);
MEMHeapBase* MEMCreateUnitHeapEx(void* memStart, uint32 heapSize, uint32 memBlockSize, uint32 alignment, uint32 createFlags);
void* MEMDestroyUnitHeap(MEMHeapHandle hHeap);
}

View file

@ -0,0 +1,516 @@
#include "Cafe/OS/common/OSCommon.h"
#include "Cafe/OS/libs/coreinit/coreinit_MPQueue.h"
#include "Cafe/OS/libs/coreinit/coreinit_Time.h"
#include "Cafe/HW/Espresso/PPCCallback.h"
#include "util/helpers/fspinlock.h"
// titles that utilize MP task queue: Yoshi's Woolly World, Fast Racing Neo, Tokyo Mirage Sessions, Mii Maker
#define AcquireMPQLock() s_workaroundSpinlock.acquire()
#define ReleaseMPQLock() s_workaroundSpinlock.release()
namespace coreinit
{
FSpinlock s_workaroundSpinlock; // workaround for a race condition
/*
Race condition pseudo-code:
WorkerThreads:
while( task = MPDequeTask() ) MPRunTask(task);
MainThread:
QueueTasks();
// wait and reset
MPWaitForTaskQWithTimeout(DONE)
MPTermTaskQ()
MPInitTaskQ()
The race condition then happens when a worker thread calls MPDequeTask()/MPRunTask while MPInitTaskQ() is being executed on the main thread.
Since MPInitTaskQ() (re)initializes the internal spinlock it's not thread-safe, leading to a corruption of the spinlock state for other threads
We work around this by using a global spinlock instead of the taskQ specific one
*/
void MPInitTask(MPTask* task, void* func, void* data, uint32 size)
{
s_workaroundSpinlock.acquire();
task->thisptr = task;
task->coreIndex = PPC_CORE_COUNT;
task->taskFunc.func = func;
task->taskFunc.data = data;
task->taskFunc.size = size;
task->taskState = MP_TASK_STATE_INIT;
task->userdata = nullptr;
task->runtime = 0;
s_workaroundSpinlock.release();
}
bool MPTermTask(MPTask* task)
{
return true;
}
bool MPRunTask(MPTask* task)
{
if (task->taskState != MP_TASK_STATE_READY)
return false;
auto* taskQ = task->taskQ.GetPtr();
if(taskQ->state != MP_TASKQ_STATE_STOPPING && taskQ->state != MP_TASKQ_STATE_STOP)
{
AcquireMPQLock(); // OSUninterruptibleSpinLock_Acquire(&taskQ->spinlock);
if (taskQ->state == MP_TASKQ_STATE_STOPPING || taskQ->state == MP_TASKQ_STATE_STOP)
{
ReleaseMPQLock(); // OSUninterruptibleSpinLock_Release(&taskQ->spinlock);
return false;
}
taskQ->taskReadyCount = taskQ->taskReadyCount - 1;
taskQ->taskRunCount = taskQ->taskRunCount + 1;
ReleaseMPQLock(); // OSUninterruptibleSpinLock_Release(&taskQ->spinlock);
const auto startTime = OSGetSystemTime();
task->coreIndex = OSGetCoreId();
task->taskState = MP_TASK_STATE_RUN;
task->taskFunc.result = PPCCoreCallback(task->taskFunc.func, task->taskFunc.data, task->taskFunc.size);
task->taskState = MP_TASK_STATE_DONE;
const auto endTime = OSGetSystemTime();
task->runtime = endTime - startTime;
cemu_assert_debug(taskQ->state != MP_TASKQ_STATE_DONE);
AcquireMPQLock(); // OSUninterruptibleSpinLock_Acquire(&taskQ->spinlock);
taskQ->taskRunCount = taskQ->taskRunCount - 1;
taskQ->taskDoneCount[1] = taskQ->taskDoneCount[1] + 1;
if (taskQ->state == MP_TASKQ_STATE_STOPPING && taskQ->taskRunCount == 0)
taskQ->state = MP_TASKQ_STATE_STOP;
if (taskQ->taskCount == taskQ->taskDoneCount[1])
taskQ->state = MP_TASKQ_STATE_DONE;
ReleaseMPQLock(); // OSUninterruptibleSpinLock_Release(&taskQ->spinlock);
return true;
}
return false;
}
bool MPGetTaskInfo(MPTask* task, MPTaskInfo* info)
{
info->state = task->taskState;
info->coreIndex = task->coreIndex;
info->runtime = task->runtime;
// not setting function result?
return true;
}
void* MPGetTaskUserData(MPTask* task)
{
return task->userdata.GetPtr();
}
void MPSetTaskUserData(MPTask* task, void* userdata)
{
task->userdata = userdata;
}
void MPInitTaskQ(MPTaskQ* taskQ, MPTask** tasks, uint32 taskCount)
{
AcquireMPQLock();
taskQ->thisptr = taskQ;
OSInitSpinLock(&taskQ->spinlock);
taskQ->state = MP_TASKQ_STATE_INIT;
taskQ->taskReadyCount = 0;
taskQ->taskCount = 0;
taskQ->taskRunCount = 0;
for(uint32 i = 0; i < OSGetCoreCount(); ++i)
{
taskQ->taskDoneCount[i] = 0;
taskQ->nextIndex[i] = 0;
taskQ->endIndex[i] = 0;
}
taskQ->taskQueue = (MEMPTR<MPTask>*)tasks;
taskQ->taskQueueSize = taskCount;
ReleaseMPQLock();
}
bool MPEnqueTask(MPTaskQ* taskq, MPTask* task)
{
bool result = false;
if(task->taskState == MP_TASK_STATE_INIT)
{
AcquireMPQLock(); // OSUninterruptibleSpinLock_Acquire(&taskq->spinlock);
const uint32 taskQState = taskq->state;
if((uint32)taskq->endIndex[1] < taskq->taskQueueSize
&& (taskQState == MP_TASKQ_STATE_INIT || taskQState == MP_TASKQ_STATE_RUN || taskQState == MP_TASKQ_STATE_STOPPING || taskQState == MP_TASKQ_STATE_STOP || taskQState == MP_TASKQ_STATE_DONE))
{
task->taskQ = taskq;
task->taskState = MP_TASK_STATE_READY;
taskq->thisptr = taskq;
const uint32 endIndex = taskq->endIndex[1];
taskq->endIndex[1] = endIndex + 1;
taskq->taskCount = taskq->taskCount + 1;
taskq->taskReadyCount = taskq->taskReadyCount + 1;
taskq->taskQueue[endIndex] = task;
if (taskQState == MP_TASKQ_STATE_DONE)
taskq->state = MP_TASKQ_STATE_RUN;
result = true;
}
ReleaseMPQLock(); // OSUninterruptibleSpinLock_Release(&taskq->spinlock);
}
return result;
}
bool MPTermTaskQ(MPTaskQ* taskq)
{
// workaround code for TMS
AcquireMPQLock(); // OSUninterruptibleSpinLock_Acquire(&taskq->spinlock);
//if ((uint32)taskq->taskReadyCount > 0 && taskq->state == MP_TASKQ_STATE_RUN)
if (taskq->state == MP_TASKQ_STATE_RUN)
{
taskq->state = MP_TASKQ_STATE_STOP;
}
while (taskq->taskRunCount != 0)
{
// wait for tasks to finish
ReleaseMPQLock(); // OSUninterruptibleSpinLock_Release(&taskq->spinlock);
OSYieldThread();
AcquireMPQLock(); // OSUninterruptibleSpinLock_Acquire(&taskq->spinlock);
}
ReleaseMPQLock(); // OSUninterruptibleSpinLock_Release(&taskq->spinlock);
return true;
}
bool MPGetTaskQInfo(MPTaskQ* taskq, MPTaskQInfo* info)
{
AcquireMPQLock(); // OSUninterruptibleSpinLock_Acquire(&taskq->spinlock);
info->state = taskq->state;
info->taskCount = taskq->taskCount;
info->taskReadyCount = taskq->taskReadyCount;
info->taskRunCount = taskq->taskRunCount;
info->taskDoneCount = taskq->taskDoneCount[1];
ReleaseMPQLock(); // OSUninterruptibleSpinLock_Release(&taskq->spinlock);
return true;
}
bool MPStartTaskQ(MPTaskQ* taskq)
{
bool result = false;
AcquireMPQLock(); // OSUninterruptibleSpinLock_Acquire(&taskq->spinlock);
if (taskq->state == MP_TASKQ_STATE_INIT || taskq->state == MP_TASKQ_STATE_STOP)
{
taskq->state = MP_TASKQ_STATE_RUN;
result = true;
}
ReleaseMPQLock(); // OSUninterruptibleSpinLock_Release(&taskq->spinlock);
return result;
}
bool MPRunTasksFromTaskQ(MPTaskQ* taskq, int granularity)
{
uint32 result = 0;
while (true)
{
if (taskq->state != MP_TASKQ_STATE_RUN)
return (taskq->state & MP_TASKQ_STATE_DONE) != 0;
AcquireMPQLock(); // OSUninterruptibleSpinLock_Acquire(&taskq->spinlock);
const auto nextIndex = taskq->nextIndex[1];
const auto endIndex = taskq->endIndex[1];
if (nextIndex == endIndex)
break;
auto newNextIndex = nextIndex + granularity;
if (endIndex < nextIndex + granularity)
newNextIndex = endIndex;
const auto workCount = (newNextIndex - nextIndex);
taskq->nextIndex[1] = newNextIndex;
taskq->taskReadyCount = taskq->taskReadyCount - workCount;
taskq->taskRunCount = taskq->taskRunCount + workCount;
ReleaseMPQLock(); // OSUninterruptibleSpinLock_Release(&taskq->spinlock);
// since we are having a granularity parameter, we might want to give the scheduler the chance for other stuff when having multiple tasks
if(result != 0)
PPCCore_switchToScheduler();
for (int i = nextIndex; i < newNextIndex; ++i)
{
const auto startTime = OSGetSystemTime();
const auto& task = taskq->taskQueue[i];
result = task->thisptr.GetMPTR();
task->taskState = MP_TASK_STATE_RUN;
task->coreIndex = OSGetCoreId();
task->taskFunc.result = PPCCoreCallback(task->taskFunc.func, task->taskFunc.data, task->taskFunc.size);
task->taskState = MP_TASK_STATE_DONE;
const auto endTime = OSGetSystemTime();
task->runtime = endTime - startTime;
}
AcquireMPQLock(); // OSUninterruptibleSpinLock_Acquire(&taskq->spinlock);
const auto runRemaining = taskq->taskRunCount - workCount;
taskq->taskRunCount = runRemaining;
const auto doneCount = taskq->taskDoneCount[1] + workCount;
taskq->taskDoneCount[1] = doneCount;
if (taskq->state == 4 && runRemaining == 0)
taskq->state = MP_TASKQ_STATE_STOP;
if (taskq->taskCount == doneCount)
taskq->state = MP_TASKQ_STATE_DONE;
ReleaseMPQLock(); // OSUninterruptibleSpinLock_Release(&taskq->spinlock);
}
ReleaseMPQLock(); // OSUninterruptibleSpinLock_Release(&taskq->spinlock);
return result != 0;
}
bool MPStopTaskQ(MPTaskQ* taskq)
{
bool result = false;
AcquireMPQLock(); // OSUninterruptibleSpinLock_Acquire(&taskq->spinlock);
if (taskq->state == MP_TASKQ_STATE_RUN)
{
taskq->state = MP_TASKQ_STATE_STOPPING;
if (taskq->taskRunCount == 0)
taskq->state = MP_TASKQ_STATE_STOP;
result = true;
}
ReleaseMPQLock(); // OSUninterruptibleSpinLock_Release(&taskq->spinlock);
return result;
}
bool MPWaitTaskQ(MPTaskQ* taskQ, uint32 waitState)
{
bool waitRun = (waitState & MP_TASKQ_STATE_RUN) != 0;
bool waitStop = (waitState & MP_TASKQ_STATE_STOP) != 0;
bool waitDone = (waitState & MP_TASKQ_STATE_DONE) != 0;
size_t loopCounter = 0;
while (waitStop || waitDone || waitRun)
{
const uint32 state = taskQ->state;
if (waitRun && HAS_FLAG(state, MP_TASKQ_STATE_RUN))
{
waitRun = false;
waitDone = false;
continue;
}
if (waitStop && HAS_FLAG(state, MP_TASKQ_STATE_STOP))
{
waitStop = false;
waitDone = false;
continue;
}
if (waitDone && HAS_FLAG(state, MP_TASKQ_STATE_DONE))
{
waitDone = false;
waitRun = false;
waitStop = false;
continue;
}
if (loopCounter > 0)
coreinit::OSSleepTicks(EspressoTime::ConvertNsToTimerTicks(50000)); // sleep thread for 0.05ms to give other threads a chance to run (avoids softlocks in YWW)
else
PPCCore_switchToScheduler();
loopCounter++;
}
return true;
}
bool MPWaitTaskQWithTimeout(MPTaskQ* taskQ, uint32 waitState, sint64 timeout)
{
bool waitRun = (waitState & MP_TASKQ_STATE_RUN) != 0;
bool waitStop = (waitState & MP_TASKQ_STATE_STOP) != 0;
bool waitDone = (waitState & MP_TASKQ_STATE_DONE) != 0;
const auto startTime = OSGetSystemTime();
const auto timerTicks = EspressoTime::ConvertNsToTimerTicks(timeout);
const auto endTime = startTime + timerTicks;
while (waitStop || waitDone || waitRun)
{
const uint32 state = taskQ->state;
if (waitRun && HAS_FLAG(state, MP_TASKQ_STATE_RUN))
{
waitRun = false;
waitDone = false;
continue;
}
if (waitStop && HAS_FLAG(state, MP_TASKQ_STATE_STOP))
{
waitStop = false;
waitDone = false;
continue;
}
if (waitDone && HAS_FLAG(state, MP_TASKQ_STATE_DONE))
{
waitDone = false;
waitRun = false;
waitStop = false;
continue;
}
if (OSGetSystemTime() >= endTime)
{
if (waitState == MP_TASKQ_STATE_DONE)
cemuLog_log(LogType::Force, "MPWaitTaskQWithTimeout(): Timeout occurred while waiting for done-only state");
return false;
}
PPCCore_switchToScheduler();
}
return true;
}
MPTask* MPDequeTask(MPTaskQ* taskq)
{
MPTask* result = nullptr;
if (taskq->state == MP_TASKQ_STATE_RUN)
{
AcquireMPQLock(); // OSUninterruptibleSpinLock_Acquire(&taskq->spinlock);
if (taskq->state == MP_TASKQ_STATE_RUN && taskq->nextIndex[1] != taskq->endIndex[1])
{
result = taskq->taskQueue[taskq->nextIndex[1]].GetPtr();
taskq->nextIndex[1] = taskq->nextIndex[1] + 1;
}
ReleaseMPQLock(); // OSUninterruptibleSpinLock_Release(&taskq->spinlock);
}
return result;
}
uint32 MPDequeTasks(MPTaskQ* taskq, MPTask** tasks, sint32 maxTasks)
{
uint32 dequeCount = 0;
if (taskq->state == MP_TASKQ_STATE_RUN)
{
AcquireMPQLock(); // OSUninterruptibleSpinLock_Acquire(&taskq->spinlock);
if (taskq->state == MP_TASKQ_STATE_RUN)
{
auto nextIndex = (sint32)taskq->nextIndex[1];
auto newEndIndex = nextIndex + maxTasks;
if (taskq->endIndex[1] < nextIndex + maxTasks)
newEndIndex = taskq->endIndex[1];
dequeCount = newEndIndex - nextIndex;
taskq->nextIndex[1] = newEndIndex;
for(int i = 0; nextIndex < newEndIndex; ++nextIndex, ++i)
{
tasks[i] = taskq->taskQueue[nextIndex].GetPtr();
}
auto idx = 0;
while (nextIndex < newEndIndex)
{
tasks[idx] = taskq->taskQueue[nextIndex].GetPtr();
nextIndex = nextIndex + 1;
idx = idx + 1;
}
}
ReleaseMPQLock(); // OSUninterruptibleSpinLock_Release(&taskq->spinlock);
}
return dequeCount;
}
bool MPResetTaskQ(MPTaskQ* taskq)
{
debug_printf("MPResetTaskQ called\n");
bool result = false;
AcquireMPQLock(); // OSUninterruptibleSpinLock_Acquire(&taskq->spinlock);
if (taskq->state == MP_TASKQ_STATE_DONE || taskq->state == MP_TASKQ_STATE_STOP)
{
taskq->state = MP_TASKQ_STATE_INIT;
taskq->taskRunCount = 0;
taskq->taskCount = taskq->endIndex[1];
taskq->taskReadyCount = taskq->endIndex[1];
for(uint32 i = 0; i < OSGetCoreCount(); ++i)
{
taskq->taskDoneCount[i] = 0;
taskq->nextIndex[i] = 0;
}
for(uint32 i = 0; i < taskq->taskCount; ++i)
{
const auto& task = taskq->taskQueue[i];
task->taskFunc.result = 0;
task->coreIndex = PPC_CORE_COUNT;
task->runtime = 0;
task->taskState = MP_TASK_STATE_READY;
}
result = true;
}
ReleaseMPQLock(); // OSUninterruptibleSpinLock_Release(&taskq->spinlock);
return result;
}
void InitializeMP()
{
// task
cafeExportRegister("coreinit", MPInitTask, LogType::CoreinitMP);
cafeExportRegister("coreinit", MPTermTask, LogType::CoreinitMP);
cafeExportRegister("coreinit", MPRunTask, LogType::CoreinitMP);
cafeExportRegister("coreinit", MPGetTaskInfo, LogType::CoreinitMP);
cafeExportRegister("coreinit", MPGetTaskUserData, LogType::CoreinitMP);
cafeExportRegister("coreinit", MPSetTaskUserData, LogType::CoreinitMP);
// taskq
cafeExportRegister("coreinit", MPInitTaskQ, LogType::CoreinitMP);
cafeExportRegister("coreinit", MPResetTaskQ, LogType::CoreinitMP);
cafeExportRegister("coreinit", MPEnqueTask, LogType::CoreinitMP);
cafeExportRegister("coreinit", MPDequeTask, LogType::CoreinitMP);
cafeExportRegister("coreinit", MPDequeTasks, LogType::CoreinitMP);
cafeExportRegister("coreinit", MPRunTasksFromTaskQ, LogType::CoreinitMP);
cafeExportRegister("coreinit", MPStartTaskQ, LogType::CoreinitMP);
cafeExportRegister("coreinit", MPWaitTaskQ, LogType::CoreinitMP);
cafeExportRegister("coreinit", MPWaitTaskQWithTimeout, LogType::CoreinitMP);
cafeExportRegister("coreinit", MPStopTaskQ, LogType::CoreinitMP);
cafeExportRegister("coreinit", MPTermTaskQ, LogType::CoreinitMP);
cafeExportRegister("coreinit", MPGetTaskQInfo, LogType::CoreinitMP);
}
}

View file

@ -0,0 +1,106 @@
#pragma once
#include "Cafe/OS/libs/coreinit/coreinit.h"
#include "Cafe/OS/libs/coreinit/coreinit_Spinlock.h"
namespace coreinit
{
enum MPTaskState
{
MP_TASK_STATE_INIT = (1 << 0),
MP_TASK_STATE_READY = (1 << 1),
MP_TASK_STATE_RUN = (1 << 2),
MP_TASK_STATE_DONE = (1 << 3)
};
enum MPTaskQState
{
MP_TASKQ_STATE_INIT = (1 << 0),
MP_TASKQ_STATE_RUN = (1 << 1),
MP_TASKQ_STATE_STOPPING = (1 << 2),
MP_TASKQ_STATE_STOP = (1 << 3),
MP_TASKQ_STATE_DONE = (1 << 4)
};
struct MPTaskFunction
{
/* +0x00 */ MEMPTR<void> func;
/* +0x04 */ MEMPTR<void> data;
/* +0x08 */ uint32be size;
/* +0x0C */ uint32be result;
};
static_assert(sizeof(MPTaskFunction) == 0x10);
#pragma pack(1)
struct MPTask
{
/* +0x00 */ MEMPTR<void> thisptr;
/* +0x04 */ MEMPTR<struct MPTaskQ> taskQ;
/* +0x08 */ uint32be taskState;
/* +0x0C */ MPTaskFunction taskFunc;
/* +0x1C */ uint32be coreIndex;
/* +0x20 */ sint64be runtime;
/* +0x28 */ MEMPTR<void> userdata;
};
static_assert(sizeof(MPTask) == 0x2C);
#pragma pack()
struct MPTaskQ
{
/* +0x00 */ MEMPTR<void> thisptr;
/* +0x04 */ uint32be state;
/* +0x08 */ uint32be taskCount;
/* +0x0C */ uint32be taskReadyCount;
/* +0x10 */ uint32be taskRunCount;
/* +0x14 */ uint32be taskDoneCount[PPC_CORE_COUNT];
/* +0x20 */ sint32be nextIndex[PPC_CORE_COUNT];
/* +0x2C */ sint32be endIndex[PPC_CORE_COUNT];
/* +0x38 */ MEMPTR<MEMPTR<MPTask>> taskQueue;
/* +0x3C */ uint32be taskQueueSize;
/* +0x40 */ OSSpinLock spinlock;
};
static_assert(sizeof(MPTaskQ) == 0x50);
struct MPTaskQInfo
{
/* +0x00 */ uint32be state;
/* +0x04 */ uint32be taskCount;
/* +0x08 */ uint32be taskReadyCount;
/* +0x0C */ uint32be taskRunCount;
/* +0x10 */ uint32be taskDoneCount;
};
static_assert(sizeof(MPTaskQInfo) == 0x14);
struct MPTaskInfo
{
/* +0x00 */ uint32be state;
/* +0x04 */ uint32be funcResult;
/* +0x08 */ uint32be coreIndex;
/* +0x0C */ sint64be runtime;
};
static_assert(sizeof(MPTaskQInfo) == 0x14);
void MPInitTask(MPTask* task, void* func, void* data, uint32 size);
bool MPTermTask(MPTask* task);
bool MPRunTask(MPTask* task);
bool MPGetTaskInfo(MPTask* task, MPTaskInfo* info);
void* MPGetTaskUserData(MPTask* task);
void MPSetTaskUserData(MPTask* task, void* userdata);
void MPInitTaskQ(MPTaskQ* taskq, MPTask** tasks, uint32 taskCount);
bool MPEnqueTask(MPTaskQ* taskq, MPTask* task);
bool MPTermTaskQ(MPTaskQ* taskq);
bool MPGetTaskQInfo(MPTaskQ* taskq, MPTaskQInfo* info);
bool MPStartTaskQ(MPTaskQ* taskq);
bool MPRunTasksFromTaskQ(MPTaskQ* taskq, int granularity);
bool MPStopTaskQ(MPTaskQ* taskq);
bool MPWaitTaskQ(MPTaskQ* taskq, uint32 waitState);
bool MPWaitTaskQWithTimeout(MPTaskQ* taskq, uint32 waitState, sint64 timeout);
MPTask* MPDequeTask(MPTaskQ* taskq);
uint32 MPDequeTasks(MPTaskQ* taskq, MPTask** tasks, sint32 maxTasks);
bool MPResetTaskQ(MPTaskQ* taskq);
void InitializeMP();
}

View file

@ -0,0 +1,225 @@
#include "Cafe/OS/common/OSCommon.h"
#include "coreinit_Memory.h"
#include "Cafe/HW/Latte/Core/LatteBufferCache.h"
#include "Cafe/OS/RPL/rpl.h"
#include "Cafe/GraphicPack/GraphicPack2.h"
#include "Cafe/CafeSystem.h"
namespace coreinit
{
void DCInvalidateRange(MPTR addr, uint32 size)
{
MPTR addrEnd = (addr + size + 0x1F) & ~0x1F;
addr &= ~0x1F;
//LatteBufferCache_notifyDCFlush(addr, addrEnd - addr);
}
void DCFlushRange(MPTR addr, uint32 size)
{
MPTR addrEnd = (addr + size + 0x1F) & ~0x1F;
addr &= ~0x1F;
LatteBufferCache_notifyDCFlush(addr, addrEnd - addr);
}
void DCFlushRangeNoSync(MPTR addr, uint32 size)
{
MPTR addrEnd = (addr + size + 0x1F) & ~0x1F;
addr &= ~0x1F;
LatteBufferCache_notifyDCFlush(addr, addrEnd - addr);
}
void DCStoreRange(MPTR addr, uint32 size)
{
MPTR addrEnd = (addr + size + 0x1F) & ~0x1F;
addr &= ~0x1F;
//LatteBufferCache_notifyDCFlush(addr, addrEnd - addr);
}
void DCStoreRangeNoSync(MPTR addr, uint32 size)
{
MPTR addrEnd = (addr + size + 0x1F) & ~0x1F;
addr &= ~0x1F;
LatteBufferCache_notifyDCFlush(addr, addrEnd - addr);
}
void DCZeroRange(MPTR addr, uint32 size)
{
MPTR alignedAddr = addr & ~31;
uint32 cachlineOffset = addr & 31;
uint32 blocks = (cachlineOffset + size + 31) / 32;
if (blocks > 0)
{
memset(memory_getPointerFromVirtualOffset(alignedAddr), 0x00, blocks * 32);
LatteBufferCache_notifyDCFlush(alignedAddr, blocks * 32);
}
}
bool OSIsAddressRangeDCValid(uint32 startOffset, uint32 range)
{
uint32 endOffset = startOffset + range - 1;
uint32 boundaryLow = 0xE8000000;
uint32 boundaryHigh = 0xEC000000;
if (startOffset < boundaryLow || startOffset >= boundaryHigh)
return false;
if (endOffset < boundaryLow || endOffset >= boundaryHigh)
return false;
return true;
}
void* coreinit_memset(void* dst, uint32 value, uint32 size)
{
memset(dst, value, size);
return dst;
}
void* coreinit_memcpy(MEMPTR<void> dst, MEMPTR<void> src, uint32 size)
{
if (dst.GetMPTR() == 0xFFFFFFFF)
{
// games this was seen in: The Swapper
// this may be a bug in the game. The last few bytes of the address space are writable, but wrap-around behavior of COS memcpy is unknown
cemu_assert_debug(false);
}
if (size > 0)
{
memcpy(dst.GetPtr(), src.GetPtr(), size);
// always flushes the cache!
LatteBufferCache_notifyDCFlush(dst.GetMPTR(), size);
}
return dst.GetPtr();
}
void* coreinit_memmove(MEMPTR<void> dst, void* src, uint32 size)
{
if (size > 0)
{
memmove(dst.GetPtr(), src, size);
// always flushes the cache!
LatteBufferCache_notifyDCFlush(dst.GetMPTR(), size);
}
return dst.GetPtr();
}
void* OSBlockMove(MEMPTR<void> dst, MEMPTR<void> src, uint32 size, bool flushDC)
{
if (size > 0)
{
memmove(dst.GetPtr(), src.GetPtr(), size);
if (flushDC)
LatteBufferCache_notifyDCFlush(dst.GetMPTR(), size);
}
return dst.GetPtr();
}
void* OSBlockSet(MEMPTR<void> dst, uint32 value, uint32 size)
{
memset(dst.GetPtr(), value&0xFF, size);
return dst.GetPtr();
}
MPTR OSEffectiveToPhysical(MPTR effectiveAddr)
{
MPTR physicalAddr = memory_virtualToPhysical(effectiveAddr);
return physicalAddr;
}
void OSMemoryBarrier(PPCInterpreter_t* hCPU)
{
// no-op
}
void OSGetMemBound(sint32 memType, MPTR* offsetOutput, uint32* sizeOutput)
{
MPTR memAddr = MPTR_NULL;
uint32 memSize = 0;
/*
Data taken from browser dump:
type start size
MEM1 0xF4000000 0x02000000
MEM2 0x106DE000 0x170C2000
*/
if (memType == 1)
{
// MEM1
memAddr = mmuRange_MEM1.getBase();
memSize = mmuRange_MEM1.getSize();
}
else if (memType == 2)
{
// MEM2
uint32 currentRPLAllocatorOffset = RPLLoader_GetDataAllocatorAddr();
// due to differences in our library implementations we currently allocate less memory for the OS/RPLs than on the actual hardware,
// as a result more memory is available to games
// however, some games crash due to internal overflows if there is too much memory available
// here we artificially reduce the available memory for the affected games
uint64 titleId = CafeSystem::GetForegroundTitleId();
if (
titleId == 0x0005000010132400ULL || // Lego Marvel Super Heroes (EU)
titleId == 0x0005000010132B00ULL || // Lego Marvel Super Heroes (US)
titleId == 0x0005000010194200ull || // Lego Dimensions (US)
titleId == 0x0005000010195D00ull || // Lego Dimensions (EU)
titleId == 0x00050000101A6200ull || // Lego Jurassic World (US)
titleId == 0x00050000101A5C00 || // Lego Jurassic World (EU)
titleId == 0x000500001014DE00 || // The Lego Movie Videogame (US)
titleId == 0x000500001014E000 || // The Lego Movie Videogame (EU)
titleId == 0x0005000010168D00 || // Lego The Hobbit (EU)
titleId == 0x000500001016A700 || // Lego The Hobbit (JP)
// The Hobbit US title id?
titleId == 0x00050000101DAB00 || // Lego Star Wars: The Force Awakens (US)
titleId == 0x00050000101DAA00 || // Lego Star Wars: The Force Awakens (EU)
// LEGO Batman 3: BEYOND GOTHAM
titleId == 0x000500001016A400 || // EU
titleId == 0x000500001016AD00 || // US
// Lego Marvel Avengers
titleId == 0x00050000101BE900 || // EU
titleId == 0x00050000101BEF00 || // US
// LEGO BATMAN 2: DC Super Heroes
titleId == 0x0005000010135500 || // EU
titleId == 0x0005000010135E00 // US
)
{
forceLogDebug_printf("Hack: Reduce available memory to simulate loaded RPLs");
currentRPLAllocatorOffset += (48 * 1024 * 1024); // 48MB
}
memAddr = currentRPLAllocatorOffset;
memSize = mmuRange_MEM2.getEnd() - currentRPLAllocatorOffset;
}
else
{
cemu_assert_debug(false);
}
if (offsetOutput)
*offsetOutput = _swapEndianU32(memAddr);
if (sizeOutput)
*sizeOutput = _swapEndianU32(memSize);
}
void InitializeMemory()
{
cafeExportRegister("coreinit", DCInvalidateRange, LogType::Placeholder);
cafeExportRegister("coreinit", DCFlushRange, LogType::Placeholder);
cafeExportRegister("coreinit", DCFlushRangeNoSync, LogType::Placeholder);
cafeExportRegister("coreinit", DCStoreRange, LogType::Placeholder);
cafeExportRegister("coreinit", DCStoreRangeNoSync, LogType::Placeholder);
cafeExportRegister("coreinit", DCZeroRange, LogType::Placeholder);
cafeExportRegister("coreinit", OSIsAddressRangeDCValid, LogType::Placeholder);
cafeExportRegisterFunc(coreinit_memcpy, "coreinit", "memcpy", LogType::Placeholder);
cafeExportRegisterFunc(coreinit_memset, "coreinit", "memset", LogType::Placeholder);
cafeExportRegisterFunc(coreinit_memmove, "coreinit", "memmove", LogType::Placeholder);
cafeExportRegister("coreinit", OSBlockMove, LogType::Placeholder);
cafeExportRegister("coreinit", OSBlockSet, LogType::Placeholder);
cafeExportRegister("coreinit", OSEffectiveToPhysical, LogType::Placeholder);
cafeExportRegister("coreinit", OSMemoryBarrier, LogType::Placeholder);
cafeExportRegister("coreinit", OSGetMemBound, LogType::Placeholder);
}
}

View file

@ -0,0 +1,8 @@
#pragma once
namespace coreinit
{
void InitializeMemory();
void OSGetMemBound(sint32 memType, MPTR* offsetOutput, uint32* sizeOutput);
}

View file

@ -0,0 +1,168 @@
#include "Cafe/OS/common/OSCommon.h"
#include "util/MemMapper/MemMapper.h"
#define OS_MAP_READ_ONLY (1)
#define OS_MAP_READ_WRITE (2)
namespace coreinit
{
struct OSVirtMemory
{
MPTR virtualAddress;
uint32 size;
uint32 alignment;
OSVirtMemory* next;
};
OSVirtMemory* virtualMemoryList = nullptr;
MPTR _VirtualMemoryAlloc(uint32 size, uint32 alignment)
{
uint32 currentAddress = MEMORY_MAPABLE_VIRT_AREA_OFFSET;
uint32 endAddress = MEMORY_MAPABLE_VIRT_AREA_OFFSET + MEMORY_MAPABLE_VIRT_AREA_SIZE;
uint32 pageSize = (uint32)MemMapper::GetPageSize();
while (true)
{
// calculated aligned start and end address for current region
currentAddress = (currentAddress + alignment - 1) & ~(alignment - 1);
currentAddress = (currentAddress + pageSize - 1) & ~(pageSize - 1);
uint32 currentEndAddress = currentAddress + size;
currentEndAddress = (currentEndAddress + pageSize - 1) & ~(pageSize - 1);
// check if out of available space
if (currentEndAddress >= endAddress)
{
debug_printf("coreinitVirtualMemory_alloc(): Unable to allocate memory\n");
debugBreakpoint();
return NULL;
}
// check for overlapping regions
OSVirtMemory* virtMemItr = virtualMemoryList;
bool emptySpaceFound = true;
while (virtMemItr)
{
// check for range collision
if (currentAddress < (virtMemItr->virtualAddress + virtMemItr->size) && currentEndAddress > virtMemItr->virtualAddress)
{
// regions overlap
// adjust current address and try again
currentAddress = virtMemItr->virtualAddress + virtMemItr->size;
emptySpaceFound = false;
break;
}
// next
virtMemItr = virtMemItr->next;
}
if (emptySpaceFound)
{
// add entry
OSVirtMemory* virtMemory = (OSVirtMemory*)malloc(sizeof(OSVirtMemory));
memset(virtMemory, 0x00, sizeof(OSVirtMemory));
virtMemory->virtualAddress = currentAddress;
virtMemory->size = currentEndAddress - currentAddress;
virtMemory->alignment = alignment;
virtMemory->next = virtualMemoryList;
virtualMemoryList = virtMemory;
return currentAddress;
}
}
return NULL;
}
void coreinitExport_OSGetAvailPhysAddrRange(PPCInterpreter_t* hCPU)
{
// parameters:
// r3 MPTR* areaStart
// r4 uint32 areaSize
memory_writeU32(hCPU->gpr[3], MEMORY_MAPABLE_PHYS_AREA_OFFSET);
memory_writeU32(hCPU->gpr[4], MEMORY_MAPABLE_PHYS_AREA_SIZE);
osLib_returnFromFunction(hCPU, 0);
}
void coreinitExport_OSAllocVirtAddr(PPCInterpreter_t* hCPU)
{
// parameters:
// r3 MPTR address
// r4 uint32 size
// r5 uint32 align
uint32 address = hCPU->gpr[3];
uint32 size = hCPU->gpr[4];
uint32 align = hCPU->gpr[5];
if (address != MPTR_NULL)
{
debug_printf("coreinitExport_OSAllocVirtAddr(): Unsupported address != NULL\n");
debugBreakpoint();
}
if (align == 0)
align = 1;
if (align != 0 && align != 1)
assert_dbg();
address = _VirtualMemoryAlloc(size, align);
debug_printf("coreinitExport_OSAllocVirtAddr(): Allocated virtual memory at 0x%08x\n", address);
osLib_returnFromFunction(hCPU, address);
}
void coreinitExport_OSMapMemory(PPCInterpreter_t* hCPU)
{
// parameters:
// r3 MPTR virtualAddress
// r4 MPTR physicalAddress
// r5 uint32 size
// r6 uint32 mode
MPTR virtualAddress = hCPU->gpr[3];
MPTR physicalAddress = hCPU->gpr[4];
uint32 size = hCPU->gpr[5];
uint32 mode = hCPU->gpr[6];
if (virtualAddress < MEMORY_MAPABLE_VIRT_AREA_OFFSET || virtualAddress >= (MEMORY_MAPABLE_VIRT_AREA_OFFSET + MEMORY_MAPABLE_VIRT_AREA_SIZE))
cemu_assert_suspicious();
uint8* virtualPtr = memory_getPointerFromVirtualOffset(virtualAddress);
MemMapper::PAGE_PERMISSION pageProtect = MemMapper::PAGE_PERMISSION::P_NONE;
if (mode == OS_MAP_READ_ONLY)
pageProtect = MemMapper::PAGE_PERMISSION::P_READ;
else if (mode == OS_MAP_READ_WRITE)
pageProtect = MemMapper::PAGE_PERMISSION::P_RW;
else
cemu_assert_unimplemented();
void* allocationResult = MemMapper::AllocateMemory(virtualPtr, size, pageProtect, true);
if (!allocationResult)
{
cemuLog_log(LogType::Force, "OSMapMemory failed");
osLib_returnFromFunction(hCPU, 0);
return;
}
osLib_returnFromFunction(hCPU, 1);
}
void coreinitExport_OSUnmapMemory(PPCInterpreter_t* hCPU)
{
// parameters:
// r3 MPTR virtualAddress
// r4 uint32 size
MPTR virtualAddress = hCPU->gpr[3];
uint32 size = hCPU->gpr[4];
if (virtualAddress < MEMORY_MAPABLE_VIRT_AREA_OFFSET || virtualAddress >= (MEMORY_MAPABLE_VIRT_AREA_OFFSET + MEMORY_MAPABLE_VIRT_AREA_SIZE))
cemu_assert_suspicious();
cemu_assert((size % MemMapper::GetPageSize()) == 0);
uint8* virtualPtr = memory_getPointerFromVirtualOffset(virtualAddress);
MemMapper::FreeMemory(virtualPtr, size, true);
osLib_returnFromFunction(hCPU, 1);
}
void InitializeMemoryMapping()
{
osLib_addFunction("coreinit", "OSGetAvailPhysAddrRange", coreinitExport_OSGetAvailPhysAddrRange);
osLib_addFunction("coreinit", "OSAllocVirtAddr", coreinitExport_OSAllocVirtAddr);
osLib_addFunction("coreinit", "OSMapMemory", coreinitExport_OSMapMemory);
osLib_addFunction("coreinit", "OSUnmapMemory", coreinitExport_OSUnmapMemory);
}
}

View file

@ -0,0 +1,5 @@
namespace coreinit
{
void InitializeMemoryMapping();
}

View file

@ -0,0 +1,132 @@
#include "Cafe/OS/common/OSCommon.h"
#include "Cafe/OS/libs/coreinit/coreinit_MessageQueue.h"
namespace coreinit
{
SysAllocator<OSMessageQueue> g_systemMessageQueue;
SysAllocator<OSMessage, 16> _systemMessageQueueArray;
void OSInitMessageQueueEx(OSMessageQueue* msgQueue, OSMessage* msgArray, uint32 msgCount, void* userData)
{
msgQueue->magic = 'mSgQ';
msgQueue->userData = userData;
msgQueue->msgArray = msgArray;
msgQueue->msgCount = msgCount;
msgQueue->firstIndex = 0;
msgQueue->usedCount = 0;
msgQueue->ukn08 = 0;
OSInitThreadQueueEx(&msgQueue->threadQueueReceive, msgQueue);
OSInitThreadQueueEx(&msgQueue->threadQueueSend, msgQueue);
}
void OSInitMessageQueue(OSMessageQueue* msgQueue, OSMessage* msgArray, uint32 msgCount)
{
OSInitMessageQueueEx(msgQueue, msgArray, msgCount, nullptr);
}
bool OSReceiveMessage(OSMessageQueue* msgQueue, OSMessage* msg, uint32 flags)
{
__OSLockScheduler(msgQueue);
while (msgQueue->usedCount == (uint32be)0)
{
if ((flags & OS_MESSAGE_BLOCK))
{
msgQueue->threadQueueReceive.queueAndWait(OSGetCurrentThread());
}
else
{
__OSUnlockScheduler(msgQueue);
return false;
}
}
// copy message
sint32 messageIndex = msgQueue->firstIndex;
OSMessage* readMsg = &(msgQueue->msgArray[messageIndex]);
memcpy(msg, readMsg, sizeof(OSMessage));
msgQueue->firstIndex = ((uint32)msgQueue->firstIndex + 1) % (uint32)(msgQueue->msgCount);
msgQueue->usedCount = (uint32)msgQueue->usedCount - 1;
// wake up any thread waiting to add a message
if (!msgQueue->threadQueueSend.isEmpty())
msgQueue->threadQueueSend.wakeupSingleThreadWaitQueue(true);
__OSUnlockScheduler(msgQueue);
return true;
}
bool OSPeekMessage(OSMessageQueue* msgQueue, OSMessage* msg)
{
__OSLockScheduler(msgQueue);
if ((msgQueue->usedCount == (uint32be)0))
{
__OSUnlockScheduler(msgQueue);
return false;
}
// copy message
sint32 messageIndex = msgQueue->firstIndex;
if (msg)
{
OSMessage* readMsg = &(msgQueue->msgArray[messageIndex]);
memcpy(msg, readMsg, sizeof(OSMessage));
}
__OSUnlockScheduler(msgQueue);
return true;
}
sint32 OSSendMessage(OSMessageQueue* msgQueue, OSMessage* msg, uint32 flags)
{
__OSLockScheduler();
while (msgQueue->usedCount >= msgQueue->msgCount)
{
if ((flags & OS_MESSAGE_BLOCK))
{
msgQueue->threadQueueSend.queueAndWait(OSGetCurrentThread());
}
else
{
__OSUnlockScheduler();
return 0;
}
}
// add message
if ((flags & OS_MESSAGE_HIGH_PRIORITY))
{
// decrease firstIndex
sint32 newFirstIndex = (sint32)((sint32)msgQueue->firstIndex + (sint32)msgQueue->msgCount - 1) % (sint32)msgQueue->msgCount;
msgQueue->firstIndex = newFirstIndex;
// insert message at new first index
msgQueue->usedCount = (uint32)msgQueue->usedCount + 1;
OSMessage* newMsg = &(msgQueue->msgArray[newFirstIndex]);
memcpy(newMsg, msg, sizeof(OSMessage));
}
else
{
sint32 messageIndex = (uint32)(msgQueue->firstIndex + msgQueue->usedCount) % (uint32)msgQueue->msgCount;
msgQueue->usedCount = (uint32)msgQueue->usedCount + 1;
OSMessage* newMsg = &(msgQueue->msgArray[messageIndex]);
memcpy(newMsg, msg, sizeof(OSMessage));
}
// wake up any thread waiting to read a message
if (!msgQueue->threadQueueReceive.isEmpty())
msgQueue->threadQueueReceive.wakeupSingleThreadWaitQueue(true);
__OSUnlockScheduler();
return 1;
}
OSMessageQueue* OSGetSystemMessageQueue()
{
return g_systemMessageQueue.GetPtr();
}
void InitializeMessageQueue()
{
OSInitMessageQueue(g_systemMessageQueue.GetPtr(), _systemMessageQueueArray.GetPtr(), _systemMessageQueueArray.GetCount());
cafeExportRegister("coreinit", OSInitMessageQueueEx, LogType::CoreinitThread);
cafeExportRegister("coreinit", OSInitMessageQueue, LogType::CoreinitThread);
cafeExportRegister("coreinit", OSReceiveMessage, LogType::CoreinitThread);
cafeExportRegister("coreinit", OSPeekMessage, LogType::CoreinitThread);
cafeExportRegister("coreinit", OSSendMessage, LogType::CoreinitThread);
cafeExportRegister("coreinit", OSGetSystemMessageQueue, LogType::CoreinitThread);
}
};

View file

@ -0,0 +1,40 @@
#pragma once
#include "Cafe/OS/libs/coreinit/coreinit_Thread.h"
namespace coreinit
{
struct OSMessage
{
MPTR message;
uint32 data0;
uint32 data1;
uint32 data2;
};
struct OSMessageQueue
{
/* +0x00 */ uint32be magic;
/* +0x04 */ MEMPTR<void> userData;
/* +0x08 */ uint32be ukn08;
/* +0x0C */ OSThreadQueue threadQueueSend;
/* +0x1C */ OSThreadQueue threadQueueReceive;
/* +0x2C */ MEMPTR<OSMessage> msgArray;
/* +0x30 */ uint32be msgCount;
/* +0x34 */ uint32be firstIndex;
/* +0x38 */ uint32be usedCount;
};
static_assert(sizeof(OSMessageQueue) == 0x3C);
// flags
#define OS_MESSAGE_BLOCK 1 // blocking send/receive
#define OS_MESSAGE_HIGH_PRIORITY 2 // put message in front of all queued messages
void OSInitMessageQueueEx(OSMessageQueue* msgQueue, OSMessage* msgArray, uint32 msgCount, void* userData);
void OSInitMessageQueue(OSMessageQueue* msgQueue, OSMessage* msgArray, uint32 msgCount);
bool OSReceiveMessage(OSMessageQueue* msgQueue, OSMessage* msg, uint32 flags);
bool OSPeekMessage(OSMessageQueue* msgQueue, OSMessage* msg);
sint32 OSSendMessage(OSMessageQueue* msgQueue, OSMessage* msg, uint32 flags);
void InitializeMessageQueue();
};

View file

@ -0,0 +1,357 @@
#include "Cafe/OS/common/OSCommon.h"
#include "Cafe/OS/libs/coreinit/coreinit_Misc.h"
namespace coreinit
{
/* coreinit logging and string format */
sint32 ppcSprintf(const char* formatStr, char* strOut, sint32 maxLength, PPCInterpreter_t* hCPU, sint32 initialParamIndex)
{
char tempStr[4096];
sint32 integerParamIndex = initialParamIndex;
sint32 floatParamIndex = 0;
sint32 writeIndex = 0;
while (*formatStr)
{
char c = *formatStr;
if (c == '%')
{
const char* formatStart = formatStr;
formatStr++;
if (*formatStr == '%')
{
// percent sign
if (writeIndex >= maxLength)
break;
strOut[writeIndex] = '%';
writeIndex++;
formatStr++;
continue;
}
// flags
bool flag_leftJustify = false;
bool flag_zeroPadding = false;
if (*formatStr == '-')
{
flag_leftJustify = true;
formatStr++;
}
if (*formatStr == '+')
{
// todo
formatStr++;
}
if (*formatStr == ' ')
{
// todo
formatStr++;
}
if (*formatStr == '#')
{
// todo
formatStr++;
}
if (*formatStr == '0')
{
flag_zeroPadding = true;
formatStr++;
}
// width
if (*formatStr == '*')
{
cemu_assert_debug(false);
formatStr++;
}
bool widthIsSpecified = false;
sint32 width = 0;
while (*formatStr >= '0' && *formatStr <= '9')
{
width *= 10;
width += (*formatStr - '0');
formatStr++;
widthIsSpecified = true;
}
// precision
if (*formatStr == '.')
{
formatStr++;
if (*formatStr == '*')
{
cemu_assert_debug(false);
}
while (*formatStr >= '0' && *formatStr <= '9')
{
formatStr++;
}
}
// length + specifier
char tempFormat[64];
if (*formatStr == 'X' || *formatStr == 'x' || *formatStr == 'u' || *formatStr == 'd' || *formatStr == 'p' || *formatStr == 'i' ||
(formatStr[0] == 'l' && formatStr[1] == 'd'))
{
// number
formatStr++;
strncpy(tempFormat, formatStart, std::min((std::ptrdiff_t)sizeof(tempFormat) - 1, formatStr - formatStart));
if ((formatStr - formatStart) < sizeof(tempFormat))
tempFormat[(formatStr - formatStart)] = '\0';
else
tempFormat[sizeof(tempFormat) - 1] = '\0';
sint32 tempLen = sprintf(tempStr, tempFormat, PPCInterpreter_getCallParamU32(hCPU, integerParamIndex));
integerParamIndex++;
for (sint32 i = 0; i < tempLen; i++)
{
if (writeIndex >= maxLength)
break;
strOut[writeIndex] = tempStr[i];
writeIndex++;
}
}
else if (*formatStr == 's')
{
// string
formatStr++;
strncpy(tempFormat, formatStart, std::min((std::ptrdiff_t)sizeof(tempFormat) - 1, formatStr - formatStart));
if ((formatStr - formatStart) < sizeof(tempFormat))
tempFormat[(formatStr - formatStart)] = '\0';
else
tempFormat[sizeof(tempFormat) - 1] = '\0';
MPTR strOffset = PPCInterpreter_getCallParamU32(hCPU, integerParamIndex);
sint32 tempLen = 0;
if (strOffset == MPTR_NULL)
tempLen = sprintf(tempStr, "NULL");
else
tempLen = sprintf(tempStr, tempFormat, memory_getPointerFromVirtualOffset(strOffset));
integerParamIndex++;
for (sint32 i = 0; i < tempLen; i++)
{
if (writeIndex >= maxLength)
break;
strOut[writeIndex] = tempStr[i];
writeIndex++;
}
strOut[std::min(maxLength - 1, writeIndex)] = '\0';
}
else if (*formatStr == 'f')
{
// float
formatStr++;
strncpy(tempFormat, formatStart, std::min((std::ptrdiff_t)sizeof(tempFormat) - 1, formatStr - formatStart));
if ((formatStr - formatStart) < sizeof(tempFormat))
tempFormat[(formatStr - formatStart)] = '\0';
else
tempFormat[sizeof(tempFormat) - 1] = '\0';
sint32 tempLen = sprintf(tempStr, tempFormat, (float)hCPU->fpr[1 + floatParamIndex].fp0);
floatParamIndex++;
for (sint32 i = 0; i < tempLen; i++)
{
if (writeIndex >= maxLength)
break;
strOut[writeIndex] = tempStr[i];
writeIndex++;
}
}
else if (*formatStr == 'c')
{
// character
formatStr++;
strncpy(tempFormat, formatStart, std::min((std::ptrdiff_t)sizeof(tempFormat) - 1, formatStr - formatStart));
if ((formatStr - formatStart) < sizeof(tempFormat))
tempFormat[(formatStr - formatStart)] = '\0';
else
tempFormat[sizeof(tempFormat) - 1] = '\0';
sint32 tempLen = sprintf(tempStr, tempFormat, PPCInterpreter_getCallParamU32(hCPU, integerParamIndex));
integerParamIndex++;
for (sint32 i = 0; i < tempLen; i++)
{
if (writeIndex >= maxLength)
break;
strOut[writeIndex] = tempStr[i];
writeIndex++;
}
}
else if (formatStr[0] == 'l' && formatStr[1] == 'f')
{
// double
formatStr += 2;
strncpy(tempFormat, formatStart, std::min((std::ptrdiff_t)sizeof(tempFormat) - 1, formatStr - formatStart));
if ((formatStr - formatStart) < sizeof(tempFormat))
tempFormat[(formatStr - formatStart)] = '\0';
else
tempFormat[sizeof(tempFormat) - 1] = '\0';
sint32 tempLen = sprintf(tempStr, tempFormat, (double)hCPU->fpr[1 + floatParamIndex].fp0);
floatParamIndex++;
for (sint32 i = 0; i < tempLen; i++)
{
if (writeIndex >= maxLength)
break;
strOut[writeIndex] = tempStr[i];
writeIndex++;
}
}
else if ((formatStr[0] == 'l' && formatStr[1] == 'l' && (formatStr[2] == 'x' || formatStr[2] == 'X')))
{
formatStr += 3;
// number (64bit)
strncpy(tempFormat, formatStart, std::min((std::ptrdiff_t)sizeof(tempFormat) - 1, formatStr - formatStart));
if ((formatStr - formatStart) < sizeof(tempFormat))
tempFormat[(formatStr - formatStart)] = '\0';
else
tempFormat[sizeof(tempFormat) - 1] = '\0';
if (integerParamIndex & 1)
integerParamIndex++;
sint32 tempLen = sprintf(tempStr, tempFormat, PPCInterpreter_getCallParamU64(hCPU, integerParamIndex));
integerParamIndex += 2;
for (sint32 i = 0; i < tempLen; i++)
{
if (writeIndex >= maxLength)
break;
strOut[writeIndex] = tempStr[i];
writeIndex++;
}
}
else
{
// unsupported / unknown specifier
cemu_assert_debug(false);
break;
}
}
else
{
if (writeIndex >= maxLength)
break;
strOut[writeIndex] = c;
writeIndex++;
formatStr++;
}
}
strOut[std::min(writeIndex, maxLength - 1)] = '\0';
return std::min(writeIndex, maxLength - 1);
}
sint32 __os_snprintf(char* outputStr, sint32 maxLength, const char* formatStr)
{
sint32 r = ppcSprintf(formatStr, outputStr, maxLength, ppcInterpreterCurrentInstance, 3);
return r;
}
enum class CafeLogType
{
OSCONSOLE = 0,
};
struct CafeLogBuffer
{
std::array<char, 270> lineBuffer;
size_t lineLength{};
};
CafeLogBuffer g_logBuffer_OSReport;
CafeLogBuffer& getLogBuffer(CafeLogType cafeLogType)
{
if (cafeLogType == CafeLogType::OSCONSOLE)
return g_logBuffer_OSReport;
// default to OSReport
return g_logBuffer_OSReport;
}
std::string_view getLogBufferName(CafeLogType cafeLogType)
{
if (cafeLogType == CafeLogType::OSCONSOLE)
return "OSConsole";
return "Unknown";
}
void WriteCafeConsole(CafeLogType cafeLogType, const char* msg, sint32 len)
{
// once a line is full or \n is written it will be posted to log
CafeLogBuffer& logBuffer = getLogBuffer(cafeLogType);
auto flushLine = [](CafeLogBuffer& cafeLogBuffer, std::string_view cafeLogName) -> void
{
cemuLog_log(LogType::CoreinitLogging, "[{0}] {1}", cafeLogName, std::basic_string_view(cafeLogBuffer.lineBuffer.data(), cafeLogBuffer.lineLength));
cafeLogBuffer.lineLength = 0;
};
while (len)
{
char c = *msg;
msg++;
len--;
if (c == '\r')
continue;
if (c == '\n')
{
// flush line immediately
flushLine(logBuffer, getLogBufferName(cafeLogType));
continue;
}
logBuffer.lineBuffer[logBuffer.lineLength] = c;
logBuffer.lineLength++;
if (logBuffer.lineLength >= logBuffer.lineBuffer.size())
flushLine(logBuffer, getLogBufferName(cafeLogType));
}
}
void OSReport(const char* format)
{
char buffer[1024 * 2];
sint32 len = ppcSprintf(format, buffer, sizeof(buffer), ppcInterpreterCurrentInstance, 1);
WriteCafeConsole(CafeLogType::OSCONSOLE, buffer, len);
}
void OSVReport(const char* format, MPTR vaArgs)
{
cemu_assert_unimplemented();
}
void COSWarn()
{
cemu_assert_debug(false);
}
void OSLogPrintf()
{
cemu_assert_debug(false);
}
void OSConsoleWrite(const char* strPtr, sint32 length)
{
if (length < 0)
return;
WriteCafeConsole(CafeLogType::OSCONSOLE, strPtr, length);
}
/* home button menu */
bool g_homeButtonMenuEnabled = false;
bool OSIsHomeButtonMenuEnabled()
{
return g_homeButtonMenuEnabled;
}
bool OSEnableHomeButtonMenu(bool enable)
{
g_homeButtonMenuEnabled = enable;
return true;
}
void miscInit()
{
cafeExportRegister("coreinit", __os_snprintf, LogType::Placeholder);
cafeExportRegister("coreinit", OSReport, LogType::Placeholder);
cafeExportRegister("coreinit", OSVReport, LogType::Placeholder);
cafeExportRegister("coreinit", COSWarn, LogType::Placeholder);
cafeExportRegister("coreinit", OSLogPrintf, LogType::Placeholder);
cafeExportRegister("coreinit", OSConsoleWrite, LogType::Placeholder);
g_homeButtonMenuEnabled = true; // enabled by default
// Disney Infinity 2.0 actually relies on home button menu being enabled by default. If it's false it will crash due to calling erreula->IsAppearHomeNixSign() before initializing erreula
cafeExportRegister("coreinit", OSIsHomeButtonMenuEnabled, LogType::CoreinitThread);
cafeExportRegister("coreinit", OSEnableHomeButtonMenu, LogType::CoreinitThread);
}
};

View file

@ -0,0 +1,6 @@
#pragma once
namespace coreinit
{
void miscInit();
};

View file

@ -0,0 +1,193 @@
#include "Cafe/OS/common/OSCommon.h"
#include "Cafe/OS/libs/gx2/GX2.h"
#include "Cafe/HW/Latte/Core/Latte.h"
#include "Cafe/OS/libs/coreinit/coreinit_OSScreen_font.h"
#include "Cafe/OS/libs/coreinit/coreinit_OSScreen.h"
#define OSSCREEN_TV (0)
#define OSSCREEN_DRC (1)
namespace coreinit
{
struct
{
sint32 x;
sint32 y;
sint32 pitch;
}screenSizes[2] =
{
{ 1280, 720, 1280}, // TV
{ 896, 480, 896 } // DRC (values might be incorrect)
};
void* currentScreenBasePtr[2] = { 0 };
void _OSScreen_Clear(uint32 screenIndex, uint32 color)
{
if (!currentScreenBasePtr[screenIndex])
return;
uint32* output = (uint32*)currentScreenBasePtr[screenIndex];
sint32 sizeInPixels = screenSizes[screenIndex].pitch * screenSizes[screenIndex].y;
color = _swapEndianU32(color);
for (sint32 i = 0; i < sizeInPixels; i++)
{
*output = color;
output++;
}
}
void coreinitExport_OSScreenInit(PPCInterpreter_t* hCPU)
{
// todo - init VI registers?
osLib_returnFromFunction(hCPU, 0);
}
void coreinitExport_OSScreenGetBufferSizeEx(PPCInterpreter_t* hCPU)
{
ppcDefineParamU32(screenIndex, 0);
cemu_assert(screenIndex < 2);
uint32 bufferSize = screenSizes[screenIndex].pitch * screenSizes[screenIndex].y * 4 * 2;
osLib_returnFromFunction(hCPU, bufferSize);
}
void _updateCurrentDrawScreen(sint32 screenIndex)
{
uint32 screenDataSize = screenSizes[screenIndex].pitch * screenSizes[screenIndex].y * 4;
if ((LatteGPUState.osScreen.screen[screenIndex].flipRequestCount & 1) != 0)
currentScreenBasePtr[screenIndex] = memory_getPointerFromPhysicalOffset(LatteGPUState.osScreen.screen[screenIndex].physPtr + screenDataSize);
else
currentScreenBasePtr[screenIndex] = memory_getPointerFromPhysicalOffset(LatteGPUState.osScreen.screen[screenIndex].physPtr);
}
void coreinitExport_OSScreenSetBufferEx(PPCInterpreter_t* hCPU)
{
ppcDefineParamU32(screenIndex, 0);
ppcDefineParamU32(buffer, 1);
cemu_assert(screenIndex < 2);
LatteGPUState.osScreen.screen[screenIndex].physPtr = buffer;
_updateCurrentDrawScreen(screenIndex);
osLib_returnFromFunction(hCPU, 0);
}
void coreinitExport_OSScreenEnableEx(PPCInterpreter_t* hCPU)
{
ppcDefineParamU32(screenIndex, 0);
ppcDefineParamU32(isEnabled, 1);
cemu_assert(screenIndex < 2);
LatteGPUState.osScreen.screen[screenIndex].isEnabled = isEnabled != 0;
osLib_returnFromFunction(hCPU, 0);
}
void coreinitExport_OSScreenClearBufferEx(PPCInterpreter_t* hCPU)
{
ppcDefineParamU32(screenIndex, 0);
ppcDefineParamU32(color, 1);
cemu_assert(screenIndex < 2);
_OSScreen_Clear(screenIndex, color);
osLib_returnFromFunction(hCPU, 0);
}
void coreinitExport_OSScreenFlipBuffersEx(PPCInterpreter_t* hCPU)
{
ppcDefineParamU32(screenIndex, 0);
cemu_assert(screenIndex < 2);
forceLogDebug_printf("OSScreenFlipBuffersEx %d", screenIndex);
LatteGPUState.osScreen.screen[screenIndex].flipRequestCount++;
_updateCurrentDrawScreen(screenIndex);
osLib_returnFromFunction(hCPU, 0);
}
void coreinitExport_OSScreenPutPixelEx(PPCInterpreter_t* hCPU)
{
ppcDefineParamU32(screenIndex, 0);
ppcDefineParamS32(x, 1);
ppcDefineParamS32(y, 2);
ppcDefineParamU32(color, 3);
if (screenIndex >= 2)
{
osLib_returnFromFunction(hCPU, 0);
return;
}
if (x >= 0 && x < screenSizes[screenIndex].x && y >= 0 && y < screenSizes[screenIndex].y)
{
*(uint32*)((uint8*)currentScreenBasePtr[screenIndex] + (x + y * screenSizes[screenIndex].pitch) * 4) = _swapEndianS32(color);
}
osLib_returnFromFunction(hCPU, 0);
}
const char* osScreenCharset = "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~";
sint32 _getOSScreenFontCharIndex(char c)
{
const char* charset = osScreenCharset;
while (*charset)
{
if (*charset == c)
{
return (sint32)(charset - osScreenCharset);
}
charset++;
}
return -1;
}
void coreinitExport_OSScreenPutFontEx(PPCInterpreter_t* hCPU)
{
ppcDefineParamU32(screenIndex, 0);
ppcDefineParamS32(x, 1);
ppcDefineParamS32(y, 2);
ppcDefineParamStr(str, 3);
// characters are:
// 16 x 32 (including the margin)
// with a margin of 4 x 8
if (y < 0)
{
debug_printf("OSScreenPutFontEx: y has invalid value\n");
osLib_returnFromFunction(hCPU, 0);
return;
}
sint32 px = x * 16;
sint32 py = y * 24;
while (*str)
{
sint32 charIndex = _getOSScreenFontCharIndex(*str);
if (charIndex >= 0)
{
const uint8* charBitmap = osscreenBitmapFont + charIndex * 50;
for (sint32 fy = 0; fy < 25; fy++)
{
for (sint32 fx = 0; fx < 14; fx++)
{
if (((charBitmap[(fx / 8) + (fy) * 2] >> (7 - (fx & 7))) & 1) == 0)
continue;
*(uint32*)((uint8*)currentScreenBasePtr[screenIndex] + ((px + fx) + (py + fy) * screenSizes[screenIndex].pitch) * 4) = 0xFFFFFFFF;
}
}
}
px += 16;
str++;
}
osLib_returnFromFunction(hCPU, 0);
}
void InitializeOSScreen()
{
osLib_addFunction("coreinit", "OSScreenInit", coreinitExport_OSScreenInit);
osLib_addFunction("coreinit", "OSScreenGetBufferSizeEx", coreinitExport_OSScreenGetBufferSizeEx);
osLib_addFunction("coreinit", "OSScreenSetBufferEx", coreinitExport_OSScreenSetBufferEx);
osLib_addFunction("coreinit", "OSScreenEnableEx", coreinitExport_OSScreenEnableEx);
osLib_addFunction("coreinit", "OSScreenClearBufferEx", coreinitExport_OSScreenClearBufferEx);
osLib_addFunction("coreinit", "OSScreenFlipBuffersEx", coreinitExport_OSScreenFlipBuffersEx);
osLib_addFunction("coreinit", "OSScreenPutPixelEx", coreinitExport_OSScreenPutPixelEx);
osLib_addFunction("coreinit", "OSScreenPutFontEx", coreinitExport_OSScreenPutFontEx);
}
}

View file

@ -0,0 +1,6 @@
#pragma once
namespace coreinit
{
void InitializeOSScreen();
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,34 @@
#include "Cafe/OS/common/OSCommon.h"
#include "Cafe/OS/common/OSCommon.h"
#include "coreinit_OverlayArena.h"
namespace coreinit
{
struct
{
bool isEnabled;
}g_coreinitOverlayArena = { 0 };
uint32 OSIsEnabledOverlayArena()
{
return g_coreinitOverlayArena.isEnabled ? 1 : 0;
}
void OSEnableOverlayArena(uint32 uknParam, uint32be* areaOffset, uint32be* areaSize)
{
if (g_coreinitOverlayArena.isEnabled == false)
{
memory_enableOverlayArena();
g_coreinitOverlayArena.isEnabled = true;
}
*areaOffset = MEMORY_OVERLAY_AREA_OFFSET;
*areaSize = MEMORY_OVERLAY_AREA_SIZE;
}
void InitializeOverlayArena()
{
cafeExportRegister("coreinit", OSIsEnabledOverlayArena, LogType::Placeholder);
cafeExportRegister("coreinit", OSEnableOverlayArena, LogType::Placeholder);
g_coreinitOverlayArena.isEnabled = false;
}
}

View file

@ -0,0 +1,4 @@
namespace coreinit
{
void InitializeOverlayArena();
};

View file

@ -0,0 +1,128 @@
#include "Cafe/OS/common/OSCommon.h"
#include "coreinit_Scheduler.h"
thread_local sint32 s_schedulerLockCount = 0;
#if BOOST_OS_WINDOWS
#include <synchapi.h>
CRITICAL_SECTION s_csSchedulerLock;
#else
#include <pthread.h>
pthread_mutex_t s_ptmSchedulerLock;
#endif
void __OSLockScheduler(void* obj)
{
#if BOOST_OS_WINDOWS
EnterCriticalSection(&s_csSchedulerLock);
#else
pthread_mutex_lock(&s_ptmSchedulerLock);
#endif
s_schedulerLockCount++;
cemu_assert_debug(s_schedulerLockCount <= 1); // >= 2 should not happen. Scheduler lock does not allow recursion
}
bool __OSHasSchedulerLock()
{
return s_schedulerLockCount > 0;
}
bool __OSTryLockScheduler(void* obj)
{
bool r;
#if BOOST_OS_WINDOWS
r = TryEnterCriticalSection(&s_csSchedulerLock);
#else
r = pthread_mutex_trylock(&s_ptmSchedulerLock) == 0;
#endif
if (r)
{
s_schedulerLockCount++;
return true;
}
return false;
}
void __OSUnlockScheduler(void* obj)
{
s_schedulerLockCount--;
cemu_assert_debug(s_schedulerLockCount >= 0);
#if BOOST_OS_WINDOWS
LeaveCriticalSection(&s_csSchedulerLock);
#else
pthread_mutex_unlock(&s_ptmSchedulerLock);
#endif
}
namespace coreinit
{
uint32 OSIsInterruptEnabled()
{
PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance();
if (hCPU == nullptr)
return 0;
return hCPU->coreInterruptMask;
}
// disables interrupts and scheduling
uint32 OSDisableInterrupts()
{
// todo - rename SchedulerLock.cpp/h to Scheduler.cpp and move this there?
PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance();
if (hCPU == nullptr)
return 0;
uint32 prevInterruptMask = hCPU->coreInterruptMask;
if (hCPU->coreInterruptMask != 0)
{
// we have no efficient method to turn off scheduling completely, so instead we just increase the remaining cycles
if (hCPU->remainingCycles >= 0x40000000)
{
forceLogDebug_printf("OSDisableInterrupts(): Warning - Interrupts already disabled? remCycles %08x LR %08x", hCPU->remainingCycles, hCPU->spr.LR);
}
hCPU->remainingCycles += 0x40000000;
}
hCPU->coreInterruptMask = 0;
return prevInterruptMask;
}
uint32 OSRestoreInterrupts(uint32 interruptMask)
{
PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance();
if (hCPU == nullptr)
return 0;
uint32 prevInterruptMask = hCPU->coreInterruptMask;
if (hCPU->coreInterruptMask == 0 && interruptMask != 0)
{
hCPU->remainingCycles -= 0x40000000;
}
hCPU->coreInterruptMask = interruptMask;
return prevInterruptMask;
}
uint32 OSEnableInterrupts()
{
PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance();
uint32 prevInterruptMask = hCPU->coreInterruptMask;
OSRestoreInterrupts(1);
return prevInterruptMask;
}
void InitializeSchedulerLock()
{
#if BOOST_OS_WINDOWS
InitializeCriticalSection(&s_csSchedulerLock);
#else
pthread_mutexattr_t ma;
pthread_mutexattr_init(&ma);
pthread_mutexattr_settype(&ma, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&s_ptmSchedulerLock, &ma);
#endif
cafeExportRegister("coreinit", __OSLockScheduler, LogType::Placeholder);
cafeExportRegister("coreinit", __OSUnlockScheduler, LogType::Placeholder);
cafeExportRegister("coreinit", OSDisableInterrupts, LogType::CoreinitThread);
cafeExportRegister("coreinit", OSEnableInterrupts, LogType::CoreinitThread);
cafeExportRegister("coreinit", OSRestoreInterrupts, LogType::CoreinitThread);
}
};

View file

@ -0,0 +1,16 @@
#pragma once
void __OSLockScheduler(void* obj = nullptr);
bool __OSHasSchedulerLock();
bool __OSTryLockScheduler(void* obj = nullptr);
void __OSUnlockScheduler(void* obj = nullptr);
namespace coreinit
{
uint32 OSIsInterruptEnabled();
uint32 OSDisableInterrupts();
uint32 OSRestoreInterrupts(uint32 interruptMask);
uint32 OSEnableInterrupts();
void InitializeSchedulerLock();
}

View file

@ -0,0 +1,217 @@
#include "Cafe/OS/common/OSCommon.h"
#include "Cafe/OS/libs/coreinit/coreinit_Thread.h"
#include "Cafe/OS/libs/coreinit/coreinit_Spinlock.h"
#include "Cafe/OS/libs/coreinit/coreinit_Time.h"
#include "Cafe/OS/libs/coreinit/coreinit.h"
namespace coreinit
{
void __OSBoostThread(OSThread_t* thread)
{
__OSLockScheduler();
thread->stateFlags |= 0x20000;
thread->context.boostCount += 1;
__OSUpdateThreadEffectivePriority(thread); // sets thread->effectivePriority to zero since boostCount != 0
__OSUnlockScheduler();
}
void __OSDeboostThread(OSThread_t* thread)
{
__OSLockScheduler();
cemu_assert_debug(thread->context.boostCount != 0);
thread->context.boostCount -= 1;
if (thread->context.boostCount == 0)
{
thread->stateFlags &= ~0x20000;
__OSUpdateThreadEffectivePriority(thread);
// todo - reschedule if lower priority than other threads on current core?
}
__OSUnlockScheduler();
}
void OSInitSpinLock(OSSpinLock* spinlock)
{
spinlock->userData = spinlock;
spinlock->ownerThread = nullptr;
spinlock->count = 0;
spinlock->interruptMask = 1;
}
bool OSAcquireSpinLock(OSSpinLock* spinlock)
{
OSThread_t* currentThread = OSGetCurrentThread();
if (spinlock->ownerThread == currentThread)
{
spinlock->count += 1;
return true;
}
else
{
// loop until lock acquired
while (!spinlock->ownerThread.atomic_compare_exchange(nullptr, currentThread))
{
OSYieldThread();
}
}
__OSBoostThread(currentThread);
return true;
}
bool OSTryAcquireSpinLock(OSSpinLock* spinlock)
{
OSThread_t* currentThread = OSGetCurrentThread();
if (spinlock->ownerThread == currentThread)
{
spinlock->count += 1;
return true;
}
// try acquire once
if (!spinlock->ownerThread.atomic_compare_exchange(nullptr, currentThread))
return false;
__OSBoostThread(currentThread);
return true;
}
bool OSTryAcquireSpinLockWithTimeout(OSSpinLock* spinlock, uint64 timeout)
{
// used by CoD: Ghosts
cemu_assert_debug((timeout >> 63) == 0); // negative?
OSThread_t* currentThread = OSGetCurrentThread();
if (spinlock->ownerThread == currentThread)
{
spinlock->count += 1;
return true;
}
else
{
// loop until lock acquired or timeout occurred
uint64 timeoutValue = coreinit_getTimerTick() + coreinit::EspressoTime::ConvertNsToTimerTicks(timeout);
while (!spinlock->ownerThread.atomic_compare_exchange(nullptr, currentThread))
{
OSYieldThread();
if (coreinit_getTimerTick() >= timeoutValue)
{
return false;
}
}
}
__OSBoostThread(currentThread);
return true;
}
bool OSReleaseSpinLock(OSSpinLock* spinlock)
{
OSThread_t* currentThread = OSGetCurrentThread();
cemu_assert_debug(spinlock->ownerThread == currentThread);
if (spinlock->count != 0)
{
spinlock->count -= 1;
return true;
}
// release spinlock
while (!spinlock->ownerThread.atomic_compare_exchange(currentThread, nullptr));
__OSDeboostThread(currentThread);
return true;
}
bool OSUninterruptibleSpinLock_Acquire(OSSpinLock* spinlock)
{
// frequently used by VC DS
OSThread_t* currentThread = OSGetCurrentThread();
cemu_assert_debug(currentThread != nullptr);
if (spinlock->ownerThread == currentThread)
{
spinlock->count += 1;
return true;
}
else
{
// loop until lock acquired
while (!spinlock->ownerThread.atomic_compare_exchange(nullptr, currentThread))
{
OSYieldThread();
}
}
__OSBoostThread(currentThread);
spinlock->interruptMask = OSDisableInterrupts();
cemu_assert_debug(spinlock->ownerThread == currentThread);
return true;
}
bool OSUninterruptibleSpinLock_TryAcquire(OSSpinLock* spinlock)
{
OSThread_t* currentThread = OSGetCurrentThread();
if (spinlock->ownerThread == currentThread)
{
spinlock->count += 1;
return true;
}
// try acquire once
if (!spinlock->ownerThread.atomic_compare_exchange(nullptr, currentThread))
return false;
__OSBoostThread(currentThread);
spinlock->interruptMask = OSDisableInterrupts();
return true;
}
bool OSUninterruptibleSpinLock_TryAcquireWithTimeout(OSSpinLock* spinlock, uint64 timeout)
{
cemu_assert_debug((timeout >> 63) == 0); // negative?
OSThread_t* currentThread = OSGetCurrentThread();
if (spinlock->ownerThread == currentThread)
{
spinlock->count += 1;
return true;
}
else
{
// loop until lock acquired or timeout occurred
uint64 timeoutValue = coreinit_getTimerTick() + coreinit::EspressoTime::ConvertNsToTimerTicks(timeout);
while (!spinlock->ownerThread.atomic_compare_exchange(nullptr, currentThread))
{
OSYieldThread();
if (coreinit_getTimerTick() >= timeoutValue)
{
return false;
}
}
}
__OSBoostThread(currentThread);
spinlock->interruptMask = OSDisableInterrupts();
return true;
}
bool OSUninterruptibleSpinLock_Release(OSSpinLock* spinlock)
{
OSThread_t* currentThread = OSGetCurrentThread();
cemu_assert_debug(spinlock->ownerThread == currentThread);
if (spinlock->count != 0)
{
spinlock->count -= 1;
return true;
}
// release spinlock
OSRestoreInterrupts(spinlock->interruptMask);
spinlock->interruptMask = 1;
while (!spinlock->ownerThread.atomic_compare_exchange(currentThread, nullptr));
__OSDeboostThread(currentThread);
return true;
}
void InitializeSpinlock()
{
cafeExportRegister("coreinit", OSInitSpinLock, LogType::Placeholder);
cafeExportRegister("coreinit", OSAcquireSpinLock, LogType::Placeholder);
cafeExportRegister("coreinit", OSTryAcquireSpinLock, LogType::Placeholder);
cafeExportRegister("coreinit", OSTryAcquireSpinLockWithTimeout, LogType::Placeholder);
cafeExportRegister("coreinit", OSReleaseSpinLock, LogType::Placeholder);
cafeExportRegister("coreinit", OSUninterruptibleSpinLock_Acquire, LogType::Placeholder);
cafeExportRegister("coreinit", OSUninterruptibleSpinLock_TryAcquire, LogType::Placeholder);
cafeExportRegister("coreinit", OSUninterruptibleSpinLock_TryAcquireWithTimeout, LogType::Placeholder);
cafeExportRegister("coreinit", OSUninterruptibleSpinLock_Release, LogType::Placeholder);
}
#pragma endregion
}

View file

@ -0,0 +1,28 @@
#pragma once
namespace coreinit
{
struct OSSpinLock
{
/* +0x00 */ MEMPTR<struct OSThread_t> ownerThread;
/* +0x04 */ MEMPTR<void> userData;
/* +0x08 */ uint32be count;
/* +0x0C */ uint32be interruptMask;
};
static_assert(sizeof(OSSpinLock) == 0x10);
void InitializeSpinlock();
void OSInitSpinLock(OSSpinLock* spinlock);
bool OSAcquireSpinLock(OSSpinLock* spinlock);
bool OSTryAcquireSpinLock(OSSpinLock* spinlock);
bool OSTryAcquireSpinLockWithTimeout(OSSpinLock* spinlock, uint64 timeout);
bool OSReleaseSpinLock(OSSpinLock* spinlock);
bool OSUninterruptibleSpinLock_Acquire(OSSpinLock* spinlock);
bool OSUninterruptibleSpinLock_TryAcquire(OSSpinLock* spinlock);
bool OSUninterruptibleSpinLock_TryAcquireWithTimeout(OSSpinLock* spinlock, uint64 timeout);
bool OSUninterruptibleSpinLock_Release(OSSpinLock* spinlock);
}

View file

@ -0,0 +1,669 @@
#include "Cafe/OS/common/OSCommon.h"
#include "Cafe/OS/libs/coreinit/coreinit.h"
#include "Cafe/OS/libs/coreinit/coreinit_Thread.h"
#include "Cafe/OS/libs/coreinit/coreinit_Alarm.h"
#include "Cafe/OS/libs/coreinit/coreinit_Time.h"
#include "util/helpers/fspinlock.h"
namespace coreinit
{
/************* OSEvent ************/
void OSInitEvent(OSEvent* event, OSEvent::EVENT_STATE initialState, OSEvent::EVENT_MODE mode)
{
event->magic = 'eVnT';
cemu_assert_debug(event->magic == 0x65566e54);
event->userData = nullptr;
event->ukn08 = 0;
event->state = initialState;
event->mode = mode;
OSInitThreadQueueEx(&event->threadQueue, event);
}
void OSInitEventEx(OSEvent* event, OSEvent::EVENT_STATE initialState, OSEvent::EVENT_MODE mode, void* userData)
{
OSInitEvent(event, initialState, mode);
event->userData = userData;
}
void OSResetEvent(OSEvent* event)
{
__OSLockScheduler();
if (event->state == OSEvent::EVENT_STATE::STATE_SIGNALED)
event->state = OSEvent::EVENT_STATE::STATE_NOT_SIGNALED;
__OSUnlockScheduler();
}
void OSWaitEventInternal(OSEvent* event)
{
if (event->state == OSEvent::EVENT_STATE::STATE_SIGNALED)
{
if (event->mode == OSEvent::EVENT_MODE::MODE_AUTO)
event->state = OSEvent::EVENT_STATE::STATE_NOT_SIGNALED;
}
else
{
// enter wait queue
event->threadQueue.queueAndWait(OSGetCurrentThread());
}
}
void OSWaitEvent(OSEvent* event)
{
__OSLockScheduler();
OSWaitEventInternal(event);
__OSUnlockScheduler();
}
struct WaitEventWithTimeoutData
{
OSThread_t* thread;
OSThreadQueue* threadQueue;
std::atomic_bool hasTimeout;
};
void _OSWaitEventWithTimeoutHandler(uint64 currentTick, void* context)
{
cemu_assert_debug(__OSHasSchedulerLock());
WaitEventWithTimeoutData* data = (WaitEventWithTimeoutData*)context;
if (data->thread->state == OSThread_t::THREAD_STATE::STATE_WAITING)
{
data->hasTimeout = true;
data->threadQueue->cancelWait(data->thread);
}
}
uint64 coreinit_getOSTime();
bool OSWaitEventWithTimeout(OSEvent* event, uint64 timeout)
{
__OSLockScheduler();
if (event->state == OSEvent::EVENT_STATE::STATE_SIGNALED)
{
if (event->mode == OSEvent::EVENT_MODE::MODE_AUTO)
event->state = OSEvent::EVENT_STATE::STATE_NOT_SIGNALED;
}
else
{
if (timeout == 0)
{
// fail immediately
__OSUnlockScheduler();
return false;
}
// wait and set timeout
// workaround for a bad implementation in some Unity games (like Qube Directors Cut, see FEventWiiU::Wait)
// where the the return value of OSWaitEventWithTimeout is ignored and instead the game measures the elapsed time to determine if a timeout occurred
timeout = timeout * 98ULL / 100ULL; // 98% (we want the function to return slightly before the actual timeout)
WaitEventWithTimeoutData data;
data.thread = OSGetCurrentThread();
data.threadQueue = &event->threadQueue;
data.hasTimeout = false;
auto hostAlarm = coreinit::OSHostAlarmCreate(coreinit::coreinit_getOSTime() + coreinit::EspressoTime::ConvertNsToTimerTicks(timeout), 0, _OSWaitEventWithTimeoutHandler, &data);
event->threadQueue.queueAndWait(OSGetCurrentThread());
coreinit::OSHostAlarmDestroy(hostAlarm);
if (data.hasTimeout)
{
__OSUnlockScheduler();
return false;
}
}
__OSUnlockScheduler();
return true;
}
void OSSignalEventInternal(OSEvent* event)
{
cemu_assert_debug(__OSHasSchedulerLock());
if (event->state == OSEvent::EVENT_STATE::STATE_SIGNALED)
{
return;
}
if (event->mode == OSEvent::EVENT_MODE::MODE_AUTO)
{
// in auto mode wake up one thread or if there is none then set signaled
if (event->threadQueue.isEmpty())
event->state = OSEvent::EVENT_STATE::STATE_SIGNALED;
else
event->threadQueue.wakeupSingleThreadWaitQueue(true);
}
else
{
// in manual mode wake up all threads and set to signaled
event->state = OSEvent::EVENT_STATE::STATE_SIGNALED;
event->threadQueue.wakeupEntireWaitQueue(true);
}
}
void OSSignalEvent(OSEvent* event)
{
__OSLockScheduler();
OSSignalEventInternal(event);
__OSUnlockScheduler();
}
void OSSignalEventAllInternal(OSEvent* event)
{
if (event->state == OSEvent::EVENT_STATE::STATE_SIGNALED)
{
return;
}
if (event->mode == OSEvent::EVENT_MODE::MODE_AUTO)
{
// in auto mode wake up one thread or if there is none then set signaled
if (event->threadQueue.isEmpty())
event->state = OSEvent::EVENT_STATE::STATE_SIGNALED;
else
event->threadQueue.wakeupEntireWaitQueue(true);
}
else
{
// in manual mode wake up all threads and set to signaled
event->state = OSEvent::EVENT_STATE::STATE_SIGNALED;
event->threadQueue.wakeupEntireWaitQueue(true);
}
}
void OSSignalEventAll(OSEvent* event)
{
__OSLockScheduler();
OSSignalEventAllInternal(event);
__OSUnlockScheduler();
}
/************* OSRendezvous ************/
SysAllocator<OSEvent> g_rendezvousEvent;
void OSInitRendezvous(OSRendezvous* rendezvous)
{
__OSLockScheduler();
rendezvous->userData = rendezvous;
for (sint32 i = 0; i < PPC_CORE_COUNT; i++)
rendezvous->coreHit[i] = 0;
__OSUnlockScheduler();
}
bool OSWaitRendezvous(OSRendezvous* rendezvous, uint32 coreMask)
{
__OSLockScheduler();
rendezvous->coreHit[OSGetCoreId()] = 1;
OSSignalEventAllInternal(g_rendezvousEvent.GetPtr());
while (true)
{
bool metAll = true;
for(sint32 i=0; i<PPC_CORE_COUNT; i++)
{
if( (coreMask & (1<<i)) == 0 )
continue; // core not required by core mask
if (rendezvous->coreHit[i] == 0)
{
metAll = false;
break;
}
}
if (metAll)
break;
OSWaitEventInternal(g_rendezvousEvent.GetPtr());
}
__OSUnlockScheduler();
return true;
}
/************* OSMutex ************/
void OSInitMutexEx(OSMutex* mutex, void* userData)
{
mutex->magic = 'mUtX';
mutex->userData = userData;
mutex->ukn08 = 0;
mutex->owner = nullptr;
mutex->lockCount = 0;
OSInitThreadQueueEx(&mutex->threadQueue, mutex);
}
void OSInitMutex(OSMutex* mutex)
{
OSInitMutexEx(mutex, nullptr);
}
void OSLockMutexInternal(OSMutex* mutex)
{
OSThread_t* currentThread = OSGetCurrentThread();
int_fast32_t failedAttempts = 0;
while (true)
{
if (mutex->owner == nullptr)
{
// acquire lock
mutex->owner = currentThread;
cemu_assert_debug(mutex->lockCount == 0);
mutex->lockCount = 1;
// cemu_assert_debug(mutex->next == nullptr && mutex->prev == nullptr); -> not zero initialized
currentThread->mutexQueue.addMutex(mutex);
break;
}
else if (mutex->owner == currentThread)
{
mutex->lockCount = mutex->lockCount + 1;
break;
}
else
{
if (failedAttempts >= 0x800)
cemuLog_force("Detected long-term contested OSLockMutex");
currentThread->waitingForMutex = mutex;
mutex->threadQueue.queueAndWait(currentThread);
currentThread->waitingForMutex = nullptr;
failedAttempts++;
}
}
}
void OSLockMutex(OSMutex* mutex)
{
__OSLockScheduler();
OSTestThreadCancelInternal();
OSLockMutexInternal(mutex);
__OSUnlockScheduler();
}
bool OSTryLockMutex(OSMutex* mutex)
{
OSThread_t* currentThread = OSGetCurrentThread();
__OSLockScheduler();
OSTestThreadCancelInternal();
if (mutex->owner == nullptr)
{
// acquire lock
mutex->owner = currentThread;
cemu_assert_debug(mutex->lockCount == 0);
mutex->lockCount = 1;
// cemu_assert_debug(mutex->next == nullptr && mutex->prev == nullptr); -> not zero initialized
currentThread->mutexQueue.addMutex(mutex);
// currentThread->cancelState = currentThread->cancelState | 0x10000;
}
else if (mutex->owner == currentThread)
{
mutex->lockCount = mutex->lockCount + 1;
}
else
{
__OSUnlockScheduler();
return false;
}
__OSUnlockScheduler();
return true;
}
void OSUnlockMutexInternal(OSMutex* mutex)
{
OSThread_t* currentThread = OSGetCurrentThread();
cemu_assert_debug(mutex->owner == currentThread);
cemu_assert_debug(mutex->lockCount > 0);
mutex->lockCount = mutex->lockCount - 1;
if (mutex->lockCount == 0)
{
currentThread->mutexQueue.removeMutex(mutex);
mutex->owner = nullptr;
if (!mutex->threadQueue.isEmpty())
mutex->threadQueue.wakeupSingleThreadWaitQueue(true);
}
// currentThread->cancelState = currentThread->cancelState & ~0x10000;
}
void OSUnlockMutex(OSMutex* mutex)
{
__OSLockScheduler();
OSUnlockMutexInternal(mutex);
__OSUnlockScheduler();
}
/************* OSCond ************/
void OSInitCond(OSCond* cond)
{
cond->magic = 0x634e6456;
cond->userData = nullptr;
cond->ukn08 = 0;
OSInitThreadQueueEx(&cond->threadQueue, cond);
}
void OSInitCondEx(OSCond* cond, void* userData)
{
OSInitCond(cond);
cond->userData = userData;
}
void OSSignalCond(OSCond* cond)
{
OSWakeupThread(&cond->threadQueue);
}
void OSWaitCond(OSCond* cond, OSMutex* mutex)
{
// seen in Bayonetta 2
// releases the mutex while waiting for the condition to be signaled
__OSLockScheduler();
OSThread_t* currentThread = OSGetCurrentThread();
cemu_assert_debug(mutex->owner == currentThread);
sint32 prevLockCount = mutex->lockCount;
// unlock mutex
mutex->lockCount = 0;
currentThread->mutexQueue.removeMutex(mutex);
mutex->owner = nullptr;
if (!mutex->threadQueue.isEmpty())
mutex->threadQueue.wakeupEntireWaitQueue(false);
// wait on condition
cond->threadQueue.queueAndWait(currentThread);
// reacquire mutex
OSLockMutexInternal(mutex);
mutex->lockCount = prevLockCount;
__OSUnlockScheduler();
}
/************* OSSemaphore ************/
void OSInitSemaphoreEx(OSSemaphore* semaphore, sint32 initialCount, void* userData)
{
__OSLockScheduler();
semaphore->magic = 0x73506852;
semaphore->userData = userData;
semaphore->ukn08 = 0;
semaphore->count = initialCount;
OSInitThreadQueueEx(&semaphore->threadQueue, semaphore);
__OSUnlockScheduler();
}
void OSInitSemaphore(OSSemaphore* semaphore, sint32 initialCount)
{
OSInitSemaphoreEx(semaphore, initialCount, nullptr);
}
sint32 OSWaitSemaphoreInternal(OSSemaphore* semaphore)
{
cemu_assert_debug(__OSHasSchedulerLock());
while (true)
{
sint32 prevCount = semaphore->count;
if (prevCount > 0)
{
semaphore->count = prevCount - 1;
return prevCount;
}
semaphore->threadQueue.queueAndWait(OSGetCurrentThread());
}
}
sint32 OSWaitSemaphore(OSSemaphore* semaphore)
{
__OSLockScheduler();
sint32 r = OSWaitSemaphoreInternal(semaphore);
__OSUnlockScheduler();
return r;
}
sint32 OSTryWaitSemaphore(OSSemaphore* semaphore)
{
__OSLockScheduler();
sint32 prevCount = semaphore->count;
if (prevCount > 0)
{
semaphore->count = prevCount - 1;
}
__OSUnlockScheduler();
return prevCount;
}
sint32 OSSignalSemaphoreInternal(OSSemaphore* semaphore, bool reschedule)
{
cemu_assert_debug(__OSHasSchedulerLock());
sint32 prevCount = semaphore->count;
semaphore->count = prevCount + 1;
semaphore->threadQueue.wakeupEntireWaitQueue(reschedule);
return prevCount;
}
sint32 OSSignalSemaphore(OSSemaphore* semaphore)
{
__OSLockScheduler();
sint32 r = OSSignalSemaphoreInternal(semaphore, true);
__OSUnlockScheduler();
return r;
}
sint32 OSGetSemaphoreCount(OSSemaphore* semaphore)
{
// seen in Assassin's Creed 4
__OSLockScheduler();
sint32 currentCount = semaphore->count;
__OSUnlockScheduler();
return currentCount;
}
/************* OSFastMutex ************/
void OSFastMutex_Init(OSFastMutex* fastMutex, void* userData)
{
fastMutex->magic = 0x664d7458;
fastMutex->userData = userData;
fastMutex->owner = nullptr;
fastMutex->lockCount = 0;
fastMutex->contendedState = 0;
fastMutex->threadQueueSmall.head = nullptr;
fastMutex->threadQueueSmall.tail = nullptr;
fastMutex->ownedLink.next = nullptr;
fastMutex->ownedLink.prev = nullptr;
fastMutex->contendedLink.next = nullptr;
fastMutex->contendedLink.prev = nullptr;
}
FSpinlock g_fastMutexSpinlock;
void _OSFastMutex_AcquireContention(OSFastMutex* fastMutex)
{
g_fastMutexSpinlock.acquire();
}
void _OSFastMutex_ReleaseContention(OSFastMutex* fastMutex)
{
g_fastMutexSpinlock.release();
}
void OSFastMutex_LockInternal(OSFastMutex* fastMutex)
{
cemu_assert_debug(!__OSHasSchedulerLock());
OSThread_t* currentThread = OSGetCurrentThread();
_OSFastMutex_AcquireContention(fastMutex);
while (true)
{
if (fastMutex->owner.atomic_compare_exchange(nullptr, currentThread))//(fastMutex->owner == nullptr)
{
// acquire lock
cemu_assert_debug(fastMutex->owner == currentThread);
cemu_assert_debug(fastMutex->lockCount == 0);
fastMutex->lockCount = 1;
// todo - add to thread owned fast mutex queue
break;
}
else if (fastMutex->owner == currentThread)
{
fastMutex->lockCount = fastMutex->lockCount + 1;
break;
}
else
{
currentThread->waitingForFastMutex = fastMutex;
__OSLockScheduler();
fastMutex->threadQueueSmall.queueOnly(currentThread);
_OSFastMutex_ReleaseContention(fastMutex);
PPCCore_switchToSchedulerWithLock();
currentThread->waitingForFastMutex = nullptr;
__OSUnlockScheduler();
_OSFastMutex_AcquireContention(fastMutex);
continue;
}
}
_OSFastMutex_ReleaseContention(fastMutex);
}
void OSFastMutex_Lock(OSFastMutex* fastMutex)
{
OSFastMutex_LockInternal(fastMutex);
}
bool OSFastMutex_TryLock(OSFastMutex* fastMutex)
{
OSThread_t* currentThread = OSGetCurrentThread();
_OSFastMutex_AcquireContention(fastMutex);
if (fastMutex->owner.atomic_compare_exchange(nullptr, currentThread))
{
// acquire lock
cemu_assert_debug(fastMutex->owner == currentThread);
cemu_assert_debug(fastMutex->lockCount == 0);
fastMutex->lockCount = 1;
// todo - add to thread owned fast mutex queue
}
else if (fastMutex->owner == currentThread)
{
fastMutex->lockCount = fastMutex->lockCount + 1;
}
else
{
_OSFastMutex_ReleaseContention(fastMutex);
return false;
}
_OSFastMutex_ReleaseContention(fastMutex);
return true;
}
void OSFastMutex_UnlockInternal(OSFastMutex* fastMutex)
{
cemu_assert_debug(!__OSHasSchedulerLock());
OSThread_t* currentThread = OSGetCurrentThread();
_OSFastMutex_AcquireContention(fastMutex);
if (fastMutex->owner != currentThread)
{
// seen in Paper Mario Color Splash
//forceLog_printf("OSFastMutex_Unlock() called on mutex which is not owned by current thread");
_OSFastMutex_ReleaseContention(fastMutex);
return;
}
cemu_assert_debug(fastMutex->lockCount > 0);
fastMutex->lockCount = fastMutex->lockCount - 1;
if (fastMutex->lockCount == 0)
{
// set owner to null
if (!fastMutex->owner.atomic_compare_exchange(currentThread, nullptr))
{
cemu_assert_debug(false); // should never happen
}
if (!fastMutex->threadQueueSmall.isEmpty())
{
__OSLockScheduler();
fastMutex->threadQueueSmall.wakeupSingleThreadWaitQueue(false);
__OSUnlockScheduler();
}
}
_OSFastMutex_ReleaseContention(fastMutex);
}
void OSFastMutex_Unlock(OSFastMutex* fastMutex)
{
//__OSLockScheduler();
OSFastMutex_UnlockInternal(fastMutex);
//__OSUnlockScheduler();
}
/************* OSFastCond ************/
void OSFastCond_Init(OSFastCond* fastCond, void* userData)
{
fastCond->magic = 0x664e6456;
fastCond->userData = userData;
fastCond->ukn08 = 0;
OSInitThreadQueueEx(&fastCond->threadQueue, fastCond);
}
void OSFastCond_Wait(OSFastCond* fastCond, OSFastMutex* fastMutex)
{
// releases the mutex while waiting for the condition to be signaled
__OSLockScheduler();
cemu_assert_debug(fastMutex->owner == OSGetCurrentThread());
sint32 prevLockCount = fastMutex->lockCount;
// unlock mutex
fastMutex->lockCount = 0;
fastMutex->owner = nullptr;
if (!fastMutex->threadQueueSmall.isEmpty())
fastMutex->threadQueueSmall.wakeupEntireWaitQueue(false);
// wait on condition
fastCond->threadQueue.queueAndWait(OSGetCurrentThread());
// reacquire mutex
__OSUnlockScheduler();
OSFastMutex_LockInternal(fastMutex);
fastMutex->lockCount = prevLockCount;
}
void OSFastCond_Signal(OSFastCond* fastCond)
{
OSWakeupThread(&fastCond->threadQueue);
}
/************* init ************/
void InitializeConcurrency()
{
OSInitEvent(g_rendezvousEvent.GetPtr(), OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, OSEvent::EVENT_MODE::MODE_AUTO);
// OSEvent
cafeExportRegister("coreinit", OSInitEvent, LogType::ThreadSync);
cafeExportRegister("coreinit", OSInitEventEx, LogType::ThreadSync);
cafeExportRegister("coreinit", OSResetEvent, LogType::ThreadSync);
cafeExportRegister("coreinit", OSWaitEvent, LogType::ThreadSync);
cafeExportRegister("coreinit", OSWaitEventWithTimeout, LogType::ThreadSync);
cafeExportRegister("coreinit", OSSignalEvent, LogType::ThreadSync);
cafeExportRegister("coreinit", OSSignalEventAll, LogType::ThreadSync);
// OSRendezvous
cafeExportRegister("coreinit", OSInitRendezvous, LogType::ThreadSync);
cafeExportRegister("coreinit", OSWaitRendezvous, LogType::ThreadSync);
// OSMutex
cafeExportRegister("coreinit", OSInitMutex, LogType::ThreadSync);
cafeExportRegister("coreinit", OSInitMutexEx, LogType::ThreadSync);
cafeExportRegister("coreinit", OSLockMutex, LogType::ThreadSync);
cafeExportRegister("coreinit", OSTryLockMutex, LogType::ThreadSync);
cafeExportRegister("coreinit", OSUnlockMutex, LogType::ThreadSync);
// OSCond
cafeExportRegister("coreinit", OSInitCond, LogType::ThreadSync);
cafeExportRegister("coreinit", OSInitCondEx, LogType::ThreadSync);
cafeExportRegister("coreinit", OSSignalCond, LogType::ThreadSync);
cafeExportRegister("coreinit", OSWaitCond, LogType::ThreadSync);
// OSSemaphore
cafeExportRegister("coreinit", OSInitSemaphore, LogType::ThreadSync);
cafeExportRegister("coreinit", OSInitSemaphoreEx, LogType::ThreadSync);
cafeExportRegister("coreinit", OSWaitSemaphore, LogType::ThreadSync);
cafeExportRegister("coreinit", OSTryWaitSemaphore, LogType::ThreadSync);
cafeExportRegister("coreinit", OSSignalSemaphore, LogType::ThreadSync);
cafeExportRegister("coreinit", OSGetSemaphoreCount, LogType::ThreadSync);
// OSFastMutex
cafeExportRegister("coreinit", OSFastMutex_Init, LogType::ThreadSync);
cafeExportRegister("coreinit", OSFastMutex_Lock, LogType::ThreadSync);
cafeExportRegister("coreinit", OSFastMutex_TryLock, LogType::ThreadSync);
cafeExportRegister("coreinit", OSFastMutex_Unlock, LogType::ThreadSync);
// OSFastCond
cafeExportRegister("coreinit", OSFastCond_Init, LogType::ThreadSync);
cafeExportRegister("coreinit", OSFastCond_Wait, LogType::ThreadSync);
cafeExportRegister("coreinit", OSFastCond_Signal, LogType::ThreadSync);
}
};

View file

@ -0,0 +1,40 @@
#include "Cafe/OS/common/OSCommon.h"
#include "Cafe/OS/libs/coreinit/coreinit_SysHeap.h"
#include "Cafe/OS/libs/coreinit/coreinit_MEM_ExpHeap.h"
namespace coreinit
{
coreinit::MEMHeapHandle _sysHeapHandle = MPTR_NULL;
sint32 _sysHeapAllocCounter = 0;
sint32 _sysHeapFreeCounter = 0;
void* OSAllocFromSystem(uint32 size, uint32 alignment)
{
_sysHeapAllocCounter++;
return coreinit::MEMAllocFromExpHeapEx(_sysHeapHandle, size, alignment);
}
void export_OSAllocFromSystem(PPCInterpreter_t* hCPU)
{
ppcDefineParamU32(size, 0);
ppcDefineParamS32(alignment, 1);
MEMPTR<void> mem = OSAllocFromSystem(size, alignment);
forceLogDebug_printf("OSAllocFromSystem(0x%x, %d) -> 0x%08x", size, alignment, mem.GetMPTR());
osLib_returnFromFunction(hCPU, mem.GetMPTR());
}
void InitSysHeap()
{
uint32 sysHeapSize = 8 * 1024 * 1024; // actual size is unknown
MEMPTR<void> heapBaseAddress = memory_getPointerFromVirtualOffset(coreinit_allocFromSysArea(sysHeapSize, 0x1000));
_sysHeapHandle = coreinit::MEMCreateExpHeapEx(heapBaseAddress.GetPtr(), sysHeapSize, MEM_HEAP_OPTION_THREADSAFE);
_sysHeapAllocCounter = 0;
_sysHeapFreeCounter = 0;
}
void InitializeSysHeap()
{
osLib_addFunction("coreinit", "OSAllocFromSystem", export_OSAllocFromSystem);
}
}

View file

@ -0,0 +1,8 @@
#pragma once
namespace coreinit
{
void InitSysHeap();
void InitializeSysHeap();
}

View file

@ -0,0 +1,25 @@
#include "Cafe/OS/common/OSCommon.h"
#include "Cafe/OS/libs/coreinit/coreinit_SystemInfo.h"
namespace coreinit
{
SysAllocator<OSSystemInfo> g_system_info;
const OSSystemInfo& OSGetSystemInfo()
{
return *g_system_info.GetPtr();
}
void InitializeSystemInfo()
{
cemu_assert(ppcCyclesSince2000 != 0);
g_system_info->busClock = ESPRESSO_BUS_CLOCK;
g_system_info->coreClock = ESPRESSO_CORE_CLOCK;
g_system_info->ticksSince2000 = ESPRESSO_CORE_CLOCK_TO_TIMER_CLOCK(ppcCyclesSince2000);
g_system_info->l2cacheSize[0] = 512*1024; // 512KB // 512KB
g_system_info->l2cacheSize[1] = 2*1024*1924; // 2MB
g_system_info->l2cacheSize[2] = 512*1024; // 512KB
g_system_info->coreClockToBusClockRatio = 5;
cafeExportRegister("coreinit", OSGetSystemInfo, LogType::Placeholder);
}
}

View file

@ -0,0 +1,21 @@
#pragma once
#include "Cafe/OS/libs/coreinit/coreinit.h"
namespace coreinit
{
struct OSSystemInfo
{
uint32be busClock;
uint32be coreClock;
uint64be ticksSince2000;
uint32be l2cacheSize[PPC_CORE_COUNT];
uint32be coreClockToBusClockRatio;
};
static_assert(sizeof(OSSystemInfo) == 0x20);
const OSSystemInfo& OSGetSystemInfo();
void InitializeSystemInfo();
};

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,605 @@
#pragma once
#include "Cafe/HW/Espresso/Const.h"
#include "Cafe/OS/libs/coreinit/coreinit_Scheduler.h"
#define OS_CONTEXT_MAGIC_0 'OSCo'
#define OS_CONTEXT_MAGIC_1 'ntxt'
struct OSThread_t;
struct OSContextRegFPSCR_t
{
// FPSCR is a 32bit register but it's stored as a 64bit double
/* +0x00 */ uint32be padding;
/* +0x04 */ uint32be fpscr;
};
struct OSContext_t
{
/* +0x000 */ betype<uint32> magic0;
/* +0x004 */ betype<uint32> magic1;
/* +0x008 */ uint32 gpr[32];
/* +0x088 */ uint32 cr;
/* +0x08C */ uint32 lr;
/* +0x090 */ uint32 ctr;
/* +0x094 */ uint32 xer;
/* +0x098 */ uint32 srr0;
/* +0x09C */ uint32 srr1;
/* +0x0A0 */ uint32 dsi_dsisr;
/* +0x0A4 */ uint32 dsi_dar;
/* +0x0A8 */ uint32 ukn0A8;
/* +0x0AC */ uint32 ukn0AC;
/* +0x0B0 */ OSContextRegFPSCR_t fpscr;
/* +0x0B8 */ uint64be fp_ps0[32];
/* +0x1B8 */ uint16be boostCount;
/* +0x1BA */ uint16 state; // OS_CONTEXT_STATE_*
/* +0x1BC */ uint32 gqr[8]; // GQR/UGQR
/* +0x1DC */ uint32be upir; // set to current core index
/* +0x1E0 */ uint64be fp_ps1[32];
/* +0x2E0 */ uint64 uknTime2E0;
/* +0x2E8 */ uint64 uknTime2E8;
/* +0x2F0 */ uint64 uknTime2F0;
/* +0x2F8 */ uint64 uknTime2F8;
/* +0x300 */ uint32 error; // returned by __gh_errno_ptr() (used by socketlasterr)
/* +0x304 */ uint32be affinity;
/* +0x308 */ uint32 ukn0308;
/* +0x30C */ uint32 ukn030C;
/* +0x310 */ uint32 ukn0310;
/* +0x314 */ uint32 ukn0314;
/* +0x318 */ uint32 ukn0318;
/* +0x31C */ uint32 ukn031C;
bool checkMagic()
{
return magic0 == (uint32)OS_CONTEXT_MAGIC_0 && magic1 == (uint32)OS_CONTEXT_MAGIC_1;
}
bool hasCoreAffinitySet(uint32 coreIndex) const
{
return (((uint32)affinity >> coreIndex) & 1) != 0;
}
void setAffinity(uint32 mask)
{
affinity = mask & 7;
}
uint32 getAffinity() const
{
return affinity;
}
};
static_assert(sizeof(OSContext_t) == 0x320);
typedef struct
{
/* +0x000 | +0x3A0 */ uint32 ukn000[0x68 / 4];
/* +0x068 | +0x408 */ MEMPTR<void> eh_globals;
/* +0x06C | +0x40C */ uint32 eh_mem_manage[9]; // struct
/* +0x090 | +0x430 */ MPTR eh_store_globals[6];
/* +0x0A8 | +0x448 */ MPTR eh_store_globals_tdeh[76];
}crt_t; // size: 0x1D8
static_assert(sizeof(crt_t) == 0x1D8, "");
#pragma pack(1)
namespace coreinit
{
/********* OSThreadQueue *********/
struct OSThreadLink
{
MEMPTR<struct OSThread_t> next;
MEMPTR<struct OSThread_t> prev;
};
static_assert(sizeof(OSThreadLink) == 8);
struct OSThreadQueueInternal
{
MEMPTR<OSThread_t> head;
MEMPTR<OSThread_t> tail;
bool isEmpty() const
{
cemu_assert_debug((!head.IsNull() == !tail.IsNull()) || (head.IsNull() == tail.IsNull()));
return head.IsNull();
}
void addThread(OSThread_t* thread, OSThreadLink* threadLink);
void addThreadByPriority(OSThread_t* thread, OSThreadLink* threadLink);
void removeThread(OSThread_t* thread, OSThreadLink* threadLink);
// puts the thread on the waiting queue and changes state to WAITING
// relinquishes timeslice
// always uses thread->waitQueueLink
void queueAndWait(OSThread_t* thread);
void queueOnly(OSThread_t* thread);
// counterparts for queueAndWait
void cancelWait(OSThread_t* thread);
void wakeupEntireWaitQueue(bool reschedule);
void wakeupSingleThreadWaitQueue(bool reschedule);
private:
OSThread_t* takeFirstFromQueue(size_t linkOffset)
{
cemu_assert_debug(__OSHasSchedulerLock());
if (head == nullptr)
return nullptr;
OSThread_t* thread = head.GetPtr();
OSThreadLink* link = _getThreadLink(thread, linkOffset);
removeThread(thread, link);
return thread;
}
static size_t getLinkOffset(OSThread_t* thread, OSThreadLink* threadLink)
{
cemu_assert_debug((void*)threadLink >= (void*)thread && (void*)threadLink < (void*)((uint8*)thread + 0x680));
return (uint8*)threadLink - (uint8*)thread;
}
static OSThreadLink* _getThreadLink(OSThread_t* thread, size_t linkOffset)
{
return (OSThreadLink*)((uint8*)thread + linkOffset);
}
void _debugCheckChain(OSThread_t* thread, OSThreadLink* threadLink)
{
#ifndef PUBLIC_RELEASE
cemu_assert_debug(tail.IsNull() == head.IsNull());
size_t linkOffset = getLinkOffset(thread, threadLink);
// expects thread to be in the chain
OSThread_t* threadItr = head.GetPtr();
while (threadItr)
{
if (threadItr == thread)
return;
threadItr = _getThreadLink(threadItr, linkOffset)->next.GetPtr();
}
cemu_assert_debug(false); // thread not in list!
#endif
}
};
static_assert(sizeof(OSThreadQueueInternal) == 0x8);
struct OSThreadQueueSmall : public OSThreadQueueInternal
{
// no extra members
};
static_assert(sizeof(OSThreadQueueSmall) == 8);
static_assert(offsetof(OSThreadQueueSmall, head) == 0x0);
static_assert(offsetof(OSThreadQueueSmall, tail) == 0x4);
struct OSThreadQueue : public OSThreadQueueInternal
{
MEMPTR<void> userData;
uint32be ukn0C;
};
static_assert(sizeof(OSThreadQueue) == 0x10);
static_assert(offsetof(OSThreadQueue, head) == 0x0);
static_assert(offsetof(OSThreadQueue, tail) == 0x4);
static_assert(offsetof(OSThreadQueue, userData) == 0x8);
static_assert(offsetof(OSThreadQueue, ukn0C) == 0xC);
/********* OSMutex *********/
struct OSMutex
{
/* +0x00 */ uint32 magic;
/* +0x04 */ MEMPTR<void> userData;
/* +0x08 */ uint32be ukn08;
/* +0x0C */ OSThreadQueue threadQueue;
/* +0x1C */ MEMPTR<OSThread_t> owner;
/* +0x20 */ sint32be lockCount;
/* +0x24 */ MEMPTR<OSMutex> next;
/* +0x28 */ MEMPTR<OSMutex> prev;
}; // size: 0x2C
static_assert(sizeof(OSMutex) == 0x2C);
struct OSMutexQueue
{
MEMPTR<OSMutex> head;
MEMPTR<OSMutex> tail;
MEMPTR<void> ukn08;
uint32be ukn0C;
bool isEmpty() const
{
cemu_assert_debug((!head.IsNull() == !tail.IsNull()) || (head.IsNull() == tail.IsNull()));
return head.IsNull();
}
void addMutex(OSMutex* mutex)
{
cemu_assert_debug(__OSHasSchedulerLock());
// insert at end
if (tail.IsNull())
{
mutex->next = nullptr;
mutex->prev = nullptr;
head = mutex;
tail = mutex;
}
else
{
tail->next = mutex;
mutex->prev = tail;
mutex->next = nullptr;
tail = mutex;
}
}
void removeMutex(OSMutex* mutex)
{
cemu_assert_debug(__OSHasSchedulerLock());
cemu_assert_debug(!head.IsNull() && !tail.IsNull());
if (mutex->prev)
mutex->prev->next = mutex->next;
else
head = mutex->next;
if (mutex->next)
mutex->next->prev = mutex->prev;
else
tail = mutex->prev;
mutex->next = nullptr;
mutex->prev = nullptr;
}
OSMutex* getFirst()
{
return head.GetPtr();
}
};
static_assert(sizeof(OSMutexQueue) == 0x10);
/********* OSFastMutex *********/
struct OSFastMutexLink
{
/* +0x00 */ MEMPTR<struct OSMutex> next;
/* +0x04 */ MEMPTR<struct OSMutex> prev;
};
struct OSFastMutex
{
/* +0x00 */ uint32be magic;
/* +0x04 */ MEMPTR<void> userData;
/* +0x08 */ uint32be contendedState; // tracks current contention state
/* +0x0C */ OSThreadQueueSmall threadQueueSmall;
/* +0x14 */ OSFastMutexLink ownedLink; // part of thread->fastMutexOwnedQueue
/* +0x1C */ MEMPTR<OSThread_t> owner;
/* +0x20 */ uint32be lockCount;
/* +0x24 */ OSFastMutexLink contendedLink;
};
static_assert(sizeof(OSFastMutex) == 0x2C);
/********* OSEvent *********/
struct OSEvent
{
enum class EVENT_MODE : uint32
{
MODE_MANUAL = 0,
MODE_AUTO = 1,
};
enum class EVENT_STATE : uint32
{
STATE_NOT_SIGNALED = 0,
STATE_SIGNALED = 1
};
/* +0x00 */ uint32be magic; // 'eVnT'
/* +0x04 */ MEMPTR<void> userData;
/* +0x08 */ uint32be ukn08;
/* +0x0C */ betype<EVENT_STATE> state; // 0 -> not signaled, 1 -> signaled
/* +0x10 */ OSThreadQueue threadQueue;
/* +0x20 */ betype<EVENT_MODE> mode;
};
static_assert(sizeof(OSEvent) == 0x24);
/********* OSRendezvous *********/
struct OSRendezvous
{
/* +0x00 */ uint32be coreHit[3];
/* +0x0C */ MEMPTR<void> userData;
};
static_assert(sizeof(OSRendezvous) == 0x10);
/********* OSCond *********/
struct OSCond
{
uint32be magic;
MEMPTR<void> userData;
uint32be ukn08;
OSThreadQueue threadQueue;
};
static_assert(sizeof(OSCond) == 0x1C);
/********* OSSemaphore *********/
struct OSSemaphore
{
uint32be magic;
MEMPTR<void> userData;
uint32be ukn08;
sint32be count;
OSThreadQueue threadQueue;
};
static_assert(sizeof(OSSemaphore) == 0x20);
/********* OSFastCond *********/
struct OSFastCond
{
uint32be magic;
MEMPTR<void> userData;
uint32be ukn08;
OSThreadQueue threadQueue;
};
static_assert(sizeof(OSFastCond) == 0x1C);
};
struct OSThread_t
{
enum class THREAD_TYPE : uint32
{
TYPE_DRIVER = 0,
TYPE_IO = 1,
TYPE_APP = 2
};
enum class THREAD_STATE : uint8
{
STATE_NONE = 0,
STATE_READY = 1,
STATE_RUNNING = 2,
STATE_WAITING = 4,
STATE_MORIBUND = 8,
};
enum ATTR_BIT : uint32
{
ATTR_AFFINITY_CORE0 = 0x1,
ATTR_AFFINITY_CORE1 = 0x2,
ATTR_AFFINITY_CORE2 = 0x4,
ATTR_DETACHED = 0x8,
// more flags?
};
enum REQUEST_FLAG_BIT : uint32
{
REQUEST_FLAG_NONE = 0,
REQUEST_FLAG_SUSPEND = 1,
REQUEST_FLAG_CANCEL = 2,
};
static sint32 GetTypePriorityBase(THREAD_TYPE threadType)
{
if (threadType == OSThread_t::THREAD_TYPE::TYPE_DRIVER)
return 0;
else if (threadType == OSThread_t::THREAD_TYPE::TYPE_IO)
return 32;
else if (threadType == OSThread_t::THREAD_TYPE::TYPE_APP)
return 64;
return 0;
}
/* +0x000 */ OSContext_t context;
/* +0x320 */ uint32be magic; // 'tHrD'
/* +0x324 */ betype<THREAD_STATE> state;
/* +0x325 */ uint8 attr;
/* +0x326 */ uint16be id; // Warriors Orochi 3 uses this to identify threads. Seems like this is always set to 0x8000 ?
/* +0x328 */ betype<sint32> suspendCounter;
/* +0x32C */ sint32be effectivePriority; // effective priority (lower is higher)
/* +0x330 */ sint32be basePriority; // base priority (lower is higher)
/* +0x334 */ uint32be exitValue; // set by OSExitThread() or by returning from the thread entrypoint
/* +0x338 */ MEMPTR<coreinit::OSThreadQueue> currentRunQueue[Espresso::CORE_COUNT]; // points to the OSThreadQueue on which this thread is (null if not in queue)
/* +0x344 */ coreinit::OSThreadLink linkRun[Espresso::CORE_COUNT];
// general wait queue / thread dependency queue
/* +0x35C */ MEMPTR<coreinit::OSThreadQueueInternal> currentWaitQueue; // shared between OSThreadQueue and OSThreadQueueSmall
/* +0x360 */ coreinit::OSThreadLink waitQueueLink;
/* +0x368 */ coreinit::OSThreadQueue joinQueue;
/* +0x378 */ MEMPTR<coreinit::OSMutex> waitingForMutex; // set when thread is waiting for OSMutex to unlock
/* +0x37C */ coreinit::OSMutexQueue mutexQueue;
/* +0x38C */ coreinit::OSThreadLink activeThreadChain; // queue of active threads (g_activeThreadQueue)
/* +0x394 */ MPTR_UINT8 stackBase; // upper limit of stack
/* +0x398 */ MPTR_UINT32 stackEnd; // lower limit of stack
/* +0x39C */ MPTR entrypoint;
/* +0x3A0 */ crt_t crt;
/* +0x578 */ sint32 alarmRelatedUkn;
/* +0x57C */ std::array<MEMPTR<void>, 16> specificArray;
/* +0x5BC */ betype<THREAD_TYPE> type;
/* +0x5C0 */ MEMPTR<char> threadName;
/* +0x5C4 */ MPTR waitAlarm; // used only by OSWaitEventWithTimeout/OSSignalEvent ?
/* +0x5C8 */ uint32 userStackPointer;
/* +0x5CC */ MEMPTR<void> cleanupCallback2;
/* +0x5D0 */ //MPTR deallocator;
MEMPTR<void> deallocatorFunc;
/* +0x5D4 */ uint32 stateFlags; // 0x5D4 | various flags? Controls if canceling/suspension is allowed (at cancel points) or not? If 1 -> Cancel/Suspension not allowed, if 0 -> Cancel/Suspension allowed
/* +0x5D8 */ betype<REQUEST_FLAG_BIT> requestFlags;
/* +0x5DC */ sint32 pendingSuspend;
/* +0x5E0 */ sint32 suspendResult;
/* +0x5E4 */ coreinit::OSThreadQueue suspendQueue;
/* +0x5F4 */ uint32 _padding5F4;
/* +0x5F8 */ uint64be quantumTicks;
/* +0x600 */ uint64 coretimeSumQuantumStart;
/* +0x608 */ uint64be wakeUpCount; // number of times the thread entered running state
/* +0x610 */ uint64be totalCycles; // sum of cycles this thread was active since it was created
/* +0x618 */ uint64be wakeUpTime; // time when thread first became active (Should only be set once(?) but we currently set this on every timeslice since thats more useful for debugging)
/* +0x620 */ uint64 wakeTimeRelatedUkn1;
/* +0x628 */ uint64 wakeTimeRelatedUkn2;
// set via OSSetExceptionCallback
/* +0x630 */ MPTR ukn630Callback[Espresso::CORE_COUNT];
/* +0x63C */ MPTR ukn63CCallback[Espresso::CORE_COUNT];
/* +0x648 */ MPTR ukn648Callback[Espresso::CORE_COUNT];
/* +0x654 */ MPTR ukn654Callback[Espresso::CORE_COUNT];
/* +0x660 */ uint32 ukn660;
// TLS
/* +0x664 */ uint16 numAllocatedTLSBlocks;
/* +0x666 */ sint16 tlsStatus;
/* +0x668 */ MPTR tlsBlocksMPTR;
/* +0x66C */ MEMPTR<coreinit::OSFastMutex> waitingForFastMutex;
/* +0x670 */ coreinit::OSFastMutexLink contendedFastMutex; // link or queue?
/* +0x678 */ coreinit::OSFastMutexLink ownedFastMutex; // link or queue?
/* +0x680 */ uint32 padding680[28 / 4];
};
static_assert(sizeof(OSThread_t) == 0x6A0-4); // todo - determine correct size
namespace coreinit
{
void InitializeThread();
void InitializeConcurrency();
OSThread_t* OSGetDefaultThread(sint32 coreIndex);
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);
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);
void OSDetachThread(OSThread_t* thread);
OSThread_t* OSGetCurrentThread();
void OSSetCurrentThread(uint32 coreIndex, OSThread_t* thread);
void __OSSetThreadBasePriority(OSThread_t* thread, sint32 newPriority);
void __OSUpdateThreadEffectivePriority(OSThread_t* thread);
bool OSSetThreadPriority(OSThread_t* thread, sint32 newPriority);
uint32 OSGetThreadAffinity(OSThread_t* thread);
void OSSetThreadName(OSThread_t* thread, char* name);
char* OSGetThreadName(OSThread_t* thread);
sint32 __OSResumeThreadInternal(OSThread_t* thread, sint32 resumeCount);
sint32 OSResumeThread(OSThread_t* thread);
void OSContinueThread(OSThread_t* thread);
void __OSSuspendThreadInternal(OSThread_t* thread);
void OSSuspendThread(OSThread_t* thread);
void OSSleepThread(OSThreadQueue* threadQueue);
void OSWakeupThread(OSThreadQueue* threadQueue);
void OSTestThreadCancelInternal();
void OSYieldThread();
void OSSleepTicks(uint64 ticks);
bool OSIsThreadTerminated(OSThread_t* thread);
bool OSIsThreadSuspended(OSThread_t* thread);
// OSThreadQueue
void OSInitThreadQueue(OSThreadQueue* threadQueue);
void OSInitThreadQueueEx(OSThreadQueue* threadQueue, void* userData);
// OSEvent
void OSInitEvent(OSEvent* event, OSEvent::EVENT_STATE initialState, OSEvent::EVENT_MODE mode);
void OSInitEventEx(OSEvent* event, OSEvent::EVENT_STATE initialState, OSEvent::EVENT_MODE mode, void* userData);
void OSResetEvent(OSEvent* event);
void OSWaitEventInternal(OSEvent* event); // assumes lock is already held
void OSWaitEvent(OSEvent* event);
bool OSWaitEventWithTimeout(OSEvent* event, uint64 timeout);
void OSSignalEventInternal(OSEvent* event); // assumes lock is already held
void OSSignalEvent(OSEvent* event);
void OSSignalEventAllInternal(OSEvent* event); // assumes lock is already held
void OSSignalEventAll(OSEvent* event);
// OSRendezvous
void OSInitRendezvous(OSRendezvous* rendezvous);
bool OSWaitRendezvous(OSRendezvous* rendezvous, uint32 coreMask);
// OSMutex
void OSInitMutex(OSMutex* mutex);
void OSInitMutexEx(OSMutex* mutex, void* userData);
void OSLockMutex(OSMutex* mutex);
bool OSTryLockMutex(OSMutex* mutex);
void OSUnlockMutex(OSMutex* mutex);
void OSUnlockMutexInternal(OSMutex* mutex);
// OSCond
void OSInitCond(OSCond* cond);
void OSInitCondEx(OSCond* cond, void* userData);
void OSSignalCond(OSCond* cond);
void OSWaitCond(OSCond* cond, OSMutex* mutex);
// OSSemaphore
void OSInitSemaphore(OSSemaphore* semaphore, sint32 initialCount);
void OSInitSemaphoreEx(OSSemaphore* semaphore, sint32 initialCount, void* userData);
sint32 OSWaitSemaphoreInternal(OSSemaphore* semaphore);
sint32 OSWaitSemaphore(OSSemaphore* semaphore);
sint32 OSTryWaitSemaphore(OSSemaphore* semaphore);
sint32 OSSignalSemaphore(OSSemaphore* semaphore);
sint32 OSSignalSemaphoreInternal(OSSemaphore* semaphore, bool reschedule);
sint32 OSGetSemaphoreCount(OSSemaphore* semaphore);
// OSFastMutex
void OSFastMutex_Init(OSFastMutex* fastMutex, void* userData);
void OSFastMutex_Lock(OSFastMutex* fastMutex);
bool OSFastMutex_TryLock(OSFastMutex* fastMutex);
void OSFastMutex_Unlock(OSFastMutex* fastMutex);
// OSFastCond
void OSFastCond_Init(OSFastCond* fastCond, void* userData);
void OSFastCond_Wait(OSFastCond* fastCond, OSFastMutex* fastMutex);
void OSFastCond_Signal(OSFastCond* fastCond);
// scheduler
void OSSchedulerBegin(sint32 numCPUEmulationThreads);
void OSSchedulerEnd();
// internal
void __OSAddReadyThreadToRunQueue(OSThread_t* thread);
bool __OSCoreShouldSwitchToThread(OSThread_t* currentThread, OSThread_t* newThread);
void __OSQueueThreadDeallocation(OSThread_t* thread);
}
#pragma pack()
// deprecated / clean up required
void coreinit_suspendThread(OSThread_t* OSThreadBE, sint32 count = 1);
void coreinit_resumeThread(OSThread_t* OSThreadBE, sint32 count = 1);
MPTR coreinitThread_getCurrentThreadMPTRDepr(PPCInterpreter_t* hCPU);
OSThread_t* coreinitThread_getCurrentThreadDepr(PPCInterpreter_t* hCPU);
extern MPTR activeThread[256];
extern sint32 activeThreadCount;
extern SlimRWLock srwlock_activeThreadList;

View file

@ -0,0 +1,169 @@
#include "Cafe/OS/common/OSCommon.h"
#include "Cafe/OS/libs/coreinit/coreinit_Thread.h"
namespace coreinit
{
// puts the thread on the waiting queue and changes state to WAITING
// relinquishes timeslice
// always uses thread->waitQueueLink
void OSThreadQueueInternal::queueAndWait(OSThread_t* thread)
{
cemu_assert_debug(__OSHasSchedulerLock());
cemu_assert_debug(thread->waitQueueLink.next == nullptr && thread->waitQueueLink.prev == nullptr);
thread->currentWaitQueue = this;
this->addThreadByPriority(thread, &thread->waitQueueLink);
cemu_assert_debug(thread->state == OSThread_t::THREAD_STATE::STATE_RUNNING);
thread->state = OSThread_t::THREAD_STATE::STATE_WAITING;
PPCCore_switchToSchedulerWithLock();
cemu_assert_debug(thread->state == OSThread_t::THREAD_STATE::STATE_RUNNING);
}
void OSThreadQueueInternal::queueOnly(OSThread_t* thread)
{
cemu_assert_debug(__OSHasSchedulerLock());
cemu_assert_debug(thread->waitQueueLink.next == nullptr && thread->waitQueueLink.prev == nullptr);
thread->currentWaitQueue = this;
this->addThreadByPriority(thread, &thread->waitQueueLink);
cemu_assert_debug(thread->state == OSThread_t::THREAD_STATE::STATE_RUNNING);
thread->state = OSThread_t::THREAD_STATE::STATE_WAITING;
}
// remove thread from wait queue and wake it up
void OSThreadQueueInternal::cancelWait(OSThread_t* thread)
{
cemu_assert_debug(__OSHasSchedulerLock());
this->removeThread(thread, &thread->waitQueueLink);
thread->state = OSThread_t::THREAD_STATE::STATE_READY;
thread->currentWaitQueue = nullptr;
coreinit::__OSAddReadyThreadToRunQueue(thread);
// todo - if waking up a thread on the same core with higher priority, reschedule
}
void OSThreadQueueInternal::addThread(OSThread_t* thread, OSThreadLink* threadLink)
{
cemu_assert_debug(__OSHasSchedulerLock());
size_t linkOffset = getLinkOffset(thread, threadLink);
// insert after tail
if (tail.IsNull())
{
threadLink->next = nullptr;
threadLink->prev = nullptr;
head = thread;
tail = thread;
}
else
{
threadLink->next = nullptr;
threadLink->prev = tail;
_getThreadLink(tail.GetPtr(), linkOffset)->next = thread;
tail = thread;
}
_debugCheckChain(thread, threadLink);
}
void OSThreadQueueInternal::addThreadByPriority(OSThread_t* thread, OSThreadLink* threadLink)
{
cemu_assert_debug(tail.IsNull() == head.IsNull()); // either must be set or none at all
cemu_assert_debug(__OSHasSchedulerLock());
size_t linkOffset = getLinkOffset(thread, threadLink);
if (tail.IsNull())
{
threadLink->next = nullptr;
threadLink->prev = nullptr;
head = thread;
tail = thread;
}
else
{
// insert towards tail based on priority
OSThread_t* threadItr = tail.GetPtr();
while (threadItr && threadItr->effectivePriority > thread->effectivePriority)
threadItr = _getThreadLink(threadItr, linkOffset)->prev.GetPtr();
if (threadItr == nullptr)
{
// insert in front
threadLink->next = head;
threadLink->prev = nullptr;
_getThreadLink(head.GetPtr(), linkOffset)->prev = thread;
head = thread;
}
else
{
threadLink->prev = threadItr;
threadLink->next = _getThreadLink(threadItr, linkOffset)->next;
if (_getThreadLink(threadItr, linkOffset)->next)
{
OSThread_t* threadAfterItr = _getThreadLink(threadItr, linkOffset)->next.GetPtr();
_getThreadLink(threadAfterItr, linkOffset)->prev = thread;
_getThreadLink(threadItr, linkOffset)->next = thread;
}
else
{
tail = thread;
_getThreadLink(threadItr, linkOffset)->next = thread;
}
}
}
_debugCheckChain(thread, threadLink);
}
void OSThreadQueueInternal::removeThread(OSThread_t* thread, OSThreadLink* threadLink)
{
cemu_assert_debug(__OSHasSchedulerLock());
size_t linkOffset = getLinkOffset(thread, threadLink);
_debugCheckChain(thread, threadLink);
if (threadLink->prev)
_getThreadLink(threadLink->prev.GetPtr(), linkOffset)->next = threadLink->next;
else
head = threadLink->next;
if (threadLink->next)
_getThreadLink(threadLink->next.GetPtr(), linkOffset)->prev = threadLink->prev;
else
tail = threadLink->prev;
threadLink->next = nullptr;
threadLink->prev = nullptr;
}
// counterpart for queueAndWait
// if reschedule is true then scheduler will switch to woken up thread (if it is runnable on the same core)
void OSThreadQueueInternal::wakeupEntireWaitQueue(bool reschedule)
{
cemu_assert_debug(__OSHasSchedulerLock());
bool shouldReschedule = false;
while (OSThread_t* thread = takeFirstFromQueue(offsetof(OSThread_t, waitQueueLink)))
{
cemu_assert_debug(thread->state == OSThread_t::THREAD_STATE::STATE_WAITING);
cemu_assert_debug(thread->suspendCounter == 0);
thread->state = OSThread_t::THREAD_STATE::STATE_READY;
thread->currentWaitQueue = nullptr;
coreinit::__OSAddReadyThreadToRunQueue(thread);
if (reschedule && thread->suspendCounter == 0 && ppcInterpreterCurrentInstance && __OSCoreShouldSwitchToThread(coreinit::OSGetCurrentThread(), thread))
shouldReschedule = true;
}
if (shouldReschedule)
PPCCore_switchToSchedulerWithLock();
}
// counterpart for queueAndWait
// if reschedule is true then scheduler will switch to woken up thread (if it is runnable on the same core)
void OSThreadQueueInternal::wakeupSingleThreadWaitQueue(bool reschedule)
{
cemu_assert_debug(__OSHasSchedulerLock());
OSThread_t* thread = takeFirstFromQueue(offsetof(OSThread_t, waitQueueLink));
cemu_assert_debug(thread);
bool shouldReschedule = false;
if (thread)
{
thread->state = OSThread_t::THREAD_STATE::STATE_READY;
thread->currentWaitQueue = nullptr;
coreinit::__OSAddReadyThreadToRunQueue(thread);
if (reschedule && thread->suspendCounter == 0 && ppcInterpreterCurrentInstance && __OSCoreShouldSwitchToThread(coreinit::OSGetCurrentThread(), thread))
shouldReschedule = true;
}
if (shouldReschedule)
PPCCore_switchToSchedulerWithLock();
}
}

View file

@ -0,0 +1,379 @@
#include "Cafe/OS/common/OSCommon.h"
#include "Cafe/OS/libs/coreinit/coreinit_Time.h"
namespace coreinit
{
uint64 coreinit_getTimerTick()
{
// bus clock is 1/5th of core clock
// timer clock is 1/4th of bus clock
return PPCInterpreter_getMainCoreCycleCounter() / 20ULL;
}
uint64 coreinit_getOSTime()
{
return coreinit_getTimerTick() + ppcCyclesSince2000TimerClock;
}
void export_OSGetTick(PPCInterpreter_t* hCPU)
{
uint64 osTime = coreinit_getOSTime();
osLib_returnFromFunction(hCPU, (uint32)osTime);
}
void export_OSGetTime(PPCInterpreter_t* hCPU)
{
uint64 osTime = coreinit_getOSTime();
osLib_returnFromFunction64(hCPU, osTime);
}
__declspec(noinline) uint64 coreinit_getTimeBase_dummy()
{
return __rdtsc();
}
void export_OSGetSystemTimeDummy(PPCInterpreter_t* hCPU)
{
osLib_returnFromFunction64(hCPU, coreinit_getTimeBase_dummy());
}
void export_OSGetSystemTime(PPCInterpreter_t* hCPU)
{
osLib_returnFromFunction64(hCPU, coreinit_getTimerTick());
}
uint32 getLeapDaysUntilYear(uint32 year)
{
if (year == 0)
return 0;
return (year + 3) / 4 - (year - 1) / 100 + (year - 1) / 400;
}
bool IsLeapYear(uint32 year)
{
if (((year & 3) == 0) && (year % 100) != 0)
return true;
if ((year % 400) == 0)
return true;
return false;
}
uint32 dayToMonth[12] =
{
0,31,59,90,120,151,181,212,243,273,304,334
};
uint32 dayToMonthLeapYear[12] =
{
0,31,60,91,121,152,182,213,244,274,305,335
};
uint32 getDayInYearByYearAndMonth(uint32 year, uint32 month)
{
// Project Zero Maiden of Black Water (JPN) gives us an invalid calendar object
month %= 12; // or return 0 if too big?
if (IsLeapYear(year))
return dayToMonthLeapYear[month];
return dayToMonth[month];
}
inline const uint64 DAY_BIAS_2000 = 0xB2575;
uint64 OSCalendarTimeToTicks(OSCalendarTime_t *calendar)
{
uint32 year = calendar->year;
uint32 leapDays = getLeapDaysUntilYear(year);
uint32 startDayOfCurrentMonth = getDayInYearByYearAndMonth(year, calendar->month);
uint64 dayInYear = (startDayOfCurrentMonth + calendar->dayOfMonth) - 1;
uint64 dayCount = dayInYear + year * 365 + leapDays - DAY_BIAS_2000;
// convert date to seconds
uint64 tSeconds = 0;
tSeconds += (uint64)dayCount * 24 * 60 * 60;
tSeconds += (uint64)calendar->hour * 60 * 60;
tSeconds += (uint64)calendar->minute * 60;
tSeconds += (uint64)calendar->second;
uint64 tSubSecondTicks = 0;
tSubSecondTicks += (uint64)calendar->millisecond * ESPRESSO_TIMER_CLOCK / 1000;
tSubSecondTicks += (uint64)calendar->microsecond * ESPRESSO_TIMER_CLOCK / 1000000;
return tSeconds * ESPRESSO_TIMER_CLOCK + tSubSecondTicks;
}
void OSTicksToCalendarTime(uint64 ticks, OSCalendarTime_t* calenderStruct)
{
uint64 tSubSecondTicks = ticks % ESPRESSO_TIMER_CLOCK;
uint64 tSeconds = ticks / ESPRESSO_TIMER_CLOCK;
uint64 microsecond = tSubSecondTicks * 1000000ull / ESPRESSO_TIMER_CLOCK;
microsecond %= 1000ull;
calenderStruct->microsecond = (uint32)microsecond;
uint64 millisecond = tSubSecondTicks * 1000ull / ESPRESSO_TIMER_CLOCK;
millisecond %= 1000ull;
calenderStruct->millisecond = (uint32)millisecond;
uint64 dayOfWeek = (tSeconds/(24ull * 60 * 60) + 6ull) % 7ull;
uint64 secondOfDay = (tSeconds % (24ull * 60 * 60));
calenderStruct->dayOfWeek = (sint32)dayOfWeek;
uint64 daysSince0AD = tSeconds / (24ull * 60 * 60) + DAY_BIAS_2000;
uint32 year = (uint32)(daysSince0AD / 365ull);
uint64 yearStartDay = year * 365 + getLeapDaysUntilYear(year);
while (yearStartDay > daysSince0AD)
{
year--;
if (IsLeapYear(year))
yearStartDay -= 366;
else
yearStartDay -= 365;
}
calenderStruct->year = year;
// calculate time of day
uint32 tempSecond = (uint32)secondOfDay;
calenderStruct->second = tempSecond % 60;
tempSecond /= 60;
calenderStruct->minute = tempSecond % 60;
tempSecond /= 60;
calenderStruct->hour = tempSecond % 24;
tempSecond /= 24;
// calculate month and day
uint32 dayInYear = (uint32)(daysSince0AD - yearStartDay);
bool isLeapYear = IsLeapYear(year);
uint32 month = 0; // 0-11
uint32 dayInMonth = 0;
if (isLeapYear && dayInYear < (31+29))
{
if (dayInYear < 31)
{
// January
month = 0;
dayInMonth = dayInYear;
}
else
{
// February
month = 1;
dayInMonth = dayInYear-31;
}
}
else
{
if (isLeapYear)
dayInYear--;
dayInMonth = dayInYear;
if (dayInYear >= 334)
{
// December
month = 11;
dayInMonth -= 334;
}
else if (dayInYear >= 304)
{
// November
month = 10;
dayInMonth -= 304;
}
else if (dayInYear >= 273)
{
// October
month = 9;
dayInMonth -= 273;
}
else if (dayInYear >= 243)
{
// September
month = 8;
dayInMonth -= 243;
}
else if (dayInYear >= 212)
{
// August
month = 7;
dayInMonth -= 212;
}
else if (dayInYear >= 181)
{
// July
month = 6;
dayInMonth -= 181;
}
else if (dayInYear >= 151)
{
// June
month = 5;
dayInMonth -= 151;
}
else if (dayInYear >= 120)
{
// May
month = 4;
dayInMonth -= 120;
}
else if (dayInYear >= 90)
{
// April
month = 3;
dayInMonth -= 90;
}
else if (dayInYear >= 59)
{
// March
month = 2;
dayInMonth -= 59;
}
else if (dayInYear >= 31)
{
// February
month = 1;
dayInMonth -= 31;
}
else
{
// January
month = 0;
dayInMonth -= 0;
}
}
calenderStruct->dayOfYear = dayInYear;
calenderStruct->month = month;
calenderStruct->dayOfMonth = dayInMonth + 1;
}
uint32 getDaysInMonth(uint32 year, uint32 month)
{
switch (month)
{
case 0: // January
return 31;
case 1: // February
return IsLeapYear(year) ? 29 : 28;
case 2: // March
return 31;
case 3: // April
return 30;
case 4: // May
return 31;
case 5: // June
return 30;
case 6: // July
return 31;
case 7: // August
return 31;
case 8: // September
return 30;
case 9: // October
return 31;
case 10: // November
return 30;
case 11: // December
return 31;
default:
cemu_assert(false);
}
return 31;
}
void verifyDateMatch(OSCalendarTime_t* ct1, OSCalendarTime_t* ct2)
{
sint64 m1 = ct1->millisecond * 1000 + ct1->microsecond;
sint64 m2 = ct2->millisecond * 1000 + ct2->microsecond;
sint64 microDif = std::abs(m1 - m2);
if (ct1->year != ct2->year ||
ct1->month != ct2->month ||
ct1->dayOfMonth != ct2->dayOfMonth ||
ct1->hour != ct2->hour ||
ct1->minute != ct2->minute ||
ct1->second != ct2->second ||
microDif > 1ull)
{
debug_printf("Mismatch\n");
assert_dbg();
}
}
void timeTest()
{
sint32 iterCount = 0;
OSCalendarTime_t ct{};
ct.year = 2000;
ct.month = 1;
ct.dayOfMonth = 1;
ct.hour = 1;
ct.minute = 1;
ct.second = 1;
ct.millisecond = 123;
ct.microsecond = 321;
while (true)
{
iterCount++;
uint64 ticks = OSCalendarTimeToTicks(&ct);
// make sure converting it back results in the same date
OSCalendarTime_t ctTemp;
OSTicksToCalendarTime(ticks, &ctTemp);
verifyDateMatch(&ct, &ctTemp);
// add a day
ticks += 24ull * 60 * 60 * ESPRESSO_TIMER_CLOCK;
OSCalendarTime_t ctOutput;
OSTicksToCalendarTime(ticks, &ctOutput);
OSCalendarTime_t ctExpected;
ctExpected = ct;
// add a day manually
sint32 daysInMonth = getDaysInMonth(ctExpected.year, ctExpected.month);
ctExpected.dayOfMonth = ctExpected.dayOfMonth + 1;
if (ctExpected.dayOfMonth >= daysInMonth+1)
{
ctExpected.dayOfMonth = 1;
ctExpected.month = ctExpected.month + 1;
if (ctExpected.month > 11)
{
ctExpected.month = 0;
ctExpected.year = ctExpected.year + 1;
}
}
verifyDateMatch(&ctExpected, &ctOutput);
ct = ctOutput;
}
}
void InitializeTimeAndCalendar()
{
osLib_addFunction("coreinit", "OSGetTime", export_OSGetTime);
osLib_addFunction("coreinit", "OSGetSystemTime", export_OSGetSystemTimeDummy); // register dummy HLE function to get Cemuhook to patch our dummy instead of the real function
osLib_addFunction("coreinit", "OSGetTick", export_OSGetTick);
cafeExportRegister("coreinit", OSTicksToCalendarTime, LogType::Placeholder);
cafeExportRegister("coreinit", OSCalendarTimeToTicks, LogType::Placeholder);
osLib_addFunction("coreinit", "OSGetSystemTime", export_OSGetSystemTime);
//timeTest();
}
};

View file

@ -0,0 +1,58 @@
#pragma once
#include "Cafe/HW/Espresso/Const.h"
namespace coreinit
{
typedef struct
{
/* +0x00 */ sint32be second; // 0-59
/* +0x04 */ sint32be minute; // 0-59
/* +0x08 */ sint32be hour; // 0-23
/* +0x0C */ sint32be dayOfMonth; // 1-31
/* +0x10 */ sint32be month; // 0-11
/* +0x14 */ sint32be year; // 2000-...
/* +0x18 */ sint32be dayOfWeek; // 0-6
/* +0x1C */ sint32be dayOfYear; // 0-365
/* +0x20 */ sint32be millisecond; // 0-999
/* +0x24 */ sint32be microsecond; // 0-999
}OSCalendarTime_t;
static_assert(sizeof(OSCalendarTime_t) == 0x28);
namespace EspressoTime
{
typedef sint64 TimerTicks;
constexpr sint64 GetCoreClock()
{
return Espresso::CORE_CLOCK;
}
constexpr sint64 GetBusClock()
{
return Espresso::BUS_CLOCK;
}
constexpr sint64 GetTimerClock()
{
return Espresso::TIMER_CLOCK;
}
inline TimerTicks ConvertNsToTimerTicks(uint64 ns)
{
return ((GetTimerClock() / 31250LL) * ((ns)) / 32000LL);
}
};
void OSTicksToCalendarTime(uint64 ticks, OSCalendarTime_t* calenderStruct);
uint64 coreinit_getOSTime();
uint64 coreinit_getTimerTick();
static uint64 OSGetSystemTime()
{
return coreinit_getTimerTick();
}
void InitializeTimeAndCalendar();
};

View file

@ -0,0 +1,119 @@
#include "Cafe/OS/common/OSCommon.h"
#include "Cafe/OS/libs/coreinit/coreinit_Time.h"
#include "Cafe/HW/Latte/Core/LatteBufferCache.h"
#define DMAE_ENDIAN_NONE 0
#define DMAE_ENDIAN_16 1
#define DMAE_ENDIAN_32 2
#define DMAE_ENDIAN_64 3
uint64 dmaeRetiredTimestamp = 0;
uint64 dmae_getTimestamp()
{
return coreinit::coreinit_getTimerTick();
}
void dmae_setRetiredTimestamp(uint64 timestamp)
{
dmaeRetiredTimestamp = timestamp;
}
void dmaeExport_DMAECopyMem(PPCInterpreter_t* hCPU)
{
if( hCPU->gpr[6] == DMAE_ENDIAN_NONE )
{
// don't change endianness
memcpy(memory_getPointerFromVirtualOffset(hCPU->gpr[3]), memory_getPointerFromVirtualOffset(hCPU->gpr[4]), hCPU->gpr[5]*4);
}
else if( hCPU->gpr[6] == DMAE_ENDIAN_32 )
{
// swap per uint32
uint32* srcBuffer = (uint32*)memory_getPointerFromVirtualOffset(hCPU->gpr[4]);
uint32* dstBuffer = (uint32*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]);
for(uint32 i=0; i<hCPU->gpr[5]; i++)
{
dstBuffer[i] = _swapEndianU32(srcBuffer[i]);
}
}
else
{
cemuLog_logDebug(LogType::Force, "DMAECopyMem(): Unsupported endian swap\n");
}
uint64 dmaeTimestamp = dmae_getTimestamp();
dmae_setRetiredTimestamp(dmaeTimestamp);
if(hCPU->gpr[5] > 0)
LatteBufferCache_notifyDCFlush(hCPU->gpr[3], hCPU->gpr[5]*4);
osLib_returnFromFunction64(hCPU, dmaeTimestamp);
}
void dmaeExport_DMAEFillMem(PPCInterpreter_t* hCPU)
{
uint32* dstBuffer = (uint32*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]);
uint32 value = hCPU->gpr[4];
uint32 numU32s = hCPU->gpr[5];
value = _swapEndianU32(value);
for(uint32 i=0; i<numU32s; i++)
{
*dstBuffer = value;
dstBuffer++;
}
uint64 dmaeTimestamp = dmae_getTimestamp();
dmae_setRetiredTimestamp(dmaeTimestamp);
osLib_returnFromFunction64(hCPU, dmaeTimestamp);
}
void dmaeExport_DMAEWaitDone(PPCInterpreter_t* hCPU)
{
//debug_printf("DMAEWaitDone(...)\n");
// parameter:
// r3/r4 uint64 dmaeTimestamp
osLib_returnFromFunction(hCPU, 1);
}
void dmaeExport_DMAESemaphore(PPCInterpreter_t* hCPU)
{
// parameter:
// r3 MPTR addr
// r4 uint32 actionType
uint32 actionType = hCPU->gpr[4];
std::atomic<uint64le>* semaphore = _rawPtrToAtomic((uint64le*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]));
if( actionType == 1 )
{
// Signal Semaphore
semaphore->fetch_add(1);
}
else if (actionType == 0) // wait
{
forceLogDebug_printf("DMAESemaphore: Unsupported wait operation");
semaphore->fetch_sub(1);
}
else
{
forceLogDebug_printf("DMAESemaphore unknown action type %d", actionType);
cemu_assert_debug(false);
}
uint64 dmaeTimestamp = dmae_getTimestamp();
dmae_setRetiredTimestamp(dmaeTimestamp);
osLib_returnFromFunction64(hCPU, dmaeTimestamp);
}
void dmaeExport_DMAEGetRetiredTimeStamp(PPCInterpreter_t* hCPU)
{
debug_printf("DMAEGetRetiredTimeStamp()\n");
osLib_returnFromFunction64(hCPU, dmaeRetiredTimestamp);
}
void dmae_load()
{
osLib_addFunction("dmae", "DMAECopyMem", dmaeExport_DMAECopyMem);
osLib_addFunction("dmae", "DMAEFillMem", dmaeExport_DMAEFillMem);
osLib_addFunction("dmae", "DMAEWaitDone", dmaeExport_DMAEWaitDone);
osLib_addFunction("dmae", "DMAESemaphore", dmaeExport_DMAESemaphore);
osLib_addFunction("dmae", "DMAEGetRetiredTimeStamp", dmaeExport_DMAEGetRetiredTimeStamp);
}

View file

@ -0,0 +1 @@
void dmae_load();

View file

@ -0,0 +1,16 @@
#include "Cafe/OS/common/OSCommon.h"
#include "drmapp.h"
namespace drmapp
{
uint32 NupChkIsFinished(uint32 ukn)
{
forceLogDebug_printf("drmapp.NupChkIsFinished() - placeholder");
return 1;
}
void Initialize()
{
cafeExportRegisterFunc(NupChkIsFinished, "drmapp", "NupChkIsFinished__3RplFv", LogType::Placeholder);
}
}

View file

@ -0,0 +1,5 @@
namespace drmapp
{
void Initialize();
}

View file

@ -0,0 +1,396 @@
#include "Cafe/OS/common/OSCommon.h"
#include "erreula.h"
#include "Cafe/HW/Latte/Renderer/Renderer.h"
#include "util/helpers/helpers.h"
#include <imgui.h>
#include "imgui/imgui_extension.h"
#include <wx/msgdlg.h>
#include "Cafe/OS/libs/coreinit/coreinit_FS.h"
#include "Cafe/OS/libs/vpad/vpad.h"
namespace nn
{
namespace erreula
{
#define RESULTTYPE_NONE 0
#define RESULTTYPE_FINISH 1
#define RESULTTYPE_NEXT 2
#define RESULTTYPE_JUMP 3
#define RESULTTYPE_PASSWORD 4
#define ERRORTYPE_CODE 0
#define ERRORTYPE_TEXT 1
#define ERRORTYPE_TEXT_ONE_BUTTON 2
#define ERRORTYPE_TEXT_TWO_BUTTON 3
#define ERREULA_STATE_HIDDEN 0
#define ERREULA_STATE_APPEARING 1
#define ERREULA_STATE_VISIBLE 2
#define ERREULA_STATE_DISAPPEARING 3
struct AppearArg_t
{
AppearArg_t() = default;
AppearArg_t(const AppearArg_t& o)
{
errorType = o.errorType;
screenType = o.screenType;
controllerType = o.controllerType;
holdType = o.holdType;
errorCode = o.errorCode;
framerate = o.framerate;
text = o.text;
button1Text = o.button1Text;
button2Text = o.button2Text;
title = o.title;
drawCursor = o.drawCursor;
}
uint32be errorType;
uint32be screenType;
uint32be controllerType;
uint32be holdType;
uint32be errorCode;
uint32be framerate;
MEMPTR<uint16be> text;
MEMPTR<uint16be> button1Text;
MEMPTR<uint16be> button2Text;
MEMPTR<uint16be> title;
uint8 padding[3];
bool drawCursor{};
};
static_assert(sizeof(AppearArg_t) == 0x2C); // maybe larger
struct HomeNixSignArg_t
{
uint32be framerate;
};
static_assert(sizeof(HomeNixSignArg_t) == 0x4); // maybe larger
struct ControllerInfo_t
{
MEMPTR<VPADStatus_t> vpadStatus;
MEMPTR<KPADStatus_t> kpadStatus[4]; // or 7 now like KPAD_MAX_CONTROLLERS?
};
static_assert(sizeof(ControllerInfo_t) == 0x14); // maybe larger
struct ErrEula_t
{
coreinit::OSMutex mutex;
uint32 regionType;
uint32 langType;
MEMPTR<coreinit::FSClient_t> fsClient;
AppearArg_t currentDialog;
uint32 state;
bool buttonPressed;
bool rightButtonPressed;
bool homeNixSignVisible;
std::chrono::steady_clock::time_point stateTimer{};
} g_errEula = {};
std::wstring GetText(uint16be* text)
{
std::wstringstream result;
while(*text != 0)
{
auto c = (uint16)*text;
result << static_cast<wchar_t>(c);
text++;
}
return result.str();
}
void export_ErrEulaCreate(PPCInterpreter_t* hCPU)
{
ppcDefineParamMEMPTR(thisptr, uint8, 0);
ppcDefineParamU32(regionType, 1);
ppcDefineParamU32(langType, 2);
ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 3);
coreinit::OSLockMutex(&g_errEula.mutex);
g_errEula.regionType = regionType;
g_errEula.langType = langType;
g_errEula.fsClient = fsClient;
coreinit::OSUnlockMutex(&g_errEula.mutex);
osLib_returnFromFunction(hCPU, 0);
}
void export_AppearHomeNixSign(PPCInterpreter_t* hCPU)
{
g_errEula.homeNixSignVisible = TRUE;
osLib_returnFromFunction(hCPU, 0);
}
void export_AppearError(PPCInterpreter_t* hCPU)
{
ppcDefineParamMEMPTR(arg, AppearArg_t, 0);
g_errEula.currentDialog = *arg.GetPtr();
g_errEula.state = ERREULA_STATE_APPEARING;
g_errEula.buttonPressed = false;
g_errEula.rightButtonPressed = false;
g_errEula.stateTimer = tick_cached();
osLib_returnFromFunction(hCPU, 0);
}
void export_GetStateErrorViewer(PPCInterpreter_t* hCPU)
{
osLib_returnFromFunction(hCPU, g_errEula.state);
}
void export_DisappearError(PPCInterpreter_t* hCPU)
{
g_errEula.state = ERREULA_STATE_HIDDEN;
osLib_returnFromFunction(hCPU, 0);
}
void export_ChangeLang(PPCInterpreter_t* hCPU)
{
ppcDefineParamU32(langType, 0);
g_errEula.langType = langType;
osLib_returnFromFunction(hCPU, 0);
}
void export_IsDecideSelectButtonError(PPCInterpreter_t* hCPU)
{
if (g_errEula.buttonPressed)
forceLogDebug_printf("IsDecideSelectButtonError: TRUE");
osLib_returnFromFunction(hCPU, g_errEula.buttonPressed);
}
void export_IsDecideSelectLeftButtonError(PPCInterpreter_t* hCPU)
{
if (g_errEula.buttonPressed)
forceLogDebug_printf("IsDecideSelectLeftButtonError: TRUE");
osLib_returnFromFunction(hCPU, g_errEula.buttonPressed);
}
void export_IsDecideSelectRightButtonError(PPCInterpreter_t* hCPU)
{
if (g_errEula.rightButtonPressed)
forceLogDebug_printf("IsDecideSelectRightButtonError: TRUE");
osLib_returnFromFunction(hCPU, g_errEula.rightButtonPressed);
}
void export_IsAppearHomeNixSign(PPCInterpreter_t* hCPU)
{
osLib_returnFromFunction(hCPU, g_errEula.homeNixSignVisible);
}
void export_DisappearHomeNixSign(PPCInterpreter_t* hCPU)
{
g_errEula.homeNixSignVisible = FALSE;
osLib_returnFromFunction(hCPU, 0);
}
void export_GetResultType(PPCInterpreter_t* hCPU)
{
uint32 result = RESULTTYPE_NONE;
if (g_errEula.buttonPressed || g_errEula.rightButtonPressed)
{
forceLogDebug_printf("GetResultType: FINISH");
result = RESULTTYPE_FINISH;
}
osLib_returnFromFunction(hCPU, result);
}
void export_Calc(PPCInterpreter_t* hCPU)
{
ppcDefineParamMEMPTR(controllerInfo, ControllerInfo_t, 0);
// TODO: check controller buttons bla to accept dialog?
osLib_returnFromFunction(hCPU, 0);
}
void render(bool mainWindow)
{
if(g_errEula.state == ERREULA_STATE_HIDDEN)
return;
if(g_errEula.state == ERREULA_STATE_APPEARING)
{
if(std::chrono::duration_cast<std::chrono::milliseconds>(tick_cached() - g_errEula.stateTimer).count() <= 1000)
{
return;
}
g_errEula.state = ERREULA_STATE_VISIBLE;
g_errEula.stateTimer = tick_cached();
}
/*else if(g_errEula.state == STATE_VISIBLE)
{
if (std::chrono::duration_cast<std::chrono::milliseconds>(tick_cached() - g_errEula.stateTimer).count() >= 1000)
{
g_errEula.state = STATE_DISAPPEARING;
g_errEula.stateTimer = tick_cached();
return;
}
}*/
else if(g_errEula.state == ERREULA_STATE_DISAPPEARING)
{
if (std::chrono::duration_cast<std::chrono::milliseconds>(tick_cached() - g_errEula.stateTimer).count() >= 2000)
{
g_errEula.state = ERREULA_STATE_HIDDEN;
g_errEula.stateTimer = tick_cached();
}
return;
}
const AppearArg_t& appearArg = g_errEula.currentDialog;
std::string text;
const uint32 errorCode = (uint32)appearArg.errorCode;
if (errorCode != 0)
{
const uint32 errorCodeHigh = errorCode / 10000;
const uint32 errorCodeLow = errorCode % 10000;
text = fmt::format("Error-Code: {:03}-{:04}\n", errorCodeHigh, errorCodeLow);
}
auto font = ImGui_GetFont(32.0f);
if (!font)
return;
const auto kPopupFlags = ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings;
auto& io = ImGui::GetIO();
ImVec2 position = { io.DisplaySize.x / 2.0f, io.DisplaySize.y / 2.0f };
ImVec2 pivot = { 0.5f, 0.5f };
ImGui::SetNextWindowPos(position, ImGuiCond_Always, pivot);
ImGui::SetNextWindowBgAlpha(0.9f);
ImGui::PushFont(font);
std::string title = "ErrEula";
if (appearArg.title)
title = boost::nowide::narrow(GetText(appearArg.title.GetPtr()));
if (ImGui::Begin(title.c_str(), nullptr, kPopupFlags))
{
const float startx = ImGui::GetWindowSize().x / 2.0f;
switch ((uint32)appearArg.errorType)
{
default:
{
// TODO layout based on error code
ImGui::TextUnformatted(text.c_str(), text.c_str() + text.size());
ImGui::Spacing();
ImGui::SetCursorPosX(startx - 50);
g_errEula.buttonPressed |= ImGui::Button("OK", {100, 0});
break;
}
case ERRORTYPE_TEXT:
{
std::string txtTmp = "Unknown Error";
if (appearArg.text)
txtTmp = boost::nowide::narrow(GetText(appearArg.text.GetPtr()));
text += txtTmp;
ImGui::TextUnformatted(text.c_str(), text.c_str() + text.size());
ImGui::Spacing();
ImGui::SetCursorPosX(startx - 50);
g_errEula.buttonPressed |= ImGui::Button("OK", { 100, 0 });
break;
}
case ERRORTYPE_TEXT_ONE_BUTTON:
{
std::string txtTmp = "Unknown Error";
if (appearArg.text)
txtTmp = boost::nowide::narrow(GetText(appearArg.text.GetPtr()));
text += txtTmp;
ImGui::TextUnformatted(text.c_str(), text.c_str() + text.size());
ImGui::Spacing();
std::string button1 = "Yes";
if (appearArg.button1Text)
button1 = boost::nowide::narrow(GetText(appearArg.button1Text.GetPtr()));
float width = std::max(100.0f, ImGui::CalcTextSize(button1.c_str()).x + 10.0f);
ImGui::SetCursorPosX(startx - (width / 2.0f));
g_errEula.buttonPressed |= ImGui::Button(button1.c_str(), { width, 0 });
break;
}
case ERRORTYPE_TEXT_TWO_BUTTON:
{
std::string txtTmp = "Unknown Error";
if (appearArg.text)
txtTmp = boost::nowide::narrow(GetText(appearArg.text.GetPtr()));
text += txtTmp;
ImGui::TextUnformatted(text.c_str(), text.c_str() + text.size());
ImGui::Spacing();
std::string button1 = "Yes";
if (appearArg.button1Text)
button1 = boost::nowide::narrow(GetText(appearArg.button1Text.GetPtr()));
std::string button2 = "No";
if (appearArg.button2Text)
button2 = boost::nowide::narrow(GetText(appearArg.button2Text.GetPtr()));
float width1 = std::max(100.0f, ImGui::CalcTextSize(button1.c_str()).x + 10.0f);
float width2 = std::max(100.0f, ImGui::CalcTextSize(button2.c_str()).x + 10.0f);
ImGui::SetCursorPosX(startx - (width1 / 2.0f) - (width2 / 2.0f) - 10);
g_errEula.buttonPressed |= ImGui::Button(button1.c_str(), { width1, 0 });
ImGui::SameLine();
g_errEula.rightButtonPressed |= ImGui::Button(button2.c_str(), { width2, 0 });
break;
}
}
ImGui::End();
}
ImGui::PopFont();
if(g_errEula.buttonPressed || g_errEula.rightButtonPressed)
{
g_errEula.state = ERREULA_STATE_DISAPPEARING;
g_errEula.stateTimer = tick_cached();
}
}
void load()
{
OSInitMutexEx(&g_errEula.mutex, nullptr);
//osLib_addFunction("erreula", "ErrEulaCreate__3RplFPUcQ3_2nn7erreula10", export_ErrEulaCreate); // copy ctor?
osLib_addFunction("erreula", "ErrEulaCreate__3RplFPUcQ3_2nn7erreula10RegionTypeQ3_2nn7erreula8LangTypeP8FSClient", export_ErrEulaCreate);
osLib_addFunction("erreula", "ErrEulaAppearHomeNixSign__3RplFRCQ3_2nn7erreula14HomeNixSignArg", export_AppearHomeNixSign);
osLib_addFunction("erreula", "ErrEulaAppearError__3RplFRCQ3_2nn7erreula9AppearArg", export_AppearError);
osLib_addFunction("erreula", "ErrEulaGetStateErrorViewer__3RplFv", export_GetStateErrorViewer);
osLib_addFunction("erreula", "ErrEulaChangeLang__3RplFQ3_2nn7erreula8LangType", export_ChangeLang);
osLib_addFunction("erreula", "ErrEulaIsDecideSelectButtonError__3RplFv", export_IsDecideSelectButtonError);
osLib_addFunction("erreula", "ErrEulaCalc__3RplFRCQ3_2nn7erreula14ControllerInfo", export_Calc);
osLib_addFunction("erreula", "ErrEulaIsDecideSelectLeftButtonError__3RplFv", export_IsDecideSelectLeftButtonError);
osLib_addFunction("erreula", "ErrEulaIsDecideSelectRightButtonError__3RplFv", export_IsDecideSelectRightButtonError);
osLib_addFunction("erreula", "ErrEulaIsAppearHomeNixSign__3RplFv", export_IsAppearHomeNixSign);
osLib_addFunction("erreula", "ErrEulaDisappearHomeNixSign__3RplFv", export_DisappearHomeNixSign);
osLib_addFunction("erreula", "ErrEulaGetResultType__3RplFv", export_GetResultType);
osLib_addFunction("erreula", "ErrEulaDisappearError__3RplFv", export_DisappearError);
}
}
}

View file

@ -0,0 +1,12 @@
#pragma once
namespace nn
{
namespace erreula
{
void render(bool mainWindow);
void load();
}
}

View file

@ -0,0 +1,472 @@
#include "Cafe/OS/common/OSCommon.h"
#include "Cafe/HW/Latte/ISA/RegDefines.h"
#include "Cafe/HW/Espresso/PPCCallback.h"
#include "GX2.h"
#include "Cafe/HW/Latte/Core/Latte.h"
#include "Cafe/OS/libs/coreinit/coreinit_Time.h"
#include "Cafe/CafeSystem.h"
#include "Cafe/HW/Latte/Core/LattePM4.h"
#include "GX2_Command.h"
#include "GX2_State.h"
#include "GX2_Memory.h"
#include "GX2_Event.h"
#include "GX2_Shader.h"
#include "GX2_Blit.h"
#include "GX2_Draw.h"
#include "GX2_Query.h"
#include "GX2_Misc.h"
#include "GX2_Surface.h"
#include "GX2_Surface_Copy.h"
#include "GX2_Texture.h"
#define GX2_TV_RENDER_NONE 0
#define GX2_TV_RENDER_480 1
#define GX2_TV_RENDER_480_WIDE 2
#define GX2_TV_RENDER_720 3
#define GX2_TV_RENDER_720I 4
#define GX2_TV_RENDER_1080 5
#define GX2_TV_RENDER_COUNT 6
struct
{
sint32 width;
sint32 height;
}tvScanBufferResolutions[GX2_TV_RENDER_COUNT] = {
0,0,
640,480,
854,480,
1280,720,
1280,720,
1920,1080
};
uint64 lastSwapTime = 0;
void gx2Export_GX2SwapScanBuffers(PPCInterpreter_t* hCPU)
{
gx2Log_printf("GX2SwapScanBuffers()");
bool isPokken = false;
uint64 titleId = CafeSystem::GetForegroundTitleId();
if (titleId == 0x00050000101DF500ull || titleId == 0x00050000101C5800ull || titleId == 0x00050000101DF400ull)
isPokken = true;
if (isPokken)
GX2::GX2DrawDone();
GX2ReserveCmdSpace(5+2);
uint64 tick64 = PPCInterpreter_getMainCoreCycleCounter() / 20ULL;
lastSwapTime = tick64;
// count flip request
// is this updated via a PM4 MEM_WRITE operation?
// Orochi Warriors seems to call GX2SwapScanBuffers on arbitrary threads/cores. The PM4 commands should go through to the GPU as long as there is no active display list and no other core is submitting commands simultaneously
// right now, we work around this by avoiding the infinite loop below (request counter incremented, but PM4 not sent)
uint32 coreIndex = PPCInterpreter_getCoreIndex(ppcInterpreterCurrentInstance);
if (GX2::sGX2MainCoreIndex == coreIndex)
LatteGPUState.sharedArea->flipRequestCountBE = _swapEndianU32(_swapEndianU32(LatteGPUState.sharedArea->flipRequestCountBE) + 1);
gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_HLE_REQUEST_SWAP_BUFFERS, 1));
gx2WriteGather_submitU32AsBE(0); // reserved
// swap frames
gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_HLE_TRIGGER_SCANBUFFER_SWAP, 1));
gx2WriteGather_submitU32AsBE(0); // reserved
// wait for flip if the CPU is too far ahead
// doing it after swap request is how the actual console does it, but that still causes issues in Pokken
while ((sint32)(_swapEndianU32(LatteGPUState.sharedArea->flipRequestCountBE) - _swapEndianU32(LatteGPUState.sharedArea->flipExecuteCountBE)) > 5)
{
GX2::GX2WaitForFlip();
}
GX2::GX2WriteGather_checkAndInsertWrapAroundMark();
osLib_returnFromFunction(hCPU, 0);
}
void gx2Export_GX2CopyColorBufferToScanBuffer(PPCInterpreter_t* hCPU)
{
gx2Log_printf("GX2CopyColorBufferToScanBuffer(0x%08x,%d)\n", hCPU->gpr[3], hCPU->gpr[4]);
GX2ReserveCmdSpace(5);
// todo: proper implementation
// hack: Avoid running to far ahead of GPU. Normally this would be guaranteed by the circular buffer model, which we currently dont fully emulate
if(GX2::GX2WriteGather_getReadWriteDistance() > 32*1024*1024 )
{
debug_printf("Waiting for GPU to catch up...\n");
PPCInterpreter_relinquishTimeslice(); // release current thread
return;
}
GX2ColorBuffer* colorBuffer = (GX2ColorBuffer*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]);
gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_HLE_COPY_COLORBUFFER_TO_SCANBUFFER, 9));
gx2WriteGather_submitU32AsBE(memory_virtualToPhysical(colorBuffer->surface.imagePtr));
gx2WriteGather_submitU32AsBE((uint32)colorBuffer->surface.width);
gx2WriteGather_submitU32AsBE((uint32)colorBuffer->surface.height);
gx2WriteGather_submitU32AsBE((uint32)colorBuffer->surface.pitch);
gx2WriteGather_submitU32AsBE((uint32)colorBuffer->surface.tileMode.value());
gx2WriteGather_submitU32AsBE((uint32)colorBuffer->surface.swizzle);
gx2WriteGather_submitU32AsBE(_swapEndianU32(colorBuffer->viewFirstSlice));
gx2WriteGather_submitU32AsBE((uint32)colorBuffer->surface.format.value());
gx2WriteGather_submitU32AsBE(hCPU->gpr[4]);
osLib_returnFromFunction(hCPU, 0);
}
void gx2Export_GX2WaitForFreeScanBuffer(PPCInterpreter_t* hCPU)
{
// todo: proper implementation
debug_printf("GX2WaitForFreeScanBuffer(): Unimplemented\n");
osLib_returnFromFunction(hCPU, 0);
}
void gx2Export_GX2GetCurrentScanBuffer(PPCInterpreter_t* hCPU)
{
// todo: proper implementation
uint32 scanTarget = hCPU->gpr[3];
GX2ColorBuffer* colorBufferBE = (GX2ColorBuffer*)memory_getPointerFromVirtualOffset(hCPU->gpr[4]);
memset(colorBufferBE, 0x00, sizeof(GX2ColorBuffer));
colorBufferBE->surface.width = 100;
colorBufferBE->surface.height = 100;
// note: For now we abuse the tiling aperture memory area as framebuffer pointers
if( scanTarget == GX2_SCAN_TARGET_TV )
{
colorBufferBE->surface.imagePtr = MEMORY_TILINGAPERTURE_AREA_ADDR+0x200000;
}
else if( scanTarget == GX2_SCAN_TARGET_DRC_FIRST )
{
colorBufferBE->surface.imagePtr = MEMORY_TILINGAPERTURE_AREA_ADDR+0x40000;
}
osLib_returnFromFunction(hCPU, 0);
}
void coreinitExport_GX2GetSystemTVScanMode(PPCInterpreter_t* hCPU)
{
// 1080p = 7
osLib_returnFromFunction(hCPU, 7);
}
void coreinitExport_GX2GetSystemTVAspectRatio(PPCInterpreter_t* hCPU)
{
osLib_returnFromFunction(hCPU, 1); // 16:9
}
void gx2Export_GX2TempGetGPUVersion(PPCInterpreter_t* hCPU)
{
osLib_returnFromFunction(hCPU, 2);
}
void _GX2InitScanBuffer(GX2ColorBuffer* colorBuffer, sint32 width, sint32 height, Latte::E_GX2SURFFMT format)
{
colorBuffer->surface.resFlag = GX2_RESFLAG_USAGE_TEXTURE | GX2_RESFLAG_USAGE_COLOR_BUFFER;
colorBuffer->surface.width = width;
colorBuffer->surface.height = height;
colorBuffer->viewFirstSlice = _swapEndianU32(0);
colorBuffer->viewNumSlices = _swapEndianU32(1);
colorBuffer->viewMip = _swapEndianU32(0);
colorBuffer->surface.numLevels = 1;
colorBuffer->surface.dim = Latte::E_DIM::DIM_2D;
colorBuffer->surface.swizzle = 0;
colorBuffer->surface.depth = 1;
colorBuffer->surface.tileMode = Latte::E_GX2TILEMODE::TM_LINEAR_GENERAL;
colorBuffer->surface.format = format;
colorBuffer->surface.mipPtr = MPTR_NULL;
colorBuffer->surface.aa = 0;
GX2::GX2CalcSurfaceSizeAndAlignment(&colorBuffer->surface);
colorBuffer->surface.resFlag = GX2_RESFLAG_USAGE_TEXTURE | GX2_RESFLAG_USAGE_COLOR_BUFFER | GX2_RESFLAG_USAGE_SCAN_BUFFER;
}
void gx2Export_GX2CalcTVSize(PPCInterpreter_t* hCPU)
{
uint32 tvRenderMode = hCPU->gpr[3];
Latte::E_GX2SURFFMT format = (Latte::E_GX2SURFFMT)hCPU->gpr[4];
uint32 bufferingMode = hCPU->gpr[5];
uint32 outputSizeMPTR = hCPU->gpr[6];
uint32 outputScaleNeededMPTR = hCPU->gpr[7];
cemu_assert(tvRenderMode < GX2_TV_RENDER_COUNT);
uint32 width = tvScanBufferResolutions[tvRenderMode].width;
uint32 height = tvScanBufferResolutions[tvRenderMode].height;
GX2ColorBuffer colorBuffer;
memset(&colorBuffer, 0, sizeof(GX2ColorBuffer));
_GX2InitScanBuffer(&colorBuffer, width, height, format);
uint32 imageSize = colorBuffer.surface.imageSize;
uint32 alignment = colorBuffer.surface.alignment;
uint32 alignmentPaddingSize = (alignment - (imageSize%alignment)) % alignment;
uint32 uknMult = 1; // probably for interlaced?
if (tvRenderMode == GX2_TV_RENDER_720I)
uknMult = 2;
uint32 adjustedBufferingMode = bufferingMode;
if (tvRenderMode < GX2_TV_RENDER_720)
adjustedBufferingMode = 4;
uint32 bufferedImageSize = (imageSize + alignmentPaddingSize) * adjustedBufferingMode;
bufferedImageSize = bufferedImageSize * uknMult - alignmentPaddingSize;
memory_writeU32(outputSizeMPTR, bufferedImageSize);
memory_writeU32(outputScaleNeededMPTR, 0); // todo
osLib_returnFromFunction(hCPU, 0);
}
void gx2Export_GX2CalcDRCSize(PPCInterpreter_t* hCPU)
{
ppcDefineParamS32(drcMode, 0);
ppcDefineParamU32(format, 1);
ppcDefineParamU32(bufferingMode, 2);
ppcDefineParamMPTR(sizeMPTR, 3);
ppcDefineParamMPTR(scaleNeededMPTR, 4);
uint32 width = 0;
uint32 height = 0;
if (drcMode > 0)
{
width = 854;
height = 480;
}
GX2ColorBuffer colorBuffer = {};
memset(&colorBuffer, 0, sizeof(colorBuffer));
_GX2InitScanBuffer(&colorBuffer, width, height, (Latte::E_GX2SURFFMT)format);
uint32 imageSize = colorBuffer.surface.imageSize;
uint32 alignment = colorBuffer.surface.alignment;
uint32 alignmentPaddingSize = (alignment - (imageSize%alignment)) % alignment;
uint32 adjustedBufferingMode = bufferingMode;
uint32 bufferedImageSize = (imageSize + alignmentPaddingSize) * adjustedBufferingMode;
bufferedImageSize = bufferedImageSize - alignmentPaddingSize;
memory_writeU32(sizeMPTR, bufferedImageSize);
memory_writeU32(scaleNeededMPTR, 0);
osLib_returnFromFunction(hCPU, 0);
}
void gx2Export_GX2SetDRCScale(PPCInterpreter_t* hCPU)
{
gx2Log_printf("GX2SetDRCScale(%d,%d)", hCPU->gpr[3], hCPU->gpr[4]);
osLib_returnFromFunction(hCPU, 0);
}
void gx2Export_GX2SetDRCConnectCallback(PPCInterpreter_t* hCPU)
{
ppcDefineParamS32(channel, 0);
ppcDefineParamMEMPTR(callback, void, 1);
gx2Log_printf("GX2SetDRCConnectCallback(%d, 0x%08x)", channel, callback.GetMPTR());
if(callback.GetPtr())
PPCCoreCallback(callback, channel, TRUE);
osLib_returnFromFunction(hCPU, 0);
}
void gx2Export_GX2SetSemaphore(PPCInterpreter_t* hCPU)
{
gx2Log_printf("GX2SetSemaphore(0x%08x,%d)", hCPU->gpr[3], hCPU->gpr[4]);
ppcDefineParamMPTR(semaphoreMPTR, 0);
ppcDefineParamS32(mode, 1);
uint32 SEM_SEL;
if (mode == 0)
{
// wait
SEM_SEL = 7;
}
else if (mode == 1)
{
// signal
SEM_SEL = 6;
}
else
{
cemu_assert_debug(false);
osLib_returnFromFunction(hCPU, 0);
return;
}
uint32 semaphoreControl = (SEM_SEL << 29);
semaphoreControl |= 0x1000; // WAIT_ON_SIGNAL
gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_MEM_SEMAPHORE, 2));
gx2WriteGather_submitU32AsBE(memory_virtualToPhysical(semaphoreMPTR)); // semaphore physical address
gx2WriteGather_submitU32AsBE(semaphoreControl); // control
osLib_returnFromFunction(hCPU, 0);
}
void gx2Export_GX2Flush(PPCInterpreter_t* hCPU)
{
gx2Log_printf("GX2Flush()");
_GX2SubmitToTCL();
osLib_returnFromFunction(hCPU, 0);
}
uint8* _GX2LastFlushPtr[PPC_CORE_COUNT] = {NULL};
uint64 _prevReturnedGPUTime = 0;
uint64 Latte_GetTime()
{
uint64 gpuTime = coreinit::coreinit_getTimerTick();
gpuTime *= 20000ULL;
if (gpuTime <= _prevReturnedGPUTime)
gpuTime = _prevReturnedGPUTime + 1; // avoid ever returning identical timestamps
_prevReturnedGPUTime = gpuTime;
return gpuTime;
}
void _GX2SubmitToTCL()
{
uint32 coreIndex = PPCInterpreter_getCoreIndex(ppcInterpreterCurrentInstance);
// do nothing if called from non-main GX2 core
if (GX2::sGX2MainCoreIndex != coreIndex)
{
forceLogDebug_printf("_GX2SubmitToTCL() called on non-main GX2 core");
return;
}
if( gx2WriteGatherPipe.displayListStart[coreIndex] != MPTR_NULL )
return; // quit if in display list
_GX2LastFlushPtr[coreIndex] = (gx2WriteGatherPipe.writeGatherPtrGxBuffer[coreIndex]);
// update last submitted CB timestamp
uint64 commandBufferTimestamp = Latte_GetTime();
LatteGPUState.lastSubmittedCommandBufferTimestamp.store(commandBufferTimestamp);
gx2Log_printf("Submitting GX2 command buffer with timestamp %016I64x", commandBufferTimestamp);
// submit HLE packet to write retirement timestamp
gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_HLE_SET_CB_RETIREMENT_TIMESTAMP, 2));
gx2WriteGather_submitU32AsBE((uint32)(commandBufferTimestamp>>32ULL));
gx2WriteGather_submitU32AsBE((uint32)(commandBufferTimestamp&0xFFFFFFFFULL));
}
uint32 _GX2GetUnflushedBytes(uint32 coreIndex)
{
uint32 unflushedBytes = 0;
if (_GX2LastFlushPtr[coreIndex] != NULL)
{
if (_GX2LastFlushPtr[coreIndex] > gx2WriteGatherPipe.writeGatherPtrGxBuffer[coreIndex])
unflushedBytes = (uint32)(gx2WriteGatherPipe.writeGatherPtrGxBuffer[coreIndex] - gx2WriteGatherPipe.gxRingBuffer + 4); // this isn't 100% correct since we ignore the bytes between the last flush address and the start of the wrap around
else
unflushedBytes = (uint32)(gx2WriteGatherPipe.writeGatherPtrGxBuffer[coreIndex] - _GX2LastFlushPtr[coreIndex]);
}
else
unflushedBytes = (uint32)(gx2WriteGatherPipe.writeGatherPtrGxBuffer[coreIndex] - gx2WriteGatherPipe.gxRingBuffer);
return unflushedBytes;
}
/*
* Guarantees that the requested amount of space is available on the current command buffer
* If the space is not available, the current command buffer is pushed to the GPU and a new one is allocated
*/
void GX2ReserveCmdSpace(uint32 reservedFreeSpaceInU32)
{
uint32 coreIndex = PPCInterpreter_getCoreIndex(ppcInterpreterCurrentInstance);
// if we are in a display list then do nothing
if( gx2WriteGatherPipe.displayListStart[coreIndex] != MPTR_NULL )
return;
uint32 unflushedBytes = _GX2GetUnflushedBytes(coreIndex);
if( unflushedBytes >= 0x1000 )
{
_GX2SubmitToTCL();
}
}
void gx2_load()
{
osLib_addFunction("gx2", "GX2GetContextStateDisplayList", gx2Export_GX2GetContextStateDisplayList);
// swap, vsync & timing
osLib_addFunction("gx2", "GX2SwapScanBuffers", gx2Export_GX2SwapScanBuffers);
osLib_addFunction("gx2", "GX2GetSwapStatus", gx2Export_GX2GetSwapStatus);
osLib_addFunction("gx2", "GX2CopyColorBufferToScanBuffer", gx2Export_GX2CopyColorBufferToScanBuffer);
osLib_addFunction("gx2", "GX2WaitForFreeScanBuffer", gx2Export_GX2WaitForFreeScanBuffer);
osLib_addFunction("gx2", "GX2GetCurrentScanBuffer", gx2Export_GX2GetCurrentScanBuffer);
// shader stuff
osLib_addFunction("gx2", "GX2GetVertexShaderGPRs", gx2Export_GX2GetVertexShaderGPRs);
osLib_addFunction("gx2", "GX2GetVertexShaderStackEntries", gx2Export_GX2GetVertexShaderStackEntries);
osLib_addFunction("gx2", "GX2GetPixelShaderGPRs", gx2Export_GX2GetPixelShaderGPRs);
osLib_addFunction("gx2", "GX2GetPixelShaderStackEntries", gx2Export_GX2GetPixelShaderStackEntries);
osLib_addFunction("gx2", "GX2SetFetchShader", gx2Export_GX2SetFetchShader);
osLib_addFunction("gx2", "GX2SetVertexShader", gx2Export_GX2SetVertexShader);
osLib_addFunction("gx2", "GX2SetPixelShader", gx2Export_GX2SetPixelShader);
osLib_addFunction("gx2", "GX2SetGeometryShader", gx2Export_GX2SetGeometryShader);
osLib_addFunction("gx2", "GX2SetComputeShader", gx2Export_GX2SetComputeShader);
osLib_addFunction("gx2", "GX2SetVertexUniformReg", gx2Export_GX2SetVertexUniformReg);
osLib_addFunction("gx2", "GX2SetVertexUniformBlock", gx2Export_GX2SetVertexUniformBlock);
osLib_addFunction("gx2", "GX2RSetVertexUniformBlock", gx2Export_GX2RSetVertexUniformBlock);
osLib_addFunction("gx2", "GX2SetPixelUniformBlock", gx2Export_GX2SetPixelUniformBlock);
osLib_addFunction("gx2", "GX2SetPixelUniformReg", gx2Export_GX2SetPixelUniformReg);
osLib_addFunction("gx2", "GX2SetGeometryUniformBlock", gx2Export_GX2SetGeometryUniformBlock);
osLib_addFunction("gx2", "GX2SetShaderModeEx", gx2Export_GX2SetShaderModeEx);
osLib_addFunction("gx2", "GX2CalcGeometryShaderInputRingBufferSize", gx2Export_GX2CalcGeometryShaderInputRingBufferSize);
osLib_addFunction("gx2", "GX2CalcGeometryShaderOutputRingBufferSize", gx2Export_GX2CalcGeometryShaderOutputRingBufferSize);
// color/depth buffers
osLib_addFunction("gx2", "GX2InitColorBufferRegs", gx2Export_GX2InitColorBufferRegs);
osLib_addFunction("gx2", "GX2InitDepthBufferRegs", gx2Export_GX2InitDepthBufferRegs);
osLib_addFunction("gx2", "GX2SetColorBuffer", gx2Export_GX2SetColorBuffer);
osLib_addFunction("gx2", "GX2SetDepthBuffer", gx2Export_GX2SetDepthBuffer);
osLib_addFunction("gx2", "GX2SetDRCBuffer", gx2Export_GX2SetDRCBuffer);
osLib_addFunction("gx2", "GX2MarkScanBufferCopied", gx2Export_GX2MarkScanBufferCopied);
// misc
osLib_addFunction("gx2", "GX2TempGetGPUVersion", gx2Export_GX2TempGetGPUVersion);
osLib_addFunction("gx2", "GX2CalcTVSize", gx2Export_GX2CalcTVSize);
osLib_addFunction("gx2", "GX2CalcDRCSize", gx2Export_GX2CalcDRCSize);
osLib_addFunction("gx2", "GX2SetDRCScale", gx2Export_GX2SetDRCScale);
osLib_addFunction("gx2", "GX2SetDRCConnectCallback", gx2Export_GX2SetDRCConnectCallback);
osLib_addFunction("gx2", "GX2GetSystemTVScanMode", coreinitExport_GX2GetSystemTVScanMode);
osLib_addFunction("gx2", "GX2GetSystemTVAspectRatio", coreinitExport_GX2GetSystemTVAspectRatio);
osLib_addFunction("gx2", "GX2SetSwapInterval", gx2Export_GX2SetSwapInterval);
osLib_addFunction("gx2", "GX2GetSwapInterval", gx2Export_GX2GetSwapInterval);
osLib_addFunction("gx2", "GX2GetGPUTimeout", gx2Export_GX2GetGPUTimeout);
osLib_addFunction("gx2", "GX2SampleTopGPUCycle", gx2Export_GX2SampleTopGPUCycle);
osLib_addFunction("gx2", "GX2SampleBottomGPUCycle", gx2Export_GX2SampleBottomGPUCycle);
osLib_addFunction("gx2", "GX2AllocateTilingApertureEx", gx2Export_GX2AllocateTilingApertureEx);
osLib_addFunction("gx2", "GX2FreeTilingAperture", gx2Export_GX2FreeTilingAperture);
// context state
osLib_addFunction("gx2", "GX2SetDefaultState", gx2Export_GX2SetDefaultState);
osLib_addFunction("gx2", "GX2SetupContextStateEx", gx2Export_GX2SetupContextStateEx);
osLib_addFunction("gx2", "GX2SetContextState", gx2Export_GX2SetContextState);
// semaphore
osLib_addFunction("gx2", "GX2SetSemaphore", gx2Export_GX2SetSemaphore);
// command buffer
osLib_addFunction("gx2", "GX2Flush", gx2Export_GX2Flush);
GX2::GX2Init_writeGather();
GX2::GX2MemInit();
GX2::GX2ResourceInit();
GX2::GX2CommandInit();
GX2::GX2SurfaceInit();
GX2::GX2SurfaceCopyInit();
GX2::GX2TextureInit();
GX2::GX2StateInit();
GX2::GX2ShaderInit();
GX2::GX2EventInit();
GX2::GX2BlitInit();
GX2::GX2DrawInit();
GX2::GX2StreamoutInit();
GX2::GX2QueryInit();
GX2::GX2MiscInit();
}

View file

@ -0,0 +1,89 @@
#pragma once
#include "Cafe/HW/Latte/Core/LatteConst.h"
// base defines for GX2
#define GX2_TRUE 1
#define GX2_FALSE 0
#define GX2_ENABLE 1
#define GX2_DISABLE 0
// tex unit base for render backends
#define CEMU_PS_TEX_UNIT_BASE 0
#define CEMU_VS_TEX_UNIT_BASE 32
#define CEMU_GS_TEX_UNIT_BASE 64
#include "GX2_Surface.h"
// general
void gx2_load();
// shader
void gx2Export_GX2SetFetchShader(PPCInterpreter_t* hCPU);
void gx2Export_GX2GetVertexShaderGPRs(PPCInterpreter_t* hCPU);
void gx2Export_GX2GetVertexShaderStackEntries(PPCInterpreter_t* hCPU);
void gx2Export_GX2GetPixelShaderGPRs(PPCInterpreter_t* hCPU);
void gx2Export_GX2GetPixelShaderStackEntries(PPCInterpreter_t* hCPU);
void gx2Export_GX2SetVertexShader(PPCInterpreter_t* hCPU);
void gx2Export_GX2SetPixelShader(PPCInterpreter_t* hCPU);
void gx2Export_GX2SetGeometryShader(PPCInterpreter_t* hCPU);
void gx2Export_GX2SetComputeShader(PPCInterpreter_t* hCPU);
void gx2Export_GX2SetVertexUniformReg(PPCInterpreter_t* hCPU);
void gx2Export_GX2SetVertexUniformBlock(PPCInterpreter_t* hCPU);
void gx2Export_GX2RSetVertexUniformBlock(PPCInterpreter_t* hCPU);
void gx2Export_GX2SetPixelUniformBlock(PPCInterpreter_t* hCPU);
void gx2Export_GX2SetPixelUniformReg(PPCInterpreter_t* hCPU);
void gx2Export_GX2SetGeometryUniformBlock(PPCInterpreter_t* hCPU);
void gx2Export_GX2SetShaderModeEx(PPCInterpreter_t* hCPU);
void gx2Export_GX2CalcGeometryShaderInputRingBufferSize(PPCInterpreter_t* hCPU);
void gx2Export_GX2CalcGeometryShaderOutputRingBufferSize(PPCInterpreter_t* hCPU);
// write gather / command queue
#define GX2_COMMAND_RING_BUFFER_SIZE (64*1024*1024) // 64MB
void gx2Export_GX2GetContextStateDisplayList(PPCInterpreter_t* hCPU);
#include "GX2_Command.h"
// misc
void gx2Export_GX2AllocateTilingApertureEx(PPCInterpreter_t* hCPU);
void gx2Export_GX2FreeTilingAperture(PPCInterpreter_t* hCPU);
void gx2Export_GX2SetSwapInterval(PPCInterpreter_t* hCPU);
void gx2Export_GX2GetSwapInterval(PPCInterpreter_t* hCPU);
void gx2Export_GX2GetSwapStatus(PPCInterpreter_t* hCPU);
void gx2Export_GX2GetGPUTimeout(PPCInterpreter_t* hCPU);
void gx2Export_GX2SampleTopGPUCycle(PPCInterpreter_t* hCPU);
void gx2Export_GX2SampleBottomGPUCycle(PPCInterpreter_t* hCPU);
// color/depth buffers
#define GX2_SCAN_TARGET_TV 1
#define GX2_SCAN_TARGET_TV_RIGH 2
#define GX2_SCAN_TARGET_DRC_FIRST 4
#define GX2_SCAN_TARGET_DRC_SECOND 8
void gx2Export_GX2InitColorBufferRegs(PPCInterpreter_t* hCPU);
void gx2Export_GX2InitDepthBufferRegs(PPCInterpreter_t* hCPU);
void gx2Export_GX2SetColorBuffer(PPCInterpreter_t* hCPU);
void gx2Export_GX2SetDepthBuffer(PPCInterpreter_t* hCPU);
void gx2Export_GX2SetDRCBuffer(PPCInterpreter_t* hCPU);
void gx2Export_GX2MarkScanBufferCopied(PPCInterpreter_t* hCPU);
// special state
#define GX2_SPECIAL_STATE_COUNT 9
// context state
void gx2Export_GX2SetDefaultState(PPCInterpreter_t* hCPU);
void gx2Export_GX2SetupContextStateEx(PPCInterpreter_t* hCPU);
void gx2Export_GX2SetContextState(PPCInterpreter_t* hCPU);
// command buffer
uint32 _GX2GetUnflushedBytes(uint32 coreIndex);
void _GX2SubmitToTCL();
void GX2ReserveCmdSpace(uint32 reservedFreeSpaceInU32);

View file

@ -0,0 +1,422 @@
#include "Cafe/OS/libs/coreinit/coreinit_DynLoad.h"
#include "Cafe/HW/Espresso/PPCCallback.h"
#include "Cafe/HW/Espresso/PPCState.h"
#include "Cafe/OS/common/OSCommon.h"
#include "Cafe/HW/Latte/ISA/LatteReg.h"
#include "Cafe/HW/Latte/Core/Latte.h"
#include "Cafe/HW/Latte/LatteAddrLib/LatteAddrLib.h"
#include "util/highresolutiontimer/HighResolutionTimer.h"
namespace GX2
{
struct AddrCreate_INPUT
{
/* +0x00 */ uint32be structSize;
/* +0x04 */ uint32be ukn04_maybeGen;
/* +0x08 */ uint32be ukn08;
/* +0x0C */ uint32be revision;
/* +0x10 */ uint32be func_Alloc;
/* +0x14 */ uint32be func_Free;
/* +0x18 */ uint32be func_Debug;
/* +0x1C */ uint32be ukn1C;
/* +0x20 */ uint32be reg263C;
/* +0x24 */ uint32be ukn24;
/* +0x28 */ uint32be ukn28;
/* +0x2C */ uint32be ukn2C;
/* +0x30 */ uint32be ukn30;
/* +0x34 */ uint32be ukn34;
/* +0x38 */ uint32be ukn38;
/* +0x3C */ uint32be ukn3C;
/* +0x40 */ uint32be ukn40;
};
struct AddrCreate_OUTPUT
{
uint32be structSize;
MEMPTR<void> addrLibPtr;
};
static_assert(sizeof(AddrCreate_INPUT) == 0x44);
static_assert(sizeof(AddrCreate_OUTPUT) == 8);
struct ADDRAllocParam
{
uint32be ukn00; // alignment?
uint32be ukn04;
uint32be size;
};
struct ADDRComputeSurfaceInfo_INPUT
{
uint32be structSize;
betype<Latte::E_HWTILEMODE> tileMode;
betype<Latte::E_HWSURFFMT> format;
uint32be bpp;
uint32be numSamples;
uint32be width;
uint32be height;
uint32be numSlices;
uint32be slice;
uint32be mipLevel;
uint32be _flags;
uint32be numFrags;
MEMPTR<void> tileInfo;
uint32be tileType;
uint32be tileIndex;
enum FLAG_BITS
{
FLAG_BIT_CUBE = (1 << 27),
FLAG_BIT_VOLUME = (1 << 26),
FLAG_BIT_OPT4SPACE = (1 << 19),
};
void SetFlagCube(bool f)
{
if (f) _flags |= FLAG_BIT_CUBE;
else _flags &= ~FLAG_BIT_CUBE;
}
void SetFlagVolume(bool f)
{
if (f) _flags |= FLAG_BIT_VOLUME;
else _flags &= ~FLAG_BIT_VOLUME;
}
void SetFlagOpt4Space(bool f)
{
if (f) _flags |= FLAG_BIT_OPT4SPACE;
else _flags &= ~FLAG_BIT_OPT4SPACE;
}
};
static_assert(sizeof(ADDRComputeSurfaceInfo_INPUT) == 0x3C);
struct ADDRComputeSurfaceInfo_OUTPUT
{
/* 0x00 */ uint32be structSize;
/* 0x04 */ uint32be pitch;
/* 0x08 */ uint32be height;
/* 0x0C */ uint32be depth;
/* 0x10 */ uint64be surfSize;
/* 0x18 */ uint32be tileMode;
/* 0x1C */ uint32be baseAlign;
/* 0x20 */ uint32be pitchAlign;
/* 0x24 */ uint32be heightAlign;
/* 0x28 */ uint32be depthAlign;
/* 0x2C */ uint32be bpp;
/* 0x30 */ uint32be pixelPitch;
/* 0x34 */ uint32be pixelHeight;
/* 0x38 */ uint32be pixelBits;
/* 0x3C */ uint32be sliceSize;
/* 0x40 */ uint32be pitchTileMax;
/* 0x44 */ uint32be heightTileMax;
/* 0x48 */ uint32be sliceTileMax;
/* 0x4C */ MEMPTR<void> tileInfo;
/* 0x50 */ uint32be tileType;
/* 0x54 */ uint32be tileIndex;
/* 0x58 */ MEMPTR<void> stereoInfo;
/* 0x5C */ uint32be _padding;
};
static_assert(sizeof(ADDRComputeSurfaceInfo_OUTPUT) == 0x60);
static void _cb_alloc(PPCInterpreter_t* hCPU)
{
ppcDefineParamStructPtr(param, ADDRAllocParam, 0);
uint32 r = coreinit_allocFromSysArea(param->size, 0x10);
osLib_returnFromFunction(hCPU, r);
}
static void _cb_free(PPCInterpreter_t* hCPU)
{
cemu_assert_unimplemented();
}
static void _cb_debug(PPCInterpreter_t* hCPU)
{
cemu_assert_unimplemented();
}
static void* sAddrLib{};
static uint32be tclFunc_AddrCreate = 0;
static uint32be tclFunc_AddrComputeSurfaceInfo = 0;
void _TestAddrLib_Init()
{
// load tcl_addr_test.rpl (from /cafelibs/)
uint32be tclHandle;
uint32 r = coreinit::OSDynLoad_Acquire("tcl_addr_test.rpl", &tclHandle);
cemu_assert_debug(r == 0);
// get imports
r = coreinit::OSDynLoad_FindExport(tclHandle, 0, "AddrCreate", &tclFunc_AddrCreate);
cemu_assert_debug(r == 0);
r = coreinit::OSDynLoad_FindExport(tclHandle, 0, "AddrComputeSurfaceInfo", &tclFunc_AddrComputeSurfaceInfo);
cemu_assert_debug(r == 0);
// call AddrCreate
StackAllocator<AddrCreate_INPUT> addrCreateIn;
memset(addrCreateIn.GetPointer(), 0, sizeof(addrCreateIn));
addrCreateIn->structSize = sizeof(addrCreateIn);
addrCreateIn->ukn04_maybeGen = 6; // R600?
addrCreateIn->ukn08 = 0x51;
addrCreateIn->revision = 71;
addrCreateIn->reg263C = 0x44902;
addrCreateIn->ukn24 = 0; // ukn
addrCreateIn->func_Alloc = PPCInterpreter_makeCallableExportDepr(_cb_alloc);
addrCreateIn->func_Free = PPCInterpreter_makeCallableExportDepr(_cb_free);
addrCreateIn->func_Debug = PPCInterpreter_makeCallableExportDepr(_cb_debug);
StackAllocator<AddrCreate_OUTPUT> addrCreateOut;
memset(addrCreateOut.GetPointer(), 0, sizeof(addrCreateOut));
addrCreateOut->structSize = sizeof(addrCreateOut);
r = PPCCoreCallback((uint32)tclFunc_AddrCreate, addrCreateIn.GetPointer(), addrCreateOut.GetPointer());
sAddrLib = addrCreateOut->addrLibPtr;
cemu_assert_debug(r == 0 && sAddrLib != nullptr);
}
void _TestAddrLib_CalculateSurfaceInfo(Latte::E_GX2SURFFMT surfaceFormat, uint32 surfaceWidth, uint32 surfaceHeight, uint32 surfaceDepth, Latte::E_DIM surfaceDim, Latte::E_GX2TILEMODE surfaceTileMode, uint32 surfaceAA, uint32 level, ADDRComputeSurfaceInfo_OUTPUT* paramOut)
{
StackAllocator<ADDRComputeSurfaceInfo_INPUT> _paramIn;
ADDRComputeSurfaceInfo_INPUT& paramIn = *_paramIn.GetPointer();
memset(&paramIn, 0, sizeof(ADDRComputeSurfaceInfo_INPUT));
memset(paramOut, 0, sizeof(ADDRComputeSurfaceInfo_OUTPUT));
Latte::E_HWSURFFMT hwFormat = GetHWFormat(surfaceFormat);
if (surfaceTileMode == Latte::E_GX2TILEMODE::TM_LINEAR_SPECIAL)
{
uint32 numSamples = 1 << surfaceAA;
uint32 blockSize = IsCompressedFormat(surfaceFormat) ? 4 : 1;
uint32 width = ((surfaceWidth >> level) + blockSize - 1) & ~(blockSize - 1);
paramOut->bpp = GetFormatBits(hwFormat);
paramOut->structSize = sizeof(ADDRComputeSurfaceInfo_OUTPUT);
paramOut->pitch = width / blockSize;
paramOut->pixelBits = paramOut->bpp;
paramOut->baseAlign = 1;
paramOut->pitchAlign = 1;
paramOut->heightAlign = 1;
paramOut->depthAlign = 1;
switch (surfaceDim)
{
case Latte::E_DIM::DIM_1D:
paramOut->height = 1;
paramOut->depth = 1;
break;
case Latte::E_DIM::DIM_2D:
paramOut->height = std::max<uint32>(surfaceHeight >> level, 1);
paramOut->depth = 1;
break;
case Latte::E_DIM::DIM_3D:
paramOut->height = surfaceHeight >> level;
paramOut->height = std::max<uint32>(paramOut->height, 1);
paramOut->depth = std::max<uint32>(surfaceDepth >> level, 1);
break;
case Latte::E_DIM::DIM_CUBEMAP:
paramOut->height = std::max<uint32>(surfaceHeight >> level, 1);
paramOut->depth = std::max<uint32>(surfaceDepth, 6);
break;
case Latte::E_DIM::DIM_1D_ARRAY:
paramOut->height = 1;
paramOut->depth = surfaceDepth;
break;
case Latte::E_DIM::DIM_2D_ARRAY:
paramOut->height = std::max<uint32>(surfaceHeight >> level, 1);
paramOut->depth = surfaceDepth;
break;
default:
break;
}
paramOut->height = ((paramOut->height + blockSize - 1) & ~(blockSize - 1)) / (uint64)blockSize;
paramOut->pixelPitch = ((surfaceWidth >> level) + blockSize - 1) & ~(blockSize - 1);
paramOut->pixelPitch = std::max<uint32>(paramOut->pixelPitch, blockSize);
paramOut->pixelHeight = ((surfaceHeight >> level) + blockSize - 1) & ~(blockSize - 1);
paramOut->pixelHeight = std::max<uint32>(paramOut->pixelHeight, blockSize);;
paramOut->pitch = std::max<uint32>(paramOut->pitch, 1);
paramOut->height = std::max<uint32>(paramOut->height, 1);
paramOut->surfSize = paramOut->bpp * numSamples * paramOut->depth * paramOut->height * paramOut->pitch >> 3;
if (surfaceDim == Latte::E_DIM::DIM_3D)
paramOut->sliceSize = (uint32)(paramOut->surfSize);
else
{
if (paramOut->surfSize == 0 && paramOut->depth == 0)
paramOut->sliceSize = 0; // edge case for (1D)_ARRAY textures with 0/0/0 res
else
paramOut->sliceSize = ((uint32)paramOut->surfSize.value() / paramOut->depth);
}
paramOut->pitchTileMax = (paramOut->pitch >> 3) - 1;
paramOut->heightTileMax = (paramOut->height >> 3) - 1;
paramOut->sliceTileMax = (paramOut->height * paramOut->pitch >> 6) - 1;
}
else
{
paramIn.structSize = sizeof(paramIn);
paramIn.tileMode = Latte::MakeHWTileMode(surfaceTileMode);
paramIn.format = hwFormat;
paramIn.bpp = GetFormatBits(hwFormat);
paramIn.numSamples = 1 << surfaceAA;
paramIn.numFrags = paramIn.numSamples;
paramIn.width = std::max<uint32>(surfaceWidth >> level, 1);
switch (surfaceDim)
{
case Latte::E_DIM::DIM_1D:
paramIn.height = 1;
paramIn.numSlices = 1;
break;
case Latte::E_DIM::DIM_2D:
paramIn.height = std::max<uint32>(surfaceHeight >> level, 1);
paramIn.numSlices = 1;
break;
case Latte::E_DIM::DIM_3D:
paramIn.height = std::max<uint32>(surfaceHeight >> level, 1);
paramIn.numSlices = std::max<uint32>(surfaceDepth >> level, 1);
break;
case Latte::E_DIM::DIM_CUBEMAP:
paramIn.height = std::max<uint32>(surfaceHeight >> level, 1);
paramIn.numSlices = std::max<uint32>(surfaceDepth, 6);
paramIn.SetFlagCube(true);
break;
case Latte::E_DIM::DIM_1D_ARRAY:
paramIn.height = 1;
paramIn.numSlices = surfaceDepth;
break;
case Latte::E_DIM::DIM_2D_ARRAY:
paramIn.height = std::max<uint32>(surfaceHeight >> level, 1);
paramIn.numSlices = surfaceDepth;
break;
case Latte::E_DIM::DIM_2D_MSAA:
paramIn.height = std::max<uint32>(surfaceHeight >> level, 1);
paramIn.numSlices = 1;
break;
case Latte::E_DIM::DIM_2D_ARRAY_MSAA:
paramIn.height = std::max<uint32>(surfaceHeight >> level, 1);
paramIn.numSlices = surfaceDepth;
break;
default:
break;
}
paramIn.slice = 0;
paramIn.mipLevel = level;
if (surfaceDim == Latte::E_DIM::DIM_3D)
paramIn.SetFlagVolume(true);
paramIn.SetFlagOpt4Space(level == 0);
paramOut->structSize = sizeof(ADDRComputeSurfaceInfo_OUTPUT);
PPCCoreCallback((uint32)tclFunc_AddrComputeSurfaceInfo, sAddrLib, _paramIn.GetPointer(), paramOut);
}
}
void _TestAddrLib_Compare(uint32 surfaceWidth, uint32 surfaceHeight, uint32 surfaceDepth, Latte::E_DIM surfaceDim, Latte::E_GX2SURFFMT surfaceFormat, Latte::E_GX2TILEMODE surfaceTileMode, uint32 surfaceAA, uint32 level)
{
// get result from tcl.rpl
StackAllocator<ADDRComputeSurfaceInfo_OUTPUT> _paramOut;
ADDRComputeSurfaceInfo_OUTPUT& tclSurfInfo = *_paramOut.GetPointer();
_TestAddrLib_CalculateSurfaceInfo(surfaceFormat, surfaceWidth, surfaceHeight, surfaceDepth, surfaceDim, surfaceTileMode, surfaceAA, level, _paramOut.GetPointer());
// get result from our implementation
LatteAddrLib::AddrSurfaceInfo_OUT ourSurfInfo;
LatteAddrLib::GX2CalculateSurfaceInfo(surfaceFormat, surfaceWidth, surfaceHeight, surfaceDepth, surfaceDim, surfaceTileMode, surfaceAA, level, &ourSurfInfo);
// compare
cemu_assert(tclSurfInfo.pitchAlign == ourSurfInfo.pitchAlign);
cemu_assert((Latte::E_HWTILEMODE)tclSurfInfo.tileMode.value() == ourSurfInfo.hwTileMode);
cemu_assert(tclSurfInfo.baseAlign == ourSurfInfo.baseAlign);
cemu_assert(tclSurfInfo.surfSize == ourSurfInfo.surfSize);
cemu_assert(tclSurfInfo.depthAlign == ourSurfInfo.depthAlign);
cemu_assert(tclSurfInfo.pitch == ourSurfInfo.pitch);
cemu_assert(tclSurfInfo.sliceSize == ourSurfInfo.sliceSize);
}
void _TestAddrLib_Run()
{
uint32 surfaceAA = 0;
std::vector<Latte::E_DIM> dimList = {
Latte::E_DIM::DIM_1D,
Latte::E_DIM::DIM_2D,
Latte::E_DIM::DIM_3D,
Latte::E_DIM::DIM_CUBEMAP,
Latte::E_DIM::DIM_1D_ARRAY,
Latte::E_DIM::DIM_2D_ARRAY,
Latte::E_DIM::DIM_2D_MSAA,
Latte::E_DIM::DIM_2D_ARRAY_MSAA
};
std::vector<Latte::E_GX2TILEMODE> tilemodeList = {
// linear
Latte::E_GX2TILEMODE::TM_LINEAR_GENERAL,
Latte::E_GX2TILEMODE::TM_LINEAR_ALIGNED,
// micro tiled
Latte::E_GX2TILEMODE::TM_1D_TILED_THIN1,
Latte::E_GX2TILEMODE::TM_1D_TILED_THICK,
// macro tiled
Latte::E_GX2TILEMODE::TM_2D_TILED_THIN1,
Latte::E_GX2TILEMODE::TM_2D_TILED_THIN4,
Latte::E_GX2TILEMODE::TM_2D_TILED_THIN2,
Latte::E_GX2TILEMODE::TM_2D_TILED_THICK,
Latte::E_GX2TILEMODE::TM_2B_TILED_THIN1,
Latte::E_GX2TILEMODE::TM_2B_TILED_THIN2,
Latte::E_GX2TILEMODE::TM_2B_TILED_THIN4,
Latte::E_GX2TILEMODE::TM_2B_TILED_THICK,
Latte::E_GX2TILEMODE::TM_3D_TILED_THIN1,
Latte::E_GX2TILEMODE::TM_3D_TILED_THICK,
Latte::E_GX2TILEMODE::TM_3B_TILED_THIN1,
Latte::E_GX2TILEMODE::TM_3B_TILED_THICK,
// special
Latte::E_GX2TILEMODE::TM_LINEAR_SPECIAL,
Latte::E_GX2TILEMODE::TM_32_SPECIAL, // note: Specific to GX2CalcSurfaceSizeAndAlignment, for AddrLib this should just be interpreted as (tm&0xF)
};
std::vector<Latte::E_GX2SURFFMT> formatList = {
Latte::E_GX2SURFFMT::HWFMT_8, Latte::E_GX2SURFFMT::HWFMT_8_8, Latte::E_GX2SURFFMT::HWFMT_8_8_8_8, // 8, 16, 32
Latte::E_GX2SURFFMT::R32_UINT, Latte::E_GX2SURFFMT::R32_G32_UINT, Latte::E_GX2SURFFMT::R32_G32_B32_A32_UINT, // 32, 64, 128
Latte::E_GX2SURFFMT::HWFMT_BC1, Latte::E_GX2SURFFMT::HWFMT_BC2, Latte::E_GX2SURFFMT::HWFMT_BC3, Latte::E_GX2SURFFMT::HWFMT_BC4, Latte::E_GX2SURFFMT::HWFMT_BC5
};
std::vector<uint32> resXYList = {
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17,
31, 32, 33, 50, 63, 64, 65, 127, 128, 129, 200, 253, 254, 255, 256, 257,
511, 512, 513, 1023, 1024, 1025, 2047, 2048, 2049, 4095, 4096, 4097
};
debug_printf("Running AddrLib test...\n");
BenchmarkTimer timer;
timer.Start();
size_t index = 0;
for (auto dim : dimList)
{
debug_printf("%d/%d\n", (int)index, (int)dimList.size());
index++;
for (auto tileMode : tilemodeList)
{
for (auto format : formatList)
{
for (uint32 level = 0; level < 16; level++)
{
for (auto depth : resXYList)
{
for (auto height : resXYList)
{
for (auto width : resXYList)
{
_TestAddrLib_Compare(width, height, depth, dim, format, tileMode, surfaceAA, level);
}
}
}
}
}
}
}
timer.Stop();
debug_printf("Test complete (in %d seconds)\n", (int)(timer.GetElapsedMilliseconds() * 0.001));
assert_dbg();
}
void _test_AddrLib()
{
return;
_TestAddrLib_Init();
_TestAddrLib_Run();
}
}

View file

@ -0,0 +1,225 @@
#include "Common/precompiled.h"
#include "GX2_Blit.h"
#include "GX2_Command.h"
#include "GX2_Surface.h"
#include "Cafe/HW/Latte/ISA/LatteReg.h"
#include "Cafe/HW/Latte/Core/LattePM4.h"
#include "Cafe/OS/common/OSCommon.h"
#include "GX2_Resource.h"
namespace GX2
{
// sets the depth/stencil clear registers and updates clear values in DepthBuffer struct
void GX2SetClearDepthStencil(GX2DepthBuffer* depthBuffer, float depthClearValue, uint8 stencilClearValue)
{
GX2ReserveCmdSpace(4);
*(uint32*)&depthBuffer->clearDepth = _swapEndianU32(*(uint32*)&depthClearValue);
depthBuffer->clearStencil = _swapEndianU32(stencilClearValue);
Latte::LATTE_DB_STENCIL_CLEAR stencilClearReg;
stencilClearReg.set_clearValue(stencilClearValue);
Latte::LATTE_DB_DEPTH_CLEAR depthClearReg;
depthClearReg.set_clearValue(depthClearValue);
gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 2),
Latte::REGADDR::DB_STENCIL_CLEAR - 0xA000,
stencilClearReg, depthClearReg);
}
// similar to GX2SetClearDepthStencil but only sets depth
void GX2SetClearDepth(GX2DepthBuffer* depthBuffer, float depthClearValue)
{
GX2ReserveCmdSpace(3);
*(uint32*)&depthBuffer->clearDepth = _swapEndianU32(*(uint32*)&depthClearValue);
Latte::LATTE_DB_DEPTH_CLEAR depthClearReg;
depthClearReg.set_clearValue(depthClearValue);
gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1),
Latte::REGADDR::DB_DEPTH_CLEAR - 0xA000,
depthClearReg);
}
// similar to GX2SetClearDepthStencil but only sets stencil
void GX2SetClearStencil(GX2DepthBuffer* depthBuffer, uint8 stencilClearValue)
{
GX2ReserveCmdSpace(3);
depthBuffer->clearStencil = _swapEndianU32(stencilClearValue);
Latte::LATTE_DB_STENCIL_CLEAR stencilClearReg;
stencilClearReg.set_clearValue(stencilClearValue);
gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1),
Latte::REGADDR::DB_STENCIL_CLEAR - 0xA000,
stencilClearReg);
}
// update DB_STENCIL_CLEAR and DB_STENCIL_CLEAR based on clear flags
void _updateDepthStencilClearRegs(float depthClearValue, uint8 stencilClearValue, GX2ClearFlags clearFlags)
{
if ((clearFlags & GX2ClearFlags::SET_DEPTH_REG) != 0 && (clearFlags & GX2ClearFlags::SET_STENCIL_REG) != 0)
{
GX2ReserveCmdSpace(4);
Latte::LATTE_DB_STENCIL_CLEAR stencilClearReg;
stencilClearReg.set_clearValue(stencilClearValue);
Latte::LATTE_DB_DEPTH_CLEAR depthClearReg;
depthClearReg.set_clearValue(depthClearValue);
gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 2),
Latte::REGADDR::DB_STENCIL_CLEAR - 0xA000,
stencilClearReg, depthClearReg);
}
else if ((clearFlags & GX2ClearFlags::SET_DEPTH_REG) != 0)
{
GX2ReserveCmdSpace(3);
Latte::LATTE_DB_DEPTH_CLEAR depthClearReg;
depthClearReg.set_clearValue(depthClearValue);
gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1),
Latte::REGADDR::DB_DEPTH_CLEAR - 0xA000,
depthClearReg);
}
else if ((clearFlags & GX2ClearFlags::SET_STENCIL_REG) != 0)
{
GX2ReserveCmdSpace(3);
Latte::LATTE_DB_STENCIL_CLEAR stencilClearReg;
stencilClearReg.set_clearValue(stencilClearValue);
gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1),
Latte::REGADDR::DB_STENCIL_CLEAR - 0xA000,
stencilClearReg);
}
}
void GX2ClearColor(GX2ColorBuffer* colorBuffer, float r, float g, float b, float a)
{
GX2ReserveCmdSpace(50);
if ((colorBuffer->surface.resFlag & GX2_RESFLAG_USAGE_COLOR_BUFFER) != 0)
{
gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_HLE_CLEAR_COLOR_DEPTH_STENCIL, 23));
gx2WriteGather_submitU32AsBE(1); // color (1)
gx2WriteGather_submitU32AsBE(memory_virtualToPhysical(colorBuffer->surface.imagePtr));
gx2WriteGather_submitU32AsBE((uint32)colorBuffer->surface.format.value());
gx2WriteGather_submitU32AsBE((uint32)colorBuffer->surface.tileMode.value());
gx2WriteGather_submitU32AsBE(colorBuffer->surface.width);
gx2WriteGather_submitU32AsBE(colorBuffer->surface.height);
gx2WriteGather_submitU32AsBE(colorBuffer->surface.pitch);
gx2WriteGather_submitU32AsBE(_swapEndianU32(colorBuffer->viewFirstSlice));
gx2WriteGather_submitU32AsBE(_swapEndianU32(colorBuffer->viewNumSlices));
gx2WriteGather_submitU32AsBE(MPTR_NULL);
gx2WriteGather_submitU32AsBE(0); // depth buffer format
gx2WriteGather_submitU32AsBE(0); // tilemode for depth buffer
gx2WriteGather_submitU32AsBE(0);
gx2WriteGather_submitU32AsBE(0);
gx2WriteGather_submitU32AsBE(0);
gx2WriteGather_submitU32AsBE(0);
gx2WriteGather_submitU32AsBE(0);
gx2WriteGather_submitU32AsBE((uint32)(r * 255.0f));
gx2WriteGather_submitU32AsBE((uint32)(g * 255.0f));
gx2WriteGather_submitU32AsBE((uint32)(b * 255.0f));
gx2WriteGather_submitU32AsBE((uint32)(a * 255.0f));
gx2WriteGather_submitU32AsBE(0); // clear depth
gx2WriteGather_submitU32AsBE(0); // clear stencil
}
else
{
debug_printf("GX2ClearColor() - unsupported surface flags\n");
}
}
void GX2ClearBuffersEx(GX2ColorBuffer* colorBuffer, GX2DepthBuffer* depthBuffer, float r, float g, float b, float a, float depthClearValue, uint8 stencilClearValue, GX2ClearFlags clearFlags)
{
GX2ReserveCmdSpace(50);
_updateDepthStencilClearRegs(depthClearValue, stencilClearValue, clearFlags);
uint32 hleClearFlags = 0;
if ((clearFlags & GX2ClearFlags::CLEAR_DEPTH) != 0)
hleClearFlags |= 2;
if ((clearFlags & GX2ClearFlags::CLEAR_STENCIL) != 0)
hleClearFlags |= 4;
hleClearFlags |= 1;
// send command to clear color, depth and stencil
if (_swapEndianU32(colorBuffer->viewFirstSlice) != 0)
debugBreakpoint();
gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_HLE_CLEAR_COLOR_DEPTH_STENCIL, 23));
gx2WriteGather_submitU32AsBE(hleClearFlags); // color (1), depth (2), stencil (4)
gx2WriteGather_submitU32AsBE(memory_virtualToPhysical(colorBuffer->surface.imagePtr));
gx2WriteGather_submitU32AsBE((uint32)colorBuffer->surface.format.value());
gx2WriteGather_submitU32AsBE((uint32)colorBuffer->surface.tileMode.value());
gx2WriteGather_submitU32AsBE((uint32)colorBuffer->surface.width);
gx2WriteGather_submitU32AsBE((uint32)colorBuffer->surface.height);
gx2WriteGather_submitU32AsBE((uint32)colorBuffer->surface.pitch);
gx2WriteGather_submitU32AsBE(_swapEndianU32(colorBuffer->viewFirstSlice));
gx2WriteGather_submitU32AsBE(_swapEndianU32(colorBuffer->viewNumSlices));
gx2WriteGather_submitU32AsBE(memory_virtualToPhysical(depthBuffer->surface.imagePtr));
gx2WriteGather_submitU32AsBE((uint32)depthBuffer->surface.format.value());
gx2WriteGather_submitU32AsBE((uint32)depthBuffer->surface.tileMode.value());
gx2WriteGather_submitU32AsBE((uint32)depthBuffer->surface.width);
gx2WriteGather_submitU32AsBE((uint32)depthBuffer->surface.height);
gx2WriteGather_submitU32AsBE((uint32)depthBuffer->surface.pitch);
gx2WriteGather_submitU32AsBE(_swapEndianU32(depthBuffer->viewFirstSlice));
gx2WriteGather_submitU32AsBE(_swapEndianU32(depthBuffer->viewNumSlices));
gx2WriteGather_submitU32AsBE((uint32)(r * 255.0f));
gx2WriteGather_submitU32AsBE((uint32)(g * 255.0f));
gx2WriteGather_submitU32AsBE((uint32)(b * 255.0f));
gx2WriteGather_submitU32AsBE((uint32)(a * 255.0f));
gx2WriteGather_submitU32AsBE(*(uint32*)&depthClearValue); // clear depth
gx2WriteGather_submitU32AsBE(stencilClearValue&0xFF); // clear stencil
}
// always uses passed depthClearValue/stencilClearValue for clearing, even if clear flags dont specify value updates
void GX2ClearDepthStencilEx(GX2DepthBuffer* depthBuffer, float depthClearValue, uint8 stencilClearValue, GX2ClearFlags clearFlags)
{
GX2ReserveCmdSpace(50);
if (!depthBuffer && (depthBuffer->surface.width == 0 || depthBuffer->surface.height == 0))
{
// Super Smash Bros tries to clear an uninitialized depth surface?
debug_printf("GX2ClearDepthStencilEx(): Attempting to clear invalid depthbuffer\n");
return;
}
_updateDepthStencilClearRegs(depthClearValue, stencilClearValue, clearFlags);
uint32 hleClearFlags = 0;
if ((clearFlags & GX2ClearFlags::CLEAR_DEPTH) != 0)
hleClearFlags |= 2;
if ((clearFlags & GX2ClearFlags::CLEAR_STENCIL) != 0)
hleClearFlags |= 4;
// send command to clear color, depth and stencil
if (hleClearFlags != 0)
{
gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_HLE_CLEAR_COLOR_DEPTH_STENCIL, 23));
gx2WriteGather_submitU32AsBE(hleClearFlags); // color (1), depth (2), stencil (4)
gx2WriteGather_submitU32AsBE(MPTR_NULL);
gx2WriteGather_submitU32AsBE(0); // format for color buffer
gx2WriteGather_submitU32AsBE(0); // tilemode for color buffer
gx2WriteGather_submitU32AsBE(0);
gx2WriteGather_submitU32AsBE(0);
gx2WriteGather_submitU32AsBE(0);
gx2WriteGather_submitU32AsBE(0);
gx2WriteGather_submitU32AsBE(0);
gx2WriteGather_submitU32AsBE(memory_virtualToPhysical(depthBuffer->surface.imagePtr));
gx2WriteGather_submitU32AsBE((uint32)depthBuffer->surface.format.value());
gx2WriteGather_submitU32AsBE((uint32)depthBuffer->surface.tileMode.value());
gx2WriteGather_submitU32AsBE((uint32)depthBuffer->surface.width);
gx2WriteGather_submitU32AsBE((uint32)depthBuffer->surface.height);
gx2WriteGather_submitU32AsBE((uint32)depthBuffer->surface.pitch);
gx2WriteGather_submitU32AsBE(_swapEndianU32(depthBuffer->viewFirstSlice));
gx2WriteGather_submitU32AsBE(_swapEndianU32(depthBuffer->viewNumSlices));
gx2WriteGather_submitU32AsBE(0);
gx2WriteGather_submitU32AsBE(0);
gx2WriteGather_submitU32AsBE(0);
gx2WriteGather_submitU32AsBE(0);
gx2WriteGather_submitU32AsBE(*(uint32*)&depthClearValue); // clear depth
gx2WriteGather_submitU32AsBE(stencilClearValue & 0xFF); // clear stencil
}
}
void GX2BlitInit()
{
cafeExportRegister("gx2", GX2SetClearDepthStencil, LogType::GX2);
cafeExportRegister("gx2", GX2SetClearDepth, LogType::GX2);
cafeExportRegister("gx2", GX2SetClearStencil, LogType::GX2);
cafeExportRegister("gx2", GX2ClearColor, LogType::GX2);
cafeExportRegister("gx2", GX2ClearBuffersEx, LogType::GX2);
cafeExportRegister("gx2", GX2ClearDepthStencilEx, LogType::GX2);
}
}

View file

@ -0,0 +1,15 @@
#pragma once
namespace GX2
{
enum class GX2ClearFlags : uint32
{
CLEAR_DEPTH = 0x01, // clear depth to given clear value
CLEAR_STENCIL = 0x02, // clear stencil to given stencil clear value
SET_DEPTH_REG = 0x04, //
SET_STENCIL_REG = 0x08,
};
void GX2BlitInit();
}
ENABLE_BITMASK_OPERATORS(GX2::GX2ClearFlags);

View file

@ -0,0 +1,308 @@
#include "Cafe/HW/Latte/Core/Latte.h"
#include "Cafe/HW/Latte/Core/LatteDraw.h"
#include "Cafe/OS/common/OSCommon.h"
#include "Cafe/HW/Latte/Core/LattePM4.h"
#include "Cafe/OS/libs/coreinit/coreinit.h"
#include "Cafe/HW/Latte/ISA/RegDefines.h"
#include "GX2.h"
#include "GX2_Command.h"
#include "GX2_Shader.h"
#include "GX2_Misc.h"
extern uint8* gxRingBufferReadPtr;
GX2WriteGatherPipeState gx2WriteGatherPipe = { 0 };
void gx2WriteGather_submitU32AsBE(uint32 v)
{
uint32 coreIndex = PPCInterpreter_getCoreIndex(ppcInterpreterCurrentInstance);
if (gx2WriteGatherPipe.writeGatherPtrWrite[coreIndex] == NULL)
return;
*(uint32*)(*gx2WriteGatherPipe.writeGatherPtrWrite[coreIndex]) = _swapEndianU32(v);
(*gx2WriteGatherPipe.writeGatherPtrWrite[coreIndex]) += 4;
}
void gx2WriteGather_submitU32AsLE(uint32 v)
{
uint32 coreIndex = PPCInterpreter_getCoreIndex(ppcInterpreterCurrentInstance);
if (gx2WriteGatherPipe.writeGatherPtrWrite[coreIndex] == NULL)
return;
*(uint32*)(*gx2WriteGatherPipe.writeGatherPtrWrite[coreIndex]) = v;
(*gx2WriteGatherPipe.writeGatherPtrWrite[coreIndex]) += 4;
}
void gx2WriteGather_submitU32AsLEArray(uint32* v, uint32 numValues)
{
uint32 coreIndex = PPCInterpreter_getCoreIndex(ppcInterpreterCurrentInstance);
if (gx2WriteGatherPipe.writeGatherPtrWrite[coreIndex] == NULL)
return;
memcpy_dwords((*gx2WriteGatherPipe.writeGatherPtrWrite[coreIndex]), v, numValues);
(*gx2WriteGatherPipe.writeGatherPtrWrite[coreIndex]) += 4 * numValues;
}
namespace GX2
{
sint32 gx2WriteGatherCurrentMainCoreIndex = -1;
bool gx2WriteGatherInited = false;
void GX2Init_writeGather() // init write gather, make current core
{
if (gx2WriteGatherPipe.gxRingBuffer == NULL)
gx2WriteGatherPipe.gxRingBuffer = (uint8*)malloc(GX2_COMMAND_RING_BUFFER_SIZE);
if (gx2WriteGatherCurrentMainCoreIndex == sGX2MainCoreIndex)
return; // write gather already configured for same core
for (sint32 i = 0; i < PPC_CORE_COUNT; i++)
{
if (i == sGX2MainCoreIndex)
{
gx2WriteGatherPipe.writeGatherPtrGxBuffer[i] = gx2WriteGatherPipe.gxRingBuffer;
gx2WriteGatherPipe.writeGatherPtrWrite[i] = &gx2WriteGatherPipe.writeGatherPtrGxBuffer[i];
}
else
{
gx2WriteGatherPipe.writeGatherPtrGxBuffer[i] = NULL;
gx2WriteGatherPipe.writeGatherPtrWrite[i] = NULL;
}
gx2WriteGatherPipe.displayListStart[i] = MPTR_NULL;
gx2WriteGatherPipe.writeGatherPtrDisplayList[i] = NULL;
gx2WriteGatherPipe.displayListMaxSize[i] = 0;
}
gx2WriteGatherCurrentMainCoreIndex = sGX2MainCoreIndex;
gx2WriteGatherInited = true;
}
void GX2WriteGather_beginDisplayList(PPCInterpreter_t* hCPU, MPTR buffer, uint32 maxSize)
{
uint32 coreIndex = PPCInterpreter_getCoreIndex(hCPU);
gx2WriteGatherPipe.displayListStart[coreIndex] = buffer;
gx2WriteGatherPipe.displayListMaxSize[coreIndex] = maxSize;
// set new write gather ptr
gx2WriteGatherPipe.writeGatherPtrDisplayList[coreIndex] = memory_getPointerFromVirtualOffset(gx2WriteGatherPipe.displayListStart[coreIndex]);
gx2WriteGatherPipe.writeGatherPtrWrite[coreIndex] = &gx2WriteGatherPipe.writeGatherPtrDisplayList[coreIndex];
}
uint32 GX2WriteGather_getDisplayListWriteDistance(sint32 coreIndex)
{
return (uint32)(*gx2WriteGatherPipe.writeGatherPtrWrite[coreIndex] - memory_getPointerFromVirtualOffset(gx2WriteGatherPipe.displayListStart[coreIndex]));
}
uint32 GX2WriteGather_getFifoWriteDistance(uint32 coreIndex)
{
uint32 writeDistance = (uint32)(gx2WriteGatherPipe.writeGatherPtrGxBuffer[coreIndex] - gx2WriteGatherPipe.gxRingBuffer);
return writeDistance;
}
uint32 GX2WriteGather_endDisplayList(PPCInterpreter_t* hCPU, MPTR buffer)
{
uint32 coreIndex = PPCInterpreter_getCoreIndex(hCPU);
if (gx2WriteGatherPipe.displayListStart[coreIndex] != MPTR_NULL)
{
uint32 currentWriteSize = GX2WriteGather_getDisplayListWriteDistance(coreIndex);
// pad to 32 byte
if (gx2WriteGatherPipe.displayListMaxSize[coreIndex] >= ((gx2WriteGatherPipe.displayListMaxSize[coreIndex] + 0x1F) & ~0x1F))
{
while ((currentWriteSize & 0x1F) != 0)
{
gx2WriteGather_submitU32AsBE(pm4HeaderType2Filler());
currentWriteSize += 4;
}
}
// get size of written data
currentWriteSize = GX2WriteGather_getDisplayListWriteDistance(coreIndex);
// disable current display list and restore write gather ptr
gx2WriteGatherPipe.displayListStart[coreIndex] = MPTR_NULL;
if (sGX2MainCoreIndex == coreIndex)
gx2WriteGatherPipe.writeGatherPtrWrite[coreIndex] = &gx2WriteGatherPipe.writeGatherPtrGxBuffer[coreIndex];
else
gx2WriteGatherPipe.writeGatherPtrWrite[coreIndex] = NULL;
// return size of (written) display list
return currentWriteSize;
}
else
{
// no active display list
// return a size of 0
return 0;
}
}
bool GX2GetCurrentDisplayList(betype<MPTR>* displayListAddr, uint32be* displayListSize)
{
uint32 coreIndex = PPCInterpreter_getCoreIndex(ppcInterpreterCurrentInstance);
if (gx2WriteGatherPipe.displayListStart[coreIndex] == MPTR_NULL)
return false;
if (displayListAddr)
*displayListAddr = gx2WriteGatherPipe.displayListStart[coreIndex];
if (displayListSize)
*displayListSize = gx2WriteGatherPipe.displayListMaxSize[coreIndex];
return true;
}
bool GX2GetDisplayListWriteStatus()
{
// returns true if we are writing to a display list
uint32 coreIndex = PPCInterpreter_getCoreIndex(ppcInterpreterCurrentInstance);
return gx2WriteGatherPipe.displayListStart[coreIndex] != MPTR_NULL;
}
bool GX2WriteGather_isDisplayListActive()
{
uint32 coreIndex = PPCInterpreter_getCoreIndex(ppcInterpreterCurrentInstance);
if (gx2WriteGatherPipe.displayListStart[coreIndex] != MPTR_NULL)
return true;
return false;
}
uint32 GX2WriteGather_getReadWriteDistance()
{
uint32 coreIndex = sGX2MainCoreIndex;
uint32 writeDistance = (uint32)(gx2WriteGatherPipe.writeGatherPtrGxBuffer[coreIndex] + GX2_COMMAND_RING_BUFFER_SIZE - gxRingBufferReadPtr);
writeDistance %= GX2_COMMAND_RING_BUFFER_SIZE;
return writeDistance;
}
void GX2WriteGather_checkAndInsertWrapAroundMark()
{
uint32 coreIndex = PPCInterpreter_getCoreIndex(ppcInterpreterCurrentInstance);
if (coreIndex != sGX2MainCoreIndex) // only if main gx2 core
return;
if (gx2WriteGatherPipe.displayListStart[coreIndex] != MPTR_NULL)
return;
uint32 writeDistance = GX2WriteGather_getFifoWriteDistance(coreIndex);
if (writeDistance >= (GX2_COMMAND_RING_BUFFER_SIZE * 3 / 5))
{
gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_HLE_FIFO_WRAP_AROUND, 1));
gx2WriteGather_submitU32AsBE(0); // empty word since we can't send commands with zero data words
gx2WriteGatherPipe.writeGatherPtrGxBuffer[coreIndex] = gx2WriteGatherPipe.gxRingBuffer;
}
}
void GX2BeginDisplayList(MEMPTR<void> displayListAddr, uint32 size)
{
GX2WriteGather_beginDisplayList(ppcInterpreterCurrentInstance, displayListAddr.GetMPTR(), size);
}
void GX2BeginDisplayListEx(MEMPTR<void> displayListAddr, uint32 size, bool profiling)
{
GX2WriteGather_beginDisplayList(ppcInterpreterCurrentInstance, displayListAddr.GetMPTR(), size);
}
uint32 GX2EndDisplayList(MEMPTR<void> displayListAddr)
{
cemu_assert_debug(displayListAddr != nullptr);
uint32 displayListSize = GX2WriteGather_endDisplayList(ppcInterpreterCurrentInstance, displayListAddr.GetMPTR());
return displayListSize;
}
void GX2CallDisplayList(MPTR addr, uint32 size)
{
cemu_assert_debug((size&3) == 0);
// write PM4 command
GX2ReserveCmdSpace(4);
gx2WriteGather_submit(pm4HeaderType3(IT_INDIRECT_BUFFER_PRIV, 3),
memory_virtualToPhysical(addr),
0, // high address bits
size / 4);
GX2::GX2WriteGather_checkAndInsertWrapAroundMark();
}
void GX2DirectCallDisplayList(void* addr, uint32 size)
{
// this API submits to TCL directly and bypasses write-gatherer
// its basically a way to manually submit a command buffer to the GPU
// as such it also affects the submission and retire timestamps
uint32 coreIndex = PPCInterpreter_getCoreIndex(ppcInterpreterCurrentInstance);
cemu_assert_debug(coreIndex == sGX2MainCoreIndex);
coreIndex = sGX2MainCoreIndex; // always submit to main queue which is owned by GX2 main core (TCLSubmitToRing does not need this workaround)
uint32be* cmdStream = (uint32be*)(gx2WriteGatherPipe.writeGatherPtrGxBuffer[coreIndex]);
cmdStream[0] = pm4HeaderType3(IT_INDIRECT_BUFFER_PRIV, 3);
cmdStream[1] = memory_virtualToPhysical(MEMPTR<void>(addr).GetMPTR());
cmdStream[2] = 0;
cmdStream[3] = size / 4;
gx2WriteGatherPipe.writeGatherPtrGxBuffer[coreIndex] += 16;
// update submission timestamp and retired timestamp
_GX2SubmitToTCL();
}
void GX2CopyDisplayList(MEMPTR<uint32be*> addr, uint32 size)
{
// copy display list to write gather
uint32* displayListDWords = (uint32*)addr.GetPtr();
uint32 dwordCount = size / 4;
if (dwordCount > 0)
{
GX2ReserveCmdSpace(dwordCount);
gx2WriteGather_submitU32AsLEArray(displayListDWords, dwordCount);
}
}
enum class GX2_PATCH_TYPE : uint32
{
FETCH_SHADER = 1,
VERTEX_SHADER = 2,
GEOMETRY_COPY_SHADER = 3,
GEOMETRY_SHADER = 4,
PIXEL_SHADER = 5,
COMPUTE_SHADER = 6
};
void GX2PatchDisplayList(uint32be* displayData, GX2_PATCH_TYPE patchType, uint32 patchOffset, void* obj)
{
cemu_assert_debug((patchOffset & 3) == 0);
if (patchType == GX2_PATCH_TYPE::VERTEX_SHADER)
{
GX2VertexShader_t* vertexShader = (GX2VertexShader_t*)obj;
displayData[patchOffset / 4 + 2] = memory_virtualToPhysical(vertexShader->GetProgramAddr()) >> 8;
}
else if (patchType == GX2_PATCH_TYPE::PIXEL_SHADER)
{
GX2PixelShader_t* pixelShader = (GX2PixelShader_t*)obj;
displayData[patchOffset / 4 + 2] = memory_virtualToPhysical(pixelShader->GetProgramAddr()) >> 8;
}
else if (patchType == GX2_PATCH_TYPE::FETCH_SHADER)
{
GX2FetchShader_t* fetchShader = (GX2FetchShader_t*)obj;
displayData[patchOffset / 4 + 2] = memory_virtualToPhysical(fetchShader->GetProgramAddr()) >> 8;
}
else if (patchType == GX2_PATCH_TYPE::GEOMETRY_COPY_SHADER)
{
GX2GeometryShader_t* geometryShader = (GX2GeometryShader_t*)obj;
displayData[patchOffset / 4 + 2] = memory_virtualToPhysical(geometryShader->GetCopyProgramAddr()) >> 8;
}
else if (patchType == GX2_PATCH_TYPE::GEOMETRY_SHADER)
{
GX2GeometryShader_t* geometryShader = (GX2GeometryShader_t*)obj;
displayData[patchOffset / 4 + 2] = memory_virtualToPhysical(geometryShader->GetGeometryProgramAddr()) >> 8;
}
else
{
forceLog_printf("GX2PatchDisplayList(): unsupported patchType %d", (uint32)patchType);
cemu_assert_debug(false);
}
}
void GX2CommandInit()
{
cafeExportRegister("gx2", GX2BeginDisplayList, LogType::GX2);
cafeExportRegister("gx2", GX2BeginDisplayListEx, LogType::GX2);
cafeExportRegister("gx2", GX2EndDisplayList, LogType::GX2);
cafeExportRegister("gx2", GX2GetCurrentDisplayList, LogType::GX2);
cafeExportRegister("gx2", GX2GetDisplayListWriteStatus, LogType::GX2);
cafeExportRegister("gx2", GX2CallDisplayList, LogType::GX2);
cafeExportRegister("gx2", GX2DirectCallDisplayList, LogType::GX2);
cafeExportRegister("gx2", GX2CopyDisplayList, LogType::GX2);
cafeExportRegister("gx2", GX2PatchDisplayList, LogType::GX2);
}
}

View file

@ -0,0 +1,101 @@
#pragma once
#include "Cafe/HW/Latte/ISA/LatteReg.h"
#include "Cafe/HW/Espresso/Const.h"
struct GX2WriteGatherPipeState
{
uint8* gxRingBuffer;
// each core has it's own write gatherer and display list state (writing)
uint8* writeGatherPtrGxBuffer[Espresso::CORE_COUNT];
uint8** writeGatherPtrWrite[Espresso::CORE_COUNT];
uint8* writeGatherPtrDisplayList[Espresso::CORE_COUNT];
MPTR displayListStart[Espresso::CORE_COUNT];
uint32 displayListMaxSize[Espresso::CORE_COUNT];
};
extern GX2WriteGatherPipeState gx2WriteGatherPipe;
void GX2ReserveCmdSpace(uint32 reservedFreeSpaceInU32); // move to GX2 namespace eventually
void gx2WriteGather_submitU32AsBE(uint32 v);
void gx2WriteGather_submitU32AsLE(uint32 v);
void gx2WriteGather_submitU32AsLEArray(uint32* v, uint32 numValues);
uint32 PPCInterpreter_getCurrentCoreIndex();
// gx2WriteGather_submit functions
template <typename ...Targs>
inline void gx2WriteGather_submit_(uint32 coreIndex, uint32be* writePtr)
{
(*gx2WriteGatherPipe.writeGatherPtrWrite[coreIndex]) = (uint8*)writePtr;
}
template <typename T, typename ...Targs>
inline void gx2WriteGather_submit_(uint32 coreIndex, uint32be* writePtr, const betype<T>& arg, Targs... args)
{
static_assert(sizeof(betype<T>) == sizeof(uint32be));
*(betype<T>*)writePtr = arg;
writePtr++;
gx2WriteGather_submit_(coreIndex, writePtr, args...);
}
template <typename T, typename ...Targs>
inline
typename std::enable_if< std::is_floating_point<T>::value, void>::type
gx2WriteGather_submit_(uint32 coreIndex, uint32be* writePtr, const T& arg, Targs... args)
{
static_assert(sizeof(T) == sizeof(uint32));
*writePtr = *(uint32*)&arg;
writePtr++;
gx2WriteGather_submit_(coreIndex, writePtr, args...);
}
template <typename T, typename ...Targs>
inline
typename std::enable_if< std::is_base_of<Latte::LATTEREG, T>::value, void>::type
gx2WriteGather_submit_(uint32 coreIndex, uint32be* writePtr, const T& arg, Targs... args)
{
static_assert(sizeof(Latte::LATTEREG) == sizeof(uint32be));
*writePtr = arg.getRawValue();
writePtr++;
gx2WriteGather_submit_(coreIndex, writePtr, args...);
}
template <typename T, typename ...Targs>
inline
typename std::enable_if< !std::is_base_of<Latte::LATTEREG, T>::value && !std::is_floating_point<T>::value, void>::type
gx2WriteGather_submit_(uint32 coreIndex, uint32be* writePtr, const T& arg, Targs... args)
{
*writePtr = arg;
writePtr++;
gx2WriteGather_submit_(coreIndex, writePtr, args...);
}
template <typename ...Targs>
inline void gx2WriteGather_submit(Targs... args)
{
uint32 coreIndex = PPCInterpreter_getCurrentCoreIndex();
if (gx2WriteGatherPipe.writeGatherPtrWrite[coreIndex] == nullptr)
return;
uint32be* writePtr = (uint32be*)(*gx2WriteGatherPipe.writeGatherPtrWrite[coreIndex]);
gx2WriteGather_submit_(coreIndex, writePtr, std::forward<Targs>(args)...);
}
namespace GX2
{
bool GX2WriteGather_isDisplayListActive();
uint32 GX2WriteGather_getReadWriteDistance();
void GX2WriteGather_checkAndInsertWrapAroundMark();
void GX2BeginDisplayList(MEMPTR<void> displayListAddr, uint32 size);
void GX2BeginDisplayListEx(MEMPTR<void> displayListAddr, uint32 size, bool profiling);
uint32 GX2EndDisplayList(MEMPTR<void> displayListAddr);
void GX2CallDisplayList(MPTR addr, uint32 size);
void GX2DirectCallDisplayList(void* addr, uint32 size);
void GX2Init_writeGather();
void GX2CommandInit();
}

View file

@ -0,0 +1,392 @@
#include "Cafe/OS/common/OSCommon.h"
#include "Cafe/HW/Latte/ISA/RegDefines.h"
#include "GX2.h"
#include "Cafe/HW/Latte/Core/Latte.h"
#include "Cafe/HW/Latte/Core/LatteDraw.h"
#include "Cafe/HW/Latte/Core/LattePM4.h"
#include "GX2_Command.h"
#include "GX2_State.h"
#include "Cafe/CafeSystem.h"
#define GPU7_REG_AREA_SIZE_CONFIG_REG 0xB00
#define GPU7_REG_AREA_SIZE_CONTEXT_REG 0x400
#define GPU7_REG_AREA_SIZE_ALU_CONST 0x800
#define GPU7_REG_AREA_SIZE_LOOP_CONST 0x60
#define GPU7_REG_AREA_SIZE_RESOURCE 0xD9E
#define GPU7_REG_AREA_SIZE_SAMPLER 0xA2 // (guessed)
#define _GX2_CALC_SHADOWMEM_NUM_U32(__v) (((((__v)*4)+0xFF)&~0xFF)/4)
MPTR gx2CurrentContextStateMPTR = MPTR_NULL;
typedef struct
{
uint32 regOffset;
uint32 regCount;
}GX2RegLoadPktEntry_t;
GX2RegLoadPktEntry_t aluConst_loadPktEntries[1] = // base: 0xC000
{
{0, 0x800},
};
GX2RegLoadPktEntry_t loopConst_loadPktEntries[1] = // base: 0xF880
{
{0, 0x60},
};
GX2RegLoadPktEntry_t samplerReg_loadPktEntries[3] = // base: 0xF000
{
{0, 0x36},
{0x36, 0x36},
{0x6C, 0x36},
};
GX2RegLoadPktEntry_t configReg_loadPktEntries[0xF] = // base: 0x2000
{
{0x300, 0x6},
{0x900, 0x48},
{0x980, 0x48},
{0xA00, 0x48},
{0x310, 0xC},
{0x542, 0x1},
{0x235, 0x1},
{0x232, 0x2},
{0x23A, 0x1},
{0x256, 0x1},
{0x60C, 0x1},
{0x5C5, 0x1},
{0x2C8, 0x1},
{0x363, 0x1},
{0x404, 0x2}
};
GX2RegLoadPktEntry_t contextReg_loadPktEntries[0x2D] = // base: 0xA000
{
{0x0, 0x2},
{0x3, 0x3},
{0xA, 0x4},
{0x10, 0x38},
{0x50, 0x34},
{0x8E, 0x4},
{0x94, 0x40},
{0x100, 0x9},
{0x10C, 0x3},
{0x10F, 0x60},
{0x185, 0xA},
{0x191, 0x27},
{0x1E0, 0x9},
{0x200, 0x1},
{0x202, 0x7},
{0xE0, 0x20},
{0x210, 0x29},
{0x250, 0x34},
{0x290, 0x1},
{0x292, 0x2},
{0x2A1, 0x1},
{0x2A5, 0x1},
{0x2A8, 0x2},
{0x2AC, 0x3},
{0x2CA, 0x1},
{0x2CC, 0x1},
{0x2CE, 0x1},
{0x300, 0x9},
{0x30C, 0x1},
{0x312, 0x1},
{0x316, 0x2},
{0x343, 0x2},
{0x349, 0x3},
{0x34C, 0x2},
{0x351, 0x1},
{0x37E, 0x6},
{0x2B4, 0x3},
{0x2B8, 0x3},
{0x2BC, 0x3},
{0x2C0, 0x3},
{0x2C8, 0x1},
{0x29B, 0x1},
{0x8C, 0x1},
{0xD5, 0x1},
{0x284, 0xC}
};
GX2RegLoadPktEntry_t resourceReg_loadPktEntries[9] = // base: 0xE000
{
{0, 0x70}, // ps tex
{0x380, 0x70},
{0x460, 0x70}, // vs tex
{0x7E0, 0x70},
{0x8B9, 0x7},
{0x8C0, 0x70},
{0x930, 0x70}, // gs tex
{0xCB0, 0x70},
{0xD89, 0x7}
};
typedef struct
{
// Hardware view of context state (register areas)
uint32 areaConfigReg[_GX2_CALC_SHADOWMEM_NUM_U32(GPU7_REG_AREA_SIZE_CONFIG_REG)];
uint32 areaContextReg[_GX2_CALC_SHADOWMEM_NUM_U32(GPU7_REG_AREA_SIZE_CONTEXT_REG)];
uint32 areaALUConst[_GX2_CALC_SHADOWMEM_NUM_U32(GPU7_REG_AREA_SIZE_ALU_CONST)];
uint32 areaLoopConst[_GX2_CALC_SHADOWMEM_NUM_U32(GPU7_REG_AREA_SIZE_LOOP_CONST)];
uint32 areaResource[_GX2_CALC_SHADOWMEM_NUM_U32(GPU7_REG_AREA_SIZE_RESOURCE)];
uint32 areaSampler[_GX2_CALC_SHADOWMEM_NUM_U32(GPU7_REG_AREA_SIZE_SAMPLER)];
}GX2HwContextState_t;
typedef struct
{
GX2HwContextState_t hwContext;
uint32 enableProfling;
/* + 0x9804 */ uint32be loadDL_size;
uint8 ukn9808[0x3FC-4];
uint8 ukn9C00[0x200];
/* +0x9E00 */ uint8 loadDL_buffer[0x300]; // this displaylist caches the IT_LOAD_* commands for swapping out context
}GX2ContextState_t;
static_assert(offsetof(GX2ContextState_t, loadDL_size) == 0x9804);
static_assert(sizeof(GX2ContextState_t) == 0xA100);
uint32 _GX2Context_CalcShadowMemSize(uint32 regCount)
{
return (regCount*4+0xFF)&~0xFF;
}
uint32 _GX2Context_CalcStateSize()
{
uint32 contextStateSize = 0;
contextStateSize += _GX2Context_CalcShadowMemSize(GPU7_REG_AREA_SIZE_CONFIG_REG);
contextStateSize += _GX2Context_CalcShadowMemSize(GPU7_REG_AREA_SIZE_CONTEXT_REG);
contextStateSize += _GX2Context_CalcShadowMemSize(GPU7_REG_AREA_SIZE_ALU_CONST);
contextStateSize += _GX2Context_CalcShadowMemSize(GPU7_REG_AREA_SIZE_LOOP_CONST);
contextStateSize += _GX2Context_CalcShadowMemSize(GPU7_REG_AREA_SIZE_RESOURCE);
contextStateSize += _GX2Context_CalcShadowMemSize(GPU7_REG_AREA_SIZE_SAMPLER);
return contextStateSize;
}
void _GX2Context_CreateLoadDL()
{
GX2ReserveCmdSpace(3);
gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_CONTEXT_CONTROL, 2));
gx2WriteGather_submitU32AsBE(0x80000077);
gx2WriteGather_submitU32AsBE(0x80000077);
}
void _GX2Context_WriteCmdDisableStateShadowing()
{
GX2ReserveCmdSpace(3);
gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_CONTEXT_CONTROL, 2));
gx2WriteGather_submitU32AsBE(0x80000000);
gx2WriteGather_submitU32AsBE(0x80000000);
}
void _GX2Context_cmdLoad(void* gx2ukn, uint32 pm4Header, MPTR physAddrRegArea, uint32 waitForIdle, uint32 numRegOffsetEntries, GX2RegLoadPktEntry_t* regOffsetEntries)
{
GX2ReserveCmdSpace(3 + numRegOffsetEntries*2);
gx2WriteGather_submitU32AsBE(pm4Header);
gx2WriteGather_submitU32AsBE(physAddrRegArea);
gx2WriteGather_submitU32AsBE(waitForIdle);
for(uint32 i=0; i<numRegOffsetEntries; i++)
{
gx2WriteGather_submitU32AsBE(regOffsetEntries[i].regOffset); // regOffset
gx2WriteGather_submitU32AsBE(regOffsetEntries[i].regCount); // regCount
}
}
#define __cmdStateLoad(__gx2State, __pm4Command, __regArea, __waitForIdle, __regLoadPktEntries) _GX2Context_cmdLoad(NULL, pm4HeaderType3(__pm4Command, 2+sizeof(__regLoadPktEntries)/sizeof(__regLoadPktEntries[0])*2), memory_virtualToPhysical(memory_getVirtualOffsetFromPointer(__regArea)), __waitForIdle, sizeof(__regLoadPktEntries)/sizeof(__regLoadPktEntries[0]), __regLoadPktEntries)
void _GX2Context_WriteCmdRestoreState(GX2ContextState_t* gx2ContextState, uint32 ukn)
{
GX2::GX2WriteGather_checkAndInsertWrapAroundMark();
MPTR physAddrContextState = memory_virtualToPhysical(memory_getVirtualOffsetFromPointer(gx2ContextState));
_GX2Context_CreateLoadDL();
__cmdStateLoad(NULL, IT_LOAD_CONFIG_REG, gx2ContextState->hwContext.areaConfigReg, 0x80000000, configReg_loadPktEntries);
__cmdStateLoad(NULL, IT_LOAD_CONTEXT_REG, gx2ContextState->hwContext.areaContextReg, 0, contextReg_loadPktEntries);
__cmdStateLoad(NULL, IT_LOAD_ALU_CONST, gx2ContextState->hwContext.areaALUConst, 0, aluConst_loadPktEntries);
__cmdStateLoad(NULL, IT_LOAD_LOOP_CONST, gx2ContextState->hwContext.areaLoopConst, 0, loopConst_loadPktEntries);
__cmdStateLoad(NULL, IT_LOAD_RESOURCE, gx2ContextState->hwContext.areaResource, 0, resourceReg_loadPktEntries);
__cmdStateLoad(NULL, IT_LOAD_SAMPLER, gx2ContextState->hwContext.areaSampler, 0, samplerReg_loadPktEntries);
}
void GX2SetDefaultState()
{
GX2ReserveCmdSpace(0x100);
Latte::LATTE_PA_CL_VTE_CNTL reg{};
reg.set_VPORT_X_OFFSET_ENA(true).set_VPORT_X_SCALE_ENA(true);
reg.set_VPORT_Y_OFFSET_ENA(true).set_VPORT_Y_SCALE_ENA(true);
reg.set_VPORT_Z_OFFSET_ENA(true).set_VPORT_Z_SCALE_ENA(true);
reg.set_VTX_W0_FMT(true);
gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1),
Latte::REGADDR::PA_CL_VTE_CNTL - 0xA000,
reg);
uint32 stencilTestEnable = GX2_FALSE;
uint32 backStencilEnable = GX2_FALSE;
uint32 frontStencilFunc = 0;
uint32 frontStencilZPass = 0;
uint32 frontStencilZFail = 0;
uint32 frontStencilFail = 0;
uint32 backStencilFunc = 0;
uint32 backStencilZPass = 0;
uint32 backStencilZFail = 0;
uint32 backStencilFail = 0;
uint32 depthControlReg = 0;
// depth stuff
depthControlReg |= (1<<1);
depthControlReg |= (1<<2);
depthControlReg |= ((1&7)<<4);
// stencil stuff
depthControlReg |= ((stencilTestEnable&1)<<0);
depthControlReg |= ((backStencilEnable&1)<<7);
depthControlReg |= ((frontStencilFunc&7)<<8);
depthControlReg |= ((frontStencilZPass&7)<<14);
depthControlReg |= ((frontStencilZFail&7)<<17);
depthControlReg |= ((frontStencilFail&7)<<11);
depthControlReg |= ((backStencilFunc&7)<<20);
depthControlReg |= ((backStencilZPass&7)<<26);
depthControlReg |= ((backStencilZFail&3)<<29);
depthControlReg |= ((backStencilFail&7)<<23);
gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 1+1));
gx2WriteGather_submitU32AsBE(Latte::REGADDR::DB_DEPTH_CONTROL-0xA000);
gx2WriteGather_submitU32AsBE(depthControlReg);
GX2::GX2SetAlphaTest(GX2_DISABLE, GX2::GX2_ALPHAFUNC::LESS, 0.0f);
GX2::GX2SetPolygonControl(Latte::LATTE_PA_SU_SC_MODE_CNTL::E_FRONTFACE::CCW, GX2_DISABLE, GX2_DISABLE, Latte::LATTE_PA_SU_SC_MODE_CNTL::E_POLYGONMODE::UKN0, Latte::LATTE_PA_SU_SC_MODE_CNTL::E_PTYPE::TRIANGLES, Latte::LATTE_PA_SU_SC_MODE_CNTL::E_PTYPE::TRIANGLES, GX2_DISABLE, GX2_DISABLE, GX2_DISABLE);
GX2::GX2SetPolygonOffset(0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
GX2::GX2SetPrimitiveRestartIndex(0xffffffff);
GX2::GX2SetTargetChannelMasks(0xF, 0xF, 0xF, 0xF, 0xF, 0xF, 0xF, 0xF);
GX2::GX2SetBlendConstantColor(0.0f, 0.0f, 0.0f, 0.0f);
GX2::GX2SetPointSize(1.0f, 1.0f);
GX2::GX2SetPointLimits(1.0f, 1.0f);
GX2::GX2SetColorControl(GX2::GX2_LOGICOP::COPY, GX2_DISABLE, GX2_DISABLE, GX2_ENABLE);
GX2::GX2SetRasterizerClipControlEx(true, true, false);
// Set clear depth to 1.0 (workaround for Darksiders 2. Investigate whether actual GX2 driver also sets this)
float depth = 1.0;
gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 1+1));
gx2WriteGather_submitU32AsBE(mmDB_DEPTH_CLEAR - 0xA000);
gx2WriteGather_submitU32AsBE(*(uint32*)&depth); // depth (as float)
// reset HLE special states
for (sint32 i = 0; i <= GX2_SPECIAL_STATE_COUNT; i++)
{
gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_HLE_SPECIAL_STATE, 2));
gx2WriteGather_submitU32AsBE(i); // state id
gx2WriteGather_submitU32AsBE(0); // disable
}
}
void gx2Export_GX2SetDefaultState(PPCInterpreter_t* hCPU)
{
gx2Log_printf("GX2SetDefaultState()");
GX2SetDefaultState();
osLib_returnFromFunction(hCPU, 0);
}
void _GX2ContextCreateRestoreStateDL(GX2ContextState_t* gx2ContextState)
{
// begin display list
if (GX2::GX2WriteGather_isDisplayListActive())
assert_dbg();
GX2::GX2BeginDisplayList((void*)gx2ContextState->loadDL_buffer, sizeof(gx2ContextState->loadDL_buffer));
_GX2Context_WriteCmdRestoreState(gx2ContextState, 0);
uint32 displayListSize = GX2::GX2EndDisplayList((void*)gx2ContextState->loadDL_buffer);
gx2ContextState->loadDL_size = displayListSize;
}
void gx2Export_GX2SetupContextStateEx(PPCInterpreter_t* hCPU)
{
gx2Log_printf("GX2SetupContextStateEx(0x%08x)\n", hCPU->gpr[3]);
cemu_assert_debug(hCPU->gpr[4] == 0 || hCPU->gpr[4] == 1);
GX2ContextState_t* gx2ContextState = (GX2ContextState_t*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]);
uint32 hwContextSize = _GX2Context_CalcStateSize();
if( hwContextSize != sizeof(GX2HwContextState_t) )
assert_dbg();
if( sizeof(GX2HwContextState_t) != 0x9800 )
assert_dbg(); // GX2 HW context size mismatch
if( sizeof(GX2ContextState_t) != 0xA100 )
assert_dbg(); // GX2 context size mismatch
memset(gx2ContextState, 0x00, sizeof(GX2ContextState_t));
gx2ContextState->enableProfling = _swapEndianU32(hCPU->gpr[4]&1);
_GX2Context_WriteCmdRestoreState(gx2ContextState, 1);
gx2CurrentContextStateMPTR = hCPU->gpr[3];
_GX2Context_CreateLoadDL();
GX2SetDefaultState();
_GX2ContextCreateRestoreStateDL(gx2ContextState);
osLib_returnFromFunction(hCPU, 0);
}
void gx2Export_GX2SetContextState(PPCInterpreter_t* hCPU)
{
gx2Log_printf("GX2SetContextState(0x%08x)\n", hCPU->gpr[3]);
// parameters:
if( hCPU->gpr[3] == MPTR_NULL )
{
// disable state shadowing
_GX2Context_WriteCmdDisableStateShadowing();
osLib_returnFromFunction(hCPU, 0);
return;
}
// check if context state changed
bool boiWorkaround = CafeSystem::GetRPXHashBase() == 0x6BCD618E; // workaround for a bug in Binding of Isaac to avoid flicker
if( boiWorkaround )
{
if( hCPU->gpr[3] != gx2CurrentContextStateMPTR ) // dont reload same state
{
GX2ContextState_t* gx2ContextState = (GX2ContextState_t*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]);
_GX2Context_WriteCmdRestoreState(gx2ContextState, 0);
_GX2Context_CreateLoadDL();
// set new context state
gx2CurrentContextStateMPTR = hCPU->gpr[3];
}
else
{
// even if it's the same context, make sure state shadowing is enabled.
_GX2Context_CreateLoadDL();
}
}
else
{
GX2ContextState_t* gx2ContextState = (GX2ContextState_t*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]);
if (gx2ContextState->loadDL_size == 0)
{
_GX2Context_CreateLoadDL();
_GX2Context_WriteCmdRestoreState(gx2ContextState, 0);
}
else
{
_GX2Context_CreateLoadDL();
GX2::GX2CallDisplayList(memory_getVirtualOffsetFromPointer(gx2ContextState->loadDL_buffer), gx2ContextState->loadDL_size);
}
// set new context state
gx2CurrentContextStateMPTR = hCPU->gpr[3];
}
// todo: Save/restore GX2 special state as well -> handle this by correctly emulating the state
osLib_returnFromFunction(hCPU, 0);
}
void gx2Export_GX2GetContextStateDisplayList(PPCInterpreter_t* hCPU)
{
gx2Log_printf("GX2GetContextStateDisplayList(0x%08x, 0x%08x, 0x%08x)\n", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]);
ppcDefineParamStructPtr(gx2ContextState, GX2ContextState_t, 0);
ppcDefineParamU32BEPtr(displayListPtrOut, 1);
ppcDefineParamU32BEPtr(displayListSizeOut, 2);
*displayListPtrOut = memory_getVirtualOffsetFromPointer(gx2ContextState->loadDL_buffer);
*displayListSizeOut = gx2ContextState->loadDL_size;
osLib_returnFromFunction(hCPU, 0);
}

View file

@ -0,0 +1,231 @@
#include "Cafe/HW/Latte/ISA/RegDefines.h"
#include "GX2.h"
#include "Cafe/HW/Latte/Core/Latte.h"
#include "Cafe/HW/Latte/Core/LatteDraw.h"
#include "Cafe/HW/Latte/ISA/LatteReg.h"
#include "Cafe/HW/Latte/Core/LattePM4.h"
#include "Cafe/OS/common/OSCommon.h"
#include "GX2_Command.h"
#include "GX2_Draw.h"
namespace GX2
{
void GX2SetAttribBuffer(uint32 bufferIndex, uint32 sizeInBytes, uint32 stride, void* data)
{
GX2ReserveCmdSpace(9);
MPTR physicalAddress = memory_virtualToPhysical(memory_getVirtualOffsetFromPointer(data));
// write PM4 command
gx2WriteGather_submit(
pm4HeaderType3(IT_SET_RESOURCE, 8),
0x8C0 + bufferIndex * 7,
physicalAddress,
sizeInBytes - 1, // size
(stride & 0xFFFF) << 11, // stride
0, // ukn
0, // ukn
0, // ukn
0xC0000000); // ukn
}
void GX2DrawIndexedEx(GX2PrimitiveMode2 primitiveMode, uint32 count, GX2IndexType indexType, void* indexData, uint32 baseVertex, uint32 numInstances)
{
GX2ReserveCmdSpace(3 + 3 + 2 + 2 + 6);
gx2WriteGather_submit(
// IT_SET_CTL_CONST
pm4HeaderType3(IT_SET_CTL_CONST, 2), 0,
baseVertex,
// IT_SET_CONFIG_REG
pm4HeaderType3(IT_SET_CONFIG_REG, 2), Latte::REGADDR::VGT_PRIMITIVE_TYPE - 0x2000,
(uint32)primitiveMode,
// IT_INDEX_TYPE
pm4HeaderType3(IT_INDEX_TYPE, 1),
(uint32)indexType,
// IT_NUM_INSTANCES
pm4HeaderType3(IT_NUM_INSTANCES, 1),
numInstances,
// IT_DRAW_INDEX_2
pm4HeaderType3(IT_DRAW_INDEX_2, 5) | 0x00000001,
-1,
memory_virtualToPhysical(memory_getVirtualOffsetFromPointer(indexData)),
0,
count,
0);
GX2::GX2WriteGather_checkAndInsertWrapAroundMark();
}
void GX2DrawIndexedEx2(GX2PrimitiveMode2 primitiveMode, uint32 count, GX2IndexType indexType, void* indexData, uint32 baseVertex, uint32 numInstances, uint32 baseInstance)
{
GX2ReserveCmdSpace(3 + 3 + 2 + 2 + 6);
gx2WriteGather_submit(
// IT_SET_CTL_CONST
pm4HeaderType3(IT_SET_CTL_CONST, 2), 0,
baseVertex,
// set base instance
pm4HeaderType3(IT_SET_CTL_CONST, 2), 1,
baseInstance,
// IT_SET_CONFIG_REG
pm4HeaderType3(IT_SET_CONFIG_REG, 2), Latte::REGADDR::VGT_PRIMITIVE_TYPE - 0x2000,
(uint32)primitiveMode,
// IT_INDEX_TYPE
pm4HeaderType3(IT_INDEX_TYPE, 1),
(uint32)indexType,
// IT_NUM_INSTANCES
pm4HeaderType3(IT_NUM_INSTANCES, 1),
numInstances,
// IT_DRAW_INDEX_2
pm4HeaderType3(IT_DRAW_INDEX_2, 5) | 0x00000001,
-1,
memory_virtualToPhysical(memory_getVirtualOffsetFromPointer(indexData)),
0,
count,
0,
// reset base instance
pm4HeaderType3(IT_SET_CTL_CONST, 2), 1,
0 // baseInstance
);
GX2::GX2WriteGather_checkAndInsertWrapAroundMark();
}
void GX2DrawEx(GX2PrimitiveMode2 primitiveMode, uint32 count, uint32 baseVertex, uint32 numInstances)
{
GX2ReserveCmdSpace(3 + 3 + 2 + 2 + 6);
gx2WriteGather_submit(
// IT_SET_CTL_CONST
pm4HeaderType3(IT_SET_CTL_CONST, 2), 0,
baseVertex,
// IT_SET_CONFIG_REG
pm4HeaderType3(IT_SET_CONFIG_REG, 2), Latte::REGADDR::VGT_PRIMITIVE_TYPE - 0x2000,
(uint32)primitiveMode,
// IT_INDEX_TYPE
pm4HeaderType3(IT_INDEX_TYPE, 1),
(uint32)GX2IndexType::U32_BE,
// IT_NUM_INSTANCES
pm4HeaderType3(IT_NUM_INSTANCES, 1),
numInstances,
// IT_DRAW_INDEX_2
pm4HeaderType3(IT_DRAW_INDEX_AUTO, 2) | 0x00000001,
count,
0 // DRAW_INITIATOR
);
GX2::GX2WriteGather_checkAndInsertWrapAroundMark();
}
void GX2DrawIndexedImmediateEx(GX2PrimitiveMode2 primitiveMode, uint32 count, GX2IndexType indexType, void* indexData, uint32 baseVertex, uint32 numInstances)
{
uint32* indexDataU32 = (uint32*)indexData;
uint32 numIndexU32s;
bool use32BitIndices = false;
if (indexType == GX2IndexType::U16_BE || indexType == GX2IndexType::U16_LE)
{
// 16bit indices
numIndexU32s = (count + 1) / 2;
}
else if (indexType == GX2IndexType::U32_BE || indexType == GX2IndexType::U32_LE)
{
// 32bit indices
numIndexU32s = count;
use32BitIndices = true;
}
else
{
cemu_assert_unimplemented();
}
GX2ReserveCmdSpace(3 + 3 + 3 + 2 + 2 + 6 + 3 + numIndexU32s);
if (numIndexU32s > 0x4000 - 2)
{
cemuLog_log(LogType::Force, "GX2DrawIndexedImmediateEx(): Draw exceeds maximum PM4 command size. Keep index size below 16KiB minus 8 byte");
return;
}
// set base vertex
gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CTL_CONST, 2));
gx2WriteGather_submitU32AsBE(0);
gx2WriteGather_submitU32AsBE(baseVertex);
// set primitive mode
gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONFIG_REG, 2));
gx2WriteGather_submitU32AsBE(Latte::REGADDR::VGT_PRIMITIVE_TYPE - 0x2000);
gx2WriteGather_submitU32AsBE((uint32)primitiveMode);
// set index type
gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_INDEX_TYPE, 1));
gx2WriteGather_submitU32AsBE((uint32)indexType);
// set number of instances
gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_NUM_INSTANCES, 1));
gx2WriteGather_submitU32AsBE((uint32)numInstances);
// request indexed draw with indices embedded into command buffer
gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_DRAW_INDEX_IMMD, 2 + numIndexU32s) | 0x00000001);
gx2WriteGather_submitU32AsBE(count);
gx2WriteGather_submitU32AsBE(0); // ukn
if (use32BitIndices)
{
for (uint32 i = 0; i < numIndexU32s; i++)
{
gx2WriteGather_submitU32AsLE(indexDataU32[i]);
}
}
else
{
for (uint32 i = 0; i < numIndexU32s; i++)
{
uint32 indexPair = indexDataU32[i];
// swap index pair
indexPair = (indexPair >> 16) | (indexPair << 16);
gx2WriteGather_submitU32AsLE(indexPair);
}
}
GX2::GX2WriteGather_checkAndInsertWrapAroundMark();
}
struct GX2DispatchComputeParam
{
/* +0x00 */ uint32be worksizeX;
/* +0x04 */ uint32be worksizeY;
/* +0x08 */ uint32be worksizeZ;
};
void GX2DispatchCompute(GX2DispatchComputeParam* dispatchParam)
{
GX2ReserveCmdSpace(9 + 10);
gx2WriteGather_submit(pm4HeaderType3(IT_SET_RESOURCE, 8),
(mmSQ_CS_DISPATCH_PARAMS - mmSQ_TEX_RESOURCE_WORD0),
memory_virtualToPhysical(MEMPTR<GX2DispatchComputeParam>(dispatchParam).GetMPTR()),
0xF,
0x862000,
1,
0xABCD1234,
0xABCD1234,
0xC0000000);
// IT_EVENT_WRITE with RST_VTX_CNT?
// set primitive mode
gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONFIG_REG, 2));
gx2WriteGather_submitU32AsBE(Latte::REGADDR::VGT_PRIMITIVE_TYPE - 0x2000);
gx2WriteGather_submitU32AsBE(1); // mode
// set number of instances
gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_NUM_INSTANCES, 1));
gx2WriteGather_submitU32AsBE(1); // numInstances
uint32 workCount = (uint32)dispatchParam->worksizeX * (uint32)dispatchParam->worksizeY * (uint32)dispatchParam->worksizeZ;
gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_DRAW_INDEX_AUTO, 2) | 0x00000001);
gx2WriteGather_submitU32AsBE(workCount);
gx2WriteGather_submitU32AsBE(0); // DRAW_INITIATOR (has source select for index generator + other unknown info)
}
void GX2DrawInit()
{
cafeExportRegister("gx2", GX2SetAttribBuffer, LogType::GX2);
cafeExportRegister("gx2", GX2DrawIndexedEx, LogType::GX2);
cafeExportRegister("gx2", GX2DrawIndexedEx2, LogType::GX2);
cafeExportRegister("gx2", GX2DrawEx, LogType::GX2);
cafeExportRegister("gx2", GX2DrawIndexedImmediateEx, LogType::GX2);
cafeExportRegister("gx2", GX2DispatchCompute, LogType::GX2);
}
}

View file

@ -0,0 +1,13 @@
#pragma once
#include "Cafe/HW/Latte/ISA/LatteReg.h"
namespace GX2
{
using GX2IndexType = Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE;
using GX2PrimitiveMode2 = Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE;
void GX2SetAttribBuffer(uint32 bufferIndex, uint32 sizeInBytes, uint32 stride, void* data);
void GX2DrawIndexedEx(GX2PrimitiveMode2 primitiveMode, uint32 count, GX2IndexType indexType, void* indexData, uint32 baseVertex, uint32 numInstances);
void GX2DrawInit();
}

View file

@ -0,0 +1,311 @@
#include "Cafe/OS/common/OSCommon.h"
#include "GX2_Command.h"
#include "GX2_Event.h"
#include "Cafe/HW/Latte/Core/LattePM4.h"
#include "Cafe/HW/MMU/MMU.h"
#include "Cafe/OS/libs/coreinit/coreinit_Thread.h"
#include "Cafe/HW/Latte/Core/Latte.h"
#include "GX2.h"
#include "Cafe/HW/Latte/Renderer/Renderer.h"
#include "config/ActiveSettings.h"
#include "util/helpers/ConcurrentQueue.h"
namespace GX2
{
SysAllocator<coreinit::OSThreadQueue> g_vsyncThreadQueue;
SysAllocator<coreinit::OSThreadQueue> g_flipThreadQueue;
SysAllocator<coreinit::OSEvent> s_updateRetirementEvent;
std::atomic<uint64> s_lastRetirementTimestamp = 0;
// called from GPU code when a command buffer is retired
void __GX2NotifyNewRetirementTimestamp(uint64 tsRetire)
{
__OSLockScheduler();
s_lastRetirementTimestamp = tsRetire;
coreinit::OSSignalEventAllInternal(s_updateRetirementEvent.GetPtr());
__OSUnlockScheduler();
}
void GX2SetGPUFence(uint32be* fencePtr, uint32 mask, uint32 compareOp, uint32 compareValue)
{
GX2ReserveCmdSpace(7);
uint8 compareOpTable[] = { 0x7,0x1,0x3,0x2,0x6,0x4,0x5,0x0 };
gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_WAIT_REG_MEM, 6));
gx2WriteGather_submitU32AsBE((uint32)(compareOpTable[compareOp & 7]) | 0x10); // compare operand + memory select (0x10)
gx2WriteGather_submitU32AsBE(memory_virtualToPhysical(memory_getVirtualOffsetFromPointer(fencePtr)) | 2); // physical address + type size flag(?)
gx2WriteGather_submitU32AsBE(0); // ukn, always set to 0
gx2WriteGather_submitU32AsBE(compareValue); // fence value
gx2WriteGather_submitU32AsBE(mask); // fence mask
gx2WriteGather_submitU32AsBE(0xA); // unknown purpose
}
enum class GX2PipeEventType : uint32
{
TOP = 0,
BOTTOM = 1,
BOTTOM_AFTER_FLUSH = 2
};
void GX2SubmitUserTimeStamp(uint64* timestampOut, uint64 value, GX2PipeEventType eventType, uint32 triggerInterrupt)
{
GX2ReserveCmdSpace(7);
MPTR physTimestampAddr = memory_virtualToPhysical(memory_getVirtualOffsetFromPointer(timestampOut));
uint32 valHigh = (uint32)(value >> 32);
uint32 valLow = (uint32)(value & 0xffffffff);
if (eventType == GX2PipeEventType::TOP)
{
// write when on top of pipe
gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_MEM_WRITE, 4));
gx2WriteGather_submitU32AsBE(physTimestampAddr | 0x2);
gx2WriteGather_submitU32AsBE(0); // 0x40000 -> 32bit write, 0x0 -> 64bit write?
gx2WriteGather_submitU32AsBE(valLow); // low
gx2WriteGather_submitU32AsBE(valHigh); // high
if (triggerInterrupt != 0)
{
// top callback
gx2WriteGather_submitU32AsBE(0x0000304A);
gx2WriteGather_submitU32AsBE(0x40000000);
}
}
else if (eventType == GX2PipeEventType::BOTTOM_AFTER_FLUSH)
{
// write when on bottom of pipe
gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_MEM_WRITE, 4));
gx2WriteGather_submitU32AsBE(physTimestampAddr | 0x2);
gx2WriteGather_submitU32AsBE(0); // 0x40000 -> 32bit write, 0x0 -> 64bit write?
gx2WriteGather_submitU32AsBE(valLow); // low
gx2WriteGather_submitU32AsBE(valHigh); // high
// trigger CB
if (triggerInterrupt != 0)
{
// bottom callback
// todo -> Fix this
gx2WriteGather_submitU32AsBE(0x0000304B); // hax -> This event is handled differently and uses a different packet?
gx2WriteGather_submitU32AsBE(0x40000000);
// trigger bottom of pipe callback
// used by Mario & Sonic Rio
gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_HLE_BOTTOM_OF_PIPE_CB, 3));
gx2WriteGather_submitU32AsBE(physTimestampAddr);
gx2WriteGather_submitU32AsBE(valLow); // low
gx2WriteGather_submitU32AsBE(valHigh); // high
}
}
else if (eventType == GX2PipeEventType::BOTTOM)
{
// fix this
// write timestamp when on bottom of pipe
if (triggerInterrupt != 0)
{
// write value and trigger CB
// todo: Use correct packet
gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_HLE_BOTTOM_OF_PIPE_CB, 3));
gx2WriteGather_submitU32AsBE(physTimestampAddr);
gx2WriteGather_submitU32AsBE(valLow); // low
gx2WriteGather_submitU32AsBE(valHigh); // high
}
else
{
// write value but don't trigger CB
gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_MEM_WRITE, 4));
gx2WriteGather_submitU32AsBE(physTimestampAddr | 0x2);
gx2WriteGather_submitU32AsBE(0); // 0x40000 -> 32bit write, 0x0 -> 64bit write?
gx2WriteGather_submitU32AsBE(valLow); // low
gx2WriteGather_submitU32AsBE(valHigh); // high
}
}
else
{
cemu_assert_debug(false);
}
}
struct GX2EventFunc
{
MEMPTR<void> callbackFuncPtr;
MEMPTR<void> userData;
}s_eventCallback[GX2CallbackEventTypeCount]{};
void GX2SetEventCallback(GX2CallbackEventType eventType, void* callbackFuncPtr, void* userData)
{
if ((size_t)eventType >= GX2CallbackEventTypeCount)
{
forceLog_printf("GX2SetEventCallback(): Unknown eventType\n");
return;
}
s_eventCallback[(size_t)eventType].callbackFuncPtr = callbackFuncPtr;
s_eventCallback[(size_t)eventType].userData = userData;
}
void GX2GetEventCallback(GX2CallbackEventType eventType, MEMPTR<void>* callbackFuncPtrOut, MEMPTR<void>* userDataOut)
{
if ((size_t)eventType >= GX2CallbackEventTypeCount)
{
forceLog_printf("GX2GetEventCallback(): Unknown eventType\n");
return;
}
if (callbackFuncPtrOut)
*callbackFuncPtrOut = s_eventCallback[(size_t)eventType].callbackFuncPtr;
if (userDataOut)
*userDataOut = s_eventCallback[(size_t)eventType].userData;
}
// event callback thread
bool s_callbackThreadLaunched{};
SysAllocator<OSThread_t> s_eventCallbackThread;
SysAllocator<uint8, 0x2000> s_eventCallbackThreadStack;
SysAllocator<char, 64> s_eventCallbackThreadName;
// event callback queue
struct GX2EventQueueEntry
{
GX2EventQueueEntry() {};
GX2EventQueueEntry(GX2CallbackEventType eventType) : eventType(eventType) {};
GX2CallbackEventType eventType{(GX2CallbackEventType)-1};
};
SysAllocator<coreinit::OSSemaphore> s_eventCbQueueSemaphore;
ConcurrentQueue<GX2EventQueueEntry> s_eventCbQueue;
void __GX2NotifyEvent(GX2CallbackEventType eventType)
{
if ((size_t)eventType >= GX2CallbackEventTypeCount)
{
cemu_assert_debug(false);
return;
}
if (s_eventCallback[(size_t)eventType].callbackFuncPtr)
{
s_eventCbQueue.push(eventType);
coreinit::OSSignalSemaphore(s_eventCbQueueSemaphore);
}
// wake up threads that are waiting for VSYNC or FLIP event
if (eventType == GX2CallbackEventType::VSYNC)
{
__OSLockScheduler();
g_vsyncThreadQueue->wakeupEntireWaitQueue(false);
__OSUnlockScheduler();
}
else if (eventType == GX2CallbackEventType::FLIP)
{
__OSLockScheduler();
g_flipThreadQueue->wakeupEntireWaitQueue(false);
__OSUnlockScheduler();
}
}
void __GX2CallbackThread(PPCInterpreter_t* hCPU)
{
while (coreinit::OSWaitSemaphore(s_eventCbQueueSemaphore))
{
GX2EventQueueEntry entry;
if (!s_eventCbQueue.peek2(entry))
continue;
if(!s_eventCallback[(size_t)entry.eventType].callbackFuncPtr)
continue;
PPCCoreCallback(s_eventCallback[(size_t)entry.eventType].callbackFuncPtr, (sint32)entry.eventType, s_eventCallback[(size_t)entry.eventType].userData);
}
osLib_returnFromFunction(hCPU, 0);
}
uint64 GX2GetLastSubmittedTimeStamp()
{
return LatteGPUState.lastSubmittedCommandBufferTimestamp.load();
}
uint64 GX2GetRetiredTimeStamp()
{
return s_lastRetirementTimestamp;
}
void GX2WaitForVsync()
{
__OSLockScheduler();
g_vsyncThreadQueue.GetPtr()->queueAndWait(coreinit::OSGetCurrentThread());
__OSUnlockScheduler();
}
void GX2WaitForFlip()
{
if ((sint32)(_swapEndianU32(LatteGPUState.sharedArea->flipRequestCountBE) == _swapEndianU32(LatteGPUState.sharedArea->flipExecuteCountBE)))
return; // dont wait if no flip is requested
__OSLockScheduler();
g_flipThreadQueue.GetPtr()->queueAndWait(coreinit::OSGetCurrentThread());
__OSUnlockScheduler();
}
bool GX2WaitTimeStamp(uint64 tsWait)
{
__OSLockScheduler();
while (tsWait > s_lastRetirementTimestamp)
{
// GPU hasn't caught up yet
coreinit::OSWaitEventInternal(s_updateRetirementEvent.GetPtr());
}
__OSUnlockScheduler();
// return true to indicate no timeout
return true;
}
void GX2DrawDone()
{
// optional force full sync (texture readback and occlusion queries)
bool forceFullSync = false;
if (g_renderer && g_renderer->GetType() == RendererAPI::Vulkan)
forceFullSync = true;
if (forceFullSync || ActiveSettings::WaitForGX2DrawDoneEnabled())
{
GX2ReserveCmdSpace(2);
// write PM4 command
gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_HLE_SYNC_ASYNC_OPERATIONS, 1));
gx2WriteGather_submitU32AsBE(0x00000000); // unused
}
// flush pipeline
if (_GX2GetUnflushedBytes(PPCInterpreter_getCoreIndex(ppcInterpreterCurrentInstance)) > 0)
_GX2SubmitToTCL();
uint64 ts = GX2GetLastSubmittedTimeStamp();
GX2WaitTimeStamp(ts);
GX2::GX2WriteGather_checkAndInsertWrapAroundMark();
}
void GX2Init_event()
{
// clear queue
// launch event callback thread
if (s_callbackThreadLaunched)
return;
s_callbackThreadLaunched = true;
strcpy(s_eventCallbackThreadName.GetPtr(), "GX2 event callback");
coreinit::OSCreateThreadType(s_eventCallbackThread, PPCInterpreter_makeCallableExportDepr(__GX2CallbackThread), 0, nullptr, (uint8*)s_eventCallbackThreadStack.GetPtr() + s_eventCallbackThreadStack.GetByteSize(), (sint32)s_eventCallbackThreadStack.GetByteSize(), 16, OSThread_t::ATTR_DETACHED, OSThread_t::THREAD_TYPE::TYPE_IO);
coreinit::OSSetThreadName(s_eventCallbackThread, s_eventCallbackThreadName);
coreinit::OSResumeThread(s_eventCallbackThread);
}
void GX2EventInit()
{
cafeExportRegister("gx2", GX2SetGPUFence, LogType::GX2);
cafeExportRegister("gx2", GX2SubmitUserTimeStamp, LogType::GX2);
cafeExportRegister("gx2", GX2SetEventCallback, LogType::GX2);
cafeExportRegister("gx2", GX2GetEventCallback, LogType::GX2);
cafeExportRegister("gx2", GX2GetLastSubmittedTimeStamp, LogType::GX2);
cafeExportRegister("gx2", GX2GetRetiredTimeStamp, LogType::GX2);
cafeExportRegister("gx2", GX2WaitForVsync, LogType::GX2);
cafeExportRegister("gx2", GX2WaitForFlip, LogType::GX2);
cafeExportRegister("gx2", GX2WaitTimeStamp, LogType::GX2);
cafeExportRegister("gx2", GX2DrawDone, LogType::GX2);
coreinit::OSInitThreadQueue(g_vsyncThreadQueue.GetPtr());
coreinit::OSInitThreadQueue(g_flipThreadQueue.GetPtr());
coreinit::OSInitEvent(s_updateRetirementEvent, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_AUTO);
coreinit::OSInitSemaphore(s_eventCbQueueSemaphore, 0);
}
}

View file

@ -0,0 +1,26 @@
#pragma once
namespace GX2
{
void GX2EventInit();
void GX2Init_event();
void GX2WaitForVsync();
void GX2WaitForFlip();
void GX2DrawDone();
enum class GX2CallbackEventType
{
TIMESTAMP_TOP = 0,
TIMESTAMP_BOTTOM = 1,
VSYNC = 2,
FLIP = 3,
// 4 is buffer overrun?
};
inline constexpr size_t GX2CallbackEventTypeCount = 5;
// notification callbacks for GPU
void __GX2NotifyNewRetirementTimestamp(uint64 tsRetire);
void __GX2NotifyEvent(GX2CallbackEventType eventType);
}

View file

@ -0,0 +1,77 @@
#include "Cafe/OS/common/OSCommon.h"
#include "Cafe/HW/Latte/ISA/RegDefines.h"
#include "GX2.h"
#include "GX2_Resource.h"
#include "Cafe/HW/Latte/Core/Latte.h"
#include "Cafe/HW/Latte/Core/LatteDraw.h"
// default GX2 allocator (not the same as the GX2R allocator, but GX2R uses this allocator by default)
MPTR gx2Mem_defaultAlloc = MPTR_NULL;
MPTR gx2Mem_defaultFree = MPTR_NULL;
void gx2Memory_GX2SetDefaultAllocator(MPTR defaultAllocFunc, MPTR defaulFreeFunc)
{
gx2Mem_defaultAlloc = defaultAllocFunc;
gx2Mem_defaultFree = defaulFreeFunc;
}
void _GX2DefaultAlloc_Alloc(PPCInterpreter_t* hCPU)
{
// parameters:
// r3 uint32 userParam
// r4 uint32 size
// r5 sint32 alignment
hCPU->gpr[3] = hCPU->gpr[4];
hCPU->gpr[4] = hCPU->gpr[5];
hCPU->instructionPointer = gCoreinitData->MEMAllocFromDefaultHeapEx.GetMPTR();
}
void _GX2DefaultAlloc_Free(PPCInterpreter_t* hCPU)
{
hCPU->gpr[3] = hCPU->gpr[4];
hCPU->instructionPointer = gCoreinitData->MEMFreeToDefaultHeap.GetMPTR();
}
void gx2Export_GX2SetDefaultAllocator(PPCInterpreter_t* hCPU)
{
gx2Log_printf("GX2SetDefaultAllocator(0x%08x, 0x%08x)\n", hCPU->gpr[3], hCPU->gpr[4]);
gx2Mem_defaultAlloc = hCPU->gpr[3];
gx2Mem_defaultFree = hCPU->gpr[4];
osLib_returnFromFunction(hCPU, 0);
}
void _GX2DefaultAllocR_Alloc(PPCInterpreter_t* hCPU)
{
gx2Log_printf("GX2DefaultAllocate(0x%08x, 0x%08x, 0x%08x)\n", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]);
// parameters:
// r3 uint32 userParam
// r4 uint32 size
// r5 sint32 alignment
hCPU->instructionPointer = gx2Mem_defaultAlloc;
}
void _GX2DefaultAllocR_Free(PPCInterpreter_t* hCPU)
{
gx2Log_printf("GX2DefaultFree(0x%08x, 0x%08x)\n", hCPU->gpr[3], hCPU->gpr[4]);
// parameters:
// r3 uint32 userParam
// r4 void* mem
hCPU->instructionPointer = gx2Mem_defaultFree;
}
namespace GX2
{
void GX2MEMAllocatorsInit()
{
// set default allocators (can be overwritten by GX2SetDefaultAllocator)
gx2Mem_defaultAlloc = PPCInterpreter_makeCallableExportDepr(_GX2DefaultAlloc_Alloc);
gx2Mem_defaultFree = PPCInterpreter_makeCallableExportDepr(_GX2DefaultAlloc_Free);
// set resource default allocator
GX2::GX2RSetAllocator(PPCInterpreter_makeCallableExportDepr(_GX2DefaultAllocR_Alloc), PPCInterpreter_makeCallableExportDepr(_GX2DefaultAllocR_Free));
}
void GX2MemInit()
{
osLib_addFunction("gx2", "GX2SetDefaultAllocator", gx2Export_GX2SetDefaultAllocator);
}
};

Some files were not shown because too many files have changed in this diff Show more