pimpmyplus/disk.py

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.