mirror of
https://github.com/robmcmullen/atrcopy.git
synced 2024-06-06 11:29:29 +00:00
Added resizable segments
This commit is contained in:
parent
7da17d65bc
commit
6f29e6053a
|
@ -8,10 +8,10 @@ except ImportError:
|
||||||
raise RuntimeError("atrcopy %s requires numpy" % __version__)
|
raise RuntimeError("atrcopy %s requires numpy" % __version__)
|
||||||
|
|
||||||
from errors import *
|
from errors import *
|
||||||
from ataridos import AtrHeader, AtariDosDiskImage, BootDiskImage, AtariDosFile, get_xex, add_atr_header
|
from ataridos import AtrHeader, AtariDosDiskImage, BootDiskImage, AtariDosFile, XexContainerSegment, get_xex, add_atr_header
|
||||||
from dos33 import Dos33DiskImage
|
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, 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
|
||||||
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
|
||||||
|
|
|
@ -2,7 +2,7 @@ import numpy as np
|
||||||
|
|
||||||
from errors import *
|
from errors import *
|
||||||
from diskimages import DiskImageBase, BaseHeader
|
from diskimages import DiskImageBase, BaseHeader
|
||||||
from segments import SegmentData, EmptySegment, ObjSegment, RawSectorsSegment, DefaultSegment, SegmentSaver, get_style_bits
|
from segments import SegmentData, EmptySegment, ObjSegment, RawSectorsSegment, DefaultSegment, SegmentedFileSegment, SegmentSaver, get_style_bits
|
||||||
from utils import *
|
from utils import *
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
@ -242,6 +242,10 @@ class XexSegmentSaver(SegmentSaver):
|
||||||
export_extensions = [".xex"]
|
export_extensions = [".xex"]
|
||||||
|
|
||||||
|
|
||||||
|
class XexContainerSegment(DefaultSegment):
|
||||||
|
can_resize_default = True
|
||||||
|
|
||||||
|
|
||||||
class XexSegment(ObjSegment):
|
class XexSegment(ObjSegment):
|
||||||
savers = [SegmentSaver, XexSegmentSaver]
|
savers = [SegmentSaver, XexSegmentSaver]
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ import numpy as np
|
||||||
|
|
||||||
from segments import SegmentData, DefaultSegment
|
from segments import SegmentData, DefaultSegment
|
||||||
from kboot import KBootImage
|
from kboot import KBootImage
|
||||||
from ataridos import AtariDosDiskImage, BootDiskImage, AtariDosFile
|
from ataridos import AtariDosDiskImage, BootDiskImage, AtariDosFile, XexContainerSegment
|
||||||
from spartados import SpartaDosDiskImage
|
from spartados import SpartaDosDiskImage
|
||||||
from cartridge import AtariCartImage, get_known_carts
|
from cartridge import AtariCartImage, get_known_carts
|
||||||
from mame import MameZipImage
|
from mame import MameZipImage
|
||||||
|
@ -16,6 +16,7 @@ log = logging.getLogger(__name__)
|
||||||
class SegmentParser(object):
|
class SegmentParser(object):
|
||||||
menu_name = ""
|
menu_name = ""
|
||||||
image_type = None
|
image_type = None
|
||||||
|
container_segment = DefaultSegment
|
||||||
|
|
||||||
def __init__(self, segment_data, strict=False):
|
def __init__(self, segment_data, strict=False):
|
||||||
self.image = None
|
self.image = None
|
||||||
|
@ -26,7 +27,7 @@ class SegmentParser(object):
|
||||||
|
|
||||||
def parse(self):
|
def parse(self):
|
||||||
r = self.segment_data
|
r = self.segment_data
|
||||||
self.segments.append(DefaultSegment(r, 0, name=self.menu_name))
|
self.segments.append(self.container_segment(r, 0, name=self.menu_name))
|
||||||
try:
|
try:
|
||||||
self.image = self.get_image(r)
|
self.image = self.get_image(r)
|
||||||
self.check_image()
|
self.check_image()
|
||||||
|
@ -80,6 +81,7 @@ class AtariBootDiskSegmentParser(SegmentParser):
|
||||||
class XexSegmentParser(SegmentParser):
|
class XexSegmentParser(SegmentParser):
|
||||||
menu_name = "XEX (Atari 8-bit executable)"
|
menu_name = "XEX (Atari 8-bit executable)"
|
||||||
image_type = AtariDosFile
|
image_type = AtariDosFile
|
||||||
|
container_segment = XexContainerSegment
|
||||||
|
|
||||||
|
|
||||||
class AtariCartSegmentParser(SegmentParser):
|
class AtariCartSegmentParser(SegmentParser):
|
||||||
|
|
|
@ -153,11 +153,45 @@ class SegmentData(object):
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
return len(self.data)
|
return len(self.data)
|
||||||
|
|
||||||
|
def resize(self, newsize):
|
||||||
|
if self.data.base is None:
|
||||||
|
try:
|
||||||
|
newdata = np.resize(self.data, (newsize,))
|
||||||
|
newstyle = np.resize(self.style, (newsize,))
|
||||||
|
except:
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
self.data = newdata
|
||||||
|
self.style = newstyle
|
||||||
|
|
||||||
|
def replace_arrays(self, base_raw):
|
||||||
|
newsize = len(base_raw)
|
||||||
|
oldsize = len(self.data_base)
|
||||||
|
if newsize < oldsize:
|
||||||
|
raise NotImplementedError("Can't truncate yet")
|
||||||
|
if self.is_indexed:
|
||||||
|
self.data.np_data = base_raw.data
|
||||||
|
self.data.base = base_raw.data.base
|
||||||
|
self.style.np_data = base_raw.style
|
||||||
|
self.style.base = base_raw.style.base
|
||||||
|
elif self.data.base is not None:
|
||||||
|
# if there is no base array, we aren't looking at a slice so we
|
||||||
|
# must be copying the entire array.
|
||||||
|
start, end = self.byte_bounds_offset()
|
||||||
|
self.data = base_raw.data[start:end]
|
||||||
|
self.style = base_raw.style[start:end]
|
||||||
|
else:
|
||||||
|
raise ValueError("The base SegmentData object should use the resize method to replace arrays")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def stringio(self):
|
def stringio(self):
|
||||||
buf = cStringIO.StringIO(self.data[:])
|
buf = cStringIO.StringIO(self.data[:])
|
||||||
return buf
|
return buf
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_base(self):
|
||||||
|
return not self.is_indexed and self.data.base is None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def data_base(self):
|
def data_base(self):
|
||||||
return self.data.np_data if self.is_indexed else self.data.base if self.data.base is not None else self.data
|
return self.data.np_data if self.is_indexed else self.data.base if self.data.base is not None else self.data
|
||||||
|
@ -301,6 +335,8 @@ class SegmentData(object):
|
||||||
|
|
||||||
class DefaultSegment(object):
|
class DefaultSegment(object):
|
||||||
savers = [SegmentSaver]
|
savers = [SegmentSaver]
|
||||||
|
use_origin_default = False
|
||||||
|
can_resize_default = False
|
||||||
|
|
||||||
def __init__(self, rawdata, start_addr=0, name="All", error=None, verbose_name=None):
|
def __init__(self, rawdata, start_addr=0, name="All", error=None, verbose_name=None):
|
||||||
self.start_addr = int(start_addr) # force python int to decouple from possibly being a numpy datatype
|
self.start_addr = int(start_addr) # force python int to decouple from possibly being a numpy datatype
|
||||||
|
@ -314,7 +350,11 @@ class DefaultSegment(object):
|
||||||
|
|
||||||
# Some segments may not have a standard place in memory, so this flag
|
# Some segments may not have a standard place in memory, so this flag
|
||||||
# can be used to skip the memory map lookup when displaying disassembly
|
# can be used to skip the memory map lookup when displaying disassembly
|
||||||
self.use_origin = False
|
self.use_origin = self.__class__.use_origin_default
|
||||||
|
|
||||||
|
# Some segments may be resized to contain additional segments not
|
||||||
|
# present when the segment was created.
|
||||||
|
self.can_resize = self.__class__.can_resize_default
|
||||||
|
|
||||||
def set_raw(self, rawdata):
|
def set_raw(self, rawdata):
|
||||||
self.rawdata = rawdata
|
self.rawdata = rawdata
|
||||||
|
@ -324,10 +364,43 @@ class DefaultSegment(object):
|
||||||
|
|
||||||
def get_raw(self):
|
def get_raw(self):
|
||||||
return self.rawdata
|
return self.rawdata
|
||||||
|
|
||||||
|
def resize(self, newsize, zeros=True):
|
||||||
|
""" Resize the data arrays.
|
||||||
|
|
||||||
|
This can only be performed on the container segment. Child segments
|
||||||
|
must adjust their rawdata to point to the correct place.
|
||||||
|
|
||||||
|
Since segments don't keep references to other segments, it is the
|
||||||
|
user's responsibility to update any child segments that point to this
|
||||||
|
segment's data.
|
||||||
|
|
||||||
|
Numpy can't do an in-place resize on an array that has a view, so the
|
||||||
|
data must be replaced and all segments that point to that raw data must
|
||||||
|
also be changed. This has to happen outside this method because it
|
||||||
|
doesn't know the segment list of segments using itself as a base.
|
||||||
|
"""
|
||||||
|
if not self.can_resize:
|
||||||
|
raise ValueError("Segment %s can't be resized" % str(self))
|
||||||
|
# only makes sense for the container (outermost) object
|
||||||
|
if not self.rawdata.is_base:
|
||||||
|
raise ValueError("Only container segments can be resized")
|
||||||
|
origsize = len(self)
|
||||||
|
self.rawdata.resize(newsize)
|
||||||
|
self.set_raw(self.rawdata) # force attributes to be reset
|
||||||
|
newsize = len(self)
|
||||||
|
if zeros:
|
||||||
|
if newsize > origsize:
|
||||||
|
self.data[origsize:] = 0
|
||||||
|
self.style[origsize:] = 0
|
||||||
|
return origsize, newsize
|
||||||
|
|
||||||
|
def replace_data(self, container):
|
||||||
|
self.rawdata.replace_arrays(container.rawdata)
|
||||||
|
|
||||||
def __getstate__(self):
|
def __getstate__(self):
|
||||||
state = dict()
|
state = dict()
|
||||||
for key in ['start_addr', 'error', 'name', 'verbose_name', 'page_size', 'map_width', 'uuid']:
|
for key in ['start_addr', 'error', 'name', 'verbose_name', 'page_size', 'map_width', 'uuid', 'use_origin', 'can_resize']:
|
||||||
state[key] = getattr(self, key)
|
state[key] = getattr(self, key)
|
||||||
r = self.rawdata
|
r = self.rawdata
|
||||||
state['_rawdata_bounds'] = list(r.byte_bounds_offset())
|
state['_rawdata_bounds'] = list(r.byte_bounds_offset())
|
||||||
|
@ -347,6 +420,10 @@ class DefaultSegment(object):
|
||||||
"""
|
"""
|
||||||
if not hasattr(self, 'uuid'):
|
if not hasattr(self, 'uuid'):
|
||||||
self.uuid = str(uuid.uuid4())
|
self.uuid = str(uuid.uuid4())
|
||||||
|
if not hasattr(self, 'use_origin'):
|
||||||
|
self.use_origin = self.__class__.use_origin_default
|
||||||
|
if not hasattr(self, 'can_resize'):
|
||||||
|
self.can_resize = self.__class__.can_resize_default
|
||||||
|
|
||||||
def reconstruct_raw(self, rawdata):
|
def reconstruct_raw(self, rawdata):
|
||||||
start, end = self._rawdata_bounds
|
start, end = self._rawdata_bounds
|
||||||
|
@ -918,6 +995,10 @@ class ObjSegment(DefaultSegment):
|
||||||
return s
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
class SegmentedFileSegment(ObjSegment):
|
||||||
|
can_resize_default = True
|
||||||
|
|
||||||
|
|
||||||
class RawSectorsSegment(DefaultSegment):
|
class RawSectorsSegment(DefaultSegment):
|
||||||
def __init__(self, rawdata, first_sector, num_sectors, count, boot_sector_size, num_boot_sectors, sector_size, **kwargs):
|
def __init__(self, rawdata, first_sector, num_sectors, count, boot_sector_size, num_boot_sectors, sector_size, **kwargs):
|
||||||
DefaultSegment.__init__(self, rawdata, 0, **kwargs)
|
DefaultSegment.__init__(self, rawdata, 0, **kwargs)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from mock import *
|
from mock import *
|
||||||
|
|
||||||
from atrcopy import SegmentData, AtariDosFile, InvalidBinaryFile
|
from atrcopy import SegmentData, AtariDosFile, InvalidBinaryFile, DefaultSegment, XexContainerSegment
|
||||||
|
|
||||||
|
|
||||||
class TestAtariDosFile(object):
|
class TestAtariDosFile(object):
|
||||||
|
@ -8,12 +8,24 @@ class TestAtariDosFile(object):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def test_segment(self):
|
def test_segment(self):
|
||||||
bytes = [0xff, 0xff, 0x00, 0x60, 0x01, 0x60, 1, 2]
|
bytes = np.asarray([0xff, 0xff, 0x00, 0x60, 0x01, 0x60, 1, 2], dtype=np.uint8)
|
||||||
rawdata = SegmentData(bytes)
|
rawdata = SegmentData(bytes)
|
||||||
image = AtariDosFile(rawdata)
|
container = XexContainerSegment(rawdata, 0)
|
||||||
|
image = AtariDosFile(container.rawdata)
|
||||||
image.parse_segments()
|
image.parse_segments()
|
||||||
|
print image.segments
|
||||||
assert len(image.segments) == 1
|
assert len(image.segments) == 1
|
||||||
assert len(image.segments[0]) == 2
|
assert len(image.segments[0]) == 2
|
||||||
|
assert np.all(image.segments[0] == bytes[6:8])
|
||||||
|
container.resize(16)
|
||||||
|
for s in image.segments:
|
||||||
|
s.replace_data(container)
|
||||||
|
new_segment = DefaultSegment(rawdata[8:16])
|
||||||
|
new_segment[:] = 99
|
||||||
|
assert np.all(image.segments[0] == bytes[6:8])
|
||||||
|
print new_segment[:]
|
||||||
|
assert np.all(new_segment[:] == 99)
|
||||||
|
|
||||||
|
|
||||||
def test_short_segment(self):
|
def test_short_segment(self):
|
||||||
bytes = [0xff, 0xff, 0x00, 0x60, 0xff, 0x60, 1, 2]
|
bytes = [0xff, 0xff, 0x00, 0x60, 0xff, 0x60, 1, 2]
|
||||||
|
|
|
@ -331,6 +331,71 @@ class TestComments(object):
|
||||||
assert item1[3] == item2[3]
|
assert item1[3] == item2[3]
|
||||||
|
|
||||||
|
|
||||||
|
class TestResize(object):
|
||||||
|
def setup(self):
|
||||||
|
data = np.arange(4096, dtype=np.uint8)
|
||||||
|
data[1::2] = np.repeat(np.arange(16, dtype=np.uint8), 128)
|
||||||
|
r = SegmentData(data)
|
||||||
|
self.container = DefaultSegment(r, 0)
|
||||||
|
self.container.can_resize = True
|
||||||
|
|
||||||
|
def test_subset(self):
|
||||||
|
c = self.container
|
||||||
|
assert not c.rawdata.is_indexed
|
||||||
|
offset = 1000
|
||||||
|
s = DefaultSegment(c.rawdata[offset:offset + offset], 0)
|
||||||
|
assert not s.rawdata.is_indexed
|
||||||
|
for i in range(offset):
|
||||||
|
assert s[i] == c[i + offset]
|
||||||
|
requested = 8192
|
||||||
|
oldraw = s.rawdata.copy()
|
||||||
|
oldid = id(s.rawdata)
|
||||||
|
oldsize, newsize = c.resize(requested)
|
||||||
|
assert newsize == requested
|
||||||
|
s.replace_data(c)
|
||||||
|
assert id(s.rawdata) == oldid
|
||||||
|
assert id(oldraw.order) == id(s.rawdata.order)
|
||||||
|
for i in range(offset):
|
||||||
|
assert s[i] == c[i + offset]
|
||||||
|
newbase = c.rawdata
|
||||||
|
newsub = s.rawdata
|
||||||
|
print c.rawdata.data
|
||||||
|
print s.rawdata.data[:]
|
||||||
|
s.rawdata.data[:] = 111
|
||||||
|
print c.rawdata.data
|
||||||
|
print s.rawdata.data[:]
|
||||||
|
for i in range(offset):
|
||||||
|
assert s[i] == c[i + offset]
|
||||||
|
|
||||||
|
def test_indexed(self):
|
||||||
|
c = self.container
|
||||||
|
assert not c.rawdata.is_indexed
|
||||||
|
s, indexes = get_indexed(self.container, 1024, 3)
|
||||||
|
assert s.rawdata.is_indexed
|
||||||
|
for i in range(len(indexes)):
|
||||||
|
assert s.get_raw_index(i) == indexes[i]
|
||||||
|
requested = 8192
|
||||||
|
oldraw = s.rawdata.copy()
|
||||||
|
oldid = id(s.rawdata)
|
||||||
|
oldsize, newsize = c.resize(requested)
|
||||||
|
assert newsize == requested
|
||||||
|
s.replace_data(c)
|
||||||
|
assert id(s.rawdata) == oldid
|
||||||
|
assert id(oldraw.order) == id(s.rawdata.order)
|
||||||
|
for i in range(len(indexes)):
|
||||||
|
assert s.get_raw_index(i) == indexes[i]
|
||||||
|
newbase = c.rawdata
|
||||||
|
newsub = s.rawdata
|
||||||
|
print c.rawdata.data
|
||||||
|
print s.rawdata.data[:]
|
||||||
|
s.rawdata.data[:] = 111
|
||||||
|
print c.rawdata.data
|
||||||
|
print s.rawdata.data[:]
|
||||||
|
for i in range(len(indexes)):
|
||||||
|
assert c.rawdata.data[indexes[i]] == s.rawdata.data[i]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
t = TestIndexed()
|
t = TestIndexed()
|
||||||
t.setup()
|
t.setup()
|
||||||
|
|
Loading…
Reference in New Issue
Block a user