Added rom image conversion to atari cart images

This commit is contained in:
Rob McMullen 2018-10-26 12:14:56 -07:00
parent a2831c0edf
commit 50488fc2e5
3 changed files with 105 additions and 33 deletions

View File

@ -19,7 +19,7 @@ from .dos33 import Dos33DiskImage
from .kboot import KBootImage, add_xexboot_header 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 .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 .spartados import SpartaDosDiskImage
from .cartridge import A8CartHeader, AtariCartImage from .cartridge import A8CartHeader, AtariCartImage, RomImage
from .parsers import SegmentParser, DefaultSegmentParser, guess_parser_for_mime, guess_parser_for_system, guess_container, iter_parsers, iter_known_segment_parsers, mime_parse_order, parsers_for_filename from .parsers import SegmentParser, DefaultSegmentParser, guess_parser_for_mime, guess_parser_for_system, guess_container, iter_parsers, iter_known_segment_parsers, mime_parse_order, parsers_for_filename
from .magic import guess_detail_for_mime from .magic import guess_detail_for_mime
from .utils import to_numpy, text_to_int from .utils import to_numpy, text_to_int

View File

@ -119,6 +119,7 @@ class A8CartHeader:
('checksum', '>u4'), ('checksum', '>u4'),
('unused','>u4') ('unused','>u4')
]) ])
nominal_length = format.itemsize
file_format = "Cart" file_format = "Cart"
def __init__(self, bytes=None, create=False): def __init__(self, bytes=None, create=False):
@ -138,7 +139,7 @@ class A8CartHeader:
self.main_origin = 0 self.main_origin = 0
self.possible_types = set() self.possible_types = set()
if create: if create:
self.header_offset = 16 self.header_offset = self.nominal_length
self.check_size(0) self.check_size(0)
if bytes is None: if bytes is None:
return return
@ -149,7 +150,7 @@ class A8CartHeader:
raise errors.InvalidCartHeader raise errors.InvalidCartHeader
self.cart_type = int(values[1]) self.cart_type = int(values[1])
self.crc = int(values[2]) self.crc = int(values[2])
self.header_offset = 16 self.header_offset = self.nominal_length
self.set_type(self.cart_type) self.set_type(self.cart_type)
else: else:
raise errors.InvalidCartHeader raise errors.InvalidCartHeader
@ -160,8 +161,15 @@ class A8CartHeader:
def __len__(self): def __len__(self):
return self.header_offset return self.header_offset
@property
def valid(self):
return self.cart_type != -1
def calc_crc_from_data(self, data):
self.crc = 0
def to_array(self): def to_array(self):
raw = np.zeros([16], dtype=np.uint8) raw = np.zeros([self.nominal_length], dtype=np.uint8)
values = raw.view(dtype=self.format)[0] values = raw.view(dtype=self.format)[0]
values[0] = b'CART' values[0] = b'CART'
values[1] = self.cart_type values[1] = self.cart_type
@ -189,7 +197,7 @@ class A8CartHeader:
def check_size(self, size): def check_size(self, size):
self.possible_types = set() self.possible_types = set()
k, r = divmod(size, 1024) k, r = divmod(size, 1024)
if r == 0 or r == 16: if r == 0 or r == self.nominal_length:
for i, t in enumerate(known_cart_types): for i, t in enumerate(known_cart_types):
valid_size = t[0] valid_size = t[0]
if k == valid_size: if k == valid_size:
@ -205,7 +213,7 @@ class BaseAtariCartImage(DiskImageBase):
try: try:
self.header = A8CartHeader(data) self.header = A8CartHeader(data)
except errors.InvalidCartHeader: except errors.InvalidCartHeader:
raise errors.InvalidDiskImage("Missing cart header") self.header = A8CartHeader()
def strict_check(self): def strict_check(self):
raise NotImplementedError raise NotImplementedError
@ -217,10 +225,10 @@ class BaseAtariCartImage(DiskImageBase):
self.header.set_type(self.cart_type) self.header.set_type(self.cart_type)
def check_size(self): def check_size(self):
if self.header is None: if not self.header.valid:
return return
k, rem = divmod((len(self) - len(self.header)), 1024) k, rem = divmod((len(self) - len(self.header)), 1024)
c = get_cart(self.cart_type) c = get_cart(self.header.cart_type)
log.debug("checking type=%d, k=%d, rem=%d for %s, %s" % (self.cart_type, k, rem, c[1], c[2])) log.debug("checking type=%d, k=%d, rem=%d for %s, %s" % (self.cart_type, k, rem, c[1], c[2]))
if rem > 0: if rem > 0:
raise errors.InvalidDiskImage("Cart not multiple of 1K") raise errors.InvalidDiskImage("Cart not multiple of 1K")
@ -252,13 +260,31 @@ class BaseAtariCartImage(DiskImageBase):
segments.append(s) segments.append(s)
return segments return segments
def create_emulator_boot_segment(self):
print(self.segments)
h = self.header
k, rem = divmod(len(self), 1024)
if rem == 0:
h.calc_crc_from_data(self.bytes)
data_with_header = np.empty(len(self) + h.nominal_length, dtype=np.uint8)
data_with_header[0:h.nominal_length] = h.to_array()
data_with_header[h.nominal_length:] = self.bytes
r = SegmentData(data_with_header)
else:
r = self.rawdata
s = ObjSegment(r, 0, 0, self.header.main_origin, name="Cart image")
return s
class AtariCartImage(BaseAtariCartImage): class AtariCartImage(BaseAtariCartImage):
def __init__(self, rawdata, cart_type, filename=""): def __init__(self, rawdata, cart_type, filename=""):
c = get_cart(cart_type)
self.cart_type = cart_type self.cart_type = cart_type
DiskImageBase.__init__(self, rawdata, filename) DiskImageBase.__init__(self, rawdata, filename)
def strict_check(self): def strict_check(self):
if not self.header.valid:
raise errors.InvalidDiskImage("Missing cart header")
if self.header.cart_type != self.cart_type: if self.header.cart_type != self.cart_type:
raise errors.InvalidDiskImage("Cart type doesn't match type defined in header") raise errors.InvalidDiskImage("Cart type doesn't match type defined in header")
@ -267,12 +293,14 @@ class Atari8bitCartImage(BaseAtariCartImage):
def strict_check(self): def strict_check(self):
if "5200" in self.header.cart_name: if "5200" in self.header.cart_name:
raise errors.InvalidDiskImage("5200 Carts don't work in the home computers.") raise errors.InvalidDiskImage("5200 Carts don't work in the home computers.")
AtariCartImage.strict_check(self)
class Atari5200CartImage(BaseAtariCartImage): class Atari5200CartImage(BaseAtariCartImage):
def strict_check(self): def strict_check(self):
if "5200" not in self.header.cart_name: if "5200" not in self.header.cart_name:
raise errors.InvalidDiskImage("Home computer carts don't work in the 5200.") raise errors.InvalidDiskImage("Home computer carts don't work in the 5200.")
AtariCartImage.strict_check(self)
def add_cart_header(bytes): def add_cart_header(bytes):

