From 6a7acc8ddcb76e552a809968758a587906c1601e Mon Sep 17 00:00:00 2001 From: jdflyer Date: Thu, 3 Sep 2026 09:21:50 -0600 Subject: [PATCH] Split cosmetics mod into submodule (#2378) --- .gitmodules | 3 + CMakeLists.txt | 2 +- mods/basic_cosmetics_mod/CMakeLists.txt | 35 - mods/basic_cosmetics_mod/mod.json | 7 - mods/basic_cosmetics_mod/res/.gitkeep | 0 mods/basic_cosmetics_mod/src/color_utils.cpp | 88 --- mods/basic_cosmetics_mod/src/color_utils.hpp | 30 - mods/basic_cosmetics_mod/src/hooks.cpp | 745 ------------------ mods/basic_cosmetics_mod/src/hooks.hpp | 7 - .../src/midna_hair_color.cpp | 115 --- .../src/midna_hair_color.hpp | 19 - mods/basic_cosmetics_mod/src/mod.cpp | 653 --------------- mods/basic_cosmetics_mod/src/mod.hpp | 65 -- .../basic_cosmetics_mod/src/texture_utils.cpp | 682 ---------------- .../basic_cosmetics_mod/src/texture_utils.hpp | 21 - mods/cosmetics | 1 + 16 files changed, 5 insertions(+), 2468 deletions(-) delete mode 100644 mods/basic_cosmetics_mod/CMakeLists.txt delete mode 100644 mods/basic_cosmetics_mod/mod.json delete mode 100644 mods/basic_cosmetics_mod/res/.gitkeep delete mode 100644 mods/basic_cosmetics_mod/src/color_utils.cpp delete mode 100644 mods/basic_cosmetics_mod/src/color_utils.hpp delete mode 100644 mods/basic_cosmetics_mod/src/hooks.cpp delete mode 100644 mods/basic_cosmetics_mod/src/hooks.hpp delete mode 100644 mods/basic_cosmetics_mod/src/midna_hair_color.cpp delete mode 100644 mods/basic_cosmetics_mod/src/midna_hair_color.hpp delete mode 100644 mods/basic_cosmetics_mod/src/mod.cpp delete mode 100644 mods/basic_cosmetics_mod/src/mod.hpp delete mode 100644 mods/basic_cosmetics_mod/src/texture_utils.cpp delete mode 100644 mods/basic_cosmetics_mod/src/texture_utils.hpp create mode 160000 mods/cosmetics diff --git a/.gitmodules b/.gitmodules index 16ad1921cc..881f8f524b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "mods/randomizer"] path = mods/randomizer url = https://github.com/TwilitRealm/dusklight-randomizer.git +[submodule "mods/cosmetics"] + path = mods/cosmetics + url = https://github.com/TwilitRealm/dusklight-cosmetics diff --git a/CMakeLists.txt b/CMakeLists.txt index 3161a199e8..4f06ae435f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -571,7 +571,7 @@ if (DUSK_ENABLE_CODE_MODS AND CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR add_subdirectory(mods/shadow_mod) add_subdirectory(mods/window_demo) add_subdirectory(mods/flow_demo) - add_subdirectory(mods/basic_cosmetics_mod) + add_subdirectory(mods/cosmetics) add_subdirectory(mods/randomizer) endif () diff --git a/mods/basic_cosmetics_mod/CMakeLists.txt b/mods/basic_cosmetics_mod/CMakeLists.txt deleted file mode 100644 index f00540864b..0000000000 --- a/mods/basic_cosmetics_mod/CMakeLists.txt +++ /dev/null @@ -1,35 +0,0 @@ -cmake_minimum_required(VERSION 3.25) -project(basic_cosmetics_mod CXX) - -if (CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) - set(DUSK_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../.." CACHE PATH "Path to dusk source root") - option(DUSK_MOD_USE_FULL_TREE "Use full build instead of the minimal mod SDK" OFF) - set(CMAKE_POSITION_INDEPENDENT_CODE ON) - if (DUSK_MOD_USE_FULL_TREE) - add_subdirectory("${DUSK_DIR}" dusk EXCLUDE_FROM_ALL) - else () - add_subdirectory("${DUSK_DIR}/sdk" dusk-sdk EXCLUDE_FROM_ALL) - endif () -endif () - -include(FetchContent) - -message(STATUS "basic_cosmetics_mod: Fetching xxhash") -FetchContent_Declare( - xxhash - GIT_REPOSITORY https://github.com/Cyan4973/xxHash.git - GIT_TAG v0.8.3 -) -FetchContent_MakeAvailable(xxhash) - -set(COSMETIC_SOURCES src/color_utils.cpp src/midna_hair_color.cpp src/texture_utils.cpp src/hooks.cpp) - -add_mod(basic_cosmetics_mod - FEATURES game fmt - SOURCES src/mod.cpp ${COSMETIC_SOURCES} - MOD_JSON mod.json - RES_DIR res - BUNDLE -) - -target_link_libraries(basic_cosmetics_mod PRIVATE xxHash::xxhash) diff --git a/mods/basic_cosmetics_mod/mod.json b/mods/basic_cosmetics_mod/mod.json deleted file mode 100644 index d6c258bfe9..0000000000 --- a/mods/basic_cosmetics_mod/mod.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "id": "dev.twilitrealm.basic_cosmetics", - "name": "Basic Cosmetics", - "version": "1.0.0", - "author": "Twilit Realm", - "description": "Change Basic Cosmetic Colors" -} diff --git a/mods/basic_cosmetics_mod/res/.gitkeep b/mods/basic_cosmetics_mod/res/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/mods/basic_cosmetics_mod/src/color_utils.cpp b/mods/basic_cosmetics_mod/src/color_utils.cpp deleted file mode 100644 index d1dc2eb096..0000000000 --- a/mods/basic_cosmetics_mod/src/color_utils.cpp +++ /dev/null @@ -1,88 +0,0 @@ -#include "color_utils.hpp" - -uint8_t desaturate_rgb_565(uint16_t rgb565Val) { - const uint32_t r = (rgb565Val & 0xf800) >> 11; - const uint32_t g = (rgb565Val & 0x7e0) >> 5; - const uint32_t b = rgb565Val & 0x1f; - - // Here we are doing a quicker (0.22 * r + 0.72 * g + 0.06 * b) which - // uses multiplies and shifts rather than division. - const uint32_t combined = 30480413 * r + 49085341 * g + 8312839 * b; - uint8_t shifted = (combined >> 24) & 0xff; - - // Check if should round up shifted value. - if (shifted < 0xff && combined & 0x00800000) { - shifted += 1; - } - - return shifted; -} - -uint16_t blend_overlay_rgb_565(uint8_t grayVal, GXColor color) { - uint32_t rTimes255, gTimes255, bTimes255; - - if (grayVal <= 0x7f) { - const uint32_t grayTimesTwo = 2 * grayVal; - - rTimes255 = grayTimesTwo * color.r; - gTimes255 = grayTimesTwo * color.g; - bTimes255 = grayTimesTwo * color.b; - } else { - const uint32_t multiplier = 2 * (255 - grayVal); - - rTimes255 = 255 * 255 - multiplier * (255 - color.r); - gTimes255 = 255 * 255 - multiplier * (255 - color.g); - bTimes255 = 255 * 255 - multiplier * (255 - color.b); - } - - // Divide each by 255 - const uint32_t r = (rTimes255 + 1 + (rTimes255 >> 8)) >> 8; - const uint32_t g = (gTimes255 + 1 + (gTimes255 >> 8)) >> 8; - const uint32_t b = (bTimes255 + 1 + (bTimes255 >> 8)) >> 8; - - return ((r & 0xf8) << 8) | ((g & 0xfc) << 3) | ((b & 0xf8) >> 3); -} - -// Helper function to perform overlay blending on a single 8-bit color channel -uint8_t blend_overlay_channel(uint8_t base, uint8_t blend) { - if (base < 128) { - return static_cast((2 * base * blend) / 255); - } - - return static_cast(255 - (2 * (255 - base) * (255 - blend)) / 255); -} - -bool is_valid_hex_color_str(std::string_view hexStr) { - return hexStr.find_first_not_of("0123456789ABCDEFabcdef") == std::string_view::npos && - hexStr.length() == 6; -} - -GXColor hex_color_str_to_gx_color(const std::string& hexColorStr) { - u8 r = std::stoi(hexColorStr.substr(0, 2), nullptr, 16); - u8 g = std::stoi(hexColorStr.substr(2, 2), nullptr, 16); - u8 b = std::stoi(hexColorStr.substr(4, 2), nullptr, 16); - return GXColor{r, g, b}; -} - -static f32 rainbowPhaseAngle = 0.f; - -GXColor get_rainbow_rgb(f32 amplitude) { - f32 phase_rad = rainbowPhaseAngle * M_PI / 180.0f; - - u8 r_val = (u8)(amplitude * (sinf(phase_rad) + 1.0f) + 0.5f); - u8 g_val = (u8)(amplitude * (sinf(phase_rad + 2.0f * M_PI / 3.0f) + 1.0f) + 0.5f); - u8 b_val = (u8)(amplitude * (sinf(phase_rad + 4.0f * M_PI / 3.0f) + 1.0f)); - GXColor rgbColor; - rgbColor.r = r_val; - rgbColor.g = g_val; - rgbColor.b = b_val; - rgbColor.a = 0xff; - return rgbColor; -} - -void update_rainbow_rgb(f32 increment) { - rainbowPhaseAngle += increment; - if (rainbowPhaseAngle >= 360.0f) { - rainbowPhaseAngle -= 360.0f; - } -} \ No newline at end of file diff --git a/mods/basic_cosmetics_mod/src/color_utils.hpp b/mods/basic_cosmetics_mod/src/color_utils.hpp deleted file mode 100644 index 94e1ce62a8..0000000000 --- a/mods/basic_cosmetics_mod/src/color_utils.hpp +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -/** - * File originally copied from console TPR with permission from isaac - * https://github.com/zsrtp/libtp_rel/blob/master/include/util/color_utils.h - */ - -#include - -#include -#include - -// Desaturates an RGB565 color to a u8 gray value (0xFF being white and 0x00 -// being black). -uint8_t desaturate_rgb_565(uint16_t rgb565Val); - -// Performs an "Overlay" blend of a u8 gray value and a pointer to a u8 -// array of {r,g,b}. Returns the result as an RGB565. -uint16_t blend_overlay_rgb_565(uint8_t grayVal, GXColor color); - -// Perform overlay blending on a single 8-bit color channel -uint8_t blend_overlay_channel(uint8_t base, uint8_t blend); - -bool is_valid_hex_color_str(std::string_view hexStr); - -GXColor hex_color_str_to_gx_color(const std::string& hexColorStr); - -GXColor get_rainbow_rgb(f32 amplitude); - -void update_rainbow_rgb(f32 increment); diff --git a/mods/basic_cosmetics_mod/src/hooks.cpp b/mods/basic_cosmetics_mod/src/hooks.cpp deleted file mode 100644 index 2c878f7632..0000000000 --- a/mods/basic_cosmetics_mod/src/hooks.cpp +++ /dev/null @@ -1,745 +0,0 @@ -#include "hooks.hpp" - -#include "color_utils.hpp" -#include "midna_hair_color.hpp" -#include "mod.hpp" -#include "types.h" - -#include "mods/svc/hook.hpp" -#include "mods/svc/log.hpp" - -#include "SSystem/SComponent/c_phase.h" -#include "d/actor/d_a_alink.h" -#include "d/actor/d_a_midna.h" -#include "d/d_a_item_static.h" -#include "d/d_bright_check.h" -#include "d/d_file_sel_info.h" -#include "d/d_file_select.h" -#include "d/d_kankyo.h" -#include "d/d_kantera_icon_meter.h" -#include "d/d_menu_collect.h" -#include "d/d_menu_dmap.h" -#include "d/d_menu_fishing.h" -#include "d/d_menu_fmap2D.h" -#include "d/d_menu_insect.h" -#include "d/d_menu_letter.h" -#include "d/d_menu_option.h" -#include "d/d_menu_ring.h" -#include "d/d_menu_save.h" -#include "d/d_menu_skill.h" -#include "d/d_meter2.h" -#include "d/d_meter2_draw.h" -#include "d/d_meter2_info.h" -#include "d/d_meter_button.h" -#include "d/d_meter_hakusha.h" -#include "d/d_msg_object.h" -#include "d/d_msg_out_font.h" -#include "d/d_msg_scrn_base.h" -#include "d/d_pane_class.h" - -// Lantern ambience color -DEFINE_HOOK(&dKy_WolfEyeLight_set, WolfEyeLightSet); -void wolf_eye_light_set_post(ModContext*, void*, void*, void*) { - auto maybeLanternColor = get_config_var_color(get_cvars().lanternGlowColor, true); - if (maybeLanternColor.has_value()) { - auto lanternColor = maybeLanternColor.value(); - - dScnKy_env_light_c* kankyo = dKy_getEnvlight(); - kankyo->field_0x0c18[0].mColor.r = lanternColor.r; - kankyo->field_0x0c18[0].mColor.g = lanternColor.g; - kankyo->field_0x0c18[0].mColor.b = lanternColor.b; - } -} - -// Lantern Sphere color -DEFINE_HOOK(&daAlink_c::preKandelaarDraw, PreKandelaarDraw); -void pre_kandelaar_draw_post(ModContext*, void*, void*, void*) { - auto maybeLanternColor = get_config_var_color(get_cvars().lanternGlowColor, true); - if (maybeLanternColor.has_value()) { - auto lanternColor = maybeLanternColor.value(); - - J3DMaterial* mat_p = daAlink_getAlinkActorClass() - ->mpKanteraGlowModel->getModelData() - ->getMaterialNodePointer(0); - - J3DGXColorS10 color; - color.r = lanternColor.r; - color.g = lanternColor.g; - color.b = lanternColor.b; - color.a = 255; - mat_p->setTevColor(1, &color); - - color.r = lanternColor.r; - color.g = lanternColor.g; - color.b = lanternColor.b; - mat_p->setTevColor(2, &color); - } -} - -// Main Lantern Meter Color -DEFINE_HOOK(&CPaneMgr::setBlackWhite, CPaneMgrSetBlackWhite); -HookAction cpane_mgr_set_black_white_pre(ModContext*, void* args, void*, void*) { - // Check for magic meter - auto pane = mods::arg(args, 0); - if (dMeter2Info_getMeterClass() == NULL || - pane != dMeter2Info_getMeterClass()->getMeterDrawPtr()->mpMagicMeter) - { - return HOOK_CONTINUE; - } - - // If magic meter, check to see we're setting lantern colors - auto& black = mods::arg_ref(args, 1); - auto& white = mods::arg_ref(args, 2); - if (black != JUtility::TColor(255, 255, 140, 255) && - white != JUtility::TColor(230, 170, 0, 255)) - { - return HOOK_CONTINUE; - } - - auto maybeLanternColor = get_config_var_color(get_cvars().lanternGlowColor, true); - if (maybeLanternColor.has_value()) { - auto lanternColor = maybeLanternColor.value(); - black = JUtility::TColor(lanternColor.r, lanternColor.g, lanternColor.b, 255); - white = JUtility::TColor(lanternColor.r, lanternColor.g, lanternColor.b, 255); - } - - return HOOK_CONTINUE; -} - -// Lantern Icon Meter Color -DEFINE_HOOK(&dKantera_icon_c::setNowGauge, KanteraIconSetNowGauge); -void kantera_icon_set_now_gauge_post(ModContext*, void* args, void*, void*) { - auto maybeLanternColor = get_config_var_color(get_cvars().lanternGlowColor, true); - if (maybeLanternColor.has_value()) { - auto lanternColor = maybeLanternColor.value(); - - auto kanteraIcon = mods::arg(args, 0); - kanteraIcon->mpGauge->setBlackWhite( - JUtility::TColor(lanternColor.r, lanternColor.g, lanternColor.b, 255), - JUtility::TColor(lanternColor.r, lanternColor.g, lanternColor.b, 255)); - } -} - -// Light Sword Effect Color -DEFINE_HOOK(&daAlink_c::setLightningSwordEffect, SetLightningSwordEffect); -void set_lightning_sword_effect_post(ModContext*, void* args, void*, void*) { - auto maybeGlowColor = get_config_var_color(get_cvars().lightSwordGlowColor, true); - if (maybeGlowColor.has_value()) { - auto glowColor = maybeGlowColor.value(); - - auto link = mods::arg(args, 0); - // Check copied from inside the hooked function - if (link->mEquipItem == 0x103 && link->checkNoResetFlg3(daPy_py_c::FLG3_UNK_100000)) { - for (size_t i = 0; i < 3; i++) { - auto emitter = dComIfGp_particle_getEmitter(link->field_0x327c[i]); - if (emitter != NULL) { - emitter->setGlobalEnvColor(glowColor.r, glowColor.g, glowColor.b); - emitter->setGlobalPrmColor(glowColor.r, glowColor.g, glowColor.b); - } - } - } - } -} - -void recolor_ui_button(ConfigVarHandle option, u64 tag, J2DScreen* screen) { - auto buttonColorStr = get_str_option(option, ""); - if (is_valid_hex_color_str(buttonColorStr)) { - auto color = hex_color_str_to_gx_color(buttonColorStr); - auto element = static_cast(screen->search(tag)); - if (element != nullptr) { - element->setBlackWhite( - JUtility::TColor(0, 0, 0, 0), JUtility::TColor(color.r, color.g, color.b, 0xFF)); - } - } -} - -// Main gameplay UI. Top left hearts and top right buttons -DEFINE_HOOK(&dMeter2Draw_c::init, dMeter2Init); -void d_meter_2_init_post(ModContext*, void* args, void*, void*) { - auto dMeter2Draw = mods::arg(args, 0); - auto screen = dMeter2Draw->getMainScreenPtr(); - - // Heart tags on main UI - static constexpr std::array kHeartTags = {MULTI_CHAR('hear_00'), MULTI_CHAR('hear_01'), - MULTI_CHAR('hear_02'), MULTI_CHAR('hear_03'), MULTI_CHAR('hear_04'), MULTI_CHAR('hear_05'), - MULTI_CHAR('hear_06'), MULTI_CHAR('hear_07'), MULTI_CHAR('hear_08'), MULTI_CHAR('hear_09'), - MULTI_CHAR('hear_10'), MULTI_CHAR('hear_11'), MULTI_CHAR('hear_12'), MULTI_CHAR('hear_13'), - MULTI_CHAR('hear_14'), MULTI_CHAR('hear_15'), MULTI_CHAR('hear_16'), MULTI_CHAR('hear_17'), - MULTI_CHAR('hear_18'), MULTI_CHAR('hear_19'), MULTI_CHAR('bigh_00'), MULTI_CHAR('bigh_01'), - MULTI_CHAR('bigh_02'), MULTI_CHAR('bigh_03')}; - - auto maybeHeartColor = get_config_var_color(get_cvars().heartColor); - if (maybeHeartColor.has_value()) { - auto heartColor = maybeHeartColor.value(); - for (auto tag : kHeartTags) { - auto element = static_cast(screen->search(tag)); - if (element != nullptr) { - element->setBlackWhite(heartColor, JUtility::TColor(200, 200, 200, 255)); - } - } - } - - recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('a_btn'), screen); - recolor_ui_button(get_cvars().bButtonColor, MULTI_CHAR('b_btn'), screen); - recolor_ui_button(get_cvars().xButtonColor, MULTI_CHAR('x_btn'), screen); - recolor_ui_button(get_cvars().yButtonColor, MULTI_CHAR('y_btn'), screen); - recolor_ui_button(get_cvars().zButtonColor, MULTI_CHAR('zbtn'), screen); -} - -// Hearts on the file select screen -DEFINE_HOOK(&dFile_info_c::setHeartCnt, FileInfoSetHeartCount); -void file_info_set_heart_count_post(ModContext*, void* args, void*, void*) { - auto fileInfo = mods::arg(args, 0); - - // Heart tags on file select - static constexpr std::array kFileSelectHeartTags{ - MULTI_CHAR('hear_20'), - MULTI_CHAR('hear_21'), - MULTI_CHAR('hear_22'), - MULTI_CHAR('hear_23'), - MULTI_CHAR('hear_24'), - MULTI_CHAR('hear_25'), - MULTI_CHAR('hear_26'), - MULTI_CHAR('hear_27'), - MULTI_CHAR('hear_28'), - MULTI_CHAR('hear_29'), - MULTI_CHAR('hear_30'), - MULTI_CHAR('hear_31'), - MULTI_CHAR('hear_32'), - MULTI_CHAR('hear_33'), - MULTI_CHAR('hear_34'), - MULTI_CHAR('hear_35'), - MULTI_CHAR('hear_36'), - MULTI_CHAR('hear_37'), - MULTI_CHAR('hear_38'), - MULTI_CHAR('hear_39'), - }; - - auto maybeHeartColor = get_config_var_color(get_cvars().heartColor); - if (maybeHeartColor.has_value()) { - auto heartColor = maybeHeartColor.value(); - for (auto tag : kFileSelectHeartTags) { - auto element = static_cast(fileInfo->mFileInfo.Scr->search(tag)); - if (element != nullptr) { - element->setBlackWhite(heartColor, JUtility::TColor(200, 200, 200, 255)); - } - } - } -} - -// Buttons that appear contextually at the bottom of the screen during gameplay -// (Are X, Y ever used there?) -DEFINE_HOOK(&dMeterButton_c::screenInitButton, ScreenInitButton); -void screen_init_button_post(ModContext*, void* args, void*, void*) { - auto meterButton = mods::arg(args, 0); - auto screen = meterButton->mpButtonScreen; - - recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('a_btn1'), screen); - recolor_ui_button(get_cvars().bButtonColor, MULTI_CHAR('b_btn'), screen); - recolor_ui_button(get_cvars().xButtonColor, MULTI_CHAR('x_btn'), screen); - recolor_ui_button(get_cvars().yButtonColor, MULTI_CHAR('y_btn'), screen); - recolor_ui_button(get_cvars().zButtonColor, MULTI_CHAR('zbtn'), screen); -} - -// A and B buttons when saving a file -DEFINE_HOOK(&dMenu_save_c::screenSet, MenuSaveScreenSet); -void menu_save_screen_set_post(ModContext*, void* args, void*, void*) { - auto menuSave = mods::arg(args, 0); - auto screen = menuSave->mSaveSel.Scr; - - recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('wabtn'), screen); - recolor_ui_button(get_cvars().bButtonColor, MULTI_CHAR('wbbtn'), screen); -} - -// A and B buttons when selecting a file -DEFINE_HOOK(&dFile_select_c::screenSet, FileSelectScreenSet); -void file_select_screen_set_post(ModContext*, void* args, void*, void*) { - auto fileSelect = mods::arg(args, 0); - auto screen = fileSelect->fileSel.Scr; - - recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('wabtn'), screen); - recolor_ui_button(get_cvars().bButtonColor, MULTI_CHAR('wbbtn'), screen); -} - -// A button on brightness check screen -DEFINE_HOOK(&dBrightCheck_c::screenSet, BrightCheckScreenSet); -void bright_check_screen_set_post(ModContext*, void* args, void*, void*) { - auto brightCheck = mods::arg(args, 0); - auto screen = brightCheck->mBrightCheck.Scr; - - recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('a_btn1'), screen); -} - -// X and Y buttons on the item wheel screen -DEFINE_HOOK(&dMenu_Ring_c::_create, MenuRingCreate); -void menu_ring_create_post(ModContext*, void* args, void*, void*) { - auto menuRing = mods::arg(args, 0); - auto screen = menuRing->mpScreen; - - recolor_ui_button(get_cvars().xButtonColor, MULTI_CHAR('xbtn'), screen); - recolor_ui_button(get_cvars().yButtonColor, MULTI_CHAR('ybtn'), screen); -} - -// A and B buttons on the main pause screen -DEFINE_HOOK(&dMenu_Collect2D_c::_create, MenuCollect2DCreate); -void menu_collect_2D_create_post(ModContext*, void* args, void*, void*) { - auto menuCollect2D = mods::arg(args, 0); - auto screen = menuCollect2D->mpScreenIcon; - - recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('a_btn'), screen); - recolor_ui_button(get_cvars().bButtonColor, MULTI_CHAR('b_btn'), screen); -} - -// A and B buttons on the fishing journal screen -DEFINE_HOOK(&dMenu_Fishing_c::screenSetDoIcon, MenuFishingScreenSetDoIcon); -void menu_fishing_screen_set_do_icon_post(ModContext*, void* args, void*, void*) { - auto menuFishing = mods::arg(args, 0); - auto screen = menuFishing->mpIconScreen; - - recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('a_btn'), screen); - recolor_ui_button(get_cvars().bButtonColor, MULTI_CHAR('b_btn'), screen); -} - -// A and B buttons on the insect collection screen -DEFINE_HOOK(&dMenu_Insect_c::screenSetDoIcon, MenuInsectScreenSetDoIcon); -void menu_insect_screen_set_do_icon_post(ModContext*, void* args, void*, void*) { - auto menuInsect = mods::arg(args, 0); - auto screen = menuInsect->mpIconScreen; - - recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('a_btn'), screen); - recolor_ui_button(get_cvars().bButtonColor, MULTI_CHAR('b_btn'), screen); -} - -// A and B buttons on the letters screen -DEFINE_HOOK(&dMenu_Letter_c::screenSetDoIcon, MenuLetterScreenSetDoIcon); -void menu_letter_screen_set_do_icon_post(ModContext*, void* args, void*, void*) { - auto menuLetter = mods::arg(args, 0); - auto screen = menuLetter->mpIconScreen; - - recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('a_btn'), screen); - recolor_ui_button(get_cvars().bButtonColor, MULTI_CHAR('b_btn'), screen); -} - -// A, B, and Z buttons on option screen -DEFINE_HOOK(&dMenu_Option_c::_create, MenuOptionCreate); -void menu_option_create_post(ModContext*, void* args, void*, void*) { - auto menuOption = mods::arg(args, 0); - auto screen = menuOption->mpScreenIcon; - auto backScreen = menuOption->mpBackScreen; - auto tvScreen = menuOption->mpTVScreen; - - recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('a_btn'), screen); - recolor_ui_button(get_cvars().bButtonColor, MULTI_CHAR('b_btn'), screen); - recolor_ui_button(get_cvars().zButtonColor, MULTI_CHAR('g_zbtn'), backScreen); - - // A Button on option's brightness check screen - recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('a_btn1'), tvScreen); -} - -// A and B buttons on the hidden skills screen -DEFINE_HOOK(&dMenu_Skill_c::screenSetDoIcon, MenuSkillScreenSetDoIcon); -void menu_skill_screen_set_do_icon_post(ModContext*, void* args, void*, void*) { - auto menuSkill = mods::arg(args, 0); - auto screen = menuSkill->mpIconScreen; - - recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('a_btn'), screen); - recolor_ui_button(get_cvars().bButtonColor, MULTI_CHAR('b_btn'), screen); -} - -// A, B, and Z buttons on the field map screen -DEFINE_HOOK(&dMenu_Fmap_c::_create, MenuFMapCreate); -void menu_fmap_create_post(ModContext*, void* args, void*, void*) { - auto menuFMap = mods::arg(args, 0); - auto screen = menuFMap->mpDraw2DTop->mpTitleScreen; - - recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('a_btn'), screen); - recolor_ui_button(get_cvars().bButtonColor, MULTI_CHAR('b_btn1'), screen); - recolor_ui_button(get_cvars().zButtonColor, MULTI_CHAR('zbtn'), screen); -} - -// A and B buttons on the dungeon map screen -DEFINE_HOOK_SYMBOL("dMenu_DmapBg_c::buttonIconScreenInit", void(dMenu_DmapBg_c*), MenuDMapButtonIconScreenInit); -void menu_dmap_button_icon_screen_init_post(ModContext*, void* args, void*, void*) { - auto menuDMap = mods::arg(args, 0); - auto screen = menuDMap->mButtonScreen; - - recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('a_btn'), screen); - recolor_ui_button(get_cvars().bButtonColor, MULTI_CHAR('b_btn'), screen); -} - -// A button on the howling screen -DEFINE_HOOK(&dMsgObject_c::talkStartInit, MsgObjectTalkStartInit); -void msg_object_talk_start_init_post(ModContext*, void* args, void*, void*) { - auto msgObject = mods::arg(args, 0); - - // If textbox kind is howling - if (msgObject->mFukiKind == 17) { - auto screen = msgObject->mpScrnDraw->mpScreen; - - recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('abtn'), screen); - } -} - -// A button when on Epona -DEFINE_HOOK(&dMeterHakusha_c::_create, MeterHakushaCreate); -void meter_hakusha_create_post(ModContext*, void* args, void*, void*) { - auto meterHakusha = mods::arg(args, 0); - auto screen = meterHakusha->mpButtonScreen; - - recolor_ui_button(get_cvars().aButtonColor, MULTI_CHAR('a_btn1'), screen); -} - -// A, B, X, and Y button icons in text -DEFINE_HOOK(&COutFont_c::createPane, OutFontCreatePane); -void out_font_create_pane_post(ModContext*, void* args, void*, void*) { - auto outFont = mods::arg(args, 0); - auto paneArr = outFont->mpPane; - - auto maybeAButtonColor = get_config_var_color(get_cvars().aButtonColor); - if (maybeAButtonColor.has_value()) { - auto aButtonColor = maybeAButtonColor.value(); - paneArr[0]->setBlackWhite(JUtility::TColor(255, 255, 255, 0), - JUtility::TColor(aButtonColor.r, aButtonColor.g, aButtonColor.b, 255)); - } - - auto maybeBButtonColor = get_config_var_color(get_cvars().bButtonColor); - if (maybeBButtonColor.has_value()) { - auto bButtonColor = maybeBButtonColor.value(); - paneArr[1]->setBlackWhite(JUtility::TColor(255, 255, 255, 0), - JUtility::TColor(bButtonColor.r, bButtonColor.g, bButtonColor.b, 255)); - } - - // Condition copied from hooked function - auto xyBlack = JUtility::TColor(255, 255, 255, 0); - if (outFont->field_0x242 == 1) { - xyBlack = JUtility::TColor(0, 0, 0, 0); - } - - auto maybeXButtonColor = get_config_var_color(get_cvars().xButtonColor); - if (maybeXButtonColor.has_value()) { - auto xButtonColor = maybeXButtonColor.value(); - paneArr[5]->setBlackWhite( - xyBlack, JUtility::TColor(xButtonColor.r, xButtonColor.g, xButtonColor.b, 255)); - } - - auto maybeYButtonColor = get_config_var_color(get_cvars().yButtonColor); - if (maybeYButtonColor.has_value()) { - auto yButtonColor = maybeYButtonColor.value(); - paneArr[6]->setBlackWhite( - xyBlack, JUtility::TColor(yButtonColor.r, yButtonColor.g, yButtonColor.b, 255)); - } -} - -// Heart icon in text -DEFINE_HOOK(&COutFontSet_c::drawFont, OutFontSetDrawFont); -void out_font_set_draw_font_post(ModContext*, void* args, void*, void*) { - auto outFontSet = mods::arg(args, 0); - - auto maybeHeartColor = get_config_var_color(get_cvars().heartColor); - if (maybeHeartColor.has_value()) { - auto heartColor = maybeHeartColor.value(); - u32 heartColor_u32 = heartColor.r << 24 | heartColor.g << 16 | heartColor.b << 8 | 0xFF; - if (outFontSet->getType() == 0x1B) { // Heart icon type - outFontSet->mColor = heartColor_u32; - } - } -} - -// Heart Drop/Piece of Heart/Heart Container model color -DEFINE_HOOK(&daItemBase_c::CreateItemHeap, ItemBaseCreateItemHeap); -void item_base_create_item_heap_post(ModContext*, void* args, void*, void*) { - auto itemBase = mods::arg(args, 0); - if (itemBase == NULL) { - return; - } - auto itemNo = itemBase->m_itemNo; - if (itemNo == dItemNo_HEART_e || itemNo == dItemNo_UTAWA_HEART_e || - itemNo == dItemNo_KAKERA_HEART_e) - { - auto maybeHeartColor = get_config_var_color(get_cvars().heartColor); - if (maybeHeartColor.has_value()) { - auto heartColor = maybeHeartColor.value(); - auto heartColorS10 = GXColorS10(heartColor.r, heartColor.g, heartColor.b, heartColor.a); - - // Edit inner heart material color for Piece of Heart / Heart Container - if (itemNo == dItemNo_UTAWA_HEART_e || itemNo == dItemNo_KAKERA_HEART_e) { - *itemBase->mpModel->getModelData()->getMaterialNodePointer(3)->getTevKColor(1) = - heartColor; - *itemBase->mpModel->getModelData()->getMaterialNodePointer(3)->getTevColor(1) = - heartColorS10; - } - - const u8 heartColorRGB[3] = {heartColor.r, heartColor.g, heartColor.b}; - u8** cRegTable = - reinterpret_cast(&itemBase->mpBrkAnm->getBrkAnm()->mAnmCRegDataR); - u8** kRegTable = - reinterpret_cast(&itemBase->mpBrkAnm->getBrkAnm()->mAnmKRegDataR); - - for (int i = 0; i < 3; i++) { - u8* cReg = cRegTable[i]; - u8* kReg = kRegTable[i]; - - auto curColor = heartColorRGB[i]; - - // Set heart drop inner heart color - cReg[0x3] = curColor; - cReg[0xB] = curColor; - - // Set heart drop outer heart color - kReg[0x3] = curColor; - kReg[0xB] = curColor; - - if (itemNo == dItemNo_KAKERA_HEART_e) { - cReg[0x13] = curColor; - cReg[0x1B] = curColor; - kReg[0x13] = curColor; - kReg[0x1B] = curColor; - } - if (itemNo == dItemNo_UTAWA_HEART_e) { - cReg[0x13] = curColor; - kReg[0x13] = curColor; - kReg[0x1B] = curColor; - kReg[0x23] = curColor; - kReg[0x2B] = curColor; - } - } - } - } -} - -// Midna Hair Color -DEFINE_HOOK(&daMidna_c::create, MidnaCreate); -void midna_create_post(ModContext*, void* args, void* retval, void*) { - auto step = reinterpret_cast(retval); - // Don't set colors if midna isn't done loading - if (*step != cPhs_COMPLEATE_e) { - return; - } - - auto midna = mods::arg(args, 0); - midna->field_0x6e0 = g_currentMidnaHairColors.normalColor; - if (dKy_darkworld_check()) { - midna->field_0x6e8 = g_currentMidnaHairColors.normalKColor; - midna->field_0x6ec = g_currentMidnaHairColors.normalKColor2; - } else { - midna->field_0x6e8 = g_currentMidnaHairColors.lNormalKColor; - midna->field_0x6ec = g_currentMidnaHairColors.lNormalKColor2; - } -} - -// Override Midna Hair Color Part 2 -DEFINE_HOOK(&daMidna_c::setBodyPartMatrix, MidnaSetBodyPartMatrix); - -static GXColorS10 midnaField0x6e0{}; -static GXColor midnaField0x6e8{}; -static GXColor midnaField0x6ec{}; - -// Copy the original values before setBodyPartMatrix runs -HookAction midna_set_body_part_matrix_pre(ModContext*, void* args, void* retval, void* userdata) { - auto midna = mods::arg(args, 0); - - midnaField0x6e0 = midna->field_0x6e0; - midnaField0x6e8 = midna->field_0x6e8; - midnaField0x6ec = midna->field_0x6ec; - - return HOOK_CONTINUE; -} - -void midna_set_body_part_matrix_post(ModContext*, void* args, void* retval, void* userdata) { - auto midna = mods::arg(args, 0); - if (midna->mpHairhandBmd != NULL) { - // Restore the original values from before setBodyPartMatrix ran (this undoes the chase) - midna->field_0x6e0 = midnaField0x6e0; - midna->field_0x6e8 = midnaField0x6e8; - midna->field_0x6ec = midnaField0x6ec; - - // Statement copied from inside function to determine colors - bool bigColors = - midna->checkStateFlg0(daMidna_c::FLG0_UNK_10000000) || - midna->mBckHeap[2].getIdx() == daMidna_c::m_anmDataTable[daMidna_c::ANM_HAIR].mResID || - midna->mBckHeap[2].getIdx() == - daMidna_c::m_anmDataTable[daMidna_c::ANM_S_TAKES].mResID || - midna->mBckHeap[2].getIdx() == - daMidna_c::m_anmDataTable[daMidna_c::ANM_S_WAITS].mResID || - midna->mBckHeap[2].getIdx() == - daMidna_c::m_anmDataTable[daMidna_c::ANM_S_PACKAWAY].mResID || - midna->mBckHeap[2].getIdx() == - daMidna_c::m_anmDataTable[daMidna_c::ANM_GRABST].mResID || - midna->checkEndResetStateFlg0(daMidna_c::ERFLG0_UNK_40) || - dComIfGp_checkPlayerStatus1(0, 0x800000); - - GXColorS10 color{}; - GXColor kcolor1{}; - GXColor kcolor2{}; - - // Set our own colors - if (bigColors) { - kcolor1 = g_currentMidnaHairColors.bigKColor; - if (dKy_darkworld_check()) { - color = g_currentMidnaHairColors.bigColor; - kcolor2 = g_currentMidnaHairColors.normalKColor2; - } else { - color = g_currentMidnaHairColors.lBigColor; - kcolor2 = g_currentMidnaHairColors.lBigKColor2; - } - } else { - color = g_currentMidnaHairColors.normalColor; - if (dKy_darkworld_check()) { - kcolor1 = g_currentMidnaHairColors.normalKColor; - kcolor2 = g_currentMidnaHairColors.normalKColor2; - } else { - kcolor1 = g_currentMidnaHairColors.lNormalKColor; - kcolor2 = g_currentMidnaHairColors.lNormalKColor2; - } - } - - // Reapply the chase that happens in the function - cLib_chaseS(&midna->field_0x6e0.r, color.r, 10); - cLib_chaseS(&midna->field_0x6e0.g, color.g, 10); - cLib_chaseS(&midna->field_0x6e0.b, color.b, 10); - cLib_chaseUC(&midna->field_0x6e8.r, kcolor1.r, 10); - cLib_chaseUC(&midna->field_0x6e8.g, kcolor1.g, 10); - cLib_chaseUC(&midna->field_0x6e8.b, kcolor1.b, 10); - cLib_chaseUC(&midna->field_0x6ec.r, kcolor2.r, 10); - cLib_chaseUC(&midna->field_0x6ec.g, kcolor2.g, 10); - cLib_chaseUC(&midna->field_0x6ec.b, kcolor2.b, 10); - } -} - -// Midna Charge Ring Color -DEFINE_HOOK(&daAlink_c::setWolfLockDomeModel, SetWolfLockDomeModel); -void wolf_lock_dome_model_post(ModContext*, void*, void*, void*) { - auto domeRingColorStr = get_str_option(get_cvars().midnaChargeRingColor, ""); - if (is_valid_hex_color_str(domeRingColorStr)) { - auto domeRingColor = hex_color_str_to_gx_color(domeRingColorStr); - const u8 domeWave1RGBA[3] = {domeRingColor.r, domeRingColor.g, domeRingColor.b}; - const u8 domeWave2RGBA[3] = {domeRingColor.r, domeRingColor.g, domeRingColor.b}; - u8** chromaRegisterTable = - reinterpret_cast(&daAlink_getAlinkActorClass()->field_0x0724->mAnmCRegDataR); - - for (int i = 0; i < 3; i++) { - u8* currentTable = chromaRegisterTable[i]; - const u8 currentWave1Color = domeWave1RGBA[i]; - const u8 currentWave2Color = domeWave2RGBA[i]; - const u8 currentBaseColor = (currentWave1Color + currentWave2Color) / 2; - - currentTable[0x3] = currentBaseColor; // Set Alpha for the ring base - currentTable[0x13] = currentWave1Color; // Set Alpha for ring wave 1 - currentTable[0x23] = currentWave2Color; // Set Alpha for ring wave 2 - currentTable[0xB] = currentBaseColor; // Set Alpha for darkworld ring base - currentTable[0x1B] = currentWave1Color; // Set Alpha for darkworld ring wave 1 - currentTable[0x2B] = currentWave2Color; // Set Alpha for darkworld ring wave 2 - } - } -} - -#define ADD_POST_HOOK(defined_hook, function, original) \ - result = mods::hook::add_post(function); \ - if (result != MOD_OK) { \ - mods::log::debug( \ - "failed to add post hook to" #original ", Result {}", static_cast(result)); \ - return result; \ - } - -#define ADD_PRE_HOOK(defined_hook, function, original) \ - result = mods::hook::add_pre(function); \ - if (result != MOD_OK) { \ - mods::log::debug( \ - "failed to add pre hook to" #original ", Result {}", static_cast(result)); \ - return result; \ - } - -ModResult add_all_hooks() { - ModResult result{}; - - // Hooks for lantern glow - ADD_POST_HOOK(WolfEyeLightSet, wolf_eye_light_set_post, dKy_WolfEyeLight_set) - ADD_POST_HOOK(PreKandelaarDraw, pre_kandelaar_draw_post, daAlink_c::preKandelaarDraw) - - // Hooks for lantern meter color - ADD_PRE_HOOK(CPaneMgrSetBlackWhite, cpane_mgr_set_black_white_pre, CPaneMgr::setBlackWhite) - ADD_POST_HOOK( - KanteraIconSetNowGauge, kantera_icon_set_now_gauge_post, dKantera_icon_c::setNowGauge) - - // Hook for midna charge ring - ADD_POST_HOOK(SetWolfLockDomeModel, wolf_lock_dome_model_post, daAlink_c::setWolfLockDomeModel) - - // Hook for light sword glow - ADD_POST_HOOK(SetLightningSwordEffect, set_lightning_sword_effect_post, - daAlink_c::setLightningSwordEffect) - - // Hooks for Midna Hair Color - ADD_POST_HOOK(MidnaCreate, midna_create_post, daMidna_c::create) - ADD_PRE_HOOK( - MidnaSetBodyPartMatrix, midna_set_body_part_matrix_pre, daMidna_c::setBodyPartMatrix) - ADD_POST_HOOK( - MidnaSetBodyPartMatrix, midna_set_body_part_matrix_post, daMidna_c::setBodyPartMatrix) - - // Hooks for UI colors - ADD_POST_HOOK(dMeter2Init, d_meter_2_init_post, dMeter2Draw_c::init) - ADD_POST_HOOK(FileInfoSetHeartCount, file_info_set_heart_count_post, dFile_info_c::setHeartCnt) - ADD_POST_HOOK(ScreenInitButton, screen_init_button_post, dMeterButton_c::screenInitButton) - ADD_POST_HOOK(MenuSaveScreenSet, menu_save_screen_set_post, dMenu_save_c::screenSet) - ADD_POST_HOOK(FileSelectScreenSet, file_select_screen_set_post, dFile_select_c::screenSet) - ADD_POST_HOOK(BrightCheckScreenSet, bright_check_screen_set_post, dBrightCheck_c::screenSet) - ADD_POST_HOOK(MenuRingCreate, menu_ring_create_post, dMenu_Ring_c::_create) - ADD_POST_HOOK(MenuCollect2DCreate, menu_collect_2D_create_post, dMenu_Collect2D_c::_create) - ADD_POST_HOOK(MenuFishingScreenSetDoIcon, menu_fishing_screen_set_do_icon_post, - dMenu_Fishing_c::screenSetDoIcon) - ADD_POST_HOOK(MenuInsectScreenSetDoIcon, menu_insect_screen_set_do_icon_post, - dMenu_Insect_c::screenSetDoIcon) - ADD_POST_HOOK(MenuLetterScreenSetDoIcon, menu_letter_screen_set_do_icon_post, - dMenu_Letter_c::screenSetDoIcon) - ADD_POST_HOOK(MenuOptionCreate, menu_option_create_post, dMenu_Option_c::_create) - ADD_POST_HOOK(MenuSkillScreenSetDoIcon, menu_skill_screen_set_do_icon_post, - dMenu_Skill_c::screenSetDoIcon) - ADD_POST_HOOK(OutFontCreatePane, out_font_create_pane_post, COutFont_c::createPane) - ADD_POST_HOOK(OutFontSetDrawFont, out_font_set_draw_font_post, COutFontSet_c::drawFont) - ADD_POST_HOOK(MenuFMapCreate, menu_fmap_create_post, dMenu_Fmap_c::_create) - ADD_POST_HOOK(MenuDMapButtonIconScreenInit, menu_dmap_button_icon_screen_init_post, - dMenu_DmapBg_c::buttonIconScreenInit) - ADD_POST_HOOK( - MsgObjectTalkStartInit, msg_object_talk_start_init_post, dMsgObject_c::talkStartInit) - ADD_POST_HOOK(MeterHakushaCreate, meter_hakusha_create_post, dMeterHakusha_c::_create) - - // Heart Model Color - ADD_POST_HOOK( - ItemBaseCreateItemHeap, item_base_create_item_heap_post, daItemBase_c::CreateItemHeap) - - return MOD_OK; -} - -#define UNINSTALL_HOOK(defined_hook) mods::hook::uninstall(svc_hook); - -ModResult remove_all_hooks() { - UNINSTALL_HOOK(WolfEyeLightSet) - UNINSTALL_HOOK(PreKandelaarDraw) - UNINSTALL_HOOK(CPaneMgrSetBlackWhite) - UNINSTALL_HOOK(KanteraIconSetNowGauge) - UNINSTALL_HOOK(SetWolfLockDomeModel) - UNINSTALL_HOOK(SetLightningSwordEffect) - UNINSTALL_HOOK(MidnaCreate) - UNINSTALL_HOOK(MidnaSetBodyPartMatrix) - UNINSTALL_HOOK(MidnaSetBodyPartMatrix) - UNINSTALL_HOOK(dMeter2Init) - UNINSTALL_HOOK(FileInfoSetHeartCount) - UNINSTALL_HOOK(ScreenInitButton) - UNINSTALL_HOOK(MenuSaveScreenSet) - UNINSTALL_HOOK(FileSelectScreenSet) - UNINSTALL_HOOK(BrightCheckScreenSet) - UNINSTALL_HOOK(MenuRingCreate) - UNINSTALL_HOOK(MenuCollect2DCreate) - UNINSTALL_HOOK(MenuFishingScreenSetDoIcon) - UNINSTALL_HOOK(MenuInsectScreenSetDoIcon) - UNINSTALL_HOOK(MenuLetterScreenSetDoIcon) - UNINSTALL_HOOK(MenuOptionCreate) - UNINSTALL_HOOK(MenuSkillScreenSetDoIcon) - UNINSTALL_HOOK(OutFontCreatePane) - UNINSTALL_HOOK(OutFontSetDrawFont) - UNINSTALL_HOOK(MenuFMapCreate) - UNINSTALL_HOOK(MenuDMapButtonIconScreenInit) - UNINSTALL_HOOK(MsgObjectTalkStartInit) - UNINSTALL_HOOK(MeterHakushaCreate) - UNINSTALL_HOOK(ItemBaseCreateItemHeap) - return MOD_OK; -} diff --git a/mods/basic_cosmetics_mod/src/hooks.hpp b/mods/basic_cosmetics_mod/src/hooks.hpp deleted file mode 100644 index 62bb77cde9..0000000000 --- a/mods/basic_cosmetics_mod/src/hooks.hpp +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -#include "mods/service.hpp" - -ModResult add_all_hooks(); - -ModResult remove_all_hooks(); \ No newline at end of file diff --git a/mods/basic_cosmetics_mod/src/midna_hair_color.cpp b/mods/basic_cosmetics_mod/src/midna_hair_color.cpp deleted file mode 100644 index 905a4345ff..0000000000 --- a/mods/basic_cosmetics_mod/src/midna_hair_color.cpp +++ /dev/null @@ -1,115 +0,0 @@ -#include "midna_hair_color.hpp" -#include "mod.hpp" - -#include "mods/svc/log.hpp" - -#include - -namespace { -constexpr std::array, 10> kMidnaHairColors = {{ - // Default - {{ - /*l_lNormalKColor*/ /*l_normalKColor*/ /*l_normalColor*/ - {0xFF, 0xDC, 0x00}, {0xB4, 0x87, 0x00}, {0x50, 0x00, 0x00}, - /*l_bigKColor*/ /*l_lBigColor*/ /*l_bigColor*/ - {0x1E, 0x00, 0x00}, {0xFF, 0x78, 0x00}, {0xFF, 0x64, 0x78}, - /*l_lNormalKColor2*//*l_normalKColor2*/ /*l_lBigKColor2*/ - {0x00, 0xC3, 0xEB}, {0x00, 0xC3, 0xC3}, {0xAA, 0xFF, 0xC3} - }}, - // Pink - {{ - {0xF5, 0xCF, 0xF3}, {0xAD, 0x7F, 0x7F}, {0x1B, 0x00, 0x20}, - {0x3C, 0x02, 0x58}, {0xE3, 0x72, 0xF2}, {0xE3, 0x5F, 0xF8}, - {0xDD, 0x00, 0xEB}, {0xDD, 0x00, 0xC3}, {0xF6, 0x4C, 0xFF} - }}, - // Red - {{ - {0xE4, 0x65, 0x41}, {0xA1, 0x3E, 0x22}, {0x21, 0x00, 0x00}, - {0x4F, 0x02, 0x01}, {0xF0, 0x3A, 0x25}, {0xF0, 0x30, 0x8C}, - {0xEB, 0x00, 0x00}, {0xEB, 0x00, 0x00}, {0xFF, 0x4F, 0x3A} - }}, - // Yellow - {{ - {0x91, 0x83, 0x0E}, {0x66, 0x50, 0x07}, {0x0E, 0x0B, 0x00}, - {0x24, 0x25, 0x02}, {0xCB, 0xB7, 0x00}, {0xCB, 0x99, 0x78}, - {0xEB, 0xDE, 0x00}, {0xEB, 0xDE, 0x00}, {0xFF, 0xF8, 0xBF} - }}, - // Green - {{ - {0x35, 0x79, 0x53}, {0x25, 0x4A, 0x2B}, {0x00, 0x0E, 0x05}, - {0x0A, 0x29, 0x10}, {0x00, 0xB6, 0x6F}, {0x00, 0x98, 0xB3}, - {0x1F, 0xEB, 0x00}, {0x1F, 0xEB, 0x00}, {0x9A, 0xFF, 0x81} - }}, - // Blue - {{ - {0x00, 0x72, 0xFF}, {0x00, 0x46, 0x85}, {0x00, 0x08, 0x28}, - {0x16, 0x1B, 0x5D}, {0x00, 0x60, 0xFF}, {0x00, 0x50, 0xFF}, - {0x00, 0x48, 0xEB}, {0x00, 0x48, 0xC3}, {0x3A, 0x66, 0xFF} - }}, - // Purple - {{ - {0x6F, 0x34, 0xFF}, {0x4E, 0x20, 0x85}, {0x0D, 0x00, 0x34}, - {0x15, 0x08, 0x79}, {0x62, 0x00, 0xFF}, {0x62, 0x00, 0xFF}, - {0x7B, 0x00, 0xEB}, {0x7B, 0x00, 0xC3}, {0x94, 0x3E, 0xFF} - }}, - // Brown - {{ - {0x00, 0x00, 0x00}, {0x00, 0x00, 0x00}, {0x1A, 0x05, 0x00}, - {0x3C, 0x19, 0x0E}, {0x3F, 0x1D, 0x0B}, {0x3F, 0x18, 0x7E}, - {0x3F, 0x1D, 0x0B}, {0x3F, 0x1D, 0x09}, {0x59, 0x32, 0x1E} - }}, - // White - {{ - {0xF0, 0xF1, 0xF1}, {0xA9, 0x94, 0x7E}, {0x09, 0x0B, 0x0C}, - {0x22, 0x24, 0x24}, {0xFF, 0xFF, 0xFF}, {0xFF, 0xD5, 0xFF}, - {0xEA, 0xEA, 0xEA}, {0xEA, 0xEA, 0xC2}, {0xF3, 0xF3, 0xF3} - }}, - // Black - {{ - {0x00, 0x00, 0x00}, {0x00, 0x00, 0x00}, {0x0B, 0x0B, 0x0B}, - {0x23, 0x23, 0x23}, {0x00, 0x00, 0x00}, {0x00, 0x00, 0x78}, - {0x00, 0x00, 0x00}, {0x00, 0x00, 0x00}, {0x00, 0x00, 0x00} - }} -}}; - -size_t get_midna_hair_color_index(ConfigVarHandle handle) { - const auto optionIndex = get_int_option(handle, 0); - if (optionIndex < 0 || optionIndex >= static_cast(kMidnaHairColors.size())) { - return 0; - } - return static_cast(optionIndex); -} -} - -MidnaHairColors g_currentMidnaHairColors = { - .normalColor = {0x50, 0x00, 0x00, 0x00}, - .normalKColor = {0xB4, 0x87, 0x00, 0x00}, - .normalKColor2 = {0x00, 0xC3, 0xC3, 0x00}, - .bigColor = {0xFF, 0x64, 0x78, 0x00}, - .bigKColor = {0x1E, 0x00, 0x00, 0x00}, - .lNormalKColor = {0xFF, 0xDC, 0x00, 0x00}, - .lNormalKColor2 = {0x00, 0xC3, 0xEB, 0x00}, - .lBigColor = {0xFF, 0x78, 0x00, 0x00}, - .lBigKColor2 = {0xAA, 0xFF, 0xC3, 0x00}, -}; - -void set_all_midna_hair_colors() { - auto& g_cvars = get_cvars(); - auto hairBaseColor = get_midna_hair_color_index(g_cvars.midnaHairBaseColor); - auto hairTipsColor = get_midna_hair_color_index(g_cvars.midnaHairTipsColor); - - // Colors we have to convert to GXColorS10 - auto& normalColor = kMidnaHairColors.at(hairBaseColor)[2]; - auto& bigColor = kMidnaHairColors.at(hairBaseColor)[5]; - auto& lBigColor = kMidnaHairColors.at(hairBaseColor)[4]; - - g_currentMidnaHairColors.normalColor = GXColorS10{normalColor.r, normalColor.g, normalColor.b}; - g_currentMidnaHairColors.normalKColor = kMidnaHairColors.at(hairBaseColor)[1]; - g_currentMidnaHairColors.normalKColor2 = kMidnaHairColors.at(hairTipsColor)[7]; - g_currentMidnaHairColors.bigColor = GXColorS10{bigColor.r, bigColor.g, bigColor.b}; - g_currentMidnaHairColors.bigKColor = kMidnaHairColors.at(hairBaseColor)[3]; - g_currentMidnaHairColors.lNormalKColor = kMidnaHairColors.at(hairBaseColor)[0]; - g_currentMidnaHairColors.lNormalKColor2 = kMidnaHairColors.at(hairTipsColor)[6]; - g_currentMidnaHairColors.lBigColor = GXColorS10{lBigColor.r, lBigColor.g, lBigColor.b}; - g_currentMidnaHairColors.lBigKColor2 = kMidnaHairColors.at(hairTipsColor)[8]; -} diff --git a/mods/basic_cosmetics_mod/src/midna_hair_color.hpp b/mods/basic_cosmetics_mod/src/midna_hair_color.hpp deleted file mode 100644 index ad08f7c8c6..0000000000 --- a/mods/basic_cosmetics_mod/src/midna_hair_color.hpp +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once - -#include - -struct MidnaHairColors { - GXColorS10 normalColor; - GXColor normalKColor; - GXColor normalKColor2; - GXColorS10 bigColor; - GXColor bigKColor; - GXColor lNormalKColor; - GXColor lNormalKColor2; - GXColorS10 lBigColor; - GXColor lBigKColor2; -}; - -extern MidnaHairColors g_currentMidnaHairColors; - -void set_all_midna_hair_colors(); diff --git a/mods/basic_cosmetics_mod/src/mod.cpp b/mods/basic_cosmetics_mod/src/mod.cpp deleted file mode 100644 index 89a321da9c..0000000000 --- a/mods/basic_cosmetics_mod/src/mod.cpp +++ /dev/null @@ -1,653 +0,0 @@ -#include "mod.hpp" -#include "color_utils.hpp" -#include "hooks.hpp" -#include "midna_hair_color.hpp" -#include "texture_utils.hpp" - -#include "mods/service.hpp" -#include "mods/svc/config.h" -#include "mods/svc/hook.hpp" -#include "mods/svc/log.h" -#include "mods/svc/log.hpp" -#include "mods/svc/ui.h" - -#include "d/d_com_inf_game.h" - -#include - -#include -#include -#include - -DEFINE_MOD(); -IMPORT_SERVICE(LogService, svc_log); -IMPORT_SERVICE(ConfigService, svc_config); -IMPORT_SERVICE(HookService, svc_hook); -IMPORT_SERVICE(TextureService, svc_texture); -IMPORT_SERVICE(UiService, svc_ui); - -static cvars g_cvars; - -cvars& get_cvars() { - return g_cvars; -} - -std::string get_str_option(ConfigVarHandle handle, const std::string& fallback) { - std::string value{}; - size_t outLength{}; - svc_config->get_string(mod_ctx, handle, NULL, 0, &outLength); - value.resize(outLength); - if (handle == 0 || svc_config->get_string( - mod_ctx, handle, value.data(), value.size() + 1, &outLength) != MOD_OK) - { - return fallback; - } - return value; -} - -int64_t get_int_option(ConfigVarHandle handle, int64_t fallback) { - int64_t value = fallback; - if (handle == 0 || svc_config->get_int(mod_ctx, handle, &value) != MOD_OK) { - return fallback; - } - return value; -} - -// Helper for getting configVar color -std::optional get_config_var_color(ConfigVarHandle handle, bool allowRainbow) { - auto colorStr = get_str_option(handle, ""); - // Convert to lowercase - for (auto& c : colorStr) { - c = static_cast(std::tolower(static_cast(c))); - } - if (colorStr == "rainbow" && allowRainbow) { - auto color = get_rainbow_rgb(127.5f); - color.r /= 2; - color.g /= 2; - color.b /= 2; - return color; - } - if (is_valid_hex_color_str(colorStr)) { - return hex_color_str_to_gx_color(colorStr); - } - return std::nullopt; -} - -namespace { -UiWindowHandle g_cosmeticsWindow = 0; -bool g_loadedAllBaseTextures = false; -constexpr uint32_t kTextureLoadRetryFrames = 30; -uint32_t g_textureLoadRetryCountdown = 0; - -constexpr const char* kOverlayPresets[] = { - "ab706e", - "6382a0", - "94749a", - "ec8644", - "b9ab00", - "ec9fc8", - "505154", - "f8f7f4", - "91723e", -}; - -constexpr const char* kLightPresets[] = { - "ff0000", - "f68821", - "f6f321", - "00ff00", - "0000ff", - "8000ff", - "a0a0a0", - "30d0d0", -}; - -constexpr const char* kRainbowLightPresets[] = { - "rainbow", - "ff0000", - "f68821", - "f6f321", - "00ff00", - "0000ff", - "8000ff", - "a0a0a0", -}; - -constexpr const char* kAButtonPresets[] = { - "ff0000", - "ff5000", - "ffaf00", - "0080ff", - "0000ff", - "8000ff", - "5555ff", - "ff20ff", -}; - -constexpr const char* kBButtonPresets[] = { - "ffff40", - "ffa0ff", - "00e87b", - "00aaff", - "6078ff", - "000000", - "00f3ff", -}; - -constexpr const char* kXyButtonPresets[] = { - "ff0000", - "ff8200", - "f7df00", - "70ff00", - "00bd11", - "0000ff", - "800088", - "000000", - "ff00aa", - "00ffff", -}; - -constexpr const char* kZButtonPresets[] = { - "ff0000", - "ff8200", - "f7df00", - "70ff00", - "00bd11", - "800088", - "000000", - "00ffff", -}; - -constexpr const char* kChargeRingPresets[] = { - "ff9f9f", - "ff0000", - "ffff00", - "00ff00", - "0000ff", - "ff00ff", - "331900", - "feffff", - "000000", -}; - -ModResult register_str_option( - const char* name, const char* defaultValue, ConfigVarHandle& outHandle, ModError* error) { - ConfigVarDesc cvarDesc = CONFIG_VAR_DESC_INIT; - cvarDesc.name = name; - cvarDesc.type = CONFIG_VAR_STRING; - cvarDesc.default_string = defaultValue; - if (svc_config->register_var(mod_ctx, &cvarDesc, &outHandle) != MOD_OK) { - return mods::set_error(error, MOD_ERROR, "failed to register cosmetics option"); - } - return MOD_OK; -} - -ModResult register_int_option( - const char* name, int64_t defaultValue, ConfigVarHandle& outHandle, ModError* error) { - ConfigVarDesc cvarDesc = CONFIG_VAR_DESC_INIT; - cvarDesc.name = name; - cvarDesc.type = CONFIG_VAR_INT; - cvarDesc.default_int = defaultValue; - if (svc_config->register_var(mod_ctx, &cvarDesc, &outHandle) != MOD_OK) { - return mods::set_error(error, MOD_ERROR, "failed to register cosmetics option"); - } - return MOD_OK; -} - -void add_control(UiElementHandle pane, const UiControlDesc& desc) { - auto result = svc_ui->pane_add_control(mod_ctx, pane, &desc, nullptr); - if (result != MOD_OK) { - mods::log::debug("pane_add_control failed {}", static_cast(result)); - } -} - -template -void add_cosmetic_option( - UiElementHandle pane, ConfigVarHandle cvar, const char* name, const char* const (&presets)[N]) { - UiControlDesc control = UI_CONTROL_DESC_INIT; - control.kind = UI_CONTROL_COLOR; - control.label = name; - control.binding = UI_BINDING_CONFIG_VAR; - control.config_var = cvar; - control.color_presets = presets; - control.color_preset_count = N; - add_control(pane, control); -} - -void add_midna_hair_option(UiElementHandle left, ConfigVarHandle cvar, const std::string& name) { - static const char* kMidnaHairOptions[] = { - "Default", "Pink", "Red", "Yellow", "Green", "Blue", "Purple", "Brown", "White", "Black"}; - UiControlDesc control = UI_CONTROL_DESC_INIT; - control.kind = UI_CONTROL_SELECT; - control.label = name.c_str(); - control.help_rml = "Choose Midna's hair color."; - control.binding = UI_BINDING_CONFIG_VAR; - control.config_var = cvar; - control.options = kMidnaHairOptions; - control.option_count = 10; - add_control(left, control); -} - -void add_group( - UiElementHandle groups, UiElementHandle colors, const char* label, UiGroupBuildFn build) { - UiGroupDesc group = UI_GROUP_DESC_INIT; - group.label = label; - group.build = build; - const auto result = svc_ui->pane_add_group(mod_ctx, groups, colors, &group, nullptr); - if (result != MOD_OK) { - mods::log::debug("pane_add_group failed {}", static_cast(result)); - } -} - -ModResult build_hero_tunic_colors(ModContext*, UiElementHandle pane, void*, ModError*) { - svc_ui->pane_add_section(mod_ctx, pane, "Hero's Tunic"); - add_cosmetic_option(pane, g_cvars.herosTunicCapColor, "Cap", kOverlayPresets); - add_cosmetic_option(pane, g_cvars.herosTunicTorsoColor, "Body", kOverlayPresets); - add_cosmetic_option(pane, g_cvars.herosTunicSkirtColor, "Skirt", kOverlayPresets); - return MOD_OK; -} - -ModResult build_zora_armor_colors(ModContext*, UiElementHandle pane, void*, ModError*) { - svc_ui->pane_add_section(mod_ctx, pane, "Zora Armor"); - add_cosmetic_option(pane, g_cvars.zoraArmorCapColor, "Cap", kOverlayPresets); - add_cosmetic_option(pane, g_cvars.zoraArmorHelmetColor, "Helmet", kOverlayPresets); - add_cosmetic_option(pane, g_cvars.zoraArmorTorsoColor, "Torso", kOverlayPresets); - add_cosmetic_option(pane, g_cvars.zoraArmorScalesColor, "Scales", kOverlayPresets); - add_cosmetic_option(pane, g_cvars.zoraArmorFlippersColor, "Flippers", kOverlayPresets); - return MOD_OK; -} - -ModResult build_sword_colors(ModContext*, UiElementHandle pane, void*, ModError*) { - svc_ui->pane_add_section(mod_ctx, pane, "Swords"); - add_cosmetic_option(pane, g_cvars.woodenSwordColor, "Wooden Sword", kOverlayPresets); - add_cosmetic_option(pane, g_cvars.ordonSwordBladeColor, "Ordon Blade", kLightPresets); - add_cosmetic_option(pane, g_cvars.ordonSwordHandleColor, "Ordon Handle", kLightPresets); - add_cosmetic_option(pane, g_cvars.msBladeColor, "Master Sword Blade", kLightPresets); - add_cosmetic_option(pane, g_cvars.msHandleColor, "Master Sword Handle", kLightPresets); - add_cosmetic_option( - pane, g_cvars.lightSwordGlowColor, "Light Sword Glow", kRainbowLightPresets); - return MOD_OK; -} - -ModResult build_equipment_colors(ModContext*, UiElementHandle pane, void*, ModError*) { - svc_ui->pane_add_section(mod_ctx, pane, "Equipment"); - add_cosmetic_option(pane, g_cvars.lanternGlowColor, "Lantern Glow", kRainbowLightPresets); - add_cosmetic_option(pane, g_cvars.boomerangColor, "Gale Boomerang", kOverlayPresets); - add_cosmetic_option(pane, g_cvars.ironBootsColor, "Iron Boots", kOverlayPresets); - add_cosmetic_option(pane, g_cvars.spinnerColor, "Spinner", kOverlayPresets); - return MOD_OK; -} - -ModResult build_button_colors(ModContext*, UiElementHandle pane, void*, ModError*) { - svc_ui->pane_add_section(mod_ctx, pane, "Buttons"); - add_cosmetic_option(pane, g_cvars.aButtonColor, "A Button", kAButtonPresets); - add_cosmetic_option(pane, g_cvars.bButtonColor, "B Button", kBButtonPresets); - add_cosmetic_option(pane, g_cvars.xButtonColor, "X Button", kXyButtonPresets); - add_cosmetic_option(pane, g_cvars.yButtonColor, "Y Button", kXyButtonPresets); - add_cosmetic_option(pane, g_cvars.zButtonColor, "Z Button", kZButtonPresets); - return MOD_OK; -} - -ModResult build_hud_colors(ModContext*, UiElementHandle pane, void*, ModError*) { - svc_ui->pane_add_section(mod_ctx, pane, "HUD"); - add_cosmetic_option(pane, g_cvars.heartColor, "Hearts", kBButtonPresets); - return MOD_OK; -} - -ModResult build_link_colors(ModContext*, UiElementHandle pane, void*, ModError*) { - svc_ui->pane_add_section(mod_ctx, pane, "Link"); - add_cosmetic_option(pane, g_cvars.linkHairColor, "Hair", kOverlayPresets); - return MOD_OK; -} - -ModResult build_midna_colors(ModContext*, UiElementHandle pane, void*, ModError*) { - svc_ui->pane_add_section(mod_ctx, pane, "Midna"); - add_cosmetic_option(pane, g_cvars.midnaChargeRingColor, "Charge Ring", kChargeRingPresets); - return MOD_OK; -} - -ModResult build_companion_colors(ModContext*, UiElementHandle pane, void*, ModError*) { - svc_ui->pane_add_section(mod_ctx, pane, "Companions"); - add_cosmetic_option(pane, g_cvars.wolfLinkColor, "Wolf Link", kOverlayPresets); - add_cosmetic_option(pane, g_cvars.eponaColor, "Epona", kOverlayPresets); - return MOD_OK; -} - -ModResult build_equipment_colors_tab( - ModContext*, UiWindowHandle, UiElementHandle left, UiElementHandle right, void*, ModError*) { - svc_ui->pane_add_section(mod_ctx, left, "Color Groups"); - add_group(left, right, "Hero's Tunic", build_hero_tunic_colors); - add_group(left, right, "Zora Armor", build_zora_armor_colors); - add_group(left, right, "Swords", build_sword_colors); - add_group(left, right, "Equipment", build_equipment_colors); - - return MOD_OK; -} - -ModResult build_ui_colors_tab( - ModContext*, UiWindowHandle, UiElementHandle left, UiElementHandle right, void*, ModError*) { - svc_ui->pane_add_section(mod_ctx, left, "Color Groups"); - add_group(left, right, "Buttons", build_button_colors); - add_group(left, right, "HUD", build_hud_colors); - - return MOD_OK; -} - -ModResult build_misc_colors_tab( - ModContext*, UiWindowHandle, UiElementHandle left, UiElementHandle right, void*, ModError*) { - svc_ui->pane_add_section(mod_ctx, left, "Hair Presets"); - add_midna_hair_option(left, g_cvars.midnaHairBaseColor, "Midna's Hair Base Color"); - add_midna_hair_option(left, g_cvars.midnaHairTipsColor, "Midna's Hair Tips Color"); - svc_ui->pane_add_section(mod_ctx, left, "Color Groups"); - add_group(left, right, "Link", build_link_colors); - add_group(left, right, "Midna", build_midna_colors); - add_group(left, right, "Companions", build_companion_colors); - - return MOD_OK; -} - -void on_cosmetics_menu_window_closed(ModContext*, UiWindowHandle, void*) { - g_cosmeticsWindow = 0; -} - -void on_open_cosmetics_menu(ModContext*, void*) { - if (g_cosmeticsWindow != 0) { - return; - } - UiTabDesc tabs[] = {UI_TAB_DESC_INIT, UI_TAB_DESC_INIT, UI_TAB_DESC_INIT}; - tabs[0].title = "Equipment"; - tabs[0].build = build_equipment_colors_tab; - tabs[1].title = "Interface"; - tabs[1].build = build_ui_colors_tab; - tabs[2].title = "Characters"; - tabs[2].build = build_misc_colors_tab; - UiWindowDesc desc = UI_WINDOW_DESC_INIT; - desc.tabs = tabs; - desc.tab_count = ARRAY_SIZE(tabs); - desc.on_closed = on_cosmetics_menu_window_closed; - if (svc_ui->window_push(mod_ctx, &desc, &g_cosmeticsWindow) != MOD_OK) { - svc_log->error(mod_ctx, "failed to open basic cosmetics window"); - } -} - -ModResult build_panel(ModContext*, UiElementHandle panel, void*, ModError*) { - UiControlDesc control = UI_CONTROL_DESC_INIT; - control.kind = UI_CONTROL_GROUP; - control.label = "Open Cosmetics Menu"; - control.on_pressed = on_open_cosmetics_menu; - add_control(panel, control); - return MOD_OK; -} -} // namespace - -void load_base_texture_data() { - // Go through each texture we can recolor and attempt to load and store - // the texture data for future recoloring - for (auto& replacements : get_texture_replacements() | std::views::values) { - for (auto& replacement : replacements) { - // If we've already loaded the texture data, don't load it again - if (replacement.loadedTextureData) { - continue; - } - - // Avoid noisy resource lookups until the archive has finished loading. - auto* resInfo = dComIfG_getObjectResInfo(replacement.arc); - if (resInfo == nullptr || resInfo->getArchive() == nullptr) { - continue; - } - - // Try to get the model this texture is part of. If we can't get it, try again later - auto model = static_cast( - dComIfG_getObjectRes(replacement.arc, replacement.modelFileName)); - if (model == nullptr) { - continue; - } - - J3DTexture* tex = model->getTexture(); - JUTNameTab* nametable = model->getTextureName(); - if (tex != nullptr && nametable != nullptr) { - for (u16 i = 0; i < tex->getNum(); i++) { - const char* texName = nametable->getName(i); - if (texName != nullptr && std::strcmp(texName, replacement.textureName) == 0) { - // Once we've found the texture, set all our TextureKey and TextureData - // fields that we can set right now. - auto imageHeader = tex->getResTIMG(i); - auto& key = replacement.key; - auto& data = replacement.data; - key.kind = TEXTURE_KEY_SOURCE; - key.has_tlut = imageHeader->numColors > 0; - key.width = imageHeader->width; - key.height = imageHeader->height; - key.gx_format = imageHeader->format; - - // Currently, no replaced textures have a tlut - key.tlut_hash = replacement.tlutHash; - - // Calculate the size of the image data - const uint32_t mipCount = - imageHeader->mipmapEnabled ? - std::max(imageHeader->mipmapCount, 1) : - 1; - auto size = get_image_data_size( - imageHeader->format, imageHeader->width, imageHeader->height, mipCount); - replacement.baseTextureData.resize(size); - std::memcpy( - replacement.baseTextureData.data(), tex->getImgDataPtr(i), size); - - // Source texture keys hash only the base mip level. - const auto baseMipSize = get_image_data_size( - imageHeader->format, imageHeader->width, imageHeader->height, 1); - auto textureHash = - XXH64(replacement.baseTextureData.data(), baseMipSize, 0); - replacement.key.texture_hash = textureHash; - - mods::log::debug("Loaded base texture data for {}. size: {:X} hash: {:X}", - replacement.textureName, size, textureHash); - replacement.loadedTextureData = true; - - data.width = imageHeader->width; - data.height = imageHeader->height; - data.mip_count = mipCount; - data.size = size; - data.gx_format = imageHeader->format; - - break; - } - } - } - } - } - - // If we've loaded all base textures for recoloring, then we don't need to call this function - // again - g_loadedAllBaseTextures = std::ranges::all_of( - get_texture_replacements() | std::views::values, [](auto& replacementList) { - return std::ranges::all_of( - replacementList, [](const TextureReplacementData& replacement) { - return replacement.loadedTextureData; - }); - }); -} - -ModResult check_and_set_recolored_textures() { - for (auto& [configVar, replacements] : get_texture_replacements()) { - // If the configvar hasn't been set, don't continue - if (configVar == 0) { - continue; - } - - auto maybeColor = get_config_var_color(configVar); - if (!maybeColor.has_value()) { - // An empty or invalid option means the original texture should be restored. - for (auto& replacement : replacements) { - if (replacement.handle != 0) { - auto result = svc_texture->unregister(mod_ctx, replacement.handle); - if (result != MOD_OK) { - mods::log::debug("Could not unregister replacement for {}. Result: {}", - replacement.textureName, static_cast(result)); - } - replacement.handle = 0; - } - replacement.curColor.reset(); - } - continue; - } - - auto color = maybeColor.value(); - for (auto& replacement : replacements) { - // If we haven't loaded the base texture yet, don't try to recolor it - if (!replacement.loadedTextureData) { - continue; - } - - // If our color hasn't changed, don't try to recolor - auto& curColor = replacement.curColor; - if (curColor == std::nullopt || curColor.value().r != color.r || - curColor.value().g != color.g || curColor.value().b != color.b) - { - // Make a copy of the base texture data to recolor - auto newTexture = replacement.baseTextureData; - recolor_texture(replacement, color, newTexture); - - TextureData newTextureData = replacement.data; - newTextureData.data = newTexture.data(); - newTextureData.size = newTexture.size(); - - // Keep the current replacement active if registering the new one fails. - TextureReplacementHandle newHandle{}; - auto result = svc_texture->register_data( - mod_ctx, &replacement.key, &newTextureData, &newHandle); - if (result != MOD_OK) { - mods::log::debug("Could not register_data for {}. Result: {}", - replacement.textureName, static_cast(result)); - } else { - mods::log::debug("Registered replacement for {}.", replacement.textureName, - static_cast(result)); - auto oldHandle = replacement.handle; - replacement.handle = newHandle; - curColor = color; - - if (oldHandle != 0) { - result = svc_texture->unregister(mod_ctx, oldHandle); - if (result != MOD_OK) { - mods::log::debug( - "Could not unregister previous replacement for {}. Result: {}", - replacement.textureName, static_cast(result)); - } - } - } - } - } - } - - return MOD_OK; -} - -void unregister_all_texture_handles() { - for (auto& replacements : get_texture_replacements() | std::views::values) { - for (auto& replacement : replacements) { - if (replacement.handle != 0) { - svc_texture->unregister(mod_ctx, replacement.handle); - replacement.handle = 0; - } - replacement.curColor.reset(); - } - } -} - -#define REGISTER_COSMETIC_OPTION(option) \ - result = register_str_option(#option, NULL, g_cvars.option, error); \ - if (result != MOD_OK) { \ - return result; \ - } - -extern "C" { -MOD_EXPORT ModResult mod_initialize(ModError* error) { - svc_log->info(mod_ctx, "basic_cosmetics_mod initialized"); - - ModResult result{}; - - REGISTER_COSMETIC_OPTION(herosTunicCapColor) - REGISTER_COSMETIC_OPTION(herosTunicTorsoColor) - REGISTER_COSMETIC_OPTION(herosTunicSkirtColor) - REGISTER_COSMETIC_OPTION(zoraArmorCapColor) - REGISTER_COSMETIC_OPTION(zoraArmorHelmetColor) - REGISTER_COSMETIC_OPTION(zoraArmorTorsoColor) - REGISTER_COSMETIC_OPTION(zoraArmorScalesColor) - REGISTER_COSMETIC_OPTION(zoraArmorFlippersColor) - REGISTER_COSMETIC_OPTION(lanternGlowColor) - REGISTER_COSMETIC_OPTION(woodenSwordColor) - REGISTER_COSMETIC_OPTION(ordonSwordBladeColor) - REGISTER_COSMETIC_OPTION(ordonSwordHandleColor) - REGISTER_COSMETIC_OPTION(msBladeColor) - REGISTER_COSMETIC_OPTION(msHandleColor) - REGISTER_COSMETIC_OPTION(lightSwordGlowColor) - REGISTER_COSMETIC_OPTION(boomerangColor) - REGISTER_COSMETIC_OPTION(ironBootsColor) - REGISTER_COSMETIC_OPTION(spinnerColor) - REGISTER_COSMETIC_OPTION(aButtonColor) - REGISTER_COSMETIC_OPTION(bButtonColor) - REGISTER_COSMETIC_OPTION(xButtonColor) - REGISTER_COSMETIC_OPTION(yButtonColor) - REGISTER_COSMETIC_OPTION(zButtonColor) - REGISTER_COSMETIC_OPTION(heartColor) - - result = register_int_option("midnaHairBaseColor", 0, g_cvars.midnaHairBaseColor, error); - if (result != MOD_OK) { - return result; - } - - result = register_int_option("midnaHairTipsColor", 0, g_cvars.midnaHairTipsColor, error); - if (result != MOD_OK) { - return result; - } - - REGISTER_COSMETIC_OPTION(midnaChargeRingColor) - REGISTER_COSMETIC_OPTION(linkHairColor) - REGISTER_COSMETIC_OPTION(wolfLinkColor) - REGISTER_COSMETIC_OPTION(eponaColor) - - UiModsPanelDesc panelDesc = UI_MODS_PANEL_DESC_INIT; - panelDesc.build = build_panel; - svc_ui->register_mods_panel(mod_ctx, &panelDesc); - - // Add all our hooks - result = add_all_hooks(); - if (result != MOD_OK) { - return result; - } - - g_loadedAllBaseTextures = false; - g_textureLoadRetryCountdown = 0; - return MOD_OK; -} - -MOD_EXPORT ModResult mod_update(ModError*) { - update_rainbow_rgb(1.0f); - set_all_midna_hair_colors(); - - if (!g_loadedAllBaseTextures) { - if (g_textureLoadRetryCountdown == 0) { - load_base_texture_data(); - g_textureLoadRetryCountdown = kTextureLoadRetryFrames; - } else { - --g_textureLoadRetryCountdown; - } - } - - check_and_set_recolored_textures(); - - return MOD_OK; -} - -MOD_EXPORT ModResult mod_shutdown(ModError*) { - svc_log->info(mod_ctx, "basic_cosmetics_mod unloaded"); - g_cosmeticsWindow = 0; - remove_all_hooks(); - unregister_all_texture_handles(); - get_texture_replacements().clear(); - g_loadedAllBaseTextures = false; - g_textureLoadRetryCountdown = 0; - return MOD_OK; -} -} diff --git a/mods/basic_cosmetics_mod/src/mod.hpp b/mods/basic_cosmetics_mod/src/mod.hpp deleted file mode 100644 index 6d77949b03..0000000000 --- a/mods/basic_cosmetics_mod/src/mod.hpp +++ /dev/null @@ -1,65 +0,0 @@ -#pragma once - -#include "mods/svc/config.h" -#include "mods/svc/texture.h" - -#include - -#include -#include -#include - -struct cvars { - ConfigVarHandle herosTunicCapColor = 0; - ConfigVarHandle herosTunicTorsoColor = 0; - ConfigVarHandle herosTunicSkirtColor = 0; - ConfigVarHandle zoraArmorCapColor = 0; - ConfigVarHandle zoraArmorHelmetColor = 0; - ConfigVarHandle zoraArmorTorsoColor = 0; - ConfigVarHandle zoraArmorScalesColor = 0; - ConfigVarHandle zoraArmorFlippersColor = 0; - ConfigVarHandle lanternGlowColor = 0; - ConfigVarHandle woodenSwordColor = 0; - ConfigVarHandle ordonSwordBladeColor = 0; - ConfigVarHandle ordonSwordHandleColor = 0; - ConfigVarHandle msBladeColor = 0; - ConfigVarHandle msHandleColor = 0; - ConfigVarHandle lightSwordGlowColor = 0; - ConfigVarHandle boomerangColor = 0; - ConfigVarHandle ironBootsColor = 0; - ConfigVarHandle spinnerColor = 0; - ConfigVarHandle heartColor = 0; - ConfigVarHandle aButtonColor = 0; - ConfigVarHandle bButtonColor = 0; - ConfigVarHandle xButtonColor = 0; - ConfigVarHandle yButtonColor = 0; - ConfigVarHandle zButtonColor = 0; - ConfigVarHandle midnaHairBaseColor = 0; - ConfigVarHandle midnaHairTipsColor = 0; - ConfigVarHandle midnaChargeRingColor = 0; - ConfigVarHandle linkHairColor = 0; - ConfigVarHandle wolfLinkColor = 0; - ConfigVarHandle eponaColor = 0; -}; - -struct TextureReplacementData { - const char* arc{}; - const char* modelFileName{}; - const char* textureName{}; - uint64_t textureHash{}; - uint64_t tlutHash{}; - TextureKey key = TEXTURE_KEY_INIT; - TextureData data = TEXTURE_DATA_INIT; - TextureReplacementHandle handle{}; - std::vector baseTextureData{}; - bool loadedTextureData = false; - std::optional curColor{}; -}; - -cvars& get_cvars(); - -std::string get_str_option(ConfigVarHandle handle, const std::string& fallback); - -int64_t get_int_option(ConfigVarHandle handle, int64_t fallback); - -std::optional get_config_var_color(ConfigVarHandle handle, bool allowRainbow = false); \ No newline at end of file diff --git a/mods/basic_cosmetics_mod/src/texture_utils.cpp b/mods/basic_cosmetics_mod/src/texture_utils.cpp deleted file mode 100644 index 98c60dbad3..0000000000 --- a/mods/basic_cosmetics_mod/src/texture_utils.cpp +++ /dev/null @@ -1,682 +0,0 @@ -#include "texture_utils.hpp" -#include "color_utils.hpp" -#include "mod.hpp" - -#include "mods/svc/config.h" -#include "mods/svc/log.hpp" - -#include "JSystem/J3DGraphLoader/J3DModelLoader.h" -#include "JSystem/JSupport/JSupport.h" -#include "JSystem/JUtility/JUTNameTab.h" -#include "d/actor/d_a_alink.h" - -static void get_gx_tile_info(uint8_t format, uint32_t& tileWidth, uint32_t& tileHeight, uint32_t& tileSize) { - switch (format) { - case GX_TF_I8: - case GX_TF_IA4: - case GX_TF_C8: - tileWidth = 8; tileHeight = 4; tileSize = 32; - break; - case GX_TF_IA8: - case GX_TF_RGB565: - case GX_TF_RGB5A3: - case GX_TF_C14X2: - tileWidth = 4; tileHeight = 4; tileSize = 32; - break; - case GX_TF_RGBA8: - tileWidth = 4; tileHeight = 4; tileSize = 64; - break; - case GX_TF_I4: - case GX_TF_C4: - case GX_TF_CMPR: - default: - tileWidth = 8; tileHeight = 8; tileSize = 32; - break; - } -} - -uint32_t get_image_data_size(uint32_t format, uint32_t width, uint32_t height, uint32_t mipmapCount) { - - uint32_t tileWidth, tileHeight, tileSize; - get_gx_tile_info(format, tileWidth, tileHeight, tileSize); - - uint32_t totalSize = 0; - - for (uint8_t i = 0; i < mipmapCount; ++i) { - // Round dimensions up to nearest tile boundary - uint32_t paddedWidth = (width + tileWidth - 1) & ~(tileWidth - 1); - uint32_t paddedHeight = (height + tileHeight - 1) & ~(tileHeight - 1); - - uint32_t tilesX = paddedWidth / tileWidth; - uint32_t tilesY = paddedHeight / tileHeight; - - totalSize += tilesX * tilesY * tileSize; - - // Downscale dimensions for next mipmap level - width = std::max(1u, width >> 1); - height = std::max(1u, height >> 1); - } - - return totalSize; -} - -// When left is greater than right -// 0b00 points to the left color -// 0b01 points to the right color -// 0b10 is closer to left color -// 0b11 is closer to right color - -// When left is not greater than right -// 0b00 points to the left color -// 0b01 points to the right color -// 0b10 is midway between the colors -// 0b11 is transparent - -// That means when maintaining the relative order, if we have to swap the colors: - -// in the case of left being greater than right: -// 0b00 will swap to 0b01 -// 0b01 will swap to 0b00 -// 0b10 will swap to 0b11 -// 0b11 will swap to 0b10 -// So the left bit stays the same, and the right bit changes -// Can do xor (^) like 0b01010101 or 0x55 for each u16 - -// in the case of left not being greater than right: -// 0b00 will swap to 0b01 -// 0b01 will swap to 0b00 -// 0b10 will stay the same -// 0b11 will stay the same -// so if the left bit is a 0, the right bit will change -uint32_t swap_index_bits(bool leftIsGreater, uint32_t bits) { - if (leftIsGreater) { - return bits ^ 0x55555555; - } - - const uint32_t mask = ((bits >> 1) & 0x55555555) ^ 0x55555555; - return bits ^ mask; -} - -void recolor_cmpr_texture(const TextureReplacementData& replacementData, const GXColor color, std::vector& newTextureDataOut) -{ - uint16_t recolors[0x100]; - for (int32_t i = 0; i < 0x100; i++) { - recolors[i] = blend_overlay_rgb_565(i, color); - } - - const uint8_t mipCount = (replacementData.data.mip_count > 0) ? replacementData.data.mip_count : 1; - uint32_t mipWidth = replacementData.key.width; - uint32_t mipHeight = replacementData.key.height; - - uint8_t* currentAddr = newTextureDataOut.data(); - - for (uint8_t mip = 0; mip < mipCount; ++mip) { - // Round dimensions up to the nearest 8x8 tile boundary - const uint32_t roundedWidth = (mipWidth + 7) & ~7; - const uint32_t roundedHeight = (mipHeight + 7) & ~7; - - const uint32_t numBlocks = (roundedWidth / 8) * (roundedHeight / 8); - const uint32_t iterations = numBlocks * 4; // 4 CMPR sub-blocks per 8x8 tile - - for (uint32_t i = 0; i < iterations; i++) { - auto* rgb565Ptr = reinterpret_cast*>(currentAddr); - - auto leftRgb565 = rgb565Ptr[0]; - auto rightRgb565 = rgb565Ptr[1]; - const bool leftIsGreater = leftRgb565 > rightRgb565; - - const uint32_t leftGrayVal = desaturate_rgb_565(leftRgb565); - const uint32_t rightGrayVal = desaturate_rgb_565(rightRgb565); - - uint16_t leftNewRgb565 = recolors[leftGrayVal]; - uint16_t rightNewRgb565 = recolors[rightGrayVal]; - - bool needsBitSwap = false; - - if (leftIsGreater) { - if (leftNewRgb565 == rightNewRgb565) { - // Need to make sure that subtracting 1 does not mess - // everything up. For example, 0x1000 - 1 => 0x0fff which is - // a completely different color. - if ((leftNewRgb565 & 0x1f) == 0) { - // If left value has 0 blue, we change its blue to 1. - leftNewRgb565 += 1; - } - rightNewRgb565 = leftNewRgb565 - 1; - } - else if (leftNewRgb565 < rightNewRgb565) { - needsBitSwap = true; - } - } - else if (leftNewRgb565 > rightNewRgb565) { - needsBitSwap = true; - } - - if (needsBitSwap) { - // The left and right colors are swapping so that their values - // are relative in the same way. We need to update the bits - // referencing the palette entries to handle the swap. - const uint16_t temp = leftNewRgb565; - leftNewRgb565 = rightNewRgb565; - rightNewRgb565 = temp; - - auto wordPtr = reinterpret_cast*>(currentAddr); - const uint32_t bits = wordPtr[1]; - - const uint32_t newBits = swap_index_bits(leftIsGreater, bits); - wordPtr[1] = newBits; - } - - rgb565Ptr[0] = leftNewRgb565; - rgb565Ptr[1] = rightNewRgb565; - - currentAddr += 8; - } - - // Halve dimensions for the next mipmap level - mipWidth = std::max(1u, mipWidth >> 1); - mipHeight = std::max(1u, mipHeight >> 1); - } -} - -void recolor_rgb5a3_texture(const TextureReplacementData& replacementData, const GXColor color, std::vector& newTextureDataOut) -{ - // Precompute lookup tables for both RGB555 (opaque) and RGB444 (translucent) modes - uint16_t recolors_rgb555[0x100]; - uint16_t recolors_rgb444[0x100]; - - for (int32_t i = 0; i < 0x100; i++) { - const uint8_t r = blend_overlay_channel(i, color.r); - const uint8_t g = blend_overlay_channel(i, color.g); - const uint8_t b = blend_overlay_channel(i, color.b); - - // Pack as RGB555: Bit 15 set to 1 + 5 bits R, G, B - recolors_rgb555[i] = 0x8000 | ((r >> 3) << 10) | ((g >> 3) << 5) | (b >> 3); - - // Pack as RGB444: 4 bits R, G, B (Bit 15 remains 0) - recolors_rgb444[i] = ((r >> 4) << 8) | ((g >> 4) << 4) | (b >> 4); - } - - const uint8_t mipCount = - replacementData.data.mip_count > 0 ? replacementData.data.mip_count : 1; - uint32_t mipWidth = replacementData.key.width; - uint32_t mipHeight = replacementData.key.height; - auto* pixelPtr = reinterpret_cast*>(newTextureDataOut.data()); - - for (uint8_t mip = 0; mip < mipCount; ++mip) { - const uint32_t roundedWidth = (mipWidth + 3) & ~3; - const uint32_t roundedHeight = (mipHeight + 3) & ~3; - const uint32_t totalPixels = roundedWidth * roundedHeight; - - for (uint32_t i = 0; i < totalPixels; i++) { - const uint16_t rawPixel = pixelPtr[i]; - - // MSB determines if pixel is opaque or translucent - if (rawPixel & 0x8000) { - // Pixel is opaque - const uint8_t r5 = (rawPixel >> 10) & 0x1F; - const uint8_t g5 = (rawPixel >> 5) & 0x1F; - const uint8_t b5 = rawPixel & 0x1F; - - // Expand 5-bit to 8-bit - const uint8_t r8 = (r5 << 3) | (r5 >> 2); - const uint8_t g8 = (g5 << 3) | (g5 >> 2); - const uint8_t b8 = (b5 << 3) | (b5 >> 2); - - const uint8_t grayVal = static_cast((r8 * 77 + g8 * 150 + b8 * 29) >> 8); - - pixelPtr[i] = recolors_rgb555[grayVal]; - } else { - // Pixel is translucent - const uint16_t alpha3 = rawPixel & 0x7000; - - const uint8_t r4 = (rawPixel >> 8) & 0x0F; - const uint8_t g4 = (rawPixel >> 4) & 0x0F; - const uint8_t b4 = rawPixel & 0x0F; - - // Expand 4-bit to 8-bit - const uint8_t r8 = (r4 << 4) | r4; - const uint8_t g8 = (g4 << 4) | g4; - const uint8_t b8 = (b4 << 4) | b4; - - const uint8_t grayVal = static_cast((r8 * 77 + g8 * 150 + b8 * 29) >> 8); - - // Combine original alpha with recolored RGB444 - pixelPtr[i] = alpha3 | recolors_rgb444[grayVal]; - } - } - - pixelPtr += totalPixels; - mipWidth = std::max(1u, mipWidth >> 1); - mipHeight = std::max(1u, mipHeight >> 1); - } -} - -// Function to encode a single 4x4 sub-block (16 pixels) into an 8-byte CMPR block -static void encode_cmpr_sub_block(uint8_t* dst, const uint8_t pixels[16]) { - uint8_t min_val = 255; - uint8_t max_val = 0; - - for (int i = 0; i < 16; ++i) { - if (pixels[i] < min_val) min_val = pixels[i]; - if (pixels[i] > max_val) max_val = pixels[i]; - } - - auto intensity_to_rgb565 = [](uint8_t val) -> uint16_t { - uint16_t r5 = val >> 3; - uint16_t g6 = val >> 2; - uint16_t b5 = val >> 3; - return static_cast((r5 << 11) | (g6 << 5) | b5); - }; - - uint16_t c0_565 = intensity_to_rgb565(max_val); - uint16_t c1_565 = intensity_to_rgb565(min_val); - uint32_t indices = 0; - - if (max_val > min_val) { - // Enforce c0_565 > c1_565 in unsigned 16-bit representation to use 4-color mode - if (c0_565 == c1_565) { - if ((c0_565 & 0x001F) < 0x001F) { - c0_565 += 1; - } else { - c1_565 -= 1; - } - } - - // Interpolated 8-bit intensity values for quantization - const int c0 = max_val; - const int c1 = min_val; - const int c2 = (2 * max_val + min_val) / 3; - const int c3 = (max_val + 2 * min_val) / 3; - - // Map each pixel to the nearest palette entry - for (int i = 0; i < 16; ++i) { - const int p = pixels[i]; - const int d0 = std::abs(p - c0); - const int d1 = std::abs(p - c1); - const int d2 = std::abs(p - c2); - const int d3 = std::abs(p - c3); - - uint32_t best_idx = 0; - int min_d = d0; - - if (d1 < min_d) { min_d = d1; best_idx = 1; } - if (d2 < min_d) { min_d = d2; best_idx = 2; } - if (d3 < min_d) { min_d = d3; best_idx = 3; } - - indices |= (best_idx << (30 - (2 * i))); - } - } - - // Account for big endian data expectation - dst[0] = static_cast(c0_565 >> 8); - dst[1] = static_cast(c0_565 & 0xFF); - dst[2] = static_cast(c1_565 >> 8); - dst[3] = static_cast(c1_565 & 0xFF); - dst[4] = static_cast(indices >> 24); - dst[5] = static_cast((indices >> 16) & 0xFF); - dst[6] = static_cast((indices >> 8) & 0xFF); - dst[7] = static_cast(indices & 0xFF); -} - -bool convert_i8_to_cmpr(TextureReplacementData& replacementData, std::vector& cmprOut) { - if (cmprOut.empty()) { - return false; - } - - const uint8_t mipCount = (replacementData.data.mip_count > 0) ? replacementData.data.mip_count : 1; - const auto expectedInputSize = get_image_data_size( - GX_TF_I8, replacementData.key.width, replacementData.key.height, mipCount); - if (cmprOut.size() < expectedInputSize) { - return false; - } - - const auto sourceData = cmprOut; - std::vector convertedData(get_image_data_size( - GX_TF_CMPR, replacementData.key.width, replacementData.key.height, mipCount)); - uint32_t mipWidth = replacementData.key.width; - uint32_t mipHeight = replacementData.key.height; - - const uint8_t* readPtr = sourceData.data(); - uint8_t* writePtr = convertedData.data(); - - for (uint8_t mip = 0; mip < mipCount; ++mip) { - const uint32_t paddedWidthI8 = (mipWidth + 7) & ~7; - const uint32_t paddedHeightI8 = (mipHeight + 3) & ~3; - const uint32_t tilesX_I8 = paddedWidthI8 / 8; - - const uint32_t paddedWidthCMPR = (mipWidth + 7) & ~7; - const uint32_t paddedHeightCMPR = (mipHeight + 7) & ~7; - const uint32_t blocksX_CMPR = paddedWidthCMPR / 8; - const uint32_t blocksY_CMPR = paddedHeightCMPR / 8; - - for (uint32_t by = 0; by < blocksY_CMPR; ++by) { - for (uint32_t bx = 0; bx < blocksX_CMPR; ++bx) { - uint8_t subBlockPixels[4][16]{}; - - for (uint32_t subBlockY = 0; subBlockY < 2; ++subBlockY) { - for (uint32_t subBlockX = 0; subBlockX < 2; ++subBlockX) { - const uint32_t subBlock = subBlockY * 2 + subBlockX; - for (uint32_t row = 0; row < 4; ++row) { - for (uint32_t col = 0; col < 4; ++col) { - const uint32_t x = bx * 8 + subBlockX * 4 + col; - const uint32_t y = by * 8 + subBlockY * 4 + row; - if (x >= mipWidth || y >= mipHeight) { - continue; - } - - const uint32_t tile = (y / 4) * tilesX_I8 + (x / 8); - const uint32_t tileOffset = (y % 4) * 8 + (x % 8); - subBlockPixels[subBlock][row * 4 + col] = - readPtr[tile * 32 + tileOffset]; - } - } - } - } - - for (const auto& subBlockPixel : subBlockPixels) { - encode_cmpr_sub_block(writePtr, subBlockPixel); - writePtr += 8; - } - } - } - - const uint32_t tilesY_I8 = paddedHeightI8 / 4; - readPtr += tilesX_I8 * tilesY_I8 * 32; - - mipWidth = std::max(1u, mipWidth >> 1); - mipHeight = std::max(1u, mipHeight >> 1); - } - - cmprOut.swap(convertedData); - - // Update format - replacementData.data.gx_format = GX_TF_CMPR; - - return true; -} - -void recolor_texture(TextureReplacementData& replacementData, GXColor color, std::vector& newTextureDataOut) { - - switch (replacementData.key.gx_format) { - case GX_TF_CMPR: - recolor_cmpr_texture(replacementData, color, newTextureDataOut); - break; - case GX_TF_RGB5A3: - recolor_rgb5a3_texture(replacementData, color, newTextureDataOut); - break; - case GX_TF_I8: - if (convert_i8_to_cmpr(replacementData, newTextureDataOut)) { - recolor_cmpr_texture(replacementData, color, newTextureDataOut); - } else { - mods::log::debug("Could not convert {} from i8 to cmpr", replacementData.textureName); - } - break; - default: - break; - } -} - -std::unordered_map>& get_texture_replacements() { - static std::unordered_map> replacements{}; - - if (replacements.empty()) { - replacements = { - {get_cvars().herosTunicCapColor, { - { - .arc = "Kmdl", - .modelFileName = "al_head.bmd", - .textureName = "al_cap", - } - }}, - {get_cvars().herosTunicTorsoColor, { - { - .arc = "Kmdl", - .modelFileName = "al.bmd", - .textureName = "al_upbody", - } - }}, - {get_cvars().herosTunicSkirtColor, { - { - .arc = "Kmdl", - .modelFileName = "al.bmd", - .textureName = "al_lowbody", - } - }}, - {get_cvars().zoraArmorCapColor, { - { - .arc = "Zmdl", - .modelFileName = "zl_head.bmd", - .textureName = "zl_cap", - } - }}, - {get_cvars().zoraArmorHelmetColor, { - { - .arc = "Zmdl", - .modelFileName = "zl_head.bmd", - .textureName = "zl_helmet", - } - }}, - {get_cvars().zoraArmorTorsoColor, { - { - .arc = "Zmdl", - .modelFileName = "zl.bmd", - .textureName = "zl_armor", - }, - { - .arc = "Zmdl", - .modelFileName = "zl.bmd", - .textureName = "zl_armL", - } - }}, - {get_cvars().zoraArmorScalesColor, { - { - .arc = "Zmdl", - .modelFileName = "zl.bmd", - .textureName = "zl_body", - } - }}, - {get_cvars().zoraArmorFlippersColor, { - { - .arc = "Zmdl", - .modelFileName = "zl.bmd", - .textureName = "zl_boots", - } - }}, - {get_cvars().woodenSwordColor, { - { - .arc = "Bmdl", // Ordon Clothes Model - .modelFileName = "al_swb.bmd", - .textureName = "al_SWB", - }, - { - .arc = "Kmdl", // Hero's Tunic Model - .modelFileName = "al_swb.bmd", - .textureName = "al_SWB", - }, - { - .arc = "Zmdl", // Zora Armor Model - .modelFileName = "al_swb.bmd", - .textureName = "al_SWB", - }, - { - .arc = "Mmdl", // Magic Armor Model - .modelFileName = "al_swb.bmd", - .textureName = "al_SWB", - }, - { - .arc = "O_gD_SWB", // Get Item Model - .modelFileName = "o_gd_al_swb.bmd", - .textureName = "al_SWB", - } - }}, - {get_cvars().ordonSwordHandleColor, { - { - .arc = "Alink", - .modelFileName = "al_swa.bmd", - .textureName = "al_SWgripA", - }, - { - .arc = "O_gD_SWA", // Get Item Model - .modelFileName = "o_gd_al_swa.bmd", - .textureName = "al_SWgripA", - } - }}, - {get_cvars().ordonSwordBladeColor, { - { - .arc = "Alink", - .modelFileName = "al_swa.bmd", - .textureName = "al_SWA", - } - }}, - {get_cvars().msHandleColor, { - { - .arc = "Alink", - .modelFileName = "al_swm.bmd", - .textureName = "al_SWgripM", - } - }}, - {get_cvars().msBladeColor, { - { - .arc = "Alink", - .modelFileName = "al_swm.bmd", - .textureName = "al_SWM", - } - }}, - {get_cvars().boomerangColor, { - { - .arc = "Alink", // Boomerang in Link's hand - .modelFileName = "al_boom.bmd", - .textureName = "L_al_boom00", - }, - { - .arc = "E_mk", // Boomerang in Ook's hand - .modelFileName = "bm.bmd", - .textureName = "L_al_boom00", - }, - { - .arc = "E_mk", // Boomerang in Ook's hand - .modelFileName = "bm.bmd", - .textureName = "bm_boom", - }, - { - .arc = "O_gD_boom", // Get Item Model - .modelFileName = "o_gd_boom.bmd", - .textureName = "L_al_boom00", - } - }}, - {get_cvars().ironBootsColor, { - { - .arc = "Bmdl", // Ordon Clothes Model - .modelFileName = "al_bootsh.bmd", - .textureName = "al_bootsH", - }, - { - .arc = "Kmdl", // Hero's Tunic Model - .modelFileName = "al_bootsh.bmd", - .textureName = "al_bootsH", - }, - { - .arc = "Zmdl", // Zora Armor Model - .modelFileName = "al_bootsh.bmd", - .textureName = "al_bootsH", - }, - { - .arc = "Mmdl", // Magic Armor Model - .modelFileName = "al_bootsh.bmd", - .textureName = "al_bootsH", - }, - { - .arc = "O_gD_boot", // Get Item Model - .modelFileName = "o_gd_al_bootsh.bmd", - .textureName = "al_bootsH", - } - }}, - {get_cvars().spinnerColor, { - { - .arc = "Alink", // Spinner used by Link - .modelFileName = "al_sp.bmd", - .textureName = "al_SP", - }, - { - .arc = "O_gD_SP", // Get Item Model - .modelFileName = "o_gd_al_sp.bmd", - .textureName = "al_SP", - } - }}, - {get_cvars().linkHairColor, { - { - .arc = "Bmdl", // Ordon Clothes Model - .modelFileName = "bl_head.bmd", - .textureName = "bl_hair", - }, - { - .arc = "Kmdl", // Hero's Tunic Model - .modelFileName = "al_head.bmd", - .textureName = "al_hair", - }, - { - .arc = "Mmdl", // Magic Armor Model - .modelFileName = "ml_head.bmd", - .textureName = "al_hair", - } - }}, - {get_cvars().wolfLinkColor, { - { - .arc = "Wmdl", - .modelFileName = "wl.bmd", - .textureName = "wl_body", - }, - { - .arc = "Wmdl", - .modelFileName = "wl.bmd", - .textureName = "wl_eye.1", - }, - { - .arc = "Wmdl", - .modelFileName = "wl.bmd", - .textureName = "wl_eye.2", - }, - { - .arc = "Wmdl", - .modelFileName = "wl.bmd", - .textureName = "wl_eye.3", - }, - { - .arc = "Wmdl", - .modelFileName = "wl.bmd", - .textureName = "wl_eye.4", - }, - { - .arc = "Wmdl", - .modelFileName = "wl.bmd", - .textureName = "wl_eye.5", - } - }}, - {get_cvars().eponaColor, { - { - .arc = "Horse", - .modelFileName = "hs.bmd", - .textureName = "hs_body", - }, - { - .arc = "Horse", - .modelFileName = "hs.bmd", - .textureName = "hs_eye.1", - }, - { - .arc = "Horse", - .modelFileName = "hs.bmd", - .textureName = "hs_eye.2", - }, - { - .arc = "Horse", - .modelFileName = "hs.bmd", - .textureName = "hs_eye.3", - }, - }}, - }; - } - - return replacements; -} diff --git a/mods/basic_cosmetics_mod/src/texture_utils.hpp b/mods/basic_cosmetics_mod/src/texture_utils.hpp deleted file mode 100644 index 49dd0df81a..0000000000 --- a/mods/basic_cosmetics_mod/src/texture_utils.hpp +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -/** - * File originally copied from console TPR with permission from isaac - * https://github.com/zsrtp/libtp_rel/blob/master/include/util/texture_utils.h - */ - -#include "mod.hpp" - -#include - -#include -#include - -uint32_t get_image_data_size( - uint32_t format, uint32_t width, uint32_t height, uint32_t mipmapCount); - -void recolor_texture( - TextureReplacementData& replacementData, GXColor color, std::vector& newTextureDataOut); - -std::unordered_map>& get_texture_replacements(); diff --git a/mods/cosmetics b/mods/cosmetics new file mode 160000 index 0000000000..93971c38aa --- /dev/null +++ b/mods/cosmetics @@ -0,0 +1 @@ +Subproject commit 93971c38aa7286c59be7c3874a95e12d1a37036a