#!/bin/bash # make_hdd.sh [--dest DIR] OUT.chd FILE_OR_TREE1 [FILE_OR_TREE2 ...] # # Собирает 32 МБ RAW-образ жёсткого диска (MBR + один раздел FAT16) с # указанными файлами и конвертит его в CHD для MAME (монтируется как -hard2). # По умолчанию файлы кладутся в корень. --dest DIR (или HDD_DEST_DIR) # переносит всё дерево под DIR, например games/sprpop. # # Требует: mtools (mpartition/mformat/mcopy) + chdman (из MAME). # CHDMAN_BIN может указывать на установленный chdman вне PATH. # Sprinter DSS штатно монтирует стандартный MBR+FAT16 (подтверждено). # # Использование: # toolchain/make_hdd.sh build/hdd/test.chd prog.exe data1 data2 ... # toolchain/make_hdd.sh --dest games/sprpop out.chd prog.exe data1 ... # toolchain/make_hdd.sh out.chd prog.exe KID:kid0.atl GUARD:g0.atl ... # — аргумент вида КАТАЛОГ:файл кладёт файл в подкаталог относительно # DIR; без префикса файл идёт непосредственно в DIR. # toolchain/make_hdd.sh out.chd prog.exe /tmp/test-tree # — каталог копируется рекурсивно вместе со своим именем. Это заметно # ускоряет сборку нагрузочных образов с сотнями мелких файлов. set -e DEST_DIR=${HDD_DEST_DIR:-} while [ $# -gt 0 ]; do case "$1" in --dest) if [ $# -lt 2 ]; then echo "make_hdd.sh: --dest требует путь" >&2 exit 1 fi DEST_DIR="$2" shift 2 ;; --dest=*) DEST_DIR="${1#--dest=}" shift ;; --) shift break ;; *) break ;; esac done if [ $# -lt 2 ]; then echo "usage: $0 [--dest DIR] OUT.chd FILE1 [FILE2 ...]" >&2 exit 1 fi for command in mpartition mformat mcopy mmd mdir; do if ! command -v "$command" >/dev/null 2>&1; then echo "make_hdd.sh: не найден host-инструмент $command (mtools)" >&2 exit 1 fi done CHDMAN_BIN=${CHDMAN_BIN:-chdman} if ! command -v "$CHDMAN_BIN" >/dev/null 2>&1; then echo "make_hdd.sh: не найден chdman: $CHDMAN_BIN" >&2 exit 1 fi OUT_CHD="$1"; shift # DSS использует обратный слэш, но mtools — прямой. Ведущие/замыкающие # разделители не несут смысла: DIR всегда задаётся относительно корня HDD. DEST_DIR="${DEST_DIR//\\//}" while [ "${DEST_DIR#/}" != "$DEST_DIR" ]; do DEST_DIR="${DEST_DIR#/}"; done while [ "${DEST_DIR%/}" != "$DEST_DIR" ]; do DEST_DIR="${DEST_DIR%/}"; done case "/$DEST_DIR/" in */./*|*/../*|*:* ) echo "make_hdd.sh: недопустимый каталог назначения: $DEST_DIR" >&2 exit 1 ;; esac # Геометрия: 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 } # mmd не создаёт родителей сам: строим путь по одному компоненту. Пустой # путь означает корень и сохраняет прежнее поведение скрипта. make_dir_tree() { local path="$1" part current="" old_ifs="$IFS" [ -z "$path" ] && return IFS='/' for part in $path; do [ -z "$part" ] && continue current="${current:+$current/}$part" mt mmd "z:/$current" >/dev/null 2>&1 || true done IFS="$old_ifs" } # MBR + раздел 1 на весь диск (тип FAT16), затем формат FAT16. echo "--- разметка и формат образа ---" mt mpartition -I -c -t $CYLS -h $HEADS -s $SECS z: mt mformat z: echo "--- копирование файлов ---" make_dir_tree "$DEST_DIR" DEST_ROOT="z:${DEST_DIR:+/$DEST_DIR}" # Аргумент может быть файлом, деревом каталогов либо парой КАТАЛОГ:файл. # Дерево копируется рекурсивно вместе с именем своего корня. Имена на # Sprinter/DSS — 8.3. for f in "$@"; do case "$f" in *:*) dir="${f%%:*}"; src="${f#*:}" target_dir="${DEST_DIR:+$DEST_DIR/}$dir" # Каталог может уже быть — это не ошибка; stderr НЕ прячем, иначе # непрошедший вопрос mtools снова окажется невидимым. make_dir_tree "$target_dir" mt mcopy -o "$src" "z:/$target_dir/" ;; *) if [ -d "$f" ]; then mt mcopy -s -o "$f" "$DEST_ROOT/" else mt mcopy -o "$f" "$DEST_ROOT/" fi ;; esac done echo "--- содержимое образа ---" mt mdir -/ z: # RAW -> CHD (без сжатия = быстро; та же геометрия). echo "--- конвертация RAW -> CHD ---" "$CHDMAN_BIN" createhd -i "$RAW" -o "$OUT_CHD" -c none \ -chs $CYLS,$HEADS,$SECS -ss 512 -f echo "готово: $OUT_CHD"