From bb92f955c8051f9e6a8a1dea3252a82d940ea4a6 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sat, 28 Mar 2026 18:29:58 +0100 Subject: [PATCH 01/19] Make the movie player work somewhat. THPs are *almost* just a bunch of JPEGs so the TL;DR is BE fixes and replacing the decoding with libjpeg-turbo. Needs changes to thp.h which should be removed from Aurora, will do that later. Also audio not implemented yet. --- CMakeLists.txt | 4 +- cmake/libjpeg.cmake | 12 ++ include/dusk/map_loader_definitions.h | 3 + libs/JSystem/src/JAudio2/JASAiCtrl.cpp | 4 + src/d/actor/d_a_movie_player.cpp | 191 +++++++++++++++++++++++-- src/dusk/audio/DuskAudioSystem.cpp | 5 + 6 files changed, 203 insertions(+), 16 deletions(-) create mode 100644 cmake/libjpeg.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 583b5efa94..0fea651943 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,6 +52,7 @@ endif () include(FetchContent) +include(ExternalProject) message(STATUS "dusk: Fetching cxxopts") FetchContent_Declare( @@ -62,6 +63,7 @@ FetchContent_Declare( FetchContent_MakeAvailable(cxxopts) +include(cmake/libjpeg.cmake) include(files.cmake) # TODO: version handling for res includes @@ -107,7 +109,7 @@ add_library(game SHARED ${DOLZEL_FILES} ${Z2AUDIOLIB_FILES} ${SSYSTEM_FILES} ${J src/dusk/imgui/ImGuiStubLog.cpp src/dusk/imgui/ImGuiAudio.cpp) -target_link_libraries(game PRIVATE game_debug cxxopts::cxxopts) +target_link_libraries(game PRIVATE game_debug cxxopts::cxxopts libjpeg-turbo-lib) target_compile_definitions(game PRIVATE TARGET_PC AVOID_UB=1 VERSION=0 NDEBUG=1 NDEBUG_DEFINED=1 DEBUG_DEFINED=0 DUSK_TP_VERSION="${DUSK_TP_VERSION}" DUSK_GAME_NAME="${DUSK_GAME_NAME}" DUSK_GAME_VERSION="${DUSK_GAME_VERSION}") add_executable(dusk src/dusk/main.cpp) diff --git a/cmake/libjpeg.cmake b/cmake/libjpeg.cmake new file mode 100644 index 0000000000..b7f719f003 --- /dev/null +++ b/cmake/libjpeg.cmake @@ -0,0 +1,12 @@ +ExternalProject_Add( + libjpeg-turbo + URL https://github.com/libjpeg-turbo/libjpeg-turbo/archive/refs/tags/3.1.90.tar.gz + URL_HASH SHA256=076ef1431f2803a91f07e0f92433d4dcf39bc9113226c4f46ba3d3d54f514c9d + DOWNLOAD_EXTRACT_TIMESTAMP TRUE + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX= +) + +ExternalProject_Get_Property(libjpeg-turbo INSTALL_DIR) +add_library(libjpeg-turbo-lib STATIC IMPORTED GLOBAL) +set_target_properties(libjpeg-turbo-lib PROPERTIES IMPORTED_LOCATION ${INSTALL_DIR}/lib/turbojpeg-static.lib) +target_include_directories(libjpeg-turbo-lib INTERFACE ${INSTALL_DIR}/include) diff --git a/include/dusk/map_loader_definitions.h b/include/dusk/map_loader_definitions.h index c2c9e4fedb..48c04d6d4d 100644 --- a/include/dusk/map_loader_definitions.h +++ b/include/dusk/map_loader_definitions.h @@ -629,5 +629,8 @@ constexpr auto gameRegions = std::to_array({ MapEntry("Cutscene: Hyrule Castle Throne Room", "R_SP301", { {0, {0, 20, 100}}, }), + MapEntry("Title screen movie map", "S_MV000", { + {0, {0, 1}}, + }), }) }); diff --git a/libs/JSystem/src/JAudio2/JASAiCtrl.cpp b/libs/JSystem/src/JAudio2/JASAiCtrl.cpp index 1285d68359..ede7c1d267 100644 --- a/libs/JSystem/src/JAudio2/JASAiCtrl.cpp +++ b/libs/JSystem/src/JAudio2/JASAiCtrl.cpp @@ -260,6 +260,10 @@ void JASDriver::finishDSPFrame() { } void JASDriver::registerMixCallback(MixCallback param_0, JASMixMode param_1) { +#if TARGET_PC + JASCriticalSection section; +#endif + extMixCallback = param_0; sMixMode = param_1; } diff --git a/src/d/actor/d_a_movie_player.cpp b/src/d/actor/d_a_movie_player.cpp index 0f9f68fb50..eca4bdb9e1 100644 --- a/src/d/actor/d_a_movie_player.cpp +++ b/src/d/actor/d_a_movie_player.cpp @@ -14,21 +14,29 @@ #pragma optimization_level 4 #pragma optimize_for_size off -#include "JSystem/JKernel/JKRExpHeap.h" +#include +#include #include "JSystem/JAudio2/JASAiCtrl.h" #include "JSystem/JAudio2/JASDriverIF.h" -#include "d/actor/d_a_movie_player.h" +#include "JSystem/JKernel/JKRExpHeap.h" #include "Z2AudioLib/Z2Instances.h" +#include "d/actor/d_a_movie_player.h" + +#include + #include "f_op/f_op_overlap_mng.h" -#include #include "dusk/gx_helper.h" +#include "dusk/memory.h" +#include "dusk/os.h" + +#include "turbojpeg.h" inline s32 daMP_NEXT_READ_SIZE(daMP_THPReadBuffer* readBuf) { - return *(s32*)readBuf->ptr; + return *(BE(s32)*)readBuf->ptr; } -#ifdef __cplusplus +#if defined(__cplusplus) && !TARGET_PC extern "C" { #endif @@ -196,6 +204,7 @@ static void __THPAudioInitialize(THPAudioDecodeInfo* info, u8* ptr) { info->encodeData++; } +#if !TARGET_PC static u8 THPStatistics[1120] ATTRIBUTE_ALIGN(32); static THPHuffmanTab* Ydchuff ATTRIBUTE_ALIGN(32); @@ -2562,8 +2571,116 @@ static void __THPHuffDecodeDCTCompV(__REGISTER THPFileInfo* info, THPCoeff* bloc } } } +#else // !TARGET_PC + +static std::vector FixJpeg(const std::span data) { + std::vector fixedData; + fixedData.reserve(data.size()); + + size_t startOfScanLocation = 0; + for (; startOfScanLocation < data.size() - 1; startOfScanLocation++) { + if (data[startOfScanLocation] == 0xFF && data[startOfScanLocation + 1] == 0xDA) { + goto sosFound; + } + } + + CRASH("Unable to find SOS marker!"); + + sosFound: + + startOfScanLocation += 2; // TODO: Skip entire SOS header? + + size_t endOfImage = data.size() - 1; + for (; endOfImage > startOfScanLocation; endOfImage--) { + if (data[endOfImage] == 0xFF && data[endOfImage + 1] == 0xD9) { + goto eoiFound; + } + } + + CRASH("Unable to find EOI marker!"); + eoiFound: + + // Copy data before SOS + for (size_t i = 0; i < startOfScanLocation; i++) { + fixedData.push_back(data[i]); + } + + // Copy data inside SOS, fixing up lacking of "byte shuffling" + for (size_t i = startOfScanLocation; i < endOfImage; i++) { + u8 value = data[i]; + fixedData.push_back(value); + if (value == 0xFF) { + fixedData.push_back(0x00); + } + } + + // Copy data after SOS. + for (size_t i = endOfImage; i < data.size(); i++) { + fixedData.push_back(data[i]); + } + + return fixedData; +} + +struct TjHandle { + tjhandle mHandle; + + explicit TjHandle(const tjhandle handle) : mHandle(handle) {} + ~TjHandle() { + tj3Destroy(mHandle); + } + + operator tjhandle() const { + return mHandle; + } +}; + +extern daMP_THPPlayer daMP_ActivePlayer; + +static s32 THPVideoDecode(void* file, size_t fileSize, void* tileY, void* tileU, void* tileV, void* work) { + const TjHandle handle(tj3Init(TJINIT_DECOMPRESS)); + if (handle == nullptr) { + OSReport_Error("Failed to create turbojpeg handle: %s", tj3GetErrorStr(nullptr)); + return 1; + } + + const auto fixedData = FixJpeg(std::span(static_cast(file), fileSize)); + + auto ret = tj3DecompressHeader(handle, fixedData.data(), fixedData.size()); + if (ret == -1) { + OSReport_Error("Parsing JPEG header failed: %s", tj3GetErrorStr(handle)); + return 1; + } + + if (tj3Get(handle, TJPARAM_JPEGWIDTH) != daMP_ActivePlayer.videoInfo.xSize) { + OSReport_Error("Invalid width in video frame!"); + return 1; + } + + if (tj3Get(handle, TJPARAM_JPEGHEIGHT) != daMP_ActivePlayer.videoInfo.ySize) { + OSReport_Error("Invalid height in video frame!"); + return 1; + } + + ret = tj3Set(handle, TJPARAM_SUBSAMP, TJSAMP_420); + if (ret != 0) { + OSReport_Error("Failed to set subsampling mode: %s", tj3GetErrorStr(handle)); + return 1; + } + + u8* planes[3] = {static_cast(tileY), static_cast(tileU), static_cast(tileV)}; + ret = tj3DecompressToYUVPlanes8(handle, fixedData.data(), fixedData.size(), planes, nullptr); + if (ret != 0) { + OSReport_Error("Image decompression failed: %s", tj3GetErrorStr(handle)); + return 1; + } + + return 0; +} +#endif static BOOL THPInit() { +#if !TARGET_PC u8* base; base = (u8*)(0xE000 << 16); @@ -2585,10 +2702,11 @@ static BOOL THPInit() { OSInitFastCast(); __THPInitFlag = TRUE; +#endif return TRUE; } -#ifdef __cplusplus +#if defined(__cplusplus) && !TARGET_PC } #endif @@ -2654,6 +2772,10 @@ void daMP_ReadThreadCancel() { } void* daMP_Reader(void*) { +#if TARGET_PC + OSSetCurrentThreadName("movie player reader"); +#endif + daMP_THPReadBuffer* buf; s32 curFrame; s32 status; @@ -2751,10 +2873,10 @@ static BOOL daMP_First; static void daMP_VideoDecode(daMP_THPReadBuffer* readBuffer) { THPTextureSet* textureSet; s32 i; - u32* tileOffsets; + BE(u32)* tileOffsets; u8* tile; - tileOffsets = (u32*)(readBuffer->ptr + 8); + tileOffsets = (BE(u32)*)(readBuffer->ptr + 8); tile = &readBuffer->ptr[daMP_ActivePlayer.compInfo.numComponents * 4] + 8; textureSet = (THPTextureSet*)daMP_PopFreeTextureSet(); @@ -2762,8 +2884,13 @@ static void daMP_VideoDecode(daMP_THPReadBuffer* readBuffer) { switch (daMP_ActivePlayer.compInfo.frameComp[i]) { case 0: { if ((daMP_ActivePlayer.videoError = THPVideoDecode( - tile, textureSet->ytexture, textureSet->utexture, - textureSet->vtexture, daMP_ActivePlayer.thpWork))) { + tile, +#if TARGET_PC + *tileOffsets, +#endif + textureSet->ytexture, textureSet->utexture, + textureSet->vtexture, + daMP_ActivePlayer.thpWork))) { if (daMP_First) { daMP_PrepareReady(FALSE); daMP_First = FALSE; @@ -2789,6 +2916,10 @@ static void daMP_VideoDecode(daMP_THPReadBuffer* readBuffer) { } static void* daMP_VideoDecoder(void* param_0) { +#if TARGET_PC + OSSetCurrentThreadName("video decoder"); +#endif + daMP_THPReadBuffer* thpBuffer; while (TRUE) { @@ -2820,6 +2951,10 @@ static void* daMP_VideoDecoder(void* param_0) { } static void* daMP_VideoDecoderForOnMemory(void* param_0) { +#if TARGET_PC + OSSetCurrentThreadName("video decoder"); +#endif + daMP_THPReadBuffer readBuffer; s32 readSize; s32 frame; @@ -2944,10 +3079,10 @@ static void daMP_PushDecodedAudioBuffer(void* buffer) { static void daMP_AudioDecode(daMP_THPReadBuffer* readBuffer) { THPAudioBuffer* audioBuf; s32 i; - u32* offsets; + BE(u32)* offsets; u8* audioData; - offsets = (u32*)(readBuffer->ptr + 8); + offsets = (BE(u32)*)(readBuffer->ptr + 8); audioData = &readBuffer->ptr[daMP_ActivePlayer.compInfo.numComponents * 4] + 8; audioBuf = (THPAudioBuffer*)daMP_PopFreeAudioBuffer(); @@ -2969,6 +3104,10 @@ static void daMP_AudioDecode(daMP_THPReadBuffer* readBuffer) { } static void* daMP_AudioDecoder(void* param_0) { +#if TARGET_PC + OSSetCurrentThreadName("movie audio decoder"); +#endif + daMP_THPReadBuffer* buf; while (TRUE) { @@ -2979,6 +3118,10 @@ static void* daMP_AudioDecoder(void* param_0) { } static void* daMP_AudioDecoderForOnMemory(void* param_0) { +#if TARGET_PC + OSSetCurrentThreadName("movie audio decoder"); +#endif + s32 size; s32 readSize; daMP_THPReadBuffer readBuffer; @@ -3168,19 +3311,26 @@ static void daMP_THPGXYuv2RgbDraw(u8* y_data, u8* u_data, u8* v_data, s16 x, TGXTexObj tobj0; TGXTexObj tobj1; TGXTexObj tobj2; +#if TARGET_PC +#define FMT (GXTexFmt)GX_TF_R8_PC +#else +#define FMT GX_TF_I8 +#endif - GXInitTexObj(&tobj0, y_data, textureWidth, textureHeight, GX_TF_I8, GX_CLAMP, GX_CLAMP, GX_FALSE); + GXInitTexObj(&tobj0, y_data, textureWidth, textureHeight, FMT, GX_CLAMP, GX_CLAMP, GX_FALSE); GXInitTexObjLOD(&tobj0, GX_NEAR, GX_NEAR, 0.0f, 0.0f, 0.0f, 0, 0, GX_ANISO_1); GXLoadTexObj(&tobj0, GX_TEXMAP0); - GXInitTexObj(&tobj1, u_data, textureWidth >> 1, textureHeight >> 1, GX_TF_I8, GX_CLAMP, GX_CLAMP, GX_FALSE); + GXInitTexObj(&tobj1, u_data, textureWidth >> 1, textureHeight >> 1, FMT, GX_CLAMP, GX_CLAMP, GX_FALSE); GXInitTexObjLOD(&tobj1, GX_NEAR, GX_NEAR, 0.0f, 0.0f, 0.0f, 0, 0, GX_ANISO_1); GXLoadTexObj(&tobj1, GX_TEXMAP1); - GXInitTexObj(&tobj2, v_data, textureWidth >> 1, textureHeight >> 1, GX_TF_I8, GX_CLAMP, GX_CLAMP, GX_FALSE); + GXInitTexObj(&tobj2, v_data, textureWidth >> 1, textureHeight >> 1, FMT, GX_CLAMP, GX_CLAMP, GX_FALSE); GXInitTexObjLOD(&tobj2, GX_NEAR, GX_NEAR, 0.0f, 0.0f, 0.0f, 0, 0, GX_ANISO_1); GXLoadTexObj(&tobj2, GX_TEXMAP2); +#undef FMT + GXBegin(GX_QUADS, GX_VTXFMT7, 4); GXPosition3s16(x, y, 0); GXTexCoord2u16(0, 0); @@ -3191,6 +3341,12 @@ static void daMP_THPGXYuv2RgbDraw(u8* y_data, u8* u_data, u8* v_data, s16 x, GXPosition3s16(x, y + polygonHeight, 0); GXTexCoord2u16(0, 1); GXEnd(); + +#if TARGET_PC + GXDestroyTexObj(&tobj0); + GXDestroyTexObj(&tobj1); + GXDestroyTexObj(&tobj2); +#endif } static u16 daMP_VolumeTable[] = { @@ -3546,6 +3702,11 @@ static BOOL daMP_THPPlayerSetBuffer(u8* buffer) { ysize = ALIGN_NEXT(daMP_ActivePlayer.videoInfo.xSize * daMP_ActivePlayer.videoInfo.ySize, 32); uvsize = ALIGN_NEXT(daMP_ActivePlayer.videoInfo.xSize * daMP_ActivePlayer.videoInfo.ySize / 4, 32); +#if TARGET_PC + assert(ysize >= tj3YUVPlaneSize(0, daMP_ActivePlayer.videoInfo.xSize, 0, daMP_ActivePlayer.videoInfo.ySize, TJSAMP_420)); + assert(uvsize >= tj3YUVPlaneSize(1, daMP_ActivePlayer.videoInfo.xSize, 0, daMP_ActivePlayer.videoInfo.ySize, TJSAMP_420)); + assert(uvsize >= tj3YUVPlaneSize(2, daMP_ActivePlayer.videoInfo.xSize, 0, daMP_ActivePlayer.videoInfo.ySize, TJSAMP_420)); +#endif for (i = 0; i < ARRAY_SIZE(daMP_ActivePlayer.textureSet); i++) { daMP_ActivePlayer.textureSet[i].ytexture = ptr; diff --git a/src/dusk/audio/DuskAudioSystem.cpp b/src/dusk/audio/DuskAudioSystem.cpp index 0ed2101dcf..522f4c9d60 100644 --- a/src/dusk/audio/DuskAudioSystem.cpp +++ b/src/dusk/audio/DuskAudioSystem.cpp @@ -111,6 +111,11 @@ int RenderNewAudioFrame() { outRaw.flush(); #endif + if (JASDriver::extMixCallback != nullptr) { + // TODO: actually mix this data in. + JASDriver::extMixCallback(countSubframes * DSP_SUBFRAME_SIZE); + } + return static_cast(countSubframes) * DSP_SUBFRAME_SIZE; } From b17b5fe405db90d214f7b3eb55093ff73c4cb8ef Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 29 Mar 2026 00:19:56 +0100 Subject: [PATCH 02/19] Works a lot better now --- files.cmake | 1 + include/d/actor/d_a_movie_player.h | 85 ++++++++++++++++++++ include/dusk/layout.hpp | 37 +++++++++ src/d/actor/d_a_movie_player.cpp | 120 ++++++++++++++++++++++------- src/dusk/audio/DuskAudioSystem.cpp | 18 +++-- src/dusk/layout.cpp | 25 ++++++ 6 files changed, 252 insertions(+), 34 deletions(-) create mode 100644 include/dusk/layout.hpp create mode 100644 src/dusk/layout.cpp diff --git a/files.cmake b/files.cmake index 8504aecb95..2339f5cb02 100644 --- a/files.cmake +++ b/files.cmake @@ -1334,6 +1334,7 @@ set(DUSK_FILES include/dusk/endian_gx.hpp src/dusk/asserts.cpp src/dusk/logging.cpp + src/dusk/layout.cpp src/dusk/stubs.cpp src/dusk/endian.cpp src/dusk/extras.c diff --git a/include/d/actor/d_a_movie_player.h b/include/d/actor/d_a_movie_player.h index e064d558dd..4e6a44bd54 100644 --- a/include/d/actor/d_a_movie_player.h +++ b/include/d/actor/d_a_movie_player.h @@ -1,7 +1,9 @@ #ifndef D_A_MOVIE_PLAYER_H #define D_A_MOVIE_PLAYER_H +#if !TARGET_PC #include +#endif #include "f_op/f_op_actor.h" #include "d/d_drawlist.h" @@ -11,6 +13,85 @@ struct daMP_THPReadBuffer { BOOL isValid; }; +#if TARGET_PC +// Copying here because thp.h is probably erroneous in the dolphin lib, +// and it's kind of a problem being there (Aurora owns the headers). +// TODO: Move this stuff in decomp? +typedef struct THPAudioRecordHeader { + BE(u32) offsetNextChannel; + BE(u32) sampleSize; + BE(s16) lCoef[8][2]; + BE(s16) rCoef[8][2]; + BE(s16) lYn1; + BE(s16) lYn2; + BE(s16) rYn1; + BE(s16) rYn2; +} THPAudioRecordHeader; + +typedef struct THPAudioDecodeInfo { + u8* encodeData; + u32 offsetNibbles; + u8 predictor; + u8 scale; + s16 yn1; + s16 yn2; +} THPAudioDecodeInfo; + +typedef struct THPTextureSet { + u8* ytexture; + u8* utexture; + u8* vtexture; + s32 frameNumber; +} THPTextureSet; + +typedef struct THPAudioBuffer { + s16* buffer; + s16* curPtr; + u32 validSample; +} THPAudioBuffer; + +typedef struct THPVideoInfo { + BE(u32) xSize; + BE(u32) ySize; + BE(u32) videoType; +} THPVideoInfo; + +typedef struct THPAudioInfo { + BE(u32) sndChannels; + BE(u32) sndFrequency; + BE(u32) sndNumSamples; + BE(u32) sndNumTracks; +} THPAudioInfo; + +typedef struct THPFrameCompInfo { + BE(u32) numComponents; + u8 frameComp[16]; +} THPFrameCompInfo; + +typedef struct THPHeader { + /* 0x00 */ char magic[4]; + /* 0x04 */ BE(u32) version; + /* 0x08 */ BE(u32) bufsize; + /* 0x0C */ BE(u32) audioMaxSamples; + /* 0x10 */ BE(f32) frameRate; + /* 0x14 */ BE(u32) numFrames; + /* 0x18 */ BE(u32) firstFrameSize; + /* 0x1C */ BE(u32) movieDataSize; + /* 0x20 */ BE(u32) compInfoDataOffsets; + /* 0x24 */ BE(u32) offsetDataOffsets; + /* 0x28 */ BE(u32) movieDataOffsets; + /* 0x2C */ BE(u32) finalFrameDataOffsets; +} THPHeader; + +u32 THPAudioDecode(s16* audioBuffer, u8* audioFrame, s32 flag); +s32 __THPAudioGetNewSample(THPAudioDecodeInfo* info); +void __THPAudioInitialize(THPAudioDecodeInfo* info, u8* ptr); + +#define THP_AUDIO_BUFFER_COUNT 3 +#define THP_READ_BUFFER_COUNT 10 +#define THP_TEXTURE_SET_COUNT 3 +#endif + struct daMP_THPPlayer { /* 0x000 */ DVDFileInfo fileInfo; /* 0x03C */ THPHeader header; @@ -34,7 +115,11 @@ struct daMP_THPPlayer { /* 0x0C8 */ s64 retaceCount; /* 0x0D0 */ s32 prevCount; /* 0x0D4 */ s32 curCount; +#if TARGET_PC + /* 0x0D8 */ std::atomic videoDecodeCount; +#else /* 0x0D8 */ s32 videoDecodeCount; +#endif /* 0x0DC */ f32 curVolume; /* 0x0E0 */ f32 targetVolume; /* 0x0E4 */ f32 deltaVolume; diff --git a/include/dusk/layout.hpp b/include/dusk/layout.hpp new file mode 100644 index 0000000000..78316d2e09 --- /dev/null +++ b/include/dusk/layout.hpp @@ -0,0 +1,37 @@ +#ifndef DUSK_LAYOUT_H +#define DUSK_LAYOUT_H + +#include "dolphin/types.h" + +namespace dusk { + +/** + * Helper struct for laying things out on the screen. Represents a rectangle via two corner + * positions. + */ +struct LayoutRect { + f32 PosX; + f32 PosY; + f32 PosX2; + f32 PosY2; + + [[nodiscard]] constexpr f32 Width() const { + return PosX2 - PosX; + } + + [[nodiscard]] constexpr f32 Height() const { + return PosY2 - PosY; + } + + /** + * Calculates the position to render one rectangle inside another, centered and maintaining aspect ratio. + */ + [[nodiscard]] static LayoutRect FitRectInRect( + f32 widthOuter, + f32 heightOuter, + f32 widthInner, + f32 heightInner); +}; +} + +#endif // DUSK_LAYOUT_H diff --git a/src/d/actor/d_a_movie_player.cpp b/src/d/actor/d_a_movie_player.cpp index eca4bdb9e1..f372c0216b 100644 --- a/src/d/actor/d_a_movie_player.cpp +++ b/src/d/actor/d_a_movie_player.cpp @@ -27,9 +27,10 @@ #include "f_op/f_op_overlap_mng.h" #include "dusk/gx_helper.h" -#include "dusk/memory.h" #include "dusk/os.h" +#include "dusk/layout.hpp" +#include "JSystem/JAudio2/JASCriticalSection.h" #include "turbojpeg.h" inline s32 daMP_NEXT_READ_SIZE(daMP_THPReadBuffer* readBuf) { @@ -2573,9 +2574,12 @@ static void __THPHuffDecodeDCTCompV(__REGISTER THPFileInfo* info, THPCoeff* bloc } #else // !TARGET_PC -static std::vector FixJpeg(const std::span data) { - std::vector fixedData; - fixedData.reserve(data.size()); +static std::vector FixedJpegData; +static tjhandle JpegDecompressHandle; + +static const std::vector& FixJpeg(const std::span data) { + FixedJpegData.resize(0); + FixedJpegData.reserve(data.size()); size_t startOfScanLocation = 0; for (; startOfScanLocation < data.size() - 1; startOfScanLocation++) { @@ -2602,48 +2606,32 @@ static std::vector FixJpeg(const std::span data) { // Copy data before SOS for (size_t i = 0; i < startOfScanLocation; i++) { - fixedData.push_back(data[i]); + FixedJpegData.push_back(data[i]); } // Copy data inside SOS, fixing up lacking of "byte shuffling" for (size_t i = startOfScanLocation; i < endOfImage; i++) { u8 value = data[i]; - fixedData.push_back(value); + FixedJpegData.push_back(value); if (value == 0xFF) { - fixedData.push_back(0x00); + FixedJpegData.push_back(0x00); } } // Copy data after SOS. for (size_t i = endOfImage; i < data.size(); i++) { - fixedData.push_back(data[i]); + FixedJpegData.push_back(data[i]); } - return fixedData; + return FixedJpegData; } -struct TjHandle { - tjhandle mHandle; - - explicit TjHandle(const tjhandle handle) : mHandle(handle) {} - ~TjHandle() { - tj3Destroy(mHandle); - } - - operator tjhandle() const { - return mHandle; - } -}; - extern daMP_THPPlayer daMP_ActivePlayer; -static s32 THPVideoDecode(void* file, size_t fileSize, void* tileY, void* tileU, void* tileV, void* work) { - const TjHandle handle(tj3Init(TJINIT_DECOMPRESS)); - if (handle == nullptr) { - OSReport_Error("Failed to create turbojpeg handle: %s", tj3GetErrorStr(nullptr)); - return 1; - } +static s32 THPVideoDecode(void* file, size_t fileSize, void* tileY, void* tileU, void* tileV, void*) { + assert(JpegDecompressHandle); + const auto handle = JpegDecompressHandle; const auto fixedData = FixJpeg(std::span(static_cast(file), fileSize)); auto ret = tj3DecompressHeader(handle, fixedData.data(), fixedData.size()); @@ -3220,8 +3208,13 @@ static void daMP_THPGXYuv2RgbSetup(const GXRenderModeObj* rmode) { Mtx44 m; Mtx e_m; +#if TARGET_PC + w = JUTVideo::getManager()->getFbWidth(); + h = JUTVideo::getManager()->getEfbHeight(); +#else w = rmode->fbWidth; h = rmode->efbHeight; +#endif var_f31 = 0.0f; #if WIDESCREEN_SUPPORT @@ -3651,6 +3644,13 @@ static BOOL daMP_THPPlayerOpen(char const* filename, BOOL onMemory) { } static BOOL daMP_THPPlayerClose() { +#if TARGET_PC + tj3Destroy(JpegDecompressHandle); + JpegDecompressHandle = nullptr; + + FixedJpegData.clear(); +#endif + if (daMP_ActivePlayer.open && daMP_ActivePlayer.state == 0) { daMP_ActivePlayer.open = 0; DVDClose(&daMP_ActivePlayer.fileInfo); @@ -3784,6 +3784,12 @@ static BOOL daMP_ProperTimingForGettingNextFrame() { } } else { s32 frameRate = daMP_ActivePlayer.header.frameRate * 100.0f; +#if TARGET_PC + // DUSK HACK: We only fire retrace callbacks *half* as often as the game expects, + // because we only run them once per frame, and normally there should be two scans + // per game frame. + frameRate *= 2; +#endif if (VIGetTvFormat() == VI_PAL) { daMP_ActivePlayer.curCount = daMP_ActivePlayer.retaceCount * frameRate / 5000; } else { @@ -4112,6 +4118,9 @@ static BOOL daMP_THPPlayerSetVolume(s32 vol, s32 duration) { if (duration < 0) duration = 0; +#if TARGET_PC + JASCriticalSection section; +#endif interrupt = OSDisableInterrupts(); daMP_ActivePlayer.targetVolume = vol; @@ -4155,11 +4164,14 @@ static BOOL daMP_ActivePlayer_Init(char const* moviePath) { daMP_THPPlayerGetVideoInfo(&daMP_videoInfo); daMP_THPPlayerGetAudioInfo(&daMP_audioInfo); +#if !TARGET_PC + // Window can be resized during playback, update this during draw. u16 width = JUTVideo::getManager()->getRenderMode()->fbWidth; u16 height = JUTVideo::getManager()->getRenderMode()->efbHeight; daMP_DrawPosX = (width - daMP_videoInfo.xSize) >> 1; daMP_DrawPosY = (height - daMP_videoInfo.ySize) >> 1; +#endif // "The memory needed for this THP movie is %d bytes\n" OS_REPORT("このTHPムービーが必要なメモリは%dバイトです\n", daMP_THPPlayerCalcNeedMemory()); @@ -4176,6 +4188,15 @@ static BOOL daMP_ActivePlayer_Init(char const* moviePath) { daMP_THPPlayerSetBuffer((u8*)daMP_buffer); +#if TARGET_PC + assert(JpegDecompressHandle == nullptr); + JpegDecompressHandle = tj3Init(TJINIT_DECOMPRESS); + if (JpegDecompressHandle == nullptr) { + OSReport_Error("Failed to create turbojpeg handle: %s", tj3GetErrorStr(nullptr)); + return 0; + } +#endif + if (!daMP_THPPlayerPrepare(0, 0, daMP_audioInfo.sndNumTracks != 1 ? OSGetTick() % daMP_audioInfo.sndNumTracks : 0)) { OSReport("Fail to prepare\n"); #if DEBUG @@ -4211,14 +4232,55 @@ static void daMP_ActivePlayer_Main() { } } +#if TARGET_PC && 0 +#include "imgui.h" +#endif + static void daMP_ActivePlayer_Draw() { - int frame = daMP_THPPlayerDrawCurrentFrame(JUTVideo::getManager()->getRenderMode(), daMP_DrawPosX, daMP_DrawPosY, daMP_videoInfo.xSize, daMP_videoInfo.ySize); +#if TARGET_PC + u16 width = JUTVideo::getManager()->getFbWidth(); + u16 height = JUTVideo::getManager()->getEfbHeight(); + + const auto rect = dusk::LayoutRect::FitRectInRect( + width, + height, + static_cast(daMP_videoInfo.xSize), + static_cast(daMP_videoInfo.ySize)); + + daMP_DrawPosX = static_cast(rect.PosX); + daMP_DrawPosY = static_cast(rect.PosY); +#endif + + int frame = daMP_THPPlayerDrawCurrentFrame( + JUTVideo::getManager()->getRenderMode(), + daMP_DrawPosX, daMP_DrawPosY, +#if TARGET_PC + static_cast(rect.Width()), + static_cast(rect.Height())); +#else + daMP_videoInfo.xSize, + daMP_videoInfo.ySize); +#endif daMP_THPPlayerDrawDone(); if (!fopOvlpM_IsPeek() && frame > 0 && (cAPICPad_ANY_BUTTON(0) || !daMP_c::daMP_c_Get_MovieRestFrame())) { dComIfGp_event_reset(); daMP_c::daMP_c_Set_PercentMovieVolume(0.0f); } + +#if TARGET_PC && 0 + if (ImGui::Begin("Movie player")) { + ImGui::Text("daMP_ReadedBufferQueue: %d", daMP_ReadedBufferQueue.usedCount); + ImGui::Text("daMP_ReadedBufferQueue2: %d", daMP_ReadedBufferQueue2.usedCount); + ImGui::Text("daMP_FreeReadBufferQueue: %d", daMP_FreeReadBufferQueue.usedCount); + ImGui::Text("daMP_DecodedTextureSetQueue: %d", daMP_DecodedTextureSetQueue.usedCount); + ImGui::Text("daMP_FreeTextureSetQueue: %d", daMP_FreeTextureSetQueue.usedCount); + ImGui::Text("daMP_DecodedAudioBufferQueue: %d", daMP_DecodedAudioBufferQueue.usedCount); + ImGui::Text("daMP_FreeAudioBufferQueue: %d", daMP_FreeAudioBufferQueue.usedCount); + } + + ImGui::End(); +#endif } static BOOL daMP_Fail_alloc; diff --git a/src/dusk/audio/DuskAudioSystem.cpp b/src/dusk/audio/DuskAudioSystem.cpp index 522f4c9d60..71b1944fd2 100644 --- a/src/dusk/audio/DuskAudioSystem.cpp +++ b/src/dusk/audio/DuskAudioSystem.cpp @@ -111,11 +111,6 @@ int RenderNewAudioFrame() { outRaw.flush(); #endif - if (JASDriver::extMixCallback != nullptr) { - // TODO: actually mix this data in. - JASDriver::extMixCallback(countSubframes * DSP_SUBFRAME_SIZE); - } - return static_cast(countSubframes) * DSP_SUBFRAME_SIZE; } @@ -138,6 +133,19 @@ void RenderAudioSubframe() { InterleaveOutputData(OutBuffer, OutInterleaveBuffer); + if (JASDriver::extMixCallback != nullptr && JASDriver::sMixMode == MIX_MODE_INTERLEAVE) { + static_assert(OutputSubframe::NUM_CHANNELS == 2); // This code only works with Stereo so far. + // NOTE: In the real game, this gets called on the entire audio frame, rather than the subframe. + // That's probably more efficient, but I didn't wanna change the code to calculate the + // entire audio buffers at once. + const auto mixData = JASDriver::extMixCallback(DSP_SUBFRAME_SIZE); + if (mixData) { + for (int i = 0; i < OutInterleaveBuffer.size(); i++) { + OutInterleaveBuffer[i] += static_cast(mixData[i]) / static_cast(0x7FFF); + } + } + } + #if defined(DUSK_DUMP_AUDIO) outRaw.write((const char*)OutInterleaveBuffer.data(), sizeof(OutInterleaveBuffer)); #endif diff --git a/src/dusk/layout.cpp b/src/dusk/layout.cpp new file mode 100644 index 0000000000..11185debd6 --- /dev/null +++ b/src/dusk/layout.cpp @@ -0,0 +1,25 @@ +#include "dusk/layout.hpp" + +using namespace dusk; + +LayoutRect LayoutRect::FitRectInRect( + const f32 widthOuter, + const f32 heightOuter, + const f32 widthInner, + const f32 heightInner) { + + // Try as if constrained vertically first. + auto width = widthInner * (heightOuter / heightInner); + auto height = heightOuter; + if (width > widthOuter) { + // Otherwise, constrained horizontally. + width = widthOuter; + height = heightOuter * (widthOuter / widthInner); + } + + // Center it + const auto posX = (widthOuter - width) / 2; + const auto posY = (heightOuter - height) / 2; + + return {posX, posY, posX + width, posY + height}; +} From 98da847bf510ceb380a00e432bccbbc826725ce2 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 29 Mar 2026 01:54:45 +0100 Subject: [PATCH 03/19] OSThread fixes OSCancelThread and OSDetachThread stubbed out cuz they're probably both broken Code cleanup, unnecessary code copied from decomp removed, copy-paste reduced. Don't immediately detach threads when creating them (what's the point??) Fix OSJoinThread --- src/dusk/OSThread.cpp | 88 ++++++++++++------------------------------- 1 file changed, 24 insertions(+), 64 deletions(-) diff --git a/src/dusk/OSThread.cpp b/src/dusk/OSThread.cpp index 7daa2cb137..322d33a331 100644 --- a/src/dusk/OSThread.cpp +++ b/src/dusk/OSThread.cpp @@ -17,6 +17,7 @@ #include #include "JSystem/JKernel/JKRHeap.h" +#include "dusk/main.h" #include "dusk/os.h" #if _WIN32 @@ -36,6 +37,13 @@ struct PCThreadData { void* param; bool started = false; bool suspended = false; + + ~PCThreadData() { + if (dusk::IsShuttingDown) { + // Don't care about threads if we're shutting down. + nativeThread.detach(); + } + } }; // Lazy-initialized to avoid DLL static init crashes (used before DllMain completes) @@ -48,6 +56,16 @@ static std::unordered_map>& GetThreadDa return map; } +static PCThreadData* GetThreadData(OSThread* thread) { + std::lock_guard mapLock(GetThreadDataMutex()); + auto it = GetThreadDataMap().find(thread); + if (it != GetThreadDataMap().end()) { + return it->second.get(); + } + + return nullptr; +} + // Side-table for OSThreadQueue -> condition_variable (for OSSleepThread/OSWakeupThread) static std::mutex& GetQueueCvMutex() { static std::mutex mtx; @@ -83,8 +101,6 @@ 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() { @@ -106,36 +122,6 @@ 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 @@ -193,8 +179,6 @@ void __OSThreadInit(void) { tls_currentThread = &sDefaultThread; // Active queue - OSInitThreadQueue(&__OSActiveThreadQueue); - EnqueueActive(&sDefaultThread); sActiveThreadCount = 1; OSReport("[PC-OSThread] Thread system initialized (multi-threaded mode)\n"); @@ -271,7 +255,6 @@ int OSCreateThread(OSThread* thread, void* (*func)(void*), void* param, } // Add to active queue - EnqueueActive(thread); sActiveThreadCount++; OSReport("[PC-OSThread] Created thread %p (priority=%d, stackSize=%u)\n", @@ -351,16 +334,7 @@ s32 OSResumeThread(OSThread* thread) { // Only wake up if suspend count drops to 0 if (thread->suspend == 0) { - PCThreadData* data = nullptr; - - // Lock the global map to safely retrieve our thread data pointer - { - std::lock_guard mapLock(GetThreadDataMutex()); - auto it = GetThreadDataMap().find(thread); - if (it != GetThreadDataMap().end()) { - data = it->second.get(); - } - } + PCThreadData* data = GetThreadData(thread); if (data) { // Lock the specific thread mutex to safely modify state and notify @@ -375,7 +349,6 @@ s32 OSResumeThread(OSThread* thread) { threadLock.unlock(); data->nativeThread = std::thread(ThreadEntryWrapper, thread, data); - data->nativeThread.detach(); OSReport("[PC-OSThread] Started thread %p\n", thread); } else { // Resume from suspension: signal the condition variable @@ -398,16 +371,7 @@ s32 OSSuspendThread(OSThread* thread) { // If transitioning from running (0) to suspended (1) if (prevSuspend == 0) { - PCThreadData* data = nullptr; - - // Lock the global map to find our thread data - { - std::lock_guard mapLock(GetThreadDataMutex()); - auto it = GetThreadDataMap().find(thread); - if (it != GetThreadDataMap().end()) { - data = it->second.get(); - } - } + PCThreadData* data = GetThreadData(thread); if (data && data->started) { std::unique_lock threadLock(data->mtx); @@ -495,7 +459,6 @@ void OSExitThread(void* val) { currentThread->val = val; if (currentThread->attr & OS_THREAD_ATTR_DETACH) { - DequeueActive(currentThread); currentThread->state = 0; } else { currentThread->state = OS_THREAD_STATE_MORIBUND; @@ -507,10 +470,10 @@ void OSExitThread(void* val) { } void OSCancelThread(OSThread* thread) { + CRASH("OSCancelThread not implemented"); if (!thread) return; if (thread->attr & OS_THREAD_ATTR_DETACH) { - DequeueActive(thread); thread->state = 0; } else { thread->state = OS_THREAD_STATE_MORIBUND; @@ -521,11 +484,11 @@ void OSCancelThread(OSThread* thread) { } void OSDetachThread(OSThread* thread) { + CRASH("OSDetachThread not implemented"); if (!thread) return; thread->attr |= OS_THREAD_ATTR_DETACH; if (thread->state == OS_THREAD_STATE_MORIBUND) { - DequeueActive(thread); thread->state = 0; } OSWakeupThread(&thread->queueJoin); @@ -534,17 +497,14 @@ void OSDetachThread(OSThread* thread) { 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->attr & OS_THREAD_ATTR_DETACH)) { + GetThreadData(thread)->nativeThread.join(); } if (thread->state == OS_THREAD_STATE_MORIBUND) { if (val) { *(s32*)val = (s32)(intptr_t)thread->val; } - DequeueActive(thread); thread->state = 0; return 1; } From 70a5bb08a4a9e0c507c89aaf76896a4c44c970aa Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 29 Mar 2026 01:54:49 +0100 Subject: [PATCH 04/19] Comment --- src/dusk/audio/DuskAudioSystem.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/dusk/audio/DuskAudioSystem.cpp b/src/dusk/audio/DuskAudioSystem.cpp index 71b1944fd2..93e161512f 100644 --- a/src/dusk/audio/DuskAudioSystem.cpp +++ b/src/dusk/audio/DuskAudioSystem.cpp @@ -138,6 +138,7 @@ void RenderAudioSubframe() { // NOTE: In the real game, this gets called on the entire audio frame, rather than the subframe. // That's probably more efficient, but I didn't wanna change the code to calculate the // entire audio buffers at once. + // This is only used for the movie player, and it seems to work fine with the smaller calls. const auto mixData = JASDriver::extMixCallback(DSP_SUBFRAME_SIZE); if (mixData) { for (int i = 0; i < OutInterleaveBuffer.size(); i++) { From 23c6b78bc935893807b79daf3d5773de03bc852b Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 29 Mar 2026 01:56:41 +0100 Subject: [PATCH 05/19] Properly clean up movie player threads Pain and suffering. --- src/d/actor/d_a_movie_player.cpp | 143 ++++++++++++++++++++++++++++--- 1 file changed, 133 insertions(+), 10 deletions(-) diff --git a/src/d/actor/d_a_movie_player.cpp b/src/d/actor/d_a_movie_player.cpp index f372c0216b..2cbfc8dc82 100644 --- a/src/d/actor/d_a_movie_player.cpp +++ b/src/d/actor/d_a_movie_player.cpp @@ -37,6 +37,14 @@ inline s32 daMP_NEXT_READ_SIZE(daMP_THPReadBuffer* readBuf) { return *(BE(s32)*)readBuf->ptr; } +#if TARGET_PC +// idk what OS_THREAD_ATTR_DETACH does, and it stops OSThreadJoin() +// probably the difference doesn't matter since we are using OS threads anyways. +#define OS_THREAD_ATTR 0 +#else +#define OS_THREAD_ATTR OS_THREAD_ATTR_DETACH +#endif + #if defined(__cplusplus) && !TARGET_PC extern "C" { #endif @@ -2700,6 +2708,9 @@ static BOOL THPInit() { static daMP_THPPlayer daMP_ActivePlayer; +#if TARGET_PC +static BOOL ReadThreadCancelled; +#endif static BOOL daMP_ReadThreadCreated; static OSMessageQueue daMP_FreeReadBufferQueue; @@ -2754,7 +2765,14 @@ void daMP_ReadThreadStart() { void daMP_ReadThreadCancel() { if (daMP_ReadThreadCreated) { +#if TARGET_PC + ReadThreadCancelled = TRUE; + OSReceiveMessage(&daMP_ReadedBufferQueue, nullptr, OS_MESSAGE_NOBLOCK); + OSSendMessage(&daMP_FreeReadBufferQueue, nullptr, OS_MESSAGE_NOBLOCK); + OSJoinThread(&daMP_ReadThread, nullptr); +#else OSCancelThread(&daMP_ReadThread); +#endif daMP_ReadThreadCreated = FALSE; } } @@ -2774,8 +2792,17 @@ void* daMP_Reader(void*) { offset = daMP_ActivePlayer.initOffset; initReadSize = daMP_ActivePlayer.initReadSize; +#if TARGET_PC + while (!ReadThreadCancelled) { +#else while (TRUE) { +#endif buf = (daMP_THPReadBuffer*)daMP_PopFreeReadBuffer(); +#if TARGET_PC + if (!buf) { + return nullptr; + } +#endif status = DVDReadPrio(&daMP_ActivePlayer.fileInfo, buf->ptr, initReadSize, offset, 2); if (status != initReadSize) { if (status == -1) @@ -2783,7 +2810,11 @@ void* daMP_Reader(void*) { if (frame == 0) daMP_PrepareReady(FALSE); +#if TARGET_PC + return nullptr; +#else OSSuspendThread(&daMP_ReadThread); +#endif } buf->frameNumber = frame; @@ -2796,20 +2827,35 @@ void* daMP_Reader(void*) { if (curFrame == daMP_ActivePlayer.header.numFrames - 1) { if (daMP_ActivePlayer.playFlag & 1) offset = daMP_ActivePlayer.header.movieDataOffsets; - else - OSSuspendThread(&daMP_ReadThread); + else { +#if TARGET_PC + return nullptr; +#else + OSSuspendThread(&daMP_ReadThread); +#endif + } } frame++; } + +#if TARGET_PC + return nullptr; +#endif } static u8 daMP_ReadThreadStack[0x2000]; +#if TARGET_PC +static BOOL VideoThreadCancelled; +#endif static BOOL daMP_VideoDecodeThreadCreated; static BOOL daMP_CreateReadThread(s32 param_0) { - if (!OSCreateThread(&daMP_ReadThread, daMP_Reader, 0, daMP_ReadThreadStack + sizeof(daMP_ReadThreadStack), sizeof(daMP_ReadThreadStack), param_0, 1)) { +#if TARGET_PC + ReadThreadCancelled = FALSE; +#endif + if (!OSCreateThread(&daMP_ReadThread, daMP_Reader, 0, daMP_ReadThreadStack + sizeof(daMP_ReadThreadStack), sizeof(daMP_ReadThreadStack), param_0, OS_THREAD_ATTR)) { OSReport("Can't create read thread\n"); return FALSE; } @@ -2868,6 +2914,12 @@ static void daMP_VideoDecode(daMP_THPReadBuffer* readBuffer) { tile = &readBuffer->ptr[daMP_ActivePlayer.compInfo.numComponents * 4] + 8; textureSet = (THPTextureSet*)daMP_PopFreeTextureSet(); +#if TARGET_PC + if (textureSet == nullptr) { + return; + } +#endif + for (i = 0; i < daMP_ActivePlayer.compInfo.numComponents; i++) { switch (daMP_ActivePlayer.compInfo.frameComp[i]) { case 0: { @@ -2905,15 +2957,23 @@ static void daMP_VideoDecode(daMP_THPReadBuffer* readBuffer) { static void* daMP_VideoDecoder(void* param_0) { #if TARGET_PC - OSSetCurrentThreadName("video decoder"); + OSSetCurrentThreadName("movie video decoder"); #endif daMP_THPReadBuffer* thpBuffer; - +#if TARGET_PC + while (!VideoThreadCancelled) { +#else while (TRUE) { +#endif if (daMP_ActivePlayer.audioExist) { for (; daMP_ActivePlayer.videoDecodeCount < 0;) { thpBuffer = (daMP_THPReadBuffer*)daMP_PopReadedBuffer2(); +#if TARGET_PC + if (thpBuffer == nullptr) { + goto exit; + } +#endif s32 remaining = ((thpBuffer->frameNumber + daMP_ActivePlayer.initReadFrame) % daMP_ActivePlayer.header.numFrames); @@ -2933,14 +2993,24 @@ static void* daMP_VideoDecoder(void* param_0) { else thpBuffer = (daMP_THPReadBuffer*)daMP_PopReadedBuffer(); +#if TARGET_PC + if (thpBuffer == nullptr) { + goto exit; + } +#endif + daMP_VideoDecode(thpBuffer); daMP_PushFreeReadBuffer(thpBuffer); } +#if TARGET_PC + exit:; + return nullptr; +#endif } static void* daMP_VideoDecoderForOnMemory(void* param_0) { #if TARGET_PC - OSSetCurrentThreadName("video decoder"); + OSSetCurrentThreadName("movie video decoder"); #endif daMP_THPReadBuffer readBuffer; @@ -2999,13 +3069,17 @@ static void* daMP_VideoDecoderForOnMemory(void* param_0) { } static BOOL daMP_CreateVideoDecodeThread(OSPriority prio, u8* param_1) { +#if TARGET_PC + VideoThreadCancelled = FALSE; +#endif + if (param_1 != NULL) { - if (!OSCreateThread(&daMP_VideoDecodeThread, daMP_VideoDecoderForOnMemory, param_1, daMP_VideoDecodeThreadStack + sizeof(daMP_VideoDecodeThreadStack), sizeof(daMP_VideoDecodeThreadStack), prio, 1)) { + if (!OSCreateThread(&daMP_VideoDecodeThread, daMP_VideoDecoderForOnMemory, param_1, daMP_VideoDecodeThreadStack + sizeof(daMP_VideoDecodeThreadStack), sizeof(daMP_VideoDecodeThreadStack), prio, OS_THREAD_ATTR)) { OSReport("Can't create video decode thread\n"); return FALSE; } } else { - if (!OSCreateThread(&daMP_VideoDecodeThread, daMP_VideoDecoder, NULL, daMP_VideoDecodeThreadStack + sizeof(daMP_VideoDecodeThreadStack), sizeof(daMP_VideoDecodeThreadStack), prio, 1)) { + if (!OSCreateThread(&daMP_VideoDecodeThread, daMP_VideoDecoder, NULL, daMP_VideoDecodeThreadStack + sizeof(daMP_VideoDecodeThreadStack), sizeof(daMP_VideoDecodeThreadStack), prio, OS_THREAD_ATTR)) { OSReport("Can't create video decode thread\n"); return FALSE; } @@ -3026,12 +3100,24 @@ static void daMP_VideoDecodeThreadStart() { void daMP_VideoDecodeThreadCancel() { if (daMP_VideoDecodeThreadCreated) { +#if TARGET_PC + VideoThreadCancelled = TRUE; + // Push junk into the queues so the thread unblocks and can exit cleanly. + daMP_PushFreeTextureSet(nullptr); + daMP_PushReadedBuffer(nullptr); + daMP_PushReadedBuffer2(nullptr); + OSJoinThread(&daMP_VideoDecodeThread, nullptr); +#else OSCancelThread(&daMP_VideoDecodeThread); +#endif daMP_VideoDecodeThreadCreated = FALSE; } } static BOOL daMP_AudioDecodeThreadCreated; +#if TARGET_PC +static BOOL AudioThreadCancelled; +#endif static OSThread daMP_AudioDecodeThread; @@ -3073,6 +3159,11 @@ static void daMP_AudioDecode(daMP_THPReadBuffer* readBuffer) { offsets = (BE(u32)*)(readBuffer->ptr + 8); audioData = &readBuffer->ptr[daMP_ActivePlayer.compInfo.numComponents * 4] + 8; audioBuf = (THPAudioBuffer*)daMP_PopFreeAudioBuffer(); +#if TARGET_PC + if (!audioBuf) { + return; + } +#endif for (i = 0; i < daMP_ActivePlayer.compInfo.numComponents; i++) { switch (daMP_ActivePlayer.compInfo.frameComp[i]) { @@ -3098,11 +3189,21 @@ static void* daMP_AudioDecoder(void* param_0) { daMP_THPReadBuffer* buf; +#if TARGET_PC + while (!AudioThreadCancelled) { +#else while (TRUE) { +#endif buf = (daMP_THPReadBuffer*)daMP_PopReadedBuffer(); +#if TARGET_PC + if (!buf) { + return nullptr; + } +#endif daMP_AudioDecode(buf); daMP_PushReadedBuffer2(buf); } + return nullptr; } static void* daMP_AudioDecoderForOnMemory(void* param_0) { @@ -3120,7 +3221,11 @@ static void* daMP_AudioDecoderForOnMemory(void* param_0) { readBuffer.ptr = (u8*)param_0; frame = 0; +#if TARGET_PC + while (!AudioThreadCancelled) { +#else while (TRUE) { +#endif readBuffer.frameNumber = frame; daMP_AudioDecode(&readBuffer); @@ -3130,7 +3235,11 @@ static void* daMP_AudioDecoderForOnMemory(void* param_0) { readSize = *(s32*)readBuffer.ptr; readBuffer.ptr = daMP_ActivePlayer.movieData; } else { +#if TARGET_PC + return nullptr; +#else OSSuspendThread(&daMP_AudioDecodeThread); +#endif } } else { size = *(s32*)readBuffer.ptr; @@ -3139,6 +3248,7 @@ static void* daMP_AudioDecoderForOnMemory(void* param_0) { } frame++; } + return nullptr; } static OSMessage daMP_FreeAudioBufferMessage[3]; @@ -3146,13 +3256,16 @@ static OSMessage daMP_FreeAudioBufferMessage[3]; static OSMessage daMP_DecodedAudioBufferMessage[3]; static BOOL daMP_CreateAudioDecodeThread(OSPriority prio, u8* param_1) { +#if TARGET_PC + AudioThreadCancelled = FALSE; +#endif if (param_1 != NULL) { - if (!OSCreateThread(&daMP_AudioDecodeThread, daMP_AudioDecoderForOnMemory, param_1, daMP_AudioDecodeThreadStack + sizeof(daMP_AudioDecodeThreadStack), sizeof(daMP_AudioDecodeThreadStack), prio, 1)) { + if (!OSCreateThread(&daMP_AudioDecodeThread, daMP_AudioDecoderForOnMemory, param_1, daMP_AudioDecodeThreadStack + sizeof(daMP_AudioDecodeThreadStack), sizeof(daMP_AudioDecodeThreadStack), prio, OS_THREAD_ATTR)) { OS_REPORT("Can't create audio decode thread\n"); return FALSE; } } else { - if (!OSCreateThread(&daMP_AudioDecodeThread, daMP_AudioDecoder, NULL, daMP_AudioDecodeThreadStack + sizeof(daMP_AudioDecodeThreadStack), sizeof(daMP_AudioDecodeThreadStack), prio, 1)) { + if (!OSCreateThread(&daMP_AudioDecodeThread, daMP_AudioDecoder, NULL, daMP_AudioDecodeThreadStack + sizeof(daMP_AudioDecodeThreadStack), sizeof(daMP_AudioDecodeThreadStack), prio, OS_THREAD_ATTR)) { OSReport("Can't create audio decode thread\n"); return FALSE; } @@ -3173,7 +3286,17 @@ void daMP_AudioDecodeThreadStart() { void daMP_AudioDecodeThreadCancel() { if (daMP_AudioDecodeThreadCreated) { +#if TARGET_PC + AudioThreadCancelled = TRUE; + // Push junk into the queues so the thread unblocks and can exit cleanly. + OSSendMessage(&daMP_ReadedBufferQueue, nullptr, OS_MESSAGE_NOBLOCK); + daMP_PushFreeAudioBuffer(nullptr); + OSReceiveMessage(&daMP_ReadedBufferQueue2, nullptr, OS_MESSAGE_NOBLOCK); + OSReceiveMessage(&daMP_DecodedAudioBufferQueue, nullptr, OS_MESSAGE_NOBLOCK); + OSJoinThread(&daMP_AudioDecodeThread, nullptr); +#else OSCancelThread(&daMP_AudioDecodeThread); +#endif daMP_AudioDecodeThreadCreated = FALSE; } } From b09da8291274b34f77f3fea0b961f7f5b89c71e1 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 29 Mar 2026 03:07:03 +0200 Subject: [PATCH 06/19] Allow movie support to be compiled out (and therefore remove the libjpeg-turbo dependency) --- CMakeLists.txt | 16 +++++++++++++--- src/d/actor/d_a_movie_player.cpp | 13 +++++++++++-- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0fea651943..797b055c47 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,6 +19,7 @@ add_subdirectory(extern/aurora EXCLUDE_FROM_ALL) option(DUSK_BUILD_WARNINGS "If off, compiler warnings will be suppressed") option(DUSK_SELECTED_OPT "If on, selected parts of the project will be compiled with optimizations on Debug, intending to make the game run at 30 FPS. Note for MSVC: you will need to remove '/RTC1' from your debug flags in CMake.") +option(DUSK_MOVIE_SUPPORT "If on, compile against libjpeg-turbo to enable THP file decoding" ON) if (CMAKE_SYSTEM_NAME STREQUAL Linux) # -Wno-multichar: Multi-character constants ('ABCD') are implementation-defined but all compilers @@ -52,7 +53,10 @@ endif () include(FetchContent) -include(ExternalProject) + +if (DUSK_MOVIE_SUPPORT) + include(ExternalProject) +endif () message(STATUS "dusk: Fetching cxxopts") FetchContent_Declare( @@ -63,7 +67,9 @@ FetchContent_Declare( FetchContent_MakeAvailable(cxxopts) -include(cmake/libjpeg.cmake) +if (DUSK_MOVIE_SUPPORT) + include(cmake/libjpeg.cmake) +endif () include(files.cmake) # TODO: version handling for res includes @@ -109,7 +115,11 @@ add_library(game SHARED ${DOLZEL_FILES} ${Z2AUDIOLIB_FILES} ${SSYSTEM_FILES} ${J src/dusk/imgui/ImGuiStubLog.cpp src/dusk/imgui/ImGuiAudio.cpp) -target_link_libraries(game PRIVATE game_debug cxxopts::cxxopts libjpeg-turbo-lib) +target_link_libraries(game PRIVATE game_debug cxxopts::cxxopts) +if (DUSK_MOVIE_SUPPORT) + target_link_libraries(game PRIVATE libjpeg-turbo-lib) + target_compile_definitions(game PRIVATE MOVIE_SUPPORT=1) +endif () target_compile_definitions(game PRIVATE TARGET_PC AVOID_UB=1 VERSION=0 NDEBUG=1 NDEBUG_DEFINED=1 DEBUG_DEFINED=0 DUSK_TP_VERSION="${DUSK_TP_VERSION}" DUSK_GAME_NAME="${DUSK_GAME_NAME}" DUSK_GAME_VERSION="${DUSK_GAME_VERSION}") add_executable(dusk src/dusk/main.cpp) diff --git a/src/d/actor/d_a_movie_player.cpp b/src/d/actor/d_a_movie_player.cpp index 2cbfc8dc82..d117dd3fa0 100644 --- a/src/d/actor/d_a_movie_player.cpp +++ b/src/d/actor/d_a_movie_player.cpp @@ -31,7 +31,10 @@ #include "dusk/layout.hpp" #include "JSystem/JAudio2/JASCriticalSection.h" + +#if MOVIE_SUPPORT #include "turbojpeg.h" +#endif inline s32 daMP_NEXT_READ_SIZE(daMP_THPReadBuffer* readBuf) { return *(BE(s32)*)readBuf->ptr; @@ -2582,6 +2585,7 @@ static void __THPHuffDecodeDCTCompV(__REGISTER THPFileInfo* info, THPCoeff* bloc } #else // !TARGET_PC +#if MOVIE_SUPPORT static std::vector FixedJpegData; static tjhandle JpegDecompressHandle; @@ -2673,6 +2677,11 @@ static s32 THPVideoDecode(void* file, size_t fileSize, void* tileY, void* tileU, return 0; } +#else // MOVIE_SUPPORT +static s32 THPVideoDecode(void*, size_t, void*, void*, void*, void*) { + return 1; // Immediate error. +} +#endif #endif static BOOL THPInit() { @@ -3767,7 +3776,7 @@ static BOOL daMP_THPPlayerOpen(char const* filename, BOOL onMemory) { } static BOOL daMP_THPPlayerClose() { -#if TARGET_PC +#if TARGET_PC && MOVIE_SUPPORT tj3Destroy(JpegDecompressHandle); JpegDecompressHandle = nullptr; @@ -4311,7 +4320,7 @@ static BOOL daMP_ActivePlayer_Init(char const* moviePath) { daMP_THPPlayerSetBuffer((u8*)daMP_buffer); -#if TARGET_PC +#if TARGET_PC && MOVIE_SUPPORT assert(JpegDecompressHandle == nullptr); JpegDecompressHandle = tj3Init(TJINIT_DECOMPRESS); if (JpegDecompressHandle == nullptr) { From 9738917a91c133ea0f7fafc397a2859bc2658294 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 29 Mar 2026 03:29:30 +0200 Subject: [PATCH 07/19] Clang compile fixes --- include/d/actor/d_a_movie_player.h | 6 +++--- src/d/actor/d_a_movie_player.cpp | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/include/d/actor/d_a_movie_player.h b/include/d/actor/d_a_movie_player.h index 4e6a44bd54..17d6a7ac2b 100644 --- a/include/d/actor/d_a_movie_player.h +++ b/include/d/actor/d_a_movie_player.h @@ -83,9 +83,9 @@ typedef struct THPHeader { /* 0x2C */ BE(u32) finalFrameDataOffsets; } THPHeader; -u32 THPAudioDecode(s16* audioBuffer, u8* audioFrame, s32 flag); -s32 __THPAudioGetNewSample(THPAudioDecodeInfo* info); -void __THPAudioInitialize(THPAudioDecodeInfo* info, u8* ptr); +static u32 THPAudioDecode(s16* audioBuffer, u8* audioFrame, s32 flag); +static s32 __THPAudioGetNewSample(THPAudioDecodeInfo* info); +static void __THPAudioInitialize(THPAudioDecodeInfo* info, u8* ptr); #define THP_AUDIO_BUFFER_COUNT 3 #define THP_READ_BUFFER_COUNT 10 diff --git a/src/d/actor/d_a_movie_player.cpp b/src/d/actor/d_a_movie_player.cpp index d117dd3fa0..14eb8dfa02 100644 --- a/src/d/actor/d_a_movie_player.cpp +++ b/src/d/actor/d_a_movie_player.cpp @@ -2638,7 +2638,7 @@ static const std::vector& FixJpeg(const std::span data) { return FixedJpegData; } -extern daMP_THPPlayer daMP_ActivePlayer; +static daMP_THPPlayer daMP_ActivePlayer; static s32 THPVideoDecode(void* file, size_t fileSize, void* tileY, void* tileU, void* tileV, void*) { assert(JpegDecompressHandle); @@ -2715,7 +2715,9 @@ static BOOL THPInit() { } #endif +#if !TARGET_PC // Defined earlier in file. static daMP_THPPlayer daMP_ActivePlayer; +#endif #if TARGET_PC static BOOL ReadThreadCancelled; From e827f19554cea194578ede18acd41ee469047429 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 29 Mar 2026 03:29:51 +0200 Subject: [PATCH 08/19] Give up on fetching libjpeg-turbo ourselves, rely on find_package and user providing it at build time --- CMakeLists.txt | 20 +++++++++++--------- cmake/libjpeg.cmake | 12 ------------ 2 files changed, 11 insertions(+), 21 deletions(-) delete mode 100644 cmake/libjpeg.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 797b055c47..cae567cbce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,6 +21,15 @@ option(DUSK_BUILD_WARNINGS "If off, compiler warnings will be suppressed") option(DUSK_SELECTED_OPT "If on, selected parts of the project will be compiled with optimizations on Debug, intending to make the game run at 30 FPS. Note for MSVC: you will need to remove '/RTC1' from your debug flags in CMake.") option(DUSK_MOVIE_SUPPORT "If on, compile against libjpeg-turbo to enable THP file decoding" ON) +find_package(libjpeg-turbo) +set(DUSK_MOVIE_SUPPORT_REAL ${DUSK_MOVIE_SUPPORT}) +if (DUSK_MOVIE_SUPPORT AND NOT libjpeg-turbo_FOUND) + message(WARNING "libjpeg-turbo not found but DUSK_MOVIE_SUPPORT set, movie playback will not be available!") + set(DUSK_MOVIE_SUPPORT_REAL OFF) +endif () + +message(WARNING "FOUND: ${libjpeg-turbo_FOUND}") + if (CMAKE_SYSTEM_NAME STREQUAL Linux) # -Wno-multichar: Multi-character constants ('ABCD') are implementation-defined but all compilers # (CW, GCC, Clang, MSVC) encode them identically in big-endian order. @@ -54,10 +63,6 @@ endif () include(FetchContent) -if (DUSK_MOVIE_SUPPORT) - include(ExternalProject) -endif () - message(STATUS "dusk: Fetching cxxopts") FetchContent_Declare( cxxopts @@ -67,9 +72,6 @@ FetchContent_Declare( FetchContent_MakeAvailable(cxxopts) -if (DUSK_MOVIE_SUPPORT) - include(cmake/libjpeg.cmake) -endif () include(files.cmake) # TODO: version handling for res includes @@ -116,8 +118,8 @@ add_library(game SHARED ${DOLZEL_FILES} ${Z2AUDIOLIB_FILES} ${SSYSTEM_FILES} ${J src/dusk/imgui/ImGuiAudio.cpp) target_link_libraries(game PRIVATE game_debug cxxopts::cxxopts) -if (DUSK_MOVIE_SUPPORT) - target_link_libraries(game PRIVATE libjpeg-turbo-lib) +if (DUSK_MOVIE_SUPPORT_REAL) + target_link_libraries(game PRIVATE libjpeg-turbo::turbojpeg-static) target_compile_definitions(game PRIVATE MOVIE_SUPPORT=1) endif () target_compile_definitions(game PRIVATE TARGET_PC AVOID_UB=1 VERSION=0 NDEBUG=1 NDEBUG_DEFINED=1 DEBUG_DEFINED=0 diff --git a/cmake/libjpeg.cmake b/cmake/libjpeg.cmake deleted file mode 100644 index b7f719f003..0000000000 --- a/cmake/libjpeg.cmake +++ /dev/null @@ -1,12 +0,0 @@ -ExternalProject_Add( - libjpeg-turbo - URL https://github.com/libjpeg-turbo/libjpeg-turbo/archive/refs/tags/3.1.90.tar.gz - URL_HASH SHA256=076ef1431f2803a91f07e0f92433d4dcf39bc9113226c4f46ba3d3d54f514c9d - DOWNLOAD_EXTRACT_TIMESTAMP TRUE - CMAKE_ARGS -DCMAKE_INSTALL_PREFIX= -) - -ExternalProject_Get_Property(libjpeg-turbo INSTALL_DIR) -add_library(libjpeg-turbo-lib STATIC IMPORTED GLOBAL) -set_target_properties(libjpeg-turbo-lib PROPERTIES IMPORTED_LOCATION ${INSTALL_DIR}/lib/turbojpeg-static.lib) -target_include_directories(libjpeg-turbo-lib INTERFACE ${INSTALL_DIR}/include) From e18e8cc5dd248fbf59e931ebc9c0347af80bc71b Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 29 Mar 2026 03:31:36 +0200 Subject: [PATCH 09/19] Forgot to remove this --- CMakeLists.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cae567cbce..051e5700fb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,8 +28,6 @@ if (DUSK_MOVIE_SUPPORT AND NOT libjpeg-turbo_FOUND) set(DUSK_MOVIE_SUPPORT_REAL OFF) endif () -message(WARNING "FOUND: ${libjpeg-turbo_FOUND}") - if (CMAKE_SYSTEM_NAME STREQUAL Linux) # -Wno-multichar: Multi-character constants ('ABCD') are implementation-defined but all compilers # (CW, GCC, Clang, MSVC) encode them identically in big-endian order. From 95d6d24d3171364808e4d1e8fb9e8f87e2b7ee3a Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 29 Mar 2026 03:50:37 +0200 Subject: [PATCH 10/19] Fix build without movie support --- src/d/actor/d_a_movie_player.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/d/actor/d_a_movie_player.cpp b/src/d/actor/d_a_movie_player.cpp index 14eb8dfa02..4573684b2f 100644 --- a/src/d/actor/d_a_movie_player.cpp +++ b/src/d/actor/d_a_movie_player.cpp @@ -2585,6 +2585,8 @@ static void __THPHuffDecodeDCTCompV(__REGISTER THPFileInfo* info, THPCoeff* bloc } #else // !TARGET_PC +static daMP_THPPlayer daMP_ActivePlayer; + #if MOVIE_SUPPORT static std::vector FixedJpegData; static tjhandle JpegDecompressHandle; @@ -2638,8 +2640,6 @@ static const std::vector& FixJpeg(const std::span data) { return FixedJpegData; } -static daMP_THPPlayer daMP_ActivePlayer; - static s32 THPVideoDecode(void* file, size_t fileSize, void* tileY, void* tileU, void* tileV, void*) { assert(JpegDecompressHandle); From 5fae2be850b4d185d9fc53fc1324faa1996218da Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Tue, 31 Mar 2026 17:33:28 -0400 Subject: [PATCH 11/19] Added camera invert X option to Tweaks menu --- include/d/actor/d_a_alink.h | 1 + src/d/actor/d_a_alink.cpp | 1 + src/d/d_camera.cpp | 7 +++++++ src/dusk/imgui/ImGuiMenuGame.cpp | 1 + 4 files changed, 10 insertions(+) diff --git a/include/d/actor/d_a_alink.h b/include/d/actor/d_a_alink.h index a4d38efc43..f2f96487d4 100644 --- a/include/d/actor/d_a_alink.h +++ b/include/d/actor/d_a_alink.h @@ -8428,6 +8428,7 @@ inline daAlink_c* daAlink_getAlinkActorClass() { #if TARGET_PC namespace dusk::tweaks { extern bool FastIronBoots; + extern bool InvertCameraXAxis; } #endif diff --git a/src/d/actor/d_a_alink.cpp b/src/d/actor/d_a_alink.cpp index 5e2530bdf3..fe8e016c7e 100644 --- a/src/d/actor/d_a_alink.cpp +++ b/src/d/actor/d_a_alink.cpp @@ -56,6 +56,7 @@ #if TARGET_PC bool dusk::tweaks::FastIronBoots = false; +bool dusk::tweaks::InvertCameraXAxis = false; #endif static int daAlink_Create(fopAc_ac_c* i_this); diff --git a/src/d/d_camera.cpp b/src/d/d_camera.cpp index a5efef9bfa..6e7e7b8f53 100644 --- a/src/d/d_camera.cpp +++ b/src/d/d_camera.cpp @@ -762,6 +762,13 @@ void dCamera_c::updatePad() { var_f29 = 0.0f; } else { var_f31 = mDoCPd_c::getSubStickX3D(mPadID); + + #if TARGET_PC + if (dusk::tweaks::InvertCameraXAxis) { + var_f31 *= -1.0f; + } + #endif + var_f30 = mDoCPd_c::getSubStickY(mPadID); var_f29 = mDoCPd_c::getSubStickValue(mPadID); } diff --git a/src/dusk/imgui/ImGuiMenuGame.cpp b/src/dusk/imgui/ImGuiMenuGame.cpp index c68140f3cd..c6acd044a9 100644 --- a/src/dusk/imgui/ImGuiMenuGame.cpp +++ b/src/dusk/imgui/ImGuiMenuGame.cpp @@ -78,6 +78,7 @@ namespace dusk { if (ImGui::BeginMenu("Tweaks")) { ImGui::MenuItem("Fast iron boots", nullptr, &tweaks::FastIronBoots); + ImGui::MenuItem("Invert Camera X Axis", nullptr, &tweaks::InvertCameraXAxis); ImGui::EndMenu(); } From cb77b629e677fd838f680bd853af3ccac44820c5 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Wed, 1 Apr 2026 23:43:26 +0200 Subject: [PATCH 12/19] Fix bad merge in #139 --- CMakeLists.txt | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d162a704b5..3992507347 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -115,15 +115,11 @@ add_library(game SHARED ${DOLZEL_FILES} ${Z2AUDIOLIB_FILES} ${SSYSTEM_FILES} ${J src/dusk/imgui/ImGuiStubLog.cpp src/dusk/imgui/ImGuiAudio.cpp) -<<<<<<< 26-03-28-movie-player -target_link_libraries(game PRIVATE game_debug cxxopts::cxxopts) +target_link_libraries(game PRIVATE game_debug cxxopts::cxxopts absl::flat_hash_map) if (DUSK_MOVIE_SUPPORT_REAL) target_link_libraries(game PRIVATE libjpeg-turbo::turbojpeg-static) target_compile_definitions(game PRIVATE MOVIE_SUPPORT=1) endif () -======= -target_link_libraries(game PRIVATE game_debug cxxopts::cxxopts absl::flat_hash_map) ->>>>>>> main target_compile_definitions(game PRIVATE TARGET_PC AVOID_UB=1 VERSION=0 NDEBUG=1 NDEBUG_DEFINED=1 DEBUG_DEFINED=0 DUSK_TP_VERSION="${DUSK_TP_VERSION}" DUSK_GAME_NAME="${DUSK_GAME_NAME}" DUSK_GAME_VERSION="${DUSK_GAME_VERSION}") target_precompile_headers(game PRIVATE "$<$:${CMAKE_SOURCE_DIR}/include/dusk_pch.hpp>") From 2c82fcb255145bf5ee526661f409732b50b3ef60 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Wed, 1 Apr 2026 23:48:45 +0200 Subject: [PATCH 13/19] How did this compile on my branch but not here... (fix d_a_movie_player.h) --- include/d/actor/d_a_movie_player.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/d/actor/d_a_movie_player.h b/include/d/actor/d_a_movie_player.h index 17d6a7ac2b..0ddc675c24 100644 --- a/include/d/actor/d_a_movie_player.h +++ b/include/d/actor/d_a_movie_player.h @@ -3,6 +3,8 @@ #if !TARGET_PC #include +#else +#include #endif #include "f_op/f_op_actor.h" #include "d/d_drawlist.h" From 0818add160ae2ec42be7905d2e0a63a34f86d5fb Mon Sep 17 00:00:00 2001 From: Irastris Date: Wed, 1 Apr 2026 18:09:40 -0400 Subject: [PATCH 14/19] Update Aurora --- extern/aurora | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/aurora b/extern/aurora index 6b9f614d32..d7722670ae 160000 --- a/extern/aurora +++ b/extern/aurora @@ -1 +1 @@ -Subproject commit 6b9f614d326aaa4a4dead870ffd22393d2a4ef53 +Subproject commit d7722670ae3b1977e513eea0167a3d9009c606c2 From c4bedd6684dd518107f23a7ef76c5ad8f94a213b Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Thu, 2 Apr 2026 01:23:53 +0200 Subject: [PATCH 15/19] BE d_msg_unit.cpp Fixes fish lengths and probably more stuff Fixes #194 --- src/d/d_msg_unit.cpp | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/d/d_msg_unit.cpp b/src/d/d_msg_unit.cpp index bfa7d7b667..98dd33b4a8 100644 --- a/src/d/d_msg_unit.cpp +++ b/src/d/d_msg_unit.cpp @@ -9,31 +9,31 @@ // temporary until a better solution is found typedef struct dMsgUnit_inf1_entry { - u32 dat1EntryOffset; + BE(u32) dat1EntryOffset; #if REGION_JPN - u16 field_0x04; - u16 field_0x06; - u16 field_0x08; - u16 field_0x0a; - u16 field_0x0c; - u16 field_0x0e; - u16 field_0x10; - u16 field_0x12; - u16 field_0x14; - u16 field_0x16; - u16 field_0x18; + BE(u16) field_0x04; + BE(u16) field_0x06; + BE(u16) field_0x08; + BE(u16) field_0x0a; + BE(u16) field_0x0c; + BE(u16) field_0x0e; + BE(u16) field_0x10; + BE(u16) field_0x12; + BE(u16) field_0x14; + BE(u16) field_0x16; + BE(u16) field_0x18; #else - u16 startFrame; - u16 endFrame; + BE(u16) startFrame; + BE(u16) endFrame; #endif } dMsgUnit_inf1_entry; typedef struct dMsgUnit_inf1_section_t { - /* 0x00 */ u32 msgType; // sectionType - /* 0x04 */ u32 size; // total size of the section - /* 0x08 */ u16 entryCount; - /* 0x0A */ u16 entryLength; - /* 0x0C */ u16 msgArchiveId; + /* 0x00 */ BE(u32) msgType; // sectionType + /* 0x04 */ BE(u32) size; // total size of the section + /* 0x08 */ BE(u16) entryCount; + /* 0x0A */ BE(u16) entryLength; + /* 0x0C */ BE(u16) msgArchiveId; /* 0x10 */ dMsgUnit_inf1_entry entries[0]; } dMsgUnit_inf1_section_t; From 7642d72a52d91af859f27178f620e3037252c48d Mon Sep 17 00:00:00 2001 From: Luke Street Date: Wed, 1 Apr 2026 18:30:12 -0600 Subject: [PATCH 16/19] Allow threads to gracefully shutdown --- extern/aurora | 2 +- .../include/JSystem/JKernel/JKRThread.h | 11 ++ libs/JSystem/src/JAudio2/JASTaskThread.cpp | 11 ++ libs/JSystem/src/JFramework/JFWSystem.cpp | 4 + libs/JSystem/src/JKernel/JKRAram.cpp | 9 ++ libs/JSystem/src/JKernel/JKRAramStream.cpp | 9 ++ libs/JSystem/src/JKernel/JKRDecomp.cpp | 9 ++ libs/JSystem/src/JKernel/JKRThread.cpp | 8 ++ libs/JSystem/src/JUtility/JUTVideo.cpp | 2 + src/d/d_resorce.cpp | 9 +- src/dusk/OSMutex.cpp | 9 ++ src/dusk/imgui/ImGuiConsole.cpp | 101 ------------------ src/dusk/stubs.cpp | 33 +++++- src/m_Do/m_Do_DVDError.cpp | 50 +++------ src/m_Do/m_Do_MemCard.cpp | 13 ++- src/m_Do/m_Do_main.cpp | 13 +-- 16 files changed, 135 insertions(+), 158 deletions(-) diff --git a/extern/aurora b/extern/aurora index d7722670ae..31ee02fe06 160000 --- a/extern/aurora +++ b/extern/aurora @@ -1 +1 @@ -Subproject commit d7722670ae3b1977e513eea0167a3d9009c606c2 +Subproject commit 31ee02fe0636d56a15fbe147e9e8673b84943a1e diff --git a/libs/JSystem/include/JSystem/JKernel/JKRThread.h b/libs/JSystem/include/JSystem/JKernel/JKRThread.h index 2830252281..79217f3675 100644 --- a/libs/JSystem/include/JSystem/JKernel/JKRThread.h +++ b/libs/JSystem/include/JSystem/JKernel/JKRThread.h @@ -101,11 +101,22 @@ public: } return message; } +#ifdef TARGET_PC + OSMessage waitMessageBlock(BOOL* received) { + OSMessage message; + BOOL rv = OSReceiveMessage(&mMessageQueue, &message, OS_MESSAGE_BLOCK); + if (received) { + *received = rv; + } + return message; + } +#else OSMessage waitMessageBlock() { OSMessage message; OSReceiveMessage(&mMessageQueue, &message, OS_MESSAGE_BLOCK); return message; } +#endif void jamMessageBlock(OSMessage message) { OSJamMessage(&mMessageQueue, message, OS_MESSAGE_BLOCK); } diff --git a/libs/JSystem/src/JAudio2/JASTaskThread.cpp b/libs/JSystem/src/JAudio2/JASTaskThread.cpp index 365424deb4..d5dfd089ea 100644 --- a/libs/JSystem/src/JAudio2/JASTaskThread.cpp +++ b/libs/JSystem/src/JAudio2/JASTaskThread.cpp @@ -85,7 +85,15 @@ void* JASTaskThread::run() { JASThreadCallStack* callstack; OSInitFastCast(); do { +#ifdef TARGET_PC + BOOL received = FALSE; + callstack = static_cast(waitMessageBlock(&received)); + if (!received) { + break; + } +#else callstack = static_cast(waitMessageBlock()); +#endif if (field_0x84) { OSSleepThread(&threadQueue_); } @@ -98,6 +106,9 @@ void* JASTaskThread::run() { JASKernel::getCommandHeap()->free(callstack); } while (true); +#ifdef TARGET_PC + return NULL; +#endif } void JASTaskThread::pause(bool param_0) { diff --git a/libs/JSystem/src/JFramework/JFWSystem.cpp b/libs/JSystem/src/JFramework/JFWSystem.cpp index e84d19a824..b4effd547a 100644 --- a/libs/JSystem/src/JFramework/JFWSystem.cpp +++ b/libs/JSystem/src/JFramework/JFWSystem.cpp @@ -79,11 +79,15 @@ void JFWSystem::init() { JUTGamePad::init(); +#ifndef TARGET_PC JUTDirectPrint* dbPrint = JUTDirectPrint::start(); +#endif JUTAssertion::create(); +#ifndef TARGET_PC JUTException::create(dbPrint); +#endif systemFont = JKR_NEW JUTResFont(CSetUpParam::systemFontRes, NULL); diff --git a/libs/JSystem/src/JKernel/JKRAram.cpp b/libs/JSystem/src/JKernel/JKRAram.cpp index 8049ee0069..32afe1cef4 100644 --- a/libs/JSystem/src/JKernel/JKRAram.cpp +++ b/libs/JSystem/src/JKernel/JKRAram.cpp @@ -94,7 +94,13 @@ void* JKRAram::run(void) { OSInitMessageQueue(&sMessageQueue, sMessageBuffer, 4); do { OSMessage msg; +#ifdef TARGET_PC + if (!OSReceiveMessage(&sMessageQueue, &msg, OS_MESSAGE_BLOCK)) { + break; + } +#else OSReceiveMessage(&sMessageQueue, &msg, OS_MESSAGE_BLOCK); +#endif JKRAramCommand* message = (JKRAramCommand*)msg; int result = message->field_0x00; JKRAMCommand* command = (JKRAMCommand*)message->command; @@ -106,6 +112,9 @@ void* JKRAram::run(void) { break; } } while (true); +#ifdef TARGET_PC + return NULL; +#endif } void JKRAram::checkOkAddress(u8* addr, u32 size, JKRAramBlock* block, u32 param_4) { diff --git a/libs/JSystem/src/JKernel/JKRAramStream.cpp b/libs/JSystem/src/JKernel/JKRAramStream.cpp index a9dc570ae0..a30f23dfaa 100644 --- a/libs/JSystem/src/JKernel/JKRAramStream.cpp +++ b/libs/JSystem/src/JKernel/JKRAramStream.cpp @@ -43,7 +43,13 @@ void* JKRAramStream::run() { for (;;) { OSMessage message; +#ifdef TARGET_PC + if (!OSReceiveMessage(&sMessageQueue, &message, OS_MESSAGE_BLOCK)) { + break; + } +#else OSReceiveMessage(&sMessageQueue, &message, OS_MESSAGE_BLOCK); +#endif JKRAramStreamCommand* command = (JKRAramStreamCommand*)message; switch (command->mType) { @@ -55,6 +61,9 @@ void* JKRAramStream::run() { break; } } +#ifdef TARGET_PC + return NULL; +#endif } s32 JKRAramStream::readFromAram() { diff --git a/libs/JSystem/src/JKernel/JKRDecomp.cpp b/libs/JSystem/src/JKernel/JKRDecomp.cpp index 3f01e60e15..776823531d 100644 --- a/libs/JSystem/src/JKernel/JKRDecomp.cpp +++ b/libs/JSystem/src/JKernel/JKRDecomp.cpp @@ -34,7 +34,13 @@ void* JKRDecomp::run() { OSInitMessageQueue(&sMessageQueue, sMessageBuffer, 8); for (;;) { OSMessage message; +#ifdef TARGET_PC + if (!OSReceiveMessage(&sMessageQueue, &message, OS_MESSAGE_BLOCK)) { + break; + } +#else OSReceiveMessage(&sMessageQueue, &message, OS_MESSAGE_BLOCK); +#endif JKRDecompCommand* command = (JKRDecompCommand*)message; decode(command->mSrcBuffer, command->mDstBuffer, command->mSrcLength, command->mDstLength); @@ -57,6 +63,9 @@ void* JKRDecomp::run() { OSSendMessage(&command->mMessageQueue, (OSMessage)1, OS_MESSAGE_NOBLOCK); } } +#ifdef TARGET_PC + return NULL; +#endif } JKRDecompCommand* JKRDecomp::prepareCommand(u8* srcBuffer, u8* dstBuffer, u32 srcLength, diff --git a/libs/JSystem/src/JKernel/JKRThread.cpp b/libs/JSystem/src/JKernel/JKRThread.cpp index 6ba97e3d4a..f90b00422c 100644 --- a/libs/JSystem/src/JKernel/JKRThread.cpp +++ b/libs/JSystem/src/JKernel/JKRThread.cpp @@ -309,7 +309,15 @@ void* JKRTask::run() { }; OSInitFastCast(); while (true) { +#ifdef TARGET_PC + BOOL received = FALSE; + TaskMessage* msg = (TaskMessage*)waitMessageBlock(&received); + if (!received) { + break; + } +#else TaskMessage* msg = (TaskMessage*)waitMessageBlock(); +#endif if (msg->field_0x0) { msg->field_0x0(msg->field_0x4); check(); diff --git a/libs/JSystem/src/JUtility/JUTVideo.cpp b/libs/JSystem/src/JUtility/JUTVideo.cpp index 1656aa7e0f..bb1f2cee3b 100644 --- a/libs/JSystem/src/JUtility/JUTVideo.cpp +++ b/libs/JSystem/src/JUtility/JUTVideo.cpp @@ -81,12 +81,14 @@ void JUTVideo::preRetraceProc(u32 retrace_count) { static void* frameBuffer = NULL; +#ifndef TARGET_PC if (frameBuffer) { const GXRenderModeObj* renderMode = JUTGetVideoManager()->getRenderMode(); u16 width = renderMode->fbWidth; u16 height = renderMode->efbHeight; JUTDirectPrint::getManager()->changeFrameBuffer(frameBuffer, width, height); } +#endif if (getManager()->mSetBlack == 1) { s32 frame_count = getManager()->mSetBlackFrameCount; diff --git a/src/d/d_resorce.cpp b/src/d/d_resorce.cpp index ab21e140c0..0bf537bfd7 100644 --- a/src/d/d_resorce.cpp +++ b/src/d/d_resorce.cpp @@ -20,10 +20,9 @@ #include #include -#include "dusk/logging.h" - #ifndef __MWERKS__ #include "dusk/extras.h" +#include "dusk/logging.h" #endif dRes_info_c::dRes_info_c() { @@ -102,11 +101,13 @@ static void setIndirectTex(J3DModelData* i_modelData) { if (memcmp(textureName, "dummy", 6) == 0) { texture->setResTIMG(i, *mDoGph_gInf_c::getFrameBufferTimg()); } -#if !TARGET_PC if (memcmp(textureName, "Zbuffer", 8) == 0) { +#if !TARGET_PC texture->setResTIMG(i, *mDoGph_gInf_c::getZbufferTimg()); - } +#else + DuskLog.warn("Zbuffer texture binding not yet supported"); #endif + } } } diff --git a/src/dusk/OSMutex.cpp b/src/dusk/OSMutex.cpp index 8e26402e0d..ef1a528f1b 100644 --- a/src/dusk/OSMutex.cpp +++ b/src/dusk/OSMutex.cpp @@ -72,6 +72,15 @@ static PCCondData& GetCondData(OSCond* cond) { return *it->second; } +void ClearCondMap() { + std::lock_guard lock(GetCondMapMutex()); + auto& map = GetCondMap(); + for (auto& pair : map) { + pair.second->cv.notify_all(); + } + map.clear(); +} + // ============================================================================ // C API functions // ============================================================================ diff --git a/src/dusk/imgui/ImGuiConsole.cpp b/src/dusk/imgui/ImGuiConsole.cpp index 60ba229ead..52bbef5f85 100644 --- a/src/dusk/imgui/ImGuiConsole.cpp +++ b/src/dusk/imgui/ImGuiConsole.cpp @@ -223,104 +223,3 @@ namespace dusk { } } } - -class Limiter -{ - using delta_clock = std::chrono::high_resolution_clock; - using duration_t = std::chrono::nanoseconds; - -public: - void Reset() - { - m_oldTime = delta_clock::now(); - } - - void Sleep(duration_t targetFrameTime) - { - if (targetFrameTime.count() == 0) - { - return; - } - - auto start = delta_clock::now(); - duration_t adjustedSleepTime = SleepTime(targetFrameTime); - if (adjustedSleepTime.count() > 0) - { - NanoSleep(adjustedSleepTime); - duration_t overslept = TimeSince(start) - adjustedSleepTime; - if (overslept < duration_t{ targetFrameTime }) - { - m_overheadTimes[m_overheadTimeIdx] = overslept; - m_overheadTimeIdx = (m_overheadTimeIdx + 1) % m_overheadTimes.size(); - } - } - Reset(); - } - - duration_t SleepTime(duration_t targetFrameTime) - { - const auto sleepTime = duration_t{ targetFrameTime } - TimeSince(m_oldTime); - m_overhead = std::accumulate(m_overheadTimes.begin(), m_overheadTimes.end(), duration_t{}) / - m_overheadTimes.size(); - if (sleepTime > m_overhead) - { - return sleepTime - m_overhead; - } - return duration_t{ 0 }; - } - -private: - delta_clock::time_point m_oldTime; - std::array m_overheadTimes{}; - size_t m_overheadTimeIdx = 0; - duration_t m_overhead = duration_t{ 0 }; - - duration_t TimeSince(delta_clock::time_point start) - { - return std::chrono::duration_cast(delta_clock::now() - start); - } - -#if _WIN32 - bool m_initialized; - double m_countPerNs; - - void NanoSleep(const duration_t duration) - { - if (!m_initialized) - { - LARGE_INTEGER freq; - QueryPerformanceFrequency(&freq); - m_countPerNs = static_cast(freq.QuadPart) / 1000000000.0; - m_initialized = true; - } - - DWORD ms = std::chrono::duration_cast(duration).count(); - auto tickCount = - static_cast(static_cast(duration.count()) * m_countPerNs); - LARGE_INTEGER count; - QueryPerformanceCounter(&count); - if (ms > 10) - { - // Adjust for Sleep overhead - ::Sleep(ms - 10); - } - auto end = count.QuadPart + tickCount; - do - { - QueryPerformanceCounter(&count); - } while (count.QuadPart < end); - } -#else - void NanoSleep(const duration_t duration) - { - std::this_thread::sleep_for(duration); - } -#endif -}; - -static Limiter g_frameLimiter; -void frame_limiter() -{ - g_frameLimiter.Sleep( - std::chrono::duration_cast(std::chrono::seconds{ 1 }) / 60); -} diff --git a/src/dusk/stubs.cpp b/src/dusk/stubs.cpp index 4e259ff97e..0b9520f3e3 100644 --- a/src/dusk/stubs.cpp +++ b/src/dusk/stubs.cpp @@ -11,7 +11,7 @@ #include #include #include - +#include #ifndef _WIN32 #include @@ -84,6 +84,16 @@ static PCMessageQueueData& GetMsgQueueData(OSMessageQueue* mq) { return *it->second; } +static void ClearMsgQueueMap() { + std::lock_guard lock(GetMsgQueueMapMutex()); + auto& map = GetMsgQueueMap(); + for (auto & [_, value] : map) { + value->cvReceive.notify_all(); + value->cvSend.notify_all(); + } + map.clear(); +} + void OSInitMessageQueue(OSMessageQueue* mq, void* msgArray, s32 msgCount) { if (!mq) return; mq->queueSend.head = mq->queueSend.tail = nullptr; @@ -104,7 +114,10 @@ int OSSendMessage(OSMessageQueue* mq, void* msg, s32 flags) { 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; }); + data.cvSend.wait(lock, [mq] { return mq->usedCount < mq->msgCount || dusk::IsShuttingDown; }); + } + if (dusk::IsShuttingDown) { + return 0; } s32 idx = (mq->firstIndex + mq->usedCount) % mq->msgCount; @@ -124,7 +137,10 @@ int OSReceiveMessage(OSMessageQueue* mq, void* msg, s32 flags) { 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; }); + data.cvReceive.wait(lock, [mq] { return mq->usedCount > 0 || dusk::IsShuttingDown; }); + } + if (dusk::IsShuttingDown) { + return 0; } if (msg) { @@ -146,7 +162,10 @@ int OSJamMessage(OSMessageQueue* mq, void* msg, s32 flags) { 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; }); + data.cvSend.wait(lock, [mq] { return mq->usedCount < mq->msgCount || dusk::IsShuttingDown; }); + } + if (dusk::IsShuttingDown) { + return 0; } // Jam inserts at the front of the queue @@ -185,8 +204,12 @@ BOOL OSGetResetButtonState() { return FALSE; } BOOL OSInitFont(OSFontHeader* fontData) { return FALSE; } BOOL OSLink(OSModuleInfo* newModule, void* bss) { return TRUE; } +void ClearCondMap(); void OSResetSystem(int reset, u32 resetCode, BOOL forceMenu) { OSReport("[PC] OSResetSystem called (reset=%d, code=%u)\n", reset, resetCode); + dusk::IsShuttingDown = true; + ClearMsgQueueMap(); + ClearCondMap(); } void OSSetStringTable(void* stringTable) {} @@ -998,7 +1021,7 @@ f32 GXGetYScaleFactor(u16 efbHeight, u16 xfbHeight) { void GXInitTexCacheRegion(GXTexRegion* region, GXBool is_32b_mipmap, u32 tmem_even, GXTexCacheSize size_even, u32 tmem_odd, GXTexCacheSize size_odd) { STUB_LOG(); -} +} // XXX, this should be some struct? // GXRenderModeObj GXNtsc480IntDf; //GXRenderModeObj GXNtsc480Int; diff --git a/src/m_Do/m_Do_DVDError.cpp b/src/m_Do/m_Do_DVDError.cpp index 62fd71ed21..2cfef5fbb4 100644 --- a/src/m_Do/m_Do_DVDError.cpp +++ b/src/m_Do/m_Do_DVDError.cpp @@ -5,17 +5,11 @@ #include "m_Do/m_Do_DVDError.h" #include "JSystem/JKernel/JKRAssertHeap.h" -#include +#include #include "m_Do/m_Do_dvd_thread.h" #include "m_Do/m_Do_ext.h" #include "m_Do/m_Do_Reset.h" -// Added for the sleep workaround -#include -#include - -#include "dusk/os.h" - #if PLATFORM_GCN const int stack_size = 3072; #else @@ -31,24 +25,25 @@ static OSThread DvdErr_thread; static u8 DvdErr_stack[stack_size] ATTRIBUTE_ALIGN(16); #pragma pop -// Alarm is not needed for the PC workaround -// static OSAlarm Alarm; +static OSAlarm Alarm; void mDoDvdErr_ThdInit() { +#ifdef TARGET_PC + // Thread is not necessary on PC + return; +#endif + if (mDoDvdErr_initialized) { return; } - // OSTime time = OSGetTime(); // Unused in workaround + OSTime time = OSGetTime(); - OSCreateThread(&DvdErr_thread, (void* (*)(void*))mDoDvdErr_Watch, NULL, - DvdErr_stack + sizeof(DvdErr_stack), sizeof(DvdErr_stack), - OSGetThreadPriority(OSGetCurrentThread()) - 3, 1); + OSCreateThread(&DvdErr_thread, (void*(*)(void*))mDoDvdErr_Watch, NULL, DvdErr_stack + sizeof(DvdErr_stack), + sizeof(DvdErr_stack), OSGetThreadPriority(OSGetCurrentThread()) - 3, 1); OSResumeThread(&DvdErr_thread); - - // PC Workaround: Disable Alarm logic. The thread will sleep itself. - // OSCreateAlarm(&Alarm); - // OSSetPeriodicAlarm(&Alarm, time, OS_BUS_CLOCK / 4, AlarmHandler); + OSCreateAlarm(&Alarm); + OSSetPeriodicAlarm(&Alarm, time, OS_BUS_CLOCK / 4, AlarmHandler); mDoDvdErr_initialized = true; } @@ -56,22 +51,15 @@ void mDoDvdErr_ThdInit() { void mDoDvdErr_ThdCleanup() { if (mDoDvdErr_initialized) { OSCancelThread(&DvdErr_thread); - // OSCancelAlarm(&Alarm); // Disable Alarm cancel + OSCancelAlarm(&Alarm); mDoDvdErr_initialized = false; } } static void mDoDvdErr_Watch(void*) { #if PLATFORM_GCN -#ifndef TARGET_PC OSDisableInterrupts(); #endif -#endif - -#if TARGET_PC - OSSetCurrentThreadName("DVD error thread"); -#endif - JKRThread(OSGetCurrentThread(), 0); JKRSetCurrentHeap(mDoExt_getAssertHeap()); @@ -82,20 +70,10 @@ static void mDoDvdErr_Watch(void*) { if (status == DVD_STATE_FATAL_ERROR) { mDoDvdThd::suspend(); } - - // PC Workaround: - // Instead of suspending and waiting for an Alarm (which might not be implemented), - // we simply sleep for a short duration. - // OS_BUS_CLOCK / 4 corresponds to roughly 1/4th of a second on GC. - // We use 250ms here to simulate the periodic check. - - // OSSuspendThread(&DvdErr_thread); // <-- Original causing deadlock without Alarm - std::this_thread::sleep_for(std::chrono::milliseconds(250)); - + OSSuspendThread(&DvdErr_thread); } while (true); } static void AlarmHandler(OSAlarm*, OSContext*) { - // This handler is no longer called in the PC workaround OSResumeThread(&DvdErr_thread); } \ No newline at end of file diff --git a/src/m_Do/m_Do_MemCard.cpp b/src/m_Do/m_Do_MemCard.cpp index bbb94f2bcd..36de5480e5 100644 --- a/src/m_Do/m_Do_MemCard.cpp +++ b/src/m_Do/m_Do_MemCard.cpp @@ -11,6 +11,7 @@ #include "m_Do/m_Do_Reset.h" #include "os_report.h" #include "dusk/os.h" +#include "dusk/main.h" #if PLATFORM_WII || PLATFORM_SHIELD #include @@ -103,11 +104,21 @@ void mDoMemCd_Ctrl_c::ThdInit() { void mDoMemCd_Ctrl_c::main() { do { OSLockMutex(&mMutex); - while (mCardCommand == COMM_NONE_e) { + while (mCardCommand == COMM_NONE_e +#ifdef TARGET_PC + && !dusk::IsShuttingDown +#endif + ) { OSWaitCond(&mCond, &mMutex); } OSUnlockMutex(&mMutex); +#ifdef TARGET_PC + if (dusk::IsShuttingDown) { + break; + } +#endif + switch (mCardCommand) { #if PLATFORM_GCN || PLATFORM_WII case COMM_RESTORE_e: diff --git a/src/m_Do/m_Do_main.cpp b/src/m_Do/m_Do_main.cpp index b3fb05b0b9..03ff6b8c0f 100644 --- a/src/m_Do/m_Do_main.cpp +++ b/src/m_Do/m_Do_main.cpp @@ -108,12 +108,8 @@ s32 LOAD_COPYDATE(void*) { AuroraInfo auroraInfo; void main01(void) { - #if TARGET_PC - Limiter frameLimiter{}; - #endif - OS_REPORT("\x1b[m"); - GXSetColorUpdate(GX_ENABLE); + // 1. Setup mDoMch_Create(); mDoGph_Create(); @@ -191,10 +187,6 @@ void main01(void) { mDoAud_Execute(); aurora_end_frame(); - - #if TARGET_PC - frameLimiter.Sleep(DUSK_FRAME_PERIOD); - #endif } while (true); exit:; @@ -312,7 +304,8 @@ int game_main(int argc, char* argv[]) { fflush(stdout); fflush(stderr); - dusk::IsShuttingDown = true; + // Notifies all CVs and causes threads to exit + OSResetSystem(OS_RESET_SHUTDOWN, 0, 0); aurora_shutdown(); From f93180bb543818f38326ed43ad1685406059ddb8 Mon Sep 17 00:00:00 2001 From: Irastris Date: Wed, 1 Apr 2026 20:57:49 -0400 Subject: [PATCH 17/19] Reset instead of softlocking when movies are unsupported --- src/d/actor/d_a_demo00.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/d/actor/d_a_demo00.cpp b/src/d/actor/d_a_demo00.cpp index c0e3da3121..99ad6c1587 100644 --- a/src/d/actor/d_a_demo00.cpp +++ b/src/d/actor/d_a_demo00.cpp @@ -1096,8 +1096,14 @@ inline int daDemo00_c::execute() { case 2: { u16 sp0A = sp0E & 0x3FFF; if ((sp0E & 0xC000) == 0) { +#if !MOVIE_SUPPORT + // If movie support isn't available, automatically reset. + // TPHD-esque. Maybe not the best solution, but it works. + dComIfGp_event_reset(); +#else fopAcM_create(fpcNm_MOVIE_PLAYER_e, sp0A, NULL, fopAcM_GetRoomNo(this), NULL, NULL, 0xFF); mDoGph_gInf_c::fadeOut(1.0f); +#endif } else { switch (sp0A) { case 0: From e9b96bdd219ffd48ada3676dbcc0e0217cf463a6 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Wed, 1 Apr 2026 21:01:59 -0600 Subject: [PATCH 18/19] Add JKRThread::Run return value --- libs/JSystem/src/JKernel/JKRThread.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libs/JSystem/src/JKernel/JKRThread.cpp b/libs/JSystem/src/JKernel/JKRThread.cpp index f90b00422c..103e6bbcbc 100644 --- a/libs/JSystem/src/JKernel/JKRThread.cpp +++ b/libs/JSystem/src/JKernel/JKRThread.cpp @@ -327,6 +327,9 @@ void* JKRTask::run() { } msg->field_0x0 = NULL; } +#ifdef TARGET_PC + return NULL; +#endif } int JKRTask::check() { From f04a0d21199f169efdf0a5853b9a66a0eaa84678 Mon Sep 17 00:00:00 2001 From: TakaRikka Date: Thu, 2 Apr 2026 03:11:58 -0700 Subject: [PATCH 19/19] add enhancements menu tab --- extern/aurora | 2 +- files.cmake | 2 + include/d/actor/d_a_alink.h | 7 --- src/d/actor/d_a_alink.cpp | 11 ++--- src/d/actor/d_a_alink_demo.inc | 12 ++++-- src/d/actor/d_a_alink_hvyboots.inc | 6 ++- src/d/d_camera.cpp | 4 +- src/d/d_kankyo.cpp | 2 +- src/dusk/imgui/ImGuiConsole.cpp | 1 + src/dusk/imgui/ImGuiConsole.hpp | 4 +- src/dusk/imgui/ImGuiMenuEnhancements.cpp | 55 ++++++++++++++++++++++++ src/dusk/imgui/ImGuiMenuEnhancements.hpp | 28 ++++++++++++ src/dusk/imgui/ImGuiMenuGame.cpp | 23 ++-------- src/dusk/imgui/ImGuiMenuGame.hpp | 8 +--- src/m_Do/m_Do_graphic.cpp | 2 +- 15 files changed, 116 insertions(+), 51 deletions(-) create mode 100644 src/dusk/imgui/ImGuiMenuEnhancements.cpp create mode 100644 src/dusk/imgui/ImGuiMenuEnhancements.hpp diff --git a/extern/aurora b/extern/aurora index 31ee02fe06..4c56dba0a4 160000 --- a/extern/aurora +++ b/extern/aurora @@ -1 +1 @@ -Subproject commit 31ee02fe0636d56a15fbe147e9e8673b84943a1e +Subproject commit 4c56dba0a401a936c19b3299d3d5545001af5642 diff --git a/files.cmake b/files.cmake index 2339f5cb02..4806722f12 100644 --- a/files.cmake +++ b/files.cmake @@ -1347,6 +1347,8 @@ set(DUSK_FILES src/dusk/imgui/ImGuiMenuGame.hpp src/dusk/imgui/ImGuiMenuTools.cpp src/dusk/imgui/ImGuiMenuTools.hpp + src/dusk/imgui/ImGuiMenuEnhancements.cpp + src/dusk/imgui/ImGuiMenuEnhancements.hpp src/dusk/imgui/ImGuiProcessOverlay.cpp src/dusk/imgui/ImGuiCameraOverlay.cpp src/dusk/imgui/ImGuiHeapOverlay.cpp diff --git a/include/d/actor/d_a_alink.h b/include/d/actor/d_a_alink.h index f2f96487d4..7c70ea54e5 100644 --- a/include/d/actor/d_a_alink.h +++ b/include/d/actor/d_a_alink.h @@ -8425,11 +8425,4 @@ inline daAlink_c* daAlink_getAlinkActorClass() { return (daAlink_c*)dComIfGp_getLinkPlayer(); } -#if TARGET_PC -namespace dusk::tweaks { - extern bool FastIronBoots; - extern bool InvertCameraXAxis; -} -#endif - #endif /* D_A_D_A_ALINK_H */ diff --git a/src/d/actor/d_a_alink.cpp b/src/d/actor/d_a_alink.cpp index fe8e016c7e..2c5bcf46cd 100644 --- a/src/d/actor/d_a_alink.cpp +++ b/src/d/actor/d_a_alink.cpp @@ -54,11 +54,6 @@ #include "res/Object/Alink.h" #include -#if TARGET_PC -bool dusk::tweaks::FastIronBoots = false; -bool dusk::tweaks::InvertCameraXAxis = false; -#endif - static int daAlink_Create(fopAc_ac_c* i_this); static int daAlink_Delete(daAlink_c* i_this); static int daAlink_Execute(daAlink_c* i_this); @@ -7515,7 +7510,7 @@ void daAlink_c::setBlendMoveAnime(f32 i_morf) { BOOL sp24 = checkEventRun(); BOOL sp20 = checkBootsMoveAnime(1); #if TARGET_PC - if (dusk::tweaks::FastIronBoots) { + if (dusk::ImGuiMenuEnhancements::m_enhancements.fastIronBoots) { sp20 = FALSE; } #endif @@ -9480,7 +9475,7 @@ void daAlink_c::setStickData() { mHeavySpeedMultiplier = mpHIO->mItem.mIronBoots.m.mInputFactor; } #if TARGET_PC - if (dusk::tweaks::FastIronBoots) { + if (dusk::ImGuiMenuEnhancements::m_enhancements.fastIronBoots) { mHeavySpeedMultiplier = 1.0f; } #endif @@ -9492,7 +9487,7 @@ void daAlink_c::setStickData() { mHeavySpeedMultiplier = mpHIO->mItem.mIronBoots.m.mWaterInputFactor; } #if TARGET_PC - if (dusk::tweaks::FastIronBoots) { + if (dusk::ImGuiMenuEnhancements::m_enhancements.fastIronBoots) { mHeavySpeedMultiplier = 1.0f; } #endif diff --git a/src/d/actor/d_a_alink_demo.inc b/src/d/actor/d_a_alink_demo.inc index 462c35d4e8..7acccbde13 100644 --- a/src/d/actor/d_a_alink_demo.inc +++ b/src/d/actor/d_a_alink_demo.inc @@ -23,6 +23,8 @@ #include "d/actor/d_a_npc_tkc.h" #include +#include "dusk/imgui/ImGuiMenuEnhancements.hpp" + BOOL daAlink_c::checkEventRun() const { return dComIfGp_event_runCheck() || checkPlayerDemoMode(); } @@ -4290,7 +4292,7 @@ static fopAc_ac_c* daAlink_searchPortal(fopAc_ac_c* i_actor, void* i_data) { } bool daAlink_c::checkAcceptWarp() { - #if VERSION != VERSION_WII_USA_R0 + #if TARGET_PC || VERSION != VERSION_WII_USA_R0 cM3dGPla plane; #endif @@ -4298,7 +4300,9 @@ bool daAlink_c::checkAcceptWarp() { * Fixed in versions above Wii USA Rev 0 by checking FLG0_WATER_IN_MOVE */ if (mLinkAcch.ChkGroundHit() && !checkModeFlg(MODE_PLAYER_FLY) - #if VERSION != VERSION_WII_USA_R0 + #if TARGET_PC + && (dusk::ImGuiMenuEnhancements::m_enhancements.restoreWiiGlitches || !checkNoResetFlg0(FLG0_WATER_IN_MOVE)) + #elif VERSION != VERSION_WII_USA_R0 && !checkNoResetFlg0(FLG0_WATER_IN_MOVE) #endif ) @@ -4307,7 +4311,9 @@ bool daAlink_c::checkAcceptWarp() { * Fixed in versions above Wii USA Rev 0 by checking getSlidePolygon */ if ( - #if VERSION != VERSION_WII_USA_R0 + #if TARGET_PC + (dusk::ImGuiMenuEnhancements::m_enhancements.restoreWiiGlitches || !getSlidePolygon(&plane)) && + #elif VERSION != VERSION_WII_USA_R0 !getSlidePolygon(&plane) && #endif !checkForestOldCentury() diff --git a/src/d/actor/d_a_alink_hvyboots.inc b/src/d/actor/d_a_alink_hvyboots.inc index 0edee19cf2..ed52ffcb36 100644 --- a/src/d/actor/d_a_alink_hvyboots.inc +++ b/src/d/actor/d_a_alink_hvyboots.inc @@ -6,6 +6,8 @@ #include "d/actor/d_a_alink.h" #include "d/actor/d_a_tag_magne.h" +#include "dusk/imgui/ImGuiMenuEnhancements.hpp" + void daAlink_c::concatMagneBootMtx() { if (checkMagneBootsOn()) { mDoMtx_stack_c::concat(mMagneBootMtx); @@ -348,7 +350,9 @@ int daAlink_c::procMagneBootsFly() { * Fixed in GCN and Wii KOR versions by adding a checkEquipHeavyBoots check */ if (dComIfG_Bgsp().ChkPolySafe(mPolyInfo2) - #if PLATFORM_GCN || VERSION == VERSION_WII_KOR + #if TARGET_PC + && (dusk::ImGuiMenuEnhancements::m_enhancements.restoreWiiGlitches || checkEquipHeavyBoots()) + #elif PLATFORM_GCN || VERSION == VERSION_WII_KOR && checkEquipHeavyBoots() #endif ) diff --git a/src/d/d_camera.cpp b/src/d/d_camera.cpp index 6e7e7b8f53..59ed1d2c11 100644 --- a/src/d/d_camera.cpp +++ b/src/d/d_camera.cpp @@ -28,6 +28,8 @@ #include "d/d_debug_camera.h" #endif +#include "dusk/imgui/ImGuiMenuEnhancements.hpp" + namespace { static f32 limitf(f32 value, f32 min, f32 max) { @@ -764,7 +766,7 @@ void dCamera_c::updatePad() { var_f31 = mDoCPd_c::getSubStickX3D(mPadID); #if TARGET_PC - if (dusk::tweaks::InvertCameraXAxis) { + if (dusk::ImGuiMenuEnhancements::m_enhancements.invertCameraXAxis) { var_f31 *= -1.0f; } #endif diff --git a/src/d/d_kankyo.cpp b/src/d/d_kankyo.cpp index e6d076c05f..a61219ffac 100644 --- a/src/d/d_kankyo.cpp +++ b/src/d/d_kankyo.cpp @@ -11381,7 +11381,7 @@ void dKy_bg_MAxx_proc(void* bg_model_p) { C_MTXLightPerspective(sp1D8, dComIfGd_getView()->fovy, camera_p->view.aspect, 1.0f, 1.0f, #if TARGET_PC - dusk::g_imguiConsole.isWaterProjectionOffsetEnabled() ? -0.01f : 0.0f, 0.0f); + dusk::ImGuiMenuEnhancements::m_enhancements.useWaterProjectionOffset ? -0.01f : 0.0f, 0.0f); #else -0.01f, 0.0f); #endif diff --git a/src/dusk/imgui/ImGuiConsole.cpp b/src/dusk/imgui/ImGuiConsole.cpp index 52bbef5f85..917b00b237 100644 --- a/src/dusk/imgui/ImGuiConsole.cpp +++ b/src/dusk/imgui/ImGuiConsole.cpp @@ -181,6 +181,7 @@ namespace dusk { if (ImGui::BeginMainMenuBar()) { m_menuGame.draw(); m_menuTools.draw(); + m_menuEnhancements.draw(); ImGui::SetCursorPosX(ImGui::GetWindowWidth() - 80.0f); ImGuiIO& io = ImGui::GetIO(); diff --git a/src/dusk/imgui/ImGuiConsole.hpp b/src/dusk/imgui/ImGuiConsole.hpp index c67bb2a885..2cb887f904 100644 --- a/src/dusk/imgui/ImGuiConsole.hpp +++ b/src/dusk/imgui/ImGuiConsole.hpp @@ -7,6 +7,7 @@ #include "imgui.h" #include "ImGuiMenuGame.hpp" #include "ImGuiMenuTools.hpp" +#include "ImGuiMenuEnhancements.hpp" namespace dusk { class ImGuiConsole { @@ -14,8 +15,6 @@ namespace dusk { ImGuiConsole(); void draw(); - bool isBloomEnabled() { return m_menuGame.isBloomEnabled(); } - bool isWaterProjectionOffsetEnabled() { return m_menuGame.isWaterProjectionOffsetEnabled(); } ImGuiMenuTools::CollisionViewSettings& getCollisionViewSettings() { return m_menuTools.getCollisionViewSettings(); } static bool CheckMenuViewToggle(ImGuiKey key, bool& active); @@ -25,6 +24,7 @@ namespace dusk { ImGuiMenuGame m_menuGame; ImGuiMenuTools m_menuTools; + ImGuiMenuEnhancements m_menuEnhancements; }; extern ImGuiConsole g_imguiConsole; diff --git a/src/dusk/imgui/ImGuiMenuEnhancements.cpp b/src/dusk/imgui/ImGuiMenuEnhancements.cpp new file mode 100644 index 0000000000..c2fb060f5b --- /dev/null +++ b/src/dusk/imgui/ImGuiMenuEnhancements.cpp @@ -0,0 +1,55 @@ +#include "fmt/format.h" +#include "imgui.h" +#include "aurora/gfx.h" + +#include "ImGuiConsole.hpp" +#include "ImGuiMenuEnhancements.hpp" +#include + +namespace dusk { + EnhancementsSettings ImGuiMenuEnhancements::m_enhancements = { + .fastIronBoots = false, + .invertCameraXAxis = false, + .restoreWiiGlitches = false, + .enableBloom = true, + .useWaterProjectionOffset = false, + }; + + ImGuiMenuEnhancements::ImGuiMenuEnhancements() {} + + void ImGuiMenuEnhancements::draw() { + if (ImGui::BeginMenu("Enhancements")) { + if (ImGui::BeginMenu("Quality of Life")) { + ImGui::Checkbox("Fast Iron Boots", &m_enhancements.fastIronBoots); + ImGui::Checkbox("Invert Camera X Axis", &m_enhancements.invertCameraXAxis); + ImGui::EndMenu(); + } + + if (ImGui::BeginMenu("Graphics")) { + ImGui::Checkbox("Native Bloom", &m_enhancements.enableBloom); + ImGui::Checkbox("Water Projection Offset", &m_enhancements.useWaterProjectionOffset); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Adds GC-specific -0.01 transS offset\n" + "that causes ~6px ghost artifacts in water reflections"); + } + + ImGui::EndMenu(); + } + + if (ImGui::BeginMenu("Restorations")) { + ImGui::Checkbox("Restore Wii 1.0 Glitches", &m_enhancements.restoreWiiGlitches); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Restores patched glitches from Wii USA 1.0, the first released version"); + } + + ImGui::EndMenu(); + } + + if (ImGui::BeginMenu("Cheats")) { + ImGui::EndMenu(); + } + + ImGui::EndMenu(); + } + } +} diff --git a/src/dusk/imgui/ImGuiMenuEnhancements.hpp b/src/dusk/imgui/ImGuiMenuEnhancements.hpp new file mode 100644 index 0000000000..16ef0a1983 --- /dev/null +++ b/src/dusk/imgui/ImGuiMenuEnhancements.hpp @@ -0,0 +1,28 @@ +#ifndef DUSK_IMGUI_MENUENHANCEMENTS_HPP +#define DUSK_IMGUI_MENUENHANCEMENTS_HPP + +#include +#include +#include + +#include "imgui.h" + +namespace dusk { + struct EnhancementsSettings { + bool fastIronBoots; + bool invertCameraXAxis; + bool restoreWiiGlitches; + bool enableBloom; + bool useWaterProjectionOffset; + }; + + class ImGuiMenuEnhancements { + public: + ImGuiMenuEnhancements(); + void draw(); + + static EnhancementsSettings m_enhancements; + }; +} + +#endif // DUSK_IMGUI_MENUENHANCEMENTS_HPP diff --git a/src/dusk/imgui/ImGuiMenuGame.cpp b/src/dusk/imgui/ImGuiMenuGame.cpp index e8f260ba4c..00b287cc41 100644 --- a/src/dusk/imgui/ImGuiMenuGame.cpp +++ b/src/dusk/imgui/ImGuiMenuGame.cpp @@ -25,17 +25,8 @@ namespace dusk { if (ImGui::BeginMenu("Graphics")) { if (ImGui::MenuItem("Toggle Fullscreen", "F11")) { - m_graphicsSettings.m_fullscreen = !m_graphicsSettings.m_fullscreen; - VISetWindowFullscreen(m_graphicsSettings.m_fullscreen); - } - - ImGui::Separator(); - - ImGui::Checkbox("Native Bloom", &m_graphicsSettings.m_enableBloom); - ImGui::Checkbox("Water Projection Offset", &m_graphicsSettings.m_waterProjectionOffset); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Adds GC-specific -0.01 transS offset\n" - "that causes ~6px ghost artifacts in water reflections"); + m_fullscreen = !m_fullscreen; + VISetWindowFullscreen(m_fullscreen); } ImGui::EndMenu(); @@ -76,12 +67,6 @@ namespace dusk { ImGui::EndMenu(); } - if (ImGui::BeginMenu("Tweaks")) { - ImGui::MenuItem("Fast iron boots", nullptr, &tweaks::FastIronBoots); - ImGui::MenuItem("Invert Camera X Axis", nullptr, &tweaks::InvertCameraXAxis); - ImGui::EndMenu(); - } - ImGui::EndMenu(); } @@ -93,8 +78,8 @@ namespace dusk { } if (ImGui::IsKeyPressed(ImGuiKey_F11)) { - m_graphicsSettings.m_fullscreen = !m_graphicsSettings.m_fullscreen; - VISetWindowFullscreen(m_graphicsSettings.m_fullscreen); + m_fullscreen = !m_fullscreen; + VISetWindowFullscreen(m_fullscreen); } } diff --git a/src/dusk/imgui/ImGuiMenuGame.hpp b/src/dusk/imgui/ImGuiMenuGame.hpp index ad6a7140ec..7df0a00973 100644 --- a/src/dusk/imgui/ImGuiMenuGame.hpp +++ b/src/dusk/imgui/ImGuiMenuGame.hpp @@ -12,8 +12,6 @@ namespace dusk { public: ImGuiMenuGame(); void draw(); - bool isBloomEnabled() { return m_graphicsSettings.m_enableBloom; } - bool isWaterProjectionOffsetEnabled() { return m_graphicsSettings.m_waterProjectionOffset; } void windowInputViewer(); void windowControllerConfig(); @@ -35,11 +33,7 @@ namespace dusk { int m_pendingPort = -1; } m_controllerConfig; - struct { - bool m_enableBloom = 1; - bool m_waterProjectionOffset = false; - bool m_fullscreen = false; - } m_graphicsSettings; + bool m_fullscreen = false; bool m_showControllerConfig = false; diff --git a/src/m_Do/m_Do_graphic.cpp b/src/m_Do/m_Do_graphic.cpp index 5c321f207f..8afbddcd2c 100644 --- a/src/m_Do/m_Do_graphic.cpp +++ b/src/m_Do/m_Do_graphic.cpp @@ -1166,7 +1166,7 @@ void mDoGph_gInf_c::bloom_c::remove() { void mDoGph_gInf_c::bloom_c::draw() { #if TARGET_PC - if (!dusk::g_imguiConsole.isBloomEnabled()) { + if (!dusk::ImGuiMenuEnhancements::m_enhancements.enableBloom) { return; } #endif