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:
Lurs
2026-02-19 10:35:42 +01:00
parent c86a2208d2
commit a4d72437ef
17 changed files with 1509 additions and 399 deletions
+3
View File
@@ -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})
+71 -2
View File
@@ -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);
}
+4
View File
@@ -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>
+2 -1
View File
@@ -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;
}
+2
View File
@@ -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);
+3
View File
@@ -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);
+1 -1
View File
@@ -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;
+2 -1
View File
@@ -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;
}
+2
View File
@@ -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);
+2 -1
View File
@@ -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;
}
+4
View File
@@ -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);
+44 -30
View File
@@ -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 {
+75 -39
View File
@@ -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;
+92
View File
@@ -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
+243
View File
@@ -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
+650
View File
@@ -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(&currentThread->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
View File
@@ -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)()) {