mirror of
https://github.com/abakh/nbsdgames.git
synced 2025-02-02 15:07:27 -05:00
261 lines
7.9 KiB
Python
261 lines
7.9 KiB
Python
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
|