243 lines
7.8 KiB
Python
243 lines
7.8 KiB
Python
# https://developer.apple.com/library/archive/documentation/mac/pdf/Devices/SCSI_Manager.pdf
|
|
|
|
import configparser
|
|
from ctypes import BigEndianStructure, c_uint16, c_uint32, c_char
|
|
from typing import BinaryIO, List
|
|
|
|
import machfs
|
|
|
|
|
|
class DriverIni(object):
|
|
def __init__(self):
|
|
self.partition_type = b'Apple_Driver43'
|
|
self.partition_flags = 0
|
|
self.booter = 0
|
|
self.bytes = 0
|
|
self.load_address_0 = 0
|
|
self.load_address_1 = 0
|
|
self.goto_address_0 = 0
|
|
self.goto_address_1 = 0
|
|
self.checksum = 0
|
|
self.processor = b'68000'
|
|
self.boot_args: List[int] = []
|
|
|
|
|
|
def driver_from_ini(section) -> DriverIni:
|
|
ini = DriverIni()
|
|
ini.partition_type = bytes(section['partition_type'], encoding='ascii')
|
|
ini.partition_flags = int(section['partition_flags'])
|
|
ini.booter = int(section['booter'])
|
|
ini.bytes = int(section['bytes'])
|
|
ini.load_address_0 = int(section['load_address_0'], 16)
|
|
ini.load_address_1 = int(section['load_address_1'], 16)
|
|
ini.goto_address_0 = int(section['goto_address_0'], 16)
|
|
ini.goto_address_1 = int(section['goto_address_1'], 16)
|
|
ini.checksum = int(section['checksum'], 16)
|
|
ini.processor = bytes(section['processor'], encoding='ascii')
|
|
ini.boot_args = [int(x, 0) for x in section['boot_args'].split(',')]
|
|
return ini
|
|
|
|
|
|
class DriverDescriptor(BigEndianStructure):
|
|
_pack_ = 1
|
|
_fields_ = [
|
|
('ddBlock', c_uint32),
|
|
('ddSize', c_uint16),
|
|
('ddType', c_uint16),
|
|
]
|
|
|
|
|
|
class Block0(BigEndianStructure):
|
|
_pack_ = 1
|
|
_fields_ = [
|
|
('sbSig', c_uint16),
|
|
('sbBlkSize', c_uint16),
|
|
('sbBlkCount', c_uint32),
|
|
|
|
# Dev Type and Dev Id both have no information from apple.
|
|
# Apple's hfdisk utility assigns zero for both, but System 6's disk utility sets these both to 1.
|
|
# I have no idea if these fields are used at all.
|
|
('sbDevType', c_uint16),
|
|
('sbDevId', c_uint16),
|
|
|
|
# Reserved. Seems to be unused by anything.
|
|
('sbData', c_uint32),
|
|
|
|
('sbDrvrCount', c_uint16),
|
|
('ddDrivers', DriverDescriptor * 61),
|
|
|
|
('_pad1', c_uint32),
|
|
('_pad2', c_uint16)
|
|
]
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.sbSig = 0x4552 # sbSIGWord magic number.
|
|
|
|
|
|
class PartitionMapBlock(BigEndianStructure):
|
|
_pack_ = 1
|
|
_fields_ = [
|
|
('dpme_signature', c_uint16),
|
|
('dpme_sigPad', c_uint16),
|
|
('dpme_map_entries', c_uint32),
|
|
('dpme_pblock_start', c_uint32),
|
|
('dpme_pblocks', c_uint32),
|
|
('dpme_name', c_char * 32),
|
|
('dpme_type', c_char * 32),
|
|
('dpme_lblock_start', c_uint32),
|
|
('dpme_lblocks', c_uint32),
|
|
|
|
# Apple Docs say this is only used by A/UX. That is not 100% true.
|
|
('dpme_flags', c_uint32),
|
|
|
|
# Note: Below data appears to only be used for SCSI Driver partitions
|
|
('dpme_boot_block', c_uint32),
|
|
('dpme_boot_bytes', c_uint32),
|
|
('dpme_load_addr', c_uint32),
|
|
('dpme_load_addr_2', c_uint32),
|
|
('dpme_goto_addr', c_uint32),
|
|
('dpme_goto_addr_2', c_uint32),
|
|
('dpme_checksum', c_uint32),
|
|
('dpme_process_id', c_char * 16),
|
|
('dpme_boot_args', c_uint32 * 32),
|
|
('dpme_reserved_3', c_uint32 * 62)
|
|
]
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.dpme_signature = 0x504d # "PM"
|
|
|
|
|
|
def create_basic_partition(name, type, start_block, block_count, flags) -> PartitionMapBlock:
|
|
block = PartitionMapBlock()
|
|
block.dpme_pblock_start = start_block
|
|
block.dpme_pblocks = block_count
|
|
block.dpme_name = name
|
|
block.dpme_type = type
|
|
block.dpme_lblocks = block_count
|
|
block.dpme_flags = flags
|
|
return block
|
|
|
|
|
|
def create_partition_map_partition() -> PartitionMapBlock:
|
|
block = PartitionMapBlock()
|
|
block.dpme_pblock_start = 1
|
|
block.dpme_pblocks = 63
|
|
block.dpme_name = b'Apple'
|
|
block.dpme_type = b'Apple_partition_map'
|
|
block.dpme_lblocks = 63
|
|
return block
|
|
|
|
|
|
def create_driver_partition_block(info: DriverIni, start_block: int, block_count: int) -> PartitionMapBlock:
|
|
block = PartitionMapBlock()
|
|
block.dpme_pblock_start = start_block
|
|
block.dpme_pblocks = block_count
|
|
block.dpme_name = b'Macintosh'
|
|
block.dpme_type = info.partition_type
|
|
block.dpme_lblocks = block_count
|
|
block.dpme_flags = info.partition_flags
|
|
block.dpme_boot_block = info.booter
|
|
block.dpme_boot_bytes = info.bytes
|
|
block.dpme_load_addr = info.load_address_0
|
|
block.dpme_load_addr_2 = info.load_address_1
|
|
block.dpme_goto_addr = info.goto_address_0
|
|
block.dpme_goto_addr_2 = info.goto_address_1
|
|
block.dpme_checksum = info.checksum
|
|
block.dpme_process_id = info.processor
|
|
for i, value in enumerate(info.boot_args):
|
|
block.dpme_boot_args[i] = value
|
|
return block
|
|
|
|
|
|
def create_bootable_disk(of: BinaryIO, volume: machfs.Volume, block_count: int):
|
|
"""
|
|
|
|
:param of:
|
|
:param volume:
|
|
:param block_count: Total blocks in the disk, including blocks used by block0, partition map, and all partitions.
|
|
:return:
|
|
"""
|
|
|
|
driver_ini = configparser.ConfigParser()
|
|
driver_ini.read('driver.ini')
|
|
driver_info = driver_from_ini(driver_ini['Driver'])
|
|
|
|
block0 = Block0()
|
|
block0.sbBlkSize = 512
|
|
block0.sbBlkCount = block_count
|
|
|
|
block0.sbDrvrCount = 1
|
|
descriptor = DriverDescriptor()
|
|
descriptor.ddBlock = 64
|
|
descriptor.ddSize = int(driver_info.bytes / int(512) + (1 if driver_info.bytes % int(512) != 0 else 0))
|
|
descriptor.ddType = 1 # Always 1
|
|
block0.ddDrivers[0] = descriptor
|
|
|
|
# Set these both to 1, just in case. See comment in Block0 class.
|
|
# TODO: Once we get a booting disk on a real MacPlus, try removing and see if it still works.
|
|
block0.sbDevType = 1
|
|
block0.sbDevId = 1
|
|
|
|
block0_bytes = bytes(block0)
|
|
if len(block0_bytes) != 512:
|
|
raise ValueError('ASSERTION FAILED! sizeof(Block0) != 512')
|
|
of.write(block0_bytes)
|
|
|
|
def write_partition_map_block(block: PartitionMapBlock):
|
|
block_bytes = bytes(block)
|
|
if len(block_bytes) != 512:
|
|
raise ValueError('ASSERTION FAILED! sizeof(PartitionMapBlock) != 512')
|
|
of.write(block_bytes)
|
|
|
|
volume_offset = 64 + 32 # Block0 + Partition Map + Driver
|
|
volume_block_count = block_count - volume_offset
|
|
|
|
partition_map_0 = create_basic_partition(
|
|
name=b'MacOS',
|
|
type=b'Apple_HFS',
|
|
start_block=volume_offset,
|
|
block_count=volume_block_count,
|
|
flags=0)
|
|
partition_map_0.dpme_map_entries = 3
|
|
write_partition_map_block(partition_map_0)
|
|
|
|
partition_map_1 = create_partition_map_partition()
|
|
partition_map_1.dpme_map_entries = 3
|
|
write_partition_map_block(partition_map_1)
|
|
|
|
partition_map_2 = create_driver_partition_block(driver_info, 64, 32)
|
|
partition_map_2.dpme_map_entries = 3
|
|
write_partition_map_block(partition_map_2)
|
|
|
|
# Write empty partition map entries
|
|
empty_block = b'\0' * 512
|
|
for i in range(1 + 3, 64): # 3 is partition map block count TODO: Kill all magic numbers
|
|
of.write(empty_block)
|
|
|
|
# Write Driver
|
|
with open('driver.bin', 'rb') as f:
|
|
for i in range(32):
|
|
|
|
of.write(f.read(512))
|
|
|
|
# Write HFS Volume
|
|
volume_data = volume.write(
|
|
size=volume_block_count * 512,
|
|
# desktopdb=False,
|
|
bootable=False
|
|
)
|
|
|
|
if len(volume_data) != volume_block_count * 512:
|
|
raise ValueError('ASSERTION FAILED! len(volume_data) != volume_block_count * 512')
|
|
of.write(volume_data)
|
|
|
|
if of.tell() != block_count * 512:
|
|
raise ValueError('Error! Output file is not expected size!')
|
|
|
|
|
|
def mb_block_count(mb: int) -> int:
|
|
kb = mb * 1024
|
|
return kb * 2 # 2 512-byte blocks = 1 kb.
|