diff --git a/CMakeLists.txt b/CMakeLists.txt index dfd89a2a50..2b73c5ea69 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 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; + }; +} 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/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/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/include/dusk/settings.h b/include/dusk/settings.h index 8e57b9db99..7381ccafe5 100644 --- a/include/dusk/settings.h +++ b/include/dusk/settings.h @@ -61,6 +61,7 @@ struct UserSettings { ConfigVar noMissClimbing; ConfigVar fastTears; ConfigVar instantSaves; + ConfigVar sunsSong; // Preferences ConfigVar enableMirrorMode; 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..3c1cd0bf1f 100644 --- a/libs/JSystem/include/JSystem/JUtility/JUTFont.h +++ b/libs/JSystem/include/JSystem/JUtility/JUTFont.h @@ -5,6 +5,17 @@ #include #include "dusk/endian.h" +#if TARGET_PC +struct FontDrawContext { + bool isTextureLoaded = false; +}; +#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 +95,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 +113,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..29d8a77b92 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 * @@ -31,7 +27,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 +39,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 +49,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(); @@ -68,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; @@ -86,6 +83,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/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..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() { @@ -231,14 +244,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; @@ -258,15 +271,26 @@ 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; 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 +314,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 +442,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 +474,15 @@ 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); + mHeight += texH * pageIdx; - GXInitTexObjLOD(texObj, GX_LINEAR, GX_LINEAR, 0.0f, 0.0f, 0.0f, 0U, 0U, GX_ANISO_1); - } else { - texObj = &found->second; + if (!context || !context->isTextureLoaded) { + GXLoadTexObj(&mJoinedTextureObject, id); + if (context) { + context->isTextureLoaded = true; + } } - GXLoadTexObj(texObj, id); - // Gets used by some other code. mTexPageIdx = pageIdx; field_0x66 = i; diff --git a/src/Z2AudioLib/Z2WolfHowlMgr.cpp b/src/Z2AudioLib/Z2WolfHowlMgr.cpp index 4b546fc54a..cb4f387a21 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_MID, 20}, {HOWL_LINE_LOW, 20}, {HOWL_LINE_HIGH, 40}, + {HOWL_LINE_MID, 20}, {HOWL_LINE_LOW, 20}, {HOWL_LINE_HIGH, 40}, +}; +#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,13 @@ void Z2WolfHowlMgr::setCorrectData(s8 curveID, Z2WolfHowlData* data) { cPitchCenter = 0.94387f; cPitchDown = 0.74915f; break; + #if TARGET_PC + case Z2WOLFHOWL_TIMESONG: + cPitchUp = 1.259906f; + cPitchCenter = 0.94387f; + cPitchDown = 0.840885f; + break; + #endif default: cPitchUp = 1.1892f; cPitchCenter = 1.0f; @@ -400,7 +417,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 +427,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 +442,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 +457,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.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); + } } } } 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 2e38298090..0a6da841c5 100644 --- a/src/d/actor/d_a_alink_wolf.inc +++ b/src/d/actor/d_a_alink_wolf.inc @@ -3954,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; @@ -4095,6 +4101,19 @@ int daAlink_c::procWolfHowlDemo() { 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/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(); 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")) diff --git a/src/dusk/crash_reporting.cpp b/src/dusk/crash_reporting.cpp index 1b1adbf774..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) { @@ -76,8 +83,6 @@ std::filesystem::path GetCrashpadHandlerPath() { } const std::filesystem::path handlerDir(basePath); - SDL_free(const_cast(basePath)); - #if _WIN32 return handlerDir / "crashpad_handler.exe"; #else @@ -113,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/imgui/ImGuiFirstRunPreset.cpp b/src/dusk/imgui/ImGuiFirstRunPreset.cpp index 99c77dac70..c218185c92 100644 --- a/src/dusk/imgui/ImGuiFirstRunPreset.cpp +++ b/src/dusk/imgui/ImGuiFirstRunPreset.cpp @@ -39,6 +39,7 @@ static void ApplyPresetDusk() { s.game.instantSaves.setValue(true); s.game.midnasLamentNonStop.setValue(true); s.game.enableFrameInterpolation.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 a50ec1ee4c..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()) { diff --git a/src/dusk/imgui/ImGuiMenuGame.cpp b/src/dusk/imgui/ImGuiMenuGame.cpp index 27e036ee2e..48c64f8f0e 100644 --- a/src/dusk/imgui/ImGuiMenuGame.cpp +++ b/src/dusk/imgui/ImGuiMenuGame.cpp @@ -16,6 +16,7 @@ #include "m_Do/m_Do_graphic.h" #include +#include #include "dusk/main.h" @@ -174,6 +175,122 @@ 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) { + if (buttonUntyped == PAD_NATIVE_BUTTON_INVALID) { + return "Not bound"; + } + + 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; @@ -303,6 +420,7 @@ namespace dusk { ImGuiBeginGroupPanel("Buttons", ImVec2(150 * scale, 20 * scale)); + SDL_Gamepad* gamepad = PADGetSDLGamepadForIndex(PADGetIndexForPort(m_controllerConfig.m_selectedPort)); u32 buttonCount; PADButtonMapping* btnMappingList = PADGetButtonMappings(m_controllerConfig.m_selectedPort, &buttonCount); if (btnMappingList != nullptr) { @@ -322,7 +440,7 @@ namespace dusk { if (m_controllerConfig.m_isReading && m_controllerConfig.m_pendingButtonMapping == &btnMappingList[i]) { dispName = fmt::format("Press a Key...##{}", btnName); } else { - const char* nativeName = PADGetNativeButtonName(btnMappingList[i].nativeButton); + const char* nativeName = GetNameForGamepadButton(gamepad, btnMappingList[i].nativeButton); if (nativeName == nullptr) { nativeName = "[unbound]"; } 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/dusk/settings.cpp b/src/dusk/settings.cpp index 4a4f6186f0..0c2f02c129 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}, + .sunsSong {"game.sunsSong", false}, // Preferences .enableMirrorMode {"game.enableMirrorMode", false}, @@ -113,6 +114,7 @@ void registerSettings() { Register(g_userSettings.game.fastClimbing); Register(g_userSettings.game.fastTears); Register(g_userSettings.game.instantSaves); + 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(); diff --git a/src/m_Do/m_Do_main.cpp b/src/m_Do/m_Do_main.cpp index 7f5487d6fe..281a892d0f 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" @@ -371,6 +373,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}, @@ -460,10 +504,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; @@ -476,7 +523,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; @@ -558,6 +605,7 @@ int game_main(int argc, char* argv[]) { main01(); dusk::ShutdownCrashReporting(); + dusk::ShutdownFileLogging(); fflush(stdout); fflush(stderr);