示例#1
0
    def load(filename, book=None, req_book=None):
        """
        Static method to load a savename file.  This will open the file once and
        read in a bit of data to determine whether this is a Book 1 character file or
        a Book 2 character file, and then call the appropriate constructor and
        return the object.  The individual Book constructors expect to be passed in
        an 
        """
        df = Savefile(filename)

        # First figure out what format to load, if needed
        if book is None:
            try:
                df.open_r()
                name = df.readstr()
                date = df.readstr()
                time = df.readstr()
                map_or_version = df.readstr()
                df.close()
            except (IOError, struct.error), e:
                raise LoadException(str(e))

            if map_or_version.startswith('book3'):
                book = 3
            elif map_or_version in B1Constants.maps:
                book = 1
            else:
                book = 2
示例#2
0
    def load_charname(self, book=None) -> None:
        """
        Read our character name; this is a bit dependant on the book number,
        which is why we pass it in here.  If the book number is not passed
        in, we will scan for map files and then use the first map's book
        number.  If there are no maps in the slot, then we'll raise a
        LoadException
        """

        # First figure out which book number we are, if we don't have it yet
        if not book:
            if not self.maps_loaded:
                self.load_maps()
            if len(self.maps) > 0:
                book = self.maps[0].book
            else:
                raise LoadException(
                    'Could not auto-detect which book version to use for charname')

        # Now do the actual loading
        if not os.path.exists(self.char_loc):
            raise LoadException(f'"char" file not found in {self.char_loc}')
        df = Savefile(self.char_loc)
        df.open_r()
        if book == 1:
            df.readint()
        else:
            df.readuchar()
        self.charname = df.readstr().decode('UTF-8')
        self.char_loaded = True
        df.close()
示例#3
0
    def load(filename, book=None, req_book=None):
        """
        Static method to load a savename file.  This will open the file once and
        read in a bit of data to determine whether this is a Book 1 character file or
        a Book 2 character file, and then call the appropriate constructor and
        return the object.  The individual Book constructors expect to be passed in
        an 
        """
        df = Savefile(filename)

        # First figure out what format to load, if needed
        if book is None:
            try:
                df.open_r()
                name = df.readstr()
                date = df.readstr()
                time = df.readstr()
                map_or_version = df.readstr()
                df.close()
            except (IOError, struct.error), e:
                raise LoadException(str(e))

            if map_or_version.startswith('book3'):
                book = 3
            elif map_or_version in B1Constants.maps:
                book = 1
            else:
                book = 2
示例#4
0
    def load(filename, book=None, req_book=None):
        """
        Static method to load a character file.  This will open the file once and
        read in a bit of data to determine whether this is a Book 1 character file or
        a Book 2 character file, and then call the appropriate constructor and
        return the object.  The individual Book constructors expect to be passed in
        an 
        """
        df = Savefile(filename)

        # First figure out what format to load, if needed
        if book is None:
            # The initial "zero" padding in Book 1 is four bytes, and only one byte in
            # Book 2.  Since the next bit of data is the character name, as a string,
            # if the second byte of the file is 00, we'll assume that it's a Book 1 file,
            # and Book 2 otherwise.
            try:
                df.open_r()
                initital = df.readuchar()
                second = df.readuchar()
                df.close()
            except (IOError, struct.error), e:
                raise LoadException(str(e))

            if second == 0:
                book = 1
            else:
                book = 2
示例#5
0
    def load(filename, book=None, req_book=None):
        """
        Static method to load a map file.  This will open the file once
        and read in a bit of data to determine whether this is a Book 1 map file
        or a Book 1 map file, and then call the appropriate constructor and
        return the object.
        """
        df = Savefile(filename)

        # Book 1 files start with 10 strings, Book 2 with 9, and Book 3 with
        # more.  To see what kind of file we have, read 11 strings and check
        # whether the last two are ASCII-only
        if book is None:
            try:
                df.open_r()
                strings = []
                for i in range(11):
                    strings.append(df.readstr())
                df.close()
            except (IOError, struct.error), e:
                raise LoadException(str(e))

            if not Map.is_ascii(strings[9]):
                book = 2
            elif not Map.is_ascii(strings[10]):
                book = 1
            else:
                book = 3
