class Terrain(mrc.Block): """Represents a single terrain piece placed in a level.""" #: Raw value for the x position of the left edge. x_raw = mrc.UInt16_BE(0x00, bitmask=b'\x0f\xff') #: If 1, blit image behind background. draw_back = mrc.Bits(0x00, 0b10000000) #: If 1, draw piece flipped vertically. draw_upsidedown = mrc.Bits(0x00, 0b01000000) #: If 1, draw piece as a hole. draw_erase = mrc.Bits(0x00, 0b00100000) #: Raw value (coarse component) for the y position of the top edge. y_raw_coarse = mrc.Int8(0x02) #: Raw value (fine component) for the y position of the top edge. y_raw_fine = mrc.Bits(0x03, 0b10000000) unknown_1 = mrc.Bits(0x03, 0b01000000) #: Index of the TerrainInfo block in the accompanying GroundDAT. obj_id = mrc.UInt8(0x03, bitmask=b'\x3f', range=range(0, 64)) @property def x(self): """The x position of the left edge.""" return (self.x_raw - 16) @property def y(self): """The y position of the top edge.""" return (self.y_raw_coarse * 2 + self.y_raw_fine) - 4 @property def repr(self): return "obj_id={}, x={}, y={}".format(self.obj_id, self.x, self.y)
class VDPColour( img.Colour ): b_raw = mrc.Bits( 0x0000, 0b00001110 ) g_raw = mrc.Bits( 0x0001, 0b11100000 ) r_raw = mrc.Bits( 0x0001, 0b00001110 ) @property def r_8( self ): return (self.r_raw << 5) @r_8.setter def r_8( self, value ): self.r_raw = value >> 5 @property def g_8( self ): return (self.g_raw << 5) @g_8.setter def g_8( self, value ): self.g_raw = value >> 5 @property def b_8( self ): return (self.b_raw << 5) @b_8.setter def b_8( self, value ): self.b_raw = value >> 5
class Interactive(mrc.Block): """Represents a single interactive piece placed in a level.""" #: Raw value for the x position of the left edge. x_raw = mrc.Int16_BE(0x00, range=range(-8, 1601)) #: The y position of the top edge. y = mrc.Int16_BE(0x02, range=range(-41, 201)) #: Index of the InteractiveInfo block in the accompanying GroundDAT. obj_id = mrc.UInt16_BE(0x04, range=range(0, 16)) #: If 1, blit image behind background. draw_back = mrc.Bits(0x06, 0b10000000) #: If 1, draw piece flipped vertically. draw_masked = mrc.Bits(0x06, 0b01000000) #: If 1, draw piece as a hole. draw_upsidedown = mrc.Bits(0x07, 0b10000000) #: Check to ensure the last chunk of the block is empty. mod_check = mrc.Const(mrc.UInt16_BE(0x06, bitmask=b'\x3f\x7f'), 0x000f) @property def x(self): """The x position of the left edge.""" return (self.x_raw - 16) - ((self.x_raw - 16) % 8) @property def repr(self): return "obj_id={}, x={}, y={}".format(self.obj_id, self.x, self.y)
class B800Char(mrc.Block): _palette = ibm_pc.EGA_DEFAULT_PALETTE code_point = mrc.UInt8(0x00) fg_blink = mrc.Bits(0x01, 0b10000000) bg_colour = mrc.Bits(0x01, 0b01110000) fg_colour = mrc.Bits(0x01, 0b00001111) @property def char(self): return CP437[self.code_point] def ansi_format(self): return ansi.format_string(self.char, self._palette[self.fg_colour], self._palette[self.bg_colour], blink=self.fg_blink) def __str__(self): return self.ansi_format() def __repr__(self): return '<{}: char {}, bg {}, fg {}>'.format(self.__class__.__name__, self.char, self.bg_colour, self.fg_colour)
class BitmapCastV4(mrc.Block): _data = None bpp = mrc.Bits(0x00, 0xf0) pitch = mrc.Bits(0x00, 0x0fff, size=2) initial_rect = mrc.BlockField(Rect, 0x02) bounding_rect = mrc.BlockField(Rect, 0x0a) reg_x = mrc.UInt16_BE(0x12) reg_y = mrc.UInt16_BE(0x14) #bpp = mrc.UInt16_BE( 0x16 ) #unk4 = mrc.Bytes( 0x18, length=0x24 ) #name = mrc.Bytes( 0x3e ) unk4 = mrc.Bytes(0x16) @property def repr(self): #return 'name={}, pitch={}, bpp={}, reg_x={}, reg_y={}, unk1={}, unk2={}'.format( self.name, self.pitch, self.bpp, self.reg_x, self.reg_y, self.unk1, self.unk2 ) return 'bpp={}, pitch={}, reg_x={}, reg_y={}, initial_rect={}, bounding_rect={}'.format( self.bpp, self.pitch, self.reg_x, self.reg_y, self.initial_rect, self.bounding_rect) def __init__(self, *argc, **argv): self.image = img.IndexedImage(self, mrc.Ref('_data.data'), mrc.Ref('pitch'), mrc.Ref('initial_rect.height'), palette=DIRECTOR_PALETTE) super().__init__(*argc, **argv)
class FixedSegmentIndicator(mrc.Block): exported = mrc.Bits(0x00, 0b00000001) shared = mrc.Bits(0x00, 0b00000010) offset = mrc.UInt16_LE(0x01) @property def repr(self): return 'offset=0x{:04x}, exported={}, shared={}'.format( self.offset, self.exported, self.shared)
class TileQuad(mrc.Block): index = mrc.Bits(0x00, 0xffc0, size=2, endian='little') flip_v = mrc.Bits(0x00, 0x0020, size=2, endian='little') flip_h = mrc.Bits(0x00, 0x0010, size=2, endian='little') unk1 = mrc.Bits(0x00, 0x000f, size=2, endian='little') @property def repr(self): return 'index: {}, flip_h: {}, flip_v: {}'.format( self.index, self.flip_h, self.flip_v)
class MacBinary(mrc.Block): version_old = mrc.Const(mrc.UInt8(0x00), 0) name_size = mrc.UInt8(0x01, range=range(1, 64)) name = mrc.Bytes(0x02, length=mrc.Ref('name_size')) type = mrc.Bytes(0x41, length=4) creator = mrc.Bytes(0x45, length=4) locked = mrc.Bits(0x49, 0b10000000) invisible = mrc.Bits(0x49, 0b01000000) bundle = mrc.Bits(0x49, 0b00100000) system = mrc.Bits(0x49, 0b00010000) bozo = mrc.Bits(0x49, 0b00001000) busy = mrc.Bits(0x49, 0b00000100) changed = mrc.Bits(0x49, 0b00000010) inited = mrc.Bits(0x49, 0b00000001) const1 = mrc.Const(mrc.UInt8(0x4a), 0) pos_y = mrc.UInt16_BE(0x4b) pos_x = mrc.UInt16_BE(0x4d) folder_id = mrc.UInt16_BE(0x4f) protected = mrc.Bits(0x51, 0b00000001) const2 = mrc.Const(mrc.UInt8(0x52), 0) data_size = mrc.UInt32_BE(0x53) resource_size = mrc.UInt32_BE(0x57) created = mrc.UInt32_BE(0x5a) modified = mrc.UInt32_BE(0x5e) data = mrc.Bytes(0x80, length=mrc.Ref('data_size')) resource = mrc.Bytes(mrc.EndOffset('data', align=0x80), length=mrc.Ref('resource_size'))
class Resource(mrc.Block): offset = mrc.UInt16_LE(0x00) size = mrc.UInt16_LE(0x02) preload = mrc.Bits(0x04, 0b01000000) sharable = mrc.Bits(0x04, 0b00100000) movable = mrc.Bits(0x04, 0b00010000) in_memory = mrc.Bits(0x04, 0b00000100) unk1 = mrc.Bits(0x04, 0b10001111) discardable = mrc.Bits(0x05, 0b00010000) unk2 = mrc.Bits(0x05, 0b11101111) resource_id_low = mrc.UInt8(0x06) int_id = mrc.Bits(0x07, 0b10000000) resource_id_high = mrc.Bits(0x07, 0b01111111) reserved = mrc.Bytes(0x08, length=0x04) @property def resource_id(self): return (self.resource_id_high << 8) + self.resource_id_low @resource_id.setter def resource_id(self, value): self.resouce_id_high = (value >> 8) & 0b01111111 self.resource_id_low = value & 0b11111111 @property def repr(self): return 'offset=0x{:04x}, size=0x{:04x}, resource_id=0x{:04x}, int_id={}'.format( self.offset, self.size, self.resource_id, self.int_id)
class MovableSegmentIndicator(mrc.Block): exported = mrc.Bits(0x00, 0b00000001) shared = mrc.Bits(0x00, 0b00000010) seg_id = mrc.UInt8(0x01) offset = mrc.UInt16_LE(0x02) unk = mrc.Bytes(0x04, length=2) @property def repr(self): return 'seg_id={}, offset=0x{:04x}, exported={}, shared={}'.format( self.seg_id, self.offset, self.exported, self.shared)
class VDPBlockMapping8( mrc.Block ): priority = mrc.Bits( 0x0000, 0b10000000 ) palette_line = mrc.Bits( 0x0000, 0b01100000 ) flip_horiz = mrc.Bits( 0x0000, 0b00010000 ) flip_vert = mrc.Bits( 0x0000, 0b00001000 ) tile_index_high = mrc.Bits( 0x0000, 0b00000111 ) tile_index_low = mrc.UInt8( 0x0001 ) @property def tile_index( self ): return ((tile_index_high << 8) + tile_index_low) * 0x20
class Fission(NDBase): const = mrc.Const(mrc.Bits(0x00, 0b00000111, size=1), 0b101) nd = mrc.Bits(0x00, 0b11111000, size=1) m = mrc.UInt8(0x01) def set_m(self, m): self.m = m return self @property def repr(self): return 'nd: ({}, {}, {}), m: {}'.format(self.ndx, self.ndy, self.ndz, self.m)
def import_data( self, buffer, parent=None ): assert utils.is_bytes( buffer ) inline_copy_bits = buffer[0] high_priority_flag = mrc.Bits( 0x01, 0b00010000 ).get_from_buffer( buffer ) palette_offset = mrc.Bits( 0x01, 0b00001100 ).get_from_buffer( buffer ) flip_horiz = mrc.Bits( 0x01, 0b00000010 ).get_from_buffer( buffer ) flip_vert = mrc.Bits( 0x01, 0b00000001 ).get_from_buffer( buffer ) incremental_copy = mrc.UInt16_BE( 0x02 ).get_from_buffer( buffer ) literal_copy = mrc.UInt16_BE( 0x04 ).get_from_buffer( buffer ) bs = BitStream( buffer[0x06:], 0, bits_reverse=True ) output = bytearray()
class PSGData(mrc.Block): type = mrc.Bits(0x00, 0b10000000) data_raw = mrc.Bits(0x00, 0b01111111) @property def channel(self): return None if not self.type else ((self.data_raw >> 5) & 0x3) @channel.setter def channel(self, value): assert self.type self.data_raw &= 0b0011111 self.data_raw |= (value & 0x3) << 5 @property def control(self): return None if not self.type else ('VOLUME' if ((self.data_raw >> 4) & 1) else 'TONE') @control.setter def control(self, value): assert self.type self.data_raw &= 0b1101111 if value == 'VOLUME': self.data_raw |= 0b0010000 @property def data(self): return self.data_raw & 0xf if self.type else self.data_raw & 0x3f @data.setter def data(self, value): if self.type: self.data_raw &= 0b1110000 self.data_raw |= value & 0xf else: self.data_raw &= 0b1000000 self.data_raw |= value & 0x3f @property def repr(self): type_str = 'LATCH' if self.type else 'DATA' result = f'type={type_str}' if self.type: result += f', channel={self.channel}' result += f', control={self.control}' result += f', data={self.data:04b}' else: result += ', data={self.data:06b}' return result
class SMove(mrc.Block): const = mrc.Const(mrc.Bits(0x00, 0b1100111111100000, size=2), 0b000100000) llda = mrc.Bits(0x00, 0b0011000000000000, size=2) lldi = mrc.Bits(0x00, 0b0000000000011111, size=2) def set_lld(self, x, y, z): if (x == y == 0): self.lldz = z elif (x == z == 0): self.lldy = y elif (y == z == 0): self.lldx = x else: raise ValueError('only one axis allowed') return self @property def lldx(self): return 0 if self.llda != 0b01 else (self.lldi - 15) @lldx.setter def lldx(self, value): assert value in range(-15, 17) self.llda = 0b01 self.lldi = value + 15 @property def lldy(self): return 0 if self.llda != 0b10 else (self.lldi - 15) @lldy.setter def lldy(self, value): assert value in range(-15, 17) self.llda = 0b10 self.lldi = value + 15 @property def lldz(self): return 0 if self.llda != 0b11 else (self.lldi - 15) @lldz.setter def lldz(self, value): assert value in range(-15, 17) self.llda = 0b11 self.lldi = value + 15 @property def repr(self): return 'lld: ({}, {}, {})'.format(self.lldx, self.lldy, self.lldz)
class SpriteChannelV4(mrc.Block): script_id = mrc.UInt8(0x00) type = mrc.UInt8(0x01, enum=SpriteType) fg_colour = mrc.UInt8(0x02) bg_colour = mrc.UInt8(0x03) line_size = mrc.Bits(0x04, 0b00000011) unk4 = mrc.Bits(0x04, 0b11111100) ink = mrc.Bits(0x05, 0b00111111) trails = mrc.Bits(0x05, 0b01000000) unk5 = mrc.Bits(0x05, 0b10000000) cast_id = mrc.UInt16_BE(0x06) y_pos = mrc.UInt16_BE(0x08) x_pos = mrc.UInt16_BE(0x0a) height = mrc.UInt16_BE(0x0c) width = mrc.UInt16_BE(0x0e)
class EGAHeader( mrc.Block ): _egalatch = None # should be manually pointed at the relevant EGALatch object latch_plane_size = mrc.UInt32_LE( 0x00 ) sprite_plane_size = mrc.UInt32_LE( 0x04 ) image_data_start = mrc.UInt32_LE( 0x08 ) sprite_data_start = mrc.UInt32_LE( 0x0c ) tile8_count = mrc.UInt16_LE( 0x10 ) tile8_offset = mrc.UInt32_LE( 0x12 ) tile32_count = mrc.UInt16_LE( 0x16 ) tile32_offset = mrc.UInt32_LE( 0x18 ) tile16_count = mrc.UInt16_LE( 0x1c ) tile16_offset = mrc.UInt32_LE( 0x1e ) bitmap_count = mrc.UInt16_LE( 0x22 ) bitmap_offset = mrc.UInt32_LE( 0x24 ) sprite_count = mrc.UInt16_LE( 0x28 ) sprite_offset = mrc.UInt32_LE( 0x2a ) latch_compressed = mrc.Bits( 0x2e, 0b00000010 ) sprite_compressed = mrc.Bits( 0x2e, 0b00000001 )
class SteelArea(mrc.Block): """Represents an indestructable rectangular area in a level.""" #: Raw value (coarse component) for the x position of the left edge. x_raw_coarse = mrc.UInt8(0x00, range=range(0, 200)) #: Raw value (fine component) for the x position of the left edge. x_raw_fine = mrc.Bits(0x01, 0b10000000) #: Raw value for the y position of the area's top edge. y_raw = mrc.UInt8(0x01, bitmask=b'\x7f', range=range(0, 128)) #: Raw value for the width. width_raw = mrc.Bits(0x02, 0b11110000) #: Raw value for the height. height_raw = mrc.Bits(0x02, 0b00001111) #: Check to ensure the last byte of the block is empty. mod_check = mrc.Const(mrc.UInt8(0x03), 0x00) @property def x(self): """The x position of the left edge.""" return (self.x_raw_coarse * 2 + self.x_raw_fine) * 4 - 16 @property def y(self): """The y position of the top edge.""" return (self.y_raw * 4) @property def width(self): """Width of the steel area.""" return (self.width_raw + 1) * 4 @property def height(self): """Height of the steel area.""" return (self.height_raw + 1) * 4 @property def repr(self): return "x={}, y={}, width={}, height={}".format( self.x, self.y, self.width, self.height)
class Relocation(mrc.Block): DETAIL_TYPES = { RelocationDetail.INTERNAL_REF: RelocationInternalRef, RelocationDetail.IMPORT_ORDINAL: RelocationImportOrdinal, RelocationDetail.IMPORT_NAME: RelocationImportName, RelocationDetail.OS_FIXUP: RelocationOSFixup } address_type = mrc.UInt8(0x00, enum=RelocationAddressType) detail_type = mrc.Bits(0x01, 0b00000011, enum=RelocationDetail) additive = mrc.Bits(0x01, 0b00000100) offset = mrc.UInt16_LE(0x02) detail = mrc.BlockField(DETAIL_TYPES, 0x04, block_type=mrc.Ref('detail_type')) @property def repr(self): return 'address_type={}, detail_type={}, offset=0x{:04x}, detail={}'.format( str(self.address_type), str(self.detail_type), self.offset, self.detail)
class ResourceInfo(mrc.Block): type_id_low = mrc.UInt8(0x00) type_id_high = mrc.Bits(0x01, 0b01111111) int_id = mrc.Bits(0x01, 0b10000000) count = mrc.UInt16_LE(0x02) reserved = mrc.Bytes(0x04, length=0x04) resources = mrc.BlockField(Resource, 0x08, count=mrc.Ref('count')) @property def type_id(self): return (self.type_id_high << 8) + self.type_id_low @type_id.setter def type_id(self, value): self.type_id_high = (value >> 8) & 0b01111111 self.type_id_low = value & 0b11111111 @property def repr(self): return 'type_id={}, int_id={}, count={}'.format( self.type_id, self.int_id, self.count)
class ANIM(mrc.Block): unk1 = mrc.UInt16_LE(0x00) unk2 = mrc.UInt16_LE(0x02) num_frames = mrc.UInt16_LE(0x04) palette_id = mrc.UInt8(0x06) four_bit = mrc.Bits(0x07, bits=0b10000000) unk4 = mrc.Bits(0x07, bits=0b01111111) unk5 = mrc.UInt32_LE(0x08) frames = mrc.BlockField(AnimFrame, 0x0c, count=mrc.Ref('num_frames'), alignment=4) @property def palette(self): if self._parent: # HACK: get the first HEAD chunk and use the palette list head = next( (x.obj for x in self._parent.chunks if x.id == b'HEAD'), None) return head.palettes[self.palette_id].colours if head else None return []
class Resource(mrc.Block): offset = mrc.UInt16_LE(0x00) size = mrc.UInt16_LE(0x02) unk2 = mrc.Bits(0x04, 0b10000000) preload = mrc.Bits(0x04, 0b01000000) sharable = mrc.Bits(0x04, 0b00100000) movable = mrc.Bits(0x04, 0b00010000) in_memory = mrc.Bits(0x04, 0b00000100) unk2 = mrc.Bits(0x04, 0b00000011) unk3 = mrc.Bits(0x05, 0b11100000) discardable = mrc.Bits(0x05, 0b00010000) unk4 = mrc.Bits(0x05, 0b00001111) int_id = mrc.Bits16(0x06, 0b1000000000000000, endian='little') resource_id = mrc.Bits16(0x06, 0b0111111111111111, endian='little') reserved = mrc.Bytes(0x08, length=0x04) @property def repr(self): return 'offset=0x{:04x}, size=0x{:04x}, resource_id=0x{:04x}, int_id={}'.format( self.offset, self.size, self.resource_id, self.int_id)
class EGAColour( img.Colour ): r_high = mrc.Bits( 0x00, 0b00000100 ) g_high = mrc.Bits( 0x00, 0b00000010 ) b_high = mrc.Bits( 0x00, 0b00000001 ) r_low = mrc.Bits( 0x00, 0b00100000 ) g_low = mrc.Bits( 0x00, 0b00010000 ) b_low = mrc.Bits( 0x00, 0b00001000 ) @property def r( self ): return ((self.r_high << 1) + self.r_low) / 3 @r.setter def r( self, value ): index = round( value * 3 ) self.r_low = index & 1 self.r_high = index >> 1 @property def g( self ): return ((self.g_high << 1) + self.g_low) / 3 @g.setter def g( self, value ): index = round( value * 3 ) self.g_low = index & 1 self.g_high = index >> 1 @property def b( self ): return ((self.b_high << 1) + self.b_low) / 3 @b.setter def b( self, value ): index = round( value * 3 ) self.b_low = index & 1 self.b_high = index >> 1
class EGAColour(img.Colour): r_high = mrc.Bits(0x00, 0b00000100) g_high = mrc.Bits(0x00, 0b00000010) b_high = mrc.Bits(0x00, 0b00000001) r_low = mrc.Bits(0x00, 0b00100000) g_low = mrc.Bits(0x00, 0b00010000) b_low = mrc.Bits(0x00, 0b00001000) @property def r_8(self): return 85 * ((self.r_high << 1) + self.r_low) @r_8.setter def r_8(self, value): index = value // 85 self.r_low = index & 1 self.r_high = index >> 1 @property def g_8(self): return 85 * ((self.g_high << 1) + self.g_low) @g_8.setter def g_8(self, value): index = value // 85 self.g_low = index & 1 self.g_high = index >> 1 @property def b_8(self): return 85 * ((self.b_high << 1) + self.b_low) @b_8.setter def b_8(self, value): index = value // 85 self.b_low = index & 1 self.b_high = index >> 1
class LMove(mrc.Block): const = mrc.Const(mrc.Bits(0x00, 0b0000111100000000, size=2), 0b1100) sld2a = mrc.Bits(0x00, 0b1100000000000000, size=2) sld1a = mrc.Bits(0x00, 0b0011000000000000, size=2) sld2i = mrc.Bits(0x00, 0b0000000011110000, size=2) sld1i = mrc.Bits(0x00, 0b0000000000001111, size=2) def set_sld1(self, x, y, z): if (x == y == 0): self.sld1z = z elif (x == z == 0): self.sld1y = y elif (y == z == 0): self.sld1x = x else: raise ValueError('only one axis allowed') return self def set_sld2(self, x, y, z): if (x == y == 0): self.sld2z = z elif (x == z == 0): self.sld2y = y elif (y == z == 0): self.sld2x = x else: raise ValueError('only one axis allowed') return self @property def sld2x(self): return 0 if self.sld2a != 0b01 else (self.sld2i - 5) @sld2x.setter def sld2x(self, value): assert value in range(-5, 11) self.sld2a = 0b01 self.sld2i = value + 5 @property def sld2y(self): return 0 if self.sld2a != 0b10 else (self.sld2i - 5) @sld2y.setter def sld2y(self, value): assert value in range(-5, 11) self.sld2a = 0b10 self.sld2i = value + 5 @property def sld2z(self): return 0 if self.sld2a != 0b11 else (self.sld2i - 5) @sld2z.setter def sld2z(self, value): assert value in range(-5, 11) self.sld2a = 0b11 self.sld2i = value + 5 @property def sld1x(self): return 0 if self.sld1a != 0b01 else (self.sld1i - 5) @sld1x.setter def sld1x(self, value): assert value in range(-5, 11) self.sld1a = 0b01 self.sld1i = value + 5 @property def sld1y(self): return 0 if self.sld1a != 0b10 else (self.sld1i - 5) @sld1y.setter def sld1y(self, value): assert value in range(-5, 11) self.sld1a = 0b10 self.sld1i = value + 5 @property def sld1z(self): return 0 if self.sld1a != 0b11 else (self.sld1i - 5) @sld1z.setter def sld1z(self, value): assert value in range(-5, 11) self.sld1a = 0b11 self.sld1i = value + 5 @property def repr(self): return 'sld1: ({}, {}, {}), sld2: ({}, {}, {})'.format( self.sld1x, self.sld1y, self.sld1z, self.sld2x, self.sld2y, self.sld2z)
class GVoid(FDBase): const = mrc.Const(mrc.Bits(0x00, 0b00000111, size=1), 0b000) nd = mrc.Bits(0x00, 0b11111000, size=1) fdx_raw = mrc.UInt8(0x01) fdy_raw = mrc.UInt8(0x02) fdz_raw = mrc.UInt8(0x03)
class Void(NDBase): const = mrc.Const(mrc.Bits(0x00, 0b00000111, size=1), 0b010) nd = mrc.Bits(0x00, 0b11111000, size=1)
class Fill(NDBase): const = mrc.Const(mrc.Bits(0x00, 0b00000111, size=1), 0b011) nd = mrc.Bits(0x00, 0b11111000, size=1)
class FusionS(NDBase): const = mrc.Const(mrc.Bits(0x00, 0b00000111), 0b110) nd = mrc.Bits(0x00, 0b11111000)
class Controls( mrc.Block ): driver_door = mrc.Bits( 0x00, 0b10000000 ) high_beams = mrc.Bits( 0x03, 0b01000000 )