From 06dc5112124e1d99a748761a93038cd33d586bf9 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Wed, 11 Mar 2026 21:50:46 +0100 Subject: [PATCH 001/350] JAudio field name/comment pass HUGE thanks to XAYRGA for their work documenting JAudio file formats --- .../include/JSystem/JAudio2/JASAramStream.h | 42 +++--- .../include/JSystem/JAudio2/JASBNKParser.h | 20 +-- .../include/JSystem/JAudio2/JASChannel.h | 15 +- .../include/JSystem/JAudio2/JASDSPChannel.h | 28 +++- .../include/JSystem/JAudio2/JASDSPInterface.h | 114 +++++++++----- .../include/JSystem/JAudio2/JASDriverIF.h | 4 + .../include/JSystem/JAudio2/JASOscillator.h | 27 +++- .../include/JSystem/JAudio2/JASWSParser.h | 35 +++-- .../include/JSystem/JAudio2/JASWaveInfo.h | 29 ++-- .../include/JSystem/JAudio2/JAUInitializer.h | 10 +- .../JSystem/include/JSystem/JAudio2/dspproc.h | 2 +- .../JSystem/include/JSystem/JAudio2/dsptask.h | 2 +- libs/JSystem/src/JAudio2/JASAramStream.cpp | 142 +++++++++--------- libs/JSystem/src/JAudio2/JASAudioReseter.cpp | 2 +- libs/JSystem/src/JAudio2/JASBNKParser.cpp | 16 +- libs/JSystem/src/JAudio2/JASBank.cpp | 16 +- libs/JSystem/src/JAudio2/JASBasicWaveBank.cpp | 10 +- libs/JSystem/src/JAudio2/JASChannel.cpp | 52 +++---- libs/JSystem/src/JAudio2/JASDSPChannel.cpp | 42 +++--- libs/JSystem/src/JAudio2/JASDSPInterface.cpp | 120 ++++++++------- libs/JSystem/src/JAudio2/JASDriverIF.cpp | 6 +- libs/JSystem/src/JAudio2/JASDrumSet.cpp | 4 +- libs/JSystem/src/JAudio2/JASOscillator.cpp | 52 +++---- .../JSystem/src/JAudio2/JASSimpleWaveBank.cpp | 2 +- libs/JSystem/src/JAudio2/JASTrack.cpp | 10 +- libs/JSystem/src/JAudio2/JASWSParser.cpp | 44 +++--- libs/JSystem/src/JAudio2/JAUInitializer.cpp | 30 ++-- libs/JSystem/src/JAudio2/JAUSectionHeap.cpp | 6 +- libs/JSystem/src/JAudio2/dspproc.cpp | 6 +- libs/JSystem/src/JAudio2/dsptask.cpp | 4 +- libs/JSystem/src/JAudio2/osdsp_task.cpp | 18 +-- src/Z2AudioLib/Z2AudioMgr.cpp | 10 +- src/d/actor/d_a_movie_player.cpp | 2 +- src/d/d_menu_option.cpp | 14 +- 34 files changed, 518 insertions(+), 418 deletions(-) diff --git a/libs/JSystem/include/JSystem/JAudio2/JASAramStream.h b/libs/JSystem/include/JSystem/JAudio2/JASAramStream.h index 17d4c6f521..119145de9a 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASAramStream.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASAramStream.h @@ -17,6 +17,8 @@ namespace JASDsp { */ class JASAramStream { public: + static const int CHANNEL_MAX = 6; + typedef void (*StreamCallback)(u32, JASAramStream*, void*); enum CallbackType { @@ -32,29 +34,29 @@ public: }; struct Header { - /* 0x00 */ u32 tag; + /* 0x00 */ u32 tag; // 'STRM' /* 0x04 */ u8 field_0x4[5]; /* 0x09 */ u8 format; /* 0x0A */ u8 bits; /* 0x0C */ u16 channels; /* 0x0E */ u16 loop; - /* 0x10 */ int field_0x10; - /* 0x14 */ u8 field_0x14[4]; + /* 0x10 */ int mSampleRate; + /* 0x14 */ u32 mSampleCount; // unused /* 0x18 */ int loop_start; /* 0x1C */ int loop_end; /* 0x20 */ u32 block_size; - /* 0x24 */ u8 field_0x24[4]; - /* 0x28 */ u8 field_0x28; - /* 0x29 */ u8 field_0x29[0x17]; + /* 0x24 */ u8 _unused2[4]; + /* 0x28 */ u8 mVolume; + /* 0x29 */ u8 _unused3[0x17]; }; // Size: 0x40 struct BlockHeader { - /* 0x00 */ u32 tag; - /* 0x04 */ u32 field_0x4; + /* 0x00 */ u32 tag; // 'BLCK' + /* 0x04 */ u32 mSize; /* 0x08 */ struct { - s16 field_0x0; - s16 field_0x2; - } field_0x8[6]; + s16 mpLast; + s16 mpPenult; + } mAdpcmContinuationData[CHANNEL_MAX]; }; // Size: 0x20 static void initSystem(u32, u32); @@ -128,14 +130,12 @@ public: static u32 getBlockSize() { return sBlockSize; } - static const int CHANNEL_MAX = 6; - /* 0x000 */ OSMessageQueue field_0x000; /* 0x020 */ OSMessageQueue field_0x020; /* 0x040 */ void* field_0x040[16]; /* 0x080 */ void* field_0x080[4]; /* 0x090 */ JASChannel* mChannels[CHANNEL_MAX]; - /* 0x0A8 */ JASChannel* field_0x0a8; + /* 0x0A8 */ JASChannel* mInitialChannel; /* 0x0AC */ bool field_0x0ac; /* 0x0AD */ bool field_0x0ad; /* 0x0AE */ u8 field_0x0ae; @@ -157,17 +157,17 @@ public: /* 0x124 */ int field_0x124; /* 0x128 */ u16 field_0x128; /* 0x12C */ int field_0x12c; - /* 0x130 */ s16 field_0x130[CHANNEL_MAX]; - /* 0x13C */ s16 field_0x13c[CHANNEL_MAX]; - /* 0x148 */ int field_0x148; + /* 0x130 */ s16 mpLasts[CHANNEL_MAX]; + /* 0x13C */ s16 mpPenults[CHANNEL_MAX]; + /* 0x148 */ int mAramAddress; /* 0x14C */ u32 field_0x14c; /* 0x150 */ StreamCallback mCallback; /* 0x154 */ void* mCallbackData; - /* 0x158 */ u16 field_0x158; + /* 0x158 */ u16 mFormat; /* 0x15A */ u16 mChannelNum; /* 0x15C */ u32 mBufCount; - /* 0x160 */ u32 field_0x160; - /* 0x164 */ u32 field_0x164; + /* 0x160 */ u32 mBlocksPerChannel; + /* 0x164 */ u32 mSampleRate; /* 0x168 */ bool mLoop; /* 0x16C */ u32 mLoopStart; /* 0x170 */ u32 mLoopEnd; @@ -177,7 +177,7 @@ public: /* 0x194 */ f32 mChannelPan[CHANNEL_MAX]; /* 0x1AC */ f32 mChannelFxMix[CHANNEL_MAX]; /* 0x1C4 */ f32 mChannelDolby[CHANNEL_MAX]; - /* 0x1DC */ u16 field_0x1dc[CHANNEL_MAX]; + /* 0x1DC */ u16 mMixConfig[CHANNEL_MAX]; static JASTaskThread* sLoadThread; static u8* sReadBuffer; diff --git a/libs/JSystem/include/JSystem/JAudio2/JASBNKParser.h b/libs/JSystem/include/JSystem/JAudio2/JASBNKParser.h index f7a4d0585b..7d466734c0 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASBNKParser.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASBNKParser.h @@ -10,21 +10,21 @@ class JKRHeap; namespace JASBNKParser { struct TFileHeader { - /* 0x0 */ BE(int) id; + /* 0x0 */ BE(int) mMagic; /* 0x4 */ BE(u32) mSize; - /* 0x8 */ u8 _08[4]; + /* 0x8 */ BE(u32) mGlobalId; /* 0xC */ BE(u32) mVersion; }; namespace Ver1 { struct TOsc { - /* 0x00 */ BE(u32) id; + /* 0x00 */ BE(u32) id; // "Osci" /* 0x04 */ u8 mTarget; - /* 0x08 */ BE(f32) _08; - /* 0x0C */ BE(u32) mTableOffset; - /* 0x10 */ BE(u32) _10; - /* 0x14 */ BE(f32) mScale; - /* 0x18 */ BE(f32) _18; + /* 0x08 */ BE(f32) mRate; + /* 0x0C */ BE(u32) mAttackEnvelopeOffset; + /* 0x10 */ BE(u32) mReleaseEnvelopeOffset; + /* 0x14 */ BE(f32) mScale; // width + /* 0x18 */ BE(f32) mVertex; }; struct TPercData { @@ -36,7 +36,7 @@ namespace JASBNKParser { }; struct TChunk { - /* 0x0 */ BE(u32) mID; + /* 0x0 */ BE(u32) mID; // Magic /* 0x4 */ BE(u32) mSize; }; @@ -113,7 +113,7 @@ namespace JASBNKParser { }; struct TOffsetData { - /* 0x000 */ u8 field_0x20[4]; + /* 0x000 */ BE(u32) mMagic; // 'BANK' /* 0x004 */ TOffset mInstOffset[0x80]; /* 0x204 */ u8 field_0x204[0x190]; /* 0x394 */ TOffset mPercOffset[12]; diff --git a/libs/JSystem/include/JSystem/JAudio2/JASChannel.h b/libs/JSystem/include/JSystem/JAudio2/JASChannel.h index 042e3343cd..53bf064d16 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASChannel.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASChannel.h @@ -45,6 +45,9 @@ public: /* 0x14 */ f32 mDolby; }; +#define CHANNEL_WAVE 0 +#define CHANNEL_OSCILLATOR 2 + /** * @ingroup jsystem-jaudio * @@ -52,7 +55,7 @@ public: class JASChannel : public JASPoolAllocObject_MultiThreaded { public: typedef void (*Callback)(u32, JASChannel*, JASDsp::TChannel*, void*); - static const int BUSOUT_CPUCH = 6; + static const int BUSOUT_CPUCH = DSP_OUTPUT_CHANNELS; static const int OSC_NUM = 2; enum CallbackType { @@ -153,10 +156,14 @@ public: /* 0xD4 */ u32 mKeySweepCount; /* 0xD8 */ u32 mSkipSamples; struct { - u32 field_0x0; - JASWaveInfo field_0x4; + u32 mChannelType; // CHANNEL_WAVE or CHANNEL_OSCILLATOR + JASWaveInfo mWaveInfo; } field_0xdc; - intptr_t field_0x104; + union { + u32 mWaveAramAddress; + u32 mOscillatorSomething; + u32 field_0x104; + }; static OSMessageQueue sBankDisposeMsgQ; static OSMessage sBankDisposeMsg[16]; diff --git a/libs/JSystem/include/JSystem/JAudio2/JASDSPChannel.h b/libs/JSystem/include/JSystem/JAudio2/JASDSPChannel.h index f843562f37..8204ab0838 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASDSPChannel.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASDSPChannel.h @@ -5,9 +5,14 @@ /** * @ingroup jsystem-jaudio - * + * Responsible for allocating and prioritizing the available hardware DSP channels. */ struct JASDSPChannel { + /** + * Callback used to update DSP channel information on a regular basis. + * Parameters are a CallbackType, hardware channel, and specified user data. + * Return -1 to immediately abort playback. + */ typedef s32 (*Callback)(u32, JASDsp::TChannel*, void*); enum Status { @@ -17,9 +22,24 @@ struct JASDSPChannel { }; enum CallbackType { + /** + * Fired on a regular basis during play to update parameters. + */ /* 0 */ CB_PLAY, + + /** + * Fired once, when the channel starts playing. + */ /* 1 */ CB_START, + + /** + * Fired once, when the channel naturally finishes playing. + */ /* 2 */ CB_STOP, + + /** + * Fired once, if the channel is abruptly stopped due to an error or prioritization. + */ /* 3 */ CB_DROP, }; @@ -46,9 +66,13 @@ struct JASDSPChannel { static JASDSPChannel* sDspChannels; /* 0x00 */ s32 mStatus; + + /** + * Priority of this DSP channel. Used to + */ /* 0x04 */ s16 mPriority; /* 0x08 */ u32 mFlags; - /* 0x0C */ u32 field_0xc; + /* 0x0C */ u32 mUpdateCounter; /* 0x10 */ Callback mCallback; /* 0x14 */ void* mCallbackData; /* 0x18 */ JASDsp::TChannel* mChannel; diff --git a/libs/JSystem/include/JSystem/JAudio2/JASDSPInterface.h b/libs/JSystem/include/JSystem/JAudio2/JASDSPInterface.h index a653431bee..ec2d9f6c89 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASDSPInterface.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASDSPInterface.h @@ -3,6 +3,16 @@ #include +/** + * Amount of separate audio channels (i.e. individual playbacks, voices) the DSP can mix at once. + */ +#define DSP_CHANNELS 64 + +/** + * Amount of audio channels the DSP can calculate outputs for. + */ +#define DSP_OUTPUT_CHANNELS 6 // Presumed 5.1 surround + struct JASWaveInfo; namespace JASDsp { @@ -29,6 +39,24 @@ namespace JASDsp { u16 field_0x10[8]; } FxBuf; + struct OutputChannelConfig { + u16 mBusConnect; + u16 mTargetVolume; + u16 mCurrentVolume; + + /** + * Gets upper 8 bits cleared when audio volume is changed mid-playback. + * Presumed to be some kind of progress used by the DSP to calculate position between + * mTargetVolume and mCurrentVolume. + */ + u16 mVolumeProgress; + }; + + /** + * DSP memory for each playback channel ("voice"). + * The DSP can read and write this memory. It is used as configuration, feedback, + * and working memory. + */ struct TChannel { void init(); void playStart(); @@ -36,67 +64,83 @@ namespace JASDsp { void replyFinishRequest(); void forceStop(); bool isActive() const; + + /** + * Check whether the DSP has finished playing this channel. + */ bool isFinish() const; void setWaveInfo(JASWaveInfo const&, u32, u32); void setOscInfo(u32); void initAutoMixer(); void setAutoMixer(u16, u8, u8, u8, u8); void setPitch(u16); - void setMixerInitVolume(u8, s16); - void setMixerVolume(u8, s16); + void setMixerInitVolume(u8 outputChannel, s16 volume); + void setMixerVolume(u8 outputChannel, s16 volume); void setPauseFlag(u8); + + /** + * Flushes backing memory of channel out of the data cache. + */ void flush(); void initFilter(); void setFilterMode(u16); void setIIRFilterParam(s16*); void setFIR8FilterParam(s16*); void setDistFilter(s16); - void setBusConnect(u8, u8); + void setBusConnect(u8 outputChannel, u8 param_1); /* 0x000 */ u16 mIsActive; /* 0x002 */ u16 mIsFinished; /* 0x004 */ u16 mPitch; - /* 0x006 */ short field_0x006; + /* 0x006 */ short _unused1; /* 0x008 */ u16 field_0x008; - /* 0x00A */ u8 field_0x00A[0x00C - 0x00A]; + /* 0x00A */ u8 _unused2[0x00C - 0x00A]; /* 0x00C */ s16 mPauseFlag; - /* 0x00E */ short field_0x00E; - /* 0x010 */ u16 field_0x010[1][4]; // array size unknown - /* 0x018 */ u8 field_0x018[0x050 - 0x018]; - /* 0x050 */ u16 field_0x050; - /* 0x052 */ u16 field_0x052; - /* 0x054 */ u16 field_0x054; - /* 0x056 */ u16 field_0x056; - /* 0x058 */ u16 field_0x058; - /* 0x05A */ u8 field_0x05A[0x060 - 0x05A]; - /* 0x060 */ short field_0x060; - /* 0x062 */ u8 field_0x062[0x064 - 0x062]; - /* 0x064 */ u16 field_0x064; - /* 0x066 */ short field_0x066; - /* 0x068 */ int field_0x068; - /* 0x06C */ u8 field_0x06C[0x070 - 0x06C]; - /* 0x070 */ int field_0x070; + /* 0x00E */ short _unused3; + /* 0x010 */ OutputChannelConfig mOutputChannels[DSP_OUTPUT_CHANNELS]; + /* 0x040 */ u8 _unused4[0x050 - 0x040]; + /* 0x050 */ u16 mAutoMixerPanDolby; // pan is upper 8 bits, dolby lower 8. + /* 0x052 */ u16 mAutoMixerFxMix; + /* 0x054 */ u16 mAutoMixerInitVolume; + /* 0x056 */ u16 mAutoMixerVolume; + /* 0x058 */ u16 mAutoMixerBeenSet; + /* 0x05A */ u8 _unused5[0x060 - 0x05A]; + /* 0x060 */ short field_0x060; // Only cleared to zero, presumed used by DSP. + /* 0x062 */ u8 _unused6[0x064 - 0x062]; + /* 0x064 */ u16 mSamplesPerBlock; + /* 0x066 */ short field_0x066; // Only cleared to zero, presumed used by DSP. + /* 0x068 */ int mSamplePosition; // Only ever initialized by code, name is guess. + /* 0x06C */ u8 _unused7[0x070 - 0x06C]; + /* 0x070 */ int mAramStreamPosition; // Seems written by DSP, used for audio streaming. /* 0x074 */ int field_0x074; - /* 0x078 */ short field_0x078[4]; - /* 0x080 */ short field_0x080[20]; - /* 0x0A8 */ short field_0x0a8[4]; - /* 0x0B0 */ u16 field_0x0b0[16]; - /* 0x0D0 */ u8 field_0x0D0[0x100 - 0x0D0]; - /* 0x100 */ u16 field_0x100; + /* 0x078 */ short field_0x078[4]; // Only cleared to zero, presumed used by DSP. + /* 0x080 */ short field_0x080[20]; // Only cleared to zero, presumed used by DSP. + /* 0x0A8 */ short field_0x0a8[4]; // Only cleared to zero, presumed used by DSP. + /* 0x0B0 */ u16 field_0x0b0[16]; // Only cleared to zero, presumed used by DSP. + /* 0x0D0 */ u8 _unused8[0x100 - 0x0D0]; + /* 0x100 */ u16 mBytesPerBlock; /* 0x102 */ u16 field_0x102; - /* 0x104 */ s16 field_0x104; - /* 0x106 */ s16 field_0x106; + + /** + * Used for decoding ADPCM data around loop edges. + */ + /* 0x104 */ s16 mpLast; + + /** + * Used for decoding ADPCM data around loop edges. + */ + /* 0x106 */ s16 mpPenult; /* 0x108 */ u16 mFilterMode; /* 0x10A */ u16 mForcedStop; /* 0x10C */ int field_0x10c; - /* 0x110 */ u32 field_0x110; - /* 0x114 */ u32 field_0x114; - /* 0x118 */ u32 field_0x118; - /* 0x11C */ int field_0x11c; + /* 0x110 */ u32 mLoopStartSample; + /* 0x114 */ u32 mEndSample; + /* 0x118 */ u32 mWaveAramAddress; + /* 0x11C */ int mSampleCount; /* 0x120 */ s16 fir_filter_params[8]; - /* 0x130 */ u8 field_0x130[0x148 - 0x130]; + /* 0x130 */ u8 _unused9[0x148 - 0x130]; /* 0x148 */ s16 iir_filter_params[8]; - /* 0x158 */ u8 field_0x158[0x180 - 0x158]; + /* 0x158 */ u8 _unused10[0x180 - 0x158]; }; void boot(void (*)(void*)); diff --git a/libs/JSystem/include/JSystem/JAudio2/JASDriverIF.h b/libs/JSystem/include/JSystem/JAudio2/JASDriverIF.h index 319c826345..5cddc38596 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASDriverIF.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASDriverIF.h @@ -3,6 +3,10 @@ #include "JSystem/JAudio2/JASCallback.h" +#define JAS_OUTPUT_MONO OS_SOUND_MODE_MONO +#define JAS_OUTPUT_STEREO OS_SOUND_MODE_STEREO +#define JAS_OUTPUT_SURROUND 2 + typedef s32 (*DriverCallback)(void*); namespace JASDriver { diff --git a/libs/JSystem/include/JSystem/JAudio2/JASOscillator.h b/libs/JSystem/include/JSystem/JAudio2/JASOscillator.h index 746d462e12..e811e890ab 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASOscillator.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASOscillator.h @@ -9,10 +9,21 @@ * */ struct JASOscillator { + enum EnvelopeMode { + /* 0x0 */ ENVELOPE_LINEAR, + /* 0x1 */ ENVELOPE_SQUARE, + /* 0x2 */ ENVELOPE_SQUARE_ROOT, + /* 0x3 */ ENVELOPE_SAMPLE_CELL, + + ENVELOPE_LOOP = 0xD, + ENVELOPE_HOLD = 0xE, + ENVELOPE_STOP = 0xF, + }; + struct Point { - /* 0x0 */ BE(s16) _0; - /* 0x2 */ BE(s16) _2; - /* 0x4 */ BE(s16) _4; + /* 0x0 */ BE(s16) mEnvelopeMode; // EnvelopeMode + /* 0x2 */ BE(s16) mTime; + /* 0x4 */ BE(s16) mValue; }; struct EffectParams { @@ -37,11 +48,11 @@ struct JASOscillator { struct Data { /* 0x00 */ u32 mTarget; - /* 0x04 */ f32 _04; + /* 0x04 */ f32 mRate; /* 0x08 */ const Point* mTable; /* 0x0C */ const Point* rel_table; - /* 0x10 */ f32 mScale; - /* 0x14 */ f32 _14; + /* 0x10 */ f32 mScale; // aka width + /* 0x14 */ f32 mVertex; }; enum Target { @@ -75,9 +86,9 @@ struct JASOscillator { /* 0x08 */ f32 _08; /* 0x0C */ f32 _0C; /* 0x10 */ f32 _10; - /* 0x14 */ u16 _14; + /* 0x14 */ u16 mCurPoint; /* 0x16 */ u16 mDirectRelease; - /* 0x18 */ u8 _18; + /* 0x18 */ u8 mEnvelopeMode; // EnvelopeMode /* 0x1A */ u16 _1A; /* 0x1C */ int _1C; diff --git a/libs/JSystem/include/JSystem/JAudio2/JASWSParser.h b/libs/JSystem/include/JSystem/JAudio2/JASWSParser.h index 020158c096..68d91006ba 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASWSParser.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASWSParser.h @@ -31,26 +31,28 @@ public: struct TWave { /* 0x00 */ u8 _00; - /* 0x01 */ u8 _01; - /* 0x02 */ u8 _02; - /* 0x04 */ BE(f32) _04; - /* 0x08 */ BE(u32) mOffset; - /* 0x0C */ BE(u32) _0C; - /* 0x10 */ BE(u32) _10; - /* 0x14 */ BE(u32) _14; - /* 0x18 */ BE(u32) _18; - /* 0x1C */ BE(u32) _1C; - /* 0x20 */ BE(s16) _20; - /* 0x22 */ BE(s16) _22; + /* 0x01 */ u8 mWaveFormat; + /* 0x02 */ u8 mBaseKey; + /* 0x04 */ BE(f32) mSampleRate; + /* 0x08 */ BE(u32) mAWOffsetStart; + /* 0x0C */ BE(u32) mAWOffsetEnd; + /* 0x10 */ BE(u32) mLoopFlags; + /* 0x14 */ BE(u32) mLoopStartSample; + /* 0x18 */ BE(u32) mLoopEndSample; + /* 0x1C */ BE(u32) mSampleCount; + /* 0x20 */ BE(s16) mpLast; + /* 0x22 */ BE(s16) mpPenult; }; struct TWaveArchive { - /* 0x00 */ char mFileName[0x74]; // unknown length + /* 0x00 */ char mFileName[0x70]; + /* 0x70 */ BE(u32) mWaveCount; /* 0x74 */ TOffset mWaveOffsets[0]; }; struct TWaveArchiveBank { - /* 0x0 */ u8 _00[8]; + /* 0x0 */ BE(u32) mMagic; // 'WINF' + /* 0x0 */ BE(u32) mArchiveCounts; /* 0x8 */ TOffset mArchiveOffsets[0]; }; @@ -66,14 +68,17 @@ public: }; struct TCtrlGroup { - /* 0x0 */ u8 _00[8]; + /* 0x0 */ BE(u32) mMagic; // 'WBCT' + /* 0x4 */ u32 mUnknown; /* 0x8 */ BE(u32) mGroupCount; /* 0xC */ TOffset mCtrlSceneOffsets[0]; }; /** @fabricated */ struct THeader { - /* 0x00 */ u8 _00[0xC]; + /* 0x00 */ BE(u32) mMagic; // 'WSYS' + /* 0x04 */ BE(u32) mSize; + /* 0x08 */ BE(u32) mId; /* 0x0C */ BE(u32) mWaveTableSize; /* 0x10 */ TOffset mArchiveBankOffset; /* 0x14 */ TOffset mCtrlGroupOffset; diff --git a/libs/JSystem/include/JSystem/JAudio2/JASWaveInfo.h b/libs/JSystem/include/JSystem/JAudio2/JASWaveInfo.h index cbff6d3ac2..62110754d7 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASWaveInfo.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASWaveInfo.h @@ -5,27 +5,32 @@ class JASWaveArc; +#define WAVE_FORMAT_ADPCM4 0 +#define WAVE_FORMAT_ADPCM2 1 +#define WAVE_FORMAT_PCM8 2 +#define WAVE_FORMAT_PCM16 3 + /** * @ingroup jsystem-jaudio * */ struct JASWaveInfo { JASWaveInfo() { - field_0x01 = 0x3c; + mBaseKey = 0x3c; field_0x20 = &one; } - /* 0x00 */ u8 field_0x00; - /* 0x01 */ u8 field_0x01; - /* 0x02 */ u8 field_0x02; - /* 0x04 */ f32 field_0x04; - /* 0x08 */ int field_0x08; - /* 0x0C */ int field_0x0c; - /* 0x10 */ u32 field_0x10; - /* 0x14 */ int field_0x14; - /* 0x18 */ int field_0x18; - /* 0x1C */ s16 field_0x1c; - /* 0x1E */ s16 field_0x1e; + /* 0x00 */ u8 mWaveFormat; + /* 0x01 */ u8 mBaseKey; + /* 0x02 */ u8 mLoopFlag; + /* 0x04 */ f32 mSampleRate; + /* 0x08 */ int mOffsetStart; + /* 0x0C */ int mOffsetLength; + /* 0x10 */ u32 mLoopStartSample; + /* 0x14 */ int mLoopEndSample; + /* 0x18 */ int mSampleCount; + /* 0x1C */ s16 mpLast; + /* 0x1E */ s16 mpPenult; /* 0x20 */ const u32* field_0x20; static u32 one; diff --git a/libs/JSystem/include/JSystem/JAudio2/JAUInitializer.h b/libs/JSystem/include/JSystem/JAudio2/JAUInitializer.h index 8b5090c580..9f9e5bc6fd 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JAUInitializer.h +++ b/libs/JSystem/include/JSystem/JAudio2/JAUInitializer.h @@ -21,7 +21,7 @@ public: /* 0x10 */ int audioThreadPriority_; /* 0x14 */ int dvdThreadId_; /* 0x18 */ int audioThreadId_; - /* 0x1C */ int field_0x1c; + /* 0x1C */ int mJasTrackPoolSize; /* 0x20 */ int field_0x20; /* 0x24 */ int aramBlockSize_; /* 0x28 */ int aramChannelNum_; @@ -38,10 +38,10 @@ public: JAU_JAIInitializer(); void initJAInterface(); - int field_0x0; - int field_0x4; - int field_0x8; - int field_0xc; + int mJaiSePoolSize; + int mJaiSeqPoolSize; + int mJaiStreamPoolSize; + int mJaiSoundChildPoolSize; }; #endif /* JAUINITIALIZER_H */ diff --git a/libs/JSystem/include/JSystem/JAudio2/dspproc.h b/libs/JSystem/include/JSystem/JAudio2/dspproc.h index 868080031a..c49c143fe4 100644 --- a/libs/JSystem/include/JSystem/JAudio2/dspproc.h +++ b/libs/JSystem/include/JSystem/JAudio2/dspproc.h @@ -4,7 +4,7 @@ #include void DSPReleaseHalt2(u32 msg); -void DsetupTable(u32 param_0, u32 param_1, u32 param_2, u32 param_3, u32 param_4); +void DsetupTable(u32 channelCount, u32 channelBufferAddress, u32 param_2, u32 param_3, u32 param_4); void DsetMixerLevel(f32 level); void DsyncFrame2ch(u32 param_0, u32 param_1, u32 param_2); void DsyncFrame4ch(u32 param_0, u32 param_1, u32 param_2, u32 param_3, u32 param_4); diff --git a/libs/JSystem/include/JSystem/JAudio2/dsptask.h b/libs/JSystem/include/JSystem/JAudio2/dsptask.h index 8042d1884b..7dbcff2dec 100644 --- a/libs/JSystem/include/JSystem/JAudio2/dsptask.h +++ b/libs/JSystem/include/JSystem/JAudio2/dsptask.h @@ -3,7 +3,7 @@ #include -void DspBoot(void (*)(void*)); +void DspBoot(void (*requestCallback)(void*)); void DspFinishWork(u16 param_0); int DSPSendCommands2(u32* msgs, u32 param_1, void (*param_2)(u16)); diff --git a/libs/JSystem/src/JAudio2/JASAramStream.cpp b/libs/JSystem/src/JAudio2/JASAramStream.cpp index 2a188b87e3..159df92739 100644 --- a/libs/JSystem/src/JAudio2/JASAramStream.cpp +++ b/libs/JSystem/src/JAudio2/JASAramStream.cpp @@ -43,7 +43,7 @@ void JASAramStream::initSystem(u32 block_size, u32 channel_max) { } JASAramStream::JASAramStream() { - field_0x0a8 = NULL; + mInitialChannel = NULL; field_0x0ac = false; field_0x0ad = false; field_0x0ae = 0; @@ -60,15 +60,15 @@ JASAramStream::JASAramStream() { field_0x114 = 0; field_0x118 = 0; field_0x12c = 0; - field_0x148 = 0; + mAramAddress = 0; field_0x14c = 0; mCallback = NULL; mCallbackData = NULL; - field_0x158 = 0; + mFormat = 0; mChannelNum = 0; mBufCount = 0; - field_0x160 = 0; - field_0x164 = 0; + mBlocksPerChannel = 0; + mSampleRate = 0; mLoop = false; mLoopStart = 0; mLoopEnd = 0; @@ -76,21 +76,21 @@ JASAramStream::JASAramStream() { mPitch = 1.0f; for (int i = 0; i < 6; i++) { mChannels[i] = NULL; - field_0x130[i] = 0; - field_0x13c[i] = 0; + mpLasts[i] = 0; + mpPenults[i] = 0; mChannelVolume[i] = 1.0f; mChannelPan[i] = 0.5f; mChannelFxMix[i] = 0.0f; mChannelDolby[i] = 0.0f; } for (int i = 0; i < 6; i++) { - field_0x1dc[i] = 0; + mMixConfig[i] = 0; } } -void JASAramStream::init(u32 param_0, u32 param_1, StreamCallback i_callback, void* i_callbackData) { +void JASAramStream::init(u32 aramAddress, u32 param_1, StreamCallback i_callback, void* i_callbackData) { JUT_ASSERT(153, sReadBuffer != NULL); - field_0x148 = param_0; + mAramAddress = aramAddress; field_0x14c = param_1; field_0x0c8 = 0.0f; field_0x0ae = 0; @@ -106,7 +106,7 @@ void JASAramStream::init(u32 param_0, u32 param_1, StreamCallback i_callback, vo } mVolume = 1.0f; mPitch = 1.0f; - field_0x1dc[0] = 0xffff; + mMixConfig[0] = 0xffff; mCallback = i_callback; mCallbackData = i_callbackData; OSInitMessageQueue(&field_0x000, field_0x040, 0x10); @@ -169,7 +169,7 @@ bool JASAramStream::cancel() { } u32 JASAramStream::getBlockSamples() const { - return field_0x158 == 0 ? (sBlockSize << 4) / 9 : sBlockSize >> 1; + return mFormat == 0 ? (sBlockSize << 4) / 9 : sBlockSize >> 1; } void JASAramStream::headerLoadTask(void* i_data) { @@ -245,18 +245,18 @@ bool JASAramStream::headerLoad(u32 param_0, int param_1) { JUT_ASSERT(428, header->bits == 16); JUT_ASSERT(429, header->channels <= sChannelMax); JUT_ASSERT(430, header->block_size == sBlockSize); - field_0x158 = header->format; + mFormat = header->format; mChannelNum = header->channels; - field_0x164 = header->field_0x10; + mSampleRate = header->mSampleRate; mLoop = header->loop != 0; mLoopStart = header->loop_start; mLoopEnd = header->loop_end; - mVolume = header->field_0x28 / 127.0f; + mVolume = header->mVolume / 127.0f; field_0x118 = 0; mBlock = 0; field_0x10c = 0; - field_0x160 = (param_0 / sBlockSize) / header->channels; - mBufCount = field_0x160; + mBlocksPerChannel = (param_0 / sBlockSize) / header->channels; + mBufCount = mBlocksPerChannel; JUT_ASSERT(445, mBufCount > 0); mBufCount--; if (mBufCount < 3) { @@ -319,14 +319,14 @@ bool JASAramStream::load() { if (field_0x114 != 0) { return false; } - u32 sp08 = field_0x148 + field_0x10c * sBlockSize; + u32 sp08 = mAramAddress + field_0x10c * sBlockSize; for (int i = 0; i < mChannelNum; i++) { (void)i; // Fakematch? It seems the only way to get the bhead->field_0x4 load in the right order is // with a pointer cast on its address in one of the two places it is read, but not both. - if (!JKRMainRamToAram(sReadBuffer + *(u32*)&bhead->field_0x4 * i + sizeof(BlockHeader), - sp08 + sBlockSize * field_0x160 * i, - bhead->field_0x4, EXPAND_SWITCH_UNKNOWN0, 0, NULL, -1, NULL)) { + if (!JKRMainRamToAram(sReadBuffer + *(u32*)&bhead->mSize * i + sizeof(BlockHeader), + sp08 + sBlockSize * mBlocksPerChannel * i, + bhead->mSize, EXPAND_SWITCH_UNKNOWN0, 0, NULL, -1, NULL)) { JUT_WARN(522, "%s", "JKRMainRamToAram Failed"); struct_80451261 = 1; return false; @@ -344,14 +344,14 @@ bool JASAramStream::load() { } } if (r28 == loop_end_block || r28 + 2 == loop_end_block) { - field_0x108 = field_0x160; + field_0x108 = mBlocksPerChannel; OSSendMessage(&field_0x020, (OSMessage)5, OS_MESSAGE_BLOCK); } else { - field_0x108 = field_0x160 - 1; + field_0x108 = mBlocksPerChannel - 1; } for (int i = 0; i < mChannelNum; i++) { - field_0x130[i] = (s16)bhead->field_0x8[i].field_0x0; - field_0x13c[i] = (s16)bhead->field_0x8[i].field_0x2; + mpLasts[i] = (s16)bhead->mAdpcmContinuationData[i].mpLast; + mpPenults[i] = (s16)bhead->mAdpcmContinuationData[i].mpPenult; } field_0x10c = 0; } @@ -402,8 +402,8 @@ void JASAramStream::updateChannel(u32 i_callbackType, JASChannel* i_channel, u32 block_samples = getBlockSamples(); switch (i_callbackType) { case JASChannel::CB_START: - if (field_0x0a8 == NULL) { - field_0x0a8 = i_channel; + if (mInitialChannel == NULL) { + mInitialChannel = i_channel; field_0x0b4 = block_samples * mBufCount; field_0x0b8 = 0; field_0x0b0 = 0; @@ -415,9 +415,9 @@ void JASAramStream::updateChannel(u32 i_callbackType, JASChannel* i_channel, break; case JASChannel::CB_PLAY: if (i_dspChannel->field_0x008 == 0) { - if (i_channel == field_0x0a8) { + if (i_channel == mInitialChannel) { field_0x12c = 0; - u32 sp28 = i_dspChannel->field_0x074 + i_dspChannel->field_0x064; + u32 sp28 = i_dspChannel->field_0x074 + i_dspChannel->mSamplesPerBlock; if (sp28 <= field_0x0b4) { field_0x0b8 += field_0x0b4 - sp28; } else { @@ -427,10 +427,10 @@ void JASAramStream::updateChannel(u32 i_callbackType, JASChannel* i_channel, } else { field_0x0b8 += field_0x0b4; field_0x0b8 += block_samples * mBufCount - sp28 - - i_dspChannel->field_0x110; + - i_dspChannel->mLoopStartSample; field_0x0b8 -= mLoopEnd; field_0x0b8 += mLoopStart; - i_dspChannel->field_0x110 = 0; + i_dspChannel->mLoopStartSample = 0; field_0x120 = 0; field_0x12c |= 2; if (field_0x0c4 < 0xffffffff) { @@ -448,7 +448,7 @@ void JASAramStream::updateChannel(u32 i_callbackType, JASChannel* i_channel, if (field_0x0c4 < 0xffffffff) { fvar1 += field_0x0b8; } - fvar1 /= field_0x164; + fvar1 /= mSampleRate; field_0x0c8 = fvar1; if (field_0x0b8 + 400 >= mLoopEnd && !field_0x0c0) { if (mLoop) { @@ -456,9 +456,9 @@ void JASAramStream::updateChannel(u32 i_callbackType, JASChannel* i_channel, if (uvar5 >= mBufCount) { uvar5 = 0; } - i_dspChannel->field_0x110 = mLoopStart % block_samples + i_dspChannel->mLoopStartSample = mLoopStart % block_samples + uvar5 * block_samples; - field_0x120 = i_dspChannel->field_0x110; + field_0x120 = i_dspChannel->mLoopStartSample; field_0x12c |= 2; } else { i_dspChannel->field_0x102 = 0; @@ -472,17 +472,17 @@ void JASAramStream::updateChannel(u32 i_callbackType, JASChannel* i_channel, field_0x0bc += (mLoopEnd - 1) / block_samples - mLoopStart / block_samples + 1; field_0x0c0 = true; } - u32 uvar4 = i_dspChannel->field_0x070 - i_channel->field_0x104; + u32 uvar4 = i_dspChannel->mAramStreamPosition - i_channel->mWaveAramAddress; if (uvar4 != 0) { uvar4--; } - u32 sp18 = uvar4 / sBlockSize; + u32 blockCount = uvar4 / sBlockSize; u32 sp14 = (mLoopEnd - 1) / getBlockSamples(); - if (sp18 != field_0x0b0) { - bool cmp = sp18 < field_0x0b0; - while (sp18 != field_0x0b0) { + if (blockCount != field_0x0b0) { + bool cmp = blockCount < field_0x0b0; + while (blockCount != field_0x0b0) { if (!sLoadThread->sendCmdMsg(loadToAramTask, this)) { - JUT_WARN(741, "sendCmdMsg Failed %d %d (%d %d)", i_dspChannel->field_0x070, i_channel->field_0x104, sp18, field_0x0b0); + JUT_WARN(741, "sendCmdMsg Failed %d %d (%d %d)", i_dspChannel->mAramStreamPosition, i_channel->mWaveAramAddress, blockCount, field_0x0b0); struct_80451261 = true; break; } @@ -503,16 +503,16 @@ void JASAramStream::updateChannel(u32 i_callbackType, JASChannel* i_channel, field_0x11c = i_dspChannel->field_0x074; field_0x12c |= 1; } - i_dspChannel->field_0x114 += block_samples; - field_0x124 = i_dspChannel->field_0x114; + i_dspChannel->mEndSample += block_samples; + field_0x124 = i_dspChannel->mEndSample; field_0x12c |= 4; - mBufCount = field_0x160; + mBufCount = mBlocksPerChannel; field_0x0ad = false; } else { - if (mBufCount != field_0x160 - 1) { - mBufCount = field_0x160 - 1; - i_dspChannel->field_0x114 -= block_samples; - field_0x124 = i_dspChannel->field_0x114; + if (mBufCount != mBlocksPerChannel - 1) { + mBufCount = mBlocksPerChannel - 1; + i_dspChannel->mEndSample -= block_samples; + field_0x124 = i_dspChannel->mEndSample; field_0x12c |= 4; if (!field_0x0c0) { i_dspChannel->field_0x074 -= block_samples; @@ -528,8 +528,8 @@ void JASAramStream::updateChannel(u32 i_callbackType, JASChannel* i_channel, field_0x0ae &= ~4; } } - field_0x0b4 = i_dspChannel->field_0x074 + i_dspChannel->field_0x064; - if (field_0x118 >= field_0x160 - 2) { + field_0x0b4 = i_dspChannel->field_0x074 + i_dspChannel->mSamplesPerBlock; + if (field_0x118 >= mBlocksPerChannel - 2) { JUT_WARN_DEVICE(810, 1, "%s", "buffer under error"); field_0x0ae |= (u8)4; } @@ -538,10 +538,10 @@ void JASAramStream::updateChannel(u32 i_callbackType, JASChannel* i_channel, i_dspChannel->field_0x074 = field_0x11c; } if (field_0x12c & 2) { - i_dspChannel->field_0x110 = field_0x120; + i_dspChannel->mLoopStartSample = field_0x120; } if (field_0x12c & 4) { - i_dspChannel->field_0x114 = field_0x124; + i_dspChannel->mEndSample = field_0x124; } if (field_0x12c & 8) { i_dspChannel->field_0x102 = field_0x128; @@ -554,8 +554,8 @@ void JASAramStream::updateChannel(u32 i_callbackType, JASChannel* i_channel, } } JUT_ASSERT(834, ch < CHANNEL_MAX); - i_dspChannel->field_0x104 = (s16)field_0x130[ch]; - i_dspChannel->field_0x106 = (s16)field_0x13c[ch]; + i_dspChannel->mpLast = (s16)mpLasts[ch]; + i_dspChannel->mpPenult = (s16)mpPenults[ch]; } break; case JASChannel::CB_STOP: @@ -646,44 +646,44 @@ static JASOscillator::Point const OSC_RELEASE_TABLE[2] = { static JASOscillator::Data const OSC_ENV = {0, 1.0f, NULL, OSC_RELEASE_TABLE, 1.0f, 0.0f}; void JASAramStream::channelStart() { - u8 r31; - switch (field_0x158) { + u8 format; + switch (mFormat) { case 0: - r31 = 0; + format = WAVE_FORMAT_ADPCM4; break; case 1: - r31 = 3; + format = WAVE_FORMAT_PCM16; break; } for (int i = 0; i < mChannelNum; i++) { JASWaveInfo wave_info; - wave_info.field_0x00 = r31; - wave_info.field_0x02 = 0xff; - wave_info.field_0x10 = 0; - wave_info.field_0x14 = mBufCount * getBlockSamples(); - wave_info.field_0x18 = wave_info.field_0x14; - wave_info.field_0x1c = 0; - wave_info.field_0x1e = 0; + wave_info.mWaveFormat = format; + wave_info.mLoopFlag = 0xff; + wave_info.mLoopStartSample = 0; + wave_info.mLoopEndSample = mBufCount * getBlockSamples(); + wave_info.mSampleCount = wave_info.mLoopEndSample; + wave_info.mpLast = 0; + wave_info.mpPenult = 0; // probably a fake match, this should be set in the JASWaveInfo constructor static u32 const one = 1; wave_info.field_0x20 = &one; JASChannel* jc = JKR_NEW JASChannel(channelCallback, this); JUT_ASSERT(963, jc); jc->setPriority(0x7f7f); - for (u32 j = 0; j < 6; j++) { - jc->setMixConfig(j, field_0x1dc[j]); + for (u32 j = 0; j < DSP_OUTPUT_CHANNELS; j++) { + jc->setMixConfig(j, mMixConfig[j]); } - jc->setInitPitch(field_0x164 / JASDriver::getDacRate()); + jc->setInitPitch(mSampleRate / JASDriver::getDacRate()); jc->setOscInit(0, &OSC_ENV); - jc->field_0xdc.field_0x4 = wave_info; - jc->field_0x104 = field_0x148 + sBlockSize * field_0x160 * i; - jc->field_0xdc.field_0x0 = 0; + jc->field_0xdc.mWaveInfo = wave_info; + jc->mWaveAramAddress = mAramAddress + sBlockSize * mBlocksPerChannel * i; + jc->field_0xdc.mChannelType = 0; int ret = jc->playForce(); JUT_ASSERT(977, ret); JUT_ASSERT_MSG(979, mChannels[i] == NULL, "channelStart for already playing channel"); mChannels[i] = jc; } - field_0x0a8 = NULL; + mInitialChannel = NULL; } diff --git a/libs/JSystem/src/JAudio2/JASAudioReseter.cpp b/libs/JSystem/src/JAudio2/JASAudioReseter.cpp index 4d4f3517c1..46853d03e7 100644 --- a/libs/JSystem/src/JAudio2/JASAudioReseter.cpp +++ b/libs/JSystem/src/JAudio2/JASAudioReseter.cpp @@ -47,7 +47,7 @@ s32 JASAudioReseter::checkDone() const { s32 JASAudioReseter::calc() { if (field_0x0==0) { - for (size_t i = 0; i<64; i++) { + for (size_t i = 0; igetStatus() == 0) { handle->drop(); diff --git a/libs/JSystem/src/JAudio2/JASBNKParser.cpp b/libs/JSystem/src/JAudio2/JASBNKParser.cpp index b9f010ad60..5b46009381 100644 --- a/libs/JSystem/src/JAudio2/JASBNKParser.cpp +++ b/libs/JSystem/src/JAudio2/JASBNKParser.cpp @@ -23,7 +23,7 @@ JASBasicBank* JASBNKParser::createBasicBank(void const* stream, JKRHeap* heap) { u32 free_size = heap->getFreeSize(); TFileHeader* filep = (TFileHeader*)stream; - JUT_ASSERT(59, filep->id == 'IBNK'); + JUT_ASSERT(59, filep->mMagic == 'IBNK'); JASBasicBank* bank = NULL; switch (filep->mVersion) { case 0: @@ -79,11 +79,11 @@ JASBasicBank* JASBNKParser::Ver1::createBasicBank(void const* stream, JKRHeap* h JUT_ASSERT(155, op->id == 'Osci'); JASOscillator::Data* data = &osc_data[i]; data->mTarget = op->mTarget; - data->_04 = op->_08; + data->mRate = op->mRate; data->mScale = op->mScale; - data->_14 = op->_18; - data->mTable = (JASOscillator::Point*)(envt + op->mTableOffset); - data->rel_table = (JASOscillator::Point*)(envt + op->_10); + data->mVertex = op->mVertex; + data->mTable = (JASOscillator::Point*)(envt + op->mAttackEnvelopeOffset); + data->rel_table = (JASOscillator::Point*)(envt + op->mReleaseEnvelopeOffset); } TListChunk* list = list_chunk; JUT_ASSERT(172, list->count <= JASBank::PRG_OSC); @@ -203,7 +203,7 @@ JASBasicBank* JASBNKParser::Ver0::createBasicBank(void const* stream, JKRHeap* h osc = JKR_NEW_ARGS (heap, 0) JASOscillator::Data; JUT_ASSERT(386, osc != NULL); osc->mTarget = tosc->mTarget; - osc->_04 = tosc->field_0x4; + osc->mRate = tosc->field_0x4; JASOscillator::Point* points = tosc->mPointOffset.ptr(header); if (points != NULL) { @@ -230,7 +230,7 @@ JASBasicBank* JASBNKParser::Ver0::createBasicBank(void const* stream, JKRHeap* h } osc->mScale = tosc->mScale; - osc->_14 = tosc->field_0x14; + osc->mVertex = tosc->field_0x14; instp->setOsc(osc_idx, osc); } @@ -312,7 +312,7 @@ JASOscillator::Data* JASBNKParser::Ver0::findOscPtr(JASBasicBank* bank, THeader JASOscillator::Point const* JASBNKParser::Ver0::getOscTableEndPtr(JASOscillator::Point const* points) { const JASOscillator::Point* ptr = points; while(true) { - s16 tmp = ptr->_0; + s16 tmp = ptr->mEnvelopeMode; ptr++; if (tmp > 10) { break; diff --git a/libs/JSystem/src/JAudio2/JASBank.cpp b/libs/JSystem/src/JAudio2/JASBank.cpp index 7d4fb86514..29ebe11cb5 100644 --- a/libs/JSystem/src/JAudio2/JASBank.cpp +++ b/libs/JSystem/src/JAudio2/JASBank.cpp @@ -42,13 +42,13 @@ JASChannel* JASBank::noteOn(JASBank const* param_0, int param_1, u8 param_2, u8 return NULL; } channel->setPriority(param_4); - channel->field_0xdc.field_0x4 = *waveInfo; - channel->field_0x104 = wavePtr; - channel->field_0xdc.field_0x0 = stack_60.field_0x1c; + channel->field_0xdc.mWaveInfo = *waveInfo; + channel->mWaveAramAddress = wavePtr; + channel->field_0xdc.mChannelType = stack_60.field_0x1c; channel->setBankDisposeID(param_0); - channel->setInitPitch(stack_60.mPitch * (waveInfo->field_0x04 / JASDriver::getDacRate())); + channel->setInitPitch(stack_60.mPitch * (waveInfo->mSampleRate / JASDriver::getDacRate())); if (stack_60.field_0x1e == 0) { - channel->setKey(param_2 - waveInfo->field_0x01); + channel->setKey(param_2 - waveInfo->mBaseKey); } channel->setInitVolume(stack_60.mVolume); channel->setVelocity(param_3); @@ -79,10 +79,10 @@ JASChannel* JASBank::noteOnOsc(int param_0, u8 param_1, u8 param_2, u16 param_3, return NULL; } channel->setPriority(param_3); - channel->field_0x104 = param_0; - channel->field_0xdc.field_0x0 = 2; + channel->mOscillatorSomething = param_0; + channel->field_0xdc.mChannelType = 2; channel->setInitPitch(16736.016f / JASDriver::getDacRate()); - channel->setKey(param_1 - channel->field_0xdc.field_0x4.field_0x01); + channel->setKey(param_1 - channel->field_0xdc.mWaveInfo.mBaseKey); channel->setVelocity(param_2); channel->setOscInit(0, &OSC_ENV); if (!channel->play()) { diff --git a/libs/JSystem/src/JAudio2/JASBasicWaveBank.cpp b/libs/JSystem/src/JAudio2/JASBasicWaveBank.cpp index 5387238a56..9170e59ed1 100644 --- a/libs/JSystem/src/JAudio2/JASBasicWaveBank.cpp +++ b/libs/JSystem/src/JAudio2/JASBasicWaveBank.cpp @@ -50,7 +50,7 @@ void JASBasicWaveBank::incWaveTable(JASBasicWaveBank::TWaveGroup const* param_0) if (!handle->mHeap) { handle->mHeap = ¶m_0->mHeap; handle->field_0x4.field_0x20 = ¶m_0->_48; - handle->field_0x4.field_0x08 = param_0->mCtrlWaveArray[i].field_0x4; + handle->field_0x4.mOffsetStart = param_0->mCtrlWaveArray[i].field_0x4; } } } @@ -64,7 +64,7 @@ void JASBasicWaveBank::decWaveTable(JASBasicWaveBank::TWaveGroup const* param_0) if (handle->mHeap == ¶m_0->mHeap) { handle->mHeap = NULL; handle->field_0x4.field_0x20 = &mNoLoad; - handle->field_0x4.field_0x08 = -1; + handle->field_0x4.mOffsetStart = -1; } } } @@ -86,9 +86,9 @@ void JASBasicWaveBank::setWaveInfo(JASBasicWaveBank::TWaveGroup* wgrp, int index JUT_ASSERT(206, index >= 0); mWaveTable[param_2].field_0x4 = param_3; mWaveTable[param_2].field_0x4.field_0x20 = &mNoLoad; - mWaveTable[param_2].field_0x4.field_0x08 = -1; + mWaveTable[param_2].field_0x4.mOffsetStart = -1; wgrp->mCtrlWaveArray[index].field_0x0 = param_2; - wgrp->mCtrlWaveArray[index].field_0x4 = param_3.field_0x08; + wgrp->mCtrlWaveArray[index].field_0x4 = param_3.mOffsetStart; } JASBasicWaveBank::TWaveGroup::TWaveGroup() { @@ -131,5 +131,5 @@ intptr_t JASBasicWaveBank::TWaveHandle::getWavePtr() const { if (base == 0) { return 0; } - return (intptr_t)base + field_0x4.field_0x08; + return (intptr_t)base + field_0x4.mOffsetStart; } diff --git a/libs/JSystem/src/JAudio2/JASChannel.cpp b/libs/JSystem/src/JAudio2/JASChannel.cpp index 8e81ef3e86..ffd5056e1d 100644 --- a/libs/JSystem/src/JAudio2/JASChannel.cpp +++ b/libs/JSystem/src/JAudio2/JASChannel.cpp @@ -31,7 +31,7 @@ JASChannel::JASChannel(Callback i_callback, void* i_callbackData) : mKeySweepCount(0), mSkipSamples(0) { - field_0xdc.field_0x0 = 0; + field_0xdc.mChannelType = 0; field_0x104 = 0; mMixConfig[0].whole = 0x150; mMixConfig[1].whole = 0x210; @@ -171,12 +171,12 @@ void JASChannel::updateEffectorParam(JASDsp::TChannel* i_channel, u16* i_mixerVo f32 pan = 0.5f; f32 dolby = 0.0f; switch (JASDriver::getOutputMode()) { - case 0: + case JAS_OUTPUT_MONO: break; - case 1: + case JAS_OUTPUT_STEREO: pan = calcPan(&pan_vector); break; - case 2: + case JAS_OUTPUT_SURROUND: pan = calcPan(&pan_vector); dolby = calcEffect(&dolby_vector); break; @@ -229,7 +229,7 @@ s32 JASChannel::initialUpdateDSPChannel(JASDsp::TChannel* i_channel) { mCallback(CB_START, this, i_channel, mCallbackData); } - if (field_0xdc.field_0x4.field_0x20[0] == 0) { + if (field_0xdc.mWaveInfo.field_0x20[0] == 0) { JUT_WARN_DEVICE(346, 2, "%s", "Lost wave data while playing"); mDspCh->free(); mDspCh = NULL; @@ -245,19 +245,19 @@ s32 JASChannel::initialUpdateDSPChannel(JASDsp::TChannel* i_channel) { return -1; } - switch (field_0xdc.field_0x0) { + switch (field_0xdc.mChannelType) { case 0: - i_channel->setWaveInfo(field_0xdc.field_0x4, field_0x104, mSkipSamples); + i_channel->setWaveInfo(field_0xdc.mWaveInfo, mWaveAramAddress, mSkipSamples); break; case 2: - i_channel->setOscInfo(field_0x104); + i_channel->setOscInfo(mOscillatorSomething); break; } - for (u8 i = 0; i < 6; i++) { + for (u8 i = 0; i < DSP_OUTPUT_CHANNELS; i++) { MixConfig mix_config = mMixConfig[i]; u32 output_mode = JASDriver::getOutputMode(); - if (output_mode == 0) { + if (output_mode == JAS_OUTPUT_MONO) { switch (mix_config.parts.upper) { case 8: mix_config.parts.upper = 11; @@ -266,7 +266,7 @@ s32 JASChannel::initialUpdateDSPChannel(JASDsp::TChannel* i_channel) { mix_config.parts.upper = 2; break; } - } else if (output_mode == 1 && mix_config.parts.upper == 8) { + } else if (output_mode == JAS_OUTPUT_STEREO && mix_config.parts.upper == 8) { mix_config.parts.upper = 11; } i_channel->setBusConnect(i, mix_config.parts.upper); @@ -281,9 +281,9 @@ s32 JASChannel::initialUpdateDSPChannel(JASDsp::TChannel* i_channel) { } mVibrate.resetCounter(); mTremolo.resetCounter(); - u16 mixer_volume[6]; + u16 mixer_volume[DSP_OUTPUT_CHANNELS]; updateEffectorParam(i_channel, mixer_volume, effect_params); - for (u8 i = 0; i < 6; i++) { + for (u8 i = 0; i < DSP_OUTPUT_CHANNELS; i++) { i_channel->setMixerInitVolume(i, mixer_volume[i]); } @@ -307,7 +307,7 @@ s32 JASChannel::updateDSPChannel(JASDsp::TChannel* i_channel) { mCallback(CB_PLAY, this, i_channel, mCallbackData); } - if (field_0xdc.field_0x4.field_0x20[0] == 0) { + if (field_0xdc.mWaveInfo.field_0x20[0] == 0) { JUT_WARN_DEVICE(456, 2, "%s","Lost wave data while playing"); mDspCh->free(); mDspCh = NULL; @@ -378,18 +378,18 @@ s32 JASChannel::updateDSPChannel(JASDsp::TChannel* i_channel) { return 0; } -void JASChannel::updateAutoMixer(JASDsp::TChannel* i_channel, f32 param_1, f32 param_2, - f32 param_3, f32 param_4) { - if (JASDriver::getOutputMode() == 0) { - param_1 *= 0.707f; +void JASChannel::updateAutoMixer(JASDsp::TChannel* i_channel, f32 volume, f32 pan, + f32 fxmix, f32 dolby) { + if (JASDriver::getOutputMode() == JAS_OUTPUT_MONO) { + volume *= 0.707f; } - param_1 = JASCalc::clamp01(param_1); + volume = JASCalc::clamp01(volume); - u16 r31 = param_1 * JASDriver::getChannelLevel_dsp(); - u8 r30 = param_2 * 127.5f; - u8 r29 = param_4 * 127.5f; - u8 r28 = param_3 * 127.5f; - i_channel->setAutoMixer(r31, r30, r29, r28, 0); + u16 dspVolume = volume * JASDriver::getChannelLevel_dsp(); + u8 dspPan = pan * 127.5f; + u8 dspDolby = dolby * 127.5f; + u8 dspFxMix = fxmix * 127.5f; + i_channel->setAutoMixer(dspVolume, dspPan, dspDolby, dspFxMix, 0); } void JASChannel::updateMixer(f32 i_volume, f32 i_pan, f32 i_fxmix, f32 i_dolby, u16* i_volumeOut) { @@ -429,7 +429,7 @@ void JASChannel::updateMixer(f32 i_volume, f32 i_pan, f32 i_fxmix, f32 i_dolby, volume *= scale; break; default: - if (JASDriver::getOutputMode() == 0) { + if (JASDriver::getOutputMode() == JAS_OUTPUT_MONO) { volume *= scale; } else { volume *= JMASinRadian(scale * JGeometry::TUtil::PI() * 0.5f); @@ -471,7 +471,7 @@ void JASChannel::updateMixer(f32 i_volume, f32 i_pan, f32 i_fxmix, f32 i_dolby, volume *= scale; break; default: - if (JASDriver::getOutputMode() == 0) { + if (JASDriver::getOutputMode() == JAS_OUTPUT_MONO) { volume *= scale; } else { volume *= JMASinRadian(scale * JGeometry::TUtil::PI() * 0.5f); diff --git a/libs/JSystem/src/JAudio2/JASDSPChannel.cpp b/libs/JSystem/src/JAudio2/JASDSPChannel.cpp index 1c5171082e..62c983248c 100644 --- a/libs/JSystem/src/JAudio2/JASDSPChannel.cpp +++ b/libs/JSystem/src/JAudio2/JASDSPChannel.cpp @@ -10,7 +10,7 @@ JASDSPChannel::JASDSPChannel() : mStatus(STATUS_INACTIVE), mPriority(-1), mFlags(0), - field_0xc(0), + mUpdateCounter(0), mCallback(NULL), mCallbackData(NULL), mChannel(NULL) @@ -42,9 +42,9 @@ void JASDSPChannel::drop() { } void JASDSPChannel::initAll() { - sDspChannels = JKR_NEW_ARRAY_ARGS(JASDSPChannel, 0x40, JASDram, 0x20); + sDspChannels = JKR_NEW_ARRAY_ARGS(JASDSPChannel, DSP_CHANNELS, JASDram, 0x20); JUT_ASSERT(102, sDspChannels); - for (int i = 0; i < 0x40; i++) { + for (int i = 0; i < DSP_CHANNELS; i++) { sDspChannels[i].mChannel = JASDsp::getDSPHandle(i); } } @@ -56,7 +56,7 @@ JASDSPChannel* JASDSPChannel::alloc(u8 i_priority, Callback i_callback, void* i_ } channel->drop(); channel->mPriority = i_priority; - channel->field_0xc = 0; + channel->mUpdateCounter = 0; channel->mCallback = i_callback; channel->mCallbackData = i_callbackData; return channel; @@ -70,7 +70,7 @@ JASDSPChannel* JASDSPChannel::allocForce(u8 i_priority, Callback i_callback, voi channel->mStatus = STATUS_INACTIVE; channel->drop(); channel->mPriority = i_priority; - channel->field_0xc = 0; + channel->mUpdateCounter = 0; channel->mCallback = i_callback; channel->mCallbackData = i_callbackData; return channel; @@ -83,16 +83,16 @@ void JASDSPChannel::setPriority(u8 i_priority) { JASDSPChannel* JASDSPChannel::getLowestChannel(int i_priority) { s16 best_priority = 0xff; int best_index = -1; - int best_unknown = 0; - for (int i = 0; i < 0x40; i++) { + int best_updateCounter = 0; + for (int i = 0; i < DSP_CHANNELS; i++) { JASDSPChannel* channel = &sDspChannels[i]; s16 priority = channel->mPriority; if (priority < 0) { return &sDspChannels[i]; } if (priority <= i_priority && priority <= best_priority) { - if (priority != best_priority || channel->field_0xc > best_unknown) { - best_unknown = channel->field_0xc; + if (priority != best_priority || channel->mUpdateCounter > best_updateCounter) { + best_updateCounter = channel->mUpdateCounter; best_index = i; best_priority = priority; } @@ -107,14 +107,14 @@ JASDSPChannel* JASDSPChannel::getLowestChannel(int i_priority) { JASDSPChannel* JASDSPChannel::getLowestActiveChannel() { s16 best_priority = 0xff; int best_index = -1; - int best_unknown = 0; - for (int i = 0; i < 0x40; i++) { + int best_updateCounter = 0; + for (int i = 0; i < DSP_CHANNELS; i++) { JASDSPChannel* channel = &sDspChannels[i]; if (channel->mStatus == STATUS_ACTIVE) { s16 priority = channel->mPriority; if (priority < 0x7f && priority <= best_priority) { - if (priority != best_priority || channel->field_0xc > best_unknown) { - best_unknown = channel->field_0xc; + if (priority != best_priority || channel->mUpdateCounter > best_updateCounter) { + best_updateCounter = channel->mUpdateCounter; best_index = i; best_priority = priority; } @@ -195,7 +195,7 @@ void JASDSPChannel::updateProc() { mChannel->playStop(); mChannel->flush(); } else { - field_0xc++; + mUpdateCounter++; if (flush) { mChannel->flush(); } @@ -205,7 +205,7 @@ void JASDSPChannel::updateProc() { } void JASDSPChannel::updateAll() { - for (u32 i = 0; i < 0x40; i++) { + for (u32 i = 0; i < DSP_CHANNELS; i++) { if ((i & 0xf) == 0 && i != 0) { JASDsp::releaseHalt((i - 1) >> 4); } @@ -230,8 +230,8 @@ JASDSPChannel* JASDSPChannel::getHandle(u32 i_index) { u32 JASDSPChannel::getNumUse() { u32 count = 0; - for (int i = 0; i < 0x40; i++) { - if (sDspChannels[i].mStatus == 0) { + for (int i = 0; i < DSP_CHANNELS; i++) { + if (sDspChannels[i].mStatus == STATUS_ACTIVE) { count++; } } @@ -240,8 +240,8 @@ u32 JASDSPChannel::getNumUse() { u32 JASDSPChannel::getNumFree() { u32 count = 0; - for (int i = 0; i < 0x40; i++) { - if (sDspChannels[i].mStatus == 1) { + for (int i = 0; i < DSP_CHANNELS; i++) { + if (sDspChannels[i].mStatus == STATUS_INACTIVE) { count++; } } @@ -250,8 +250,8 @@ u32 JASDSPChannel::getNumFree() { u32 JASDSPChannel::getNumBreak() { u32 count = 0; - for (int i = 0; i < 0x40; i++) { - if (sDspChannels[i].mStatus == 2) { + for (int i = 0; i < DSP_CHANNELS; i++) { + if (sDspChannels[i].mStatus == STATUS_DROP) { count++; } } diff --git a/libs/JSystem/src/JAudio2/JASDSPInterface.cpp b/libs/JSystem/src/JAudio2/JASDSPInterface.cpp index ba2d110436..e8d7fe050c 100644 --- a/libs/JSystem/src/JAudio2/JASDSPInterface.cpp +++ b/libs/JSystem/src/JAudio2/JASDSPInterface.cpp @@ -72,8 +72,8 @@ f32 JASDsp::getDSPMixerLevel() { return sDSPVolume; } -JASDsp::TChannel* JASDsp::getDSPHandle(int param_0) { - return CH_BUF + param_0; +JASDsp::TChannel* JASDsp::getDSPHandle(int index) { + return CH_BUF + index; } JASDsp::TChannel* JASDsp::getDSPHandleNc(int param_0) { @@ -87,12 +87,12 @@ void JASDsp::setFilterTable(s16* param_0, s16* param_1, u32 param_2) { } void JASDsp::flushBuffer() { - DCFlushRange(CH_BUF, sizeof(TChannel) * 64); + DCFlushRange(CH_BUF, sizeof(TChannel) * DSP_CHANNELS); DCFlushRange(FX_BUF, sizeof(FxBuf) * 4); } void JASDsp::invalChannelAll() { - DCInvalidateRange(CH_BUF, sizeof(TChannel) * 64); + DCInvalidateRange(CH_BUF, sizeof(TChannel) * DSP_CHANNELS); } u8 const ATTRIBUTE_ALIGN(32) JASDsp::DSPADPCM_FILTER[64] = { @@ -426,16 +426,16 @@ u32 const ATTRIBUTE_ALIGN(32) JASDsp::DSPRES_FILTER[320] = { }; void JASDsp::initBuffer() { - CH_BUF = JKR_NEW_ARRAY_ARGS(TChannel, 64, JASDram, 0x20); + CH_BUF = JKR_NEW_ARRAY_ARGS(TChannel, DSP_CHANNELS, JASDram, 0x20); JUT_ASSERT(354, CH_BUF); FX_BUF = JKR_NEW_ARRAY_ARGS(FxBuf, 4, JASDram, 0x20); JUT_ASSERT(356, FX_BUF); - JASCalc::bzero(CH_BUF, 0x6000); + JASCalc::bzero(CH_BUF, sizeof(TChannel) * DSP_CHANNELS); JASCalc::bzero(FX_BUF, sizeof(FxBuf) * 4); for (u8 i = 0; i < 4; i++) { setFXLine(i, NULL, NULL); } - DsetupTable(0x40, uintptr_t(CH_BUF), uintptr_t(&DSPRES_FILTER), uintptr_t(&DSPADPCM_FILTER), uintptr_t(FX_BUF)); + DsetupTable(DSP_CHANNELS, uintptr_t(CH_BUF), uintptr_t(&DSPRES_FILTER), uintptr_t(&DSPADPCM_FILTER), uintptr_t(FX_BUF)); flushBuffer(); } @@ -501,8 +501,8 @@ void JASDsp::TChannel::init() { mIsFinished = 0; mForcedStop = 0; mIsActive = 0; - field_0x058 = 0; - field_0x068 = 0; + mAutoMixerBeenSet = 0; + mSamplePosition = 0; initFilter(); } @@ -549,41 +549,41 @@ bool JASDsp::TChannel::isFinish() const { return mIsFinished != 0; } -void JASDsp::TChannel::setWaveInfo(JASWaveInfo const& param_0, u32 param_1, u32 param_2) { +void JASDsp::TChannel::setWaveInfo(JASWaveInfo const& waveInfo, u32 aramAddress, u32 skipSamples) { int i; JUT_ASSERT(610, dspMutex); - field_0x118 = param_1; + mWaveAramAddress = aramAddress; static const u8 COMP_BLOCKSAMPLES[8] = {0x10, 0x10, 0x01, 0x01, 0x01, 0x10, 0x10, 0x01}; - field_0x064 = COMP_BLOCKSAMPLES[param_0.field_0x00]; + mSamplesPerBlock = COMP_BLOCKSAMPLES[waveInfo.mWaveFormat]; static const u8 COMP_BLOCKBYTES[8] = {0x09, 0x05, 0x08, 0x10, 0x01, 0x01, 0x01, 0x01}; - field_0x100 = COMP_BLOCKBYTES[param_0.field_0x00]; - field_0x068 = 0; - if (field_0x100 >= 4) { - field_0x11c = param_0.field_0x18; - field_0x102 = param_0.field_0x02; + mBytesPerBlock = COMP_BLOCKBYTES[waveInfo.mWaveFormat]; + mSamplePosition = 0; + if (mBytesPerBlock >= 4) { + mSampleCount = waveInfo.mSampleCount; + field_0x102 = waveInfo.mLoopFlag; if (field_0x102) { - if (param_2 == 1) { - param_2 = param_0.field_0x10; + if (skipSamples == 1) { + skipSamples = waveInfo.mLoopStartSample; } - field_0x110 = param_0.field_0x10; - field_0x114 = param_0.field_0x14; - field_0x104 = param_0.field_0x1c; - field_0x106 = param_0.field_0x1e; + mLoopStartSample = waveInfo.mLoopStartSample; + mEndSample = waveInfo.mLoopEndSample; + mpLast = waveInfo.mpLast; + mpPenult = waveInfo.mpPenult; } else { - field_0x114 = field_0x11c; + mEndSample = mSampleCount; } - if (param_2 && field_0x114 > param_2) { - switch (param_0.field_0x00) { - case 0: - case 1: - field_0x068 = param_2; - field_0x118 += param_2 * field_0x100 >> 4; - field_0x110 -= param_2; - field_0x114 -= param_2; + if (skipSamples && mEndSample > skipSamples) { + switch (waveInfo.mWaveFormat) { + case WAVE_FORMAT_ADPCM4: + case WAVE_FORMAT_ADPCM2: + mSamplePosition = skipSamples; + mWaveAramAddress += skipSamples * mBytesPerBlock >> 4; + mLoopStartSample -= skipSamples; + mEndSample -= skipSamples; break; - case 2: - case 3: - field_0x068 = param_2; + case WAVE_FORMAT_PCM8: + case WAVE_FORMAT_PCM16: + mSamplePosition = skipSamples; break; } } @@ -595,28 +595,27 @@ void JASDsp::TChannel::setWaveInfo(JASWaveInfo const& param_0, u32 param_1, u32 void JASDsp::TChannel::setOscInfo(u32 param_0) { JUT_ASSERT(671, dspMutex); - field_0x118 = 0; - field_0x064 = 16; - field_0x100 = param_0; + mWaveAramAddress = 0; + mSamplesPerBlock = 16; + mBytesPerBlock = param_0; } void JASDsp::TChannel::initAutoMixer() { JUT_ASSERT(688, dspMutex); - if (field_0x058) { - field_0x054 = field_0x056; + if (mAutoMixerBeenSet) { + mAutoMixerInitVolume = mAutoMixerVolume; } else { - field_0x054 = 0; - field_0x058 = 1; + mAutoMixerInitVolume = 0; + mAutoMixerBeenSet = 1; } } -void JASDsp::TChannel::setAutoMixer(u16 param_0, u8 param_1, u8 param_2, u8 param_3, - u8 param_4) { +void JASDsp::TChannel::setAutoMixer(u16 volume, u8 pan, u8 dolby, u8 fxMix, u8) { JUT_ASSERT(709, dspMutex); - field_0x050 = (param_1 << 8) | param_2; - field_0x052 = param_3 << 8 | param_3 << 1; - field_0x056 = param_0; - field_0x058 = 1; + mAutoMixerPanDolby = (pan << 8) | dolby; + mAutoMixerFxMix = fxMix << 8 | fxMix << 1; + mAutoMixerVolume = volume; + mAutoMixerBeenSet = 1; } void JASDsp::TChannel::setPitch(u16 param_0) { @@ -627,21 +626,20 @@ void JASDsp::TChannel::setPitch(u16 param_0) { mPitch = param_0; } -void JASDsp::TChannel::setMixerInitVolume(u8 param_0, s16 param_1) { +void JASDsp::TChannel::setMixerInitVolume(u8 outputChannel, s16 volume) { JUT_ASSERT(798, dspMutex); - u16* tmp = field_0x010[param_0]; - tmp[2] = param_1; - tmp[1] = param_1; - tmp[3] = 0; + OutputChannelConfig& cfg = mOutputChannels[outputChannel]; + cfg.mCurrentVolume = volume; + cfg.mTargetVolume = volume; + cfg.mVolumeProgress = 0; } -void JASDsp::TChannel::setMixerVolume(u8 param_0, s16 param_1) { - u16* tmp; +void JASDsp::TChannel::setMixerVolume(u8 outputChannel, s16 volume) { JUT_ASSERT(841, dspMutex); if (mForcedStop == 0) { - tmp = field_0x010[param_0]; - tmp[1] = param_1; - tmp[3] &= 0xff; + OutputChannelConfig& cfg = mOutputChannels[outputChannel]; + cfg.mTargetVolume = volume; + cfg.mVolumeProgress &= 0xff; } } @@ -699,16 +697,16 @@ void JASDsp::TChannel::setDistFilter(s16 param_0) { iir_filter_params[4] = param_0; } -void JASDsp::TChannel::setBusConnect(u8 param_0, u8 param_1) { +void JASDsp::TChannel::setBusConnect(u8 outputChannel, u8 param_1) { JUT_ASSERT(973, dspMutex); - u16* tmp = field_0x010[param_0]; + OutputChannelConfig& tmp = mOutputChannels[outputChannel]; static u16 const connect_table[12] = { 0x0000, 0x0D00, 0x0D60, 0x0DC0, 0x0E20, 0x0E80, 0x0EE0, 0x0CA0, 0x0F40, 0x0FA0, 0x0B00, 0x09A0, }; u16 r30 = 0; r30 = connect_table[param_1]; - tmp[0] = r30; + tmp.mBusConnect = r30; } u16 DSP_CreateMap2(u32 param_0) { diff --git a/libs/JSystem/src/JAudio2/JASDriverIF.cpp b/libs/JSystem/src/JAudio2/JASDriverIF.cpp index 2c9f305b61..7d602a8856 100644 --- a/libs/JSystem/src/JAudio2/JASDriverIF.cpp +++ b/libs/JSystem/src/JAudio2/JASDriverIF.cpp @@ -23,10 +23,10 @@ f32 JASDriver::getDSPLevel() { return JASDsp::getDSPMixerLevel(); } -u32 JASDriver::JAS_SYSTEM_OUTPUT_MODE = 0x00000001; +u32 JASDriver::JAS_SYSTEM_OUTPUT_MODE = JAS_OUTPUT_STEREO; -void JASDriver::setOutputMode(u32 param_0) { - JAS_SYSTEM_OUTPUT_MODE = param_0; +void JASDriver::setOutputMode(u32 mode) { + JAS_SYSTEM_OUTPUT_MODE = mode; } u32 JASDriver::getOutputMode() { diff --git a/libs/JSystem/src/JAudio2/JASDrumSet.cpp b/libs/JSystem/src/JAudio2/JASDrumSet.cpp index a7a483fe29..8c8736451d 100644 --- a/libs/JSystem/src/JAudio2/JASDrumSet.cpp +++ b/libs/JSystem/src/JAudio2/JASDrumSet.cpp @@ -49,11 +49,11 @@ bool JASDrumSet::getParam(int key, int param_1, JASInstParam* param_2) const { static JASOscillator::Data osc; osc.mTarget = 0; - osc._04 = 1.0f; + osc.mRate = 1.0f; osc.mTable = NULL; osc.rel_table = NULL; osc.mScale = 1.0f; - osc._14 = 0.0f; + osc.mVertex = 0.0f; static JASOscillator::Data* oscp = &osc; diff --git a/libs/JSystem/src/JAudio2/JASOscillator.cpp b/libs/JSystem/src/JAudio2/JASOscillator.cpp index 4073af7424..f4bf2ade6f 100644 --- a/libs/JSystem/src/JAudio2/JASOscillator.cpp +++ b/libs/JSystem/src/JAudio2/JASOscillator.cpp @@ -4,9 +4,9 @@ JASOscillator::JASOscillator() { mData = NULL; - _14 = 0; + mCurPoint = 0; mDirectRelease = 0; - _18 = 0; + mEnvelopeMode = 0; _1C = 0; _04 = _08 = _10 = _0C = 0.0f; } @@ -17,7 +17,7 @@ void JASOscillator::initStart(JASOscillator::Data const* data) { _04 = 0.0f; _08 = 0.0f; _0C = 0.0f; - _14 = 0; + mCurPoint = 0; mDirectRelease = 0; if (!data) { _1C = 0; @@ -30,8 +30,8 @@ void JASOscillator::initStart(JASOscillator::Data const* data) { return; } - _10 = mData->mTable[0]._4 / 32768.0f; - _18 = mData->mTable[0]._0; + _10 = mData->mTable[0].mValue / 32768.0f; + mEnvelopeMode = mData->mTable[0].mEnvelopeMode; _1C = 1; } @@ -44,13 +44,13 @@ void JASOscillator::incCounter(f32 param_0) { case 1: break; } - _04 += param_0 * mData->_04; + _04 += param_0 * mData->mRate; update(); } f32 JASOscillator::getValue() const { JUT_ASSERT(120, mData); - return _08 * mData->mScale + mData->_14; + return _08 * mData->mScale + mData->mVertex; } void JASOscillator::release() { @@ -63,8 +63,8 @@ void JASOscillator::release() { _04 = 0.0f; _0C = _08; _10 = 0.0f; - _14 = 0; - _18 = (mDirectRelease >> 14) & 3; + mCurPoint = 0; + mEnvelopeMode = (mDirectRelease >> 14) & 3; _1C = 4; update(); return; @@ -74,9 +74,9 @@ void JASOscillator::release() { JUT_ASSERT(157, mData->rel_table != NULL); _04 = 0.0f; _0C = _08; - _10 = mData->rel_table[0]._4 / 32768.0f; - _14 = 0; - _18 = mData->rel_table[0]._0; + _10 = mData->rel_table[0].mValue / 32768.0f; + mCurPoint = 0; + mEnvelopeMode = mData->rel_table[0].mEnvelopeMode; } _1C = 3; @@ -104,31 +104,31 @@ void JASOscillator::update() { return; } - while (_04 >= psVar1[_14]._2) { - _04 -= psVar1[_14]._2; + while (_04 >= psVar1[mCurPoint].mTime) { + _04 -= psVar1[mCurPoint].mTime; _08 = _10; - _14++; + mCurPoint++; _0C = _08; - const BE(s16)* ps = &psVar1[_14]._0; - s16 r26 = ps[0]; - switch(r26) { - case 0xf: + const Point* ps = &psVar1[mCurPoint]; + s16 mode = ps->mEnvelopeMode; + switch(mode) { + case ENVELOPE_STOP: _1C = 0; return; - case 0xe: + case ENVELOPE_HOLD: _1C = 2; return; - case 0xd: - _14 = ps[2]; + case ENVELOPE_LOOP: + mCurPoint = ps->mValue; break; default: - _18 = r26; - _10 = ps[2] / 32768.0f; + mEnvelopeMode = mode; + _10 = ps->mValue / 32768.0f; break; } } - updateCurrentValue(psVar1[_14]._2); + updateCurrentValue(psVar1[mCurPoint].mTime); } f32 const JASOscillator::sCurveTableLinear[17] = { @@ -163,7 +163,7 @@ static f32* table_list[4] = { }; void JASOscillator::updateCurrentValue(f32 param_0) { - f32* table = table_list[_18]; + f32* table = table_list[mEnvelopeMode]; f32 fVar1 = 16.0f * (_04 / param_0); u32 index = (u32) fVar1; f32 fVar3 = (fVar1 - index); diff --git a/libs/JSystem/src/JAudio2/JASSimpleWaveBank.cpp b/libs/JSystem/src/JAudio2/JASSimpleWaveBank.cpp index 8bfcc2c460..52935324d8 100644 --- a/libs/JSystem/src/JAudio2/JASSimpleWaveBank.cpp +++ b/libs/JSystem/src/JAudio2/JASSimpleWaveBank.cpp @@ -44,7 +44,7 @@ intptr_t JASSimpleWaveBank::TWaveHandle::getWavePtr() const { if (base == NULL) { return 0; } - return (intptr_t)base + mWaveInfo.field_0x08; + return (intptr_t)base + mWaveInfo.mOffsetStart; } JASSimpleWaveBank::TWaveHandle::TWaveHandle() { diff --git a/libs/JSystem/src/JAudio2/JASTrack.cpp b/libs/JSystem/src/JAudio2/JASTrack.cpp index de070adde3..a60577ca38 100644 --- a/libs/JSystem/src/JAudio2/JASTrack.cpp +++ b/libs/JSystem/src/JAudio2/JASTrack.cpp @@ -356,7 +356,7 @@ int JASTrack::gateOn(u32 param_0, u32 i_velocity, f32 i_time, u32 i_flags) { } else { JASChannel* channel = channel_mgr->mChannels[0]; if (channel != NULL) { - channel->setKey(uvar7 - channel->field_0xdc.field_0x4.field_0x01); + channel->setKey(uvar7 - channel->field_0xdc.mWaveInfo.mBaseKey); channel->setVelocity(i_velocity); channel->setUpdateTimer(update_timer); } @@ -545,10 +545,10 @@ void JASTrack::setOscTable(u32 osc_no, JASOscillator::Point const* i_table) { void JASTrack::setOscAdsr(s16 param_0, s16 param_1, s16 param_2, s16 param_3, u16 i_directRelease) { mOscParam[0] = sEnvOsc; mOscParam[0].mTable = mOscPoint; - mOscPoint[0]._2 = param_0; - mOscPoint[1]._2 = param_1; - mOscPoint[2]._2 = param_2; - mOscPoint[2]._4 = param_3; + mOscPoint[0].mTime = param_0; + mOscPoint[1].mTime = param_1; + mOscPoint[2].mTime = param_2; + mOscPoint[2].mValue = param_3; mDirectRelease = i_directRelease; } diff --git a/libs/JSystem/src/JAudio2/JASWSParser.cpp b/libs/JSystem/src/JAudio2/JASWSParser.cpp index 35ec9526ed..4288377548 100644 --- a/libs/JSystem/src/JAudio2/JASWSParser.cpp +++ b/libs/JSystem/src/JAudio2/JASWSParser.cpp @@ -48,17 +48,17 @@ JASBasicWaveBank* JASWSParser::createBasicWaveBank(void const* stream, JKRHeap* for (int j = 0; j < ctrl->mWaveCount; j++) { TWave* wave = archive->mWaveOffsets[j].ptr(header); JASWaveInfo wave_info; - wave_info.field_0x00 = wave->_01; - wave_info.field_0x01 = wave->_02; - wave_info.field_0x04 = wave->_04; - wave_info.field_0x08 = wave->mOffset; - wave_info.field_0x0c = wave->_0C; - wave_info.field_0x02 = wave->_10 == 0 ? 0 : 0xff; - wave_info.field_0x10 = wave->_14; - wave_info.field_0x14 = wave->_18; - wave_info.field_0x18 = wave->_1C; - wave_info.field_0x1c = wave->_20; - wave_info.field_0x1e = wave->_22; + wave_info.mWaveFormat = wave->mWaveFormat; + wave_info.mBaseKey = wave->mBaseKey; + wave_info.mSampleRate = wave->mSampleRate; + wave_info.mOffsetStart = wave->mAWOffsetStart; + wave_info.mOffsetLength = wave->mAWOffsetEnd; + wave_info.mLoopFlag = wave->mLoopFlags == 0 ? 0 : 0xff; + wave_info.mLoopStartSample = wave->mLoopStartSample; + wave_info.mLoopEndSample = wave->mLoopEndSample; + wave_info.mSampleCount = wave->mSampleCount; + wave_info.mpLast = wave->mpLast; + wave_info.mpPenult = wave->mpPenult; TCtrlWave* ctrl_wave = ctrl->mCtrlWaveOffsets[j].ptr(header); u16 local_74 = JSULoHalf(ctrl_wave->_00); wave_bank->setWaveInfo(wave_group, j, local_74, wave_info); @@ -104,17 +104,17 @@ JASSimpleWaveBank* JASWSParser::createSimpleWaveBank(void const* stream, JKRHeap for (int i = 0; i < ctrl->mWaveCount; i++) { TWave* wave = archive->mWaveOffsets[i].ptr(header); JASWaveInfo wave_info; - wave_info.field_0x00 = wave->_01; - wave_info.field_0x01 = wave->_02; - wave_info.field_0x04 = wave->_04; - wave_info.field_0x08 = wave->mOffset; - wave_info.field_0x0c = wave->_0C; - wave_info.field_0x02 = wave->_10 == 0 ? 0 : 0xff; - wave_info.field_0x10 = wave->_14; - wave_info.field_0x14 = wave->_18; - wave_info.field_0x18 = wave->_1C; - wave_info.field_0x1c = wave->_20; - wave_info.field_0x1e = wave->_22; + wave_info.mWaveFormat = wave->mWaveFormat; + wave_info.mBaseKey = wave->mBaseKey; + wave_info.mSampleRate = wave->mSampleRate; + wave_info.mOffsetStart = wave->mAWOffsetStart; + wave_info.mOffsetLength = wave->mAWOffsetEnd; + wave_info.mLoopFlag = wave->mLoopFlags == 0 ? 0 : 0xff; + wave_info.mLoopStartSample = wave->mLoopStartSample; + wave_info.mLoopEndSample = wave->mLoopEndSample; + wave_info.mSampleCount = wave->mSampleCount; + wave_info.mpLast = wave->mpLast; + wave_info.mpPenult = wave->mpPenult; TCtrlWave* ctrl_wave = ctrl->mCtrlWaveOffsets[i].ptr(header); u32 tmp = JSULoHalf(ctrl_wave->_00); wave_bank->setWaveInfo(tmp, wave_info); diff --git a/libs/JSystem/src/JAudio2/JAUInitializer.cpp b/libs/JSystem/src/JAudio2/JAUInitializer.cpp index 3800170f77..9a9a3eeabd 100644 --- a/libs/JSystem/src/JAudio2/JAUInitializer.cpp +++ b/libs/JSystem/src/JAudio2/JAUInitializer.cpp @@ -27,7 +27,7 @@ JAU_JASInitializer::JAU_JASInitializer() { #endif dvdThreadPriority_ = 3; audioThreadPriority_ = 2; - field_0x1c = 0x80; + mJasTrackPoolSize = 0x80; dspLevel_ = 1.0f; aramBlockSize_ = 0x2760; aramChannelNum_ = 2; @@ -54,7 +54,7 @@ void JAU_JASInitializer::initJASystem(JKRSolidHeap* heap) { JASKernel::setupAramHeap(audioMemory_, audioMemSize_); - JASTrack::newMemPool(field_0x1c); + JASTrack::newMemPool(mJasTrackPoolSize); if (field_0x20 > 0) { JASTrack::TChannelMgr::newMemPool(field_0x20); } @@ -80,33 +80,33 @@ void JAU_JASInitializer::initJASystem(JKRSolidHeap* heap) { } #if PLATFORM_SHIELD - JASDriver::setOutputMode(1); + JASDriver::setOutputMode(JAS_OUTPUT_STEREO); #else switch (OSGetSoundMode()) { - case 0: - JASDriver::setOutputMode(0); + case OS_SOUND_MODE_MONO: + JASDriver::setOutputMode(JAS_OUTPUT_MONO); break; - case 1: - JASDriver::setOutputMode(1); + case OS_SOUND_MODE_STEREO: + JASDriver::setOutputMode(JAS_OUTPUT_STEREO); break; } #endif } JAU_JAIInitializer::JAU_JAIInitializer() { - field_0x0 = 100; - field_0x4 = 4; - field_0x8 = 2; - field_0xc = 16; + mJaiSePoolSize = 100; + mJaiSeqPoolSize = 4; + mJaiStreamPoolSize = 2; + mJaiSoundChildPoolSize = 16; } // NONMATCHING JASPoolAllocObject<_> locations void JAU_JAIInitializer::initJAInterface() { s32 r30 = JASDram->getFreeSize(); - JAIStream::newMemPool(field_0x8); - JAISeq::newMemPool(field_0x4); - JAISe::newMemPool(field_0x0); - JAISoundChild::newMemPool(field_0xc); + JAIStream::newMemPool(mJaiStreamPoolSize); + JAISeq::newMemPool(mJaiSeqPoolSize); + JAISe::newMemPool(mJaiSePoolSize); + JAISoundChild::newMemPool(mJaiSoundChildPoolSize); s32 r29 = JASDram->getFreeSize(); OS_REPORT("JAU_JAIInitializer uses %d bytes\n", r30 - r29); } diff --git a/libs/JSystem/src/JAudio2/JAUSectionHeap.cpp b/libs/JSystem/src/JAudio2/JAUSectionHeap.cpp index 8207c21f2f..e1f45de1ae 100644 --- a/libs/JSystem/src/JAudio2/JAUSectionHeap.cpp +++ b/libs/JSystem/src/JAudio2/JAUSectionHeap.cpp @@ -245,11 +245,11 @@ u8* JAUSection::newStaticSeqDataBlock_(JAISoundID param_0, u32 size) { return NULL; } -bool JAUSection::newStaticSeqData(JAISoundID param_0, void const* param_1, u32 param_2) { +bool JAUSection::newStaticSeqData(JAISoundID param_0, void const* param_1, u32 size) { { - u8* r30 = newStaticSeqDataBlock_(param_0, param_2); + u8* r30 = newStaticSeqDataBlock_(param_0, size); if (r30) { - memcpy(r30, param_1, param_2); + memcpy(r30, param_1, size); return true; } } diff --git a/libs/JSystem/src/JAudio2/dspproc.cpp b/libs/JSystem/src/JAudio2/dspproc.cpp index a9c08e37ff..6a40210fd0 100644 --- a/libs/JSystem/src/JAudio2/dspproc.cpp +++ b/libs/JSystem/src/JAudio2/dspproc.cpp @@ -20,10 +20,10 @@ static void setup_callback(u16 param_0) { flag = FALSE; } -void DsetupTable(u32 param_0, u32 param_1, u32 param_2, u32 param_3, u32 param_4) { +void DsetupTable(u32 channelCount, u32 channelBufferAddress, u32 param_2, u32 param_3, u32 param_4) { u32 table[5]; - table[0] = (param_0 & 0xFFFF) | 0x81000000; - table[1] = param_1; + table[0] = (channelCount & 0xFFFF) | 0x81000000; + table[1] = channelBufferAddress; table[2] = param_2; table[3] = param_3; table[4] = param_4; diff --git a/libs/JSystem/src/JAudio2/dsptask.cpp b/libs/JSystem/src/JAudio2/dsptask.cpp index 86d6323333..881c75252a 100644 --- a/libs/JSystem/src/JAudio2/dsptask.cpp +++ b/libs/JSystem/src/JAudio2/dsptask.cpp @@ -527,7 +527,7 @@ static DSPTaskInfo audio_task ATTRIBUTE_ALIGN(32); static u8 AUDIO_YIELD_BUFFER[8192] ATTRIBUTE_ALIGN(32); -void DspBoot(void (*param_0)(void*)) { +void DspBoot(void (*requestCallback)(void*)) { DspInitWork(); OS_REPORT("Dsp をブートします\n"); audio_task.priority = 0xf0; @@ -542,7 +542,7 @@ void DspBoot(void (*param_0)(void*)) { audio_task.init_cb = DspHandShake; audio_task.res_cb = NULL; audio_task.done_cb = NULL; - audio_task.req_cb = param_0; + audio_task.req_cb = requestCallback; DSPInit(); DSPAddPriorTask(&audio_task); OS_REPORT("Dspブートしました\n"); diff --git a/libs/JSystem/src/JAudio2/osdsp_task.cpp b/libs/JSystem/src/JAudio2/osdsp_task.cpp index d9f4973880..7ec8f3f855 100644 --- a/libs/JSystem/src/JAudio2/osdsp_task.cpp +++ b/libs/JSystem/src/JAudio2/osdsp_task.cpp @@ -12,7 +12,7 @@ extern "C" void __DSP_remove_task(DSPTaskInfo* task); static void Dsp_Update_Request(); -static vu8 struct_80451308; +static vu8 DspRunningStatus; static u8 struct_80451309; DSPTaskInfo* DSP_prior_task; @@ -23,7 +23,7 @@ extern "C" void __DSPHandler(__OSInterrupt interrupt, OSContext* context) { OSClearContext(&funcContext); OSSetCurrentContext(&funcContext); - if (struct_80451308 == 1 || struct_80451308 == 0) { + if (DspRunningStatus == 1 || DspRunningStatus == 0) { __DSP_curr_task = DSP_prior_task; } @@ -38,7 +38,7 @@ extern "C" void __DSPHandler(__OSInterrupt interrupt, OSContext* context) { case 0xDCD10000: __DSP_curr_task->state = 1; if (__DSP_curr_task == DSP_prior_task) { - struct_80451308 = 1; + DspRunningStatus = 1; } if (__DSP_curr_task->init_cb != NULL) { __DSP_curr_task->init_cb(__DSP_curr_task); @@ -47,7 +47,7 @@ extern "C" void __DSPHandler(__OSInterrupt interrupt, OSContext* context) { case 0xDCD10001: __DSP_curr_task->state = 1; if (__DSP_curr_task == DSP_prior_task) { - struct_80451308 = 1; + DspRunningStatus = 1; Dsp_Update_Request(); } if (__DSP_curr_task->res_cb != NULL) { @@ -97,7 +97,7 @@ extern "C" void __DSPHandler(__OSInterrupt interrupt, OSContext* context) { __DSP_curr_task = DSP_prior_task; Dsp_Update_Request(); } else { - struct_80451308 = 3; + DspRunningStatus = 3; DSPSendMailToDSP(0xCDD10001); while (DSPCheckMailToDSP() != 0); __DSP_exec_task(DSP_prior_task, __DSP_first_task); @@ -113,7 +113,7 @@ extern "C" void __DSPHandler(__OSInterrupt interrupt, OSContext* context) { static u32 sync_stack[5]; void DsyncFrame2(u32 param_0, u32 param_1, u32 param_2) { - if (struct_80451308 != 1) { + if (DspRunningStatus != 1) { sync_stack[0] = param_0; struct_80451309 = 1; sync_stack[1] = param_1; @@ -125,7 +125,7 @@ void DsyncFrame2(u32 param_0, u32 param_1, u32 param_2) { } static void DsyncFrame3(u32 param_0, u32 param_1, u32 param_2, u32 param_3, u32 param_4) { - if (struct_80451308 != 1) { + if (DspRunningStatus != 1) { sync_stack[0] = param_0; struct_80451309 = 2; sync_stack[1] = param_1; @@ -152,9 +152,9 @@ static void Dsp_Update_Request() { } int Dsp_Running_Check() { - return struct_80451308 == 1 ? TRUE : FALSE; + return DspRunningStatus == 1 ? TRUE : FALSE; } void Dsp_Running_Start() { - struct_80451308 = 1; + DspRunningStatus = 1; } diff --git a/src/Z2AudioLib/Z2AudioMgr.cpp b/src/Z2AudioLib/Z2AudioMgr.cpp index 32fa4de849..218881fbc9 100644 --- a/src/Z2AudioLib/Z2AudioMgr.cpp +++ b/src/Z2AudioLib/Z2AudioMgr.cpp @@ -30,15 +30,15 @@ Z2AudioMgr::Z2AudioMgr() : mSoundStarter(true) { void Z2AudioMgr::init(JKRSolidHeap* heap, u32 memSize, void* baaData, JKRArchive* seqArc) { JAU_JASInitializer JASInitializer; JASInitializer.audioMemSize_ = memSize; - JASInitializer.field_0x1c = 140; + JASInitializer.mJasTrackPoolSize = 140; JASInitializer.dspLevel_ = 1.3f; JASInitializer.waveArcDir_ = "Audiores/Waves/"; JASInitializer.initJASystem(heap); JAU_JAIInitializer JAIInitializer; - JAIInitializer.field_0x0 = 78; - JAIInitializer.field_0x4 = 4; - JAIInitializer.field_0xc = 48; + JAIInitializer.mJaiSePoolSize = 78; + JAIInitializer.mJaiSeqPoolSize = 4; + JAIInitializer.mJaiSoundChildPoolSize = 48; JAIInitializer.initJAInterface(); JAISeMgr* seMgr = mSoundMgr.getSeMgr(); @@ -157,7 +157,7 @@ void Z2AudioMgr::init(JKRSolidHeap* heap, u32 memSize, void* baaData, JKRArchive } void Z2AudioMgr::setOutputMode(u32 mode) { - if (mode <= 2) { + if (mode <= JAS_OUTPUT_SURROUND) { JAISetOutputMode(mode); } } diff --git a/src/d/actor/d_a_movie_player.cpp b/src/d/actor/d_a_movie_player.cpp index c2d1c394c0..0f9f68fb50 100644 --- a/src/d/actor/d_a_movie_player.cpp +++ b/src/d/actor/d_a_movie_player.cpp @@ -3268,7 +3268,7 @@ static void daMP_MixAudio(s16* destination, s16*, u32 sample) { if (r_mix > 32767) r_mix = 32767; - if (JASDriver::getOutputMode() == 0) { + if (JASDriver::getOutputMode() == JAS_OUTPUT_MONO) { l_mix = r_mix = ((r_mix >> 1) + (l_mix >> 1)); r_mix = (s16)r_mix; l_mix = (s16)l_mix; diff --git a/src/d/d_menu_option.cpp b/src/d/d_menu_option.cpp index 5e00afe208..abe9f864ce 100644 --- a/src/d/d_menu_option.cpp +++ b/src/d/d_menu_option.cpp @@ -24,6 +24,8 @@ #include "m_Do/m_Do_graphic.h" #include +#include "JSystem/JAudio2/JASDriverIF.h" + typedef void (dMenu_Option_c::*initFunc)(); static initFunc init[] = { &dMenu_Option_c::atten_init, @@ -89,7 +91,7 @@ dMenu_Option_c::dMenu_Option_c(JKRArchive* i_archive, STControl* i_stick) { dMenu_Option_c::~dMenu_Option_c() {} -static const u32 dMo_soundMode[3] = {0, 1, 2}; +static const u32 dMo_soundMode[3] = {JAS_OUTPUT_MONO, JAS_OUTPUT_STEREO, JAS_OUTPUT_SURROUND}; void dMenu_Option_c::_create() { static const u64 text_a_tag[5] = {MULTI_CHAR('atext1_1'), MULTI_CHAR('atext1_2'), MULTI_CHAR('atext1_3'), MULTI_CHAR('atext1_4'), MULTI_CHAR('atext1_5')}; @@ -1755,13 +1757,13 @@ void dMenu_Option_c::screenSet() { } } -void dMenu_Option_c::setSoundMode(u32 param_0) { - switch (param_0) { - case 0: +void dMenu_Option_c::setSoundMode(u32 soundMode) { + switch (soundMode) { + case JAS_OUTPUT_MONO: OSSetSoundMode(OS_SOUND_MODE_MONO); break; - case 1: - case 2: + case JAS_OUTPUT_STEREO: + case JAS_OUTPUT_SURROUND: // Via dolby pro logic 2, so it's over 2 output channels. OSSetSoundMode(OS_SOUND_MODE_STEREO); break; } From 4561e169e9a45469f04457f760294dd6ef1a68df Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Thu, 12 Mar 2026 07:04:51 +0100 Subject: [PATCH 002/350] Fix build Heck --- libs/JSystem/include/JSystem/JAudio2/JASChannel.h | 1 + 1 file changed, 1 insertion(+) diff --git a/libs/JSystem/include/JSystem/JAudio2/JASChannel.h b/libs/JSystem/include/JSystem/JAudio2/JASChannel.h index 53bf064d16..89bc381575 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASChannel.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASChannel.h @@ -6,6 +6,7 @@ #include "JSystem/JAudio2/JASOscillator.h" #include "JSystem/JAudio2/JASSoundParams.h" #include "JSystem/JAudio2/JASWaveInfo.h" +#include "JSystem/JAudio2/JASDSPInterface.h" #include struct JASDSPChannel; From 98d71f97eaa3caff7ca430148509b13c598a73fe Mon Sep 17 00:00:00 2001 From: madeline Date: Tue, 10 Mar 2026 16:12:10 -0700 Subject: [PATCH 003/350] stub dsp send commands for now --- libs/JSystem/src/JAudio2/dsptask.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libs/JSystem/src/JAudio2/dsptask.cpp b/libs/JSystem/src/JAudio2/dsptask.cpp index 881c75252a..de54bcc7f5 100644 --- a/libs/JSystem/src/JAudio2/dsptask.cpp +++ b/libs/JSystem/src/JAudio2/dsptask.cpp @@ -4,6 +4,7 @@ #include "JSystem/JAudio2/osdsp.h" #include "global.h" #include "os_report.h" +#include "dusk/logging.h" static void DspInitWork(); static void DspHandShake(void* param_0); @@ -549,6 +550,9 @@ void DspBoot(void (*requestCallback)(void*)) { } int DSPSendCommands2(u32* param_1, u32 param_2, void (*callBack)(u16)) { + callBack(param_1[0]); + STUB_RET(1); + s32 i; BOOL interruptFlag; s32 startWorkStatus; From 23b2edafdf080a29631226370e487324f7e2e850 Mon Sep 17 00:00:00 2001 From: madeline Date: Tue, 10 Mar 2026 16:12:20 -0700 Subject: [PATCH 004/350] enable audio --- include/dusk/audio.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/dusk/audio.h b/include/dusk/audio.h index 46a2dd8859..e3dfda9f52 100644 --- a/include/dusk/audio.h +++ b/include/dusk/audio.h @@ -2,13 +2,13 @@ #define DUSK_AUDIO_H #if TARGET_PC -#define DUSK_AUDIO_DISABLED 1 +#define DUSK_AUDIO_DISABLED 0 #else #define DUSK_AUDIO_DISABLED 0 #endif #if TARGET_PC -#define DUSK_AUDIO_SKIP(...) return __VA_ARGS__; +#define DUSK_AUDIO_SKIP(...) #else #define DUSK_AUDIO_SKIP(...) #endif From 8e69478939ff43f68cee629676038569ab007830 Mon Sep 17 00:00:00 2001 From: madeline Date: Tue, 10 Mar 2026 16:12:38 -0700 Subject: [PATCH 005/350] fix truncated pointer --- src/m_Do/m_Do_dvd_thread.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/m_Do/m_Do_dvd_thread.cpp b/src/m_Do/m_Do_dvd_thread.cpp index 526b5a255e..6ea1cf99f7 100644 --- a/src/m_Do/m_Do_dvd_thread.cpp +++ b/src/m_Do/m_Do_dvd_thread.cpp @@ -159,7 +159,11 @@ void mDoDvdThd_param_c::mainLoop() { while (command = this->getFirstCommand()) { this->cut(command); if (mDoDvdThd::SyncWidthSound) { + #if TARGET_PC + JASDvd::getThreadPointer()->sendCmdMsg(cb, &command, sizeof(void*)); + #else JASDvd::getThreadPointer()->sendCmdMsg(cb, &command, 4); + #endif } else { cb(&command); } From 78de6cf3cf969fefb7688f4f35640ad2863bd255 Mon Sep 17 00:00:00 2001 From: madeline Date: Tue, 10 Mar 2026 20:10:45 -0700 Subject: [PATCH 006/350] fix createBasicBank (for now) --- libs/JSystem/src/JAudio2/JASBNKParser.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libs/JSystem/src/JAudio2/JASBNKParser.cpp b/libs/JSystem/src/JAudio2/JASBNKParser.cpp index 5b46009381..ddab6d2e38 100644 --- a/libs/JSystem/src/JAudio2/JASBNKParser.cpp +++ b/libs/JSystem/src/JAudio2/JASBNKParser.cpp @@ -128,8 +128,10 @@ JASBasicBank* JASBNKParser::Ver1::createBasicBank(void const* stream, JKRHeap* h case 'Perc': { JASDrumSet* drump = JKR_NEW_ARGS (heap, 0) JASDrumSet; JUT_ASSERT(264, drump != NULL); + #if PLATFORM_SHIELD u32 pmap_count = data[1]; JUT_ASSERT(268, pmap_count <= 128); + #endif u32 count = *data++; drump->newPercArray(count, heap); for (int j = 0; j < count; j++) { @@ -137,8 +139,10 @@ JASBasicBank* JASBNKParser::Ver1::createBasicBank(void const* stream, JKRHeap* h if (offset != 0) { JASDrumSet::TPerc* percp = JKR_NEW_ARGS (heap, 0) JASDrumSet::TPerc; JUT_ASSERT(277, percp); + #if PLATFORM_SHIELD u32 type = data[0]; JUT_ASSERT(282, type == 'Pmap'); + #endif BE(u32)* ptr = (BE(u32)*)((intptr_t)stream + offset); TPercData* perc_data = (TPercData*)(ptr + 1); percp->setVolume(perc_data->mVolume); From 26fe1bc4d5142bd02d7a5670668d6376daeaff40 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Thu, 12 Mar 2026 08:26:35 +0100 Subject: [PATCH 007/350] Fix JASTaskThread::allocCallStack on 64-bit --- libs/JSystem/src/JAudio2/JASTaskThread.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/JSystem/src/JAudio2/JASTaskThread.cpp b/libs/JSystem/src/JAudio2/JASTaskThread.cpp index 30a6d07a98..1d031671b4 100644 --- a/libs/JSystem/src/JAudio2/JASTaskThread.cpp +++ b/libs/JSystem/src/JAudio2/JASTaskThread.cpp @@ -26,7 +26,7 @@ JASTaskThread::~JASTaskThread() { void* JASTaskThread::allocCallStack(JASThreadCallback callback, const void* msg, u32 msgSize) { ThreadMemPool* heap; - u32 size = msgSize + 8; + u32 size = msgSize + offsetof(JASThreadCallStack, msg); JASThreadCallStack *callStack = (JASThreadCallStack*) JASKernel::getCommandHeap()->alloc(size); if (callStack == NULL) { return NULL; From 227b785273612ed2c726513be1b1fe2d82de7a36 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Thu, 12 Mar 2026 11:10:21 +0100 Subject: [PATCH 008/350] Implement JASCriticalSection with a regular mutex --- .../include/JSystem/JAudio2/JASCriticalSection.h | 6 ++++++ src/dusk/audio/JASCriticalSection.cpp | 13 +++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 src/dusk/audio/JASCriticalSection.cpp diff --git a/libs/JSystem/include/JSystem/JAudio2/JASCriticalSection.h b/libs/JSystem/include/JSystem/JAudio2/JASCriticalSection.h index 53faae80fe..ed4cb023ed 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASCriticalSection.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASCriticalSection.h @@ -8,12 +8,18 @@ * */ class JASCriticalSection { +public: +#if TARGET_PC + JASCriticalSection(); + ~JASCriticalSection(); +#else public: JASCriticalSection() { mInterruptState = OSDisableInterrupts(); }; ~JASCriticalSection() { OSRestoreInterrupts(mInterruptState); }; private: u32 mInterruptState; +#endif }; #endif /* JASCRITICALSECTION_H */ diff --git a/src/dusk/audio/JASCriticalSection.cpp b/src/dusk/audio/JASCriticalSection.cpp new file mode 100644 index 0000000000..9b315b9d0b --- /dev/null +++ b/src/dusk/audio/JASCriticalSection.cpp @@ -0,0 +1,13 @@ +#include "JSystem/JAudio2/JASCriticalSection.h" + +#include + +static std::recursive_mutex gAudioThreadMutex; + +JASCriticalSection::JASCriticalSection() { + gAudioThreadMutex.lock(); +} + +JASCriticalSection::~JASCriticalSection() { + gAudioThreadMutex.unlock(); +} From a2403dca19eece7a68a648a5b0fd9bf78ed35999 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Thu, 12 Mar 2026 11:11:09 +0100 Subject: [PATCH 009/350] Make stub log thread safe --- src/dusk/imgui/ImGuiStubLog.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/dusk/imgui/ImGuiStubLog.cpp b/src/dusk/imgui/ImGuiStubLog.cpp index fc9780800a..92bdf7fb9a 100644 --- a/src/dusk/imgui/ImGuiStubLog.cpp +++ b/src/dusk/imgui/ImGuiStubLog.cpp @@ -1,4 +1,5 @@ #include +#include #include "dusk/logging.h" #include "imgui.h" @@ -9,6 +10,7 @@ namespace dusk { static ImGuiTextBuffer StubLogBuffer; static std::vector LineOffsets; static bool StubLogPaused; + static std::mutex StubLogMutex; const char* LogLevelName(const AuroraLogLevel level) { switch (level) { @@ -32,6 +34,8 @@ namespace dusk { return; } + std::lock_guard lock(StubLogMutex); + LineOffsets.push_back(StubLogBuffer.size()); const auto levelName = LogLevelName(level); StubLogBuffer.appendf("[%s | %s] %s\n", levelName, module, message); @@ -40,6 +44,8 @@ namespace dusk { static void ClearPastFrame(); void ImGuiMenuTools::ShowStubLog() { + std::lock_guard lock(StubLogMutex); + if (!ImGuiConsole::CheckMenuViewToggle(ImGuiKey_F5, m_showStubLog)) { ClearPastFrame(); return; From 82bf73e867e6e3a26ceefc51ff399f7fb45b2c5c Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Thu, 12 Mar 2026 11:12:46 +0100 Subject: [PATCH 010/350] Fix warning in stub_log.cpp --- src/dusk/imgui/ImGuiStubLog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dusk/imgui/ImGuiStubLog.cpp b/src/dusk/imgui/ImGuiStubLog.cpp index 92bdf7fb9a..595ae655f6 100644 --- a/src/dusk/imgui/ImGuiStubLog.cpp +++ b/src/dusk/imgui/ImGuiStubLog.cpp @@ -62,7 +62,7 @@ namespace dusk { if (ImGui::BeginChild("scrolling")) { ImGuiListClipper clipper; - clipper.Begin(LineOffsets.size()); + clipper.Begin(static_cast(LineOffsets.size())); while (clipper.Step()) { for (int idx = clipper.DisplayStart; idx < clipper.DisplayEnd; idx++) { const char* lineStart = StubLogBuffer.begin() + LineOffsets[idx]; From f724316094f0400ae27f5482e0da9a6f2a85175e Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Thu, 12 Mar 2026 11:46:09 +0100 Subject: [PATCH 011/350] Fix JAISoundID endianness issues --- libs/JSystem/include/JSystem/JAudio2/JAISound.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/libs/JSystem/include/JSystem/JAudio2/JAISound.h b/libs/JSystem/include/JSystem/JAudio2/JAISound.h index 5e1cd40b4b..155eca1e65 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JAISound.h +++ b/libs/JSystem/include/JSystem/JAudio2/JAISound.h @@ -5,6 +5,7 @@ #include "JSystem/JAudio2/JAIAudible.h" #include "JSystem/JUtility/JUTAssert.h" #include "global.h" +#include "dusk/endian.h" #include class JAISound; @@ -55,16 +56,16 @@ public: void setAnonymous() { id_.composite_ = -1; } union { - u32 composite_; + BE(u32) composite_; struct { union { - u16 value; + BE(u16) value; struct { u8 sectionID; u8 groupID; } parts; } type; - u16 waveID; + BE(u16) waveID; } info; } id_; }; From 5c409fa8aa05267efe59b53b407bea6cca076600 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Thu, 12 Mar 2026 11:46:31 +0100 Subject: [PATCH 012/350] Add SDL-mixer dependency --- CMakeLists.txt | 4 +++- cmake/SDL-mixer.cmake | 28 ++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 cmake/SDL-mixer.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 9395f83759..f4bfc954f7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -62,7 +62,9 @@ FetchContent_Declare( FetchContent_MakeAvailable(cxxopts) +message(STATUS "Dusk: Fetching SDL3-mixer") +include(cmake/SDL-mixer.cmake) include(files.cmake) # TODO: version handling for res includes @@ -94,7 +96,7 @@ target_include_directories(game_debug PUBLIC extern ${CMAKE_SOURCE_DIR}/build/${DUSK_TP_VERSION}/include build/${DUSK_TP_VERSION}/include) -target_link_libraries(game_debug PUBLIC aurora::core aurora::gx aurora::gd aurora::si aurora::vi aurora::pad aurora::mtx aurora::os aurora::dvd aurora::card) +target_link_libraries(game_debug PUBLIC aurora::core aurora::gx aurora::gd aurora::si aurora::vi aurora::pad aurora::mtx aurora::os aurora::dvd aurora::card SDL3_mixer::SDL3_mixer) set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) add_library(game SHARED ${DOLZEL_FILES} ${Z2AUDIOLIB_FILES} ${SSYSTEM_FILES} ${JSYSTEM_FILES} ${REL_FILES} ${DUSK_FILES} ${DOLPHIN_FILES} diff --git a/cmake/SDL-mixer.cmake b/cmake/SDL-mixer.cmake new file mode 100644 index 0000000000..e2106c4eb6 --- /dev/null +++ b/cmake/SDL-mixer.cmake @@ -0,0 +1,28 @@ +set(SDLMIXER_VENDORED ON) +set(SDLMIXER_STRICT ON) +set(BUILD_SHARED_LIBS OFF) + +set(SDLMIXER_AIFF OFF) +set(SDLMIXER_WAVE OFF) +set(SDLMIXER_VOC OFF) +set(SDLMIXER_AU OFF) +set(SDLMIXER_FLAC OFF) +set(SDLMIXER_GME OFF) +set(SDLMIXER_MOD OFF) +set(SDLMIXER_MP3 OFF) +set(SDLMIXER_MIDI OFF) +set(SDLMIXER_OPUS OFF) +set(SDLMIXER_WAVPACK OFF) +set(SDLMIXER_VORBIS_STB OFF) +set(SDLMIXER_VORBIS_VORBISFILE OFF) + +FetchContent_Declare(SDL-mixer + URL https://github.com/libsdl-org/SDL_mixer/archive/refs/tags/release-3.2.0.tar.gz + URL_HASH SHA256=1077fef4a6e9513027da0a08e6f1b8645f8c88c8bef001c17befeb52b129b5dd + DOWNLOAD_EXTRACT_TIMESTAMP TRUE +) + +FetchContent_MakeAvailable(SDL-mixer) +if (NOT TARGET SDL3_mixer::SDL3_mixer) + message(FATAL_ERROR "Failed to make SDL-mixer available") +endif () From eef58c15fc9b57f75393b1a6c707b24c711752b4 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Thu, 12 Mar 2026 11:47:04 +0100 Subject: [PATCH 013/350] Remove JASAudioThread race condition fixing code I'm not planning on using JASAudioThread anymore --- .../include/JSystem/JAudio2/JASAudioThread.h | 5 ----- libs/JSystem/src/JAudio2/JASAudioThread.cpp | 18 +----------------- src/Z2AudioLib/Z2AudioMgr.cpp | 13 ------------- 3 files changed, 1 insertion(+), 35 deletions(-) diff --git a/libs/JSystem/include/JSystem/JAudio2/JASAudioThread.h b/libs/JSystem/include/JSystem/JAudio2/JASAudioThread.h index 6be7d07000..dd9fed2183 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASAudioThread.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASAudioThread.h @@ -32,11 +32,6 @@ struct JASAudioThread : public JKRThread, public JASGlobalInstance::newMemPool(0x48); JASDriver::startDMA(); -#if TARGET_PC - OSLockMutex(&sThreadInitCompleteMutex); - sThreadInitComplete = true; - OSUnlockMutex(&sThreadInitCompleteMutex); - OSSignalCond(&sThreadInitCompleteCond); -#endif - OSMessage msg; while (true) { msg = waitMessageBlock(); diff --git a/src/Z2AudioLib/Z2AudioMgr.cpp b/src/Z2AudioLib/Z2AudioMgr.cpp index 218881fbc9..1cfc4b28b1 100644 --- a/src/Z2AudioLib/Z2AudioMgr.cpp +++ b/src/Z2AudioLib/Z2AudioMgr.cpp @@ -106,19 +106,6 @@ void Z2AudioMgr::init(JKRSolidHeap* heap, u32 memSize, void* baaData, JKRArchive JASPoolAllocObject::newMemPool(0x4e); OS_REPORT("[Z2AudioMgr::init]before Create Section: %d\n", heap->getFreeSize()); -#if TARGET_PC - // Fix a race condition with OS threading where JAUNewSectionHeap will use all the remaining - // space in the JASDram heap before JASAudioThread has finished initializing. - - OSLockMutex(&JASAudioThread::sThreadInitCompleteMutex); - while (!JASAudioThread::sThreadInitComplete) { - OSWaitCond( - &JASAudioThread::sThreadInitCompleteCond, - &JASAudioThread::sThreadInitCompleteMutex); - } - OSUnlockMutex(&JASAudioThread::sThreadInitCompleteMutex); -#endif - JAUSectionHeap* sectionHeap = JAUNewSectionHeap(true); sectionHeap->setSeqDataArchive(seqArc); size_t resMaxSize = JASResArcLoader::getResMaxSize(seqArc); From 4139cb9a2c628c71fbf4b6c37464e5f7290adf63 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Thu, 12 Mar 2026 11:47:22 +0100 Subject: [PATCH 014/350] Constant for DSP_SUBFRAME_SIZE --- libs/JSystem/include/JSystem/JAudio2/JASDSPInterface.h | 5 +++++ libs/JSystem/src/JAudio2/JASAiCtrl.cpp | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/libs/JSystem/include/JSystem/JAudio2/JASDSPInterface.h b/libs/JSystem/include/JSystem/JAudio2/JASDSPInterface.h index ec2d9f6c89..9bb9eecb16 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASDSPInterface.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASDSPInterface.h @@ -13,6 +13,11 @@ */ #define DSP_OUTPUT_CHANNELS 6 // Presumed 5.1 surround +/** + * Amount of audio samples rendered by the DSP in a single sub frame. + */ +#define DSP_SUBFRAME_SIZE 0x50 + struct JASWaveInfo; namespace JASDsp { diff --git a/libs/JSystem/src/JAudio2/JASAiCtrl.cpp b/libs/JSystem/src/JAudio2/JASAiCtrl.cpp index 4e1c296388..ec60bd9d89 100644 --- a/libs/JSystem/src/JAudio2/JASAiCtrl.cpp +++ b/libs/JSystem/src/JAudio2/JASAiCtrl.cpp @@ -271,11 +271,11 @@ u32 JASDriver::getSubFrames() { } u32 JASDriver::getDacSize() { - return sSubFrames * 0x50 * 2; + return sSubFrames * DSP_SUBFRAME_SIZE * 2; } u32 JASDriver::getFrameSamples() { - return sSubFrames * 0x50; + return sSubFrames * DSP_SUBFRAME_SIZE; } void JASDriver::mixMonoTrack(s16* buffer, u32 param_1, MixCallback param_2) { From 29b0a41c27001f6f831b7e41cc27889b13405ebd Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Thu, 12 Mar 2026 11:48:02 +0100 Subject: [PATCH 015/350] Block DSP commands, no DSP emulated --- libs/JSystem/src/JAudio2/dspproc.cpp | 4 ++++ libs/JSystem/src/JAudio2/dsptask.cpp | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/libs/JSystem/src/JAudio2/dspproc.cpp b/libs/JSystem/src/JAudio2/dspproc.cpp index 6a40210fd0..891722b7f4 100644 --- a/libs/JSystem/src/JAudio2/dspproc.cpp +++ b/libs/JSystem/src/JAudio2/dspproc.cpp @@ -21,6 +21,10 @@ static void setup_callback(u16 param_0) { } void DsetupTable(u32 channelCount, u32 channelBufferAddress, u32 param_2, u32 param_3, u32 param_4) { +#if TARGET_PC + return; +#endif + u32 table[5]; table[0] = (channelCount & 0xFFFF) | 0x81000000; table[1] = channelBufferAddress; diff --git a/libs/JSystem/src/JAudio2/dsptask.cpp b/libs/JSystem/src/JAudio2/dsptask.cpp index de54bcc7f5..7d2e8dd2ad 100644 --- a/libs/JSystem/src/JAudio2/dsptask.cpp +++ b/libs/JSystem/src/JAudio2/dsptask.cpp @@ -550,9 +550,9 @@ void DspBoot(void (*requestCallback)(void*)) { } int DSPSendCommands2(u32* param_1, u32 param_2, void (*callBack)(u16)) { - callBack(param_1[0]); - STUB_RET(1); - +#if TARGET_PC + OSPanic(__FILE__, __LINE__, "We do not have a DSP"); +#endif s32 i; BOOL interruptFlag; s32 startWorkStatus; From afdc39653faefec50be192c039fe9225e391968b Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Thu, 12 Mar 2026 11:48:55 +0100 Subject: [PATCH 016/350] nop JASDsp::releaseHalt --- libs/JSystem/src/JAudio2/JASDSPInterface.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libs/JSystem/src/JAudio2/JASDSPInterface.cpp b/libs/JSystem/src/JAudio2/JASDSPInterface.cpp index e8d7fe050c..a4c91b103c 100644 --- a/libs/JSystem/src/JAudio2/JASDSPInterface.cpp +++ b/libs/JSystem/src/JAudio2/JASDSPInterface.cpp @@ -50,6 +50,10 @@ void JASDsp::boot(void (*param_0)(void*)) { } void JASDsp::releaseHalt(u32 param_0) { +#if TARGET_PC + // Dusk DSP does not work incrementally. + return; +#endif DSPReleaseHalt2(param_0); } From 0c993fd7ed0fa017b8d06d4546a52155622a4c37 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Thu, 12 Mar 2026 11:49:03 +0100 Subject: [PATCH 017/350] Basic audio system start --- CMakeLists.txt | 6 +- include/dusk/audio/DuskAudioSystem.h | 3 + libs/JSystem/src/JAudio2/JAUInitializer.cpp | 6 ++ src/dusk/audio/DuskAudioSystem.cpp | 82 +++++++++++++++++++++ src/dusk/audio/DuskDsp.cpp | 16 ++++ src/dusk/audio/DuskDsp.hpp | 9 +++ 6 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 include/dusk/audio/DuskAudioSystem.h create mode 100644 src/dusk/audio/DuskAudioSystem.cpp create mode 100644 src/dusk/audio/DuskDsp.cpp create mode 100644 src/dusk/audio/DuskDsp.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index f4bfc954f7..ceebbc1818 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -83,7 +83,11 @@ source_group("dusk" FILES ${DUSK_FILES}) # game_debug is for game code files that we know work when compiled with DEBUG=1 # Of course, if building a release build, this distinction is irrelevant -add_library(game_debug STATIC ${JSYSTEM_DEBUG_FILES} ${SSYSTEM_FILES}) +add_library(game_debug STATIC ${JSYSTEM_DEBUG_FILES} ${SSYSTEM_FILES} + src/dusk/audio/DuskAudioSystem.cpp + src/dusk/audio/JASCriticalSection.cpp + src/dusk/audio/DuskDsp.hpp + src/dusk/audio/DuskDsp.cpp) target_compile_definitions(game_debug PRIVATE TARGET_PC AVOID_UB=1 VERSION=0 $<$:DEBUG=1>) # Make these properties PUBLIC so that the regular game target also sees them. diff --git a/include/dusk/audio/DuskAudioSystem.h b/include/dusk/audio/DuskAudioSystem.h new file mode 100644 index 0000000000..0ea13125ad --- /dev/null +++ b/include/dusk/audio/DuskAudioSystem.h @@ -0,0 +1,3 @@ +#pragma once + +void DuskAudioInitialize(); diff --git a/libs/JSystem/src/JAudio2/JAUInitializer.cpp b/libs/JSystem/src/JAudio2/JAUInitializer.cpp index 9a9a3eeabd..7d56282b45 100644 --- a/libs/JSystem/src/JAudio2/JAUInitializer.cpp +++ b/libs/JSystem/src/JAudio2/JAUInitializer.cpp @@ -15,6 +15,8 @@ #include "JSystem/JKernel/JKRSolidHeap.h" #include "JSystem/JKernel/JKRThread.h" +#include "dusk/audio/DuskAudioSystem.h" + JAU_JASInitializer::JAU_JASInitializer() { audioMemory_ = 0; audioMemSize_ = 0; @@ -60,7 +62,11 @@ void JAU_JASInitializer::initJASystem(JKRSolidHeap* heap) { } JASDvd::createThread(dvdThreadPriority_, 0x80, 0x1000); +#if TARGET_PC + DuskAudioInitialize(); +#else JASAudioThread::create(audioThreadPriority_); +#endif JKRThreadSwitch* threadSwitch = JKRThreadSwitch::getManager(); if (threadSwitch) { if (dvdThreadId_ >= 0) { diff --git a/src/dusk/audio/DuskAudioSystem.cpp b/src/dusk/audio/DuskAudioSystem.cpp new file mode 100644 index 0000000000..566d2ab400 --- /dev/null +++ b/src/dusk/audio/DuskAudioSystem.cpp @@ -0,0 +1,82 @@ +#include "dusk/audio/DuskAudioSystem.h" + +#include +#include + +#include "JSystem/JAudio2/JASAiCtrl.h" +#include "JSystem/JAudio2/JASChannel.h" +#include "JSystem/JAudio2/JASCriticalSection.h" +#include "JSystem/JAudio2/JASDSPChannel.h" +#include "JSystem/JAudio2/JASHeapCtrl.h" + +#include "DuskDsp.hpp" + +static DspSubframe AllSubframeBuffers[DSP_OUTPUT_CHANNELS]; + +static SDL_AudioStream* PlaybackStream; + +static void SDLCALL GetNewAudio( + void *userdata, + SDL_AudioStream *stream, + int additional_amount, + int total_amount); + +static int RenderNewAudioFrame(); +static void RenderAudioSubframe(); + +static void InitSDL3Output() { + SDL_Init(SDL_INIT_AUDIO); + + constexpr SDL_AudioSpec spec = { + SDL_AUDIO_S16, + 1, + 32000, + }; + PlaybackStream = SDL_OpenAudioDeviceStream( + SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, + &spec, + &GetNewAudio, + nullptr); +} + +void DuskAudioInitialize() { + InitSDL3Output(); + + JASDsp::initBuffer(); + JASDSPChannel::initAll(); + + JASPoolAllocObject_MultiThreaded::newMemPool(0x48); + + SDL_ResumeAudioStreamDevice(PlaybackStream); +} + +void SDLCALL GetNewAudio( + void*, + SDL_AudioStream*, + int, + int total_amount) { + while (total_amount > 0) { + const int rendered = RenderNewAudioFrame(); + total_amount -= rendered; + } +} + +int RenderNewAudioFrame() { + JASCriticalSection section; + const u32 countSubframes = JASDriver::getSubFrames(); + + for (u32 i = 0; i < countSubframes; i++) { + RenderAudioSubframe(); + } + + return static_cast(countSubframes) * DSP_SUBFRAME_SIZE; +} + +void RenderAudioSubframe() { + DspSubframe& subFrame = AllSubframeBuffers[0]; + + JASDriver::updateDSP(); + DuskDspRender(subFrame); + + SDL_PutAudioStreamData(PlaybackStream, &subFrame, sizeof(subFrame)); +} diff --git a/src/dusk/audio/DuskDsp.cpp b/src/dusk/audio/DuskDsp.cpp new file mode 100644 index 0000000000..63cc5707e3 --- /dev/null +++ b/src/dusk/audio/DuskDsp.cpp @@ -0,0 +1,16 @@ +#include "DuskDsp.hpp" + +#include + +static float SinePos; + +void DuskDspRender(DspSubframe& subframe) { + subframe.fill(0); + + for (auto& elem : subframe) { + elem = static_cast(sinf(SinePos) * std::numeric_limits::max() * 0.2); + SinePos += 0.05f; + } + + auto& channels = *reinterpret_cast*>(JASDsp::CH_BUF); +} diff --git a/src/dusk/audio/DuskDsp.hpp b/src/dusk/audio/DuskDsp.hpp new file mode 100644 index 0000000000..418c285686 --- /dev/null +++ b/src/dusk/audio/DuskDsp.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include "JSystem/JAudio2/JASDSPInterface.h" + +#include + +using DspSubframe = std::array; + +void DuskDspRender(DspSubframe& subframe); From 86898db01c783afc9bb3893397b6511d363f26af Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Thu, 12 Mar 2026 11:56:15 +0100 Subject: [PATCH 018/350] BE JAUSoundTableItem --- libs/JSystem/include/JSystem/JAudio2/JAUSoundTable.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libs/JSystem/include/JSystem/JAudio2/JAUSoundTable.h b/libs/JSystem/include/JSystem/JAudio2/JAUSoundTable.h index 0226a917f7..4bb9985d28 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JAUSoundTable.h +++ b/libs/JSystem/include/JSystem/JAudio2/JAUSoundTable.h @@ -12,9 +12,9 @@ struct JAUSoundTableItem { u8 mPriority; u8 field_0x1; - u16 mResourceId; - u32 field_0x4; - f32 field_0x8; + BE(u16) mResourceId; + BE(u32) field_0x4; + BE(f32) field_0x8; }; /** From f4f27cefc7e8572200e10339a21ab851430aa7a8 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Thu, 12 Mar 2026 12:13:14 +0100 Subject: [PATCH 019/350] BE JASAramStream --- .../include/JSystem/JAudio2/JASAramStream.h | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/libs/JSystem/include/JSystem/JAudio2/JASAramStream.h b/libs/JSystem/include/JSystem/JAudio2/JASAramStream.h index 119145de9a..5c718d9263 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASAramStream.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASAramStream.h @@ -34,28 +34,28 @@ public: }; struct Header { - /* 0x00 */ u32 tag; // 'STRM' + /* 0x00 */ BE(u32) tag; // 'STRM' /* 0x04 */ u8 field_0x4[5]; /* 0x09 */ u8 format; /* 0x0A */ u8 bits; - /* 0x0C */ u16 channels; - /* 0x0E */ u16 loop; - /* 0x10 */ int mSampleRate; - /* 0x14 */ u32 mSampleCount; // unused - /* 0x18 */ int loop_start; - /* 0x1C */ int loop_end; - /* 0x20 */ u32 block_size; + /* 0x0C */ BE(u16) channels; + /* 0x0E */ BE(u16) loop; + /* 0x10 */ BE(int) mSampleRate; + /* 0x14 */ BE(u32) mSampleCount; // unused + /* 0x18 */ BE(int) loop_start; + /* 0x1C */ BE(int) loop_end; + /* 0x20 */ BE(u32) block_size; /* 0x24 */ u8 _unused2[4]; /* 0x28 */ u8 mVolume; /* 0x29 */ u8 _unused3[0x17]; }; // Size: 0x40 struct BlockHeader { - /* 0x00 */ u32 tag; // 'BLCK' - /* 0x04 */ u32 mSize; + /* 0x00 */ BE(u32) tag; // 'BLCK' + /* 0x04 */ BE(u32) mSize; /* 0x08 */ struct { - s16 mpLast; - s16 mpPenult; + BE(s16) mpLast; + BE(s16) mpPenult; } mAdpcmContinuationData[CHANNEL_MAX]; }; // Size: 0x20 From 44260d7b4a28026c4a1c4723981e1470a4c49035 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Thu, 12 Mar 2026 12:24:28 +0100 Subject: [PATCH 020/350] BE JASSeqReader.h --- libs/JSystem/include/JSystem/JAudio2/JASSeqReader.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/JSystem/include/JSystem/JAudio2/JASSeqReader.h b/libs/JSystem/include/JSystem/JAudio2/JASSeqReader.h index 9dce7f56f7..42a34b9dff 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASSeqReader.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASSeqReader.h @@ -41,7 +41,7 @@ public: #ifdef __MWERKS__ return *((u16*)field_0x04)++; #else - u16* value = (u16*)field_0x04; + BE(u16)* value = (BE(u16)*)field_0x04; field_0x04 += 2; return *value; #endif @@ -51,7 +51,7 @@ public: #ifdef __MWERKS__ return (*((u32*)field_0x04)++) & 0x00ffffff; #else - u32* value = (u32*)field_0x04; + BE(u32)* value = (BE(u32)*)field_0x04; field_0x04 += 4; return (*value) & 0x00ffffff; #endif From 0636bbfb222a3401caf5605fbd49a9c42cda81b9 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Thu, 12 Mar 2026 12:38:30 +0100 Subject: [PATCH 021/350] Fix JASAramStream layout --- libs/JSystem/include/JSystem/JAudio2/JASAramStream.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/libs/JSystem/include/JSystem/JAudio2/JASAramStream.h b/libs/JSystem/include/JSystem/JAudio2/JASAramStream.h index 5c718d9263..f095b1b489 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASAramStream.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASAramStream.h @@ -35,9 +35,15 @@ public: struct Header { /* 0x00 */ BE(u32) tag; // 'STRM' +#if TARGET_PC + /* 0x04 */ BE(u32) soundBlockSize; + /* 0x08 */ BE(u16) format; + /* 0x0A */ BE(u16) bits; +#else /* 0x04 */ u8 field_0x4[5]; /* 0x09 */ u8 format; /* 0x0A */ u8 bits; +#endif /* 0x0C */ BE(u16) channels; /* 0x0E */ BE(u16) loop; /* 0x10 */ BE(int) mSampleRate; From 9d88bb5d3fd78b50eac63d5d0a5ab017617e0983 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Thu, 12 Mar 2026 12:46:40 +0100 Subject: [PATCH 022/350] Fix BE in JASAramStream::load --- libs/JSystem/src/JAudio2/JASAramStream.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/JSystem/src/JAudio2/JASAramStream.cpp b/libs/JSystem/src/JAudio2/JASAramStream.cpp index 159df92739..6cbc523c60 100644 --- a/libs/JSystem/src/JAudio2/JASAramStream.cpp +++ b/libs/JSystem/src/JAudio2/JASAramStream.cpp @@ -324,7 +324,7 @@ bool JASAramStream::load() { (void)i; // Fakematch? It seems the only way to get the bhead->field_0x4 load in the right order is // with a pointer cast on its address in one of the two places it is read, but not both. - if (!JKRMainRamToAram(sReadBuffer + *(u32*)&bhead->mSize * i + sizeof(BlockHeader), + if (!JKRMainRamToAram(sReadBuffer + bhead->mSize * i + sizeof(BlockHeader), sp08 + sBlockSize * mBlocksPerChannel * i, bhead->mSize, EXPAND_SWITCH_UNKNOWN0, 0, NULL, -1, NULL)) { JUT_WARN(522, "%s", "JKRMainRamToAram Failed"); From 40e126a35f9acf6fae1cc4404720f290df2d6885 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Thu, 12 Mar 2026 12:46:54 +0100 Subject: [PATCH 023/350] Make JKRAramPiece trap on invalid pointers --- libs/JSystem/src/JKernel/JKRAramPiece.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/libs/JSystem/src/JKernel/JKRAramPiece.cpp b/libs/JSystem/src/JKernel/JKRAramPiece.cpp index f01b530cfe..0946275b83 100644 --- a/libs/JSystem/src/JKernel/JKRAramPiece.cpp +++ b/libs/JSystem/src/JKernel/JKRAramPiece.cpp @@ -27,6 +27,10 @@ JSUList JKRAramPiece::sAramPieceCommandList; OSMutex JKRAramPiece::mMutex; +#if DEBUG && TARGET_PC +volatile u8 forceRead; +#endif + JKRAMCommand* JKRAramPiece::orderAsync(int direction, uintptr_t source, uintptr_t destination, u32 length, JKRAramBlock* block, JKRAMCommand::AsyncCallback callback) { lock(); @@ -45,6 +49,12 @@ JKRAMCommand* JKRAramPiece::orderAsync(int direction, uintptr_t source, uintptr_ JKRAramPiece::prepareCommand(direction, source, destination, length, block, callback); message->setting(1, command); +#if DEBUG && TARGET_PC + if (direction == ARAM_DIR_MRAM_TO_ARAM) { + forceRead = *reinterpret_cast(source); + } +#endif + OSSendMessage(&JKRAram::sMessageQueue, message, OS_MESSAGE_BLOCK); if (command->mCallback != NULL) { sAramPieceCommandList.append(&command->mPieceLink); From 7f4be58b1b41b9d250b0b16a7096ef4530d992d8 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Thu, 12 Mar 2026 13:17:20 +0100 Subject: [PATCH 024/350] Fix JASTrackNodeOffset missing offset --- libs/JSystem/include/JSystem/JAudio2/JASTrack.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/JSystem/include/JSystem/JAudio2/JASTrack.h b/libs/JSystem/include/JSystem/JAudio2/JASTrack.h index 796bb6afd7..b54a98aa77 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASTrack.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASTrack.h @@ -54,7 +54,7 @@ struct JASTrack : public JASPoolAllocObject_MultiThreaded { /* 0x4c */ JASTrack* mTrack; }; - struct TList : JGadget::TLinkList { + struct TList : JGadget::TLinkList { TList() : mCallbackRegistered(false) {} void append(JASTrack*); void seqMain(); From 7b1d88cc2c203760fb391db0b15e750610c17e78 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Thu, 12 Mar 2026 14:08:58 +0100 Subject: [PATCH 025/350] Fix DVD command sizes --- libs/JSystem/src/JAudio2/JASAramStream.cpp | 6 +++--- libs/JSystem/src/JAudio2/JASWaveArcLoader.cpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/libs/JSystem/src/JAudio2/JASAramStream.cpp b/libs/JSystem/src/JAudio2/JASAramStream.cpp index 6cbc523c60..544a26ac99 100644 --- a/libs/JSystem/src/JAudio2/JASAramStream.cpp +++ b/libs/JSystem/src/JAudio2/JASAramStream.cpp @@ -126,7 +126,7 @@ bool JASAramStream::prepare(s32 param_0, int param_1) { data.stream = this; data.field_0x4 = field_0x14c; data.field_0x8 = param_1; - if (!sLoadThread->sendCmdMsg(headerLoadTask, &data, 0xc)) { + if (!sLoadThread->sendCmdMsg(headerLoadTask, &data, sizeof(data))) { JUT_WARN(254, "%s", "sendCmdMsg headerLoadTask Failed"); JASDriver::rejectCallback(channelProcCallback, this); return false; @@ -194,7 +194,7 @@ void JASAramStream::firstLoadTask(void* i_data) { } if (data->field_0x4 != 0) { data->field_0x4--; - if (!sLoadThread->sendCmdMsg(firstLoadTask, data, 0xc)) { + if (!sLoadThread->sendCmdMsg(firstLoadTask, data, sizeof(*data))) { JUT_WARN(372, "%s", "sendCmdMsg firstLoadTask Failed"); struct_80451261 = true; } @@ -277,7 +277,7 @@ bool JASAramStream::headerLoad(u32 param_0, int param_1) { data.stream = this; data.field_0x4 = field_0x108 - 1; data.field_0x8 = param_1; - if (!sLoadThread->sendCmdMsg(firstLoadTask, &data, 0xc)) { + if (!sLoadThread->sendCmdMsg(firstLoadTask, &data, sizeof(data))) { JUT_WARN(472, "%s", "sendCmdMsg firstLoadTask Failed"); struct_80451261 = true; return false; diff --git a/libs/JSystem/src/JAudio2/JASWaveArcLoader.cpp b/libs/JSystem/src/JAudio2/JASWaveArcLoader.cpp index 54018a9e25..d14ff70298 100644 --- a/libs/JSystem/src/JAudio2/JASWaveArcLoader.cpp +++ b/libs/JSystem/src/JAudio2/JASWaveArcLoader.cpp @@ -99,7 +99,7 @@ bool JASWaveArc::sendLoadCmd() { _5a++; - if (JASDvd::getThreadPointer()->sendCmdMsg(loadToAramCallback, &commandInfo, 0x10) == 0) { + if (JASDvd::getThreadPointer()->sendCmdMsg(loadToAramCallback, &commandInfo, sizeof(commandInfo)) == 0) { JUT_WARN(193, "%s", "sendCmdMsg loadToAramCallback Failed"); mHeap.free(); return false; From 1b8f4677deb61563a64d362e84867b97c4416ea0 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Thu, 12 Mar 2026 14:09:39 +0100 Subject: [PATCH 026/350] Some more names --- .../include/JSystem/JAudio2/JASAramStream.h | 4 +- .../JSystem/JAudio2/JAUStreamAramMgr.h | 4 +- libs/JSystem/src/JAudio2/JAIStream.cpp | 6 +- libs/JSystem/src/JAudio2/JASAramStream.cpp | 72 +++++++++---------- 4 files changed, 43 insertions(+), 43 deletions(-) diff --git a/libs/JSystem/include/JSystem/JAudio2/JASAramStream.h b/libs/JSystem/include/JSystem/JAudio2/JASAramStream.h index f095b1b489..dc9dea9645 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASAramStream.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASAramStream.h @@ -166,13 +166,13 @@ public: /* 0x130 */ s16 mpLasts[CHANNEL_MAX]; /* 0x13C */ s16 mpPenults[CHANNEL_MAX]; /* 0x148 */ int mAramAddress; - /* 0x14C */ u32 field_0x14c; + /* 0x14C */ u32 mAramSize; /* 0x150 */ StreamCallback mCallback; /* 0x154 */ void* mCallbackData; /* 0x158 */ u16 mFormat; /* 0x15A */ u16 mChannelNum; /* 0x15C */ u32 mBufCount; - /* 0x160 */ u32 mBlocksPerChannel; + /* 0x160 */ u32 mAramBlocksPerChannel; /* 0x164 */ u32 mSampleRate; /* 0x168 */ bool mLoop; /* 0x16C */ u32 mLoopStart; diff --git a/libs/JSystem/include/JSystem/JAudio2/JAUStreamAramMgr.h b/libs/JSystem/include/JSystem/JAudio2/JAUStreamAramMgr.h index bf861b495d..f1cfe8f7f6 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JAUStreamAramMgr.h +++ b/libs/JSystem/include/JSystem/JAudio2/JAUStreamAramMgr.h @@ -48,13 +48,13 @@ template class JAUStreamStaticAramMgr_ : public JAUStreamAramMgrBase_ { public: JAUStreamStaticAramMgr_() { field_0x4c = 0; } - virtual void* newStreamAram(u32* param_0) { + virtual void* newStreamAram(u32* size) { for (u32 i = 0; i < field_0x4c; i++) { if (this->field_0x4.test(i)) { continue; } this->field_0x4.set(i, true); - *param_0 = this->mHeaps[i].getSize(); + *size = this->mHeaps[i].getSize(); return this->mHeaps[i].getBase(); } return NULL; diff --git a/libs/JSystem/src/JAudio2/JAIStream.cpp b/libs/JSystem/src/JAudio2/JAIStream.cpp index 72f37ec248..5799080b12 100644 --- a/libs/JSystem/src/JAudio2/JAIStream.cpp +++ b/libs/JSystem/src/JAudio2/JAIStream.cpp @@ -48,7 +48,7 @@ void JAIStream::JAIStreamMgr_startID_(JAISoundID id, s32 streamFileEntry, } bool JAIStream::prepare_prepareStream_() { - u32 local_28; + u32 size; JAIStreamAramMgr* streamAramMgr; switch (field_0x290) { @@ -56,9 +56,9 @@ bool JAIStream::prepare_prepareStream_() { streamAramMgr = streamMgr_->getStreamAramMgr(); JUT_ASSERT(100, streamAramMgr); - streamAramAddr_ = streamAramMgr->newStreamAram(&local_28); + streamAramAddr_ = streamAramMgr->newStreamAram(&size); if (streamAramAddr_ != NULL) { - inner_.aramStream_.init((uintptr_t)streamAramAddr_, local_28, &JAIStream_JASAramStreamCallback_, this); + inner_.aramStream_.init((uintptr_t)streamAramAddr_, size, &JAIStream_JASAramStreamCallback_, this); field_0x290 = 1; prepareCount_ = 0; } else { diff --git a/libs/JSystem/src/JAudio2/JASAramStream.cpp b/libs/JSystem/src/JAudio2/JASAramStream.cpp index 544a26ac99..77de8e17f7 100644 --- a/libs/JSystem/src/JAudio2/JASAramStream.cpp +++ b/libs/JSystem/src/JAudio2/JASAramStream.cpp @@ -19,8 +19,8 @@ u32 JASAramStream::sBlockSize; u32 JASAramStream::sChannelMax; -bool struct_80451260; -bool struct_80451261; +bool dvdHasErrored; +bool hasErrored; void JASAramStream::initSystem(u32 block_size, u32 channel_max) { JUT_ASSERT(66, block_size % 32 == 0); @@ -37,8 +37,8 @@ void JASAramStream::initSystem(u32 block_size, u32 channel_max) { JUT_ASSERT(79, sReadBuffer); sBlockSize = block_size; sChannelMax = channel_max; - struct_80451260 = false; - struct_80451261 = false; + dvdHasErrored = false; + hasErrored = false; } } @@ -61,13 +61,13 @@ JASAramStream::JASAramStream() { field_0x118 = 0; field_0x12c = 0; mAramAddress = 0; - field_0x14c = 0; + mAramSize = 0; mCallback = NULL; mCallbackData = NULL; mFormat = 0; mChannelNum = 0; mBufCount = 0; - mBlocksPerChannel = 0; + mAramBlocksPerChannel = 0; mSampleRate = 0; mLoop = false; mLoopStart = 0; @@ -88,10 +88,10 @@ JASAramStream::JASAramStream() { } } -void JASAramStream::init(u32 aramAddress, u32 param_1, StreamCallback i_callback, void* i_callbackData) { +void JASAramStream::init(u32 aramAddress, u32 aramSize, StreamCallback i_callback, void* i_callbackData) { JUT_ASSERT(153, sReadBuffer != NULL); mAramAddress = aramAddress; - field_0x14c = param_1; + mAramSize = aramSize; field_0x0c8 = 0.0f; field_0x0ae = 0; field_0x0ac = false; @@ -124,7 +124,7 @@ bool JASAramStream::prepare(s32 param_0, int param_1) { } TaskData data; data.stream = this; - data.field_0x4 = field_0x14c; + data.field_0x4 = mAramSize; data.field_0x8 = param_1; if (!sLoadThread->sendCmdMsg(headerLoadTask, &data, sizeof(data))) { JUT_WARN(254, "%s", "sendCmdMsg headerLoadTask Failed"); @@ -188,7 +188,7 @@ void JASAramStream::firstLoadTask(void* i_data) { if (data->field_0x8 == 0) { if (!sLoadThread->sendCmdMsg(prepareFinishTask, _this)) { JUT_WARN(364, "%s", "sendCmdMsg prepareFinishTask Failed"); - struct_80451261 = true; + hasErrored = true; } } } @@ -196,7 +196,7 @@ void JASAramStream::firstLoadTask(void* i_data) { data->field_0x4--; if (!sLoadThread->sendCmdMsg(firstLoadTask, data, sizeof(*data))) { JUT_WARN(372, "%s", "sendCmdMsg firstLoadTask Failed"); - struct_80451261 = true; + hasErrored = true; } JASCriticalSection cs; _this->field_0x118++; @@ -227,8 +227,8 @@ void JASAramStream::prepareFinishTask(void* i_this) { } } -bool JASAramStream::headerLoad(u32 param_0, int param_1) { - if (struct_80451261) { +bool JASAramStream::headerLoad(u32 aramSize, int param_1) { + if (hasErrored) { return false; } if (field_0x114 != 0) { @@ -236,7 +236,7 @@ bool JASAramStream::headerLoad(u32 param_0, int param_1) { } if (DVDReadPrio(&mDvdFileInfo, sReadBuffer, sizeof(Header), 0, 1) < 0) { JUT_WARN(420, "%s", "DVDReadPrio Failed"); - struct_80451261 = true; + hasErrored = true; return false; } Header* header = (Header*)sReadBuffer; @@ -255,8 +255,8 @@ bool JASAramStream::headerLoad(u32 param_0, int param_1) { field_0x118 = 0; mBlock = 0; field_0x10c = 0; - mBlocksPerChannel = (param_0 / sBlockSize) / header->channels; - mBufCount = mBlocksPerChannel; + mAramBlocksPerChannel = (aramSize / sBlockSize) / header->channels; + mBufCount = mAramBlocksPerChannel; JUT_ASSERT(445, mBufCount > 0); mBufCount--; if (mBufCount < 3) { @@ -279,7 +279,7 @@ bool JASAramStream::headerLoad(u32 param_0, int param_1) { data.field_0x8 = param_1; if (!sLoadThread->sendCmdMsg(firstLoadTask, &data, sizeof(data))) { JUT_WARN(472, "%s", "sendCmdMsg firstLoadTask Failed"); - struct_80451261 = true; + hasErrored = true; return false; } JASCriticalSection cs; @@ -293,7 +293,7 @@ bool JASAramStream::load() { JASCriticalSection cs; field_0x118--; } - if (struct_80451261) { + if (hasErrored) { return false; } if (field_0x114 != 0) { @@ -311,7 +311,7 @@ bool JASAramStream::load() { } if (DVDReadPrio(&mDvdFileInfo, sReadBuffer, size, offset, 1) < 0) { JUT_WARN(507, "%s", "DVDReadPrio Failed"); - struct_80451261 = true; + hasErrored = true; return false; } BlockHeader* bhead = (BlockHeader*)sReadBuffer; @@ -325,10 +325,10 @@ bool JASAramStream::load() { // Fakematch? It seems the only way to get the bhead->field_0x4 load in the right order is // with a pointer cast on its address in one of the two places it is read, but not both. if (!JKRMainRamToAram(sReadBuffer + bhead->mSize * i + sizeof(BlockHeader), - sp08 + sBlockSize * mBlocksPerChannel * i, + sp08 + sBlockSize * mAramBlocksPerChannel * i, bhead->mSize, EXPAND_SWITCH_UNKNOWN0, 0, NULL, -1, NULL)) { JUT_WARN(522, "%s", "JKRMainRamToAram Failed"); - struct_80451261 = 1; + hasErrored = 1; return false; } } @@ -344,10 +344,10 @@ bool JASAramStream::load() { } } if (r28 == loop_end_block || r28 + 2 == loop_end_block) { - field_0x108 = mBlocksPerChannel; + field_0x108 = mAramBlocksPerChannel; OSSendMessage(&field_0x020, (OSMessage)5, OS_MESSAGE_BLOCK); } else { - field_0x108 = mBlocksPerChannel - 1; + field_0x108 = mAramBlocksPerChannel - 1; } for (int i = 0; i < mChannelNum; i++) { mpLasts[i] = (s16)bhead->mAdpcmContinuationData[i].mpLast; @@ -371,7 +371,7 @@ s32 JASAramStream::dvdErrorCheck(void* param_0) { s32 status = DVDGetDriveStatus(); switch (status) { case DVD_STATE_END: - struct_80451260 = false; + dvdHasErrored = false; break; case DVD_STATE_BUSY: break; @@ -385,7 +385,7 @@ s32 JASAramStream::dvdErrorCheck(void* param_0) { case DVD_STATE_RETRY: case DVD_STATE_FATAL_ERROR: default: - struct_80451260 = true; + dvdHasErrored = true; break; } return 0; @@ -441,7 +441,7 @@ void JASAramStream::updateChannel(u32 i_callbackType, JASChannel* i_channel, } if (field_0x0b8 > mLoopEnd) { JUT_WARN(686, "%s", "mReadSample > mLoopEnd"); - struct_80451261 = true; + hasErrored = true; } f32 fvar1 = field_0x0c4; fvar1 *= mLoopEnd - mLoopStart; @@ -483,7 +483,7 @@ void JASAramStream::updateChannel(u32 i_callbackType, JASChannel* i_channel, while (blockCount != field_0x0b0) { if (!sLoadThread->sendCmdMsg(loadToAramTask, this)) { JUT_WARN(741, "sendCmdMsg Failed %d %d (%d %d)", i_dspChannel->mAramStreamPosition, i_channel->mWaveAramAddress, blockCount, field_0x0b0); - struct_80451261 = true; + hasErrored = true; break; } { @@ -506,11 +506,11 @@ void JASAramStream::updateChannel(u32 i_callbackType, JASChannel* i_channel, i_dspChannel->mEndSample += block_samples; field_0x124 = i_dspChannel->mEndSample; field_0x12c |= 4; - mBufCount = mBlocksPerChannel; + mBufCount = mAramBlocksPerChannel; field_0x0ad = false; } else { - if (mBufCount != mBlocksPerChannel - 1) { - mBufCount = mBlocksPerChannel - 1; + if (mBufCount != mAramBlocksPerChannel - 1) { + mBufCount = mAramBlocksPerChannel - 1; i_dspChannel->mEndSample -= block_samples; field_0x124 = i_dspChannel->mEndSample; field_0x12c |= 4; @@ -523,13 +523,13 @@ void JASAramStream::updateChannel(u32 i_callbackType, JASChannel* i_channel, } } } else { - if (field_0x118 == 0 && !struct_80451260) { + if (field_0x118 == 0 && !dvdHasErrored) { field_0x0ae &= ~2; field_0x0ae &= ~4; } } field_0x0b4 = i_dspChannel->field_0x074 + i_dspChannel->mSamplesPerBlock; - if (field_0x118 >= mBlocksPerChannel - 2) { + if (field_0x118 >= mAramBlocksPerChannel - 2) { JUT_WARN_DEVICE(810, 1, "%s", "buffer under error"); field_0x0ae |= (u8)4; } @@ -571,7 +571,7 @@ void JASAramStream::updateChannel(u32 i_callbackType, JASChannel* i_channel, field_0x114 = 1; if (!sLoadThread->sendCmdMsg(finishTask, this)) { JUT_WARN(854, "%s", "sendCmdMsg finishTask Failed"); - struct_80451261 = true; + hasErrored = true; return; } } @@ -614,10 +614,10 @@ s32 JASAramStream::channelProc() { } } - if (struct_80451261) { + if (hasErrored) { field_0x0ae |= 8; } - if (struct_80451260) { + if (dvdHasErrored) { field_0x0ae |= 2; } @@ -676,7 +676,7 @@ void JASAramStream::channelStart() { jc->setInitPitch(mSampleRate / JASDriver::getDacRate()); jc->setOscInit(0, &OSC_ENV); jc->field_0xdc.mWaveInfo = wave_info; - jc->mWaveAramAddress = mAramAddress + sBlockSize * mBlocksPerChannel * i; + jc->mWaveAramAddress = mAramAddress + sBlockSize * mAramBlocksPerChannel * i; jc->field_0xdc.mChannelType = 0; int ret = jc->playForce(); JUT_ASSERT(977, ret); From d41225980ace7dbebb0e351f8e8bf0fb94a4a936 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Thu, 12 Mar 2026 14:46:18 +0100 Subject: [PATCH 027/350] Fix incorrect JASChannel size for JASPoolAllocObject_MultiThreaded Despair --- libs/JSystem/src/JAudio2/JASAudioThread.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libs/JSystem/src/JAudio2/JASAudioThread.cpp b/libs/JSystem/src/JAudio2/JASAudioThread.cpp index c978e62aeb..943a3e5cfe 100644 --- a/libs/JSystem/src/JAudio2/JASAudioThread.cpp +++ b/libs/JSystem/src/JAudio2/JASAudioThread.cpp @@ -55,9 +55,14 @@ private: BOOL mInterrupts; }; +#if !TARGET_PC class JASChannel { u8 filler[0x108]; }; +#else +// You don't want to know how long I spent debugging this. +#include "JSystem/JAudio2/JASChannel.h" +#endif // NONMATCHING location of JASPoolAllocObject_MultiThreaded void* JASAudioThread::run() { From 67a159eeef295f911a0eb7c836979c4ea9ecba9c Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Thu, 12 Mar 2026 14:46:44 +0100 Subject: [PATCH 028/350] Make JASMemPool_MultiThreaded use ObjectLevelLockable Shrug I wrote this in despair debugging the previous issue. Can't hurt I guess. --- .../include/JSystem/JAudio2/JASHeapCtrl.h | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/libs/JSystem/include/JSystem/JAudio2/JASHeapCtrl.h b/libs/JSystem/include/JSystem/JAudio2/JASHeapCtrl.h index 70f63bc889..8e1381cda3 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASHeapCtrl.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASHeapCtrl.h @@ -352,6 +352,28 @@ template JASMemPool JASPoolAllocObject::memPool_; template class JASMemPool_MultiThreaded : public JASGenericMemPool { public: +#if TARGET_PC + OSMutex mutex; + + JASMemPool_MultiThreaded() { + OSInitMutex(&mutex); + } + + void newMemPool(int param_0) { + JASThreadingModel::ObjectLevelLockable::Lock lock(mutex); + JASGenericMemPool::newMemPool(sizeof(T), param_0); + } + + void* alloc(size_t count) { + JASThreadingModel::ObjectLevelLockable::Lock lock(mutex); + return JASGenericMemPool::alloc(count); + } + + void free(void* ptr, u32 param_1) { + JASThreadingModel::ObjectLevelLockable::Lock lock(mutex); + JASGenericMemPool::free(ptr, param_1); + } +#else void newMemPool(int param_0) { typename JASThreadingModel::InterruptsDisable >::Lock lock(*this); JASGenericMemPool::newMemPool(sizeof(T), param_0); @@ -366,6 +388,7 @@ public: typename JASThreadingModel::InterruptsDisable >::Lock lock(*this); JASGenericMemPool::free(ptr, param_1); } +#endif }; /** From 2a09783153e1bd6af63f653de74b037b8cfc7d7e Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sat, 14 Mar 2026 11:21:16 +0100 Subject: [PATCH 029/350] Fix another JASTaskThread::allocCallStack size --- libs/JSystem/src/JAudio2/JASTaskThread.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/JSystem/src/JAudio2/JASTaskThread.cpp b/libs/JSystem/src/JAudio2/JASTaskThread.cpp index 1d031671b4..365424deb4 100644 --- a/libs/JSystem/src/JAudio2/JASTaskThread.cpp +++ b/libs/JSystem/src/JAudio2/JASTaskThread.cpp @@ -40,7 +40,7 @@ void* JASTaskThread::allocCallStack(JASThreadCallback callback, const void* msg, void* JASTaskThread::allocCallStack(JASThreadCallback callback, void* msg) { JASThreadCallStack *callStack; - callStack = (JASThreadCallStack*)JASKernel::getCommandHeap()->alloc(12); + callStack = (JASThreadCallStack*)JASKernel::getCommandHeap()->alloc(offsetof(JASThreadCallStack, msg) + sizeof(void*)); if (callStack == NULL) { return NULL; } From 4c2d0064a2636a9c7a4459f0a8bbbc588a3d9238 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sat, 14 Mar 2026 11:21:23 +0100 Subject: [PATCH 030/350] Add CRASH() macro --- include/global.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/global.h b/include/global.h index 375b92de1a..94a2c9dc6e 100644 --- a/include/global.h +++ b/include/global.h @@ -200,4 +200,6 @@ static const float INF = 2000000000.0f; using std::isnan; #endif +#define CRASH(msg, ...) OSPanic(__FILE__, __LINE__, msg, __VA_ARGS__) + #endif From e2fb0e3a70dc562c8e39a092d9910c3f2fa603e7 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sat, 14 Mar 2026 12:19:40 +0100 Subject: [PATCH 031/350] ADPCM4 decode impl --- CMakeLists.txt | 4 ++- src/dusk/audio/Adpcm.cpp | 60 ++++++++++++++++++++++++++++++++++++++++ src/dusk/audio/Adpcm.hpp | 14 ++++++++++ 3 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 src/dusk/audio/Adpcm.cpp create mode 100644 src/dusk/audio/Adpcm.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index ceebbc1818..25b78d60b3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -87,7 +87,9 @@ add_library(game_debug STATIC ${JSYSTEM_DEBUG_FILES} ${SSYSTEM_FILES} src/dusk/audio/DuskAudioSystem.cpp src/dusk/audio/JASCriticalSection.cpp src/dusk/audio/DuskDsp.hpp - src/dusk/audio/DuskDsp.cpp) + src/dusk/audio/DuskDsp.cpp + src/dusk/audio/Adpcm.cpp + src/dusk/audio/Adpcm.hpp) target_compile_definitions(game_debug PRIVATE TARGET_PC AVOID_UB=1 VERSION=0 $<$:DEBUG=1>) # Make these properties PUBLIC so that the regular game target also sees them. diff --git a/src/dusk/audio/Adpcm.cpp b/src/dusk/audio/Adpcm.cpp new file mode 100644 index 0000000000..f4318d5d59 --- /dev/null +++ b/src/dusk/audio/Adpcm.cpp @@ -0,0 +1,60 @@ +#include "Adpcm.hpp" + +#include +#include + +#include "JSystem/JAudio2/JASAramStream.h" + +// https://github.com/magcius/vgmtrans/blob/8e34ddc2fb43948dc1e1a8759c739a0c1c7b62d7/src/main/formats/JaiSeqScanner.cpp#L489-L531 +// https://github.com/XAYRGA/JaiSeqX/blob/f29c024ec3663503f506aa02bcd503ada6e7d8aa/JaiSeqXLJA/DSP/JAIDSPADPCM4.cs#L86-L87 + +static constexpr u16 Coefficient0[] = { + 0,0x0800,0,0x0400, + 0x1000,0x0e00,0x0c00,0x1200, + 0x1068,0x12c0,0x1400,0x0800, + 0x0400,0xfc00,0xfc00,0xf800 +}; + +static constexpr u16 Coefficient1[] = { + 0,0,0x0800,0x0400,0xf800, + 0xfa00,0xfc00,0xf600,0xf738, + 0xf704,0xf400,0xf800,0xfc00, + 0x0400,0,0, +}; + +constexpr int AdpcmFrameSize = 9; + +static s16 Clamp16(s32 value) { + if (value > 0x7FFF) return 0x7FFF; + if (value < -0x8000) return -0x8000; + return value; +} + +void dusk::audio::Adpcm4ToPcm16(const u8* adpcm, size_t adpcmLength, s16* pcm, size_t pcmLength, s16& hist2, s16& hist1) { + assert (adpcmLength % AdpcmFrameSize == 0 && "ADPCM must be divisible by frame size"); + + const auto expectedPcmLength = (adpcmLength / AdpcmFrameSize) * 16; + assert (expectedPcmLength <= pcmLength && "PCM output buffer is too small!"); + + for (int i = 0; i < adpcmLength; i += AdpcmFrameSize) { + u8 header = adpcm[i]; + + s32 scale = 1 << (header >> 4); + u8 coefIndex = header & 0xF; + + s16 coef0 = (s16)Coefficient0[coefIndex]; + s16 coef1 = (s16)Coefficient1[coefIndex]; + + for (int sampleIdx = 0; sampleIdx < 16; sampleIdx++) { + u8 adpcmValue = adpcm[i + 1 + sampleIdx / 2]; + u8 unsignedNibble = sampleIdx % 2 == 0 ? adpcmValue >> 4 : adpcmValue & 0xF; + s8 signedNibble = ((s8)(unsignedNibble << 4)) >> 4; + s16 sample = Clamp16((((signedNibble * scale) << 11) + (coef0 * hist1 + coef1 * hist2)) >> 11); + + hist2 = hist1; + hist1 = sample; + + *pcm++ = sample; + } + } +} diff --git a/src/dusk/audio/Adpcm.hpp b/src/dusk/audio/Adpcm.hpp new file mode 100644 index 0000000000..e893d54791 --- /dev/null +++ b/src/dusk/audio/Adpcm.hpp @@ -0,0 +1,14 @@ +#ifndef DUSK_ADPCM_HPP +#define DUSK_ADPCM_HPP + +#include + +namespace dusk::audio { + constexpr u32 Adpcm4FrameSize = 9; + constexpr u32 AdpcmSampleCount = 16; + constexpr u32 Adpcm2FrameSize = 5; + + void Adpcm4ToPcm16(const u8* adpcm, size_t adpcmLength, s16* pcm, size_t pcmLength, s16& hist1, s16& hist0); +} + +#endif // DUSK_ADPCM_HPP From 2ee0b8a27ce48fb37f08d77d838af8e6fcfb5f74 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sat, 14 Mar 2026 12:20:02 +0100 Subject: [PATCH 032/350] More documenting for JASAramStream and JASDSPInterface --- .../include/JSystem/JAudio2/JASAramStream.h | 131 +++++-- .../include/JSystem/JAudio2/JASDSPInterface.h | 42 ++- libs/JSystem/src/JAudio2/JASAramStream.cpp | 333 ++++++++++-------- libs/JSystem/src/JAudio2/JASDSPInterface.cpp | 6 +- 4 files changed, 337 insertions(+), 175 deletions(-) diff --git a/libs/JSystem/include/JSystem/JAudio2/JASAramStream.h b/libs/JSystem/include/JSystem/JAudio2/JASAramStream.h index dc9dea9645..dcfcebd30a 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASAramStream.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASAramStream.h @@ -4,6 +4,7 @@ #include "JSystem/JAudio2/JASTaskThread.h" #include "JSystem/JUtility/JUTAssert.h" #include +#include "dusk/endian.h" class JASChannel; @@ -11,9 +12,12 @@ namespace JASDsp { struct TChannel; } +#define STREAM_FORMAT_ADPCM4 0 +#define STREAM_FORMAT_PCM16 1 + /** * @ingroup jsystem-jaudio - * + * Plays streamed music from DVD .ast files. */ class JASAramStream { public: @@ -29,8 +33,8 @@ public: // Used internally for passing data to task functions struct TaskData { /* 0x0 */ JASAramStream* stream; - /* 0x4 */ u32 field_0x4; - /* 0x8 */ int field_0x8; + /* 0x4 */ u32 param0; + /* 0x8 */ int param1; }; struct Header { @@ -73,6 +77,10 @@ public: bool stop(u16); bool pause(bool); bool cancel(); + + /** + * Calculate the amount of (decoded) audio samples in a single block of streamed audio. + */ u32 getBlockSamples() const; static void headerLoadTask(void*); static void firstLoadTask(void*); @@ -136,33 +144,92 @@ public: static u32 getBlockSize() { return sBlockSize; } - /* 0x000 */ OSMessageQueue field_0x000; - /* 0x020 */ OSMessageQueue field_0x020; - /* 0x040 */ void* field_0x040[16]; - /* 0x080 */ void* field_0x080[4]; + /** + * Queue used to send specific commands that will be processed on the audio thread. + * These commands are sent from the main thread. + */ + /* 0x000 */ OSMessageQueue mMainCommandQueue; + + /** + * Queue used to send specific commands that will be processed on the audio thread. + * These commands are sent from the load (DVD) thread. + */ + /* 0x020 */ OSMessageQueue mLoadCommandQueue; + + /** + * Backing message storage for mMainCommandQueue. + */ + /* 0x040 */ void* mMainCommandQueueArray[16]; + + /** + * Backing message storage for mLoadCommandQueue. + */ + /* 0x080 */ void* mLoadCommandQueueArray[4]; /* 0x090 */ JASChannel* mChannels[CHANNEL_MAX]; - /* 0x0A8 */ JASChannel* mInitialChannel; - /* 0x0AC */ bool field_0x0ac; - /* 0x0AD */ bool field_0x0ad; - /* 0x0AE */ u8 field_0x0ae; + + /** + * The first audio channel initialized among mChannels. + * Used for the majority of bookkeeping, other channels replicate its state. + */ + /* 0x0A8 */ JASChannel* mPrimaryChannel; + + /** + * If true, stream has finished preparing (reading headers and initial blocks), + * and is ready to play. + */ + /* 0x0AC */ bool mPrepareFinished; + /* 0x0AD */ bool mLoopEndLoaded; + + /** + * Bitflag containing pause reasons/state for the stream. + */ + /* 0x0AE */ u8 mPauseFlags; /* 0x0B0 */ int field_0x0b0; - /* 0x0B4 */ int field_0x0b4; - /* 0x0B8 */ u32 field_0x0b8; + + /** + * (adjusted) value of mSamplesLeft on the primary channel last subframe. + * Used to calculate how many samples have been read and determine when the DSP looped. + */ + /* 0x0B4 */ int mLastSamplesLeft; + + /** + * How many (decoded) samples the DSP has read so far. + */ + /* 0x0B8 */ u32 mReadSample; /* 0x0BC */ int field_0x0bc; - /* 0x0C0 */ bool field_0x0c0; + + /** + * If true, the current end (of loop, or just finish) is very close. + * Loop start/end positions are modified while this is set to account for this. + */ + /* 0x0C0 */ bool mEndSetup; /* 0x0C4 */ volatile u32 field_0x0c4; /* 0x0C8 */ volatile f32 field_0x0c8; /* 0x0CC */ DVDFileInfo mDvdFileInfo; - /* 0x108 */ u32 field_0x108; - /* 0x10C */ int field_0x10c; + /* 0x108 */ u32 mRingEndIndex; + + /** + * Index into the ARAM ring buffer that is currently being loaded. + * Wrapped around when incremented. + */ + /* 0x10C */ int mBlockRingIndex; + + /** + * Block currently being loaded. + */ /* 0x110 */ u32 mBlock; - /* 0x114 */ u8 field_0x114; - /* 0x118 */ u32 field_0x118; - /* 0x11C */ int field_0x11c; - /* 0x120 */ int field_0x120; - /* 0x124 */ int field_0x124; - /* 0x128 */ u16 field_0x128; - /* 0x12C */ int field_0x12c; + /* 0x114 */ u8 mIsCancelled; + /* 0x118 */ u32 mPendingLoadTasks; + /* 0x11C */ int mUpdateSamplesLeft; + /* 0x120 */ int mUpdateLoopStartSample; + /* 0x124 */ int mUpdateEndSample; + /* 0x128 */ u16 mUpdateLoopFlag; + + /** + * Bitflags updated in the play callback to track what data needs to be synchronized + * between all channels. + */ + /* 0x12C */ int mChannelUpdateFlags; /* 0x130 */ s16 mpLasts[CHANNEL_MAX]; /* 0x13C */ s16 mpPenults[CHANNEL_MAX]; /* 0x148 */ int mAramAddress; @@ -185,9 +252,27 @@ public: /* 0x1C4 */ f32 mChannelDolby[CHANNEL_MAX]; /* 0x1DC */ u16 mMixConfig[CHANNEL_MAX]; + /** + * Thread that will be sent DVD load commands. + * This is the JASDvd thread in practice. + */ static JASTaskThread* sLoadThread; + + /** + * Buffer used to read DVD data. Can store the size of an entire streamed audio block. + */ static u8* sReadBuffer; + + /** + * Block size used by all streamed music in the game. + * This is 0x2760 for TP. + */ static u32 sBlockSize; + + /** + * Maximum amount of output channels for all streamed music in the game. + * This is 2 for TP (stereo). + */ static u32 sChannelMax; }; diff --git a/libs/JSystem/include/JSystem/JAudio2/JASDSPInterface.h b/libs/JSystem/include/JSystem/JAudio2/JASDSPInterface.h index 9bb9eecb16..8f1ca3dd72 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASDSPInterface.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASDSPInterface.h @@ -94,11 +94,29 @@ namespace JASDsp { void setDistFilter(s16); void setBusConnect(u8 outputChannel, u8 param_1); + /** + * Whether this channel is currently actively playing audio. + */ /* 0x000 */ u16 mIsActive; + + /** + * Written by DSP to indicate playback has finished. + */ /* 0x002 */ u16 mIsFinished; + + /** + * Pitch shift via changing playback speed. + */ /* 0x004 */ u16 mPitch; /* 0x006 */ short _unused1; - /* 0x008 */ u16 field_0x008; + + /** + * Set to 1 when playback starts, cleared by DSP later, + * checked by JASAramStream before actually doing processing. + * Presumably to instruct DSP to clear state? + * (Corroborated by fields JASAramStream checks never being cleared explicitly by CPU.) + */ + /* 0x008 */ u16 mResetFlag; /* 0x00A */ u8 _unused2[0x00C - 0x00A]; /* 0x00C */ s16 mPauseFlag; /* 0x00E */ short _unused3; @@ -112,19 +130,33 @@ namespace JASDsp { /* 0x05A */ u8 _unused5[0x060 - 0x05A]; /* 0x060 */ short field_0x060; // Only cleared to zero, presumed used by DSP. /* 0x062 */ u8 _unused6[0x064 - 0x062]; + + /** + * Samples per ADPCM frame for ADPCM audio. Seems just set to 1 for PCM formats. + * Name could use improvement, probably? + */ /* 0x064 */ u16 mSamplesPerBlock; /* 0x066 */ short field_0x066; // Only cleared to zero, presumed used by DSP. - /* 0x068 */ int mSamplePosition; // Only ever initialized by code, name is guess. + /* 0x068 */ u32 mSamplePosition; // Only ever initialized by code, name is guess. /* 0x06C */ u8 _unused7[0x070 - 0x06C]; - /* 0x070 */ int mAramStreamPosition; // Seems written by DSP, used for audio streaming. - /* 0x074 */ int field_0x074; + + /** + * Current audio read position in ARAM. Updated by DSP. + */ + /* 0x070 */ u32 mAramStreamPosition; + + /** + * Amount of (decoded) audio samples left until the end of the buffer. + * Gets written by DSP, but also CPU. + */ + /* 0x074 */ u32 mSamplesLeft; // Never directly cleared to zero. Seems sus. Cleared by DSP? /* 0x078 */ short field_0x078[4]; // Only cleared to zero, presumed used by DSP. /* 0x080 */ short field_0x080[20]; // Only cleared to zero, presumed used by DSP. /* 0x0A8 */ short field_0x0a8[4]; // Only cleared to zero, presumed used by DSP. /* 0x0B0 */ u16 field_0x0b0[16]; // Only cleared to zero, presumed used by DSP. /* 0x0D0 */ u8 _unused8[0x100 - 0x0D0]; /* 0x100 */ u16 mBytesPerBlock; - /* 0x102 */ u16 field_0x102; + /* 0x102 */ u16 mLoopFlag; /** * Used for decoding ADPCM data around loop edges. diff --git a/libs/JSystem/src/JAudio2/JASAramStream.cpp b/libs/JSystem/src/JAudio2/JASAramStream.cpp index 77de8e17f7..30d4d8da7a 100644 --- a/libs/JSystem/src/JAudio2/JASAramStream.cpp +++ b/libs/JSystem/src/JAudio2/JASAramStream.cpp @@ -1,11 +1,12 @@ #include "JSystem/JSystem.h" // IWYU pragma: keep -#include "JSystem/JAudio2/JASAramStream.h" #include "JSystem/JAudio2/JASAiCtrl.h" +#include "JSystem/JAudio2/JASAramStream.h" +#include "JSystem/JAudio2/JASAudioThread.h" #include "JSystem/JAudio2/JASChannel.h" #include "JSystem/JAudio2/JASCriticalSection.h" -#include "JSystem/JAudio2/JASDriverIF.h" #include "JSystem/JAudio2/JASDSPInterface.h" +#include "JSystem/JAudio2/JASDriverIF.h" #include "JSystem/JAudio2/JASDvdThread.h" #include "JSystem/JKernel/JKRAram.h" #include "JSystem/JKernel/JKRSolidHeap.h" @@ -22,6 +23,21 @@ u32 JASAramStream::sChannelMax; bool dvdHasErrored; bool hasErrored; +#define PAUSE_REQUESTED 1 +#define PAUSE_DVD_ERROR 2 +#define PAUSE_UNDERFLOW 4 +#define PAUSE_OTHER_ERROR 8 + +// CMDs for mMainCommandQueue. +#define CMD_START 0 +#define CMD_STOP 1 // upper 16 bits of cmd contain oscillator direct release value. +#define CMD_PAUSE 2 +#define CMD_UNPAUSE 3 + +// CMDs for mLoadCommandQueue +#define CMD_PREPARE_FINISHED 4 +#define CMD_LOOP_END_LOADED 5 + void JASAramStream::initSystem(u32 block_size, u32 channel_max) { JUT_ASSERT(66, block_size % 32 == 0); JUT_ASSERT(67, block_size % 9 == 0); @@ -33,6 +49,10 @@ void JASAramStream::initSystem(u32 block_size, u32 channel_max) { if (sLoadThread == NULL) { sLoadThread = JASDvd::getThreadPointer(); } + + // Pretty sure this 0x20 corresponds to sizeof(BlockHeader). + // But that shouldn't be getting multiplied by the channel count. + // Bug in the original game, I guess? sReadBuffer = JKR_NEW_ARRAY_ARGS(u8, (block_size + 0x20) * channel_max, JASDram, 0x20); JUT_ASSERT(79, sReadBuffer); sBlockSize = block_size; @@ -43,23 +63,23 @@ void JASAramStream::initSystem(u32 block_size, u32 channel_max) { } JASAramStream::JASAramStream() { - mInitialChannel = NULL; - field_0x0ac = false; - field_0x0ad = false; - field_0x0ae = 0; + mPrimaryChannel = NULL; + mPrepareFinished = false; + mLoopEndLoaded = false; + mPauseFlags = 0; field_0x0b0 = 0; - field_0x0b4 = 0; - field_0x0b8 = 0; + mLastSamplesLeft = 0; + mReadSample = 0; field_0x0bc = 0; - field_0x0c0 = false; + mEndSetup = false; field_0x0c4 = 0; field_0x0c8 = 0.0f; - field_0x108 = 0; - field_0x10c = 0; + mRingEndIndex = 0; + mBlockRingIndex = 0; mBlock = 0; - field_0x114 = 0; - field_0x118 = 0; - field_0x12c = 0; + mIsCancelled = 0; + mPendingLoadTasks = 0; + mChannelUpdateFlags = 0; mAramAddress = 0; mAramSize = 0; mCallback = NULL; @@ -93,10 +113,10 @@ void JASAramStream::init(u32 aramAddress, u32 aramSize, StreamCallback i_callbac mAramAddress = aramAddress; mAramSize = aramSize; field_0x0c8 = 0.0f; - field_0x0ae = 0; - field_0x0ac = false; - field_0x0ad = false; - field_0x114 = 0; + mPauseFlags = 0; + mPrepareFinished = false; + mLoopEndLoaded = false; + mIsCancelled = 0; mChannelNum = 0; for (int i = 0; i < 6; i++) { mChannelVolume[i] = 1.0f; @@ -109,8 +129,8 @@ void JASAramStream::init(u32 aramAddress, u32 aramSize, StreamCallback i_callbac mMixConfig[0] = 0xffff; mCallback = i_callback; mCallbackData = i_callbackData; - OSInitMessageQueue(&field_0x000, field_0x040, 0x10); - OSInitMessageQueue(&field_0x020, field_0x080, 4); + OSInitMessageQueue(&mMainCommandQueue, mMainCommandQueueArray, ARRAY_SIZEU(mMainCommandQueueArray)); + OSInitMessageQueue(&mLoadCommandQueue, mLoadCommandQueueArray, ARRAY_SIZEU(mLoadCommandQueueArray)); } bool JASAramStream::prepare(s32 param_0, int param_1) { @@ -124,8 +144,8 @@ bool JASAramStream::prepare(s32 param_0, int param_1) { } TaskData data; data.stream = this; - data.field_0x4 = mAramSize; - data.field_0x8 = param_1; + data.param0 = mAramSize; + data.param1 = param_1; if (!sLoadThread->sendCmdMsg(headerLoadTask, &data, sizeof(data))) { JUT_WARN(254, "%s", "sendCmdMsg headerLoadTask Failed"); JASDriver::rejectCallback(channelProcCallback, this); @@ -135,24 +155,24 @@ bool JASAramStream::prepare(s32 param_0, int param_1) { } bool JASAramStream::start() { - if (!OSSendMessage(&field_0x000, (OSMessage)0, OS_MESSAGE_NOBLOCK)) { + if (!OSSendMessage(&mMainCommandQueue, (OSMessage)CMD_START, OS_MESSAGE_NOBLOCK)) { JUT_WARN(273, "%s", "OSSendMessage Failed") return false; } return true; } -bool JASAramStream::stop(u16 param_0) { - if (!OSSendMessage(&field_0x000, (OSMessage)(uintptr_t)(param_0 << 0x10 | 1), OS_MESSAGE_NOBLOCK)) { +bool JASAramStream::stop(u16 directRelease) { + if (!OSSendMessage(&mMainCommandQueue, (OSMessage)(uintptr_t)(directRelease << 0x10 | CMD_STOP), OS_MESSAGE_NOBLOCK)) { JUT_WARN(290, "%s", "OSSendMessage Failed"); return false; } return true; } -bool JASAramStream::pause(bool param_0) { - OSMessage msg = param_0 ? (OSMessage)2 : (OSMessage)3; - if (!OSSendMessage(&field_0x000, msg, OS_MESSAGE_NOBLOCK)) { +bool JASAramStream::pause(bool newPauseFlag) { + OSMessage msg = newPauseFlag ? (OSMessage)CMD_PAUSE : (OSMessage)CMD_UNPAUSE; + if (!OSSendMessage(&mMainCommandQueue, msg, OS_MESSAGE_NOBLOCK)) { JUT_WARN(308, "%s", "OSSendMessage Failed"); return false; } @@ -160,7 +180,7 @@ bool JASAramStream::pause(bool param_0) { } bool JASAramStream::cancel() { - field_0x114 = 1; + mIsCancelled = 1; if (!sLoadThread->sendCmdMsg(finishTask, this)) { JUT_WARN(326, "%s", "sendCmdMsg finishTask Failed"); return false; @@ -169,12 +189,12 @@ bool JASAramStream::cancel() { } u32 JASAramStream::getBlockSamples() const { - return mFormat == 0 ? (sBlockSize << 4) / 9 : sBlockSize >> 1; + return mFormat == STREAM_FORMAT_ADPCM4 ? (sBlockSize << 4) / 9 : sBlockSize >> 1; } void JASAramStream::headerLoadTask(void* i_data) { TaskData* data = (TaskData*)i_data; - data->stream->headerLoad(data->field_0x4, data->field_0x8); + data->stream->headerLoad(data->param0, data->param1); } void JASAramStream::firstLoadTask(void* i_data) { @@ -183,23 +203,23 @@ void JASAramStream::firstLoadTask(void* i_data) { if (!_this->load()) { return; } - if (data->field_0x8 > 0) { - data->field_0x8--; - if (data->field_0x8 == 0) { + if (data->param1 > 0) { + data->param1--; + if (data->param1 == 0) { if (!sLoadThread->sendCmdMsg(prepareFinishTask, _this)) { JUT_WARN(364, "%s", "sendCmdMsg prepareFinishTask Failed"); hasErrored = true; } } } - if (data->field_0x4 != 0) { - data->field_0x4--; + if (data->param0 != 0) { + data->param0--; if (!sLoadThread->sendCmdMsg(firstLoadTask, data, sizeof(*data))) { JUT_WARN(372, "%s", "sendCmdMsg firstLoadTask Failed"); hasErrored = true; } JASCriticalSection cs; - _this->field_0x118++; + _this->mPendingLoadTasks++; } } @@ -221,7 +241,7 @@ void JASAramStream::finishTask(void* i_this) { void JASAramStream::prepareFinishTask(void* i_this) { JASAramStream* _this = (JASAramStream*)i_this; - OSSendMessage(&_this->field_0x020, (OSMessage)4, OS_MESSAGE_BLOCK); + OSSendMessage(&_this->mLoadCommandQueue, (OSMessage)CMD_PREPARE_FINISHED, OS_MESSAGE_BLOCK); if (_this->mCallback != NULL) { _this->mCallback(CB_STOP, _this, _this->mCallbackData); } @@ -231,7 +251,7 @@ bool JASAramStream::headerLoad(u32 aramSize, int param_1) { if (hasErrored) { return false; } - if (field_0x114 != 0) { + if (mIsCancelled != 0) { return false; } if (DVDReadPrio(&mDvdFileInfo, sReadBuffer, sizeof(Header), 0, 1) < 0) { @@ -252,9 +272,9 @@ bool JASAramStream::headerLoad(u32 aramSize, int param_1) { mLoopStart = header->loop_start; mLoopEnd = header->loop_end; mVolume = header->mVolume / 127.0f; - field_0x118 = 0; + mPendingLoadTasks = 0; mBlock = 0; - field_0x10c = 0; + mBlockRingIndex = 0; mAramBlocksPerChannel = (aramSize / sBlockSize) / header->channels; mBufCount = mAramBlocksPerChannel; JUT_ASSERT(445, mBufCount > 0); @@ -262,28 +282,28 @@ bool JASAramStream::headerLoad(u32 aramSize, int param_1) { if (mBufCount < 3) { JUT_WARN(449, "%s", "Too few Buffer-Size"); } - field_0x108 = mBufCount; + mRingEndIndex = mBufCount; u32 local_2c = (mLoopEnd - 1) / getBlockSamples(); if (local_2c <= mBufCount && mLoop) { JUT_WARN(458, "%s", "Too few samples for Loop-buffer"); } - if (param_1 < 0 || param_1 > field_0x108) { - param_1 = field_0x108; + if (param_1 < 0 || param_1 > mRingEndIndex) { + param_1 = mRingEndIndex; } - if (field_0x114 != 0) { + if (mIsCancelled != 0) { return false; } TaskData data; data.stream = this; - data.field_0x4 = field_0x108 - 1; - data.field_0x8 = param_1; + data.param0 = mRingEndIndex - 1; + data.param1 = param_1; if (!sLoadThread->sendCmdMsg(firstLoadTask, &data, sizeof(data))) { JUT_WARN(472, "%s", "sendCmdMsg firstLoadTask Failed"); hasErrored = true; return false; } JASCriticalSection cs; - field_0x118++; + mPendingLoadTasks++; return true; } @@ -291,12 +311,12 @@ bool JASAramStream::headerLoad(u32 aramSize, int param_1) { bool JASAramStream::load() { { JASCriticalSection cs; - field_0x118--; + mPendingLoadTasks--; } if (hasErrored) { return false; } - if (field_0x114 != 0) { + if (mIsCancelled != 0) { return false; } u32 loop_end_block = (mLoopEnd - 1) / getBlockSamples(); @@ -316,26 +336,26 @@ bool JASAramStream::load() { } BlockHeader* bhead = (BlockHeader*)sReadBuffer; JUT_ASSERT(512, bhead->tag == 'BLCK'); - if (field_0x114 != 0) { + if (mIsCancelled != 0) { return false; } - u32 sp08 = mAramAddress + field_0x10c * sBlockSize; + u32 blockBaseOffset = mAramAddress + mBlockRingIndex * sBlockSize; for (int i = 0; i < mChannelNum; i++) { (void)i; // Fakematch? It seems the only way to get the bhead->field_0x4 load in the right order is // with a pointer cast on its address in one of the two places it is read, but not both. if (!JKRMainRamToAram(sReadBuffer + bhead->mSize * i + sizeof(BlockHeader), - sp08 + sBlockSize * mAramBlocksPerChannel * i, + blockBaseOffset + sBlockSize * mAramBlocksPerChannel * i, bhead->mSize, EXPAND_SWITCH_UNKNOWN0, 0, NULL, -1, NULL)) { JUT_WARN(522, "%s", "JKRMainRamToAram Failed"); hasErrored = 1; return false; } } - field_0x10c++; - if (field_0x10c >= field_0x108) { + mBlockRingIndex++; + if (mBlockRingIndex >= mRingEndIndex) { u32 r28 = mBlock; - r28 += field_0x108 - 1; + r28 += mRingEndIndex - 1; if (mLoop) { JUT_ASSERT(537, loop_start_block < loop_end_block); while (r28 > loop_end_block) { @@ -344,16 +364,16 @@ bool JASAramStream::load() { } } if (r28 == loop_end_block || r28 + 2 == loop_end_block) { - field_0x108 = mAramBlocksPerChannel; - OSSendMessage(&field_0x020, (OSMessage)5, OS_MESSAGE_BLOCK); + mRingEndIndex = mAramBlocksPerChannel; + OSSendMessage(&mLoadCommandQueue, (OSMessage)CMD_LOOP_END_LOADED, OS_MESSAGE_BLOCK); } else { - field_0x108 = mAramBlocksPerChannel - 1; + mRingEndIndex = mAramBlocksPerChannel - 1; } for (int i = 0; i < mChannelNum; i++) { mpLasts[i] = (s16)bhead->mAdpcmContinuationData[i].mpLast; mpPenults[i] = (s16)bhead->mAdpcmContinuationData[i].mpPenult; } - field_0x10c = 0; + mBlockRingIndex = 0; } mBlock++; if (mBlock > loop_end_block && mLoop) { @@ -397,80 +417,105 @@ void JASAramStream::channelCallback(u32 i_callbackType, JASChannel* i_channel, stream->updateChannel(i_callbackType, i_channel, i_dspChannel); } +#define CHANNEL_UPDATE_SAMPLES_LEFT 1 +#define CHANNEL_UPDATE_LOOP_START 2 +#define CHANNEL_UPDATE_END_SAMPLE 4 +#define CHANNEL_UPDATE_LOOP_FLAG 8 + + void JASAramStream::updateChannel(u32 i_callbackType, JASChannel* i_channel, JASDsp::TChannel* i_dspChannel) { u32 block_samples = getBlockSamples(); switch (i_callbackType) { case JASChannel::CB_START: - if (mInitialChannel == NULL) { - mInitialChannel = i_channel; - field_0x0b4 = block_samples * mBufCount; - field_0x0b8 = 0; + if (mPrimaryChannel == NULL) { + mPrimaryChannel = i_channel; + mLastSamplesLeft = block_samples * mBufCount; + mReadSample = 0; field_0x0b0 = 0; field_0x0bc = (mLoopEnd - 1) / block_samples; - field_0x0c0 = 0; + mEndSetup = 0; field_0x0c4 = 0; - field_0x12c = 0; + mChannelUpdateFlags = 0; } break; case JASChannel::CB_PLAY: - if (i_dspChannel->field_0x008 == 0) { - if (i_channel == mInitialChannel) { - field_0x12c = 0; - u32 sp28 = i_dspChannel->field_0x074 + i_dspChannel->mSamplesPerBlock; - if (sp28 <= field_0x0b4) { - field_0x0b8 += field_0x0b4 - sp28; + if (i_dspChannel->mResetFlag == 0) { + if (i_channel == mPrimaryChannel) { + if (JASAudioThread::snIntCount == 1) { + OSReportForceEnableOn(); + OSReport("mSamplesLeft: %08d, mAramStreamPosition: %08d\n", i_dspChannel->mSamplesLeft, i_dspChannel->mAramStreamPosition); + } + + mChannelUpdateFlags = 0; + u32 adjustedSamplesLeft = i_dspChannel->mSamplesLeft + i_dspChannel->mSamplesPerBlock; + if (adjustedSamplesLeft <= mLastSamplesLeft) { + mReadSample += mLastSamplesLeft - adjustedSamplesLeft; } else { - if (!field_0x0c0) { - field_0x0b8 += field_0x0b4; - field_0x0b8 += block_samples * mBufCount - sp28; + // The DSP has looped. + + if (!mEndSetup) { + // Just looping the ring buffer, data continues as normal. + mReadSample += mLastSamplesLeft; + mReadSample += block_samples * mBufCount - adjustedSamplesLeft; } else { - field_0x0b8 += field_0x0b4; - field_0x0b8 += block_samples * mBufCount - sp28 + // We hit the actual file loop position. + mReadSample += mLastSamplesLeft; + mReadSample += block_samples * mBufCount - adjustedSamplesLeft - i_dspChannel->mLoopStartSample; - field_0x0b8 -= mLoopEnd; - field_0x0b8 += mLoopStart; + mReadSample -= mLoopEnd; + mReadSample += mLoopStart; i_dspChannel->mLoopStartSample = 0; - field_0x120 = 0; - field_0x12c |= 2; + mUpdateLoopStartSample = 0; + mChannelUpdateFlags |= CHANNEL_UPDATE_LOOP_START; +#if !TARGET_PC // The variable assigned here is never used. if (field_0x0c4 < 0xffffffff) { field_0x0c4++; } - field_0x0c0 = false; +#endif + mEndSetup = false; } } - if (field_0x0b8 > mLoopEnd) { + if (mReadSample > mLoopEnd) { JUT_WARN(686, "%s", "mReadSample > mLoopEnd"); hasErrored = true; } + +#if !TARGET_PC // The variable assigned here is never used. f32 fvar1 = field_0x0c4; fvar1 *= mLoopEnd - mLoopStart; if (field_0x0c4 < 0xffffffff) { - fvar1 += field_0x0b8; + fvar1 += mReadSample; } fvar1 /= mSampleRate; field_0x0c8 = fvar1; - if (field_0x0b8 + 400 >= mLoopEnd && !field_0x0c0) { +#endif + + if (mReadSample + 400 >= mLoopEnd && !mEndSetup) { if (mLoop) { + // File needs to loop. Adjust loop start position + // (out of the normal ring buffer behavior). u32 uvar5 = field_0x0bc + 1; if (uvar5 >= mBufCount) { uvar5 = 0; } i_dspChannel->mLoopStartSample = mLoopStart % block_samples + uvar5 * block_samples; - field_0x120 = i_dspChannel->mLoopStartSample; - field_0x12c |= 2; + mUpdateLoopStartSample = i_dspChannel->mLoopStartSample; + mChannelUpdateFlags |= CHANNEL_UPDATE_LOOP_START; } else { - i_dspChannel->field_0x102 = 0; - field_0x128 = 0; - field_0x12c |= 8; + // File doesn't need to loop, just unset loop flag + // and let the DSP finish naturally. + i_dspChannel->mLoopFlag = 0; + mUpdateLoopFlag = 0; + mChannelUpdateFlags |= CHANNEL_UPDATE_LOOP_FLAG; } int sp20 = field_0x0bc * block_samples + mLoopEnd % block_samples; - i_dspChannel->field_0x074 -= block_samples * mBufCount - sp20; - field_0x11c = i_dspChannel->field_0x074; - field_0x12c |= 1; + i_dspChannel->mSamplesLeft -= block_samples * mBufCount - sp20; + mUpdateSamplesLeft = i_dspChannel->mSamplesLeft; + mChannelUpdateFlags |= CHANNEL_UPDATE_SAMPLES_LEFT; field_0x0bc += (mLoopEnd - 1) / block_samples - mLoopStart / block_samples + 1; - field_0x0c0 = true; + mEndSetup = true; } u32 uvar4 = i_dspChannel->mAramStreamPosition - i_channel->mWaveAramAddress; if (uvar4 != 0) { @@ -488,7 +533,7 @@ void JASAramStream::updateChannel(u32 i_callbackType, JASChannel* i_channel, } { JASCriticalSection cs; - field_0x118++; + mPendingLoadTasks++; } field_0x0b0++; if (field_0x0b0 >= mBufCount) { @@ -497,58 +542,58 @@ void JASAramStream::updateChannel(u32 i_callbackType, JASChannel* i_channel, } if (cmp) { field_0x0bc -= mBufCount; - if (field_0x0ad) { - if (!field_0x0c0) { - i_dspChannel->field_0x074 += block_samples; - field_0x11c = i_dspChannel->field_0x074; - field_0x12c |= 1; + if (mLoopEndLoaded) { + if (!mEndSetup) { + i_dspChannel->mSamplesLeft += block_samples; + mUpdateSamplesLeft = i_dspChannel->mSamplesLeft; + mChannelUpdateFlags |= CHANNEL_UPDATE_SAMPLES_LEFT; } i_dspChannel->mEndSample += block_samples; - field_0x124 = i_dspChannel->mEndSample; - field_0x12c |= 4; + mUpdateEndSample = i_dspChannel->mEndSample; + mChannelUpdateFlags |= CHANNEL_UPDATE_END_SAMPLE; mBufCount = mAramBlocksPerChannel; - field_0x0ad = false; + mLoopEndLoaded = false; } else { if (mBufCount != mAramBlocksPerChannel - 1) { mBufCount = mAramBlocksPerChannel - 1; i_dspChannel->mEndSample -= block_samples; - field_0x124 = i_dspChannel->mEndSample; - field_0x12c |= 4; - if (!field_0x0c0) { - i_dspChannel->field_0x074 -= block_samples; - field_0x11c = i_dspChannel->field_0x074; - field_0x12c |= 1; + mUpdateEndSample = i_dspChannel->mEndSample; + mChannelUpdateFlags |= CHANNEL_UPDATE_END_SAMPLE; + if (!mEndSetup) { + i_dspChannel->mSamplesLeft -= block_samples; + mUpdateSamplesLeft = i_dspChannel->mSamplesLeft; + mChannelUpdateFlags |= CHANNEL_UPDATE_SAMPLES_LEFT; } } } } } else { - if (field_0x118 == 0 && !dvdHasErrored) { - field_0x0ae &= ~2; - field_0x0ae &= ~4; + if (mPendingLoadTasks == 0 && !dvdHasErrored) { + mPauseFlags &= ~PAUSE_DVD_ERROR; + mPauseFlags &= ~PAUSE_UNDERFLOW; } } - field_0x0b4 = i_dspChannel->field_0x074 + i_dspChannel->mSamplesPerBlock; - if (field_0x118 >= mAramBlocksPerChannel - 2) { + mLastSamplesLeft = i_dspChannel->mSamplesLeft + i_dspChannel->mSamplesPerBlock; + if (mPendingLoadTasks >= mAramBlocksPerChannel - 2) { JUT_WARN_DEVICE(810, 1, "%s", "buffer under error"); - field_0x0ae |= (u8)4; + mPauseFlags |= (u8)PAUSE_UNDERFLOW; } } else { - if (field_0x12c & 1) { - i_dspChannel->field_0x074 = field_0x11c; + if (mChannelUpdateFlags & CHANNEL_UPDATE_SAMPLES_LEFT) { + i_dspChannel->mSamplesLeft = mUpdateSamplesLeft; } - if (field_0x12c & 2) { - i_dspChannel->mLoopStartSample = field_0x120; + if (mChannelUpdateFlags & CHANNEL_UPDATE_LOOP_START) { + i_dspChannel->mLoopStartSample = mUpdateLoopStartSample; } - if (field_0x12c & 4) { - i_dspChannel->mEndSample = field_0x124; + if (mChannelUpdateFlags & CHANNEL_UPDATE_END_SAMPLE) { + i_dspChannel->mEndSample = mUpdateEndSample; } - if (field_0x12c & 8) { - i_dspChannel->field_0x102 = field_0x128; + if (mChannelUpdateFlags & CHANNEL_UPDATE_LOOP_FLAG) { + i_dspChannel->mLoopFlag = mUpdateLoopFlag; } } int ch = 0; - for (; ch < 6; ch++) { + for (; ch < CHANNEL_MAX; ch++) { if (i_channel == mChannels[ch]) { break; } @@ -568,7 +613,7 @@ void JASAramStream::updateChannel(u32 i_callbackType, JASChannel* i_channel, } } if (!open_channel) { - field_0x114 = 1; + mIsCancelled = 1; if (!sLoadThread->sendCmdMsg(finishTask, this)) { JUT_WARN(854, "%s", "sendCmdMsg finishTask Failed"); hasErrored = true; @@ -577,48 +622,48 @@ void JASAramStream::updateChannel(u32 i_callbackType, JASChannel* i_channel, } break; } - i_channel->setPauseFlag(field_0x0ae != 0); + i_channel->setPauseFlag(mPauseFlags != 0); } s32 JASAramStream::channelProc() { OSMessage msg; - while (OSReceiveMessage(&field_0x020, &msg, OS_MESSAGE_NOBLOCK)) { + while (OSReceiveMessage(&mLoadCommandQueue, &msg, OS_MESSAGE_NOBLOCK)) { switch ((uintptr_t)msg) { - case 4: - field_0x0ac = true; + case CMD_PREPARE_FINISHED: + mPrepareFinished = true; break; - case 5: - field_0x0ad = true; + case CMD_LOOP_END_LOADED: + mLoopEndLoaded = true; break; } } - if (!field_0x0ac) { + if (!mPrepareFinished) { return 0; } - while (OSReceiveMessage(&field_0x000, &msg, OS_MESSAGE_NOBLOCK)) { + while (OSReceiveMessage(&mMainCommandQueue, &msg, OS_MESSAGE_NOBLOCK)) { switch ((uintptr_t)msg & 0xff) { - case 0: + case CMD_START: channelStart(); break; - case 1: + case CMD_STOP: channelStop(JSUHiHalf((uintptr_t)msg)); break; - case 2: - field_0x0ae |= 1; + case CMD_PAUSE: + mPauseFlags |= PAUSE_REQUESTED; break; - case 3: - field_0x0ae &= ~1; + case CMD_UNPAUSE: + mPauseFlags &= ~PAUSE_REQUESTED; break; } } if (hasErrored) { - field_0x0ae |= 8; + mPauseFlags |= PAUSE_OTHER_ERROR; } if (dvdHasErrored) { - field_0x0ae |= 2; + mPauseFlags |= PAUSE_DVD_ERROR; } for (int i = 0; i < mChannelNum; i++) { @@ -648,10 +693,10 @@ static JASOscillator::Data const OSC_ENV = {0, 1.0f, NULL, OSC_RELEASE_TABLE, 1. void JASAramStream::channelStart() { u8 format; switch (mFormat) { - case 0: + case STREAM_FORMAT_ADPCM4: format = WAVE_FORMAT_ADPCM4; break; - case 1: + case STREAM_FORMAT_PCM16: format = WAVE_FORMAT_PCM16; break; } @@ -683,7 +728,7 @@ void JASAramStream::channelStart() { JUT_ASSERT_MSG(979, mChannels[i] == NULL, "channelStart for already playing channel"); mChannels[i] = jc; } - mInitialChannel = NULL; + mPrimaryChannel = NULL; } diff --git a/libs/JSystem/src/JAudio2/JASDSPInterface.cpp b/libs/JSystem/src/JAudio2/JASDSPInterface.cpp index a4c91b103c..00c7a66539 100644 --- a/libs/JSystem/src/JAudio2/JASDSPInterface.cpp +++ b/libs/JSystem/src/JAudio2/JASDSPInterface.cpp @@ -514,7 +514,7 @@ void JASDsp::TChannel::playStart() { JUT_ASSERT(508, dspMutex); field_0x10c = 0; field_0x060 = 0; - field_0x008 = 1; + mResetFlag = 1; field_0x066 = 0; int i; for (i = 0; i < 4; i++) { @@ -564,8 +564,8 @@ void JASDsp::TChannel::setWaveInfo(JASWaveInfo const& waveInfo, u32 aramAddress, mSamplePosition = 0; if (mBytesPerBlock >= 4) { mSampleCount = waveInfo.mSampleCount; - field_0x102 = waveInfo.mLoopFlag; - if (field_0x102) { + mLoopFlag = waveInfo.mLoopFlag; + if (mLoopFlag) { if (skipSamples == 1) { skipSamples = waveInfo.mLoopStartSample; } From 002a34f18bf7df30fb203f8e1755d9e95a9b2658 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sat, 14 Mar 2026 12:21:26 +0100 Subject: [PATCH 033/350] We have audio!! --- include/dusk/audio/DuskAudioSystem.h | 4 +- libs/JSystem/src/JAudio2/JAUInitializer.cpp | 2 +- src/dusk/audio/DuskAudioSystem.cpp | 27 +++- src/dusk/audio/DuskDsp.cpp | 150 ++++++++++++++++++-- src/dusk/audio/DuskDsp.hpp | 13 +- 5 files changed, 178 insertions(+), 18 deletions(-) diff --git a/include/dusk/audio/DuskAudioSystem.h b/include/dusk/audio/DuskAudioSystem.h index 0ea13125ad..b06bdbe84e 100644 --- a/include/dusk/audio/DuskAudioSystem.h +++ b/include/dusk/audio/DuskAudioSystem.h @@ -1,3 +1,5 @@ #pragma once -void DuskAudioInitialize(); +namespace dusk::audio { + void Initialize(); +} diff --git a/libs/JSystem/src/JAudio2/JAUInitializer.cpp b/libs/JSystem/src/JAudio2/JAUInitializer.cpp index 7d56282b45..0cc3fcbb62 100644 --- a/libs/JSystem/src/JAudio2/JAUInitializer.cpp +++ b/libs/JSystem/src/JAudio2/JAUInitializer.cpp @@ -63,7 +63,7 @@ void JAU_JASInitializer::initJASystem(JKRSolidHeap* heap) { JASDvd::createThread(dvdThreadPriority_, 0x80, 0x1000); #if TARGET_PC - DuskAudioInitialize(); + dusk::audio::Initialize(); #else JASAudioThread::create(audioThreadPriority_); #endif diff --git a/src/dusk/audio/DuskAudioSystem.cpp b/src/dusk/audio/DuskAudioSystem.cpp index 566d2ab400..191df90686 100644 --- a/src/dusk/audio/DuskAudioSystem.cpp +++ b/src/dusk/audio/DuskAudioSystem.cpp @@ -2,6 +2,8 @@ #include #include +#include +#include #include "JSystem/JAudio2/JASAiCtrl.h" #include "JSystem/JAudio2/JASChannel.h" @@ -10,6 +12,9 @@ #include "JSystem/JAudio2/JASHeapCtrl.h" #include "DuskDsp.hpp" +#include "JSystem/JAudio2/JASAudioThread.h" + +using namespace dusk::audio; static DspSubframe AllSubframeBuffers[DSP_OUTPUT_CHANNELS]; @@ -39,7 +44,7 @@ static void InitSDL3Output() { nullptr); } -void DuskAudioInitialize() { +void dusk::audio::Initialize() { InitSDL3Output(); JASDsp::initBuffer(); @@ -53,22 +58,30 @@ void DuskAudioInitialize() { void SDLCALL GetNewAudio( void*, SDL_AudioStream*, - int, - int total_amount) { - while (total_amount > 0) { + int needed, + int) { + while (needed > 0) { const int rendered = RenderNewAudioFrame(); - total_amount -= rendered; + needed -= rendered; } } +static std::ofstream outRaw("guh.raw", std::ios_base::out | std::ios_base::binary); + int RenderNewAudioFrame() { JASCriticalSection section; const u32 countSubframes = JASDriver::getSubFrames(); + JASAudioThread::setDSPSyncCount(countSubframes); + for (u32 i = 0; i < countSubframes; i++) { RenderAudioSubframe(); + + JASAudioThread::snIntCount -= 1; } + outRaw.flush(); + return static_cast(countSubframes) * DSP_SUBFRAME_SIZE; } @@ -76,7 +89,9 @@ void RenderAudioSubframe() { DspSubframe& subFrame = AllSubframeBuffers[0]; JASDriver::updateDSP(); - DuskDspRender(subFrame); + DspRender(subFrame); + + outRaw.write((const char*)subFrame.data(), subFrame.size() * sizeof(s16)); SDL_PutAudioStreamData(PlaybackStream, &subFrame, sizeof(subFrame)); } diff --git a/src/dusk/audio/DuskDsp.cpp b/src/dusk/audio/DuskDsp.cpp index 63cc5707e3..cb2c01d1cb 100644 --- a/src/dusk/audio/DuskDsp.cpp +++ b/src/dusk/audio/DuskDsp.cpp @@ -1,16 +1,150 @@ +#include +#include + #include "DuskDsp.hpp" -#include +#include +#include +#include -static float SinePos; +#include "Adpcm.hpp" +#include "global.h" -void DuskDspRender(DspSubframe& subframe) { - subframe.fill(0); +using namespace dusk::audio; - for (auto& elem : subframe) { - elem = static_cast(sinf(SinePos) * std::numeric_limits::max() * 0.2); - SinePos += 0.05f; +ChannelAuxData dusk::audio::ChannelAux[DSP_CHANNELS] = {}; + +static bool ValidateChannelWaveFormat(const JASDsp::TChannel& channel) { + if (channel.mSamplesPerBlock == AdpcmSampleCount && channel.mBytesPerBlock == Adpcm4FrameSize) + return true; + /* + if (channel.mSamplesPerBlock == AdpcmSampleCount && channel.mBytesPerBlock == Adpcm2FrameSize) + return true; + if (channel.mSamplesPerBlock == 1 && channel.mBytesPerBlock == 8) + return true; + if (channel.mSamplesPerBlock == 1 && channel.mBytesPerBlock == 16) + return true; + */ + return false; +} + +static void ValidateChannel(const JASDsp::TChannel& channel) { + if (!ValidateChannelWaveFormat(channel)) { + CRASH( + "Unable to handle channel format: %02x, %02x\n", + channel.mSamplesPerBlock, + channel.mBytesPerBlock); + } +} + +static u32 ConvertDataLengthToSamples(const JASDsp::TChannel& channel, u32 dataLen) { + if (dataLen % channel.mBytesPerBlock != 0) { + CRASH("Indivisible data length: %d\n", dataLen); } - auto& channels = *reinterpret_cast*>(JASDsp::CH_BUF); + return (dataLen / channel.mBytesPerBlock) * channel.mSamplesPerBlock; +} + +static u32 ConvertSamplesToDataLength(const JASDsp::TChannel& channel, u32 samples) { + if (samples % channel.mSamplesPerBlock != 0) { + CRASH("Indivisible sample count: %d\n", samples); + } + + return (samples / channel.mSamplesPerBlock) * channel.mBytesPerBlock; +} + +static void RenderChannel( + JASDsp::TChannel& channel, + ChannelAuxData& channelAux, + DspSubframe& subframe); + +static void ResetChannel(JASDsp::TChannel& channel) { + channel.mSamplesLeft = channel.mEndSample - channel.mSamplePosition; + + channel.mResetFlag = false; +} + +static void MixSubframe(DspSubframe& dst, const DspSubframe& src) { + for (int i = 0; i < dst.size(); i++) { + dst[i] = static_cast(dst[i] + src[i]); + } +} + +void dusk::audio::DspRender(DspSubframe& subframe) { + subframe.fill(0); + + // This cast half exists because my debugger sucks and this is an easy way to look at the data. + auto& channels = *reinterpret_cast*>(JASDsp::CH_BUF); + + for (int i = 0; i < channels.size(); i++) { + auto& channel = channels[i]; + auto& channelAux = ChannelAux[i]; + + if (!channel.mIsActive) { + continue; + } + + ValidateChannel(channel); + + DspSubframe channelSubframe = {}; + RenderChannel(channel, channelAux, channelSubframe); + MixSubframe(subframe, channelSubframe); + } +} + +static void RenderChannel( + JASDsp::TChannel& channel, + ChannelAuxData& channelAux, + DspSubframe& subframe) { + if (channel.mResetFlag) { + ResetChannel(channel); + } + + auto aramBase = static_cast(ARGetStorageAddress()) + channel.mWaveAramAddress; + + // Streaming logic directly modifies mSamplesLeft. + // So we use that as our tracking of where we are. + auto curSamplePosition = channel.mEndSample - channel.mSamplesLeft; + auto dataPosition = ConvertSamplesToDataLength(channel, curSamplePosition); + + u32 renderSamples = std::min(channel.mSamplesLeft, static_cast(DSP_SUBFRAME_SIZE)); + + Adpcm4ToPcm16( + aramBase + dataPosition, + ConvertSamplesToDataLength(channel, renderSamples), + subframe.data(), + renderSamples, + channelAux.hist1, + channelAux.hist0); + + channel.mSamplesLeft -= renderSamples; + channel.mSamplePosition += renderSamples; + + if (channel.mSamplesLeft == 0) { + // Reached end of buffer. + if (!channel.mLoopFlag) { + // Finish. + channel.mIsFinished = true; + return; + } + + channel.mSamplesLeft = channel.mEndSample - channel.mLoopStartSample; + channel.mSamplePosition = channel.mLoopStartSample; + curSamplePosition = channel.mEndSample - channel.mSamplesLeft; + dataPosition = ConvertSamplesToDataLength(channel, curSamplePosition); + + Adpcm4ToPcm16( + aramBase + dataPosition, + ConvertSamplesToDataLength(channel, DSP_SUBFRAME_SIZE - renderSamples), + subframe.data() + renderSamples, + subframe.size() - renderSamples, + channelAux.hist1, + channelAux.hist0); + + channel.mSamplesLeft -= (DSP_SUBFRAME_SIZE - renderSamples); + channel.mSamplePosition += (DSP_SUBFRAME_SIZE - renderSamples); + } + + channel.mAramStreamPosition = channel.mWaveAramAddress + + ConvertSamplesToDataLength(channel, channel.mSamplePosition); } diff --git a/src/dusk/audio/DuskDsp.hpp b/src/dusk/audio/DuskDsp.hpp index 418c285686..a8013d6669 100644 --- a/src/dusk/audio/DuskDsp.hpp +++ b/src/dusk/audio/DuskDsp.hpp @@ -4,6 +4,15 @@ #include -using DspSubframe = std::array; +namespace dusk::audio { + struct ChannelAuxData { + s16 hist1; + s16 hist0; + }; -void DuskDspRender(DspSubframe& subframe); + extern ChannelAuxData ChannelAux[DSP_CHANNELS]; + + using DspSubframe = std::array; + + void DspRender(DspSubframe& subframe); +} From dca0964f270558db50938e53e7d48db1cd71daa6 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sat, 14 Mar 2026 14:16:49 +0100 Subject: [PATCH 034/350] Assign names to threads Visible in debuggers etc --- include/dusk/os.h | 14 +++++++++ .../include/JSystem/JKernel/JKRThread.h | 4 +++ libs/JSystem/src/JKernel/JKRThread.cpp | 12 ++++++++ src/dusk/OSThread.cpp | 30 +++++++++++++++++++ src/m_Do/m_Do_DVDError.cpp | 7 +++++ src/m_Do/m_Do_MemCard.cpp | 5 ++++ src/m_Do/m_Do_dvd_thread.cpp | 4 +++ 7 files changed, 76 insertions(+) create mode 100644 include/dusk/os.h diff --git a/include/dusk/os.h b/include/dusk/os.h new file mode 100644 index 0000000000..180bdb9b1a --- /dev/null +++ b/include/dusk/os.h @@ -0,0 +1,14 @@ +#ifndef DUSK_OS_H +#define DUSK_OS_H + +#ifdef __cplusplus +extern "C" { +#endif + +void OSSetCurrentThreadName(const char* name); + +#ifdef __cplusplus +} +#endif + +#endif // DUSK_OS_H \ No newline at end of file diff --git a/libs/JSystem/include/JSystem/JKernel/JKRThread.h b/libs/JSystem/include/JSystem/JKernel/JKRThread.h index b58dc6232d..2830252281 100644 --- a/libs/JSystem/include/JSystem/JKernel/JKRThread.h +++ b/libs/JSystem/include/JSystem/JKernel/JKRThread.h @@ -132,6 +132,10 @@ public: static JSUList sThreadList; // static u8 sThreadList[12]; + +#if TARGET_PC + const char* mThreadName; +#endif }; class JKRIdleThread : public JKRThread { diff --git a/libs/JSystem/src/JKernel/JKRThread.cpp b/libs/JSystem/src/JKernel/JKRThread.cpp index 96dfd53499..6ba97e3d4a 100644 --- a/libs/JSystem/src/JKernel/JKRThread.cpp +++ b/libs/JSystem/src/JKernel/JKRThread.cpp @@ -7,6 +7,10 @@ #include "global.h" #include +#if TARGET_PC +#include "dusk/os.h" +#endif + JSUList JKRThread::sThreadList(0); void* JKRIdleThread::sThread; @@ -88,6 +92,14 @@ void JKRThread::setCommon_heapSpecified(JKRHeap* heap, u32 stack_size, int param } void* JKRThread::start(void* thread) { +#if TARGET_PC + auto& thd = *static_cast(thread); + if (thd.mThreadName == nullptr) { + thd.mThreadName = typeid(thd).name(); + } + OSSetCurrentThreadName(thd.mThreadName); +#endif + return ((JKRThread*)thread)->run(); } diff --git a/src/dusk/OSThread.cpp b/src/dusk/OSThread.cpp index 2d19139272..5301614c5b 100644 --- a/src/dusk/OSThread.cpp +++ b/src/dusk/OSThread.cpp @@ -17,6 +17,12 @@ #include #include "JSystem/JKernel/JKRHeap.h" +#include "dusk/os.h" + +#if _WIN32 +#define WIN32_LEAN_AND_MEAN 1 +#include +#endif // ============================================================================ // Malloc-based allocator to bypass JKRHeap operator new/delete @@ -744,6 +750,30 @@ OSInterruptMask __OSUnmaskInterrupts(OSInterruptMask mask) { return 0; } +void OSSetCurrentThreadName(const char* name) { + // "Why is this current thread only?", you might ask? + // Because macOS requires that. For some reason. + +#if _WIN32 + wchar_t buffer[256]; + const auto converted = MultiByteToWideChar( + CP_UTF8, + 0, + name, + -1, + buffer, + sizeof(buffer)/sizeof(wchar_t)); + if (converted == 0) { + CRASH("OSSetThreadName: MultiByteToWideChar failed"); + } + + const auto result = SetThreadDescription(GetCurrentThread(), buffer); + if (!SUCCEEDED(result)) { + CRASH("OSSetThreadName: SetThreadDescription failed"); + } +#endif +} + #ifdef __cplusplus } #endif diff --git a/src/m_Do/m_Do_DVDError.cpp b/src/m_Do/m_Do_DVDError.cpp index 4984ea22c7..62fd71ed21 100644 --- a/src/m_Do/m_Do_DVDError.cpp +++ b/src/m_Do/m_Do_DVDError.cpp @@ -14,6 +14,8 @@ #include #include +#include "dusk/os.h" + #if PLATFORM_GCN const int stack_size = 3072; #else @@ -65,6 +67,11 @@ static void mDoDvdErr_Watch(void*) { OSDisableInterrupts(); #endif #endif + +#if TARGET_PC + OSSetCurrentThreadName("DVD error thread"); +#endif + JKRThread(OSGetCurrentThread(), 0); JKRSetCurrentHeap(mDoExt_getAssertHeap()); diff --git a/src/m_Do/m_Do_MemCard.cpp b/src/m_Do/m_Do_MemCard.cpp index 919774d2e0..bbb94f2bcd 100644 --- a/src/m_Do/m_Do_MemCard.cpp +++ b/src/m_Do/m_Do_MemCard.cpp @@ -10,6 +10,7 @@ #include "m_Do/m_Do_MemCardRWmng.h" #include "m_Do/m_Do_Reset.h" #include "os_report.h" +#include "dusk/os.h" #if PLATFORM_WII || PLATFORM_SHIELD #include @@ -866,6 +867,10 @@ mDoMemCd_Ctrl_c g_mDoMemCd_control; static int mDoMemCd_main(void*) { JKRThread(OSGetCurrentThread(), 0); +#if TARGET_PC + OSSetCurrentThreadName("MemCardThread"); +#endif + JKRSetCurrentHeap(mDoExt_getAssertHeap()); g_mDoMemCd_control.main(); diff --git a/src/m_Do/m_Do_dvd_thread.cpp b/src/m_Do/m_Do_dvd_thread.cpp index 6ea1cf99f7..c5f982bc9d 100644 --- a/src/m_Do/m_Do_dvd_thread.cpp +++ b/src/m_Do/m_Do_dvd_thread.cpp @@ -10,6 +10,7 @@ #include "JSystem/JKernel/JKRDvdRipper.h" #include "JSystem/JKernel/JKRExpHeap.h" #include "JSystem/JKernel/JKRMemArchive.h" +#include "dusk/os.h" #include "m_Do/m_Do_Reset.h" #include "m_Do/m_Do_controller_pad.h" #include "m_Do/m_Do_ext.h" @@ -17,6 +18,9 @@ s32 mDoDvdThd::main(void* param_0) { JKRThread(OSGetCurrentThread(), 0); +#if TARGET_PC + OSSetCurrentThreadName("DVD thread"); +#endif JKRSetCurrentHeap(mDoExt_getAssertHeap()); mDoDvdThd_param_c* param = static_cast(param_0); param->mainLoop(); From 60141f9381a31f269c46a4531b4c958a57886765 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sat, 14 Mar 2026 14:22:33 +0100 Subject: [PATCH 035/350] Don't make the DAC rate weird --- libs/JSystem/src/JAudio2/JASAiCtrl.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libs/JSystem/src/JAudio2/JASAiCtrl.cpp b/libs/JSystem/src/JAudio2/JASAiCtrl.cpp index ec60bd9d89..fd9446932d 100644 --- a/libs/JSystem/src/JAudio2/JASAiCtrl.cpp +++ b/libs/JSystem/src/JAudio2/JASAiCtrl.cpp @@ -98,7 +98,10 @@ void JASDriver::setOutputRate(JASOutputRate param_0) { sSubFrames = 10; sDacRate = 48000.0f; } + +#if !TARGET_PC sDacRate *= 1.0008897f; +#endif } const JASDriver::MixFunc JASDriver::sMixFuncs[4] = { From 8534691af7ad3561b480fb40a0983692a031e002 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sat, 14 Mar 2026 15:50:39 +0100 Subject: [PATCH 036/350] Resample audio with SDL3 --- src/dusk/audio/DuskAudioSystem.cpp | 3 +- src/dusk/audio/DuskDsp.cpp | 90 +++++++++++++++++++++++------- src/dusk/audio/DuskDsp.hpp | 6 ++ 3 files changed, 78 insertions(+), 21 deletions(-) diff --git a/src/dusk/audio/DuskAudioSystem.cpp b/src/dusk/audio/DuskAudioSystem.cpp index 191df90686..9b089eb807 100644 --- a/src/dusk/audio/DuskAudioSystem.cpp +++ b/src/dusk/audio/DuskAudioSystem.cpp @@ -35,7 +35,7 @@ static void InitSDL3Output() { constexpr SDL_AudioSpec spec = { SDL_AUDIO_S16, 1, - 32000, + SampleRate, }; PlaybackStream = SDL_OpenAudioDeviceStream( SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, @@ -46,6 +46,7 @@ static void InitSDL3Output() { void dusk::audio::Initialize() { InitSDL3Output(); + DspInit(); JASDsp::initBuffer(); JASDSPChannel::initAll(); diff --git a/src/dusk/audio/DuskDsp.cpp b/src/dusk/audio/DuskDsp.cpp index cb2c01d1cb..5b4c5d0730 100644 --- a/src/dusk/audio/DuskDsp.cpp +++ b/src/dusk/audio/DuskDsp.cpp @@ -5,7 +5,6 @@ #include #include -#include #include "Adpcm.hpp" #include "global.h" @@ -58,9 +57,18 @@ static void RenderChannel( ChannelAuxData& channelAux, DspSubframe& subframe); -static void ResetChannel(JASDsp::TChannel& channel) { +static void ResetChannel(JASDsp::TChannel& channel, const ChannelAuxData& aux) { channel.mSamplesLeft = channel.mEndSample - channel.mSamplePosition; + const SDL_AudioSpec spec = { + SDL_AUDIO_S16, + 1, + static_cast(static_cast(SampleRate) * channel.mPitch / 4096) + }; + + SDL_ClearAudioStream(aux.resampleStream); + SDL_SetAudioStreamFormat(aux.resampleStream, &spec, nullptr); + channel.mResetFlag = false; } @@ -92,13 +100,21 @@ void dusk::audio::DspRender(DspSubframe& subframe) { } } -static void RenderChannel( - JASDsp::TChannel& channel, - ChannelAuxData& channelAux, - DspSubframe& subframe) { - if (channel.mResetFlag) { - ResetChannel(channel); - } +static void SDLCALL ReadChannelSamples( + void *userdata, + SDL_AudioStream *stream, + int additional_amount, + int) { + + const auto index = static_cast(reinterpret_cast(userdata)); + auto& channel = JASDsp::CH_BUF[index]; + auto& aux = ChannelAux[index]; + + additional_amount = ALIGN_NEXT(additional_amount, channel.mSamplesPerBlock); + + int requestedSize = static_cast(sizeof(s16) * additional_amount); + auto requested = static_cast(alloca(requestedSize)); + memset(requested, 0, requestedSize); auto aramBase = static_cast(ARGetStorageAddress()) + channel.mWaveAramAddress; @@ -107,15 +123,15 @@ static void RenderChannel( auto curSamplePosition = channel.mEndSample - channel.mSamplesLeft; auto dataPosition = ConvertSamplesToDataLength(channel, curSamplePosition); - u32 renderSamples = std::min(channel.mSamplesLeft, static_cast(DSP_SUBFRAME_SIZE)); + u32 renderSamples = std::min(channel.mSamplesLeft, static_cast(additional_amount)); Adpcm4ToPcm16( aramBase + dataPosition, ConvertSamplesToDataLength(channel, renderSamples), - subframe.data(), + requested, renderSamples, - channelAux.hist1, - channelAux.hist0); + aux.hist1, + aux.hist0); channel.mSamplesLeft -= renderSamples; channel.mSamplePosition += renderSamples; @@ -135,16 +151,50 @@ static void RenderChannel( Adpcm4ToPcm16( aramBase + dataPosition, - ConvertSamplesToDataLength(channel, DSP_SUBFRAME_SIZE - renderSamples), - subframe.data() + renderSamples, - subframe.size() - renderSamples, - channelAux.hist1, - channelAux.hist0); + ConvertSamplesToDataLength(channel, additional_amount - renderSamples), + requested + renderSamples, + additional_amount - renderSamples, + aux.hist1, + aux.hist0); - channel.mSamplesLeft -= (DSP_SUBFRAME_SIZE - renderSamples); - channel.mSamplePosition += (DSP_SUBFRAME_SIZE - renderSamples); + channel.mSamplesLeft -= (additional_amount - renderSamples); + channel.mSamplePosition += (additional_amount - renderSamples); } channel.mAramStreamPosition = channel.mWaveAramAddress + ConvertSamplesToDataLength(channel, channel.mSamplePosition); + + SDL_PutAudioStreamData(stream, requested, requestedSize); +} + +static void RenderChannel( + JASDsp::TChannel& channel, + ChannelAuxData& channelAux, + DspSubframe& subframe) { + if (channel.mResetFlag) { + ResetChannel(channel, channelAux); + } + + SDL_GetAudioStreamData( + channelAux.resampleStream, + subframe.data(), + static_cast(subframe.size() * sizeof(s16))); +} + +void dusk::audio::DspInit() { + constexpr SDL_AudioSpec spec = { + SDL_AUDIO_S16, + 1, + SampleRate + }; + + for (int i = 0; i < DSP_CHANNELS; i++) { + auto& aux = ChannelAux[i]; + aux.resampleStream = SDL_CreateAudioStream(&spec, &spec); + + SDL_SetAudioStreamGetCallback( + aux.resampleStream, + ReadChannelSamples, + reinterpret_cast(i)); + } } diff --git a/src/dusk/audio/DuskDsp.hpp b/src/dusk/audio/DuskDsp.hpp index a8013d6669..186f7de677 100644 --- a/src/dusk/audio/DuskDsp.hpp +++ b/src/dusk/audio/DuskDsp.hpp @@ -4,15 +4,21 @@ #include +#include "SDL3/SDL_audio.h" + namespace dusk::audio { + constexpr int SampleRate = 32000; + struct ChannelAuxData { s16 hist1; s16 hist0; + SDL_AudioStream* resampleStream; }; extern ChannelAuxData ChannelAux[DSP_CHANNELS]; using DspSubframe = std::array; + void DspInit(); void DspRender(DspSubframe& subframe); } From 7d95231ddc8403e96074996d05ddf26d63b06f67 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sat, 14 Mar 2026 16:42:41 +0100 Subject: [PATCH 037/350] Fix OFFSET_PTR name conflicts --- .../JSystem/J3DGraphAnimator/J3DAnimation.h | 162 +++++++++--------- .../JSystem/J3DGraphAnimator/J3DCluster.h | 22 +-- .../JSystem/J3DGraphLoader/J3DClusterLoader.h | 20 +-- 3 files changed, 102 insertions(+), 102 deletions(-) diff --git a/libs/JSystem/include/JSystem/J3DGraphAnimator/J3DAnimation.h b/libs/JSystem/include/JSystem/J3DGraphAnimator/J3DAnimation.h index 81c363988f..84b27ea969 100644 --- a/libs/JSystem/include/JSystem/J3DGraphAnimator/J3DAnimation.h +++ b/libs/JSystem/include/JSystem/J3DGraphAnimator/J3DAnimation.h @@ -9,9 +9,9 @@ #include "dusk/endian.h" #if TARGET_PC -#define OFFSET_PTR BE(u32) +#define OFFSET_PTR_V0 BE(u32) #else -#define OFFSET_PTR void* +#define OFFSET_PTR_V0 void* #endif struct J3DTransformInfo; @@ -80,7 +80,7 @@ struct J3DAnmColorKeyTable { */ struct J3DAnmVtxColorIndexData { /* 0x00 */ BE(u16) mNum; - /* 0x04 */ OFFSET_PTR mpData; + /* 0x04 */ OFFSET_PTR_V0 mpData; }; /** @@ -200,13 +200,13 @@ struct J3DAnmVtxColorFullData { /* 0x0A */ BE(s16) mFrameMax; /* 0x0C */ BE(u16) mAnmTableNum[2]; /* 0x10 */ u8 field_0x10[0x18 - 0x10]; - /* 0x18 */ OFFSET_PTR mTableOffsets[2]; - /* 0x20 */ OFFSET_PTR mVtxColorIndexDataOffsets[2]; - /* 0x28 */ OFFSET_PTR mVtxColorIndexPointerOffsets[2]; - /* 0x30 */ OFFSET_PTR mRValuesOffset; - /* 0x34 */ OFFSET_PTR mGValuesOffset; - /* 0x38 */ OFFSET_PTR mBValuesOffset; - /* 0x3C */ OFFSET_PTR mAValuesOffset; + /* 0x18 */ OFFSET_PTR_V0 mTableOffsets[2]; + /* 0x20 */ OFFSET_PTR_V0 mVtxColorIndexDataOffsets[2]; + /* 0x28 */ OFFSET_PTR_V0 mVtxColorIndexPointerOffsets[2]; + /* 0x30 */ OFFSET_PTR_V0 mRValuesOffset; + /* 0x34 */ OFFSET_PTR_V0 mGValuesOffset; + /* 0x38 */ OFFSET_PTR_V0 mBValuesOffset; + /* 0x3C */ OFFSET_PTR_V0 mAValuesOffset; }; // Size = 0x40 STATIC_ASSERT(sizeof(J3DAnmVtxColorFullData) == 0x40); @@ -222,8 +222,8 @@ struct J3DAnmVisibilityFullData { /* 0x0A */ BE(s16) mFrameMax; /* 0x0C */ BE(u16) field_0xc; /* 0x0E */ BE(u16) field_0xe; - /* 0x10 */ OFFSET_PTR mTableOffset; - /* 0x14 */ OFFSET_PTR mValuesOffset; + /* 0x10 */ OFFSET_PTR_V0 mTableOffset; + /* 0x14 */ OFFSET_PTR_V0 mValuesOffset; }; // Size = 0x18 STATIC_ASSERT(sizeof(J3DAnmVisibilityFullData) == 0x18); @@ -239,10 +239,10 @@ struct J3DAnmTransformFullData { /* 0x0A */ BE(s16) mFrameMax; /* 0x0C */ BE(u16) field_0xc; /* 0x0E */ u8 field_0xe[0x14 - 0xe]; - /* 0x14 */ OFFSET_PTR mTableOffset; - /* 0x18 */ OFFSET_PTR mScaleValOffset; - /* 0x1C */ OFFSET_PTR mRotValOffset; - /* 0x20 */ OFFSET_PTR mTransValOffset; + /* 0x14 */ OFFSET_PTR_V0 mTableOffset; + /* 0x18 */ OFFSET_PTR_V0 mScaleValOffset; + /* 0x1C */ OFFSET_PTR_V0 mRotValOffset; + /* 0x20 */ OFFSET_PTR_V0 mTransValOffset; }; // Size = 0x24 STATIC_ASSERT(sizeof(J3DAnmTransformFullData) == 0x24); @@ -261,13 +261,13 @@ struct J3DAnmColorKeyData { /* 0x12 */ BE(u16) field_0x12; /* 0x14 */ BE(u16) field_0x14; /* 0x16 */ BE(u16) field_0x16; - /* 0x18 */ OFFSET_PTR mTableOffset; - /* 0x1C */ OFFSET_PTR mUpdateMaterialIDOffset; - /* 0x20 */ OFFSET_PTR mNameTabOffset; - /* 0x24 */ OFFSET_PTR mRValOffset; - /* 0x28 */ OFFSET_PTR mGValOffset; - /* 0x2C */ OFFSET_PTR mBValOffset; - /* 0x30 */ OFFSET_PTR mAValOffset; + /* 0x18 */ OFFSET_PTR_V0 mTableOffset; + /* 0x1C */ OFFSET_PTR_V0 mUpdateMaterialIDOffset; + /* 0x20 */ OFFSET_PTR_V0 mNameTabOffset; + /* 0x24 */ OFFSET_PTR_V0 mRValOffset; + /* 0x28 */ OFFSET_PTR_V0 mGValOffset; + /* 0x2C */ OFFSET_PTR_V0 mBValOffset; + /* 0x30 */ OFFSET_PTR_V0 mAValOffset; }; // Size = 0x34 STATIC_ASSERT(sizeof(J3DAnmColorKeyData) == 0x34); @@ -285,26 +285,26 @@ struct J3DAnmTextureSRTKeyData { /* 0x0E */ BE(u16) field_0xe; /* 0x10 */ BE(u16) field_0x10; /* 0x12 */ BE(u16) field_0x12; - /* 0x14 */ OFFSET_PTR mTableOffset; - /* 0x18 */ OFFSET_PTR mUpdateMatIDOffset; - /* 0x1C */ OFFSET_PTR mNameTab1Offset; - /* 0x20 */ OFFSET_PTR mUpdateTexMtxIDOffset; - /* 0x24 */ OFFSET_PTR unkOffset; - /* 0x28 */ OFFSET_PTR mScaleValOffset; - /* 0x2C */ OFFSET_PTR mRotValOffset; - /* 0x30 */ OFFSET_PTR mTransValOffset; + /* 0x14 */ OFFSET_PTR_V0 mTableOffset; + /* 0x18 */ OFFSET_PTR_V0 mUpdateMatIDOffset; + /* 0x1C */ OFFSET_PTR_V0 mNameTab1Offset; + /* 0x20 */ OFFSET_PTR_V0 mUpdateTexMtxIDOffset; + /* 0x24 */ OFFSET_PTR_V0 unkOffset; + /* 0x28 */ OFFSET_PTR_V0 mScaleValOffset; + /* 0x2C */ OFFSET_PTR_V0 mRotValOffset; + /* 0x30 */ OFFSET_PTR_V0 mTransValOffset; /* 0x34 */ BE(u16) field_0x34; /* 0x36 */ BE(u16) field_0x36; /* 0x38 */ BE(u16) field_0x38; /* 0x3A */ BE(u16) field_0x3a; - /* 0x3C */ OFFSET_PTR mInfoTable2Offset; - /* 0x40 */ OFFSET_PTR field_0x40; - /* 0x44 */ OFFSET_PTR mNameTab2Offset; - /* 0x48 */ OFFSET_PTR field_0x48; - /* 0x4C */ OFFSET_PTR field_0x4c; - /* 0x50 */ OFFSET_PTR field_0x50; - /* 0x54 */ OFFSET_PTR field_0x54; - /* 0x58 */ OFFSET_PTR field_0x58; + /* 0x3C */ OFFSET_PTR_V0 mInfoTable2Offset; + /* 0x40 */ OFFSET_PTR_V0 field_0x40; + /* 0x44 */ OFFSET_PTR_V0 mNameTab2Offset; + /* 0x48 */ OFFSET_PTR_V0 field_0x48; + /* 0x4C */ OFFSET_PTR_V0 field_0x4c; + /* 0x50 */ OFFSET_PTR_V0 field_0x50; + /* 0x54 */ OFFSET_PTR_V0 field_0x54; + /* 0x58 */ OFFSET_PTR_V0 field_0x58; /* 0x5C */ BE(s32) field_0x5c; }; // Size = 0x60 @@ -321,13 +321,13 @@ struct J3DAnmVtxColorKeyData { /* 0x0A */ BE(s16) mFrameMax; /* 0x0C */ BE(u16) mAnmTableNum[2]; /* 0x10 */ u8 field_0x10[0x18 - 0x10]; - /* 0x18 */ OFFSET_PTR mTableOffsets[2]; - /* 0x20 */ OFFSET_PTR mVtxColoIndexDataOffset[2]; - /* 0x28 */ OFFSET_PTR mVtxColoIndexPointerOffset[2]; - /* 0x30 */ OFFSET_PTR mRValOffset; - /* 0x34 */ OFFSET_PTR mGValOffset; - /* 0x38 */ OFFSET_PTR mBValOffset; - /* 0x3C */ OFFSET_PTR mAValOffset; + /* 0x18 */ OFFSET_PTR_V0 mTableOffsets[2]; + /* 0x20 */ OFFSET_PTR_V0 mVtxColoIndexDataOffset[2]; + /* 0x28 */ OFFSET_PTR_V0 mVtxColoIndexPointerOffset[2]; + /* 0x30 */ OFFSET_PTR_V0 mRValOffset; + /* 0x34 */ OFFSET_PTR_V0 mGValOffset; + /* 0x38 */ OFFSET_PTR_V0 mBValOffset; + /* 0x3C */ OFFSET_PTR_V0 mAValOffset; }; // Size = 0x40 STATIC_ASSERT(sizeof(J3DAnmVtxColorKeyData) == 0x40); @@ -343,10 +343,10 @@ struct J3DAnmTexPatternFullData { /* 0x0A */ BE(s16) mFrameMax; /* 0x0C */ BE(u16) field_0xc; /* 0x0E */ BE(u16) field_0xe; - /* 0x10 */ OFFSET_PTR mTableOffset; - /* 0x14 */ OFFSET_PTR mValuesOffset; - /* 0x18 */ OFFSET_PTR mUpdateMaterialIDOffset; - /* 0x1C */ OFFSET_PTR mNameTabOffset; + /* 0x10 */ OFFSET_PTR_V0 mTableOffset; + /* 0x14 */ OFFSET_PTR_V0 mValuesOffset; + /* 0x18 */ OFFSET_PTR_V0 mUpdateMaterialIDOffset; + /* 0x1C */ OFFSET_PTR_V0 mNameTabOffset; }; // Size = 0x20 STATIC_ASSERT(sizeof(J3DAnmTexPatternFullData) == 0x20); @@ -370,20 +370,20 @@ struct J3DAnmTevRegKeyData { /* 0x1A */ BE(u16) field_0x1a; /* 0x1C */ BE(u16) field_0x1c; /* 0x1E */ BE(u16) field_0x1e; - /* 0x20 */ OFFSET_PTR mCRegTableOffset; - /* 0x24 */ OFFSET_PTR mKRegTableOffset; - /* 0x28 */ OFFSET_PTR mCRegUpdateMaterialIDOffset; - /* 0x2C */ OFFSET_PTR mKRegUpdateMaterialIDOffset; - /* 0x30 */ OFFSET_PTR mCRegNameTabOffset; - /* 0x34 */ OFFSET_PTR mKRegNameTabOffset; - /* 0x38 */ OFFSET_PTR mCRValuesOffset; - /* 0x3C */ OFFSET_PTR mCGValuesOffset; - /* 0x40 */ OFFSET_PTR mCBValuesOffset; - /* 0x44 */ OFFSET_PTR mCAValuesOffset; - /* 0x48 */ OFFSET_PTR mKRValuesOffset; - /* 0x4C */ OFFSET_PTR mKGValuesOffset; - /* 0x50 */ OFFSET_PTR mKBValuesOffset; - /* 0x54 */ OFFSET_PTR mKAValuesOffset; + /* 0x20 */ OFFSET_PTR_V0 mCRegTableOffset; + /* 0x24 */ OFFSET_PTR_V0 mKRegTableOffset; + /* 0x28 */ OFFSET_PTR_V0 mCRegUpdateMaterialIDOffset; + /* 0x2C */ OFFSET_PTR_V0 mKRegUpdateMaterialIDOffset; + /* 0x30 */ OFFSET_PTR_V0 mCRegNameTabOffset; + /* 0x34 */ OFFSET_PTR_V0 mKRegNameTabOffset; + /* 0x38 */ OFFSET_PTR_V0 mCRValuesOffset; + /* 0x3C */ OFFSET_PTR_V0 mCGValuesOffset; + /* 0x40 */ OFFSET_PTR_V0 mCBValuesOffset; + /* 0x44 */ OFFSET_PTR_V0 mCAValuesOffset; + /* 0x48 */ OFFSET_PTR_V0 mKRValuesOffset; + /* 0x4C */ OFFSET_PTR_V0 mKGValuesOffset; + /* 0x50 */ OFFSET_PTR_V0 mKBValuesOffset; + /* 0x54 */ OFFSET_PTR_V0 mKAValuesOffset; }; // Size = 0x58 STATIC_ASSERT(sizeof(J3DAnmTevRegKeyData) == 0x58); @@ -399,13 +399,13 @@ struct J3DAnmColorFullData { /* PlaceHolder Structure */ /* 0x0C */ BE(s16) mFrameMax; /* 0x0E */ BE(u16) mUpdateMaterialNum; /* 0x10 */ u8 field_0x10[0x18 - 0x10]; - /* 0x18 */ OFFSET_PTR mTableOffset; - /* 0x1C */ OFFSET_PTR mUpdateMaterialIDOffset; - /* 0x20 */ OFFSET_PTR mNameTabOffset; - /* 0x24 */ OFFSET_PTR mRValuesOffset; - /* 0x28 */ OFFSET_PTR mGValuesOffset; - /* 0x2C */ OFFSET_PTR mBValuesOffset; - /* 0x30 */ OFFSET_PTR mAValuesOffset; + /* 0x18 */ OFFSET_PTR_V0 mTableOffset; + /* 0x1C */ OFFSET_PTR_V0 mUpdateMaterialIDOffset; + /* 0x20 */ OFFSET_PTR_V0 mNameTabOffset; + /* 0x24 */ OFFSET_PTR_V0 mRValuesOffset; + /* 0x28 */ OFFSET_PTR_V0 mGValuesOffset; + /* 0x2C */ OFFSET_PTR_V0 mBValuesOffset; + /* 0x30 */ OFFSET_PTR_V0 mAValuesOffset; }; // Size = 0x34 STATIC_ASSERT(sizeof(J3DAnmColorFullData) == 0x34); @@ -431,10 +431,10 @@ struct J3DAnmTransformKeyData { /* 0x0E */ BE(u16) mSCount; /* 0x10 */ BE(u16) mRCount; /* 0x12 */ BE(u16) mTCount; - /* 0x14 */ OFFSET_PTR mJointAnimationTableOffs; - /* 0x18 */ OFFSET_PTR mSTableOffs; - /* 0x1c */ OFFSET_PTR mRTableOffs; - /* 0x20 */ OFFSET_PTR mTTableOffs; + /* 0x14 */ OFFSET_PTR_V0 mJointAnimationTableOffs; + /* 0x18 */ OFFSET_PTR_V0 mSTableOffs; + /* 0x1c */ OFFSET_PTR_V0 mRTableOffs; + /* 0x20 */ OFFSET_PTR_V0 mTTableOffs; }; /** @@ -446,8 +446,8 @@ struct J3DAnmClusterKeyData { /* 0x08 */ u8 field_0x8; /* 0x0A */ BE(s16) mFrameMax; /* 0x0C */ BE(s32) field_0xc; - /* 0x10 */ OFFSET_PTR mTableOffset; - /* 0x14 */ OFFSET_PTR mWeightOffset; + /* 0x10 */ OFFSET_PTR_V0 mTableOffset; + /* 0x14 */ OFFSET_PTR_V0 mWeightOffset; }; /** @@ -459,8 +459,8 @@ struct J3DAnmClusterFullData { /* 0x08 */ u8 field_0x8; /* 0x0A */ BE(s16) mFrameMax; /* 0x0C */ BE(s32) field_0xc; - /* 0x10 */ OFFSET_PTR mTableOffset; - /* 0x14 */ OFFSET_PTR mWeightOffset; + /* 0x10 */ OFFSET_PTR_V0 mTableOffset; + /* 0x14 */ OFFSET_PTR_V0 mWeightOffset; }; /** @@ -718,7 +718,7 @@ public: // Address to which getAnmVtxColorIndexData pointers are relative. u16* colorAddressBase[2]; - u16* offsetColorIndexAddress(u8 index, OFFSET_PTR ptr) const { + u16* offsetColorIndexAddress(u8 index, OFFSET_PTR_V0 ptr) const { return colorAddressBase[index] + ptr; } #endif @@ -994,6 +994,6 @@ public: /* 0x10 */ f32 mFrame; }; // Size: 0x14 -#undef OFFSET_PTR +#undef OFFSET_PTR_V0 #endif /* J3DANIMATION_H */ diff --git a/libs/JSystem/include/JSystem/J3DGraphAnimator/J3DCluster.h b/libs/JSystem/include/JSystem/J3DGraphAnimator/J3DCluster.h index df7ca2e5c4..4680cd1fff 100644 --- a/libs/JSystem/include/JSystem/J3DGraphAnimator/J3DCluster.h +++ b/libs/JSystem/include/JSystem/J3DGraphAnimator/J3DCluster.h @@ -14,9 +14,9 @@ class J3DAnmCluster; class JUTNameTab; #if TARGET_PC -#define OFFSET_PTR BE(u32) +#define OFFSET_PTR_V0 BE(u32) #else -#define OFFSET_PTR void* +#define OFFSET_PTR_V0 void* #endif /** @@ -49,7 +49,7 @@ public: /* 0x00 */ BE(f32) mMaxAngle; /* 0x04 */ BE(f32) mMinAngle; - /* 0x08 */ OFFSET_PTR mClusterKey; + /* 0x08 */ OFFSET_PTR_V0 mClusterKey; /* 0x0C */ u8 mFlags; /* 0x0E */ u8 field_0xe[0x10 - 0xD]; /* 0x10 */ BE(u16) mKeyNum; @@ -57,9 +57,9 @@ public: /* 0x14 */ BE(u16) field_0x14; /* 0x16 */ BE(u16) field_0x16; #if TARGET_PC - OFFSET_PTR field_0x18; - OFFSET_PTR mClusterVertex; - OFFSET_PTR mDeformer; + OFFSET_PTR_V0 field_0x18; + OFFSET_PTR_V0 mClusterVertex; + OFFSET_PTR_V0 mDeformer; #else /* 0x18 */ u16* field_0x18; /* 0x1C */ J3DClusterVertex* mClusterVertex; @@ -82,8 +82,8 @@ public: /* 0x00 */ BE(u16) mPosNum; /* 0x02 */ BE(u16) mNrmNum; - /* 0x04 */ OFFSET_PTR field_0x4; - /* 0x08 */ OFFSET_PTR field_0x8; + /* 0x04 */ OFFSET_PTR_V0 field_0x4; + /* 0x08 */ OFFSET_PTR_V0 field_0x8; }; // Size: 0x0C /** @@ -144,14 +144,14 @@ public: /* 0x00 */ BE(u16) mNum; #if TARGET_PC - /* 0x04 */ OFFSET_PTR field_0x4; - /* 0x08 */ OFFSET_PTR field_0x8; + /* 0x04 */ OFFSET_PTR_V0 field_0x4; + /* 0x08 */ OFFSET_PTR_V0 field_0x8; #else /* 0x04 */ u16* field_0x4; /* 0x08 */ u16* field_0x8; #endif }; // Size: 0x0C -#undef OFFSET_PTR +#undef OFFSET_PTR_V0 #endif /* J3DCLUSTER_H */ diff --git a/libs/JSystem/include/JSystem/J3DGraphLoader/J3DClusterLoader.h b/libs/JSystem/include/JSystem/J3DGraphLoader/J3DClusterLoader.h index 8fa2681f35..2899065150 100644 --- a/libs/JSystem/include/JSystem/J3DGraphLoader/J3DClusterLoader.h +++ b/libs/JSystem/include/JSystem/J3DGraphLoader/J3DClusterLoader.h @@ -7,9 +7,9 @@ #include "dusk/endian.h" #if TARGET_PC -#define OFFSET_PTR BE(u32) +#define OFFSET_PTR_V0 BE(u32) #else -#define OFFSET_PTR void* +#define OFFSET_PTR_V0 void* #endif /** @@ -31,13 +31,13 @@ public: /* 0x0C */ BE(u16) mClusterVertexNum; /* 0x0E */ BE(u16) mVtxPosNum; /* 0x10 */ BE(u16) mVtxNrmNum; - /* 0x14 */ OFFSET_PTR mClusterPointer; - /* 0x18 */ OFFSET_PTR mClusterKeyPointer; - /* 0x1C */ OFFSET_PTR mClusterVertex; - /* 0x20 */ OFFSET_PTR mVtxPos; - /* 0x24 */ OFFSET_PTR mVtxNrm; - /* 0x28 */ OFFSET_PTR mClusterName; - /* 0x2C */ OFFSET_PTR mClusterKeyName; + /* 0x14 */ OFFSET_PTR_V0 mClusterPointer; + /* 0x18 */ OFFSET_PTR_V0 mClusterKeyPointer; + /* 0x1C */ OFFSET_PTR_V0 mClusterVertex; + /* 0x20 */ OFFSET_PTR_V0 mVtxPos; + /* 0x24 */ OFFSET_PTR_V0 mVtxNrm; + /* 0x28 */ OFFSET_PTR_V0 mClusterName; + /* 0x2C */ OFFSET_PTR_V0 mClusterKeyName; }; /** @@ -67,6 +67,6 @@ public: /* 0x04 */ J3DDeformData* mpDeformData; }; -#undef OFFSET_PTR +#undef OFFSET_PTR_V0 #endif /* J3DCLUSTERLOADER_H */ From 8d06aa8837df4432dec838cdca0730693ca84c13 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sat, 14 Mar 2026 16:43:20 +0100 Subject: [PATCH 038/350] Fix JAUSoundAnimation --- .../JSystem/JAudio2/JAUSoundAnimator.h | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/libs/JSystem/include/JSystem/JAudio2/JAUSoundAnimator.h b/libs/JSystem/include/JSystem/JAudio2/JAUSoundAnimator.h index b9e1d433c9..e81cd70ee4 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JAUSoundAnimator.h +++ b/libs/JSystem/include/JSystem/JAudio2/JAUSoundAnimator.h @@ -2,6 +2,7 @@ #define JAUSOUNDANIMATOR_H #include "JSystem/JAudio2/JAISound.h" +#include "dusk/offset_ptr.h" class JAUSoundAnimation; @@ -62,10 +63,10 @@ public: } /* 0x00 */ JAISoundID mSoundId; - /* 0x04 */ f32 field_0x04; - /* 0x08 */ f32 field_0x08; - /* 0x0C */ f32 field_0x0c; - /* 0x10 */ u32 mFlags; + /* 0x04 */ BE(f32) field_0x04; + /* 0x08 */ BE(f32) field_0x08; + /* 0x0C */ BE(f32) field_0x0c; + /* 0x10 */ BE(u32) mFlags; /* 0x14 */ u8 field_0x14; /* 0x15 */ u8 field_0x15; /* 0x16 */ u8 field_0x16; @@ -97,23 +98,31 @@ public: int getEndSoundIndex(f32) const; u16 getNumSounds() const { +#if TARGET_PC + return mNumSounds; +#else if (mControl != NULL) { return mControl->getNumSounds(this); } else { return mNumSounds; } +#endif } const JAUSoundAnimationSound* getSound(int i_index) const { +#if TARGET_PC + return &mSounds + i_index; +#else if (mControl != NULL) { return mControl->getSound(this, i_index); } else { return &mSounds + i_index; } +#endif } - /* 0x0 */ u16 mNumSounds; - /* 0x4 */ JAUSoundAnimationControl* mControl; + /* 0x0 */ BE(u16) mNumSounds; + /* 0x4 */ OFFSET_PTR(JAUSoundAnimationControl) mControl; /* 0x8 */ JAUSoundAnimationSound mSounds; // actually an array }; From 61b6111954cf65084faab2182a85c1a7e5902747 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sat, 14 Mar 2026 16:43:29 +0100 Subject: [PATCH 039/350] Fix sound userdata pointers being truncated --- include/Z2AudioLib/Z2SoundHandles.h | 2 +- src/Z2AudioLib/Z2SoundHandles.cpp | 2 +- src/Z2AudioLib/Z2SoundObject.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/Z2AudioLib/Z2SoundHandles.h b/include/Z2AudioLib/Z2SoundHandles.h index 2d9d490f5e..5915e5967b 100644 --- a/include/Z2AudioLib/Z2SoundHandles.h +++ b/include/Z2AudioLib/Z2SoundHandles.h @@ -22,7 +22,7 @@ public: bool isActive() const; Z2SoundHandlePool* getHandleSoundID(JAISoundID soundID); - Z2SoundHandlePool* getHandleUserData(u32 userData); + Z2SoundHandlePool* getHandleUserData(uintptr_t userData); void stopAllSounds(u32 fadeTime); diff --git a/src/Z2AudioLib/Z2SoundHandles.cpp b/src/Z2AudioLib/Z2SoundHandles.cpp index 9b6aa5a9ba..de7e265ae7 100644 --- a/src/Z2AudioLib/Z2SoundHandles.cpp +++ b/src/Z2AudioLib/Z2SoundHandles.cpp @@ -40,7 +40,7 @@ Z2SoundHandlePool* Z2SoundHandles::getHandleSoundID(JAISoundID soundID) { return NULL; } -Z2SoundHandlePool* Z2SoundHandles::getHandleUserData(u32 userData) { +Z2SoundHandlePool* Z2SoundHandles::getHandleUserData(uintptr_t userData) { JSULink* i; for (i = getFirst(); i != NULL; i = i->getNext()) { Z2SoundHandlePool* handle = i->getObject(); diff --git a/src/Z2AudioLib/Z2SoundObject.cpp b/src/Z2AudioLib/Z2SoundObject.cpp index 0978842251..4581ca389e 100644 --- a/src/Z2AudioLib/Z2SoundObject.cpp +++ b/src/Z2AudioLib/Z2SoundObject.cpp @@ -500,7 +500,7 @@ void Z2SoundObjAnime::startSoundInner(const JGeometry::TVec3& pos, f32 para JUT_ASSERT(747, curSoundIndex_ < animation_->getNumSounds()); const JAUSoundAnimationSound* animationSound = animation_->getSound(curSoundIndex_); - u32 user_data = (uintptr_t)animationSound; + uintptr_t user_data = (uintptr_t)animationSound; if (reverse_) { curSoundIndex_--; } else { From 3748d61156b605dacfd26f44267af2b37a0707da Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sat, 14 Mar 2026 16:44:04 +0100 Subject: [PATCH 040/350] Disable debug code in JASAramStream --- libs/JSystem/src/JAudio2/JASAramStream.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libs/JSystem/src/JAudio2/JASAramStream.cpp b/libs/JSystem/src/JAudio2/JASAramStream.cpp index 30d4d8da7a..56e3e6effd 100644 --- a/libs/JSystem/src/JAudio2/JASAramStream.cpp +++ b/libs/JSystem/src/JAudio2/JASAramStream.cpp @@ -442,10 +442,12 @@ void JASAramStream::updateChannel(u32 i_callbackType, JASChannel* i_channel, case JASChannel::CB_PLAY: if (i_dspChannel->mResetFlag == 0) { if (i_channel == mPrimaryChannel) { + /* if (JASAudioThread::snIntCount == 1) { OSReportForceEnableOn(); OSReport("mSamplesLeft: %08d, mAramStreamPosition: %08d\n", i_dspChannel->mSamplesLeft, i_dspChannel->mAramStreamPosition); } + */ mChannelUpdateFlags = 0; u32 adjustedSamplesLeft = i_dspChannel->mSamplesLeft + i_dspChannel->mSamplesPerBlock; From 15cc111474148f21dd067f7113f3c6bd16949871 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sat, 14 Mar 2026 17:35:09 +0100 Subject: [PATCH 041/350] Audio debug imgui --- CMakeLists.txt | 3 ++- src/dusk/imgui/ImGuiMenuTools.cpp | 2 ++ src/dusk/imgui/ImGuiMenuTools.hpp | 3 +++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 25b78d60b3..12c4154994 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -106,7 +106,8 @@ target_link_libraries(game_debug PUBLIC aurora::core aurora::gx aurora::gd auror set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) add_library(game SHARED ${DOLZEL_FILES} ${Z2AUDIOLIB_FILES} ${SSYSTEM_FILES} ${JSYSTEM_FILES} ${REL_FILES} ${DUSK_FILES} ${DOLPHIN_FILES} - src/dusk/imgui/ImGuiStubLog.cpp) + src/dusk/imgui/ImGuiStubLog.cpp + src/dusk/imgui/ImGuiAudio.cpp) target_link_libraries(game PRIVATE game_debug cxxopts::cxxopts) target_compile_definitions(game PRIVATE TARGET_PC AVOID_UB=1 VERSION=0 NDEBUG=1 NDEBUG_DEFINED=1 DEBUG_DEFINED=0 diff --git a/src/dusk/imgui/ImGuiMenuTools.cpp b/src/dusk/imgui/ImGuiMenuTools.cpp index 64afcc2ddf..fe21a9088a 100644 --- a/src/dusk/imgui/ImGuiMenuTools.cpp +++ b/src/dusk/imgui/ImGuiMenuTools.cpp @@ -16,6 +16,7 @@ namespace dusk { ImGui::MenuItem("Stub Log", "F5", &m_showStubLog); ImGui::MenuItem("Camera", "F6", &m_showCameraOverlay); ImGui::MenuItem("Map Loader", nullptr, &m_showMapLoader); + ImGui::MenuItem("Audio Debug", "F7", &m_showAudioDebug); ImGui::EndMenu(); } @@ -25,6 +26,7 @@ namespace dusk { ShowHeapOverlay(); ShowStubLog(); ShowMapLoader(); + ShowAudioDebug(); DuskDebugPad(); // temporary, remove later } diff --git a/src/dusk/imgui/ImGuiMenuTools.hpp b/src/dusk/imgui/ImGuiMenuTools.hpp index 1a2e5c705f..d5905cd2f9 100644 --- a/src/dusk/imgui/ImGuiMenuTools.hpp +++ b/src/dusk/imgui/ImGuiMenuTools.hpp @@ -18,6 +18,7 @@ namespace dusk { void ShowHeapOverlay(); void ShowStubLog(); void ShowMapLoader(); + void ShowAudioDebug(); private: bool m_showDebugOverlay = false; @@ -33,6 +34,8 @@ namespace dusk { bool m_showStubLog = false; bool m_showMapLoader = false; + + bool m_showAudioDebug = false; struct { int mapIdx = -1; int regionIdx = -1; From 79a71c6182dfc859156091882d89d1b962c9547d Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sat, 14 Mar 2026 17:38:26 +0100 Subject: [PATCH 042/350] Foiled again --- src/dusk/imgui/ImGuiAudio.cpp | 38 +++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 src/dusk/imgui/ImGuiAudio.cpp diff --git a/src/dusk/imgui/ImGuiAudio.cpp b/src/dusk/imgui/ImGuiAudio.cpp new file mode 100644 index 0000000000..4dfd64fe9c --- /dev/null +++ b/src/dusk/imgui/ImGuiAudio.cpp @@ -0,0 +1,38 @@ +#include "ImGuiConsole.hpp" +#include "ImGuiMenuTools.hpp" +#include "JSystem/JAudio2/JASCriticalSection.h" +#include "JSystem/JAudio2/JASDSPInterface.h" + +void dusk::ImGuiMenuTools::ShowAudioDebug() { + if (!ImGuiConsole::CheckMenuViewToggle(ImGuiKey_F7, m_showAudioDebug)) { + return; + } + + if (!ImGui::Begin("Audio Debug", &m_showAudioDebug)) { + ImGui::End(); + return; + } + + { + JASCriticalSection cs; + for (int i = 0; i < DSP_CHANNELS; i++) { + auto& channel = JASDsp::CH_BUF[i]; + if (!channel.mIsActive) { + continue; + } + + char buf[64]; + snprintf(buf, sizeof(buf), "%d", i); + + if (ImGui::BeginChild(buf, ImVec2(), ImGuiChildFlags_Border | ImGuiChildFlags_AutoResizeY)) { + ImGui::Text("[%02X]", i); + ImGui::TextUnformatted(channel.mLoopFlag ? "Loop: true" : "Loop: false"); + ImGui::Text("Format: %02X/%02X", channel.mSamplesPerBlock, channel.mBytesPerBlock); + } + + ImGui::EndChild(); + } + } + + ImGui::End(); +} \ No newline at end of file From 28f290530623689703849fa85492512c9ebc41ed Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sat, 14 Mar 2026 17:55:19 +0100 Subject: [PATCH 043/350] Use type-level operator delete overload when appropriate in jkrDelete Found by Lars --- libs/JSystem/include/JSystem/JKernel/JKRHeap.h | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/libs/JSystem/include/JSystem/JKernel/JKRHeap.h b/libs/JSystem/include/JSystem/JKernel/JKRHeap.h index db4d40b0a3..99f438b0e2 100644 --- a/libs/JSystem/include/JSystem/JKernel/JKRHeap.h +++ b/libs/JSystem/include/JSystem/JKernel/JKRHeap.h @@ -276,7 +276,18 @@ void jkrDelete(T* ptr) { return; } ptr->~T(); - operator delete(ptr, JKRHeapToken::Dummy); + + if constexpr (requires { T::operator delete(ptr, JKRHeapToken::Dummy); }) { + T::operator delete(ptr, JKRHeapToken::Dummy); + } else if constexpr (requires { T::operator delete(ptr, sizeof(T), JKRHeapToken::Dummy); }) { + T::operator delete(ptr, sizeof(T), JKRHeapToken::Dummy); + } else if constexpr (requires { T::operator delete(ptr); }) { + T::operator delete(ptr); + } else if constexpr (requires { T::operator delete(ptr, sizeof(T)); }) { + T::operator delete(ptr, sizeof(T)); + } else { + operator delete(ptr, JKRHeapToken::Dummy); + } } template<> From 79ddc6815da8349e9c1a7985d5a1fd0f94af2cba Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 15 Mar 2026 01:23:11 +0100 Subject: [PATCH 044/350] Names mixed in with some BE fixes --- include/Z2AudioLib/Z2DebugSys.h | 2 +- .../include/JSystem/JAudio2/JAISeqDataMgr.h | 16 +++--- .../include/JSystem/JAudio2/JASSeqParser.h | 6 +-- .../include/JSystem/JAudio2/JASSeqReader.h | 50 +++++++++--------- .../include/JSystem/JAudio2/JASTrackPort.h | 6 +-- .../include/JSystem/JAudio2/JAUAudibleParam.h | 7 +-- .../JSystem/JAudio2/JAUSeqCollection.h | 22 ++++---- libs/JSystem/src/JAudio2/JAISe.cpp | 2 +- libs/JSystem/src/JAudio2/JAISeq.cpp | 2 +- libs/JSystem/src/JAudio2/JASSeqCtrl.cpp | 6 +-- libs/JSystem/src/JAudio2/JASSeqParser.cpp | 8 +-- libs/JSystem/src/JAudio2/JASSeqReader.cpp | 52 +++++++++---------- libs/JSystem/src/JAudio2/JASTrack.cpp | 4 +- libs/JSystem/src/JAudio2/JASTrackPort.cpp | 12 ++--- libs/JSystem/src/JAudio2/JAUSeqCollection.cpp | 26 +++++----- .../src/JAudio2/JAUSeqDataBlockMgr.cpp | 4 +- 16 files changed, 114 insertions(+), 111 deletions(-) diff --git a/include/Z2AudioLib/Z2DebugSys.h b/include/Z2AudioLib/Z2DebugSys.h index a98292e8c1..d7dd19fcf2 100644 --- a/include/Z2AudioLib/Z2DebugSys.h +++ b/include/Z2AudioLib/Z2DebugSys.h @@ -27,7 +27,7 @@ public: virtual ~Z2HioSeSeqDataMgr() {} virtual SeqDataReturnValue getSeqData(JAISoundID param_1, JAISeqData* param_2) { if (field_0x18->getSeqList()->getSeqData(param_1, param_2)) { - param_2->field_0x4 = 4; + param_2->mOffset = 4; return SeqDataReturnValue_2; } else { return JAUSeqDataMgr_SeqCollection::getSeqData(param_1, param_2); diff --git a/libs/JSystem/include/JSystem/JAudio2/JAISeqDataMgr.h b/libs/JSystem/include/JSystem/JAudio2/JAISeqDataMgr.h index 510f1f4905..a517a58c16 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JAISeqDataMgr.h +++ b/libs/JSystem/include/JSystem/JAudio2/JAISeqDataMgr.h @@ -10,17 +10,17 @@ */ struct JAISeqData { JAISeqData(const void* param_0, u32 param_1) { - field_0x0 = (void*)param_0; - field_0x4 = param_1; + mBase = (void*)param_0; + mOffset = param_1; } void set(const void* param_0, u32 param_1) { - field_0x0 = (void*)param_0; - field_0x4 = param_1; + mBase = (void*)param_0; + mOffset = param_1; } - /* 0x00 */ void* field_0x0; - /* 0x04 */ u32 field_0x4; + /* 0x00 */ void* mBase; + /* 0x04 */ u32 mOffset; }; /** @@ -29,10 +29,10 @@ struct JAISeqData { */ struct JAISeqDataRegion { bool intersects(const JAISeqData& seqData) const { - if ((uintptr_t)addr + size < (uintptr_t)seqData.field_0x0) { + if ((uintptr_t)addr + size < (uintptr_t)seqData.mBase) { return false; } - if ((uintptr_t)seqData.field_0x0 + seqData.field_0x4 < (uintptr_t)addr) { + if ((uintptr_t)seqData.mBase + seqData.mOffset < (uintptr_t)addr) { return false; } return true; diff --git a/libs/JSystem/include/JSystem/JAudio2/JASSeqParser.h b/libs/JSystem/include/JSystem/JAudio2/JASSeqParser.h index 096cbadd98..51db04ba26 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASSeqParser.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASSeqParser.h @@ -14,9 +14,9 @@ public: enum BranchCondition {}; struct CmdInfo { - s32 (JASSeqParser::*field_0x0)(JASTrack*, u32*); - u16 field_0xc; - u16 field_0xe; + s32 (JASSeqParser::*mHandler)(JASTrack*, u32*); + u16 mParameterCount; + u16 mParameterTypes; }; virtual ~JASSeqParser() {} diff --git a/libs/JSystem/include/JSystem/JAudio2/JASSeqReader.h b/libs/JSystem/include/JSystem/JAudio2/JASSeqReader.h index 42a34b9dff..878713602a 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASSeqReader.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASSeqReader.h @@ -3,6 +3,8 @@ #include +#define JAS_SEQ_STACK_SIZE 8 + /** * @ingroup jsystem-jaudio * @@ -19,55 +21,55 @@ public: int readMidiValue(); void jump(u32 param_1) { - field_0x04 = field_0x00 + param_1; + mCurPos = mBase + param_1; } void jump(void* param_1) { - field_0x04 = (u8*)param_1; + mCurPos = (u8*)param_1; } u32 get24(u32 param_0) const { - return (*(u32*)(field_0x00 + param_0 - 1)) & 0xffffff; + return (*(BE(u32)*)(mBase + param_0 - 1)) & 0xffffff; } - u32* getBase() { return (u32*)field_0x00; } - void* getAddr(u32 param_0) { return field_0x00 + param_0; } - u8 getByte(u32 param_0) const { return *(field_0x00 + param_0); } - u16 get16(u32 param_0) const { return *(u16*)(field_0x00 + param_0); } - u32 get32(u32 param_0) const { return *(u32*)(field_0x00 + param_0); } - u8* getCur() { return field_0x04; } - u32 readByte() { return *field_0x04++; } + u32* getBase() { return (u32*)mBase; } + void* getAddr(u32 param_0) { return mBase + param_0; } + u8 getByte(u32 param_0) const { return *(mBase + param_0); } + u16 get16(u32 param_0) const { return *(BE(u16)*)(mBase + param_0); } + u32 get32(u32 param_0) const { return *(BE(u32)*)(mBase + param_0); } + u8* getCur() { return mCurPos; } + u32 readByte() { return *mCurPos++; } u32 read16() { #ifdef __MWERKS__ - return *((u16*)field_0x04)++; + return *((u16*)mCurPos)++; #else - BE(u16)* value = (BE(u16)*)field_0x04; - field_0x04 += 2; + BE(u16)* value = (BE(u16)*)mCurPos; + mCurPos += 2; return *value; #endif } u32 read24() { - field_0x04--; + mCurPos--; #ifdef __MWERKS__ - return (*((u32*)field_0x04)++) & 0x00ffffff; + return (*((u32*)mCurPos)++) & 0x00ffffff; #else - BE(u32)* value = (BE(u32)*)field_0x04; - field_0x04 += 4; + BE(u32)* value = (BE(u32)*)mCurPos; + mCurPos += 4; return (*value) & 0x00ffffff; #endif } u16 getLoopCount() const { - if (field_0x08 == 0) { + if (mCurStackDepth == 0) { return 0; } - return field_0x2c[field_0x08 - 1]; + return mLoopCount[mCurStackDepth - 1]; } - /* 0x00 */ u8* field_0x00; - /* 0x04 */ u8* field_0x04; - /* 0x08 */ u32 field_0x08; - /* 0x0C */ u16* field_0x0c[8]; - /* 0x2C */ u16 field_0x2c[8]; + /* 0x00 */ u8* mBase; + /* 0x04 */ u8* mCurPos; + /* 0x08 */ u32 mCurStackDepth; + /* 0x0C */ u16* mReturnAddr[JAS_SEQ_STACK_SIZE]; + /* 0x2C */ u16 mLoopCount[JAS_SEQ_STACK_SIZE]; }; #endif /* JASSEQREADER_H */ diff --git a/libs/JSystem/include/JSystem/JAudio2/JASTrackPort.h b/libs/JSystem/include/JSystem/JAudio2/JASTrackPort.h index 393a701c27..781da78049 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASTrackPort.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASTrackPort.h @@ -19,12 +19,12 @@ public: u32 checkImport(u32) const; u32 checkExport(u32) const; - u16 get(u32 param_0) const { return field_0x4[param_0]; } - void set(u32 param_0, u16 param_1) { field_0x4[param_0] = param_1; } + u16 get(u32 param_0) const { return mPortValues[param_0]; } + void set(u32 param_0, u16 param_1) { mPortValues[param_0] = param_1; } u16 field_0x0; u16 field_0x2; - u16 field_0x4[MAX_PORTS]; + u16 mPortValues[MAX_PORTS]; }; #endif /* JASTRACKPORT_H */ diff --git a/libs/JSystem/include/JSystem/JAudio2/JAUAudibleParam.h b/libs/JSystem/include/JSystem/JAudio2/JAUAudibleParam.h index 89b016cb0a..50f5b45f09 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JAUAudibleParam.h +++ b/libs/JSystem/include/JSystem/JAudio2/JAUAudibleParam.h @@ -2,6 +2,7 @@ #define JAUAUDIBLEPARAM_H #include +#include "dusk/endian.h" /** * @ingroup jsystem-jaudio @@ -14,8 +15,8 @@ struct JAUAudibleParam { union { struct { - u16 f0; - u16 f1; + BE(u16) f0; + BE(u16) f1; } half; struct { u8 b0_0 : 4; @@ -29,7 +30,7 @@ struct JAUAudibleParam { u8 b2; u8 b3; } bytes; - u32 raw; + BE(u32) raw; } field_0x0; }; diff --git a/libs/JSystem/include/JSystem/JAudio2/JAUSeqCollection.h b/libs/JSystem/include/JSystem/JAudio2/JAUSeqCollection.h index 1812976e2f..bfc88ef3ad 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JAUSeqCollection.h +++ b/libs/JSystem/include/JSystem/JAudio2/JAUSeqCollection.h @@ -11,11 +11,11 @@ struct JAISeqDataRegion; * */ struct JAUSeqCollectionData { - s8 field_0x0; - s8 field_0x1; - BE(u16) field_0x2; - BE(u32) field_0x4; - BE(u32) field_0x8; + s8 mMagic1; // 'S' + s8 mMagic2; // 'C' + BE(u16) mNumSoundCategories; + BE(u32) mSectionSize; + BE(u32) mTableOffsets[0]; // VLA }; /** @@ -29,12 +29,12 @@ public: bool getSeqData(int, int, JAISeqData*); bool getSeqDataRegion(JAISeqDataRegion*); - bool isValid() const { return field_0x8; } + bool isValid() const { return mHeader; } - /* 0x00 */ u16 field_0x0; - /* 0x04 */ const BE(u32)* field_0x4; - /* 0x08 */ const JAUSeqCollectionData* field_0x8; - /* 0x0C */ int field_0xc; + /* 0x00 */ u16 mNumSoundCategories; + /* 0x04 */ const BE(u32)* mTableOffsets; + /* 0x08 */ const JAUSeqCollectionData* mHeader; + /* 0x0C */ u32 mSectionSize; }; /** @@ -49,7 +49,7 @@ public: SeqDataReturnValue getSeqData(JAISoundID, JAISeqData*); ~JAUSeqDataMgr_SeqCollection(); - const void* getResource() const { return field_0x4; } + const void* getResource() const { return mTableOffsets; } void init(const void* param_1) { JAUSeqCollection::init(param_1); } /* 0x14 */ JAISeqDataUser* user_; diff --git a/libs/JSystem/src/JAudio2/JAISe.cpp b/libs/JSystem/src/JAudio2/JAISe.cpp index 4e453811a9..504c875bad 100644 --- a/libs/JSystem/src/JAudio2/JAISe.cpp +++ b/libs/JSystem/src/JAudio2/JAISe.cpp @@ -57,7 +57,7 @@ void JAISe::startTrack_(const JASSoundParams& params) { } inner_.field_0x26c = 1; - inner_.track.setSeqData(inner_.mSeqData.field_0x0, inner_.mSeqData.field_0x4); + inner_.track.setSeqData(inner_.mSeqData.mBase, inner_.mSeqData.mOffset); inner_.track.startSeq(); } diff --git a/libs/JSystem/src/JAudio2/JAISeq.cpp b/libs/JSystem/src/JAudio2/JAISeq.cpp index e3d07d5418..c05494b82e 100644 --- a/libs/JSystem/src/JAudio2/JAISeq.cpp +++ b/libs/JSystem/src/JAudio2/JAISeq.cpp @@ -40,7 +40,7 @@ void JAISeq::JAISeqMgr_startID_(JAISoundID id, const JGeometry::TVec3* posP void JAISeq::playSeqData_(const JASSoundParams& params, JAISoundActivity activity) { JUT_ASSERT(72, JASTrack_isFreeOrStopped( & inner_.outputTrack )); - inner_.outputTrack.setSeqData(inner_.mSeqData.field_0x0, inner_.mSeqData.field_0x4); + inner_.outputTrack.setSeqData(inner_.mSeqData.mBase, inner_.mSeqData.mOffset); if (audible_) { initTrack_JAISound_(&inner_.outputTrack); } else { diff --git a/libs/JSystem/src/JAudio2/JASSeqCtrl.cpp b/libs/JSystem/src/JAudio2/JASSeqCtrl.cpp index 044f3873f2..a4e4a4b441 100644 --- a/libs/JSystem/src/JAudio2/JASSeqCtrl.cpp +++ b/libs/JSystem/src/JAudio2/JASSeqCtrl.cpp @@ -34,9 +34,9 @@ void JASSeqCtrl::init() { field_0x51 = 0; } -void JASSeqCtrl::start(void* param_0, u32 param_1) { - mReader.init(param_0); - mReader.jump(param_1); +void JASSeqCtrl::start(void* base, u32 offset) { + mReader.init(base); + mReader.jump(offset); } int JASSeqCtrl::tickProc(JASTrack* param_0) { diff --git a/libs/JSystem/src/JAudio2/JASSeqParser.cpp b/libs/JSystem/src/JAudio2/JASSeqParser.cpp index ea91cc06ae..21cb551cb0 100644 --- a/libs/JSystem/src/JAudio2/JASSeqParser.cpp +++ b/libs/JSystem/src/JAudio2/JASSeqParser.cpp @@ -959,10 +959,10 @@ s32 JASSeqParser::parseCommand(JASTrack* param_0, u8 cmd, u16 param_2) { } else { cmdInfo = &sExtCmdInfo[seqCtrl->readByte() & 0xff]; } - u16 r28 = (u16)cmdInfo->field_0xe; + u16 r28 = (u16)cmdInfo->mParameterTypes; r28 |= param_2; u32 stack_28[8]; - for (int i = 0; i < cmdInfo->field_0xc; i++, r28 >>= 2) { + for (int i = 0; i < cmdInfo->mParameterCount; i++, r28 >>= 2) { int r27 = 0; switch (r28 & 3) { case 0: @@ -980,11 +980,11 @@ s32 JASSeqParser::parseCommand(JASTrack* param_0, u8 cmd, u16 param_2) { } stack_28[i] = r27; } - s32 (JASSeqParser::*ptr)(JASTrack*, u32*) = cmdInfo->field_0x0; + s32 (JASSeqParser::*ptr)(JASTrack*, u32*) = cmdInfo->mHandler; if (!ptr) { return 0; } - return execCommand(param_0, ptr, cmdInfo->field_0xc, stack_28); + return execCommand(param_0, ptr, cmdInfo->mParameterCount, stack_28); } s32 JASSeqParser::parseRegCommand(JASTrack* param_0, int param_1) { diff --git a/libs/JSystem/src/JAudio2/JASSeqReader.cpp b/libs/JSystem/src/JAudio2/JASSeqReader.cpp index c259c57d40..459c2edeee 100644 --- a/libs/JSystem/src/JAudio2/JASSeqReader.cpp +++ b/libs/JSystem/src/JAudio2/JASSeqReader.cpp @@ -8,81 +8,81 @@ #include "JSystem/JAudio2/JASSeqReader.h" void JASSeqReader::init() { - field_0x00 = 0; - field_0x04 = 0; - field_0x08 = 0; + mBase = 0; + mCurPos = 0; + mCurStackDepth = 0; - for (int i = 0; i < 8; i++) { - field_0x0c[i] = NULL; - field_0x2c[i] = 0; + for (int i = 0; i < JAS_SEQ_STACK_SIZE; i++) { + mReturnAddr[i] = NULL; + mLoopCount[i] = 0; } } -void JASSeqReader::init(void* param_0) { - field_0x00 = (u8*)param_0; - field_0x04 = field_0x00; - field_0x08 = 0; +void JASSeqReader::init(void* base) { + mBase = (u8*)base; + mCurPos = mBase; + mCurStackDepth = 0; - for (int i = 0; i < 8; i++) { - field_0x0c[i] = NULL; - field_0x2c[i] = 0; + for (int i = 0; i < JAS_SEQ_STACK_SIZE; i++) { + mReturnAddr[i] = NULL; + mLoopCount[i] = 0; } } bool JASSeqReader::call(u32 param_0) { - if (field_0x08 >= 8) { + if (mCurStackDepth >= JAS_SEQ_STACK_SIZE) { JUT_WARN(42, "%s", "Cannot exec call command"); return false; } - field_0x0c[field_0x08++] = (u16*)field_0x04; - field_0x04 = field_0x00 + param_0; + mReturnAddr[mCurStackDepth++] = (u16*)mCurPos; + mCurPos = mBase + param_0; return true; } bool JASSeqReader::loopStart(u32 param_0) { - if (8 <= field_0x08) { + if (JAS_SEQ_STACK_SIZE <= mCurStackDepth) { JUT_WARN(53, "%s", "Cannot exec loopStart command"); return false; } - field_0x0c[field_0x08] = (u16*)field_0x04; - field_0x2c[field_0x08++] = param_0; + mReturnAddr[mCurStackDepth] = (u16*)mCurPos; + mLoopCount[mCurStackDepth++] = param_0; return true; } bool JASSeqReader::loopEnd() { - if (field_0x08 == 0) { + if (mCurStackDepth == 0) { JUT_WARN(65, "%s", "cannot loopE for call-stack is NULL"); return false; } - u16 tmp = field_0x2c[field_0x08 - 1]; + u16 tmp = mLoopCount[mCurStackDepth - 1]; if (tmp != 0) { tmp--; } if (!tmp) { - field_0x08--; + mCurStackDepth--; return true; } - field_0x2c[field_0x08 - 1] = tmp; - field_0x04 = (u8*)field_0x0c[field_0x08 - 1]; + mLoopCount[mCurStackDepth - 1] = tmp; + mCurPos = (u8*)mReturnAddr[mCurStackDepth - 1]; return true; } bool JASSeqReader::ret() { - if (field_0x08 == 0) { + if (mCurStackDepth == 0) { return false; } - field_0x04 = (u8*)field_0x0c[--field_0x08]; + mCurPos = (u8*)mReturnAddr[--mCurStackDepth]; return true; } diff --git a/libs/JSystem/src/JAudio2/JASTrack.cpp b/libs/JSystem/src/JAudio2/JASTrack.cpp index a60577ca38..68fbee63e5 100644 --- a/libs/JSystem/src/JAudio2/JASTrack.cpp +++ b/libs/JSystem/src/JAudio2/JASTrack.cpp @@ -171,9 +171,9 @@ void JASTrack::assignExtBuffer(u32 index, JASSoundParams* i_soundParams) { mChannelMgrs[index]->mSoundParams = i_soundParams; } -void JASTrack::setSeqData(void* param_0, u32 param_1) { +void JASTrack::setSeqData(void* base, u32 offset) { JUT_ASSERT(257, mStatus == STATUS_FREE); - mSeqCtrl.start(param_0, param_1); + mSeqCtrl.start(base, offset); } void JASTrack::startSeq() { diff --git a/libs/JSystem/src/JAudio2/JASTrackPort.cpp b/libs/JSystem/src/JAudio2/JASTrackPort.cpp index e822212ec9..bde86b37f9 100644 --- a/libs/JSystem/src/JAudio2/JASTrackPort.cpp +++ b/libs/JSystem/src/JAudio2/JASTrackPort.cpp @@ -3,8 +3,8 @@ #include "JSystem/JAudio2/JASTrackPort.h" void JASTrackPort::init() { - for (int i = 0; i < 16; i++) { - field_0x4[i] = 0; + for (int i = 0; i < MAX_PORTS; i++) { + mPortValues[i] = 0; } field_0x0 = 0; field_0x2 = 0; @@ -13,25 +13,25 @@ void JASTrackPort::init() { u16 JASTrackPort::readImport(u32 port_num) { JUT_ASSERT(27, port_num < MAX_PORTS); field_0x0 = field_0x0 & ~(1 << port_num); - return field_0x4[port_num]; + return mPortValues[port_num]; } u16 JASTrackPort::readExport(u32 port_num) { JUT_ASSERT(34, port_num < MAX_PORTS); field_0x2 = field_0x2 & ~(1 << port_num); - return field_0x4[port_num]; + return mPortValues[port_num]; } void JASTrackPort::writeImport(u32 port_num, u16 param_1) { JUT_ASSERT(41, port_num < MAX_PORTS); field_0x0 = field_0x0 | (1 << port_num); - field_0x4[port_num] = param_1; + mPortValues[port_num] = param_1; } void JASTrackPort::writeExport(u32 port_num, u16 param_1) { JUT_ASSERT(47, port_num < MAX_PORTS); field_0x2 = field_0x2 | (1 << port_num); - field_0x4[port_num] = param_1; + mPortValues[port_num] = param_1; } u32 JASTrackPort::checkImport(u32 param_0) const { diff --git a/libs/JSystem/src/JAudio2/JAUSeqCollection.cpp b/libs/JSystem/src/JAudio2/JAUSeqCollection.cpp index 1301cbc4fd..a6071a75e6 100644 --- a/libs/JSystem/src/JAudio2/JAUSeqCollection.cpp +++ b/libs/JSystem/src/JAudio2/JAUSeqCollection.cpp @@ -4,39 +4,39 @@ #include "JSystem/JUtility/JUTAssert.h" JAUSeqCollection::JAUSeqCollection() { - field_0x8 = NULL; + mHeader = NULL; } void JAUSeqCollection::init(void const* param_0) { - field_0x8 = (const JAUSeqCollectionData*)param_0; - if (field_0x8->field_0x0 != 0x53 || field_0x8->field_0x1 != 0x43) { - field_0x8 = NULL; + mHeader = (const JAUSeqCollectionData*)param_0; + if (mHeader->mMagic1 != 0x53 || mHeader->mMagic2 != 0x43) { + mHeader = NULL; return; } - field_0x0 = field_0x8->field_0x2; - field_0xc = field_0x8->field_0x4; - field_0x4 = &field_0x8->field_0x8; + mNumSoundCategories = mHeader->mNumSoundCategories; + mSectionSize = mHeader->mSectionSize; + mTableOffsets = mHeader->mTableOffsets; } bool JAUSeqCollection::getSeqData(int param_0, int param_1, JAISeqData* param_2) { - if (param_0 >= field_0x0) { + if (param_0 >= mNumSoundCategories) { return false; } - u32 r29 = field_0x4[param_0]; - BE(u32)* puVar2 = (BE(u32)*)((u8*)field_0x8 + r29); + u32 r29 = mTableOffsets[param_0]; + BE(u32)* puVar2 = (BE(u32)*)((u8*)mHeader + r29); if (param_1 >= puVar2[0]) { return false; } - param_2->set((void*)field_0x8, puVar2[param_1 + 1]); + param_2->set((void*)mHeader, puVar2[param_1 + 1]); return true; } bool JAUSeqCollection::getSeqDataRegion(JAISeqDataRegion* param_0) { if (isValid()) { - param_0->addr = (u8*)field_0x8; - param_0->size = field_0xc; + param_0->addr = (u8*)mHeader; + param_0->size = mSectionSize; return true; } return false; diff --git a/libs/JSystem/src/JAudio2/JAUSeqDataBlockMgr.cpp b/libs/JSystem/src/JAudio2/JAUSeqDataBlockMgr.cpp index 2fafe9a4bc..698c4aff65 100644 --- a/libs/JSystem/src/JAudio2/JAUSeqDataBlockMgr.cpp +++ b/libs/JSystem/src/JAudio2/JAUSeqDataBlockMgr.cpp @@ -87,8 +87,8 @@ s32 JAUDynamicSeqDataBlocks::getSeqData(JAISoundID param_0, JAISeqDataUser* para u8* seqData = mLoadedBlocks.getSeqData(param_0); if (seqData != NULL) { - param_2->field_0x0 = seqData; - param_2->field_0x4 = 0; + param_2->mBase = seqData; + param_2->mOffset = 0; return 2; } From 71bd970d9a2c2cf6c75aab3e3b83d4ba5d98110b Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 15 Mar 2026 01:32:13 +0100 Subject: [PATCH 045/350] Implement DSP pause somewhat --- src/dusk/audio/DuskDsp.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/dusk/audio/DuskDsp.cpp b/src/dusk/audio/DuskDsp.cpp index 5b4c5d0730..80e7ffe028 100644 --- a/src/dusk/audio/DuskDsp.cpp +++ b/src/dusk/audio/DuskDsp.cpp @@ -92,6 +92,12 @@ void dusk::audio::DspRender(DspSubframe& subframe) { continue; } + if (channel.mPauseFlag) { + // Not really sure what the practical difference between pause and + // deactivation is. Either avoids clearing state or allows the DSP to avoid popping? + continue; + } + ValidateChannel(channel); DspSubframe channelSubframe = {}; From f00edb2fa7e35818f949d21306b56973db78a49c Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 15 Mar 2026 17:56:26 +0100 Subject: [PATCH 046/350] Un-stub audio wave loading code guh --- src/Z2AudioLib/Z2SceneMgr.cpp | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/Z2AudioLib/Z2SceneMgr.cpp b/src/Z2AudioLib/Z2SceneMgr.cpp index 776a0c2d69..9e3bf2c91c 100644 --- a/src/Z2AudioLib/Z2SceneMgr.cpp +++ b/src/Z2AudioLib/Z2SceneMgr.cpp @@ -2096,10 +2096,6 @@ bool Z2SceneMgr::loadSceneWave(u32 wave, u32 bank) { #endif bool Z2SceneMgr::loadSeWave(u32 wave) { - return 0; -} - -/* //lw stub lw JAUSectionHeap* sectionHeap = JASGlobalInstance::getInstance(); JUT_ASSERT(3030, sectionHeap); @@ -2114,13 +2110,8 @@ bool Z2SceneMgr::loadSeWave(u32 wave) { JUT_WARN_DEVICE(3038, 1, "Z2SceneMgr::cannot load SE wave:%d\n", wave); return false; } -*/ + bool Z2SceneMgr::loadBgmWave(u32 wave) { - return true; -} - - /* //lw stub lw - JAUSectionHeap* sectionHeap = JASGlobalInstance::getInstance(); JUT_ASSERT(3047, sectionHeap); @@ -2134,4 +2125,4 @@ bool Z2SceneMgr::loadBgmWave(u32 wave) { JUT_WARN_DEVICE(3055, 1, "Z2SceneMgr::cannot load BGM wave:%d\n", wave); return false; -} */ +} From b939a7caaf4a6a48c51174e5e334d408c7a78b55 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 15 Mar 2026 17:57:44 +0100 Subject: [PATCH 047/350] More audio debug imgui that idk how useful it is --- src/dusk/imgui/ImGuiAudio.cpp | 130 ++++++++++++++++++++++++++++++---- 1 file changed, 118 insertions(+), 12 deletions(-) diff --git a/src/dusk/imgui/ImGuiAudio.cpp b/src/dusk/imgui/ImGuiAudio.cpp index 4dfd64fe9c..56976bce55 100644 --- a/src/dusk/imgui/ImGuiAudio.cpp +++ b/src/dusk/imgui/ImGuiAudio.cpp @@ -1,7 +1,106 @@ #include "ImGuiConsole.hpp" #include "ImGuiMenuTools.hpp" +#include "JSystem/JAudio2/JAISeMgr.h" +#include "JSystem/JAudio2/JAIStreamMgr.h" #include "JSystem/JAudio2/JASCriticalSection.h" #include "JSystem/JAudio2/JASDSPInterface.h" +#include "JSystem/JAudio2/JASTrack.h" + +static void ShowAllDspChannels() { + for (int i = 0; i < DSP_CHANNELS; i++) { + auto& channel = JASDsp::CH_BUF[i]; + if (!channel.mIsActive) { + continue; + } + + char buf[64]; + snprintf(buf, sizeof(buf), "%d", i); + + if (ImGui::BeginChild(buf, ImVec2(), ImGuiChildFlags_Border | ImGuiChildFlags_AutoResizeY)) { + ImGui::Text("[%02X]", i); + ImGui::TextUnformatted(channel.mLoopFlag ? "Loop: true" : "Loop: false"); + ImGui::Text("Format: %02X/%02X", channel.mSamplesPerBlock, channel.mBytesPerBlock); + ImGui::Text("Position: %08X/%08X", channel.mSamplePosition, channel.mEndSample); + ImGui::Text("Memory: %08X/%08X", channel.mWaveAramAddress, channel.mAramStreamPosition); + } + + ImGui::EndChild(); + } +} + +static void ShowAllTracks() { + if (ImGui::Button("Pause all")) { + for (auto& track : JASTrack::sTrackList) { + track.pause(true); + } + } + + for (auto& track : JASTrack::sTrackList) { + char buf[32]; + snprintf(buf, sizeof(buf), "%p", &track); + + if (ImGui::BeginChild(buf, ImVec2(), ImGuiChildFlags_Border | ImGuiChildFlags_AutoResizeY)) { + ImGui::Text("[%p]", &track); + bool paused = track.mFlags.pause; + ImGui::Checkbox("Paused", &paused); + track.mFlags.pause = paused; + + for (int i = 0; i < JASTrack::MAX_CHILDREN; i++) { + const auto child = track.getChild(i); + if (child != nullptr) { + ImGui::Text("child: [%p]", child); + } + } + } + + ImGui::EndChild(); + } +} + +static void ShowAllJAIStreams() { + auto& mgr = *JAIStreamMgr::getInstance(); + + for (auto streamLink = mgr.getStreamList()->getFirst(); streamLink != nullptr; streamLink = streamLink->getNext()) { + auto& stream = *streamLink->getObject(); + char buf[32]; + snprintf(buf, sizeof(buf), "%p", &stream); + + if (ImGui::BeginChild(buf, ImVec2(), ImGuiChildFlags_Border | ImGuiChildFlags_AutoResizeY)) { + ImGui::Text("[%p]", &stream); + bool paused = stream.status_.field_0x0.flags.paused; + ImGui::Checkbox("Paused", &paused); + stream.status_.field_0x0.flags.paused = paused; + } + + ImGui::EndChild(); + } +} + +static void ShowAllJAISes() { + auto& mgr = *JAISeMgr::getInstance(); + + for (int i = 0; i < JAISeMgr::NUM_CATEGORIES; i++) { + const auto category = mgr.getCategory(i); + + char buf[32]; + snprintf(buf, sizeof(buf), "%i", i); + + if (ImGui::BeginChild(buf, ImVec2(), ImGuiChildFlags_Border | ImGuiChildFlags_AutoResizeY)) { + ImGui::Text("Category: %i", i); + if (ImGui::Button("Pause All")) { + category->pause(true); + } + + for (auto seLink = category->getSeList()->getFirst(); seLink != nullptr; seLink = seLink->getNext()) { + const auto se = seLink->getObject(); + ImGui::Text("[%p]", se); + ImGui::Text(se->status_.field_0x0.flags.paused ? "Paused" : "Not paused"); + } + } + + ImGui::EndChild(); + } +} void dusk::ImGuiMenuTools::ShowAudioDebug() { if (!ImGuiConsole::CheckMenuViewToggle(ImGuiKey_F7, m_showAudioDebug)) { @@ -15,22 +114,29 @@ void dusk::ImGuiMenuTools::ShowAudioDebug() { { JASCriticalSection cs; - for (int i = 0; i < DSP_CHANNELS; i++) { - auto& channel = JASDsp::CH_BUF[i]; - if (!channel.mIsActive) { - continue; + + if (ImGui::BeginTabBar("Tabs")) { + if (ImGui::BeginTabItem("DSP channels")) { + ShowAllDspChannels(); + ImGui::EndTabItem(); } - char buf[64]; - snprintf(buf, sizeof(buf), "%d", i); - - if (ImGui::BeginChild(buf, ImVec2(), ImGuiChildFlags_Border | ImGuiChildFlags_AutoResizeY)) { - ImGui::Text("[%02X]", i); - ImGui::TextUnformatted(channel.mLoopFlag ? "Loop: true" : "Loop: false"); - ImGui::Text("Format: %02X/%02X", channel.mSamplesPerBlock, channel.mBytesPerBlock); + if (ImGui::BeginTabItem("JAITrack")) { + ShowAllTracks(); + ImGui::EndTabItem(); } - ImGui::EndChild(); + if (ImGui::BeginTabItem("JAIStream")) { + ShowAllJAIStreams(); + ImGui::EndTabItem(); + } + + if (ImGui::BeginTabItem("JAISe")) { + ShowAllJAISes(); + ImGui::EndTabItem(); + } + + ImGui::EndTabBar(); } } From 7b01de257c44d7c7694fc6090250d77376aed357 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 15 Mar 2026 17:59:11 +0100 Subject: [PATCH 048/350] More minor naming --- libs/JSystem/src/JAudio2/JASSeqParser.cpp | 39 +++++++++++++++-------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/libs/JSystem/src/JAudio2/JASSeqParser.cpp b/libs/JSystem/src/JAudio2/JASSeqParser.cpp index 21cb551cb0..1c80ea52c3 100644 --- a/libs/JSystem/src/JAudio2/JASSeqParser.cpp +++ b/libs/JSystem/src/JAudio2/JASSeqParser.cpp @@ -937,6 +937,9 @@ s32 JASSeqParser::parseNoteOff(JASTrack* param_0, u8 param_1) { s32 JASSeqParser::parseNoteOn(JASTrack* param_0, u8 param_1) { JASSeqCtrl* seqCtrl = param_0->getSeqCtrl(); + if (seqCtrl->trace) { + printf("Guh\n"); + } u32 r28 = seqCtrl->readByte(); u32 r30 = r28; r30 &= 7; @@ -950,7 +953,7 @@ s32 JASSeqParser::parseNoteOn(JASTrack* param_0, u8 param_1) { return 0; } -s32 JASSeqParser::parseCommand(JASTrack* param_0, u8 cmd, u16 param_2) { +s32 JASSeqParser::parseCommand(JASTrack* param_0, u8 cmd, u16 parameterTypesOverride) { JASSeqCtrl* seqCtrl = param_0->getSeqCtrl(); CmdInfo* cmdInfo = NULL; if (cmd != 0xb0) { @@ -959,32 +962,32 @@ s32 JASSeqParser::parseCommand(JASTrack* param_0, u8 cmd, u16 param_2) { } else { cmdInfo = &sExtCmdInfo[seqCtrl->readByte() & 0xff]; } - u16 r28 = (u16)cmdInfo->mParameterTypes; - r28 |= param_2; - u32 stack_28[8]; - for (int i = 0; i < cmdInfo->mParameterCount; i++, r28 >>= 2) { - int r27 = 0; - switch (r28 & 3) { + u16 parameterTypes = (u16)cmdInfo->mParameterTypes; + parameterTypes |= parameterTypesOverride; + u32 args[8]; + for (int i = 0; i < cmdInfo->mParameterCount; i++, parameterTypes >>= 2) { + int value = 0; + switch (parameterTypes & 3) { case 0: - r27 = (u8)seqCtrl->readByte(); + value = (u8)seqCtrl->readByte(); break; case 1: - r27 = (u16)seqCtrl->read16(); + value = (u16)seqCtrl->read16(); break; case 2: - r27 = seqCtrl->read24(); + value = seqCtrl->read24(); break; case 3: - r27 = readReg(param_0, (u8)seqCtrl->readByte()); + value = readReg(param_0, (u8)seqCtrl->readByte()); break; } - stack_28[i] = r27; + args[i] = value; } s32 (JASSeqParser::*ptr)(JASTrack*, u32*) = cmdInfo->mHandler; if (!ptr) { return 0; } - return execCommand(param_0, ptr, cmdInfo->mParameterCount, stack_28); + return execCommand(param_0, ptr, cmdInfo->mParameterCount, args); } s32 JASSeqParser::parseRegCommand(JASTrack* param_0, int param_1) { @@ -1004,7 +1007,15 @@ s32 JASSeqParser::parseRegCommand(JASTrack* param_0, int param_1) { } s32 JASSeqParser::parse(JASTrack* param_0) { - u32 r31 = param_0->getSeqCtrl()->readByte(); + JASSeqCtrl* ctrl = param_0->getSeqCtrl(); + u32 base = ctrl->mReader.mCurPos - ctrl->mReader.mBase; + u32 r31 = ctrl->readByte(); + + if (ctrl->trace) { + OSReportForceEnableOn(); + OSReport("%06X: %02X\n", base, r31); + fflush(stdout); + } s32 r30 = 0; if ((r31 & 0x80) == 0) { r30 = parseNoteOn(param_0, r31); From 496742bdd0938f2335218cf96ac9a57b6603e720 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 15 Mar 2026 18:41:48 +0100 Subject: [PATCH 049/350] Oops --- libs/JSystem/src/JAudio2/JASSeqParser.cpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/libs/JSystem/src/JAudio2/JASSeqParser.cpp b/libs/JSystem/src/JAudio2/JASSeqParser.cpp index 1c80ea52c3..05e63acc05 100644 --- a/libs/JSystem/src/JAudio2/JASSeqParser.cpp +++ b/libs/JSystem/src/JAudio2/JASSeqParser.cpp @@ -937,9 +937,6 @@ s32 JASSeqParser::parseNoteOff(JASTrack* param_0, u8 param_1) { s32 JASSeqParser::parseNoteOn(JASTrack* param_0, u8 param_1) { JASSeqCtrl* seqCtrl = param_0->getSeqCtrl(); - if (seqCtrl->trace) { - printf("Guh\n"); - } u32 r28 = seqCtrl->readByte(); u32 r30 = r28; r30 &= 7; @@ -1010,12 +1007,6 @@ s32 JASSeqParser::parse(JASTrack* param_0) { JASSeqCtrl* ctrl = param_0->getSeqCtrl(); u32 base = ctrl->mReader.mCurPos - ctrl->mReader.mBase; u32 r31 = ctrl->readByte(); - - if (ctrl->trace) { - OSReportForceEnableOn(); - OSReport("%06X: %02X\n", base, r31); - fflush(stdout); - } s32 r30 = 0; if ((r31 & 0x80) == 0) { r30 = parseNoteOn(param_0, r31); From e308dfda9d5b524718092a818a8fac702344c6e8 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 15 Mar 2026 20:35:06 +0100 Subject: [PATCH 050/350] Allow decoding less PCM samples than a full frame --- src/dusk/audio/Adpcm.cpp | 5 +++-- src/dusk/audio/DuskDsp.cpp | 4 +++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/dusk/audio/Adpcm.cpp b/src/dusk/audio/Adpcm.cpp index f4318d5d59..67b9ac9fd3 100644 --- a/src/dusk/audio/Adpcm.cpp +++ b/src/dusk/audio/Adpcm.cpp @@ -33,8 +33,7 @@ static s16 Clamp16(s32 value) { void dusk::audio::Adpcm4ToPcm16(const u8* adpcm, size_t adpcmLength, s16* pcm, size_t pcmLength, s16& hist2, s16& hist1) { assert (adpcmLength % AdpcmFrameSize == 0 && "ADPCM must be divisible by frame size"); - const auto expectedPcmLength = (adpcmLength / AdpcmFrameSize) * 16; - assert (expectedPcmLength <= pcmLength && "PCM output buffer is too small!"); + auto endPtr = pcm + pcmLength; for (int i = 0; i < adpcmLength; i += AdpcmFrameSize) { u8 header = adpcm[i]; @@ -55,6 +54,8 @@ void dusk::audio::Adpcm4ToPcm16(const u8* adpcm, size_t adpcmLength, s16* pcm, s hist1 = sample; *pcm++ = sample; + if (endPtr == pcm) + return; } } } diff --git a/src/dusk/audio/DuskDsp.cpp b/src/dusk/audio/DuskDsp.cpp index 80e7ffe028..a1fe8545dd 100644 --- a/src/dusk/audio/DuskDsp.cpp +++ b/src/dusk/audio/DuskDsp.cpp @@ -46,7 +46,9 @@ static u32 ConvertDataLengthToSamples(const JASDsp::TChannel& channel, u32 dataL static u32 ConvertSamplesToDataLength(const JASDsp::TChannel& channel, u32 samples) { if (samples % channel.mSamplesPerBlock != 0) { - CRASH("Indivisible sample count: %d\n", samples); + // Ensure we round up. + samples += channel.mSamplesPerBlock; + //CRASH("Indivisible sample count: %d\n", samples); } return (samples / channel.mSamplesPerBlock) * channel.mBytesPerBlock; From c88fa61213b476f93721c47c4a077fd973522dd5 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 15 Mar 2026 20:35:30 +0100 Subject: [PATCH 051/350] Basic volume control, no panning yet --- src/dusk/audio/DuskDsp.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/dusk/audio/DuskDsp.cpp b/src/dusk/audio/DuskDsp.cpp index a1fe8545dd..3692834b13 100644 --- a/src/dusk/audio/DuskDsp.cpp +++ b/src/dusk/audio/DuskDsp.cpp @@ -7,6 +7,7 @@ #include #include "Adpcm.hpp" +#include "JSystem/JAudio2/JASDriverIF.h" #include "global.h" using namespace dusk::audio; @@ -187,6 +188,16 @@ static void RenderChannel( channelAux.resampleStream, subframe.data(), static_cast(subframe.size() * sizeof(s16))); + + for (auto& sample : subframe) { + u16 volume; + if (channel.mAutoMixerBeenSet) { + volume = channel.mAutoMixerVolume; + } else { + volume = channel.mOutputChannels[0].mTargetVolume; + } + sample = (s16)((s64)sample * volume / JASDriver::getChannelLevel_dsp()); + } } void dusk::audio::DspInit() { From 859d99d657754af430bd958fbf7ce5c858e6f277 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 15 Mar 2026 20:36:04 +0100 Subject: [PATCH 052/350] Ignore oscillator channels Idk if these show up yet but they did when the BMS stuff was broken --- src/dusk/audio/DuskDsp.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/dusk/audio/DuskDsp.cpp b/src/dusk/audio/DuskDsp.cpp index 3692834b13..25bc822e9f 100644 --- a/src/dusk/audio/DuskDsp.cpp +++ b/src/dusk/audio/DuskDsp.cpp @@ -101,6 +101,12 @@ void dusk::audio::DspRender(DspSubframe& subframe) { continue; } + if (channel.mBytesPerBlock == 0) { + // I think these are oscillator channels? Not backed by audio. + channel.mIsFinished = true; + continue; + } + ValidateChannel(channel); DspSubframe channelSubframe = {}; From 5d355baae884c04f2c0499ed7cdb94d1352d25ac Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Mon, 16 Mar 2026 13:57:04 +0100 Subject: [PATCH 053/350] Output as F32 This completely fucks up audio. Pending debugging to figure out why --- src/dusk/audio/DuskAudioSystem.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/dusk/audio/DuskAudioSystem.cpp b/src/dusk/audio/DuskAudioSystem.cpp index 9b089eb807..ae23b995f3 100644 --- a/src/dusk/audio/DuskAudioSystem.cpp +++ b/src/dusk/audio/DuskAudioSystem.cpp @@ -33,7 +33,7 @@ static void InitSDL3Output() { SDL_Init(SDL_INIT_AUDIO); constexpr SDL_AudioSpec spec = { - SDL_AUDIO_S16, + SDL_AUDIO_F32, 1, SampleRate, }; @@ -68,6 +68,7 @@ void SDLCALL GetNewAudio( } static std::ofstream outRaw("guh.raw", std::ios_base::out | std::ios_base::binary); +static std::ofstream outRawF32("guh.f32.raw", std::ios_base::out | std::ios_base::binary); int RenderNewAudioFrame() { JASCriticalSection section; @@ -92,7 +93,14 @@ void RenderAudioSubframe() { JASDriver::updateDSP(); DspRender(subFrame); - outRaw.write((const char*)subFrame.data(), subFrame.size() * sizeof(s16)); + std::array guh = {}; + for (int i = 0; i < DSP_SUBFRAME_SIZE; i++) { + guh[i] = (f32)subFrame[i] / (f32)0x7FFF; + } - SDL_PutAudioStreamData(PlaybackStream, &subFrame, sizeof(subFrame)); + outRaw.write((const char*)subFrame.data(), sizeof(subFrame)); + outRawF32.write((const char*)guh.data(), sizeof(guh)); + + SDL_PutAudioStreamData(PlaybackStream, guh.data(), sizeof(guh)); + // SDL_PutAudioStreamData(PlaybackStream, subFrame.data(), sizeof(subFrame)); } From ca130d881dea91dd7db517ab3450b0b279016576 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Mon, 16 Mar 2026 14:27:27 +0100 Subject: [PATCH 054/350] Only mark channel as finished when resample buffer is empty Does not fix the audio corruption but necessary either way --- src/dusk/audio/DuskDsp.cpp | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/dusk/audio/DuskDsp.cpp b/src/dusk/audio/DuskDsp.cpp index 25bc822e9f..947b414bc3 100644 --- a/src/dusk/audio/DuskDsp.cpp +++ b/src/dusk/audio/DuskDsp.cpp @@ -125,6 +125,12 @@ static void SDLCALL ReadChannelSamples( auto& channel = JASDsp::CH_BUF[index]; auto& aux = ChannelAux[index]; + if (channel.mSamplesLeft == 0 && !channel.mLoopFlag) { + // May get called when we're out of data to read. + // This is expected, as we need to drain the resampler channel before we mark the channel as finished. + return; + } + additional_amount = ALIGN_NEXT(additional_amount, channel.mSamplesPerBlock); int requestedSize = static_cast(sizeof(s16) * additional_amount); @@ -154,8 +160,6 @@ static void SDLCALL ReadChannelSamples( if (channel.mSamplesLeft == 0) { // Reached end of buffer. if (!channel.mLoopFlag) { - // Finish. - channel.mIsFinished = true; return; } @@ -190,10 +194,15 @@ static void RenderChannel( ResetChannel(channel, channelAux); } - SDL_GetAudioStreamData( + int wantRead = static_cast(subframe.size() * sizeof(s16)); + auto read = SDL_GetAudioStreamData( channelAux.resampleStream, subframe.data(), - static_cast(subframe.size() * sizeof(s16))); + wantRead); + + if (read < wantRead) { + channel.mIsFinished = true; + } for (auto& sample : subframe) { u16 volume; From 8fecff317096d35f736c121de4db85e47ce16cb0 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Mon, 16 Mar 2026 15:44:02 +0100 Subject: [PATCH 055/350] Clang Windows compile fixes --- include/f_pc/f_pc_manager.h | 4 ++++ include/global.h | 7 +++++-- src/d/actor/d_a_alink.cpp | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/include/f_pc/f_pc_manager.h b/include/f_pc/f_pc_manager.h index f772949d85..d8d9cbe1c9 100644 --- a/include/f_pc/f_pc_manager.h +++ b/include/f_pc/f_pc_manager.h @@ -9,7 +9,11 @@ #include "f_pc/f_pc_stdcreate_req.h" #include "f_pc/f_pc_searcher.h" +#if TARGET_PC +enum : u32 { +#else enum { +#endif fpcM_UNK_PROCESS_ID_e = 0xFFFFFFFE, fpcM_ERROR_PROCESS_ID_e = 0xFFFFFFFF, }; diff --git a/include/global.h b/include/global.h index 94a2c9dc6e..36fb40dfb1 100644 --- a/include/global.h +++ b/include/global.h @@ -83,7 +83,11 @@ extern int __abs(int); void* __memcpy(void*, const void*, int); #endif -#ifdef _MSVC_LANG +#ifndef M_PI +#define M_PI 3.14159265358979323846f +#endif + +#if defined(_MSVC_LANG) && !defined(__clang__) inline int __builtin_clz(unsigned int v) { int count = 32; while (v != 0) { @@ -94,7 +98,6 @@ inline int __builtin_clz(unsigned int v) { } #define COMPOUND_LITERAL(x) -#define M_PI 3.14159265358979323846f #else #define COMPOUND_LITERAL(x) (x) diff --git a/src/d/actor/d_a_alink.cpp b/src/d/actor/d_a_alink.cpp index 93bfd80296..493d788a52 100644 --- a/src/d/actor/d_a_alink.cpp +++ b/src/d/actor/d_a_alink.cpp @@ -1467,7 +1467,7 @@ static f32 l_ladderAnmBaseTransY = 102.00054168701172f; static dCcD_SrcCyl l_cylSrc = { { - {0, {{AT_TYPE_WOLF_ATTACK, 3, 0x1A}, {0xD8FFFDFF, 5}, 0x73}}, + {0, {{(u32)AT_TYPE_WOLF_ATTACK, 3, 0x1A}, {0xD8FFFDFF, 5}, 0x73}}, {dCcD_SE_WOLF_BITE, 3, 1, 0, {1}}, {dCcD_SE_NONE, 6, 0, 0, {0}}, {0}, From ba0468860cf41556e3624dee4f0e5769259a9683 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Mon, 16 Mar 2026 15:44:26 +0100 Subject: [PATCH 056/350] Fix sprintf buffer too small in dMsgObject_c::readMessageGroupLocal --- src/d/d_msg_object.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/d/d_msg_object.cpp b/src/d/d_msg_object.cpp index 39c44b1470..37ca9cff28 100644 --- a/src/d/d_msg_object.cpp +++ b/src/d/d_msg_object.cpp @@ -1602,7 +1602,10 @@ u8 dMsgObject_c::isSend() { } void dMsgObject_c::readMessageGroupLocal(mDoDvdThd_mountXArchive_c** p_arcMount) { - static char arcName[22]; +#if TARGET_PC + static char arcName[32]; +#else +#endif int msgGroup = dStage_stagInfo_GetMsgGroup(dComIfGp_getStage()->getStagInfo()); #if REGION_PAL @@ -1625,7 +1628,12 @@ void dMsgObject_c::readMessageGroupLocal(mDoDvdThd_mountXArchive_c** p_arcMount) #elif REGION_JPN sprintf(arcName, "/res/Msgjp/bmgres%d.arc", msgGroup); #else +#if TARGET_PC + // Original game UB + snprintf(arcName, sizeof(arcName), "/res/Msgus/bmgres%d.arc", msgGroup); +#else sprintf(arcName, "/res/Msgus/bmgres%d.arc", msgGroup); +#endif #endif *p_arcMount = mDoDvdThd_mountXArchive_c::create(arcName, 0, JKRArchive::MOUNT_MEM, NULL); From 5739a902e30abc4d84997eed7fe099f347efdada Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Mon, 16 Mar 2026 15:44:36 +0100 Subject: [PATCH 057/350] Fix NullTexData too small --- libs/JSystem/src/J3DGraphBase/J3DSys.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libs/JSystem/src/J3DGraphBase/J3DSys.cpp b/libs/JSystem/src/J3DGraphBase/J3DSys.cpp index c34ba546f0..2f9cf51a89 100644 --- a/libs/JSystem/src/J3DGraphBase/J3DSys.cpp +++ b/libs/JSystem/src/J3DGraphBase/J3DSys.cpp @@ -17,7 +17,11 @@ Vec J3DSys::mParentS; J3DTexCoordScaleInfo J3DSys::sTexCoordScaleTable[8]; +#if TARGET_PC // Original game bug, array is too small. +static u8 NullTexData[0x20] ATTRIBUTE_ALIGN(32) = {0}; +#else static u8 NullTexData[0x10] ATTRIBUTE_ALIGN(32) = {0}; +#endif static Mtx j3dIdentityMtx = { 1.0f, 0.0f, 0.0f, 0.0f, From f6b41a0f5791527793d27f703897b590eb77a64d Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Mon, 16 Mar 2026 15:48:09 +0100 Subject: [PATCH 058/350] dStage_startStage_c::set fix strcpy to same pointer And just switch to strncpy_s while we're at it. --- src/d/d_stage.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/d/d_stage.cpp b/src/d/d_stage.cpp index 1f6186112b..87fbd9e40b 100644 --- a/src/d/d_stage.cpp +++ b/src/d/d_stage.cpp @@ -147,7 +147,14 @@ static int dStage_RoomKeepDoorInit(dStage_dt_c* i_stage, void* i_data, int entry } void dStage_startStage_c::set(const char* i_Name, s8 i_RoomNo, s16 i_Point, s8 i_Layer) { +#if TARGET_PC + // UB fix. + if (mName != i_Name) { + strncpy_s(mName, sizeof(mName), i_Name, sizeof(mName)); + } +#else strcpy(mName, i_Name); +#endif mRoomNo = i_RoomNo; mPoint = i_Point; mLayer = i_Layer; From 59802795ba6498db7fdcfc4445074351b355e04e Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Mon, 16 Mar 2026 15:53:15 +0100 Subject: [PATCH 059/350] Fix fopAcM_cullingCheck use after scope --- src/f_op/f_op_actor_mng.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/f_op/f_op_actor_mng.cpp b/src/f_op/f_op_actor_mng.cpp index fa4da69e68..0f5c47df37 100644 --- a/src/f_op/f_op_actor_mng.cpp +++ b/src/f_op/f_op_actor_mng.cpp @@ -1020,10 +1020,15 @@ cull_sphere l_cullSizeSphere[fopAc_CULLSPHERE_MAX_e] = { s32 fopAcM_cullingCheck(fopAc_ac_c const* i_actor) { MtxP mtx_p; +#if AVOID_UB + Mtx concat_mtx; +#endif if (fopAcM_GetMtx(i_actor) == NULL) { mtx_p = j3dSys.getViewMtx(); } else { +#if !AVOID_UB Mtx concat_mtx; +#endif cMtx_concat(j3dSys.getViewMtx(), fopAcM_GetMtx(i_actor), concat_mtx); mtx_p = concat_mtx; } From 778c85c1304a0de101453af97bfd5f65f84b9661 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Mon, 16 Mar 2026 16:48:11 +0100 Subject: [PATCH 060/350] Fix setBusConnect OOB for "dolby mode" channels --- libs/JSystem/src/JAudio2/JASDSPInterface.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/libs/JSystem/src/JAudio2/JASDSPInterface.cpp b/libs/JSystem/src/JAudio2/JASDSPInterface.cpp index 00c7a66539..49ca691011 100644 --- a/libs/JSystem/src/JAudio2/JASDSPInterface.cpp +++ b/libs/JSystem/src/JAudio2/JASDSPInterface.cpp @@ -704,6 +704,15 @@ void JASDsp::TChannel::setDistFilter(s16 param_0) { void JASDsp::TChannel::setBusConnect(u8 outputChannel, u8 param_1) { JUT_ASSERT(973, dspMutex); OutputChannelConfig& tmp = mOutputChannels[outputChannel]; +#if AVOID_UB + if (param_1 == 255) { + // Seems to happen for "dolby mode" where the mix config is 0xFFFF. + // Probably UB without side effect in the base game as afaict the DSP + // doesn't look at mix config when "dolby mode" is active. + return; + } + JUT_ASSERT(0, param_1 < 12); +#endif static u16 const connect_table[12] = { 0x0000, 0x0D00, 0x0D60, 0x0DC0, 0x0E20, 0x0E80, 0x0EE0, 0x0CA0, 0x0F40, 0x0FA0, 0x0B00, 0x09A0, From a4bec36b1239d4ce58b520b6f52ef6f28b43cb70 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Mon, 16 Mar 2026 16:48:38 +0100 Subject: [PATCH 061/350] BE fix MixConfig --- libs/JSystem/include/JSystem/JAudio2/JASChannel.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/JSystem/include/JSystem/JAudio2/JASChannel.h b/libs/JSystem/include/JSystem/JAudio2/JASChannel.h index 89bc381575..ce9b0b20de 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASChannel.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASChannel.h @@ -79,7 +79,7 @@ public: }; union MixConfig { - u16 whole; + BE(u16) whole; struct { u8 upper; u8 lower0 : 4; From 4590cd6419dff4f9b8a590e271efce96878ea574 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Mon, 16 Mar 2026 16:48:45 +0100 Subject: [PATCH 062/350] Fix CUTOFF_TO_IIR_TABLE OOB --- libs/JSystem/include/JSystem/JAudio2/JASCalc.h | 4 ++++ libs/JSystem/src/JAudio2/JASCalc.cpp | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/libs/JSystem/include/JSystem/JAudio2/JASCalc.h b/libs/JSystem/include/JSystem/JAudio2/JASCalc.h index bb8054675f..35b8fd5516 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASCalc.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASCalc.h @@ -33,7 +33,11 @@ struct JASCalc { f32 fake2(s32 x); f32 fake3(); +#if AVOID_UB + static const s16 CUTOFF_TO_IIR_TABLE[129][4]; +#else static const s16 CUTOFF_TO_IIR_TABLE[128][4]; +#endif }; template diff --git a/libs/JSystem/src/JAudio2/JASCalc.cpp b/libs/JSystem/src/JAudio2/JASCalc.cpp index be87a8aa4e..9ac209c96b 100644 --- a/libs/JSystem/src/JAudio2/JASCalc.cpp +++ b/libs/JSystem/src/JAudio2/JASCalc.cpp @@ -132,7 +132,11 @@ void JASCalc::bzero(void* dest, u32 size) { } } +#if AVOID_UB +s16 const JASCalc::CUTOFF_TO_IIR_TABLE[129][4] = { +#else s16 const JASCalc::CUTOFF_TO_IIR_TABLE[128][4] = { +#endif 0x0F5C, 0x0A3D, 0x4665, 0x1E73, 0x0F5E, 0x0A3D, 0x4664, 0x1E73, 0x0F63, 0x0A3C, 0x4661, 0x1E71, @@ -261,6 +265,10 @@ s16 const JASCalc::CUTOFF_TO_IIR_TABLE[128][4] = { 0x7C7A, 0x0052, 0x0233, 0x00F4, 0x7E3B, 0x0029, 0x011B, 0x007A, 0x7FFF, 0x0000, 0x0000, 0x0000, +#if AVOID_UB + // Game OOB reads this in some cases. + 0,0,0,0 +#endif }; // currently required because of missing functions From b0644a3cd041e43ff34f9d0346d9f6b4d59f552a Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Mon, 16 Mar 2026 23:35:57 +0100 Subject: [PATCH 063/350] Disable JASAiCtrl safety kill code Doesn't work properly and causes weird behavior --- libs/JSystem/src/JAudio2/JASAiCtrl.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libs/JSystem/src/JAudio2/JASAiCtrl.cpp b/libs/JSystem/src/JAudio2/JASAiCtrl.cpp index fd9446932d..1285d68359 100644 --- a/libs/JSystem/src/JAudio2/JASAiCtrl.cpp +++ b/libs/JSystem/src/JAudio2/JASAiCtrl.cpp @@ -152,6 +152,8 @@ void JASDriver::updateDSP() { JASPortCmd::execAllCommand(); DSPSyncCallback(); +#if !TARGET_PC + // Safety kill code? Our audio engine isn't consistent enough and hits this incorrectly. static u32 old_time = 0; static u32 history[10] = {0x000F4240}; u32 r28 = OSGetTick(); @@ -172,6 +174,7 @@ void JASDriver::updateDSP() { JASDSPChannel::killActiveChannel(); #endif } +#endif JASChannel::receiveBankDisposeMsg(); JASDSPChannel::updateAll(); From f8b9e20779ec513a9ef75723f897b7a21d0bb46c Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Mon, 16 Mar 2026 23:37:57 +0100 Subject: [PATCH 064/350] More ImGuiAudio stuff --- src/dusk/imgui/ImGuiAudio.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/dusk/imgui/ImGuiAudio.cpp b/src/dusk/imgui/ImGuiAudio.cpp index 56976bce55..2c1f6d96ad 100644 --- a/src/dusk/imgui/ImGuiAudio.cpp +++ b/src/dusk/imgui/ImGuiAudio.cpp @@ -3,12 +3,23 @@ #include "JSystem/JAudio2/JAISeMgr.h" #include "JSystem/JAudio2/JAIStreamMgr.h" #include "JSystem/JAudio2/JASCriticalSection.h" +#include "JSystem/JAudio2/JASDSPChannel.h" #include "JSystem/JAudio2/JASDSPInterface.h" #include "JSystem/JAudio2/JASTrack.h" static void ShowAllDspChannels() { + int activeChannels = 0; + for (int i = 0; i < DSP_CHANNELS; i++) { + if (JASDsp::CH_BUF[i].mIsActive) { + activeChannels++; + } + } + + ImGui::Text("Active channels: %d", activeChannels); + for (int i = 0; i < DSP_CHANNELS; i++) { auto& channel = JASDsp::CH_BUF[i]; + auto& jasChannel = JASDSPChannel::sDspChannels[i]; if (!channel.mIsActive) { continue; } @@ -19,8 +30,11 @@ static void ShowAllDspChannels() { if (ImGui::BeginChild(buf, ImVec2(), ImGuiChildFlags_Border | ImGuiChildFlags_AutoResizeY)) { ImGui::Text("[%02X]", i); ImGui::TextUnformatted(channel.mLoopFlag ? "Loop: true" : "Loop: false"); + ImGui::SameLine(); + ImGui::Text("Priority: %hd", jasChannel.mPriority); ImGui::Text("Format: %02X/%02X", channel.mSamplesPerBlock, channel.mBytesPerBlock); ImGui::Text("Position: %08X/%08X", channel.mSamplePosition, channel.mEndSample); + ImGui::SameLine(); ImGui::Text("Memory: %08X/%08X", channel.mWaveAramAddress, channel.mAramStreamPosition); } From c1c97e63d97e4f257ebe87339af6dbdf4e5f411d Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Tue, 17 Mar 2026 00:03:15 +0100 Subject: [PATCH 065/350] Clear history samples when resetting channel --- src/dusk/audio/DuskDsp.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/dusk/audio/DuskDsp.cpp b/src/dusk/audio/DuskDsp.cpp index 947b414bc3..3c5679e9df 100644 --- a/src/dusk/audio/DuskDsp.cpp +++ b/src/dusk/audio/DuskDsp.cpp @@ -60,7 +60,7 @@ static void RenderChannel( ChannelAuxData& channelAux, DspSubframe& subframe); -static void ResetChannel(JASDsp::TChannel& channel, const ChannelAuxData& aux) { +static void ResetChannel(JASDsp::TChannel& channel, ChannelAuxData& aux) { channel.mSamplesLeft = channel.mEndSample - channel.mSamplePosition; const SDL_AudioSpec spec = { @@ -69,6 +69,9 @@ static void ResetChannel(JASDsp::TChannel& channel, const ChannelAuxData& aux) { static_cast(static_cast(SampleRate) * channel.mPitch / 4096) }; + aux.hist0 = 0; + aux.hist1 = 0; + SDL_ClearAudioStream(aux.resampleStream); SDL_SetAudioStreamFormat(aux.resampleStream, &spec, nullptr); From 530d69c9bed701e761c72b06cc6792b2cf2e1bf6 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Tue, 17 Mar 2026 00:03:35 +0100 Subject: [PATCH 066/350] Audio processing in f32 --- src/dusk/audio/DuskAudioSystem.cpp | 16 +++++++--------- src/dusk/audio/DuskDsp.cpp | 28 +++++++++++++++++----------- src/dusk/audio/DuskDsp.hpp | 2 +- 3 files changed, 25 insertions(+), 21 deletions(-) diff --git a/src/dusk/audio/DuskAudioSystem.cpp b/src/dusk/audio/DuskAudioSystem.cpp index ae23b995f3..1d625a8101 100644 --- a/src/dusk/audio/DuskAudioSystem.cpp +++ b/src/dusk/audio/DuskAudioSystem.cpp @@ -67,8 +67,9 @@ void SDLCALL GetNewAudio( } } +#if 0 static std::ofstream outRaw("guh.raw", std::ios_base::out | std::ios_base::binary); -static std::ofstream outRawF32("guh.f32.raw", std::ios_base::out | std::ios_base::binary); +#endif int RenderNewAudioFrame() { JASCriticalSection section; @@ -82,7 +83,9 @@ int RenderNewAudioFrame() { JASAudioThread::snIntCount -= 1; } +#if 0 outRaw.flush(); +#endif return static_cast(countSubframes) * DSP_SUBFRAME_SIZE; } @@ -93,14 +96,9 @@ void RenderAudioSubframe() { JASDriver::updateDSP(); DspRender(subFrame); - std::array guh = {}; - for (int i = 0; i < DSP_SUBFRAME_SIZE; i++) { - guh[i] = (f32)subFrame[i] / (f32)0x7FFF; - } - +#if 0 outRaw.write((const char*)subFrame.data(), sizeof(subFrame)); - outRawF32.write((const char*)guh.data(), sizeof(guh)); +#endif - SDL_PutAudioStreamData(PlaybackStream, guh.data(), sizeof(guh)); - // SDL_PutAudioStreamData(PlaybackStream, subFrame.data(), sizeof(subFrame)); + SDL_PutAudioStreamData(PlaybackStream, subFrame.data(), sizeof(subFrame)); } diff --git a/src/dusk/audio/DuskDsp.cpp b/src/dusk/audio/DuskDsp.cpp index 3c5679e9df..a901cc0cdc 100644 --- a/src/dusk/audio/DuskDsp.cpp +++ b/src/dusk/audio/DuskDsp.cpp @@ -80,7 +80,7 @@ static void ResetChannel(JASDsp::TChannel& channel, ChannelAuxData& aux) { static void MixSubframe(DspSubframe& dst, const DspSubframe& src) { for (int i = 0; i < dst.size(); i++) { - dst[i] = static_cast(dst[i] + src[i]); + dst[i] += src[i]; } } @@ -197,7 +197,7 @@ static void RenderChannel( ResetChannel(channel, channelAux); } - int wantRead = static_cast(subframe.size() * sizeof(s16)); + int wantRead = static_cast(subframe.size() * sizeof(subframe[0])); auto read = SDL_GetAudioStreamData( channelAux.resampleStream, subframe.data(), @@ -207,27 +207,33 @@ static void RenderChannel( channel.mIsFinished = true; } + u16 volume; + if (channel.mAutoMixerBeenSet) { + volume = channel.mAutoMixerVolume; + } else { + volume = channel.mOutputChannels[0].mTargetVolume; + } + f32 ratio = static_cast(volume) / static_cast(JASDriver::getChannelLevel_dsp()); for (auto& sample : subframe) { - u16 volume; - if (channel.mAutoMixerBeenSet) { - volume = channel.mAutoMixerVolume; - } else { - volume = channel.mOutputChannels[0].mTargetVolume; - } - sample = (s16)((s64)sample * volume / JASDriver::getChannelLevel_dsp()); + sample *= ratio; } } void dusk::audio::DspInit() { - constexpr SDL_AudioSpec spec = { + constexpr SDL_AudioSpec srcSpec = { SDL_AUDIO_S16, 1, SampleRate }; + constexpr SDL_AudioSpec dstSpec = { + SDL_AUDIO_F32, + 1, + SampleRate + }; for (int i = 0; i < DSP_CHANNELS; i++) { auto& aux = ChannelAux[i]; - aux.resampleStream = SDL_CreateAudioStream(&spec, &spec); + aux.resampleStream = SDL_CreateAudioStream(&srcSpec, &dstSpec); SDL_SetAudioStreamGetCallback( aux.resampleStream, diff --git a/src/dusk/audio/DuskDsp.hpp b/src/dusk/audio/DuskDsp.hpp index 186f7de677..76a4ef2e14 100644 --- a/src/dusk/audio/DuskDsp.hpp +++ b/src/dusk/audio/DuskDsp.hpp @@ -17,7 +17,7 @@ namespace dusk::audio { extern ChannelAuxData ChannelAux[DSP_CHANNELS]; - using DspSubframe = std::array; + using DspSubframe = std::array; void DspInit(); void DspRender(DspSubframe& subframe); From 3cd351488805c149eb23fa4b417f8172afeee23f Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Thu, 19 Mar 2026 16:04:18 +0100 Subject: [PATCH 067/350] More imgui stuff!!! --- libs/JSystem/src/JAudio2/JASChannel.cpp | 2 +- src/dusk/imgui/ImGuiAudio.cpp | 118 ++++++++++++++++++++---- 2 files changed, 99 insertions(+), 21 deletions(-) diff --git a/libs/JSystem/src/JAudio2/JASChannel.cpp b/libs/JSystem/src/JAudio2/JASChannel.cpp index ffd5056e1d..61fe80a098 100644 --- a/libs/JSystem/src/JAudio2/JASChannel.cpp +++ b/libs/JSystem/src/JAudio2/JASChannel.cpp @@ -470,7 +470,7 @@ void JASChannel::updateMixer(f32 i_volume, f32 i_pan, f32 i_fxmix, f32 i_dolby, case 6: volume *= scale; break; - default: + default: // 0, 1, 4, 5, 8-15 if (JASDriver::getOutputMode() == JAS_OUTPUT_MONO) { volume *= scale; } else { diff --git a/src/dusk/imgui/ImGuiAudio.cpp b/src/dusk/imgui/ImGuiAudio.cpp index 2c1f6d96ad..07caf9f0c0 100644 --- a/src/dusk/imgui/ImGuiAudio.cpp +++ b/src/dusk/imgui/ImGuiAudio.cpp @@ -1,12 +1,81 @@ #include "ImGuiConsole.hpp" #include "ImGuiMenuTools.hpp" #include "JSystem/JAudio2/JAISeMgr.h" +#include "JSystem/JAudio2/JAISeqMgr.h" #include "JSystem/JAudio2/JAIStreamMgr.h" #include "JSystem/JAudio2/JASCriticalSection.h" #include "JSystem/JAudio2/JASDSPChannel.h" #include "JSystem/JAudio2/JASDSPInterface.h" +#include "JSystem/JAudio2/JASDriverIF.h" #include "JSystem/JAudio2/JASTrack.h" +static std::array channelSortIndices = {}; + +static bool sortUpdateCount = true; + +static void DisplayDspChannel(int i) { + auto& channel = JASDsp::CH_BUF[i]; + auto& jasChannel = JASDSPChannel::sDspChannels[i]; + if (!channel.mIsActive) { + return; + } + + char buf[64]; + snprintf(buf, sizeof(buf), "%d", i); + + if (ImGui::BeginChild(buf, ImVec2(), ImGuiChildFlags_Border | ImGuiChildFlags_AutoResizeY)) { + ImGui::Text("[%02X]", i); + ImGui::SameLine(); + ImGui::Text("Update count: %d", jasChannel.mUpdateCounter); + ImGui::TextUnformatted(channel.mLoopFlag ? "Loop: true" : "Loop: false"); + ImGui::SameLine(); + ImGui::Text("Priority: %hd", jasChannel.mPriority); + ImGui::Text("Format: %02X/%02X", channel.mSamplesPerBlock, channel.mBytesPerBlock); + ImGui::Text("Position: %08X/%08X", channel.mSamplePosition, channel.mEndSample); + ImGui::SameLine(); + ImGui::Text("Memory: %08X/%08X", channel.mWaveAramAddress, channel.mAramStreamPosition); + + if (channel.mAutoMixerBeenSet) { + auto pan = (channel.mAutoMixerPanDolby >> 8) / 127.5f; + auto dolby = (channel.mAutoMixerPanDolby & 0xFF) / 127.5f; + auto fxMix = (channel.mAutoMixerFxMix >> 8) / 127.5f; + auto volume = (channel.mAutoMixerVolume) / (f32) JASDriver::getChannelLevel_dsp(); + ImGui::Text( + "Auto mixer active (pan: %f, dolby: %f, fx: %f, volume: %f)", + pan, dolby, fxMix, volume); + } else { + ImGui::Text( + "Bus connect: %04X,%04X,%04X,%04X,%04X,%04X", + channel.mOutputChannels[0].mBusConnect, + channel.mOutputChannels[1].mBusConnect, + channel.mOutputChannels[2].mBusConnect, + channel.mOutputChannels[3].mBusConnect, + channel.mOutputChannels[4].mBusConnect, + channel.mOutputChannels[5].mBusConnect); + } + } + + ImGui::EndChild(); +} + +static void InitChannelSortIndices() { + for (int i = 0; i < channelSortIndices.size(); i++) { + channelSortIndices[i] = i; + } +} + +static void SortChannelsByUpdateCount() { + InitChannelSortIndices(); + std::ranges::sort( + channelSortIndices, + [](u8 a, u8 b) { + auto& jasChannelA = JASDSPChannel::sDspChannels[a]; + auto& jasChannelB = JASDSPChannel::sDspChannels[b]; + + return jasChannelA.mUpdateCounter > jasChannelB.mUpdateCounter; + }); +} + static void ShowAllDspChannels() { int activeChannels = 0; for (int i = 0; i < DSP_CHANNELS; i++) { @@ -16,29 +85,17 @@ static void ShowAllDspChannels() { } ImGui::Text("Active channels: %d", activeChannels); + ImGui::Checkbox("Sort by update count", &sortUpdateCount); - for (int i = 0; i < DSP_CHANNELS; i++) { - auto& channel = JASDsp::CH_BUF[i]; - auto& jasChannel = JASDSPChannel::sDspChannels[i]; - if (!channel.mIsActive) { - continue; + if (sortUpdateCount) { + SortChannelsByUpdateCount(); + for (u8 index : channelSortIndices) { + DisplayDspChannel(index); } - - char buf[64]; - snprintf(buf, sizeof(buf), "%d", i); - - if (ImGui::BeginChild(buf, ImVec2(), ImGuiChildFlags_Border | ImGuiChildFlags_AutoResizeY)) { - ImGui::Text("[%02X]", i); - ImGui::TextUnformatted(channel.mLoopFlag ? "Loop: true" : "Loop: false"); - ImGui::SameLine(); - ImGui::Text("Priority: %hd", jasChannel.mPriority); - ImGui::Text("Format: %02X/%02X", channel.mSamplesPerBlock, channel.mBytesPerBlock); - ImGui::Text("Position: %08X/%08X", channel.mSamplePosition, channel.mEndSample); - ImGui::SameLine(); - ImGui::Text("Memory: %08X/%08X", channel.mWaveAramAddress, channel.mAramStreamPosition); + } else { + for (int i = 0; i < DSP_CHANNELS; i++) { + DisplayDspChannel(i); } - - ImGui::EndChild(); } } @@ -58,6 +115,9 @@ static void ShowAllTracks() { bool paused = track.mFlags.pause; ImGui::Checkbox("Paused", &paused); track.mFlags.pause = paused; + bool muted = track.mFlags.mute; + ImGui::Checkbox("Muted", &muted); + track.mFlags.mute = muted; for (int i = 0; i < JASTrack::MAX_CHILDREN; i++) { const auto child = track.getChild(i); @@ -116,6 +176,19 @@ static void ShowAllJAISes() { } } + +static void ShowAllJAISeqs() { + auto& mgr = *JAISeqMgr::getInstance(); + + if (ImGui::Button("Pause")) { + mgr.pause(true); + } + ImGui::SameLine(); + if (ImGui::Button("Unpause")) { + mgr.pause(false); + } +} + void dusk::ImGuiMenuTools::ShowAudioDebug() { if (!ImGuiConsole::CheckMenuViewToggle(ImGuiKey_F7, m_showAudioDebug)) { return; @@ -150,6 +223,11 @@ void dusk::ImGuiMenuTools::ShowAudioDebug() { ImGui::EndTabItem(); } + if (ImGui::BeginTabItem("JAISeq")) { + ShowAllJAISeqs(); + ImGui::EndTabItem(); + } + ImGui::EndTabBar(); } } From 3d396d66b6665a81ebd5ab8baeeb7da055448bd5 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Thu, 19 Mar 2026 17:47:11 +0100 Subject: [PATCH 068/350] Stereo audio! --- src/dusk/audio/DuskAudioSystem.cpp | 26 ++++-- src/dusk/audio/DuskDsp.cpp | 126 ++++++++++++++++++++++++----- src/dusk/audio/DuskDsp.hpp | 22 ++++- 3 files changed, 147 insertions(+), 27 deletions(-) diff --git a/src/dusk/audio/DuskAudioSystem.cpp b/src/dusk/audio/DuskAudioSystem.cpp index 1d625a8101..3becb76cfa 100644 --- a/src/dusk/audio/DuskAudioSystem.cpp +++ b/src/dusk/audio/DuskAudioSystem.cpp @@ -2,8 +2,10 @@ #include #include +#include #include #include +#include #include "JSystem/JAudio2/JASAiCtrl.h" #include "JSystem/JAudio2/JASChannel.h" @@ -16,7 +18,8 @@ using namespace dusk::audio; -static DspSubframe AllSubframeBuffers[DSP_OUTPUT_CHANNELS]; +static OutputSubframe OutBuffer; +static std::array OutInterleaveBuffer; static SDL_AudioStream* PlaybackStream; @@ -34,7 +37,7 @@ static void InitSDL3Output() { constexpr SDL_AudioSpec spec = { SDL_AUDIO_F32, - 1, + 2, SampleRate, }; PlaybackStream = SDL_OpenAudioDeviceStream( @@ -90,15 +93,28 @@ int RenderNewAudioFrame() { return static_cast(countSubframes) * DSP_SUBFRAME_SIZE; } +static void InterleaveOutputData(const OutputSubframe& data, std::span target) { + assert(target.size() >= data.channels[0].size() * OutputSubframe::NUM_CHANNELS); + + size_t outPos = 0; + for (size_t inPos = 0; inPos < data.channels[0].size(); inPos++) { + for (size_t channelIdx = 0; channelIdx < OutputSubframe::NUM_CHANNELS; channelIdx++) { + target[outPos++] = data.channels[channelIdx][inPos]; + } + } +} + void RenderAudioSubframe() { - DspSubframe& subFrame = AllSubframeBuffers[0]; + OutBuffer = {}; JASDriver::updateDSP(); - DspRender(subFrame); + DspRender(OutBuffer); #if 0 outRaw.write((const char*)subFrame.data(), sizeof(subFrame)); #endif - SDL_PutAudioStreamData(PlaybackStream, subFrame.data(), sizeof(subFrame)); + InterleaveOutputData(OutBuffer, OutInterleaveBuffer); + + SDL_PutAudioStreamData(PlaybackStream, &OutInterleaveBuffer, sizeof(OutInterleaveBuffer)); } diff --git a/src/dusk/audio/DuskDsp.cpp b/src/dusk/audio/DuskDsp.cpp index a901cc0cdc..a042b0aa88 100644 --- a/src/dusk/audio/DuskDsp.cpp +++ b/src/dusk/audio/DuskDsp.cpp @@ -5,6 +5,7 @@ #include #include +#include #include "Adpcm.hpp" #include "JSystem/JAudio2/JASDriverIF.h" @@ -58,7 +59,7 @@ static u32 ConvertSamplesToDataLength(const JASDsp::TChannel& channel, u32 sampl static void RenderChannel( JASDsp::TChannel& channel, ChannelAuxData& channelAux, - DspSubframe& subframe); + OutputSubframe& subframe); static void ResetChannel(JASDsp::TChannel& channel, ChannelAuxData& aux) { channel.mSamplesLeft = channel.mEndSample - channel.mSamplePosition; @@ -84,9 +85,7 @@ static void MixSubframe(DspSubframe& dst, const DspSubframe& src) { } } -void dusk::audio::DspRender(DspSubframe& subframe) { - subframe.fill(0); - +void dusk::audio::DspRender(OutputSubframe& subframe) { // This cast half exists because my debugger sucks and this is an easy way to look at the data. auto& channels = *reinterpret_cast*>(JASDsp::CH_BUF); @@ -112,9 +111,12 @@ void dusk::audio::DspRender(DspSubframe& subframe) { ValidateChannel(channel); - DspSubframe channelSubframe = {}; + OutputSubframe channelSubframe = {}; RenderChannel(channel, channelAux, channelSubframe); - MixSubframe(subframe, channelSubframe); + + for (int o = 0; o < subframe.channels.size(); o++) { + MixSubframe(subframe.channels[o], channelSubframe.channels[o]); + } } } @@ -189,34 +191,116 @@ static void SDLCALL ReadChannelSamples( SDL_PutAudioStreamData(stream, requested, requestedSize); } +constexpr u16 GetBusConnect(const OutputChannel channel) { + switch (channel) { + // TODO: This is a guess for now. + case OutputChannel::LEFT: + return 0x0D00; + case OutputChannel::RIGHT: + return 0x0D60; + default: + CRASH("Invalid output channel!"); + } +} + +static const JASDsp::OutputChannelConfig* GetOutputConfig( + const JASDsp::TChannel& sourceChannel, + OutputChannel channel) { + + auto busConnect = GetBusConnect(channel); + for (const auto& mOutputChannel : sourceChannel.mOutputChannels) { + auto config = &mOutputChannel; + if (config->mBusConnect == busConnect) { + return config; + } + } + + return nullptr; +} + +static f32 GetVolumeForOutputChannel( + const JASDsp::TChannel& sourceChannel, + OutputChannel outputChannel) { + + u16 volume; + f32 panValue = 1; + if (sourceChannel.mAutoMixerBeenSet) { + volume = sourceChannel.mAutoMixerVolume; + + auto autoMixerPan = static_cast(sourceChannel.mAutoMixerPanDolby >> 8) / 127; + + switch (outputChannel) { + case OutputChannel::LEFT: + panValue = 1 - autoMixerPan; + break; + case OutputChannel::RIGHT: + panValue = autoMixerPan; + break; + default: + CRASH("Unhandled output channel: OutputChannel"); + } + + } else { + auto config = GetOutputConfig(sourceChannel, outputChannel); + if (config == nullptr) { + return 0; + } + + volume = config->mTargetVolume; + } + + // TODO: interpolate to avoid popping. + f32 ratio = static_cast(volume) / static_cast(JASDriver::getChannelLevel_dsp()); + ratio *= panValue; + + return ratio; +} + +static void RenderOutputChannel( + const JASDsp::TChannel& sourceChannel, + OutputChannel outputChannel, + const std::span inputSamples, + OutputSubframe& fullOutputSubframe) { + + auto& outputSubframe = fullOutputSubframe[outputChannel]; + assert(inputSamples.size() <= outputSubframe.size()); + + auto volume = GetVolumeForOutputChannel(sourceChannel, outputChannel); + if (volume == 0) { + return; + } + + for (int i = 0; i < inputSamples.size(); i++) { + outputSubframe[i] = inputSamples[i] * volume; + } +} + static void RenderChannel( JASDsp::TChannel& channel, ChannelAuxData& channelAux, - DspSubframe& subframe) { + OutputSubframe& subframe) { if (channel.mResetFlag) { ResetChannel(channel, channelAux); } - int wantRead = static_cast(subframe.size() * sizeof(subframe[0])); + DspSubframe audioLoadBuffer = {}; + + int wantRead = sizeof(audioLoadBuffer); auto read = SDL_GetAudioStreamData( channelAux.resampleStream, - subframe.data(), + &audioLoadBuffer, wantRead); if (read < wantRead) { channel.mIsFinished = true; } - u16 volume; - if (channel.mAutoMixerBeenSet) { - volume = channel.mAutoMixerVolume; - } else { - volume = channel.mOutputChannels[0].mTargetVolume; - } - f32 ratio = static_cast(volume) / static_cast(JASDriver::getChannelLevel_dsp()); - for (auto& sample : subframe) { - sample *= ratio; - } + auto hasReadSamples = std::span(audioLoadBuffer).subspan(0, wantRead / sizeof(f32)); + + static_assert(OutputSubframe::NUM_CHANNELS == 2, "Keep RenderChannel in sync!"); + + RenderOutputChannel(channel, OutputChannel::LEFT, hasReadSamples, subframe); + RenderOutputChannel(channel, OutputChannel::RIGHT, hasReadSamples, subframe); } void dusk::audio::DspInit() { @@ -231,13 +315,13 @@ void dusk::audio::DspInit() { SampleRate }; - for (int i = 0; i < DSP_CHANNELS; i++) { + for (u32 i = 0; i < DSP_CHANNELS; i++) { auto& aux = ChannelAux[i]; aux.resampleStream = SDL_CreateAudioStream(&srcSpec, &dstSpec); SDL_SetAudioStreamGetCallback( aux.resampleStream, ReadChannelSamples, - reinterpret_cast(i)); + reinterpret_cast(static_cast(i))); } } diff --git a/src/dusk/audio/DuskDsp.hpp b/src/dusk/audio/DuskDsp.hpp index 76a4ef2e14..0b47cf2570 100644 --- a/src/dusk/audio/DuskDsp.hpp +++ b/src/dusk/audio/DuskDsp.hpp @@ -3,12 +3,21 @@ #include "JSystem/JAudio2/JASDSPInterface.h" #include +#include #include "SDL3/SDL_audio.h" namespace dusk::audio { constexpr int SampleRate = 32000; + enum class OutputChannel : u8 { + LEFT, + RIGHT, + OutputChannel_MAX + }; + + constexpr + struct ChannelAuxData { s16 hist1; s16 hist0; @@ -19,6 +28,17 @@ namespace dusk::audio { using DspSubframe = std::array; + struct OutputSubframe { + static constexpr int NUM_CHANNELS = static_cast(OutputChannel::OutputChannel_MAX); + + std::array channels; + + DspSubframe& operator[](OutputChannel channel) { + assert(channel < OutputChannel::OutputChannel_MAX); + return channels[static_cast(channel)]; + } + }; + void DspInit(); - void DspRender(DspSubframe& subframe); + void DspRender(OutputSubframe& subframe); } From 6df246eb067a3afebbcee781d6d0e8e6a822734c Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Thu, 19 Mar 2026 20:05:47 +0100 Subject: [PATCH 069/350] PCM16 support Wanted this to test something, not sure if it's used in-game --- src/dusk/audio/DuskDsp.cpp | 51 +++++++++++++++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/src/dusk/audio/DuskDsp.cpp b/src/dusk/audio/DuskDsp.cpp index a042b0aa88..730a5fb856 100644 --- a/src/dusk/audio/DuskDsp.cpp +++ b/src/dusk/audio/DuskDsp.cpp @@ -18,13 +18,13 @@ ChannelAuxData dusk::audio::ChannelAux[DSP_CHANNELS] = {}; static bool ValidateChannelWaveFormat(const JASDsp::TChannel& channel) { if (channel.mSamplesPerBlock == AdpcmSampleCount && channel.mBytesPerBlock == Adpcm4FrameSize) return true; + if (channel.mSamplesPerBlock == 1 && channel.mBytesPerBlock == 16) + return true; /* if (channel.mSamplesPerBlock == AdpcmSampleCount && channel.mBytesPerBlock == Adpcm2FrameSize) return true; if (channel.mSamplesPerBlock == 1 && channel.mBytesPerBlock == 8) return true; - if (channel.mSamplesPerBlock == 1 && channel.mBytesPerBlock == 16) - return true; */ return false; } @@ -47,6 +47,16 @@ static u32 ConvertDataLengthToSamples(const JASDsp::TChannel& channel, u32 dataL } static u32 ConvertSamplesToDataLength(const JASDsp::TChannel& channel, u32 samples) { + if (channel.mSamplesPerBlock == 1) { + if (channel.mBytesPerBlock == 16) { + return samples * 2; + } + if (channel.mBytesPerBlock == 8) { + return samples; + } + CRASH("Unknown format"); + } + if (samples % channel.mSamplesPerBlock != 0) { // Ensure we round up. samples += channel.mSamplesPerBlock; @@ -120,6 +130,37 @@ void dusk::audio::DspRender(OutputSubframe& subframe) { } } +static void ReadSamplesCore( + const JASDsp::TChannel& channel, + const u8* data, + size_t dataLength, + s16* pcm, + size_t pcmLength, + s16& hist1, + s16& hist0) { + if (channel.mSamplesPerBlock == 1) { + if (channel.mBytesPerBlock == 0x10) { + // PCM16 + assert(reinterpret_cast(data) % 2 == 0 && "PCM data must be aligned"); + assert(dataLength % 2 == 0 && "Data length must be multiple of 2"); + assert(dataLength * 2 >= pcmLength && "Input too small!"); + + auto srcPcm = reinterpret_cast(data); + for (size_t i = 0; i < pcmLength; i++) { + pcm[i] = srcPcm[i]; + } + } else { + CRASH("Unsupported format: PCM8"); + } + } else { + if (channel.mBytesPerBlock == 9) { + Adpcm4ToPcm16(data, dataLength, pcm, pcmLength, hist1, hist0); + } else { + CRASH("Unsupported format: ADPCM2"); + } + } +} + static void SDLCALL ReadChannelSamples( void *userdata, SDL_AudioStream *stream, @@ -151,7 +192,8 @@ static void SDLCALL ReadChannelSamples( u32 renderSamples = std::min(channel.mSamplesLeft, static_cast(additional_amount)); - Adpcm4ToPcm16( + ReadSamplesCore( + channel, aramBase + dataPosition, ConvertSamplesToDataLength(channel, renderSamples), requested, @@ -173,7 +215,8 @@ static void SDLCALL ReadChannelSamples( curSamplePosition = channel.mEndSample - channel.mSamplesLeft; dataPosition = ConvertSamplesToDataLength(channel, curSamplePosition); - Adpcm4ToPcm16( + ReadSamplesCore( + channel, aramBase + dataPosition, ConvertSamplesToDataLength(channel, additional_amount - renderSamples), requested + renderSamples, From 5419d357d8c043b03d6441ac0320452cb49e4943 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Thu, 19 Mar 2026 20:06:02 +0100 Subject: [PATCH 070/350] Skip work in ReadChannelSamples if no new samples needed --- src/dusk/audio/DuskDsp.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/dusk/audio/DuskDsp.cpp b/src/dusk/audio/DuskDsp.cpp index 730a5fb856..61d9abc92f 100644 --- a/src/dusk/audio/DuskDsp.cpp +++ b/src/dusk/audio/DuskDsp.cpp @@ -167,6 +167,10 @@ static void SDLCALL ReadChannelSamples( int additional_amount, int) { + if (additional_amount == 0) { + return; + } + const auto index = static_cast(reinterpret_cast(userdata)); auto& channel = JASDsp::CH_BUF[index]; auto& aux = ChannelAux[index]; From a1631942b653450d108f3d53411c5f260b8a3095 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Thu, 19 Mar 2026 20:06:28 +0100 Subject: [PATCH 071/350] Move the audio dumping code to a define --- src/dusk/audio/DuskAudioSystem.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/dusk/audio/DuskAudioSystem.cpp b/src/dusk/audio/DuskAudioSystem.cpp index 3becb76cfa..e01d427901 100644 --- a/src/dusk/audio/DuskAudioSystem.cpp +++ b/src/dusk/audio/DuskAudioSystem.cpp @@ -16,6 +16,8 @@ #include "DuskDsp.hpp" #include "JSystem/JAudio2/JASAudioThread.h" +#define DUSK_DUMP_AUDIO + using namespace dusk::audio; static OutputSubframe OutBuffer; @@ -70,7 +72,7 @@ void SDLCALL GetNewAudio( } } -#if 0 +#if defined(DUSK_DUMP_AUDIO) static std::ofstream outRaw("guh.raw", std::ios_base::out | std::ios_base::binary); #endif @@ -86,7 +88,7 @@ int RenderNewAudioFrame() { JASAudioThread::snIntCount -= 1; } -#if 0 +#if defined(DUSK_DUMP_AUDIO) outRaw.flush(); #endif @@ -110,11 +112,11 @@ void RenderAudioSubframe() { JASDriver::updateDSP(); DspRender(OutBuffer); -#if 0 - outRaw.write((const char*)subFrame.data(), sizeof(subFrame)); -#endif - InterleaveOutputData(OutBuffer, OutInterleaveBuffer); +#if defined(DUSK_DUMP_AUDIO) + outRaw.write((const char*)OutInterleaveBuffer.data(), sizeof(OutInterleaveBuffer)); +#endif + SDL_PutAudioStreamData(PlaybackStream, &OutInterleaveBuffer, sizeof(OutInterleaveBuffer)); } From 5d5969f415caa3230ebec30ec21e05df191c6674 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Thu, 19 Mar 2026 20:07:39 +0100 Subject: [PATCH 072/350] Load bearing include --- src/dusk/audio/DuskDsp.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/dusk/audio/DuskDsp.cpp b/src/dusk/audio/DuskDsp.cpp index 61d9abc92f..9d6f3d10d0 100644 --- a/src/dusk/audio/DuskDsp.cpp +++ b/src/dusk/audio/DuskDsp.cpp @@ -9,6 +9,7 @@ #include "Adpcm.hpp" #include "JSystem/JAudio2/JASDriverIF.h" +#include "dusk/endian.h" #include "global.h" using namespace dusk::audio; From 911a12aaeab726bcb5685915abeda7655c24b7fe Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Thu, 19 Mar 2026 20:27:46 +0100 Subject: [PATCH 073/350] How did this compile ??? --- src/dusk/audio/DuskDsp.hpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/dusk/audio/DuskDsp.hpp b/src/dusk/audio/DuskDsp.hpp index 0b47cf2570..e2dec52e1a 100644 --- a/src/dusk/audio/DuskDsp.hpp +++ b/src/dusk/audio/DuskDsp.hpp @@ -16,8 +16,6 @@ namespace dusk::audio { OutputChannel_MAX }; - constexpr - struct ChannelAuxData { s16 hist1; s16 hist0; From b8b64304e434fa200bd69dcb62a97964d645c64f Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Thu, 19 Mar 2026 20:28:15 +0100 Subject: [PATCH 074/350] Attempt to allow sample rates to be changed dynamically Doesn't fix the walking bomb things :( --- src/dusk/audio/DuskDsp.cpp | 21 +++++++++++++++++---- src/dusk/audio/DuskDsp.hpp | 1 + 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/dusk/audio/DuskDsp.cpp b/src/dusk/audio/DuskDsp.cpp index 9d6f3d10d0..f52d191d09 100644 --- a/src/dusk/audio/DuskDsp.cpp +++ b/src/dusk/audio/DuskDsp.cpp @@ -72,20 +72,31 @@ static void RenderChannel( ChannelAuxData& channelAux, OutputSubframe& subframe); -static void ResetChannel(JASDsp::TChannel& channel, ChannelAuxData& aux) { - channel.mSamplesLeft = channel.mEndSample - channel.mSamplePosition; +constexpr static int PitchToSampleRate(u16 value) { + return static_cast(static_cast(SampleRate) * value / 4096); +} + +static void UpdateSampleRate(const JASDsp::TChannel& channel, ChannelAuxData& aux) { + auto sampleRate = PitchToSampleRate(channel.mPitch); const SDL_AudioSpec spec = { SDL_AUDIO_S16, 1, - static_cast(static_cast(SampleRate) * channel.mPitch / 4096) + sampleRate }; + SDL_SetAudioStreamFormat(aux.resampleStream, &spec, nullptr); + aux.prevPitch = channel.mPitch; +} + +static void ResetChannel(JASDsp::TChannel& channel, ChannelAuxData& aux) { + channel.mSamplesLeft = channel.mEndSample - channel.mSamplePosition; + aux.hist0 = 0; aux.hist1 = 0; SDL_ClearAudioStream(aux.resampleStream); - SDL_SetAudioStreamFormat(aux.resampleStream, &spec, nullptr); + UpdateSampleRate(channel, aux); channel.mResetFlag = false; } @@ -329,6 +340,8 @@ static void RenderChannel( OutputSubframe& subframe) { if (channel.mResetFlag) { ResetChannel(channel, channelAux); + } else if (channelAux.prevPitch != channel.mPitch) { + UpdateSampleRate(channel, channelAux); } DspSubframe audioLoadBuffer = {}; diff --git a/src/dusk/audio/DuskDsp.hpp b/src/dusk/audio/DuskDsp.hpp index e2dec52e1a..23e05b60e6 100644 --- a/src/dusk/audio/DuskDsp.hpp +++ b/src/dusk/audio/DuskDsp.hpp @@ -20,6 +20,7 @@ namespace dusk::audio { s16 hist1; s16 hist0; SDL_AudioStream* resampleStream; + u16 prevPitch; }; extern ChannelAuxData ChannelAux[DSP_CHANNELS]; From a6817f69d0ee8d01aef6d0b79fe20012533f02f9 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Fri, 20 Mar 2026 17:51:09 +0100 Subject: [PATCH 075/350] struct JASWaveArc forward declaration fix --- libs/JSystem/include/JSystem/JAudio2/JASWaveInfo.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/JSystem/include/JSystem/JAudio2/JASWaveInfo.h b/libs/JSystem/include/JSystem/JAudio2/JASWaveInfo.h index 62110754d7..cac518836e 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASWaveInfo.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASWaveInfo.h @@ -3,7 +3,7 @@ #include -class JASWaveArc; +struct JASWaveArc; #define WAVE_FORMAT_ADPCM4 0 #define WAVE_FORMAT_ADPCM2 1 From ebebb1af344a320ede660cdd24c623930c7793c3 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Fri, 20 Mar 2026 17:51:42 +0100 Subject: [PATCH 076/350] Audio system cleanup and comments --- include/dusk/audio/DuskAudioSystem.h | 3 + src/dusk/audio/DuskAudioSystem.cpp | 22 +++- src/dusk/audio/DuskDsp.cpp | 155 ++++++++++++++++----------- src/dusk/audio/DuskDsp.hpp | 41 +++++++ 4 files changed, 152 insertions(+), 69 deletions(-) diff --git a/include/dusk/audio/DuskAudioSystem.h b/include/dusk/audio/DuskAudioSystem.h index b06bdbe84e..c41762e146 100644 --- a/include/dusk/audio/DuskAudioSystem.h +++ b/include/dusk/audio/DuskAudioSystem.h @@ -1,5 +1,8 @@ #pragma once namespace dusk::audio { + /** + * Initialize the audio system and start playing audio. + */ void Initialize(); } diff --git a/src/dusk/audio/DuskAudioSystem.cpp b/src/dusk/audio/DuskAudioSystem.cpp index e01d427901..7c2572dd53 100644 --- a/src/dusk/audio/DuskAudioSystem.cpp +++ b/src/dusk/audio/DuskAudioSystem.cpp @@ -16,7 +16,7 @@ #include "DuskDsp.hpp" #include "JSystem/JAudio2/JASAudioThread.h" -#define DUSK_DUMP_AUDIO +// #define DUSK_DUMP_AUDIO using namespace dusk::audio; @@ -25,13 +25,25 @@ static std::array OutInte static SDL_AudioStream* PlaybackStream; +/** + * SDL audiostream callback to trigger rendering of new audio data. + */ static void SDLCALL GetNewAudio( - void *userdata, - SDL_AudioStream *stream, - int additional_amount, - int total_amount); + void*, + SDL_AudioStream*, + int needed, + int); +/** + * Render an entire new frame of audio and output it to SDL3. + * Note: "audio frames" are unrelated to video frames. + * @return Amount of audio samples rendered. + */ static int RenderNewAudioFrame(); + +/** + * Render an audio subframe and output it to SDL3. + */ static void RenderAudioSubframe(); static void InitSDL3Output() { diff --git a/src/dusk/audio/DuskDsp.cpp b/src/dusk/audio/DuskDsp.cpp index f52d191d09..97c6f1c6d1 100644 --- a/src/dusk/audio/DuskDsp.cpp +++ b/src/dusk/audio/DuskDsp.cpp @@ -16,6 +16,9 @@ using namespace dusk::audio; ChannelAuxData dusk::audio::ChannelAux[DSP_CHANNELS] = {}; +/** + * Validate that a DSP channel's format is actually something we know how to play. + */ static bool ValidateChannelWaveFormat(const JASDsp::TChannel& channel) { if (channel.mSamplesPerBlock == AdpcmSampleCount && channel.mBytesPerBlock == Adpcm4FrameSize) return true; @@ -30,6 +33,9 @@ static bool ValidateChannelWaveFormat(const JASDsp::TChannel& channel) { return false; } +/** + * Validate that a DSP channel is actually something we know how to play. + */ static void ValidateChannel(const JASDsp::TChannel& channel) { if (!ValidateChannelWaveFormat(channel)) { CRASH( @@ -39,39 +45,27 @@ static void ValidateChannel(const JASDsp::TChannel& channel) { } } -static u32 ConvertDataLengthToSamples(const JASDsp::TChannel& channel, u32 dataLen) { - if (dataLen % channel.mBytesPerBlock != 0) { - CRASH("Indivisible data length: %d\n", dataLen); - } - - return (dataLen / channel.mBytesPerBlock) * channel.mSamplesPerBlock; -} - static u32 ConvertSamplesToDataLength(const JASDsp::TChannel& channel, u32 samples) { - if (channel.mSamplesPerBlock == 1) { - if (channel.mBytesPerBlock == 16) { - return samples * 2; - } - if (channel.mBytesPerBlock == 8) { - return samples; - } - CRASH("Unknown format"); - } - if (samples % channel.mSamplesPerBlock != 0) { // Ensure we round up. samples += channel.mSamplesPerBlock; //CRASH("Indivisible sample count: %d\n", samples); } - return (samples / channel.mSamplesPerBlock) * channel.mBytesPerBlock; + return (samples / channel.mSamplesPerBlock) * BlockBytes(channel); } +/** + * Render the audio data contributed by a single DSP channel. Reads & decodes new input samples. + */ static void RenderChannel( JASDsp::TChannel& channel, ChannelAuxData& channelAux, OutputSubframe& subframe); +/** + * Converts a pitch value on a DSP channel to a sample rate. + */ constexpr static int PitchToSampleRate(u16 value) { return static_cast(static_cast(SampleRate) * value / 4096); } @@ -89,6 +83,9 @@ static void UpdateSampleRate(const JASDsp::TChannel& channel, ChannelAuxData& au aux.prevPitch = channel.mPitch; } +/** + * Reset state for a DSP channel between independent playbacks. + */ static void ResetChannel(JASDsp::TChannel& channel, ChannelAuxData& aux) { channel.mSamplesLeft = channel.mEndSample - channel.mSamplePosition; @@ -101,6 +98,9 @@ static void ResetChannel(JASDsp::TChannel& channel, ChannelAuxData& aux) { channel.mResetFlag = false; } +/** + * Mix subframe data from src into dst. + */ static void MixSubframe(DspSubframe& dst, const DspSubframe& src) { for (int i = 0; i < dst.size(); i++) { dst[i] += src[i]; @@ -142,14 +142,16 @@ void dusk::audio::DspRender(OutputSubframe& subframe) { } } -static void ReadSamplesCore( +/** + * Actually decode samples from memory for the given audio channel. + */ +static void ReadSampleData( const JASDsp::TChannel& channel, + ChannelAuxData& aux, const u8* data, size_t dataLength, s16* pcm, - size_t pcmLength, - s16& hist1, - s16& hist0) { + size_t pcmLength) { if (channel.mSamplesPerBlock == 1) { if (channel.mBytesPerBlock == 0x10) { // PCM16 @@ -166,16 +168,62 @@ static void ReadSamplesCore( } } else { if (channel.mBytesPerBlock == 9) { - Adpcm4ToPcm16(data, dataLength, pcm, pcmLength, hist1, hist0); + Adpcm4ToPcm16(data, dataLength, pcm, pcmLength, aux.hist1, aux.hist0); } else { CRASH("Unsupported format: ADPCM2"); } } } +/** + * Read a single *contiguous* chunk of sample data from a channel, + * writes the samples to the channel's resampler stream. + * + * @returns Amount of samples actually read. + */ +static int ReadChannelSamplesChunk( + JASDsp::TChannel& channel, + ChannelAuxData& aux, + int desiredSamples) { + + assert(desiredSamples >= 0); + + auto aramBase = static_cast(ARGetStorageAddress()) + channel.mWaveAramAddress; + + // Streaming logic directly modifies mSamplesLeft. + // So we use that as our tracking of where we are. + auto curSamplePosition = channel.mEndSample - channel.mSamplesLeft; + assert(curSamplePosition % channel.mSamplesPerBlock == 0); + auto dataPosition = ConvertSamplesToDataLength(channel, curSamplePosition); + + u32 renderSamples = std::min(channel.mSamplesLeft, static_cast(desiredSamples)); + + int renderSize = static_cast(sizeof(s16) * renderSamples); + auto renderData = static_cast(alloca(renderSize)); + memset(renderData, 0, renderSize); + + ReadSampleData( + channel, + aux, + aramBase + dataPosition, + ConvertSamplesToDataLength(channel, renderSamples), + renderData, + renderSamples); + + channel.mSamplesLeft -= renderSamples; + channel.mSamplePosition += renderSamples; + + SDL_PutAudioStreamData(aux.resampleStream, renderData, renderSize); + + return static_cast(renderSamples); +} + +/** + * Reads new audio channels from a DSP channel and writes them to the resampler stream. + */ static void SDLCALL ReadChannelSamples( void *userdata, - SDL_AudioStream *stream, + SDL_AudioStream*, int additional_amount, int) { @@ -195,30 +243,8 @@ static void SDLCALL ReadChannelSamples( additional_amount = ALIGN_NEXT(additional_amount, channel.mSamplesPerBlock); - int requestedSize = static_cast(sizeof(s16) * additional_amount); - auto requested = static_cast(alloca(requestedSize)); - memset(requested, 0, requestedSize); - - auto aramBase = static_cast(ARGetStorageAddress()) + channel.mWaveAramAddress; - - // Streaming logic directly modifies mSamplesLeft. - // So we use that as our tracking of where we are. - auto curSamplePosition = channel.mEndSample - channel.mSamplesLeft; - auto dataPosition = ConvertSamplesToDataLength(channel, curSamplePosition); - - u32 renderSamples = std::min(channel.mSamplesLeft, static_cast(additional_amount)); - - ReadSamplesCore( - channel, - aramBase + dataPosition, - ConvertSamplesToDataLength(channel, renderSamples), - requested, - renderSamples, - aux.hist1, - aux.hist0); - - channel.mSamplesLeft -= renderSamples; - channel.mSamplePosition += renderSamples; + auto samplesRead = ReadChannelSamplesChunk(channel, aux, additional_amount); + additional_amount -= samplesRead; if (channel.mSamplesLeft == 0) { // Reached end of buffer. @@ -228,28 +254,19 @@ static void SDLCALL ReadChannelSamples( channel.mSamplesLeft = channel.mEndSample - channel.mLoopStartSample; channel.mSamplePosition = channel.mLoopStartSample; - curSamplePosition = channel.mEndSample - channel.mSamplesLeft; - dataPosition = ConvertSamplesToDataLength(channel, curSamplePosition); + } - ReadSamplesCore( - channel, - aramBase + dataPosition, - ConvertSamplesToDataLength(channel, additional_amount - renderSamples), - requested + renderSamples, - additional_amount - renderSamples, - aux.hist1, - aux.hist0); - - channel.mSamplesLeft -= (additional_amount - renderSamples); - channel.mSamplePosition += (additional_amount - renderSamples); + if (additional_amount >= 0) { + ReadChannelSamplesChunk(channel, aux, additional_amount); } channel.mAramStreamPosition = channel.mWaveAramAddress + ConvertSamplesToDataLength(channel, channel.mSamplePosition); - - SDL_PutAudioStreamData(stream, requested, requestedSize); } +/** + * Get the expected BusConnect value needed to define the given output channel in a DSP channel. + */ constexpr u16 GetBusConnect(const OutputChannel channel) { switch (channel) { // TODO: This is a guess for now. @@ -262,6 +279,10 @@ constexpr u16 GetBusConnect(const OutputChannel channel) { } } +/** + * For a DSP channel the JASDsp::OutputChannelConfig value targeting the given output channel. + * Returns null if the DSP channel does not output to this output channel. + */ static const JASDsp::OutputChannelConfig* GetOutputConfig( const JASDsp::TChannel& sourceChannel, OutputChannel channel) { @@ -277,6 +298,9 @@ static const JASDsp::OutputChannelConfig* GetOutputConfig( return nullptr; } +/** + * Get the volume that the given DSP channel should render to the given output channel at. + */ static f32 GetVolumeForOutputChannel( const JASDsp::TChannel& sourceChannel, OutputChannel outputChannel) { @@ -315,6 +339,9 @@ static f32 GetVolumeForOutputChannel( return ratio; } +/** + * Given decoded & resampled input samples, render a DSP channel to a given output channel. + */ static void RenderOutputChannel( const JASDsp::TChannel& sourceChannel, OutputChannel outputChannel, diff --git a/src/dusk/audio/DuskDsp.hpp b/src/dusk/audio/DuskDsp.hpp index 23e05b60e6..9dbffbfec4 100644 --- a/src/dusk/audio/DuskDsp.hpp +++ b/src/dusk/audio/DuskDsp.hpp @@ -16,6 +16,9 @@ namespace dusk::audio { OutputChannel_MAX }; + /** + * Data stored by DSP implementation for each DSP channel. + */ struct ChannelAuxData { s16 hist1; s16 hist0; @@ -25,8 +28,14 @@ namespace dusk::audio { extern ChannelAuxData ChannelAux[DSP_CHANNELS]; + /** + * Data storage for a single subframe and output channel's worth of samples. + */ using DspSubframe = std::array; + /** + * Data storage for a single subframe's worth of samples, across all output channels. + */ struct OutputSubframe { static constexpr int NUM_CHANNELS = static_cast(OutputChannel::OutputChannel_MAX); @@ -38,6 +47,38 @@ namespace dusk::audio { } }; + /** + * Initialize the DSP system, creating data storage needed for channels and such. + */ void DspInit(); + + /** + * Render a subframe of audio with the current DSP state. + */ void DspRender(OutputSubframe& subframe); + + /** + * Get the amount of samples a single "block" of this channel's data has. + */ + constexpr u32 BlockSamples(const JASDsp::TChannel& channel) { + return channel.mSamplesPerBlock; + } + + /** + * Get the amount of bytes a single "block" of this channel's data has. + */ + constexpr u32 BlockBytes(const JASDsp::TChannel& channel) { + if (channel.mSamplesPerBlock == 1) { + if (channel.mBytesPerBlock == 16) { + return 2; + } + if (channel.mBytesPerBlock == 8) { + return 1; + } + + CRASH("Unknown format"); + } + + return channel.mBytesPerBlock; + } } From 82511a95af3b8b6014bfd6c36d15f8c3e553b4f8 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Fri, 20 Mar 2026 17:54:37 +0100 Subject: [PATCH 077/350] CLion? More like CLiedToMe --- src/dusk/audio/DuskDsp.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/dusk/audio/DuskDsp.hpp b/src/dusk/audio/DuskDsp.hpp index 9dbffbfec4..9168320e0f 100644 --- a/src/dusk/audio/DuskDsp.hpp +++ b/src/dusk/audio/DuskDsp.hpp @@ -6,6 +6,8 @@ #include #include "SDL3/SDL_audio.h" +// ReSharper disable once CppUnusedIncludeDirective +#include "global.h" namespace dusk::audio { constexpr int SampleRate = 32000; From fb1323a583e5505cb3b412095129283920ce3df9 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Fri, 20 Mar 2026 18:05:17 +0100 Subject: [PATCH 078/350] Stop compiling some DSP-related files. Replace it all with stubs. --- CMakeLists.txt | 3 ++- files.cmake | 4 ---- src/dusk/audio/DspStub.cpp | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 5 deletions(-) create mode 100644 src/dusk/audio/DspStub.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 12c4154994..1ee357e1a7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -89,7 +89,8 @@ add_library(game_debug STATIC ${JSYSTEM_DEBUG_FILES} ${SSYSTEM_FILES} src/dusk/audio/DuskDsp.hpp src/dusk/audio/DuskDsp.cpp src/dusk/audio/Adpcm.cpp - src/dusk/audio/Adpcm.hpp) + src/dusk/audio/Adpcm.hpp + src/dusk/audio/DspStub.cpp) target_compile_definitions(game_debug PRIVATE TARGET_PC AVOID_UB=1 VERSION=0 $<$:DEBUG=1>) # Make these properties PUBLIC so that the regular game target also sees them. diff --git a/files.cmake b/files.cmake index cae7f66aed..2290f68b53 100644 --- a/files.cmake +++ b/files.cmake @@ -507,10 +507,6 @@ set(JSYSTEM_DEBUG_FILES libs/JSystem/src/JAudio2/JASDSPInterface.cpp libs/JSystem/src/JAudio2/JASDriverIF.cpp libs/JSystem/src/JAudio2/JASSoundParams.cpp - libs/JSystem/src/JAudio2/dspproc.cpp - libs/JSystem/src/JAudio2/dsptask.cpp - libs/JSystem/src/JAudio2/osdsp.cpp - libs/JSystem/src/JAudio2/osdsp_task.cpp libs/JSystem/src/JAudio2/JAIAudible.cpp libs/JSystem/src/JAudio2/JAIAudience.cpp libs/JSystem/src/JAudio2/JAISe.cpp diff --git a/src/dusk/audio/DspStub.cpp b/src/dusk/audio/DspStub.cpp new file mode 100644 index 0000000000..c2dec809f2 --- /dev/null +++ b/src/dusk/audio/DspStub.cpp @@ -0,0 +1,35 @@ +#include "JSystem/JAudio2/dspproc.h" +#include "JSystem/JAudio2/osdsp_task.h" +#include "JSystem/JAudio2/dsptask.h" +#include "global.h" +#include "os.h" + +void DSPReleaseHalt2(u32) { + CRASH("We do not directly emulate the DSP"); +} +void DsetupTable(u32, u32, u32, u32, u32) { + // Nada. +} +void DsetMixerLevel(f32) { + // Nada for now, but maybe we should care about this? +} +void DsyncFrame2ch(u32, u32, u32) { + CRASH("We do not directly emulate the DSP"); +} +void DsyncFrame4ch(u32, u32, u32, u32, u32) { + CRASH("We do not directly emulate the DSP"); +} + +void DspBoot(void (*)(void*)) { + CRASH("We do not directly emulate the DSP"); +} +void DspFinishWork(u16) { + CRASH("We do not directly emulate the DSP"); +} +int DSPSendCommands2(u32*, u32, void (*)(u16)) { + CRASH("We do not directly emulate the DSP"); +} + +void DsyncFrame2(u32, u32, u32) { + CRASH("We do not directly emulate the DSP"); +} From d257229e9c74037dcc7c6ce71d4c161eb9d9cc68 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Fri, 20 Mar 2026 18:50:47 +0100 Subject: [PATCH 079/350] Stop compiling more DSP stubs --- libs/JSystem/src/JAudio2/JASAudioThread.cpp | 6 ++++ src/dusk/stubs.cpp | 40 --------------------- 2 files changed, 6 insertions(+), 40 deletions(-) diff --git a/libs/JSystem/src/JAudio2/JASAudioThread.cpp b/libs/JSystem/src/JAudio2/JASAudioThread.cpp index baa3d8b42e..fa476d1cb4 100644 --- a/libs/JSystem/src/JAudio2/JASAudioThread.cpp +++ b/libs/JSystem/src/JAudio2/JASAudioThread.cpp @@ -66,6 +66,7 @@ class JASChannel { // NONMATCHING location of JASPoolAllocObject_MultiThreaded void* JASAudioThread::run() { +#if !TARGET_PC OSInitFastCast(); JASDriver::initAI(DMACallback); JASDsp::boot(DSPCallback); @@ -115,6 +116,9 @@ void* JASAudioThread::run() { JUT_PANIC(152, "AUDIO THREAD INVALID MESSAGE\n"); } } +#else + return 0; +#endif } void JASAudioThread::DMACallback() { @@ -128,6 +132,7 @@ void JASAudioThread::DMACallback() { } void JASAudioThread::DSPCallback(void*) { +#if !TARGET_PC JASAudioThread* pAudioThread = getInstance(); JUT_ASSERT(184, pAudioThread); while (DSPCheckMailFromDSP() == 0) { } @@ -144,4 +149,5 @@ void JASAudioThread::DSPCallback(void*) { JASDsp::finishWork(mail); } } +#endif } diff --git a/src/dusk/stubs.cpp b/src/dusk/stubs.cpp index 73f4e93d80..3e44240d24 100644 --- a/src/dusk/stubs.cpp +++ b/src/dusk/stubs.cpp @@ -402,46 +402,6 @@ VIRetraceCallback VISetPreRetraceCallback(VIRetraceCallback cb) { } // extern "C" -#pragma mark DSP -#include -extern "C" void __DSP_insert_task(DSPTaskInfo* task) { - STUB_LOG(); -} - -extern "C" void __DSP_boot_task(DSPTaskInfo*) { - STUB_LOG(); -} - -extern "C" void __DSP_exec_task(DSPTaskInfo*, DSPTaskInfo*) { - STUB_LOG(); -} - -extern "C" void __DSP_remove_task(DSPTaskInfo* task) { - STUB_LOG(); -} - -void DSPAssertInt(void) { - STUB_LOG(); -} -u32 DSPCheckMailFromDSP(void) { - STUB_LOG(); - return 0; -} -u32 DSPCheckMailToDSP(void) { - STUB_LOG(); - return 0; -} -void DSPInit(void) { - STUB_LOG(); -} -u32 DSPReadMailFromDSP(void) { - STUB_LOG(); - return 0; -} -void DSPSendMailToDSP(u32 mail) { - STUB_LOG(); -} - #pragma mark Z2Audio class Z2AudioCS { public: From f28585d18cc2aca4718df42f6a6f6447e5aa1245 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Fri, 20 Mar 2026 19:38:49 +0100 Subject: [PATCH 080/350] Fix block-unaligned loop decoding --- src/dusk/audio/DuskDsp.cpp | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/src/dusk/audio/DuskDsp.cpp b/src/dusk/audio/DuskDsp.cpp index 97c6f1c6d1..81cceddb75 100644 --- a/src/dusk/audio/DuskDsp.cpp +++ b/src/dusk/audio/DuskDsp.cpp @@ -179,7 +179,7 @@ static void ReadSampleData( * Read a single *contiguous* chunk of sample data from a channel, * writes the samples to the channel's resampler stream. * - * @returns Amount of samples actually read. + * @returns Amount of samples actually read. Can be greater than the amount requested. */ static int ReadChannelSamplesChunk( JASDsp::TChannel& channel, @@ -193,6 +193,22 @@ static int ReadChannelSamplesChunk( // Streaming logic directly modifies mSamplesLeft. // So we use that as our tracking of where we are. auto curSamplePosition = channel.mEndSample - channel.mSamplesLeft; + + u32 skipSamples = curSamplePosition % channel.mSamplesPerBlock; + if (skipSamples != 0) { + // We need to start reading in the middle of a block. This can happen thanks to loops. + // So we move back to the start of the block and keep track that those samples should + // *not* be emitted. + desiredSamples += static_cast(skipSamples); + curSamplePosition -= skipSamples; + + channel.mSamplesLeft += skipSamples; + channel.mSamplePosition -= skipSamples; + } + + // Pad desiredSamples so that we always leave the channel block-aligned. + desiredSamples = ALIGN_NEXT(desiredSamples, channel.mSamplesPerBlock); + assert(curSamplePosition % channel.mSamplesPerBlock == 0); auto dataPosition = ConvertSamplesToDataLength(channel, curSamplePosition); @@ -213,9 +229,14 @@ static int ReadChannelSamplesChunk( channel.mSamplesLeft -= renderSamples; channel.mSamplePosition += renderSamples; - SDL_PutAudioStreamData(aux.resampleStream, renderData, renderSize); + SDL_PutAudioStreamData( + aux.resampleStream, + renderData + skipSamples, + static_cast(renderSize - skipSamples * sizeof(u16))); - return static_cast(renderSamples); + assert(channel.mSamplePosition % channel.mSamplesPerBlock == 0 || channel.mSamplesLeft == 0); + + return static_cast(renderSamples - skipSamples); } /** @@ -241,8 +262,6 @@ static void SDLCALL ReadChannelSamples( return; } - additional_amount = ALIGN_NEXT(additional_amount, channel.mSamplesPerBlock); - auto samplesRead = ReadChannelSamplesChunk(channel, aux, additional_amount); additional_amount -= samplesRead; @@ -254,6 +273,9 @@ static void SDLCALL ReadChannelSamples( channel.mSamplesLeft = channel.mEndSample - channel.mLoopStartSample; channel.mSamplePosition = channel.mLoopStartSample; + + aux.hist1 = channel.mpPenult; + aux.hist0 = channel.mpLast; } if (additional_amount >= 0) { From 3ad7e70963125bfee2ebb38f97a94540a9ff51c6 Mon Sep 17 00:00:00 2001 From: Lurs Date: Wed, 25 Mar 2026 09:35:16 +0100 Subject: [PATCH 081/350] removing Z2Creature::startCreatureVoice stub --- src/Z2AudioLib/Z2Creature.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Z2AudioLib/Z2Creature.cpp b/src/Z2AudioLib/Z2Creature.cpp index a17e3d516c..7a27e9e9c4 100644 --- a/src/Z2AudioLib/Z2Creature.cpp +++ b/src/Z2AudioLib/Z2Creature.cpp @@ -211,11 +211,6 @@ Z2SoundHandlePool* Z2Creature::startCreatureSoundLevel(JAISoundID soundID, u32 m } Z2SoundHandlePool* Z2Creature::startCreatureVoice(JAISoundID soundID, s8 reverb) { - #if TARGET_PC - // stubbed - return nullptr; - #endif - switch (soundID) { case Z2SE_MDN_V_FLY_OUT: case Z2SE_MDN_V_MGN_TAME: From 72e2c71de6e205006b8d895ac1a718d45e097515 Mon Sep 17 00:00:00 2001 From: Lurs Date: Thu, 26 Mar 2026 11:02:24 +0100 Subject: [PATCH 082/350] Fix audio memory leak --- src/Z2AudioLib/Z2Audience.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Z2AudioLib/Z2Audience.cpp b/src/Z2AudioLib/Z2Audience.cpp index be7bd4b126..9adab4084f 100644 --- a/src/Z2AudioLib/Z2Audience.cpp +++ b/src/Z2AudioLib/Z2Audience.cpp @@ -552,7 +552,11 @@ JAIAudible* Z2Audience::newAudible(const JGeometry::TVec3& pos, JAISoundID } void Z2Audience::deleteAudible(JAIAudible* audible) { +#if TARGET_PC + JKR_DELETE(static_cast(audible)); +#else JKR_DELETE(audible); +#endif } Z2Audible::~Z2Audible() {} From e2923d374581b781f24c37523d5154222fdf7e63 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Thu, 26 Mar 2026 15:03:48 +0100 Subject: [PATCH 083/350] Fix shutdown crash --- include/dusk/main.h | 8 ++++++++ libs/JSystem/src/JAudio2/JAIAudience.cpp | 9 +++++++++ src/m_Do/m_Do_main.cpp | 7 +++++++ 3 files changed, 24 insertions(+) create mode 100644 include/dusk/main.h diff --git a/include/dusk/main.h b/include/dusk/main.h new file mode 100644 index 0000000000..b357de8ea5 --- /dev/null +++ b/include/dusk/main.h @@ -0,0 +1,8 @@ +#ifndef DUSK_MAIN_H +#define DUSK_MAIN_H + +namespace dusk { + extern bool IsShuttingDown; +} + +#endif // DUSK_MAIN_H diff --git a/libs/JSystem/src/JAudio2/JAIAudience.cpp b/libs/JSystem/src/JAudio2/JAIAudience.cpp index 3e1205e2fc..a14422a943 100644 --- a/libs/JSystem/src/JAudio2/JAIAudience.cpp +++ b/libs/JSystem/src/JAudio2/JAIAudience.cpp @@ -1,11 +1,20 @@ #include "JSystem/JSystem.h" // IWYU pragma: keep #include "JSystem/JAudio2/JAIAudience.h" + #include "JSystem/JAudio2/JAISeMgr.h" #include "JSystem/JAudio2/JAISeqMgr.h" #include "JSystem/JAudio2/JAIStreamMgr.h" +#include "dusk/main.h" JAIAudience::~JAIAudience() { +#if TARGET_PC + if (dusk::IsShuttingDown) { + // Those asserts down there crash on shutdown from dtors. + return; + } +#endif + JUT_ASSERT(14, ! JAISeMgr::getInstance() || ( JAISeMgr::getInstance() ->getAudience() != this )); JUT_ASSERT(15, ! JAISeqMgr::getInstance() || ( JAISeqMgr::getInstance() ->getAudience() != this )); JUT_ASSERT(16, ! JAIStreamMgr::getInstance() || ( JAIStreamMgr::getInstance() ->getAudience() != this )); diff --git a/src/m_Do/m_Do_main.cpp b/src/m_Do/m_Do_main.cpp index 20fc2a8aee..d03b352151 100644 --- a/src/m_Do/m_Do_main.cpp +++ b/src/m_Do/m_Do_main.cpp @@ -47,6 +47,7 @@ #include "dusk/dusk.h" #include "dusk/logging.h" #include "dusk/time.h" +#include "dusk/main.h" #include #include @@ -73,6 +74,10 @@ const int audioHeapSize = 0x14D800; // ========================================================================= #define COPYDATE_PATH "/str/Final/Release/COPYDATE" +#if TARGET_PC +bool dusk::IsShuttingDown = false; +#endif + s32 LOAD_COPYDATE(void*) { char buffer[32]; memset(buffer, 0, sizeof(buffer)); @@ -306,6 +311,8 @@ int game_main(int argc, char* argv[]) { fflush(stdout); fflush(stderr); + dusk::IsShuttingDown = true; + aurora_shutdown(); return 0; From 1faded3faf3672739a8b4d1cf670b958dc13201c Mon Sep 17 00:00:00 2001 From: Lurs Date: Fri, 27 Mar 2026 12:23:41 +0100 Subject: [PATCH 084/350] fix stuck note e.g. in Hyrule field --- libs/JSystem/src/JKernel/JKRHeap.cpp | 8 +++++++- src/dusk/audio/DuskDsp.cpp | 5 +++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/libs/JSystem/src/JKernel/JKRHeap.cpp b/libs/JSystem/src/JKernel/JKRHeap.cpp index bc02d877fb..c2b32999fd 100644 --- a/libs/JSystem/src/JKernel/JKRHeap.cpp +++ b/libs/JSystem/src/JKernel/JKRHeap.cpp @@ -585,7 +585,13 @@ void* operator new(size_t size JKR_HEAP_TOKEN_PARAM, int alignment) { #endif void* operator new(size_t size JKR_HEAP_TOKEN_PARAM, JKRHeap* heap, int alignment) { - return JKRHeap::alloc(size, alignment, heap); + void* mem = JKRHeap::alloc(size, alignment, heap); +#if TARGET_PC + if (mem == nullptr) { + return fallback_alloc(size, abs(alignment), true); + } +#endif + return mem; } #if !TARGET_PC diff --git a/src/dusk/audio/DuskDsp.cpp b/src/dusk/audio/DuskDsp.cpp index 81cceddb75..9c58d8de59 100644 --- a/src/dusk/audio/DuskDsp.cpp +++ b/src/dusk/audio/DuskDsp.cpp @@ -125,6 +125,11 @@ void dusk::audio::DspRender(OutputSubframe& subframe) { continue; } + if (channel.mForcedStop) { + channel.mIsFinished = true; + continue; + } + if (channel.mBytesPerBlock == 0) { // I think these are oscillator channels? Not backed by audio. channel.mIsFinished = true; From 2e441c91563ba56ba7ad41da51fc8444dc724602 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Fri, 27 Mar 2026 12:42:37 +0100 Subject: [PATCH 085/350] Skip oscillator channels properly I think --- src/dusk/audio/DuskDsp.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/dusk/audio/DuskDsp.cpp b/src/dusk/audio/DuskDsp.cpp index 9c58d8de59..bbcf4e94fd 100644 --- a/src/dusk/audio/DuskDsp.cpp +++ b/src/dusk/audio/DuskDsp.cpp @@ -130,8 +130,9 @@ void dusk::audio::DspRender(OutputSubframe& subframe) { continue; } - if (channel.mBytesPerBlock == 0) { + if (channel.mWaveAramAddress == 0) { // I think these are oscillator channels? Not backed by audio. + // No idea how to implement these yet, so skip them. channel.mIsFinished = true; continue; } From 625a94dcff7ceca18a861d7854f106cd4a57ad60 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Fri, 27 Mar 2026 13:35:33 +0100 Subject: [PATCH 086/350] Replace ugly cast in dusk::audio::DspRender --- src/dusk/audio/DuskDsp.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/dusk/audio/DuskDsp.cpp b/src/dusk/audio/DuskDsp.cpp index bbcf4e94fd..76e6154f80 100644 --- a/src/dusk/audio/DuskDsp.cpp +++ b/src/dusk/audio/DuskDsp.cpp @@ -108,8 +108,7 @@ static void MixSubframe(DspSubframe& dst, const DspSubframe& src) { } void dusk::audio::DspRender(OutputSubframe& subframe) { - // This cast half exists because my debugger sucks and this is an easy way to look at the data. - auto& channels = *reinterpret_cast*>(JASDsp::CH_BUF); + std::span channels(JASDsp::CH_BUF, DSP_CHANNELS); for (int i = 0; i < channels.size(); i++) { auto& channel = channels[i]; From 6073a0c3f059edb189c61857436ca18b20cf2384 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Fri, 27 Mar 2026 14:54:03 +0100 Subject: [PATCH 087/350] Implement master volume & volume interpolation Does not fix clicking :( Fixes https://github.com/TakaRikka/dusk/issues/132 Fixes https://github.com/TakaRikka/dusk/issues/128 --- include/dusk/audio/DuskAudioSystem.h | 8 +++ src/dusk/audio/DuskAudioSystem.cpp | 15 ++++++ src/dusk/audio/DuskDsp.cpp | 77 ++++++++++++++++++++++++---- src/dusk/audio/DuskDsp.hpp | 26 ++++++++++ src/dusk/imgui/ImGuiAudio.cpp | 16 ++++-- src/dusk/imgui/ImGuiMenuGame.cpp | 9 +++- 6 files changed, 134 insertions(+), 17 deletions(-) diff --git a/include/dusk/audio/DuskAudioSystem.h b/include/dusk/audio/DuskAudioSystem.h index c41762e146..1b31db6e7c 100644 --- a/include/dusk/audio/DuskAudioSystem.h +++ b/include/dusk/audio/DuskAudioSystem.h @@ -1,8 +1,16 @@ #pragma once +#include + namespace dusk::audio { /** * Initialize the audio system and start playing audio. */ void Initialize(); + + void SetMasterVolume(f32 value); + + u32 GetResetCount(int channelIdx); + + f32 VolumeFromU16(u16 value); } diff --git a/src/dusk/audio/DuskAudioSystem.cpp b/src/dusk/audio/DuskAudioSystem.cpp index 7c2572dd53..0ed2101dcf 100644 --- a/src/dusk/audio/DuskAudioSystem.cpp +++ b/src/dusk/audio/DuskAudioSystem.cpp @@ -15,6 +15,7 @@ #include "DuskDsp.hpp" #include "JSystem/JAudio2/JASAudioThread.h" +#include "JSystem/JAudio2/JASDriverIF.h" // #define DUSK_DUMP_AUDIO @@ -73,6 +74,12 @@ void dusk::audio::Initialize() { SDL_ResumeAudioStreamDevice(PlaybackStream); } +void dusk::audio::SetMasterVolume(const f32 value) { + JASCriticalSection section; + + MasterVolume = value; +} + void SDLCALL GetNewAudio( void*, SDL_AudioStream*, @@ -132,3 +139,11 @@ void RenderAudioSubframe() { SDL_PutAudioStreamData(PlaybackStream, &OutInterleaveBuffer, sizeof(OutInterleaveBuffer)); } + +u32 dusk::audio::GetResetCount(int channelIdx) { + return ChannelAux[channelIdx].resetCount; +} + +f32 dusk::audio::VolumeFromU16(u16 value) { + return static_cast(value) / static_cast(JASDriver::getChannelLevel_dsp()); +} diff --git a/src/dusk/audio/DuskDsp.cpp b/src/dusk/audio/DuskDsp.cpp index 76e6154f80..2b26ea478c 100644 --- a/src/dusk/audio/DuskDsp.cpp +++ b/src/dusk/audio/DuskDsp.cpp @@ -9,6 +9,7 @@ #include "Adpcm.hpp" #include "JSystem/JAudio2/JASDriverIF.h" +#include "dusk/audio/DuskAudioSystem.h" #include "dusk/endian.h" #include "global.h" @@ -16,6 +17,9 @@ using namespace dusk::audio; ChannelAuxData dusk::audio::ChannelAux[DSP_CHANNELS] = {}; +f32 dusk::audio::MasterVolume = 1.0f; +f32 dusk::audio::PrevMasterVolume = 1.0f; + /** * Validate that a DSP channel's format is actually something we know how to play. */ @@ -87,6 +91,8 @@ static void UpdateSampleRate(const JASDsp::TChannel& channel, ChannelAuxData& au * Reset state for a DSP channel between independent playbacks. */ static void ResetChannel(JASDsp::TChannel& channel, ChannelAuxData& aux) { + aux.resetCount += 1; + channel.mSamplesLeft = channel.mEndSample - channel.mSamplePosition; aux.hist0 = 0; @@ -95,6 +101,10 @@ static void ResetChannel(JASDsp::TChannel& channel, ChannelAuxData& aux) { SDL_ClearAudioStream(aux.resampleStream); UpdateSampleRate(channel, aux); + for (auto& volume : aux.prevVolume) { + volume = NAN; + } + channel.mResetFlag = false; } @@ -145,6 +155,11 @@ void dusk::audio::DspRender(OutputSubframe& subframe) { MixSubframe(subframe.channels[o], channelSubframe.channels[o]); } } + + for (auto& channel : subframe.channels) { + ApplyVolume(channel, channel, PrevMasterVolume, MasterVolume); + } + PrevMasterVolume = MasterVolume; } /** @@ -325,17 +340,24 @@ static const JASDsp::OutputChannelConfig* GetOutputConfig( return nullptr; } +struct VolumeValue { + f32 Target; + f32 Init; +}; + /** * Get the volume that the given DSP channel should render to the given output channel at. */ -static f32 GetVolumeForOutputChannel( +static VolumeValue GetVolumeForOutputChannel( const JASDsp::TChannel& sourceChannel, OutputChannel outputChannel) { u16 volume; + u16 initVolume; f32 panValue = 1; if (sourceChannel.mAutoMixerBeenSet) { volume = sourceChannel.mAutoMixerVolume; + initVolume = sourceChannel.mAutoMixerInitVolume; auto autoMixerPan = static_cast(sourceChannel.mAutoMixerPanDolby >> 8) / 127; @@ -353,17 +375,21 @@ static f32 GetVolumeForOutputChannel( } else { auto config = GetOutputConfig(sourceChannel, outputChannel); if (config == nullptr) { - return 0; + return {0, 0}; } volume = config->mTargetVolume; + initVolume = config->mCurrentVolume; } // TODO: interpolate to avoid popping. - f32 ratio = static_cast(volume) / static_cast(JASDriver::getChannelLevel_dsp()); - ratio *= panValue; + f32 targetRatio = VolumeFromU16(volume); + targetRatio *= panValue; - return ratio; + f32 initRatio = VolumeFromU16(initVolume); + initRatio *= panValue; + + return {targetRatio, initRatio}; } /** @@ -371,6 +397,7 @@ static f32 GetVolumeForOutputChannel( */ static void RenderOutputChannel( const JASDsp::TChannel& sourceChannel, + ChannelAuxData& aux, OutputChannel outputChannel, const std::span inputSamples, OutputSubframe& fullOutputSubframe) { @@ -379,13 +406,20 @@ static void RenderOutputChannel( assert(inputSamples.size() <= outputSubframe.size()); auto volume = GetVolumeForOutputChannel(sourceChannel, outputChannel); - if (volume == 0) { + + f32 targetVolume = volume.Target; + auto& prevVolume = aux.PrevVolume(outputChannel); + if (std::isnan(prevVolume)) { + // Initialize previous volume to new volume on first render. + prevVolume = volume.Init; + } + + if (prevVolume == 0 && targetVolume == 0) { return; } - for (int i = 0; i < inputSamples.size(); i++) { - outputSubframe[i] = inputSamples[i] * volume; - } + ApplyVolume(outputSubframe, inputSamples, prevVolume, targetVolume); + prevVolume = targetVolume; } static void RenderChannel( @@ -414,8 +448,8 @@ static void RenderChannel( static_assert(OutputSubframe::NUM_CHANNELS == 2, "Keep RenderChannel in sync!"); - RenderOutputChannel(channel, OutputChannel::LEFT, hasReadSamples, subframe); - RenderOutputChannel(channel, OutputChannel::RIGHT, hasReadSamples, subframe); + RenderOutputChannel(channel, channelAux, OutputChannel::LEFT, hasReadSamples, subframe); + RenderOutputChannel(channel, channelAux,OutputChannel::RIGHT, hasReadSamples, subframe); } void dusk::audio::DspInit() { @@ -440,3 +474,24 @@ void dusk::audio::DspInit() { reinterpret_cast(static_cast(i))); } } + +void dusk::audio::ApplyVolume( + std::span dst, + const std::span src, + const f32 startVolume, + const f32 endVolume) { + assert(dst.size() >= src.size()); + + if (startVolume == endVolume) { + for (int i = 0; i < src.size(); i++) { + dst[i] = src[i] * startVolume; + } + } else { + const f32 step = (endVolume - startVolume) / static_cast(src.size()); + auto curVolume = startVolume; + for (int i = 0; i < src.size(); i++) { + dst[i] = src[i] * curVolume; + curVolume += step; + } + } +} diff --git a/src/dusk/audio/DuskDsp.hpp b/src/dusk/audio/DuskDsp.hpp index 9168320e0f..c84bb338db 100644 --- a/src/dusk/audio/DuskDsp.hpp +++ b/src/dusk/audio/DuskDsp.hpp @@ -6,6 +6,8 @@ #include #include "SDL3/SDL_audio.h" +#include + // ReSharper disable once CppUnusedIncludeDirective #include "global.h" @@ -26,6 +28,21 @@ namespace dusk::audio { s16 hist0; SDL_AudioStream* resampleStream; u16 prevPitch; + + // Used for debugging tools. + u32 resetCount; + + /** + * Previous volume values, per output channel. + * Used to avoid clicking when volumes change. + * Set to NaN after channel reset, indicating that initial volume value is previous. + */ + f32 prevVolume[static_cast(OutputChannel::OutputChannel_MAX)]; + + f32& PrevVolume(OutputChannel channel) { + assert(channel < OutputChannel::OutputChannel_MAX); + return prevVolume[static_cast(channel)]; + } }; extern ChannelAuxData ChannelAux[DSP_CHANNELS]; @@ -83,4 +100,13 @@ namespace dusk::audio { return channel.mBytesPerBlock; } + + /** + * Apply a volume level to audio data. + * Interpolates across the two provided volume levels to avoid clicking. + */ + void ApplyVolume(std::span dst, std::span src, f32 startVolume, f32 endVolume); + + extern f32 MasterVolume; + extern f32 PrevMasterVolume; } diff --git a/src/dusk/imgui/ImGuiAudio.cpp b/src/dusk/imgui/ImGuiAudio.cpp index 07caf9f0c0..b348a5c3d9 100644 --- a/src/dusk/imgui/ImGuiAudio.cpp +++ b/src/dusk/imgui/ImGuiAudio.cpp @@ -6,14 +6,16 @@ #include "JSystem/JAudio2/JASCriticalSection.h" #include "JSystem/JAudio2/JASDSPChannel.h" #include "JSystem/JAudio2/JASDSPInterface.h" -#include "JSystem/JAudio2/JASDriverIF.h" #include "JSystem/JAudio2/JASTrack.h" +#include "dusk/audio/DuskAudioSystem.h" static std::array channelSortIndices = {}; static bool sortUpdateCount = true; static void DisplayDspChannel(int i) { + using namespace dusk::audio; + auto& channel = JASDsp::CH_BUF[i]; auto& jasChannel = JASDSPChannel::sDspChannels[i]; if (!channel.mIsActive) { @@ -39,19 +41,25 @@ static void DisplayDspChannel(int i) { auto pan = (channel.mAutoMixerPanDolby >> 8) / 127.5f; auto dolby = (channel.mAutoMixerPanDolby & 0xFF) / 127.5f; auto fxMix = (channel.mAutoMixerFxMix >> 8) / 127.5f; - auto volume = (channel.mAutoMixerVolume) / (f32) JASDriver::getChannelLevel_dsp(); + auto volume = VolumeFromU16(channel.mAutoMixerVolume); ImGui::Text( "Auto mixer active (pan: %f, dolby: %f, fx: %f, volume: %f)", pan, dolby, fxMix, volume); } else { ImGui::Text( - "Bus connect: %04X,%04X,%04X,%04X,%04X,%04X", + "Bus connect: %04X(%.2f),%04X(%.2f),%04X(%.2f),%04X(%.2f),%04X(%.2f),%04X(%.2f)", channel.mOutputChannels[0].mBusConnect, + VolumeFromU16(channel.mOutputChannels[0].mTargetVolume), channel.mOutputChannels[1].mBusConnect, + VolumeFromU16(channel.mOutputChannels[1].mTargetVolume), channel.mOutputChannels[2].mBusConnect, + VolumeFromU16(channel.mOutputChannels[2].mTargetVolume), channel.mOutputChannels[3].mBusConnect, + VolumeFromU16(channel.mOutputChannels[3].mTargetVolume), channel.mOutputChannels[4].mBusConnect, - channel.mOutputChannels[5].mBusConnect); + VolumeFromU16(channel.mOutputChannels[4].mTargetVolume), + channel.mOutputChannels[5].mBusConnect, + VolumeFromU16(channel.mOutputChannels[5].mTargetVolume)); } } diff --git a/src/dusk/imgui/ImGuiMenuGame.cpp b/src/dusk/imgui/ImGuiMenuGame.cpp index a0b79579eb..416b7d062c 100644 --- a/src/dusk/imgui/ImGuiMenuGame.cpp +++ b/src/dusk/imgui/ImGuiMenuGame.cpp @@ -7,8 +7,9 @@ #include #include "JSystem/JUtility/JUTGamePad.h" -#include "m_Do/m_Do_controller_pad.h" +#include "dusk/audio/DuskAudioSystem.h" #include "m_Do/m_Do_audio.h" +#include "m_Do/m_Do_controller_pad.h" namespace dusk { ImGuiMenuGame::ImGuiMenuGame() {} @@ -30,6 +31,8 @@ namespace dusk { ImGui::Text("Master Volume"); ImGui::SliderFloat("##m_masterVolume", &m_audioSettings.m_masterVolume, 0.0f, 1.0f, ""); + /* + // TODO: implement additional settings ImGui::Text("Main Music Volume"); ImGui::SliderFloat("##m_mainMusicVolume", &m_audioSettings.m_mainMusicVolume, 0.0f, 1.0f, ""); @@ -44,8 +47,10 @@ namespace dusk { Z2AudioMgr* audioMgr = Z2AudioMgr::getInterface(); if (audioMgr != nullptr) { - // TODO: actually apply volume settings } + */ + + audio::SetMasterVolume(m_audioSettings.m_masterVolume); ImGui::EndMenu(); } From b01581b043dd45518a99f0838d41d3da188c7f62 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Fri, 27 Mar 2026 14:54:17 +0100 Subject: [PATCH 088/350] Show new audio channels as green on the debug tool --- src/dusk/imgui/ImGuiAudio.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/dusk/imgui/ImGuiAudio.cpp b/src/dusk/imgui/ImGuiAudio.cpp index b348a5c3d9..96bd854fbd 100644 --- a/src/dusk/imgui/ImGuiAudio.cpp +++ b/src/dusk/imgui/ImGuiAudio.cpp @@ -10,6 +10,7 @@ #include "dusk/audio/DuskAudioSystem.h" static std::array channelSortIndices = {}; +static std::array lastResetCounts = {}; static bool sortUpdateCount = true; @@ -28,7 +29,14 @@ static void DisplayDspChannel(int i) { if (ImGui::BeginChild(buf, ImVec2(), ImGuiChildFlags_Border | ImGuiChildFlags_AutoResizeY)) { ImGui::Text("[%02X]", i); ImGui::SameLine(); - ImGui::Text("Update count: %d", jasChannel.mUpdateCounter); + + auto resetCount = GetResetCount(i); + ImColor color = IM_COL32_WHITE; + if (lastResetCounts[i] != resetCount) { + lastResetCounts[i] = resetCount; + color = IM_COL32(0, 0xFF, 0, 0xFF); + } + ImGui::TextColored(color, "Update count: %d, reset count: %ud", jasChannel.mUpdateCounter, resetCount); ImGui::TextUnformatted(channel.mLoopFlag ? "Loop: true" : "Loop: false"); ImGui::SameLine(); ImGui::Text("Priority: %hd", jasChannel.mPriority); From 967ea0c939d5b225e91371c442b8c1002cf0f601 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Fri, 27 Mar 2026 14:54:27 +0100 Subject: [PATCH 089/350] Audio channels are sorted with a stable sort --- src/dusk/imgui/ImGuiAudio.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dusk/imgui/ImGuiAudio.cpp b/src/dusk/imgui/ImGuiAudio.cpp index 96bd854fbd..707091b158 100644 --- a/src/dusk/imgui/ImGuiAudio.cpp +++ b/src/dusk/imgui/ImGuiAudio.cpp @@ -82,7 +82,7 @@ static void InitChannelSortIndices() { static void SortChannelsByUpdateCount() { InitChannelSortIndices(); - std::ranges::sort( + std::ranges::stable_sort( channelSortIndices, [](u8 a, u8 b) { auto& jasChannelA = JASDSPChannel::sDspChannels[a]; From c9151fca4a0670bdcaf351a9fd0fc1cd78589b70 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Fri, 27 Mar 2026 15:10:31 +0100 Subject: [PATCH 090/350] Remove SDL mixer Didn't end up using it. --- CMakeLists.txt | 5 +---- cmake/SDL-mixer.cmake | 28 ---------------------------- 2 files changed, 1 insertion(+), 32 deletions(-) delete mode 100644 cmake/SDL-mixer.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 1ee357e1a7..583b5efa94 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -62,9 +62,6 @@ FetchContent_Declare( FetchContent_MakeAvailable(cxxopts) -message(STATUS "Dusk: Fetching SDL3-mixer") - -include(cmake/SDL-mixer.cmake) include(files.cmake) # TODO: version handling for res includes @@ -103,7 +100,7 @@ target_include_directories(game_debug PUBLIC extern ${CMAKE_SOURCE_DIR}/build/${DUSK_TP_VERSION}/include build/${DUSK_TP_VERSION}/include) -target_link_libraries(game_debug PUBLIC aurora::core aurora::gx aurora::gd aurora::si aurora::vi aurora::pad aurora::mtx aurora::os aurora::dvd aurora::card SDL3_mixer::SDL3_mixer) +target_link_libraries(game_debug PUBLIC aurora::core aurora::gx aurora::gd aurora::si aurora::vi aurora::pad aurora::mtx aurora::os aurora::dvd aurora::card) set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) add_library(game SHARED ${DOLZEL_FILES} ${Z2AUDIOLIB_FILES} ${SSYSTEM_FILES} ${JSYSTEM_FILES} ${REL_FILES} ${DUSK_FILES} ${DOLPHIN_FILES} diff --git a/cmake/SDL-mixer.cmake b/cmake/SDL-mixer.cmake deleted file mode 100644 index e2106c4eb6..0000000000 --- a/cmake/SDL-mixer.cmake +++ /dev/null @@ -1,28 +0,0 @@ -set(SDLMIXER_VENDORED ON) -set(SDLMIXER_STRICT ON) -set(BUILD_SHARED_LIBS OFF) - -set(SDLMIXER_AIFF OFF) -set(SDLMIXER_WAVE OFF) -set(SDLMIXER_VOC OFF) -set(SDLMIXER_AU OFF) -set(SDLMIXER_FLAC OFF) -set(SDLMIXER_GME OFF) -set(SDLMIXER_MOD OFF) -set(SDLMIXER_MP3 OFF) -set(SDLMIXER_MIDI OFF) -set(SDLMIXER_OPUS OFF) -set(SDLMIXER_WAVPACK OFF) -set(SDLMIXER_VORBIS_STB OFF) -set(SDLMIXER_VORBIS_VORBISFILE OFF) - -FetchContent_Declare(SDL-mixer - URL https://github.com/libsdl-org/SDL_mixer/archive/refs/tags/release-3.2.0.tar.gz - URL_HASH SHA256=1077fef4a6e9513027da0a08e6f1b8645f8c88c8bef001c17befeb52b129b5dd - DOWNLOAD_EXTRACT_TIMESTAMP TRUE -) - -FetchContent_MakeAvailable(SDL-mixer) -if (NOT TARGET SDL3_mixer::SDL3_mixer) - message(FATAL_ERROR "Failed to make SDL-mixer available") -endif () From 0178ab35b3b0e9f0914e50898a27367f24fb1d0c Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Fri, 27 Mar 2026 16:54:51 +0100 Subject: [PATCH 091/350] Add replacement helpers for strncpy[_s] Portable without vulnerabilities. --- include/dusk/string.hpp | 52 ++++++++++++++++++++++++++++ libs/JSystem/src/JKernel/JKRHeap.cpp | 5 ++- src/d/d_stage.cpp | 4 ++- 3 files changed, 57 insertions(+), 4 deletions(-) create mode 100644 include/dusk/string.hpp diff --git a/include/dusk/string.hpp b/include/dusk/string.hpp new file mode 100644 index 0000000000..f1a365ebbb --- /dev/null +++ b/include/dusk/string.hpp @@ -0,0 +1,52 @@ +#ifndef DUSK_STRING_HPP +#define DUSK_STRING_HPP + +#include "global.h" +#include +#include + +namespace dusk { + +/** + * Copy a string to a fixed-size array. + * Truncates if the destination is not large enough, always inserts a null terminator (padding the remainder of the buffer with zeroes.) + */ +template +void SafeStringCopyTruncate(char (&buffer)[BufSize], const char* src) { + static_assert(BufSize > 0, "Target buffer cannot be size zero"); + + // Can't just use strncpy as I can't figure out how to selectively mute the warnings on MSVC. + // And can't use strncpy_s on MSVC as it doesn't fill the entire buffer. + constexpr size_t copyBufSize = BufSize - 1; + const auto srcLen = strlen(src); + const auto copyLen = srcLen > copyBufSize ? copyBufSize : srcLen; + memcpy(buffer, src, copyLen); + memset(buffer + copyLen, 0, BufSize - copyLen); +} + +/** + * Copy a string to a fixed-size array. + * Aborts if the destination is not large enough, always inserts a null terminator (padding the remainder of the buffer with zeroes.) + */ +template +void SafeStringCopy(char (&buffer)[BufSize], const char* src) { + static_assert(BufSize > 0, "Target buffer cannot be size zero"); + if (buffer == src) { + CRASH("Cannot copy string to same buffer"); + } + + // Can't just use strncpy as I can't figure out how to selectively mute the warnings on MSVC. + // And can't use strncpy_s on MSVC as it doesn't fill the entire buffer. + constexpr size_t copyBufSize = BufSize - 1; + const auto srcLen = strlen(src); + if (srcLen > copyBufSize) { + CRASH("Destination buffer too small!"); + } + + memcpy(buffer, src, srcLen); + memset(buffer + srcLen, 0, BufSize - srcLen); +} + +} + +#endif // DUSK_STRING_HPP diff --git a/libs/JSystem/src/JKernel/JKRHeap.cpp b/libs/JSystem/src/JKernel/JKRHeap.cpp index c2b32999fd..ce1899a4ba 100644 --- a/libs/JSystem/src/JKernel/JKRHeap.cpp +++ b/libs/JSystem/src/JKernel/JKRHeap.cpp @@ -15,6 +15,7 @@ #include "JSystem/JUtility/JUTAssert.h" #include "JSystem/JUtility/JUTException.h" +#include "dusk/string.hpp" #ifdef __MWERKS__ #include #else @@ -701,9 +702,7 @@ JKRHeap* JKRHeap::getCurrentHeap() { } void JKRHeap::setName(const char* name) { - size_t len = strlen(name); - strncpy(mName, name, sizeof(mName) - 1); - mName[sizeof(mName) - 1] = '\0'; + dusk::SafeStringCopyTruncate(mName, name); } void JKRHeap::setNamef(const char* fmt, ...) { diff --git a/src/d/d_stage.cpp b/src/d/d_stage.cpp index 9ada8b57b6..4d8c531b3c 100644 --- a/src/d/d_stage.cpp +++ b/src/d/d_stage.cpp @@ -21,6 +21,8 @@ #include "m_Do/m_Do_Reset.h" #include #include + +#include "dusk/string.hpp" #if TARGET_PC #include #include @@ -154,7 +156,7 @@ void dStage_startStage_c::set(const char* i_Name, s8 i_RoomNo, s16 i_Point, s8 i #if TARGET_PC // UB fix. if (mName != i_Name) { - strncpy_s(mName, sizeof(mName), i_Name, sizeof(mName)); + dusk::SafeStringCopy(mName, i_Name); } #else strcpy(mName, i_Name); From 96a61ed0b606c695e9b0c1769a33d3a5cd2144ad Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Fri, 27 Mar 2026 17:07:54 +0100 Subject: [PATCH 092/350] Fix CRASH() macro outside MSVC Just add a CRASHF() overload that... isn't necessary anymore. Oh well. --- include/global.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/global.h b/include/global.h index df3723bd57..56662eb5be 100644 --- a/include/global.h +++ b/include/global.h @@ -214,6 +214,7 @@ using std::isnan; #define IS_REF_NONNULL(r) (1) #endif -#define CRASH(msg, ...) OSPanic(__FILE__, __LINE__, msg, __VA_ARGS__) +#define CRASH(msg) OSPanic(__FILE__, __LINE__, "%s", msg) +#define CRASHF(msg, ...) OSPanic(__FILE__, __LINE__, msg, __VA_ARGS__) #endif From 0e1718e80d745b448e33e8914094347f53e0cd1b Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Fri, 27 Mar 2026 17:16:41 +0100 Subject: [PATCH 093/350] Do use strncpy in SafeStringCopy I figured out how to mute the warnings. --- include/dusk/string.hpp | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/include/dusk/string.hpp b/include/dusk/string.hpp index f1a365ebbb..598ae83730 100644 --- a/include/dusk/string.hpp +++ b/include/dusk/string.hpp @@ -7,6 +7,17 @@ namespace dusk { +inline void strncpyProxy(char* dst, const char* src, size_t count) { +#if _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4996) +#endif + strncpy(dst, src, count); +#if _MSC_VER +#pragma warning(pop) +#endif +} + /** * Copy a string to a fixed-size array. * Truncates if the destination is not large enough, always inserts a null terminator (padding the remainder of the buffer with zeroes.) @@ -15,13 +26,8 @@ template void SafeStringCopyTruncate(char (&buffer)[BufSize], const char* src) { static_assert(BufSize > 0, "Target buffer cannot be size zero"); - // Can't just use strncpy as I can't figure out how to selectively mute the warnings on MSVC. - // And can't use strncpy_s on MSVC as it doesn't fill the entire buffer. - constexpr size_t copyBufSize = BufSize - 1; - const auto srcLen = strlen(src); - const auto copyLen = srcLen > copyBufSize ? copyBufSize : srcLen; - memcpy(buffer, src, copyLen); - memset(buffer + copyLen, 0, BufSize - copyLen); + strncpyProxy(buffer, src, BufSize); + buffer[BufSize - 1] = 0; } /** @@ -35,16 +41,12 @@ void SafeStringCopy(char (&buffer)[BufSize], const char* src) { CRASH("Cannot copy string to same buffer"); } - // Can't just use strncpy as I can't figure out how to selectively mute the warnings on MSVC. - // And can't use strncpy_s on MSVC as it doesn't fill the entire buffer. - constexpr size_t copyBufSize = BufSize - 1; - const auto srcLen = strlen(src); - if (srcLen > copyBufSize) { + if (strlen(src) > BufSize - 1) { CRASH("Destination buffer too small!"); } - memcpy(buffer, src, srcLen); - memset(buffer + srcLen, 0, BufSize - srcLen); + strncpyProxy(buffer, src, BufSize); + buffer[BufSize - 1] = 0; } } From 4b30cd7ddb5bfd3c6c4a3a5e69a4354646a9a209 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Fri, 27 Mar 2026 17:17:45 +0100 Subject: [PATCH 094/350] Re-add src == dst check in SafeStringCopyTruncate Accidentally dropped this while iterating on the exact implementation. --- include/dusk/string.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/dusk/string.hpp b/include/dusk/string.hpp index 598ae83730..7de1b1ad75 100644 --- a/include/dusk/string.hpp +++ b/include/dusk/string.hpp @@ -26,6 +26,10 @@ template void SafeStringCopyTruncate(char (&buffer)[BufSize], const char* src) { static_assert(BufSize > 0, "Target buffer cannot be size zero"); + if (buffer == src) { + CRASH("Cannot copy string to same buffer"); + } + strncpyProxy(buffer, src, BufSize); buffer[BufSize - 1] = 0; } From 3f443d66538842dca61b238e9fa8bed370b81ddb Mon Sep 17 00:00:00 2001 From: Irastris Date: Fri, 27 Mar 2026 23:28:24 -0400 Subject: [PATCH 095/350] Fix MSVC RelWithDebInfo/Release compilation after upstream merge --- libs/JSystem/src/JAHostIO/JAHioNode.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libs/JSystem/src/JAHostIO/JAHioNode.cpp b/libs/JSystem/src/JAHostIO/JAHioNode.cpp index 66c9431a41..9a30991e4f 100644 --- a/libs/JSystem/src/JAHostIO/JAHioNode.cpp +++ b/libs/JSystem/src/JAHostIO/JAHioNode.cpp @@ -101,7 +101,9 @@ JAHioNode* JAHioNode::getParent() { void JAHioNode::listenPropertyEvent(const JORPropertyEvent* event) { propertyEvent(JAH_P_EVENT0, (uintptr_t)event->id); +#if DEBUG JORReflexible::listenPropertyEvent(event); +#endif propertyEvent(JAH_P_EVENT1, (uintptr_t)event->id); } From 6491062dabcf15b911d84f6ddf058445685599e7 Mon Sep 17 00:00:00 2001 From: Max Roncace Date: Fri, 27 Mar 2026 23:50:31 -0400 Subject: [PATCH 096/350] Fix Clang compiler error due to CRASH macro --- include/global.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/include/global.h b/include/global.h index 3720586ae3..bb30611486 100644 --- a/include/global.h +++ b/include/global.h @@ -218,7 +218,6 @@ using std::isnan; #define IS_REF_NONNULL(r) (1) #endif -#define CRASH(msg) OSPanic(__FILE__, __LINE__, "%s", msg) -#define CRASHF(msg, ...) OSPanic(__FILE__, __LINE__, msg, __VA_ARGS__) +#define CRASH(msg, ...) OSPanic(__FILE__, __LINE__, "%s", msg, ##__VA_ARGS__) #endif From f53917016d38733be7d1e33e2c4fd69600c03b64 Mon Sep 17 00:00:00 2001 From: CraftyBoss Date: Sat, 28 Mar 2026 00:56:10 -0400 Subject: [PATCH 097/350] Fix uninitialized field causing occasional crash in d_camera::lockonCamera, fix wolf howling scissors --- src/d/d_camera.cpp | 11 ++++++++++- src/d/d_msg_scrn_howl.cpp | 29 ++++++++++++++++++++++++++++- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/d/d_camera.cpp b/src/d/d_camera.cpp index ad12a4b773..ce2c7c169a 100644 --- a/src/d/d_camera.cpp +++ b/src/d/d_camera.cpp @@ -4846,6 +4846,15 @@ bool dCamera_c::lockonCamera(s32 param_0) { } } +#if TARGET_PC + f32 sp194 = 1.0f; + if (mCurCamStyleTimer < lockon_change_timer && !lockon->field_0x2a) { + sp194 = dCamMath::rationalBezierRatio((f32)mCurCamStyleTimer / lockon_change_timer, 0.5f); + ang2 *= sp194; + } else if (mCurCamStyleTimer >= lockon_change_timer) { + lockon->field_0x2a = true; + } +#else f32 sp194; if (mCurCamStyleTimer < lockon_change_timer && !lockon->field_0x2a) { sp194 = dCamMath::rationalBezierRatio((f32)mCurCamStyleTimer / lockon_change_timer, 0.5f); @@ -4854,7 +4863,7 @@ bool dCamera_c::lockonCamera(s32 param_0) { lockon->field_0x2a = true; sp194 = 1.0f; } - +#endif cSAngle ang3(mViewCache.mDirection.U().Inv() - ang1); if (mCurCamStyleTimer != 0 && mCurCamStyleTimer < lockon_change_timer) { diff --git a/src/d/d_msg_scrn_howl.cpp b/src/d/d_msg_scrn_howl.cpp index 07018b376d..965bd25075 100644 --- a/src/d/d_msg_scrn_howl.cpp +++ b/src/d/d_msg_scrn_howl.cpp @@ -475,6 +475,10 @@ void dMsgScrnHowl_c::drawWave() { s32 local_94 = 0; Vec fVar12 = field_0x128; Vec this_02 = field_0x140; +#if TARGET_PC // TODO: make this actually use the scissor + f32 fVar1 = 1; + f32 fVar2 = 1; +#else f32 fVar1 = mDoGph_gInf_c::getWidthF() / FB_WIDTH; f32 fVar2 = mDoGph_gInf_c::getHeightF() / FB_HEIGHT; grafContext->scissor( @@ -484,6 +488,8 @@ void dMsgScrnHowl_c::drawWave() { 32.0f + ((this_02.y - fVar12.y) + 2.0f) ); grafContext->setScissor(); +#endif + bool bVar5 = true; if (field_0x2798 == 0) { if (mPlotTime != field_0x212c) { @@ -578,10 +584,17 @@ void dMsgScrnHowl_c::drawGuide() { J2DGrafContext* grafContext = dComIfGp_getCurrentGrafPort(); Vec local_b0 = field_0x128; Vec local_bc = field_0x140; +#if TARGET_PC + grafContext->scissor( + (local_b0.x - mDoGph_gInf_c::getMinXF()) / (mDoGph_gInf_c::getWidthF() / mDoGph_gInf_c::getWidth()), + field_0x2118, (local_bc.x - local_b0.x) / (mDoGph_gInf_c::getWidthF() / mDoGph_gInf_c::getWidth()), + field_0x2120); +#else grafContext->scissor( (local_b0.x - mDoGph_gInf_c::getMinXF()) / (mDoGph_gInf_c::getWidthF() / FB_WIDTH), field_0x2118, (local_bc.x - local_b0.x) / (mDoGph_gInf_c::getWidthF() / FB_WIDTH), field_0x2120); +#endif grafContext->setScissor(); f32 local_cc = mpLineH[0]->getGlobalPosX(); s16 sVar12 = 0; @@ -709,11 +722,19 @@ void dMsgScrnHowl_c::drawGuide2() { } Vec local_58 = field_0x128; Vec local_64 = field_0x140; - f32 local_70 = mDoGph_gInf_c::getHeightF() / FB_HEIGHT; +#if TARGET_PC + f32 local_70 = mDoGph_gInf_c::getHeightF() / mDoGph_gInf_c::getHeight(); + grafContext->scissor( + (local_58.x - mDoGph_gInf_c::getMinXF()) / (mDoGph_gInf_c::getWidthF() / mDoGph_gInf_c::getWidth()), + field_0x2118, (local_64.x - local_58.x) / (mDoGph_gInf_c::getWidthF() / mDoGph_gInf_c::getWidth()), + field_0x2120); +#else + f32 local_70 = mDoGph_gInf_c::getHeightF() / mDoGph_gInf_c::getHeight(); grafContext->scissor( (local_58.x - mDoGph_gInf_c::getMinXF()) / (mDoGph_gInf_c::getWidthF() / FB_WIDTH), field_0x2118, (local_64.x - local_58.x) / (mDoGph_gInf_c::getWidthF() / FB_WIDTH), field_0x2120); +#endif grafContext->setScissor(); f32 local_74 = mpLineH[0]->getGlobalPosX(); s16 local_134 = 0; @@ -815,9 +836,15 @@ void dMsgScrnHowl_c::drawEffect() { Vec vec1 = field_0x128; Vec vec2 = field_0x140; mDoGph_gInf_c::getHeightF(); +#if TARGET_PC + grafContext->scissor( + (vec1.x - mDoGph_gInf_c::getMinXF()) / (mDoGph_gInf_c::getWidthF() / mDoGph_gInf_c::getWidth()), field_0x2118, + 12.0f + ((vec2.x - vec1.x) / (mDoGph_gInf_c::getWidthF() / mDoGph_gInf_c::getWidth())), field_0x2120); +#else grafContext->scissor( (vec1.x - mDoGph_gInf_c::getMinXF()) / (mDoGph_gInf_c::getWidthF() / FB_WIDTH), field_0x2118, 12.0f + ((vec2.x - vec1.x) / (mDoGph_gInf_c::getWidthF() / FB_WIDTH)), field_0x2120); +#endif grafContext->setScissor(); u8 timer = daAlink_getAlinkActorClass()->getWolfHowlMgrP()->getReleaseTimer(); u8 screenAlpha = mpScreen->search(MULTI_CHAR('line00'))->getAlpha(); From 472551d24adebffa389158d66b8b82f274c33e88 Mon Sep 17 00:00:00 2001 From: CraftyBoss Date: Sat, 28 Mar 2026 10:47:55 -0400 Subject: [PATCH 098/350] increase d_a_coach_2D heap size --- src/d/actor/d_a_coach_2D.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/d/actor/d_a_coach_2D.cpp b/src/d/actor/d_a_coach_2D.cpp index 7c53aa1f10..eab333cb8b 100644 --- a/src/d/actor/d_a_coach_2D.cpp +++ b/src/d/actor/d_a_coach_2D.cpp @@ -14,6 +14,8 @@ #include "JSystem/J2DGraph/J2DAnmLoader.h" #include +#include "dusk/memory.h" + class daCoach2D_HIO_c : public mDoHIO_entry_c { public: struct Param { @@ -153,7 +155,7 @@ int daCoach2D_c::createHeap() { int daCoach2D_c::create() { int phase_state = dComIfG_resLoad(this, l_arcName); if (phase_state == cPhs_COMPLEATE_e) { - if (!fopAcM_entrySolidHeap(this, daCoach2D_createHeap, 0x5050)) { + if (!fopAcM_entrySolidHeap(this, daCoach2D_createHeap, HEAP_SIZE(0x5050, 0x6000))) { return cPhs_ERROR_e; } From 76131c071f6b18072c99f4bc31978a589199e8a1 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sat, 28 Mar 2026 18:29:58 +0100 Subject: [PATCH 099/350] 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 9b3f72582a91dfd1db9d86bccd0fde09e99cfc21 Mon Sep 17 00:00:00 2001 From: Irastris Date: Sat, 28 Mar 2026 18:58:18 -0400 Subject: [PATCH 100/350] Revert d_s_logo speedup, skip logos entirely instead --- src/d/d_s_logo.cpp | 47 +++++++++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/src/d/d_s_logo.cpp b/src/d/d_s_logo.cpp index a878de8106..6aa3778dbf 100644 --- a/src/d/d_s_logo.cpp +++ b/src/d/d_s_logo.cpp @@ -412,11 +412,11 @@ void dScnLogo_c::progOutDraw() { } #endif - mDoGph_gInf_c::startFadeIn(1); + mDoGph_gInf_c::startFadeIn(30); } else { mExecCommand = EXEC_PROG_SET; mTimer = 150; - mDoGph_gInf_c::startFadeIn(1); + mDoGph_gInf_c::startFadeIn(30); } } } @@ -427,7 +427,7 @@ void dScnLogo_c::progSetDraw() { if (mTimer == 0) { mExecCommand = EXEC_PROG_SET2; mTimer = 30; - mDoGph_gInf_c::startFadeOut(1); + mDoGph_gInf_c::startFadeOut(30); } } @@ -463,15 +463,15 @@ void dScnLogo_c::progChangeDraw() { mTimer = 90; #else if (mDoRst::getWarningDispFlag() != 0) { - mTimer = 1; + mTimer = 90; mExecCommand = EXEC_NINTENDO_IN; } else { - mTimer = 1; + mTimer = 120; mExecCommand = EXEC_WARNING_IN; } #endif - mDoGph_gInf_c::startFadeIn(1); + mDoGph_gInf_c::startFadeIn(30); } } @@ -516,8 +516,8 @@ void dScnLogo_c::warningDispDraw() { #endif { mExecCommand = EXEC_WARNING_OUT; - mTimer = 1; - mDoGph_gInf_c::startFadeOut(1); + mTimer = 30; + mDoGph_gInf_c::startFadeOut(30); mDoRst::setWarningDispFlag(1); } } @@ -526,9 +526,9 @@ void dScnLogo_c::warningOutDraw() { dComIfGd_set2DOpa(mWarning); if (mTimer == 0) { - mTimer = 1; + mTimer = 90; mExecCommand = EXEC_NINTENDO_IN; - mDoGph_gInf_c::startFadeIn(1); + mDoGph_gInf_c::startFadeIn(30); } } @@ -537,8 +537,8 @@ void dScnLogo_c::nintendoInDraw() { if (mTimer == 0) { mExecCommand = EXEC_NINTENDO_OUT; - mTimer = 1; - mDoGph_gInf_c::startFadeOut(1); + mTimer = 30; + mDoGph_gInf_c::startFadeOut(30); } } @@ -547,8 +547,8 @@ void dScnLogo_c::nintendoOutDraw() { if (mTimer == 0) { mExecCommand = EXEC_DOLBY_IN; - mTimer = 1; - mDoGph_gInf_c::startFadeIn(1); + mTimer = 30; + mDoGph_gInf_c::startFadeIn(30); } } @@ -557,8 +557,8 @@ void dScnLogo_c::dolbyInDraw() { if (mTimer == 0) { mExecCommand = EXEC_DOLBY_OUT; - mTimer = 1; - mDoGph_gInf_c::startFadeOut(1); + mTimer = 30; + mDoGph_gInf_c::startFadeOut(30); } } @@ -567,8 +567,8 @@ void dScnLogo_c::dolbyOutDraw() { if (mTimer == 0) { mExecCommand = EXEC_DOLBY_OUT2; - mTimer = 1; - mDoGph_gInf_c::startFadeIn(1); + mTimer = 30; + mDoGph_gInf_c::startFadeIn(30); } } @@ -1098,7 +1098,7 @@ int dScnLogo_c::create() { mTimer = 90; #endif - mDoGph_gInf_c::startFadeIn(1); + mDoGph_gInf_c::startFadeIn(30); #if !(PLATFORM_WII || PLATFORM_SHIELD) checkProgSelect(); @@ -1107,13 +1107,18 @@ int dScnLogo_c::create() { mTimer = 1; field_0x218 = getProgressiveMode(); } else { + #if TARGET_PC + mTimer = 0; // Possibly unnecessary but just in case + mExecCommand = EXEC_DVD_WAIT; + #else if (mDoRst::getWarningDispFlag()) { - mTimer = 1; // 90; + mTimer = 90; mExecCommand = EXEC_NINTENDO_IN; } else { - mTimer = 1; + mTimer = 120; mExecCommand = EXEC_WARNING_IN; } + #endif mDoRst::setProgSeqFlag(1); } From b0cd4c6f3a1ce63bd65a8dc6e1c86b336adb7de9 Mon Sep 17 00:00:00 2001 From: Irastris Date: Sat, 28 Mar 2026 19:18:28 -0400 Subject: [PATCH 101/350] Update Aurora submodule --- extern/aurora | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/aurora b/extern/aurora index d76b70fc72..236a7a5e9a 160000 --- a/extern/aurora +++ b/extern/aurora @@ -1 +1 @@ -Subproject commit d76b70fc72c6a5904a9e78c0526353f8b8e93c61 +Subproject commit 236a7a5e9a65b3d74371c2eae8fd161f3c5eacb1 From c28150be402a732633a9ff002c5f7f0efcd9a7b8 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 29 Mar 2026 00:19:56 +0100 Subject: [PATCH 102/350] 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 933e7f14c89ddc288058811e8a2e887a02bbf106 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 29 Mar 2026 01:54:45 +0100 Subject: [PATCH 103/350] 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 7d73e93198b317e82ab784ce10c3dfe9e9b50c17 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 29 Mar 2026 01:54:49 +0100 Subject: [PATCH 104/350] 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 654a7a6f4c4b7d70552780e7e9bedee081a1d6e3 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 29 Mar 2026 01:56:41 +0100 Subject: [PATCH 105/350] 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 58ca78ea80e449784cbea6500fffee270d354b0f Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 29 Mar 2026 03:07:03 +0200 Subject: [PATCH 106/350] 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 0ecd16133d14f9a3ebd106ab660d3efc23de48e9 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 29 Mar 2026 03:29:30 +0200 Subject: [PATCH 107/350] 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 4550595911480dde7968784760893b46d67f1ab6 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 29 Mar 2026 03:29:51 +0200 Subject: [PATCH 108/350] 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 7ed3419b5d1aff728dc89abb344841d2512c3c31 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 29 Mar 2026 03:31:36 +0200 Subject: [PATCH 109/350] 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 28338ff35417b756a9b15255d30cc454a8d33c34 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 29 Mar 2026 03:50:37 +0200 Subject: [PATCH 110/350] 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 5dacb27c4cb3cb5cceaefb27cf2e947b3a553bb9 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 29 Mar 2026 04:34:45 +0200 Subject: [PATCH 111/350] Don't include weak_bss_1109_to_1009.h on PC Not relevant to us. --- include/d/dolzel_base.pch | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/d/dolzel_base.pch b/include/d/dolzel_base.pch index ac45474890..e603a50e14 100644 --- a/include/d/dolzel_base.pch +++ b/include/d/dolzel_base.pch @@ -2,7 +2,9 @@ #define DOLZEL_BASE_PCH // Fixes weak .bss +#if !TARGET_PC #include "weak_bss_1109_to_1009.h" // IWYU pragma: export +#endif #include // IWYU pragma: export #include // IWYU pragma: export From 576937f8b04b6dc2706ed0ebb2f5fd6a2731e157 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 29 Mar 2026 04:35:44 +0200 Subject: [PATCH 112/350] Don't include logging infrastructure in m_Do_graphic.h This ends up wasting a ton of compile time because it pulls in all of Aurora's log infrastructure and fmt and all the noise into a thousand game files --- include/m_Do/m_Do_graphic.h | 5 +---- src/d/actor/d_a_obj_brg.cpp | 2 ++ src/d/d_resorce.cpp | 2 ++ src/d/d_stage.cpp | 1 + src/dusk/imgui/ImGuiMapLoader.cpp | 1 + 5 files changed, 7 insertions(+), 4 deletions(-) diff --git a/include/m_Do/m_Do_graphic.h b/include/m_Do/m_Do_graphic.h index c30c28a517..b4f947546d 100644 --- a/include/m_Do/m_Do_graphic.h +++ b/include/m_Do/m_Do_graphic.h @@ -4,7 +4,6 @@ #include "JSystem/JFramework/JFWDisplay.h" #include "m_Do/m_Do_mtx.h" #include "global.h" -#include "dusk/logging.h" #if TARGET_PC #include #endif @@ -100,9 +99,7 @@ public: } static int startFadeOut(int param_0) { return JFWDisplay::getManager()->startFadeOut(param_0); } - static int startFadeIn(int param_0) { - DuskLog.debug("mDoGph_gInf_c::startFadeIn START"); - return JFWDisplay::getManager()->startFadeIn(param_0); } + static int startFadeIn(int param_0) { return JFWDisplay::getManager()->startFadeIn(param_0); } static void setFadeColor(JUtility::TColor& color) { mFader->setColor(color); } static void setClearColor(JUtility::TColor color) { JFWDisplay::getManager()->setClearColor(color); } static void setBackColor(GXColor& color) { mBackColor = color; } diff --git a/src/d/actor/d_a_obj_brg.cpp b/src/d/actor/d_a_obj_brg.cpp index b2acca8f98..cb666f3205 100644 --- a/src/d/actor/d_a_obj_brg.cpp +++ b/src/d/actor/d_a_obj_brg.cpp @@ -16,6 +16,8 @@ #include "f_op/f_op_camera_mng.h" #include +#include "dusk/logging.h" + static void ride_call_back(dBgW* i_bgw, fopAc_ac_c* i_bgActor, fopAc_ac_c* i_rideActor) { obj_brg_class* a_this = (obj_brg_class*)i_bgActor; diff --git a/src/d/d_resorce.cpp b/src/d/d_resorce.cpp index 1dce982025..ab21e140c0 100644 --- a/src/d/d_resorce.cpp +++ b/src/d/d_resorce.cpp @@ -20,6 +20,8 @@ #include #include +#include "dusk/logging.h" + #ifndef __MWERKS__ #include "dusk/extras.h" #endif diff --git a/src/d/d_stage.cpp b/src/d/d_stage.cpp index 53f610267f..df706ecb5f 100644 --- a/src/d/d_stage.cpp +++ b/src/d/d_stage.cpp @@ -22,6 +22,7 @@ #include #include +#include "dusk/logging.h" #include "dusk/string.hpp" #if TARGET_PC #include diff --git a/src/dusk/imgui/ImGuiMapLoader.cpp b/src/dusk/imgui/ImGuiMapLoader.cpp index 89ab6aa120..8d11c0673b 100644 --- a/src/dusk/imgui/ImGuiMapLoader.cpp +++ b/src/dusk/imgui/ImGuiMapLoader.cpp @@ -5,6 +5,7 @@ #include "ImGuiConsole.hpp" #include "ImGuiMenuTools.hpp" #include "dusk/map_loader_definitions.h" +#include "fmt/format.h" namespace dusk { void ImGuiMenuTools::ShowMapLoader() { From d2c5614a4765976e2fb483a63f762b97a8700847 Mon Sep 17 00:00:00 2001 From: Max Roncace Date: Sun, 29 Mar 2026 01:12:40 -0400 Subject: [PATCH 113/350] Use separate heap for DVD decompression This fixes crashes when first entering North Faaron cave and Renado's Sanctuary due to system heap exhaustion resulting from multiple threads allocating on the heap at once. There's some sort of weird contention going on - for some reason the game gobbles up seemingly any amount of memory you throw at it when the two threads are competing for heap space. Using a separate heap in the async thread sidesteps the issue. --- libs/JSystem/include/JSystem/JKernel/JKRDvdRipper.h | 9 +++++++++ libs/JSystem/src/JKernel/JKRDvdRipper.cpp | 12 ++++++++++++ src/m_Do/m_Do_machine.cpp | 9 +++++++++ 3 files changed, 30 insertions(+) diff --git a/libs/JSystem/include/JSystem/JKernel/JKRDvdRipper.h b/libs/JSystem/include/JSystem/JKernel/JKRDvdRipper.h index 82b9c21a2e..0720605efb 100644 --- a/libs/JSystem/include/JSystem/JKernel/JKRDvdRipper.h +++ b/libs/JSystem/include/JSystem/JKernel/JKRDvdRipper.h @@ -36,6 +36,9 @@ public: static JSUList sDvdAsyncList; static u32 sSZSBufferSize; static bool errorRetry; +#if TARGET_PC + static JKRHeap* sHeap; +#endif enum EAllocDirection { UNKNOWN_EALLOC_DIRECTION = 0, @@ -54,6 +57,12 @@ public: static bool isErrorRetry(void) { return errorRetry; } inline static u32 getSZSBufferSize() { return sSZSBufferSize; } + +#if TARGET_PC + static inline JKRHeap* getHeap() { return sHeap; } + static inline void setHeap(JKRHeap* i_heap) { sHeap = i_heap; } + +#endif }; // void JKRDecompressFromDVD(JKRDvdFile*, void*, u32, u32, u32, u32, u32*); diff --git a/libs/JSystem/src/JKernel/JKRDvdRipper.cpp b/libs/JSystem/src/JKernel/JKRDvdRipper.cpp index ae644ee9e1..ae65f42ba3 100644 --- a/libs/JSystem/src/JKernel/JKRDvdRipper.cpp +++ b/libs/JSystem/src/JKernel/JKRDvdRipper.cpp @@ -13,6 +13,8 @@ #include #include +#include "JSystem/JKernel/JKRExpHeap.h" + static int JKRDecompressFromDVD(JKRDvdFile*, void*, u32, u32, u32, u32, u32*); static int decompSZS_subroutine(u8*, u8*); static u8* firstSrcData(); @@ -240,6 +242,8 @@ static OSMutex decompMutex; u32 JKRDvdRipper::sSZSBufferSize = 0x00000400; +JKRHeap* JKRDvdRipper::sHeap = NULL; + static u8* szpBuf; static u8* szpEnd; @@ -282,12 +286,20 @@ static int JKRDecompressFromDVD(JKRDvdFile* dvdFile, void* dst, u32 fileSize, u3 OSLockMutex(&decompMutex); u32 result = 0; u32 szsBufferSize = JKRDvdRipper::getSZSBufferSize(); +#if TARGET_PC + szpBuf = (u8 *)JKRAllocFromHeap(JKRDvdRipper::getHeap(), szsBufferSize, -0x20); +#else szpBuf = (u8 *)JKRAllocFromSysHeap(szsBufferSize, -0x20); +#endif JUT_ASSERT(909, szpBuf != NULL); szpEnd = szpBuf + szsBufferSize; if (inFileOffset != 0) { +#if TARGET_PC + refBuf = (u8 *)JKRAllocFromHeap(JKRDvdRipper::getHeap(), 0x1120, -4); +#else refBuf = (u8 *)JKRAllocFromSysHeap(0x1120, -4); +#endif JUT_ASSERT(918, refBuf != NULL); refEnd = refBuf + 0x1120; refCurrent = refBuf; diff --git a/src/m_Do/m_Do_machine.cpp b/src/m_Do/m_Do_machine.cpp index f50c8a6fdb..d5258b6f98 100644 --- a/src/m_Do/m_Do_machine.cpp +++ b/src/m_Do/m_Do_machine.cpp @@ -29,6 +29,10 @@ #include "DynamicLink.h" #include "os_report.h" +#if TARGET_PC +#include +#endif + #if !PLATFORM_GCN #include #include @@ -991,6 +995,11 @@ int mDoMch_Create() { GXSetVerifyCallback((GXVerifyCallback)&myGXVerifyCallback); #endif JKRDvdRipper::setSZSBufferSize(0x4000); +#if TARGET_PC + JKRHeap* dvdHeap = JKRCreateExpHeap(0x10000, NULL, false); + assert(dvdHeap != NULL); + JKRDvdRipper::setHeap(dvdHeap); +#endif JKRDvdAramRipper::setSZSBufferSize(0x4000); JKRAram::setSZSBufferSize(0x2000); mDoDvdThd::create(OSGetThreadPriority(OSGetCurrentThread()) - 2); From b5624901464cb1282192f22733ca092416b58c25 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 29 Mar 2026 13:24:59 +0200 Subject: [PATCH 114/350] Use PCH to massively speed up compile time A change to a common header goes from 90s to 20s on my system. Crazy. --- CMakeLists.txt | 3 ++- include/d/dusk_pch.hpp | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 include/d/dusk_pch.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 583b5efa94..66c98017e8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.13) +cmake_minimum_required(VERSION 3.16) if (APPLE) project(dusk LANGUAGES C CXX OBJC OBJCXX) else () @@ -110,6 +110,7 @@ add_library(game SHARED ${DOLZEL_FILES} ${Z2AUDIOLIB_FILES} ${SSYSTEM_FILES} ${J target_link_libraries(game PRIVATE game_debug cxxopts::cxxopts) 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/d/dusk_pch.hpp>") add_executable(dusk src/dusk/main.cpp) target_compile_definitions(dusk PRIVATE TARGET_PC AVOID_UB=1 VERSION=0) target_include_directories(dusk PRIVATE include) diff --git a/include/d/dusk_pch.hpp b/include/d/dusk_pch.hpp new file mode 100644 index 0000000000..9215459504 --- /dev/null +++ b/include/d/dusk_pch.hpp @@ -0,0 +1,7 @@ +#pragma once + +#define PROCS_DUMP_NAMES 1 + +#include "dolzel_rel.pch" +#include "dolzel.pch" +#include "dolzel_base.pch" From 0c5bae421427d8440ee4af15ef588a746304033c Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 29 Mar 2026 13:35:26 +0200 Subject: [PATCH 115/350] Move --- CMakeLists.txt | 2 +- include/d/dusk_pch.hpp | 7 ------- include/dusk_pch.hpp | 7 +++++++ 3 files changed, 8 insertions(+), 8 deletions(-) delete mode 100644 include/d/dusk_pch.hpp create mode 100644 include/dusk_pch.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 66c98017e8..1637eb202d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -110,7 +110,7 @@ add_library(game SHARED ${DOLZEL_FILES} ${Z2AUDIOLIB_FILES} ${SSYSTEM_FILES} ${J target_link_libraries(game PRIVATE game_debug cxxopts::cxxopts) 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/d/dusk_pch.hpp>") +target_precompile_headers(game PRIVATE "$<$:${CMAKE_SOURCE_DIR}/include/dusk_pch.hpp>") add_executable(dusk src/dusk/main.cpp) target_compile_definitions(dusk PRIVATE TARGET_PC AVOID_UB=1 VERSION=0) target_include_directories(dusk PRIVATE include) diff --git a/include/d/dusk_pch.hpp b/include/d/dusk_pch.hpp deleted file mode 100644 index 9215459504..0000000000 --- a/include/d/dusk_pch.hpp +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -#define PROCS_DUMP_NAMES 1 - -#include "dolzel_rel.pch" -#include "dolzel.pch" -#include "dolzel_base.pch" diff --git a/include/dusk_pch.hpp b/include/dusk_pch.hpp new file mode 100644 index 0000000000..adac2d33d2 --- /dev/null +++ b/include/dusk_pch.hpp @@ -0,0 +1,7 @@ +#pragma once + +#define PROCS_DUMP_NAMES 1 + +#include "d/dolzel.pch" +#include "d/dolzel_base.pch" +#include "d/dolzel_rel.pch" From bd08d05f3a13eaf9aa880260b26f133b023b5ffd Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 29 Mar 2026 18:21:27 +0200 Subject: [PATCH 116/350] Fix stub log memory leaking if imgui rendering is disabled --- src/dusk/imgui/ImGuiConsole.cpp | 3 +++ src/dusk/imgui/ImGuiMenuTools.hpp | 1 + src/dusk/imgui/ImGuiStubLog.cpp | 15 +++++++++++---- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/dusk/imgui/ImGuiConsole.cpp b/src/dusk/imgui/ImGuiConsole.cpp index cc8b758cb7..60ba229ead 100644 --- a/src/dusk/imgui/ImGuiConsole.cpp +++ b/src/dusk/imgui/ImGuiConsole.cpp @@ -174,6 +174,7 @@ namespace dusk { void ImGuiConsole::draw() { if (CheckMenuViewToggle(ImGuiKey_F1, m_isHidden)) { + m_menuTools.afterDraw(); return; } @@ -187,6 +188,8 @@ namespace dusk { ImGui::EndMainMenuBar(); } + + m_menuTools.afterDraw(); } bool ImGuiConsole::CheckMenuViewToggle(ImGuiKey key, bool& active) { diff --git a/src/dusk/imgui/ImGuiMenuTools.hpp b/src/dusk/imgui/ImGuiMenuTools.hpp index 35703d8aa2..66c820dd78 100644 --- a/src/dusk/imgui/ImGuiMenuTools.hpp +++ b/src/dusk/imgui/ImGuiMenuTools.hpp @@ -23,6 +23,7 @@ namespace dusk { ImGuiMenuTools(); void draw(); + void afterDraw(); void ShowDebugOverlay(); void ShowCameraOverlay(); diff --git a/src/dusk/imgui/ImGuiStubLog.cpp b/src/dusk/imgui/ImGuiStubLog.cpp index 5f77dccd38..d8638da297 100644 --- a/src/dusk/imgui/ImGuiStubLog.cpp +++ b/src/dusk/imgui/ImGuiStubLog.cpp @@ -36,18 +36,20 @@ namespace dusk { std::lock_guard lock(StubLogMutex); + if (StubLogBuffer.size() > 1024 * 1024) { + DuskLog.warn("Stub log FULL. Dropping logs!"); + return; + } + LineOffsets.push_back(StubLogBuffer.size()); const auto levelName = LogLevelName(level); StubLogBuffer.appendf("[%s | %s] %s\n", levelName, module, message); } - static void ClearPastFrame(); - void ImGuiMenuTools::ShowStubLog() { std::lock_guard lock(StubLogMutex); if (!ImGuiConsole::CheckMenuViewToggle(ImGuiKey_F5, m_showStubLog)) { - ClearPastFrame(); return; } @@ -78,7 +80,6 @@ namespace dusk { } ImGui::End(); - ClearPastFrame(); } void ClearPastFrame() { @@ -88,4 +89,10 @@ namespace dusk { StubLogBuffer.clear(); LineOffsets.clear(); } + + void ImGuiMenuTools::afterDraw() { + std::lock_guard lock(StubLogMutex); + + ClearPastFrame(); + } } From 9527ce006949460648202246647f0924422e1d02 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 29 Mar 2026 18:21:36 +0200 Subject: [PATCH 117/350] Add fullscreen checkbox to game menu --- src/dusk/imgui/ImGuiMenuGame.cpp | 4 ++++ src/dusk/imgui/ImGuiMenuGame.hpp | 1 + 2 files changed, 5 insertions(+) diff --git a/src/dusk/imgui/ImGuiMenuGame.cpp b/src/dusk/imgui/ImGuiMenuGame.cpp index e4ff609ccb..ff1ef73205 100644 --- a/src/dusk/imgui/ImGuiMenuGame.cpp +++ b/src/dusk/imgui/ImGuiMenuGame.cpp @@ -29,6 +29,10 @@ namespace dusk { ImGui::SetTooltip("Adds GC-specific -0.01 transS offset\n" "that causes ~6px ghost artifacts in water reflections"); } + ImGui::Checkbox("Fullscreen", &m_graphicsSettings.m_fullscreen); + if (m_graphicsSettings.m_fullscreen != VIGetWindowFullscreen()) { + VISetWindowFullscreen(m_graphicsSettings.m_fullscreen); + } ImGui::EndMenu(); } diff --git a/src/dusk/imgui/ImGuiMenuGame.hpp b/src/dusk/imgui/ImGuiMenuGame.hpp index 35e7e397aa..1582ffab89 100644 --- a/src/dusk/imgui/ImGuiMenuGame.hpp +++ b/src/dusk/imgui/ImGuiMenuGame.hpp @@ -37,6 +37,7 @@ namespace dusk { struct { bool m_enableBloom = 1; bool m_waterProjectionOffset = false; + bool m_fullscreen = false; } m_graphicsSettings; bool m_showControllerConfig = false; From 5b18c7bbf3b562edb9b123a8d2297499e3177232 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 29 Mar 2026 18:22:12 +0200 Subject: [PATCH 118/350] Fix bad DSP assert Fixes #150 Fixes #133 --- src/dusk/audio/DuskDsp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dusk/audio/DuskDsp.cpp b/src/dusk/audio/DuskDsp.cpp index 2b26ea478c..7b9401d394 100644 --- a/src/dusk/audio/DuskDsp.cpp +++ b/src/dusk/audio/DuskDsp.cpp @@ -254,7 +254,7 @@ static int ReadChannelSamplesChunk( renderData + skipSamples, static_cast(renderSize - skipSamples * sizeof(u16))); - assert(channel.mSamplePosition % channel.mSamplesPerBlock == 0 || channel.mSamplesLeft == 0); + assert(curSamplePosition % channel.mSamplesPerBlock == 0 || channel.mSamplesLeft == 0); return static_cast(renderSamples - skipSamples); } From 65da97eca28367b96221c9a9597616ba47fc6399 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 29 Mar 2026 18:22:45 +0200 Subject: [PATCH 119/350] Fix Midna portal spawning after boss #140 --- src/d/actor/d_a_midna.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/d/actor/d_a_midna.cpp b/src/d/actor/d_a_midna.cpp index cbbf1915e2..88a5bb38bb 100644 --- a/src/d/actor/d_a_midna.cpp +++ b/src/d/actor/d_a_midna.cpp @@ -1834,12 +1834,12 @@ void daMidna_c::setBckAnime(J3DAnmTransform* i_bck, int i_attr, f32 i_morf) { } } else { u8* buf = mBckHeap[0].getBuffer(); - if (*(u32*)(buf + 0x1c) == 0xffffffff) { + if (*(BE(u32)*)(buf + 0x1c) == 0xffffffff) { offStateFlg1(FLG1_UNK_800); bas = NULL; } else { onStateFlg1(FLG1_UNK_800); - bas = mBckHeap[0].getBuffer() + *(u32*)(buf + 0x1c); + bas = mBckHeap[0].getBuffer() + *(BE(u32)*)(buf + 0x1c); } } From b536b73762e9e8870508a733c22f395bf96850d9 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Sun, 29 Mar 2026 14:59:27 -0600 Subject: [PATCH 120/350] d_a_obj_brg: Fix out-of-bounds brg_init_pos --- src/d/actor/d_a_obj_brg.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/d/actor/d_a_obj_brg.cpp b/src/d/actor/d_a_obj_brg.cpp index cb666f3205..64dd01ee45 100644 --- a/src/d/actor/d_a_obj_brg.cpp +++ b/src/d/actor/d_a_obj_brg.cpp @@ -1796,6 +1796,10 @@ static int daObj_Brg_Create(fopAc_ac_c* i_this) { cXyz(334.0f, 85.0f, -16054.0f), cXyz(334.0f, 150.0f, -16270.0f), cXyz(334.0f, 216.0f, -16485.0f), +#ifdef TARGET_PC + // Avoids out-of-bounds index (n=22) + cXyz(0.f, 0.f, 0.f), +#endif }; for (brno = 0; brno < a_this->field_0xb1ea; brno++) { From db5d1cd20ec5ad45dc58554c4cf57cf4d367c449 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Sun, 29 Mar 2026 15:08:27 -0600 Subject: [PATCH 121/350] Update aurora --- extern/aurora | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/aurora b/extern/aurora index 236a7a5e9a..5ff8232fd7 160000 --- a/extern/aurora +++ b/extern/aurora @@ -1 +1 @@ -Subproject commit 236a7a5e9a65b3d74371c2eae8fd161f3c5eacb1 +Subproject commit 5ff8232fd7fa4524bb71cdf0dbb32e5bc6d72ccb From ba0caceca6a0b31bb28e44f8776e7c586ea45233 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Sun, 29 Mar 2026 15:42:26 -0600 Subject: [PATCH 122/350] Update aurora --- extern/aurora | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/aurora b/extern/aurora index 5ff8232fd7..beb6fdbbbc 160000 --- a/extern/aurora +++ b/extern/aurora @@ -1 +1 @@ -Subproject commit 5ff8232fd7fa4524bb71cdf0dbb32e5bc6d72ccb +Subproject commit beb6fdbbbc08e672d9c23db1881800420d57e883 From c98b96f94eff76d380c62f3935fefe56057dc99e Mon Sep 17 00:00:00 2001 From: Luzagar <40547781+Luzagar@users.noreply.github.com> Date: Mon, 30 Mar 2026 01:27:52 +0200 Subject: [PATCH 123/350] E ws work (#3137) * e_ws work * more doc * add debug message * move declaration * more match * test * Revert "more match" This reverts commit 3d910e6e913d84685aa40fe993a50402b3ee27a1. * fix * cleanup * fix * rename mIsHome --- include/d/actor/d_a_e_ws.h | 22 +-- src/d/actor/d_a_e_ws.cpp | 382 +++++++++++++++++++------------------ 2 files changed, 212 insertions(+), 192 deletions(-) diff --git a/include/d/actor/d_a_e_ws.h b/include/d/actor/d_a_e_ws.h index c7b6ae5c58..ada0509d24 100644 --- a/include/d/actor/d_a_e_ws.h +++ b/include/d/actor/d_a_e_ws.h @@ -50,30 +50,30 @@ public: int create(); /* 0x5AC */ request_of_phase_process_class mPhase; - /* 0x5B4 */ mDoExt_McaMorfSO* mpModelMorf; + /* 0x5B4 */ mDoExt_McaMorfSO* mAnm_p; /* 0x5B8 */ Z2CreatureEnemy mSound; - /* 0x65C */ cXyz field_0x65c; - /* 0x668 */ csXyz field_0x668; - /* 0x66E */ csXyz field_0x66e; + /* 0x65C */ cXyz mHomePos; + /* 0x668 */ csXyz mTargetWallAngle; + /* 0x66E */ csXyz mWallAngle; /* 0x674 */ f32 mDownColor; /* 0x678 */ f32 mBodyScale; /* 0x67C */ int mAction; /* 0x680 */ int mMode; /* 0x684 */ u32 mShadowId; /* 0x688 */ s16 mTargetAngle; - /* 0x68A */ s16 mTargetStep; - /* 0x68C */ u8 mMoveWaitTimer; + /* 0x68A */ s16 mStepAngle; + /* 0x68C */ u8 mWaitTimer; /* 0x68E */ s16 mInvulnerabilityTimer; - /* 0x690 */ u8 field_0x690; - /* 0x691 */ u8 field_0x691; - /* 0x692 */ u8 mSwbit; - /* 0x694 */ dBgS_AcchCir mAcchCir; + /* 0x690 */ u8 mIsReturnHome; + /* 0x691 */ u8 arg0; + /* 0x692 */ u8 bitSw; + /* 0x694 */ dBgS_AcchCir mBgc; /* 0x6D4 */ dBgS_ObjAcch mAcch; /* 0x8AC */ dCcD_Stts mCcStts; /* 0x8E8 */ dCcD_Sph mCcSph; /* 0xA20 */ dCcD_Sph mCcBokkuriSph; /* 0xB58 */ dCcU_AtInfo mAtInfo; - /* 0xB7C */ u8 mHIOInit; + /* 0xB7C */ u8 mHioSet; }; STATIC_ASSERT(sizeof(daE_WS_c) == 0xb80); diff --git a/src/d/actor/d_a_e_ws.cpp b/src/d/actor/d_a_e_ws.cpp index a5fe07f24b..761b046166 100644 --- a/src/d/actor/d_a_e_ws.cpp +++ b/src/d/actor/d_a_e_ws.cpp @@ -10,6 +10,10 @@ #include #include "f_op/f_op_actor_enemy.h" +#define PLAYER_NOT_FOUND 0 +#define PLAYER_TARGET 1 +#define PLAYER_NEAR 2 + class daE_WS_HIO_c : public JORReflexible { public: daE_WS_HIO_c(); @@ -28,6 +32,7 @@ public: /* 0x24 */ u8 debug_ON; }; + namespace { static dCcD_SrcSph cc_ws_src = { { @@ -66,13 +71,27 @@ daE_WS_HIO_c::daE_WS_HIO_c() { attack_speed = 10.0f; } +#if DEBUG +void daE_WS_HIO_c::genMessage(JORMContext* ctx) { + ctx->genLabel("スタルウォーーーーーーーール", 0x80000001); + ctx->genSlider("基本サイズ", &base_size, 0.0f, 5.0f); + ctx->genSlider("サーチ角度", &search_angle, 0.0f, 30000.0f); + ctx->genSlider("攻撃速度", &attack_speed, 0.0f, 100.0f); + ctx->genSlider("移動範囲", &move_range, 0.0f, 1000.0f); + ctx->genSlider("サーチ範囲", &search_range, 0.0f, 1000.0f); + ctx->genSlider("サーチY上下範囲", &search_y_range, 0.0f, 1000.0f); + ctx->genSlider("地面までの距離", &dist_to_ground, 0.0f, 1000.0f); + ctx->genCheckBox("デバック表示", &debug_ON, 0x1); +} +#endif + int daE_WS_c::draw() { - J3DModel* model_p = mpModelMorf->getModel(); + J3DModel* model = mAnm_p->getModel(); g_env_light.settingTevStruct(0, ¤t.pos, &tevStr); - g_env_light.setLightTevColorType_MAJI(model_p, &tevStr); + g_env_light.setLightTevColorType_MAJI(model, &tevStr); if (mDownColor) { - J3DModelData* modelData_p = model_p->getModelData(); + J3DModelData* modelData_p = model->getModelData(); for (u16 i = 0; i < modelData_p->getMaterialNum(); i++) { J3DMaterial* material_p = modelData_p->getMaterialNodePointer(i); material_p->getTevColor(0)->r = mDownColor; @@ -81,48 +100,48 @@ int daE_WS_c::draw() { } } - mpModelMorf->entryDL(); + mAnm_p->entryDL(); cXyz sp8; sp8.set(current.pos.x, 100.0f + current.pos.y, current.pos.z); - mShadowId = dComIfGd_setShadow(mShadowId, 1, model_p, &sp8, 400.0f, 0.0f, current.pos.y, mAcch.GetGroundH(), mAcch.m_gnd, &tevStr, 0, 1.0f, dDlst_shadowControl_c::getSimpleTex()); + mShadowId = dComIfGd_setShadow(mShadowId, 1, model, &sp8, 400.0f, 0.0f, current.pos.y, mAcch.GetGroundH(), mAcch.m_gnd, &tevStr, 0, 1.0f, dDlst_shadowControl_c::getSimpleTex()); return 1; } -static int daE_WS_Draw(daE_WS_c* a_this) { - return a_this->draw(); +static int daE_WS_Draw(daE_WS_c* i_this) { + return i_this->draw(); } void daE_WS_c::setBck(int i_anm, u8 i_mode, f32 i_morf, f32 i_speed) { - mpModelMorf->setAnm((J3DAnmTransform*)dComIfG_getObjectRes("E_WS", i_anm), i_mode, i_morf, i_speed, 0.0f, -1.0f); + mAnm_p->setAnm((J3DAnmTransform*)dComIfG_getObjectRes("E_WS", i_anm), i_mode, i_morf, i_speed, 0.0f, -1.0f); } void daE_WS_c::setFootSound() { - if (mpModelMorf->getAnm() == dComIfG_getObjectRes("E_WS", 7)) { - if (mpModelMorf->checkFrame(0.0f) || - mpModelMorf->checkFrame(4.5f) || - mpModelMorf->checkFrame(7.5f) || - mpModelMorf->checkFrame(9.0f) || - mpModelMorf->checkFrame(13.5f) || - mpModelMorf->checkFrame(16.0f) || - mpModelMorf->checkFrame(19.0f) || - mpModelMorf->checkFrame(23.5f) || - mpModelMorf->checkFrame(25.0f) || - mpModelMorf->checkFrame(28.0f) || - mpModelMorf->checkFrame(32.5f) || - mpModelMorf->checkFrame(36.0f) || - mpModelMorf->checkFrame(39.5f)) + if (mAnm_p->getAnm() == dComIfG_getObjectRes("E_WS", 7)) { + if (mAnm_p->checkFrame(0.0f) || + mAnm_p->checkFrame(4.5f) || + mAnm_p->checkFrame(7.5f) || + mAnm_p->checkFrame(9.0f) || + mAnm_p->checkFrame(13.5f) || + mAnm_p->checkFrame(16.0f) || + mAnm_p->checkFrame(19.0f) || + mAnm_p->checkFrame(23.5f) || + mAnm_p->checkFrame(25.0f) || + mAnm_p->checkFrame(28.0f) || + mAnm_p->checkFrame(32.5f) || + mAnm_p->checkFrame(36.0f) || + mAnm_p->checkFrame(39.5f)) { mSound.startCreatureSound(Z2SE_EN_WS_FOOTNOTE, 0, -1); } - } else if (mpModelMorf->getAnm() == dComIfG_getObjectRes("E_WS", 8)) { - if (mpModelMorf->checkFrame(0.5f) || - mpModelMorf->checkFrame(6.0f) || - mpModelMorf->checkFrame(11.0f) || - mpModelMorf->checkFrame(16.0f) || - mpModelMorf->checkFrame(21.0f) || - mpModelMorf->checkFrame(26.5f) || - mpModelMorf->checkFrame(31.0f)) + } else if (mAnm_p->getAnm() == dComIfG_getObjectRes("E_WS", 8)) { + if (mAnm_p->checkFrame(0.5f) || + mAnm_p->checkFrame(6.0f) || + mAnm_p->checkFrame(11.0f) || + mAnm_p->checkFrame(16.0f) || + mAnm_p->checkFrame(21.0f) || + mAnm_p->checkFrame(26.5f) || + mAnm_p->checkFrame(31.0f)) { mSound.startCreatureSound(Z2SE_EN_WS_FOOTNOTE, 0, -1); } @@ -141,13 +160,13 @@ f32 daE_WS_c::calcTargetDist(cXyz i_basePos, cXyz i_targetPos) { } s16 daE_WS_c::calcTargetAngle(cXyz i_basePos, cXyz i_targetPos) { - cXyz sp1C; - cXyz sp10 = i_targetPos - i_basePos; + cXyz mae; + cXyz ato = i_targetPos - i_basePos; - mDoMtx_stack_c::XrotS(-field_0x668.x); - mDoMtx_stack_c::YrotM(-field_0x668.y); - mDoMtx_stack_c::multVec(&sp10, &sp1C); - return cM_atan2s(sp1C.x, sp1C.z); + mDoMtx_stack_c::XrotS(-mTargetWallAngle.x); + mDoMtx_stack_c::YrotM(-mTargetWallAngle.y); + mDoMtx_stack_c::multVec(&ato, &mae); + return cM_atan2s(mae.x, mae.z); } static u8 hio_set; @@ -171,13 +190,13 @@ int daE_WS_c::checkPlayerPos() { dComIfGp_checkPlayerStatus1(0, 0x2000000) || dComIfGp_checkPlayerStatus1(0, 0x10000) || calcTargetDist(current.pos, player_pos) < 150.0f) && - checkInSearchRange(player_pos, field_0x65c) && checkInSearchRange(current.pos, field_0x65c)) + checkInSearchRange(player_pos, mHomePos) && checkInSearchRange(current.pos, mHomePos)) { dBgS_GndChk gndchk; cXyz gndpos; mDoMtx_stack_c::transS(current.pos); - mDoMtx_stack_c::ZXYrotM(field_0x66e); + mDoMtx_stack_c::ZXYrotM(mWallAngle); mDoMtx_stack_c::ZXYrotM(shape_angle); mDoMtx_stack_c::transM(0.0f, 100.0f, 0.0f); mDoMtx_stack_c::multVecZero(&gndpos); @@ -187,17 +206,17 @@ int daE_WS_c::checkPlayerPos() { if (current.pos.y - gndpos.y > l_HIO.dist_to_ground) { // Return 1 if walltula is looking towards player if (cLib_distanceAngleS(shape_angle.y, calcTargetAngle(current.pos, player_pos)) < l_HIO.search_angle) { - return 1; + return PLAYER_TARGET; } // otherwise return 2 if player is near the walltula if (calcTargetDist(current.pos, player_pos) < 150.0f) { - return 2; + return PLAYER_NEAR; } } } - return 0; + return PLAYER_NOT_FOUND; } bool daE_WS_c::checkAttackEnd() { @@ -205,17 +224,17 @@ bool daE_WS_c::checkAttackEnd() { mDoMtx_stack_c::copy(daPy_getLinkPlayerActorClass()->getModelJointMtx(0)); mDoMtx_stack_c::multVecZero(&player_pos); - BOOL r30 = false; + BOOL checkPlayerNear = FALSE; if ( daPy_getPlayerActorClass()->checkClimbMove() || dComIfGp_checkPlayerStatus1(0, 0x02000000) || dComIfGp_checkPlayerStatus1(0, 0x10000) || calcTargetDist(current.pos, player_pos) < 200.0f ) { - r30 = true; + checkPlayerNear = TRUE; } - if (!r30 || - !checkInSearchRange(current.pos, field_0x65c) || + if (!checkPlayerNear || + !checkInSearchRange(current.pos, mHomePos) || checkBeforeBg(shape_angle.y) ) { @@ -228,41 +247,41 @@ bool daE_WS_c::checkAttackEnd() { } void daE_WS_c::executeWait() { - int temp_r3 = checkPlayerPos(); - if (temp_r3 == 1) { + int playerCheck = checkPlayerPos(); + if (playerCheck == PLAYER_TARGET) { setActionMode(ACTION_ATTACK_e); return; } - if (temp_r3 == 2 && mMode != 3 && mMode != 4) { + if (playerCheck == PLAYER_NEAR && mMode != 3 && mMode != 4) { mMode = 2; } switch (mMode) { case 0: - mMoveWaitTimer = 50.0f + cM_rndF(50.0f); + mWaitTimer = 50.0f + cM_rndF(50.0f); setBck(9, 2, 3.0f, 1.0f); mMode = 1; /* fallthrough */ case 1: - if (mMoveWaitTimer == 0) { + if (mWaitTimer == 0) { mMode = 2; } break; case 2: speedF = 0.0f; - field_0x690 = 0; - mTargetAngle = shape_angle.y + cM_rndFX(32768.0f); + mIsReturnHome = 0; + mTargetAngle = shape_angle.y + cM_rndFX(32768.0f); // random turn up to ±180° - if (temp_r3 == 2) { - mTargetStep = 0x200; + if (playerCheck == PLAYER_NEAR) { + mStepAngle = 0x200; setBck(8, 2, 3.0f, 2.4f); } else { - if (calcTargetDist(current.pos, field_0x65c) >= l_HIO.move_range) { - mTargetAngle = calcTargetAngle(current.pos, field_0x65c); - field_0x690 = 1; + if (calcTargetDist(current.pos, mHomePos) >= l_HIO.move_range) { + mTargetAngle = calcTargetAngle(current.pos, mHomePos); + mIsReturnHome = 1; } - mTargetStep = 0x100; + mStepAngle = 0x100; setBck(8, 2, 3.0f, 1.2f); } @@ -271,37 +290,37 @@ void daE_WS_c::executeWait() { case 3: setFootSound(); - if (cLib_chaseAngleS(&shape_angle.y, mTargetAngle, mTargetStep)) { + if (cLib_chaseAngleS(&shape_angle.y, mTargetAngle, mStepAngle)) { mMode = 4; - mMoveWaitTimer = 10; + mWaitTimer = 10; setBck(9, 2, 3.0f, 1.0f); } break; case 4: - if (mMoveWaitTimer == 0) { + if (mWaitTimer == 0) { mMode = 5; } break; case 5: mMode = 6; speedF = 3.0f; - mMoveWaitTimer = 20.0f + cM_rndF(10.0f); + mWaitTimer = 20.0f + cM_rndF(10.0f); setBck(7, 2, 3.0f, 1.0f); /* fallthrough */ case 6: setFootSound(); - if (field_0x690 == 0) { - if (calcTargetDist(current.pos, field_0x65c) >= l_HIO.move_range) { - mMoveWaitTimer = 0; + if (mIsReturnHome == 0) { + if (calcTargetDist(current.pos, mHomePos) >= l_HIO.move_range) { + mWaitTimer = 0; } } if (checkBeforeBg(shape_angle.y)) { - mMoveWaitTimer = 0; + mWaitTimer = 0; } - if (mMoveWaitTimer == 0) { + if (mWaitTimer == 0) { speedF = 0.0f; mMode = 0; } @@ -333,11 +352,11 @@ void daE_WS_c::executeAttack() { mMode = 2; setBck(10, 2, 3.0f, 1.0f); mSound.startCreatureVoice(Z2SE_EN_WS_V_YOKOKU, -1); - mMoveWaitTimer = 10; + mWaitTimer = 10; } break; case 2: - if (mMoveWaitTimer == 0) { + if (mWaitTimer == 0) { speedF = l_HIO.attack_speed * mBodyScale; setBck(7, 2, 3.0f, 3.0f); mMode = 3; @@ -352,20 +371,20 @@ void daE_WS_c::executeAttack() { setFootSound(); cLib_chaseAngleS(&shape_angle.y, calcTargetAngle(current.pos, player_pos), 0x400); - BOOL r28 = false; + BOOL checkAttackStart = FALSE; if (checkBeforeBg(shape_angle.y)) { - r28 = true; + checkAttackStart = TRUE; } if (mCcSph.ChkAtHit()) { - cCcD_Obj* r27 = mCcSph.GetAtHitObj(); - if (fopAcM_GetName(dCc_GetAc(r27->GetAc())) == fpcNm_ALINK_e) { - r28 = true; + cCcD_Obj* hitObj = mCcSph.GetAtHitObj(); + if (fopAcM_GetName(dCc_GetAc(hitObj->GetAc())) == fpcNm_ALINK_e) { + checkAttackStart = TRUE; } } - if (!checkInSearchRange(current.pos, field_0x65c)) { - r28 = true; + if (!checkInSearchRange(current.pos, mHomePos)) { + checkAttackStart = TRUE; } - if (r28) { + if (checkAttackStart) { mMode = 4; speedF = 0.0f; setBck(4, 0, 3.0f, 1.0f); @@ -378,11 +397,11 @@ void daE_WS_c::executeAttack() { break; } case 4: - if (mpModelMorf->checkFrame(7.5f)) { + if (mAnm_p->checkFrame(7.5f)) { mSound.startCreatureVoice(Z2SE_EN_WS_V_ATTACK, -1); } - if (mpModelMorf->isStop()) { + if (mAnm_p->isStop()) { setActionMode(ACTION_WAIT_e); } /* fallthrough */ @@ -408,9 +427,9 @@ void daE_WS_c::executeDown() { mSound.startCreatureVoice(Z2SE_EN_WS_V_DAMAGE, -1); /* fallthrough */ case 1: - if (mpModelMorf->isStop()) { - mAcchCir.SetWall(0.0f, 4.0f); - current.angle.y = field_0x668.y; + if (mAnm_p->isStop()) { + mBgc.SetWall(0.0f, 4.0f); + current.angle.y = mTargetWallAngle.y; setBck(6, 0, 3.0f, 0.0f); mSound.startCreatureVoice(Z2SE_EN_WS_V_DEATH, -1); speedF = 5.0f; @@ -433,7 +452,7 @@ void daE_WS_c::executeDown() { speedF = 3.0f + cM_rndF(2.0f); speed.y = 12.0f; mMode = 3; - mMoveWaitTimer = 30; + mWaitTimer = 30; setBck(6, 0, 5.0f, 1.0f); mSound.startCreatureSound(Z2SE_CM_BODYFALL_S, 0, -1); } @@ -467,8 +486,8 @@ void daE_WS_c::executeDown() { case 4: cLib_addCalc2(&mDownColor, -20.0f, 1.0f, 0.4f); - if (mpModelMorf->isStop()) { - mMoveWaitTimer = 15; + if (mAnm_p->isStop()) { + mWaitTimer = 15; mMode = 5; return; } @@ -476,12 +495,12 @@ void daE_WS_c::executeDown() { case 5: cLib_addCalc2(&mDownColor, -20.0f, 1.0f, 0.4f); - if (mMoveWaitTimer == 0) { + if (mWaitTimer == 0) { fopAcM_delete(this); fopAcM_createDisappear(this, ¤t.pos, 7, 0, 7); - if (mSwbit != 0xFF && !dComIfGs_isSwitch(mSwbit, fopAcM_GetRoomNo(this))) { - dComIfGs_onSwitch(mSwbit, fopAcM_GetRoomNo(this)); + if (bitSw != 0xFF && !dComIfGs_isSwitch(bitSw, fopAcM_GetRoomNo(this))) { + dComIfGs_onSwitch(bitSw, fopAcM_GetRoomNo(this)); } } break; @@ -497,36 +516,36 @@ void daE_WS_c::executeWindDown() { mCcSph.OffTgSetBit(); mCcBokkuriSph.OffTgSetBit(); - mAcchCir.SetWall(0.0f, 4.0f); + mBgc.SetWall(0.0f, 4.0f); mMode = 1; setBck(7, 2, 3.0f, 1.0f); mSound.startCreatureVoice(Z2SE_EN_WS_V_DAMAGE, -1); - mMoveWaitTimer = 5; + mWaitTimer = 5; mAcch.SetGroundUpY(20.0f); attention_info.flags = 0; speed.y = 0.0f; speedF = 5.0f; - current.angle.y = field_0x668.y; - mTargetStep = -0x800; + current.angle.y = mTargetWallAngle.y; + mStepAngle = -0x800; break; case 1: - shape_angle.y += mTargetStep; - cLib_chaseAngleS(&field_0x66e.y, 0, 0x400); - cLib_chaseAngleS(&field_0x66e.x, 0, 0x400); + shape_angle.y += mStepAngle; + cLib_chaseAngleS(&mWallAngle.y, 0, 0x400); + cLib_chaseAngleS(&mWallAngle.x, 0, 0x400); shape_angle.x += 0x800; shape_angle.z += 0x800; speed.y = 30.0f; - if (mMoveWaitTimer == 0) { + if (mWaitTimer == 0) { mMode = 2; gravity = -3.0f; } break; case 2: - shape_angle.y += mTargetStep; - cLib_chaseAngleS(&field_0x66e.y, 0, 0x400); - cLib_chaseAngleS(&field_0x66e.x, 0, 0x400); + shape_angle.y += mStepAngle; + cLib_chaseAngleS(&mWallAngle.y, 0, 0x400); + cLib_chaseAngleS(&mWallAngle.x, 0, 0x400); cLib_chaseAngleS(&shape_angle.x, -0x8000, 0x400); cLib_chaseAngleS(&shape_angle.z, 0, 0x400); @@ -534,15 +553,15 @@ void daE_WS_c::executeWindDown() { speedF = 3.0f + cM_rndF(2.0f); speed.y = 12.0f; mMode = 3; - mMoveWaitTimer = 30; + mWaitTimer = 30; setBck(6, 0, 5.0f, 1.0f); mSound.startCreatureVoice(Z2SE_EN_WS_V_DEATH, -1); mSound.startCreatureSound(Z2SE_CM_BODYFALL_S, 0, -1); } break; case 3: - shape_angle.y += mTargetStep; - cLib_chaseAngleS(&mTargetStep, 0, 0x80); + shape_angle.y += mStepAngle; + cLib_chaseAngleS(&mStepAngle, 0, 0x80); cLib_chaseAngleS(&shape_angle.x, -0x8000, 0x400); cLib_chaseAngleS(&shape_angle.z, 0, 0x400); @@ -556,23 +575,23 @@ void daE_WS_c::executeWindDown() { break; case 4: cLib_addCalc2(&mDownColor, -20.0f, 1.0f, 0.4f); - shape_angle.y += mTargetStep; - cLib_chaseAngleS(&mTargetStep, 0, 0x80); - shape_angle.y += mTargetStep; + shape_angle.y += mStepAngle; + cLib_chaseAngleS(&mStepAngle, 0, 0x80); + shape_angle.y += mStepAngle; - if (mpModelMorf->isStop()) { - mMoveWaitTimer = 15; + if (mAnm_p->isStop()) { + mWaitTimer = 15; mMode = 5; } break; case 5: cLib_addCalc2(&mDownColor, -20.0f, 1.0f, 0.4f); - if (mMoveWaitTimer == 0) { + if (mWaitTimer == 0) { fopAcM_delete(this); fopAcM_createDisappear(this, ¤t.pos, 7, 0, 7); - if (mSwbit != 0xFF && !dComIfGs_isSwitch(mSwbit, fopAcM_GetRoomNo(this))) { - dComIfGs_onSwitch(mSwbit, fopAcM_GetRoomNo(this)); + if (bitSw != 0xFF && !dComIfGs_isSwitch(bitSw, fopAcM_GetRoomNo(this))) { + dComIfGs_onSwitch(bitSw, fopAcM_GetRoomNo(this)); } } break; @@ -653,47 +672,47 @@ void daE_WS_c::action() { mSound.setLinkSearch(field_0x566); if (mAction != ACTION_DOWN_e && mAction != ACTION_WIND_DOWN_e) { - cXyz sp14; - mDoMtx_stack_c::YrotS(field_0x668.y); - mDoMtx_stack_c::XrotM(field_0x668.x); + cXyz ato; + mDoMtx_stack_c::YrotS(mTargetWallAngle.y); + mDoMtx_stack_c::XrotM(mTargetWallAngle.x); mDoMtx_stack_c::YrotM(current.angle.y); - cXyz sp8(0.0f, 0.0f, speedF); - mDoMtx_stack_c::multVec(&sp8, &sp14); - speed = sp14; + cXyz mae(0.0f, 0.0f, speedF); + mDoMtx_stack_c::multVec(&mae, &ato); + speed = ato; current.pos += speed; } else { fopAcM_posMoveF(this, NULL); } mAcch.CrrPos(dComIfG_Bgsp()); - mpModelMorf->play(0, dComIfGp_getReverb(fopAcM_GetRoomNo(this))); + mAnm_p->play(0, dComIfGp_getReverb(fopAcM_GetRoomNo(this))); } void daE_WS_c::mtx_set() { mDoMtx_stack_c::transS(current.pos); - mDoMtx_stack_c::ZXYrotM(field_0x66e); + mDoMtx_stack_c::ZXYrotM(mWallAngle); mDoMtx_stack_c::ZXYrotM(shape_angle); mDoMtx_stack_c::scaleM(mBodyScale, mBodyScale, mBodyScale); - J3DModel* model_p = mpModelMorf->getModel(); + J3DModel* model_p = mAnm_p->getModel(); model_p->setBaseTRMtx(mDoMtx_stack_c::get()); - mpModelMorf->modelCalc(); + mAnm_p->modelCalc(); } void daE_WS_c::cc_set() { cXyz mae; cXyz ato; - J3DModel* model_p = mpModelMorf->getModel(); + J3DModel* model = mAnm_p->getModel(); - mDoMtx_stack_c::YrotS(field_0x668.y); - mDoMtx_stack_c::XrotM(field_0x668.x); + mDoMtx_stack_c::YrotS(mTargetWallAngle.y); + mDoMtx_stack_c::XrotM(mTargetWallAngle.x); mae.set(0.0f, 15.0f + nREG_F(10), 0.0f); mDoMtx_stack_c::multVec(&mae, &ato); attention_info.position = current.pos + ato; attention_info.position.y += 90.0f * mBodyScale; - MTXCopy(model_p->getAnmMtx(1), mDoMtx_stack_c::get()); + MTXCopy(model->getAnmMtx(1), mDoMtx_stack_c::get()); mae.set(-15.0f + nREG_F(5), -10.0f + nREG_F(6), nREG_F(7)); mDoMtx_stack_c::multVec(&mae, &eyePos); @@ -707,8 +726,8 @@ void daE_WS_c::cc_set() { } int daE_WS_c::execute() { - if (mMoveWaitTimer != 0) { - mMoveWaitTimer--; + if (mWaitTimer != 0) { + mWaitTimer--; } if (mInvulnerabilityTimer != 0) { @@ -725,8 +744,8 @@ int daE_WS_c::execute() { return 1; } -static int daE_WS_Execute(daE_WS_c* a_this) { - return a_this->execute(); +static int daE_WS_Execute(daE_WS_c* i_this) { + return i_this->execute(); } void daE_WS_c::checkInitialWall() { @@ -739,7 +758,7 @@ void daE_WS_c::checkInitialWall() { linchk.Set(¤t.pos, &endpos, NULL); if (dComIfG_Bgsp().LineCross(&linchk)) { - if (field_0x691 == 0 && dComIfG_Bgsp().GetWallCode(linchk) != 1 && dComIfG_Bgsp().GetWallCode(linchk) != 4) { + if (arg0 == 0 && dComIfG_Bgsp().GetWallCode(linchk) != 1 && dComIfG_Bgsp().GetWallCode(linchk) != 4) { return; } @@ -749,9 +768,9 @@ void daE_WS_c::checkInitialWall() { dComIfG_Bgsp().GetTriPla(linchk, &tri); cXyz* tri_np = tri.GetNP(); - field_0x668.y = cM_atan2s(tri_np->x, tri_np->z); - field_0x668.x = cM_atan2s(tri_np->absXZ(), tri_np->y); - field_0x66e = field_0x668; + mTargetWallAngle.y = cM_atan2s(tri_np->x, tri_np->z); + mTargetWallAngle.x = cM_atan2s(tri_np->absXZ(), tri_np->y); + mWallAngle = mTargetWallAngle; return; } } @@ -759,8 +778,8 @@ void daE_WS_c::checkInitialWall() { bool daE_WS_c::checkBeforeBg(s16 i_angle) { dBgS_LinChk linchk; - cXyz sp68; - cXyz sp5C; + cXyz mae; + cXyz ato; cXyz endpos; cXyz startpos; @@ -768,56 +787,56 @@ bool daE_WS_c::checkBeforeBg(s16 i_angle) { return false; } - mDoMtx_stack_c::YrotS(field_0x668.y); - mDoMtx_stack_c::XrotM(field_0x668.x); + mDoMtx_stack_c::YrotS(mTargetWallAngle.y); + mDoMtx_stack_c::XrotM(mTargetWallAngle.x); mDoMtx_stack_c::YrotM(i_angle); - sp68.set(0.0f, 50.0f * mBodyScale, 0.0f); - mDoMtx_stack_c::multVec(&sp68, &startpos); + mae.set(0.0f, 50.0f * mBodyScale, 0.0f); + mDoMtx_stack_c::multVec(&mae, &startpos); startpos += current.pos; - mDoMtx_stack_c::YrotS(field_0x668.y); - mDoMtx_stack_c::XrotM(field_0x668.x); + mDoMtx_stack_c::YrotS(mTargetWallAngle.y); + mDoMtx_stack_c::XrotM(mTargetWallAngle.x); mDoMtx_stack_c::YrotM(i_angle); - sp68.set(0.0f, 0.0f, 50.0f * mBodyScale); - mDoMtx_stack_c::multVec(&sp68, &sp5C); + mae.set(0.0f, 0.0f, 50.0f * mBodyScale); + mDoMtx_stack_c::multVec(&mae, &ato); - sp68.set(sp5C.x, 0.0f, sp5C.z); - endpos = startpos + sp68; + mae.set(ato.x, 0.0f, ato.z); + endpos = startpos + mae; linchk.Set(&startpos, &endpos, NULL); if (dComIfG_Bgsp().LineCross(&linchk)) { return 1; } - if (sp5C.y > 0.0f) { - sp68.set(0.0f, 50.0f * mBodyScale, 0.0f); + if (ato.y > 0.0f) { + mae.set(0.0f, 50.0f * mBodyScale, 0.0f); } else { - sp68.set(0.0f, -l_HIO.dist_to_ground, 0.0f); + mae.set(0.0f, -l_HIO.dist_to_ground, 0.0f); } - endpos = startpos + sp68; + endpos = startpos + mae; linchk.Set(&startpos, &endpos, NULL); if (dComIfG_Bgsp().LineCross(&linchk)) { return true; } - mDoMtx_stack_c::YrotS(field_0x668.y); - mDoMtx_stack_c::XrotM(field_0x668.x); + mDoMtx_stack_c::YrotS(mTargetWallAngle.y); + mDoMtx_stack_c::XrotM(mTargetWallAngle.x); mDoMtx_stack_c::YrotM(i_angle); - sp68.set(0.0f, 50.0f * mBodyScale, 100.0f * mBodyScale); - mDoMtx_stack_c::multVec(&sp68, &sp5C); - startpos = current.pos + sp5C; + mae.set(0.0f, 50.0f * mBodyScale, 100.0f * mBodyScale); + mDoMtx_stack_c::multVec(&mae, &ato); + startpos = current.pos + ato; cXyz sp38(0.0f, -40.0f * mBodyScale, 100.0f * mBodyScale); - mDoMtx_stack_c::multVec(&sp38, &sp5C); - endpos = current.pos + sp5C; + mDoMtx_stack_c::multVec(&sp38, &ato); + endpos = current.pos + ato; linchk.Set(&startpos, &endpos, NULL); if (!dComIfG_Bgsp().LineCross(&linchk)) { return true; } - if (field_0x691 == 0 && dComIfG_Bgsp().GetWallCode(linchk) != 1 && dComIfG_Bgsp().GetWallCode(linchk) != 4) { + if (arg0 == 0 && dComIfG_Bgsp().GetWallCode(linchk) != 1 && dComIfG_Bgsp().GetWallCode(linchk) != 4) { return true; } @@ -825,7 +844,7 @@ bool daE_WS_c::checkBeforeBg(s16 i_angle) { dComIfG_Bgsp().GetTriPla(linchk, &tri); cXyz* tri_np = tri.GetNP(); - cLib_chaseAngleS(&field_0x66e.y, cM_atan2s(tri_np->x, tri_np->z), 0x100); + cLib_chaseAngleS(&mWallAngle.y, cM_atan2s(tri_np->x, tri_np->z), 0x100); checkWall(); return false; } @@ -834,7 +853,7 @@ bool daE_WS_c::checkWall() { cXyz startpos; cXyz endpos; mDoMtx_stack_c::transS(current.pos); - mDoMtx_stack_c::ZXYrotM(field_0x66e); + mDoMtx_stack_c::ZXYrotM(mWallAngle); mDoMtx_stack_c::ZXYrotM(shape_angle); mDoMtx_stack_c::transM(0.0f, 100.0f, 0.0f); @@ -846,7 +865,7 @@ bool daE_WS_c::checkWall() { dBgS_LinChk linchk; linchk.Set(&startpos, &endpos, NULL); if (dComIfG_Bgsp().LineCross(&linchk)) { - if (field_0x691 == 0 && dComIfG_Bgsp().GetWallCode(linchk) != 1 && dComIfG_Bgsp().GetWallCode(linchk) != 4) { + if (arg0 == 0 && dComIfG_Bgsp().GetWallCode(linchk) != 1 && dComIfG_Bgsp().GetWallCode(linchk) != 4) { return false; } @@ -856,8 +875,8 @@ bool daE_WS_c::checkWall() { dComIfG_Bgsp().GetTriPla(linchk, &tri); cXyz* tri_np = tri.GetNP(); - field_0x668.y = cM_atan2s(tri_np->x, tri_np->z); - field_0x668.x = cM_atan2s(tri_np->absXZ(), tri_np->y); + mTargetWallAngle.y = cM_atan2s(tri_np->x, tri_np->z); + mTargetWallAngle.x = cM_atan2s(tri_np->absXZ(), tri_np->y); return true; } @@ -871,8 +890,8 @@ static int daE_WS_IsDelete(daE_WS_c* a_this) { int daE_WS_c::_delete() { dComIfG_resDelete(&mPhase, "E_WS"); - if (mHIOInit) { - hio_set = false; + if (mHioSet) { + hio_set = FALSE; mDoHIO_DELETE_CHILD(l_HIO.id); } @@ -883,24 +902,25 @@ int daE_WS_c::_delete() { return 1; } -static int daE_WS_Delete(daE_WS_c* a_this) { - return a_this->_delete(); +static int daE_WS_Delete(daE_WS_c* i_this) { + fopAcM_RegisterDeleteID(i_this, "E_WS"); + return i_this->_delete(); } int daE_WS_c::CreateHeap() { J3DModelData* modelData = (J3DModelData*)dComIfG_getObjectRes("E_WS", 0xD); JUT_ASSERT(1401, modelData != NULL); - mpModelMorf = new mDoExt_McaMorfSO(modelData, NULL, NULL, (J3DAnmTransform*)dComIfG_getObjectRes("E_WS", 7), 0, 1.0f, 0, -1, &mSound, 0x80000, 0x11000084); - if (mpModelMorf == NULL || mpModelMorf->getModel() == NULL) { + mAnm_p = new mDoExt_McaMorfSO(modelData, NULL, NULL, (J3DAnmTransform*)dComIfG_getObjectRes("E_WS", 7), 0, 1.0f, 0, -1, &mSound, 0x80000, 0x11000084); + if (mAnm_p == NULL || mAnm_p->getModel() == NULL) { return 0; } return 1; } -static int useHeapInit(fopAc_ac_c* i_this) { - return ((daE_WS_c*)i_this)->CreateHeap(); +static int useHeapInit(fopAc_ac_c* actor) { + return ((daE_WS_c*)actor)->CreateHeap(); } int daE_WS_c::create() { @@ -910,8 +930,8 @@ int daE_WS_c::create() { if (phase_state == cPhs_COMPLEATE_e) { OS_REPORT("E_WS PARAM %x\n", fopAcM_GetParam(this)); - mSwbit = (fopAcM_GetParam(this) & 0xFF00) >> 8; - if (mSwbit != 0xFF && dComIfGs_isSwitch(mSwbit, fopAcM_GetRoomNo(this))) { + bitSw = (fopAcM_GetParam(this) & 0xFF00) >> 8; + if (bitSw != 0xFF && dComIfGs_isSwitch(bitSw, fopAcM_GetRoomNo(this))) { OS_REPORT("E_WS やられ後なので再セットしません\n"); return cPhs_ERROR_e; } @@ -922,13 +942,13 @@ int daE_WS_c::create() { if (!hio_set) { hio_set = true; - mHIOInit = true; + mHioSet = true; l_HIO.id = mDoHIO_CREATE_CHILD("スタルウォール", &l_HIO); } - field_0x691 = fopAcM_GetParam(this); - if (field_0x691 == 0xFF) { - field_0x691 = 0; + arg0 = fopAcM_GetParam(this); + if (arg0 == 0xFF) { + arg0 = 0; } if (((fopAcM_GetParam(this) & 0xFF0000) >> 0x10) == 1) { @@ -938,12 +958,12 @@ int daE_WS_c::create() { } attention_info.flags = fopAc_AttnFlag_BATTLE_e; - fopAcM_SetMtx(this, mpModelMorf->getModel()->getBaseTRMtx()); + fopAcM_SetMtx(this, mAnm_p->getModel()->getBaseTRMtx()); fopAcM_SetMin(this, -200.0f, -200.0f, -200.0f); fopAcM_SetMax(this, 200.0f, 200.0f, 200.0f); - mAcch.Set(fopAcM_GetPosition_p(this), fopAcM_GetOldPosition_p(this), this, 1, &mAcchCir, fopAcM_GetSpeed_p(this), NULL, NULL); - mAcchCir.SetWall(0.0f, 0.0f); + mAcch.Set(fopAcM_GetPosition_p(this), fopAcM_GetOldPosition_p(this), this, 1, &mBgc, fopAcM_GetSpeed_p(this), NULL, NULL); + mBgc.SetWall(0.0f, 0.0f); health = 10; field_0x560 = 10; @@ -963,7 +983,7 @@ int daE_WS_c::create() { setActionMode(ACTION_WAIT_e); checkInitialWall(); - field_0x65c = current.pos; + mHomePos = current.pos; speed.y = 0.0f; gravity = 0.0f; mtx_set(); @@ -972,8 +992,8 @@ int daE_WS_c::create() { return phase_state; } -static int daE_WS_Create(daE_WS_c* a_this) { - return a_this->create(); +static int daE_WS_Create(daE_WS_c* i_this) { + return i_this->create(); } static actor_method_class l_daE_WS_Method = { From cb6ee4b21e25cb40a08c0f1effeebda79896a7e5 Mon Sep 17 00:00:00 2001 From: TakaRikka <38417346+TakaRikka@users.noreply.github.com> Date: Sun, 29 Mar 2026 16:43:00 -0700 Subject: [PATCH 124/350] d_path / d_vibration debug work (#3138) * dpath debug * dvibration debug * d_meter2_info debug * gcyl/gpsh debug --- include/d/d_meter2_info.h | 5 +- include/d/d_msg_object.h | 4 + include/d/d_path.h | 2 +- include/d/d_stage.h | 2 +- include/d/d_vibration.h | 16 +- include/m_Do/m_Do_controller_pad.h | 2 +- .../include/JSystem/JUtility/JUTGamePad.h | 4 +- src/SSystem/SComponent/c_m3d_g_cyl.cpp | 27 +- src/SSystem/SComponent/c_m3d_g_sph.cpp | 18 +- src/d/actor/d_a_npc_tk.cpp | 2 +- src/d/actor/d_a_tag_sppath.cpp | 2 +- src/d/d_meter2_info.cpp | 264 ++++++++++- src/d/d_path.cpp | 195 ++++++-- src/d/d_stage.cpp | 4 +- src/d/d_vib_pattern.cpp | 27 ++ src/d/d_vibration.cpp | 419 +++++++++++++++--- 16 files changed, 869 insertions(+), 124 deletions(-) diff --git a/include/d/d_meter2_info.h b/include/d/d_meter2_info.h index 72ca6ad9b2..51f07f4dc3 100644 --- a/include/d/d_meter2_info.h +++ b/include/d/d_meter2_info.h @@ -321,6 +321,7 @@ int dMeter2Info_setNewLetterSender(); bool dMeter2Info_isItemOpenCheck(); bool dMeter2Info_isMapOpenCheck(); s16 dMeter2Info_getNowLifeGauge(); +bool dMeter2Info_isNextStage(const char*, s16, s16, s16); #if WIDESCREEN_SUPPORT void dMeter2Info_onWide2D(); @@ -844,10 +845,6 @@ inline void dMeter2Info_onTempBit(int i_bit) { g_meter2_info.onTempBit(i_bit); } -inline bool dMeter2Info_isNextStage(const char*, s16, s16, s16) { - return false; -} - inline void dMeter2Info_setFloatingMessage(u16 i_msgID, s16 i_msgTimer, bool i_wakuVisible) { g_meter2_info.setFloatingMessage(i_msgID, i_msgTimer, i_wakuVisible); } diff --git a/include/d/d_msg_object.h b/include/d/d_msg_object.h index 45606e4e81..b55ea73904 100644 --- a/include/d/d_msg_object.h +++ b/include/d/d_msg_object.h @@ -562,6 +562,10 @@ inline void dMsgObject_setOffering(u16 i_num) { dComIfGs_setEventReg(0xF8FF, i_num & 0xFF); } +inline void dMsgObject_setLetterNameID(u16 id) { + dMsgObject_getMsgObjectClass()->setLetterNameID(id); +} + class dMsgObject_HowlHIO_c { public: dMsgObject_HowlHIO_c(); diff --git a/include/d/d_path.h b/include/d/d_path.h index d8ac2af674..810e7df04b 100644 --- a/include/d/d_path.h +++ b/include/d/d_path.h @@ -20,7 +20,7 @@ struct dPath { /* 0x2 */ u16 m_nextID; /* 0x4 */ u8 field_0x4; /* 0x5 */ bool m_closed; - /* 0x6 */ u8 field_0x6; + /* 0x6 */ u8 swbit; /* 0x7 */ u8 field_0x7; /* 0x8 */ dPnt* m_points; }; diff --git a/include/d/d_stage.h b/include/d/d_stage.h index d18be4f09f..d01e0243d7 100644 --- a/include/d/d_stage.h +++ b/include/d/d_stage.h @@ -263,7 +263,7 @@ struct dStage_MemoryConfig_c { // PATH / RPAT struct dPath; struct dStage_dPath_c { - /* 0x0 */ int m_num; + /* 0x0 */ int num; /* 0x4 */ dPath* m_path; }; diff --git a/include/d/d_vibration.h b/include/d/d_vibration.h index 204f6e759c..4018acea50 100644 --- a/include/d/d_vibration.h +++ b/include/d/d_vibration.h @@ -47,18 +47,21 @@ class dVibTest_c : public JORReflexible { public: dVibTest_c(); + void Init(); + void setDefault(); + virtual void listenPropertyEvent(const JORPropertyEvent*); virtual void genMessage(JORMContext*); - virtual ~dVibTest_c() {} + virtual ~dVibTest_c(); - /* 0x04 */ s8 field_0x4; + /* 0x04 */ s8 id; /* 0x06 */ u16 m_pattern; /* 0x08 */ u16 m_pattern2; /* 0x0A */ u16 field_0xa; /* 0x0C */ s16 m_randombit; /* 0x0E */ s16 m_length; /* 0x10 */ int field_0x10; - /* 0x14 */ int m_vibswitch; + /* 0x14 */ s32 m_vibswitch; /* 0x18 */ u16 m_displayDbg; }; @@ -82,6 +85,10 @@ public: void Pause(); void Remove(); + #if DEBUG + int testShake(); + #endif + static const vib_pattern MS_patt[VIBMODE_S_MAX]; static const vib_pattern CS_patt[VIBMODE_S_MAX]; static const vib_pattern MQ_patt[VIBMODE_Q_MAX]; @@ -123,4 +130,7 @@ private: /* 0x8C */ s32 mMode; }; // Size: 0x90 +extern const char* shock_names[VIBMODE_S_MAX]; +extern const char* quake_names[VIBMODE_Q_MAX]; + #endif /* D_D_VIBRATION_H */ diff --git a/include/m_Do/m_Do_controller_pad.h b/include/m_Do/m_Do_controller_pad.h index 48d5256ea2..588c5950e5 100644 --- a/include/m_Do/m_Do_controller_pad.h +++ b/include/m_Do/m_Do_controller_pad.h @@ -60,7 +60,7 @@ public: static f32 getAnalogR(u32 pad) { return getCpadInfo(pad).mTriggerRight; } static f32 getAnalogL(u32 pad) { return getCpadInfo(pad).mTriggerLeft; } static BOOL isConnect(u32 pad) { return JUTGamePad::getPortStatus((JUTGamePad::EPadPort)pad) == 0; } - static void startMotorWave(u32 pad, void* data, JUTGamePad::CRumble::ERumble rumble, u32 length) { + static void startMotorWave(u32 pad, u8* data, JUTGamePad::CRumble::ERumble rumble, u32 length) { m_gamePad[pad]->startMotorWave(data, rumble, length); } static void stopMotor(u32 pad) { m_gamePad[pad]->stopMotor(); } diff --git a/libs/JSystem/include/JSystem/JUtility/JUTGamePad.h b/libs/JSystem/include/JSystem/JUtility/JUTGamePad.h index bf939f0831..30bf685700 100644 --- a/libs/JSystem/include/JSystem/JUtility/JUTGamePad.h +++ b/libs/JSystem/include/JSystem/JUtility/JUTGamePad.h @@ -117,7 +117,7 @@ public: } void stopMotorWave() { mRumble.stopPatternedRumbleAtThePeriod(); } - void stopMotor() { mRumble.stopMotor(mPortNum, false); } + void stopMotor() { mRumble.stopMotor(mPortNum); } void stopMotorHard() { CRumble::stopMotorHard(mPortNum); } static s8 getPortStatus(EPadPort port) { @@ -233,7 +233,7 @@ public: /* 0x10 */ u8* field_0x10; }; // Size: 0x14 - void startMotorWave(void* data, CRumble::ERumble rumble, u32 length) { + void startMotorWave(u8* data, CRumble::ERumble rumble, u32 length) { mRumble.startPatternedRumble(data, rumble, length); } diff --git a/src/SSystem/SComponent/c_m3d_g_cyl.cpp b/src/SSystem/SComponent/c_m3d_g_cyl.cpp index 19bf8d795c..c2f292e08d 100644 --- a/src/SSystem/SComponent/c_m3d_g_cyl.cpp +++ b/src/SSystem/SComponent/c_m3d_g_cyl.cpp @@ -6,6 +6,8 @@ #include "SSystem/SComponent/c_m3d_g_cyl.h" #include "SSystem/SComponent/c_m3d.h" +#include "JSystem/JUtility/JUTAssert.h" + cM3dGCyl::cM3dGCyl(const cXyz* center, f32 radius, f32 height) { SetC(*center); SetR(radius); @@ -24,21 +26,30 @@ void cM3dGCyl::Set(const cXyz& center, f32 radius, f32 height) { SetH(height); } -void cM3dGCyl::SetC(const cXyz& center) { - mCenter = center; +void cM3dGCyl::SetC(const cXyz& pos) { + JUT_ASSERT(67, !isnan(pos.x)); + JUT_ASSERT(68, !isnan(pos.y)); + JUT_ASSERT(69, !isnan(pos.z)); + + JUT_ASSERT(72, -1.0e32f < pos.x && pos.x < 1.0e32f && -1.0e32f < pos.y && pos.y < 1.0e32f && -1.0e32f < pos.z && pos.z < 1.0e32f); + + mCenter = pos; } -void cM3dGCyl::SetH(f32 height) { - mHeight = height; +void cM3dGCyl::SetH(f32 h) { + JUT_ASSERT(82, !isnan(h)); + JUT_ASSERT(83, -1.0e32f < h && h < 1.0e32f); + mHeight = h; } -void cM3dGCyl::SetR(f32 radius) { - mRadius = radius; +void cM3dGCyl::SetR(f32 r) { + JUT_ASSERT(106, !isnan(r)); + JUT_ASSERT(107, -1.0e32f < r && r < 1.0e32f); + mRadius = r; } bool cM3dGCyl::cross(const cM3dGSph* other, cXyz* out) const { - f32 f; - return cM3d_Cross_CylSph(this, other, out, &f); + return cM3d_Cross_CylSph(this, other, out); } bool cM3dGCyl::cross(const cM3dGCyl* other, cXyz* out) const { diff --git a/src/SSystem/SComponent/c_m3d_g_sph.cpp b/src/SSystem/SComponent/c_m3d_g_sph.cpp index 3fd65af53a..9d730b0579 100644 --- a/src/SSystem/SComponent/c_m3d_g_sph.cpp +++ b/src/SSystem/SComponent/c_m3d_g_sph.cpp @@ -6,8 +6,16 @@ #include "SSystem/SComponent/c_m3d_g_sph.h" #include "SSystem/SComponent/c_m3d.h" -void cM3dGSph::SetC(const cXyz& center) { - mCenter = center; +#include "JSystem/JUtility/JUTAssert.h" + +void cM3dGSph::SetC(const cXyz& p) { + JUT_ASSERT(19, !isnan(p.x)); + JUT_ASSERT(20, !isnan(p.y)); + JUT_ASSERT(21, !isnan(p.z)); + + JUT_ASSERT(24, -1.0e32f < p.x && p.x < 1.0e32f && -1.0e32f < p.y && p.y < 1.0e32f && -1.0e32f < p.z && p.z < 1.0e32f); + + mCenter = p; } void cM3dGSph::Set(const cXyz& center, f32 radius) { @@ -20,8 +28,10 @@ void cM3dGSph::Set(const cM3dGSphS& other) { SetR(other.mRadius); } -void cM3dGSph::SetR(f32 radius) { - mRadius = radius; +void cM3dGSph::SetR(f32 r) { + JUT_ASSERT(54, !isnan(r)); + JUT_ASSERT(55, -1.0e32f < r && r < 1.0e32f); + mRadius = r; } bool cM3dGSph::cross(const cM3dGSph* other, cXyz* out) const { diff --git a/src/d/actor/d_a_npc_tk.cpp b/src/d/actor/d_a_npc_tk.cpp index adf3a00575..8ecae48dd1 100644 --- a/src/d/actor/d_a_npc_tk.cpp +++ b/src/d/actor/d_a_npc_tk.cpp @@ -2014,7 +2014,7 @@ void daNPC_TK_c::executeWolfPerch() { mWolfPathData = dPath_GetRoomPath(mpPath1->m_nextID, fopAcM_GetRoomNo(this)); JUT_ASSERT(2498, mWolfPathData != NULL); - field_0x6ea = mWolfPathData->field_0x6; + field_0x6ea = mWolfPathData->swbit; field_0x6e8 = mWolfPathData->field_0x4; field_0x6e9 = mWolfPathData->field_0x7; field_0x6d0.Init(mWolfPathData); diff --git a/src/d/actor/d_a_tag_sppath.cpp b/src/d/actor/d_a_tag_sppath.cpp index 81fbe04184..9fdb00fe55 100644 --- a/src/d/actor/d_a_tag_sppath.cpp +++ b/src/d/actor/d_a_tag_sppath.cpp @@ -189,7 +189,7 @@ int daTagSppath_c::execute() { } if (mpBestPath->field_0x4 == 0) { - if (mpBestPath->field_0x6 == 0xff || fopAcM_isSwitch(this, mpBestPath->field_0x6) == 0) { + if (mpBestPath->swbit == 0xff || fopAcM_isSwitch(this, mpBestPath->swbit) == 0) { field_0x6e8 = 1; } else { field_0x6e8 = 2; diff --git a/src/d/d_meter2_info.cpp b/src/d/d_meter2_info.cpp index b74bab0c5d..3e8d2601c1 100644 --- a/src/d/d_meter2_info.cpp +++ b/src/d/d_meter2_info.cpp @@ -11,6 +11,8 @@ #include "d/d_meter_map.h" #include "d/d_msg_class.h" #include "d/d_msg_object.h" +#include "d/d_meter_HIO.h" + #include enum ITEMICON_RES_FILE_ID { @@ -362,19 +364,18 @@ void dMeter2Info_c::getString(u32 i_stringID, char* o_string, JMSMesgEntry_c* i_ } JMSMesgInfo_c* bmg_inf = (JMSMesgInfo_c*)(msgRes + sizeof(bmg_header_t)); - u8* bmg_data = (u8*)bmg_inf + bmg_inf->header.size + sizeof(bmg_section_t); // pointer to start of message data + u8* bmg_data = (u8*)bmg_inf + bmg_inf->header.size; + u8* string_data = bmg_data + sizeof(bmg_section_t); // pointer to start of message data char* string_ptr = NULL; for (u16 i = 0; i < bmg_inf->entry_num; i++) { - u8* entry = ((u8*)bmg_inf + (i * sizeof(JMSMesgEntry_c))); - // check if i_stringID equals the message entry "Message ID" - if (i_stringID == *(u16*)(entry + 0x14)) { - string_ptr = (char*)(bmg_data + *(u32*)(entry + 0x10)); // use entry "String Offset" to get string pointer + if (i_stringID == bmg_inf->entries[i].message_id) { + string_ptr = (char*)(string_data + bmg_inf->entries[i].string_offset); // use entry "String Offset" to get string pointer strcpy(o_string, string_ptr); if (i_msgEntry != NULL) { - memcpy(i_msgEntry, entry + 0x10, sizeof(JMSMesgEntry_c)); + memcpy(i_msgEntry, &bmg_inf->entries[i], sizeof(JMSMesgEntry_c)); } return; @@ -508,6 +509,10 @@ void dMeter2Info_c::getStringKanji(u32 i_stringID, char* o_string, JMSMesgEntry_ } } +static void dummyString() { + OS_REPORT("レボ用ID=====>%d, %d\n"); +} + f32 dMeter2Info_c::getStringLength(J2DTextBox* i_textbox, char* i_string) { f32 str_width = 0.0f; f32 str_len = 0.0f; @@ -1003,6 +1008,104 @@ s16 dMeter2Info_c::get4thTexture(u8 i_itemType) { } void dMeter2Info_c::set1stColor(u8 i_itemType, J2DPicture* i_pic) { + // TODO: probably some way to rectify this for both versions + #if VERSION == VERSION_SHIELD_DEBUG + static JUtility::TColor const black_color[37] = { + JUtility::TColor(0x00, 0x00, 0x00, 0x00), JUtility::TColor(0x00, 0x60, 0x00, 0x00), JUtility::TColor(0x00, 0x00, 0xC0, 0x00), + JUtility::TColor(0xA0, 0x60, 0x00, 0x00), JUtility::TColor(0xA0, 0x00, 0x00, 0x00), JUtility::TColor(0x40, 0x00, 0x60, 0x00), + JUtility::TColor(0xE0, 0x00, 0x00, 0x00), JUtility::TColor(0x40, 0x40, 0x40, 0x00), JUtility::TColor(0x6E, 0x6E, 0x64, 0x00), + JUtility::TColor(0x32, 0x00, 0x00, 0x00), JUtility::TColor(0x00, 0x7F, 0x00, 0x00), JUtility::TColor(0x00, 0x00, 0x7F, 0x00), + JUtility::TColor(0xAF, 0x9B, 0x6E, 0x00), JUtility::TColor(0xAA, 0x9B, 0x6E, 0x00), JUtility::TColor(0x55, 0x37, 0x14, 0x00), + JUtility::TColor(0x6E, 0x6E, 0x6E, 0x00), JUtility::TColor(0x6E, 0x6E, 0x6E, 0x00), JUtility::TColor(0xFF, 0x58, 0x00, 0x00), + JUtility::TColor(0x6C, 0x3E, 0x00, 0x00), JUtility::TColor(0x00, 0x00, 0x00, 0x00), JUtility::TColor(0x32, 0x00, 0x00, 0x00), + JUtility::TColor(0x00, 0x00, 0x7F, 0x00), JUtility::TColor(0x00, 0x7F, 0x00, 0x00), JUtility::TColor(0x55, 0x37, 0x14, 0x00), + JUtility::TColor(0x00, 0x00, 0x22, 0x00), JUtility::TColor(0x2B, 0x18, 0x22, 0x00), JUtility::TColor(0x00, 0x00, 0x00, 0x00), + JUtility::TColor(0x46, 0x46, 0x41, 0x00), JUtility::TColor(0x46, 0x46, 0x41, 0x00), JUtility::TColor(0x46, 0x46, 0x41, 0x00), + JUtility::TColor(0x46, 0x46, 0x41, 0x00), JUtility::TColor(0x00, 0x00, 0x00, 0x00), JUtility::TColor(0x00, 0x00, 0x00, 0x00), + JUtility::TColor(0x00, 0x00, 0x00, 0x00), JUtility::TColor(0x00, 0x00, 0x00, 0x00), JUtility::TColor(0x00, 0x00, 0x00, 0x00), + JUtility::TColor(0x00, 0x00, 0x00, 0x00), + }; + + static JUtility::TColor const white_color[37] = { + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0x60, 0xFF, 0x00, 0xFF), JUtility::TColor(0x00, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0x00, 0xFF), JUtility::TColor(0xFF, 0x80, 0x80, 0xFF), JUtility::TColor(0xBE, 0x40, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xC0, 0x00, 0xFF), JUtility::TColor(0xC0, 0xC0, 0xC0, 0xFF), JUtility::TColor(0xF5, 0xF5, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xD2, 0xAA, 0xFF), JUtility::TColor(0xEF, 0xF5, 0xC9, 0xFF), JUtility::TColor(0xB0, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xF0, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xF0, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xD7, 0xFF), + JUtility::TColor(0xF5, 0xF5, 0xFF, 0xFF), JUtility::TColor(0xF5, 0xF5, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xC2, 0xFF), + JUtility::TColor(0xFF, 0x9D, 0x00, 0xFF), JUtility::TColor(0xC8, 0xC8, 0xC8, 0xFF), JUtility::TColor(0xFF, 0xD2, 0xAA, 0xFF), + JUtility::TColor(0xB0, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xEF, 0xF5, 0xC9, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xD7, 0xFF), + JUtility::TColor(0xD7, 0xCF, 0xF5, 0xFF), JUtility::TColor(0xFF, 0xFF, 0x33, 0xFF), JUtility::TColor(0xC8, 0xC8, 0xC8, 0xFF), + JUtility::TColor(0xF5, 0xF5, 0xFF, 0xFF), JUtility::TColor(0xF5, 0xF5, 0xFF, 0xFF), JUtility::TColor(0xF5, 0xF5, 0xFF, 0xFF), + JUtility::TColor(0xF5, 0xF5, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + }; + + static JUtility::TColor const vertex_color_lu[37] = { + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0x00), + JUtility::TColor(0xFF, 0x78, 0xAF, 0xFF), JUtility::TColor(0x5C, 0xB4, 0x16, 0xFF), JUtility::TColor(0xA4, 0xFF, 0x00, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0x00, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0x78, 0xAF, 0xFF), + JUtility::TColor(0xA4, 0xFF, 0x00, 0xFF), JUtility::TColor(0x5C, 0xB4, 0x16, 0xFF), JUtility::TColor(0xFF, 0xFF, 0x00, 0xFF), + JUtility::TColor(0xC9, 0xB4, 0xFF, 0xFF), JUtility::TColor(0x3C, 0x0A, 0x00, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0x00), JUtility::TColor(0xFF, 0xFF, 0xFF, 0x00), JUtility::TColor(0xFF, 0xFF, 0xFF, 0x00), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0x00), JUtility::TColor(0xFF, 0xA0, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + }; + + static JUtility::TColor const vertex_color_ru[37] = { + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0x00), + JUtility::TColor(0xFF, 0xFF, 0x73, 0xFF), JUtility::TColor(0xFF, 0xFF, 0x2A, 0xFF), JUtility::TColor(0x98, 0xFF, 0x00, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0x73, 0xFF), + JUtility::TColor(0x98, 0xFF, 0x00, 0xFF), JUtility::TColor(0xFF, 0xFF, 0x2A, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0x39, 0xFF), JUtility::TColor(0xFF, 0xFF, 0x00, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0x00), JUtility::TColor(0xFF, 0xFF, 0xFF, 0x00), JUtility::TColor(0xFF, 0xFF, 0xFF, 0x00), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0x00), JUtility::TColor(0xFF, 0xA0, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + }; + + static JUtility::TColor const vertex_color_ld[37] = { + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0x78, 0x6E, 0x3C, 0xFF), + JUtility::TColor(0xFF, 0x00, 0x00, 0xFF), JUtility::TColor(0x3C, 0x32, 0x50, 0xFF), JUtility::TColor(0x00, 0x00, 0x74, 0xFF), + JUtility::TColor(0xFA, 0xC8, 0x9B, 0xFF), JUtility::TColor(0xFA, 0xC8, 0x9B, 0xFF), JUtility::TColor(0x46, 0x87, 0x00, 0xFF), + JUtility::TColor(0x5A, 0xB4, 0xB4, 0xFF), JUtility::TColor(0x5A, 0xB4, 0xB4, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0x00, 0x00, 0x00, 0xFF), JUtility::TColor(0xFF, 0x00, 0x00, 0xFF), + JUtility::TColor(0x00, 0x00, 0x74, 0xFF), JUtility::TColor(0x3C, 0x32, 0x50, 0xFF), JUtility::TColor(0x46, 0x87, 0x00, 0xFF), + JUtility::TColor(0x3C, 0x32, 0x50, 0x49), JUtility::TColor(0xFF, 0xFF, 0x00, 0xFF), JUtility::TColor(0x00, 0x00, 0x00, 0xFF), + JUtility::TColor(0x00, 0x00, 0x00, 0xFF), JUtility::TColor(0x00, 0x00, 0x00, 0xFF), JUtility::TColor(0x00, 0x00, 0x00, 0xFF), + JUtility::TColor(0x00, 0x00, 0x00, 0xFF), JUtility::TColor(0xE0, 0x00, 0xE0, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + }; + + static JUtility::TColor const vertex_color_rd[37] = { + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0x78, 0x6E, 0x3C, 0xFF), + JUtility::TColor(0xFF, 0x96, 0x00, 0xFF), JUtility::TColor(0x55, 0x42, 0x00, 0xFF), JUtility::TColor(0x61, 0x48, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xAA, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xAA, 0xFF), JUtility::TColor(0xAF, 0x91, 0x23, 0xFF), + JUtility::TColor(0xE6, 0xFA, 0xFF, 0xFF), JUtility::TColor(0xE6, 0xFA, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0x00, 0x00, 0x00, 0xFF), JUtility::TColor(0xFF, 0x96, 0x00, 0xFF), + JUtility::TColor(0x61, 0x48, 0xFF, 0xFF), JUtility::TColor(0x55, 0x42, 0x00, 0xFF), JUtility::TColor(0xAF, 0x91, 0x23, 0xFF), + JUtility::TColor(0x3C, 0x09, 0x4E, 0xFF), JUtility::TColor(0xBA, 0x98, 0x00, 0xFF), JUtility::TColor(0x00, 0x00, 0x00, 0xFF), + JUtility::TColor(0x00, 0x00, 0x00, 0xFF), JUtility::TColor(0x00, 0x00, 0x00, 0xFF), JUtility::TColor(0x00, 0x00, 0x00, 0xFF), + JUtility::TColor(0x00, 0x00, 0x00, 0xFF), JUtility::TColor(0xE0, 0x00, 0xE0, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + }; + #else static GXColor const black_color[37] = { {0x00, 0x00, 0x00, 0x00}, {0x00, 0x60, 0x00, 0x00}, {0x00, 0x00, 0xC0, 0x00}, {0xA0, 0x60, 0x00, 0x00}, {0xA0, 0x00, 0x00, 0x00}, {0x40, 0x00, 0x60, 0x00}, @@ -1098,6 +1201,7 @@ void dMeter2Info_c::set1stColor(u8 i_itemType, J2DPicture* i_pic) { {0xFF, 0xFF, 0xFF, 0xFF}, {0xFF, 0xFF, 0xFF, 0xFF}, {0xFF, 0xFF, 0xFF, 0xFF}, {0xFF, 0xFF, 0xFF, 0xFF}, }; + #endif i_pic->setBlackWhite(*(JUtility::TColor*)&black_color[i_itemType], *(JUtility::TColor*)&white_color[i_itemType]); @@ -1108,6 +1212,104 @@ void dMeter2Info_c::set1stColor(u8 i_itemType, J2DPicture* i_pic) { } void dMeter2Info_c::set2ndColor(u8 i_itemType, J2DPicture* i_pic) { + // TODO: probably some way to rectify this for both versions + #if VERSION == VERSION_SHIELD_DEBUG + static JUtility::TColor const black_color[37] = { + JUtility::TColor(0x00, 0x00, 0x00, 0x00), JUtility::TColor(0x00, 0x00, 0x00, 0x00), JUtility::TColor(0x00, 0x00, 0x00, 0x00), + JUtility::TColor(0x00, 0x00, 0x00, 0x00), JUtility::TColor(0x00, 0x00, 0x00, 0x00), JUtility::TColor(0x00, 0x00, 0x00, 0x00), + JUtility::TColor(0x00, 0x00, 0x00, 0x00), JUtility::TColor(0x00, 0x00, 0x00, 0x00), JUtility::TColor(0x00, 0x00, 0x00, 0x00), + JUtility::TColor(0x00, 0x00, 0x00, 0x00), JUtility::TColor(0x00, 0x00, 0x00, 0x00), JUtility::TColor(0x00, 0x00, 0x00, 0x00), + JUtility::TColor(0x00, 0x00, 0x00, 0x00), JUtility::TColor(0x00, 0x00, 0x00, 0x00), JUtility::TColor(0x00, 0x00, 0x00, 0x00), + JUtility::TColor(0x00, 0x00, 0x00, 0x00), JUtility::TColor(0x00, 0x00, 0x00, 0x00), JUtility::TColor(0x00, 0x00, 0x00, 0x00), + JUtility::TColor(0x00, 0x00, 0x00, 0x00), JUtility::TColor(0x00, 0x00, 0x00, 0x00), JUtility::TColor(0x00, 0x00, 0x00, 0x00), + JUtility::TColor(0x00, 0x00, 0x00, 0x00), JUtility::TColor(0x00, 0x00, 0x00, 0x00), JUtility::TColor(0x00, 0x00, 0x00, 0x00), + JUtility::TColor(0x00, 0x00, 0x00, 0x00), JUtility::TColor(0x00, 0x00, 0x00, 0x00), JUtility::TColor(0x00, 0x00, 0x00, 0x00), + JUtility::TColor(0x00, 0x86, 0xD4, 0x00), JUtility::TColor(0xE6, 0x1E, 0xFF, 0x00), JUtility::TColor(0x00, 0x00, 0x00, 0x00), + JUtility::TColor(0x00, 0x00, 0x00, 0x00), JUtility::TColor(0x00, 0x00, 0x00, 0x00), JUtility::TColor(0x00, 0x00, 0x00, 0x00), + JUtility::TColor(0x00, 0x00, 0x00, 0x00), JUtility::TColor(0x00, 0x00, 0x00, 0x00), JUtility::TColor(0x00, 0x00, 0x00, 0x00), + JUtility::TColor(0x00, 0x00, 0x00, 0x00), + }; + + static JUtility::TColor const white_color[37] = { + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xF5, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xC8, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + }; + + static JUtility::TColor const vertex_color_lu[37] = { + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0x00, 0x00, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + }; + + static JUtility::TColor const vertex_color_ru[37] = { + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0x00, 0xFF, 0xFF, 0x58), JUtility::TColor(0xFF, 0xFF, 0xFF, 0x58), JUtility::TColor(0xFF, 0xFF, 0xFF, 0x58), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0x58), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + }; + + static JUtility::TColor const vertex_color_ld[37] = { + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + }; + + static JUtility::TColor const vertex_color_rd[37] = { + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xCD, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xCD, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + JUtility::TColor(0xFF, 0xFF, 0xFF, 0xFF), + }; + #else static GXColor const black_color[37] = { {0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00}, @@ -1203,6 +1405,7 @@ void dMeter2Info_c::set2ndColor(u8 i_itemType, J2DPicture* i_pic) { {0xFF, 0xFF, 0xFF, 0xFF}, {0xFF, 0xFF, 0xFF, 0xFF}, {0xFF, 0xFF, 0xFF, 0xFF}, {0xFF, 0xFF, 0xFF, 0xFF}, }; + #endif i_pic->setBlackWhite(*(JUtility::TColor*)&black_color[i_itemType], *(JUtility::TColor*)&white_color[i_itemType]); @@ -1235,6 +1438,8 @@ void dMeter2Info_c::set3rdColor(u8 i_itemType, J2DPicture* i_pic) { } void dMeter2Info_c::set4thColor(u8 i_itemType, J2DPicture* i_pic) { + UNUSED(i_itemType); + i_pic->setBlackWhite(JUtility::TColor(0, 0, 0, 0), JUtility::TColor(0xff, 0xff, 0xff, 0xff)); i_pic->setCornerColor( JUtility::TColor(0xff, 0xff, 0xff, 0xff), JUtility::TColor(0xff, 0xff, 0xff, 0xff), @@ -1443,6 +1648,18 @@ u8 dMeter2Info_getPixel(f32 i_posX, f32 i_posY, f32 param_2, f32 param_3, f32 i_ return (var_r24 & 0x7000) != 0; } +bool dMeter2Info_isNextStage(const char* i_name, s16 i_roomNo, s16 i_point, s16 i_layer) { + if (strcmp(dComIfGp_getNextStageName(), i_name) == 0 + && dComIfGp_getNextStageRoomNo() == i_roomNo + && dComIfGp_getNextStagePoint() == i_point + && dComIfGp_getNextStageLayer() == i_layer + ) { + return true; + } + + return false; +} + void dMeter2Info_setCloth(u8 i_clothId, bool i_offItemBit) { switch (i_clothId) { case dItemNo_WEAR_CASUAL_e: @@ -1586,6 +1803,7 @@ u8 dMeter2Info_getNewLetterNum() { } int dMeter2Info_setNewLetterSender() { + int ret = 0; u8 check = 0; for (int i = 0; i < 0x40; i++) { @@ -1593,10 +1811,9 @@ int dMeter2Info_setNewLetterSender() { u16 letterEvent = dMenu_Letter::getLetterEventFlag(i); if (dComIfGs_isEventBit(dSv_event_flag_c::saveBitLabels[letterEvent])) { if (check == 0) { - u16 letterName = dMenu_Letter::getLetterName(i); - dMsgObject_c::setLetterNameID(letterName); + dMsgObject_setLetterNameID(dMenu_Letter::getLetterName(i)); } else { - dMsgObject_c::setLetterNameID(0); + dMsgObject_setLetterNameID(0); return 0; } check++; @@ -1604,7 +1821,7 @@ int dMeter2Info_setNewLetterSender() { } } - return 0; + return ret; } int dMeter2Info_recieveLetter() { @@ -1636,6 +1853,33 @@ int dMeter2Info_recieveLetter() { return rv; } +#if WIDESCREEN_SUPPORT +f32 dMeter2Info_getWide2DPosX(f32* param_0) { + J2DOrthoGraph graf(0.0f, 0.0f, 640.0f, 456.0f, -1.0f, 1.0f); + graf.setOrtho(mDoGph_gInf_c::getMinXF(), mDoGph_gInf_c::getMinYF(), mDoGph_gInf_c::getWidthF(), mDoGph_gInf_c::getHeightF(), -1.0f, 1.0f); + JGeometry::TBox2* bounds = graf.getBounds(); + const JGeometry::TBox2* ortho = graf.getOrtho(); + + f32 var_f31 = 304.0f; + f32 var_f30 = 608.0f / (ortho->f.x - ortho->i.x); + f32 var_f29 = *param_0 - var_f31; + *param_0 = var_f29 * var_f30 + var_f31; + return *param_0; +} + +void dMeter2Info_onWide2D() { + g_ringHIO.updateOnWide(); + g_drawHIO.updateOnWide(); +} + +void dMeter2Info_offWide2D() { + g_ringHIO.updateOffWide(); + g_drawHIO.updateOffWide(); +} +#endif + +void dMeter2Info_set2DVibrationT() {} + void dMeter2Info_set2DVibration() {} void dMeter2Info_set2DVibrationM() {} diff --git a/src/d/d_path.cpp b/src/d/d_path.cpp index a01cfdc8a6..b1fa3b7891 100644 --- a/src/d/d_path.cpp +++ b/src/d/d_path.cpp @@ -8,7 +8,38 @@ #include "d/d_path.h" #include "d/d_com_inf_game.h" -dPnt* dPath_GetPnt(dPath const* path, int pnt_index) { +#if DEBUG +#include "d/d_debug_viewer.h" +#endif + +class dPath_HIO : public JORReflexible { +public: + dPath_HIO() {} + ~dPath_HIO(); + + void genMessage(JORMContext* mctx) { + mctx->genCheckBox("デバッグ表示", &flags, 2); + mctx->genSlider("部屋番号", &roomNo, -2, 64); + mctx->genLabel("-3=現在プレイヤーのいる部屋, -2=旧常駐、-1=常駐", 0); + } + + void Ct() { + flags = 0; + roomNo = -3; + } + + int GetRoomNo() { return roomNo; } + bool ChkDispLine() { return flags & 2; } + + /* 0x4 */ s8 id; + /* 0x6 */ u16 flags; + /* 0x8 */ s16 roomNo; +}; + +dPnt* dPath_GetPnt(const dPath* path, int pnt_index) { + JUT_ASSERT(27, path != NULL); + JUT_ASSERT(28, 0 <= pnt_index && pnt_index < path->m_num); + if (path == NULL || path->m_points == NULL || pnt_index < 0 || pnt_index >= path->m_num) { return NULL; } @@ -17,116 +48,212 @@ dPnt* dPath_GetPnt(dPath const* path, int pnt_index) { } dPath* dPath_GetRoomPath(int path_index, int room_no) { - dStage_dPath_c* path; + dStage_dPath_c* pd; if (room_no == -1) { - path = dComIfGp_getStage()->getPath2Inf(); + dStage_dt_c* stage = dComIfGp_getStage(); + pd = stage->getPath2Inf(); } else { + JUT_ASSERT(58, 0 <= room_no && room_no < 64); dStage_roomDt_c* roomDt = dComIfGp_roomControl_getStatusRoomDt(room_no); if (roomDt == NULL) { return NULL; } - path = roomDt->getPath2Inf(); + pd = roomDt->getPath2Inf(); } - if (path == NULL || path_index < 0 || path_index >= path->m_num) { + if (pd == NULL || path_index < 0 || path_index >= pd->num) { return NULL; } - return &path->m_path[path_index]; + return &pd->m_path[path_index]; } -dPath* dPath_GetNextRoomPath(dPath const* p_path, int room_no) { - dStage_dPath_c* path; +dPath* dPath_GetNextRoomPath(const dPath* path, int room_no) { + dStage_dPath_c* pd; if (room_no == -1) { - path = dComIfGp_getStage()->getPath2Inf(); + dStage_dt_c* stage = dComIfGp_getStage(); + pd = stage->getPath2Inf(); } else { dStage_roomDt_c* roomDt = dComIfGp_roomControl_getStatusRoomDt(room_no); if (roomDt == NULL) { return NULL; } - path = roomDt->getPath2Inf(); + pd = roomDt->getPath2Inf(); } - int next_id = p_path->m_nextID; - if (path == NULL || next_id == 0xFFFF) { + int next_id = path->m_nextID; + if (pd == NULL || next_id == 0xFFFF) { return NULL; } - if (next_id < 0 || next_id >= path->m_num) { + JUT_ASSERT(111, 0 <= next_id && next_id < pd->num); + if (next_id < 0 || next_id >= pd->num) { return NULL; } - return &path->m_path[next_id]; + return &pd->m_path[next_id]; } #if !PLATFORM_GCN // Fakematch due to differing return type on non-GCN platforms -int dPath_GetPolyRoomPathVec(cBgS_PolyInfo const& poly, cXyz* p_pathVec, int* param_2) { +int dPath_GetPolyRoomPathVec(const cBgS_PolyInfo& polyinfo, cXyz* vec, int* param_2) #else -u8 dPath_GetPolyRoomPathVec(cBgS_PolyInfo const& poly, cXyz* p_pathVec, int* param_2) { +u8 dPath_GetPolyRoomPathVec(const cBgS_PolyInfo& polyinfo, cXyz* vec, int* param_2) #endif - int roomId = dComIfG_Bgsp().GetRoomId(poly); - int roomPathId = dComIfG_Bgsp().GetRoomPathId(poly); +{ + int room_no = dComIfG_Bgsp().GetRoomId(polyinfo); + int path_idx = dComIfG_Bgsp().GetRoomPathId(polyinfo); - p_pathVec->x = 0.0f; - p_pathVec->y = 0.0f; - p_pathVec->z = 0.0f; + vec->x = 0.0f; + vec->y = 0.0f; + vec->z = 0.0f; *param_2 = 0; - if (roomId == -1) { + if (room_no == -1) { return 0; } - dPath* path = dPath_GetRoomPath(roomPathId, roomId); + dPath* path = dPath_GetRoomPath(path_idx, room_no); if (path == NULL) { return 0; } - if (path->field_0x6 != 0xFF && dComIfGs_isSwitch(path->field_0x6, roomId)) { + if (path->swbit != 0xFF && dComIfGs_isSwitch(path->swbit, room_no)) { return 0; } - int pnt_no = dComIfG_Bgsp().GetRoomPathPntNo(poly); + int pnt_no = dComIfG_Bgsp().GetRoomPathPntNo(polyinfo); if (pnt_no == 0xFF || pnt_no < 0 || pnt_no >= path->m_num) { return 0; } - dPnt* pnt_start = path->m_points; - dPnt* pnt_end = &pnt_start[pnt_no]; + dPnt* pnt_end = &path->m_points[pnt_no]; dPnt* pnt_begin; if (pnt_no == path->m_num - 1) { - pnt_begin = pnt_start; + pnt_begin = path->m_points; } else { - pnt_begin = &pnt_start[pnt_no + 1]; + pnt_begin = &path->m_points[pnt_no + 1]; } - p_pathVec->x = pnt_begin->m_position.x - pnt_end->m_position.x; - p_pathVec->y = pnt_begin->m_position.y - pnt_end->m_position.y; - p_pathVec->z = pnt_begin->m_position.z - pnt_end->m_position.z; + vec->x = pnt_begin->m_position.x - pnt_end->m_position.x; + vec->y = pnt_begin->m_position.y - pnt_end->m_position.y; + vec->z = pnt_begin->m_position.z - pnt_end->m_position.z; *param_2 = path->field_0x4; return 1; } +#if DEBUG +dPath_HIO::~dPath_HIO() {} + +static dPath_HIO s_hio; +#endif + #if VERSION == VERSION_SHIELD_DEBUG void dPath_Ct() { #if DEBUG + s_hio.Ct(); + s_hio.id = mDoHIO_CREATE_CHILD("レール", &s_hio); #endif } void dPath_Dt() { - #if DEBUG - #endif + mDoHIO_DELETE_CHILD(s_hio.id); } void dPath_Move() {} void dPath_Draw() { #if DEBUG + if (s_hio.ChkDispLine()) { + static int start_counter; + int counter = start_counter / 20; + dStage_dPath_c* pd; + + int hio_roomNo = s_hio.GetRoomNo(); + if (hio_roomNo == -3) { + int roomNo = dComIfGp_roomControl_getStayNo(); + dStage_roomDt_c* roomDt = dComIfGp_roomControl_getStatusRoomDt(roomNo); + if (roomDt == NULL) { + return; + } + + pd = roomDt->getPath2Inf(); + } else if (hio_roomNo == -2) { + pd = dComIfGp_getStage()->getPathInf(); + } else if (hio_roomNo == -1) { + pd = dComIfGp_getStage()->getPath2Inf(); + } else { + dStage_roomDt_c* roomDt = dComIfGp_roomControl_getStatusRoomDt(hio_roomNo); + if (roomDt == NULL) { + return; + } + + pd = roomDt->getPath2Inf(); + } + + if (pd != NULL) { + dPath* path = pd->m_path; + for (int i = 0; i < pd->num; i++) { + if (path->m_num >= 1) { + dPnt* pnt = path->m_points; + cXyz start_pos; + cXyz end_pos; + + for (int j = 0; j < path->m_num - 1; j++) { + start_pos.set(pnt[0].m_position); + end_pos.set(pnt[1].m_position); + + if (counter == 0) { + dDbVw_drawLineOpa(start_pos, end_pos, (GXColor){0xFF, 0xFF, 0xFF, 0xFF}, TRUE, 12); + } else { + dDbVw_drawLineOpa(start_pos, end_pos, (GXColor){0xFF, 0, 0, 0xFF}, TRUE, 12); + } + + counter++; + if (counter >= 8) { + counter = 0; + } + + pnt++; + } + + if (dPath_ChkClose(path)) { + start_pos.set(pnt[0].m_position); + end_pos.set(path[1].m_points->m_position); + + if (counter == 0) { + dDbVw_drawLineOpa(start_pos, end_pos, (GXColor){0xFF, 0xFF, 0xFF, 0xFF}, TRUE, 12); + } else { + dDbVw_drawLineOpa(start_pos, end_pos, (GXColor){0xFF, 0, 0, 0xFF}, TRUE, 12); + } + + counter++; + if (counter >= 8) { + counter = 0; + } + } + } + + path++; + } + } + + start_counter--; + if (start_counter < 0) { + start_counter = 160; + } + } #endif } + +static void dummy() { + cXyz pos; + GXColor color; + dDbVw_drawSphereOpa(pos, 0.0f, color, 0); +} #endif diff --git a/src/d/d_stage.cpp b/src/d/d_stage.cpp index f44f432d2f..0bb387a01f 100644 --- a/src/d/d_stage.cpp +++ b/src/d/d_stage.cpp @@ -2104,7 +2104,7 @@ static int dStage_pathInfoInit(dStage_dt_c* i_stage, void* i_data, int entryNum, i_stage->setPathInfo(path_c); - for (int i = 0; i < path_c->m_num; i++) { + for (int i = 0; i < path_c->num; i++) { if ((uintptr_t)path->m_points < 0x80000000) { path->m_points = (dPnt*)((uintptr_t)path->m_points + i_stage->getPntInf()->m_pnt_offset); } @@ -2129,7 +2129,7 @@ static int dStage_rpatInfoInit(dStage_dt_c* i_stage, void* i_data, int i_num, vo dPath* pPath = pStagePath->m_path; i_stage->setPath2Info(pStagePath); - for (s32 i = 0; i < pStagePath->m_num; pPath++, i++, (void)0) { + for (s32 i = 0; i < pStagePath->num; pPath++, i++, (void)0) { if ((uintptr_t)pPath->m_points >= 0x80000000) { continue; } diff --git a/src/d/d_vib_pattern.cpp b/src/d/d_vib_pattern.cpp index d8263b45bd..6c06016cf5 100644 --- a/src/d/d_vib_pattern.cpp +++ b/src/d/d_vib_pattern.cpp @@ -59,3 +59,30 @@ const vib_pattern dVibration_c::CQ_patt[VIBMODE_Q_MAX] = { /* VIBMODE_Q_POWER8 */ {4, 32, 0x6B6D6B6D}, /* VIBMODE_Q_HORSE */ {0, 27, 0x20201000}, }; + +const char* shock_names[VIBMODE_S_MAX] = { + "VIBMODE_S_CUSTOM", + "VIBMODE_S_POWER1", + "VIBMODE_S_POWER2", + "VIBMODE_S_POWER3", + "VIBMODE_S_POWER4", + "VIBMODE_S_POWER5", + "VIBMODE_S_POWER6", + "VIBMODE_S_POWER7", + "VIBMODE_S_POWER8", + "VIBMODE_S_DOKUTT", + "VIBMODE_S_FOR2D", +}; + +const char* quake_names[VIBMODE_Q_MAX] = { + "VIBMODE_Q_CUSTOM", + "VIBMODE_Q_POWER1", + "VIBMODE_Q_POWER2", + "VIBMODE_Q_POWER3", + "VIBMODE_Q_POWER4", + "VIBMODE_Q_POWER5", + "VIBMODE_Q_POWER6", + "VIBMODE_Q_POWER7", + "VIBMODE_Q_POWER8", + "VIBMODE_Q_HORSE", +}; diff --git a/src/d/d_vibration.cpp b/src/d/d_vibration.cpp index d3bd59c5cf..b43a1bdcd9 100644 --- a/src/d/d_vibration.cpp +++ b/src/d/d_vibration.cpp @@ -5,6 +5,7 @@ #include "f_op/f_op_camera_mng.h" #include "m_Do/m_Do_controller_pad.h" #include "SSystem/SComponent/c_math.h" +#include "d/d_s_play.h" #include #define RESET_FRAME -99 @@ -26,27 +27,30 @@ u16* makedata(u16* data, u32 pattern, s32 length) { s32 rollshift(u32 pattern, s32 length, s32 index) { index %= length; - return (pattern >> index) | (pattern << (length - index)); + u32 var_r31 = pattern; + return (var_r31 >> index) | (var_r31 << (length - index)); } u32 makebits(u32 bits, s32 length, s32 numbits) { + bits &= (-1 << (32 - length)); + u32 mask = bits; s32 i; - u32 mask = bits & (-1 << (32 - length)); - bits = mask; for (i = length; i < numbits; i += length) { - bits = mask | (bits >> length); + mask = bits | (mask >> length); } - return bits; + return mask; } u32 randombit(s32 rounds, s32 length) { u32 value = 0; + int i; - for (int i = 0; i < rounds; i++) { + for (i = 0; i < rounds; i++) { value |= 0x40000000 >> (u32)(length * cM_rnd()); } + return value; } }; @@ -65,10 +69,13 @@ int dVibration_c::Run() { mMotor.mQuake.field_0x0 |= 1; } - mMotor.mQuake.mFrame = RESET_FRAME; - mMotor.mShock.mFrame = RESET_FRAME; + mMotor.mShock.mFrame = mMotor.mQuake.mFrame = RESET_FRAME; } + #if DEBUG + testShake(); + #endif + if ((mCamera.mShock.field_0x0 & 1) && mCamera.mShock.mFrame != RESET_FRAME) { mCamera.mShock.mFrame = 0; mCamera.mShock.mVibMode = VIBMODE_S_NONE; @@ -80,6 +87,8 @@ int dVibration_c::Run() { } if (mCamera.mShock.mFrame == 0 || mCamera.mQuake.mFrame == 0) { + s32 pattern; + u32 sp1C = 0; u32 rumble = 0; if (mCamera.mShock.mVibMode == VIBMODE_S_NONE || (mCamera.mShock.field_0x0 & 1)) { mCamera.mShock.mFrame = RESET_FRAME; @@ -99,7 +108,7 @@ int dVibration_c::Run() { rumble |= RUMBLE_QUAKE; } - s32 length, pattern, bits, flags; + s32 length, bits, flags; switch (rumble) { case RUMBLE_SHOCK: length = mCamera.mShock.mLength; @@ -114,6 +123,12 @@ int dVibration_c::Run() { if (dComIfGp_getCamera(0) != NULL && dComIfGp_getCamera(0)->field_0x22f == 0x47) { dCam_getBody()->StartShake(length, (u8*)&pattern, flags, mCamera.mShock.mPos.norm()); } + + #if DEBUG + if (mVibTest.m_displayDbg & 0x8000) { + OS_REPORT("vibration: %06d: start camera(%d) %x %d\n", mFrame, rumble, pattern, length); + } + #endif break; case RUMBLE_QUAKE: length = mCamera.mQuake.mLength; @@ -128,11 +143,18 @@ int dVibration_c::Run() { if (dComIfGp_getCamera(0) != NULL && dComIfGp_getCamera(0)->field_0x22f == 0x47) { dCam_getBody()->StartShake(length, (u8*)&pattern, flags, mCamera.mQuake.mPos.norm()); } + + #if DEBUG + if (mVibTest.m_displayDbg & 0x8000) { + OS_REPORT("vibration: %06d: start camera(%d) %x %d\n", mFrame, rumble, pattern, length); + } + #endif break; case RUMBLE_SHOCK | RUMBLE_QUAKE: pattern = mCamera.mShock.mPattern << mCamera.mShock.mFrame; length = mCamera.mShock.mLength - mCamera.mShock.mFrame; - pattern |= rollshift(makebits(mCamera.mQuake.mPattern, mCamera.mQuake.mLength, length), length, mFrame); + sp1C = makebits(mCamera.mQuake.mPattern, mCamera.mQuake.mLength, length); + pattern |= rollshift(sp1C, length, mFrame); pattern |= randombit(mCamera.mShock.mRounds > mCamera.mQuake.mRounds ? mCamera.mShock.mRounds : mCamera.mQuake.mRounds, length); flags = mCamera.mShock.mFlags | mCamera.mQuake.mFlags; @@ -144,13 +166,24 @@ int dVibration_c::Run() { dCam_getBody()->StartShake(length, (u8*)&pattern, flags, cXyz(mCamera.mShock.mPos + mCamera.mQuake.mPos).norm()); } - mCamera.mQuake.mFrame = 0; - mCamera.mShock.mFrame = 0; + mCamera.mShock.mFrame = mCamera.mQuake.mFrame = 0; + + #if DEBUG + if (mVibTest.m_displayDbg & 0x8000) { + OS_REPORT("vibration: %06d: start camera(%d) %x %d\n", mFrame, rumble, pattern, length); + } + #endif break; default: if (dComIfGp_getCamera(0) != NULL && dComIfGp_getCamera(0)->field_0x22f == 0x47) { dCam_getBody()->StopShake(); } + + #if DEBUG + if (mVibTest.m_displayDbg & 0x8000) { + OS_REPORT("vibration: %06d: stop camera\n", mFrame); + } + #endif break; } } @@ -166,6 +199,7 @@ int dVibration_c::Run() { } if (mMotor.mShock.mFrame == 0 || mMotor.mQuake.mFrame == 0) { + s32 sp14 = 0; u32 rumble = 0; if (mMotor.mShock.mVibMode == VIBMODE_S_NONE || (mMotor.mShock.field_0x0 & 1)) { mMotor.mShock.mFrame = RESET_FRAME; @@ -194,39 +228,64 @@ int dVibration_c::Run() { pattern = mMotor.mShock.mPattern; pattern |= randombit(mMotor.mShock.mRounds, length); mMotor.mShock.mStopFrame = length; - mDoCPd_c::startMotorWave(PAD_1, makedata(data, pattern, length), JUTGamePad::CRumble::VAL_0, 60); + mDoCPd_c::startMotorWave(PAD_1, (u8*)makedata(data, pattern, length), JUTGamePad::CRumble::VAL_0, 60); + + #if DEBUG + if (mVibTest.m_displayDbg & 0x8000) { + OS_REPORT("vibration: %06d: start motor (%d) %x %d\n", mFrame, rumble, pattern, length); + } + #endif break; case RUMBLE_QUAKE: length = mMotor.mQuake.mLength; pattern = rollshift(mMotor.mQuake.mPattern, length, mFrame); pattern |= randombit(mMotor.mQuake.mRounds, length); mMotor.mQuake.mStopFrame = INT_MAX; - mDoCPd_c::startMotorWave(PAD_1, makedata(data, pattern, length), JUTGamePad::CRumble::VAL_1, 60); + + OS_REPORT("d_vibration mDoCPd_c::startMotorWave\n"); + + mDoCPd_c::startMotorWave(PAD_1, (u8*)makedata(data, pattern, length), JUTGamePad::CRumble::VAL_1, 60); + + #if DEBUG + if (mVibTest.m_displayDbg & 0x8000) { + OS_REPORT("vibration: %06d: start motor (%d) %x %d\n", mFrame, rumble, pattern, length); + } + #endif break; case RUMBLE_SHOCK | RUMBLE_QUAKE: pattern = mMotor.mShock.mPattern << mMotor.mShock.mFrame; length = mMotor.mShock.mLength - mMotor.mShock.mFrame; - pattern |= rollshift(makebits(mMotor.mQuake.mPattern, mMotor.mQuake.mLength, length), length, mFrame); + sp14 = makebits(mMotor.mQuake.mPattern, mMotor.mQuake.mLength, length); + pattern |= rollshift(sp14, length, mFrame); pattern |= randombit(mMotor.mShock.mRounds > mMotor.mQuake.mRounds ? mMotor.mShock.mRounds : mMotor.mQuake.mRounds, length); mMotor.mQuake.mStopFrame = length; mMotor.mShock.mStopFrame = length; - mMotor.mQuake.mFrame = 0; - mMotor.mShock.mFrame = 0; - mDoCPd_c::startMotorWave(PAD_1, makedata(data, pattern, length), JUTGamePad::CRumble::VAL_0, 60); + mMotor.mShock.mFrame = mMotor.mQuake.mFrame = 0; + + mDoCPd_c::startMotorWave(PAD_1, (u8*)makedata(data, pattern, length), JUTGamePad::CRumble::VAL_0, 60); + + #if DEBUG + if (mVibTest.m_displayDbg & 0x8000) { + OS_REPORT("vibration: %06d: start motor (%d) %x %d\n", mFrame, rumble, pattern, length); + } + #endif break; default: mDoCPd_c::stopMotorWave(PAD_1); mDoCPd_c::stopMotor(PAD_1); - mMotor.mQuake.mStopFrame = RESET_FRAME; - mMotor.mShock.mStopFrame = RESET_FRAME; + mMotor.mShock.mStopFrame = mMotor.mQuake.mStopFrame = RESET_FRAME; + + #if DEBUG + if (mVibTest.m_displayDbg & 0x8000) { + OS_REPORT("vibration: %06d: stop motor\n", mFrame); + } + #endif break; } } - mCamera.mQuake.field_0x0 = 0; - mCamera.mShock.field_0x0 = 0; - mMotor.mQuake.field_0x0 = 0; - mMotor.mShock.field_0x0 = 0; + mCamera.mShock.field_0x0 = mCamera.mQuake.field_0x0 = 0; + mMotor.mShock.field_0x0 = mMotor.mQuake.field_0x0 = 0; if (mCamera.mShock.mFrame >= 0) { mCamera.mShock.mFrame++; @@ -254,9 +313,22 @@ int dVibration_c::Run() { if (mMotor.mQuake.mFrame >= 930) { mMotor.mQuake.mFrame = 0; + + #if DEBUG + if (mVibTest.m_displayDbg & 0x8000) { + OS_REPORT("vibration: %06d: stop motor @ limit restart\n", mFrame); + } + #endif } else if (mMotor.mQuake.mFrame >= 900) { mDoCPd_c::stopMotorWave(PAD_1); mDoCPd_c::stopMotor(PAD_1); + + #if DEBUG + if ((mVibTest.m_displayDbg & 0x8000) && mMotor.mQuake.mFrame == 900) { + OS_REPORT("vibration: %06d: stop motor @ limit\n", mFrame); + } + #endif + mMotor.mQuake.mFrame++; } else if (mMotor.mQuake.mFrame >= 0) { mMotor.mQuake.mFrame++; @@ -292,6 +364,12 @@ bool dVibration_c::StartShock(int i_vibmode, int i_flags, cXyz i_pos) { ret = true; } + #if DEBUG + if ((mVibTest.m_displayDbg & 0x8000) && ret) { + OS_REPORT("vibration: %06d: start shock %d %d\n", mFrame, i_vibmode, i_flags); + } + #endif + return ret; } @@ -317,6 +395,12 @@ bool dVibration_c::StartQuake(int i_vibmode, int i_flags, cXyz i_pos) { ret = true; } + #if DEBUG + if ((mVibTest.m_displayDbg & 0x8000) && ret) { + OS_REPORT("vibration: %06d: start quake %d %d\n", mFrame, i_vibmode, i_flags); + } + #endif + return ret; } @@ -349,6 +433,12 @@ bool dVibration_c::StartQuake(const u8* i_pattern, int i_rounds, int i_flags, cX ret = true; } + #if DEBUG + if ((mVibTest.m_displayDbg & 0x8000) && ret) { + OS_REPORT("vibration: %06d: start quake %x %d %d\n", mFrame, bits, i_rounds, i_flags); + } + #endif + return ret; } @@ -369,9 +459,114 @@ int dVibration_c::StopQuake(int i_flags) { ret = TRUE; } + #if DEBUG + if ((mVibTest.m_displayDbg & 0x8000) && ret) { + OS_REPORT("vibration: %06d: stop quake %d\n", mFrame, i_flags); + } + #endif + return ret; } +#if DEBUG +int dVibration_c::testShake() { + int var_r29, var_r28; + int ret = FALSE; + s32 cam_bits, cam_len, cam_rounds; + s32 motor_bits, motor_len, motor_rounds; + + switch (mVibTest.field_0x10) { + case 10: + if (mVibTest.m_vibswitch >= 1 && mVibTest.m_vibswitch < 100) { + var_r29 = mVibTest.m_vibswitch; + cam_bits = CS_patt[var_r29].bits; + cam_len = CS_patt[var_r29].length; + cam_rounds = CS_patt[var_r29].rounds; + + motor_bits = MS_patt[var_r29].bits; + motor_len = MS_patt[var_r29].length; + motor_rounds = MS_patt[var_r29].rounds; + + OS_REPORT("vibration: TEST C b %x l %d r %d\n", cam_bits, cam_len, cam_rounds); + OS_REPORT("(SHOCK) m %2d M b %x l %d r %d\n", motor_bits, motor_len, motor_rounds); + } else { + motor_bits = cam_bits = (mVibTest.m_pattern << 0x10) | mVibTest.m_pattern2; + motor_len = cam_len = mVibTest.m_length; + motor_rounds = cam_rounds = mVibTest.m_randombit; + } + + if (mVibTest.field_0xa & 0x7E) { + mCamera.mShock.mVibMode = 0; + mCamera.mShock.mFrame = 0; + mCamera.mShock.mFlags = mVibTest.field_0xa; + mCamera.mShock.mPos = cXyz(0.0f, 1.0f, 0.0f); + mCamera.mShock.mPattern = cam_bits; + mCamera.mShock.mLength = cam_len; + mCamera.mShock.mRounds = cam_rounds; + } + + if (mVibTest.field_0xa & 1) { + mMotor.mShock.mVibMode = 0; + mMotor.mShock.mFrame = 0; + mMotor.mShock.mPattern = motor_bits; + mMotor.mShock.mLength = motor_len; + mMotor.mShock.mRounds = motor_rounds; + } + + mVibTest.field_0x10 = 0; + ret = TRUE; + break; + case 20: + if (mVibTest.m_vibswitch >= 100 && mVibTest.m_vibswitch < 200) { + var_r28 = mVibTest.m_vibswitch - 100; + cam_bits = CQ_patt[var_r28].bits; + cam_len = CQ_patt[var_r28].length; + cam_rounds = CQ_patt[var_r28].rounds; + + motor_bits = MQ_patt[var_r28].bits; + motor_len = MQ_patt[var_r28].length; + motor_rounds = MQ_patt[var_r28].rounds; + + OS_REPORT("vibration: TEST C b %x l %d r %d\n", cam_bits, cam_len, cam_rounds); + OS_REPORT("(QUAKE) m %2d M b %x l %d r %d\n", motor_bits, motor_len, motor_rounds); + } else { + motor_bits = cam_bits = (mVibTest.m_pattern << 0x10) | mVibTest.m_pattern2; + motor_len = cam_len = mVibTest.m_length; + motor_rounds = cam_rounds = mVibTest.m_randombit; + } + + if (mVibTest.field_0xa & 0x7E) { + mCamera.mQuake.mVibMode = 0; + mCamera.mQuake.mFrame = 0; + mCamera.mQuake.mFlags = mVibTest.field_0xa; + mCamera.mQuake.mPos = cXyz(0.0f, 1.0f, 0.0f); + mCamera.mQuake.mPattern = cam_bits; + mCamera.mQuake.mLength = cam_len; + mCamera.mQuake.mRounds = cam_rounds; + } + + if (mVibTest.field_0xa & 1) { + mMotor.mQuake.mVibMode = 0; + mMotor.mQuake.mFrame = 0; + mMotor.mQuake.mPattern = motor_bits; + mMotor.mQuake.mLength = motor_len; + mMotor.mQuake.mRounds = motor_rounds; + } + + mVibTest.field_0x10 = 0; + ret = TRUE; + break; + case 21: + OS_REPORT("vibration: TEST STOP\n"); + StopQuake(0x1F); + mVibTest.field_0x10 = 0; + break; + } + + return ret; +} +#endif + void dVibration_c::Kill() { mDoCPd_c::stopMotorWaveHard(PAD_1); mDoCPd_c::stopMotorHard(PAD_1); @@ -383,30 +578,18 @@ bool dVibration_c::CheckQuake() { } void dVibration_c::setDefault() { - mMotor.mShock.mVibMode = VIBMODE_S_NONE; - mCamera.mShock.mVibMode = VIBMODE_S_NONE; - mMotor.mQuake.mVibMode = VIBMODE_Q_NONE; - mCamera.mQuake.mVibMode = VIBMODE_Q_NONE; - mMotor.mShock.field_0x0 = 0; - mCamera.mShock.field_0x0 = 0; - mMotor.mQuake.field_0x0 = 0; - mCamera.mQuake.field_0x0 = 0; - mMotor.mShock.mPattern = 0; - mCamera.mShock.mPattern = 0; - mMotor.mQuake.mPattern = 0; - mCamera.mQuake.mPattern = 0; - mMotor.mShock.mLength = 0; - mCamera.mShock.mLength = 0; - mMotor.mQuake.mLength = 0; - mCamera.mQuake.mLength = 0; - mMotor.mShock.mRounds = 0; - mCamera.mShock.mRounds = 0; - mMotor.mQuake.mRounds = 0; - mCamera.mQuake.mRounds = 0; - mMotor.mShock.mFrame = RESET_FRAME; - mCamera.mShock.mFrame = RESET_FRAME; - mMotor.mQuake.mFrame = RESET_FRAME; - mCamera.mQuake.mFrame = RESET_FRAME; + mCamera.mShock.mVibMode = mMotor.mShock.mVibMode = VIBMODE_S_NONE; + mCamera.mQuake.mVibMode = mMotor.mQuake.mVibMode = VIBMODE_Q_NONE; + mCamera.mShock.field_0x0 = mMotor.mShock.field_0x0 = 0; + mCamera.mQuake.field_0x0 = mMotor.mQuake.field_0x0 = 0; + mCamera.mShock.mPattern = mMotor.mShock.mPattern = 0; + mCamera.mQuake.mPattern = mMotor.mQuake.mPattern = 0; + mCamera.mShock.mLength = mMotor.mShock.mLength = 0; + mCamera.mQuake.mLength = mMotor.mQuake.mLength = 0; + mCamera.mShock.mRounds = mMotor.mShock.mRounds = 0; + mCamera.mQuake.mRounds = mMotor.mQuake.mRounds = 0; + mCamera.mShock.mFrame = mMotor.mShock.mFrame = RESET_FRAME; + mCamera.mQuake.mFrame = mMotor.mQuake.mFrame = RESET_FRAME; mMotor.mShock.mStopFrame = RESET_FRAME; mMotor.mQuake.mStopFrame = RESET_FRAME; mMode = MODE_WAIT; @@ -416,6 +599,10 @@ void dVibration_c::setDefault() { void dVibration_c::Init() { Kill(); setDefault(); + + #if DEBUG + mVibTest.Init(); + #endif } void dVibration_c::Pause() { @@ -425,10 +612,8 @@ void dVibration_c::Pause() { mDoCPd_c::stopMotorHard(PAD_1); } - mMotor.mShock.mVibMode = VIBMODE_S_NONE; - mCamera.mShock.mVibMode = VIBMODE_S_NONE; - mMotor.mShock.mFrame = RESET_FRAME; - mCamera.mShock.mFrame = RESET_FRAME; + mCamera.mShock.mVibMode = mMotor.mShock.mVibMode = VIBMODE_S_NONE; + mCamera.mShock.mFrame = mMotor.mShock.mFrame = RESET_FRAME; if (mCamera.mQuake.mVibMode != VIBMODE_Q_NONE) { mCamera.mQuake.mFrame = 0; @@ -444,3 +629,133 @@ void dVibration_c::Pause() { void dVibration_c::Remove() { Kill(); } + +#if DEBUG +void dVibTest_c::setDefault() { + m_pattern = m_pattern2 = 0; + field_0xa = 0; + field_0x10 = 0; + m_randombit = 0; + m_vibswitch = 0; + m_displayDbg = 0; + m_length = 32; +} + +dVibTest_c::dVibTest_c() { + setDefault(); + id = mDoHIO_CREATE_CHILD("振動処理", this); +} + +dVibTest_c::~dVibTest_c() { + mDoHIO_DELETE_CHILD(id); +} + +void dVibTest_c::Init() { + setDefault(); +} + +void dVibTest_c::genMessage(JORMContext* mctx) { + int i; + + mctx->genLabel("- パターン", 0, 0); + mctx->genCheckBox(" ", &m_pattern, 0x8000, 0, NULL, 10, 0x14, 0x14, 0x14); + mctx->genCheckBox(" ", &m_pattern, 0x4000, 0, NULL, 0x1e, 0x14, 0x14, 0x14); + mctx->genCheckBox(" ", &m_pattern, 0x2000, 0, NULL, 0x32, 0x14, 0x14, 0x14); + mctx->genCheckBox(" ", &m_pattern, 0x1000, 0, NULL, 0x46, 0x14, 0x14, 0x14); + mctx->genCheckBox(" ", &m_pattern, 0x800, 0, NULL, 0x5a, 0x14, 0x14, 0x14); + mctx->genCheckBox(" ", &m_pattern, 0x400, 0, NULL, 0x6e, 0x14, 0x14, 0x14); + mctx->genCheckBox(" ", &m_pattern, 0x200, 0, NULL, 0x82, 0x14, 0x14, 0x14); + mctx->genCheckBox(" ", &m_pattern, 0x100, 0, NULL, 0x96, 0x14, 0x14, 0x14); + mctx->genCheckBox(" ", &m_pattern, 0x80, 0, NULL, 0xaa, 0x14, 0x14, 0x14); + mctx->genCheckBox(" ", &m_pattern, 0x40, 0, NULL, 0xbe, 0x14, 0x14, 0x14); + mctx->genCheckBox(" ", &m_pattern, 0x20, 0, NULL, 0xd2, 0x14, 0x14, 0x14); + mctx->genCheckBox(" ", &m_pattern, 0x10, 0, NULL, 0xe6, 0x14, 0x14, 0x14); + mctx->genCheckBox(" ", &m_pattern, 8, 0, NULL, 0xfa, 0x14, 0x14, 0x14); + mctx->genCheckBox(" ", &m_pattern, 4, 0, NULL, 0x10e, 0x14, 0x14, 0x14); + mctx->genCheckBox(" ", &m_pattern, 2, 0, NULL, 0x122, 0x14, 0x14, 0x14); + mctx->genCheckBox(" ", &m_pattern, 1, 0, NULL, 0x136, 0x14, 0x14, 0x14); + mctx->genCheckBox(" ", &m_pattern2, 0x8000, 0, NULL, 0x14a, 0x14, 0x14, 0x14); + mctx->genCheckBox(" ", &m_pattern2, 0x4000, 0, NULL, 0x15e, 0x14, 0x14, 0x14); + mctx->genCheckBox(" ", &m_pattern2, 0x2000, 0, NULL, 0x172, 0x14, 0x14, 0x14); + mctx->genCheckBox(" ", &m_pattern2, 0x1000, 0, NULL, 0x186, 0x14, 0x14, 0x14); + mctx->genCheckBox(" ", &m_pattern2, 0x800, 0, NULL, 0x19a, 0x14, 0x14, 0x14); + mctx->genCheckBox(" ", &m_pattern2, 0x400, 0, NULL, 0x1ae, 0x14, 0x14, 0x14); + mctx->genCheckBox(" ", &m_pattern2, 0x200, 0, NULL, 0x1c2, 0x14, 0x14, 0x14); + mctx->genCheckBox(" ", &m_pattern2, 0x100, 0, NULL, 0x1d6, 0x14, 0x14, 0x14); + mctx->genCheckBox(" ", &m_pattern2, 0x80, 0, NULL, 0x1ea, 0x14, 0x14, 0x14); + mctx->genCheckBox(" ", &m_pattern2, 0x40, 0, NULL, 0x1fe, 0x14, 0x14, 0x14); + mctx->genCheckBox(" ", &m_pattern2, 0x20, 0, NULL, 0x212, 0x14, 0x14, 0x14); + mctx->genCheckBox(" ", &m_pattern2, 0x10, 0, NULL, 0x226, 0x14, 0x14, 0x14); + mctx->genCheckBox(" ", &m_pattern2, 8, 0, NULL, 0x23a, 0x14, 0x14, 0x14); + mctx->genCheckBox(" ", &m_pattern2, 4, 0, NULL, 0x24e, 0x14, 0x14, 0x14); + mctx->genCheckBox(" ", &m_pattern2, 2, 0, NULL, 0x262, 0x14, 0x14, 0x14); + mctx->genCheckBox(" ", &m_pattern2, 1, 0, NULL, 0x276, 0x14, 0x14, 0x14); + + mctx->genLabel("-", 0, 0); + mctx->startComboBox("振動切替", &m_vibswitch, 0x130); + for (i = 0; i < 11; i++) { + mctx->genComboBoxItem(shock_names[i], i); + } + for (i = 0; i < 10; i++) { + mctx->genComboBoxItem(quake_names[i], i + 100); + } + mctx->endComboBox(); + + mctx->genLabel("- VIBMODE_S_* は衝撃型用、VIBMODE_Q_* は地震型用です", 0); + mctx->genLabel("-", 0); + + mctx->genLabel("- タイプ", 0); + mctx->genCheckBox("振動パック", &field_0xa, 1, 0); + mctx->genCheckBox("座標揺れ", &field_0xa, 2, 0); + mctx->genCheckBox("座標揺れ(強)", &field_0xa, 0x40, 0); + mctx->genCheckBox("画角揺れ", &field_0xa, 4, 0); + mctx->genCheckBox("Z揺れ", &field_0xa, 8, 0); + mctx->genCheckBox("ブラー", &field_0xa, 0x10, 0); + + mctx->genLabel("-", 0); + mctx->genSlider("長さ", &m_length, 0, 0x20); + mctx->genSlider("ランダムビット", &m_randombit, 0, 0x20); + + mctx->genLabel("-", 0); + mctx->genButton("衝撃型揺れ開始", 0x12d, 0); + mctx->genButton("地震型揺れ開始", 0x12e, 0); + mctx->genButton("地震型揺れ停止", 0x12f, 0); + + mctx->genLabel("-", 0, 0); + mctx->genCheckBox(" デバッグ表示", &m_displayDbg, 0x8000); + + mctx->genLabel("-", 0, 0); + mctx->genLabel("-", 0, 0); + mctx->genButton("揺れ実験(強)", 0x131, 0); + mctx->genButton("揺れ実験(中)", 0x132, 0); + mctx->genButton("揺れ実験(弱)", 0x133, 0); + + mctx->genLabel("-", 0, 0); + mctx->genLabel("-", 0, 0); +} + +void dVibTest_c::listenPropertyEvent(const JORPropertyEvent* event) { + JORReflexible::listenPropertyEvent(event); + + switch ((int)event->id) { + case 0x12D: + field_0x10 = 10; + break; + case 0x12E: + field_0x10 = 20; + break; + case 0x12F: + field_0x10 = 21; + break; + case 0x131: + dComIfGp_getVibration().StartShock(XREG_S(1) + 8, 0x1F, cXyz(0.0f, 1.0f, 0.0f)); + break; + case 0x132: + dComIfGp_getVibration().StartShock(XREG_S(2) + 4, 0x1F, cXyz(0.0f, 1.0f, 0.0f)); + break; + case 0x133: + dComIfGp_getVibration().StartShock(XREG_S(3) + 2, 0x1F, cXyz(0.0f, 1.0f, 0.0f)); + break; + } +} +#endif From 0ce0182f50fb4c5b96a99a1e6dc6fa097966a049 Mon Sep 17 00:00:00 2001 From: Max Roncace Date: Sun, 29 Mar 2026 22:23:44 -0400 Subject: [PATCH 125/350] Fix Clang compile error due to introduction of PCH --- include/Z2AudioLib/Z2Instances.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/Z2AudioLib/Z2Instances.h b/include/Z2AudioLib/Z2Instances.h index 0f1a02cdb1..b8e6af7a3e 100644 --- a/include/Z2AudioLib/Z2Instances.h +++ b/include/Z2AudioLib/Z2Instances.h @@ -6,6 +6,9 @@ #include "JSystem/JAudio2/JASAudioThread.h" #include "JSystem/JAudio2/JAUSoundTable.h" +#if TARGET_PC +#define AUDIO_INSTANCES +#else #define AUDIO_INSTANCES \ template<> JASDefaultBankTable* JASGlobalInstance::sInstance; \ template<> JASAudioThread* JASGlobalInstance::sInstance; \ @@ -32,5 +35,6 @@ template<> Z2EnvSeMgr* JASGlobalInstance::sInstance; \ template<> Z2SpeechMgr* JASGlobalInstance::sInstance; \ template<> Z2WolfHowlMgr* JASGlobalInstance::sInstance; +#endif #endif From 84e56cf4e33808163411f34093a9dc58eb0f9866 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Sun, 29 Mar 2026 23:02:49 -0600 Subject: [PATCH 126/350] Increase shadow resolution --- src/d/d_drawlist.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/d/d_drawlist.cpp b/src/d/d_drawlist.cpp index ee6b01f9fd..410c7ff477 100644 --- a/src/d/d_drawlist.cpp +++ b/src/d/d_drawlist.cpp @@ -1398,7 +1398,11 @@ void dDlst_shadowSimple_c::set(cXyz* param_0, f32 param_1, f32 param_2, cXyz* pa } void dDlst_shadowControl_c::init() { +#if TARGET_PC + static u16 l_realImageSize[2] = {512, 256}; +#else static u16 l_realImageSize[2] = {192, 64}; +#endif for (int i = 0; i < 2; i++) { u16 size = l_realImageSize[i]; From 6f4695506f3e1155436138bdfde537e3be4364fb Mon Sep 17 00:00:00 2001 From: Luke Street Date: Sun, 29 Mar 2026 23:08:53 -0600 Subject: [PATCH 127/350] Avoid allocating RAM for shadow textures --- src/d/d_drawlist.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/d/d_drawlist.cpp b/src/d/d_drawlist.cpp index 410c7ff477..69937c264c 100644 --- a/src/d/d_drawlist.cpp +++ b/src/d/d_drawlist.cpp @@ -1406,7 +1406,12 @@ void dDlst_shadowControl_c::init() { for (int i = 0; i < 2; i++) { u16 size = l_realImageSize[i]; +#ifdef TARGET_PC + // Don't need to allocate an actual texture buffer for aurora; only a placeholder + u32 buffer_size = 0x20; +#else u32 buffer_size = GXGetTexBufferSize(size, size, 5, GX_DISABLE, 0); +#endif field_0x15ef0[i] = JKR_NEW_ARRAY_ARGS(u8, buffer_size, 0x20); GXInitTexObj(&field_0x15eb0[i], field_0x15ef0[i], size, size, GX_TF_RGB5A3, GX_CLAMP, GX_CLAMP, GX_DISABLE); @@ -1640,7 +1645,7 @@ int dDlst_shadowControl_c::setReal(u32 param_1, s8 param_2, J3DModel* param_3, c u32 rv = pdVar12->set(mNextID, param_3, param_4, param_5, param_6, param_7, dVar17, dVar16); if (!rv) { return 0; - } + } mRealNum++; if (pdVar10 == NULL) { if (pdVar11 == NULL) { From a9dbfc5ddd050ce25d87479b2f10a8b9b9ab34de Mon Sep 17 00:00:00 2001 From: Irastris Date: Mon, 30 Mar 2026 02:25:52 -0400 Subject: [PATCH 128/350] Update Aurora submodule --- extern/aurora | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/aurora b/extern/aurora index beb6fdbbbc..507fda8591 160000 --- a/extern/aurora +++ b/extern/aurora @@ -1 +1 @@ -Subproject commit beb6fdbbbc08e672d9c23db1881800420d57e883 +Subproject commit 507fda8591f2c896cf7f0bd4c51bb05ecc0cccf6 From 0319eaa7b8d3729dd51e9db0aeda231c4d3611c9 Mon Sep 17 00:00:00 2001 From: Irastris Date: Mon, 30 Mar 2026 02:26:12 -0400 Subject: [PATCH 129/350] Revert "Increase shadow resolution" --- src/d/d_drawlist.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/d/d_drawlist.cpp b/src/d/d_drawlist.cpp index 69937c264c..9a37846dcb 100644 --- a/src/d/d_drawlist.cpp +++ b/src/d/d_drawlist.cpp @@ -1398,11 +1398,7 @@ void dDlst_shadowSimple_c::set(cXyz* param_0, f32 param_1, f32 param_2, cXyz* pa } void dDlst_shadowControl_c::init() { -#if TARGET_PC - static u16 l_realImageSize[2] = {512, 256}; -#else static u16 l_realImageSize[2] = {192, 64}; -#endif for (int i = 0; i < 2; i++) { u16 size = l_realImageSize[i]; From 4d10b9b868c8894a6eefb6ea7689c29c54ebfac0 Mon Sep 17 00:00:00 2001 From: TakaRikka Date: Mon, 30 Mar 2026 00:08:07 -0700 Subject: [PATCH 130/350] fix flowers --- include/d/actor/d_flower.h | 5 +++++ include/d/actor/d_grass.h | 4 ++-- src/d/actor/d_a_grass.cpp | 7 +----- src/d/actor/d_flower.inc | 44 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 52 insertions(+), 8 deletions(-) diff --git a/include/d/actor/d_flower.h b/include/d/actor/d_flower.h index 92bdad59a2..cd505335a8 100644 --- a/include/d/actor/d_flower.h +++ b/include/d/actor/d_flower.h @@ -103,6 +103,11 @@ public: /* 0x12A48 */ u32 m_Jhana01DL_size; /* 0x12A4C */ u8* mp_Jhana01_cDL; /* 0x12A50 */ u32 m_Jhana01_cDL_size; + +#if TARGET_PC + TGXTexObj mTexObj_l_J_Ohana00_64TEX; + TGXTexObj mTexObj_l_J_Ohana01_64128_0419TEX; +#endif }; // Size: 0x12A54 #endif /* D_FLOWER_H */ diff --git a/include/d/actor/d_grass.h b/include/d/actor/d_grass.h index 5a152ed478..47b948679d 100644 --- a/include/d/actor/d_grass.h +++ b/include/d/actor/d_grass.h @@ -108,8 +108,8 @@ public: /* 0x1D714 */ s16 field_0x1d714; #if TARGET_PC - GXTexObj mTexObj_l_M_Hijiki00TEX{}; - GXTexObj mTexObj_l_M_kusa05_RGBATEX{}; + TGXTexObj mTexObj_l_M_Hijiki00TEX; + TGXTexObj mTexObj_l_M_kusa05_RGBATEX; #endif }; // Size: 0x1D718 diff --git a/src/d/actor/d_a_grass.cpp b/src/d/actor/d_a_grass.cpp index 1668386edf..f04d5cfcd2 100644 --- a/src/d/actor/d_a_grass.cpp +++ b/src/d/actor/d_a_grass.cpp @@ -80,12 +80,7 @@ void daGrass_c::deleteGrass() { } } -dGrass_packet_c::~dGrass_packet_c() { -#if TARGET_PC - GXDestroyTexObj(&mTexObj_l_M_kusa05_RGBATEX); - GXDestroyTexObj(&mTexObj_l_M_Hijiki00TEX); -#endif -} +dGrass_packet_c::~dGrass_packet_c() {} void daGrass_c::executeGrass() { if (m_grass != NULL) { diff --git a/src/d/actor/d_flower.inc b/src/d/actor/d_flower.inc index 13bc1ad251..0018a79960 100644 --- a/src/d/actor/d_flower.inc +++ b/src/d/actor/d_flower.inc @@ -509,6 +509,42 @@ dFlower_packet_c::dFlower_packet_c() { unused += 0x2000; } +#if TARGET_LITTLE_ENDIAN + static bool initialized = false; + if (!initialized) { + for (int i = 0; i < (ARRAY_SIZE(l_flowerPos) / sizeof(Vec)); i++) { + be_swap(((Vec*)l_flowerPos)[i]); + } + for (int i = 0; i < (ARRAY_SIZE(l_flowerTexCoord) / sizeof(Vec)); i++) { + be_swap(((Vec*)l_flowerTexCoord)[i]); + } + for (int i = 0; i < (ARRAY_SIZE(l_flowerPos2) / sizeof(Vec)); i++) { + be_swap(((Vec*)l_flowerPos2)[i]); + } + for (int i = 0; i < (ARRAY_SIZE(l_flowerTexCoord2) / sizeof(Vec)); i++) { + be_swap(((Vec*)l_flowerTexCoord2)[i]); + } + for (int i = 0; i < (ARRAY_SIZE(l_flowerNormal) / sizeof(Vec)); i++) { + be_swap(((Vec*)l_flowerNormal)[i]); + } + for (int i = 0; i < (ARRAY_SIZE(l_flowerNormal2) / sizeof(Vec)); i++) { + be_swap(((Vec*)l_flowerNormal2)[i]); + } + + initialized = true; + } +#endif + +#if TARGET_PC + GXInitTexObj(&mTexObj_l_J_Ohana00_64TEX, l_J_Ohana00_64TEX, + l_J_Ohana00_64TEX__width, l_J_Ohana00_64TEX__height, GX_TF_CMPR, GX_MIRROR, GX_MIRROR, GX_FALSE + ); + + GXInitTexObj(&mTexObj_l_J_Ohana01_64128_0419TEX, l_J_Ohana01_64128_0419TEX, + l_J_Ohana01_64128_0419TEX__width, l_J_Ohana01_64128_0419TEX__height, GX_TF_CMPR, GX_MIRROR, GX_MIRROR, GX_FALSE + ); +#endif + m_deleteRoom = &dFlower_packet_c::deleteRoom; #if AVOID_UB @@ -615,6 +651,10 @@ void dFlower_packet_c::draw() { GXLoadPosMtxImm(sp44->m_modelMtx, 0); GXLoadNrmMtxImm(j3dSys.getViewMtx(), 0); +#if TARGET_PC + GXLoadTexObj(&mTexObj_l_J_Ohana00_64TEX, GX_TEXMAP0); +#endif + if (!cLib_checkBit(sp44->m_state, 8)) { GXCallDisplayList(&l_J_hana00DL, 0x140); } else { @@ -757,6 +797,10 @@ void dFlower_packet_c::draw() { GXLoadPosMtxImm(sp34->m_modelMtx, 0); GXLoadNrmMtxImm(j3dSys.getViewMtx(), 0); +#if TARGET_PC + GXLoadTexObj(&mTexObj_l_J_Ohana01_64128_0419TEX, GX_TEXMAP0); +#endif + if (!cLib_checkBit(sp34->m_state, 8)) { if (!cLib_checkBit(sp34->m_state, 0x10)) { GXCallDisplayList(mp_Jhana01DL, m_Jhana01DL_size); From 82a1b02060da80cb2d5c9669da35bd3f90c2dbb3 Mon Sep 17 00:00:00 2001 From: TakaRikka Date: Mon, 30 Mar 2026 00:18:57 -0700 Subject: [PATCH 131/350] bg obj sfx fix --- src/d/actor/d_a_bg_obj.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/d/actor/d_a_bg_obj.cpp b/src/d/actor/d_a_bg_obj.cpp index 4c7fb4d03e..7d4adb9e47 100644 --- a/src/d/actor/d_a_bg_obj.cpp +++ b/src/d/actor/d_a_bg_obj.cpp @@ -976,7 +976,7 @@ void daBgObj_c::setSe() { temp++; for (; i != 0; i--) { - fopAcM_seStart(this, *temp, 0); + fopAcM_seStart(this, *(BE(u32)*)temp, 0); temp++; } } From 3f2bd02ae09876bdc72b70a67c765105dde94e3d Mon Sep 17 00:00:00 2001 From: Lurs Date: Mon, 30 Mar 2026 09:33:54 +0200 Subject: [PATCH 132/350] Fix softlock when entering Faron cave (and maybe others) in Release --- src/d/actor/d_a_e_fz.cpp | 7 +++++++ src/d/actor/d_a_e_oc.cpp | 8 ++++++++ src/d/actor/d_a_obj_stick.cpp | 7 +++++++ src/d/actor/d_a_tag_magne.cpp | 8 ++++++++ 4 files changed, 30 insertions(+) diff --git a/src/d/actor/d_a_e_fz.cpp b/src/d/actor/d_a_e_fz.cpp index 6fa54a0ffe..6673ba1092 100644 --- a/src/d/actor/d_a_e_fz.cpp +++ b/src/d/actor/d_a_e_fz.cpp @@ -905,9 +905,16 @@ s32 daE_FZ_c::_delete() { return 1; } +#if TARGET_PC +static int daE_FZ_Delete(daE_FZ_c* i_this) { + i_this->_delete(); + return 1; +} +#else static void daE_FZ_Delete(daE_FZ_c* i_this) { i_this->_delete(); } +#endif s32 daE_FZ_c::CreateHeap() { J3DModelData* model_data = (J3DModelData*)dComIfG_getObjectRes("E_FZ", 3); diff --git a/src/d/actor/d_a_e_oc.cpp b/src/d/actor/d_a_e_oc.cpp index 01f282b0ea..63db111ce7 100644 --- a/src/d/actor/d_a_e_oc.cpp +++ b/src/d/actor/d_a_e_oc.cpp @@ -2662,10 +2662,18 @@ int daE_OC_c::_delete() { return 1; } +#if TARGET_PC +static int daE_OC_Delete(daE_OC_c* i_this) { + fopAcM_RegisterDeleteID(i_this, "E_OC"); + i_this->_delete(); + return 1; +} +#else static void daE_OC_Delete(daE_OC_c* i_this) { fopAcM_RegisterDeleteID(i_this, "E_OC"); i_this->_delete(); } +#endif int daE_OC_c::CreateHeap() { J3DModelData* modelData = (J3DModelData*) dComIfG_getObjectRes(mName, 0x3); diff --git a/src/d/actor/d_a_obj_stick.cpp b/src/d/actor/d_a_obj_stick.cpp index 2e448c1579..aa46f13716 100644 --- a/src/d/actor/d_a_obj_stick.cpp +++ b/src/d/actor/d_a_obj_stick.cpp @@ -202,9 +202,16 @@ static u32 daObj_Stick_Create(void* i_this) { return static_cast(i_this)->create(); } +#if TARGET_PC +static int daObj_Stick_Delete(void* param_0) { + static_cast(param_0)->Delete(); + return 1; +} +#else static void daObj_Stick_Delete(void* param_0) { static_cast(param_0)->Delete(); } +#endif static void daObj_Stick_Execute(void* param_0) { static_cast(param_0)->Execute(); diff --git a/src/d/actor/d_a_tag_magne.cpp b/src/d/actor/d_a_tag_magne.cpp index 8b47511eeb..9c15398463 100644 --- a/src/d/actor/d_a_tag_magne.cpp +++ b/src/d/actor/d_a_tag_magne.cpp @@ -44,10 +44,18 @@ int daTagMagne_c::_delete() { return 1; } +#if TARGET_PC +static int daTagMagne_Delete(daTagMagne_c* i_this) { + int id = fopAcM_GetID(i_this); + i_this->_delete(); + return 1; +} +#else static void daTagMagne_Delete(daTagMagne_c* i_this) { int id = fopAcM_GetID(i_this); i_this->_delete(); } +#endif static void daTagMagne_Create(fopAc_ac_c* i_this) { daTagMagne_c* magne = static_cast(i_this); From 5685fa58c619245cc21eb9a78d2b9e6ab4cc67aa Mon Sep 17 00:00:00 2001 From: TakaRikka <38417346+TakaRikka@users.noreply.github.com> Date: Mon, 30 Mar 2026 02:10:42 -0700 Subject: [PATCH 133/350] Z2Audio player debug work / fix actor method returns (#3140) * Z2Audio player debug work * fix actor method returns * wii build fix --- include/Z2AudioCS/SpkTable.h | 4 +- include/Z2AudioCS/Z2AudioCS.h | 2 +- include/Z2AudioLib/Z2SoundPlayer.h | 54 +- include/Z2AudioLib/Z2WaveArcLoader.h | 29 + include/d/actor/d_a_npc_sola.h | 2 +- .../JSystem/JAWExtSystem/JAWGraphContext.h | 9 + .../include/JSystem/JAWExtSystem/JAWWindow.h | 36 +- .../include/JSystem/JAudio2/JAISound.h | 8 + .../include/JSystem/JAudio2/JASHeapCtrl.h | 3 + .../JSystem/JAudio2/JASWaveArcLoader.h | 2 + .../include/JSystem/JAudio2/JAUSoundTable.h | 2 + .../include/JSystem/JSupport/JSUList.h | 2 + libs/JSystem/src/JAWExtSystem/JAWWindow.cpp | 62 +- src/Z2AudioCS/Z2AudioCS.cpp | 6 +- src/Z2AudioLib/Z2SeView.cpp | 0 src/Z2AudioLib/Z2SoundPlayer.cpp | 498 +++++++- src/Z2AudioLib/Z2TrackView.cpp | 0 src/Z2AudioLib/Z2WaveArcLoader.cpp | 335 ++++++ src/d/actor/d_a_e_fz.cpp | 1023 +++++++++-------- src/d/actor/d_a_e_oc.cpp | 20 +- src/d/actor/d_a_e_zs.cpp | 6 +- src/d/actor/d_a_myna.cpp | 6 +- src/d/actor/d_a_npc_sola.cpp | 13 +- src/d/actor/d_a_obj_Y_taihou.cpp | 16 +- src/d/actor/d_a_obj_fan.cpp | 4 +- src/d/actor/d_a_obj_inobone.cpp | 6 +- src/d/actor/d_a_obj_stick.cpp | 18 +- src/d/actor/d_a_peru.cpp | 76 +- src/d/actor/d_a_tag_magne.cpp | 8 +- src/d/d_cursor_mng.cpp | 4 +- 30 files changed, 1508 insertions(+), 746 deletions(-) create mode 100644 src/Z2AudioLib/Z2SeView.cpp create mode 100644 src/Z2AudioLib/Z2TrackView.cpp create mode 100644 src/Z2AudioLib/Z2WaveArcLoader.cpp diff --git a/include/Z2AudioCS/SpkTable.h b/include/Z2AudioCS/SpkTable.h index 8fc12bd735..862fdba3c1 100644 --- a/include/Z2AudioCS/SpkTable.h +++ b/include/Z2AudioCS/SpkTable.h @@ -18,10 +18,10 @@ public: SpkTable(void); void setResource(void* res); - inline s32 getName(s32 num) { + inline const char* getName(s32 num) { JUT_ASSERT(0x35, num >= 0); JUT_ASSERT(0x36, num < mNumOfSound); - return *(mDataOffsets + num); + return (const char*)*(mDataOffsets + num); } inline s32 getNumOfSound() const { return mNumOfSound; } inline bool isValid(void) const { return mIsInitialized; } diff --git a/include/Z2AudioCS/Z2AudioCS.h b/include/Z2AudioCS/Z2AudioCS.h index 7d13accabc..694218ab4f 100644 --- a/include/Z2AudioCS/Z2AudioCS.h +++ b/include/Z2AudioCS/Z2AudioCS.h @@ -19,7 +19,7 @@ public: static SpkSoundHandle* getHandleSoundID(s32 soundNum); static SpkSoundHandle* start(s32 id, s32 chan); static SpkSoundHandle* startLevel(s32 id, s32 chan); - static s32 getName(s32 num); + static const char* getName(s32 num); static s32 getNumOfSound(void); static void stopAll(s32 chan, s32 msec); static void stop(s32 chan); diff --git a/include/Z2AudioLib/Z2SoundPlayer.h b/include/Z2AudioLib/Z2SoundPlayer.h index 6937ba63af..3baf4c15b5 100644 --- a/include/Z2AudioLib/Z2SoundPlayer.h +++ b/include/Z2AudioLib/Z2SoundPlayer.h @@ -3,6 +3,7 @@ #include "JSystem/JAWExtSystem/JAWWindow.h" #include "JSystem/JAudio2/JAISoundHandles.h" +#include "JSystem/JAudio2/JAUSoundTable.h" class Z2SoundPlayer : public JAWWindow { public: @@ -24,43 +25,32 @@ public: virtual void onKeyRight(const JUTGamePad&); u32 getCursorMoveMax(const JUTGamePad&); + int getMenuNumberMax(); + void correctSeNumber(); - /* 0x3ED */ u8 field_0x3ed; - /* 0x3EE */ u8 field_0x3ee; - /* 0x3EF */ u8 field_0x3ef; - /* 0x3F0 */ u8 field_0x3f0; - /* 0x3F4 */ const char* field_0x3f4; - /* 0x3F8 */ short field_0x3f8; - /* 0x3FA */ short field_0x3fa; - /* 0x3FC */ short field_0x3fc; - /* 0x3FE */ short field_0x3fe; - /* 0x400 */ short field_0x400; - /* 0x402 */ short field_0x402; - /* 0x404 */ const char* field_0x404; - /* 0x408 */ const char* field_0x408; - /* 0x40C */ const char* field_0x40c; - /* 0x410 */ const char* field_0x410; - /* 0x414 */ const char* field_0x414; - /* 0x418 */ const char* field_0x418; - /* 0x41C */ const char* field_0x41c; - /* 0x420 */ const char* field_0x420; - /* 0x424 */ const char* field_0x424; - /* 0x428 */ const char* field_0x428; - /* 0x42C */ const char* field_0x42c; - /* 0x430 */ const char* field_0x430; - /* 0x434 */ const char* field_0x434; - /* 0x438 */ const char* field_0x438; - /* 0x43C */ const char* field_0x43c; - /* 0x440 */ const char* field_0x440; - /* 0x444 */ short field_0x444; + void onDrawSoundItem(JAWGraphContext*, JAUSoundNameTable*, int, const JUtility::TColor&, const JUtility::TColor&, const char*, u32, u32, u32); + + /* 0x3ED */ bool field_0x3ed; + /* 0x3EE */ bool field_0x3ee; + /* 0x3EF */ bool field_0x3ef; + /* 0x3F0 */ bool field_0x3f0; + /* 0x3F4 */ const char* m_name; + /* 0x3F8 */ s16 field_0x3f8; + /* 0x3FA */ s16 field_0x3fa; + /* 0x3FC */ s16 field_0x3fc; + /* 0x3FE */ s16 field_0x3fe; + /* 0x400 */ s16 field_0x400; + /* 0x402 */ s16 m_portNum; + /* 0x404 */ const char* m_portNames[16]; + /* 0x444 */ s16 m_portVal; /* 0x446 */ u8 field_0x446; - /* 0x448 */ short* field_0x448[7]; - /* 0x464 */ int field_0x464; - /* 0x468 */ int field_0x468; + /* 0x448 */ s16* field_0x448[7]; + /* 0x464 */ u32 m_cursorY; + /* 0x468 */ int m_cursorMax; /* 0x46C */ JAISoundHandle field_0x46c[8]; /* 0x48C */ JAISoundHandles field_0x48c; /* 0x494 */ JAISoundHandle field_0x494; - /* 0x498 */ JAISoundHandle* field_0x498; + /* 0x498 */ JAISoundHandle* mp_subBgmHandle; /* 0x49C */ JAISoundHandle field_0x49c; /* 0x4A0 */ int field_0x4a0; /* 0x4A4 */ f32 field_0x4a4; diff --git a/include/Z2AudioLib/Z2WaveArcLoader.h b/include/Z2AudioLib/Z2WaveArcLoader.h index 81a4bc2123..ac15c50001 100644 --- a/include/Z2AudioLib/Z2WaveArcLoader.h +++ b/include/Z2AudioLib/Z2WaveArcLoader.h @@ -2,10 +2,39 @@ #define Z2WAVEARCLOADER_H #include "JSystem/JAWExtSystem/JAWWindow.h" +#include "JSystem/JAudio2/JASWaveArcLoader.h" +#include "JSystem/JAudio2/JASWaveInfo.h" class Z2WaveArcLoader : public JAWWindow { public: Z2WaveArcLoader(); + virtual ~Z2WaveArcLoader(); + + virtual void onDraw(JAWGraphContext*); + + void checkWaveBank(); + void checkWaveArc(); + + virtual void onKeyUp(const JUTGamePad&); + virtual void onKeyDown(const JUTGamePad&); + virtual void onKeyLeft(const JUTGamePad&); + virtual void onKeyRight(const JUTGamePad&); + virtual void onTrigA(const JUTGamePad&); + virtual void onTrigB(const JUTGamePad&); + virtual void onTrigZ(const JUTGamePad&); + + /* 0x3F0 */ JASWaveBank* mpWaveBank; + /* 0x3F4 */ JASWaveArc* mpWaveArc; + /* 0x3F8 */ u32 mWaveUsedSize; + /* 0x3FC */ int mTotalUsedSize; + /* 0x400 */ u8 mBankNo; + /* 0x404 */ u32 mArcCount; + /* 0x408 */ u32 field_0x408; + /* 0x40C */ u32 field_0x40c; + /* 0x410 */ u32 field_0x410; + /* 0x414 */ u8 field_0x414; + /* 0x415 */ u8 field_0x415; + /* 0x416 */ bool mIsLoadTail; }; #endif /* Z2WAVEARCLOADER_H */ diff --git a/include/d/actor/d_a_npc_sola.h b/include/d/actor/d_a_npc_sola.h index 6d6cf742f8..0b13842f9c 100644 --- a/include/d/actor/d_a_npc_sola.h +++ b/include/d/actor/d_a_npc_sola.h @@ -77,7 +77,7 @@ public: int CreateHeap(); int Delete(); int Execute(); - void Draw(); + int Draw(); static BOOL createHeapCallBack(fopAc_ac_c*); static BOOL ctrlJointCallBack(J3DJoint*, int); bool getType(); diff --git a/libs/JSystem/include/JSystem/JAWExtSystem/JAWGraphContext.h b/libs/JSystem/include/JSystem/JAWExtSystem/JAWGraphContext.h index 2719500e1c..99fac05626 100644 --- a/libs/JSystem/include/JSystem/JAWExtSystem/JAWGraphContext.h +++ b/libs/JSystem/include/JSystem/JAWExtSystem/JAWGraphContext.h @@ -24,6 +24,15 @@ public: void setGXforPrint(); void setGXforDraw(); + void color(const JUtility::TColor& color0, const JUtility::TColor& color1) { + color(color0, color1, color0, color1); + } + + void lineWidth(u8 width) { + field_0x16 = width; + GXSetLineWidth(field_0x16, GX_TO_ZERO); + } + /* 0x00 */ J2DPrint* field_0x0; /* 0x04 */ JUtility::TColor field_0x4; /* 0x08 */ JUtility::TColor field_0x8; diff --git a/libs/JSystem/include/JSystem/JAWExtSystem/JAWWindow.h b/libs/JSystem/include/JSystem/JAWExtSystem/JAWWindow.h index 7a570565cd..7ada3a548e 100644 --- a/libs/JSystem/include/JSystem/JAWExtSystem/JAWWindow.h +++ b/libs/JSystem/include/JSystem/JAWExtSystem/JAWWindow.h @@ -16,8 +16,8 @@ public: y = i_y; } - /* 0x00*/ int x; - /* 0x04*/ int y; + /* 0x00 */ int x; + /* 0x04 */ int y; }; class JAWWindow { @@ -29,9 +29,9 @@ public: virtual void drawSelf(f32, f32); virtual void drawSelf(f32, f32, Mtx*); - /* 0x0FC */ JAWGraphContext field_0xfc; + /* 0x0FC */ JAWGraphContext m_graf; /* 0x118 */ JAWWindow* m_pParent; - /* 0x11C */ JUTPoint field_0x11c; + /* 0x11C */ JUTPoint m_point; }; class TJ2DWindowDraw : public J2DWindow { @@ -94,21 +94,29 @@ public: static JUtility::TColor convJudaColor(u16); void padProc(const JUTGamePad&); + void setWindowColor(const JUtility::TColor& color) { + setWindowColor(color, color, color, color); + } + + void setWindowColor(u8 r, u8 g, u8 b, u8 a) { + setWindowColor(JUtility::TColor(r, g, b, a)); + } + /* 0x004 */ Mtx mMatrix; /* 0x034 */ u8 field_0x34[0x38 - 0x34]; - /* 0x038 */ TJ2DWindowDraw field_0x38; - /* 0x180 */ J2DTextBox field_0x180; - /* 0x2B0 */ TWindowText field_0x2b0; - /* 0x3D8 */ JUtility::TColor field_0x3d8; - /* 0x3DC */ JUtility::TColor field_0x3dc; - /* 0x3E0 */ JUtility::TColor field_0x3e0; - /* 0x3E4 */ JUtility::TColor field_0x3e4; + /* 0x038 */ TJ2DWindowDraw m_drawWindow; + /* 0x180 */ J2DTextBox m_titleText; + /* 0x2B0 */ TWindowText m_windowText; + /* 0x3D8 */ JUtility::TColor m_windowColor0; + /* 0x3DC */ JUtility::TColor m_windowColor1; + /* 0x3E0 */ JUtility::TColor m_windowColor2; + /* 0x3E4 */ JUtility::TColor m_windowColor3; /* 0x3E8 */ int field_0x3e8; - /* 0x3EC */ u8 field_0x3ec; + /* 0x3EC */ u8 m_isInit; void setMatrix(Mtx mtx) { MTXCopy(mtx, mMatrix); } - void setAlpha(u8 alpha) { field_0x38.setAlpha(alpha); } - void draw(int x, int y, const J2DGrafContext* p_grafCtx) { field_0x38.drawPane(x, y, p_grafCtx); } + void setAlpha(u8 alpha) { m_drawWindow.setAlpha(alpha); } + void draw(int x, int y, const J2DGrafContext* p_grafCtx) { m_drawWindow.drawPane(x, y, p_grafCtx); } }; #endif /* JAWWINDOW_H */ diff --git a/libs/JSystem/include/JSystem/JAudio2/JAISound.h b/libs/JSystem/include/JSystem/JAudio2/JAISound.h index 5e1cd40b4b..3e4dca5741 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JAISound.h +++ b/libs/JSystem/include/JSystem/JAudio2/JAISound.h @@ -49,6 +49,12 @@ public: JAISoundID(const JAISoundID& other) { id_.composite_ = other.id_.composite_; }; + JAISoundID(unsigned int sectionID, unsigned int groupID, unsigned int waveID) { + id_.info.type.parts.sectionID = sectionID; + id_.info.type.parts.groupID = groupID; + id_.info.waveID = waveID; + } + JAISoundID() {} bool isAnonymous() const { return id_.composite_ == -1; } @@ -322,6 +328,8 @@ public: status_.field_0x0.flags.paused = param_0; } + bool isPaused() const { return status_.field_0x0.flags.paused; } + void updateLifeTime(u32 lifeTime) { if (lifeTime > lifeTime_) { lifeTime_ = lifeTime; diff --git a/libs/JSystem/include/JSystem/JAudio2/JASHeapCtrl.h b/libs/JSystem/include/JSystem/JAudio2/JASHeapCtrl.h index e2cc9d5f62..f7095e289f 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASHeapCtrl.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASHeapCtrl.h @@ -33,6 +33,9 @@ public: bool isAllocated() const { return mBase; } u32 getSize() const { return mSize; } + JSUTree* getFirstChild() { return mTree.getFirstChild(); } + JSUTree* getEndChild() { return mTree.getEndChild(); } + /* 0x00 */ JSUTree mTree; /* 0x1C */ OSMutex mMutex; /* 0x34 */ JASDisposer* mDisposer; diff --git a/libs/JSystem/include/JSystem/JAudio2/JASWaveArcLoader.h b/libs/JSystem/include/JSystem/JAudio2/JASWaveArcLoader.h index 593e69f1fd..eea3eddac3 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASWaveArcLoader.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASWaveArcLoader.h @@ -54,6 +54,8 @@ struct JASWaveArc : JASDisposer { virtual void onEraseDone() {} s32 getStatus() const { return mStatus; } + u32 getFileSize() const { return mFileLength; } + JASHeap* getHeap() { return &mHeap; } struct loadToAramCallbackParams { // not official struct name diff --git a/libs/JSystem/include/JSystem/JAudio2/JAUSoundTable.h b/libs/JSystem/include/JSystem/JAudio2/JAUSoundTable.h index b85744ff94..284dc6015c 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JAUSoundTable.h +++ b/libs/JSystem/include/JSystem/JAudio2/JAUSoundTable.h @@ -148,6 +148,8 @@ struct JAUSoundTable : public JASGlobalInstance { void init(void const*); u8 getTypeID(JAISoundID) const; JAUSoundTableItem* getData(JAISoundID) const; + int getNumGroups_inSection(u8) const; + int getNumItems_inGroup(u8, u8) const; JAUSoundTableItem* getItem(JAUSoundTableGroup* group, int index) const { u32 offset = group->getItemOffset(index); diff --git a/libs/JSystem/include/JSystem/JSupport/JSUList.h b/libs/JSystem/include/JSystem/JSupport/JSUList.h index 3021a3a879..69356600a7 100644 --- a/libs/JSystem/include/JSystem/JSupport/JSUList.h +++ b/libs/JSystem/include/JSystem/JSupport/JSUList.h @@ -230,6 +230,8 @@ public: return *this; } + operator int() { return (int)mTree; } + T* getObject() const { return this->mTree->getObject(); } bool operator==(const JSUTree* other) const { return this->mTree == other; } diff --git a/libs/JSystem/src/JAWExtSystem/JAWWindow.cpp b/libs/JSystem/src/JAWExtSystem/JAWWindow.cpp index 5163355615..e551e59952 100644 --- a/libs/JSystem/src/JAWExtSystem/JAWWindow.cpp +++ b/libs/JSystem/src/JAWExtSystem/JAWWindow.cpp @@ -6,29 +6,29 @@ #include "JSystem/JGeometry.h" JAWWindow::JAWWindow(const char* param_1, int param_2, int param_3) : - field_0x38('WIN1', JGeometry::TBox2(0.0f, 0.0f, param_2, param_3), "frame_lu.bti"), - field_0x180('TITL', JGeometry::TBox2(10.0f, 10.0f, 640.0f, 170.0f), (const ResFONT*)JUTResFONT_Ascfont_fix16, param_1, -1, HBIND_LEFT, VBIND_TOP), - field_0x2b0(this), - field_0x3d8(JUtility::TColor(0, 0, 0, 255)), - field_0x3dc(JUtility::TColor(0, 0, 0, 255)), - field_0x3e0(JUtility::TColor(0, 0, 0, 255)), - field_0x3e4(JUtility::TColor(0, 0, 0, 255)) { + m_drawWindow('WIN1', JGeometry::TBox2(0.0f, 0.0f, param_2, param_3), "frame_lu.bti"), + m_titleText('TITL', JGeometry::TBox2(10.0f, 10.0f, 640.0f, 170.0f), (const ResFONT*)JUTResFONT_Ascfont_fix16, param_1, -1, HBIND_LEFT, VBIND_TOP), + m_windowText(this), + m_windowColor0(JUtility::TColor(0, 0, 0, 255)), + m_windowColor1(JUtility::TColor(0, 0, 0, 255)), + m_windowColor2(JUtility::TColor(0, 0, 0, 255)), + m_windowColor3(JUtility::TColor(0, 0, 0, 255)) { field_0x3e8 = 0; - field_0x3ec = 0; - field_0x38.setContentsColor(field_0x3d8, field_0x3e0, field_0x3dc, field_0x3e4); - field_0x180.setCharColor(JUtility::TColor(0, 255, 0, 255)); - field_0x180.setGradColor(JUtility::TColor(255, 255, 255, 255)); - field_0x38.appendChild(&field_0x180); - field_0x38.appendChild(&field_0x2b0); + m_isInit = FALSE; + m_drawWindow.setContentsColor(m_windowColor0, m_windowColor2, m_windowColor1, m_windowColor3); + m_titleText.setCharColor(JUtility::TColor(0, 255, 0, 255)); + m_titleText.setGradColor(JUtility::TColor(255, 255, 255, 255)); + m_drawWindow.appendChild(&m_titleText); + m_drawWindow.appendChild(&m_windowText); } JAWWindow::~JAWWindow() {} BOOL JAWWindow::initIf() { - if (field_0x3ec) { + if (m_isInit) { return TRUE; } else { - field_0x3ec = TRUE; + m_isInit = TRUE; return onInit(); } } @@ -36,30 +36,30 @@ BOOL JAWWindow::initIf() { BOOL JAWWindow::onInit() { return TRUE; } void JAWWindow::setTitleColor(const JUtility::TColor& param_1, const JUtility::TColor& param_2) { - field_0x180.setCharColor(param_1); - field_0x180.setGradColor(param_2); + m_titleText.setCharColor(param_1); + m_titleText.setGradColor(param_2); } void JAWWindow::setWindowColor(const JUtility::TColor& param_1, const JUtility::TColor& param_2, const JUtility::TColor& param_3, const JUtility::TColor& param_4) { - field_0x3d8 = param_1; - field_0x3dc = param_2; - field_0x3e0 = param_3; - field_0x3e4 = param_4; - field_0x38.setContentsColor(field_0x3d8, field_0x3e0, field_0x3dc, field_0x3e4); + m_windowColor0 = param_1; + m_windowColor1 = param_2; + m_windowColor2 = param_3; + m_windowColor3 = param_4; + m_drawWindow.setContentsColor(m_windowColor0, m_windowColor2, m_windowColor1, m_windowColor3); } void JAWWindow::onDraw(JAWGraphContext*) {} void JAWWindow::move(f32 param_1, f32 param_2) { - field_0x38.move(param_1, param_2); + m_drawWindow.move(param_1, param_2); } void JAWWindow::addPosition(f32 param_1, f32 param_2) { - field_0x38.add(param_1, param_2); + m_drawWindow.add(param_1, param_2); } void JAWWindow::addSize(f32 width, f32 height) { - JGeometry::TBox2 bounds = field_0x38.getBounds(); + JGeometry::TBox2 bounds = m_drawWindow.getBounds(); f32 newWidth = width + bounds.getWidth(); f32 newHeight = height + bounds.getHeight(); if (newWidth < 36.0f) { @@ -72,7 +72,7 @@ void JAWWindow::addSize(f32 width, f32 height) { } else if (newHeight > 480.0f) { newHeight = 480.0f; } - field_0x38.resize(newWidth, newHeight); + m_drawWindow.resize(newWidth, newHeight); } JUtility::TColor JAWWindow::convJudaColor(u16 param_1) { @@ -189,7 +189,7 @@ void JAWWindow::padProc(const JUTGamePad& pad) { JAWWindow::TWindowText::TWindowText(JAWWindow* window) : J2DPane('TEXT', JGeometry::TBox2(10.0f, 30.0f, 650.0f, 510.0f)), m_pParent(window), - field_0x11c(0, 0) { + m_point(0, 0) { } JAWWindow::TWindowText::~TWindowText() {} @@ -201,15 +201,15 @@ void JAWWindow::TWindowText::drawSelf(f32 param_1, f32 param_2) { } void JAWWindow::TWindowText::drawSelf(f32, f32, Mtx* param_3) { - field_0xfc.reset(); - MTXTrans(*param_3, -field_0x11c.x, -field_0x11c.y, 0.0f); + m_graf.reset(); + MTXTrans(*param_3, -m_point.x, -m_point.y, 0.0f); Mtx stack_48; MTXConcat(*param_3, mGlobalMtx, stack_48); GXLoadPosMtxImm(stack_48, 0); - field_0xfc.setParentAlpha(mColorAlpha); + m_graf.setParentAlpha(mColorAlpha); JUT_ASSERT(209, m_pParent != NULL); m_pParent->setMatrix(stack_48); - m_pParent->onDraw(&field_0xfc); + m_pParent->onDraw(&m_graf); } static void dummy(J2DPane* pane, J2DWindow* window) { diff --git a/src/Z2AudioCS/Z2AudioCS.cpp b/src/Z2AudioCS/Z2AudioCS.cpp index e619876537..c21c1a898c 100644 --- a/src/Z2AudioCS/Z2AudioCS.cpp +++ b/src/Z2AudioCS/Z2AudioCS.cpp @@ -127,12 +127,12 @@ SpkSoundHandle* Z2AudioCS::startLevel(s32 id, s32 chan) { return handle; } -s32 Z2AudioCS::getName(s32 num) { +const char* Z2AudioCS::getName(s32 num) { if (JASGlobalInstance::getInstance() == NULL) { - return 0; + return NULL; } if (JASGlobalInstance::getInstance()->getData() == NULL) { - return 0; + return NULL; } return JASGlobalInstance::getInstance()->getData()->getTableMgr().getName(num); diff --git a/src/Z2AudioLib/Z2SeView.cpp b/src/Z2AudioLib/Z2SeView.cpp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/Z2AudioLib/Z2SoundPlayer.cpp b/src/Z2AudioLib/Z2SoundPlayer.cpp index d2d6fdecf3..1302d07674 100644 --- a/src/Z2AudioLib/Z2SoundPlayer.cpp +++ b/src/Z2AudioLib/Z2SoundPlayer.cpp @@ -1,46 +1,55 @@ #include "Z2AudioLib/Z2SoundPlayer.h" -#include "Z2AudioLib/Z2SeqMgr.h" +#include "Z2AudioLib/Z2AudioMgr.h" +#include "Z2AudioCS/Z2AudioCS.h" +#include "JSystem/JUtility/JUTGamePad.h" -Z2SoundPlayer::Z2SoundPlayer(const char* param_1) : JAWWindow("Player for JAudio2", 500, 140), field_0x48c(field_0x46c, 8) { - field_0x3ed = 0; - field_0x3ee = 0; - field_0x3ef = 0; - field_0x3f0 = 0; - field_0x3f4 = param_1; +Z2SoundPlayer::Z2SoundPlayer(const char* projectName) + : JAWWindow("Player for JAudio2", 500, 140), + field_0x48c(field_0x46c, 8) +{ + field_0x3ed = false; + field_0x3ee = false; + field_0x3ef = false; + field_0x3f0 = false; + + m_name = projectName; field_0x3f8 = 0; field_0x3fc = 0; field_0x3fa = 0; field_0x3fe = 0; field_0x400 = 0; - field_0x402 = 0; - field_0x444 = 0; + m_portNum = 0; + m_portVal = 0; field_0x446 = 0; - field_0x404 = "CMD_PORT"; - field_0x408 = "END_PORT"; - field_0x40c = "STA_PORT"; - field_0x410 = "WAIT_PORT"; - field_0x414 = "NUM_PORT"; - field_0x418 = "PORT_5"; - field_0x41c = "MAP_PORT"; - field_0x420 = "NOTE_PORT"; - field_0x424 = "SE_SELECT_PORT"; - field_0x428 = "BGM_STATUS_PORT"; - field_0x42c = "BGM_PORT2"; - field_0x430 = "BGM_PORT3"; - field_0x434 = "PORT12"; - field_0x438 = "PORT13"; - field_0x43c = "FILTER_PORT"; - field_0x440 = "FX_PORT"; + + m_portNames[0] = "CMD_PORT"; + m_portNames[1] = "END_PORT"; + m_portNames[2] = "STA_PORT"; + m_portNames[3] = "WAIT_PORT"; + m_portNames[4] = "NUM_PORT"; + m_portNames[5] = "PORT_5"; + m_portNames[6] = "MAP_PORT"; + m_portNames[7] = "NOTE_PORT"; + m_portNames[8] = "SE_SELECT_PORT"; + m_portNames[9] = "BGM_STATUS_PORT"; + m_portNames[10] = "BGM_PORT2"; + m_portNames[11] = "BGM_PORT3"; + m_portNames[12] = "PORT12"; + m_portNames[13] = "PORT13"; + m_portNames[14] = "FILTER_PORT"; + m_portNames[15] = "FX_PORT"; + u8 r30 = 0; field_0x448[r30++] = &field_0x3f8; field_0x448[r30++] = &field_0x3fa; field_0x448[r30++] = &field_0x3fc; field_0x448[r30++] = &field_0x3fe; field_0x448[r30++] = &field_0x400; - field_0x448[r30++] = &field_0x402; - field_0x448[r30] = &field_0x444; - field_0x464 = 0; - field_0x468 = 7; + field_0x448[r30++] = &m_portNum; + field_0x448[r30] = &m_portVal; + + m_cursorY = 0; + m_cursorMax = 7; field_0x4a0 = 0; field_0x4a4 = 1.0f; field_0x4a8 = 0.5f; @@ -48,65 +57,446 @@ Z2SoundPlayer::Z2SoundPlayer(const char* param_1) : JAWWindow("Player for JAudio field_0x4b0 = 0.0f; field_0x4b4 = 0.0f; field_0x4b8 = 1.0f; - field_0x498 = Z2GetSeqMgr()->getSubBgmHandle(); + + mp_subBgmHandle = Z2GetSeqMgr()->getSubBgmHandle(); } -void Z2SoundPlayer::onDraw(JAWGraphContext*) { +void Z2SoundPlayer::onDraw(JAWGraphContext* graf) { + JAUSoundNameTable* soundTable = JASGlobalInstance::getInstance(); + + int var_r27 = 0; + if (field_0x3ed) { + var_r27 = 2; + } + u8 cursorY = 0; + + graf->color(JUtility::TColor(0, 0x7F, 0xFF, 0xFF), JUtility::TColor(0, 0xFF, 0x7F, 0xFF)); + graf->print(1, cursorY++, "Project: %s", m_name); + + graf->color(0xFF >> (u8)var_r27, 0xFF >> (u8)var_r27, 0xFF >> (u8)var_r27, 0xFF); + graf->print(0, m_cursorY + 1, ">"); + + JUtility::TColor sp28(0xFF >> (u8)var_r27, 0xFF >> (u8)var_r27, 0xFF >> (u8)var_r27, 0xFF); + JUtility::TColor sp24(0, 0xFF >> (u8)var_r27, 0, 0xFF); + + onDrawSoundItem(graf, soundTable, cursorY++, + sp28, + sp24, + "SEQ NUM ", + 1, 0, field_0x3f8 + ); + + graf->color(sp28); + const char* cateName; + if (soundTable != NULL) { + cateName = soundTable->getGroupName(JAISoundID(0, field_0x3fa & 0xFF, 0)); + } else { + cateName = ""; + } + graf->print(1, cursorY++, "SE CATE %03x %s", field_0x3fa, cateName); + + onDrawSoundItem(graf, soundTable, cursorY++, + sp28, + sp24, + "SE NUM ", + 0, field_0x3fa & 0xFF, field_0x3fc + ); + onDrawSoundItem(graf, soundTable, cursorY++, + sp28, + sp24, + "STRM NUM", + 2, 0, field_0x3fe + ); + + graf->color(sp28); + graf->print(1, cursorY++, "CORE_SPK %03x %s", field_0x400, Z2AudioCS::getName(field_0x400)); + + graf->color(field_0x446 + 0xC0, 0xC0, 0xC0, 0xFF); + graf->print(1, cursorY++, "PORT NUM %03x %s", m_portNum, m_portNames[m_portNum]); + graf->print(1, cursorY++, "PORT VAL %03x", m_portVal); + + graf->color(0, 0x50, 0xC8, 0xFF); + if (field_0x3ee) { + graf->print(1, cursorY, "DOLBY ON\n"); + } + + graf->color(0xC8, 0x50, 0, 0xFF); + if (field_0x3f0) { + graf->print(10, cursorY, "LEVEL SE CALL"); + } + + graf->color(0xFF, 0, 0, 0xFF); + graf->print(0x16, cursorY, "%s", field_0x3ed ? "PAUSE ON" : ""); + + graf->color(0, 0xC8, (field_0x4a0 * 200) / 30, 0xFF); + if (field_0x3ef) { + graf->print(0x16, cursorY, "RESET %d\n", field_0x4a0); + } } -void Z2SoundPlayer::onTrigA(const JUTGamePad&) { +void Z2SoundPlayer::onTrigA(const JUTGamePad& pad) { + switch (m_cursorY) { + case 0: { + JAISoundID sp24(1, 0, (u16)field_0x3f8); + switch (sp24) { + case 0x100000a: + case 0x100000b: + case 0x100000f: + case 0x1000012: + case 0x1000014: + case 0x100001b: + case 0x100001c: + case 0x1000024: + Z2GetSoundMgr()->startSound(sp24, mp_subBgmHandle, NULL); + break; + default: + Z2GetSoundMgr()->startSound(sp24, &field_0x494, NULL); + break; + } + break; + } + case 1: + case 2: + if (!field_0x3f0) { + JAISoundHandle* handle = field_0x48c.getFreeHandle(); + JAISoundID sp20(0, field_0x3fa & 0xFF, (u16)field_0x3fc); + if (handle != NULL) { + Z2GetSoundStarter()->startSound(sp20, handle, NULL); + if (*handle) { + Z2GetSoundStarter()->setPortData(handle, m_portNum, m_portVal, -1); + } + } + } + break; + case 3: + Z2GetSoundMgr()->startSound(JAISoundID(2, 0, (u16)field_0x3fe), &field_0x49c, NULL); + break; + case 4: + if (!field_0x3f0) { + Z2AudioCS::start(field_0x400, 0); + } + break; + case 5: + case 6: + field_0x446 = 64; + if (m_portNum < 9) { + JAISoundHandle* handle = field_0x48c.getFreeHandle(); + JAISoundID sp1C(0, field_0x3fa & 0xFF, (u16)field_0x3fc); + if (handle != NULL) { + Z2GetSoundStarter()->startSound(sp1C, handle, NULL); + if (*handle) { + Z2GetSoundStarter()->setPortData(handle, m_portNum, m_portVal, -1); + } + } + } else { + if (field_0x494) { + Z2GetSoundStarter()->setPortData(&field_0x494, m_portNum, m_portVal, -1); + } + if (mp_subBgmHandle->isSoundAttached()) { + Z2GetSoundStarter()->setPortData(mp_subBgmHandle, m_portNum, m_portVal, -1); + } + } + break; + } } -void Z2SoundPlayer::onTrigB(const JUTGamePad&) { +void Z2SoundPlayer::onTrigB(const JUTGamePad& pad) { + switch (m_cursorY) { + case 0: { + if (field_0x494) { + field_0x494->stop(0); + } + field_0x494.releaseSound(); + + if (mp_subBgmHandle->isSoundAttached()) { + (*mp_subBgmHandle)->stop(); + } + break; + } + case 1: + case 2: { + JAISoundHandle* handle = field_0x48c.getHandleSoundID(JAISoundID(0, field_0x3fa & 0xFF, (u16)field_0x3fc)); + if (handle) { + (*handle)->stop(); + handle->releaseSound(); + } + break; + } + case 3: + if (field_0x49c) { + field_0x49c->stop(); + } + + field_0x49c.releaseSound(); + break; + case 4: + Z2AudioCS::stop(0); + break; + case 5: + case 6: + m_portVal = 0; + field_0x446 = 0; + + if (field_0x494) { + Z2GetSoundStarter()->setPortData(&field_0x494, m_portNum, m_portVal, -1); + } + + if (mp_subBgmHandle->isSoundAttached()) { + Z2GetSoundStarter()->setPortData(mp_subBgmHandle, m_portNum, m_portVal, -1); + } + break; + } } void Z2SoundPlayer::frameWork() { + switch (m_cursorY) { + case 0: + case 1: + case 2: + case 3: + case 4: + break; + } + if (field_0x3ef) { + field_0x4a0++; + + if (Z2GetAudioMgr()->hasReset()) { + Z2GetAudioMgr()->resetRecover(); + field_0x3ef = false; + OS_REPORT("RESET END \n"); + } + } } -void Z2SoundPlayer::onPadProc(const JUTGamePad&) { +void Z2SoundPlayer::onPadProc(const JUTGamePad& pad) { + if (field_0x3f0 && pad.testButton(PAD_BUTTON_A)) { + switch (m_cursorY) { + case 2: { + JAISoundID sp14(0, field_0x3fa & 0xFF, (u16)field_0x3fc); + JAISoundHandle* handle = field_0x48c.getHandleSoundID(sp14); + if (handle == NULL) { + handle = field_0x48c.getFreeHandle(); + } + if (handle != NULL) { + Z2GetAudioMgr()->startLevelSound(sp14, handle, NULL); + } + + if (*handle) { + Z2GetSoundStarter()->setPortData(handle, m_portNum, m_portVal, -1); + } + break; + } + case 4: + Z2AudioCS::startLevel(field_0x400, 0); + break; + } + } } -void Z2SoundPlayer::onTrigX(const JUTGamePad&) { +void Z2SoundPlayer::onTrigX(const JUTGamePad& pad) { + switch (m_cursorY) { + case 0: { + if (field_0x494) { + field_0x494->pause(!field_0x494->isPaused()); + } + if (mp_subBgmHandle->isSoundAttached()) { + (*mp_subBgmHandle)->pause(!(*mp_subBgmHandle)->isPaused()); + } + break; + } + case 1: + case 2: { + JAISoundHandle* handle = field_0x48c.getHandleSoundID(JAISoundID(0, field_0x3fa & 0xFF, (u16)field_0x3fc)); + if (handle) { + (*handle)->pause(!(*handle)->isPaused()); + } + break; + } + case 3: + if (field_0x49c) { + (field_0x49c)->pause(!(field_0x49c)->isPaused()); + } + break; + case 4: + Z2AudioCS::stop(0); + break; + } } -void Z2SoundPlayer::onTrigY(const JUTGamePad&) { - - -} -void Z2SoundPlayer::onTrigZ(const JUTGamePad&) { - - -} -void Z2SoundPlayer::onTrigL(const JUTGamePad&) { - +void Z2SoundPlayer::onTrigY(const JUTGamePad& pad) { + if (!field_0x3ef) { + Z2GetAudioMgr()->resetProcess(30, false); + field_0x3ef = true; + field_0x4a0 = 0; + } } -void Z2SoundPlayer::onKeyLeft(const JUTGamePad&) { - +void Z2SoundPlayer::onTrigZ(const JUTGamePad& pad) { + bool* pvar = &field_0x3f0; + bool newValue = *pvar ^ 1; + *pvar = newValue; } -void Z2SoundPlayer::onKeyRight(const JUTGamePad&) { +void Z2SoundPlayer::onTrigL(const JUTGamePad& pad) { + bool* pvar = &field_0x3ee; + bool newValue = *pvar ^ 1; + *pvar = newValue; + if (field_0x3ee) { + Z2GetAudioMgr()->setOutputMode(2); + } else { + Z2GetAudioMgr()->setOutputMode(1); + } } -void Z2SoundPlayer::onKeyUp(const JUTGamePad&) { +void Z2SoundPlayer::onKeyLeft(const JUTGamePad& pad) { + int moveMax = getCursorMoveMax(pad); + int menuMax = getMenuNumberMax(); + if (moveMax >= menuMax) { + moveMax = 1; + } + + if (*field_0x448[m_cursorY] > moveMax - 1) { + *field_0x448[m_cursorY] -= (s16)moveMax; + } else { + *field_0x448[m_cursorY] = (*field_0x448[m_cursorY] + menuMax) - moveMax; + } + + if (m_cursorY == 1) { + correctSeNumber(); + } } -void Z2SoundPlayer::onKeyDown(const JUTGamePad&) { +void Z2SoundPlayer::onKeyRight(const JUTGamePad& pad) { + int moveMax = getCursorMoveMax(pad); + int menuMax = getMenuNumberMax(); + if (moveMax >= menuMax) { + moveMax = 1; + } + + if (*field_0x448[m_cursorY] < menuMax - moveMax) { + *field_0x448[m_cursorY] += (s16)moveMax; + } else { + *field_0x448[m_cursorY] = (*field_0x448[m_cursorY] + moveMax) - menuMax; + } + + if (m_cursorY == 1) { + correctSeNumber(); + } } -void Z2SoundPlayer::onKeyMenu(const JUTGamePad&) { - +void Z2SoundPlayer::onKeyUp(const JUTGamePad& pad) { + if (m_cursorY != 0) { + m_cursorY--; + } else { + m_cursorY = m_cursorMax - 1; + } } -u32 Z2SoundPlayer::getCursorMoveMax(const JUTGamePad&) { - +void Z2SoundPlayer::onKeyDown(const JUTGamePad& pad) { + if (m_cursorY < m_cursorMax - 1) { + m_cursorY++; + } else { + m_cursorY = 0; + } +} + +void Z2SoundPlayer::onKeyMenu(const JUTGamePad& pad) { + bool* pvar = &field_0x3ed; + bool newValue = *pvar ^ 1; + *pvar = newValue; + + Z2GetSoundMgr()->pauseAllGameSound(field_0x3ed); +} + +u32 Z2SoundPlayer::getCursorMoveMax(const JUTGamePad& pad) { + u32 num = 1; + + if (pad.testButton(PAD_TRIGGER_Z) && pad.testButton(PAD_BUTTON_X)) { + num = 128; + } else if (pad.testButton(PAD_TRIGGER_Z)) { + num = 16; + } else if (pad.testButton(PAD_BUTTON_X)) { + num = 8; + } + + return num; +} + +int Z2SoundPlayer::getMenuNumberMax() { + JAUSoundTable* soundTable = JASGlobalInstance::getInstance(); + JUT_ASSERT(550, soundTable); + + int num = 0; + switch (m_cursorY) { + case 0: + num = soundTable->getNumItems_inGroup(1, 0); + break; + case 1: + num = soundTable->getNumGroups_inSection(0); + break; + case 2: + num = soundTable->getNumItems_inGroup(0, field_0x3fa); + break; + case 3: + num = soundTable->getNumItems_inGroup(2, 0); + break; + case 4: + num = Z2AudioCS::getNumOfSound(); + break; + case 5: + num = 16; + break; + case 6: + num = 128; + break; + } + + return num; +} + +void Z2SoundPlayer::correctSeNumber() { + JAUSoundTable* soundTable = JASGlobalInstance::getInstance(); + JUT_ASSERT(594, soundTable); + + int num = soundTable->getNumItems_inGroup(0, field_0x3fa); + if (num == 0) { + field_0x3fc = 0; + } else if (field_0x3fc >= num) { + field_0x3fc = num - 1; + } +} + +void Z2SoundPlayer::onDrawSoundItem(JAWGraphContext* graf, JAUSoundNameTable* nameTable, int cursorY, + const JUtility::TColor& color0, const JUtility::TColor& color1, + const char* label, u32 sectionID, u32 groupID, u32 waveID) +{ + static const char szNoEntry[] = "(NO ENTRY)"; + + const char* soundName = ""; + if (nameTable != NULL) { + soundName = nameTable->getName(JAISoundID(sectionID, groupID, waveID)); + if (*soundName == 0) { + soundName = szNoEntry; + } + } else { + soundName = ""; + } + + // !@bug comparing strings directly instead of using strcmp + if (soundName == szNoEntry) { + graf->color(color1); + } else { + graf->color(color0); + } + + graf->print(1, cursorY, "%s %03x %s", label, waveID, soundName); } diff --git a/src/Z2AudioLib/Z2TrackView.cpp b/src/Z2AudioLib/Z2TrackView.cpp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/Z2AudioLib/Z2WaveArcLoader.cpp b/src/Z2AudioLib/Z2WaveArcLoader.cpp new file mode 100644 index 0000000000..2522f4a877 --- /dev/null +++ b/src/Z2AudioLib/Z2WaveArcLoader.cpp @@ -0,0 +1,335 @@ +#include "Z2AudioLib/Z2WaveArcLoader.h" +#include "JSystem/JAudio2/JAUSectionHeap.h" +#include "JSystem/JAudio2/JASWaveInfo.h" + +#include + +static f32 cStatusBar_X1 = 8.0f; +static f32 cStatusBar_X2 = 16.0f; +static f32 cWaveBar_X1 = 30.0f; +static f32 cWaveBar_X2 = 80.0f; +static f32 cBar_Y = 5.0f; +static f32 cBarHeight = 310.0f; + +static u8 lbl_8074A738 = 10; +static u8 lbl_8074A739 = 23; + +static u32 cWaveArcListSize = 10; + +Z2WaveArcLoader::Z2WaveArcLoader() : + JAWWindow("Z2WaveArcLoader", 420, 365) +{ + setWindowColor(0, 0, 0x50, 0xFF); + + mpWaveBank = NULL; + mpWaveArc = NULL; + mWaveUsedSize = 0; + mTotalUsedSize = 0; + mBankNo = 0; + mArcCount = 0; + field_0x408 = 0; + field_0x40c = 0; + field_0x410 = 0; + field_0x414 = 0; + mIsLoadTail = false; + + checkWaveBank(); + checkWaveArc(); +} + +Z2WaveArcLoader::~Z2WaveArcLoader() {} + +// nonmatching with tree iterator stuff +void Z2WaveArcLoader::onDraw(JAWGraphContext* graf) { + int cursorX = 8; + JASHeap* rootheap = JASWaveArcLoader::getRootHeap(); + u32 aramsize = JASKernel::getAramSize(); + + mWaveUsedSize = 0; + + for (u32 i = 0; i < 255; i++) { + JASWaveBank* bank = JASGlobalInstance::getInstance()->getWaveBankTable().getWaveBank(i); + if (bank != NULL) { + u32 arcCount = bank->getArcCount(); + for (int j = 0; j < arcCount; j++) { + JASWaveArc* arc = bank->getWaveArc(j); + if (arc != NULL && arc->getStatus() == 2) { + mWaveUsedSize += arc->getFileSize(); + } + } + } + } + + u32 otherUsedSize = mTotalUsedSize - mWaveUsedSize; + u32 totalFree = rootheap->getTotalFreeSize(); + u32 linearFreeSize = rootheap->getFreeSize(); + u32 sp54 = totalFree - linearFreeSize; + + graf->color(JUtility::TColor(0x32, 0x96, 0xFF, 0xFF), JUtility::TColor(0xC8, 0xC8, 0xFF, 0xFF)); + graf->print(cursorX, 0, "ARAM STATUS\n"); + + graf->color(0xFF, 0xFF, 0xFF, 0xFF); + graf->print(cursorX, 1, " total used : %8d\n", mTotalUsedSize); + + graf->color(0xFF, 0x40, 0x40, 0xFF); + graf->print(cursorX, 2, " wave used : %8d\n", mWaveUsedSize); + + graf->color(0xFF, 0xFF, 0x40, 0xFF); + graf->print(cursorX, 3, " other used : %8d\n", otherUsedSize); + + graf->color(0x40, 0xFF, 0x40, 0xFF); + graf->print(cursorX, 4, " linear free : %8d\n", linearFreeSize); + + graf->color(0xFF, 0xFF, 0xFF, 0xFF); + graf->print(cursorX, 5, " total free : %8d\n", totalFree); + graf->print(cursorX, 6, " ------------------------\n"); + graf->print(cursorX, 7, " aram size : %8d\n", aramsize); + + graf->color(0xFF, 0xFF, 0x20, 0xFF); + f32 var_f30 = cBar_Y; + f32 var_f31 = var_f30 + (((f32)otherUsedSize / (f32)aramsize) * cBarHeight); + graf->fillBox(JGeometry::TBox2(cStatusBar_X1, var_f30, cStatusBar_X2, var_f31)); + + graf->color(0xFF, 0x20, 0x20, 0xFF); + var_f30 = var_f31; + var_f31 += ((f32)mWaveUsedSize / (f32)aramsize) * cBarHeight; + graf->fillBox(JGeometry::TBox2(cStatusBar_X1, var_f30, cStatusBar_X2, var_f31)); + + graf->color(0x20, 0xFF, 0x20, 0xFF); + var_f30 = var_f31; + var_f31 += ((f32)linearFreeSize / (f32)aramsize) * cBarHeight; + graf->fillBox(JGeometry::TBox2(cStatusBar_X1, var_f30, cStatusBar_X2, var_f31)); + + graf->color(0x20, 0x20, 0xFF, 0xFF); + var_f30 = var_f31; + var_f31 += ((f32)sp54 / (f32)aramsize) * cBarHeight; + graf->fillBox(JGeometry::TBox2(cStatusBar_X1, var_f30, cStatusBar_X2, var_f31)); + + graf->color(0x40, 0x40, 0xFF, 0xFF); + graf->fillBox(JGeometry::TBox2(cWaveBar_X1, cBar_Y, cWaveBar_X2, cBar_Y + cBarHeight)); + + void* pbase = rootheap->getBase(); + u32 heapSize = rootheap->getSize(); + int sp48 = 0; + + mTotalUsedSize = 0; + + for (JSUTreeIterator i = rootheap->getFirstChild(); (int)i != (int)rootheap->getEndChild(); ++i) { + intptr_t sp40 = (char*)i->getBase() - (char*)pbase; + u32 sp3C = i->getSize(); + mTotalUsedSize += sp3C; + + int sp38 = (sp40 * cBarHeight) / heapSize; + int sp34 = (sp3C * cBarHeight) / heapSize; + JGeometry::TBox2 spD0(cWaveBar_X1, cBar_Y + (f32)sp38, cWaveBar_X2, (f32)sp34 + (cBar_Y + (f32)sp38)); + u8 sp9 = 0xFF; + sp9 = sp48 % 2 ? (u8)0xC0 : (u8)0x80; + graf->color(0xFF, sp9, 0x40, 0xFF); + graf->fillBox(spD0); + + if (mpWaveArc != NULL) { + if (mpWaveArc->getHeap() == i.getObject()) { + graf->color(0xFF, 0xFF, 0xFF, 0xFF); + graf->lineWidth(15); + graf->drawFrame(spD0); + } + } + + sp48++; + } + + graf->color(JUtility::TColor(0xC8, 0xC8, 0xFF, 0xFF), JUtility::TColor(0x32, 0x96, 0xFF, 0xFF)); + if (mIsLoadTail) { + graf->print(cursorX, 9, "WAVE BANK LOAD:Tail"); + } else { + graf->print(cursorX, 9, "WAVE BANK LOAD:Linear"); + } + + int var_r26 = lbl_8074A738; + + if (mpWaveBank == NULL) { + graf->color(0x64, 0x64, 0x64, 0xFF); + graf->print(cursorX, var_r26, " ws:%3d Not Registerd", mBankNo); + + graf->color(0xFF, 0xFF, 0xFF, 0xFF); + graf->print(cursorX, var_r26, ">"); + } else { + graf->color(0xFF, 0xFF, 0xFF, 0xFF); + if (field_0x414 < 2) { + graf->print(cursorX, field_0x414 + var_r26, ">"); + } else { + graf->print(cursorX, field_0x414 + (var_r26 + 1), ">"); + } + + graf->print(cursorX, var_r26++, " ws:%3d", mBankNo); + graf->print(cursorX, var_r26++, " aw:%3d-%3d /%3d", field_0x40c, field_0x410, mArcCount - 1); + + graf->color(0xFF, 0xA0, 0x64, 0xFF); + graf->print(cursorX, var_r26++, " aw_status aw_size"); + + graf->color(0xFF, 0xFF, 0xFF, 0xFF); + + for (int i = field_0x40c; i < field_0x410 + 1; i++) { + JASWaveArc* arc = mpWaveBank->getWaveArc(i); + if (arc == NULL) { + break; + } + + switch (arc->getStatus()) { + case 0: + graf->color(0x64, 0x64, 0x64, 0xFF); + graf->print(cursorX, var_r26, "%3d: NOT_LOAD %8d", i, arc->getFileSize()); + break; + case 1: + graf->color(0xFF, 0x64, 0x64, 0xFF); + graf->print(cursorX, var_r26, "%3d: WAIT_LOAD %8d", i, arc->getFileSize()); + break; + case 2: + graf->color(0xFF, 0xFF, 0xC8, 0xFF); + graf->print(cursorX, var_r26, "%3d: LOAD %8d", i, arc->getFileSize()); + break; + } + + var_r26++; + } + } + + var_r26 = lbl_8074A739; + graf->color(0x96, 0x96, 0xE1, 0xFF); + graf->print(cursorX, var_r26++, "-----------------"); + graf->print(cursorX, var_r26++, " A -> LOAD WAVE"); + graf->print(cursorX, var_r26++, " B -> ERASE WAVE"); + graf->print(cursorX, var_r26++, "-----------------"); +} + +void Z2WaveArcLoader::checkWaveBank() { + JAUSectionHeap* sectionHeap = JASGlobalInstance::getInstance(); + const JAUSectionHeap::TSectionHeapData& heapData = sectionHeap->getSectionHeapData(); + const JAUSection::TSectionData& sectionData = sectionHeap->getSectionData(); + + if (sectionData.registeredWaveBankTables.test(mBankNo)) { + mpWaveBank = heapData.waveBankTable.getWaveBank(mBankNo); + mArcCount = mpWaveBank->getArcCount(); + } else { + mpWaveBank = NULL; + mArcCount = 0; + } + + if (mBankNo == 1) { + mIsLoadTail = true; + } else { + mIsLoadTail = false; + } +} + +void Z2WaveArcLoader::checkWaveArc() { + field_0x40c = field_0x408 * cWaveArcListSize; + if (field_0x40c > mArcCount - 1) { + field_0x408--; + field_0x40c = field_0x408 * cWaveArcListSize; + } + + field_0x410 = (field_0x408 + 1) * cWaveArcListSize - 1; + + if (field_0x410 > mArcCount - 1) { + field_0x410 = mArcCount - 1; + } + + if (field_0x414 > 1) { + if ((field_0x40c + field_0x414) - 2 > field_0x410) { + field_0x414 = (field_0x410 - field_0x40c) + 2; + } + + mpWaveArc = mpWaveBank->getWaveArc(field_0x40c + field_0x414 - 2); + } else { + mpWaveArc = NULL; + } +} + +void Z2WaveArcLoader::onKeyUp(const JUTGamePad&) { + if (mpWaveBank == NULL) { + field_0x414 = 0; + } else { + if (field_0x414 != 0) { + field_0x414--; + } else { + field_0x414 = 11; + } + + checkWaveArc(); + } +} + +void Z2WaveArcLoader::onKeyDown(const JUTGamePad&) { + if (mpWaveBank == NULL) { + field_0x414 = 0; + } else { + if (field_0x414 < 11) { + field_0x414++; + } else { + field_0x414 = 0; + } + + checkWaveArc(); + } +} + +void Z2WaveArcLoader::onKeyLeft(const JUTGamePad&) { + if (field_0x414 == 0) { + if (mBankNo != 0) { + mBankNo--; + } else { + mBankNo = 0; + } + + checkWaveBank(); + } else if (mpWaveBank != NULL) { + if (field_0x408 != 0) { + field_0x408--; + } else { + field_0x408 = 0; + } + + checkWaveArc(); + } +} + +void Z2WaveArcLoader::onKeyRight(const JUTGamePad&) { + if (field_0x414 == 0) { + if (mBankNo < 255) { + mBankNo++; + } else { + mBankNo = 255; + } + + checkWaveBank(); + } else if (mpWaveBank != NULL) { + if (field_0x408 < 255) { + field_0x408++; + } else { + field_0x408 = 255; + } + + checkWaveArc(); + } +} + +void Z2WaveArcLoader::onTrigA(const JUTGamePad&) { + if (mpWaveArc != NULL && mpWaveArc->getStatus() == 0) { + if (mIsLoadTail) { + mpWaveArc->loadTail(NULL); + } else { + mpWaveArc->load(NULL); + } + } +} + +void Z2WaveArcLoader::onTrigB(const JUTGamePad&) { + if (mpWaveArc != NULL && mpWaveArc->getStatus() == 2) { + mpWaveArc->erase(); + } +} + +void Z2WaveArcLoader::onTrigZ(const JUTGamePad&) { + mIsLoadTail = !mIsLoadTail; +} diff --git a/src/d/actor/d_a_e_fz.cpp b/src/d/actor/d_a_e_fz.cpp index cefe6237cd..a48c4df8f0 100644 --- a/src/d/actor/d_a_e_fz.cpp +++ b/src/d/actor/d_a_e_fz.cpp @@ -3,16 +3,17 @@ * @brief Enemy - Mini Freezard */ -#include "d/dolzel_rel.h" // IWYU pragma: keep +#include "d/dolzel_rel.h" // IWYU pragma: keep -#include "d/actor/d_a_e_fz.h" -#include "f_op/f_op_actor_enemy.h" -#include "d/actor/d_a_mirror.h" -#include "d/actor/d_a_b_yo.h" -#include "d/d_com_inf_game.h" -#include "d/d_item.h" #include "SSystem/SComponent/c_math.h" #include "SSystem/SComponent/c_xyz.h" +#include "d/actor/d_a_b_yo.h" +#include "d/actor/d_a_e_fz.h" +#include "d/actor/d_a_mirror.h" +#include "d/d_com_inf_game.h" +#include "d/d_item.h" +#include "f_op/f_op_actor_enemy.h" + class daE_FZ_HIO_c { public: @@ -42,26 +43,26 @@ namespace { static dCcD_SrcSph cc_fz_src = { { - {0x0, {{0x100, 0x1, 0x0}, {0xd0fbfdff, 0x43}, 0x65}}, // mObj - {dCcD_SE_METAL, 0x0, 0x0, 0x0, 0x0}, // mGObjAt - {dCcD_SE_NONE, 0x0, 0x0, 0x0, 0x6}, // mGObjTg - {0x0}, // mGObjCo - }, // mObjInf + {0x0, {{0x100, 0x1, 0x0}, {0xd0fbfdff, 0x43}, 0x65}}, // mObj + {dCcD_SE_METAL, 0x0, 0x0, 0x0, 0x0}, // mGObjAt + {dCcD_SE_NONE, 0x0, 0x0, 0x0, 0x6}, // mGObjTg + {0x0}, // mGObjCo + }, // mObjInf { - {{0.0f, 0.0f, 0.0f}, 40.0f} // mSph - } // mSphAttr + {{0.0f, 0.0f, 0.0f}, 40.0f} // mSph + } // mSphAttr }; static dCcD_SrcSph cc_fz_at_src = { { - {0x0, {{0x100, 0x1, 0x1d}, {0x0, 0x0}, 0x0}}, // mObj - {dCcD_SE_METAL, 0x0, 0x0, 0x0, 0x0}, // mGObjAt - {dCcD_SE_NONE, 0x0, 0x0, 0x0, 0x2}, // mGObjTg - {0x0}, // mGObjCo - }, // mObjInf + {0x0, {{0x100, 0x1, 0x1d}, {0x0, 0x0}, 0x0}}, // mObj + {dCcD_SE_METAL, 0x0, 0x0, 0x0, 0x0}, // mGObjAt + {dCcD_SE_NONE, 0x0, 0x0, 0x0, 0x2}, // mGObjTg + {0x0}, // mGObjCo + }, // mObjInf { - {{0.0f, 0.0f, 0.0f}, 40.0f} // mSph - } // mSphAttr + {{0.0f, 0.0f, 0.0f}, 40.0f} // mSph + } // mSphAttr }; } // namespace @@ -84,7 +85,7 @@ daE_FZ_HIO_c::daE_FZ_HIO_c() { } s32 daE_FZ_c::draw() { - if (field_0x714 == 2 && !checkItemGet(dItemNo_IRONBALL_e,1)) { + if (field_0x714 == 2 && !checkItemGet(dItemNo_IRONBALL_e, 1)) { return 1; } @@ -100,13 +101,13 @@ s32 daE_FZ_c::draw() { pos.set(current.pos.x, current.pos.y + 10.0f, current.pos.z); field_0x70c = dComIfGd_setShadow(field_0x70c, 1, model, &pos, 300.0f, 0.0f, current.pos.y, - mObjAcch.GetGroundH(), mObjAcch.m_gnd, &tevStr, - 0, 1.0f, &dDlst_shadowControl_c::mSimpleTexObj); + mObjAcch.GetGroundH(), mObjAcch.m_gnd, &tevStr, 0, 1.0f, + &dDlst_shadowControl_c::mSimpleTexObj); return 1; } -static void daE_FZ_Draw(daE_FZ_c* i_this) { - i_this->draw(); +static int daE_FZ_Draw(daE_FZ_c* i_this) { + return i_this->draw(); } void daE_FZ_c::setActionMode(int i_actionMode, int i_actionPhase) { @@ -136,7 +137,7 @@ void daE_FZ_c::mBoundSoundset() { if (speed < 1) speed = 1; - mCreature.startCreatureSound(Z2SE_EN_FZ_BOUND,speed,-1); + mCreature.startCreatureSound(Z2SE_EN_FZ_BOUND, speed, -1); } void daE_FZ_c::deadnextSet(bool param_0) { @@ -150,7 +151,7 @@ void daE_FZ_c::deadnextSet(bool param_0) { } mTgCoSph.ClrTgHit(); - fopAcM_OffStatus(this,0); + fopAcM_OffStatus(this, 0); attention_info.flags &= ~fopAc_AttnFlag_BATTLE_e; mAtSph.OffAtSetBit(); @@ -160,7 +161,7 @@ void daE_FZ_c::deadnextSet(bool param_0) { speedF = 0.0f; field_0x6fc = 0; - setActionMode(ACT_DAMAGE,0); + setActionMode(ACT_DAMAGE, 0); } static u8 data_806C1BA0; @@ -168,178 +169,186 @@ static u8 data_806C1BA0; static daE_FZ_HIO_c l_HIO; void daE_FZ_c::damage_check() { - csXyz s_pos; - cXyz pos; - cXyz pos2; - cXyz pos3; + csXyz s_pos; + cXyz pos; + cXyz pos2; + cXyz pos3; - if (1 < health) { - scale.set(l_HIO.field_0x0c, l_HIO.field_0x0c, l_HIO.field_0x0c); - setMidnaBindEffect(this, &mCreature, ¤t.pos, &scale); + if (1 < health) { + scale.set(l_HIO.field_0x0c, l_HIO.field_0x0c, l_HIO.field_0x0c); + setMidnaBindEffect(this, &mCreature, ¤t.pos, &scale); - if (field_0x712 == 0) { - pos.set(dComIfGp_getPlayer(0)->current.pos); - mStts.Move(); + if (field_0x712 == 0) { + pos.set(dComIfGp_getPlayer(0)->current.pos); + mStts.Move(); - if (field_0x714 == 3) { - if (mTgCoSph.ChkTgHit()) { - mAtInfo.mpCollider = mTgCoSph.GetTgHitObj(); + if (field_0x714 == 3) { + if (mTgCoSph.ChkTgHit()) { + mAtInfo.mpCollider = mTgCoSph.GetTgHitObj(); - if (mTgCoSph.GetTgHitObj()->ChkAtType(AT_TYPE_IRON_BALL)) { - deadnextSet(false); - } - } - } else { - if (mTgCoSph.ChkTgHit()) { - mAtInfo.mpCollider = mTgCoSph.GetTgHitObj(); - - if (mTgCoSph.GetTgHitObj()->ChkAtType(AT_TYPE_40) || mTgCoSph.GetTgHitObj()->ChkAtType(AT_TYPE_BOOMERANG)) { - current.angle.y = fopAcM_searchPlayerAngleY(this) + 32768; - f32 tmp_l_hio = l_HIO.field_0x28; - speedF = tmp_l_hio; - field_0x6fc = tmp_l_hio; - mBoundSoundset(); - setActionMode(ACT_DAMAGE,1); - return; - } - - pos2 = current.pos - *mTgCoSph.GetTgHitPosP(); - pos3.set(*mTgCoSph.GetTgHitPosP()); - - s_pos.x = 0; - s_pos.y = pos2.atan2sX_Z(); - s_pos.z = 0; - - if (mTgCoSph.GetTgHitObj()->ChkAtType(AT_TYPE_SPINNER) || mTgCoSph.GetTgHitObj()->ChkAtType(AT_TYPE_ARROW)) { - current.angle.y = fopAcM_searchPlayerAngleY(this) + 32768; - f32 tmp_l_hio = l_HIO.field_0x28; - speedF = tmp_l_hio; - field_0x6fc = tmp_l_hio; - mBoundSoundset(); - dComIfGp_setHitMark(2,this,&pos3,&s_pos,0,AT_TYPE_0); - setActionMode(ACT_DAMAGE,1); - return; - } - - cXyz cStack_54(l_HIO.field_0x0c, l_HIO.field_0x0c, l_HIO.field_0x0c); - dComIfGp_particle_set(0x85ba, ¤t.pos, &shape_angle, &cStack_54); - - if (mTgCoSph.GetTgHitObj()->ChkAtType(AT_TYPE_HOOKSHOT)) { - health -= 20; - - if (1 < health) { - current.angle.y = fopAcM_searchPlayerAngleY(this) + 32768; - - f32 tmp_l_hio = l_HIO.field_0x28; - speedF = tmp_l_hio; - field_0x6fc = tmp_l_hio; - mCreature.startCreatureSound(Z2SE_EN_FZ_DAMAGE,0,-1); - - f32 tmp_l_hio2 = l_HIO.field_0x28; - speedF = tmp_l_hio2; - field_0x6fc = tmp_l_hio2; - setActionMode(ACT_DAMAGE,1); - dComIfGp_setHitMark(3,this,&pos3,&s_pos,0,AT_TYPE_0); - return; - } - - deadnextSet(true); - dComIfGp_setHitMark(1,this,&pos3,&s_pos,0,AT_TYPE_0); - return; - } - - if (mTgCoSph.GetTgHitObj()->ChkAtType(AT_TYPE_IRON_BALL)) { - deadnextSet(false); - mTgCoSph.ClrTgHit(); - dComIfGp_setHitMark(3,this,&pos3,&s_pos,0,AT_TYPE_0); - return; - } - - cc_at_check(this,&mAtInfo); - - if (mAtInfo.mHitStatus == 0) { - dComIfGp_setHitMark(1,this,&pos3,&s_pos,0,AT_TYPE_0); - } else { - dComIfGp_setHitMark(3,this,&pos3,&s_pos,0,AT_TYPE_0); - } - - mLastWallHitAngle = mAtInfo.mHitDirection.y; - setReflectAngle(); - current.angle.y += -32768; - field_0x712 = 10; - - if (1 < health) { - mCreature.startCreatureSound(Z2SE_EN_FZ_DAMAGE,0,-1); - f32 tmp_l_hio = l_HIO.field_0x28; - speedF = tmp_l_hio; - field_0x6fc = tmp_l_hio; - setActionMode(ACT_DAMAGE,1); - return; - } - - deadnextSet(true); - return; - } else { - if (mObjAcch.ChkGroundHit() && mTgCoSph.ChkCoHit()) { - fopAc_ac_c* co_hit_actor = mTgCoSph.GetCoHitAc(); - - if (fopAcM_IsActor(co_hit_actor) && fopAcM_GetName(co_hit_actor) == fpcNm_E_FZ_e) { - pos = current.pos - mTgCoSph.GetCoHitAc()->current.pos; - mTgCoSph.ClrCoHit(); - f32 co_hit_actor_speed = co_hit_actor->speedF; - - if (co_hit_actor_speed > l_HIO.field_0x28 * 0.2f || speedF > l_HIO.field_0x28 * 0.2f) { - pos = current.pos - co_hit_actor->current.pos; - mLastWallHitAngle = pos.atan2sX_Z(); - setReflectAngle(); - - f32 tmp2 = speedF; - f32 tmp = co_hit_actor->speedF; - - if (speedF > tmp) { - co_hit_actor->speedF = tmp2; - static_cast(co_hit_actor)->field_0x6fc = tmp2; - } else { - speedF = tmp; - field_0x6fc = tmp; + if (mTgCoSph.GetTgHitObj()->ChkAtType(AT_TYPE_IRON_BALL)) { + deadnextSet(false); + } } - - mBoundSoundset(); - setActionMode(ACT_DAMAGE,5); - return; - } - } - } - - if (mAtSph.ChkAtHit()) { - fopAc_ac_c* player = dComIfGp_getPlayer(0); - fopAc_ac_c* at_hit_actor = mAtSph.GetAtHitAc(); - - current.angle.y = fopAcM_searchPlayerAngleY(this) + 32768; - - if (player != at_hit_actor) { - mAtSph.ClrAtHit(); } else { - if (mAtSph.ChkAtShieldHit()) { - f32 l_hio_28 = l_HIO.field_0x28; - speedF = l_hio_28; - field_0x6fc = l_hio_28; - setActionMode(ACT_DAMAGE,1); + if (mTgCoSph.ChkTgHit()) { + mAtInfo.mpCollider = mTgCoSph.GetTgHitObj(); - } else { - if (mActionMode != ACT_DAMAGE) { - field_0x712 = 10; - setActionMode(ACT_DAMAGE,3); + if (mTgCoSph.GetTgHitObj()->ChkAtType(AT_TYPE_40) || + mTgCoSph.GetTgHitObj()->ChkAtType(AT_TYPE_BOOMERANG)) + { + current.angle.y = fopAcM_searchPlayerAngleY(this) + 32768; + f32 tmp_l_hio = l_HIO.field_0x28; + speedF = tmp_l_hio; + field_0x6fc = tmp_l_hio; + mBoundSoundset(); + setActionMode(ACT_DAMAGE, 1); + return; + } + + pos2 = current.pos - *mTgCoSph.GetTgHitPosP(); + pos3.set(*mTgCoSph.GetTgHitPosP()); + + s_pos.x = 0; + s_pos.y = pos2.atan2sX_Z(); + s_pos.z = 0; + + if (mTgCoSph.GetTgHitObj()->ChkAtType(AT_TYPE_SPINNER) || + mTgCoSph.GetTgHitObj()->ChkAtType(AT_TYPE_ARROW)) + { + current.angle.y = fopAcM_searchPlayerAngleY(this) + 32768; + f32 tmp_l_hio = l_HIO.field_0x28; + speedF = tmp_l_hio; + field_0x6fc = tmp_l_hio; + mBoundSoundset(); + dComIfGp_setHitMark(2, this, &pos3, &s_pos, 0, AT_TYPE_0); + setActionMode(ACT_DAMAGE, 1); + return; + } + + cXyz cStack_54(l_HIO.field_0x0c, l_HIO.field_0x0c, l_HIO.field_0x0c); + dComIfGp_particle_set(0x85ba, ¤t.pos, &shape_angle, &cStack_54); + + if (mTgCoSph.GetTgHitObj()->ChkAtType(AT_TYPE_HOOKSHOT)) { + health -= 20; + + if (1 < health) { + current.angle.y = fopAcM_searchPlayerAngleY(this) + 32768; + + f32 tmp_l_hio = l_HIO.field_0x28; + speedF = tmp_l_hio; + field_0x6fc = tmp_l_hio; + mCreature.startCreatureSound(Z2SE_EN_FZ_DAMAGE, 0, -1); + + f32 tmp_l_hio2 = l_HIO.field_0x28; + speedF = tmp_l_hio2; + field_0x6fc = tmp_l_hio2; + setActionMode(ACT_DAMAGE, 1); + dComIfGp_setHitMark(3, this, &pos3, &s_pos, 0, AT_TYPE_0); + return; + } + + deadnextSet(true); + dComIfGp_setHitMark(1, this, &pos3, &s_pos, 0, AT_TYPE_0); + return; + } + + if (mTgCoSph.GetTgHitObj()->ChkAtType(AT_TYPE_IRON_BALL)) { + deadnextSet(false); + mTgCoSph.ClrTgHit(); + dComIfGp_setHitMark(3, this, &pos3, &s_pos, 0, AT_TYPE_0); + return; + } + + cc_at_check(this, &mAtInfo); + + if (mAtInfo.mHitStatus == 0) { + dComIfGp_setHitMark(1, this, &pos3, &s_pos, 0, AT_TYPE_0); + } else { + dComIfGp_setHitMark(3, this, &pos3, &s_pos, 0, AT_TYPE_0); + } + + mLastWallHitAngle = mAtInfo.mHitDirection.y; + setReflectAngle(); + current.angle.y += -32768; + field_0x712 = 10; + + if (1 < health) { + mCreature.startCreatureSound(Z2SE_EN_FZ_DAMAGE, 0, -1); + f32 tmp_l_hio = l_HIO.field_0x28; + speedF = tmp_l_hio; + field_0x6fc = tmp_l_hio; + setActionMode(ACT_DAMAGE, 1); + return; + } + + deadnextSet(true); + return; + } else { + if (mObjAcch.ChkGroundHit() && mTgCoSph.ChkCoHit()) { + fopAc_ac_c* co_hit_actor = mTgCoSph.GetCoHitAc(); + + if (fopAcM_IsActor(co_hit_actor) && + fopAcM_GetName(co_hit_actor) == fpcNm_E_FZ_e) + { + pos = current.pos - mTgCoSph.GetCoHitAc()->current.pos; + mTgCoSph.ClrCoHit(); + f32 co_hit_actor_speed = co_hit_actor->speedF; + + if (co_hit_actor_speed > l_HIO.field_0x28 * 0.2f || + speedF > l_HIO.field_0x28 * 0.2f) + { + pos = current.pos - co_hit_actor->current.pos; + mLastWallHitAngle = pos.atan2sX_Z(); + setReflectAngle(); + + f32 tmp2 = speedF; + f32 tmp = co_hit_actor->speedF; + + if (speedF > tmp) { + co_hit_actor->speedF = tmp2; + static_cast(co_hit_actor)->field_0x6fc = tmp2; + } else { + speedF = tmp; + field_0x6fc = tmp; + } + + mBoundSoundset(); + setActionMode(ACT_DAMAGE, 5); + return; + } + } + } + + if (mAtSph.ChkAtHit()) { + fopAc_ac_c* player = dComIfGp_getPlayer(0); + fopAc_ac_c* at_hit_actor = mAtSph.GetAtHitAc(); + + current.angle.y = fopAcM_searchPlayerAngleY(this) + 32768; + + if (player != at_hit_actor) { + mAtSph.ClrAtHit(); + } else { + if (mAtSph.ChkAtShieldHit()) { + f32 l_hio_28 = l_HIO.field_0x28; + speedF = l_hio_28; + field_0x6fc = l_hio_28; + setActionMode(ACT_DAMAGE, 1); + + } else { + if (mActionMode != ACT_DAMAGE) { + field_0x712 = 10; + setActionMode(ACT_DAMAGE, 3); + } + } + mBoundSoundset(); + mAtSph.ClrAtHit(); + } + } } - } - mBoundSoundset(); - mAtSph.ClrAtHit(); } - } } - } } - } } bool daE_FZ_c::way_gake_check() { @@ -350,13 +359,13 @@ bool daE_FZ_c::way_gake_check() { return false; } - cMtx_YrotS((MtxP)calc_mtx,current.angle.y); + cMtx_YrotS((MtxP)calc_mtx, current.angle.y); pos.x = 0.0f; pos.y = 300.0f; pos.z = 70.0f; - MtxPosition(&pos,&field_0x6dc); + MtxPosition(&pos, &field_0x6dc); field_0x6dc += current.pos; field_0x6e8.set(field_0x6dc); @@ -374,234 +383,231 @@ bool daE_FZ_c::way_gake_check() { } void daE_FZ_c::executeWait() { - cXyz pos; - cXyz pos2; - s16 angle; - f32 tmp = l_HIO.field_0x14; + cXyz pos; + cXyz pos2; + s16 angle; + f32 tmp = l_HIO.field_0x14; - switch (mActionPhase) { - case 0: - if (fopAcM_wayBgCheck(this,200.0f,50.0f)) { - angle = cM_rndFX(10000.0f) + 32768.0f; - } else { + switch (mActionPhase) { + case 0: + if (fopAcM_wayBgCheck(this, 200.0f, 50.0f)) { + angle = cM_rndFX(10000.0f) + 32768.0f; + } else { + pos2.x = home.pos.x + cM_rndFX(l_HIO.field_0x10); + pos2.y = home.pos.y; + pos2.z = home.pos.z + cM_rndFX(l_HIO.field_0x10); - pos2.x = home.pos.x + cM_rndFX(l_HIO.field_0x10); - pos2.y = home.pos.y; - pos2.z = home.pos.z + cM_rndFX(l_HIO.field_0x10); + pos = pos2 - current.pos; - pos = pos2 - current.pos; + angle = pos.atan2sX_Z() - current.angle.y; - angle = pos.atan2sX_Z() - current.angle.y; - - if (angle > 12288) { - angle = 12288; - } - else { - if (angle < -12288) - angle = -12288; - } - } - - mAngleFromPlayer = current.angle.y + angle; - field_0x710 = l_HIO.field_0x06 + cM_rndFX(l_HIO.field_0x30); - mActionPhase = 1; - case 1: - if (way_gake_check()) { - pos2 = current.pos - home.pos; - angle = pos2.atan2sX_Z(); - mAngleFromPlayer = angle; - current.angle.y = angle; - } - - if (field_0x714 == 4) { - field_0x710 = 10; - speedF = 0.0f; - angle = fopAcM_searchPlayerAngleY(this); - mAngleFromPlayer = angle; - current.angle.y = angle; - tmp = l_HIO.field_0x18; - } - - if (mObjAcch.ChkGroundHit() && dComIfG_Bgsp().GetPolyAtt0(mObjAcch.m_gnd) == 8) { - angle = shape_angle.y - mAngleFromPlayer; - - if (abs(angle) < 512 && field_0x710 == 0) { - cLib_addCalc0(&speedF, 0.1f, 0.1f); + if (angle > 12288) { + angle = 12288; + } else { + if (angle < -12288) + angle = -12288; + } + } + + mAngleFromPlayer = current.angle.y + angle; + field_0x710 = l_HIO.field_0x06 + cM_rndFX(l_HIO.field_0x30); + mActionPhase = 1; + case 1: + if (way_gake_check()) { + pos2 = current.pos - home.pos; + angle = pos2.atan2sX_Z(); + mAngleFromPlayer = angle; + current.angle.y = angle; + } + + if (field_0x714 == 4) { + field_0x710 = 10; + speedF = 0.0f; + angle = fopAcM_searchPlayerAngleY(this); + mAngleFromPlayer = angle; + current.angle.y = angle; + tmp = l_HIO.field_0x18; + } + + if (mObjAcch.ChkGroundHit() && dComIfG_Bgsp().GetPolyAtt0(mObjAcch.m_gnd) == 8) { + angle = shape_angle.y - mAngleFromPlayer; + + if (abs(angle) < 512 && field_0x710 == 0) { + cLib_addCalc0(&speedF, 0.1f, 0.1f); + } + } else { + cLib_addCalc0(&speedF, 0.1f, l_HIO.field_0x2c); + } + + if (field_0x710 == 0 && speedF < 0.2f) { + angle = shape_angle.y - mAngleFromPlayer; + + if (abs(angle) < 512) { + current.angle.y = shape_angle.y; + setActionMode(ACT_MOVE, 0); + } } - } else { - cLib_addCalc0(&speedF,0.1f,l_HIO.field_0x2c); } - if (field_0x710 == 0 && speedF < 0.2f) { - angle = shape_angle.y - mAngleFromPlayer; + cLib_addCalcAngleS2(&shape_angle.y, mAngleFromPlayer, 8, 1280); - if (abs(angle) < 512) { - current.angle.y = shape_angle.y; - setActionMode(ACT_MOVE,0); - } + if (fopAcM_searchPlayerDistance(this) <= tmp && !way_gake_check()) { + if (!fopAcM_otherBgCheck(this, dComIfGp_getPlayer(0))) { + current.angle.y = shape_angle.y; + setActionMode(ACT_ATTACK, 0); + } } - } - - cLib_addCalcAngleS2(&shape_angle.y,mAngleFromPlayer,8,1280); - - if (fopAcM_searchPlayerDistance(this) <= tmp && !way_gake_check()) { - if (!fopAcM_otherBgCheck(this,dComIfGp_getPlayer(0))) { - current.angle.y = shape_angle.y; - setActionMode(ACT_ATTACK,0); - } - } } void daE_FZ_c::executeMove() { switch (mActionPhase) { - case 0: - field_0x710 = l_HIO.field_0x08 + cM_rndFX(l_HIO.field_0x34); - mActionPhase = 1; - case 1: - cLib_addCalcAngleS2(¤t.angle.y,mAngleFromPlayer,8,256); - cLib_addCalc2(&speedF,l_HIO.field_0x1c,1.0f,3.0f); + case 0: + field_0x710 = l_HIO.field_0x08 + cM_rndFX(l_HIO.field_0x34); + mActionPhase = 1; + case 1: + cLib_addCalcAngleS2(¤t.angle.y, mAngleFromPlayer, 8, 256); + cLib_addCalc2(&speedF, l_HIO.field_0x1c, 1.0f, 3.0f); - if (fopAcM_wayBgCheck(this, 200.0f,50.0f) != 0 || field_0x710 == 0) { - setActionMode(ACT_WAIT,0); - } - default: - if (way_gake_check()) { - speedF = 0; - setActionMode(ACT_WAIT,0); - } else { - shape_angle.y = current.angle.y; + if (fopAcM_wayBgCheck(this, 200.0f, 50.0f) != 0 || field_0x710 == 0) { + setActionMode(ACT_WAIT, 0); + } + default: + if (way_gake_check()) { + speedF = 0; + setActionMode(ACT_WAIT, 0); + } else { + shape_angle.y = current.angle.y; - if (fopAcM_searchPlayerDistance(this) <= l_HIO.field_0x14) { - setActionMode(ACT_ATTACK,0); - } + if (fopAcM_searchPlayerDistance(this) <= l_HIO.field_0x14) { + setActionMode(ACT_ATTACK, 0); } + } } } void daE_FZ_c::executeAttack() { switch (mActionPhase) { case 0: - cLib_addCalcAngleS2(¤t.angle.y,fopAcM_searchPlayerAngleY(this),8,0x300); + cLib_addCalcAngleS2(¤t.angle.y, fopAcM_searchPlayerAngleY(this), 8, 0x300); if (way_gake_check() == 0) { - cLib_addCalc2(&speedF,l_HIO.field_0x20,0.7f,1.0f); + cLib_addCalc2(&speedF, l_HIO.field_0x20, 0.7f, 1.0f); } else { speedF = 0.0f; } default: shape_angle.y = current.angle.y; if (!(fopAcM_searchPlayerDistance(this) >= l_HIO.field_0x10)) { - if (fopAcM_otherBgCheck(this,dComIfGp_getPlayer(0)) == 0) { + if (fopAcM_otherBgCheck(this, dComIfGp_getPlayer(0)) == 0) { return; } } } - setActionMode(ACT_WAIT,0); + setActionMode(ACT_WAIT, 0); } void daE_FZ_c::executeDamage() { - cXyz pos; - pos.set(l_HIO.field_0x0c, l_HIO.field_0x0c, l_HIO.field_0x0c); - f32 tmp; + cXyz pos; + pos.set(l_HIO.field_0x0c, l_HIO.field_0x0c, l_HIO.field_0x0c); + f32 tmp; - switch(mActionPhase) { - case 0: - dComIfGp_particle_set(0x85b8,¤t.pos,&shape_angle,&pos); - dComIfGp_particle_set(0x85b9,¤t.pos,&shape_angle,&pos); + switch (mActionPhase) { + case 0: + dComIfGp_particle_set(0x85b8, ¤t.pos, &shape_angle, &pos); + dComIfGp_particle_set(0x85b9, ¤t.pos, &shape_angle, &pos); - if (field_0x714 != 3) { - field_0x564 = 25; - fopAcM_createItemFromEnemyID(field_0x564,¤t.pos,-1,-1,0,0,0,0); - } else { - if (cM_rnd() < 0.2f) { - fopAcM_createItem(¤t.pos,0,-1,-1,0,0,0); - } - } - fopAcM_delete(this); - break; - case 1: - tmp = l_HIO.field_0x28; - speedF = tmp; - field_0x6fc = tmp; - case 5: - mStts.SetWeight(118); - current.angle.y < 0 ? field_0x704 = 0 : field_0x704 = 1; - mActionPhase = 2; - case 2: - if (mObjAcch.ChkGroundHit() && dComIfG_Bgsp().GetPolyAtt0(mObjAcch.m_gnd) == 8) { - tmp = 0.2f; - } else { - tmp = 1.0f; - } + if (field_0x714 != 3) { + field_0x564 = 25; + fopAcM_createItemFromEnemyID(field_0x564, ¤t.pos, -1, -1, 0, 0, 0, 0); + } else { + if (cM_rnd() < 0.2f) { + fopAcM_createItem(¤t.pos, 0, -1, -1, 0, 0, 0); + } + } + fopAcM_delete(this); + break; + case 1: + tmp = l_HIO.field_0x28; + speedF = tmp; + field_0x6fc = tmp; + case 5: + mStts.SetWeight(118); + current.angle.y < 0 ? field_0x704 = 0 : field_0x704 = 1; + mActionPhase = 2; + case 2: + if (mObjAcch.ChkGroundHit() && dComIfG_Bgsp().GetPolyAtt0(mObjAcch.m_gnd) == 8) { + tmp = 0.2f; + } else { + tmp = 1.0f; + } - cLib_addCalc0(&speedF,0.1f,tmp); + cLib_addCalc0(&speedF, 0.1f, tmp); - if (field_0x704 == 0) { - s16 value = 4096.0f - (4096.0f / field_0x6fc) * (field_0x6fc - speedF); - shape_angle.y -= value; - } else { - s16 value = 4096.0f - (4096.0f / field_0x6fc) * (field_0x6fc - speedF); - shape_angle.y += value; - } + if (field_0x704 == 0) { + s16 value = 4096.0f - (4096.0f / field_0x6fc) * (field_0x6fc - speedF); + shape_angle.y -= value; + } else { + s16 value = 4096.0f - (4096.0f / field_0x6fc) * (field_0x6fc - speedF); + shape_angle.y += value; + } - if (mObjAcch.ChkWallHit()) { - mLastWallHitAngle = mAcchCir.GetWallAngleY(); - setReflectAngle(); - mBoundSoundset(); - } + if (mObjAcch.ChkWallHit()) { + mLastWallHitAngle = mAcchCir.GetWallAngleY(); + setReflectAngle(); + mBoundSoundset(); + } - if (speedF < 0.3f) { - current.angle.y = shape_angle.y; - mStts.SetWeight(100); - setActionMode(ACT_WAIT,0); - } - break; - case 3: - mAngleFromPlayer = fopAcM_searchPlayerAngleY(this); - if (current.angle.y < 0) { - field_0x704 = 0; - } - else { - field_0x704 = 1; - } - tmp = l_HIO.field_0x24; - speedF = tmp; - field_0x6fc = tmp; - mActionPhase = 4; - case 4: - if (field_0x704 == 0) { - s16 value = 4096.0f - (4096.0f / field_0x6fc) * (field_0x6fc - speedF); - shape_angle.y -= value; - } - else { - s16 value = 4096.0f - (4096.0f / field_0x6fc) * (field_0x6fc - speedF); - shape_angle.y += value; - } + if (speedF < 0.3f) { + current.angle.y = shape_angle.y; + mStts.SetWeight(100); + setActionMode(ACT_WAIT, 0); + } + break; + case 3: + mAngleFromPlayer = fopAcM_searchPlayerAngleY(this); + if (current.angle.y < 0) { + field_0x704 = 0; + } else { + field_0x704 = 1; + } + tmp = l_HIO.field_0x24; + speedF = tmp; + field_0x6fc = tmp; + mActionPhase = 4; + case 4: + if (field_0x704 == 0) { + s16 value = 4096.0f - (4096.0f / field_0x6fc) * (field_0x6fc - speedF); + shape_angle.y -= value; + } else { + s16 value = 4096.0f - (4096.0f / field_0x6fc) * (field_0x6fc - speedF); + shape_angle.y += value; + } - cLib_addCalcAngleS2(¤t.angle.y,mAngleFromPlayer,1,512); - cLib_addCalc0(&speedF,0.1f,0.1f); + cLib_addCalcAngleS2(¤t.angle.y, mAngleFromPlayer, 1, 512); + cLib_addCalc0(&speedF, 0.1f, 0.1f); - if (mObjAcch.ChkWallHit()) { - mLastWallHitAngle = mAcchCir.GetWallAngleY(); - setReflectAngle(); - mBoundSoundset(); - } + if (mObjAcch.ChkWallHit()) { + mLastWallHitAngle = mAcchCir.GetWallAngleY(); + setReflectAngle(); + mBoundSoundset(); + } - if (speedF < 0.2f) { - current.angle.y = shape_angle.y; - setActionMode(ACT_ATTACK,0); + if (speedF < 0.2f) { + current.angle.y = shape_angle.y; + setActionMode(ACT_ATTACK, 0); + } + break; + case 6: + health = 0; + if (field_0x710 == 1 || mObjAcch.ChkGroundHit()) { + mActionPhase = 0; + } } - break; - case 6: - health = 0; - if (field_0x710 == 1 || mObjAcch.ChkGroundHit()) { - mActionPhase = 0; - } - } } void daE_FZ_c::executeRollMove() { - if (fopAcM_SearchByID(fopAcM_GetLinkId(this),&mpBlizzetaActor) == 0 || !mpBlizzetaActor) return; + if (fopAcM_SearchByID(fopAcM_GetLinkId(this), &mpBlizzetaActor) == 0 || !mpBlizzetaActor) + return; u32 model_no = static_cast(mpBlizzetaActor)->getModelNo(); if (model_no < 4 || 6 < model_no) { @@ -625,10 +631,10 @@ void daE_FZ_c::executeRollMove() { field_0x710 = (20 - field_0x715) * 2; case 1: if (field_0x710 == 0) { - cLib_chaseF(&mRadiusBase,1.0f,0.03f); + cLib_chaseF(&mRadiusBase, 1.0f, 0.03f); } - cLib_chaseAngleS(&field_0x704,1024,16); + cLib_chaseAngleS(&field_0x704, 1024, 16); pos = mpBlizzetaActor->current.pos; pos.x += (f32)(mode_rarius * cM_ssin(roll_angle + field_0x715 * 0xccc)); @@ -639,23 +645,24 @@ void daE_FZ_c::executeRollMove() { if (static_cast(mpBlizzetaActor)->getFrizadAttack() == 3) { mActionPhase = 2; speedF = 60.0f; - current.angle.y = cLib_targetAngleY(&static_cast(mpBlizzetaActor)->current.pos,¤t.pos); + current.angle.y = cLib_targetAngleY( + &static_cast(mpBlizzetaActor)->current.pos, ¤t.pos); } break; case 2: - cLib_chaseF(&mRadiusBase,1.0,0.1); - cLib_chaseAngleS(&field_0x704,512,16); + cLib_chaseF(&mRadiusBase, 1.0, 0.1); + cLib_chaseAngleS(&field_0x704, 512, 16); if (mObjAcch.ChkWallHit() || !mObjAcch.ChkGroundHit()) { - setActionMode(ACT_DAMAGE,0); - mCreature.startCreatureSound(Z2SE_EN_FZ_DEATH,0,-1); + setActionMode(ACT_DAMAGE, 0); + mCreature.startCreatureSound(Z2SE_EN_FZ_DEATH, 0, -1); return; } if (mAtSph.ChkAtHit()) { fopAc_ac_c* at_hit_actor = mAtSph.GetAtHitAc(); if ((fopAcM_GetName(at_hit_actor) == fpcNm_ALINK_e) || mAtSph.ChkAtShieldHit()) { - setActionMode(ACT_DAMAGE,0); + setActionMode(ACT_DAMAGE, 0); return; } } @@ -787,26 +794,26 @@ void daE_FZ_c::action() { } void daE_FZ_c::mtx_set() { - mDoMtx_stack_c::transS(current.pos.x,current.pos.y,current.pos.z); - mDoMtx_stack_c::ZXYrotM(shape_angle); - mDoMtx_stack_c::scaleM(l_HIO.field_0x0c,l_HIO.field_0x0c,l_HIO.field_0x0c); - mDoMtx_stack_c::scaleM(mRadiusBase,mRadiusBase,mRadiusBase); - mpModel->setBaseTRMtx(mDoMtx_stack_c::get()); + mDoMtx_stack_c::transS(current.pos.x, current.pos.y, current.pos.z); + mDoMtx_stack_c::ZXYrotM(shape_angle); + mDoMtx_stack_c::scaleM(l_HIO.field_0x0c, l_HIO.field_0x0c, l_HIO.field_0x0c); + mDoMtx_stack_c::scaleM(mRadiusBase, mRadiusBase, mRadiusBase); + mpModel->setBaseTRMtx(mDoMtx_stack_c::get()); } void daE_FZ_c::cc_set() { cXyz pos; cXyz pos2; - pos.set(0.0f,60.0f,0.0f); - mDoMtx_stack_c::multVec(&pos,&eyePos); + pos.set(0.0f, 60.0f, 0.0f); + mDoMtx_stack_c::multVec(&pos, &eyePos); attention_info.position = eyePos; attention_info.position.y += 25.0f; mDoMtx_stack_c::copy(mpModel->getBaseTRMtx()); - pos.set(0.0f,40.0f,0.0f); - mDoMtx_stack_c::multVec(&pos,&pos2); + pos.set(0.0f, 40.0f, 0.0f); + mDoMtx_stack_c::multVec(&pos, &pos2); mTgCoSph.SetC(pos2); mTgCoSph.SetR(mRadiusBase * 60.0f); @@ -815,8 +822,8 @@ void daE_FZ_c::cc_set() { mDoMtx_stack_c::copy(mpModel->getBaseTRMtx()); - pos.set(0.0f,25.0f,0.0f); - mDoMtx_stack_c::multVec(&pos,&pos2); + pos.set(0.0f, 25.0f, 0.0f); + mDoMtx_stack_c::multVec(&pos, &pos2); mAtSph.SetC(pos2); mAtSph.SetR(mRadiusBase * 40.0f); @@ -826,16 +833,16 @@ void daE_FZ_c::cc_set() { s32 daE_FZ_c::execute() { if (field_0x714 == 2) { - if (checkItemGet(dItemNo_IRONBALL_e,1) == 0) { + if (checkItemGet(dItemNo_IRONBALL_e, 1) == 0) { return 1; } if (attention_info.distances[fopAc_attn_BATTLE_e] == 0) { attention_info.distances[fopAc_attn_BATTLE_e] = 69; - fopAcM_SetGroup(this,2); - #if DEBUG - fopAcM_OnStatus(this,0); - #endif + fopAcM_SetGroup(this, 2); +#if DEBUG + fopAcM_OnStatus(this, 0); +#endif attention_info.flags |= fopAc_AttnFlag_BATTLE_e; } } @@ -849,9 +856,9 @@ s32 daE_FZ_c::execute() { if (field_0x712 != 0) field_0x712 -= 1; - action(); // set current action - mtx_set(); // update model matrix - cc_set(); // update sphere colliders + action(); // set current action + mtx_set(); // update model matrix + cc_set(); // update sphere colliders mCreature.framework(0, dComIfGp_getReverb(fopAcM_GetRoomNo(this))); @@ -875,8 +882,8 @@ s32 daE_FZ_c::execute() { return 1; } -static void daE_FZ_Execute(daE_FZ_c* i_this) { - i_this->execute(); +static int daE_FZ_Execute(daE_FZ_c* i_this) { + return i_this->execute(); } void daE_FZ_c::demoDelete() { @@ -887,12 +894,12 @@ void daE_FZ_c::demoDelete() { fopAcM_delete(this); } -static bool daE_FZ_IsDelete(daE_FZ_c* i_this) { - return true; +static int daE_FZ_IsDelete(daE_FZ_c* i_this) { + return 1; } s32 daE_FZ_c::_delete() { - dComIfG_resDelete(&mPhaseReq,"E_FZ"); + dComIfG_resDelete(&mPhaseReq, "E_FZ"); if (field_0xc21 != 0) { data_806C1BA0 = 0; @@ -905,8 +912,8 @@ s32 daE_FZ_c::_delete() { return 1; } -static void daE_FZ_Delete(daE_FZ_c* i_this) { - i_this->_delete(); +static int daE_FZ_Delete(daE_FZ_c* i_this) { + return i_this->_delete(); } s32 daE_FZ_c::CreateHeap() { @@ -921,122 +928,120 @@ s32 daE_FZ_c::CreateHeap() { } static int useHeapInit(fopAc_ac_c* i_this) { - return static_cast(i_this)->CreateHeap(); + return static_cast(i_this)->CreateHeap(); } s32 daE_FZ_c::create() { - fopAcM_ct(this,daE_FZ_c); + fopAcM_ct(this, daE_FZ_c); - s32 phase = dComIfG_resLoad(&mPhaseReq,"E_FZ"); - if (phase == cPhs_COMPLEATE_e) { - if (!fopAcM_entrySolidHeap(this,useHeapInit,6480)) { - return cPhs_ERROR_e; - } - if (data_806C1BA0 == 0) { - data_806C1BA0 = 1; - field_0xc21 = 1; - l_HIO.field_0x04 = -1; + s32 phase = dComIfG_resLoad(&mPhaseReq, "E_FZ"); + if (phase == cPhs_COMPLEATE_e) { + if (!fopAcM_entrySolidHeap(this, useHeapInit, 6480)) { + return cPhs_ERROR_e; + } + if (data_806C1BA0 == 0) { + data_806C1BA0 = 1; + field_0xc21 = 1; + l_HIO.field_0x04 = -1; + } + + attention_info.flags = fopAc_AttnFlag_BATTLE_e; + attention_info.distances[fopAc_attn_BATTLE_e] = 69; + + fopAcM_SetMtx(this, mpModel->getBaseTRMtx()); + fopAcM_SetMin(this, -200.0f, -200.0f, -200.0f); + fopAcM_SetMax(this, 200.0f, 200.0f, 200.0f); + + mStts.Init(100, 0, this); + health = 80; + field_0x560 = 80; + + field_0x714 = fopAcM_GetParam(this); + field_0x715 = fopAcM_GetParam(this) >> 8; + + if (field_0x714 == 255) + field_0x714 = 0; + + if (field_0x714 == 1 || field_0x714 == 3) { + speed.y = cM_rndFX(10.0f) + 30.0f; + f32 rng = cM_rndFX(1.0f); + speedF = rng + 4.0f; + field_0x6fc = rng + 4.0f; + if (field_0x714 == 1) { + fopAcM_OnStatus(this, fopAcStts_UNK_0x4000_e); + } + } + + mObjAcch.Set(fopAcM_GetPosition_p(this), fopAcM_GetOldPosition_p(this), this, 1, &mAcchCir, + fopAcM_GetSpeed_p(this), 0, 0); + + if (field_0x714 == 3) { + mAcchCir.SetWall(35.0f, 70.0f); + } else { + mAcchCir.SetWall(20.0f, 60.0f); + } + + mObjAcch.CrrPos(dComIfG_Bgsp()); + + mTgCoSph.Set(cc_fz_src); + mTgCoSph.SetStts(&mStts); + + mAtSph.Set(cc_fz_at_src); + mAtSph.SetStts(&mStts); + + mCreature.init(¤t.pos, &eyePos, 3, 1); + mCreature.setEnemyName("E_fz"); + + mAtInfo.mpSound = &mCreature; + mAtInfo.mPowerType = 1; + + gravity = -5.0f; + + shape_angle.z = 0; + shape_angle.x = 0; + + s16 random = cM_rndFX(10000.0f); + shape_angle.y = random; + current.angle.y = random; + + field_0x670.set(current.pos); + + for (int i = 0; i < 4; i++) { + field_0x67c[i].set(current.pos); + } + + if (field_0x714 == 2 && !checkItemGet(dItemNo_IRONBALL_e, 1)) { + attention_info.distances[fopAc_attn_BATTLE_e] = 0; + fopAcM_SetGroup(this, 0); + fopAcM_OffStatus(this, 0); + attention_info.flags &= ~fopAc_AttnFlag_BATTLE_e; + } + + if (field_0x714 == 3) { + mRadiusBase = 0.0f; + attention_info.flags &= ~fopAc_AttnFlag_BATTLE_e; + mAtSph.SetAtType(AT_TYPE_CSTATUE_SWING); + mAtSph.SetAtSpl(dCcG_At_Spl_UNK_1); + setActionMode(ACT_ROLLMOVE, 0); + } else { + mAtSph.SetAtMtrl(dCcD_MTRL_ICE); + mRadiusBase = 1.0f; + cM_rnd() < 0.5f ? setActionMode(ACT_WAIT, 0) : setActionMode(ACT_MOVE, 0); + } + + mtx_set(); } - attention_info.flags = fopAc_AttnFlag_BATTLE_e; - attention_info.distances[fopAc_attn_BATTLE_e] = 69; - - fopAcM_SetMtx(this,mpModel->getBaseTRMtx()); - fopAcM_SetMin(this,-200.0f,-200.0f,-200.0f); - fopAcM_SetMax(this,200.0f,200.0f,200.0f); - - mStts.Init(100,0,this); - health = 80; - field_0x560 = 80; - - field_0x714 = fopAcM_GetParam(this); - field_0x715 = fopAcM_GetParam(this) >> 8; - - if (field_0x714 == 255) - field_0x714 = 0; - - if (field_0x714 == 1 || field_0x714 == 3) { - speed.y = cM_rndFX(10.0f) + 30.0f; - f32 rng = cM_rndFX(1.0f); - speedF = rng + 4.0f; - field_0x6fc = rng + 4.0f; - if (field_0x714 == 1) { - fopAcM_OnStatus(this,fopAcStts_UNK_0x4000_e); - } - } - - mObjAcch.Set(fopAcM_GetPosition_p(this),fopAcM_GetOldPosition_p(this), this, 1, &mAcchCir, fopAcM_GetSpeed_p(this), 0, 0); - - if (field_0x714 == 3) { - mAcchCir.SetWall(35.0f,70.0f); - } - else { - mAcchCir.SetWall(20.0f,60.0f); - } - - mObjAcch.CrrPos(dComIfG_Bgsp()); - - mTgCoSph.Set(cc_fz_src); - mTgCoSph.SetStts(&mStts); - - mAtSph.Set(cc_fz_at_src); - mAtSph.SetStts(&mStts); - - mCreature.init(¤t.pos,&eyePos,3,1); - mCreature.setEnemyName("E_fz"); - - mAtInfo.mpSound = &mCreature; - mAtInfo.mPowerType = 1; - - gravity = -5.0f; - - shape_angle.z = 0; - shape_angle.x = 0; - - s16 random = cM_rndFX(10000.0f); - shape_angle.y = random; - current.angle.y = random; - - field_0x670.set(current.pos); - - for (int i = 0; i < 4; i++) { - field_0x67c[i].set(current.pos); - } - - if (field_0x714 == 2 && !checkItemGet(dItemNo_IRONBALL_e,1)) { - attention_info.distances[fopAc_attn_BATTLE_e] = 0; - fopAcM_SetGroup(this,0); - fopAcM_OffStatus(this,0); - attention_info.flags &= ~fopAc_AttnFlag_BATTLE_e; - } - - if (field_0x714 == 3) { - mRadiusBase = 0.0f; - attention_info.flags &= ~fopAc_AttnFlag_BATTLE_e; - mAtSph.SetAtType(AT_TYPE_CSTATUE_SWING); - mAtSph.SetAtSpl(dCcG_At_Spl_UNK_1); - setActionMode(ACT_ROLLMOVE,0); - } else { - mAtSph.SetAtMtrl(dCcD_MTRL_ICE); - mRadiusBase = 1.0f; - cM_rnd() < 0.5f ? setActionMode(ACT_WAIT,0) : setActionMode(ACT_MOVE,0); - } - - mtx_set(); - } - - return phase; + return phase; } -static void daE_FZ_Create(daE_FZ_c* i_this) { - i_this->create(); +static int daE_FZ_Create(daE_FZ_c* i_this) { + return i_this->create(); } static actor_method_class l_daE_FZ_Method = { - (process_method_func)daE_FZ_Create, - (process_method_func)daE_FZ_Delete, - (process_method_func)daE_FZ_Execute, - (process_method_func)daE_FZ_IsDelete, + (process_method_func)daE_FZ_Create, (process_method_func)daE_FZ_Delete, + (process_method_func)daE_FZ_Execute, (process_method_func)daE_FZ_IsDelete, (process_method_func)daE_FZ_Draw, }; diff --git a/src/d/actor/d_a_e_oc.cpp b/src/d/actor/d_a_e_oc.cpp index bac4901f68..2a81709609 100644 --- a/src/d/actor/d_a_e_oc.cpp +++ b/src/d/actor/d_a_e_oc.cpp @@ -189,8 +189,8 @@ int daE_OC_c::draw() { return 1; } -static void daE_OC_Draw(daE_OC_c* i_this) { - i_this->draw(); +static int daE_OC_Draw(daE_OC_c* i_this) { + return i_this->draw(); } daE_OC_c* E_OC_n::m_battle_oc; @@ -2639,12 +2639,12 @@ int daE_OC_c::execute() { return 1; } -static void daE_OC_Execute(daE_OC_c* i_this) { - i_this->execute(); +static int daE_OC_Execute(daE_OC_c* i_this) { + return i_this->execute(); } -static bool daE_OC_IsDelete(daE_OC_c* param_0) { - return true; +static int daE_OC_IsDelete(daE_OC_c* param_0) { + return 1; } int daE_OC_c::_delete() { @@ -2662,9 +2662,9 @@ int daE_OC_c::_delete() { return 1; } -static void daE_OC_Delete(daE_OC_c* i_this) { +static int daE_OC_Delete(daE_OC_c* i_this) { fopAcM_RegisterDeleteID(i_this, "E_OC"); - i_this->_delete(); + return i_this->_delete(); } int daE_OC_c::CreateHeap() { @@ -2833,8 +2833,8 @@ cPhs_Step daE_OC_c::create() { return phase; } -static void daE_OC_Create(daE_OC_c* i_this) { - i_this->create(); +static int daE_OC_Create(daE_OC_c* i_this) { + return i_this->create(); } static actor_method_class l_daE_OC_Method = { diff --git a/src/d/actor/d_a_e_zs.cpp b/src/d/actor/d_a_e_zs.cpp index fea09e166a..b17ca8f438 100644 --- a/src/d/actor/d_a_e_zs.cpp +++ b/src/d/actor/d_a_e_zs.cpp @@ -495,8 +495,8 @@ int daE_ZS_c::CreateHeap() { return 1; } -static void useHeapInit(fopAc_ac_c* i_this) { - static_cast(i_this)->CreateHeap(); +static int useHeapInit(fopAc_ac_c* i_this) { + return static_cast(i_this)->CreateHeap(); } int daE_ZS_c::create() { @@ -504,7 +504,7 @@ int daE_ZS_c::create() { int phase = dComIfG_resLoad(&mPhase, "E_ZS"); if (phase == cPhs_COMPLEATE_e) { OS_REPORT("E_ZS PARAM %x\n", fopAcM_GetParam(this)); - if (!fopAcM_entrySolidHeap(this, (heapCallbackFunc)useHeapInit, 0xFC0)) { + if (!fopAcM_entrySolidHeap(this, useHeapInit, 0xFC0)) { return cPhs_ERROR_e; } diff --git a/src/d/actor/d_a_myna.cpp b/src/d/actor/d_a_myna.cpp index 064096cd1b..3833fe2034 100644 --- a/src/d/actor/d_a_myna.cpp +++ b/src/d/actor/d_a_myna.cpp @@ -74,9 +74,9 @@ static char* l_bckFileNameTBL[] = { static char* l_btpFileNameTBL[] = {"MYNA.btp"}; -static void createHeapCallBack(fopAc_ac_c* i_this) { +static int createHeapCallBack(fopAc_ac_c* i_this) { daMyna_c* a_this = static_cast(i_this); - a_this->createHeap(); + return a_this->createHeap(); } static int jntNodeCallBack(J3DJoint* i_jnt, int param_1) { @@ -339,7 +339,7 @@ int daMyna_c::create() { return phase; } - if (!fopAcM_entrySolidHeap(this, (heapCallbackFunc)&createHeapCallBack, 0x21F0)) { + if (!fopAcM_entrySolidHeap(this, createHeapCallBack, 0x21F0)) { return cPhs_ERROR_e; } diff --git a/src/d/actor/d_a_npc_sola.cpp b/src/d/actor/d_a_npc_sola.cpp index a70746c4f1..0bac7e4fb6 100644 --- a/src/d/actor/d_a_npc_sola.cpp +++ b/src/d/actor/d_a_npc_sola.cpp @@ -194,14 +194,13 @@ int daNpc_solA_c::Execute() { return execute(); } -void daNpc_solA_c::Draw() { +int daNpc_solA_c::Draw() { if (mpMatAnm[0] != NULL) { J3DModelData* mdlData_p = mpMorf[0]->getModel()->getModelData(); mdlData_p->getMaterialNodePointer(getEyeballMaterialNo())->setMaterialAnm(mpMatAnm[0]); } - draw(FALSE, FALSE, mpHIO->m.common.real_shadow_size, NULL, 100.0f, FALSE, FALSE, - FALSE); - return; + + return draw(FALSE, FALSE, mpHIO->m.common.real_shadow_size, NULL, 100.0f, FALSE, FALSE, FALSE); } BOOL daNpc_solA_c::createHeapCallBack(fopAc_ac_c* a_this) { @@ -481,12 +480,12 @@ static int daNpc_solA_Execute(void* param_0) { return static_cast(param_0)->Execute(); } -static void daNpc_solA_Draw(void* param_0) { +static int daNpc_solA_Draw(void* param_0) { return static_cast(param_0)->Draw(); } -static bool daNpc_solA_IsDelete(void* param_0) { - return true; +static int daNpc_solA_IsDelete(void* param_0) { + return 1; } static actor_method_class daNpc_solA_MethodTable = { diff --git a/src/d/actor/d_a_obj_Y_taihou.cpp b/src/d/actor/d_a_obj_Y_taihou.cpp index ab9d2bf4cf..61f96a12ab 100644 --- a/src/d/actor/d_a_obj_Y_taihou.cpp +++ b/src/d/actor/d_a_obj_Y_taihou.cpp @@ -356,21 +356,21 @@ int daObjYtaihou_c::Delete() { return 1; } -static void daObjYtaihou_create1st(daObjYtaihou_c* i_this) { +static int daObjYtaihou_create1st(daObjYtaihou_c* i_this) { fopAcM_ct(i_this, daObjYtaihou_c); - i_this->create1st(); + return i_this->create1st(); } -static void daObjYtaihou_MoveBGDelete(daObjYtaihou_c* i_this) { - i_this->MoveBGDelete(); +static int daObjYtaihou_MoveBGDelete(daObjYtaihou_c* i_this) { + return i_this->MoveBGDelete(); } -static void daObjYtaihou_MoveBGExecute(daObjYtaihou_c* i_this) { - i_this->MoveBGExecute(); +static int daObjYtaihou_MoveBGExecute(daObjYtaihou_c* i_this) { + return i_this->MoveBGExecute(); } -static void daObjYtaihou_MoveBGDraw(daObjYtaihou_c* i_this) { - i_this->Draw(); +static int daObjYtaihou_MoveBGDraw(daObjYtaihou_c* i_this) { + return i_this->Draw(); } static actor_method_class daObjYtaihou_METHODS = { diff --git a/src/d/actor/d_a_obj_fan.cpp b/src/d/actor/d_a_obj_fan.cpp index 40d196706e..a3eb23a200 100644 --- a/src/d/actor/d_a_obj_fan.cpp +++ b/src/d/actor/d_a_obj_fan.cpp @@ -310,9 +310,9 @@ int daObjFan_c::Delete() { return 1; } -static void daObjFan_create1st(daObjFan_c* param_0) { +static int daObjFan_create1st(daObjFan_c* param_0) { fopAcM_ct(param_0, daObjFan_c); - param_0->create1st(); + return param_0->create1st(); } static int daObjFan_MoveBGDelete(daObjFan_c* param_0) { diff --git a/src/d/actor/d_a_obj_inobone.cpp b/src/d/actor/d_a_obj_inobone.cpp index d73c5e5f9f..2076405828 100644 --- a/src/d/actor/d_a_obj_inobone.cpp +++ b/src/d/actor/d_a_obj_inobone.cpp @@ -89,8 +89,8 @@ static void* s_boar_sub(void* i_actor, void* i_data) { return NULL; } -static void CheckCreateHeap(fopAc_ac_c* i_this) { - static_cast(i_this)->CreateHeap(); +static int CheckCreateHeap(fopAc_ac_c* i_this) { + return static_cast(i_this)->CreateHeap(); } void daObjIBone_c::initBaseMtx() { @@ -150,7 +150,7 @@ int daObjIBone_c::create() { int result = dComIfG_resLoad(&mPhase, l_arcName); if (result == cPhs_COMPLEATE_e) { - if (!fopAcM_entrySolidHeap(this, (heapCallbackFunc)CheckCreateHeap, 0x860)) { + if (!fopAcM_entrySolidHeap(this, CheckCreateHeap, 0x860)) { return cPhs_ERROR_e; } else if (!Create()) { return cPhs_ERROR_e; diff --git a/src/d/actor/d_a_obj_stick.cpp b/src/d/actor/d_a_obj_stick.cpp index 69878c10c5..d6aa8d1e22 100644 --- a/src/d/actor/d_a_obj_stick.cpp +++ b/src/d/actor/d_a_obj_stick.cpp @@ -198,24 +198,24 @@ void daObj_Stick_c::setMtx() { mpModel->setBaseTRMtx(mDoMtx_stack_c::get()); } -static u32 daObj_Stick_Create(void* i_this) { +static int daObj_Stick_Create(void* i_this) { return static_cast(i_this)->create(); } -static void daObj_Stick_Delete(void* param_0) { - static_cast(param_0)->Delete(); +static int daObj_Stick_Delete(void* param_0) { + return static_cast(param_0)->Delete(); } -static void daObj_Stick_Execute(void* param_0) { - static_cast(param_0)->Execute(); +static int daObj_Stick_Execute(void* param_0) { + return static_cast(param_0)->Execute(); } -static void daObj_Stick_Draw(void* param_0) { - static_cast(param_0)->Draw(); +static int daObj_Stick_Draw(void* param_0) { + return static_cast(param_0)->Draw(); } -static bool daObj_Stick_IsDelete(void* param_0) { - return true; +static int daObj_Stick_IsDelete(void* param_0) { + return 1; } static actor_method_class daObj_Stick_MethodTable = { diff --git a/src/d/actor/d_a_peru.cpp b/src/d/actor/d_a_peru.cpp index 51a1c7ba75..add706d4d7 100644 --- a/src/d/actor/d_a_peru.cpp +++ b/src/d/actor/d_a_peru.cpp @@ -34,60 +34,40 @@ static s8* l_loadResPtrnList[3] = { l_loadResPtrn0, }; -static u8 l_faceMotionAnmData[140] = { - 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1E, - 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0A, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x02, - 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x02, - 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, - 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1F, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, +static daNpcT_faceMotionAnmData_c l_faceMotionAnmData[5] = { + {-1, 0, 0, 30, 2, 1, 1}, + {10, 0, 1, 30, 2, 1, 1}, + {8, 2, 1, 32, 2, 1, 0}, + {9, 0, 1, 33, 0, 1, 0}, + {7, 0, 1, 31, 0, 1, 0}, }; -static u8 l_motionAnmData[308] = { - 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1B, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, - 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x02, - 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, - 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, - 0x00, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1B, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, - 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, - 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, - 0x00, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1B, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, - 0x00, 0x01, 0x00, 0x00, +static daNpcT_motionAnmData_c l_motionAnmData[11] = { + {20, 2, 1, 27, 0, 1, 1, 0}, + {17, 2, 1, 27, 0, 1, 1, 0}, + {18, 2, 1, 27, 0, 1, 1, 0}, + {21, 2, 1, 27, 0, 1, 1, 0}, + {16, 2, 1, 27, 0, 1, 1, 0}, + {15, 2, 1, 27, 0, 1, 1, 0}, + {19, 0, 1, 27, 0, 1, 1, 0}, + {13, 0, 1, 27, 0, 1, 1, 0}, + {14, 2, 1, 27, 0, 1, 1, 0}, + {12, 0, 1, 27, 0, 1, 1, 0}, + {11, 0, 1, 27, 0, 1, 1, 0}, }; -static u8 l_faceMotionSequenceData[80] = { - 0x00, 0x01, 0xFF, 0x01, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, - 0x00, 0x02, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, - 0x00, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, - 0x00, 0x03, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, - 0x00, 0x04, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, +static daNpcT_MotionSeqMngr_c::sequenceStepData_c l_faceMotionSequenceData[20] = { + {1, -1, 1}, {-1, 0, 0}, {-1, 0, 0}, {-1, 0, 0}, {2, -1, 0}, {-1, 0, 0}, {-1, 0, 0}, {-1, 0, 0}, + {0, -1, 0}, {-1, 0, 0}, {-1, 0, 0}, {-1, 0, 0}, {3, -1, 0}, {-1, 0, 0}, {-1, 0, 0}, {-1, 0, 0}, + {4, -1, 0}, {-1, 0, 0}, {-1, 0, 0}, {-1, 0, 0}, }; -static u8 l_motionSequenceData[160] = { - 0x00, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, - 0x00, 0x01, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, - 0x00, 0x02, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, - 0x00, 0x05, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, - 0x00, 0x06, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, - 0x00, 0x07, 0xFF, 0x00, 0x00, 0x08, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, - 0x00, 0x09, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, - 0x00, 0x0A, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, - 0x00, 0x03, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, - 0x00, 0x04, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, +static daNpcT_MotionSeqMngr_c::sequenceStepData_c l_motionSequenceData[40] = { + {0, -1, 0}, {-1, 0, 0}, {-1, 0, 0}, {-1, 0, 0}, {1, -1, 0}, {-1, 0, 0}, {-1, 0, 0}, {-1, 0, 0}, + {2, -1, 0}, {-1, 0, 0}, {-1, 0, 0}, {-1, 0, 0}, {5, -1, 0}, {-1, 0, 0}, {-1, 0, 0}, {-1, 0, 0}, + {6, -1, 0}, {-1, 0, 0}, {-1, 0, 0}, {-1, 0, 0}, {7, -1, 0}, {8, -1, 0}, {-1, 0, 0}, {-1, 0, 0}, + {9, -1, 0}, {-1, 0, 0}, {-1, 0, 0}, {-1, 0, 0}, {10, -1, 0}, {-1, 0, 0}, {-1, 0, 0}, {-1, 0, 0}, + {3, -1, 0}, {-1, 0, 0}, {-1, 0, 0}, {-1, 0, 0}, {4, -1, 0}, {-1, 0, 0}, {-1, 0, 0}, {-1, 0, 0}, }; const char* daPeru_c::mCutNameList[3] = { diff --git a/src/d/actor/d_a_tag_magne.cpp b/src/d/actor/d_a_tag_magne.cpp index 8b47511eeb..ca74c0655a 100644 --- a/src/d/actor/d_a_tag_magne.cpp +++ b/src/d/actor/d_a_tag_magne.cpp @@ -44,15 +44,15 @@ int daTagMagne_c::_delete() { return 1; } -static void daTagMagne_Delete(daTagMagne_c* i_this) { +static int daTagMagne_Delete(daTagMagne_c* i_this) { int id = fopAcM_GetID(i_this); - i_this->_delete(); + return i_this->_delete(); } -static void daTagMagne_Create(fopAc_ac_c* i_this) { +static int daTagMagne_Create(fopAc_ac_c* i_this) { daTagMagne_c* magne = static_cast(i_this); int id = fopAcM_GetID(i_this); - magne->create(); + return magne->create(); } static actor_method_class l_daTagMagne_Method = { diff --git a/src/d/d_cursor_mng.cpp b/src/d/d_cursor_mng.cpp index 223d01bffc..d5586091d1 100644 --- a/src/d/d_cursor_mng.cpp +++ b/src/d/d_cursor_mng.cpp @@ -315,7 +315,7 @@ void dCsr_mng_c::bloObj_c::calcPaneObjNum(J2DPane* i_pane) { } JSUTreeIterator iter = i_pane->getPaneTree()->getFirstChild(); - while (iter != NULL) { + while (iter) { calcPaneObjNum(*iter); ++iter; } @@ -331,7 +331,7 @@ void dCsr_mng_c::bloObj_c::createPaneObj(paneObj_c** i_panes, J2DPane* i_pane) { } JSUTreeIterator iter = i_pane->getPaneTree()->getFirstChild(); - while (iter != NULL) { + while (iter) { createPaneObj(i_panes, *iter); ++iter; } From 5ce7c047a2e83689cc2865b4fc96fe51ae6f1109 Mon Sep 17 00:00:00 2001 From: TakaRikka Date: Mon, 30 Mar 2026 03:33:15 -0700 Subject: [PATCH 134/350] fix camera shake --- src/d/d_camera.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/d/d_camera.cpp b/src/d/d_camera.cpp index ce2c7c169a..a5efef9bfa 100644 --- a/src/d/d_camera.cpp +++ b/src/d/d_camera.cpp @@ -10532,6 +10532,10 @@ int dCamera_c::StartShake(s32 i_length, u8* i_pattern, s32 i_flags, cXyz i_pos) #define PATTERN_LENGTH_MAX 4 #endif + #if TARGET_PC + *(u32*)i_pattern = BSWAP32(*(u32*)i_pattern); + #endif + if (i_length < 0 || i_length > PATTERN_LENGTH_MAX << 3) { OS_REPORT("camera: shake: too long data\n"); i_length = PATTERN_LENGTH_MAX << 3; From 1f05995c1333ee735f86363f200cc70321cd0ea3 Mon Sep 17 00:00:00 2001 From: TakaRikka Date: Mon, 30 Mar 2026 13:08:29 -0700 Subject: [PATCH 135/350] the rest of maddie's flower stuff --- src/d/actor/d_flower.inc | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/d/actor/d_flower.inc b/src/d/actor/d_flower.inc index 0018a79960..3a5a25529a 100644 --- a/src/d/actor/d_flower.inc +++ b/src/d/actor/d_flower.inc @@ -5,8 +5,14 @@ #include "JSystem/J3DGraphBase/J3DDrawBuffer.h" #include "SSystem/SComponent/c_counter.h" +#if TARGET_PC +const u16 l_J_Ohana00_64TEX__width = 64; +const u16 l_J_Ohana00_64TEX__height = 64; +#else const u16 l_J_Ohana00_64TEX__width = 63; const u16 l_J_Ohana00_64TEX__height = 63; +#endif + #include "assets/l_J_Ohana00_64TEX.h" static u8 l_flowerPos[708] = { @@ -72,8 +78,8 @@ static u8 l_flowerNormal[180] = { 0x46, 0x54, 0x0D, 0x3C, 0x4B, 0xEE, 0x80, 0x3F, 0x7F, 0xF5, 0xF9, 0x3C, 0x49, 0x81, 0xBF, }; -static u8 l_flowerColor[8] = { - 0xFF, 0xFF, 0xFF, 0xFF, 0xB2, 0xB2, 0xB2, 0xFF, +static GXColor l_flowerColor[2] = { + {0xFF, 0xFF, 0xFF, 0xFF}, {0xB2, 0xB2, 0xB2, 0xFF} }; static u8 l_flowerTexCoord[] = { @@ -108,8 +114,14 @@ l_matDL__d_a_grass(l_J_Ohana00_64TEX) #include "assets/l_matLight4DL.h" l_matLight4DL(l_J_Ohana00_64TEX) +#if TARGET_PC +const u16 l_J_Ohana01_64128_0419TEX__width = 64; +const u16 l_J_Ohana01_64128_0419TEX__height = 128; +#else const u16 l_J_Ohana01_64128_0419TEX__width = 63; const u16 l_J_Ohana01_64128_0419TEX__height = 127; +#endif + #include "assets/l_J_Ohana01_64128_0419TEX.h" static u8 l_flowerPos2[1224] = { @@ -213,8 +225,8 @@ static u8 l_flowerNormal2[288] = { 0x3B, 0x76, 0x7B, 0x1C, 0x3B, 0x99, 0x6B, 0x76, 0x3F, 0x7F, 0xF5, 0xF9, 0xBC, 0x8A, 0x1D, 0xFC, }; -static u8 l_flowerColor2[8] = { - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB2, 0xFF, +static GXColor l_flowerColor2[2] = { + {0xFF, 0xFF, 0xFF, 0xFF}, {0xFF, 0xFF, 0xB2, 0xFF} }; static u8 l_flowerTexCoord2[] = { From e52ffcef12c5c49ccbc8ed50532acfc6110a7108 Mon Sep 17 00:00:00 2001 From: TakaRikka Date: Mon, 30 Mar 2026 14:44:40 -0700 Subject: [PATCH 136/350] fix pad rumble --- src/d/d_vibration.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/d/d_vibration.cpp b/src/d/d_vibration.cpp index 5328cd7b06..a145ae878c 100644 --- a/src/d/d_vibration.cpp +++ b/src/d/d_vibration.cpp @@ -22,6 +22,13 @@ u16* makedata(u16* data, u32 pattern, s32 length) { data[1] = pattern >> 16; data[2] = pattern; data[3] = 0; + +#if TARGET_PC + data[0] = BSWAP16(data[0]); + data[1] = BSWAP16(data[1]); + data[2] = BSWAP16(data[2]); +#endif + return data; } From d00104285bbcea5c912df11e7bf0d5934cc467ca Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Tue, 31 Mar 2026 01:12:21 +0200 Subject: [PATCH 137/350] Fix JUTGamePad stopPatternedRumbleAtThePeriod division by zero Happens for me when loading a save in Death Mountain Twilight. Confirmed in Dolphin with the same save file. PowerPC does not raise an exception on division by zero, so I assume this is an original game "bug" --- libs/JSystem/src/JUtility/JUTGamePad.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/libs/JSystem/src/JUtility/JUTGamePad.cpp b/libs/JSystem/src/JUtility/JUTGamePad.cpp index 31b8d59f1a..091cc382d1 100644 --- a/libs/JSystem/src/JUtility/JUTGamePad.cpp +++ b/libs/JSystem/src/JUtility/JUTGamePad.cpp @@ -535,6 +535,14 @@ void JUTGamePad::CRumble::stopPatternedRumble(s16 port) { } void JUTGamePad::CRumble::stopPatternedRumbleAtThePeriod() { +#if TARGET_PC + if (mFrameCount == 0) { + // Does not trap on hardware + // PowerPC spec says result is "undefined". Let's just write zero. + mLength = 0; + return; + } +#endif u32 r31 = mFrame % mFrameCount; mLength = (mFrame + mFrameCount - 1) % mFrameCount; } From 984cf000d41721c4a2a08e9c5a59318d0ba4ff81 Mon Sep 17 00:00:00 2001 From: TakaRikka Date: Mon, 30 Mar 2026 17:52:19 -0700 Subject: [PATCH 138/350] unstub map rendering --- src/d/d_map.cpp | 2 -- src/d/d_map_path.cpp | 8 ++++---- src/d/d_map_path_dmap.cpp | 3 --- src/d/d_menu_ring.cpp | 4 ---- src/dusk/imgui/ImGuiMenuTools.cpp | 5 ++--- src/m_Do/m_Do_ext.cpp | 2 -- 6 files changed, 6 insertions(+), 18 deletions(-) diff --git a/src/d/d_map.cpp b/src/d/d_map.cpp index 76289d52dd..a0ee8d8128 100644 --- a/src/d/d_map.cpp +++ b/src/d/d_map.cpp @@ -341,7 +341,6 @@ inline u8 twoValueLineInterpolation(u8 param_0, u8 param_1, f32 param_2) { } void renderingAmap_c::draw() { - #if REQUIRES_GX_LINES f32 tmp = ((f32)(g_Counter.mCounter0 % dMap_HIO_prm_res_dst_s::m_res->field_0x1aa) / (f32)dMap_HIO_prm_res_dst_s::m_res->field_0x1aa); tmp = tmp; @@ -377,7 +376,6 @@ void renderingAmap_c::draw() { setAmapPaletteColor(0x2E, temp_r31, temp_r30, temp_r29, temp_r28); renderingDAmap_c::draw(); - #endif } int renderingAmap_c::getDispType() const { diff --git a/src/d/d_map_path.cpp b/src/d/d_map_path.cpp index 47f0f1e43c..acc29ae1d3 100644 --- a/src/d/d_map_path.cpp +++ b/src/d/d_map_path.cpp @@ -346,13 +346,11 @@ void dRenderingMap_c::makeResTIMG(ResTIMG* p_image, u16 width, u16 height, u8* p void dRenderingMap_c::renderingMap() { preRenderingMap(); if (isDrawPath()) { - #if REQUIRES_GX_LINES preDrawPath(); beforeDrawPath(); drawPath(); afterDrawPath(); postDrawPath(); - #endif } postRenderingMap(); } @@ -473,7 +471,6 @@ void dRenderingFDAmap_c::renderingDecoration(dDrawPath_c::line_class const* p_li GXSetTevColor(GX_TEVREG1, lineColor); for (int i = 0; i < data_num; i++) { -#if REQUIRES_GX_LINES #ifndef HYRULE_FIELD_SPEEDHACK if (i < data_num - 1) { GXSetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_C0); @@ -494,11 +491,14 @@ void dRenderingFDAmap_c::renderingDecoration(dDrawPath_c::line_class const* p_li GXSetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_TEXA); GXSetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV); #endif -#endif + + // aurora doesn't support GX_POINTS yet + #if !TARGET_PC GXBegin(GX_POINTS, GX_VTXFMT0, 1); GXPosition1x16(data_p[0]); GXTexCoord2f32(0, 0); GXEnd(); + #endif data_p++; } diff --git a/src/d/d_map_path_dmap.cpp b/src/d/d_map_path_dmap.cpp index cd76155327..f53b253eb8 100644 --- a/src/d/d_map_path_dmap.cpp +++ b/src/d/d_map_path_dmap.cpp @@ -617,10 +617,7 @@ bool renderingDAmap_c::isSwitch(dDrawPath_c::group_class const* i_group) { } void renderingDAmap_c::draw() { -#if !TARGET_PC - // Currently breaks Aurora. renderingMap(); -#endif mIsDraw = true; } diff --git a/src/d/d_menu_ring.cpp b/src/d/d_menu_ring.cpp index cad04d6ab6..3949adffed 100644 --- a/src/d/d_menu_ring.cpp +++ b/src/d/d_menu_ring.cpp @@ -1262,10 +1262,8 @@ void dMenu_Ring_c::drawItem() { (g_ringHIO.mItemAlphaMin + fVar16 * (g_ringHIO.mItemAlphaMax - g_ringHIO.mItemAlphaMin)); for (int i = 0; i < mItemsTotal; i++) { if (i != mCurrentSlot || (mStatus != STATUS_WAIT && mStatus != STATUS_EXPLAIN && mStatus != STATUS_EXPLAIN_FORCE)) { - #if REQUIRES_GX_LINES J2DDrawFrame(mItemSlotPosX[i] - 24.0f + mCenterPosX, mItemSlotPosY[i] - 24.0f + mCenterPosY, 48.0f, 48.0f, g_ringHIO.mItemFrame[g_ringHIO.UNSELECT_FRAME], 6); - #endif f32 fVar17 = 1.0f; if (i != mCurrentSlot) { fVar17 = ringAlpha / 255.0f; @@ -1308,10 +1306,8 @@ void dMenu_Ring_c::drawItem() { void dMenu_Ring_c::drawItem2() { s32 idx = mCurrentSlot; if (mStatus == STATUS_WAIT || mStatus == STATUS_EXPLAIN || mStatus == STATUS_EXPLAIN_FORCE) { - #if REQUIRES_GX_LINES J2DDrawFrame(mItemSlotPosX[idx] - 24.0f + mCenterPosX, mItemSlotPosY[idx] - 24.0f + mCenterPosY, 48.0f, 48.0f, g_ringHIO.mItemFrame[g_ringHIO.SELECT_FRAME], 6); - #endif for (int i = 0; i < 3; i++) { if (mpItemTex[idx][i] != NULL) { diff --git a/src/dusk/imgui/ImGuiMenuTools.cpp b/src/dusk/imgui/ImGuiMenuTools.cpp index e77941af52..e2537a264a 100644 --- a/src/dusk/imgui/ImGuiMenuTools.cpp +++ b/src/dusk/imgui/ImGuiMenuTools.cpp @@ -25,8 +25,7 @@ namespace dusk { if (ImGui::BeginMenu("Collision View")) { ImGui::Checkbox("Enable Terrain view", &m_collisionViewSettings.m_enableTerrainView); - // can't use wireframe atm because aurora doesn't support GX_LINES - //ImGui::Checkbox("Enable wireframe view", &m_collisionViewSettings.m_enableWireframe); + ImGui::Checkbox("Enable wireframe view", &m_collisionViewSettings.m_enableWireframe); ImGui::SliderFloat("Opacity##terrain", &m_collisionViewSettings.m_terrainViewOpacity, 0.0f, 100.0f); ImGui::SliderFloat("Draw Range", &m_collisionViewSettings.m_drawRange, 0.0f, 1000.0f); ImGui::Separator(); @@ -189,4 +188,4 @@ namespace dusk { ImGui::End(); } -} \ No newline at end of file +} diff --git a/src/m_Do/m_Do_ext.cpp b/src/m_Do/m_Do_ext.cpp index a7829bdb50..993937f0e3 100644 --- a/src/m_Do/m_Do_ext.cpp +++ b/src/m_Do/m_Do_ext.cpp @@ -1738,8 +1738,6 @@ void mDoExt_McaMorfSO::setAnm(J3DAnmTransform* i_anm, int i_attr, f32 i_morf, f3 setLoopFrame(getFrame()); setMorf(i_morf); - STUB_RET(); - if (mpSound != NULL) { if (i_anm != NULL) { mpBas = static_cast(i_anm)->getBas(); From 54094a9b6cbfcb6fc323d612c087e440da3336cd Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Tue, 31 Mar 2026 03:17:02 +0200 Subject: [PATCH 139/350] Fix d_a_obj_stone bound crashing due to accessing a field improperly Different vtable layout on mwerks made this direct pointer cast not work. access the field instead. Also I went searching if there's any other cases of this bug, didn't find any but did find something else to clean up. --- src/d/actor/d_a_npc_tk.cpp | 2 +- src/d/actor/d_a_obj_stone.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/d/actor/d_a_npc_tk.cpp b/src/d/actor/d_a_npc_tk.cpp index 34fef630fb..0349f8ebc0 100644 --- a/src/d/actor/d_a_npc_tk.cpp +++ b/src/d/actor/d_a_npc_tk.cpp @@ -2518,7 +2518,7 @@ void daNPC_TK_c::executeResistanceDemo() { 0x200, 0x10); shape_angle.x = -current.angle.x; - cLib_addCalcAngleS(¤t.angle.y, cLib_targetAngleY((Vec*)¤t, &posWithOffset), 8, + cLib_addCalcAngleS(¤t.angle.y, cLib_targetAngleY(¤t.pos, &posWithOffset), 8, 0x400, 0x10); shape_angle.y = current.angle.y; diff --git a/src/d/actor/d_a_obj_stone.cpp b/src/d/actor/d_a_obj_stone.cpp index 5cf5716873..f74adf12a7 100644 --- a/src/d/actor/d_a_obj_stone.cpp +++ b/src/d/actor/d_a_obj_stone.cpp @@ -126,7 +126,7 @@ static f32 bound(cXyz* param_0, cBgS_PolyInfo const& param_1, f32 param_2) { cXyz pos; f32 abs = param_0->abs(); - C_VECReflect(param_0, (Vec*)&plane, &pos); + C_VECReflect(param_0, &plane.mNormal, &pos); *param_0 = pos * abs * param_2; return param_0->absXZ(); From 4b2a3e8da964e33495eec78a7f81d458c0b304b8 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Tue, 31 Mar 2026 03:17:56 +0200 Subject: [PATCH 140/350] Fast iron boots tweak --- include/d/actor/d_a_alink.h | 6 ++++++ src/d/actor/d_a_alink.cpp | 19 +++++++++++++++++++ src/dusk/imgui/ImGuiMenuGame.cpp | 6 ++++++ 3 files changed, 31 insertions(+) diff --git a/include/d/actor/d_a_alink.h b/include/d/actor/d_a_alink.h index 7c70ea54e5..a4d38efc43 100644 --- a/include/d/actor/d_a_alink.h +++ b/include/d/actor/d_a_alink.h @@ -8425,4 +8425,10 @@ inline daAlink_c* daAlink_getAlinkActorClass() { return (daAlink_c*)dComIfGp_getLinkPlayer(); } +#if TARGET_PC +namespace dusk::tweaks { + extern bool FastIronBoots; +} +#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 4f106ece22..5e2530bdf3 100644 --- a/src/d/actor/d_a_alink.cpp +++ b/src/d/actor/d_a_alink.cpp @@ -54,6 +54,10 @@ #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); @@ -7509,6 +7513,11 @@ void daAlink_c::setBlendMoveAnime(f32 i_morf) { f32 sp28 = mpHIO->mMove.m.mFootPositionRatio; BOOL sp24 = checkEventRun(); BOOL sp20 = checkBootsMoveAnime(1); +#if TARGET_PC + if (dusk::tweaks::FastIronBoots) { + sp20 = FALSE; + } +#endif f32 var_f29; @@ -9469,6 +9478,11 @@ void daAlink_c::setStickData() { } else { mHeavySpeedMultiplier = mpHIO->mItem.mIronBoots.m.mInputFactor; } +#if TARGET_PC + if (dusk::tweaks::FastIronBoots) { + mHeavySpeedMultiplier = 1.0f; + } +#endif mStickValue *= mHeavySpeedMultiplier; } else if (checkBootsOrArmorHeavy()) { if (checkZoraWearAbility()) { @@ -9476,6 +9490,11 @@ void daAlink_c::setStickData() { } else { mHeavySpeedMultiplier = mpHIO->mItem.mIronBoots.m.mWaterInputFactor; } +#if TARGET_PC + if (dusk::tweaks::FastIronBoots) { + mHeavySpeedMultiplier = 1.0f; + } +#endif mStickValue *= mHeavySpeedMultiplier; } else if ((checkWolf() && field_0x2fbc == 11 && checkWaterPolygonUnder()) || mGndPolyAtt0 == 11) diff --git a/src/dusk/imgui/ImGuiMenuGame.cpp b/src/dusk/imgui/ImGuiMenuGame.cpp index ff1ef73205..2e6a4fac32 100644 --- a/src/dusk/imgui/ImGuiMenuGame.cpp +++ b/src/dusk/imgui/ImGuiMenuGame.cpp @@ -7,6 +7,7 @@ #include #include "JSystem/JUtility/JUTGamePad.h" +#include "d/actor/d_a_alink.h" #include "dusk/audio/DuskAudioSystem.h" #include "m_Do/m_Do_audio.h" #include "m_Do/m_Do_controller_pad.h" @@ -71,6 +72,11 @@ namespace dusk { ImGui::EndMenu(); } + if (ImGui::BeginMenu("Tweaks")) { + ImGui::MenuItem("Fast iron boots", nullptr, &tweaks::FastIronBoots); + ImGui::EndMenu(); + } + ImGui::EndMenu(); } From 356f8d3015349784597031a9de0c21547771a7fc Mon Sep 17 00:00:00 2001 From: YunataSavior Date: Mon, 30 Mar 2026 19:24:13 -0700 Subject: [PATCH 141/350] gitignore: raw, gci, and zip --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index 2fa85e7a9c..ae7fa75384 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,10 @@ orig/*/* *.map *.MAP +# Save files +*.raw +*.gci + # Build files build/ .ninja_* @@ -32,6 +36,7 @@ compile_commands.json # Miscellaneous *.exe +*.zip # MacOS .DS_Store From 3d81e633ff62992925b5ef1abc251486804c66be Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Tue, 31 Mar 2026 04:36:33 +0200 Subject: [PATCH 142/350] Disable suspicious assert causing cutscene crashes From my reading of the code this assert is likely incorrect. This throws on cases where usize has a sane-looking value (the length of the pContent null-terminated string), and from my understanding of how the data is parsed, this length is needed to figure out the location of the next "paragraph". --- libs/JSystem/src/JStudio/JStudio_JAudio2/object-sound.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libs/JSystem/src/JStudio/JStudio_JAudio2/object-sound.cpp b/libs/JSystem/src/JStudio/JStudio_JAudio2/object-sound.cpp index e4ce109f09..9bdde8b5cc 100644 --- a/libs/JSystem/src/JStudio/JStudio_JAudio2/object-sound.cpp +++ b/libs/JSystem/src/JStudio/JStudio_JAudio2/object-sound.cpp @@ -225,7 +225,9 @@ void JStudio_JAudio2::TAdaptor_sound::adaptor_do_PARENT_NODE(JStudio::data::TEOp case JStudio::data::UNK_0x18: if (field_0x13c != NULL) { JUT_ASSERT(431, pContent!=NULL); +#if DUSK_SUSPICIOUS_ASSERTS JUT_ASSERT(432, uSize==0); +#endif field_0x140 = field_0x13c->JSGFindNodeID((char*)pContent); if (field_0x140 == -1) { return; From 0fec5da59074fa7b5b224b406af0783f5b3c40b6 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Mon, 30 Mar 2026 22:14:42 -0600 Subject: [PATCH 143/350] Use for SDL_main --- src/dusk/main.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/dusk/main.cpp b/src/dusk/main.cpp index 4155482488..7d19325f8f 100644 --- a/src/dusk/main.cpp +++ b/src/dusk/main.cpp @@ -3,6 +3,8 @@ #include #endif +#include + int game_main(int argc, char* argv[]); void WindowsSetupConsole(); From f1165bedd74ca8f01e38ef1d7828b1ad52d0c939 Mon Sep 17 00:00:00 2001 From: Irastris Date: Tue, 31 Mar 2026 00:47:24 -0400 Subject: [PATCH 144/350] Change fullscreen checkbox to menu item, add F11 keybind --- src/dusk/imgui/ImGuiMenuGame.cpp | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/dusk/imgui/ImGuiMenuGame.cpp b/src/dusk/imgui/ImGuiMenuGame.cpp index 2e6a4fac32..e4f0ecffab 100644 --- a/src/dusk/imgui/ImGuiMenuGame.cpp +++ b/src/dusk/imgui/ImGuiMenuGame.cpp @@ -24,16 +24,20 @@ namespace dusk { ImGui::Separator(); if (ImGui::BeginMenu("Graphics")) { + if (ImGui::MenuItem("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"); } - ImGui::Checkbox("Fullscreen", &m_graphicsSettings.m_fullscreen); - if (m_graphicsSettings.m_fullscreen != VIGetWindowFullscreen()) { - VISetWindowFullscreen(m_graphicsSettings.m_fullscreen); - } + ImGui::EndMenu(); } @@ -86,6 +90,11 @@ namespace dusk { if ((ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl)) && ImGui::IsKeyPressed(ImGuiKey_R)) { JUTGamePad::C3ButtonReset::sResetSwitchPushing = true; } + + if (ImGui::IsKeyPressed(ImGuiKey_F11)) { + m_graphicsSettings.m_fullscreen = !m_graphicsSettings.m_fullscreen; + VISetWindowFullscreen(m_graphicsSettings.m_fullscreen); + } } static void drawVirtualStick(const char* id, const ImVec2& stick) { From cd1b0bdd329b0722f8c0769286eb5fbd7ec49bf8 Mon Sep 17 00:00:00 2001 From: Irastris Date: Tue, 31 Mar 2026 00:49:43 -0400 Subject: [PATCH 145/350] Should have done this too, don't commit when tired kids --- src/dusk/imgui/ImGuiMenuGame.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dusk/imgui/ImGuiMenuGame.cpp b/src/dusk/imgui/ImGuiMenuGame.cpp index e4f0ecffab..c68140f3cd 100644 --- a/src/dusk/imgui/ImGuiMenuGame.cpp +++ b/src/dusk/imgui/ImGuiMenuGame.cpp @@ -24,7 +24,7 @@ namespace dusk { ImGui::Separator(); if (ImGui::BeginMenu("Graphics")) { - if (ImGui::MenuItem("Fullscreen", "F11")) { + if (ImGui::MenuItem("Toggle Fullscreen", "F11")) { m_graphicsSettings.m_fullscreen = !m_graphicsSettings.m_fullscreen; VISetWindowFullscreen(m_graphicsSettings.m_fullscreen); } From b109aa027326cea94cb7423a4f5cd7cc93152436 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Tue, 31 Mar 2026 01:06:36 -0600 Subject: [PATCH 146/350] Update GXSetArray usages (fixes & `le` arg) --- extern/aurora | 2 +- .../include/JSystem/J3DGraphBase/J3DSys.h | 4 +- libs/JSystem/src/J3DGraphBase/J3DShape.cpp | 15 +++---- libs/JSystem/src/JParticle/JPAResource.cpp | 8 ++-- src/d/actor/d_a_mant.cpp | 6 +-- src/d/actor/d_a_obj_flag2.cpp | 8 ++-- src/d/actor/d_a_obj_flag3.cpp | 8 ++-- src/d/actor/d_flower.inc | 42 ++++--------------- src/d/actor/d_grass.inc | 8 ++-- src/d/d_drawlist.cpp | 4 +- src/d/d_map.cpp | 2 +- src/d/d_map_path.cpp | 18 +++++--- src/d/d_map_path_dmap.cpp | 6 +-- src/m_Do/m_Do_ext.cpp | 16 +++---- 14 files changed, 65 insertions(+), 82 deletions(-) diff --git a/extern/aurora b/extern/aurora index 507fda8591..819c72914e 160000 --- a/extern/aurora +++ b/extern/aurora @@ -1 +1 @@ -Subproject commit 507fda8591f2c896cf7f0bd4c51bb05ecc0cccf6 +Subproject commit 819c72914e90cb5eec3edf6c14ca9d7c7bf9aa3d diff --git a/libs/JSystem/include/JSystem/J3DGraphBase/J3DSys.h b/libs/JSystem/include/JSystem/J3DGraphBase/J3DSys.h index 9c806c92ba..b3c4f1eb16 100644 --- a/libs/JSystem/include/JSystem/J3DGraphBase/J3DSys.h +++ b/libs/JSystem/include/JSystem/J3DGraphBase/J3DSys.h @@ -150,13 +150,13 @@ struct J3DSys { void setModelDrawMtx(Mtx* pMtxArr) { J3D_ASSERT_NULLPTR(230, pMtxArr); mModelDrawMtx = pMtxArr; - GXSETARRAY(GX_POS_MTX_ARRAY, mModelDrawMtx, sizeof(*mModelDrawMtx), sizeof(*mModelDrawMtx)); + GXSETARRAY(GX_POS_MTX_ARRAY, mModelDrawMtx, 10 * sizeof(Mtx), sizeof(*mModelDrawMtx), true); } void setModelNrmMtx(Mtx33* pMtxArr) { J3D_ASSERT_NULLPTR(241, pMtxArr); mModelNrmMtx = pMtxArr; - GXSETARRAY(GX_NRM_MTX_ARRAY, mModelNrmMtx, sizeof(*mModelNrmMtx), sizeof(*mModelNrmMtx)); + GXSETARRAY(GX_NRM_MTX_ARRAY, mModelNrmMtx, 10 * sizeof(Mtx33), sizeof(*mModelNrmMtx), true); } void* getVtxPos() { return mVtxPos; } diff --git a/libs/JSystem/src/J3DGraphBase/J3DShape.cpp b/libs/JSystem/src/J3DGraphBase/J3DShape.cpp index b3c16f00a7..76f58b7586 100644 --- a/libs/JSystem/src/J3DGraphBase/J3DShape.cpp +++ b/libs/JSystem/src/J3DGraphBase/J3DShape.cpp @@ -134,12 +134,13 @@ void J3DLoadCPCmd(u8 addr, u32 val) { } #if TARGET_PC -static void J3DLoadArrayBasePtr(GXAttr attr, void* data, u32 size) { +static void J3DLoadArrayBasePtr(GXAttr attr, void* data, u32 size, bool le) { u32 idx = (attr == GX_VA_NBT) ? 1 : (attr - GX_VA_POS); GXCmd1u8(GX_LOAD_AURORA); GXCmd1u16(GX_LOAD_AURORA_ARRAYBASE | idx); GXCmd1u64((u64)data); - GXCmd1u64((u64)size); + GXCmd1u32(size); + GXCmd1u8(le ? 1 : 0); } #else static void J3DLoadArrayBasePtr(GXAttr attr, void* data) { @@ -152,15 +153,15 @@ void J3DShape::loadVtxArray() const { #if TARGET_PC // TODO: these can very easily overcount if the data isn't in F32 format if (j3dSys.getVtxPos() != mVertexData->getVtxPosArray()) { - J3DLoadArrayBasePtr(GX_VA_POS, j3dSys.getVtxPos(), j3dSys.mVtxPosNum * sizeof(Vec)); + J3DLoadArrayBasePtr(GX_VA_POS, j3dSys.getVtxPos(), j3dSys.mVtxPosNum * sizeof(Vec), true); } if (!mHasNBT && j3dSys.getVtxNrm() != mVertexData->getVtxNrmArray()) { - J3DLoadArrayBasePtr(GX_VA_NRM, j3dSys.getVtxNrm(), j3dSys.mVtxNrmNum * sizeof(Vec)); + J3DLoadArrayBasePtr(GX_VA_NRM, j3dSys.getVtxNrm(), j3dSys.mVtxNrmNum * sizeof(Vec), true); } if (j3dSys.getVtxCol() != mVertexData->getVtxColorArray(0)) { - J3DLoadArrayBasePtr(GX_VA_CLR0, j3dSys.getVtxCol(), j3dSys.mVtxColNum * sizeof(GXColor)); + J3DLoadArrayBasePtr(GX_VA_CLR0, j3dSys.getVtxCol(), j3dSys.mVtxColNum * sizeof(GXColor), true); } #else J3DLoadArrayBasePtr(GX_VA_POS, j3dSys.getVtxPos()); @@ -257,9 +258,9 @@ void J3DShape::makeVtxArrayCmd() { for (u32 i = 0; i < 12; i++) { GXAttr attr = GXAttr(i + GX_VA_POS); if (array[i] != nullptr) - GDSetArraySized(attr, array[i], mVertexData->getVtxArrByteSize(attr), mVertexData->getVtxArrStride(attr)); + GDSetArraySized(attr, array[i], mVertexData->getVtxArrByteSize(attr), mVertexData->getVtxArrStride(attr), true); else - GDSetArraySized(attr, nullptr, 0, mVertexData->getVtxArrStride(attr)); + GDSetArraySized(attr, nullptr, 0, mVertexData->getVtxArrStride(attr), true); } #else for (u32 i = 0; i < 12; i++) { diff --git a/libs/JSystem/src/JParticle/JPAResource.cpp b/libs/JSystem/src/JParticle/JPAResource.cpp index 5c232a09f3..4a33ed0aab 100644 --- a/libs/JSystem/src/JParticle/JPAResource.cpp +++ b/libs/JSystem/src/JParticle/JPAResource.cpp @@ -932,8 +932,8 @@ void JPAResource::setPTev() { int center_offset = pEsp != NULL ? (pEsp->getScaleCenterX() + 3 * pEsp->getScaleCenterY()) * 0xC : 0x30; int pos_offset = center_offset + base_plane_type * 0x6C; int crd_offset = (pBsp->getTilingS() + 2 * pBsp->getTilingT()) * 8; - GXSETARRAY(GX_VA_POS, jpa_pos + pos_offset, ARRAY_SIZEU(jpa_pos) - pos_offset, 3); - GXSETARRAY(GX_VA_TEX0, jpa_crd + crd_offset, ARRAY_SIZEU(jpa_crd) - crd_offset, 2); + GXSETARRAY(GX_VA_POS, jpa_pos + pos_offset, ARRAY_SIZEU(jpa_pos) - pos_offset, 3, true); + GXSETARRAY(GX_VA_TEX0, jpa_crd + crd_offset, ARRAY_SIZEU(jpa_crd) - crd_offset, 2, true); GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR_NULL); if (pEts != NULL) { @@ -978,8 +978,8 @@ void JPAResource::setCTev(JPAEmitterWorkData* work) { int base_plane_type = (pCsp->getType() == 3 || pCsp->getType() == 7) ? pCsp->getBasePlaneType() : 0; int pos_offset = 0x30 + base_plane_type * 0x6C; - GXSETARRAY(GX_VA_POS, jpa_pos + pos_offset, ARRAY_SIZEU(jpa_pos) - pos_offset, 3); - GXSETARRAY(GX_VA_TEX0, jpa_crd, ARRAY_SIZEU(jpa_crd), 2); + GXSETARRAY(GX_VA_POS, jpa_pos + pos_offset, ARRAY_SIZEU(jpa_pos) - pos_offset, 3, true); + GXSETARRAY(GX_VA_TEX0, jpa_crd, ARRAY_SIZEU(jpa_crd), 2, true); GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP1, GX_COLOR_NULL); GXSetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, 0x3C); GXSetTevDirect(GX_TEVSTAGE0); diff --git a/src/d/actor/d_a_mant.cpp b/src/d/actor/d_a_mant.cpp index c1c5e4b297..a3b48f1098 100644 --- a/src/d/actor/d_a_mant.cpp +++ b/src/d/actor/d_a_mant.cpp @@ -268,9 +268,9 @@ void daMant_packet_c::draw() { GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_NRM, GX_CLR_RGB, GX_F32, 0); GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_CLR_RGBA, GX_F32, 0); - GXSETARRAY(GX_VA_POS, this->getPos(), sizeof(mPos[0]), 12); - GXSETARRAY(GX_VA_NRM, this->getNrm(), sizeof(mNrm[0]), 12); - GXSETARRAY(GX_VA_TEX0, &l_texCoord, sizeof(l_texCoord), 8); + GXSETARRAY(GX_VA_POS, this->getPos(), sizeof(mPos[0]), 12, true); + GXSETARRAY(GX_VA_NRM, this->getNrm(), sizeof(mNrm[0]), 12, true); + GXSETARRAY(GX_VA_TEX0, &l_texCoord, sizeof(l_texCoord), 8, false); // TODO: set to true when converted to float literals GXSetZCompLoc(0); GXSetZMode(GX_ENABLE, GX_LEQUAL, GX_ENABLE); diff --git a/src/d/actor/d_a_obj_flag2.cpp b/src/d/actor/d_a_obj_flag2.cpp index b4f89cd39a..4741f4d5a3 100644 --- a/src/d/actor/d_a_obj_flag2.cpp +++ b/src/d/actor/d_a_obj_flag2.cpp @@ -274,9 +274,9 @@ void FlagCloth_c::draw() { GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_CLR_RGBA, GX_F32, 0); GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_NRM, GX_CLR_RGB, GX_F32, 0); GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_CLR_RGBA, GX_F32, 0); - GXSETARRAY(GX_VA_POS, getPos(), sizeof(mPositions), sizeof(cXyz)); - GXSETARRAY(GX_VA_NRM, getNormal(), sizeof(mNormals), sizeof(cXyz)); - GXSETARRAY(GX_VA_TEX0, mpTexCoord, sizeof(mpTexCoord), 8); + GXSETARRAY(GX_VA_POS, getPos(), sizeof(mPositions), sizeof(cXyz), true); + GXSETARRAY(GX_VA_NRM, getNormal(), sizeof(mNormals), sizeof(cXyz), true); + GXSETARRAY(GX_VA_TEX0, mpTexCoord, sizeof(l_texCoord), 8, true); GXSetZCompLoc(GX_FALSE); GXSetZMode(GX_TRUE, GX_LEQUAL, GX_TRUE); GXLoadTexObj(&mTexObj, GX_TEXMAP0); @@ -304,7 +304,7 @@ void FlagCloth_c::draw() { GXSetClipMode(GX_CLIP_ENABLE); GXSetCullMode(GX_CULL_BACK); GXCallDisplayList(l_pennant_flagDL, 0x80); - GXSETARRAY(GX_VA_NRM, getNormalBack(), sizeof(mNormalBacks), sizeof(cXyz)); + GXSETARRAY(GX_VA_NRM, getNormalBack(), sizeof(mNormalBacks), sizeof(cXyz), true); GXSetCullMode(GX_CULL_FRONT); GXCallDisplayList(l_pennant_flagDL, 0x80); J3DShape::resetVcdVatCache(); diff --git a/src/d/actor/d_a_obj_flag3.cpp b/src/d/actor/d_a_obj_flag3.cpp index 7337a4b97d..fc696db1ab 100644 --- a/src/d/actor/d_a_obj_flag3.cpp +++ b/src/d/actor/d_a_obj_flag3.cpp @@ -233,9 +233,9 @@ inline void FlagCloth2_c::draw() { GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_CLR_RGBA, GX_F32, 0); GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_NRM, GX_CLR_RGB, GX_F32, 0); GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_CLR_RGBA, GX_F32, 0); - GXSETARRAY(GX_VA_POS, getPos(), sizeof(mPositions), sizeof(cXyz)); - GXSETARRAY(GX_VA_NRM, getNormal(), sizeof(mNormals), sizeof(cXyz)); - GXSETARRAY(GX_VA_TEX0, mTexCoord, sizeof(mTexCoord), 8); + GXSETARRAY(GX_VA_POS, getPos(), sizeof(mPositions), sizeof(cXyz), true); + GXSETARRAY(GX_VA_NRM, getNormal(), sizeof(mNormals), sizeof(cXyz), true); + GXSETARRAY(GX_VA_TEX0, mTexCoord, sizeof(mTexCoord), 8, true); GXSetZCompLoc(GX_FALSE); GXSetZMode(GX_TRUE, GX_LEQUAL, GX_TRUE); GXLoadTexObj(&mTexObj, GX_TEXMAP0); @@ -276,7 +276,7 @@ inline void FlagCloth2_c::draw() { GXEnd(); } - GXSETARRAY(GX_VA_NRM, getNormalBack(), sizeof(mNormalBacks), sizeof(cXyz)); + GXSETARRAY(GX_VA_NRM, getNormalBack(), sizeof(mNormalBacks), sizeof(cXyz), true); GXSetCullMode(GX_CULL_FRONT); for (int i = 0; i < 5; i++) { diff --git a/src/d/actor/d_flower.inc b/src/d/actor/d_flower.inc index 3a5a25529a..762b1e02e9 100644 --- a/src/d/actor/d_flower.inc +++ b/src/d/actor/d_flower.inc @@ -521,32 +521,6 @@ dFlower_packet_c::dFlower_packet_c() { unused += 0x2000; } -#if TARGET_LITTLE_ENDIAN - static bool initialized = false; - if (!initialized) { - for (int i = 0; i < (ARRAY_SIZE(l_flowerPos) / sizeof(Vec)); i++) { - be_swap(((Vec*)l_flowerPos)[i]); - } - for (int i = 0; i < (ARRAY_SIZE(l_flowerTexCoord) / sizeof(Vec)); i++) { - be_swap(((Vec*)l_flowerTexCoord)[i]); - } - for (int i = 0; i < (ARRAY_SIZE(l_flowerPos2) / sizeof(Vec)); i++) { - be_swap(((Vec*)l_flowerPos2)[i]); - } - for (int i = 0; i < (ARRAY_SIZE(l_flowerTexCoord2) / sizeof(Vec)); i++) { - be_swap(((Vec*)l_flowerTexCoord2)[i]); - } - for (int i = 0; i < (ARRAY_SIZE(l_flowerNormal) / sizeof(Vec)); i++) { - be_swap(((Vec*)l_flowerNormal)[i]); - } - for (int i = 0; i < (ARRAY_SIZE(l_flowerNormal2) / sizeof(Vec)); i++) { - be_swap(((Vec*)l_flowerNormal2)[i]); - } - - initialized = true; - } -#endif - #if TARGET_PC GXInitTexObj(&mTexObj_l_J_Ohana00_64TEX, l_J_Ohana00_64TEX, l_J_Ohana00_64TEX__width, l_J_Ohana00_64TEX__height, GX_TF_CMPR, GX_MIRROR, GX_MIRROR, GX_FALSE @@ -579,10 +553,10 @@ void dFlower_packet_c::draw() { GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_NRM, GX_NRM_XYZ, GX_F32, 0); GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_CLR0, GX_CLR_RGBA, GX_RGBA8, 0); GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_F32, 0); - GXSETARRAY(GX_VA_POS, &l_flowerPos, sizeof(l_flowerPos), 0xC); - GXSETARRAY(GX_VA_NRM, &l_flowerNormal, sizeof(l_flowerNormal), 0xC); - GXSETARRAY(GX_VA_CLR0, &l_flowerColor, sizeof(l_flowerColor), 4); - GXSETARRAY(GX_VA_TEX0, &l_flowerTexCoord, sizeof(l_flowerTexCoord), 8); + GXSETARRAY(GX_VA_POS, &l_flowerPos, sizeof(l_flowerPos), 0xC, false); + GXSETARRAY(GX_VA_NRM, &l_flowerNormal, sizeof(l_flowerNormal), 0xC, false); + GXSETARRAY(GX_VA_CLR0, &l_flowerColor, sizeof(l_flowerColor), 4, false); + GXSETARRAY(GX_VA_TEX0, &l_flowerTexCoord, sizeof(l_flowerTexCoord), 8, false); GXColor sp64; dFlower_room_c* sp5C = m_room; @@ -678,10 +652,10 @@ void dFlower_packet_c::draw() { sp5C++; } - GXSETARRAY(GX_VA_POS, mp_pos, sizeof(l_flowerPos2), 0xC); - GXSETARRAY(GX_VA_NRM, &l_flowerNormal2, sizeof(l_flowerNormal2), 0xC); - GXSETARRAY(GX_VA_CLR0, mp_colors, sizeof(l_flowerColor2), 4); - GXSETARRAY(GX_VA_TEX0, mp_texCoords, sizeof(l_flowerTexCoord2), 8); + GXSETARRAY(GX_VA_POS, mp_pos, sizeof(l_flowerPos2), 0xC, true); + GXSETARRAY(GX_VA_NRM, &l_flowerNormal2, sizeof(l_flowerNormal2), 0xC, false); + GXSETARRAY(GX_VA_CLR0, mp_colors, sizeof(l_flowerColor2), 4, true); + GXSETARRAY(GX_VA_TEX0, mp_texCoords, sizeof(l_flowerTexCoord2), 8, true); sp5C = m_room; diff --git a/src/d/actor/d_grass.inc b/src/d/actor/d_grass.inc index 2078e917ad..6351cc75fc 100644 --- a/src/d/actor/d_grass.inc +++ b/src/d/actor/d_grass.inc @@ -536,10 +536,10 @@ void dGrass_packet_c::draw() { GXSetVtxDescv(l_vtxDescList); GXSetVtxAttrFmtv(GX_VTXFMT0, l_vtxAttrFmtList); - GXSETARRAY(GX_VA_POS, mp_pos, sizeof(l_pos), sizeof(Vec)); - GXSETARRAY(GX_VA_NRM, mp_normal, sizeof(l_normal), sizeof(Vec)); - GXSETARRAY(GX_VA_CLR0, mp_colors, sizeof(l_color), sizeof(GXColor)); - GXSETARRAY(GX_VA_TEX0, mp_texCoords, sizeof(l_texCoord), 8); + GXSETARRAY(GX_VA_POS, mp_pos, sizeof(l_pos), sizeof(Vec), true); + GXSETARRAY(GX_VA_NRM, mp_normal, sizeof(l_normal), sizeof(Vec), true); + GXSETARRAY(GX_VA_CLR0, mp_colors, sizeof(l_color), sizeof(GXColor), true); + GXSETARRAY(GX_VA_TEX0, mp_texCoords, sizeof(l_texCoord), 8, true); GXColorS10 spA0 = {0, 0, 0, 0}; GXColorS10 sp98 = {0, 0, 0, 0}; diff --git a/src/d/d_drawlist.cpp b/src/d/d_drawlist.cpp index 9a37846dcb..8b34e99c2d 100644 --- a/src/d/d_drawlist.cpp +++ b/src/d/d_drawlist.cpp @@ -1521,7 +1521,7 @@ void dDlst_shadowControl_c::draw(Mtx param_0) { dKy_GxFog_set(); GXSetChanCtrl(GX_ALPHA0, GX_DISABLE, GX_SRC_REG, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE); - GXSETARRAY(GX_VA_POS, l_shadowVolPos, sizeof(l_shadowVolPos), sizeof(l_shadowVolPos[0])); + GXSETARRAY(GX_VA_POS, l_shadowVolPos, sizeof(l_shadowVolPos), sizeof(l_shadowVolPos[0]), true); GXSetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX3x4, GX_TG_POS, GX_TEXMTX0); GXSetNumTevStages(1); GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_F32, 0); @@ -1553,7 +1553,7 @@ void dDlst_shadowControl_c::draw(Mtx param_0) { GXSetTevSwapModeTable(GX_TEV_SWAP0, GX_CH_RED, GX_CH_GREEN, GX_CH_BLUE, GX_CH_ALPHA); GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_CLR_RGBA, GX_RGB8, 0); - GXSETARRAY(GX_VA_POS, l_simpleShadowPos, sizeof(l_simpleShadowPos), sizeof(l_simpleShadowPos[0])); + GXSETARRAY(GX_VA_POS, l_simpleShadowPos, sizeof(l_simpleShadowPos), sizeof(l_simpleShadowPos[0]), true); GXSetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY); GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR_NULL); GXSetAlphaCompare(GX_ALWAYS, 0, GX_AOP_OR, GX_ALWAYS, 0); diff --git a/src/d/d_map.cpp b/src/d/d_map.cpp index a0ee8d8128..3198b21f51 100644 --- a/src/d/d_map.cpp +++ b/src/d/d_map.cpp @@ -375,7 +375,7 @@ void renderingAmap_c::draw() { dMap_HIO_prm_res_dst_s::m_res->field_0x1a7, tmp); setAmapPaletteColor(0x2E, temp_r31, temp_r30, temp_r29, temp_r28); - renderingDAmap_c::draw(); + GX_DEBUG_GROUP(renderingDAmap_c::draw); } int renderingAmap_c::getDispType() const { diff --git a/src/d/d_map_path.cpp b/src/d/d_map_path.cpp index acc29ae1d3..a07f681f45 100644 --- a/src/d/d_map_path.cpp +++ b/src/d/d_map_path.cpp @@ -292,11 +292,22 @@ void dDrawPath_c::rendering(dDrawPath_c::floor_class const* p_floor) { } } +#ifdef TARGET_PC +static u32 getRoomPosArraySize(const dDrawPath_c::room_class* room) { + if (room->mpFloor == NULL || room->mpFloatData == NULL || room->mFloorNum == 0) { + return 0; + } + const dDrawPath_c::group_class* firstGroup = room->mpFloor[0].mpGroup; + JUT_ASSERT(0, firstGroup != NULL); + JUT_ASSERT(0, (const u8*)firstGroup >= (const u8*)room->mpFloatData); + return (const u8*)firstGroup - (const u8*)room->mpFloatData; +} +#endif + void dDrawPath_c::rendering(dDrawPath_c::room_class const* room) { JUT_ASSERT(1043, room != NULL); if (room != NULL) { - // TODO: FILL IN SIZE. - GXSETARRAY(GX_VA_POS, room->mpFloatData, 0, 8); + GXSetArray(GX_VA_POS, room->mpFloatData, getRoomPosArraySize(room), 8, false); floor_class* floor = room->mpFloor; if (floor != NULL) { @@ -492,13 +503,10 @@ void dRenderingFDAmap_c::renderingDecoration(dDrawPath_c::line_class const* p_li GXSetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV); #endif - // aurora doesn't support GX_POINTS yet - #if !TARGET_PC GXBegin(GX_POINTS, GX_VTXFMT0, 1); GXPosition1x16(data_p[0]); GXTexCoord2f32(0, 0); GXEnd(); - #endif data_p++; } diff --git a/src/d/d_map_path_dmap.cpp b/src/d/d_map_path_dmap.cpp index f53b253eb8..f2a41e8fd3 100644 --- a/src/d/d_map_path_dmap.cpp +++ b/src/d/d_map_path_dmap.cpp @@ -848,7 +848,7 @@ void renderingPlusDoor_c::drawDoorCommon(stage_tgsc_data_class const* i_doorData GXSetVtxDesc(GX_VA_TEX0, GX_INDEX8); GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_CLR_RGBA, GX_F32, 0); GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_CLR_RGBA, GX_RGB565, 0); - GXSETARRAY(GX_VA_TEX0, (void*)l_tex0, sizeof(l_tex0), 2); + GXSETARRAY(GX_VA_TEX0, l_tex0, sizeof(l_tex0), 2, true); setTevSettingIntensityTextureToCI(); @@ -1007,7 +1007,7 @@ void renderingPlusDoorAndCursor_c::drawTreasure() { GXSetVtxDesc(GX_VA_TEX0, GX_INDEX8); GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_CLR_RGB, GX_F32, 0); GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_CLR_RGBA, GX_RGB565, 0); - GXSETARRAY(GX_VA_TEX0, (void*)l_iconTex0, sizeof(l_iconTex0), 2); + GXSETARRAY(GX_VA_TEX0, l_iconTex0, sizeof(l_iconTex0), 2, true); setTevSettingIntensityTextureToCI(); @@ -1081,7 +1081,7 @@ void renderingPlusDoorAndCursor_c::drawTreasureAfterPlayer() { GXSetVtxDesc(GX_VA_TEX0, GX_INDEX8); GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_CLR_RGB, GX_F32, 0); GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_CLR_RGBA, GX_RGB565, 0); - GXSETARRAY(GX_VA_TEX0, (void*)l_iconTex0, sizeof(l_iconTex0), 2); + GXSETARRAY(GX_VA_TEX0, l_iconTex0, sizeof(l_iconTex0), 2, true); setTevSettingIntensityTextureToCI(); diff --git a/src/m_Do/m_Do_ext.cpp b/src/m_Do/m_Do_ext.cpp index 993937f0e3..d744d048ba 100644 --- a/src/m_Do/m_Do_ext.cpp +++ b/src/m_Do/m_Do_ext.cpp @@ -2383,8 +2383,8 @@ void mDoExt_3DlineMat0_c::draw() { int var_r26 = (field_0x14 << 1) & 0xFFFF; for (int i = 0; i < field_0x10; i++) { - GXSETARRAY(GX_VA_POS, field_0x18->field_0x8[field_0x16], sizeof(cXyz) * var_r26, sizeof(cXyz)); - GXSETARRAY(GX_VA_NRM, field_0x18->field_0x10[field_0x16], 3 * var_r26, 3); + GXSETARRAY(GX_VA_POS, field_0x18->field_0x8[field_0x16], sizeof(cXyz) * var_r26, sizeof(cXyz), true); + GXSETARRAY(GX_VA_NRM, field_0x18->field_0x10[field_0x16], 3 * var_r26, 3, true); GXBegin(GX_TRIANGLESTRIP, GX_VTXFMT0, var_r26); for (u16 j = 0; j < (u16)var_r26; j++) { @@ -2699,11 +2699,11 @@ void mDoExt_3DlineMat1_c::draw() { mDoExt_3Dline_c* lines = mpLines; u16 vert_num = field_0x34 << 1; for (s32 i = 0; i < mNumLines; i++) { - GXSETARRAY(GX_VA_POS, lines->field_0x8[mIsDrawn], vert_num * sizeof(cXyz), sizeof(cXyz)); + GXSETARRAY(GX_VA_POS, lines->field_0x8[mIsDrawn], vert_num * sizeof(cXyz), sizeof(cXyz), true); GXSETARRAY(GX_VA_NRM, lines->field_0x10[mIsDrawn], vert_num * sizeof(mDoExt_3Dline_field_0x10_c), - sizeof(mDoExt_3Dline_field_0x10_c)); - GXSETARRAY(GX_VA_TEX0, lines->field_0x18[mIsDrawn], vert_num * sizeof(cXy), sizeof(cXy)); + sizeof(mDoExt_3Dline_field_0x10_c), true); + GXSETARRAY(GX_VA_TEX0, lines->field_0x18[mIsDrawn], vert_num * sizeof(cXy), sizeof(cXy), true); GXBegin(GX_TRIANGLESTRIP, GX_VTXFMT0, vert_num); u16 j = 0; @@ -3036,7 +3036,7 @@ mDoExt_cube8pPacket::mDoExt_cube8pPacket(cXyz* i_points, const GXColor& i_color) } void drawCube(MtxP mtx, cXyz* pos, const GXColor& color) { - GXSETARRAY(GX_VA_POS, pos, sizeof(*pos), sizeof(*pos)); + GXSETARRAY(GX_VA_POS, pos, sizeof(cXyz) * 8, sizeof(cXyz), true); GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_F32, 0); GXClearVtxDesc(); GXSetVtxDesc(GX_VA_POS, GX_INDEX8); @@ -3115,7 +3115,7 @@ mDoExt_quadPacket::mDoExt_quadPacket(cXyz* i_points, const GXColor& i_color, u8 } void mDoExt_quadPacket::draw() { - GXSETARRAY(GX_VA_POS, mPoints, sizeof(mPoints), sizeof(cXyz)); + GXSETARRAY(GX_VA_POS, mPoints, sizeof(mPoints), sizeof(cXyz), true); GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_F32, 0); GXClearVtxDesc(); GXSetVtxDesc(GX_VA_POS, GX_INDEX8); @@ -3167,7 +3167,7 @@ mDoExt_trianglePacket::mDoExt_trianglePacket(cXyz* i_points, const GXColor& i_co void mDoExt_trianglePacket::draw() { j3dSys.reinitGX(); - GXSETARRAY(GX_VA_POS, mPoints, sizeof(mPoints), sizeof(cXyz)); + GXSETARRAY(GX_VA_POS, mPoints, sizeof(mPoints), sizeof(cXyz), true); GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_F32, 0); GXClearVtxDesc(); GXSetVtxDesc(GX_VA_POS, GX_INDEX8); From b7aec5a207ae9f6e40566931252ba2baf79932f7 Mon Sep 17 00:00:00 2001 From: TakaRikka Date: Tue, 31 Mar 2026 00:47:41 -0700 Subject: [PATCH 147/350] enable hyrule field speedhack for now --- src/d/d_map_path.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/d/d_map_path.cpp b/src/d/d_map_path.cpp index a07f681f45..b5ecee95b3 100644 --- a/src/d/d_map_path.cpp +++ b/src/d/d_map_path.cpp @@ -455,7 +455,7 @@ dMpath_n::dTexObjAggregate_c dMpath_n::m_texObjAgg; * make the map look worse for extra speed in the emulator, especially in large * areas such as hyrule field. */ -// #define HYRULE_FIELD_SPEEDHACK +#define HYRULE_FIELD_SPEEDHACK void dRenderingFDAmap_c::renderingDecoration(dDrawPath_c::line_class const* p_line) { s32 width = getDecorationLineWidth(p_line->field_0x1); From 53c915d627e304fca4bdc032a7d082d036d48a7b Mon Sep 17 00:00:00 2001 From: TakaRikka Date: Tue, 31 Mar 2026 01:08:11 -0700 Subject: [PATCH 148/350] remove unneeded ospanic --- libs/JSystem/src/JMath/JMath.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/libs/JSystem/src/JMath/JMath.cpp b/libs/JSystem/src/JMath/JMath.cpp index 43b9680308..899210e293 100644 --- a/libs/JSystem/src/JMath/JMath.cpp +++ b/libs/JSystem/src/JMath/JMath.cpp @@ -77,8 +77,6 @@ void JMAFastVECNormalize(__REGISTER const Vec* src, __REGISTER Vec* dst) { dst->y = src->y * length; dst->z = src->z * length; #endif // clang-format on - - OSPanic(__FILE__, __LINE__, "UNIMPLEMENTED"); } void JMAVECScaleAdd(__REGISTER const Vec* vec1, __REGISTER const Vec* vec2, __REGISTER Vec* dst, From bf07cf4c5fdbd54dadcd6070e5465703985b40f3 Mon Sep 17 00:00:00 2001 From: Lurs Date: Tue, 31 Mar 2026 11:38:59 +0200 Subject: [PATCH 149/350] Minimap fixes (needs change for intensityI8 in aurora) and some other BE fixes --- include/d/d_map.h | 16 ++++++++-------- include/d/d_map_path.h | 2 +- libs/JSystem/src/J3DGraphBase/J3DTevs.cpp | 2 +- src/d/actor/d_a_obj_flag.cpp | 2 +- src/d/d_bg_parts.cpp | 2 +- src/d/d_menu_fmap.cpp | 2 +- src/d/d_msg_object.cpp | 8 ++++---- 7 files changed, 17 insertions(+), 17 deletions(-) diff --git a/include/d/d_map.h b/include/d/d_map.h index 66a51aae75..28786e4f94 100644 --- a/include/d/d_map.h +++ b/include/d/d_map.h @@ -66,14 +66,14 @@ struct dMap_prm_res_s { /* 0x1A7 */ u8 field_0x1a7; /* 0x1A8 */ u8 field_0x1a8; /* 0x1A9 */ u8 field_0x1a9; - /* 0x1AA */ s16 field_0x1aa; - /* 0x1AC */ s16 field_0x1ac; - /* 0x1AE */ s16 field_0x1ae; - /* 0x1B0 */ s16 field_0x1b0; - /* 0x1B2 */ s16 field_0x1b2; - /* 0x1B4 */ s16 field_0x1b4; - /* 0x1B6 */ s16 field_0x1b6; - /* 0x1B8 */ f32 cursor_size; + /* 0x1AA */ BE(s16) field_0x1aa; + /* 0x1AC */ BE(s16) field_0x1ac; + /* 0x1AE */ BE(s16) field_0x1ae; + /* 0x1B0 */ BE(s16) field_0x1b0; + /* 0x1B2 */ BE(s16) field_0x1b2; + /* 0x1B4 */ BE(s16) field_0x1b4; + /* 0x1B6 */ BE(s16) field_0x1b6; + /* 0x1B8 */ BE(f32) cursor_size; }; struct dMap_prm_hio_s { diff --git a/include/d/d_map_path.h b/include/d/d_map_path.h index 3dcfd2b38d..495fdf396a 100644 --- a/include/d/d_map_path.h +++ b/include/d/d_map_path.h @@ -5,7 +5,7 @@ #include "JSystem/JHostIO/JORMContext.h" struct dMpath_RGB5A3_s { - u16 color; + BE(u16) color; }; namespace dMpath_ColorCnv_n { diff --git a/libs/JSystem/src/J3DGraphBase/J3DTevs.cpp b/libs/JSystem/src/J3DGraphBase/J3DTevs.cpp index 0c712dac81..09ddc0b405 100644 --- a/libs/JSystem/src/J3DGraphBase/J3DTevs.cpp +++ b/libs/JSystem/src/J3DGraphBase/J3DTevs.cpp @@ -276,7 +276,7 @@ bool isTexNoReg(void* pDL) { } u16 getTexNoReg(void* pDL) { - u32 var_r31 = *(u32*)((u8*)pDL + 1); + u32 var_r31 = *(BE(u32)*)((u8*)pDL + 1); return var_r31 & 0xFFFFFF; } diff --git a/src/d/actor/d_a_obj_flag.cpp b/src/d/actor/d_a_obj_flag.cpp index 9e4d238fed..b3f55ff1d2 100644 --- a/src/d/actor/d_a_obj_flag.cpp +++ b/src/d/actor/d_a_obj_flag.cpp @@ -16,7 +16,7 @@ daObjFlag_c::M_attrs const daObjFlag_c::M_attr = { }; void daObjFlag_c::create_init() { - field_0x5dc = (*(u32*)dComIfG_getObjectRes(daSetBgObj_c::getArcName(this), "spec.dat")) & 0xffff; + field_0x5dc = (*(BE(u32)*)dComIfG_getObjectRes(daSetBgObj_c::getArcName(this), "spec.dat")) & 0xffff; mPos = cXyz(current.pos.x, current.pos.y + field_0x5dc, current.pos.z); mFlagJoints[0].mRv = (short)(cM_rnd() * 65535.0f); mFlagJoints[1].mRv = (short)(cM_rnd() * 65535.0f); diff --git a/src/d/d_bg_parts.cpp b/src/d/d_bg_parts.cpp index 930c3f63b1..3c378bf325 100644 --- a/src/d/d_bg_parts.cpp +++ b/src/d/d_bg_parts.cpp @@ -106,7 +106,7 @@ void dBgp_c::modelMaterial_c::set(J3DModelData* i_modelData, J3DMaterial* i_mate void dBgp_c::model_c::create(J3DModelData* i_modelData, Mtx i_mtx) { const void* binary = i_modelData->getBinary(); - mId = *(u32*)((char*)binary + 0x1C); + mId = *(BE(u32)*)((char*)binary + 0x1C); if (mId != 0xFFFF) { addShare(mId); } diff --git a/src/d/d_menu_fmap.cpp b/src/d/d_menu_fmap.cpp index c8969f2ac3..851e22b7c7 100644 --- a/src/d/d_menu_fmap.cpp +++ b/src/d/d_menu_fmap.cpp @@ -2278,7 +2278,7 @@ void dMenu_Fmap_c::decodeFieldMapData() { dMenuMapCommon_c::Stage_c* stage_data = (dMenuMapCommon_c::Stage_c*)(field_data + mpFieldDat->mStageDataOffset); mDataNumMax = stage_data->mCount; - mAllTitleName = *(u16*)(field_data + 0x16); + mAllTitleName = *(BE(u16)*)(field_data + 0x16); setTitleName(mAllTitleName); dMenu_Fmap_field_region_data_c::data* regions = region_data->mData; char tex_path[20]; diff --git a/src/d/d_msg_object.cpp b/src/d/d_msg_object.cpp index 9a8f6d594a..8836acff10 100644 --- a/src/d/d_msg_object.cpp +++ b/src/d/d_msg_object.cpp @@ -656,7 +656,7 @@ void dMsgObject_c::setMessageIndexDemo(u32 revoMsgIndex, bool param_2) { u32 dMsgObject_c::getMessageIndex(u32 param_0) { u32 i = 0; JMSMesgInfo_c* pMsg = (JMSMesgInfo_c*)((char*)mpMsgDt + 0x20); - u32 msgIndexCount = *((u16*)((char*)mpMsgDt + 0x28)); + u32 msgIndexCount = *((BE(u16)*)((char*)mpMsgDt + 0x28)); int rv; for (; i < msgIndexCount; i++) { if (pMsg->entries[i].message_id == param_0) { @@ -683,12 +683,12 @@ u32 dMsgObject_c::getRevoMessageIndex(u32 param_1) { JUT_ASSERT(1916, groupID==s_groupID || groupID == 0) changeGroup(groupID); pMsg = (JMSMesgInfo_c*)((char*)mpMsgDt + 0x20); - msgIndexCount = *((u16*)((char*)mpMsgDt + 0x28)); + msgIndexCount = *((BE(u16)*)((char*)mpMsgDt + 0x28)); for (; i < msgIndexCount; i++) { if (pMsg->entries[i].message_id == param_1) { s8* ptr = (s8*)pMsg + pMsg->header.size + pMsg->entries[i].string_offset + 8; if (ptr[0] == 26 && ptr[2] == 3 && (s8)ptr[4] == 0) { - rv = pMsg->entries[*(int*)(ptr + 5)].message_id; + rv = pMsg->entries[*(BE(int)*)(ptr + 5)].message_id; } else { rv = param_1; } @@ -706,7 +706,7 @@ u32 dMsgObject_c::getRevoMessageIndex(u32 param_1) { u32 dMsgObject_c::getMessageIndexAlways(u32 param_0) { u32 i = 0; JMSMesgInfo_c* pMsg = (JMSMesgInfo_c*)((char*)mpMsgRes + 0x20); - u32 msgIndexCount = *((u16*)((char*)mpMsgRes + 0x28)); + u32 msgIndexCount = *((BE(u16)*)((char*)mpMsgRes + 0x28)); int rv; for (; i < msgIndexCount; i++) { if (pMsg->entries[i].message_id == param_0) { From 734d1e8945942ae8c440de4e2b86eb772ac62b0f Mon Sep 17 00:00:00 2001 From: Lurs Date: Tue, 31 Mar 2026 13:14:08 +0200 Subject: [PATCH 150/350] Fix also the big map --- extern/aurora | 2 +- include/d/d_menu_map_common.h | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/extern/aurora b/extern/aurora index 819c72914e..d819f47229 160000 --- a/extern/aurora +++ b/extern/aurora @@ -1 +1 @@ -Subproject commit 819c72914e90cb5eec3edf6c14ca9d7c7bf9aa3d +Subproject commit d819f47229e6d2db1ffbb4314cb36382c6ad8a8a diff --git a/include/d/d_menu_map_common.h b/include/d/d_menu_map_common.h index 84da15328c..989aee7d8b 100644 --- a/include/d/d_menu_map_common.h +++ b/include/d/d_menu_map_common.h @@ -75,9 +75,9 @@ public: /* 0x00 */ char mName[8]; /* 0x08 */ u8 mRoomNo; /* 0x09 */ u8 mRegionNo; - /* 0x0A */ u16 mAreaName; - /* 0x0C */ f32 mOffsetX; - /* 0x10 */ f32 mOffsetZ; + /* 0x0A */ BE(u16) mAreaName; + /* 0x0C */ BE(f32) mOffsetX; + /* 0x10 */ BE(f32) mOffsetZ; }; /* 0x0 */ u8 mCount; From aea7d97bb5ab594d46a521d4507100cd3f4bee9d Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Tue, 31 Mar 2026 21:11:19 +0200 Subject: [PATCH 151/350] Fix heap corruption caused by JKRExpHeap::do_freeAll Caused by a size that should've been sizeof(CMemBlock). Simple way to repro was to open and close the full map on dpad, afterwards heap check would fail. --- libs/JSystem/src/JKernel/JKRExpHeap.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/JSystem/src/JKernel/JKRExpHeap.cpp b/libs/JSystem/src/JKernel/JKRExpHeap.cpp index 3ec15cd161..fbcde13113 100644 --- a/libs/JSystem/src/JKernel/JKRExpHeap.cpp +++ b/libs/JSystem/src/JKernel/JKRExpHeap.cpp @@ -472,7 +472,7 @@ void JKRExpHeap::do_freeAll() { JKRHeap::callAllDisposer(); mHeadFreeList = (CMemBlock*)mStart; mTailFreeList = mHeadFreeList; - mHeadFreeList->initiate(NULL, NULL, mSize - 0x10, 0, 0); + mHeadFreeList->initiate(NULL, NULL, mSize - sizeof(CMemBlock), 0, 0); mHeadUsedList = NULL; mTailUsedList = NULL; #if DEBUG From 4fd8d26bab5fcae499badb002d7aadd36a8e5174 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Tue, 31 Mar 2026 21:13:13 +0200 Subject: [PATCH 152/350] Heap allocation failure logs free blocks, always crashes even without DEBUG --- libs/JSystem/src/JKernel/JKRExpHeap.cpp | 30 ++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/libs/JSystem/src/JKernel/JKRExpHeap.cpp b/libs/JSystem/src/JKernel/JKRExpHeap.cpp index fbcde13113..4a6712cb65 100644 --- a/libs/JSystem/src/JKernel/JKRExpHeap.cpp +++ b/libs/JSystem/src/JKernel/JKRExpHeap.cpp @@ -214,9 +214,33 @@ void* JKRExpHeap::do_alloc(u32 size, int alignment) { #endif #if TARGET_PC - JUT_ASSERT_MSG_F(__LINE__, ptr != nullptr, - "Failed to alloc memory! (%s) (0x%X bytes).\n Heap size: %u\n Used size: %u", - this->getName(), size, getSize(), getTotalUsedSize()); + if (!ptr) { + // Allocation failed. + OSReport_Error( + "Failed to alloc memory! (%s) (0x%X bytes).\n Heap size: 0x%X\n Used size: 0x%X\n", + this->getName(), size, getSize(), getTotalUsedSize()); + OSReport_Error("Free block list as follows:\n"); + OSReport_Error("Start | End | Size \n"); + + int i = 0; + for (const CMemBlock* block = mHeadFreeList; block; block = block->mNext) { + if (block->mMagic) { + // Allocated, ignore. + continue; + } + if (i++ > 10) { + OSReport_Error("\n"); + break; + } + + auto blockStart = (uintptr_t)block - (uintptr_t)mStart; + auto blockEnd = (uintptr_t)block + block->size - (uintptr_t)mStart; + auto blockSize = block->size; + OSReport_Error("%08X | %08X | %08X\n", (u32) blockStart, (u32) blockEnd, (u32) blockSize); + } + + CRASH("Aborting due to allocation failure!"); + } #else if (ptr == NULL) { JUTWarningConsole_f(":::cannot alloc memory (0x%x byte).\n", size); From ab7e34975e0cb07e81a5c1f1f88f64d50ca2bcd0 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Tue, 31 Mar 2026 21:14:02 +0200 Subject: [PATCH 153/350] Allow OSReport to *really* be forced on via menu. There's a ton of stuff that's slipping under the radar because OSReport_system keeps disabling OSReport. --- include/os_report.h | 6 ++++++ src/dusk/imgui/ImGuiMenuTools.cpp | 1 + src/m_Do/m_Do_printf.cpp | 15 +++++++++++++++ 3 files changed, 22 insertions(+) diff --git a/include/os_report.h b/include/os_report.h index 1fcf08bc57..78177a199d 100644 --- a/include/os_report.h +++ b/include/os_report.h @@ -31,4 +31,10 @@ extern u8 __OSReport_Warning_disable; extern u8 __OSReport_System_disable; extern u8 __OSReport_enable; +#if TARGET_PC +namespace dusk { + extern bool OSReportReallyForceEnable; +} +#endif + #endif // _OS_REPORT_H diff --git a/src/dusk/imgui/ImGuiMenuTools.cpp b/src/dusk/imgui/ImGuiMenuTools.cpp index e2537a264a..313d398498 100644 --- a/src/dusk/imgui/ImGuiMenuTools.cpp +++ b/src/dusk/imgui/ImGuiMenuTools.cpp @@ -45,6 +45,7 @@ namespace dusk { ImGui::MenuItem("Player Info", nullptr, &m_showPlayerInfo); ImGui::MenuItem("Save Editor", nullptr, &m_showSaveEditor); ImGui::MenuItem("Audio Debug", "F7", &m_showAudioDebug); + ImGui::MenuItem("OSReport Force", nullptr, &OSReportReallyForceEnable); ImGui::EndMenu(); } diff --git a/src/m_Do/m_Do_printf.cpp b/src/m_Do/m_Do_printf.cpp index f4ef1fad00..3cacece77d 100644 --- a/src/m_Do/m_Do_printf.cpp +++ b/src/m_Do/m_Do_printf.cpp @@ -21,6 +21,10 @@ u8 __OSReport_System_disable; u8 __OSReport_enable; +#if TARGET_PC +bool dusk::OSReportReallyForceEnable = false; +#endif + #ifdef __GEKKO__ asm void OSSwitchFiberEx(__REGISTER u32 param_0, __REGISTER u32 param_1, __REGISTER u32 param_2, __REGISTER u32 param_3, __REGISTER u32 code, __REGISTER u32 stack) { nofralloc @@ -153,6 +157,9 @@ void mDoPrintf_vprintf_Thread(char const* fmt, va_list args) { #endif vprintf(fmt, args); + if (dusk::OSReportReallyForceEnable) { + fflush(stdout); + } #if DEBUG if (thread != NULL) { @@ -170,12 +177,16 @@ void mDoPrintf_vprintf(char const* fmt, va_list args) { #if DEBUG mDoPrintf_vprintf_Thread(fmt, args); #else +#if !TARGET_PC u8* stackPtr = (u8*)OSGetStackPointer(); if (stackPtr < (u8*)currentThread->stackEnd + 0xA00 || stackPtr > currentThread->stackBase) { mDoPrintf_vprintf_Interrupt(fmt, args); } else { +#endif mDoPrintf_vprintf_Thread(fmt, args); +#if !TARGET_PC } +#endif #endif } } @@ -184,7 +195,11 @@ void mDoPrintf_VReport(const char* fmt, va_list args) { if (!print_initialized) { OSReportInit(); } +#if TARGET_PC + if (dusk::OSReportReallyForceEnable || __OSReport_enable || !__OSReport_disable) { +#else if (__OSReport_enable || !__OSReport_disable) { +#endif OSThread* currentThread = mDoExt_GetCurrentRunningThread(); if (__OSReport_MonopolyThread == NULL || __OSReport_MonopolyThread == currentThread) { mDoPrintf_vprintf(fmt, args); From e61d7a19b800b8310d1865ae41c6c66b5a635d1d Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Tue, 31 Mar 2026 21:18:25 +0200 Subject: [PATCH 154/350] Expand heap debug ImGui Now allows seeing all blocks in ExpHeaps and running heap integrity checks. --- CMakeLists.txt | 2 +- .../include/JSystem/JKernel/JKRExpHeap.h | 7 + src/dusk/imgui/ImGuiHeapOverlay.cpp | 169 +++++++++++++++++- 3 files changed, 173 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1637eb202d..7d434d874b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -107,7 +107,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 absl::flat_hash_map) 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/libs/JSystem/include/JSystem/JKernel/JKRExpHeap.h b/libs/JSystem/include/JSystem/JKernel/JKRExpHeap.h index a31b2c5e84..09687e688b 100644 --- a/libs/JSystem/include/JSystem/JKernel/JKRExpHeap.h +++ b/libs/JSystem/include/JSystem/JKernel/JKRExpHeap.h @@ -121,6 +121,13 @@ public: static s32 getUsedSize_(JKRExpHeap* heap) { return heap->mSize - heap->getTotalFreeSize(); } static void* getState_(TState* state) { return getState_buf_(state); } + +#if TARGET_PC + [[nodiscard]] CMemBlock* getFreeHead() { return mHeadFreeList; } + [[nodiscard]] const CMemBlock* getFreeHead() const { return mHeadFreeList; } + [[nodiscard]] CMemBlock* getUsedHead() { return mHeadUsedList; } + [[nodiscard]] const CMemBlock* getUsedHead() const { return mHeadUsedList; } +#endif }; inline JKRExpHeap* JKRCreateExpHeap(u32 size, JKRHeap* parent, bool errorFlag) { diff --git a/src/dusk/imgui/ImGuiHeapOverlay.cpp b/src/dusk/imgui/ImGuiHeapOverlay.cpp index 588857cedb..655993950e 100644 --- a/src/dusk/imgui/ImGuiHeapOverlay.cpp +++ b/src/dusk/imgui/ImGuiHeapOverlay.cpp @@ -1,14 +1,25 @@ #include #include -#include "JSystem/JFramework/JFWSystem.h" -#include "JSystem/JKernel/JKRHeap.h" -#include "imgui.h" #include "ImGuiConsole.hpp" #include "ImGuiMenuTools.hpp" +#include "JSystem/JFramework/JFWSystem.h" +#include "JSystem/JKernel/JKRExpHeap.h" +#include "JSystem/JKernel/JKRHeap.h" +#include "absl/container/flat_hash_map.h" +#include "imgui.h" + +struct OpenHeapData { + bool Safe; + bool HeapCheckRan; + bool HeapCheckFailed; +}; + +static absl::flat_hash_map OpenHeapWindows; namespace dusk { static void DrawTableCore(); + void ShowHeapDetailed(JKRHeap* heap, OpenHeapData& data, bool& open); void ImGuiMenuTools::ShowHeapOverlay() { if (!ImGuiConsole::CheckMenuViewToggle(ImGuiKey_F4, m_showHeapOverlay)) { @@ -16,9 +27,13 @@ namespace dusk { } if (ImGui::Begin("Heaps", &m_showHeapOverlay)) { + for (auto& x : OpenHeapWindows) { + x.second.Safe = false; + } + if (ImGui::BeginTable( "heaps", - 5, + 6, ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable)) { DrawTableCore(); @@ -28,6 +43,19 @@ namespace dusk { } ImGui::End(); + + std::vector closeQueue; + for (auto& [heap, safe] : OpenHeapWindows) { + auto open = true; + ShowHeapDetailed(heap, safe, open); + if (!open) { + closeQueue.push_back(heap); + } + } + + for (auto toRemove : closeQueue) { + OpenHeapWindows.erase(toRemove); + } } static void DrawHeap(JKRHeap* heap, int depth = 0); @@ -44,6 +72,7 @@ namespace dusk { ImGui::TextUnformatted("Total"); ImGui::TableNextColumn(); ImGui::TextUnformatted("Type"); + ImGui::TableNextColumn(); DrawHeap(reinterpret_cast(JFWSystem::rootHeap)); } @@ -70,8 +99,15 @@ namespace dusk { static void DrawHeap(JKRHeap* heap, const int depth) { ImGui::TableNextRow(); + char idBuf[32]; + snprintf(idBuf, sizeof(idBuf), "%p", heap); + ImGui::PushID(idBuf); ImGui::TableNextColumn(); + if (OpenHeapWindows.find(heap) != OpenHeapWindows.end()) { + OpenHeapWindows[heap].Safe = true; + } + auto indentSize = depth * 16; if (indentSize != 0) ImGui::Indent(indentSize); @@ -103,9 +139,134 @@ namespace dusk { auto typeString = GetHeapType(heap); ImGui::TextUnformatted(typeString.data(), typeString.data() + 4); + ImGui::TableNextColumn(); + if (ImGui::Button("View")) { + OpenHeapWindows[heap].Safe = true; + } + const JSUTree& tree = heap->getHeapTree(); for (JSUTreeIterator iter(tree.getFirstChild()); iter != tree.getEndChild(); ++iter) { DrawHeap(*iter, depth + 1); } + + ImGui::PopID(); + } + + struct MemBlockPair { + JKRExpHeap::CMemBlock* block; + bool used; + + auto& operator->() { + return block; + } + }; + + static std::vector FindAllHeapBlocks(JKRExpHeap* heap) { + std::vector result; + + for (JKRExpHeap::CMemBlock* b = heap->getFreeHead(); b; b = b->getNextBlock()) { + result.push_back({b, false}); + } + + for (JKRExpHeap::CMemBlock* b = heap->getUsedHead(); b; b = b->getNextBlock()) { + result.push_back({b, true}); + } + + std::ranges::sort(result, [](auto a, auto b) { return a.block < b.block; }); + + return result; + } + + static void ShowHeapDetailed(JKRHeap* heap, OpenHeapData& data, bool& open) { + char title[128]; + const char* name = data.Safe ? heap->getName() : "INVALID"; + snprintf(title, sizeof(title), "Heap %s##%p", heap->getName(), static_cast(heap)); + + if (!ImGui::Begin(name, &open)) { + ImGui::End(); + return; + } + + if (!data.Safe) { + ImGui::TextUnformatted("Heap no longer exists"); + ImGui::End(); + return; + } + + heap->lock(); + + ImGui::Text("Name: %s", heap->getName()); + const auto size = BytesToString(heap->getSize()); + const auto freeSize = BytesToString(heap->getFreeSize()); + ImGui::Text("Size: %08X (%s), free: %08X (%s)", heap->getSize(), size.c_str(), heap->getFreeSize(), freeSize.c_str()); + + if (ImGui::Button("Check")) { + data.HeapCheckFailed = !heap->check(); + data.HeapCheckRan = true; + } + + if (data.HeapCheckFailed) { + ImGui::SameLine(); + ImColor red = IM_COL32(0xFF, 0, 0, 0xFF); + ImGui::TextColored(red, "Heap check failed"); + } else if (data.HeapCheckRan) { + ImGui::SameLine(); + ImColor red = IM_COL32(0, 0xFF, 0, 0xFF); + ImGui::TextColored(red, "Heap check passed"); + } + + if (heap->getHeapType() == 'EXPH') { + auto expHeap = dynamic_cast(heap); + + ImGui::SeparatorText("Blocks"); + + if (ImGui::BeginTable("Blocks", 5, ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable)) { + ImGui::TableSetupColumn("Start", ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("End", ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("Size"); + ImGui::TableSetupColumn("Allocated", ImGuiTableColumnFlags_WidthFixed, ImGui::CalcTextSize("Allocated").x); + ImGui::TableSetupColumn("IsValid", ImGuiTableColumnFlags_WidthFixed, ImGui::CalcTextSize("IsValid").x); + ImGui::TableHeadersRow(); + + const auto blocks = FindAllHeapBlocks(expHeap); + + for (auto block : blocks) { + assert(block->getSize() != 0); + ImGui::TableNextRow(); + + if (block.used) { + ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg1, IM_COL32(0xFF, 0, 0, 0x44)); + } else { + ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg1, IM_COL32(0, 0xFF, 0, 0x44)); + } + + char bufId[32]; + snprintf(bufId, sizeof(bufId), "%p", block); + ImGui::PushID(bufId); + ImGui::TableNextColumn(); + ImGui::Text("%08X", (u32)((uintptr_t)block.block - (uintptr_t)expHeap->getStartAddr())); + ImGui::TableNextColumn(); + ImGui::Text("%08X", (u32)((uintptr_t)block.block + block->getSize() - (uintptr_t)expHeap->getStartAddr())); + ImGui::TableNextColumn(); + auto sizeNice = BytesToString(block->getSize()); + ImGui::Text("%08X (%s)", block->getSize(), sizeNice.c_str()); + ImGui::TableNextColumn(); + ImGui::TextUnformatted(block.used ? "True" : "False"); + ImGui::TableNextColumn(); + ImGui::TextUnformatted(block->isValid() ? "True" : "False"); + if (block->isValid() != block.used) { + ImGui::SameLine(); + ImGui::TextUnformatted("(!!!)"); + } + ImGui::PopID(); + } + + ImGui::EndTable(); + } + } + + ImGui::End(); + + heap->unlock(); } } From 081dfcb9a8b958996307ebdf2b1223d1ecd1a5d9 Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Tue, 31 Mar 2026 17:33:28 -0400 Subject: [PATCH 155/350] 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 2a926724106625c45642c6c979e5a9e36066c1d5 Mon Sep 17 00:00:00 2001 From: Max Roncace Date: Tue, 31 Mar 2026 18:46:05 -0400 Subject: [PATCH 156/350] Update Aurora for input changes --- extern/aurora | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/aurora b/extern/aurora index d819f47229..decc4e4f75 160000 --- a/extern/aurora +++ b/extern/aurora @@ -1 +1 @@ -Subproject commit d819f47229e6d2db1ffbb4314cb36382c6ad8a8a +Subproject commit decc4e4f750d61cf1b9d1b82c6e8e94cd396b617 From 7749106ec76a49289bb5d140c771782648839929 Mon Sep 17 00:00:00 2001 From: Max Roncace Date: Tue, 31 Mar 2026 18:46:39 -0400 Subject: [PATCH 157/350] Fix compile error in ImGuiHeapOverlay due to invalid static keyword --- src/dusk/imgui/ImGuiHeapOverlay.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dusk/imgui/ImGuiHeapOverlay.cpp b/src/dusk/imgui/ImGuiHeapOverlay.cpp index 655993950e..62d1d9f48c 100644 --- a/src/dusk/imgui/ImGuiHeapOverlay.cpp +++ b/src/dusk/imgui/ImGuiHeapOverlay.cpp @@ -177,7 +177,7 @@ namespace dusk { return result; } - static void ShowHeapDetailed(JKRHeap* heap, OpenHeapData& data, bool& open) { + void ShowHeapDetailed(JKRHeap* heap, OpenHeapData& data, bool& open) { char title[128]; const char* name = data.Safe ? heap->getName() : "INVALID"; snprintf(title, sizeof(title), "Heap %s##%p", heap->getName(), static_cast(heap)); From 8a9116501daf404b8146b82b30f45f57aecce441 Mon Sep 17 00:00:00 2001 From: Max Roncace Date: Mon, 30 Mar 2026 23:03:10 -0400 Subject: [PATCH 158/350] Implement controller axis remapping This utilizes new functionality in encounter/aurora#73. --- src/dusk/imgui/ImGuiMenuGame.cpp | 178 +++++++++++++++++++++++++------ src/dusk/imgui/ImGuiMenuGame.hpp | 3 +- 2 files changed, 148 insertions(+), 33 deletions(-) diff --git a/src/dusk/imgui/ImGuiMenuGame.cpp b/src/dusk/imgui/ImGuiMenuGame.cpp index c68140f3cd..7b90dcb5b5 100644 --- a/src/dusk/imgui/ImGuiMenuGame.cpp +++ b/src/dusk/imgui/ImGuiMenuGame.cpp @@ -121,17 +121,38 @@ namespace dusk { return; } - // if pending for an input mapping, check to set new input - if (m_controllerConfig.m_pendingMapping != nullptr) { + // if pending for a button mapping, check to set new input + if (m_controllerConfig.m_pendingButtonMapping != nullptr) { s32 nativeButton = PADGetNativeButtonPressed(m_controllerConfig.m_pendingPort); if (nativeButton != -1) { - m_controllerConfig.m_pendingMapping->nativeButton = nativeButton; - m_controllerConfig.m_pendingMapping = nullptr; + m_controllerConfig.m_pendingButtonMapping->nativeButton = nativeButton; + m_controllerConfig.m_pendingButtonMapping = nullptr; m_controllerConfig.m_pendingPort = -1; PADBlockInput(false); } } + // if pending for an axis mapping, check to set new input + if (m_controllerConfig.m_pendingAxisMapping != nullptr) { + auto nativeAxis = PADGetNativeAxisPulled(m_controllerConfig.m_pendingPort); + if (nativeAxis.nativeAxis != -1) { + m_controllerConfig.m_pendingAxisMapping->nativeAxis = nativeAxis; + m_controllerConfig.m_pendingAxisMapping->nativeButton = -1; + m_controllerConfig.m_pendingAxisMapping = nullptr; + m_controllerConfig.m_pendingPort = -1; + PADBlockInput(false); + } else { + auto nativeButton = PADGetNativeButtonPressed(m_controllerConfig.m_pendingPort); + if (nativeButton != -1) { + m_controllerConfig.m_pendingAxisMapping->nativeAxis = {-1, AXIS_SIGN_POSITIVE}; + m_controllerConfig.m_pendingAxisMapping->nativeButton = nativeButton; + m_controllerConfig.m_pendingAxisMapping = nullptr; + m_controllerConfig.m_pendingPort = -1; + PADBlockInput(false); + } + } + } + ImGuiWindowFlags windowFlags = ImGuiWindowFlags_NoResize | ImGuiWindowFlags_AlwaysAutoResize; @@ -155,10 +176,12 @@ namespace dusk { ImGui::EndTabBar(); // if tab is changed while waiting for input, cancel pending - if (m_controllerConfig.m_pendingMapping != nullptr && + if ((m_controllerConfig.m_pendingButtonMapping != nullptr || + m_controllerConfig.m_pendingAxisMapping != nullptr) && m_controllerConfig.m_pendingPort != m_controllerConfig.m_selectedPort) { - m_controllerConfig.m_pendingMapping = nullptr; + m_controllerConfig.m_pendingButtonMapping = nullptr; + m_controllerConfig.m_pendingAxisMapping = nullptr; m_controllerConfig.m_pendingPort = -1; PADBlockInput(false); } @@ -216,34 +239,78 @@ namespace dusk { } // buttons panel - constexpr float buttonSize = 40; + constexpr float uiButtonSize = 40; ImGuiBeginGroupPanel("Buttons", ImVec2(150, 20)); u32 buttonCount; - PADButtonMapping* mappingList = PADGetButtonMappings(m_controllerConfig.m_selectedPort, &buttonCount); - if (mappingList != nullptr) { + PADButtonMapping* btnMappingList = PADGetButtonMappings(m_controllerConfig.m_selectedPort, &buttonCount); + if (btnMappingList != nullptr) { for (int i = 0; i < buttonCount; i++) { - const char* btnName = PADGetButtonName(mappingList[i].padButton); + const char* btnName = PADGetButtonName(btnMappingList[i].padButton); ImVec2 len = ImGui::CalcTextSize(btnName); ImVec2 pos = ImGui::GetCursorPos(); ImGui::SetCursorPosY(pos.y + len.y / 4); - ImGui::SetCursorPosX(pos.x + abs(len.x - buttonSize)); + ImGui::SetCursorPosX(pos.x + abs(len.x - uiButtonSize)); ImGui::Text("%s", btnName); ImGui::SameLine(); ImGui::SetCursorPosY(pos.y); - bool pressed = ImGui::Button(m_controllerConfig.m_isReading && m_controllerConfig.m_pendingMapping == &mappingList[i] - ? fmt::format("Press a Key...##{}", btnName).c_str() - : fmt::format("{0}##-{1}", PADGetNativeButtonName(mappingList[i].nativeButton), i).c_str(), + std::string dispName; + if (m_controllerConfig.m_isReading && m_controllerConfig.m_pendingButtonMapping == &btnMappingList[i]) { + dispName = fmt::format("Press a Key...##{}", btnName); + } else { + dispName = fmt::format("{0}##-{1}", PADGetNativeButtonName(btnMappingList[i].nativeButton), i); + } + bool pressed = ImGui::Button(dispName.c_str(), ImVec2(100.0f, 20.0f)); if (pressed) { m_controllerConfig.m_isReading = true; m_controllerConfig.m_pendingPort = m_controllerConfig.m_selectedPort; - m_controllerConfig.m_pendingMapping = &mappingList[i]; + m_controllerConfig.m_pendingButtonMapping = &btnMappingList[i]; + PADBlockInput(true); + } + } + } + + ImGuiEndGroupPanel(); + ImGui::SameLine(); + + uint32_t axisCount; + PADAxisMapping* axisMappingList = PADGetAxisMappings(m_controllerConfig.m_selectedPort, &axisCount); + + ImGuiBeginGroupPanel("Triggers", ImVec2(150, 20)); + + PADAxis triggers[] = {PAD_AXIS_TRIGGER_L, PAD_AXIS_TRIGGER_R}; + if (axisMappingList != nullptr) { + for (PADAxis trigger : triggers) { + const char* axisName = PADGetAxisName(axisMappingList[trigger].padAxis); + ImVec2 len = ImGui::CalcTextSize(axisName); + ImVec2 pos = ImGui::GetCursorPos(); + + ImGui::SetCursorPosY(pos.y + len.y / 4); + ImGui::SetCursorPosX(pos.x + abs(len.x - uiButtonSize)); + ImGui::Text("%s", axisName); + ImGui::SameLine(); + + ImGui::SetCursorPosY(pos.y); + + std::string dispName; + if (m_controllerConfig.m_isReading && m_controllerConfig.m_pendingAxisMapping == &axisMappingList[trigger]) { + dispName = fmt::format("Press a Key...##{}", axisName); + } else { + dispName = fmt::format("{0}##-{1}", PADGetNativeAxisName(axisMappingList[trigger].nativeAxis), trigger); + } + bool pressed = ImGui::Button(dispName.c_str(), + ImVec2(100.0f, 20.0f)); + + if (pressed) { + m_controllerConfig.m_isReading = true; + m_controllerConfig.m_pendingPort = m_controllerConfig.m_selectedPort; + m_controllerConfig.m_pendingAxisMapping = &axisMappingList[trigger]; PADBlockInput(true); } } @@ -254,32 +321,52 @@ namespace dusk { int port = m_controllerConfig.m_selectedPort; - const char* stickDirections[] = { - "Up", - "Down", - "Left", - "Right", - }; - // main stick panel ImGuiBeginGroupPanel("Control Stick", ImVec2(150, 20)); drawVirtualStick("##mainStick", ImVec2{ mDoCPd_c::getStickX(port), mDoCPd_c::getStickY(port) }); - { - for (int i = 0; i < 4; i++) { - const char* label = stickDirections[i]; + if (axisMappingList != nullptr) { + const PADAxis lStickAxes[] = {PAD_AXIS_LEFT_Y_POS, PAD_AXIS_LEFT_Y_NEG, PAD_AXIS_LEFT_X_NEG, PAD_AXIS_LEFT_X_POS}; + for (auto axis : lStickAxes) { + const char* label = PADGetAxisDirectionLabel(axis); ImVec2 len = ImGui::CalcTextSize(label); ImVec2 pos = ImGui::GetCursorPos(); ImGui::SetCursorPosY(pos.y + len.y / 4); - ImGui::SetCursorPosX(pos.x + abs(len.x - buttonSize)); + ImGui::SetCursorPosX(pos.x + abs(len.x - uiButtonSize)); ImGui::Text("%s", label); ImGui::SameLine(); ImGui::SetCursorPosY(pos.y); - bool pressed = ImGui::Button(fmt::format("Temp##{}", label).c_str(), ImVec2(100.0f, 20.0f)); + std::string dispName; + if (m_controllerConfig.m_isReading && m_controllerConfig.m_pendingAxisMapping == &axisMappingList[axis]) { + dispName = fmt::format("Press a Key...##{}", label); + } else { + if (axisMappingList[axis].nativeAxis.nativeAxis != -1) { + const char* signStr; + if (axis == PAD_AXIS_TRIGGER_L || axis == PAD_AXIS_TRIGGER_R) { + signStr = ""; + } else if (axisMappingList[axis].nativeAxis.sign == AXIS_SIGN_POSITIVE) { + signStr = "+"; + } else { + signStr = "-"; + } + dispName = fmt::format("{0}{1}##-{2}", PADGetNativeAxisName(axisMappingList[axis].nativeAxis), signStr, axis); + } else { + assert(axisMappingList[axis].nativeButton != -1); + dispName = fmt::format("{0}##-{1}", PADGetNativeButtonName(axisMappingList[axis].nativeButton), axis); + } + } + bool pressed = ImGui::Button(dispName.c_str(), ImVec2(100.0f, 20.0f)); + + if (pressed) { + m_controllerConfig.m_isReading = true; + m_controllerConfig.m_pendingPort = m_controllerConfig.m_selectedPort; + m_controllerConfig.m_pendingAxisMapping = &axisMappingList[axis]; + PADBlockInput(true); + } } } @@ -303,20 +390,47 @@ namespace dusk { drawVirtualStick("##subStick", ImVec2{ mDoCPd_c::getSubStickX(port), mDoCPd_c::getSubStickY(port) }); - { - for (int i = 0; i < 4; i++) { - const char* label = stickDirections[i]; + if (axisMappingList != nullptr) { + const PADAxis rStickAxes[] = {PAD_AXIS_RIGHT_Y_POS, PAD_AXIS_RIGHT_Y_NEG, PAD_AXIS_RIGHT_X_NEG, PAD_AXIS_RIGHT_X_POS}; + for (auto axis : rStickAxes) { + const char* label = PADGetAxisDirectionLabel(axisMappingList[axis].padAxis); ImVec2 len = ImGui::CalcTextSize(label); ImVec2 pos = ImGui::GetCursorPos(); ImGui::SetCursorPosY(pos.y + len.y / 4); - ImGui::SetCursorPosX(pos.x + abs(len.x - buttonSize)); + ImGui::SetCursorPosX(pos.x + abs(len.x - uiButtonSize)); ImGui::Text("%s", label); ImGui::SameLine(); ImGui::SetCursorPosY(pos.y); - bool pressed = ImGui::Button(fmt::format("Temp##sub{}", label).c_str(), ImVec2(100.0f, 20.0f)); + std::string dispName; + if (m_controllerConfig.m_isReading && m_controllerConfig.m_pendingAxisMapping == &axisMappingList[axis]) { + dispName = fmt::format("Press a Key...##sub{}", label); + } else { + if (axisMappingList[axis].nativeAxis.nativeAxis != -1) { + const char* signStr; + if (axis == PAD_AXIS_TRIGGER_L || axis == PAD_AXIS_TRIGGER_R) { + signStr = ""; + } else if (axisMappingList[axis].nativeAxis.sign == AXIS_SIGN_POSITIVE) { + signStr = "+"; + } else { + signStr = "-"; + } + dispName = fmt::format("{0}{1}##-{2}", PADGetNativeAxisName(axisMappingList[axis].nativeAxis), signStr, axis); + } else { + assert(axisMappingList[axis].nativeButton != -1); + dispName = fmt::format("{0}##-{1}", PADGetNativeButtonName(axisMappingList[axis].nativeButton), axis); + } + } + bool pressed = ImGui::Button(fmt::format("{0}##sub{1}", dispName, label).c_str(), ImVec2(100.0f, 20.0f)); + + if (pressed) { + m_controllerConfig.m_isReading = true; + m_controllerConfig.m_pendingPort = m_controllerConfig.m_selectedPort; + m_controllerConfig.m_pendingAxisMapping = &axisMappingList[axis]; + PADBlockInput(true); + } } } diff --git a/src/dusk/imgui/ImGuiMenuGame.hpp b/src/dusk/imgui/ImGuiMenuGame.hpp index 1582ffab89..ad6a7140ec 100644 --- a/src/dusk/imgui/ImGuiMenuGame.hpp +++ b/src/dusk/imgui/ImGuiMenuGame.hpp @@ -30,7 +30,8 @@ namespace dusk { struct { int m_selectedPort = 0; bool m_isReading = false; - PADButtonMapping* m_pendingMapping = nullptr; + PADButtonMapping* m_pendingButtonMapping = nullptr; + PADAxisMapping* m_pendingAxisMapping = nullptr; int m_pendingPort = -1; } m_controllerConfig; From 1146c30ec053886f2197d7b143f6a87d31ea7490 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Wed, 1 Apr 2026 00:59:17 +0200 Subject: [PATCH 159/350] More GX debug groups --- include/dusk/gx_helper.h | 9 +++++++++ src/d/d_drawlist.cpp | 6 ++++++ src/m_Do/m_Do_graphic.cpp | 20 ++++++++++---------- 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/include/dusk/gx_helper.h b/include/dusk/gx_helper.h index adb547ac29..bf81424c99 100644 --- a/include/dusk/gx_helper.h +++ b/include/dusk/gx_helper.h @@ -42,4 +42,13 @@ typedef GXTexObjRAII TGXTexObj; typedef GXTexObj TGXTexObj; #endif +struct GXScopedDebugGroup { + explicit GXScopedDebugGroup(const char* text) { + GXPushDebugGroup(text); + } + ~GXScopedDebugGroup() { + GXPopDebugGroup(); + } +}; + #endif // DUSK_GX_HELPER_H diff --git a/src/d/d_drawlist.cpp b/src/d/d_drawlist.cpp index 8b34e99c2d..e6e8f54f37 100644 --- a/src/d/d_drawlist.cpp +++ b/src/d/d_drawlist.cpp @@ -1865,6 +1865,12 @@ int dDlst_list_c::set(dDlst_base_c**& p_start, dDlst_base_c**& p_end, dDlst_base void dDlst_list_c::draw(dDlst_base_c** p_start, dDlst_base_c** p_end) { for (; p_start < p_end; p_start++) { dDlst_base_c* dlst = *p_start; + +#if DEBUG && TARGET_PC + char buf[64]; + snprintf(buf, sizeof(buf), "%s::draw()", typeid(dlst).name()); + GXScopedDebugGroup scope(buf); +#endif dlst->draw(); } } diff --git a/src/m_Do/m_Do_graphic.cpp b/src/m_Do/m_Do_graphic.cpp index 94ba2fe6ce..c2d6dcc23c 100644 --- a/src/m_Do/m_Do_graphic.cpp +++ b/src/m_Do/m_Do_graphic.cpp @@ -1653,7 +1653,7 @@ int mDoGph_Painter() { #endif dComIfGp_setCurrentGrafPort(&ortho); - dComIfGd_drawCopy2D(); + GX_DEBUG_GROUP(dComIfGd_drawCopy2D); #if DEBUG // "↓↓↓↓↓↓↓↓↓↓ CPU time measuring start ↓↓↓↓↓↓↓↓↓↓" @@ -2166,25 +2166,25 @@ int mDoGph_Painter() { JPADrawInfo draw_info3(m5, 0.0f, FB_HEIGHT, 0.0f, FB_WIDTH); if (!dComIfGp_isPauseFlag()) { - dComIfGp_particle_draw2Dback(&draw_info3); + GX_DEBUG_GROUP(dComIfGp_particle_draw2Dback, &draw_info3); } - dComIfGp_particle_draw2DmenuBack(&draw_info3); + GX_DEBUG_GROUP(dComIfGp_particle_draw2DmenuBack, &draw_info3); ortho.setPort(); - dComIfGd_draw2DOpa(); - drawItem3D(); + GX_DEBUG_GROUP(dComIfGd_draw2DOpa); + GX_DEBUG_GROUP(drawItem3D); ortho.setPort(); #if DEBUG captureScreenSetPort(); #endif - dComIfGd_draw2DOpaTop(); - dComIfGd_draw2DXlu(); + GX_DEBUG_GROUP(dComIfGd_draw2DOpaTop); + GX_DEBUG_GROUP(dComIfGd_draw2DXlu); - if (!dComIfGp_isPauseFlag()) { - dComIfGp_particle_draw2Dfore(&draw_info3); + if (dComIfGp_isPauseFlag()) { + GX_DEBUG_GROUP(dComIfGp_particle_draw2Dfore, &draw_info3); } #if DEBUG @@ -2197,7 +2197,7 @@ int mDoGph_Painter() { mDoGph_gInf_c::calcFade(); } - dComIfGp_particle_draw2DmenuFore(&draw_info3); + GX_DEBUG_GROUP(dComIfGp_particle_draw2DmenuFore, &draw_info3); j3dSys.setViewMtx(m4); } else { // No camera window active — still draw 2D display lists From d15936cd6100c1c2ce64422a9b0adf379ec9fe7a Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Wed, 1 Apr 2026 01:09:51 +0200 Subject: [PATCH 160/350] Couple names in dMeterMap_c --- include/d/d_meter_map.h | 10 +++---- src/d/d_meter_map.cpp | 66 ++++++++++++++++++++--------------------- 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/include/d/d_meter_map.h b/include/d/d_meter_map.h index ecf645a6ac..b17f51846e 100644 --- a/include/d/d_meter_map.h +++ b/include/d/d_meter_map.h @@ -89,7 +89,7 @@ public: virtual void draw(); virtual ~dMeterMap_c(); - bool isDispPosInsideFlg() { return field_0x2d != 0; } + bool isDispPosInsideFlg() { return mMapIsInside != 0; } dMeterMap_c* getMapPointer() { return (dMeterMap_c*)mMap; } void setSizeW(f32 w) { mSizeW = w; } void setSizeH(f32 h) { mSizeH = h; } @@ -108,15 +108,15 @@ private: /* 0x0C */ s32 mIsCompass; /* 0x10 */ s32 mIsMap; /* 0x14 */ u32 field_0x14; - /* 0x18 */ f32 field_0x18; - /* 0x1C */ f32 field_0x1c; + /* 0x18 */ f32 mDrawPosX; + /* 0x1C */ f32 mDrawPosY; /* 0x20 */ f32 mSizeW; /* 0x24 */ f32 mSizeH; - /* 0x28 */ s16 field_0x28; + /* 0x28 */ s16 mSlidePositionOffset; /* 0x2A */ u8 field_0x2a; /* 0x2B */ u8 field_0x2b; /* 0x2C */ u8 mMapAlpha; - /* 0x2D */ u8 field_0x2d; + /* 0x2D */ u8 mMapIsInside; /* 0x2E */ u8 field_0x2e; /* 0x30 */ int field_0x30; }; diff --git a/src/d/d_meter_map.cpp b/src/d/d_meter_map.cpp index 1e0f7e397d..8f31ef3e21 100644 --- a/src/d/d_meter_map.cpp +++ b/src/d/d_meter_map.cpp @@ -302,12 +302,12 @@ bool dMeterMap_c::isEventRunCheck() { f32 dMeterMap_c::getMapDispEdgeLeftX_Layout() { #if (PLATFORM_WII || PLATFORM_SHIELD) if (mDoGph_gInf_c::isWide()) { - return g_meter_mapHIO.mWideBottomLeftX + field_0x28; + return g_meter_mapHIO.mWideBottomLeftX + mSlidePositionOffset; } - return g_meter_mapHIO.mNormalBottomLeftX + field_0x28; + return g_meter_mapHIO.mNormalBottomLeftX + mSlidePositionOffset; #else - return field_0x28 + 35; + return mSlidePositionOffset + 35; #endif } @@ -349,14 +349,14 @@ s16 dMeterMap_c::getDispPosOutSide_OffsetX() { void dMeterMap_c::setDispPosInsideFlg_SE_On() { if (isEnableDispMapAndMapDispSizeTypeNo()) { dComIfGp_mapShow(); - field_0x2d = 1; + mMapIsInside = 1; field_0x2e = 7; } } void dMeterMap_c::setDispPosOutsideFlg_SE_On() { dComIfGp_mapHide(); - field_0x2d = 0; + mMapIsInside = 0; field_0x2e = 7; } @@ -456,44 +456,44 @@ void dMeterMap_c::_create(J2DScreen* unused) { field_0x2a = 0; if (dComIfGp_checkMapShow()) { - field_0x2d = 1; + mMapIsInside = 1; if (!isEnableDispMapAndMapDispSizeTypeNo()) { - field_0x2d = 0; + mMapIsInside = 0; } if (!isMapOpenCheck()) { - field_0x2d = 0; + mMapIsInside = 0; } } else { - field_0x2d = 0; + mMapIsInside = 0; } - if (field_0x2d != 0) { - field_0x2d = 1; - field_0x28 = getDispPosInside_OffsetX(); + if (mMapIsInside != 0) { + mMapIsInside = 1; + mSlidePositionOffset = getDispPosInside_OffsetX(); dMeter2Info_setMapStatus(1); } else { - field_0x2d = 0; - field_0x28 = getDispPosOutSide_OffsetX(); + mMapIsInside = 0; + mSlidePositionOffset = getDispPosOutSide_OffsetX(); dMeter2Info_setMapStatus(0); } field_0x2e = 0; - field_0x28 = 0; + mSlidePositionOffset = 0; field_0x30 = 0; /* dSv_event_flag_c::M_085 - Twilight Hyrule Field - Midna dialogue right before Boss Bug's Tear of Light appears */ field_0x2b = dComIfGs_isEventBit(dSv_event_flag_c::saveBitLabels[118]); } void dMeterMap_c::setDispPosOutSide() { - field_0x2d = 0; - field_0x28 = getDispPosOutSide_OffsetX(); + mMapIsInside = 0; + mSlidePositionOffset = getDispPosOutSide_OffsetX(); } void dMeterMap_c::setDispPosInSide() { - field_0x2d = 1; - field_0x28 = getDispPosInside_OffsetX(); + mMapIsInside = 1; + mSlidePositionOffset = getDispPosInside_OffsetX(); } void dMeterMap_c::_delete() { @@ -506,7 +506,7 @@ void dMeterMap_c::_delete() { } if (isEnableDispMapAndMapDispSizeTypeNo()) { - if (field_0x2d != 0) { + if (mMapIsInside != 0) { dComIfGp_mapShow(); } else { dComIfGp_mapHide(); @@ -548,16 +548,16 @@ void dMeterMap_c::_move(u32 param_0) { ctrlShowMap(); } - if (field_0x2d != 0) { - if (field_0x28 != getDispPosInside_OffsetX()) { - if (!cLib_addCalcAngleS(&field_0x28, getDispPosInside_OffsetX(), 2, 60, 10)) { + if (mMapIsInside != 0) { + if (mSlidePositionOffset != getDispPosInside_OffsetX()) { + if (!cLib_addCalcAngleS(&mSlidePositionOffset, getDispPosInside_OffsetX(), 2, 60, 10)) { #if DEBUG cLib_checkBit((int)field_0x2e, 4); #endif } } } else { - cLib_addCalcAngleS(&field_0x28, getDispPosOutSide_OffsetX(), 2, 60, 10); + cLib_addCalcAngleS(&mSlidePositionOffset, getDispPosOutSide_OffsetX(), 2, 60, 10); } Vec map_pos = dMapInfo_n::getMapPlayerPos(); @@ -579,8 +579,8 @@ void dMeterMap_c::_move(u32 param_0) { mSizeH = (s16)sizeH; #endif - field_0x18 = field_0x28 + getMapDispEdgeLeftX_Layout(); - field_0x1c = getMapDispEdgeBottomY_Layout() - mSizeH; + mDrawPosX = mSlidePositionOffset + getMapDispEdgeLeftX_Layout(); + mDrawPosY = getMapDispEdgeBottomY_Layout() - mSizeH; mMap->_move(map_pos.x, map_pos.z, stayNo, map_pos.y); field_0x30 = dComIfGp_event_runCheck(); @@ -609,8 +609,8 @@ void dMeterMap_c::draw() { graf->setup2D(); f32 sizeX = mSizeW; f32 sizeY = mSizeH; - f32 tmp2 = field_0x18; - f32 tmp3 = field_0x1c; + f32 drawPosX = mDrawPosX; + f32 drawPosY = mDrawPosY; u8 alpha = mMapAlpha; #if DEBUG @@ -620,7 +620,7 @@ void dMeterMap_c::draw() { #endif mMapJ2DPicture->setAlpha(alpha); - mMapJ2DPicture->draw(tmp2, tmp3, sizeX, sizeY, false, false, false); + mMapJ2DPicture->draw(drawPosX, drawPosY, sizeX, sizeY, false, false, false); mMapJ2DPicture->calcMtx(); } } @@ -640,7 +640,7 @@ void dMeterMap_c::ctrlShowMap() { dMeter2Info_getPauseStatus() == 2 || dMeter2Info_getPauseStatus() == 6) { #if !DEBUG - if (dMeter2Info_getMapStatus() == 0 && field_0x2d == 0) { + if (dMeter2Info_getMapStatus() == 0 && mMapIsInside == 0) { setDispPosInsideFlg_SE_On(); Z2GetAudioMgr()->seStart(Z2SE_SY_MAP_OPEN_S, NULL, 0, 0, 1.0f, 1.0f, -1.0f, -1.0f, 0); @@ -653,7 +653,7 @@ void dMeterMap_c::ctrlShowMap() { #if DEBUG dMeter2Info_getMapStatus() == 0 && #else - dMeter2Info_getMapStatus() == 1 && field_0x2d != 0 && + dMeter2Info_getMapStatus() == 1 && mMapIsInside != 0 && #endif isFmapScreen() ) { @@ -690,7 +690,7 @@ void dMeterMap_c::ctrlShowMap() { dMeter2Info_resetPauseStatus(); if (isDmapScreen()) { #if !DEBUG - if (dMeter2Info_getMapStatus() == 0 && field_0x2d == 0) { + if (dMeter2Info_getMapStatus() == 0 && mMapIsInside == 0) { setDispPosInsideFlg_SE_On(); Z2GetAudioMgr()->seStart(Z2SE_SY_MAP_OPEN_S, NULL, 0, 0, 1.0f, 1.0f, -1.0f, -1.0f, 0); @@ -703,7 +703,7 @@ void dMeterMap_c::ctrlShowMap() { #if DEBUG dMeter2Info_getMapStatus() == 0 #else - dMeter2Info_getMapStatus() == 1 && field_0x2d != 0 + dMeter2Info_getMapStatus() == 1 && mMapIsInside != 0 #endif ) { dMeter2Info_setMapStatus(6); From fb88f02b9f0c0bd83737770077cc50111e2f1a2e Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Wed, 1 Apr 2026 01:11:13 +0200 Subject: [PATCH 161/350] Don't render HUD map if invisible Very nice on debug builds since you can avoid huge FPS drops by hiding the map now. --- src/d/d_meter_map.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/d/d_meter_map.cpp b/src/d/d_meter_map.cpp index 8f31ef3e21..7e55d899fd 100644 --- a/src/d/d_meter_map.cpp +++ b/src/d/d_meter_map.cpp @@ -593,6 +593,14 @@ void dMeterMap_c::_draw() { dComIfGd_set2DOpa(this); } #else +#if TARGET_PC + // Optimization: don't draw map if it's off-screen/invisible. + // Especially useful in debug builds on Hyrule field etc., it's slow! + if ((!mMapIsInside && mSlidePositionOffset == getDispPosOutSide_OffsetX()) || mMapAlpha == 0) { + return; + } +#endif + mMap->_draw(); dComIfGd_set2DOpa(this); #endif From 427a27c803d379aafcb2c25568bc6c8abfba9b7a Mon Sep 17 00:00:00 2001 From: Max Roncace Date: Tue, 31 Mar 2026 21:27:24 -0400 Subject: [PATCH 162/350] Update Aurora for PAD fixes --- extern/aurora | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/aurora b/extern/aurora index decc4e4f75..db43a26884 160000 --- a/extern/aurora +++ b/extern/aurora @@ -1 +1 @@ -Subproject commit decc4e4f750d61cf1b9d1b82c6e8e94cd396b617 +Subproject commit db43a26884c3c6abbbdbee003b6c2575bf2e3a60 From 186e94e1204e5f0dc48f5356f04c6531705b9ad8 Mon Sep 17 00:00:00 2001 From: Irastris Date: Tue, 31 Mar 2026 23:31:45 -0400 Subject: [PATCH 163/350] Update Aurora for CARD refactor --- extern/aurora | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/aurora b/extern/aurora index db43a26884..9c3d4665ce 160000 --- a/extern/aurora +++ b/extern/aurora @@ -1 +1 @@ -Subproject commit db43a26884c3c6abbbdbee003b6c2575bf2e3a60 +Subproject commit 9c3d4665ceabd43241a67fbfae8e5a9eb9684353 From a3e3720cb1a710025a238ce4b1b436ef3b83fcc2 Mon Sep 17 00:00:00 2001 From: TakaRikka Date: Tue, 31 Mar 2026 22:51:26 -0700 Subject: [PATCH 164/350] coo crash fix --- src/d/d_stage.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/d/d_stage.cpp b/src/d/d_stage.cpp index 6c828b97e3..057bcdf4ff 100644 --- a/src/d/d_stage.cpp +++ b/src/d/d_stage.cpp @@ -392,6 +392,16 @@ static void dummy1(dStage_roomControl_c* roomControl) { } JKRExpHeap* dStage_roomControl_c::createMemoryBlock(int i_blockIdx, u32 i_heapSize) { + #if TARGET_PC + // Cave of Ordeals crashes around floor 29 due to no free heap space + // Increasing the size here avoids that, though its ugly. maybe TODO a better fix + if (strcmp(dComIfGp_getStartStageName(), "D_SB01") == 0) { + u32 prev = i_heapSize; + i_heapSize *= 2; + DuskLog.warn("Doubling heap size for D_SB01... ({}) -> ({})", prev, i_heapSize); + } + #endif + if (mMemoryBlock[i_blockIdx] == NULL) { mMemoryBlock[i_blockIdx] = JKRCreateExpHeap(i_heapSize, mDoExt_getArchiveHeap(), false); JKRHEAP_NAMEF(mMemoryBlock[i_blockIdx], "Room control memory block %d", i_blockIdx); From c027b6ef6ac7d0aefe332f12dfe0f7a365dafaa3 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Wed, 1 Apr 2026 00:53:08 -0600 Subject: [PATCH 165/350] Update aurora & increase map resolution --- extern/aurora | 2 +- src/d/d_drawlist.cpp | 3 +-- src/d/d_map.cpp | 17 ++++++++++++++--- src/d/d_menu_dmap_map.cpp | 23 ++++++++++++++++------- src/d/d_menu_fmap_map.cpp | 4 ++++ src/dusk/imgui/ImGuiMenuTools.cpp | 5 ++++- src/m_Do/m_Do_graphic.cpp | 10 +++++++--- 7 files changed, 47 insertions(+), 17 deletions(-) diff --git a/extern/aurora b/extern/aurora index 9c3d4665ce..6b9f614d32 160000 --- a/extern/aurora +++ b/extern/aurora @@ -1 +1 @@ -Subproject commit 9c3d4665ceabd43241a67fbfae8e5a9eb9684353 +Subproject commit 6b9f614d326aaa4a4dead870ffd22393d2a4ef53 diff --git a/src/d/d_drawlist.cpp b/src/d/d_drawlist.cpp index e6e8f54f37..ede33efd65 100644 --- a/src/d/d_drawlist.cpp +++ b/src/d/d_drawlist.cpp @@ -1403,8 +1403,7 @@ void dDlst_shadowControl_c::init() { u16 size = l_realImageSize[i]; #ifdef TARGET_PC - // Don't need to allocate an actual texture buffer for aurora; only a placeholder - u32 buffer_size = 0x20; + u32 buffer_size = 0x20; // No need to allocate memory for texture #else u32 buffer_size = GXGetTexBufferSize(size, size, 5, GX_DISABLE, 0); #endif diff --git a/src/d/d_map.cpp b/src/d/d_map.cpp index 3198b21f51..46b0bbf742 100644 --- a/src/d/d_map.cpp +++ b/src/d/d_map.cpp @@ -1097,7 +1097,7 @@ void dMap_c::resCopy() { } } -dMap_c::dMap_c(int param_0, int param_1, int param_2, int param_3) { +dMap_c::dMap_c(int width, int height, int param_2, int param_3) { m_res_src = NULL; m_res = NULL; mResTIMG = NULL; @@ -1169,8 +1169,8 @@ dMap_c::dMap_c(int param_0, int param_1, int param_2, int param_3) { resCopy(); - mTexSizeX = param_0; - mTexSizeY = param_1; + mTexSizeX = width; + mTexSizeY = height; if (dMap_HIO_prm_res_dst_s::m_res->field_0x1ae > 0) { field_0x74 = dMap_HIO_prm_res_dst_s::m_res->field_0x1b0 / 6; @@ -1180,12 +1180,23 @@ dMap_c::dMap_c(int param_0, int param_1, int param_2, int param_3) { mImage_p = JKR_NEW_ARRAY_ARGS(u8, buffer_size, 0x20); JUT_ASSERT(2638, mImage_p != NULL); + +#ifdef TARGET_PC + // Increase map render resolution + renderingDAmap_c::init(mImage_p, mTexSizeX * 4, mTexSizeY * 4, mTexSizeX, mTexSizeY); +#else renderingDAmap_c::init(mImage_p, mTexSizeX, mTexSizeY, mTexSizeX, mTexSizeY); +#endif mResTIMG = JKR_NEW_ARGS (0x20) ResTIMG; JUT_ASSERT(2647, mResTIMG != NULL); +#ifdef TARGET_PC + // Increase map render resolution + makeResTIMG(mResTIMG, mTexSizeX * 4, mTexSizeY * 4, mImage_p, (u8*)m_res, 0x33); +#else makeResTIMG(mResTIMG, mTexSizeX, mTexSizeY, mImage_p, (u8*)m_res, 0x33); +#endif } #if DEBUG diff --git a/src/d/d_menu_dmap_map.cpp b/src/d/d_menu_dmap_map.cpp index 0754bf7e35..23881464e8 100644 --- a/src/d/d_menu_dmap_map.cpp +++ b/src/d/d_menu_dmap_map.cpp @@ -300,16 +300,25 @@ void dMenu_DmapMap_c::_delete() { } } -void dMenu_DmapMap_c::setTexture(u16 param_0, u16 param_1, u16 param_2, u16 param_3) { +void dMenu_DmapMap_c::setTexture(u16 width, u16 height, u16 param_2, u16 param_3) { +#ifdef TARGET_PC + // Increase map render resolution + width *= 4; + height *= 4; +#endif for (int lp1 = 0; lp1 < 2; lp1++) { - u32 var_r27 = GXGetTexBufferSize(param_0, param_1, 9, 0, 0); - mMapImage_p[lp1] = JKR_NEW_ARRAY_ARGS(u8, var_r27, 0x20); +#ifdef TARGET_PC + u32 sz = 0x20; // No need to allocate memory for texture +#else + u32 sz = GXGetTexBufferSize(width, height, 9, 0, 0); +#endif + mMapImage_p[lp1] = JKR_NEW_ARRAY_ARGS(u8, sz, 0x20); JUT_ASSERT(1672, mMapImage_p[lp1] != NULL); - mRend[lp1].init(mMapImage_p[lp1], param_0, param_1, param_2, param_3); + mRend[lp1].init(mMapImage_p[lp1], width, height, param_2, param_3); mResTIMG[lp1] = JKR_NEW_ARGS (0x20) ResTIMG; JUT_ASSERT(1687, mResTIMG[lp1] != NULL); - mRend[lp1].makeResTIMG(mResTIMG[lp1], param_0, param_1, mMapImage_p[lp1], (u8*)dMdm_HIO_prm_res_dst_s::m_res, 30); + mRend[lp1].makeResTIMG(mResTIMG[lp1], width, height, mMapImage_p[lp1], (u8*)dMdm_HIO_prm_res_dst_s::m_res, 30); } } @@ -929,7 +938,7 @@ f32 dMenu_StageMapCtrl_c::m_zoomCenterMinZ; f32 dMenu_StageMapCtrl_c::m_zoomCenterMaxZ; -void dMenu_StageMapCtrl_c::_create(u16 param_0, u16 param_1, u16 param_2, u16 param_3, +void dMenu_StageMapCtrl_c::_create(u16 width, u16 height, u16 param_2, u16 param_3, s8 param_4, void* param_5) { field_0xe6 = dComIfGp_roomControl_getStayNo(); field_0xe7 = param_4; @@ -946,7 +955,7 @@ void dMenu_StageMapCtrl_c::_create(u16 param_0, u16 param_1, u16 param_2, u16 pa field_0x98 = param_3; f32 var_f26 = field_0x98 > field_0x94 ? field_0x98 : field_0x94; - dMenu_DmapMap_c::_create(param_0, param_1, param_2, param_3, param_5); + dMenu_DmapMap_c::_create(width, height, param_2, param_3, param_5); getInitDispCenter(&field_0x9c, &field_0xa0); field_0xa4 = field_0x9c; diff --git a/src/d/d_menu_fmap_map.cpp b/src/d/d_menu_fmap_map.cpp index fbbd9267e5..3d1a9e2523 100644 --- a/src/d/d_menu_fmap_map.cpp +++ b/src/d/d_menu_fmap_map.cpp @@ -659,7 +659,11 @@ const GXColor* dMenu_FmapMap_c::getColor(int param_0) { void dMenu_FmapMap_c::setTexture(u16 i_width, u16 i_height, u16 param_2, u16 param_3) { mMapImage_p = NULL; mResTIMG = NULL; +#ifdef TARGET_PC + int size = 0x20; // No need to allocate memory for texture +#else int size = GXGetTexBufferSize(i_width, i_height, GX_TF_C8, 0, 0); +#endif mMapImage_p = JKR_NEW_ARRAY_ARGS(u8, size, 0x20); init(mMapImage_p, i_width, i_height, param_2, param_3); mResTIMG = JKR_NEW_ARGS (0x20) ResTIMG; diff --git a/src/dusk/imgui/ImGuiMenuTools.cpp b/src/dusk/imgui/ImGuiMenuTools.cpp index 313d398498..40bb3fa1ec 100644 --- a/src/dusk/imgui/ImGuiMenuTools.cpp +++ b/src/dusk/imgui/ImGuiMenuTools.cpp @@ -124,10 +124,13 @@ namespace dusk { BytesToString(stats->lastIndexSize))); ImGuiStringViewText(fmt::format(FMT_STRING("Storage size: {}\n"), BytesToString(stats->lastStorageSize))); + ImGuiStringViewText(fmt::format(FMT_STRING("Tex upload size: {}\n"), + BytesToString(stats->lastTextureUploadSize))); ImGuiStringViewText(fmt::format( FMT_STRING("Total: {}\n"), BytesToString(stats->lastVertSize + stats->lastUniformSize + - stats->lastIndexSize + stats->lastStorageSize))); + stats->lastIndexSize + stats->lastStorageSize + + stats->lastTextureUploadSize))); } ImGui::End(); } diff --git a/src/m_Do/m_Do_graphic.cpp b/src/m_Do/m_Do_graphic.cpp index c2d6dcc23c..5c321f207f 100644 --- a/src/m_Do/m_Do_graphic.cpp +++ b/src/m_Do/m_Do_graphic.cpp @@ -319,11 +319,11 @@ void mDoGph_gInf_c::create() { m_fullFrameBufferTex = (char*)m_fullFrameBufferTimg + sizeof(ResTIMG); #endif - mFrameBufferTimg = createTimg(FB_WIDTH / 2, FB_HEIGHT / 2, 6); + mFrameBufferTimg = createTimg(FB_WIDTH / 2, FB_HEIGHT / 2, GX_TF_RGBA8); JUT_ASSERT(374, mFrameBufferTimg != NULL); mFrameBufferTex = (char*)mFrameBufferTimg + sizeof(ResTIMG); - mZbufferTimg = createTimg(FB_WIDTH / 2, FB_HEIGHT / 2, 3); + mZbufferTimg = createTimg(FB_WIDTH / 2, FB_HEIGHT / 2, GX_TF_IA8); JUT_ASSERT(381, mZbufferTimg != NULL); mZbufferTex = (char*)mZbufferTimg + sizeof(ResTIMG); @@ -1139,7 +1139,11 @@ void mDoGph_drawFilterQuad(s8 param_0, s8 param_1) { void mDoGph_gInf_c::bloom_c::create() { if (m_buffer == NULL) { - u32 size = GXGetTexBufferSize(FB_WIDTH / 2, FB_HEIGHT / 2, 6, GX_FALSE, 0); +#ifdef TARGET_PC + u32 size = 0x20; // No need to allocate memory for texture +#else + u32 size = GXGetTexBufferSize(FB_WIDTH / 2, FB_HEIGHT / 2, GX_TF_RGBA8, GX_FALSE, 0); +#endif m_buffer = mDoExt_getArchiveHeap()->alloc(size, -32); JUT_ASSERT(1621, m_buffer != NULL); From 07280eeb96d3597231799b656470c43b7d561adf Mon Sep 17 00:00:00 2001 From: TakaRikka Date: Wed, 1 Apr 2026 00:17:41 -0700 Subject: [PATCH 166/350] fix dmap --- src/d/d_menu_dmap_map.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/d/d_menu_dmap_map.cpp b/src/d/d_menu_dmap_map.cpp index 23881464e8..8b345fbcbd 100644 --- a/src/d/d_menu_dmap_map.cpp +++ b/src/d/d_menu_dmap_map.cpp @@ -303,8 +303,8 @@ void dMenu_DmapMap_c::_delete() { void dMenu_DmapMap_c::setTexture(u16 width, u16 height, u16 param_2, u16 param_3) { #ifdef TARGET_PC // Increase map render resolution - width *= 4; - height *= 4; + width *= 2; + height *= 2; #endif for (int lp1 = 0; lp1 < 2; lp1++) { #ifdef TARGET_PC From 603a71b2e13ffda125a557a64fccbb8e54f84611 Mon Sep 17 00:00:00 2001 From: TakaRikka Date: Wed, 1 Apr 2026 00:32:06 -0700 Subject: [PATCH 167/350] increase fmap res --- src/d/d_menu_fmap_map.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/d/d_menu_fmap_map.cpp b/src/d/d_menu_fmap_map.cpp index 3d1a9e2523..2c5b1cea8e 100644 --- a/src/d/d_menu_fmap_map.cpp +++ b/src/d/d_menu_fmap_map.cpp @@ -657,6 +657,12 @@ const GXColor* dMenu_FmapMap_c::getColor(int param_0) { } void dMenu_FmapMap_c::setTexture(u16 i_width, u16 i_height, u16 param_2, u16 param_3) { +#ifdef TARGET_PC + // Increase map render resolution + i_width *= 2; + i_height *= 2; +#endif + mMapImage_p = NULL; mResTIMG = NULL; #ifdef TARGET_PC From 4c3633c49575998b9b962f9beaa31e0c5ebc749c Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Wed, 1 Apr 2026 14:37:24 +0200 Subject: [PATCH 168/350] Implement OSSetCurrentThreadName on macOS --- src/dusk/OSThread.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/dusk/OSThread.cpp b/src/dusk/OSThread.cpp index 7daa2cb137..7774dc6a5c 100644 --- a/src/dusk/OSThread.cpp +++ b/src/dusk/OSThread.cpp @@ -22,6 +22,8 @@ #if _WIN32 #define WIN32_LEAN_AND_MEAN 1 #include +#elif __APPLE__ +#include #endif // ============================================================================ @@ -722,6 +724,8 @@ void OSSetCurrentThreadName(const char* name) { if (!SUCCEEDED(result)) { CRASH("OSSetThreadName: SetThreadDescription failed"); } +#elif __APPLE__ + pthread_setname_np(name); #endif } From c3516c368ad8695f594ac0a58598f36b1a4d6469 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Wed, 1 Apr 2026 15:16:04 +0200 Subject: [PATCH 169/350] Fix letter menu rendering Fixes https://github.com/TakaRikka/dusk/issues/181 --- src/d/d_menu_letter.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/d/d_menu_letter.cpp b/src/d/d_menu_letter.cpp index d0017558b3..f393fb0785 100644 --- a/src/d/d_menu_letter.cpp +++ b/src/d/d_menu_letter.cpp @@ -223,8 +223,13 @@ void dMenu_Letter_c::_draw() { f32 y1 = local_178.y; Vec local_184; local_184 = afStack_138.getGlobalVtx(field_0x1ec, &mtx, 3, false, 0); +#if TARGET_PC + f32 dVar17 = mDoGph_gInf_c::getWidthF() / mDoGph_gInf_c::getWidth(); + f32 dVar16 = mDoGph_gInf_c::getHeightF() / mDoGph_gInf_c::getHeight(); +#else f32 dVar17 = mDoGph_gInf_c::getWidthF() / FB_WIDTH; f32 dVar16 = mDoGph_gInf_c::getHeightF() / FB_HEIGHT; +#endif f32 fVar1 = (x1 - mDoGph_gInf_c::getMinXF()) / dVar17; f32 fVar2 = y1 / dVar16; grafContext->scissor(fVar1, fVar2, From 73ace7716335d5ab282376a70d0e57a1f2367aaa Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Wed, 1 Apr 2026 18:11:49 +0200 Subject: [PATCH 170/350] Implement Quick Transform from Rando --- files.cmake | 1 + include/d/actor/d_a_alink.h | 5 ++ include/d/d_meter2_draw.h | 6 ++ src/d/actor/d_a_alink_quicktransform.cpp | 74 +++++++++++++++++++++++ src/dusk/imgui/ImGuiMenuGame.cpp | 1 + src/f_ap/f_ap_game.cpp | 77 ++++++++++++++++++++++++ 6 files changed, 164 insertions(+) create mode 100644 src/d/actor/d_a_alink_quicktransform.cpp diff --git a/files.cmake b/files.cmake index 8504aecb95..aeb068b583 100644 --- a/files.cmake +++ b/files.cmake @@ -1332,6 +1332,7 @@ set(DOLPHIN_FILES set(DUSK_FILES include/dusk/endian_gx.hpp + src/d/actor/d_a_alink_quicktransform.cpp src/dusk/asserts.cpp src/dusk/logging.cpp src/dusk/stubs.cpp diff --git a/include/d/actor/d_a_alink.h b/include/d/actor/d_a_alink.h index a4d38efc43..93668c550a 100644 --- a/include/d/actor/d_a_alink.h +++ b/include/d/actor/d_a_alink.h @@ -4547,6 +4547,10 @@ public: /* 0x03848 */ cXyz* field_0x3848; /* 0x0384C */ cXyz* field_0x384c; /* 0x03850 */ daAlink_procFunc mpProcFunc; + +#if TARGET_PC + void handleQuickTransform(); +#endif }; // Size: 0x385C class daAlinkHIO_data_c : public JORReflexible { @@ -8428,6 +8432,7 @@ inline daAlink_c* daAlink_getAlinkActorClass() { #if TARGET_PC namespace dusk::tweaks { extern bool FastIronBoots; + extern bool QuickTransform; } #endif diff --git a/include/d/d_meter2_draw.h b/include/d/d_meter2_draw.h index c11bd680bb..10cded803d 100644 --- a/include/d/d_meter2_draw.h +++ b/include/d/d_meter2_draw.h @@ -148,6 +148,12 @@ public: void setEmphasisB(u8 param_0) { field_0x762 = param_0; } u8 getInsideObjCheck() { return field_0x772; } +#if TARGET_PC + constexpr f32 getButtonZAlpha() const { + return mButtonZAlpha; + } +#endif + private: /* 0x004 */ item_params mItemParams[4]; /* 0x074 */ JKRExpHeap* heap; diff --git a/src/d/actor/d_a_alink_quicktransform.cpp b/src/d/actor/d_a_alink_quicktransform.cpp new file mode 100644 index 0000000000..f193651bae --- /dev/null +++ b/src/d/actor/d_a_alink_quicktransform.cpp @@ -0,0 +1,74 @@ +#include "d/actor/d_a_alink.h" +#include "d/actor/d_a_midna.h" +#include "d/d_meter2.h" +#include "d/d_meter2_draw.h" +#include "d/d_meter2_info.h" + +bool dusk::tweaks::QuickTransform = false; + +void daAlink_c::handleQuickTransform() { + if (!dusk::tweaks::QuickTransform) { + return; + } + + // Ensure that link is not in a cutscene. + if (checkEventRun()) { + return; + } + + // Check to see if Link has the ability to transform. + if (!dComIfGs_isEventBit(dSv_event_flag_c::M_077)) { + return; + } + + // Make sure Link isn't riding anything (horse, boar, etc.) + if (checkRide()) { + return; + } + + switch (mProcID) { + // Make sure Link is not underwater or talking to someone. + case PROC_TALK: + case PROC_SWIM_UP: + case PROC_SWIM_DIVE: + return; + // If Link is targeting or pulling a chain, we don't want to remove the ability to use items in combat accidently. + case PROC_ATN_ACTOR_MOVE: + case PROC_ATN_ACTOR_WAIT: + case PROC_WOLF_ATN_AC_MOVE: + break; + default: + // Disable the input that was just pressed, as sometimes it could cause items to be used or Wolf Link to dig. + mDoCPd_c::getCpadInfo(PAD_1).mPressedButtonFlags = 0; + break; + } + + // Ensure there is a proper pointer to the mMeterClass and mpMeterDraw structs in g_meter2_info. + const auto meterClassPtr = g_meter2_info.getMeterClass(); + if (!meterClassPtr) { + return; + } + + const auto meterDrawPtr = meterClassPtr->getMeterDrawPtr(); + if (!meterDrawPtr) { + return; + } + + // Ensure that the Z Button is not dimmed + if (meterDrawPtr->getButtonZAlpha() != 1.f) { + return; + } + + // The game will crash if trying to quick transform while holding the Ball and Chain + if (mEquipItem == dItemNo_IRONBALL_e) { + return; + } + + // Use the game's default checks for if the player can currently transform + if (!m_midnaActor->checkMetamorphoseEnableBase()) { + return; + } + + OSReport("Running quick transform!"); + procCoMetamorphoseInit(); +} \ No newline at end of file diff --git a/src/dusk/imgui/ImGuiMenuGame.cpp b/src/dusk/imgui/ImGuiMenuGame.cpp index 7b90dcb5b5..835fed0f7c 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("Quick Transform (R+Y)", nullptr, &tweaks::QuickTransform); ImGui::EndMenu(); } diff --git a/src/f_ap/f_ap_game.cpp b/src/f_ap/f_ap_game.cpp index 520d85d20f..e97e97148c 100644 --- a/src/f_ap/f_ap_game.cpp +++ b/src/f_ap/f_ap_game.cpp @@ -721,6 +721,67 @@ void fapGm_After() { fopCamM_Management(); } +#if TARGET_PC +// Analog L is currently not being used, so commented out +// static f32 prevFrameAnalogL = 0.f; +static f32 prevFrameAnalogR = 0.f; + +static bool DuskCheckButtonCombo(u32 combo, bool checkAnalog) { + const auto& padInfo = mDoCPd_c::getCpadInfo(PAD_1); + + // Get the buttons that are currently held and were pressed this frame + uint32_t buttonsHeld = padInfo.mButtonFlags; + uint32_t buttonsPressedThisFrame = padInfo.mPressedButtonFlags; + + if (checkAnalog) { + // Get the threshold for the analog buttons + constexpr float analogThreshold = 0.7f; // 70% + + // Check if L is included in the button combo + // Analog L is currently not being used, so commented out + /* + if ( combo & PadInputs::Button_L ) + { + // Check if analog L is at 70% or more + if ( padInfo->mTriggerLeft >= analogThreshold ) + { + // Manually set the bit for L being pressed + buttonsHeld |= PadInputs::Button_L; + + // If prevFrameAnalogL is less than 70%, then 70% was reached this frame + if ( prevFrameAnalogL < analogThreshold ) + { + buttonsPressedThisFrame |= PadInputs::Button_L; + } + } + } + */ + + // Check if R is included in the button combo + if (combo & PAD_TRIGGER_R) { + // Check if analog R is at 70% or more + if (padInfo.mTriggerRight >= analogThreshold) { + // Manually set the bit for R being pressed + buttonsHeld |= PAD_TRIGGER_R; + + // If prevFrameAnalogR is less than 70%, then 70% was reached this frame + if (prevFrameAnalogR < analogThreshold) { + buttonsPressedThisFrame |= PAD_TRIGGER_R; + } + } + } + } + + // Check if the button combo is held + if ((buttonsHeld & combo) != combo) { + return false; + } + + // Check if at least one button in the combo was pressed this frame + return buttonsPressedThisFrame & combo; +} +#endif + void fapGm_Execute() { static u32 sExecCount = 0; if (sExecCount < 10 || (sExecCount % 300 == 0)) { @@ -732,8 +793,24 @@ void fapGm_Execute() { JUTDbPrint::getManager()->setCharColor(g_HIO.mColor); #endif +#if TARGET_PC + const auto& padInfo = mDoCPd_c::getCpadInfo(PAD_1); + if (DuskCheckButtonCombo(PAD_TRIGGER_R | PAD_BUTTON_Y, true)) { + if (const auto link = g_dComIfG_gameInfo.play.getPlayer(0)) { + dynamic_cast(link)->handleQuickTransform(); + } + } +#endif + fpcM_Management(NULL, fapGm_After); cCt_Counter(0); + +#if TARGET_PC + // Main code has ran, so update previous frame variables + // Analog L is currently not being used, so commented out + // prevFrameAnalogL = padInfo.mTriggerLeft; + prevFrameAnalogR = padInfo.mTriggerRight; +#endif } fapGm_HIO_c g_HIO; From 1096e5c8692547fb0f3518fca19d8c04a567dbe4 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Wed, 1 Apr 2026 23:43:26 +0200 Subject: [PATCH 171/350] 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 a83c53cf37f253f7878b3ca62f4b11dba56081a4 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Wed, 1 Apr 2026 23:48:45 +0200 Subject: [PATCH 172/350] 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 15b63c84eeb900813f4600218a5d4963b2ddbd69 Mon Sep 17 00:00:00 2001 From: Irastris Date: Tue, 31 Mar 2026 04:33:08 -0400 Subject: [PATCH 173/350] Prevent some array lookups in c_cc_d and c_math from going out of bounds --- src/SSystem/SComponent/c_cc_d.cpp | 15 +++++++++++++++ src/SSystem/SComponent/c_math.cpp | 3 +++ 2 files changed, 18 insertions(+) diff --git a/src/SSystem/SComponent/c_cc_d.cpp b/src/SSystem/SComponent/c_cc_d.cpp index 569d64e5e7..d77e155adc 100644 --- a/src/SSystem/SComponent/c_cc_d.cpp +++ b/src/SSystem/SComponent/c_cc_d.cpp @@ -65,9 +65,14 @@ void cCcD_DivideArea::CalcDivideInfo(cCcD_DivideInfo* pDivideInfo, const cM3dGAa if (!mXDiffIsZero) { s32 var1 = mInvScaledXDiff * (aab.GetMinP()->x - GetMinP()->x); s32 var3 = mInvScaledXDiff * (aab.GetMaxP()->x - GetMinP()->x); +#if TARGET_PC + var1 = var1 < 0 ? 0 : var1 > 31 ? 31 : var1; + var3 = var3 < 0 ? 0 : var3 > 31 ? 31 : var3; +#else if (31 < var3) { var3 = 31; } +#endif xDivInfo = l_base[var3]; if (0 < var1) { @@ -81,9 +86,14 @@ void cCcD_DivideArea::CalcDivideInfo(cCcD_DivideInfo* pDivideInfo, const cM3dGAa if (!mYDiffIsZero) { s32 var1 = mInvScaledYDiff * (aab.GetMinP()->y - GetMinP()->y); s32 var3 = mInvScaledYDiff * (aab.GetMaxP()->y - GetMinP()->y); +#if TARGET_PC + var1 = var1 < 0 ? 0 : var1 > 31 ? 31 : var1; + var3 = var3 < 0 ? 0 : var3 > 31 ? 31 : var3; +#else if (31 < var3) { var3 = 31; } +#endif yDivInfo = l_base[var3]; if (0 < var1) { @@ -97,9 +107,14 @@ void cCcD_DivideArea::CalcDivideInfo(cCcD_DivideInfo* pDivideInfo, const cM3dGAa if (!mZDiffIsZero) { s32 var1 = mInvScaledZDiff * (aab.GetMinP()->z - GetMinP()->z); s32 var3 = mInvScaledZDiff * (aab.GetMaxP()->z - GetMinP()->z); +#if TARGET_PC + var1 = var1 < 0 ? 0 : var1 > 31 ? 31 : var1; + var3 = var3 < 0 ? 0 : var3 > 31 ? 31 : var3; +#else if (31 < var3) { var3 = 31; } +#endif zDivInfo = l_base[var3]; if (0 < var1) { diff --git a/src/SSystem/SComponent/c_math.cpp b/src/SSystem/SComponent/c_math.cpp index 2cc54fece9..7a95a46949 100644 --- a/src/SSystem/SComponent/c_math.cpp +++ b/src/SSystem/SComponent/c_math.cpp @@ -109,6 +109,9 @@ static u16 atntable[1025] = { u16 U_GetAtanTable(f32 f0, f32 f1) { int idx = f0 / f1 * 0x400; +#if TARGET_PC + idx = idx < 0 ? 0 : idx > 0x400 ? 0x400 : idx; +#endif return atntable[idx]; } From abf931ebcbb7601f277b3a6619be646318e2e03f Mon Sep 17 00:00:00 2001 From: Irastris Date: Tue, 31 Mar 2026 13:02:19 -0400 Subject: [PATCH 174/350] Add logging to the Epona minigame fixes and some research comments --- src/SSystem/SComponent/c_cc_d.cpp | 24 ++++++++++++++++++++++++ src/SSystem/SComponent/c_math.cpp | 5 +++++ 2 files changed, 29 insertions(+) diff --git a/src/SSystem/SComponent/c_cc_d.cpp b/src/SSystem/SComponent/c_cc_d.cpp index d77e155adc..76c4920b8f 100644 --- a/src/SSystem/SComponent/c_cc_d.cpp +++ b/src/SSystem/SComponent/c_cc_d.cpp @@ -6,6 +6,7 @@ #include "SSystem/SComponent/c_cc_d.h" #include "JSystem/JUtility/JUTAssert.h" #include +#include "dusk/logging.h" #define CHECK_FLOAT_RANGE(line, x) JUT_ASSERT(line, -1.0e32f < x && x < 1.0e32f); @@ -66,6 +67,15 @@ void cCcD_DivideArea::CalcDivideInfo(cCcD_DivideInfo* pDivideInfo, const cM3dGAa s32 var1 = mInvScaledXDiff * (aab.GetMinP()->x - GetMinP()->x); s32 var3 = mInvScaledXDiff * (aab.GetMaxP()->x - GetMinP()->x); #if TARGET_PC + // yDiv seems to be the problematic part here, but I clamped xDiv and zDiv too just in case. + // Unlike var1, var3 being greater than 31 isn't logged because it is every frame and the original code accounted for that. + // This goes for yDiv and zDiv as well. + if (var1 < 0 || var1 > 31) { + DuskLog.warn("CalcDivideInfo: xDiv var1 out of range: {}", var1); + } + if (var3 < 0) { + DuskLog.warn("CalcDivideInfo: xDiv var3 out of range: {}", var3); + } var1 = var1 < 0 ? 0 : var1 > 31 ? 31 : var1; var3 = var3 < 0 ? 0 : var3 > 31 ? 31 : var3; #else @@ -87,6 +97,14 @@ void cCcD_DivideArea::CalcDivideInfo(cCcD_DivideInfo* pDivideInfo, const cM3dGAa s32 var1 = mInvScaledYDiff * (aab.GetMinP()->y - GetMinP()->y); s32 var3 = mInvScaledYDiff * (aab.GetMaxP()->y - GetMinP()->y); #if TARGET_PC + // When the Epona taming minigame starts as Wolf Link, yDiv var1 can be 32. + // When the Epona taming minigame ends, yDiv var1 and var3 can both be INT_MIN. + if (var1 < 0 || var1 > 31) { + DuskLog.warn("CalcDivideInfo: yDiv var1 out of range: {}", var1); + } + if (var3 < 0) { + DuskLog.warn("CalcDivideInfo: yDiv var3 out of range: {}", var3); + } var1 = var1 < 0 ? 0 : var1 > 31 ? 31 : var1; var3 = var3 < 0 ? 0 : var3 > 31 ? 31 : var3; #else @@ -108,6 +126,12 @@ void cCcD_DivideArea::CalcDivideInfo(cCcD_DivideInfo* pDivideInfo, const cM3dGAa s32 var1 = mInvScaledZDiff * (aab.GetMinP()->z - GetMinP()->z); s32 var3 = mInvScaledZDiff * (aab.GetMaxP()->z - GetMinP()->z); #if TARGET_PC + if (var1 < 0 || var1 > 31) { + DuskLog.warn("CalcDivideInfo: zDiv var1 out of range: {}", var1); + } + if (var3 < 0) { + DuskLog.warn("CalcDivideInfo: zDiv var3 out of range: {}", var3); + } var1 = var1 < 0 ? 0 : var1 > 31 ? 31 : var1; var3 = var3 < 0 ? 0 : var3 > 31 ? 31 : var3; #else diff --git a/src/SSystem/SComponent/c_math.cpp b/src/SSystem/SComponent/c_math.cpp index 7a95a46949..7baeea689c 100644 --- a/src/SSystem/SComponent/c_math.cpp +++ b/src/SSystem/SComponent/c_math.cpp @@ -7,6 +7,7 @@ #include "SSystem/SComponent/c_m3d.h" #include #include +#include "dusk/logging.h" s16 cM_rad2s(f32 rad) { s32 s = (std::fmod(rad, 2 * M_PI) * (0x8000 / M_PI)); @@ -110,6 +111,10 @@ static u16 atntable[1025] = { u16 U_GetAtanTable(f32 f0, f32 f1) { int idx = f0 / f1 * 0x400; #if TARGET_PC + // When Wolf Link is playing a Human Link animation, idx can be INT_MIN every frame + if (idx < 0 || idx > 0x400) { + DuskLog.warn("U_GetAtanTable: idx out of range: {}", idx); + } idx = idx < 0 ? 0 : idx > 0x400 ? 0x400 : idx; #endif return atntable[idx]; From f2814da1b4c7c777cb933eac5c4b9f2082dbb68f Mon Sep 17 00:00:00 2001 From: Irastris Date: Wed, 1 Apr 2026 18:09:40 -0400 Subject: [PATCH 175/350] 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 7d57ff914b6e39c162b20b4035b1e8d9850ab54e Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Thu, 2 Apr 2026 01:23:53 +0200 Subject: [PATCH 176/350] 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 2e920d510d564841a75a19b3fe67bb9165f8947d Mon Sep 17 00:00:00 2001 From: Luke Street Date: Wed, 1 Apr 2026 18:30:12 -0600 Subject: [PATCH 177/350] 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 e1d99c76ae7e116194c24788115651d1d532ba27 Mon Sep 17 00:00:00 2001 From: Irastris Date: Wed, 1 Apr 2026 20:57:49 -0400 Subject: [PATCH 178/350] 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 9b7d5bd261a703b17ab3447c67b2c1bbcb58dbf7 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Wed, 1 Apr 2026 21:01:59 -0600 Subject: [PATCH 179/350] 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 2019cabecea055df8c97d7e3972ef667d230e2ad Mon Sep 17 00:00:00 2001 From: TakaRikka Date: Thu, 2 Apr 2026 03:11:58 -0700 Subject: [PATCH 180/350] 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 From a1b2d8a2c66a1e995f7e8b2efc491ca3feb08614 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Thu, 2 Apr 2026 23:45:22 +0200 Subject: [PATCH 181/350] Just use mDoCPd_c for the input check --- src/f_ap/f_ap_game.cpp | 71 +----------------------------------------- 1 file changed, 1 insertion(+), 70 deletions(-) diff --git a/src/f_ap/f_ap_game.cpp b/src/f_ap/f_ap_game.cpp index e97e97148c..1cbbb7449f 100644 --- a/src/f_ap/f_ap_game.cpp +++ b/src/f_ap/f_ap_game.cpp @@ -721,67 +721,6 @@ void fapGm_After() { fopCamM_Management(); } -#if TARGET_PC -// Analog L is currently not being used, so commented out -// static f32 prevFrameAnalogL = 0.f; -static f32 prevFrameAnalogR = 0.f; - -static bool DuskCheckButtonCombo(u32 combo, bool checkAnalog) { - const auto& padInfo = mDoCPd_c::getCpadInfo(PAD_1); - - // Get the buttons that are currently held and were pressed this frame - uint32_t buttonsHeld = padInfo.mButtonFlags; - uint32_t buttonsPressedThisFrame = padInfo.mPressedButtonFlags; - - if (checkAnalog) { - // Get the threshold for the analog buttons - constexpr float analogThreshold = 0.7f; // 70% - - // Check if L is included in the button combo - // Analog L is currently not being used, so commented out - /* - if ( combo & PadInputs::Button_L ) - { - // Check if analog L is at 70% or more - if ( padInfo->mTriggerLeft >= analogThreshold ) - { - // Manually set the bit for L being pressed - buttonsHeld |= PadInputs::Button_L; - - // If prevFrameAnalogL is less than 70%, then 70% was reached this frame - if ( prevFrameAnalogL < analogThreshold ) - { - buttonsPressedThisFrame |= PadInputs::Button_L; - } - } - } - */ - - // Check if R is included in the button combo - if (combo & PAD_TRIGGER_R) { - // Check if analog R is at 70% or more - if (padInfo.mTriggerRight >= analogThreshold) { - // Manually set the bit for R being pressed - buttonsHeld |= PAD_TRIGGER_R; - - // If prevFrameAnalogR is less than 70%, then 70% was reached this frame - if (prevFrameAnalogR < analogThreshold) { - buttonsPressedThisFrame |= PAD_TRIGGER_R; - } - } - } - } - - // Check if the button combo is held - if ((buttonsHeld & combo) != combo) { - return false; - } - - // Check if at least one button in the combo was pressed this frame - return buttonsPressedThisFrame & combo; -} -#endif - void fapGm_Execute() { static u32 sExecCount = 0; if (sExecCount < 10 || (sExecCount % 300 == 0)) { @@ -794,8 +733,7 @@ void fapGm_Execute() { #endif #if TARGET_PC - const auto& padInfo = mDoCPd_c::getCpadInfo(PAD_1); - if (DuskCheckButtonCombo(PAD_TRIGGER_R | PAD_BUTTON_Y, true)) { + if (mDoCPd_c::getHoldR(PAD_1) && mDoCPd_c::getTrigY(PAD_1)) { if (const auto link = g_dComIfG_gameInfo.play.getPlayer(0)) { dynamic_cast(link)->handleQuickTransform(); } @@ -804,13 +742,6 @@ void fapGm_Execute() { fpcM_Management(NULL, fapGm_After); cCt_Counter(0); - -#if TARGET_PC - // Main code has ran, so update previous frame variables - // Analog L is currently not being used, so commented out - // prevFrameAnalogL = padInfo.mTriggerLeft; - prevFrameAnalogR = padInfo.mTriggerRight; -#endif } fapGm_HIO_c g_HIO; From 5972b740cde0d1a157cfdc2b5a6bebc91160cdb0 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Thu, 2 Apr 2026 23:45:35 +0200 Subject: [PATCH 182/350] Rename d_a_alink_quicktransform.cpp to d_a_alink_dusk.cpp --- files.cmake | 2 +- .../actor/{d_a_alink_quicktransform.cpp => d_a_alink_dusk.cpp} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename src/d/actor/{d_a_alink_quicktransform.cpp => d_a_alink_dusk.cpp} (100%) diff --git a/files.cmake b/files.cmake index 6c04d9a22e..26cae0f1a9 100644 --- a/files.cmake +++ b/files.cmake @@ -1332,7 +1332,7 @@ set(DOLPHIN_FILES set(DUSK_FILES include/dusk/endian_gx.hpp - src/d/actor/d_a_alink_quicktransform.cpp + src/d/actor/d_a_alink_dusk.cpp src/dusk/asserts.cpp src/dusk/logging.cpp src/dusk/layout.cpp diff --git a/src/d/actor/d_a_alink_quicktransform.cpp b/src/d/actor/d_a_alink_dusk.cpp similarity index 100% rename from src/d/actor/d_a_alink_quicktransform.cpp rename to src/d/actor/d_a_alink_dusk.cpp From 08268eb015869c66dd07210cce87709b64143529 Mon Sep 17 00:00:00 2001 From: TakaRikka Date: Thu, 2 Apr 2026 19:08:30 -0700 Subject: [PATCH 183/350] add mirror mode toggle --- include/m_Do/m_Do_controller_pad.h | 29 +++++++++++++- include/m_Do/m_Do_graphic.h | 9 +++-- src/dusk/imgui/ImGuiMenuEnhancements.cpp | 2 + src/dusk/imgui/ImGuiMenuEnhancements.hpp | 1 + src/m_Do/m_Do_graphic.cpp | 49 ++++++++++++------------ 5 files changed, 61 insertions(+), 29 deletions(-) diff --git a/include/m_Do/m_Do_controller_pad.h b/include/m_Do/m_Do_controller_pad.h index 05e4490d4a..b8cee23ca3 100644 --- a/include/m_Do/m_Do_controller_pad.h +++ b/include/m_Do/m_Do_controller_pad.h @@ -4,6 +4,8 @@ #include "JSystem/JUtility/JUTGamePad.h" #include "SSystem/SComponent/c_API_controller_pad.h" +#include "dusk/imgui/ImGuiMenuEnhancements.hpp" + // Controller Ports 1 - 4 enum { PAD_1, PAD_2, PAD_3, PAD_4 }; @@ -52,8 +54,31 @@ public: static f32 getStickX3D(u32 pad) { return getCpadInfo(pad).mMainStickPosX; } static f32 getStickValue(u32 pad) { return getCpadInfo(pad).mMainStickValue; } static s16 getStickAngle(u32 pad) { return getCpadInfo(pad).mMainStickAngle; } - static s16 getStickAngle3D(u32 pad) { return getCpadInfo(pad).mMainStickAngle; } - static f32 getSubStickX3D(u32 pad) { return getCpadInfo(pad).mCStickPosX; } + + static s16 getStickAngle3D(u32 pad) { + #if TARGET_PC + if (dusk::ImGuiMenuEnhancements::m_enhancements.mirrorMode) { + return -getCpadInfo(pad).mMainStickAngle; + } else { + return getCpadInfo(pad).mMainStickAngle; + } + #else + return getCpadInfo(pad).mMainStickAngle; + #endif + } + + static f32 getSubStickX3D(u32 pad) { + #if TARGET_PC + if (dusk::ImGuiMenuEnhancements::m_enhancements.mirrorMode) { + return -getCpadInfo(pad).mCStickPosX; + } else { + return getCpadInfo(pad).mCStickPosX; + } + #else + return getCpadInfo(pad).mCStickPosX; + #endif + } + static f32 getSubStickX(u32 pad) { return getCpadInfo(pad).mCStickPosX; } static f32 getSubStickY(u32 pad) { return getCpadInfo(pad).mCStickPosY; } static f32 getSubStickValue(u32 pad) { return getCpadInfo(pad).mCStickValue; } diff --git a/include/m_Do/m_Do_graphic.h b/include/m_Do/m_Do_graphic.h index b4f947546d..3d560efeb6 100644 --- a/include/m_Do/m_Do_graphic.h +++ b/include/m_Do/m_Do_graphic.h @@ -312,6 +312,12 @@ public: static JKRHeap* m_heap; #endif +#if PLATFORM_WII || PLATFORM_SHIELD || TARGET_PC + static ResTIMG* m_fullFrameBufferTimg; + static void* m_fullFrameBufferTex; + static TGXTexObj m_fullFrameBufferTexObj; +#endif + #if PLATFORM_WII || PLATFORM_SHIELD static void resetDimming(); @@ -326,9 +332,6 @@ public: #if WIDESCREEN_SUPPORT static u8 mWide; static u8 mWideZoom; - static ResTIMG* m_fullFrameBufferTimg; - static void* m_fullFrameBufferTex; - static TGXTexObj m_fullFrameBufferTexObj; static f32 m_aspect; static f32 m_scale; diff --git a/src/dusk/imgui/ImGuiMenuEnhancements.cpp b/src/dusk/imgui/ImGuiMenuEnhancements.cpp index 9103c51fbd..5ea315d8ec 100644 --- a/src/dusk/imgui/ImGuiMenuEnhancements.cpp +++ b/src/dusk/imgui/ImGuiMenuEnhancements.cpp @@ -13,6 +13,7 @@ namespace dusk { .restoreWiiGlitches = false, .enableBloom = true, .useWaterProjectionOffset = false, + .mirrorMode = false, }; ImGuiMenuEnhancements::ImGuiMenuEnhancements() {} @@ -33,6 +34,7 @@ namespace dusk { ImGui::SetTooltip("Adds GC-specific -0.01 transS offset\n" "that causes ~6px ghost artifacts in water reflections"); } + ImGui::Checkbox("Mirror Mode", &m_enhancements.mirrorMode); ImGui::EndMenu(); } diff --git a/src/dusk/imgui/ImGuiMenuEnhancements.hpp b/src/dusk/imgui/ImGuiMenuEnhancements.hpp index 7b4775789b..0238317ce4 100644 --- a/src/dusk/imgui/ImGuiMenuEnhancements.hpp +++ b/src/dusk/imgui/ImGuiMenuEnhancements.hpp @@ -15,6 +15,7 @@ namespace dusk { bool restoreWiiGlitches; bool enableBloom; bool useWaterProjectionOffset; + bool mirrorMode; }; class ImGuiMenuEnhancements { diff --git a/src/m_Do/m_Do_graphic.cpp b/src/m_Do/m_Do_graphic.cpp index 8afbddcd2c..fe5e669a91 100644 --- a/src/m_Do/m_Do_graphic.cpp +++ b/src/m_Do/m_Do_graphic.cpp @@ -259,7 +259,7 @@ static ResTIMG* createTimg(u16 width, u16 height, u32 format) { JUTFader* mDoGph_gInf_c::mFader; -#if PLATFORM_WII || PLATFORM_SHIELD +#if PLATFORM_WII || PLATFORM_SHIELD || TARGET_PC ResTIMG* mDoGph_gInf_c::m_fullFrameBufferTimg; void* mDoGph_gInf_c::m_fullFrameBufferTex; #endif @@ -313,7 +313,7 @@ void mDoGph_gInf_c::create() { JUTProcBar::getManager()->setVisible(false); JUTDbPrint::getManager()->setVisible(false); - #if PLATFORM_WII || PLATFORM_SHIELD + #if PLATFORM_WII || PLATFORM_SHIELD || TARGET_PC m_fullFrameBufferTimg = createTimg(FB_WIDTH, FB_HEIGHT, 6); JUT_ASSERT(366, m_fullFrameBufferTimg != NULL); m_fullFrameBufferTex = (char*)m_fullFrameBufferTimg + sizeof(ResTIMG); @@ -388,7 +388,7 @@ void mDoGph_gInf_c::onBlure() { onBlure(cMtx_getIdentity()); } -#if PLATFORM_WII || PLATFORM_SHIELD +#if PLATFORM_WII || PLATFORM_SHIELD || TARGET_PC TGXTexObj mDoGph_gInf_c::m_fullFrameBufferTexObj; #endif @@ -789,8 +789,8 @@ int mDoGph_AfterOfDraw() { return 1; } -#if PLATFORM_WII -void drawFilterQuad(s8 param_0, s8 param_1) { +#if PLATFORM_WII || TARGET_PC +void mDoGph_drawFilterQuad(s8 param_0, s8 param_1) { GXBegin(GX_QUADS, GX_VTXFMT0, 4); GXPosition3s8(0, 0, -5); GXTexCoord2s8(0, 0); @@ -802,9 +802,6 @@ void drawFilterQuad(s8 param_0, s8 param_1) { GXTexCoord2s8(0, 1); GXEnd(); } - -// mapping to simplify call changes between wii / other platforms -#define mDoGph_drawFilterQuad drawFilterQuad #endif static void drawDepth2(view_class* param_0, view_port_class* param_1, int param_2) { @@ -1122,7 +1119,7 @@ static void trimming(view_class* param_0, view_port_class* param_1) { param_1->scissor.height); } -#if !PLATFORM_WII +#if !PLATFORM_WII && !TARGET_PC void mDoGph_drawFilterQuad(s8 param_0, s8 param_1) { GXBegin(GX_QUADS, GX_VTXFMT0, 4); GXPosition2s8(0, 0); @@ -1203,12 +1200,12 @@ void mDoGph_gInf_c::bloom_c::draw() { GXClearVtxDesc(); GXSetVtxDesc(GX_VA_POS, GX_DIRECT); GXSetVtxDesc(GX_VA_TEX0, GX_DIRECT); - #if PLATFORM_WII - GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_CLR_RGBA, GX_RGB8, 0); + #if PLATFORM_WII || TARGET_PC + GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_S8, 0); #else - GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_CLR_RGB, GX_RGB8, 0); + GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XY, GX_S8, 0); #endif - GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_CLR_RGBA, GX_RGB8, 0); + GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_S8, 0); if (mMonoColor.a != 0) { GXSetNumTevStages(1); GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR_NULL); @@ -1452,12 +1449,12 @@ static void motionBlure(view_class* param_0) { GXClearVtxDesc(); GXSetVtxDesc(GX_VA_POS, GX_DIRECT); GXSetVtxDesc(GX_VA_TEX0, GX_DIRECT); - #if PLATFORM_WII - GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_CLR_RGBA, GX_RGB8, 0); + #if PLATFORM_WII || TARGET_PC + GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_S8, 0); #else - GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_CLR_RGB, GX_RGB8, 0); + GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XY, GX_S8, 0); #endif - GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_CLR_RGBA, GX_RGB8, 0); + GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_S8, 0); mDoGph_drawFilterQuad(1, 1); GXSetProjection(param_0->projMtx, GX_PERSPECTIVE); } @@ -2103,10 +2100,14 @@ int mDoGph_Painter() { fapGm_HIO_c::startCpuTimer(); #endif - #if PLATFORM_WII - if (data_8053a730) { - GXSetTexCopySrc(0, 0, FB_WIDTH, FB_HEIGHT); - GXSetTexCopyDst(FB_WIDTH, FB_HEIGHT, (GXTexFmt)mDoGph_gInf_c::m_fullFrameBufferTimg->format, 0); + #if TARGET_PC + if (dusk::ImGuiMenuEnhancements::m_enhancements.mirrorMode) + #elif PLATFORM_WII + if (data_8053a730) + #endif + { + GXSetTexCopySrc(0, 0, mDoGph_gInf_c::getWidth(), mDoGph_gInf_c::getHeight()); + GXSetTexCopyDst(mDoGph_gInf_c::getWidth(), mDoGph_gInf_c::getHeight(), (GXTexFmt)mDoGph_gInf_c::m_fullFrameBufferTimg->format, 0); GXCopyTex(mDoGph_gInf_c::m_fullFrameBufferTex, 0); GXPixModeSync(); GXInvalidateTexAll(); @@ -2132,9 +2133,9 @@ int mDoGph_Painter() { GXSetFogRangeAdj(GX_DISABLE, 0, NULL); GXSetCullMode(GX_CULL_NONE); GXSetDither(GX_ENABLE); - + Mtx44 mtx; - MTXOrtho(mtx, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 10.0f); + MTXOrtho(mtx, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 10.0f); GXSetProjection(mtx, GX_ORTHOGRAPHIC); GXLoadPosMtxImm(cMtx_getIdentity(), GX_PNMTX0); GXSetCurrentMtx(0); @@ -2143,7 +2144,7 @@ int mDoGph_Painter() { GXSetVtxDesc(GX_VA_TEX0, GX_DIRECT); GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_S8, 0); GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_RGB8, 0); - drawFilterQuad(1, 1); + mDoGph_drawFilterQuad(1, 1); } #endif From f432cc89695f69a1fd14fc9160b11c87c192ffba Mon Sep 17 00:00:00 2001 From: TakaRikka Date: Thu, 2 Apr 2026 19:15:41 -0700 Subject: [PATCH 184/350] fix --- src/m_Do/m_Do_graphic.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/m_Do/m_Do_graphic.cpp b/src/m_Do/m_Do_graphic.cpp index fe5e669a91..0096334810 100644 --- a/src/m_Do/m_Do_graphic.cpp +++ b/src/m_Do/m_Do_graphic.cpp @@ -2105,6 +2105,7 @@ int mDoGph_Painter() { #elif PLATFORM_WII if (data_8053a730) #endif + #if TARGET_PC || PLATFORM_WII { GXSetTexCopySrc(0, 0, mDoGph_gInf_c::getWidth(), mDoGph_gInf_c::getHeight()); GXSetTexCopyDst(mDoGph_gInf_c::getWidth(), mDoGph_gInf_c::getHeight(), (GXTexFmt)mDoGph_gInf_c::m_fullFrameBufferTimg->format, 0); From 01f0a5aef0d6eab5778bba33935846db19280c62 Mon Sep 17 00:00:00 2001 From: Max Roncace Date: Thu, 2 Apr 2026 23:06:32 -0400 Subject: [PATCH 185/350] Fix crash when first talking to Fyrus The animation metadata passed into playExpression is invalid and causes a junk pointer to be read, triggering a segfault. --- src/d/actor/d_a_npc_grz.cpp | 10 +++++++++- src/d/actor/d_a_npc_tks.cpp | 8 +++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/d/actor/d_a_npc_grz.cpp b/src/d/actor/d_a_npc_grz.cpp index c245340c4c..104c0c4756 100644 --- a/src/d/actor/d_a_npc_grz.cpp +++ b/src/d/actor/d_a_npc_grz.cpp @@ -1363,7 +1363,15 @@ void daNpc_Grz_c::playExpression() { daNpcF_anmPlayData dat7 = {ANM_LIEDOWN, mpHIO->m.common.morf_frame, 0}; daNpcF_anmPlayData* pDat7[1] = {&dat7}; daNpcF_anmPlayData dat8 = {ANM_GETUP, mpHIO->m.common.morf_frame, 1}; - daNpcF_anmPlayData* pDat8[1] = {&dat8}; +#if TARGET_PC + // BUG: If mNumLoops is non-zero, the data array MUST be null-terminated to prevent + // playExpression from reading junk when it increments the index. + // For some reason this doesn't crash on hardware, but it triggers a segfault on PC + // when first talking to Fyrus after defating him. + daNpcF_anmPlayData* pDat8[2] = {&dat8, NULL}; +#else + daNpcF_anmPlayData* pDat8[2] = {&dat8}; +#endif daNpcF_anmPlayData dat9 = {ANM_F_WEAK_WAIT, mpHIO->m.common.morf_frame, 0}; daNpcF_anmPlayData* pDat9[1] = {&dat9}; daNpcF_anmPlayData dat10 = {ANM_NONE, mpHIO->m.common.morf_frame, 0}; diff --git a/src/d/actor/d_a_npc_tks.cpp b/src/d/actor/d_a_npc_tks.cpp index f1c33003f8..9b0ee4af70 100644 --- a/src/d/actor/d_a_npc_tks.cpp +++ b/src/d/actor/d_a_npc_tks.cpp @@ -824,9 +824,11 @@ void daNpcTks_c::playMotion() { daNpcF_anmPlayData dat4b = {ANM_FLY, 0.0f, 0}; daNpcF_anmPlayData* pDat4[2] = {&dat4a, &dat4b}; - #if TARGET_PC - // Note: this fixes a crash in the cutscene after entering city and likely needs to be revisited as its - // unclear why this was 1 in the first place and unclear why this fixes it +#if TARGET_PC + // BUG: If mNumLoops is non-zero, the data array MUST be null-terminated to prevent + // playExpression from reading junk when it increments the index. + // For some reason this doesn't crash on hardware, but it triggers a segfault on PC + // in the cutscene after entering city. daNpcF_anmPlayData dat5 = {ANM_JUMP_E, 0.0f, 0}; #else daNpcF_anmPlayData dat5 = {ANM_JUMP_E, 0.0f, 1}; From 7413944af09e3b8d227f448e4b5b6dbde2976477 Mon Sep 17 00:00:00 2001 From: TakaRikka Date: Thu, 2 Apr 2026 21:10:57 -0700 Subject: [PATCH 186/350] fix plumm balloon score proj --- src/d/actor/d_a_balloon_2D.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/d/actor/d_a_balloon_2D.cpp b/src/d/actor/d_a_balloon_2D.cpp index d23a64965a..957e7df281 100644 --- a/src/d/actor/d_a_balloon_2D.cpp +++ b/src/d/actor/d_a_balloon_2D.cpp @@ -304,7 +304,11 @@ void daBalloon2D_c::addScoreCount(cXyz* param_1, u32 param_2, u8 param_3) { field_0x5f8[current].field_0xf = field_0x5f8[prev].field_0xf; } cXyz acStack_2c; + #if TARGET_PC + mDoLib_project(param_1, &acStack_2c, { 0, 0, FB_WIDTH, FB_HEIGHT }); + #else mDoLib_project(param_1, &acStack_2c); + #endif field_0x5f8[0].field_0x0.set(acStack_2c); field_0x5f8[0].field_0xc = param_2; field_0x5f8[0].field_0xe = 60; From 6cbb8ffc8b4950125a6e2bc9b084d6d4e7a38bed Mon Sep 17 00:00:00 2001 From: Luke Street Date: Thu, 2 Apr 2026 23:07:33 -0600 Subject: [PATCH 187/350] Update aurora & use new frame buffer API --- CMakeLists.txt | 8 ++++++- extern/aurora | 2 +- src/d/d_drawlist.cpp | 17 +++++++++++++++ src/d/d_error_msg.cpp | 5 +++++ src/d/d_gameover.cpp | 7 ++++++- src/d/d_map.cpp | 11 ---------- src/d/d_map_path.cpp | 44 ++++++++++++++++++++++++++++++++++++++- src/d/d_menu_dmap_map.cpp | 5 ----- src/d/d_menu_fmap_map.cpp | 6 ------ src/d/d_menu_window.cpp | 9 ++++++++ src/d/d_ovlp_fade3.cpp | 7 ++++--- src/m_Do/m_Do_graphic.cpp | 12 +++++++++++ 12 files changed, 104 insertions(+), 29 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3992507347..e0cf678839 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -117,7 +117,13 @@ add_library(game SHARED ${DOLZEL_FILES} ${Z2AUDIOLIB_FILES} ${SSYSTEM_FILES} ${J 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) + if (TARGET libjpeg-turbo::turbojpeg-static) + message(STATUS "dusk: Linking against libjpeg-turbo static library") + target_link_libraries(game PRIVATE libjpeg-turbo::turbojpeg-static) + else () + message(STATUS "dusk: Linking against libjpeg-turbo shared library") + target_link_libraries(game PRIVATE libjpeg-turbo::turbojpeg) + endif () 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/extern/aurora b/extern/aurora index 4c56dba0a4..33ec54347d 160000 --- a/extern/aurora +++ b/extern/aurora @@ -1 +1 @@ -Subproject commit 4c56dba0a401a936c19b3299d3d5545001af5642 +Subproject commit 33ec54347df497c3f8c237e5c2b65590fb1807df diff --git a/src/d/d_drawlist.cpp b/src/d/d_drawlist.cpp index ede33efd65..8b7457637d 100644 --- a/src/d/d_drawlist.cpp +++ b/src/d/d_drawlist.cpp @@ -1398,7 +1398,12 @@ void dDlst_shadowSimple_c::set(cXyz* param_0, f32 param_1, f32 param_2, cXyz* pa } void dDlst_shadowControl_c::init() { +#if TARGET_PC + // Increase shadow map resolution + static u16 l_realImageSize[2] = {1024, 512}; +#else static u16 l_realImageSize[2] = {192, 64}; +#endif for (int i = 0; i < 2; i++) { u16 size = l_realImageSize[i]; @@ -1471,11 +1476,18 @@ void dDlst_shadowControl_c::imageDraw(Mtx param_0) { int tex = 0; u16 r27; u16 r26; +#ifdef TARGET_PC + bool needsRestore = false; +#endif for (; shadowReal; shadowReal = shadowReal->getZsortNext()) { if (shadowReal->isUse()) { if (r29 == 0) { r27 = GXGetTexObjWidth(field_0x15eb0 + tex); r26 = r27 * 2; +#ifdef TARGET_PC + GXCreateFrameBuffer(r26, r26); + needsRestore = true; +#endif GXSetViewport(0.0f, 0.0f, r26, r26, 0.0f, 1.0f); GXSetScissor(0, 0, r26, r26); } @@ -1503,6 +1515,11 @@ void dDlst_shadowControl_c::imageDraw(Mtx param_0) { GXPixModeSync(); GXSetAlphaUpdate(GX_DISABLE); } +#ifdef TARGET_PC + if (needsRestore) { + GXRestoreFrameBuffer(); + } +#endif GXSetClipMode(GX_CLIP_ENABLE); GXSetDither(GX_TRUE); } diff --git a/src/d/d_error_msg.cpp b/src/d/d_error_msg.cpp index b0274b8583..faa40ae543 100644 --- a/src/d/d_error_msg.cpp +++ b/src/d/d_error_msg.cpp @@ -240,8 +240,13 @@ static void drawCapture(u8 alpha) { static bool l_texCopied = false; if (!l_texCopied) { +#if TARGET_PC + GXSetTexCopySrc(0, 0, mDoGph_gInf_c::getWidth(), mDoGph_gInf_c::getHeight()); + GXSetTexCopyDst(mDoGph_gInf_c::getWidth(), mDoGph_gInf_c::getHeight(), (GXTexFmt)mDoGph_gInf_c::getFrameBufferTimg()->format, GX_TRUE); +#else GXSetTexCopySrc(0, 0, FB_WIDTH, FB_HEIGHT); GXSetTexCopyDst(FB_WIDTH / 2, FB_HEIGHT / 2, (GXTexFmt)mDoGph_gInf_c::getFrameBufferTimg()->format, GX_TRUE); +#endif GXCopyTex(mDoGph_gInf_c::getFrameBufferTex(), GX_FALSE); l_texCopied = true; } diff --git a/src/d/d_gameover.cpp b/src/d/d_gameover.cpp index 236cad603c..693bcf371e 100644 --- a/src/d/d_gameover.cpp +++ b/src/d/d_gameover.cpp @@ -37,8 +37,13 @@ void dDlst_Gameover_CAPTURE_c::draw() { TGXTexObj tex_obj; Mtx44 m; +#if TARGET_PC + GXSetTexCopySrc(0, 0, mDoGph_gInf_c::getWidth(), mDoGph_gInf_c::getHeight()); + GXSetTexCopyDst(mDoGph_gInf_c::getWidth(), mDoGph_gInf_c::getHeight(), GX_TF_RGB565, GX_TRUE); +#else GXSetTexCopySrc(0, 0, FB_WIDTH, FB_HEIGHT); - GXSetTexCopyDst(FB_WIDTH / 2, FB_HEIGHT / 2, GX_TF_RGB565, 1); + GXSetTexCopyDst(FB_WIDTH / 2, FB_HEIGHT / 2, GX_TF_RGB565, GX_TRUE); +#endif GXCopyTex(mDoGph_gInf_c::mZbufferTex, 0); GXPixModeSync(); GXInitTexObj(&tex_obj, mDoGph_gInf_c::mFrameBufferTex, FB_WIDTH / 2, FB_HEIGHT / 2, diff --git a/src/d/d_map.cpp b/src/d/d_map.cpp index 46b0bbf742..5da8f95a46 100644 --- a/src/d/d_map.cpp +++ b/src/d/d_map.cpp @@ -1180,23 +1180,12 @@ dMap_c::dMap_c(int width, int height, int param_2, int param_3) { mImage_p = JKR_NEW_ARRAY_ARGS(u8, buffer_size, 0x20); JUT_ASSERT(2638, mImage_p != NULL); - -#ifdef TARGET_PC - // Increase map render resolution - renderingDAmap_c::init(mImage_p, mTexSizeX * 4, mTexSizeY * 4, mTexSizeX, mTexSizeY); -#else renderingDAmap_c::init(mImage_p, mTexSizeX, mTexSizeY, mTexSizeX, mTexSizeY); -#endif mResTIMG = JKR_NEW_ARGS (0x20) ResTIMG; JUT_ASSERT(2647, mResTIMG != NULL); -#ifdef TARGET_PC - // Increase map render resolution - makeResTIMG(mResTIMG, mTexSizeX * 4, mTexSizeY * 4, mImage_p, (u8*)m_res, 0x33); -#else makeResTIMG(mResTIMG, mTexSizeX, mTexSizeY, mImage_p, (u8*)m_res, 0x33); -#endif } #if DEBUG diff --git a/src/d/d_map_path.cpp b/src/d/d_map_path.cpp index b5ecee95b3..f7ce0f1a51 100644 --- a/src/d/d_map_path.cpp +++ b/src/d/d_map_path.cpp @@ -14,6 +14,14 @@ #include "m_Do/m_Do_lib.h" #include +#ifdef TARGET_PC +constexpr u16 kMapResolutionMultiplier = 4; +// Line widths are relative to the framebuffer size. Since we're rendering to a separate +// framebuffer, we have to scale them accordingly. The original game used about half of the +// EFB for the map rendering, so this is a reasonable approximation. +constexpr u8 kMapLineWidthMultiplier = 2; +#endif + void dMpath_n::dTexObjAggregate_c::create() { static int const data[7] = { 79, 80, 77, 78, 76, 81, 82, @@ -234,7 +242,11 @@ void dDrawPath_c::rendering(dDrawPath_c::line_class const* p_line) { int width = getLineWidth(p_line->field_0x1); if (width > 0 && p_line->mDataNum >= 2) { - GXSetLineWidth(width, GX_TO_ZERO); +#ifdef TARGET_PC + GXSetLineWidth(width * kMapLineWidthMultiplier, GX_TO_ZERO); +#else + GXSetLineWidth(width * 2, GX_TO_ZERO); +#endif GXSetTevColor(GX_TEVREG0, *getLineColor(p_line->field_0x0 & 0x3F, p_line->field_0x1)); GXBegin(GX_LINESTRIP, GX_VTXFMT0, p_line->mDataNum); @@ -333,8 +345,14 @@ void dRenderingMap_c::makeResTIMG(ResTIMG* p_image, u16 width, u16 height, u8* p u8* p_palette, u16 param_5) const { p_image->format = GX_TF_C8; p_image->alphaEnabled = 2; +#ifdef TARGET_PC + // Increase map render resolution + p_image->width = width * kMapResolutionMultiplier; + p_image->height = height * kMapResolutionMultiplier; +#else p_image->width = width; p_image->height = height; +#endif p_image->wrapS = GX_CLAMP; p_image->wrapT = GX_CLAMP; p_image->indexTexture = true; @@ -412,8 +430,17 @@ void dRenderingFDAmap_c::drawBack() const { } void dRenderingFDAmap_c::preRenderingMap() { +#ifdef TARGET_PC + // Increase map render resolution + const u16 w = mTexWidth * kMapResolutionMultiplier; + const u16 h = mTexHeight * kMapResolutionMultiplier; + GXCreateFrameBuffer(w, h); + GXSetViewport(0.0f, 0.0f, w, h, 0.0f, 1.0f); + GXSetScissor(0, 0, w, h); +#else GXSetViewport(0.0f, 0.0f, mTexWidth, mTexHeight, 0.0f, 1.0f); GXSetScissor(0, 0, mTexWidth, mTexHeight); +#endif GXSetNumChans(1); GXSetNumTevStages(1); GXSetChanCtrl(GX_COLOR0A0, GX_FALSE, GX_SRC_REG, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_NONE, @@ -440,9 +467,19 @@ void dRenderingFDAmap_c::preRenderingMap() { void dRenderingFDAmap_c::postRenderingMap() { GXSetCopyFilter(GX_FALSE, NULL, GX_FALSE, NULL); +#ifdef TARGET_PC + // Increase map render resolution + const u16 w = mTexWidth * kMapResolutionMultiplier; + const u16 h = mTexHeight * kMapResolutionMultiplier; + GXSetTexCopySrc(0, 0, w, h); + GXSetTexCopyDst(w, h, GX_CTF_R8, GX_FALSE); + GXCopyTex(field_0x4, GX_TRUE); + GXRestoreFrameBuffer(); +#else GXSetTexCopySrc(0, 0, mTexWidth, mTexHeight); GXSetTexCopyDst(mTexWidth, mTexHeight, GX_CTF_R8, GX_FALSE); GXCopyTex(field_0x4, GX_TRUE); +#endif GXPixModeSync(); GXSetClipMode(GX_CLIP_ENABLE); GXSetDither(GX_TRUE); @@ -474,8 +511,13 @@ void dRenderingFDAmap_c::renderingDecoration(dDrawPath_c::line_class const* p_li BE(u16)* data_p = p_line->mpData; s32 data_num = p_line->mDataNum; +#ifdef TARGET_PC + GXSetLineWidth(width * kMapLineWidthMultiplier, GX_TO_ZERO); + GXSetPointSize(width * kMapLineWidthMultiplier, GX_TO_ONE); +#else GXSetLineWidth(width, GX_TO_ONE); GXSetPointSize(width, GX_TO_ONE); +#endif GXColor lineColor = *getDecoLineColor(p_line->field_0x0 & 0x3f, p_line->field_0x1); GXSetTevColor(GX_TEVREG0, lineColor); lineColor.r = lineColor.r - 4; diff --git a/src/d/d_menu_dmap_map.cpp b/src/d/d_menu_dmap_map.cpp index 8b345fbcbd..8971b2630d 100644 --- a/src/d/d_menu_dmap_map.cpp +++ b/src/d/d_menu_dmap_map.cpp @@ -301,11 +301,6 @@ void dMenu_DmapMap_c::_delete() { } void dMenu_DmapMap_c::setTexture(u16 width, u16 height, u16 param_2, u16 param_3) { -#ifdef TARGET_PC - // Increase map render resolution - width *= 2; - height *= 2; -#endif for (int lp1 = 0; lp1 < 2; lp1++) { #ifdef TARGET_PC u32 sz = 0x20; // No need to allocate memory for texture diff --git a/src/d/d_menu_fmap_map.cpp b/src/d/d_menu_fmap_map.cpp index 2c5b1cea8e..3d1a9e2523 100644 --- a/src/d/d_menu_fmap_map.cpp +++ b/src/d/d_menu_fmap_map.cpp @@ -657,12 +657,6 @@ const GXColor* dMenu_FmapMap_c::getColor(int param_0) { } void dMenu_FmapMap_c::setTexture(u16 i_width, u16 i_height, u16 param_2, u16 param_3) { -#ifdef TARGET_PC - // Increase map render resolution - i_width *= 2; - i_height *= 2; -#endif - mMapImage_p = NULL; mResTIMG = NULL; #ifdef TARGET_PC diff --git a/src/d/d_menu_window.cpp b/src/d/d_menu_window.cpp index 2244be6797..3e173049bd 100644 --- a/src/d/d_menu_window.cpp +++ b/src/d/d_menu_window.cpp @@ -39,13 +39,22 @@ public: GXSetTexCopySrc(0, 0, FB_WIDTH, FB_HEIGHT); #endif +#if TARGET_PC + GXSetTexCopyDst(mDoGph_gInf_c::getWidth(), mDoGph_gInf_c::getHeight(), (GXTexFmt)mDoGph_gInf_c::getFrameBufferTimg()->format, GX_ENABLE); +#else GXSetTexCopyDst(FB_WIDTH / 2, FB_HEIGHT / 2, (GXTexFmt)mDoGph_gInf_c::getFrameBufferTimg()->format, GX_ENABLE); +#endif GXCopyTex(mDoGph_gInf_c::getFrameBufferTex(), GX_FALSE); GXPixModeSync(); } else { TGXTexObj tex; +#if TARGET_PC + GXInitTexObj(&tex, mDoGph_gInf_c::getFrameBufferTex(), mDoGph_gInf_c::getWidth(), mDoGph_gInf_c::getHeight(), + (GXTexFmt)mDoGph_gInf_c::getFrameBufferTimg()->format, GX_CLAMP, GX_CLAMP, GX_FALSE); +#else GXInitTexObj(&tex, mDoGph_gInf_c::getFrameBufferTex(), FB_WIDTH / 2, FB_HEIGHT / 2, (GXTexFmt)mDoGph_gInf_c::getFrameBufferTimg()->format, GX_CLAMP, GX_CLAMP, GX_FALSE); +#endif GXInitTexObjLOD(&tex, GX_LINEAR, GX_LINEAR, 0.0f, 0.0f, 0.0f, GX_FALSE, GX_FALSE, GX_ANISO_1); GXLoadTexObj(&tex, GX_TEXMAP0); GXSetNumChans(0); diff --git a/src/d/d_ovlp_fade3.cpp b/src/d/d_ovlp_fade3.cpp index f5da4064a0..70304451fb 100644 --- a/src/d/d_ovlp_fade3.cpp +++ b/src/d/d_ovlp_fade3.cpp @@ -13,12 +13,13 @@ #include "m_Do/m_Do_graphic.h" void dDlst_snapShot_c::draw() { - #if TARGET_PC +#if TARGET_PC GXSetTexCopySrc(0, 0, mDoGph_gInf_c::getWidth(), mDoGph_gInf_c::getHeight()); - #else + GXSetTexCopyDst(mDoGph_gInf_c::getWidth(), mDoGph_gInf_c::getHeight(), GX_TF_RGBA8, GX_TRUE); +#else GXSetTexCopySrc(0, 0, FB_WIDTH, FB_HEIGHT); - #endif GXSetTexCopyDst(FB_WIDTH / 2, FB_HEIGHT / 2, GX_TF_RGBA8, GX_TRUE); +#endif GXCopyTex(mDoGph_gInf_c::getFrameBufferTex(), GX_FALSE); GXPixModeSync(); } diff --git a/src/m_Do/m_Do_graphic.cpp b/src/m_Do/m_Do_graphic.cpp index 8afbddcd2c..fb3caf3d38 100644 --- a/src/m_Do/m_Do_graphic.cpp +++ b/src/m_Do/m_Do_graphic.cpp @@ -1224,10 +1224,14 @@ void mDoGph_gInf_c::bloom_c::draw() { mDoGph_drawFilterQuad(4, 4); } if (enabled) { +#ifdef TARGET_PC + GXCreateFrameBuffer(width, height); +#else // Store off m_buffer to copy over again at the end. GXSetTexCopySrc(0, 0, width / 2, height / 2); GXSetTexCopyDst(width / 2, height / 2, GX_TF_RGBA8, 0); GXCopyTex(m_buffer, 0); +#endif GXSetNumTevStages(3); GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR_NULL); @@ -1351,6 +1355,9 @@ void mDoGph_gInf_c::bloom_c::draw() { GXSetTexCopyDst(width / 4, height / 4, GX_TF_RGBA8, GX_FALSE); GXCopyTex(zBufferTex, GX_FALSE); +#ifdef TARGET_PC + GXRestoreFrameBuffer(); +#else // Copy back m_buffer to screen. GXInitTexObj(&tmp_tex2, m_buffer, width / 2, height / 2, GX_TF_RGBA8, GX_CLAMP, GX_CLAMP, GX_FALSE); @@ -1369,6 +1376,7 @@ void mDoGph_gInf_c::bloom_c::draw() { GX_TEVPREV); GXSetBlendMode(GX_BM_NONE, GX_BL_ONE, GX_BL_ONE, GX_LO_OR); mDoGph_drawFilterQuad(2, 2); +#endif // Now blend our bloom into the real FB. GXLoadTexObj(&tmp_tex1, GX_TEXMAP0); @@ -1414,7 +1422,11 @@ static void retry_captue_frame(view_class* param_0, view_port_class* param_1, in var_r24 = width >> 1; var_r23 = height >> 1; GXSetTexCopySrc(x_orig, y_orig_pos, width, height); +#ifdef TARGET_PC + GXSetTexCopyDst(width, height, (GXTexFmt)mDoGph_gInf_c::getFrameBufferTimg()->format, GX_TRUE); +#else GXSetTexCopyDst(var_r24, var_r23, (GXTexFmt)mDoGph_gInf_c::getFrameBufferTimg()->format, GX_TRUE); +#endif GXCopyTex(tex, GX_FALSE); GXPixModeSync(); GXInvalidateTexAll(); From 4216165dfb96baed9216900ce7a098d7eb0192ee Mon Sep 17 00:00:00 2001 From: Luke Street Date: Fri, 3 Apr 2026 17:04:22 -0600 Subject: [PATCH 188/350] Copy more imgui stuff from Metaforce --- CMakeLists.txt | 7 + extern/aurora | 2 +- files.cmake | 2 + res/NotoMono-Regular.ttf | Bin 0 -> 107848 bytes src/dusk/imgui/ImGuiCameraOverlay.cpp | 3 +- src/dusk/imgui/ImGuiConsole.cpp | 57 +++++- src/dusk/imgui/ImGuiConsole.hpp | 61 ++++--- src/dusk/imgui/ImGuiControllerOverlay.cpp | 4 +- src/dusk/imgui/ImGuiEngine.cpp | 201 ++++++++++++++++++++++ src/dusk/imgui/ImGuiEngine.hpp | 25 +++ src/dusk/imgui/ImGuiMapLoader.cpp | 3 +- src/dusk/imgui/ImGuiMenuGame.cpp | 35 ++-- src/dusk/imgui/ImGuiMenuTools.cpp | 3 + src/dusk/imgui/ImGuiMenuTools.hpp | 2 +- src/m_Do/m_Do_main.cpp | 6 + 15 files changed, 362 insertions(+), 49 deletions(-) create mode 100644 res/NotoMono-Regular.ttf create mode 100644 src/dusk/imgui/ImGuiEngine.cpp create mode 100644 src/dusk/imgui/ImGuiEngine.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index e0cf678839..248dfcf6b1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -134,6 +134,13 @@ target_compile_definitions(dusk PRIVATE TARGET_PC AVOID_UB=1 VERSION=0) target_include_directories(dusk PRIVATE include) target_link_libraries(dusk PRIVATE game aurora::main) +add_custom_command(TARGET dusk POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + "${CMAKE_SOURCE_DIR}/res" + "$/res" + COMMENT "Copying resources" +) + include(extern/aurora/cmake/AuroraCopyRuntimeDLLs.cmake) aurora_copy_runtime_dlls(dusk game) diff --git a/extern/aurora b/extern/aurora index 33ec54347d..37631c3bf3 160000 --- a/extern/aurora +++ b/extern/aurora @@ -1 +1 @@ -Subproject commit 33ec54347df497c3f8c237e5c2b65590fb1807df +Subproject commit 37631c3bf382c1564d3f29c4c3f2dc6cf7617fa9 diff --git a/files.cmake b/files.cmake index 26cae0f1a9..e5b470dde2 100644 --- a/files.cmake +++ b/files.cmake @@ -1344,6 +1344,8 @@ set(DUSK_FILES #src/dusk/m_Do_ext_dusk.cpp src/dusk/imgui/ImGuiConsole.hpp src/dusk/imgui/ImGuiConsole.cpp + src/dusk/imgui/ImGuiEngine.cpp + src/dusk/imgui/ImGuiEngine.hpp src/dusk/imgui/ImGuiMenuGame.cpp src/dusk/imgui/ImGuiMenuGame.hpp src/dusk/imgui/ImGuiMenuTools.cpp diff --git a/res/NotoMono-Regular.ttf b/res/NotoMono-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..3560a3a0c87b328a63c3c5dcd2020f3fc32dbbe9 GIT binary patch literal 107848 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%0Am6uY2R}KQC=> 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

#cf7b!d>yWeyc2A*xN`Qh-!Gha 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 ke@Vs-7zxz!&}3uAWBWorkSize; ImVec2 windowPos; ImVec2 windowPosPivot; - constexpr float padding = 10.0f; + const float padding = 10.0f * ImGuiScale(); windowPos.x = (corner & 1) != 0 ? (workPos.x + workSize.x - padding) : (workPos.x + padding); windowPos.y = (corner & 2) != 0 ? (workPos.y + workSize.y - padding) : (workPos.y + padding); windowPosPivot.x = (corner & 1) != 0 ? 1.0f : 0.0f; @@ -173,23 +175,32 @@ namespace dusk { ImGuiConsole::ImGuiConsole() {} void ImGuiConsole::draw() { + if (!m_isLaunchInitialized) { + m_toasts.emplace_back("Press F1 to toggle menu"s, 5.f); + m_isLaunchInitialized = true; + } + if (CheckMenuViewToggle(ImGuiKey_F1, m_isHidden)) { + ShowToasts(); m_menuTools.afterDraw(); return; } + // TODO: we need to be able to render the menu bar & any overlays separately + // The code currently ties them all together, so hiding the menu hides all windows + if (ImGui::BeginMainMenuBar()) { m_menuGame.draw(); m_menuTools.draw(); m_menuEnhancements.draw(); - ImGui::SetCursorPosX(ImGui::GetWindowWidth() - 80.0f); + ImGui::SetCursorPosX(ImGui::GetWindowWidth() - 80.0f * ImGuiScale()); ImGuiIO& io = ImGui::GetIO(); ImGuiStringViewText(fmt::format(FMT_STRING("FPS: {:.2f}\n"), io.Framerate)); ImGui::EndMainMenuBar(); } - + ShowToasts(); m_menuTools.afterDraw(); } @@ -223,4 +234,44 @@ namespace dusk { return "Null"sv; } } + + void ImGuiConsole::ShowToasts() { + if (m_toasts.empty()) { + return; + } + auto& toast = m_toasts.front(); + const float dt = ImGui::GetIO().DeltaTime; + toast.remain -= dt; + toast.current += dt; + + const ImGuiViewport* viewport = ImGui::GetMainViewport(); + const ImVec2 workPos = viewport->WorkPos; + const ImVec2 workSize = viewport->WorkSize; + constexpr float padding = 10.0f; + const ImVec2 windowPos{workPos.x + workSize.x / 2, workPos.y + workSize.y - padding}; + ImGui::SetNextWindowPos(windowPos, ImGuiCond_Always, ImVec2{0.5f, 1.f}); + + const float alpha = std::min({toast.remain, toast.current, 1.f}); + ImGui::SetNextWindowBgAlpha(alpha * 0.65f); + ImVec4 textColor = ImGui::GetStyleColorVec4(ImGuiCol_Text); + textColor.w *= alpha; + ImVec4 borderColor = ImGui::GetStyleColorVec4(ImGuiCol_Border); + borderColor.w *= alpha; + ImGui::PushStyleColor(ImGuiCol_Text, textColor); + ImGui::PushStyleColor(ImGuiCol_Border, borderColor); + if (ImGui::Begin("Toast", nullptr, + ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | + ImGuiWindowFlags_NoSavedSettings | + ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav | + ImGuiWindowFlags_NoMove)) + { + ImGuiStringViewText(toast.message); + } + ImGui::End(); + ImGui::PopStyleColor(2); + + if (toast.remain <= 0.f) { + m_toasts.pop_front(); + } + } } diff --git a/src/dusk/imgui/ImGuiConsole.hpp b/src/dusk/imgui/ImGuiConsole.hpp index 2cb887f904..db67c2a1cf 100644 --- a/src/dusk/imgui/ImGuiConsole.hpp +++ b/src/dusk/imgui/ImGuiConsole.hpp @@ -2,41 +2,56 @@ #define DUSK_IMGUI_HPP #include +#include #include -#include "imgui.h" +#include "ImGuiMenuEnhancements.hpp" #include "ImGuiMenuGame.hpp" #include "ImGuiMenuTools.hpp" -#include "ImGuiMenuEnhancements.hpp" +#include "imgui.h" namespace dusk { - class ImGuiConsole { - public: - ImGuiConsole(); - void draw(); +class ImGuiConsole { +public: + ImGuiConsole(); + void draw(); - ImGuiMenuTools::CollisionViewSettings& getCollisionViewSettings() { return m_menuTools.getCollisionViewSettings(); } + ImGuiMenuTools::CollisionViewSettings& getCollisionViewSettings() { + return m_menuTools.getCollisionViewSettings(); + } - static bool CheckMenuViewToggle(ImGuiKey key, bool& active); + static bool CheckMenuViewToggle(ImGuiKey key, bool& active); - private: - bool m_isHidden = false; +private: + struct Toast { + std::string message; + float remain; + float current = 0.f; + Toast(std::string message, float duration) noexcept : message(std::move(message)), + remain(duration) {} + }; - ImGuiMenuGame m_menuGame; - ImGuiMenuTools m_menuTools; - ImGuiMenuEnhancements m_menuEnhancements; - }; + bool m_isHidden = true; + bool m_isLaunchInitialized = false; + std::deque m_toasts; + ImGuiMenuGame m_menuGame; + ImGuiMenuTools m_menuTools; + ImGuiMenuEnhancements m_menuEnhancements; - extern ImGuiConsole g_imguiConsole; + void ShowToasts(); +}; - std::string_view backend_name(AuroraBackend backend); - std::string BytesToString(size_t bytes); - void SetOverlayWindowLocation(int corner); - bool ShowCornerContextMenu(int& corner, int avoidCorner); - void ImGuiStringViewText(std::string_view text); - void ImGuiBeginGroupPanel(const char* name, const ImVec2& size); - void ImGuiEndGroupPanel(); -} +extern ImGuiConsole g_imguiConsole; + +std::string_view backend_name(AuroraBackend backend); +std::string BytesToString(size_t bytes); +void SetOverlayWindowLocation(int corner); +bool ShowCornerContextMenu(int& corner, int avoidCorner); +void ImGuiStringViewText(std::string_view text); +void ImGuiBeginGroupPanel(const char* name, const ImVec2& size); +void ImGuiEndGroupPanel(); +float ImGuiScale(); +} // namespace dusk void DuskDebugPad(); diff --git a/src/dusk/imgui/ImGuiControllerOverlay.cpp b/src/dusk/imgui/ImGuiControllerOverlay.cpp index 0da82ac878..306bace22a 100644 --- a/src/dusk/imgui/ImGuiControllerOverlay.cpp +++ b/src/dusk/imgui/ImGuiControllerOverlay.cpp @@ -13,8 +13,6 @@ namespace dusk { ImGui::TextUnformatted(text.c_str()); } - static inline float GetScale() { return ImGui::GetCurrentContext()->CurrentDpiScale; } - void ImGuiMenuGame::windowInputViewer() { if (!m_showInputViewer) { return; @@ -35,7 +33,7 @@ namespace dusk { ImGui::SetNextWindowBgAlpha(0.65f); if (ImGui::Begin("Input Viewer", nullptr, windowFlags)) { - float scale = GetScale(); + float scale = ImGuiScale(); if (!m_controllerName.empty()) { TextCenter(m_controllerName); ImGui::Separator(); diff --git a/src/dusk/imgui/ImGuiEngine.cpp b/src/dusk/imgui/ImGuiEngine.cpp new file mode 100644 index 0000000000..4e2097cf3d --- /dev/null +++ b/src/dusk/imgui/ImGuiEngine.cpp @@ -0,0 +1,201 @@ +#include "ImGuiEngine.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include "dusk/logging.h" + +#ifdef IMGUI_ENABLE_FREETYPE +#include "misc/freetype/imgui_freetype.h" +#endif + +namespace dusk { +namespace { +std::string GetAssetPath(const char* assetName) { + const char* basePath = SDL_GetBasePath(); + if (basePath != nullptr && basePath[0] != '\0') { + return std::string(basePath) + "res/" + assetName; + } + return std::string("res/") + assetName; +} + +bool AssetExists(const std::string& path) { + SDL_PathInfo pathInfo{}; + return SDL_GetPathInfo(path.c_str(), &pathInfo) && pathInfo.type == SDL_PATHTYPE_FILE; +} +} // namespace + +ImFont* ImGuiEngine::fontNormal; +ImFont* ImGuiEngine::fontLarge; +ImTextureID ImGuiEngine::duskIcon; + +void ImGuiEngine_Initialize(float scale) { + ImGui::GetCurrentContext(); + ImGuiIO& io = ImGui::GetIO(); + io.Fonts->Clear(); + + const std::string fontPath = GetAssetPath("NotoMono-Regular.ttf"); + const bool hasFontFile = AssetExists(fontPath); + + ImFontConfig fontConfig{}; + fontConfig.SizePixels = std::floor(15.f * scale); + snprintf(static_cast(fontConfig.Name), sizeof(fontConfig.Name), + "Noto Mono Regular, %dpx", static_cast(fontConfig.SizePixels)); + ImGuiEngine::fontNormal = + hasFontFile ? + io.Fonts->AddFontFromFileTTF(fontPath.c_str(), fontConfig.SizePixels, &fontConfig) : + nullptr; + if (ImGuiEngine::fontNormal == nullptr) { + if (hasFontFile) { + DuskLog.warn("Failed to load font '{}': {}", fontPath, SDL_GetError()); + } + ImGuiEngine::fontNormal = io.Fonts->AddFontDefault(&fontConfig); + } + + fontConfig.SizePixels = std::floor(26.f * scale); +#ifdef IMGUI_ENABLE_FREETYPE + fontConfig.FontBuilderFlags |= ImGuiFreeTypeBuilderFlags_Bold; + snprintf(static_cast(fontConfig.Name), sizeof(fontConfig.Name), "Noto Mono Bold, %dpx", + static_cast(fontConfig.SizePixels)); +#else + snprintf(static_cast(fontConfig.Name), sizeof(fontConfig.Name), + "Noto Mono Regular, %dpx", static_cast(fontConfig.SizePixels)); +#endif + ImGuiEngine::fontLarge = + hasFontFile ? + io.Fonts->AddFontFromFileTTF(fontPath.c_str(), fontConfig.SizePixels, &fontConfig) : + nullptr; + if (ImGuiEngine::fontLarge == nullptr) { + if (hasFontFile) { + DuskLog.warn("Failed to load font '{}': {}", fontPath, SDL_GetError()); + } + ImGuiEngine::fontLarge = io.Fonts->AddFontDefault(&fontConfig); + } + + auto& style = ImGui::GetStyle(); + style = {}; // Reset sizes + style.WindowPadding = ImVec2(15, 15); + style.WindowRounding = 5.0f; + style.FrameBorderSize = 1.f; + style.FramePadding = ImVec2(5, 5); + style.FrameRounding = 4.0f; + style.ItemSpacing = ImVec2(12, 8); + style.ItemInnerSpacing = ImVec2(8, 6); + style.IndentSpacing = 25.0f; + style.ScrollbarSize = 15.0f; + style.ScrollbarRounding = 9.0f; + style.GrabMinSize = 5.0f; + style.GrabRounding = 3.0f; + style.PopupBorderSize = 1.f; + style.PopupRounding = 7.0; + style.TabBorderSize = 1.f; + style.TabRounding = 3.f; + + auto* colors = style.Colors; + colors[ImGuiCol_Text] = ImVec4(0.95f, 0.96f, 0.98f, 1.00f); + colors[ImGuiCol_TextDisabled] = ImVec4(0.36f, 0.42f, 0.47f, 1.00f); + colors[ImGuiCol_WindowBg] = ImVec4(0.11f, 0.15f, 0.17f, 1.00f); + colors[ImGuiCol_ChildBg] = ImVec4(0.15f, 0.18f, 0.22f, 1.00f); + colors[ImGuiCol_PopupBg] = ImVec4(0.08f, 0.08f, 0.08f, 0.94f); + colors[ImGuiCol_Border] = ImVec4(0.08f, 0.10f, 0.12f, 1.00f); + colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_FrameBg] = ImVec4(0.20f, 0.25f, 0.29f, 1.00f); + colors[ImGuiCol_FrameBgHovered] = ImVec4(0.12f, 0.20f, 0.28f, 1.00f); + colors[ImGuiCol_FrameBgActive] = ImVec4(0.09f, 0.12f, 0.14f, 1.00f); + colors[ImGuiCol_TitleBg] = ImVec4(0.09f, 0.12f, 0.14f, 0.65f); + colors[ImGuiCol_TitleBgActive] = ImVec4(0.08f, 0.10f, 0.12f, 1.00f); + colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.00f, 0.00f, 0.00f, 0.51f); + colors[ImGuiCol_MenuBarBg] = ImVec4(0.15f, 0.18f, 0.22f, 1.00f); + colors[ImGuiCol_ScrollbarBg] = ImVec4(0.02f, 0.02f, 0.02f, 0.39f); + colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.20f, 0.25f, 0.29f, 1.00f); + colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.18f, 0.22f, 0.25f, 1.00f); + colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.09f, 0.21f, 0.31f, 1.00f); + colors[ImGuiCol_CheckMark] = ImVec4(0.28f, 0.56f, 1.00f, 1.00f); + colors[ImGuiCol_SliderGrab] = ImVec4(0.28f, 0.56f, 1.00f, 1.00f); + colors[ImGuiCol_SliderGrabActive] = ImVec4(0.37f, 0.61f, 1.00f, 1.00f); + colors[ImGuiCol_Button] = ImVec4(0.20f, 0.25f, 0.29f, 1.00f); + colors[ImGuiCol_ButtonHovered] = ImVec4(0.28f, 0.56f, 1.00f, 1.00f); + colors[ImGuiCol_ButtonActive] = ImVec4(0.06f, 0.53f, 0.98f, 1.00f); + colors[ImGuiCol_Header] = ImVec4(0.20f, 0.25f, 0.29f, 0.55f); + colors[ImGuiCol_HeaderHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.80f); + colors[ImGuiCol_HeaderActive] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_Separator] = ImVec4(0.20f, 0.25f, 0.29f, 1.00f); + colors[ImGuiCol_SeparatorHovered] = ImVec4(0.10f, 0.40f, 0.75f, 0.78f); + colors[ImGuiCol_SeparatorActive] = ImVec4(0.10f, 0.40f, 0.75f, 1.00f); + colors[ImGuiCol_ResizeGrip] = ImVec4(0.26f, 0.59f, 0.98f, 0.25f); + colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.67f); + colors[ImGuiCol_ResizeGripActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.95f); + colors[ImGuiCol_Tab] = ImVec4(0.11f, 0.15f, 0.17f, 1.00f); + colors[ImGuiCol_TabHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.80f); + colors[ImGuiCol_TabActive] = ImVec4(0.20f, 0.25f, 0.29f, 1.00f); + colors[ImGuiCol_TabUnfocused] = ImVec4(0.11f, 0.15f, 0.17f, 1.00f); + colors[ImGuiCol_TabUnfocusedActive] = ImVec4(0.11f, 0.15f, 0.17f, 1.00f); + colors[ImGuiCol_PlotLines] = ImVec4(0.61f, 0.61f, 0.61f, 1.00f); + colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f); + colors[ImGuiCol_PlotHistogram] = ImVec4(0.06f, 0.53f, 0.98f, 1.00f); + colors[ImGuiCol_PlotHistogramHovered] = ImVec4(0.28f, 0.56f, 1.00f, 1.00f); + colors[ImGuiCol_TextSelectedBg] = ImVec4(0.26f, 0.59f, 0.98f, 0.35f); + colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f); + colors[ImGuiCol_NavHighlight] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f); + colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f); + colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.35f); + + style.ScaleAllSizes(scale); +} + +Icon GetIcon() { + const std::string iconPath = GetAssetPath("icon.png"); + if (!AssetExists(iconPath)) { + return {}; + } + + SDL_Surface* loadedSurface = SDL_LoadPNG(iconPath.c_str()); + if (loadedSurface == nullptr) { + DuskLog.warn("Failed to load icon '{}': {}", iconPath, SDL_GetError()); + return {}; + } + + SDL_Surface* rgbaSurface = SDL_ConvertSurface(loadedSurface, SDL_PIXELFORMAT_RGBA32); + SDL_DestroySurface(loadedSurface); + if (rgbaSurface == nullptr) { + DuskLog.warn("Failed to convert icon '{}': {}", iconPath, SDL_GetError()); + return {}; + } + + const auto iconWidth = static_cast(rgbaSurface->w); + const auto iconHeight = static_cast(rgbaSurface->h); + const size_t rowSize = static_cast(iconWidth) * 4; + const size_t size = rowSize * static_cast(iconHeight); + auto ptr = std::make_unique(size); + for (uint32_t row = 0; row < iconHeight; ++row) { + const auto* src = static_cast(rgbaSurface->pixels) + + static_cast(row) * static_cast(rgbaSurface->pitch); + auto* dst = ptr.get() + static_cast(row) * rowSize; + std::memcpy(dst, src, rowSize); + } + + SDL_DestroySurface(rgbaSurface); + return Icon{ + std::move(ptr), + size, + iconWidth, + iconHeight, + }; +} + +void ImGuiEngine_AddTextures() { + auto icon = GetIcon(); + if (icon.data == nullptr || icon.width == 0 || icon.height == 0) { + ImGuiEngine::duskIcon = 0; + return; + } + + ImGuiEngine::duskIcon = aurora_imgui_add_texture(icon.width, icon.height, icon.data.get()); +} +} // namespace dusk diff --git a/src/dusk/imgui/ImGuiEngine.hpp b/src/dusk/imgui/ImGuiEngine.hpp new file mode 100644 index 0000000000..18e49c2a47 --- /dev/null +++ b/src/dusk/imgui/ImGuiEngine.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include +#include "imgui.h" +#include "misc/cpp/imgui_stdlib.h" + +namespace dusk { +class ImGuiEngine { +public: + static ImFont* fontNormal; + static ImFont* fontLarge; + static ImTextureID duskIcon; +}; + +void ImGuiEngine_Initialize(float scale); +void ImGuiEngine_AddTextures(); + +struct Icon { + std::unique_ptr data; + size_t size; + uint32_t width; + uint32_t height; +}; +Icon GetIcon(); +} // namespace dusk diff --git a/src/dusk/imgui/ImGuiMapLoader.cpp b/src/dusk/imgui/ImGuiMapLoader.cpp index 8d11c0673b..4baf48bdab 100644 --- a/src/dusk/imgui/ImGuiMapLoader.cpp +++ b/src/dusk/imgui/ImGuiMapLoader.cpp @@ -17,7 +17,8 @@ namespace dusk { ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav; ImGui::SetNextWindowBgAlpha(0.65f); - ImGui::SetNextWindowSizeConstraints(ImVec2(300, 0), ImVec2(FLT_MAX, FLT_MAX)); + ImGui::SetNextWindowSizeConstraints(ImVec2(300.f * ImGuiScale(), 0), + ImVec2(FLT_MAX, FLT_MAX)); if (!ImGui::Begin("Map Loader", &m_showMapLoader, windowFlags)) { ImGui::End(); diff --git a/src/dusk/imgui/ImGuiMenuGame.cpp b/src/dusk/imgui/ImGuiMenuGame.cpp index 00b287cc41..398e64de3a 100644 --- a/src/dusk/imgui/ImGuiMenuGame.cpp +++ b/src/dusk/imgui/ImGuiMenuGame.cpp @@ -84,13 +84,14 @@ namespace dusk { } static void drawVirtualStick(const char* id, const ImVec2& stick) { - ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPos().x + 5, ImGui::GetCursorPos().y)); + float scale = ImGuiScale(); + ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPos().x + 5 * scale, ImGui::GetCursorPos().y)); - ImGui::BeginChild(id, ImVec2(80, 80)); + ImGui::BeginChild(id, ImVec2(80 * scale, 80 * scale)); ImDrawList* dl = ImGui::GetWindowDrawList(); ImVec2 p = ImGui::GetCursorScreenPos(); - float radius = ImGui::GetCurrentContext()->CurrentDpiScale * 30.0f; + float radius = 30.0f * scale; ImVec2 pos = ImVec2(p.x + radius, p.y + radius); constexpr ImU32 stickGray = IM_COL32(150, 150, 150, 255); @@ -98,7 +99,7 @@ namespace dusk { constexpr ImU32 red = IM_COL32(230, 0, 0, 255); dl->AddCircleFilled(pos, radius, stickGray, 8); - dl->AddCircleFilled(ImVec2(pos.x + stick.x * (radius), pos.y + -stick.y * (radius)), 3, red); + dl->AddCircleFilled(ImVec2(pos.x + stick.x * (radius), pos.y + -stick.y * (radius)), 3 * scale, red); ImGui::EndChild(); } @@ -139,12 +140,14 @@ namespace dusk { } } + float scale = ImGuiScale(); ImGuiWindowFlags windowFlags = ImGuiWindowFlags_NoResize | ImGuiWindowFlags_AlwaysAutoResize; ImGui::SetNextWindowBgAlpha(0.65f); - ImGui::SetNextWindowSizeConstraints(ImVec2(850, 400), ImVec2(850, 400)); + ImGui::SetNextWindowSizeConstraints(ImVec2(850 * scale, 400 * scale), + ImVec2(850 * scale, 400 * scale)); if (!ImGui::Begin("Controller Config", &m_showControllerConfig, windowFlags)) { ImGui::End(); @@ -225,9 +228,9 @@ namespace dusk { } // buttons panel - constexpr float uiButtonSize = 40; + const float uiButtonSize = 40 * scale; - ImGuiBeginGroupPanel("Buttons", ImVec2(150, 20)); + ImGuiBeginGroupPanel("Buttons", ImVec2(150 * scale, 20 * scale)); u32 buttonCount; PADButtonMapping* btnMappingList = PADGetButtonMappings(m_controllerConfig.m_selectedPort, &buttonCount); @@ -251,7 +254,7 @@ namespace dusk { dispName = fmt::format("{0}##-{1}", PADGetNativeButtonName(btnMappingList[i].nativeButton), i); } bool pressed = ImGui::Button(dispName.c_str(), - ImVec2(100.0f, 20.0f)); + ImVec2(100.0f * scale, 20.0f * scale)); if (pressed) { m_controllerConfig.m_isReading = true; @@ -268,7 +271,7 @@ namespace dusk { uint32_t axisCount; PADAxisMapping* axisMappingList = PADGetAxisMappings(m_controllerConfig.m_selectedPort, &axisCount); - ImGuiBeginGroupPanel("Triggers", ImVec2(150, 20)); + ImGuiBeginGroupPanel("Triggers", ImVec2(150 * scale, 20 * scale)); PADAxis triggers[] = {PAD_AXIS_TRIGGER_L, PAD_AXIS_TRIGGER_R}; if (axisMappingList != nullptr) { @@ -291,7 +294,7 @@ namespace dusk { dispName = fmt::format("{0}##-{1}", PADGetNativeAxisName(axisMappingList[trigger].nativeAxis), trigger); } bool pressed = ImGui::Button(dispName.c_str(), - ImVec2(100.0f, 20.0f)); + ImVec2(100.0f * scale, 20.0f * scale)); if (pressed) { m_controllerConfig.m_isReading = true; @@ -308,7 +311,7 @@ namespace dusk { int port = m_controllerConfig.m_selectedPort; // main stick panel - ImGuiBeginGroupPanel("Control Stick", ImVec2(150, 20)); + ImGuiBeginGroupPanel("Control Stick", ImVec2(150 * scale, 20 * scale)); drawVirtualStick("##mainStick", ImVec2{ mDoCPd_c::getStickX(port), mDoCPd_c::getStickY(port) }); @@ -345,7 +348,7 @@ namespace dusk { dispName = fmt::format("{0}##-{1}", PADGetNativeButtonName(axisMappingList[axis].nativeButton), axis); } } - bool pressed = ImGui::Button(dispName.c_str(), ImVec2(100.0f, 20.0f)); + bool pressed = ImGui::Button(dispName.c_str(), ImVec2(100.0f * scale, 20.0f * scale)); if (pressed) { m_controllerConfig.m_isReading = true; @@ -372,7 +375,7 @@ namespace dusk { ImGui::SameLine(); // sub stick panel - ImGuiBeginGroupPanel("C Stick", ImVec2(150, 20)); + ImGuiBeginGroupPanel("C Stick", ImVec2(150 * scale, 20 * scale)); drawVirtualStick("##subStick", ImVec2{ mDoCPd_c::getSubStickX(port), mDoCPd_c::getSubStickY(port) }); @@ -409,7 +412,7 @@ namespace dusk { dispName = fmt::format("{0}##-{1}", PADGetNativeButtonName(axisMappingList[axis].nativeButton), axis); } } - bool pressed = ImGui::Button(fmt::format("{0}##sub{1}", dispName, label).c_str(), ImVec2(100.0f, 20.0f)); + bool pressed = ImGui::Button(fmt::format("{0}##sub{1}", dispName, label).c_str(), ImVec2(100.0f * scale, 20.0f * scale)); if (pressed) { m_controllerConfig.m_isReading = true; @@ -434,7 +437,7 @@ namespace dusk { ImGui::SameLine(); // Triggers Panel - ImGuiBeginGroupPanel("Triggers", ImVec2(150, 20)); + ImGuiBeginGroupPanel("Triggers", ImVec2(150 * scale, 20 * scale)); if (deadZones != nullptr) { ImGui::Text("L Threshold"); @@ -460,7 +463,7 @@ namespace dusk { ImGui::SameLine(); // Options panel - ImGuiBeginGroupPanel("Options", ImVec2(150, 20)); + ImGuiBeginGroupPanel("Options", ImVec2(150 * scale, 20 * scale)); if (deadZones != nullptr) { ImGui::Checkbox("Enable Dead Zones", &deadZones->useDeadzones); diff --git a/src/dusk/imgui/ImGuiMenuTools.cpp b/src/dusk/imgui/ImGuiMenuTools.cpp index 40bb3fa1ec..ca855a00cb 100644 --- a/src/dusk/imgui/ImGuiMenuTools.cpp +++ b/src/dusk/imgui/ImGuiMenuTools.cpp @@ -131,6 +131,9 @@ namespace dusk { BytesToString(stats->lastVertSize + stats->lastUniformSize + stats->lastIndexSize + stats->lastStorageSize + stats->lastTextureUploadSize))); + + // TODO: persist to config + ShowCornerContextMenu(m_debugOverlayCorner, m_cameraOverlayCorner); } ImGui::End(); } diff --git a/src/dusk/imgui/ImGuiMenuTools.hpp b/src/dusk/imgui/ImGuiMenuTools.hpp index 66c820dd78..42fcbdbe42 100644 --- a/src/dusk/imgui/ImGuiMenuTools.hpp +++ b/src/dusk/imgui/ImGuiMenuTools.hpp @@ -38,7 +38,7 @@ namespace dusk { private: bool m_showDebugOverlay = false; - int m_debugOverlayCorner = 0; // top-left + int m_debugOverlayCorner = 2; // bottom-left bool m_showCameraOverlay = false; int m_cameraOverlayCorner = 3; diff --git a/src/m_Do/m_Do_main.cpp b/src/m_Do/m_Do_main.cpp index 03ff6b8c0f..3c51875bd5 100644 --- a/src/m_Do/m_Do_main.cpp +++ b/src/m_Do/m_Do_main.cpp @@ -48,6 +48,7 @@ #include "dusk/logging.h" #include "dusk/time.h" #include "dusk/main.h" +#include "dusk/imgui/ImGuiEngine.hpp" #include #include @@ -225,6 +226,10 @@ static AuroraBackend ParseAuroraBackend(const std::string& value) { exit(1); } +static void aurora_imgui_init_callback(const AuroraWindowSize* size) { + dusk::ImGuiEngine_Initialize(size->scale); +} + // ========================================================================= // PC ENTRY POINT // ========================================================================= @@ -270,6 +275,7 @@ int game_main(int argc, char* argv[]) { config.mem1Size = 256 * 1024 * 1024; config.mem2Size = 24 * 1024 * 1024; config.allowJoystickBackgroundEvents = true; + config.imGuiInitCallback = &aurora_imgui_init_callback; auroraInfo = aurora_initialize(argc, argv, &config); From 1e2e40c52a5b6041c075ade3a0dee77ab149a5f7 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Fri, 3 Apr 2026 17:09:29 -0600 Subject: [PATCH 189/350] Rescale imgui on display scale change --- src/m_Do/m_Do_main.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/m_Do/m_Do_main.cpp b/src/m_Do/m_Do_main.cpp index 3c51875bd5..185ca8543c 100644 --- a/src/m_Do/m_Do_main.cpp +++ b/src/m_Do/m_Do_main.cpp @@ -157,6 +157,9 @@ void main01(void) { case AURORA_WINDOW_RESIZED: mDoGph_gInf_c::setWindowSize(event->windowSize); break; + case AURORA_DISPLAY_SCALE_CHANGED: + dusk::ImGuiEngine_Initialize(event->windowSize.scale); + break; case AURORA_EXIT: goto exit; } From 82425d9bf6b611f3f27a17ce082e06724a5fabaa Mon Sep 17 00:00:00 2001 From: Luke Street Date: Fri, 3 Apr 2026 17:28:32 -0600 Subject: [PATCH 190/350] Add pipeline progress overlay --- src/dusk/imgui/ImGuiConsole.cpp | 36 +++++++++++++++++++++++++++++++-- src/dusk/imgui/ImGuiConsole.hpp | 4 +++- src/m_Do/m_Do_graphic.cpp | 6 +++++- 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/src/dusk/imgui/ImGuiConsole.cpp b/src/dusk/imgui/ImGuiConsole.cpp index 9bfdb71d38..ae7f87c681 100644 --- a/src/dusk/imgui/ImGuiConsole.cpp +++ b/src/dusk/imgui/ImGuiConsole.cpp @@ -174,7 +174,7 @@ namespace dusk { ImGuiConsole::ImGuiConsole() {} - void ImGuiConsole::draw() { + void ImGuiConsole::PreDraw() { if (!m_isLaunchInitialized) { m_toasts.emplace_back("Press F1 to toggle menu"s, 5.f); m_isLaunchInitialized = true; @@ -182,7 +182,6 @@ namespace dusk { if (CheckMenuViewToggle(ImGuiKey_F1, m_isHidden)) { ShowToasts(); - m_menuTools.afterDraw(); return; } @@ -200,8 +199,13 @@ namespace dusk { ImGui::EndMainMenuBar(); } + ShowToasts(); + } + + void ImGuiConsole::PostDraw() { m_menuTools.afterDraw(); + ShowPipelineProgress(); } bool ImGuiConsole::CheckMenuViewToggle(ImGuiKey key, bool& active) { @@ -274,4 +278,32 @@ namespace dusk { m_toasts.pop_front(); } } + + void ImGuiConsole::ShowPipelineProgress() { + const auto* stats = aurora_get_stats(); + const u32 queuedPipelines = stats->queuedPipelines; + if (queuedPipelines == 0) { + return; + } + const u32 createdPipelines = stats->createdPipelines; + const u32 totalPipelines = queuedPipelines + createdPipelines; + + const auto* viewport = ImGui::GetMainViewport(); + const auto padding = viewport->WorkPos.y + 10.f; + const auto halfWidth = viewport->GetWorkCenter().x; + ImGui::SetNextWindowPos(ImVec2{halfWidth, padding}, ImGuiCond_Always, ImVec2{0.5f, 0.f}); + ImGui::SetNextWindowSize(ImVec2{halfWidth, 0.f}, ImGuiCond_Always); + ImGui::SetNextWindowBgAlpha(0.65f); + ImGui::Begin("Pipelines", nullptr, + ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing); + const auto percent = static_cast(createdPipelines) / static_cast(totalPipelines); + const auto progressStr = fmt::format("Processing pipelines: {} / {}", createdPipelines, totalPipelines); + const auto textSize = ImGui::CalcTextSize(progressStr.data(), progressStr.data() + progressStr.size()); + ImGui::NewLine(); + ImGui::SameLine(ImGui::GetWindowWidth() / 2.f - textSize.x + textSize.x / 2.f); + ImGuiStringViewText(progressStr); + ImGui::ProgressBar(percent); + ImGui::End(); + } } diff --git a/src/dusk/imgui/ImGuiConsole.hpp b/src/dusk/imgui/ImGuiConsole.hpp index db67c2a1cf..e12cc81844 100644 --- a/src/dusk/imgui/ImGuiConsole.hpp +++ b/src/dusk/imgui/ImGuiConsole.hpp @@ -14,7 +14,8 @@ namespace dusk { class ImGuiConsole { public: ImGuiConsole(); - void draw(); + void PreDraw(); + void PostDraw(); ImGuiMenuTools::CollisionViewSettings& getCollisionViewSettings() { return m_menuTools.getCollisionViewSettings(); @@ -39,6 +40,7 @@ private: ImGuiMenuEnhancements m_menuEnhancements; void ShowToasts(); + void ShowPipelineProgress(); }; extern ImGuiConsole g_imguiConsole; diff --git a/src/m_Do/m_Do_graphic.cpp b/src/m_Do/m_Do_graphic.cpp index 06d6efd4eb..9bbbecbee8 100644 --- a/src/m_Do/m_Do_graphic.cpp +++ b/src/m_Do/m_Do_graphic.cpp @@ -1634,6 +1634,10 @@ int mDoGph_Painter() { if (wn != 0) sDiagLoggedWindow = true; } +#if TARGET_PC + dusk::g_imguiConsole.PreDraw(); +#endif + #if DEBUG drawHeapMap(); #endif @@ -2244,7 +2248,7 @@ int mDoGph_Painter() { #endif #if TARGET_PC - dusk::g_imguiConsole.draw(); + dusk::g_imguiConsole.PostDraw(); #endif mDoGph_gInf_c::endRender(); From 4ebd734e6eb91a9599edf2ee30f6a6be8db6f7b4 Mon Sep 17 00:00:00 2001 From: Irastris Date: Fri, 3 Apr 2026 21:53:39 -0400 Subject: [PATCH 191/350] Fix Windows builds --- src/dusk/imgui/ImGuiConsole.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/dusk/imgui/ImGuiConsole.cpp b/src/dusk/imgui/ImGuiConsole.cpp index ae7f87c681..6a06fce882 100644 --- a/src/dusk/imgui/ImGuiConsole.cpp +++ b/src/dusk/imgui/ImGuiConsole.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -14,6 +15,7 @@ #include "JSystem/JUtility/JUTGamePad.h" #if _WIN32 +#define NOMINMAX #include "Windows.h" #endif From 3d9b4b0ff52a0108c861a92e7a00058017e3824a Mon Sep 17 00:00:00 2001 From: madeline Date: Fri, 3 Apr 2026 19:54:57 -0700 Subject: [PATCH 192/350] better gitignore --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index ae7fa75384..a381014599 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,7 @@ compile_commands.json # ISOs *.iso + +*.ini +*.controller +pipeline_cache.bin From 3b895609d30fe7a70aa571810eb208f659ec7a4c Mon Sep 17 00:00:00 2001 From: Max Roncace Date: Thu, 2 Apr 2026 23:58:38 -0400 Subject: [PATCH 193/350] Refactor Dusk settings out to common interface used by game and UI code --- files.cmake | 1 + include/dusk/settings.h | 71 ++++++++++++++++++++++++ include/m_Do/m_Do_controller_pad.h | 7 +-- src/d/actor/d_a_alink.cpp | 6 +- src/d/actor/d_a_alink_demo.inc | 6 +- src/d/actor/d_a_alink_dusk.cpp | 5 +- src/d/actor/d_a_alink_hvyboots.inc | 4 +- src/d/d_bg_s.cpp | 21 +++---- src/d/d_camera.cpp | 4 +- src/d/d_cc_s.cpp | 17 +++--- src/d/d_kankyo.cpp | 4 +- src/dusk/imgui/ImGuiConsole.hpp | 6 +- src/dusk/imgui/ImGuiMenuEnhancements.cpp | 45 +++++++-------- src/dusk/imgui/ImGuiMenuEnhancements.hpp | 12 ---- src/dusk/imgui/ImGuiMenuGame.cpp | 24 ++++---- src/dusk/imgui/ImGuiMenuGame.hpp | 10 ---- src/dusk/imgui/ImGuiMenuTools.cpp | 17 +++--- src/dusk/imgui/ImGuiMenuTools.hpp | 15 ----- src/dusk/settings.cpp | 66 ++++++++++++++++++++++ src/m_Do/m_Do_graphic.cpp | 4 +- 20 files changed, 215 insertions(+), 130 deletions(-) create mode 100644 include/dusk/settings.h create mode 100644 src/dusk/settings.cpp diff --git a/files.cmake b/files.cmake index e5b470dde2..278ecdb815 100644 --- a/files.cmake +++ b/files.cmake @@ -1341,6 +1341,7 @@ set(DUSK_FILES src/dusk/extras.c src/dusk/extras.cpp src/dusk/globals.cpp + src/dusk/settings.cpp #src/dusk/m_Do_ext_dusk.cpp src/dusk/imgui/ImGuiConsole.hpp src/dusk/imgui/ImGuiConsole.cpp diff --git a/include/dusk/settings.h b/include/dusk/settings.h new file mode 100644 index 0000000000..daba1287d8 --- /dev/null +++ b/include/dusk/settings.h @@ -0,0 +1,71 @@ +#ifndef DUSK_CONFIG_H +#define DUSK_CONFIG_H + +namespace dusk { + +// Persistent user settings + +struct UserSettings { + // Program settings + + struct { + // Video + bool enableFullscreen; + } video; + + struct { + // Audio + float masterVolume; + float mainMusicVolume; + float subMusicVolume; + float soundEffectsVolume; + float fanfareVolume; + } audio; + + // Game settings + + struct { + // QoL + bool enableQuickTransform; + + // Preferences + bool enableMirrorMode; + bool invertCameraXAxis; + + // Graphics + bool enableBloom; + bool useWaterProjectionOffset; + + // Cheats + bool enableFastIronBoots; + + // Technical + bool restoreWiiGlitches; + } game; +}; + +UserSettings& getSettings(); + +// Transient settings + +struct CollisionViewSettings { + bool enableTerrainView; + bool enableWireframe; + bool enableAtView; + bool enableTgView; + bool enableCoView; + float terrainViewOpacity; + float colliderViewOpacity; + float drawRange; +}; + +struct TransientSettings { + CollisionViewSettings collisionView; +}; + +TransientSettings& getTransientSettings(); + +} + +#endif // DUSK_CONFIG_H + diff --git a/include/m_Do/m_Do_controller_pad.h b/include/m_Do/m_Do_controller_pad.h index b8cee23ca3..e187efd17d 100644 --- a/include/m_Do/m_Do_controller_pad.h +++ b/include/m_Do/m_Do_controller_pad.h @@ -3,8 +3,7 @@ #include "JSystem/JUtility/JUTGamePad.h" #include "SSystem/SComponent/c_API_controller_pad.h" - -#include "dusk/imgui/ImGuiMenuEnhancements.hpp" +#include "dusk/settings.h" // Controller Ports 1 - 4 enum { PAD_1, PAD_2, PAD_3, PAD_4 }; @@ -57,7 +56,7 @@ public: static s16 getStickAngle3D(u32 pad) { #if TARGET_PC - if (dusk::ImGuiMenuEnhancements::m_enhancements.mirrorMode) { + if (dusk::getSettings().game.enableMirrorMode) { return -getCpadInfo(pad).mMainStickAngle; } else { return getCpadInfo(pad).mMainStickAngle; @@ -69,7 +68,7 @@ public: static f32 getSubStickX3D(u32 pad) { #if TARGET_PC - if (dusk::ImGuiMenuEnhancements::m_enhancements.mirrorMode) { + if (dusk::getSettings().game.enableMirrorMode) { return -getCpadInfo(pad).mCStickPosX; } else { return getCpadInfo(pad).mCStickPosX; diff --git a/src/d/actor/d_a_alink.cpp b/src/d/actor/d_a_alink.cpp index 2c5bcf46cd..48269ddc56 100644 --- a/src/d/actor/d_a_alink.cpp +++ b/src/d/actor/d_a_alink.cpp @@ -7510,7 +7510,7 @@ void daAlink_c::setBlendMoveAnime(f32 i_morf) { BOOL sp24 = checkEventRun(); BOOL sp20 = checkBootsMoveAnime(1); #if TARGET_PC - if (dusk::ImGuiMenuEnhancements::m_enhancements.fastIronBoots) { + if (dusk::getSettings().game.enableFastIronBoots) { sp20 = FALSE; } #endif @@ -9475,7 +9475,7 @@ void daAlink_c::setStickData() { mHeavySpeedMultiplier = mpHIO->mItem.mIronBoots.m.mInputFactor; } #if TARGET_PC - if (dusk::ImGuiMenuEnhancements::m_enhancements.fastIronBoots) { + if (dusk::getSettings().game.enableFastIronBoots) { mHeavySpeedMultiplier = 1.0f; } #endif @@ -9487,7 +9487,7 @@ void daAlink_c::setStickData() { mHeavySpeedMultiplier = mpHIO->mItem.mIronBoots.m.mWaterInputFactor; } #if TARGET_PC - if (dusk::ImGuiMenuEnhancements::m_enhancements.fastIronBoots) { + if (dusk::getSettings().game.enableFastIronBoots) { 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 7acccbde13..39387582cb 100644 --- a/src/d/actor/d_a_alink_demo.inc +++ b/src/d/actor/d_a_alink_demo.inc @@ -23,8 +23,6 @@ #include "d/actor/d_a_npc_tkc.h" #include -#include "dusk/imgui/ImGuiMenuEnhancements.hpp" - BOOL daAlink_c::checkEventRun() const { return dComIfGp_event_runCheck() || checkPlayerDemoMode(); } @@ -4301,7 +4299,7 @@ bool daAlink_c::checkAcceptWarp() { */ if (mLinkAcch.ChkGroundHit() && !checkModeFlg(MODE_PLAYER_FLY) #if TARGET_PC - && (dusk::ImGuiMenuEnhancements::m_enhancements.restoreWiiGlitches || !checkNoResetFlg0(FLG0_WATER_IN_MOVE)) + && (dusk::getSettings().game.restoreWiiGlitches || !checkNoResetFlg0(FLG0_WATER_IN_MOVE)) #elif VERSION != VERSION_WII_USA_R0 && !checkNoResetFlg0(FLG0_WATER_IN_MOVE) #endif @@ -4312,7 +4310,7 @@ bool daAlink_c::checkAcceptWarp() { */ if ( #if TARGET_PC - (dusk::ImGuiMenuEnhancements::m_enhancements.restoreWiiGlitches || !getSlidePolygon(&plane)) && + (dusk::getSettings().game.restoreWiiGlitches || !getSlidePolygon(&plane)) && #elif VERSION != VERSION_WII_USA_R0 !getSlidePolygon(&plane) && #endif diff --git a/src/d/actor/d_a_alink_dusk.cpp b/src/d/actor/d_a_alink_dusk.cpp index 47501d4f3d..83a5d592a5 100644 --- a/src/d/actor/d_a_alink_dusk.cpp +++ b/src/d/actor/d_a_alink_dusk.cpp @@ -3,10 +3,9 @@ #include "d/d_meter2.h" #include "d/d_meter2_draw.h" #include "d/d_meter2_info.h" -#include "dusk/imgui/ImGuiMenuEnhancements.hpp" void daAlink_c::handleQuickTransform() { - if (!dusk::ImGuiMenuEnhancements::m_enhancements.quickTransform) { + if (!dusk::getSettings().game.enableQuickTransform) { return; } @@ -70,4 +69,4 @@ void daAlink_c::handleQuickTransform() { OSReport("Running quick transform!"); procCoMetamorphoseInit(); -} \ No newline at end of file +} diff --git a/src/d/actor/d_a_alink_hvyboots.inc b/src/d/actor/d_a_alink_hvyboots.inc index ed52ffcb36..6f7fa8da40 100644 --- a/src/d/actor/d_a_alink_hvyboots.inc +++ b/src/d/actor/d_a_alink_hvyboots.inc @@ -6,8 +6,6 @@ #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); @@ -351,7 +349,7 @@ int daAlink_c::procMagneBootsFly() { */ if (dComIfG_Bgsp().ChkPolySafe(mPolyInfo2) #if TARGET_PC - && (dusk::ImGuiMenuEnhancements::m_enhancements.restoreWiiGlitches || checkEquipHeavyBoots()) + && (dusk::getSettings().game.restoreWiiGlitches || checkEquipHeavyBoots()) #elif PLATFORM_GCN || VERSION == VERSION_WII_KOR && checkEquipHeavyBoots() #endif diff --git a/src/d/d_bg_s.cpp b/src/d/d_bg_s.cpp index f218424d30..f48c58fead 100644 --- a/src/d/d_bg_s.cpp +++ b/src/d/d_bg_s.cpp @@ -10,12 +10,14 @@ #include "d/d_bg_w.h" #include "d/d_com_inf_game.h" #include "f_op/f_op_actor_mng.h" -#include "dusk/offset_ptr.h" #include "d/d_debug_viewer.h" #include "d/d_bg_s_capt_poly.h" -#include "dusk/imgui/ImGuiConsole.hpp" +#if TARGET_PC +#include "dusk/offset_ptr.h" +#include "dusk/settings.h" +#endif #if DEBUG int g_ground_counter; @@ -619,8 +621,8 @@ static int poly_draw(dBgS_CaptPoly* capt, cBgD_Vtx_t* vtxList, int v0, int v1, i GXColor wall_color = {0, 0xFF, 0, 0xFF}; #if TARGET_PC - dusk::ImGuiMenuTools::CollisionViewSettings collisionViewSettings = dusk::g_imguiConsole.getCollisionViewSettings(); - f32 view_opacity = 255 * (collisionViewSettings.m_terrainViewOpacity / 100.0f); + const auto& collisionViewSettings = dusk::getTransientSettings().collisionView; + f32 view_opacity = 255 * (collisionViewSettings.terrainViewOpacity / 100.0f); ground_color.a = view_opacity; roof_color.a = view_opacity; wall_color.a = view_opacity; @@ -658,16 +660,16 @@ void dBgS::Draw() { cBgS::Draw(); #if TARGET_PC - #define IMGUI_TOGGLE_HIO_FLAG(status, flag) \ + #define DUSK_TOGGLE_HIO_FLAG(status, flag) \ if (status) { \ s_InsideHio.m_flags |= flag; \ } else { \ s_InsideHio.m_flags &= ~flag; \ } - dusk::ImGuiMenuTools::CollisionViewSettings collisionViewSettings = dusk::g_imguiConsole.getCollisionViewSettings(); - IMGUI_TOGGLE_HIO_FLAG(collisionViewSettings.m_enableTerrainView, dBgS_InsideHIO::FLAG_DISP_POLY_e); - IMGUI_TOGGLE_HIO_FLAG(collisionViewSettings.m_enableWireframe, dBgS_InsideHIO::FLAG_WHITE_WIRE_e); + const auto& collisionViewSettings = dusk::getTransientSettings().collisionView; + DUSK_TOGGLE_HIO_FLAG(collisionViewSettings.enableTerrainView, dBgS_InsideHIO::FLAG_DISP_POLY_e); + DUSK_TOGGLE_HIO_FLAG(collisionViewSettings.enableWireframe, dBgS_InsideHIO::FLAG_WHITE_WIRE_e); #endif if (s_InsideHio.ChkDispPoly()) { @@ -680,8 +682,7 @@ void dBgS::Draw() { f32 var_f31 = fabsf(s_InsideHio.m_p0.x); #if TARGET_PC - dusk::ImGuiMenuTools::CollisionViewSettings collisionViewSettings = dusk::g_imguiConsole.getCollisionViewSettings(); - var_f31 = collisionViewSettings.m_drawRange; + var_f31 = collisionViewSettings.drawRange; #endif min.x = player->current.pos.x - var_f31; diff --git a/src/d/d_camera.cpp b/src/d/d_camera.cpp index 59ed1d2c11..6540c74ec9 100644 --- a/src/d/d_camera.cpp +++ b/src/d/d_camera.cpp @@ -28,8 +28,6 @@ #include "d/d_debug_camera.h" #endif -#include "dusk/imgui/ImGuiMenuEnhancements.hpp" - namespace { static f32 limitf(f32 value, f32 min, f32 max) { @@ -766,7 +764,7 @@ void dCamera_c::updatePad() { var_f31 = mDoCPd_c::getSubStickX3D(mPadID); #if TARGET_PC - if (dusk::ImGuiMenuEnhancements::m_enhancements.invertCameraXAxis) { + if (dusk::getSettings().game.invertCameraXAxis) { var_f31 *= -1.0f; } #endif diff --git a/src/d/d_cc_s.cpp b/src/d/d_cc_s.cpp index ab71f08aaa..2a19de3dda 100644 --- a/src/d/d_cc_s.cpp +++ b/src/d/d_cc_s.cpp @@ -9,8 +9,9 @@ #include "d/d_com_inf_game.h" #include "d/d_jnt_col.h" #include "f_op/f_op_actor_mng.h" - -#include "dusk/imgui/ImGuiConsole.hpp" +#if TARGET_PC +#include "dusk/settings.h" +#endif class dCcS_HIO : public JORReflexible { public: @@ -770,19 +771,19 @@ void dCcS::Draw() { #endif #if TARGET_PC -#define IMGUI_TOGGLE_HIO_FLAG(status, flag) \ +#define DUSK_TOGGLE_HIO_FLAG(status, flag) \ if (status) { \ s_Hio.m_flags |= flag; \ } else { \ s_Hio.m_flags &= ~flag; \ } - dusk::ImGuiMenuTools::CollisionViewSettings collisionViewSettings = dusk::g_imguiConsole.getCollisionViewSettings(); - IMGUI_TOGGLE_HIO_FLAG(collisionViewSettings.m_enableAtView, dCcS_HIO::FLAG_AT_ON_e); - IMGUI_TOGGLE_HIO_FLAG(collisionViewSettings.m_enableTgView, dCcS_HIO::FLAG_TG_ON_e); - IMGUI_TOGGLE_HIO_FLAG(collisionViewSettings.m_enableCoView, dCcS_HIO::FLAG_CO_ON_e); + const auto& collisionViewSettings = dusk::getTransientSettings().collisionView; + DUSK_TOGGLE_HIO_FLAG(collisionViewSettings.enableAtView, dCcS_HIO::FLAG_AT_ON_e); + DUSK_TOGGLE_HIO_FLAG(collisionViewSettings.enableTgView, dCcS_HIO::FLAG_TG_ON_e); + DUSK_TOGGLE_HIO_FLAG(collisionViewSettings.enableCoView, dCcS_HIO::FLAG_CO_ON_e); - f32 view_opacity = 255 * (collisionViewSettings.m_colliderViewOpacity / 100.0f); + f32 view_opacity = 255 * (collisionViewSettings.colliderViewOpacity / 100.0f); #endif if (s_Hio.CheckAtOn()) { diff --git a/src/d/d_kankyo.cpp b/src/d/d_kankyo.cpp index a61219ffac..eb1900dc4e 100644 --- a/src/d/d_kankyo.cpp +++ b/src/d/d_kankyo.cpp @@ -32,7 +32,7 @@ #include #include #if TARGET_PC -#include "dusk/imgui/ImGuiConsole.hpp" +#include "dusk/settings.h" #endif static void GxXFog_set(); @@ -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::ImGuiMenuEnhancements::m_enhancements.useWaterProjectionOffset ? -0.01f : 0.0f, 0.0f); + dusk::getSettings().game.useWaterProjectionOffset ? -0.01f : 0.0f, 0.0f); #else -0.01f, 0.0f); #endif diff --git a/src/dusk/imgui/ImGuiConsole.hpp b/src/dusk/imgui/ImGuiConsole.hpp index e12cc81844..a7c433be4d 100644 --- a/src/dusk/imgui/ImGuiConsole.hpp +++ b/src/dusk/imgui/ImGuiConsole.hpp @@ -17,11 +17,7 @@ public: void PreDraw(); void PostDraw(); - ImGuiMenuTools::CollisionViewSettings& getCollisionViewSettings() { - return m_menuTools.getCollisionViewSettings(); - } - - static bool CheckMenuViewToggle(ImGuiKey key, bool& active); + static bool CheckMenuViewToggle(ImGuiKey key, bool& active); private: struct Toast { diff --git a/src/dusk/imgui/ImGuiMenuEnhancements.cpp b/src/dusk/imgui/ImGuiMenuEnhancements.cpp index 5ea315d8ec..eaf33a1908 100644 --- a/src/dusk/imgui/ImGuiMenuEnhancements.cpp +++ b/src/dusk/imgui/ImGuiMenuEnhancements.cpp @@ -1,46 +1,45 @@ -#include "fmt/format.h" #include "imgui.h" -#include "aurora/gfx.h" -#include "ImGuiConsole.hpp" #include "ImGuiMenuEnhancements.hpp" -#include + +#include "dusk/settings.h" namespace dusk { - EnhancementsSettings ImGuiMenuEnhancements::m_enhancements = { - .fastIronBoots = false, - .invertCameraXAxis = false, - .restoreWiiGlitches = false, - .enableBloom = true, - .useWaterProjectionOffset = false, - .mirrorMode = 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::Checkbox("Quick Transform (R+Y)", &getSettings().game.enableQuickTransform); + ImGui::EndMenu(); + } + + if (ImGui::BeginMenu("Preferences")) { + ImGui::Checkbox("Mirror Mode", &getSettings().game.enableMirrorMode); + ImGui::Checkbox("Invert Camera X Axis", &getSettings().game.invertCameraXAxis); + ImGui::EndMenu(); } if (ImGui::BeginMenu("Graphics")) { - ImGui::Checkbox("Native Bloom", &m_enhancements.enableBloom); - ImGui::Checkbox("Water Projection Offset", &m_enhancements.useWaterProjectionOffset); + ImGui::Checkbox("Native Bloom", &getSettings().game.enableBloom); + ImGui::Checkbox("Water Projection Offset", &getSettings().game.useWaterProjectionOffset); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Adds GC-specific -0.01 transS offset\n" "that causes ~6px ghost artifacts in water reflections"); } - ImGui::Checkbox("Mirror Mode", &m_enhancements.mirrorMode); ImGui::EndMenu(); } - if (ImGui::BeginMenu("Restorations")) { - ImGui::Checkbox("Restore Wii 1.0 Glitches", &m_enhancements.restoreWiiGlitches); + if (ImGui::BeginMenu("Cheats")) { + ImGui::Checkbox("Fast Iron Boots", &getSettings().game.enableFastIronBoots); + + ImGui::EndMenu(); + } + + if (ImGui::BeginMenu("Technical")) { + ImGui::Checkbox("Restore Wii 1.0 Glitches", &getSettings().game.restoreWiiGlitches); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Restores patched glitches from Wii USA 1.0, the first released version"); } @@ -48,10 +47,6 @@ namespace dusk { ImGui::EndMenu(); } - if (ImGui::BeginMenu("Cheats")) { - ImGui::EndMenu(); - } - ImGui::EndMenu(); } } diff --git a/src/dusk/imgui/ImGuiMenuEnhancements.hpp b/src/dusk/imgui/ImGuiMenuEnhancements.hpp index 0238317ce4..f40baaad65 100644 --- a/src/dusk/imgui/ImGuiMenuEnhancements.hpp +++ b/src/dusk/imgui/ImGuiMenuEnhancements.hpp @@ -8,22 +8,10 @@ #include "imgui.h" namespace dusk { - struct EnhancementsSettings { - bool fastIronBoots; - bool invertCameraXAxis; - bool quickTransform; - bool restoreWiiGlitches; - bool enableBloom; - bool useWaterProjectionOffset; - bool mirrorMode; - }; - class ImGuiMenuEnhancements { public: ImGuiMenuEnhancements(); void draw(); - - static EnhancementsSettings m_enhancements; }; } diff --git a/src/dusk/imgui/ImGuiMenuGame.cpp b/src/dusk/imgui/ImGuiMenuGame.cpp index 398e64de3a..e3aeb4e136 100644 --- a/src/dusk/imgui/ImGuiMenuGame.cpp +++ b/src/dusk/imgui/ImGuiMenuGame.cpp @@ -1,15 +1,13 @@ #include "fmt/format.h" #include "imgui.h" -#include "aurora/gfx.h" #include "ImGuiConsole.hpp" #include "ImGuiMenuGame.hpp" #include #include "JSystem/JUtility/JUTGamePad.h" -#include "d/actor/d_a_alink.h" #include "dusk/audio/DuskAudioSystem.h" -#include "m_Do/m_Do_audio.h" +#include "dusk/settings.h" #include "m_Do/m_Do_controller_pad.h" namespace dusk { @@ -25,8 +23,8 @@ namespace dusk { if (ImGui::BeginMenu("Graphics")) { if (ImGui::MenuItem("Toggle Fullscreen", "F11")) { - m_fullscreen = !m_fullscreen; - VISetWindowFullscreen(m_fullscreen); + getSettings().video.enableFullscreen = !getSettings().video.enableFullscreen; + VISetWindowFullscreen(getSettings().video.enableFullscreen); } ImGui::EndMenu(); @@ -34,28 +32,28 @@ namespace dusk { if (ImGui::BeginMenu("Audio")) { ImGui::Text("Master Volume"); - ImGui::SliderFloat("##m_masterVolume", &m_audioSettings.m_masterVolume, 0.0f, 1.0f, ""); + ImGui::SliderFloat("##masterVolume", &getSettings().audio.masterVolume, 0.0f, 1.0f, ""); /* // TODO: implement additional settings ImGui::Text("Main Music Volume"); - ImGui::SliderFloat("##m_mainMusicVolume", &m_audioSettings.m_mainMusicVolume, 0.0f, 1.0f, ""); + ImGui::SliderFloat("##mainMusicVolume", &getSettings().audio.mainMusicVolume, 0.0f, 1.0f, ""); ImGui::Text("Sub Music Volume"); - ImGui::SliderFloat("##m_subMusicVolume", &m_audioSettings.m_subMusicVolume, 0.0f, 1.0f, ""); + ImGui::SliderFloat("##subMusicVolume", &getSettings().audio.subMusicVolume, 0.0f, 1.0f, ""); ImGui::Text("Sound Effects Volume"); - ImGui::SliderFloat("##m_soundEffectsVolume", &m_audioSettings.m_soundEffectsVolume, 0.0f, 1.0f, ""); + ImGui::SliderFloat("##soundEffectsVolume", &getSettings().audio.soundEffectsVolume, 0.0f, 1.0f, ""); ImGui::Text("Fanfare Volume"); - ImGui::SliderFloat("##m_fanfareVolume", &m_audioSettings.m_fanfareVolume, 0.0f, 1.0f, ""); + ImGui::SliderFloat("##fanfareVolume", &getSettings().audio.fanfareVolume, 0.0f, 1.0f, ""); Z2AudioMgr* audioMgr = Z2AudioMgr::getInterface(); if (audioMgr != nullptr) { } */ - audio::SetMasterVolume(m_audioSettings.m_masterVolume); + audio::SetMasterVolume(getSettings().audio.masterVolume); ImGui::EndMenu(); } @@ -78,8 +76,8 @@ namespace dusk { } if (ImGui::IsKeyPressed(ImGuiKey_F11)) { - m_fullscreen = !m_fullscreen; - VISetWindowFullscreen(m_fullscreen); + getSettings().video.enableFullscreen = !getSettings().video.enableFullscreen; + VISetWindowFullscreen(getSettings().video.enableFullscreen); } } diff --git a/src/dusk/imgui/ImGuiMenuGame.hpp b/src/dusk/imgui/ImGuiMenuGame.hpp index 7df0a00973..6d7ed614aa 100644 --- a/src/dusk/imgui/ImGuiMenuGame.hpp +++ b/src/dusk/imgui/ImGuiMenuGame.hpp @@ -17,14 +17,6 @@ namespace dusk { void windowControllerConfig(); private: - struct { - float m_masterVolume = 1.0f; - float m_mainMusicVolume = 1.0f; - float m_subMusicVolume = 1.0f; - float m_soundEffectsVolume = 1.0f; - float m_fanfareVolume = 1.0f; - } m_audioSettings; - struct { int m_selectedPort = 0; bool m_isReading = false; @@ -33,8 +25,6 @@ namespace dusk { int m_pendingPort = -1; } m_controllerConfig; - bool m_fullscreen = false; - bool m_showControllerConfig = false; bool m_showInputViewer = false; diff --git a/src/dusk/imgui/ImGuiMenuTools.cpp b/src/dusk/imgui/ImGuiMenuTools.cpp index ca855a00cb..4fbe7745b7 100644 --- a/src/dusk/imgui/ImGuiMenuTools.cpp +++ b/src/dusk/imgui/ImGuiMenuTools.cpp @@ -23,16 +23,17 @@ namespace dusk { ImGui::Separator(); + auto& collisionView = getTransientSettings().collisionView; if (ImGui::BeginMenu("Collision View")) { - ImGui::Checkbox("Enable Terrain view", &m_collisionViewSettings.m_enableTerrainView); - ImGui::Checkbox("Enable wireframe view", &m_collisionViewSettings.m_enableWireframe); - ImGui::SliderFloat("Opacity##terrain", &m_collisionViewSettings.m_terrainViewOpacity, 0.0f, 100.0f); - ImGui::SliderFloat("Draw Range", &m_collisionViewSettings.m_drawRange, 0.0f, 1000.0f); + ImGui::Checkbox("Enable Terrain view", &collisionView.enableTerrainView); + ImGui::Checkbox("Enable wireframe view", &collisionView.enableWireframe); + ImGui::SliderFloat("Opacity##terrain", &collisionView.terrainViewOpacity, 0.0f, 100.0f); + ImGui::SliderFloat("Draw Range", &collisionView.drawRange, 0.0f, 1000.0f); ImGui::Separator(); - ImGui::Checkbox("Enable Attack Collider view", &m_collisionViewSettings.m_enableAtView); - ImGui::Checkbox("Enable Target Collider view", &m_collisionViewSettings.m_enableTgView); - ImGui::Checkbox("Enable Push Collider view", &m_collisionViewSettings.m_enableCoView); - ImGui::SliderFloat("Opacity##colliders", &m_collisionViewSettings.m_colliderViewOpacity, 0.0f, 100.0f); + ImGui::Checkbox("Enable Attack Collider view", &collisionView.enableAtView); + ImGui::Checkbox("Enable Target Collider view", &collisionView.enableTgView); + ImGui::Checkbox("Enable Push Collider view", &collisionView.enableCoView); + ImGui::SliderFloat("Opacity##colliders", &collisionView.colliderViewOpacity, 0.0f, 100.0f); ImGui::EndMenu(); } diff --git a/src/dusk/imgui/ImGuiMenuTools.hpp b/src/dusk/imgui/ImGuiMenuTools.hpp index 42fcbdbe42..35604e0c93 100644 --- a/src/dusk/imgui/ImGuiMenuTools.hpp +++ b/src/dusk/imgui/ImGuiMenuTools.hpp @@ -10,17 +10,6 @@ namespace dusk { class ImGuiMenuTools { public: - struct CollisionViewSettings { - bool m_enableTerrainView = false; - bool m_enableWireframe = false; - bool m_enableAtView = false; - bool m_enableTgView = false; - bool m_enableCoView = false; - float m_terrainViewOpacity = 50.0f; - float m_colliderViewOpacity = 50.0f; - float m_drawRange = 100.0f; - }; - ImGuiMenuTools(); void draw(); void afterDraw(); @@ -34,8 +23,6 @@ namespace dusk { void ShowPlayerInfo(); void ShowAudioDebug(); - CollisionViewSettings& getCollisionViewSettings() { return m_collisionViewSettings; } - private: bool m_showDebugOverlay = false; int m_debugOverlayCorner = 2; // bottom-left @@ -67,8 +54,6 @@ namespace dusk { bool m_isDevelopmentMode = false; bool m_showPlayerInfo = false; - CollisionViewSettings m_collisionViewSettings; - bool m_showSaveEditor = false; ImGuiSaveEditor m_saveEditor; }; diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp new file mode 100644 index 0000000000..9a5bb20c8c --- /dev/null +++ b/src/dusk/settings.cpp @@ -0,0 +1,66 @@ +#include "dusk/settings.h" + +namespace dusk { + +UserSettings g_userSettings = { + // Program settings + + // Video + .video = { + .enableFullscreen = false, + }, + + // Audio + .audio = { + .masterVolume = 1.0f, + .mainMusicVolume = 1.0f, + .subMusicVolume = 1.0f, + .soundEffectsVolume = 1.0f, + .fanfareVolume = 1.0f, + }, + + // Game settings + .game = { + // Quality of Life + .enableQuickTransform = false, + + // Preferences + .enableMirrorMode = false, + .invertCameraXAxis = false, + + // Graphics + .enableBloom = true, + .useWaterProjectionOffset = false, + + // Cheats + .enableFastIronBoots = false, + + // Technical + .restoreWiiGlitches = false, + } +}; + +UserSettings& getSettings() { + return g_userSettings; +} + +// Transient settings + +static TransientSettings g_transientSettings = { + .collisionView = { + .enableTerrainView = false, + .enableWireframe = false, + .enableAtView = false, + .enableTgView = false, + .enableCoView = false, + .terrainViewOpacity = 50.0f, + .colliderViewOpacity = 50.0f, + .drawRange = 100.0f, + }, +}; + +TransientSettings& getTransientSettings() { + return g_transientSettings; +} + +} diff --git a/src/m_Do/m_Do_graphic.cpp b/src/m_Do/m_Do_graphic.cpp index 9bbbecbee8..a7a09a8d87 100644 --- a/src/m_Do/m_Do_graphic.cpp +++ b/src/m_Do/m_Do_graphic.cpp @@ -1163,7 +1163,7 @@ void mDoGph_gInf_c::bloom_c::remove() { void mDoGph_gInf_c::bloom_c::draw() { #if TARGET_PC - if (!dusk::ImGuiMenuEnhancements::m_enhancements.enableBloom) { + if (!dusk::getSettings().game.enableBloom) { return; } #endif @@ -2117,7 +2117,7 @@ int mDoGph_Painter() { #endif #if TARGET_PC - if (dusk::ImGuiMenuEnhancements::m_enhancements.mirrorMode) + if (dusk::getSettings().game.enableMirrorMode) #elif PLATFORM_WII if (data_8053a730) #endif From dc558f94dfd2dc619367dc577f8802b491307817 Mon Sep 17 00:00:00 2001 From: Max Roncace Date: Fri, 3 Apr 2026 00:23:09 -0400 Subject: [PATCH 194/350] Move hotkey bindings to separate header --- include/dusk/hotkeys.h | 19 +++++++++++++++++++ src/dusk/imgui/ImGuiMenuGame.cpp | 5 +++-- src/dusk/imgui/ImGuiMenuTools.cpp | 13 +++++++------ 3 files changed, 29 insertions(+), 8 deletions(-) create mode 100644 include/dusk/hotkeys.h diff --git a/include/dusk/hotkeys.h b/include/dusk/hotkeys.h new file mode 100644 index 0000000000..ba187a544c --- /dev/null +++ b/include/dusk/hotkeys.h @@ -0,0 +1,19 @@ +#ifndef DUSK_HOTKEYS_H +#define DUSK_HOTKEYS_H + +namespace dusk::hotkeys { + +constexpr const char* DO_RESET = "Ctrl+R"; + +constexpr const char* TOGGLE_FULLSCREEN = "F11"; + +constexpr const char* SHOW_PROCESS_MANAGEMENT = "F2"; +constexpr const char* SHOW_DEBUG_OVERLAY = "F3"; +constexpr const char* SHOW_HEAP_VIEWER = "F4"; +constexpr const char* SHOW_STUB_LOG = "F5"; +constexpr const char* SHOW_CAMERA_DEBUG = "F6"; +constexpr const char* SHOW_AUDIO_DEBUG = "F7"; + +} + +#endif // DUSK_HOTKEYS_H diff --git a/src/dusk/imgui/ImGuiMenuGame.cpp b/src/dusk/imgui/ImGuiMenuGame.cpp index e3aeb4e136..91aa0314df 100644 --- a/src/dusk/imgui/ImGuiMenuGame.cpp +++ b/src/dusk/imgui/ImGuiMenuGame.cpp @@ -7,6 +7,7 @@ #include "JSystem/JUtility/JUTGamePad.h" #include "dusk/audio/DuskAudioSystem.h" +#include "dusk/hotkeys.h" #include "dusk/settings.h" #include "m_Do/m_Do_controller_pad.h" @@ -15,14 +16,14 @@ namespace dusk { void ImGuiMenuGame::draw() { if (ImGui::BeginMenu("Game")) { - if (ImGui::MenuItem("Reset", "Ctrl+R")) { + if (ImGui::MenuItem("Reset", hotkeys::DO_RESET)) { JUTGamePad::C3ButtonReset::sResetSwitchPushing = true; } ImGui::Separator(); if (ImGui::BeginMenu("Graphics")) { - if (ImGui::MenuItem("Toggle Fullscreen", "F11")) { + if (ImGui::MenuItem("Toggle Fullscreen", hotkeys::TOGGLE_FULLSCREEN)) { getSettings().video.enableFullscreen = !getSettings().video.enableFullscreen; VISetWindowFullscreen(getSettings().video.enableFullscreen); } diff --git a/src/dusk/imgui/ImGuiMenuTools.cpp b/src/dusk/imgui/ImGuiMenuTools.cpp index 4fbe7745b7..20e92768a6 100644 --- a/src/dusk/imgui/ImGuiMenuTools.cpp +++ b/src/dusk/imgui/ImGuiMenuTools.cpp @@ -2,6 +2,7 @@ #include "imgui.h" #include "aurora/gfx.h" +#include "dusk/hotkeys.h" #include "ImGuiConsole.hpp" #include "ImGuiMenuTools.hpp" @@ -37,15 +38,15 @@ namespace dusk { ImGui::EndMenu(); } - ImGui::MenuItem("Process Management", "F2", &m_showProcessManagement); - ImGui::MenuItem("Debug Overlay", "F3", &m_showDebugOverlay); - ImGui::MenuItem("Heap Viewer", "F4", &m_showHeapOverlay); - ImGui::MenuItem("Stub Log", "F5", &m_showStubLog); - ImGui::MenuItem("Debug Camera", "F6", &m_showCameraOverlay); + ImGui::MenuItem("Process Management", hotkeys::SHOW_PROCESS_MANAGEMENT, &m_showProcessManagement); + ImGui::MenuItem("Debug Overlay", hotkeys::SHOW_DEBUG_OVERLAY, &m_showDebugOverlay); + ImGui::MenuItem("Heap Viewer", hotkeys::SHOW_HEAP_VIEWER, &m_showHeapOverlay); + ImGui::MenuItem("Stub Log", hotkeys::SHOW_STUB_LOG, &m_showStubLog); + ImGui::MenuItem("Debug Camera", hotkeys::SHOW_CAMERA_DEBUG, &m_showCameraOverlay); ImGui::MenuItem("Map Loader", nullptr, &m_showMapLoader); ImGui::MenuItem("Player Info", nullptr, &m_showPlayerInfo); ImGui::MenuItem("Save Editor", nullptr, &m_showSaveEditor); - ImGui::MenuItem("Audio Debug", "F7", &m_showAudioDebug); + ImGui::MenuItem("Audio Debug", hotkeys::SHOW_AUDIO_DEBUG, &m_showAudioDebug); ImGui::MenuItem("OSReport Force", nullptr, &OSReportReallyForceEnable); ImGui::EndMenu(); } From 50d06229c161c46087a2db15ff273d75ddb4690a Mon Sep 17 00:00:00 2001 From: Max Roncace Date: Tue, 17 Mar 2026 01:07:22 -0400 Subject: [PATCH 195/350] Add tweak to enable/disable TV settings screen --- include/d/d_s_name.h | 4 ++ include/dusk/settings.h | 1 + src/d/d_s_name.cpp | 77 ++++++++++++++++-------- src/dusk/imgui/ImGuiMenuEnhancements.cpp | 4 ++ src/dusk/settings.cpp | 1 + 5 files changed, 63 insertions(+), 24 deletions(-) diff --git a/include/d/d_s_name.h b/include/d/d_s_name.h index 73d3d703be..72a44c02de 100644 --- a/include/d/d_s_name.h +++ b/include/d/d_s_name.h @@ -48,6 +48,7 @@ public: void FileSelectClose(); void brightCheckOpen(); void brightCheck(); + void doPreLoadSetup(); void changeGameScene(); #if VERSION == VERSION_GCN_PAL @@ -70,6 +71,9 @@ private: /* 0x41E */ u8 mWaitTimer; /* 0x41F */ u8 field_0x41f; /* 0x420 */ u8 field_0x420; +#if TARGET_PC + bool mShowTvSettingsScreen; +#endif }; #endif /* D_S_D_S_NAME_H */ diff --git a/include/dusk/settings.h b/include/dusk/settings.h index daba1287d8..db49560fff 100644 --- a/include/dusk/settings.h +++ b/include/dusk/settings.h @@ -27,6 +27,7 @@ struct UserSettings { struct { // QoL bool enableQuickTransform; + bool hideTvSettingsScreen; // Preferences bool enableMirrorMode; diff --git a/src/d/d_s_name.cpp b/src/d/d_s_name.cpp index 5f22177654..e001d97c71 100644 --- a/src/d/d_s_name.cpp +++ b/src/d/d_s_name.cpp @@ -18,6 +18,12 @@ #include "f_op/f_op_overlap_mng.h" #include "dusk/memory.h" +#if TARGET_PC +#define SHOW_TV_SETTINGS_SCREEN (this->mShowTvSettingsScreen) +#else +#define SHOW_TV_SETTINGS_SCREEN (1) +#endif + static dSn_HIO_c g_snHIO; #if VERSION == VERSION_GCN_PAL @@ -293,13 +299,27 @@ void dScnName_c::FileSelectMain() { } void dScnName_c::FileSelectMainNormal() { +#if TARGET_PC + mShowTvSettingsScreen = !dusk::getSettings().game.hideTvSettingsScreen; +#endif + switch(dFs_c->isSelectEnd()) { case 1: - mWaitTimer = 15; - mDoGph_gInf_c::setFadeColor(*(JUtility::TColor*)&g_blackColor); - mDoGph_gInf_c::startFadeOut(15); + if (SHOW_TV_SETTINGS_SCREEN) { + mWaitTimer = 15; + mDoGph_gInf_c::setFadeColor(*(JUtility::TColor*)&g_blackColor); + mDoGph_gInf_c::startFadeOut(15); + } else { + mWaitTimer = 1; + } + mProc = dScnName_PROC_FileSelectClose; field_0x420 = 1; + + if (!SHOW_TV_SETTINGS_SCREEN) { + mDoAud_seStart(Z2SE_ENTER_GAME, NULL, 0, 0); + } + break; } } @@ -308,12 +328,17 @@ void dScnName_c::FileSelectClose() { mWaitTimer--; if (mWaitTimer == 0) { - mProc = dScnName_PROC_BrightCheckOpen; - mWaitTimer = 15; - mDrawProc = 1; - mDoGph_gInf_c::setFadeColor(*(JUtility::TColor*)&g_blackColor); - mDoGph_gInf_c::startFadeIn(15); - field_0x420 = 0; + if (SHOW_TV_SETTINGS_SCREEN) { + mProc = dScnName_PROC_BrightCheckOpen; + mWaitTimer = 15; + mDrawProc = 1; + mDoGph_gInf_c::setFadeColor(*(JUtility::TColor*)&g_blackColor); + mDoGph_gInf_c::startFadeIn(15); + field_0x420 = 0; + } else { + doPreLoadSetup(); + field_0x420 = 0; + } } } @@ -330,24 +355,28 @@ void dScnName_c::brightCheck() { mBrightCheck->_move(); if (mBrightCheck->isEnd()) { - dComIfGs_setSaveTotalTime(dComIfGs_getTotalTime()); - dComIfGs_setSaveStartTime(OSGetTime()); - mDoAud_bgmStop(45); - - field_0x41f = 0; - mProc = dScnName_PROC_ChangeGameScene; - - // Reset rupee "first-time collection" flags so the collection cutscene will play again - dComIfGs_offItemFirstBit(dItemNo_GREEN_RUPEE_e); - dComIfGs_offItemFirstBit(dItemNo_BLUE_RUPEE_e); - dComIfGs_offItemFirstBit(dItemNo_YELLOW_RUPEE_e); - dComIfGs_offItemFirstBit(dItemNo_RED_RUPEE_e); - dComIfGs_offItemFirstBit(dItemNo_PURPLE_RUPEE_e); - dComIfGs_offItemFirstBit(dItemNo_ORANGE_RUPEE_e); - dComIfGs_offItemFirstBit(dItemNo_SILVER_RUPEE_e); + doPreLoadSetup(); } } +void dScnName_c::doPreLoadSetup() { + dComIfGs_setSaveTotalTime(dComIfGs_getTotalTime()); + dComIfGs_setSaveStartTime(OSGetTime()); + mDoAud_bgmStop(45); + + field_0x41f = 0; + mProc = dScnName_PROC_ChangeGameScene; + + // Reset rupee "first-time collection" flags so the collection cutscene will play again + dComIfGs_offItemFirstBit(dItemNo_GREEN_RUPEE_e); + dComIfGs_offItemFirstBit(dItemNo_BLUE_RUPEE_e); + dComIfGs_offItemFirstBit(dItemNo_YELLOW_RUPEE_e); + dComIfGs_offItemFirstBit(dItemNo_RED_RUPEE_e); + dComIfGs_offItemFirstBit(dItemNo_PURPLE_RUPEE_e); + dComIfGs_offItemFirstBit(dItemNo_ORANGE_RUPEE_e); + dComIfGs_offItemFirstBit(dItemNo_SILVER_RUPEE_e); +} + void dScnName_c::changeGameScene() { if (!mDoRst::isReset() && !fopOvlpM_IsPeek()) { dComIfGs_gameStart(); diff --git a/src/dusk/imgui/ImGuiMenuEnhancements.cpp b/src/dusk/imgui/ImGuiMenuEnhancements.cpp index eaf33a1908..eed938de69 100644 --- a/src/dusk/imgui/ImGuiMenuEnhancements.cpp +++ b/src/dusk/imgui/ImGuiMenuEnhancements.cpp @@ -11,6 +11,10 @@ namespace dusk { if (ImGui::BeginMenu("Enhancements")) { if (ImGui::BeginMenu("Quality of Life")) { ImGui::Checkbox("Quick Transform (R+Y)", &getSettings().game.enableQuickTransform); + ImGui::Checkbox("Hide TV Settings Screen", &getSettings().game.hideTvSettingsScreen); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Hides the TV calibration screen shown when loading a save"); + } ImGui::EndMenu(); } diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index 9a5bb20c8c..d1ba67b585 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -23,6 +23,7 @@ UserSettings g_userSettings = { .game = { // Quality of Life .enableQuickTransform = false, + .hideTvSettingsScreen = false, // Preferences .enableMirrorMode = false, From 7180e9bd6fd31bdd19631a48c700de14c357f52b Mon Sep 17 00:00:00 2001 From: madeline Date: Fri, 3 Apr 2026 21:51:04 -0700 Subject: [PATCH 196/350] do resampling manually --- src/dusk/audio/DuskDsp.cpp | 176 ++++++++++++++++------------------ src/dusk/audio/DuskDsp.hpp | 13 ++- src/dusk/imgui/ImGuiAudio.cpp | 7 +- 3 files changed, 98 insertions(+), 98 deletions(-) diff --git a/src/dusk/audio/DuskDsp.cpp b/src/dusk/audio/DuskDsp.cpp index 7b9401d394..7cf1c44cd8 100644 --- a/src/dusk/audio/DuskDsp.cpp +++ b/src/dusk/audio/DuskDsp.cpp @@ -74,19 +74,6 @@ constexpr static int PitchToSampleRate(u16 value) { return static_cast(static_cast(SampleRate) * value / 4096); } -static void UpdateSampleRate(const JASDsp::TChannel& channel, ChannelAuxData& aux) { - auto sampleRate = PitchToSampleRate(channel.mPitch); - - const SDL_AudioSpec spec = { - SDL_AUDIO_S16, - 1, - sampleRate - }; - - SDL_SetAudioStreamFormat(aux.resampleStream, &spec, nullptr); - aux.prevPitch = channel.mPitch; -} - /** * Reset state for a DSP channel between independent playbacks. */ @@ -98,8 +85,9 @@ static void ResetChannel(JASDsp::TChannel& channel, ChannelAuxData& aux) { aux.hist0 = 0; aux.hist1 = 0; - SDL_ClearAudioStream(aux.resampleStream); - UpdateSampleRate(channel, aux); + aux.decodeBufCount = 0; + aux.resamplePos = 0.0; + aux.resamplePrev = 0; for (auto& volume : aux.prevVolume) { volume = NAN; @@ -196,15 +184,16 @@ static void ReadSampleData( } /** - * Read a single *contiguous* chunk of sample data from a channel, - * writes the samples to the channel's resampler stream. + * Read a single *contiguous* chunk of sample data from a channel into outBuf * - * @returns Amount of samples actually read. Can be greater than the amount requested. + * @returns Amount of samples written to outBuf. May be less than desiredSamples */ static int ReadChannelSamplesChunk( JASDsp::TChannel& channel, ChannelAuxData& aux, - int desiredSamples) { + int desiredSamples, + s16* outBuf, + int outBufSize) { assert(desiredSamples >= 0); @@ -249,61 +238,49 @@ static int ReadChannelSamplesChunk( channel.mSamplesLeft -= renderSamples; channel.mSamplePosition += renderSamples; - SDL_PutAudioStreamData( - aux.resampleStream, - renderData + skipSamples, - static_cast(renderSize - skipSamples * sizeof(u16))); + int outputCount = static_cast(renderSamples - skipSamples); + + // this should never be hit with the limits on pitch shift (i think) but just in case!! + outputCount = std::min(outputCount, outBufSize); + if (outputCount > 0) { + memcpy(outBuf, renderData + skipSamples, outputCount * sizeof(s16)); + } assert(curSamplePosition % channel.mSamplesPerBlock == 0 || channel.mSamplesLeft == 0); - return static_cast(renderSamples - skipSamples); + return outputCount; } /** - * Reads new audio channels from a DSP channel and writes them to the resampler stream. + * Fill decodeBuf with at least `needed` samples, fewer may be written if the channel has no loop and its data ends */ -static void SDLCALL ReadChannelSamples( - void *userdata, - SDL_AudioStream*, - int additional_amount, - int) { - - if (additional_amount == 0) { - return; - } - - const auto index = static_cast(reinterpret_cast(userdata)); - auto& channel = JASDsp::CH_BUF[index]; - auto& aux = ChannelAux[index]; - - if (channel.mSamplesLeft == 0 && !channel.mLoopFlag) { - // May get called when we're out of data to read. - // This is expected, as we need to drain the resampler channel before we mark the channel as finished. - return; - } - - auto samplesRead = ReadChannelSamplesChunk(channel, aux, additional_amount); - additional_amount -= samplesRead; - - if (channel.mSamplesLeft == 0) { - // Reached end of buffer. - if (!channel.mLoopFlag) { - return; +static void FillDecodeBuf(JASDsp::TChannel& channel, ChannelAuxData& aux, int needed) { + while (aux.decodeBufCount < needed) { + if (channel.mSamplesLeft == 0) { + if (!channel.mLoopFlag) { + // we aren't a looping channel and there's no samples left, we out of this fuckin loop + break; + } else { + // we are looping, handle loop logic + channel.mSamplesLeft = channel.mEndSample - channel.mLoopStartSample; + channel.mSamplePosition = channel.mLoopStartSample; + aux.hist1 = channel.mpPenult; + aux.hist0 = channel.mpLast; + } } - channel.mSamplesLeft = channel.mEndSample - channel.mLoopStartSample; - channel.mSamplePosition = channel.mLoopStartSample; + int remainingDecodeSpace = ChannelAuxData::DECODE_BUF_SIZE - aux.decodeBufCount; + if (remainingDecodeSpace == 0) { + break; + } - aux.hist1 = channel.mpPenult; - aux.hist0 = channel.mpLast; + aux.decodeBufCount += ReadChannelSamplesChunk( + channel, aux, std::min(remainingDecodeSpace, needed - aux.decodeBufCount), + aux.decodeBuf + aux.decodeBufCount, remainingDecodeSpace + ); } - if (additional_amount >= 0) { - ReadChannelSamplesChunk(channel, aux, additional_amount); - } - - channel.mAramStreamPosition = channel.mWaveAramAddress - + ConvertSamplesToDataLength(channel, channel.mSamplePosition); + channel.mAramStreamPosition = channel.mWaveAramAddress + ConvertSamplesToDataLength(channel, channel.mSamplePosition); } /** @@ -422,57 +399,70 @@ static void RenderOutputChannel( prevVolume = targetVolume; } +/** + * Fetch, decode, resample, output + */ static void RenderChannel( JASDsp::TChannel& channel, ChannelAuxData& channelAux, OutputSubframe& subframe) { + if (channel.mResetFlag) { ResetChannel(channel, channelAux); - } else if (channelAux.prevPitch != channel.mPitch) { - UpdateSampleRate(channel, channelAux); } - DspSubframe audioLoadBuffer = {}; + // how many input samples we step per output sample, aka the resampling ratio + f32 step = (f32)PitchToSampleRate(channel.mPitch) / SampleRate; - int wantRead = sizeof(audioLoadBuffer); - auto read = SDL_GetAudioStreamData( - channelAux.resampleStream, - &audioLoadBuffer, - wantRead); + // how many input samples to resample to DSP_SUBFRAME_SIZE output samples + int needed = static_cast(channelAux.resamplePos + DSP_SUBFRAME_SIZE * step) + 2; - if (read < wantRead) { + FillDecodeBuf(channel, channelAux, needed); + + // source ran dry, channel is finished + if(channelAux.decodeBufCount < needed) { channel.mIsFinished = true; } - auto hasReadSamples = std::span(audioLoadBuffer).subspan(0, wantRead / sizeof(f32)); + DspSubframe audioLoadBuffer = {}; + f64 pos = channelAux.resamplePos; + s16 prev = channelAux.resamplePrev; + s16 next = channelAux.decodeBufCount > 0 ? channelAux.decodeBuf[0] : prev; + int srcIdx = 0; + + // linear resampling and f32 conversion + for (int i = 0; i < DSP_SUBFRAME_SIZE; i++) { + audioLoadBuffer[i] = static_cast(prev + pos * (next - prev)) / 32768.0f; + pos += step; + while (pos >= 1.0) { + pos -= 1.0; + prev = next; + srcIdx++; + next = srcIdx < channelAux.decodeBufCount ? channelAux.decodeBuf[srcIdx] : prev; + } + } + + // save resampler state for the next subframe, prevents popping on pitch change + channelAux.resamplePos = pos; + channelAux.resamplePrev = prev; + + // move any remaining samples in the decode buf to the beginning + int remainingDecodeBuf = channelAux.decodeBufCount - srcIdx; + if (remainingDecodeBuf > 0) { + memmove(channelAux.decodeBuf, channelAux.decodeBuf + srcIdx, remainingDecodeBuf * sizeof(s16)); + } + + channelAux.decodeBufCount = std::max(0, remainingDecodeBuf); + + auto hasReadSamples = std::span(audioLoadBuffer).subspan(0, DSP_SUBFRAME_SIZE); static_assert(OutputSubframe::NUM_CHANNELS == 2, "Keep RenderChannel in sync!"); RenderOutputChannel(channel, channelAux, OutputChannel::LEFT, hasReadSamples, subframe); - RenderOutputChannel(channel, channelAux,OutputChannel::RIGHT, hasReadSamples, subframe); + RenderOutputChannel(channel, channelAux, OutputChannel::RIGHT, hasReadSamples, subframe); } void dusk::audio::DspInit() { - constexpr SDL_AudioSpec srcSpec = { - SDL_AUDIO_S16, - 1, - SampleRate - }; - constexpr SDL_AudioSpec dstSpec = { - SDL_AUDIO_F32, - 1, - SampleRate - }; - - for (u32 i = 0; i < DSP_CHANNELS; i++) { - auto& aux = ChannelAux[i]; - aux.resampleStream = SDL_CreateAudioStream(&srcSpec, &dstSpec); - - SDL_SetAudioStreamGetCallback( - aux.resampleStream, - ReadChannelSamples, - reinterpret_cast(static_cast(i))); - } } void dusk::audio::ApplyVolume( diff --git a/src/dusk/audio/DuskDsp.hpp b/src/dusk/audio/DuskDsp.hpp index c84bb338db..35b6ccabc9 100644 --- a/src/dusk/audio/DuskDsp.hpp +++ b/src/dusk/audio/DuskDsp.hpp @@ -26,8 +26,6 @@ namespace dusk::audio { struct ChannelAuxData { s16 hist1; s16 hist0; - SDL_AudioStream* resampleStream; - u16 prevPitch; // Used for debugging tools. u32 resetCount; @@ -43,6 +41,17 @@ namespace dusk::audio { assert(channel < OutputChannel::OutputChannel_MAX); return prevVolume[static_cast(channel)]; } + + // buffer for decoding before resampling, size is chosen based on how many input samples we would need to fetch for the highest possible pitch + // to fill one subframe of output samples after resampling + static constexpr int DECODE_BUF_SIZE = 2048; + s16 decodeBuf[DECODE_BUF_SIZE]; + int decodeBufCount; + + // basically stores our position between resamplePrev and decodeBuf[0] so we don't lose that fractional resampler position next subframe + f32 resamplePos; + // last consumed sample from decodeBuf + s16 resamplePrev; }; extern ChannelAuxData ChannelAux[DSP_CHANNELS]; diff --git a/src/dusk/imgui/ImGuiAudio.cpp b/src/dusk/imgui/ImGuiAudio.cpp index 707091b158..ae01f119f4 100644 --- a/src/dusk/imgui/ImGuiAudio.cpp +++ b/src/dusk/imgui/ImGuiAudio.cpp @@ -50,9 +50,10 @@ static void DisplayDspChannel(int i) { auto dolby = (channel.mAutoMixerPanDolby & 0xFF) / 127.5f; auto fxMix = (channel.mAutoMixerFxMix >> 8) / 127.5f; auto volume = VolumeFromU16(channel.mAutoMixerVolume); + auto pitch = channel.mPitch / 4096.0f; ImGui::Text( - "Auto mixer active (pan: %f, dolby: %f, fx: %f, volume: %f)", - pan, dolby, fxMix, volume); + "Auto mixer active (pan: %f, dolby: %f, fx: %f, volume: %f, pitch %f)", + pan, dolby, fxMix, volume, pitch); } else { ImGui::Text( "Bus connect: %04X(%.2f),%04X(%.2f),%04X(%.2f),%04X(%.2f),%04X(%.2f),%04X(%.2f)", @@ -249,4 +250,4 @@ void dusk::ImGuiMenuTools::ShowAudioDebug() { } ImGui::End(); -} \ No newline at end of file +} From ef35ae882b2f3bd1e95e525ff16fa5bab222fa76 Mon Sep 17 00:00:00 2001 From: Max Roncace Date: Sat, 4 Apr 2026 01:30:24 -0400 Subject: [PATCH 197/350] Fix crashes when entering Henna's shop --- .../JSystem/J3DGraphLoader/J3DMaterialFactory_v21.h | 4 ++-- .../JSystem/src/J3DGraphLoader/J3DMaterialFactory_v21.cpp | 4 ++-- src/d/actor/d_a_npc_henna.cpp | 8 ++++++++ 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/libs/JSystem/include/JSystem/J3DGraphLoader/J3DMaterialFactory_v21.h b/libs/JSystem/include/JSystem/J3DGraphLoader/J3DMaterialFactory_v21.h index 6b70201532..7df974b854 100644 --- a/libs/JSystem/include/JSystem/J3DGraphLoader/J3DMaterialFactory_v21.h +++ b/libs/JSystem/include/JSystem/J3DGraphLoader/J3DMaterialFactory_v21.h @@ -82,7 +82,7 @@ public: /* 0x00 */ u16 mMaterialNum; /* 0x04 */ J3DMaterialInitData_v21* mpMaterialInitData; - /* 0x08 */ u16* mpMaterialID; + /* 0x08 */ BE(u16)* mpMaterialID; /* 0x0C */ GXColor* mpMatColor; /* 0x10 */ u8* mpColorChanNum; /* 0x14 */ J3DColorChanInfo* mpColorChanInfo; @@ -91,7 +91,7 @@ public: /* 0x20 */ J3DTexCoord2Info* mpTexCoord2Info; /* 0x24 */ J3DTexMtxInfo* mpTexMtxInfo; /* 0x28 */ J3DTexMtxInfo* field_0x28; - /* 0x2C */ u16* mpTexNo; + /* 0x2C */ BE(u16)* mpTexNo; /* 0x30 */ GXCullMode* mpCullMode; /* 0x34 */ J3DTevOrderInfo* mpTevOrderInfo; /* 0x38 */ GXColorS10* mpTevColor; diff --git a/libs/JSystem/src/J3DGraphLoader/J3DMaterialFactory_v21.cpp b/libs/JSystem/src/J3DGraphLoader/J3DMaterialFactory_v21.cpp index 229309545a..5cf98498ff 100644 --- a/libs/JSystem/src/J3DGraphLoader/J3DMaterialFactory_v21.cpp +++ b/libs/JSystem/src/J3DGraphLoader/J3DMaterialFactory_v21.cpp @@ -10,7 +10,7 @@ J3DMaterialFactory_v21::J3DMaterialFactory_v21(J3DMaterialBlock_v21 const& i_block) { mMaterialNum = i_block.mMaterialNum; mpMaterialInitData = JSUConvertOffsetToPtr(&i_block, i_block.mpMaterialInitData); - mpMaterialID = JSUConvertOffsetToPtr(&i_block, i_block.mpMaterialID); + mpMaterialID = JSUConvertOffsetToPtr(&i_block, i_block.mpMaterialID); mpCullMode = JSUConvertOffsetToPtr(&i_block, i_block.mpCullMode); mpMatColor = JSUConvertOffsetToPtr(&i_block, i_block.mpMatColor); mpColorChanNum = JSUConvertOffsetToPtr(&i_block, i_block.mpColorChanNum); @@ -20,7 +20,7 @@ J3DMaterialFactory_v21::J3DMaterialFactory_v21(J3DMaterialBlock_v21 const& i_blo mpTexCoord2Info = JSUConvertOffsetToPtr(&i_block, i_block.mpTexCoord2Info); mpTexMtxInfo = JSUConvertOffsetToPtr(&i_block, i_block.mpTexMtxInfo); field_0x28 = JSUConvertOffsetToPtr(&i_block, i_block.field_0x38); - mpTexNo = JSUConvertOffsetToPtr(&i_block, i_block.mpTexNo); + mpTexNo = JSUConvertOffsetToPtr(&i_block, i_block.mpTexNo); mpTevOrderInfo = JSUConvertOffsetToPtr(&i_block, i_block.mpTevOrderInfo); mpTevColor = JSUConvertOffsetToPtr(&i_block, i_block.mpTevColor); mpTevKColor = JSUConvertOffsetToPtr(&i_block, i_block.mpTevKColor); diff --git a/src/d/actor/d_a_npc_henna.cpp b/src/d/actor/d_a_npc_henna.cpp index 59f13ceabe..339d5ee82c 100644 --- a/src/d/actor/d_a_npc_henna.cpp +++ b/src/d/actor/d_a_npc_henna.cpp @@ -2709,6 +2709,14 @@ static int daNpc_Henna_Create(fopAc_ac_c* i_this) { } i_this->attention_info.flags = (fopAc_AttnFlag_SPEAK_e | fopAc_AttnFlag_TALK_e); a_this->action = 0; + +#if AVOID_UB + a_this->field_0x654 = 0; + a_this->field_0x658 = 0; + a_this->field_0x662 = 0; + a_this->field_0x664 = 0; +#endif + fopAcM_SetMtx(i_this, a_this->mpMorf->getModel()->getBaseTRMtx()); lbl_82_bss_90 = 0; if (a_this->arg0 == 1) { From f340def9af33220e0c2f620148adf3ac994a8103 Mon Sep 17 00:00:00 2001 From: madeline Date: Fri, 3 Apr 2026 22:33:38 -0700 Subject: [PATCH 198/350] fix getDopplerPower fixes #213 --- libs/JSystem/include/JSystem/JAudio2/JAUAudibleParam.h | 2 +- src/Z2AudioLib/Z2Audience.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/JSystem/include/JSystem/JAudio2/JAUAudibleParam.h b/libs/JSystem/include/JSystem/JAudio2/JAUAudibleParam.h index 50f5b45f09..44d4137d9b 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JAUAudibleParam.h +++ b/libs/JSystem/include/JSystem/JAudio2/JAUAudibleParam.h @@ -10,7 +10,7 @@ */ struct JAUAudibleParam { f32 getDopplerPower() const { - return (u32)((*(u8*)&field_0x0.raw >> 4) & 0xf) * (1.0f / 15.0f); + return field_0x0.bytes.b0_0 * (1.0f / 15.0f); } union { diff --git a/src/Z2AudioLib/Z2Audience.cpp b/src/Z2AudioLib/Z2Audience.cpp index 9adab4084f..5bb91eef31 100644 --- a/src/Z2AudioLib/Z2Audience.cpp +++ b/src/Z2AudioLib/Z2Audience.cpp @@ -802,7 +802,7 @@ f32 Z2Audience::calcFxMix_(f32 param_0, int distVolBit) const { f32 Z2Audience::calcPitch_(Z2AudibleChannel* channel, const Z2Audible* audible, const Z2AudioCamera* camera) const { JAUAudibleParam audParam = *audible->getAudibleParam(); - if ((*(u8*)&audParam.field_0x0.raw >> 4) & 0xf) { + if (audParam.field_0x0.bytes.b0_0) { JGeometry::TVec3 aTStack_4c; aTStack_4c.normalize(channel->field_0x14.field_0x00); JAUAudibleParam audParam = *audible->getAudibleParam(); From bc5c5ceeb7ade1db98c11f41cde9dde953aa981c Mon Sep 17 00:00:00 2001 From: Max Roncace Date: Sat, 4 Apr 2026 01:36:16 -0400 Subject: [PATCH 199/350] Fix a couple more BE issues in J3DMaterialFactory_v21 --- .../include/JSystem/J3DGraphLoader/J3DMaterialFactory_v21.h | 4 ++-- libs/JSystem/src/J3DGraphLoader/J3DMaterialFactory_v21.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/libs/JSystem/include/JSystem/J3DGraphLoader/J3DMaterialFactory_v21.h b/libs/JSystem/include/JSystem/J3DGraphLoader/J3DMaterialFactory_v21.h index 7df974b854..93d4fb9b7d 100644 --- a/libs/JSystem/include/JSystem/J3DGraphLoader/J3DMaterialFactory_v21.h +++ b/libs/JSystem/include/JSystem/J3DGraphLoader/J3DMaterialFactory_v21.h @@ -92,9 +92,9 @@ public: /* 0x24 */ J3DTexMtxInfo* mpTexMtxInfo; /* 0x28 */ J3DTexMtxInfo* field_0x28; /* 0x2C */ BE(u16)* mpTexNo; - /* 0x30 */ GXCullMode* mpCullMode; + /* 0x30 */ BE(GXCullMode)* mpCullMode; /* 0x34 */ J3DTevOrderInfo* mpTevOrderInfo; - /* 0x38 */ GXColorS10* mpTevColor; + /* 0x38 */ BE(GXColorS10)* mpTevColor; /* 0x3C */ GXColor* mpTevKColor; /* 0x40 */ u8* mpTevStageNum; /* 0x44 */ J3DTevStageInfo* mpTevStageInfo; diff --git a/libs/JSystem/src/J3DGraphLoader/J3DMaterialFactory_v21.cpp b/libs/JSystem/src/J3DGraphLoader/J3DMaterialFactory_v21.cpp index 5cf98498ff..2566708f63 100644 --- a/libs/JSystem/src/J3DGraphLoader/J3DMaterialFactory_v21.cpp +++ b/libs/JSystem/src/J3DGraphLoader/J3DMaterialFactory_v21.cpp @@ -11,7 +11,7 @@ J3DMaterialFactory_v21::J3DMaterialFactory_v21(J3DMaterialBlock_v21 const& i_blo mMaterialNum = i_block.mMaterialNum; mpMaterialInitData = JSUConvertOffsetToPtr(&i_block, i_block.mpMaterialInitData); mpMaterialID = JSUConvertOffsetToPtr(&i_block, i_block.mpMaterialID); - mpCullMode = JSUConvertOffsetToPtr(&i_block, i_block.mpCullMode); + mpCullMode = JSUConvertOffsetToPtr(&i_block, i_block.mpCullMode); mpMatColor = JSUConvertOffsetToPtr(&i_block, i_block.mpMatColor); mpColorChanNum = JSUConvertOffsetToPtr(&i_block, i_block.mpColorChanNum); mpColorChanInfo = JSUConvertOffsetToPtr(&i_block, i_block.mpColorChanInfo); @@ -22,7 +22,7 @@ J3DMaterialFactory_v21::J3DMaterialFactory_v21(J3DMaterialBlock_v21 const& i_blo field_0x28 = JSUConvertOffsetToPtr(&i_block, i_block.field_0x38); mpTexNo = JSUConvertOffsetToPtr(&i_block, i_block.mpTexNo); mpTevOrderInfo = JSUConvertOffsetToPtr(&i_block, i_block.mpTevOrderInfo); - mpTevColor = JSUConvertOffsetToPtr(&i_block, i_block.mpTevColor); + mpTevColor = JSUConvertOffsetToPtr(&i_block, i_block.mpTevColor); mpTevKColor = JSUConvertOffsetToPtr(&i_block, i_block.mpTevKColor); mpTevStageNum = JSUConvertOffsetToPtr(&i_block, i_block.mpTevStageNum); mpTevStageInfo = JSUConvertOffsetToPtr(&i_block, i_block.mpTevStageInfo); From 2b71c7f863046ad9efc8cb0dfeaaf4dc86620ce8 Mon Sep 17 00:00:00 2001 From: Max Roncace Date: Sat, 4 Apr 2026 01:41:04 -0400 Subject: [PATCH 200/350] Fix compile error in J3DMaterialFactory_v21 --- libs/JSystem/src/J3DGraphLoader/J3DMaterialFactory_v21.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/JSystem/src/J3DGraphLoader/J3DMaterialFactory_v21.cpp b/libs/JSystem/src/J3DGraphLoader/J3DMaterialFactory_v21.cpp index 2566708f63..f6cca25525 100644 --- a/libs/JSystem/src/J3DGraphLoader/J3DMaterialFactory_v21.cpp +++ b/libs/JSystem/src/J3DGraphLoader/J3DMaterialFactory_v21.cpp @@ -260,7 +260,7 @@ J3DGXColorS10 J3DMaterialFactory_v21::newTevColor(int i_idx, int i_no) const { J3DGXColorS10 dflt = defaultTevColor; J3DMaterialInitData_v21* mtl_init_data = &mpMaterialInitData[mpMaterialID[i_idx]]; if (mtl_init_data->mTevColorIdx[i_no] != 0xffff) { - return mpTevColor[mtl_init_data->mTevColorIdx[i_no]]; + return (GXColorS10)mpTevColor[mtl_init_data->mTevColorIdx[i_no]]; } else { return dflt; } From 6966f33a8bf948fcf8f6c9a88c9550891f5fa30c Mon Sep 17 00:00:00 2001 From: Irastris Date: Sat, 4 Apr 2026 01:52:48 -0400 Subject: [PATCH 201/350] Remove AuroraConfig configPath --- src/m_Do/m_Do_main.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/m_Do/m_Do_main.cpp b/src/m_Do/m_Do_main.cpp index 185ca8543c..b8eeba892e 100644 --- a/src/m_Do/m_Do_main.cpp +++ b/src/m_Do/m_Do_main.cpp @@ -272,7 +272,6 @@ int game_main(int argc, char* argv[]) { config.windowWidth = 608 * 2; config.windowHeight = 448 * 2; config.desiredBackend = ParseAuroraBackend(parsed_arg_options["backend"].as()); - config.configPath = "."; config.logCallback = &aurora_log_callback; config.logLevel = (AuroraLogLevel)parsed_arg_options["log-level"].as(); config.mem1Size = 256 * 1024 * 1024; From a0c361c6c9784021f8a34314337747253b1992ee Mon Sep 17 00:00:00 2001 From: madeline Date: Sat, 4 Apr 2026 01:38:28 -0700 Subject: [PATCH 202/350] reverb proof of concept --- CMakeLists.txt | 8 +- include/dusk/settings.h | 2 +- libs/freeverb/CMakeLists.txt | 13 ++ libs/freeverb/allpass.cpp | 36 +++++ libs/freeverb/allpass.hpp | 48 ++++++ libs/freeverb/comb.cpp | 48 ++++++ libs/freeverb/comb.hpp | 55 +++++++ libs/freeverb/denormals.h | 15 ++ libs/freeverb/revmodel.cpp | 252 +++++++++++++++++++++++++++++++ libs/freeverb/revmodel.hpp | 87 +++++++++++ libs/freeverb/tuning.h | 60 ++++++++ src/dusk/audio/DuskDsp.cpp | 54 +++++-- src/dusk/audio/DuskDsp.hpp | 4 + src/dusk/imgui/ImGuiMenuGame.cpp | 4 +- src/dusk/settings.cpp | 1 + 15 files changed, 668 insertions(+), 19 deletions(-) create mode 100644 libs/freeverb/CMakeLists.txt create mode 100644 libs/freeverb/allpass.cpp create mode 100644 libs/freeverb/allpass.hpp create mode 100644 libs/freeverb/comb.cpp create mode 100644 libs/freeverb/comb.hpp create mode 100644 libs/freeverb/denormals.h create mode 100644 libs/freeverb/revmodel.cpp create mode 100644 libs/freeverb/revmodel.hpp create mode 100644 libs/freeverb/tuning.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 248dfcf6b1..272ff76db3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,6 +17,8 @@ set(AURORA_ENABLE_DVD ON CACHE BOOL "Enable DVD API support" FORCE) set(AURORA_ENABLE_CARD ON CACHE BOOL "Enable CARD API support" FORCE) add_subdirectory(extern/aurora EXCLUDE_FROM_ALL) +add_subdirectory(libs/freeverb) + 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) @@ -108,14 +110,14 @@ target_include_directories(game_debug PUBLIC extern ${CMAKE_SOURCE_DIR}/build/${DUSK_TP_VERSION}/include build/${DUSK_TP_VERSION}/include) -target_link_libraries(game_debug PUBLIC aurora::core aurora::gx aurora::gd aurora::si aurora::vi aurora::pad aurora::mtx aurora::os aurora::dvd aurora::card) +target_link_libraries(game_debug PUBLIC aurora::core aurora::gx aurora::gd aurora::si aurora::vi aurora::pad aurora::mtx aurora::os aurora::dvd aurora::card freeverb) set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) add_library(game SHARED ${DOLZEL_FILES} ${Z2AUDIOLIB_FILES} ${SSYSTEM_FILES} ${JSYSTEM_FILES} ${REL_FILES} ${DUSK_FILES} ${DOLPHIN_FILES} src/dusk/imgui/ImGuiStubLog.cpp src/dusk/imgui/ImGuiAudio.cpp) -target_link_libraries(game PRIVATE game_debug cxxopts::cxxopts absl::flat_hash_map) +target_link_libraries(game PRIVATE game_debug cxxopts::cxxopts absl::flat_hash_map freeverb) if (DUSK_MOVIE_SUPPORT_REAL) if (TARGET libjpeg-turbo::turbojpeg-static) message(STATUS "dusk: Linking against libjpeg-turbo static library") @@ -132,7 +134,7 @@ target_precompile_headers(game PRIVATE "$<$:${CMAKE_SOURCE add_executable(dusk src/dusk/main.cpp) target_compile_definitions(dusk PRIVATE TARGET_PC AVOID_UB=1 VERSION=0) target_include_directories(dusk PRIVATE include) -target_link_libraries(dusk PRIVATE game aurora::main) +target_link_libraries(dusk PRIVATE game aurora::main freeverb) add_custom_command(TARGET dusk POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory diff --git a/include/dusk/settings.h b/include/dusk/settings.h index daba1287d8..64fee289ba 100644 --- a/include/dusk/settings.h +++ b/include/dusk/settings.h @@ -20,6 +20,7 @@ struct UserSettings { float subMusicVolume; float soundEffectsVolume; float fanfareVolume; + bool enableReverb; } audio; // Game settings @@ -68,4 +69,3 @@ TransientSettings& getTransientSettings(); } #endif // DUSK_CONFIG_H - diff --git a/libs/freeverb/CMakeLists.txt b/libs/freeverb/CMakeLists.txt new file mode 100644 index 0000000000..c4f4d4ed95 --- /dev/null +++ b/libs/freeverb/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 3.10) + +project(freeverb LANGUAGES CXX) + +add_library(freeverb + comb.cpp + allpass.cpp + revmodel.cpp +) + +target_include_directories(freeverb PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} +) diff --git a/libs/freeverb/allpass.cpp b/libs/freeverb/allpass.cpp new file mode 100644 index 0000000000..5d80eda2bd --- /dev/null +++ b/libs/freeverb/allpass.cpp @@ -0,0 +1,36 @@ +// Allpass filter implementation +// +// Written by Jezar at Dreampoint, June 2000 +// http://www.dreampoint.co.uk +// This code is public domain + +#include "allpass.hpp" + +allpass::allpass() +{ + bufidx = 0; +} + +void allpass::setbuffer(float *buf, int size) +{ + buffer = buf; + bufsize = size; +} + +void allpass::mute() +{ + for (int i=0; i=bufsize) bufidx = 0; + + return output; +} + +#endif//_allpass + +//ends \ No newline at end of file diff --git a/libs/freeverb/comb.cpp b/libs/freeverb/comb.cpp new file mode 100644 index 0000000000..c05f5069c8 --- /dev/null +++ b/libs/freeverb/comb.cpp @@ -0,0 +1,48 @@ +// Comb filter implementation +// +// Written by Jezar at Dreampoint, June 2000 +// http://www.dreampoint.co.uk +// This code is public domain + +#include "comb.hpp" + +comb::comb() +{ + filterstore = 0; + bufidx = 0; +} + +void comb::setbuffer(float *buf, int size) +{ + buffer = buf; + bufsize = size; +} + +void comb::mute() +{ + for (int i=0; i=bufsize) bufidx = 0; + + return output; +} + +#endif //_comb_ + +//ends diff --git a/libs/freeverb/denormals.h b/libs/freeverb/denormals.h new file mode 100644 index 0000000000..f871412714 --- /dev/null +++ b/libs/freeverb/denormals.h @@ -0,0 +1,15 @@ +// Macro for killing denormalled numbers +// +// Written by Jezar at Dreampoint, June 2000 +// http://www.dreampoint.co.uk +// Based on IS_DENORMAL macro by Jon Watte +// This code is public domain + +#ifndef _denormals_ +#define _denormals_ + +#define undenormalise(sample) if(((*(unsigned int*)&sample)&0x7f800000)==0) sample=0.0f + +#endif//_denormals_ + +//ends diff --git a/libs/freeverb/revmodel.cpp b/libs/freeverb/revmodel.cpp new file mode 100644 index 0000000000..feaa3bc04c --- /dev/null +++ b/libs/freeverb/revmodel.cpp @@ -0,0 +1,252 @@ +// Reverb model implementation +// +// Written by Jezar at Dreampoint, June 2000 +// http://www.dreampoint.co.uk +// This code is public domain + +#include "revmodel.hpp" + +revmodel::revmodel() +{ + // Tie the components to their buffers + combL[0].setbuffer(bufcombL1,combtuningL1); + combR[0].setbuffer(bufcombR1,combtuningR1); + combL[1].setbuffer(bufcombL2,combtuningL2); + combR[1].setbuffer(bufcombR2,combtuningR2); + combL[2].setbuffer(bufcombL3,combtuningL3); + combR[2].setbuffer(bufcombR3,combtuningR3); + combL[3].setbuffer(bufcombL4,combtuningL4); + combR[3].setbuffer(bufcombR4,combtuningR4); + combL[4].setbuffer(bufcombL5,combtuningL5); + combR[4].setbuffer(bufcombR5,combtuningR5); + combL[5].setbuffer(bufcombL6,combtuningL6); + combR[5].setbuffer(bufcombR6,combtuningR6); + combL[6].setbuffer(bufcombL7,combtuningL7); + combR[6].setbuffer(bufcombR7,combtuningR7); + combL[7].setbuffer(bufcombL8,combtuningL8); + combR[7].setbuffer(bufcombR8,combtuningR8); + allpassL[0].setbuffer(bufallpassL1,allpasstuningL1); + allpassR[0].setbuffer(bufallpassR1,allpasstuningR1); + allpassL[1].setbuffer(bufallpassL2,allpasstuningL2); + allpassR[1].setbuffer(bufallpassR2,allpasstuningR2); + allpassL[2].setbuffer(bufallpassL3,allpasstuningL3); + allpassR[2].setbuffer(bufallpassR3,allpasstuningR3); + allpassL[3].setbuffer(bufallpassL4,allpasstuningL4); + allpassR[3].setbuffer(bufallpassR4,allpasstuningR4); + + // Set default values + allpassL[0].setfeedback(0.5f); + allpassR[0].setfeedback(0.5f); + allpassL[1].setfeedback(0.5f); + allpassR[1].setfeedback(0.5f); + allpassL[2].setfeedback(0.5f); + allpassR[2].setfeedback(0.5f); + allpassL[3].setfeedback(0.5f); + allpassR[3].setfeedback(0.5f); + setwet(initialwet); + setroomsize(initialroom); + setdry(initialdry); + setdamp(initialdamp); + setwidth(initialwidth); + setmode(initialmode); + + // Buffer will be full of rubbish - so we MUST mute them + mute(); +} + +void revmodel::mute() +{ + if (getmode() >= freezemode) + return; + + for (int i=0;i 0) + { + outL = outR = 0; + input = (*inputL + *inputR) * gain; + + // Accumulate comb filters in parallel + for(int i=0; i 0) + { + outL = outR = 0; + input = (*inputL + *inputR) * gain; + + // Accumulate comb filters in parallel + for(int i=0; i= freezemode) + { + roomsize1 = 1; + damp1 = 0; + gain = muted; + } + else + { + roomsize1 = roomsize; + damp1 = damp; + gain = fixedgain; + } + + for(i=0; i= freezemode) + return 1; + else + return 0; +} + +//ends diff --git a/libs/freeverb/revmodel.hpp b/libs/freeverb/revmodel.hpp new file mode 100644 index 0000000000..10fe7c67d5 --- /dev/null +++ b/libs/freeverb/revmodel.hpp @@ -0,0 +1,87 @@ +// Reverb model declaration +// +// Written by Jezar at Dreampoint, June 2000 +// http://www.dreampoint.co.uk +// This code is public domain + +#ifndef _revmodel_ +#define _revmodel_ + +#include "comb.hpp" +#include "allpass.hpp" +#include "tuning.h" + +class revmodel +{ +public: + revmodel(); + void mute(); + void processmix(float *inputL, float *inputR, float *outputL, float *outputR, long numsamples, int skip); + void processreplace(float *inputL, float *inputR, float *outputL, float *outputR, long numsamples, int skip); + void setroomsize(float value); + float getroomsize(); + void setdamp(float value); + float getdamp(); + void setwet(float value); + float getwet(); + void setdry(float value); + float getdry(); + void setwidth(float value); + float getwidth(); + void setmode(float value); + float getmode(); +private: + void update(); +private: + float gain; + float roomsize,roomsize1; + float damp,damp1; + float wet,wet1,wet2; + float dry; + float width; + float mode; + + // The following are all declared inline + // to remove the need for dynamic allocation + // with its subsequent error-checking messiness + + // Comb filters + comb combL[numcombs]; + comb combR[numcombs]; + + // Allpass filters + allpass allpassL[numallpasses]; + allpass allpassR[numallpasses]; + + // Buffers for the combs + float bufcombL1[combtuningL1]; + float bufcombR1[combtuningR1]; + float bufcombL2[combtuningL2]; + float bufcombR2[combtuningR2]; + float bufcombL3[combtuningL3]; + float bufcombR3[combtuningR3]; + float bufcombL4[combtuningL4]; + float bufcombR4[combtuningR4]; + float bufcombL5[combtuningL5]; + float bufcombR5[combtuningR5]; + float bufcombL6[combtuningL6]; + float bufcombR6[combtuningR6]; + float bufcombL7[combtuningL7]; + float bufcombR7[combtuningR7]; + float bufcombL8[combtuningL8]; + float bufcombR8[combtuningR8]; + + // Buffers for the allpasses + float bufallpassL1[allpasstuningL1]; + float bufallpassR1[allpasstuningR1]; + float bufallpassL2[allpasstuningL2]; + float bufallpassR2[allpasstuningR2]; + float bufallpassL3[allpasstuningL3]; + float bufallpassR3[allpasstuningR3]; + float bufallpassL4[allpasstuningL4]; + float bufallpassR4[allpasstuningR4]; +}; + +#endif//_revmodel_ + +//ends \ No newline at end of file diff --git a/libs/freeverb/tuning.h b/libs/freeverb/tuning.h new file mode 100644 index 0000000000..baaa9ce004 --- /dev/null +++ b/libs/freeverb/tuning.h @@ -0,0 +1,60 @@ +// Reverb model tuning values +// +// Written by Jezar at Dreampoint, June 2000 +// http://www.dreampoint.co.uk +// This code is public domain + +#ifndef _tuning_ +#define _tuning_ + +const int numcombs = 8; +const int numallpasses = 4; +const float muted = 0; +const float fixedgain = 0.015f; +const float scalewet = 3; +const float scaledry = 2; +const float scaledamp = 0.4f; +const float scaleroom = 0.28f; +const float offsetroom = 0.7f; +const float initialroom = 0.5f; +const float initialdamp = 0.5f; +const float initialwet = 1/scalewet; +const float initialdry = 0; +const float initialwidth = 1; +const float initialmode = 0; +const float freezemode = 0.5f; +const int stereospread = 23; + +// These values assume 44.1KHz sample rate +// they will probably be OK for 48KHz sample rate +// but would need scaling for 96KHz (or other) sample rates. +// The values were obtained by listening tests. +const int combtuningL1 = 1116; +const int combtuningR1 = 1116+stereospread; +const int combtuningL2 = 1188; +const int combtuningR2 = 1188+stereospread; +const int combtuningL3 = 1277; +const int combtuningR3 = 1277+stereospread; +const int combtuningL4 = 1356; +const int combtuningR4 = 1356+stereospread; +const int combtuningL5 = 1422; +const int combtuningR5 = 1422+stereospread; +const int combtuningL6 = 1491; +const int combtuningR6 = 1491+stereospread; +const int combtuningL7 = 1557; +const int combtuningR7 = 1557+stereospread; +const int combtuningL8 = 1617; +const int combtuningR8 = 1617+stereospread; +const int allpasstuningL1 = 556; +const int allpasstuningR1 = 556+stereospread; +const int allpasstuningL2 = 441; +const int allpasstuningR2 = 441+stereospread; +const int allpasstuningL3 = 341; +const int allpasstuningR3 = 341+stereospread; +const int allpasstuningL4 = 225; +const int allpasstuningR4 = 225+stereospread; + +#endif//_tuning_ + +//ends + diff --git a/src/dusk/audio/DuskDsp.cpp b/src/dusk/audio/DuskDsp.cpp index 7cf1c44cd8..fd1ae66620 100644 --- a/src/dusk/audio/DuskDsp.cpp +++ b/src/dusk/audio/DuskDsp.cpp @@ -19,6 +19,7 @@ ChannelAuxData dusk::audio::ChannelAux[DSP_CHANNELS] = {}; f32 dusk::audio::MasterVolume = 1.0f; f32 dusk::audio::PrevMasterVolume = 1.0f; +bool dusk::audio::EnableReverb = true; /** * Validate that a DSP channel's format is actually something we know how to play. @@ -112,32 +113,47 @@ void dusk::audio::DspRender(OutputSubframe& subframe) { auto& channel = channels[i]; auto& channelAux = ChannelAux[i]; - if (!channel.mIsActive) { - continue; - } + bool skipRender = false; - if (channel.mPauseFlag) { + if (!channel.mIsActive) { + skipRender = true; + } + else if (channel.mPauseFlag) { // Not really sure what the practical difference between pause and // deactivation is. Either avoids clearing state or allows the DSP to avoid popping? - continue; + skipRender = true; } - - if (channel.mForcedStop) { + else if (channel.mForcedStop) { channel.mIsFinished = true; - continue; + skipRender = true; } - - if (channel.mWaveAramAddress == 0) { + else if (channel.mWaveAramAddress == 0) { // I think these are oscillator channels? Not backed by audio. // No idea how to implement these yet, so skip them. channel.mIsFinished = true; - continue; + skipRender = true; } - ValidateChannel(channel); - OutputSubframe channelSubframe = {}; - RenderChannel(channel, channelAux, channelSubframe); + + if (!skipRender) { + ValidateChannel(channel); + RenderChannel(channel, channelAux, channelSubframe); + // todo figure out an actual way to convert this accurately to dry/wet + channelAux.reverb.setwet(((channel.mAutoMixerFxMix >> 8) / 255.0f)); + channelAux.reverb.setdry(1.0f - channelAux.reverb.getwet()); + } + + if (EnableReverb) { + channelAux.reverb.processreplace( + channelSubframe.channels[0].data(), + channelSubframe.channels[1].data(), + channelSubframe.channels[0].data(), + channelSubframe.channels[1].data(), + DSP_SUBFRAME_SIZE, + 1 + ); + } for (int o = 0; o < subframe.channels.size(); o++) { MixSubframe(subframe.channels[o], channelSubframe.channels[o]); @@ -463,6 +479,16 @@ static void RenderChannel( } void dusk::audio::DspInit() { + for (int i = 0; i < DSP_CHANNELS; i++) { + auto& channelAux = ChannelAux[i]; + channelAux.reverb.setwet(0.0f); + channelAux.reverb.setdry(1.0f); + channelAux.reverb.setroomsize(0.2f); + channelAux.reverb.setdamp(0.3f); + channelAux.reverb.setwidth(1.0f); + channelAux.reverb.setmode(0.0f); + channelAux.reverb.mute(); + } } void dusk::audio::ApplyVolume( diff --git a/src/dusk/audio/DuskDsp.hpp b/src/dusk/audio/DuskDsp.hpp index 35b6ccabc9..3c414375f3 100644 --- a/src/dusk/audio/DuskDsp.hpp +++ b/src/dusk/audio/DuskDsp.hpp @@ -8,6 +8,8 @@ #include "SDL3/SDL_audio.h" #include +#include "revmodel.hpp" + // ReSharper disable once CppUnusedIncludeDirective #include "global.h" @@ -29,6 +31,7 @@ namespace dusk::audio { // Used for debugging tools. u32 resetCount; + revmodel reverb; /** * Previous volume values, per output channel. @@ -118,4 +121,5 @@ namespace dusk::audio { extern f32 MasterVolume; extern f32 PrevMasterVolume; + extern bool EnableReverb; } diff --git a/src/dusk/imgui/ImGuiMenuGame.cpp b/src/dusk/imgui/ImGuiMenuGame.cpp index 91aa0314df..7bdea47862 100644 --- a/src/dusk/imgui/ImGuiMenuGame.cpp +++ b/src/dusk/imgui/ImGuiMenuGame.cpp @@ -6,6 +6,7 @@ #include #include "JSystem/JUtility/JUTGamePad.h" +#include "dusk/audio/DuskDsp.hpp" #include "dusk/audio/DuskAudioSystem.h" #include "dusk/hotkeys.h" #include "dusk/settings.h" @@ -34,7 +35,7 @@ namespace dusk { if (ImGui::BeginMenu("Audio")) { ImGui::Text("Master Volume"); ImGui::SliderFloat("##masterVolume", &getSettings().audio.masterVolume, 0.0f, 1.0f, ""); - + ImGui::Checkbox("Enable Reverb", &getSettings().audio.enableReverb); /* // TODO: implement additional settings ImGui::Text("Main Music Volume"); @@ -55,6 +56,7 @@ namespace dusk { */ audio::SetMasterVolume(getSettings().audio.masterVolume); + audio::EnableReverb = getSettings().audio.enableReverb; ImGui::EndMenu(); } diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index 9a5bb20c8c..ed2071ca30 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -17,6 +17,7 @@ UserSettings g_userSettings = { .subMusicVolume = 1.0f, .soundEffectsVolume = 1.0f, .fanfareVolume = 1.0f, + .enableReverb = true }, // Game settings From c7ad544b19bc4c0a5f50255d87601caf9df8c29b Mon Sep 17 00:00:00 2001 From: TakaRikka Date: Sat, 4 Apr 2026 02:37:59 -0700 Subject: [PATCH 203/350] couple of hd qol toggles --- extern/aurora | 2 +- include/dusk/settings.h | 3 +++ src/d/actor/d_a_alink_demo.inc | 8 ++++++++ src/d/d_s_name.cpp | 7 +++++++ src/d/d_save.cpp | 14 ++++++++++++++ src/dusk/imgui/ImGuiMenuEnhancements.cpp | 16 ++++++++++++++++ src/dusk/settings.cpp | 3 +++ 7 files changed, 52 insertions(+), 1 deletion(-) diff --git a/extern/aurora b/extern/aurora index 37631c3bf3..b17da31593 160000 --- a/extern/aurora +++ b/extern/aurora @@ -1 +1 @@ -Subproject commit 37631c3bf382c1564d3f29c4c3f2dc6cf7617fa9 +Subproject commit b17da315932b85d7d708eaf3e44914f70045a44e diff --git a/include/dusk/settings.h b/include/dusk/settings.h index db49560fff..fddcd3b0f0 100644 --- a/include/dusk/settings.h +++ b/include/dusk/settings.h @@ -28,6 +28,9 @@ struct UserSettings { // QoL bool enableQuickTransform; bool hideTvSettingsScreen; + bool biggerWallets; + bool noReturnRupees; + bool disableRupeeCutscenes; // Preferences bool enableMirrorMode; diff --git a/src/d/actor/d_a_alink_demo.inc b/src/d/actor/d_a_alink_demo.inc index 39387582cb..aa77b24129 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/settings.h" + BOOL daAlink_c::checkEventRun() const { return dComIfGp_event_runCheck() || checkPlayerDemoMode(); } @@ -2230,6 +2232,12 @@ void daAlink_c::setGetSubBgm(int i_itemNo) { } BOOL daAlink_c::checkTreasureRupeeReturn(int i_itemNo) const { + #if TARGET_PC + if (dusk::getSettings().game.noReturnRupees) { + return FALSE; + } + #endif + static const int dummy = 0; if (i_itemNo == dItemNo_LINKS_SAVINGS_e) { diff --git a/src/d/d_s_name.cpp b/src/d/d_s_name.cpp index e001d97c71..735ce4d0ca 100644 --- a/src/d/d_s_name.cpp +++ b/src/d/d_s_name.cpp @@ -17,6 +17,7 @@ #include "m_Do/m_Do_main.h" #include "f_op/f_op_overlap_mng.h" #include "dusk/memory.h" +#include "dusk/settings.h" #if TARGET_PC #define SHOW_TV_SETTINGS_SCREEN (this->mShowTvSettingsScreen) @@ -367,6 +368,12 @@ void dScnName_c::doPreLoadSetup() { field_0x41f = 0; mProc = dScnName_PROC_ChangeGameScene; + #if TARGET_PC + if (dusk::getSettings().game.disableRupeeCutscenes) { + return; + } + #endif + // Reset rupee "first-time collection" flags so the collection cutscene will play again dComIfGs_offItemFirstBit(dItemNo_GREEN_RUPEE_e); dComIfGs_offItemFirstBit(dItemNo_BLUE_RUPEE_e); diff --git a/src/d/d_save.cpp b/src/d/d_save.cpp index 06b92994db..3289a49c64 100644 --- a/src/d/d_save.cpp +++ b/src/d/d_save.cpp @@ -25,6 +25,8 @@ #include "lingcod/lingcod.h" #endif +#include "dusk/settings.h" + static u8 dSv_item_rename(u8 i_itemNo) { switch (i_itemNo) { case dItemNo_OIL_BOTTLE_2_e: @@ -111,11 +113,23 @@ u16 dSv_player_status_a_c::getRupeeMax() const { if (mWalletSize < 3) { // if you make this a default, it wont match. Compiler, pls. switch (mWalletSize) { case WALLET: + #if TARGET_PC + return dusk::getSettings().game.biggerWallets ? 500 : 300; + #else return 300; + #endif case BIG_WALLET: + #if TARGET_PC + return dusk::getSettings().game.biggerWallets ? 1000 : 600; + #else return 600; + #endif case GIANT_WALLET: + #if TARGET_PC + return dusk::getSettings().game.biggerWallets ? 2000 : 1000; + #else return 1000; + #endif } } diff --git a/src/dusk/imgui/ImGuiMenuEnhancements.cpp b/src/dusk/imgui/ImGuiMenuEnhancements.cpp index eed938de69..8ed7b1d8c4 100644 --- a/src/dusk/imgui/ImGuiMenuEnhancements.cpp +++ b/src/dusk/imgui/ImGuiMenuEnhancements.cpp @@ -11,6 +11,22 @@ namespace dusk { if (ImGui::BeginMenu("Enhancements")) { if (ImGui::BeginMenu("Quality of Life")) { ImGui::Checkbox("Quick Transform (R+Y)", &getSettings().game.enableQuickTransform); + + ImGui::Checkbox("Bigger Wallets", &getSettings().game.biggerWallets); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Wallet sizes are like in the HD version (500, 1000, 2000)"); + } + + ImGui::Checkbox("No Rupee Returns", &getSettings().game.noReturnRupees); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Always collect Rupees even if your Wallet is too full"); + } + + ImGui::Checkbox("Disable Rupee Cutscenes", &getSettings().game.disableRupeeCutscenes); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Rupees won't play cutscenes after you've collected them the first time"); + } + ImGui::Checkbox("Hide TV Settings Screen", &getSettings().game.hideTvSettingsScreen); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Hides the TV calibration screen shown when loading a save"); diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index d1ba67b585..734188987a 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -24,6 +24,9 @@ UserSettings g_userSettings = { // Quality of Life .enableQuickTransform = false, .hideTvSettingsScreen = false, + .biggerWallets = false, + .noReturnRupees = false, + .disableRupeeCutscenes = false, // Preferences .enableMirrorMode = false, From 6b5d39d94e7c0003de009da607a8a5eb67c57127 Mon Sep 17 00:00:00 2001 From: TakaRikka Date: Sat, 4 Apr 2026 02:59:53 -0700 Subject: [PATCH 204/350] no sword recoil enhancement --- include/dusk/settings.h | 1 + src/d/actor/d_a_alink_cut.inc | 8 ++++++++ src/dusk/imgui/ImGuiMenuEnhancements.cpp | 5 +++++ src/dusk/settings.cpp | 1 + 4 files changed, 15 insertions(+) diff --git a/include/dusk/settings.h b/include/dusk/settings.h index fddcd3b0f0..cd9a1e0cf0 100644 --- a/include/dusk/settings.h +++ b/include/dusk/settings.h @@ -31,6 +31,7 @@ struct UserSettings { bool biggerWallets; bool noReturnRupees; bool disableRupeeCutscenes; + bool noSwordRecoil; // Preferences bool enableMirrorMode; diff --git a/src/d/actor/d_a_alink_cut.inc b/src/d/actor/d_a_alink_cut.inc index 4e9dd18aee..8e61a262cb 100644 --- a/src/d/actor/d_a_alink_cut.inc +++ b/src/d/actor/d_a_alink_cut.inc @@ -7,6 +7,8 @@ #include "d/actor/d_a_b_gnd.h" #include "SSystem/SComponent/c_math.h" +#include "dusk/settings.h" + enum daAlink_CutNmParamType { CUT_NM_PARAM_VERTICAL, CUT_NM_PARAM_LEFT, @@ -369,6 +371,12 @@ BOOL daAlink_c::changeCutReverseProc(daAlink_c::daAlink_ANM i_anmID) { return procCutReverseInit(i_anmID); } + #if TARGET_PC + if (dusk::getSettings().game.noSwordRecoil) { + return FALSE; + } + #endif + if (checkNoResetFlg0(FLG0_CUT_AT_FLG) || mEquipItem == dItemNo_COPY_ROD_e) { cXyz sp28; Vec sp1C; diff --git a/src/dusk/imgui/ImGuiMenuEnhancements.cpp b/src/dusk/imgui/ImGuiMenuEnhancements.cpp index 8ed7b1d8c4..d0285789cd 100644 --- a/src/dusk/imgui/ImGuiMenuEnhancements.cpp +++ b/src/dusk/imgui/ImGuiMenuEnhancements.cpp @@ -27,6 +27,11 @@ namespace dusk { ImGui::SetTooltip("Rupees won't play cutscenes after you've collected them the first time"); } + ImGui::Checkbox("No Sword Recoil", &getSettings().game.noSwordRecoil); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Link won't recoil when his sword hits walls"); + } + ImGui::Checkbox("Hide TV Settings Screen", &getSettings().game.hideTvSettingsScreen); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Hides the TV calibration screen shown when loading a save"); diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index 734188987a..7e658f6e5c 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -27,6 +27,7 @@ UserSettings g_userSettings = { .biggerWallets = false, .noReturnRupees = false, .disableRupeeCutscenes = false, + .noSwordRecoil = false, // Preferences .enableMirrorMode = false, From 9a1eec4abc382b38e804c4f6ee901c661659defe Mon Sep 17 00:00:00 2001 From: TakaRikka Date: Sat, 4 Apr 2026 03:52:28 -0700 Subject: [PATCH 205/350] quick climbing qol, difficulty options --- include/dusk/settings.h | 3 ++ src/d/actor/d_a_alink_damage.inc | 7 +++ src/d/actor/d_a_alink_hang.inc | 68 ++++++++++++++++++++---- src/dusk/imgui/ImGuiMenuEnhancements.cpp | 15 ++++++ src/dusk/settings.cpp | 3 ++ 5 files changed, 85 insertions(+), 11 deletions(-) diff --git a/include/dusk/settings.h b/include/dusk/settings.h index cd9a1e0cf0..b822a2ffbb 100644 --- a/include/dusk/settings.h +++ b/include/dusk/settings.h @@ -32,6 +32,9 @@ struct UserSettings { bool noReturnRupees; bool disableRupeeCutscenes; bool noSwordRecoil; + int damageMultiplier; + bool instantDeath; + bool fastClimbing; // Preferences bool enableMirrorMode; diff --git a/src/d/actor/d_a_alink_damage.inc b/src/d/actor/d_a_alink_damage.inc index 122bf8f248..420011cfd6 100644 --- a/src/d/actor/d_a_alink_damage.inc +++ b/src/d/actor/d_a_alink_damage.inc @@ -152,6 +152,13 @@ f32 daAlink_c::damageMagnification(BOOL i_checkZoraMag, int param_1) { base_mag = 1.0f; } + #if TARGET_PC + base_mag *= dusk::getSettings().game.damageMultiplier; + if (dusk::getSettings().game.instantDeath) { + base_mag = 9999.0f; + } + #endif + if (checkWolf() && !checkCargoCarry() && param_1 == 0) { return 2.0f * base_mag; } diff --git a/src/d/actor/d_a_alink_hang.inc b/src/d/actor/d_a_alink_hang.inc index b99fe71c85..a98020ce4f 100644 --- a/src/d/actor/d_a_alink_hang.inc +++ b/src/d/actor/d_a_alink_hang.inc @@ -13,8 +13,15 @@ #include "d/actor/d_a_obj_swhang.h" f32 daAlink_c::getHangMoveAnmSpeed() { + #if TARGET_PC + return getAnmSpeedStickRate( + dusk::getSettings().game.fastClimbing ? 2.0f : mpHIO->mWallHang.mWallMove.m.mMinAnmSpeed, + dusk::getSettings().game.fastClimbing ? 2.0f : mpHIO->mWallHang.mWallMove.m.mMaxAnmSpeed + ); + #else return getAnmSpeedStickRate(mpHIO->mWallHang.mWallMove.m.mMinAnmSpeed, mpHIO->mWallHang.mWallMove.m.mMaxAnmSpeed); + #endif } int daAlink_c::getHangDirectionFromAngle() { @@ -1209,7 +1216,12 @@ void daAlink_c::setLadderPos(int param_0) { f32 daAlink_c::getLadderMoveAnmSpeed() { return getAnmSpeedStickRate(mpHIO->mLadder.m.mMoveMinASpeed, - mpHIO->mLadder.m.mMoveMaxSpeed); + #if TARGET_PC + dusk::getSettings().game.fastClimbing ? 1.575f : mpHIO->mLadder.m.mMoveMaxSpeed + #else + mpHIO->mLadder.m.mMoveMaxSpeed + #endif + ); } int daAlink_c::changeLadderMoveProc(int param_0) { @@ -1304,8 +1316,14 @@ int daAlink_c::procLadderUpStartInit() { mNormalSpeed = 0.0f; speedF = 0.0f; - setSingleAnimeBaseSpeed(ANM_LADDER_UP_START, mpHIO->mLadder.m.mClimbUpStartASpeed, - mpHIO->mLadder.m.mClimbUpStartInterp); + setSingleAnimeBaseSpeed(ANM_LADDER_UP_START, + #if TARGET_PC + dusk::getSettings().game.fastClimbing ? 1.8f : mpHIO->mLadder.m.mClimbUpStartASpeed, + #else + mpHIO->mLadder.m.mClimbUpStartASpeed, + #endif + mpHIO->mLadder.m.mClimbUpStartInterp); + field_0x2f99 = 0x10; field_0x3588 = l_waitBaseAnime; dComIfGp_setPlayerStatus0(0, 0x2000000); @@ -1354,8 +1372,14 @@ int daAlink_c::procLadderUpEndInit(BOOL param_0) { anm_id = ANM_LADDER_UP_END_RIGHT; } - setSingleAnimeBaseSpeed(anm_id, mpHIO->mLadder.m.mClimbUpEndASpeed, - mpHIO->mLadder.m.mClimbUpEndInterp); + setSingleAnimeBaseSpeed(anm_id, + #if TARGET_PC + dusk::getSettings().game.fastClimbing ? 1.765f : mpHIO->mLadder.m.mClimbUpEndASpeed, + #else + mpHIO->mLadder.m.mClimbUpEndASpeed, + #endif + mpHIO->mLadder.m.mClimbUpEndInterp); + field_0x2f99 = 14; setSpecialGravity(0.0f, maxFallSpeed, 0); @@ -1413,8 +1437,14 @@ int daAlink_c::procLadderDownStartInit() { shape_angle.y = field_0x306e + 0x8000; current.angle.y = field_0x306e; - setSingleAnimeBaseSpeed(ANM_LADDER_DOWN_START, mpHIO->mLadder.m.mClimbDownStartASpeed, - mpHIO->mLadder.m.mClimbDownStartInterp); + setSingleAnimeBaseSpeed(ANM_LADDER_DOWN_START, + #if TARGET_PC + dusk::getSettings().game.fastClimbing ? 1.8f : mpHIO->mLadder.m.mClimbDownStartASpeed, + #else + mpHIO->mLadder.m.mClimbDownStartASpeed, + #endif + mpHIO->mLadder.m.mClimbDownStartInterp); + field_0x2f99 = 0x10; field_0x3588 = l_waitBaseAnime; dComIfGp_setPlayerStatus0(0, 0x2000000); @@ -1469,8 +1499,14 @@ int daAlink_c::procLadderDownEndInit(BOOL param_0) { anm_id = ANM_LADDER_DOWN_END_RIGHT; } - setSingleAnimeBaseSpeed(anm_id, mpHIO->mLadder.m.mClimbDownEndASpeed, - mpHIO->mLadder.m.mClimbDownEndInterp); + setSingleAnimeBaseSpeed(anm_id, + #if TARGET_PC + dusk::getSettings().game.fastClimbing ? 1.8f : mpHIO->mLadder.m.mClimbDownEndASpeed, + #else + mpHIO->mLadder.m.mClimbDownEndASpeed, + #endif + mpHIO->mLadder.m.mClimbDownEndInterp); + field_0x2f99 = 14; setSpecialGravity(0.0f, maxFallSpeed, 0); @@ -1602,12 +1638,22 @@ int daAlink_c::procLadderMove() { f32 daAlink_c::getClimbMoveUpDownAnmSpeed() { return getAnmSpeedStickRate(mpHIO->mLadder.m.mWallVerticalMinAnmSpeed, - mpHIO->mLadder.m.mWallVerticalMaxAnmSpeed); + #if TARGET_PC + dusk::getSettings().game.fastClimbing ? 1.875f : mpHIO->mLadder.m.mWallVerticalMaxAnmSpeed + #else + mpHIO->mLadder.m.mWallVerticalMaxAnmSpeed + #endif + ); } f32 daAlink_c::getClimbMoveSideAnmSpeed() { return getAnmSpeedStickRate(mpHIO->mLadder.m.mWallHorizontalMinAnmSpeed, - mpHIO->mLadder.m.mWallHorizontalMaxAnmSpeed); + #if TARGET_PC + dusk::getSettings().game.fastClimbing ? 2.0f : mpHIO->mLadder.m.mWallHorizontalMaxAnmSpeed + #else + mpHIO->mLadder.m.mWallHorizontalMaxAnmSpeed + #endif + ); } BOOL daAlink_c::checkClimbCode(cBgS_PolyInfo& i_polyinfo) { diff --git a/src/dusk/imgui/ImGuiMenuEnhancements.cpp b/src/dusk/imgui/ImGuiMenuEnhancements.cpp index d0285789cd..1ccb06e202 100644 --- a/src/dusk/imgui/ImGuiMenuEnhancements.cpp +++ b/src/dusk/imgui/ImGuiMenuEnhancements.cpp @@ -32,6 +32,11 @@ namespace dusk { ImGui::SetTooltip("Link won't recoil when his sword hits walls"); } + ImGui::Checkbox("Faster Climbing", &getSettings().game.fastClimbing); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Quicker climbing on ladders and vines like the HD version"); + } + ImGui::Checkbox("Hide TV Settings Screen", &getSettings().game.hideTvSettingsScreen); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Hides the TV calibration screen shown when loading a save"); @@ -63,6 +68,16 @@ namespace dusk { ImGui::EndMenu(); } + if (ImGui::BeginMenu("Difficulty")) { + ImGui::SliderInt("Damage Multiplier", &getSettings().game.damageMultiplier, 1, 8, "x%d"); + ImGui::Checkbox("Instant Death", &getSettings().game.instantDeath); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Any hit will instantly kill you"); + } + + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Technical")) { ImGui::Checkbox("Restore Wii 1.0 Glitches", &getSettings().game.restoreWiiGlitches); if (ImGui::IsItemHovered()) { diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index 7e658f6e5c..a8a4d219a9 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -28,6 +28,9 @@ UserSettings g_userSettings = { .noReturnRupees = false, .disableRupeeCutscenes = false, .noSwordRecoil = false, + .damageMultiplier = 1, + .instantDeath = false, + .fastClimbing = false, // Preferences .enableMirrorMode = false, From c0f553cb12aef243bdf37ca1bdd08da5003ac638 Mon Sep 17 00:00:00 2001 From: TakaRikka Date: Sat, 4 Apr 2026 06:02:23 -0700 Subject: [PATCH 206/350] hd faster tears toggle --- include/dusk/settings.h | 1 + src/d/actor/d_a_e_ym.cpp | 17 +++++++- src/d/actor/d_a_obj_drop.cpp | 53 ++++++++++++++++++++++++ src/dusk/imgui/ImGuiMenuEnhancements.cpp | 5 +++ src/dusk/imgui/ImGuiSaveEditor.cpp | 2 +- src/dusk/settings.cpp | 1 + 6 files changed, 77 insertions(+), 2 deletions(-) diff --git a/include/dusk/settings.h b/include/dusk/settings.h index b822a2ffbb..582c5a1872 100644 --- a/include/dusk/settings.h +++ b/include/dusk/settings.h @@ -35,6 +35,7 @@ struct UserSettings { int damageMultiplier; bool instantDeath; bool fastClimbing; + bool fastTears; // Preferences bool enableMirrorMode; diff --git a/src/d/actor/d_a_e_ym.cpp b/src/d/actor/d_a_e_ym.cpp index a5544b4041..758bec6269 100644 --- a/src/d/actor/d_a_e_ym.cpp +++ b/src/d/actor/d_a_e_ym.cpp @@ -16,6 +16,8 @@ #include "f_op/f_op_camera_mng.h" #include +#include "dusk/settings.h" + class daE_YM_HIO_c: public JORReflexible { public: daE_YM_HIO_c(); @@ -1066,7 +1068,11 @@ void daE_YM_c::executeDown() { } if (mAcch.ChkGroundHit()) { if (mFlyType != 1) { + #if TARGET_PC + bckSet(6, 0, 0.0f, dusk::getSettings().game.fastTears && dComIfGp_event_getMode() == 0 ? 2.0f : 1.0f); + #else bckSet(6, 0, 0.0f, 1.0f); + #endif } if (mMode == 1) { mSound.startCreatureSound(Z2SE_EN_YM_LAND, 0, -1); @@ -1086,7 +1092,11 @@ void daE_YM_c::executeDown() { if (current.pos.y < gnd_cross) { mSound.startCreatureSound(Z2SE_CM_BODYFALL_WATER_M, 0, -1); mSound.startCreatureSound(Z2SE_EN_YM_MOGAKU, 0, -1); + #if TARGET_PC + bckSet(6, 0, 0.0f, dusk::getSettings().game.fastTears && dComIfGp_event_getMode() == 0 ? 2.0f : 1.0f); + #else bckSet(6, 0, 0.0f, 1.0f); + #endif speedF = 0.0f; speed.y = gravity = 0.0f; current.pos.y = gnd_cross; @@ -1102,8 +1112,13 @@ void daE_YM_c::executeDown() { f32 gnd_cross_2 = dComIfG_Bgsp().GroundCross(&gnd_chk); if (gnd_cross_2 == -G_CM3D_F_INF || std::abs(gnd_cross_2 - current.pos.y) > 1000.0f || dComIfG_Bgsp().GetGroundCode(gnd_chk) == 4 || dComIfG_Bgsp().GetGroundCode(gnd_chk) == 10 - || dComIfG_Bgsp().GetGroundCode(gnd_chk) == 5) { + || dComIfG_Bgsp().GetGroundCode(gnd_chk) == 5) + { + #if TARGET_PC + bckSet(6, 0, 0.0f, dusk::getSettings().game.fastTears && dComIfGp_event_getMode() == 0 ? 2.0f : 1.0f); + #else bckSet(6, 0, 0.0f, 1.0f); + #endif mMode = 3; speedF = 0.0f; shape_angle.x = -0x8000; diff --git a/src/d/actor/d_a_obj_drop.cpp b/src/d/actor/d_a_obj_drop.cpp index 64fc1f537d..275a3d6e12 100644 --- a/src/d/actor/d_a_obj_drop.cpp +++ b/src/d/actor/d_a_obj_drop.cpp @@ -20,6 +20,8 @@ #include "d/actor/d_a_e_ymb.h" #include "f_op/f_op_camera_mng.h" +#include "dusk/settings.h" + #if DEBUG daObjDrop_HIO_c l_HIO; #endif @@ -261,8 +263,18 @@ int daObjDrop_c::modeParentWait() { } mModeAction = 1; + +#if TARGET_PC + mModeTimer = dusk::getSettings().game.fastTears && dComIfGp_event_getMode() == 0 ? 20 : 40; + if (dusk::getSettings().game.fastTears && dComIfGp_event_getMode() == 0) { + current.pos.y += 100.0f; + } else { + current.pos.y += 300.0f; + } +#else mModeTimer = 40; current.pos.y += 300.0f; +#endif mSound.startSound(Z2SE_OBJ_LIGHTDROP_APPEAR, 0, -1); break; case 1: @@ -272,7 +284,11 @@ int daObjDrop_c::modeParentWait() { break; case 2: createBodyEffect(); +#if TARGET_PC + mModeTimer = dusk::getSettings().game.fastTears && dComIfGp_event_getMode() == 0 ? 5 : 45; +#else mModeTimer = 45; +#endif mModeAction = 0; setMode(MODE_WAIT_e); break; @@ -281,6 +297,18 @@ int daObjDrop_c::modeParentWait() { return 1; } +#if TARGET_PC +static inline BOOL checkGetCargoRide() { + if ((daPy_getPlayerActorClass()->checkCargoCarry() && strcmp(dComIfGp_getStartStageName(), "F_SP112") == 0) || + dComIfGs_isLightDropGetFlag(dComIfGp_getStartStageDarkArea())) + { + return true; + } + + return false; +} +#endif + int daObjDrop_c::modeWait() { daPy_py_c* pplayer = daPy_getPlayerActorClass(); @@ -302,7 +330,32 @@ int daObjDrop_c::modeWait() { case 1: case 2: case 50: + #if TARGET_PC + if (dusk::getSettings().game.fastTears && dComIfGp_event_getMode() == 0) { + f32 player_dist = current.pos.abs(daPy_getPlayerActorClass()->current.pos); + f32 home_dist = current.pos.abs(home.pos); + + if (checkGetCargoRide() && player_dist < 1000.0f) { + mTargetPos = pplayer->current.pos; + mTargetPos.y += 100.0f; + + f32 temp = 3000.0f - home_dist; + if (temp < 0.0f) { + temp = 0.0f; + } + + cLib_chaseF(&speedF, (temp / 3000.0f) * 10.0f * (temp / 3000.0f), 1.0f); + } else { + mTargetPos = home.pos; + cLib_chaseF(&speedF, 2.0f, 0.5f); + } + } else { + cLib_chaseF(&speedF, 7.5f, 0.4f); + } + #else cLib_chaseF(&speedF, 7.5f, 0.4f); + #endif + if (mModeAction == 1) { cLib_chasePos(¤t.pos, mTargetPos, speedF); } diff --git a/src/dusk/imgui/ImGuiMenuEnhancements.cpp b/src/dusk/imgui/ImGuiMenuEnhancements.cpp index 1ccb06e202..cf7fcc41d7 100644 --- a/src/dusk/imgui/ImGuiMenuEnhancements.cpp +++ b/src/dusk/imgui/ImGuiMenuEnhancements.cpp @@ -37,6 +37,11 @@ namespace dusk { ImGui::SetTooltip("Quicker climbing on ladders and vines like the HD version"); } + ImGui::Checkbox("Faster Tears of Light", &getSettings().game.fastTears); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Tears of Light dropped by Shadow Insects pop out faster like the HD version"); + } + ImGui::Checkbox("Hide TV Settings Screen", &getSettings().game.hideTvSettingsScreen); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Hides the TV calibration screen shown when loading a save"); diff --git a/src/dusk/imgui/ImGuiSaveEditor.cpp b/src/dusk/imgui/ImGuiSaveEditor.cpp index ff04b1f1a2..a9cca636f8 100644 --- a/src/dusk/imgui/ImGuiSaveEditor.cpp +++ b/src/dusk/imgui/ImGuiSaveEditor.cpp @@ -823,7 +823,7 @@ namespace dusk { for (int e = 0; e < 255; e++) { ImGui::Text("%03d ", e); ImGui::SameLine(); - for (int i = 8; i >= 0; i--) { + for (int i = 7; i >= 0; i--) { bool flag = event.mEvent[e] & (1 << i); if (ImGui::Checkbox(fmt::format("##event{0}{1}", e, i).c_str(), &flag)) { if (flag) diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index a8a4d219a9..ea17eef374 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -31,6 +31,7 @@ UserSettings g_userSettings = { .damageMultiplier = 1, .instantDeath = false, .fastClimbing = false, + .fastTears = false, // Preferences .enableMirrorMode = false, From cf022a9a55a9e422a383443c28304d63fdd8a64d Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Sat, 4 Apr 2026 11:36:15 -0400 Subject: [PATCH 207/350] Non-Stop Midna's Lament Audio Enhancement --- include/dusk/settings.h | 3 +++ src/Z2AudioLib/Z2SoundObjMgr.cpp | 7 +++++++ src/dusk/imgui/ImGuiMenuEnhancements.cpp | 9 +++++++++ src/dusk/settings.cpp | 3 +++ 4 files changed, 22 insertions(+) diff --git a/include/dusk/settings.h b/include/dusk/settings.h index 582c5a1872..d6e7ac2791 100644 --- a/include/dusk/settings.h +++ b/include/dusk/settings.h @@ -45,6 +45,9 @@ struct UserSettings { bool enableBloom; bool useWaterProjectionOffset; + // Audio + bool midnasLamentNonStop; + // Cheats bool enableFastIronBoots; diff --git a/src/Z2AudioLib/Z2SoundObjMgr.cpp b/src/Z2AudioLib/Z2SoundObjMgr.cpp index 6b080134fb..9fd9c4addb 100644 --- a/src/Z2AudioLib/Z2SoundObjMgr.cpp +++ b/src/Z2AudioLib/Z2SoundObjMgr.cpp @@ -107,6 +107,13 @@ static Z2EnemyInfo mEnemyInfo[64] = { void Z2SoundObjMgr::searchEnemy() { twilightBattle_ = 0; + #if TARGET_PC + if (Z2GetSeqMgr()->checkBgmIDPlaying(Z2BGM_MIDNA_SOS)) { + Z2GetSeqMgr()->changeSubBgmStatus(0); + return; + } + #endif + if (!Z2GetLink()) { Z2GetSeqMgr()->stopBattleBgm(1, 1); return; diff --git a/src/dusk/imgui/ImGuiMenuEnhancements.cpp b/src/dusk/imgui/ImGuiMenuEnhancements.cpp index cf7fcc41d7..a0559c89b4 100644 --- a/src/dusk/imgui/ImGuiMenuEnhancements.cpp +++ b/src/dusk/imgui/ImGuiMenuEnhancements.cpp @@ -67,6 +67,15 @@ namespace dusk { ImGui::EndMenu(); } + if (ImGui::BeginMenu("Audio")) { + ImGui::Checkbox("Non-Stop Midna's Lament", &getSettings().game.midnasLamentNonStop); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Prevents enemy music while Midna's Lament is playing."); + } + + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Cheats")) { ImGui::Checkbox("Fast Iron Boots", &getSettings().game.enableFastIronBoots); diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index ea17eef374..7a11101906 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -41,6 +41,9 @@ UserSettings g_userSettings = { .enableBloom = true, .useWaterProjectionOffset = false, + // Audio + .midnasLamentNonStop = false, + // Cheats .enableFastIronBoots = false, From b4daecaab3c7909e1b8eb2637da7f3ff8947d29e Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Sat, 4 Apr 2026 11:57:42 -0400 Subject: [PATCH 208/350] oops, forgot to check the box --- src/Z2AudioLib/Z2SoundObjMgr.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Z2AudioLib/Z2SoundObjMgr.cpp b/src/Z2AudioLib/Z2SoundObjMgr.cpp index 9fd9c4addb..19099c7675 100644 --- a/src/Z2AudioLib/Z2SoundObjMgr.cpp +++ b/src/Z2AudioLib/Z2SoundObjMgr.cpp @@ -108,7 +108,9 @@ void Z2SoundObjMgr::searchEnemy() { twilightBattle_ = 0; #if TARGET_PC - if (Z2GetSeqMgr()->checkBgmIDPlaying(Z2BGM_MIDNA_SOS)) { + if (Z2GetSeqMgr()->checkBgmIDPlaying(Z2BGM_MIDNA_SOS) && + dusk::getSettings().game.midnasLamentNonStop) + { Z2GetSeqMgr()->changeSubBgmStatus(0); return; } From f0d6d2de68afa1f36a44de068216c5136f0790ee Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Sat, 4 Apr 2026 11:59:08 -0400 Subject: [PATCH 209/350] No Climbing Miss Animation toggle, similar to TPHD --- include/dusk/settings.h | 1 + src/d/actor/d_a_alink_hang.inc | 12 ++++++++++++ src/dusk/imgui/ImGuiMenuEnhancements.cpp | 6 ++++++ src/dusk/settings.cpp | 1 + 4 files changed, 20 insertions(+) diff --git a/include/dusk/settings.h b/include/dusk/settings.h index 582c5a1872..ffd1016c48 100644 --- a/include/dusk/settings.h +++ b/include/dusk/settings.h @@ -35,6 +35,7 @@ struct UserSettings { int damageMultiplier; bool instantDeath; bool fastClimbing; + bool noMissClimbing; bool fastTears; // Preferences diff --git a/src/d/actor/d_a_alink_hang.inc b/src/d/actor/d_a_alink_hang.inc index a98020ce4f..89f11fc940 100644 --- a/src/d/actor/d_a_alink_hang.inc +++ b/src/d/actor/d_a_alink_hang.inc @@ -285,7 +285,11 @@ int daAlink_c::procHangStartInit() { offNoResetFlg2(FLG2_UNK_2000); if (checkHangFootWall()) { + #if TARGET_PC + if (!is_prev_hangReady && cM_rnd() < 0.7f && !dusk::getSettings().game.noMissClimbing) { + #else if (!is_prev_hangReady && cM_rnd() < 0.7f) { + #endif setSingleAnimeParam(ANM_CLIMB_HANG_MISS, &mpHIO->mLadder.m.mWallAttachMissAnm); field_0x3478 = mpHIO->mLadder.m.mWallAttachMissAnm.mCancelFrame; voiceStart(Z2SE_AL_V_FOOT_MISS); @@ -2084,7 +2088,11 @@ int daAlink_c::procClimbUpStartInit(int param_0) { speed.y = 0.0f; mNormalSpeed = 0.0f; + #if TARGET_PC + if (param_0 || var_r29 || cM_rnd() < 0.3f || dusk::getSettings().game.noMissClimbing) { + #else if (param_0 || var_r29 || cM_rnd() < 0.3f) { + #endif setSingleAnimeParam(ANM_CLIMB_HANG, &mpHIO->mLadder.m.mWallAttachAnm); field_0x3478 = mpHIO->mLadder.m.mWallAttachAnm.mCancelFrame; voiceStart(Z2SE_AL_V_JUMP_HANG); @@ -2161,7 +2169,11 @@ int daAlink_c::procClimbDownStartInit(s16 param_0) { deleteEquipItem(TRUE, FALSE); + #if TARGET_PC + if (cM_rnd() < 0.7f || dusk::getSettings().game.noMissClimbing) { + #else if (cM_rnd() < 0.7f) { + #endif setSingleAnimeParam(ANM_CLIMB_HANG, &mpHIO->mLadder.m.mWallAttachAnm); field_0x3478 = mpHIO->mLadder.m.mWallAttachAnm.mCancelFrame; mProcVar0.field_0x3008 = 0; diff --git a/src/dusk/imgui/ImGuiMenuEnhancements.cpp b/src/dusk/imgui/ImGuiMenuEnhancements.cpp index cf7fcc41d7..ec0a249f9f 100644 --- a/src/dusk/imgui/ImGuiMenuEnhancements.cpp +++ b/src/dusk/imgui/ImGuiMenuEnhancements.cpp @@ -37,6 +37,12 @@ namespace dusk { ImGui::SetTooltip("Quicker climbing on ladders and vines like the HD version"); } + ImGui::Checkbox("No Climbing Miss Animation", &getSettings().game.noMissClimbing); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Prevents Link from playing a struggle animation\n" + "when using the Clawshot on vines at a weird angle."); + } + ImGui::Checkbox("Faster Tears of Light", &getSettings().game.fastTears); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Tears of Light dropped by Shadow Insects pop out faster like the HD version"); diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index ea17eef374..f39c4fd5b3 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -31,6 +31,7 @@ UserSettings g_userSettings = { .damageMultiplier = 1, .instantDeath = false, .fastClimbing = false, + .noMissClimbing = false, .fastTears = false, // Preferences From afbb41bd37bb8c3a976dbd89602f7b8903addbd2 Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Sat, 4 Apr 2026 14:30:42 -0400 Subject: [PATCH 210/350] Can transform anywhere + Refactored checks to see if you can Quick Transform --- include/dusk/settings.h | 1 + src/d/actor/d_a_alink_dusk.cpp | 50 +++++++++++++----------- src/d/actor/d_a_midna.cpp | 11 ++++-- src/dusk/imgui/ImGuiMenuEnhancements.cpp | 6 +++ src/dusk/settings.cpp | 1 + 5 files changed, 44 insertions(+), 25 deletions(-) diff --git a/include/dusk/settings.h b/include/dusk/settings.h index 582c5a1872..2de5b376eb 100644 --- a/include/dusk/settings.h +++ b/include/dusk/settings.h @@ -47,6 +47,7 @@ struct UserSettings { // Cheats bool enableFastIronBoots; + bool canTransformAnywhere; // Technical bool restoreWiiGlitches; diff --git a/src/d/actor/d_a_alink_dusk.cpp b/src/d/actor/d_a_alink_dusk.cpp index 83a5d592a5..95218268ac 100644 --- a/src/d/actor/d_a_alink_dusk.cpp +++ b/src/d/actor/d_a_alink_dusk.cpp @@ -19,28 +19,6 @@ void daAlink_c::handleQuickTransform() { return; } - // Make sure Link isn't riding anything (horse, boar, etc.) - if (checkRide()) { - return; - } - - switch (mProcID) { - // Make sure Link is not underwater or talking to someone. - case PROC_TALK: - case PROC_SWIM_UP: - case PROC_SWIM_DIVE: - return; - // If Link is targeting or pulling a chain, we don't want to remove the ability to use items in combat accidently. - case PROC_ATN_ACTOR_MOVE: - case PROC_ATN_ACTOR_WAIT: - case PROC_WOLF_ATN_AC_MOVE: - break; - default: - // Disable the input that was just pressed, as sometimes it could cause items to be used or Wolf Link to dig. - mDoCPd_c::getCpadInfo(PAD_1).mPressedButtonFlags = 0; - break; - } - // Ensure there is a proper pointer to the mMeterClass and mpMeterDraw structs in g_meter2_info. const auto meterClassPtr = g_meter2_info.getMeterClass(); if (!meterClassPtr) { @@ -52,18 +30,46 @@ void daAlink_c::handleQuickTransform() { return; } + mDoCPd_c::getCpadInfo(PAD_1).mPressedButtonFlags = 0; + // Ensure that the Z Button is not dimmed if (meterDrawPtr->getButtonZAlpha() != 1.f) { + Z2GetAudioMgr()->seStart(Z2SE_SYS_ERROR, NULL, 0, 0, 1.0f, 1.0f, -1.0f, -1.0f, 0); return; } // The game will crash if trying to quick transform while holding the Ball and Chain if (mEquipItem == dItemNo_IRONBALL_e) { + Z2GetAudioMgr()->seStart(Z2SE_SYS_ERROR, NULL, 0, 0, 1.0f, 1.0f, -1.0f, -1.0f, 0); return; } // Use the game's default checks for if the player can currently transform if (!m_midnaActor->checkMetamorphoseEnableBase()) { + Z2GetAudioMgr()->seStart(Z2SE_SYS_ERROR, NULL, 0, 0, 1.0f, 1.0f, -1.0f, -1.0f, 0); + return; + } + + bool canTransform = false; + + if (mLinkAcch.ChkGroundHit() && !checkModeFlg(MODE_PLAYER_FLY) && !checkMagneBootsOn()) { + if (!checkForestOldCentury()) { + if (checkMidnaRide()) { + if ((checkWolf() && + (checkModeFlg(MODE_UNK_1000) || dComIfGp_checkPlayerStatus0(0, 0x10))) || + (!checkWolf() && + (checkEventRun() || getMidnaActor()->checkMetamorphoseEnable()) && + (checkModeFlg(4) || dComIfGp_checkPlayerStatus0(0, 0x10)))) + { + canTransform = true; + } + } + } + } + + if (!canTransform) + { + Z2GetAudioMgr()->seStart(Z2SE_SYS_ERROR, NULL, 0, 0, 1.0f, 1.0f, -1.0f, -1.0f, 0); return; } diff --git a/src/d/actor/d_a_midna.cpp b/src/d/actor/d_a_midna.cpp index 88a5bb38bb..c63f4e5251 100644 --- a/src/d/actor/d_a_midna.cpp +++ b/src/d/actor/d_a_midna.cpp @@ -3046,12 +3046,17 @@ void daMidna_c::setMidnaNoDrawFlg() { BOOL daMidna_c::checkMetamorphoseEnableBase() { BOOL tmp; - if ( - !daAlink_getAlinkActorClass()->checkMidnaRide() || (g_env_light.mEvilInitialized & 0x80) || + if (!daAlink_getAlinkActorClass()->checkMidnaRide() || (g_env_light.mEvilInitialized & 0x80) || /* dSv_event_flag_c::M_077 - Main Event - Get shadow crystal (can now transform) */ !dComIfGs_isEventBit(0xD04) || +#if TARGET_PC + (fopAcIt_Judge((fopAcIt_JudgeFunc)daMidna_searchNpc, &tmp) && + !dusk::getSettings().game.canTransformAnywhere) +#else fopAcIt_Judge((fopAcIt_JudgeFunc)daMidna_searchNpc, &tmp) - ) { +#endif + ) + { return FALSE; } return TRUE; diff --git a/src/dusk/imgui/ImGuiMenuEnhancements.cpp b/src/dusk/imgui/ImGuiMenuEnhancements.cpp index cf7fcc41d7..7f96becd75 100644 --- a/src/dusk/imgui/ImGuiMenuEnhancements.cpp +++ b/src/dusk/imgui/ImGuiMenuEnhancements.cpp @@ -70,6 +70,12 @@ namespace dusk { if (ImGui::BeginMenu("Cheats")) { ImGui::Checkbox("Fast Iron Boots", &getSettings().game.enableFastIronBoots); + ImGui::Checkbox("Can Transform Anywhere", + &getSettings().game.canTransformAnywhere); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Allows you to transform between forms even if NPCs are looking"); + } + ImGui::EndMenu(); } diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index ea17eef374..1ac11bdeb7 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -43,6 +43,7 @@ UserSettings g_userSettings = { // Cheats .enableFastIronBoots = false, + .canTransformAnywhere = false, // Technical .restoreWiiGlitches = false, From 845939d8b473681862ea7984287457093255ddbe Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Sat, 4 Apr 2026 14:31:16 -0400 Subject: [PATCH 211/350] removed period in description --- src/dusk/imgui/ImGuiMenuEnhancements.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dusk/imgui/ImGuiMenuEnhancements.cpp b/src/dusk/imgui/ImGuiMenuEnhancements.cpp index a0559c89b4..6a84e020c4 100644 --- a/src/dusk/imgui/ImGuiMenuEnhancements.cpp +++ b/src/dusk/imgui/ImGuiMenuEnhancements.cpp @@ -70,7 +70,7 @@ namespace dusk { if (ImGui::BeginMenu("Audio")) { ImGui::Checkbox("Non-Stop Midna's Lament", &getSettings().game.midnasLamentNonStop); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Prevents enemy music while Midna's Lament is playing."); + ImGui::SetTooltip("Prevents enemy music while Midna's Lament is playing"); } ImGui::EndMenu(); From be2a4e4e03d9733344d5b625f1542bc165e0f830 Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Sat, 4 Apr 2026 14:31:40 -0400 Subject: [PATCH 212/350] removed period in description --- src/dusk/imgui/ImGuiMenuEnhancements.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dusk/imgui/ImGuiMenuEnhancements.cpp b/src/dusk/imgui/ImGuiMenuEnhancements.cpp index ec0a249f9f..20ba760e83 100644 --- a/src/dusk/imgui/ImGuiMenuEnhancements.cpp +++ b/src/dusk/imgui/ImGuiMenuEnhancements.cpp @@ -40,7 +40,7 @@ namespace dusk { ImGui::Checkbox("No Climbing Miss Animation", &getSettings().game.noMissClimbing); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Prevents Link from playing a struggle animation\n" - "when using the Clawshot on vines at a weird angle."); + "when using the Clawshot on vines at a weird angle"); } ImGui::Checkbox("Faster Tears of Light", &getSettings().game.fastTears); From 9f08b7054f18520996f4e86a50022ed0df907d84 Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Sat, 4 Apr 2026 14:47:39 -0400 Subject: [PATCH 213/350] No Low HP Sound Enhancement --- include/dusk/settings.h | 3 +++ src/Z2AudioLib/Z2AudioMgr.cpp | 7 +++++++ src/dusk/imgui/ImGuiMenuEnhancements.cpp | 9 +++++++++ src/dusk/settings.cpp | 3 +++ 4 files changed, 22 insertions(+) diff --git a/include/dusk/settings.h b/include/dusk/settings.h index 582c5a1872..aaff862284 100644 --- a/include/dusk/settings.h +++ b/include/dusk/settings.h @@ -45,6 +45,9 @@ struct UserSettings { bool enableBloom; bool useWaterProjectionOffset; + // Audio + bool noLowHpSound; + // Cheats bool enableFastIronBoots; diff --git a/src/Z2AudioLib/Z2AudioMgr.cpp b/src/Z2AudioLib/Z2AudioMgr.cpp index 985c500aec..d6d78fb4b7 100644 --- a/src/Z2AudioLib/Z2AudioMgr.cpp +++ b/src/Z2AudioLib/Z2AudioMgr.cpp @@ -155,7 +155,14 @@ void Z2AudioMgr::zeldaGFrameWork() { mSpeechMgr.framework(); processSeFramework(); processBgmFramework(); + + #if TARGET_PC + if (!dusk::getSettings().game.noLowHpSound) { + processHeartGaugeSound(); + } + #else processHeartGaugeSound(); + #endif #if DEBUG mDebugSys.debugframework(); diff --git a/src/dusk/imgui/ImGuiMenuEnhancements.cpp b/src/dusk/imgui/ImGuiMenuEnhancements.cpp index cf7fcc41d7..b7309e0846 100644 --- a/src/dusk/imgui/ImGuiMenuEnhancements.cpp +++ b/src/dusk/imgui/ImGuiMenuEnhancements.cpp @@ -67,6 +67,15 @@ namespace dusk { ImGui::EndMenu(); } + if (ImGui::BeginMenu("Audio")) { + ImGui::Checkbox("No Low HP Sound", &getSettings().game.noLowHpSound); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Disable the beeping sound when having low health"); + } + + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Cheats")) { ImGui::Checkbox("Fast Iron Boots", &getSettings().game.enableFastIronBoots); diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index ea17eef374..625fb114ed 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -41,6 +41,9 @@ UserSettings g_userSettings = { .enableBloom = true, .useWaterProjectionOffset = false, + // Audio + .noLowHpSound = false, + // Cheats .enableFastIronBoots = false, From a00fb1c05c6e940cca4a162f25d9c40fd6489f36 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sat, 4 Apr 2026 22:47:48 +0200 Subject: [PATCH 214/350] Config system v1 Roughly inspired by what I've learned from my work on Space Station 14, without some of the unnecessary cruft and complexity. Implementation is relatively simple once I figured out all the template order shenanigans. --- CMakeLists.txt | 8 +- files.cmake | 6 + include/dusk/appname.hpp | 20 ++ include/dusk/config.hpp | 114 +++++++++++ include/dusk/config_var.hpp | 228 ++++++++++++++++++++++ include/dusk/io.hpp | 75 ++++++++ include/dusk/scope.hpp | 29 +++ include/dusk/settings.hpp | 24 +++ include/m_Do/m_Do_controller_pad.h | 5 +- src/d/actor/d_a_alink.cpp | 8 +- src/d/actor/d_a_alink_demo.inc | 4 +- src/d/actor/d_a_alink_dusk.cpp | 2 +- src/d/actor/d_a_alink_hvyboots.inc | 2 +- src/d/d_camera.cpp | 2 +- src/d/d_kankyo.cpp | 2 +- src/dusk/config.cpp | 230 +++++++++++++++++++++++ src/dusk/imgui/ImGuiConfig.hpp | 17 ++ src/dusk/imgui/ImGuiMenuEnhancements.cpp | 25 +-- src/dusk/imgui/ImGuiMenuEnhancements.hpp | 12 -- src/dusk/io.cpp | 143 ++++++++++++++ src/dusk/settings.cpp | 28 +++ src/m_Do/m_Do_graphic.cpp | 4 +- src/m_Do/m_Do_main.cpp | 39 +++- 23 files changed, 984 insertions(+), 43 deletions(-) create mode 100644 include/dusk/appname.hpp create mode 100644 include/dusk/config.hpp create mode 100644 include/dusk/config_var.hpp create mode 100644 include/dusk/io.hpp create mode 100644 include/dusk/scope.hpp create mode 100644 include/dusk/settings.hpp create mode 100644 src/dusk/config.cpp create mode 100644 src/dusk/imgui/ImGuiConfig.hpp create mode 100644 src/dusk/io.cpp create mode 100644 src/dusk/settings.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index e0cf678839..9e2be043ee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -69,6 +69,12 @@ FetchContent_Declare( ) FetchContent_MakeAvailable(cxxopts) +FetchContent_Declare(json + URL https://github.com/nlohmann/json/releases/download/v3.12.0/json.tar.xz + URL_HASH SHA256=42f6e95cad6ec532fd372391373363b62a14af6d771056dbfc86160e6dfff7aa + DOWNLOAD_EXTRACT_TIMESTAMP TRUE +) +FetchContent_MakeAvailable(json) include(files.cmake) @@ -108,7 +114,7 @@ target_include_directories(game_debug PUBLIC extern ${CMAKE_SOURCE_DIR}/build/${DUSK_TP_VERSION}/include build/${DUSK_TP_VERSION}/include) -target_link_libraries(game_debug PUBLIC aurora::core aurora::gx aurora::gd aurora::si aurora::vi aurora::pad aurora::mtx aurora::os aurora::dvd aurora::card) +target_link_libraries(game_debug PUBLIC aurora::core aurora::gx aurora::gd aurora::si aurora::vi aurora::pad aurora::mtx aurora::os aurora::dvd aurora::card nlohmann_json::nlohmann_json) set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) add_library(game SHARED ${DOLZEL_FILES} ${Z2AUDIOLIB_FILES} ${SSYSTEM_FILES} ${JSYSTEM_FILES} ${REL_FILES} ${DUSK_FILES} ${DOLPHIN_FILES} diff --git a/files.cmake b/files.cmake index 26cae0f1a9..8a9e7be4d5 100644 --- a/files.cmake +++ b/files.cmake @@ -1332,16 +1332,22 @@ set(DOLPHIN_FILES set(DUSK_FILES include/dusk/endian_gx.hpp + include/dusk/config.hpp + include/dusk/settings.hpp src/d/actor/d_a_alink_dusk.cpp src/dusk/asserts.cpp + src/dusk/config.cpp + src/dusk/settings.cpp src/dusk/logging.cpp src/dusk/layout.cpp src/dusk/stubs.cpp src/dusk/endian.cpp src/dusk/extras.c src/dusk/extras.cpp + src/dusk/io.cpp src/dusk/globals.cpp #src/dusk/m_Do_ext_dusk.cpp + src/dusk/imgui/ImGuiConfig.hpp src/dusk/imgui/ImGuiConsole.hpp src/dusk/imgui/ImGuiConsole.cpp src/dusk/imgui/ImGuiMenuGame.cpp diff --git a/include/dusk/appname.hpp b/include/dusk/appname.hpp new file mode 100644 index 0000000000..d5d5f40083 --- /dev/null +++ b/include/dusk/appname.hpp @@ -0,0 +1,20 @@ +#ifndef DUSK_APPNAME_HPP +#define DUSK_APPNAME_HPP + +namespace dusk { + /** + * \brief The internal application name for the game. + * + * This gets used for file paths and such, and cannot be changed! + */ + constexpr auto AppName = "Dusk"; + + /** + * \brief The internal organization name for the game. + * + * This gets used for file paths and such, and cannot be changed! + */ + constexpr auto OrgName = "TwilitRealm"; +} + +#endif // DUSK_APPNAME_HPP diff --git a/include/dusk/config.hpp b/include/dusk/config.hpp new file mode 100644 index 0000000000..5cd0807b3b --- /dev/null +++ b/include/dusk/config.hpp @@ -0,0 +1,114 @@ +#ifndef DUSK_CONFIG_HPP +#define DUSK_CONFIG_HPP + +#include +#include "nlohmann/json.hpp" +#include "config_var.hpp" + +namespace dusk::config { + +/** + * \brief Base class containing virtual functions used for save/load of CVars. + */ +class ConfigImplBase { +protected: + virtual ~ConfigImplBase() = default; + +public: + /** + * \brief Load a JSON value into a CVar at the Value layer. + */ + virtual void loadFromJson(ConfigVarBase& cVar, const nlohmann::json& jsonValue) const = 0; + + /** + * \brief Load a simple launch argument into the CVar at the Override layer. + */ + virtual void loadFromArg(ConfigVarBase& cVar, std::string_view stringValue) const = 0; + + /** + * \brief Dump the value contained in the CVar to JSON. + */ + [[nodiscard]] virtual nlohmann::json dumpToJson(const ConfigVarBase& cVar) const = 0; +}; + +template +class ConfigImpl : public ConfigImplBase { + // Just downcasting the references... + void loadFromJson(ConfigVarBase& cVar, const nlohmann::json& jsonValue) const final { + assert(typeid(cVar) == typeid(ConfigVar)); + loadFromJson(dynamic_cast&>(cVar), jsonValue); + } + + void loadFromArg(ConfigVarBase& cVar, std::string_view stringValue) const final { + assert(typeid(cVar) == typeid(ConfigVar)); + loadFromArg(dynamic_cast&>(cVar), stringValue); + } + + [[nodiscard]] nlohmann::json dumpToJson(const ConfigVarBase& cVar) const final { + assert(typeid(cVar) == typeid(ConfigVar)); + return dumpToJson(dynamic_cast&>(cVar)); + } + + /** + * \brief Load a JSON value into a CVar at the Value layer. + */ + static void loadFromJson(ConfigVar& cVar, const nlohmann::json& jsonValue); + + /** + * \brief Load a simple launch argument into the CVar at the Override layer. + */ + static void loadFromArg(ConfigVar& cVar, std::string_view stringValue); + + /** + * \brief Dump the value contained in the CVar to JSON. + */ + [[nodiscard]] static nlohmann::json dumpToJson(const ConfigVar& cVar); +}; + +/** + * \brief Thrown by config loading functions if the value provided is invalid for the CVar. + */ +class InvalidConfigError : public std::runtime_error { +public: + explicit InvalidConfigError(const char* what) : runtime_error(what) {} +}; + +/** + * \brief Register a CVar to make the config system aware of it. + * + * This must be done on startup *before* config has been loaded. + */ +void Register(ConfigVarBase& configVar); + +/** + * \brief Indicate that all registrations have happened and everything should lock in. + */ +void FinishRegistration(); + +/** + * \brief Load config from the standard user preferences location. + */ +void LoadFromUserPreferences(); +void LoadFromFileName(const char* path); + +/** + * \brief Save the config to file. + */ +void Save(); + +/** + * \brief Get a registered CVar by name. + * + * @return null if the CVar does not exist. + */ +ConfigVarBase* GetConfigVar(std::string_view name); + +template +const ConfigImplBase* GetConfigImpl() { + static ConfigImpl config; + return &config; +} + +} // namespace dusk::config + +#endif diff --git a/include/dusk/config_var.hpp b/include/dusk/config_var.hpp new file mode 100644 index 0000000000..b775aa0584 --- /dev/null +++ b/include/dusk/config_var.hpp @@ -0,0 +1,228 @@ +#ifndef DUSK_CONFIG_VAR_HPP +#define DUSK_CONFIG_VAR_HPP + +#include "dolphin/types.h" +#include +#include +#include + +/** + * The configuration system. + * + * Configuration works via "configuration variables" aka "CVars". Each stores a single value that + * may be individually written to/from a configuration file. + * + * CVars, like ogres, have layers. Higher layers (e.g. a set value) override lower layers + * (e.g. the default value). + * + * To define a CVar, simply make a global variable of type ConfigVar, + * and make sure Register() is called on it during program startup. + * + * config_var.hpp contains the simplest "just the configuration vars themselves". + * This should be safe to include for files that need to *access* configuration, + * without blowing up compile times on implementation details. + * + * config.hpp on the other hand contains far more calls for mutating, loading, and defining CVars. + */ +namespace dusk::config { + +/** + * \brief Layers that a configuration variable can currently be at. + * + * A configuration variable can be on one of multiple *layers*, which determines where + * the current value is coming from. + */ +enum class ConfigVarLayer : u8 { + /** + * The CVar is at the default value defined by the application code. + */ + Default, + + /** + * The CVar has been modified by the user and may be saved to config. + */ + Value, + + /** + * The CVar is modified by launch argument, overruling the normal config value. + * Will not get saved to config. + */ + Override, +}; + +class ConfigImplBase; + +/** + * Base class that all CVars inherit from. + * You want the templated ConfigVar instead for actual usage. + */ +class ConfigVarBase { +protected: + /** + * The name of this CVar, used in the configuration file. + */ + const char* name; + + /** + * Whether this CVar has been registered with the global managing logic. + * If this is not done, it is not functional. + */ + bool registered; + + /** + * The layer this CVar is at. + */ + ConfigVarLayer layer; + + /** + * Pointer to an implementation struct for various load/save calls. + */ + const ConfigImplBase* impl; + + ConfigVarBase(const char* name, const ConfigImplBase* impl); + virtual ~ConfigVarBase() = default; + + /** + * Check that the CVar is registered, aborting if this is not the case. + */ + void checkRegistered() const { + if (!registered) + abort(); + } + +public: + /** + * Get the name of this CVar, used in the configuration file. + */ + [[nodiscard]] const char* getName() const noexcept; + + /** + * Get the pointer to the implementation struct. + */ + [[nodiscard]] const ConfigImplBase* getImpl() const noexcept; + + /** + * Get the layer this CVar is currently at. + */ + [[nodiscard]] constexpr ConfigVarLayer getLayer() const noexcept { + return layer; + } + + /** + * Mark this CVar as being registered with the central save/load logic. + * This is necessary to make it legal to access. + */ + void markRegistered(); +}; + +template +concept ConfigValueInteger = + std::is_same_v + || std::is_same_v + || std::is_same_v + || std::is_same_v + || std::is_same_v + || std::is_same_v + || std::is_same_v + || std::is_same_v; + +/** + * \brief Concept that defines the legal set of types that can be used for CVar values. + * + * Valid types cannot be cv-qualified and must be basic primitive types (int, float, bool), + * strings, or enums of the basic primitives. + */ +template +concept ConfigValue = + !std::is_const_v + && !std::is_volatile_v + && (std::is_same_v + || ConfigValueInteger + || std::is_same_v + || std::is_same_v + || std::is_same_v + || (std::is_enum_v && ConfigValueInteger>)); + +template +const ConfigImplBase* GetConfigImpl(); + +/** + * \brief A CVar storing values. + * + * @tparam T The type of value stored in the CVar. + */ +template +class ConfigVar : public ConfigVarBase { + T defaultValue; + T value; + T overrideValue; + +public: + /** + * \brief Construct a CVar. + * + * @param name The name of this CVar. Must be unique. + * @param arg Arguments to forward to construct the default value. + */ + template + explicit ConfigVar(const char* name, Args&&... arg) + : ConfigVarBase(name, GetConfigImpl()), defaultValue(std::forward(arg)...), + value(), overrideValue() {} + + /** + * \brief Get the current value of the CVar. + */ + [[nodiscard]] constexpr const T& getValue() const noexcept { + checkRegistered(); + switch (layer) { + case ConfigVarLayer::Default: + return defaultValue; + case ConfigVarLayer::Value: + return value; + case ConfigVarLayer::Override: + return overrideValue; + default: + abort(); + } + } + + /** + * \brief Change the value of a CVar. + * + * The new value is always stored at the Value layer. + * + * @param newValue The new value the CVar will get. + * @param replaceOverride If true, clear an existing override layer if there is one. + * If this is false and there is an override layer, + * the result of getValue() will not change immediately. + */ + void setValue(T newValue, bool replaceOverride = true) { + checkRegistered(); + value = std::move(newValue); + + if (replaceOverride) { + overrideValue = {}; + layer = ConfigVarLayer::Value; + } else if (layer != ConfigVarLayer::Override) { + layer = ConfigVarLayer::Value; + } + } + + /** + * \brief Give a CVar an override value. + * + * This overrides (but does not replace) the apparent set value of this CVar. + * The overriden value will not get saved to config. + * + * @param newValue The new value the CVar will get. + */ + void setOverrideValue(T newValue) { + checkRegistered(); + overrideValue = std::move(newValue); + layer = ConfigVarLayer::Override; + } +}; + +} + +#endif // DUSK_CONFIG_VAR_HPP diff --git a/include/dusk/io.hpp b/include/dusk/io.hpp new file mode 100644 index 0000000000..fb71a77d68 --- /dev/null +++ b/include/dusk/io.hpp @@ -0,0 +1,75 @@ +#ifndef DUSK_IO_HPP +#define DUSK_IO_HPP + +#include + +// I can't believe it's 2026 and neither SDL (no error codes) nor +// C++ (no error codes) have a file system API functional enough for me to use. +// Here you go, this one's inspired by C#. I only wrote the functions I need. + +namespace dusk::io { + +/** + * \brief A simple file stream wrapping cstdio FILE*. + * + * Methods on this class throw appropriate C++ exceptions when an error occurs. + */ +class FileStream { + void* file; + +public: + FileStream() noexcept; + + /** + * \brief Take ownership of a FILE* handle. + */ + explicit FileStream(void* file); + FileStream(const FileStream& other) = delete; + FileStream(FileStream&& other) noexcept; + + ~FileStream(); + + /** + * \brief Open a file for reading at the given path. + */ + static FileStream OpenRead(const char* utf8Path); + + /** + * \brief Create a file for writing. + * + * If there is an existing file, its contents are demolished. + */ + static FileStream Create(const char* utf8Path); + + /** + * \brief Read the byte contents of a file directly into a vector. + */ + static std::vector ReadAllBytes(const char* utf8Path); + + /** + * \brief Read the byte contents of a file directly into a vector. + */ + static void WriteAllText(const char* utf8Path, std::string_view text); + + /** + * \brief Read the remaining contents of the file directly into a vector. + */ + std::vector ReadFull(); + + /** + * Get direct access to the underlying FILE* handle. + */ + [[nodiscard]] void* GetFileHandle() const noexcept { + return file; + } + + /** + * Write data to the file. + */ + void Write(const char* data, size_t dataLen); +}; + +} + + +#endif // DUSK_IO_HPP diff --git a/include/dusk/scope.hpp b/include/dusk/scope.hpp new file mode 100644 index 0000000000..e1cfcb9a58 --- /dev/null +++ b/include/dusk/scope.hpp @@ -0,0 +1,29 @@ +#ifndef DUSK_SCOPE_HPP +#define DUSK_SCOPE_HPP + +namespace dusk { + +/** + * A simple value wrapper that will destroy the value at the end of its scope. + * @tparam T The type of value contained. + * @tparam Destructor The type of function used to destroy the value. + */ +template + struct ScopeValue { + T value; + Destructor destructor; + + explicit ScopeValue(T value, Destructor destructor) : value(value), destructor(destructor) { + } + + ~ScopeValue() { + destructor(value); + } + + constexpr operator T&() const noexcept { + return value; + } + }; +} + +#endif // DUSK_SCOPE_HPP diff --git a/include/dusk/settings.hpp b/include/dusk/settings.hpp new file mode 100644 index 0000000000..79c9981647 --- /dev/null +++ b/include/dusk/settings.hpp @@ -0,0 +1,24 @@ +#ifndef DUSK_SETTINGS_HPP +#define DUSK_SETTINGS_HPP + +#include "config_var.hpp" + +namespace dusk::settings { + using namespace dusk::config; + + namespace enhancements { + extern ConfigVar FastIronBoots; + extern ConfigVar InvertCameraXAxis; + extern ConfigVar QuickTransform; + extern ConfigVar RestoreWiiGlitches; + extern ConfigVar EnableBloom; + extern ConfigVar UseWaterProjectionOffset; + extern ConfigVar MirrorMode; + + void Register(); + } + + void Register(); +} + +#endif // DUSK_SETTINGS_HPP diff --git a/include/m_Do/m_Do_controller_pad.h b/include/m_Do/m_Do_controller_pad.h index b8cee23ca3..57de651bc0 100644 --- a/include/m_Do/m_Do_controller_pad.h +++ b/include/m_Do/m_Do_controller_pad.h @@ -3,6 +3,7 @@ #include "JSystem/JUtility/JUTGamePad.h" #include "SSystem/SComponent/c_API_controller_pad.h" +#include "dusk/settings.hpp" #include "dusk/imgui/ImGuiMenuEnhancements.hpp" @@ -57,7 +58,7 @@ public: static s16 getStickAngle3D(u32 pad) { #if TARGET_PC - if (dusk::ImGuiMenuEnhancements::m_enhancements.mirrorMode) { + if (dusk::settings::enhancements::MirrorMode.getValue()) { return -getCpadInfo(pad).mMainStickAngle; } else { return getCpadInfo(pad).mMainStickAngle; @@ -69,7 +70,7 @@ public: static f32 getSubStickX3D(u32 pad) { #if TARGET_PC - if (dusk::ImGuiMenuEnhancements::m_enhancements.mirrorMode) { + if (dusk::settings::enhancements::MirrorMode.getValue()) { return -getCpadInfo(pad).mCStickPosX; } else { return getCpadInfo(pad).mCStickPosX; diff --git a/src/d/actor/d_a_alink.cpp b/src/d/actor/d_a_alink.cpp index 2c5bcf46cd..f847fde1cb 100644 --- a/src/d/actor/d_a_alink.cpp +++ b/src/d/actor/d_a_alink.cpp @@ -54,6 +54,8 @@ #include "res/Object/Alink.h" #include +#include "dusk/settings.hpp" + 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); @@ -7510,7 +7512,7 @@ void daAlink_c::setBlendMoveAnime(f32 i_morf) { BOOL sp24 = checkEventRun(); BOOL sp20 = checkBootsMoveAnime(1); #if TARGET_PC - if (dusk::ImGuiMenuEnhancements::m_enhancements.fastIronBoots) { + if (dusk::settings::enhancements::FastIronBoots.getValue()) { sp20 = FALSE; } #endif @@ -9475,7 +9477,7 @@ void daAlink_c::setStickData() { mHeavySpeedMultiplier = mpHIO->mItem.mIronBoots.m.mInputFactor; } #if TARGET_PC - if (dusk::ImGuiMenuEnhancements::m_enhancements.fastIronBoots) { + if (dusk::settings::enhancements::FastIronBoots.getValue()) { mHeavySpeedMultiplier = 1.0f; } #endif @@ -9487,7 +9489,7 @@ void daAlink_c::setStickData() { mHeavySpeedMultiplier = mpHIO->mItem.mIronBoots.m.mWaterInputFactor; } #if TARGET_PC - if (dusk::ImGuiMenuEnhancements::m_enhancements.fastIronBoots) { + if (dusk::settings::enhancements::FastIronBoots.getValue()) { 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 7acccbde13..936b0128d5 100644 --- a/src/d/actor/d_a_alink_demo.inc +++ b/src/d/actor/d_a_alink_demo.inc @@ -4301,7 +4301,7 @@ bool daAlink_c::checkAcceptWarp() { */ if (mLinkAcch.ChkGroundHit() && !checkModeFlg(MODE_PLAYER_FLY) #if TARGET_PC - && (dusk::ImGuiMenuEnhancements::m_enhancements.restoreWiiGlitches || !checkNoResetFlg0(FLG0_WATER_IN_MOVE)) + && (dusk::settings::enhancements::RestoreWiiGlitches.getValue() || !checkNoResetFlg0(FLG0_WATER_IN_MOVE)) #elif VERSION != VERSION_WII_USA_R0 && !checkNoResetFlg0(FLG0_WATER_IN_MOVE) #endif @@ -4312,7 +4312,7 @@ bool daAlink_c::checkAcceptWarp() { */ if ( #if TARGET_PC - (dusk::ImGuiMenuEnhancements::m_enhancements.restoreWiiGlitches || !getSlidePolygon(&plane)) && + (dusk::settings::enhancements::RestoreWiiGlitches.getValue() || !getSlidePolygon(&plane)) && #elif VERSION != VERSION_WII_USA_R0 !getSlidePolygon(&plane) && #endif diff --git a/src/d/actor/d_a_alink_dusk.cpp b/src/d/actor/d_a_alink_dusk.cpp index 47501d4f3d..f49443a0d0 100644 --- a/src/d/actor/d_a_alink_dusk.cpp +++ b/src/d/actor/d_a_alink_dusk.cpp @@ -6,7 +6,7 @@ #include "dusk/imgui/ImGuiMenuEnhancements.hpp" void daAlink_c::handleQuickTransform() { - if (!dusk::ImGuiMenuEnhancements::m_enhancements.quickTransform) { + if (!dusk::settings::enhancements::QuickTransform.getValue()) { return; } diff --git a/src/d/actor/d_a_alink_hvyboots.inc b/src/d/actor/d_a_alink_hvyboots.inc index ed52ffcb36..af00c28b1b 100644 --- a/src/d/actor/d_a_alink_hvyboots.inc +++ b/src/d/actor/d_a_alink_hvyboots.inc @@ -351,7 +351,7 @@ int daAlink_c::procMagneBootsFly() { */ if (dComIfG_Bgsp().ChkPolySafe(mPolyInfo2) #if TARGET_PC - && (dusk::ImGuiMenuEnhancements::m_enhancements.restoreWiiGlitches || checkEquipHeavyBoots()) + && (dusk::settings::enhancements::RestoreWiiGlitches.getValue() || 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 59ed1d2c11..6e63d8b940 100644 --- a/src/d/d_camera.cpp +++ b/src/d/d_camera.cpp @@ -766,7 +766,7 @@ void dCamera_c::updatePad() { var_f31 = mDoCPd_c::getSubStickX3D(mPadID); #if TARGET_PC - if (dusk::ImGuiMenuEnhancements::m_enhancements.invertCameraXAxis) { + if (dusk::settings::enhancements::InvertCameraXAxis.getValue()) { var_f31 *= -1.0f; } #endif diff --git a/src/d/d_kankyo.cpp b/src/d/d_kankyo.cpp index a61219ffac..e772098eae 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::ImGuiMenuEnhancements::m_enhancements.useWaterProjectionOffset ? -0.01f : 0.0f, 0.0f); + dusk::settings::enhancements::UseWaterProjectionOffset.getValue() ? -0.01f : 0.0f, 0.0f); #else -0.01f, 0.0f); #endif diff --git a/src/dusk/config.cpp b/src/dusk/config.cpp new file mode 100644 index 0000000000..aeafb5ab56 --- /dev/null +++ b/src/dusk/config.cpp @@ -0,0 +1,230 @@ +#include "dusk/config.hpp" +#include "SDL3/SDL_filesystem.h" +#include "SDL3/SDL_iostream.h" +#include "dusk/appname.hpp" +#include "dusk/scope.hpp" +#include "fmt/format.h" +#include "nlohmann/json.hpp" +#include "absl/container/flat_hash_map.h" + +#include "aurora/lib/logging.hpp" +#include "dusk/io.hpp" + +#include +#include + +using namespace dusk::config; + +constexpr auto ConfigFileName = "config.json"; + +using json = nlohmann::json; + +aurora::Module DuskConfigLog("dusk::config"); + +static absl::flat_hash_map RegisteredConfigVars; +static bool RegistrationDone; + +static std::string GetConfigJsonPath() { + dusk::ScopeValue folder(SDL_GetPrefPath(dusk::OrgName, dusk::AppName), SDL_free); + if (folder.value == nullptr) { + DuskConfigLog.error("Failed to get user preference path: %s", SDL_GetError()); + return ""; + } + + return fmt::format("{}{}", folder.value, ConfigFileName); +} + +ConfigVarBase::ConfigVarBase(const char* name, const ConfigImplBase* impl) : name(name), registered(false), layer(ConfigVarLayer::Default), impl(impl) { +} + +const char* ConfigVarBase::getName() const noexcept { + return name; +} + +const ConfigImplBase* ConfigVarBase::getImpl() const noexcept { + return impl; +} + +template +void ConfigImpl::loadFromJson(ConfigVar& cVar, const json& jsonValue) { + cVar.setValue(jsonValue.get(), false); +} + +template +nlohmann::json ConfigImpl::dumpToJson(const ConfigVar& cVar) { + return cVar.getValue(); +} + +template requires std::is_integral_v && std::is_signed_v +static void loadFromArgImpl(ConfigVar& cVar, const std::string_view stringValue) { + const std::string str(stringValue); + const auto result = std::stoll(str); + if (result >= std::numeric_limits::min() && result <= std::numeric_limits::max()) { + cVar.setOverrideValue(result); + } else { + throw std::out_of_range("Value is too large"); + } +} + +template requires std::is_integral_v && std::is_unsigned_v +static void loadFromArgImpl(ConfigVar& cVar, const std::string_view stringValue) { + const std::string str(stringValue); + const auto result = std::stoull(str); + if (result <= std::numeric_limits::max()) { + cVar.setOverrideValue(result); + } else { + throw std::out_of_range("Value is too large"); + } +} + +static void loadFromArgImpl(ConfigVar& cVar, const std::string_view stringValue) { + const std::string str(stringValue); + const auto result = std::stof(str); + cVar.setOverrideValue(result); +} + +static void loadFromArgImpl(ConfigVar& cVar, const std::string_view stringValue) { + const std::string str(stringValue); + const auto result = std::stod(str); + cVar.setOverrideValue(result); +} + +static void loadFromArgImpl(ConfigVar& cVar, const std::string_view stringValue) { + cVar.setOverrideValue(std::string(stringValue)); +} + +template +void ConfigImpl::loadFromArg(ConfigVar& cVar, const std::string_view stringValue) { + loadFromArgImpl(cVar, stringValue); +} + +template<> +void ConfigImpl::loadFromArg(ConfigVar& cVar, const std::string_view stringValue) { + if (stringValue == "1" || stringValue == "TRUE" || stringValue == "true" || stringValue == "True") { + cVar.setOverrideValue(true); + } else if (stringValue == "0" || stringValue == "FALSE" || stringValue == "false" || stringValue == "False") { + cVar.setOverrideValue(false); + } else { + throw InvalidConfigError("Value cannot be parsed as boolean"); + } +} + +// My IDE is convinced this namespace is necessary. It shouldn't be AFAICT? +namespace dusk::config { + template class ConfigImpl; + template class ConfigImpl; + template class ConfigImpl; + template class ConfigImpl; + template class ConfigImpl; + template class ConfigImpl; + template class ConfigImpl; + template class ConfigImpl; + template class ConfigImpl; + template class ConfigImpl; + template class ConfigImpl; + template class ConfigImpl; +} + +void dusk::config::Register(ConfigVarBase& configVar) { + const auto& name = configVar.getName(); + if (RegistrationDone) { + DuskConfigLog.fatal("Tried to register CVar {} after registrations closed!", name); + } + + if (RegisteredConfigVars.contains(name)) { + DuskConfigLog.fatal("Tried to register CVar {} twice!", name); + } + + RegisteredConfigVars[name] = &configVar; + configVar.markRegistered(); +} + +void ConfigVarBase::markRegistered() { + if (registered) + abort(); + + registered = true; +} + +void dusk::config::FinishRegistration() { + RegistrationDone = true; +} + +void dusk::config::LoadFromUserPreferences() { + const auto configPath = GetConfigJsonPath(); + if (configPath.empty()) { + return; + } + LoadFromFileName(configPath.c_str()); +} + +static void LoadFromPath(const char* path) { + auto data = dusk::io::FileStream::ReadAllBytes(path); + + json j = json::parse(data); + if (!j.is_object()) { + DuskConfigLog.error("Config JSON is not an object!"); + return; + } + + for (const auto& el : j.items()) { + const auto& key = el.key(); + auto configVar = RegisteredConfigVars.find(key); + if (configVar == RegisteredConfigVars.end()) { + DuskConfigLog.error("Unknown key '{}' found in config!", key); + continue; + } + + try { + configVar->second->getImpl()->loadFromJson(*configVar->second, el.value()); + } catch (std::exception& e) { + DuskConfigLog.error("Failed to load key '{}' from config: {}", key, e.what()); + } + } +} + +void dusk::config::LoadFromFileName(const char* path) { + if (!RegistrationDone) { + DuskConfigLog.fatal("Registration not finished yet!"); + } + + DuskConfigLog.info("Loading config from '{}'", path); + + try { + LoadFromPath(path); + } catch (const std::system_error& e) { + if (e.code() == std::errc::no_such_file_or_directory) { + DuskConfigLog.info("Config file did not exist, staying with defaults"); + } else { + DuskConfigLog.error("Failed to load from config! {}", e.what()); + } + } +} + +void dusk::config::Save() { + const auto configPath = GetConfigJsonPath(); + if (configPath.empty()) { + return; + } + + DuskConfigLog.info("Saving config to '{}'", configPath); + + json j; + + for (const auto& pair : RegisteredConfigVars) { + if (pair.second->getLayer() == ConfigVarLayer::Value) { + j[pair.first] = pair.second->getImpl()->dumpToJson(*pair.second); + } + } + + io::FileStream::WriteAllText(configPath.c_str(), j.dump(4)); +} + +ConfigVarBase* dusk::config::GetConfigVar(std::string_view name) { + const auto configVar = RegisteredConfigVars.find(name); + if (configVar != RegisteredConfigVars.end()) { + return configVar->second; + } + + return nullptr; +} \ No newline at end of file diff --git a/src/dusk/imgui/ImGuiConfig.hpp b/src/dusk/imgui/ImGuiConfig.hpp new file mode 100644 index 0000000000..c4ecb61d77 --- /dev/null +++ b/src/dusk/imgui/ImGuiConfig.hpp @@ -0,0 +1,17 @@ +#ifndef DUSK_IMGUICONFIG_HPP +#define DUSK_IMGUICONFIG_HPP + +#include "dusk/config.hpp" +#include "imgui.h" + +namespace dusk::config { + inline void ImguiCheckbox(const char* title, ConfigVar& var) { + bool copy = var.getValue(); + if (ImGui::Checkbox(title, ©)) { + var.setValue(copy); + Save(); + } + } +} + +#endif // DUSK_IMGUICONFIG_HPP diff --git a/src/dusk/imgui/ImGuiMenuEnhancements.cpp b/src/dusk/imgui/ImGuiMenuEnhancements.cpp index 5ea315d8ec..c582bc2335 100644 --- a/src/dusk/imgui/ImGuiMenuEnhancements.cpp +++ b/src/dusk/imgui/ImGuiMenuEnhancements.cpp @@ -4,43 +4,36 @@ #include "ImGuiConsole.hpp" #include "ImGuiMenuEnhancements.hpp" +#include "ImGuiConfig.hpp" +#include "dusk/settings.hpp" #include namespace dusk { - EnhancementsSettings ImGuiMenuEnhancements::m_enhancements = { - .fastIronBoots = false, - .invertCameraXAxis = false, - .restoreWiiGlitches = false, - .enableBloom = true, - .useWaterProjectionOffset = false, - .mirrorMode = 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); + config::ImguiCheckbox("Fast Iron Boots", settings::enhancements::FastIronBoots); + config::ImguiCheckbox("Invert Camera X Axis", settings::enhancements::InvertCameraXAxis); + config::ImguiCheckbox("Quick Transform (R+Y)", settings::enhancements::QuickTransform); ImGui::EndMenu(); } if (ImGui::BeginMenu("Graphics")) { - ImGui::Checkbox("Native Bloom", &m_enhancements.enableBloom); - ImGui::Checkbox("Water Projection Offset", &m_enhancements.useWaterProjectionOffset); + config::ImguiCheckbox("Native Bloom", settings::enhancements::EnableBloom); + config::ImguiCheckbox("Water Projection Offset", settings::enhancements::UseWaterProjectionOffset); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Adds GC-specific -0.01 transS offset\n" "that causes ~6px ghost artifacts in water reflections"); } - ImGui::Checkbox("Mirror Mode", &m_enhancements.mirrorMode); + config::ImguiCheckbox("Mirror Mode", settings::enhancements::MirrorMode); ImGui::EndMenu(); } if (ImGui::BeginMenu("Restorations")) { - ImGui::Checkbox("Restore Wii 1.0 Glitches", &m_enhancements.restoreWiiGlitches); + config::ImguiCheckbox("Restore Wii 1.0 Glitches", settings::enhancements::RestoreWiiGlitches); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Restores patched glitches from Wii USA 1.0, the first released version"); } diff --git a/src/dusk/imgui/ImGuiMenuEnhancements.hpp b/src/dusk/imgui/ImGuiMenuEnhancements.hpp index 0238317ce4..f40baaad65 100644 --- a/src/dusk/imgui/ImGuiMenuEnhancements.hpp +++ b/src/dusk/imgui/ImGuiMenuEnhancements.hpp @@ -8,22 +8,10 @@ #include "imgui.h" namespace dusk { - struct EnhancementsSettings { - bool fastIronBoots; - bool invertCameraXAxis; - bool quickTransform; - bool restoreWiiGlitches; - bool enableBloom; - bool useWaterProjectionOffset; - bool mirrorMode; - }; - class ImGuiMenuEnhancements { public: ImGuiMenuEnhancements(); void draw(); - - static EnhancementsSettings m_enhancements; }; } diff --git a/src/dusk/io.cpp b/src/dusk/io.cpp new file mode 100644 index 0000000000..f930c2ceaa --- /dev/null +++ b/src/dusk/io.cpp @@ -0,0 +1,143 @@ +#include "dusk/io.hpp" +#include +#include + +using namespace dusk::io; + +#if _WIN32 +#define MODE_TYPE wchar_t +#define MODE(val) L##val +#define ftell(file) _ftelli64(file) +#define fseek(a, b, c) _fseeki64(a, b, c) +#else +#define MODE_TYPE char +#define MODE(val) ##val +#endif + +static FILE* ThrowIfNotOpen(const FileStream& file) { + if (!file.GetFileHandle()) { + throw std::runtime_error("Invalid file handle!"); + } + + return static_cast(file.GetFileHandle()); +} + +[[noreturn]] static void ThrowForError(int code) { + throw std::system_error(std::make_error_code(static_cast(code))); +} + +static FILE* OpenCore(const std::filesystem::path& path, const MODE_TYPE* mode) { + FILE* file; + + int err; +#if _WIN32 + static_assert(std::is_same_v); + err = _wfopen_s(&file, path.c_str(), mode); +#else + errno = 0; + static_assert(std::is_same_v); + file = fopen(path.c_str(), mode); + err = errno; +#endif + + if (!file) { + ThrowForError(err); + } + + return file; +} + +static FILE* OpenCore(const char* path, const MODE_TYPE* mode) { + return OpenCore(reinterpret_cast(path), mode); +} + +FileStream::FileStream() noexcept : file(nullptr) { +} + +FileStream::FileStream(void* file) : file(file) { + if (!file) { + CRASH("Invalid file handle"); + } +} + +FileStream::FileStream(FileStream&& other) noexcept { + file = other.file; + other.file = nullptr; +} + +FileStream::~FileStream() { + if (file) + fclose(static_cast(file)); +} + +FileStream FileStream::OpenRead(const char* utf8Path) { + return FileStream(OpenCore(utf8Path, MODE("rb"))); +} + +FileStream FileStream::Create(const char* utf8Path) { + return FileStream(OpenCore(utf8Path, MODE("wb"))); +} + +std::vector FileStream::ReadFull() { + const auto fileHandle = ThrowIfNotOpen(*this); + + std::vector result; + + const auto startPos = ftell(fileHandle); + if (startPos == -1) { + ThrowForError(errno); + } + + auto seekRes = fseek(fileHandle, 0, SEEK_END); + if (seekRes != 0) { + ThrowForError(errno); + } + + const auto endPos = ftell(fileHandle); + if (endPos == -1) { + ThrowForError(errno); + } + + seekRes = fseek(fileHandle, startPos, SEEK_SET); + if (seekRes != 0) { + ThrowForError(errno); + } + + result.resize(endPos - startPos); + + if (result.empty()) { + return result; + } + + auto ret = fread(result.data(), 1, result.size(), fileHandle); + if (ret < result.size()) { + int err = errno; + // Error or EOF + if (feof(fileHandle)) { + result.resize(ret); + } else { + ThrowForError(err); + } + } + + return result; +} + +std::vector FileStream::ReadAllBytes(const char* utf8Path) { + auto handle = OpenRead(utf8Path); + return std::move(handle.ReadFull()); +} + +void FileStream::Write(const char* data, size_t dataLen) { + FILE* fileHandle = ThrowIfNotOpen(*this); + + const auto ret = fwrite(data, 1, dataLen, fileHandle); + if (ret < dataLen) { + ThrowForError(errno); + } +} + +void FileStream::WriteAllText(const char* utf8Path, const std::string_view text) { + auto handle = Create(utf8Path); + handle.Write(text.data(), text.size()); +} diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp new file mode 100644 index 0000000000..2be800b6a5 --- /dev/null +++ b/src/dusk/settings.cpp @@ -0,0 +1,28 @@ +#include "dusk/settings.hpp" +#include "dusk/config.hpp" + +namespace dusk::settings::enhancements { + ConfigVar FastIronBoots("enhancements.fast_iron_boots", false); + ConfigVar InvertCameraXAxis("enhancements.invert_camera_x_axis", false); + ConfigVar QuickTransform("enhancements.quick_transform", false); + ConfigVar RestoreWiiGlitches("enhancements.restore_wii_glitches", false); + ConfigVar EnableBloom("enhancements.enable_bloom", true); + ConfigVar UseWaterProjectionOffset("enhancements.use_water_projection_offset", false); + ConfigVar MirrorMode("enhancements.mirror_mode", false); + + void Register() { + Register(FastIronBoots); + Register(InvertCameraXAxis); + Register(QuickTransform); + Register(RestoreWiiGlitches); + Register(EnableBloom); + Register(UseWaterProjectionOffset); + Register(MirrorMode); + } +} + +namespace dusk::settings { + void Register() { + enhancements::Register(); + } +} diff --git a/src/m_Do/m_Do_graphic.cpp b/src/m_Do/m_Do_graphic.cpp index 06d6efd4eb..8ecc4bbb8f 100644 --- a/src/m_Do/m_Do_graphic.cpp +++ b/src/m_Do/m_Do_graphic.cpp @@ -1163,7 +1163,7 @@ void mDoGph_gInf_c::bloom_c::remove() { void mDoGph_gInf_c::bloom_c::draw() { #if TARGET_PC - if (!dusk::ImGuiMenuEnhancements::m_enhancements.enableBloom) { + if (!dusk::settings::enhancements::EnableBloom.getValue()) { return; } #endif @@ -2113,7 +2113,7 @@ int mDoGph_Painter() { #endif #if TARGET_PC - if (dusk::ImGuiMenuEnhancements::m_enhancements.mirrorMode) + if (dusk::settings::enhancements::MirrorMode.getValue()) #elif PLATFORM_WII if (data_8053a730) #endif diff --git a/src/m_Do/m_Do_main.cpp b/src/m_Do/m_Do_main.cpp index 03ff6b8c0f..b8d6fe9728 100644 --- a/src/m_Do/m_Do_main.cpp +++ b/src/m_Do/m_Do_main.cpp @@ -56,6 +56,8 @@ #include #include "cxxopts.hpp" +#include "dusk/config.hpp" +#include "dusk/settings.hpp" // --- GLOBALS --- s8 mDoMain::developmentMode = -1; @@ -225,10 +227,41 @@ static AuroraBackend ParseAuroraBackend(const std::string& value) { exit(1); } +static void ApplyCVarOverrides(const cxxopts::OptionValue& option) { + if (option.count() == 0) { + return; + } + + const auto& cVars = option.as>(); + for (const auto& cvarArg : cVars) { + const auto sep = cvarArg.find('='); + if (sep == std::string::npos) { + DuskLog.fatal("--cvar argument has no '=': '{}'", cvarArg); + continue; + } + + const auto name = std::string_view(cvarArg).substr(0, sep); + const auto value = std::string_view(cvarArg).substr(sep + 1); + + const auto cVar = dusk::config::GetConfigVar(name); + if (!cVar) { + DuskLog.fatal("Unknown --cvar name: '{}'", name); + } + + try { + cVar->getImpl()->loadFromArg(*cVar, value); + } catch (const std::exception& e) { + DuskLog.fatal("Unable to parse: '{}': {}", value, e.what()); + } + } +} + // ========================================================================= // PC ENTRY POINT // ========================================================================= int game_main(int argc, char* argv[]) { + dusk::settings::Register(); + cxxopts::ParseResult parsed_arg_options; try { @@ -238,7 +271,8 @@ int game_main(int argc, char* argv[]) { ("l,log-level", "Log level from " + std::to_string(AuroraLogLevel::LOG_DEBUG) + " to " + std::to_string(AuroraLogLevel::LOG_FATAL), cxxopts::value()->default_value("0")) ("h,help", "Print usage") ("dvd", "Path to DVD image file", cxxopts::value()->default_value("game.iso")) - ("backend", "Graphics API backend to use (auto, d3d11, d3d12, metal, vulkan, opengl, opengles, webgpu, null)", cxxopts::value()->default_value("auto")); + ("backend", "Graphics API backend to use (auto, d3d11, d3d12, metal, vulkan, opengl, opengles, webgpu, null)", cxxopts::value()->default_value("auto")) + ("cvar", "Override configuration variables without modifying config", cxxopts::value>()); arg_options.parse_positional({"dvd"}); arg_options.positional_help(""); @@ -257,6 +291,9 @@ int game_main(int argc, char* argv[]) { exit(1); } + dusk::config::LoadFromUserPreferences(); + ApplyCVarOverrides(parsed_arg_options["cvar"]); + AuroraConfig config{}; config.appName = "Dusk"; config.windowPosX = -1; From 63422ecdd10e811917e0334e9c3d9d5b17c91b83 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sat, 4 Apr 2026 22:49:51 +0200 Subject: [PATCH 215/350] I realize that's an important comment for C++ --- include/dusk/config_var.hpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/dusk/config_var.hpp b/include/dusk/config_var.hpp index b775aa0584..bb1710662c 100644 --- a/include/dusk/config_var.hpp +++ b/include/dusk/config_var.hpp @@ -171,6 +171,9 @@ public: /** * \brief Get the current value of the CVar. + * + * This reference is not guaranteed to remain up-to-date after modification of the CVar. + * It will, however, remain sound to access. */ [[nodiscard]] constexpr const T& getValue() const noexcept { checkRegistered(); From ae42e75e1b27492a9a0bd01629a41ce7960d50cd Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sat, 4 Apr 2026 23:00:33 +0200 Subject: [PATCH 216/350] Implicit conversion operator is probably a good idea tbh. --- include/dusk/config_var.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/dusk/config_var.hpp b/include/dusk/config_var.hpp index bb1710662c..064d37028d 100644 --- a/include/dusk/config_var.hpp +++ b/include/dusk/config_var.hpp @@ -211,6 +211,10 @@ public: } } + operator const T&() { + return getValue(); + } + /** * \brief Give a CVar an override value. * From ed7263ee11a0f71abcfcbf1417357b3d1c3c5573 Mon Sep 17 00:00:00 2001 From: madeline Date: Sat, 4 Apr 2026 15:59:18 -0700 Subject: [PATCH 217/350] better reverb --- src/dusk/audio/DuskDsp.cpp | 36 ++++++++++++++++++++++++------------ src/dusk/audio/DuskDsp.hpp | 2 +- src/dusk/settings.cpp | 2 +- 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/src/dusk/audio/DuskDsp.cpp b/src/dusk/audio/DuskDsp.cpp index fd1ae66620..3ca34bfa20 100644 --- a/src/dusk/audio/DuskDsp.cpp +++ b/src/dusk/audio/DuskDsp.cpp @@ -139,20 +139,32 @@ void dusk::audio::DspRender(OutputSubframe& subframe) { if (!skipRender) { ValidateChannel(channel); RenderChannel(channel, channelAux, channelSubframe); - // todo figure out an actual way to convert this accurately to dry/wet - channelAux.reverb.setwet(((channel.mAutoMixerFxMix >> 8) / 255.0f)); - channelAux.reverb.setdry(1.0f - channelAux.reverb.getwet()); } if (EnableReverb) { + // scale the input to the reverb rather than using wet/dry on the output. + // this way the reverb's internal buffers accumulate energy proportional to mAutoMixerFxMix, + // so any tail always decays at the correct level regardless of mAutoMixerFxMix changes + // prevents transients when the next sound starts playing with a different reverb level + // 700.0f was pulled out of my ass and just sounds good enough for console + f32 inputGain = (!skipRender) ? (channel.mAutoMixerFxMix >> 8) / 700.0f : 0.0f; + + OutputSubframe reverbSubframe = {}; + for (int j = 0; j < DSP_SUBFRAME_SIZE; j++) { + reverbSubframe.channels[0][j] = channelSubframe.channels[0][j] * inputGain; + reverbSubframe.channels[1][j] = channelSubframe.channels[1][j] * inputGain; + } + channelAux.reverb.processreplace( - channelSubframe.channels[0].data(), - channelSubframe.channels[1].data(), - channelSubframe.channels[0].data(), - channelSubframe.channels[1].data(), - DSP_SUBFRAME_SIZE, - 1 + reverbSubframe.channels[0].data(), reverbSubframe.channels[1].data(), + reverbSubframe.channels[0].data(), reverbSubframe.channels[1].data(), + DSP_SUBFRAME_SIZE, 1 ); + + for (int j = 0; j < DSP_SUBFRAME_SIZE; j++) { + channelSubframe.channels[0][j] += reverbSubframe.channels[0][j]; + channelSubframe.channels[1][j] += reverbSubframe.channels[1][j]; + } } for (int o = 0; o < subframe.channels.size(); o++) { @@ -481,10 +493,10 @@ static void RenderChannel( void dusk::audio::DspInit() { for (int i = 0; i < DSP_CHANNELS; i++) { auto& channelAux = ChannelAux[i]; - channelAux.reverb.setwet(0.0f); - channelAux.reverb.setdry(1.0f); + channelAux.reverb.setwet(1.0f); + channelAux.reverb.setdry(0.0f); channelAux.reverb.setroomsize(0.2f); - channelAux.reverb.setdamp(0.3f); + channelAux.reverb.setdamp(0.7f); channelAux.reverb.setwidth(1.0f); channelAux.reverb.setmode(0.0f); channelAux.reverb.mute(); diff --git a/src/dusk/audio/DuskDsp.hpp b/src/dusk/audio/DuskDsp.hpp index 3c414375f3..520a948208 100644 --- a/src/dusk/audio/DuskDsp.hpp +++ b/src/dusk/audio/DuskDsp.hpp @@ -54,7 +54,7 @@ namespace dusk::audio { // basically stores our position between resamplePrev and decodeBuf[0] so we don't lose that fractional resampler position next subframe f32 resamplePos; // last consumed sample from decodeBuf - s16 resamplePrev; + s16 resamplePrev; }; extern ChannelAuxData ChannelAux[DSP_CHANNELS]; diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index ed2071ca30..14269e833b 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -12,7 +12,7 @@ UserSettings g_userSettings = { // Audio .audio = { - .masterVolume = 1.0f, + .masterVolume =0.8f, .mainMusicVolume = 1.0f, .subMusicVolume = 1.0f, .soundEffectsVolume = 1.0f, From 9e050ccea9184a3cf81a1b9169accab31d0a7041 Mon Sep 17 00:00:00 2001 From: madeline Date: Sat, 4 Apr 2026 16:07:36 -0700 Subject: [PATCH 218/350] h --- src/dusk/settings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index 14269e833b..db1e9e8b92 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -12,7 +12,7 @@ UserSettings g_userSettings = { // Audio .audio = { - .masterVolume =0.8f, + .masterVolume = 0.8f, .mainMusicVolume = 1.0f, .subMusicVolume = 1.0f, .soundEffectsVolume = 1.0f, From 52fed2b6938a252a36ce23d7bec4305d2a555d9f Mon Sep 17 00:00:00 2001 From: madeline Date: Sat, 4 Apr 2026 17:14:09 -0700 Subject: [PATCH 219/350] dump audio and bigger room size --- src/dusk/audio/DuskAudioSystem.cpp | 16 ------------ src/dusk/audio/DuskDsp.cpp | 40 +++++++++++++++++++++++++++++- src/dusk/audio/DuskDsp.hpp | 1 + src/dusk/imgui/ImGuiAudio.cpp | 2 ++ 4 files changed, 42 insertions(+), 17 deletions(-) diff --git a/src/dusk/audio/DuskAudioSystem.cpp b/src/dusk/audio/DuskAudioSystem.cpp index 93e161512f..c7313749c2 100644 --- a/src/dusk/audio/DuskAudioSystem.cpp +++ b/src/dusk/audio/DuskAudioSystem.cpp @@ -3,8 +3,6 @@ #include #include #include -#include -#include #include #include "JSystem/JAudio2/JASAiCtrl.h" @@ -17,8 +15,6 @@ #include "JSystem/JAudio2/JASAudioThread.h" #include "JSystem/JAudio2/JASDriverIF.h" -// #define DUSK_DUMP_AUDIO - using namespace dusk::audio; static OutputSubframe OutBuffer; @@ -91,10 +87,6 @@ void SDLCALL GetNewAudio( } } -#if defined(DUSK_DUMP_AUDIO) -static std::ofstream outRaw("guh.raw", std::ios_base::out | std::ios_base::binary); -#endif - int RenderNewAudioFrame() { JASCriticalSection section; const u32 countSubframes = JASDriver::getSubFrames(); @@ -107,10 +99,6 @@ int RenderNewAudioFrame() { JASAudioThread::snIntCount -= 1; } -#if defined(DUSK_DUMP_AUDIO) - outRaw.flush(); -#endif - return static_cast(countSubframes) * DSP_SUBFRAME_SIZE; } @@ -147,10 +135,6 @@ void RenderAudioSubframe() { } } -#if defined(DUSK_DUMP_AUDIO) - outRaw.write((const char*)OutInterleaveBuffer.data(), sizeof(OutInterleaveBuffer)); -#endif - SDL_PutAudioStreamData(PlaybackStream, &OutInterleaveBuffer, sizeof(OutInterleaveBuffer)); } diff --git a/src/dusk/audio/DuskDsp.cpp b/src/dusk/audio/DuskDsp.cpp index 3ca34bfa20..e104749991 100644 --- a/src/dusk/audio/DuskDsp.cpp +++ b/src/dusk/audio/DuskDsp.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include "Adpcm.hpp" @@ -17,9 +18,30 @@ using namespace dusk::audio; ChannelAuxData dusk::audio::ChannelAux[DSP_CHANNELS] = {}; +static bool sDumpWasActive = false; +static FILE* sChannelDumpFiles[DSP_CHANNELS] = {}; + +static void OpenChannelDumpFiles() { + char name[32]; + for (int i = 0; i < DSP_CHANNELS; i++) { + snprintf(name, sizeof(name), "channel_%02d.raw", i); + sChannelDumpFiles[i] = fopen(name, "wb"); + } +} + +static void CloseChannelDumpFiles() { + for (int i = 0; i < DSP_CHANNELS; i++) { + if (sChannelDumpFiles[i]) { + fclose(sChannelDumpFiles[i]); + sChannelDumpFiles[i] = nullptr; + } + } +} + f32 dusk::audio::MasterVolume = 1.0f; f32 dusk::audio::PrevMasterVolume = 1.0f; bool dusk::audio::EnableReverb = true; +bool dusk::audio::DumpAudio = false; /** * Validate that a DSP channel's format is actually something we know how to play. @@ -107,6 +129,15 @@ static void MixSubframe(DspSubframe& dst, const DspSubframe& src) { } void dusk::audio::DspRender(OutputSubframe& subframe) { + if (DumpAudio != sDumpWasActive) { + sDumpWasActive = DumpAudio; + if (DumpAudio) { + OpenChannelDumpFiles(); + } else { + CloseChannelDumpFiles(); + } + } + std::span channels(JASDsp::CH_BUF, DSP_CHANNELS); for (int i = 0; i < channels.size(); i++) { @@ -167,6 +198,13 @@ void dusk::audio::DspRender(OutputSubframe& subframe) { } } + if (DumpAudio && sChannelDumpFiles[i]) { + for (int j = 0; j < DSP_SUBFRAME_SIZE; j++) { + fwrite(&channelSubframe.channels[0][j], sizeof(f32), 1, sChannelDumpFiles[i]); + fwrite(&channelSubframe.channels[1][j], sizeof(f32), 1, sChannelDumpFiles[i]); + } + } + for (int o = 0; o < subframe.channels.size(); o++) { MixSubframe(subframe.channels[o], channelSubframe.channels[o]); } @@ -495,7 +533,7 @@ void dusk::audio::DspInit() { auto& channelAux = ChannelAux[i]; channelAux.reverb.setwet(1.0f); channelAux.reverb.setdry(0.0f); - channelAux.reverb.setroomsize(0.2f); + channelAux.reverb.setroomsize(0.4f); channelAux.reverb.setdamp(0.7f); channelAux.reverb.setwidth(1.0f); channelAux.reverb.setmode(0.0f); diff --git a/src/dusk/audio/DuskDsp.hpp b/src/dusk/audio/DuskDsp.hpp index 520a948208..036a4bb9f0 100644 --- a/src/dusk/audio/DuskDsp.hpp +++ b/src/dusk/audio/DuskDsp.hpp @@ -122,4 +122,5 @@ namespace dusk::audio { extern f32 MasterVolume; extern f32 PrevMasterVolume; extern bool EnableReverb; + extern bool DumpAudio; } diff --git a/src/dusk/imgui/ImGuiAudio.cpp b/src/dusk/imgui/ImGuiAudio.cpp index ae01f119f4..3328d1b97c 100644 --- a/src/dusk/imgui/ImGuiAudio.cpp +++ b/src/dusk/imgui/ImGuiAudio.cpp @@ -8,6 +8,7 @@ #include "JSystem/JAudio2/JASDSPInterface.h" #include "JSystem/JAudio2/JASTrack.h" #include "dusk/audio/DuskAudioSystem.h" +#include "dusk/audio/DuskDsp.hpp" static std::array channelSortIndices = {}; static std::array lastResetCounts = {}; @@ -102,6 +103,7 @@ static void ShowAllDspChannels() { } ImGui::Text("Active channels: %d", activeChannels); + ImGui::Checkbox("Dump channels to disk", &dusk::audio::DumpAudio); ImGui::Checkbox("Sort by update count", &sortUpdateCount); if (sortUpdateCount) { From 8fd43b2cbfabb87dacd75c39b08b7a1f1d870d98 Mon Sep 17 00:00:00 2001 From: Irastris Date: Sun, 5 Apr 2026 01:19:53 -0400 Subject: [PATCH 220/350] Fix building again & minor reformatting --- src/dusk/imgui/ImGuiMenuEnhancements.cpp | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/dusk/imgui/ImGuiMenuEnhancements.cpp b/src/dusk/imgui/ImGuiMenuEnhancements.cpp index 805334b1a0..5f8ec912a2 100644 --- a/src/dusk/imgui/ImGuiMenuEnhancements.cpp +++ b/src/dusk/imgui/ImGuiMenuEnhancements.cpp @@ -52,11 +52,16 @@ namespace dusk { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Hides the TV calibration screen shown when loading a save"); } + ImGui::EndMenu(); } if (ImGui::BeginMenu("Preferences")) { ImGui::Checkbox("Mirror Mode", &getSettings().game.enableMirrorMode); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Mirrors the world, matching the Wii version of the game"); + } + ImGui::Checkbox("Invert Camera X Axis", &getSettings().game.invertCameraXAxis); ImGui::EndMenu(); @@ -64,10 +69,11 @@ namespace dusk { if (ImGui::BeginMenu("Graphics")) { ImGui::Checkbox("Native Bloom", &getSettings().game.enableBloom); + ImGui::Checkbox("Water Projection Offset", &getSettings().game.useWaterProjectionOffset); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Adds GC-specific -0.01 transS offset\n" - "that causes ~6px ghost artifacts in water reflections"); + "that causes ~6px ghost artifacts in water reflections"); } ImGui::EndMenu(); @@ -77,7 +83,8 @@ namespace dusk { ImGui::Checkbox("No Low HP Sound", &getSettings().game.noLowHpSound); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Disable the beeping sound when having low health"); - + } + ImGui::Checkbox("Non-Stop Midna's Lament", &getSettings().game.midnasLamentNonStop); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Prevents enemy music while Midna's Lament is playing"); @@ -89,10 +96,10 @@ namespace dusk { if (ImGui::BeginMenu("Cheats")) { ImGui::Checkbox("Fast Iron Boots", &getSettings().game.enableFastIronBoots); - ImGui::Checkbox("Can Transform Anywhere", - &getSettings().game.canTransformAnywhere); + ImGui::Checkbox("Can Transform Anywhere", &getSettings().game.canTransformAnywhere); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Allows you to transform between forms even if NPCs are looking"); + ImGui::SetTooltip( + "Allows you to transform between forms even if NPCs are looking"); } ImGui::EndMenu(); @@ -100,6 +107,7 @@ namespace dusk { if (ImGui::BeginMenu("Difficulty")) { ImGui::SliderInt("Damage Multiplier", &getSettings().game.damageMultiplier, 1, 8, "x%d"); + ImGui::Checkbox("Instant Death", &getSettings().game.instantDeath); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Any hit will instantly kill you"); @@ -111,13 +119,12 @@ namespace dusk { if (ImGui::BeginMenu("Technical")) { ImGui::Checkbox("Restore Wii 1.0 Glitches", &getSettings().game.restoreWiiGlitches); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Restores patched glitches from Wii USA 1.0, the first released version"); + ImGui::SetTooltip("Restores patched glitches from Wii USA 1.0,\n" + "the first released version"); } ImGui::EndMenu(); } - - ImGui::EndMenu(); } } } From a9a71331d613984f1586837edc366d460b5fe3d7 Mon Sep 17 00:00:00 2001 From: madeline Date: Sat, 4 Apr 2026 22:22:18 -0700 Subject: [PATCH 221/350] fix .clangd on vscode --- CMakeLists.txt | 1 + compile_flags.txt | 12 ++++++++++++ extern/aurora | 2 +- libs/freeverb/CMakeLists.txt | 4 ---- src/dusk/audio/DuskDsp.hpp | 2 +- 5 files changed, 15 insertions(+), 6 deletions(-) create mode 100644 compile_flags.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 272ff76db3..a8a861514f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -106,6 +106,7 @@ target_include_directories(game_debug PUBLIC src assets/${DUSK_TP_VERSION} libs/JSystem/include + libs extern/aurora/include/dolphin extern ${CMAKE_SOURCE_DIR}/build/${DUSK_TP_VERSION}/include diff --git a/compile_flags.txt b/compile_flags.txt new file mode 100644 index 0000000000..120832b8cb --- /dev/null +++ b/compile_flags.txt @@ -0,0 +1,12 @@ +--std=c++20 +-DTARGET_PC +-DAVOID_UB=1 +-DVERSION=0 +-Iinclude +-Isrc +-Ilibs/JSystem/include +-Ilibs +-Iextern/aurora/include/dolphin +-Iextern/aurora/include +-Iextern +-Ibuild/GZ2E01/include diff --git a/extern/aurora b/extern/aurora index b17da31593..37631c3bf3 160000 --- a/extern/aurora +++ b/extern/aurora @@ -1 +1 @@ -Subproject commit b17da315932b85d7d708eaf3e44914f70045a44e +Subproject commit 37631c3bf382c1564d3f29c4c3f2dc6cf7617fa9 diff --git a/libs/freeverb/CMakeLists.txt b/libs/freeverb/CMakeLists.txt index c4f4d4ed95..c0c12da293 100644 --- a/libs/freeverb/CMakeLists.txt +++ b/libs/freeverb/CMakeLists.txt @@ -7,7 +7,3 @@ add_library(freeverb allpass.cpp revmodel.cpp ) - -target_include_directories(freeverb PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR} -) diff --git a/src/dusk/audio/DuskDsp.hpp b/src/dusk/audio/DuskDsp.hpp index 036a4bb9f0..728234b75c 100644 --- a/src/dusk/audio/DuskDsp.hpp +++ b/src/dusk/audio/DuskDsp.hpp @@ -8,7 +8,7 @@ #include "SDL3/SDL_audio.h" #include -#include "revmodel.hpp" +#include "freeverb/revmodel.hpp" // ReSharper disable once CppUnusedIncludeDirective #include "global.h" From 0188dc6d3e0a231d9a9905e6191e57597e928114 Mon Sep 17 00:00:00 2001 From: madeline Date: Sat, 4 Apr 2026 22:22:34 -0700 Subject: [PATCH 222/350] a --- extern/aurora | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/aurora b/extern/aurora index 37631c3bf3..b17da31593 160000 --- a/extern/aurora +++ b/extern/aurora @@ -1 +1 @@ -Subproject commit 37631c3bf382c1564d3f29c4c3f2dc6cf7617fa9 +Subproject commit b17da315932b85d7d708eaf3e44914f70045a44e From afdd3034d80e890811fc96a1518964e0e1f1b30f Mon Sep 17 00:00:00 2001 From: Irastris Date: Sun, 5 Apr 2026 01:23:49 -0400 Subject: [PATCH 223/350] I hate you VS --- src/dusk/imgui/ImGuiMenuEnhancements.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/dusk/imgui/ImGuiMenuEnhancements.cpp b/src/dusk/imgui/ImGuiMenuEnhancements.cpp index 5f8ec912a2..3607214f4b 100644 --- a/src/dusk/imgui/ImGuiMenuEnhancements.cpp +++ b/src/dusk/imgui/ImGuiMenuEnhancements.cpp @@ -98,8 +98,7 @@ namespace dusk { ImGui::Checkbox("Can Transform Anywhere", &getSettings().game.canTransformAnywhere); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip( - "Allows you to transform between forms even if NPCs are looking"); + ImGui::SetTooltip("Allows you to transform even if NPCs are looking"); } ImGui::EndMenu(); From 4614a0c64237cfd9509167c725c08a6a178e98d4 Mon Sep 17 00:00:00 2001 From: madeline Date: Sat, 4 Apr 2026 22:45:00 -0700 Subject: [PATCH 224/350] fix clangd for vscode and remove win platform variants since thats done through environment on windows --- .vscode/settings.json | 10 +++++++--- cmake-variants.yaml | 16 ---------------- 2 files changed, 7 insertions(+), 19 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 4573e4a21c..bacff1eb9b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,10 @@ { - "cmake.buildDirectory": "${workspaceFolder}/build/dusk/${buildType}/${variant:platform}/${variant:tp_version}", + "cmake.buildDirectory": "${workspaceFolder}/build/dusk/${buildType}/${variant:tp_version}", + "cmake.generator": "Ninja", + "cmake.configureSettings": { + "CMAKE_EXPORT_COMPILE_COMMANDS": "ON" + }, + "cmake.copyCompileCommands": "${workspaceFolder}/compile_commands.json", "[c]": { "files.encoding": "utf8", "editor.defaultFormatter": "llvm-vs-code-extensions.vscode-clangd" @@ -44,8 +49,7 @@ "build.ninja": true, ".ninja_*": true, "objdiff.json": true, - ".cache/**": true, - "compile_commands.json": true, + ".cache/**": true }, "clangd.arguments": [ "--function-arg-placeholders=0", diff --git a/cmake-variants.yaml b/cmake-variants.yaml index f9f6ad51bd..62d2642311 100644 --- a/cmake-variants.yaml +++ b/cmake-variants.yaml @@ -14,22 +14,6 @@ buildType: long: Optimized, with debug symbols buildType: RelWithDebInfo -platform: - default: win64 - description: Target platform - choices: - win32: - short: Win32 - long: Windows 32-bit (MSVC) - settings: - CMAKE_GENERATOR_PLATFORM: Win32 - - win64: - short: Win64 - long: Windows 64-bit (MSVC) - settings: - CMAKE_GENERATOR_PLATFORM: x64 - tp_version: default: GZ2E01 description: TP Version From 5650842c6443db0f926a68208557f2b839dca470 Mon Sep 17 00:00:00 2001 From: madeline Date: Sat, 4 Apr 2026 22:45:35 -0700 Subject: [PATCH 225/350] no longer needed --- compile_flags.txt | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 compile_flags.txt diff --git a/compile_flags.txt b/compile_flags.txt deleted file mode 100644 index 120832b8cb..0000000000 --- a/compile_flags.txt +++ /dev/null @@ -1,12 +0,0 @@ ---std=c++20 --DTARGET_PC --DAVOID_UB=1 --DVERSION=0 --Iinclude --Isrc --Ilibs/JSystem/include --Ilibs --Iextern/aurora/include/dolphin --Iextern/aurora/include --Iextern --Ibuild/GZ2E01/include From c00123c06bbb7d390fa74d6e34279027c96842a9 Mon Sep 17 00:00:00 2001 From: Lurs <2795933+Lurs@users.noreply.github.com> Date: Sun, 5 Apr 2026 07:46:28 +0200 Subject: [PATCH 226/350] Fixes #207 (A bug I brought myself in :( ) --- src/d/d_kankyo.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/d/d_kankyo.cpp b/src/d/d_kankyo.cpp index eb1900dc4e..ec35ee8bc7 100644 --- a/src/d/d_kankyo.cpp +++ b/src/d/d_kankyo.cpp @@ -9696,7 +9696,7 @@ void dKy_ParticleColor_get_base(cXyz* param_0, dKy_tevstr_c* param_1, GXColor* p f32 var_f31; #if AVOID_UB - var_f31 = 0; + var_f31 = 100000000.0f; #endif if (dKy_SunMoon_Light_Check() == TRUE && i <= 1) { From 6462b97413eeb0fae6ad1a79c13c2f6e21aa9a56 Mon Sep 17 00:00:00 2001 From: madeline Date: Sat, 4 Apr 2026 23:04:08 -0700 Subject: [PATCH 227/350] make the reverb sound slightly more accurate --- src/dusk/audio/DuskDsp.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/dusk/audio/DuskDsp.cpp b/src/dusk/audio/DuskDsp.cpp index e104749991..3e32d3e7ff 100644 --- a/src/dusk/audio/DuskDsp.cpp +++ b/src/dusk/audio/DuskDsp.cpp @@ -177,8 +177,8 @@ void dusk::audio::DspRender(OutputSubframe& subframe) { // this way the reverb's internal buffers accumulate energy proportional to mAutoMixerFxMix, // so any tail always decays at the correct level regardless of mAutoMixerFxMix changes // prevents transients when the next sound starts playing with a different reverb level - // 700.0f was pulled out of my ass and just sounds good enough for console - f32 inputGain = (!skipRender) ? (channel.mAutoMixerFxMix >> 8) / 700.0f : 0.0f; + // 600.0f was pulled out of my ass and just sounds good enough for console + f32 inputGain = (!skipRender) ? (channel.mAutoMixerFxMix >> 8) / 600.0f : 0.0f; OutputSubframe reverbSubframe = {}; for (int j = 0; j < DSP_SUBFRAME_SIZE; j++) { @@ -533,7 +533,7 @@ void dusk::audio::DspInit() { auto& channelAux = ChannelAux[i]; channelAux.reverb.setwet(1.0f); channelAux.reverb.setdry(0.0f); - channelAux.reverb.setroomsize(0.4f); + channelAux.reverb.setroomsize(0.5f); channelAux.reverb.setdamp(0.7f); channelAux.reverb.setwidth(1.0f); channelAux.reverb.setmode(0.0f); From cef767ccaa637acf762552cb8845ea46365922fa Mon Sep 17 00:00:00 2001 From: Irastris Date: Sun, 5 Apr 2026 02:23:55 -0400 Subject: [PATCH 228/350] Fix enhancements menu v3_reallyforreal_final_v02 --- src/dusk/imgui/ImGuiMenuEnhancements.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/dusk/imgui/ImGuiMenuEnhancements.cpp b/src/dusk/imgui/ImGuiMenuEnhancements.cpp index 3607214f4b..3069d9b3f6 100644 --- a/src/dusk/imgui/ImGuiMenuEnhancements.cpp +++ b/src/dusk/imgui/ImGuiMenuEnhancements.cpp @@ -124,6 +124,8 @@ namespace dusk { ImGui::EndMenu(); } + + ImGui::EndMenu(); } } } From 0b04ef6463a25159abcfb8083eef32d9d640a043 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 5 Apr 2026 19:03:26 +0200 Subject: [PATCH 229/350] Set Aurora config path --- include/dusk/dusk.h | 1 + include/dusk/scope.hpp | 29 ----------------------------- src/dusk/config.cpp | 28 ++++++++++------------------ src/m_Do/m_Do_main.cpp | 17 ++++++++++++++++- 4 files changed, 27 insertions(+), 48 deletions(-) delete mode 100644 include/dusk/scope.hpp diff --git a/include/dusk/dusk.h b/include/dusk/dusk.h index d84e496827..7fc2a23070 100644 --- a/include/dusk/dusk.h +++ b/include/dusk/dusk.h @@ -4,5 +4,6 @@ #include extern AuroraInfo auroraInfo; +extern const char* configPath; #endif // DUSK_DUSK_H diff --git a/include/dusk/scope.hpp b/include/dusk/scope.hpp deleted file mode 100644 index e1cfcb9a58..0000000000 --- a/include/dusk/scope.hpp +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef DUSK_SCOPE_HPP -#define DUSK_SCOPE_HPP - -namespace dusk { - -/** - * A simple value wrapper that will destroy the value at the end of its scope. - * @tparam T The type of value contained. - * @tparam Destructor The type of function used to destroy the value. - */ -template - struct ScopeValue { - T value; - Destructor destructor; - - explicit ScopeValue(T value, Destructor destructor) : value(value), destructor(destructor) { - } - - ~ScopeValue() { - destructor(value); - } - - constexpr operator T&() const noexcept { - return value; - } - }; -} - -#endif // DUSK_SCOPE_HPP diff --git a/src/dusk/config.cpp b/src/dusk/config.cpp index aeafb5ab56..159706fac8 100644 --- a/src/dusk/config.cpp +++ b/src/dusk/config.cpp @@ -1,8 +1,4 @@ #include "dusk/config.hpp" -#include "SDL3/SDL_filesystem.h" -#include "SDL3/SDL_iostream.h" -#include "dusk/appname.hpp" -#include "dusk/scope.hpp" #include "fmt/format.h" #include "nlohmann/json.hpp" #include "absl/container/flat_hash_map.h" @@ -13,6 +9,8 @@ #include #include +#include "dusk/dusk.h" + using namespace dusk::config; constexpr auto ConfigFileName = "config.json"; @@ -25,13 +23,7 @@ static absl::flat_hash_map RegisteredConfigVar static bool RegistrationDone; static std::string GetConfigJsonPath() { - dusk::ScopeValue folder(SDL_GetPrefPath(dusk::OrgName, dusk::AppName), SDL_free); - if (folder.value == nullptr) { - DuskConfigLog.error("Failed to get user preference path: %s", SDL_GetError()); - return ""; - } - - return fmt::format("{}{}", folder.value, ConfigFileName); + return fmt::format("{}{}", configPath, ConfigFileName); } ConfigVarBase::ConfigVarBase(const char* name, const ConfigImplBase* impl) : name(name), registered(false), layer(ConfigVarLayer::Default), impl(impl) { @@ -151,11 +143,11 @@ void dusk::config::FinishRegistration() { } void dusk::config::LoadFromUserPreferences() { - const auto configPath = GetConfigJsonPath(); - if (configPath.empty()) { + const auto configJsonPath = GetConfigJsonPath(); + if (configJsonPath.empty()) { return; } - LoadFromFileName(configPath.c_str()); + LoadFromFileName(configJsonPath.c_str()); } static void LoadFromPath(const char* path) { @@ -202,12 +194,12 @@ void dusk::config::LoadFromFileName(const char* path) { } void dusk::config::Save() { - const auto configPath = GetConfigJsonPath(); - if (configPath.empty()) { + const auto configJsonPath = GetConfigJsonPath(); + if (configJsonPath.empty()) { return; } - DuskConfigLog.info("Saving config to '{}'", configPath); + DuskConfigLog.info("Saving config to '{}'", configJsonPath); json j; @@ -217,7 +209,7 @@ void dusk::config::Save() { } } - io::FileStream::WriteAllText(configPath.c_str(), j.dump(4)); + io::FileStream::WriteAllText(configJsonPath.c_str(), j.dump(4)); } ConfigVarBase* dusk::config::GetConfigVar(std::string_view name) { diff --git a/src/m_Do/m_Do_main.cpp b/src/m_Do/m_Do_main.cpp index ba86f17d79..16d2644f69 100644 --- a/src/m_Do/m_Do_main.cpp +++ b/src/m_Do/m_Do_main.cpp @@ -44,6 +44,7 @@ #include #include #include "SSystem/SComponent/c_API.h" +#include "dusk/appname.hpp" #include "dusk/dusk.h" #include "dusk/logging.h" #include "dusk/time.h" @@ -56,6 +57,7 @@ #include #include +#include "SDL3/SDL_filesystem.h" #include "cxxopts.hpp" #include "dusk/config.hpp" @@ -108,6 +110,7 @@ s32 LOAD_COPYDATE(void*) { } AuroraInfo auroraInfo; +const char* configPath; void main01(void) { OS_REPORT("\x1b[m"); @@ -263,6 +266,15 @@ static void ApplyCVarOverrides(const cxxopts::OptionValue& option) { } } +static const char* CalculateConfigPath() { + const auto result = SDL_GetPrefPath(dusk::OrgName, dusk::AppName); + if (!result) { + DuskLog.fatal("Unable to get PrefPath: {}", SDL_GetError()); + } + + return result; +} + // ========================================================================= // PC ENTRY POINT // ========================================================================= @@ -299,11 +311,14 @@ int game_main(int argc, char* argv[]) { exit(1); } + configPath = CalculateConfigPath(); + dusk::config::LoadFromUserPreferences(); ApplyCVarOverrides(parsed_arg_options["cvar"]); AuroraConfig config{}; - config.appName = "Dusk"; + config.appName = dusk::AppName; + config.configPath = configPath; config.windowPosX = -1; config.windowPosY = -1; config.windowWidth = 608 * 2; From 758619c257f670e95378d18dcd5dceda3da63362 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 5 Apr 2026 19:09:33 +0200 Subject: [PATCH 230/350] DUSK_SELECTED_OPT applies to freeverb Fixes audio stuttering on debug builds --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index a8a861514f..e3fbb82437 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -156,4 +156,5 @@ if (DUSK_SELECTED_OPT) target_compile_options(xxhash PRIVATE ${_opt_flags}) target_compile_options(aurora_gx PRIVATE ${_opt_flags}) + target_compile_options(freeverb PRIVATE ${_opt_flags}) endif () From bbcd8e7fed4434d074600eb54cd47a4513dfd898 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 5 Apr 2026 19:10:42 +0200 Subject: [PATCH 231/350] Fix bad snprintf in ImGuiHeapOverlay.cpp --- src/dusk/imgui/ImGuiHeapOverlay.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dusk/imgui/ImGuiHeapOverlay.cpp b/src/dusk/imgui/ImGuiHeapOverlay.cpp index 62d1d9f48c..577db12fc1 100644 --- a/src/dusk/imgui/ImGuiHeapOverlay.cpp +++ b/src/dusk/imgui/ImGuiHeapOverlay.cpp @@ -241,7 +241,7 @@ namespace dusk { } char bufId[32]; - snprintf(bufId, sizeof(bufId), "%p", block); + snprintf(bufId, sizeof(bufId), "%p", block.block); ImGui::PushID(bufId); ImGui::TableNextColumn(); ImGui::Text("%08X", (u32)((uintptr_t)block.block - (uintptr_t)expHeap->getStartAddr())); From a06a1a72ee96358a06ec68df0bc4d69c960d78a6 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 5 Apr 2026 19:15:45 +0200 Subject: [PATCH 232/350] Fix destructible bridges by removing a STUB_RET Fixes #127 --- src/d/actor/d_a_obj_brg.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/d/actor/d_a_obj_brg.cpp b/src/d/actor/d_a_obj_brg.cpp index 64dd01ee45..5615a79622 100644 --- a/src/d/actor/d_a_obj_brg.cpp +++ b/src/d/actor/d_a_obj_brg.cpp @@ -590,8 +590,6 @@ static void cut_control2(obj_brg_class* i_this, br_s* i_part) { } static void himo_cut_control1(obj_brg_class* i_this, cXyz* param_1, f32 param_2) { - STUB_RET(); - cXyz sp74 = {}; cXyz sp80 = {}; cXyz sp8C = {}; From 3f652f17d541f47d7e9344de2286548c8a628c86 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 5 Apr 2026 19:20:10 +0200 Subject: [PATCH 233/350] appname.hpp -> app_info.hpp --- include/dusk/{appname.hpp => app_info.hpp} | 0 src/m_Do/m_Do_main.cpp | 8 ++++---- 2 files changed, 4 insertions(+), 4 deletions(-) rename include/dusk/{appname.hpp => app_info.hpp} (100%) diff --git a/include/dusk/appname.hpp b/include/dusk/app_info.hpp similarity index 100% rename from include/dusk/appname.hpp rename to include/dusk/app_info.hpp diff --git a/src/m_Do/m_Do_main.cpp b/src/m_Do/m_Do_main.cpp index 16d2644f69..ffe6bb5ab6 100644 --- a/src/m_Do/m_Do_main.cpp +++ b/src/m_Do/m_Do_main.cpp @@ -44,12 +44,12 @@ #include #include #include "SSystem/SComponent/c_API.h" -#include "dusk/appname.hpp" +#include "dusk/app_info.hpp" #include "dusk/dusk.h" -#include "dusk/logging.h" -#include "dusk/time.h" -#include "dusk/main.h" #include "dusk/imgui/ImGuiEngine.hpp" +#include "dusk/logging.h" +#include "dusk/main.h" +#include "dusk/time.h" #include #include From 7368b052a12bf5c91f284c8cb5473802633d58c2 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 5 Apr 2026 19:29:06 +0200 Subject: [PATCH 234/350] Remove and tighten down on some unused stubs --- src/dusk/OSContext.cpp | 21 +++------------------ src/dusk/stubs.cpp | 11 ++--------- 2 files changed, 5 insertions(+), 27 deletions(-) diff --git a/src/dusk/OSContext.cpp b/src/dusk/OSContext.cpp index 4f54b8b963..9aa6932e52 100644 --- a/src/dusk/OSContext.cpp +++ b/src/dusk/OSContext.cpp @@ -35,16 +35,6 @@ void OSInitContext(OSContext* context, u32 pc, u32 newsp) { context->gpr[1] = newsp; } -u32 OSSaveContext(OSContext* context) { - // On PC we don't save PowerPC registers. - // Return 0 = "context was just saved" (as opposed to 1 = "restored from save"). - return 0; -} - -void OSLoadContext(OSContext* context) { - // No-op on PC (no PowerPC register restore) -} - void OSDumpContext(OSContext* context) { if (!context) { OSReport("[PC] OSDumpContext: NULL context\n"); @@ -66,20 +56,15 @@ void OSSaveFPUContext(OSContext* fpucontext) { } u32 OSGetStackPointer(void) { - // Return approximate stack pointer - volatile u32 dummy; - return (u32)(uintptr_t)&dummy; + return 0; } u32 OSSwitchStack(u32 newsp) { - // Not meaningful on PC - return current sp - return OSGetStackPointer(); + abort(); } int OSSwitchFiber(u32 pc, u32 newsp) { - // Not meaningful on PC - OSReport("[PC] OSSwitchFiber: not supported on PC\n"); - return 0; + abort(); } void __OSContextInit(void) { diff --git a/src/dusk/stubs.cpp b/src/dusk/stubs.cpp index 0b9520f3e3..2cb385966e 100644 --- a/src/dusk/stubs.cpp +++ b/src/dusk/stubs.cpp @@ -1062,22 +1062,15 @@ extern "C" void KPADEnableDPD(s32) { void LCDisable(void) { STUB_LOG(); } -void LCQueueWait(__REGISTER u32 len) { - STUB_LOG(); -} -u32 LCStoreData(void* destAddr, void* srcAddr, u32 nBytes) { - STUB_LOG(); - return 0; -} #pragma mark PPC Arch // MSR stuff? void PPCHalt() { - STUB_LOG(); + abort(); } extern "C" void PPCSync(void) { - STUB_LOG(); + // Does nothing on PC } u32 PPCMfhid2() { From 68fff3feeb2bf8d39d3e8a08fadb698f4504ab8a Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 5 Apr 2026 19:32:04 +0200 Subject: [PATCH 235/350] Get rid of separate game dylib Just link that shit into the exe. Means we can turn off CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS and save file size. --- CMakeLists.txt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e3fbb82437..12650efb25 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -113,8 +113,7 @@ target_include_directories(game_debug PUBLIC build/${DUSK_TP_VERSION}/include) target_link_libraries(game_debug PUBLIC aurora::core aurora::gx aurora::gd aurora::si aurora::vi aurora::pad aurora::mtx aurora::os aurora::dvd aurora::card freeverb) -set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) -add_library(game SHARED ${DOLZEL_FILES} ${Z2AUDIOLIB_FILES} ${SSYSTEM_FILES} ${JSYSTEM_FILES} ${REL_FILES} ${DUSK_FILES} ${DOLPHIN_FILES} +add_library(game STATIC ${DOLZEL_FILES} ${Z2AUDIOLIB_FILES} ${SSYSTEM_FILES} ${JSYSTEM_FILES} ${REL_FILES} ${DUSK_FILES} ${DOLPHIN_FILES} src/dusk/imgui/ImGuiStubLog.cpp src/dusk/imgui/ImGuiAudio.cpp) @@ -145,7 +144,7 @@ add_custom_command(TARGET dusk POST_BUILD ) include(extern/aurora/cmake/AuroraCopyRuntimeDLLs.cmake) -aurora_copy_runtime_dlls(dusk game) +aurora_copy_runtime_dlls(dusk) if (DUSK_SELECTED_OPT) if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") From 029fcf09415c8d7aa9a7623328c34e5db25b1322 Mon Sep 17 00:00:00 2001 From: madeline Date: Sun, 5 Apr 2026 11:05:31 -0700 Subject: [PATCH 236/350] fix trimming issue closes #201 --- src/d/d_camera.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/d/d_camera.cpp b/src/d/d_camera.cpp index 6540c74ec9..b2a5709736 100644 --- a/src/d/d_camera.cpp +++ b/src/d/d_camera.cpp @@ -10156,9 +10156,9 @@ bool dCamera_c::eventCamera(s32 param_0) { mEye = mCenter + mDirection.Xyz(); } - int* sp90_i; + BE(int)* sp90_i; if (getEvStringData(sp90, "Trim", "DEFAULT") != false) { - sp90_i = (int*)sp90; + sp90_i = (BE(int)*)sp90; if (*sp90_i == 'STAN') { mEventData.field_0x1c = 0; } else if (*sp90_i == 'VIST') { From 5a8d327e77172f316fb747d08c178acd2ccdaf69 Mon Sep 17 00:00:00 2001 From: Max Roncace Date: Sun, 5 Apr 2026 14:20:55 -0400 Subject: [PATCH 237/350] Revert "Get rid of separate game dylib" This reverts commit 68fff3feeb2bf8d39d3e8a08fadb698f4504ab8a. --- CMakeLists.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 12650efb25..e3fbb82437 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -113,7 +113,8 @@ target_include_directories(game_debug PUBLIC build/${DUSK_TP_VERSION}/include) target_link_libraries(game_debug PUBLIC aurora::core aurora::gx aurora::gd aurora::si aurora::vi aurora::pad aurora::mtx aurora::os aurora::dvd aurora::card freeverb) -add_library(game STATIC ${DOLZEL_FILES} ${Z2AUDIOLIB_FILES} ${SSYSTEM_FILES} ${JSYSTEM_FILES} ${REL_FILES} ${DUSK_FILES} ${DOLPHIN_FILES} +set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) +add_library(game SHARED ${DOLZEL_FILES} ${Z2AUDIOLIB_FILES} ${SSYSTEM_FILES} ${JSYSTEM_FILES} ${REL_FILES} ${DUSK_FILES} ${DOLPHIN_FILES} src/dusk/imgui/ImGuiStubLog.cpp src/dusk/imgui/ImGuiAudio.cpp) @@ -144,7 +145,7 @@ add_custom_command(TARGET dusk POST_BUILD ) include(extern/aurora/cmake/AuroraCopyRuntimeDLLs.cmake) -aurora_copy_runtime_dlls(dusk) +aurora_copy_runtime_dlls(dusk game) if (DUSK_SELECTED_OPT) if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") From 0bfbe97850c035964bfcbd34df9a3a5dfbc3a350 Mon Sep 17 00:00:00 2001 From: Jeffrey Crowell Date: Sun, 5 Apr 2026 20:30:03 +0200 Subject: [PATCH 238/350] force scale of 1.0 (#230) combine with https://github.com/encounter/aurora/pull/54 to have aurora actually handle the scaling properly instead of doing it hackily in dusk. --- src/dusk/imgui/ImGuiConsole.cpp | 2 +- src/dusk/imgui/ImGuiEngine.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dusk/imgui/ImGuiConsole.cpp b/src/dusk/imgui/ImGuiConsole.cpp index 6a06fce882..c3d84b375e 100644 --- a/src/dusk/imgui/ImGuiConsole.cpp +++ b/src/dusk/imgui/ImGuiConsole.cpp @@ -23,7 +23,7 @@ using namespace std::string_literals; using namespace std::string_view_literals; namespace dusk { - float ImGuiScale() { return ImGui::GetIO().DisplayFramebufferScale.x; } + float ImGuiScale() { return 1.0f; } void ImGuiStringViewText(std::string_view text) { // begin()/end() do not work on MSVC diff --git a/src/dusk/imgui/ImGuiEngine.cpp b/src/dusk/imgui/ImGuiEngine.cpp index 4e2097cf3d..a6abc5b0b9 100644 --- a/src/dusk/imgui/ImGuiEngine.cpp +++ b/src/dusk/imgui/ImGuiEngine.cpp @@ -38,6 +38,7 @@ void ImGuiEngine_Initialize(float scale) { ImGui::GetCurrentContext(); ImGuiIO& io = ImGui::GetIO(); io.Fonts->Clear(); + io.FontGlobalScale = scale > 0.0f ? 1.0f / scale : 1.0f; const std::string fontPath = GetAssetPath("NotoMono-Regular.ttf"); const bool hasFontFile = AssetExists(fontPath); @@ -146,7 +147,6 @@ void ImGuiEngine_Initialize(float scale) { colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f); colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.35f); - style.ScaleAllSizes(scale); } Icon GetIcon() { From bc62b667409432d2b219b783c3f9ea8857796bb2 Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Sun, 5 Apr 2026 14:30:48 -0400 Subject: [PATCH 239/350] Resize ImGui windows automatically + Move "Tools" menu after "Enhancements" (#232) * Resize ImGui windows automatically + Move "Tools" menu after "Enhancements" * Oops, forgot one of those * Always resize Tools menu * Moved hotkeys to allow fullscreen & reset to be done without the menu open --- src/dusk/imgui/ImGuiCameraOverlay.cpp | 2 -- src/dusk/imgui/ImGuiConsole.cpp | 15 ++++++++++++++- src/dusk/imgui/ImGuiConsole.hpp | 5 ++++- src/dusk/imgui/ImGuiMapLoader.cpp | 2 -- src/dusk/imgui/ImGuiMenuGame.cpp | 11 ----------- src/dusk/imgui/ImGuiMenuTools.cpp | 4 ++-- src/dusk/imgui/ImGuiSaveEditor.cpp | 4 ++-- 7 files changed, 22 insertions(+), 21 deletions(-) diff --git a/src/dusk/imgui/ImGuiCameraOverlay.cpp b/src/dusk/imgui/ImGuiCameraOverlay.cpp index 9d3ef60142..599af30f37 100644 --- a/src/dusk/imgui/ImGuiCameraOverlay.cpp +++ b/src/dusk/imgui/ImGuiCameraOverlay.cpp @@ -26,8 +26,6 @@ namespace dusk { } ImGui::SetNextWindowBgAlpha(0.65f); - ImGui::SetNextWindowSizeConstraints(ImVec2(300.f * ImGuiScale(), 0), - ImVec2(FLT_MAX, FLT_MAX)); if (!ImGui::Begin("Camera Debug", nullptr, windowFlags)) { ImGui::End(); diff --git a/src/dusk/imgui/ImGuiConsole.cpp b/src/dusk/imgui/ImGuiConsole.cpp index c3d84b375e..5ccfb1d68f 100644 --- a/src/dusk/imgui/ImGuiConsole.cpp +++ b/src/dusk/imgui/ImGuiConsole.cpp @@ -182,6 +182,17 @@ namespace dusk { m_isLaunchInitialized = true; } + if ((ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl)) && + ImGui::IsKeyPressed(ImGuiKey_R)) + { + JUTGamePad::C3ButtonReset::sResetSwitchPushing = true; + } + + if (ImGui::IsKeyPressed(ImGuiKey_F11)) { + getSettings().video.enableFullscreen = !getSettings().video.enableFullscreen; + VISetWindowFullscreen(getSettings().video.enableFullscreen); + } + if (CheckMenuViewToggle(ImGuiKey_F1, m_isHidden)) { ShowToasts(); return; @@ -192,9 +203,11 @@ namespace dusk { if (ImGui::BeginMainMenuBar()) { m_menuGame.draw(); - m_menuTools.draw(); m_menuEnhancements.draw(); + // Keep always last + m_menuTools.draw(); + ImGui::SetCursorPosX(ImGui::GetWindowWidth() - 80.0f * ImGuiScale()); ImGuiIO& io = ImGui::GetIO(); ImGuiStringViewText(fmt::format(FMT_STRING("FPS: {:.2f}\n"), io.Framerate)); diff --git a/src/dusk/imgui/ImGuiConsole.hpp b/src/dusk/imgui/ImGuiConsole.hpp index a7c433be4d..27032ed39b 100644 --- a/src/dusk/imgui/ImGuiConsole.hpp +++ b/src/dusk/imgui/ImGuiConsole.hpp @@ -31,10 +31,13 @@ private: bool m_isHidden = true; bool m_isLaunchInitialized = false; std::deque m_toasts; + ImGuiMenuGame m_menuGame; - ImGuiMenuTools m_menuTools; ImGuiMenuEnhancements m_menuEnhancements; + // Keep always last + ImGuiMenuTools m_menuTools; + void ShowToasts(); void ShowPipelineProgress(); }; diff --git a/src/dusk/imgui/ImGuiMapLoader.cpp b/src/dusk/imgui/ImGuiMapLoader.cpp index 4baf48bdab..428d458903 100644 --- a/src/dusk/imgui/ImGuiMapLoader.cpp +++ b/src/dusk/imgui/ImGuiMapLoader.cpp @@ -17,8 +17,6 @@ namespace dusk { ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav; ImGui::SetNextWindowBgAlpha(0.65f); - ImGui::SetNextWindowSizeConstraints(ImVec2(300.f * ImGuiScale(), 0), - ImVec2(FLT_MAX, FLT_MAX)); if (!ImGui::Begin("Map Loader", &m_showMapLoader, windowFlags)) { ImGui::End(); diff --git a/src/dusk/imgui/ImGuiMenuGame.cpp b/src/dusk/imgui/ImGuiMenuGame.cpp index 7bdea47862..961c9e8381 100644 --- a/src/dusk/imgui/ImGuiMenuGame.cpp +++ b/src/dusk/imgui/ImGuiMenuGame.cpp @@ -73,15 +73,6 @@ namespace dusk { windowInputViewer(); windowControllerConfig(); - - if ((ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl)) && ImGui::IsKeyPressed(ImGuiKey_R)) { - JUTGamePad::C3ButtonReset::sResetSwitchPushing = true; - } - - if (ImGui::IsKeyPressed(ImGuiKey_F11)) { - getSettings().video.enableFullscreen = !getSettings().video.enableFullscreen; - VISetWindowFullscreen(getSettings().video.enableFullscreen); - } } static void drawVirtualStick(const char* id, const ImVec2& stick) { @@ -147,8 +138,6 @@ namespace dusk { ImGuiWindowFlags_AlwaysAutoResize; ImGui::SetNextWindowBgAlpha(0.65f); - ImGui::SetNextWindowSizeConstraints(ImVec2(850 * scale, 400 * scale), - ImVec2(850 * scale, 400 * scale)); if (!ImGui::Begin("Controller Config", &m_showControllerConfig, windowFlags)) { ImGui::End(); diff --git a/src/dusk/imgui/ImGuiMenuTools.cpp b/src/dusk/imgui/ImGuiMenuTools.cpp index 20e92768a6..c0494b1e5b 100644 --- a/src/dusk/imgui/ImGuiMenuTools.cpp +++ b/src/dusk/imgui/ImGuiMenuTools.cpp @@ -146,10 +146,10 @@ namespace dusk { } ImGuiIO& io = ImGui::GetIO(); - ImGuiWindowFlags windowFlags = ImGuiWindowFlags_NoResize; + ImGuiWindowFlags windowFlags = + ImGuiWindowFlags_NoResize | ImGuiWindowFlags_AlwaysAutoResize; ImGui::SetNextWindowBgAlpha(0.65f); - ImGui::SetNextWindowSizeConstraints(ImVec2(300, 200), ImVec2(300, 200)); if (ImGui::Begin("Player Info", &m_showPlayerInfo, windowFlags)) { daAlink_c* player = (daAlink_c*)dComIfGp_getPlayer(0); diff --git a/src/dusk/imgui/ImGuiSaveEditor.cpp b/src/dusk/imgui/ImGuiSaveEditor.cpp index a9cca636f8..9748893571 100644 --- a/src/dusk/imgui/ImGuiSaveEditor.cpp +++ b/src/dusk/imgui/ImGuiSaveEditor.cpp @@ -284,10 +284,10 @@ namespace dusk { void ImGuiSaveEditor::draw(bool& open) { ImGuiIO& io = ImGui::GetIO(); - ImGuiWindowFlags windowFlags = ImGuiWindowFlags_NoResize; + ImGuiWindowFlags windowFlags = + ImGuiWindowFlags_NoResize | ImGuiWindowFlags_AlwaysAutoResize; ImGui::SetNextWindowBgAlpha(0.65f); - ImGui::SetNextWindowSizeConstraints(ImVec2(600, 700), ImVec2(600, 700)); if (ImGui::Begin("Save Editor", &open, windowFlags)) { if (ImGui::BeginTabBar("SaveEditorTabBar", ImGuiTabBarFlags_NoCloseWithMiddleMouseButton)) { From 9a0986535faf71eef742ac671de4d1063642801e Mon Sep 17 00:00:00 2001 From: Irastris Date: Sun, 5 Apr 2026 14:31:45 -0400 Subject: [PATCH 240/350] Use int for audio sliders, show current value (#235) --- include/dusk/settings.h | 10 +++++----- src/dusk/imgui/ImGuiMenuGame.cpp | 15 ++++++++------- src/dusk/settings.cpp | 10 +++++----- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/include/dusk/settings.h b/include/dusk/settings.h index c58b0434a3..86b192f6f0 100644 --- a/include/dusk/settings.h +++ b/include/dusk/settings.h @@ -15,11 +15,11 @@ struct UserSettings { struct { // Audio - float masterVolume; - float mainMusicVolume; - float subMusicVolume; - float soundEffectsVolume; - float fanfareVolume; + int masterVolume; + int mainMusicVolume; + int subMusicVolume; + int soundEffectsVolume; + int fanfareVolume; bool enableReverb; } audio; diff --git a/src/dusk/imgui/ImGuiMenuGame.cpp b/src/dusk/imgui/ImGuiMenuGame.cpp index 961c9e8381..cd1d5a06f3 100644 --- a/src/dusk/imgui/ImGuiMenuGame.cpp +++ b/src/dusk/imgui/ImGuiMenuGame.cpp @@ -34,28 +34,29 @@ namespace dusk { if (ImGui::BeginMenu("Audio")) { ImGui::Text("Master Volume"); - ImGui::SliderFloat("##masterVolume", &getSettings().audio.masterVolume, 0.0f, 1.0f, ""); + ImGui::SliderInt("##masterVolume", &getSettings().audio.masterVolume, 0, 100); ImGui::Checkbox("Enable Reverb", &getSettings().audio.enableReverb); + /* - // TODO: implement additional settings + // TODO: Implement additional settings ImGui::Text("Main Music Volume"); - ImGui::SliderFloat("##mainMusicVolume", &getSettings().audio.mainMusicVolume, 0.0f, 1.0f, ""); + ImGui::SliderFloat("##mainMusicVolume", &getSettings().audio.mainMusicVolume, 0, 100); ImGui::Text("Sub Music Volume"); - ImGui::SliderFloat("##subMusicVolume", &getSettings().audio.subMusicVolume, 0.0f, 1.0f, ""); + ImGui::SliderFloat("##subMusicVolume", &getSettings().audio.subMusicVolume, 0, 100); ImGui::Text("Sound Effects Volume"); - ImGui::SliderFloat("##soundEffectsVolume", &getSettings().audio.soundEffectsVolume, 0.0f, 1.0f, ""); + ImGui::SliderFloat("##soundEffectsVolume", &getSettings().audio.soundEffectsVolume, 0, 100); ImGui::Text("Fanfare Volume"); - ImGui::SliderFloat("##fanfareVolume", &getSettings().audio.fanfareVolume, 0.0f, 1.0f, ""); + ImGui::SliderFloat("##fanfareVolume", &getSettings().audio.fanfareVolume, 0, 100); Z2AudioMgr* audioMgr = Z2AudioMgr::getInterface(); if (audioMgr != nullptr) { } */ - audio::SetMasterVolume(getSettings().audio.masterVolume); + audio::SetMasterVolume(getSettings().audio.masterVolume / 100.0f); audio::EnableReverb = getSettings().audio.enableReverb; ImGui::EndMenu(); diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index 23bc7327bd..88a212cbc7 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -12,11 +12,11 @@ UserSettings g_userSettings = { // Audio .audio = { - .masterVolume = 0.8f, - .mainMusicVolume = 1.0f, - .subMusicVolume = 1.0f, - .soundEffectsVolume = 1.0f, - .fanfareVolume = 1.0f, + .masterVolume = 80, + .mainMusicVolume = 100, + .subMusicVolume = 100, + .soundEffectsVolume = 100, + .fanfareVolume = 100, .enableReverb = true }, From ad50d549a8655447e73ac51ddf1e37371f8cdb08 Mon Sep 17 00:00:00 2001 From: madeline Date: Sun, 5 Apr 2026 11:47:49 -0700 Subject: [PATCH 241/350] turbo keybind tool --- include/dusk/hotkeys.h | 2 ++ include/dusk/settings.h | 4 ++++ libs/JSystem/src/JFramework/JFWDisplay.cpp | 7 ++++++- src/dusk/imgui/ImGuiConsole.cpp | 3 +++ src/dusk/imgui/ImGuiMenuTools.cpp | 3 +++ src/dusk/settings.cpp | 4 ++++ 6 files changed, 22 insertions(+), 1 deletion(-) diff --git a/include/dusk/hotkeys.h b/include/dusk/hotkeys.h index ba187a544c..c40f35925f 100644 --- a/include/dusk/hotkeys.h +++ b/include/dusk/hotkeys.h @@ -14,6 +14,8 @@ constexpr const char* SHOW_STUB_LOG = "F5"; constexpr const char* SHOW_CAMERA_DEBUG = "F6"; constexpr const char* SHOW_AUDIO_DEBUG = "F7"; +constexpr const char* TURBO = "Tab"; + } #endif // DUSK_HOTKEYS_H diff --git a/include/dusk/settings.h b/include/dusk/settings.h index c58b0434a3..5d46311168 100644 --- a/include/dusk/settings.h +++ b/include/dusk/settings.h @@ -57,6 +57,9 @@ struct UserSettings { // Technical bool restoreWiiGlitches; + + // Controls + bool enableTurboKeybind; } game; }; @@ -77,6 +80,7 @@ struct CollisionViewSettings { struct TransientSettings { CollisionViewSettings collisionView; + bool skipFrameRateLimit; }; TransientSettings& getTransientSettings(); diff --git a/libs/JSystem/src/JFramework/JFWDisplay.cpp b/libs/JSystem/src/JFramework/JFWDisplay.cpp index b55649a1bc..6a257f5244 100644 --- a/libs/JSystem/src/JFramework/JFWDisplay.cpp +++ b/libs/JSystem/src/JFramework/JFWDisplay.cpp @@ -15,6 +15,7 @@ #include "aurora/aurora.h" #include "dusk/gx_helper.h" #include "dusk/logging.h" +#include "dusk/settings.h" #include "global.h" void JFWDisplay::ctor_subroutine(bool enableAlpha) { @@ -347,7 +348,11 @@ void JFWDisplay::waitBlanking(int param_0) { } static void waitForTick(u32 p1, u16 p2) { - + #if TARGET_PC + if (dusk::getTransientSettings().skipFrameRateLimit) { + p1 = OS_TIMER_CLOCK / 120; + } + #endif if (p1 != 0) { diff --git a/src/dusk/imgui/ImGuiConsole.cpp b/src/dusk/imgui/ImGuiConsole.cpp index 6a06fce882..2cec734534 100644 --- a/src/dusk/imgui/ImGuiConsole.cpp +++ b/src/dusk/imgui/ImGuiConsole.cpp @@ -13,6 +13,7 @@ #include "ImGuiConsole.hpp" #include "JSystem/JUtility/JUTGamePad.h" +#include "dusk/settings.h" #if _WIN32 #define NOMINMAX @@ -182,6 +183,8 @@ namespace dusk { m_isLaunchInitialized = true; } + getTransientSettings().skipFrameRateLimit = getSettings().game.enableTurboKeybind && ImGui::IsKeyDown(ImGuiKey_Tab); + if (CheckMenuViewToggle(ImGuiKey_F1, m_isHidden)) { ShowToasts(); return; diff --git a/src/dusk/imgui/ImGuiMenuTools.cpp b/src/dusk/imgui/ImGuiMenuTools.cpp index 20e92768a6..6b27ac64b2 100644 --- a/src/dusk/imgui/ImGuiMenuTools.cpp +++ b/src/dusk/imgui/ImGuiMenuTools.cpp @@ -3,6 +3,7 @@ #include "aurora/gfx.h" #include "dusk/hotkeys.h" +#include "dusk/settings.h" #include "ImGuiConsole.hpp" #include "ImGuiMenuTools.hpp" @@ -48,6 +49,8 @@ namespace dusk { ImGui::MenuItem("Save Editor", nullptr, &m_showSaveEditor); ImGui::MenuItem("Audio Debug", hotkeys::SHOW_AUDIO_DEBUG, &m_showAudioDebug); ImGui::MenuItem("OSReport Force", nullptr, &OSReportReallyForceEnable); + ImGui::Separator(); + ImGui::MenuItem("Enable Turbo Key", hotkeys::TURBO, &getSettings().game.enableTurboKeybind); ImGui::EndMenu(); } diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index 23bc7327bd..29eba4c694 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -53,6 +53,9 @@ UserSettings g_userSettings = { // Technical .restoreWiiGlitches = false, + + // Controls + .enableTurboKeybind = true, } }; @@ -73,6 +76,7 @@ static TransientSettings g_transientSettings = { .colliderViewOpacity = 50.0f, .drawRange = 100.0f, }, + .skipFrameRateLimit = false, }; TransientSettings& getTransientSettings() { From 8edcfb20527927aedd15f40d48183be269606783 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 5 Apr 2026 22:45:46 +0200 Subject: [PATCH 242/350] Add "lock aspect ratio" setting Uses new Aurora functionality: https://github.com/encounter/aurora/pull/97 Fixes #138 --- include/dusk/dusk.h | 8 ++++++++ include/dusk/settings.h | 1 + src/dusk/imgui/ImGuiMenuGame.cpp | 12 +++++++++++- src/dusk/settings.cpp | 1 + src/m_Do/m_Do_main.cpp | 4 ++-- 5 files changed, 23 insertions(+), 3 deletions(-) diff --git a/include/dusk/dusk.h b/include/dusk/dusk.h index d84e496827..744b0f9152 100644 --- a/include/dusk/dusk.h +++ b/include/dusk/dusk.h @@ -5,4 +5,12 @@ extern AuroraInfo auroraInfo; +constexpr u32 defaultWindowWidth = 608; +constexpr u32 defaultWindowHeight = 448; + +constexpr u32 defaultAspectRatioW = 19; +constexpr u32 defaultAspectRatioH = 14; + +static_assert(defaultWindowWidth / defaultAspectRatioW == defaultWindowHeight / defaultAspectRatioH); + #endif // DUSK_DUSK_H diff --git a/include/dusk/settings.h b/include/dusk/settings.h index 07047fad99..b4ded9f83e 100644 --- a/include/dusk/settings.h +++ b/include/dusk/settings.h @@ -11,6 +11,7 @@ struct UserSettings { struct { // Video bool enableFullscreen; + bool lockAspectRatio; } video; struct { diff --git a/src/dusk/imgui/ImGuiMenuGame.cpp b/src/dusk/imgui/ImGuiMenuGame.cpp index cd1d5a06f3..b1849a6856 100644 --- a/src/dusk/imgui/ImGuiMenuGame.cpp +++ b/src/dusk/imgui/ImGuiMenuGame.cpp @@ -6,8 +6,9 @@ #include #include "JSystem/JUtility/JUTGamePad.h" -#include "dusk/audio/DuskDsp.hpp" #include "dusk/audio/DuskAudioSystem.h" +#include "dusk/audio/DuskDsp.hpp" +#include "dusk/dusk.h" #include "dusk/hotkeys.h" #include "dusk/settings.h" #include "m_Do/m_Do_controller_pad.h" @@ -29,6 +30,15 @@ namespace dusk { VISetWindowFullscreen(getSettings().video.enableFullscreen); } + bool& lockAspect = getSettings().video.lockAspectRatio; + if (ImGui::Checkbox("Lock Aspect Ratio", &lockAspect)) { + if (lockAspect) { + VILockAspectRatio(defaultAspectRatioW, defaultAspectRatioH); + } else { + VIUnlockAspectRatio(); + } + } + ImGui::EndMenu(); } diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index 38fa455e47..5625b284e3 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -8,6 +8,7 @@ UserSettings g_userSettings = { // Video .video = { .enableFullscreen = false, + .lockAspectRatio = false, }, // Audio diff --git a/src/m_Do/m_Do_main.cpp b/src/m_Do/m_Do_main.cpp index b8eeba892e..84c50a5f52 100644 --- a/src/m_Do/m_Do_main.cpp +++ b/src/m_Do/m_Do_main.cpp @@ -269,8 +269,8 @@ int game_main(int argc, char* argv[]) { config.appName = "Dusk"; config.windowPosX = -1; config.windowPosY = -1; - config.windowWidth = 608 * 2; - config.windowHeight = 448 * 2; + config.windowWidth = defaultWindowWidth * 2; + config.windowHeight = defaultWindowHeight * 2; config.desiredBackend = ParseAuroraBackend(parsed_arg_options["backend"].as()); config.logCallback = &aurora_log_callback; config.logLevel = (AuroraLogLevel)parsed_arg_options["log-level"].as(); From 31c7c84c5fd542c06d6ca170fd0d18218faf1e47 Mon Sep 17 00:00:00 2001 From: Max Roncace Date: Sun, 5 Apr 2026 16:48:44 -0400 Subject: [PATCH 243/350] Build "game" as a static library (#236) * Build "game" as a static library This effectively renames the "game" lib to game_base, builds it and game_debug as object libraries, and links both into a single static library. There is also some refactoring done to remove the game_base's dependencies on game_debug's compile-time parameters which have now been moved into shared buildscript variables. * Fix build --------- Co-authored-by: PJB3005 --- CMakeLists.txt | 85 +++++++++++++++++++++++++++++++------------------- 1 file changed, 53 insertions(+), 32 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e3fbb82437..dbf9a92e88 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -88,9 +88,37 @@ message(STATUS "dusk: TP Version: ${DUSK_TP_VERSION}") source_group("dolzel" FILES ${DOLZEL_FILES} ${Z2AUDIOLIB_FILES} ${JSYSTEM_FILES} ${JSYSTEM_DEBUG_FILES} ${REL_FILES}) source_group("dusk" FILES ${DUSK_FILES}) +set(GAME_COMPILE_DEFS TARGET_PC AVOID_UB=1 VERSION=0 + DUSK_TP_VERSION="${DUSK_TP_VERSION}" DUSK_GAME_NAME="${DUSK_GAME_NAME}" DUSK_GAME_VERSION="${DUSK_GAME_VERSION}") + +set(GAME_INCLUDE_DIRS + include + src + assets/${DUSK_TP_VERSION} + libs/JSystem/include + libs + extern/aurora/include/dolphin + extern + ${CMAKE_SOURCE_DIR}/build/${DUSK_TP_VERSION}/include + build/${DUSK_TP_VERSION}/include) + +set(GAME_LIBS aurora::core aurora::gx aurora::gd aurora::si aurora::vi aurora::pad aurora::mtx aurora::os aurora::dvd + aurora::card freeverb cxxopts::cxxopts absl::flat_hash_map) + +if (DUSK_MOVIE_SUPPORT_REAL) + if (TARGET libjpeg-turbo::turbojpeg-static) + message(STATUS "dusk: Linking against libjpeg-turbo static library") + list(APPEND GAME_LIBS "libjpeg-turbo::turbojpeg-static") + else () + message(STATUS "dusk: Linking against libjpeg-turbo shared library") + list(APPEND GAME_LIBS "libjpeg-turbo::turbojpeg") + endif () + list(APPEND GAME_COMPILE_DEFS MOVIE_SUPPORT=1) +endif () + # game_debug is for game code files that we know work when compiled with DEBUG=1 # Of course, if building a release build, this distinction is irrelevant -add_library(game_debug STATIC ${JSYSTEM_DEBUG_FILES} ${SSYSTEM_FILES} +add_library(game_debug OBJECT ${JSYSTEM_DEBUG_FILES} ${SSYSTEM_FILES} src/dusk/audio/DuskAudioSystem.cpp src/dusk/audio/JASCriticalSection.cpp src/dusk/audio/DuskDsp.hpp @@ -98,40 +126,33 @@ add_library(game_debug STATIC ${JSYSTEM_DEBUG_FILES} ${SSYSTEM_FILES} src/dusk/audio/Adpcm.cpp src/dusk/audio/Adpcm.hpp src/dusk/audio/DspStub.cpp) -target_compile_definitions(game_debug PRIVATE TARGET_PC AVOID_UB=1 VERSION=0 $<$:DEBUG=1>) -# Make these properties PUBLIC so that the regular game target also sees them. -target_include_directories(game_debug PUBLIC - include - src - assets/${DUSK_TP_VERSION} - libs/JSystem/include - libs - extern/aurora/include/dolphin - extern - ${CMAKE_SOURCE_DIR}/build/${DUSK_TP_VERSION}/include - build/${DUSK_TP_VERSION}/include) -target_link_libraries(game_debug PUBLIC aurora::core aurora::gx aurora::gd aurora::si aurora::vi aurora::pad aurora::mtx aurora::os aurora::dvd aurora::card freeverb) - -set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) -add_library(game SHARED ${DOLZEL_FILES} ${Z2AUDIOLIB_FILES} ${SSYSTEM_FILES} ${JSYSTEM_FILES} ${REL_FILES} ${DUSK_FILES} ${DOLPHIN_FILES} +# game_base is for all other game code files +add_library(game_base OBJECT ${DOLZEL_FILES} ${Z2AUDIOLIB_FILES} ${JSYSTEM_FILES} ${REL_FILES} ${DUSK_FILES} + ${DOLPHIN_FILES} src/dusk/imgui/ImGuiStubLog.cpp src/dusk/imgui/ImGuiAudio.cpp) -target_link_libraries(game PRIVATE game_debug cxxopts::cxxopts absl::flat_hash_map freeverb) -if (DUSK_MOVIE_SUPPORT_REAL) - if (TARGET libjpeg-turbo::turbojpeg-static) - message(STATUS "dusk: Linking against libjpeg-turbo static library") - target_link_libraries(game PRIVATE libjpeg-turbo::turbojpeg-static) - else () - message(STATUS "dusk: Linking against libjpeg-turbo shared library") - target_link_libraries(game PRIVATE libjpeg-turbo::turbojpeg) - endif () - 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>") +target_compile_definitions(game_debug PRIVATE ${GAME_COMPILE_DEFS} $<$:DEBUG=1>) +target_compile_definitions(game_base PRIVATE ${GAME_COMPILE_DEFS} NDEBUG=1 NDEBUG_DEFINED=1 DEBUG_DEFINED=0) + +# only apply PCH to game_base since not all headers are necessarily validated with DEBUG=1 +target_precompile_headers(game_base PRIVATE "$<$:${CMAKE_SOURCE_DIR}/include/dusk_pch.hpp>") + +target_include_directories(game_debug PRIVATE ${GAME_INCLUDE_DIRS}) +target_include_directories(game_base PRIVATE ${GAME_INCLUDE_DIRS}) + +# This implicitly pulls in the library include directories even though no +# linking actually takes place for object libraries +target_link_libraries(game_debug PRIVATE ${GAME_LIBS}) +target_link_libraries(game_base PRIVATE ${GAME_LIBS}) + +# Combined game library +add_library(game STATIC + $ + $) +target_link_libraries(game PUBLIC ${GAME_LIBS}) + add_executable(dusk src/dusk/main.cpp) target_compile_definitions(dusk PRIVATE TARGET_PC AVOID_UB=1 VERSION=0) target_include_directories(dusk PRIVATE include) @@ -145,7 +166,7 @@ add_custom_command(TARGET dusk POST_BUILD ) include(extern/aurora/cmake/AuroraCopyRuntimeDLLs.cmake) -aurora_copy_runtime_dlls(dusk game) +aurora_copy_runtime_dlls(dusk) if (DUSK_SELECTED_OPT) if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") From 0ef5eadea2fe5c53a45373dee5f7b06697586e46 Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Sat, 21 Mar 2026 13:41:46 -0400 Subject: [PATCH 244/350] initial commit with working 3D view at all times --- CMakeLists.txt | 2 +- include/m_Do/m_Do_graphic.h | 12 ++++++++++++ src/d/d_menu_collect.cpp | 2 +- src/d/d_menu_item_explain.cpp | 5 +++++ src/d/d_meter2_draw.cpp | 33 +++++++++++++++++++++++++++++++++ src/d/d_meter_HIO.cpp | 2 +- src/d/d_meter_map.cpp | 6 ++++++ src/m_Do/m_Do_graphic.cpp | 31 +++++++++++++++++++++++++++++++ 8 files changed, 90 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index dbf9a92e88..cfb76e85ff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -88,7 +88,7 @@ message(STATUS "dusk: TP Version: ${DUSK_TP_VERSION}") source_group("dolzel" FILES ${DOLZEL_FILES} ${Z2AUDIOLIB_FILES} ${JSYSTEM_FILES} ${JSYSTEM_DEBUG_FILES} ${REL_FILES}) source_group("dusk" FILES ${DUSK_FILES}) -set(GAME_COMPILE_DEFS TARGET_PC AVOID_UB=1 VERSION=0 +set(GAME_COMPILE_DEFS TARGET_PC WIDESCREEN_SUPPORT AVOID_UB=1 VERSION=0 DUSK_TP_VERSION="${DUSK_TP_VERSION}" DUSK_GAME_NAME="${DUSK_GAME_NAME}" DUSK_GAME_VERSION="${DUSK_GAME_VERSION}") set(GAME_INCLUDE_DIRS diff --git a/include/m_Do/m_Do_graphic.h b/include/m_Do/m_Do_graphic.h index 3d560efeb6..d69ab59a9c 100644 --- a/include/m_Do/m_Do_graphic.h +++ b/include/m_Do/m_Do_graphic.h @@ -117,6 +117,13 @@ public: static void setTickRate(u32 rate) { JFWDisplay::getManager()->setTickRate(rate); } static void waitBlanking(int wait) { JFWDisplay::getManager()->waitBlanking(wait); } +#if TARGET_PC + static f32 hudAspectScaleDown; + static f32 hudAspectScaleUp; + static f32 ScaleHUDXLeft(f32 baseX) { return getMinXF() + baseX; } + static f32 ScaleHUDXRight(f32 baseX) { return -getMinXF() + baseX; } +#endif + static void setBlureMtx(const Mtx m) { cMtx_copy(m, mBlureMtx); } @@ -266,7 +273,12 @@ public: #if WIDESCREEN_SUPPORT static void setTvSize(); + #if TARGET_PC + static void onWide(f32 width, f32 height); + #else static void onWide(); + #endif + static void offWide(); static u8 isWide(); diff --git a/src/d/d_menu_collect.cpp b/src/d/d_menu_collect.cpp index c1281a86c2..c30bd85f9b 100644 --- a/src/d/d_menu_collect.cpp +++ b/src/d/d_menu_collect.cpp @@ -2555,7 +2555,7 @@ f32 dMenu_Collect3D_c::mViewOffsetY = -100.0f; void dMenu_Collect3D_c::setupItem3D(Mtx param_0) { #if TARGET_PC - f32 scaleFactor = mDoGph_gInf_c::getWidth() / FB_WIDTH; // TODO: get display pixel density from aurora + f32 scaleFactor = mDoGph_gInf_c::getHeight() / FB_HEIGHT; GXSetViewport(0.0f, mViewOffsetY * scaleFactor, mDoGph_gInf_c::getWidth(), mDoGph_gInf_c::getHeight(), 0.0f, 1.0f); #else GXSetViewport(0.0f, mViewOffsetY, FB_WIDTH, FB_HEIGHT, 0.0f, 1.0f); diff --git a/src/d/d_menu_item_explain.cpp b/src/d/d_menu_item_explain.cpp index 21a3a32fdd..c106b4bd8c 100644 --- a/src/d/d_menu_item_explain.cpp +++ b/src/d/d_menu_item_explain.cpp @@ -310,7 +310,12 @@ void dMenu_ItemExplain_c::draw(J2DOrthoGraph* i_graph) { mpLabel->scale(g_ringHIO.mItemDescTitleScale, g_ringHIO.mItemDescTitleScale); mpLabel->paneTrans(g_ringHIO.mItemDescTitlePosX, g_ringHIO.mItemDescTitlePosY); if (mpBackTex != NULL) { + #if TARGET_PC + mpBackTex->draw(mDoGph_gInf_c::ScaleHUDXLeft(0.0f), 0.0f, mDoGph_gInf_c::getWidthF(), + FB_HEIGHT, false, false, false); + #else mpBackTex->draw(0.0f, 0.0f, FB_WIDTH, FB_HEIGHT, false, false, false); + #endif } if (field_0xc8 != field_0xd0) { field_0xd0 = field_0xc8; diff --git a/src/d/d_meter2_draw.cpp b/src/d/d_meter2_draw.cpp index f222ee752a..ab4b1cc86c 100644 --- a/src/d/d_meter2_draw.cpp +++ b/src/d/d_meter2_draw.cpp @@ -564,7 +564,13 @@ void dMeter2Draw_c::exec(u32 i_status) { { mButtonsPosX = g_drawHIO.mMainHUDButtonsPosX; mButtonsPosY = g_drawHIO.mMainHUDButtonsPosY; + + #if TARGET_PC + mpButtonParent->paneTrans(mDoGph_gInf_c::ScaleHUDXRight(g_drawHIO.mMainHUDButtonsPosX), + g_drawHIO.mMainHUDButtonsPosY); + #else mpButtonParent->paneTrans(g_drawHIO.mMainHUDButtonsPosX, g_drawHIO.mMainHUDButtonsPosY); + #endif } if (mButtonsScale != g_drawHIO.mMainHUDButtonsScale) { @@ -1476,7 +1482,11 @@ void dMeter2Draw_c::drawLife(s16 i_maxLife, s16 i_life, f32 i_posX, f32 i_posY) mpBigHeart->scale(g_drawHIO.mBigHeartScale, g_drawHIO.mBigHeartScale); } + #if TARGET_PC + mpLifeParent->paneTrans(mDoGph_gInf_c::ScaleHUDXLeft(i_posX), i_posY); + #else mpLifeParent->paneTrans(i_posX, i_posY); + #endif } void dMeter2Draw_c::setAlphaLifeChange(bool param_0) { @@ -1589,7 +1599,14 @@ void dMeter2Draw_c::drawKanteraScreen(u8 i_meterType) { mpMagicFrameR->move(field_0x59c[i_meterType], field_0x5a8[i_meterType]); mpMagicBase->resize(field_0x5b4[i_meterType], field_0x5c0[i_meterType]); mpMagicParent->scale(field_0x5cc[i_meterType], field_0x5d8[i_meterType]); + + #if TARGET_PC + mpMagicParent->paneTrans(mDoGph_gInf_c::ScaleHUDXLeft(field_0x5e4[i_meterType]), + field_0x5f0[i_meterType]); + #else mpMagicParent->paneTrans(field_0x5e4[i_meterType], field_0x5f0[i_meterType]); + #endif + mpKanteraScreen->draw(0.0f, 0.0f, graf_ctx); } @@ -1854,7 +1871,12 @@ void dMeter2Draw_c::drawLightDrop(u8 i_num, u8 i_needNum, f32 i_posX, f32 i_posY mLightDropVesselScale = i_vesselScale; mpLightDropParent->scale(mLightDropVesselScale * field_0x6f8, mLightDropVesselScale * field_0x6f8); + + #if TARGET_PC + mpLightDropParent->paneTrans(mDoGph_gInf_c::ScaleHUDXRight(i_posX), i_posY); + #else mpLightDropParent->paneTrans(i_posX, i_posY); + #endif } void dMeter2Draw_c::setAlphaLightDropChange(bool unused) {} @@ -2001,7 +2023,13 @@ void dMeter2Draw_c::drawRupee(s16 i_rupeeNum) { mpRupeeKeyParent->scale(g_drawHIO.mRupeeKeyScale * field_0x718, g_drawHIO.mRupeeKeyScale * field_0x718); + + #if TARGET_PC + mpRupeeKeyParent->paneTrans(mDoGph_gInf_c::ScaleHUDXRight(g_drawHIO.mRupeeKeyPosX), + g_drawHIO.mRupeeKeyPosY); + #else mpRupeeKeyParent->paneTrans(g_drawHIO.mRupeeKeyPosX, g_drawHIO.mRupeeKeyPosY); + #endif mpRupeeParent[0]->scale(g_drawHIO.mRupeeScale, g_drawHIO.mRupeeScale); mpRupeeParent[0]->paneTrans(g_drawHIO.mRupeePosX, g_drawHIO.mRupeePosY); @@ -2582,7 +2610,12 @@ void dMeter2Draw_c::drawButtonCross(f32 i_posX, f32 i_posY) { mpButtonCrossParent->scale(g_drawHIO.mButtonCrossScale, g_drawHIO.mButtonCrossScale); mpTextI->scale(g_drawHIO.mButtonCrossTextScale, g_drawHIO.mButtonCrossTextScale); mpTextM->scale(g_drawHIO.mButtonCrossTextScale, g_drawHIO.mButtonCrossTextScale); + + #if TARGET_PC + mpButtonCrossParent->paneTrans(mDoGph_gInf_c::ScaleHUDXLeft(i_posX), i_posY); + #else mpButtonCrossParent->paneTrans(i_posX, i_posY); + #endif } void dMeter2Draw_c::setAlphaButtonCrossAnimeMin() { diff --git a/src/d/d_meter_HIO.cpp b/src/d/d_meter_HIO.cpp index 685f9c580c..240dcb8d45 100644 --- a/src/d/d_meter_HIO.cpp +++ b/src/d/d_meter_HIO.cpp @@ -3003,7 +3003,7 @@ void dMeter_drawHIO_c::updateFMsgDebug() { #endif dMeter_ringHIO_c::dMeter_ringHIO_c() { -#if WIDESCREEN_SUPPORT +#if WIDESCREEN_SUPPORT && !TARGET_PC updateOnWide(); #else mRingRadiusH = 175.0f; diff --git a/src/d/d_meter_map.cpp b/src/d/d_meter_map.cpp index 7e55d899fd..362a996d60 100644 --- a/src/d/d_meter_map.cpp +++ b/src/d/d_meter_map.cpp @@ -628,7 +628,13 @@ void dMeterMap_c::draw() { #endif mMapJ2DPicture->setAlpha(alpha); + #if TARGET_PC + mMapJ2DPicture->draw(mDoGph_gInf_c::ScaleHUDXLeft(drawPosX), drawPosY, sizeX, sizeY, false, false, + false); + #else mMapJ2DPicture->draw(drawPosX, drawPosY, sizeX, sizeY, false, false, false); + #endif + mMapJ2DPicture->calcMtx(); } } diff --git a/src/m_Do/m_Do_graphic.cpp b/src/m_Do/m_Do_graphic.cpp index a7a09a8d87..3c2b0240bc 100644 --- a/src/m_Do/m_Do_graphic.cpp +++ b/src/m_Do/m_Do_graphic.cpp @@ -552,8 +552,16 @@ const tvSize l_tvSize[2] = { {808, 448}, }; +#if TARGET_PC +tvSize pc_tvSize = {608, 448}; +#endif + void mDoGph_gInf_c::setTvSize() { +#if TARGET_PC + const tvSize* tvsize = &pc_tvSize; +#else const tvSize* tvsize = &l_tvSize[mWide]; +#endif m_width = tvsize->width; m_height = tvsize->height; @@ -572,18 +580,35 @@ void mDoGph_gInf_c::setTvSize() { m_aspect = m_widthF / m_heightF; m_scale = m_aspect / 1.3571428f; m_invScale = 1.0f / m_scale; + +#if TARGET_PC + hudAspectScaleDown = 1.3571428f / mDoGph_gInf_c::getAspect(); + hudAspectScaleUp = 1.0f / hudAspectScaleDown; +#endif } +#if TARGET_PC +void mDoGph_gInf_c::onWide(f32 width, f32 height) { + mWide = TRUE; + pc_tvSize.width = width; + pc_tvSize.height = height; + setTvSize(); +} +#else void mDoGph_gInf_c::onWide() { mWide = TRUE; setTvSize(); dMeter2Info_onWide2D(); } +#endif void mDoGph_gInf_c::offWide() { mWide = FALSE; setTvSize(); + +#if !TARGET_PC dMeter2Info_offWide2D(); +#endif } void mDoGph_gInf_c::onWideZoom() { @@ -686,10 +711,16 @@ void mDoGph_gInf_c::setWideZoomLightProjection(Mtx& m) { #endif #if TARGET_PC +f32 mDoGph_gInf_c::hudAspectScaleDown = 1.0f; +f32 mDoGph_gInf_c::hudAspectScaleUp = 1.0f; + void mDoGph_gInf_c::setWindowSize(AuroraWindowSize const& size) { JUTVideo::getManager()->setWindowSize(size); dComIfGp_setWindow(0, 0.0f, 0.0f, getWidth(), getHeight(), 0.0f, 1.0f, 0, 2); mFader->mBox.set(0, 0, getWidth(), getHeight()); + + f32 newWidth = (getWidth() / getHeight()) * 448.0f; + onWide(newWidth, 448.0f); } #endif From 319bd3823fce89f40305b62a9bee256f827d657c Mon Sep 17 00:00:00 2001 From: Irastris Date: Mon, 30 Mar 2026 04:09:05 -0400 Subject: [PATCH 245/350] Fix fmap cursor and options screen --- src/d/d_menu_fmap2D.cpp | 4 ++++ src/d/d_menu_option.cpp | 12 ++++++++++++ 2 files changed, 16 insertions(+) diff --git a/src/d/d_menu_fmap2D.cpp b/src/d/d_menu_fmap2D.cpp index 29ec5db854..eac8d1992b 100644 --- a/src/d/d_menu_fmap2D.cpp +++ b/src/d/d_menu_fmap2D.cpp @@ -359,7 +359,11 @@ void dMenu_Fmap2DBack_c::draw() { drawDebugRegionArea(); } +#if TARGET_PC + grafPort->scissor(scissorLeft, scissorTop, mDoGph_gInf_c::getWidth(), mDoGph_gInf_c::getHeight()); +#else grafPort->scissor(scissorLeft, scissorTop, scissorWidth, scissorHeight); +#endif grafPort->setScissor(); if (isArrowDrawFlag()) { diff --git a/src/d/d_menu_option.cpp b/src/d/d_menu_option.cpp index abe9f864ce..9384bc06b9 100644 --- a/src/d/d_menu_option.cpp +++ b/src/d/d_menu_option.cpp @@ -555,13 +555,25 @@ void dMenu_Option_c::_draw() { #endif mpBlackTex->setAlpha(0xff); +#if TARGET_PC + mpBlackTex->draw(mDoGph_gInf_c::getMinXF(), mDoGph_gInf_c::getMinYF(), mDoGph_gInf_c::getWidthF(), mDoGph_gInf_c::getHeightF(), 0, 0, 0); +#else mpBlackTex->draw(0.0f, 0.0f, FB_WIDTH, FB_HEIGHT, 0, 0, 0); +#endif mpBackScreen->draw(0.0f, 0.0f, ctx); f32 alpha = (f32)g_drawHIO.mOptionScreen.mBackgroundAlpha * (f32)field_0x374; mpBlackTex->setAlpha(alpha); +#if TARGET_PC + mpBlackTex->draw(mDoGph_gInf_c::getMinXF(), mDoGph_gInf_c::getMinYF(), mDoGph_gInf_c::getWidthF(), mDoGph_gInf_c::getHeightF(), 0, 0, 0); +#else mpBlackTex->draw(0.0f, 0.0f, FB_WIDTH, FB_HEIGHT, 0, 0, 0); +#endif mpScreen->draw(0.0f, 0.0f, ctx); mpClipScreen->draw(0.0f, 0.0f, ctx); +#if TARGET_PC + ctx->scissor(0.0f, 0.0f, mDoGph_gInf_c::getWidth(), mDoGph_gInf_c::getHeight()); + ctx->setScissor(); +#endif mpShadowScreen->draw(0.0f, 0.0f, ctx); if (field_0x3f3 == 1 || field_0x3f3 == 2 || field_0x3f3 == 3) { mpTVScreen->draw(0.0f, 0.0f, ctx); From f5703925d6645d59150a774404c3d7c3ac6386f9 Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Tue, 31 Mar 2026 13:37:18 -0400 Subject: [PATCH 246/350] scale bloom depending on the window size --- src/m_Do/m_Do_graphic.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/m_Do/m_Do_graphic.cpp b/src/m_Do/m_Do_graphic.cpp index 3c2b0240bc..2657b0cd16 100644 --- a/src/m_Do/m_Do_graphic.cpp +++ b/src/m_Do/m_Do_graphic.cpp @@ -1316,7 +1316,13 @@ void mDoGph_gInf_c::bloom_c::draw() { GXSetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, 0x3c); for (int texCoord = (int)GX_TEXCOORD1; texCoord < (int)GX_MAX_TEXCOORD; texCoord++) { GXSetTexCoordGen((GXTexCoordID)texCoord, GX_TG_MTX2x4, GX_TG_TEX0, iVar11); + + #if TARGET_PC + f32 dVar15 = mBlureSize * ((448.0f / getHeight()) / 6400.0f); + #else f32 dVar15 = mBlureSize * (1.0f / 6400.0f); + #endif + mDoMtx_stack_c::transS((dVar15 * cM_scos(sVar10)) * getInvScale(), dVar15 * cM_ssin(sVar10), 0.0f); GXLoadTexMtxImm(mDoMtx_stack_c::get(), iVar11, GX_MTX2x4); From ef7fcc04cc8a6bacb3f3ef3a151f987c0f8a4096 Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Tue, 31 Mar 2026 13:45:53 -0400 Subject: [PATCH 247/350] fix reticle position --- src/d/actor/d_a_player.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/d/actor/d_a_player.cpp b/src/d/actor/d_a_player.cpp index f38fe15a3b..a2e56068d7 100644 --- a/src/d/actor/d_a_player.cpp +++ b/src/d/actor/d_a_player.cpp @@ -402,11 +402,12 @@ void daPy_sightPacket_c::setSight() { #if TARGET_PC mDoLib_project(&mPos, &proj, {0, 0, FB_WIDTH, FB_HEIGHT}); + mDoMtx_stack_c::transS(proj.x - 16.0f, proj.y - 8.0f, proj.z); #else mDoLib_project(&mPos, &proj); + mDoMtx_stack_c::transS(proj.x, proj.y, proj.z); #endif - mDoMtx_stack_c::transS(proj.x, proj.y, proj.z); mDoMtx_stack_c::scaleM(32.0f, 32.0f, 32.0f); mDoMtx_copy(mDoMtx_stack_c::get(), mProjMtx); dComIfGd_set2DXlu(this); From f7cbd6a95c002276b760d80d8e66aa356c756c15 Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Tue, 31 Mar 2026 13:48:23 -0400 Subject: [PATCH 248/350] nvm im dumb --- src/d/actor/d_a_player.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/d/actor/d_a_player.cpp b/src/d/actor/d_a_player.cpp index a2e56068d7..f38fe15a3b 100644 --- a/src/d/actor/d_a_player.cpp +++ b/src/d/actor/d_a_player.cpp @@ -402,12 +402,11 @@ void daPy_sightPacket_c::setSight() { #if TARGET_PC mDoLib_project(&mPos, &proj, {0, 0, FB_WIDTH, FB_HEIGHT}); - mDoMtx_stack_c::transS(proj.x - 16.0f, proj.y - 8.0f, proj.z); #else mDoLib_project(&mPos, &proj); - mDoMtx_stack_c::transS(proj.x, proj.y, proj.z); #endif + mDoMtx_stack_c::transS(proj.x, proj.y, proj.z); mDoMtx_stack_c::scaleM(32.0f, 32.0f, 32.0f); mDoMtx_copy(mDoMtx_stack_c::get(), mProjMtx); dComIfGd_set2DXlu(this); From 97db5f8fa67cc07aa3b1cce2fdfe5a2da49e1296 Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Tue, 31 Mar 2026 14:19:51 -0400 Subject: [PATCH 249/350] fix reticles for good --- include/m_Do/m_Do_graphic.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/m_Do/m_Do_graphic.h b/include/m_Do/m_Do_graphic.h index d69ab59a9c..421e1ae47f 100644 --- a/include/m_Do/m_Do_graphic.h +++ b/include/m_Do/m_Do_graphic.h @@ -8,7 +8,7 @@ #include #endif -#if WIDESCREEN_SUPPORT +#if WIDESCREEN_SUPPORT && !TARGET_PC #define FB_WIDTH (640) #define FB_HEIGHT (456) #else From e0e16bb0acb60e41d0388a0b3be915060d176473 Mon Sep 17 00:00:00 2001 From: Lurs Date: Tue, 31 Mar 2026 23:36:53 +0200 Subject: [PATCH 250/350] Fix arrows for dungeon map --- src/d/d_menu_dmap.cpp | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/d/d_menu_dmap.cpp b/src/d/d_menu_dmap.cpp index 97721d9dd2..739dfc7843 100644 --- a/src/d/d_menu_dmap.cpp +++ b/src/d/d_menu_dmap.cpp @@ -864,7 +864,16 @@ void dMenu_DmapBg_c::draw() { J2DOrthoGraph* grafContext = (J2DOrthoGraph*)dComIfGp_getCurrentGrafPort(); grafContext->setup2D(); +#if TARGET_PC + // GXGetScissor uses 11-bit GC register fields (max 2047) which overflow + // at window widths > ~1705px, producing garbage values on restore. + scissor_left = 0; + scissor_top = 0; + scissor_width = (u32)mDoGph_gInf_c::getWidth(); + scissor_height = (u32)mDoGph_gInf_c::getHeight(); +#else GXGetScissor(&scissor_left, &scissor_top, &scissor_width, &scissor_height); +#endif #if TARGET_PC grafContext->scissor(field_0xd94, 0.0f, mDoGph_gInf_c::getWidth(), mDoGph_gInf_c::getHeight()); #else @@ -876,8 +885,8 @@ void dMenu_DmapBg_c::draw() { dMenu_Dmap_c::myclass->drawFloorScreenBack(mFloorScreen, field_0xd94, field_0xd98, grafContext); #if TARGET_PC - f32 dVar21 = mDoGph_gInf_c::getWidth() / FB_WIDTH; - f32 dVar16 = mDoGph_gInf_c::getHeight() / FB_HEIGHT; + f32 dVar21 = mDoGph_gInf_c::getWidthF() / mDoGph_gInf_c::getWidth(); + f32 dVar16 = mDoGph_gInf_c::getHeightF() / mDoGph_gInf_c::getHeight(); #else f32 dVar21 = mDoGph_gInf_c::getWidthF() / FB_WIDTH; f32 dVar16 = mDoGph_gInf_c::getHeightF() / FB_HEIGHT; @@ -890,8 +899,15 @@ void dMenu_DmapBg_c::draw() { Mtx mtx; Vec local_200 = pane.getGlobalVtx(center_pane, &mtx, 0, false, 0); Vec local_20c = pane.getGlobalVtx(center_pane, &mtx, 3, false, 0); +#if TARGET_PC + grafContext->scissor(((local_200.x - mDoGph_gInf_c::getMinXF()) / dVar21), + ((local_200.y - mDoGph_gInf_c::getMinYF()) / dVar16), + ((local_20c.x - local_200.x) / dVar21), + (2.0f + (local_20c.y - local_200.y)) / dVar16); +#else grafContext->scissor(((local_200.x - mDoGph_gInf_c::getMinXF()) / dVar21), ((local_200.y / dVar16) / dVar16), ((local_20c.x - local_200.x) / dVar21), 2.0f + (local_20c.y - local_200.y)); +#endif grafContext->setScissor(); f32 dVar17 = field_0xd8c / 255.0f; @@ -925,10 +941,17 @@ void dMenu_DmapBg_c::draw() { Mtx local_110; Vec local_218 = pane.getGlobalVtx(center_pane, &local_110, 0, false, 0); Vec local_224 = pane.getGlobalVtx(center_pane, &local_110, 3, false, 0); +#if TARGET_PC + f32 local_294 = ((local_218.x - mDoGph_gInf_c::getMinXF()) / dVar21); + f32 local_298 = ((local_218.y - mDoGph_gInf_c::getMinYF()) / dVar16); + f32 local_29c = ((local_224.x - local_218.x) / dVar21); + f32 local_2a0 = (2.0f + (local_224.y - local_218.y)) / dVar16; +#else f32 local_294 = ((local_218.x - mDoGph_gInf_c::getMinXF()) / dVar21); f32 local_298 = ((local_218.y / dVar16) / dVar16); f32 local_29c = ((local_224.x - local_218.x) / dVar21); f32 local_2a0 = 2.0f + (local_224.y - local_218.y); +#endif grafContext->scissor(local_294, local_298, local_29c, local_2a0); grafContext->setScissor(); From 1b00ddc5c5da7945780a534ca165a83bf8d0fcab Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Wed, 1 Apr 2026 19:25:27 -0400 Subject: [PATCH 251/350] fix building --- src/d/d_meter_map.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/d/d_meter_map.cpp b/src/d/d_meter_map.cpp index 362a996d60..6b78e2242b 100644 --- a/src/d/d_meter_map.cpp +++ b/src/d/d_meter_map.cpp @@ -629,8 +629,8 @@ void dMeterMap_c::draw() { mMapJ2DPicture->setAlpha(alpha); #if TARGET_PC - mMapJ2DPicture->draw(mDoGph_gInf_c::ScaleHUDXLeft(drawPosX), drawPosY, sizeX, sizeY, false, false, - false); + mMapJ2DPicture->draw(mDoGph_gInf_c::ScaleHUDXLeft(drawPosX), drawPosY, sizeX, sizeY, false, + false, false); #else mMapJ2DPicture->draw(drawPosX, drawPosY, sizeX, sizeY, false, false, false); #endif From 79dc5de912c262fbda5a86e928828d008fe16619 Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Wed, 1 Apr 2026 20:20:09 -0400 Subject: [PATCH 252/350] Fix underwater effect + Fading to work at any aspect ratio --- src/d/d_ovlp_fade2.cpp | 6 ++++++ src/d/d_ovlp_fade3.cpp | 6 ++++++ src/m_Do/m_Do_graphic.cpp | 6 ++++++ 3 files changed, 18 insertions(+) diff --git a/src/d/d_ovlp_fade2.cpp b/src/d/d_ovlp_fade2.cpp index 093aaadddd..37dbeedba2 100644 --- a/src/d/d_ovlp_fade2.cpp +++ b/src/d/d_ovlp_fade2.cpp @@ -51,8 +51,14 @@ void dOvlpFd2_dlst_c::draw() { GXEnd(); Mtx44 m; + + #if TARGET_PC + C_MTXPerspective(m, 60.0f, 1.3571428f, 100.0f, 100000.0f); + #else C_MTXPerspective(m, 60.0f, mDoGph_gInf_c::getWidthF() / mDoGph_gInf_c::getHeightF(), 100.0f, 100000.0f); + #endif + GXSetProjection(m, GX_PERSPECTIVE); #ifdef TARGET_PC mDoGph_gInf_c::getFrameBufferTexObj()->reset(); diff --git a/src/d/d_ovlp_fade3.cpp b/src/d/d_ovlp_fade3.cpp index 70304451fb..d6f432f3ee 100644 --- a/src/d/d_ovlp_fade3.cpp +++ b/src/d/d_ovlp_fade3.cpp @@ -64,8 +64,14 @@ void dOvlpFd3_dlst_c::draw() { GXEnd(); Mtx44 m; + + #if TARGET_PC + C_MTXPerspective(m, 60.0f, 1.3571428f, 100.0f, 100000.0f); + #else C_MTXPerspective(m, 60.0f, mDoGph_gInf_c::getWidthF() / mDoGph_gInf_c::getHeightF(), 100.0f, 100000.0f); + #endif + GXSetProjection(m, GX_PERSPECTIVE); #ifdef TARGET_PC mDoGph_gInf_c::getFrameBufferTexObj()->reset(); diff --git a/src/m_Do/m_Do_graphic.cpp b/src/m_Do/m_Do_graphic.cpp index 2657b0cd16..e4a640fd88 100644 --- a/src/m_Do/m_Do_graphic.cpp +++ b/src/m_Do/m_Do_graphic.cpp @@ -2051,7 +2051,13 @@ int mDoGph_Painter() { Mtx m2; Mtx44 m; + + #if TARGET_PC + C_MTXPerspective(m, AREG_F(8) + 60.0f, 1.3571428f, 1.0f, 100000.0f); + #else C_MTXPerspective(m, AREG_F(8) + 60.0f, mDoGph_gInf_c::getAspect(), 1.0f, 100000.0f); + #endif + GXSetProjection(m, GX_PERSPECTIVE); cXyz sp38c(0.0f, 0.0f, AREG_F(7) + -2.0f); cXyz sp398(0.0f, 1.0f, 0.0f); From 162d108dbcaeb7bb0a4c96fdef8c2c1008c34920 Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Wed, 1 Apr 2026 20:24:59 -0400 Subject: [PATCH 253/350] Fix black screen effect when turning into Wolf Link --- src/d/actor/d_a_alink_effect.inc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/d/actor/d_a_alink_effect.inc b/src/d/actor/d_a_alink_effect.inc index 6e76280ff6..1c02cb54f0 100644 --- a/src/d/actor/d_a_alink_effect.inc +++ b/src/d/actor/d_a_alink_effect.inc @@ -1562,6 +1562,11 @@ void daAlink_c::setMetamorphoseEffectStartLink() { emitter->setGlobalParticleScale(effScale); } #endif + + #if TARGET_PC + static const Vec effWideScale = {mDoGph_gInf_c::getAspect(), 1.0f, 1.0f}; + emitter->setGlobalParticleScale(effWideScale); + #endif } void daAlink_c::setMetamorphoseEffect() { From d400557c4534337b9b6c70980319553742dba88e Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Wed, 1 Apr 2026 20:38:59 -0400 Subject: [PATCH 254/350] Fix Wolf Senses vignette on widescreen --- src/d/d_kankyo.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/d/d_kankyo.cpp b/src/d/d_kankyo.cpp index ec35ee8bc7..4a07f0be60 100644 --- a/src/d/d_kankyo.cpp +++ b/src/d/d_kankyo.cpp @@ -775,7 +775,7 @@ static void dKy_FiveSenses_fullthrottle_dark_static1() { particle_size.y = 1.0f; particle_size.z = 1.0f; - #if !PLATFORM_GCN + #if !PLATFORM_GCN || TARGET_PC particle_size.x *= mDoGph_gInf_c::getScale(); #endif From 05b2ea7a32bd85d34a63340f848ff4a201acf5f5 Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Thu, 2 Apr 2026 13:45:59 -0400 Subject: [PATCH 255/350] fix full map rendering with multiple ratios, cursor still glitched --- src/d/d_menu_fmap2D.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/d/d_menu_fmap2D.cpp b/src/d/d_menu_fmap2D.cpp index eac8d1992b..c9ab812400 100644 --- a/src/d/d_menu_fmap2D.cpp +++ b/src/d/d_menu_fmap2D.cpp @@ -1195,7 +1195,7 @@ f32 dMenu_Fmap2DBack_c::getMapScissorAreaSizeX() { } f32 dMenu_Fmap2DBack_c::getMapScissorAreaSizeRealX() { -#if PLATFORM_GCN +#if PLATFORM_GCN && !TARGET_PC return getMapScissorAreaSizeX(); #else return getMapScissorAreaSizeX() * mDoGph_gInf_c::getScale(); From 4981a8904b911de1662408f188e9a4c41b730425 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Thu, 2 Apr 2026 20:29:07 +0200 Subject: [PATCH 256/350] Fix compile by giving WIDESCREEN_SUPPORT a value --- CMakeLists.txt | 2 +- extern/aurora | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cfb76e85ff..8be9c03446 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -88,7 +88,7 @@ message(STATUS "dusk: TP Version: ${DUSK_TP_VERSION}") source_group("dolzel" FILES ${DOLZEL_FILES} ${Z2AUDIOLIB_FILES} ${JSYSTEM_FILES} ${JSYSTEM_DEBUG_FILES} ${REL_FILES}) source_group("dusk" FILES ${DUSK_FILES}) -set(GAME_COMPILE_DEFS TARGET_PC WIDESCREEN_SUPPORT AVOID_UB=1 VERSION=0 +set(GAME_COMPILE_DEFS TARGET_PC WIDESCREEN_SUPPORT=1 AVOID_UB=1 VERSION=0 DUSK_TP_VERSION="${DUSK_TP_VERSION}" DUSK_GAME_NAME="${DUSK_GAME_NAME}" DUSK_GAME_VERSION="${DUSK_GAME_VERSION}") set(GAME_INCLUDE_DIRS diff --git a/extern/aurora b/extern/aurora index b17da31593..7eb8efcd17 160000 --- a/extern/aurora +++ b/extern/aurora @@ -1 +1 @@ -Subproject commit b17da315932b85d7d708eaf3e44914f70045a44e +Subproject commit 7eb8efcd172ceed27d120ffb6de86c63d1f3cad0 From 9e5fe7df3ff2a46e94fd11b1ef40bb0c801a1331 Mon Sep 17 00:00:00 2001 From: CraftyBoss Date: Sat, 4 Apr 2026 23:42:00 -0700 Subject: [PATCH 257/350] update HUD positions in updateOnWide instead of when drawing this change makes it so positions will properly update when resizing the window, instead of only updating on room load --- src/d/d_meter2_draw.cpp | 27 --------------------------- src/d/d_meter2_info.cpp | 4 ++++ src/d/d_meter_HIO.cpp | 13 ++++++++++++- src/m_Do/m_Do_graphic.cpp | 4 +--- 4 files changed, 17 insertions(+), 31 deletions(-) diff --git a/src/d/d_meter2_draw.cpp b/src/d/d_meter2_draw.cpp index ab4b1cc86c..58c6e24dc9 100644 --- a/src/d/d_meter2_draw.cpp +++ b/src/d/d_meter2_draw.cpp @@ -565,12 +565,7 @@ void dMeter2Draw_c::exec(u32 i_status) { mButtonsPosX = g_drawHIO.mMainHUDButtonsPosX; mButtonsPosY = g_drawHIO.mMainHUDButtonsPosY; - #if TARGET_PC - mpButtonParent->paneTrans(mDoGph_gInf_c::ScaleHUDXRight(g_drawHIO.mMainHUDButtonsPosX), - g_drawHIO.mMainHUDButtonsPosY); - #else mpButtonParent->paneTrans(g_drawHIO.mMainHUDButtonsPosX, g_drawHIO.mMainHUDButtonsPosY); - #endif } if (mButtonsScale != g_drawHIO.mMainHUDButtonsScale) { @@ -1482,11 +1477,7 @@ void dMeter2Draw_c::drawLife(s16 i_maxLife, s16 i_life, f32 i_posX, f32 i_posY) mpBigHeart->scale(g_drawHIO.mBigHeartScale, g_drawHIO.mBigHeartScale); } - #if TARGET_PC - mpLifeParent->paneTrans(mDoGph_gInf_c::ScaleHUDXLeft(i_posX), i_posY); - #else mpLifeParent->paneTrans(i_posX, i_posY); - #endif } void dMeter2Draw_c::setAlphaLifeChange(bool param_0) { @@ -1600,12 +1591,7 @@ void dMeter2Draw_c::drawKanteraScreen(u8 i_meterType) { mpMagicBase->resize(field_0x5b4[i_meterType], field_0x5c0[i_meterType]); mpMagicParent->scale(field_0x5cc[i_meterType], field_0x5d8[i_meterType]); - #if TARGET_PC - mpMagicParent->paneTrans(mDoGph_gInf_c::ScaleHUDXLeft(field_0x5e4[i_meterType]), - field_0x5f0[i_meterType]); - #else mpMagicParent->paneTrans(field_0x5e4[i_meterType], field_0x5f0[i_meterType]); - #endif mpKanteraScreen->draw(0.0f, 0.0f, graf_ctx); } @@ -1872,11 +1858,7 @@ void dMeter2Draw_c::drawLightDrop(u8 i_num, u8 i_needNum, f32 i_posX, f32 i_posY mpLightDropParent->scale(mLightDropVesselScale * field_0x6f8, mLightDropVesselScale * field_0x6f8); - #if TARGET_PC - mpLightDropParent->paneTrans(mDoGph_gInf_c::ScaleHUDXRight(i_posX), i_posY); - #else mpLightDropParent->paneTrans(i_posX, i_posY); - #endif } void dMeter2Draw_c::setAlphaLightDropChange(bool unused) {} @@ -2024,12 +2006,7 @@ void dMeter2Draw_c::drawRupee(s16 i_rupeeNum) { mpRupeeKeyParent->scale(g_drawHIO.mRupeeKeyScale * field_0x718, g_drawHIO.mRupeeKeyScale * field_0x718); - #if TARGET_PC - mpRupeeKeyParent->paneTrans(mDoGph_gInf_c::ScaleHUDXRight(g_drawHIO.mRupeeKeyPosX), - g_drawHIO.mRupeeKeyPosY); - #else mpRupeeKeyParent->paneTrans(g_drawHIO.mRupeeKeyPosX, g_drawHIO.mRupeeKeyPosY); - #endif mpRupeeParent[0]->scale(g_drawHIO.mRupeeScale, g_drawHIO.mRupeeScale); mpRupeeParent[0]->paneTrans(g_drawHIO.mRupeePosX, g_drawHIO.mRupeePosY); @@ -2611,11 +2588,7 @@ void dMeter2Draw_c::drawButtonCross(f32 i_posX, f32 i_posY) { mpTextI->scale(g_drawHIO.mButtonCrossTextScale, g_drawHIO.mButtonCrossTextScale); mpTextM->scale(g_drawHIO.mButtonCrossTextScale, g_drawHIO.mButtonCrossTextScale); - #if TARGET_PC - mpButtonCrossParent->paneTrans(mDoGph_gInf_c::ScaleHUDXLeft(i_posX), i_posY); - #else mpButtonCrossParent->paneTrans(i_posX, i_posY); - #endif } void dMeter2Draw_c::setAlphaButtonCrossAnimeMin() { diff --git a/src/d/d_meter2_info.cpp b/src/d/d_meter2_info.cpp index 3e8d2601c1..820e65dc0d 100644 --- a/src/d/d_meter2_info.cpp +++ b/src/d/d_meter2_info.cpp @@ -1868,12 +1868,16 @@ f32 dMeter2Info_getWide2DPosX(f32* param_0) { } void dMeter2Info_onWide2D() { +#if !TARGET_PC g_ringHIO.updateOnWide(); +#endif g_drawHIO.updateOnWide(); } void dMeter2Info_offWide2D() { +#if !TARGET_PC g_ringHIO.updateOffWide(); +#endif g_drawHIO.updateOffWide(); } #endif diff --git a/src/d/d_meter_HIO.cpp b/src/d/d_meter_HIO.cpp index 240dcb8d45..94b1c79420 100644 --- a/src/d/d_meter_HIO.cpp +++ b/src/d/d_meter_HIO.cpp @@ -2287,7 +2287,18 @@ dMeter_drawHIO_c::dMeter_drawHIO_c() { } #if WIDESCREEN_SUPPORT -void dMeter_drawHIO_c::updateOnWide() {} +void dMeter_drawHIO_c::updateOnWide() { +#if TARGET_PC + g_drawHIO = {}; // this might be a bad idea + + g_drawHIO.mMainHUDButtonsPosX = mDoGph_gInf_c::ScaleHUDXRight(g_drawHIO.mMainHUDButtonsPosX); + g_drawHIO.mRupeeKeyPosX = mDoGph_gInf_c::ScaleHUDXRight(g_drawHIO.mRupeeKeyPosX); + g_drawHIO.mButtonCrossOFFPosX = mDoGph_gInf_c::ScaleHUDXLeft(g_drawHIO.mButtonCrossOFFPosX); + g_drawHIO.mButtonCrossONPosX = mDoGph_gInf_c::ScaleHUDXLeft(g_drawHIO.mButtonCrossONPosX); + g_drawHIO.mLifeGaugePosX = mDoGph_gInf_c::ScaleHUDXLeft(g_drawHIO.mLifeGaugePosX); + g_drawHIO.mLanternMeterPosX = mDoGph_gInf_c::ScaleHUDXLeft(g_drawHIO.mLanternMeterPosX); +#endif +} void dMeter_drawHIO_c::updateOffWide() {} #endif diff --git a/src/m_Do/m_Do_graphic.cpp b/src/m_Do/m_Do_graphic.cpp index e4a640fd88..5c535c2eb1 100644 --- a/src/m_Do/m_Do_graphic.cpp +++ b/src/m_Do/m_Do_graphic.cpp @@ -593,6 +593,7 @@ void mDoGph_gInf_c::onWide(f32 width, f32 height) { pc_tvSize.width = width; pc_tvSize.height = height; setTvSize(); + dMeter2Info_onWide2D(); } #else void mDoGph_gInf_c::onWide() { @@ -605,10 +606,7 @@ void mDoGph_gInf_c::onWide() { void mDoGph_gInf_c::offWide() { mWide = FALSE; setTvSize(); - -#if !TARGET_PC dMeter2Info_offWide2D(); -#endif } void mDoGph_gInf_c::onWideZoom() { From 1478b7054a57e70d7b657b4a8995493cb953e882 Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Sun, 5 Apr 2026 11:15:36 -0400 Subject: [PATCH 258/350] Modified Map Name to work with aspect ratios --- src/d/d_msg_scrn_place.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/d/d_msg_scrn_place.cpp b/src/d/d_msg_scrn_place.cpp index 881852c259..b2c28efc95 100644 --- a/src/d/d_msg_scrn_place.cpp +++ b/src/d/d_msg_scrn_place.cpp @@ -101,13 +101,25 @@ void dMsgScrnPlace_c::exec() { mpFontParent->scale(g_MsgObject_HIO_c.mStageTitleCharSizeX, g_MsgObject_HIO_c.mStageTitleCharSizeY); + +#if TARGET_PC + mpFontParent->paneTrans(mDoGph_gInf_c::ScaleHUDXLeft(g_MsgObject_HIO_c.mStageTitleCharPosX), + g_MsgObject_HIO_c.mStageTitleCharPosY - mScaleX); +#else mpFontParent->paneTrans(g_MsgObject_HIO_c.mStageTitleCharPosX, g_MsgObject_HIO_c.mStageTitleCharPosY - mScaleX); +#endif mpBaseParent->scale(g_MsgObject_HIO_c.mStageTitleBaseSizeX, g_MsgObject_HIO_c.mStageTitleBaseSizeY); + +#if TARGET_PC + mpBaseParent->paneTrans(mDoGph_gInf_c::ScaleHUDXLeft(g_MsgObject_HIO_c.mStageTitleBasePosX), + g_MsgObject_HIO_c.mStageTitleBasePosY - mScaleY); +#else mpBaseParent->paneTrans(g_MsgObject_HIO_c.mStageTitleBasePosX, g_MsgObject_HIO_c.mStageTitleBasePosY - mScaleY); +#endif if (isTalkNow()) { fukiAlpha(1.0f); From 8f367a99534ee95908c29583dbce167c5c302dd3 Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Sun, 5 Apr 2026 12:30:27 -0400 Subject: [PATCH 259/350] Almost all menus scaled --- include/d/d_bright_check.h | 3 ++ include/d/d_file_select.h | 3 ++ include/d/d_menu_collect.h | 5 ++ include/d/d_menu_save.h | 5 ++ include/d/d_name.h | 5 ++ src/d/d_bright_check.cpp | 49 +++++++++++++++++++ src/d/d_file_select.cpp | 99 ++++++++++++++++++++++++++++++++++++++ src/d/d_menu_collect.cpp | 98 +++++++++++++++++++++++++++++++++++++ src/d/d_menu_fishing.cpp | 7 +++ src/d/d_menu_insect.cpp | 14 ++++++ src/d/d_menu_save.cpp | 56 +++++++++++++++++++++ src/d/d_menu_skill.cpp | 14 ++++++ src/d/d_name.cpp | 60 +++++++++++++++++++++++ 13 files changed, 418 insertions(+) diff --git a/include/d/d_bright_check.h b/include/d/d_bright_check.h index d8686944e6..dc9ae941cf 100644 --- a/include/d/d_bright_check.h +++ b/include/d/d_bright_check.h @@ -29,6 +29,9 @@ public: void _move(); void modeWait(); void modeMove(); + #if TARGET_PC + void brightCheckWide(); + #endif void _draw(); void draw() { diff --git a/include/d/d_file_select.h b/include/d/d_file_select.h index 810831a830..72f7cb7f42 100644 --- a/include/d/d_file_select.h +++ b/include/d/d_file_select.h @@ -367,6 +367,9 @@ public: void menuCursorShow(); void yesnoWakuAlpahAnmInit(u8, u8, u8, u8); bool yesnoWakuAlpahAnm(u8); + #if TARGET_PC + void fileSelectWide(); + #endif void _draw(); void errorMoveAnmInitSet(int, int); bool errorMoveAnm(); diff --git a/include/d/d_menu_collect.h b/include/d/d_menu_collect.h index 01ee32f6d4..587fd5c103 100644 --- a/include/d/d_menu_collect.h +++ b/include/d/d_menu_collect.h @@ -29,6 +29,11 @@ public: class dMenu_Collect2D_c : public dDlst_base_c { public: dMenu_Collect2D_c(JKRExpHeap*, STControl*, CSTControl*); + + #if TARGET_PC + void menuCollectWide(); + #endif + void _create(); void _delete(); void initialize(); diff --git a/include/d/d_menu_save.h b/include/d/d_menu_save.h index a6b174bbf4..4a70d1d045 100644 --- a/include/d/d_menu_save.h +++ b/include/d/d_menu_save.h @@ -263,6 +263,11 @@ public: void setSaveData(); void setInitSaveData(); void _draw(); + + #if TARGET_PC + void menuSaveWide(); + #endif + void _draw2(); virtual ~dMenu_save_c() {} diff --git a/include/d/d_name.h b/include/d/d_name.h index 0d877e6b09..e685871738 100644 --- a/include/d/d_name.h +++ b/include/d/d_name.h @@ -110,6 +110,11 @@ public: void menuCursorMove(); void menuCursorMove2(); void selectCursorPosSet(int); + + #if TARGET_PC + void nameWide(); + #endif + void _draw(); void screenSet(); void displayInit(); diff --git a/src/d/d_bright_check.cpp b/src/d/d_bright_check.cpp index 36f24ebd72..ac28e46838 100644 --- a/src/d/d_bright_check.cpp +++ b/src/d/d_bright_check.cpp @@ -143,7 +143,56 @@ void dBrightCheck_c::modeMove() { } } +#if TARGET_PC +void dBrightCheck_c::brightCheckWide() { + // Main Canvas + mBrightCheck.Scr->scale(mDoGph_gInf_c::hudAspectScaleUp, 1.0f); + mBrightCheck.Scr->translate(mDoGph_gInf_c::getMinXF(), 0.0f); + + // Right Square + mBrightCheck.Scr->search(MULTI_CHAR('fuchi_1'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + mBrightCheck.Scr->search(MULTI_CHAR('big_squa'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + + // Middle Square + mBrightCheck.Scr->search(MULTI_CHAR('fuchi_3'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + mBrightCheck.Scr->search(MULTI_CHAR('big_squ1'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + + // Left Square + mBrightCheck.Scr->search(MULTI_CHAR('fuchi_4'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + mBrightCheck.Scr->search(MULTI_CHAR('big_squ2'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + + // Gray Squares + mBrightCheck.Scr->search(MULTI_CHAR('gray_n'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + mBrightCheck.Scr->search(MULTI_CHAR('fuchi_2'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + + // Confirm A Button + mBrightCheck.Scr->search(MULTI_CHAR('abtn_n'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + mBrightCheck.Scr->search(MULTI_CHAR('gcabtn_n'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + + // Text + mBrightCheck.Scr->search(MULTI_CHAR('menu_6n'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + mBrightCheck.Scr->search(MULTI_CHAR('menu_9n'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + mBrightCheck.Scr->search(MULTI_CHAR('menu_10n'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + mBrightCheck.Scr->search(MULTI_CHAR('menu_7n'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + mBrightCheck.Scr->search(MULTI_CHAR('menu_8n'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + mBrightCheck.Scr->search(MULTI_CHAR('fmenu_8n'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + mBrightCheck.Scr->search(MULTI_CHAR('fmenu_7n'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + mBrightCheck.Scr->search(MULTI_CHAR('fmenu_10'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + mBrightCheck.Scr->search(MULTI_CHAR('fmenu_6n'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + mBrightCheck.Scr->search(MULTI_CHAR('fmenu_9n'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + mBrightCheck.Scr->search(MULTI_CHAR('t_t00'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + mBrightCheck.Scr->search(MULTI_CHAR('f_t00'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + + // Spirals + mBrightCheck.Scr->search(MULTI_CHAR('t_mo_l_n'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + mBrightCheck.Scr->search(MULTI_CHAR('t_mo_r_n'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); +} +#endif + void dBrightCheck_c::_draw() { + #if TARGET_PC + brightCheckWide(); + #endif dComIfGd_set2DOpa(&mBrightCheck); } diff --git a/src/d/d_file_select.cpp b/src/d/d_file_select.cpp index 16aafb721f..c739b39db2 100644 --- a/src/d/d_file_select.cpp +++ b/src/d/d_file_select.cpp @@ -2091,7 +2091,12 @@ void dFile_select_c::yesnoCursorShow() { Vec pos = mYnSelPane[field_0x0268]->getGlobalVtxCenter(0, 0); mSelIcon->setPos(pos.x, pos.y, mYnSelPane[field_0x0268]->getPanePtr(), true); mSelIcon->setAlphaRate(1.0f); + + #if TARGET_PC + mSelIcon->setParam(0.96f * mDoGph_gInf_c::hudAspectScaleUp, 0.84f, 0.06f, 0.5f, 0.5f); + #else mSelIcon->setParam(0.96f, 0.84f, 0.06f, 0.5f, 0.5f); + #endif } } @@ -2243,7 +2248,12 @@ void dFile_select_c::YesNoCancelMove() { mSelIcon->setPos(vtxCenter.x, vtxCenter.y, m3mSelPane[mSelectMenuNum]->getPanePtr(), true); mSelIcon->setAlphaRate(1.0f); + + #if TARGET_PC + mSelIcon->setParam(0.96f * mDoGph_gInf_c::hudAspectScaleUp, 0.84f, 0.06f, 0.5f, 0.5f); + #else mSelIcon->setParam(0.96f, 0.84f, 0.06f, 0.5f, 0.5f); + #endif #if PLATFORM_WII || PLATFORM_SHIELD field_0x4333 = mSelectMenuNum; @@ -3126,7 +3136,13 @@ void dFile_select_c::screenSet() { mSelIcon = JKR_NEW dSelect_cursor_c(0, 1.0f, NULL); JUT_ASSERT(5209, mSelIcon != NULL); + + #if TARGET_PC + mSelIcon->setParam(0.96f * mDoGph_gInf_c::hudAspectScaleUp, 0.94f, 0.03f, 0.7f, 0.7f); + #else mSelIcon->setParam(0.96f, 0.94f, 0.03f, 0.7f, 0.7f); + #endif + Vec vtxCenter; vtxCenter = mSelFilePanes[mSelectNum]->getGlobalVtxCenter(false, 0); mSelIcon->setPos(vtxCenter.x, vtxCenter.y, mSelFilePanes[mSelectNum]->getPanePtr(), true); @@ -3257,7 +3273,13 @@ void dFile_select_c::screenSetCopySel() { mSelIcon2 = JKR_NEW dSelect_cursor_c(0, 1.0f, NULL); JUT_ASSERT(5406, mSelIcon2 != NULL); + + #if TARGET_PC + mSelIcon2->setParam(0.96f * mDoGph_gInf_c::hudAspectScaleUp, 0.94f, 0.03f, 0.7f, 0.7f); + #else mSelIcon2->setParam(0.96f, 0.94f, 0.03f, 0.7f, 0.7f); + #endif + Vec center = mCpSelPane[0]->getGlobalVtxCenter(false, 0); mSelIcon2->setPos(center.x, center.y, mCpSelPane[0]->getPanePtr(), true); mSelIcon2->setAlphaRate(0.0f); @@ -3647,7 +3669,12 @@ void dFile_select_c::selFileCursorShow() { Vec local_1c = mSelFilePanes[mSelectNum]->getGlobalVtxCenter(false, 0); mSelIcon->setPos(local_1c.x, local_1c.y, mSelFilePanes[mSelectNum]->getPanePtr(), true); mSelIcon->setAlphaRate(1.0f); + + #if TARGET_PC + mSelIcon->setParam(0.96f * mDoGph_gInf_c::hudAspectScaleUp, 0.94f, 0.03f, 0.7f, 0.7f); + #else mSelIcon->setParam(0.96f, 0.94f, 0.03f, 0.7f, 0.7f); + #endif } void dFile_select_c::menuWakuAlpahAnmInit(u8 i_idx, u8 param_1, u8 param_2, u8 param_3) { @@ -3689,7 +3716,12 @@ void dFile_select_c::menuCursorShow() { Vec local_24 = m3mSelPane[mSelectMenuNum]->getGlobalVtxCenter(false, 0); mSelIcon->setPos(local_24.x, local_24.y, m3mSelPane[mSelectMenuNum]->getPanePtr(), true); mSelIcon->setAlphaRate(1.0f); + + #if TARGET_PC + mSelIcon->setParam(0.96f * mDoGph_gInf_c::hudAspectScaleUp, 0.84f, 0.06f, 0.5f, 0.5f); + #else mSelIcon->setParam(0.96f, 0.84f, 0.06f, 0.5f, 0.5f); + #endif } } @@ -3731,7 +3763,74 @@ bool dFile_select_c::yesnoWakuAlpahAnm(u8 param_1) { return rv; } +#if TARGET_PC +void dFile_select_c::fileSelectWide() { + mYnSel.ScrYn->scale(mDoGph_gInf_c::hudAspectScaleUp, 1.0f); + mYnSel.ScrYn->translate(mDoGph_gInf_c::getMinXF(), 0.0f); + + mYnSel.ScrYn->search(MULTI_CHAR('w_no_t'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + mYnSel.ScrYn->search(MULTI_CHAR('f_no_t'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + mYnSel.ScrYn->search(MULTI_CHAR('w_yes_t'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + mYnSel.ScrYn->search(MULTI_CHAR('f_yes_t'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + + m3mSel.Scr3m->scale(mDoGph_gInf_c::hudAspectScaleUp, 1.0f); + m3mSel.Scr3m->translate(mDoGph_gInf_c::getMinXF(), 0.0f); + + m3mSel.Scr3m->search(MULTI_CHAR('w_sta'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + m3mSel.Scr3m->search(MULTI_CHAR('f_sta'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + m3mSel.Scr3m->search(MULTI_CHAR('w_del'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + m3mSel.Scr3m->search(MULTI_CHAR('f_del'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + m3mSel.Scr3m->search(MULTI_CHAR('w_cop_t'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + m3mSel.Scr3m->search(MULTI_CHAR('f_cop_t'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + + fileSel.Scr->scale(mDoGph_gInf_c::hudAspectScaleUp, 1.0f); + fileSel.Scr->translate(mDoGph_gInf_c::getMinXF(), 0.0f); + + fileSel.Scr->search(MULTI_CHAR('t_for'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + fileSel.Scr->search(MULTI_CHAR('t_for1'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + + fileSel.Scr->search(MULTI_CHAR('w_btn_n'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + + fileSel.Scr->search(MULTI_CHAR('w_n_bk00'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + fileSel.Scr->search(MULTI_CHAR('w_n_bk01'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + fileSel.Scr->search(MULTI_CHAR('w_n_bk02'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + + fileSel.Scr->search(MULTI_CHAR('w_dat_i0'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + fileSel.Scr->search(MULTI_CHAR('w_dat_i1'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + fileSel.Scr->search(MULTI_CHAR('w_dat_i2'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + + mCpSel.Scr->search(MULTI_CHAR('w_dat_i1'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + mCpSel.Scr->search(MULTI_CHAR('w_dat_i2'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + mCpSel.Scr->search(MULTI_CHAR('w_n_bk01'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + mCpSel.Scr->search(MULTI_CHAR('w_n_bk02'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + + mSelDt.ScrDt->search(MULTI_CHAR('tate_n0'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + mSelDt.ScrDt->search(MULTI_CHAR('tate_n1'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + mSelDt.ScrDt->search(MULTI_CHAR('ken_n0'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + mSelDt.ScrDt->search(MULTI_CHAR('ken_n1'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + mSelDt.ScrDt->search(MULTI_CHAR('fuku_n0'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + mSelDt.ScrDt->search(MULTI_CHAR('fuku_n1'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + mSelDt.ScrDt->search(MULTI_CHAR('fuku_n2'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + + // Spirals + fileSel.Scr->search(MULTI_CHAR('w_uzu00'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + fileSel.Scr->search(MULTI_CHAR('w_uzu01'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + fileSel.Scr->search(MULTI_CHAR('w_uzu02'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + fileSel.Scr->search(MULTI_CHAR('w_uzu03'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + fileSel.Scr->search(MULTI_CHAR('w_uzu04'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + fileSel.Scr->search(MULTI_CHAR('w_uzu05'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + fileSel.Scr->search(MULTI_CHAR('w_uzu06'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + fileSel.Scr->search(MULTI_CHAR('w_uzu07'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + fileSel.Scr->search(MULTI_CHAR('w_uzu08'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + fileSel.Scr->search(MULTI_CHAR('w_uzu09'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); +} +#endif + void dFile_select_c::_draw() { + #if TARGET_PC + fileSelectWide(); + #endif + if (!mHasDrawn) { dComIfGd_set2DOpa(&fileSel); diff --git a/src/d/d_menu_collect.cpp b/src/d/d_menu_collect.cpp index c30bd85f9b..ed5586e866 100644 --- a/src/d/d_menu_collect.cpp +++ b/src/d/d_menu_collect.cpp @@ -95,6 +95,78 @@ dMenu_Collect2D_c::~dMenu_Collect2D_c() { } } +#if TARGET_PC +void dMenu_Collect2D_c::menuCollectWide() { + // Main Canvas + mpScreen->scale(mDoGph_gInf_c::hudAspectScaleUp, 1.0f); + mpScreen->translate(mDoGph_gInf_c::getMinXF(), 0.0f); + + // Pieces of Heart + mpScreen->search(MULTI_CHAR('heart_n'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + + // Scents + mpScreen->search(MULTI_CHAR('wolf_n'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + + // Quiver + mpScreen->search(MULTI_CHAR('item_0_n'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + + // Wallet + mpScreen->search(MULTI_CHAR('item_1_n'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + + // Poes + mpScreen->search(MULTI_CHAR('item_2_n'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + + // Fish Bestiary + mpScreen->search(MULTI_CHAR('fish_3_n'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + + // Letters + mpScreen->search(MULTI_CHAR('lett_4_n'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + + // Hidden Skills + mpScreen->search(MULTI_CHAR('maki_5_n'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + + // Green Tunic + mpScreen->search(MULTI_CHAR('fuku_n0'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + + // Zora Armor + mpScreen->search(MULTI_CHAR('fuku_n1'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + + // Magic Armor + mpScreen->search(MULTI_CHAR('fuku_n2'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + + // Ordon Shield + mpScreen->search(MULTI_CHAR('tate_n0'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + + // Hylian Shield + mpScreen->search(MULTI_CHAR('tate_n1'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + + // Ordon Sword + mpScreen->search(MULTI_CHAR('ken_n0'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + + // Master Sword + mpScreen->search(MULTI_CHAR('ken_n1'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + + // Bugs + mpScreen->search(MULTI_CHAR('kabu_6n'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + + // "Collection" Text + mpScreen->search(MULTI_CHAR('t_t00'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + mpScreen->search(MULTI_CHAR('f_t00'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + + // "Save" Text + mpScreen->search(MULTI_CHAR('sa_tex_n'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + + // "Options" Text + mpScreen->search(MULTI_CHAR('op_tex_n'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + + // Item Name Text + mpScreen->search(MULTI_CHAR('itemn_n'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + + // Item Description Text + mpScreen->search(MULTI_CHAR('infotxtn'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); +} +#endif + void dMenu_Collect2D_c::_create() { mpHeap->getTotalFreeSize(); mpScreen = JKR_NEW J2DScreen(); @@ -108,7 +180,17 @@ void dMenu_Collect2D_c::_create() { mpButtonAB[i] = NULL; mpButtonText[i] = NULL; } + + #if TARGET_PC + mpScreenIcon->translate(-mDoGph_gInf_c::getMinXF(), 0.0f); + #endif + dPaneClass_showNullPane(mpScreenIcon); + + #if TARGET_PC + menuCollectWide(); + #endif + mpDraw2DTop = JKR_NEW dMenu_Collect2DTop_c(this); ResTIMG* image = (ResTIMG*)dComIfGp_getMain2DArchive()->getResource('TIMG', "tt_block8x8.bti"); mpBlackTex = JKR_NEW J2DPicture(image); @@ -1004,11 +1086,23 @@ void dMenu_Collect2D_c::cursorPosSet() { Vec pos = mpSelPm[mCursorX][mCursorY]->getGlobalVtxCenter(false, 0); mpDrawCursor->setPos(pos.x, pos.y, mpSelPm[mCursorX][mCursorY]->getPanePtr(), false); if (mCursorY == 5) { + #if TARGET_PC + mpDrawCursor->setParam(1.1f * mDoGph_gInf_c::hudAspectScaleUp, 0.85f, 0.05f, 0.5f, 0.5f); + #else mpDrawCursor->setParam(1.1f, 0.85f, 0.05f, 0.5f, 0.5f); + #endif } else if (mCursorX == 6 && mCursorY == 0) { + #if TARGET_PC + mpDrawCursor->setParam(0.6f * mDoGph_gInf_c::hudAspectScaleUp, 0.85f, 0.03f, 0.6f, 0.6f); + #else mpDrawCursor->setParam(0.6f, 0.85f, 0.03f, 0.6f, 0.6f); + #endif } else { + #if TARGET_PC + mpDrawCursor->setParam(1.0f * mDoGph_gInf_c::hudAspectScaleUp, 1.0f, 0.1f, 0.7f, 0.7f); + #else mpDrawCursor->setParam(1.0f, 1.0f, 0.1f, 0.7f, 0.7f); + #endif } } @@ -2028,6 +2122,10 @@ void dMenu_Collect2D_c::_move() { void dMenu_Collect2D_c::_draw() { + #if TARGET_PC + menuCollectWide(); + #endif + J2DGrafContext* grafPort = dComIfGp_getCurrentGrafPort(); grafPort->setup2D(); mpScreen->draw(0.0f, 0.0f, grafPort); diff --git a/src/d/d_menu_fishing.cpp b/src/d/d_menu_fishing.cpp index 189ce57e0f..615323767b 100644 --- a/src/d/d_menu_fishing.cpp +++ b/src/d/d_menu_fishing.cpp @@ -114,7 +114,14 @@ void dMenu_Fishing_c::_draw() { if (mpArchive) { J2DGrafContext* grafPort = dComIfGp_getCurrentGrafPort(); mpBlackTex->setAlpha(0xff); + + #if TARGET_PC + mpBlackTex->draw(mDoGph_gInf_c::getMinXF(), mDoGph_gInf_c::getMinYF(), + mDoGph_gInf_c::getWidthF(), mDoGph_gInf_c::getHeightF(), 0, 0, 0); + #else mpBlackTex->draw(0.0f, 0.0f, FB_WIDTH, FB_HEIGHT, 0, 0, 0); + #endif + mpScreen->draw(0.0f, 0.0f, grafPort); mpIconScreen->draw(0.0f, 0.0f, grafPort); } diff --git a/src/d/d_menu_insect.cpp b/src/d/d_menu_insect.cpp index 82be9287b6..7558fe61b4 100644 --- a/src/d/d_menu_insect.cpp +++ b/src/d/d_menu_insect.cpp @@ -161,12 +161,26 @@ void dMenu_Insect_c::_draw() { if (mpArchive != NULL) { J2DGrafContext* grafPort = dComIfGp_getCurrentGrafPort(); mpBlackTex->setAlpha(0xff); + + #if TARGET_PC + mpBlackTex->draw(mDoGph_gInf_c::getMinXF(), mDoGph_gInf_c::getMinYF(), + mDoGph_gInf_c::getWidthF(), mDoGph_gInf_c::getHeightF(), 0, 0, 0); + #else mpBlackTex->draw(0.0f, 0.0f, FB_WIDTH, FB_HEIGHT, 0, 0, 0); + #endif + mpScreen->draw(0.0f, 0.0f, grafPort); mpDrawCursor->draw(); field_0xfc = mpExpParent->getAlphaRate() * 150.0f; mpBlackTex->setAlpha(field_0xfc); + + #if TARGET_PC + mpBlackTex->draw(mDoGph_gInf_c::getMinXF(), mDoGph_gInf_c::getMinYF(), + mDoGph_gInf_c::getWidthF(), mDoGph_gInf_c::getHeightF(), 0, 0, 0); + #else mpBlackTex->draw(0.0f, 0.0f, FB_WIDTH, FB_HEIGHT, 0, 0, 0); + #endif + mpExpScreen->draw(0.0f, 0.0f, grafPort); mpSelect_c->setOffsetX(g_drawHIO.mInsectListScreen.mConfirmOptionPosX_4x3); mpSelect_c->translate(g_drawHIO.mInsectListScreen.mConfirmOptionPosX_4x3 + 486.0f, diff --git a/src/d/d_menu_save.cpp b/src/d/d_menu_save.cpp index ef1fb9d61e..7ecc41f823 100644 --- a/src/d/d_menu_save.cpp +++ b/src/d/d_menu_save.cpp @@ -384,7 +384,12 @@ void dMenu_save_c::screenSet() { mSelectedFile = dComIfGs_getDataNum(); mSelIcon = JKR_NEW dSelect_cursor_c(0, 1.0f, NULL); + + #if TARGET_PC + mSelIcon->setParam(0.96f * mDoGph_gInf_c::hudAspectScaleUp, 0.94f, 0.03f, 0.7f, 0.7f); + #else mSelIcon->setParam(0.96f, 0.94f, 0.03f, 0.7f, 0.7f); + #endif Vec pos; pos = mpSelData[mSelectedFile]->getGlobalVtxCenter(false, 0); @@ -2516,7 +2521,12 @@ void dMenu_save_c::yesnoCursorShow() { Vec pos = mpNoYes[mYesNoCursor]->getGlobalVtxCenter(false, 0); mSelIcon->setPos(pos.x, pos.y, mpNoYes[mYesNoCursor]->getPanePtr(), true); mSelIcon->setAlphaRate(1.0f); + + #if TARGET_PC + mSelIcon->setParam(0.96f * mDoGph_gInf_c::hudAspectScaleUp, 0.84f, 0.06f, 0.5f, 0.5f); + #else mSelIcon->setParam(0.96f, 0.84f, 0.06f, 0.5f, 0.5f); + #endif } } @@ -2664,7 +2674,12 @@ void dMenu_save_c::selFileCursorShow() { Vec pos = mpSelData[mSelectedFile]->getGlobalVtxCenter(false, 0); mSelIcon->setPos(pos.x, pos.y, mpSelData[mSelectedFile]->getPanePtr(), true); mSelIcon->setAlphaRate(1.0f); + + #if TARGET_PC + mSelIcon->setParam(0.96f * mDoGph_gInf_c::hudAspectScaleUp, 0.94f, 0.03f, 0.7f, 0.7f); + #else mSelIcon->setParam(0.96f, 0.94f, 0.03f, 0.7f, 0.7f); + #endif } void dMenu_save_c::yesnoWakuAlpahAnmInit(u8 yesnoIdx, u8 startAlpha, u8 endAlpha, u8 anmTimer) { @@ -2763,6 +2778,43 @@ void dMenu_save_c::_draw() { } } +#if TARGET_PC +void dMenu_save_c::menuSaveWide() { + mSaveSel.Scr->scale(mDoGph_gInf_c::hudAspectScaleUp, 1.0f); + mSaveSel.Scr->translate(mDoGph_gInf_c::getMinXF(), 0.0f); + + mSaveSel.Scr->search(MULTI_CHAR('t_for'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + mSaveSel.Scr->search(MULTI_CHAR('t_for1'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + + mSaveSel.Scr->search(MULTI_CHAR('w_btn_n'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + + mSaveSel.Scr->search(MULTI_CHAR('w_n_bk00'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + mSaveSel.Scr->search(MULTI_CHAR('w_n_bk01'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + mSaveSel.Scr->search(MULTI_CHAR('w_n_bk02'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + + mSaveSel.Scr->search(MULTI_CHAR('w_dat_i0'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + mSaveSel.Scr->search(MULTI_CHAR('w_dat_i1'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + mSaveSel.Scr->search(MULTI_CHAR('w_dat_i2'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + + mSaveSel.Scr->search(MULTI_CHAR('w_no_t'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + mSaveSel.Scr->search(MULTI_CHAR('f_no_t'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + mSaveSel.Scr->search(MULTI_CHAR('w_yes_t'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + mSaveSel.Scr->search(MULTI_CHAR('f_yes_t'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + + // Spirals + mSaveSel.Scr->search(MULTI_CHAR('w_uzu00'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + mSaveSel.Scr->search(MULTI_CHAR('w_uzu01'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + mSaveSel.Scr->search(MULTI_CHAR('w_uzu02'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + mSaveSel.Scr->search(MULTI_CHAR('w_uzu03'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + mSaveSel.Scr->search(MULTI_CHAR('w_uzu04'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + mSaveSel.Scr->search(MULTI_CHAR('w_uzu05'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + mSaveSel.Scr->search(MULTI_CHAR('w_uzu06'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + mSaveSel.Scr->search(MULTI_CHAR('w_uzu07'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + mSaveSel.Scr->search(MULTI_CHAR('w_uzu08'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + mSaveSel.Scr->search(MULTI_CHAR('w_uzu09'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); +} +#endif + void dMenu_save_c::_draw2() { if (field_0x21a1 == 0) { if (mpScrnExplain != NULL) { @@ -2770,6 +2822,10 @@ void dMenu_save_c::_draw2() { } if (mDisplayMenu) { + #if TARGET_PC + menuSaveWide(); + #endif + dComIfGd_set2DOpa(&mSaveSel); for (int i = 0; i < 3; i++) { diff --git a/src/d/d_menu_skill.cpp b/src/d/d_menu_skill.cpp index cc22e136c7..78ab82af94 100644 --- a/src/d/d_menu_skill.cpp +++ b/src/d/d_menu_skill.cpp @@ -143,12 +143,26 @@ void dMenu_Skill_c::_draw() { J2DGrafContext* context = dComIfGp_getCurrentGrafPort(); u8 alpha = mpBlackTex->mAlpha; mpBlackTex->setAlpha(0xff); + + #if TARGET_PC + mpBlackTex->draw(mDoGph_gInf_c::getMinXF(), mDoGph_gInf_c::getMinYF(), + mDoGph_gInf_c::getWidthF(), mDoGph_gInf_c::getHeightF(), 0, 0, 0); + #else mpBlackTex->draw(0.0f, 0.0f, FB_WIDTH, FB_HEIGHT, 0, 0, 0); + #endif + mpBlackTex->setAlpha(alpha); mpMenuScreen->draw(mPosX, 0.0f, context); mpDrawCursor->draw(); if (mProcess == 1 || mProcess == 2 || mProcess == 3) { + + #if TARGET_PC + mpBlackTex->draw(mDoGph_gInf_c::getMinXF(), mDoGph_gInf_c::getMinYF(), + mDoGph_gInf_c::getWidthF(), mDoGph_gInf_c::getHeightF(), 0, 0, 0); + #else mpBlackTex->draw(0.0f, 0.0f, FB_WIDTH, FB_HEIGHT, 0, 0, 0); + #endif + mpLetterScreen->draw(0.0f, 0.0f, context); if (mStringID != 0) { mpString->getString(mStringID, (J2DTextBox*)mpTextPane->getPanePtr(), NULL, NULL, diff --git a/src/d/d_name.cpp b/src/d/d_name.cpp index ee51e2e583..120bcff752 100644 --- a/src/d/d_name.cpp +++ b/src/d/d_name.cpp @@ -918,6 +918,10 @@ void dName_c::selectCursorMove() { g_nmHIO.mSelCharScale); ((J2DTextBox*)mMojiIcon[mCharRow + mCharColumn * 5]->getPanePtr()) ->setWhite(JUtility::TColor(0xC8, 0xC8, 0xC8, 0xFF)); + + #if TARGET_PC + nameWide(); + #endif Vec pos = mMojiIcon[mCharRow + mCharColumn * 5]->getGlobalVtxCenter(false, 0); mSelIcon->setPos(pos.x, pos.y, mMojiIcon[mCharRow + mCharColumn * 5]->getPanePtr(), true); @@ -1281,7 +1285,58 @@ void dName_c::selectCursorPosSet(int row) { } } +#if TARGET_PC +void dName_c::nameWide() { + //Resize Select Icon + mSelIcon->setParam(0.82f * mDoGph_gInf_c::hudAspectScaleUp, 0.77f, 0.05f, 0.4f, 0.4f); + + // List of Characters Box + static u64 l_tagName[65] = { + MULTI_CHAR('m_00_0'), MULTI_CHAR('m_00_1'), MULTI_CHAR('m_00_2'), MULTI_CHAR('m_00_3'), MULTI_CHAR('m_00_4'), MULTI_CHAR('m_01_0'), MULTI_CHAR('m_01_1'), MULTI_CHAR('m_01_2'), MULTI_CHAR('m_01_3'), + MULTI_CHAR('m_01_4'), MULTI_CHAR('m_02_0'), MULTI_CHAR('m_02_1'), MULTI_CHAR('m_02_2'), MULTI_CHAR('m_02_3'), MULTI_CHAR('m_02_4'), MULTI_CHAR('m03_0'), MULTI_CHAR('m03_1'), MULTI_CHAR('m03_2'), + MULTI_CHAR('m03_3'), MULTI_CHAR('m03_4'), MULTI_CHAR('m_04_0'), MULTI_CHAR('m_04_1'), MULTI_CHAR('m_04_2'), MULTI_CHAR('m_04_3'), MULTI_CHAR('m_04_4'), MULTI_CHAR('m_05_0'), MULTI_CHAR('m_05_1'), + MULTI_CHAR('m_05_2'), MULTI_CHAR('m_05_3'), MULTI_CHAR('m_05_4'), MULTI_CHAR('m_06_0'), MULTI_CHAR('m_06_1'), MULTI_CHAR('m_06_2'), MULTI_CHAR('m_06_3'), MULTI_CHAR('m_06_4'), MULTI_CHAR('m_07_0'), + MULTI_CHAR('m_07_1'), MULTI_CHAR('m_07_2'), MULTI_CHAR('m_07_3'), MULTI_CHAR('m_07_4'), MULTI_CHAR('m_08_0'), MULTI_CHAR('m_08_1'), MULTI_CHAR('m_08_2'), MULTI_CHAR('m_08_3'), MULTI_CHAR('m_08_4'), + MULTI_CHAR('m_09_0'), MULTI_CHAR('m_09_1'), MULTI_CHAR('m_09_2'), MULTI_CHAR('m_09_3'), MULTI_CHAR('m_09_4'), MULTI_CHAR('m_10_0'), MULTI_CHAR('m_10_1'), MULTI_CHAR('m_10_2'), MULTI_CHAR('m_10_3'), + MULTI_CHAR('m_10_4'), MULTI_CHAR('m_11_0'), MULTI_CHAR('m_11_1'), MULTI_CHAR('m_11_2'), MULTI_CHAR('m_11_3'), MULTI_CHAR('m_11_4'), MULTI_CHAR('m12_0'), MULTI_CHAR('m12_1'), MULTI_CHAR('m12_2'), + MULTI_CHAR('m12_3'), MULTI_CHAR('m12_4'), + }; + + for (u32 i = 0; i < 65; i++) { + nameIn.NameInScr->search(l_tagName[i])->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + } + + // "END" Text + nameIn.NameInScr->search(MULTI_CHAR('p_end_2'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + nameIn.NameInScr->search(MULTI_CHAR('p_end_1'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + nameIn.NameInScr->search(MULTI_CHAR('p_end_0'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + + // Letters being typed + static u64 l_nameTagName[8] = { + MULTI_CHAR('name_00'), MULTI_CHAR('name_01'), MULTI_CHAR('name_02'), MULTI_CHAR('name_03'), MULTI_CHAR('name_04'), MULTI_CHAR('name_05'), MULTI_CHAR('name_06'), MULTI_CHAR('name_07'), + }; + + for (u32 i = 0; i < 8; i++) { + nameIn.NameInScr->search(l_nameTagName[i])->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + } + + // Underscores when typing below letters + static u64 l_nameCurTagName[8] = { + MULTI_CHAR('s__n_00'), MULTI_CHAR('s__n_01'), MULTI_CHAR('s__n_02'), MULTI_CHAR('s__n_03'), MULTI_CHAR('s__n_04'), MULTI_CHAR('s__n_05'), MULTI_CHAR('s__n_06'), MULTI_CHAR('s__n_07'), + }; + + for (u32 i = 0; i < 8; i++) { + nameIn.NameInScr->search(l_nameCurTagName[i]) + ->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + } +} +#endif + void dName_c::_draw() { + #if TARGET_PC + nameWide(); + #endif + dComIfGd_set2DOpa(&nameIn); dComIfGd_set2DOpa(mSelIcon); } @@ -1484,7 +1539,12 @@ void dName_c::screenSet() { mSelIcon = JKR_NEW dSelect_cursor_c(0, 1.0f, NULL); JUT_ASSERT(0, mSelIcon != NULL); + + #if TARGET_PC + mSelIcon->setParam(0.82f * mDoGph_gInf_c::hudAspectScaleUp, 0.77f, 0.05f, 0.4f, 0.4f); + #else mSelIcon->setParam(0.82f, 0.77f, 0.05f, 0.4f, 0.4f); + #endif Vec pos = mMojiIcon[mCharRow + mCharColumn * 5]->getGlobalVtxCenter(false, 0); mSelIcon->setPos(pos.x, pos.y, mMojiIcon[mCharRow + mCharColumn * 5]->getPanePtr(), true); From d2ce53d5d4b87147efc267dc5b56f1b9ef0096ce Mon Sep 17 00:00:00 2001 From: bdamja <95828582+bdamja@users.noreply.github.com> Date: Sun, 5 Apr 2026 12:31:28 -0600 Subject: [PATCH 260/350] button to restore default window size and position (#234) --- src/dusk/imgui/ImGuiMenuGame.cpp | 8 ++++++++ src/m_Do/m_Do_main.cpp | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/dusk/imgui/ImGuiMenuGame.cpp b/src/dusk/imgui/ImGuiMenuGame.cpp index cd1d5a06f3..46a1e58ec9 100644 --- a/src/dusk/imgui/ImGuiMenuGame.cpp +++ b/src/dusk/imgui/ImGuiMenuGame.cpp @@ -11,6 +11,7 @@ #include "dusk/hotkeys.h" #include "dusk/settings.h" #include "m_Do/m_Do_controller_pad.h" +#include "m_Do/m_Do_graphic.h" namespace dusk { ImGuiMenuGame::ImGuiMenuGame() {} @@ -29,6 +30,13 @@ namespace dusk { VISetWindowFullscreen(getSettings().video.enableFullscreen); } + if (ImGui::MenuItem("Default Window Size")) { + getSettings().video.enableFullscreen = false; + VISetWindowFullscreen(false); + VISetWindowSize(FB_WIDTH * 2, FB_HEIGHT * 2); + VICenterWindow(); + } + ImGui::EndMenu(); } diff --git a/src/m_Do/m_Do_main.cpp b/src/m_Do/m_Do_main.cpp index b8eeba892e..3a70e0a783 100644 --- a/src/m_Do/m_Do_main.cpp +++ b/src/m_Do/m_Do_main.cpp @@ -269,8 +269,8 @@ int game_main(int argc, char* argv[]) { config.appName = "Dusk"; config.windowPosX = -1; config.windowPosY = -1; - config.windowWidth = 608 * 2; - config.windowHeight = 448 * 2; + config.windowWidth = FB_WIDTH * 2; + config.windowHeight = FB_HEIGHT * 2; config.desiredBackend = ParseAuroraBackend(parsed_arg_options["backend"].as()); config.logCallback = &aurora_log_callback; config.logLevel = (AuroraLogLevel)parsed_arg_options["log-level"].as(); From ac47d37377554c2261b7742ebfd3792b394676f0 Mon Sep 17 00:00:00 2001 From: madeline Date: Sun, 5 Apr 2026 12:57:52 -0700 Subject: [PATCH 261/350] update aurora sm --- extern/aurora | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/aurora b/extern/aurora index 7eb8efcd17..50163edfe7 160000 --- a/extern/aurora +++ b/extern/aurora @@ -1 +1 @@ -Subproject commit 7eb8efcd172ceed27d120ffb6de86c63d1f3cad0 +Subproject commit 50163edfe76694f6c920bbd74febf1dd22a35618 From 066fcd0b2b3b53d8914b7c9f322136fd251b91aa Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 5 Apr 2026 23:09:51 +0200 Subject: [PATCH 262/350] Un-duplicate togglefullscreen code again --- src/dusk/imgui/ImGuiConsole.cpp | 4 +--- src/dusk/imgui/ImGuiMenuGame.cpp | 3 ++- src/dusk/imgui/ImGuiMenuGame.hpp | 2 ++ 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/dusk/imgui/ImGuiConsole.cpp b/src/dusk/imgui/ImGuiConsole.cpp index e1e8b83d31..95fb0d598f 100644 --- a/src/dusk/imgui/ImGuiConsole.cpp +++ b/src/dusk/imgui/ImGuiConsole.cpp @@ -193,9 +193,7 @@ namespace dusk { } if (ImGui::IsKeyPressed(ImGuiKey_F11)) { - settings::video::enableFullscreen.setValue(!settings::video::enableFullscreen); - VISetWindowFullscreen(settings::video::enableFullscreen); - config::Save(); + ImGuiMenuGame::ToggleFullscreen(); } if (CheckMenuViewToggle(ImGuiKey_F1, m_isHidden)) { diff --git a/src/dusk/imgui/ImGuiMenuGame.cpp b/src/dusk/imgui/ImGuiMenuGame.cpp index 992a111e78..ba872a3b86 100644 --- a/src/dusk/imgui/ImGuiMenuGame.cpp +++ b/src/dusk/imgui/ImGuiMenuGame.cpp @@ -14,9 +14,10 @@ #include "m_Do/m_Do_controller_pad.h" namespace dusk { - static void ToggleFullscreen() { + void ImGuiMenuGame::ToggleFullscreen() { settings::video::enableFullscreen.setValue(!settings::video::enableFullscreen); VISetWindowFullscreen(settings::video::enableFullscreen); + config::Save(); } ImGuiMenuGame::ImGuiMenuGame() {} diff --git a/src/dusk/imgui/ImGuiMenuGame.hpp b/src/dusk/imgui/ImGuiMenuGame.hpp index 6d7ed614aa..77e5952d1a 100644 --- a/src/dusk/imgui/ImGuiMenuGame.hpp +++ b/src/dusk/imgui/ImGuiMenuGame.hpp @@ -16,6 +16,8 @@ namespace dusk { void windowInputViewer(); void windowControllerConfig(); + static void ToggleFullscreen(); + private: struct { int m_selectedPort = 0; From cba63d177b5f13739b8c3fa31a012ccd636512de Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 5 Apr 2026 23:13:33 +0200 Subject: [PATCH 263/350] Explanation at top of config.hpp (config_var.hpp already had one) --- include/dusk/config.hpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/include/dusk/config.hpp b/include/dusk/config.hpp index 5cd0807b3b..258e012f2b 100644 --- a/include/dusk/config.hpp +++ b/include/dusk/config.hpp @@ -7,6 +7,14 @@ namespace dusk::config { +/* + * config.hpp is a heavier "full" header for the configuration system. + * For a basic overview and the basic types (sufficient for accessing settings), + * look at config_var.hpp. + * + * Avoid including this header in the entire game, it's heavier than I'd like! + */ + /** * \brief Base class containing virtual functions used for save/load of CVars. */ From f2be41355cbeb9c053f82a35a1b656a293c2af6e Mon Sep 17 00:00:00 2001 From: Max Roncace Date: Sun, 5 Apr 2026 17:34:18 -0400 Subject: [PATCH 264/350] Reapply settings changes on top of #209 --- include/dusk/settings.h | 100 +++++++------- include/m_Do/m_Do_controller_pad.h | 4 +- src/Z2AudioLib/Z2AudioMgr.cpp | 2 +- src/Z2AudioLib/Z2SoundObjMgr.cpp | 2 +- src/d/actor/d_a_alink.cpp | 6 +- src/d/actor/d_a_alink_cut.inc | 2 +- src/d/actor/d_a_alink_damage.inc | 4 +- src/d/actor/d_a_alink_demo.inc | 6 +- src/d/actor/d_a_alink_dusk.cpp | 2 +- src/d/actor/d_a_alink_hang.inc | 24 ++-- src/d/actor/d_a_alink_hvyboots.inc | 2 +- src/d/actor/d_a_e_ym.cpp | 6 +- src/d/actor/d_a_midna.cpp | 2 +- src/d/actor/d_a_obj_drop.cpp | 8 +- src/d/d_camera.cpp | 2 +- src/d/d_kankyo.cpp | 2 +- src/d/d_s_name.cpp | 4 +- src/d/d_save.cpp | 6 +- src/dusk/imgui/ImGuiConsole.cpp | 2 +- src/dusk/imgui/ImGuiMenuEnhancements.cpp | 40 +++--- src/dusk/imgui/ImGuiMenuGame.cpp | 12 +- src/dusk/imgui/ImGuiMenuTools.cpp | 2 +- src/dusk/io.cpp | 4 + src/dusk/settings.cpp | 166 ++++++++++++----------- src/m_Do/m_Do_graphic.cpp | 4 +- src/m_Do/m_Do_main.cpp | 2 +- 26 files changed, 216 insertions(+), 200 deletions(-) diff --git a/include/dusk/settings.h b/include/dusk/settings.h index 68850a28d0..cf4d79f6ac 100644 --- a/include/dusk/settings.h +++ b/include/dusk/settings.h @@ -1,68 +1,76 @@ #ifndef DUSK_CONFIG_H #define DUSK_CONFIG_H -#include "config_var.hpp" +#include "dusk/config_var.hpp" + +namespace dusk { -namespace dusk::settings { using namespace config; // Persistent user settings -namespace video { -extern ConfigVar enableFullscreen; -} +struct UserSettings { + // Program settings -namespace audio { -extern ConfigVar masterVolume; -extern ConfigVar mainMusicVolume; -extern ConfigVar subMusicVolume; -extern ConfigVar soundEffectsVolume; -extern ConfigVar fanfareVolume; -extern ConfigVar enableReverb; -} + struct { + // Video + ConfigVar enableFullscreen; + } video; -namespace game { -// QoL -extern ConfigVar enableQuickTransform; -extern ConfigVar hideTvSettingsScreen; -extern ConfigVar biggerWallets; -extern ConfigVar noReturnRupees; -extern ConfigVar disableRupeeCutscenes; -extern ConfigVar noSwordRecoil; -extern ConfigVar damageMultiplier; -extern ConfigVar instantDeath; -extern ConfigVar fastClimbing; -extern ConfigVar fastTears; -extern ConfigVar noMissClimbing; + struct { + // Audio + ConfigVar masterVolume; + ConfigVar mainMusicVolume; + ConfigVar subMusicVolume; + ConfigVar soundEffectsVolume; + ConfigVar fanfareVolume; + ConfigVar enableReverb; + } audio; -// Preferences -extern ConfigVar enableMirrorMode; -extern ConfigVar invertCameraXAxis; + // Game settings -// Graphics -extern ConfigVar enableBloom; -extern ConfigVar useWaterProjectionOffset; + struct { + // QoL + ConfigVar enableQuickTransform; + ConfigVar hideTvSettingsScreen; + ConfigVar biggerWallets; + ConfigVar noReturnRupees; + ConfigVar disableRupeeCutscenes; + ConfigVar noSwordRecoil; + ConfigVar damageMultiplier; + ConfigVar instantDeath; + ConfigVar fastClimbing; + ConfigVar noMissClimbing; + ConfigVar fastTears; -// Audio -extern ConfigVar noLowHpSound; -extern ConfigVar midnasLamentNonStop; + // Preferences + ConfigVar enableMirrorMode; + ConfigVar invertCameraXAxis; -// Cheats -extern ConfigVar enableFastIronBoots; -extern ConfigVar canTransformAnywhere; + // Graphics + ConfigVar enableBloom; + ConfigVar useWaterProjectionOffset; -// Technical -extern ConfigVar restoreWiiGlitches; + // Audio + ConfigVar noLowHpSound; + ConfigVar midnasLamentNonStop; -// Controls -extern ConfigVar enableTurboKeybind; -} + // Cheats + ConfigVar enableFastIronBoots; + ConfigVar canTransformAnywhere; -void Register(); + // Technical + ConfigVar restoreWiiGlitches; -} + // Controls + ConfigVar enableTurboKeybind; + } game; +}; + +UserSettings& getSettings(); + +void registerSettings(); -namespace dusk { // Transient settings struct CollisionViewSettings { diff --git a/include/m_Do/m_Do_controller_pad.h b/include/m_Do/m_Do_controller_pad.h index f76a38cac8..e187efd17d 100644 --- a/include/m_Do/m_Do_controller_pad.h +++ b/include/m_Do/m_Do_controller_pad.h @@ -56,7 +56,7 @@ public: static s16 getStickAngle3D(u32 pad) { #if TARGET_PC - if (dusk::settings::game::enableMirrorMode) { + if (dusk::getSettings().game.enableMirrorMode) { return -getCpadInfo(pad).mMainStickAngle; } else { return getCpadInfo(pad).mMainStickAngle; @@ -68,7 +68,7 @@ public: static f32 getSubStickX3D(u32 pad) { #if TARGET_PC - if (dusk::settings::game::enableMirrorMode) { + if (dusk::getSettings().game.enableMirrorMode) { return -getCpadInfo(pad).mCStickPosX; } else { return getCpadInfo(pad).mCStickPosX; diff --git a/src/Z2AudioLib/Z2AudioMgr.cpp b/src/Z2AudioLib/Z2AudioMgr.cpp index 8c05678222..d6d78fb4b7 100644 --- a/src/Z2AudioLib/Z2AudioMgr.cpp +++ b/src/Z2AudioLib/Z2AudioMgr.cpp @@ -157,7 +157,7 @@ void Z2AudioMgr::zeldaGFrameWork() { processBgmFramework(); #if TARGET_PC - if (!dusk::settings::game::noLowHpSound) { + if (!dusk::getSettings().game.noLowHpSound) { processHeartGaugeSound(); } #else diff --git a/src/Z2AudioLib/Z2SoundObjMgr.cpp b/src/Z2AudioLib/Z2SoundObjMgr.cpp index 463040d692..19099c7675 100644 --- a/src/Z2AudioLib/Z2SoundObjMgr.cpp +++ b/src/Z2AudioLib/Z2SoundObjMgr.cpp @@ -109,7 +109,7 @@ void Z2SoundObjMgr::searchEnemy() { #if TARGET_PC if (Z2GetSeqMgr()->checkBgmIDPlaying(Z2BGM_MIDNA_SOS) && - dusk::settings::game::midnasLamentNonStop) + dusk::getSettings().game.midnasLamentNonStop) { Z2GetSeqMgr()->changeSubBgmStatus(0); return; diff --git a/src/d/actor/d_a_alink.cpp b/src/d/actor/d_a_alink.cpp index 993f78037b..48269ddc56 100644 --- a/src/d/actor/d_a_alink.cpp +++ b/src/d/actor/d_a_alink.cpp @@ -7510,7 +7510,7 @@ void daAlink_c::setBlendMoveAnime(f32 i_morf) { BOOL sp24 = checkEventRun(); BOOL sp20 = checkBootsMoveAnime(1); #if TARGET_PC - if (dusk::settings::game::enableFastIronBoots) { + if (dusk::getSettings().game.enableFastIronBoots) { sp20 = FALSE; } #endif @@ -9475,7 +9475,7 @@ void daAlink_c::setStickData() { mHeavySpeedMultiplier = mpHIO->mItem.mIronBoots.m.mInputFactor; } #if TARGET_PC - if (dusk::settings::game::enableFastIronBoots) { + if (dusk::getSettings().game.enableFastIronBoots) { mHeavySpeedMultiplier = 1.0f; } #endif @@ -9487,7 +9487,7 @@ void daAlink_c::setStickData() { mHeavySpeedMultiplier = mpHIO->mItem.mIronBoots.m.mWaterInputFactor; } #if TARGET_PC - if (dusk::settings::game::enableFastIronBoots) { + if (dusk::getSettings().game.enableFastIronBoots) { mHeavySpeedMultiplier = 1.0f; } #endif diff --git a/src/d/actor/d_a_alink_cut.inc b/src/d/actor/d_a_alink_cut.inc index 5928f7e891..8e61a262cb 100644 --- a/src/d/actor/d_a_alink_cut.inc +++ b/src/d/actor/d_a_alink_cut.inc @@ -372,7 +372,7 @@ BOOL daAlink_c::changeCutReverseProc(daAlink_c::daAlink_ANM i_anmID) { } #if TARGET_PC - if (dusk::settings::game::noSwordRecoil) { + if (dusk::getSettings().game.noSwordRecoil) { return FALSE; } #endif diff --git a/src/d/actor/d_a_alink_damage.inc b/src/d/actor/d_a_alink_damage.inc index 9bbe04206b..420011cfd6 100644 --- a/src/d/actor/d_a_alink_damage.inc +++ b/src/d/actor/d_a_alink_damage.inc @@ -153,8 +153,8 @@ f32 daAlink_c::damageMagnification(BOOL i_checkZoraMag, int param_1) { } #if TARGET_PC - base_mag *= dusk::settings::game::damageMultiplier; - if (dusk::settings::game::instantDeath) { + base_mag *= dusk::getSettings().game.damageMultiplier; + if (dusk::getSettings().game.instantDeath) { base_mag = 9999.0f; } #endif diff --git a/src/d/actor/d_a_alink_demo.inc b/src/d/actor/d_a_alink_demo.inc index c1c8c53bd2..aa77b24129 100644 --- a/src/d/actor/d_a_alink_demo.inc +++ b/src/d/actor/d_a_alink_demo.inc @@ -2233,7 +2233,7 @@ void daAlink_c::setGetSubBgm(int i_itemNo) { BOOL daAlink_c::checkTreasureRupeeReturn(int i_itemNo) const { #if TARGET_PC - if (dusk::settings::game::noReturnRupees) { + if (dusk::getSettings().game.noReturnRupees) { return FALSE; } #endif @@ -4307,7 +4307,7 @@ bool daAlink_c::checkAcceptWarp() { */ if (mLinkAcch.ChkGroundHit() && !checkModeFlg(MODE_PLAYER_FLY) #if TARGET_PC - && (dusk::settings::game::restoreWiiGlitches || !checkNoResetFlg0(FLG0_WATER_IN_MOVE)) + && (dusk::getSettings().game.restoreWiiGlitches || !checkNoResetFlg0(FLG0_WATER_IN_MOVE)) #elif VERSION != VERSION_WII_USA_R0 && !checkNoResetFlg0(FLG0_WATER_IN_MOVE) #endif @@ -4318,7 +4318,7 @@ bool daAlink_c::checkAcceptWarp() { */ if ( #if TARGET_PC - (dusk::settings::game::restoreWiiGlitches || !getSlidePolygon(&plane)) && + (dusk::getSettings().game.restoreWiiGlitches || !getSlidePolygon(&plane)) && #elif VERSION != VERSION_WII_USA_R0 !getSlidePolygon(&plane) && #endif diff --git a/src/d/actor/d_a_alink_dusk.cpp b/src/d/actor/d_a_alink_dusk.cpp index fdd309adae..95218268ac 100644 --- a/src/d/actor/d_a_alink_dusk.cpp +++ b/src/d/actor/d_a_alink_dusk.cpp @@ -5,7 +5,7 @@ #include "d/d_meter2_info.h" void daAlink_c::handleQuickTransform() { - if (!dusk::settings::game::enableQuickTransform) { + if (!dusk::getSettings().game.enableQuickTransform) { return; } diff --git a/src/d/actor/d_a_alink_hang.inc b/src/d/actor/d_a_alink_hang.inc index 73218f4b87..89f11fc940 100644 --- a/src/d/actor/d_a_alink_hang.inc +++ b/src/d/actor/d_a_alink_hang.inc @@ -15,8 +15,8 @@ f32 daAlink_c::getHangMoveAnmSpeed() { #if TARGET_PC return getAnmSpeedStickRate( - dusk::settings::game::fastClimbing ? 2.0f : mpHIO->mWallHang.mWallMove.m.mMinAnmSpeed, - dusk::settings::game::fastClimbing ? 2.0f : mpHIO->mWallHang.mWallMove.m.mMaxAnmSpeed + dusk::getSettings().game.fastClimbing ? 2.0f : mpHIO->mWallHang.mWallMove.m.mMinAnmSpeed, + dusk::getSettings().game.fastClimbing ? 2.0f : mpHIO->mWallHang.mWallMove.m.mMaxAnmSpeed ); #else return getAnmSpeedStickRate(mpHIO->mWallHang.mWallMove.m.mMinAnmSpeed, @@ -286,7 +286,7 @@ int daAlink_c::procHangStartInit() { if (checkHangFootWall()) { #if TARGET_PC - if (!is_prev_hangReady && cM_rnd() < 0.7f && !dusk::settings::game::noMissClimbing) { + if (!is_prev_hangReady && cM_rnd() < 0.7f && !dusk::getSettings().game.noMissClimbing) { #else if (!is_prev_hangReady && cM_rnd() < 0.7f) { #endif @@ -1221,7 +1221,7 @@ void daAlink_c::setLadderPos(int param_0) { f32 daAlink_c::getLadderMoveAnmSpeed() { return getAnmSpeedStickRate(mpHIO->mLadder.m.mMoveMinASpeed, #if TARGET_PC - dusk::settings::game::fastClimbing ? 1.575f : mpHIO->mLadder.m.mMoveMaxSpeed + dusk::getSettings().game.fastClimbing ? 1.575f : mpHIO->mLadder.m.mMoveMaxSpeed #else mpHIO->mLadder.m.mMoveMaxSpeed #endif @@ -1322,7 +1322,7 @@ int daAlink_c::procLadderUpStartInit() { setSingleAnimeBaseSpeed(ANM_LADDER_UP_START, #if TARGET_PC - dusk::settings::game::fastClimbing ? 1.8f : mpHIO->mLadder.m.mClimbUpStartASpeed, + dusk::getSettings().game.fastClimbing ? 1.8f : mpHIO->mLadder.m.mClimbUpStartASpeed, #else mpHIO->mLadder.m.mClimbUpStartASpeed, #endif @@ -1378,7 +1378,7 @@ int daAlink_c::procLadderUpEndInit(BOOL param_0) { setSingleAnimeBaseSpeed(anm_id, #if TARGET_PC - dusk::settings::game::fastClimbing ? 1.765f : mpHIO->mLadder.m.mClimbUpEndASpeed, + dusk::getSettings().game.fastClimbing ? 1.765f : mpHIO->mLadder.m.mClimbUpEndASpeed, #else mpHIO->mLadder.m.mClimbUpEndASpeed, #endif @@ -1443,7 +1443,7 @@ int daAlink_c::procLadderDownStartInit() { setSingleAnimeBaseSpeed(ANM_LADDER_DOWN_START, #if TARGET_PC - dusk::settings::game::fastClimbing ? 1.8f : mpHIO->mLadder.m.mClimbDownStartASpeed, + dusk::getSettings().game.fastClimbing ? 1.8f : mpHIO->mLadder.m.mClimbDownStartASpeed, #else mpHIO->mLadder.m.mClimbDownStartASpeed, #endif @@ -1505,7 +1505,7 @@ int daAlink_c::procLadderDownEndInit(BOOL param_0) { setSingleAnimeBaseSpeed(anm_id, #if TARGET_PC - dusk::settings::game::fastClimbing ? 1.8f : mpHIO->mLadder.m.mClimbDownEndASpeed, + dusk::getSettings().game.fastClimbing ? 1.8f : mpHIO->mLadder.m.mClimbDownEndASpeed, #else mpHIO->mLadder.m.mClimbDownEndASpeed, #endif @@ -1643,7 +1643,7 @@ int daAlink_c::procLadderMove() { f32 daAlink_c::getClimbMoveUpDownAnmSpeed() { return getAnmSpeedStickRate(mpHIO->mLadder.m.mWallVerticalMinAnmSpeed, #if TARGET_PC - dusk::settings::game::fastClimbing ? 1.875f : mpHIO->mLadder.m.mWallVerticalMaxAnmSpeed + dusk::getSettings().game.fastClimbing ? 1.875f : mpHIO->mLadder.m.mWallVerticalMaxAnmSpeed #else mpHIO->mLadder.m.mWallVerticalMaxAnmSpeed #endif @@ -1653,7 +1653,7 @@ f32 daAlink_c::getClimbMoveUpDownAnmSpeed() { f32 daAlink_c::getClimbMoveSideAnmSpeed() { return getAnmSpeedStickRate(mpHIO->mLadder.m.mWallHorizontalMinAnmSpeed, #if TARGET_PC - dusk::settings::game::fastClimbing ? 2.0f : mpHIO->mLadder.m.mWallHorizontalMaxAnmSpeed + dusk::getSettings().game.fastClimbing ? 2.0f : mpHIO->mLadder.m.mWallHorizontalMaxAnmSpeed #else mpHIO->mLadder.m.mWallHorizontalMaxAnmSpeed #endif @@ -2089,7 +2089,7 @@ int daAlink_c::procClimbUpStartInit(int param_0) { mNormalSpeed = 0.0f; #if TARGET_PC - if (param_0 || var_r29 || cM_rnd() < 0.3f || dusk::settings::game::noMissClimbing) { + if (param_0 || var_r29 || cM_rnd() < 0.3f || dusk::getSettings().game.noMissClimbing) { #else if (param_0 || var_r29 || cM_rnd() < 0.3f) { #endif @@ -2170,7 +2170,7 @@ int daAlink_c::procClimbDownStartInit(s16 param_0) { deleteEquipItem(TRUE, FALSE); #if TARGET_PC - if (cM_rnd() < 0.7f || dusk::settings::game::noMissClimbing) { + if (cM_rnd() < 0.7f || dusk::getSettings().game.noMissClimbing) { #else if (cM_rnd() < 0.7f) { #endif diff --git a/src/d/actor/d_a_alink_hvyboots.inc b/src/d/actor/d_a_alink_hvyboots.inc index fd9848d85c..6f7fa8da40 100644 --- a/src/d/actor/d_a_alink_hvyboots.inc +++ b/src/d/actor/d_a_alink_hvyboots.inc @@ -349,7 +349,7 @@ int daAlink_c::procMagneBootsFly() { */ if (dComIfG_Bgsp().ChkPolySafe(mPolyInfo2) #if TARGET_PC - && (dusk::settings::game::restoreWiiGlitches || checkEquipHeavyBoots()) + && (dusk::getSettings().game.restoreWiiGlitches || checkEquipHeavyBoots()) #elif PLATFORM_GCN || VERSION == VERSION_WII_KOR && checkEquipHeavyBoots() #endif diff --git a/src/d/actor/d_a_e_ym.cpp b/src/d/actor/d_a_e_ym.cpp index 0b87d419e3..758bec6269 100644 --- a/src/d/actor/d_a_e_ym.cpp +++ b/src/d/actor/d_a_e_ym.cpp @@ -1069,7 +1069,7 @@ void daE_YM_c::executeDown() { if (mAcch.ChkGroundHit()) { if (mFlyType != 1) { #if TARGET_PC - bckSet(6, 0, 0.0f, dusk::settings::game::fastTears && dComIfGp_event_getMode() == 0 ? 2.0f : 1.0f); + bckSet(6, 0, 0.0f, dusk::getSettings().game.fastTears && dComIfGp_event_getMode() == 0 ? 2.0f : 1.0f); #else bckSet(6, 0, 0.0f, 1.0f); #endif @@ -1093,7 +1093,7 @@ void daE_YM_c::executeDown() { mSound.startCreatureSound(Z2SE_CM_BODYFALL_WATER_M, 0, -1); mSound.startCreatureSound(Z2SE_EN_YM_MOGAKU, 0, -1); #if TARGET_PC - bckSet(6, 0, 0.0f, dusk::settings::game::fastTears && dComIfGp_event_getMode() == 0 ? 2.0f : 1.0f); + bckSet(6, 0, 0.0f, dusk::getSettings().game.fastTears && dComIfGp_event_getMode() == 0 ? 2.0f : 1.0f); #else bckSet(6, 0, 0.0f, 1.0f); #endif @@ -1115,7 +1115,7 @@ void daE_YM_c::executeDown() { || dComIfG_Bgsp().GetGroundCode(gnd_chk) == 5) { #if TARGET_PC - bckSet(6, 0, 0.0f, dusk::settings::game::fastTears && dComIfGp_event_getMode() == 0 ? 2.0f : 1.0f); + bckSet(6, 0, 0.0f, dusk::getSettings().game.fastTears && dComIfGp_event_getMode() == 0 ? 2.0f : 1.0f); #else bckSet(6, 0, 0.0f, 1.0f); #endif diff --git a/src/d/actor/d_a_midna.cpp b/src/d/actor/d_a_midna.cpp index a50a3cb1f7..c63f4e5251 100644 --- a/src/d/actor/d_a_midna.cpp +++ b/src/d/actor/d_a_midna.cpp @@ -3051,7 +3051,7 @@ BOOL daMidna_c::checkMetamorphoseEnableBase() { !dComIfGs_isEventBit(0xD04) || #if TARGET_PC (fopAcIt_Judge((fopAcIt_JudgeFunc)daMidna_searchNpc, &tmp) && - !dusk::settings::game::canTransformAnywhere) + !dusk::getSettings().game.canTransformAnywhere) #else fopAcIt_Judge((fopAcIt_JudgeFunc)daMidna_searchNpc, &tmp) #endif diff --git a/src/d/actor/d_a_obj_drop.cpp b/src/d/actor/d_a_obj_drop.cpp index 1fb93a4270..275a3d6e12 100644 --- a/src/d/actor/d_a_obj_drop.cpp +++ b/src/d/actor/d_a_obj_drop.cpp @@ -265,8 +265,8 @@ int daObjDrop_c::modeParentWait() { mModeAction = 1; #if TARGET_PC - mModeTimer = dusk::settings::game::fastTears && dComIfGp_event_getMode() == 0 ? 20 : 40; - if (dusk::settings::game::fastTears && dComIfGp_event_getMode() == 0) { + mModeTimer = dusk::getSettings().game.fastTears && dComIfGp_event_getMode() == 0 ? 20 : 40; + if (dusk::getSettings().game.fastTears && dComIfGp_event_getMode() == 0) { current.pos.y += 100.0f; } else { current.pos.y += 300.0f; @@ -285,7 +285,7 @@ int daObjDrop_c::modeParentWait() { case 2: createBodyEffect(); #if TARGET_PC - mModeTimer = dusk::settings::game::fastTears && dComIfGp_event_getMode() == 0 ? 5 : 45; + mModeTimer = dusk::getSettings().game.fastTears && dComIfGp_event_getMode() == 0 ? 5 : 45; #else mModeTimer = 45; #endif @@ -331,7 +331,7 @@ int daObjDrop_c::modeWait() { case 2: case 50: #if TARGET_PC - if (dusk::settings::game::fastTears && dComIfGp_event_getMode() == 0) { + if (dusk::getSettings().game.fastTears && dComIfGp_event_getMode() == 0) { f32 player_dist = current.pos.abs(daPy_getPlayerActorClass()->current.pos); f32 home_dist = current.pos.abs(home.pos); diff --git a/src/d/d_camera.cpp b/src/d/d_camera.cpp index a946d85220..b2a5709736 100644 --- a/src/d/d_camera.cpp +++ b/src/d/d_camera.cpp @@ -764,7 +764,7 @@ void dCamera_c::updatePad() { var_f31 = mDoCPd_c::getSubStickX3D(mPadID); #if TARGET_PC - if (dusk::settings::game::invertCameraXAxis) { + if (dusk::getSettings().game.invertCameraXAxis) { var_f31 *= -1.0f; } #endif diff --git a/src/d/d_kankyo.cpp b/src/d/d_kankyo.cpp index 8b6d197624..ec35ee8bc7 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::settings::game::useWaterProjectionOffset ? -0.01f : 0.0f, 0.0f); + dusk::getSettings().game.useWaterProjectionOffset ? -0.01f : 0.0f, 0.0f); #else -0.01f, 0.0f); #endif diff --git a/src/d/d_s_name.cpp b/src/d/d_s_name.cpp index df512d8cb2..735ce4d0ca 100644 --- a/src/d/d_s_name.cpp +++ b/src/d/d_s_name.cpp @@ -301,7 +301,7 @@ void dScnName_c::FileSelectMain() { void dScnName_c::FileSelectMainNormal() { #if TARGET_PC - mShowTvSettingsScreen = !dusk::settings::game::hideTvSettingsScreen; + mShowTvSettingsScreen = !dusk::getSettings().game.hideTvSettingsScreen; #endif switch(dFs_c->isSelectEnd()) { @@ -369,7 +369,7 @@ void dScnName_c::doPreLoadSetup() { mProc = dScnName_PROC_ChangeGameScene; #if TARGET_PC - if (dusk::settings::game::disableRupeeCutscenes) { + if (dusk::getSettings().game.disableRupeeCutscenes) { return; } #endif diff --git a/src/d/d_save.cpp b/src/d/d_save.cpp index e9977bc534..3289a49c64 100644 --- a/src/d/d_save.cpp +++ b/src/d/d_save.cpp @@ -114,19 +114,19 @@ u16 dSv_player_status_a_c::getRupeeMax() const { switch (mWalletSize) { case WALLET: #if TARGET_PC - return dusk::settings::game::biggerWallets ? 500 : 300; + return dusk::getSettings().game.biggerWallets ? 500 : 300; #else return 300; #endif case BIG_WALLET: #if TARGET_PC - return dusk::settings::game::biggerWallets ? 1000 : 600; + return dusk::getSettings().game.biggerWallets ? 1000 : 600; #else return 600; #endif case GIANT_WALLET: #if TARGET_PC - return dusk::settings::game::biggerWallets ? 2000 : 1000; + return dusk::getSettings().game.biggerWallets ? 2000 : 1000; #else return 1000; #endif diff --git a/src/dusk/imgui/ImGuiConsole.cpp b/src/dusk/imgui/ImGuiConsole.cpp index 95fb0d598f..fe5d3e7c9a 100644 --- a/src/dusk/imgui/ImGuiConsole.cpp +++ b/src/dusk/imgui/ImGuiConsole.cpp @@ -184,7 +184,7 @@ namespace dusk { m_isLaunchInitialized = true; } - getTransientSettings().skipFrameRateLimit = settings::game::enableTurboKeybind && ImGui::IsKeyDown(ImGuiKey_Tab); + getTransientSettings().skipFrameRateLimit = getSettings().game.enableTurboKeybind && ImGui::IsKeyDown(ImGuiKey_Tab); if ((ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl)) && ImGui::IsKeyPressed(ImGuiKey_R)) diff --git a/src/dusk/imgui/ImGuiMenuEnhancements.cpp b/src/dusk/imgui/ImGuiMenuEnhancements.cpp index 537539cd02..3fbd55a8f2 100644 --- a/src/dusk/imgui/ImGuiMenuEnhancements.cpp +++ b/src/dusk/imgui/ImGuiMenuEnhancements.cpp @@ -10,45 +10,45 @@ namespace dusk { void ImGuiMenuEnhancements::draw() { if (ImGui::BeginMenu("Enhancements")) { if (ImGui::BeginMenu("Quality of Life")) { - config::ImGuiCheckbox("Quick Transform (R+Y)", settings::game::enableQuickTransform); + config::ImGuiCheckbox("Quick Transform (R+Y)", getSettings().game.enableQuickTransform); - config::ImGuiCheckbox("Bigger Wallets", settings::game::biggerWallets); + config::ImGuiCheckbox("Bigger Wallets", getSettings().game.biggerWallets); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Wallet sizes are like in the HD version (500, 1000, 2000)"); } - config::ImGuiCheckbox("No Rupee Returns", settings::game::noReturnRupees); + config::ImGuiCheckbox("No Rupee Returns", getSettings().game.noReturnRupees); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Always collect Rupees even if your Wallet is too full"); } - config::ImGuiCheckbox("Disable Rupee Cutscenes", settings::game::disableRupeeCutscenes); + config::ImGuiCheckbox("Disable Rupee Cutscenes", getSettings().game.disableRupeeCutscenes); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Rupees won't play cutscenes after you've collected them the first time"); } - config::ImGuiCheckbox("No Sword Recoil", settings::game::noSwordRecoil); + config::ImGuiCheckbox("No Sword Recoil", getSettings().game.noSwordRecoil); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Link won't recoil when his sword hits walls"); } - config::ImGuiCheckbox("Faster Climbing", settings::game::fastClimbing); + config::ImGuiCheckbox("Faster Climbing", getSettings().game.fastClimbing); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Quicker climbing on ladders and vines like the HD version"); } - config::ImGuiCheckbox("No Climbing Miss Animation", settings::game::noMissClimbing); + config::ImGuiCheckbox("No Climbing Miss Animation", getSettings().game.noMissClimbing); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Prevents Link from playing a struggle animation\n" "when using the Clawshot on vines at a weird angle"); } - config::ImGuiCheckbox("Faster Tears of Light", settings::game::fastTears); + config::ImGuiCheckbox("Faster Tears of Light", getSettings().game.fastTears); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Tears of Light dropped by Shadow Insects pop out faster like the HD version"); } - config::ImGuiCheckbox("Hide TV Settings Screen", settings::game::hideTvSettingsScreen); + config::ImGuiCheckbox("Hide TV Settings Screen", getSettings().game.hideTvSettingsScreen); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Hides the TV calibration screen shown when loading a save"); } @@ -57,20 +57,20 @@ namespace dusk { } if (ImGui::BeginMenu("Preferences")) { - config::ImGuiCheckbox("Mirror Mode", settings::game::enableMirrorMode); + config::ImGuiCheckbox("Mirror Mode", getSettings().game.enableMirrorMode); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Mirrors the world, matching the Wii version of the game"); } - config::ImGuiCheckbox("Invert Camera X Axis", settings::game::invertCameraXAxis); + config::ImGuiCheckbox("Invert Camera X Axis", getSettings().game.invertCameraXAxis); ImGui::EndMenu(); } if (ImGui::BeginMenu("Graphics")) { - config::ImGuiCheckbox("Native Bloom", settings::game::enableBloom); + config::ImGuiCheckbox("Native Bloom", getSettings().game.enableBloom); - config::ImGuiCheckbox("Water Projection Offset", settings::game::useWaterProjectionOffset); + config::ImGuiCheckbox("Water Projection Offset", getSettings().game.useWaterProjectionOffset); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Adds GC-specific -0.01 transS offset\n" "that causes ~6px ghost artifacts in water reflections"); @@ -80,12 +80,12 @@ namespace dusk { } if (ImGui::BeginMenu("Audio")) { - config::ImGuiCheckbox("No Low HP Sound", settings::game::noLowHpSound); + config::ImGuiCheckbox("No Low HP Sound", getSettings().game.noLowHpSound); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Disable the beeping sound when having low health"); } - config::ImGuiCheckbox("Non-Stop Midna's Lament", settings::game::midnasLamentNonStop); + config::ImGuiCheckbox("Non-Stop Midna's Lament", getSettings().game.midnasLamentNonStop); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Prevents enemy music while Midna's Lament is playing"); } @@ -94,9 +94,9 @@ namespace dusk { } if (ImGui::BeginMenu("Cheats")) { - config::ImGuiCheckbox("Fast Iron Boots", settings::game::enableFastIronBoots); + config::ImGuiCheckbox("Fast Iron Boots", getSettings().game.enableFastIronBoots); - config::ImGuiCheckbox("Can Transform Anywhere", settings::game::canTransformAnywhere); + config::ImGuiCheckbox("Can Transform Anywhere", getSettings().game.canTransformAnywhere); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Allows you to transform even if NPCs are looking"); } @@ -105,9 +105,9 @@ namespace dusk { } if (ImGui::BeginMenu("Difficulty")) { - config::ImGuiSliderInt("Damage Multiplier", settings::game::damageMultiplier, 1, 8, "x%d"); + config::ImGuiSliderInt("Damage Multiplier", getSettings().game.damageMultiplier, 1, 8, "x%d"); - config::ImGuiCheckbox("Instant Death", settings::game::instantDeath); + config::ImGuiCheckbox("Instant Death", getSettings().game.instantDeath); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Any hit will instantly kill you"); } @@ -116,7 +116,7 @@ namespace dusk { } if (ImGui::BeginMenu("Technical")) { - config::ImGuiCheckbox("Restore Wii 1.0 Glitches", settings::game::restoreWiiGlitches); + config::ImGuiCheckbox("Restore Wii 1.0 Glitches", getSettings().game.restoreWiiGlitches); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Restores patched glitches from Wii USA 1.0,\n" "the first released version"); diff --git a/src/dusk/imgui/ImGuiMenuGame.cpp b/src/dusk/imgui/ImGuiMenuGame.cpp index ba872a3b86..b2ed26b20a 100644 --- a/src/dusk/imgui/ImGuiMenuGame.cpp +++ b/src/dusk/imgui/ImGuiMenuGame.cpp @@ -15,8 +15,8 @@ namespace dusk { void ImGuiMenuGame::ToggleFullscreen() { - settings::video::enableFullscreen.setValue(!settings::video::enableFullscreen); - VISetWindowFullscreen(settings::video::enableFullscreen); + getSettings().video.enableFullscreen.setValue(!getSettings().video.enableFullscreen); + VISetWindowFullscreen(getSettings().video.enableFullscreen); config::Save(); } @@ -40,8 +40,8 @@ namespace dusk { if (ImGui::BeginMenu("Audio")) { ImGui::Text("Master Volume"); - config::ImGuiSliderInt("##masterVolume", settings::audio::masterVolume, 0, 100); - config::ImGuiCheckbox("Enable Reverb", settings::audio::enableReverb); + config::ImGuiSliderInt("##masterVolume", getSettings().audio.masterVolume, 0, 100); + config::ImGuiCheckbox("Enable Reverb", getSettings().audio.enableReverb); /* // TODO: Implement additional settings ImGui::Text("Main Music Volume"); @@ -61,8 +61,8 @@ namespace dusk { } */ - audio::SetMasterVolume(settings::audio::masterVolume / 100.0f); - audio::EnableReverb = settings::audio::enableReverb; + audio::SetMasterVolume(getSettings().audio.masterVolume / 100.0f); + audio::EnableReverb = getSettings().audio.enableReverb; ImGui::EndMenu(); } diff --git a/src/dusk/imgui/ImGuiMenuTools.cpp b/src/dusk/imgui/ImGuiMenuTools.cpp index 754f9a72e7..6a2fc2ede0 100644 --- a/src/dusk/imgui/ImGuiMenuTools.cpp +++ b/src/dusk/imgui/ImGuiMenuTools.cpp @@ -51,7 +51,7 @@ namespace dusk { ImGui::MenuItem("Audio Debug", hotkeys::SHOW_AUDIO_DEBUG, &m_showAudioDebug); ImGui::MenuItem("OSReport Force", nullptr, &OSReportReallyForceEnable); ImGui::Separator(); - config::ImGuiMenuItem("Enable Turbo Key", hotkeys::TURBO, settings::game::enableTurboKeybind); + config::ImGuiMenuItem("Enable Turbo Key", hotkeys::TURBO, getSettings().game.enableTurboKeybind); ImGui::EndMenu(); } diff --git a/src/dusk/io.cpp b/src/dusk/io.cpp index f930c2ceaa..50b5cab5bd 100644 --- a/src/dusk/io.cpp +++ b/src/dusk/io.cpp @@ -11,7 +11,11 @@ using namespace dusk::io; #define fseek(a, b, c) _fseeki64(a, b, c) #else #define MODE_TYPE char +#if _MSVC_VER #define MODE(val) ##val +#else +#define MODE(val) val +#endif #endif static FILE* ThrowIfNotOpen(const FileStream& file) { diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index 6bfc057a90..278f783dbf 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -1,95 +1,99 @@ #include "dusk/settings.h" #include "dusk/config.hpp" -namespace dusk::settings { -namespace video { -ConfigVar enableFullscreen("video.enableFullscreen", false); +namespace dusk { + +UserSettings g_userSettings = { + .video = { + .enableFullscreen {"video.enableFullscreen", false}, + }, + + .audio = { + .masterVolume {"audio.masterVolume", 80}, + .mainMusicVolume {"audio.mainMusicVolume", 100}, + .subMusicVolume {"audio.subMusicVolume", 100}, + .soundEffectsVolume {"audio.soundEffectsVolume", 100}, + .fanfareVolume {"audio.fanfareVolume", 100}, + .enableReverb {"audio.enableReverb", true}, + }, + + .game = { + // Quality of Life + .enableQuickTransform {"game.enableQuickTransform", false}, + .hideTvSettingsScreen {"game.hideTvSettingsScreen", false}, + .biggerWallets {"game.biggerWallets", false}, + .noReturnRupees {"game.noReturnRupees", false}, + .disableRupeeCutscenes {"game.disableRupeeCutscenes", false}, + .noSwordRecoil {"game.noSwordRecoil", false}, + .damageMultiplier {"game.damageMultiplier", 1}, + .instantDeath {"game.instantDeath", false}, + .fastClimbing {"game.fastClimbing", false}, + .noMissClimbing {"game.noMissClimbing", false}, + .fastTears {"game.fastTears", false}, + + // Preferences + .enableMirrorMode {"game.enableMirrorMode", false}, + .invertCameraXAxis {"game.invertCameraXAxis", false}, + + // Graphics + .enableBloom {"game.enableBloom", true}, + .useWaterProjectionOffset {"game.useWaterProjectionOffset", false}, + + // Audio + .noLowHpSound {"game.noLowHpSound", false}, + .midnasLamentNonStop {"game.midnasLamentNonStop", false}, + + // Cheats + .enableFastIronBoots {"game.enableFastIronBoots", false}, + .canTransformAnywhere {"game.canTransformAnywhere", false}, + + // Technical + .restoreWiiGlitches {"game.restoreWiiGlitches", false}, + + // Controls + .enableTurboKeybind {"game.enableTurboKeybind", true}, + }, +}; + +UserSettings& getSettings() { + return g_userSettings; } -namespace audio { -ConfigVar masterVolume("audio.masterVolume", 80); -ConfigVar mainMusicVolume("audio.mainMusicVolume", 100); -ConfigVar subMusicVolume("audio.subMusicVolume", 100); -ConfigVar soundEffectsVolume("audio.soundEffectsVolume", 100); -ConfigVar fanfareVolume("audio.fanfareVolume", 100); -ConfigVar enableReverb("audio.enableReverb", true); -} - -namespace game { -// Quality of Life -ConfigVar enableQuickTransform("game.enableQuickTransform", false); -ConfigVar hideTvSettingsScreen("game.hideTvSettingsScreen", false); -ConfigVar biggerWallets("game.biggerWallets", false); -ConfigVar noReturnRupees("game.noReturnRupees", false); -ConfigVar disableRupeeCutscenes("game.disableRupeeCutscenes", false); -ConfigVar noSwordRecoil("game.noSwordRecoil", false); -ConfigVar damageMultiplier("game.damageMultiplier", 1); -ConfigVar instantDeath("game.instantDeath", false); -ConfigVar fastClimbing("game.fastClimbing", false); -ConfigVar fastTears("game.fastTears", false); -ConfigVar noMissClimbing("game.noMissClimbing", false); - -// Preferences -ConfigVar enableMirrorMode("game.enableMirrorMode", false); -ConfigVar invertCameraXAxis("game.invertCameraXAxis", false); - -// Graphics -ConfigVar enableBloom("game.enableBloom", true); -ConfigVar useWaterProjectionOffset("game.useWaterProjectionOffset", false); - -// Audio -ConfigVar noLowHpSound("game.noLowHpSound", false); -ConfigVar midnasLamentNonStop("game.midnasLamentNonStop", false); - -// Cheats -ConfigVar enableFastIronBoots("game.enableFastIronBoots", false); -ConfigVar canTransformAnywhere("game.canTransformAnywhere", false); - -// Technical -ConfigVar restoreWiiGlitches("game.restoreWiiGlitches", false); - -// Controls -ConfigVar enableTurboKeybind("game.enableTurboKeybind", true); -} - -void Register() { +void registerSettings() { // Video - Register(video::enableFullscreen); + Register(g_userSettings.video.enableFullscreen); // Audio - Register(audio::masterVolume); - Register(audio::mainMusicVolume); - Register(audio::subMusicVolume); - Register(audio::soundEffectsVolume); - Register(audio::fanfareVolume); - Register(audio::enableReverb); + Register(g_userSettings.audio.masterVolume); + Register(g_userSettings.audio.mainMusicVolume); + Register(g_userSettings.audio.subMusicVolume); + Register(g_userSettings.audio.soundEffectsVolume); + Register(g_userSettings.audio.fanfareVolume); + Register(g_userSettings.audio.enableReverb); // Game - Register(game::enableQuickTransform); - Register(game::hideTvSettingsScreen); - Register(game::biggerWallets); - Register(game::noReturnRupees); - Register(game::disableRupeeCutscenes); - Register(game::noSwordRecoil); - Register(game::damageMultiplier); - Register(game::instantDeath); - Register(game::fastClimbing); - Register(game::fastTears); - Register(game::enableMirrorMode); - Register(game::invertCameraXAxis); - Register(game::enableBloom); - Register(game::useWaterProjectionOffset); - Register(game::enableFastIronBoots); - Register(game::canTransformAnywhere); - Register(game::restoreWiiGlitches); - Register(game::noMissClimbing); - Register(game::noLowHpSound); - Register(game::midnasLamentNonStop); - Register(game::enableTurboKeybind); + Register(g_userSettings.game.enableQuickTransform); + Register(g_userSettings.game.hideTvSettingsScreen); + Register(g_userSettings.game.biggerWallets); + Register(g_userSettings.game.noReturnRupees); + Register(g_userSettings.game.disableRupeeCutscenes); + Register(g_userSettings.game.noSwordRecoil); + Register(g_userSettings.game.damageMultiplier); + Register(g_userSettings.game.instantDeath); + Register(g_userSettings.game.fastClimbing); + Register(g_userSettings.game.fastTears); + Register(g_userSettings.game.enableMirrorMode); + Register(g_userSettings.game.invertCameraXAxis); + Register(g_userSettings.game.enableBloom); + Register(g_userSettings.game.useWaterProjectionOffset); + Register(g_userSettings.game.enableFastIronBoots); + Register(g_userSettings.game.canTransformAnywhere); + Register(g_userSettings.game.restoreWiiGlitches); + Register(g_userSettings.game.noMissClimbing); + Register(g_userSettings.game.noLowHpSound); + Register(g_userSettings.game.midnasLamentNonStop); + Register(g_userSettings.game.enableTurboKeybind); } -} - -namespace dusk { // Transient settings diff --git a/src/m_Do/m_Do_graphic.cpp b/src/m_Do/m_Do_graphic.cpp index 7ee50fc0ea..a7a09a8d87 100644 --- a/src/m_Do/m_Do_graphic.cpp +++ b/src/m_Do/m_Do_graphic.cpp @@ -1163,7 +1163,7 @@ void mDoGph_gInf_c::bloom_c::remove() { void mDoGph_gInf_c::bloom_c::draw() { #if TARGET_PC - if (!dusk::settings::game::enableBloom) { + if (!dusk::getSettings().game.enableBloom) { return; } #endif @@ -2117,7 +2117,7 @@ int mDoGph_Painter() { #endif #if TARGET_PC - if (dusk::settings::game::enableMirrorMode) + if (dusk::getSettings().game.enableMirrorMode) #elif PLATFORM_WII if (data_8053a730) #endif diff --git a/src/m_Do/m_Do_main.cpp b/src/m_Do/m_Do_main.cpp index ffe6bb5ab6..632d696ae9 100644 --- a/src/m_Do/m_Do_main.cpp +++ b/src/m_Do/m_Do_main.cpp @@ -279,7 +279,7 @@ static const char* CalculateConfigPath() { // PC ENTRY POINT // ========================================================================= int game_main(int argc, char* argv[]) { - dusk::settings::Register(); + dusk::registerSettings(); dusk::config::FinishRegistration(); cxxopts::ParseResult parsed_arg_options; From ff4f2f9b551be2d3c3fa5c696776d4f4bcc647af Mon Sep 17 00:00:00 2001 From: madeline Date: Sun, 5 Apr 2026 15:05:38 -0700 Subject: [PATCH 265/350] fix cloud, sun, moon, rain, fog, lens flare, etc, culling --- src/d/d_kankyo_rain.cpp | 57 +++++++++++++++++++++++++++++------------ 1 file changed, 41 insertions(+), 16 deletions(-) diff --git a/src/d/d_kankyo_rain.cpp b/src/d/d_kankyo_rain.cpp index 019f30f101..c1927c5875 100644 --- a/src/d/d_kankyo_rain.cpp +++ b/src/d/d_kankyo_rain.cpp @@ -113,7 +113,12 @@ void dKyr_lenzflare_move() { cXyz vect; cXyz proj; cXyz center; + + #if TARGET_PC + mDoLib_project(lenz_packet->mPositions, &proj, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(lenz_packet->mPositions, &proj); + #endif center.x = FB_WIDTH / 2; center.y = FB_HEIGHT / 2; @@ -213,7 +218,12 @@ void dKyr_sun_move() { } cXyz proj; + + #if TARGET_PC + mDoLib_project(sun_packet->mPos, &proj, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(sun_packet->mPos, &proj); + #endif for (int i = 0; i < 5; i++) { cXyz chkpnt = proj; @@ -4111,7 +4121,11 @@ void dKyr_drawStar(Mtx drawMtx, u8** tex) { } } + #if TARGET_PC + mDoLib_project(&moon_pos, &moon_proj, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(&moon_pos, &moon_proj); + #endif GXSetNumChans(1); GXSetChanCtrl(GX_COLOR0, GX_DISABLE, GX_SRC_REG, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_CLAMP, GX_AF_NONE); @@ -4248,7 +4262,11 @@ void dKyr_drawStar(Mtx drawMtx, u8** tex) { sp68.y = spBC.y + star_pos.y; sp68.z = spBC.z + star_pos.z; + #if TARGET_PC + mDoLib_project(&sp68, &star_proj, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(&sp68, &star_proj); + #endif moon_proj.z = 0.0f; star_proj.z = 0.0f; @@ -4630,7 +4648,11 @@ void drawVrkumo(Mtx drawMtx, GXColor& color, u8** tex) { } if (g_env_light.daytime > 105.0f && g_env_light.daytime < 240.0f && !dComIfGp_event_runCheck() && sun_packet != NULL && sun_packet->mSunAlpha > 0.0f) { + #if TARGET_PC + mDoLib_project(&sun_packet->mPos[0], &proj, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(&sun_packet->mPos[0], &proj); + #endif if (proj.x > 0.0f && proj.x < FB_WIDTH && proj.y > spC4 && proj.y < (458.0f - spC4)) { pass = 0; } @@ -4895,7 +4917,12 @@ void drawVrkumo(Mtx drawMtx, GXColor& color, u8** tex) { x = 100.0f; y = 100.0f; z = 100.0f; + + #if TARGET_PC + mDoLib_project(&spF0, &proj, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(&spF0, &proj); + #endif if (proj.x > -x && proj.x < (FB_WIDTH + x) && proj.y > -y && proj.y < (458.0f + z)) { break; @@ -4945,7 +4972,12 @@ void drawVrkumo(Mtx drawMtx, GXColor& color, u8** tex) { x = 100.0f; y = 100.0f; z = 100.0f; + + #if TARGET_PC + mDoLib_project(&spE4, &proj, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(&spE4, &proj); + #endif if (proj.x > -x && proj.x < (FB_WIDTH + x) && proj.y > -y && proj.y < (458.0f + z)) { break; @@ -5986,21 +6018,18 @@ static void dKyr_evil_draw2(Mtx drawMtx, u8** tex) { sp34.y = 80.0f; sp34.z = 80.0f; + #if TARGET_PC + mDoLib_project(&sp7C, &proj, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(&sp7C, &proj); + #endif -#if TARGET_PC - if (!(proj.x > -sp34.x) || !(proj.x < (dComIfGd_getViewport()->width + sp34.x)) || - !(proj.y > -sp34.y) || !(proj.y < (dComIfGd_getViewport()->height + sp34.z))) - { - continue; - } -#else if (!(proj.x > -sp34.x) || !(proj.x < (FB_WIDTH + sp34.x)) || !(proj.y > -sp34.y) || !(proj.y < (458.0f + sp34.z))) { continue; } -#endif + } f32 sp40; @@ -6219,21 +6248,17 @@ void dKyr_evil_draw(Mtx drawMtx, u8** tex) { sp44.y = 80.0f; sp44.z = 80.0f; + #if TARGET_PC + mDoLib_project(&spA4, &proj, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(&spA4, &proj); + #endif -#if TARGET_PC - if (!(proj.x > -sp44.x) || !(proj.x < (dComIfGd_getViewport()->width + sp44.x)) || - !(proj.y > -sp44.y) || !(proj.y < (dComIfGd_getViewport()->height + sp44.z))) - { - continue; - } -#else if (!(proj.x > -sp44.x) || !(proj.x < (FB_WIDTH + sp44.x)) || !(proj.y > -sp44.y) || !(proj.y < (458.0f + sp44.z))) { continue; } -#endif } f32 sp5C; From c3ffc004b9a46b540356e2a20f03b643dca1fdc0 Mon Sep 17 00:00:00 2001 From: madeline Date: Sun, 5 Apr 2026 16:31:57 -0700 Subject: [PATCH 266/350] fix every viewport projection issue fixes #123 fixes #115 --- src/d/actor/d_a_alink_kandelaar.inc | 5 +++++ src/d/actor/d_a_demo00.cpp | 5 +++++ src/d/actor/d_a_e_fk.cpp | 6 ++++++ src/d/actor/d_a_e_fs.cpp | 6 ++++++ src/d/actor/d_a_e_sm.cpp | 6 ++++++ src/d/actor/d_a_npc.cpp | 11 +++++------ src/d/actor/d_a_obj_ari.cpp | 6 ++++++ src/d/actor/d_a_obj_bhashi.cpp | 6 ++++++ src/d/actor/d_a_obj_cho.cpp | 6 ++++++ src/d/actor/d_a_obj_crvfence.cpp | 6 ++++++ src/d/actor/d_a_obj_crvhahen.cpp | 5 +++++ src/d/actor/d_a_obj_dan.cpp | 6 ++++++ src/d/actor/d_a_obj_gomikabe.cpp | 6 ++++++ src/d/actor/d_a_obj_hhashi.cpp | 6 ++++++ src/d/actor/d_a_obj_kamakiri.cpp | 6 ++++++ src/d/actor/d_a_obj_katatsumuri.cpp | 5 +++++ src/d/actor/d_a_obj_kuwagata.cpp | 5 +++++ src/d/actor/d_a_obj_ten.cpp | 6 ++++++ src/d/actor/d_a_obj_tombo.cpp | 6 ++++++ src/d/actor/d_a_obj_zra_freeze.cpp | 5 +++++ src/d/d_insect.cpp | 4 ++++ src/d/d_kankyo.cpp | 4 ++++ src/d/d_kankyo_debug.cpp | 5 +++++ src/d/d_msg_object.cpp | 12 ++++++++++++ src/d/d_msg_scrn_item.cpp | 11 +++++++++++ src/d/d_msg_scrn_talk.cpp | 13 ++++++++++++- 26 files changed, 161 insertions(+), 7 deletions(-) diff --git a/src/d/actor/d_a_alink_kandelaar.inc b/src/d/actor/d_a_alink_kandelaar.inc index 339ff90ce6..88c2152e03 100644 --- a/src/d/actor/d_a_alink_kandelaar.inc +++ b/src/d/actor/d_a_alink_kandelaar.inc @@ -180,7 +180,12 @@ void daAlink_c::preKandelaarDraw() { mat_p->setTevColor(2, &color); cXyz proj; + + #if TARGET_PC + mDoLib_project(&mKandelaarFlamePos, &proj, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(&mKandelaarFlamePos, &proj); + #endif camera_process_class* camera_p = dComIfGp_getCamera(0); f32 trimHeight; diff --git a/src/d/actor/d_a_demo00.cpp b/src/d/actor/d_a_demo00.cpp index 99ad6c1587..871e9ebca6 100644 --- a/src/d/actor/d_a_demo00.cpp +++ b/src/d/actor/d_a_demo00.cpp @@ -1658,7 +1658,12 @@ int daDemo00_c::draw() { MTXCopy(mModel.field_0x5d4->getAnmMtx(0), mDoMtx_stack_c::get()); spb0.set(0.0f, 0.0f, 0.0f); mDoMtx_stack_c::multVec(&spb0, &sp98); + + #if TARGET_PC + mDoLib_project(&sp98, &spa4, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(&sp98, &spa4); + #endif if (spa4.x >= -700.0f && spa4.x < 1600.0f && spa4.y >= -200.0f && spa4.y < 600.0f) { if (mModel.mID.field_0x18 == 0 || mModel.mID.field_0x18 == 1) { diff --git a/src/d/actor/d_a_e_fk.cpp b/src/d/actor/d_a_e_fk.cpp index 5840df0495..87734da262 100644 --- a/src/d/actor/d_a_e_fk.cpp +++ b/src/d/actor/d_a_e_fk.cpp @@ -429,7 +429,13 @@ void daE_FK_c::DamageAction() { bool daE_FK_c::checkViewArea() { Vec proj; + + #if TARGET_PC + mDoLib_project(¤t.pos, &proj, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(¤t.pos, &proj); + #endif + return (proj.x >= 0.0f && proj.x <= FB_WIDTH) && (proj.y >= 0.0f && proj.y <= FB_HEIGHT); } diff --git a/src/d/actor/d_a_e_fs.cpp b/src/d/actor/d_a_e_fs.cpp index 3ed859c686..54dd10ad81 100644 --- a/src/d/actor/d_a_e_fs.cpp +++ b/src/d/actor/d_a_e_fs.cpp @@ -463,7 +463,13 @@ static void damage_check(e_fs_class* i_this) { static bool checkViewArea(cXyz* i_pos) { Vec proj; + + #if TARGET_PC + mDoLib_project(i_pos, &proj, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(i_pos, &proj); + #endif + bool ret = false; if (proj.x >= 0.0f && proj.x <= FB_WIDTH && proj.y >= 0.0f && proj.y <= FB_HEIGHT) { ret = true; diff --git a/src/d/actor/d_a_e_sm.cpp b/src/d/actor/d_a_e_sm.cpp index b85e22e640..8894399cec 100644 --- a/src/d/actor/d_a_e_sm.cpp +++ b/src/d/actor/d_a_e_sm.cpp @@ -1362,7 +1362,13 @@ void daE_SM_c::E_SM_C_Hook() { bool daE_SM_c::CheckViewArea() { Vec vec; + + #if TARGET_PC + mDoLib_project(¤t.pos, &vec, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(¤t.pos, &vec); + #endif + bool rv = false; if (vec.x >= 0.0f && vec.x <= FB_WIDTH && vec.y >= 0.0f && vec.y <= FB_HEIGHT) { diff --git a/src/d/actor/d_a_npc.cpp b/src/d/actor/d_a_npc.cpp index b903ce8c8f..6ec2fbe7a9 100644 --- a/src/d/actor/d_a_npc.cpp +++ b/src/d/actor/d_a_npc.cpp @@ -2694,16 +2694,15 @@ BOOL daNpcT_chkActorInScreen(fopAc_ac_c* i_ActorP, f32 param_1, f32 param_2, f32 } for (int i = 0; i < 8; i++) { + #if TARGET_PC + mDoLib_project(&pos_array[i], &proj, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(&pos_array[i], &proj); -#if TARGET_PC - if (0.0f < proj.x && proj.x < mDoGph_gInf_c::getWidth() && 0.0f < proj.y && proj.y < mDoGph_gInf_c::getHeight()) { - continue; - } -#else + #endif + if (0.0f < proj.x && proj.x < FB_WIDTH && 0.0f < proj.y && proj.y < FB_HEIGHT) { continue; } -#endif return false; } diff --git a/src/d/actor/d_a_obj_ari.cpp b/src/d/actor/d_a_obj_ari.cpp index 267b0003af..3e2727e2f2 100644 --- a/src/d/actor/d_a_obj_ari.cpp +++ b/src/d/actor/d_a_obj_ari.cpp @@ -499,7 +499,13 @@ void daObjARI_c::Z_BufferChk() { cXyz vec2, vec1; vec1 = current.pos; vec1.y += 20.0f; + + #if TARGET_PC + mDoLib_project(&vec1, &vec2, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(&vec1, &vec2); + #endif + f32 trim_height; camera_process_class* camera = dComIfGp_getCamera(0); if (camera != NULL) { diff --git a/src/d/actor/d_a_obj_bhashi.cpp b/src/d/actor/d_a_obj_bhashi.cpp index 00a35bc158..33595c7a77 100644 --- a/src/d/actor/d_a_obj_bhashi.cpp +++ b/src/d/actor/d_a_obj_bhashi.cpp @@ -285,7 +285,13 @@ bool Hahen_c::CheckCull() { bool Hahen_c::checkViewArea() { Vec proj; + + #if TARGET_PC + mDoLib_project(&pos, &proj, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(&pos, &proj); + #endif + return (proj.x >= -50.0f && proj.x <= 658.0f) && (proj.y >= -50.0f && proj.y <= 498.0f); } diff --git a/src/d/actor/d_a_obj_cho.cpp b/src/d/actor/d_a_obj_cho.cpp index 8fbc2dbde8..27b0b8ce0e 100644 --- a/src/d/actor/d_a_obj_cho.cpp +++ b/src/d/actor/d_a_obj_cho.cpp @@ -289,7 +289,13 @@ void daObjCHO_c::Z_BufferChk() { cXyz vec2, vec1; vec1 = current.pos; vec1.y += 20.0f; + + #if TARGET_PC + mDoLib_project(&vec1, &vec2, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(&vec1, &vec2); + #endif + f32 trim_height; camera_process_class* camera = dComIfGp_getCamera(0); if (camera != NULL) { diff --git a/src/d/actor/d_a_obj_crvfence.cpp b/src/d/actor/d_a_obj_crvfence.cpp index 9712205ea3..03ff2b1d6a 100644 --- a/src/d/actor/d_a_obj_crvfence.cpp +++ b/src/d/actor/d_a_obj_crvfence.cpp @@ -224,7 +224,13 @@ void daObjCRVFENCE_c::NormalAction() { bool daObjCRVFENCE_c::checkViewArea(cXyz* param_1) { Vec sp24; + + #if TARGET_PC + mDoLib_project(param_1, &sp24, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(param_1, &sp24); + #endif + bool rv = false; bool bVar1 = false; diff --git a/src/d/actor/d_a_obj_crvhahen.cpp b/src/d/actor/d_a_obj_crvhahen.cpp index 52d180f547..0b44a57ec8 100644 --- a/src/d/actor/d_a_obj_crvhahen.cpp +++ b/src/d/actor/d_a_obj_crvhahen.cpp @@ -137,7 +137,12 @@ void daObjCRVHAHEN_c::CheckCull() { bool daObjCRVHAHEN_c::checkViewArea(cXyz* i_this) { Vec proj; + + #if TARGET_PC + mDoLib_project(i_this, &proj, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(i_this, &proj); + #endif bool ret = false; diff --git a/src/d/actor/d_a_obj_dan.cpp b/src/d/actor/d_a_obj_dan.cpp index cc176b9a20..53dbf4e1e4 100644 --- a/src/d/actor/d_a_obj_dan.cpp +++ b/src/d/actor/d_a_obj_dan.cpp @@ -267,7 +267,13 @@ void daObjDAN_c::Z_BufferChk() { cXyz vec2, vec1; vec1 = current.pos; vec1.y += 20.0f; + + #if TARGET_PC + mDoLib_project(&vec1, &vec2, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(&vec1, &vec2); + #endif + f32 trim_height; camera_process_class* camera = dComIfGp_getCamera(0); if (camera != NULL) { diff --git a/src/d/actor/d_a_obj_gomikabe.cpp b/src/d/actor/d_a_obj_gomikabe.cpp index 66963f0efd..a5b58a44a5 100644 --- a/src/d/actor/d_a_obj_gomikabe.cpp +++ b/src/d/actor/d_a_obj_gomikabe.cpp @@ -201,7 +201,13 @@ void daObjGOMIKABE_c::CheckCull() { bool daObjGOMIKABE_c::checkViewArea(cXyz param_1) { Vec local_24; + + #if TARGET_PC + mDoLib_project(¶m_1, &local_24, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(¶m_1, &local_24); + #endif + bool rv = false; if (local_24.x >= 0.0f && local_24.x <= FB_WIDTH && local_24.y >= 0.0f && local_24.y <= FB_HEIGHT) { rv = true; diff --git a/src/d/actor/d_a_obj_hhashi.cpp b/src/d/actor/d_a_obj_hhashi.cpp index d5ab5558b0..ea45fa5ea1 100644 --- a/src/d/actor/d_a_obj_hhashi.cpp +++ b/src/d/actor/d_a_obj_hhashi.cpp @@ -214,7 +214,13 @@ void daObjHHASHI_c::CheckCull() { bool daObjHHASHI_c::checkViewArea(int param_1) { Vec local_20; + + #if TARGET_PC + mDoLib_project(&field_0x5b0[param_1], &local_20, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(&field_0x5b0[param_1], &local_20); + #endif + bool rv = false; if (local_20.x >= 0.0f && local_20.x <= FB_WIDTH && local_20.y >= 0.0f && local_20.y <= FB_HEIGHT) { rv = true; diff --git a/src/d/actor/d_a_obj_kamakiri.cpp b/src/d/actor/d_a_obj_kamakiri.cpp index 2b641d7769..daa6560e6f 100644 --- a/src/d/actor/d_a_obj_kamakiri.cpp +++ b/src/d/actor/d_a_obj_kamakiri.cpp @@ -517,7 +517,13 @@ void daObjKAM_c::Z_BufferChk() { cXyz currentOffset; currentOffset = current.pos; currentOffset.y += 20.0f; + + #if TARGET_PC + mDoLib_project(¤tOffset, ¤tProj, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(¤tOffset, ¤tProj); + #endif + camera_process_class* camera = dComIfGp_getCamera(0); f32 cameraHeight; if (camera != NULL) { diff --git a/src/d/actor/d_a_obj_katatsumuri.cpp b/src/d/actor/d_a_obj_katatsumuri.cpp index 8b85644d80..03d40a1066 100644 --- a/src/d/actor/d_a_obj_katatsumuri.cpp +++ b/src/d/actor/d_a_obj_katatsumuri.cpp @@ -611,7 +611,12 @@ void daObjKAT_c::Z_BufferChk() { cXyz curWithOff; curWithOff = current.pos; curWithOff.y += 20.0f; + + #if TARGET_PC + mDoLib_project(&curWithOff, &projected, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(&curWithOff, &projected); + #endif camera_process_class* camera = dComIfGp_getCamera(0); f32 unkFloat1; diff --git a/src/d/actor/d_a_obj_kuwagata.cpp b/src/d/actor/d_a_obj_kuwagata.cpp index ddb00d443d..914e25dfb1 100644 --- a/src/d/actor/d_a_obj_kuwagata.cpp +++ b/src/d/actor/d_a_obj_kuwagata.cpp @@ -528,7 +528,12 @@ void daObjKUW_c::Z_BufferChk() { cStack_68 = current.pos; cStack_68.y += 20.0f; + + #if TARGET_PC + mDoLib_project(&cStack_68, &local_5c, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(&cStack_68, &local_5c); + #endif camera_process_class* cc = dComIfGp_getCamera(0); f32 trimHeight; diff --git a/src/d/actor/d_a_obj_ten.cpp b/src/d/actor/d_a_obj_ten.cpp index fbedd6a8c9..1efe358607 100644 --- a/src/d/actor/d_a_obj_ten.cpp +++ b/src/d/actor/d_a_obj_ten.cpp @@ -593,7 +593,13 @@ void daObjTEN_c::Z_BufferChk() { cXyz cStack_68; cStack_68 = current.pos; cStack_68.y += 20.0f; + + #if TARGET_PC + mDoLib_project(&cStack_68, &local_5c, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(&cStack_68, &local_5c); + #endif + camera_process_class* camera = dComIfGp_getCamera(0); f32 trimHeight; if (camera != NULL) { diff --git a/src/d/actor/d_a_obj_tombo.cpp b/src/d/actor/d_a_obj_tombo.cpp index 092b1c46c3..48db14ac92 100644 --- a/src/d/actor/d_a_obj_tombo.cpp +++ b/src/d/actor/d_a_obj_tombo.cpp @@ -504,7 +504,13 @@ void daObjTOMBO_c::Z_BufferChk() { cXyz cStack_68; cStack_68 = current.pos; cStack_68.y += 20.0f; + + #if TARGET_PC + mDoLib_project(&cStack_68, &local_5c, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(&cStack_68, &local_5c); + #endif + camera_process_class* pCamera = dComIfGp_getCamera(0); f32 trimHeight; if (pCamera != NULL) { diff --git a/src/d/actor/d_a_obj_zra_freeze.cpp b/src/d/actor/d_a_obj_zra_freeze.cpp index 1525bdb572..08932c439d 100644 --- a/src/d/actor/d_a_obj_zra_freeze.cpp +++ b/src/d/actor/d_a_obj_zra_freeze.cpp @@ -38,7 +38,12 @@ BOOL daZraFreeze_c::chkActorInScreen() { mDoMtx_stack_c::transM(0.0f, 0.0f, 0.0f); PSMTXMultVecArray(mDoMtx_stack_c::get(), vec, vec, 8); for (int i = 0; i < 8; i++) { + #if TARGET_PC + mDoLib_project(&vec[i], &proj, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(&vec[i], &proj); + #endif + if (0.0f < proj.x && proj.x < FB_WIDTH && 0.0f < proj.y && proj.y < FB_HEIGHT) { continue; } diff --git a/src/d/d_insect.cpp b/src/d/d_insect.cpp index 3f1d2fae11..380f545bd3 100644 --- a/src/d/d_insect.cpp +++ b/src/d/d_insect.cpp @@ -82,7 +82,11 @@ void dInsect_c::CalcZBuffer(f32 param_0) { pos = current.pos; pos.y += 20.0f; + #if TARGET_PC + mDoLib_project(&pos, &pos_projected, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(&pos, &pos_projected); + #endif if (dComIfGp_getCamera(0)) { camera_trim_height = dComIfGp_getCamera(0)->mCamera.mTrimHeight; diff --git a/src/d/d_kankyo.cpp b/src/d/d_kankyo.cpp index ec35ee8bc7..50f9d716fc 100644 --- a/src/d/d_kankyo.cpp +++ b/src/d/d_kankyo.cpp @@ -10977,7 +10977,11 @@ void dKy_depth_dist_set(void* process_p) { f32 var_f31 = sp24.abs(camera_p->view.lookat.eye); if (var_f31 < 2000.0f && var_f31 < kankyo->field_0x1268) { + #if TARGET_PC + mDoLib_project(&actor_p->eyePos, &sp30, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(&actor_p->eyePos, &sp30); + #endif if ((sp30.x >= 0.0f && sp30.x < FB_WIDTH) && (sp30.y >= 0.0f && #if DEBUG diff --git a/src/d/d_kankyo_debug.cpp b/src/d/d_kankyo_debug.cpp index 7fdad26ed2..03bfeac196 100644 --- a/src/d/d_kankyo_debug.cpp +++ b/src/d/d_kankyo_debug.cpp @@ -915,7 +915,12 @@ void dKydb_dungeonlight_draw() { rot.y = 0; dDbVw_drawCubeXlu(player->current.pos, size, rot, color); + #if TARGET_PC + mDoLib_project(&g_env_light.dungeonlight[i].mPosition, &proj, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(&g_env_light.dungeonlight[i].mPosition, &proj); + #endif + if (proj.x > 30.0f) { proj.x -= 30.0f; } diff --git a/src/d/d_msg_object.cpp b/src/d/d_msg_object.cpp index 8836acff10..3676639908 100644 --- a/src/d/d_msg_object.cpp +++ b/src/d/d_msg_object.cpp @@ -1464,12 +1464,24 @@ void dMsgObject_c::fukiPosCalc(bool param_1) { fopAc_ac_c* player = dComIfGp_getPlayer(0); cXyz local_3c; cXyz cStack_48; + + #if TARGET_PC + mDoLib_project(&player->eyePos, &cStack_48, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(&player->eyePos, &cStack_48); + #endif + f32 temp; if ((field_0x100->pos == cXyz(0.0f, 0.0f, 0.0f))) { temp = cStack_48.y; } else { + + #if TARGET_PC + mDoLib_project(&field_0x100->pos, &local_3c, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(&field_0x100->pos, &local_3c); + #endif + if (local_3c.x >= 0.0f && local_3c.x <= FB_WIDTH && local_3c.y >= 0.0f && local_3c.y <= FB_HEIGHT) { diff --git a/src/d/d_msg_scrn_item.cpp b/src/d/d_msg_scrn_item.cpp index bb4daf3927..7cba6c7b86 100644 --- a/src/d/d_msg_scrn_item.cpp +++ b/src/d/d_msg_scrn_item.cpp @@ -557,11 +557,22 @@ void dMsgScrnItem_c::fukiPosCalc(u8 param_1) { cXyz local_70; cXyz cStack_7c; f32 f3; + + #if TARGET_PC + mDoLib_project(&player->eyePos, &cStack_7c, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(&player->eyePos, &cStack_7c); + #endif + if (iVar6->pos == cXyz(0.0f, 0.0f, 0.0f)) { f3 = cStack_7c.y; } else { + #if TARGET_PC + mDoLib_project(&iVar6->pos, &local_70, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(&iVar6->pos, &local_70); + #endif + if (local_70.x >= 0.0f && local_70.x <= FB_WIDTH && local_70.y >= 0.0f && local_70.y <= FB_HEIGHT) { diff --git a/src/d/d_msg_scrn_talk.cpp b/src/d/d_msg_scrn_talk.cpp index 226ece0304..546e42112f 100644 --- a/src/d/d_msg_scrn_talk.cpp +++ b/src/d/d_msg_scrn_talk.cpp @@ -441,11 +441,22 @@ void dMsgScrnTalk_c::fukiPosCalc(u8 param_1) { cXyz local_70; cXyz cStack_7c; f32 f3y; + + #if TARGET_PC + mDoLib_project(&player->eyePos, &cStack_7c, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(&player->eyePos, &cStack_7c); + #endif + if (msgActor->pos == cXyz(0.0f, 0.0f, 0.0f)) { f3y = cStack_7c.y; } else { - mDoLib_project(&msgActor->pos, &local_70); + #if TARGET_PC + mDoLib_project(&msgActor->pos, &local_70, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else + mDoLib_project(&msgActor->pos, &local_70,); + #endif + if (local_70.x >= 0.0f && local_70.x <= 608.0f && local_70.y >= 0.0f && local_70.y <= 448.0f) { From 6b7e122ba8985dcadbc8688e5870ae1dfccc1fd8 Mon Sep 17 00:00:00 2001 From: madeline Date: Mon, 6 Apr 2026 00:48:10 -0700 Subject: [PATCH 267/350] reorganize menus a bit --- src/dusk/imgui/ImGuiMenuEnhancements.cpp | 6 ++++++ src/dusk/imgui/ImGuiMenuTools.cpp | 5 +---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/dusk/imgui/ImGuiMenuEnhancements.cpp b/src/dusk/imgui/ImGuiMenuEnhancements.cpp index 3fbd55a8f2..a7a1e1d698 100644 --- a/src/dusk/imgui/ImGuiMenuEnhancements.cpp +++ b/src/dusk/imgui/ImGuiMenuEnhancements.cpp @@ -125,6 +125,12 @@ namespace dusk { ImGui::EndMenu(); } + if (ImGui::BeginMenu("Tools")) { + config::ImGuiCheckbox("Enable Turbo Key", getSettings().game.enableTurboKeybind); + + ImGui::EndMenu(); + } + ImGui::EndMenu(); } } diff --git a/src/dusk/imgui/ImGuiMenuTools.cpp b/src/dusk/imgui/ImGuiMenuTools.cpp index 6a2fc2ede0..7478adc0e5 100644 --- a/src/dusk/imgui/ImGuiMenuTools.cpp +++ b/src/dusk/imgui/ImGuiMenuTools.cpp @@ -7,7 +7,6 @@ #include "ImGuiConsole.hpp" #include "ImGuiMenuTools.hpp" -#include "ImGuiConfig.hpp" #include "d/actor/d_a_alink.h" #include "d/actor/d_a_horse.h" #include "d/d_com_inf_game.h" @@ -19,7 +18,7 @@ namespace dusk { void ImGuiMenuTools::draw() { bool isToggleDevelopmentMode = false; - if (ImGui::BeginMenu("Tools")) { + if (ImGui::BeginMenu("Debug")) { if (ImGui::Checkbox("Development Mode", &m_isDevelopmentMode)) { isToggleDevelopmentMode = true; } @@ -50,8 +49,6 @@ namespace dusk { ImGui::MenuItem("Save Editor", nullptr, &m_showSaveEditor); ImGui::MenuItem("Audio Debug", hotkeys::SHOW_AUDIO_DEBUG, &m_showAudioDebug); ImGui::MenuItem("OSReport Force", nullptr, &OSReportReallyForceEnable); - ImGui::Separator(); - config::ImGuiMenuItem("Enable Turbo Key", hotkeys::TURBO, getSettings().game.enableTurboKeybind); ImGui::EndMenu(); } From 7ae9f0f83dd126b559798a27a5a7a07eb70f22db Mon Sep 17 00:00:00 2001 From: madeline Date: Mon, 6 Apr 2026 07:53:46 -0700 Subject: [PATCH 268/350] runtime data asset loader --- files.cmake | 2 + include/dusk/dvd_asset.hpp | 22 ++++++++ src/d/actor/d_a_mant.cpp | 22 ++++++++ src/d/actor/d_a_player.cpp | 6 +++ src/d/actor/d_flower.inc | 35 ++++++++++++ src/d/actor/d_grass.inc | 29 ++++++++-- src/d/d_error_msg.cpp | 17 ++++++ src/dusk/dvd_asset.cpp | 106 +++++++++++++++++++++++++++++++++++++ src/m_Do/m_Do_ext.cpp | 3 ++ 9 files changed, 239 insertions(+), 3 deletions(-) create mode 100644 include/dusk/dvd_asset.hpp create mode 100644 src/dusk/dvd_asset.cpp diff --git a/files.cmake b/files.cmake index 7d07408613..e9149a1777 100644 --- a/files.cmake +++ b/files.cmake @@ -1333,6 +1333,8 @@ set(DOLPHIN_FILES set(DUSK_FILES include/dusk/endian_gx.hpp include/dusk/config.hpp + include/dusk/dvd_asset.hpp + src/dusk/dvd_asset.cpp src/d/actor/d_a_alink_dusk.cpp src/dusk/asserts.cpp src/dusk/config.cpp diff --git a/include/dusk/dvd_asset.hpp b/include/dusk/dvd_asset.hpp new file mode 100644 index 0000000000..3c36b75195 --- /dev/null +++ b/include/dusk/dvd_asset.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "dolphin/types.h" + +namespace dusk { + +/** + * Load bytes from the main DOL by GameCube virtual address + */ +bool LoadDolAsset(void* dst, u32 virtualAddress, s32 size); + +/** + * Load bytes from a REL file in the ISO filesystem + */ +void* LoadRelAsset(const char* dvdPath, s32 offset, s32 size); + +/** + * Load bytes from a REL inside RELS.arc + */ +bool LoadArchivedRelAsset(void* dst, u32 memType, const char* relFileName, s32 offset, s32 size); + +} // namespace dusk diff --git a/src/d/actor/d_a_mant.cpp b/src/d/actor/d_a_mant.cpp index a3b48f1098..1760db60fa 100644 --- a/src/d/actor/d_a_mant.cpp +++ b/src/d/actor/d_a_mant.cpp @@ -10,11 +10,21 @@ #include "d/actor/d_a_b_gnd.h" #include "d/d_com_inf_game.h" +#if TARGET_PC +#include "dusk/dvd_asset.hpp" +static u8* l_Egnd_mantTEX_get() { static u8* p = (u8*)dusk::LoadRelAsset("/rel/Final/Release/d_a_mant.rel", 0x1C00, 0x4000); return p; } +static u8* l_Egnd_mantTEX_U_get() { static u8* p = (u8*)dusk::LoadRelAsset("/rel/Final/Release/d_a_mant.rel", 0x5C00, 0x4000); return p; } +static u8* l_Egnd_mantPAL_get() { static u8* p = (u8*)dusk::LoadRelAsset("/rel/Final/Release/d_a_mant.rel", 0x9C00, 0x60); return p; } +#define l_Egnd_mantTEX (l_Egnd_mantTEX_get()) +#define l_Egnd_mantTEX_U (l_Egnd_mantTEX_U_get()) +#define l_Egnd_mantPAL (l_Egnd_mantPAL_get()) +#else #include "assets/l_Egnd_mantTEX.h" #include "assets/l_Egnd_mantTEX_U.h" #include "assets/l_Egnd_mantPAL.h" +#endif #include "d/d_s_play.h" static u32 l_pos[507] = { @@ -239,20 +249,32 @@ static u32 l_texCoord[338] = { 0x3F800000, 0x00000000, }; +#if TARGET_PC +static u8* l_Egnd_mantDL_get() { static u8* p = (u8*)dusk::LoadRelAsset("/rel/Final/Release/d_a_mant.rel", 0xA9A0, 0x3EC); return p; } +#define l_Egnd_mantDL (l_Egnd_mantDL_get()) +#else #include "assets/l_Egnd_mantDL.h" +#endif +#if !TARGET_PC static void* pal_d = (void*)&l_Egnd_mantPAL; static void* tex_d[2] = { (void*)&l_Egnd_mantTEX, (void*)&l_Egnd_mantTEX_U, }; +#endif static char lbl_277_bss_0; void daMant_packet_c::draw() { +#if TARGET_PC + void* image = l_Egnd_mantTEX; + void* lut = l_Egnd_mantPAL; +#else void* image = tex_d[0]; void* lut = pal_d; +#endif j3dSys.reinitGX(); GXSetNumIndStages(0); diff --git a/src/d/actor/d_a_player.cpp b/src/d/actor/d_a_player.cpp index f38fe15a3b..697635d69d 100644 --- a/src/d/actor/d_a_player.cpp +++ b/src/d/actor/d_a_player.cpp @@ -368,7 +368,13 @@ JKRHeap* daPy_anmHeap_c::setAnimeHeap() { } #if !PLATFORM_WII +#if TARGET_PC +#include "dusk/dvd_asset.hpp" +static const u8* l_sightDL_get() { static u8 buf[0x89]; static bool _ = (dusk::LoadDolAsset(buf, 0x803BA0C0, 0x89), true); return buf; } +#define l_sightDL (l_sightDL_get()) +#else #include "assets/l_sightDL__d_a_player.h" +#endif void daPy_sightPacket_c::draw() { TGXTexObj texObj; diff --git a/src/d/actor/d_flower.inc b/src/d/actor/d_flower.inc index 762b1e02e9..93a7e35097 100644 --- a/src/d/actor/d_flower.inc +++ b/src/d/actor/d_flower.inc @@ -13,7 +13,13 @@ const u16 l_J_Ohana00_64TEX__width = 63; const u16 l_J_Ohana00_64TEX__height = 63; #endif +#if TARGET_PC +#include "dusk/dvd_asset.hpp" +static u8* l_J_Ohana00_64TEX_get() { static u8 buf[0x800]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x9060, 0x800), true); return buf; } +#define l_J_Ohana00_64TEX (l_J_Ohana00_64TEX_get()) +#else #include "assets/l_J_Ohana00_64TEX.h" +#endif static u8 l_flowerPos[708] = { 0xC0, 0x8C, 0x2C, 0xF7, 0x42, 0x05, 0xBC, 0xDF, 0xC1, 0xA1, 0x00, 0x70, 0xBF, 0x50, 0x51, 0xB9, @@ -104,6 +110,16 @@ static u8 l_flowerTexCoord[] = { 0x3E, 0xA7, 0x67, 0x4D, 0x3C, 0x14, 0x46, 0x74, 0x3E, 0xA7, 0x73, 0xE2, 0xBC, 0x2F, 0x46, 0xAA, 0x3E, 0xA7, 0x72, 0xD6, 0xBD, 0x2F, 0x46, 0xAA}; +#if TARGET_PC +static u8* l_J_hana00DL_get() { static u8 buf[0x150]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x9D20, 0x150), true); return buf; } +static u8* l_J_hana00_cDL_get() { static u8 buf[0xDE]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x9E80, 0xDE), true); return buf; } +static u8* l_matDL_get() { static u8 buf[0x99]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x9F60, 0x99), true); return buf; } +static u8* l_matLight4DL_get() { static u8 buf[0x99]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0xA000, 0x99), true); return buf; } +#define l_J_hana00DL (l_J_hana00DL_get()) +#define l_J_hana00_cDL (l_J_hana00_cDL_get()) +#define l_matDL (l_matDL_get()) +#define l_matLight4DL (l_matLight4DL_get()) +#else #include "assets/l_J_hana00DL.h" #include "assets/l_J_hana00_cDL.h" @@ -113,6 +129,7 @@ l_matDL__d_a_grass(l_J_Ohana00_64TEX) #include "assets/l_matLight4DL.h" l_matLight4DL(l_J_Ohana00_64TEX) +#endif #if TARGET_PC const u16 l_J_Ohana01_64128_0419TEX__width = 64; @@ -122,7 +139,12 @@ const u16 l_J_Ohana01_64128_0419TEX__width = 63; const u16 l_J_Ohana01_64128_0419TEX__height = 127; #endif +#if TARGET_PC +static u8* l_J_Ohana01_64128_0419TEX_get() { static u8 buf[0x1000]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0xA0A0, 0x1000), true); return buf; } +#define l_J_Ohana01_64128_0419TEX (l_J_Ohana01_64128_0419TEX_get()) +#else #include "assets/l_J_Ohana01_64128_0419TEX.h" +#endif static u8 l_flowerPos2[1224] = { 0x40, 0x25, 0x9F, 0x34, 0x42, 0xC2, 0x95, 0x72, 0xC1, 0x22, 0x34, 0x78, 0x41, 0x4D, 0xF9, 0x63, @@ -249,6 +271,18 @@ static u8 l_flowerTexCoord2[] = { 0x40, 0x1B, 0x7D, 0x52, 0x3F, 0x97, 0xF6, 0xBA, 0x40, 0x04, 0x26, 0x74, 0x3F, 0x80, 0x3F, 0x79, 0x40, 0x1B, 0x7D, 0x52, 0x3F, 0x80, 0x3F, 0x79, 0x40, 0x1B, 0x7D, 0x52, 0x3F, 0x51, 0x10, 0x6F}; +#if TARGET_PC +static u8* l_J_hana01DL_get() { static u8 buf[0x138]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0xB7C0, 0x138), true); return buf; } +static u8* l_J_hana01_c_00DL_get() { static u8 buf[0xDE]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0xB900, 0xDE), true); return buf; } +static u8* l_J_hana01_c_01DL_get() { static u8 buf[0x128]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0xB9E0, 0x128), true); return buf; } +static u8* l_mat2DL_get() { static u8 buf[0x99]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0xBB20, 0x99), true); return buf; } +static u8* l_mat2Light4DL_get() { static u8 buf[0x99]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0xBBC0, 0x99), true); return buf; } +#define l_J_hana01DL (l_J_hana01DL_get()) +#define l_J_hana01_c_00DL (l_J_hana01_c_00DL_get()) +#define l_J_hana01_c_01DL (l_J_hana01_c_01DL_get()) +#define l_mat2DL (l_mat2DL_get()) +#define l_mat2Light4DL (l_mat2Light4DL_get()) +#else #include "assets/l_J_hana01DL.h" #include "assets/l_J_hana01_c_00DL.h" @@ -260,6 +294,7 @@ l_mat2DL__d_a_grass(l_J_Ohana01_64128_0419TEX) #include "assets/l_mat2Light4DL.h" l_mat2Light4DL(l_J_Ohana01_64128_0419TEX) +#endif void dFlower_data_c::WorkCo(fopAc_ac_c* i_hitActor, u32 i_massFlg, int i_roomNo) { cXyz sp8; diff --git a/src/d/actor/d_grass.inc b/src/d/actor/d_grass.inc index 6351cc75fc..3645488b0f 100644 --- a/src/d/actor/d_grass.inc +++ b/src/d/actor/d_grass.inc @@ -13,11 +13,19 @@ const u16 l_M_Hijiki00TEX__width = 31; const u16 l_M_Hijiki00TEX__height = 31; -#include "assets/l_M_kusa05_RGBATEX.h" // ALIGN 32 - const u16 l_M_kusa05_RGBATEX__width = 31; const u16 l_M_kusa05_RGBATEX__height = 31; -#include "assets/l_M_Hijiki00TEX.h" // ALIGN 32 + +#if TARGET_PC +#include "dusk/dvd_asset.hpp" +static u8* l_M_kusa05_RGBATEX_get() { static u8 buf[0x800]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x7680, 0x800), true); return buf; } +static u8* l_M_Hijiki00TEX_get() { static u8 buf[0x800]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x7E80, 0x800), true); return buf; } +#define l_M_kusa05_RGBATEX (l_M_kusa05_RGBATEX_get()) +#define l_M_Hijiki00TEX (l_M_Hijiki00TEX_get()) +#else +#include "assets/l_M_kusa05_RGBATEX.h" // ALIGN 32 +#include "assets/l_M_Hijiki00TEX.h" // ALIGN 32 +#endif static u8 l_pos[960] = { 0x3F, 0x4A, 0x56, 0xEF, 0xC2, 0x20, 0x00, 0x00, 0x41, 0xFB, 0x17, 0xE4, 0x41, 0xAA, 0xBB, 0xEA, @@ -102,6 +110,20 @@ static u8 l_texCoord[160] = { 0x3F, 0x94, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0xBD, 0xC0, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, }; +#if TARGET_PC +static u8* l_M_Kusa_9qDL_get() { static u8 buf[0xCB]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x8B00, 0xCB), true); return buf; } +static u8* l_M_Kusa_9q_cDL_get() { static u8 buf[0xCB]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x8BE0, 0xCB), true); return buf; } +static u8* l_M_TenGusaDL_get() { static u8 buf[0xD4]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x8CC0, 0xD4), true); return buf; } +static u8* l_Tengusa_matDL_get() { static u8 buf[0xA8]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x8DA0, 0xA8), true); return buf; } +static u8* l_kusa9q_matDL_get() { static u8 buf[0xA8]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x8E60, 0xA8), true); return buf; } +static u8* l_kusa9q_l4_matDL_get() { static u8 buf[0xA8]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x8F20, 0xA8), true); return buf; } +#define l_M_Kusa_9qDL (l_M_Kusa_9qDL_get()) +#define l_M_Kusa_9q_cDL (l_M_Kusa_9q_cDL_get()) +#define l_M_TenGusaDL (l_M_TenGusaDL_get()) +#define l_Tengusa_matDL (l_Tengusa_matDL_get()) +#define l_kusa9q_matDL (l_kusa9q_matDL_get()) +#define l_kusa9q_l4_matDL (l_kusa9q_l4_matDL_get()) +#else #include "assets/l_M_Kusa_9qDL.h" #include "assets/l_M_Kusa_9q_cDL.h" @@ -117,6 +139,7 @@ l_kusa9q_matDL(l_M_kusa05_RGBATEX) #include "assets/l_kusa9q_l4_matDL.h" l_kusa9q_l4_matDL(l_M_kusa05_RGBATEX) +#endif void dGrass_data_c::WorkCo(fopAc_ac_c* i_hitActor, u32 i_massFlg, int i_roomNo) { cXyz sp18; diff --git a/src/d/d_error_msg.cpp b/src/d/d_error_msg.cpp index faa40ae543..f1d63b5a1f 100644 --- a/src/d/d_error_msg.cpp +++ b/src/d/d_error_msg.cpp @@ -15,6 +15,22 @@ #include "m_Do/m_Do_Reset.h" #include "m_Do/m_Do_graphic.h" +#if TARGET_PC +#include "dusk/dvd_asset.hpp" +static const u8* black_tex_get() { static u8 buf[0x40]; static bool _ = (dusk::LoadDolAsset(buf, 0x8037B140, 0x40), true); return buf; } +static const u8* msg_data_get() { static u8 buf[0x260]; static bool _ = (dusk::LoadDolAsset(buf, 0x8037B180, 0x260), true); return buf; } +static const u8* font_data_get() { static u8 buf[0x12260]; static bool _ = (dusk::LoadDolAsset(buf, 0x8037B3E0, 0x12260), true); return buf; } +#define black_tex (black_tex_get()) +#define msg_data (msg_data_get()) +#define font_data (font_data_get()) +#if VERSION == VERSION_GCN_PAL +// TODO: find addreses for pal languages +#define msg_data_ge ((const u8*)nullptr) +#define msg_data_fr ((const u8*)nullptr) +#define msg_data_sp ((const u8*)nullptr) +#define msg_data_it ((const u8*)nullptr) +#endif +#else #include "assets/black_tex.h" #include "assets/msg_data.h" #if VERSION == VERSION_GCN_PAL @@ -24,6 +40,7 @@ #include "assets/msg_data_it.h" #endif #include "assets/font_data.h" +#endif #define MSG_READING_DISC 0 #define MSG_COVER_OPEN 1 diff --git a/src/dusk/dvd_asset.cpp b/src/dusk/dvd_asset.cpp new file mode 100644 index 0000000000..bfa30cc941 --- /dev/null +++ b/src/dusk/dvd_asset.cpp @@ -0,0 +1,106 @@ +#include "dusk/dvd_asset.hpp" +#include "dusk/logging.h" +#include "dusk/endian.h" +#include "aurora/dvd.h" +#include "DynamicLink.h" +#include "JSystem/JKernel/JKRArchive.h" +#include "JSystem/JKernel/JKRDvdRipper.h" + +#include + +namespace dusk { + +static const u8* s_dolData = nullptr; // pointer to dol data +static size_t s_dolSize = 0; +struct DolSection { u32 fileOffset, vaddr, size; }; +static DolSection s_dolSections[18]; // 7 text + 11 data +static int s_dolSectionCount = 0; + +static bool EnsureDolParsed() { + if (s_dolData) return true; + + s32 sz = 0; + const u8* p = aurora_dvd_get_dol(sz); + if (!p || sz < 256) { + DuskLog.fatal("dvd_asset: aurora_dvd_get_dol failed (size={})", sz); return false; + } + + s_dolData = p; + s_dolSize = sz; + + const BE(u32)* hdr = (const BE(u32)*)s_dolData; + s_dolSectionCount = 0; + + // 0x00: text file offsets 0x12: text vaddrs 0x24: text sizes + for (int i = 0; i < 7; i++) { + u32 off = hdr[0x00+i], addr = hdr[0x12+i], sz = hdr[0x24+i]; + if (sz > 0 && off > 0) { + s_dolSections[s_dolSectionCount++] = {off, addr, sz}; + } + } + // 0x07: data file offsets 0x19: data vaddrs 0x2B: data sizes + for (int i = 0; i < 11; i++) { + u32 off = hdr[0x07+i], addr = hdr[0x19+i], sz = hdr[0x2B+i]; + if (sz > 0 && off > 0) { + s_dolSections[s_dolSectionCount++] = {off, addr, sz}; + } + } + + return true; +} + +static s32 DolVaToFileOffset(u32 va) { + if (!EnsureDolParsed()) return -1; + for (int i = 0; i < s_dolSectionCount; i++) { + const auto& sec = s_dolSections[i]; + if (va >= sec.vaddr && va < sec.vaddr + sec.size) { + return static_cast(sec.fileOffset + (va - sec.vaddr)); + } + } + DuskLog.fatal("dvd_asset: VA 0x{:08X} not found in any DOL section", va); + return -1; +} + +bool LoadDolAsset(void* dst, u32 virtualAddress, s32 size) { + s32 fileOffset = DolVaToFileOffset(virtualAddress); + if (fileOffset < 0) { + return false; + } + + if (size <= 0 || (size_t)(fileOffset + size) > s_dolSize) { + DuskLog.fatal("dvd_asset: DOL read out of range (offset={:#x} size={:#x} dolSize={})", fileOffset, size, s_dolSize); + return false; + } + + std::memcpy(dst, s_dolData + fileOffset, size); + return true; +} + +void* LoadRelAsset(const char* dvdPath, s32 offset, s32 size) { + void* p = JKRDvdRipper::loadToMainRAM(dvdPath, nullptr, EXPAND_SWITCH_UNKNOWN1, (u32)size, nullptr, JKRDvdRipper::ALLOC_DIRECTION_FORWARD, (u32)offset, nullptr, nullptr); + if (!p) DuskLog.fatal("dvd_asset: failed to load {} (offset={:#x} size={:#x})", dvdPath, offset, size); + return p; +} + +bool LoadArchivedRelAsset(void* dst, u32 memType, const char* relFileName, s32 offset, s32 size) { + // On TARGET_PC, cDyl_InitCallback skips DynamicModuleControl::initialize() due to static linking + // Mount RELS.arc on first use so sArchive is available + static bool s_mountAttempted = false; + if (!DynamicModuleControl::sArchive && !s_mountAttempted) { + s_mountAttempted = true; DynamicModuleControl::initialize(); + } + + if (!DynamicModuleControl::sArchive) { + DuskLog.fatal("dvd_asset: RELS archive not mounted"); return false; + } + + const u8* rel = static_cast(DynamicModuleControl::sArchive->getResource(memType, relFileName)); + if (!rel) { + DuskLog.fatal("dvd_asset: {} not found in RELS archive", relFileName); return false; + } + + std::memcpy(dst, rel + offset, size); + return true; +} + +} // namespace dusk diff --git a/src/m_Do/m_Do_ext.cpp b/src/m_Do/m_Do_ext.cpp index d744d048ba..0593815b1d 100644 --- a/src/m_Do/m_Do_ext.cpp +++ b/src/m_Do/m_Do_ext.cpp @@ -2873,7 +2873,10 @@ void mDoExt_3DlineMat1_c::update(int param_0, f32 param_1, GXColor& param_2, u16 sp_38++; } } + +#if !TARGET_PC #include "assets/l_mat2DL__d_a_grass.h" +#endif void mDoExt_3DlineMat2_c::setMaterial() { j3dSys.reinitGX(); From 879ea55d77994d56ab9f3496ec6a3ed855229058 Mon Sep 17 00:00:00 2001 From: madeline Date: Mon, 6 Apr 2026 07:55:10 -0700 Subject: [PATCH 269/350] remove includes --- CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3a074ea869..448a58a45e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -100,7 +100,6 @@ set(GAME_COMPILE_DEFS TARGET_PC AVOID_UB=1 VERSION=0 set(GAME_INCLUDE_DIRS include src - assets/${DUSK_TP_VERSION} libs/JSystem/include libs extern/aurora/include/dolphin From 450046328f9b05cec37f71c94913b6aa062e6078 Mon Sep 17 00:00:00 2001 From: madeline Date: Mon, 6 Apr 2026 07:57:00 -0700 Subject: [PATCH 270/350] walk that one on back --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 448a58a45e..3a074ea869 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -100,6 +100,7 @@ set(GAME_COMPILE_DEFS TARGET_PC AVOID_UB=1 VERSION=0 set(GAME_INCLUDE_DIRS include src + assets/${DUSK_TP_VERSION} libs/JSystem/include libs extern/aurora/include/dolphin From 68083250f599630825001dfbe1d4964220ae0fc7 Mon Sep 17 00:00:00 2001 From: madeline Date: Mon, 6 Apr 2026 08:49:24 -0700 Subject: [PATCH 271/350] address review --- include/dusk/dvd_asset.hpp | 4 ++-- src/d/actor/d_a_mant.cpp | 8 ++++---- src/d/actor/d_flower.inc | 10 +++++----- src/dusk/dvd_asset.cpp | 39 ++++++++++++++++++++++++++++---------- 4 files changed, 40 insertions(+), 21 deletions(-) diff --git a/include/dusk/dvd_asset.hpp b/include/dusk/dvd_asset.hpp index 3c36b75195..014c683d44 100644 --- a/include/dusk/dvd_asset.hpp +++ b/include/dusk/dvd_asset.hpp @@ -10,9 +10,9 @@ namespace dusk { bool LoadDolAsset(void* dst, u32 virtualAddress, s32 size); /** - * Load bytes from a REL file in the ISO filesystem + * Load bytes from a REL file in the ISO filesystem, dst must be 32-byte aligned */ -void* LoadRelAsset(const char* dvdPath, s32 offset, s32 size); +bool LoadRelAsset(void* dst, const char* dvdPath, s32 offset, s32 size); /** * Load bytes from a REL inside RELS.arc diff --git a/src/d/actor/d_a_mant.cpp b/src/d/actor/d_a_mant.cpp index 1760db60fa..bcd7f05986 100644 --- a/src/d/actor/d_a_mant.cpp +++ b/src/d/actor/d_a_mant.cpp @@ -12,9 +12,9 @@ #if TARGET_PC #include "dusk/dvd_asset.hpp" -static u8* l_Egnd_mantTEX_get() { static u8* p = (u8*)dusk::LoadRelAsset("/rel/Final/Release/d_a_mant.rel", 0x1C00, 0x4000); return p; } -static u8* l_Egnd_mantTEX_U_get() { static u8* p = (u8*)dusk::LoadRelAsset("/rel/Final/Release/d_a_mant.rel", 0x5C00, 0x4000); return p; } -static u8* l_Egnd_mantPAL_get() { static u8* p = (u8*)dusk::LoadRelAsset("/rel/Final/Release/d_a_mant.rel", 0x9C00, 0x60); return p; } +static u8* l_Egnd_mantTEX_get() { alignas(32) static u8 buf[0x4000]; static bool _ = (dusk::LoadRelAsset(buf, "/rel/Final/Release/d_a_mant.rel", 0x1C00, 0x4000), true); return buf; } +static u8* l_Egnd_mantTEX_U_get() { alignas(32) static u8 buf[0x4000]; static bool _ = (dusk::LoadRelAsset(buf, "/rel/Final/Release/d_a_mant.rel", 0x5C00, 0x4000), true); return buf; } +static u8* l_Egnd_mantPAL_get() { alignas(32) static u8 buf[0x60]; static bool _ = (dusk::LoadRelAsset(buf, "/rel/Final/Release/d_a_mant.rel", 0x9C00, 0x60), true); return buf; } #define l_Egnd_mantTEX (l_Egnd_mantTEX_get()) #define l_Egnd_mantTEX_U (l_Egnd_mantTEX_U_get()) #define l_Egnd_mantPAL (l_Egnd_mantPAL_get()) @@ -250,7 +250,7 @@ static u32 l_texCoord[338] = { }; #if TARGET_PC -static u8* l_Egnd_mantDL_get() { static u8* p = (u8*)dusk::LoadRelAsset("/rel/Final/Release/d_a_mant.rel", 0xA9A0, 0x3EC); return p; } +static u8* l_Egnd_mantDL_get() { alignas(32) static u8 buf[0x3EC]; static bool _ = (dusk::LoadRelAsset(buf, "/rel/Final/Release/d_a_mant.rel", 0xA9A0, 0x3EC), true); return buf; } #define l_Egnd_mantDL (l_Egnd_mantDL_get()) #else #include "assets/l_Egnd_mantDL.h" diff --git a/src/d/actor/d_flower.inc b/src/d/actor/d_flower.inc index 93a7e35097..de6ac7c1dc 100644 --- a/src/d/actor/d_flower.inc +++ b/src/d/actor/d_flower.inc @@ -619,9 +619,9 @@ void dFlower_packet_c::draw() { } if (sp4C <= 2) { - GXCallDisplayList(&l_matLight4DL, 0x80); + GXCallDisplayList(l_matLight4DL, 0x80); } else { - GXCallDisplayList(&l_matDL, 0x80); + GXCallDisplayList(l_matDL, 0x80); } GXSetTevColorS10(GX_TEVREG0, sp80); @@ -677,9 +677,9 @@ void dFlower_packet_c::draw() { #endif if (!cLib_checkBit(sp44->m_state, 8)) { - GXCallDisplayList(&l_J_hana00DL, 0x140); + GXCallDisplayList(l_J_hana00DL, 0x140); } else { - GXCallDisplayList(&l_J_hana00_cDL, 0xC0); + GXCallDisplayList(l_J_hana00_cDL, 0xC0); } } } @@ -826,7 +826,7 @@ void dFlower_packet_c::draw() { if (!cLib_checkBit(sp34->m_state, 0x10)) { GXCallDisplayList(mp_Jhana01DL, m_Jhana01DL_size); } else { - GXCallDisplayList(&l_J_hana01_c_00DL, 0xC0); + GXCallDisplayList(l_J_hana01_c_00DL, 0xC0); } } else { GXCallDisplayList(mp_Jhana01_cDL, m_Jhana01_cDL_size); diff --git a/src/dusk/dvd_asset.cpp b/src/dusk/dvd_asset.cpp index bfa30cc941..9bcea14077 100644 --- a/src/dusk/dvd_asset.cpp +++ b/src/dusk/dvd_asset.cpp @@ -12,7 +12,22 @@ namespace dusk { static const u8* s_dolData = nullptr; // pointer to dol data static size_t s_dolSize = 0; -struct DolSection { u32 fileOffset, vaddr, size; }; + +struct DolHeader { + BE(u32) textOffset[7]; + BE(u32) dataOffset[11]; + BE(u32) textAddr[7]; + BE(u32) dataAddr[11]; + BE(u32) textSize[7]; + BE(u32) dataSize[11]; +}; + +struct DolSection { + u32 fileOffset; + u32 vaddr; + u32 size; +}; + static DolSection s_dolSections[18]; // 7 text + 11 data static int s_dolSectionCount = 0; @@ -22,25 +37,29 @@ static bool EnsureDolParsed() { s32 sz = 0; const u8* p = aurora_dvd_get_dol(sz); if (!p || sz < 256) { - DuskLog.fatal("dvd_asset: aurora_dvd_get_dol failed (size={})", sz); return false; + DuskLog.fatal("dvd_asset: aurora_dvd_get_dol failed (size={})", sz); + return false; } s_dolData = p; s_dolSize = sz; - const BE(u32)* hdr = (const BE(u32)*)s_dolData; + const auto* hdr = (const DolHeader*)s_dolData; s_dolSectionCount = 0; - // 0x00: text file offsets 0x12: text vaddrs 0x24: text sizes for (int i = 0; i < 7; i++) { - u32 off = hdr[0x00+i], addr = hdr[0x12+i], sz = hdr[0x24+i]; + u32 off = hdr->textOffset[i]; + u32 addr = hdr->textAddr[i]; + u32 sz = hdr->textSize[i]; if (sz > 0 && off > 0) { s_dolSections[s_dolSectionCount++] = {off, addr, sz}; } } - // 0x07: data file offsets 0x19: data vaddrs 0x2B: data sizes + for (int i = 0; i < 11; i++) { - u32 off = hdr[0x07+i], addr = hdr[0x19+i], sz = hdr[0x2B+i]; + u32 off = hdr->dataOffset[i]; + u32 addr = hdr->dataAddr[i]; + u32 sz = hdr->dataSize[i]; if (sz > 0 && off > 0) { s_dolSections[s_dolSectionCount++] = {off, addr, sz}; } @@ -76,10 +95,10 @@ bool LoadDolAsset(void* dst, u32 virtualAddress, s32 size) { return true; } -void* LoadRelAsset(const char* dvdPath, s32 offset, s32 size) { - void* p = JKRDvdRipper::loadToMainRAM(dvdPath, nullptr, EXPAND_SWITCH_UNKNOWN1, (u32)size, nullptr, JKRDvdRipper::ALLOC_DIRECTION_FORWARD, (u32)offset, nullptr, nullptr); +bool LoadRelAsset(void* dst, const char* dvdPath, s32 offset, s32 size) { + void* p = JKRDvdRipper::loadToMainRAM(dvdPath, (u8*)dst, EXPAND_SWITCH_UNKNOWN1, (u32)size, nullptr, JKRDvdRipper::ALLOC_DIRECTION_FORWARD, (u32)offset, nullptr, nullptr); if (!p) DuskLog.fatal("dvd_asset: failed to load {} (offset={:#x} size={:#x})", dvdPath, offset, size); - return p; + return p != nullptr; } bool LoadArchivedRelAsset(void* dst, u32 memType, const char* relFileName, s32 offset, s32 size) { From f3a77c6cd7aca5bd615f179ad91c1466e7f39b14 Mon Sep 17 00:00:00 2001 From: madeline Date: Mon, 6 Apr 2026 08:58:52 -0700 Subject: [PATCH 272/350] update include directories --- CMakeLists.txt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3a074ea869..a237daaacc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -104,9 +104,7 @@ set(GAME_INCLUDE_DIRS libs/JSystem/include libs extern/aurora/include/dolphin - extern - ${CMAKE_SOURCE_DIR}/build/${DUSK_TP_VERSION}/include - build/${DUSK_TP_VERSION}/include) + extern) set(GAME_LIBS aurora::core aurora::gx aurora::gd aurora::si aurora::vi aurora::pad aurora::mtx aurora::os aurora::dvd aurora::card freeverb cxxopts::cxxopts absl::flat_hash_map nlohmann_json::nlohmann_json) From b09545d996ffb3a4b0b719acaf00e7bfeb92168a Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Mon, 6 Apr 2026 13:21:04 -0400 Subject: [PATCH 273/350] fix building --- src/dusk/imgui/ImGuiMenuGame.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dusk/imgui/ImGuiMenuGame.cpp b/src/dusk/imgui/ImGuiMenuGame.cpp index 3ecfab2490..6ae32ce1a4 100644 --- a/src/dusk/imgui/ImGuiMenuGame.cpp +++ b/src/dusk/imgui/ImGuiMenuGame.cpp @@ -37,7 +37,7 @@ namespace dusk { } if (ImGui::MenuItem("Default Window Size")) { - getSettings().video.enableFullscreen = false; + getSettings().video.enableFullscreen.setValue(false); VISetWindowFullscreen(false); VISetWindowSize(FB_WIDTH * 2, FB_HEIGHT * 2); VICenterWindow(); From abd94ee1adb47498613075acec33d536fcb8efe7 Mon Sep 17 00:00:00 2001 From: Lurs <2795933+Lurs@users.noreply.github.com> Date: Mon, 6 Apr 2026 09:21:26 +0200 Subject: [PATCH 274/350] double heaps to fix Midna's eye (#106) and various other error messages I got. Also updating aurora. --- extern/aurora | 2 +- libs/JSystem/src/J3DGraphLoader/J3DModelLoader.cpp | 2 +- src/d/actor/d_a_alink_swindow.inc | 3 +++ src/d/actor/d_a_player.cpp | 7 +++---- src/d/d_k_wmark.cpp | 4 ++++ src/d/d_kankyo.cpp | 4 ++++ src/f_op/f_op_actor_mng.cpp | 3 +++ 7 files changed, 19 insertions(+), 6 deletions(-) diff --git a/extern/aurora b/extern/aurora index b17da31593..5a23bcbd5e 160000 --- a/extern/aurora +++ b/extern/aurora @@ -1 +1 @@ -Subproject commit b17da315932b85d7d708eaf3e44914f70045a44e +Subproject commit 5a23bcbd5e758c3a854ebda2c0bd8332b346e1ff diff --git a/libs/JSystem/src/J3DGraphLoader/J3DModelLoader.cpp b/libs/JSystem/src/J3DGraphLoader/J3DModelLoader.cpp index 72a48007c0..3d064e33de 100644 --- a/libs/JSystem/src/J3DGraphLoader/J3DModelLoader.cpp +++ b/libs/JSystem/src/J3DGraphLoader/J3DModelLoader.cpp @@ -242,7 +242,7 @@ void J3DModelLoader::setupBBoardInfo() { J3DMaterial* mesh = mpModelData->getJointNodePointer(i)->getMesh(); if (mesh != NULL) { u32 shape_index = mesh->getShape()->getIndex(); - u16* index_table = JSUConvertOffsetToPtr(mpShapeBlock, + BE(u16)* index_table = JSUConvertOffsetToPtr(mpShapeBlock, (uintptr_t)mpShapeBlock->mpIndexTable); J3DShapeInitData* shape_init_data = JSUConvertOffsetToPtr(mpShapeBlock, diff --git a/src/d/actor/d_a_alink_swindow.inc b/src/d/actor/d_a_alink_swindow.inc index cf3593dfce..81c7a1ca18 100644 --- a/src/d/actor/d_a_alink_swindow.inc +++ b/src/d/actor/d_a_alink_swindow.inc @@ -40,6 +40,9 @@ void daAlink_c::setOriginalHeap(JKRExpHeap** i_ppheap, u32 i_size) { u32 var_r29 = 0x90; u32 var_r28 = 0x10; u32 size = ROUND(i_size, 16); +#if TARGET_PC + size *= 2; +#endif JKRHeap* parent = mDoExt_getGameHeap(); JKRExpHeap* heap = JKRExpHeap::create(size + (var_r29 + var_r28), parent, true); diff --git a/src/d/actor/d_a_player.cpp b/src/d/actor/d_a_player.cpp index f38fe15a3b..43646380b3 100644 --- a/src/d/actor/d_a_player.cpp +++ b/src/d/actor/d_a_player.cpp @@ -237,11 +237,7 @@ void* daPy_anmHeap_c::mallocBuffer() { return mBuffer; } -#if TARGET_PC void daPy_anmHeap_c::createHeap(daPy_anmHeap_c::daAlinkHEAP_TYPE i_heapType, const char* name) { -#else -void daPy_anmHeap_c::createHeap(daPy_anmHeap_c::daAlinkHEAP_TYPE i_heapType, const char* name) { -#endif u32 size; if (i_heapType == 4) { @@ -255,6 +251,9 @@ void daPy_anmHeap_c::createHeap(daPy_anmHeap_c::daAlinkHEAP_TYPE i_heapType, con } else { size = 0xA0; } +#if TARGET_PC + size *= 2; +#endif char* tmpWork; mDoExt_transAnmBas* tmpTransBas; diff --git a/src/d/d_k_wmark.cpp b/src/d/d_k_wmark.cpp index 379b55f532..3894e965c3 100644 --- a/src/d/d_k_wmark.cpp +++ b/src/d/d_k_wmark.cpp @@ -33,7 +33,11 @@ int dkWmark_c::create() { mColorType = this->parameters; } +#if TARGET_PC + mpHeap = mDoExt_createSolidHeapFromGameToCurrent(0x1100, 0x20); +#else mpHeap = mDoExt_createSolidHeapFromGameToCurrent(0x880, 0x20); +#endif if (mpHeap != NULL) { JKRHEAP_NAME(mpHeap, "dkWmark_c::mpHeap"); J3DModelData* modelData = (J3DModelData*)dComIfG_getObjectRes("Alink", 0x23); diff --git a/src/d/d_kankyo.cpp b/src/d/d_kankyo.cpp index 50f9d716fc..9326555777 100644 --- a/src/d/d_kankyo.cpp +++ b/src/d/d_kankyo.cpp @@ -1175,7 +1175,11 @@ static void undwater_init() { J3DModelData* modelData2 = (J3DModelData*)dComIfG_getObjectRes("Always", 0x1D); JUT_ASSERT(1867, modelData2 != NULL); +#if TARGET_PC + g_env_light.undwater_ef_heap = mDoExt_createSolidHeapFromGameToCurrent(0xC00, 0x20); +#else g_env_light.undwater_ef_heap = mDoExt_createSolidHeapFromGameToCurrent(0x600, 0x20); +#endif JKRHEAP_NAME(g_env_light.undwater_ef_heap, "g_env_light.undwater_ef_heap"); if (g_env_light.undwater_ef_heap != NULL) { diff --git a/src/f_op/f_op_actor_mng.cpp b/src/f_op/f_op_actor_mng.cpp index 36217076bf..dca07f98ba 100644 --- a/src/f_op/f_op_actor_mng.cpp +++ b/src/f_op/f_op_actor_mng.cpp @@ -730,6 +730,9 @@ u8 var_r30 = fopAcM::HeapAdjustEntry; #endif u32 size = i_size & 0xFFFFFF; +#if TARGET_PC + size *= 2; +#endif bool result = fopAcM_entrySolidHeap_(i_actor, i_heapCallback, size); #if DEBUG fopAcM::HeapDummyCheck = var_r29; From 5d5b1a629d39d819573aa47abfca948e13524d89 Mon Sep 17 00:00:00 2001 From: Lurs <2795933+Lurs@users.noreply.github.com> Date: Mon, 6 Apr 2026 21:13:23 +0200 Subject: [PATCH 275/350] Fix "[NEW] JKRHeap (d_a_bg Anms) FULL! Fallback to malloc for size XXX" errors/warnings --- src/d/d_stage.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/d/d_stage.cpp b/src/d/d_stage.cpp index 057bcdf4ff..f3cdfed17b 100644 --- a/src/d/d_stage.cpp +++ b/src/d/d_stage.cpp @@ -393,13 +393,7 @@ static void dummy1(dStage_roomControl_c* roomControl) { JKRExpHeap* dStage_roomControl_c::createMemoryBlock(int i_blockIdx, u32 i_heapSize) { #if TARGET_PC - // Cave of Ordeals crashes around floor 29 due to no free heap space - // Increasing the size here avoids that, though its ugly. maybe TODO a better fix - if (strcmp(dComIfGp_getStartStageName(), "D_SB01") == 0) { - u32 prev = i_heapSize; - i_heapSize *= 2; - DuskLog.warn("Doubling heap size for D_SB01... ({}) -> ({})", prev, i_heapSize); - } + i_heapSize *= 2; #endif if (mMemoryBlock[i_blockIdx] == NULL) { From 88f8433767cdf7afda89b68efc1590418f3df46a Mon Sep 17 00:00:00 2001 From: Lurs <2795933+Lurs@users.noreply.github.com> Date: Mon, 6 Apr 2026 22:09:50 +0200 Subject: [PATCH 276/350] Fix "[ERROR] memory free error!!" message while leaving options menu --- src/d/d_menu_window.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/d/d_menu_window.cpp b/src/d/d_menu_window.cpp index 3e173049bd..e9131462c3 100644 --- a/src/d/d_menu_window.cpp +++ b/src/d/d_menu_window.cpp @@ -1507,7 +1507,11 @@ void dMw_c::checkMemSize() { OS_REPORT("memory check ===> diff ==> %d, start ==> %d, now ==> %d\n", diff, mMemSize, now_size); +#if TARGET_PC + if (diff > 0x40) { +#else if (diff > 0x20) { +#endif OSReport_Error("memory free error!!\n"); } mMemSize = 0; From 06e10fb2cbf309776dc907d7fb970b96940f7a72 Mon Sep 17 00:00:00 2001 From: Lurs <2795933+Lurs@users.noreply.github.com> Date: Mon, 6 Apr 2026 23:08:34 +0200 Subject: [PATCH 277/350] Use HEAP_SIZE() --- src/d/d_k_wmark.cpp | 7 ++----- src/d/d_kankyo.cpp | 7 ++----- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/d/d_k_wmark.cpp b/src/d/d_k_wmark.cpp index 3894e965c3..b93ab14211 100644 --- a/src/d/d_k_wmark.cpp +++ b/src/d/d_k_wmark.cpp @@ -6,6 +6,7 @@ #include "d/dolzel.h" // IWYU pragma: keep #include "d/d_k_wmark.h" +#include "dusk/memory.h" #include "JSystem/J3DGraphBase/J3DMaterial.h" #include "SSystem/SComponent/c_math.h" #include "d/actor/d_a_player.h" @@ -33,11 +34,7 @@ int dkWmark_c::create() { mColorType = this->parameters; } -#if TARGET_PC - mpHeap = mDoExt_createSolidHeapFromGameToCurrent(0x1100, 0x20); -#else - mpHeap = mDoExt_createSolidHeapFromGameToCurrent(0x880, 0x20); -#endif + mpHeap = mDoExt_createSolidHeapFromGameToCurrent(HEAP_SIZE(0x880, 0x1100), 0x20); if (mpHeap != NULL) { JKRHEAP_NAME(mpHeap, "dkWmark_c::mpHeap"); J3DModelData* modelData = (J3DModelData*)dComIfG_getObjectRes("Alink", 0x23); diff --git a/src/d/d_kankyo.cpp b/src/d/d_kankyo.cpp index 9326555777..1652f3c2a0 100644 --- a/src/d/d_kankyo.cpp +++ b/src/d/d_kankyo.cpp @@ -1,6 +1,7 @@ #include "d/dolzel.h" // IWYU pragma: keep #include "d/d_kankyo.h" +#include "dusk/memory.h" #ifdef __REVOLUTION_SDK__ #include #else @@ -1175,11 +1176,7 @@ static void undwater_init() { J3DModelData* modelData2 = (J3DModelData*)dComIfG_getObjectRes("Always", 0x1D); JUT_ASSERT(1867, modelData2 != NULL); -#if TARGET_PC - g_env_light.undwater_ef_heap = mDoExt_createSolidHeapFromGameToCurrent(0xC00, 0x20); -#else - g_env_light.undwater_ef_heap = mDoExt_createSolidHeapFromGameToCurrent(0x600, 0x20); -#endif + g_env_light.undwater_ef_heap = mDoExt_createSolidHeapFromGameToCurrent(HEAP_SIZE(0x600, 0xC00), 0x20); JKRHEAP_NAME(g_env_light.undwater_ef_heap, "g_env_light.undwater_ef_heap"); if (g_env_light.undwater_ef_heap != NULL) { From 9bbf83ec185ee566542a4d14d6690b846b64e92e Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Mon, 6 Apr 2026 23:15:56 +0200 Subject: [PATCH 278/350] Fix cCcS layout being interpreted inconsistently across TUs Fixes #245 Might be the cause of #176 too but I wasn't able to repro that in either case, so can't confirm. --- include/SSystem/SComponent/c_cc_s.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/SSystem/SComponent/c_cc_s.h b/include/SSystem/SComponent/c_cc_s.h index 62815fb3b6..525781279b 100644 --- a/include/SSystem/SComponent/c_cc_s.h +++ b/include/SSystem/SComponent/c_cc_s.h @@ -15,7 +15,7 @@ public: /* 0x0400 */ cCcD_Obj* mpObjTg[0x300]; /* 0x1000 */ cCcD_Obj* mpObjCo[0x100]; /* 0x1400 */ cCcD_Obj* mpObj[0x500]; -#if DEBUG +#if TARGET_PC || DEBUG /* 0x2800 */ u32 m_debug_code; #endif /* 0x2800 */ u16 mObjAtCount; From 8065ff6eb6e3a06459890f96a1277ab97b659745 Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Mon, 6 Apr 2026 19:08:10 -0400 Subject: [PATCH 279/350] Accelerate saving, copying & erasing save files --- src/d/d_file_select.cpp | 4 ++++ src/d/d_menu_save.cpp | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/d/d_file_select.cpp b/src/d/d_file_select.cpp index c739b39db2..f63403d886 100644 --- a/src/d/d_file_select.cpp +++ b/src/d/d_file_select.cpp @@ -70,7 +70,11 @@ dFs_HIO_c::dFs_HIO_c() { select_icon_appear_frames = 5; appear_display_wait_frames = 15; field_0x000d = 15; + #if TARGET_PC + card_wait_frames = 0; + #else card_wait_frames = 90; + #endif test_frame_counts[0] = 1.11f; test_frame_counts[1] = 1.11f; test_frame_counts[2] = 1.11f; diff --git a/src/d/d_menu_save.cpp b/src/d/d_menu_save.cpp index 7ecc41f823..eb7b6a35b8 100644 --- a/src/d/d_menu_save.cpp +++ b/src/d/d_menu_save.cpp @@ -56,7 +56,11 @@ static dMs_HIO_c g_msHIO; dMs_HIO_c::dMs_HIO_c() { mDisplayWaitFrames = 15; + #if TARGET_PC + mCardWaitFrames = 0; + #else mCardWaitFrames = 90; + #endif mEffectDispFrames = 5; mCharSwitchFrames = 5; mSelectIcon = 5; From d3142f779ba8d5fdec9c0a573f913963a42c085b Mon Sep 17 00:00:00 2001 From: Jeffrey Crowell Date: Mon, 6 Apr 2026 20:33:27 -0400 Subject: [PATCH 280/350] use "Cmd" instead of "Ctrl" for hotkeys on Apple (#239) --- include/dusk/hotkeys.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/dusk/hotkeys.h b/include/dusk/hotkeys.h index c40f35925f..6e98d62521 100644 --- a/include/dusk/hotkeys.h +++ b/include/dusk/hotkeys.h @@ -3,7 +3,11 @@ namespace dusk::hotkeys { +#if __APPLE__ +constexpr const char* DO_RESET = "Cmd+R"; +#else constexpr const char* DO_RESET = "Ctrl+R"; +#endif constexpr const char* TOGGLE_FULLSCREEN = "F11"; From 1ec39a12af6b930e712807c229d70097f3dd7b57 Mon Sep 17 00:00:00 2001 From: CraftyBoss Date: Mon, 6 Apr 2026 17:43:23 -0700 Subject: [PATCH 281/350] update aurora --- extern/aurora | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/aurora b/extern/aurora index 50163edfe7..5a23bcbd5e 160000 --- a/extern/aurora +++ b/extern/aurora @@ -1 +1 @@ -Subproject commit 50163edfe76694f6c920bbd74febf1dd22a35618 +Subproject commit 5a23bcbd5e758c3a854ebda2c0bd8332b346e1ff From f43e4452b4d2d1f76c6ba68ca7e93f7405dc85ac Mon Sep 17 00:00:00 2001 From: CraftyBoss Date: Mon, 6 Apr 2026 18:26:33 -0700 Subject: [PATCH 282/350] fast spinner cheat impl just makes it so holding R while on the spinner increases its speed to 60 units --- include/dusk/settings.h | 1 + src/dusk/imgui/ImGuiMenuEnhancements.cpp | 5 +++++ src/dusk/settings.cpp | 2 ++ src/f_ap/f_ap_game.cpp | 10 ++++++++++ 4 files changed, 18 insertions(+) diff --git a/include/dusk/settings.h b/include/dusk/settings.h index cf4d79f6ac..0cb3ba60b7 100644 --- a/include/dusk/settings.h +++ b/include/dusk/settings.h @@ -58,6 +58,7 @@ struct UserSettings { // Cheats ConfigVar enableFastIronBoots; ConfigVar canTransformAnywhere; + ConfigVar fastSpinner; // Technical ConfigVar restoreWiiGlitches; diff --git a/src/dusk/imgui/ImGuiMenuEnhancements.cpp b/src/dusk/imgui/ImGuiMenuEnhancements.cpp index a7a1e1d698..e9404e2476 100644 --- a/src/dusk/imgui/ImGuiMenuEnhancements.cpp +++ b/src/dusk/imgui/ImGuiMenuEnhancements.cpp @@ -101,6 +101,11 @@ namespace dusk { ImGui::SetTooltip("Allows you to transform even if NPCs are looking"); } + config::ImGuiCheckbox("Fast Spinner", getSettings().game.fastSpinner); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Speeds up Spinner movement when holding R."); + } + ImGui::EndMenu(); } diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index 278f783dbf..32c6a5beb4 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -46,6 +46,7 @@ UserSettings g_userSettings = { // Cheats .enableFastIronBoots {"game.enableFastIronBoots", false}, .canTransformAnywhere {"game.canTransformAnywhere", false}, + .fastSpinner {"game.fastSpinner", false}, // Technical .restoreWiiGlitches {"game.restoreWiiGlitches", false}, @@ -93,6 +94,7 @@ void registerSettings() { Register(g_userSettings.game.noLowHpSound); Register(g_userSettings.game.midnasLamentNonStop); Register(g_userSettings.game.enableTurboKeybind); + Register(g_userSettings.game.fastSpinner); } // Transient settings diff --git a/src/f_ap/f_ap_game.cpp b/src/f_ap/f_ap_game.cpp index 1cbbb7449f..b1edb7fa23 100644 --- a/src/f_ap/f_ap_game.cpp +++ b/src/f_ap/f_ap_game.cpp @@ -738,6 +738,16 @@ void fapGm_Execute() { dynamic_cast(link)->handleQuickTransform(); } } + + if (dusk::getSettings().game.fastSpinner && mDoCPd_c::getHoldR(PAD_1)) { + if (const auto link = g_dComIfG_gameInfo.play.getPlayer(0)) { + auto spinnerActor = (fopAc_ac_c*)dynamic_cast(link)->getSpinnerActor(); + if (spinnerActor) { + if (spinnerActor->speedF < 60.f) + spinnerActor->speedF += 2.f; + } + } + } #endif fpcM_Management(NULL, fapGm_After); From 7d9d041b7d3c6a61f894097658f9b3d3a6ad68d4 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Mon, 6 Apr 2026 21:25:50 -0600 Subject: [PATCH 283/350] Update aurora & fixes for OS header changes --- extern/aurora | 2 +- libs/JSystem/src/JUtility/JUTException.cpp | 16 ++++++++++++++++ src/dusk/OSContext.cpp | 9 ++------- src/dusk/OSThread.cpp | 6 +++--- src/dusk/stubs.cpp | 4 ++-- src/m_Do/m_Do_machine.cpp | 2 ++ 6 files changed, 26 insertions(+), 13 deletions(-) diff --git a/extern/aurora b/extern/aurora index 5a23bcbd5e..854ce1c178 160000 --- a/extern/aurora +++ b/extern/aurora @@ -1 +1 @@ -Subproject commit 5a23bcbd5e758c3a854ebda2c0bd8332b346e1ff +Subproject commit 854ce1c178756ca307137c81afc3cc626fce5fb3 diff --git a/libs/JSystem/src/JUtility/JUTException.cpp b/libs/JSystem/src/JUtility/JUTException.cpp index 509acbfb3e..514ec6dd8a 100644 --- a/libs/JSystem/src/JUtility/JUTException.cpp +++ b/libs/JSystem/src/JUtility/JUTException.cpp @@ -101,6 +101,9 @@ JUTException* JUTException::create(JUTDirectPrint* directPrint) { OSMessage JUTException::sMessageBuffer[1] = {0}; void* JUTException::run() { +#ifdef TARGET_PC + return NULL; +#else u32 msr = PPCMfmsr(); msr &= ~0x0900; PPCMtmsr(msr); @@ -132,6 +135,7 @@ void* JUTException::run() { sErrorManager->mDirectPrint->changeFrameBuffer(mFrameMemory); sErrorManager->printContext(error, context, r24, r23); } +#endif } void* JUTException::sConsoleBuffer; @@ -145,6 +149,7 @@ u32 JUTException::msr; u32 JUTException::fpscr; void JUTException::errorHandler(OSError error, OSContext* context, u32 param_3, u32 param_4) { +#ifndef TARGET_PC msr = PPCMfmsr(); fpscr = context->fpscr; OSFillFPUContext(context); @@ -165,6 +170,7 @@ void JUTException::errorHandler(OSError error, OSContext* context, u32 param_3, OSSendMessage(&sMessageQueue, &exCallbackObject, OS_MESSAGE_BLOCK); OSEnableScheduler(); OSYieldThread(); +#endif } void JUTException::panic_f_va(char const* file, int line, char const* format, va_list args) { @@ -234,6 +240,7 @@ void JUTException::showFloatSub(int index, f32 value) { } void JUTException::showFloat(OSContext* context) { +#ifndef TARGET_PC if (!sConsole) { return; } @@ -251,6 +258,7 @@ void JUTException::showFloat(OSContext* context) { sConsole->print(" "); showFloatSub(21, context->fpr[21]); sConsole->print("\n"); +#endif } bool JUTException::searchPartialModule(u32 address, u32* module_id, u32* section_id, @@ -333,6 +341,7 @@ void JUTException::showStack(OSContext* context) { } void JUTException::showMainInfo(u16 error, OSContext* context, u32 dsisr, u32 dar) { +#ifndef TARGET_PC if (!sConsole) { return; } @@ -391,9 +400,11 @@ void JUTException::showMainInfo(u16 error, OSContext* context, u32 dsisr, u32 da } sConsole->print_f("SRR0: %08XH SRR1:%08XH\n", context->srr0, context->srr1); sConsole->print_f("DSISR: %08XH DAR: %08XH\n", dsisr, dar); +#endif } void JUTException::showGPR(OSContext* context) { +#ifndef TARGET_PC if (!sConsole) { return; } @@ -404,6 +415,7 @@ void JUTException::showGPR(OSContext* context) { context->gpr[i + 11], i + 22, context->gpr[i + 22]); } sConsole->print_f("R%02d:%08XH R%02d:%08XH\n", 10, context->gpr[10], 21, context->gpr[21]); +#endif } bool JUTException::showMapInfo_subroutine(u32 address, bool begin_with_newline) { @@ -454,6 +466,7 @@ bool JUTException::showMapInfo_subroutine(u32 address, bool begin_with_newline) } void JUTException::showGPRMap(OSContext* context) { +#ifndef TARGET_PC if (!sConsole) { return; } @@ -480,9 +493,11 @@ void JUTException::showGPRMap(OSContext* context) { if (!found_address_register) { sConsole->print(" no register which seem to address.\n"); } +#endif } void JUTException::showSRR0Map(OSContext* context) { +#ifndef TARGET_PC if (!sConsole) { return; } @@ -497,6 +512,7 @@ void JUTException::showSRR0Map(OSContext* context) { } JUTConsoleManager::getManager()->drawDirect(true); } +#endif } void JUTException::printDebugInfo(JUTException::EInfoPage page, OSError error, OSContext* context, diff --git a/src/dusk/OSContext.cpp b/src/dusk/OSContext.cpp index 9aa6932e52..d2bb1803ea 100644 --- a/src/dusk/OSContext.cpp +++ b/src/dusk/OSContext.cpp @@ -23,16 +23,11 @@ void OSSetCurrentContext(OSContext* context) { } void OSClearContext(OSContext* context) { - if (!context) return; - context->mode = 0; - context->state = 0; + // No-op on PC } void OSInitContext(OSContext* context, u32 pc, u32 newsp) { - if (!context) return; - memset(context, 0, sizeof(OSContext)); - context->srr0 = pc; - context->gpr[1] = newsp; + // No-op on PC } void OSDumpContext(OSContext* context) { diff --git a/src/dusk/OSThread.cpp b/src/dusk/OSThread.cpp index ea9c568eef..af339e8656 100644 --- a/src/dusk/OSThread.cpp +++ b/src/dusk/OSThread.cpp @@ -101,7 +101,7 @@ static thread_local OSThread* tls_currentThread = nullptr; static OSThread sDefaultThread; static u8 sDefaultStack[64 * 1024]; -static u32 sDefaultStackEnd = OS_THREAD_STACK_MAGIC; +static u8 sDefaultStackEnd = OS_THREAD_STACK_MAGIC; // Global interrupt mutex (coarse-grained lock replacing interrupt disable) // Lazy-initialized to avoid DLL static init crashes @@ -237,7 +237,7 @@ int OSCreateThread(OSThread* thread, void* (*func)(void*), void* param, // Stack (stack points to TOP on GameCube) thread->stackBase = (u8*)stack; - thread->stackEnd = (u32*)((uintptr_t)stack - stackSize); + thread->stackEnd = (u8*)((uintptr_t)stack - stackSize); *thread->stackEnd = OS_THREAD_STACK_MAGIC; OSClearContext(&thread->context); @@ -496,7 +496,7 @@ void OSDetachThread(OSThread* thread) { OSWakeupThread(&thread->queueJoin); } -int OSJoinThread(OSThread* thread, void* val) { +BOOL OSJoinThread(OSThread* thread, void** val) { if (!thread) return 0; if (!(thread->attr & OS_THREAD_ATTR_DETACH)) { diff --git a/src/dusk/stubs.cpp b/src/dusk/stubs.cpp index 2cb385966e..e655ed6d7d 100644 --- a/src/dusk/stubs.cpp +++ b/src/dusk/stubs.cpp @@ -94,7 +94,7 @@ static void ClearMsgQueueMap() { map.clear(); } -void OSInitMessageQueue(OSMessageQueue* mq, void* msgArray, s32 msgCount) { +void OSInitMessageQueue(OSMessageQueue* mq, OSMessage* msgArray, s32 msgCount) { if (!mq) return; mq->queueSend.head = mq->queueSend.tail = nullptr; mq->queueReceive.head = mq->queueReceive.tail = nullptr; @@ -128,7 +128,7 @@ int OSSendMessage(OSMessageQueue* mq, void* msg, s32 flags) { return 1; } -int OSReceiveMessage(OSMessageQueue* mq, void* msg, s32 flags) { +BOOL OSReceiveMessage(OSMessageQueue* mq, OSMessage* msg, s32 flags) { if (!mq) return 0; PCMessageQueueData& data = GetMsgQueueData(mq); diff --git a/src/m_Do/m_Do_machine.cpp b/src/m_Do/m_Do_machine.cpp index d5258b6f98..fdab588957 100644 --- a/src/m_Do/m_Do_machine.cpp +++ b/src/m_Do/m_Do_machine.cpp @@ -592,6 +592,7 @@ void myExceptionCallback(u16, OSContext*, u32, u32) { VIFlush(); } +#ifndef TARGET_PC static void fault_callback_scroll(u16, OSContext* p_context, u32, u32) { JUTException* manager = JUTException::getManager(); JUTConsole* exConsole = manager->getConsole(); @@ -714,6 +715,7 @@ static void fault_callback_scroll(u16, OSContext* p_context, u32, u32) { } while (true); } while (true); } +#endif static void dummy_string() { DEAD_STRING("\x1B[32m%-24s = size=%d KB\n\x1B[m"); From 0084ccf5ba6b3414d7ba833961d481ad8fefe1fb Mon Sep 17 00:00:00 2001 From: madeline Date: Mon, 6 Apr 2026 20:35:54 -0700 Subject: [PATCH 284/350] fix basically all known audio bugs fixes #148 fixes #146 fixes #130 --- libs/JSystem/src/JAudio2/JASSeqParser.cpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/libs/JSystem/src/JAudio2/JASSeqParser.cpp b/libs/JSystem/src/JAudio2/JASSeqParser.cpp index 7b417f2c82..376ec22b4c 100644 --- a/libs/JSystem/src/JAudio2/JASSeqParser.cpp +++ b/libs/JSystem/src/JAudio2/JASSeqParser.cpp @@ -884,6 +884,19 @@ s32 JASSeqParser::cmdPrintf(JASTrack* param_0, u32* param_1) { s32 JASSeqParser::execNoteOnGate(JASTrack* param_0, u32 param_1, u32 param_2, u32 param_3, u32 param_4) { JASSeqCtrl* seqCtrl = param_0->getSeqCtrl(); + + int r31 = 0; + +#if TARGET_PC + // CodeWarrior on PPC allocates MSB-first for bit fields i think, which is stupid + // so in reality these are stored in bit 6 and 7 not but 0 and 1, do this to get around it + if (param_4 & 0x40) { + r31 |= 2; + } + if (param_4 & 0x80) { + r31 |= 1; + } +#else // likely fake match, this may use some actual union defined somewhere else union { u8 val; @@ -893,13 +906,13 @@ s32 JASSeqParser::execNoteOnGate(JASTrack* param_0, u32 param_1, u32 param_2, u3 } bits; } tmp; tmp.val = param_4; - int r31 = 0; if (tmp.bits.bit1) { r31 |= 2; } if (tmp.bits.bit0) { r31 |= 1; } +#endif if (param_3 == 0) { r31 |= 4; } From d6d5c318aea8809dc3fc8185670075a3d8041f99 Mon Sep 17 00:00:00 2001 From: madeline Date: Mon, 6 Apr 2026 07:53:46 -0700 Subject: [PATCH 285/350] runtime data asset loader --- files.cmake | 2 + include/dusk/dvd_asset.hpp | 22 ++++++++ src/d/actor/d_a_mant.cpp | 22 ++++++++ src/d/actor/d_a_player.cpp | 6 +++ src/d/actor/d_flower.inc | 35 ++++++++++++ src/d/actor/d_grass.inc | 29 ++++++++-- src/d/d_error_msg.cpp | 17 ++++++ src/dusk/dvd_asset.cpp | 106 +++++++++++++++++++++++++++++++++++++ src/m_Do/m_Do_ext.cpp | 3 ++ 9 files changed, 239 insertions(+), 3 deletions(-) create mode 100644 include/dusk/dvd_asset.hpp create mode 100644 src/dusk/dvd_asset.cpp diff --git a/files.cmake b/files.cmake index 7d07408613..e9149a1777 100644 --- a/files.cmake +++ b/files.cmake @@ -1333,6 +1333,8 @@ set(DOLPHIN_FILES set(DUSK_FILES include/dusk/endian_gx.hpp include/dusk/config.hpp + include/dusk/dvd_asset.hpp + src/dusk/dvd_asset.cpp src/d/actor/d_a_alink_dusk.cpp src/dusk/asserts.cpp src/dusk/config.cpp diff --git a/include/dusk/dvd_asset.hpp b/include/dusk/dvd_asset.hpp new file mode 100644 index 0000000000..3c36b75195 --- /dev/null +++ b/include/dusk/dvd_asset.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "dolphin/types.h" + +namespace dusk { + +/** + * Load bytes from the main DOL by GameCube virtual address + */ +bool LoadDolAsset(void* dst, u32 virtualAddress, s32 size); + +/** + * Load bytes from a REL file in the ISO filesystem + */ +void* LoadRelAsset(const char* dvdPath, s32 offset, s32 size); + +/** + * Load bytes from a REL inside RELS.arc + */ +bool LoadArchivedRelAsset(void* dst, u32 memType, const char* relFileName, s32 offset, s32 size); + +} // namespace dusk diff --git a/src/d/actor/d_a_mant.cpp b/src/d/actor/d_a_mant.cpp index a3b48f1098..1760db60fa 100644 --- a/src/d/actor/d_a_mant.cpp +++ b/src/d/actor/d_a_mant.cpp @@ -10,11 +10,21 @@ #include "d/actor/d_a_b_gnd.h" #include "d/d_com_inf_game.h" +#if TARGET_PC +#include "dusk/dvd_asset.hpp" +static u8* l_Egnd_mantTEX_get() { static u8* p = (u8*)dusk::LoadRelAsset("/rel/Final/Release/d_a_mant.rel", 0x1C00, 0x4000); return p; } +static u8* l_Egnd_mantTEX_U_get() { static u8* p = (u8*)dusk::LoadRelAsset("/rel/Final/Release/d_a_mant.rel", 0x5C00, 0x4000); return p; } +static u8* l_Egnd_mantPAL_get() { static u8* p = (u8*)dusk::LoadRelAsset("/rel/Final/Release/d_a_mant.rel", 0x9C00, 0x60); return p; } +#define l_Egnd_mantTEX (l_Egnd_mantTEX_get()) +#define l_Egnd_mantTEX_U (l_Egnd_mantTEX_U_get()) +#define l_Egnd_mantPAL (l_Egnd_mantPAL_get()) +#else #include "assets/l_Egnd_mantTEX.h" #include "assets/l_Egnd_mantTEX_U.h" #include "assets/l_Egnd_mantPAL.h" +#endif #include "d/d_s_play.h" static u32 l_pos[507] = { @@ -239,20 +249,32 @@ static u32 l_texCoord[338] = { 0x3F800000, 0x00000000, }; +#if TARGET_PC +static u8* l_Egnd_mantDL_get() { static u8* p = (u8*)dusk::LoadRelAsset("/rel/Final/Release/d_a_mant.rel", 0xA9A0, 0x3EC); return p; } +#define l_Egnd_mantDL (l_Egnd_mantDL_get()) +#else #include "assets/l_Egnd_mantDL.h" +#endif +#if !TARGET_PC static void* pal_d = (void*)&l_Egnd_mantPAL; static void* tex_d[2] = { (void*)&l_Egnd_mantTEX, (void*)&l_Egnd_mantTEX_U, }; +#endif static char lbl_277_bss_0; void daMant_packet_c::draw() { +#if TARGET_PC + void* image = l_Egnd_mantTEX; + void* lut = l_Egnd_mantPAL; +#else void* image = tex_d[0]; void* lut = pal_d; +#endif j3dSys.reinitGX(); GXSetNumIndStages(0); diff --git a/src/d/actor/d_a_player.cpp b/src/d/actor/d_a_player.cpp index 43646380b3..d6f7c54a49 100644 --- a/src/d/actor/d_a_player.cpp +++ b/src/d/actor/d_a_player.cpp @@ -367,7 +367,13 @@ JKRHeap* daPy_anmHeap_c::setAnimeHeap() { } #if !PLATFORM_WII +#if TARGET_PC +#include "dusk/dvd_asset.hpp" +static const u8* l_sightDL_get() { static u8 buf[0x89]; static bool _ = (dusk::LoadDolAsset(buf, 0x803BA0C0, 0x89), true); return buf; } +#define l_sightDL (l_sightDL_get()) +#else #include "assets/l_sightDL__d_a_player.h" +#endif void daPy_sightPacket_c::draw() { TGXTexObj texObj; diff --git a/src/d/actor/d_flower.inc b/src/d/actor/d_flower.inc index 762b1e02e9..93a7e35097 100644 --- a/src/d/actor/d_flower.inc +++ b/src/d/actor/d_flower.inc @@ -13,7 +13,13 @@ const u16 l_J_Ohana00_64TEX__width = 63; const u16 l_J_Ohana00_64TEX__height = 63; #endif +#if TARGET_PC +#include "dusk/dvd_asset.hpp" +static u8* l_J_Ohana00_64TEX_get() { static u8 buf[0x800]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x9060, 0x800), true); return buf; } +#define l_J_Ohana00_64TEX (l_J_Ohana00_64TEX_get()) +#else #include "assets/l_J_Ohana00_64TEX.h" +#endif static u8 l_flowerPos[708] = { 0xC0, 0x8C, 0x2C, 0xF7, 0x42, 0x05, 0xBC, 0xDF, 0xC1, 0xA1, 0x00, 0x70, 0xBF, 0x50, 0x51, 0xB9, @@ -104,6 +110,16 @@ static u8 l_flowerTexCoord[] = { 0x3E, 0xA7, 0x67, 0x4D, 0x3C, 0x14, 0x46, 0x74, 0x3E, 0xA7, 0x73, 0xE2, 0xBC, 0x2F, 0x46, 0xAA, 0x3E, 0xA7, 0x72, 0xD6, 0xBD, 0x2F, 0x46, 0xAA}; +#if TARGET_PC +static u8* l_J_hana00DL_get() { static u8 buf[0x150]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x9D20, 0x150), true); return buf; } +static u8* l_J_hana00_cDL_get() { static u8 buf[0xDE]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x9E80, 0xDE), true); return buf; } +static u8* l_matDL_get() { static u8 buf[0x99]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x9F60, 0x99), true); return buf; } +static u8* l_matLight4DL_get() { static u8 buf[0x99]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0xA000, 0x99), true); return buf; } +#define l_J_hana00DL (l_J_hana00DL_get()) +#define l_J_hana00_cDL (l_J_hana00_cDL_get()) +#define l_matDL (l_matDL_get()) +#define l_matLight4DL (l_matLight4DL_get()) +#else #include "assets/l_J_hana00DL.h" #include "assets/l_J_hana00_cDL.h" @@ -113,6 +129,7 @@ l_matDL__d_a_grass(l_J_Ohana00_64TEX) #include "assets/l_matLight4DL.h" l_matLight4DL(l_J_Ohana00_64TEX) +#endif #if TARGET_PC const u16 l_J_Ohana01_64128_0419TEX__width = 64; @@ -122,7 +139,12 @@ const u16 l_J_Ohana01_64128_0419TEX__width = 63; const u16 l_J_Ohana01_64128_0419TEX__height = 127; #endif +#if TARGET_PC +static u8* l_J_Ohana01_64128_0419TEX_get() { static u8 buf[0x1000]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0xA0A0, 0x1000), true); return buf; } +#define l_J_Ohana01_64128_0419TEX (l_J_Ohana01_64128_0419TEX_get()) +#else #include "assets/l_J_Ohana01_64128_0419TEX.h" +#endif static u8 l_flowerPos2[1224] = { 0x40, 0x25, 0x9F, 0x34, 0x42, 0xC2, 0x95, 0x72, 0xC1, 0x22, 0x34, 0x78, 0x41, 0x4D, 0xF9, 0x63, @@ -249,6 +271,18 @@ static u8 l_flowerTexCoord2[] = { 0x40, 0x1B, 0x7D, 0x52, 0x3F, 0x97, 0xF6, 0xBA, 0x40, 0x04, 0x26, 0x74, 0x3F, 0x80, 0x3F, 0x79, 0x40, 0x1B, 0x7D, 0x52, 0x3F, 0x80, 0x3F, 0x79, 0x40, 0x1B, 0x7D, 0x52, 0x3F, 0x51, 0x10, 0x6F}; +#if TARGET_PC +static u8* l_J_hana01DL_get() { static u8 buf[0x138]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0xB7C0, 0x138), true); return buf; } +static u8* l_J_hana01_c_00DL_get() { static u8 buf[0xDE]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0xB900, 0xDE), true); return buf; } +static u8* l_J_hana01_c_01DL_get() { static u8 buf[0x128]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0xB9E0, 0x128), true); return buf; } +static u8* l_mat2DL_get() { static u8 buf[0x99]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0xBB20, 0x99), true); return buf; } +static u8* l_mat2Light4DL_get() { static u8 buf[0x99]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0xBBC0, 0x99), true); return buf; } +#define l_J_hana01DL (l_J_hana01DL_get()) +#define l_J_hana01_c_00DL (l_J_hana01_c_00DL_get()) +#define l_J_hana01_c_01DL (l_J_hana01_c_01DL_get()) +#define l_mat2DL (l_mat2DL_get()) +#define l_mat2Light4DL (l_mat2Light4DL_get()) +#else #include "assets/l_J_hana01DL.h" #include "assets/l_J_hana01_c_00DL.h" @@ -260,6 +294,7 @@ l_mat2DL__d_a_grass(l_J_Ohana01_64128_0419TEX) #include "assets/l_mat2Light4DL.h" l_mat2Light4DL(l_J_Ohana01_64128_0419TEX) +#endif void dFlower_data_c::WorkCo(fopAc_ac_c* i_hitActor, u32 i_massFlg, int i_roomNo) { cXyz sp8; diff --git a/src/d/actor/d_grass.inc b/src/d/actor/d_grass.inc index 6351cc75fc..3645488b0f 100644 --- a/src/d/actor/d_grass.inc +++ b/src/d/actor/d_grass.inc @@ -13,11 +13,19 @@ const u16 l_M_Hijiki00TEX__width = 31; const u16 l_M_Hijiki00TEX__height = 31; -#include "assets/l_M_kusa05_RGBATEX.h" // ALIGN 32 - const u16 l_M_kusa05_RGBATEX__width = 31; const u16 l_M_kusa05_RGBATEX__height = 31; -#include "assets/l_M_Hijiki00TEX.h" // ALIGN 32 + +#if TARGET_PC +#include "dusk/dvd_asset.hpp" +static u8* l_M_kusa05_RGBATEX_get() { static u8 buf[0x800]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x7680, 0x800), true); return buf; } +static u8* l_M_Hijiki00TEX_get() { static u8 buf[0x800]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x7E80, 0x800), true); return buf; } +#define l_M_kusa05_RGBATEX (l_M_kusa05_RGBATEX_get()) +#define l_M_Hijiki00TEX (l_M_Hijiki00TEX_get()) +#else +#include "assets/l_M_kusa05_RGBATEX.h" // ALIGN 32 +#include "assets/l_M_Hijiki00TEX.h" // ALIGN 32 +#endif static u8 l_pos[960] = { 0x3F, 0x4A, 0x56, 0xEF, 0xC2, 0x20, 0x00, 0x00, 0x41, 0xFB, 0x17, 0xE4, 0x41, 0xAA, 0xBB, 0xEA, @@ -102,6 +110,20 @@ static u8 l_texCoord[160] = { 0x3F, 0x94, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0xBD, 0xC0, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, }; +#if TARGET_PC +static u8* l_M_Kusa_9qDL_get() { static u8 buf[0xCB]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x8B00, 0xCB), true); return buf; } +static u8* l_M_Kusa_9q_cDL_get() { static u8 buf[0xCB]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x8BE0, 0xCB), true); return buf; } +static u8* l_M_TenGusaDL_get() { static u8 buf[0xD4]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x8CC0, 0xD4), true); return buf; } +static u8* l_Tengusa_matDL_get() { static u8 buf[0xA8]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x8DA0, 0xA8), true); return buf; } +static u8* l_kusa9q_matDL_get() { static u8 buf[0xA8]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x8E60, 0xA8), true); return buf; } +static u8* l_kusa9q_l4_matDL_get() { static u8 buf[0xA8]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x8F20, 0xA8), true); return buf; } +#define l_M_Kusa_9qDL (l_M_Kusa_9qDL_get()) +#define l_M_Kusa_9q_cDL (l_M_Kusa_9q_cDL_get()) +#define l_M_TenGusaDL (l_M_TenGusaDL_get()) +#define l_Tengusa_matDL (l_Tengusa_matDL_get()) +#define l_kusa9q_matDL (l_kusa9q_matDL_get()) +#define l_kusa9q_l4_matDL (l_kusa9q_l4_matDL_get()) +#else #include "assets/l_M_Kusa_9qDL.h" #include "assets/l_M_Kusa_9q_cDL.h" @@ -117,6 +139,7 @@ l_kusa9q_matDL(l_M_kusa05_RGBATEX) #include "assets/l_kusa9q_l4_matDL.h" l_kusa9q_l4_matDL(l_M_kusa05_RGBATEX) +#endif void dGrass_data_c::WorkCo(fopAc_ac_c* i_hitActor, u32 i_massFlg, int i_roomNo) { cXyz sp18; diff --git a/src/d/d_error_msg.cpp b/src/d/d_error_msg.cpp index faa40ae543..f1d63b5a1f 100644 --- a/src/d/d_error_msg.cpp +++ b/src/d/d_error_msg.cpp @@ -15,6 +15,22 @@ #include "m_Do/m_Do_Reset.h" #include "m_Do/m_Do_graphic.h" +#if TARGET_PC +#include "dusk/dvd_asset.hpp" +static const u8* black_tex_get() { static u8 buf[0x40]; static bool _ = (dusk::LoadDolAsset(buf, 0x8037B140, 0x40), true); return buf; } +static const u8* msg_data_get() { static u8 buf[0x260]; static bool _ = (dusk::LoadDolAsset(buf, 0x8037B180, 0x260), true); return buf; } +static const u8* font_data_get() { static u8 buf[0x12260]; static bool _ = (dusk::LoadDolAsset(buf, 0x8037B3E0, 0x12260), true); return buf; } +#define black_tex (black_tex_get()) +#define msg_data (msg_data_get()) +#define font_data (font_data_get()) +#if VERSION == VERSION_GCN_PAL +// TODO: find addreses for pal languages +#define msg_data_ge ((const u8*)nullptr) +#define msg_data_fr ((const u8*)nullptr) +#define msg_data_sp ((const u8*)nullptr) +#define msg_data_it ((const u8*)nullptr) +#endif +#else #include "assets/black_tex.h" #include "assets/msg_data.h" #if VERSION == VERSION_GCN_PAL @@ -24,6 +40,7 @@ #include "assets/msg_data_it.h" #endif #include "assets/font_data.h" +#endif #define MSG_READING_DISC 0 #define MSG_COVER_OPEN 1 diff --git a/src/dusk/dvd_asset.cpp b/src/dusk/dvd_asset.cpp new file mode 100644 index 0000000000..bfa30cc941 --- /dev/null +++ b/src/dusk/dvd_asset.cpp @@ -0,0 +1,106 @@ +#include "dusk/dvd_asset.hpp" +#include "dusk/logging.h" +#include "dusk/endian.h" +#include "aurora/dvd.h" +#include "DynamicLink.h" +#include "JSystem/JKernel/JKRArchive.h" +#include "JSystem/JKernel/JKRDvdRipper.h" + +#include + +namespace dusk { + +static const u8* s_dolData = nullptr; // pointer to dol data +static size_t s_dolSize = 0; +struct DolSection { u32 fileOffset, vaddr, size; }; +static DolSection s_dolSections[18]; // 7 text + 11 data +static int s_dolSectionCount = 0; + +static bool EnsureDolParsed() { + if (s_dolData) return true; + + s32 sz = 0; + const u8* p = aurora_dvd_get_dol(sz); + if (!p || sz < 256) { + DuskLog.fatal("dvd_asset: aurora_dvd_get_dol failed (size={})", sz); return false; + } + + s_dolData = p; + s_dolSize = sz; + + const BE(u32)* hdr = (const BE(u32)*)s_dolData; + s_dolSectionCount = 0; + + // 0x00: text file offsets 0x12: text vaddrs 0x24: text sizes + for (int i = 0; i < 7; i++) { + u32 off = hdr[0x00+i], addr = hdr[0x12+i], sz = hdr[0x24+i]; + if (sz > 0 && off > 0) { + s_dolSections[s_dolSectionCount++] = {off, addr, sz}; + } + } + // 0x07: data file offsets 0x19: data vaddrs 0x2B: data sizes + for (int i = 0; i < 11; i++) { + u32 off = hdr[0x07+i], addr = hdr[0x19+i], sz = hdr[0x2B+i]; + if (sz > 0 && off > 0) { + s_dolSections[s_dolSectionCount++] = {off, addr, sz}; + } + } + + return true; +} + +static s32 DolVaToFileOffset(u32 va) { + if (!EnsureDolParsed()) return -1; + for (int i = 0; i < s_dolSectionCount; i++) { + const auto& sec = s_dolSections[i]; + if (va >= sec.vaddr && va < sec.vaddr + sec.size) { + return static_cast(sec.fileOffset + (va - sec.vaddr)); + } + } + DuskLog.fatal("dvd_asset: VA 0x{:08X} not found in any DOL section", va); + return -1; +} + +bool LoadDolAsset(void* dst, u32 virtualAddress, s32 size) { + s32 fileOffset = DolVaToFileOffset(virtualAddress); + if (fileOffset < 0) { + return false; + } + + if (size <= 0 || (size_t)(fileOffset + size) > s_dolSize) { + DuskLog.fatal("dvd_asset: DOL read out of range (offset={:#x} size={:#x} dolSize={})", fileOffset, size, s_dolSize); + return false; + } + + std::memcpy(dst, s_dolData + fileOffset, size); + return true; +} + +void* LoadRelAsset(const char* dvdPath, s32 offset, s32 size) { + void* p = JKRDvdRipper::loadToMainRAM(dvdPath, nullptr, EXPAND_SWITCH_UNKNOWN1, (u32)size, nullptr, JKRDvdRipper::ALLOC_DIRECTION_FORWARD, (u32)offset, nullptr, nullptr); + if (!p) DuskLog.fatal("dvd_asset: failed to load {} (offset={:#x} size={:#x})", dvdPath, offset, size); + return p; +} + +bool LoadArchivedRelAsset(void* dst, u32 memType, const char* relFileName, s32 offset, s32 size) { + // On TARGET_PC, cDyl_InitCallback skips DynamicModuleControl::initialize() due to static linking + // Mount RELS.arc on first use so sArchive is available + static bool s_mountAttempted = false; + if (!DynamicModuleControl::sArchive && !s_mountAttempted) { + s_mountAttempted = true; DynamicModuleControl::initialize(); + } + + if (!DynamicModuleControl::sArchive) { + DuskLog.fatal("dvd_asset: RELS archive not mounted"); return false; + } + + const u8* rel = static_cast(DynamicModuleControl::sArchive->getResource(memType, relFileName)); + if (!rel) { + DuskLog.fatal("dvd_asset: {} not found in RELS archive", relFileName); return false; + } + + std::memcpy(dst, rel + offset, size); + return true; +} + +} // namespace dusk diff --git a/src/m_Do/m_Do_ext.cpp b/src/m_Do/m_Do_ext.cpp index d744d048ba..0593815b1d 100644 --- a/src/m_Do/m_Do_ext.cpp +++ b/src/m_Do/m_Do_ext.cpp @@ -2873,7 +2873,10 @@ void mDoExt_3DlineMat1_c::update(int param_0, f32 param_1, GXColor& param_2, u16 sp_38++; } } + +#if !TARGET_PC #include "assets/l_mat2DL__d_a_grass.h" +#endif void mDoExt_3DlineMat2_c::setMaterial() { j3dSys.reinitGX(); From 9f62ad45a062db80476ac8a5871d539a0e103623 Mon Sep 17 00:00:00 2001 From: madeline Date: Mon, 6 Apr 2026 07:55:10 -0700 Subject: [PATCH 286/350] remove includes --- CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3a9d283dfa..e50a0b8cbb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -100,7 +100,6 @@ set(GAME_COMPILE_DEFS TARGET_PC WIDESCREEN_SUPPORT=1 AVOID_UB=1 VERSION=0 set(GAME_INCLUDE_DIRS include src - assets/${DUSK_TP_VERSION} libs/JSystem/include libs extern/aurora/include/dolphin From 8ce429b3ba8c396b03d71e58cb3c31c125393be8 Mon Sep 17 00:00:00 2001 From: madeline Date: Mon, 6 Apr 2026 07:57:00 -0700 Subject: [PATCH 287/350] walk that one on back --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index e50a0b8cbb..3a9d283dfa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -100,6 +100,7 @@ set(GAME_COMPILE_DEFS TARGET_PC WIDESCREEN_SUPPORT=1 AVOID_UB=1 VERSION=0 set(GAME_INCLUDE_DIRS include src + assets/${DUSK_TP_VERSION} libs/JSystem/include libs extern/aurora/include/dolphin From 02c2631d6cc09021f8bbb53621cce78ca30c1bd9 Mon Sep 17 00:00:00 2001 From: madeline Date: Mon, 6 Apr 2026 08:49:24 -0700 Subject: [PATCH 288/350] address review --- include/dusk/dvd_asset.hpp | 4 ++-- src/d/actor/d_a_mant.cpp | 8 ++++---- src/d/actor/d_flower.inc | 10 +++++----- src/dusk/dvd_asset.cpp | 39 ++++++++++++++++++++++++++++---------- 4 files changed, 40 insertions(+), 21 deletions(-) diff --git a/include/dusk/dvd_asset.hpp b/include/dusk/dvd_asset.hpp index 3c36b75195..014c683d44 100644 --- a/include/dusk/dvd_asset.hpp +++ b/include/dusk/dvd_asset.hpp @@ -10,9 +10,9 @@ namespace dusk { bool LoadDolAsset(void* dst, u32 virtualAddress, s32 size); /** - * Load bytes from a REL file in the ISO filesystem + * Load bytes from a REL file in the ISO filesystem, dst must be 32-byte aligned */ -void* LoadRelAsset(const char* dvdPath, s32 offset, s32 size); +bool LoadRelAsset(void* dst, const char* dvdPath, s32 offset, s32 size); /** * Load bytes from a REL inside RELS.arc diff --git a/src/d/actor/d_a_mant.cpp b/src/d/actor/d_a_mant.cpp index 1760db60fa..bcd7f05986 100644 --- a/src/d/actor/d_a_mant.cpp +++ b/src/d/actor/d_a_mant.cpp @@ -12,9 +12,9 @@ #if TARGET_PC #include "dusk/dvd_asset.hpp" -static u8* l_Egnd_mantTEX_get() { static u8* p = (u8*)dusk::LoadRelAsset("/rel/Final/Release/d_a_mant.rel", 0x1C00, 0x4000); return p; } -static u8* l_Egnd_mantTEX_U_get() { static u8* p = (u8*)dusk::LoadRelAsset("/rel/Final/Release/d_a_mant.rel", 0x5C00, 0x4000); return p; } -static u8* l_Egnd_mantPAL_get() { static u8* p = (u8*)dusk::LoadRelAsset("/rel/Final/Release/d_a_mant.rel", 0x9C00, 0x60); return p; } +static u8* l_Egnd_mantTEX_get() { alignas(32) static u8 buf[0x4000]; static bool _ = (dusk::LoadRelAsset(buf, "/rel/Final/Release/d_a_mant.rel", 0x1C00, 0x4000), true); return buf; } +static u8* l_Egnd_mantTEX_U_get() { alignas(32) static u8 buf[0x4000]; static bool _ = (dusk::LoadRelAsset(buf, "/rel/Final/Release/d_a_mant.rel", 0x5C00, 0x4000), true); return buf; } +static u8* l_Egnd_mantPAL_get() { alignas(32) static u8 buf[0x60]; static bool _ = (dusk::LoadRelAsset(buf, "/rel/Final/Release/d_a_mant.rel", 0x9C00, 0x60), true); return buf; } #define l_Egnd_mantTEX (l_Egnd_mantTEX_get()) #define l_Egnd_mantTEX_U (l_Egnd_mantTEX_U_get()) #define l_Egnd_mantPAL (l_Egnd_mantPAL_get()) @@ -250,7 +250,7 @@ static u32 l_texCoord[338] = { }; #if TARGET_PC -static u8* l_Egnd_mantDL_get() { static u8* p = (u8*)dusk::LoadRelAsset("/rel/Final/Release/d_a_mant.rel", 0xA9A0, 0x3EC); return p; } +static u8* l_Egnd_mantDL_get() { alignas(32) static u8 buf[0x3EC]; static bool _ = (dusk::LoadRelAsset(buf, "/rel/Final/Release/d_a_mant.rel", 0xA9A0, 0x3EC), true); return buf; } #define l_Egnd_mantDL (l_Egnd_mantDL_get()) #else #include "assets/l_Egnd_mantDL.h" diff --git a/src/d/actor/d_flower.inc b/src/d/actor/d_flower.inc index 93a7e35097..de6ac7c1dc 100644 --- a/src/d/actor/d_flower.inc +++ b/src/d/actor/d_flower.inc @@ -619,9 +619,9 @@ void dFlower_packet_c::draw() { } if (sp4C <= 2) { - GXCallDisplayList(&l_matLight4DL, 0x80); + GXCallDisplayList(l_matLight4DL, 0x80); } else { - GXCallDisplayList(&l_matDL, 0x80); + GXCallDisplayList(l_matDL, 0x80); } GXSetTevColorS10(GX_TEVREG0, sp80); @@ -677,9 +677,9 @@ void dFlower_packet_c::draw() { #endif if (!cLib_checkBit(sp44->m_state, 8)) { - GXCallDisplayList(&l_J_hana00DL, 0x140); + GXCallDisplayList(l_J_hana00DL, 0x140); } else { - GXCallDisplayList(&l_J_hana00_cDL, 0xC0); + GXCallDisplayList(l_J_hana00_cDL, 0xC0); } } } @@ -826,7 +826,7 @@ void dFlower_packet_c::draw() { if (!cLib_checkBit(sp34->m_state, 0x10)) { GXCallDisplayList(mp_Jhana01DL, m_Jhana01DL_size); } else { - GXCallDisplayList(&l_J_hana01_c_00DL, 0xC0); + GXCallDisplayList(l_J_hana01_c_00DL, 0xC0); } } else { GXCallDisplayList(mp_Jhana01_cDL, m_Jhana01_cDL_size); diff --git a/src/dusk/dvd_asset.cpp b/src/dusk/dvd_asset.cpp index bfa30cc941..9bcea14077 100644 --- a/src/dusk/dvd_asset.cpp +++ b/src/dusk/dvd_asset.cpp @@ -12,7 +12,22 @@ namespace dusk { static const u8* s_dolData = nullptr; // pointer to dol data static size_t s_dolSize = 0; -struct DolSection { u32 fileOffset, vaddr, size; }; + +struct DolHeader { + BE(u32) textOffset[7]; + BE(u32) dataOffset[11]; + BE(u32) textAddr[7]; + BE(u32) dataAddr[11]; + BE(u32) textSize[7]; + BE(u32) dataSize[11]; +}; + +struct DolSection { + u32 fileOffset; + u32 vaddr; + u32 size; +}; + static DolSection s_dolSections[18]; // 7 text + 11 data static int s_dolSectionCount = 0; @@ -22,25 +37,29 @@ static bool EnsureDolParsed() { s32 sz = 0; const u8* p = aurora_dvd_get_dol(sz); if (!p || sz < 256) { - DuskLog.fatal("dvd_asset: aurora_dvd_get_dol failed (size={})", sz); return false; + DuskLog.fatal("dvd_asset: aurora_dvd_get_dol failed (size={})", sz); + return false; } s_dolData = p; s_dolSize = sz; - const BE(u32)* hdr = (const BE(u32)*)s_dolData; + const auto* hdr = (const DolHeader*)s_dolData; s_dolSectionCount = 0; - // 0x00: text file offsets 0x12: text vaddrs 0x24: text sizes for (int i = 0; i < 7; i++) { - u32 off = hdr[0x00+i], addr = hdr[0x12+i], sz = hdr[0x24+i]; + u32 off = hdr->textOffset[i]; + u32 addr = hdr->textAddr[i]; + u32 sz = hdr->textSize[i]; if (sz > 0 && off > 0) { s_dolSections[s_dolSectionCount++] = {off, addr, sz}; } } - // 0x07: data file offsets 0x19: data vaddrs 0x2B: data sizes + for (int i = 0; i < 11; i++) { - u32 off = hdr[0x07+i], addr = hdr[0x19+i], sz = hdr[0x2B+i]; + u32 off = hdr->dataOffset[i]; + u32 addr = hdr->dataAddr[i]; + u32 sz = hdr->dataSize[i]; if (sz > 0 && off > 0) { s_dolSections[s_dolSectionCount++] = {off, addr, sz}; } @@ -76,10 +95,10 @@ bool LoadDolAsset(void* dst, u32 virtualAddress, s32 size) { return true; } -void* LoadRelAsset(const char* dvdPath, s32 offset, s32 size) { - void* p = JKRDvdRipper::loadToMainRAM(dvdPath, nullptr, EXPAND_SWITCH_UNKNOWN1, (u32)size, nullptr, JKRDvdRipper::ALLOC_DIRECTION_FORWARD, (u32)offset, nullptr, nullptr); +bool LoadRelAsset(void* dst, const char* dvdPath, s32 offset, s32 size) { + void* p = JKRDvdRipper::loadToMainRAM(dvdPath, (u8*)dst, EXPAND_SWITCH_UNKNOWN1, (u32)size, nullptr, JKRDvdRipper::ALLOC_DIRECTION_FORWARD, (u32)offset, nullptr, nullptr); if (!p) DuskLog.fatal("dvd_asset: failed to load {} (offset={:#x} size={:#x})", dvdPath, offset, size); - return p; + return p != nullptr; } bool LoadArchivedRelAsset(void* dst, u32 memType, const char* relFileName, s32 offset, s32 size) { From e47f67ee8c66db600c09fcea3ab72fb024a754dc Mon Sep 17 00:00:00 2001 From: madeline Date: Mon, 6 Apr 2026 08:58:52 -0700 Subject: [PATCH 289/350] update include directories --- CMakeLists.txt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3a9d283dfa..164d5aa893 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -104,9 +104,7 @@ set(GAME_INCLUDE_DIRS libs/JSystem/include libs extern/aurora/include/dolphin - extern - ${CMAKE_SOURCE_DIR}/build/${DUSK_TP_VERSION}/include - build/${DUSK_TP_VERSION}/include) + extern) set(GAME_LIBS aurora::core aurora::gx aurora::gd aurora::si aurora::vi aurora::pad aurora::mtx aurora::os aurora::dvd aurora::card freeverb cxxopts::cxxopts absl::flat_hash_map nlohmann_json::nlohmann_json) From 3b9c1e280b7d048f91f207aeefbcbd5dce112a93 Mon Sep 17 00:00:00 2001 From: madeline Date: Mon, 6 Apr 2026 20:52:38 -0700 Subject: [PATCH 290/350] use the new thing in aurora --- src/dusk/dvd_asset.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/dusk/dvd_asset.cpp b/src/dusk/dvd_asset.cpp index 9bcea14077..3aa25be94b 100644 --- a/src/dusk/dvd_asset.cpp +++ b/src/dusk/dvd_asset.cpp @@ -1,7 +1,7 @@ #include "dusk/dvd_asset.hpp" #include "dusk/logging.h" #include "dusk/endian.h" -#include "aurora/dvd.h" +#include "dolphin/dvd.h" #include "DynamicLink.h" #include "JSystem/JKernel/JKRArchive.h" #include "JSystem/JKernel/JKRDvdRipper.h" @@ -35,9 +35,9 @@ static bool EnsureDolParsed() { if (s_dolData) return true; s32 sz = 0; - const u8* p = aurora_dvd_get_dol(sz); + const u8* p = DVDGetDOLLocation(sz); if (!p || sz < 256) { - DuskLog.fatal("dvd_asset: aurora_dvd_get_dol failed (size={})", sz); + DuskLog.fatal("dvd_asset: DVDGetDOLLocation failed (size={})", sz); return false; } From 3ae15b828c29ee965a2e15f14e9e7ae51b5e8119 Mon Sep 17 00:00:00 2001 From: madeline Date: Mon, 6 Apr 2026 21:06:04 -0700 Subject: [PATCH 291/350] use updated aurora signature --- src/dusk/dvd_asset.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dusk/dvd_asset.cpp b/src/dusk/dvd_asset.cpp index 3aa25be94b..b9e0729be9 100644 --- a/src/dusk/dvd_asset.cpp +++ b/src/dusk/dvd_asset.cpp @@ -35,7 +35,7 @@ static bool EnsureDolParsed() { if (s_dolData) return true; s32 sz = 0; - const u8* p = DVDGetDOLLocation(sz); + const u8* p = DVDGetDOLLocation(&sz); if (!p || sz < 256) { DuskLog.fatal("dvd_asset: DVDGetDOLLocation failed (size={})", sz); return false; From b37dfc0a9b444f0702dd9337678440a320eddaba Mon Sep 17 00:00:00 2001 From: madeline Date: Mon, 6 Apr 2026 21:31:29 -0700 Subject: [PATCH 292/350] update readme --- README.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 200913770a..59f12abc2a 100644 --- a/README.md +++ b/README.md @@ -37,25 +37,24 @@ sudo dnf groupinstall "Development Tools" "Development Libraries" ``` #### Setup -1. Clone and initialize the Dusk repository +Clone and initialize the Dusk repository ```sh git clone --recursive https://github.com/TakaRikka/dusk.git cd dusk git pull git submodule update --recursive ``` -2. Generate assets -```sh -cp /path/to/GZ2E01.iso orig/GZ2E01/. -python3 ./configure.py -ninja -``` #### Building **Visual Studio (Recommended for Windows)** ```sh cmake -B build/dusk -G "Visual Studio 17 2022" -A x64 # Win32 for 32bit ``` + +**Visual Studio Code with ninja** + +Project has support for the CMakeTools extension with variants and has a debug launch target + **ninja (Windows/macOS/Linux)** ```sh cmake -B build/dusk -GNinja From fc351553ec793c475442c9b28a61499047d4a182 Mon Sep 17 00:00:00 2001 From: madeline Date: Mon, 6 Apr 2026 21:51:40 -0700 Subject: [PATCH 293/350] disable all drawing in d_error_msg and remove need for assets --- src/d/d_error_msg.cpp | 32 +++++++------------------------- 1 file changed, 7 insertions(+), 25 deletions(-) diff --git a/src/d/d_error_msg.cpp b/src/d/d_error_msg.cpp index f1d63b5a1f..efd2512c11 100644 --- a/src/d/d_error_msg.cpp +++ b/src/d/d_error_msg.cpp @@ -15,22 +15,7 @@ #include "m_Do/m_Do_Reset.h" #include "m_Do/m_Do_graphic.h" -#if TARGET_PC -#include "dusk/dvd_asset.hpp" -static const u8* black_tex_get() { static u8 buf[0x40]; static bool _ = (dusk::LoadDolAsset(buf, 0x8037B140, 0x40), true); return buf; } -static const u8* msg_data_get() { static u8 buf[0x260]; static bool _ = (dusk::LoadDolAsset(buf, 0x8037B180, 0x260), true); return buf; } -static const u8* font_data_get() { static u8 buf[0x12260]; static bool _ = (dusk::LoadDolAsset(buf, 0x8037B3E0, 0x12260), true); return buf; } -#define black_tex (black_tex_get()) -#define msg_data (msg_data_get()) -#define font_data (font_data_get()) -#if VERSION == VERSION_GCN_PAL -// TODO: find addreses for pal languages -#define msg_data_ge ((const u8*)nullptr) -#define msg_data_fr ((const u8*)nullptr) -#define msg_data_sp ((const u8*)nullptr) -#define msg_data_it ((const u8*)nullptr) -#endif -#else +#if !TARGET_PC #include "assets/black_tex.h" #include "assets/msg_data.h" #if VERSION == VERSION_GCN_PAL @@ -66,6 +51,7 @@ struct BMG_INF1 : JUTDataBlockHeader { #endif static void messageSet(u32 status, bool i_drawBg) { + #if !TARGET_PC BMG_INF1* inf1; const char* msg_p; @@ -194,9 +180,11 @@ static void messageSet(u32 status, bool i_drawBg) { tpane.draw(x, y + 10.0f, FB_WIDTH, HBIND_LEFT); #endif #endif + #endif } void dDvdErrorMsg_c::draw(s32 status) { + #if !TARGET_PC JUtility::TColor backColor = g_clearColor; JFWDisplay::getManager()->setClearColor(backColor); mDoGph_gInf_c::beginRender(); @@ -224,6 +212,7 @@ void dDvdErrorMsg_c::draw(s32 status) { mDoGph_gInf_c::endRender(); JFWDisplay::getManager()->resetFader(); + #endif } bool dDvdErrorMsg_c::execute() { @@ -254,16 +243,12 @@ bool dDvdErrorMsg_c::execute() { static u8 l_captureAlpha = 0xFF; static void drawCapture(u8 alpha) { + #if !TARGET_PC static bool l_texCopied = false; if (!l_texCopied) { -#if TARGET_PC - GXSetTexCopySrc(0, 0, mDoGph_gInf_c::getWidth(), mDoGph_gInf_c::getHeight()); - GXSetTexCopyDst(mDoGph_gInf_c::getWidth(), mDoGph_gInf_c::getHeight(), (GXTexFmt)mDoGph_gInf_c::getFrameBufferTimg()->format, GX_TRUE); -#else GXSetTexCopySrc(0, 0, FB_WIDTH, FB_HEIGHT); GXSetTexCopyDst(FB_WIDTH / 2, FB_HEIGHT / 2, (GXTexFmt)mDoGph_gInf_c::getFrameBufferTimg()->format, GX_TRUE); -#endif GXCopyTex(mDoGph_gInf_c::getFrameBufferTex(), GX_FALSE); l_texCopied = true; } @@ -273,9 +258,6 @@ static void drawCapture(u8 alpha) { GXSetAlphaUpdate(GX_FALSE); j3dSys.drawInit(); -#ifdef TARGET_PC - mDoGph_gInf_c::getFrameBufferTexObj()->reset(); -#endif GXInitTexObj(mDoGph_gInf_c::getFrameBufferTexObj(), mDoGph_gInf_c::getFrameBufferTex(), FB_WIDTH / 2, FB_HEIGHT / 2, (GXTexFmt)mDoGph_gInf_c::getFrameBufferTimg()->format, GX_CLAMP, GX_CLAMP, GX_FALSE); GXInitTexObjLOD(mDoGph_gInf_c::getFrameBufferTexObj(), GX_LINEAR, GX_LINEAR, 0.0f, 0.0f, 0.0f, GX_FALSE, GX_FALSE, GX_ANISO_1); GXLoadTexObj(mDoGph_gInf_c::getFrameBufferTexObj(), GX_TEXMAP0); @@ -315,6 +297,7 @@ static void drawCapture(u8 alpha) { mDoGph_drawFilterQuad(1, 1); mDoGph_gInf_c::endRender(); JFWDisplay::getManager()->resetFader(); + #endif } bool dShutdownErrorMsg_c::execute() { @@ -342,6 +325,5 @@ bool dShutdownErrorMsg_c::execute() { mDoRst_reset(1, 1, 1); } } - return true; } From 462001d398a3d69a9511062f247baeb1f7a67945 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Mon, 6 Apr 2026 23:07:48 -0600 Subject: [PATCH 294/350] Add vsync toggle & honor fullscreen setting --- extern/aurora | 2 +- include/dusk/settings.h | 1 + src/dusk/imgui/ImGuiMenuGame.cpp | 9 +++++++++ src/dusk/settings.cpp | 2 ++ src/m_Do/m_Do_main.cpp | 2 ++ 5 files changed, 15 insertions(+), 1 deletion(-) diff --git a/extern/aurora b/extern/aurora index 854ce1c178..f5c5917832 160000 --- a/extern/aurora +++ b/extern/aurora @@ -1 +1 @@ -Subproject commit 854ce1c178756ca307137c81afc3cc626fce5fb3 +Subproject commit f5c59178322c31b42676f49dd24e760e3b014f24 diff --git a/include/dusk/settings.h b/include/dusk/settings.h index 0cb3ba60b7..2436be70f5 100644 --- a/include/dusk/settings.h +++ b/include/dusk/settings.h @@ -15,6 +15,7 @@ struct UserSettings { struct { // Video ConfigVar enableFullscreen; + ConfigVar enableVsync; } video; struct { diff --git a/src/dusk/imgui/ImGuiMenuGame.cpp b/src/dusk/imgui/ImGuiMenuGame.cpp index 6ae32ce1a4..f200f734d4 100644 --- a/src/dusk/imgui/ImGuiMenuGame.cpp +++ b/src/dusk/imgui/ImGuiMenuGame.cpp @@ -14,6 +14,8 @@ #include "m_Do/m_Do_controller_pad.h" #include "m_Do/m_Do_graphic.h" +#include + namespace dusk { void ImGuiMenuGame::ToggleFullscreen() { getSettings().video.enableFullscreen.setValue(!getSettings().video.enableFullscreen); @@ -36,6 +38,13 @@ namespace dusk { ToggleFullscreen(); } + bool vsync = getSettings().video.enableVsync; + if (ImGui::Checkbox("Enable Vsync", &vsync)) { + getSettings().video.enableVsync.setValue(vsync); + aurora_enable_vsync(vsync); + config::Save(); + } + if (ImGui::MenuItem("Default Window Size")) { getSettings().video.enableFullscreen.setValue(false); VISetWindowFullscreen(false); diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index 32c6a5beb4..d923a2b00e 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -6,6 +6,7 @@ namespace dusk { UserSettings g_userSettings = { .video = { .enableFullscreen {"video.enableFullscreen", false}, + .enableVsync {"video.enableVsync", true}, }, .audio = { @@ -63,6 +64,7 @@ UserSettings& getSettings() { void registerSettings() { // Video Register(g_userSettings.video.enableFullscreen); + Register(g_userSettings.video.enableVsync); // Audio Register(g_userSettings.audio.masterVolume); diff --git a/src/m_Do/m_Do_main.cpp b/src/m_Do/m_Do_main.cpp index 51f74eef00..9cb7e41764 100644 --- a/src/m_Do/m_Do_main.cpp +++ b/src/m_Do/m_Do_main.cpp @@ -319,6 +319,8 @@ int game_main(int argc, char* argv[]) { AuroraConfig config{}; config.appName = dusk::AppName; config.configPath = configPath; + config.vsync = dusk::getSettings().video.enableVsync; + config.startFullscreen = dusk::getSettings().video.enableFullscreen; config.windowPosX = -1; config.windowPosY = -1; config.windowWidth = FB_WIDTH * 2; From 21f95a0aa8f3d263e149773195d37f5bb98edf1d Mon Sep 17 00:00:00 2001 From: TakaRikka Date: Mon, 6 Apr 2026 22:47:55 -0700 Subject: [PATCH 295/350] fix flowers --- src/d/actor/d_flower.inc | 40 +++++++++++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/src/d/actor/d_flower.inc b/src/d/actor/d_flower.inc index 762b1e02e9..662e7679db 100644 --- a/src/d/actor/d_flower.inc +++ b/src/d/actor/d_flower.inc @@ -521,6 +521,32 @@ dFlower_packet_c::dFlower_packet_c() { unused += 0x2000; } +#if TARGET_LITTLE_ENDIAN + static bool initialized = false; + if (!initialized) { + for (int i = 0; i < (ARRAY_SIZE(l_flowerPos) / sizeof(Vec)); i++) { + be_swap(((Vec*)l_flowerPos)[i]); + } + for (int i = 0; i < (ARRAY_SIZE(l_flowerTexCoord) / sizeof(Vec)); i++) { + be_swap(((Vec*)l_flowerTexCoord)[i]); + } + for (int i = 0; i < (ARRAY_SIZE(l_flowerPos2) / sizeof(Vec)); i++) { + be_swap(((Vec*)l_flowerPos2)[i]); + } + for (int i = 0; i < (ARRAY_SIZE(l_flowerTexCoord2) / sizeof(Vec)); i++) { + be_swap(((Vec*)l_flowerTexCoord2)[i]); + } + for (int i = 0; i < (ARRAY_SIZE(l_flowerNormal) / sizeof(Vec)); i++) { + be_swap(((Vec*)l_flowerNormal)[i]); + } + for (int i = 0; i < (ARRAY_SIZE(l_flowerNormal2) / sizeof(Vec)); i++) { + be_swap(((Vec*)l_flowerNormal2)[i]); + } + + initialized = true; + } +#endif + #if TARGET_PC GXInitTexObj(&mTexObj_l_J_Ohana00_64TEX, l_J_Ohana00_64TEX, l_J_Ohana00_64TEX__width, l_J_Ohana00_64TEX__height, GX_TF_CMPR, GX_MIRROR, GX_MIRROR, GX_FALSE @@ -553,10 +579,10 @@ void dFlower_packet_c::draw() { GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_NRM, GX_NRM_XYZ, GX_F32, 0); GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_CLR0, GX_CLR_RGBA, GX_RGBA8, 0); GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_F32, 0); - GXSETARRAY(GX_VA_POS, &l_flowerPos, sizeof(l_flowerPos), 0xC, false); - GXSETARRAY(GX_VA_NRM, &l_flowerNormal, sizeof(l_flowerNormal), 0xC, false); - GXSETARRAY(GX_VA_CLR0, &l_flowerColor, sizeof(l_flowerColor), 4, false); - GXSETARRAY(GX_VA_TEX0, &l_flowerTexCoord, sizeof(l_flowerTexCoord), 8, false); + GXSETARRAY(GX_VA_POS, &l_flowerPos, sizeof(l_flowerPos), sizeof(Vec), true); + GXSETARRAY(GX_VA_NRM, &l_flowerNormal, sizeof(l_flowerNormal), sizeof(Vec), true); + GXSETARRAY(GX_VA_CLR0, &l_flowerColor, sizeof(l_flowerColor), sizeof(GXColor), true); + GXSETARRAY(GX_VA_TEX0, &l_flowerTexCoord, sizeof(l_flowerTexCoord), 8, true); GXColor sp64; dFlower_room_c* sp5C = m_room; @@ -652,9 +678,9 @@ void dFlower_packet_c::draw() { sp5C++; } - GXSETARRAY(GX_VA_POS, mp_pos, sizeof(l_flowerPos2), 0xC, true); - GXSETARRAY(GX_VA_NRM, &l_flowerNormal2, sizeof(l_flowerNormal2), 0xC, false); - GXSETARRAY(GX_VA_CLR0, mp_colors, sizeof(l_flowerColor2), 4, true); + GXSETARRAY(GX_VA_POS, mp_pos, sizeof(l_flowerPos2), sizeof(Vec), true); + GXSETARRAY(GX_VA_NRM, &l_flowerNormal2, sizeof(l_flowerNormal2), sizeof(Vec), true); + GXSETARRAY(GX_VA_CLR0, mp_colors, sizeof(l_flowerColor2), sizeof(GXColor), true); GXSETARRAY(GX_VA_TEX0, mp_texCoords, sizeof(l_flowerTexCoord2), 8, true); sp5C = m_room; From 20c00a0ac269f938e638f95c5d0781a8b6746b86 Mon Sep 17 00:00:00 2001 From: madeline Date: Mon, 6 Apr 2026 23:20:58 -0700 Subject: [PATCH 296/350] adjust dol sightDL address based on version --- src/d/actor/d_a_player.cpp | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/d/actor/d_a_player.cpp b/src/d/actor/d_a_player.cpp index d6f7c54a49..f56a8399cd 100644 --- a/src/d/actor/d_a_player.cpp +++ b/src/d/actor/d_a_player.cpp @@ -369,7 +369,24 @@ JKRHeap* daPy_anmHeap_c::setAnimeHeap() { #if !PLATFORM_WII #if TARGET_PC #include "dusk/dvd_asset.hpp" -static const u8* l_sightDL_get() { static u8 buf[0x89]; static bool _ = (dusk::LoadDolAsset(buf, 0x803BA0C0, 0x89), true); return buf; } +static const u8* l_sightDL_get() { + static u8 buf[0x89]; + static bool _ = ( + dusk::LoadDolAsset( + buf, + #if VERSION_GCN_PAL + 0x803BBDA0, + #elif VERSION_GCN_JPN + 0x803B4220, + #elif VERSION_GCN_USA + 0x803BA0C0, + #endif + 0x89 + ), + true + ); + return buf; +} #define l_sightDL (l_sightDL_get()) #else #include "assets/l_sightDL__d_a_player.h" From dc4d0598f3d8a415848b84cb37b3d7deff7317fa Mon Sep 17 00:00:00 2001 From: madeline Date: Mon, 6 Apr 2026 23:21:29 -0700 Subject: [PATCH 297/350] correctly this time --- src/d/actor/d_a_player.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/d/actor/d_a_player.cpp b/src/d/actor/d_a_player.cpp index f56a8399cd..4c8b31c4c6 100644 --- a/src/d/actor/d_a_player.cpp +++ b/src/d/actor/d_a_player.cpp @@ -374,11 +374,11 @@ static const u8* l_sightDL_get() { static bool _ = ( dusk::LoadDolAsset( buf, - #if VERSION_GCN_PAL + #if VERSION == VERSION_GCN_PAL 0x803BBDA0, - #elif VERSION_GCN_JPN + #elif VERSION == VERSION_GCN_JPN 0x803B4220, - #elif VERSION_GCN_USA + #elif VERSION == VERSION_GCN_USA 0x803BA0C0, #endif 0x89 From b346285229a80dc349af58faf4b739cd070c1070 Mon Sep 17 00:00:00 2001 From: madeline Date: Tue, 7 Apr 2026 05:59:03 -0700 Subject: [PATCH 298/350] fix the mf map regions being ass --- src/d/d_meter2_info.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/d/d_meter2_info.cpp b/src/d/d_meter2_info.cpp index 820e65dc0d..7307755c04 100644 --- a/src/d/d_meter2_info.cpp +++ b/src/d/d_meter2_info.cpp @@ -1639,7 +1639,7 @@ u8 dMeter2Info_getPixel(f32 i_posX, f32 i_posY, f32 param_2, f32 param_3, f32 i_ JUT_ASSERT(3074, *pixel < i_resTimg->numColors); - u16* palette_p = (u16*)((uintptr_t)i_resTimg + i_resTimg->paletteOffset); + BE(u16)* palette_p = (BE(u16)*)((uintptr_t)i_resTimg + i_resTimg->paletteOffset); u16 var_r24 = (u16)palette_p[*pixel]; if (var_r24 & 0x8000) { return 1; From 843a87581e03817549262209dab9963d7c43a77c Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Tue, 7 Apr 2026 15:41:56 +0200 Subject: [PATCH 299/350] Fix Argorok Peahat camera init race condition Fixes #264 --- src/d/actor/d_a_e_ph.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/d/actor/d_a_e_ph.cpp b/src/d/actor/d_a_e_ph.cpp index dc20cb2361..df22c6cb66 100644 --- a/src/d/actor/d_a_e_ph.cpp +++ b/src/d/actor/d_a_e_ph.cpp @@ -1154,6 +1154,18 @@ int daE_PH_c::create() { int phase_state = dComIfG_resLoad(&mPhase, "E_PH"); if (phase_state == cPhs_COMPLEATE_e) { + +#if TARGET_PC + // Due to our loads being so much faster, peahats can initialize *before* the camera. + // This breaks the peahat used for camera focus during transition to phase 2 of Argorok. + // as it caches incorrect camera parameters in its init. + if (auto cam = dComIfGp_getCamera(0)) { + if (cam->phase_request.mpHandlerTable != nullptr) { + return cPhs_INIT_e; + } + } +#endif + mAction = fopAcM_GetParam(this) & 0xF; if (dComIfGs_isZoneSwitch(2, fopAcM_GetRoomNo(this)) && mAction == 4) { @@ -1256,6 +1268,10 @@ int daE_PH_c::create() { if (mAction == 5) { dCamera_c* camera_p = dCam_getBody(); mCamFovY = camera_p->Fovy(); + if (mCamFovY < 1) { + OSReport("GUH"); + mCamFovY = 10; + } mCamCenter = camera_p->Center(); mCamCenterTarget = mCamCenter; From c6b53b079c8a5a1dd42e35f78468e7eb6bf113cb Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Tue, 7 Apr 2026 15:44:37 +0200 Subject: [PATCH 300/350] More TU debug consistency Thought this was the cause of #264. Didn't end up being the case but figure it's still best to fix it. --- CMakeLists.txt | 4 ++-- include/d/d_camera.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 164d5aa893..66c86bb5f4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -137,8 +137,8 @@ add_library(game_base OBJECT ${DOLZEL_FILES} ${Z2AUDIOLIB_FILES} ${JSYSTEM_FILES src/dusk/imgui/ImGuiStubLog.cpp src/dusk/imgui/ImGuiAudio.cpp) -target_compile_definitions(game_debug PRIVATE ${GAME_COMPILE_DEFS} $<$:DEBUG=1>) -target_compile_definitions(game_base PRIVATE ${GAME_COMPILE_DEFS} NDEBUG=1 NDEBUG_DEFINED=1 DEBUG_DEFINED=0) +target_compile_definitions(game_debug PRIVATE ${GAME_COMPILE_DEFS} $<$:DEBUG=1> $<$:PARTIAL_DEBUG=1>) +target_compile_definitions(game_base PRIVATE ${GAME_COMPILE_DEFS} NDEBUG=1 NDEBUG_DEFINED=1 DEBUG_DEFINED=0 $<$:PARTIAL_DEBUG=1>) # only apply PCH to game_base since not all headers are necessarily validated with DEBUG=1 target_precompile_headers(game_base PRIVATE "$<$:${CMAKE_SOURCE_DIR}/include/dusk_pch.hpp>") diff --git a/include/d/d_camera.h b/include/d/d_camera.h index 37b634d18f..85e930c395 100644 --- a/include/d/d_camera.h +++ b/include/d/d_camera.h @@ -1154,7 +1154,7 @@ public: static engine_fn engine_tbl[]; /* 0x000 */ camera_class* field_0x0; -#if DEBUG +#if PARTIAL_DEBUG || DEBUG // Ensure struct layout consistent in all TUs. cXyz dbg_field_0x04[16]; s8 dbg_field_0xc4[0x10]; u32 dbg_field_0xd4; From 186789a9fb111855f51b6d72453e4b5c98c55d8f Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Tue, 7 Apr 2026 15:45:09 +0200 Subject: [PATCH 301/350] Guh --- src/d/actor/d_a_e_ph.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/d/actor/d_a_e_ph.cpp b/src/d/actor/d_a_e_ph.cpp index df22c6cb66..3213c2716e 100644 --- a/src/d/actor/d_a_e_ph.cpp +++ b/src/d/actor/d_a_e_ph.cpp @@ -1268,10 +1268,6 @@ int daE_PH_c::create() { if (mAction == 5) { dCamera_c* camera_p = dCam_getBody(); mCamFovY = camera_p->Fovy(); - if (mCamFovY < 1) { - OSReport("GUH"); - mCamFovY = 10; - } mCamCenter = camera_p->Center(); mCamCenterTarget = mCamCenter; From 7126ab236ddc727049ac1857ad3a81626b90e852 Mon Sep 17 00:00:00 2001 From: madeline Date: Tue, 7 Apr 2026 07:14:39 -0700 Subject: [PATCH 302/350] fix crashes on window resize --- src/d/d_menu_window.cpp | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/d/d_menu_window.cpp b/src/d/d_menu_window.cpp index e9131462c3..7fd5ea31a2 100644 --- a/src/d/d_menu_window.cpp +++ b/src/d/d_menu_window.cpp @@ -32,7 +32,7 @@ public: if (getDrawFlag() == 1) { setDrawFlag(); dComIfGp_onPauseFlag(); - + #if TARGET_PC GXSetTexCopySrc(0, 0, mDoGph_gInf_c::getWidth(), mDoGph_gInf_c::getHeight()); #else @@ -46,17 +46,23 @@ public: #endif GXCopyTex(mDoGph_gInf_c::getFrameBufferTex(), GX_FALSE); GXPixModeSync(); - } else { - TGXTexObj tex; #if TARGET_PC - GXInitTexObj(&tex, mDoGph_gInf_c::getFrameBufferTex(), mDoGph_gInf_c::getWidth(), mDoGph_gInf_c::getHeight(), + // init mTexObj at capture time so the gpu ref survives window resizes + GXInitTexObj(&mTexObj, mDoGph_gInf_c::getFrameBufferTex(), mDoGph_gInf_c::getWidth(), mDoGph_gInf_c::getHeight(), (GXTexFmt)mDoGph_gInf_c::getFrameBufferTimg()->format, GX_CLAMP, GX_CLAMP, GX_FALSE); + GXInitTexObjLOD(&mTexObj, GX_LINEAR, GX_LINEAR, 0.0f, 0.0f, 0.0f, GX_FALSE, GX_FALSE, GX_ANISO_1); +#endif + } else { +#if TARGET_PC + // reuse the persistent mTexObj + GXLoadTexObj(&mTexObj, GX_TEXMAP0); #else + TGXTexObj tex; GXInitTexObj(&tex, mDoGph_gInf_c::getFrameBufferTex(), FB_WIDTH / 2, FB_HEIGHT / 2, (GXTexFmt)mDoGph_gInf_c::getFrameBufferTimg()->format, GX_CLAMP, GX_CLAMP, GX_FALSE); -#endif GXInitTexObjLOD(&tex, GX_LINEAR, GX_LINEAR, 0.0f, 0.0f, 0.0f, GX_FALSE, GX_FALSE, GX_ANISO_1); GXLoadTexObj(&tex, GX_TEXMAP0); +#endif GXSetNumChans(0); GXSetNumTexGens(1); GXSetTexCoordGen2(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, 60, GX_FALSE, 125); @@ -119,6 +125,9 @@ private: /* 0x4 */ u8 mFlag; /* 0x5 */ u8 mAlpha; /* 0x6 */ u8 mTopFlag; +#if TARGET_PC + TGXTexObj mTexObj; +#endif }; BOOL dMw_UP_TRIGGER() { From df6e86ef23e8308b2262c2bd0d566bbce758bcb5 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Tue, 7 Apr 2026 19:54:51 +0200 Subject: [PATCH 303/350] Hide mouse cursor when imgui hidden Fixes https://github.com/TwilitRealm/dusk/issues/224 --- src/dusk/imgui/ImGuiConsole.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/dusk/imgui/ImGuiConsole.cpp b/src/dusk/imgui/ImGuiConsole.cpp index fe5d3e7c9a..d27d033034 100644 --- a/src/dusk/imgui/ImGuiConsole.cpp +++ b/src/dusk/imgui/ImGuiConsole.cpp @@ -13,6 +13,7 @@ #include "ImGuiConsole.hpp" #include "JSystem/JUtility/JUTGamePad.h" +#include "SDL3/SDL_mouse.h" #include "dusk/config.hpp" #include "dusk/settings.h" @@ -198,9 +199,14 @@ namespace dusk { if (CheckMenuViewToggle(ImGuiKey_F1, m_isHidden)) { ShowToasts(); + ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange; + SDL_HideCursor(); return; } + ImGui::GetIO().ConfigFlags &= ~ImGuiConfigFlags_NoMouseCursorChange; + // Imgui will re-show cursor. + // TODO: we need to be able to render the menu bar & any overlays separately // The code currently ties them all together, so hiding the menu hides all windows From 39fae82047af67a95450c39da78f84b9baccec77 Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Tue, 7 Apr 2026 13:56:23 -0400 Subject: [PATCH 304/350] Fixes audio volume at boot if changed (#270) * Fixes audio volume at boot if changed * Update Settings refactor for settings we want to update in the future * remove unused include --------- Co-authored-by: MelonSpeedruns --- include/dusk/audio/DuskAudioSystem.h | 2 ++ src/dusk/audio/DuskAudioSystem.cpp | 6 ++++++ src/dusk/imgui/ImGuiConsole.cpp | 9 ++++++++- src/dusk/imgui/ImGuiConsole.hpp | 1 + src/dusk/imgui/ImGuiMenuGame.cpp | 3 --- 5 files changed, 17 insertions(+), 4 deletions(-) diff --git a/include/dusk/audio/DuskAudioSystem.h b/include/dusk/audio/DuskAudioSystem.h index 1b31db6e7c..724776f5f1 100644 --- a/include/dusk/audio/DuskAudioSystem.h +++ b/include/dusk/audio/DuskAudioSystem.h @@ -8,6 +8,8 @@ namespace dusk::audio { */ void Initialize(); + void SetEnableReverb(bool value); + void SetMasterVolume(f32 value); u32 GetResetCount(int channelIdx); diff --git a/src/dusk/audio/DuskAudioSystem.cpp b/src/dusk/audio/DuskAudioSystem.cpp index c7313749c2..7b464e4f0e 100644 --- a/src/dusk/audio/DuskAudioSystem.cpp +++ b/src/dusk/audio/DuskAudioSystem.cpp @@ -76,6 +76,12 @@ void dusk::audio::SetMasterVolume(const f32 value) { MasterVolume = value; } +void dusk::audio::SetEnableReverb(const bool value) { + JASCriticalSection section; + + EnableReverb = value; +} + void SDLCALL GetNewAudio( void*, SDL_AudioStream*, diff --git a/src/dusk/imgui/ImGuiConsole.cpp b/src/dusk/imgui/ImGuiConsole.cpp index fe5d3e7c9a..4329849800 100644 --- a/src/dusk/imgui/ImGuiConsole.cpp +++ b/src/dusk/imgui/ImGuiConsole.cpp @@ -15,6 +15,7 @@ #include "JSystem/JUtility/JUTGamePad.h" #include "dusk/config.hpp" #include "dusk/settings.h" +#include "dusk/audio/DuskAudioSystem.h" #if _WIN32 #define NOMINMAX @@ -178,13 +179,19 @@ namespace dusk { ImGuiConsole::ImGuiConsole() {} + void ImGuiConsole::UpdateSettings() { + dusk::audio::SetMasterVolume(dusk::getSettings().audio.masterVolume / 100.0f); + dusk::audio::SetEnableReverb(dusk::getSettings().audio.enableReverb); + getTransientSettings().skipFrameRateLimit = getSettings().game.enableTurboKeybind && ImGui::IsKeyDown(ImGuiKey_Tab); + } + void ImGuiConsole::PreDraw() { if (!m_isLaunchInitialized) { m_toasts.emplace_back("Press F1 to toggle menu"s, 5.f); m_isLaunchInitialized = true; } - getTransientSettings().skipFrameRateLimit = getSettings().game.enableTurboKeybind && ImGui::IsKeyDown(ImGuiKey_Tab); + UpdateSettings(); if ((ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl)) && ImGui::IsKeyPressed(ImGuiKey_R)) diff --git a/src/dusk/imgui/ImGuiConsole.hpp b/src/dusk/imgui/ImGuiConsole.hpp index 27032ed39b..2ab61a0ffb 100644 --- a/src/dusk/imgui/ImGuiConsole.hpp +++ b/src/dusk/imgui/ImGuiConsole.hpp @@ -14,6 +14,7 @@ namespace dusk { class ImGuiConsole { public: ImGuiConsole(); + void UpdateSettings(); void PreDraw(); void PostDraw(); diff --git a/src/dusk/imgui/ImGuiMenuGame.cpp b/src/dusk/imgui/ImGuiMenuGame.cpp index f200f734d4..1608209bac 100644 --- a/src/dusk/imgui/ImGuiMenuGame.cpp +++ b/src/dusk/imgui/ImGuiMenuGame.cpp @@ -78,9 +78,6 @@ namespace dusk { } */ - audio::SetMasterVolume(getSettings().audio.masterVolume / 100.0f); - audio::EnableReverb = getSettings().audio.enableReverb; - ImGui::EndMenu(); } From 208f2aba9fc69c652658b17d05ae4de82b10d222 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Tue, 7 Apr 2026 20:06:29 +0200 Subject: [PATCH 305/350] Cleanly shut down JKRDecomp thread Properly join the thread to ensure it's shut down before we try to exit. Fixes #268 --- include/m_Do/m_Do_machine.h | 3 +++ .../include/JSystem/JFramework/JFWSystem.h | 3 +++ .../JSystem/include/JSystem/JKernel/JKRAram.h | 3 +++ .../include/JSystem/JKernel/JKRDecomp.h | 3 +++ libs/JSystem/src/JFramework/JFWSystem.cpp | 6 ++++++ libs/JSystem/src/JKernel/JKRAram.cpp | 6 ++++++ libs/JSystem/src/JKernel/JKRDecomp.cpp | 21 +++++++++++++------ src/m_Do/m_Do_machine.cpp | 6 ++++++ src/m_Do/m_Do_main.cpp | 2 ++ 9 files changed, 47 insertions(+), 6 deletions(-) diff --git a/include/m_Do/m_Do_machine.h b/include/m_Do/m_Do_machine.h index 53c47c07a7..eeea7e77ce 100644 --- a/include/m_Do/m_Do_machine.h +++ b/include/m_Do/m_Do_machine.h @@ -15,6 +15,9 @@ void my_SysPrintHeap(char const*, void*, u32); void mDoMch_HeapCheckAll(); void mDoMch_HeapFreeFillAll(); int mDoMch_Create(); +#if TARGET_PC +void mDoMch_Destroy(); +#endif extern GXRenderModeObj g_ntscZeldaProg; diff --git a/libs/JSystem/include/JSystem/JFramework/JFWSystem.h b/libs/JSystem/include/JSystem/JFramework/JFWSystem.h index bb4dfa01cd..288b5f03d8 100644 --- a/libs/JSystem/include/JSystem/JFramework/JFWSystem.h +++ b/libs/JSystem/include/JSystem/JFramework/JFWSystem.h @@ -33,6 +33,9 @@ struct JFWSystem { static void firstInit(); static void init(); +#if TARGET_PC + static void shutdown(); +#endif static JUTConsole* getSystemConsole() { return systemConsole; } static JKRExpHeap* getSystemHeap() { return systemHeap; } diff --git a/libs/JSystem/include/JSystem/JKernel/JKRAram.h b/libs/JSystem/include/JSystem/JKernel/JKRAram.h index 9c8ebd88d1..dd3bf54f9c 100644 --- a/libs/JSystem/include/JSystem/JKernel/JKRAram.h +++ b/libs/JSystem/include/JSystem/JKernel/JKRAram.h @@ -39,6 +39,9 @@ public: public: static JKRAram* create(u32, u32, s32, s32, s32); +#if TARGET_PC + static void destroy(); +#endif static void checkOkAddress(u8*, u32, JKRAramBlock*, u32); static void changeGroupIdIfNeed(u8*, int); static JKRAramBlock* mainRamToAram(u8*, u32, u32, JKRExpandSwitch, u32, JKRHeap*, int, u32*); diff --git a/libs/JSystem/include/JSystem/JKernel/JKRDecomp.h b/libs/JSystem/include/JSystem/JKernel/JKRDecomp.h index 9131ecfbaa..2e7ce0ef1c 100644 --- a/libs/JSystem/include/JSystem/JKernel/JKRDecomp.h +++ b/libs/JSystem/include/JSystem/JKernel/JKRDecomp.h @@ -48,6 +48,9 @@ private: public: static JKRDecomp* create(s32); +#if TARGET_PC + static void destroy(); +#endif static JKRDecompCommand* prepareCommand(u8*, u8*, u32, u32, JKRDecompCommand::AsyncCallback); static void sendCommand(JKRDecompCommand*); static bool sync(JKRDecompCommand*, int); diff --git a/libs/JSystem/src/JFramework/JFWSystem.cpp b/libs/JSystem/src/JFramework/JFWSystem.cpp index b4effd547a..d6322f34c9 100644 --- a/libs/JSystem/src/JFramework/JFWSystem.cpp +++ b/libs/JSystem/src/JFramework/JFWSystem.cpp @@ -116,3 +116,9 @@ void JFWSystem::init() { void* buffer = systemHeap->alloc(CSetUpParam::exConsoleBufferSize, 4); JUTException::createConsole(buffer, CSetUpParam::exConsoleBufferSize); } + +#if TARGET_PC +void JFWSystem::shutdown() { + JKRAram::destroy(); +} +#endif diff --git a/libs/JSystem/src/JKernel/JKRAram.cpp b/libs/JSystem/src/JKernel/JKRAram.cpp index 32afe1cef4..2770328afa 100644 --- a/libs/JSystem/src/JKernel/JKRAram.cpp +++ b/libs/JSystem/src/JKernel/JKRAram.cpp @@ -42,6 +42,12 @@ JKRAram* JKRAram::create(u32 aram_audio_buffer_size, u32 aram_audio_graph_size, return sAramObject; } +#if TARGET_PC +void JKRAram::destroy() { + JKRDecomp::destroy(); +} +#endif + OSMessage JKRAram::sMessageBuffer[4] = { NULL, NULL, diff --git a/libs/JSystem/src/JKernel/JKRDecomp.cpp b/libs/JSystem/src/JKernel/JKRDecomp.cpp index 776823531d..a97edea0a3 100644 --- a/libs/JSystem/src/JKernel/JKRDecomp.cpp +++ b/libs/JSystem/src/JKernel/JKRDecomp.cpp @@ -20,6 +20,15 @@ JKRDecomp* JKRDecomp::create(s32 priority) { return sDecompObject; } +#if TARGET_PC +void JKRDecomp::destroy() { + if (sDecompObject) { + OSSendMessage(&sMessageQueue, nullptr, OS_MESSAGE_NOBLOCK); + OSJoinThread(sDecompObject->getThreadRecord(), nullptr); + } +} +#endif + OSMessage JKRDecomp::sMessageBuffer[8] = {0}; OSMessageQueue JKRDecomp::sMessageQueue = {0}; @@ -34,15 +43,15 @@ 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; +#if TARGET_PC + if (!command) { + break; + } +#endif + decode(command->mSrcBuffer, command->mDstBuffer, command->mSrcLength, command->mDstLength); if (command->field_0x20 != 0) { diff --git a/src/m_Do/m_Do_machine.cpp b/src/m_Do/m_Do_machine.cpp index fdab588957..6b186a77e6 100644 --- a/src/m_Do/m_Do_machine.cpp +++ b/src/m_Do/m_Do_machine.cpp @@ -1010,3 +1010,9 @@ int mDoMch_Create() { return 1; } + +#if TARGET_PC +void mDoMch_Destroy() { + JFWSystem::shutdown(); +} +#endif diff --git a/src/m_Do/m_Do_main.cpp b/src/m_Do/m_Do_main.cpp index 9cb7e41764..8742436c4e 100644 --- a/src/m_Do/m_Do_main.cpp +++ b/src/m_Do/m_Do_main.cpp @@ -366,6 +366,8 @@ int game_main(int argc, char* argv[]) { fflush(stdout); fflush(stderr); + mDoMch_Destroy(); + // Notifies all CVs and causes threads to exit OSResetSystem(OS_RESET_SHUTDOWN, 0, 0); From f723ab0a0b144e1d91fc4d4a281649724c0c0dee Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Tue, 7 Apr 2026 20:12:52 +0200 Subject: [PATCH 306/350] Fix movie running at uncapped FPS Fixes #267 --- src/d/actor/d_a_movie_player.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/d/actor/d_a_movie_player.cpp b/src/d/actor/d_a_movie_player.cpp index 4573684b2f..c3074ac877 100644 --- a/src/d/actor/d_a_movie_player.cpp +++ b/src/d/actor/d_a_movie_player.cpp @@ -4473,7 +4473,9 @@ int daMP_c::daMP_c_Get_arg_movieNo() { int daMP_c::daMP_c_Init() { JUT_ASSERT(9469, m_myObj == NULL); +#if !TARGET_PC // We don't properly simulate retrace interval so this doesn't work. mDoGph_gInf_c::setFrameRate(1); +#endif daMP_Fail_alloc = FALSE; int demoNo = daMP_c_Get_arg_demoNo(); From 2d7c98e0c860310d3216183174220a197d1d779f Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Tue, 7 Apr 2026 20:36:44 +0200 Subject: [PATCH 307/350] Fix bigger wallet type display in collection screen Fixes https://github.com/TwilitRealm/dusk/issues/223 --- include/d/d_menu_collect.h | 4 ++++ src/d/d_menu_collect.cpp | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/include/d/d_menu_collect.h b/include/d/d_menu_collect.h index 587fd5c103..55d5373c71 100644 --- a/include/d/d_menu_collect.h +++ b/include/d/d_menu_collect.h @@ -50,7 +50,11 @@ public: void changeShield(); void changeClothe(); void setArrowMaxNum(u8); +#if TARGET_PC + void setWalletSizeNum(u16); +#else void setWalletMaxNum(u16); +#endif void setSmellType(); void setHeartPiece(); void setPohMaxNum(u8); diff --git a/src/d/d_menu_collect.cpp b/src/d/d_menu_collect.cpp index ed5586e866..72a390a0cc 100644 --- a/src/d/d_menu_collect.cpp +++ b/src/d/d_menu_collect.cpp @@ -590,6 +590,15 @@ void dMenu_Collect2D_c::screenSet() { field_0x184[4][2] = 0x196; field_0x184[5][2] = 0x195; field_0x184[6][2] = 0; +#if TARGET_PC // Since we allow changing wallet sizes, do something more robust. + if (dComIfGs_getWalletSize() == WALLET) { + field_0x184[0][3] = 0x199; + } else if (dComIfGs_getWalletSize() == BIG_WALLET) { + field_0x184[0][3] = 0x19a; + } else { + field_0x184[0][3] = 0x19b; + } +#else if (dComIfGs_getRupeeMax() == WALLET_MAX) { field_0x184[0][3] = 0x199; } else if (dComIfGs_getRupeeMax() == BIG_WALLET_MAX) { @@ -597,6 +606,7 @@ void dMenu_Collect2D_c::screenSet() { } else { field_0x184[0][3] = 0x19b; } +#endif if (dComIfGs_getArrowMax() == QUIVER_MAX) { field_0x184[1][3] = 0x1b9; } else if (dComIfGs_getArrowMax() == BIG_QUIVER_MAX) { @@ -757,7 +767,11 @@ void dMenu_Collect2D_c::screenSet() { setItemNameString(mCursorX, mCursorY); cursorPosSet(); setArrowMaxNum(dComIfGs_getArrowMax()); +#if TARGET_PC + setWalletSizeNum(dComIfGs_getWalletSize()); +#else setWalletMaxNum(dComIfGs_getRupeeMax()); +#endif setSmellType(); setHeartPiece(); setPohMaxNum(dComIfGs_getPohSpiritNum()); @@ -1242,6 +1256,27 @@ void dMenu_Collect2D_c::setArrowMaxNum(u8 param_0) { } } +#if TARGET_PC +void dMenu_Collect2D_c::setWalletSizeNum(u16 i_walletSize) { + switch (i_walletSize) { + case WALLET: + mpScreen->search(MULTI_CHAR('item_1_0'))->show(); + mpScreen->search(MULTI_CHAR('item_1_1'))->hide(); + mpScreen->search(MULTI_CHAR('item_1_2'))->hide(); + break; + case BIG_WALLET: + mpScreen->search(MULTI_CHAR('item_1_0'))->hide(); + mpScreen->search(MULTI_CHAR('item_1_1'))->show(); + mpScreen->search(MULTI_CHAR('item_1_2'))->hide(); + break; + case GIANT_WALLET: + mpScreen->search(MULTI_CHAR('item_1_0'))->hide(); + mpScreen->search(MULTI_CHAR('item_1_1'))->hide(); + mpScreen->search(MULTI_CHAR('item_1_2'))->show(); + break; + } +} +#else void dMenu_Collect2D_c::setWalletMaxNum(u16 i_walletSize) { switch (i_walletSize) { case 300: @@ -1261,6 +1296,7 @@ void dMenu_Collect2D_c::setWalletMaxNum(u16 i_walletSize) { break; } } +#endif void dMenu_Collect2D_c::setSmellType() { static const u64 smell_tag[5] = { From 9cf6f3497db9e59ea431d6b8970f842ef84204cc Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Tue, 7 Apr 2026 21:43:38 +0200 Subject: [PATCH 308/350] Fix JUTResFont performance Just store the GXTexObjs in a separately allocated hashmap, so they can be persisted cross-frame. Easy. Fixes #227 --- .../include/JSystem/JUtility/JUTResFont.h | 8 ++++ libs/JSystem/src/JUtility/JUTResFont.cpp | 45 +++++++++++++++++-- 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/libs/JSystem/include/JSystem/JUtility/JUTResFont.h b/libs/JSystem/include/JSystem/JUtility/JUTResFont.h index 09d3303da0..8709822d7b 100644 --- a/libs/JSystem/include/JSystem/JUtility/JUTResFont.h +++ b/libs/JSystem/include/JSystem/JUtility/JUTResFont.h @@ -18,6 +18,10 @@ struct BlockHeader { BE(u32) size; }; +#if TARGET_PC +struct GlyphTextures; +#endif + /** * @ingroup jsystem-jutility * @@ -64,7 +68,11 @@ public: // some types uncertain, may need to be fixed /* 0x1C */ int mWidth; /* 0x20 */ int mHeight; +#if TARGET_PC + GlyphTextures* mGlyphTextures; +#else /* 0x24 */ TGXTexObj mTexObj; +#endif /* 0x44 */ int mTexPageIdx; /* 0x48 */ const ResFONT* mResFont; /* 0x4C */ ResFONT::INF1* mInf1Ptr; diff --git a/libs/JSystem/src/JUtility/JUTResFont.cpp b/libs/JSystem/src/JUtility/JUTResFont.cpp index e6410defdc..10443d6404 100644 --- a/libs/JSystem/src/JUtility/JUTResFont.cpp +++ b/libs/JSystem/src/JUtility/JUTResFont.cpp @@ -6,19 +6,39 @@ #include "JSystem/JUtility/JUTAssert.h" #include "JSystem/JUtility/JUTConsole.h" #include +#include "absl/container/node_hash_map.h" + +#if TARGET_PC +struct GlyphTextures { + absl::node_hash_map textures; +}; +#endif JUTResFont::JUTResFont() { +#if TARGET_PC + mGlyphTextures = new GlyphTextures(); +#endif initialize_state(); JUTFont::initialize_state(); } JUTResFont::JUTResFont(const ResFONT* pFont, JKRHeap* pHeap) { +#if TARGET_PC + mGlyphTextures = new GlyphTextures(); +#endif initialize_state(); JUTFont::initialize_state(); initiate(pFont, pHeap); } JUTResFont::~JUTResFont() { +#if TARGET_PC + for (auto& pair : mGlyphTextures->textures) { + pair.second.reset(); + } + delete mGlyphTextures; +#endif + if (mValid) { delete_and_initialize(); JUTFont::initialize_state(); @@ -414,12 +434,30 @@ void JUTResFont::loadImage(int code, GXTexMapID id){ mWidth = cellCol * cellW; mHeight = cellRow * cellH; +#if TARGET_PC + const auto found = mGlyphTextures->textures.find(code); + GXTexObj* texObj; + if (found == mGlyphTextures->textures.end()) { + texObj = &mGlyphTextures->textures[code]; + void* pImg = &mpGlyphBlocks[i]->data[pageIdx * mpGlyphBlocks[i]->textureSize]; + GXInitTexObj(texObj, pImg, mpGlyphBlocks[i]->textureWidth, + mpGlyphBlocks[i]->textureHeight, (GXTexFmt)(u16)mpGlyphBlocks[i]->textureFormat, + GX_CLAMP, GX_CLAMP, 0); + + GXInitTexObjLOD(texObj, GX_LINEAR, GX_LINEAR, 0.0f, 0.0f, 0.0f, 0U, 0U, GX_ANISO_1); + } else { + texObj = &found->second; + } + + GXLoadTexObj(texObj, id); + + // Gets used by some other code. + mTexPageIdx = pageIdx; + field_0x66 = i; +#else if (pageIdx != mTexPageIdx || i != field_0x66) { void* pImg = &mpGlyphBlocks[i]->data[pageIdx * mpGlyphBlocks[i]->textureSize]; -#ifdef TARGET_PC - mTexObj.reset(); -#endif GXInitTexObj(&mTexObj, pImg, mpGlyphBlocks[i]->textureWidth, mpGlyphBlocks[i]->textureHeight, (GXTexFmt)(u16)mpGlyphBlocks[i]->textureFormat, GX_CLAMP, GX_CLAMP, 0); @@ -430,6 +468,7 @@ void JUTResFont::loadImage(int code, GXTexMapID id){ } GXLoadTexObj(&mTexObj, id); +#endif } } From 3306c0be944fc827055290212256dc1be67a3a72 Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Tue, 7 Apr 2026 16:03:12 -0400 Subject: [PATCH 309/350] Don't play 2 sounds when starting a save with TV Settings off --- src/d/d_file_select.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/d/d_file_select.cpp b/src/d/d_file_select.cpp index f63403d886..1f6318dad1 100644 --- a/src/d/d_file_select.cpp +++ b/src/d/d_file_select.cpp @@ -1193,7 +1193,13 @@ void dFile_select_c::menuSelect() { // Handles copy / start / delete actions depending on which menu is selected from menuSelect void dFile_select_c::menuSelectStart() { - mDoAud_seStart(Z2SE_SY_CURSOR_OK, NULL, 0, 0); + #if TARGET_PC + if (!dusk::getSettings().game.hideTvSettingsScreen || mSelectMenuNum != 1) { + mDoAud_seStart(Z2SE_SY_CURSOR_OK, NULL, 0, 0); + } + #else + mDoAud_seStart(Z2SE_SY_CURSOR_OK, NULL, 0, 0); + #endif if (mSelectMenuNum == 1) { dComIfGs_setCardToMemory((u8*)mSaveData, mSelectNum); From 4737e079d9ec2065579548ba119d59f3a214c489 Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Tue, 7 Apr 2026 16:04:14 -0400 Subject: [PATCH 310/350] fix line denting --- src/d/d_file_select.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/d/d_file_select.cpp b/src/d/d_file_select.cpp index 1f6318dad1..8d4f98187b 100644 --- a/src/d/d_file_select.cpp +++ b/src/d/d_file_select.cpp @@ -1195,8 +1195,8 @@ void dFile_select_c::menuSelect() { void dFile_select_c::menuSelectStart() { #if TARGET_PC if (!dusk::getSettings().game.hideTvSettingsScreen || mSelectMenuNum != 1) { - mDoAud_seStart(Z2SE_SY_CURSOR_OK, NULL, 0, 0); - } + mDoAud_seStart(Z2SE_SY_CURSOR_OK, NULL, 0, 0); + } #else mDoAud_seStart(Z2SE_SY_CURSOR_OK, NULL, 0, 0); #endif From 393bf7b77a1b07218b81e09ef0a449c1790906ec Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Tue, 7 Apr 2026 22:17:48 +0200 Subject: [PATCH 311/350] Fix Aurora draw call display in performance overlay It was getting reset at the start of the frame... Just cache the values. --- include/dusk/dusk.h | 6 ++++++ src/dusk/imgui/ImGuiMenuTools.cpp | 27 ++++++++++++++------------- src/m_Do/m_Do_main.cpp | 2 ++ 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/include/dusk/dusk.h b/include/dusk/dusk.h index 7fc2a23070..832b656160 100644 --- a/include/dusk/dusk.h +++ b/include/dusk/dusk.h @@ -3,7 +3,13 @@ #include +#include "aurora/gfx.h" + extern AuroraInfo auroraInfo; extern const char* configPath; +namespace dusk { + extern AuroraStats lastFrameAuroraStats; +} + #endif // DUSK_DUSK_H diff --git a/src/dusk/imgui/ImGuiMenuTools.cpp b/src/dusk/imgui/ImGuiMenuTools.cpp index 7478adc0e5..4a455672a9 100644 --- a/src/dusk/imgui/ImGuiMenuTools.cpp +++ b/src/dusk/imgui/ImGuiMenuTools.cpp @@ -10,6 +10,7 @@ #include "d/actor/d_a_alink.h" #include "d/actor/d_a_horse.h" #include "d/d_com_inf_game.h" +#include "dusk/dusk.h" #include "m_Do/m_Do_main.h" namespace dusk { @@ -109,31 +110,31 @@ namespace dusk { } hasPrevious = true; - AuroraStats const* stats = aurora_get_stats(); + const auto& stats = lastFrameAuroraStats; ImGuiStringViewText( - fmt::format(FMT_STRING("Queued pipelines: {}\n"), stats->queuedPipelines)); + fmt::format(FMT_STRING("Queued pipelines: {}\n"), stats.queuedPipelines)); ImGuiStringViewText( - fmt::format(FMT_STRING("Done pipelines: {}\n"), stats->createdPipelines)); + fmt::format(FMT_STRING("Done pipelines: {}\n"), stats.createdPipelines)); ImGuiStringViewText( - fmt::format(FMT_STRING("Draw call count: {}\n"), stats->drawCallCount)); + fmt::format(FMT_STRING("Draw call count: {}\n"), stats.drawCallCount)); ImGuiStringViewText(fmt::format(FMT_STRING("Merged draw calls: {}\n"), - stats->mergedDrawCallCount)); + stats.mergedDrawCallCount)); ImGuiStringViewText(fmt::format(FMT_STRING("Vertex size: {}\n"), - BytesToString(stats->lastVertSize))); + BytesToString(stats.lastVertSize))); ImGuiStringViewText(fmt::format(FMT_STRING("Uniform size: {}\n"), - BytesToString(stats->lastUniformSize))); + BytesToString(stats.lastUniformSize))); ImGuiStringViewText(fmt::format(FMT_STRING("Index size: {}\n"), - BytesToString(stats->lastIndexSize))); + BytesToString(stats.lastIndexSize))); ImGuiStringViewText(fmt::format(FMT_STRING("Storage size: {}\n"), - BytesToString(stats->lastStorageSize))); + BytesToString(stats.lastStorageSize))); ImGuiStringViewText(fmt::format(FMT_STRING("Tex upload size: {}\n"), - BytesToString(stats->lastTextureUploadSize))); + BytesToString(stats.lastTextureUploadSize))); ImGuiStringViewText(fmt::format( FMT_STRING("Total: {}\n"), - BytesToString(stats->lastVertSize + stats->lastUniformSize + - stats->lastIndexSize + stats->lastStorageSize + - stats->lastTextureUploadSize))); + BytesToString(stats.lastVertSize + stats.lastUniformSize + + stats.lastIndexSize + stats.lastStorageSize + + stats.lastTextureUploadSize))); // TODO: persist to config ShowCornerContextMenu(m_debugOverlayCorner, m_cameraOverlayCorner); diff --git a/src/m_Do/m_Do_main.cpp b/src/m_Do/m_Do_main.cpp index 8742436c4e..e42e6ea98b 100644 --- a/src/m_Do/m_Do_main.cpp +++ b/src/m_Do/m_Do_main.cpp @@ -110,6 +110,7 @@ s32 LOAD_COPYDATE(void*) { } AuroraInfo auroraInfo; +AuroraStats dusk::lastFrameAuroraStats; const char* configPath; void main01(void) { @@ -182,6 +183,7 @@ void main01(void) { VIWaitForRetrace(); #if TARGET_PC + dusk::lastFrameAuroraStats = *aurora_get_stats(); if (!aurora_begin_frame()) { DuskLog.debug("aurora_begin_frame returned false, skipping draw this frame"); continue; From d97d55ea087221eb36081b2d39d06ffa031f50c4 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Tue, 7 Apr 2026 22:37:57 +0200 Subject: [PATCH 312/350] Fix minimap "don't render" code not always working due to rounding errors Significant performance boost in some areas like Gerudo Desert when map isn't visible. --- src/d/d_meter_map.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/d/d_meter_map.cpp b/src/d/d_meter_map.cpp index 6b78e2242b..b5900a4be5 100644 --- a/src/d/d_meter_map.cpp +++ b/src/d/d_meter_map.cpp @@ -596,7 +596,8 @@ void dMeterMap_c::_draw() { #if TARGET_PC // Optimization: don't draw map if it's off-screen/invisible. // Especially useful in debug builds on Hyrule field etc., it's slow! - if ((!mMapIsInside && mSlidePositionOffset == getDispPosOutSide_OffsetX()) || mMapAlpha == 0) { + // That +3 is an arbitrary bias to avoid rounding issues causing this to fail. + if ((!mMapIsInside && mSlidePositionOffset <= getDispPosOutSide_OffsetX() + 3) || mMapAlpha == 0) { return; } #endif From f195bce7e095ee6496801140203b9fe374ec6a75 Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Tue, 7 Apr 2026 16:59:19 -0400 Subject: [PATCH 313/350] Does this fix fps for anyone? --- src/d/d_map.cpp | 4 ++++ src/d/d_meter_map.cpp | 9 --------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/d/d_map.cpp b/src/d/d_map.cpp index 5da8f95a46..3fe638cd2f 100644 --- a/src/d/d_map.cpp +++ b/src/d/d_map.cpp @@ -542,6 +542,10 @@ void renderingAmap_c::rendering(dDrawPath_c::poly_class const* i_poly) { bool renderingAmap_c::isDrawOutSideTrim() { bool rt = false; + #if TARGET_PC + return 0; + #endif + if (getDispType() == 0 || getDispType() == 4 || getDispType() == 3 || getDispType() == 2 || getDispType() == 5) { diff --git a/src/d/d_meter_map.cpp b/src/d/d_meter_map.cpp index b5900a4be5..419235c970 100644 --- a/src/d/d_meter_map.cpp +++ b/src/d/d_meter_map.cpp @@ -593,15 +593,6 @@ void dMeterMap_c::_draw() { dComIfGd_set2DOpa(this); } #else -#if TARGET_PC - // Optimization: don't draw map if it's off-screen/invisible. - // Especially useful in debug builds on Hyrule field etc., it's slow! - // That +3 is an arbitrary bias to avoid rounding issues causing this to fail. - if ((!mMapIsInside && mSlidePositionOffset <= getDispPosOutSide_OffsetX() + 3) || mMapAlpha == 0) { - return; - } -#endif - mMap->_draw(); dComIfGd_set2DOpa(this); #endif From 3cc3f093196b6e2b47b464188f7fbd5184b0073b Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Tue, 7 Apr 2026 18:22:41 -0400 Subject: [PATCH 314/350] rename name of button to "Force 4:3 Aspect Ratio" --- src/dusk/imgui/ImGuiMenuGame.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dusk/imgui/ImGuiMenuGame.cpp b/src/dusk/imgui/ImGuiMenuGame.cpp index 50877caad9..d9b521d98a 100644 --- a/src/dusk/imgui/ImGuiMenuGame.cpp +++ b/src/dusk/imgui/ImGuiMenuGame.cpp @@ -54,7 +54,7 @@ namespace dusk { } bool lockAspect = getSettings().video.lockAspectRatio; - if (ImGui::Checkbox("Lock Aspect Ratio", &lockAspect)) { + if (ImGui::Checkbox("Force 4:3 Aspect Ratio", &lockAspect)) { getSettings().video.lockAspectRatio.setValue(lockAspect); if (lockAspect) { From 4e052d0aca9a241cb8933e2159a8f520eb756239 Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Tue, 7 Apr 2026 18:49:24 -0400 Subject: [PATCH 315/350] Move audio settings to init and set rather than update, improving performance --- src/dusk/imgui/ImGuiConfig.hpp | 20 ++++++++++++++++---- src/dusk/imgui/ImGuiConsole.cpp | 5 +++-- src/dusk/imgui/ImGuiMenuGame.cpp | 9 +++++++-- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/dusk/imgui/ImGuiConfig.hpp b/src/dusk/imgui/ImGuiConfig.hpp index fc5aa34bbd..e3e80b9920 100644 --- a/src/dusk/imgui/ImGuiConfig.hpp +++ b/src/dusk/imgui/ImGuiConfig.hpp @@ -5,36 +5,48 @@ #include "imgui.h" namespace dusk::config { - inline void ImGuiCheckbox(const char* title, ConfigVar& var) { + inline bool ImGuiCheckbox(const char* title, ConfigVar& var) { bool copy = var.getValue(); if (ImGui::Checkbox(title, ©)) { var.setValue(copy); Save(); + return true; } + + return false; } - static void ImGuiSliderFloat(const char* label, ConfigVar& var, float v_min, float v_max, const char* format = "%.3f", ImGuiSliderFlags flags = 0) { + static bool ImGuiSliderFloat(const char* label, ConfigVar& var, float v_min, float v_max, const char* format = "%.3f", ImGuiSliderFlags flags = 0) { float val = var; if (ImGui::SliderFloat(label, &val, v_min, v_max, format, flags)) { var.setValue(val); Save(); + return true; } + + return false; } - static void ImGuiSliderInt(const char* label, ConfigVar& var, int v_min, int v_max, const char* format = "%d", ImGuiSliderFlags flags = 0) { + static bool ImGuiSliderInt(const char* label, ConfigVar& var, int v_min, int v_max, const char* format = "%d", ImGuiSliderFlags flags = 0) { int val = var; if (ImGui::SliderInt(label, &val, v_min, v_max, format, flags)) { var.setValue(val); Save(); + return true; } + + return false; } - static void ImGuiMenuItem(const char* label, const char* shortcut, ConfigVar& p_selected, bool enabled = true) { + static bool ImGuiMenuItem(const char* label, const char* shortcut, ConfigVar& p_selected, bool enabled = true) { bool copy = p_selected.getValue(); if (ImGui::MenuItem(label, shortcut, ©, enabled)) { p_selected.setValue(copy); Save(); + return true; } + + return false; } } diff --git a/src/dusk/imgui/ImGuiConsole.cpp b/src/dusk/imgui/ImGuiConsole.cpp index e0ca351e4f..35609f641c 100644 --- a/src/dusk/imgui/ImGuiConsole.cpp +++ b/src/dusk/imgui/ImGuiConsole.cpp @@ -186,11 +186,12 @@ namespace dusk { } else { VIUnlockAspectRatio(); } + + dusk::audio::SetMasterVolume(dusk::getSettings().audio.masterVolume / 100.0f); + dusk::audio::SetEnableReverb(dusk::getSettings().audio.enableReverb); } void ImGuiConsole::UpdateSettings() { - dusk::audio::SetMasterVolume(dusk::getSettings().audio.masterVolume / 100.0f); - dusk::audio::SetEnableReverb(dusk::getSettings().audio.enableReverb); getTransientSettings().skipFrameRateLimit = getSettings().game.enableTurboKeybind && ImGui::IsKeyDown(ImGuiKey_Tab); } diff --git a/src/dusk/imgui/ImGuiMenuGame.cpp b/src/dusk/imgui/ImGuiMenuGame.cpp index d9b521d98a..050e5c5c77 100644 --- a/src/dusk/imgui/ImGuiMenuGame.cpp +++ b/src/dusk/imgui/ImGuiMenuGame.cpp @@ -71,8 +71,13 @@ namespace dusk { if (ImGui::BeginMenu("Audio")) { ImGui::Text("Master Volume"); - config::ImGuiSliderInt("##masterVolume", getSettings().audio.masterVolume, 0, 100); - config::ImGuiCheckbox("Enable Reverb", getSettings().audio.enableReverb); + if (config::ImGuiSliderInt("##masterVolume", getSettings().audio.masterVolume, 0, 100)) { + dusk::audio::SetMasterVolume(dusk::getSettings().audio.masterVolume / 100.0f); + } + + if (config::ImGuiCheckbox("Enable Reverb", getSettings().audio.enableReverb)) { + dusk::audio::SetEnableReverb(dusk::getSettings().audio.enableReverb); + } /* // TODO: Implement additional settings ImGui::Text("Main Music Volume"); From 0ac464cbbcf4102dd9000790fb3aed9aa252bd3e Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Tue, 7 Apr 2026 18:51:04 -0400 Subject: [PATCH 316/350] remove useless dusk calls --- src/dusk/imgui/ImGuiConsole.cpp | 4 ++-- src/dusk/imgui/ImGuiMenuGame.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/dusk/imgui/ImGuiConsole.cpp b/src/dusk/imgui/ImGuiConsole.cpp index 35609f641c..bc86b015d8 100644 --- a/src/dusk/imgui/ImGuiConsole.cpp +++ b/src/dusk/imgui/ImGuiConsole.cpp @@ -187,8 +187,8 @@ namespace dusk { VIUnlockAspectRatio(); } - dusk::audio::SetMasterVolume(dusk::getSettings().audio.masterVolume / 100.0f); - dusk::audio::SetEnableReverb(dusk::getSettings().audio.enableReverb); + dusk::audio::SetMasterVolume(getSettings().audio.masterVolume / 100.0f); + dusk::audio::SetEnableReverb(getSettings().audio.enableReverb); } void ImGuiConsole::UpdateSettings() { diff --git a/src/dusk/imgui/ImGuiMenuGame.cpp b/src/dusk/imgui/ImGuiMenuGame.cpp index 050e5c5c77..cbe26ba81c 100644 --- a/src/dusk/imgui/ImGuiMenuGame.cpp +++ b/src/dusk/imgui/ImGuiMenuGame.cpp @@ -72,11 +72,11 @@ namespace dusk { if (ImGui::BeginMenu("Audio")) { ImGui::Text("Master Volume"); if (config::ImGuiSliderInt("##masterVolume", getSettings().audio.masterVolume, 0, 100)) { - dusk::audio::SetMasterVolume(dusk::getSettings().audio.masterVolume / 100.0f); + dusk::audio::SetMasterVolume(getSettings().audio.masterVolume / 100.0f); } if (config::ImGuiCheckbox("Enable Reverb", getSettings().audio.enableReverb)) { - dusk::audio::SetEnableReverb(dusk::getSettings().audio.enableReverb); + dusk::audio::SetEnableReverb(getSettings().audio.enableReverb); } /* // TODO: Implement additional settings From 9716dc5d874713b78e074cfe8430eb61fc0b7a73 Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Tue, 7 Apr 2026 19:19:28 -0400 Subject: [PATCH 317/350] make the game over screen widescreen --- src/d/d_gameover.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/d/d_gameover.cpp b/src/d/d_gameover.cpp index 693bcf371e..baec970a32 100644 --- a/src/d/d_gameover.cpp +++ b/src/d/d_gameover.cpp @@ -432,7 +432,14 @@ void dDlst_GameOverScrnDraw_c::draw() { img_white.a = 255; mpBackImg->setBlackWhite(img_black, img_white); + + #if TARGET_PC + mpBackImg->draw(mDoGph_gInf_c::getMinXF(), mDoGph_gInf_c::getMinYF(), + mDoGph_gInf_c::getWidthF(), mDoGph_gInf_c::getHeightF(), false, false, + false); + #else mpBackImg->draw(0.0f, 0.0f, FB_WIDTH, FB_HEIGHT, false, false, false); + #endif } else { JUtility::TColor img_black; JUtility::TColor img_white; @@ -447,7 +454,13 @@ void dDlst_GameOverScrnDraw_c::draw() { img_white.b = l_HIO.mWhite.b; img_white.a = l_HIO.mWhite.a; + #if TARGET_PC + mpBackImg->draw(mDoGph_gInf_c::getMinXF(), mDoGph_gInf_c::getMinYF(), + mDoGph_gInf_c::getWidthF(), mDoGph_gInf_c::getHeightF(), false, false, + false); + #else mpBackImg->draw(0.0f, 0.0f, FB_WIDTH, FB_HEIGHT, false, false, false); + #endif static f32 offset[8] = {-138.0f, -96.0f, -56.0f, -18.0f, 42.0f, 75.0f, 110.0f, 143.0f}; From 450dc89a852524a59ef7e1fa5dfc56fefd869641 Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Tue, 7 Apr 2026 19:23:41 -0400 Subject: [PATCH 318/350] Also fix buttons placement in the item menu. --- src/d/d_meter_HIO.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/d/d_meter_HIO.cpp b/src/d/d_meter_HIO.cpp index 94b1c79420..dbfe11cf53 100644 --- a/src/d/d_meter_HIO.cpp +++ b/src/d/d_meter_HIO.cpp @@ -2292,6 +2292,7 @@ void dMeter_drawHIO_c::updateOnWide() { g_drawHIO = {}; // this might be a bad idea g_drawHIO.mMainHUDButtonsPosX = mDoGph_gInf_c::ScaleHUDXRight(g_drawHIO.mMainHUDButtonsPosX); + g_drawHIO.mRingHUDButtonsPosX = mDoGph_gInf_c::ScaleHUDXRight(g_drawHIO.mRingHUDButtonsPosX); g_drawHIO.mRupeeKeyPosX = mDoGph_gInf_c::ScaleHUDXRight(g_drawHIO.mRupeeKeyPosX); g_drawHIO.mButtonCrossOFFPosX = mDoGph_gInf_c::ScaleHUDXLeft(g_drawHIO.mButtonCrossOFFPosX); g_drawHIO.mButtonCrossONPosX = mDoGph_gInf_c::ScaleHUDXLeft(g_drawHIO.mButtonCrossONPosX); From 8062cbf009b951b71d794414e311d63854542fb1 Mon Sep 17 00:00:00 2001 From: madeline Date: Tue, 7 Apr 2026 21:39:28 -0700 Subject: [PATCH 319/350] instant saves as a toggle instead of default --- include/dusk/settings.h | 1 + src/d/d_menu_save.cpp | 13 +++++-------- src/dusk/imgui/ImGuiMenuEnhancements.cpp | 5 +++++ src/dusk/settings.cpp | 2 ++ 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/include/dusk/settings.h b/include/dusk/settings.h index 2436be70f5..12fd79c5a6 100644 --- a/include/dusk/settings.h +++ b/include/dusk/settings.h @@ -43,6 +43,7 @@ struct UserSettings { ConfigVar fastClimbing; ConfigVar noMissClimbing; ConfigVar fastTears; + ConfigVar instantSaves; // Preferences ConfigVar enableMirrorMode; diff --git a/src/d/d_menu_save.cpp b/src/d/d_menu_save.cpp index eb7b6a35b8..618677866b 100644 --- a/src/d/d_menu_save.cpp +++ b/src/d/d_menu_save.cpp @@ -18,6 +18,7 @@ #include "m_Do/m_Do_controller_pad.h" #include "m_Do/m_Do_graphic.h" #include "d/d_msg_scrn_explain.h" +#include "dusk/settings.h" #include "JSystem/J2DGraph/J2DAnmLoader.h" #include "f_op/f_op_msg_mng.h" @@ -56,11 +57,7 @@ static dMs_HIO_c g_msHIO; dMs_HIO_c::dMs_HIO_c() { mDisplayWaitFrames = 15; - #if TARGET_PC - mCardWaitFrames = 0; - #else mCardWaitFrames = 90; - #endif mEffectDispFrames = 5; mCharSwitchFrames = 5; mSelectIcon = 5; @@ -1186,7 +1183,7 @@ void dMenu_save_c::cardFormatYesSel2Disp() { bool moveAnm = yesnoMenuMoveAnm(); if (txtChangeAnm == true && moveAnm == true) { - mWaitTimer = g_msHIO.mCardWaitFrames; + mWaitTimer = dusk::getSettings().game.instantSaves ? 0 : g_msHIO.mCardWaitFrames; g_mDoMemCd_control.command_format(); mMenuProc = PROC_MEMCARD_FORMAT; } @@ -1258,7 +1255,7 @@ void dMenu_save_c::makeGameFileDisp() { bool ketteiDispAnm = ketteiTxtDispAnm(); if (txtChangeAnm == true && moveAnm == true && ketteiDispAnm == true) { - mWaitTimer = g_msHIO.mCardWaitFrames; + mWaitTimer = dusk::getSettings().game.instantSaves ? 0 : g_msHIO.mCardWaitFrames; setInitSaveData(); dataSave(); mMenuProc = PROC_MEMCARD_MAKE_GAME_FILE; @@ -1952,7 +1949,7 @@ void dMenu_save_c::saveMoveDisp() { if (headerTxtChanged == true && yesnoAnmComplete == true && ketteiAnmComplete == true && modoruAnmComplete == 1 && check == 1) { - mWaitTimer = g_msHIO.mCardWaitFrames; + mWaitTimer = dusk::getSettings().game.instantSaves ? 0 : g_msHIO.mCardWaitFrames; dataWrite(); mMenuProc = PROC_MEMCARD_DATA_SAVE_WAIT; } @@ -1970,7 +1967,7 @@ void dMenu_save_c::saveMoveDisp2() { if (headerTxtChanged == true && dataMoveAnm == true && wakuAnmComplete == true && ketteiAnmComplete == true && modoruAnmComplete == 1 && check == 1) { - mWaitTimer = g_msHIO.mCardWaitFrames; + mWaitTimer = dusk::getSettings().game.instantSaves ? 0 : g_msHIO.mCardWaitFrames; dataWrite(); mMenuProc = PROC_MEMCARD_DATA_SAVE_WAIT; } diff --git a/src/dusk/imgui/ImGuiMenuEnhancements.cpp b/src/dusk/imgui/ImGuiMenuEnhancements.cpp index e9404e2476..b917fefd39 100644 --- a/src/dusk/imgui/ImGuiMenuEnhancements.cpp +++ b/src/dusk/imgui/ImGuiMenuEnhancements.cpp @@ -52,6 +52,11 @@ namespace dusk { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Hides the TV calibration screen shown when loading a save"); } + + config::ImGuiCheckbox("Instant Saves", getSettings().game.instantSaves); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Skip the delay when writing to the Memory Card"); + } ImGui::EndMenu(); } diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index d923a2b00e..186c6c9ccf 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -31,6 +31,7 @@ UserSettings g_userSettings = { .fastClimbing {"game.fastClimbing", false}, .noMissClimbing {"game.noMissClimbing", false}, .fastTears {"game.fastTears", false}, + .instantSaves {"game.instantSaves", false}, // Preferences .enableMirrorMode {"game.enableMirrorMode", false}, @@ -85,6 +86,7 @@ void registerSettings() { Register(g_userSettings.game.instantDeath); Register(g_userSettings.game.fastClimbing); Register(g_userSettings.game.fastTears); + Register(g_userSettings.game.instantSaves); Register(g_userSettings.game.enableMirrorMode); Register(g_userSettings.game.invertCameraXAxis); Register(g_userSettings.game.enableBloom); From 7b4d9ce807f3e0b22d6b17a033acdfd6d41b3115 Mon Sep 17 00:00:00 2001 From: madeline Date: Tue, 7 Apr 2026 22:11:50 -0700 Subject: [PATCH 320/350] frame usage debug info --- include/dusk/dusk.h | 1 + libs/JSystem/src/JFramework/JFWDisplay.cpp | 3 +++ src/dusk/imgui/ImGuiMenuTools.cpp | 1 + src/m_Do/m_Do_main.cpp | 1 + 4 files changed, 6 insertions(+) diff --git a/include/dusk/dusk.h b/include/dusk/dusk.h index a466f10a88..b751990d9c 100644 --- a/include/dusk/dusk.h +++ b/include/dusk/dusk.h @@ -10,6 +10,7 @@ extern const char* configPath; namespace dusk { extern AuroraStats lastFrameAuroraStats; + extern float frameUsagePct; } constexpr u32 defaultWindowWidth = 608; diff --git a/libs/JSystem/src/JFramework/JFWDisplay.cpp b/libs/JSystem/src/JFramework/JFWDisplay.cpp index 6a257f5244..1e5139ec0c 100644 --- a/libs/JSystem/src/JFramework/JFWDisplay.cpp +++ b/libs/JSystem/src/JFramework/JFWDisplay.cpp @@ -13,6 +13,7 @@ #include "JSystem/JUtility/JUTDbPrint.h" #include "JSystem/JUtility/JUTProcBar.h" #include "aurora/aurora.h" +#include "dusk/dusk.h" #include "dusk/gx_helper.h" #include "dusk/logging.h" #include "dusk/settings.h" @@ -358,10 +359,12 @@ static void waitForTick(u32 p1, u16 p2) { { static OSTime nextTick = OSGetTime(); OSTime time = OSGetTime(); + OSTime waitTime = (nextTick > time) ? (nextTick - time) : 0; while (time < nextTick) { JFWDisplay::getManager()->threadSleep((nextTick - time)); time = OSGetTime(); } + dusk::frameUsagePct = 100.0f * (1.0f - (float)waitTime / (float)p1); nextTick = time + p1; } else { static u32 nextCount = VIGetRetraceCount(); diff --git a/src/dusk/imgui/ImGuiMenuTools.cpp b/src/dusk/imgui/ImGuiMenuTools.cpp index 4a455672a9..44e92a4082 100644 --- a/src/dusk/imgui/ImGuiMenuTools.cpp +++ b/src/dusk/imgui/ImGuiMenuTools.cpp @@ -97,6 +97,7 @@ namespace dusk { hasPrevious = true; ImGuiStringViewText(fmt::format(FMT_STRING("FPS: {:.2f}\n"), io.Framerate)); + ImGuiStringViewText(fmt::format(FMT_STRING("Frame usage: {:.1f}%\n"), frameUsagePct)); if (hasPrevious) { ImGui::Separator(); diff --git a/src/m_Do/m_Do_main.cpp b/src/m_Do/m_Do_main.cpp index 73298895ee..bcf958780e 100644 --- a/src/m_Do/m_Do_main.cpp +++ b/src/m_Do/m_Do_main.cpp @@ -111,6 +111,7 @@ s32 LOAD_COPYDATE(void*) { AuroraInfo auroraInfo; AuroraStats dusk::lastFrameAuroraStats; +float dusk::frameUsagePct = 0.0f; const char* configPath; void main01(void) { From 1a27ea4ad928cf47366542dcc4ad27a811bf3f41 Mon Sep 17 00:00:00 2001 From: madeline Date: Tue, 7 Apr 2026 22:15:19 -0700 Subject: [PATCH 321/350] add p1 == 0 logic --- libs/JSystem/src/JFramework/JFWDisplay.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libs/JSystem/src/JFramework/JFWDisplay.cpp b/libs/JSystem/src/JFramework/JFWDisplay.cpp index 1e5139ec0c..26788f9f38 100644 --- a/libs/JSystem/src/JFramework/JFWDisplay.cpp +++ b/libs/JSystem/src/JFramework/JFWDisplay.cpp @@ -377,6 +377,7 @@ static void waitForTick(u32 p1, u16 p2) { msg = 0; } } while (((intptr_t)msg - (intptr_t)nextCount) < 0); + dusk::frameUsagePct = 100.0f; nextCount = (intptr_t)msg + uVar1; } } From 3e12ff92fabb08d0fa0c725e647c0157b0fd7f52 Mon Sep 17 00:00:00 2001 From: madeline Date: Tue, 7 Apr 2026 23:45:43 -0700 Subject: [PATCH 322/350] get rid of half width in drawDepth2 --- src/m_Do/m_Do_graphic.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/m_Do/m_Do_graphic.cpp b/src/m_Do/m_Do_graphic.cpp index 5c535c2eb1..5887c22495 100644 --- a/src/m_Do/m_Do_graphic.cpp +++ b/src/m_Do/m_Do_graphic.cpp @@ -940,8 +940,15 @@ static void drawDepth2(view_class* param_0, view_port_class* param_1, int param_ GX_FALSE, 0); } + #if TARGET_PC + // use full size for pc for higher quality background elements + u16 halfWidth = width; + u16 halfHeight = height; + #else u16 halfWidth = width >> 1; u16 halfHeight = height >> 1; + #endif + GXRenderModeObj* sp24 = JUTGetVideoManager()->getRenderMode(); GXSetCopyFilter(GX_FALSE, NULL, GX_TRUE, sp24->vfilter); GXSetTexCopySrc(x_orig, y_orig_pos, width, height); From 24fb855663beb8e39b3b8e0b083bc9ba0ae67585 Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Wed, 8 Apr 2026 11:20:08 -0400 Subject: [PATCH 323/350] Oxygen & Tears of Light now widescreen --- src/d/d_meter_HIO.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/d/d_meter_HIO.cpp b/src/d/d_meter_HIO.cpp index dbfe11cf53..c23e6c3230 100644 --- a/src/d/d_meter_HIO.cpp +++ b/src/d/d_meter_HIO.cpp @@ -2293,7 +2293,10 @@ void dMeter_drawHIO_c::updateOnWide() { g_drawHIO.mMainHUDButtonsPosX = mDoGph_gInf_c::ScaleHUDXRight(g_drawHIO.mMainHUDButtonsPosX); g_drawHIO.mRingHUDButtonsPosX = mDoGph_gInf_c::ScaleHUDXRight(g_drawHIO.mRingHUDButtonsPosX); + g_drawHIO.mLightDrop.mVesselPosX = mDoGph_gInf_c::ScaleHUDXRight(g_drawHIO.mLightDrop.mVesselPosX); + g_drawHIO.mLightDrop.mVesselTalkPosX = mDoGph_gInf_c::ScaleHUDXRight(g_drawHIO.mLightDrop.mVesselTalkPosX); g_drawHIO.mRupeeKeyPosX = mDoGph_gInf_c::ScaleHUDXRight(g_drawHIO.mRupeeKeyPosX); + g_drawHIO.mOxygenMeterPosX = mDoGph_gInf_c::ScaleHUDXLeft(g_drawHIO.mOxygenMeterPosX); g_drawHIO.mButtonCrossOFFPosX = mDoGph_gInf_c::ScaleHUDXLeft(g_drawHIO.mButtonCrossOFFPosX); g_drawHIO.mButtonCrossONPosX = mDoGph_gInf_c::ScaleHUDXLeft(g_drawHIO.mButtonCrossONPosX); g_drawHIO.mLifeGaugePosX = mDoGph_gInf_c::ScaleHUDXLeft(g_drawHIO.mLifeGaugePosX); From 3559c72dc4377b8781e1a4f6040945f4f0e66459 Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Wed, 8 Apr 2026 11:39:53 -0400 Subject: [PATCH 324/350] Modified line width back to vanilla default --- src/d/d_map_path.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/d/d_map_path.cpp b/src/d/d_map_path.cpp index f7ce0f1a51..c57dd85a48 100644 --- a/src/d/d_map_path.cpp +++ b/src/d/d_map_path.cpp @@ -19,7 +19,7 @@ constexpr u16 kMapResolutionMultiplier = 4; // Line widths are relative to the framebuffer size. Since we're rendering to a separate // framebuffer, we have to scale them accordingly. The original game used about half of the // EFB for the map rendering, so this is a reasonable approximation. -constexpr u8 kMapLineWidthMultiplier = 2; +constexpr u8 kMapLineWidthMultiplier = 1; #endif void dMpath_n::dTexObjAggregate_c::create() { From db7732eb392cb5b15031f553cae6c28cfc45107a Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Wed, 8 Apr 2026 11:44:35 -0400 Subject: [PATCH 325/350] nvm that --- src/d/d_map_path.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/d/d_map_path.cpp b/src/d/d_map_path.cpp index c57dd85a48..f7ce0f1a51 100644 --- a/src/d/d_map_path.cpp +++ b/src/d/d_map_path.cpp @@ -19,7 +19,7 @@ constexpr u16 kMapResolutionMultiplier = 4; // Line widths are relative to the framebuffer size. Since we're rendering to a separate // framebuffer, we have to scale them accordingly. The original game used about half of the // EFB for the map rendering, so this is a reasonable approximation. -constexpr u8 kMapLineWidthMultiplier = 1; +constexpr u8 kMapLineWidthMultiplier = 2; #endif void dMpath_n::dTexObjAggregate_c::create() { From 8e797829f9966ee9d45f2d0a37d96d6072324994 Mon Sep 17 00:00:00 2001 From: Jeffrey Crowell Date: Wed, 8 Apr 2026 15:13:21 -0400 Subject: [PATCH 326/350] Fix Crash at the end of the Armogohma cutscene #282 zero stack buffers in JASSeqParser::cmdPrintf on PC builds use AVOID_UB instead of TARGET_PC --- libs/JSystem/src/JAudio2/JASSeqParser.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/libs/JSystem/src/JAudio2/JASSeqParser.cpp b/libs/JSystem/src/JAudio2/JASSeqParser.cpp index 376ec22b4c..b2077ce499 100644 --- a/libs/JSystem/src/JAudio2/JASSeqParser.cpp +++ b/libs/JSystem/src/JAudio2/JASSeqParser.cpp @@ -813,9 +813,15 @@ s32 JASSeqParser::cmdDump(JASTrack* param_0, u32* param_1) { } s32 JASSeqParser::cmdPrintf(JASTrack* param_0, u32* param_1) { +#if AVOID_UB + u8 stack_c[4] = {0}; + u32 stack_10[4] = {0}; + char buffer[128] = {0}; +#else u8 stack_c[4]; u32 stack_10[4]; char buffer[128]; +#endif JASSeqCtrl* seqCtrl = param_0->getSeqCtrl(); u32 r30 = 0; From 0fcd55def7ba9038b7eb497ca198b71a7f955345 Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Wed, 8 Apr 2026 17:17:03 -0400 Subject: [PATCH 327/350] Fixes senses when changing window size --- src/d/actor/d_a_alink_effect.inc | 3 +-- src/d/d_kankyo.cpp | 8 ++++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/d/actor/d_a_alink_effect.inc b/src/d/actor/d_a_alink_effect.inc index 1c02cb54f0..d595cd4293 100644 --- a/src/d/actor/d_a_alink_effect.inc +++ b/src/d/actor/d_a_alink_effect.inc @@ -1564,8 +1564,7 @@ void daAlink_c::setMetamorphoseEffectStartLink() { #endif #if TARGET_PC - static const Vec effWideScale = {mDoGph_gInf_c::getAspect(), 1.0f, 1.0f}; - emitter->setGlobalParticleScale(effWideScale); + emitter->setGlobalParticleScale(mDoGph_gInf_c::getScale(), 1.0f); #endif } diff --git a/src/d/d_kankyo.cpp b/src/d/d_kankyo.cpp index b9262bab23..8644d433aa 100644 --- a/src/d/d_kankyo.cpp +++ b/src/d/d_kankyo.cpp @@ -821,12 +821,20 @@ static void dKy_FiveSenses_fullthrottle_dark_static1() { } if (kankyo->senses_ef_emitter1 != NULL) { + #if TARGET_PC + kankyo->senses_ef_emitter1->setGlobalParticleScale(mDoGph_gInf_c::getScale(), 1.0f); + #endif + kankyo->senses_ef_emitter1->setGlobalTranslation(particle_pos.x, particle_pos.y, particle_pos.z); kankyo->senses_ef_emitter1->setGlobalAlpha(kankyo->senses_effect_strength * 255.0f); } if (kankyo->senses_ef_emitter2 != NULL) { + #if TARGET_PC + kankyo->senses_ef_emitter2->setGlobalParticleScale(mDoGph_gInf_c::getScale(), 1.0f); + #endif + kankyo->senses_ef_emitter2->setGlobalTranslation(particle_pos.x, particle_pos.y, particle_pos.z); From 03bb887bb5f98e0ebe8549a1d48da3076fc68eb1 Mon Sep 17 00:00:00 2001 From: SuperDude88 <82904174+SuperDude88@users.noreply.github.com> Date: Wed, 8 Apr 2026 17:58:21 -0400 Subject: [PATCH 328/350] Free Magic Armor Remove the rupee drain for using the magic armor --- include/dusk/settings.h | 1 + src/d/actor/d_a_alink.cpp | 10 ++++++++++ src/d/actor/d_a_alink_damage.inc | 5 +++++ src/d/actor/d_a_alink_wolf.inc | 11 ++++++++++- src/dusk/imgui/ImGuiMenuEnhancements.cpp | 5 +++++ src/dusk/settings.cpp | 2 ++ 6 files changed, 33 insertions(+), 1 deletion(-) diff --git a/include/dusk/settings.h b/include/dusk/settings.h index 2436be70f5..ea7ec1521b 100644 --- a/include/dusk/settings.h +++ b/include/dusk/settings.h @@ -60,6 +60,7 @@ struct UserSettings { ConfigVar enableFastIronBoots; ConfigVar canTransformAnywhere; ConfigVar fastSpinner; + ConfigVar freeMagicArmor; // Technical ConfigVar restoreWiiGlitches; diff --git a/src/d/actor/d_a_alink.cpp b/src/d/actor/d_a_alink.cpp index 48269ddc56..670dd169c3 100644 --- a/src/d/actor/d_a_alink.cpp +++ b/src/d/actor/d_a_alink.cpp @@ -12677,7 +12677,11 @@ void daAlink_c::setMagicArmorBrk(int i_status) { } BOOL daAlink_c::checkMagicArmorHeavy() const { +#if TARGET_PC + return checkMagicArmorWearAbility() && (dComIfGs_getRupee() == 0 && !dusk::getSettings().game.freeMagicArmor); +#else return checkMagicArmorWearAbility() && dComIfGs_getRupee() == 0; +#endif } BOOL daAlink_c::checkBootsOrArmorHeavy() const { @@ -18583,7 +18587,13 @@ int daAlink_c::execute() { field_0x372c = cXyz::Zero; field_0x2fb8 = 0; +#if TARGET_PC + // This handles rupee drain and transitions between rupees/no rupees + // We can skip all of that if the magic armor doesn't use rupees + if (!dusk::getSettings().game.freeMagicArmor && checkMagicArmorWearAbility() && mClothesChangeWaitTimer == 0) { +#else if (checkMagicArmorWearAbility() && mClothesChangeWaitTimer == 0) { +#endif if (checkMagicArmorNoDamage() && !checkEventRun()) { if (field_0x2fc3 == 0) { field_0x2fc3 = 10; diff --git a/src/d/actor/d_a_alink_damage.inc b/src/d/actor/d_a_alink_damage.inc index 420011cfd6..ac5175e314 100644 --- a/src/d/actor/d_a_alink_damage.inc +++ b/src/d/actor/d_a_alink_damage.inc @@ -187,6 +187,11 @@ int daAlink_c::setDamagePoint(int i_dmgAmount, BOOL i_checkZoraMag, BOOL i_setDm } if (checkMagicArmorNoDamage()) { +#if TARGET_PC + if(dusk::getSettings().game.freeMagicArmor) { + i_dmgAmount = 0; + } +#endif dComIfGp_setItemRupeeCount(-i_dmgAmount * 10); } else #if DEBUG diff --git a/src/d/actor/d_a_alink_wolf.inc b/src/d/actor/d_a_alink_wolf.inc index 2bf1129f17..2e38298090 100644 --- a/src/d/actor/d_a_alink_wolf.inc +++ b/src/d/actor/d_a_alink_wolf.inc @@ -313,7 +313,12 @@ void daAlink_c::changeLink(int param_0) { mpLinkHandModel = initModel(static_cast(dComIfG_getObjectRes(l_mArcName, "al_hands.bmd")), 0); - if (dComIfGs_getRupee() != 0) { +#if TARGET_PC + if (dComIfGs_getRupee() != 0 || dusk::getSettings().game.freeMagicArmor) +#else + if (dComIfGs_getRupee() != 0) +#endif + { setMagicArmorBrk(1); } else { setMagicArmorBrk(0); @@ -398,7 +403,11 @@ void daAlink_c::changeLink(int param_0) { field_0x06ec = field_0x064C->getMaterialNodePointer(1)->getShape(); field_0x06f0 = field_0x064C->getMaterialNodePointer(2)->getShape(); +#if TARGET_PC + if (dComIfGs_getRupee() != 0 || dusk::getSettings().game.freeMagicArmor) { +#else if (dComIfGs_getRupee() != 0) { +#endif var_r27 = 4; } else { var_r27 = 5; diff --git a/src/dusk/imgui/ImGuiMenuEnhancements.cpp b/src/dusk/imgui/ImGuiMenuEnhancements.cpp index e9404e2476..b17ab76b1b 100644 --- a/src/dusk/imgui/ImGuiMenuEnhancements.cpp +++ b/src/dusk/imgui/ImGuiMenuEnhancements.cpp @@ -106,6 +106,11 @@ namespace dusk { ImGui::SetTooltip("Speeds up Spinner movement when holding R."); } + config::ImGuiCheckbox("Free Magic Armor", getSettings().game.freeMagicArmor); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Makes the magic armor work without rupees."); + } + ImGui::EndMenu(); } diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index d923a2b00e..ce203e2016 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -48,6 +48,7 @@ UserSettings g_userSettings = { .enableFastIronBoots {"game.enableFastIronBoots", false}, .canTransformAnywhere {"game.canTransformAnywhere", false}, .fastSpinner {"game.fastSpinner", false}, + .freeMagicArmor {"game.freeMagicArmor", false}, // Technical .restoreWiiGlitches {"game.restoreWiiGlitches", false}, @@ -91,6 +92,7 @@ void registerSettings() { Register(g_userSettings.game.useWaterProjectionOffset); Register(g_userSettings.game.enableFastIronBoots); Register(g_userSettings.game.canTransformAnywhere); + Register(g_userSettings.game.freeMagicArmor); Register(g_userSettings.game.restoreWiiGlitches); Register(g_userSettings.game.noMissClimbing); Register(g_userSettings.game.noLowHpSound); From 6173b1b42eb60bd7f04f087a2c8c124ae1e70b4f Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Wed, 8 Apr 2026 18:35:41 -0400 Subject: [PATCH 329/350] Fix the rest of the water projections --- src/d/actor/d_a_obj_groundwater.cpp | 8 ++++++++ src/d/actor/d_a_obj_lv3Water.cpp | 6 ++++++ src/d/actor/d_a_obj_lv3Water2.cpp | 9 ++++++++- src/d/actor/d_a_obj_lv3WaterB.cpp | 7 +++++++ src/d/actor/d_a_obj_rstair.cpp | 7 +++++++ src/d/actor/d_a_obj_tp.cpp | 7 +++++++ 6 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/d/actor/d_a_obj_groundwater.cpp b/src/d/actor/d_a_obj_groundwater.cpp index 2d3d05a36c..ab6b7af922 100644 --- a/src/d/actor/d_a_obj_groundwater.cpp +++ b/src/d/actor/d_a_obj_groundwater.cpp @@ -300,11 +300,19 @@ int daGrdWater_c::Draw() { J3DTexMtxInfo* mtxInfo = &material->getTexGenBlock()->getTexMtx(0)->getTexMtxInfo(); if (mtxInfo != NULL) { Mtx afStack_50; + + #if TARGET_PC + C_MTXLightPerspective(afStack_50, dComIfGd_getView()->fovy, dComIfGd_getView()->aspect, + 1.0f, 1.0f, dusk::getSettings().game.useWaterProjectionOffset ? -0.01f : 0.0f, 0.0f); + #else C_MTXLightPerspective(afStack_50, dComIfGd_getView()->fovy, dComIfGd_getView()->aspect, 1.0f, 1.0f, -0.01f, 0.0f); + #endif + #if WIDESCREEN_SUPPORT mDoGph_gInf_c::setWideZoomLightProjection(afStack_50); #endif + mtxInfo->setEffectMtx(afStack_50); modelData2->simpleCalcMaterial(0, (MtxP)j3dDefaultMtx); } diff --git a/src/d/actor/d_a_obj_lv3Water.cpp b/src/d/actor/d_a_obj_lv3Water.cpp index a228b2cbc8..934cb3140d 100644 --- a/src/d/actor/d_a_obj_lv3Water.cpp +++ b/src/d/actor/d_a_obj_lv3Water.cpp @@ -371,8 +371,14 @@ int daLv3Water_c::Draw() { texMtxInfo = &material->getTexGenBlock()->getTexMtx(0)->getTexMtxInfo(); if (texMtxInfo != NULL) { Mtx lightProjMtx; + + #if TARGET_PC + C_MTXLightPerspective(lightProjMtx, dComIfGd_getView()->fovy, + dComIfGd_getView()->aspect, 1.0f, 1.0f, dusk::getSettings().game.useWaterProjectionOffset ? -0.01f : 0.0f, 0.0f); + #else C_MTXLightPerspective(lightProjMtx, dComIfGd_getView()->fovy, dComIfGd_getView()->aspect, 1.0f, 1.0f, -0.01f, 0.0f); + #endif #if WIDESCREEN_SUPPORT mDoGph_gInf_c::setWideZoomLightProjection(lightProjMtx); diff --git a/src/d/actor/d_a_obj_lv3Water2.cpp b/src/d/actor/d_a_obj_lv3Water2.cpp index b6814875cc..4e367e8912 100644 --- a/src/d/actor/d_a_obj_lv3Water2.cpp +++ b/src/d/actor/d_a_obj_lv3Water2.cpp @@ -197,7 +197,14 @@ int daLv3Water2_c::Draw() { texMtxInfo = &btkMaterial->getTexGenBlock()->getTexMtx(0)->getTexMtxInfo(); if(texMtxInfo) { Mtx lightProjMtx; - C_MTXLightPerspective(lightProjMtx, dComIfGd_getView()->fovy, dComIfGd_getView()->aspect, 1.0f, 1.0f, -0.01f, 0); + + #if TARGET_PC + C_MTXLightPerspective(lightProjMtx, dComIfGd_getView()->fovy, + dComIfGd_getView()->aspect, 1.0f, 1.0f, dusk::getSettings().game.useWaterProjectionOffset ? -0.01f : 0.0f, 0); + #else + C_MTXLightPerspective(lightProjMtx, dComIfGd_getView()->fovy, + dComIfGd_getView()->aspect, 1.0f, 1.0f, -0.01f, 0); + #endif #if WIDESCREEN_SUPPORT mDoGph_gInf_c::setWideZoomLightProjection(lightProjMtx); diff --git a/src/d/actor/d_a_obj_lv3WaterB.cpp b/src/d/actor/d_a_obj_lv3WaterB.cpp index f4ad49685d..33285d975f 100644 --- a/src/d/actor/d_a_obj_lv3WaterB.cpp +++ b/src/d/actor/d_a_obj_lv3WaterB.cpp @@ -27,8 +27,15 @@ static int daObj_Lv3waterB_Draw(obj_lv3WaterB_class* i_this) { J3DTexMtxInfo* tex_mtx_info = &material_p->getTexGenBlock()->getTexMtx(0)->getTexMtxInfo(); if (tex_mtx_info != NULL) { Mtx m; + + #if TARGET_PC + C_MTXLightPerspective(m, dComIfGd_getView()->fovy, dComIfGd_getView()->aspect, 1.0f, 1.0f, + dusk::getSettings().game.useWaterProjectionOffset ? -0.015f : 0.0f, 0.0f); + #else C_MTXLightPerspective(m, dComIfGd_getView()->fovy, dComIfGd_getView()->aspect, 1.0f, 1.0f, -0.015f, 0.0f); + #endif + #if WIDESCREEN_SUPPORT mDoGph_gInf_c::setWideZoomLightProjection(m); #endif diff --git a/src/d/actor/d_a_obj_rstair.cpp b/src/d/actor/d_a_obj_rstair.cpp index 609a0cf7b8..7e44cc3700 100644 --- a/src/d/actor/d_a_obj_rstair.cpp +++ b/src/d/actor/d_a_obj_rstair.cpp @@ -313,8 +313,15 @@ int daObjRotStair_c::Draw() { J3DTexMtxInfo* texMtxInfo = &material->getTexGenBlock()->getTexMtx(0)->getTexMtxInfo(); if (texMtxInfo != NULL) { Mtx lightMtx; + + #if TARGET_PC + C_MTXLightPerspective(lightMtx, dComIfGd_getView()->fovy, + dComIfGd_getView()->aspect, 1.0f, 1.0f, dusk::getSettings().game.useWaterProjectionOffset ? -0.01f : 0.0f, 0); + #else C_MTXLightPerspective(lightMtx, dComIfGd_getView()->fovy, dComIfGd_getView()->aspect, 1.0f, 1.0f, -0.01f, 0); + #endif + #if WIDESCREEN_SUPPORT mDoGph_gInf_c::setWideZoomLightProjection(lightMtx); #endif diff --git a/src/d/actor/d_a_obj_tp.cpp b/src/d/actor/d_a_obj_tp.cpp index 2690de3e04..e2ede47b15 100644 --- a/src/d/actor/d_a_obj_tp.cpp +++ b/src/d/actor/d_a_obj_tp.cpp @@ -36,8 +36,15 @@ static int daObj_Tp_Draw(obj_tp_class* i_this) { &material->getTexGenBlock()->getTexMtx(0)->getTexMtxInfo(); if (texMtxInfo != NULL) { Mtx lightProjMtx; + + #if TARGET_PC + C_MTXLightPerspective(lightProjMtx, dComIfGd_getView()->fovy, + dComIfGd_getView()->aspect, 1.0f, 1.0f, dusk::getSettings().game.useWaterProjectionOffset ? -0.01f : 0.0f, 0); + #else C_MTXLightPerspective(lightProjMtx, dComIfGd_getView()->fovy, dComIfGd_getView()->aspect, 1.0f, 1.0f, -0.01f, 0); + #endif + #if WIDESCREEN_SUPPORT mDoGph_gInf_c::setWideZoomLightProjection(lightProjMtx); #endif From 4f8e27a1c7774d787ec335cce2b4307fab581228 Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Wed, 8 Apr 2026 18:37:50 -0400 Subject: [PATCH 330/350] Fix line indent --- src/d/actor/d_a_obj_lv3Water2.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/d/actor/d_a_obj_lv3Water2.cpp b/src/d/actor/d_a_obj_lv3Water2.cpp index 4e367e8912..55fc94d605 100644 --- a/src/d/actor/d_a_obj_lv3Water2.cpp +++ b/src/d/actor/d_a_obj_lv3Water2.cpp @@ -202,8 +202,7 @@ int daLv3Water2_c::Draw() { C_MTXLightPerspective(lightProjMtx, dComIfGd_getView()->fovy, dComIfGd_getView()->aspect, 1.0f, 1.0f, dusk::getSettings().game.useWaterProjectionOffset ? -0.01f : 0.0f, 0); #else - C_MTXLightPerspective(lightProjMtx, dComIfGd_getView()->fovy, - dComIfGd_getView()->aspect, 1.0f, 1.0f, -0.01f, 0); + C_MTXLightPerspective(lightProjMtx, dComIfGd_getView()->fovy, dComIfGd_getView()->aspect, 1.0f, 1.0f, -0.01f, 0); #endif #if WIDESCREEN_SUPPORT From b2a66c8cbdd07b764999c4ebb63145886a693b4c Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Wed, 8 Apr 2026 18:50:57 -0400 Subject: [PATCH 331/350] Fix Action icons in Text boxes --- src/d/d_msg_scrn_item.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/d/d_msg_scrn_item.cpp b/src/d/d_msg_scrn_item.cpp index 7cba6c7b86..40732eff25 100644 --- a/src/d/d_msg_scrn_item.cpp +++ b/src/d/d_msg_scrn_item.cpp @@ -411,7 +411,7 @@ void dMsgScrnItem_c::drawSelf() { f32 globalPosX = mpTm_c[0]->getGlobalPosX(); - #if WIDESCREEN_SUPPORT + #if WIDESCREEN_SUPPORT && !TARGET_PC if (mDoGph_gInf_c::isWide()) { drawOutFont(g_MsgObject_HIO_c.mBoxItemTextPosX + 7.0f + YREG_F(2), g_MsgObject_HIO_c.mBoxItemTextPosY, 1.0f); From 68716645cb5b5c814a2ecec5d8e3b081f4097d46 Mon Sep 17 00:00:00 2001 From: TakaRikka Date: Thu, 9 Apr 2026 04:09:17 -0700 Subject: [PATCH 332/350] a little controller config ui cleanup --- extern/aurora | 2 +- src/dusk/imgui/ImGuiConsole.cpp | 7 ++-- src/dusk/imgui/ImGuiMenuGame.cpp | 62 +++++++++++++++----------------- 3 files changed, 35 insertions(+), 36 deletions(-) diff --git a/extern/aurora b/extern/aurora index f5c5917832..b1bf531884 160000 --- a/extern/aurora +++ b/extern/aurora @@ -1 +1 @@ -Subproject commit f5c59178322c31b42676f49dd24e760e3b014f24 +Subproject commit b1bf531884172075dd97403edddc7d3301f4ca46 diff --git a/src/dusk/imgui/ImGuiConsole.cpp b/src/dusk/imgui/ImGuiConsole.cpp index 93b5b4a787..fa47e53da3 100644 --- a/src/dusk/imgui/ImGuiConsole.cpp +++ b/src/dusk/imgui/ImGuiConsole.cpp @@ -158,10 +158,13 @@ namespace dusk { auto itemMin = ImGui::GetItemRectMin(); auto itemMax = ImGui::GetItemRectMax(); + float frameSpacingY = 8.0f; + float frameBottomPadding = 10.0f; + ImVec2 halfFrame = ImVec2((frameHeight * 0.25f) * 0.5f, frameHeight * 0.5f); ImGui::GetWindowDrawList()->AddRect( - ImVec2(itemMin.x + halfFrame.x, itemMin.y + halfFrame.y), - ImVec2(itemMax.x - halfFrame.x, itemMax.y), + ImVec2(itemMin.x + halfFrame.x, itemMin.y + halfFrame.y + frameSpacingY), + ImVec2(itemMax.x - halfFrame.x, itemMax.y + frameBottomPadding), ImColor(ImGui::GetStyleColorVec4(ImGuiCol_Border)), halfFrame.x); diff --git a/src/dusk/imgui/ImGuiMenuGame.cpp b/src/dusk/imgui/ImGuiMenuGame.cpp index cbe26ba81c..8125583c8b 100644 --- a/src/dusk/imgui/ImGuiMenuGame.cpp +++ b/src/dusk/imgui/ImGuiMenuGame.cpp @@ -116,9 +116,9 @@ namespace dusk { static void drawVirtualStick(const char* id, const ImVec2& stick) { float scale = ImGuiScale(); - ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPos().x + 5 * scale, ImGui::GetCursorPos().y)); + ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPos().x + 45 * scale, ImGui::GetCursorPos().y + 10)); - ImGui::BeginChild(id, ImVec2(80 * scale, 80 * scale)); + ImGui::BeginChild(id, ImVec2(80 * scale, 80 * scale), 0, ImGuiWindowFlags_NoBackground); ImDrawList* dl = ImGui::GetWindowDrawList(); ImVec2 p = ImGui::GetCursorScreenPos(); @@ -222,6 +222,7 @@ namespace dusk { // controller selection combo box bool changedController = false; int changedControllerIndex = 0; + ImGui::SetNextItemWidth(400.0f * scale); if (ImGui::BeginCombo("##ControllerDeviceList", currentName.c_str())) { for (int i = 0; const auto& name : controllerList) { if (ImGui::Selectable(name.c_str(), currentName == name)) { @@ -300,7 +301,7 @@ namespace dusk { uint32_t axisCount; PADAxisMapping* axisMappingList = PADGetAxisMappings(m_controllerConfig.m_selectedPort, &axisCount); - ImGuiBeginGroupPanel("Triggers", ImVec2(150 * scale, 20 * scale)); + ImGuiBeginGroupPanel("Analog Triggers", ImVec2(150 * scale, 20 * scale)); PADAxis triggers[] = {PAD_AXIS_TRIGGER_L, PAD_AXIS_TRIGGER_R}; if (axisMappingList != nullptr) { @@ -334,11 +335,34 @@ namespace dusk { } } + int port = m_controllerConfig.m_selectedPort; + PADDeadZones* deadZones = PADGetDeadZones(port); + + if (deadZones != nullptr) { + ImGui::Text("L Threshold"); + ImGui::SameLine(); + { + float tmp = static_cast(deadZones->leftTriggerActivationZone * 100.f) / 32767.f; + if (ImGui::DragFloat("##LThreshold", &tmp, 0.5f, 0.f, 100.f, "%.3f%%")) { + deadZones->leftTriggerActivationZone = static_cast((tmp / 100.f) * 32767); + } + } + } + + if (deadZones != nullptr) { + ImGui::Text("R Threshold"); + ImGui::SameLine(); + { + float tmp = static_cast(deadZones->rightTriggerActivationZone * 100.f) / 32767.f; + if (ImGui::DragFloat("##RThreshold", &tmp, 0.5f, 0.f, 100.f, "%.3f%%")) { + deadZones->rightTriggerActivationZone = static_cast((tmp / 100.f) * 32767); + } + } + } + ImGuiEndGroupPanel(); ImGui::SameLine(); - int port = m_controllerConfig.m_selectedPort; - // main stick panel ImGuiBeginGroupPanel("Control Stick", ImVec2(150 * scale, 20 * scale)); @@ -388,8 +412,6 @@ namespace dusk { } } - PADDeadZones* deadZones = PADGetDeadZones(port); - if (deadZones != nullptr) { ImGui::Text("Dead Zone"); { @@ -465,32 +487,6 @@ namespace dusk { ImGuiEndGroupPanel(); ImGui::SameLine(); - // Triggers Panel - ImGuiBeginGroupPanel("Triggers", ImVec2(150 * scale, 20 * scale)); - - if (deadZones != nullptr) { - ImGui::Text("L Threshold"); - { - float tmp = static_cast(deadZones->leftTriggerActivationZone * 100.f) / 32767.f; - if (ImGui::DragFloat("##LThreshold", &tmp, 0.5f, 0.f, 100.f, "%.3f%%")) { - deadZones->leftTriggerActivationZone = static_cast((tmp / 100.f) * 32767); - } - } - } - - if (deadZones != nullptr) { - ImGui::Text("R Threshold"); - { - float tmp = static_cast(deadZones->rightTriggerActivationZone * 100.f) / 32767.f; - if (ImGui::DragFloat("##RThreshold", &tmp, 0.5f, 0.f, 100.f, "%.3f%%")) { - deadZones->rightTriggerActivationZone = static_cast((tmp / 100.f) * 32767); - } - } - } - - ImGuiEndGroupPanel(); - ImGui::SameLine(); - // Options panel ImGuiBeginGroupPanel("Options", ImVec2(150 * scale, 20 * scale)); From c6b78994a8895d76a9a1a20c735e963794170449 Mon Sep 17 00:00:00 2001 From: madeline Date: Thu, 9 Apr 2026 04:59:36 -0700 Subject: [PATCH 333/350] ignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index a381014599..a1839ff34e 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,5 @@ compile_commands.json *.ini *.controller pipeline_cache.bin + +extract From 744e29f401aa377a39c44a066e04d0e81ce333c3 Mon Sep 17 00:00:00 2001 From: madeline Date: Thu, 9 Apr 2026 08:25:06 -0700 Subject: [PATCH 334/350] optimize dsp by 27X --- libs/freeverb/allpass.hpp | 11 +--- libs/freeverb/comb.hpp | 8 +-- libs/freeverb/denormals.h | 63 +++++++++++++++++++---- libs/freeverb/revmodel.cpp | 43 ++++++++++++---- libs/freeverb/revmodel.hpp | 6 +-- src/dusk/audio/DuskDsp.cpp | 102 +++++++++++++++++++------------------ src/dusk/audio/DuskDsp.hpp | 3 -- 7 files changed, 144 insertions(+), 92 deletions(-) diff --git a/libs/freeverb/allpass.hpp b/libs/freeverb/allpass.hpp index 4fc294d139..b9c5904229 100644 --- a/libs/freeverb/allpass.hpp +++ b/libs/freeverb/allpass.hpp @@ -6,7 +6,6 @@ #ifndef _allpass_ #define _allpass_ -#include "denormals.h" class allpass { @@ -29,18 +28,12 @@ public: inline float allpass::process(float input) { - float output; - float bufout; - - bufout = buffer[bufidx]; - undenormalise(bufout); - - output = -input + bufout; + float bufout = buffer[bufidx]; buffer[bufidx] = input + (bufout*feedback); if(++bufidx>=bufsize) bufidx = 0; - return output; + return -input + bufout; } #endif//_allpass diff --git a/libs/freeverb/comb.hpp b/libs/freeverb/comb.hpp index 4a73b615fb..52c01b070e 100644 --- a/libs/freeverb/comb.hpp +++ b/libs/freeverb/comb.hpp @@ -7,7 +7,6 @@ #ifndef _comb_ #define _comb_ -#include "denormals.h" class comb { @@ -35,14 +34,9 @@ private: inline float comb::process(float input) { - float output; - - output = buffer[bufidx]; - undenormalise(output); + float output = buffer[bufidx]; filterstore = (output*damp2) + (filterstore*damp1); - undenormalise(filterstore); - buffer[bufidx] = input + (filterstore*feedback); if(++bufidx>=bufsize) bufidx = 0; diff --git a/libs/freeverb/denormals.h b/libs/freeverb/denormals.h index f871412714..d68cb444f7 100644 --- a/libs/freeverb/denormals.h +++ b/libs/freeverb/denormals.h @@ -1,15 +1,58 @@ -// Macro for killing denormalled numbers -// -// Written by Jezar at Dreampoint, June 2000 -// http://www.dreampoint.co.uk -// Based on IS_DENORMAL macro by Jon Watte -// This code is public domain - #ifndef _denormals_ #define _denormals_ -#define undenormalise(sample) if(((*(unsigned int*)&sample)&0x7f800000)==0) sample=0.0f +#if defined(__x86_64__) || defined(_M_X64) || defined(__i386__) || defined(_M_IX86) -#endif//_denormals_ +#include +using denormal_state = unsigned int; +inline denormal_state denormals_enable() +{ + denormal_state saved = _mm_getcsr(); + _mm_setcsr(saved | 0x8040); // FTZ (0x8000) | DAZ (0x0040) + return saved; +} +inline void denormals_restore(denormal_state saved) { _mm_setcsr(saved); } -//ends +#elif defined(__aarch64__) || defined(_M_ARM64) + +#include +using denormal_state = uint64_t; +inline denormal_state denormals_enable() +{ + denormal_state saved; + asm volatile("mrs %0, fpcr" : "=r"(saved)); + asm volatile("msr fpcr, %0" :: "r"(saved | (1ULL << 24))); // FZ + return saved; +} +inline void denormals_restore(denormal_state saved) +{ + asm volatile("msr fpcr, %0" :: "r"(saved)); +} + +#elif defined(__arm__) || defined(_M_ARM) + +#include +using denormal_state = uint32_t; +inline denormal_state denormals_enable() +{ + denormal_state saved; + asm volatile("vmrs %0, fpscr" : "=r"(saved)); + asm volatile("vmsr fpscr, %0" :: "r"(saved | (1U << 24))); // FZ + return saved; +} +inline void denormals_restore(denormal_state saved) +{ + asm volatile("vmsr fpscr, %0" :: "r"(saved)); +} + +#else + +// unknown platform so denormals will be preserved +#warning "This platform is not supported for denormals, reverb will be very slow!" +using denormal_state = int; +inline denormal_state denormals_enable() { return 0; } +inline void denormals_restore(denormal_state) {} + +#endif + +#endif diff --git a/libs/freeverb/revmodel.cpp b/libs/freeverb/revmodel.cpp index feaa3bc04c..d4f9cf2513 100644 --- a/libs/freeverb/revmodel.cpp +++ b/libs/freeverb/revmodel.cpp @@ -5,6 +5,7 @@ // This code is public domain #include "revmodel.hpp" +#include "denormals.h" revmodel::revmodel() { @@ -71,14 +72,17 @@ void revmodel::mute() } } -void revmodel::processreplace(float *inputL, float *inputR, float *outputL, float *outputR, long numsamples, int skip) +float revmodel::processreplace(float *inputL, float *inputR, float *outputL, float *outputR, long numsamples, int skip, float inputGain) { - float outL,outR,input; + float outL,outR; + float wetSumSqL = 0.0f, wetSumSqR = 0.0f; + long totalSamples = numsamples; + auto savedCSR = denormals_enable(); while(numsamples-- > 0) { outL = outR = 0; - input = (*inputL + *inputR) * gain; + float input = (*inputL + *inputR) * gain * inputGain; // Accumulate comb filters in parallel for(int i=0; i 0) { outL = outR = 0; - input = (*inputL + *inputR) * gain; + float input = (*inputL + *inputR) * gain * inputGain; // Accumulate comb filters in parallel for(int i=0; i #include "Adpcm.hpp" +#include "freeverb/revmodel.hpp" #include "JSystem/JAudio2/JASDriverIF.h" #include "dusk/audio/DuskAudioSystem.h" #include "dusk/endian.h" @@ -18,6 +19,9 @@ using namespace dusk::audio; ChannelAuxData dusk::audio::ChannelAux[DSP_CHANNELS] = {}; +static revmodel SharedReverb; +static bool ReverbHasTail = false; + static bool sDumpWasActive = false; static FILE* sChannelDumpFiles[DSP_CHANNELS] = {}; @@ -140,37 +144,37 @@ void dusk::audio::DspRender(OutputSubframe& subframe) { std::span channels(JASDsp::CH_BUF, DSP_CHANNELS); + DspSubframe reverbInputL = {}; + DspSubframe reverbInputR = {}; + bool anyReverbInput = false; + for (int i = 0; i < channels.size(); i++) { auto& channel = channels[i]; auto& channelAux = ChannelAux[i]; - bool skipRender = false; - if (!channel.mIsActive) { - skipRender = true; + continue; } else if (channel.mPauseFlag) { // Not really sure what the practical difference between pause and // deactivation is. Either avoids clearing state or allows the DSP to avoid popping? - skipRender = true; + continue; } else if (channel.mForcedStop) { channel.mIsFinished = true; - skipRender = true; + continue; } else if (channel.mWaveAramAddress == 0) { // I think these are oscillator channels? Not backed by audio. // No idea how to implement these yet, so skip them. channel.mIsFinished = true; - skipRender = true; + continue; } + ValidateChannel(channel); + OutputSubframe channelSubframe = {}; - - if (!skipRender) { - ValidateChannel(channel); - RenderChannel(channel, channelAux, channelSubframe); - } + RenderChannel(channel, channelAux, channelSubframe); if (EnableReverb) { // scale the input to the reverb rather than using wet/dry on the output. @@ -178,31 +182,23 @@ void dusk::audio::DspRender(OutputSubframe& subframe) { // so any tail always decays at the correct level regardless of mAutoMixerFxMix changes // prevents transients when the next sound starts playing with a different reverb level // 600.0f was pulled out of my ass and just sounds good enough for console - f32 inputGain = (!skipRender) ? (channel.mAutoMixerFxMix >> 8) / 600.0f : 0.0f; - - OutputSubframe reverbSubframe = {}; - for (int j = 0; j < DSP_SUBFRAME_SIZE; j++) { - reverbSubframe.channels[0][j] = channelSubframe.channels[0][j] * inputGain; - reverbSubframe.channels[1][j] = channelSubframe.channels[1][j] * inputGain; - } - - channelAux.reverb.processreplace( - reverbSubframe.channels[0].data(), reverbSubframe.channels[1].data(), - reverbSubframe.channels[0].data(), reverbSubframe.channels[1].data(), - DSP_SUBFRAME_SIZE, 1 - ); - - for (int j = 0; j < DSP_SUBFRAME_SIZE; j++) { - channelSubframe.channels[0][j] += reverbSubframe.channels[0][j]; - channelSubframe.channels[1][j] += reverbSubframe.channels[1][j]; + f32 inputGain = (channel.mAutoMixerFxMix >> 8) / 600.0f; + if (inputGain > 0) { + anyReverbInput = true; + for (int j = 0; j < DSP_SUBFRAME_SIZE; j++) { + reverbInputL[j] += channelSubframe.channels[0][j] * inputGain; + reverbInputR[j] += channelSubframe.channels[1][j] * inputGain; + } } } if (DumpAudio && sChannelDumpFiles[i]) { + f32 interleaved[DSP_SUBFRAME_SIZE * 2]; for (int j = 0; j < DSP_SUBFRAME_SIZE; j++) { - fwrite(&channelSubframe.channels[0][j], sizeof(f32), 1, sChannelDumpFiles[i]); - fwrite(&channelSubframe.channels[1][j], sizeof(f32), 1, sChannelDumpFiles[i]); + interleaved[j * 2 + 0] = channelSubframe.channels[0][j]; + interleaved[j * 2 + 1] = channelSubframe.channels[1][j]; } + fwrite(interleaved, sizeof(f32), DSP_SUBFRAME_SIZE * 2, sChannelDumpFiles[i]); } for (int o = 0; o < subframe.channels.size(); o++) { @@ -210,6 +206,17 @@ void dusk::audio::DspRender(OutputSubframe& subframe) { } } + if (EnableReverb && (anyReverbInput || ReverbHasTail)) { + // Equivalent to -80 dBFS: rms = 1e-4, rms^2 = 1e-8, sumSq = 2 * N * 1e-8 + constexpr f32 REVERB_ENERGY_EPSILON = 2.0f * DSP_SUBFRAME_SIZE * 1e-8f; + f32 wetEnergy = SharedReverb.processmix( + reverbInputL.data(), reverbInputR.data(), + subframe.channels[0].data(), subframe.channels[1].data(), + DSP_SUBFRAME_SIZE, 1, 1.0f + ); + ReverbHasTail = wetEnergy >= REVERB_ENERGY_EPSILON; + } + for (auto& channel : subframe.channels) { ApplyVolume(channel, channel, PrevMasterVolume, MasterVolume); } @@ -341,7 +348,7 @@ static void FillDecodeBuf(JASDsp::TChannel& channel, ChannelAuxData& aux, int ne } aux.decodeBufCount += ReadChannelSamplesChunk( - channel, aux, std::min(remainingDecodeSpace, needed - aux.decodeBufCount), + channel, aux, std::min(remainingDecodeSpace, needed - aux.decodeBufCount), aux.decodeBuf + aux.decodeBufCount, remainingDecodeSpace ); } @@ -491,17 +498,17 @@ static void RenderChannel( } DspSubframe audioLoadBuffer = {}; - f64 pos = channelAux.resamplePos; + f32 pos = channelAux.resamplePos; s16 prev = channelAux.resamplePrev; s16 next = channelAux.decodeBufCount > 0 ? channelAux.decodeBuf[0] : prev; int srcIdx = 0; // linear resampling and f32 conversion for (int i = 0; i < DSP_SUBFRAME_SIZE; i++) { - audioLoadBuffer[i] = static_cast(prev + pos * (next - prev)) / 32768.0f; + audioLoadBuffer[i] = (prev + pos * (next - prev)) / 32768.0f; pos += step; - while (pos >= 1.0) { - pos -= 1.0; + while (pos >= 1.0f) { + pos -= 1.0f; prev = next; srcIdx++; next = srcIdx < channelAux.decodeBufCount ? channelAux.decodeBuf[srcIdx] : prev; @@ -529,16 +536,13 @@ static void RenderChannel( } void dusk::audio::DspInit() { - for (int i = 0; i < DSP_CHANNELS; i++) { - auto& channelAux = ChannelAux[i]; - channelAux.reverb.setwet(1.0f); - channelAux.reverb.setdry(0.0f); - channelAux.reverb.setroomsize(0.5f); - channelAux.reverb.setdamp(0.7f); - channelAux.reverb.setwidth(1.0f); - channelAux.reverb.setmode(0.0f); - channelAux.reverb.mute(); - } + SharedReverb.setwet(1.0f); + SharedReverb.setdry(0.0f); + SharedReverb.setroomsize(0.5f); + SharedReverb.setdamp(0.7f); + SharedReverb.setwidth(1.0f); + SharedReverb.setmode(0.0f); + SharedReverb.mute(); } void dusk::audio::ApplyVolume( @@ -549,15 +553,13 @@ void dusk::audio::ApplyVolume( assert(dst.size() >= src.size()); if (startVolume == endVolume) { - for (int i = 0; i < src.size(); i++) { + for (int i = 0; i < (int)src.size(); i++) { dst[i] = src[i] * startVolume; } } else { const f32 step = (endVolume - startVolume) / static_cast(src.size()); - auto curVolume = startVolume; - for (int i = 0; i < src.size(); i++) { - dst[i] = src[i] * curVolume; - curVolume += step; + for (int i = 0; i < (int)src.size(); i++) { + dst[i] = src[i] * (startVolume + i * step); } } } diff --git a/src/dusk/audio/DuskDsp.hpp b/src/dusk/audio/DuskDsp.hpp index 728234b75c..c7cb751458 100644 --- a/src/dusk/audio/DuskDsp.hpp +++ b/src/dusk/audio/DuskDsp.hpp @@ -8,8 +8,6 @@ #include "SDL3/SDL_audio.h" #include -#include "freeverb/revmodel.hpp" - // ReSharper disable once CppUnusedIncludeDirective #include "global.h" @@ -31,7 +29,6 @@ namespace dusk::audio { // Used for debugging tools. u32 resetCount; - revmodel reverb; /** * Previous volume values, per output channel. From 905574339b44421c8839ab880b8ab94ed2af4403 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Wed, 8 Apr 2026 13:30:50 +0200 Subject: [PATCH 335/350] Fix JKRDecomp crash I caused By changing more things :sloggers: --- libs/JSystem/src/JKernel/JKRThread.cpp | 6 +++++- src/dusk/OSThread.cpp | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/libs/JSystem/src/JKernel/JKRThread.cpp b/libs/JSystem/src/JKernel/JKRThread.cpp index 103e6bbcbc..31c2cbe6e5 100644 --- a/libs/JSystem/src/JKernel/JKRThread.cpp +++ b/libs/JSystem/src/JKernel/JKRThread.cpp @@ -88,7 +88,11 @@ void JKRThread::setCommon_heapSpecified(JKRHeap* heap, u32 stack_size, int param mThreadRecord = (OSThread*)JKRAllocFromHeap(mHeap, sizeof(OSThread), 0x20); JUT_ASSERT(168, mThreadRecord); - OSCreateThread(mThreadRecord, start, this, (u8*)mStackMemory + mStackSize, mStackSize, param_3, 1); +#if TARGET_PC + OSCreateThread(mThreadRecord, start, this, (u8*)mStackMemory + mStackSize, mStackSize, param_3, 0); +#else + OSCreateThread(mThreadRecord, start, this, (u8*)mStackMemory + mStackSize, mStackSize, param_3, OS_THREAD_ATTR_DETACH); +#endif } void* JKRThread::start(void* thread) { diff --git a/src/dusk/OSThread.cpp b/src/dusk/OSThread.cpp index af339e8656..d5d73df243 100644 --- a/src/dusk/OSThread.cpp +++ b/src/dusk/OSThread.cpp @@ -41,7 +41,7 @@ struct PCThreadData { bool suspended = false; ~PCThreadData() { - if (dusk::IsShuttingDown) { + if (dusk::IsShuttingDown && nativeThread.joinable()) { // Don't care about threads if we're shutting down. nativeThread.detach(); } From a2d008ab09e09d077b9202f41cd2cfb3f2438512 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Wed, 8 Apr 2026 03:13:37 +0200 Subject: [PATCH 336/350] Add Tracy stuff --- CMakeLists.txt | 2 +- include/d/d_com_inf_game.h | 49 +++++++++++++++++++ .../src/J3DGraphBase/J3DDrawBuffer.cpp | 3 ++ libs/JSystem/src/J3DGraphBase/J3DPacket.cpp | 13 +++-- libs/JSystem/src/J3DGraphBase/J3DShape.cpp | 7 +-- libs/JSystem/src/J3DGraphBase/J3DSys.cpp | 4 ++ libs/JSystem/src/JAudio2/JASAiCtrl.cpp | 3 ++ libs/JSystem/src/JFramework/JFWDisplay.cpp | 2 + libs/JSystem/src/JParticle/JPABaseShape.cpp | 18 +++++++ .../src/JParticle/JPAEmitterManager.cpp | 3 ++ libs/JSystem/src/JParticle/JPAResource.cpp | 4 ++ src/d/d_kankyo_rain.cpp | 1 + src/d/d_particle.cpp | 26 +++++----- src/dusk/OSThread.cpp | 5 ++ src/dusk/audio/DuskAudioSystem.cpp | 9 ++++ src/dusk/audio/DuskDsp.cpp | 2 + src/dusk/audio/JASCriticalSection.cpp | 4 +- src/dusk/imgui/ImGuiConsole.cpp | 2 + src/dusk/logging.cpp | 3 ++ src/dusk/stubs.cpp | 3 ++ src/f_ap/f_ap_game.cpp | 34 +++++++------ src/f_pc/f_pc_manager.cpp | 3 ++ src/m_Do/m_Do_audio.cpp | 2 + src/m_Do/m_Do_controller_pad.cpp | 2 + src/m_Do/m_Do_graphic.cpp | 30 ++++++++---- src/m_Do/m_Do_main.cpp | 3 ++ 26 files changed, 190 insertions(+), 47 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 66c86bb5f4..dd848c776f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -107,7 +107,7 @@ set(GAME_INCLUDE_DIRS extern) set(GAME_LIBS aurora::core aurora::gx aurora::gd aurora::si aurora::vi aurora::pad aurora::mtx aurora::os aurora::dvd - aurora::card freeverb cxxopts::cxxopts absl::flat_hash_map nlohmann_json::nlohmann_json) + aurora::card freeverb cxxopts::cxxopts absl::flat_hash_map nlohmann_json::nlohmann_json TracyClient) if (DUSK_MOVIE_SUPPORT_REAL) if (TARGET libjpeg-turbo::turbojpeg-static) diff --git a/include/d/d_com_inf_game.h b/include/d/d_com_inf_game.h index fc60c5d107..78ab356500 100644 --- a/include/d/d_com_inf_game.h +++ b/include/d/d_com_inf_game.h @@ -17,6 +17,8 @@ #include "m_Do/m_Do_graphic.h" #include +#include "tracy/Tracy.hpp" + enum dComIfG_ButtonStatus { /* 0x00 */ BUTTON_STATUS_NONE, /* 0x01 */ BUTTON_STATUS_LET_GO, @@ -3109,114 +3111,133 @@ inline void dComIfGp_particle_calcMenu() { } inline void dComIfGp_particle_draw(JPADrawInfo* i_drawInfo) { + ZoneScoped; if (g_dComIfG_gameInfo.play.getParticle() != NULL) { g_dComIfG_gameInfo.play.getParticle()->drawNormal(i_drawInfo); } } inline void dComIfGp_particle_drawFog(JPADrawInfo* i_drawInfo) { + ZoneScoped; if (g_dComIfG_gameInfo.play.getParticle() != NULL) { g_dComIfG_gameInfo.play.getParticle()->drawNormalFog(i_drawInfo); } } inline void dComIfGp_particle_drawP1(JPADrawInfo* i_drawInfo) { + ZoneScoped; if (g_dComIfG_gameInfo.play.getParticle() != NULL) { g_dComIfG_gameInfo.play.getParticle()->drawNormalP1(i_drawInfo); } } inline void dComIfGp_particle_drawProjection(JPADrawInfo* i_drawInfo) { + ZoneScoped; if (g_dComIfG_gameInfo.play.getParticle() != NULL) { g_dComIfG_gameInfo.play.getParticle()->drawProjection(i_drawInfo); } } inline void dComIfGp_particle_drawNormalPri0_A(JPADrawInfo* i_drawInfo) { + ZoneScoped; if (g_dComIfG_gameInfo.play.getParticle() != NULL) { g_dComIfG_gameInfo.play.getParticle()->drawNormalPri0_A(i_drawInfo); } } inline void dComIfGp_particle_drawNormalPri0_B(JPADrawInfo* i_drawInfo) { + ZoneScoped; if (g_dComIfG_gameInfo.play.getParticle() != NULL) { g_dComIfG_gameInfo.play.getParticle()->drawNormalPri0_B(i_drawInfo); } } inline void dComIfGp_particle_drawFogPri0_A(JPADrawInfo* i_drawInfo) { + ZoneScoped; if (g_dComIfG_gameInfo.play.getParticle() != NULL) { g_dComIfG_gameInfo.play.getParticle()->drawFogPri0_A(i_drawInfo); } } inline void dComIfGp_particle_drawFogPri0_B(JPADrawInfo* i_drawInfo) { + ZoneScoped; if (g_dComIfG_gameInfo.play.getParticle() != NULL) { g_dComIfG_gameInfo.play.getParticle()->drawFogPri0_B(i_drawInfo); } } inline void dComIfGp_particle_drawFogPri1(JPADrawInfo* i_drawInfo) { + ZoneScoped; if (g_dComIfG_gameInfo.play.getParticle() != NULL) { g_dComIfG_gameInfo.play.getParticle()->drawFogPri1(i_drawInfo); } } inline void dComIfGp_particle_drawFogPri2(JPADrawInfo* i_drawInfo) { + ZoneScoped; if (g_dComIfG_gameInfo.play.getParticle() != NULL) { g_dComIfG_gameInfo.play.getParticle()->drawFogPri2(i_drawInfo); } } inline void dComIfGp_particle_drawFogPri3(JPADrawInfo* i_drawInfo) { + ZoneScoped; if (g_dComIfG_gameInfo.play.getParticle() != NULL) { g_dComIfG_gameInfo.play.getParticle()->drawFogPri3(i_drawInfo); } } inline void dComIfGp_particle_drawFogPri4(JPADrawInfo* i_drawInfo) { + ZoneScoped; if (g_dComIfG_gameInfo.play.getParticle() != NULL) { g_dComIfG_gameInfo.play.getParticle()->drawFogPri4(i_drawInfo); } } inline void dComIfGp_particle_drawDarkworld(JPADrawInfo* i_drawInfo) { + ZoneScoped; if (g_dComIfG_gameInfo.play.getParticle() != NULL) { g_dComIfG_gameInfo.play.getParticle()->drawDarkworld(i_drawInfo); } } inline void dComIfGp_particle_drawScreen(JPADrawInfo* i_drawInfo) { + ZoneScoped; if (g_dComIfG_gameInfo.play.getParticle() != NULL) { g_dComIfG_gameInfo.play.getParticle()->drawFogScreen(i_drawInfo); } } inline void dComIfGp_particle_draw2Dgame(JPADrawInfo* i_drawInfo) { + ZoneScoped; if (g_dComIfG_gameInfo.play.getParticle() != NULL) { g_dComIfG_gameInfo.play.getParticle()->draw2Dgame(i_drawInfo); } } inline void dComIfGp_particle_draw2Dfore(JPADrawInfo* i_drawInfo) { + ZoneScoped; if (g_dComIfG_gameInfo.play.getParticle() != NULL) { g_dComIfG_gameInfo.play.getParticle()->draw2Dfore(i_drawInfo); } } inline void dComIfGp_particle_draw2Dback(JPADrawInfo* i_drawInfo) { + ZoneScoped; if (g_dComIfG_gameInfo.play.getParticle() != NULL) { g_dComIfG_gameInfo.play.getParticle()->draw2Dback(i_drawInfo); } } inline void dComIfGp_particle_draw2DmenuFore(JPADrawInfo* i_drawInfo) { + ZoneScoped; if (g_dComIfG_gameInfo.play.getParticle() != NULL) { g_dComIfG_gameInfo.play.getParticle()->draw2DmenuFore(i_drawInfo); } } inline void dComIfGp_particle_draw2DmenuBack(JPADrawInfo* i_drawInfo) { + ZoneScoped; if (g_dComIfG_gameInfo.play.getParticle() != NULL) { g_dComIfG_gameInfo.play.getParticle()->draw2DmenuBack(i_drawInfo); } @@ -4736,114 +4757,142 @@ inline void dComIfGd_setViewport(view_port_class* port) { } inline void dComIfGd_entryZSortListZxlu(J3DPacket* i_packet, cXyz& param_1) { + ZoneScoped; g_dComIfG_gameInfo.drawlist.entryZSortListZxlu(i_packet, param_1); } inline void dComIfGd_entryZSortXluList(J3DPacket* i_packet, cXyz& param_1) { + ZoneScoped; g_dComIfG_gameInfo.drawlist.entryZSortXluList(i_packet, param_1); } inline void dComIfGd_drawCopy2D() { + ZoneScoped; g_dComIfG_gameInfo.drawlist.drawCopy2D(); } inline void dComIfGd_drawOpaListSky() { + ZoneScoped; g_dComIfG_gameInfo.drawlist.drawOpaListSky(); } inline void dComIfGd_drawXluListSky() { + ZoneScoped; g_dComIfG_gameInfo.drawlist.drawXluListSky(); } inline void dComIfGd_drawOpaListBG() { + ZoneScoped; g_dComIfG_gameInfo.drawlist.drawOpaListBG(); } inline void dComIfGd_drawOpaListDarkBG() { + ZoneScoped; g_dComIfG_gameInfo.drawlist.drawOpaListDarkBG(); } inline void dComIfGd_drawOpaListMiddle() { + ZoneScoped; g_dComIfG_gameInfo.drawlist.drawOpaListMiddle(); } inline void dComIfGd_drawOpaList() { + ZoneScoped; g_dComIfG_gameInfo.drawlist.drawOpaList(); } inline void dComIfGd_drawOpaListDark() { + ZoneScoped; g_dComIfG_gameInfo.drawlist.drawOpaListDark(); } inline void dComIfGd_drawOpaListPacket() { + ZoneScoped; g_dComIfG_gameInfo.drawlist.drawOpaListPacket(); } inline void dComIfGd_drawXluListBG() { + ZoneScoped; g_dComIfG_gameInfo.drawlist.drawXluListBG(); } inline void dComIfGd_drawXluListDarkBG() { + ZoneScoped; g_dComIfG_gameInfo.drawlist.drawXluListDarkBG(); } inline void dComIfGd_drawXluList() { + ZoneScoped; g_dComIfG_gameInfo.drawlist.drawXluList(); } inline void dComIfGd_drawXluListDark() { + ZoneScoped; g_dComIfG_gameInfo.drawlist.drawXluListDark(); } inline void dComIfGd_drawXluListInvisible() { + ZoneScoped; g_dComIfG_gameInfo.drawlist.drawXluListInvisible(); } inline void dComIfGd_drawOpaListInvisible() { + ZoneScoped; g_dComIfG_gameInfo.drawlist.drawOpaListInvisible(); } inline void dComIfGd_drawXluListZxlu() { + ZoneScoped; g_dComIfG_gameInfo.drawlist.drawXluListZxlu(); } inline void dComIfGd_drawXluList2DScreen() { + ZoneScoped; g_dComIfG_gameInfo.drawlist.drawXluList2DScreen(); } inline void dComIfGd_drawOpaList3Dlast() { + ZoneScoped; g_dComIfG_gameInfo.drawlist.drawOpaList3Dlast(); } inline void dComIfGd_draw2DOpa() { + ZoneScoped; g_dComIfG_gameInfo.drawlist.draw2DOpa(); } inline void dComIfGd_draw2DOpaTop() { + ZoneScoped; g_dComIfG_gameInfo.drawlist.draw2DOpaTop(); } inline void dComIfGd_draw2DXlu() { + ZoneScoped; g_dComIfG_gameInfo.drawlist.draw2DXlu(); } inline void dComIfGd_drawOpaListFilter() { + ZoneScoped; g_dComIfG_gameInfo.drawlist.drawOpaListFilter(); } inline void dComIfGd_drawIndScreen() { + ZoneScoped; g_dComIfG_gameInfo.drawlist.drawOpaListP0(); } inline void dComIfGd_drawListZxlu() { + ZoneScoped; g_dComIfG_gameInfo.drawlist.drawXluListZxlu(); } inline void dComIfGd_drawShadow(Mtx param_0) { + ZoneScoped; g_dComIfG_gameInfo.drawlist.drawShadow(param_0); } inline void dComIfGd_imageDrawShadow(Mtx param_0) { + ZoneScoped; g_dComIfG_gameInfo.drawlist.imageDrawShadow(param_0); } diff --git a/libs/JSystem/src/J3DGraphBase/J3DDrawBuffer.cpp b/libs/JSystem/src/J3DGraphBase/J3DDrawBuffer.cpp index b18a53b8c1..c533face96 100644 --- a/libs/JSystem/src/J3DGraphBase/J3DDrawBuffer.cpp +++ b/libs/JSystem/src/J3DGraphBase/J3DDrawBuffer.cpp @@ -8,6 +8,7 @@ #include "JSystem/J3DGraphBase/J3DDrawBuffer.h" #include "JSystem/J3DGraphBase/J3DMaterial.h" #include "JSystem/JKernel/JKRHeap.h" +#include "tracy/Tracy.hpp" void J3DDrawBuffer::calcZRatio() { mZRatio = (mZFar - mZNear) / (f32)mEntryTableSize; @@ -224,6 +225,7 @@ void J3DDrawBuffer::draw() const { } void J3DDrawBuffer::drawHead() const { + ZoneScoped; u32 size = mEntryTableSize; J3DPacket** buf = mpBuffer; @@ -235,6 +237,7 @@ void J3DDrawBuffer::drawHead() const { } void J3DDrawBuffer::drawTail() const { + ZoneScoped; for (int i = mEntryTableSize - 1; i >= 0; i--) { for (J3DPacket* packet = mpBuffer[i]; packet != NULL; packet = packet->getNextPacket()) { packet->draw(); diff --git a/libs/JSystem/src/J3DGraphBase/J3DPacket.cpp b/libs/JSystem/src/J3DGraphBase/J3DPacket.cpp index a702ff5523..bf2348020a 100644 --- a/libs/JSystem/src/J3DGraphBase/J3DPacket.cpp +++ b/libs/JSystem/src/J3DGraphBase/J3DPacket.cpp @@ -1,14 +1,15 @@ #include "JSystem/JSystem.h" // IWYU pragma: keep -#include "JSystem/J3DGraphBase/J3DPacket.h" +#include +#include #include "JSystem/J3DGraphAnimator/J3DModel.h" #include "JSystem/J3DGraphBase/J3DDrawBuffer.h" #include "JSystem/J3DGraphBase/J3DMaterial.h" +#include "JSystem/J3DGraphBase/J3DPacket.h" #include "JSystem/J3DGraphBase/J3DShapeMtx.h" #include "JSystem/JKernel/JKRHeap.h" -#include -#include #include "global.h" +#include "tracy/Tracy.hpp" J3DError J3DDisplayListObj::newDisplayList(u32 maxSize) { mMaxSize = ALIGN_NEXT(maxSize, 0x20); @@ -207,12 +208,13 @@ bool J3DMatPacket::isSame(J3DMatPacket* pOther) const { } void J3DMatPacket::draw() { + ZoneScoped; #if TARGET_PC j3dSys.setTexture(mpTexture); #endif mpMaterial->load(); -#if TARGET_PC +#if DEBUG && TARGET_PC if (mpMaterial->mMaterialName != nullptr) { char buf[64]; snprintf(buf, sizeof(buf), "Mat: %s", mpMaterial->mMaterialName); @@ -239,7 +241,7 @@ void J3DMatPacket::draw() { J3DShape::resetVcdVatCache(); -#if TARGET_PC +#if DEBUG && TARGET_PC if (mpMaterial->mMaterialName != nullptr) { GXPopDebugGroup(); } @@ -388,6 +390,7 @@ void J3DShapePacket::draw() { } void J3DShapePacket::drawFast() { + ZoneScoped; if (!checkFlag(J3DShpFlag_Hidden) && mpShape != NULL) { prepareDraw(); diff --git a/libs/JSystem/src/J3DGraphBase/J3DShape.cpp b/libs/JSystem/src/J3DGraphBase/J3DShape.cpp index 76f58b7586..fc7ac5b727 100644 --- a/libs/JSystem/src/J3DGraphBase/J3DShape.cpp +++ b/libs/JSystem/src/J3DGraphBase/J3DShape.cpp @@ -3,13 +3,13 @@ #include "JSystem/J3DGraphBase/J3DShape.h" #include +#include +#include #include "JSystem/J3DGraphBase/J3DFifo.h" #include "JSystem/J3DGraphBase/J3DPacket.h" #include "JSystem/J3DGraphBase/J3DVertex.h" -#include "JSystem/J3DGraphBase/J3DFifo.h" -#include -#include #include "JSystem/JKernel/JKRHeap.h" +#include "tracy/Tracy.hpp" void J3DGDSetVtxAttrFmtv(GXVtxFmt, GXVtxAttrFmtList const*, bool); void J3DFifoLoadPosMtxImm(Mtx, u32); @@ -318,6 +318,7 @@ void J3DShape::setArrayAndBindPipeline() const { } void J3DShape::drawFast() const { + ZoneScoped; if (sOldVcdVatCmd != mVcdVatCmd) { GXCallDisplayList(mVcdVatCmd, kVcdVatDLSize); sOldVcdVatCmd = mVcdVatCmd; diff --git a/libs/JSystem/src/J3DGraphBase/J3DSys.cpp b/libs/JSystem/src/J3DGraphBase/J3DSys.cpp index 2f9cf51a89..b4598bfa7a 100644 --- a/libs/JSystem/src/J3DGraphBase/J3DSys.cpp +++ b/libs/JSystem/src/J3DGraphBase/J3DSys.cpp @@ -6,6 +6,7 @@ #include "JSystem/J3DGraphBase/J3DTexture.h" #include "dusk/gx_helper.h" #include "global.h" +#include "tracy/Tracy.hpp" J3DSys j3dSys; @@ -121,6 +122,8 @@ void J3DSys::setTexCacheRegion(GXTexCacheSize size) { } void J3DSys::drawInit() { + ZoneScoped; + GXInvalidateVtxCache(); GXSetCurrentMtx(GX_PNMTX0); GXSetCullMode(GX_CULL_BACK); @@ -226,6 +229,7 @@ void J3DSys::drawInit() { } void J3DSys::reinitGX() { + ZoneScoped; reinitGenMode(); reinitLighting(); reinitTransform(); diff --git a/libs/JSystem/src/JAudio2/JASAiCtrl.cpp b/libs/JSystem/src/JAudio2/JASAiCtrl.cpp index ede7c1d267..75b475342b 100644 --- a/libs/JSystem/src/JAudio2/JASAiCtrl.cpp +++ b/libs/JSystem/src/JAudio2/JASAiCtrl.cpp @@ -18,6 +18,8 @@ #include #include +#include "tracy/Tracy.hpp" + s16* JASDriver::sDmaDacBuffer[3]; static u8 data_804507A8 = 3; @@ -143,6 +145,7 @@ void JASDriver::updateDac() { } void JASDriver::updateDSP() { + ZoneScoped; JASProbe::start(3, "SFR-UPDATE"); JASDsp::invalChannelAll(); diff --git a/libs/JSystem/src/JFramework/JFWDisplay.cpp b/libs/JSystem/src/JFramework/JFWDisplay.cpp index 26788f9f38..6c7a5ebcae 100644 --- a/libs/JSystem/src/JFramework/JFWDisplay.cpp +++ b/libs/JSystem/src/JFramework/JFWDisplay.cpp @@ -18,6 +18,7 @@ #include "dusk/logging.h" #include "dusk/settings.h" #include "global.h" +#include "tracy/Tracy.hpp" void JFWDisplay::ctor_subroutine(bool enableAlpha) { mEnableAlpha = enableAlpha; @@ -349,6 +350,7 @@ void JFWDisplay::waitBlanking(int param_0) { } static void waitForTick(u32 p1, u16 p2) { + ZoneScopedC(tracy::Color::DimGray); #if TARGET_PC if (dusk::getTransientSettings().skipFrameRateLimit) { p1 = OS_TIMER_CLOCK / 120; diff --git a/libs/JSystem/src/JParticle/JPABaseShape.cpp b/libs/JSystem/src/JParticle/JPABaseShape.cpp index f28add564e..178606a687 100644 --- a/libs/JSystem/src/JParticle/JPABaseShape.cpp +++ b/libs/JSystem/src/JParticle/JPABaseShape.cpp @@ -9,6 +9,8 @@ #include #include +#include "tracy/Tracy.hpp" + void JPASetPointSize(JPAEmitterWorkData* work) { GXSetPointSize((u8)(25.0f * work->mGlobalPtclScl.x), GX_TO_ONE); } @@ -684,6 +686,8 @@ void JPADrawDirection(JPAEmitterWorkData* param_0, JPABaseParticle* param_1) { return; } + ZoneScoped; + JGeometry::TVec3 local_6c; JGeometry::TVec3 local_78; p_direction[param_0->mDirType](param_0, param_1, &local_6c); @@ -729,6 +733,8 @@ void JPADrawRotDirection(JPAEmitterWorkData* param_0, JPABaseParticle* param_1) return; } + ZoneScoped; + f32 sinRot = JMASSin(param_1->mRotateAngle); f32 cosRot = JMASCos(param_1->mRotateAngle); JGeometry::TVec3 local_6c; @@ -779,6 +785,8 @@ void JPADrawDBillboard(JPAEmitterWorkData* param_0, JPABaseParticle* param_1) { return; } + ZoneScoped; + JGeometry::TVec3 local_70; p_direction[param_0->mDirType](param_0, param_1, &local_70); JGeometry::TVec3 aTStack_7c(param_0->mPosCamMtx[2][0], param_0->mPosCamMtx[2][1], param_0->mPosCamMtx[2][2]); @@ -814,6 +822,8 @@ void JPADrawRotation(JPAEmitterWorkData* param_0, JPABaseParticle* param_1) { return; } + ZoneScoped; + f32 sinRot = JMASSin(param_1->mRotateAngle); f32 cosRot = JMASCos(param_1->mRotateAngle); f32 particleX = param_0->mGlobalPtclScl.x * param_1->mParticleScaleX; @@ -835,6 +845,8 @@ void JPADrawPoint(JPAEmitterWorkData* work, JPABaseParticle* ptcl) { return; } + ZoneScoped; + GXSetVtxDesc(GX_VA_POS, GX_DIRECT); GXSetVtxDesc(GX_VA_TEX0, GX_DIRECT); GXBegin(GX_POINTS, GX_VTXFMT1, 1); @@ -850,6 +862,8 @@ void JPADrawLine(JPAEmitterWorkData* param_0, JPABaseParticle* param_1) { return; } + ZoneScoped; + JGeometry::TVec3 local_1c(param_1->mPosition); JGeometry::TVec3 local_28; param_1->getVelVec(&local_28); @@ -883,6 +897,8 @@ JPANode* getPrev(JPANode* param_0) { typedef JPANode* (*getNodeFunc)(JPANode*); void JPADrawStripe(JPAEmitterWorkData* param_0) { + ZoneScoped; + JPABaseShape* shape = param_0->mpRes->getBsp(); u32 ptcl_num = param_0->mpAlivePtcl->getNum(); if (ptcl_num < 2) { @@ -969,6 +985,8 @@ void JPADrawStripe(JPAEmitterWorkData* param_0) { } void JPADrawStripeX(JPAEmitterWorkData* param_0) { + ZoneScoped; + JPABaseShape* shape = param_0->mpRes->getBsp(); u32 ptcl_num = param_0->mpAlivePtcl->getNum(); if (ptcl_num < 2) { diff --git a/libs/JSystem/src/JParticle/JPAEmitterManager.cpp b/libs/JSystem/src/JParticle/JPAEmitterManager.cpp index 5cefe85329..3a793000ed 100644 --- a/libs/JSystem/src/JParticle/JPAEmitterManager.cpp +++ b/libs/JSystem/src/JParticle/JPAEmitterManager.cpp @@ -8,6 +8,8 @@ #include "JSystem/JUtility/JUTAssert.h" #include +#include "tracy/Tracy.hpp" + JPAEmitterManager::JPAEmitterManager(u32 i_ptclNum, u32 i_emtrNum, JKRHeap* pHeap, u8 i_gidMax, u8 i_ridMax) { emtrNum = i_emtrNum; @@ -85,6 +87,7 @@ void JPAEmitterManager::calc(u8 group_id) { } void JPAEmitterManager::draw(JPADrawInfo const* drawInfo, u8 group_id) { + ZoneScoped; JUT_ASSERT(192, group_id < gidMax); drawInfo->getCamMtx(pWd->mPosCamMtx); drawInfo->getPrjMtx(pWd->mPrjMtx); diff --git a/libs/JSystem/src/JParticle/JPAResource.cpp b/libs/JSystem/src/JParticle/JPAResource.cpp index 4a33ed0aab..2ae4709fc3 100644 --- a/libs/JSystem/src/JParticle/JPAResource.cpp +++ b/libs/JSystem/src/JParticle/JPAResource.cpp @@ -16,6 +16,7 @@ #include "JSystem/JParticle/JPAParticle.h" #include "JSystem/JParticle/JPAResourceManager.h" #include "global.h" +#include "tracy/Tracy.hpp" JPAResource::JPAResource() { mpCalcEmitterFuncList = mpDrawEmitterFuncList = mpDrawEmitterChildFuncList = NULL; @@ -782,6 +783,7 @@ bool JPAResource::calc(JPAEmitterWorkData* work, JPABaseEmitter* emtr) { } void JPAResource::draw(JPAEmitterWorkData* work, JPABaseEmitter* emtr) { + ZoneScoped; work->mpEmtr = emtr; work->mpRes = this; work->mDrawCount = 0; @@ -798,6 +800,7 @@ void JPAResource::draw(JPAEmitterWorkData* work, JPABaseEmitter* emtr) { } void JPAResource::drawP(JPAEmitterWorkData* work) { + ZoneScoped; work->mpEmtr->clearStatus(0x80); work->mGlobalPtclScl.x = work->mpEmtr->mGlobalPScl.x * pBsp->getBaseSizeX(); @@ -860,6 +863,7 @@ void JPAResource::drawP(JPAEmitterWorkData* work) { } void JPAResource::drawC(JPAEmitterWorkData* work) { + ZoneScoped; work->mpEmtr->setStatus(0x80); if (pCsp->isScaleInherited()) { diff --git a/src/d/d_kankyo_rain.cpp b/src/d/d_kankyo_rain.cpp index c1927c5875..d91e0c03ba 100644 --- a/src/d/d_kankyo_rain.cpp +++ b/src/d/d_kankyo_rain.cpp @@ -4041,6 +4041,7 @@ void dKyr_drawSnow(Mtx drawMtx, u8** tex) { } void dKyr_drawStar(Mtx drawMtx, u8** tex) { + ZoneScoped; dScnKy_env_light_c* envlight = dKy_getEnvlight(); dKankyo_star_Packet* star_packet = g_env_light.mpStarPacket; camera_class* camera = (camera_class*)dComIfGp_getCamera(0); diff --git a/src/d/d_particle.cpp b/src/d/d_particle.cpp index 6c8d6e3f75..248424e9a2 100644 --- a/src/d/d_particle.cpp +++ b/src/d/d_particle.cpp @@ -8,22 +8,23 @@ // weak data from it (unlike here). #include "d/d_particle.h" -#include "d/d_jnt_col.h" +#include +#include "JSystem/J3DGraphAnimator/J3DMaterialAnm.h" +#include "JSystem/J3DGraphBase/J3DMaterial.h" #include "JSystem/JKernel/JKRExpHeap.h" #include "JSystem/JKernel/JKRSolidHeap.h" -#include "JSystem/J3DGraphBase/J3DMaterial.h" -#include "JSystem/J3DGraphAnimator/J3DMaterialAnm.h" +#include "JSystem/JMath/JMATrigonometric.h" #include "JSystem/JParticle/JPAEmitterManager.h" #include "JSystem/JParticle/JPAResourceManager.h" -#include "JSystem/JMath/JMATrigonometric.h" -#include "d/d_s_play.h" -#include -#include "d/d_com_inf_game.h" -#include "m_Do/m_Do_lib.h" -#include "m_Do/m_Do_graphic.h" -#include "f_op/f_op_actor_mng.h" -#include "d/actor/d_a_player.h" #include "SSystem/SComponent/c_math.h" +#include "d/actor/d_a_player.h" +#include "d/d_com_inf_game.h" +#include "d/d_jnt_col.h" +#include "d/d_s_play.h" +#include "f_op/f_op_actor_mng.h" +#include "m_Do/m_Do_graphic.h" +#include "m_Do/m_Do_lib.h" +#include "tracy/Tracy.hpp" #ifndef __MWERKS__ #include "dusk/math.h" @@ -1325,6 +1326,7 @@ void dPa_control_c::cleanup() { } void dPa_control_c::calc3D() { + ZoneScoped; if (isStatus(2)) { offStatus(2); } else { @@ -1336,12 +1338,14 @@ void dPa_control_c::calc3D() { } void dPa_control_c::calc2D() { + ZoneScoped; for (u8 i = 14; i <= 16; i++) { mEmitterMng->calc(i); } } void dPa_control_c::calcMenu() { + ZoneScoped; if (mEmitterMng != NULL) { for (u8 i = 17; i <= 18; i++) { mEmitterMng->calc(i); diff --git a/src/dusk/OSThread.cpp b/src/dusk/OSThread.cpp index af339e8656..ae42fa6f5d 100644 --- a/src/dusk/OSThread.cpp +++ b/src/dusk/OSThread.cpp @@ -17,6 +17,7 @@ #include #include "JSystem/JKernel/JKRHeap.h" +#include "common/TracySystem.hpp" #include "dusk/main.h" #include "dusk/os.h" @@ -667,6 +668,9 @@ void OSSetCurrentThreadName(const char* name) { // "Why is this current thread only?", you might ask? // Because macOS requires that. For some reason. +#if TRACY_ENABLE + tracy::SetThreadName(name); +#else #if _WIN32 wchar_t buffer[256]; const auto converted = MultiByteToWideChar( @@ -687,6 +691,7 @@ void OSSetCurrentThreadName(const char* name) { #elif __APPLE__ pthread_setname_np(name); #endif +#endif } #ifdef __cplusplus diff --git a/src/dusk/audio/DuskAudioSystem.cpp b/src/dusk/audio/DuskAudioSystem.cpp index 7b464e4f0e..c818e46738 100644 --- a/src/dusk/audio/DuskAudioSystem.cpp +++ b/src/dusk/audio/DuskAudioSystem.cpp @@ -14,6 +14,7 @@ #include "DuskDsp.hpp" #include "JSystem/JAudio2/JASAudioThread.h" #include "JSystem/JAudio2/JASDriverIF.h" +#include "tracy/Tracy.hpp" using namespace dusk::audio; @@ -82,18 +83,25 @@ void dusk::audio::SetEnableReverb(const bool value) { EnableReverb = value; } +#ifdef TRACY_ENABLE +static auto FrameName = "GetNewAudio"; +#endif + void SDLCALL GetNewAudio( void*, SDL_AudioStream*, int needed, int) { + FrameMarkStart(FrameName); while (needed > 0) { const int rendered = RenderNewAudioFrame(); needed -= rendered; } + FrameMarkEnd(FrameName); } int RenderNewAudioFrame() { + ZoneScoped; JASCriticalSection section; const u32 countSubframes = JASDriver::getSubFrames(); @@ -120,6 +128,7 @@ static void InterleaveOutputData(const OutputSubframe& data, std::span targ } void RenderAudioSubframe() { + ZoneScoped; OutBuffer = {}; JASDriver::updateDSP(); diff --git a/src/dusk/audio/DuskDsp.cpp b/src/dusk/audio/DuskDsp.cpp index e7dff6a2a5..854c49edc6 100644 --- a/src/dusk/audio/DuskDsp.cpp +++ b/src/dusk/audio/DuskDsp.cpp @@ -14,6 +14,7 @@ #include "dusk/audio/DuskAudioSystem.h" #include "dusk/endian.h" #include "global.h" +#include "tracy/Tracy.hpp" using namespace dusk::audio; @@ -133,6 +134,7 @@ static void MixSubframe(DspSubframe& dst, const DspSubframe& src) { } void dusk::audio::DspRender(OutputSubframe& subframe) { + ZoneScoped; if (DumpAudio != sDumpWasActive) { sDumpWasActive = DumpAudio; if (DumpAudio) { diff --git a/src/dusk/audio/JASCriticalSection.cpp b/src/dusk/audio/JASCriticalSection.cpp index 9b315b9d0b..9baf2d9bac 100644 --- a/src/dusk/audio/JASCriticalSection.cpp +++ b/src/dusk/audio/JASCriticalSection.cpp @@ -2,7 +2,9 @@ #include -static std::recursive_mutex gAudioThreadMutex; +#include "tracy/Tracy.hpp" + +static TracyLockable(std::recursive_mutex, gAudioThreadMutex); JASCriticalSection::JASCriticalSection() { gAudioThreadMutex.lock(); diff --git a/src/dusk/imgui/ImGuiConsole.cpp b/src/dusk/imgui/ImGuiConsole.cpp index fa47e53da3..a238bb7c7a 100644 --- a/src/dusk/imgui/ImGuiConsole.cpp +++ b/src/dusk/imgui/ImGuiConsole.cpp @@ -17,6 +17,7 @@ #include "dusk/settings.h" #include "dusk/audio/DuskAudioSystem.h" #include "dusk/dusk.h" +#include "tracy/Tracy.hpp" #if _WIN32 #define NOMINMAX @@ -200,6 +201,7 @@ namespace dusk { } void ImGuiConsole::PreDraw() { + ZoneScoped; if (!m_isLaunchInitialized) { InitSettings(); diff --git a/src/dusk/logging.cpp b/src/dusk/logging.cpp index 1a92f9ac37..37d1d4f55f 100644 --- a/src/dusk/logging.cpp +++ b/src/dusk/logging.cpp @@ -2,6 +2,8 @@ #include #include +#include "tracy/Tracy.hpp" + bool StubLogEnabled = true; using namespace std::literals::string_view_literals; @@ -32,6 +34,7 @@ static bool IsForStubLog(const char* message) { void aurora_log_callback(AuroraLogLevel level, const char* module, const char* message, unsigned int len) { + ZoneScoped; if (StubLogEnabled && level != LOG_FATAL && IsForStubLog(message)) { dusk::SendToStubLog(level, module, message); return; diff --git a/src/dusk/stubs.cpp b/src/dusk/stubs.cpp index e655ed6d7d..668541f49c 100644 --- a/src/dusk/stubs.cpp +++ b/src/dusk/stubs.cpp @@ -13,6 +13,8 @@ #include #include +#include "tracy/Tracy.hpp" + #ifndef _WIN32 #include #include @@ -355,6 +357,7 @@ void VISetNextFrameBuffer(void* fb) { } void VIWaitForRetrace() { + ZoneScoped; sRetraceCount++; if (sVIPreRetraceCallback) { sVIPreRetraceCallback(sRetraceCount); diff --git a/src/f_ap/f_ap_game.cpp b/src/f_ap/f_ap_game.cpp index b1edb7fa23..180e6bc361 100644 --- a/src/f_ap/f_ap_game.cpp +++ b/src/f_ap/f_ap_game.cpp @@ -1,26 +1,27 @@ #include "f_ap/f_ap_game.h" +#include +#include "DynamicLink.h" +#include "JSystem/J3DGraphLoader/J3DModelLoader.h" +#include "JSystem/J3DGraphLoader/J3DModelSaver.h" +#include "JSystem/JHostIO/JORFile.h" +#include "JSystem/JKernel/JKRAram.h" +#include "JSystem/JKernel/JKRAramArchive.h" +#include "JSystem/JKernel/JKRSolidHeap.h" +#include "JSystem/JUtility/JUTDbPrint.h" #include "SSystem/SComponent/c_counter.h" +#include "d/actor/d_a_alink.h" +#include "d/actor/d_a_grass.h" +#include "d/actor/d_a_midna.h" +#include "d/d_model.h" +#include "d/d_tresure.h" +#include "dusk/logging.h" #include "f_op/f_op_camera_mng.h" #include "f_op/f_op_draw_tag.h" #include "f_op/f_op_overlap_mng.h" #include "f_op/f_op_scene_mng.h" -#include "m_Do/m_Do_main.h" #include "m_Do/m_Do_graphic.h" -#include "DynamicLink.h" -#include "JSystem/JKernel/JKRSolidHeap.h" -#include "JSystem/JKernel/JKRAram.h" -#include "JSystem/JKernel/JKRAramArchive.h" -#include "JSystem/JUtility/JUTDbPrint.h" -#include "JSystem/JHostIO/JORFile.h" -#include "JSystem/J3DGraphLoader/J3DModelLoader.h" -#include "JSystem/J3DGraphLoader/J3DModelSaver.h" -#include "d/actor/d_a_alink.h" -#include "d/actor/d_a_midna.h" -#include "d/d_model.h" -#include "d/actor/d_a_grass.h" -#include "d/d_tresure.h" -#include -#include "dusk/logging.h" +#include "m_Do/m_Do_main.h" +#include "tracy/Tracy.hpp" fapGm_HIO_c::fapGm_HIO_c() { mUsingHostIO = true; @@ -722,6 +723,7 @@ void fapGm_After() { } void fapGm_Execute() { + ZoneScoped; static u32 sExecCount = 0; if (sExecCount < 10 || (sExecCount % 300 == 0)) { DuskLog.debug("fapGm_Execute frame={}", sExecCount); diff --git a/src/f_pc/f_pc_manager.cpp b/src/f_pc/f_pc_manager.cpp index 4f676c242f..a3d85d4df6 100644 --- a/src/f_pc/f_pc_manager.cpp +++ b/src/f_pc/f_pc_manager.cpp @@ -22,6 +22,8 @@ #include "m_Do/m_Do_controller_pad.h" #include +#include "tracy/Tracy.hpp" + void fpcM_Draw(void* i_proc) { fpcDw_Execute((base_process_class*)i_proc); } @@ -43,6 +45,7 @@ BOOL fpcM_IsCreating(fpc_ProcID i_id) { } void fpcM_Management(fpcM_ManagementFunc i_preExecuteFn, fpcM_ManagementFunc i_postExecuteFn) { + ZoneScoped; MtxInit(); if (!fapGm_HIO_c::isCaptureScreen()) { dComIfGd_peekZdata(); diff --git a/src/m_Do/m_Do_audio.cpp b/src/m_Do/m_Do_audio.cpp index fc386bda88..fc44eb7fb6 100644 --- a/src/m_Do/m_Do_audio.cpp +++ b/src/m_Do/m_Do_audio.cpp @@ -12,6 +12,7 @@ #include "d/d_debug_viewer.h" #include "m_Do/m_Do_Reset.h" #include "m_Do/m_Do_dvd_thread.h" +#include "tracy/Tracy.hpp" #if PLATFORM_WII || PLATFORM_SHIELD #include "Z2AudioCS/Z2AudioCS.h" @@ -157,6 +158,7 @@ static void mDoAud_Create() { void mDoAud_Execute() { DUSK_AUDIO_SKIP() + ZoneScoped; if (!mDoAud_zelAudio_c::isInitFlag()) { if (!mDoRst::isShutdown() && !mDoRst::isReturnToMenu()) { diff --git a/src/m_Do/m_Do_controller_pad.cpp b/src/m_Do/m_Do_controller_pad.cpp index c8a8566313..8ba03fc63b 100644 --- a/src/m_Do/m_Do_controller_pad.cpp +++ b/src/m_Do/m_Do_controller_pad.cpp @@ -10,6 +10,7 @@ #include "f_ap/f_ap_game.h" #include "m_Do/m_Do_Reset.h" #include "m_Do/m_Do_main.h" +#include "tracy/Tracy.hpp" JUTGamePad* mDoCPd_c::m_gamePad[4]; @@ -56,6 +57,7 @@ void mDoCPd_c::create() { } void mDoCPd_c::read() { + ZoneScoped; JUTGamePad::read(); if (!mDoRst::isReset() && mDoRst::is3ButtonReset()) { diff --git a/src/m_Do/m_Do_graphic.cpp b/src/m_Do/m_Do_graphic.cpp index 5887c22495..a5bd4640c8 100644 --- a/src/m_Do/m_Do_graphic.cpp +++ b/src/m_Do/m_Do_graphic.cpp @@ -7,6 +7,9 @@ #include "d/dolzel.h" // IWYU pragma: keep +#include +#include +#include "DynamicLink.h" #include "JSystem/J2DGraph/J2DOrthoGraph.h" #include "JSystem/J2DGraph/J2DPrint.h" #include "JSystem/JAWExtSystem/JAWExtSystem.h" @@ -19,24 +22,22 @@ #include "SSystem/SComponent/c_math.h" #include "d/actor/d_a_player.h" #include "d/d_com_inf_game.h" -#include "d/d_menu_collect.h" +#include "d/d_debug_viewer.h" #include "d/d_jcam_editor.h" #include "d/d_jpreviewer.h" -#include +#include "d/d_menu_collect.h" +#include "d/d_meter2_info.h" +#include "d/d_s_play.h" +#include "dusk/endian.h" +#include "dusk/gx_helper.h" +#include "dusk/logging.h" #include "f_ap/f_ap_game.h" #include "f_op/f_op_camera_mng.h" #include "m_Do/m_Do_controller_pad.h" #include "m_Do/m_Do_graphic.h" #include "m_Do/m_Do_machine.h" #include "m_Do/m_Do_main.h" -#include "d/d_debug_viewer.h" -#include "d/d_meter2_info.h" -#include "d/d_s_play.h" -#include "DynamicLink.h" -#include -#include "dusk/endian.h" -#include "dusk/logging.h" -#include "dusk/gx_helper.h" +#include "tracy/Tracy.hpp" #if PLATFORM_WII || PLATFORM_SHIELD #include @@ -348,6 +349,8 @@ void mDoGph_gInf_c::create() { static bool data_80450BE8; void mDoGph_gInf_c::beginRender() { + ZoneScoped; + #if PLATFORM_WII || PLATFORM_SHIELD VISetTrapFilter(fapGmHIO_getTrapFilter() ? 1 : 0); VISetGamma((VIGamma)fapGmHIO_getGamma()); @@ -416,6 +419,7 @@ void mDoGph_gInf_c::fadeOut(f32 fadeSpeed) { } void darwFilter(GXColor matColor) { + ZoneScoped; GXSetNumChans(1); GXSetChanCtrl(GX_COLOR0A0, GX_FALSE, GX_SRC_REG, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE); @@ -834,6 +838,7 @@ void mDoGph_drawFilterQuad(s8 param_0, s8 param_1) { #endif static void drawDepth2(view_class* param_0, view_port_class* param_1, int param_2) { + ZoneScoped; static GXColorS10 l_tevColor0 = {0, 0, 0, 0}; if (daPy_getLinkPlayerActorClass() != NULL) { @@ -1087,6 +1092,7 @@ static void drawDepth2(view_class* param_0, view_port_class* param_1, int param_ } static void trimming(view_class* param_0, view_port_class* param_1) { + ZoneScoped; UNUSED(param_0); s16 y_orig = (int)param_1->y_orig & ~7; @@ -1199,6 +1205,7 @@ void mDoGph_gInf_c::bloom_c::remove() { void mDoGph_gInf_c::bloom_c::draw() { #if TARGET_PC + ZoneScoped; if (!dusk::getSettings().game.enableBloom) { return; } @@ -1473,6 +1480,7 @@ static void retry_captue_frame(view_class* param_0, view_port_class* param_1, in } static void motionBlure(view_class* param_0) { + ZoneScoped; if (g_env_light.is_blure) { GXLoadTexObj(mDoGph_gInf_c::getFrameBufferTexObj(), GX_TEXMAP0); GXColor local_60; @@ -1652,6 +1660,7 @@ static void captureScreenPerspDrawInfo(JPADrawInfo& info) { #endif static void drawItem3D() { + ZoneScoped; Mtx item_mtx; dMenu_Collect3D_c::setupItem3D(item_mtx); @@ -1668,6 +1677,7 @@ static void drawItem3D() { } int mDoGph_Painter() { + ZoneScoped; // Diagnostic: log windowNum to track game state machine progress static bool sDiagLoggedWindow = false; if (!sDiagLoggedWindow) { diff --git a/src/m_Do/m_Do_main.cpp b/src/m_Do/m_Do_main.cpp index bcf958780e..31833cf0e8 100644 --- a/src/m_Do/m_Do_main.cpp +++ b/src/m_Do/m_Do_main.cpp @@ -60,6 +60,7 @@ #include "SDL3/SDL_filesystem.h" #include "cxxopts.hpp" #include "dusk/config.hpp" +#include "tracy/Tracy.hpp" // --- GLOBALS --- s8 mDoMain::developmentMode = -1; @@ -198,6 +199,8 @@ void main01(void) { mDoAud_Execute(); aurora_end_frame(); + + FrameMark; } while (true); exit:; From fb0ce81325ff7dd79c863c2a1a0a4c56ea7b2c7a Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Wed, 8 Apr 2026 01:06:42 +0200 Subject: [PATCH 337/350] Make waitForTick properly sleep MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It was spinwaiting 💀 --- libs/JSystem/src/JFramework/JFWDisplay.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/libs/JSystem/src/JFramework/JFWDisplay.cpp b/libs/JSystem/src/JFramework/JFWDisplay.cpp index 26788f9f38..a726889863 100644 --- a/libs/JSystem/src/JFramework/JFWDisplay.cpp +++ b/libs/JSystem/src/JFramework/JFWDisplay.cpp @@ -5,6 +5,7 @@ #include #include #include +#include "SDL3/SDL_timer.h" #include "JSystem/J2DGraph/J2DOrthoGraph.h" #include "JSystem/JFramework/JFWDisplay.h" #include "JSystem/JKernel/JKRHeap.h" @@ -384,6 +385,12 @@ static void waitForTick(u32 p1, u16 p2) { JSUList JFWAlarm::sList(false); +#if TARGET_PC +void JFWDisplay::threadSleep(s64 time) { + SDL_DelayNS(OSTicksToMicroseconds(time) * 1'000); +} +#else + static void JFWThreadAlarmHandler(OSAlarm* p_alarm, OSContext* p_ctx) { JFWAlarm* alarm = static_cast(p_alarm); alarm->removeLink(); @@ -401,6 +408,7 @@ void JFWDisplay::threadSleep(s64 time) { OSSuspendThread(alarm.getThread()); OSRestoreInterrupts(status); } +#endif static void dummy() { JUTXfb::getManager()->setDisplayingXfbIndex(0); From 6d69e50252d38cfda435b1fe826b5acc01dc23ac Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Thu, 9 Apr 2026 19:01:58 +0200 Subject: [PATCH 338/350] Make configure.py explicitly refuse since it's not needed anymore Avoid people getting confused if they try to run it. --- configure.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/configure.py b/configure.py index 313d24f55c..147b7150e7 100755 --- a/configure.py +++ b/configure.py @@ -1,5 +1,9 @@ #!/usr/bin/env python3 +print("You do not need to run configure.py for Dusk! This file is here to avoid conflicts with decomp.") +print("Use CMake to configure your build instead.") +exit(1) + ### # Generates build files for the project. # This file also includes the project configuration, From c8628b6799a9600693dd8274acf45972fe69c07f Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Thu, 9 Apr 2026 13:50:30 -0400 Subject: [PATCH 339/350] Fix shop cursor positions --- src/m_Do/m_Do_lib.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/m_Do/m_Do_lib.cpp b/src/m_Do/m_Do_lib.cpp index b865d9eff8..93df5c23cc 100644 --- a/src/m_Do/m_Do_lib.cpp +++ b/src/m_Do/m_Do_lib.cpp @@ -153,8 +153,13 @@ void mDoLib_project(Vec* src, Vec* dst, JGeometry::TBox2 viewport) { xOffset = (0.5f * ((2.0f * viewport.i.x) + viewport.f.x)) - (int)(FB_WIDTH / 2); xSize = FB_WIDTH; } else { + #if TARGET_PC + xOffset = mDoGph_gInf_c::getMinXF(); + xSize = viewport.f.x * mDoGph_gInf_c::hudAspectScaleUp; + #else xOffset = viewport.i.x; xSize = viewport.f.x; + #endif } if (viewport.i.y != 0.0f) { From 03f81f75ca24e79c0fb9b95aa9fc6f1f42df2514 Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Thu, 9 Apr 2026 17:40:51 -0400 Subject: [PATCH 340/350] Moved check behind HYRULE_FIELD_SPEEDHACK define --- src/d/d_map.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/d/d_map.cpp b/src/d/d_map.cpp index 3fe638cd2f..7dc5187a88 100644 --- a/src/d/d_map.cpp +++ b/src/d/d_map.cpp @@ -539,10 +539,16 @@ void renderingAmap_c::rendering(dDrawPath_c::poly_class const* i_poly) { } } +/* Enabling the following definition will modify the following function to + * make the map look worse for extra speed in the emulator, especially in large + * areas such as hyrule field. + */ +#define HYRULE_FIELD_SPEEDHACK + bool renderingAmap_c::isDrawOutSideTrim() { bool rt = false; - #if TARGET_PC + #ifdef HYRULE_FIELD_SPEEDHACK return 0; #endif From 7d86edde95042c3ae01e29ffe310d208465ff145 Mon Sep 17 00:00:00 2001 From: madeline Date: Thu, 9 Apr 2026 14:44:55 -0700 Subject: [PATCH 341/350] IIR filter --- src/dusk/audio/DuskDsp.cpp | 40 ++++++++++++++++++++++++++++++++++++++ src/dusk/audio/DuskDsp.hpp | 10 ++++++++++ 2 files changed, 50 insertions(+) diff --git a/src/dusk/audio/DuskDsp.cpp b/src/dusk/audio/DuskDsp.cpp index e7dff6a2a5..b5c3e2588c 100644 --- a/src/dusk/audio/DuskDsp.cpp +++ b/src/dusk/audio/DuskDsp.cpp @@ -116,6 +116,14 @@ static void ResetChannel(JASDsp::TChannel& channel, ChannelAuxData& aux) { aux.resamplePos = 0.0; aux.resamplePrev = 0; + aux.prev_lp_out = 0.0f; + aux.prev_lp_in = 0.0f; + + aux.biq_in1 = 0.0f; + aux.biq_in2 = 0.0f; + aux.biq_out1 = 0.0f; + aux.biq_out2 = 0.0f; + for (auto& volume : aux.prevVolume) { volume = NAN; } @@ -519,6 +527,38 @@ static void RenderChannel( channelAux.resamplePos = pos; channelAux.resamplePrev = prev; + // IIR FILTER + + // IIR part 1, low-pass: out[n] = (in[n] - in[n-1]) * (coeff/128) + out[n-1] + if (s16 coeff = channel.iir_filter_params[4]; coeff != 0) { + for (f32& sample : audioLoadBuffer) { + f32 out = std::clamp( + (sample - channelAux.prev_lp_in) * ((f32)coeff / 128.0f) + channelAux.prev_lp_out, -1.0f, 1.0f + ); + + channelAux.prev_lp_in = sample; // in[n-1] = in[n] + sample = channelAux.prev_lp_out = out; // out[n-1] = out[n] + } + } + + // IIR part 2, biquad: out[n] = (b1*in[n-1] + b2*in[n-2] + a1*out[n-1] + a2*out[n-2]) / 32768 + if ((channel.mFilterMode & 0x20) != 0) { + for (f32& sample : audioLoadBuffer) { + f32 out = std::clamp(( + channel.iir_filter_params[0] * channelAux.biq_in1 + // b1 + channel.iir_filter_params[1] * channelAux.biq_in2 + // b2 + channel.iir_filter_params[2] * channelAux.biq_out1 + // a1 + channel.iir_filter_params[3] * channelAux.biq_out2 // a2 + ) / 32768.0f, -1.0f, 1.0f); + + // shift history, then store new input and output + channelAux.biq_in2 = channelAux.biq_in1; // in[n-2] = in[n-1] + channelAux.biq_in1 = sample; // in[n-1] = in[n] + channelAux.biq_out2 = channelAux.biq_out1; // out[n-2] = out[n-1] + sample = channelAux.biq_out1 = out; // out[n-1] = out[n] + } + } + // move any remaining samples in the decode buf to the beginning int remainingDecodeBuf = channelAux.decodeBufCount - srcIdx; if (remainingDecodeBuf > 0) { diff --git a/src/dusk/audio/DuskDsp.hpp b/src/dusk/audio/DuskDsp.hpp index c7cb751458..3ca90d6311 100644 --- a/src/dusk/audio/DuskDsp.hpp +++ b/src/dusk/audio/DuskDsp.hpp @@ -52,6 +52,16 @@ namespace dusk::audio { f32 resamplePos; // last consumed sample from decodeBuf s16 resamplePrev; + + // low pass previous state + f32 prev_lp_out; // out[n-1] + f32 prev_lp_in; // in[n-1] + + // biquad state + f32 biq_in1; // in[n-1] + f32 biq_in2; // in[n-2] + f32 biq_out1; // out[n-1] + f32 biq_out2; // out[n-2] }; extern ChannelAuxData ChannelAux[DSP_CHANNELS]; From 4d9e0d6fd0dd924ea6288eef08e6e8a09aaf6162 Mon Sep 17 00:00:00 2001 From: madeline Date: Thu, 9 Apr 2026 15:55:49 -0700 Subject: [PATCH 342/350] fix targeted things being off camera --- src/d/d_camera.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/d/d_camera.cpp b/src/d/d_camera.cpp index b2a5709736..b5583cd93f 100644 --- a/src/d/d_camera.cpp +++ b/src/d/d_camera.cpp @@ -2417,10 +2417,18 @@ f32 dCamera_c::radiusActorInSight(fopAc_ac_c* i_actor1, fopAc_ac_c* i_actor2, cX dDlst_window_c* window = get_window(field_0x0); scissor_class* scissor = window->getScissor(); f32 var_f29 = cAngle::d2r(i_fovY) * 0.5f; +#if TARGET_PC + view_port_class* viewport = window->getViewPort(); + f32 var_f28 = (scissor->height - mTrimHeight * 2.0f) / viewport->height; + f32 sp34 = var_f29 * var_f28 * (mTrimHeight < 0.01f ? 0.95f : 1.0f); + var_f29 *= mWindowAspect; + var_f28 = scissor->width / viewport->width; +#else f32 var_f28 = (scissor->height - mTrimHeight * 2.0f) / FB_HEIGHT; f32 sp34 = var_f29 * var_f28 * (mTrimHeight < 0.01f ? 0.95f : 1.0f); var_f29 *= mWindowAspect; var_f28 = scissor->width / FB_WIDTH; +#endif f32 sp30 = var_f29 * var_f28 * 0.85f; cXyz pos1 = attentionPos(i_actor1); From 7eeee622820d366142de3d1b03e48a72d5d1fcf9 Mon Sep 17 00:00:00 2001 From: TakaRikka Date: Thu, 9 Apr 2026 16:55:07 -0700 Subject: [PATCH 343/350] fix dominion rod cam spinning --- src/d/d_camera.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/d/d_camera.cpp b/src/d/d_camera.cpp index b5583cd93f..de9c9223cf 100644 --- a/src/d/d_camera.cpp +++ b/src/d/d_camera.cpp @@ -5022,7 +5022,11 @@ bool dCamera_c::lockonCamera(s32 param_0) { f32 sp160; cSAngle ang6 = ang3 - ang2; curveWeight = mCamSetup.CurveWeight(); + #if AVOID_UB + f32 sp15C = 0.0f; + #else f32 sp15C; + #endif f32 sp158 = mPadInfo.mCStick.mLastPosX; if (mCamParam.Flag(param_0, 0x40)) { sp158 = 0.0f; From 229c8ed88f869816c7246e2d235cf12c98efce80 Mon Sep 17 00:00:00 2001 From: qwertyquerty Date: Thu, 9 Apr 2026 18:22:53 -0700 Subject: [PATCH 344/350] CI/CD, CMake improvements, more targets, fix gcc (#258) * CI attempt * syntax * fix cmake for linux * fix include directories and merge main * fix PDB fighting * fix gcc compiling * fix SSCACHE for windows * try and fix gcc * more CI presets * remove the android target for now * bump cmake minimum to fix debug information format * yet another attempt at fixing gcc * yet another attempt at fixing gcc * better CI matrixing * yet another attempt at fixing GCC * fix arm * fix CI * placeholder icons * compile dawn from source for windows arm64 * fix icons and linker warnings * fix cmake * fetch libjpegturbo --------- Co-authored-by: Luke Street --- .github/workflows/build.yml | 206 +++ CMakeLists.txt | 296 ++++- CMakePresets.json | 603 +++++++++ ci/build-appimage.sh | 18 + include/dusk/config_var.hpp | 2 +- include/dusk/time.h | 7 +- include/global.h | 5 +- ios.toolchain.cmake | 1177 +++++++++++++++++ libs/JSystem/src/JGadget/linklist.cpp | 2 +- .../homebuttonLib/nw4hbm/math/triangular.h | 2 +- platforms/freedesktop/1024x1024/apps/dusk.png | Bin 0 -> 5337 bytes platforms/freedesktop/128x128/apps/dusk.png | Bin 0 -> 306 bytes platforms/freedesktop/16x16/apps/dusk.png | Bin 0 -> 79 bytes platforms/freedesktop/256x256/apps/dusk.png | Bin 0 -> 761 bytes platforms/freedesktop/32x32/apps/dusk.png | Bin 0 -> 99 bytes platforms/freedesktop/48x48/apps/dusk.png | Bin 0 -> 123 bytes platforms/freedesktop/512x512/apps/dusk.png | Bin 0 -> 1881 bytes platforms/freedesktop/64x64/apps/dusk.png | Bin 0 -> 179 bytes platforms/freedesktop/dusk.desktop | 9 + .../01J-lp-oVM-view-Ze5-6b-2t3.nib | Bin 0 -> 1186 bytes .../LaunchScreen.storyboardc/Info.plist | Bin 0 -> 258 bytes .../UIViewController-01J-lp-oVM.nib | Bin 0 -> 896 bytes platforms/ios/Info.plist.in | 79 ++ platforms/macos/Info.plist.in | 30 + .../BYZ-38-t0r-view-8bC-Xf-vdC.nib | Bin 0 -> 2001 bytes .../LaunchScreen.storyboardc/Info.plist | Bin 0 -> 258 bytes .../UIViewController-BYZ-38-t0r.nib | Bin 0 -> 896 bytes platforms/tvos/Info.plist.in | 49 + src/d/actor/d_a_b_bq.cpp | 84 +- src/d/actor/d_a_b_ob.cpp | 36 +- src/d/actor/d_a_e_ymb.cpp | 2 +- src/d/actor/d_a_tbox.cpp | 2 +- src/d/d_camera.cpp | 72 +- src/m_Do/m_Do_main.cpp | 6 + version.h.in | 20 + 35 files changed, 2561 insertions(+), 146 deletions(-) create mode 100644 .github/workflows/build.yml create mode 100644 CMakePresets.json create mode 100755 ci/build-appimage.sh create mode 100644 ios.toolchain.cmake create mode 100644 platforms/freedesktop/1024x1024/apps/dusk.png create mode 100644 platforms/freedesktop/128x128/apps/dusk.png create mode 100644 platforms/freedesktop/16x16/apps/dusk.png create mode 100644 platforms/freedesktop/256x256/apps/dusk.png create mode 100644 platforms/freedesktop/32x32/apps/dusk.png create mode 100644 platforms/freedesktop/48x48/apps/dusk.png create mode 100644 platforms/freedesktop/512x512/apps/dusk.png create mode 100644 platforms/freedesktop/64x64/apps/dusk.png create mode 100644 platforms/freedesktop/dusk.desktop create mode 100644 platforms/ios/Base.lproj/LaunchScreen.storyboardc/01J-lp-oVM-view-Ze5-6b-2t3.nib create mode 100644 platforms/ios/Base.lproj/LaunchScreen.storyboardc/Info.plist create mode 100644 platforms/ios/Base.lproj/LaunchScreen.storyboardc/UIViewController-01J-lp-oVM.nib create mode 100644 platforms/ios/Info.plist.in create mode 100644 platforms/macos/Info.plist.in create mode 100644 platforms/tvos/Base.lproj/LaunchScreen.storyboardc/BYZ-38-t0r-view-8bC-Xf-vdC.nib create mode 100644 platforms/tvos/Base.lproj/LaunchScreen.storyboardc/Info.plist create mode 100644 platforms/tvos/Base.lproj/LaunchScreen.storyboardc/UIViewController-BYZ-38-t0r.nib create mode 100644 platforms/tvos/Info.plist.in create mode 100644 version.h.in diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000000..1412aec639 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,206 @@ +name: Build + +on: + push: + paths-ignore: + - '*.md' + - '*LICENSE' + pull_request: + +env: + SCCACHE_GHA_ENABLED: "true" + RUSTC_WRAPPER: "sccache" + +jobs: + build-linux: + name: Build Linux (${{matrix.name}}) + runs-on: ${{matrix.runner}} + + strategy: + fail-fast: false + matrix: + include: + - name: GCC x86_64 + runner: ubuntu-latest + preset: gcc + artifact_arch: x86_64 + - name: GCC aarch64 + runner: ubuntu-24.04-arm + preset: gcc + artifact_arch: aarch64 + # - name: Clang x86_64 + # runner: ubuntu-latest + # preset: clang + # artifact_arch: x86_64 + + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + submodules: recursive + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get -y install ninja-build clang lld openssl libcurl4-openssl-dev \ + zlib1g-dev libglu1-mesa-dev libdbus-1-dev libvulkan-dev libxi-dev libxrandr-dev libasound2-dev \ + libpulse-dev libudev-dev libpng-dev libncurses5-dev libx11-xcb-dev libfreetype-dev \ + libxinerama-dev libxcursor-dev python3-markupsafe libgtk-3-dev libssl-dev \ + libxss-dev libfuse2 + + - name: Setup sccache + uses: mozilla-actions/sccache-action@v0.0.9 + + - name: Configure CMake + run: cmake --preset x-linux-ci-${{matrix.preset}} + + - name: Build + run: cmake --build --preset x-linux-ci-${{matrix.preset}} + + - name: Generate AppImage + run: ci/build-appimage.sh + + - name: Upload artifacts + uses: actions/upload-artifact@v7 + with: + name: dusk-${{env.DUSK_VERSION}}-linux-${{matrix.preset}}-${{matrix.artifact_arch}} + path: | + build/install/Dusk-*.AppImage + build/install/debug.tar.* + + + build-apple: + name: Build Apple (${{matrix.name}}) + runs-on: macos-latest + strategy: + fail-fast: false + matrix: + include: + - name: AppleClang macOS universal + platform: macos + preset: x-macos-ci + artifact_name: macos-appleclang-universal + # - name: AppleClang iOS arm64 # TODO enable when CI is free + # platform: ios + # preset: x-ios-ci + # artifact_name: ios-appleclang-arm64 + # - name: AppleClang tvOS arm64 # TODO enable when CI is free + # platform: tvos + # preset: x-tvos-ci + # artifact_name: tvos-appleclang-arm64 + + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + submodules: recursive + + - name: Update Homebrew + if: matrix.platform == 'tvos' + run: | + brew update + brew upgrade --formula + + - name: Install dependencies + run: brew install cmake ninja + + - name: Install markupsafe + if: matrix.platform == 'tvos' + run: pip3 install --break-system-packages markupsafe + + - name: Install Rust iOS target + if: matrix.platform == 'ios' + run: rustup target add aarch64-apple-ios + + - name: Install Rust tvOS target + if: matrix.platform == 'tvos' + run: | + rustup toolchain install nightly + rustup target add --toolchain nightly aarch64-apple-tvos + + - name: Setup sccache + uses: mozilla-actions/sccache-action@v0.0.9 + + - name: Configure CMake + run: cmake --preset ${{matrix.preset}} + + - name: Build + run: cmake --build --preset ${{matrix.preset}} + + - name: Upload artifacts + uses: actions/upload-artifact@v7 + with: + name: dusk-${{env.DUSK_VERSION}}-${{matrix.artifact_name}} + path: | + build/install/Dusk.app + build/install/debug.tar.* + + build-windows: + name: Build Windows (${{matrix.name}}) + runs-on: ${{matrix.runner}} + + env: + BUILD_DIR: C:\build + SCCACHE_DIR: C:\sccache + + strategy: + fail-fast: false + matrix: + include: + - name: MSVC x86_64 + runner: windows-latest + preset: msvc + msvc_arch: amd64 + vcpkg_arch: x64 + artifact_arch: x86_64 + - name: MSVC arm64 + runner: windows-11-arm + preset: arm64-msvc + msvc_arch: arm64 + vcpkg_arch: arm64 + artifact_arch: arm64 + # - name: Clang x86_64 + # runner: windows-latest + # preset: clang + # msvc_arch: amd64 + # vcpkg_arch: x64 + # artifact_arch: x86_64 + + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + submodules: recursive + + - name: Enable Visual Studio environment + uses: ilammy/msvc-dev-cmd@v1 + with: + arch: ${{matrix.msvc_arch}} + + # msvc-dev-cmd sets VCPKG_ROOT, set it back + - name: Override VCPKG_ROOT + run: echo "VCPKG_ROOT=C:\vcpkg" >> $env:GITHUB_ENV + + - name: Setup sccache + uses: mozilla-actions/sccache-action@v0.0.9 + + - name: Install dependencies + run: | + choco install ninja + vcpkg install zlib:${{matrix.vcpkg_arch}}-windows-static bzip2:${{matrix.vcpkg_arch}}-windows-static ` + zstd:${{matrix.vcpkg_arch}}-windows-static liblzma:${{matrix.vcpkg_arch}}-windows-static ` + freetype:${{matrix.vcpkg_arch}}-windows-static + + - name: Configure CMake + run: cmake --preset x-windows-ci-${{matrix.preset}} + + - name: Build + run: cmake --build --preset x-windows-ci-${{matrix.preset}} + + - name: Upload artifacts + uses: actions/upload-artifact@v7 + with: + name: dusk-${{env.DUSK_VERSION}}-win32-msvc-${{matrix.artifact_arch}} + path: | + ${{env.BUILD_DIR}}/install/*.exe + ${{env.BUILD_DIR}}/install/debug.7z diff --git a/CMakeLists.txt b/CMakeLists.txt index dd848c776f..4cca061f1f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,14 +1,86 @@ -cmake_minimum_required(VERSION 3.16) -if (APPLE) - project(dusk LANGUAGES C CXX OBJC OBJCXX) -else () - project(dusk LANGUAGES C CXX) +cmake_minimum_required(VERSION 3.25) + +if (NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "RelWithDebInfo" CACHE STRING + "Build type options: Debug Release RelWithDebInfo MinSizeRel" FORCE) endif () +# obtain revision info from git +find_package(Git) +if (GIT_FOUND) + # make sure version information gets re-run when the current Git HEAD changes + execute_process(WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMAND ${GIT_EXECUTABLE} rev-parse --git-path HEAD + OUTPUT_VARIABLE dusk_git_head_filename + OUTPUT_STRIP_TRAILING_WHITESPACE) + set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${dusk_git_head_filename}") + + execute_process(WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMAND ${GIT_EXECUTABLE} rev-parse --symbolic-full-name HEAD + OUTPUT_VARIABLE dusk_git_head_symbolic + OUTPUT_STRIP_TRAILING_WHITESPACE) + execute_process(WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + COMMAND ${GIT_EXECUTABLE} rev-parse --git-path ${dusk_git_head_symbolic} + OUTPUT_VARIABLE dusk_git_head_symbolic_filename + OUTPUT_STRIP_TRAILING_WHITESPACE) + set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${dusk_git_head_symbolic_filename}") + + # defines DUSK_WC_REVISION + execute_process(WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMAND ${GIT_EXECUTABLE} rev-parse HEAD + OUTPUT_VARIABLE DUSK_WC_REVISION + OUTPUT_STRIP_TRAILING_WHITESPACE) + # defines DUSK_WC_DESCRIBE + execute_process(WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMAND ${GIT_EXECUTABLE} describe --tags --long --dirty --match "v*" + OUTPUT_VARIABLE DUSK_WC_DESCRIBE + OUTPUT_STRIP_TRAILING_WHITESPACE) + + # remove the git hash, then collapse a clean "-0" suffix only + string(REGEX REPLACE "-[^-]+(-dirty|)$" "\\1" DUSK_WC_DESCRIBE "${DUSK_WC_DESCRIBE}") + string(REGEX REPLACE "-0$" "" DUSK_WC_DESCRIBE "${DUSK_WC_DESCRIBE}") + + # defines DUSK_WC_BRANCH + execute_process(WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMAND ${GIT_EXECUTABLE} rev-parse --abbrev-ref HEAD + OUTPUT_VARIABLE DUSK_WC_BRANCH + OUTPUT_STRIP_TRAILING_WHITESPACE) + # defines DUSK_WC_DATE + execute_process(WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMAND ${GIT_EXECUTABLE} log -1 --format=%ad + OUTPUT_VARIABLE DUSK_WC_DATE + OUTPUT_STRIP_TRAILING_WHITESPACE) +else () + message(STATUS "Unable to find git, commit information will not be available") +endif () + +if (DUSK_WC_DESCRIBE) + string(REGEX REPLACE "v([0-9]+)\.([0-9]+)\.([0-9]+)\-([0-9]+).*" "\\1.\\2.\\3.\\4" DUSK_VERSION_STRING "${DUSK_WC_DESCRIBE}") + string(REGEX REPLACE "v([0-9]+)\.([0-9]+)\.([0-9]+).*" "\\1.\\2.\\3" DUSK_SHORT_VERSION_STRING "${DUSK_WC_DESCRIBE}") +else () + set(DUSK_WC_DESCRIBE "UNKNOWN-VERSION") + set(DUSK_VERSION_STRING "0.0.0") +endif () + +# Add version information to CI environment variables +if(DEFINED ENV{GITHUB_ENV}) + file(APPEND "$ENV{GITHUB_ENV}" "DUSK_VERSION=${DUSK_WC_DESCRIBE}\n") +endif() +message(STATUS "Dusk version set to ${DUSK_WC_DESCRIBE}") +message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") +project(dusk LANGUAGES C CXX VERSION ${DUSK_VERSION_STRING}) +if (APPLE AND NOT TVOS AND CMAKE_SYSTEM_NAME STREQUAL tvOS) + # ios.toolchain.cmake hack for SDL + set(TVOS ON) + set(IOS OFF) +endif () + +if(APPLE AND NOT CMAKE_OSX_SYSROOT) + # If the Xcode SDK is lagging behind system version, CMake needs this done first + execute_process(COMMAND xcrun --sdk macosx --show-sdk-path + OUTPUT_VARIABLE CMAKE_OSX_SYSROOT + OUTPUT_STRIP_TRAILING_WHITESPACE) +endif() + set(CMAKE_C_STANDARD 11) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_POSITION_INDEPENDENT_CODE ON) +set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) if (CMAKE_SYSTEM_NAME STREQUAL Linux) set(DAWN_USE_WAYLAND ON CACHE BOOL "Enable support for Wayland surface" FORCE) @@ -19,15 +91,46 @@ add_subdirectory(extern/aurora EXCLUDE_FROM_ALL) add_subdirectory(libs/freeverb) -option(DUSK_BUILD_WARNINGS "If off, compiler warnings will be suppressed") +option(DUSK_BUILD_WARNINGS "Enable compiler warnings (off by default)") 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) +if (DUSK_MOVIE_SUPPORT) + find_package(libjpeg-turbo QUIET) + if (libjpeg-turbo_FOUND) + message(STATUS "dusk: Using system libjpeg-turbo") + else () + message(STATUS "dusk: Fetching libjpeg-turbo") + include(ExternalProject) + set(_jpeg_install_dir ${CMAKE_BINARY_DIR}/libjpeg-turbo-install) + if (WIN32) + set(_jpeg_lib ${_jpeg_install_dir}/lib/turbojpeg-static.lib) + else () + set(_jpeg_lib ${_jpeg_install_dir}/lib/libturbojpeg.a) + endif () + ExternalProject_Add(libjpeg-turbo-ext + URL https://github.com/libjpeg-turbo/libjpeg-turbo/archive/refs/tags/3.1.0.tar.gz + URL_HASH SHA256=35fec2e1ddfb05ecf6d93e50bc57c1e54bc81c16d611ddf6eff73fff266d8285 + CMAKE_ARGS + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} + -DCMAKE_INSTALL_PREFIX=${_jpeg_install_dir} + -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} + -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} + -DCMAKE_C_COMPILER_LAUNCHER=${CMAKE_C_COMPILER_LAUNCHER} + -DCMAKE_MSVC_RUNTIME_LIBRARY=${CMAKE_MSVC_RUNTIME_LIBRARY} + -DENABLE_SHARED=OFF + -DWITH_TURBOJPEG=ON + -DWITH_JAVA=OFF + BUILD_BYPRODUCTS ${_jpeg_lib} + ) + file(MAKE_DIRECTORY ${_jpeg_install_dir}/include) + add_library(libjpeg-turbo::turbojpeg-static STATIC IMPORTED GLOBAL) + set_target_properties(libjpeg-turbo::turbojpeg-static PROPERTIES + IMPORTED_LOCATION ${_jpeg_lib} + INTERFACE_INCLUDE_DIRECTORIES ${_jpeg_install_dir}/include + ) + add_dependencies(libjpeg-turbo::turbojpeg-static libjpeg-turbo-ext) + endif () endif () if (CMAKE_SYSTEM_NAME STREQUAL Linux) @@ -48,6 +151,8 @@ elseif (MSVC) add_compile_options(/bigobj) add_compile_options(/Zc:strictStrings-) add_compile_options(/MP) + add_compile_options(/FS) + if (NOT DUSK_BUILD_WARNINGS) add_compile_options(/W0) else () @@ -63,33 +168,34 @@ endif () include(FetchContent) +# Declare all dependencies first so CMake can download them in parallel message(STATUS "dusk: Fetching cxxopts") -FetchContent_Declare( - cxxopts - GIT_REPOSITORY https://github.com/jarro2783/cxxopts.git - GIT_TAG v3.3.1 +FetchContent_Declare(cxxopts + URL https://github.com/jarro2783/cxxopts/archive/refs/tags/v3.3.1.tar.gz + URL_HASH SHA256=3bfc70542c521d4b55a46429d808178916a579b28d048bd8c727ee76c39e2072 + DOWNLOAD_EXTRACT_TIMESTAMP TRUE ) - -FetchContent_MakeAvailable(cxxopts) +message(STATUS "dusk: Fetching nlohmann/json") FetchContent_Declare(json URL https://github.com/nlohmann/json/releases/download/v3.12.0/json.tar.xz URL_HASH SHA256=42f6e95cad6ec532fd372391373363b62a14af6d771056dbfc86160e6dfff7aa DOWNLOAD_EXTRACT_TIMESTAMP TRUE ) -FetchContent_MakeAvailable(json) +FetchContent_MakeAvailable(cxxopts json) + +configure_file(${CMAKE_SOURCE_DIR}/version.h.in ${CMAKE_BINARY_DIR}/version.h) include(files.cmake) # TODO: version handling for res includes +set(DUSK_BUNDLE_NAME Dusk) +set(DUSK_BUNDLE_IDENTIFIER dev.decomp.dusk) set(DUSK_GAME_NAME "GZ2E") set(DUSK_GAME_VERSION "01") - set(DUSK_TP_VERSION ${DUSK_GAME_NAME}${DUSK_GAME_VERSION}) -message(STATUS "dusk: Game Name: ${DUSK_GAME_NAME}") -message(STATUS "dusk: Game Version: ${DUSK_GAME_VERSION}") -message(STATUS "dusk: TP Version: ${DUSK_TP_VERSION}") +message(STATUS "dusk: Game Version: ${DUSK_TP_VERSION}") source_group("dolzel" FILES ${DOLZEL_FILES} ${Z2AUDIOLIB_FILES} ${JSYSTEM_FILES} ${JSYSTEM_DEBUG_FILES} ${REL_FILES}) source_group("dusk" FILES ${DUSK_FILES}) @@ -104,20 +210,15 @@ set(GAME_INCLUDE_DIRS libs/JSystem/include libs extern/aurora/include/dolphin - extern) + extern + ${CMAKE_BINARY_DIR}) set(GAME_LIBS aurora::core aurora::gx aurora::gd aurora::si aurora::vi aurora::pad aurora::mtx aurora::os aurora::dvd aurora::card freeverb cxxopts::cxxopts absl::flat_hash_map nlohmann_json::nlohmann_json TracyClient) -if (DUSK_MOVIE_SUPPORT_REAL) - if (TARGET libjpeg-turbo::turbojpeg-static) - message(STATUS "dusk: Linking against libjpeg-turbo static library") - list(APPEND GAME_LIBS "libjpeg-turbo::turbojpeg-static") - else () - message(STATUS "dusk: Linking against libjpeg-turbo shared library") - list(APPEND GAME_LIBS "libjpeg-turbo::turbojpeg") - endif () - list(APPEND GAME_COMPILE_DEFS MOVIE_SUPPORT=1) +if (DUSK_MOVIE_SUPPORT) + list(APPEND GAME_LIBS libjpeg-turbo::turbojpeg-static) + list(APPEND GAME_COMPILE_DEFS MOVIE_SUPPORT=1) endif () # game_debug is for game code files that we know work when compiled with DEBUG=1 @@ -125,17 +226,13 @@ endif () add_library(game_debug OBJECT ${JSYSTEM_DEBUG_FILES} ${SSYSTEM_FILES} src/dusk/audio/DuskAudioSystem.cpp src/dusk/audio/JASCriticalSection.cpp - src/dusk/audio/DuskDsp.hpp src/dusk/audio/DuskDsp.cpp src/dusk/audio/Adpcm.cpp - src/dusk/audio/Adpcm.hpp - src/dusk/audio/DspStub.cpp) + src/dusk/audio/DspStub.cpp + src/dusk/imgui/ImGuiAudio.cpp) # game_base is for all other game code files -add_library(game_base OBJECT ${DOLZEL_FILES} ${Z2AUDIOLIB_FILES} ${JSYSTEM_FILES} ${REL_FILES} ${DUSK_FILES} - ${DOLPHIN_FILES} - src/dusk/imgui/ImGuiStubLog.cpp - src/dusk/imgui/ImGuiAudio.cpp) +add_library(game_base OBJECT ${DOLZEL_FILES} ${Z2AUDIOLIB_FILES} ${JSYSTEM_FILES} ${REL_FILES} ${DUSK_FILES} ${DOLPHIN_FILES}) target_compile_definitions(game_debug PRIVATE ${GAME_COMPILE_DEFS} $<$:DEBUG=1> $<$:PARTIAL_DEBUG=1>) target_compile_definitions(game_base PRIVATE ${GAME_COMPILE_DEFS} NDEBUG=1 NDEBUG_DEFINED=1 DEBUG_DEFINED=0 $<$:PARTIAL_DEBUG=1>) @@ -160,7 +257,7 @@ target_link_libraries(game PUBLIC ${GAME_LIBS}) add_executable(dusk src/dusk/main.cpp) target_compile_definitions(dusk PRIVATE TARGET_PC AVOID_UB=1 VERSION=0) target_include_directories(dusk PRIVATE include) -target_link_libraries(dusk PRIVATE game aurora::main freeverb) +target_link_libraries(dusk PRIVATE game aurora::main) add_custom_command(TARGET dusk POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory @@ -169,6 +266,49 @@ add_custom_command(TARGET dusk POST_BUILD COMMENT "Copying resources" ) +if (APPLE) + if (IOS) + set(DUSK_RESOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios) + set(DUSK_INFO_PLIST ${DUSK_RESOURCE_DIR}/Info.plist.in) + file(GLOB_RECURSE DUSK_RESOURCE_FILES "${DUSK_RESOURCE_DIR}/Base.lproj/*") + endif () + if (IOS OR TVOS) + target_sources(dusk PRIVATE ${DUSK_RESOURCE_FILES}) + foreach (FILE ${DUSK_RESOURCE_FILES}) + file(RELATIVE_PATH NEW_FILE "${DUSK_RESOURCE_DIR}" ${FILE}) + get_filename_component(NEW_FILE_PATH ${NEW_FILE} DIRECTORY) + set_property(SOURCE ${FILE} PROPERTY MACOSX_PACKAGE_LOCATION "Resources/${NEW_FILE_PATH}") + endforeach () + set_target_properties( + dusk PROPERTIES + MACOSX_BUNDLE TRUE + MACOSX_BUNDLE_BUNDLE_NAME ${DUSK_BUNDLE_NAME} + MACOSX_BUNDLE_GUI_IDENTIFIER ${DUSK_BUNDLE_IDENTIFIER} + MACOSX_BUNDLE_BUNDLE_VERSION ${DUSK_VERSION_STRING} + MACOSX_BUNDLE_SHORT_VERSION_STRING ${DUSK_SHORT_VERSION_STRING} + OUTPUT_NAME dusk + XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "YES" + XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "YES" + ) + if (CMAKE_GENERATOR STREQUAL "Xcode") + set_target_properties(dusk PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${DUSK_INFO_PLIST}) + elseif (DEFINED DUSK_INFO_PLIST) + set(MACOSX_BUNDLE_EXECUTABLE_NAME dusk) + set(MACOSX_BUNDLE_GUI_IDENTIFIER ${DUSK_BUNDLE_IDENTIFIER}) + set(MACOSX_BUNDLE_BUNDLE_NAME ${DUSK_BUNDLE_NAME}) + set(MACOSX_BUNDLE_BUNDLE_VERSION ${DUSK_VERSION_STRING}) + set(MACOSX_BUNDLE_SHORT_VERSION_STRING ${DUSK_SHORT_VERSION_STRING}) + set(DUSK_GENERATED_INFO_PLIST ${CMAKE_CURRENT_BINARY_DIR}/dusk.Info.plist) + configure_file(${DUSK_INFO_PLIST} ${DUSK_GENERATED_INFO_PLIST}) + add_custom_command( + TARGET dusk POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${DUSK_GENERATED_INFO_PLIST} $/Info.plist + VERBATIM + ) + endif () + endif () +endif () + include(extern/aurora/cmake/AuroraCopyRuntimeDLLs.cmake) aurora_copy_runtime_dlls(dusk) @@ -183,3 +323,79 @@ if (DUSK_SELECTED_OPT) target_compile_options(aurora_gx PRIVATE ${_opt_flags}) target_compile_options(freeverb PRIVATE ${_opt_flags}) endif () + +# Packaging logic +function(get_target_output_name target result_var) + get_target_property(output_name ${target} OUTPUT_NAME) + if (NOT output_name) + set(${result_var} "${target}" PARENT_SCOPE) + else () + set(${result_var} "${output_name}" PARENT_SCOPE) + endif () +endfunction() +function(get_target_prefix target result_var) + set(${result_var} "" PARENT_SCOPE) + if (APPLE) + # Have to recreate some bundle logic here, since CMake can't tell us + get_target_property(is_bundle ${target} MACOSX_BUNDLE) + if (is_bundle) + get_target_output_name(${target} output_name) + if (CMAKE_SYSTEM_NAME STREQUAL Darwin) + set(${result_var} "${output_name}.app/Contents/MacOS/" PARENT_SCOPE) + else () + set(${result_var} "${output_name}.app/" PARENT_SCOPE) + endif () + endif () + endif () +endfunction() +list(APPEND BINARY_TARGETS dusk) +set(EXTRA_TARGETS "") +if (TARGET crashpad_handler) + list(APPEND EXTRA_TARGETS crashpad_handler) +endif () +install(TARGETS ${BINARY_TARGETS} ${EXTRA_TARGETS} DESTINATION ${CMAKE_INSTALL_PREFIX}) +if (CMAKE_BUILD_TYPE STREQUAL Debug OR CMAKE_BUILD_TYPE STREQUAL RelWithDebInfo) + set(DEBUG_FILES_LIST "") + foreach (target IN LISTS BINARY_TARGETS EXTRA_TARGETS) + get_target_output_name(${target} output_name) + if (WIN32) + install(FILES $ DESTINATION ${CMAKE_INSTALL_PREFIX} OPTIONAL) + elseif (APPLE) + get_target_prefix(${target} target_prefix) + install(CODE "execute_process(WORKING_DIRECTORY \"${CMAKE_INSTALL_PREFIX}\" COMMAND rm -fr \"$.dSYM\")") + install(CODE "execute_process(WORKING_DIRECTORY \"${CMAKE_INSTALL_PREFIX}\" COMMAND dsymutil \"${target_prefix}$\")") + install(CODE "execute_process(WORKING_DIRECTORY \"${CMAKE_INSTALL_PREFIX}\" COMMAND strip -S \"${target_prefix}$\")") + if (NOT target_prefix STREQUAL "") + install(CODE "execute_process(WORKING_DIRECTORY \"${CMAKE_INSTALL_PREFIX}\" COMMAND mv \"${target_prefix}$.dSYM\" .)") + endif () + elseif (UNIX) + get_target_prefix(${target} target_prefix) + install(CODE "execute_process(WORKING_DIRECTORY \"${CMAKE_INSTALL_PREFIX}\" COMMAND objcopy --only-keep-debug \"${target_prefix}$\" \"${target_prefix}$.dbg\")") + install(CODE "execute_process(WORKING_DIRECTORY \"${CMAKE_INSTALL_PREFIX}\" COMMAND objcopy --strip-debug --add-gnu-debuglink=$.dbg \"${target_prefix}$\")") + endif () + list(APPEND DEBUG_FILES_LIST "${output_name}") + endforeach () + if (WIN32) + list(TRANSFORM DEBUG_FILES_LIST APPEND ".pdb") + list(JOIN DEBUG_FILES_LIST " " DEBUG_FILES) + install(CODE "execute_process(WORKING_DIRECTORY \"${CMAKE_INSTALL_PREFIX}\" COMMAND 7z a -t7z \"${CMAKE_INSTALL_PREFIX}/debug.7z\" ${DEBUG_FILES})") + elseif (APPLE) + list(TRANSFORM DEBUG_FILES_LIST APPEND ".dSYM") + list(JOIN DEBUG_FILES_LIST " " DEBUG_FILES) + install(CODE "execute_process(WORKING_DIRECTORY \"${CMAKE_INSTALL_PREFIX}\" COMMAND tar acfv \"${CMAKE_INSTALL_PREFIX}/debug.tar.xz\" ${DEBUG_FILES})") + elseif (UNIX) + list(TRANSFORM DEBUG_FILES_LIST APPEND ".dbg") + list(JOIN DEBUG_FILES_LIST " " DEBUG_FILES) + install(CODE "execute_process(WORKING_DIRECTORY \"${CMAKE_INSTALL_PREFIX}\" COMMAND tar -I \"xz -9 -T0\" -cvf \"${CMAKE_INSTALL_PREFIX}/debug.tar.xz\" ${DEBUG_FILES})") + endif () +endif () +foreach (target IN LISTS BINARY_TARGETS) + get_target_prefix(${target} target_prefix) + foreach (extra_target IN LISTS EXTRA_TARGETS) + get_target_prefix(${extra_target} extra_prefix) + if (NOT "${target_prefix}" STREQUAL "${extra_prefix}") + # Copy extra target to target prefix + install(CODE "execute_process(WORKING_DIRECTORY \"${CMAKE_INSTALL_PREFIX}\" COMMAND cp \"${extra_prefix}$\" \"${target_prefix}$\")") + endif () + endforeach () +endforeach () diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 0000000000..73c9915aba --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,603 @@ +{ + "version": 2, + "cmakeMinimumRequired": { + "major": 3, + "minor": 25, + "patch": 0 + }, + "configurePresets": [ + { + "name": "debug", + "hidden": true, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "CMAKE_MSVC_RUNTIME_LIBRARY": "MultiThreadedDebugDLL" + } + }, + { + "name": "relwithdebinfo", + "hidden": true, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "RelWithDebInfo", + "CMAKE_MSVC_RUNTIME_LIBRARY": "MultiThreaded" + } + }, + { + "name": "linux-default", + "displayName": "Linux (default)", + "generator": "Ninja", + "binaryDir": "${sourceDir}/build/${presetName}", + "cacheVariables": { + "CMAKE_INSTALL_PREFIX": "${sourceDir}/build/install" + }, + "vendor": { + "microsoft.com/VisualStudioSettings/CMake/1.0": { + "hostOS": [ + "Linux" + ] + }, + "microsoft.com/VisualStudioRemoteSettings/CMake/1.0": { + "sourceDir": "$env{HOME}/.vs/$ms{projectDirName}" + } + } + }, + { + "name": "linux-default-debug", + "displayName": "Linux (default) Debug", + "inherits": [ + "debug", + "linux-default" + ] + }, + { + "name": "linux-default-relwithdebinfo", + "displayName": "Linux (default) RelWithDebInfo", + "inherits": [ + "relwithdebinfo", + "linux-default" + ] + }, + { + "name": "linux-clang", + "displayName": "Linux (Clang)", + "inherits": [ + "linux-default" + ], + "cacheVariables": { + "CMAKE_C_COMPILER": "clang", + "CMAKE_CXX_COMPILER": "clang++" + } + }, + { + "name": "linux-clang-debug", + "displayName": "Linux (Clang) Debug", + "inherits": [ + "debug", + "linux-clang" + ] + }, + { + "name": "linux-clang-relwithdebinfo", + "displayName": "Linux (Clang) RelWithDebInfo", + "inherits": [ + "relwithdebinfo", + "linux-clang" + ] + }, + { + "name": "windows-msvc", + "displayName": "Windows (MSVC)", + "generator": "Ninja", + "binaryDir": "${sourceDir}/out/build/${presetName}", + "architecture": { + "value": "x64", + "strategy": "external" + }, + "cacheVariables": { + "CMAKE_C_COMPILER": "cl", + "CMAKE_CXX_COMPILER": "cl", + "CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install" + }, + "vendor": { + "microsoft.com/VisualStudioSettings/CMake/1.0": { + "hostOS": [ + "Windows" + ] + } + } + }, + { + "name": "windows-msvc-debug", + "displayName": "Windows (MSVC) Debug", + "inherits": [ + "debug", + "windows-msvc" + ] + }, + { + "name": "windows-msvc-relwithdebinfo", + "displayName": "Windows (MSVC) RelWithDebInfo", + "inherits": [ + "relwithdebinfo", + "windows-msvc" + ] + }, + { + "name": "windows-arm64-msvc", + "displayName": "Windows ARM64 (MSVC)", + "generator": "Ninja", + "binaryDir": "${sourceDir}/out/build/${presetName}", + "architecture": { + "value": "arm64", + "strategy": "external" + }, + "cacheVariables": { + "CMAKE_C_COMPILER": "cl", + "CMAKE_CXX_COMPILER": "cl", + "CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install", + "AURORA_DAWN_PROVIDER": "vendor" + }, + "vendor": { + "microsoft.com/VisualStudioSettings/CMake/1.0": { + "hostOS": [ + "Windows" + ] + } + } + }, + { + "name": "windows-clang", + "displayName": "Windows (Clang)", + "inherits": [ + "windows-msvc" + ], + "cacheVariables": { + "CMAKE_C_COMPILER": "clang-cl", + "CMAKE_CXX_COMPILER": "clang-cl", + "CMAKE_LINKER": "lld-link" + }, + "vendor": { + "microsoft.com/VisualStudioSettings/CMake/1.0": { + "intelliSenseMode": "windows-clang-x64" + } + } + }, + { + "name": "windows-clang-debug", + "displayName": "Windows (Clang) Debug", + "inherits": [ + "debug", + "windows-clang" + ] + }, + { + "name": "windows-clang-relwithdebinfo", + "displayName": "Windows (Clang) RelWithDebInfo", + "inherits": [ + "relwithdebinfo", + "windows-clang" + ] + }, + { + "name": "macos-default", + "displayName": "macOS (default)", + "generator": "Ninja", + "binaryDir": "${sourceDir}/build/${presetName}", + "cacheVariables": { + "CMAKE_INSTALL_PREFIX": "${sourceDir}/build/install" + }, + "vendor": { + "microsoft.com/VisualStudioSettings/CMake/1.0": { + "hostOS": [ + "macOS" + ] + } + } + }, + { + "name": "macos-default-debug", + "displayName": "macOS (default) Debug", + "inherits": [ + "debug", + "macos-default" + ] + }, + { + "name": "macos-default-relwithdebinfo", + "displayName": "macOS (default) RelWithDebInfo", + "inherits": [ + "relwithdebinfo", + "macos-default" + ] + }, + { + "name": "ios-default", + "displayName": "iOS", + "generator": "Ninja", + "binaryDir": "${sourceDir}/build/${presetName}", + "inherits": [ + "relwithdebinfo" + ], + "cacheVariables": { + "CMAKE_TOOLCHAIN_FILE": "ios.toolchain.cmake", + "CMAKE_INSTALL_PREFIX": "${sourceDir}/build/install", + "PLATFORM": "OS64", + "DEPLOYMENT_TARGET": "14.0", + "ENABLE_BITCODE": { + "type": "BOOL", + "value": false + }, + "ENABLE_ARC": { + "type": "BOOL", + "value": false + }, + "Rust_CARGO_TARGET": "aarch64-apple-ios", + "BUILD_SHARED_LIBS": { + "type": "BOOL", + "value": false + }, + "CMAKE_DISABLE_FIND_PACKAGE_BZip2": { + "type": "BOOL", + "value": true + }, + "CMAKE_DISABLE_FIND_PACKAGE_LibLZMA": { + "type": "BOOL", + "value": true + }, + "CMAKE_DISABLE_FIND_PACKAGE_zstd": { + "type": "BOOL", + "value": true + }, + "CMAKE_DISABLE_FIND_PACKAGE_Freetype": { + "type": "BOOL", + "value": true + } + }, + "vendor": { + "microsoft.com/VisualStudioSettings/CMake/1.0": { + "hostOS": [ + "macOS" + ] + } + } + }, + { + "name": "tvos-default", + "displayName": "tvOS", + "generator": "Ninja", + "binaryDir": "${sourceDir}/build/${presetName}", + "inherits": [ + "relwithdebinfo" + ], + "cacheVariables": { + "CMAKE_TOOLCHAIN_FILE": "ios.toolchain.cmake", + "CMAKE_INSTALL_PREFIX": "${sourceDir}/build/install", + "PLATFORM": "TVOS", + "DEPLOYMENT_TARGET": "14.5", + "ENABLE_BITCODE": { + "type": "BOOL", + "value": false + }, + "Rust_CARGO_TARGET": "aarch64-apple-tvos", + "Rust_TOOLCHAIN": "nightly", + "BUILD_SHARED_LIBS": { + "type": "BOOL", + "value": false + }, + "CMAKE_DISABLE_FIND_PACKAGE_BZip2": { + "type": "BOOL", + "value": true + }, + "CMAKE_DISABLE_FIND_PACKAGE_LibLZMA": { + "type": "BOOL", + "value": true + }, + "CMAKE_DISABLE_FIND_PACKAGE_zstd": { + "type": "BOOL", + "value": true + } + }, + "vendor": { + "microsoft.com/VisualStudioSettings/CMake/1.0": { + "hostOS": [ + "macOS" + ] + } + } + }, + { + "name": "android-base", + "hidden": true, + "generator": "Ninja", + "binaryDir": "${sourceDir}/build/${presetName}", + "inherits": [ + "relwithdebinfo" + ], + "cacheVariables": { + "CMAKE_INSTALL_PREFIX": "${sourceDir}/build/install", + "CMAKE_TOOLCHAIN_FILE": "$env{ANDROID_HOME}/ndk/$env{ANDROID_NDK_VERSION}/build/cmake/android.toolchain.cmake", + "ANDROID_PLATFORM": "android-24" + } + }, + { + "name": "android-arm64", + "displayName": "Android (arm64-v8a)", + "inherits": [ + "android-base" + ], + "cacheVariables": { + "ANDROID_ABI": "arm64-v8a" + } + }, + { + "name": "android-x86_64", + "displayName": "Android (x86_64)", + "inherits": [ + "android-base" + ], + "cacheVariables": { + "ANDROID_ABI": "x86_64" + } + }, + { + "name": "x-linux-ci", + "hidden": true, + "inherits": [ + "relwithdebinfo" + ], + "cacheVariables": { + "CMAKE_C_COMPILER_LAUNCHER": "sccache", + "CMAKE_CXX_COMPILER_LAUNCHER": "sccache" + } + }, + { + "name": "x-linux-ci-gcc", + "inherits": [ + "x-linux-ci", + "linux-default" + ] + }, + { + "name": "x-linux-ci-clang", + "inherits": [ + "x-linux-ci", + "linux-clang" + ] + }, + { + "name": "x-macos-ci", + "inherits": [ + "macos-default-relwithdebinfo" + ], + "cacheVariables": { + "CMAKE_C_COMPILER_LAUNCHER": "sccache", + "CMAKE_CXX_COMPILER_LAUNCHER": "sccache" + } + }, + { + "name": "x-ios-ci", + "inherits": [ + "ios-default" + ], + "cacheVariables": { + "CMAKE_C_COMPILER_LAUNCHER": "sccache", + "CMAKE_CXX_COMPILER_LAUNCHER": "sccache" + } + }, + { + "name": "x-tvos-ci", + "inherits": [ + "tvos-default" + ], + "cacheVariables": { + "CMAKE_C_COMPILER_LAUNCHER": "sccache", + "CMAKE_CXX_COMPILER_LAUNCHER": "sccache" + } + }, + { + "name": "x-windows-ci", + "hidden": true, + "inherits": [ + "relwithdebinfo" + ], + "binaryDir": "$env{BUILD_DIR}", + "cacheVariables": { + "CMAKE_INSTALL_PREFIX": "$env{BUILD_DIR}/install", + "CMAKE_C_COMPILER_LAUNCHER": "sccache", + "CMAKE_CXX_COMPILER_LAUNCHER": "sccache", + "CMAKE_MSVC_DEBUG_INFORMATION_FORMAT": "Embedded" + } + }, + { + "name": "x-windows-ci-msvc", + "inherits": [ + "x-windows-ci", + "windows-msvc" + ] + }, + { + "name": "x-windows-ci-clang", + "inherits": [ + "x-windows-ci", + "windows-clang" + ] + }, + { + "name": "x-windows-ci-arm64-msvc", + "inherits": [ + "x-windows-ci", + "windows-arm64-msvc" + ] + } + ], + "buildPresets": [ + { + "name": "linux-default-debug", + "configurePreset": "linux-default-debug", + "description": "Linux (default) debug build", + "displayName": "Linux (default) Debug" + }, + { + "name": "linux-default-relwithdebinfo", + "configurePreset": "linux-default-relwithdebinfo", + "description": "Linux (default) release build with debug info", + "displayName": "Linux (default) RelWithDebInfo" + }, + { + "name": "linux-clang-debug", + "configurePreset": "linux-clang-debug", + "description": "Linux (Clang) debug build", + "displayName": "Linux (Clang) Debug" + }, + { + "name": "linux-clang-relwithdebinfo", + "configurePreset": "linux-clang-relwithdebinfo", + "description": "Linux (Clang) release build with debug info", + "displayName": "Linux (Clang) RelWithDebInfo" + }, + { + "name": "macos-default-debug", + "configurePreset": "macos-default-debug", + "description": "macOS debug build", + "displayName": "macOS Debug" + }, + { + "name": "macos-default-relwithdebinfo", + "configurePreset": "macos-default-relwithdebinfo", + "description": "macOS release build with debug info", + "displayName": "macOS RelWithDebInfo" + }, + { + "name": "ios-default", + "configurePreset": "ios-default", + "description": "iOS release build with debug info", + "displayName": "iOS RelWithDebInfo", + "targets": [ + "dusk" + ] + }, + { + "name": "tvos-default", + "configurePreset": "tvos-default", + "description": "tvOS release build with debug info", + "displayName": "tvOS RelWithDebInfo", + "targets": [ + "dusk" + ] + }, + { + "name": "android-arm64", + "configurePreset": "android-arm64", + "description": "Android arm64-v8a release build with debug info", + "displayName": "Android arm64-v8a RelWithDebInfo", + "targets": [ + "dusk" + ] + }, + { + "name": "android-x86_64", + "configurePreset": "android-x86_64", + "description": "Android x86_64 release build with debug info", + "displayName": "Android x86_64 RelWithDebInfo", + "targets": [ + "dusk" + ] + }, + { + "name": "windows-msvc-debug", + "configurePreset": "windows-msvc-debug", + "description": "Windows (MSVC) debug build", + "displayName": "Windows (MSVC) Debug" + }, + { + "name": "windows-msvc-relwithdebinfo", + "configurePreset": "windows-msvc-relwithdebinfo", + "description": "Windows (MSVC) release build with debug info", + "displayName": "Windows (MSVC) RelWithDebInfo" + }, + { + "name": "windows-clang-debug", + "configurePreset": "windows-clang-debug", + "description": "Windows (Clang) debug build", + "displayName": "Windows (Clang) Debug" + }, + { + "name": "windows-clang-relwithdebinfo", + "configurePreset": "windows-clang-relwithdebinfo", + "description": "Windows (Clang) release build with debug info", + "displayName": "Windows (Clang) RelWithDebInfo" + }, + { + "name": "x-linux-ci-gcc", + "configurePreset": "x-linux-ci-gcc", + "description": "(Internal) Linux CI GCC", + "displayName": "(Internal) Linux CI GCC", + "targets": [ + "install" + ] + }, + { + "name": "x-linux-ci-clang", + "configurePreset": "x-linux-ci-clang", + "description": "(Internal) Linux CI Clang", + "displayName": "(Internal) Linux CI Clang", + "targets": [ + "install" + ] + }, + { + "name": "x-macos-ci", + "configurePreset": "x-macos-ci", + "description": "(Internal) macOS CI", + "displayName": "(Internal) macOS CI", + "targets": [ + "install" + ] + }, + { + "name": "x-ios-ci", + "configurePreset": "x-ios-ci", + "description": "(Internal) iOS CI", + "displayName": "(Internal) iOS CI", + "targets": [ + "install" + ] + }, + { + "name": "x-tvos-ci", + "configurePreset": "x-tvos-ci", + "description": "(Internal) tvOS CI", + "displayName": "(Internal) tvOS CI", + "targets": [ + "install" + ] + }, + { + "name": "x-windows-ci-msvc", + "configurePreset": "x-windows-ci-msvc", + "description": "(Internal) Windows CI MSVC", + "displayName": "(Internal) Windows CI MSVC", + "targets": [ + "install" + ] + }, + { + "name": "x-windows-ci-clang", + "configurePreset": "x-windows-ci-clang", + "description": "(Internal) Windows CI Clang", + "displayName": "(Internal) Windows CI Clang", + "targets": [ + "install" + ] + }, + { + "name": "x-windows-ci-arm64-msvc", + "configurePreset": "x-windows-ci-arm64-msvc", + "description": "(Internal) Windows CI MSVC arm64", + "displayName": "(Internal) Windows CI MSVC arm64", + "targets": [ + "install" + ] + } + ] +} diff --git a/ci/build-appimage.sh b/ci/build-appimage.sh new file mode 100755 index 0000000000..82c4cffaeb --- /dev/null +++ b/ci/build-appimage.sh @@ -0,0 +1,18 @@ +#!/bin/bash -ex +shopt -s extglob + +# Get linuxdeploy +cd "$RUNNER_WORKSPACE" +curl -fOL https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-$(uname -m).AppImage +chmod +x linuxdeploy-$(uname -m).AppImage + +# Build AppImage +cd "$GITHUB_WORKSPACE" +mkdir -p build/appdir/usr/{bin,share/{applications,icons/hicolor}} +cp build/install/!(*.*) build/appdir/usr/bin +cp -r platforms/freedesktop/{16x16,32x32,48x48,64x64,128x128,256x256,512x512,1024x1024} build/appdir/usr/share/icons/hicolor +cp platforms/freedesktop/dusk.desktop build/appdir/usr/share/applications + +cd build/install +VERSION="$DUSK_VERSION" NO_STRIP=1 "$RUNNER_WORKSPACE"/linuxdeploy-$(uname -m).AppImage \ + --appdir "$GITHUB_WORKSPACE"/build/appdir --output appimage diff --git a/include/dusk/config_var.hpp b/include/dusk/config_var.hpp index 064d37028d..6b78ee2e33 100644 --- a/include/dusk/config_var.hpp +++ b/include/dusk/config_var.hpp @@ -165,7 +165,7 @@ public: * @param arg Arguments to forward to construct the default value. */ template - explicit ConfigVar(const char* name, Args&&... arg) + ConfigVar(const char* name, Args&&... arg) : ConfigVarBase(name, GetConfigImpl()), defaultValue(std::forward(arg)...), value(), overrideValue() {} diff --git a/include/dusk/time.h b/include/dusk/time.h index 501875971b..94f819f76b 100644 --- a/include/dusk/time.h +++ b/include/dusk/time.h @@ -14,6 +14,7 @@ #endif #include #include +#include #endif #include "dusk/logging.h" @@ -92,7 +93,11 @@ private: } do { QueryPerformanceCounter(¤t); - _mm_pause(); // Yield CPU +#if defined(_M_ARM64) || defined(_M_ARM) + __yield(); +#else + _mm_pause(); +#endif } while (current.QuadPart - start.QuadPart < ticksToWait); } #else diff --git a/include/global.h b/include/global.h index bb30611486..ff2d18427d 100644 --- a/include/global.h +++ b/include/global.h @@ -194,15 +194,12 @@ static const float INF = 2000000000.0f; #endif // potential fakematch? -#if DEBUG #define FABSF fabsf -#else -#define FABSF std::fabsf -#endif #ifndef __MWERKS__ #if __cplusplus #include +#include using std::isnan; #endif #endif diff --git a/ios.toolchain.cmake b/ios.toolchain.cmake new file mode 100644 index 0000000000..fb6052fc9b --- /dev/null +++ b/ios.toolchain.cmake @@ -0,0 +1,1177 @@ +# This file is part of the ios-cmake project. It was retrieved from +# https://github.com/leetal/ios-cmake.git, which is a fork of +# https://github.com/gerstrong/ios-cmake.git, which is a fork of +# https://github.com/cristeab/ios-cmake.git, which is a fork of +# https://code.google.com/p/ios-cmake/. Which in turn is based off of +# the Platform/Darwin.cmake and Platform/UnixPaths.cmake files which +# are included with CMake 2.8.4 +# +# The ios-cmake project is licensed under the new BSD license. +# +# Copyright (c) 2014, Bogdan Cristea and LTE Engineering Software, +# Kitware, Inc., Insight Software Consortium. All rights reserved. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +# This file is based on the Platform/Darwin.cmake and +# Platform/UnixPaths.cmake files which are included with CMake 2.8.4 +# It has been altered for iOS development. +# +# Updated by Alex Stewart (alexs.mac@gmail.com) +# +# ***************************************************************************** +# Now maintained by Alexander Widerberg (widerbergaren [at] gmail.com) +# under the BSD-3-Clause license +# https://github.com/leetal/ios-cmake +# ***************************************************************************** +# +# INFORMATION / HELP +# +############################################################################### +# OPTIONS # +############################################################################### +# +# PLATFORM: (default "OS64") +# OS = Build for iPhoneOS. +# OS64 = Build for arm64 iphoneOS. +# OS64COMBINED = Build for arm64 x86_64 iphoneOS + iphoneOS Simulator. Combined into FAT STATIC lib (only supported on 3.14+ of CMake with "-G Xcode" argument in combination with the "cmake --install" CMake build step) +# SIMULATOR = Build for x86 i386 iphoneOS Simulator. +# SIMULATOR64 = Build for x86_64 iphoneOS Simulator. +# SIMULATORARM64 = Build for arm64 iphoneOS Simulator. +# SIMULATOR64COMBINED = Build for arm64 x86_64 iphoneOS Simulator. Combined into FAT STATIC lib (supported on 3.14+ of CMakewith "-G Xcode" argument ONLY) +# TVOS = Build for arm64 tvOS. +# TVOSCOMBINED = Build for arm64 x86_64 tvOS + tvOS Simulator. Combined into FAT STATIC lib (only supported on 3.14+ of CMake with "-G Xcode" argument in combination with the "cmake --install" CMake build step) +# SIMULATOR_TVOS = Build for x86_64 tvOS Simulator. +# SIMULATORARM64_TVOS = Build for arm64 tvOS Simulator. +# VISIONOSCOMBINED = Build for arm64 visionOS + visionOS Simulator. Combined into FAT STATIC lib (only supported on 3.14+ of CMake with "-G Xcode" argument in combination with the "cmake --install" CMake build step) +# VISIONOS = Build for arm64 visionOS. +# SIMULATOR_VISIONOS = Build for arm64 visionOS Simulator. +# WATCHOS = Build for armv7k arm64_32 for watchOS. +# WATCHOSCOMBINED = Build for armv7k arm64_32 x86_64 watchOS + watchOS Simulator. Combined into FAT STATIC lib (only supported on 3.14+ of CMake with "-G Xcode" argument in combination with the "cmake --install" CMake build step) +# SIMULATOR_WATCHOS = Build for x86_64 for watchOS Simulator. +# SIMULATORARM64_WATCHOS = Build for arm64 for watchOS Simulator. +# SIMULATOR_WATCHOSCOMBINED = Build for arm64 x86_64 for watchOS Simulator. Combined into FAT STATIC lib (supported on 3.14+ of CMakewith "-G Xcode" argument ONLY) +# MAC = Build for x86_64 macOS. +# MAC_ARM64 = Build for Apple Silicon macOS. +# MAC_UNIVERSAL = Combined build for x86_64 and Apple Silicon on macOS. +# MAC_CATALYST = Build for x86_64 macOS with Catalyst support (iOS toolchain on macOS). +# Note: The build argument "MACOSX_DEPLOYMENT_TARGET" can be used to control min-version of macOS +# MAC_CATALYST_ARM64 = Build for Apple Silicon macOS with Catalyst support (iOS toolchain on macOS). +# Note: The build argument "MACOSX_DEPLOYMENT_TARGET" can be used to control min-version of macOS +# MAC_CATALYST_UNIVERSAL = Combined build for x86_64 and Apple Silicon on Catalyst. +# +# CMAKE_OSX_SYSROOT: Path to the SDK to use. By default this is +# automatically determined from PLATFORM and xcodebuild, but +# can also be manually specified (although this should not be required). +# +# CMAKE_DEVELOPER_ROOT: Path to the Developer directory for the platform +# being compiled for. By default, this is automatically determined from +# CMAKE_OSX_SYSROOT, but can also be manually specified (although this should +# not be required). +# +# DEPLOYMENT_TARGET: Minimum SDK version to target. Default 6.0 on watchOS, 13.0 on tvOS+iOS/iPadOS, 11.0 on macOS, 1.0 on visionOS +# +# NAMED_LANGUAGE_SUPPORT: +# ON (default) = Will require "enable_language(OBJC) and/or enable_language(OBJCXX)" for full OBJC|OBJCXX support +# OFF = Will embed the OBJC and OBJCXX flags into the CMAKE_C_FLAGS and CMAKE_CXX_FLAGS (legacy behavior, CMake version < 3.16) +# +# ENABLE_BITCODE: (ON|OFF) Enables or disables bitcode support. Default OFF +# +# ENABLE_ARC: (ON|OFF) Enables or disables ARC support. Default ON (ARC enabled by default) +# +# ENABLE_VISIBILITY: (ON|OFF) Enables or disables symbol visibility support. Default OFF (visibility hidden by default) +# +# ENABLE_STRICT_TRY_COMPILE: (ON|OFF) Enables or disables strict try_compile() on all Check* directives (will run linker +# to actually check if linking is possible). Default OFF (will set CMAKE_TRY_COMPILE_TARGET_TYPE to STATIC_LIBRARY) +# +# ARCHS: (armv7 armv7s armv7k arm64 arm64_32 i386 x86_64) If specified, will override the default architectures for the given PLATFORM +# OS = armv7 armv7s arm64 (if applicable) +# OS64 = arm64 (if applicable) +# SIMULATOR = i386 +# SIMULATOR64 = x86_64 +# SIMULATORARM64 = arm64 +# TVOS = arm64 +# SIMULATOR_TVOS = x86_64 (i386 has since long been deprecated) +# SIMULATORARM64_TVOS = arm64 +# WATCHOS = armv7k arm64_32 (if applicable) +# SIMULATOR_WATCHOS = x86_64 (i386 has since long been deprecated) +# SIMULATORARM64_WATCHOS = arm64 +# MAC = x86_64 +# MAC_ARM64 = arm64 +# MAC_UNIVERSAL = x86_64 arm64 +# MAC_CATALYST = x86_64 +# MAC_CATALYST_ARM64 = arm64 +# MAC_CATALYST_UNIVERSAL = x86_64 arm64 +# +# NOTE: When manually specifying ARCHS, put a semi-colon between the entries. E.g., -DARCHS="armv7;arm64" +# +############################################################################### +# END OPTIONS # +############################################################################### +# +# This toolchain defines the following properties (available via get_property()) for use externally: +# +# PLATFORM: The currently targeted platform. +# XCODE_VERSION: Version number (not including Build version) of Xcode detected. +# SDK_VERSION: Version of SDK being used. +# OSX_ARCHITECTURES: Architectures being compiled for (generated from PLATFORM). +# APPLE_TARGET_TRIPLE: Used by autoconf build systems. NOTE: If "ARCHS" is overridden, this will *NOT* be set! +# +# This toolchain defines the following macros for use externally: +# +# set_xcode_property (TARGET XCODE_PROPERTY XCODE_VALUE XCODE_VARIANT) +# A convenience macro for setting xcode specific properties on targets. +# Available variants are: All, Release, RelWithDebInfo, Debug, MinSizeRel +# example: set_xcode_property (myioslib IPHONEOS_DEPLOYMENT_TARGET "3.1" "all"). +# +# find_host_package (PROGRAM ARGS) +# A macro used to find executable programs on the host system, not within the +# environment. Thanks to the android-cmake project for providing the +# command. +# + +cmake_minimum_required(VERSION 3.8..3.30) + +# CMake invokes the toolchain file twice during the first build, but only once during subsequent rebuilds. +# NOTE: To improve single-library build-times, provide the flag "OS_SINGLE_BUILD" as a build argument. +if(DEFINED OS_SINGLE_BUILD AND DEFINED ENV{_IOS_TOOLCHAIN_HAS_RUN}) + return() +endif() +set(ENV{_IOS_TOOLCHAIN_HAS_RUN} true) + +# List of supported platform values +list(APPEND _supported_platforms + "OS" "OS64" "OS64COMBINED" "SIMULATOR" "SIMULATOR64" "SIMULATORARM64" "SIMULATOR64COMBINED" + "TVOS" "TVOSCOMBINED" "SIMULATOR_TVOS" "SIMULATORARM64_TVOS" + "WATCHOS" "WATCHOSCOMBINED" "SIMULATOR_WATCHOS" "SIMULATORARM64_WATCHOS" "SIMULATOR_WATCHOSCOMBINED" + "MAC" "MAC_ARM64" "MAC_UNIVERSAL" + "VISIONOS" "SIMULATOR_VISIONOS" "VISIONOSCOMBINED" + "MAC_CATALYST" "MAC_CATALYST_ARM64" "MAC_CATALYST_UNIVERSAL") + +# Cache what generator is used +set(USED_CMAKE_GENERATOR "${CMAKE_GENERATOR}") + +# Check if using a CMake version capable of building combined FAT builds (simulator and target slices combined in one static lib) +if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.14") + set(MODERN_CMAKE YES) +endif() + +# Get the Xcode version being used. +# Problem: CMake runs toolchain files multiple times, but can't read cache variables on some runs. +# Workaround: On the first run (in which cache variables are always accessible), set an intermediary environment variable. +# +# NOTE: This pattern is used in many places in this toolchain to speed up checks of all sorts +if(DEFINED XCODE_VERSION_INT) + # Environment variables are always preserved. + set(ENV{_XCODE_VERSION_INT} "${XCODE_VERSION_INT}") +elseif(DEFINED ENV{_XCODE_VERSION_INT}) + set(XCODE_VERSION_INT "$ENV{_XCODE_VERSION_INT}") +elseif(NOT DEFINED XCODE_VERSION_INT) + find_program(XCODEBUILD_EXECUTABLE xcodebuild) + if(NOT XCODEBUILD_EXECUTABLE) + message(FATAL_ERROR "xcodebuild not found. Please install either the standalone commandline tools or Xcode.") + endif() + execute_process(COMMAND ${XCODEBUILD_EXECUTABLE} -version + OUTPUT_VARIABLE XCODE_VERSION_INT + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + string(REGEX MATCH "Xcode [0-9\\.]+" XCODE_VERSION_INT "${XCODE_VERSION_INT}") + string(REGEX REPLACE "Xcode ([0-9\\.]+)" "\\1" XCODE_VERSION_INT "${XCODE_VERSION_INT}") + set(XCODE_VERSION_INT "${XCODE_VERSION_INT}" CACHE INTERNAL "") +endif() + +# Assuming that xcode 12.0 is installed you most probably have ios sdk 14.0 or later installed (tested on Big Sur) +# if you don't set a deployment target it will be set the way you only get 64-bit builds +#if(NOT DEFINED DEPLOYMENT_TARGET AND XCODE_VERSION_INT VERSION_GREATER 12.0) +# Temporarily fix the arm64 issues in CMake install-combined by excluding arm64 for simulator builds (needed for Apple Silicon...) +# set(CMAKE_XCODE_ATTRIBUTE_EXCLUDED_ARCHS[sdk=iphonesimulator*] "arm64") +#endif() + +# Check if the platform variable is set +if(DEFINED PLATFORM) + # Environment variables are always preserved. + set(ENV{_PLATFORM} "${PLATFORM}") +elseif(DEFINED ENV{_PLATFORM}) + set(PLATFORM "$ENV{_PLATFORM}") +elseif(NOT DEFINED PLATFORM) + message(FATAL_ERROR "PLATFORM argument not set. Bailing configure since I don't know what target you want to build for!") +endif () + +if(PLATFORM MATCHES ".*COMBINED" AND NOT CMAKE_GENERATOR MATCHES "Xcode") + message(FATAL_ERROR "The combined builds support requires Xcode to be used as a generator via '-G Xcode' command-line argument in CMake") +endif() + +# Safeguard that the platform value is set and is one of the supported values +list(FIND _supported_platforms ${PLATFORM} contains_PLATFORM) +if("${contains_PLATFORM}" EQUAL "-1") + string(REPLACE ";" "\n * " _supported_platforms_formatted "${_supported_platforms}") + message(FATAL_ERROR " Invalid PLATFORM specified! Current value: ${PLATFORM}.\n" + " Supported PLATFORM values: \n * ${_supported_platforms_formatted}") +endif() + +# Check if Apple Silicon is supported +if(PLATFORM MATCHES "^(MAC_ARM64)$|^(MAC_CATALYST_ARM64)$|^(MAC_UNIVERSAL)$|^(MAC_CATALYST_UNIVERSAL)$" AND ${CMAKE_VERSION} VERSION_LESS "3.19.5") + message(FATAL_ERROR "Apple Silicon builds requires a minimum of CMake 3.19.5") +endif() + +# Touch the toolchain variable to suppress the "unused variable" warning. +# This happens if CMake is invoked with the same command line the second time. +if(CMAKE_TOOLCHAIN_FILE) +endif() + +# Fix for PThread library not in path +set(CMAKE_THREAD_LIBS_INIT "-lpthread") +set(CMAKE_HAVE_THREADS_LIBRARY 1) +set(CMAKE_USE_WIN32_THREADS_INIT 0) +set(CMAKE_USE_PTHREADS_INIT 1) + +# Specify named language support defaults. +if(NOT DEFINED NAMED_LANGUAGE_SUPPORT AND ${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.16") + set(NAMED_LANGUAGE_SUPPORT ON) + message(STATUS "[DEFAULTS] Using explicit named language support! E.g., enable_language(CXX) is needed in the project files.") +elseif(NOT DEFINED NAMED_LANGUAGE_SUPPORT AND ${CMAKE_VERSION} VERSION_LESS "3.16") + set(NAMED_LANGUAGE_SUPPORT OFF) + message(STATUS "[DEFAULTS] Disabling explicit named language support. Falling back to legacy behavior.") +elseif(DEFINED NAMED_LANGUAGE_SUPPORT AND ${CMAKE_VERSION} VERSION_LESS "3.16") + message(FATAL_ERROR "CMake named language support for OBJC and OBJCXX was added in CMake 3.16.") +endif() +set(NAMED_LANGUAGE_SUPPORT_INT ${NAMED_LANGUAGE_SUPPORT} CACHE BOOL + "Whether or not to enable explicit named language support" FORCE) + +# Specify the minimum version of the deployment target. +if(NOT DEFINED DEPLOYMENT_TARGET) + if (PLATFORM MATCHES "WATCHOS") + # Unless specified, SDK version 6.0 is used by default as minimum target version (watchOS). + set(DEPLOYMENT_TARGET "6.0") + elseif(PLATFORM STREQUAL "MAC") + # Unless specified, SDK version 11.0 (Big Sur) is used by default as the minimum target version (macOS on x86). + set(DEPLOYMENT_TARGET "11.0") + elseif(PLATFORM STREQUAL "VISIONOS" OR PLATFORM STREQUAL "SIMULATOR_VISIONOS" OR PLATFORM STREQUAL "VISIONOSCOMBINED") + # Unless specified, SDK version 1.0 is used by default as minimum target version (visionOS). + set(DEPLOYMENT_TARGET "1.0") + elseif(PLATFORM STREQUAL "MAC_ARM64") + # Unless specified, SDK version 11.0 (Big Sur) is used by default as the minimum target version (macOS on arm). + set(DEPLOYMENT_TARGET "11.0") + elseif(PLATFORM STREQUAL "MAC_UNIVERSAL") + # Unless specified, SDK version 11.0 (Big Sur) is used by default as minimum target version for universal builds. + set(DEPLOYMENT_TARGET "11.0") + elseif(PLATFORM STREQUAL "MAC_CATALYST" OR PLATFORM STREQUAL "MAC_CATALYST_ARM64" OR PLATFORM STREQUAL "MAC_CATALYST_UNIVERSAL") + # Unless specified, SDK version 13.1 is used by default as the minimum target version (mac catalyst minimum requirement). + set(DEPLOYMENT_TARGET "13.1") + else() + # Unless specified, SDK version 13.0 is used by default as the minimum target version (iOS, tvOS). + set(DEPLOYMENT_TARGET "13.0") + endif() + message(STATUS "[DEFAULTS] Using the default min-version since DEPLOYMENT_TARGET not provided!") +elseif(DEFINED DEPLOYMENT_TARGET AND PLATFORM MATCHES "^MAC_CATALYST" AND ${DEPLOYMENT_TARGET} VERSION_LESS "13.1") + message(FATAL_ERROR "Mac Catalyst builds requires a minimum deployment target of 13.1!") +endif() + +# Store the DEPLOYMENT_TARGET in the cache +set(DEPLOYMENT_TARGET "${DEPLOYMENT_TARGET}" CACHE INTERNAL "") + +# Handle the case where we are targeting iOS and a version above 10.3.4 (32-bit support dropped officially) +if(PLATFORM STREQUAL "OS" AND DEPLOYMENT_TARGET VERSION_GREATER_EQUAL 10.3.4) + set(PLATFORM "OS64") + message(STATUS "Targeting minimum SDK version ${DEPLOYMENT_TARGET}. Dropping 32-bit support.") +elseif(PLATFORM STREQUAL "SIMULATOR" AND DEPLOYMENT_TARGET VERSION_GREATER_EQUAL 10.3.4) + set(PLATFORM "SIMULATOR64") + message(STATUS "Targeting minimum SDK version ${DEPLOYMENT_TARGET}. Dropping 32-bit support.") +endif() + +set(PLATFORM_INT "${PLATFORM}") + +if(DEFINED ARCHS) + string(REPLACE ";" "-" ARCHS_SPLIT "${ARCHS}") +endif() + +# Determine the platform name and architectures for use in xcodebuild commands +# from the specified PLATFORM_INT name. +if(PLATFORM_INT STREQUAL "OS") + set(SDK_NAME iphoneos) + if(NOT ARCHS) + set(ARCHS armv7 armv7s arm64) + set(APPLE_TARGET_TRIPLE_INT arm-apple-ios${DEPLOYMENT_TARGET}) + else() + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-ios${DEPLOYMENT_TARGET}) + endif() +elseif(PLATFORM_INT STREQUAL "OS64") + set(SDK_NAME iphoneos) + if(NOT ARCHS) + if (XCODE_VERSION_INT VERSION_GREATER 10.0) + set(ARCHS arm64) # FIXME: Add arm64e when Apple has fixed the integration issues with it, libarclite_iphoneos.a is currently missing bitcode markers for example + else() + set(ARCHS arm64) + endif() + set(APPLE_TARGET_TRIPLE_INT arm64-apple-ios${DEPLOYMENT_TARGET}) + else() + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-ios${DEPLOYMENT_TARGET}) + endif() +elseif(PLATFORM_INT STREQUAL "OS64COMBINED") + set(SDK_NAME iphoneos) + if(MODERN_CMAKE) + if(NOT ARCHS) + if (XCODE_VERSION_INT VERSION_GREATER 12.0) + set(ARCHS arm64 x86_64) + set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=iphoneos*] "arm64") + set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=iphonesimulator*] "x86_64 arm64") + set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=iphoneos*] "arm64") + set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=iphonesimulator*] "x86_64 arm64") + else() + set(ARCHS arm64 x86_64) + set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=iphoneos*] "arm64") + set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=iphonesimulator*] "x86_64") + set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=iphoneos*] "arm64") + set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=iphonesimulator*] "x86_64") + endif() + set(APPLE_TARGET_TRIPLE_INT arm64-x86_64-apple-ios${DEPLOYMENT_TARGET}) + else() + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-ios${DEPLOYMENT_TARGET}) + endif() + else() + message(FATAL_ERROR "Please make sure that you are running CMake 3.14+ to make the OS64COMBINED setting work") + endif() +elseif(PLATFORM_INT STREQUAL "SIMULATOR64COMBINED") + set(SDK_NAME iphonesimulator) + if(MODERN_CMAKE) + if(NOT ARCHS) + if (XCODE_VERSION_INT VERSION_GREATER 12.0) + set(ARCHS arm64 x86_64) # FIXME: Add arm64e when Apple have fixed the integration issues with it, libarclite_iphoneos.a is currently missing bitcode markers for example + set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=iphoneos*] "") + set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=iphonesimulator*] "x86_64 arm64") + set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=iphoneos*] "") + set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=iphonesimulator*] "x86_64 arm64") + else() + set(ARCHS arm64 x86_64) + set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=iphoneos*] "") + set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=iphonesimulator*] "x86_64") + set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=iphoneos*] "") + set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=iphonesimulator*] "x86_64") + endif() + set(APPLE_TARGET_TRIPLE_INT aarch64-x86_64-apple-ios${DEPLOYMENT_TARGET}-simulator) + else() + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-ios${DEPLOYMENT_TARGET}-simulator) + endif() + else() + message(FATAL_ERROR "Please make sure that you are running CMake 3.14+ to make the SIMULATOR64COMBINED setting work") + endif() +elseif(PLATFORM_INT STREQUAL "SIMULATOR") + set(SDK_NAME iphonesimulator) + if(NOT ARCHS) + set(ARCHS i386) + set(APPLE_TARGET_TRIPLE_INT i386-apple-ios${DEPLOYMENT_TARGET}-simulator) + else() + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-ios${DEPLOYMENT_TARGET}-simulator) + endif() + message(DEPRECATION "SIMULATOR IS DEPRECATED. Consider using SIMULATOR64 instead.") +elseif(PLATFORM_INT STREQUAL "SIMULATOR64") + set(SDK_NAME iphonesimulator) + if(NOT ARCHS) + set(ARCHS x86_64) + set(APPLE_TARGET_TRIPLE_INT x86_64-apple-ios${DEPLOYMENT_TARGET}-simulator) + else() + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-ios${DEPLOYMENT_TARGET}-simulator) + endif() +elseif(PLATFORM_INT STREQUAL "SIMULATORARM64") + set(SDK_NAME iphonesimulator) + if(NOT ARCHS) + set(ARCHS arm64) + set(APPLE_TARGET_TRIPLE_INT arm64-apple-ios${DEPLOYMENT_TARGET}-simulator) + else() + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-ios${DEPLOYMENT_TARGET}-simulator) + endif() +elseif(PLATFORM_INT STREQUAL "TVOS") + set(SDK_NAME appletvos) + if(NOT ARCHS) + set(ARCHS arm64) + set(APPLE_TARGET_TRIPLE_INT arm64-apple-tvos${DEPLOYMENT_TARGET}) + else() + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-tvos${DEPLOYMENT_TARGET}) + endif() +elseif (PLATFORM_INT STREQUAL "TVOSCOMBINED") + set(SDK_NAME appletvos) + if(MODERN_CMAKE) + if(NOT ARCHS) + set(ARCHS arm64 x86_64) + set(APPLE_TARGET_TRIPLE_INT arm64-x86_64-apple-tvos${DEPLOYMENT_TARGET}) + set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=appletvos*] "arm64") + set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=appletvsimulator*] "x86_64 arm64") + set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=appletvos*] "arm64") + set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=appletvsimulator*] "x86_64 arm64") + else() + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-tvos${DEPLOYMENT_TARGET}) + endif() + else() + message(FATAL_ERROR "Please make sure that you are running CMake 3.14+ to make the TVOSCOMBINED setting work") + endif() +elseif(PLATFORM_INT STREQUAL "SIMULATOR_TVOS") + set(SDK_NAME appletvsimulator) + if(NOT ARCHS) + set(ARCHS x86_64) + set(APPLE_TARGET_TRIPLE_INT x86_64-apple-tvos${DEPLOYMENT_TARGET}-simulator) + else() + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-tvos${DEPLOYMENT_TARGET}-simulator) + endif() +elseif(PLATFORM_INT STREQUAL "SIMULATORARM64_TVOS") + set(SDK_NAME appletvsimulator) + if(NOT ARCHS) + set(ARCHS arm64) + set(APPLE_TARGET_TRIPLE_INT arm64-apple-tvos${DEPLOYMENT_TARGET}-simulator) + else() + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-tvos${DEPLOYMENT_TARGET}-simulator) + endif() +elseif(PLATFORM_INT STREQUAL "WATCHOS") + set(SDK_NAME watchos) + if(NOT ARCHS) + if (XCODE_VERSION_INT VERSION_GREATER 10.0) + set(ARCHS armv7k arm64_32) + set(APPLE_TARGET_TRIPLE_INT arm64_32-apple-watchos${DEPLOYMENT_TARGET}) + else() + set(ARCHS armv7k) + set(APPLE_TARGET_TRIPLE_INT arm-apple-watchos${DEPLOYMENT_TARGET}) + endif() + else() + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-watchos${DEPLOYMENT_TARGET}) + endif() +elseif(PLATFORM_INT STREQUAL "WATCHOSCOMBINED") + set(SDK_NAME watchos) + if(MODERN_CMAKE) + if(NOT ARCHS) + if (XCODE_VERSION_INT VERSION_GREATER 10.0) + set(ARCHS armv7k arm64_32 x86_64) + set(APPLE_TARGET_TRIPLE_INT arm64_32-x86_64-apple-watchos${DEPLOYMENT_TARGET}) + set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=watchos*] "armv7k arm64_32") + set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=watchsimulator*] "x86_64") + set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=watchos*] "armv7k arm64_32") + set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=watchsimulator*] "x86_64") + else() + set(ARCHS armv7k i386) + set(APPLE_TARGET_TRIPLE_INT arm-i386-apple-watchos${DEPLOYMENT_TARGET}) + set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=watchos*] "armv7k") + set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=watchsimulator*] "i386") + set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=watchos*] "armv7k") + set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=watchsimulator*] "i386") + endif() + else() + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-watchos${DEPLOYMENT_TARGET}) + endif() + else() + message(FATAL_ERROR "Please make sure that you are running CMake 3.14+ to make the WATCHOSCOMBINED setting work") + endif() +elseif(PLATFORM_INT STREQUAL "SIMULATOR_WATCHOS") + set(SDK_NAME watchsimulator) + if(NOT ARCHS) + if (XCODE_VERSION_INT VERSION_GREATER 10.0) + set(ARCHS x86_64) + set(APPLE_TARGET_TRIPLE_INT x86_64-apple-watchos${DEPLOYMENT_TARGET}-simulator) + else() + set(ARCHS i386) + set(APPLE_TARGET_TRIPLE_INT i386-apple-watchos${DEPLOYMENT_TARGET}-simulator) + endif() + else() + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-watchos${DEPLOYMENT_TARGET}-simulator) + endif() +elseif(PLATFORM_INT STREQUAL "SIMULATORARM64_WATCHOS") + set(SDK_NAME watchsimulator) + if(NOT ARCHS) + set(ARCHS arm64) + set(APPLE_TARGET_TRIPLE_INT arm64-apple-watchos${DEPLOYMENT_TARGET}-simulator) + else() + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-watchos${DEPLOYMENT_TARGET}-simulator) + endif() +elseif(PLATFORM_INT STREQUAL "SIMULATOR_WATCHOSCOMBINED") + set(SDK_NAME watchsimulator) + if(MODERN_CMAKE) + if(NOT ARCHS) + if (XCODE_VERSION_INT VERSION_GREATER 12.0) + set(ARCHS arm64 x86_64) + set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=watchos*] "") + set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=watchsimulator*] "arm64 x86_64") + set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=watchos*] "") + set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=watchsimulator*] "arm64 x86_64") + set(APPLE_TARGET_TRIPLE_INT arm64_x86_64-apple-watchos${DEPLOYMENT_TARGET}-simulator) + else() + set(ARCHS arm64 i386) + set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=watchos*] "") + set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=watchsimulator*] "i386") + set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=watchos*] "") + set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=watchsimulator*] "i386") + set(APPLE_TARGET_TRIPLE_INT arm64_i386-apple-watchos${DEPLOYMENT_TARGET}-simulator) + endif() + else() + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-watchos${DEPLOYMENT_TARGET}-simulator) + endif() + else() + message(FATAL_ERROR "Please make sure that you are running CMake 3.14+ to make the SIMULATOR_WATCHOSCOMBINED setting work") + endif() +elseif(PLATFORM_INT STREQUAL "SIMULATOR_VISIONOS") + set(SDK_NAME xrsimulator) + if(NOT ARCHS) + set(ARCHS arm64) + set(APPLE_TARGET_TRIPLE_INT arm64-apple-xros${DEPLOYMENT_TARGET}-simulator) + else() + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-xros${DEPLOYMENT_TARGET}-simulator) + endif() +elseif(PLATFORM_INT STREQUAL "VISIONOS") + set(SDK_NAME xros) + if(NOT ARCHS) + set(ARCHS arm64) + set(APPLE_TARGET_TRIPLE_INT arm64-apple-xros${DEPLOYMENT_TARGET}) + else() + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-xros${DEPLOYMENT_TARGET}) + endif() +elseif(PLATFORM_INT STREQUAL "VISIONOSCOMBINED") + set(SDK_NAME xros) + if(MODERN_CMAKE) + if(NOT ARCHS) + set(ARCHS arm64) + set(APPLE_TARGET_TRIPLE_INT arm64-apple-xros${DEPLOYMENT_TARGET}) + set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=xros*] "arm64") + set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=xrsimulator*] "arm64") + else() + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-xros${DEPLOYMENT_TARGET}) + endif() + else() + message(FATAL_ERROR "Please make sure that you are running CMake 3.14+ to make the VISIONOSCOMBINED setting work") + endif() +elseif(PLATFORM_INT STREQUAL "MAC" OR PLATFORM_INT STREQUAL "MAC_CATALYST") + set(SDK_NAME macosx) + if(NOT ARCHS) + set(ARCHS x86_64) + endif() + string(REPLACE ";" "-" ARCHS_SPLIT "${ARCHS}") + if(PLATFORM_INT STREQUAL "MAC") + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-macosx${DEPLOYMENT_TARGET}) + elseif(PLATFORM_INT STREQUAL "MAC_CATALYST") + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-ios${DEPLOYMENT_TARGET}-macabi) + endif() +elseif(PLATFORM_INT MATCHES "^(MAC_ARM64)$|^(MAC_CATALYST_ARM64)$") + set(SDK_NAME macosx) + if(NOT ARCHS) + set(ARCHS arm64) + endif() + string(REPLACE ";" "-" ARCHS_SPLIT "${ARCHS}") + if(PLATFORM_INT STREQUAL "MAC_ARM64") + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-macosx${DEPLOYMENT_TARGET}) + elseif(PLATFORM_INT STREQUAL "MAC_CATALYST_ARM64") + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-ios${DEPLOYMENT_TARGET}-macabi) + endif() +elseif(PLATFORM_INT STREQUAL "MAC_UNIVERSAL") + set(SDK_NAME macosx) + if(NOT ARCHS) + set(ARCHS "x86_64;arm64") + endif() + # For universal builds, don't set target triple - let CMake handle it + # string(REPLACE ";" "-" ARCHS_SPLIT "${ARCHS}") + # set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-macosx${DEPLOYMENT_TARGET}) +elseif(PLATFORM_INT STREQUAL "MAC_CATALYST_UNIVERSAL") + set(SDK_NAME macosx) + if(NOT ARCHS) + set(ARCHS "x86_64;arm64") + endif() + string(REPLACE ";" "-" ARCHS_SPLIT "${ARCHS}") + set(APPLE_TARGET_TRIPLE_INT apple-ios${DEPLOYMENT_TARGET}-macabi) +else() + message(FATAL_ERROR "Invalid PLATFORM: ${PLATFORM_INT}") +endif() + +string(REPLACE ";" " " ARCHS_SPACED "${ARCHS}") + +if(MODERN_CMAKE AND PLATFORM_INT MATCHES ".*COMBINED" AND NOT CMAKE_GENERATOR MATCHES "Xcode") + message(FATAL_ERROR "The COMBINED options only work with Xcode generator, -G Xcode") +endif() + +if(CMAKE_GENERATOR MATCHES "Xcode" AND PLATFORM_INT MATCHES "^MAC_CATALYST") + set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LIBRARY "libc++") + set(CMAKE_XCODE_ATTRIBUTE_SUPPORTED_PLATFORMS "macosx") + set(CMAKE_XCODE_ATTRIBUTE_SUPPORTS_MACCATALYST "YES") + if(NOT DEFINED MACOSX_DEPLOYMENT_TARGET) + set(CMAKE_XCODE_ATTRIBUTE_MACOSX_DEPLOYMENT_TARGET "10.15") + else() + set(CMAKE_XCODE_ATTRIBUTE_MACOSX_DEPLOYMENT_TARGET "${MACOSX_DEPLOYMENT_TARGET}") + endif() +elseif(CMAKE_GENERATOR MATCHES "Xcode") + set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LIBRARY "libc++") + set(CMAKE_XCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGET "${DEPLOYMENT_TARGET}") + if(NOT PLATFORM_INT MATCHES ".*COMBINED") + set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=${SDK_NAME}*] "${ARCHS_SPACED}") + set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=${SDK_NAME}*] "${ARCHS_SPACED}") + endif() +endif() + +# If the user did not specify the SDK root to use, then query xcodebuild for it. +if(DEFINED CMAKE_OSX_SYSROOT_INT) + # Environment variables are always preserved. + set(ENV{_CMAKE_OSX_SYSROOT_INT} "${CMAKE_OSX_SYSROOT_INT}") +elseif(DEFINED ENV{_CMAKE_OSX_SYSROOT_INT}) + set(CMAKE_OSX_SYSROOT_INT "$ENV{_CMAKE_OSX_SYSROOT_INT}") +elseif(NOT DEFINED CMAKE_OSX_SYSROOT_INT) + execute_process(COMMAND ${XCODEBUILD_EXECUTABLE} -version -sdk ${SDK_NAME} Path + OUTPUT_VARIABLE CMAKE_OSX_SYSROOT_INT + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) +endif() + +if (NOT DEFINED CMAKE_OSX_SYSROOT_INT AND NOT DEFINED CMAKE_OSX_SYSROOT) + message(SEND_ERROR "Please make sure that Xcode is installed and that the toolchain" + "is pointing to the correct path. Please run:" + "sudo xcode-select -s /Applications/Xcode.app/Contents/Developer" + "and see if that fixes the problem for you.") + message(FATAL_ERROR "Invalid CMAKE_OSX_SYSROOT: ${CMAKE_OSX_SYSROOT} " + "does not exist.") +elseif(DEFINED CMAKE_OSX_SYSROOT_INT) + set(CMAKE_OSX_SYSROOT_INT "${CMAKE_OSX_SYSROOT_INT}" CACHE INTERNAL "") + # Specify the location or name of the platform SDK to be used in CMAKE_OSX_SYSROOT. + set(CMAKE_OSX_SYSROOT "${CMAKE_OSX_SYSROOT_INT}" CACHE INTERNAL "") +endif() + +# Use bitcode or not +if(NOT DEFINED ENABLE_BITCODE) + message(STATUS "[DEFAULTS] Disabling bitcode support by default. ENABLE_BITCODE not provided for override!") + set(ENABLE_BITCODE OFF) +endif() +set(ENABLE_BITCODE_INT ${ENABLE_BITCODE} CACHE BOOL + "Whether or not to enable bitcode" FORCE) +# Use ARC or not +if(NOT DEFINED ENABLE_ARC) + # Unless specified, enable ARC support by default + set(ENABLE_ARC ON) + message(STATUS "[DEFAULTS] Enabling ARC support by default. ENABLE_ARC not provided!") +endif() +set(ENABLE_ARC_INT ${ENABLE_ARC} CACHE BOOL "Whether or not to enable ARC" FORCE) +# Use hidden visibility or not +if(NOT DEFINED ENABLE_VISIBILITY) + # Unless specified, disable symbols visibility by default + set(ENABLE_VISIBILITY OFF) + message(STATUS "[DEFAULTS] Hiding symbols visibility by default. ENABLE_VISIBILITY not provided!") +endif() +set(ENABLE_VISIBILITY_INT ${ENABLE_VISIBILITY} CACHE BOOL "Whether or not to hide symbols from the dynamic linker (-fvisibility=hidden)" FORCE) +# Set strict compiler checks or not +if(NOT DEFINED ENABLE_STRICT_TRY_COMPILE) + # Unless specified, disable strict try_compile() + set(ENABLE_STRICT_TRY_COMPILE OFF) + message(STATUS "[DEFAULTS] Using NON-strict compiler checks by default. ENABLE_STRICT_TRY_COMPILE not provided!") +endif() +set(ENABLE_STRICT_TRY_COMPILE_INT ${ENABLE_STRICT_TRY_COMPILE} CACHE BOOL + "Whether or not to use strict compiler checks" FORCE) + +# Get the SDK version information. +if(DEFINED SDK_VERSION) + # Environment variables are always preserved. + set(ENV{_SDK_VERSION} "${SDK_VERSION}") +elseif(DEFINED ENV{_SDK_VERSION}) + set(SDK_VERSION "$ENV{_SDK_VERSION}") +elseif(NOT DEFINED SDK_VERSION) + execute_process(COMMAND ${XCODEBUILD_EXECUTABLE} -sdk ${CMAKE_OSX_SYSROOT_INT} -version SDKVersion + OUTPUT_VARIABLE SDK_VERSION + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) +endif() + +# Find the Developer root for the specific iOS platform being compiled for +# from CMAKE_OSX_SYSROOT. Should be ../../ from SDK specified in +# CMAKE_OSX_SYSROOT. There does not appear to be a direct way to obtain +# this information from xcrun or xcodebuild. +if (NOT DEFINED CMAKE_DEVELOPER_ROOT AND NOT CMAKE_GENERATOR MATCHES "Xcode") + get_filename_component(PLATFORM_SDK_DIR ${CMAKE_OSX_SYSROOT_INT} PATH) + get_filename_component(CMAKE_DEVELOPER_ROOT ${PLATFORM_SDK_DIR} PATH) + if (NOT EXISTS "${CMAKE_DEVELOPER_ROOT}") + message(FATAL_ERROR "Invalid CMAKE_DEVELOPER_ROOT: ${CMAKE_DEVELOPER_ROOT} does not exist.") + endif() +endif() + +# Find the C & C++ compilers for the specified SDK. +if(DEFINED CMAKE_C_COMPILER) + # Environment variables are always preserved. + set(ENV{_CMAKE_C_COMPILER} "${CMAKE_C_COMPILER}") +elseif(DEFINED ENV{_CMAKE_C_COMPILER}) + set(CMAKE_C_COMPILER "$ENV{_CMAKE_C_COMPILER}") + set(CMAKE_ASM_COMPILER ${CMAKE_C_COMPILER}) +elseif(NOT DEFINED CMAKE_C_COMPILER) + execute_process(COMMAND xcrun -sdk ${CMAKE_OSX_SYSROOT_INT} -find clang + OUTPUT_VARIABLE CMAKE_C_COMPILER + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + set(CMAKE_ASM_COMPILER ${CMAKE_C_COMPILER}) +endif() +if(DEFINED CMAKE_CXX_COMPILER) + # Environment variables are always preserved. + set(ENV{_CMAKE_CXX_COMPILER} "${CMAKE_CXX_COMPILER}") +elseif(DEFINED ENV{_CMAKE_CXX_COMPILER}) + set(CMAKE_CXX_COMPILER "$ENV{_CMAKE_CXX_COMPILER}") +elseif(NOT DEFINED CMAKE_CXX_COMPILER) + execute_process(COMMAND xcrun -sdk ${CMAKE_OSX_SYSROOT_INT} -find clang++ + OUTPUT_VARIABLE CMAKE_CXX_COMPILER + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) +endif() +# Find (Apple's) libtool. +if(DEFINED BUILD_LIBTOOL) + # Environment variables are always preserved. + set(ENV{_BUILD_LIBTOOL} "${BUILD_LIBTOOL}") +elseif(DEFINED ENV{_BUILD_LIBTOOL}) + set(BUILD_LIBTOOL "$ENV{_BUILD_LIBTOOL}") +elseif(NOT DEFINED BUILD_LIBTOOL) + execute_process(COMMAND xcrun -sdk ${CMAKE_OSX_SYSROOT_INT} -find libtool + OUTPUT_VARIABLE BUILD_LIBTOOL + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) +endif() +# Find the toolchain's provided install_name_tool if none is found on the host +if(DEFINED CMAKE_INSTALL_NAME_TOOL) + # Environment variables are always preserved. + set(ENV{_CMAKE_INSTALL_NAME_TOOL} "${CMAKE_INSTALL_NAME_TOOL}") +elseif(DEFINED ENV{_CMAKE_INSTALL_NAME_TOOL}) + set(CMAKE_INSTALL_NAME_TOOL "$ENV{_CMAKE_INSTALL_NAME_TOOL}") +elseif(NOT DEFINED CMAKE_INSTALL_NAME_TOOL) + execute_process(COMMAND xcrun -sdk ${CMAKE_OSX_SYSROOT_INT} -find install_name_tool + OUTPUT_VARIABLE CMAKE_INSTALL_NAME_TOOL_INT + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + set(CMAKE_INSTALL_NAME_TOOL ${CMAKE_INSTALL_NAME_TOOL_INT} CACHE INTERNAL "") +endif() + +# Configure libtool to be used instead of ar + ranlib to build static libraries. +# This is required on Xcode 7+, but should also work on previous versions of +# Xcode. +get_property(languages GLOBAL PROPERTY ENABLED_LANGUAGES) +foreach(lang ${languages}) + set(CMAKE_${lang}_CREATE_STATIC_LIBRARY "${BUILD_LIBTOOL} -static -o " CACHE INTERNAL "") +endforeach() + +# CMake 3.14+ support building for iOS, watchOS, and tvOS out of the box. +if(MODERN_CMAKE) + if(SDK_NAME MATCHES "iphone") + set(CMAKE_SYSTEM_NAME iOS) + elseif(SDK_NAME MATCHES "xros") + set(CMAKE_SYSTEM_NAME visionOS) + elseif(SDK_NAME MATCHES "xrsimulator") + set(CMAKE_SYSTEM_NAME visionOS) + elseif(SDK_NAME MATCHES "macosx") + set(CMAKE_SYSTEM_NAME Darwin) + elseif(SDK_NAME MATCHES "appletv") + set(CMAKE_SYSTEM_NAME tvOS) + elseif(SDK_NAME MATCHES "watch") + set(CMAKE_SYSTEM_NAME watchOS) + endif() + # Provide flags for a combined FAT library build on newer CMake versions + if(PLATFORM_INT MATCHES ".*COMBINED") + set(CMAKE_IOS_INSTALL_COMBINED YES) + if(CMAKE_GENERATOR MATCHES "Xcode") + # Set the SDKROOT Xcode properties to a Xcode-friendly value (the SDK_NAME, E.g, iphoneos) + # This way, Xcode will automatically switch between the simulator and device SDK when building. + set(CMAKE_XCODE_ATTRIBUTE_SDKROOT "${SDK_NAME}") + # Force to not build just one ARCH, but all! + set(CMAKE_XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH "NO") + endif() + endif() +elseif(NOT DEFINED CMAKE_SYSTEM_NAME AND ${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.10") + # Legacy code path prior to CMake 3.14 or fallback if no CMAKE_SYSTEM_NAME specified + set(CMAKE_SYSTEM_NAME iOS) +elseif(NOT DEFINED CMAKE_SYSTEM_NAME) + # Legacy code path before CMake 3.14 or fallback if no CMAKE_SYSTEM_NAME specified + set(CMAKE_SYSTEM_NAME Darwin) +endif() +# Standard settings. +set(CMAKE_SYSTEM_VERSION ${SDK_VERSION} CACHE INTERNAL "") +set(UNIX ON CACHE BOOL "") +set(APPLE ON CACHE BOOL "") +if(PLATFORM STREQUAL "MAC" OR PLATFORM STREQUAL "MAC_ARM64" OR PLATFORM STREQUAL "MAC_UNIVERSAL") + set(IOS OFF CACHE BOOL "") + set(MACOS ON CACHE BOOL "") +elseif(PLATFORM STREQUAL "MAC_CATALYST" OR PLATFORM STREQUAL "MAC_CATALYST_ARM64" OR PLATFORM STREQUAL "MAC_CATALYST_UNIVERSAL") + set(IOS ON CACHE BOOL "") + set(MACOS ON CACHE BOOL "") +elseif(PLATFORM STREQUAL "VISIONOS" OR PLATFORM STREQUAL "SIMULATOR_VISIONOS" OR PLATFORM STREQUAL "VISIONOSCOMBINED") + set(IOS OFF CACHE BOOL "") + set(VISIONOS ON CACHE BOOL "") +else() + set(IOS ON CACHE BOOL "") +endif() +# Set the architectures for which to build. +set(CMAKE_OSX_ARCHITECTURES ${ARCHS} CACHE INTERNAL "") +# Change the type of target generated for try_compile() so it'll work when cross-compiling, weak compiler checks +if(NOT ENABLE_STRICT_TRY_COMPILE_INT) + set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) +endif() +# All iOS/Darwin specific settings - some may be redundant. +if (NOT DEFINED CMAKE_MACOSX_BUNDLE) + set(CMAKE_MACOSX_BUNDLE YES) +endif() +set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO") +set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "NO") +set(CMAKE_SHARED_LIBRARY_PREFIX "lib") +set(CMAKE_SHARED_LIBRARY_SUFFIX ".dylib") +set(CMAKE_EXTRA_SHARED_LIBRARY_SUFFIXES ".tbd" ".so") +set(CMAKE_SHARED_MODULE_PREFIX "lib") +set(CMAKE_SHARED_MODULE_SUFFIX ".so") +set(CMAKE_C_COMPILER_ABI ELF) +set(CMAKE_CXX_COMPILER_ABI ELF) +set(CMAKE_C_HAS_ISYSROOT 1) +set(CMAKE_CXX_HAS_ISYSROOT 1) +set(CMAKE_MODULE_EXISTS 1) +set(CMAKE_DL_LIBS "") +set(CMAKE_C_OSX_COMPATIBILITY_VERSION_FLAG "-compatibility_version ") +set(CMAKE_C_OSX_CURRENT_VERSION_FLAG "-current_version ") +set(CMAKE_CXX_OSX_COMPATIBILITY_VERSION_FLAG "${CMAKE_C_OSX_COMPATIBILITY_VERSION_FLAG}") +set(CMAKE_CXX_OSX_CURRENT_VERSION_FLAG "${CMAKE_C_OSX_CURRENT_VERSION_FLAG}") + +if(ARCHS MATCHES "((^|;|, )(arm64|arm64e|x86_64))+") + set(CMAKE_C_SIZEOF_DATA_PTR 8) + set(CMAKE_CXX_SIZEOF_DATA_PTR 8) + if(ARCHS MATCHES "((^|;|, )(arm64|arm64e))+") + set(CMAKE_SYSTEM_PROCESSOR "aarch64") + else() + set(CMAKE_SYSTEM_PROCESSOR "x86_64") + endif() +else() + set(CMAKE_C_SIZEOF_DATA_PTR 4) + set(CMAKE_CXX_SIZEOF_DATA_PTR 4) + set(CMAKE_SYSTEM_PROCESSOR "arm") +endif() + +# Note that only Xcode 7+ supports the newer more specific: +# -m${SDK_NAME}-version-min flags, older versions of Xcode use: +# -m(ios/ios-simulator)-version-min instead. +if(${CMAKE_VERSION} VERSION_LESS "3.11") + if(PLATFORM_INT STREQUAL "OS" OR PLATFORM_INT STREQUAL "OS64") + if(XCODE_VERSION_INT VERSION_LESS 7.0) + set(SDK_NAME_VERSION_FLAGS + "-mios-version-min=${DEPLOYMENT_TARGET}") + else() + # Xcode 7.0+ uses flags we can build directly from SDK_NAME. + set(SDK_NAME_VERSION_FLAGS + "-m${SDK_NAME}-version-min=${DEPLOYMENT_TARGET}") + endif() + elseif(PLATFORM_INT STREQUAL "TVOS") + set(SDK_NAME_VERSION_FLAGS + "-mtvos-version-min=${DEPLOYMENT_TARGET}") + elseif(PLATFORM_INT STREQUAL "SIMULATOR_TVOS") + set(SDK_NAME_VERSION_FLAGS + "-mtvos-simulator-version-min=${DEPLOYMENT_TARGET}") +elseif(PLATFORM_INT STREQUAL "SIMULATORARM64_TVOS") + set(SDK_NAME_VERSION_FLAGS + "-mtvos-simulator-version-min=${DEPLOYMENT_TARGET}") + elseif(PLATFORM_INT STREQUAL "WATCHOS") + set(SDK_NAME_VERSION_FLAGS + "-mwatchos-version-min=${DEPLOYMENT_TARGET}") + elseif(PLATFORM_INT STREQUAL "SIMULATOR_WATCHOS") + set(SDK_NAME_VERSION_FLAGS + "-mwatchos-simulator-version-min=${DEPLOYMENT_TARGET}") + elseif(PLATFORM_INT STREQUAL "SIMULATORARM64_WATCHOS") + set(SDK_NAME_VERSION_FLAGS + "-mwatchos-simulator-version-min=${DEPLOYMENT_TARGET}") + elseif(PLATFORM_INT STREQUAL "MAC") + set(SDK_NAME_VERSION_FLAGS + "-mmacosx-version-min=${DEPLOYMENT_TARGET}") + else() + # SIMULATOR or SIMULATOR64 both use -mios-simulator-version-min. + set(SDK_NAME_VERSION_FLAGS + "-mios-simulator-version-min=${DEPLOYMENT_TARGET}") + endif() +elseif(NOT PLATFORM_INT MATCHES "^MAC_CATALYST") + # Newer versions of CMake sets the version min flags correctly, skip this for Mac Catalyst targets + set(CMAKE_OSX_DEPLOYMENT_TARGET ${DEPLOYMENT_TARGET} CACHE INTERNAL "Minimum OS X deployment version") +endif() + +if(DEFINED APPLE_TARGET_TRIPLE_INT) + set(APPLE_TARGET_TRIPLE ${APPLE_TARGET_TRIPLE_INT} CACHE INTERNAL "") + set(CMAKE_C_COMPILER_TARGET ${APPLE_TARGET_TRIPLE}) + set(CMAKE_CXX_COMPILER_TARGET ${APPLE_TARGET_TRIPLE}) + set(CMAKE_ASM_COMPILER_TARGET ${APPLE_TARGET_TRIPLE}) +endif() + +if(PLATFORM_INT MATCHES "^MAC_CATALYST") + set(C_TARGET_FLAGS "-isystem ${CMAKE_OSX_SYSROOT_INT}/System/iOSSupport/usr/include -iframework ${CMAKE_OSX_SYSROOT_INT}/System/iOSSupport/System/Library/Frameworks") +endif() + +if(ENABLE_BITCODE_INT) + set(BITCODE "-fembed-bitcode") + set(CMAKE_XCODE_ATTRIBUTE_BITCODE_GENERATION_MODE "bitcode") + set(CMAKE_XCODE_ATTRIBUTE_ENABLE_BITCODE "YES") +else() + set(BITCODE "") + set(CMAKE_XCODE_ATTRIBUTE_ENABLE_BITCODE "NO") +endif() + +if(ENABLE_ARC_INT) + set(FOBJC_ARC "-fobjc-arc") + set(CMAKE_XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_ARC "YES") +else() + set(FOBJC_ARC "-fno-objc-arc") + set(CMAKE_XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_ARC "NO") +endif() + +if(NAMED_LANGUAGE_SUPPORT_INT) + set(OBJC_VARS "-fobjc-abi-version=2 -DOBJC_OLD_DISPATCH_PROTOTYPES=0") + set(OBJC_LEGACY_VARS "") +else() + set(OBJC_VARS "") + set(OBJC_LEGACY_VARS "-fobjc-abi-version=2 -DOBJC_OLD_DISPATCH_PROTOTYPES=0") +endif() + +if(NOT ENABLE_VISIBILITY_INT) + foreach(lang ${languages}) + set(CMAKE_${lang}_VISIBILITY_PRESET "hidden" CACHE INTERNAL "") + endforeach() + set(CMAKE_XCODE_ATTRIBUTE_GCC_SYMBOLS_PRIVATE_EXTERN "YES") + set(VISIBILITY "-fvisibility=hidden -fvisibility-inlines-hidden") +else() + foreach(lang ${languages}) + set(CMAKE_${lang}_VISIBILITY_PRESET "default" CACHE INTERNAL "") + endforeach() + set(CMAKE_XCODE_ATTRIBUTE_GCC_SYMBOLS_PRIVATE_EXTERN "NO") + set(VISIBILITY "-fvisibility=default") +endif() + +if(DEFINED APPLE_TARGET_TRIPLE) + set(APPLE_TARGET_TRIPLE_FLAG "-target ${APPLE_TARGET_TRIPLE}") +endif() + +#Check if Xcode generator is used since that will handle these flags automagically +if(CMAKE_GENERATOR MATCHES "Xcode") + message(STATUS "Not setting any manual command-line buildflags, since Xcode is selected as the generator. Modifying the Xcode build-settings directly instead.") +else() + set(CMAKE_C_FLAGS "${C_TARGET_FLAGS} ${APPLE_TARGET_TRIPLE_FLAG} ${SDK_NAME_VERSION_FLAGS} ${OBJC_LEGACY_VARS} ${BITCODE} ${VISIBILITY} ${CMAKE_C_FLAGS}" CACHE INTERNAL + "Flags used by the compiler during all C build types.") + set(CMAKE_C_FLAGS_DEBUG "-O0 -g ${CMAKE_C_FLAGS_DEBUG}") + set(CMAKE_C_FLAGS_MINSIZEREL "-DNDEBUG -Os ${CMAKE_C_FLAGS_MINSIZEREL}") + set(CMAKE_C_FLAGS_RELWITHDEBINFO "-DNDEBUG -O2 -g ${CMAKE_C_FLAGS_RELWITHDEBINFO}") + set(CMAKE_C_FLAGS_RELEASE "-DNDEBUG -O3 ${CMAKE_C_FLAGS_RELEASE}") + set(CMAKE_CXX_FLAGS "${C_TARGET_FLAGS} ${APPLE_TARGET_TRIPLE_FLAG} ${SDK_NAME_VERSION_FLAGS} ${OBJC_LEGACY_VARS} ${BITCODE} ${VISIBILITY} ${CMAKE_CXX_FLAGS}" CACHE INTERNAL + "Flags used by the compiler during all CXX build types.") + set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g ${CMAKE_CXX_FLAGS_DEBUG}") + set(CMAKE_CXX_FLAGS_MINSIZEREL "-DNDEBUG -Os ${CMAKE_CXX_FLAGS_MINSIZEREL}") + set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-DNDEBUG -O2 -g ${CMAKE_CXX_FLAGS_RELWITHDEBINFO}") + set(CMAKE_CXX_FLAGS_RELEASE "-DNDEBUG -O3 ${CMAKE_CXX_FLAGS_RELEASE}") + if(NAMED_LANGUAGE_SUPPORT_INT) + set(CMAKE_OBJC_FLAGS "${C_TARGET_FLAGS} ${APPLE_TARGET_TRIPLE_FLAG} ${SDK_NAME_VERSION_FLAGS} ${BITCODE} ${VISIBILITY} ${FOBJC_ARC} ${OBJC_VARS} ${CMAKE_OBJC_FLAGS}" CACHE INTERNAL + "Flags used by the compiler during all OBJC build types.") + set(CMAKE_OBJC_FLAGS_DEBUG "-O0 -g ${CMAKE_OBJC_FLAGS_DEBUG}") + set(CMAKE_OBJC_FLAGS_MINSIZEREL "-DNDEBUG -Os ${CMAKE_OBJC_FLAGS_MINSIZEREL}") + set(CMAKE_OBJC_FLAGS_RELWITHDEBINFO "-DNDEBUG -O2 -g ${CMAKE_OBJC_FLAGS_RELWITHDEBINFO}") + set(CMAKE_OBJC_FLAGS_RELEASE "-DNDEBUG -O3 ${CMAKE_OBJC_FLAGS_RELEASE}") + set(CMAKE_OBJCXX_FLAGS "${C_TARGET_FLAGS} ${APPLE_TARGET_TRIPLE_FLAG} ${SDK_NAME_VERSION_FLAGS} ${BITCODE} ${VISIBILITY} ${FOBJC_ARC} ${OBJC_VARS} ${CMAKE_OBJCXX_FLAGS}" CACHE INTERNAL + "Flags used by the compiler during all OBJCXX build types.") + set(CMAKE_OBJCXX_FLAGS_DEBUG "-O0 -g ${CMAKE_OBJCXX_FLAGS_DEBUG}") + set(CMAKE_OBJCXX_FLAGS_MINSIZEREL "-DNDEBUG -Os ${CMAKE_OBJCXX_FLAGS_MINSIZEREL}") + set(CMAKE_OBJCXX_FLAGS_RELWITHDEBINFO "-DNDEBUG -O2 -g ${CMAKE_OBJCXX_FLAGS_RELWITHDEBINFO}") + set(CMAKE_OBJCXX_FLAGS_RELEASE "-DNDEBUG -O3 ${CMAKE_OBJCXX_FLAGS_RELEASE}") + endif() + set(CMAKE_C_LINK_FLAGS "${C_TARGET_FLAGS} ${SDK_NAME_VERSION_FLAGS} -Wl,-search_paths_first ${CMAKE_C_LINK_FLAGS}" CACHE INTERNAL + "Flags used by the compiler for all C link types.") + set(CMAKE_CXX_LINK_FLAGS "${C_TARGET_FLAGS} ${SDK_NAME_VERSION_FLAGS} -Wl,-search_paths_first ${CMAKE_CXX_LINK_FLAGS}" CACHE INTERNAL + "Flags used by the compiler for all CXX link types.") + if(NAMED_LANGUAGE_SUPPORT_INT) + set(CMAKE_OBJC_LINK_FLAGS "${C_TARGET_FLAGS} ${SDK_NAME_VERSION_FLAGS} -Wl,-search_paths_first ${CMAKE_OBJC_LINK_FLAGS}" CACHE INTERNAL + "Flags used by the compiler for all OBJC link types.") + set(CMAKE_OBJCXX_LINK_FLAGS "${C_TARGET_FLAGS} ${SDK_NAME_VERSION_FLAGS} -Wl,-search_paths_first ${CMAKE_OBJCXX_LINK_FLAGS}" CACHE INTERNAL + "Flags used by the compiler for all OBJCXX link types.") + endif() + set(CMAKE_ASM_FLAGS "${CMAKE_C_FLAGS} -x assembler-with-cpp" CACHE INTERNAL + "Flags used by the compiler for all ASM build types.") +endif() + +## Print status messages to inform of the current state +message(STATUS "Configuring ${SDK_NAME} build for platform: ${PLATFORM_INT}, architecture(s): ${ARCHS}") +message(STATUS "Using SDK: ${CMAKE_OSX_SYSROOT_INT}") +message(STATUS "Using C compiler: ${CMAKE_C_COMPILER}") +message(STATUS "Using CXX compiler: ${CMAKE_CXX_COMPILER}") +message(STATUS "Using libtool: ${BUILD_LIBTOOL}") +message(STATUS "Using install name tool: ${CMAKE_INSTALL_NAME_TOOL}") +if(DEFINED APPLE_TARGET_TRIPLE) + message(STATUS "Autoconf target triple: ${APPLE_TARGET_TRIPLE}") +endif() +message(STATUS "Using minimum deployment version: ${DEPLOYMENT_TARGET}" + " (SDK version: ${SDK_VERSION})") +if(MODERN_CMAKE) + message(STATUS "Merging integrated CMake 3.14+ iOS,tvOS,watchOS,macOS toolchain(s) with this toolchain!") + if(PLATFORM_INT MATCHES ".*COMBINED") + message(STATUS "Will combine built (static) artifacts into FAT lib...") + endif() +endif() +if(CMAKE_GENERATOR MATCHES "Xcode") + message(STATUS "Using Xcode version: ${XCODE_VERSION_INT}") +endif() +message(STATUS "CMake version: ${CMAKE_VERSION}") +if(DEFINED SDK_NAME_VERSION_FLAGS) + message(STATUS "Using version flags: ${SDK_NAME_VERSION_FLAGS}") +endif() +message(STATUS "Using a data_ptr size of: ${CMAKE_CXX_SIZEOF_DATA_PTR}") +if(ENABLE_BITCODE_INT) + message(STATUS "Bitcode: Enabled") +else() + message(STATUS "Bitcode: Disabled") +endif() + +if(ENABLE_ARC_INT) + message(STATUS "ARC: Enabled") +else() + message(STATUS "ARC: Disabled") +endif() + +if(ENABLE_VISIBILITY_INT) + message(STATUS "Hiding symbols: Disabled") +else() + message(STATUS "Hiding symbols: Enabled") +endif() + +# Set global properties +set_property(GLOBAL PROPERTY PLATFORM "${PLATFORM}") +set_property(GLOBAL PROPERTY APPLE_TARGET_TRIPLE "${APPLE_TARGET_TRIPLE_INT}") +set_property(GLOBAL PROPERTY SDK_VERSION "${SDK_VERSION}") +set_property(GLOBAL PROPERTY XCODE_VERSION "${XCODE_VERSION_INT}") +set_property(GLOBAL PROPERTY OSX_ARCHITECTURES "${CMAKE_OSX_ARCHITECTURES}") + +# Export configurable variables for the try_compile() command. +set(CMAKE_TRY_COMPILE_PLATFORM_VARIABLES + PLATFORM + XCODE_VERSION_INT + SDK_VERSION + NAMED_LANGUAGE_SUPPORT + DEPLOYMENT_TARGET + CMAKE_DEVELOPER_ROOT + CMAKE_OSX_SYSROOT_INT + ENABLE_BITCODE + ENABLE_ARC + CMAKE_ASM_COMPILER + CMAKE_C_COMPILER + CMAKE_C_COMPILER_TARGET + CMAKE_CXX_COMPILER + CMAKE_CXX_COMPILER_TARGET + BUILD_LIBTOOL + CMAKE_INSTALL_NAME_TOOL + CMAKE_C_FLAGS + CMAKE_C_DEBUG + CMAKE_C_MINSIZEREL + CMAKE_C_RELWITHDEBINFO + CMAKE_C_RELEASE + CMAKE_CXX_FLAGS + CMAKE_CXX_FLAGS_DEBUG + CMAKE_CXX_FLAGS_MINSIZEREL + CMAKE_CXX_FLAGS_RELWITHDEBINFO + CMAKE_CXX_FLAGS_RELEASE + CMAKE_C_LINK_FLAGS + CMAKE_CXX_LINK_FLAGS + CMAKE_ASM_FLAGS +) + +if(NAMED_LANGUAGE_SUPPORT_INT) + list(APPEND CMAKE_TRY_COMPILE_PLATFORM_VARIABLES + CMAKE_OBJC_FLAGS + CMAKE_OBJC_DEBUG + CMAKE_OBJC_MINSIZEREL + CMAKE_OBJC_RELWITHDEBINFO + CMAKE_OBJC_RELEASE + CMAKE_OBJCXX_FLAGS + CMAKE_OBJCXX_DEBUG + CMAKE_OBJCXX_MINSIZEREL + CMAKE_OBJCXX_RELWITHDEBINFO + CMAKE_OBJCXX_RELEASE + CMAKE_OBJC_LINK_FLAGS + CMAKE_OBJCXX_LINK_FLAGS + ) +endif() + +set(CMAKE_PLATFORM_HAS_INSTALLNAME 1) +set(CMAKE_SHARED_LINKER_FLAGS "-rpath @executable_path/Frameworks -rpath @loader_path/Frameworks") +set(CMAKE_SHARED_LIBRARY_CREATE_C_FLAGS "-dynamiclib -Wl,-headerpad_max_install_names") +set(CMAKE_SHARED_MODULE_CREATE_C_FLAGS "-bundle -Wl,-headerpad_max_install_names") +set(CMAKE_SHARED_MODULE_LOADER_C_FLAG "-Wl,-bundle_loader,") +set(CMAKE_SHARED_MODULE_LOADER_CXX_FLAG "-Wl,-bundle_loader,") +set(CMAKE_FIND_LIBRARY_SUFFIXES ".tbd" ".dylib" ".so" ".a") +set(CMAKE_SHARED_LIBRARY_SONAME_C_FLAG "-install_name") + +# Set the find root to the SDK developer roots. +# Note: CMAKE_FIND_ROOT_PATH is only useful when cross-compiling. Thus, do not set on macOS builds. +if(NOT PLATFORM_INT MATCHES "^MAC.*$") + list(APPEND CMAKE_FIND_ROOT_PATH "${CMAKE_OSX_SYSROOT_INT}" CACHE INTERNAL "") + set(CMAKE_IGNORE_PATH "/System/Library/Frameworks;/usr/local/lib;/opt/homebrew" CACHE INTERNAL "") +endif() + +# Default to searching for frameworks first. +IF(NOT DEFINED CMAKE_FIND_FRAMEWORK) + set(CMAKE_FIND_FRAMEWORK FIRST) +ENDIF(NOT DEFINED CMAKE_FIND_FRAMEWORK) + +# Set up the default search directories for frameworks. +if(PLATFORM_INT MATCHES "^MAC_CATALYST") + set(CMAKE_FRAMEWORK_PATH + ${CMAKE_DEVELOPER_ROOT}/Library/PrivateFrameworks + ${CMAKE_OSX_SYSROOT_INT}/System/Library/Frameworks + ${CMAKE_OSX_SYSROOT_INT}/System/iOSSupport/System/Library/Frameworks + ${CMAKE_FRAMEWORK_PATH} CACHE INTERNAL "") +else() + set(CMAKE_FRAMEWORK_PATH + ${CMAKE_DEVELOPER_ROOT}/Library/PrivateFrameworks + ${CMAKE_OSX_SYSROOT_INT}/System/Library/Frameworks + ${CMAKE_FRAMEWORK_PATH} CACHE INTERNAL "") +endif() + +# By default, search both the specified iOS SDK and the remainder of the host filesystem. +if(NOT CMAKE_FIND_ROOT_PATH_MODE_PROGRAM) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM BOTH CACHE INTERNAL "") +endif() +if(NOT CMAKE_FIND_ROOT_PATH_MODE_LIBRARY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY BOTH CACHE INTERNAL "") +endif() +if(NOT CMAKE_FIND_ROOT_PATH_MODE_INCLUDE) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE BOTH CACHE INTERNAL "") +endif() +if(NOT CMAKE_FIND_ROOT_PATH_MODE_PACKAGE) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE BOTH CACHE INTERNAL "") +endif() + +# +# Some helper-macros below to simplify and beautify the CMakeFile +# + +# This little macro lets you set any Xcode specific property. +macro(set_xcode_property TARGET XCODE_PROPERTY XCODE_VALUE XCODE_RELVERSION) + set(XCODE_RELVERSION_I "${XCODE_RELVERSION}") + if(XCODE_RELVERSION_I STREQUAL "All") + set_property(TARGET ${TARGET} PROPERTY XCODE_ATTRIBUTE_${XCODE_PROPERTY} "${XCODE_VALUE}") + else() + set_property(TARGET ${TARGET} PROPERTY XCODE_ATTRIBUTE_${XCODE_PROPERTY}[variant=${XCODE_RELVERSION_I}] "${XCODE_VALUE}") + endif() +endmacro(set_xcode_property) + +# This macro lets you find executable programs on the host system. +macro(find_host_package) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE NEVER) + set(_TOOLCHAIN_IOS ${IOS}) + set(IOS OFF) + find_package(${ARGN}) + set(IOS ${_TOOLCHAIN_IOS}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM BOTH) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY BOTH) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE BOTH) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE BOTH) +endmacro(find_host_package) diff --git a/libs/JSystem/src/JGadget/linklist.cpp b/libs/JSystem/src/JGadget/linklist.cpp index 366a8db320..b9593c31c6 100644 --- a/libs/JSystem/src/JGadget/linklist.cpp +++ b/libs/JSystem/src/JGadget/linklist.cpp @@ -8,7 +8,7 @@ namespace { template class TPRIsEqual_pointer_ { public: - TPRIsEqual_pointer_(const T* p) { this->p_ = p; } + TPRIsEqual_pointer_(const T* p) { this->p_ = p; } bool operator()(const T& rSrc) const { return &rSrc == this->p_; } diff --git a/libs/revolution/src/homebuttonLib/nw4hbm/math/triangular.h b/libs/revolution/src/homebuttonLib/nw4hbm/math/triangular.h index 27be799ff6..d4c45733c6 100644 --- a/libs/revolution/src/homebuttonLib/nw4hbm/math/triangular.h +++ b/libs/revolution/src/homebuttonLib/nw4hbm/math/triangular.h @@ -19,7 +19,7 @@ namespace nw4hbm { inline f32 TanFIdx(f32 fidx) { // They were just like "Ah fuck it we already got too many tables" haha lol - return std::tanf(fidx * convert::FIdx2Rad); + return tanf(fidx * convert::FIdx2Rad); } inline f32 CosRad(f32 rad) { diff --git a/platforms/freedesktop/1024x1024/apps/dusk.png b/platforms/freedesktop/1024x1024/apps/dusk.png new file mode 100644 index 0000000000000000000000000000000000000000..862cbed0d868c0e200ed0462fcf3ee742a78ec4a GIT binary patch literal 5337 zcmeAS@N?(olHy`uVBq!ia0y~yU;#2&7&w@K)Q9>#R~Q&X7I?ZihE&XXd)<+jK|$ca zhT?YqCkutdKSWICx>3IQIwJ#v(z9wH`Jo3$Gzc&Qbt`^{`0L{;vT1O^s^r>mdKI;Vst03pj6{Qv*} literal 0 HcmV?d00001 diff --git a/platforms/freedesktop/128x128/apps/dusk.png b/platforms/freedesktop/128x128/apps/dusk.png new file mode 100644 index 0000000000000000000000000000000000000000..aaf38689d2a613c4205cfae571fbac7b14c68dc8 GIT binary patch literal 306 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1SEZ8zRdwrKRsO>Ln`LHz39lxpultBz|O<$ zp8`62Y^yw3O?K{lp1Cp7zJr~?!J2{L79+z2K8A)e28J!n3=3o#7~V24WDH#?yNfPI V-d62seFF>x22WQ%mvv4FO#lppPOty~ literal 0 HcmV?d00001 diff --git a/platforms/freedesktop/16x16/apps/dusk.png b/platforms/freedesktop/16x16/apps/dusk.png new file mode 100644 index 0000000000000000000000000000000000000000..21b653c63d722867d59d0dd9d7965f2a080ffb60 GIT binary patch literal 79 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61SBU+%rFB|VxBIJAr*6yJ&NL8g_0z$8mvnE bk;TC9F)LW0gq!gZPz8ghtDnm{r-UW|Z51D-C9Ar*7pUN+=qFyJ|` zVUu+KLFrQmUkG}$O;9gB-@GPLzrmGF;Vi=v$p#bV326+McpPRidKfcgjtY(j$bct? aTkP6=!uK}DTv`B3(hQ!ielF{r5}E+eUfG@i literal 0 HcmV?d00001 diff --git a/platforms/freedesktop/32x32/apps/dusk.png b/platforms/freedesktop/32x32/apps/dusk.png new file mode 100644 index 0000000000000000000000000000000000000000..b879a01602c4147f6179c51640912843d252e47d GIT binary patch literal 99 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1SJ1Ryj={Wv^-rLLn`JZdlbdH1|KoVI=F&$ hu~<(OI)g`)o1yozk577R%2J>%22WQ%mvv4FO#r*E8}mdKI;Vst02xgt@c;k- literal 0 HcmV?d00001 diff --git a/platforms/freedesktop/512x512/apps/dusk.png b/platforms/freedesktop/512x512/apps/dusk.png new file mode 100644 index 0000000000000000000000000000000000000000..6334f80e8137c23e6df85085238746b8ca48e734 GIT binary patch literal 1881 zcmeAS@N?(olHy`uVBq!ia0y~yU;;9k7&t&wwUqN(1_pKoPZ!6KiaBqu8uBt2@Eq8% zNxJ`_^r?d{1ijfN=og=7R>-)<%@C2uz!1pBAdosrjRwJJDj3ZOqh-NpaX2Dsgs+TI X5B*E`H4B*mt4sz@S3j3^P63kcv5P&j)fcD2N<5aQ&Rz zG2^9*e=J%KGTljA|GD%qD{qD)bJT?fF_Q&UWZIN5F8OF^b81mc0nil;p00i_>zopr E07lI>H2?qr literal 0 HcmV?d00001 diff --git a/platforms/freedesktop/dusk.desktop b/platforms/freedesktop/dusk.desktop new file mode 100644 index 0000000000..2e19d7ea26 --- /dev/null +++ b/platforms/freedesktop/dusk.desktop @@ -0,0 +1,9 @@ +[Desktop Entry] +Name=Dusk +GenericName=Dusk +Comment=The Legend of Zelda: Twilight Princess +Exec=dusk +Icon=dusk +Terminal=false +Type=Application +Categories=Graphics;3DGraphics;Game diff --git a/platforms/ios/Base.lproj/LaunchScreen.storyboardc/01J-lp-oVM-view-Ze5-6b-2t3.nib b/platforms/ios/Base.lproj/LaunchScreen.storyboardc/01J-lp-oVM-view-Ze5-6b-2t3.nib new file mode 100644 index 0000000000000000000000000000000000000000..15a4af7811dad90f23cea3ad667fa031a6330c9c GIT binary patch literal 1186 zcmZuw+iuf95M4?Fskw?zK;jhvKR}bTAdBEsHEEyM_B37U*kO0wV#GsSr%l?X>HP|A zDVGNj5`0p>f@fwoL2X4N&8#y!<2iHA*fI-w?oP28xIzdq(C(r=LA#IUpgl(WGeF27 zo)0pFG#ag>)tp;S(oQnBlBC_Fxw<&Nw~!ts$?^Qc*ZK5&lKhx!{Y=weN%A|{shAdX z#+hJ_4-+i#!ddYG;x9U!cU=&8!+a*=Vi-)aYQ(8zVW4u_6|)dVfhXFQ{m_Xeh%H?; z61brUe56*FF{ZUFdt!=7*sYi)8i$d59x)G04+6<18St)Un}N>)@W!bh!RLxO&S17g zc@?mw#57xm9K2B;)`3d~Vq3StSL=dN%6K16xlZ3ifGF~jDiQ1**pV}XuM;f{`t>P_ z=Ot*$vjGdLs_#n090nm-pvU}n0dwB>ksS-FcsB#XtOkKlT;OEL;j6G-f*J73&TDX` zLUeqg`mBd3N})Rq-lnd@Apimd-G&-PRop)Bc_}$_A_)kpK;7vo zS8{7$T}2brhdG@cL+~BT)?vF&T|jyCpJG_6hXFz_Vwv1HrwVfYl8R~6qC_3=SM@w+ zysaB*+fNL&)0@Uk^bO+<`b7I(?H9FgWt#>rKWQ4c`slBr-#4zJPjel!@QnE&kaytw zMpg&cRFDff1_H6+md@o)uH()BztecA7wime=hk~Ur>q})KlH&+p^w?6>~i*)+&dto zJ-B7$Fm^-+Rum4>LEm^fpGzJcl1!Rga(v`aGjkd2rlCIX?%;xP1J8}jUSYJ8G$!3fPwcpVqmpctGpLdlUa3_l?L^`2 ftEDT>eW2t3)5cIZT=Q}yeLxxiEvoLLCn0|U5M6S{yH+73vvWl3!Grl%H6X;*wgNnVy$elA7WQ)K(dgpP5&J&=)TtjI1~eXi{c= z-bGfncmZj2+jI>Ky>xR5bo0Y}DUlUmkiI}@Gn>+KKQ-9w!p>HM?S$6fs;?|selQ{DTX=RdUmsRx1Q zi>8cVH}K{;P16e9fPUbWX$ZUsyah-VOQ>QZISRZA3s*&>44c5K;X8;_`UAQSrc*Hw!7OG4Hdw910UEU6y^URAO>7Ubv6 ze6-guCXt9iE^ahbhL?Y{!j})?cPf8Wv076quY~ z`#6>11{MEtm?=n)t+*;FQ@#?C)}hQ&JLK3Z7eiy_vJ_hk;xo>RAyI6NfgIXQ^NicX z*f%D(5ckM!#4a%g?pPdI+_l)78j{PX49QiC*DSh-6N2tKg}dj?Ein#hwUvOJkSR+} z3@KB!0%=1e_&gJ07NT&`D${=!3M%7eP9sEWyZjv)b$?lNf gqKyVfr$B9iSyP4H*rrQSpWb4 literal 0 HcmV?d00001 diff --git a/platforms/ios/Info.plist.in b/platforms/ios/Info.plist.in new file mode 100644 index 0000000000..54dfc5179a --- /dev/null +++ b/platforms/ios/Info.plist.in @@ -0,0 +1,79 @@ + + + + + CFBundleDevelopmentRegion + en-US + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + APPL + CFBundleSignature + ???? + CFBundleExecutable + ${MACOSX_BUNDLE_EXECUTABLE_NAME} + CFBundleIdentifier + ${MACOSX_BUNDLE_GUI_IDENTIFIER} + CFBundleName + ${MACOSX_BUNDLE_BUNDLE_NAME} + CFBundleDisplayName + ${MACOSX_BUNDLE_BUNDLE_NAME} + CFBundleVersion + ${MACOSX_BUNDLE_BUNDLE_VERSION} + CFBundleShortVersionString + ${MACOSX_BUNDLE_SHORT_VERSION_STRING} + CFBundleSupportedPlatforms + + iPhoneOS + + CFBundleIcons + + CFBundlePrimaryIcon + + CFBundleIconFiles + + AppIcon60x60 + + CFBundleIconName + AppIcon + + + CFBundleIcons~ipad + + CFBundlePrimaryIcon + + CFBundleIconFiles + + AppIcon60x60 + AppIcon76x76 + + CFBundleIconName + AppIcon + + + MinimumOSVersion + ${DEPLOYMENT_TARGET} + LSRequiresIPhoneOS + + LSApplicationCategoryType + public.app-category.adventure-games + UILaunchStoryboardName + LaunchScreen + UIRequiresFullScreen + + UIStatusBarHidden + + UIStatusBarStyle + UIStatusBarStyleDarkContent + UISupportedInterfaceOrientations + + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIDeviceFamily + + 1 + 2 + + + diff --git a/platforms/macos/Info.plist.in b/platforms/macos/Info.plist.in new file mode 100644 index 0000000000..c91435dc2e --- /dev/null +++ b/platforms/macos/Info.plist.in @@ -0,0 +1,30 @@ + + + + + CFBundleDevelopmentRegion + en-US + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + APPL + CFBundleSignature + ???? + CFBundleExecutable + ${MACOSX_BUNDLE_EXECUTABLE_NAME} + CFBundleIconFile + mainicon.icns + CFBundleIdentifier + ${MACOSX_BUNDLE_GUI_IDENTIFIER} + CFBundleName + ${MACOSX_BUNDLE_BUNDLE_NAME} + CFBundleDisplayName + ${MACOSX_BUNDLE_BUNDLE_NAME} + CFBundleVersion + ${MACOSX_BUNDLE_BUNDLE_VERSION} + CFBundleShortVersionString + ${MACOSX_BUNDLE_SHORT_VERSION_STRING} + NSHighResolutionCapable + + + diff --git a/platforms/tvos/Base.lproj/LaunchScreen.storyboardc/BYZ-38-t0r-view-8bC-Xf-vdC.nib b/platforms/tvos/Base.lproj/LaunchScreen.storyboardc/BYZ-38-t0r-view-8bC-Xf-vdC.nib new file mode 100644 index 0000000000000000000000000000000000000000..ff0fd7b09adb5a276caf5e2a3d32633a467d89a7 GIT binary patch literal 2001 zcmZuy%X8aA7~j?wako6n`~CO>pwoYV*hvc-QY90`>5-AGt+Pbd8fleaPL^#&vg0_< zHciu{4J9;`rZXIv;l_b0!vzl9V1@%ToZ!|Qe7kaDJBerZ)B1bu?|baGnwm*F`6)U> zP9cOatao5tfb|}%Em$pB=aUFM0pCZDAylb^eps*0`?Z!|UGQsdzrNTEHfnRX8o_Qg zycg6S`2K!9`ZkQd52GLIb3ZnMpQ_={LG2gc|Fs_d7Dm6U;@ee#|64fL&r&lR)+M2G~7PW~vX;>av)pVWYnQf*ycWBPzWb(o_=@)!SjWL5d z9VnwE<5Q39eB9?6FuU9_sLkC8HcpCch5)d%$C*Q1`Z+m(9RkxTO&Rc&03oywsR_9H zk&e#V)Gkaypevew36ADH$iB;}LNd&Vt1&*|7`AH}oE#nomX{58TD3XrfwkL*H*Hyv ze~lC%$une_7K!bOY%gThVd8RX%j5$nsM!Q7jgT2)jpjZfc`mYWyU$k_tm=-i{4_C_ zp-yF&lj0Dwn4=j*vMN0R92;D8%Q^%FAr2%#XP%QM8}WINN3KKWi~<>wwY>l2AZO1a56b)#UJV-bIJWM@=l`-uBqcfO;eLYASKMvNS>7G?UnWrE&08aVjKfL{f=f!_dH!EXcg zCA}qSQ_@HY@M(|)7={N>7#(OECxF&dEj4qATEvAmAkH>cBy&kHp>4;B3xUDStfqDb zKyt!wdr&g}SQ@;UTJGqyJG{zZ4GY#6_$8osm3d`B`5K+OgI|HaI|vC}DcA=1>5hB} zG**>6_)M&@i_gNQg&zkhl*LfIFHKg^z#badSH3*;SE8O;gK~*WB}(ov%OVbRy)Y1y zGQdwt`c#jxvWJw1G7k-;Y$0V|IdGfULx}@PVhbg@lGGkb?JFXR1C-BzM+jpVKMR`{ zeh#QO^qf3Mgw&66{TRv&=rXb%PWHSpY9!{sv} zejRx0$=k^_IHn@Z-UP-s_${ED(#182xt^+vryx=q6-|%39!0QCJC0FC?Mgf#${mSN z(qX#W1r&lv6dNr9@3O}Yc#&eGMPKxH|J6mCzG$&SrtXzW%z>MFF!9kUxQU;c9%kf= ip6lFiiKgczLN@`zI9zoYyrUksh;Y{#jWPzASM)z>7KZr% literal 0 HcmV?d00001 diff --git a/platforms/tvos/Base.lproj/LaunchScreen.storyboardc/Info.plist b/platforms/tvos/Base.lproj/LaunchScreen.storyboardc/Info.plist new file mode 100644 index 0000000000000000000000000000000000000000..9a41f2cb91b33c655127099449c0f7903f304b2b GIT binary patch literal 258 zcmYc)$jK}&F)+By$i&RT&Ji!58tNIAnOg3gpI1_ppOceXM6S{yH+73vvWl3!Grl%H6X;*wgNnVy$elA7WQ)K(dgpP5&J&=)TtjI1~eXi{c= z-bGfncmZj2+jN~GqjZfebW041D3KLl>b literal 0 HcmV?d00001 diff --git a/platforms/tvos/Base.lproj/LaunchScreen.storyboardc/UIViewController-BYZ-38-t0r.nib b/platforms/tvos/Base.lproj/LaunchScreen.storyboardc/UIViewController-BYZ-38-t0r.nib new file mode 100644 index 0000000000000000000000000000000000000000..f2fed880d85e9b815e8f447ef676cd1f07c67511 GIT binary patch literal 896 zcmZ`%+iuf95H**ASuW-F6-Yq{Ui#Xmtz;1;Rb7|M+uEL{3tMlr-X!%~Hg0Zhoum|6 z+6N>=k>E@CEoQgSJ|G_Ecy-QrXJ*e-y@h!hZL$_zaU6$WJi>U6@et!Z#uJQRDBi^H z<7x}VW)Sb^JATV*7~HUzvTU|d48t*-}C&3)<5+y z@O;sf5$pwCnbS0_(k&PSUWJChi@;liRI!9ACX(a8tFmxSG%C;nUJKtrq|%p=9a(;z zr7Yy|iY2jV8+u&zy@ay_;&sZKa29w=+X`et|Ngp0F@IUebq4KBH3W|rnJ4920qywJlOgxlu5&437i6x z6Ko%+GTgB0KMgYl>4_EBBxTB1Lee@^SZarySmknP%v_OTn?Zcec`+o4Z7`4{n`xeL zhZy@NRV~cwh`!geQ6_pXWZt;di7ja6^U8ivOy@h4QA+5C&kW(^a z$*Cb_s#YOwhyX|IH(2~+zST4QZD7E5p;Z6h3*o4ofq~ILn|2sCyJ-m5D?jv@|EMkY4S#o0L zD%{=lqPH%2U}ZCWZj%c4FpVIAf68^L{`pfoLp;t)Ni%2MIkzGEql{4TmBdYQCMek* i^4ql00O=H{Ei&slm6>yj`f3wB+k#qCaZr~l;rs@i(jtif literal 0 HcmV?d00001 diff --git a/platforms/tvos/Info.plist.in b/platforms/tvos/Info.plist.in new file mode 100644 index 0000000000..841d120cc2 --- /dev/null +++ b/platforms/tvos/Info.plist.in @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + en-US + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + APPL + CFBundleSignature + ???? + CFBundleExecutable + ${MACOSX_BUNDLE_EXECUTABLE_NAME} + CFBundleIdentifier + ${MACOSX_BUNDLE_GUI_IDENTIFIER} + CFBundleName + ${MACOSX_BUNDLE_BUNDLE_NAME} + CFBundleDisplayName + ${MACOSX_BUNDLE_BUNDLE_NAME} + CFBundleVersion + ${MACOSX_BUNDLE_BUNDLE_VERSION} + CFBundleShortVersionString + ${MACOSX_BUNDLE_SHORT_VERSION_STRING} + CFBundleSupportedPlatforms + + AppleTVOS + + CFBundleIcons + + CFBundlePrimaryIcon + App Icon + + MinimumOSVersion + ${DEPLOYMENT_TARGET} + LSRequiresIPhoneOS + + LSApplicationCategoryType + public.app-category.adventure-games + NSHumanReadableCopyright + ${MACOSX_BUNDLE_COPYRIGHT} + NSHighResolutionCapable + + UILaunchStoryboardName + LaunchScreen + UIUserInterfaceStyle + Automatic + + diff --git a/src/d/actor/d_a_b_bq.cpp b/src/d/actor/d_a_b_bq.cpp index f109c11509..70ab84705b 100644 --- a/src/d/actor/d_a_b_bq.cpp +++ b/src/d/actor/d_a_b_bq.cpp @@ -1444,11 +1444,11 @@ static void demo_camera(b_bq_class* i_this) { i_this->mDemoCamEyeTarget.set(240.0f, 274.0f, 2075.0f); i_this->field_0x1288.x = - std::fabsf(i_this->mDemoCamCenterTarget.x - i_this->mDemoCamCenter.x); + fabsf(i_this->mDemoCamCenterTarget.x - i_this->mDemoCamCenter.x); i_this->field_0x1288.y = - std::fabsf(i_this->mDemoCamCenterTarget.y - i_this->mDemoCamCenter.y); + fabsf(i_this->mDemoCamCenterTarget.y - i_this->mDemoCamCenter.y); i_this->field_0x1288.z = - std::fabsf(i_this->mDemoCamCenterTarget.z - i_this->mDemoCamCenter.z); + fabsf(i_this->mDemoCamCenterTarget.z - i_this->mDemoCamCenter.z); } spFC.set(0.0f, 0.0f, 1700.0f); @@ -1478,16 +1478,16 @@ static void demo_camera(b_bq_class* i_this) { i_this->mDemoCamCenterTarget.set(76.0f, 204.0f, 1782.0f); i_this->mDemoCamEyeTarget.set(-41.0f, 261.0f, 2095.0f); - i_this->field_0x127c.x = std::fabsf(i_this->mDemoCamEyeTarget.x - i_this->mDemoCamEye.x); - i_this->field_0x127c.y = std::fabsf(i_this->mDemoCamEyeTarget.y - i_this->mDemoCamEye.y); - i_this->field_0x127c.z = std::fabsf(i_this->mDemoCamEyeTarget.z - i_this->mDemoCamEye.z); + i_this->field_0x127c.x = fabsf(i_this->mDemoCamEyeTarget.x - i_this->mDemoCamEye.x); + i_this->field_0x127c.y = fabsf(i_this->mDemoCamEyeTarget.y - i_this->mDemoCamEye.y); + i_this->field_0x127c.z = fabsf(i_this->mDemoCamEyeTarget.z - i_this->mDemoCamEye.z); i_this->field_0x1288.x = - std::fabsf(i_this->mDemoCamCenterTarget.x - i_this->mDemoCamCenter.x); + fabsf(i_this->mDemoCamCenterTarget.x - i_this->mDemoCamCenter.x); i_this->field_0x1288.y = - std::fabsf(i_this->mDemoCamCenterTarget.y - i_this->mDemoCamCenter.y); + fabsf(i_this->mDemoCamCenterTarget.y - i_this->mDemoCamCenter.y); i_this->field_0x1288.z = - std::fabsf(i_this->mDemoCamCenterTarget.z - i_this->mDemoCamCenter.z); + fabsf(i_this->mDemoCamCenterTarget.z - i_this->mDemoCamCenter.z); i_this->field_0x129c = 0.0f; i_this->mDemoMode = 12; @@ -1584,16 +1584,16 @@ static void demo_camera(b_bq_class* i_this) { i_this->mDemoCamCenterTarget.set(0.0f, 278.0f, 1252.0f); i_this->mDemoCamEyeTarget.set(0.0f, 86.0f, 2167.0f); - i_this->field_0x127c.x = std::fabsf(i_this->mDemoCamEyeTarget.x - i_this->mDemoCamEye.x); - i_this->field_0x127c.y = std::fabsf(i_this->mDemoCamEyeTarget.y - i_this->mDemoCamEye.y); - i_this->field_0x127c.z = std::fabsf(i_this->mDemoCamEyeTarget.z - i_this->mDemoCamEye.z); + i_this->field_0x127c.x = fabsf(i_this->mDemoCamEyeTarget.x - i_this->mDemoCamEye.x); + i_this->field_0x127c.y = fabsf(i_this->mDemoCamEyeTarget.y - i_this->mDemoCamEye.y); + i_this->field_0x127c.z = fabsf(i_this->mDemoCamEyeTarget.z - i_this->mDemoCamEye.z); i_this->field_0x1288.x = - std::fabsf(i_this->mDemoCamCenterTarget.x - i_this->mDemoCamCenter.x); + fabsf(i_this->mDemoCamCenterTarget.x - i_this->mDemoCamCenter.x); i_this->field_0x1288.y = - std::fabsf(i_this->mDemoCamCenterTarget.y - i_this->mDemoCamCenter.y); + fabsf(i_this->mDemoCamCenterTarget.y - i_this->mDemoCamCenter.y); i_this->field_0x1288.z = - std::fabsf(i_this->mDemoCamCenterTarget.z - i_this->mDemoCamCenter.z); + fabsf(i_this->mDemoCamCenterTarget.z - i_this->mDemoCamCenter.z); i_this->field_0x129c = 0.0f; i_this->mDemoMode = 14; @@ -1741,16 +1741,16 @@ static void demo_camera(b_bq_class* i_this) { i_this->mDemoCamCenterTarget.set(-2243.0f, 1340.0f, 977.0f); i_this->mDemoCamEyeTarget.set(-1226.0f, 980.0f, 1350.0f); - i_this->field_0x127c.x = std::fabsf(i_this->mDemoCamEyeTarget.x - i_this->mDemoCamEye.x); - i_this->field_0x127c.y = std::fabsf(i_this->mDemoCamEyeTarget.y - i_this->mDemoCamEye.y); - i_this->field_0x127c.z = std::fabsf(i_this->mDemoCamEyeTarget.z - i_this->mDemoCamEye.z); + i_this->field_0x127c.x = fabsf(i_this->mDemoCamEyeTarget.x - i_this->mDemoCamEye.x); + i_this->field_0x127c.y = fabsf(i_this->mDemoCamEyeTarget.y - i_this->mDemoCamEye.y); + i_this->field_0x127c.z = fabsf(i_this->mDemoCamEyeTarget.z - i_this->mDemoCamEye.z); i_this->field_0x1288.x = - std::fabsf(i_this->mDemoCamCenterTarget.x - i_this->mDemoCamCenter.x); + fabsf(i_this->mDemoCamCenterTarget.x - i_this->mDemoCamCenter.x); i_this->field_0x1288.y = - std::fabsf(i_this->mDemoCamCenterTarget.y - i_this->mDemoCamCenter.y); + fabsf(i_this->mDemoCamCenterTarget.y - i_this->mDemoCamCenter.y); i_this->field_0x1288.z = - std::fabsf(i_this->mDemoCamCenterTarget.z - i_this->mDemoCamCenter.z); + fabsf(i_this->mDemoCamCenterTarget.z - i_this->mDemoCamCenter.z); i_this->field_0x129c = 0.0f; i_this->mDemoMode = 34; @@ -1814,9 +1814,9 @@ static void demo_camera(b_bq_class* i_this) { i_this->mDemoCamEye.set(95.0f, 50.0f, 2800.0f); i_this->mDemoCamEyeTarget.set(72.0f, 52.0f, 2153.0f); - i_this->field_0x127c.x = std::fabsf(i_this->mDemoCamEyeTarget.x - i_this->mDemoCamEye.x); - i_this->field_0x127c.y = std::fabsf(i_this->mDemoCamEyeTarget.y - i_this->mDemoCamEye.y); - i_this->field_0x127c.z = std::fabsf(i_this->mDemoCamEyeTarget.z - i_this->mDemoCamEye.z); + i_this->field_0x127c.x = fabsf(i_this->mDemoCamEyeTarget.x - i_this->mDemoCamEye.x); + i_this->field_0x127c.y = fabsf(i_this->mDemoCamEyeTarget.y - i_this->mDemoCamEye.y); + i_this->field_0x127c.z = fabsf(i_this->mDemoCamEyeTarget.z - i_this->mDemoCamEye.z); i_this->field_0x1288.set(0.0f, 0.0f, 0.0f); i_this->field_0x129c = 0.0f; @@ -1973,18 +1973,18 @@ static void demo_camera(b_bq_class* i_this) { i_this->mDemoCamEyeTarget.set(1214.0f, 350.0f, 2696.0f); i_this->field_0x127c.x = - std::fabsf(i_this->mDemoCamEyeTarget.x - i_this->mDemoCamEye.x); + fabsf(i_this->mDemoCamEyeTarget.x - i_this->mDemoCamEye.x); i_this->field_0x127c.y = - std::fabsf(i_this->mDemoCamEyeTarget.y - i_this->mDemoCamEye.y); + fabsf(i_this->mDemoCamEyeTarget.y - i_this->mDemoCamEye.y); i_this->field_0x127c.z = - std::fabsf(i_this->mDemoCamEyeTarget.z - i_this->mDemoCamEye.z); + fabsf(i_this->mDemoCamEyeTarget.z - i_this->mDemoCamEye.z); i_this->field_0x1288.x = - std::fabsf(i_this->mDemoCamCenterTarget.x - i_this->mDemoCamCenter.x); + fabsf(i_this->mDemoCamCenterTarget.x - i_this->mDemoCamCenter.x); i_this->field_0x1288.y = - std::fabsf(i_this->mDemoCamCenterTarget.y - i_this->mDemoCamCenter.y); + fabsf(i_this->mDemoCamCenterTarget.y - i_this->mDemoCamCenter.y); i_this->field_0x1288.z = - std::fabsf(i_this->mDemoCamCenterTarget.z - i_this->mDemoCamCenter.z); + fabsf(i_this->mDemoCamCenterTarget.z - i_this->mDemoCamCenter.z); i_this->field_0x129c = 0.0f; } else if (i_this->mDemoModeTimer < 140) { @@ -1993,18 +1993,18 @@ static void demo_camera(b_bq_class* i_this) { i_this->mDemoCamEyeTarget.set(23.0f, 108.0f, 2155.0f); i_this->field_0x127c.x = - std::fabsf(i_this->mDemoCamEyeTarget.x - i_this->mDemoCamEye.x); + fabsf(i_this->mDemoCamEyeTarget.x - i_this->mDemoCamEye.x); i_this->field_0x127c.y = - std::fabsf(i_this->mDemoCamEyeTarget.y - i_this->mDemoCamEye.y); + fabsf(i_this->mDemoCamEyeTarget.y - i_this->mDemoCamEye.y); i_this->field_0x127c.z = - std::fabsf(i_this->mDemoCamEyeTarget.z - i_this->mDemoCamEye.z); + fabsf(i_this->mDemoCamEyeTarget.z - i_this->mDemoCamEye.z); i_this->field_0x1288.x = - std::fabsf(i_this->mDemoCamCenterTarget.x - i_this->mDemoCamCenter.x); + fabsf(i_this->mDemoCamCenterTarget.x - i_this->mDemoCamCenter.x); i_this->field_0x1288.y = - std::fabsf(i_this->mDemoCamCenterTarget.y - i_this->mDemoCamCenter.y); + fabsf(i_this->mDemoCamCenterTarget.y - i_this->mDemoCamCenter.y); i_this->field_0x1288.z = - std::fabsf(i_this->mDemoCamCenterTarget.z - i_this->mDemoCamCenter.z); + fabsf(i_this->mDemoCamCenterTarget.z - i_this->mDemoCamCenter.z); } cam_3d_morf(i_this, 0.1f); @@ -2042,18 +2042,18 @@ static void demo_camera(b_bq_class* i_this) { i_this->mDemoCamEyeTarget.set(953.0f, 997.0f, -36.0f); i_this->field_0x127c.x = - std::fabsf(i_this->mDemoCamEyeTarget.x - i_this->mDemoCamEye.x); + fabsf(i_this->mDemoCamEyeTarget.x - i_this->mDemoCamEye.x); i_this->field_0x127c.y = - std::fabsf(i_this->mDemoCamEyeTarget.y - i_this->mDemoCamEye.y); + fabsf(i_this->mDemoCamEyeTarget.y - i_this->mDemoCamEye.y); i_this->field_0x127c.z = - std::fabsf(i_this->mDemoCamEyeTarget.z - i_this->mDemoCamEye.z); + fabsf(i_this->mDemoCamEyeTarget.z - i_this->mDemoCamEye.z); i_this->field_0x1288.x = - std::fabsf(i_this->mDemoCamCenterTarget.x - i_this->mDemoCamCenter.x); + fabsf(i_this->mDemoCamCenterTarget.x - i_this->mDemoCamCenter.x); i_this->field_0x1288.y = - std::fabsf(i_this->mDemoCamCenterTarget.y - i_this->mDemoCamCenter.y); + fabsf(i_this->mDemoCamCenterTarget.y - i_this->mDemoCamCenter.y); i_this->field_0x1288.z = - std::fabsf(i_this->mDemoCamCenterTarget.z - i_this->mDemoCamCenter.z); + fabsf(i_this->mDemoCamCenterTarget.z - i_this->mDemoCamCenter.z); cXyz pos(0.0f, 0.0f, 0.0f); for (int i = 0; i < 5; i++) { diff --git a/src/d/actor/d_a_b_ob.cpp b/src/d/actor/d_a_b_ob.cpp index e697b180ac..5622375fec 100644 --- a/src/d/actor/d_a_b_ob.cpp +++ b/src/d/actor/d_a_b_ob.cpp @@ -2462,12 +2462,12 @@ static void demo_camera(b_ob_class* i_this) { i_this->mDemoCamCenterTarget.set(80.0f, -24093.0f, 160.0f); i_this->mDemoCamEyeTarget.set(-447.0f, -22850.0f, -718.0f); - i_this->field_0x5cb4 = std::fabsf(i_this->mDemoCamEyeTarget.x - i_this->mDemoCamEye.x); - i_this->field_0x5cb8 = std::fabsf(i_this->mDemoCamEyeTarget.y - i_this->mDemoCamEye.y); - i_this->field_0x5cbc = std::fabsf(i_this->mDemoCamEyeTarget.z - i_this->mDemoCamEye.z); - i_this->field_0x5cc0 = std::fabsf(i_this->mDemoCamCenterTarget.x - i_this->mDemoCamCenter.x); - i_this->field_0x5cc4 = std::fabsf(i_this->mDemoCamCenterTarget.y - i_this->mDemoCamCenter.y); - i_this->field_0x5cc8 = std::fabsf(i_this->mDemoCamCenterTarget.z - i_this->mDemoCamCenter.z); + i_this->field_0x5cb4 = fabsf(i_this->mDemoCamEyeTarget.x - i_this->mDemoCamEye.x); + i_this->field_0x5cb8 = fabsf(i_this->mDemoCamEyeTarget.y - i_this->mDemoCamEye.y); + i_this->field_0x5cbc = fabsf(i_this->mDemoCamEyeTarget.z - i_this->mDemoCamEye.z); + i_this->field_0x5cc0 = fabsf(i_this->mDemoCamCenterTarget.x - i_this->mDemoCamCenter.x); + i_this->field_0x5cc4 = fabsf(i_this->mDemoCamCenterTarget.y - i_this->mDemoCamCenter.y); + i_this->field_0x5cc8 = fabsf(i_this->mDemoCamCenterTarget.z - i_this->mDemoCamCenter.z); i_this->field_0x5cd0 = 0.0f; daPy_getPlayerActorClass()->changeDemoMode(0x17, 0, 0, 0); @@ -2590,12 +2590,12 @@ static void demo_camera(b_ob_class* i_this) { i_this->mDemoCamCenterTarget.set(-6378.0f, -21886.0f, 7150.0f); i_this->mDemoCamEyeTarget.set(-6961.0f, -21727.0f, 7278.0f); - i_this->field_0x5cb4 = std::fabsf(i_this->mDemoCamEyeTarget.x - i_this->mDemoCamEye.x); - i_this->field_0x5cb8 = std::fabsf(i_this->mDemoCamEyeTarget.y - i_this->mDemoCamEye.y); - i_this->field_0x5cbc = std::fabsf(i_this->mDemoCamEyeTarget.z - i_this->mDemoCamEye.z); - i_this->field_0x5cc0 = std::fabsf(i_this->mDemoCamCenterTarget.x - i_this->mDemoCamCenter.x); - i_this->field_0x5cc4 = std::fabsf(i_this->mDemoCamCenterTarget.y - i_this->mDemoCamCenter.y); - i_this->field_0x5cc8 = std::fabsf(i_this->mDemoCamCenterTarget.z - i_this->mDemoCamCenter.z); + i_this->field_0x5cb4 = fabsf(i_this->mDemoCamEyeTarget.x - i_this->mDemoCamEye.x); + i_this->field_0x5cb8 = fabsf(i_this->mDemoCamEyeTarget.y - i_this->mDemoCamEye.y); + i_this->field_0x5cbc = fabsf(i_this->mDemoCamEyeTarget.z - i_this->mDemoCamEye.z); + i_this->field_0x5cc0 = fabsf(i_this->mDemoCamCenterTarget.x - i_this->mDemoCamCenter.x); + i_this->field_0x5cc4 = fabsf(i_this->mDemoCamCenterTarget.y - i_this->mDemoCamCenter.y); + i_this->field_0x5cc8 = fabsf(i_this->mDemoCamCenterTarget.z - i_this->mDemoCamCenter.z); i_this->field_0x5cd0 = 0.0f; i_this->mDemoAction = 46; @@ -2656,12 +2656,12 @@ static void demo_camera(b_ob_class* i_this) { i_this->mDemoCamCenterTarget.set(-2933.0f, -22626.0f, 6829.0f); i_this->mDemoCamEyeTarget.set(-3295.0f, -22459.0f, 7307.0f); - i_this->field_0x5cb4 = std::fabsf(i_this->mDemoCamEyeTarget.x - i_this->mDemoCamEye.x); - i_this->field_0x5cb8 = std::fabsf(i_this->mDemoCamEyeTarget.y - i_this->mDemoCamEye.y); - i_this->field_0x5cbc = std::fabsf(i_this->mDemoCamEyeTarget.z - i_this->mDemoCamEye.z); - i_this->field_0x5cc0 = std::fabsf(i_this->mDemoCamCenterTarget.x - i_this->mDemoCamCenter.x); - i_this->field_0x5cc4 = std::fabsf(i_this->mDemoCamCenterTarget.y - i_this->mDemoCamCenter.y); - i_this->field_0x5cc8 = std::fabsf(i_this->mDemoCamCenterTarget.z - i_this->mDemoCamCenter.z); + i_this->field_0x5cb4 = fabsf(i_this->mDemoCamEyeTarget.x - i_this->mDemoCamEye.x); + i_this->field_0x5cb8 = fabsf(i_this->mDemoCamEyeTarget.y - i_this->mDemoCamEye.y); + i_this->field_0x5cbc = fabsf(i_this->mDemoCamEyeTarget.z - i_this->mDemoCamEye.z); + i_this->field_0x5cc0 = fabsf(i_this->mDemoCamCenterTarget.x - i_this->mDemoCamCenter.x); + i_this->field_0x5cc4 = fabsf(i_this->mDemoCamCenterTarget.y - i_this->mDemoCamCenter.y); + i_this->field_0x5cc8 = fabsf(i_this->mDemoCamCenterTarget.z - i_this->mDemoCamCenter.z); i_this->field_0x5cd0 = 0.0f; i_this->mDemoAction = 48; diff --git a/src/d/actor/d_a_e_ymb.cpp b/src/d/actor/d_a_e_ymb.cpp index e107b2a8b9..2604b06480 100644 --- a/src/d/actor/d_a_e_ymb.cpp +++ b/src/d/actor/d_a_e_ymb.cpp @@ -516,7 +516,7 @@ void daE_YMB_c::checkWaterPos() { field_0x6cc = wtr_pos; field_0x69c.y = wtr_pos + 1000.0f + l_HIO.fly_height_adjust; - UNUSED(std::fabsf(field_0x6cc - current.pos.y)); + UNUSED(fabsf(field_0x6cc - current.pos.y)); if (current.pos.y < (200.0f + field_0x6cc)) { if (field_0x715 == 0) { setWaterEffect1(); diff --git a/src/d/actor/d_a_tbox.cpp b/src/d/actor/d_a_tbox.cpp index b936b9b6e7..6cad41b4e3 100644 --- a/src/d/actor/d_a_tbox.cpp +++ b/src/d/actor/d_a_tbox.cpp @@ -650,7 +650,7 @@ void daTbox_c::dropProcInit() { field_0x97c = false; f32 delta_y = pos.y - pnt1.y; - f32 abs_gravity = std::fabsf(fopAcM_GetGravity(this)); + f32 abs_gravity = fabsf(fopAcM_GetGravity(this)); var_f30 = JMAFastSqrt(2.0f * delta_y / abs_gravity); speedF = pos.absXZ(pnt1) / var_f30; diff --git a/src/d/d_camera.cpp b/src/d/d_camera.cpp index de9c9223cf..70e8c105bb 100644 --- a/src/d/d_camera.cpp +++ b/src/d/d_camera.cpp @@ -2458,8 +2458,8 @@ f32 dCamera_c::radiusActorInSight(fopAc_ac_c* i_actor1, fopAc_ac_c* i_actor2, cX int bVar2 = 0; - var_f29 = std::fabsf(cM_atan2f(pos1.x, -pos1.z)); - var_f28 = std::fabsf(cM_atan2f(pos1.y, -pos1.z)); + var_f29 = fabsf(cM_atan2f(pos1.x, -pos1.z)); + var_f28 = fabsf(cM_atan2f(pos1.y, -pos1.z)); if (var_f29 > sp30) { bVar2 |= 1; } @@ -2467,8 +2467,8 @@ f32 dCamera_c::radiusActorInSight(fopAc_ac_c* i_actor1, fopAc_ac_c* i_actor2, cX bVar2 |= 2; } - var_f29 = std::fabsf(cM_atan2f(pos2.x, -pos2.z)); - var_f28 = std::fabsf(cM_atan2f(pos2.y, -pos2.z)); + var_f29 = fabsf(cM_atan2f(pos2.x, -pos2.z)); + var_f28 = fabsf(cM_atan2f(pos2.y, -pos2.z)); if (var_f29 > sp30) { bVar2 |= 4; } @@ -2493,25 +2493,25 @@ f32 dCamera_c::radiusActorInSight(fopAc_ac_c* i_actor1, fopAc_ac_c* i_actor2, cX f32 var_f31; if (bVar2 & 1) { - var_f31 = pos1.z + std::fabsf(pos1.x) / local_12c; + var_f31 = pos1.z + fabsf(pos1.x) / local_12c; if (var_f31 > ret) { ret = var_f31; } } if (bVar2 & 2) { - var_f31 = pos1.z + std::fabsf(pos1.y) / local_130; + var_f31 = pos1.z + fabsf(pos1.y) / local_130; if (var_f31 > ret) { ret = var_f31; } } if (bVar2 & 4) { - var_f31 = pos2.z + std::fabsf(pos2.x) / local_12c; + var_f31 = pos2.z + fabsf(pos2.x) / local_12c; if (var_f31 > ret) { ret = var_f31; } } if (bVar2 & 8) { - var_f31 = pos2.z + std::fabsf(pos2.y) / local_130; + var_f31 = pos2.z + fabsf(pos2.y) / local_130; if (var_f31 > ret) { ret = var_f31; } @@ -2799,7 +2799,7 @@ bool dCamera_c::bumpCheck(u32 i_flags) { } wind_pow -= 0.3f; cSAngle angle = wind_globe.U() - mViewCache.mDirection.U(); - mFovy += mCamSetup.WindShakeGap4Fvy() * cM_rndFX(wind_pow / 0.7f) * (1.0f - std::fabsf(angle.Norm())); + mFovy += mCamSetup.WindShakeGap4Fvy() * cM_rndFX(wind_pow / 0.7f) * (1.0f - fabsf(angle.Norm())); #if DEBUG dDbVw_Report(300, 180, "wind %f %f", wind_pow, angle.Norm()); @@ -2943,7 +2943,7 @@ bool dCamera_c::bumpCheck(u32 i_flags) { cXyz cross; f32 dot = VECDotProduct(normal1, normal2); VECCrossProduct(normal1, normal2, &cross); - if (chkCornerCos(dot) && std::fabsf(cross.y) > 0.5f + if (chkCornerCos(dot) && fabsf(cross.y) > 0.5f && cBgW_CheckBWall(normal2->y)) { var_r27 = 5; } else { @@ -3056,7 +3056,7 @@ bool dCamera_c::bumpCheck(u32 i_flags) { field_0x96c += (1.0f - field_0x96c) * 0.1f; } if (sp0C || !(mMonitor.field_0xc < 5.0f) - || !(std::fabsf(mPadInfo.mCStick.mLastPosX) < 0.05f)) { + || !(fabsf(mPadInfo.mCStick.mLastPosX) < 0.05f)) { field_0x968 = 0.2f; } else { field_0x968 *= mMonitor.field_0xc / 5.0f; @@ -3999,7 +3999,7 @@ bool dCamera_c::chaseCamera(s32 param_0) { } cSAngle ang; - if ((chkFlag(0x100000) || sp1C || bVar6) && std::fabsf(mPadInfo.mCStick.mLastPosX) < 0.05f) { + if ((chkFlag(0x100000) || sp1C || bVar6) && fabsf(mPadInfo.mCStick.mLastPosX) < 0.05f) { ang.Val(directionOf(mpPlayerActor).Inv()); } else { ang.Val(mViewCache.mDirection.U()); @@ -4014,13 +4014,13 @@ bool dCamera_c::chaseCamera(s32 param_0) { pos3 = pos; pos.y += dist; sp1AC = cXyz(mCenter - pos3).abs() * 8.0f; - sp1BC = std::fabsf(dist1 > sp1AC ? dist1 : sp1AC); + sp1BC = fabsf(dist1 > sp1AC ? dist1 : sp1AC); dist1 = val18 + (val17 - val18) * chase->field_0xa4; - sp1AC = std::fabsf(mFovy - dist1); + sp1AC = fabsf(mFovy - dist1); f32 sin = cSAngle(mFovy > dist1 ? mFovy : dist1).Sin(); sp1AC = 100.0f * (sin * sin) * sp1AC; - sp1BC = std::fabsf(sp1BC > sp1AC ? sp1BC : sp1AC); + sp1BC = fabsf(sp1BC > sp1AC ? sp1BC : sp1AC); sp1BC *= 1.2f; sp1BC *= 0.00625f; chase->field_0x4 = (int)(JMAFastSqrt(sp1BC) * 2.2f) + 1; @@ -4128,7 +4128,7 @@ bool dCamera_c::chaseCamera(s32 param_0) { mViewCache.mCenter += (pos - mViewCache.mCenter) * rate; f32 dist = dCamMath::xyzHorizontalDistance(pos, chase->field_0x58); - if (dist < std::fabsf(vec.x > vec.z ? vec.x : vec.z) + 20.0f) { + if (dist < fabsf(vec.x > vec.z ? vec.x : vec.z) + 20.0f) { cXyz attention_pos = attentionPos(mpPlayerActor); attention_pos.y -= 15.0f; dBgS_CamLinChk lin_chk; @@ -4294,7 +4294,7 @@ bool dCamera_c::chaseCamera(s32 param_0) { cSAngle ang = -mViewCache.mDirection.U(); delta = dCamMath::xyzRotateY(delta, ang); cXyz vec5 = delta; - if (std::fabsf(vec5.y) < 200.0f) { + if (fabsf(vec5.y) < 200.0f) { vec5.y = 0.0f; vec5.x *= 0.5f; f32 sp16C = vec5.abs(); @@ -4415,17 +4415,17 @@ bool dCamera_c::chaseCamera(s32 param_0) { bool sp11 = false; f32 sp140 = 0.05f; f32 last_pos_x = mPadInfo.mCStick.mLastPosX; - f32 sp138 = std::fabsf(last_pos_x); + f32 sp138 = fabsf(last_pos_x); f32 sp134 = mPadInfo.mCStick.mLastPosY; - f32 sp130 = std::fabsf(sp134); + f32 sp130 = fabsf(sp134); f32 sp12C = 8.0f; f32 sp128 = 12.0f; chase->field_0x93 = false; - if (sp11 || (!mCamParam.Flag(param_0, 0x40) && std::fabsf(last_pos_x) > sp140)) { + if (sp11 || (!mCamParam.Flag(param_0, 0x40) && fabsf(last_pos_x) > sp140)) { chase->field_0xac += (dCamMath::rationalBezierRatio(last_pos_x, 0.5f) * sp12C - chase->field_0xac) * chase->field_0x4c; ang3 = globe.U() + cSAngle(chase->field_0xac); - sp148 = std::fabsf(last_pos_x) - 0.05f; + sp148 = fabsf(last_pos_x) - 0.05f; if (mCamSetup.CheckFlag(0x1000) && mFakeAngleSys.field_0x0 == 0) { setUSOAngle(); } @@ -4922,7 +4922,7 @@ bool dCamera_c::lockonCamera(s32 param_0) { sp0C = true; } - f32 fVar43 = 1.0f - std::fabsf(mPadInfo.mCStick.mLastPosY); + f32 fVar43 = 1.0f - fabsf(mPadInfo.mCStick.mLastPosY); f32 fVar44; if (bVar1) { @@ -4981,12 +4981,12 @@ bool dCamera_c::lockonCamera(s32 param_0) { cSAngle u, v; cSAngle ang5 = globe.V() - lockon->field_0x34.V(); if (sp0C) { - r = lockon->field_0x34.R() * 0.75f * std::fabsf(ang5.Cos()); + r = lockon->field_0x34.R() * 0.75f * fabsf(ang5.Cos()); u.Val(lockon->field_0x34.U() + (ang4 - lockon->field_0x34.U()) * lockon->field_0x58); v.Val(lockon->field_0x34.V() + ang5 * 0.05f); } else { r = lockon->field_0x34.R() + (sp184 - lockon->field_0x34.R()) * - lockon->field_0x54 * std::fabsf(ang5.Cos()); + lockon->field_0x54 * fabsf(ang5.Cos()); u.Val(lockon->field_0x34.U() + (ang4 - lockon->field_0x34.U()) * lockon->field_0x58); v.Val(lockon->field_0x34.V() + ang5 * lockon->field_0x58); } @@ -5043,9 +5043,9 @@ bool dCamera_c::lockonCamera(s32 param_0) { } bool bVar3 = false; - if (std::fabsf(sp158) > 0.05f) { + if (fabsf(sp158) > 0.05f) { cSAngle ang = globe2.U() + cSAngle(dCamMath::rationalBezierRatio(sp158, 0.5f) * 7.5f); - sp15C = std::fabsf(sp158) - 0.05f; + sp15C = fabsf(sp158) - 0.05f; lockon->field_0x42 = ang; lockon->field_0x4c = 0.0f; bVar3 = true; @@ -5109,7 +5109,7 @@ bool dCamera_c::lockonCamera(s32 param_0) { } ang6 = ang1.Inv() - mViewCache.mDirection.U(); - if (std::fabsf(ang6.Degree()) < 2.0f && false) { + if (fabsf(ang6.Degree()) < 2.0f && false) { lockon->field_0x2a = true; } u2 += ang6 * sp15C * lockon->field_0x4c; @@ -5137,7 +5137,7 @@ bool dCamera_c::lockonCamera(s32 param_0) { } else { lockon->field_0xc = 0; if (!mBG.field_0xc0.field_0x44 && !bVar1) { - v2 += (globe2.V() - v2) * fVar43 * std::fabsf(mViewCache.mDirection.V().Cos()); + v2 += (globe2.V() - v2) * fVar43 * fabsf(mViewCache.mDirection.V().Cos()); } else { cSAngle ang7 = lockon->field_0x34.V(); ang7 *= cSAngle(lockon->field_0x34.U() - mViewCache.mDirection.U()).Cos(); @@ -5469,7 +5469,7 @@ bool dCamera_c::talktoCamera(s32 param_0) { if (talk->field_0x84 != 0) { talk->field_0x48 = 1; } else { - talk->field_0x48 = (int)(JMAFastSqrt(std::fabsf(mViewCache.mDirection.R() - talk->field_0x28.R())) / 2.0f); + talk->field_0x48 = (int)(JMAFastSqrt(fabsf(mViewCache.mDirection.R() - talk->field_0x28.R())) / 2.0f); if (talk->field_0x48 < 2) { talk->field_0x48 = 2; } @@ -5609,7 +5609,7 @@ bool dCamera_c::talktoCamera(s32 param_0) { for (i = 0; i < 36; i++) { sp26C = talk->field_0x28.U() - talk->field_0x30.U(); - if (std::fabsf(sp26C.Degree()) < 10.0f) { + if (fabsf(sp26C.Degree()) < 10.0f) { talk->field_0x28.U(talk->field_0x28.U() + sp270); } else { if (!sp5B) { @@ -7172,7 +7172,7 @@ bool dCamera_c::subjectCamera(s32 param_0) { f32 zoom_fovy = dCamMath::zoomFovy(val17 * 0.5f, tmp2) * 2.0f; mViewCache.mFovy += (zoom_fovy - mViewCache.mFovy) * val22; setComZoomScale(tmp2); - setComZoomForcus(1.0f - std::fabsf(sp88 - sp84) * -511.0f); + setComZoomForcus(1.0f - fabsf(sp88 - sp84) * -511.0f); if (check_owner_action(mPadID, 0x200000)) { setComStat(8); } @@ -7263,12 +7263,12 @@ bool dCamera_c::magneCamera(s32 param_0) { cSAngle stack_238; if (mCurMode == 1) { stack_238 = sp54.Inv(); - } else if (std::fabsf(cstick_x) > 0.05f) { + } else if (fabsf(cstick_x) > 0.05f) { stack_238 = magne->field_0x1c.U() + cSAngle(dCamMath::rationalBezierRatio(cstick_x, 0.5f) * 10.0f); } else { cSAngle stack_23c = sp54.Inv() - stack_230.U(); f32 sin = stack_23c.Sin(); - f32 tmp = std::fabsf(sin * mPadInfo.mMainStick.mLastValue); + f32 tmp = fabsf(sin * mPadInfo.mMainStick.mLastValue); f32 tmp2 = stack_23c.Cos() > 0.0f ? 8.0f : 4.0f; stack_238 = stack_230.U() + cSAngle(sin * tmp2 * dCamMath::rationalBezierRatio(tmp, 1.0f)); } @@ -7584,7 +7584,7 @@ bool dCamera_c::towerCamera(s32 param_0) { cXyz stack_21c = relationalPos(mpPlayerActor, &stack_210); f32 sp1A8 = cXyz(mEye - stack_21c).abs() - val7; f32 sp1A4 = cXyz(mCenter - stack_21c).abs() - val7; - f32 sp1A0 = std::fabsf(sp1A8 > sp1A4 ? sp1A8 : sp1A4); + f32 sp1A0 = fabsf(sp1A8 > sp1A4 ? sp1A8 : sp1A4); sp1A8 = heightOf(mpPlayerActor); sp1A0 /= (sp1A8 < 10.0f ? 10.0f : sp1A8); tower->field_0x4 = (int)(JMAFastSqrt(sp1A0) * sp220) + 1; @@ -7706,9 +7706,9 @@ bool dCamera_c::towerCamera(s32 param_0) { cSAngle sp108; cSAngle sp104 = stack_454 + (stack_454 - stack_458) * sp188; - if (!mCamParam.Flag(param_0, 0x40) && std::fabsf(sp18C) > sp184) { + if (!mCamParam.Flag(param_0, 0x40) && fabsf(sp18C) > sp184) { cSAngle sp100 = mViewCache.mDirection.U() + cSAngle(dCamMath::rationalBezierRatio(sp18C, 0.5f) * sp180); - f32 sp17C = std::fabsf(sp18C) - sp184; + f32 sp17C = fabsf(sp18C) - sp184; sp108.Val(mViewCache.mDirection.U() + (sp100 - mViewCache.mDirection.U()) * sp17C); tower->field_0x6a = true; tower->field_0x78 += (0.8f - tower->field_0x78) * 0.05f; diff --git a/src/m_Do/m_Do_main.cpp b/src/m_Do/m_Do_main.cpp index 31833cf0e8..e470c8a0d7 100644 --- a/src/m_Do/m_Do_main.cpp +++ b/src/m_Do/m_Do_main.cpp @@ -49,6 +49,8 @@ #include "dusk/imgui/ImGuiEngine.hpp" #include "dusk/logging.h" #include "dusk/main.h" +#include "dusk/imgui/ImGuiConsole.hpp" +#include "version.h" #include "dusk/time.h" #include @@ -341,6 +343,10 @@ int game_main(int argc, char* argv[]) { auroraInfo = aurora_initialize(argc, argv, &config); + VISetWindowTitle( + fmt::format("Dusk {} [{}]", DUSK_WC_DESCRIBE, dusk::backend_name(auroraInfo.backend)) + .c_str()); + const auto& dvd_path = parsed_arg_options["dvd"].as(); DuskLog.info("Loading DVD image: {}", dvd_path); if (!aurora_dvd_open(dvd_path.c_str())) { diff --git a/version.h.in b/version.h.in new file mode 100644 index 0000000000..c85a6dc659 --- /dev/null +++ b/version.h.in @@ -0,0 +1,20 @@ +#ifndef VERSION_H +#define VERSION_H + +#define DUSK_WC_DESCRIBE "@DUSK_WC_DESCRIBE@" +#define DUSK_VERSION_STRING "@DUSK_VERSION_STRING@" + +#define DUSK_WC_BRANCH "@DUSK_WC_BRANCH@" +#define DUSK_WC_REVISION "@DUSK_WC_REVISION@" +#define DUSK_WC_DATE "@DUSK_WC_DATE@" +#define DUSK_BUILD_TYPE "@CMAKE_BUILD_TYPE@" + +#if defined(__x86_64__) || defined(_M_AMD64) +#define DUSK_DLPACKAGE "dusk-@DUSK_WC_DESCRIBE@-@PLATFORM_NAME@-x86_64" +#elif defined(__i386__) || defined(_M_IX86) +#define DUSK_DLPACKAGE "dusk-@DUSK_WC_DESCRIBE@-@PLATFORM_NAME@-x86" +#elif defined(__aarch64__) || defined(_M_ARM64) +#define DUSK_DLPACKAGE "dusk-@DUSK_WC_DESCRIBE@-@PLATFORM_NAME@-arm64" +#endif + +#endif From 4328a1bb5d3c4d80a4d4ef39bcbfdca1f1099945 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Fri, 10 Apr 2026 03:24:25 +0200 Subject: [PATCH 345/350] Set default PAD mapping to fix Z (#297) * Set default PAD mapping to fix Z Requires https://github.com/encounter/aurora/pull/111 * Update to new API --- src/m_Do/m_Do_main.cpp | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/m_Do/m_Do_main.cpp b/src/m_Do/m_Do_main.cpp index e470c8a0d7..edceafd01c 100644 --- a/src/m_Do/m_Do_main.cpp +++ b/src/m_Do/m_Do_main.cpp @@ -283,6 +283,37 @@ static const char* CalculateConfigPath() { return result; } +static constexpr PADDefaultMapping defaultPadMapping = { + .buttons = { + {SDL_GAMEPAD_BUTTON_SOUTH, PAD_BUTTON_A}, + {SDL_GAMEPAD_BUTTON_EAST, PAD_BUTTON_B}, + {SDL_GAMEPAD_BUTTON_WEST, PAD_BUTTON_X}, + {SDL_GAMEPAD_BUTTON_NORTH, PAD_BUTTON_Y}, + {SDL_GAMEPAD_BUTTON_START, PAD_BUTTON_START}, + {SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, PAD_TRIGGER_Z}, + {PAD_NATIVE_BUTTON_INVALID, PAD_TRIGGER_L}, + {PAD_NATIVE_BUTTON_INVALID, PAD_TRIGGER_R}, + {SDL_GAMEPAD_BUTTON_DPAD_UP, PAD_BUTTON_UP}, + {SDL_GAMEPAD_BUTTON_DPAD_DOWN, PAD_BUTTON_DOWN}, + {SDL_GAMEPAD_BUTTON_DPAD_LEFT, PAD_BUTTON_LEFT}, + {SDL_GAMEPAD_BUTTON_DPAD_RIGHT, PAD_BUTTON_RIGHT}, + }, + .axes = { + {{SDL_GAMEPAD_AXIS_LEFTX, AXIS_SIGN_POSITIVE}, SDL_GAMEPAD_BUTTON_INVALID, PAD_AXIS_LEFT_X_POS}, + {{SDL_GAMEPAD_AXIS_LEFTX, AXIS_SIGN_NEGATIVE}, SDL_GAMEPAD_BUTTON_INVALID, PAD_AXIS_LEFT_X_NEG}, + // SDL's gamepad y-axis is inverted from GC's + {{SDL_GAMEPAD_AXIS_LEFTY, AXIS_SIGN_NEGATIVE}, SDL_GAMEPAD_BUTTON_INVALID, PAD_AXIS_LEFT_Y_POS}, + {{SDL_GAMEPAD_AXIS_LEFTY, AXIS_SIGN_POSITIVE}, SDL_GAMEPAD_BUTTON_INVALID, PAD_AXIS_LEFT_Y_NEG}, + {{SDL_GAMEPAD_AXIS_RIGHTX, AXIS_SIGN_POSITIVE}, SDL_GAMEPAD_BUTTON_INVALID, PAD_AXIS_RIGHT_X_POS}, + {{SDL_GAMEPAD_AXIS_RIGHTX, AXIS_SIGN_NEGATIVE}, SDL_GAMEPAD_BUTTON_INVALID, PAD_AXIS_RIGHT_X_NEG}, + // see above + {{SDL_GAMEPAD_AXIS_RIGHTY, AXIS_SIGN_NEGATIVE}, SDL_GAMEPAD_BUTTON_INVALID, PAD_AXIS_RIGHT_Y_POS}, + {{SDL_GAMEPAD_AXIS_RIGHTY, AXIS_SIGN_POSITIVE}, SDL_GAMEPAD_BUTTON_INVALID, PAD_AXIS_RIGHT_Y_NEG}, + {{SDL_GAMEPAD_AXIS_LEFT_TRIGGER, AXIS_SIGN_POSITIVE}, SDL_GAMEPAD_BUTTON_INVALID, PAD_AXIS_TRIGGER_L}, + {{SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, AXIS_SIGN_POSITIVE}, SDL_GAMEPAD_BUTTON_INVALID, PAD_AXIS_TRIGGER_R}, + }, +}; + // ========================================================================= // PC ENTRY POINT // ========================================================================= @@ -341,6 +372,8 @@ int game_main(int argc, char* argv[]) { config.allowJoystickBackgroundEvents = true; config.imGuiInitCallback = &aurora_imgui_init_callback; + PADSetDefaultMapping(&defaultPadMapping); + auroraInfo = aurora_initialize(argc, argv, &config); VISetWindowTitle( From 0714e9f153828a386ccc8ba35138d3c9e38553c6 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Thu, 9 Apr 2026 19:26:10 -0600 Subject: [PATCH 346/350] Update aurora --- extern/aurora | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/aurora b/extern/aurora index b1bf531884..3bedc9950d 160000 --- a/extern/aurora +++ b/extern/aurora @@ -1 +1 @@ -Subproject commit b1bf531884172075dd97403edddc7d3301f4ca46 +Subproject commit 3bedc9950da12533f34c1f51fce1b7e6fe5ddcbc From 1d9fcb2fa508d170084594bec3c8bf40a49c1c21 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Thu, 9 Apr 2026 19:35:33 -0600 Subject: [PATCH 347/350] Update README.md & build instructions (+ CLion) --- README.md | 60 +++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 45 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 59f12abc2a..e4f3c551fc 100644 --- a/README.md +++ b/README.md @@ -2,22 +2,22 @@ ### Building #### Prerequisites -* [CMake 3.30+](https://cmake.org) +* [CMake 3.25+](https://cmake.org) * Windows: Install `CMake Tools` in Visual Studio * macOS: `brew install cmake` * [Python 3+](https://python.org) * Windows: [Microsoft Store](https://go.microsoft.com/fwlink?linkID=2082640) * Verify it's added to `%PATH%` by typing `python` in `cmd`. * macOS: `brew install python@3` -* **[Windows]** [Visual Studio 2022 Community](https://www.visualstudio.com/en-us/products/visual-studio-community-vs.aspx) +* **[Windows]** [Visual Studio 2026 Community](https://www.visualstudio.com/en-us/products/visual-studio-community-vs.aspx) * Select `C++ Development` and verify the following packages are included: - * `Windows 10 SDK` + * `Windows 11 SDK` * `CMake Tools` * `C++ Clang Compiler` * `C++ Clang-cl` -* **[macOS]** [Xcode 11.5+](https://developer.apple.com/xcode/download/) -* **[Linux]** Actively tested on Ubuntu 20.04, Arch Linux & derivatives. - * Ubuntu 20.04+ packages +* **[macOS]** [Xcode 16.4+](https://developer.apple.com/xcode/download/) +* **[Linux]** Actively tested on Ubuntu 24.04, Arch Linux & derivatives. + * Ubuntu 24.04+ packages ``` build-essential curl git ninja-build clang lld zlib1g-dev libcurl4-openssl-dev \ libglu1-mesa-dev libdbus-1-dev libvulkan-dev libxi-dev libxrandr-dev libasound2-dev libpulse-dev \ @@ -39,28 +39,58 @@ #### Setup Clone and initialize the Dusk repository ```sh -git clone --recursive https://github.com/TakaRikka/dusk.git +git clone --recursive https://github.com/TwilitRealm/dusk.git cd dusk git pull -git submodule update --recursive +git submodule update --init --recursive ``` #### Building -**Visual Studio (Recommended for Windows)** + +**CLion (Windows / macOS / Linux)** + +Open the project directory in CLion. Enable the appropriate presets for your platform: + +![CLion](assets/clion.png) + +**Visual Studio (Windows)** + +Open the project directory in Visual Studio. The CMake configuration will be loaded automatically. + +**ninja (macOS)** + ```sh -cmake -B build/dusk -G "Visual Studio 17 2022" -A x64 # Win32 for 32bit +cmake --preset macos-default-relwithdebinfo +cmake --build --preset macos-default-relwithdebinfo ``` -**Visual Studio Code with ninja** +Alternate presets available: +- `macos-default-debug`: Clang, Debug -Project has support for the CMakeTools extension with variants and has a debug launch target +**ninja (Linux)** -**ninja (Windows/macOS/Linux)** ```sh -cmake -B build/dusk -GNinja -ninja -C build/dusk +cmake --preset linux-default-relwithdebinfo +cmake --build --preset linux-default-relwithdebinfo ``` +Alternate presets available: +- `linux-default-debug`: GCC, Debug +- `linux-clang-relwithdebinfo`: Clang, RelWithDebInfo +- `linux-clang-debug`: Clang, Debug + +**ninja (Windows)** + +```sh +cmake --preset windows-msvc-relwithdebinfo +cmake --build --preset windows-msvc-relwithdebinfo +``` + +Alternate presets available: +- `windows-msvc-debug`: MSVC, Debug +- `windows-clang-relwithdebinfo`: Clang-cl, RelWithDebInfo +- `windows-clang-debug`: Clang-cl, Debug + #### Running Pass the disc image as a positional argument. Supported formats: ISO (GCM), RVZ, WIA, WBFS, CISO, GCZ ```sh From 280e03c0fe631b398402bd6d2a4cadf7b7648e06 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Thu, 9 Apr 2026 19:35:54 -0600 Subject: [PATCH 348/350] Add missing image --- assets/clion.png | Bin 0 -> 555857 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/clion.png diff --git a/assets/clion.png b/assets/clion.png new file mode 100644 index 0000000000000000000000000000000000000000..d4f4f6fb197aacc4d9ea2ebbc7c5c9bfaa404714 GIT binary patch literal 555857 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≦fYtrW-v^ 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

y4A$hZ}}eAYW&S4d{Zi; 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

0Z` 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 M+HkFWP5b-*0=Pc2uK)l5 literal 0 HcmV?d00001 From 04212b5b6cc2a395ca65bf51b32f054fea651900 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Thu, 9 Apr 2026 19:37:42 -0600 Subject: [PATCH 349/350] Support shared libjpeg-turbo --- CMakeLists.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4cca061f1f..d87bb1afe2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -217,7 +217,11 @@ set(GAME_LIBS aurora::core aurora::gx aurora::gd aurora::si aurora::vi aurora::p aurora::card freeverb cxxopts::cxxopts absl::flat_hash_map nlohmann_json::nlohmann_json TracyClient) if (DUSK_MOVIE_SUPPORT) - list(APPEND GAME_LIBS libjpeg-turbo::turbojpeg-static) + if (TARGET libjpeg-turbo::turbojpeg-static) + list(APPEND GAME_LIBS libjpeg-turbo::turbojpeg-static) + else () + list(APPEND GAME_LIBS libjpeg-turbo::turbojpeg) + endif () list(APPEND GAME_COMPILE_DEFS MOVIE_SUPPORT=1) endif () From 6b74f8eb25ac5b89687892c991435af498a6cd1d Mon Sep 17 00:00:00 2001 From: Luke Street Date: Thu, 9 Apr 2026 19:39:53 -0600 Subject: [PATCH 350/350] Update run path --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e4f3c551fc..754d3ff1dc 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ Alternate presets available: #### Running Pass the disc image as a positional argument. Supported formats: ISO (GCM), RVZ, WIA, WBFS, CISO, GCZ ```sh -build/dusk/dusk /path/to/game.rvz +build/{preset}/dusk /path/to/game.rvz ``` If no path is specified, Dusk defaults to `game.iso` in the current working directory.