diff --git a/extern/aurora b/extern/aurora index 4dd23c74d8..4d7ff2ac11 160000 --- a/extern/aurora +++ b/extern/aurora @@ -1 +1 @@ -Subproject commit 4dd23c74d8bac5b1ba8be6ccb0270dc1bcb37ef9 +Subproject commit 4d7ff2ac115409192ab17e45771c739ea004cef1 diff --git a/files.cmake b/files.cmake index 259339c31c..5e5334c847 100644 --- a/files.cmake +++ b/files.cmake @@ -1376,6 +1376,7 @@ set(DUSK_FILES src/dusk/imgui/ImGuiSaveEditor.cpp src/dusk/imgui/ImGuiStateShare.hpp src/dusk/imgui/ImGuiStateShare.cpp + src/dusk/iso_validate.cpp src/dusk/offset_ptr.cpp src/dusk/OSContext.cpp src/dusk/OSThread.cpp diff --git a/src/d/actor/d_a_myna.cpp b/src/d/actor/d_a_myna.cpp index b23845a922..776f219f93 100644 --- a/src/d/actor/d_a_myna.cpp +++ b/src/d/actor/d_a_myna.cpp @@ -363,6 +363,18 @@ int daMyna_c::destroy() { mpMorf->stopZelAnime(); } +#ifdef TARGET_PC + // !@bug d_a_myna.rel unload used to zero these file-statics; with static linking they dangle across scenes. + daMyna_LightActor = NULL; + daMyna_evtTagActor0 = NULL; + daMyna_evtTagActor1 = NULL; + daMyna_actor_count = 0; + for (int i = 0; i < 10; i++) { + daMyna_targetActor[i] = NULL; + daMyna_subActor[i] = NULL; + } +#endif + #if DEBUG l_HOSTIO.removeHIO(); #endif diff --git a/src/dusk/imgui/ImGuiConsole.cpp b/src/dusk/imgui/ImGuiConsole.cpp index 612c95d7f3..0cf3906d75 100644 --- a/src/dusk/imgui/ImGuiConsole.cpp +++ b/src/dusk/imgui/ImGuiConsole.cpp @@ -40,9 +40,15 @@ namespace dusk { void ImGuiTextCenter(std::string_view text) { ImGui::NewLine(); - float fontSize = ImGui::CalcTextSize(text.data(), text.data() + text.size()).x; + float fontSize = ImGui::CalcTextSize( + text.data(), + text.data() + text.size(), + false, + ImGui::GetWindowSize().x).x; ImGui::SameLine(ImGui::GetWindowSize().x / 2 - fontSize + fontSize / 2); + ImGui::PushTextWrapPos(ImGui::GetWindowSize().x); ImGuiStringViewText(text); + ImGui::PopTextWrapPos(); } bool ImGuiButtonCenter(std::string_view text) { diff --git a/src/dusk/imgui/ImGuiMenuEnhancements.cpp b/src/dusk/imgui/ImGuiMenuEnhancements.cpp index a6058592da..b7d75fea2f 100644 --- a/src/dusk/imgui/ImGuiMenuEnhancements.cpp +++ b/src/dusk/imgui/ImGuiMenuEnhancements.cpp @@ -9,107 +9,117 @@ namespace dusk { void ImGuiMenuEnhancements::draw() { if (ImGui::BeginMenu("Enhancements")) { - if (ImGui::BeginMenu("Quality of Life")) { - config::ImGuiCheckbox("Quick Transform (R+Y)", getSettings().game.enableQuickTransform); + if (ImGui::BeginMenu("Gameplay")) { + ImGui::SeparatorText("Preferences"); + + config::ImGuiCheckbox("Mirror Mode", getSettings().game.enableMirrorMode); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Allows you to quickly transform between forms\n" - "without having to talk to Midna."); + ImGui::SetTooltip("Mirrors the world horizontally, matching the Wii version of the game."); } - config::ImGuiCheckbox("Sun's Song (R+X)", getSettings().game.sunsSong); + config::ImGuiCheckbox("Disable Main HUD", getSettings().game.disableMainHUD); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Allows Wolf Link to howl and change the time of day."); + ImGui::SetTooltip("Disables the main HUD of the game.\n" + "Useful for recording or a more immersive experience!"); } + ImGui::SeparatorText("Difficulty"); + + config::ImGuiSliderInt("Damage Multiplier", getSettings().game.damageMultiplier, 1, 8, "x%d"); + + config::ImGuiCheckbox("Instant Death", getSettings().game.instantDeath); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Any hit will instantly kill you."); + } + + config::ImGuiCheckbox("No Heart Drops", getSettings().game.noHeartDrops); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Hearts will never drop from enemies,\n" + "pots and various other places."); + } + + ImGui::SeparatorText("Quality of Life"); + config::ImGuiCheckbox("Bigger Wallets", getSettings().game.biggerWallets); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Wallet sizes are like in the HD version. (500, 1000, 2000)"); } - config::ImGuiCheckbox("No Rupee Returns", getSettings().game.noReturnRupees); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Always collect Rupees even if your Wallet is too full."); - } - config::ImGuiCheckbox("Disable Rupee Cutscenes", getSettings().game.disableRupeeCutscenes); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Rupees won't play cutscenes after you've collected them the first time."); } - config::ImGuiCheckbox("No Sword Recoil", getSettings().game.noSwordRecoil); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Link won't recoil when his sword hits walls."); - } - config::ImGuiCheckbox("Faster Climbing", getSettings().game.fastClimbing); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Quicker climbing on ladders and vines like the HD version."); } + config::ImGuiCheckbox("Faster Tears of Light", getSettings().game.fastTears); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Tears of Light dropped by Shadow Insects pop out faster like the HD version."); + } + + config::ImGuiCheckbox("Instant Saves", getSettings().game.instantSaves); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Skip the delay when writing to the Memory Card."); + } + config::ImGuiCheckbox("No Climbing Miss Animation", getSettings().game.noMissClimbing); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Prevents Link from playing a struggle animation\n" "when grabbing ledges or climbing on vines."); } - config::ImGuiCheckbox("Faster Tears of Light", getSettings().game.fastTears); + config::ImGuiCheckbox("No Rupee Returns", getSettings().game.noReturnRupees); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Tears of Light dropped by Shadow Insects pop out faster like the HD version."); + ImGui::SetTooltip("Always collect Rupees even if your Wallet is too full."); } - config::ImGuiCheckbox("Hide TV Settings Screen", getSettings().game.hideTvSettingsScreen); + config::ImGuiCheckbox("No Sword Recoil", getSettings().game.noSwordRecoil); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Hides the TV calibration screen shown when loading a save."); + ImGui::SetTooltip("Link won't recoil when his sword hits walls."); + } + + config::ImGuiCheckbox("Skip TV Settings Screen", getSettings().game.hideTvSettingsScreen); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Skip the TV calibration screen shown when loading a save."); } config::ImGuiCheckbox("Skip Warning Screen", getSettings().game.skipWarningScreen); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Skips the warning screen shown when loading the game."); + ImGui::SetTooltip("Skip the warning screen shown when starting the game."); } - config::ImGuiCheckbox("Instant Saves", getSettings().game.instantSaves); + config::ImGuiCheckbox("Sun's Song (R+X)", getSettings().game.sunsSong); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Skip the delay when writing to the Memory Card."); + ImGui::SetTooltip("Allows Wolf Link to howl and change the time of day."); } - ImGui::EndMenu(); - } - - if (ImGui::BeginMenu("Preferences")) { - config::ImGuiCheckbox("Mirror Mode", getSettings().game.enableMirrorMode); + config::ImGuiCheckbox("Quick Transform (R+Y)", getSettings().game.enableQuickTransform); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Mirrors the world, matching the Wii version of the game."); - } - - config::ImGuiCheckbox("Invert Camera X Axis", getSettings().game.invertCameraXAxis); - - config::ImGuiCheckbox("Disable Main HUD", getSettings().game.disableMainHUD); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Disables the main HUD of the game.\n" - "Useful for recording or a more immersive experience!"); + ImGui::SetTooltip("Transform instantly by pressing R and Y simultaneously."); } ImGui::EndMenu(); } if (ImGui::BeginMenu("Graphics")) { + config::ImGuiSliderInt("Shadow Resolution", getSettings().game.shadowResolutionMultiplier, 1, 8, "x%d"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Improves the shadow resolution, making them higher quality."); + } + config::ImGuiCheckbox("Unlock Framerate", getSettings().game.enableFrameInterpolation); const bool frameInterpolationHovered = ImGui::IsItemHovered(); - ImGui::SameLine(); ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.72f, 0.2f, 1.0f)); ImGui::TextUnformatted("[EXPERIMENTAL]"); ImGui::PopStyleColor(); - if (frameInterpolationHovered || ImGui::IsItemHovered()) { ImGui::SetTooltip("Uses inter-frame interpolation to enable higher frame rates.\nVisual artifacts, animation glitches, or instability may occur."); } - config::ImGuiSliderInt("Shadow Resolution", getSettings().game.shadowResolutionMultiplier, 1, 8, "x%d"); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Improves the shadow resolution, making them higher quality."); - } - ImGui::EndMenu(); } @@ -128,6 +138,10 @@ namespace dusk { } if (ImGui::BeginMenu("Input")) { + config::ImGuiCheckbox("Invert Camera X Axis", getSettings().game.invertCameraXAxis); + + ImGui::SeparatorText("Gyro"); + config::ImGuiCheckbox("Gyro Aim", getSettings().game.enableGyroAim); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Enables the gyroscope on supported controllers while aiming the\n" @@ -139,9 +153,18 @@ namespace dusk { config::ImGuiCheckbox("Invert Gyro Pitch", getSettings().game.gyroAimInvertPitch); config::ImGuiCheckbox("Invert Gyro Yaw", getSettings().game.gyroAimInvertYaw); + ImGui::SeparatorText("Tools"); + + config::ImGuiCheckbox("Turbo Key", getSettings().game.enableTurboKeybind); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Hold TAB to increase game speed by up to 4x."); + } + ImGui::EndMenu(); } + ImGui::Separator(); + if (ImGui::BeginMenu("Cheats")) { config::ImGuiCheckbox("Fast Iron Boots", getSettings().game.enableFastIronBoots); @@ -163,23 +186,6 @@ namespace dusk { ImGui::EndMenu(); } - if (ImGui::BeginMenu("Difficulty")) { - config::ImGuiSliderInt("Damage Multiplier", getSettings().game.damageMultiplier, 1, 8, "x%d"); - - config::ImGuiCheckbox("No Heart Drops", getSettings().game.noHeartDrops); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Hearts will never drop from enemies,\n" - "pots and various other places."); - } - - config::ImGuiCheckbox("Instant Death", getSettings().game.instantDeath); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Any hit will instantly kill you."); - } - - ImGui::EndMenu(); - } - if (ImGui::BeginMenu("Technical")) { config::ImGuiCheckbox("Restore Wii 1.0 Glitches", getSettings().game.restoreWiiGlitches); if (ImGui::IsItemHovered()) { @@ -190,15 +196,6 @@ namespace dusk { ImGui::EndMenu(); } - if (ImGui::BeginMenu("Tools")) { - config::ImGuiCheckbox("Turbo Key", getSettings().game.enableTurboKeybind); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Hold TAB to increase game speed by up to 4x."); - } - - ImGui::EndMenu(); - } - ImGui::EndMenu(); } } diff --git a/src/dusk/imgui/ImGuiPreLaunchWindow.cpp b/src/dusk/imgui/ImGuiPreLaunchWindow.cpp index 5925fd8c31..c24dbd8247 100644 --- a/src/dusk/imgui/ImGuiPreLaunchWindow.cpp +++ b/src/dusk/imgui/ImGuiPreLaunchWindow.cpp @@ -7,6 +7,7 @@ #include "ImGuiConsole.hpp" #include "dusk/main.h" #include "dusk/settings.h" +#include "../iso_validate.hpp" #include #include @@ -26,15 +27,42 @@ static constexpr std::array skGameDiscFileFilters{{ {"All Files", "*"}, }}; +static std::string ShowIsoInvalidError(const iso::ValidationError code) { + using namespace std::literals::string_literals; + + switch (code) { + case iso::ValidationError::IOError: + return "Unknown IO error occurred"s; + case iso::ValidationError::InvalidImage: + return "Unable to interpret selected file as a disc image"s; + case iso::ValidationError::WrongGame: + return "Selected disc image is for a different game"s; + case iso::ValidationError::WrongVersion: + return "Selected disc image is for an unsupported version of the game. Only North American GameCube (NTSC/GZ2E01) is supported at this time."s; + case iso::ValidationError::ExecutableMismatch: + return "Selected disc image contains modified executable files."s; + default: + return "Unknown error"s; + } +} + void fileDialogCallback(void* userdata, const char* const* filelist, [[maybe_unused]] int filter) { auto* self = static_cast(userdata); + self->m_errorString.clear(); if (filelist != nullptr) { if (filelist[0] == nullptr) { // Cancelled self->m_selectedIsoPath.clear(); } else { - self->m_selectedIsoPath = filelist[0]; - getSettings().backend.isoPath.setValue(self->m_selectedIsoPath); + const auto path = filelist[0]; + const auto ret = iso::validate(path); + if (ret != iso::ValidationError::Success) { + self->m_selectedIsoPath.clear(); + self->m_errorString = std::move(ShowIsoInvalidError(ret)); + return; + } + self->m_selectedIsoPath = path; + getSettings().backend.isoPath.setValue(path); config::Save(); } } else { @@ -111,6 +139,10 @@ void ImGuiPreLaunchWindow::drawMainMenu() { ImGui::PushFont(ImGuiEngine::fontLarge); if (!isSelectedPathValid()) { + if (!m_errorString.empty()) { + ImGuiTextCenter(m_errorString); + } + if (ImGuiButtonCenter("Select disc image...")) { SDL_ShowOpenFileDialog(&fileDialogCallback, this, aurora::window::get_sdl_window(), skGameDiscFileFilters.data(), int(skGameDiscFileFilters.size()), @@ -148,6 +180,10 @@ void ImGuiPreLaunchWindow::drawOptions() { if (ImGui::BeginChild("OptionsChild", ImVec2(childWidth, endCursorY - cursorY), ImGuiChildFlags_None, ImGuiWindowFlags_NoBackground)) { + if (!m_errorString.empty()) { + ImGuiTextCenter(m_errorString); + } + ImGui::InputText("Game ISO Path", &m_selectedIsoPath, ImGuiInputTextFlags_ReadOnly); ImGui::SameLine(); if (ImGui::Button("Set")) { diff --git a/src/dusk/imgui/ImGuiSaveEditor.cpp b/src/dusk/imgui/ImGuiSaveEditor.cpp index fd45789ccf..1618998249 100644 --- a/src/dusk/imgui/ImGuiSaveEditor.cpp +++ b/src/dusk/imgui/ImGuiSaveEditor.cpp @@ -445,7 +445,7 @@ namespace dusk { } if (ImGui::BeginTabItem("Minigame")) { - //DrawFlagsTab(); + drawMinigameTab(); ImGui::EndTabItem(); } @@ -829,8 +829,8 @@ namespace dusk { ImGuiBeginGroupPanel("Items", { 200, 100 }); for (int slot = 0; slot < 24; slot++) { - ImGui::Text("Slot %02d: ", slot); - ImGui::SameLine(); + ImGui::Text("Slot %02d (%s): ", slot, itemMap.find(getSlotDefault(slot))->second.m_name.c_str()); + ImGui::SameLine(240.0f); if (ImGui::BeginCombo(fmt::format("##ItemComboBox{}", slot).c_str(), itemMap.find(item.mItems[slot])->second.m_name.c_str())) { if (ImGui::Selectable("None")) { dComIfGs_setItem(slot, dItemNo_NONE_e); @@ -856,7 +856,27 @@ namespace dusk { } ImGuiEndGroupPanel(); + dSv_player_item_record_c& itemRecord = dComIfGs_getSaveData()->getPlayer().getItemRecord(); + dSv_player_item_max_c& itemMax = dComIfGs_getSaveData()->getPlayer().getItemMax(); + ImGuiBeginGroupPanel("Item Max Capacities", { 200, 100 }); + ImGui::InputScalar("Arrows Max", ImGuiDataType_U8, &itemMax.mItemMax[0]); + ImGui::InputScalar("Normal Bombs Max", ImGuiDataType_U8, &itemMax.mItemMax[1]); + ImGui::InputScalar("Water Bombs Max", ImGuiDataType_U8, &itemMax.mItemMax[2]); + ImGui::InputScalar("Bomblings Max", ImGuiDataType_U8, &itemMax.mItemMax[3]); + ImGuiEndGroupPanel(); + + ImGuiBeginGroupPanel("Item Amounts", { 200, 100 }); + ImGui::InputScalar("Arrows Amount", ImGuiDataType_U8, &itemRecord.mArrowNum); + ImGui::InputScalar("Slingshot Amount", ImGuiDataType_U8, &itemRecord.mPachinkoNum); + ImGui::InputScalar("Bomb Bag 1 Amount", ImGuiDataType_U8, &itemRecord.mBombNum[0]); + ImGui::InputScalar("Bomb Bag 2 Amount", ImGuiDataType_U8, &itemRecord.mBombNum[1]); + ImGui::InputScalar("Bomb Bag 3 Amount", ImGuiDataType_U8, &itemRecord.mBombNum[2]); + ImGui::InputScalar("Bottle 1 Amount", ImGuiDataType_U8, &itemRecord.mBottleNum[0]); + ImGui::InputScalar("Bottle 2 Amount", ImGuiDataType_U8, &itemRecord.mBottleNum[1]); + ImGui::InputScalar("Bottle 3 Amount", ImGuiDataType_U8, &itemRecord.mBottleNum[2]); + ImGui::InputScalar("Bottle 4 Amount", ImGuiDataType_U8, &itemRecord.mBottleNum[3]); + ImGuiEndGroupPanel(); } static inline void setItemFirstBit(u8 itemNo, bool owned) { @@ -902,27 +922,42 @@ namespace dusk { void ImGuiSaveEditor::drawCollectionTab() { if (ImGui::TreeNode("Equipment")) { if (ImGui::TreeNode("Swords")) { + static u8 sword_list[] = { + dItemNo_SWORD_e, + dItemNo_MASTER_SWORD_e, + dItemNo_WOOD_STICK_e, + dItemNo_LIGHT_SWORD_e, + }; + for (int i = 0; i < 4; i++) { - bool got = dComIfGs_isCollectSword((u8)i) != 0; + bool got = dComIfGs_isItemFirstBit(sword_list[i]) != 0; if (ImGui::Checkbox(fmt::format("{0}##sword_{1}", sSwordNames[i], i).c_str(), &got)) { - if (got) dComIfGs_setCollectSword((u8)i); - else dComIfGs_offCollectSword((u8)i); + if (got) dComIfGs_onItemFirstBit(sword_list[i]); + else dComIfGs_offItemFirstBit(sword_list[i]); } } ImGui::TreePop(); } if (ImGui::TreeNode("Shields")) { + static u8 shield_list[] = { + dItemNo_SHIELD_e, + dItemNo_WOOD_SHIELD_e, + dItemNo_HYLIA_SHIELD_e, + }; + for (int i = 0; i < 3; i++) { - bool got = dComIfGs_isCollectShield((u8)i) != 0; + bool got = dComIfGs_isItemFirstBit(shield_list[i]) != 0; if (ImGui::Checkbox(fmt::format("{0}##shield_{1}", sShieldNames[i], i).c_str(), &got)) { - if (got) dComIfGs_setCollectShield((u8)i); - else dComIfGs_offCollectShield((u8)i); + if (got) dComIfGs_onItemFirstBit(shield_list[i]); + else dComIfGs_offItemFirstBit(shield_list[i]); } } ImGui::TreePop(); } + // TODO: the game checks if you're in ranch clothes, and if so won't display any other tunics in the menu + // find a way to deal with this later if (ImGui::TreeNode("Tunics")) { bool ordonClothes = dComIfGs_isItemFirstBit(dItemNo_WEAR_CASUAL_e) != 0; if (ImGui::Checkbox("Ordon Clothes##tunic_ordon", &ordonClothes)) { @@ -1235,7 +1270,7 @@ namespace dusk { } ImGuiEndGroupPanel(); - ImVec2 cursor = ImGui::GetCursorPos(); + ImVec2 start_cursor = ImGui::GetCursorPos(); ImGui::SameLine(); @@ -1245,13 +1280,17 @@ namespace dusk { } ImGuiEndGroupPanel(); - ImGui::SetCursorPos(cursor); + ImVec2 cursor = ImGui::GetCursorPos(); + + ImGui::SetCursorPos(start_cursor); ImGuiBeginGroupPanel("Item", { 100, 100 }); for (int j = 0; j < 1; j++) { drawFlagList(fmt::format("##_item{}", j).c_str(), membit.mItem[j]); } ImGuiEndGroupPanel(); + + ImGui::SetCursorPos({ start_cursor.x, cursor.y }); } void ImGuiSaveEditor::drawFlagsTab() { @@ -1328,7 +1367,7 @@ namespace dusk { dSv_event_c& event = dComIfGs_getSaveData()->mEvent; for (int e = 0; e < 255; e++) { ImGui::Text("%03d ", e); - ImGui::SameLine(); + ImGui::SameLine(80.0f); for (int i = 7; i >= 0; i--) { bool flag = event.mEvent[e] & (1 << i); if (ImGui::Checkbox(fmt::format("##event{0}{1}", e, i).c_str(), &flag)) { @@ -1345,10 +1384,25 @@ namespace dusk { } } + void ImGuiSaveEditor::drawMinigameTab() { + dSv_MiniGame_c& minigame = dComIfGs_getSaveData()->getMiniGame(); + InputScalarBE("STAR Game Time (Milliseconds)", ImGuiDataType_U32, &minigame.mHookGameTime); + InputScalarBE("Snowboard Race Time (Milliseconds)", ImGuiDataType_U32, &minigame.mRaceGameTime); + InputScalarBE("Fruit-Pop-Flight Score", ImGuiDataType_U32, &minigame.mBalloonScore); + } + void ImGuiSaveEditor::drawConfigTab() { dSv_player_config_c& config = dComIfGs_getSaveData()->getPlayer().getConfig(); ImGui::Checkbox("Enable Vibration", (bool*)&config.mVibration); - if (ImGui::BeginCombo("Target Type", "Hold")) { + + static const char* kTargetTypeNames[] = {"Hold", "Switch"}; + if (ImGui::BeginCombo("Target Type", kTargetTypeNames[config.mAttentionType])) { + if (ImGui::Selectable("Hold")) { + config.mAttentionType = 0; + } + if (ImGui::Selectable("Switch")) { + config.mAttentionType = 1; + } ImGui::EndCombo(); } diff --git a/src/dusk/imgui/ImGuiSaveEditor.hpp b/src/dusk/imgui/ImGuiSaveEditor.hpp index 4b211311b0..dc7c122079 100644 --- a/src/dusk/imgui/ImGuiSaveEditor.hpp +++ b/src/dusk/imgui/ImGuiSaveEditor.hpp @@ -17,6 +17,7 @@ namespace dusk { void drawInventoryTab(); void drawCollectionTab(); void drawFlagsTab(); + void drawMinigameTab(); void drawConfigTab(); private: diff --git a/src/dusk/iso_validate.cpp b/src/dusk/iso_validate.cpp new file mode 100644 index 0000000000..755b4eede0 --- /dev/null +++ b/src/dusk/iso_validate.cpp @@ -0,0 +1,126 @@ +#include "iso_validate.hpp" + +#include +#include + +#include "SDL3/SDL_iostream.h" + +namespace dusk::iso { + +constexpr const char* TP_GAME_IDS[] = { + "GZ2E01", // GCN USA + "GZ2P01", // GCN PAL + "GZ2J01", // GCN JPN + "RZDE01", // Wii USA + "RZDP01", // Wii PAL + "RZDJ01", // Wii JPN + "RZDK01", // Wii KOR +}; + +constexpr const char* SUPPORTED_TP_GAME_IDS[] = { + "GZ2E01", // GCN USA +}; + +template +constexpr bool matches(const char (&id)[6], const char* const (&valid)[N]) { + for (auto elem : valid) { + if (strncmp(id, elem, 6) == 0) { + return true; + } + } + + return false; +} + +struct NodHandleWrapper { + NodHandle* handle; + + NodHandleWrapper() : handle(nullptr) { + } + + ~NodHandleWrapper() { + if (handle != nullptr) { + nod_free(handle); + handle = nullptr; + } + } +}; + +static ValidationError convertNodError(NodResult result) { + switch (result) { + case NOD_RESULT_ERR_IO: + return ValidationError::IOError; + case NOD_RESULT_ERR_FORMAT: + return ValidationError::InvalidImage; + default: + return ValidationError::Unknown; + } +} + +s64 StreamReadAt(void* user_data, u64 offset, void* out, size_t len) { + if (len == 0) { + return 0; + } + + auto io = static_cast(user_data); + + auto ret = SDL_SeekIO(io, static_cast(offset), SDL_IO_SEEK_SET); + if (ret < 0) { + return -1; + } + + auto read = SDL_ReadIO(io, out, len); + if (read == 0) { + if (SDL_GetIOStatus(io) == SDL_IO_STATUS_EOF) { + return 0; + } + + return -1; + } + + return static_cast(read); +} + +s64 StreamLength(void* user_data) { + auto io = static_cast(user_data); + return SDL_GetIOSize(io); +} + +void StreamClose(void* user_data) { + auto io = static_cast(user_data); + SDL_CloseIO(io); +} + +ValidationError validate(const char* path) { + NodHandleWrapper disc; + + const auto sdlStream = SDL_IOFromFile(path, "rb"); + const NodDiscStream nod_stream { + .user_data = sdlStream, + .read_at = StreamReadAt, + .stream_len = StreamLength, + .close = StreamClose, + }; + + auto result = nod_disc_open_stream(&nod_stream, nullptr, &disc.handle); + if (disc.handle == nullptr || result != NOD_RESULT_OK) { + return convertNodError(result); + } + + NodDiscHeader header{}; + result = nod_disc_header(disc.handle, &header); + if (result != NOD_RESULT_OK) { + return convertNodError(result); + } + + if (!matches(header.game_id, TP_GAME_IDS)) { + return ValidationError::WrongGame; + } + + if (!matches(header.game_id, SUPPORTED_TP_GAME_IDS)) { + return ValidationError::WrongVersion; + } + + return ValidationError::Success; +} +} // namespace dusk::iso \ No newline at end of file diff --git a/src/dusk/iso_validate.hpp b/src/dusk/iso_validate.hpp new file mode 100644 index 0000000000..da1ef1f2a6 --- /dev/null +++ b/src/dusk/iso_validate.hpp @@ -0,0 +1,18 @@ +#ifndef DUSK_ISO_VALIDATE_HPP +#define DUSK_ISO_VALIDATE_HPP + +namespace dusk::iso { + enum class ValidationError : u8 { + Success = 0, + IOError, + InvalidImage, + WrongGame, + WrongVersion, + ExecutableMismatch, + Unknown + }; + + ValidationError validate(const char* path); +} + +#endif // DUSK_ISO_VALIDATE_HPP diff --git a/src/m_Do/m_Do_ext.cpp b/src/m_Do/m_Do_ext.cpp index 577ada0837..024dd596a8 100644 --- a/src/m_Do/m_Do_ext.cpp +++ b/src/m_Do/m_Do_ext.cpp @@ -2383,8 +2383,8 @@ void mDoExt_3DlineMat0_c::draw() { int var_r26 = (field_0x14 << 1) & 0xFFFF; for (int i = 0; i < field_0x10; i++) { - GXSETARRAY(GX_VA_POS, field_0x18->field_0x8[field_0x16], sizeof(cXyz) * var_r26, sizeof(cXyz), true); - GXSETARRAY(GX_VA_NRM, field_0x18->field_0x10[field_0x16], 3 * var_r26, 3, true); + GXSETARRAY(GX_VA_POS, var_r28->field_0x8[field_0x16], sizeof(cXyz) * var_r26, sizeof(cXyz), true); + GXSETARRAY(GX_VA_NRM, var_r28->field_0x10[field_0x16], 3 * var_r26, 3, true); GXBegin(GX_TRIANGLESTRIP, GX_VTXFMT0, var_r26); for (u16 j = 0; j < (u16)var_r26; j++) {