# https://github.com/alexandergitter/theme-hospital-spec/blob/master/format-specification.md class Reader: def __init__(self, fname): self.load(fname) def load(self, fname): handle = open(fname, 'rb') self.data = handle.read() self.offset = 0 handle.close() def reset(self): self.offset = 0 def size(self): return len(self.data) def get(self, index): return self.data[index] def byte(self): assert self.offset < self.size() b = self.data[self.offset] self.offset = self.offset + 1 return b def word(self): b = self.byte() c = self.byte() return b | (c << 8) def long(self): b = self.word() c = self.word() return b | (c << 16) class Palette: def __init__(self): self.palette = None def load(self, reader): reader.reset() assert reader.size() == 3 * 256 self.palette = [] for i in range(256): r = reader.byte() g = reader.byte() b = reader.byte() self.palette.append((r, g, b)) def make_palette(fname): reader = Reader(fname) p = Palette() p.load(reader) return p class Frame: """ Meta information of a single frame. frame_offset: Offset of the frame in the image file. width: Width of the image height: Height of the image soundindex: If not 0, index into the filetable of the sounds data file start_animation: Whether this frame is the start of an animation next_frame: Index of the next frame in the L{Frames} list. """ def __init__(self, listindex, width, height, soundindex, flags, nextindex): self.frame_offset = listindex * 2 self.width = width self.height = height self.soundindex = soundindex self.start_animation = (flags & 1) != 0 self.next_frame = nextindex class Frames: """ All meta information of fromes in a file. """ def __init__(self): self.frames = [] def load(self, reader): reader.reset() length = reader.size() assert length % 10 == 0 self.frames = [] while length > 0: lind = reader.long() w = reader.byte() h = reader.byte() sindex = reader.byte() flags = reader.byte() nextframe = reader.word() self.frames.append(Frame(lind, w, h, sindex, flags, nextframe)) length = length - 10 class FrameList: def __init__(self): self.list = [] def load(self, reader): reader.reset() assert reader.size() % 2 == 0 self.list = [] for i in range(reader.size() // 2): w = reader.word() if w == 0xffff: self.list.append(None) else: self.list.append(w) # Position in the sprite elements. class SpriteElement: """ sprite_pos: Absolute position in the sprite table. offset_x: Horizontal offset of the image. offset_y: Vertical offset of the image. layer_class: Layer class. flags: Render flags. layerid: Layer of this sprite. """ def __init__(self, sprite_pos, offset_x, offset_y, layer_class, flags, layerid): self.sprite_pos = sprite_pos self.offset_x = offset_x self.offset_y = offset_y self.layer_class = layer_class self.flags = flags self.layerid = layerid def flip_vert(self): """Whether to flip the image vertically on rendering""" return (self.flags & 1) != 0 def flip_hor(self): """Whether to flip the image horizontally on rendering""" return (self.flags & 2) != 0 def alpha50(self): """Whether to draw the inage with 50% alpha.""" return (self.flags & 4) != 0 def alpha75(self): """Whether to draw the inage with 75% alpha.""" return (self.flags & 8) != 0 class SpriteElements: def __init__(self): self.elements = [] # XXX Finish me! class SpriteTableElement: def __init__(self, offset, width, height): self.offset = offset self.width = width self.height = height def __str__(self): return "offset={}, width={}, height={}".format(self.offset, self.width, self.height) class SpriteTable: def __init__(self): self.sprites = [] def load(self, reader): reader.reset() length = reader.size() print(length) assert length % 6 == 0 self.sprites = [] while length > 0: offset = reader.long() width = reader.byte() height = reader.byte() length = length - 6 self.sprites.append(SpriteTableElement(offset, width, height)) def MakeSpriteTable(fname): reader = Reader(fname) sprt = SpriteTable() sprt.load(reader) return sprt #data = load('CD/HOSP/DATA/VFRA-1.ANI') #Looks like Frames pal = make_palette('MPALETTE.DAT.decoded') dat = Reader('VSPR-0.DAT.decoded') sprt = MakeSpriteTable("VSPR-0.TAB.decoded") print("Number of sprites: {}".format(len(sprt.sprites))) #print("\n".join(str(s) for s in sprt.sprites)) from PIL import Image # Raw graphics def raw_graphics(spr, pal, reader): print(spr) im = Image.new("RGBA", (spr.width, spr.height), (0, 0, 0, 0)) offset = spr.offset for y in range(spr.height): for x in range(spr.width): v = reader.get(offset) col = pal.palette[v] col = (col[0], col[1], col[2], 255) offset = offset + 1 im.putpixel((x, y), col) return im def extra_chunk_graphics(spr, pal, reader): if spr.width == 0 or spr.height == 0: return None print(spr) im = Image.new("RGBA", (spr.width, spr.height), (200, 0, 200, 255)) offset = spr.offset x, y = 0, 0 while y < spr.height: v = reader.get(offset) offset = offset + 1 if v == 0: # End of the row. x, y = 0, y+1 continue if v <= 63: # v opaque pixels while v > 0: w = reader.get(offset) offset = offset + 1 col = pal.palette[w] col = (col[0], col[1], col[2], 255) im.putpixel((x, y), col) x = x + 1 if x >= spr.width: x, y = 0, y+1 if y >= spr.height: break; v = v - 1 continue if v <= 127: # (v-60) times the same pixel v = v - 60 w = reader.get(offset) offset = offset + 1 col = pal.palette[w] col = (col[0], col[1], col[2], 255) while v > 0: im.putpixel((x, y), col) x = x + 1 if x >= spr.width: x, y = 0, y+1 if y >= spr.height: break; v = v - 1 continue if v < 192: # (v-128) transparent pixels v = v - 128 while v > 0: x = x + 1 if x >= spr.width: x, y = 0, y+1 if y >= spr.height: break; v = v - 1 continue if v < 255: # (v - 124) times the same pixel v = v - 124 w = reader.get(offset) offset = offset + 1 col = pal.palette[w] col = (col[0], col[1], col[2], 255) while v > 0: im.putpixel((x, y), col) x = x + 1 if x >= spr.width: x, y = 0, y+1 if y >= spr.height: break; v = v - 1 continue assert v == 255 V = reader.get(offset) w = reader.get(offset) col = pal.palette[w] col = (col[0], col[1], col[2], 255) while v > 0: im.putpixel((x, y), col) x = x + 1 if x >= spr.width: x, y = 0, y+1 if y >= spr.height: break; v = v - 1 return im for sprite in [5, 21, 345]: #im = raw_graphics(sprt.sprites[sprite], pal, dat) im = extra_chunk_graphics(sprt.sprites[sprite], pal, dat) im.save('sprite_{}.png'.format(sprite))