diff options
Diffstat (limited to 'bubbob/macbinary.py')
-rw-r--r-- | bubbob/macbinary.py | 260 |
1 files changed, 260 insertions, 0 deletions
diff --git a/bubbob/macbinary.py b/bubbob/macbinary.py new file mode 100644 index 0000000..616bce0 --- /dev/null +++ b/bubbob/macbinary.py @@ -0,0 +1,260 @@ +import struct + + +def padto(n, m): + return (n+m-1) & ~(m-1) + +def resourceclass(rtype): + return globals().get(rtype.strip() + 'Resource', Resource) + + +class TypeList: + + def __init__(self, type, fmap, fdata, namebase, start, count): + self.type = type + self.fmap = fmap + self.fdata = fdata + self.namebase = namebase + self.start = start + self.count = count + self.ids = None + + def resources(self): + if self.ids is None: + ResourceCls = resourceclass(self.type) + d = {} + self.fmap.seek(self.start) + for resid, resname, resattr, resofshi, resofslo in [ + struct.unpack(">HHBBHxxxx", self.fmap.read(12)) + for i in range(self.count)]: + if resname == 0xffff: + name = None + else: + self.fmap.seek(self.namebase + resname) + namelen, = struct.unpack(">B", self.fmap.read(1)) + name = self.fmap.read(namelen) + assert resid not in d + d[resid] = ResourceCls(self.type, resid, name, resattr, + self.fdata, resofslo + (resofshi<<16)) + self.ids = d + return self.ids + + def __getitem__(self, id): + return self.resources()[id] + + def keys(self): + return self.resources().keys() + + def values(self): + return self.resources().values() + + def items(self): + return self.resources().items() + + def namedict(self): + return dict([(r.name, r) for r in self.resources().values() if r.name is not None]) + + +class MacBinary: + + def __init__(self, f): + if type(f) is type(''): + f = open(f, 'rb') + self.f = f + self.f.seek(0x53) + self.dataforksize, self.resforksize = struct.unpack(">ll", self.f.read(8)) + self.loadresources() + + def getdata(self): + self.f.seek(0x80) + return self.f.read(self.dataforksize) + + def loadresources(self): + f = Subfile(self.f, padto(0x80 + self.dataforksize, 0x80), self.resforksize) + ofsdata, ofsmap, lendata, lenmap = struct.unpack(">llll", f.read(16)) + fdata = Subfile(f, ofsdata, lendata) + fmap = Subfile(f, ofsmap, lenmap) + fmap.seek(24) + ofstype, ofsname = struct.unpack(">HH", fmap.read(4)) + self.dtypes = {} + fmap.seek(ofstype) + numtypes, = struct.unpack(">H", fmap.read(2)) + numtypes = numtypes + 1 + for rtype, num, ofsref in [struct.unpack(">4sHH", fmap.read(8)) + for i in range(numtypes)]: + assert rtype not in self.dtypes + self.dtypes[rtype] = TypeList(rtype, fmap, fdata, ofsname, + ofstype + ofsref, num + 1) + + def __getitem__(self, rtype): + return self.dtypes[rtype] + + def types(self): + return self.dtypes + + def keys(self): + return self.dtypes.keys() + + def values(self): + return self.dtypes.values() + + def items(self): + return self.dtypes.items() + + +class Subfile: + def __init__(self, f, start, length): + if start < 0: + raise ValueError, 'negative position' + if isinstance(f, Subfile): + if start + length > f.length: + raise ValueError, 'subfile out of bounds' + f, start = f.f, f.start+start + self.f = f + self.start = start + self.length = length + self.position = 0 + def read(self, size=None): + if size is None or self.position + size > self.length: + size = self.length - self.position + if size <= 0: + return '' + self.f.seek(self.start + self.position) + self.position = self.position + size + return self.f.read(size) + def seek(self, npos): + if npos < 0: + raise ValueError, 'negative position' + self.position = npos + + +class Resource: + + def __init__(self, type, id, name, attr, srcfile, srcofs): + self.type = type + self.id = id + self.name = name + self.attr = attr + self.srcfile = srcfile + self.srcofs = srcofs + + def subfile(self): + self.srcfile.seek(self.srcofs) + length, = struct.unpack(">l", self.srcfile.read(4)) + return Subfile(self.srcfile, self.srcofs + 4, length) + + def load(self): + return self.subfile().read() + + +class RGBImage: + def __init__(self, w, h, data): + assert len(data) == 3*w*h + self.w = w + self.h = h + self.data = data + + +def loadcolormap(f): + size, = struct.unpack(">xxxxxxH", f.read(8)) + size = size + 1 + d = {} + for index, r, g, b in [struct.unpack(">HHHH", f.read(8)) for i in range(size)]: + assert index not in d, 'duplicate color index' + d[index] = r/256.0, g/256.0, b/256.0 + return d + +def image2rgb(image): + # returns (w, h, data) + h = len(image) + result1 = [] + for line in image: + for r, g, b in line: + result1.append(chr(int(r)) + chr(int(g)) + chr(int(b))) + return len(image[0]), len(image), ''.join(result1) + + +class clutResource(Resource): + # a color table + def gettable(self): + return loadcolormap(self.subfile()) + + +class ppatResource(Resource): + # a pattern + def getimage(self): + f = self.subfile() + pattype, patmap, patdata = struct.unpack(">Hll", f.read(10)) + if pattype != 1: + raise ValueError, 'Pattern type not supported' + f.seek(patmap) + (rowBytes, h, w, packType, packSize, + pixelType, pixelSize, cmpCount, cmpSize, pmTable) = ( + struct.unpack(">xxxxHxxxxHHxxHlxxxxxxxxHHHHxxxxlxxxx", f.read(50))) + isBitmap = (rowBytes & 0x8000) != 0 + rowBytes &= 0x3FFF + if packType != 0: + raise ValueError, 'packed image not supported' + if pixelType != 0 or cmpCount != 1: + raise ValueError, 'direct RGB image not supported' + assert cmpSize == pixelSize and pixelSize in [1,2,4,8] + f.seek(pmTable) + colormap = loadcolormap(f) + bits_per_pixel = pixelSize + pixels_per_byte = 8 // bits_per_pixel + image = [] + f.seek(patdata) + for y in range(h): + line = f.read(rowBytes) + imgline = [] + for x in range(w): + n = x//pixels_per_byte + idx = ((ord(line[n]) >> ((pixels_per_byte - 1 - x%pixels_per_byte) * bits_per_pixel)) + & ((1<<bits_per_pixel)-1)) + imgline.append(colormap[idx]) + image.append(imgline) + return image + + +class LEVLResource(Resource): + # bub & bob level + WIDTH = 32 + HEIGHT = 25 + MONSTERS = 30 + WALLS = { 1:'#', 0:' '} + WINDS = { 0:' ', 1:'>', 2:'<', 3:'v', 4:'^', 5:'x', 0x66:' '} + FLAGS = ['flag0', 'letter', 'fire', 'lightning', 'water', 'top', 'flag6', 'flag7'] + + def getlevel(self, mnstrlist): + f = self.subfile() + result = {} + + walls = [] + for y in range(self.HEIGHT): + line = f.read(self.WIDTH//8) + line = [self.WALLS[(ord(line[x//8]) >> (x%8)) & 1] + for x in range(self.WIDTH)] + walls.append(''.join(line)) + result['walls'] = '\n'.join(walls) + + winds = [] + for y in range(self.HEIGHT): + line = f.read(self.WIDTH) + line = [self.WINDS[ord(v)] for v in line] + winds.append(''.join(line)) + result['winds'] = '\n'.join(winds) + + monsters = [] + for i in range(self.MONSTERS): + x,y,monster_type,f1,f2,f3 = struct.unpack(">BBBBBB", f.read(6)) + if monster_type != 0: + assert f1 == 0, f1 + cls = mnstrlist[monster_type-1] + monsters.append(cls(x=x, y=y, dir=f2, player=f3)) + result['monsters'] = monsters + + result['level'], = struct.unpack('>H', f.read(2)) + for i in range(8): + result[self.FLAGS[i]] = ord(f.read(1)) + + return result |