diff --git a/Global/Global.c b/Global/Global.c index e66ad4e..f983324 100644 --- a/Global/Global.c +++ b/Global/Global.c @@ -100,7 +100,7 @@ int16_t MIC_VOL_MAP[10]= {-9000,-7000,-5000,-3000,-1000,1000,3000,5000,7000,9000 uint8_t TOUCH_AREA_NUM = (sizeof(Touch_Areas)/sizeof(Touch_AreaTypeDef)); -const uint8_t Version[3] = {0,1,11}; +const uint8_t Version[3] = {0,2,0}; /* major.minor.patch — 0.2.0: tone pack 0903 addr remap */ uint8_t Led = 8; bool PressFlag = 0; @@ -304,8 +304,7 @@ void Power_Key_Scan() void (*CurrUIProcress)(DisplayTaskMessage_Type xEvent); /* UI 公共全局变量 */ -//volatile uint32_t ADDRESS = 0x0001b8F0; -volatile uint32_t ADDRESS = 0x00000000; +volatile uint32_t ADDRESS = FLASH_ADDR_MODE_UNIVERSAL; uint8_t KEY_ID_1629 = 0; bool StartFlag = false; bool InModeFlag = false; diff --git a/UI/UI_Idle.c b/UI/UI_Idle.c index fbd3a97..4c83e32 100644 --- a/UI/UI_Idle.c +++ b/UI/UI_Idle.c @@ -150,22 +150,22 @@ void Touch_Mode_Select_Action(int8_t FLAG, uint8_t ID, uint8_t areaIndex) switch (ID) { - case 0: /* 万能 */ + case 0: /* 万能 → 3.bin */ if (mGuiData[GUI_AUTOBAND_SW].Current) - ADDRESS = 0x0009EB5F; + ADDRESS = FLASH_ADDR_AUTOBAND_LEGACY; else - ADDRESS = 0x0001B8F0; + ADDRESS = FLASH_ADDR_MODE_UNIVERSAL; MenuPage_SongMode_Proc(); break; - case 1: /* 专业 */ + case 1: /* 专业 → 1.bin */ if (mGuiData[GUI_AUTOBAND_SW].Current) - ADDRESS = 0x0009EB5F; + ADDRESS = FLASH_ADDR_AUTOBAND_LEGACY; else - ADDRESS = 0x00000000; + ADDRESS = FLASH_ADDR_MODE_EXPERT; MenuPage_ExpertMode_Proc(); break; - case 2: /* 普通 */ - ADDRESS = 0x0009598F; + case 2: /* 普通 → 1.bin */ + ADDRESS = FLASH_ADDR_MODE_NORMAL; MenuPage_FreeMode_Proc(); break; default: @@ -266,7 +266,8 @@ void IdleProcess(DisplayTaskMessage_Type msg) BSP_MainPowerEnable(1); LCD_WR_REG(0x29); //Display on - LCD_WR_PIC_FROM_FLASH(40, 50, 160, 190, 0x0000CB70); + LCD_WR_PIC_FROM_FLASH(40, 50, EXTFLASH_CHARGING_W, EXTFLASH_CHARGING_H, + EXTFLASH_CHARGING_ADDR); LCD_BLK_Set(); flag = 1; } diff --git a/UI/UI_SongMode.c b/UI/UI_SongMode.c index 4c812c3..e596748 100644 --- a/UI/UI_SongMode.c +++ b/UI/UI_SongMode.c @@ -225,10 +225,16 @@ void Touch_Action(int8_t FLAG, uint8_t ID, uint8_t areaIndex,uint8_t value) if(mGuiData[GUI_AUTOBAND_SW].Current) { - ADDRESS = 0x0009EB5B; + ADDRESS = FLASH_ADDR_AUTOBAND_LEGACY; }else - { - ADDRESS = 0x00000000; + { + /* 关闭 AutoBand:回到当前 TAB 对应音色库 */ + switch (mGuiData[GUI_TAB_INDEX].Current) + { + case 1: ADDRESS = FLASH_ADDR_MODE_EXPERT; break; + case 2: ADDRESS = FLASH_ADDR_MODE_NORMAL; break; + default: ADDRESS = FLASH_ADDR_MODE_UNIVERSAL; break; + } } if (!UI_SetFocus(GUI_MODE_PARAM)) label_ModeSelece_ui(&UI_Label[GUI_MODE_PARAM],&mGuiData[GUI_MODE_PARAM]); @@ -263,42 +269,34 @@ void Touch_Action(int8_t FLAG, uint8_t ID, uint8_t areaIndex,uint8_t value) app_tm1629_all_on(LED_COLOR_G); switch(mGuiData[ID].Current) { - case 0: + case 0: /* 万能 → 3.bin */ if(mGuiData[GUI_TAB_LAST_INDEX].Current == mGuiData[ID].Current) break; StartFlag = 0; AutoBandTop1_Stop(); if(mGuiData[GUI_AUTOBAND_SW].Current) - { - ADDRESS = 0x0009EB5F; - } + ADDRESS = FLASH_ADDR_AUTOBAND_LEGACY; else - { - ADDRESS = 0x00000000; - } + ADDRESS = FLASH_ADDR_MODE_UNIVERSAL; break; - case 1: + case 1: /* 专业 → 1.bin */ if(mGuiData[GUI_TAB_LAST_INDEX].Current == mGuiData[ID].Current) break; StartFlag = 0; AutoBandTop1_Stop(); if(mGuiData[GUI_AUTOBAND_SW].Current) - { - ADDRESS = 0x0009EB5F; - } + ADDRESS = FLASH_ADDR_AUTOBAND_LEGACY; else - { - ADDRESS = 0x00000000; - } + ADDRESS = FLASH_ADDR_MODE_EXPERT; break; - case 2: + case 2: /* 普通 → 1.bin */ if(mGuiData[GUI_TAB_LAST_INDEX].Current == mGuiData[ID].Current) break; StartFlag = 0; AutoBandTop1_Stop(); - ADDRESS = 0x0009598F; + ADDRESS = FLASH_ADDR_MODE_NORMAL; break; } Refresh_TabSelect_ui(&UI_Label[ID], &mGuiData[ID]); diff --git a/UI/UI_global.c b/UI/UI_global.c index d212152..2e50065 100644 --- a/UI/UI_global.c +++ b/UI/UI_global.c @@ -236,15 +236,19 @@ void UI_GotoCurrentModePage(void) switch (mGuiData[GUI_TAB_INDEX].Current) { case 0: - ADDRESS = mGuiData[GUI_AUTOBAND_SW].Current ? 0x0009EB5F : 0x0001B8F0; + ADDRESS = mGuiData[GUI_AUTOBAND_SW].Current + ? FLASH_ADDR_AUTOBAND_LEGACY + : FLASH_ADDR_MODE_UNIVERSAL; MenuPage_SongMode_Proc(); break; case 1: - ADDRESS = mGuiData[GUI_AUTOBAND_SW].Current ? 0x0009EB5F : 0x00000000; + ADDRESS = mGuiData[GUI_AUTOBAND_SW].Current + ? FLASH_ADDR_AUTOBAND_LEGACY + : FLASH_ADDR_MODE_EXPERT; MenuPage_ExpertMode_Proc(); break; case 2: - ADDRESS = 0x0009598F; + ADDRESS = FLASH_ADDR_MODE_NORMAL; MenuPage_FreeMode_Proc(); break; default: @@ -803,15 +807,19 @@ void UI_NavBar_Action(uint8_t value) switch (value) { case UI0902_NAV_UNIVERSAL: - ADDRESS = mGuiData[GUI_AUTOBAND_SW].Current ? 0x0009EB5F : 0x0001B8F0; + ADDRESS = mGuiData[GUI_AUTOBAND_SW].Current + ? FLASH_ADDR_AUTOBAND_LEGACY + : FLASH_ADDR_MODE_UNIVERSAL; MenuPage_SongMode_Proc(); break; case UI0902_NAV_NORMAL: - ADDRESS = 0x0009598F; + ADDRESS = FLASH_ADDR_MODE_NORMAL; MenuPage_FreeMode_Proc(); break; case UI0902_NAV_EXPERT: - ADDRESS = mGuiData[GUI_AUTOBAND_SW].Current ? 0x0009EB5F : 0x00000000; + ADDRESS = mGuiData[GUI_AUTOBAND_SW].Current + ? FLASH_ADDR_AUTOBAND_LEGACY + : FLASH_ADDR_MODE_EXPERT; MenuPage_ExpertMode_Proc(); break; case UI0902_NAV_SETTING: diff --git a/project/inc/ExtFlash_Tone_Addr.h b/project/inc/ExtFlash_Tone_Addr.h new file mode 100644 index 0000000..188cc71 --- /dev/null +++ b/project/inc/ExtFlash_Tone_Addr.h @@ -0,0 +1,42 @@ +#ifndef __EXTFLASH_TONE_ADDR_H +#define __EXTFLASH_TONE_ADDR_H + +/* Auto-generated by tools/pack_extflash_tone_0903.py — do not hand-edit. */ +/* W25Q128 absolute offsets. Boot logo display uses UI0902_BOOT_LOGO_ADDR. */ +/* Tone resource pack has no in-bin version field; release tag = EXTFLASH_TONE_RES_VER. */ +#define EXTFLASH_TONE_RES_VER "0903" +#define EXTFLASH_TONE_RES_VER_MAJOR 0 +#define EXTFLASH_TONE_RES_VER_MINOR 9 +#define EXTFLASH_TONE_RES_VER_PATCH 3 + +#define EXTFLASH_LOGO_ADDR 0x00000000UL +#define EXTFLASH_LOGO_SIZE 52080UL + +#define EXTFLASH_CHARGING_ADDR 0x0000CB70UL +#define EXTFLASH_CHARGING_SIZE 60800UL +#define EXTFLASH_CHARGING_W 160 +#define EXTFLASH_CHARGING_H 190 + +#define EXTFLASH_BIN1_RHYTHM_ADDR 0x0001B8F0UL +#define EXTFLASH_BIN1_RHYTHM_SIZE 530317UL + +#define EXTFLASH_BIN2_SONG_HAITIAN_ADDR 0x0009D07DUL +#define EXTFLASH_BIN2_SONG_HAITIAN_SIZE 46474UL + +#define EXTFLASH_BIN3_UNIVERSAL_ADDR 0x000A8607UL +#define EXTFLASH_BIN3_UNIVERSAL_SIZE 31854UL + +/* Convenience aliases used by UI ADDRESS */ +#define FLASH_ADDR_MODE_NORMAL EXTFLASH_BIN1_RHYTHM_ADDR /* 普通 */ +#define FLASH_ADDR_MODE_EXPERT EXTFLASH_BIN1_RHYTHM_ADDR /* 专业 */ +#define FLASH_ADDR_SONG_HAITIAN EXTFLASH_BIN2_SONG_HAITIAN_ADDR +#define FLASH_ADDR_MODE_UNIVERSAL EXTFLASH_BIN3_UNIVERSAL_ADDR /* 万能 */ + +/* Legacy AutoBand bank — not repacked in 0903; left unchanged in firmware */ +#define FLASH_ADDR_AUTOBAND_LEGACY 0x0009EB5FUL + +#define EXTFLASH_TONE_PACK_END 0x000B0275UL +#define EXTFLASH_TONE_PACK_SIZE 721525UL +#define UI0902_RES_BASE_SAFE_GAP (0x00100000UL - EXTFLASH_TONE_PACK_END) + +#endif diff --git a/project/inc/includes.h b/project/inc/includes.h index 05477e5..7a784c3 100644 --- a/project/inc/includes.h +++ b/project/inc/includes.h @@ -37,6 +37,7 @@ #include "Drv_ILI9341_Lcd_Image.h" #include "Drv_ILI9341_Lcd_Image_UI0902.h" #include "UI0902_Res_Addr.h" +#include "ExtFlash_Tone_Addr.h" #include "Drv_ILI9341_Lcd_Dump.h" #include "Drv_XPT2046_Touch.h" diff --git a/tools/pack_extflash_tone_0903.py b/tools/pack_extflash_tone_0903.py new file mode 100644 index 0000000..9761e39 --- /dev/null +++ b/tools/pack_extflash_tone_0903.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""Pack external W25Q128 tone/logo image for K1 (0903). + +Layout (absolute W25Q128 offsets): + 0x00000000 logo.bin (legacy pad; boot UI uses UI0902 full-screen logo) + 0x0000CB70 Charg.bin (160x190 RGB565 charging art) + 0x0001B8F0 1.bin 普通/专业 31 rhythms + 0x0009D07D 2.bin 海阔天空 + 0x000A8607 3.bin 万能模式 + +Outputs: + tools/out/extflash_tone_0903.bin + project/inc/ExtFlash_Tone_Addr.h + Doc/音色文件/0903/FLASH_MAP.txt +""" +from __future__ import annotations + +import os +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] # firmware project root +REPO = ROOT.parent.parent # 一诺国际吉他 +OUT_DIR = ROOT / "tools" / "out" +OUT_BIN = OUT_DIR / "extflash_tone_0903.bin" +OUT_HDR = ROOT / "project" / "inc" / "ExtFlash_Tone_Addr.h" +OUT_MAP = REPO / "Doc" / "音色文件" / "0903" / "FLASH_MAP.txt" + +TONE_DIR = REPO / "Doc" / "音色文件" / "0903" + + +def find_ziliao() -> Path: + for p in REPO.iterdir(): + if p.is_dir() and (p / "logo.bin").is_file() and (p / "Charg.bin").is_file(): + return p + raise FileNotFoundError("资料/logo.bin + Charg.bin not found under repo") + + +def main() -> None: + ziliao = find_ziliao() + parts = [ + ("LOGO", ziliao / "logo.bin", 0x00000000, "legacy pad; boot uses UI0902_BOOT_LOGO"), + ("CHARGING", ziliao / "Charg.bin", 0x0000CB70, "160x190 RGB565"), + ("BIN1_RHYTHM", TONE_DIR / "1.bin", 0x0001B8F0, "普通/专业 31 条节奏"), + ("BIN2_SONG_HAITIAN", TONE_DIR / "2.bin", None, "本地曲目 海阔天空"), + ("BIN3_UNIVERSAL", TONE_DIR / "3.bin", None, "万能模式"), + ] + + blobs = [] + cursor = 0 + rows = [] + for name, path, force_off, note in parts: + data = path.read_bytes() + if force_off is not None: + if cursor > force_off: + raise SystemExit(f"{name}: cursor 0x{cursor:X} past forced 0x{force_off:X}") + if cursor < force_off: + pad = force_off - cursor + blobs.append(b"\xFF" * pad) + cursor = force_off + rows.append((f"(pad)", force_off - pad, pad, "gap fill 0xFF")) + off = cursor + blobs.append(data) + cursor += len(data) + rows.append((name, off, len(data), f"{note} <- {path.name}")) + + OUT_DIR.mkdir(parents=True, exist_ok=True) + packed = b"".join(blobs) + OUT_BIN.write_bytes(packed) + + # named lookup + by_name = {r[0]: r for r in rows if not r[0].startswith("(")} + + hdr = f"""#ifndef __EXTFLASH_TONE_ADDR_H +#define __EXTFLASH_TONE_ADDR_H + +/* Auto-generated by tools/pack_extflash_tone_0903.py — do not hand-edit. */ +/* W25Q128 absolute offsets. Boot logo display uses UI0902_BOOT_LOGO_ADDR. */ +/* Tone resource pack has no in-bin version field; release tag = EXTFLASH_TONE_RES_VER. */ +#define EXTFLASH_TONE_RES_VER "0903" +#define EXTFLASH_TONE_RES_VER_MAJOR 0 +#define EXTFLASH_TONE_RES_VER_MINOR 9 +#define EXTFLASH_TONE_RES_VER_PATCH 3 + +#define EXTFLASH_LOGO_ADDR 0x{by_name['LOGO'][1]:08X}UL +#define EXTFLASH_LOGO_SIZE {by_name['LOGO'][2]}UL + +#define EXTFLASH_CHARGING_ADDR 0x{by_name['CHARGING'][1]:08X}UL +#define EXTFLASH_CHARGING_SIZE {by_name['CHARGING'][2]}UL +#define EXTFLASH_CHARGING_W 160 +#define EXTFLASH_CHARGING_H 190 + +#define EXTFLASH_BIN1_RHYTHM_ADDR 0x{by_name['BIN1_RHYTHM'][1]:08X}UL +#define EXTFLASH_BIN1_RHYTHM_SIZE {by_name['BIN1_RHYTHM'][2]}UL + +#define EXTFLASH_BIN2_SONG_HAITIAN_ADDR 0x{by_name['BIN2_SONG_HAITIAN'][1]:08X}UL +#define EXTFLASH_BIN2_SONG_HAITIAN_SIZE {by_name['BIN2_SONG_HAITIAN'][2]}UL + +#define EXTFLASH_BIN3_UNIVERSAL_ADDR 0x{by_name['BIN3_UNIVERSAL'][1]:08X}UL +#define EXTFLASH_BIN3_UNIVERSAL_SIZE {by_name['BIN3_UNIVERSAL'][2]}UL + +/* Convenience aliases used by UI ADDRESS */ +#define FLASH_ADDR_MODE_NORMAL EXTFLASH_BIN1_RHYTHM_ADDR /* 普通 */ +#define FLASH_ADDR_MODE_EXPERT EXTFLASH_BIN1_RHYTHM_ADDR /* 专业 */ +#define FLASH_ADDR_SONG_HAITIAN EXTFLASH_BIN2_SONG_HAITIAN_ADDR +#define FLASH_ADDR_MODE_UNIVERSAL EXTFLASH_BIN3_UNIVERSAL_ADDR /* 万能 */ + +/* Legacy AutoBand bank — not repacked in 0903; left unchanged in firmware */ +#define FLASH_ADDR_AUTOBAND_LEGACY 0x0009EB5FUL + +#define EXTFLASH_TONE_PACK_END 0x{cursor:08X}UL +#define EXTFLASH_TONE_PACK_SIZE {cursor}UL +#define UI0902_RES_BASE_SAFE_GAP (0x00100000UL - EXTFLASH_TONE_PACK_END) + +#endif +""" + OUT_HDR.write_text(hdr, encoding="utf-8", newline="\n") + + map_lines = [ + "K1 external Flash map — tone pack 0903", + f"Packed file: Code/.../tools/out/{OUT_BIN.name} ({len(packed)} bytes)", + f"Ends at 0x{cursor:X}; UI0902_RES_BASE=0x00100000; free gap={0x100000 - cursor} bytes", + "", + f"{'Name':<22} {'Offset':>10} {'Size':>10} Note", + "-" * 72, + ] + for name, off, size, note in rows: + map_lines.append(f"{name:<22} 0x{off:08X} {size:10d} {note}") + map_lines += [ + "", + "Firmware ADDRESS mapping:", + " 普通/专业 -> FLASH_ADDR_MODE_NORMAL/EXPERT (1.bin)", + " 海阔天空 -> FLASH_ADDR_SONG_HAITIAN (2.bin)", + " 万能 -> FLASH_ADDR_MODE_UNIVERSAL (3.bin)", + " AutoBand -> FLASH_ADDR_AUTOBAND_LEGACY 0x9EB5F (unchanged; overlaps 2.bin region — do not enable until remapped)", + " Boot logo -> UI0902_BOOT_LOGO_ADDR (full-screen); packed logo.bin only pads 0x0..0xCB70", + " Charging -> EXTFLASH_CHARGING_ADDR", + ] + OUT_MAP.write_text("\n".join(map_lines) + "\n", encoding="utf-8", newline="\n") + + print(f"Wrote {OUT_BIN} ({len(packed)} bytes)") + print(f"Wrote {OUT_HDR}") + print(f"Wrote {OUT_MAP}") + for name, off, size, note in rows: + print(f" 0x{off:08X} {size:8d} {name} {note}") + if cursor > 0x00100000: + raise SystemExit("ERROR: pack overflows into UI0902_RES_BASE") + print(f"OK: {0x00100000 - cursor} bytes free before UI0902 @ 0x00100000") + + +if __name__ == "__main__": + main() diff --git a/tools/publish_k1_release.py b/tools/publish_k1_release.py new file mode 100644 index 0000000..a633c05 --- /dev/null +++ b/tools/publish_k1_release.py @@ -0,0 +1,135 @@ +# -*- coding: utf-8 -*- +"""Build K1 release zip: MCU .bin + tone .res + SoundWalkerIAP + docs. + +No RTT flashing. Version stays Firmware Version[] (currently 0.2.0). +""" +from __future__ import annotations + +import hashlib +import shutil +import subprocess +import zipfile +from datetime import datetime +from pathlib import Path + +REPO = Path(r"C:\Users\qjyu\Documents\SoundWalker\一诺国际吉他") +PROJ = REPO / "Code" / "YNGJ-GT1-M - AT32F403ARCT7" +EXE_BIN = PROJ / "project" / "IAR_V7.4" / "YNGJ-GT1-M" / "Exe" / "YNGJ-GT1-M.bin" +TONE_BIN = PROJ / "tools" / "out" / "extflash_tone_0903.bin" +MAP_TXT = REPO / "Doc" / "音色文件" / "0903" / "FLASH_MAP.txt" +IAP_DIR = REPO / "升级" / "MCU主控升级" +OUT_ROOT = REPO / "tools" / "out" + +FW_VER = "0.2.0" +TONE_RES_VER = "0903" + + +def git_info(cwd: Path) -> tuple[str, str]: + def run(args: list[str]) -> str: + r = subprocess.run( + args, cwd=str(cwd), capture_output=True, text=True, encoding="utf-8", errors="replace" + ) + return (r.stdout or "").strip() + + commit = run(["git", "rev-parse", "--short=7", "HEAD"]) or "nogit" + subject = run(["git", "log", "-1", "--pretty=%s"]) or "" + return commit, subject + + +def sha256(path: Path) -> str: + h = hashlib.sha256() + with path.open("rb") as f: + for chunk in iter(lambda: f.read(1 << 20), b""): + h.update(chunk) + return h.hexdigest() + + +def main() -> None: + if not EXE_BIN.is_file(): + raise SystemExit(f"missing MCU bin: {EXE_BIN}") + if not TONE_BIN.is_file(): + raise SystemExit(f"missing tone pack: {TONE_BIN} (run pack_extflash_tone_0903.py)") + + iap = IAP_DIR / "SoundWalkerIAP.exe" + guide = IAP_DIR / "升级步骤.docx" + if not iap.is_file(): + raise SystemExit(f"missing IAP tool: {iap}") + if not guide.is_file(): + raise SystemExit(f"missing guide: {guide}") + + # date + commit only (no minutes, no dirty tag) + stamp = datetime.now().strftime("%Y%m%d") + commit, subject = git_info(PROJ) + base = f"K1_MCU_v{FW_VER}_toneRes_v{TONE_RES_VER}_{stamp}_{commit}" + + OUT_ROOT.mkdir(parents=True, exist_ok=True) + pkg = OUT_ROOT / base + if pkg.exists(): + shutil.rmtree(pkg) + pkg.mkdir(parents=True) + + mcu_name = f"YNGJ-GT1-M_MCU_v{FW_VER}_{stamp}.bin" + res_name = f"extflash_toneRes_v{TONE_RES_VER}_{stamp}.res" + mcu_dst = pkg / mcu_name + res_dst = pkg / res_name + shutil.copy2(EXE_BIN, mcu_dst) + shutil.copy2(TONE_BIN, res_dst) + shutil.copy2(iap, pkg / "SoundWalkerIAP.exe") + shutil.copy2(guide, pkg / "升级步骤.docx") + if MAP_TXT.is_file(): + shutil.copy2(MAP_TXT, pkg / "FLASH_MAP.txt") + + # flat convenience copies + shutil.copy2(res_dst, OUT_ROOT / "extflash_tone_0903.res") + shutil.copy2(mcu_dst, OUT_ROOT / mcu_name) + + readme = pkg / "README.txt" + readme.write_text( + "\n".join( + [ + f"K1 release {base}", + f"Built {datetime.now().strftime('%Y-%m-%d %H:%M:%S %z')}", + f"Git HEAD {commit}", + f"Git subject {subject}", + f"FW version {FW_VER} (settings UI Version[])", + f"Tone res toneRes_v{TONE_RES_VER} (no in-bin version; tag in filename)", + "", + "Contents:", + f" {mcu_name}", + f" size={mcu_dst.stat().st_size} sha256={sha256(mcu_dst)}", + f" flash with SoundWalkerIAP.exe (USB IAP)", + f" {res_name}", + f" size={res_dst.stat().st_size} sha256={sha256(res_dst)}", + " W25Q128 layout: logo@0 + Charg@0xCB70 + 1.bin@0x1B8F0 + 2.bin + 3.bin", + " Must pair with MCU v0.2.0 (address map changed).", + " SoundWalkerIAP.exe", + " 升级步骤.docx", + " FLASH_MAP.txt", + "", + "Notes:", + " - Boot logo uses UI0902 full-screen asset; packed logo.bin only pads 0x0..0xCB70.", + " - AutoBand legacy 0x9EB5F not remapped in this release.", + " - Flash MCU firmware AND tone .res together.", + "", + ] + ), + encoding="utf-8", + newline="\n", + ) + + zip_path = OUT_ROOT / f"{base}.zip" + if zip_path.exists(): + zip_path.unlink() + with zipfile.ZipFile(zip_path, "w", compression=zipfile.ZIP_DEFLATED) as zf: + for f in sorted(pkg.rglob("*")): + if f.is_file(): + zf.write(f, arcname=f"{base}/{f.relative_to(pkg).as_posix()}") + + print(f"PKG {pkg}") + print(f"ZIP {zip_path} ({zip_path.stat().st_size} bytes)") + for f in sorted(pkg.iterdir()): + print(f" {f.name} {f.stat().st_size}") + + +if __name__ == "__main__": + main()