From 35ea13c53a4ef4263ec712df599708649773e540 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Fri, 24 Apr 2026 12:25:15 -0600 Subject: [PATCH 01/27] Update aurora --- extern/aurora | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/aurora b/extern/aurora index be1395c134..9ff83bcb97 160000 --- a/extern/aurora +++ b/extern/aurora @@ -1 +1 @@ -Subproject commit be1395c1343a5587cceea25754daa5cacce34f76 +Subproject commit 9ff83bcb9718391a25c3c5ffa1faf470effb1c86 From 1fea4f02ed556b798eb7fcbfced02b7c3a2ec423 Mon Sep 17 00:00:00 2001 From: Irastris Date: Fri, 24 Apr 2026 14:26:52 -0400 Subject: [PATCH 02/27] Fix camera bouncing with interp enabled --- src/dusk/frame_interpolation.cpp | 38 ++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/src/dusk/frame_interpolation.cpp b/src/dusk/frame_interpolation.cpp index f3f8a842e1..941c9ed318 100644 --- a/src/dusk/frame_interpolation.cpp +++ b/src/dusk/frame_interpolation.cpp @@ -292,12 +292,20 @@ void interp_view(::view_class* view) { return; const f32 step = get_interpolation_step(); + const bool is_cam_curr_authoritative = g_is_sim_frame && step <= 0.0f; + cXyz eye; cXyz center; cXyz up; - lerp_xyz(&eye, s_cam_prev.eye, s_cam_curr.eye, step); - lerp_xyz(¢er, s_cam_prev.center, s_cam_curr.center, step); - lerp_xyz(&up, s_cam_prev.up, s_cam_curr.up, step); + if (is_cam_curr_authoritative) { + eye = s_cam_curr.eye; + center = s_cam_curr.center; + up = s_cam_curr.up; + } else { + lerp_xyz(&eye, s_cam_prev.eye, s_cam_curr.eye, step); + lerp_xyz(¢er, s_cam_prev.center, s_cam_curr.center, step); + lerp_xyz(&up, s_cam_prev.up, s_cam_curr.up, step); + } if (!up.normalizeRS()) { up = s_cam_curr.up; up.normalizeRS(); @@ -306,19 +314,25 @@ void interp_view(::view_class* view) { view->lookat.eye = eye; view->lookat.center = center; view->lookat.up = up; - view->bank = lerp_bank(s_cam_prev.bank, s_cam_curr.bank, step); - view->fovy = s_cam_prev.fovy + (s_cam_curr.fovy - s_cam_prev.fovy) * step; - view->aspect = s_cam_prev.aspect + (s_cam_curr.aspect - s_cam_prev.aspect) * step; - view->near_ = s_cam_prev.near_ + (s_cam_curr.near_ - s_cam_prev.near_) * step; - view->far_ = s_cam_prev.far_ + (s_cam_curr.far_ - s_cam_prev.far_) * step; + if (is_cam_curr_authoritative) { + view->bank = s_cam_curr.bank; + view->fovy = s_cam_curr.fovy; + view->aspect = s_cam_curr.aspect; + view->near_ = s_cam_curr.near_; + view->far_ = s_cam_curr.far_; + } else { + view->bank = lerp_bank(s_cam_prev.bank, s_cam_curr.bank, step); + view->fovy = s_cam_prev.fovy + (s_cam_curr.fovy - s_cam_prev.fovy) * step; + view->aspect = s_cam_prev.aspect + (s_cam_curr.aspect - s_cam_prev.aspect) * step; + view->near_ = s_cam_prev.near_ + (s_cam_curr.near_ - s_cam_prev.near_) * step; + view->far_ = s_cam_prev.far_ + (s_cam_curr.far_ - s_cam_prev.far_) * step; + } // FRAME INTERP TODO: It might be better if I rewired the game to not clear this flag until the // next sim frame, but I don't care enough to right now #if WIDESCREEN_SUPPORT - if (mDoGph_gInf_c::isWide() && !mDoGph_gInf_c::isWideZoom() && step >= 0.5f ? - s_cam_curr.wideZoom : - s_cam_prev.wideZoom) - { + const f32 wide_step = is_cam_curr_authoritative ? 1.0f : step; + if (mDoGph_gInf_c::isWide() && !mDoGph_gInf_c::isWideZoom() && wide_step >= 0.5f ? s_cam_curr.wideZoom : s_cam_prev.wideZoom) { mDoGph_gInf_c::onWideZoom(); } #endif From f0e8379fa52cf9ccd608a21582408f0e4bc55a5a Mon Sep 17 00:00:00 2001 From: Luke Street Date: Fri, 24 Apr 2026 13:19:09 -0600 Subject: [PATCH 03/27] Fix UaF in d_a_mirror Resolves #505 --- src/d/actor/d_a_mirror.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/d/actor/d_a_mirror.cpp b/src/d/actor/d_a_mirror.cpp index fb4142c0bb..746f32a9f6 100644 --- a/src/d/actor/d_a_mirror.cpp +++ b/src/d/actor/d_a_mirror.cpp @@ -599,6 +599,13 @@ int daMirror_c::execute() { return 1; } +#if TARGET_PC + if (mPacket.mbReset) { + mPacket.mModelCount = 0; + mPacket.mbReset = false; + } +#endif + daPy_py_c* player = daPy_getLinkPlayerActorClass(); JUT_ASSERT(0, player != NULL); From b2c09d56d6551d74f6b6654098f24b1afd03d1a7 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Fri, 24 Apr 2026 13:34:51 -0600 Subject: [PATCH 04/27] Remove unnecessary logic from dMirror_packet_c::entryModel --- src/d/actor/d_a_mirror.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/d/actor/d_a_mirror.cpp b/src/d/actor/d_a_mirror.cpp index 746f32a9f6..86ef4314f3 100644 --- a/src/d/actor/d_a_mirror.cpp +++ b/src/d/actor/d_a_mirror.cpp @@ -84,13 +84,6 @@ void dMirror_packet_c::calcMinMax() { } int dMirror_packet_c::entryModel(J3DModel* i_model) { -#if TARGET_PC - if (mbReset) { - mModelCount = 0; - mbReset = false; - } -#endif - if (mModelCount >= 0x40) { return 0; } From 171dbdd0a14b46b5baebfd3f6ad929547a0cce92 Mon Sep 17 00:00:00 2001 From: roeming Date: Fri, 24 Apr 2026 16:50:31 -0400 Subject: [PATCH 05/27] Implement custom sorting when sorting event flags by flag on/off --- src/dusk/imgui/ImGuiSaveEditor.cpp | 96 +++++++++++++++++++++++------- 1 file changed, 76 insertions(+), 20 deletions(-) diff --git a/src/dusk/imgui/ImGuiSaveEditor.cpp b/src/dusk/imgui/ImGuiSaveEditor.cpp index eced44c7f7..cc89d37275 100644 --- a/src/dusk/imgui/ImGuiSaveEditor.cpp +++ b/src/dusk/imgui/ImGuiSaveEditor.cpp @@ -1347,6 +1347,51 @@ namespace dusk { } } + template + concept FlagIter = requires(T t) { + ++t; + --t; + t + 1; + t < t; + { t->flagID } -> std::convertible_to; + }; + + template + concept FlagTester = requires(T t, u16 flagID) { + { t(flagID) } -> std::convertible_to; + }; + + static void sortByFlags(FlagIter auto begin, FlagIter auto end, FlagTester auto&& flagTester) { + if (begin == end) return; + + FlagIter auto fullEnd = end; + + // We want to find the location of where we can swap our `On` flags to. + // We're gonna put the `Off` bits first, and the `On` bits last. 0 < 1 + // We can achieve this by skipping all the `On` bits at the end. + + // backtrack until we find a bit that is off + while (begin < --end && flagTester(end->flagID)) { + // move the end pointer back while we find on bits + } + + // end should now be pointing to a bit that is off + while (begin < end) { + // if there's a flag that's on + if (flagTester(begin->flagID)) { + // move it to the end + std::rotate(begin, begin + 1, fullEnd); + // move back the end of where we're checking + --end; + // begin will now point to the next piece of data + // because we've rotated the data >= begin to the left + } else { + // not on, check next flag + ++begin; + } + } + } + void ImGuiSaveEditor::drawFlagsTab() { if (ImGui::TreeNode("Current Region Flags")) { dSv_memBit_c& membit = g_dComIfG_gameInfo.info.mMemory.mBit; @@ -1446,30 +1491,41 @@ namespace dusk { sort != nullptr && sort->SpecsCount > 0 && (sort->SpecsDirty || sort->Specs[0].ColumnIndex == COLUMN_FLAG)) { - auto column = sort->Specs->ColumnIndex; - const auto cmp = [&](const duskImguiEventFlagEntry& l, - const duskImguiEventFlagEntry& r) -> bool { - switch (column) { - case COLUMN_FLAG: - return (bool)event.isEventBit(l.flagID) < - (bool)event.isEventBit(r.flagID); - case COLUMN_NAME: - return l.flagName < r.flagName; - case COLUMN_LOC: - return l.location < r.location; - case COLUMN_DESC: - return l.description < r.description; - } - return false; - }; - + const auto column = sort->Specs[0].ColumnIndex; const auto direction = sort->Specs[0].SortDirection; - if (direction == ImGuiSortDirection_Ascending) { - std::sort(std::begin(duskImguiEventFlags), std::end(duskImguiEventFlags), cmp); + // if we're sorting by flags, do special sort, regular sort is bad for sorting bools + // it can swap values that are the same, and that causes constant reordering + if (column == COLUMN_FLAG) { + const auto testEventFunc = [&event](u16 flag) -> bool { return event.isEventBit(flag); }; + + if (direction == ImGuiSortDirection_Ascending) { + sortByFlags(std::begin(duskImguiEventFlags), + std::end(duskImguiEventFlags), testEventFunc); + } else { + sortByFlags(std::rbegin(duskImguiEventFlags), + std::rend(duskImguiEventFlags), testEventFunc); + } } else { - std::sort(std::rbegin(duskImguiEventFlags), std::rend(duskImguiEventFlags), cmp); + const auto cmp = [column](const duskImguiEventFlagEntry& l, + const duskImguiEventFlagEntry& r) -> bool { + switch (column) { + case COLUMN_NAME: return l.flagName < r.flagName; + case COLUMN_LOC: return l.location < r.location; + case COLUMN_DESC: return l.description < r.description; + default: return false; + } + }; + + if (direction == ImGuiSortDirection_Ascending) { + std::sort(std::begin(duskImguiEventFlags), + std::end(duskImguiEventFlags), cmp); + } else { + std::sort(std::rbegin(duskImguiEventFlags), + std::rend(duskImguiEventFlags), cmp); + } } + sort->SpecsDirty = false; } From d5aabe9f9ed2a13d275413cca114bf4b99c929b1 Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Fri, 24 Apr 2026 18:06:02 -0400 Subject: [PATCH 06/27] Even faster tears to match TPHD --- src/d/actor/d_a_obj_drop.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/d/actor/d_a_obj_drop.cpp b/src/d/actor/d_a_obj_drop.cpp index 275a3d6e12..2b373c1e64 100644 --- a/src/d/actor/d_a_obj_drop.cpp +++ b/src/d/actor/d_a_obj_drop.cpp @@ -265,7 +265,7 @@ int daObjDrop_c::modeParentWait() { mModeAction = 1; #if TARGET_PC - mModeTimer = dusk::getSettings().game.fastTears && dComIfGp_event_getMode() == 0 ? 20 : 40; + mModeTimer = dusk::getSettings().game.fastTears && dComIfGp_event_getMode() == 0 ? 0 : 40; if (dusk::getSettings().game.fastTears && dComIfGp_event_getMode() == 0) { current.pos.y += 100.0f; } else { @@ -285,7 +285,7 @@ int daObjDrop_c::modeParentWait() { case 2: createBodyEffect(); #if TARGET_PC - mModeTimer = dusk::getSettings().game.fastTears && dComIfGp_event_getMode() == 0 ? 5 : 45; + mModeTimer = dusk::getSettings().game.fastTears && dComIfGp_event_getMode() == 0 ? 0 : 45; #else mModeTimer = 45; #endif From 274bf61b3ea216ec4f3e0db0610d8858e9d9420e Mon Sep 17 00:00:00 2001 From: Irastris Date: Fri, 24 Apr 2026 22:56:47 -0400 Subject: [PATCH 07/27] Fix Link's hands detaching when interp is enabled --- src/d/actor/d_a_alink.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/d/actor/d_a_alink.cpp b/src/d/actor/d_a_alink.cpp index e6943baf96..4fea05cd53 100644 --- a/src/d/actor/d_a_alink.cpp +++ b/src/d/actor/d_a_alink.cpp @@ -18936,11 +18936,20 @@ void daAlink_c::setDrawHand() { mpLinkHandModel->setBaseTRMtx(mpLinkModel->getBaseTRMtx()); mpLinkHandModel->calc(); +#if TARGET_PC + // FRAME INTERP NOTE: Always set these, otherwise the hands occasionally zip to origin. + // Doing it regardless of interpolation being active seems harmless. + mpLinkHandModel->setAnmMtx(1, mpLinkModel->getAnmMtx(9)); + mpLinkHandModel->setAnmMtx(2, mpLinkModel->getAnmMtx(0xE)); +#endif + if (var_r30 == 0xFE || var_r30 == 0xFB) { field_0x06d0 = field_0x06d8; } else { field_0x06d0 = mpLinkHandModel->getModelData()->getMaterialNodePointer(var_r30)->getShape(); +#if !TARGET_PC mpLinkHandModel->setAnmMtx(1, mpLinkModel->getAnmMtx(9)); +#endif } if (var_r30 == 0xFB) { @@ -18959,7 +18968,9 @@ void daAlink_c::setDrawHand() { field_0x06d4 = field_0x06dc; } else { field_0x06d4 = mpLinkHandModel->getModelData()->getMaterialNodePointer(var_r29)->getShape(); +#if !TARGET_PC mpLinkHandModel->setAnmMtx(2, mpLinkModel->getAnmMtx(0xE)); +#endif } if (var_r29 == 0xFB) { From c61e32cd4fd52af7495fc9366fae7c2f86c81384 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Fri, 24 Apr 2026 22:10:56 -0600 Subject: [PATCH 08/27] Update aurora for GXPeekZ Resolves #281 Resolves #468 --- extern/aurora | 2 +- src/dusk/stubs.cpp | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/extern/aurora b/extern/aurora index 9ff83bcb97..8d9618dc4b 160000 --- a/extern/aurora +++ b/extern/aurora @@ -1 +1 @@ -Subproject commit 9ff83bcb9718391a25c3c5ffa1faf470effb1c86 +Subproject commit 8d9618dc4bd82e476754c5b841b85fba6ee6ddab diff --git a/src/dusk/stubs.cpp b/src/dusk/stubs.cpp index 07e420445c..c6832d2cac 100644 --- a/src/dusk/stubs.cpp +++ b/src/dusk/stubs.cpp @@ -1020,10 +1020,6 @@ void GXInitTexCacheRegion(GXTexRegion* region, GXBool is_32b_mipmap, u32 tmem_ev // XXX, this should be some struct? // GXRenderModeObj GXNtsc480IntDf; //GXRenderModeObj GXNtsc480Int; -void GXPeekZ(u16 x, u16 y, u32* z) { - STUB_LOG(); - *z = 0; -} void GXReadXfRasMetric(u32* xf_wait_in, u32* xf_wait_out, u32* ras_busy, u32* clocks) { STUB_LOG(); *xf_wait_in = 0; From c8e89a0f998b88bf464aeabfc3913c4af6675a46 Mon Sep 17 00:00:00 2001 From: Irastris Date: Sat, 25 Apr 2026 00:31:37 -0400 Subject: [PATCH 09/27] Fix grass cutting when interp is enabled --- src/d/d_particle.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/d/d_particle.cpp b/src/d/d_particle.cpp index 248424e9a2..9b89afc7b1 100644 --- a/src/d/d_particle.cpp +++ b/src/d/d_particle.cpp @@ -1972,7 +1972,12 @@ void dPa_light8PcallBack::draw(JPABaseEmitter* param_1, JPABaseParticle* param_2 JGeometry::TVec3 local_154; JGeometry::TVec3 local_160; JGeometry::TVec3 local_16c; - dPa_setWindPower(param_2); +#if TARGET_PC + if (dusk::frame_interp::is_sim_frame()) +#endif + { + dPa_setWindPower(param_2); + } MTXIdentity(local_60); MTXIdentity(auStack_90); param_2->getBaseAxis(&local_10c); From 4e264d6a22d98835d62b3c29cb48376e9a88adaa Mon Sep 17 00:00:00 2001 From: Luke Street Date: Fri, 24 Apr 2026 22:53:41 -0600 Subject: [PATCH 10/27] Fix Zbuffer texture binding Resolves #511 --- src/d/d_resorce.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/d/d_resorce.cpp b/src/d/d_resorce.cpp index 73f031beae..bb982c4afb 100644 --- a/src/d/d_resorce.cpp +++ b/src/d/d_resorce.cpp @@ -102,11 +102,7 @@ static void setIndirectTex(J3DModelData* i_modelData) { texture->setResTIMG(i, *mDoGph_gInf_c::getFrameBufferTimg()); } if (memcmp(textureName, "Zbuffer", 8) == 0) { -#if !TARGET_PC texture->setResTIMG(i, *mDoGph_gInf_c::getZbufferTimg()); -#else - DuskLog.warn("Zbuffer texture binding not yet supported"); -#endif } } } From e755148f1673b23d41016fa58def537b2e3a9ddf Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Sat, 25 Apr 2026 16:59:04 -0400 Subject: [PATCH 11/27] Fix some UBs (#544) Co-authored-by: MelonSpeedruns --- src/d/actor/d_a_alink.cpp | 5 +++++ src/d/actor/d_flower.inc | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/src/d/actor/d_a_alink.cpp b/src/d/actor/d_a_alink.cpp index 4fea05cd53..28c979bab4 100644 --- a/src/d/actor/d_a_alink.cpp +++ b/src/d/actor/d_a_alink.cpp @@ -5877,6 +5877,11 @@ void daAlink_c::setItemMatrix(int param_0) { modelCalc(mSheathModel); int var_r26; + + #if AVOID_UB + var_r26 = 0; + #endif + if (!checkNoResetFlg3(FLG3_UNK_4000000)) { if (mEquipItem == 0x103 || param_0 != 0) { mSwordModel->setBaseTRMtx(mpLinkModel->getAnmMtx(mLeftItemJntNo)); diff --git a/src/d/actor/d_flower.inc b/src/d/actor/d_flower.inc index 58daa354a1..1850e1e1ad 100644 --- a/src/d/actor/d_flower.inc +++ b/src/d/actor/d_flower.inc @@ -789,6 +789,10 @@ void dFlower_packet_c::draw() { GXColor sp28; + #if AVOID_UB + sp28 = {1, 1, 1, 1}; + #endif + //u8 sp26, sp25, sp24; GXColor sp24; sp24.r = -0.4f * temp_r29->AmbCol.r * var_f29; From 60e8836968685bc7fc05460967ca87e07db0b390 Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Sat, 25 Apr 2026 16:59:30 -0400 Subject: [PATCH 12/27] Tears Collect during cutscenes (#539) TPHD has tears collectable just as fast during cutscenes, this changes our implementation to reflect that. Co-authored-by: MelonSpeedruns --- src/d/actor/d_a_e_ym.cpp | 6 +++--- src/d/actor/d_a_obj_drop.cpp | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/d/actor/d_a_e_ym.cpp b/src/d/actor/d_a_e_ym.cpp index 758bec6269..3f6bef6788 100644 --- a/src/d/actor/d_a_e_ym.cpp +++ b/src/d/actor/d_a_e_ym.cpp @@ -1069,7 +1069,7 @@ void daE_YM_c::executeDown() { if (mAcch.ChkGroundHit()) { if (mFlyType != 1) { #if TARGET_PC - bckSet(6, 0, 0.0f, dusk::getSettings().game.fastTears && dComIfGp_event_getMode() == 0 ? 2.0f : 1.0f); + bckSet(6, 0, 0.0f, dusk::getSettings().game.fastTears ? 2.0f : 1.0f); #else bckSet(6, 0, 0.0f, 1.0f); #endif @@ -1093,7 +1093,7 @@ void daE_YM_c::executeDown() { mSound.startCreatureSound(Z2SE_CM_BODYFALL_WATER_M, 0, -1); mSound.startCreatureSound(Z2SE_EN_YM_MOGAKU, 0, -1); #if TARGET_PC - bckSet(6, 0, 0.0f, dusk::getSettings().game.fastTears && dComIfGp_event_getMode() == 0 ? 2.0f : 1.0f); + bckSet(6, 0, 0.0f, dusk::getSettings().game.fastTears ? 2.0f : 1.0f); #else bckSet(6, 0, 0.0f, 1.0f); #endif @@ -1115,7 +1115,7 @@ void daE_YM_c::executeDown() { || dComIfG_Bgsp().GetGroundCode(gnd_chk) == 5) { #if TARGET_PC - bckSet(6, 0, 0.0f, dusk::getSettings().game.fastTears && dComIfGp_event_getMode() == 0 ? 2.0f : 1.0f); + bckSet(6, 0, 0.0f, dusk::getSettings().game.fastTears ? 2.0f : 1.0f); #else bckSet(6, 0, 0.0f, 1.0f); #endif diff --git a/src/d/actor/d_a_obj_drop.cpp b/src/d/actor/d_a_obj_drop.cpp index 2b373c1e64..18d27840f7 100644 --- a/src/d/actor/d_a_obj_drop.cpp +++ b/src/d/actor/d_a_obj_drop.cpp @@ -265,8 +265,8 @@ int daObjDrop_c::modeParentWait() { mModeAction = 1; #if TARGET_PC - mModeTimer = dusk::getSettings().game.fastTears && dComIfGp_event_getMode() == 0 ? 0 : 40; - if (dusk::getSettings().game.fastTears && dComIfGp_event_getMode() == 0) { + mModeTimer = dusk::getSettings().game.fastTears ? 0 : 40; + if (dusk::getSettings().game.fastTears) { current.pos.y += 100.0f; } else { current.pos.y += 300.0f; @@ -285,7 +285,7 @@ int daObjDrop_c::modeParentWait() { case 2: createBodyEffect(); #if TARGET_PC - mModeTimer = dusk::getSettings().game.fastTears && dComIfGp_event_getMode() == 0 ? 0 : 45; + mModeTimer = dusk::getSettings().game.fastTears ? 0 : 45; #else mModeTimer = 45; #endif @@ -331,7 +331,7 @@ int daObjDrop_c::modeWait() { case 2: case 50: #if TARGET_PC - if (dusk::getSettings().game.fastTears && dComIfGp_event_getMode() == 0) { + if (dusk::getSettings().game.fastTears) { f32 player_dist = current.pos.abs(daPy_getPlayerActorClass()->current.pos); f32 home_dist = current.pos.abs(home.pos); From 5ec5f8864a06586339a4ad36ed8b3b8d2e745f1f Mon Sep 17 00:00:00 2001 From: Irastris Date: Sat, 25 Apr 2026 16:59:40 -0400 Subject: [PATCH 13/27] Register interp callback for d_a_e_sm2 Fixes chu color changes --- src/d/actor/d_a_e_sm2.cpp | 57 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/src/d/actor/d_a_e_sm2.cpp b/src/d/actor/d_a_e_sm2.cpp index fdbc338670..81633aa009 100644 --- a/src/d/actor/d_a_e_sm2.cpp +++ b/src/d/actor/d_a_e_sm2.cpp @@ -14,6 +14,10 @@ #include "f_op/f_op_camera_mng.h" #include +#if TARGET_PC +#include "dusk/frame_interpolation.h" +#endif + class daE_SM2_HIO_c : public fOpAcm_HIO_entry_c { public: daE_SM2_HIO_c(); @@ -76,6 +80,55 @@ static int nodeCallBack(J3DJoint* i_joint, int param_1) { return 1; } +#if TARGET_PC +static void daE_SM2_interp_callback(bool isSimFrame, void* pUserWork) { + e_sm2_class* i_this = static_cast(pUserWork); + if (i_this == NULL) { + return; + } + + fopAc_ac_c* actor = (fopAc_ac_c*)&i_this->enemy; + g_env_light.settingTevStruct(0, &actor->current.pos, &actor->tevStr); + + if (!i_this->isPiece) { + if (i_this->modelMorf == NULL) { + return; + } + J3DModel* model = i_this->modelMorf->getModel(); + if (model == NULL) { + return; + } + g_env_light.setLightTevColorType_MAJI(model, &actor->tevStr); + + J3DMaterial* material = model->getModelData()->getMaterialNodePointer(0); + material->getTevKColor(1)->r = i_this->color_R; + material->getTevKColor(1)->g = i_this->color_G; + material->getTevKColor(1)->b = i_this->color_B; + material->getTevKColor(1)->a = 217.0f * i_this->color_alpha; + + if (i_this->pbtk != NULL) { + i_this->pbtk->entry(model->getModelData()); + } + } else { + if (i_this->pieceModelMorf == NULL) { + return; + } + J3DModel* model = i_this->pieceModelMorf->getModel(); + if (model == NULL) { + return; + } + + J3DMaterial* material = model->getModelData()->getMaterialNodePointer(0); + material->getTevKColor(1)->r = i_this->color_R; + material->getTevKColor(1)->g = i_this->color_G; + material->getTevKColor(1)->b = i_this->color_B; + material->getTevKColor(1)->a = 217.0f * i_this->color_alpha; + + g_env_light.setLightTevColorType_MAJI(model, &actor->tevStr); + } +} +#endif + static int daE_SM2_Draw(e_sm2_class* i_this) { fopAc_ac_c* actor = (fopAc_ac_c*)&i_this->enemy; g_env_light.settingTevStruct(0, &actor->current.pos, &actor->tevStr); @@ -122,6 +175,10 @@ static int daE_SM2_Draw(e_sm2_class* i_this) { i_this->pieceInvisModel.entryDL(NULL); } +#if TARGET_PC + dusk::frame_interp::add_interpolation_callback(&daE_SM2_interp_callback, i_this); +#endif + return 1; } From 41420bc71cdd27d805a6c1f8906063fb0ed75608 Mon Sep 17 00:00:00 2001 From: Irastris Date: Sat, 25 Apr 2026 17:07:49 -0400 Subject: [PATCH 14/27] Chu Fix 2: Electric Boogaloo --- src/d/actor/d_a_e_sm2.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/d/actor/d_a_e_sm2.cpp b/src/d/actor/d_a_e_sm2.cpp index 81633aa009..f55c4411cb 100644 --- a/src/d/actor/d_a_e_sm2.cpp +++ b/src/d/actor/d_a_e_sm2.cpp @@ -131,6 +131,11 @@ static void daE_SM2_interp_callback(bool isSimFrame, void* pUserWork) { static int daE_SM2_Draw(e_sm2_class* i_this) { fopAc_ac_c* actor = (fopAc_ac_c*)&i_this->enemy; + +#if TARGET_PC + dusk::frame_interp::add_interpolation_callback(&daE_SM2_interp_callback, i_this); +#endif + g_env_light.settingTevStruct(0, &actor->current.pos, &actor->tevStr); J3DModel* model; @@ -175,10 +180,6 @@ static int daE_SM2_Draw(e_sm2_class* i_this) { i_this->pieceInvisModel.entryDL(NULL); } -#if TARGET_PC - dusk::frame_interp::add_interpolation_callback(&daE_SM2_interp_callback, i_this); -#endif - return 1; } From 977ad16806128059036d964849dc1f76d81ac6e3 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sat, 25 Apr 2026 23:46:12 +0200 Subject: [PATCH 15/27] GCN PAL support (#533) * Basic PAL ISO & language support Probably still needs much more work * Add language selector to pre-launch * Store DVDDiskID in a global Can use this later for things * More version system API improvements * d_name mostly region switching fully JPN doesn't work yet cuz it'll be a nightmare, probably. * More version switching support * Mark GCN PAL as supported ROM * Fix remaining REL assets to have PAL offsets * d_a_mg_fish PAL * d_a_mg_fshop PAL * isRegionUsa helper * d_menu_fishing PAL * d_msg_class PAL * m_Do_MemCardRWmng PAL * Update CARDInit call & remove DUSK_TP_VERSION * Fix Ganon cape Missed this one. * Remove tp_version from Sentry --------- Co-authored-by: Luke Street --- CMakeLists.txt | 10 +- cmake-variants.yaml | 10 - files.cmake | 1 + include/d/d_name.h | 2 +- include/d/d_s_logo.h | 4 +- include/dusk/dvd_asset.hpp | 15 +- include/dusk/settings.h | 16 + include/dusk/version.hpp | 67 ++++ include/global.h | 4 + src/d/actor/d_a_mant.cpp | 13 +- src/d/actor/d_a_mg_fish.cpp | 27 +- src/d/actor/d_a_mg_fshop.cpp | 22 +- src/d/actor/d_a_mg_rod.cpp | 30 +- src/d/actor/d_a_player.cpp | 13 +- src/d/actor/d_a_title.cpp | 30 +- src/d/actor/d_flower.inc | 28 +- src/d/actor/d_grass.inc | 19 +- src/d/d_file_sel_info.cpp | 15 +- src/d/d_menu_fishing.cpp | 8 +- src/d/d_msg_class.cpp | 14 +- src/d/d_msg_object.cpp | 29 +- src/d/d_msg_unit.cpp | 4 +- src/d/d_name.cpp | 399 ++++++++++++++++++------ src/d/d_s_logo.cpp | 182 ++++++++++- src/d/d_save.cpp | 8 +- src/dusk/config.cpp | 1 + src/dusk/crash_reporting.cpp | 1 - src/dusk/dvd_asset.cpp | 28 +- src/dusk/imgui/ImGuiPreLaunchWindow.cpp | 18 ++ src/dusk/iso_validate.cpp | 1 + src/dusk/settings.cpp | 3 + src/dusk/version.cpp | 82 +++++ src/m_Do/m_Do_MemCard.cpp | 7 +- src/m_Do/m_Do_MemCardRWmng.cpp | 36 ++- src/m_Do/m_Do_main.cpp | 21 ++ 35 files changed, 957 insertions(+), 211 deletions(-) create mode 100644 include/dusk/version.hpp create mode 100644 src/dusk/version.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 19fd5d673f..b326931e79 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -280,22 +280,16 @@ set(DUSK_COMPANY_NAME "Twilit Realm") set(DUSK_FILE_DESCRIPTION "Dusk") set(DUSK_PRODUCT_NAME "Dusk") set(DUSK_COPYRIGHT "Copyright (C) Twilit Realm contributors") -set(DUSK_GAME_NAME "GZ2E") -set(DUSK_GAME_VERSION "01") -set(DUSK_TP_VERSION ${DUSK_GAME_NAME}${DUSK_GAME_VERSION}) - -message(STATUS "dusk: Game Version: ${DUSK_TP_VERSION}") source_group("dolzel" FILES ${DOLZEL_FILES} ${Z2AUDIOLIB_FILES} ${REL_FILES}) source_group("dusk" FILES ${DUSK_FILES}) -set(GAME_COMPILE_DEFS TARGET_PC WIDESCREEN_SUPPORT=1 AVOID_UB=1 VERSION=0 - DUSK_TP_VERSION="${DUSK_TP_VERSION}" DUSK_GAME_NAME="${DUSK_GAME_NAME}" DUSK_GAME_VERSION="${DUSK_GAME_VERSION}") +set(GAME_COMPILE_DEFS TARGET_PC WIDESCREEN_SUPPORT=1 AVOID_UB=1 VERSION=0) set(GAME_INCLUDE_DIRS include src - assets/${DUSK_TP_VERSION} + assets/GZ2E01 # TODO: make this dynamic if needed? libs/JSystem/include libs extern/aurora/include/dolphin diff --git a/cmake-variants.yaml b/cmake-variants.yaml index 62d2642311..37edcede95 100644 --- a/cmake-variants.yaml +++ b/cmake-variants.yaml @@ -13,13 +13,3 @@ buildType: short: RelWithDebInfo long: Optimized, with debug symbols buildType: RelWithDebInfo - -tp_version: - default: GZ2E01 - description: TP Version - choices: - GZ2E01: - short: GZ2E01 - long: GZ2E01 - settings: - DUSK_TP_VERSION: GZ2E01 diff --git a/files.cmake b/files.cmake index af101b6c47..d45dd7dc7c 100644 --- a/files.cmake +++ b/files.cmake @@ -1466,4 +1466,5 @@ set(DUSK_FILES src/dusk/OSThread.cpp src/dusk/OSMutex.cpp src/dusk/discord_presence.cpp + src/dusk/version.cpp ) diff --git a/include/d/d_name.h b/include/d/d_name.h index e685871738..3ac6592457 100644 --- a/include/d/d_name.h +++ b/include/d/d_name.h @@ -89,7 +89,7 @@ public: void MojiSelectAnm3(); int mojiChange(u8); void selectMojiSet(); - #if REGION_JPN + #if TARGET_PC || REGION_JPN int checkDakuon(int, u8); int setDakuon(int, u8); #endif diff --git a/include/d/d_s_logo.h b/include/d/d_s_logo.h index 56d10eef82..ab714a17ae 100644 --- a/include/d/d_s_logo.h +++ b/include/d/d_s_logo.h @@ -79,7 +79,7 @@ public: bool isProgressiveMode(); void setRenderMode(); - #if VERSION == VERSION_GCN_PAL || PLATFORM_WII || PLATFORM_SHIELD + #if TARGET_PC || VERSION == VERSION_GCN_PAL || PLATFORM_WII || PLATFORM_SHIELD u8 getPalLanguage(); #endif @@ -149,7 +149,7 @@ public: /* 0x200 */ dDlst_2D_c* mNvLogo; /* 0x204 */ dDlst_2D_c* mMocImg; #endif -#if VERSION == VERSION_GCN_PAL +#if TARGET_PC || VERSION == VERSION_GCN_PAL /* 0x1FC */ mDoDvdThd_mountArchive_c* mpPalLogoResCommand; #endif /* 0x1FC */ request_of_phase_process_class* m_preLoad_dylPhase; diff --git a/include/dusk/dvd_asset.hpp b/include/dusk/dvd_asset.hpp index 014c683d44..8594d4631c 100644 --- a/include/dusk/dvd_asset.hpp +++ b/include/dusk/dvd_asset.hpp @@ -1,22 +1,31 @@ #pragma once #include "dolphin/types.h" +#include "version.hpp" namespace dusk { +struct OffsetVersion { + version::GameVersion mGameVersion; + u32 mOffset; + + constexpr OffsetVersion(const version::GameVersion gameVersion, const u32 offset) + : mGameVersion(gameVersion), mOffset(offset) {} +}; + /** * Load bytes from the main DOL by GameCube virtual address */ -bool LoadDolAsset(void* dst, u32 virtualAddress, s32 size); +bool LoadDolAsset(void* dst, std::initializer_list virtualAddress, s32 size); /** * Load bytes from a REL file in the ISO filesystem, dst must be 32-byte aligned */ -bool LoadRelAsset(void* dst, const char* dvdPath, s32 offset, s32 size); +bool LoadRelAsset(void* dst, const char* dvdPath, std::initializer_list offset, s32 size); /** * Load bytes from a REL inside RELS.arc */ -bool LoadArchivedRelAsset(void* dst, u32 memType, const char* relFileName, s32 offset, s32 size); +bool LoadArchivedRelAsset(void* dst, u32 memType, const char* relFileName, std::initializer_list offset, s32 size); } // namespace dusk diff --git a/include/dusk/settings.h b/include/dusk/settings.h index 039e51af7d..9721b93614 100644 --- a/include/dusk/settings.h +++ b/include/dusk/settings.h @@ -13,12 +13,26 @@ enum class BloomMode : int { Dusk = 2, }; +enum class GameLanguage : u8 { + English = OS_LANGUAGE_ENGLISH, + German = OS_LANGUAGE_GERMAN, + French = OS_LANGUAGE_FRENCH, + Spanish = OS_LANGUAGE_SPANISH, + Italian = OS_LANGUAGE_ITALIAN, +}; + namespace config { template <> struct ConfigEnumRange { static constexpr auto min = BloomMode::Off; static constexpr auto max = BloomMode::Dusk; }; + +template <> +struct ConfigEnumRange { + static constexpr auto min = GameLanguage::English; + static constexpr auto max = GameLanguage::Italian; +}; } // Persistent user settings @@ -46,6 +60,8 @@ struct UserSettings { // Game settings struct { + ConfigVar language; + // QoL ConfigVar enableQuickTransform; ConfigVar hideTvSettingsScreen; diff --git a/include/dusk/version.hpp b/include/dusk/version.hpp new file mode 100644 index 0000000000..fe393b049b --- /dev/null +++ b/include/dusk/version.hpp @@ -0,0 +1,67 @@ +#ifndef DUSK_VERSION_HPP +#define DUSK_VERSION_HPP + +/** + * Functionality for switching game behavior based on the loaded game version (e.g. PAL/JPN, GC/Wii) + */ +namespace dusk::version { + enum class GameVersion : u8 { + GcnUsa = VERSION_GCN_USA, + GcnPal = VERSION_GCN_PAL, + GcnJpn = VERSION_GCN_JPN, + WiiUsaRev0 = VERSION_WII_USA_R0, + WiiUsa = VERSION_WII_USA_R2, + WiiPal = VERSION_WII_PAL, + WiiJpn = VERSION_WII_JPN, + WiiKor = VERSION_WII_KOR, + }; + + bool isGcn(); + bool isWii(); + bool isPalOrAtLeastWiiR2(); + + bool isRegionPal(); + bool isRegionJpn(); + bool isRegionUsa(); + + GameVersion getGameVersion(); + + const DVDDiskID& getDiskID(); + + void init(); + + template + struct VersionOption { + GameVersion mVersion; + T mValue; + + constexpr VersionOption(GameVersion version, T value) : mVersion(version), mValue(value) {} + }; + + template + const T& versionSelect(const std::initializer_list> options) { + const auto version = getGameVersion(); + for (const auto& opt : options) { + if (opt.mVersion == version) { + return opt.mValue; + } + } + + // Unable to find value. + abort(); + } + + template + const T& versionSelect(const std::initializer_list> options, const T& defaultValue) { + const auto version = getGameVersion(); + for (const auto& opt : options) { + if (opt.mVersion == version) { + return opt.mValue; + } + } + + return defaultValue; + } +} // namespace dusk::version + +#endif // DUSK_VERSION_HPP diff --git a/include/global.h b/include/global.h index 9301d12e11..556c5f6207 100644 --- a/include/global.h +++ b/include/global.h @@ -230,12 +230,16 @@ 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_BLOCK(cond) if (cond) { +#define IF_DUSK_BLOCK_END } #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_DUSK_BLOCK(cond) +#define IF_DUSK_BLOCK_END #define IF_NOT_DUSK(statement) statement #define DUSK_IF_ELSE(dusk, orig) orig #endif diff --git a/src/d/actor/d_a_mant.cpp b/src/d/actor/d_a_mant.cpp index 282502419f..6a559c0e1c 100644 --- a/src/d/actor/d_a_mant.cpp +++ b/src/d/actor/d_a_mant.cpp @@ -13,9 +13,12 @@ #if TARGET_PC #include "dusk/dvd_asset.hpp" #include "dusk/frame_interpolation.h" -static u8* l_Egnd_mantTEX_get() { alignas(32) static u8 buf[0x4000]; static bool _ = (dusk::LoadRelAsset(buf, "/rel/Final/Release/d_a_mant.rel", 0x1C00, 0x4000), true); return buf; } -static u8* l_Egnd_mantTEX_U_get() { alignas(32) static u8 buf[0x4000]; static bool _ = (dusk::LoadRelAsset(buf, "/rel/Final/Release/d_a_mant.rel", 0x5C00, 0x4000), true); return buf; } -static u8* l_Egnd_mantPAL_get() { alignas(32) static u8 buf[0x60]; static bool _ = (dusk::LoadRelAsset(buf, "/rel/Final/Release/d_a_mant.rel", 0x9C00, 0x60), true); return buf; } + +using GameVersion = dusk::version::GameVersion; + +static u8* l_Egnd_mantTEX_get() { alignas(32) static u8 buf[0x4000]; static bool _ = (dusk::LoadRelAsset(buf, "/rel/Final/Release/d_a_mant.rel", {{GameVersion::GcnUsa, 0x1C00}, {GameVersion::GcnPal, 0x1C00}}, 0x4000), true); return buf; } +static u8* l_Egnd_mantTEX_U_get() { alignas(32) static u8 buf[0x4000]; static bool _ = (dusk::LoadRelAsset(buf, "/rel/Final/Release/d_a_mant.rel", {{GameVersion::GcnUsa, 0x5C00}, {GameVersion::GcnPal, 0x5C00}}, 0x4000), true); return buf; } +static u8* l_Egnd_mantPAL_get() { alignas(32) static u8 buf[0x60]; static bool _ = (dusk::LoadRelAsset(buf, "/rel/Final/Release/d_a_mant.rel", {{GameVersion::GcnUsa, 0x9C00}, {GameVersion::GcnPal, 0x9C00}}, 0x60), true); return buf; } #define l_Egnd_mantTEX (l_Egnd_mantTEX_get()) #define l_Egnd_mantTEX_U (l_Egnd_mantTEX_U_get()) #define l_Egnd_mantPAL (l_Egnd_mantPAL_get()) @@ -251,7 +254,9 @@ static u32 l_texCoord[338] = { }; #if TARGET_PC -static u8* l_Egnd_mantDL_get() { alignas(32) static u8 buf[0x3EC]; static bool _ = (dusk::LoadRelAsset(buf, "/rel/Final/Release/d_a_mant.rel", 0xA9A0, 0x3EC), true); return buf; } +using GameVersion = dusk::version::GameVersion; + +static u8* l_Egnd_mantDL_get() { alignas(32) static u8 buf[0x3EC]; static bool _ = (dusk::LoadRelAsset(buf, "/rel/Final/Release/d_a_mant.rel", {{GameVersion::GcnUsa, 0xA9A0}, {GameVersion::GcnPal, 0xA9A0}}, 0x3EC), true); return buf; } #define l_Egnd_mantDL (l_Egnd_mantDL_get()) #else #include "assets/l_Egnd_mantDL.h" diff --git a/src/d/actor/d_a_mg_fish.cpp b/src/d/actor/d_a_mg_fish.cpp index 90781345ad..6791bcb61c 100644 --- a/src/d/actor/d_a_mg_fish.cpp +++ b/src/d/actor/d_a_mg_fish.cpp @@ -22,6 +22,7 @@ #include "d/d_s_play.h" #include "d/d_vibration.h" #include "f_op/f_op_kankyo_mng.h" +#include "dusk/version.hpp" #include #define ANM_MG_FISH_MOUTH_CLOSE 4 @@ -3230,7 +3231,17 @@ static int daMg_Fish_Execute(mg_fish_class* i_this) { daPy_py_c* player = daPy_getPlayerActorClass(); -#if VERSION == VERSION_GCN_JPN +#if TARGET_PC + if (dusk::version::isRegionJpn()) { + lit_1008 = 0; + } else if (dusk::version::isRegionPal()) { + if (dComIfGs_getPalLanguage() == dSv_player_config_c::LANGUAGE_ENGLISH) { + lit_1008 = 2; + } else { + lit_1008 = 0; + } + } +#elif VERSION == VERSION_GCN_JPN lit_1008 = 0; #elif VERSION == VERSION_GCN_PAL if (dComIfGs_getPalLanguage() == dSv_player_config_c::LANGUAGE_ENGLISH) { @@ -3843,7 +3854,19 @@ static int daMg_Fish_Create(fopAc_ac_c* i_this) { a_this->mResName = "O_gD_bott"; } -#if VERSION == VERSION_GCN_JPN +#if TARGET_PC + if (dusk::version::isRegionJpn()) { + lit_1008 = 0; + } else if (dusk::version::isRegionPal()) { + if (dComIfGs_getPalLanguage() == dSv_player_config_c::LANGUAGE_ENGLISH) { + lit_1008 = 2; + } else { + lit_1008 = 0; + } + } else { + lit_1008 = 1; + } +#elif VERSION == VERSION_GCN_JPN lit_1008 = 0; #elif VERSION == VERSION_GCN_PAL if (dComIfGs_getPalLanguage() == dSv_player_config_c::LANGUAGE_ENGLISH) { diff --git a/src/d/actor/d_a_mg_fshop.cpp b/src/d/actor/d_a_mg_fshop.cpp index 99f39e410e..f0905efd5d 100644 --- a/src/d/actor/d_a_mg_fshop.cpp +++ b/src/d/actor/d_a_mg_fshop.cpp @@ -5,14 +5,15 @@ #include "d/dolzel_rel.h" // IWYU pragma: keep +#include "Z2AudioLib/Z2Instances.h" +#include "d/actor/d_a_mg_fish.h" #include "d/actor/d_a_mg_fshop.h" #include "d/actor/d_a_npc_henna.h" -#include "d/actor/d_a_mg_fish.h" #include "d/actor/d_a_player.h" -#include "f_op/f_op_camera_mng.h" -#include "d/d_timer.h" #include "d/d_s_play.h" -#include "Z2AudioLib/Z2Instances.h" +#include "d/d_timer.h" +#include "dusk/version.hpp" +#include "f_op/f_op_camera_mng.h" #if TARGET_PC #include "dusk/gyro.h" @@ -1742,7 +1743,18 @@ static int daFshop_Create(fopAc_ac_c* actor) { fopAcM_createChild(fpcNm_FSHOP_e, fopAcM_GetID(actor), 0xFFFFFF23, &actor->current.pos, fopAcM_GetRoomNo(actor), NULL, NULL, -1, NULL); u8 sp10; -#if VERSION == VERSION_GCN_PAL || VERSION == VERSION_WII_PAL +#if TARGET_PC + if (dusk::version::isRegionPal()) { + if (dComIfGs_getPalLanguage() == dSv_player_config_c::LANGUAGE_ENGLISH) { + sp10 = 2; + } else { + sp10 = 0; + } + } else { + sp10 = 1; + } + +#elif VERSION == VERSION_GCN_PAL || VERSION == VERSION_WII_PAL if (dComIfGs_getPalLanguage() == dSv_player_config_c::LANGUAGE_ENGLISH) { sp10 = 2; } else { diff --git a/src/d/actor/d_a_mg_rod.cpp b/src/d/actor/d_a_mg_rod.cpp index 96ad7aa148..956a9e3cff 100644 --- a/src/d/actor/d_a_mg_rod.cpp +++ b/src/d/actor/d_a_mg_rod.cpp @@ -25,6 +25,8 @@ #include #include +#include "dusk/version.hpp" + class dmg_rod_HIO_c : public JORReflexible { public: dmg_rod_HIO_c(); @@ -5734,10 +5736,22 @@ static void play_camera_u(dmg_rod_class* i_this) { static int dmg_rod_Execute(dmg_rod_class* i_this) { fopAc_ac_c* actor = &i_this->actor; + #if TARGET_PC + if (dusk::version::isPalOrAtLeastWiiR2()) { + if (dComIfGs_getPalLanguage() == 0) { + data_804BBBD4 = 2; + } else { + data_804BBBD4 = 0; + } + } else if (dusk::version::isRegionJpn()) { + data_804BBBD4 = 0; + } else { + data_804BBBD4 = 1; + } //TODO: It seems possible that dComIfGs_getPalLanguage returns a constant value for non-PAL // versions (causing the first block to be elided), and it's also possible that the value // being compared against is an enum value with per-version definitions. - #if VERSION == VERSION_SHIELD_DEBUG + #elif VERSION == VERSION_SHIELD_DEBUG if (dComIfGs_getPalLanguage() == 1) { data_804BBBD4 = 2; } else { @@ -6303,7 +6317,19 @@ static int dmg_rod_Create(fopAc_ac_c* i_this) { heap_size = 0xC9A0; } - #if VERSION == VERSION_SHIELD_DEBUG + #if TARGET_PC + if (dusk::version::isPalOrAtLeastWiiR2()) { + if (dComIfGs_getPalLanguage() == 0) { + data_804BBBD4 = 2; + } else { + data_804BBBD4 = 0; + } + } else if (dusk::version::isRegionJpn()) { + data_804BBBD4 = 0; + } else { + data_804BBBD4 = 1; + } + #elif VERSION == VERSION_SHIELD_DEBUG if (dComIfGs_getPalLanguage() == 1) { data_804BBBD4 = 2; } else { diff --git a/src/d/actor/d_a_player.cpp b/src/d/actor/d_a_player.cpp index 8ceb0ef237..6148d23e3a 100644 --- a/src/d/actor/d_a_player.cpp +++ b/src/d/actor/d_a_player.cpp @@ -369,18 +369,17 @@ JKRHeap* daPy_anmHeap_c::setAnimeHeap() { #if !PLATFORM_WII #if TARGET_PC #include "dusk/dvd_asset.hpp" +using GameVersion = dusk::version::GameVersion; static const u8* l_sightDL_get() { static u8 buf[0x89]; static bool _ = ( dusk::LoadDolAsset( buf, - #if VERSION == VERSION_GCN_PAL - 0x803BBDA0, - #elif VERSION == VERSION_GCN_JPN - 0x803B4220, - #elif VERSION == VERSION_GCN_USA - 0x803BA0C0, - #endif +{ + {GameVersion::GcnUsa, 0x803BA0C0}, + {GameVersion::GcnPal, 0x803BBDA0}, + {GameVersion::GcnJpn, 0x803B4220} + }, 0x89 ), true diff --git a/src/d/actor/d_a_title.cpp b/src/d/actor/d_a_title.cpp index 6df52abc5b..a75a96cb2e 100644 --- a/src/d/actor/d_a_title.cpp +++ b/src/d/actor/d_a_title.cpp @@ -1,21 +1,22 @@ #include "d/dolzel_rel.h" // IWYU pragma: keep +#include "JSystem/J2DGraph/J2DScreen.h" +#include "JSystem/J2DGraph/J2DTextBox.h" +#include "JSystem/JKernel/JKRExpHeap.h" +#include "JSystem/JKernel/JKRMemArchive.h" #include "d/actor/d_a_title.h" +#include "d/d_com_inf_game.h" +#include "d/d_demo.h" +#include "d/d_menu_collect.h" +#include "d/d_pane_class_alpha.h" #include "d/d_s_logo.h" #include "d/d_s_play.h" -#include "d/d_demo.h" -#include "d/d_pane_class_alpha.h" -#include "d/d_menu_collect.h" +#include "dusk/version.hpp" +#include "f_op/f_op_msg_mng.h" +#include "f_op/f_op_overlap_mng.h" +#include "f_op/f_op_scene_mng.h" #include "m_Do/m_Do_Reset.h" #include "m_Do/m_Do_controller_pad.h" -#include "d/d_com_inf_game.h" -#include "JSystem/JKernel/JKRExpHeap.h" -#include "f_op/f_op_overlap_mng.h" -#include "f_op/f_op_msg_mng.h" -#include "f_op/f_op_scene_mng.h" -#include "JSystem/J2DGraph/J2DScreen.h" -#include "JSystem/JKernel/JKRMemArchive.h" -#include "JSystem/J2DGraph/J2DTextBox.h" #include "m_Do/m_Do_graphic.h" #ifdef TARGET_PC @@ -49,7 +50,10 @@ static u8 const lit_3772[12] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; -#if VERSION == VERSION_GCN_PAL +#if TARGET_PC +using namespace dusk::version; +#define l_arcName versionSelect({{GameVersion::GcnPal, "TitlePal"}}, "Title") +#elif VERSION == VERSION_GCN_PAL static char const l_arcName[] = "TitlePal"; #else static char const l_arcName[] = "Title"; @@ -59,7 +63,7 @@ daTit_HIO_c::daTit_HIO_c() { mPSScaleX = 1.0f; mPSScaleY = 1.0f; - #if VERSION == VERSION_GCN_PAL + #if TARGET_PC || VERSION == VERSION_GCN_PAL switch (OSGetLanguage()) { case OS_LANGUAGE_ENGLISH: case OS_LANGUAGE_GERMAN: diff --git a/src/d/actor/d_flower.inc b/src/d/actor/d_flower.inc index 1850e1e1ad..5f54ea4bda 100644 --- a/src/d/actor/d_flower.inc +++ b/src/d/actor/d_flower.inc @@ -17,7 +17,8 @@ const u16 l_J_Ohana00_64TEX__height = 63; #if TARGET_PC #include "dusk/dvd_asset.hpp" -static u8* l_J_Ohana00_64TEX_get() { static u8 buf[0x800]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x9060, 0x800), true); return buf; } +using GameVersion = dusk::version::GameVersion; +static u8* l_J_Ohana00_64TEX_get() { static u8 buf[0x800]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", {{GameVersion::GcnUsa, 0x9060}, {GameVersion::GcnPal, 0x9060}}, 0x800), true); return buf; } #define l_J_Ohana00_64TEX (l_J_Ohana00_64TEX_get()) #else #include "assets/l_J_Ohana00_64TEX.h" @@ -113,10 +114,12 @@ static u8 l_flowerTexCoord[] = { 0x3E, 0xA7, 0x72, 0xD6, 0xBD, 0x2F, 0x46, 0xAA}; #if TARGET_PC -static u8* l_J_hana00DL_get() { static u8 buf[0x150]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x9D20, 0x150), true); return buf; } -static u8* l_J_hana00_cDL_get() { static u8 buf[0xDE]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x9E80, 0xDE), true); return buf; } -static u8* l_matDL_get() { static u8 buf[0x99]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x9F60, 0x99), true); return buf; } -static u8* l_matLight4DL_get() { static u8 buf[0x99]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0xA000, 0x99), true); return buf; } +using GameVersion = dusk::version::GameVersion; + +static u8* l_J_hana00DL_get() { static u8 buf[0x150]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", {{GameVersion::GcnUsa, 0x9D20}, {GameVersion::GcnPal, 0x9D20}}, 0x150), true); return buf; } +static u8* l_J_hana00_cDL_get() { static u8 buf[0xDE]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", {{GameVersion::GcnUsa, 0x9E80}, {GameVersion::GcnPal, 0x9E80}}, 0xDE), true); return buf; } +static u8* l_matDL_get() { static u8 buf[0x99]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", {{GameVersion::GcnUsa, 0x9F60}, {GameVersion::GcnPal, 0x9F60}}, 0x99), true); return buf; } +static u8* l_matLight4DL_get() { static u8 buf[0x99]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", {{GameVersion::GcnUsa, 0xA000}, {GameVersion::GcnPal, 0xA000}}, 0x99), true); return buf; } #define l_J_hana00DL (l_J_hana00DL_get()) #define l_J_hana00_cDL (l_J_hana00_cDL_get()) #define l_matDL (l_matDL_get()) @@ -142,7 +145,8 @@ const u16 l_J_Ohana01_64128_0419TEX__height = 127; #endif #if TARGET_PC -static u8* l_J_Ohana01_64128_0419TEX_get() { static u8 buf[0x1000]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0xA0A0, 0x1000), true); return buf; } +using GameVersion = dusk::version::GameVersion; +static u8* l_J_Ohana01_64128_0419TEX_get() { static u8 buf[0x1000]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", {{GameVersion::GcnUsa, 0xA0A0}, {GameVersion::GcnPal, 0xA0A0}}, 0x1000), true); return buf; } #define l_J_Ohana01_64128_0419TEX (l_J_Ohana01_64128_0419TEX_get()) #else #include "assets/l_J_Ohana01_64128_0419TEX.h" @@ -274,11 +278,13 @@ static u8 l_flowerTexCoord2[] = { 0x40, 0x1B, 0x7D, 0x52, 0x3F, 0x80, 0x3F, 0x79, 0x40, 0x1B, 0x7D, 0x52, 0x3F, 0x51, 0x10, 0x6F}; #if TARGET_PC -static u8* l_J_hana01DL_get() { static u8 buf[0x138]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0xB7C0, 0x138), true); return buf; } -static u8* l_J_hana01_c_00DL_get() { static u8 buf[0xDE]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0xB900, 0xDE), true); return buf; } -static u8* l_J_hana01_c_01DL_get() { static u8 buf[0x128]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0xB9E0, 0x128), true); return buf; } -static u8* l_mat2DL_get() { static u8 buf[0x99]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0xBB20, 0x99), true); return buf; } -static u8* l_mat2Light4DL_get() { static u8 buf[0x99]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0xBBC0, 0x99), true); return buf; } +using GameVersion = dusk::version::GameVersion; + +static u8* l_J_hana01DL_get() { static u8 buf[0x138]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", {{GameVersion::GcnUsa, 0xB7C0}, {GameVersion::GcnPal, 0xB7C0}}, 0x138), true); return buf; } +static u8* l_J_hana01_c_00DL_get() { static u8 buf[0xDE]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", {{GameVersion::GcnUsa, 0xB900}, {GameVersion::GcnPal, 0xB900}}, 0xDE), true); return buf; } +static u8* l_J_hana01_c_01DL_get() { static u8 buf[0x128]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", {{GameVersion::GcnUsa, 0xB9E0}, {GameVersion::GcnPal, 0xB9E0}}, 0x128), true); return buf; } +static u8* l_mat2DL_get() { static u8 buf[0x99]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", {{GameVersion::GcnUsa, 0xBB20}, {GameVersion::GcnPal, 0xBB20}}, 0x99), true); return buf; } +static u8* l_mat2Light4DL_get() { static u8 buf[0x99]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", {{GameVersion::GcnUsa, 0xBBC0}, {GameVersion::GcnPal, 0xBBC0}}, 0x99), true); return buf; } #define l_J_hana01DL (l_J_hana01DL_get()) #define l_J_hana01_c_00DL (l_J_hana01_c_00DL_get()) #define l_J_hana01_c_01DL (l_J_hana01_c_01DL_get()) diff --git a/src/d/actor/d_grass.inc b/src/d/actor/d_grass.inc index ab2724060f..9bc8815753 100644 --- a/src/d/actor/d_grass.inc +++ b/src/d/actor/d_grass.inc @@ -20,8 +20,9 @@ const u16 l_M_kusa05_RGBATEX__height = 31; #if TARGET_PC #include "dusk/dvd_asset.hpp" -static u8* l_M_kusa05_RGBATEX_get() { static u8 buf[0x800]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x7680, 0x800), true); return buf; } -static u8* l_M_Hijiki00TEX_get() { static u8 buf[0x800]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x7E80, 0x800), true); return buf; } +using GameVersion = dusk::version::GameVersion; +static u8* l_M_kusa05_RGBATEX_get() { static u8 buf[0x800]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", {{GameVersion::GcnUsa, 0x7680}, {GameVersion::GcnPal, 0x7680}}, 0x800), true); return buf; } +static u8* l_M_Hijiki00TEX_get() { static u8 buf[0x800]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", {{GameVersion::GcnUsa, 0x7E80}, {GameVersion::GcnPal, 0x7E80}}, 0x800), true); return buf; } #define l_M_kusa05_RGBATEX (l_M_kusa05_RGBATEX_get()) #define l_M_Hijiki00TEX (l_M_Hijiki00TEX_get()) #else @@ -113,12 +114,14 @@ static u8 l_texCoord[160] = { }; #if TARGET_PC -static u8* l_M_Kusa_9qDL_get() { static u8 buf[0xCB]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x8B00, 0xCB), true); return buf; } -static u8* l_M_Kusa_9q_cDL_get() { static u8 buf[0xCB]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x8BE0, 0xCB), true); return buf; } -static u8* l_M_TenGusaDL_get() { static u8 buf[0xD4]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x8CC0, 0xD4), true); return buf; } -static u8* l_Tengusa_matDL_get() { static u8 buf[0xA8]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x8DA0, 0xA8), true); return buf; } -static u8* l_kusa9q_matDL_get() { static u8 buf[0xA8]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x8E60, 0xA8), true); return buf; } -static u8* l_kusa9q_l4_matDL_get() { static u8 buf[0xA8]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x8F20, 0xA8), true); return buf; } +using GameVersion = dusk::version::GameVersion; + +static u8* l_M_Kusa_9qDL_get() { static u8 buf[0xCB]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", {{GameVersion::GcnUsa, 0x8B00}, {GameVersion::GcnPal, 0x8B00}}, 0xCB), true); return buf; } +static u8* l_M_Kusa_9q_cDL_get() { static u8 buf[0xCB]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", {{GameVersion::GcnUsa, 0x8BE0}, {GameVersion::GcnPal, 0x8BE0}}, 0xCB), true); return buf; } +static u8* l_M_TenGusaDL_get() { static u8 buf[0xD4]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", {{GameVersion::GcnUsa, 0x8CC0}, {GameVersion::GcnPal, 0x8CC0}}, 0xD4), true); return buf; } +static u8* l_Tengusa_matDL_get() { static u8 buf[0xA8]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", {{GameVersion::GcnUsa, 0x8DA0}, {GameVersion::GcnPal, 0x8DA0}}, 0xA8), true); return buf; } +static u8* l_kusa9q_matDL_get() { static u8 buf[0xA8]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", {{GameVersion::GcnUsa, 0x8E60}, {GameVersion::GcnPal, 0x8E60}}, 0xA8), true); return buf; } +static u8* l_kusa9q_l4_matDL_get() { static u8 buf[0xA8]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", {{GameVersion::GcnUsa, 0x8F20}, {GameVersion::GcnPal, 0x8F20}}, 0xA8), true); return buf; } #define l_M_Kusa_9qDL (l_M_Kusa_9qDL_get()) #define l_M_Kusa_9q_cDL (l_M_Kusa_9q_cDL_get()) #define l_M_TenGusaDL (l_M_TenGusaDL_get()) diff --git a/src/d/d_file_sel_info.cpp b/src/d/d_file_sel_info.cpp index f305f59a9e..0c987e6d63 100644 --- a/src/d/d_file_sel_info.cpp +++ b/src/d/d_file_sel_info.cpp @@ -14,6 +14,8 @@ #include #include +#include "dusk/version.hpp" + dFile_info_c::dFile_info_c(JKRArchive* i_archive, u8 param_1) { mArchive = i_archive; field_0x22 = param_1; @@ -169,7 +171,18 @@ void dFile_info_c::setSaveDate(dSv_save_c* i_savedata) { OSCalendarTime time; OSTicksToCalendarTime(i_savedata->getPlayer().getPlayerStatusB().getDateIpl(), &time); - #if (VERSION == VERSION_GCN_JPN) || (VERSION == VERSION_WII_JPN) + #if TARGET_PC + if (dusk::version::isRegionJpn()) { + sprintf(mSaveDate, "%d.%02d.%02d %02d:%02d", time.year, time.mon + 1, time.mday, + time.hour, time.min); + } else if (dusk::version::isRegionPal() && dComIfGs_getPalLanguage() != dSv_player_config_c::LANGUAGE_ENGLISH) { + sprintf(mSaveDate, "%02d/%02d/%d %02d:%02d", time.mday, time.mon + 1, time.year, time.hour, + time.min); + } else { + sprintf(mSaveDate, "%02d/%02d/%d %02d:%02d", time.mon + 1, time.mday, time.year, time.hour, + time.min); + } + #elif (VERSION == VERSION_GCN_JPN) || (VERSION == VERSION_WII_JPN) sprintf(mSaveDate, "%d.%02d.%02d %02d:%02d", time.year, time.mon + 1, time.mday, time.hour, time.min); #elif VERSION == VERSION_GCN_PAL diff --git a/src/d/d_menu_fishing.cpp b/src/d/d_menu_fishing.cpp index 615323767b..b76002d540 100644 --- a/src/d/d_menu_fishing.cpp +++ b/src/d/d_menu_fishing.cpp @@ -16,6 +16,8 @@ #include "m_Do/m_Do_graphic.h" #include +#include "dusk/version.hpp" + typedef void (dMenu_Fishing_c::*initFunc)(); initFunc map_init_process[] = { &dMenu_Fishing_c::wait_init, @@ -135,9 +137,9 @@ bool dMenu_Fishing_c::isSync() { } void dMenu_Fishing_c::init() { - #if VERSION == VERSION_GCN_PAL + #if TARGET_PC || VERSION == VERSION_GCN_PAL BOOL isEnglish = FALSE; - if (dComIfGs_getPalLanguage() == dSv_player_config_c::LANGUAGE_ENGLISH) { + if (dusk::version::isRegionUsa() || (dusk::version::isRegionPal() && dComIfGs_getPalLanguage() == dSv_player_config_c::LANGUAGE_ENGLISH)) { isEnglish = TRUE; } #endif @@ -145,7 +147,7 @@ void dMenu_Fishing_c::init() { for (int i = 0; i < MAX_FINDABLE_FISHES; i++) { if (dComIfGs_getFishNum(i) != 0) { // Fish has been caught once, display it along with it's params - #if VERSION == VERSION_GCN_PAL + #if TARGET_PC || VERSION == VERSION_GCN_PAL if (isEnglish) { setFishParam(i, dComIfGs_getFishNum(i), dComIfGs_getFishSize(i) / 2.54f); } else { diff --git a/src/d/d_msg_class.cpp b/src/d/d_msg_class.cpp index 7af18f0732..4040eb0d7f 100644 --- a/src/d/d_msg_class.cpp +++ b/src/d/d_msg_class.cpp @@ -307,7 +307,7 @@ static u8 getOutFontNumberType(int param_0) { } } -#if VERSION == VERSION_GCN_PAL +#if TARGET_PC || VERSION == VERSION_GCN_PAL static void setPlayerName(char* i_player_name, u8 param_2) { if (param_2 != 0) { strcpy(i_player_name, dComIfGs_getPlayerName()); @@ -1485,7 +1485,7 @@ bool jmessage_tMeasureProcessor::do_tag(u32 i_tag, void const* i_data, u32 i_siz char buffer[40]; switch (i_tag & 0xFF00FFFF) { case MSGTAG_PLAYER_GENITIV: - #if VERSION == VERSION_GCN_PAL + #if TARGET_PC || VERSION == VERSION_GCN_PAL if (dComIfGs_getPalLanguage() == dSv_player_config_c::LANGUAGE_GERMAN) { setPlayerName(buffer, 1); } else { @@ -1495,7 +1495,7 @@ bool jmessage_tMeasureProcessor::do_tag(u32 i_tag, void const* i_data, u32 i_siz push_word(buffer); return true; case MSGTAG_HORSE_GENITIV: - #if VERSION == VERSION_GCN_PAL + #if TARGET_PC || VERSION == VERSION_GCN_PAL if (dComIfGs_getPalLanguage() == dSv_player_config_c::LANGUAGE_GERMAN) { setHorseName(buffer, 1); } else { @@ -4270,7 +4270,7 @@ bool jmessage_string_tMeasureProcessor::do_tag(u32 i_tag, void const* i_data, u3 char buffer[40]; switch (i_tag & 0xFF00FFFF) { case MSGTAG_PLAYER_GENITIV: - #if VERSION == VERSION_GCN_PAL + #if TARGET_PC || VERSION == VERSION_GCN_PAL if (dComIfGs_getPalLanguage() == dSv_player_config_c::LANGUAGE_GERMAN) { setPlayerName(buffer, 1); } else { @@ -4281,7 +4281,7 @@ bool jmessage_string_tMeasureProcessor::do_tag(u32 i_tag, void const* i_data, u3 stack_pushCurrent(buffer); break; case MSGTAG_HORSE_GENITIV: - #if VERSION == VERSION_GCN_PAL + #if TARGET_PC || VERSION == VERSION_GCN_PAL if (dComIfGs_getPalLanguage() == dSv_player_config_c::LANGUAGE_GERMAN) { setHorseName(buffer, 1); } else { @@ -4878,7 +4878,7 @@ bool jmessage_string_tRenderingProcessor::do_tag(u32 i_tag, void const* i_data, char buffer[40]; switch (i_tag & 0xFF00FFFF) { case MSGTAG_PLAYER_GENITIV: - #if VERSION == VERSION_GCN_PAL + #if TARGET_PC || VERSION == VERSION_GCN_PAL if (dComIfGs_getPalLanguage() == dSv_player_config_c::LANGUAGE_GERMAN) { setPlayerName(buffer, 1); } else { @@ -4889,7 +4889,7 @@ bool jmessage_string_tRenderingProcessor::do_tag(u32 i_tag, void const* i_data, push_word(buffer); break; case MSGTAG_HORSE_GENITIV: - #if VERSION == VERSION_GCN_PAL + #if TARGET_PC || VERSION == VERSION_GCN_PAL if (dComIfGs_getPalLanguage() == dSv_player_config_c::LANGUAGE_GERMAN) { setHorseName(buffer, 1); } else { diff --git a/src/d/d_msg_object.cpp b/src/d/d_msg_object.cpp index 9b7b64ebda..ae0e3d8427 100644 --- a/src/d/d_msg_object.cpp +++ b/src/d/d_msg_object.cpp @@ -24,9 +24,11 @@ #include "f_op/f_op_msg_mng.h" #include #include + +#include "JSystem/JKernel/JKRExpHeap.h" +#include "dusk/version.hpp" #include "m_Do/m_Do_controller_pad.h" #include "m_Do/m_Do_lib.h" -#include "JSystem/JKernel/JKRExpHeap.h" #if TARGET_PC #include "dusk/settings.h" @@ -1643,7 +1645,30 @@ void dMsgObject_c::readMessageGroupLocal(mDoDvdThd_mountXArchive_c** p_arcMount) #else #if TARGET_PC // Original game UB - snprintf(arcName, sizeof(arcName), "/res/Msgus/bmgres%d.arc", msgGroup); + + if (dusk::version::isRegionPal()) { + switch (dComIfGs_getPalLanguage()) { + case dSv_player_config_c::LANGUAGE_GERMAN: + snprintf(arcName, sizeof(arcName), "/res/Msgde/bmgres%d.arc", msgGroup); + break; + case dSv_player_config_c::LANGUAGE_FRENCH: + snprintf(arcName, sizeof(arcName), "/res/Msgfr/bmgres%d.arc", msgGroup); + break; + case dSv_player_config_c::LANGUAGE_SPANISH: + snprintf(arcName, sizeof(arcName), "/res/Msgsp/bmgres%d.arc", msgGroup); + break; + case dSv_player_config_c::LANGUAGE_ITALIAN: + snprintf(arcName, sizeof(arcName), "/res/Msgit/bmgres%d.arc", msgGroup); + break; + default: + snprintf(arcName, sizeof(arcName), "/res/Msguk/bmgres%d.arc", msgGroup); + } + } else if (dusk::version::isRegionJpn()) { + snprintf(arcName, sizeof(arcName), "/res/Msgjp/bmgres%d.arc", msgGroup); + } else { + snprintf(arcName, sizeof(arcName), "/res/Msgus/bmgres%d.arc", msgGroup); + } + #else sprintf(arcName, "/res/Msgus/bmgres%d.arc", msgGroup); #endif diff --git a/src/d/d_msg_unit.cpp b/src/d/d_msg_unit.cpp index f773abd373..7bbe8b3d70 100644 --- a/src/d/d_msg_unit.cpp +++ b/src/d/d_msg_unit.cpp @@ -334,11 +334,11 @@ void dMsgUnit_c::setTag(int i_type, int i_value, char* o_buffer, bool param_4) { vals[1] = ((dMsgUnit_inf1_section_t*)pInfoBlock)->entries[i_type].endFrame; #endif -#if REGION_PAL +#if TARGET_PC || REGION_PAL if (i_value == 1 || (dComIfGs_getPalLanguage() == dSv_player_config_c::LANGUAGE_FRENCH && i_value == 0)) { -#elif !REGION_USA +#elif !REGION_USA // Dusk TODO: What? This checks for Spanish when *not* PAL or USA? if (i_value == 1 || (dComIfGs_getPalLanguage() == dSv_player_config_c::LANGUAGE_SPANISH && i_value == 0)) { diff --git a/src/d/d_name.cpp b/src/d/d_name.cpp index e4232eff34..1f3ccbb1d7 100644 --- a/src/d/d_name.cpp +++ b/src/d/d_name.cpp @@ -8,9 +8,15 @@ #include "m_Do/m_Do_controller_pad.h" #include #include + #include "JSystem/J2DGraph/J2DAnmLoader.h" +#include "dusk/version.hpp" #include "f_op/f_op_msg_mng.h" +static bool isPalOrJpn() { + return dusk::version::isRegionPal() || dusk::version::isRegionJpn(); +} + static const char* l_mojiHira[65] = { "あ", "い", "う", "え", "お", "か", "き", "く", "け", "こ", "さ", "し", "す", "せ", "そ", "た", "ち", "つ", "て", "と", "な", "に", "ぬ", "ね", "の", "は", @@ -66,15 +72,31 @@ static const char* l_mojiEisu[65] = { "X", "k", "x", ",", "L", "Y", "l", "y", ".", "M", "Z", "m", "z", " ", }; -#if REGION_PAL -static char* l_mojiEisuPal_1[65] = { +#if TARGET_PC +// The game normally mutates this string list to fill in the real character codes. +// That can't work on a modern platform, so instead I've filled them out ahead of time. +static const char* l_mojiEisuPal_1[65] = { + "A", "N", "\xC0", "\xCF", "1", "B", "O", "\xC1", "\xD0", "2", "C", "P", "\xC2", "\xD1", "3", "D", "Q", + "\xC3", "\xD2", "4", "E", "R", "\xC4", "\xD3", "5", "F", "S", "\xC5", "\xD4", "6", "G", "T", "\xC6", "\xD5", + "7", "H", "U", "\xC7", "\xD6", "8", "I", "V", "\xC8", "\xD7", "9", "J", "W", "\xC9", "\xD8", "0", "K", + "X", "\xCA", "\xD9", ",", "L", "Y", "\xCB", "\xDA", ".", "M", "Z", "\xCC", "\xDB", " ", +}; + +static const char* l_mojiEisuPal_2[65] = { + "a", "n", "\xE0", "\xEF", "1", "b", "o", "\xE1", "\xF0", "2", "c", "p", "\xE2", "\xF1", "3", "d", "q", + "\xE3", "\xF2", "4", "e", "r", "\xE4", "\xF3", "5", "f", "s", "\xE5", "\xF4", "6", "g", "t", "\xE6", + "\xF5", "7", "h", "u", "\xE7", "\xF6", "8", "i", "v", "\xE8", "\xF7", "9", "j", "w", "\xE9", "\xF8", "0", + "k", "x", "\xEA", "\xF9", ",", "l", "y", "\xEB", "\xFA", ".", "m", "z", "\xEC", "\xFB", " ", +}; +#elif REGION_PAL +static const char* l_mojiEisuPal_1[65] = { "A", "N", "AA", "BB", "1", "B", "O", "CC", "DD", "2", "C", "P", "EE", "FF", "3", "D", "Q", "GG", "HH", "4", "E", "R", "II", "JJ", "5", "F", "S", "KK", "LL", "6", "G", "T", "MM", "NN", "7", "H", "U", "OO", "PP", "8", "I", "V", "QQ", "RR", "9", "J", "W", "SS", "TT", "0", "K", "X", "UU", "VV", ",", "L", "Y", "WW", "XX", ".", "M", "Z", "YY", "ZZ", " ", }; -static char* l_mojiEisuPal_2[65] = { +static const char* l_mojiEisuPal_2[65] = { "a", "n", "aa", "bb", "1", "b", "o", "cc", "dd", "2", "c", "p", "ee", "ff", "3", "d", "q", "gg", "hh", "4", "e", "r", "ii", "jj", "5", "f", "s", "kk", "ll", "6", "g", "t", "mm", "nn", "7", "h", "u", "oo", "pp", "8", "i", "v", "qq", "rr", "9", "j", "w", "ss", "tt", "0", @@ -82,6 +104,16 @@ static char* l_mojiEisuPal_2[65] = { }; #endif +#if TARGET_PC +// ' ' (full-width space) +#define SPACE_MAYBE_FULL (dusk::version::isRegionJpn() ? '\x81\x40' : ' ') +#elif REGION_JPN +// ' ' (full-width space) +#define SPACE_MAYBE_FULL '\x81\x40' +#else +#define SPACE_MAYBE_FULL ' ' +#endif + static dNm_HIO_c g_nmHIO; typedef void (dName_c::*selProcFunc)(void); @@ -163,13 +195,18 @@ void dName_c::init() { field_0x2ac = mSelProc; field_0x2ad = mSelProc; field_0x2ae = field_0x2ac; - #if REGION_PAL || REGION_JPN + #if TARGET_PC + mMojiSet = isPalOrJpn() ? MOJI_HIRA : MOJI_EIGO; + #elif REGION_PAL || REGION_JPN mMojiSet = MOJI_HIRA; #else mMojiSet = MOJI_EIGO; #endif mPrevMojiSet = 255; - #if REGION_PAL || REGION_JPN + #if TARGET_PC + mSelMenu = isPalOrJpn() ? MENU_HIRA : MENU_END; + mPrevSelMenu = isPalOrJpn() ? MENU_HIRA : MENU_END; + #elif REGION_PAL || REGION_JPN mSelMenu = MENU_HIRA; mPrevSelMenu = MENU_HIRA; #else @@ -187,7 +224,8 @@ void dName_c::initial() { mNextNameStr[0] = 0; } - #if REGION_PAL || REGION_JPN + #if TARGET_PC || REGION_PAL || REGION_JPN + IF_DUSK_BLOCK(isPalOrJpn()) if (mSelProc == PROC_MOJI_SELECT) { mMenuIcon[mMojiSet]->scale(g_nmHIO.mMenuScale, g_nmHIO.mMenuScale); mMenuText[mMojiSet]->setWhite(JUtility::TColor(0xC8, 0xC8, 0xC8, 0xFF)); @@ -196,6 +234,7 @@ void dName_c::initial() { mMenuText[mPrevMojiSet]->setWhite(JUtility::TColor(0x96, 0x96, 0x96, 0xFF)); } } + IF_DUSK_BLOCK_END #endif } @@ -230,8 +269,8 @@ void dName_c::_move() { stick->checkTrigger(); (this->*SelProc[mSelProc])(); - #if REGION_PAL || REGION_JPN - if (mDoCPd_c::getTrigY(PAD_1)) { + #if TARGET_PC || REGION_PAL || REGION_JPN + if (IF_DUSK(isPalOrJpn() &&) mDoCPd_c::getTrigY(PAD_1)) { mDoAud_seStart(Z2SE_SY_DUMMY, 0, 0, 0); mPrevMojiSet = mMojiSet; mMojiSet++; @@ -245,8 +284,8 @@ void dName_c::_move() { mojiListChange(); } else { #endif - #if REGION_JPN - if (mDoCPd_c::getTrigX(PAD_1)) { + #if TARGET_PC || REGION_JPN + if (IF_DUSK(dusk::version::isRegionJpn() &&) mDoCPd_c::getTrigX(PAD_1)) { if (mCurPos != 0) { if (mojiChange(mCurPos - 1) == 1) { mDoAud_seStart(Z2SE_SY_DUMMY, 0, 0, 0); @@ -283,12 +322,14 @@ void dName_c::_move() { backSpace(); } } else if (mDoCPd_c::getTrigStart(PAD_1)) { +#define EIGO_OR_END DUSK_IF_ELSE((dusk::version::isRegionPal() ? MENU_EIGO : MENU_END), MENU_END) + #if REGION_PAL if ((mSelProc != PROC_MENU_SELECT || mSelMenu != MENU_EIGO) && (mSelProc == PROC_MENU_SELECT || mSelProc == PROC_MOJI_SELECT)) { #else - if ((mSelProc != PROC_MENU_SELECT || mSelMenu != MENU_END) && + if ((mSelProc != PROC_MENU_SELECT || mSelMenu != EIGO_OR_END) && (mSelProc == PROC_MENU_SELECT || mSelProc == PROC_MOJI_SELECT)) { #endif @@ -297,7 +338,7 @@ void dName_c::_move() { #if REGION_PAL mSelMenu = MENU_EIGO; #else - mSelMenu = MENU_END; + mSelMenu = EIGO_OR_END; #endif switch (mSelProc) { @@ -314,10 +355,10 @@ void dName_c::_move() { } } } - #if REGION_JPN + #if TARGET_PC || REGION_JPN } #endif - #if REGION_PAL || REGION_JPN + #if TARGET_PC || REGION_PAL || REGION_JPN } #endif @@ -327,10 +368,9 @@ void dName_c::_move() { int dName_c::nameCheck() { for (int i = 8, len = 7; i > 0; i--) { #if REGION_JPN - // ' ' (full-width space) if (mChrInfo[len].mCharacter != ' ' && mChrInfo[len].mCharacter != '\x81\x40') { #else - if (mChrInfo[len].mCharacter != ' ') { + if (mChrInfo[len].mCharacter != ' ' IF_DUSK(&& (!dusk::version::isRegionJpn() || mChrInfo[len].mCharacter != '\x81\x40'))) { #endif return len + 1; } @@ -344,8 +384,8 @@ void dName_c::playNameSet(int nameLength) { char* str = mInputStr; for (int i = 0; i < nameLength; i++) { - #if REGION_JPN - if (mChrInfo[i].mMojiSet == 2) { + #if TARGET_PC || REGION_JPN + if (!dusk::version::isRegionJpn() || mChrInfo[i].mMojiSet == 2) { *str = mChrInfo[i].mCharacter; str += 1; } else { @@ -727,7 +767,34 @@ int dName_c::getMoji() { int result = -1; const char* moji; - #if REGION_PAL + #if TARGET_PC + if (dusk::version::isRegionPal()) { + switch (mMojiSet) { + case MOJI_HIRA: + moji = l_mojiEisuPal_1[mCharRow + mCharColumn * 5]; + break; + case MOJI_KATA: + moji = l_mojiEisuPal_2[mCharRow + mCharColumn * 5]; + break; + default: + abort(); + } + } else { + switch (mMojiSet) { + case MOJI_HIRA: + moji = l_mojiHira[mCharRow + mCharColumn * 5]; + break; + case MOJI_KATA: + moji = l_mojikata[mCharRow + mCharColumn * 5]; + break; + case MOJI_EIGO: + moji = l_mojiEisu[mCharRow + mCharColumn * 5]; + break; + default: + abort(); + } + } + #elif REGION_PAL switch (mMojiSet) { case MOJI_HIRA: moji = l_mojiEisuPal_1[mCharRow + mCharColumn * 5]; @@ -750,7 +817,17 @@ int dName_c::getMoji() { } #endif - #if REGION_JPN + #if TARGET_PC + if (dusk::version::isRegionJpn()) { + if (*(u8*)moji >> 4 == 0x8 || *(u8*)moji >> 4 == 0x9) { + result = *(u16*)moji; + } else { + result = *moji; + } + } else { + result = *moji; + } + #elif REGION_JPN if (*(u8*)moji >> 4 == 0x8 || *(u8*)moji >> 4 == 0x9) { result = *(u16*)moji; } else { @@ -763,6 +840,14 @@ int dName_c::getMoji() { return result; } +#if TARGET_PC +#define CHAR_TRUNC(val) (dusk::version::isRegionPal() ? val & 0xFF : val) +#elif REGION_PAL +#define CHAR_TRUNC(val) (val & 0xFF) +#else +#define CHAR_TRUNC(val) val +#endif + void dName_c::setMoji(int moji) { if (mCurPos == 8 || nameCheck() == 8) { mDoAud_seStart(Z2SE_SYS_ERROR, NULL, 0, 0); @@ -771,24 +856,14 @@ void dName_c::setMoji(int moji) { s32 notEmpty = false; for (int i = mCurPos; i < 8; i++) { - #if REGION_JPN - // ' ' (full-width space) - if (mChrInfo[i].mCharacter != '\x81\x40') { - #else - if (mChrInfo[i].mCharacter != ' ') { - #endif + if (mChrInfo[i].mCharacter != SPACE_MAYBE_FULL) { notEmpty = true; break; } } if (notEmpty) { - #if REGION_JPN - // ' ' (full-width space) - if (mChrInfo[7].mCharacter == '\x81\x40') { - #else - if (mChrInfo[7].mCharacter == ' ') { - #endif + if (mChrInfo[7].mCharacter == SPACE_MAYBE_FULL) { for (int i = 6; i >= mCurPos; i--) { mChrInfo[i + 1] = mChrInfo[i]; } @@ -797,11 +872,7 @@ void dName_c::setMoji(int moji) { mChrInfo[mCurPos].mRow = mCharRow; mChrInfo[mCurPos].mMojiSet = mMojiSet; mChrInfo[mCurPos].field_0x3 = 1; - #if REGION_PAL - mChrInfo[mCurPos].mCharacter = moji & 0xFF; - #else - mChrInfo[mCurPos].mCharacter = moji; - #endif + mChrInfo[mCurPos].mCharacter = CHAR_TRUNC(moji); if (mCurPos != 8) { mLastCurPos = mCurPos; @@ -814,11 +885,7 @@ void dName_c::setMoji(int moji) { mChrInfo[mCurPos].mRow = mCharRow; mChrInfo[mCurPos].mMojiSet = mMojiSet; mChrInfo[mCurPos].field_0x3 = 1; - #if REGION_PAL - mChrInfo[mCurPos].mCharacter = moji & 0xFF; - #else - mChrInfo[mCurPos].mCharacter = moji; - #endif + mChrInfo[mCurPos].mCharacter = CHAR_TRUNC(moji); if (mCurPos != 8) { mLastCurPos = mCurPos; @@ -844,13 +911,8 @@ void dName_c::setNameText() { "CR\x1b" "CC[000000]\x1bGM[0]%c\x1bHM\x1b" "CC[ffffff]\x1bGM[0]%c", - #if REGION_PAL - (u8)mChrInfo[i].mCharacter & 0xFF, - (u8)mChrInfo[i].mCharacter & 0xFF - #else - (u8)mChrInfo[i].mCharacter, - (u8)mChrInfo[i].mCharacter - #endif + CHAR_TRUNC((u8)mChrInfo[i].mCharacter), + CHAR_TRUNC((u8)mChrInfo[i].mCharacter) ); #if REGION_JPN } else { @@ -889,7 +951,29 @@ void dName_c::nameCursorMove() { void dName_c::selectCursorMove() { int idx; - #if REGION_PAL + #if TARGET_PC + if (dusk::version::isRegionPal()) { + if (mCharColumn < 3) { + idx = 0; + } else if (mCharColumn < 6) { + idx = 1; + } else if (mCharColumn >= 6) { + idx = 2; + } + } else if (dusk::version::isRegionJpn()) { + if (mCharColumn < 3) { + idx = 0; + } else if (mCharColumn < 6) { + idx = 1; + } else if (mCharColumn < 8) { + idx = 2; + } else if (mCharColumn >= 8) { + idx = 3; + } + } else { + idx = 3; + } + #elif REGION_PAL if (mCharColumn < 3) { idx = 0; } else if (mCharColumn < 6) { @@ -930,7 +1014,36 @@ void dName_c::selectCursorMove() { void dName_c::menuCursorPosSet() { mPrevSelMenu = mSelMenu; - #if REGION_PAL + #if TARGET_PC + if (dusk::version::isRegionPal()) { + if (mCharColumn < 3) { + mSelMenu = MENU_HIRA; + } else if (mCharColumn < 6) { + mSelMenu = MENU_KATA; + } else if (mCharColumn >= 6) { + mSelMenu = MENU_EIGO; + } + } else if (dusk::version::isRegionJpn()) { + if (mCharColumn < 3) { + mSelMenu = MENU_HIRA; + return; + } + if (mCharColumn < 6) { + mSelMenu = MENU_KATA; + return; + } + if (mCharColumn < 8) { + mSelMenu = MENU_EIGO; + return; + } + if (mCharColumn >= 8) { + mSelMenu = MENU_END; + return; + } + } else { + mSelMenu = MENU_END; + } + #elif REGION_PAL if (mCharColumn < 3) { mSelMenu = MENU_HIRA; } else if (mCharColumn < 6) { @@ -961,28 +1074,28 @@ void dName_c::menuCursorPosSet() { } void dName_c::MenuSelect() { - #if REGION_PAL || REGION_JPN - if (stick->checkRightTrigger()) { + #if TARGET_PC || REGION_PAL || REGION_JPN + if (isPalOrJpn() && stick->checkRightTrigger()) { mDoAud_seStart(Z2SE_SY_CURSOR_OPTION, NULL, 0, 0); mPrevSelMenu = mSelMenu; mSelMenu++; #if REGION_PAL if (mSelMenu > MENU_EIGO) { #else - if (mSelMenu > MENU_END) { + if (mSelMenu > EIGO_OR_END) { #endif mSelMenu = MENU_HIRA; } MenuSelectAnmInit(); mSelProc = PROC_MENU_SEL_ANM; - } else if (stick->checkLeftTrigger()) { + } else if (isPalOrJpn() && stick->checkLeftTrigger()) { mDoAud_seStart(Z2SE_SY_CURSOR_OPTION, NULL, 0, 0); mPrevSelMenu = mSelMenu; if (mSelMenu == MENU_HIRA) { #if REGION_JPN mSelMenu = MENU_END; #else - mSelMenu = MENU_EIGO; + mSelMenu = dusk::version::isRegionJpn() ? MENU_END : MENU_EIGO; #endif } else { mSelMenu--; @@ -1009,7 +1122,7 @@ void dName_c::MenuSelect() { #if REGION_PAL if (mSelMenu == MENU_EIGO) { #else - if (mSelMenu == MENU_END) { + if (mSelMenu == EIGO_OR_END) { #endif if (nameCheck() != 0) { mDoAud_seStart(Z2SE_SY_NAME_OK, NULL, 0, 0); @@ -1024,7 +1137,7 @@ void dName_c::MenuSelect() { #if REGION_PAL if (mSelMenu == MENU_EIGO) { #else - if (mSelMenu == MENU_END) { + if (mSelMenu == EIGO_OR_END) { #endif if (nameCheck() != 0) { mDoAud_seStart(Z2SE_SY_NAME_OK, NULL, 0, 0); @@ -1067,9 +1180,11 @@ void dName_c::MenuSelectAnm2() { if (canMove == true) { if (prevMenu_i != mojiSet_i) { mMenuText[prevMenu_i]->setWhite(JUtility::TColor(0x96, 0x96, 0x96, 0xFF)); - #if REGION_PAL || REGION_JPN + #if TARGET_PC || REGION_PAL || REGION_JPN + IF_DUSK_BLOCK(isPalOrJpn()) mMenuIcon[mojiSet_i]->scale(g_nmHIO.mMenuScale, g_nmHIO.mMenuScale); mMenuText[mojiSet_i]->setWhite(JUtility::TColor(0xC8, 0xC8, 0xC8, 0xFF)); + IF_DUSK_BLOCK_END #endif } selectCursorMove(); @@ -1081,6 +1196,11 @@ void dName_c::MenuSelectAnm2() { void dName_c::MenuSelectAnm3() {} void dName_c::menuAbtnSelect() { +#if TARGET_PC + if (dusk::version::isRegionPal() && mSelMenu == MENU_EIGO) { + goto pal_eigo; + } +#endif switch (mSelMenu) { case MENU_HIRA: case MENU_KATA: @@ -1098,6 +1218,7 @@ void dName_c::menuAbtnSelect() { #else case MENU_END: #endif + IF_DUSK(pal_eigo:) int nameNum = nameCheck(); if (nameNum != 0) { playNameSet(nameNum); @@ -1116,44 +1237,33 @@ void dName_c::backSpace() { if (mCurPos != 0) { mDoAud_seStart(Z2SE_SY_NAME_DELETE, NULL, 0, 0); - #if REGION_JPN - // ' ' (full-width space) - if (mCurPos == 8 && mChrInfo[7].mCharacter != '\x81\x40') { - #else - if (mCurPos == 8 && mChrInfo[7].mCharacter != ' ') { - #endif + if (mCurPos == 8 && mChrInfo[7].mCharacter != SPACE_MAYBE_FULL) { mChrInfo[7].mColumn = 7; mChrInfo[7].mRow = 1; - #if REGION_PAL || REGION_JPN + #if TARGET_PC + mChrInfo[7].mMojiSet = isPalOrJpn() ? MOJI_HIRA : MOJI_EIGO; + #elif REGION_PAL || REGION_JPN mChrInfo[7].mMojiSet = MOJI_HIRA; #else mChrInfo[7].mMojiSet = MOJI_EIGO; #endif mChrInfo[7].field_0x3 = 1; - #if REGION_JPN - // ' ' (full-width space) - mChrInfo[7].mCharacter = '\x81\x40'; - #else - mChrInfo[7].mCharacter = ' '; - #endif + mChrInfo[7].mCharacter = SPACE_MAYBE_FULL; } else { for (int i = mCurPos - 1; i < 7; i++) { mChrInfo[i] = mChrInfo[i + 1]; } mChrInfo[7].mColumn = 7; mChrInfo[7].mRow = 1; - #if REGION_PAL || REGION_JPN +#if TARGET_PC + mChrInfo[7].mMojiSet = isPalOrJpn() ? MOJI_HIRA : MOJI_EIGO; +#elif REGION_PAL || REGION_JPN mChrInfo[7].mMojiSet = MOJI_HIRA; #else mChrInfo[7].mMojiSet = MOJI_EIGO; #endif mChrInfo[7].field_0x3 = 1; - #if REGION_JPN - // ' ' (full-width space) - mChrInfo[7].mCharacter = '\x81\x40'; - #else - mChrInfo[7].mCharacter = ' '; - #endif + mChrInfo[7].mCharacter = SPACE_MAYBE_FULL; } setNameText(); @@ -1164,7 +1274,31 @@ void dName_c::backSpace() { } void dName_c::mojiListChange() { - #if REGION_PAL + #if TARGET_PC + const char** mojiSet; + if (dusk::version::isRegionPal()) { + switch (mMojiSet) { + case MOJI_HIRA: + mojiSet = l_mojiEisuPal_1; + break; + case MOJI_KATA: + mojiSet = l_mojiEisuPal_2; + break; + } + } else { + switch (mMojiSet) { + case MOJI_HIRA: + mojiSet = l_mojiHira; + break; + case MOJI_KATA: + mojiSet = l_mojikata; + break; + case MOJI_EIGO: + mojiSet = l_mojiEisu; + break; + } + } + #elif REGION_PAL char** mojiSet; switch (mMojiSet) { @@ -1212,7 +1346,8 @@ void dName_c::mojiListChange() { strcpy(mMojiText[i], buf); } - #if REGION_PAL || REGION_JPN + #if TARGET_PC || REGION_PAL || REGION_JPN + IF_DUSK_BLOCK(isPalOrJpn()) if (mSelProc == PROC_MOJI_SELECT) { mMenuIcon[mMojiSet]->scale(g_nmHIO.mMenuScale, g_nmHIO.mMenuScale); mMenuText[mMojiSet]->setWhite(JUtility::TColor(0xC8, 0xC8, 0xC8, 0xFF)); @@ -1221,6 +1356,7 @@ void dName_c::mojiListChange() { mMenuText[mPrevMojiSet]->setWhite(JUtility::TColor(0x96, 0x96, 0x96, 0xFF)); } } + IF_DUSK_BLOCK_END #endif } @@ -1241,9 +1377,11 @@ void dName_c::menuCursorMove2() { if (menu_i != mojiSet_i) { mMenuIcon[menu_i]->scale(g_nmHIO.mMenuScale, g_nmHIO.mMenuScale); mMenuText[menu_i]->setWhite(JUtility::TColor(0xC8, 0xC8, 0xC8, 0xFF)); - #if REGION_PAL || REGION_JPN + #if TARGET_PC || REGION_PAL || REGION_JPN + IF_DUSK_BLOCK(isPalOrJpn()) mMenuIcon[mojiSet_i]->scale(1.0f, 1.0f); mMenuText[mojiSet_i]->setWhite(JUtility::TColor(0x96, 0x96, 0x96, 0xFF)); + IF_DUSK_BLOCK_END #endif } @@ -1265,7 +1403,9 @@ void dName_c::selectCursorPosSet(int row) { mCharColumn = 3; break; case MENU_EIGO: - #if REGION_PAL + #if TARGET_PC + mCharColumn = dusk::version::isRegionPal() ? 8 : 6; + #elif REGION_PAL mCharColumn = 8; #else mCharColumn = 6; @@ -1481,9 +1621,11 @@ void dName_c::screenSet() { #endif } - #if !(REGION_PAL || REGION_JPN) + #if TARGET_PC || !(REGION_PAL || REGION_JPN) + IF_DUSK_BLOCK(!isPalOrJpn()) mMenuIcon[0]->hide(); mMenuIcon[1]->hide(); + IF_DUSK_BLOCK_END #endif mMojiPane = nameIn.NameInScr->search(MULTI_CHAR('moji_n')); @@ -1501,25 +1643,28 @@ void dName_c::screenSet() { ((J2DTextBox*)nameTagPane[i])->setFont(nameIn.font); ((J2DTextBox*)nameTagPane[i])->setString(72, ""); ((J2DTextBox*)nameTagPane[i])->setWhite(JUtility::TColor(0xC8, 0xC8, 0xC8, 0xFF)); - #if REGION_PAL + #if TARGET_PC || REGION_PAL + IF_DUSK_BLOCK(dusk::version::isRegionPal()) ((J2DTextBox*)nameTagPane[i])->resize(24.0f, 23.0f); + IF_DUSK_BLOCK_END #endif mNameText[i] = ((J2DTextBox*)nameTagPane[i])->getStringPtr(); } - #if REGION_PAL + #if REGION_PAL // DUSK version note: this code mutates strings. We just edit the table. + IF_DUSK_BLOCK(dusk::version::isRegionPal()) int idx = 2; - static u8 palMoji00[13] = { + static const u8 palMoji00[13] = { 0xC0, 0xC1, 0xC2, 0xC4, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, }; - static u8 palMoji01[13] = { + static const u8 palMoji01[13] = { 0xCF, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD6, 0x8C, 0xD9, 0xDA, 0xDB, 0xDC, 0x2D, }; - static u8 palMoji10[13] = { + static const u8 palMoji10[13] = { 0xE0, 0xE1, 0xE2, 0xE4, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, }; - static u8 palMoji11[13] = { + static const u8 palMoji11[13] = { 0xEF, 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF6, 0x9C, 0xF9, 0xFA, 0xFB, 0xFC, 0xDF, }; @@ -1536,6 +1681,7 @@ void dName_c::screenSet() { l_mojiEisuPal_2[idx + 1][0] = palMoji11[i]; l_mojiEisuPal_2[idx + 1][1] = 0; } + IF_DUSK_BLOCK_END #endif mCharColumn = 0; @@ -1574,18 +1720,15 @@ void dName_c::displayInit() { mNameCursor[i]->hide(); mChrInfo[i].mColumn = 7; mChrInfo[i].mRow = 1; - #if REGION_PAL || REGION_JPN + #if TARGET_PC + mChrInfo[i].mMojiSet = isPalOrJpn() ? MOJI_HIRA : MOJI_EIGO; + #elif REGION_PAL || REGION_JPN mChrInfo[i].mMojiSet = MOJI_HIRA; #else mChrInfo[i].mMojiSet = MOJI_EIGO; #endif mChrInfo[i].field_0x3 = 1; - #if REGION_JPN - // ' ' (full-width space) - mChrInfo[i].mCharacter = '\x81\x40'; - #else - mChrInfo[i].mCharacter = ' '; - #endif + mChrInfo[i].mCharacter = SPACE_MAYBE_FULL; } mIsInputEnd = false; @@ -1596,7 +1739,63 @@ void dName_c::NameStrSet() { int i = 0; while (*moji != 0) { - #if REGION_PAL + #if TARGET_PC + if (dusk::version::isRegionPal()) { + mChrInfo[i].mCharacter = static_cast(*moji); + + for (int j = 0; j < 65; j++) { + if (mChrInfo[i].mCharacter == *(u8*)l_mojiEisuPal_1[j] || + mChrInfo[i].mCharacter == *(u16*)l_mojiEisuPal_2[j]) + { + mChrInfo[i].mColumn = j / 5; + mChrInfo[i].mRow = j % 5; + mChrInfo[i].mMojiSet = MOJI_HIRA; + break; + } + } + moji++; + i++; + } else { + if (*(u8*)moji >> 4 == 8 || *(u8*)moji >> 4 == 9) { + mChrInfo[i].mCharacter = *(u16*)moji; + + for (int j = 0; j < 65; j++) { + if (mChrInfo[i].mCharacter == *(u16*)l_mojiHira[j] || + mChrInfo[i].mCharacter == *(u16*)l_mojiHira2[j] || + mChrInfo[i].mCharacter == *(u16*)l_mojiHira3[j]) + { + mChrInfo[i].mColumn = j / 5; + mChrInfo[i].mRow = j % 5; + mChrInfo[i].mMojiSet = MOJI_HIRA; + break; + } else if (mChrInfo[i].mCharacter == *(u16*)l_mojikata[j] || + mChrInfo[i].mCharacter == *(u16*)l_mojikata2[j] || + mChrInfo[i].mCharacter == *(u16*)l_mojikata3[j]) + { + mChrInfo[i].mColumn = j / 5; + mChrInfo[i].mRow = j % 5; + mChrInfo[i].mMojiSet = MOJI_KATA; + break; + } + } + moji += 2; + i++; + } else { + mChrInfo[i].mCharacter = *moji; + + for (int j = 0; j < 65; j++) { + if (mChrInfo[i].mCharacter == *(u8*)l_mojiEisu[j]) { + mChrInfo[i].mColumn = j / 5; + mChrInfo[i].mRow = j % 5; + mChrInfo[i].mMojiSet = MOJI_EIGO; + break; + } + } + moji++; + i++; + } + } + #elif REGION_PAL mChrInfo[i].mCharacter = static_cast(*moji); for (int j = 0; j < 65; j++) { @@ -1669,7 +1868,9 @@ s32 dName_c::getMenuPosIdx(u8 selPos) { result = 1; break; case 2: - #if REGION_PAL + #if TARGET_PC + result = dusk::version::isRegionPal() ? 3 : 2; + #elif REGION_PAL result = 3; #else result = 2; diff --git a/src/d/d_s_logo.cpp b/src/d/d_s_logo.cpp index f04df0fa39..cc5d0114a4 100644 --- a/src/d/d_s_logo.cpp +++ b/src/d/d_s_logo.cpp @@ -24,6 +24,7 @@ #include "JSystem/JUtility/JUTConsole.h" #include "dusk/logging.h" +#include "dusk/version.hpp" #if !PLATFORM_GCN #include @@ -44,7 +45,12 @@ struct homeBtnData { }; #endif -#if VERSION == VERSION_SHIELD +#if TARGET_PC +using namespace dusk::version; + +#define LOGO_ARC versionSelect({{GameVersion::GcnJpn, "Logo"}, {GameVersion::GcnPal, "LogoPal"}}, "LogoUs") +#define MSG_PATH versionSelect({{GameVersion::GcnJpn, "/res/Msgjp/bmgres.arc"}}, "/res/Msgus/bmgres.arc") +#elif VERSION == VERSION_SHIELD #define LOGO_ARC "LogoUs" #define MSG_PATH "/res/Msgcn/bmgres.arc" #elif VERSION == VERSION_GCN_JPN @@ -789,7 +795,13 @@ dScnLogo_c::~dScnLogo_c() { JKR_DELETE(mProgressiveNo); JKR_DELETE(mProgressiveSel); - #if VERSION == VERSION_GCN_PAL + #if TARGET_PC + if (getGameVersion() == GameVersion::GcnPal) { + mpPalLogoResCommand->getArchive()->removeResourceAll(); + mpPalLogoResCommand->getArchive()->unmount(); + mpPalLogoResCommand->destroy(); + } + #elif VERSION == VERSION_GCN_PAL mpPalLogoResCommand->getArchive()->removeResourceAll(); mpPalLogoResCommand->getArchive()->unmount(); mpPalLogoResCommand->destroy(); @@ -951,7 +963,8 @@ static int phase_0(dScnLogo_c* i_this) { JUT_ASSERT(1528, i_this->mLogo01Heap != NULL); JKRHEAP_NAME(i_this->mLogo01Heap, "Logo01"); - #if VERSION == VERSION_GCN_PAL + #if TARGET_PC || VERSION == VERSION_GCN_PAL + IF_DUSK_BLOCK(getGameVersion() == GameVersion::GcnPal) switch (i_this->getPalLanguage()) { case 1: i_this->mpPalLogoResCommand = mDoDvdThd_mountArchive_c::create("/res/Layout/LogoPalGm.arc", 0, NULL); @@ -970,6 +983,7 @@ static int phase_0(dScnLogo_c* i_this) { i_this->mpPalLogoResCommand = mDoDvdThd_mountArchive_c::create("/res/Layout/LogoPalUk.arc", 0, NULL); break; } + IF_DUSK_BLOCK_END #endif #if PLATFORM_WII || PLATFORM_SHIELD @@ -990,7 +1004,8 @@ static int phase_1(dScnLogo_c* i_this) { } #endif - #if VERSION == VERSION_GCN_PAL + #if TARGET_PC || VERSION == VERSION_GCN_PAL + IF_DUSK_BLOCK(getGameVersion() == GameVersion::GcnPal) if (!mDoDvdThd::SyncWidthSound) { return cPhs_INIT_e; } @@ -998,6 +1013,7 @@ static int phase_1(dScnLogo_c* i_this) { if (!i_this->mpPalLogoResCommand->sync()) { return cPhs_INIT_e; } + IF_DUSK_BLOCK_END #endif int rt; @@ -1237,7 +1253,113 @@ void dScnLogo_c::logoInitGC() { ResTIMG* dolbyImg = (ResTIMG*)dComIfG_getObjectRes(LOGO_ARC, 3); mDolbyLogo = JKR_NEW dDlst_2D_c(dolbyImg, 189, 150, 232, 112, 255); -#if VERSION == VERSION_GCN_PAL +#if TARGET_PC + if (getGameVersion() == GameVersion::GcnPal) { + u8 language = getPalLanguage(); + if (language >= 5) { + language = 0; + } + + static const char* choice[] = { + "50_60_choice_eng.bti", + "50_60_choice_ger.bti", + "50_60_choice_fra.bti", + "50_60_choice_spa.bti", + "50_60_choice_ita.bti", + }; + + static const char* yes[] = { + "60_set_eng.bti", + "60_set_ger.bti", + "60_set_fra.bti", + "60_set_spa.bti", + "60_set_ita.bti", + }; + + static const char* no[] = { + "50_set_eng.bti", + "50_set_ger.bti", + "50_set_fra.bti", + "50_set_spa.bti", + "50_set_ita.bti", + }; + + static const char* prog[] = { + "progressive_pro.bti", + "progressive_pro_gm.bti", + "progressive_pro_fr.bti", + "progressive_pro_sp.bti", + "progressive_pro_it.bti", + }; + + static const char* intr[] = { + "progressive_inter.bti", + "progressive_inter_gm.bti", + "progressive_inter_fr.bti", + "progressive_inter_sp.bti", + "progressive_inter_it.bti", + }; + + static const char* warning[] = { + "warning.bti", + "warning_gm.bti", + "warning_fr.bti", + "warning_sp.bti", + "warning_it.bti", + }; + + static const char* warningPs[] = { + "warning_pstart.bti", + "warning_pstart_gm.bti", + "warning_pstart_fr.bti", + "warning_pstart_sp.bti", + "warning_pstart_it.bti", + }; + + ResTIMG* warningImg = (ResTIMG*)mpPalLogoResCommand->getArchive()->getResource('DAT ', warning[language]); + mWarning = JKR_NEW dDlst_2D_c(warningImg, 0, 0, FB_WIDTH, FB_HEIGHT, 255); + + ResTIMG* warnStartImg = (ResTIMG*)mpPalLogoResCommand->getArchive()->getResource('DAT ', warningPs[language]); + mWarningStart = JKR_NEW dDlst_2D_c(warnStartImg, 0, 359, FB_WIDTH, 48, 255); + + ResTIMG* progChoiceImg = (ResTIMG*)mpPalLogoResCommand->getArchive()->getResource('DAT ', choice[language]); + mProgressiveChoice = JKR_NEW dDlst_2D_c(progChoiceImg, 113, 143, 416, 210, 255); + + ResTIMG* progYesImg = (ResTIMG*)mpPalLogoResCommand->getArchive()->getResource('DAT ', yes[language]); + mProgressiveYes = JKR_NEW dDlst_2D_c(progYesImg, 121, 352, 200, 72, 255); + mProgressiveYes->getPicture()->setWhite(JUtility::TColor(160, 160, 160, 255)); + + ResTIMG* progNoImg = (ResTIMG*)mpPalLogoResCommand->getArchive()->getResource('DAT ', no[language]); + mProgressiveNo = JKR_NEW dDlst_2D_c(progNoImg, 320, 352, 200, 72, 255); + mProgressiveNo->getPicture()->setWhite(JUtility::TColor(160, 160, 160, 255)); + + mProgressivePro = (ResTIMG*)mpPalLogoResCommand->getArchive()->getResource('DAT ', prog[language]); + mProgressiveInter = (ResTIMG*)mpPalLogoResCommand->getArchive()->getResource('DAT ', intr[language]); + mProgressiveSel = JKR_NEW dDlst_2D_c(mProgressivePro, 153, 309, 336, 88, 255); + } else { + ResTIMG* warningImg = (ResTIMG*)dComIfG_getObjectRes(LOGO_ARC, 10); + mWarning = JKR_NEW dDlst_2D_c(warningImg, 0, 0, FB_WIDTH, FB_HEIGHT, 255); + + ResTIMG* warnStartImg = (ResTIMG*)dComIfG_getObjectRes(LOGO_ARC, 11); + mWarningStart = JKR_NEW dDlst_2D_c(warnStartImg, 0, 359, FB_WIDTH, 48, 255); + + ResTIMG* progChoiceImg = (ResTIMG*)dComIfG_getObjectRes(LOGO_ARC, 5); + mProgressiveChoice = JKR_NEW dDlst_2D_c(progChoiceImg, 113, 281, 416, 72, 255); + + ResTIMG* progYesImg = (ResTIMG*)dComIfG_getObjectRes(LOGO_ARC, 9); + mProgressiveYes = JKR_NEW dDlst_2D_c(progYesImg, 211, 372, 80, 32, 255); + mProgressiveYes->getPicture()->setWhite(JUtility::TColor(160, 160, 160, 255)); + + ResTIMG* progNoImg = (ResTIMG*)dComIfG_getObjectRes(LOGO_ARC, 7); + mProgressiveNo = JKR_NEW dDlst_2D_c(progNoImg, 350, 372, 80, 32, 255); + mProgressiveNo->getPicture()->setWhite(JUtility::TColor(160, 160, 160, 255)); + + mProgressivePro = (ResTIMG*)dComIfG_getObjectRes(LOGO_ARC, 8); + mProgressiveInter = (ResTIMG*)dComIfG_getObjectRes(LOGO_ARC, 6); + mProgressiveSel = JKR_NEW dDlst_2D_c(mProgressivePro, 153, 309, 336, 88, 255); + } + +#elif VERSION == VERSION_GCN_PAL u8 language = getPalLanguage(); if (language >= 5) { language = 0; @@ -1395,7 +1517,30 @@ void dScnLogo_c::dvdDataLoad() { mpButtonCommand = aramMount(BUTTON_RES_PATH, mDoExt_getJ2dHeap()); mpCardIconCommand = aramMount(ICON_RES_PATH, mDoExt_getJ2dHeap()); - #if VERSION == VERSION_GCN_PAL + #if TARGET_PC + if (getGameVersion() == GameVersion::GcnPal) { + switch (getPalLanguage()) { + case 1: + mpBmgResCommand = onMemMount("/res/Msgde/bmgres.arc"); + break; + case 2: + mpBmgResCommand = onMemMount("/res/Msgfr/bmgres.arc"); + break; + case 3: + mpBmgResCommand = onMemMount("/res/Msgsp/bmgres.arc"); + break; + case 4: + mpBmgResCommand = onMemMount("/res/Msgit/bmgres.arc"); + break; + case 0: + default: + mpBmgResCommand = onMemMount("/res/Msguk/bmgres.arc"); + break; + } + } else { + mpBmgResCommand = onMemMount(MSG_PATH); + } + #elif VERSION == VERSION_GCN_PAL switch (getPalLanguage()) { case 1: mpBmgResCommand = onMemMount("/res/Msgde/bmgres.arc"); @@ -1435,7 +1580,10 @@ void dScnLogo_c::dvdDataLoad() { mpMsgResCommand[1] = aramMount(MSG_RES1_PATH, mDoExt_getJ2dHeap()); mpMsgResCommand[2] = aramMount(MSG_RES2_PATH, mDoExt_getJ2dHeap()); mpMsgResCommand[3] = aramMount(MSG_RES3_PATH, mDoExt_getJ2dHeap()); -#if VERSION == VERSION_GCN_JPN +#if TARGET_PC + const auto res4Path = versionSelect({{GameVersion::GcnJpn, "/res/Layout/msgres04.arc"}}, "/res/Layout/msgres04F.arc"); + mpMsgResCommand[4] = aramMount( res4Path, mDoExt_getJ2dHeap()); +#elif VERSION == VERSION_GCN_JPN mpMsgResCommand[4] = aramMount("/res/Layout/msgres04.arc", mDoExt_getJ2dHeap()); #else mpMsgResCommand[4] = aramMount("/res/Layout/msgres04F.arc", mDoExt_getJ2dHeap()); @@ -1445,7 +1593,23 @@ void dScnLogo_c::dvdDataLoad() { mpMain2DCommand = onMemMount(MAIN2D_PATH); -#if VERSION == VERSION_GCN_JPN +#if TARGET_PC + const auto fontResPath = versionSelect( + { + {GameVersion::GcnJpn, "/res/Fontjp/fontres.arc"}, + {GameVersion::GcnPal, "/res/Fonteu/fontres.arc"}, + }, "/res/Fontus/fontres.arc"); + const auto fontRubyPath = versionSelect( + { + {GameVersion::GcnJpn, "/res/Fontjp/rubyres.arc"}, + {GameVersion::GcnPal, "/res/Fonteu/rubyres.arc"}, + }, "/res/Fontus/rubyres.arc"); + + // Note: GCN_JPN mounts this archive as tail instead of head. + // I'm guessing this is fine since we have more RAM. + mpFontResCommand = onMemMount(fontResPath); + mpRubyResCommand = onMemMount(fontRubyPath); +#elif VERSION == VERSION_GCN_JPN mpFontResCommand = mDoDvdThd_mountXArchive_c::create("/res/Fontjp/fontres.arc", 1, JKRArchive::MOUNT_MEM, NULL); mpRubyResCommand = onMemMount("/res/Fontjp/rubyres.arc"); #elif VERSION == VERSION_GCN_PAL @@ -1545,7 +1709,7 @@ static int dScnLogo_IsDelete(dScnLogo_c* i_this) { return 1; } -#if VERSION == VERSION_GCN_PAL || PLATFORM_WII || PLATFORM_SHIELD +#if TARGET_PC || VERSION == VERSION_GCN_PAL || PLATFORM_WII || PLATFORM_SHIELD u8 dScnLogo_c::getPalLanguage() { u8 language; diff --git a/src/d/d_save.cpp b/src/d/d_save.cpp index 3289a49c64..be95d1f190 100644 --- a/src/d/d_save.cpp +++ b/src/d/d_save.cpp @@ -16,6 +16,8 @@ #include #include +#include "dusk/version.hpp" + #if PLATFORM_WII || PLATFORM_SHIELD #include #include @@ -1027,7 +1029,7 @@ void dSv_player_config_c::init() { mAttentionType = 0; mVibration = 1; -#if DEBUG +#if DEBUG // DUSK VERSION SUPPORT: This field isn't used, so we can ignore it. mLanguage = SCGetLanguage(); #elif REGION_PAL || VERSION >= VERSION_WII_USA_R2 mLanguage = OSGetLanguage(); @@ -1072,7 +1074,8 @@ void dSv_player_config_c::setVibration(u8 i_status) { } u8 dSv_player_config_c::getPalLanguage() const { -#if VERSION == VERSION_GCN_PAL +#if TARGET_PC || VERSION == VERSION_GCN_PAL + IF_DUSK_BLOCK(dusk::version::getGameVersion() == dusk::version::GameVersion::GcnPal) switch (OSGetLanguage()) { case 0: return LANGUAGE_ENGLISH; @@ -1085,6 +1088,7 @@ u8 dSv_player_config_c::getPalLanguage() const { case 4: return LANGUAGE_ITALIAN; } + IF_DUSK_BLOCK_END #elif VERSION >= VERSION_WII_USA_R0 switch (SCGetLanguage()) { case 1: diff --git a/src/dusk/config.cpp b/src/dusk/config.cpp index 176967359d..856cdce4a3 100644 --- a/src/dusk/config.cpp +++ b/src/dusk/config.cpp @@ -154,6 +154,7 @@ namespace dusk::config { template class ConfigImpl; template class ConfigImpl; template class ConfigImpl; + template class ConfigImpl; } void dusk::config::Register(ConfigVarBase& configVar) { diff --git a/src/dusk/crash_reporting.cpp b/src/dusk/crash_reporting.cpp index 0499f0d6a1..657e5ef5d2 100644 --- a/src/dusk/crash_reporting.cpp +++ b/src/dusk/crash_reporting.cpp @@ -167,7 +167,6 @@ void InitializeCrashReporting() { sentry_set_tag("git_branch", DUSK_WC_BRANCH); sentry_set_tag("build_type", DUSK_BUILD_TYPE); - sentry_set_tag("tp_version", DUSK_TP_VERSION); g_sentryInitialized = true; DuskLog.info("Initialized Sentry crash reporting"); diff --git a/src/dusk/dvd_asset.cpp b/src/dusk/dvd_asset.cpp index b9e0729be9..75c82107cc 100644 --- a/src/dusk/dvd_asset.cpp +++ b/src/dusk/dvd_asset.cpp @@ -80,8 +80,19 @@ static s32 DolVaToFileOffset(u32 va) { return -1; } -bool LoadDolAsset(void* dst, u32 virtualAddress, s32 size) { - s32 fileOffset = DolVaToFileOffset(virtualAddress); +static u32 GetOffsetForVersion(std::initializer_list version) { + const auto gameVersion = dusk::version::getGameVersion(); + for (auto elem : version) { + if (elem.mGameVersion == gameVersion) { + return elem.mOffset; + } + } + + DuskLog.fatal("Unable to find offset for this game version!"); +} + +bool LoadDolAsset(void* dst, std::initializer_list virtualAddress, s32 size) { + s32 fileOffset = DolVaToFileOffset(GetOffsetForVersion(virtualAddress)); if (fileOffset < 0) { return false; } @@ -95,13 +106,16 @@ bool LoadDolAsset(void* dst, u32 virtualAddress, s32 size) { return true; } -bool LoadRelAsset(void* dst, const char* dvdPath, s32 offset, s32 size) { - void* p = JKRDvdRipper::loadToMainRAM(dvdPath, (u8*)dst, EXPAND_SWITCH_UNKNOWN1, (u32)size, nullptr, JKRDvdRipper::ALLOC_DIRECTION_FORWARD, (u32)offset, nullptr, nullptr); - if (!p) DuskLog.fatal("dvd_asset: failed to load {} (offset={:#x} size={:#x})", dvdPath, offset, size); +bool LoadRelAsset(void* dst, const char* dvdPath, std::initializer_list offset, s32 size) { + auto resOffset = GetOffsetForVersion(offset); + void* p = JKRDvdRipper::loadToMainRAM(dvdPath, (u8*)dst, EXPAND_SWITCH_UNKNOWN1, (u32)size, nullptr, JKRDvdRipper::ALLOC_DIRECTION_FORWARD, resOffset, nullptr, nullptr); + if (!p) DuskLog.fatal("dvd_asset: failed to load {} (offset={:#x} size={:#x})", dvdPath, resOffset, size); return p != nullptr; } -bool LoadArchivedRelAsset(void* dst, u32 memType, const char* relFileName, s32 offset, s32 size) { +bool LoadArchivedRelAsset(void* dst, u32 memType, const char* relFileName, std::initializer_list offset, s32 size) { + auto resOffset = GetOffsetForVersion(offset); + // On TARGET_PC, cDyl_InitCallback skips DynamicModuleControl::initialize() due to static linking // Mount RELS.arc on first use so sArchive is available static bool s_mountAttempted = false; @@ -118,7 +132,7 @@ bool LoadArchivedRelAsset(void* dst, u32 memType, const char* relFileName, s32 o DuskLog.fatal("dvd_asset: {} not found in RELS archive", relFileName); return false; } - std::memcpy(dst, rel + offset, size); + std::memcpy(dst, rel + resOffset, size); return true; } diff --git a/src/dusk/imgui/ImGuiPreLaunchWindow.cpp b/src/dusk/imgui/ImGuiPreLaunchWindow.cpp index 2727e78718..6347e9bc01 100644 --- a/src/dusk/imgui/ImGuiPreLaunchWindow.cpp +++ b/src/dusk/imgui/ImGuiPreLaunchWindow.cpp @@ -22,6 +22,10 @@ typedef void (ImGuiPreLaunchWindow::*drawFunc)(); drawFunc drawTable[2] = {&ImGuiPreLaunchWindow::drawMainMenu, &ImGuiPreLaunchWindow::drawOptions}; +static constexpr std::array skLanguageNames = { + "English", "German", "French", "Spanish", "Italian" +}; + static constexpr std::array skGameDiscFileFilters{{ {"Game Disc Images", "iso;gcm;ciso;gcz;nfs;rvz;wbfs;wia;tgc"}, {"All Files", "*"}, @@ -195,6 +199,20 @@ void ImGuiPreLaunchWindow::drawOptions() { false); } + // TODO: Only show if PAL disc selected? + // Language selection + auto selectedLanguage = getSettings().game.language.getValue(); + if (ImGui::BeginCombo("Language", skLanguageNames[static_cast(selectedLanguage)])) { + for (u8 i = 0; i < skLanguageNames.size(); ++i) { + if (ImGui::Selectable(skLanguageNames[i])) { + getSettings().game.language.setValue(static_cast(i)); + config::Save(); + } + } + + ImGui::EndCombo(); + } + AuroraBackend configuredBackend = BACKEND_AUTO; const std::string& configuredBackendId = getSettings().backend.graphicsBackend; if (!try_parse_backend(configuredBackendId, configuredBackend)) { diff --git a/src/dusk/iso_validate.cpp b/src/dusk/iso_validate.cpp index 755b4eede0..0b4a19ae9c 100644 --- a/src/dusk/iso_validate.cpp +++ b/src/dusk/iso_validate.cpp @@ -19,6 +19,7 @@ constexpr const char* TP_GAME_IDS[] = { constexpr const char* SUPPORTED_TP_GAME_IDS[] = { "GZ2E01", // GCN USA + "GZ2P01", // GCN PAL }; template diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index 3e6ee733fe..c7d19beb31 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -20,6 +20,8 @@ UserSettings g_userSettings = { }, .game = { + .language { "game.language", GameLanguage::English }, + // Quality of Life .enableQuickTransform {"game.enableQuickTransform", false}, .hideTvSettingsScreen {"game.hideTvSettingsScreen", false}, @@ -123,6 +125,7 @@ void registerSettings() { Register(g_userSettings.audio.enableReverb); // Game + Register(g_userSettings.game.language); Register(g_userSettings.game.enableQuickTransform); Register(g_userSettings.game.hideTvSettingsScreen); Register(g_userSettings.game.skipWarningScreen); diff --git a/src/dusk/version.cpp b/src/dusk/version.cpp new file mode 100644 index 0000000000..e925dabaf8 --- /dev/null +++ b/src/dusk/version.cpp @@ -0,0 +1,82 @@ +#include "dusk/version.hpp" + +#include "dusk/logging.h" + +namespace dusk::version { + +using namespace std::string_view_literals; + +static bool versionInitialized; +static GameVersion gameVersion; +static DVDDiskID diskId; + +void init() { + versionInitialized = true; + + if (!DVDLowReadDiskID(&diskId, nullptr)) { + DuskLog.fatal("DVDLowReadDiskID failed to return instantly."); + } + + std::string_view company(diskId.company, sizeof(diskId.company)); + std::string_view game(diskId.gameName, sizeof(diskId.gameName)); + + if (company != "01"sv) { + DuskLog.fatal("Wrong company ID in disc: {}", company); + } + + if (game == "GZ2E"sv) { + gameVersion = GameVersion::GcnUsa; + } else if (game == "GZ2P") { + gameVersion = GameVersion::GcnPal; + } else { + // TODO: Handle remaining valid versions. + DuskLog.fatal("Unknown/unsupported game version in disc: {}", game); + } + + DuskLog.info("Loaded game disc is {}{}", game, company); +} + +bool isGcn() { + return getGameVersion() == GameVersion::GcnUsa + || getGameVersion() == GameVersion::GcnPal + || getGameVersion() == GameVersion::GcnJpn; +} + +bool isWii() { + return getGameVersion() == GameVersion::WiiUsaRev0 + || getGameVersion() == GameVersion::WiiUsa + || getGameVersion() == GameVersion::WiiPal + || getGameVersion() == GameVersion::WiiJpn + || getGameVersion() == GameVersion::WiiKor; +} + +bool isPalOrAtLeastWiiR2() { + return getGameVersion() == GameVersion::GcnPal || getGameVersion() >= GameVersion::WiiUsa; +} + +bool isRegionJpn() { + return getGameVersion() == GameVersion::WiiJpn || getGameVersion() == GameVersion::GcnJpn; +} + +bool isRegionPal() { + return getGameVersion() == GameVersion::WiiPal || getGameVersion() == GameVersion::GcnPal; +} + +bool isRegionUsa() { + return getGameVersion() == GameVersion::WiiUsa || getGameVersion() == GameVersion::WiiUsaRev0 + || getGameVersion() == GameVersion::GcnUsa; +} + +GameVersion getGameVersion() { + if (!versionInitialized) { + abort(); + } + + return gameVersion; +} + +const DVDDiskID& getDiskID() { + return diskId; +} + +} // namespace dusk::version \ No newline at end of file diff --git a/src/m_Do/m_Do_MemCard.cpp b/src/m_Do/m_Do_MemCard.cpp index 5cb612f128..dbf962b173 100644 --- a/src/m_Do/m_Do_MemCard.cpp +++ b/src/m_Do/m_Do_MemCard.cpp @@ -12,6 +12,7 @@ #include "os_report.h" #include "dusk/os.h" #include "dusk/main.h" +#include "dusk/version.hpp" #if PLATFORM_WII || PLATFORM_SHIELD #include @@ -79,7 +80,11 @@ void mDoMemCd_Ctrl_c::ThdInit() { #if !PLATFORM_SHIELD CARDSetLoadType((CARDFileType)dusk::getSettings().backend.cardFileType.getValue()); - CARDInit(DUSK_GAME_NAME, DUSK_GAME_VERSION); + char version[5] = {}; + char maker[3] = {}; + std::memcpy(version, dusk::version::getDiskID().gameName, 4); + std::memcpy(maker, dusk::version::getDiskID().company, 2); + CARDInit(version, maker); #endif mCopyToPos = 0; diff --git a/src/m_Do/m_Do_MemCardRWmng.cpp b/src/m_Do/m_Do_MemCardRWmng.cpp index 518bdd27e9..d7739d6158 100644 --- a/src/m_Do/m_Do_MemCardRWmng.cpp +++ b/src/m_Do/m_Do_MemCardRWmng.cpp @@ -11,6 +11,8 @@ #include #include +#include "dusk/version.hpp" + #if VERSION == VERSION_GCN_JPN #define HEADER_TITLE "ゼルダの伝説 トワイライトプリンセス" #define HEADER_COMMENT "%d月%d日のセーブデータです" @@ -313,12 +315,44 @@ s32 mDoMemCdRWm_StoreBannerNAND(NANDFileInfo* file) { #endif static void mDoMemCdRWm_BuildHeader(mDoMemCdRWm_HeaderData* header) { +#if !TARGET_PC snprintf(header->mTitle, sizeof(header->mTitle), HEADER_TITLE); +#endif OSCalendarTime time; OSTicksToCalendarTime(OSGetTime(), &time); -#if VERSION == VERSION_GCN_PAL +#if TARGET_PC + if (dusk::version::isRegionPal()) { + snprintf(header->mTitle, sizeof(header->mTitle), HEADER_TITLE); + + switch (dComIfGs_getPalLanguage()) { + case dSv_player_config_c::LANGUAGE_ENGLISH: + snprintf(header->mComment, sizeof(header->mComment), "%d/%d Save Data", time.mon + 1, time.mday); + break; + case dSv_player_config_c::LANGUAGE_GERMAN: + snprintf(header->mComment, sizeof(header->mComment), "%d/%d Spielstand", time.mday, time.mon + 1); + break; + case dSv_player_config_c::LANGUAGE_FRENCH: + snprintf(header->mComment, sizeof(header->mComment), "Donn%ces de jeu %d/%d", 0xE9, time.mday, time.mon + 1); + break; + case dSv_player_config_c::LANGUAGE_SPANISH: + snprintf(header->mComment, sizeof(header->mComment), "Datos guardados el %d/%d", time.mday, time.mon + 1); + break; + case dSv_player_config_c::LANGUAGE_ITALIAN: + snprintf(header->mComment, sizeof(header->mComment), "Dati salvati: %d/%d", time.mday, time.mon + 1); + break; + } + } else if (dusk::version::isRegionUsa()) { + snprintf(header->mTitle, sizeof(header->mTitle), HEADER_TITLE); + + snprintf(header->mComment, sizeof(header->mComment), HEADER_COMMENT, time.mon + 1, time.mday); + } else { + // TODO JPN SHIFT-JIS + // snprintf(header->mTitle, sizeof(header->mTitle), "ゼルダの伝説 トワイライトプリンセス"); + // snprintf(header->mComment, sizeof(header->mComment), "%d月%d日のセーブデータです", time.mon + 1, time.mday); + } +#elif VERSION == VERSION_GCN_PAL switch (dComIfGs_getPalLanguage()) { case dSv_player_config_c::LANGUAGE_ENGLISH: snprintf(header->mComment, sizeof(header->mComment), "%d/%d Save Data", time.mon + 1, time.mday); diff --git a/src/m_Do/m_Do_main.cpp b/src/m_Do/m_Do_main.cpp index 579cbd8c8b..d25debb38e 100644 --- a/src/m_Do/m_Do_main.cpp +++ b/src/m_Do/m_Do_main.cpp @@ -71,6 +71,7 @@ #include "dusk/config.hpp" #include "dusk/imgui/ImGuiConsole.hpp" #include "dusk/settings.h" +#include "dusk/version.hpp" #include "dusk/discord_presence.hpp" #include "tracy/Tracy.hpp" #include "f_pc/f_pc_draw.h" @@ -465,6 +466,23 @@ static constexpr PADDefaultMapping defaultPadMapping = { static bool mainCalled = false; +static u8 selectedLanguage; + +u8 OSGetLanguage() { + return selectedLanguage; +} + +static void LanguageInit() { + // Keep language at 0 (English) if not on a PAL disk. + // Doubt this matters, but avoid funky shit. + if (!dusk::version::isRegionPal()) { + return; + } + + // Cache this to avoid funky shenanigans. + selectedLanguage = static_cast(dusk::getSettings().game.language.getValue()); +} + // ========================================================================= // PC ENTRY POINT // ========================================================================= @@ -597,6 +615,9 @@ int game_main(int argc, char* argv[]) { } } + dusk::version::init(); + LanguageInit(); + OSInit(); mDoMain::sPowerOnTime = OSGetTime(); From 0e5ea0d3b1552cd00beffa05b41b889cc9a8c539 Mon Sep 17 00:00:00 2001 From: Howard Luck Date: Sat, 25 Apr 2026 15:48:45 -0600 Subject: [PATCH 16/27] fix d_meter_button flashing (#549) --- include/d/d_meter_button.h | 5 +++++ src/d/d_meter_button.cpp | 16 +++++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/include/d/d_meter_button.h b/include/d/d_meter_button.h index 3028abeab5..0f05d32440 100644 --- a/include/d/d_meter_button.h +++ b/include/d/d_meter_button.h @@ -343,6 +343,11 @@ public: /* 0x624 */ f32 mMidonaPosX; /* 0x628 */ f32 mMidonaPosY; /* 0x62C */ f32 mMidonaScale; + +#ifdef TARGET_PC + bool mWasListen[2]; + bool mWasRepeat[2]; +#endif }; #endif /* D_METER_D_METER_BUTTON_H */ diff --git a/src/d/d_meter_button.cpp b/src/d/d_meter_button.cpp index d36a124d17..e55b42bc1a 100644 --- a/src/d/d_meter_button.cpp +++ b/src/d/d_meter_button.cpp @@ -368,8 +368,22 @@ void dMeterButton_c::draw() { } if (var_r3) { +#ifdef TARGET_PC + if (dusk::frame_interp::get_ui_tick_pending()) { + mWasListen[i] = var_r22; + mWasRepeat[i] = var_r23; + } else { + var_r22 = mWasListen[i]; + var_r23 = mWasRepeat[i]; + } +#endif if (var_r22) { - if (field_0x2e8[i] == 18.0f) { +#ifdef TARGET_PC + if (field_0x2e8[i] == 18.0f && dusk::frame_interp::get_ui_tick_pending()) +#else + if (field_0x2e8[i] == 18.0f) +#endif + { mDoAud_seStart(Z2SE_SY_HINT_BUTTON_BLINK, NULL, 0, 0); } From 1b792eb964d6c77ea73c485f2da256455edf5456 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Sat, 25 Apr 2026 16:18:16 -0600 Subject: [PATCH 17/27] Update aurora --- extern/aurora | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/aurora b/extern/aurora index 8d9618dc4b..135e976867 160000 --- a/extern/aurora +++ b/extern/aurora @@ -1 +1 @@ -Subproject commit 8d9618dc4bd82e476754c5b841b85fba6ee6ddab +Subproject commit 135e976867490380356878c8bb8d1956932489c5 From ca9330b412da1e6d84ff0ae9d017206e89e1c237 Mon Sep 17 00:00:00 2001 From: Irastris Date: Sat, 25 Apr 2026 20:40:29 -0400 Subject: [PATCH 18/27] Register interp callback for d_a_obj_lv8OptiLift Fixes disappearing platforms --- src/d/actor/d_a_obj_lv8OptiLift.cpp | 41 +++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/d/actor/d_a_obj_lv8OptiLift.cpp b/src/d/actor/d_a_obj_lv8OptiLift.cpp index a8610989a7..6b6c806d64 100644 --- a/src/d/actor/d_a_obj_lv8OptiLift.cpp +++ b/src/d/actor/d_a_obj_lv8OptiLift.cpp @@ -11,6 +11,10 @@ #include "d/d_com_inf_game.h" #include "d/d_path.h" +#if TARGET_PC +#include "dusk/frame_interpolation.h" +#endif + daOptiLift_HIO_c::daOptiLift_HIO_c() { mStopDisappearTime = 30; mStartMoveTime = 30; @@ -412,7 +416,44 @@ void daOptiLift_c::setNextPoint() { mCurrentPoint = next_point; } +#if TARGET_PC +static void daOptiLift_interp_callback(bool isSimFrame, void* pUserWork) { + daOptiLift_c* lift = static_cast(pUserWork); + if (lift == NULL || lift->mpModel == NULL) { + return; + } + + g_env_light.settingTevStruct(0x10, &lift->current.pos, &lift->tevStr); + g_env_light.setLightTevColorType_MAJI(lift->mpModel, &lift->tevStr); + + J3DModelData* modelData = lift->mpModel->getModelData(); + J3DMaterial* materialp = modelData->getMaterialNodePointer(0); + + if (materialp->getTexGenBlock()->getTexMtx(1) != NULL) { + J3DTexMtxInfo* mtx_info = &materialp->getTexGenBlock()->getTexMtx(1)->getTexMtxInfo(); + if (mtx_info != NULL) { + Mtx m; + C_MTXLightOrtho(m, 100.0f, -100.0f, -100.0f, 100.0f, 1.0f, 1.0f, 0.0f, 0.0f); + mDoMtx_stack_c::XrotS(0x4000); + mDoMtx_stack_c::transM(-lift->current.pos.x, -lift->current.pos.y, -lift->current.pos.z); + cMtx_concat(m, mDoMtx_stack_c::get(), mtx_info->mEffectMtx); + } + } + + lift->mBtk.entry(modelData); + + J3DGXColor* color = materialp->getTevKColor(1); + color->r = l_HIO.mColorR; + color->g = l_HIO.mColorG; + color->b = l_HIO.mColorB; +} +#endif + int daOptiLift_c::Draw() { +#if TARGET_PC + dusk::frame_interp::add_interpolation_callback(&daOptiLift_interp_callback, this); +#endif + g_env_light.settingTevStruct(0x10, ¤t.pos, &tevStr); g_env_light.setLightTevColorType_MAJI(mpModel, &tevStr); From 6fc862814419dd36e3e06e388d3575f97b8ccb03 Mon Sep 17 00:00:00 2001 From: Howard Luck Date: Sat, 25 Apr 2026 18:44:38 -0600 Subject: [PATCH 19/27] fix gdorf horseback reins + demo cuts (#551) --- include/d/actor/d_a_b_gnd.h | 9 ++++++ src/d/actor/d_a_b_gnd.cpp | 60 +++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/include/d/actor/d_a_b_gnd.h b/include/d/actor/d_a_b_gnd.h index b9c8781ec6..0827102eec 100644 --- a/include/d/actor/d_a_b_gnd.h +++ b/include/d/actor/d_a_b_gnd.h @@ -188,6 +188,15 @@ public: /* 0x273C */ f32 mKankyoBlend; /* 0x2740 */ u8 field_0x2740; /* 0x2744 */ dMsgFlow_c mMsgFlow; +#if TARGET_PC + cXyz mReinsInterpPrev[2][16]; + cXyz mReinsInterpCurr[2][16]; + cXyz mReinsTexInterpPrev[2]; + cXyz mReinsTexInterpCurr[2]; + bool mReinsInterpPrevValid; + bool mReinsInterpCurrValid; + s8 mDemoCamSyncTicks; +#endif }; STATIC_ASSERT(sizeof(b_gnd_class) == 0x2790); diff --git a/src/d/actor/d_a_b_gnd.cpp b/src/d/actor/d_a_b_gnd.cpp index 2272c35376..cd6bce78c4 100644 --- a/src/d/actor/d_a_b_gnd.cpp +++ b/src/d/actor/d_a_b_gnd.cpp @@ -17,6 +17,9 @@ #include "Z2AudioLib/Z2Instances.h" +#include "dusk/frame_interpolation.h" +#include "dusk/settings.h" + class daB_GND_HIO_c : public JORReflexible { public: daB_GND_HIO_c(); @@ -279,6 +282,30 @@ static int h_nodeCallBack(J3DJoint* i_joint, int param_2) { return 1; } +#if TARGET_PC +static void b_gnd_rein_interp_callback(bool isSimFrame, void* pUserWork) { + b_gnd_class* i_this = (b_gnd_class*)pUserWork; + if (!i_this->mReinsInterpPrevValid || !i_this->mReinsInterpCurrValid) { + return; + } + const f32 alpha = dusk::frame_interp::get_interpolation_step(); + for (int r = 0; r < 2; r++) { + cXyz* dst = i_this->mHorseReins[r].getPos(0); + for (int i = 0; i < 16; i++) { + const cXyz& p0 = i_this->mReinsInterpPrev[r][i]; + const cXyz& p1 = i_this->mReinsInterpCurr[r][i]; + dst[i] = p0 + (p1 - p0) * alpha; + } + } + cXyz* dst = i_this->field_0x21e8.getPos(0); + for (int i = 0; i < 2; i++) { + const cXyz& p0 = i_this->mReinsTexInterpPrev[i]; + const cXyz& p1 = i_this->mReinsTexInterpCurr[i]; + dst[i] = p0 + (p1 - p0) * alpha; + } +} +#endif + static int daB_GND_Draw(b_gnd_class* i_this) { fopAc_ac_c* a_this = (fopAc_ac_c*)i_this; @@ -366,6 +393,21 @@ static int daB_GND_Draw(b_gnd_class* i_this) { i_this->field_0x21e8.update(2, l_color, &a_this->tevStr); dComIfGd_set3DlineMat(&i_this->field_0x21e8); +#if TARGET_PC + if (dusk::getSettings().game.enableFrameInterpolation) { + if (i_this->mReinsInterpCurrValid) { + memcpy(i_this->mReinsInterpPrev, i_this->mReinsInterpCurr, sizeof(i_this->mReinsInterpCurr)); + memcpy(i_this->mReinsTexInterpPrev, i_this->mReinsTexInterpCurr, sizeof(i_this->mReinsTexInterpCurr)); + i_this->mReinsInterpPrevValid = true; + } + for (int r = 0; r < 2; r++) { + memcpy(i_this->mReinsInterpCurr[r], i_this->mHorseReins[r].getPos(0), 16 * sizeof(cXyz)); + } + memcpy(i_this->mReinsTexInterpCurr, i_this->field_0x21e8.getPos(0), 2 * sizeof(cXyz)); + i_this->mReinsInterpCurrValid = true; + dusk::frame_interp::add_interpolation_callback(&b_gnd_rein_interp_callback, i_this); + } +#endif } return 1; @@ -1189,10 +1231,16 @@ static void b_gnd_h_end(b_gnd_class* i_this) { if (i_this->mDemoCamMode < 32) { i_this->mDemoCamMode = 32; +#if TARGET_PC + i_this->mDemoCamSyncTicks = 2; +#endif } else { i_this->mDemoCamMode = 34; i_this->mDemoCamTimer = 0; i_this->mMoveMode = 2; +#if TARGET_PC + i_this->mDemoCamSyncTicks = 2; +#endif } } break; @@ -2887,6 +2935,9 @@ static void demo_camera(b_gnd_class* i_this) { cXyz spF0; s8 sp8 = false; +#if TARGET_PC + const s16 entry_demo_cam_mode = i_this->mDemoCamMode; +#endif switch (i_this->mDemoCamMode) { case 0: break; @@ -3725,6 +3776,15 @@ static void demo_camera(b_gnd_class* i_this) { i_this->mDemoCamTimer = 10000; } } +#if TARGET_PC + if (entry_demo_cam_mode != i_this->mDemoCamMode) { + i_this->mDemoCamSyncTicks = 2; + } + if (i_this->mDemoCamSyncTicks > 0) { + dusk::frame_interp::request_presentation_sync(); + i_this->mDemoCamSyncTicks--; + } +#endif } static void anm_se_set(b_gnd_class* i_this) { From 574b6197a23619ed8f59b6e7bb949d69f4a7a016 Mon Sep 17 00:00:00 2001 From: madeline Date: Sat, 25 Apr 2026 18:23:12 -0700 Subject: [PATCH 20/27] achievements --- files.cmake | 3 + include/dusk/achievements.h | 65 ++++ include/dusk/settings.h | 1 + src/dusk/achievements.cpp | 467 +++++++++++++++++++++++++ src/dusk/imgui/ImGuiAchievements.cpp | 228 ++++++++++++ src/dusk/imgui/ImGuiAchievements.hpp | 23 ++ src/dusk/imgui/ImGuiConsole.cpp | 11 + src/dusk/imgui/ImGuiFirstRunPreset.cpp | 1 + src/dusk/imgui/ImGuiMenuGame.cpp | 1 + src/dusk/imgui/ImGuiMenuTools.cpp | 10 + src/dusk/imgui/ImGuiMenuTools.hpp | 6 + src/dusk/settings.cpp | 2 + 12 files changed, 818 insertions(+) create mode 100644 include/dusk/achievements.h create mode 100644 src/dusk/achievements.cpp create mode 100644 src/dusk/imgui/ImGuiAchievements.cpp create mode 100644 src/dusk/imgui/ImGuiAchievements.hpp diff --git a/files.cmake b/files.cmake index af101b6c47..01b3863505 100644 --- a/files.cmake +++ b/files.cmake @@ -1460,6 +1460,9 @@ set(DUSK_FILES src/dusk/imgui/ImGuiSaveEditor.cpp src/dusk/imgui/ImGuiStateShare.hpp src/dusk/imgui/ImGuiStateShare.cpp + src/dusk/imgui/ImGuiAchievements.hpp + src/dusk/imgui/ImGuiAchievements.cpp + src/dusk/achievements.cpp src/dusk/iso_validate.cpp src/dusk/offset_ptr.cpp src/dusk/OSContext.cpp diff --git a/include/dusk/achievements.h b/include/dusk/achievements.h new file mode 100644 index 0000000000..cd4294b6f1 --- /dev/null +++ b/include/dusk/achievements.h @@ -0,0 +1,65 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "nlohmann/json.hpp" + +namespace dusk { + +enum class AchievementCategory : uint8_t { + Story, + Collection, + Challenge, + Minigame, + Glitched +}; + +struct Achievement { + const char* key; + const char* name; + const char* description; + AchievementCategory category; + bool isCounter; + int32_t goal; + int32_t progress; + bool unlocked; +}; + +// Responsible for updating a.progress. +// Use extra for any per-achievement state that must survive across frames or sessions, extra is saved +using AchievementCheckFn = std::function; + +class AchievementSystem { +public: + static AchievementSystem& get(); + + void load(); + void save(); + void tick(); + void clearAll(); + + std::vector getAchievements() const; + bool hasPendingUnlock() const { return !m_pendingUnlocks.empty(); } + std::string consumePendingUnlock(); + +private: + struct Entry { + Achievement achievement; + AchievementCheckFn check; + nlohmann::json extra; + }; + + AchievementSystem(); + static std::vector makeEntries(); + void processEntry(Entry& e); + + std::vector m_entries; + bool m_loaded = false; + bool m_dirty = false; + std::queue m_pendingUnlocks; +}; + +} // namespace dusk diff --git a/include/dusk/settings.h b/include/dusk/settings.h index 039e51af7d..8d67805ff3 100644 --- a/include/dusk/settings.h +++ b/include/dusk/settings.h @@ -70,6 +70,7 @@ struct UserSettings { ConfigVar disableMainHUD; ConfigVar pauseOnFocusLost; ConfigVar enableLinkDollRotation; + ConfigVar enableAchievementNotifications; // Graphics diff --git a/src/dusk/achievements.cpp b/src/dusk/achievements.cpp new file mode 100644 index 0000000000..071332a7c2 --- /dev/null +++ b/src/dusk/achievements.cpp @@ -0,0 +1,467 @@ +#include "dusk/achievements.h" +#include "dusk/io.hpp" +#include "dusk/main.h" +#include "d/d_com_inf_game.h" +#include "d/d_meter2_info.h" +#include "d/actor/d_a_alink.h" +#include "d/actor/d_a_npc4.h" +#include "d/actor/d_a_player.h" +#include "d/d_demo.h" +#include "f_pc/f_pc_name.h" + +#include +#include + +namespace dusk { + +using json = nlohmann::json; + +static void checkGoatHerding(Achievement& a, int32_t threshMs) { + if (dMeter2Info_getMaxCount() != 20 || dMeter2Info_getNowCount() != 20) { + return; + } + const int32_t elapsed = dMeter2Info_getTimeMs(); + if (elapsed > 0 && elapsed <= threshMs) { + a.progress = 1; + } +} + +static constexpr auto ACHIEVEMENTS_FILENAME = "achievements.json"; + +std::vector AchievementSystem::makeEntries() { + return { + { + { + "hero_of_twilight", + "Hero of Twilight", + "Deliver the finishing blow to Ganondorf.", + AchievementCategory::Story, + false, 0, 0, false + }, + [](Achievement& a, json&) { + const auto* link = static_cast(daPy_getPlayerActorClass()); + if (link != nullptr && link->mProcID == daAlink_c::PROC_GANON_FINISH) { + a.progress = 1; + } + }, + {} + }, + { + { + "rollgoal_8", + "Rollgoal Novice", + "Complete the first 8 rollgoal stages.", + AchievementCategory::Minigame, + true, 8, 0, false + }, + [](Achievement& a, json&) { + a.progress = std::min((int)dComIfGs_getEventReg(0xf63f), 8); + }, + {} + }, + { + { + "rollgoal_all", + "Lost Your Marbles", + "Complete all rollgoal stages.", + AchievementCategory::Minigame, + true, 64, 0, false + }, + [](Achievement& a, json&) { + if (dComIfGs_isEventBit(dSv_event_flag_c::KORO2_ALLCLEAR)) { + a.progress = 64; + } else { + a.progress = dComIfGs_getEventReg(0xf63f); + } + }, + {} + }, + { + { + "goat_30s", + "Ranch Hand", + "Herd all 20 goats into the pen in under 30 seconds.", + AchievementCategory::Minigame, + false, 0, 0, false + }, + [](Achievement& a, json&) { + checkGoatHerding(a, 30000); + }, + {} + }, + { + { + "goat_20s", + "Bane of Howard", + "Herd all 20 goats into the pen in under 20 seconds.", + AchievementCategory::Minigame, + false, 0, 0, false + }, + [](Achievement& a, json&) { + checkGoatHerding(a, 20000); + }, + {} + }, + { + { + "goat_18s", + "King of the Ranch", + "Herd all 20 goats into the pen in under 18 seconds.", + AchievementCategory::Minigame, + false, 0, 0, false + }, + [](Achievement& a, json&) { + checkGoatHerding(a, 18000); + }, + {} + }, + { + { + "cave_of_ordeals", + "Conqueror of Ordeals", + "Clear all 50 floors of the Cave of Ordeals.", + AchievementCategory::Challenge, + false, 0, 0, false + }, + [](Achievement& a, json&) { + if (daNpcF_chkEvtBit(0x1F9)) { + a.progress = 1; + } + }, + {} + }, + { + { + "cave_of_ordeals_heartless", + "Indomitable", + "Clear all 50 floors of the Cave of Ordeals with only 3 heart containers.", + AchievementCategory::Challenge, + false, 0, 0, false + }, + [](Achievement& a, json&) { + if (daNpcF_chkEvtBit(0x1F9) && dComIfGs_getMaxLife() <= 15) { + a.progress = 1; + } + }, + {} + }, + { + { + "speedrun_12h", + "Been There Done That", + "Defeat Ganondorf with a total save file play time under 12 hours.", + AchievementCategory::Challenge, + false, 0, 0, false + }, + [](Achievement& a, json&) { + const auto* link = static_cast(daPy_getPlayerActorClass()); + if (link == nullptr || link->mProcID != daAlink_c::PROC_GANON_FINISH) { + return; + } + const int64_t ticks = (static_cast(OSGetTime()) - dComIfGs_getSaveStartTime()) + dComIfGs_getSaveTotalTime(); + if (ticks / OS_TIMER_CLOCK < 12 * 3600) { + a.progress = 1; + } + }, + {} + }, + { + { + "speedrun_8h", + "Swift Blade", + "Defeat Ganondorf with a total save file play time under 6 hours.", + AchievementCategory::Challenge, + false, 0, 0, false + }, + [](Achievement& a, json&) { + const auto* link = static_cast(daPy_getPlayerActorClass()); + if (link == nullptr || link->mProcID != daAlink_c::PROC_GANON_FINISH) { + return; + } + const int64_t ticks = (static_cast(OSGetTime()) - dComIfGs_getSaveStartTime()) + dComIfGs_getSaveTotalTime(); + if (ticks / OS_TIMER_CLOCK < 8 * 3600) { + a.progress = 1; + } + }, + {} + }, + { + { + "princess_of_bugs", + "The Princess of Bugs", + "Deliver all 24 golden bugs to Agitha.", + AchievementCategory::Collection, + true, 24, 0, false + }, + [](Achievement& a, json&) { + a.progress = dComIfGs_checkGetInsectNum(); + }, + {} + }, + { + { + "all_poes", + "Poe Collector", + "Collect all 60 Poe Souls.", + AchievementCategory::Collection, + true, 60, 0, false + }, + [](Achievement& a, json&) { + a.progress = dComIfGs_getPohSpiritNum(); + }, + {} + }, + { + { + "hylian_loach", + "Legendary Catch", + "Catch a Hylian Loach.", + AchievementCategory::Collection, + false, 0, 0, false + }, + [](Achievement& a, json&) { + if (dComIfGs_getFishNum(1) > 0) { + a.progress = 1; + } + }, + {} + }, + { + { + "all_fish", + "Gone Fishin'", + "Catch all 6 species of fish.", + AchievementCategory::Collection, + true, 6, 0, false + }, + [](Achievement& a, json&) { + int nUniqueFish = 0; + for (int i = 0; i < 6; ++i) { + if (dComIfGs_getFishNum(i) != 0) { + nUniqueFish++; + } + } + a.progress = nUniqueFish; + }, + {} + }, + { + { + "a_big_heart", + "A Big Heart", + "Reach maximum health with all 20 heart containers.", + AchievementCategory::Collection, + true, 20, 0, false + }, + [](Achievement& a, json&) { + a.progress = dComIfGs_getMaxLife() / 5; + }, + {} + }, + { + { + "back_in_time", + "Back in Time", + "Perform the Back in Time glitch to play on the title screen.", + AchievementCategory::Glitched, + false, 0, 0, false + }, + [](Achievement& a, json&) { + static int titleNoDemoFrames = 0; + if (fopAcM_SearchByName(fpcNm_TITLE_e) == nullptr) { + titleNoDemoFrames = 0; + return; + } + const auto* link = static_cast(daPy_getPlayerActorClass()); + if (link != nullptr && dDemo_c::getMode() == 0) { + if (++titleNoDemoFrames >= 60) { + a.progress = 1; + } + } else { + titleNoDemoFrames = 0; + } + }, + {} + }, + { + { + "early_master_sword", + "Early Master Sword", + "Obtain the Master Sword before completing Midna's Desperate Hour.", + AchievementCategory::Glitched, + false, 0, 0, false + }, + [](Achievement& a, json&) { + if (dComIfGs_isCollectSword(COLLECT_MASTER_SWORD) && !dComIfGs_isEventBit(0x1E08)) { + a.progress = 1; + } + }, + {} + }, + { + { + "earliest_master_sword", + "Earliest Master Sword", + "Obtain the Master Sword before meeting Midna.", + AchievementCategory::Glitched, + false, 0, 0, false + }, + [](Achievement& a, json&) { + if (dComIfGs_isCollectSword(COLLECT_MASTER_SWORD) && !dComIfGs_isTransformLV(0)) { + a.progress = 1; + } + }, + {} + }, + { + { + "ultimate_delivery", + "The Ultimate Delivery", + "Have all 16 postman letters at the same time.", + AchievementCategory::Glitched, + true, 16, 0, false + }, + [](Achievement& a, json&) { + a.progress = dMeter2Info_getRecieveLetterNum(); + }, + {} + }, + { + { + "speedrun_4h", + "Hero of Time", + "Defeat Ganondorf with a total save file play time under 4 hours.", + AchievementCategory::Glitched, + false, 0, 0, false + }, + [](Achievement& a, json&) { + const auto* link = static_cast(daPy_getPlayerActorClass()); + if (link == nullptr || link->mProcID != daAlink_c::PROC_GANON_FINISH) { + return; + } + const int64_t ticks = (static_cast(OSGetTime()) - dComIfGs_getSaveStartTime()) + dComIfGs_getSaveTotalTime(); + if (ticks / OS_TIMER_CLOCK < 4 * 3600) { + a.progress = 1; + } + }, + {} + } + }; +} + +AchievementSystem::AchievementSystem() : m_entries(makeEntries()) {} + +AchievementSystem& AchievementSystem::get() { + static AchievementSystem instance; + return instance; +} + +std::string AchievementSystem::consumePendingUnlock() { + std::string msg = std::move(m_pendingUnlocks.front()); + m_pendingUnlocks.pop(); + return msg; +} + +std::vector AchievementSystem::getAchievements() const { + std::vector result; + result.reserve(m_entries.size()); + for (const auto& e : m_entries) { + result.push_back(e.achievement); + } + return result; +} + +void AchievementSystem::load() { + m_loaded = true; + const auto filePath = dusk::ConfigPath / ACHIEVEMENTS_FILENAME; + if (!std::filesystem::exists(filePath)) { + return; + } + try { + auto data = io::FileStream::ReadAllBytes(filePath.string().c_str()); + auto j = json::parse(data); + if (!j.is_object()) { + return; + } + for (auto& e : m_entries) { + if (!j.contains(e.achievement.key)) { + continue; + } + const auto& entry = j[e.achievement.key]; + if (entry.contains("progress")) { + e.achievement.progress = entry["progress"].get(); + } + if (entry.contains("unlocked")) { + e.achievement.unlocked = entry["unlocked"].get(); + } + if (entry.contains("extra")) { + e.extra = entry["extra"]; + } + } + } catch (const std::exception&) {} +} + +void AchievementSystem::save() { + json j = json::object(); + for (const auto& e : m_entries) { + json entry = { + {"progress", e.achievement.progress}, + {"unlocked", e.achievement.unlocked}, + }; + if (!e.extra.is_null()) { + entry["extra"] = e.extra; + } + j[e.achievement.key] = std::move(entry); + } + try { + io::FileStream::WriteAllText( + (dusk::ConfigPath / ACHIEVEMENTS_FILENAME).string().c_str(), + j.dump(2) + ); + } catch (const std::exception&) {} +} + +void AchievementSystem::clearAll() { + m_entries = makeEntries(); + save(); +} + +void AchievementSystem::processEntry(Entry& e) { + if (e.achievement.unlocked) { + return; + } + const int32_t prevProgress = e.achievement.progress; + e.check(e.achievement, e.extra); + + const bool progressChanged = e.achievement.progress != prevProgress; + const bool nowUnlocked = e.achievement.isCounter ? + e.achievement.progress >= e.achievement.goal : + e.achievement.progress > 0; + + if (nowUnlocked) { + e.achievement.progress = e.achievement.isCounter ? e.achievement.goal : 1; + e.achievement.unlocked = true; + m_pendingUnlocks.push(e.achievement.name); + m_dirty = true; + } else if (progressChanged) { + m_dirty = true; + } +} + +void AchievementSystem::tick() { + if (!m_loaded) { + load(); + } + if (!dusk::IsGameLaunched) { + return; + } + for (auto& e : m_entries) { + processEntry(e); + } + if (m_dirty) { + save(); + m_dirty = false; + } +} + +} // namespace dusk diff --git a/src/dusk/imgui/ImGuiAchievements.cpp b/src/dusk/imgui/ImGuiAchievements.cpp new file mode 100644 index 0000000000..4b844b8386 --- /dev/null +++ b/src/dusk/imgui/ImGuiAchievements.cpp @@ -0,0 +1,228 @@ +#include "ImGuiAchievements.hpp" +#include "ImGuiConfig.hpp" +#include "dusk/achievements.h" +#include "dusk/settings.h" +#include "fmt/format.h" +#include "imgui.h" + +namespace dusk { + +void ImGuiAchievements::notify(std::string name) { + if (m_notifyTimer <= 0.f) { + m_notifyName = std::move(name); + m_notifyTimer = NOTIFY_DURATION; + } else { + m_notifyQueue.push(std::move(name)); + } +} + +void ImGuiAchievements::showNotification() { + if (!getSettings().game.enableAchievementNotifications.getValue()) { + return; + } + if (m_notifyTimer <= 0.f) { + if (m_notifyQueue.empty()) { + return; + } + m_notifyName = std::move(m_notifyQueue.front()); + m_notifyQueue.pop(); + m_notifyTimer = NOTIFY_DURATION; + } + + m_notifyTimer -= ImGui::GetIO().DeltaTime; + + const float alpha = std::min({ + m_notifyTimer / NOTIFY_FADE_TIME, + (NOTIFY_DURATION - m_notifyTimer) / NOTIFY_FADE_TIME, + 1.0f + }); + + const ImGuiViewport* viewport = ImGui::GetMainViewport(); + const float padding = 12.0f; + ImGui::SetNextWindowPos( + ImVec2(viewport->WorkPos.x + viewport->WorkSize.x - padding, viewport->WorkPos.y + padding), + ImGuiCond_Always, ImVec2(1.0f, 0.0f) + ); + + ImGui::SetNextWindowBgAlpha(alpha * 0.92f); + ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.08f, 0.06f, 0.01f, alpha * 0.92f)); + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(1.0f, 0.8f, 0.1f, alpha)); + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f, alpha)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 2.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(14.0f, 10.0f)); + + constexpr ImGuiWindowFlags flags = + ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | + ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | + ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoInputs; + + if (ImGui::Begin("##achievement_notify", nullptr, flags)) { + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.82f, 0.1f, alpha)); + ImGui::TextUnformatted("Achievement Unlocked!"); + ImGui::PopStyleColor(); + ImGui::Spacing(); + ImGui::TextUnformatted(m_notifyName.c_str()); + } + ImGui::End(); + + ImGui::PopStyleVar(2); + ImGui::PopStyleColor(3); +} + +void ImGuiAchievements::draw(bool& open) { + showNotification(); + + if (!open) { + return; + } + + ImGui::SetNextWindowSizeConstraints(ImVec2(640, 200), ImVec2(800, 900)); + ImGui::SetNextWindowSize(ImVec2(640, 480), ImGuiCond_FirstUseEver); + + if (!ImGui::Begin( + "Achievements", &open, + ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav) + ) + { + ImGui::End(); + return; + } + + const auto achievements = AchievementSystem::get().getAchievements(); + + int unlocked = 0; + for (const auto& a : achievements) { + if (a.unlocked) { + ++unlocked; + } + } + + ImGui::Text("%d / %d achievements unlocked", unlocked, (int)achievements.size()); + ImGui::SameLine(); + config::ImGuiCheckbox("Notifications", getSettings().game.enableAchievementNotifications); + ImGui::Separator(); + + static const struct { + AchievementCategory cat; + const char* label; + ImVec4 color; + } ACHIEVEMENT_CATEGORIES[] = { + {AchievementCategory::Story, "Story", ImVec4(1.0f, 0.82f, 0.1f, 1.0f)}, + {AchievementCategory::Collection, "Collection", ImVec4(0.3f, 0.85f, 0.4f, 1.0f)}, + {AchievementCategory::Challenge, "Challenge", ImVec4(1.0f, 0.65f, 0.15f, 1.0f)}, + {AchievementCategory::Minigame, "Minigame", ImVec4(0.5f, 0.85f, 1.0f, 1.0f)}, + {AchievementCategory::Glitched, "Glitched", ImVec4(0.75f, 0.4f, 1.0f, 1.0f)}, + }; + + const float footerHeight = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing(); + + if (ImGui::BeginTabBar("##achievement_tabs", ImGuiTabBarFlags_FittingPolicyScroll)) { + for (const auto& catInfo : ACHIEVEMENT_CATEGORIES) { + int catTotal = 0, catUnlocked = 0; + for (const auto& a : achievements) { + if (a.category == catInfo.cat) { + ++catTotal; + if (a.unlocked) { + ++catUnlocked; + } + } + } + if (catTotal == 0) { + continue; + } + + const std::string tabLabel = fmt::format("{} ({}/{})", catInfo.label, catUnlocked, catTotal); + + ImGui::PushStyleColor(ImGuiCol_Text, catInfo.color); + const bool tabOpen = ImGui::BeginTabItem(tabLabel.c_str()); + ImGui::PopStyleColor(); + + if (tabOpen) { + ImGui::BeginChild( + "##cat_list", + ImVec2(0, -footerHeight), + ImGuiChildFlags_None, + ImGuiWindowFlags_NoBackground + ); + + ImGui::Spacing(); + + for (const auto& a : achievements) { + if (a.category != catInfo.cat) { + continue; + } + ImGui::PushID(a.key); + + ImGui::PushStyleColor( + ImGuiCol_Text, + a.unlocked ? + ImVec4(1.0f, 0.65f, 0.15f, 1.0f) : + ImGui::GetStyleColorVec4(ImGuiCol_Text) + ); + + ImGui::TextUnformatted(a.name); + ImGui::PopStyleColor(); + + const char* statusLabel = a.unlocked ? "[Unlocked]" : "[Locked]"; + ImGui::SameLine( + ImGui::GetContentRegionMax().x - + ImGui::CalcTextSize(statusLabel).x + ); + + if (a.unlocked) { + ImGui::TextColored(ImVec4(0.2f, 0.8f, 0.2f, 1.0f), "%s", statusLabel); + } else { + ImGui::TextColored(ImVec4(0.8f, 0.2f, 0.2f, 1.0f), "%s", statusLabel); + } + + ImGui::TextDisabled("%s", a.description); + + if (a.isCounter) { + const float fraction = a.goal > 0 ? (float)(a.progress) / (float)(a.goal) : 1.0f; + const std::string overlay = fmt::format("{} / {}", a.progress, a.goal); + ImGui::PushStyleColor( + ImGuiCol_PlotHistogram, + a.unlocked ? + ImVec4(0.4f, 0.7f, 0.1f, 1.0f) : + ImVec4(0.2f, 0.45f, 0.8f, 1.0f) + ); + ImGui::ProgressBar(fraction, ImVec2(-1.0f, 0.0f), overlay.c_str()); + ImGui::PopStyleColor(); + } + + ImGui::Spacing(); + ImGui::PopID(); + } + + ImGui::EndChild(); + ImGui::EndTabItem(); + } + } + ImGui::EndTabBar(); + } + + ImGui::Separator(); + ImGui::Spacing(); + + if (ImGui::Button("Clear All Achievements")) { + ImGui::OpenPopup("##confirm_clear"); + } + + if (ImGui::BeginPopup("##confirm_clear")) { + ImGui::Text("Reset all achievement progress?"); + ImGui::Spacing(); + if (ImGui::Button("Yes, reset all")) { + AchievementSystem::get().clearAll(); + ImGui::CloseCurrentPopup(); + } + ImGui::SameLine(); + if (ImGui::Button("Cancel")) { + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } + + ImGui::End(); +} + +} // namespace dusk diff --git a/src/dusk/imgui/ImGuiAchievements.hpp b/src/dusk/imgui/ImGuiAchievements.hpp new file mode 100644 index 0000000000..5ee77373fc --- /dev/null +++ b/src/dusk/imgui/ImGuiAchievements.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include +#include + +namespace dusk { + +class ImGuiAchievements { +public: + void draw(bool& open); + void notify(std::string name); + +private: + void showNotification(); + + std::string m_notifyName; + float m_notifyTimer = 0.f; + std::queue m_notifyQueue; + static constexpr float NOTIFY_DURATION = 4.0f; + static constexpr float NOTIFY_FADE_TIME = 0.5f; +}; + +} // namespace dusk diff --git a/src/dusk/imgui/ImGuiConsole.cpp b/src/dusk/imgui/ImGuiConsole.cpp index 085b34dc2d..c8a9fa86aa 100644 --- a/src/dusk/imgui/ImGuiConsole.cpp +++ b/src/dusk/imgui/ImGuiConsole.cpp @@ -14,6 +14,7 @@ #include "SDL3/SDL_events.h" #include "SDL3/SDL_mouse.h" #include "aurora/lib/window.hpp" +#include "dusk/achievements.h" #include "dusk/audio/DuskAudioSystem.h" #include "dusk/config.hpp" #include "dusk/dusk.h" @@ -295,6 +296,15 @@ namespace dusk { UpdateSettings(); + AchievementSystem::get().tick(); + while (AchievementSystem::get().hasPendingUnlock()) { + if (getSettings().game.enableAchievementNotifications) { + m_menuTools.notifyAchievement(AchievementSystem::get().consumePendingUnlock()); + } else { + AchievementSystem::get().consumePendingUnlock(); + } + } + if ((ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl)) && ImGui::IsKeyPressed(ImGuiKey_R)) { @@ -374,6 +384,7 @@ namespace dusk { m_menuTools.ShowSaveEditor(); } m_menuTools.ShowStateShare(); + m_menuTools.ShowAchievements(); DuskDebugPad(); // temporary, remove later // Hide mouse cursor if the F1 menu is not open and the cursor is idle for 3 seconds. diff --git a/src/dusk/imgui/ImGuiFirstRunPreset.cpp b/src/dusk/imgui/ImGuiFirstRunPreset.cpp index fb2d181562..8009305870 100644 --- a/src/dusk/imgui/ImGuiFirstRunPreset.cpp +++ b/src/dusk/imgui/ImGuiFirstRunPreset.cpp @@ -35,6 +35,7 @@ static void ApplyPresetDusk() { ApplyPresetHD(); auto& s = getSettings(); + s.game.enableAchievementNotifications.setValue(true); s.game.enableQuickTransform.setValue(true); s.game.instantSaves.setValue(true); s.game.midnasLamentNonStop.setValue(true); diff --git a/src/dusk/imgui/ImGuiMenuGame.cpp b/src/dusk/imgui/ImGuiMenuGame.cpp index 7e54f2ffa3..8cb50e143d 100644 --- a/src/dusk/imgui/ImGuiMenuGame.cpp +++ b/src/dusk/imgui/ImGuiMenuGame.cpp @@ -444,6 +444,7 @@ namespace dusk { void ImGuiMenuGame::drawInterfaceMenu() { if (ImGui::BeginMenu("Interface")) { + config::ImGuiCheckbox("Achievement Notifications", getSettings().game.enableAchievementNotifications); config::ImGuiCheckbox("Skip Pre-Launch UI", getSettings().backend.skipPreLaunchUI); config::ImGuiCheckbox("Show Pipeline Compilation", getSettings().backend.showPipelineCompilation); #if DUSK_ENABLE_SENTRY_NATIVE diff --git a/src/dusk/imgui/ImGuiMenuTools.cpp b/src/dusk/imgui/ImGuiMenuTools.cpp index 00a03e635b..a82067a438 100644 --- a/src/dusk/imgui/ImGuiMenuTools.cpp +++ b/src/dusk/imgui/ImGuiMenuTools.cpp @@ -58,6 +58,8 @@ namespace dusk { ImGui::EndDisabled(); } + ImGui::MenuItem("Achievements", nullptr, &m_showAchievements); + #if DUSK_CAN_OPEN_DATA_FOLDER ImGui::Separator(); if (ImGui::MenuItem("Open Data Folder")) { @@ -252,4 +254,12 @@ namespace dusk { ImGui::End(); ImGui::PopFont(); } + + void ImGuiMenuTools::ShowAchievements() { + m_achievementsWindow.draw(m_showAchievements); + } + + void ImGuiMenuTools::notifyAchievement(std::string name) { + m_achievementsWindow.notify(std::move(name)); + } } diff --git a/src/dusk/imgui/ImGuiMenuTools.hpp b/src/dusk/imgui/ImGuiMenuTools.hpp index e48aab2742..de94ad2d8f 100644 --- a/src/dusk/imgui/ImGuiMenuTools.hpp +++ b/src/dusk/imgui/ImGuiMenuTools.hpp @@ -5,6 +5,7 @@ #include #include "imgui.h" +#include "ImGuiAchievements.hpp" #include "ImGuiSaveEditor.hpp" #include "ImGuiStateShare.hpp" @@ -26,6 +27,8 @@ namespace dusk { void ShowAudioDebug(); void ShowSaveEditor(); void ShowStateShare(); + void ShowAchievements(); + void notifyAchievement(std::string name); private: bool m_showDebugOverlay = false; @@ -65,6 +68,9 @@ namespace dusk { bool m_showStateShare = false; ImGuiStateShare m_stateShare; + + bool m_showAchievements = false; + ImGuiAchievements m_achievementsWindow; }; } diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index 3e6ee733fe..b6dc559610 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -44,6 +44,7 @@ UserSettings g_userSettings = { .disableMainHUD {"game.disableMainHUD", false}, .pauseOnFocusLost {"game.pauseOnFocusLost", false}, .enableLinkDollRotation = {"game.enableLinkDollRotation", false }, + .enableAchievementNotifications {"game.enableAchievementNotifications", false}, // Graphics .bloomMode {"game.bloomMode", BloomMode::Classic}, @@ -153,6 +154,7 @@ void registerSettings() { Register(g_userSettings.game.freeMagicArmor); Register(g_userSettings.game.restoreWiiGlitches); Register(g_userSettings.game.enableLinkDollRotation); + Register(g_userSettings.game.enableAchievementNotifications); Register(g_userSettings.game.noMissClimbing); Register(g_userSettings.game.noLowHpSound); Register(g_userSettings.game.midnasLamentNonStop); From 9ddfcc38c9f875f9ddf5fc2d6c8d0a3cd0981864 Mon Sep 17 00:00:00 2001 From: Howard Luck Date: Sat, 25 Apr 2026 20:37:36 -0600 Subject: [PATCH 21/27] fix interp on ooks rope (#553) --- include/d/actor/d_a_e_mb.h | 6 ++++++ src/d/actor/d_a_e_mb.cpp | 29 +++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/include/d/actor/d_a_e_mb.h b/include/d/actor/d_a_e_mb.h index f36c744879..527cf30eac 100644 --- a/include/d/actor/d_a_e_mb.h +++ b/include/d/actor/d_a_e_mb.h @@ -44,6 +44,12 @@ public: /* 0x88C */ u8 field_0x88C[0x8C8 - 0x88C]; /* 0x8C8 */ s8 field_0x8c8; /* 0x8C9 */ u8 mInitHIO; +#if TARGET_PC + cXyz mRopeInterpPrev[16]; + cXyz mRopeInterpCurr[16]; + bool mRopeInterpPrevValid; + bool mRopeInterpCurrValid; +#endif }; STATIC_ASSERT(sizeof(e_mb_class) == 0x8cc); diff --git a/src/d/actor/d_a_e_mb.cpp b/src/d/actor/d_a_e_mb.cpp index 939b253475..aa77a99612 100644 --- a/src/d/actor/d_a_e_mb.cpp +++ b/src/d/actor/d_a_e_mb.cpp @@ -12,6 +12,8 @@ #include "d/d_bomb.h" #include "c/c_damagereaction.h" #include "Z2AudioLib/Z2Instances.h" +#include "dusk/frame_interpolation.h" +#include "dusk/settings.h" #define ACTION_STANDBY 0 #define ACTION_WALK1 1 @@ -63,6 +65,22 @@ static void anm_init(e_mb_class* i_this, int i_anmID, f32 i_morf, u8 i_attr, f32 i_this->mAnm = i_anmID; } +#if TARGET_PC +static void e_mb_rope_interp_callback(bool isSimFrame, void* pUserWork) { + e_mb_class* i_this = (e_mb_class*)pUserWork; + if (!i_this->mRopeInterpPrevValid || !i_this->mRopeInterpCurrValid) { + return; + } + const f32 alpha = dusk::frame_interp::get_interpolation_step(); + cXyz* dst = i_this->mRopeMat.getPos(0); + for (int i = 0; i < 16; i++) { + const cXyz& p0 = i_this->mRopeInterpPrev[i]; + const cXyz& p1 = i_this->mRopeInterpCurr[i]; + dst[i] = p0 + (p1 - p0) * alpha; + } +} +#endif + static int daE_MB_Draw(e_mb_class* i_this) { fopAc_ac_c* a_this = (fopAc_ac_c*)i_this; @@ -86,6 +104,17 @@ static int daE_MB_Draw(e_mb_class* i_this) { static GXColor l_color = {0x14, 0x0F, 0x00, 0xFF}; i_this->mRopeMat.update(16, l_color, &a_this->tevStr); dComIfGd_set3DlineMat(&i_this->mRopeMat); +#if TARGET_PC + if (dusk::getSettings().game.enableFrameInterpolation) { + if (i_this->mRopeInterpCurrValid) { + memcpy(i_this->mRopeInterpPrev, i_this->mRopeInterpCurr, sizeof(i_this->mRopeInterpCurr)); + i_this->mRopeInterpPrevValid = true; + } + memcpy(i_this->mRopeInterpCurr, i_this->mRopeMat.getPos(0), 16 * sizeof(cXyz)); + i_this->mRopeInterpCurrValid = true; + dusk::frame_interp::add_interpolation_callback(&e_mb_rope_interp_callback, i_this); + } +#endif return 1; } From f7b5350d60eb96b92446e2d20a0a331e678c2ac0 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Sat, 25 Apr 2026 20:42:02 -0600 Subject: [PATCH 22/27] Update README.md --- README.md | 25 +++---------------------- 1 file changed, 3 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index c2fe53576d..b6d6cec863 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ - ### **[Discord](https://discord.gg/QACynxeyna)** # Setup -**⚠️Dusk does NOT provide any copyrighted assets. You must provide your own copy of the game.** +**⚠️ Dusk does NOT provide any copyrighted assets. You must provide your own copy of the game.** ### 1. Verify your ROM dump First make sure your dump of the game is clean and supported by Dusk. You can do this by checking the sha1 hash of your dump against this list of supported versions. @@ -22,29 +22,10 @@ First make sure your dump of the game is clean and supported by Dusk. You can do - Select Options, then set the ISO Path to your supported game dump - Press Start Game to play! -![DuskOptions](assets/dusk_options.png) +![Dusk options](assets/dusk_options.png) # Building If you'd like to build Dusk from source, please read the [build instructions](docs/building.md). # Credits -- Taka -- encounter -- Antidote -- caseif -- CraftyBoss -- crowell -- dooplecks -- gymnast86 -- Irastris -- kipcode66 -- Lars -- LunarSoap -- Maddie -- MelonSpeedruns -- Pheenoh -- PJB -- Roeming -- YunataSavior - -Special thanks to the TP Decomp team, the GC/Wii Decomp community, the Aurora developers, and the TP speedrunning community. +Special thanks to the [TP decompilation](https://github.com/zeldaret/tp) team, the GC/Wii decompilation community, the [Aurora](https://github.com/encounter/aurora) developers, the [TP speedrunning community](https://zsrtp.link), and all [contributors](https://github.com/TwilitRealm/dusk/graphs/contributors). From ba58d2486e5dbae1c98ee363cbac0ea1f1511923 Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Sat, 25 Apr 2026 23:35:43 -0400 Subject: [PATCH 23/27] Free Camera (#542) * freecam wip * added manual mode * fix freecam collision * made freecam into its own function * Added settings & Radius modification when camer is higher up --------- Co-authored-by: MelonSpeedruns --- include/d/d_cam_param.h | 6 +++ include/d/d_camera.h | 5 ++ include/dusk/settings.h | 5 +- src/d/d_camera.cpp | 66 ++++++++++++++++++++++++++ src/dusk/imgui/ImGuiFirstRunPreset.cpp | 1 + src/dusk/imgui/ImGuiMenuGame.cpp | 10 +++- src/dusk/settings.cpp | 8 +++- 7 files changed, 98 insertions(+), 3 deletions(-) diff --git a/include/d/d_cam_param.h b/include/d/d_cam_param.h index d6ff7b46d7..867c66e38d 100644 --- a/include/d/d_cam_param.h +++ b/include/d/d_cam_param.h @@ -143,6 +143,12 @@ public: /* 0x20 */ JORFile mFile; #endif +#if TARGET_PC + /* 0x24 */ u8 mManualMode; + /* 0x25 */ f32 freeXAngle; + /* 0x29 */ f32 freeYAngle; +#endif + u32 Id(s32 i_style) { return mCamStyleData[i_style].field_0x0; } int Algorythmn(s32 i_style) { return mCamStyleData[i_style].field_0x4; } int Algorythmn() { return mCurrentStyle->field_0x4; } diff --git a/include/d/d_camera.h b/include/d/d_camera.h index 85e930c395..105c9cae9f 100644 --- a/include/d/d_camera.h +++ b/include/d/d_camera.h @@ -273,6 +273,8 @@ public: /* 0xA4 */ f32 field_0xa4; /* 0xA8 */ int field_0xa8; /* 0xAC */ f32 field_0xac; + f32 xAngle; + f32 yAngle; }; struct LockOnData { @@ -1024,6 +1026,9 @@ public: bool colosseumCamera(s32); bool test1Camera(s32); bool test2Camera(s32); + #if TARGET_PC + bool freeCamera(); + #endif bool towerCamera(s32); bool hookshotCamera(s32); bool railCamera(s32); diff --git a/include/dusk/settings.h b/include/dusk/settings.h index 8a54197317..a7815569d0 100644 --- a/include/dusk/settings.h +++ b/include/dusk/settings.h @@ -82,7 +82,6 @@ struct UserSettings { // Preferences ConfigVar enableMirrorMode; - ConfigVar invertCameraXAxis; ConfigVar disableMainHUD; ConfigVar pauseOnFocusLost; ConfigVar enableLinkDollRotation; @@ -112,6 +111,10 @@ struct UserSettings { ConfigVar gyroDeadband; ConfigVar gyroInvertPitch; ConfigVar gyroInvertYaw; + ConfigVar freeCamera; + ConfigVar invertCameraXAxis; + ConfigVar invertCameraYAxis; + ConfigVar freeCameraSensitivity; // Cheats ConfigVar infiniteHearts; diff --git a/src/d/d_camera.cpp b/src/d/d_camera.cpp index f805a6a08b..917b30709a 100644 --- a/src/d/d_camera.cpp +++ b/src/d/d_camera.cpp @@ -800,6 +800,10 @@ void dCamera_c::updatePad() { } mLockLActive = 1; + + #if TARGET_PC + mCamParam.mManualMode = 0; + #endif } else { mLockLJustActivated = 0; mLockLActive = 0; @@ -833,6 +837,12 @@ void dCamera_c::updatePad() { mHoldB = mDoCPd_c::getHoldB(mPadID) ? true : false; mTrigB = mDoCPd_c::getTrigB(mPadID) ? true : false; + #if TARGET_PC + if (mCamParam.mManualMode) { + return; + } + #endif + bool sp6B = true; bool sp6C = true; int temp1; @@ -1167,6 +1177,13 @@ bool dCamera_c::Run() { } } else { sp0F = (this->*engine_tbl[mCamParam.Algorythmn(mCamStyle)])(mCamStyle); + + #if TARGET_PC + if (mCamParam.Algorythmn(mCamStyle) != 1) { + mCamParam.mManualMode = 0; + } + #endif + field_0x170++; field_0x160++; mCurCamStyleTimer++; @@ -3078,6 +3095,11 @@ bool dCamera_c::bumpCheck(u32 i_flags) { } else { field_0x968 *= mMonitor.field_0xc / 5.0f; } + + #if TARGET_PC + if (!dusk::getSettings().game.freeCamera || !mCamParam.mManualMode) { + #endif + f32 tmp = field_0x96c * (mIsWolf == 1 ? 30.0f : 30.0f); center += vec3.norm() * (tmp * globe.V().Sin()); cSGlobe globe2(vec2 - center); @@ -3091,6 +3113,10 @@ bool dCamera_c::bumpCheck(u32 i_flags) { vec = lin_chk1.GetCross(); } + #if TARGET_PC + } + #endif + #if DEBUG if (mCamSetup.CheckFlag(0x8000)) { dDbVw_Report(20, 235, " U"); @@ -4604,6 +4630,11 @@ bool dCamera_c::chaseCamera(s32 param_0) { sp110 = mViewCache.mDirection.R(); mViewCache.mDirection.R(mViewCache.mDirection.R() + (fVar55 - mViewCache.mDirection.R()) * chase->field_0x74); + + #if TARGET_PC + freeCamera(); + #endif + chase->field_0x64 = mViewCache.mCenter + mViewCache.mDirection.Xyz(); mViewCache.mEye = chase->field_0x64; @@ -7444,6 +7475,41 @@ bool dCamera_c::test2Camera(s32 param_0) { return false; } +#if TARGET_PC +bool dCamera_c::freeCamera() { + if (!dusk::getSettings().game.freeCamera) { + mCamParam.mManualMode = 0; + return false; + } + + cXyz camMovement = {mPadInfo.mCStick.mLastPosX, mPadInfo.mCStick.mLastPosY, 0.0f}; + f32 magnitude = sqrt(mPadInfo.mCStick.mLastPosX * mPadInfo.mCStick.mLastPosX + mPadInfo.mCStick.mLastPosY * mPadInfo.mCStick.mLastPosY); + + if (mPadInfo.mCStick.mLastPosX != 0 || mPadInfo.mCStick.mLastPosY != 0) { + if (!mCamParam.mManualMode) { + mCamParam.mManualMode = 1; + mCamParam.freeXAngle = mViewCache.mDirection.mAzimuth.Degree(); + mCamParam.freeYAngle = mViewCache.mDirection.mInclination.Degree(); + } + + camMovement = camMovement.normalize(); + camMovement.x *= (dusk::getSettings().game.invertCameraXAxis ? 1.0f : -1.0f) * dusk::getSettings().game.freeCameraSensitivity * 4.0f; + camMovement.y *= (dusk::getSettings().game.invertCameraYAxis ? 1.0f : -1.0f) * dusk::getSettings().game.freeCameraSensitivity * 4.0f; + mCamParam.freeXAngle += camMovement.x * magnitude * dusk::getSettings().game.freeCameraSensitivity; + mCamParam.freeYAngle += camMovement.y * magnitude * dusk::getSettings().game.freeCameraSensitivity; + } + + if (mCamParam.mManualMode) { + mCamParam.freeYAngle = std::clamp(mCamParam.freeYAngle, -35.0f, 60.0f); + mViewCache.mDirection.mAzimuth = cSAngle(mCamParam.freeXAngle); + mViewCache.mDirection.mInclination = cSAngle(mCamParam.freeYAngle); + mViewCache.mDirection.mRadius = std::clamp(mCamParam.freeYAngle * 15.0f, 300.0f, 10000.0f); + } + + return mCamParam.mManualMode; +} +#endif + bool dCamera_c::towerCamera(s32 param_0) { cSAngle stack_444 = cSAngle(mCamSetup.ChargeLatitude()); f32 sp224 = mCamSetup.ChargeBRatio(); diff --git a/src/dusk/imgui/ImGuiFirstRunPreset.cpp b/src/dusk/imgui/ImGuiFirstRunPreset.cpp index 8009305870..2a9bcc5c6e 100644 --- a/src/dusk/imgui/ImGuiFirstRunPreset.cpp +++ b/src/dusk/imgui/ImGuiFirstRunPreset.cpp @@ -29,6 +29,7 @@ static void ApplyPresetHD() { s.game.fastTears.setValue(true); s.game.biggerWallets.setValue(true); s.game.invertCameraXAxis.setValue(true); + s.game.freeCamera.setValue(true); } static void ApplyPresetDusk() { diff --git a/src/dusk/imgui/ImGuiMenuGame.cpp b/src/dusk/imgui/ImGuiMenuGame.cpp index 8cb50e143d..0a687233aa 100644 --- a/src/dusk/imgui/ImGuiMenuGame.cpp +++ b/src/dusk/imgui/ImGuiMenuGame.cpp @@ -383,7 +383,15 @@ namespace dusk { ImGui::SeparatorText("Camera"); - config::ImGuiCheckbox("Invert Camera X Axis", getSettings().game.invertCameraXAxis); + config::ImGuiCheckbox("Free Camera", getSettings().game.freeCamera); + + if (getSettings().game.freeCamera) { + config::ImGuiCheckbox("Invert Camera X Axis", getSettings().game.invertCameraXAxis); + config::ImGuiCheckbox("Invert Camera Y Axis", getSettings().game.invertCameraYAxis); + config::ImGuiSliderFloat("Free Camera Sensitivity", getSettings().game.freeCameraSensitivity, 0.5f, 2.0f, "%.1f"); + } else { + config::ImGuiCheckbox("Invert Camera X Axis", getSettings().game.invertCameraXAxis); + } ImGui::SeparatorText("Gyro"); diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index e10e6d9ee9..eb1d513ba9 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -42,7 +42,6 @@ UserSettings g_userSettings = { // Preferences .enableMirrorMode {"game.enableMirrorMode", false}, - .invertCameraXAxis {"game.invertCameraXAxis", false}, .disableMainHUD {"game.disableMainHUD", false}, .pauseOnFocusLost {"game.pauseOnFocusLost", false}, .enableLinkDollRotation = {"game.enableLinkDollRotation", false }, @@ -71,6 +70,10 @@ UserSettings g_userSettings = { .gyroDeadband {"game.gyroDeadband", 0.04f}, .gyroInvertPitch {"game.gyroInvertPitch", false}, .gyroInvertYaw {"game.gyroInvertYaw", false}, + .freeCamera {"game.freeCamera", false}, + .invertCameraXAxis {"game.invertCameraXAxis", false}, + .invertCameraYAxis {"game.invertCameraYAxis", false}, + .freeCameraSensitivity {"game.freeCameraSensitivity", 1.0f}, // Cheats .infiniteHearts {"game.infiniteHearts", false}, @@ -144,6 +147,8 @@ void registerSettings() { Register(g_userSettings.game.sunsSong); Register(g_userSettings.game.enableMirrorMode); Register(g_userSettings.game.invertCameraXAxis); + Register(g_userSettings.game.invertCameraYAxis); + Register(g_userSettings.game.freeCameraSensitivity); Register(g_userSettings.game.disableMainHUD); Register(g_userSettings.game.pauseOnFocusLost); Register(g_userSettings.game.bloomMode); @@ -183,6 +188,7 @@ void registerSettings() { Register(g_userSettings.game.gyroSmoothing); Register(g_userSettings.game.gyroInvertPitch); Register(g_userSettings.game.gyroInvertYaw); + Register(g_userSettings.game.freeCamera); Register(g_userSettings.backend.isoPath); Register(g_userSettings.backend.graphicsBackend); From 34ec362876dc99795c6fdd22b93547ea1365398d Mon Sep 17 00:00:00 2001 From: Pheenoh Date: Sat, 25 Apr 2026 21:37:11 -0600 Subject: [PATCH 24/27] fix zant fog spin and culling --- src/d/d_kankyo_rain.cpp | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/src/d/d_kankyo_rain.cpp b/src/d/d_kankyo_rain.cpp index 64cf2247e0..822612768c 100644 --- a/src/d/d_kankyo_rain.cpp +++ b/src/d/d_kankyo_rain.cpp @@ -5986,7 +5986,12 @@ static void dKyr_evil_draw2(Mtx drawMtx, u8** tex) { TGXTexObj texobj; dKyr_set_btitex(&texobj, (ResTIMG*)tex[1]); - rot += 0.7f; +#if TARGET_PC + if (dusk::frame_interp::get_ui_tick_pending()) +#endif + { + rot += 0.7f; + } MTXRotRad(rotMtx, 'Z', DEG_TO_RAD(rot)); MTXConcat(camMtx, rotMtx, camMtx); @@ -6037,7 +6042,14 @@ static void dKyr_evil_draw2(Mtx drawMtx, u8** tex) { sp34.y = 80.0f; sp34.z = 80.0f; mDoLib_project(&sp7C, &proj); - if (!(proj.x > -sp34.x) || !(proj.x < (FB_WIDTH + sp34.x)) || +#if TARGET_PC + f32 cullMinX = mDoGph_gInf_c::getSafeMinXF() - sp34.x; + f32 cullMaxX = mDoGph_gInf_c::getSafeMinXF() + mDoGph_gInf_c::getSafeWidthF() + sp34.x; +#else + f32 cullMinX = -sp34.x; + f32 cullMaxX = FB_WIDTH + sp34.x; +#endif + if (!(proj.x > cullMinX) || !(proj.x < cullMaxX) || !(proj.y > -sp34.y) || !(proj.y < (458.0f + sp34.z))) { continue; @@ -6208,7 +6220,12 @@ void dKyr_evil_draw(Mtx drawMtx, u8** tex) { TGXTexObj texobj; dKyr_set_btitex(&texobj, (ResTIMG*)tex[0]); - rot += 1.0f; +#if TARGET_PC + if (dusk::frame_interp::get_ui_tick_pending()) +#endif + { + rot += 1.0f; + } MTXRotRad(rotMtx, 'Z', DEG_TO_RAD(rot)); MTXConcat(camMtx, rotMtx, camMtx); @@ -6262,7 +6279,14 @@ void dKyr_evil_draw(Mtx drawMtx, u8** tex) { sp44.z = 80.0f; mDoLib_project(&spA4, &proj); - if (!(proj.x > -sp44.x) || !(proj.x < (FB_WIDTH + sp44.x)) || +#if TARGET_PC + f32 cullMinX = mDoGph_gInf_c::getSafeMinXF() - sp44.x; + f32 cullMaxX = mDoGph_gInf_c::getSafeMinXF() + mDoGph_gInf_c::getSafeWidthF() + sp44.x; +#else + f32 cullMinX = -sp44.x; + f32 cullMaxX = FB_WIDTH + sp44.x; +#endif + if (!(proj.x > cullMinX) || !(proj.x < cullMaxX) || !(proj.y > -sp44.y) || !(proj.y < (458.0f + sp44.z))) { continue; From 02924549be425dd9818aa1df3f8148669d97a934 Mon Sep 17 00:00:00 2001 From: Pheenoh Date: Sat, 25 Apr 2026 21:55:40 -0600 Subject: [PATCH 25/27] fix idle howl blinking --- src/d/d_msg_scrn_howl.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/d/d_msg_scrn_howl.cpp b/src/d/d_msg_scrn_howl.cpp index 1d39cba1a1..c701ea0b2b 100644 --- a/src/d/d_msg_scrn_howl.cpp +++ b/src/d/d_msg_scrn_howl.cpp @@ -20,6 +20,7 @@ #include "Z2AudioLib/Z2WolfHowlMgr.h" #if TARGET_PC +#include "dusk/frame_interpolation.h" #include "dusk/settings.h" #endif @@ -580,9 +581,14 @@ void dMsgScrnHowl_c::drawWave() { f17 = local_60; f18 = local_64; } else { - field_0x2134++; - if (field_0x2134 > 30) { - field_0x2134 = 0; +#if TARGET_PC + if (dusk::frame_interp::get_ui_tick_pending()) +#endif + { + field_0x2134++; + if (field_0x2134 > 30) { + field_0x2134 = 0; + } } if (field_0x2134 < 15) { local_dc = field_0x2134 / 15.0f; From c1231885fe515f13791ae32f33e48471f9991a31 Mon Sep 17 00:00:00 2001 From: Phillip Stephens Date: Sat, 25 Apr 2026 22:17:17 -0700 Subject: [PATCH 26/27] Add rumble intensity sliders to input configurator (#558) --- src/dusk/imgui/ImGuiMenuGame.cpp | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/dusk/imgui/ImGuiMenuGame.cpp b/src/dusk/imgui/ImGuiMenuGame.cpp index 0a687233aa..42dfc8af65 100644 --- a/src/dusk/imgui/ImGuiMenuGame.cpp +++ b/src/dusk/imgui/ImGuiMenuGame.cpp @@ -965,7 +965,7 @@ namespace dusk { ImGui::SameLine(); // Options panel - ImGuiBeginGroupPanel("Options", ImVec2(150 * scale, 20 * scale)); + ImGuiBeginGroupPanel("Options", ImVec2(150 * scale, -1)); if (deadZones != nullptr) { if (ImGui::Checkbox("Enable Dead Zones", &deadZones->useDeadzones)) { @@ -975,7 +975,24 @@ namespace dusk { PADSerializeMappings(); } } - + + if (PADSupportsRumbleIntensity(m_controllerConfig.m_selectedPort)) { + ImGuiBeginGroupPanel("Rumble Intensity", ImVec2(150 * scale, -1)); + u16 low; + u16 high; + (void)PADGetRumbleIntensity(m_controllerConfig.m_selectedPort, &low, &high); + float fLow = static_cast(low / 32767) * 100.f; + bool changed = ImGui::SliderFloat("Low", &fLow, 0.f, 100.f, "%.0f%%"); + float fHigh = static_cast(high / 32767) * 100.f; + changed |= ImGui::SliderFloat("High", &fHigh, 0.f, 100.f, "%.0f%%"); + if (changed) { + PADSetRumbleIntensity(m_controllerConfig.m_selectedPort, + static_cast((fLow / 100.f) * 32767), + static_cast((fHigh / 100.f) * 32767)); + PADSerializeMappings(); + } + ImGuiEndGroupPanel(); + } ImGuiEndGroupPanel(); ImGui::End(); From 8784958c40d8205df876d2ef5dd2b3b837d328f5 Mon Sep 17 00:00:00 2001 From: Phillip Stephens Date: Sat, 25 Apr 2026 22:51:39 -0700 Subject: [PATCH 27/27] Quick PAD rumble fixes, add button to test rumble configuration --- extern/aurora | 2 +- src/dusk/imgui/ImGuiMenuGame.cpp | 12 ++++++++---- src/dusk/imgui/ImGuiMenuGame.hpp | 1 + 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/extern/aurora b/extern/aurora index 135e976867..8af9057689 160000 --- a/extern/aurora +++ b/extern/aurora @@ -1 +1 @@ -Subproject commit 135e976867490380356878c8bb8d1956932489c5 +Subproject commit 8af9057689b9b85a55bbbd6218bc95304558fabd diff --git a/src/dusk/imgui/ImGuiMenuGame.cpp b/src/dusk/imgui/ImGuiMenuGame.cpp index 42dfc8af65..dcfcc55657 100644 --- a/src/dusk/imgui/ImGuiMenuGame.cpp +++ b/src/dusk/imgui/ImGuiMenuGame.cpp @@ -981,16 +981,20 @@ namespace dusk { u16 low; u16 high; (void)PADGetRumbleIntensity(m_controllerConfig.m_selectedPort, &low, &high); - float fLow = static_cast(low / 32767) * 100.f; + float fLow = (static_cast(low) / 32767.f) * 100.f; bool changed = ImGui::SliderFloat("Low", &fLow, 0.f, 100.f, "%.0f%%"); - float fHigh = static_cast(high / 32767) * 100.f; + float fHigh = (static_cast(high) / 32767.f) * 100.f; changed |= ImGui::SliderFloat("High", &fHigh, 0.f, 100.f, "%.0f%%"); if (changed) { PADSetRumbleIntensity(m_controllerConfig.m_selectedPort, - static_cast((fLow / 100.f) * 32767), - static_cast((fHigh / 100.f) * 32767)); + static_cast((fLow / 100) * 32767), + static_cast((fHigh / 100) * 32767)); PADSerializeMappings(); } + if (ImGui::Button(fmt::format("{0}...##rumbleTest", m_controllerConfig.m_isRumbling ? "Stop": "Test").c_str(), {-1, 0})) { + PADControlMotor(m_controllerConfig.m_selectedPort, !m_controllerConfig.m_isRumbling ? PAD_MOTOR_RUMBLE : PAD_MOTOR_STOP_HARD); + m_controllerConfig.m_isRumbling ^= 1; + } ImGuiEndGroupPanel(); } ImGuiEndGroupPanel(); diff --git a/src/dusk/imgui/ImGuiMenuGame.hpp b/src/dusk/imgui/ImGuiMenuGame.hpp index e21374c8f4..fb3f3f10ca 100644 --- a/src/dusk/imgui/ImGuiMenuGame.hpp +++ b/src/dusk/imgui/ImGuiMenuGame.hpp @@ -32,6 +32,7 @@ namespace dusk { PADButtonMapping* m_pendingButtonMapping = nullptr; PADAxisMapping* m_pendingAxisMapping = nullptr; int m_pendingPort = -1; + bool m_isRumbling = false; } m_controllerConfig; bool m_showControllerConfig = false;