示例#6
0
    def load_charname(self, book=None):
        """
        Read our character name; this is a bit dependant on the book number,
        which is why we pass it in here.  If the book number is not passed
        in, we will scan for map files and then use the first map's book
        number.  If there are no maps in the slot, then we'll raise a
        LoadException
        """

        # First figure out which book number we are, if we don't have it yet
        if not book:
            if not self.maps_loaded:
                self.load_maps()
            if len(self.maps) > 0:
                book = self.maps[0].book
            else:
                raise LoadException(
                    'Could not auto-detect which book version to use for charname'
                )

        # Now do the actual loading
        if not os.path.exists(self.char_loc):
            raise LoadException('"char" file not found in %s' % (directory))
        df = Savefile(self.char_loc)
        df.open_r()
        if book == 1:
            df.readint()
        else:
            df.readuchar()
        self.charname = df.readstr()
        self.char_loaded = True
        df.close()
示例#7
0
    def load(filename, book=None, req_book=None):
        """
        Static method to load a character file.  This will open the file once and
        read in a bit of data to determine whether this is a Book 1 character file or
        a Book 2 character file, and then call the appropriate constructor and
        return the object.  The individual Book constructors expect to be passed in
        an 
        """
        df = Savefile(filename)

        # First figure out what format to load, if needed
        if book is None:
            # The initial "zero" padding in Book 1 is four bytes, and only one byte in
            # Book 2.  Since the next bit of data is the character name, as a string,
            # if the second byte of the file is 00, we'll assume that it's a Book 1 file,
            # and Book 2 otherwise.
            try:
                df.open_r()
                initital = df.readuchar()
                second = df.readuchar()
                df.close()
            except (IOError, struct.error), e:
                raise LoadException(str(e))

            if second == 0:
                book = 1
            else:
                book = 2
示例#8
0
    def get_mapinfo(filename=None, map_df=None):
        """
        Given a filename or a passed filehandle, loads the first few bits of
        information from a map file, and will return a tuple containing the
        Eschalon Book the map belongs to, the internal "map name" of the map,
        and a Savefile object pointing to the map.  Will raise a LoadException
        if it encounters errors.

        Book 1 files start with 10 strings
        Book 2 files start with 9 strings, followed by a uchar whose value
          will always be 1 (the "loadhook" var, presumably)
        Book 3 files start with 12 strings, the first of which is a version,
          which so far is always 0.992.

        So, to figure out dynamically what kind of file we're loading:
          1) Read 9 strings, remember the first one
          2) Read the next uchar - if it's 1, then we're editing Book 2
          3) If the first string is "0.992", then we're editing Book 3
          4) Otherwise, we're editing Book 1

        Theoretically, that way this works even if a Book 2 map happens
        to use a mapname of 0.992, in an effort to be cheeky.
        """
        if filename is not None:
            df = Savefile(filename)
        elif map_df is not None:
            df = map_df
        else:
            raise LoadException('One of filename or map_df must be passed in')
        stringlist = []
        try:
            df.open_r()
            for i in range(9):
                stringlist.append(df.readstr())
            nextbyte = df.readuchar()
            df.close()
        except (IOError, struct.error) as e:
            raise LoadException(str(e))

        if nextbyte == 1:
            detected_book = 2
            detected_mapname = stringlist[0]
        # TODO: We're checking for a blank string here to cover up
        # for some invalid data that older versions of the unofficial
        # pre-1.0.0 builds.  By the time 1.1.0 rolls around, or so,
        # we should get rid of that.
        elif stringlist[0] == '0.992' or stringlist[0] == '':
            detected_book = 3
            detected_mapname = stringlist[1]
        else:
            detected_book = 1
            detected_mapname = stringlist[1]

        return detected_book, detected_mapname, df
