From 50488fc2e5fb940f6f30f1859033f1623ba34235 Mon Sep 17 00:00:00 2001 From: Rob McMullen Date: Fri, 26 Oct 2018 12:14:56 -0700 Subject: [PATCH] Added rom image conversion to atari cart images --- atrcopy/__init__.py | 2 +- atrcopy/cartridge.py | 42 ++++++++++++++++---- test/test_cart.py | 94 ++++++++++++++++++++++++++++++++------------ 3 files changed, 105 insertions(+), 33 deletions(-) diff --git a/atrcopy/__init__.py b/atrcopy/__init__.py index 4963486..123606c 100644 --- a/atrcopy/__init__.py +++ b/atrcopy/__init__.py @@ -19,7 +19,7 @@ from .dos33 import Dos33DiskImage 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 .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 .magic import guess_detail_for_mime from .utils import to_numpy, text_to_int diff --git a/atrcopy/cartridge.py b/atrcopy/cartridge.py index cacc8cd..902eba6 100644 --- a/atrcopy/cartridge.py +++ b/atrcopy/cartridge.py @@ -119,6 +119,7 @@ class A8CartHeader: ('checksum', '>u4'), ('unused','>u4') ]) + nominal_length = format.itemsize file_format = "Cart" def __init__(self, bytes=None, create=False): @@ -138,7 +139,7 @@ class A8CartHeader: self.main_origin = 0 self.possible_types = set() if create: - self.header_offset = 16 + self.header_offset = self.nominal_length self.check_size(0) if bytes is None: return @@ -149,7 +150,7 @@ class A8CartHeader: raise errors.InvalidCartHeader self.cart_type = int(values[1]) self.crc = int(values[2]) - self.header_offset = 16 + self.header_offset = self.nominal_length self.set_type(self.cart_type) else: raise errors.InvalidCartHeader @@ -160,8 +161,15 @@ class A8CartHeader: def __len__(self): 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): - raw = np.zeros([16], dtype=np.uint8) + raw = np.zeros([self.nominal_length], dtype=np.uint8) values = raw.view(dtype=self.format)[0] values[0] = b'CART' values[1] = self.cart_type @@ -189,7 +197,7 @@ class A8CartHeader: def check_size(self, size): self.possible_types = set() 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): valid_size = t[0] if k == valid_size: @@ -205,7 +213,7 @@ class BaseAtariCartImage(DiskImageBase): try: self.header = A8CartHeader(data) except errors.InvalidCartHeader: - raise errors.InvalidDiskImage("Missing cart header") + self.header = A8CartHeader() def strict_check(self): raise NotImplementedError @@ -217,10 +225,10 @@ class BaseAtariCartImage(DiskImageBase): self.header.set_type(self.cart_type) def check_size(self): - if self.header is None: + if not self.header.valid: return 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])) if rem > 0: raise errors.InvalidDiskImage("Cart not multiple of 1K") @@ -252,13 +260,31 @@ class BaseAtariCartImage(DiskImageBase): segments.append(s) 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): def __init__(self, rawdata, cart_type, filename=""): + c = get_cart(cart_type) self.cart_type = cart_type DiskImageBase.__init__(self, rawdata, filename) def strict_check(self): + if not self.header.valid: + raise errors.InvalidDiskImage("Missing cart header") if self.header.cart_type != self.cart_type: raise errors.InvalidDiskImage("Cart type doesn't match type defined in header") @@ -267,12 +293,14 @@ class Atari8bitCartImage(BaseAtariCartImage): def strict_check(self): if "5200" in self.header.cart_name: raise errors.InvalidDiskImage("5200 Carts don't work in the home computers.") + AtariCartImage.strict_check(self) class Atari5200CartImage(BaseAtariCartImage): def strict_check(self): if "5200" not in self.header.cart_name: raise errors.InvalidDiskImage("Home computer carts don't work in the 5200.") + AtariCartImage.strict_check(self) def add_cart_header(bytes): diff --git a/test/test_cart.py b/test/test_cart.py index 627a53c..2e992d5 100644 --- a/test/test_cart.py +++ b/test/test_cart.py @@ -3,8 +3,8 @@ from __future__ import division from builtins import object from mock import * -from atrcopy import AtariCartImage, SegmentData -from atrcopy import errors +from atrcopy import AtariCartImage, SegmentData, RomImage, errors +from atrcopy.cartridge import known_cart_types class TestAtariCart: @@ -17,26 +17,24 @@ class TestAtariCart: data[4:8].view(">u4")[0] = cart_type return data - def test_unbanked(self): - carts = [ + @pytest.mark.parametrize("k_size,cart_type", [ (8, 1), (16, 2), (8, 21), (2, 57), (4, 58), (4, 59), - ] - for k_size, cart_type in carts: - data = self.get_cart(k_size, cart_type) - rawdata = SegmentData(data) - image = AtariCartImage(rawdata, cart_type) - image.parse_segments() - assert len(image.segments) == 2 - assert len(image.segments[0]) == 16 - assert len(image.segments[1]) == k_size * 1024 + ]) + def test_unbanked(self, k_size, cart_type): + data = self.get_cart(k_size, cart_type) + rawdata = SegmentData(data) + image = AtariCartImage(rawdata, cart_type) + image.parse_segments() + assert len(image.segments) == 2 + assert len(image.segments[0]) == 16 + assert len(image.segments[1]) == k_size * 1024 - def test_banked(self): - carts = [ + @pytest.mark.parametrize("k_size,main_size,banked_size,cart_type", [ (32, 8, 8, 12), (64, 8, 8, 13), (64, 8, 8, 67), @@ -44,16 +42,16 @@ class TestAtariCart: (256, 8, 8, 23), (512, 8, 8, 24), (1024, 8, 8, 25), - ] - for k_size, main_size, banked_size, cart_type in carts: - data = self.get_cart(k_size, cart_type) - rawdata = SegmentData(data) - image = AtariCartImage(rawdata, cart_type) - image.parse_segments() - assert len(image.segments) == 1 + 1 + (k_size - main_size) //banked_size - assert len(image.segments[0]) == 16 - assert len(image.segments[1]) == main_size * 1024 - assert len(image.segments[2]) == banked_size * 1024 + ]) + def test_banked(self, k_size, main_size, banked_size, cart_type): + data = self.get_cart(k_size, cart_type) + rawdata = SegmentData(data) + image = AtariCartImage(rawdata, cart_type) + image.parse_segments() + assert len(image.segments) == 1 + 1 + (k_size - main_size) //banked_size + assert len(image.segments[0]) == 16 + assert len(image.segments[1]) == main_size * 1024 + assert len(image.segments[2]) == banked_size * 1024 def test_bad(self): k_size = 32 @@ -74,9 +72,55 @@ class TestAtariCart: 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__": + from atrcopy.parsers import mime_parse_order print("\n".join(mime_parse_order)) t = TestAtariCart()