mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-06-02 17:48:21 -04:00
OS threading, DVD I/O, Endianness fixes, ARAM emulation, GX vertex fix
Major changes: - Implement Big-Endian to Little-Endian byte-swapping for all RARC archive types (JKRCompArchive, JKRMemArchive, JKRDvdArchive, JKRAramArchive) - Implement DVD file I/O via DvdEmu (DVDOpen, DVDFastOpen, DVDReadPrio, DVDReadAsyncPrio, DVDConvertPathToEntrynum) - Fix YAZ0 decompression endianness in JKRDvdRipper, JKRDecomp, JKRAram, and JKRDvdAramRipper (use JKRDecompExpandSize instead of direct header read) - Emulate ARAM with 16MB malloc buffer and synchronous memcpy in ARQPostRequest instead of hardware DMA transfers that hang on PC - Add real OS threading implementation (OSContext, OSThread, OSMutex) using native Windows threads with side-table pattern for GC struct compatibility - Fix font endianness for JUTResFont and JUTCacheFont - Redirect GXVert.h to Aurora's PC implementation to prevent FIFO writes to the GameCube hardware address 0xCC008000 - Add Z-buffer texture format support (GX_TF_Z24X8, GX_TF_Z16, GX_TF_Z8) in Aurora's texture converter
This commit is contained in:
@@ -1358,6 +1358,9 @@ set(DUSK_FILES
|
||||
#src/dusk/m_Do_ext_dusk.cpp
|
||||
src/dusk/jsystem_stubs.cpp
|
||||
src/dusk/dvd_emu.cpp
|
||||
src/dolphin/os/OSContext.cpp
|
||||
src/dolphin/os/OSThread.cpp
|
||||
src/dolphin/os/OSMutex.cpp
|
||||
)
|
||||
|
||||
source_group("dolzel" FILES ${DOLZEL_FILES} ${Z2AUDIOLIB_FILES} ${SSYSTEM_FILES} ${JSYSTEM_FILES} ${REL_FILES})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#ifndef JKRARCHIVE_H
|
||||
#ifndef JKRARCHIVE_H -j n
|
||||
#define JKRARCHIVE_H
|
||||
|
||||
#include "JSystem/JKernel/JKRCompression.h"
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
class JKRHeap;
|
||||
|
||||
/**
|
||||
/** m
|
||||
* @ingroup jsystem-jkernel
|
||||
*
|
||||
*/
|
||||
@@ -220,6 +220,75 @@ protected:
|
||||
static u32 sCurrentDirID;
|
||||
};
|
||||
|
||||
#ifdef TARGET_PC
|
||||
#include "dusk/endian.h"
|
||||
|
||||
// Byte-swap archive header from Big-Endian to host after loading from disk
|
||||
inline void JKRSwapArcHeader(SArcHeader* h) {
|
||||
h->signature = be32(h->signature);
|
||||
h->file_length = be32(h->file_length);
|
||||
h->header_length = be32(h->header_length);
|
||||
h->file_data_offset = be32(h->file_data_offset);
|
||||
h->file_data_length = be32(h->file_data_length);
|
||||
h->field_0x14 = be32(h->field_0x14);
|
||||
h->field_0x18 = be32(h->field_0x18);
|
||||
h->field_0x1c = be32(h->field_0x1c);
|
||||
}
|
||||
|
||||
// Byte-swap archive data info block from Big-Endian to host
|
||||
inline void JKRSwapArcDataInfo(SArcDataInfo* info) {
|
||||
info->num_nodes = be32(info->num_nodes);
|
||||
info->node_offset = be32(info->node_offset);
|
||||
info->num_file_entries = be32(info->num_file_entries);
|
||||
info->file_entry_offset = be32(info->file_entry_offset);
|
||||
info->string_table_length = be32(info->string_table_length);
|
||||
info->string_table_offset = be32(info->string_table_offset);
|
||||
info->next_free_file_id = be16(info->next_free_file_id);
|
||||
}
|
||||
|
||||
// Byte-swap all directory entries
|
||||
inline void JKRSwapDirEntries(JKRArchive::SDIDirEntry* nodes, u32 count) {
|
||||
for (u32 i = 0; i < count; i++) {
|
||||
nodes[i].type = be32(nodes[i].type);
|
||||
nodes[i].name_offset = be32(nodes[i].name_offset);
|
||||
nodes[i].field_0x8 = be16(nodes[i].field_0x8);
|
||||
nodes[i].num_entries = be16(nodes[i].num_entries);
|
||||
nodes[i].first_file_index = be32(nodes[i].first_file_index);
|
||||
}
|
||||
}
|
||||
|
||||
// Byte-swap all file entries
|
||||
inline void JKRSwapFileEntries(JKRArchive::SDIFileEntry* files, u32 count) {
|
||||
for (u32 i = 0; i < count; i++) {
|
||||
files[i].file_id = be16(files[i].file_id);
|
||||
files[i].name_hash = be16(files[i].name_hash);
|
||||
files[i].type_flags_and_name_offset = be32(files[i].type_flags_and_name_offset);
|
||||
files[i].data_offset = be32(files[i].data_offset);
|
||||
files[i].data_size = be32(files[i].data_size);
|
||||
// data pointer is runtime-only, no swap needed
|
||||
}
|
||||
}
|
||||
|
||||
// Swap all archive structures after loading from disk
|
||||
inline void JKRSwapArchiveMemory(SArcDataInfo* arcInfo) {
|
||||
// First swap the info block itself to read offsets
|
||||
JKRSwapArcDataInfo(arcInfo);
|
||||
|
||||
// Then swap directory and file entries using the now-native offsets
|
||||
JKRArchive::SDIDirEntry* nodes = (JKRArchive::SDIDirEntry*)((u8*)arcInfo + arcInfo->node_offset);
|
||||
JKRArchive::SDIFileEntry* files = (JKRArchive::SDIFileEntry*)((u8*)arcInfo + arcInfo->file_entry_offset);
|
||||
|
||||
JKRSwapDirEntries(nodes, arcInfo->num_nodes);
|
||||
JKRSwapFileEntries(files, arcInfo->num_file_entries);
|
||||
}
|
||||
#else
|
||||
inline void JKRSwapArcHeader(SArcHeader*) {}
|
||||
inline void JKRSwapArcDataInfo(SArcDataInfo*) {}
|
||||
inline void JKRSwapDirEntries(JKRArchive::SDIDirEntry*, u32) {}
|
||||
inline void JKRSwapFileEntries(JKRArchive::SDIFileEntry*, u32) {}
|
||||
inline void JKRSwapArchiveMemory(SArcDataInfo*) {}
|
||||
#endif
|
||||
|
||||
inline JKRCompression JKRConvertAttrToCompressionType(int attr) {
|
||||
return JKRArchive::convertAttrToCompressionType(attr);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
|
||||
#ifdef __REVOLUTION_SDK__
|
||||
#include <revolution/gx/GXVert.h>
|
||||
#elif defined(TARGET_PC)
|
||||
// On PC, use Aurora's GXVert declarations (extern functions implemented in
|
||||
// GXVert.cpp, no hardware FIFO writes to 0xCC008000)
|
||||
#include "../../../extern/aurora/include/dolphin/gx/GXVert.h"
|
||||
#else
|
||||
#include <dolphin/types.h>
|
||||
#include <dolphin/os.h>
|
||||
|
||||
@@ -351,7 +351,8 @@ int decompSZS_subroutine(u8* src, u8* dest) {
|
||||
}
|
||||
|
||||
SYaz0Header* header = (SYaz0Header*)src;
|
||||
endPtr = dest + (header->length - fileOffset);
|
||||
u32 decompressedLength = JKRDecompExpandSize(src);
|
||||
endPtr = dest + (decompressedLength - fileOffset);
|
||||
if (endPtr > dest + maxDest) {
|
||||
endPtr = dest + maxDest;
|
||||
}
|
||||
|
||||
@@ -121,6 +121,7 @@ bool JKRAramArchive::open(s32 entryNum) {
|
||||
JKRDvdToMainRam(entryNum, (u8*)mem, EXPAND_SWITCH_UNKNOWN1, 32, NULL,
|
||||
JKRDvdRipper::ALLOC_DIRECTION_FORWARD, 0, &mCompression, NULL);
|
||||
DCInvalidateRange(mem, 32);
|
||||
JKRSwapArcHeader(mem);
|
||||
int alignment = mMountDirection == MOUNT_DIRECTION_HEAD ? 32 : -32;
|
||||
u32 alignedSize = ALIGN_NEXT(mem->file_data_offset, 32);
|
||||
mArcInfoBlock = (SArcDataInfo*)JKRAllocFromHeap(mHeap, alignedSize, alignment);
|
||||
@@ -130,6 +131,7 @@ bool JKRAramArchive::open(s32 entryNum) {
|
||||
JKRDvdToMainRam(entryNum, (u8*)mArcInfoBlock, EXPAND_SWITCH_UNKNOWN1, alignedSize, NULL,
|
||||
JKRDvdRipper::ALLOC_DIRECTION_FORWARD, 32, NULL, NULL);
|
||||
DCInvalidateRange(mArcInfoBlock, alignedSize);
|
||||
JKRSwapArchiveMemory(mArcInfoBlock);
|
||||
|
||||
mNodes = (SDIDirEntry*)((u8*)mArcInfoBlock + mArcInfoBlock->node_offset);
|
||||
mFiles = (SDIFileEntry*)((u8*)mArcInfoBlock + mArcInfoBlock->file_entry_offset);
|
||||
|
||||
@@ -88,6 +88,7 @@ bool JKRCompArchive::open(s32 entryNum) {
|
||||
|
||||
JKRDvdToMainRam(entryNum, (u8 *)arcHeader, EXPAND_SWITCH_UNKNOWN1, 32, NULL, JKRDvdRipper::ALLOC_DIRECTION_FORWARD, 0, &mCompression, NULL);
|
||||
DCInvalidateRange(arcHeader, 32);
|
||||
JKRSwapArcHeader(arcHeader);
|
||||
|
||||
mSizeOfMemPart = arcHeader->field_0x14;
|
||||
mSizeOfAramPart = arcHeader->field_0x18;
|
||||
@@ -108,6 +109,7 @@ bool JKRCompArchive::open(s32 entryNum) {
|
||||
JKRDvdToMainRam(entryNum, (u8 *)mArcInfoBlock, EXPAND_SWITCH_UNKNOWN1, (uintptr_t)arcHeader->file_data_offset + mSizeOfMemPart,
|
||||
NULL, JKRDvdRipper::ALLOC_DIRECTION_FORWARD, 0x20, NULL, NULL);
|
||||
DCInvalidateRange(mArcInfoBlock, (uintptr_t)arcHeader->file_data_offset + mSizeOfMemPart);
|
||||
JKRSwapArchiveMemory(mArcInfoBlock);
|
||||
field_0x64 = (uintptr_t)mArcInfoBlock + arcHeader->file_data_offset;
|
||||
|
||||
if (mSizeOfAramPart != 0) {
|
||||
@@ -156,6 +158,7 @@ bool JKRCompArchive::open(s32 entryNum) {
|
||||
else {
|
||||
// arcHeader + 1 should lead to 0x20, which is the data after the header
|
||||
JKRHeap::copyMemory((u8 *)mArcInfoBlock, arcHeader + 1, (arcHeader->file_data_offset + mSizeOfMemPart));
|
||||
JKRSwapArchiveMemory(mArcInfoBlock);
|
||||
field_0x64 = (uintptr_t)mArcInfoBlock + arcHeader->file_data_offset;
|
||||
if (mSizeOfAramPart != 0) {
|
||||
mAramPart = (JKRAramBlock*)JKRAllocFromAram(mSizeOfAramPart, JKRAramHeap::HEAD);
|
||||
|
||||
@@ -206,7 +206,7 @@ void JKRDecomp::decodeSZS(u8* src_buffer, u8* dst_buffer, u32 srcSize, u32 dstSi
|
||||
s32 chunkBitsLeft = 0;
|
||||
s32 chunkBits;
|
||||
|
||||
decompEnd = dst_buffer + *(int*)(src_buffer + 4) - dstSize;
|
||||
decompEnd = dst_buffer + JKRDecompExpandSize(src_buffer) - dstSize;
|
||||
|
||||
if (srcSize == 0) {
|
||||
return;
|
||||
|
||||
@@ -329,7 +329,8 @@ static int decompSZS_subroutine(u8* src, u32 dest) {
|
||||
}
|
||||
|
||||
SYaz0Header* header = (SYaz0Header*)src;
|
||||
endAddr = dest + (header->length - fileOffset);
|
||||
u32 decompressedLength = JKRDecompExpandSize(src);
|
||||
endAddr = dest + (decompressedLength - fileOffset);
|
||||
if (endAddr > dest + maxDest) {
|
||||
endAddr = dest + maxDest;
|
||||
}
|
||||
|
||||
@@ -76,6 +76,7 @@ bool JKRDvdArchive::open(s32 entryNum) {
|
||||
JKRDvdToMainRam(entryNum, (u8*)arcHeader, EXPAND_SWITCH_UNKNOWN1, sizeof(SArcHeader), NULL,
|
||||
JKRDvdRipper::ALLOC_DIRECTION_FORWARD, 0, &mCompression, NULL);
|
||||
DCInvalidateRange(arcHeader, sizeof(SArcHeader));
|
||||
JKRSwapArcHeader(arcHeader);
|
||||
|
||||
int alignment;
|
||||
alignment = mMountDirection == MOUNT_DIRECTION_HEAD ? 0x20 : -0x20;
|
||||
@@ -90,6 +91,7 @@ bool JKRDvdArchive::open(s32 entryNum) {
|
||||
arcHeader->file_data_offset, NULL, JKRDvdRipper::ALLOC_DIRECTION_FORWARD,
|
||||
sizeof(SArcHeader), NULL, NULL);
|
||||
DCInvalidateRange(mArcInfoBlock, arcHeader->file_data_offset);
|
||||
JKRSwapArchiveMemory(mArcInfoBlock);
|
||||
|
||||
mNodes = (SDIDirEntry*)((intptr_t)&mArcInfoBlock->num_nodes + mArcInfoBlock->node_offset);
|
||||
mFiles = (SDIFileEntry*)((intptr_t)&mArcInfoBlock->num_nodes + mArcInfoBlock->file_entry_offset);
|
||||
|
||||
@@ -329,7 +329,8 @@ int decompSZS_subroutine(u8* src, u8* dest) {
|
||||
}
|
||||
|
||||
SYaz0Header* header = (SYaz0Header*)src;
|
||||
endPtr = dest + (header->length - fileOffset);
|
||||
u32 decompressedLength = JKRDecompExpandSize(src);
|
||||
endPtr = dest + (decompressedLength - fileOffset);
|
||||
if (endPtr > dest + maxDest) {
|
||||
endPtr = dest + maxDest;
|
||||
}
|
||||
|
||||
@@ -89,8 +89,10 @@ bool JKRMemArchive::open(s32 entryNum, JKRArchive::EMountDirection mountDirectio
|
||||
mMountMode = UNKNOWN_MOUNT_MODE;
|
||||
}
|
||||
else {
|
||||
JKRSwapArcHeader(mArcHeader);
|
||||
JUT_ASSERT(438, mArcHeader->signature == 'RARC');
|
||||
mArcInfoBlock = (SArcDataInfo *)((u8 *)mArcHeader + mArcHeader->header_length);
|
||||
JKRSwapArchiveMemory(mArcInfoBlock);
|
||||
mNodes = (SDIDirEntry *)((u8 *)&mArcInfoBlock->num_nodes + mArcInfoBlock->node_offset);
|
||||
mFiles = (SDIFileEntry *)((u8 *)&mArcInfoBlock->num_nodes + mArcInfoBlock->file_entry_offset);
|
||||
mStringTable = (char *)((u8 *)&mArcInfoBlock->num_nodes + mArcInfoBlock->string_table_offset);
|
||||
@@ -111,8 +113,10 @@ bool JKRMemArchive::open(s32 entryNum, JKRArchive::EMountDirection mountDirectio
|
||||
|
||||
bool JKRMemArchive::open(void* buffer, u32 bufferSize, JKRMemBreakFlag flag) {
|
||||
mArcHeader = (SArcHeader *)buffer;
|
||||
JKRSwapArcHeader(mArcHeader);
|
||||
JUT_ASSERT(491, mArcHeader->signature == 'RARC');
|
||||
mArcInfoBlock = (SArcDataInfo *)((u8 *)mArcHeader + mArcHeader->header_length);
|
||||
JKRSwapArchiveMemory(mArcInfoBlock);
|
||||
mNodes = (SDIDirEntry *)((u8 *)&mArcInfoBlock->num_nodes + mArcInfoBlock->node_offset);
|
||||
mFiles = (SDIFileEntry *)((u8 *)&mArcInfoBlock->num_nodes + mArcInfoBlock->file_entry_offset);
|
||||
mStringTable = (char *)((u8 *)&mArcInfoBlock->num_nodes + mArcInfoBlock->string_table_offset);
|
||||
|
||||
@@ -8,6 +8,15 @@
|
||||
#include <dolphin/gx.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef TARGET_PC
|
||||
#include "dusk/endian.h"
|
||||
static inline u32 R32(u32 v) { return be32(v); }
|
||||
static inline u16 R16(u16 v) { return be16(v); }
|
||||
#else
|
||||
static inline u32 R32(u32 v) { return v; }
|
||||
static inline u16 R16(u16 v) { return v; }
|
||||
#endif
|
||||
|
||||
JUTCacheFont::JUTCacheFont(ResFONT const* p_fontRes, u32 cacheSize, JKRHeap* p_heap) {
|
||||
initialize_state();
|
||||
JUTResFont::initialize_state();
|
||||
@@ -73,24 +82,26 @@ int JUTCacheFont::getMemorySize(ResFONT const* p_font, u16* o_widCount, u32* o_w
|
||||
u32 glyTexSize;
|
||||
|
||||
u8* fontInf = (u8*)p_font->data;
|
||||
for (int i = 0; i < p_font->numBlocks; i++) {
|
||||
switch (((BlockHeader*)fontInf)->magic) {
|
||||
for (int i = 0; i < (int)R32(p_font->numBlocks); i++) {
|
||||
u32 blkMagic = R32(((BlockHeader*)fontInf)->magic);
|
||||
u32 blkSize = R32(((BlockHeader*)fontInf)->size);
|
||||
switch (blkMagic) {
|
||||
case 'INF1':
|
||||
break;
|
||||
case 'WID1':
|
||||
totalWidSize += ((BlockHeader*)fontInf)->size;
|
||||
totalWidSize += blkSize;
|
||||
widBlockCount++;
|
||||
break;
|
||||
case 'GLY1':
|
||||
totalGlySize += ((BlockHeader*)fontInf)->size;
|
||||
glyTexSize = ((ResFONT::GLY1*)fontInf)->textureSize;
|
||||
totalGlySize += blkSize;
|
||||
glyTexSize = R32(((ResFONT::GLY1*)fontInf)->textureSize);
|
||||
glyBlockCount++;
|
||||
if (glyTexSize > maxGlyTexSize) {
|
||||
maxGlyTexSize = glyTexSize;
|
||||
}
|
||||
break;
|
||||
case 'MAP1':
|
||||
totalMapSize += ((BlockHeader*)fontInf)->size;
|
||||
totalMapSize += blkSize;
|
||||
mapBlockCount++;
|
||||
break;
|
||||
default:
|
||||
@@ -98,7 +109,7 @@ int JUTCacheFont::getMemorySize(ResFONT const* p_font, u16* o_widCount, u32* o_w
|
||||
break;
|
||||
}
|
||||
|
||||
fontInf += ((BlockHeader*)fontInf)->size;
|
||||
fontInf += blkSize;
|
||||
}
|
||||
|
||||
if (o_widCount != NULL) {
|
||||
@@ -257,56 +268,59 @@ void JUTCacheFont::setBlock() {
|
||||
ResFONT::MAP1* pMap = (ResFONT::MAP1*)field_0x84;
|
||||
u32 aramAddress = field_0xac->getAddress();
|
||||
mMaxCode = 0xffff;
|
||||
const int* pData = (int*)mResFont->data;
|
||||
const u8* pData = (const u8*)mResFont->data;
|
||||
|
||||
for (int i = 0; i < mResFont->numBlocks; i++) {
|
||||
for (int i = 0; i < (int)R32(mResFont->numBlocks); i++) {
|
||||
u32 blkMagic = R32(((BlockHeader*)pData)->magic);
|
||||
u32 blkSize = R32(((BlockHeader*)pData)->size);
|
||||
u32 u;
|
||||
switch (*pData) {
|
||||
switch (blkMagic) {
|
||||
case 'INF1':
|
||||
memcpy(mInf1Ptr, pData, 0x20);
|
||||
u = mInf1Ptr->fontType;
|
||||
u = R16(mInf1Ptr->fontType);
|
||||
JUT_ASSERT(448, u < suAboutEncoding_);
|
||||
mIsLeadByte = &JUTResFont::saoAboutEncoding_[u];
|
||||
break;
|
||||
case 'WID1':
|
||||
memcpy(pWidth, pData, pData[1]);
|
||||
memcpy(pWidth, pData, blkSize);
|
||||
mpWidthBlocks[widthNum] = (ResFONT::WID1*)pWidth;
|
||||
widthNum++;
|
||||
pWidth += pData[1];
|
||||
pWidth += blkSize;
|
||||
break;
|
||||
case 'GLY1':
|
||||
case 'GLY1': {
|
||||
memcpy(piVar5, pData, 0x20);
|
||||
JKRAramBlock* iVar1;
|
||||
iVar1 = JKRMainRamToAram((u8*)pData + 0x20, aramAddress, pData[1] - 0x20,
|
||||
iVar1 = JKRMainRamToAram((u8*)pData + 0x20, aramAddress, blkSize - 0x20,
|
||||
EXPAND_SWITCH_UNKNOWN0, 0, NULL, 0xffffffff, NULL);
|
||||
if (iVar1 == NULL) {
|
||||
JUTException::panic("JUTCacheFont.cpp", 0x1dd,
|
||||
"trouble occurred in JKRMainRamToAram.");
|
||||
}
|
||||
piVar5->magic = aramAddress;
|
||||
if (piVar5->textureSize > mMaxSheetSize) {
|
||||
mMaxSheetSize = piVar5->textureSize;
|
||||
if (R32(piVar5->textureSize) > mMaxSheetSize) {
|
||||
mMaxSheetSize = R32(piVar5->textureSize);
|
||||
}
|
||||
mpGlyphBlocks[gylphNum] = piVar5;
|
||||
gylphNum++;
|
||||
piVar5++;
|
||||
aramAddress += pData[1] - 0x20;
|
||||
aramAddress += blkSize - 0x20;
|
||||
break;
|
||||
}
|
||||
case 'MAP1':
|
||||
memcpy(pMap, pData, pData[1]);
|
||||
memcpy(pMap, pData, blkSize);
|
||||
mpMapBlocks[mapNum] = pMap;
|
||||
if (mMaxCode > mpMapBlocks[mapNum]->startCode) {
|
||||
mMaxCode = mpMapBlocks[mapNum]->startCode;
|
||||
if (mMaxCode > R16(mpMapBlocks[mapNum]->startCode)) {
|
||||
mMaxCode = R16(mpMapBlocks[mapNum]->startCode);
|
||||
}
|
||||
mapNum++;
|
||||
pMap = (ResFONT::MAP1*)((u8*)pMap + pData[1]);
|
||||
pMap = (ResFONT::MAP1*)((u8*)pMap + blkSize);
|
||||
break;
|
||||
default:
|
||||
JUTReportConsole("Unknown data block\n");
|
||||
break;
|
||||
}
|
||||
|
||||
pData = (int*)((u8*)pData + pData[1]);
|
||||
pData = pData + blkSize;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -390,8 +404,8 @@ JUTCacheFont::TCachePage* JUTCacheFont::loadCache_char_subroutine(int* param_0,
|
||||
rv = NULL;
|
||||
int i = 0;
|
||||
for (; i < mGly1BlockNum; i++) {
|
||||
if (mpGlyphBlocks[i]->startCode <= *r29 && *r29 <= mpGlyphBlocks[i]->endCode) {
|
||||
*r29 -= mpGlyphBlocks[i]->startCode;
|
||||
if (R16(mpGlyphBlocks[i]->startCode) <= *r29 && *r29 <= R16(mpGlyphBlocks[i]->endCode)) {
|
||||
*r29 -= R16(mpGlyphBlocks[i]->startCode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -461,23 +475,23 @@ ResFONT* JUTResFont::getResFont() const {
|
||||
}
|
||||
|
||||
int JUTResFont::getFontType() const {
|
||||
return mInf1Ptr->fontType;
|
||||
return R16(mInf1Ptr->fontType);
|
||||
}
|
||||
|
||||
int JUTResFont::getLeading() const {
|
||||
return mInf1Ptr->leading;
|
||||
return R16(mInf1Ptr->leading);
|
||||
}
|
||||
|
||||
s32 JUTResFont::getWidth() const {
|
||||
return mInf1Ptr->width;
|
||||
return R16(mInf1Ptr->width);
|
||||
}
|
||||
|
||||
s32 JUTResFont::getAscent() const {
|
||||
return mInf1Ptr->ascent;
|
||||
return R16(mInf1Ptr->ascent);
|
||||
}
|
||||
|
||||
s32 JUTResFont::getDescent() const {
|
||||
return mInf1Ptr->descent;
|
||||
return R16(mInf1Ptr->descent);
|
||||
}
|
||||
|
||||
s32 JUTResFont::getHeight() const {
|
||||
|
||||
@@ -7,6 +7,19 @@
|
||||
#include "JSystem/JUtility/JUTConsole.h"
|
||||
#include <dolphin/gx.h>
|
||||
|
||||
#ifdef TARGET_PC
|
||||
#include "dusk/endian.h"
|
||||
// Font resource data is embedded as Big-Endian (GameCube format).
|
||||
// Wrap all multi-byte field accesses through these helpers.
|
||||
static inline u32 R32(u32 v) { return be32(v); }
|
||||
static inline u16 R16(u16 v) { return be16(v); }
|
||||
static inline s16 RS16(s16 v) { return be16s(v); }
|
||||
#else
|
||||
static inline u32 R32(u32 v) { return v; }
|
||||
static inline u16 R16(u16 v) { return v; }
|
||||
static inline s16 RS16(s16 v) { return v; }
|
||||
#endif
|
||||
|
||||
JUTResFont::JUTResFont() {
|
||||
initialize_state();
|
||||
JUTFont::initialize_state();
|
||||
@@ -90,8 +103,8 @@ void JUTResFont::countBlock() {
|
||||
mMap1BlockNum = 0;
|
||||
|
||||
u8* pData = (u8*)&mResFont->data;
|
||||
for (u32 i = 0; i < mResFont->numBlocks; i++, pData += ((BlockHeader*)pData)->size) {
|
||||
int magic = ((BlockHeader*)pData)->magic;
|
||||
for (u32 i = 0; i < R32(mResFont->numBlocks); i++, pData += R32(((BlockHeader*)pData)->size)) {
|
||||
u32 magic = R32(((BlockHeader*)pData)->magic);
|
||||
switch (magic) {
|
||||
case 'WID1':
|
||||
mWid1BlockNum++;
|
||||
@@ -124,11 +137,13 @@ void JUTResFont::setBlock() {
|
||||
mMaxCode = -1;
|
||||
|
||||
BlockHeader* data = (BlockHeader*)mResFont->data;
|
||||
for (u32 i = 0; i < mResFont->numBlocks; data = (BlockHeader*)data->getNext(), i++) {
|
||||
switch (data->magic) {
|
||||
for (u32 i = 0; i < R32(mResFont->numBlocks);
|
||||
data = (BlockHeader*)((u8*)data + R32(data->size)), i++) {
|
||||
u32 magic = R32(data->magic);
|
||||
switch (magic) {
|
||||
case 'INF1': {
|
||||
mInf1Ptr = (ResFONT::INF1*)data;
|
||||
u32 u = mInf1Ptr->fontType;
|
||||
u32 u = R16(mInf1Ptr->fontType);
|
||||
JUT_ASSERT(244, u < suAboutEncoding_);
|
||||
mIsLeadByte = &saoAboutEncoding_[u];
|
||||
break;
|
||||
@@ -146,8 +161,8 @@ void JUTResFont::setBlock() {
|
||||
|
||||
case 'MAP1':
|
||||
mpMapBlocks[mapNum] = (ResFONT::MAP1*)data;
|
||||
if (mMaxCode > mpMapBlocks[mapNum]->startCode) {
|
||||
mMaxCode = mpMapBlocks[mapNum]->startCode;
|
||||
if (mMaxCode > R16(mpMapBlocks[mapNum]->startCode)) {
|
||||
mMaxCode = R16(mpMapBlocks[mapNum]->startCode);
|
||||
}
|
||||
mapNum++;
|
||||
break;
|
||||
@@ -236,10 +251,14 @@ f32 JUTResFont::drawChar_scale(f32 pos_x, f32 pos_y, f32 scale_x, f32 scale_y, i
|
||||
y1 = pos_y - getAscent() * (scale_y / getHeight());
|
||||
f32 y2 = getDescent() * (scale_y / getHeight()) + pos_y;
|
||||
|
||||
s32 u1 = (mWidth * 0x8000) / mpGlyphBlocks[field_0x66]->textureWidth;
|
||||
s32 v1 = (mHeight * 0x8000) / mpGlyphBlocks[field_0x66]->textureHeight;
|
||||
s32 u2 = ((mWidth + mpGlyphBlocks[field_0x66]->cellWidth) * 0x8000) / mpGlyphBlocks[field_0x66]->textureWidth;
|
||||
s32 v2 = ((mHeight + mpGlyphBlocks[field_0x66]->cellHeight) * 0x8000) / mpGlyphBlocks[field_0x66]->textureHeight;
|
||||
u16 texW = R16(mpGlyphBlocks[field_0x66]->textureWidth);
|
||||
u16 texH = R16(mpGlyphBlocks[field_0x66]->textureHeight);
|
||||
u16 cellW = R16(mpGlyphBlocks[field_0x66]->cellWidth);
|
||||
u16 cellH = R16(mpGlyphBlocks[field_0x66]->cellHeight);
|
||||
s32 u1 = (mWidth * 0x8000) / texW;
|
||||
s32 v1 = (mHeight * 0x8000) / texH;
|
||||
s32 u2 = ((mWidth + cellW) * 0x8000) / texW;
|
||||
s32 v2 = ((mHeight + cellH) * 0x8000) / texH;
|
||||
|
||||
GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_F32, 0);
|
||||
GXBegin(GX_QUADS, GX_VTXFMT0, 4);
|
||||
@@ -282,11 +301,14 @@ void JUTResFont::loadFont(int code, GXTexMapID texMapID, JUTFont::TWidth* pDstWi
|
||||
void JUTResFont::getWidthEntry(int code, JUTFont::TWidth* i_width) const {
|
||||
int fontCode = getFontCode(code);
|
||||
i_width->field_0x0 = 0;
|
||||
i_width->field_0x1 = mInf1Ptr->width;
|
||||
i_width->field_0x1 = R16(mInf1Ptr->width);
|
||||
|
||||
for (int i = 0; i < mWid1BlockNum; i++) {
|
||||
if (mpWidthBlocks[i]->startCode <= fontCode && fontCode <= mpWidthBlocks[i]->endCode) {
|
||||
*i_width = *(JUTFont::TWidth*)&mpWidthBlocks[i]->mChunkNum[(fontCode - mpWidthBlocks[i]->startCode) * 2];
|
||||
u16 sc = R16(mpWidthBlocks[i]->startCode);
|
||||
u16 ec = R16(mpWidthBlocks[i]->endCode);
|
||||
if (sc <= fontCode && fontCode <= ec) {
|
||||
// TWidth is two u8 fields, no byte-swap needed
|
||||
*i_width = *(JUTFont::TWidth*)&mpWidthBlocks[i]->mChunkNum[(fontCode - sc) * 2];
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -295,7 +317,7 @@ void JUTResFont::getWidthEntry(int code, JUTFont::TWidth* i_width) const {
|
||||
s32 JUTResFont::getCellWidth() const {
|
||||
if (mpGlyphBlocks) {
|
||||
if (mpGlyphBlocks[0]) {
|
||||
return mpGlyphBlocks[0]->cellWidth;
|
||||
return R16(mpGlyphBlocks[0]->cellWidth);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -305,7 +327,7 @@ s32 JUTResFont::getCellWidth() const {
|
||||
s32 JUTResFont::getCellHeight() const {
|
||||
if (mpGlyphBlocks) {
|
||||
if (mpGlyphBlocks[0]) {
|
||||
return mpGlyphBlocks[0]->cellHeight;
|
||||
return R16(mpGlyphBlocks[0]->cellHeight);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -328,43 +350,46 @@ int JUTResFont::getFontCode(int chr) const {
|
||||
0x8294, 0x8295, 0x8296, 0x8297, 0x8298, 0x8299, 0x829A, 0x816F, 0x8162, 0x8170, 0x8160,
|
||||
};
|
||||
|
||||
int ret = mInf1Ptr->defaultCode;
|
||||
int ret = R16(mInf1Ptr->defaultCode);
|
||||
if ((getFontType() == 2) && (mMaxCode >= 0x8000U) && (chr >= 0x20) && (chr < 0x7FU)) {
|
||||
chr = halftofull[chr - 32];
|
||||
}
|
||||
for (int i = 0; i < mMap1BlockNum; i++) {
|
||||
if ((mpMapBlocks[i]->startCode <= chr) && (chr <= mpMapBlocks[i]->endCode)) {
|
||||
if (mpMapBlocks[i]->mappingMethod == 0) {
|
||||
ret = chr - mpMapBlocks[i]->startCode;
|
||||
u16 sc = R16(mpMapBlocks[i]->startCode);
|
||||
u16 ec = R16(mpMapBlocks[i]->endCode);
|
||||
u16 mm = R16(mpMapBlocks[i]->mappingMethod);
|
||||
if ((sc <= chr) && (chr <= ec)) {
|
||||
if (mm == 0) {
|
||||
ret = chr - sc;
|
||||
break;
|
||||
} else if (mpMapBlocks[i]->mappingMethod == 2) {
|
||||
} else if (mm == 2) {
|
||||
u16* leading_temp = &mpMapBlocks[i]->mLeading;
|
||||
ret = leading_temp[chr - mpMapBlocks[i]->startCode];
|
||||
ret = R16(leading_temp[chr - sc]);
|
||||
break;
|
||||
} else if (mpMapBlocks[i]->mappingMethod == 3) {
|
||||
} else if (mm == 3) {
|
||||
u16* leading_temp = &mpMapBlocks[i]->mLeading;
|
||||
int phi_r5 = 0;
|
||||
int phi_r6_2 = mpMapBlocks[i]->numEntries - 1;
|
||||
int phi_r6_2 = R16(mpMapBlocks[i]->numEntries) - 1;
|
||||
|
||||
while (phi_r6_2 >= phi_r5) {
|
||||
int temp_r7 = (phi_r6_2 + phi_r5) / 2;
|
||||
|
||||
if (chr < leading_temp[temp_r7 * 2]) {
|
||||
if (chr < R16(leading_temp[temp_r7 * 2])) {
|
||||
phi_r6_2 = temp_r7 - 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (chr > leading_temp[temp_r7 * 2]) {
|
||||
if (chr > R16(leading_temp[temp_r7 * 2])) {
|
||||
phi_r5 = temp_r7 + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
ret = leading_temp[temp_r7 * 2 + 1];
|
||||
ret = R16(leading_temp[temp_r7 * 2 + 1]);
|
||||
break;
|
||||
}
|
||||
} else if (mpMapBlocks[i]->mappingMethod == 1) {
|
||||
} else if (mm == 1) {
|
||||
u16* phi_r5_2 = NULL;
|
||||
if (mpMapBlocks[i]->numEntries == 1) {
|
||||
if (R16(mpMapBlocks[i]->numEntries) == 1) {
|
||||
phi_r5_2 = &mpMapBlocks[i]->mLeading;
|
||||
}
|
||||
ret = convertSjis(chr, phi_r5_2);
|
||||
@@ -380,26 +405,37 @@ void JUTResFont::loadImage(int code, GXTexMapID id){
|
||||
int i = 0;
|
||||
for (; i < mGly1BlockNum; i++)
|
||||
{
|
||||
if (mpGlyphBlocks[i]->startCode <= code && code <= mpGlyphBlocks[i]->endCode)
|
||||
u16 sc = R16(mpGlyphBlocks[i]->startCode);
|
||||
u16 ec = R16(mpGlyphBlocks[i]->endCode);
|
||||
if (sc <= code && code <= ec)
|
||||
{
|
||||
code -= mpGlyphBlocks[i]->startCode;
|
||||
code -= sc;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i != mGly1BlockNum) {
|
||||
s32 pageNumCells = mpGlyphBlocks[i]->numRows * mpGlyphBlocks[i]->numColumns;
|
||||
u16 numRows = R16(mpGlyphBlocks[i]->numRows);
|
||||
u16 numCols = R16(mpGlyphBlocks[i]->numColumns);
|
||||
u16 cellW = R16(mpGlyphBlocks[i]->cellWidth);
|
||||
u16 cellH = R16(mpGlyphBlocks[i]->cellHeight);
|
||||
u32 texSize = R32(mpGlyphBlocks[i]->textureSize);
|
||||
u16 texW = R16(mpGlyphBlocks[i]->textureWidth);
|
||||
u16 texH = R16(mpGlyphBlocks[i]->textureHeight);
|
||||
u16 texFmt = R16(mpGlyphBlocks[i]->textureFormat);
|
||||
|
||||
s32 pageNumCells = numRows * numCols;
|
||||
s32 pageIdx = code / pageNumCells;
|
||||
s32 cellIdxInPage = code - pageIdx * pageNumCells;
|
||||
s32 cellRow = (cellIdxInPage / mpGlyphBlocks[i]->numRows);
|
||||
s32 cellCol = (cellIdxInPage - cellRow * mpGlyphBlocks[i]->numRows);
|
||||
mWidth = cellCol * mpGlyphBlocks[i]->cellWidth;
|
||||
mHeight = cellRow * mpGlyphBlocks[i]->cellHeight;
|
||||
s32 cellRow = (cellIdxInPage / numRows);
|
||||
s32 cellCol = (cellIdxInPage - cellRow * numRows);
|
||||
mWidth = cellCol * cellW;
|
||||
mHeight = cellRow * cellH;
|
||||
|
||||
if (pageIdx != mTexPageIdx || i != field_0x66)
|
||||
{
|
||||
GXInitTexObj(&mTexObj, &mpGlyphBlocks[i]->data[pageIdx * mpGlyphBlocks[i]->textureSize], mpGlyphBlocks[i]->textureWidth,
|
||||
mpGlyphBlocks[i]->textureHeight, (GXTexFmt)mpGlyphBlocks[i]->textureFormat, GX_CLAMP, GX_CLAMP, 0);
|
||||
GXInitTexObj(&mTexObj, &mpGlyphBlocks[i]->data[pageIdx * texSize], texW,
|
||||
texH, (GXTexFmt)texFmt, GX_CLAMP, GX_CLAMP, 0);
|
||||
|
||||
GXInitTexObjLOD(&mTexObj, GX_LINEAR, GX_LINEAR, 0.0f, 0.0f, 0.0f, 0U, 0U, GX_ANISO_1);
|
||||
mTexPageIdx = pageIdx;
|
||||
@@ -422,7 +458,7 @@ int JUTResFont::convertSjis(int inChr, u16* inLead) const {
|
||||
u16 lead = 0x31c;
|
||||
|
||||
if (inLead) {
|
||||
lead = *inLead;
|
||||
lead = R16(*inLead);
|
||||
}
|
||||
|
||||
r29 = tmp2 + (tmp - 0x88) * 0xbc + -0x5e + lead;
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
// OSContext.cpp - PC implementation of GameCube OSContext API
|
||||
// Replaces PowerPC assembly context switching with minimal PC stubs.
|
||||
// On PC there is no register-level context save/restore; the OS handles
|
||||
// thread contexts natively via std::thread.
|
||||
|
||||
#include <dolphin/dolphin.h>
|
||||
#include <dolphin/os.h>
|
||||
#include <cstring>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// --- Current context pointer (per-process, not per-thread) ---
|
||||
static OSContext* sCurrentContext = nullptr;
|
||||
|
||||
OSContext* OSGetCurrentContext(void) {
|
||||
return sCurrentContext;
|
||||
}
|
||||
|
||||
void OSSetCurrentContext(OSContext* context) {
|
||||
sCurrentContext = context;
|
||||
}
|
||||
|
||||
void OSClearContext(OSContext* context) {
|
||||
if (!context) return;
|
||||
context->mode = 0;
|
||||
context->state = 0;
|
||||
}
|
||||
|
||||
void OSInitContext(OSContext* context, u32 pc, u32 newsp) {
|
||||
if (!context) return;
|
||||
memset(context, 0, sizeof(OSContext));
|
||||
context->srr0 = pc;
|
||||
context->gpr[1] = newsp;
|
||||
}
|
||||
|
||||
u32 OSSaveContext(OSContext* context) {
|
||||
// On PC we don't save PowerPC registers.
|
||||
// Return 0 = "context was just saved" (as opposed to 1 = "restored from save").
|
||||
return 0;
|
||||
}
|
||||
|
||||
void OSLoadContext(OSContext* context) {
|
||||
// No-op on PC (no PowerPC register restore)
|
||||
}
|
||||
|
||||
void OSDumpContext(OSContext* context) {
|
||||
if (!context) {
|
||||
OSReport("[PC] OSDumpContext: NULL context\n");
|
||||
return;
|
||||
}
|
||||
OSReport("[PC] OSDumpContext: context at %p (no register info on PC)\n", context);
|
||||
}
|
||||
|
||||
void OSFillFPUContext(OSContext* context) {
|
||||
// No-op on PC (no PowerPC FPU state)
|
||||
}
|
||||
|
||||
void OSLoadFPUContext(OSContext* fpucontext) {
|
||||
// No-op on PC
|
||||
}
|
||||
|
||||
void OSSaveFPUContext(OSContext* fpucontext) {
|
||||
// No-op on PC
|
||||
}
|
||||
|
||||
u32 OSGetStackPointer(void) {
|
||||
// Return approximate stack pointer
|
||||
volatile u32 dummy;
|
||||
return (u32)(uintptr_t)&dummy;
|
||||
}
|
||||
|
||||
u32 OSSwitchStack(u32 newsp) {
|
||||
// Not meaningful on PC - return current sp
|
||||
return OSGetStackPointer();
|
||||
}
|
||||
|
||||
int OSSwitchFiber(u32 pc, u32 newsp) {
|
||||
// Not meaningful on PC
|
||||
OSReport("[PC] OSSwitchFiber: not supported on PC\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
void __OSContextInit(void) {
|
||||
// On GC this installs the FPU exception handler.
|
||||
// On PC nothing to do.
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,243 @@
|
||||
// OSMutex.cpp - PC implementation of GameCube OSMutex/OSCond API
|
||||
// Uses std::recursive_mutex and std::condition_variable_any behind the
|
||||
// unchanged GameCube C API. The OSMutex struct layout is preserved so
|
||||
// game code can read its fields.
|
||||
|
||||
#include <dolphin/dolphin.h>
|
||||
#include <dolphin/os.h>
|
||||
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <unordered_map>
|
||||
#include <memory>
|
||||
#include <cstdlib>
|
||||
|
||||
// ============================================================================
|
||||
// Malloc-based allocator to bypass JKRHeap operator new/delete
|
||||
// Without this, side-table allocations call operator new -> JKRHeap::alloc
|
||||
// -> OSLockMutex -> GetMutexData -> operator new ... infinite recursion.
|
||||
// ============================================================================
|
||||
|
||||
template<typename T>
|
||||
struct MallocAllocator {
|
||||
using value_type = T;
|
||||
MallocAllocator() = default;
|
||||
template<typename U> MallocAllocator(const MallocAllocator<U>&) noexcept {}
|
||||
T* allocate(std::size_t n) {
|
||||
void* p = std::malloc(n * sizeof(T));
|
||||
if (!p) throw std::bad_alloc();
|
||||
return static_cast<T*>(p);
|
||||
}
|
||||
void deallocate(T* p, std::size_t) noexcept { std::free(p); }
|
||||
template<typename U> bool operator==(const MallocAllocator<U>&) const noexcept { return true; }
|
||||
template<typename U> bool operator!=(const MallocAllocator<U>&) const noexcept { return false; }
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct MallocDeleter {
|
||||
void operator()(T* p) const {
|
||||
p->~T();
|
||||
std::free(p);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, typename... Args>
|
||||
std::unique_ptr<T, MallocDeleter<T>> make_malloc_unique(Args&&... args) {
|
||||
void* mem = std::malloc(sizeof(T));
|
||||
if (!mem) throw std::bad_alloc();
|
||||
T* obj = new (mem) T(std::forward<Args>(args)...);
|
||||
return std::unique_ptr<T, MallocDeleter<T>>(obj);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Side-table: native mutex per OSMutex
|
||||
// ============================================================================
|
||||
|
||||
struct PCMutexData {
|
||||
std::recursive_mutex nativeMutex;
|
||||
};
|
||||
|
||||
template<typename K, typename V>
|
||||
using MallocMap = std::unordered_map<K, V, std::hash<K>, std::equal_to<K>,
|
||||
MallocAllocator<std::pair<const K, V>>>;
|
||||
|
||||
// Lazy-initialized to avoid DLL static init crashes
|
||||
static std::mutex& GetMutexMapMutex() {
|
||||
static std::mutex mtx;
|
||||
return mtx;
|
||||
}
|
||||
static MallocMap<OSMutex*, std::unique_ptr<PCMutexData, MallocDeleter<PCMutexData>>>& GetMutexMap() {
|
||||
static MallocMap<OSMutex*, std::unique_ptr<PCMutexData, MallocDeleter<PCMutexData>>> map;
|
||||
return map;
|
||||
}
|
||||
|
||||
static PCMutexData& GetMutexData(OSMutex* mutex) {
|
||||
std::lock_guard<std::mutex> lock(GetMutexMapMutex());
|
||||
auto& map = GetMutexMap();
|
||||
auto it = map.find(mutex);
|
||||
if (it == map.end()) {
|
||||
auto result = map.emplace(mutex, make_malloc_unique<PCMutexData>());
|
||||
return *result.first->second;
|
||||
}
|
||||
return *it->second;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Side-table: native condition variable per OSCond
|
||||
// ============================================================================
|
||||
|
||||
struct PCCondData {
|
||||
std::condition_variable_any cv;
|
||||
};
|
||||
|
||||
// Lazy-initialized to avoid DLL static init crashes
|
||||
static std::mutex& GetCondMapMutex() {
|
||||
static std::mutex mtx;
|
||||
return mtx;
|
||||
}
|
||||
static MallocMap<OSCond*, std::unique_ptr<PCCondData, MallocDeleter<PCCondData>>>& GetCondMap() {
|
||||
static MallocMap<OSCond*, std::unique_ptr<PCCondData, MallocDeleter<PCCondData>>> map;
|
||||
return map;
|
||||
}
|
||||
|
||||
static PCCondData& GetCondData(OSCond* cond) {
|
||||
std::lock_guard<std::mutex> lock(GetCondMapMutex());
|
||||
auto& map = GetCondMap();
|
||||
auto it = map.find(cond);
|
||||
if (it == map.end()) {
|
||||
auto result = map.emplace(cond, make_malloc_unique<PCCondData>());
|
||||
return *result.first->second;
|
||||
}
|
||||
return *it->second;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// C API functions
|
||||
// ============================================================================
|
||||
|
||||
extern "C" {
|
||||
|
||||
void OSInitMutex(OSMutex* mutex) {
|
||||
if (!mutex) return;
|
||||
OSInitThreadQueue(&mutex->queue);
|
||||
mutex->thread = nullptr;
|
||||
mutex->count = 0;
|
||||
|
||||
// Create/reset side-table entry
|
||||
GetMutexData(mutex);
|
||||
}
|
||||
|
||||
void OSLockMutex(OSMutex* mutex) {
|
||||
if (!mutex) return;
|
||||
|
||||
PCMutexData& data = GetMutexData(mutex);
|
||||
data.nativeMutex.lock();
|
||||
|
||||
// Update GC-visible fields
|
||||
OSThread* currentThread = OSGetCurrentThread();
|
||||
mutex->thread = currentThread;
|
||||
mutex->count++;
|
||||
}
|
||||
|
||||
void OSUnlockMutex(OSMutex* mutex) {
|
||||
if (!mutex) return;
|
||||
|
||||
OSThread* currentThread = OSGetCurrentThread();
|
||||
if (mutex->thread != currentThread) return;
|
||||
|
||||
mutex->count--;
|
||||
if (mutex->count == 0) {
|
||||
mutex->thread = nullptr;
|
||||
}
|
||||
|
||||
PCMutexData& data = GetMutexData(mutex);
|
||||
data.nativeMutex.unlock();
|
||||
}
|
||||
|
||||
BOOL OSTryLockMutex(OSMutex* mutex) {
|
||||
if (!mutex) return FALSE;
|
||||
|
||||
PCMutexData& data = GetMutexData(mutex);
|
||||
if (data.nativeMutex.try_lock()) {
|
||||
OSThread* currentThread = OSGetCurrentThread();
|
||||
mutex->thread = currentThread;
|
||||
mutex->count++;
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Internal: unlock all mutexes held by a thread (called on thread exit)
|
||||
// ============================================================================
|
||||
|
||||
void __OSUnlockAllMutex(OSThread* thread) {
|
||||
// On GC this walks the thread's mutex queue.
|
||||
// On PC the native mutexes are cleaned up when threads exit.
|
||||
// Clear the GC-visible queue.
|
||||
if (!thread) return;
|
||||
thread->queueMutex.head = nullptr;
|
||||
thread->queueMutex.tail = nullptr;
|
||||
}
|
||||
|
||||
int __OSCheckDeadLock(OSThread* thread) {
|
||||
// Simplified: native OS handles deadlock detection.
|
||||
return 0;
|
||||
}
|
||||
|
||||
int __OSCheckMutexes(OSThread* thread) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Condition Variable API
|
||||
// ============================================================================
|
||||
|
||||
void OSInitCond(OSCond* cond) {
|
||||
if (!cond) return;
|
||||
OSInitThreadQueue(&cond->queue);
|
||||
GetCondData(cond);
|
||||
}
|
||||
|
||||
void OSWaitCond(OSCond* cond, OSMutex* mutex) {
|
||||
if (!cond || !mutex) return;
|
||||
|
||||
PCCondData& condData = GetCondData(cond);
|
||||
PCMutexData& mutexData = GetMutexData(mutex);
|
||||
|
||||
// Save and clear the GC mutex state
|
||||
OSThread* currentThread = OSGetCurrentThread();
|
||||
s32 savedCount = mutex->count;
|
||||
mutex->count = 0;
|
||||
mutex->thread = nullptr;
|
||||
|
||||
// Unlock the recursive mutex the same number of times it was locked
|
||||
for (s32 i = 0; i < savedCount; i++) {
|
||||
mutexData.nativeMutex.unlock();
|
||||
}
|
||||
|
||||
// Wait on the condition variable
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> lock(mutexData.nativeMutex);
|
||||
condData.cv.wait(lock);
|
||||
}
|
||||
|
||||
// Re-lock the recursive mutex the same number of times
|
||||
for (s32 i = 0; i < savedCount; i++) {
|
||||
mutexData.nativeMutex.lock();
|
||||
}
|
||||
|
||||
// Restore GC mutex state
|
||||
mutex->thread = currentThread;
|
||||
mutex->count = savedCount;
|
||||
}
|
||||
|
||||
void OSSignalCond(OSCond* cond) {
|
||||
if (!cond) return;
|
||||
PCCondData& condData = GetCondData(cond);
|
||||
condData.cv.notify_all();
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,650 @@
|
||||
// OSThread.cpp - PC implementation of GameCube OSThread API
|
||||
// Maps GameCube cooperative threading to native OS threads via std::thread.
|
||||
// The OSThread struct layout is preserved so game code can read its fields.
|
||||
// A side-table stores the native std::thread and synchronization primitives.
|
||||
|
||||
#include <dolphin/dolphin.h>
|
||||
#include <dolphin/os.h>
|
||||
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <unordered_map>
|
||||
#include <atomic>
|
||||
#include <cstring>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
|
||||
// ============================================================================
|
||||
// Malloc-based allocator to bypass JKRHeap operator new/delete
|
||||
// ============================================================================
|
||||
|
||||
template<typename T>
|
||||
struct MallocAllocator {
|
||||
using value_type = T;
|
||||
MallocAllocator() = default;
|
||||
template<typename U> MallocAllocator(const MallocAllocator<U>&) noexcept {}
|
||||
T* allocate(std::size_t n) {
|
||||
void* p = std::malloc(n * sizeof(T));
|
||||
if (!p) throw std::bad_alloc();
|
||||
return static_cast<T*>(p);
|
||||
}
|
||||
void deallocate(T* p, std::size_t) noexcept { std::free(p); }
|
||||
template<typename U> bool operator==(const MallocAllocator<U>&) const noexcept { return true; }
|
||||
template<typename U> bool operator!=(const MallocAllocator<U>&) const noexcept { return false; }
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct MallocDeleter {
|
||||
void operator()(T* p) const {
|
||||
p->~T();
|
||||
std::free(p);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, typename... Args>
|
||||
std::unique_ptr<T, MallocDeleter<T>> make_malloc_unique(Args&&... args) {
|
||||
void* mem = std::malloc(sizeof(T));
|
||||
if (!mem) throw std::bad_alloc();
|
||||
T* obj = new (mem) T(std::forward<Args>(args)...);
|
||||
return std::unique_ptr<T, MallocDeleter<T>>(obj);
|
||||
}
|
||||
|
||||
template<typename K, typename V>
|
||||
using MallocMap = std::unordered_map<K, V, std::hash<K>, std::equal_to<K>,
|
||||
MallocAllocator<std::pair<const K, V>>>;
|
||||
|
||||
// ============================================================================
|
||||
// Side-table: native thread data per OSThread
|
||||
// ============================================================================
|
||||
|
||||
struct PCThreadData {
|
||||
std::thread nativeThread;
|
||||
std::mutex mtx;
|
||||
std::condition_variable cv;
|
||||
void* (*func)(void*);
|
||||
void* param;
|
||||
bool started = false;
|
||||
bool suspended = false;
|
||||
};
|
||||
|
||||
// Lazy-initialized to avoid DLL static init crashes (used before DllMain completes)
|
||||
static std::mutex& GetThreadDataMutex() {
|
||||
static std::mutex mtx;
|
||||
return mtx;
|
||||
}
|
||||
static MallocMap<OSThread*, std::unique_ptr<PCThreadData, MallocDeleter<PCThreadData>>>& GetThreadDataMap() {
|
||||
static MallocMap<OSThread*, std::unique_ptr<PCThreadData, MallocDeleter<PCThreadData>>> map;
|
||||
return map;
|
||||
}
|
||||
|
||||
// Side-table for OSThreadQueue -> condition_variable (for OSSleepThread/OSWakeupThread)
|
||||
static std::mutex& GetQueueCvMutex() {
|
||||
static std::mutex mtx;
|
||||
return mtx;
|
||||
}
|
||||
static MallocMap<OSThreadQueue*, std::unique_ptr<std::condition_variable, MallocDeleter<std::condition_variable>>>& GetQueueCvMap() {
|
||||
static MallocMap<OSThreadQueue*, std::unique_ptr<std::condition_variable, MallocDeleter<std::condition_variable>>> map;
|
||||
return map;
|
||||
}
|
||||
|
||||
static std::condition_variable& GetQueueCV(OSThreadQueue* queue) {
|
||||
std::lock_guard<std::mutex> lock(GetQueueCvMutex());
|
||||
auto& map = GetQueueCvMap();
|
||||
auto it = map.find(queue);
|
||||
if (it == map.end()) {
|
||||
auto result = map.emplace(queue, make_malloc_unique<std::condition_variable>());
|
||||
return *result.first->second;
|
||||
}
|
||||
return *it->second;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Thread-local current thread pointer
|
||||
// ============================================================================
|
||||
|
||||
static thread_local OSThread* tls_currentThread = nullptr;
|
||||
|
||||
// ============================================================================
|
||||
// Global state
|
||||
// ============================================================================
|
||||
|
||||
static OSThread sDefaultThread;
|
||||
static u8 sDefaultStack[64 * 1024];
|
||||
static u32 sDefaultStackEnd = OS_THREAD_STACK_MAGIC;
|
||||
|
||||
OSThreadQueue __OSActiveThreadQueue;
|
||||
|
||||
// Global interrupt mutex (coarse-grained lock replacing interrupt disable)
|
||||
// Lazy-initialized to avoid DLL static init crashes
|
||||
static std::recursive_mutex& GetInterruptMutex() {
|
||||
static std::recursive_mutex mtx;
|
||||
return mtx;
|
||||
}
|
||||
static thread_local int sInterruptLockCount = 0;
|
||||
|
||||
// Scheduler suspend count
|
||||
static std::atomic<s32> sSchedulerSuspendCount{0};
|
||||
|
||||
// Active thread count
|
||||
static std::atomic<s32> sActiveThreadCount{0};
|
||||
|
||||
// Switch thread callback
|
||||
static OSSwitchThreadCallback sSwitchThreadCallback = nullptr;
|
||||
|
||||
// ============================================================================
|
||||
// Internal helpers
|
||||
// ============================================================================
|
||||
|
||||
// Linked list macros for the active thread queue
|
||||
static void EnqueueActive(OSThread* thread) {
|
||||
OSThread* prev = __OSActiveThreadQueue.tail;
|
||||
if (prev == nullptr) {
|
||||
__OSActiveThreadQueue.head = thread;
|
||||
} else {
|
||||
prev->linkActive.next = thread;
|
||||
}
|
||||
thread->linkActive.prev = prev;
|
||||
thread->linkActive.next = nullptr;
|
||||
__OSActiveThreadQueue.tail = thread;
|
||||
}
|
||||
|
||||
static void DequeueActive(OSThread* thread) {
|
||||
OSThread* next = thread->linkActive.next;
|
||||
OSThread* prev = thread->linkActive.prev;
|
||||
if (next == nullptr) {
|
||||
__OSActiveThreadQueue.tail = prev;
|
||||
} else {
|
||||
next->linkActive.prev = prev;
|
||||
}
|
||||
if (prev == nullptr) {
|
||||
__OSActiveThreadQueue.head = next;
|
||||
} else {
|
||||
prev->linkActive.next = next;
|
||||
}
|
||||
thread->linkActive.next = nullptr;
|
||||
thread->linkActive.prev = nullptr;
|
||||
}
|
||||
|
||||
// Thread entry wrapper - runs on the new std::thread
|
||||
static void ThreadEntryWrapper(OSThread* thread, PCThreadData* data) {
|
||||
// Set thread-local pointer
|
||||
tls_currentThread = thread;
|
||||
|
||||
// Set context pointers for this thread
|
||||
OSClearContext(&thread->context);
|
||||
OSSetCurrentContext(&thread->context);
|
||||
|
||||
thread->state = OS_THREAD_STATE_RUNNING;
|
||||
|
||||
// Call the actual thread function
|
||||
void* result = data->func(data->param);
|
||||
|
||||
// Thread returned - equivalent to OSExitThread
|
||||
thread->val = result;
|
||||
thread->state = OS_THREAD_STATE_MORIBUND;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// C API functions
|
||||
// ============================================================================
|
||||
|
||||
extern "C" {
|
||||
|
||||
void __OSThreadInit(void) {
|
||||
memset(&sDefaultThread, 0, sizeof(OSThread));
|
||||
|
||||
sDefaultThread.state = OS_THREAD_STATE_RUNNING;
|
||||
sDefaultThread.attr = OS_THREAD_ATTR_DETACH;
|
||||
sDefaultThread.priority = 16;
|
||||
sDefaultThread.base = 16;
|
||||
sDefaultThread.suspend = 0;
|
||||
sDefaultThread.val = (void*)(intptr_t)-1;
|
||||
sDefaultThread.mutex = nullptr;
|
||||
sDefaultThread.queue = nullptr;
|
||||
|
||||
OSInitThreadQueue(&sDefaultThread.queueJoin);
|
||||
sDefaultThread.queueMutex.head = sDefaultThread.queueMutex.tail = nullptr;
|
||||
sDefaultThread.link.next = sDefaultThread.link.prev = nullptr;
|
||||
sDefaultThread.linkActive.next = sDefaultThread.linkActive.prev = nullptr;
|
||||
|
||||
// Stack pointers (JKRThread reads these)
|
||||
sDefaultThread.stackBase = sDefaultStack + sizeof(sDefaultStack);
|
||||
sDefaultThread.stackEnd = &sDefaultStackEnd;
|
||||
sDefaultStackEnd = OS_THREAD_STACK_MAGIC;
|
||||
|
||||
OSClearContext(&sDefaultThread.context);
|
||||
|
||||
sDefaultThread.error = 0;
|
||||
sDefaultThread.specific[0] = nullptr;
|
||||
sDefaultThread.specific[1] = nullptr;
|
||||
|
||||
// Set as current thread for main thread
|
||||
tls_currentThread = &sDefaultThread;
|
||||
|
||||
// Active queue
|
||||
OSInitThreadQueue(&__OSActiveThreadQueue);
|
||||
EnqueueActive(&sDefaultThread);
|
||||
sActiveThreadCount = 1;
|
||||
|
||||
OSReport("[PC-OSThread] Thread system initialized (multi-threaded mode)\n");
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Thread Queue
|
||||
// ============================================================================
|
||||
|
||||
void OSInitThreadQueue(OSThreadQueue* queue) {
|
||||
if (queue) {
|
||||
queue->head = queue->tail = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Current Thread
|
||||
// ============================================================================
|
||||
|
||||
OSThread* OSGetCurrentThread(void) {
|
||||
// Lazy-init for main thread if __OSThreadInit hasn't been called yet
|
||||
if (tls_currentThread == nullptr) {
|
||||
__OSThreadInit();
|
||||
}
|
||||
return tls_currentThread;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Thread Creation
|
||||
// ============================================================================
|
||||
|
||||
int OSCreateThread(OSThread* thread, void* (*func)(void*), void* param,
|
||||
void* stack, u32 stackSize, OSPriority priority, u16 attr) {
|
||||
if (!thread) return 0;
|
||||
if (priority < OS_PRIORITY_MIN || priority > OS_PRIORITY_MAX) return 0;
|
||||
|
||||
// Ensure thread system is initialized
|
||||
OSGetCurrentThread();
|
||||
|
||||
memset(thread, 0, sizeof(OSThread));
|
||||
|
||||
thread->state = OS_THREAD_STATE_READY;
|
||||
thread->attr = attr & 1u;
|
||||
thread->base = priority;
|
||||
thread->priority = priority;
|
||||
thread->suspend = 1; // Created suspended (GC behavior)
|
||||
thread->val = (void*)(intptr_t)-1;
|
||||
thread->mutex = nullptr;
|
||||
|
||||
OSInitThreadQueue(&thread->queueJoin);
|
||||
thread->queueMutex.head = thread->queueMutex.tail = nullptr;
|
||||
thread->link.next = thread->link.prev = nullptr;
|
||||
thread->linkActive.next = thread->linkActive.prev = nullptr;
|
||||
|
||||
// Stack (stack points to TOP on GameCube)
|
||||
thread->stackBase = (u8*)stack;
|
||||
thread->stackEnd = (u32*)((uintptr_t)stack - stackSize);
|
||||
*thread->stackEnd = OS_THREAD_STACK_MAGIC;
|
||||
|
||||
OSClearContext(&thread->context);
|
||||
|
||||
thread->error = 0;
|
||||
thread->specific[0] = nullptr;
|
||||
thread->specific[1] = nullptr;
|
||||
|
||||
// Create side-table entry (but don't start the thread yet)
|
||||
{
|
||||
auto data = make_malloc_unique<PCThreadData>();
|
||||
data->func = func;
|
||||
data->param = param;
|
||||
|
||||
std::lock_guard<std::mutex> lock(GetThreadDataMutex());
|
||||
GetThreadDataMap()[thread] = std::move(data);
|
||||
}
|
||||
|
||||
// Add to active queue
|
||||
EnqueueActive(thread);
|
||||
sActiveThreadCount++;
|
||||
|
||||
OSReport("[PC-OSThread] Created thread %p (priority=%d, stackSize=%u)\n",
|
||||
thread, priority, stackSize);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Resume / Suspend
|
||||
// ============================================================================
|
||||
|
||||
s32 OSResumeThread(OSThread* thread) {
|
||||
if (!thread) return 0;
|
||||
|
||||
s32 prevSuspend = thread->suspend;
|
||||
if (thread->suspend > 0) {
|
||||
thread->suspend--;
|
||||
}
|
||||
|
||||
if (thread->suspend == 0) {
|
||||
std::lock_guard<std::mutex> lock(GetThreadDataMutex());
|
||||
auto it = GetThreadDataMap().find(thread);
|
||||
if (it != GetThreadDataMap().end()) {
|
||||
PCThreadData* data = it->second.get();
|
||||
if (!data->started) {
|
||||
// First resume: launch the native thread
|
||||
data->started = true;
|
||||
data->suspended = false;
|
||||
data->nativeThread = std::thread(ThreadEntryWrapper, thread, data);
|
||||
data->nativeThread.detach();
|
||||
OSReport("[PC-OSThread] Started thread %p\n", thread);
|
||||
} else if (data->suspended) {
|
||||
// Resume from suspension: signal the CV
|
||||
data->suspended = false;
|
||||
data->cv.notify_one();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return prevSuspend;
|
||||
}
|
||||
|
||||
s32 OSSuspendThread(OSThread* thread) {
|
||||
if (!thread) return 0;
|
||||
|
||||
s32 prevSuspend = thread->suspend;
|
||||
thread->suspend++;
|
||||
|
||||
if (prevSuspend == 0) {
|
||||
std::lock_guard<std::mutex> lock(GetThreadDataMutex());
|
||||
auto it = GetThreadDataMap().find(thread);
|
||||
if (it != GetThreadDataMap().end()) {
|
||||
PCThreadData* data = it->second.get();
|
||||
if (data->started) {
|
||||
data->suspended = true;
|
||||
// The thread must check its suspended flag and wait
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return prevSuspend;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Sleep / Wakeup (thread queue based)
|
||||
// ============================================================================
|
||||
|
||||
void OSSleepThread(OSThreadQueue* queue) {
|
||||
if (!queue) return;
|
||||
|
||||
OSThread* currentThread = OSGetCurrentThread();
|
||||
if (!currentThread) return;
|
||||
|
||||
currentThread->state = OS_THREAD_STATE_WAITING;
|
||||
currentThread->queue = queue;
|
||||
|
||||
// Enqueue into the thread queue
|
||||
OSThread* prev = queue->tail;
|
||||
if (prev == nullptr) {
|
||||
queue->head = currentThread;
|
||||
} else {
|
||||
prev->link.next = currentThread;
|
||||
}
|
||||
currentThread->link.prev = prev;
|
||||
currentThread->link.next = nullptr;
|
||||
queue->tail = currentThread;
|
||||
|
||||
// Wait on the condition variable for this queue
|
||||
std::condition_variable& cv = GetQueueCV(queue);
|
||||
std::unique_lock<std::mutex> lock(GetQueueCvMutex());
|
||||
cv.wait(lock, [currentThread]() {
|
||||
return currentThread->state != OS_THREAD_STATE_WAITING;
|
||||
});
|
||||
}
|
||||
|
||||
void OSWakeupThread(OSThreadQueue* queue) {
|
||||
if (!queue) return;
|
||||
|
||||
// Wake all threads in the queue
|
||||
OSThread* thread = queue->head;
|
||||
while (thread) {
|
||||
OSThread* next = thread->link.next;
|
||||
thread->state = OS_THREAD_STATE_READY;
|
||||
thread->link.next = nullptr;
|
||||
thread->link.prev = nullptr;
|
||||
thread->queue = nullptr;
|
||||
thread = next;
|
||||
}
|
||||
queue->head = queue->tail = nullptr;
|
||||
|
||||
// Notify all waiters
|
||||
std::condition_variable& cv = GetQueueCV(queue);
|
||||
cv.notify_all();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Exit / Cancel / Detach / Join
|
||||
// ============================================================================
|
||||
|
||||
void OSExitThread(void* val) {
|
||||
OSThread* currentThread = OSGetCurrentThread();
|
||||
if (!currentThread) return;
|
||||
|
||||
currentThread->val = val;
|
||||
|
||||
if (currentThread->attr & OS_THREAD_ATTR_DETACH) {
|
||||
DequeueActive(currentThread);
|
||||
currentThread->state = 0;
|
||||
} else {
|
||||
currentThread->state = OS_THREAD_STATE_MORIBUND;
|
||||
}
|
||||
|
||||
// Wake anyone waiting to join
|
||||
OSWakeupThread(¤tThread->queueJoin);
|
||||
sActiveThreadCount--;
|
||||
}
|
||||
|
||||
void OSCancelThread(OSThread* thread) {
|
||||
if (!thread) return;
|
||||
|
||||
if (thread->attr & OS_THREAD_ATTR_DETACH) {
|
||||
DequeueActive(thread);
|
||||
thread->state = 0;
|
||||
} else {
|
||||
thread->state = OS_THREAD_STATE_MORIBUND;
|
||||
}
|
||||
|
||||
OSWakeupThread(&thread->queueJoin);
|
||||
sActiveThreadCount--;
|
||||
}
|
||||
|
||||
void OSDetachThread(OSThread* thread) {
|
||||
if (!thread) return;
|
||||
thread->attr |= OS_THREAD_ATTR_DETACH;
|
||||
|
||||
if (thread->state == OS_THREAD_STATE_MORIBUND) {
|
||||
DequeueActive(thread);
|
||||
thread->state = 0;
|
||||
}
|
||||
OSWakeupThread(&thread->queueJoin);
|
||||
}
|
||||
|
||||
int OSJoinThread(OSThread* thread, void* val) {
|
||||
if (!thread) return 0;
|
||||
|
||||
if (!(thread->attr & OS_THREAD_ATTR_DETACH) &&
|
||||
thread->state != OS_THREAD_STATE_MORIBUND &&
|
||||
thread->queueJoin.head == nullptr) {
|
||||
OSSleepThread(&thread->queueJoin);
|
||||
}
|
||||
|
||||
if (thread->state == OS_THREAD_STATE_MORIBUND) {
|
||||
if (val) {
|
||||
*(s32*)val = (s32)(intptr_t)thread->val;
|
||||
}
|
||||
DequeueActive(thread);
|
||||
thread->state = 0;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Yield / Terminated / Active
|
||||
// ============================================================================
|
||||
|
||||
void OSYieldThread(void) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
|
||||
BOOL OSIsThreadSuspended(OSThread* thread) {
|
||||
return (thread && thread->suspend > 0) ? TRUE : FALSE;
|
||||
}
|
||||
|
||||
BOOL OSIsThreadTerminated(OSThread* thread) {
|
||||
if (!thread) return TRUE;
|
||||
return (thread->state == OS_THREAD_STATE_MORIBUND || thread->state == 0) ? TRUE : FALSE;
|
||||
}
|
||||
|
||||
s32 OSCheckActiveThreads(void) {
|
||||
return sActiveThreadCount.load();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Priority
|
||||
// ============================================================================
|
||||
|
||||
int OSSetThreadPriority(OSThread* thread, OSPriority priority) {
|
||||
if (!thread) return 0;
|
||||
if (priority < OS_PRIORITY_MIN || priority > OS_PRIORITY_MAX) return 0;
|
||||
thread->base = priority;
|
||||
thread->priority = priority;
|
||||
return 1;
|
||||
}
|
||||
|
||||
s32 OSGetThreadPriority(OSThread* thread) {
|
||||
if (!thread) return 16;
|
||||
return thread->base;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Switch Thread Callback
|
||||
// ============================================================================
|
||||
|
||||
OSSwitchThreadCallback OSSetSwitchThreadCallback(OSSwitchThreadCallback callback) {
|
||||
OSSwitchThreadCallback prev = sSwitchThreadCallback;
|
||||
sSwitchThreadCallback = callback;
|
||||
return prev;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Scheduler (atomic counter, no real effect with native OS threads)
|
||||
// ============================================================================
|
||||
|
||||
s32 OSDisableScheduler(void) {
|
||||
return sSchedulerSuspendCount.fetch_add(1);
|
||||
}
|
||||
|
||||
s32 OSEnableScheduler(void) {
|
||||
return sSchedulerSuspendCount.fetch_sub(1);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Interrupts (global recursive mutex for mutual exclusion)
|
||||
// ============================================================================
|
||||
|
||||
BOOL OSDisableInterrupts(void) {
|
||||
GetInterruptMutex().lock();
|
||||
sInterruptLockCount++;
|
||||
return (BOOL)(sInterruptLockCount > 1); // TRUE if was already locked
|
||||
}
|
||||
|
||||
BOOL OSRestoreInterrupts(BOOL level) {
|
||||
if (sInterruptLockCount > 0) {
|
||||
sInterruptLockCount--;
|
||||
GetInterruptMutex().unlock();
|
||||
}
|
||||
return level;
|
||||
}
|
||||
|
||||
BOOL OSEnableInterrupts(void) {
|
||||
if (sInterruptLockCount > 0) {
|
||||
sInterruptLockCount--;
|
||||
GetInterruptMutex().unlock();
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Idle function (stub on PC)
|
||||
// ============================================================================
|
||||
|
||||
OSThread* OSSetIdleFunction(OSIdleFunction idleFunction, void* param, void* stack, u32 stackSize) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
OSThread* OSGetIdleFunction(void) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Thread-specific storage
|
||||
// ============================================================================
|
||||
|
||||
void OSSetThreadSpecific(s32 index, void* ptr) {
|
||||
OSThread* thread = OSGetCurrentThread();
|
||||
if (thread && index >= 0 && index < OS_THREAD_SPECIFIC_MAX) {
|
||||
thread->specific[index] = ptr;
|
||||
}
|
||||
}
|
||||
|
||||
void* OSGetThreadSpecific(s32 index) {
|
||||
OSThread* thread = OSGetCurrentThread();
|
||||
if (thread && index >= 0 && index < OS_THREAD_SPECIFIC_MAX) {
|
||||
return thread->specific[index];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Clear stack (minimal implementation)
|
||||
// ============================================================================
|
||||
|
||||
void OSClearStack(u8 val) {
|
||||
// On PC we don't clear the stack - it's managed by the OS
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Internal functions used by OSMutex
|
||||
// ============================================================================
|
||||
|
||||
s32 __OSGetEffectivePriority(OSThread* thread) {
|
||||
// On PC with native threads, priority inversion handling is simplified.
|
||||
// Just return the base priority.
|
||||
return thread ? thread->base : 16;
|
||||
}
|
||||
|
||||
void __OSPromoteThread(OSThread* thread, s32 priority) {
|
||||
// Simplified: no real priority inheritance on PC
|
||||
if (thread && priority < thread->priority) {
|
||||
thread->priority = priority;
|
||||
}
|
||||
}
|
||||
|
||||
void __OSReschedule(void) {
|
||||
// With native OS threads, rescheduling is handled by the OS.
|
||||
// Nothing to do here.
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Interrupt handler registration (stub)
|
||||
// ============================================================================
|
||||
|
||||
__OSInterruptHandler __OSSetInterruptHandler(__OSInterrupt interrupt,
|
||||
__OSInterruptHandler handler) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
OSInterruptMask __OSUnmaskInterrupts(OSInterruptMask mask) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
+309
-324
@@ -2,17 +2,18 @@
|
||||
#include <dolphin/gx/GXVert.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <cstdlib>
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <unordered_map>
|
||||
#include <memory>
|
||||
#include <dusk/dvd_emu.h>
|
||||
|
||||
// Credits: Super Monkey Ball
|
||||
|
||||
/*
|
||||
void OSReport(const char* msg, ...) {
|
||||
va_list args;
|
||||
va_start(args, msg);
|
||||
vprintf(msg, args);
|
||||
va_end(args);
|
||||
}
|
||||
*/
|
||||
// ==========================================================================
|
||||
// General OS
|
||||
// ==========================================================================
|
||||
|
||||
u32 OSGetConsoleType() {
|
||||
return OS_CONSOLE_RETAIL1;
|
||||
@@ -22,219 +23,155 @@ u32 OSGetSoundMode() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
// Consolidated OS functions (moved from other sections)
|
||||
void OSClearContext(OSContext* context) {
|
||||
puts("OSClearContext is a stub");
|
||||
}
|
||||
|
||||
void OSInit() {
|
||||
puts("OSInit is a stub");
|
||||
// Thread system is lazy-initialized via OSGetCurrentThread()
|
||||
}
|
||||
|
||||
void OSInitMutex(OSMutex* mutex) {
|
||||
puts("OSInitMutex is a stub");
|
||||
// ==========================================================================
|
||||
// Message Queue (thread-safe implementation)
|
||||
// ==========================================================================
|
||||
|
||||
// Malloc-based allocator to bypass JKRHeap operator new/delete
|
||||
template<typename T>
|
||||
struct MallocAllocator {
|
||||
using value_type = T;
|
||||
MallocAllocator() = default;
|
||||
template<typename U> MallocAllocator(const MallocAllocator<U>&) noexcept {}
|
||||
T* allocate(std::size_t n) {
|
||||
void* p = std::malloc(n * sizeof(T));
|
||||
if (!p) throw std::bad_alloc();
|
||||
return static_cast<T*>(p);
|
||||
}
|
||||
void deallocate(T* p, std::size_t) noexcept { std::free(p); }
|
||||
template<typename U> bool operator==(const MallocAllocator<U>&) const noexcept { return true; }
|
||||
template<typename U> bool operator!=(const MallocAllocator<U>&) const noexcept { return false; }
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct MallocDeleter {
|
||||
void operator()(T* p) const {
|
||||
p->~T();
|
||||
std::free(p);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, typename... Args>
|
||||
std::unique_ptr<T, MallocDeleter<T>> make_malloc_unique(Args&&... args) {
|
||||
void* mem = std::malloc(sizeof(T));
|
||||
if (!mem) throw std::bad_alloc();
|
||||
T* obj = new (mem) T(std::forward<Args>(args)...);
|
||||
return std::unique_ptr<T, MallocDeleter<T>>(obj);
|
||||
}
|
||||
|
||||
void OSUnlockMutex(OSMutex* mutex) {
|
||||
puts("OSUnlockMutex is a stub");
|
||||
template<typename K, typename V>
|
||||
using MallocMap = std::unordered_map<K, V, std::hash<K>, std::equal_to<K>,
|
||||
MallocAllocator<std::pair<const K, V>>>;
|
||||
|
||||
// Side-table for native synchronization per OSMessageQueue
|
||||
struct PCMessageQueueData {
|
||||
std::mutex mtx;
|
||||
std::condition_variable cvSend; // Notified when space becomes available
|
||||
std::condition_variable cvReceive; // Notified when a message arrives
|
||||
};
|
||||
|
||||
// Lazy-initialized to avoid DLL static init crashes
|
||||
static std::mutex& GetMsgQueueMapMutex() {
|
||||
static std::mutex mtx;
|
||||
return mtx;
|
||||
}
|
||||
static MallocMap<OSMessageQueue*, std::unique_ptr<PCMessageQueueData, MallocDeleter<PCMessageQueueData>>>& GetMsgQueueMap() {
|
||||
static MallocMap<OSMessageQueue*, std::unique_ptr<PCMessageQueueData, MallocDeleter<PCMessageQueueData>>> map;
|
||||
return map;
|
||||
}
|
||||
|
||||
BOOL OSTryLockMutex(OSMutex* mutex) {
|
||||
puts("OSTryLockMutex is a stub");
|
||||
return FALSE;
|
||||
static PCMessageQueueData& GetMsgQueueData(OSMessageQueue* mq) {
|
||||
std::lock_guard<std::mutex> lock(GetMsgQueueMapMutex());
|
||||
auto& map = GetMsgQueueMap();
|
||||
auto it = map.find(mq);
|
||||
if (it == map.end()) {
|
||||
auto result = map.emplace(mq, make_malloc_unique<PCMessageQueueData>());
|
||||
return *result.first->second;
|
||||
}
|
||||
return *it->second;
|
||||
}
|
||||
|
||||
void* OSAllocFromArenaLo(u32 size, u32 align) {
|
||||
puts("OSAllocFromArenaLo is a stub");
|
||||
return NULL;
|
||||
void OSInitMessageQueue(OSMessageQueue* mq, void* msgArray, s32 msgCount) {
|
||||
if (!mq) return;
|
||||
mq->queueSend.head = mq->queueSend.tail = nullptr;
|
||||
mq->queueReceive.head = mq->queueReceive.tail = nullptr;
|
||||
mq->msgArray = msgArray;
|
||||
mq->msgCount = msgCount;
|
||||
mq->firstIndex = 0;
|
||||
mq->usedCount = 0;
|
||||
GetMsgQueueData(mq); // Ensure side-table entry exists
|
||||
}
|
||||
|
||||
BOOL OSDisableInterrupts() {
|
||||
puts("OSDisableInterrupts is a stub");
|
||||
return FALSE;
|
||||
int OSSendMessage(OSMessageQueue* mq, void* msg, s32 flags) {
|
||||
if (!mq) return 0;
|
||||
|
||||
PCMessageQueueData& data = GetMsgQueueData(mq);
|
||||
std::unique_lock<std::mutex> lock(data.mtx);
|
||||
|
||||
if (mq->usedCount >= mq->msgCount) {
|
||||
if (flags == OS_MESSAGE_NOBLOCK) return 0;
|
||||
// BLOCK: wait until space is available
|
||||
data.cvSend.wait(lock, [mq]() { return mq->usedCount < mq->msgCount; });
|
||||
}
|
||||
|
||||
s32 idx = (mq->firstIndex + mq->usedCount) % mq->msgCount;
|
||||
((OSMessage*)mq->msgArray)[idx] = msg;
|
||||
mq->usedCount++;
|
||||
|
||||
data.cvReceive.notify_one();
|
||||
return 1;
|
||||
}
|
||||
|
||||
void OSSleepThread(OSThreadQueue* queue) {
|
||||
puts("OSSleepThread is a stub");
|
||||
int OSReceiveMessage(OSMessageQueue* mq, void* msg, s32 flags) {
|
||||
if (!mq) return 0;
|
||||
|
||||
PCMessageQueueData& data = GetMsgQueueData(mq);
|
||||
std::unique_lock<std::mutex> lock(data.mtx);
|
||||
|
||||
if (mq->usedCount == 0) {
|
||||
if (flags == OS_MESSAGE_NOBLOCK) return 0;
|
||||
// BLOCK: wait until a message arrives
|
||||
data.cvReceive.wait(lock, [mq]() { return mq->usedCount > 0; });
|
||||
}
|
||||
|
||||
if (msg) {
|
||||
*(OSMessage*)msg = ((OSMessage*)mq->msgArray)[mq->firstIndex];
|
||||
}
|
||||
mq->firstIndex = (mq->firstIndex + 1) % mq->msgCount;
|
||||
mq->usedCount--;
|
||||
|
||||
data.cvSend.notify_one();
|
||||
return 1;
|
||||
}
|
||||
|
||||
void OSDumpContext(OSContext* context) {
|
||||
puts("OSDumpContext is a stub");
|
||||
int OSJamMessage(OSMessageQueue* mq, void* msg, s32 flags) {
|
||||
if (!mq) return 0;
|
||||
|
||||
PCMessageQueueData& data = GetMsgQueueData(mq);
|
||||
std::unique_lock<std::mutex> lock(data.mtx);
|
||||
|
||||
if (mq->usedCount >= mq->msgCount) {
|
||||
if (flags == OS_MESSAGE_NOBLOCK) return 0;
|
||||
// BLOCK: wait until space is available
|
||||
data.cvSend.wait(lock, [mq]() { return mq->usedCount < mq->msgCount; });
|
||||
}
|
||||
|
||||
// Jam inserts at the front of the queue
|
||||
mq->firstIndex = (mq->firstIndex - 1 + mq->msgCount) % mq->msgCount;
|
||||
((OSMessage*)mq->msgArray)[mq->firstIndex] = msg;
|
||||
mq->usedCount++;
|
||||
|
||||
data.cvReceive.notify_one();
|
||||
return 1;
|
||||
}
|
||||
|
||||
void OSSignalCond(OSCond* cond) {
|
||||
puts("OSSignalCond is a stub");
|
||||
}
|
||||
|
||||
void OSCreateAlarm(OSAlarm* alarm) {
|
||||
puts("OSCreateAlarm is a stub");
|
||||
}
|
||||
|
||||
void OSCancelAlarm(OSAlarm* alarm) {
|
||||
puts("OSCancelAlarm is a stub");
|
||||
}
|
||||
|
||||
s32 OSCheckActiveThreads(void) {
|
||||
puts("OSCheckActiveThreads is a stub");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int OSCreateThread(OSThread* thread, void* (*func)(void*), void* param, void* stack, u32 stackSize,
|
||||
OSPriority priority, u16 attr) {
|
||||
puts("OSCreateThread is a stub");
|
||||
return 0;
|
||||
}
|
||||
|
||||
s32 OSDisableScheduler() {
|
||||
puts("OSDisableScheduler is a stub");
|
||||
return 0;
|
||||
}
|
||||
|
||||
void OSDetachThread(OSThread* thread) {
|
||||
puts("OSDetachThread is a stub");
|
||||
}
|
||||
|
||||
OSThread* OSGetCurrentThread() {
|
||||
puts("OSGetCurrentThread is a stub");
|
||||
return 0;
|
||||
}
|
||||
|
||||
u16 OSGetFontEncode() {
|
||||
puts("OSGetFontEncode is a stub");
|
||||
return 0;
|
||||
}
|
||||
|
||||
char* OSGetFontTexture(char* string, void** image, s32* x, s32* y, s32* width) {
|
||||
puts("OSGetFontTexture is a stub");
|
||||
return 0;
|
||||
}
|
||||
|
||||
char* OSGetFontWidth(char* string, s32* width) {
|
||||
puts("OSGetFontWidth is a stub");
|
||||
return 0;
|
||||
}
|
||||
|
||||
BOOL OSGetResetButtonState() {
|
||||
puts("OSGetResetButtonState is a stub");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
u32 OSGetStackPointer() {
|
||||
puts("OSGetStackPointer is a stub");
|
||||
return 0;
|
||||
}
|
||||
|
||||
BOOL OSInitFont(OSFontHeader* fontData) {
|
||||
puts("OSInitFont is a stub");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
BOOL OSLink(OSModuleInfo* newModule, void* bss) {
|
||||
puts("OSLink is a stub");
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void OSLoadContext(OSContext* context) {
|
||||
puts("OSLoadContext is a stub");
|
||||
}
|
||||
|
||||
void OSResetSystem(int reset, u32 resetCode, BOOL forceMenu) {
|
||||
puts("OSResetSystem is a stub");
|
||||
}
|
||||
|
||||
BOOL OSRestoreInterrupts(BOOL level) {
|
||||
puts("OSRestoreInterrupts is a stub");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
s32 OSResumeThread(OSThread* thread) {
|
||||
puts("OSResumeThread is a stub");
|
||||
return 0;
|
||||
}
|
||||
|
||||
void OSSetCurrentContext(OSContext* context) {
|
||||
puts("OSSetCurrentContext is a stub");
|
||||
}
|
||||
|
||||
void OSSetStringTable(void* stringTable) {
|
||||
puts("OSSetStringTable is a stub");
|
||||
}
|
||||
|
||||
OSSwitchThreadCallback OSSetSwitchThreadCallback(OSSwitchThreadCallback callback) {
|
||||
puts("OSSetSwitchThreadCallback is a stub");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int OSSetThreadPriority(OSThread* thread, s32 priority) {
|
||||
puts("OSSetThreadPriority is a stub");
|
||||
return 0;
|
||||
}
|
||||
|
||||
void OSWaitCond(OSCond* cond, OSMutex* mutex) {
|
||||
puts("OSWaitCond is a stub");
|
||||
}
|
||||
|
||||
void OSYieldThread(void) {
|
||||
puts("OSYieldThread is a stub");
|
||||
}
|
||||
|
||||
s32 OSSuspendThread(OSThread* thread) {
|
||||
puts("OSSuspendThread is a stub");
|
||||
return 0;
|
||||
}
|
||||
|
||||
void OSCancelThread(OSThread* thread) {
|
||||
puts("OSCancelThread is a stub");
|
||||
}
|
||||
|
||||
void OSTicksToCalendarTime(OSTime ticks, OSCalendarTime* td) {
|
||||
puts("OSTicksToCalendarTime is a stub");
|
||||
}
|
||||
|
||||
BOOL OSUnlink(OSModuleInfo* oldModule) {
|
||||
puts("OSUnlink is a stub");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
void OSSwitchFiberEx(__REGISTER u32 param_0, __REGISTER u32 param_1, __REGISTER u32 param_2,
|
||||
__REGISTER u32 param_3, __REGISTER u32 code, __REGISTER u32 stack) {
|
||||
puts("OSSwitchFiberEx is a stub");
|
||||
}
|
||||
|
||||
void OSWakeupThread(OSThreadQueue* queue) {
|
||||
puts("OSWakeupThread is a stub");
|
||||
}
|
||||
|
||||
u32 __OSGetDIConfig() {
|
||||
puts("__OSGetDIConfig is a stub");
|
||||
return 0;
|
||||
}
|
||||
|
||||
__OSInterruptHandler __OSSetInterruptHandler(__OSInterrupt interrupt,
|
||||
__OSInterruptHandler handler) {
|
||||
puts("__OSSetInterruptHandler is a stub");
|
||||
return 0;
|
||||
}
|
||||
|
||||
OSInterruptMask __OSUnmaskInterrupts(OSInterruptMask mask) {
|
||||
puts("__OSUnmaskInterrupts is a stub");
|
||||
return 0;
|
||||
}
|
||||
|
||||
BOOL OSEnableInterrupts() {
|
||||
puts("OSEnableInterrupts is a stub");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
s32 OSEnableScheduler() {
|
||||
puts("OSEnableScheduler is a stub");
|
||||
return 0;
|
||||
}
|
||||
|
||||
void OSExitThread(void* val) {
|
||||
puts("OSExitThread is a stub");
|
||||
}
|
||||
// ==========================================================================
|
||||
// Arena Functions
|
||||
// ==========================================================================
|
||||
|
||||
static void* sArenaLo = nullptr;
|
||||
static void* sArenaHi = nullptr;
|
||||
@@ -247,86 +184,6 @@ void* OSGetArenaLo(void) {
|
||||
return sArenaLo;
|
||||
}
|
||||
|
||||
OSContext* OSGetCurrentContext(void) {
|
||||
puts("OSGetCurrentContext is a stub");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
u32 OSGetProgressiveMode(void) {
|
||||
puts("OSGetProgressiveMode is a stub");
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 OSGetResetCode(void) {
|
||||
puts("OSGetResetCode is a stub");
|
||||
return 0;
|
||||
}
|
||||
|
||||
BOOL OSGetResetSwitchState() {
|
||||
puts("OSGetResetSwitchState is a stub");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
s32 OSGetThreadPriority(OSThread* thread) {
|
||||
puts("OSGetThreadPriority is a stub");
|
||||
return 0;
|
||||
}
|
||||
|
||||
OSTick OSGetTick(void) {
|
||||
puts("OSGetTick is a stub");
|
||||
return 0;
|
||||
}
|
||||
|
||||
OSTime OSGetTime(void) {
|
||||
puts("OSGetTime is a stub");
|
||||
return 0;
|
||||
}
|
||||
|
||||
void OSInitCond(OSCond* cond) {
|
||||
puts("OSInitCond is a stub");
|
||||
}
|
||||
|
||||
void OSInitMessageQueue(OSMessageQueue* mq, void* msgArray, s32 msgCount) {
|
||||
puts("OSInitMessageQueue is a stub");
|
||||
}
|
||||
|
||||
void OSInitThreadQueue(OSThreadQueue* queue) {
|
||||
puts("OSInitThreadQueue is a stub");
|
||||
}
|
||||
|
||||
BOOL OSIsThreadTerminated(OSThread* thread) {
|
||||
puts("OSIsThreadTerminated is a stub");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
int OSJamMessage(OSMessageQueue* mq, void* msg, s32 flags) {
|
||||
puts("OSJamMessage is a stub");
|
||||
return 0;
|
||||
}
|
||||
|
||||
BOOL OSLinkFixed(OSModuleInfo* newModule, void* bss) {
|
||||
puts("OSLinkFixed is a stub");
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void OSLockMutex(OSMutex* mutex) {
|
||||
puts("OSLockMutex is a stub");
|
||||
}
|
||||
|
||||
void OSProtectRange(u32 chan, void* addr, u32 nBytes, u32 control) {
|
||||
puts("OSProtectRange is a stub");
|
||||
}
|
||||
|
||||
int OSReceiveMessage(OSMessageQueue* mq, void* msg, s32 flags) {
|
||||
puts("OSReceiveMessage is a stub");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int OSSendMessage(OSMessageQueue* mq, void* msg, s32 flags) {
|
||||
puts("OSSendMessage is a stub");
|
||||
return 0;
|
||||
}
|
||||
|
||||
void OSSetArenaHi(void* newHi) {
|
||||
sArenaHi = newHi;
|
||||
}
|
||||
@@ -335,38 +192,78 @@ void OSSetArenaLo(void* newLo) {
|
||||
sArenaLo = newLo;
|
||||
}
|
||||
|
||||
void OSSetPeriodicAlarm(OSAlarm* alarm, OSTime start, OSTime period, OSAlarmHandler handler) {
|
||||
puts("OSSetPeriodicAlarm is a stub");
|
||||
}
|
||||
void* OSAllocFromArenaLo(u32 size, u32 align) {
|
||||
if (!sArenaLo || !sArenaHi) return nullptr;
|
||||
|
||||
void OSSetProgressiveMode(u32 on) {
|
||||
puts("OSSetProgressiveMode is a stub");
|
||||
}
|
||||
uintptr_t lo = (uintptr_t)sArenaLo;
|
||||
if (align > 0) {
|
||||
lo = (lo + align - 1) & ~((uintptr_t)align - 1);
|
||||
}
|
||||
|
||||
void OSSetSaveRegion(void* start, void* end) {
|
||||
puts("OSSetSaveRegion is a stub");
|
||||
}
|
||||
uintptr_t hi = (uintptr_t)sArenaHi;
|
||||
if (lo + size > hi) {
|
||||
OSReport("[PC-Arena] OSAllocFromArenaLo: out of arena space (need %u, have %u)\n",
|
||||
size, (u32)(hi - lo));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
OSErrorHandler OSSetErrorHandler(OSError error, OSErrorHandler handler) {
|
||||
puts("OSSetErrorHandler is a stub");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void OSSetAlarm(OSAlarm* alarm, OSTime tick, OSAlarmHandler handler) {
|
||||
puts("OSSetAlarm is a stub");
|
||||
void* result = (void*)lo;
|
||||
sArenaLo = (void*)(lo + size);
|
||||
return result;
|
||||
}
|
||||
|
||||
void* OSInitAlloc(void* arenaStart, void* arenaEnd, int maxHeaps) {
|
||||
puts("OSInitAlloc is a stub");
|
||||
return NULL;
|
||||
return arenaStart;
|
||||
}
|
||||
|
||||
void OSFillFPUContext(__REGISTER OSContext* context) {
|
||||
puts("OSFillFPUContext is a stub");
|
||||
}
|
||||
// ==========================================================================
|
||||
// Remaining OS Stubs
|
||||
// ==========================================================================
|
||||
|
||||
void OSSetSoundMode(u32 mode) {}
|
||||
|
||||
void OSCreateAlarm(OSAlarm* alarm) {}
|
||||
|
||||
void OSCancelAlarm(OSAlarm* alarm) {}
|
||||
|
||||
void OSTicksToCalendarTime(OSTime ticks, OSCalendarTime* td) {
|
||||
if (td) memset(td, 0, sizeof(OSCalendarTime));
|
||||
}
|
||||
|
||||
OSTick OSGetTick(void) { return 0; }
|
||||
OSTime OSGetTime(void) { return 0; }
|
||||
|
||||
u16 OSGetFontEncode() { return 0; }
|
||||
|
||||
char* OSGetFontTexture(char* string, void** image, s32* x, s32* y, s32* width) { return 0; }
|
||||
char* OSGetFontWidth(char* string, s32* width) { return 0; }
|
||||
|
||||
BOOL OSGetResetButtonState() { return FALSE; }
|
||||
BOOL OSInitFont(OSFontHeader* fontData) { return FALSE; }
|
||||
BOOL OSLink(OSModuleInfo* newModule, void* bss) { return TRUE; }
|
||||
|
||||
void OSResetSystem(int reset, u32 resetCode, BOOL forceMenu) {
|
||||
OSReport("[PC] OSResetSystem called (reset=%d, code=%u)\n", reset, resetCode);
|
||||
}
|
||||
|
||||
void OSSetStringTable(void* stringTable) {}
|
||||
BOOL OSUnlink(OSModuleInfo* oldModule) { return FALSE; }
|
||||
|
||||
void OSSwitchFiberEx(__REGISTER u32 param_0, __REGISTER u32 param_1, __REGISTER u32 param_2,
|
||||
__REGISTER u32 param_3, __REGISTER u32 code, __REGISTER u32 stack) {}
|
||||
|
||||
u32 __OSGetDIConfig() { return 0; }
|
||||
u32 OSGetProgressiveMode(void) { return 0; }
|
||||
u32 OSGetResetCode(void) { return 0; }
|
||||
BOOL OSGetResetSwitchState() { return FALSE; }
|
||||
BOOL OSLinkFixed(OSModuleInfo* newModule, void* bss) { return TRUE; }
|
||||
void OSProtectRange(u32 chan, void* addr, u32 nBytes, u32 control) {}
|
||||
void OSSetPeriodicAlarm(OSAlarm* alarm, OSTime start, OSTime period, OSAlarmHandler handler) {}
|
||||
void OSSetProgressiveMode(u32 on) {}
|
||||
void OSSetSaveRegion(void* start, void* end) {}
|
||||
OSErrorHandler OSSetErrorHandler(OSError error, OSErrorHandler handler) { return NULL; }
|
||||
void OSSetAlarm(OSAlarm* alarm, OSTime tick, OSAlarmHandler handler) {}
|
||||
|
||||
#pragma mark SOUND
|
||||
void SoundChoID(int a, int b) {
|
||||
puts("SoundChoID is a stub");
|
||||
@@ -1274,30 +1171,84 @@ void AIStopDMA(void) {
|
||||
|
||||
#pragma mark AR
|
||||
#include <dolphin/ar.h>
|
||||
// Auxilary RAM doesn't exist on PC platforms, do we need to call malloc/free for these instead?
|
||||
// For now, we will just stub these functions.
|
||||
|
||||
// ARAM emulation: allocate a large buffer to simulate the GameCube's Auxiliary RAM.
|
||||
// ARAM "addresses" are offsets into this buffer. On GameCube, ARAM is 16 MB starting
|
||||
// at a base address returned by ARInit. We emulate this by malloc'ing a 16 MB buffer
|
||||
// and using a simple bump allocator (matching ARAlloc behavior on real hardware).
|
||||
static const u32 ARAM_EMU_SIZE = 16 * 1024 * 1024; // 16 MB (GameCube ARAM size)
|
||||
static u8* sAramBuffer = nullptr;
|
||||
static u32 sAramAllocPtr = 0; // bump allocator offset into sAramBuffer
|
||||
|
||||
// Convert an ARAM "address" (offset) to a real host pointer
|
||||
static u8* aramToHost(u32 aramAddr) {
|
||||
if (!sAramBuffer || aramAddr >= ARAM_EMU_SIZE) {
|
||||
return nullptr;
|
||||
}
|
||||
return sAramBuffer + aramAddr;
|
||||
}
|
||||
|
||||
u32 ARAlloc(u32 length) {
|
||||
puts("ARAlloc is a stub");
|
||||
return 0;
|
||||
// Simple bump allocator (matching GameCube behavior - ARAlloc never frees)
|
||||
u32 addr = sAramAllocPtr;
|
||||
sAramAllocPtr += (length + 31) & ~31; // 32-byte align
|
||||
if (sAramAllocPtr > ARAM_EMU_SIZE) {
|
||||
OSReport("[ARAM] ERROR: ARAlloc overflow! Requested %u, used %u/%u\n",
|
||||
length, sAramAllocPtr, ARAM_EMU_SIZE);
|
||||
return 0;
|
||||
}
|
||||
OSReport("[ARAM] ARAlloc(%u) -> 0x%08X\n", length, addr);
|
||||
return addr;
|
||||
}
|
||||
|
||||
u32 ARGetSize(void) {
|
||||
return 0x10000; // 64KB, this is the size of the AR memory region
|
||||
return ARAM_EMU_SIZE;
|
||||
}
|
||||
|
||||
u32 ARInit(u32* stack_index_addr, u32 num_entries) {
|
||||
puts("ARInit is a stub");
|
||||
if (!sAramBuffer) {
|
||||
sAramBuffer = (u8*)malloc(ARAM_EMU_SIZE);
|
||||
if (sAramBuffer) {
|
||||
memset(sAramBuffer, 0, ARAM_EMU_SIZE);
|
||||
OSReport("[ARAM] Initialized %u bytes of emulated ARAM\n", ARAM_EMU_SIZE);
|
||||
} else {
|
||||
OSReport("[ARAM] FATAL: Failed to allocate ARAM emulation buffer!\n");
|
||||
}
|
||||
}
|
||||
// Return base address (start of usable ARAM, after stack entries)
|
||||
sAramAllocPtr = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#pragma mark ARQ
|
||||
void ARQPostRequest(ARQRequest* request, u32 owner, u32 type, u32 priority, u32 source, u32 dest,
|
||||
u32 length, ARQCallback callback) {
|
||||
puts("ARQPostRequest is a stub");
|
||||
// Emulate ARAM DMA transfers using memcpy.
|
||||
// type 0 = MRAM -> ARAM, type 1 = ARAM -> MRAM
|
||||
if (type == ARAM_DIR_MRAM_TO_ARAM) {
|
||||
// Main RAM -> ARAM: source is a host pointer (cast to u32), dest is an ARAM offset
|
||||
u8* hostSrc = (u8*)(uintptr_t)source;
|
||||
u8* aramDst = aramToHost(dest);
|
||||
if (aramDst && hostSrc) {
|
||||
memcpy(aramDst, hostSrc, length);
|
||||
}
|
||||
} else {
|
||||
// ARAM -> Main RAM: source is an ARAM offset, dest is a host pointer (cast to u32)
|
||||
u8* aramSrc = aramToHost(source);
|
||||
u8* hostDst = (u8*)(uintptr_t)dest;
|
||||
if (aramSrc && hostDst) {
|
||||
memcpy(hostDst, aramSrc, length);
|
||||
}
|
||||
}
|
||||
|
||||
// Immediately invoke the callback (synchronous on PC, no DMA latency)
|
||||
if (callback) {
|
||||
callback((u32)(uintptr_t)request);
|
||||
}
|
||||
}
|
||||
|
||||
void ARQInit() {
|
||||
puts("ARQInit is a stub");
|
||||
// Nothing to do on PC - ARAM is initialized in ARInit
|
||||
}
|
||||
|
||||
#pragma mark DVD
|
||||
@@ -1327,11 +1278,24 @@ int DVDCloseDir(DVDDir* dir) {
|
||||
return 0;
|
||||
}
|
||||
s32 DVDConvertPathToEntrynum(const char* pathPtr) {
|
||||
puts("DVDConvertPathToEntrynum is a stub");
|
||||
return 0;
|
||||
return DVDConvertPathToEntrynum_Emu(pathPtr);
|
||||
}
|
||||
BOOL DVDFastOpen(s32 entrynum, DVDFileInfo* fileInfo) {
|
||||
puts("DVDFastOpen is a stub");
|
||||
const char* path = DVDGetPathForEntry(entrynum);
|
||||
if (!path) {
|
||||
OSReport("[DVD] DVDFastOpen: no path for entry %d\n", entrynum);
|
||||
return FALSE;
|
||||
}
|
||||
u32 fileSize = DvdEmu::getFileSize(path);
|
||||
if (fileSize == 0) {
|
||||
OSReport("[DVD] DVDFastOpen: file not found or empty for entry %d (%s)\n", entrynum, path);
|
||||
return FALSE;
|
||||
}
|
||||
// Repurpose startAddr to store entrynum for later DVDReadPrio lookups
|
||||
fileInfo->startAddr = (u32)entrynum;
|
||||
fileInfo->length = fileSize;
|
||||
fileInfo->callback = NULL;
|
||||
fileInfo->cb.state = 0;
|
||||
return TRUE;
|
||||
}
|
||||
s32 DVDGetCommandBlockStatus(const DVDCommandBlock* block) {
|
||||
@@ -1343,15 +1307,19 @@ DVDDiskID* DVDGetCurrentDiskID(void) {
|
||||
return NULL;
|
||||
}
|
||||
s32 DVDGetDriveStatus(void) {
|
||||
puts("DVDGetDriveStatus is a stub");
|
||||
//puts("DVDGetDriveStatus is a stub");
|
||||
return 0;
|
||||
}
|
||||
void DVDInit(void) {
|
||||
puts("DVDInit is a stub");
|
||||
}
|
||||
BOOL DVDOpen(const char* fileName, DVDFileInfo* fileInfo) {
|
||||
puts("DVDOpen is a stub");
|
||||
return TRUE;
|
||||
s32 entryNum = DVDConvertPathToEntrynum(fileName);
|
||||
if (entryNum < 0) {
|
||||
OSReport("[DVD] DVDOpen: file not found: %s\n", fileName);
|
||||
return FALSE;
|
||||
}
|
||||
return DVDFastOpen(entryNum, fileInfo);
|
||||
}
|
||||
int DVDOpenDir(const char* dirName, DVDDir* dir) {
|
||||
puts("DVDOpenDir is a stub");
|
||||
@@ -1359,7 +1327,18 @@ int DVDOpenDir(const char* dirName, DVDDir* dir) {
|
||||
}
|
||||
BOOL DVDReadAsyncPrio(DVDFileInfo* fileInfo, void* addr, s32 length, s32 offset,
|
||||
DVDCallback callback, s32 prio) {
|
||||
puts("DVDReadAsyncPrio is a stub");
|
||||
// Synchronous read, then invoke callback with result
|
||||
s32 entryNum = (s32)fileInfo->startAddr;
|
||||
const char* path = DVDGetPathForEntry(entryNum);
|
||||
if (!path) {
|
||||
OSReport("[DVD] DVDReadAsyncPrio: no path for entry %d\n", entryNum);
|
||||
if (callback) callback(-1, fileInfo);
|
||||
return FALSE;
|
||||
}
|
||||
u32 bytesRead = DvdEmu::loadFileToBuffer(path, addr, (u32)length, (u32)offset);
|
||||
if (callback) {
|
||||
callback((s32)bytesRead, fileInfo);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
int DVDReadDir(DVDDir* dir, DVDDirEntry* dirent) {
|
||||
@@ -1367,8 +1346,14 @@ int DVDReadDir(DVDDir* dir, DVDDirEntry* dirent) {
|
||||
return 0;
|
||||
}
|
||||
s32 DVDReadPrio(DVDFileInfo* fileInfo, void* addr, s32 length, s32 offset, s32 prio) {
|
||||
puts("DVDReadPrio is a stub");
|
||||
return 0;
|
||||
s32 entryNum = (s32)fileInfo->startAddr;
|
||||
const char* path = DVDGetPathForEntry(entryNum);
|
||||
if (!path) {
|
||||
OSReport("[DVD] DVDReadPrio: no path for entry %d\n", entryNum);
|
||||
return -1;
|
||||
}
|
||||
u32 bytesRead = DvdEmu::loadFileToBuffer(path, addr, (u32)length, (u32)offset);
|
||||
return (s32)bytesRead;
|
||||
}
|
||||
|
||||
void DVDReadAbsAsyncForBS(void* a, struct bb2struct* b, int c, int d, void (*e)()) {
|
||||
|
||||
Reference in New Issue
Block a user