mirror of
https://github.com/robmcmullen/atrcopy.git
synced 2024-12-27 23:32:06 +00:00
Merge branch 'fstbt'
This commit is contained in:
commit
3a0774d00d
@ -453,3 +453,4 @@ Turns out there are a ton of Apple ][ disk imaging tools! I was pointed to the l
|
||||
* `dos33fsprogs <https://github.com/deater/dos33fsprogs>`_ (C)
|
||||
* `apple2-disk-util <https://github.com/slotek/apple2-disk-util>`_ (Ruby)
|
||||
* `dsk2nib <https://github.com/slotek/dsk2nib>`_ (C)
|
||||
* `standard-delivery <https://github.com/peterferrie/standard-delivery>`_ (6502 assembly) Apple II single-sector fast boot-loader
|
||||
|
@ -22,7 +22,7 @@ from .kboot import KBootImage, add_xexboot_header
|
||||
from .segments import SegmentData, SegmentSaver, DefaultSegment, EmptySegment, ObjSegment, RawSectorsSegment, SegmentedFileSegment, user_bit_mask, match_bit_mask, comment_bit_mask, data_style, selected_bit_mask, diff_bit_mask, not_user_bit_mask, interleave_segments, SegmentList, get_style_mask, get_style_bits
|
||||
from .spartados import SpartaDosDiskImage
|
||||
from .cartridge import A8CartHeader, AtariCartImage
|
||||
from .parsers import SegmentParser, DefaultSegmentParser, guess_parser_for_mime, guess_parser_for_system, iter_parsers, iter_known_segment_parsers, mime_parse_order
|
||||
from .parsers import SegmentParser, DefaultSegmentParser, guess_parser_for_mime, guess_parser_for_system, iter_parsers, iter_known_segment_parsers, mime_parse_order, parsers_for_filename
|
||||
from .utils import to_numpy, text_to_int
|
||||
|
||||
|
||||
@ -178,7 +178,7 @@ def crc_files(image, files):
|
||||
print("%s: %08x" % (dirent.filename, crc))
|
||||
|
||||
|
||||
def assemble(image, source_files, data_files, obj_files, run_addr=""):
|
||||
def assemble_segments(source_files, data_files, obj_files, run_addr=""):
|
||||
if source_files:
|
||||
try:
|
||||
import pyatasm
|
||||
@ -238,6 +238,10 @@ def assemble(image, source_files, data_files, obj_files, run_addr=""):
|
||||
except ValueError:
|
||||
run_addr = None
|
||||
|
||||
return segments, run_addr
|
||||
|
||||
def assemble(image, source_files, data_files, obj_files, run_addr=""):
|
||||
segments, run_addr = assemble_segments(source_files, data_files, obj_files, run_addr)
|
||||
file_data, filetype = image.create_executable_file_image(segments, run_addr)
|
||||
print("total file size: $%x (%d) bytes" % (len(file_data), len(file_data)))
|
||||
changed = save_file(image, options.output, filetype, file_data)
|
||||
@ -245,6 +249,14 @@ def assemble(image, source_files, data_files, obj_files, run_addr=""):
|
||||
image.save()
|
||||
|
||||
|
||||
def boot_image(image_name, source_files, data_files, obj_files, run_addr=""):
|
||||
image_cls = parsers_for_filename(image_name)[0]
|
||||
segments, run_addr = assemble_segments(source_files, data_files, obj_files, run_addr)
|
||||
image = image_cls.create_boot_image(segments, run_addr)
|
||||
print("saving boot disk %s" % (image_name))
|
||||
image.save(image_name)
|
||||
|
||||
|
||||
def shred_image(image, value=0):
|
||||
print("shredding: free sectors from %s filled with %d" % (image, value))
|
||||
if not options.dry_run:
|
||||
@ -373,6 +385,7 @@ def run():
|
||||
"extract": ["x"],
|
||||
"add": ["a"],
|
||||
"create": ["c"],
|
||||
"boot": ["b"],
|
||||
"assemble": ["s", "asm"],
|
||||
"delete": ["rm", "del"],
|
||||
"vtoc": ["v"],
|
||||
@ -434,6 +447,14 @@ def run():
|
||||
assembly_parser.add_argument("-r", "--run-addr", "--brun", action="store", default="", help="run address of binary file if not the first byte of the first segment")
|
||||
assembly_parser.add_argument("-o", "--output", action="store", default="", required=True, help="output file name in disk image")
|
||||
|
||||
command = "boot"
|
||||
boot_parser = subparsers.add_parser(command, help="Create a bootable disk image", aliases=command_aliases[command])
|
||||
boot_parser.add_argument("-f", "--force", action="store_true", default=False, help="allow file overwrites in the disk image")
|
||||
boot_parser.add_argument("-s", "--asm", nargs="*", action="append", help="source file(s) to assemble using pyatasm")
|
||||
boot_parser.add_argument("-d","--data", nargs="*", action="append", help="binary data file(s) to add to assembly, specify as file@addr. Only a portion of the file may be included; specify the subset using standard python slice notation: file[subset]@addr")
|
||||
boot_parser.add_argument("-b", "--obj", "--bload", nargs="*", action="append", help="binary file(s) to add to assembly, either executables or labeled memory dumps (e.g. BSAVE on Apple ][), parsing each file's binary segments to add to the resulting disk image at the load address for each segment")
|
||||
boot_parser.add_argument("-r", "--run-addr", "--brun", action="store", default="", help="run address of binary file if not the first byte of the first segment")
|
||||
|
||||
command = "delete"
|
||||
delete_parser = subparsers.add_parser(command, help="Delete files from the disk image", aliases=command_aliases[command])
|
||||
delete_parser.add_argument("-f", "--force", action="store_true", default=False, help="remove the file even if it is write protected ('locked' in Atari DOS 2 terms), if write-protect is supported disk image")
|
||||
@ -511,6 +532,11 @@ def run():
|
||||
|
||||
if command == "create":
|
||||
create_image(options.template[0], disk_image_name)
|
||||
elif command == "boot":
|
||||
asm = options.asm[0] if options.asm else []
|
||||
data = options.data[0] if options.data else []
|
||||
obj = options.obj[0] if options.obj else []
|
||||
boot_image(disk_image_name, asm, data, obj, options.run_addr)
|
||||
else:
|
||||
parser = find_diskimage(disk_image_name)
|
||||
if parser and parser.image:
|
||||
|
@ -20,7 +20,7 @@ class BaseHeader(object):
|
||||
file_format = "generic" # text descriptor of file format
|
||||
sector_class = WriteableSector
|
||||
|
||||
def __init__(self, sector_size=256, initial_sectors=0, vtoc_sector=0, starting_sector_label=0):
|
||||
def __init__(self, sector_size=256, initial_sectors=0, vtoc_sector=0, starting_sector_label=0, create=False):
|
||||
self.image_size = 0
|
||||
self.sector_size = sector_size
|
||||
self.payload_bytes = sector_size
|
||||
@ -96,7 +96,7 @@ class BaseHeader(object):
|
||||
|
||||
|
||||
class DiskImageBase(object):
|
||||
def __init__(self, rawdata, filename=""):
|
||||
def __init__(self, rawdata, filename="", create=False):
|
||||
self.rawdata = rawdata
|
||||
self.bytes = self.rawdata.get_data()
|
||||
self.style = self.rawdata.get_style()
|
||||
@ -109,7 +109,10 @@ class DiskImageBase(object):
|
||||
self.segments = []
|
||||
self.all_sane = True
|
||||
self.default_filetype = ""
|
||||
self.setup()
|
||||
if create:
|
||||
self.header = self.new_header(self)
|
||||
else:
|
||||
self.setup()
|
||||
|
||||
def __len__(self):
|
||||
return len(self.rawdata)
|
||||
@ -307,6 +310,10 @@ class DiskImageBase(object):
|
||||
def create_executable_file_image(self, segments, run_addr=None):
|
||||
raise NotImplementedError
|
||||
|
||||
@classmethod
|
||||
def create_boot_image(self, segments, run_addr=None):
|
||||
raise NotImplementedError
|
||||
|
||||
# file writing methods
|
||||
|
||||
def begin_transaction(self):
|
||||
|
4
atrcopy/fstbt.py
Normal file
4
atrcopy/fstbt.py
Normal file
@ -0,0 +1,4 @@
|
||||
# generated file - recompile fstbt.s to make changes
|
||||
|
||||
# append jump target (lo, hi) and sector address list before saving to disk
|
||||
fstbt = b"\x01\xa8\x8dP\xc0\x8dR\xc0\x8dW\xc0\xee\x0f\x08\xadl\x08\xc9\xc0\xf0U\xc9\xd0\xf0\xf2\xc9\xd1\xd0\x05\x8dT\xc0\xf0\xe9\xc9\xd2\xd0\x05\x8dU\xc0\xf0\xe0\x85'\xc8\xc0\x10\x90\t\xf0\x05 M\x08\xa8,\xa0\x01\x84=\xc8\xa5'\xf0\xc9\x8a {\xf8\t\xc0H\xa9[H`\xe6A\x06@ U\x08\x18 Z\x08\xe6@\xa5@)\x03*\x05+\xa8\xb9\x80\xc0\xa90L\xa8\xfcL"
|
@ -10,6 +10,7 @@ from .spartados import SpartaDosDiskImage
|
||||
from .cartridge import AtariCartImage, get_known_carts
|
||||
from .mame import MameZipImage
|
||||
from .dos33 import Dos33DiskImage, ProdosDiskImage, Dos33BinFile
|
||||
from .standard_delivery import StandardDeliveryImage
|
||||
from .errors import *
|
||||
|
||||
import logging
|
||||
@ -174,6 +175,17 @@ def iter_parsers(r):
|
||||
return None, None
|
||||
|
||||
|
||||
def parsers_for_filename(name):
|
||||
matches = []
|
||||
for mime in mime_parse_order:
|
||||
parsers = mime_parsers[mime]
|
||||
found = None
|
||||
for parser in parsers:
|
||||
print("parser: %s = %s" % (mime, parser))
|
||||
matches.append(StandardDeliveryImage)
|
||||
return matches
|
||||
|
||||
|
||||
mime_parsers = {
|
||||
"application/vnd.atari8bit.atr": [
|
||||
KBootSegmentParser,
|
||||
|
150
atrcopy/standard_delivery.py
Normal file
150
atrcopy/standard_delivery.py
Normal file
@ -0,0 +1,150 @@
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
import numpy as np
|
||||
|
||||
from .errors import *
|
||||
from .segments import SegmentData
|
||||
from .diskimages import BaseHeader, DiskImageBase
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class StandardDeliveryHeader(BaseHeader):
|
||||
file_format = "Apple ][ Standard Delivery"
|
||||
|
||||
def __init__(self, bytes=None, sector_size=256, create=False):
|
||||
BaseHeader.__init__(self, sector_size, create=create)
|
||||
if bytes is None:
|
||||
return
|
||||
|
||||
data = bytes[0:5]
|
||||
if np.all(data == (0x01, 0xa8, 0xee, 0x06, 0x08)):
|
||||
log.debug("Found 48k loader")
|
||||
else:
|
||||
raise InvalidDiskImage("No %s boot header" % self.file_format)
|
||||
|
||||
def __str__(self):
|
||||
return "Standard Delivery Boot Disk (size=%d (%dx%dB)" % (self.file_format, self.image_size, self.max_sectors, self.sector_size)
|
||||
|
||||
def check_size(self, size):
|
||||
if size != 143360:
|
||||
raise InvalidDiskImage("Incorrect size for Standard Delivery image")
|
||||
self.image_size = size
|
||||
self.tracks_per_disk = 35
|
||||
self.sectors_per_track = 16
|
||||
self.max_sectors = self.tracks_per_disk * self.sectors_per_track
|
||||
|
||||
|
||||
class StandardDeliveryImage(DiskImageBase):
|
||||
def __str__(self):
|
||||
return str(self.header)
|
||||
|
||||
def read_header(self):
|
||||
self.header = StandardDeliveryHeader(self.bytes[0:256])
|
||||
|
||||
@classmethod
|
||||
def new_header(cls, diskimage, format="DSK"):
|
||||
if format.lower() == "dsk":
|
||||
header = StandardDeliveryHeader(create=True)
|
||||
header.check_size(diskimage.size)
|
||||
else:
|
||||
raise RuntimeError("Unknown header type %s" % format)
|
||||
return header
|
||||
|
||||
def check_size(self):
|
||||
pass
|
||||
|
||||
def get_boot_sector_info(self):
|
||||
pass
|
||||
|
||||
def get_vtoc(self):
|
||||
pass
|
||||
|
||||
def get_directory(self, directory=None):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def create_boot_image(cls, segments, run_addr=None):
|
||||
raw = SegmentData(np.zeros([143360], dtype=np.uint8))
|
||||
dsk = cls(raw, create=True)
|
||||
if run_addr is None:
|
||||
run_addr = segments[0].start_addr
|
||||
|
||||
chunks = []
|
||||
|
||||
for s in segments:
|
||||
# find size in 256 byte chunks that start on a page boundary
|
||||
# since the loader only deals with page boundaries
|
||||
origin = s.start_addr
|
||||
chunk_start, padding = divmod(origin, 256)
|
||||
size = ((len(s) + padding + 255) // 256) * 256
|
||||
chunk = np.zeros([size], dtype=np.uint8)
|
||||
chunk[padding:padding + len(s)] = s[:]
|
||||
chunks.append((chunk_start, chunk))
|
||||
print("segment: %s, chunk=%s" % (str(s), str(chunks[-1])))
|
||||
|
||||
# break up the chunks into sectors
|
||||
|
||||
# NOTE: fstbt implied that the sector order was staggered, but in
|
||||
# AppleWin, by trial and error, works with the following order
|
||||
|
||||
# index = 1 # on the first track, sector 0 is reserved for boot sector
|
||||
# sector_order = [0, 2, 4, 6, 8, 10, 12, 14, 1, 3, 5, 7, 9, 11, 13, 15]
|
||||
|
||||
index = 1
|
||||
sector_order = [0, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 15]
|
||||
track = 0
|
||||
count = 0
|
||||
|
||||
sector_list = []
|
||||
address_list = [0xd1]
|
||||
|
||||
boot_sector = dsk.header.create_sector()
|
||||
boot_sector.sector_num = 0
|
||||
boot_sector.track_num = 1
|
||||
sector_list.append(boot_sector)
|
||||
|
||||
for chunk_start, chunk_data in chunks:
|
||||
count = len(chunk_data) // 256
|
||||
if chunk_start == 0x20 and count == 32:
|
||||
# Assume this is an HGR screen, use interesting load effect,
|
||||
# not the usual venetian blind
|
||||
chunk_hi = [0x20, 0x24, 0x28, 0x2c, 0x30, 0x34, 0x38, 0x3c, 0x21, 0x25, 0x29, 0x2d, 0x31, 0x35, 0x39, 0x3d, 0x22, 0x26, 0x2a, 0x2e, 0x32, 0x36, 0x3a, 0x3e, 0x23, 0x27, 0x2b, 0x2f, 0x33, 0x37, 0x3b, 0x3f]
|
||||
else:
|
||||
chunk_hi = range(chunk_start, chunk_start + count)
|
||||
for n in range(count):
|
||||
i = (chunk_hi[n] - chunk_start) * 256
|
||||
sector = dsk.header.create_sector(chunk_data[i:i+256])
|
||||
sector.sector_num = dsk.header.sector_from_track(track, sector_order[index])
|
||||
count += 1
|
||||
#sector.sector_num = count
|
||||
sector_list.append(sector)
|
||||
address_list.append(chunk_hi[n])
|
||||
# sector.data[0] = sector.sector_num
|
||||
# sector.data[1] = hi
|
||||
# sector.data[2:16] = 0xff
|
||||
print("%s at %02x00: %s ..." % (sector_list[-1], address_list[-1], " ".join(["%02x" % h for h in chunk_data[i:i + 16]])))
|
||||
index += 1
|
||||
if index >= len(sector_order):
|
||||
index = 0
|
||||
track += 1
|
||||
if chunk_start == 0x40:
|
||||
address_list.append(0xd2)
|
||||
|
||||
print("address list %s" % str(address_list))
|
||||
boot_code = get_fstbt_code(boot_sector.data, address_list, run_addr)
|
||||
|
||||
dsk.write_sector_list(sector_list)
|
||||
|
||||
return dsk
|
||||
|
||||
from . fstbt import fstbt
|
||||
|
||||
def get_fstbt_code(data, address_list, run_addr):
|
||||
pointer = len(fstbt)
|
||||
data[0:pointer] = np.fromstring(fstbt, dtype=np.uint8)
|
||||
hi, lo = divmod(run_addr, 256)
|
||||
data[pointer:pointer + 2] = (lo, hi)
|
||||
address_list.append(0xc0) # last sector flag
|
||||
data[pointer + 2:pointer + 2 + len(address_list)] = address_list
|
Loading…
Reference in New Issue
Block a user