diff --git a/tools/pack_extflash_tone_0903.py b/tools/pack_extflash_tone_0903.py index 423086f..3b9973d 100644 --- a/tools/pack_extflash_tone_0903.py +++ b/tools/pack_extflash_tone_0903.py @@ -2,12 +2,12 @@ # -*- coding: utf-8 -*- """Pack external W25Q128 tone/logo image for K1 (0903). -Layout (absolute W25Q128 offsets): +Layout (absolute W25Q128 offsets — BIN2/BIN3 are FIXED so FW addresses stay stable): 0x00000000 logo.bin (legacy pad; boot UI uses UI0902 full-screen logo) 0x0000CB70 Charg.bin (legacy pad; charge UI uses UI0902_CHARGE_SCREEN full-screen) 0x0001B8F0 1.bin 普通/专业 31 rhythms - 0x0009D07D 2.bin 海阔天空 - 0x000A71AC 3.bin 万能模式(紧随 2.bin;具体偏移以打包结果为准) + 0x0009D07D 2.bin 本地曲目(变长,尾部 0xFF 填到 BIN3) + 0x000A71AC 3.bin 万能模式(固定;勿随 2.bin 长度漂移) Outputs: tools/out/extflash_tone_0903.bin @@ -16,7 +16,7 @@ Outputs: """ from __future__ import annotations -import os +import struct import subprocess import sys from pathlib import Path @@ -30,6 +30,15 @@ OUT_MAP = REPO / "Doc" / "音色文件" / "0903" / "FLASH_MAP.txt" TONE_DIR = REPO / "Doc" / "音色文件" / "0903" +# Fixed map — must match project/inc/ExtFlash_Tone_Addr.h consumed by firmware. +OFF_LOGO = 0x00000000 +OFF_CHARGING = 0x0000CB70 +OFF_BIN1 = 0x0001B8F0 +OFF_BIN2 = 0x0009D07D +OFF_BIN3 = 0x000A71AC # FIXED: never place 3.bin by concatenating after variable 2.bin +UI0902_RES_BASE = 0x00100000 +DAB_MAGIC = b"\xABDAB" + def find_ziliao() -> Path: for p in REPO.iterdir(): @@ -38,29 +47,51 @@ def find_ziliao() -> Path: raise FileNotFoundError("资料/logo.bin + Charg.bin not found under repo") +def dab_ok(blob: bytes, off: int, expect_cnt: int | None = None) -> None: + if off + 16 > len(blob): + raise SystemExit(f"DAB check @0x{off:X}: past end of pack ({len(blob)})") + magic = blob[off : off + 4] + cnt = struct.unpack_from(" None: ziliao = find_ziliao() parts = [ - ("LOGO", ziliao / "logo.bin", 0x00000000, "legacy pad; boot uses UI0902_BOOT_LOGO"), - ("CHARGING", ziliao / "Charg.bin", 0x0000CB70, "legacy pad; UI uses UI0902_CHARGE_SCREEN"), - ("BIN1_RHYTHM", TONE_DIR / "1.bin", 0x0001B8F0, "普通/专业 31 条节奏"), - ("BIN2_SONG_HAITIAN", TONE_DIR / "2.bin", None, "本地曲目 海阔天空"), - ("BIN3_UNIVERSAL", TONE_DIR / "3.bin", None, "万能模式"), + ("LOGO", ziliao / "logo.bin", OFF_LOGO, "legacy pad; boot uses UI0902_BOOT_LOGO"), + ("CHARGING", ziliao / "Charg.bin", OFF_CHARGING, "legacy pad; UI uses UI0902_CHARGE_SCREEN"), + ("BIN1_RHYTHM", TONE_DIR / "1.bin", OFF_BIN1, "普通/专业 31 条节奏"), + ("BIN2_SONG_HAITIAN", TONE_DIR / "2.bin", OFF_BIN2, "本地曲目"), + ("BIN3_UNIVERSAL", TONE_DIR / "3.bin", OFF_BIN3, "万能模式(固定偏移)"), ] - blobs = [] + blobs: list[bytes] = [] cursor = 0 - rows = [] + rows: list[tuple[str, int, int, str]] = [] 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")) + if cursor > force_off: + raise SystemExit( + f"{name}: cursor 0x{cursor:X} past forced 0x{force_off:X} " + f"(previous blob too large; enlarge next fixed gap or shrink prior file)" + ) + if cursor < force_off: + pad = force_off - cursor + blobs.append(b"\xFF" * pad) + rows.append(("(pad)", cursor, pad, "gap fill 0xFF")) + cursor = force_off + # BIN2 must not overflow into fixed BIN3 slot + if name == "BIN2_SONG_HAITIAN" and force_off + len(data) > OFF_BIN3: + raise SystemExit( + f"BIN2 too large ({len(data)} bytes): " + f"0x{force_off:X}+{len(data)} overflows fixed BIN3 @0x{OFF_BIN3:X} " + f"(max {OFF_BIN3 - force_off} bytes)" + ) off = cursor blobs.append(data) cursor += len(data) @@ -70,9 +101,21 @@ def main() -> None: 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("(")} + # Hard integrity gates — catch 2.bin drift / missing 3.bin before publish. + dab_ok(packed, by_name["BIN1_RHYTHM"][1], expect_cnt=31) + dab_ok(packed, by_name["BIN2_SONG_HAITIAN"][1], expect_cnt=1) + dab_ok(packed, OFF_BIN3, expect_cnt=3) + if by_name["BIN3_UNIVERSAL"][1] != OFF_BIN3: + raise SystemExit( + f"BIN3 placed @0x{by_name['BIN3_UNIVERSAL'][1]:X} but FW requires 0x{OFF_BIN3:X}" + ) + if packed[OFF_BIN3 : OFF_BIN3 + len(Path(TONE_DIR / "3.bin").read_bytes())] != ( + TONE_DIR / "3.bin" + ).read_bytes(): + raise SystemExit("BIN3 region does not match Doc/.../0903/3.bin") + hdr = f"""#ifndef __EXTFLASH_TONE_ADDR_H #define __EXTFLASH_TONE_ADDR_H @@ -98,7 +141,7 @@ def main() -> None: #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_ADDR 0x{OFF_BIN3:08X}UL #define EXTFLASH_BIN3_UNIVERSAL_SIZE {by_name['BIN3_UNIVERSAL'][2]}UL /* Convenience aliases used by UI ADDRESS */ @@ -123,6 +166,9 @@ def main() -> None: 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", "", + "NOTE: BIN3 is FIXED at 0x000A71AC. Shrinking 2.bin pads 0xFF up to BIN3;", + " growing 2.bin beyond (0xA71AC-0x9D07D)=41263 bytes fails the pack.", + "", f"{'Name':<22} {'Offset':>10} {'Size':>10} Note", "-" * 72, ] @@ -132,17 +178,17 @@ def main() -> None: "", "Firmware ADDRESS mapping:", " 普通/专业 -> FLASH_ADDR_MODE_NORMAL/EXPERT (1.bin)", - " 海阔天空 -> FLASH_ADDR_SONG_HAITIAN (2.bin)", - " 万能 -> FLASH_ADDR_MODE_UNIVERSAL (3.bin)", + " 本地曲目 -> FLASH_ADDR_SONG_HAITIAN (2.bin @ 0x9D07D)", + " 万能 -> FLASH_ADDR_MODE_UNIVERSAL (3.bin @ 0xA71AC FIXED)", " 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", "", "Add a local song:", - " 1. Pack the new preset into 2.bin", + " 1. Pack the new preset into 2.bin (must stay <= 41263 bytes unless BIN3 offset is raised in pack script + FW)", " 2. Append a row to local_songs.csv (index,code,name)", " 3. python tools/pack_extflash_tone_0903.py (also regenerates LocalSongNames.h)", - " 4. Rebuild firmware and flash MCU + ExtFlash", + " 4. Rebuild firmware and flash MCU + ExtFlash ALL.res", ] OUT_MAP.write_text("\n".join(map_lines) + "\n", encoding="utf-8", newline="\n") @@ -151,11 +197,11 @@ def main() -> None: print(f"Wrote {OUT_MAP}") for name, off, size, note in rows: print(f" 0x{off:08X} {size:8d} {name} {note}") - if cursor > 0x00100000: + if cursor > UI0902_RES_BASE: raise SystemExit("ERROR: pack overflows into UI0902_RES_BASE") - print(f"OK: {0x00100000 - cursor} bytes free before UI0902 @ 0x00100000") + print(f"OK: {UI0902_RES_BASE - cursor} bytes free before UI0902 @ 0x{UI0902_RES_BASE:X}") + print(f"OK: BIN3 fixed @0x{OFF_BIN3:X} magic=ABDAB cnt=3 matches 3.bin") - # Keep LocalSongNames.h in sync with Doc/.../local_songs.csv when packing tones. gen = ROOT / "tools" / "gen_local_song_names.py" r = subprocess.run([sys.executable, str(gen)], check=False) if r.returncode != 0: diff --git a/tools/publish_k1_release.py b/tools/publish_k1_release.py index 912e6b0..41372d1 100644 --- a/tools/publish_k1_release.py +++ b/tools/publish_k1_release.py @@ -69,6 +69,28 @@ def build_combined_extflash(tone: bytes, ui: bytes) -> bytes: return tone + (b"\xFF" * pad) + ui +def verify_tone_layout(tone: bytes) -> None: + """Refuse to ship ALL.res if BIN3 is missing/shifted (2.bin length drift).""" + dab = b"\xABDAB" + checks = [ + (0x0001B8F0, 31, "BIN1"), + (0x0009D07D, 1, "BIN2"), + (0x000A71AC, 3, "BIN3"), + ] + for off, want_cnt, name in checks: + if off + 16 > len(tone): + raise SystemExit(f"verify {name}: tone pack too short for 0x{off:X}") + magic = tone[off : off + 4] + cnt = int.from_bytes(tone[off + 12 : off + 16], "little") + if magic != dab or cnt != want_cnt: + raise SystemExit( + f"verify {name} @0x{off:X} failed: magic={magic.hex()} cnt={cnt} " + f"(want ABDAB/{want_cnt}). Re-run pack_extflash_tone_0903.py; " + f"do not publish a pack where 2.bin length shifted 3.bin." + ) + print("verify tone layout: BIN1/BIN2/BIN3 DAB OK at fixed FW addresses") + + def main() -> None: if not BOOT_BIN.is_file(): raise SystemExit(f"missing Boot bin: {BOOT_BIN}") @@ -97,8 +119,10 @@ def main() -> None: pkg.mkdir(parents=True) tone_data = TONE_BIN.read_bytes() + verify_tone_layout(tone_data) ui_data = UI0902_BIN.read_bytes() combined = build_combined_extflash(tone_data, ui_data) + verify_tone_layout(combined) # same offsets in ALL.res boot_name = f"AT32F403ARCT7_BOOT_v{FW_VER}_{stamp}.bin" mcu_name = f"YNGJ-GT1-M_MCU_v{FW_VER}_{stamp}.bin" @@ -147,7 +171,7 @@ def main() -> None: "", "外部 Flash 分区:", " 0x00000000 音色/充电图 (toneRes)", - " logo pad + Charg@0xCB70 + 1.bin@0x1B8F0 + 2.bin(HKTK/2s) + 3.bin", + " logo pad + Charg@0xCB70 + 1.bin@0x1B8F0 + 2.bin@0x9D07D + 3.bin@0xA71AC(FIXED)", " 0x00100000 UI0902 图片 (模式选择、开机 Logo、充电、烧录模式、设置/调音台等)", " 0x001D2000 UI0902_FLASH_MODE 烧录模式全屏图(Boot 读取)", "",