View File

@ -3,8 +3,8 @@ from __future__ import division
from builtins import object from builtins import object
from mock import * from mock import *
from atrcopy import AtariCartImage, SegmentData from atrcopy import AtariCartImage, SegmentData, RomImage, errors
from atrcopy import errors from atrcopy.cartridge import known_cart_types
class TestAtariCart: class TestAtariCart:
@ -17,26 +17,24 @@ class TestAtariCart:
data[4:8].view(">u4")[0] = cart_type data[4:8].view(">u4")[0] = cart_type
return data return data
def test_unbanked(self): @pytest.mark.parametrize("k_size,cart_type", [
carts = [
(8, 1), (8, 1),
(16, 2), (16, 2),
(8, 21), (8, 21),
(2, 57), (2, 57),
(4, 58), (4, 58),
(4, 59), (4, 59),
] ])
for k_size, cart_type in carts: def test_unbanked(self, k_size, cart_type):
data = self.get_cart(k_size, cart_type) data = self.get_cart(k_size, cart_type)
rawdata = SegmentData(data) rawdata = SegmentData(data)
image = AtariCartImage(rawdata, cart_type) image = AtariCartImage(rawdata, cart_type)
image.parse_segments() image.parse_segments()
assert len(image.segments) == 2 assert len(image.segments) == 2
assert len(image.segments[0]) == 16 assert len(image.segments[0]) == 16
assert len(image.segments[1]) == k_size * 1024 assert len(image.segments[1]) == k_size * 1024
def test_banked(self): @pytest.mark.parametrize("k_size,main_size,banked_size,cart_type", [
carts = [
(32, 8, 8, 12), (32, 8, 8, 12),
(64, 8, 8, 13), (64, 8, 8, 13),
(64, 8, 8, 67), (64, 8, 8, 67),
@ -44,16 +42,16 @@ class TestAtariCart:
(256, 8, 8, 23), (256, 8, 8, 23),
(512, 8, 8, 24), (512, 8, 8, 24),
(1024, 8, 8, 25), (1024, 8, 8, 25),
] ])
for k_size, main_size, banked_size, cart_type in carts: def test_banked(self, k_size, main_size, banked_size, cart_type):
data = self.get_cart(k_size, cart_type) data = self.get_cart(k_size, cart_type)
rawdata = SegmentData(data) rawdata = SegmentData(data)
image = AtariCartImage(rawdata, cart_type) image = AtariCartImage(rawdata, cart_type)
image.parse_segments() image.parse_segments()
assert len(image.segments) == 1 + 1 + (k_size - main_size) //banked_size assert len(image.segments) == 1 + 1 + (k_size - main_size) //banked_size
assert len(image.segments[0]) == 16 assert len(image.segments[0]) == 16
assert len(image.segments[1]) == main_size * 1024 assert len(image.segments[1]) == main_size * 1024
assert len(image.segments[2]) == banked_size * 1024 assert len(image.segments[2]) == banked_size * 1024
def test_bad(self): def test_bad(self):
k_size = 32 k_size = 32
@ -74,9 +72,55 @@ class TestAtariCart:
image = AtariCartImage(rawdata, 1337) image = AtariCartImage(rawdata, 1337)
class TestRomCart:
def setup(self):
pass
def get_rom(self, k_size):
data = np.zeros((k_size * 1024), dtype=np.uint8)
return data
@pytest.mark.parametrize("k_size", [1, 2, 4, 8, 16, 32, 64])
def test_typical_rom_sizes(self, k_size):
data = self.get_rom(k_size)
rawdata = SegmentData(data)
rom_image = RomImage(rawdata)
rom_image.strict_check()
rom_image.parse_segments()
assert len(rom_image.segments) == 1
assert len(rom_image.segments[0]) == k_size * 1024
@pytest.mark.parametrize("k_size", [1, 2, 4, 8, 16, 32, 64])
def test_invalid_rom_sizes(self, k_size):
data = np.zeros((k_size * 1024) + 17, dtype=np.uint8)
rawdata = SegmentData(data)
with pytest.raises(errors.InvalidDiskImage):
rom_image = RomImage(rawdata)
@pytest.mark.parametrize("cart", known_cart_types)
def test_conversion_to_atari_cart(self, cart):
cart_type = cart[0]
name = cart[1]
k_size = cart[2]
if "Bounty" in name:
return
data = self.get_rom(k_size)
rawdata = SegmentData(data)
rom_image = RomImage(rawdata)
rom_image.strict_check()
rom_image.parse_segments()
new_cart_image = AtariCartImage(rawdata, cart_type)
new_cart_image.relaxed_check()
new_cart_image.parse_segments()
assert new_cart_image.header.valid
s = new_cart_image.create_emulator_boot_segment()
assert len(s) == len(rawdata) + new_cart_image.header.nominal_length
assert s[0:4].tobytes() == b'CART'
assert s[4:8].view(dtype=">u4") == cart_type
if __name__ == "__main__": if __name__ == "__main__":
from atrcopy.parsers import mime_parse_order
print("\n".join(mime_parse_order)) print("\n".join(mime_parse_order))
t = TestAtariCart() t = TestAtariCart()