示例#9
0
    def load(filename, book=None, req_book=None):
        """
        Static method to load a savename file.  This will open the file once and
        read in a bit of data to determine whether this is a Book 1 character file or
        a Book 2 character file, and then call the appropriate constructor and
        return the object.  The individual Book constructors expect to be passed in
        an 
        """
        df = Savefile(filename)

        # First figure out what format to load, if needed
        if book is None:
            try:
                df.open_r()
                name = df.readstr().decode('UTF-8')
                date = df.readstr().decode('UTF-8')
                time = df.readstr().decode('UTF-8')
                map_or_version = df.readstr().decode('UTF-8')
                df.close()
            except (IOError, struct.error) as e:
                LOG.error("Failed to load book", exc_info=True)
                raise LoadException(e) from e

            if map_or_version.startswith(b'book3'):
                book = 3
            elif map_or_version in B1Constants.maps:
                book = 1
            else:
                book = 2

        # See if we're required to conform to a specific book
        if (req_book is not None and book != req_book):
            raise LoadException(
                'This utility can only load Book %d Character files; this file is from Book %d' % (req_book, book))

        # Now actually return the object
        if book == 1:
            c.switch_to_book(1)
            return B1Savename(df)
        elif book == 2:
            c.switch_to_book(2)
            return B2Savename(df)
        else:
            c.switch_to_book(3)
            return B3Savename(df)
示例#10
0
    def get_mapinfo(filename=None, map_df=None):
        """
        Given a filename or a passed filehandle, loads the first few bits of
        information from a map file, and will return a tuple containing the
        Eschalon Book the map belongs to, the internal "map name" of the map,
        and a Savefile object pointing to the map.  Will raise a LoadException
        if it encounters errors.

        Book 1 files start with 10 strings
        Book 2 files start with 9 strings, followed by a uchar whose value
          will always be 1 (the "loadhook" var, presumably)
        Book 3 files start with 12 strings, the first of which is a version,
          which so far is always 0.992.
        
        So, to figure out dynamically what kind of file we're loading:
          1) Read 9 strings, remember the first one
          2) Read the next uchar - if it's 1, then we're editing Book 2
          3) If the first string is "0.992", then we're editing Book 3
          4) Otherwise, we're editing Book 1
        
        Theoretically, that way this works even if a Book 2 map happens
        to use a mapname of 0.992, in an effort to be cheeky.
        """
        if filename is not None:
            df = Savefile(filename)
        elif map_df is not None:
            df = map_df
        else:
            raise LoadException('One of filename or map_df must be passed in')
        stringlist = []
        try:
            df.open_r()
            for i in range(9):
                stringlist.append(df.readstr())
            nextbyte = df.readuchar()
            df.close()
        except (IOError, struct.error), e:
            raise LoadException(str(e))
示例#11
0
    def get_mapinfo(filename=None, map_df=None):
        """
        Given a filename or a passed filehandle, loads the first few bits of
        information from a map file, and will return a tuple containing the
        Eschalon Book the map belongs to, the internal "map name" of the map,
        and a Savefile object pointing to the map.  Will raise a LoadException
        if it encounters errors.

        Book 1 files start with 10 strings
        Book 2 files start with 9 strings, followed by a uchar whose value
          will always be 1 (the "loadhook" var, presumably)
        Book 3 files start with 12 strings, the first of which is a version,
          which so far is always 0.992.
        
        So, to figure out dynamically what kind of file we're loading:
          1) Read 9 strings, remember the first one
          2) Read the next uchar - if it's 1, then we're editing Book 2
          3) If the first string is "0.992", then we're editing Book 3
          4) Otherwise, we're editing Book 1
        
        Theoretically, that way this works even if a Book 2 map happens
        to use a mapname of 0.992, in an effort to be cheeky.
        """
        if filename is not None:
            df = Savefile(filename)
        elif map_df is not None:
            df = map_df
        else:
            raise LoadException('One of filename or map_df must be passed in')
        stringlist = []
        try:
            df.open_r()
            for i in range(9):
                stringlist.append(df.readstr())
            nextbyte = df.readuchar()
            df.close()
        except (IOError, struct.error), e:
            raise LoadException(str(e))
