summaryrefslogtreecommitdiff
path: root/bubbob/macbinary.py
diff options
context:
space:
mode:
Diffstat (limited to 'bubbob/macbinary.py')
-rw-r--r--bubbob/macbinary.py260
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