164 lines
6.9 KiB
Bash
Executable File
164 lines
6.9 KiB
Bash
Executable File
#!/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"
|