示例#12
0
class B1Gfx(Gfx):
    """
    Grphics structure for Book 1
    """

    book = 1
    wall_types: Dict[Any, Any] = {}
    wall_gfx_group: Dict[Any, Any] = {}
    tilebuf_mult = 1
    item_dim = 42
    item_cols = 10
    item_rows = 24
    tile_width = 52
    tile_height = 26
    floor_cols = 6
    floor_rows = 32
    decal_cols = 6
    decal_rows = 32
    obj_a_width = 52
    obj_a_height = 52
    obj_a_cols = 6
    obj_a_rows = 16
    obj_a_offset = 1
    obj_b_width = 52
    obj_b_height = 78
    obj_b_cols = 6
    obj_b_rows = 10
    obj_b_offset = 101
    obj_c_width = 52
    obj_c_height = 78
    obj_c_cols = 6
    obj_c_rows = 10
    obj_c_offset = 161
    obj_d_width = 52
    obj_d_height = 130
    obj_d_cols = 5
    obj_d_rows = 1
    obj_d_offset = 251
    walldecal_cols = 6
    walldecal_rows = 10

    GFX_SET_A = 1
    GFX_SET_B = 2
    GFX_SET_C = 3
    GFX_SET_TREE = 4

    def __init__(self, datadir, eschalondata):

        # Wall graphic groupings
        for i in range(101):
            self.wall_gfx_group[i] = self.GFX_SET_A
        for i in range(101, 161):
            self.wall_gfx_group[i] = self.GFX_SET_B
        for i in range(161, 251):
            self.wall_gfx_group[i] = self.GFX_SET_C
        for i in range(251, 256):
            self.wall_gfx_group[i] = self.GFX_SET_TREE

        # Wall object types
        for i in (list(range(127)) + list(range(132, 142)) + list(range(143, 153)) +
                  list(range(154, 161)) + list(range(214, 251))):
            self.wall_types[i] = self.TYPE_OBJ
        for i in range(161, 214):
            self.wall_types[i] = self.TYPE_WALL
        for i in (list(range(251, 256)) + list(range(127, 132)) + [142, 153]):
            self.wall_types[i] = self.TYPE_TREE

        # Restricted entities (only one direction)
        self.restrict_ents = [50, 55, 58, 66, 67, 71]

        # Book 1 specific vars (just the PAK structure stuff)
        self.unknownh1 = -1
        self.unknownh2 = -1
        self.numfiles = -1
        self.compressed_idx_size = -1
        self.unknowni1 = -1
        self.fileindex = {}
        self.zeroindex = -1

        # Book 1 specific caches
        self.itemcache = None

        # Graphics PAK file
        self.pakloc = os.path.join(eschalondata.gamedir, 'gfx.pak')
        if os.path.isfile(self.pakloc):
            self.df = Savefile(self.pakloc)
        else:
            self.df = None

        # Set our loaded status
        self.loaded = False

        # Finally call the parent constructor
        super(B1Gfx, self).__init__(datadir, eschalondata)

    def readfile(self, filename: str) -> object:
        """ Reads a given filename out of the PAK. """
        if self.loaded:
            filepath = os.path.join(
                self.eschalondata.gamedir, 'packedgraphics', filename)
            if os.path.isfile(filepath):
                return open(filepath, 'rb').read()
            if filename in self.fileindex:
                self.df.open_r()
                self.df.seek(self.zeroindex +
                             self.fileindex[filename].abs_index)
                # On Windows, we need to specify bufsize or memory gets clobbered
                filedata = zlib.decompress(
                    self.df.read(self.fileindex[filename].size_compressed),
                    15,
                    self.fileindex[filename].size_real)
                self.df.close()
                return filedata
            else:
                raise LoadException(
                    'Filename %s not found in archive' % (filename))
        else:
            raise LoadException('PAK Index has not been loaded')

    def initialread(self):
        """ Read in the main file index. """

        df = self.df
        if df is None:
            self.loaded = True
            return

        df.open_r()
        header = df.read(4)
        if (header != b'!PAK'):
            df.close()
            raise LoadException('Invalid PAK header')

        # Initial Values
        self.unknownh1 = df.readshort()
        self.unknownh2 = df.readshort()
        self.numfiles = df.readint()
        self.compressed_idx_size = df.readint()
        self.unknowni1 = df.readint()

        # Now load in the index
        decobj = zlib.decompressobj()
        indexdata = decobj.decompress(df.read())
        self.zeroindex = df.tell() - len(decobj.unused_data)
        for i in range(self.numfiles):
            index = PakIndex(indexdata[:272])
            indexdata = indexdata[272:]
            self.fileindex[index.filename] = index

        # Close and clean up
        df.close()
        self.loaded = True

    def get_item(self, item, size=None, gdk=True):
        if (self.itemcache is None):
            self.itemcache = GfxCache(self.readfile(
                'items_mastersheet.png'), 42, 42, 10)
        return self.itemcache.getimg(item.pictureid + 1, size, gdk)

    def get_floor(self, floornum, size=None, gdk=False):
        if (floornum == 0):
            return None
        if (self.floorcache is None):
            self.floorcache = GfxCache(self.readfile(
                'iso_tileset_base.png'), 52, 26, 6)
        return self.floorcache.getimg(floornum, size, gdk)

    def get_decal(self, decalnum, size=None, gdk=False):
        if (decalnum == 0):
            return None
        if (self.decalcache is None):
            self.decalcache = GfxCache(self.readfile(
                'iso_tileset_base_decals.png'), 52, 26, 6)
        return self.decalcache.getimg(decalnum, size, gdk)

    # Returns a tuple, first item is the surface, second is the extra height to add while drawing
    def get_object(self, objnum, size=None, gdk=False, treeset=0):
        """
        Note that we ignore the treeset flag in book 1
        """
        if (objnum == 0):
            return (None, 0, 0)
        try:
            gfxgroup = self.wall_gfx_group[objnum]
        except KeyError:
            return (None, 0, 0)
        if gfxgroup == self.GFX_SET_A:
            if (self.objcache1 is None):
                self.objcache1 = GfxCache(self.readfile(
                    'iso_tileset_obj_a.png'), 52, 52, 6)
            return (self.objcache1.getimg(objnum, size, gdk), 1, 0)
        elif gfxgroup == self.GFX_SET_B:
            if (self.objcache2 is None):
                self.objcache2 = GfxCache(self.readfile(
                    'iso_tileset_obj_b.png'), 52, 78, 6)
            return (self.objcache2.getimg(objnum - 100, size, gdk), 2, 0)
        elif gfxgroup == self.GFX_SET_C:
            if (self.objcache3 is None):
                self.objcache3 = GfxCache(self.readfile(
                    'iso_tileset_obj_c.png'), 52, 78, 6)
            return (self.objcache3.getimg(objnum - 160, size, gdk), 2, 0)
        else:
            if (self.objcache4 is None):
                self.objcache4 = GfxCache(
                    self.readfile('iso_trees.png'), 52, 130, 5)
            if (objnum in self.treemap):
                return (self.objcache4.getimg(self.treemap[objnum], size, gdk), 4, 0)
            else:
                return (None, 4, 0)

    def get_object_decal(self, decalnum, size=None, gdk=False):
        if (decalnum == 0):
            return None
        if (self.objdecalcache is None):
            self.objdecalcache = GfxCache(self.readfile(
                'iso_tileset_obj_decals.png'), 52, 78, 6)
        return self.objdecalcache.getimg(decalnum, size, gdk)

    def get_flame(self, size=None, gdk=False):
        """
        Grabs the flame graphic, so it's clear when viewing maps.  I provide my own
        image here instead of using the game's because the file bundled with the game
        doesn't have transparency information, and I don't feel like doing a conversion.
        """
        if (self.flamecache is None):
            with open(os.path.join(self.datadir, 'torch_single.png'), 'rb') as df:
                flamedata = df.read()
            self.flamecache = B1GfxEntCache(flamedata, 1, 1)
        if (size is None):
            size = self.tile_width
        return self.flamecache.getimg(1, int(size * self.flamecache.size_scale), gdk)

    def get_entity(self, entnum, direction, size=None, gdk=False):
        entity = self.eschalondata.get_entity(entnum)
        if not entity:
            return None
        entnum = entity.gfxfile
        if (entnum not in self.entcache):
            filename = 'mo%d.png' % (entnum)
            if (entnum in self.restrict_ents):
                self.entcache[entnum] = B1GfxEntCache(
                    self.readfile(filename), 2, 1)
            else:
                self.entcache[entnum] = B1GfxEntCache(self.readfile(filename))
        cache = self.entcache[entnum]
        if (size is None):
            size = self.tile_width
        return cache.getimg(direction, int(size * cache.size_scale), gdk)

    def get_avatar(self, avatarnum):
        if avatarnum < 0 or avatarnum > 7:
            return None
        if avatarnum not in self.avatarcache:
            if avatarnum == 7:
                if os.path.exists(os.path.join(self.eschalondata.gamedir, 'mypic.png')):
                    self.avatarcache[avatarnum] = GdkPixbuf.Pixbuf.new_from_file(
                        os.path.join(self.eschalondata.gamedir, 'mypic.png'))
                else:
                    return None
            else:
                self.avatarcache[avatarnum] = GfxCache(
                    self.readfile('{}.png'.format(avatarnum)), 60, 60, 1).pixbuf
        return self.avatarcache[avatarnum]
