Pin BIN3 to fixed 0xA71AC and reject packs where 2.bin drift shifts it.
Variable 2.bin length previously moved 3.bin; firmware still read 0xA71AC and saw zeros. Pack now pads to the fixed slot and publish verifies DAB headers before shipping ALL.res. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
cd21e4e45e
commit
99f7ed3e5a
|
|
@ -2,12 +2,12 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""Pack external W25Q128 tone/logo image for K1 (0903).
|
"""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)
|
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)
|
0x0000CB70 Charg.bin (legacy pad; charge UI uses UI0902_CHARGE_SCREEN full-screen)
|
||||||
0x0001B8F0 1.bin 普通/专业 31 rhythms
|
0x0001B8F0 1.bin 普通/专业 31 rhythms
|
||||||
0x0009D07D 2.bin 海阔天空
|
0x0009D07D 2.bin 本地曲目(变长,尾部 0xFF 填到 BIN3)
|
||||||
0x000A71AC 3.bin 万能模式(紧随 2.bin;具体偏移以打包结果为准)
|
0x000A71AC 3.bin 万能模式(固定;勿随 2.bin 长度漂移)
|
||||||
|
|
||||||
Outputs:
|
Outputs:
|
||||||
tools/out/extflash_tone_0903.bin
|
tools/out/extflash_tone_0903.bin
|
||||||
|
|
@ -16,7 +16,7 @@ Outputs:
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import struct
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
@ -30,6 +30,15 @@ OUT_MAP = REPO / "Doc" / "音色文件" / "0903" / "FLASH_MAP.txt"
|
||||||
|
|
||||||
TONE_DIR = REPO / "Doc" / "音色文件" / "0903"
|
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:
|
def find_ziliao() -> Path:
|
||||||
for p in REPO.iterdir():
|
for p in REPO.iterdir():
|
||||||
|
|
@ -38,29 +47,51 @@ def find_ziliao() -> Path:
|
||||||
raise FileNotFoundError("资料/logo.bin + Charg.bin not found under repo")
|
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("<I", blob, off + 12)[0]
|
||||||
|
if magic != DAB_MAGIC:
|
||||||
|
raise SystemExit(
|
||||||
|
f"DAB check @0x{off:X}: bad magic {magic.hex()} (want {DAB_MAGIC.hex()})"
|
||||||
|
)
|
||||||
|
if expect_cnt is not None and cnt != expect_cnt:
|
||||||
|
raise SystemExit(f"DAB check @0x{off:X}: cnt={cnt} want {expect_cnt}")
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
ziliao = find_ziliao()
|
ziliao = find_ziliao()
|
||||||
parts = [
|
parts = [
|
||||||
("LOGO", ziliao / "logo.bin", 0x00000000, "legacy pad; boot uses UI0902_BOOT_LOGO"),
|
("LOGO", ziliao / "logo.bin", OFF_LOGO, "legacy pad; boot uses UI0902_BOOT_LOGO"),
|
||||||
("CHARGING", ziliao / "Charg.bin", 0x0000CB70, "legacy pad; UI uses UI0902_CHARGE_SCREEN"),
|
("CHARGING", ziliao / "Charg.bin", OFF_CHARGING, "legacy pad; UI uses UI0902_CHARGE_SCREEN"),
|
||||||
("BIN1_RHYTHM", TONE_DIR / "1.bin", 0x0001B8F0, "普通/专业 31 条节奏"),
|
("BIN1_RHYTHM", TONE_DIR / "1.bin", OFF_BIN1, "普通/专业 31 条节奏"),
|
||||||
("BIN2_SONG_HAITIAN", TONE_DIR / "2.bin", None, "本地曲目 海阔天空"),
|
("BIN2_SONG_HAITIAN", TONE_DIR / "2.bin", OFF_BIN2, "本地曲目"),
|
||||||
("BIN3_UNIVERSAL", TONE_DIR / "3.bin", None, "万能模式"),
|
("BIN3_UNIVERSAL", TONE_DIR / "3.bin", OFF_BIN3, "万能模式(固定偏移)"),
|
||||||
]
|
]
|
||||||
|
|
||||||
blobs = []
|
blobs: list[bytes] = []
|
||||||
cursor = 0
|
cursor = 0
|
||||||
rows = []
|
rows: list[tuple[str, int, int, str]] = []
|
||||||
for name, path, force_off, note in parts:
|
for name, path, force_off, note in parts:
|
||||||
data = path.read_bytes()
|
data = path.read_bytes()
|
||||||
if force_off is not None:
|
|
||||||
if cursor > force_off:
|
if cursor > force_off:
|
||||||
raise SystemExit(f"{name}: cursor 0x{cursor:X} past forced 0x{force_off:X}")
|
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:
|
if cursor < force_off:
|
||||||
pad = force_off - cursor
|
pad = force_off - cursor
|
||||||
blobs.append(b"\xFF" * pad)
|
blobs.append(b"\xFF" * pad)
|
||||||
|
rows.append(("(pad)", cursor, pad, "gap fill 0xFF"))
|
||||||
cursor = force_off
|
cursor = force_off
|
||||||
rows.append((f"(pad)", force_off - pad, pad, "gap fill 0xFF"))
|
# 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
|
off = cursor
|
||||||
blobs.append(data)
|
blobs.append(data)
|
||||||
cursor += len(data)
|
cursor += len(data)
|
||||||
|
|
@ -70,9 +101,21 @@ def main() -> None:
|
||||||
packed = b"".join(blobs)
|
packed = b"".join(blobs)
|
||||||
OUT_BIN.write_bytes(packed)
|
OUT_BIN.write_bytes(packed)
|
||||||
|
|
||||||
# named lookup
|
|
||||||
by_name = {r[0]: r for r in rows if not r[0].startswith("(")}
|
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
|
hdr = f"""#ifndef __EXTFLASH_TONE_ADDR_H
|
||||||
#define __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_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_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
|
#define EXTFLASH_BIN3_UNIVERSAL_SIZE {by_name['BIN3_UNIVERSAL'][2]}UL
|
||||||
|
|
||||||
/* Convenience aliases used by UI ADDRESS */
|
/* 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"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"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",
|
f"{'Name':<22} {'Offset':>10} {'Size':>10} Note",
|
||||||
"-" * 72,
|
"-" * 72,
|
||||||
]
|
]
|
||||||
|
|
@ -132,17 +178,17 @@ def main() -> None:
|
||||||
"",
|
"",
|
||||||
"Firmware ADDRESS mapping:",
|
"Firmware ADDRESS mapping:",
|
||||||
" 普通/专业 -> FLASH_ADDR_MODE_NORMAL/EXPERT (1.bin)",
|
" 普通/专业 -> FLASH_ADDR_MODE_NORMAL/EXPERT (1.bin)",
|
||||||
" 海阔天空 -> FLASH_ADDR_SONG_HAITIAN (2.bin)",
|
" 本地曲目 -> FLASH_ADDR_SONG_HAITIAN (2.bin @ 0x9D07D)",
|
||||||
" 万能 -> FLASH_ADDR_MODE_UNIVERSAL (3.bin)",
|
" 万能 -> FLASH_ADDR_MODE_UNIVERSAL (3.bin @ 0xA71AC FIXED)",
|
||||||
" AutoBand -> FLASH_ADDR_AUTOBAND_LEGACY 0x9EB5F (unchanged; overlaps 2.bin region — do not enable until remapped)",
|
" 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",
|
" Boot logo -> UI0902_BOOT_LOGO_ADDR (full-screen); packed logo.bin only pads 0x0..0xCB70",
|
||||||
" Charging -> EXTFLASH_CHARGING_ADDR",
|
" Charging -> EXTFLASH_CHARGING_ADDR",
|
||||||
"",
|
"",
|
||||||
"Add a local song:",
|
"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)",
|
" 2. Append a row to local_songs.csv (index,code,name)",
|
||||||
" 3. python tools/pack_extflash_tone_0903.py (also regenerates LocalSongNames.h)",
|
" 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")
|
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}")
|
print(f"Wrote {OUT_MAP}")
|
||||||
for name, off, size, note in rows:
|
for name, off, size, note in rows:
|
||||||
print(f" 0x{off:08X} {size:8d} {name} {note}")
|
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")
|
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"
|
gen = ROOT / "tools" / "gen_local_song_names.py"
|
||||||
r = subprocess.run([sys.executable, str(gen)], check=False)
|
r = subprocess.run([sys.executable, str(gen)], check=False)
|
||||||
if r.returncode != 0:
|
if r.returncode != 0:
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,28 @@ def build_combined_extflash(tone: bytes, ui: bytes) -> bytes:
|
||||||
return tone + (b"\xFF" * pad) + ui
|
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:
|
def main() -> None:
|
||||||
if not BOOT_BIN.is_file():
|
if not BOOT_BIN.is_file():
|
||||||
raise SystemExit(f"missing Boot bin: {BOOT_BIN}")
|
raise SystemExit(f"missing Boot bin: {BOOT_BIN}")
|
||||||
|
|
@ -97,8 +119,10 @@ def main() -> None:
|
||||||
pkg.mkdir(parents=True)
|
pkg.mkdir(parents=True)
|
||||||
|
|
||||||
tone_data = TONE_BIN.read_bytes()
|
tone_data = TONE_BIN.read_bytes()
|
||||||
|
verify_tone_layout(tone_data)
|
||||||
ui_data = UI0902_BIN.read_bytes()
|
ui_data = UI0902_BIN.read_bytes()
|
||||||
combined = build_combined_extflash(tone_data, ui_data)
|
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"
|
boot_name = f"AT32F403ARCT7_BOOT_v{FW_VER}_{stamp}.bin"
|
||||||
mcu_name = f"YNGJ-GT1-M_MCU_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 分区:",
|
"外部 Flash 分区:",
|
||||||
" 0x00000000 音色/充电图 (toneRes)",
|
" 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、充电、烧录模式、设置/调音台等)",
|
" 0x00100000 UI0902 图片 (模式选择、开机 Logo、充电、烧录模式、设置/调音台等)",
|
||||||
" 0x001D2000 UI0902_FLASH_MODE 烧录模式全屏图(Boot 读取)",
|
" 0x001D2000 UI0902_FLASH_MODE 烧录模式全屏图(Boot 读取)",
|
||||||
"",
|
"",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue