diff --git a/CMakeLists.txt b/CMakeLists.txt index 7d434d874b..3992507347 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,6 +19,14 @@ 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) + +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 () if (CMAKE_SYSTEM_NAME STREQUAL Linux) # -Wno-multichar: Multi-character constants ('ABCD') are implementation-defined but all compilers @@ -108,6 +116,10 @@ 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 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_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>") diff --git a/extern/aurora b/extern/aurora index 6b9f614d32..4c56dba0a4 160000 --- a/extern/aurora +++ b/extern/aurora @@ -1 +1 @@ -Subproject commit 6b9f614d326aaa4a4dead870ffd22393d2a4ef53 +Subproject commit 4c56dba0a401a936c19b3299d3d5545001af5642 diff --git a/files.cmake b/files.cmake index aeb068b583..6c04d9a22e 100644 --- a/files.cmake +++ b/files.cmake @@ -1335,6 +1335,7 @@ set(DUSK_FILES src/d/actor/d_a_alink_quicktransform.cpp src/dusk/asserts.cpp src/dusk/logging.cpp + src/dusk/layout.cpp src/dusk/stubs.cpp src/dusk/endian.cpp src/dusk/extras.c @@ -1347,6 +1348,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 93668c550a..f74aef4f0a 100644 --- a/include/d/actor/d_a_alink.h +++ b/include/d/actor/d_a_alink.h @@ -8429,11 +8429,4 @@ inline daAlink_c* daAlink_getAlinkActorClass() { return (daAlink_c*)dComIfGp_getLinkPlayer(); } -#if TARGET_PC -namespace dusk::tweaks { - extern bool FastIronBoots; - extern bool QuickTransform; -} -#endif - #endif /* D_A_D_A_ALINK_H */ diff --git a/include/d/actor/d_a_movie_player.h b/include/d/actor/d_a_movie_player.h index e064d558dd..0ddc675c24 100644 --- a/include/d/actor/d_a_movie_player.h +++ b/include/d/actor/d_a_movie_player.h @@ -1,7 +1,11 @@ #ifndef D_A_MOVIE_PLAYER_H #define D_A_MOVIE_PLAYER_H +#if !TARGET_PC #include +#else +#include +#endif #include "f_op/f_op_actor.h" #include "d/d_drawlist.h" @@ -11,6 +15,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; + +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 +#define THP_TEXTURE_SET_COUNT 3 +#endif + struct daMP_THPPlayer { /* 0x000 */ DVDFileInfo fileInfo; /* 0x03C */ THPHeader header; @@ -34,7 +117,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/include/dusk/map_loader_definitions.h b/include/dusk/map_loader_definitions.h index 31dfef7157..a0f7150d2d 100644 --- a/include/dusk/map_loader_definitions.h +++ b/include/dusk/map_loader_definitions.h @@ -617,5 +617,8 @@ static const 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/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/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/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..103e6bbcbc 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(); @@ -319,6 +327,9 @@ void* JKRTask::run() { } msg->field_0x0 = NULL; } +#ifdef TARGET_PC + return NULL; +#endif } int JKRTask::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/actor/d_a_alink.cpp b/src/d/actor/d_a_alink.cpp index 5e2530bdf3..2c5bcf46cd 100644 --- a/src/d/actor/d_a_alink.cpp +++ b/src/d/actor/d_a_alink.cpp @@ -54,10 +54,6 @@ #include "res/Object/Alink.h" #include -#if TARGET_PC -bool dusk::tweaks::FastIronBoots = 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); @@ -7514,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 @@ -9479,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 @@ -9491,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/actor/d_a_alink_quicktransform.cpp b/src/d/actor/d_a_alink_quicktransform.cpp index f193651bae..47501d4f3d 100644 --- a/src/d/actor/d_a_alink_quicktransform.cpp +++ b/src/d/actor/d_a_alink_quicktransform.cpp @@ -3,11 +3,10 @@ #include "d/d_meter2.h" #include "d/d_meter2_draw.h" #include "d/d_meter2_info.h" - -bool dusk::tweaks::QuickTransform = false; +#include "dusk/imgui/ImGuiMenuEnhancements.hpp" void daAlink_c::handleQuickTransform() { - if (!dusk::tweaks::QuickTransform) { + if (!dusk::ImGuiMenuEnhancements::m_enhancements.quickTransform) { return; } 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: diff --git a/src/d/actor/d_a_movie_player.cpp b/src/d/actor/d_a_movie_player.cpp index 0f9f68fb50..4573684b2f 100644 --- a/src/d/actor/d_a_movie_player.cpp +++ b/src/d/actor/d_a_movie_player.cpp @@ -14,21 +14,41 @@ #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/os.h" +#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 *(s32*)readBuf->ptr; + return *(BE(s32)*)readBuf->ptr; } -#ifdef __cplusplus +#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 @@ -196,6 +216,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 +2583,109 @@ 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; + +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++) { + 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++) { + 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]; + FixedJpegData.push_back(value); + if (value == 0xFF) { + FixedJpegData.push_back(0x00); + } + } + + // Copy data after SOS. + for (size_t i = endOfImage; i < data.size(); i++) { + FixedJpegData.push_back(data[i]); + } + + return FixedJpegData; +} + +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()); + 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; +} +#else // MOVIE_SUPPORT +static s32 THPVideoDecode(void*, size_t, void*, void*, void*, void*) { + return 1; // Immediate error. +} +#endif +#endif static BOOL THPInit() { +#if !TARGET_PC u8* base; base = (u8*)(0xE000 << 16); @@ -2585,15 +2707,21 @@ static BOOL THPInit() { OSInitFastCast(); __THPInitFlag = TRUE; +#endif return TRUE; } -#ifdef __cplusplus +#if defined(__cplusplus) && !TARGET_PC } #endif +#if !TARGET_PC // Defined earlier in file. static daMP_THPPlayer daMP_ActivePlayer; +#endif +#if TARGET_PC +static BOOL ReadThreadCancelled; +#endif static BOOL daMP_ReadThreadCreated; static OSMessageQueue daMP_FreeReadBufferQueue; @@ -2648,12 +2776,23 @@ 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; } } void* daMP_Reader(void*) { +#if TARGET_PC + OSSetCurrentThreadName("movie player reader"); +#endif + daMP_THPReadBuffer* buf; s32 curFrame; s32 status; @@ -2664,8 +2803,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) @@ -2673,7 +2821,11 @@ void* daMP_Reader(void*) { if (frame == 0) daMP_PrepareReady(FALSE); +#if TARGET_PC + return nullptr; +#else OSSuspendThread(&daMP_ReadThread); +#endif } buf->frameNumber = frame; @@ -2686,20 +2838,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; } @@ -2751,19 +2918,30 @@ 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(); +#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: { 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,12 +2967,24 @@ static void daMP_VideoDecode(daMP_THPReadBuffer* readBuffer) { } static void* daMP_VideoDecoder(void* param_0) { - daMP_THPReadBuffer* thpBuffer; +#if TARGET_PC + 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); @@ -2814,12 +3004,26 @@ 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("movie video decoder"); +#endif + daMP_THPReadBuffer readBuffer; s32 readSize; s32 frame; @@ -2876,13 +3080,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; } @@ -2903,12 +3111,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; @@ -2944,12 +3164,17 @@ 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(); +#if TARGET_PC + if (!audioBuf) { + return; + } +#endif for (i = 0; i < daMP_ActivePlayer.compInfo.numComponents; i++) { switch (daMP_ActivePlayer.compInfo.frameComp[i]) { @@ -2969,16 +3194,34 @@ 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; +#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) { +#if TARGET_PC + OSSetCurrentThreadName("movie audio decoder"); +#endif + s32 size; s32 readSize; daMP_THPReadBuffer readBuffer; @@ -2989,7 +3232,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); @@ -2999,7 +3246,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; @@ -3008,6 +3259,7 @@ static void* daMP_AudioDecoderForOnMemory(void* param_0) { } frame++; } + return nullptr; } static OSMessage daMP_FreeAudioBufferMessage[3]; @@ -3015,13 +3267,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; } @@ -3042,7 +3297,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; } } @@ -3077,8 +3342,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 @@ -3168,19 +3438,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 +3468,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[] = { @@ -3495,6 +3778,13 @@ static BOOL daMP_THPPlayerOpen(char const* filename, BOOL onMemory) { } static BOOL daMP_THPPlayerClose() { +#if TARGET_PC && MOVIE_SUPPORT + tj3Destroy(JpegDecompressHandle); + JpegDecompressHandle = nullptr; + + FixedJpegData.clear(); +#endif + if (daMP_ActivePlayer.open && daMP_ActivePlayer.state == 0) { daMP_ActivePlayer.open = 0; DVDClose(&daMP_ActivePlayer.fileInfo); @@ -3546,6 +3836,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; @@ -3623,6 +3918,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 { @@ -3951,6 +4252,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; @@ -3994,11 +4298,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()); @@ -4015,6 +4322,15 @@ static BOOL daMP_ActivePlayer_Init(char const* moviePath) { daMP_THPPlayerSetBuffer((u8*)daMP_buffer); +#if TARGET_PC && MOVIE_SUPPORT + 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 @@ -4050,14 +4366,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/d/d_camera.cpp b/src/d/d_camera.cpp index a5efef9bfa..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) { @@ -762,6 +764,13 @@ void dCamera_c::updatePad() { var_f29 = 0.0f; } else { var_f31 = mDoCPd_c::getSubStickX3D(mPadID); + + #if TARGET_PC + if (dusk::ImGuiMenuEnhancements::m_enhancements.invertCameraXAxis) { + var_f31 *= -1.0f; + } + #endif + var_f30 = mDoCPd_c::getSubStickY(mPadID); var_f29 = mDoCPd_c::getSubStickValue(mPadID); } 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/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; 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/OSThread.cpp b/src/dusk/OSThread.cpp index 7774dc6a5c..ea9c568eef 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 @@ -38,6 +39,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) @@ -50,6 +58,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; @@ -85,8 +103,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() { @@ -108,36 +124,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 @@ -195,8 +181,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"); @@ -273,7 +257,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", @@ -353,16 +336,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 @@ -377,7 +351,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 @@ -400,16 +373,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); @@ -497,7 +461,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; @@ -509,10 +472,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; @@ -523,11 +486,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); @@ -536,17 +499,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; } diff --git a/src/dusk/audio/DuskAudioSystem.cpp b/src/dusk/audio/DuskAudioSystem.cpp index 0ed2101dcf..93e161512f 100644 --- a/src/dusk/audio/DuskAudioSystem.cpp +++ b/src/dusk/audio/DuskAudioSystem.cpp @@ -133,6 +133,20 @@ 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. + // 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++) { + 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/imgui/ImGuiConsole.cpp b/src/dusk/imgui/ImGuiConsole.cpp index 60ba229ead..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(); @@ -223,104 +224,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/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..9103c51fbd --- /dev/null +++ b/src/dusk/imgui/ImGuiMenuEnhancements.cpp @@ -0,0 +1,56 @@ +#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::Checkbox("Quick Transform (R+Y)", &m_enhancements.quickTransform); + 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..7b4775789b --- /dev/null +++ b/src/dusk/imgui/ImGuiMenuEnhancements.hpp @@ -0,0 +1,29 @@ +#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 quickTransform; + 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 835fed0f7c..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("Quick Transform (R+Y)", nullptr, &tweaks::QuickTransform); - 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/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}; +} 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_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 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();