SprPoP: обобщить HDD-сборку и очистить метаданные
Добавить общий каталог назначения для HDD и удалить локальную копию упаковщика.\n\nУбрать устаревшие generated-имена ресурсов, выводить число страниц Kid из kid.arc и ограничить звуковую таблицу горячим модулем.\n\nЗафиксировать планы runtime-индексов музыки и PCM-эффектов.
This commit is contained in:
@@ -1,92 +0,0 @@
|
||||
#!/bin/bash
|
||||
# make_hdd.sh OUT.chd FILE1 [FILE2 ...]
|
||||
#
|
||||
# Собирает 32 МБ RAW-образ жёсткого диска (MBR + один раздел FAT16) с
|
||||
# указанными файлами в корне и конвертит его в CHD для MAME (монтируется
|
||||
# как -hard2). Быстрая альтернатива floppy (mc.img): MAME эмулирует FDD
|
||||
# с реальной черепашьей скоростью, HDD — в разы быстрее.
|
||||
#
|
||||
# Требует: mtools (mpartition/mformat/mcopy) + chdman (из MAME).
|
||||
# Sprinter DSS штатно монтирует стандартный MBR+FAT16 (подтверждено).
|
||||
#
|
||||
# Использование:
|
||||
# tools/make_hdd.sh mame/v306/IMG/test_hdd.chd prog.exe data1 data2 ...
|
||||
# tools/make_hdd.sh out.chd prog.exe KID:kid0.atl GUARD:g0.atl ...
|
||||
# — аргумент вида КАТАЛОГ:файл кладёт файл в подкаталог образа
|
||||
# (каталог создаётся); без префикса файл идёт в корень.
|
||||
set -e
|
||||
|
||||
if [ $# -lt 2 ]; then
|
||||
echo "usage: $0 OUT.chd FILE1 [FILE2 ...]" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
OUT_CHD="$1"; shift
|
||||
|
||||
# Геометрия: 128*16*32*512 = 32 МБ. BPS=512.
|
||||
CYLS=128; HEADS=16; SECS=32
|
||||
SECTORS=$((CYLS * HEADS * SECS)) # 65536
|
||||
|
||||
# macOS mktemp требует X'ы в КОНЦЕ шаблона.
|
||||
RAW=$(mktemp /tmp/sphdd.raw.XXXXXX)
|
||||
MTC=$(mktemp /tmp/sphdd.mtc.XXXXXX)
|
||||
trap 'rm -f "$RAW" "$MTC"' EXIT
|
||||
|
||||
dd if=/dev/zero of="$RAW" bs=512 count=$SECTORS status=none
|
||||
|
||||
# mtools-диск z: = раздел 1 нашего RAW-образа.
|
||||
printf 'drive z: file="%s" partition=1\n' "$RAW" > "$MTC"
|
||||
export MTOOLSRC="$MTC" MTOOLS_SKIP_CHECK=1
|
||||
|
||||
# mtools собран с enable-raw-term и, когда ему нужно что-то спросить,
|
||||
# открывает УПРАВЛЯЮЩИЙ ТЕРМИНАЛ напрямую (/dev/tty) — в обход stdin.
|
||||
# Поэтому ни `< /dev/null`, ни перенаправление stdio не спасают: из
|
||||
# терминала сборка молча вставала навсегда на `mmd z:/BG` (подтверждено
|
||||
# lsof: fd 0 = /dev/null, fd 4 = /dev/tty, процесс спит). Лечится только
|
||||
# отсутствием управляющего терминала — запускаем mtools в новой сессии,
|
||||
# тогда открыть /dev/tty нечего и mtools идёт неинтерактивным путём.
|
||||
#
|
||||
# setsid(1) в macOS нет, поэтому делаем то же самое питоном.
|
||||
# Каналов, откуда mtools может ждать ответ, ДВА, и закрывать надо оба:
|
||||
# /dev/tty — убирается новой сессией (os.setsid);
|
||||
# stdin — убирается перенаправлением из /dev/null.
|
||||
# Закрыть только один недостаточно: с одним лишь setsid mmd переключался на
|
||||
# stdin и вис уже на нём (lsof: fd 0 = /dev/ttys002).
|
||||
mt() {
|
||||
python3 -c 'import os,sys; os.setsid(); os.execvp(sys.argv[1], sys.argv[1:])' \
|
||||
"$@" < /dev/null
|
||||
}
|
||||
|
||||
# MBR + раздел 1 на весь диск (тип FAT16), затем формат FAT16.
|
||||
echo "--- разметка и формат образа ---"
|
||||
mt mpartition -I -c -t $CYLS -h $HEADS -s $SECS z:
|
||||
mt mformat z:
|
||||
|
||||
echo "--- копирование файлов ---"
|
||||
|
||||
# Аргумент может быть либо файлом (кладём в корень), либо парой
|
||||
# КАТАЛОГ:файл — тогда каталог создаётся на образе и файл кладётся туда.
|
||||
# Имена на Sprinter/DSS — 8.3, каталоги тоже (KID, GUARD, BG, LEVELS...).
|
||||
mt mmd "z:/games" >/dev/null 2>&1 || true
|
||||
mt mmd "z:/games/sprpop" >/dev/null 2>&1 || true
|
||||
|
||||
for f in "$@"; do
|
||||
case "$f" in
|
||||
*:*) dir="${f%%:*}"; src="${f#*:}"
|
||||
# Каталог может уже быть — это не ошибка; stderr НЕ прячем, иначе
|
||||
# непрошедший вопрос mtools снова окажется невидимым.
|
||||
mt mmd "z:/games/sprpop/$dir" >/dev/null 2>&1 || true
|
||||
echo "COPY $src to z:/games/sprpop/$dir/"
|
||||
mt mcopy -o "$src" "z:/games/sprpop/$dir/" ;;
|
||||
*) mt mcopy -o "$f" "z:/games/sprpop/" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo "--- содержимое образа ---"
|
||||
mt mdir -/ z:
|
||||
|
||||
# RAW -> CHD (без сжатия = быстро; та же геометрия).
|
||||
echo "--- конвертация RAW -> CHD ---"
|
||||
chdman createhd -i "$RAW" -o "$OUT_CHD" -c none \
|
||||
-chs $CYLS,$HEADS,$SECS -ss 512 -f
|
||||
echo "готово: $OUT_CHD"
|
||||
@@ -450,7 +450,7 @@ def write_header(out_path, env_pages):
|
||||
файлов и палитра. Поэтому здесь два массива имён и один индекс."""
|
||||
lines = [
|
||||
"/* pop_bg_atlas.h — раскладка атласов статического фона PoP.",
|
||||
" * Сгенерировано toolchain/pop_pack_bg.py — НЕ править вручную.",
|
||||
" * Сгенерировано tools/pop_pack_bg.py — НЕ править вручную.",
|
||||
" *",
|
||||
" * Прямая адресация (ноль remap-таблиц в W2):",
|
||||
" * ENV фон id N -> atlas env_bg[N>>%d], idx N&%d" % (ENV_SHIFT, ENV_MASK),
|
||||
@@ -474,27 +474,12 @@ def write_header(out_path, env_pages):
|
||||
"#define POP_PAL_ENV 0x50",
|
||||
"#define POP_PAL_WALL 0x60",
|
||||
"",
|
||||
"/* Имена файлов атласов по тайлсету (грузятся atlas_load). Таблицы",
|
||||
" * видны только тому, кто попросил POP_BG_ATLAS_NAMES: иначе копия",
|
||||
" * строк уедет в каждый включивший заголовок модуль. */",
|
||||
"/* Палитры пока остаются отдельными файлами рядом с bg.arc. Имена",
|
||||
" * видит только модуль загрузки, чтобы не размножать строки. */",
|
||||
"#ifdef POP_BG_ATLAS_NAMES",
|
||||
"static const char *const pop_env_atl[POP_TILESETS][POP_ENV_PAGES] = {",
|
||||
]
|
||||
for prefix, _ in SETS.values():
|
||||
row = ", ".join(('"%s%s"' % (prefix, nm.split("_", 1)[1]) if nm else "0")
|
||||
for nm in env_pages)
|
||||
lines.append(" { %s }," % row)
|
||||
lines += [
|
||||
"};",
|
||||
'static const char *const pop_wall_atl[POP_TILESETS] = { %s };'
|
||||
% ", ".join('"%swall.atl"' % p for p, _ in SETS.values()),
|
||||
'static const char *const pop_fore_atl[POP_TILESETS] = { %s };'
|
||||
% ", ".join('"%sfore.atl"' % p for p, _ in SETS.values()),
|
||||
'static const char *const pop_tile_pal[POP_TILESETS] = { %s };'
|
||||
% ", ".join('"%stile.pal"' % p for p, _ in SETS.values()),
|
||||
"#endif /* POP_BG_ATLAS_NAMES */",
|
||||
'#define POP_POT_ATL "pop_pot.atl" /* chtab_1: зелья, от набора не зависит */',
|
||||
"#define POP_PAL_POT 0x%02X" % POT_PAL_BASE,
|
||||
"/* Пузырёк зелья: красный набор = id 16..22 (кадры оригинала),",
|
||||
" зелёный (перо/переворот) и синий (вред/открыть) = те же кадры",
|
||||
" под id %d..%d и %d..%d (draw_tile_anim, seg008:652). */"
|
||||
@@ -502,7 +487,6 @@ def write_header(out_path, env_pages):
|
||||
POT_BUBBLE_BLUE_BASE, POT_BUBBLE_BLUE_BASE + len(POT_BUBBLE_IDS) - 1),
|
||||
"#define POP_POT_BUBB_GREEN %d" % POT_BUBBLE_GREEN_BASE,
|
||||
"#define POP_POT_BUBB_BLUE %d" % POT_BUBBLE_BLUE_BASE,
|
||||
'#define POP_BG_PAL "pop_bg.pal"',
|
||||
"",
|
||||
"#endif",
|
||||
"",
|
||||
|
||||
@@ -11,7 +11,8 @@ Sprinter 0x70-0x7F (env 0x50 / wall 0x60 заняты фоном). Пиксел
|
||||
фейковые записи (как в фоне). Переиспользует pack_atlas/blob/scale6to8
|
||||
из pop_pack_bg.
|
||||
|
||||
Выход: build/atl/kid/kid0..N.atl + kid.pal + kid_atlas.h
|
||||
Выход: build/atl/kid/kid0..N.atl + зеркальные атласы + kid.pal.
|
||||
Количество страниц рантайм получает из сгенерированного описания kid_arc.h.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
@@ -200,21 +201,6 @@ def main():
|
||||
|
||||
build_palette(os.path.join(OUT_DIR, "kid.pal"))
|
||||
|
||||
# C-заголовок
|
||||
hdr = str(P.GEN / "kid_atlas.h")
|
||||
with open(hdr, "w") as f:
|
||||
f.write("/* kid_atlas.h — раскладка атласов Kid. Сгенерировано "
|
||||
"pop_pack_kid.py. */\n#ifndef KID_ATLAS_H\n#define KID_ATLAS_H\n")
|
||||
f.write(f"#define KID_SHIFT {SHIFT}\n#define KID_MASK {MASK}\n")
|
||||
f.write(f"#define KID_PAGES {len(names)}\n#define KID_PAL 0x{PAL_BASE:02X}\n")
|
||||
f.write("static const char *const kid_atl[KID_PAGES] = {\n")
|
||||
for nm in names:
|
||||
f.write(" %s,\n" % ('"%s"' % nm if nm else "0"))
|
||||
f.write('};\n#define KID_PAL_FILE "kid.pal"\n')
|
||||
f.write('#define KID_SWORD_ATL "sword.atl" /* chtab_0: меч в руке */\n')
|
||||
f.write(f"#define KID_SWORD_ID0 {SWORD_IDS[0]} /* индекс в атласе = sword_tbl.id - ID0 */\n")
|
||||
f.write("#endif\n")
|
||||
|
||||
print(f"ИТОГО: {len(ids)} спрайтов Kid, {len(names)} EMM-страниц, "
|
||||
f"{total} Б диск (+1КБ палитра) -> {OUT_DIR}")
|
||||
|
||||
|
||||
@@ -214,7 +214,7 @@ def write_header(path, counts, pal):
|
||||
vals += [b, g, r, 0] # формат gfx_pal_load: B, G, R, 0
|
||||
lines = [
|
||||
"/* pop_shadow_atlas.h — раскладка атласа ТЕНИ.",
|
||||
" * СГЕНЕРЕНО toolchain/pop_pack_shadow.py — НЕ править вручную.",
|
||||
" * СГЕНЕРЕНО tools/pop_pack_shadow.py — НЕ править вручную.",
|
||||
" *",
|
||||
" * Две половины, потому что и у оригинала их две (см. шапку",
|
||||
" * упаковщика): кадры вне боя адресуют chtab_2 (спрайты Кида),",
|
||||
@@ -240,18 +240,6 @@ def write_header(path, counts, pal):
|
||||
"/* Половина «в бою» — SHADOW.DAT (chtab_5, кадры 150..189). */",
|
||||
"#define POP_SHADOW_FGT_PAGES %d" % counts["sf"],
|
||||
"",
|
||||
"static const char *const pop_shadow_kid_atl[POP_SHADOW_KID_PAGES] = {",
|
||||
]
|
||||
lines += [' "SHADOW\\\\sk%d.atl",' % i for i in range(counts["sk"])]
|
||||
lines += [
|
||||
"};",
|
||||
"",
|
||||
"static const char *const pop_shadow_fgt_atl[POP_SHADOW_FGT_PAGES] = {",
|
||||
]
|
||||
lines += [' "SHADOW\\\\sf%d.atl",' % i for i in range(counts["sf"])]
|
||||
lines += [
|
||||
"};",
|
||||
"",
|
||||
"/* 16 цветов, запись 4 байта (B, G, R, 0) — формат gfx_pal_load. */",
|
||||
"static const uint8_t pop_shadow_pal[64] = {",
|
||||
" " + ",".join("0x%02X" % v for v in vals) + ",",
|
||||
|
||||
@@ -155,7 +155,7 @@ def main():
|
||||
|
||||
lines = [
|
||||
"/* pop_sound_tbl.h — раскладка оцифрованных эффектов PoP.",
|
||||
" * СГЕНЕРЕНО toolchain/pop_pack_sound.py — НЕ править вручную.",
|
||||
" * СГЕНЕРЕНО tools/pop_pack_sound.py — НЕ править вручную.",
|
||||
" *",
|
||||
" * Данные: 8 бит беззнаковые, МОНО, %g Гц (CBL_FREQ_10K9) — формат" % RATE,
|
||||
" * CBL один в один, конвертировать в рантайме нечего.",
|
||||
@@ -180,6 +180,8 @@ def main():
|
||||
"#define POP_SND_SILENCE_PAGE 0",
|
||||
"#define POP_SND_SILENCE_OFF 0x0000",
|
||||
"",
|
||||
"/* Полная таблица нужна только горячей половине звука. */",
|
||||
"#ifdef POP_SND_TABLE",
|
||||
"typedef struct { uint8_t page; uint16_t off; uint16_t len; } pop_snd_ent_t;",
|
||||
"",
|
||||
"/* len == 0 — у звука нет оцифровки (это музыка, см. sound_plan.md). */",
|
||||
@@ -190,16 +192,11 @@ def main():
|
||||
lines.append(" { %d, 0x%04X, %5d }, /* %2d */" % (off >> 14, off & 0x3FFF, ln, sid))
|
||||
lines += [
|
||||
"};",
|
||||
"#endif /* POP_SND_TABLE */",
|
||||
"",
|
||||
"#endif",
|
||||
"",
|
||||
"/* Имена файлов видит ТОЛЬКО тот, кто попросил POP_SND_FILES (холодная",
|
||||
" * половина): иначе копия строк уезжает в каждый включивший заголовок",
|
||||
" * модуль, а в резиденте W1/W2 каждая сотня байт на счету. Тот же",
|
||||
" * приём, что у POP_BG_ATLAS_NAMES в pop_bg_atlas.h. */",
|
||||
"#ifdef POP_SND_FILES",
|
||||
"static const char *const pop_snd_files[POP_SND_PAGES] = {",
|
||||
]
|
||||
lines += [' "SND\\\\s%d.bin",' % i for i in range(npages)]
|
||||
lines += ["};", "#endif", "", "#endif", ""]
|
||||
with open(HDR, "w") as f:
|
||||
f.write("\n".join(lines))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user