From 46b051e28dfde8a2f2c45bfe6e57374d70ff24ed Mon Sep 17 00:00:00 2001 From: Linkzenic Date: Thu, 30 Jul 2026 23:26:34 -0700 Subject: [PATCH 1/2] Refresh bundled NEI equipment icons --- .github/workflows/android-release.yml | 1 + .github/workflows/android-test-apk.yml | 1 + .../java/com/dishii/soh/MainActivity.java | 2 +- soh/mods/extended_equipment.c | 16 ++++-- tools/ci/verify_nei_equipment_icons.py | 52 +++++++++++++++++++ 5 files changed, 67 insertions(+), 5 deletions(-) create mode 100644 tools/ci/verify_nei_equipment_icons.py diff --git a/.github/workflows/android-release.yml b/.github/workflows/android-release.yml index cb499dca9..3e2e81525 100644 --- a/.github/workflows/android-release.yml +++ b/.github/workflows/android-release.yml @@ -42,6 +42,7 @@ jobs: cmake --build build-linux --target GenerateSohOtr --parallel cp soh/soh.o2r Android/app/src/main/assets/soh.o2r tools/ci/inject_android_renderer_shaders.sh + python3 tools/ci/verify_nei_equipment_icons.py Android/app/src/main/assets/soh.o2r - name: Decode keystore run: | diff --git a/.github/workflows/android-test-apk.yml b/.github/workflows/android-test-apk.yml index ac9c53986..0a9abf157 100644 --- a/.github/workflows/android-test-apk.yml +++ b/.github/workflows/android-test-apk.yml @@ -60,6 +60,7 @@ jobs: cmake --build build-linux --target GenerateSohOtr --parallel cp soh/soh.o2r Android/app/src/main/assets/soh.o2r tools/ci/inject_android_renderer_shaders.sh + python3 tools/ci/verify_nei_equipment_icons.py Android/app/src/main/assets/soh.o2r - name: Decode keystore run: | diff --git a/Android/app/src/main/java/com/dishii/soh/MainActivity.java b/Android/app/src/main/java/com/dishii/soh/MainActivity.java index 97dd3b786..4ca0d8ad1 100644 --- a/Android/app/src/main/java/com/dishii/soh/MainActivity.java +++ b/Android/app/src/main/java/com/dishii/soh/MainActivity.java @@ -139,7 +139,7 @@ public class MainActivity extends SDLActivity{ private static final int TOUCH_FACE_BUTTON_LAYOUT_GAMECUBE = 2; private static final String SUPPORT_FILES_VERSION_MARKER = ".android_support_files_version"; // Bump this only when bundled Android support assets or archive layout changes. - private static final String SUPPORT_FILES_VERSION = "sohnei-android-support-6"; + private static final String SUPPORT_FILES_VERSION = "sohnei-android-support-7"; private AlertDialog dataRootMigrationDialog; private AlertDialog setupProgressDialog; diff --git a/soh/mods/extended_equipment.c b/soh/mods/extended_equipment.c index b1204e156..ef8f0e984 100644 --- a/soh/mods/extended_equipment.c +++ b/soh/mods/extended_equipment.c @@ -451,11 +451,19 @@ void* ExtEquip_GetIcon(s16 equipType, u8 index) { const char* path = sExtEquipIconPaths[equipType][index - 1]; - // Return the managed resource name, matching the item-page icon path. The - // renderer resolves the texture's real dimensions and scales it into the - // equipment page's existing 32x32 quad, preserving the 128x128 source. + // Return the managed resource name only after confirming it resolves. This + // preserves the source texture metadata used by the renderer while keeping + // an outdated or incomplete support archive from producing an empty slot. if (path != NULL) { - return (void*)path; + if (strstr(path, "textures/icon_item_custom/") != NULL) { + if (ResourceMgr_FileExists(path)) { + return (void*)path; + } + } else if (ResourceMgr_GetResourceDataByNameHandlingMQ(path) != NULL) { + // MM resources can be mounted after the extension cache is built, + // so resolve them directly before returning the managed path. + return (void*)path; + } } // These buffers have static lifetime and are always valid 32x32 RGBA32 diff --git a/tools/ci/verify_nei_equipment_icons.py b/tools/ci/verify_nei_equipment_icons.py new file mode 100644 index 000000000..c05fad7d6 --- /dev/null +++ b/tools/ci/verify_nei_equipment_icons.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +"""Fail an Android build if its generated support archive omits NEI icons.""" + +import sys +import zipfile +from pathlib import Path + + +REQUIRED_ICONS = ( + "textures/icon_item_custom/gItemIconCaneOfByrnaTex", + "textures/icon_item_custom/gItemIconFourSwordTex", + "textures/icon_item_custom/gItemIconDrillshaftTex", + "textures/icon_item_custom/gItemIconDivineShieldTex", + "textures/icon_item_custom/gItemIconGerudoScimitarTex", + "textures/icon_item_custom/gItemIconMagicCapeTex", + "textures/icon_item_custom/gItemIconPending4Tex", + "textures/icon_item_custom/gItemIconChampionsTunicTex", + "textures/icon_item_custom/gItemIconPegasusAnkletTex", + "textures/icon_item_custom/gItemIconWaterDragonScaleTex", +) + + +def main() -> int: + if len(sys.argv) != 2: + print(f"usage: {Path(sys.argv[0]).name} ", file=sys.stderr) + return 2 + + archive_path = Path(sys.argv[1]) + if not archive_path.is_file(): + print(f"missing support archive: {archive_path}", file=sys.stderr) + return 1 + + try: + with zipfile.ZipFile(archive_path) as archive: + entries = {name.removeprefix("__OTR__") for name in archive.namelist()} + except zipfile.BadZipFile: + print(f"invalid support archive: {archive_path}", file=sys.stderr) + return 1 + + missing = [icon for icon in REQUIRED_ICONS if icon not in entries] + if missing: + print("generated support archive is missing NEI equipment icons:", file=sys.stderr) + for icon in missing: + print(f" {icon}", file=sys.stderr) + return 1 + + print(f"verified {len(REQUIRED_ICONS)} NEI equipment icons in {archive_path}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From b309fc5c0b05af2e716246804e342a139c8a42a6 Mon Sep 17 00:00:00 2001 From: Linkzenic Date: Fri, 31 Jul 2026 00:00:39 -0700 Subject: [PATCH 2/2] Refresh bundled Fusion equipment icons --- .github/workflows/android-release.yml | 1 + .github/workflows/android-test-apk.yml | 3 +- .../java/com/dishii/soh/MainActivity.java | 2 +- soh/mods/extended_equipment.c | 16 ++++-- tools/ci/verify_nei_equipment_icons.py | 52 +++++++++++++++++++ 5 files changed, 68 insertions(+), 6 deletions(-) create mode 100644 tools/ci/verify_nei_equipment_icons.py diff --git a/.github/workflows/android-release.yml b/.github/workflows/android-release.yml index 0e2cdcf55..794b90260 100644 --- a/.github/workflows/android-release.yml +++ b/.github/workflows/android-release.yml @@ -50,6 +50,7 @@ jobs: cmake -S . -B build-linux -G Ninja -DCMAKE_BUILD_TYPE=Release cmake --build build-linux --target GenerateSohOtr --parallel python3 -c 'import sys, zipfile; required={"textures/wind-waker/clouds/cloud_mae", "textures/wind-waker/clouds/cloud_naka", "textures/wind-waker/clouds/cloudtx_01", "textures/wind-waker/clouds/cloudtx_02", "textures/wind-waker/clouds/cloudtx_03"}; archive=set(zipfile.ZipFile("soh/soh.o2r").namelist()); missing=sorted(required-archive); print("Missing Sky assets: " + ", ".join(missing)) if missing else print("Verified all Sky cloud assets in soh.o2r"); sys.exit(bool(missing))' + python3 tools/ci/verify_nei_equipment_icons.py soh/soh.o2r cp soh/soh.o2r Android/app/src/main/assets/soh.o2r tools/ci/inject_android_renderer_shaders.sh diff --git a/.github/workflows/android-test-apk.yml b/.github/workflows/android-test-apk.yml index 79c59ddb4..05a64e905 100644 --- a/.github/workflows/android-test-apk.yml +++ b/.github/workflows/android-test-apk.yml @@ -22,7 +22,7 @@ jobs: - name: Checkout Fusion uses: actions/checkout@v4 with: - ref: soh-fusion-android + ref: ${{ github.ref }} path: fusion fetch-depth: 0 submodules: recursive @@ -69,6 +69,7 @@ jobs: cmake -S fusion -B fusion/build-linux -G Ninja -DCMAKE_BUILD_TYPE=Release cmake --build fusion/build-linux --target GenerateSohOtr --parallel python3 -c 'import sys, zipfile; required={"textures/wind-waker/clouds/cloud_mae", "textures/wind-waker/clouds/cloud_naka", "textures/wind-waker/clouds/cloudtx_01", "textures/wind-waker/clouds/cloudtx_02", "textures/wind-waker/clouds/cloudtx_03"}; archive=set(zipfile.ZipFile("fusion/soh/soh.o2r").namelist()); missing=sorted(required-archive); print("Missing Sky assets: " + ", ".join(missing)) if missing else print("Verified all Sky cloud assets in Fusion soh.o2r"); sys.exit(bool(missing))' + python3 fusion/tools/ci/verify_nei_equipment_icons.py fusion/soh/soh.o2r cp fusion/soh/soh.o2r fusion/Android/app/src/main/assets/soh.o2r cd fusion tools/ci/inject_android_renderer_shaders.sh diff --git a/Android/app/src/main/java/com/dishii/soh/MainActivity.java b/Android/app/src/main/java/com/dishii/soh/MainActivity.java index 9ff21c932..32f9f645b 100644 --- a/Android/app/src/main/java/com/dishii/soh/MainActivity.java +++ b/Android/app/src/main/java/com/dishii/soh/MainActivity.java @@ -139,7 +139,7 @@ public class MainActivity extends SDLActivity{ private static final int TOUCH_FACE_BUTTON_LAYOUT_GAMECUBE = 2; private static final String SUPPORT_FILES_VERSION_MARKER = ".android_support_files_version"; // Bump this only when bundled Android support assets or archive layout changes. - private static final String SUPPORT_FILES_VERSION = "sohfusion-android-support-4"; + private static final String SUPPORT_FILES_VERSION = "sohfusion-android-support-5"; private AlertDialog dataRootMigrationDialog; private AlertDialog setupProgressDialog; diff --git a/soh/mods/extended_equipment.c b/soh/mods/extended_equipment.c index b1204e156..ef8f0e984 100644 --- a/soh/mods/extended_equipment.c +++ b/soh/mods/extended_equipment.c @@ -451,11 +451,19 @@ void* ExtEquip_GetIcon(s16 equipType, u8 index) { const char* path = sExtEquipIconPaths[equipType][index - 1]; - // Return the managed resource name, matching the item-page icon path. The - // renderer resolves the texture's real dimensions and scales it into the - // equipment page's existing 32x32 quad, preserving the 128x128 source. + // Return the managed resource name only after confirming it resolves. This + // preserves the source texture metadata used by the renderer while keeping + // an outdated or incomplete support archive from producing an empty slot. if (path != NULL) { - return (void*)path; + if (strstr(path, "textures/icon_item_custom/") != NULL) { + if (ResourceMgr_FileExists(path)) { + return (void*)path; + } + } else if (ResourceMgr_GetResourceDataByNameHandlingMQ(path) != NULL) { + // MM resources can be mounted after the extension cache is built, + // so resolve them directly before returning the managed path. + return (void*)path; + } } // These buffers have static lifetime and are always valid 32x32 RGBA32 diff --git a/tools/ci/verify_nei_equipment_icons.py b/tools/ci/verify_nei_equipment_icons.py new file mode 100644 index 000000000..643f2fb50 --- /dev/null +++ b/tools/ci/verify_nei_equipment_icons.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +"""Fail an Android build if its generated support archive omits equipment icons.""" + +import sys +import zipfile +from pathlib import Path + + +REQUIRED_ICONS = ( + "textures/icon_item_custom/gItemIconCaneOfByrnaTex", + "textures/icon_item_custom/gItemIconFourSwordTex", + "textures/icon_item_custom/gItemIconDrillshaftTex", + "textures/icon_item_custom/gItemIconDivineShieldTex", + "textures/icon_item_custom/gItemIconGerudoScimitarTex", + "textures/icon_item_custom/gItemIconMagicCapeTex", + "textures/icon_item_custom/gItemIconPending4Tex", + "textures/icon_item_custom/gItemIconChampionsTunicTex", + "textures/icon_item_custom/gItemIconPegasusAnkletTex", + "textures/icon_item_custom/gItemIconWaterDragonScaleTex", +) + + +def main() -> int: + if len(sys.argv) != 2: + print(f"usage: {Path(sys.argv[0]).name} ", file=sys.stderr) + return 2 + + archive_path = Path(sys.argv[1]) + if not archive_path.is_file(): + print(f"missing support archive: {archive_path}", file=sys.stderr) + return 1 + + try: + with zipfile.ZipFile(archive_path) as archive: + entries = {name.removeprefix("__OTR__") for name in archive.namelist()} + except zipfile.BadZipFile: + print(f"invalid support archive: {archive_path}", file=sys.stderr) + return 1 + + missing = [icon for icon in REQUIRED_ICONS if icon not in entries] + if missing: + print("generated support archive is missing equipment icons:", file=sys.stderr) + for icon in missing: + print(f" {icon}", file=sys.stderr) + return 1 + + print(f"verified {len(REQUIRED_ICONS)} equipment icons in {archive_path}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())