示例#13
0
    """ A class to hold information on an individual file in the pak. """

    def __init__(self, data):
        (self.size_compressed,
                self.abs_index,
                self.size_real,
                self.unknowni1) = unpack('<IIII', data[:16])
        self.filename = data[16:]
        self.filename = self.filename[:self.filename.index("\x00")]

df = Savefile(sys.argv[1])

df.open_r()
header = df.read(4)
if (header != '!PAK'):
  df.close()
  raise LoadException('Invalid PAK header')

# Initial Values
unknownh1 = df.readshort()
unknownh2 = df.readshort()
numfiles = df.readint()
compressed_idx_size = df.readint()
unknowni1 = df.readint()

# Now load in the index
decobj = zlib.decompressobj()
indexdata = decobj.decompress(df.read())
zeroindex = df.tell() - len(decobj.unused_data)
decobj = None
fileindex = {}
示例#14
0
class B1Gfx(Gfx):
    """
    Grphics structure for Book 1
    """

    book = 1
    wall_types = {}
    wall_gfx_group = {}
    tilebuf_mult = 1
    item_dim = 42
    item_cols = 10
    item_rows = 24
    tile_width = 52
    tile_height = 26
    floor_cols = 6
    floor_rows = 32
    decal_cols = 6
    decal_rows = 32
    obj_a_width = 52
    obj_a_height = 52
    obj_a_cols = 6
    obj_a_rows = 16
    obj_a_offset = 1
    obj_b_width = 52
    obj_b_height = 78
    obj_b_cols = 6
    obj_b_rows = 10
    obj_b_offset = 101
    obj_c_width = 52
    obj_c_height = 78
    obj_c_cols = 6
    obj_c_rows = 10
    obj_c_offset = 161
    obj_d_width = 52
    obj_d_height = 130
    obj_d_cols = 5
    obj_d_rows = 1
    obj_d_offset = 251
    walldecal_cols = 6
    walldecal_rows = 10

    GFX_SET_A = 1
    GFX_SET_B = 2
    GFX_SET_C = 3
    GFX_SET_TREE = 4

    def __init__(self, datadir, eschalondata):

        # Wall graphic groupings
        for i in range(101):
            self.wall_gfx_group[i] = self.GFX_SET_A
        for i in range(101, 161):
            self.wall_gfx_group[i] = self.GFX_SET_B
        for i in range(161, 251):
            self.wall_gfx_group[i] = self.GFX_SET_C
        for i in range(251, 256):
            self.wall_gfx_group[i] = self.GFX_SET_TREE

        # Wall object types
        for i in (range(127) + range(132, 142) + range(143, 153) +
                range(154, 161) + range(214, 251)):
            self.wall_types[i] = self.TYPE_OBJ
        for i in range(161, 214):
            self.wall_types[i] = self.TYPE_WALL
        for i in (range(251, 256) + range(127, 132) + [142, 153]):
            self.wall_types[i] = self.TYPE_TREE

        # Restricted entities (only one direction)
        self.restrict_ents = [50, 55, 58, 66, 67, 71]

        # Book 1 specific vars (just the PAK structure stuff)
        self.unknownh1 = -1
        self.unknownh2 = -1
        self.numfiles = -1
        self.compressed_idx_size = -1
        self.unknowni1 = -1
        self.fileindex = {}
        self.zeroindex = -1

        # Book 1 specific caches
        self.itemcache = None

        # Graphics PAK file
        self.pakloc = os.path.join(eschalondata.gamedir, 'gfx.pak')
        if os.path.isfile(self.pakloc):
            self.df = Savefile(self.pakloc)
        else:
            self.df = None

        # Set our loaded status
        self.loaded = False

        # Finally call the parent constructor
        super(B1Gfx, self).__init__(datadir, eschalondata)

    def readfile(self, filename):
        """ Reads a given filename out of the PAK. """
        if self.loaded:
            filepath = os.path.join(self.eschalondata.gamedir, 'packedgraphics', filename)
            if os.path.isfile(filepath):
                return open(filepath, 'rb').read()
            if (filename in self.fileindex):
                self.df.open_r()
                self.df.seek(self.zeroindex + self.fileindex[filename].abs_index)
                # On Windows, we need to specify bufsize or memory gets clobbered
                filedata = zlib.decompress(
                    self.df.read(self.fileindex[filename].size_compressed),
                    15,
                    self.fileindex[filename].size_real)
                self.df.close()
                return filedata
            else:
                raise LoadException('Filename %s not found in archive' % (filename))
        else:
            raise LoadException('PAK Index has not been loaded')

    def initialread(self):
        """ Read in the main file index. """

        df = self.df
        if df is None:
            self.loaded = True
            return

        df.open_r()
        header = df.read(4)
        if (header != '!PAK'):
            df.close()
            raise LoadException('Invalid PAK header')
        
        # Initial Values
        self.unknownh1 = df.readshort()
        self.unknownh2 = df.readshort()
        self.numfiles = df.readint()
        self.compressed_idx_size = df.readint()
        self.unknowni1 = df.readint()

        # Now load in the index
        decobj = zlib.decompressobj()
        indexdata = decobj.decompress(df.read())
        self.zeroindex = df.tell() - len(decobj.unused_data)
        decobj = None
        for i in range(self.numfiles):
            index = PakIndex(indexdata[:272])
            indexdata = indexdata[272:]
            self.fileindex[index.filename] = index

        # Close and clean up
        df.close()
        self.loaded = True

    def get_item(self, item, size=None, gdk=True):
        if (self.itemcache is None):
            self.itemcache = GfxCache(self.readfile('items_mastersheet.png'), 42, 42, 10)
        return self.itemcache.getimg(item.pictureid+1, size, gdk)

    def get_floor(self, floornum, size=None, gdk=False):
        if (floornum == 0):
            return None
        if (self.floorcache is None):
            self.floorcache = GfxCache(self.readfile('iso_tileset_base.png'), 52, 26, 6)
        return self.floorcache.getimg(floornum, size, gdk)

    def get_decal(self, decalnum, size=None, gdk=False):
        if (decalnum == 0):
            return None
        if (self.decalcache is None):
            self.decalcache = GfxCache(self.readfile('iso_tileset_base_decals.png'), 52, 26, 6)
        return self.decalcache.getimg(decalnum, size, gdk)

    # Returns a tuple, first item is the surface, second is the extra height to add while drawing
    def get_object(self, objnum, size=None, gdk=False, treeset=0):
        """
        Note that we ignore the treeset flag in book 1
        """
        if (objnum == 0):
            return (None, 0, 0)
        try:
            gfxgroup = self.wall_gfx_group[objnum]
        except KeyError:
            return (None, 0, 0)
        if gfxgroup == self.GFX_SET_A:
            if (self.objcache1 is None):
                self.objcache1 = GfxCache(self.readfile('iso_tileset_obj_a.png'), 52, 52, 6)
            return (self.objcache1.getimg(objnum, size, gdk), 1, 0)
        elif gfxgroup == self.GFX_SET_B:
            if (self.objcache2 is None):
                self.objcache2 = GfxCache(self.readfile('iso_tileset_obj_b.png'), 52, 78, 6)
            return (self.objcache2.getimg(objnum-100, size, gdk), 2, 0)
        elif gfxgroup == self.GFX_SET_C:
            if (self.objcache3 is None):
                self.objcache3 = GfxCache(self.readfile('iso_tileset_obj_c.png'), 52, 78, 6)
            return (self.objcache3.getimg(objnum-160, size, gdk), 2, 0)
        else:
            if (self.objcache4 is None):
                self.objcache4 = GfxCache(self.readfile('iso_trees.png'), 52, 130, 5)
            if (objnum in self.treemap):
                return (self.objcache4.getimg(self.treemap[objnum], size, gdk), 4, 0)
            else:
                return (None, 4, 0)

    def get_object_decal(self, decalnum, size=None, gdk=False):
        if (decalnum == 0):
            return None
        if (self.objdecalcache is None):
            self.objdecalcache = GfxCache(self.readfile('iso_tileset_obj_decals.png'), 52, 78, 6)
        return self.objdecalcache.getimg(decalnum, size, gdk)

    def get_flame(self, size=None, gdk=False):
        """
        Grabs the flame graphic, so it's clear when viewing maps.  I provide my own
        image here instead of using the game's because the file bundled with the game
        doesn't have transparency information, and I don't feel like doing a conversion.
        """
        if (self.flamecache is None):
            df = open(os.path.join(self.datadir, 'torch_single.png'), 'rb')
            flamedata = df.read()
            df.close()
            self.flamecache = B1GfxEntCache(flamedata, 1, 1)
        if (size is None):
            size = self.tile_width
        return self.flamecache.getimg(1, int(size*self.flamecache.size_scale), gdk)

    def get_entity(self, entnum, direction, size=None, gdk=False):
        entity = self.eschalondata.get_entity(entnum)
        if not entity:
            return None
        entnum = entity.gfxfile
        if (entnum not in self.entcache):
            filename = 'mo%d.png' % (entnum)
            if (entnum in self.restrict_ents):
                self.entcache[entnum] = B1GfxEntCache(self.readfile(filename), 2, 1)
            else:
                self.entcache[entnum] = B1GfxEntCache(self.readfile(filename))
        cache = self.entcache[entnum]
        if (size is None):
            size = self.tile_width
        return cache.getimg(direction, int(size*cache.size_scale), gdk)

    def get_avatar(self, avatarnum):
        if (avatarnum < 0 or avatarnum > 7):
            return None
        if (avatarnum not in self.avatarcache):
            if (avatarnum == 7):
                if (os.path.exists(os.path.join(self.eschalondata.gamedir, 'mypic.png'))):
                    self.avatarcache[avatarnum] = gtk.gdk.pixbuf_new_from_file(os.path.join(self.eschalondata.gamedir, 'mypic.png'))
                else:
                    return None
            else:
                self.avatarcache[avatarnum] = GfxCache(self.readfile('%d.png' % (avatarnum)), 60, 60, 1).pixbuf
        return self.avatarcache[avatarnum]