class MetaTile(mrc.Block): top_left = mrc.BlockField(TileQuad, 0x00) top_right = mrc.BlockField(TileQuad, 0x02) bottom_left = mrc.BlockField(TileQuad, 0x04) bottom_right = mrc.BlockField(TileQuad, 0x06) @property def repr(self): return 'top_left: {}, top_right: {}, bottom_left: {}, bottom_right: {}'.format( self.top_left, self.top_right, self.bottom_left, self.bottom_right)
class ResourceFork(mrc.Block): """ """ resourceDataOffset = mrc.UInt32_BE(0x00) resourceMapOffset = mrc.UInt32_BE(0x04) resourceDataSize = mrc.UInt32_BE(0x08) resourceMapSize = mrc.UInt32_BE(0x0c) resourceData = mrc.BlockField(ResourceData, offset=mrc.Ref("resourceDataOffset"), length=mrc.Ref("resourceDataSize")) resourceMap = mrc.BlockField(MapData, offset=mrc.Ref("resourceMapOffset"), length=mrc.Ref("resourceMapSize"))
class PRSFile(mrc.Block): magic = mrc.Const(mrc.Bytes(0x00, length=0x18), b'PRS Format Resource File') unk1 = mrc.UInt8(0x18) unk2 = mrc.UInt8(0x19) unk3 = mrc.UInt8(0x1f) chunks = mrc.BlockField(PRSChunk, 0x30, stream=True)
class B800Screen(mrc.Block): B800_SCREEN_WIDTH = 80 chars = mrc.BlockField(B800Char, 0x00, count=2000) @property def text(self): return '\n'.join([ ''.join([ c.char for c in self.chars[i * self.B800_SCREEN_WIDTH:] [:self.B800_SCREEN_WIDTH] ]) for i in range((len(self.chars) + 1) // self.B800_SCREEN_WIDTH) ]) def ansi_format(self): result = [] for i in range((len(self.chars) + 1) // self.B800_SCREEN_WIDTH): for c in self.chars[i * self.B800_SCREEN_WIDTH:][:self. B800_SCREEN_WIDTH]: result.append(c.ansi_format()) result.append('\n') return ''.join(result) def print(self): print(self.ansi_format()) def __str__(self): return self.ansi_format() def __repr__(self): return '<{}: {} chars, {}x{}>'.format( self.__class__.__name__, len(self.chars), self.B800_SCREEN_WIDTH, 1 + (len(self.chars) - 1) // self.B800_SCREEN_WIDTH)
class EXE(mrc.Block): dos_magic = mrc.Const(mrc.Bytes(0x00, length=2), b'MZ') dos_header = mrc.Bytes(0x02, length=0x3a) ne_offset = mrc.UInt16_LE(0x3c) dos_stub = mrc.Bytes(0x3e, length=mrc.Ref('dos_stub_length')) ne_header = mrc.BlockField(NEHeader, mrc.Ref('ne_offset')) segdata = mrc.Bytes( mrc.EndOffset('ne_header', align=mrc.Ref('sector_align'))) @property def sector_align(self): if self.ne_header: return 1 << self.ne_header.sector_shift return 32 @property def dos_stub_length(self): return self.ne_offset - 0x3e def __init__(self, *args, **kwargs): self.segdatastore = mrc.Store(self, mrc.Ref('segdata'), base_offset=mrc.EndOffset( 'ne_header', neg=True, align=mrc.Ref('sector_align')), align=mrc.Ref('sector_align')) super().__init__(*args, **kwargs)
class ChannelV4(mrc.Block): CHANNEL_MAP = { None: EmptyChannelV4, # ChannelType.SPRITE: SpriteChannelV4, ChannelType.FRAME_SCRIPT: ScriptChannelV4, # ChannelType.PALETTE: mrc.Unknown, } channel_size = mrc.UInt16_BE(0x00) channel_offset = mrc.UInt16_BE(0x02) @property def channel_row(self): return self.channel_offset // 0x14 @property def channel_type(self): return self.channel_offset % 0x14 @property def channel_type_wrap(self): return (ChannelType.PALETTE if self.channel_offset == 0x14 else self.channel_type) if self.channel_size else None data = mrc.BlockField(CHANNEL_MAP, 0x04, block_type=mrc.Ref('channel_type_wrap'), default_klass=mrc.Unknown, length=mrc.Ref('channel_size')) @property def repr(self): return f'channel_size=0x{self.channel_size:02x}, channel_offset=0x{self.channel_offset:04x}, channel_row={self.channel_row}, channel_type={self.channel_type}'
class Segment(mrc.Block): RELOCATION_TYPES = {1: RelocationTable, 0: NullRelocationTable} data = mrc.Bytes(0x00, length=mrc.Ref('_parent.size')) relocations = mrc.BlockField(RELOCATION_TYPES, mrc.EndOffset('data'), block_type=mrc.Ref('_parent.relocations'))
class KeyV4(mrc.Block): unk1 = mrc.UInt16_P(0x00) unk2 = mrc.UInt16_P(0x02) unk3 = mrc.UInt32_P(0x04) entry_count = mrc.UInt32_P(0x08) entries = mrc.BlockField(KeyEntry, 0x0c, count=mrc.Ref('entry_count')) garbage = mrc.Bytes(mrc.EndOffset('entries'))
class Test(mrc.Block): count = mrc.UInt8(0x00) elements = mrc.BlockField(ElementRef, count=mrc.Ref('count')) raw_data = mrc.Bytes(mrc.EndOffset('elements')) def __init__(self, *args, **kwargs): self.store = mrc.Store(self, mrc.Ref('raw_data')) super().__init__(*args, **kwargs)
class ResidentNameTable(mrc.Block): module_name_size = mrc.UInt8(0x00) module_name = mrc.Bytes(0x01, length=mrc.Ref('module_name_size')) padding = mrc.UInt16_LE(mrc.EndOffset('module_name')) resnames = mrc.BlockField(ResidentName, mrc.EndOffset('padding'), stream=True, stream_end=b'\x00')
class RIFX(mrc.Block): _endian = 'big' CHUNK_MAP_CLASS = RIFXMap magic = mrc.Const(mrc.UInt32_P(0x00), Tag(b'RIFX')) size = mrc.UInt32_P(0x04) map = mrc.BlockField(mrc.Ref('CHUNK_MAP_CLASS'), 0x08, length=mrc.Ref('size'))
class ShapeCastV4(mrc.Block): type = mrc.UInt16_BE(0x00, enum=ShapeType) rect = mrc.BlockField(Rect, 0x02) pattern = mrc.UInt16_BE(0x0a) fg_colour = mrc.UInt8(0x0c) bg_colour = mrc.UInt8(0x0d) fill_type = mrc.UInt8(0x0e) line_thickness = mrc.UInt8(0x0f) line_direction = mrc.UInt8(0x10)
class FrameV4(mrc.Block): size = mrc.UInt16_BE(0x00) channels = mrc.BlockField(ChannelV4, 0x02, stream=True, length=mrc.Ref('size_channels')) @property def size_channels(self): return self.size - 0x02
class MUSFile(mrc.Block): count = mrc.UInt32_LE(0x00) entries = mrc.BlockField(MusicEntry, 0x04, count=mrc.Ref('count')) music_raw = mrc.Bytes(mrc.EndOffset('entries')) def __init__(self, *args, **kwargs): self.tracks = mrc.Store(parent=self, source=mrc.Ref('music_raw'), base_offset=mrc.EndOffset('entries', neg=True)) super().__init__(*args, **kwargs)
class IMGFile(mrc.Block): count = mrc.UInt32_LE(0x00) entries = mrc.BlockField(ImageEntry, 0x04, count=mrc.Ref('count')) images_raw = mrc.Bytes(mrc.EndOffset('entries')) def __init__(self, *args, **kwargs): self.images = mrc.Store(parent=self, source=mrc.Ref('images_raw'), base_offset=mrc.EndOffset('entries', neg=True)) super().__init__(*args, **kwargs)
class GroundDAT(mrc.Block): """Represents a single graphical style.""" _vgagr = None # should be replaced by the correct VgagrDAT object #: Information for every type of interactive piece. interactive_info = mrc.BlockField(InteractiveInfo, 0x0000, count=16, fill=b'\x00' * 28) #: Information for every type of terrain piece. terrain_info = mrc.BlockField(TerrainInfo, 0x01c0, count=64, fill=b'\x00' * 8) #: Extended EGA palette used for rendering the level preview. palette_ega_preview = img.Palette(ibm_pc.EGAColour, 0x03c0, count=8) #: Copy of EGA palette used for rendering lemmings/action bar. #: Colours 0-6 are not used by the game, instead there is a palette embedded #: in the executable. #: Colour 7 is used for drawing the minimap and dirt particles. palette_ega_standard = img.Palette(ibm_pc.EGAColour, 0x03c8, count=8) #: EGA palette used for rendering interactive/terrain pieces. palette_ega_custom = img.Palette(ibm_pc.EGAColour, 0x03d0, count=8) #: VGA palette used for rendering interactive/terrain pieces. palette_vga_custom = img.Palette(ibm_pc.VGAColour, 0x03d8, count=8) #: Copy of VGA palette used for rendering lemmings/action bar. #: Colours 0-6 are not used by the game, instead there is a palette embedded #: in the executable. #: Colour 7 is used for drawing the minimap and dirt particles. palette_vga_standard = img.Palette(ibm_pc.VGAColour, 0x03f0, count=8) #: VGA palette used for rendering the level preview. palette_vga_preview = img.Palette(ibm_pc.VGAColour, 0x0408, count=8) @property def palette(self): if not hasattr(self, '_palette'): self._palette = [ img.Transparent() ] + self.palette_vga_standard[1:] + self.palette_vga_custom return self._palette
class SplashVGA(mrc.Block): palette = mrc.BlockField(ibm_pc.VGAColour, 0x0000, count=256) image_data = mrc.Bytes(0x0300) def __init__(self, *args, **kwargs): mrc.Block.__init__(self, *args, **kwargs) self.image = img.IndexedImage(self, width=320, height=200, palette=mrc.Ref('palette'), source=mrc.Ref('image_data'))
class ResourceInfo(mrc.Block): int_id = mrc.Bits16(0x00, 0b1000000000000000, endian='little') type_id = mrc.Bits16(0x00, 0b0111111111111111, endian='little') count = mrc.UInt16_LE(0x02) reserved = mrc.Bytes(0x04, length=0x04) resources = mrc.BlockField(Resource, 0x08, count=mrc.Ref('count')) @property def repr(self): return 'type_id={}, int_id={}, count={}'.format( self.type_id, self.int_id, self.count)
class ResourceTable(mrc.Block): align_shift = mrc.UInt16_LE(0x00) resourceinfo = mrc.BlockField(ResourceInfo, 0x02, stream=True, stream_end=b'\x00\x00') #name_data = mrc.Bytes( mrc.EndOffset( 'resourceinfo' ), length=0x107 ) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs)
class Song(mrc.Block): mode = mrc.UInt8(0x0000) speed = mrc.UInt16_LE(0x0001) tempo = mrc.UInt8(0x0003) pattlen = mrc.UInt8(0x0004) chandelay = mrc.UInt8(0x0005, count=9) regbd = mrc.UInt8(0x000e) patch_count = mrc.UInt16_LE(0x000f) patches = mrc.BlockField(SongPatch, 0x0011, count=mrc.Ref('patch_count')) position_count = mrc.UInt16_LE(mrc.EndOffset('patches')) positions = mrc.BlockField(SongPosition, mrc.EndOffset('position_count'), count=mrc.Ref('position_count')) num_digital = mrc.UInt16_LE(mrc.EndOffset('positions')) patterns = mrc.BlockField(SongPattern, mrc.EndOffset('num_digital'), stream=True)
class SoundCastV4(mrc.Block): unk1 = mrc.Bytes(0x00, length=0x20) extra_size = mrc.UInt8(0x20) extra = mrc.BlockField(SoundCastV4Extra, 0x21, stream=True, length=mrc.Ref('extra_size')) @property def repr(self): extra_str = f', name={self.extra[0].name}' if self.extra else '' return f'unk1={self.unk1}{extra_str}'
class ScriptContextV4(mrc.Block): #test = mrc.Bytes() unk1 = mrc.Bytes(0x00, length=0x8) list_count = mrc.UInt32_BE(0x08) list_count_2 = mrc.UInt32_BE(0x0c) list_offset = mrc.UInt16_BE(0x10) unk2 = mrc.UInt16_BE(0x12) unk3 = mrc.Bytes(0x14, length=22) entries = mrc.BlockField(ScriptContextEntry, mrc.Ref('list_offset'), count=mrc.Ref('list_count'))
class SoundCastV4(mrc.Block): unk1 = mrc.Bytes(0x00, length=0x20) extra_size = mrc.UInt8(0x20) extra = mrc.BlockField(SoundCastV4Extra, 0x21, stream=True, length=mrc.Ref('extra_size')) @property def repr(self): return 'unk1={}{}'.format( self.unk1, ', name={}'.format(self.extra[0].name) if self.extra else '')
class VgagrDAT(mrc.Block): stores = mrc.BlockField(VgagrStore, 0x0000, stream=True, transform=DATCompressor()) @property def terrain_store(self): return self.stores[0] @property def interact_store(self): return self.stores[1]
class UPS(mrc.Block): STOP_CHECK = lambda buffer, pointer: pointer >= len(buffer) - 12 magic = mrc.Const(mrc.Bytes(0x00, length=4), b'UPS1') input_size = UIntVLV(0x04) output_size = UIntVLV(mrc.EndOffset('input_size')) blocks = mrc.BlockField(UPSBlock, mrc.EndOffset('output_size'), stream=True, stop_check=STOP_CHECK) input_crc32 = mrc.UInt32_LE(mrc.EndOffset('blocks')) output_crc32 = mrc.UInt32_LE(mrc.EndOffset('input_crc32')) patch_crc32 = mrc.UInt32_LE(mrc.EndOffset('output_crc32'))
class IPS(mrc.Block): magic = mrc.Const(mrc.Bytes(0x00, length=5), b'PATCH') records = mrc.BlockField(IPSRecord, 0x05, stream=True, stream_end=b'EOF') @property def repr(self): return f'records: {len( self.records )}' def create(self, source, target): pass def patch(self, source): return source
class ScriptConstantV5(mrc.Block): SCRIPT_CONSTANT_TYPES = { 0x0001: ScriptConstantString, 0x0004: ScriptConstantUInt32, 0x0009: ScriptConstantFloat } const_type = mrc.UInt32_BE(0x00, enum=ScriptConstantType) const = mrc.BlockField(SCRIPT_CONSTANT_TYPES, 0x04, block_type=mrc.Ref('const_type')) @property def repr(self): return '{}: {}'.format(self.const_type, self.const.repr)
class NEHeader(NEBase): linker_ver = mrc.UInt8(0x02) linker_rev = mrc.UInt8(0x03) entry_size = mrc.UInt16_LE(0x06) crc = mrc.UInt32_LE(0x08) ds_id = mrc.UInt16_LE(0x0e) resource_count = mrc.UInt16_LE(0x34) segtable = mrc.BlockField(SegmentHeader, mrc.Ref('segtable_offset'), count=mrc.Ref('segtable_count')) restable = mrc.BlockField(ResourceTable, mrc.Ref('restable_offset')) resnametable = mrc.BlockField(ResidentNameTable, mrc.Ref('resnames_offset')) modreftable = mrc.BlockField(ModuleReference, mrc.Ref('modref_offset'), count=mrc.Ref('modref_count')) impnamedata = mrc.Bytes(mrc.Ref('impnames_offset'), length=mrc.Ref('impnames_size')) #entrydata = mrc.BlockField( EntryBundle, mrc.Ref( 'entry_offset' ), stream=True, length=mrc.Ref( 'entry_size' ) ) entrydata = mrc.Bytes(mrc.Ref('entry_offset'), length=mrc.Ref('entry_size')) nonresnametable = mrc.BlockField(ResidentNameTable, mrc.Ref('nonresnames_offset')) @property def nonresnames_offset(self): return self.nonresnames_rel_offset - (self._parent.ne_offset if self._parent else 0) def __init__(self, *args, **kwargs): self.impnamestore = mrc.Store(self, mrc.Ref('impnamedata')) super().__init__(*args, **kwargs)
class MMapV4(mrc.Block): unk1 = mrc.Bytes(0x00, length=8) entries_max = mrc.UInt32_P(0x04) entries_used = mrc.UInt32_P(0x08) unk2 = mrc.Const(mrc.Bytes(0x0c, length=8), b'\xff' * 8) unk3 = mrc.UInt32_P(0x14) entries = mrc.BlockField(MMapEntry, 0x18, count=mrc.Ref('entries_max'), fill=b'\xaa' * 0x14) garbage = mrc.Bytes(mrc.EndOffset('entries')) @property def repr(self): return f'entries_max: {self.entries_max}, entries_used: {self.entries_used}'
class ScriptConstantV4(mrc.Block): SCRIPT_CONSTANT_TYPES = { 0x0001: ScriptConstantString, 0x0004: ScriptConstantUInt32, 0x0009: ScriptConstantFloat } const_type = mrc.UInt16_BE(0x00, enum=ScriptConstantType) const = mrc.BlockField(SCRIPT_CONSTANT_TYPES, 0x02, block_type=mrc.Ref('const_type')) @property def repr(self): return f'{self.const_type}: {self.const.repr}'