Подготовить автономную сборку и запуск перед разделением проектов

This commit is contained in:
Александр Петров
2026-09-15 23:04:04 +03:00
parent 5323b168a7
commit 0e74aaa7ee
116 changed files with 6908 additions and 333 deletions
+242
View File
@@ -0,0 +1,242 @@
#!/usr/bin/env python3
"""Упаковщик FAT12-дискеты Sprinter. Образ принадлежит вызывающему проекту."""
import struct
import sys
import os
def create_floppy_image(output_file, files_to_add):
"""Создаёт FAT12 образ дискеты 1.44MB и добавляет файлы"""
# Размер дискеты 1.44MB
SECTOR_SIZE = 512
TOTAL_SECTORS = 2880
FAT_SIZE = 9
ROOT_DIR_ENTRIES = 224
# Проверяем весь набор до открытия выходного файла: ошибочная упаковка
# не должна уничтожать предыдущий пригодный носитель приложения.
capacity = TOTAL_SECTORS - 1 - 2 * FAT_SIZE - ROOT_DIR_ENTRIES * 32 // SECTOR_SIZE
needed = 0
names = set()
if len(files_to_add) > ROOT_DIR_ENTRIES:
raise ValueError("слишком много файлов для корневого каталога FAT12")
for filename, src_path in files_to_add:
name, ext = os.path.splitext(filename)
short_name = (name[:8].upper(), ext[1:4].upper())
# Исторические тесты SDK могут иметь длинное имя; на носителе оно
# сокращается до 8.3. Коллизия после сокращения — настоящая ошибка.
if not name or short_name in names:
raise ValueError(f"пустое или повторное имя FAT 8.3: {filename}")
short_name[0].encode('ascii')
short_name[1].encode('ascii')
names.add(short_name)
needed += max(1, (os.path.getsize(src_path) + SECTOR_SIZE - 1) // SECTOR_SIZE)
if needed > capacity:
raise ValueError(f"данные не помещаются на дискету: {needed} > {capacity} секторов")
os.makedirs(os.path.dirname(os.path.abspath(output_file)), exist_ok=True)
# Создаём пустой образ
with open(output_file, 'wb') as f:
f.write(b'\x00' * (TOTAL_SECTORS * SECTOR_SIZE))
# Читаем и модифицируем образ
with open(output_file, 'r+b') as f:
# BPB (BIOS Parameter Block) - начинаем с смещения 0
# Jump instruction
f.seek(0)
f.write(b'\xEB\x3C\x90')
# OEM name
f.write(b'MSDOS5.0')
# Bytes per sector
f.write(struct.pack('<H', SECTOR_SIZE))
# Sectors per cluster
f.write(struct.pack('B', 1))
# Reserved sectors
f.write(struct.pack('<H', 1))
# Number of FATs
f.write(struct.pack('B', 2))
# Root directory entries
f.write(struct.pack('<H', ROOT_DIR_ENTRIES))
# Total sectors (16-bit)
f.write(struct.pack('<H', 0))
# Media descriptor
f.write(struct.pack('B', 0xF0))
# Sectors per FAT
f.write(struct.pack('<H', FAT_SIZE))
# Sectors per track
f.write(struct.pack('<H', 18))
# Heads
f.write(struct.pack('<H', 2))
# Hidden sectors
f.write(struct.pack('<I', 0))
# Total sectors (32-bit)
f.write(struct.pack('<I', TOTAL_SECTORS))
# Extended BPB
# Drive number
f.write(struct.pack('B', 0))
# Reserved
f.write(struct.pack('B', 0))
# Extended boot signature
f.write(struct.pack('B', 0x29))
# Volume serial number
f.write(struct.pack('<I', 0x12345678))
# Volume label
f.write(b'NO NAME ')
# File system type
f.write(b'FAT12 ')
# Boot signature
f.seek(510)
f.write(struct.pack('<H', 0xAA55))
# FAT таблицы
fat_start = 1 * SECTOR_SIZE
fat1_start = fat_start
fat2_start = fat_start + FAT_SIZE * SECTOR_SIZE
# Инициализация FAT
# Первые две записи зарезервированы
fat = [0xFFF8, 0xFFFF]
# Добавляем файлы
root_dir_start = (1 + 2 * FAT_SIZE) * SECTOR_SIZE
data_start = root_dir_start + ROOT_DIR_ENTRIES * 32
cluster = 2 # Первый кластер данных
for filename, src_path in files_to_add:
# Читаем файл
with open(src_path, 'rb') as src:
file_data = src.read()
file_size = len(file_data)
print(f"Добавляем файл: {filename}, размер: {file_size} байт (0x{file_size:04X})")
# Вычисляем количество кластеров
clusters_needed = max(1, (file_size + SECTOR_SIZE - 1) // SECTOR_SIZE)
# Добавляем записи в FAT
first_cluster = cluster
for i in range(clusters_needed):
if i == clusters_needed - 1:
fat.append(0xFFFF) # Последний кластер
else:
fat.append(cluster + 1)
cluster += 1
# Записываем данные файла
file_offset = (first_cluster - 2) * SECTOR_SIZE
f.seek(data_start + file_offset)
f.write(file_data)
# Создаём запись в корневом каталоге
# Имя файла (8.3 формат)
name, ext = os.path.splitext(filename)
name = name.upper().ljust(8, ' ')[:8]
# ext comes from splitext as ".XYZ" (with leading dot) or "".
# Strip the dot first, then space-pad to 3 chars. The old form
# `ext.upper().ljust(3, ' ')[1:4]` lost padding for 2-char
# extensions like ".MD" → "MD" (2 bytes), corrupting the dir
# entry layout.
ext = ext[1:].upper().ljust(3, ' ')[:3] if ext else ' '
# Атрибуты
attr = 0x20 # Archive
print(f"Атрибуты: 0x{attr:02X}")
# Время и дата (упрощённо)
time_val = 0x0000
date_val = 0x0021
# Первый кластер (FAT12: младший байт по смещению 26, старший по 27)
first_cluster_lo = first_cluster & 0xFF
first_cluster_hi = (first_cluster >> 8) & 0xFF
# Размер файла (пробуем big-endian для Sprinter)
file_size_bytes = struct.pack('<I', file_size)
print(f"Размер файла в записи: {file_size_bytes.hex()}")
# Записываем в корневой каталог (32 байта)
dir_entry = bytearray(32)
dir_entry[0:8] = name.encode('ascii')
dir_entry[8:11] = ext.encode('ascii')
dir_entry[11] = attr
dir_entry[12:22] = b'\x00' * 10 # Reserved
dir_entry[22:24] = struct.pack('<H', time_val)
dir_entry[24:26] = struct.pack('<H', date_val)
dir_entry[26] = first_cluster_lo
dir_entry[27] = first_cluster_hi
dir_entry[28:32] = file_size_bytes
print(f"Атрибуты в записи: 0x{dir_entry[11]:02X}")
print(f"Запись каталога: {dir_entry.hex()}")
# Находим свободное место в корневом каталоге
f.seek(root_dir_start)
found = False
for i in range(ROOT_DIR_ENTRIES):
entry_pos = root_dir_start + i * 32
f.seek(entry_pos)
first_byte = f.read(1)
if first_byte == b'\x00' or first_byte == b'\xE5':
f.seek(entry_pos)
f.write(dir_entry)
found = True
print(f"Записан в корневой каталог, запись #{i}, позиция 0x{entry_pos:04X}")
break
if not found:
print(f"Ошибка: нет места в корневом каталоге для {filename}")
return False
# Записываем FAT
# FAT12 кодирование: 3 байта на 2 записи
fat_bytes = bytearray()
for i in range(0, len(fat), 2):
if i + 1 < len(fat):
val1 = fat[i] & 0xFFF
val2 = fat[i + 1] & 0xFFF
fat_bytes.extend([
val1 & 0xFF,
((val2 & 0xF) << 4) | ((val1 >> 8) & 0xF),
(val2 >> 4) & 0xFF
])
else:
val1 = fat[i] & 0xFFF
fat_bytes.extend([
val1 & 0xFF,
(val1 >> 8) & 0xF,
0x00
])
# Записываем FAT1
f.seek(fat1_start)
f.write(fat_bytes[:FAT_SIZE * SECTOR_SIZE])
# Записываем FAT2
f.seek(fat2_start)
f.write(fat_bytes[:FAT_SIZE * SECTOR_SIZE])
print(f"Создан образ: {output_file}")
print(f"Использовано кластеров: {cluster - 2}")
return True
if __name__ == '__main__':
if len(sys.argv) < 3:
print("Использование: python3 make_disk.py <output.img> <file1> [file2 ...]")
sys.exit(1)
output_file = sys.argv[1]
files = []
for i in range(2, len(sys.argv)):
files.append((os.path.basename(sys.argv[i]), sys.argv[i]))
try:
ok = create_floppy_image(output_file, files)
except (OSError, ValueError, UnicodeEncodeError) as error:
print(f"make_disk: {error}", file=sys.stderr)
sys.exit(1)
sys.exit(0 if ok else 1)