diff --git a/CMakeLists.txt b/CMakeLists.txt index bdfd4978ab..058256da6c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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}) diff --git a/include/JSystem/JKernel/JKRArchive.h b/include/JSystem/JKernel/JKRArchive.h index cf4b3ef045..7d49a595b8 100644 --- a/include/JSystem/JKernel/JKRArchive.h +++ b/include/JSystem/JKernel/JKRArchive.h @@ -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); } diff --git a/include/dolphin/gx/GXVert.h b/include/dolphin/gx/GXVert.h index c508e164bb..374b87af6f 100644 --- a/include/dolphin/gx/GXVert.h +++ b/include/dolphin/gx/GXVert.h @@ -3,6 +3,10 @@ #ifdef __REVOLUTION_SDK__ #include +#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 #include diff --git a/src/JSystem/JKernel/JKRAram.cpp b/src/JSystem/JKernel/JKRAram.cpp index ffea3d9af7..a69ac99fba 100644 --- a/src/JSystem/JKernel/JKRAram.cpp +++ b/src/JSystem/JKernel/JKRAram.cpp @@ -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; } diff --git a/src/JSystem/JKernel/JKRAramArchive.cpp b/src/JSystem/JKernel/JKRAramArchive.cpp index 811978caa0..fd5ddbbf48 100644 --- a/src/JSystem/JKernel/JKRAramArchive.cpp +++ b/src/JSystem/JKernel/JKRAramArchive.cpp @@ -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); diff --git a/src/JSystem/JKernel/JKRCompArchive.cpp b/src/JSystem/JKernel/JKRCompArchive.cpp index 8a1577f592..7666cd8820 100644 --- a/src/JSystem/JKernel/JKRCompArchive.cpp +++ b/src/JSystem/JKernel/JKRCompArchive.cpp @@ -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); diff --git a/src/JSystem/JKernel/JKRDecomp.cpp b/src/JSystem/JKernel/JKRDecomp.cpp index 87557d65bf..e14b285edd 100644 --- a/src/JSystem/JKernel/JKRDecomp.cpp +++ b/src/JSystem/JKernel/JKRDecomp.cpp @@ -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; diff --git a/src/JSystem/JKernel/JKRDvdAramRipper.cpp b/src/JSystem/JKernel/JKRDvdAramRipper.cpp index 3ca276e050..3e07727a58 100644 --- a/src/JSystem/JKernel/JKRDvdAramRipper.cpp +++ b/src/JSystem/JKernel/JKRDvdAramRipper.cpp @@ -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; } diff --git a/src/JSystem/JKernel/JKRDvdArchive.cpp b/src/JSystem/JKernel/JKRDvdArchive.cpp index ed88d791e7..0bf2b5017a 100644 --- a/src/JSystem/JKernel/JKRDvdArchive.cpp +++ b/src/JSystem/JKernel/JKRDvdArchive.cpp @@ -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); diff --git a/src/JSystem/JKernel/JKRDvdRipper.cpp b/src/JSystem/JKernel/JKRDvdRipper.cpp index cf84ef9eec..0017119c2b 100644 --- a/src/JSystem/JKernel/JKRDvdRipper.cpp +++ b/src/JSystem/JKernel/JKRDvdRipper.cpp @@ -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; } diff --git a/src/JSystem/JKernel/JKRMemArchive.cpp b/src/JSystem/JKernel/JKRMemArchive.cpp index 0c0ef4887c..1bc4b2877d 100644 --- a/src/JSystem/JKernel/JKRMemArchive.cpp +++ b/src/JSystem/JKernel/JKRMemArchive.cpp @@ -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); diff --git a/src/JSystem/JUtility/JUTCacheFont.cpp b/src/JSystem/JUtility/JUTCacheFont.cpp index a4f22ab3d6..a47dcb4eb0 100644 --- a/src/JSystem/JUtility/JUTCacheFont.cpp +++ b/src/JSystem/JUtility/JUTCacheFont.cpp @@ -8,6 +8,15 @@ #include #include +#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 { diff --git a/src/JSystem/JUtility/JUTResFont.cpp b/src/JSystem/JUtility/JUTResFont.cpp index 62abfbd213..ad3aae4d30 100644 --- a/src/JSystem/JUtility/JUTResFont.cpp +++ b/src/JSystem/JUtility/JUTResFont.cpp @@ -7,6 +7,19 @@ #include "JSystem/JUtility/JUTConsole.h" #include +#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; diff --git a/src/dolphin/os/OSContext.cpp b/src/dolphin/os/OSContext.cpp new file mode 100644 index 0000000000..4f54b8b963 --- /dev/null +++ b/src/dolphin/os/OSContext.cpp @@ -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 +#include +#include + +#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 diff --git a/src/dolphin/os/OSMutex.cpp b/src/dolphin/os/OSMutex.cpp new file mode 100644 index 0000000000..9ec3197cb1 --- /dev/null +++ b/src/dolphin/os/OSMutex.cpp @@ -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 +#include + +#include +#include +#include +#include +#include + +// ============================================================================ +// 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 +struct MallocAllocator { + using value_type = T; + MallocAllocator() = default; + template MallocAllocator(const MallocAllocator&) noexcept {} + T* allocate(std::size_t n) { + void* p = std::malloc(n * sizeof(T)); + if (!p) throw std::bad_alloc(); + return static_cast(p); + } + void deallocate(T* p, std::size_t) noexcept { std::free(p); } + template bool operator==(const MallocAllocator&) const noexcept { return true; } + template bool operator!=(const MallocAllocator&) const noexcept { return false; } +}; + +template +struct MallocDeleter { + void operator()(T* p) const { + p->~T(); + std::free(p); + } +}; + +template +std::unique_ptr> 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)...); + return std::unique_ptr>(obj); +} + +// ============================================================================ +// Side-table: native mutex per OSMutex +// ============================================================================ + +struct PCMutexData { + std::recursive_mutex nativeMutex; +}; + +template +using MallocMap = std::unordered_map, std::equal_to, + MallocAllocator>>; + +// Lazy-initialized to avoid DLL static init crashes +static std::mutex& GetMutexMapMutex() { + static std::mutex mtx; + return mtx; +} +static MallocMap>>& GetMutexMap() { + static MallocMap>> map; + return map; +} + +static PCMutexData& GetMutexData(OSMutex* mutex) { + std::lock_guard lock(GetMutexMapMutex()); + auto& map = GetMutexMap(); + auto it = map.find(mutex); + if (it == map.end()) { + auto result = map.emplace(mutex, make_malloc_unique()); + 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>>& GetCondMap() { + static MallocMap>> map; + return map; +} + +static PCCondData& GetCondData(OSCond* cond) { + std::lock_guard lock(GetCondMapMutex()); + auto& map = GetCondMap(); + auto it = map.find(cond); + if (it == map.end()) { + auto result = map.emplace(cond, make_malloc_unique()); + 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 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 diff --git a/src/dolphin/os/OSThread.cpp b/src/dolphin/os/OSThread.cpp new file mode 100644 index 0000000000..74fffaa4e5 --- /dev/null +++ b/src/dolphin/os/OSThread.cpp @@ -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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// ============================================================================ +// Malloc-based allocator to bypass JKRHeap operator new/delete +// ============================================================================ + +template +struct MallocAllocator { + using value_type = T; + MallocAllocator() = default; + template MallocAllocator(const MallocAllocator&) noexcept {} + T* allocate(std::size_t n) { + void* p = std::malloc(n * sizeof(T)); + if (!p) throw std::bad_alloc(); + return static_cast(p); + } + void deallocate(T* p, std::size_t) noexcept { std::free(p); } + template bool operator==(const MallocAllocator&) const noexcept { return true; } + template bool operator!=(const MallocAllocator&) const noexcept { return false; } +}; + +template +struct MallocDeleter { + void operator()(T* p) const { + p->~T(); + std::free(p); + } +}; + +template +std::unique_ptr> 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)...); + return std::unique_ptr>(obj); +} + +template +using MallocMap = std::unordered_map, std::equal_to, + MallocAllocator>>; + +// ============================================================================ +// 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>>& GetThreadDataMap() { + static MallocMap>> map; + return map; +} + +// Side-table for OSThreadQueue -> condition_variable (for OSSleepThread/OSWakeupThread) +static std::mutex& GetQueueCvMutex() { + static std::mutex mtx; + return mtx; +} +static MallocMap>>& GetQueueCvMap() { + static MallocMap>> map; + return map; +} + +static std::condition_variable& GetQueueCV(OSThreadQueue* queue) { + std::lock_guard lock(GetQueueCvMutex()); + auto& map = GetQueueCvMap(); + auto it = map.find(queue); + if (it == map.end()) { + auto result = map.emplace(queue, make_malloc_unique()); + 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 sSchedulerSuspendCount{0}; + +// Active thread count +static std::atomic 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(); + data->func = func; + data->param = param; + + std::lock_guard 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 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 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 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 diff --git a/src/dusk/stubs.cpp b/src/dusk/stubs.cpp index 670c718c1d..95df5128e5 100644 --- a/src/dusk/stubs.cpp +++ b/src/dusk/stubs.cpp @@ -2,17 +2,18 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include -// 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 +struct MallocAllocator { + using value_type = T; + MallocAllocator() = default; + template MallocAllocator(const MallocAllocator&) noexcept {} + T* allocate(std::size_t n) { + void* p = std::malloc(n * sizeof(T)); + if (!p) throw std::bad_alloc(); + return static_cast(p); + } + void deallocate(T* p, std::size_t) noexcept { std::free(p); } + template bool operator==(const MallocAllocator&) const noexcept { return true; } + template bool operator!=(const MallocAllocator&) const noexcept { return false; } +}; + +template +struct MallocDeleter { + void operator()(T* p) const { + p->~T(); + std::free(p); + } +}; + +template +std::unique_ptr> 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)...); + return std::unique_ptr>(obj); } -void OSUnlockMutex(OSMutex* mutex) { - puts("OSUnlockMutex is a stub"); +template +using MallocMap = std::unordered_map, std::equal_to, + MallocAllocator>>; + +// 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>>& GetMsgQueueMap() { + static MallocMap>> map; + return map; } -BOOL OSTryLockMutex(OSMutex* mutex) { - puts("OSTryLockMutex is a stub"); - return FALSE; +static PCMessageQueueData& GetMsgQueueData(OSMessageQueue* mq) { + std::lock_guard lock(GetMsgQueueMapMutex()); + auto& map = GetMsgQueueMap(); + auto it = map.find(mq); + if (it == map.end()) { + auto result = map.emplace(mq, make_malloc_unique()); + 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 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 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 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 -// 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)()) {