From 228a305110b3a6c16ecac603322c9840d6f46eb4 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Fri, 27 Mar 2026 19:52:09 +0100 Subject: [PATCH 01/14] Show better button names in controller mapping UI Requires https://github.com/encounter/aurora/pull/68 --- src/dusk/imgui/ImGuiMenuGame.cpp | 118 ++++++++++++++++++++++++++++++- 1 file changed, 116 insertions(+), 2 deletions(-) diff --git a/src/dusk/imgui/ImGuiMenuGame.cpp b/src/dusk/imgui/ImGuiMenuGame.cpp index 79dfcb022d..22225eb9dc 100644 --- a/src/dusk/imgui/ImGuiMenuGame.cpp +++ b/src/dusk/imgui/ImGuiMenuGame.cpp @@ -9,6 +9,7 @@ #include "JSystem/JUtility/JUTGamePad.h" #include "m_Do/m_Do_controller_pad.h" #include "m_Do/m_Do_audio.h" +#include namespace dusk { ImGuiMenuGame::ImGuiMenuGame() {} @@ -92,6 +93,118 @@ namespace dusk { ImGui::EndChild(); } + struct SpecificButtonName { + SDL_GamepadType Type; + const char* Name; + }; + + struct ButtonNames { + SDL_GamepadButton Button; + std::vector Names; + }; + +// clang-format off + static const std::vector GamepadButtonNames = { + { SDL_GAMEPAD_BUTTON_LEFT_STICK, { + {SDL_GAMEPAD_TYPE_PS3, "L3"}, + {SDL_GAMEPAD_TYPE_PS4, "L3"}, + {SDL_GAMEPAD_TYPE_PS5, "L3"}, + {SDL_GAMEPAD_TYPE_XBOX360, "Left Stick"}, + {SDL_GAMEPAD_TYPE_XBOXONE, "Left Stick"}, + {SDL_GAMEPAD_TYPE_GAMECUBE, "Control Stick"}, + }}, + { SDL_GAMEPAD_BUTTON_RIGHT_STICK, { + {SDL_GAMEPAD_TYPE_PS3, "R3"}, + {SDL_GAMEPAD_TYPE_PS4, "R3"}, + {SDL_GAMEPAD_TYPE_PS5, "R3"}, + {SDL_GAMEPAD_TYPE_XBOX360, "Right Stick"}, + {SDL_GAMEPAD_TYPE_XBOXONE, "Right Stick"}, + {SDL_GAMEPAD_TYPE_GAMECUBE, "C Stick"}, + }}, + { SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, { + {SDL_GAMEPAD_TYPE_PS3, "L1"}, + {SDL_GAMEPAD_TYPE_PS4, "L1"}, + {SDL_GAMEPAD_TYPE_PS5, "L1"}, + {SDL_GAMEPAD_TYPE_XBOX360, "LB"}, + {SDL_GAMEPAD_TYPE_XBOXONE, "LB"}, + }}, + { SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, { + {SDL_GAMEPAD_TYPE_PS3, "R1"}, + {SDL_GAMEPAD_TYPE_PS4, "R1"}, + {SDL_GAMEPAD_TYPE_PS5, "R1"}, + {SDL_GAMEPAD_TYPE_XBOX360, "RB"}, + {SDL_GAMEPAD_TYPE_XBOXONE, "RB"}, + {SDL_GAMEPAD_TYPE_GAMECUBE, "Z"}, + }}, + { SDL_GAMEPAD_BUTTON_BACK, { + {SDL_GAMEPAD_TYPE_PS3, "Select"}, + {SDL_GAMEPAD_TYPE_PS4, "Share"}, + {SDL_GAMEPAD_TYPE_PS5, "Create"}, + {SDL_GAMEPAD_TYPE_XBOX360, "Back"}, + {SDL_GAMEPAD_TYPE_XBOXONE, "View"}, + }}, + { SDL_GAMEPAD_BUTTON_START, { + {SDL_GAMEPAD_TYPE_PS3, "Start"}, + {SDL_GAMEPAD_TYPE_PS4, "Options"}, + {SDL_GAMEPAD_TYPE_PS5, "Options"}, + {SDL_GAMEPAD_TYPE_XBOX360, "Start"}, + {SDL_GAMEPAD_TYPE_XBOXONE, "Menu"}, + {SDL_GAMEPAD_TYPE_GAMECUBE, "Start/Pause"}, + }}, + }; +// clang-format on + + static const char* GetNameForGamepadButton(SDL_Gamepad* gamepad, u32 buttonUntyped) { + auto button = static_cast(buttonUntyped); + auto label = SDL_GetGamepadButtonLabel(gamepad, button); + + switch (label) { + case SDL_GAMEPAD_BUTTON_LABEL_A: + return "A"; + case SDL_GAMEPAD_BUTTON_LABEL_B: + return "B"; + case SDL_GAMEPAD_BUTTON_LABEL_X: + return "X"; + case SDL_GAMEPAD_BUTTON_LABEL_Y: + return "Y"; + case SDL_GAMEPAD_BUTTON_LABEL_CROSS: + return "Cross"; + case SDL_GAMEPAD_BUTTON_LABEL_CIRCLE: + return "Circle"; + case SDL_GAMEPAD_BUTTON_LABEL_TRIANGLE: + return "Triangle"; + case SDL_GAMEPAD_BUTTON_LABEL_SQUARE: + return "Square"; + default:; // Fall through + } + + auto padType = SDL_GetGamepadType(gamepad); + for (const auto& buttonNames : GamepadButtonNames) { + if (buttonNames.Button != button) { + continue; + } + + for (const auto& name : buttonNames.Names) { + if (name.Type == padType) { + return name.Name; + } + } + } + + switch (button) { + case SDL_GAMEPAD_BUTTON_DPAD_LEFT: + return "D-pad left"; + case SDL_GAMEPAD_BUTTON_DPAD_RIGHT: + return "D-pad right"; + case SDL_GAMEPAD_BUTTON_DPAD_UP: + return "D-pad up"; + case SDL_GAMEPAD_BUTTON_DPAD_DOWN: + return "D-pad down"; + default: + return PADGetNativeButtonName(buttonUntyped); + } + } + void ImGuiMenuGame::windowControllerConfig() { if (!m_showControllerConfig) { return; @@ -196,6 +309,7 @@ namespace dusk { ImGuiBeginGroupPanel("Buttons", ImVec2(150, 20)); + SDL_Gamepad* gamepad = PADGetSDLGamepadForIndex(PADGetIndexForPort(m_controllerConfig.m_selectedPort)); u32 buttonCount; PADButtonMapping* mappingList = PADGetButtonMappings(m_controllerConfig.m_selectedPort, &buttonCount); if (mappingList != nullptr) { @@ -213,7 +327,7 @@ namespace dusk { 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(), + : fmt::format("{0}##-{1}", GetNameForGamepadButton(gamepad, mappingList[i].nativeButton), i).c_str(), ImVec2(100.0f, 20.0f)); if (pressed) { @@ -321,7 +435,7 @@ namespace dusk { } } } - + if (deadZones != nullptr) { ImGui::Text("R Threshold"); { From 18995f3d7c87d9534785fc9a09917c6aae8c9c65 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Mon, 13 Apr 2026 13:27:21 +0200 Subject: [PATCH 02/14] Enable some draw call merging in text rendering Don't set state between characters if possible. Next step is page merging so it can do full lines of text at once. --- include/global.h | 2 + .../include/JSystem/JUtility/JUTFont.h | 24 ++++++- .../include/JSystem/JUtility/JUTResFont.h | 11 +++- libs/JSystem/src/J2DGraph/J2DPrint.cpp | 11 +++- libs/JSystem/src/JUtility/JUTResFont.cpp | 66 +++++++++++++------ 5 files changed, 89 insertions(+), 25 deletions(-) diff --git a/include/global.h b/include/global.h index d634332797..eda2a610ec 100644 --- a/include/global.h +++ b/include/global.h @@ -220,10 +220,12 @@ using std::isnan; // Some basic macros that are more convenient than putting down #if blocks for one-line changes. #if TARGET_PC #define IF_DUSK(statement) statement +#define IF_DUSK_ARG(expr) , expr #define IF_NOT_DUSK(statement) #define DUSK_IF_ELSE(dusk, orig) dusk #else #define IF_DUSK(statement) +#define IF_DUSK_ARG(expr) #define IF_NOT_DUSK(statement) statement #define DUSK_IF_ELSE(dusk, orig) orig #endif diff --git a/libs/JSystem/include/JSystem/JUtility/JUTFont.h b/libs/JSystem/include/JSystem/JUtility/JUTFont.h index 2718132048..d07e09a590 100644 --- a/libs/JSystem/include/JSystem/JUtility/JUTFont.h +++ b/libs/JSystem/include/JSystem/JUtility/JUTFont.h @@ -5,6 +5,18 @@ #include #include "dusk/endian.h" +#if TARGET_PC +struct FontDrawContext { + int loadedBlock = -1; + int loadedPage = -1; +}; +#define FONT_DRAW_CTX , FontDrawContext* context +#define FONT_DRAW_CTX_ARG , context +#else +#define FONT_DRAW_CTX +#define FONT_DRAW_CTX_ARG +#endif + /** * @ingroup jsystem-jutility * @@ -84,7 +96,12 @@ public: /* 0x0C */ virtual void setGX() = 0; /* 0x10 */ virtual void setGX(JUtility::TColor col1, JUtility::TColor col2) { setGX(); } - /* 0x14 */ virtual f32 drawChar_scale(f32 a1, f32 a2, f32 a3, f32 a4, int a5, bool a6) = 0; + /* 0x14 */ virtual f32 drawChar_scale(f32 a1, f32 a2, f32 a3, f32 a4, int a5, bool a6 FONT_DRAW_CTX) = 0; +#if TARGET_PC + f32 drawChar_scale(f32 a1, f32 a2, f32 a3, f32 a4, int a5, bool a6) { + return drawChar_scale(a1, a2, a3, a4, a5, a6, nullptr); + } +#endif /* 0x18 */ virtual int getLeading() const = 0; /* 0x1C */ virtual s32 getAscent() const = 0; /* 0x20 */ virtual s32 getDescent() const = 0; @@ -97,6 +114,11 @@ public: /* 0x3C */ virtual ResFONT* getResFont() const = 0; /* 0x40 */ virtual bool isLeadByte(int a1) const = 0; +#if TARGET_PC + virtual void pushDrawState() = 0; + virtual void popDrawState() = 0; +#endif + static bool isLeadByte_1Byte(int b) { return false; } diff --git a/libs/JSystem/include/JSystem/JUtility/JUTResFont.h b/libs/JSystem/include/JSystem/JUtility/JUTResFont.h index 8709822d7b..68fd798ba5 100644 --- a/libs/JSystem/include/JSystem/JUtility/JUTResFont.h +++ b/libs/JSystem/include/JSystem/JUtility/JUTResFont.h @@ -31,7 +31,7 @@ public: virtual ~JUTResFont(); virtual void setGX(); virtual void setGX(JUtility::TColor, JUtility::TColor); - virtual f32 drawChar_scale(f32, f32, f32, f32, int, bool); + virtual f32 drawChar_scale(f32, f32, f32, f32, int, bool FONT_DRAW_CTX); virtual int getLeading() const; virtual s32 getAscent() const; virtual s32 getDescent() const; @@ -43,7 +43,7 @@ public: virtual int getFontType() const; virtual ResFONT* getResFont() const; virtual bool isLeadByte(int) const; - virtual void loadImage(int, GXTexMapID); + virtual void loadImage(int, GXTexMapID FONT_DRAW_CTX); virtual void setBlock(); JUTResFont(ResFONT const*, JKRHeap*); @@ -53,10 +53,15 @@ public: bool initiate(ResFONT const*, JKRHeap*); bool protected_initiate(ResFONT const*, JKRHeap*); void countBlock(); - void loadFont(int, GXTexMapID, JUTFont::TWidth*); + void loadFont(int, GXTexMapID, JUTFont::TWidth* FONT_DRAW_CTX); int getFontCode(int) const; int convertSjis(int, BE(u16)*) const; +#if TARGET_PC + void pushDrawState() override; + void popDrawState() override; +#endif + inline void delete_and_initialize() { deleteMemBlocks_ResFont(); initialize_state(); diff --git a/libs/JSystem/src/J2DGraph/J2DPrint.cpp b/libs/JSystem/src/J2DGraph/J2DPrint.cpp index bd7156f706..69d71aa6c5 100644 --- a/libs/JSystem/src/J2DGraph/J2DPrint.cpp +++ b/libs/JSystem/src/J2DGraph/J2DPrint.cpp @@ -211,6 +211,11 @@ f32 J2DPrint::parse(const u8* pString, int length, int param_2, u16* param_3, local_bc.a = local_bc.a * alpha / 0xFF; mFont->setGradColor(local_b8, field_0x22 ? local_bc : local_b8); +#if TARGET_PC + FontDrawContext context; + mFont->pushDrawState(); +#endif + do { bool b2ByteCharacter = false; bool r25; @@ -312,9 +317,9 @@ f32 J2DPrint::parse(const u8* pString, int length, int param_2, u16* param_3, } else { if (param_6) { if (param_3 != NULL) { - mFont->drawChar_scale(mCursorH + (s16)param_3[someIndex], mCursorV, (s32)mScaleX, (s32)mScaleY, iCharacter, true); + mFont->drawChar_scale(mCursorH + (s16)param_3[someIndex], mCursorV, (s32)mScaleX, (s32)mScaleY, iCharacter, true IF_DUSK_ARG(&context)); } else { - mFont->drawChar_scale(mCursorH, mCursorV, (s32)mScaleX, (s32)mScaleY, iCharacter, true); + mFont->drawChar_scale(mCursorH, mCursorV, (s32)mScaleX, (s32)mScaleY, iCharacter, true IF_DUSK_ARG(&context)); } } mCursorH += field_0x34; @@ -353,6 +358,8 @@ f32 J2DPrint::parse(const u8* pString, int length, int param_2, u16* param_3, iCharacter = *(pString++); } while (true); + IF_DUSK(mFont->popDrawState()); + if (param_3 != NULL) { param_3[someIndex] = 0xFFFF; } diff --git a/libs/JSystem/src/JUtility/JUTResFont.cpp b/libs/JSystem/src/JUtility/JUTResFont.cpp index 206968f053..8583d42c58 100644 --- a/libs/JSystem/src/JUtility/JUTResFont.cpp +++ b/libs/JSystem/src/JUtility/JUTResFont.cpp @@ -231,14 +231,14 @@ void JUTResFont::setGX(JUtility::TColor col1, JUtility::TColor col2) { } f32 JUTResFont::drawChar_scale(f32 pos_x, f32 pos_y, f32 scale_x, f32 scale_y, int str_int, - bool flag) { + bool flag FONT_DRAW_CTX) { f32 x1; f32 x2; f32 y1; JUT_ASSERT(378, mValid); JUTFont::TWidth width; - loadFont(str_int, GX_TEXMAP0, &width); + loadFont(str_int, GX_TEXMAP0, &width FONT_DRAW_CTX_ARG); if ((mFixed) || (!flag)) { x1 = pos_x; @@ -266,7 +266,13 @@ f32 JUTResFont::drawChar_scale(f32 pos_x, f32 pos_y, f32 scale_x, f32 scale_y, i s32 u2 = ((mWidth + cellW) * 0x8000) / texW; s32 v2 = ((mHeight + cellH) * 0x8000) / texH; +#if TARGET_PC + if (!context) { + pushDrawState(); + } +#else GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_F32, 0); +#endif GXBegin(GX_QUADS, GX_VTXFMT0, 4); // Bottom Left @@ -290,18 +296,33 @@ f32 JUTResFont::drawChar_scale(f32 pos_x, f32 pos_y, f32 scale_x, f32 scale_y, i GXTexCoord2u16(u1, v2); GXEnd(); +#if TARGET_PC + if (!context) { + popDrawState(); + } +#else GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_S16, 0); - +#endif return retval; } -void JUTResFont::loadFont(int code, GXTexMapID texMapID, JUTFont::TWidth* pDstWidth) { +#if TARGET_PC +void JUTResFont::pushDrawState() { + GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_F32, 0); +} + +void JUTResFont::popDrawState() { + GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_S16, 0); +} +#endif + +void JUTResFont::loadFont(int code, GXTexMapID texMapID, JUTFont::TWidth* pDstWidth FONT_DRAW_CTX) { if (pDstWidth != 0) { getWidthEntry(code, pDstWidth); } int fontCode = getFontCode(code); - loadImage(fontCode, texMapID); + loadImage(fontCode, texMapID FONT_DRAW_CTX_ARG); } void JUTResFont::getWidthEntry(int code, JUTFont::TWidth* i_width) const { @@ -403,7 +424,7 @@ int JUTResFont::getFontCode(int chr) const { return ret; } -void JUTResFont::loadImage(int code, GXTexMapID id){ +void JUTResFont::loadImage(int code, GXTexMapID id FONT_DRAW_CTX){ int i = 0; for (; i < mGly1BlockNum; i++) { @@ -435,22 +456,29 @@ void JUTResFont::loadImage(int code, GXTexMapID id){ mHeight = cellRow * cellH; #if TARGET_PC - const auto found = mGlyphTextures->textures.find(pageIdx); - GXTexObj* texObj; - if (found == mGlyphTextures->textures.end()) { - texObj = &mGlyphTextures->textures[pageIdx]; - 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); + if (!context || context->loadedPage != pageIdx || context->loadedBlock != i) { + const auto found = mGlyphTextures->textures.find(pageIdx); + GXTexObj* texObj; + if (found == mGlyphTextures->textures.end()) { + texObj = &mGlyphTextures->textures[pageIdx]; + 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; + GXInitTexObjLOD(texObj, GX_LINEAR, GX_LINEAR, 0.0f, 0.0f, 0.0f, 0U, 0U, GX_ANISO_1); + } else { + texObj = &found->second; + } + + GXLoadTexObj(texObj, id); + + if (context) { + context->loadedBlock = i; + context->loadedPage = pageIdx; + } } - GXLoadTexObj(texObj, id); - // Gets used by some other code. mTexPageIdx = pageIdx; field_0x66 = i; From 277d16c1107864ff92cd8efb7007ffa92a39a9d1 Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Mon, 13 Apr 2026 10:46:09 -0400 Subject: [PATCH 03/14] Time Stones Feature --- include/d/actor/d_a_obj_wind_stone.h | 3 +++ include/dusk/settings.h | 1 + src/d/actor/d_a_alink_wolf.inc | 29 +++++++++++++++++++----- src/d/actor/d_a_obj_wind_stone.cpp | 13 +++++++++++ src/dusk/imgui/ImGuiFirstRunPreset.cpp | 1 + src/dusk/imgui/ImGuiMenuEnhancements.cpp | 5 ++++ src/dusk/settings.cpp | 2 ++ 7 files changed, 48 insertions(+), 6 deletions(-) diff --git a/include/d/actor/d_a_obj_wind_stone.h b/include/d/actor/d_a_obj_wind_stone.h index f5e52dfc94..7e44cb7108 100644 --- a/include/d/actor/d_a_obj_wind_stone.h +++ b/include/d/actor/d_a_obj_wind_stone.h @@ -27,6 +27,9 @@ public: bool chkEveOccur(); void exeModeHowl(); void exeModeMapDisp(); + #if TARGET_PC + void exeModeSetTime(); + #endif bool chkMapDispMode(); u8 getGoldWolfIdx(); diff --git a/include/dusk/settings.h b/include/dusk/settings.h index 68ea49c752..168c5ca31b 100644 --- a/include/dusk/settings.h +++ b/include/dusk/settings.h @@ -47,6 +47,7 @@ struct UserSettings { ConfigVar noMissClimbing; ConfigVar fastTears; ConfigVar instantSaves; + ConfigVar timeStones; // Preferences ConfigVar enableMirrorMode; diff --git a/src/d/actor/d_a_alink_wolf.inc b/src/d/actor/d_a_alink_wolf.inc index 2e38298090..7f4bef7e42 100644 --- a/src/d/actor/d_a_alink_wolf.inc +++ b/src/d/actor/d_a_alink_wolf.inc @@ -3868,12 +3868,7 @@ void daAlink_c::setWolfHowlNotHappen(int param_0) { } int daAlink_c::procWolfHowlDemoInit() { - if (!dComIfGp_event_compulsory(this, NULL, 0xFFFF)) { - return 0; - } - - mDemo.setSpecialDemoType(); - + #if TARGET_PC s16 name; if (field_0x27f4 != NULL) { name = fopAcM_GetName(field_0x27f4); @@ -3881,6 +3876,28 @@ int daAlink_c::procWolfHowlDemoInit() { name = fpcNm_ALINK_e; } + if (name == fpcNm_Obj_WindStone_e && !static_cast(field_0x27f4)->chkEveOccur() && dusk::getSettings().game.timeStones) { + Z2GetSeMgr()->seStart(Z2SE_READ_RIDDLE_B, NULL, 0, 0, 1.0f, 1.0f, -1.0f, -1.0f, 0); + dKy_instant_timechg(0.0f); + return 0; + } + #endif + + if (!dComIfGp_event_compulsory(this, NULL, 0xFFFF)) { + return 0; + } + + mDemo.setSpecialDemoType(); + + #if !TARGET_PC + s16 name; + if (field_0x27f4 != NULL) { + name = fopAcM_GetName(field_0x27f4); + } else { + name = fpcNm_ALINK_e; + } + #endif + if (name == fpcNm_Tag_WaraHowl_e) { shape_angle.y = field_0x27f4->shape_angle.y; current.angle.y = shape_angle.y; diff --git a/src/d/actor/d_a_obj_wind_stone.cpp b/src/d/actor/d_a_obj_wind_stone.cpp index 3158b93848..861b402d06 100644 --- a/src/d/actor/d_a_obj_wind_stone.cpp +++ b/src/d/actor/d_a_obj_wind_stone.cpp @@ -87,6 +87,9 @@ int daWindStone_c::execute() { exeModeMapDisp(); break; case 2: + #if TARGET_PC + exeModeSetTime(); + #endif break; } setModelMtx(); @@ -180,6 +183,16 @@ void daWindStone_c::exeModeMapDisp() { } } +#if TARGET_PC +void daWindStone_c::exeModeSetTime() { + attention_info.flags = 0; + if (!chkEveOccur() && chkWlfInRange() && dusk::getSettings().game.timeStones) { + attention_info.flags |= fopAc_AttnFlag_ETC_e; + attention_info.distances[fopAc_attn_ETC_e] = 65; + } +} +#endif + bool daWindStone_c::chkMapDispMode() { if (fopAcM_isSwitch(this, getSwBit2())) { return false; diff --git a/src/dusk/imgui/ImGuiFirstRunPreset.cpp b/src/dusk/imgui/ImGuiFirstRunPreset.cpp index b73eaff6e6..f42b444ec7 100644 --- a/src/dusk/imgui/ImGuiFirstRunPreset.cpp +++ b/src/dusk/imgui/ImGuiFirstRunPreset.cpp @@ -37,6 +37,7 @@ static void ApplyPresetDusk() { s.game.instantSaves.setValue(true); s.game.midnasLamentNonStop.setValue(true); s.game.enableFrameInterpolation.setValue(true); + s.game.timeStones.setValue(true); } // ========================================================================= diff --git a/src/dusk/imgui/ImGuiMenuEnhancements.cpp b/src/dusk/imgui/ImGuiMenuEnhancements.cpp index a50ec1ee4c..7827781400 100644 --- a/src/dusk/imgui/ImGuiMenuEnhancements.cpp +++ b/src/dusk/imgui/ImGuiMenuEnhancements.cpp @@ -63,6 +63,11 @@ namespace dusk { ImGui::SetTooltip("Skip the delay when writing to the Memory Card."); } + config::ImGuiCheckbox("Time Stones", getSettings().game.timeStones); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Allows Wolf Link to use Howling Stones to set the time to midnight."); + } + ImGui::EndMenu(); } diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index 9d3d873acc..a1773b1346 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -35,6 +35,7 @@ UserSettings g_userSettings = { .noMissClimbing {"game.noMissClimbing", false}, .fastTears {"game.fastTears", false}, .instantSaves {"game.instantSaves", false}, + .timeStones {"game.timeStones", false}, // Preferences .enableMirrorMode {"game.enableMirrorMode", false}, @@ -111,6 +112,7 @@ void registerSettings() { Register(g_userSettings.game.fastClimbing); Register(g_userSettings.game.fastTears); Register(g_userSettings.game.instantSaves); + Register(g_userSettings.game.timeStones); Register(g_userSettings.game.enableMirrorMode); Register(g_userSettings.game.invertCameraXAxis); Register(g_userSettings.game.enableBloom); From f15a4f07e54da4a19737f53778544e386545cfa4 Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Mon, 13 Apr 2026 13:03:28 -0400 Subject: [PATCH 04/14] Time Stones now with Song of Time --- include/Z2AudioLib/Z2WolfHowlMgr.h | 4 ++++ src/Z2AudioLib/Z2WolfHowlMgr.cpp | 32 +++++++++++++++++++++++++---- src/d/actor/d_a_alink_wolf.inc | 33 +++++++++++++++--------------- src/d/d_kankyo.cpp | 13 ++++++++++++ 4 files changed, 61 insertions(+), 21 deletions(-) diff --git a/include/Z2AudioLib/Z2WolfHowlMgr.h b/include/Z2AudioLib/Z2WolfHowlMgr.h index 2b2595d1ea..10b746a4c1 100644 --- a/include/Z2AudioLib/Z2WolfHowlMgr.h +++ b/include/Z2AudioLib/Z2WolfHowlMgr.h @@ -39,6 +39,10 @@ enum Z2WolfHowlCurveID { Z2WOLFHOWL_NEWSONG2, Z2WOLFHOWL_NEWSONG3, + #if TARGET_PC + Z2WOLFHOWL_TIMESONG, + #endif + Z2WOLFHOWL_MAX }; diff --git a/src/Z2AudioLib/Z2WolfHowlMgr.cpp b/src/Z2AudioLib/Z2WolfHowlMgr.cpp index 4b546fc54a..5f86b237b6 100644 --- a/src/Z2AudioLib/Z2WolfHowlMgr.cpp +++ b/src/Z2AudioLib/Z2WolfHowlMgr.cpp @@ -115,7 +115,14 @@ static Z2WolfHowlLine sNewSong3[9] = { {HOWL_LINE_MID, 45}, }; -static Z2WolfHowlData sGuideData[9] = { +#if TARGET_PC +static Z2WolfHowlLine sHowlTimeSong[6] = { + {HOWL_LINE_HIGH, 30}, {HOWL_LINE_LOW, 40}, {HOWL_LINE_MID, 30}, + {HOWL_LINE_HIGH, 30}, {HOWL_LINE_LOW, 40}, {HOWL_LINE_MID, 30}, +}; +#endif + +static Z2WolfHowlData sGuideData[9 IF_DUSK(+1)] = { {ARRAY_SIZE(sHowlTobikusa), sHowlTobikusa}, {ARRAY_SIZE(sHowlUmakusa), sHowlUmakusa}, {ARRAY_SIZE(sHowlZeldaSong), sHowlZeldaSong}, @@ -125,6 +132,9 @@ static Z2WolfHowlData sGuideData[9] = { {ARRAY_SIZE(sNewSong1), sNewSong1}, {ARRAY_SIZE(sNewSong2), sNewSong2}, {ARRAY_SIZE(sNewSong3), sNewSong3}, + #if TARGET_PC + {ARRAY_SIZE(sHowlTimeSong), sHowlTimeSong}, + #endif }; Z2WolfHowlMgr::Z2WolfHowlMgr() : JASGlobalInstance(true) { @@ -356,6 +366,11 @@ void Z2WolfHowlMgr::setCorrectData(s8 curveID, Z2WolfHowlData* data) { cPitchCenter = 0.94387f; cPitchDown = 0.74915f; break; + case Z2WOLFHOWL_TIMESONG: + cPitchUp = 1.12246f; + cPitchCenter = 0.94387f; + cPitchDown = 0.74915f; + break; default: cPitchUp = 1.1892f; cPitchCenter = 1.0f; @@ -400,7 +415,7 @@ u8 Z2WolfHowlMgr::getCorrectLineNum() { return 0; } -static JAISoundID sCorrectPhrase[9] = { +static JAISoundID sCorrectPhrase[9 IF_DUSK(+1)] = { Z2BGM_HOWL_TOBIKUSA, Z2BGM_HOWL_UMAKUSA, Z2BGM_HOWL_ZELDASONG, @@ -410,9 +425,12 @@ static JAISoundID sCorrectPhrase[9] = { Z2BGM_NEW_01_HOWL, Z2BGM_NEW_02_HOWL, Z2BGM_NEW_03_HOWL, + #if TARGET_PC + 0xFFFFFFFF, + #endif }; -static JAISoundID sWindStoneSound[9] = { +static JAISoundID sWindStoneSound[9 IF_DUSK(+1)] = { 0xFFFFFFFF, 0xFFFFFFFF, Z2BGM_STONE_ZELDASONG, @@ -422,9 +440,12 @@ static JAISoundID sWindStoneSound[9] = { Z2BGM_NEW_01_STONE, Z2BGM_NEW_02_STONE, Z2BGM_NEW_03_STONE, + #if TARGET_PC + 0xFFFFFFFF, + #endif }; -static JAISoundID sCorrectDuo[9] = { +static JAISoundID sCorrectDuo[9 IF_DUSK(+1)] = { 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, @@ -434,6 +455,9 @@ static JAISoundID sCorrectDuo[9] = { Z2BGM_NEW_01_DUO, Z2BGM_NEW_02_DUO, Z2BGM_NEW_03_DUO, + #if TARGET_PC + 0xFFFFFFFF, + #endif }; s8 Z2WolfHowlMgr::checkLine() { diff --git a/src/d/actor/d_a_alink_wolf.inc b/src/d/actor/d_a_alink_wolf.inc index 7f4bef7e42..ff0755648e 100644 --- a/src/d/actor/d_a_alink_wolf.inc +++ b/src/d/actor/d_a_alink_wolf.inc @@ -3868,35 +3868,18 @@ void daAlink_c::setWolfHowlNotHappen(int param_0) { } int daAlink_c::procWolfHowlDemoInit() { - #if TARGET_PC - s16 name; - if (field_0x27f4 != NULL) { - name = fopAcM_GetName(field_0x27f4); - } else { - name = fpcNm_ALINK_e; - } - - if (name == fpcNm_Obj_WindStone_e && !static_cast(field_0x27f4)->chkEveOccur() && dusk::getSettings().game.timeStones) { - Z2GetSeMgr()->seStart(Z2SE_READ_RIDDLE_B, NULL, 0, 0, 1.0f, 1.0f, -1.0f, -1.0f, 0); - dKy_instant_timechg(0.0f); - return 0; - } - #endif - if (!dComIfGp_event_compulsory(this, NULL, 0xFFFF)) { return 0; } mDemo.setSpecialDemoType(); - #if !TARGET_PC s16 name; if (field_0x27f4 != NULL) { name = fopAcM_GetName(field_0x27f4); } else { name = fpcNm_ALINK_e; } - #endif if (name == fpcNm_Tag_WaraHowl_e) { shape_angle.y = field_0x27f4->shape_angle.y; @@ -3959,7 +3942,15 @@ int daAlink_c::procWolfHowlDemoInit() { } else if (name == fpcNm_TAG_HOWL_e) { mZ2WolfHowlMgr.setCorrectCurve(static_cast(field_0x27f4)->getCurveID()); } else if (name == fpcNm_Obj_WindStone_e) { + #if TARGET_PC + if (!static_cast(field_0x27f4)->chkEveOccur() && dusk::getSettings().game.timeStones) { + mZ2WolfHowlMgr.setCorrectCurve(9); + } else { + mZ2WolfHowlMgr.setCorrectCurve(static_cast(field_0x27f4)->getTuneId()); + } + #else mZ2WolfHowlMgr.setCorrectCurve(static_cast(field_0x27f4)->getTuneId()); + #endif mProcVar0.field_0x3008 = static_cast(field_0x27f4)->getNextSceneId(); mProcVar4.field_0x3010 = 1; } else if (name == fpcNm_Obj_SmWStone_e) { @@ -4109,6 +4100,14 @@ int daAlink_c::procWolfHowlDemo() { if (checkUnderMove0BckNoArcWolf(WANM_HOWL_END) || isSkipEdge) { if (checkAnmEnd(frameCtrl_p) || isSkipEdge) { if (mProcVar0.mHowlExitID >= 0) { + #if TARGET_PC + if (daAlink_getAlinkActorClass()->getCorrectCurveID() == 9) { + g_env_light.time_change_rate = 1.0f; + dComIfGp_event_reset(); + dCam_getBody()->EndEventCamera(fopAcM_GetID(this)); + return 1; + } + #endif dStage_changeScene(mProcVar0.mHowlExitID, 0.0f, 0, fopAcM_GetRoomNo(this), shape_angle.y, -1); } else { diff --git a/src/d/d_kankyo.cpp b/src/d/d_kankyo.cpp index 0bd3db8e2a..60e4d619a4 100644 --- a/src/d/d_kankyo.cpp +++ b/src/d/d_kankyo.cpp @@ -1540,8 +1540,21 @@ void dScnKy_env_light_c::setDaytime() { } if (dComIfGp_roomControl_getTimePass() && !field_0x130a && temp_r29) { + #if TARGET_PC + f32 prev = daytime; + #endif + daytime += time_change_rate; + #if TARGET_PC + if (time_change_rate == 1.0f && + (std::fmod(daytime - 90.0f + 360.0f, 360.0f) < std::fmod(prev - 90.0f + 360.0f, 360.0f) || + std::fmod(daytime - 285.0f + 360.0f, 360.0f) < std::fmod(prev - 285.0f + 360.0f, 360.0f))) + { + g_env_light.time_change_rate = 0.012f; + } + #endif + // Stage is Fishing Pond or Hena's Hut if (!strcmp(dComIfGp_getStartStageName(), "F_SP127") || !strcmp(dComIfGp_getStartStageName(), "R_SP127")) From e3b3eabfeb91f68e7b89efe108d17cb41100a23a Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Mon, 13 Apr 2026 13:05:11 -0400 Subject: [PATCH 05/14] oops forgot a define somewhere --- src/Z2AudioLib/Z2WolfHowlMgr.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Z2AudioLib/Z2WolfHowlMgr.cpp b/src/Z2AudioLib/Z2WolfHowlMgr.cpp index 5f86b237b6..320b861fa8 100644 --- a/src/Z2AudioLib/Z2WolfHowlMgr.cpp +++ b/src/Z2AudioLib/Z2WolfHowlMgr.cpp @@ -366,11 +366,13 @@ void Z2WolfHowlMgr::setCorrectData(s8 curveID, Z2WolfHowlData* data) { cPitchCenter = 0.94387f; cPitchDown = 0.74915f; break; + #if TARGET_PC case Z2WOLFHOWL_TIMESONG: cPitchUp = 1.12246f; cPitchCenter = 0.94387f; cPitchDown = 0.74915f; break; + #endif default: cPitchUp = 1.1892f; cPitchCenter = 1.0f; From 143aa51eb370aa59941138abd9f26a8c5a7e5a52 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Mon, 13 Apr 2026 19:09:16 +0200 Subject: [PATCH 06/14] Make JUTResFont load all texture data into one texture Together with the previous change, this enables entire blocks of text to be rendered in one draw call. --- .../include/JSystem/JUtility/JUTFont.h | 3 +- .../include/JSystem/JUtility/JUTResFont.h | 14 +++- libs/JSystem/src/JUtility/JUTResFont.cpp | 76 ++++++++++--------- 3 files changed, 51 insertions(+), 42 deletions(-) diff --git a/libs/JSystem/include/JSystem/JUtility/JUTFont.h b/libs/JSystem/include/JSystem/JUtility/JUTFont.h index d07e09a590..3c1cd0bf1f 100644 --- a/libs/JSystem/include/JSystem/JUtility/JUTFont.h +++ b/libs/JSystem/include/JSystem/JUtility/JUTFont.h @@ -7,8 +7,7 @@ #if TARGET_PC struct FontDrawContext { - int loadedBlock = -1; - int loadedPage = -1; + bool isTextureLoaded = false; }; #define FONT_DRAW_CTX , FontDrawContext* context #define FONT_DRAW_CTX_ARG , context diff --git a/libs/JSystem/include/JSystem/JUtility/JUTResFont.h b/libs/JSystem/include/JSystem/JUtility/JUTResFont.h index 68fd798ba5..d9094f72a9 100644 --- a/libs/JSystem/include/JSystem/JUtility/JUTResFont.h +++ b/libs/JSystem/include/JSystem/JUtility/JUTResFont.h @@ -18,10 +18,6 @@ struct BlockHeader { BE(u32) size; }; -#if TARGET_PC -struct GlyphTextures; -#endif - /** * @ingroup jsystem-jutility * @@ -91,6 +87,16 @@ public: /* 0x66 */ u16 field_0x66; /* 0x68 */ u16 mMaxCode; /* 0x6C */ const IsLeadByte_func* mIsLeadByte; + +#if TARGET_PC + // Dusk change: we use a single large texture for all characters. + // This enables better draw call merging, ideally enabling entire blocks of + // text to be one draw call. + TGXTexObj mJoinedTextureObject; + u16 mJoinedTextureHeight; + + void initJoinedTexture(); +#endif }; extern u8 const JUTResFONT_Ascfont_fix12[]; diff --git a/libs/JSystem/src/JUtility/JUTResFont.cpp b/libs/JSystem/src/JUtility/JUTResFont.cpp index 8583d42c58..3ef83b227e 100644 --- a/libs/JSystem/src/JUtility/JUTResFont.cpp +++ b/libs/JSystem/src/JUtility/JUTResFont.cpp @@ -6,26 +6,13 @@ #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); @@ -33,10 +20,7 @@ JUTResFont::JUTResFont(const ResFONT* pFont, JKRHeap* pHeap) { JUTResFont::~JUTResFont() { #if TARGET_PC - for (auto& pair : mGlyphTextures->textures) { - pair.second.reset(); - } - delete mGlyphTextures; + mJoinedTextureObject.reset(); #endif if (mValid) { @@ -70,6 +54,33 @@ bool JUTResFont::initiate(const ResFONT* pFont, JKRHeap* pHeap) { return true; } +#if TARGET_PC +void JUTResFont::initJoinedTexture() { + if (mGly1BlockNum != 1) { + CRASH("mGly1BlockNum must be 1!"); + } + + const auto& block = *mpGlyphBlocks[0]; + if (block.textureWidth % 8 != 0 || block.textureHeight % 8 != 0) { + // Idk how the GameCube's tiling texture format works so this is a safety check. + CRASH("Texture size not divisible!"); + } + + int pageCount = 0; + u32 pageNumCells = block.numRows * block.numColumns; + for (u32 code = block.startCode; code < block.endCode; code += pageNumCells) { + pageCount += 1; + } + + mJoinedTextureHeight = block.textureHeight * pageCount; + GXInitTexObj(&mJoinedTextureObject, block.data, block.textureWidth, + mJoinedTextureHeight, static_cast(block.textureFormat.host()), + GX_CLAMP, GX_CLAMP, false); + + GXInitTexObjLOD(&mJoinedTextureObject, GX_LINEAR, GX_LINEAR, 0.0f, 0.0f, 0.0f, 0U, 0U, GX_ANISO_1); +} +#endif + bool JUTResFont::protected_initiate(const ResFONT* pFont, JKRHeap* pHeap) { void** p; delete_and_initialize(); @@ -100,8 +111,10 @@ bool JUTResFont::protected_initiate(const ResFONT* pFont, JKRHeap* pHeap) { mpMapBlocks = JKR_NEW_ARRAY_ARGS(ResFONT::MAP1*, mMap1BlockNum, p); } setBlock(); - return true; + IF_DUSK(initJoinedTexture()); + + return true; } void JUTResFont::countBlock() { @@ -258,8 +271,13 @@ f32 JUTResFont::drawChar_scale(f32 pos_x, f32 pos_y, f32 scale_x, f32 scale_y, i f32 y2 = getDescent() * (scale_y / getHeight()) + pos_y; u16 texW = mpGlyphBlocks[field_0x66]->textureWidth; +#if TARGET_PC + u16 texH = mJoinedTextureHeight; +#else u16 texH = mpGlyphBlocks[field_0x66]->textureHeight; +#endif u16 cellW = mpGlyphBlocks[field_0x66]->cellWidth; + u16 cellH = mpGlyphBlocks[field_0x66]->cellHeight; s32 u1 = (mWidth * 0x8000) / texW; s32 v1 = (mHeight * 0x8000) / texH; @@ -456,26 +474,12 @@ void JUTResFont::loadImage(int code, GXTexMapID id FONT_DRAW_CTX){ mHeight = cellRow * cellH; #if TARGET_PC - if (!context || context->loadedPage != pageIdx || context->loadedBlock != i) { - const auto found = mGlyphTextures->textures.find(pageIdx); - GXTexObj* texObj; - if (found == mGlyphTextures->textures.end()) { - texObj = &mGlyphTextures->textures[pageIdx]; - 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); + mHeight += texH * pageIdx; + if (!context || !context->isTextureLoaded) { + GXLoadTexObj(&mJoinedTextureObject, id); if (context) { - context->loadedBlock = i; - context->loadedPage = pageIdx; + context->isTextureLoaded = true; } } From d865c82f760eecbaca9355af01f9b23bffc5de72 Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Mon, 13 Apr 2026 18:08:05 -0400 Subject: [PATCH 07/14] Added howling anywhere (R+X) --- include/d/actor/d_a_alink.h | 1 + include/d/actor/d_a_obj_wind_stone.h | 3 -- include/dusk/settings.h | 2 +- src/Z2AudioLib/Z2WolfHowlMgr.cpp | 8 ++-- src/d/actor/d_a_alink_dusk.cpp | 59 ++++++++++++++++++++++++ src/d/actor/d_a_alink_wolf.inc | 35 +++++++------- src/d/actor/d_a_obj_wind_stone.cpp | 13 ------ src/dusk/imgui/ImGuiFirstRunPreset.cpp | 2 +- src/dusk/imgui/ImGuiMenuEnhancements.cpp | 14 ++++-- src/dusk/settings.cpp | 4 +- src/f_ap/f_ap_game.cpp | 6 +++ 11 files changed, 102 insertions(+), 45 deletions(-) diff --git a/include/d/actor/d_a_alink.h b/include/d/actor/d_a_alink.h index b68a4de5a9..53228af84b 100644 --- a/include/d/actor/d_a_alink.h +++ b/include/d/actor/d_a_alink.h @@ -4549,6 +4549,7 @@ public: /* 0x03850 */ daAlink_procFunc mpProcFunc; #if TARGET_PC + void handleWolfHowl(); void handleQuickTransform(); bool checkGyroAimItemContext(); #endif diff --git a/include/d/actor/d_a_obj_wind_stone.h b/include/d/actor/d_a_obj_wind_stone.h index 7e44cb7108..f5e52dfc94 100644 --- a/include/d/actor/d_a_obj_wind_stone.h +++ b/include/d/actor/d_a_obj_wind_stone.h @@ -27,9 +27,6 @@ public: bool chkEveOccur(); void exeModeHowl(); void exeModeMapDisp(); - #if TARGET_PC - void exeModeSetTime(); - #endif bool chkMapDispMode(); u8 getGoldWolfIdx(); diff --git a/include/dusk/settings.h b/include/dusk/settings.h index b6a54d3115..4cda8654b6 100644 --- a/include/dusk/settings.h +++ b/include/dusk/settings.h @@ -61,7 +61,7 @@ struct UserSettings { ConfigVar noMissClimbing; ConfigVar fastTears; ConfigVar instantSaves; - ConfigVar timeStones; + ConfigVar sunsSong; // Preferences ConfigVar enableMirrorMode; diff --git a/src/Z2AudioLib/Z2WolfHowlMgr.cpp b/src/Z2AudioLib/Z2WolfHowlMgr.cpp index 320b861fa8..cb4f387a21 100644 --- a/src/Z2AudioLib/Z2WolfHowlMgr.cpp +++ b/src/Z2AudioLib/Z2WolfHowlMgr.cpp @@ -117,8 +117,8 @@ static Z2WolfHowlLine sNewSong3[9] = { #if TARGET_PC static Z2WolfHowlLine sHowlTimeSong[6] = { - {HOWL_LINE_HIGH, 30}, {HOWL_LINE_LOW, 40}, {HOWL_LINE_MID, 30}, - {HOWL_LINE_HIGH, 30}, {HOWL_LINE_LOW, 40}, {HOWL_LINE_MID, 30}, + {HOWL_LINE_MID, 20}, {HOWL_LINE_LOW, 20}, {HOWL_LINE_HIGH, 40}, + {HOWL_LINE_MID, 20}, {HOWL_LINE_LOW, 20}, {HOWL_LINE_HIGH, 40}, }; #endif @@ -368,9 +368,9 @@ void Z2WolfHowlMgr::setCorrectData(s8 curveID, Z2WolfHowlData* data) { break; #if TARGET_PC case Z2WOLFHOWL_TIMESONG: - cPitchUp = 1.12246f; + cPitchUp = 1.259906f; cPitchCenter = 0.94387f; - cPitchDown = 0.74915f; + cPitchDown = 0.840885f; break; #endif default: diff --git a/src/d/actor/d_a_alink_dusk.cpp b/src/d/actor/d_a_alink_dusk.cpp index dbff923a2e..9b937d3678 100644 --- a/src/d/actor/d_a_alink_dusk.cpp +++ b/src/d/actor/d_a_alink_dusk.cpp @@ -4,6 +4,65 @@ #include "d/d_meter2_draw.h" #include "d/d_meter2_info.h" +void daAlink_c::handleWolfHowl() { + if (checkWolf()) { + if (!dusk::getSettings().game.sunsSong) { + return; + } + + // Check to see if Link has the ability to transform. + if (!dComIfGs_isEventBit(dSv_event_flag_c::M_077)) { + return; + } + + // 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 link is not in a cutscene. + if (checkEventRun()) { + Z2GetAudioMgr()->seStart(Z2SE_SYS_ERROR, NULL, 0, 0, 1.0f, 1.0f, -1.0f, -1.0f, 0); + 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; + } + + 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; + } + } + } + } + + getWolfHowlMgrP()->setCorrectCurve(9); + procWolfHowlDemoInit(); + } +} + void daAlink_c::handleQuickTransform() { if (!dusk::getSettings().game.enableQuickTransform) { return; diff --git a/src/d/actor/d_a_alink_wolf.inc b/src/d/actor/d_a_alink_wolf.inc index ff0755648e..0a6da841c5 100644 --- a/src/d/actor/d_a_alink_wolf.inc +++ b/src/d/actor/d_a_alink_wolf.inc @@ -3942,15 +3942,7 @@ int daAlink_c::procWolfHowlDemoInit() { } else if (name == fpcNm_TAG_HOWL_e) { mZ2WolfHowlMgr.setCorrectCurve(static_cast(field_0x27f4)->getCurveID()); } else if (name == fpcNm_Obj_WindStone_e) { - #if TARGET_PC - if (!static_cast(field_0x27f4)->chkEveOccur() && dusk::getSettings().game.timeStones) { - mZ2WolfHowlMgr.setCorrectCurve(9); - } else { - mZ2WolfHowlMgr.setCorrectCurve(static_cast(field_0x27f4)->getTuneId()); - } - #else mZ2WolfHowlMgr.setCorrectCurve(static_cast(field_0x27f4)->getTuneId()); - #endif mProcVar0.field_0x3008 = static_cast(field_0x27f4)->getNextSceneId(); mProcVar4.field_0x3010 = 1; } else if (name == fpcNm_Obj_SmWStone_e) { @@ -3962,7 +3954,13 @@ int daAlink_c::procWolfHowlDemoInit() { mZ2WolfHowlMgr.setCorrectCurve(-1); } } else { + #if TARGET_PC + if (mZ2WolfHowlMgr.getCorrectCurveID() != 9) { + mZ2WolfHowlMgr.setCorrectCurve(-1); + } + #else mZ2WolfHowlMgr.setCorrectCurve(-1); + #endif } mNormalSpeed = 0.0f; @@ -4100,17 +4098,22 @@ int daAlink_c::procWolfHowlDemo() { if (checkUnderMove0BckNoArcWolf(WANM_HOWL_END) || isSkipEdge) { if (checkAnmEnd(frameCtrl_p) || isSkipEdge) { if (mProcVar0.mHowlExitID >= 0) { - #if TARGET_PC - if (daAlink_getAlinkActorClass()->getCorrectCurveID() == 9) { - g_env_light.time_change_rate = 1.0f; - dComIfGp_event_reset(); - dCam_getBody()->EndEventCamera(fopAcM_GetID(this)); - return 1; - } - #endif dStage_changeScene(mProcVar0.mHowlExitID, 0.0f, 0, fopAcM_GetRoomNo(this), shape_angle.y, -1); } else { + #if TARGET_PC + if (daAlink_getAlinkActorClass()->getCorrectCurveID() == 9) { + if (dComIfGp_roomControl_getTimePass()) { + g_env_light.time_change_rate = 1.0f; + dComIfGp_event_reset(); + dCam_getBody()->EndEventCamera(fopAcM_GetID(this)); + } else { + setWolfHowlNotHappen(isSkipEdge); + } + return 1; + } + #endif + fopAc_ac_c* actor_p = NULL; if (gwolf_p == NULL) { fopAcIt_Executor((fopAcIt_ExecutorFunc)daAlink_searchWolfHowl, diff --git a/src/d/actor/d_a_obj_wind_stone.cpp b/src/d/actor/d_a_obj_wind_stone.cpp index 861b402d06..3158b93848 100644 --- a/src/d/actor/d_a_obj_wind_stone.cpp +++ b/src/d/actor/d_a_obj_wind_stone.cpp @@ -87,9 +87,6 @@ int daWindStone_c::execute() { exeModeMapDisp(); break; case 2: - #if TARGET_PC - exeModeSetTime(); - #endif break; } setModelMtx(); @@ -183,16 +180,6 @@ void daWindStone_c::exeModeMapDisp() { } } -#if TARGET_PC -void daWindStone_c::exeModeSetTime() { - attention_info.flags = 0; - if (!chkEveOccur() && chkWlfInRange() && dusk::getSettings().game.timeStones) { - attention_info.flags |= fopAc_AttnFlag_ETC_e; - attention_info.distances[fopAc_attn_ETC_e] = 65; - } -} -#endif - bool daWindStone_c::chkMapDispMode() { if (fopAcM_isSwitch(this, getSwBit2())) { return false; diff --git a/src/dusk/imgui/ImGuiFirstRunPreset.cpp b/src/dusk/imgui/ImGuiFirstRunPreset.cpp index ec11511c3c..c218185c92 100644 --- a/src/dusk/imgui/ImGuiFirstRunPreset.cpp +++ b/src/dusk/imgui/ImGuiFirstRunPreset.cpp @@ -39,7 +39,7 @@ static void ApplyPresetDusk() { s.game.instantSaves.setValue(true); s.game.midnasLamentNonStop.setValue(true); s.game.enableFrameInterpolation.setValue(true); - s.game.timeStones.setValue(true); + s.game.sunsSong.setValue(true); s.game.bloomMode.setValue(BloomMode::Dusk); } diff --git a/src/dusk/imgui/ImGuiMenuEnhancements.cpp b/src/dusk/imgui/ImGuiMenuEnhancements.cpp index 7827781400..8c65c97912 100644 --- a/src/dusk/imgui/ImGuiMenuEnhancements.cpp +++ b/src/dusk/imgui/ImGuiMenuEnhancements.cpp @@ -11,6 +11,15 @@ namespace dusk { if (ImGui::BeginMenu("Enhancements")) { if (ImGui::BeginMenu("Quality of Life")) { config::ImGuiCheckbox("Quick Transform (R+Y)", getSettings().game.enableQuickTransform); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Allows you to quickly transform between forms\n" + "without having to talk to Midna."); + } + + config::ImGuiCheckbox("Sun's Song (R+X)", getSettings().game.sunsSong); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Allows Wolf Link to howl and change the time of day."); + } config::ImGuiCheckbox("Bigger Wallets", getSettings().game.biggerWallets); if (ImGui::IsItemHovered()) { @@ -63,11 +72,6 @@ namespace dusk { ImGui::SetTooltip("Skip the delay when writing to the Memory Card."); } - config::ImGuiCheckbox("Time Stones", getSettings().game.timeStones); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Allows Wolf Link to use Howling Stones to set the time to midnight."); - } - ImGui::EndMenu(); } diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index a6d5037db7..f25825da90 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -35,7 +35,7 @@ UserSettings g_userSettings = { .noMissClimbing {"game.noMissClimbing", false}, .fastTears {"game.fastTears", false}, .instantSaves {"game.instantSaves", false}, - .timeStones {"game.timeStones", false}, + .sunsSong {"game.sunsSong", false}, // Preferences .enableMirrorMode {"game.enableMirrorMode", false}, @@ -112,7 +112,7 @@ void registerSettings() { Register(g_userSettings.game.fastClimbing); Register(g_userSettings.game.fastTears); Register(g_userSettings.game.instantSaves); - Register(g_userSettings.game.timeStones); + Register(g_userSettings.game.sunsSong); Register(g_userSettings.game.enableMirrorMode); Register(g_userSettings.game.invertCameraXAxis); Register(g_userSettings.game.bloomMode); diff --git a/src/f_ap/f_ap_game.cpp b/src/f_ap/f_ap_game.cpp index f54ca1edad..84d2ab8e97 100644 --- a/src/f_ap/f_ap_game.cpp +++ b/src/f_ap/f_ap_game.cpp @@ -748,6 +748,12 @@ void fapGm_Execute() { #endif #if TARGET_PC + if (mDoCPd_c::getHoldR(PAD_1) && mDoCPd_c::getTrigX(PAD_1)) { + if (const auto link = g_dComIfG_gameInfo.play.getPlayer(0)) { + dynamic_cast(link)->handleWolfHowl(); + } + } + 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(); From 5155270fd3fc06e97611dc015582cd61209094b3 Mon Sep 17 00:00:00 2001 From: kipcode66 Date: Mon, 13 Apr 2026 22:17:06 -0400 Subject: [PATCH 08/14] Add flake.nix to build on NixOS --- flake.lock | 27 +++++++++++++++++++++++++++ flake.nix | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 flake.lock create mode 100644 flake.nix diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000000..8ec14d2852 --- /dev/null +++ b/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1775710090, + "narHash": "sha256-ar3rofg+awPB8QXDaFJhJ2jJhu+KqN/PRCXeyuXR76E=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "4c1018dae018162ec878d42fec712642d214fdfa", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000000..cd703bda64 --- /dev/null +++ b/flake.nix @@ -0,0 +1,33 @@ +{ + inputs = { + nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable"; + }; + outputs = { self, nixpkgs }: + let + pkgs = import nixpkgs { system = "x86_64-linux"; }; + dusk = pkgs.stdenv.mkDerivation { + name = "dusk"; + src = ./.; + nativeBuildInputs = [ + pkgs.cmake + pkgs.pkg-config + pkgs.wayland + ]; + buildInputs = [ + pkgs.libGL + pkgs.libX11 + pkgs.libXcursor + pkgs.libxi + pkgs.libxcb + pkgs.libxrandr + pkgs.libxscrnsaver + pkgs.libxtst + pkgs.libjpeg8 + pkgs.libxkbcommon + pkgs.libglvnd + ]; + }; + in { + packages.x86_64-linux.default = dusk; + }; +} From 00803b53ab480fbf2b623f1c36c2ebccbc2e37a6 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Tue, 14 Apr 2026 00:57:45 -0600 Subject: [PATCH 09/14] I'm silly --- src/dusk/crash_reporting.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/dusk/crash_reporting.cpp b/src/dusk/crash_reporting.cpp index 1b1adbf774..2d7534965e 100644 --- a/src/dusk/crash_reporting.cpp +++ b/src/dusk/crash_reporting.cpp @@ -76,8 +76,6 @@ std::filesystem::path GetCrashpadHandlerPath() { } const std::filesystem::path handlerDir(basePath); - SDL_free(const_cast(basePath)); - #if _WIN32 return handlerDir / "crashpad_handler.exe"; #else From 588910c642484cb8aae0fafb1351afec3679c025 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Tue, 14 Apr 2026 01:13:43 -0600 Subject: [PATCH 10/14] Fix DUSK_VERSION_STRING on tag commits --- CMakeLists.txt | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7a213b819b..8ecd64c6ad 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,12 +48,17 @@ 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}") +if (DUSK_WC_DESCRIBE MATCHES "^v([0-9]+)\\.([0-9]+)\\.([0-9]+)(-([0-9]+).*)?$") + set(DUSK_SHORT_VERSION_STRING "${CMAKE_MATCH_1}.${CMAKE_MATCH_2}.${CMAKE_MATCH_3}") + if (CMAKE_MATCH_5) + set(DUSK_VERSION_STRING "${DUSK_SHORT_VERSION_STRING}.${CMAKE_MATCH_5}") + else () + set(DUSK_VERSION_STRING "${DUSK_SHORT_VERSION_STRING}.0") + endif () else () set(DUSK_WC_DESCRIBE "UNKNOWN-VERSION") - set(DUSK_VERSION_STRING "0.0.0") + set(DUSK_VERSION_STRING "0.0.0.0") + set(DUSK_SHORT_VERSION_STRING "0.0.0") endif () # Add version information to CI environment variables From 27d95c37d5cd30acb6ae2a911857760b983540db Mon Sep 17 00:00:00 2001 From: Luke Street Date: Tue, 14 Apr 2026 01:32:58 -0600 Subject: [PATCH 11/14] Create log files under configDir/logs; seed initial pipeline cache --- include/dusk/logging.h | 3 + src/dusk/crash_reporting.cpp | 16 ++++ src/dusk/logging.cpp | 140 +++++++++++++++++++++++++++++------ src/m_Do/m_Do_main.cpp | 50 ++++++++++++- 4 files changed, 186 insertions(+), 23 deletions(-) diff --git a/include/dusk/logging.h b/include/dusk/logging.h index 7f239d8b45..0a9cbf238d 100644 --- a/include/dusk/logging.h +++ b/include/dusk/logging.h @@ -7,6 +7,9 @@ void aurora_log_callback(AuroraLogLevel level, const char* module, const char* message, unsigned int len); namespace dusk { + void InitializeFileLogging(const char* configDir, AuroraLogLevel logLevel); + void ShutdownFileLogging(); + const char* GetLogFilePath(); void SendToStubLog(AuroraLogLevel level, const char* module, const char* message); } diff --git a/src/dusk/crash_reporting.cpp b/src/dusk/crash_reporting.cpp index 2d7534965e..73f432e418 100644 --- a/src/dusk/crash_reporting.cpp +++ b/src/dusk/crash_reporting.cpp @@ -69,6 +69,13 @@ std::filesystem::path GetSentryDatabasePath() { return std::filesystem::path(configPath) / "sentry"; } +std::filesystem::path GetLogAttachmentPath() { + if (const char* logPath = GetLogFilePath()) { + return logPath; + } + return {}; +} + std::filesystem::path GetCrashpadHandlerPath() { const char* basePath = SDL_GetBasePath(); if (!basePath) { @@ -111,6 +118,15 @@ void ConfigurePathOptions(sentry_options_t* options) { sentry_options_set_handler_path(options, handlerPathUtf8.c_str()); } #endif + + const auto logPath = GetLogAttachmentPath(); + if (!logPath.empty()) { +#if _WIN32 + sentry_options_add_attachmentw(options, logPath.wstring().c_str()); +#else + sentry_options_add_attachment(options, logPath.string().c_str()); +#endif + } } #endif diff --git a/src/dusk/logging.cpp b/src/dusk/logging.cpp index 37d1d4f55f..323945dd8b 100644 --- a/src/dusk/logging.cpp +++ b/src/dusk/logging.cpp @@ -1,6 +1,11 @@ #include "dusk/logging.h" +#include +#include #include #include +#include +#include +#include #include "tracy/Tracy.hpp" @@ -20,6 +25,60 @@ static constexpr std::string_view StubFragments[] = { "but selective updates are not implemented"sv, }; +namespace { +std::mutex g_logMutex; +FILE* g_logFile = nullptr; +std::string g_logFilePath; + +const char* LogLevelString(AuroraLogLevel level) { + switch (level) { + case LOG_DEBUG: + return "DEBUG"; + case LOG_INFO: + return "INFO"; + case LOG_WARNING: + return "WARNING"; + case LOG_ERROR: + return "ERROR"; + case LOG_FATAL: + return "FATAL"; + } + + return "??"; +} + +FILE* LogStreamForLevel(AuroraLogLevel level) { + return level >= LOG_ERROR ? stderr : stdout; +} + +std::string MakeTimestampedLogName() { + const auto now = std::chrono::system_clock::now(); + const std::time_t nowTime = std::chrono::system_clock::to_time_t(now); + + std::tm localTime{}; +#if _WIN32 + localtime_s(&localTime, &nowTime); +#else + localtime_r(&nowTime, &localTime); +#endif + + std::array buffer{}; + std::strftime(buffer.data(), buffer.size(), "dusk-%Y%m%d-%H%M%S.log", &localTime); + return buffer.data(); +} + +void WriteLogLine(FILE* out, const char* levelStr, const char* module, const char* message, unsigned int len) { + if (out == nullptr) { + return; + } + + std::fprintf(out, "[%s | %s] ", levelStr, module); + std::fwrite(message, 1, len, out); + std::fputc('\n', out); + std::fflush(out); +} +} // namespace + static bool IsForStubLog(const char* message) { std::string_view msg_view(message); @@ -40,32 +99,69 @@ void aurora_log_callback(AuroraLogLevel level, const char* module, const char* m return; } - const char* levelStr = "??"; - FILE* out = stdout; - switch (level) { - case LOG_DEBUG: - levelStr = "DEBUG"; - break; - case LOG_INFO: - levelStr = "INFO"; - break; - case LOG_WARNING: - levelStr = "WARNING"; - break; - case LOG_ERROR: - levelStr = "ERROR"; - out = stderr; - break; - case LOG_FATAL: - levelStr = "FATAL"; - out = stderr; - break; + if (module == nullptr) { + module = ""; } - fprintf(out, "[%s | %s] %s\n", levelStr, module, message); + + const char* levelStr = LogLevelString(level); + FILE* out = LogStreamForLevel(level); + WriteLogLine(out, levelStr, module, message, len); + + { + std::lock_guard lock(g_logMutex); + if (g_logFile != nullptr) { + WriteLogLine(g_logFile, levelStr, module, message, len); + } + } + if (level == LOG_FATAL) { - fflush(out); abort(); } } aurora::Module DuskLog("dusk"); + +void dusk::InitializeFileLogging(const char* configDir, AuroraLogLevel logLevel) { + std::lock_guard lock(g_logMutex); + if (g_logFile != nullptr || configDir == nullptr) { + return; + } + + std::error_code ec; + const std::filesystem::path logsDir = std::filesystem::path(configDir) / "logs"; + std::filesystem::create_directories(logsDir, ec); + if (ec) { + std::fprintf(stderr, "[WARNING | dusk] Failed to create log directory '%s': %s\n", + logsDir.string().c_str(), ec.message().c_str()); + return; + } + + const std::filesystem::path logPath = logsDir / MakeTimestampedLogName(); + g_logFile = std::fopen(logPath.string().c_str(), "wb"); + if (g_logFile == nullptr) { + std::fprintf(stderr, "[WARNING | dusk] Failed to open log file '%s'\n", + logPath.string().c_str()); + return; + } + + g_logFilePath = logPath.string(); + aurora::g_config.logCallback = &aurora_log_callback; + aurora::g_config.logLevel = logLevel; + WriteLogLine(g_logFile, "INFO", "dusk", "File logging initialized", 24); +} + +void dusk::ShutdownFileLogging() { + std::lock_guard lock(g_logMutex); + if (g_logFile == nullptr) { + return; + } + + std::fflush(g_logFile); + std::fclose(g_logFile); + g_logFile = nullptr; +} + +const char* dusk::GetLogFilePath() { + std::lock_guard lock(g_logMutex); + return g_logFilePath.empty() ? nullptr : g_logFilePath.c_str(); +} diff --git a/src/m_Do/m_Do_main.cpp b/src/m_Do/m_Do_main.cpp index 2c6278e202..fbe0bce3cc 100644 --- a/src/m_Do/m_Do_main.cpp +++ b/src/m_Do/m_Do_main.cpp @@ -43,6 +43,8 @@ #include #include +#include +#include #include #include "SSystem/SComponent/c_API.h" #include "dusk/app_info.hpp" @@ -366,6 +368,48 @@ static const char* CalculateConfigPath() { return result; } +static void EnsureInitialPipelineCache(const char* configDir) { + if (configDir == nullptr) { + return; + } + + const std::filesystem::path configPathFs(configDir); + const std::filesystem::path pipelineCachePath = configPathFs / "pipeline_cache.db"; + if (std::filesystem::exists(pipelineCachePath)) { + return; + } + + const char* basePath = SDL_GetBasePath(); + if (basePath == nullptr) { + DuskLog.warn("Unable to resolve base path while seeding pipeline cache: {}", SDL_GetError()); + return; + } + + const std::filesystem::path initialPipelineCachePath = + std::filesystem::path(basePath) / "initial_pipeline_cache.db"; + if (!std::filesystem::exists(initialPipelineCachePath)) { + DuskLog.info("No bundled initial pipeline cache found at '{}'", initialPipelineCachePath.string()); + return; + } + + std::error_code ec; + std::filesystem::create_directories(configPathFs, ec); + if (ec) { + DuskLog.warn("Failed to create config directory '{}' for pipeline cache: {}", + configPathFs.string(), ec.message()); + return; + } + + std::filesystem::copy_file(initialPipelineCachePath, pipelineCachePath, std::filesystem::copy_options::none, ec); + if (ec) { + DuskLog.warn("Failed to seed pipeline cache from '{}' to '{}': {}", + initialPipelineCachePath.string(), pipelineCachePath.string(), ec.message()); + return; + } + + DuskLog.info("Seeded pipeline cache from '{}'", initialPipelineCachePath.string()); +} + static constexpr PADDefaultMapping defaultPadMapping = { .buttons = { {SDL_GAMEPAD_BUTTON_SOUTH, PAD_BUTTON_A}, @@ -444,10 +488,13 @@ int game_main(int argc, char* argv[]) { } configPath = CalculateConfigPath(); + const auto startupLogLevel = static_cast(parsed_arg_options["log-level"].as()); + dusk::InitializeFileLogging(configPath, startupLogLevel); dusk::config::LoadFromUserPreferences(); ApplyCVarOverrides(parsed_arg_options["cvar"]); dusk::InitializeCrashReporting(); + EnsureInitialPipelineCache(configPath); AuroraConfig config{}; config.appName = dusk::AppName; @@ -460,7 +507,7 @@ int game_main(int argc, char* argv[]) { config.windowHeight = defaultWindowHeight * 2; config.desiredBackend = ResolveDesiredBackend(parsed_arg_options); config.logCallback = &aurora_log_callback; - config.logLevel = (AuroraLogLevel)parsed_arg_options["log-level"].as(); + config.logLevel = startupLogLevel; config.mem1Size = 256 * 1024 * 1024; config.mem2Size = 24 * 1024 * 1024; config.allowJoystickBackgroundEvents = true; @@ -542,6 +589,7 @@ int game_main(int argc, char* argv[]) { main01(); dusk::ShutdownCrashReporting(); + dusk::ShutdownFileLogging(); fflush(stdout); fflush(stderr); From 505ef363fde9149093491c468111c1d8ccbd74fd Mon Sep 17 00:00:00 2001 From: madeline Date: Tue, 14 Apr 2026 01:55:14 -0700 Subject: [PATCH 12/14] more save state fixes --- src/d/actor/d_a_alink.cpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/d/actor/d_a_alink.cpp b/src/d/actor/d_a_alink.cpp index 7d102b8554..6787c53de7 100644 --- a/src/d/actor/d_a_alink.cpp +++ b/src/d/actor/d_a_alink.cpp @@ -19316,11 +19316,20 @@ void daAlink_c::setWaterDropColor(const J3DGXColorS10* i_color) { if (!checkNoResetFlg2(FLG2_UNK_80000)) { if (checkZoraWearAbility()) { +#if TARGET_PC + if (field_0x064C->getMaterialNum() >= 14) +#endif + { field_0x064C->getMaterialNodePointer(13)->setTevColor(1, i_color); field_0x064C->getMaterialNodePointer(0)->setTevColor(1, i_color); field_0x064C->getMaterialNodePointer(1)->setTevColor(1, i_color); mpLinkHatModel->getModelData()->getMaterialNodePointer(1)->setTevColor(1, i_color); + } } else if (checkMagicArmorWearAbility()) { +#if TARGET_PC + if (field_0x064C->getMaterialNum() >= 12) +#endif + { field_0x064C->getMaterialNodePointer(11)->setTevColor(1, i_color); field_0x064C->getMaterialNodePointer(10)->setTevColor(1, i_color); field_0x064C->getMaterialNodePointer(9)->setTevColor(1, i_color); @@ -19328,11 +19337,21 @@ void daAlink_c::setWaterDropColor(const J3DGXColorS10* i_color) { field_0x064C->getMaterialNodePointer(6)->setTevColor(1, i_color); mpLinkHatModel->getModelData()->getMaterialNodePointer(2)->setTevColor(1, i_color); mpLinkHatModel->getModelData()->getMaterialNodePointer(1)->setTevColor(1, i_color); + } } else if (checkCasualWearFlg()) { +#if TARGET_PC + if (field_0x064C->getMaterialNum() >= 8) +#endif + { field_0x064C->getMaterialNodePointer(7)->setTevColor(1, i_color); mpLinkHatModel->getModelData()->getMaterialNodePointer(0)->setTevColor(1, i_color); field_0x064C->getMaterialNodePointer(5)->setTevColor(1, var_r31); + } } else { +#if TARGET_PC + if (field_0x064C->getMaterialNum() >= 18) +#endif + { field_0x064C->getMaterialNodePointer(17)->setTevColor(1, i_color); field_0x064C->getMaterialNodePointer(9)->setTevColor(1, i_color); field_0x064C->getMaterialNodePointer(0)->setTevColor(1, i_color); @@ -19342,6 +19361,7 @@ void daAlink_c::setWaterDropColor(const J3DGXColorS10* i_color) { field_0x064C->getMaterialNodePointer(16)->setTevColor(1, var_r31); field_0x064C->getMaterialNodePointer(15)->setTevColor(1, var_r31); field_0x064C->getMaterialNodePointer(14)->setTevColor(1, var_r31); + } } } } From ddfe21b18a59feb8b2934ae7908564fbf0b1a495 Mon Sep 17 00:00:00 2001 From: CraftyBoss Date: Tue, 14 Apr 2026 02:45:16 -0700 Subject: [PATCH 13/14] fix shadow resolution changing causing a crash operator delete[] was used instead of JKR_DELETE_ARRAY, which causes standard allocator to freak out --- src/d/d_drawlist.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/d/d_drawlist.cpp b/src/d/d_drawlist.cpp index 2ef4732455..24798f4482 100644 --- a/src/d/d_drawlist.cpp +++ b/src/d/d_drawlist.cpp @@ -1459,7 +1459,7 @@ void dDlst_shadowControl_c::init() { #else u32 buffer_size = GXGetTexBufferSize(size, size, 5, GX_DISABLE, 0); #endif - delete mShadowTexData[i]; + JKR_DELETE_ARRAY(mShadowTexData[i]); mShadowTexData[i] = JKR_NEW_ARRAY_ARGS(u8, buffer_size, 0x20); mShadowTexObj[i].reset(); From 86c1e21ac151e3b5377a29f1fd9c7c8b37bd3d67 Mon Sep 17 00:00:00 2001 From: CraftyBoss Date: Tue, 14 Apr 2026 03:07:47 -0700 Subject: [PATCH 14/14] fix compilation error GlyphTextures no longer exists --- libs/JSystem/include/JSystem/JUtility/JUTResFont.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/libs/JSystem/include/JSystem/JUtility/JUTResFont.h b/libs/JSystem/include/JSystem/JUtility/JUTResFont.h index d9094f72a9..29d8a77b92 100644 --- a/libs/JSystem/include/JSystem/JUtility/JUTResFont.h +++ b/libs/JSystem/include/JSystem/JUtility/JUTResFont.h @@ -69,11 +69,7 @@ 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;