示例#1
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()
示例#2
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
示例#3
0
    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
示例#4
0
    def read(self):
        """ Read in the whole save file from a file descriptor. """

        try:
            # Open the file
            self.df.open_r()

            # This is the most guesswork of the three books since it's
            # farthest from the B3 format.  It's also harder to tell where
            # sections begin and end since everything's a 4-byte int.  Not
            # well checked, probably has bugs.
            self.savename = self.df.readstr()
            self.savedate = self.df.readstr()
            self.savetime = self.df.readstr()
            self.mapname = self.df.readstr()
            self.totalsecs = self.df.readint()
            self.totalturns = self.df.readint()
            self.totaldays = self.df.readint()
            self.coloration = self.df.readint()
            for i in range(10):
                self.options.append(self.df.readint())
            for i in range(255):
                self.narratives.append(self.df.readint())
            for i in range(150):
                self.quests.append(self.df.readint())

            # Close the file
            self.df.close()

        except (IOError, struct.error), e:
            raise LoadException(str(e))
示例#5
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
示例#6
0
    def read(self):
        """ Read in the whole save file from a file descriptor. """

        try:
            # Open the file
            self.df.open_r()

            # Based on BW's spec from
            # http://www.basiliskgames.com/forums/viewtopic.php?f=33&t=9328&start=60#p58108
            #
            # We evidently screwed something up, though, since we're off by
            # 24 bytes at the end. Almost certainly has bugs
            self.savename = self.df.readstr()
            self.savedate = self.df.readstr()
            self.savetime = self.df.readstr()
            self.savever = self.df.readstr()
            self.mapname = self.df.readstr()
            self.totalsecs = self.df.readint()
            self.totalturns = self.df.readint()
            self.totaldays = self.df.readint()
            self.coloration = self.df.readint()
            for i in range(12):
                self.options.append(self.df.readuchar())
            for i in range(255):
                self.narratives.append(self.df.readuchar())
            for i in range(300):
                self.quests.append(self.df.readint())
            for i in range(100):
                self.npcs.append(self.df.readuchar())
            self.quicktravel = self.df.readint()
            self.combatmode = self.df.readint()
            self.rules = self.df.readint()
            self.seed = self.df.readint()
            self.challenges = self.df.readint()
            for i in range(8):
                self.stats.append(self.df.readuchar())
            self.weather_type = self.df.readint()
            self.weather_duration = self.df.readint()
            self.weather_start = self.df.readint()
            self.weather_next = self.df.readint()
            self.cloud_darkener = self.df.readint()
            self.cloud_alpha = self.df.readfloat()
            self.weather_number = self.df.readint()
            self.weather_active = self.df.readint()
            # This is bogus - I'm sure we've lost 24 bytes somewhere
            # along the way
            for i in range(6):
                self.unknowns.append(self.df.readint())
            try:
                self.modpath = self.df.readstr()
            except LoadException:
                self.modpath = ''

            # Close the file
            self.df.close()

        except (IOError, struct.error), e:
            raise LoadException(str(e))
示例#7
0
    def __init__(self, directory, load_all=False, book=None):
        """ Empty object. """
        self.directory = directory

        # Make sure we really are a directory
        if not os.path.isdir(directory):
            raise LoadException('%s is not a directory' % (directory))

        # Store our modification time
        self.timestamp_epoch = os.path.getmtime(directory)
        self.timestamp = time.strftime('%a %b %d, %Y, %I:%M %p',
                                       time.gmtime(self.timestamp_epoch))

        # Find the save name
        self.savename_loc = os.path.join(directory, 'savename')
        if not os.path.exists(self.savename_loc):
            raise LoadException('"savename" file not found in %s' %
                                (directory))
        self.savenameobj = Savename.load(self.savename_loc)
        self.savenameobj.read()
        self.savename = self.savenameobj.savename

        # Set up our charname values
        self.char_loc = os.path.join(directory, 'char')
        self.charname = 'n/a'
        self.char_loaded = False

        # Set up our map list
        self.maps = []
        self.maps_loaded = False

        # Load all information if asked
        if load_all:
            self.load_maps()
            if book is None:
                try:
                    self.load_charname()
                except LoadException:
                    # We'll just do without the charname in this case
                    pass
            else:
                # We'll allow the exception if we're expecting a particular
                # book
                self.load_charname(book)
示例#8
0
 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')
示例#9
0
 def new(book, datadir, eschalondata):
     """
     Returns a B1Gfx or B2Gfx object, depending on the book that we're working with
     """
     if book == 1:
         return B1Gfx(datadir, eschalondata)
     elif book == 2:
         return B2Gfx(datadir, eschalondata)
     elif book == 3:
         return B3Gfx(datadir, eschalondata)
     else:
         raise LoadException('Book number must be 1, 2, or 3 (passed %d)' % (book))
示例#10
0
    def load(filename, 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 which Eschalon game the mapfile
        comes from, and calls the appropriate constructor to return the object.
        If req_book is passed in, it will raise a LoadException if the detected
        Book number doesn't match.  This will also raise a LoadException if
        it's unable to determine the version (generally due to being passed
        something that's not a map file).

        Note that this method does not actually read in the entire map file.
        It does "preload" the map name, however, so that it can be easily
        referenced in lists.  Use .read() on the resulting map object to actually
        read in the map data.
        """

        # Get some information about the filename
        (detected_book, detected_mapname, df) = Map.get_mapinfo(filename)

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

        # Now actually return the object
        if detected_book == 1:
            c.switch_to_book(1)
            return B1Map(df)
        elif detected_book == 2:
            c.switch_to_book(2)
            return B2Map(df)
        elif detected_book == 3:
            c.switch_to_book(3)
            return B3Map(df)
        else:
            raise LoadException(
                'Unknown book version found for "%s"; perhaps it is not an Eschalon map file'
                % (filename))
示例#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
    def read(self):
        """ Read in the whole save file from a file descriptor. """

        try:
            # Open the file
            self.df.open_r()

            # Based on BW's B3 spec and looking at the file manually.
            # Oddly, lines up better than the B3 format.  Not well checked,
            # though, and probably has bugs
            self.savename = self.df.readstr()
            self.savedate = self.df.readstr()
            self.savetime = self.df.readstr()
            self.mapname = self.df.readstr()
            self.totalsecs = self.df.readint()
            self.totalturns = self.df.readint()
            self.totaldays = self.df.readint()
            self.coloration = self.df.readint()
            for i in range(12):
                self.options.append(self.df.readuchar())
            for i in range(255):
                self.narratives.append(self.df.readuchar())
            for i in range(200):
                self.quests.append(self.df.readint())
            for i in range(69):
                self.npcs.append(self.df.readuchar())
            self.quicktravel = self.df.readint()
            self.combatmode = self.df.readint()
            self.rules = self.df.readint()
            self.seed = self.df.readint()
            self.challenges = self.df.readint()
            for i in range(8):
                self.stats.append(self.df.readuchar())
            self.weather_type = self.df.readint()
            self.weather_duration = self.df.readint()
            self.weather_start = self.df.readint()
            self.weather_next = self.df.readint()
            self.cloud_darkener = self.df.readint()
            self.cloud_alpha = self.df.readfloat()
            self.weather_number = self.df.readint()
            self.weather_active = self.df.readint()
            for i in range(6):
                self.unknowns.append(self.df.readint())

            # Close the file
            self.df.close()

        except (IOError, struct.error), e:
            raise LoadException(str(e))
示例#13
0
    def read(self, df):
        """ Read our data. """

        df.open_r()
        self.day_stocked = df.readint()
        item_count = df.readint()
        self.gold = df.readint()

        for i in range(item_count):
            item = Item.new(c.book)
            item.read(df)
            self.items.append(item)

        extradata = df.read()
        if len(extradata) > 0:
            raise LoadException('Extra data at end of merchant file')

        df.close()
示例#14
0
 def new(filename, book, map_df=None, ent_df=None):
     """
     Sets up a new, blank Map object with the given book.  Will raise a
     LoadException if we're passed a book we don't know about.  Optionally
     pass in a datafile object to load our data from.
     """
     if map_df is None:
         df = Savefile(filename)
     else:
         df = map_df
     if book == 1:
         c.switch_to_book(1)
         return B1Map(df, ent_df)
     elif book == 2:
         c.switch_to_book(2)
         return B2Map(df, ent_df)
     elif book == 3:
         c.switch_to_book(3)
         return B3Map(df, ent_df)
     else:
         raise LoadException('Unknown book version specified: %d' % (book))
示例#15
0
class B2Map(Map):
    """
    Book 2 Map definitions
    """

    book = 2

    def __init__(self, df, ent_df=None):

        # Book 2 specific vars
        self.entrancescript = ''
        self.returnscript = ''
        self.exitscript = ''
        self.random_sound1 = ''
        self.loadhook = 1
        self.unusedc1 = 1
        self.random_entity_1 = 0
        self.random_entity_2 = 0
        self.map_flags = 0
        self.start_tile = 0
        self.tree_set = 0
        self.last_turn = 0
        self.unusedstr1 = ''
        self.unusedstr2 = ''
        self.unusedstr3 = ''

        # Now the base attributes
        super(B2Map, self).__init__(df, ent_df)

    def read(self):
        """ Read in the whole map from a file descriptor. """

        try:

            # Open the file
            self.df.open_r()

            # Start processing
            self.mapname = self.df.readstr()
            self.entrancescript = self.df.readstr()
            self.returnscript = self.df.readstr()
            self.exitscript = self.df.readstr()
            self.skybox = self.df.readstr()
            self.music1 = self.df.readstr()
            self.music2 = self.df.readstr()
            self.atmos_sound_day = self.df.readstr()
            self.random_sound1 = self.df.readstr()
            self.loadhook = self.df.readuchar()
            self.unusedc1 = self.df.readuchar()
            self.random_entity_1 = self.df.readuchar()
            self.random_entity_2 = self.df.readuchar()
            self.color_r = self.df.readuchar()
            self.color_g = self.df.readuchar()
            self.color_b = self.df.readuchar()
            self.color_a = self.df.readuchar()
            self.parallax_x = self.df.readint()
            self.parallax_y = self.df.readint()
            self.map_flags = self.df.readint()
            self.start_tile = self.df.readint()
            self.tree_set = self.df.readint()

            self.last_turn = self.df.readint()

            self.unusedstr1 = self.df.readstr()
            self.unusedstr2 = self.df.readstr()
            self.unusedstr3 = self.df.readstr()

            # Tiles
            self.set_tile_savegame()
            for i in range(200 * 100):
                self.addtile()

            # Tilecontents...  Just keep going until EOF
            try:
                while (self.addtilecontent()):
                    pass
            except FirstItemLoadException, e:
                pass

            # Entities...  Just keep going until EOF (note that this is in a separate file)
            # Also note that we have to support situations where there is no entity file
            if (self.df_ent.exists()):
                self.df_ent.open_r()
                try:
                    while (self.addentity()):
                        pass
                except FirstItemLoadException, e:
                    pass
                self.df_ent.close()

            # If there's extra data at the end, we likely don't have
            # a valid char file
            self.extradata = self.df.read()
            if (len(self.extradata) > 0):
                raise LoadException('Extra data at end of file')

            # Close the file
            self.df.close()
示例#16
0
class Savename(object):
    book = None

    def __init__(self, df):
        """ A fresh object. """

        self.savename = ''
        self.savedate = ''
        self.savetime = ''
        self.mapname = ''
        self.totalsecs = 0
        self.totalturns = 0
        self.totaldays = 0
        self.coloration = 0
        self.narratives = []  # Has the player seen this narrative? 0=no, 1=yes
        self.quests = []
        self.npcs = []  # Has the player talked to this NPC? 0=no, 1=yes
        self.quicktravel = 0
        self.options = []  # volume controls, tactical grid, etc.
        self.unknowns = []
        self.df = df

    def replicate(self):
        newsn = Savename.load(self.df.filename, self.book)

        if self.book == 1:
            newsn = B1Savename(Savefile(self.df.filename))
        elif self.book == 2:
            newsn = B2Savename(Savefile(self.df.filename))
        elif self.book == 3:
            newsn = B3Savename(Savefile(self.df.filename))

        # Single vals (no need to do actual replication)
        newsn.savename = self.savename
        newsn.savedate = self.savedate
        newsn.savetime = self.savetime
        newsn.mapname = self.mapname
        newsn.totalsecs = self.totalsecs
        newsn.totalturns = self.totalturns
        newsn.totaldays = self.totaldays
        newsn.coloration = self.coloration
        newsn.quicktravel = self.quicktravel

        # Lists that need copying
        for val in self.options:
            newsn.options.append(val)
        for val in self.narratives:
            newsn.narratives.append(val)
        for val in self.quests:
            newsn.quests.append(val)
        for val in self.npcs:
            newsn.npcs.append(val)

        # Call out to the subclass replication function
        self._sub_replicate(newsn)

        # Now return our duplicated object
        return newsn

    def _sub_replicate(self, newsn):
        """
        Just a stub function for superclasses to override, to replicate any
        superclass-specific data
        """
        pass

    def write(self):
        raise NotImplementedError(
            'Writing savenames is not currently supported')

    @staticmethod
    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

        # 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)
示例#17
0
    def read(self):
        """ Read in the whole save file from a file descriptor. """

        try:

            # Open the file
            self.df.open_r()

            # Start processing
            self.unknown.initzero = self.df.readint()

            # Character info
            self.name = self.df.readstr()
            self.unknown.charstring = self.df.readstr()
            self.origin = self.df.readstr()
            self.axiom = self.df.readstr()
            self.classname = self.df.readstr()
            self.unknown.charone = self.df.readint()
            self.strength = self.df.readint()
            self.dexterity = self.df.readint()
            self.endurance = self.df.readint()
            self.speed = self.df.readint()
            self.intelligence = self.df.readint()
            self.wisdom = self.df.readint()
            self.perception = self.df.readint()
            self.concentration = self.df.readint()

            # Skills
            for key in c.skilltable.keys():
                self.addskill(key, self.df.readint())

            # More stats
            self.maxhp = self.df.readint()
            self.maxmana = self.df.readint()
            self.curhp = self.df.readint()
            self.curmana = self.df.readint()
            self.experience = self.df.readint()
            self.level = self.df.readint()
            self.gold = self.df.readint()
            self.extra_att_points = self.df.readint()
            self.extra_skill_points = self.df.readint()

            # Character statuses
            for i in range(26):
                self.statuses.append(self.df.readint())
                self.unknown.sparseiblock.append(self.df.readint())

            # More Unknowns
            for i in range(17):
                self.unknown.iblock1.append(self.df.readint())
            for i in range(5):
                self.unknown.ssiblocks1.append(self.df.readstr())
                self.unknown.ssiblocks2.append(self.df.readstr())
                self.unknown.ssiblocki.append(self.df.readint())
            self.unknown.extstr1 = self.df.readstr()
            self.unknown.extstr2 = self.df.readstr()

            # Torches
            self.torches = self.df.readint()
            self.torchused = self.df.readint()

            # Further unknown
            self.unknown.anotherzero = self.df.readint()

            # Most of the spells (minus the last four Elemental)
            for i in range(35):
                self.addspell()

            # Readied Spells
            for i in range(10):
                self.addreadyslot(self.df.readstr(), self.df.readint())

            # Position/orientation
            self.orientation = self.df.readint()
            self.xpos = self.df.readint()
            self.ypos = self.df.readint()
            
            # These have *something* to do with your avatar, or effects that your
            # avatar has.  For instance, my avatar ordinarily looks like this:
            #    00 00 00 40    - 1073741824
            #    3F 08 00 00    - 2111
            #    00 0A 00 00    - 2560
            #    00 14 00 00    - 5120
            # When I have gravedigger's flame on, though, the GUI effect is described:
            #    04 1F 85 6B    - 1803886340
            #    3F F0 00 00    - 61503
            #    00 78 00 00    - 30720
            #    00 3C 00 00    - 15360
            # Torch on:
            #    02 CD CC 4C    - 1288490242
            #    3F A0 00 00    - 41023
            #    00 96 00 00    - 38400
            #    00 7D 00 00    - 32000
            # Gravedigger's + Torch on:
            #    06 1F 85 6B    - 1803886342
            #    3F F0 00 00    - 61503
            #    00 96 00 00    - 38400
            #    00 7D 00 00    - 32000
            # Invisible/Chameleon doesn't seem to apply here though.  Maybe just lighting fx?
            # Also, these certainly could be Not Actually ints; perhaps they're something else.
            for i in range(4):
                self.fxblock.append(self.df.readint())

            # An unknown, seems to be a multiple of 256
            self.unknown.anotherint = self.df.readint()

            # Character profile pic (multiple of 256, for some reason)
            self.picid = self.df.readint()

            # Disease flag
            self.disease = self.df.readint()

            # More Unknowns.  Apparently there's one 2-byte integer in here, too.
            self.unknown.shortval = self.df.readshort()
            self.unknown.emptystr = self.df.readstr()
            for i in range(21):
                self.unknown.iblock2.append(self.df.readint())
            self.unknown.preinvs1 = self.df.readstr()
            self.unknown.preinvs2 = self.df.readstr()
            self.unknown.preinvzero1 = self.df.readint()
            self.unknown.preinvzero2 = self.df.readint()

            # Inventory
            for i in range(self.inv_rows * self.inv_cols):
                self.additem()

            # Equipped
            self.quiver.read(self.df);
            self.helm.read(self.df);
            self.cloak.read(self.df);
            self.amulet.read(self.df);
            self.torso.read(self.df);
            self.weap_prim.read(self.df);
            self.belt.read(self.df);
            self.gauntlet.read(self.df);
            self.legs.read(self.df);
            self.ring1.read(self.df);
            self.ring2.read(self.df);
            self.shield.read(self.df);
            self.feet.read(self.df);
            self.weap_alt.read(self.df);

            # Readied items
            for i in range(8):
                self.readyitems[i].read(self.df)

            # For some reason, the last of the spells here.
            for i in range(4):
                try:
                    self.addspell()
                except struct.error, e:
                    # Apparently some versions don't always write these out,
                    # hack in some fake values if that's the case.
                    for j in range(4-i):
                        self.spells.append(0)
                    break

            # If there's extra data at the end, we likely don't have
            # a valid char file
            self.unknown.extradata = self.df.read()
            if (len(self.unknown.extradata)>0):
                raise LoadException('Extra data at end of file')

            # Close the file
            self.df.close()
示例#18
0
    def read(self):
        """ Read in the whole save file from a file descriptor. """

        try:

            # Open the file
            self.df.open_r()

            # Start processing
            self.unknown.initzero = self.df.readuchar()

            # Character info
            self.name = self.df.readstr()
            self.gender = self.df.readuchar()
            self.origin = self.df.readuchar()
            self.axiom = self.df.readuchar()
            self.classname = self.df.readuchar()
            self.unknown.version = self.df.readuchar()
            if self.unknown.version == 1:
                raise LoadException('This savegame was probably saved in v1.02 of Book 2, only 1.03 and higher is supported')
            self.strength = self.df.readuchar()
            self.dexterity = self.df.readuchar()
            self.endurance = self.df.readuchar()
            self.speed = self.df.readuchar()
            self.intelligence = self.df.readuchar()
            self.wisdom = self.df.readuchar()
            self.perception = self.df.readuchar()
            self.concentration = self.df.readuchar()

            # Skills
            for key in sorted(c.skilltable.keys()):
                self.addskill(key, self.df.readuchar())

            # More stats
            self.extra_att_points = self.df.readuchar()
            self.extra_skill_points = self.df.readuchar()
            self.maxhp = self.df.readint()
            self.maxmana = self.df.readint()
            self.curhp = self.df.readint()
            self.curmana = self.df.readint()
            self.experience = self.df.readint()
            self.level = self.df.readint()
            self.hunger = self.df.readint()
            self.thirst = self.df.readint()

            # FX block
            for i in range(7):
                self.fxblock.append(self.df.readint())

            # Non-permanent Chracter Statuses (will expire automatically)
            for i in range(26):
                self.statuses.append(self.df.readint())
                self.statuses_extra.append(self.df.readint())

            # Portal anchor locations
            for i in range(6):
                portal_anchor = []
                portal_anchor.append(self.df.readint())
                portal_anchor.append(self.df.readstr())
                portal_anchor.append(self.df.readstr())
                self.portal_locs.append(portal_anchor)

            # Unknown
            self.unknown.zero1 = self.df.readuchar()

            # Spells
            for i in range(len(c.spelltable)):
                self.addspell()

            # Currently-readied spell
            self.readied_spell = self.df.readstr()
            self.readied_spell_lvl = self.df.readuchar()

            # Readied Spells
            for i in range(10):
                self.addreadyslot(self.df.readstr(), self.df.readuchar())

            # Alchemy Recipes
            for i in range(25):
                self.addalchemy()

            # Some unknown values (zeroes so far)
            for i in range(14):
                self.unknown.zeros.append(self.df.readint())

            # Position/orientation
            self.orientation = self.df.readuchar()
            self.xpos = self.df.readint()
            self.ypos = self.df.readint()

            # Some unknowns
            for i in range(5):
                self.unknown.strangeblock.append(self.df.readuchar())
            self.unknown.unknowni1 = self.df.readint()
            self.unknown.unknowni2 = self.df.readint()
            self.unknown.unknowni3 = self.df.readint()
            self.unknown.usually_one = self.df.readuchar()

            # Permanent Statuses (bitfield)
            self.permstatuses = self.df.readint()

            # More stats
            self.picid = self.df.readint()
            self.gold = self.df.readint()
            self.torches = self.df.readint()
            self.torchused = self.df.readint()

            # Keyring
            for i in range(20):
                self.keyring.append(self.df.readstr())

            # More unknowns
            self.unknown.unknowns1 = self.df.readshort()
            self.unknown.unknownstr1 = self.df.readstr()
            for i in range(29):
                self.unknown.twentyninezeros.append(self.df.readuchar())
            self.unknown.unknownstr2 = self.df.readstr()
            self.unknown.unknownstr3 = self.df.readstr()
            self.unknown.unknowns2 = self.df.readshort()

            # Inventory
            for i in range(self.inv_rows * self.inv_cols):
                self.additem()

            # Equipped
            self.quiver.read(self.df);
            self.helm.read(self.df);
            self.cloak.read(self.df);
            self.amulet.read(self.df);
            self.torso.read(self.df);
            self.weap_prim.read(self.df);
            self.belt.read(self.df);
            self.gauntlet.read(self.df);
            self.legs.read(self.df);
            self.ring1.read(self.df);
            self.ring2.read(self.df);
            self.shield.read(self.df);
            self.feet.read(self.df);

            # Readied items
            for i in range(10):
                self.readyitems[i].read(self.df)

            # Equipment Slots
            for i in range(13):
                self.equip_slot_1.append(self.df.readstr())
                self.equip_slot_2.append(self.df.readstr())

            # If there's extra data at the end, we likely don't have
            # a valid char file
            self.unknown.extradata = self.df.read()
            if (len(self.unknown.extradata)>0):
                raise LoadException('Extra data at end of file')

            # Close the file
            self.df.close()

        except (IOError, struct.error), e:
            raise LoadException(str(e))
示例#19
0
                        pass
                except FirstItemLoadException, e:
                    pass
                self.df_ent.close()

            # If there's extra data at the end, we likely don't have
            # a valid char file
            self.extradata = self.df.read()
            if (len(self.extradata) > 0):
                raise LoadException('Extra data at end of file')

            # Close the file
            self.df.close()

        except (IOError, struct.error), e:
            raise LoadException(str(e))

    def write(self):
        """ Writes out the map to the file descriptor. """

        # We require a '.map' extension
        self.check_map_extension()

        # Open the file
        self.df.open_w()

        # Start
        self.df.writestr(self.mapid)
        self.df.writestr(self.mapname)
        self.df.writestr(self.music1)
        self.df.writestr(self.music2)
示例#20
0
class B3Map(B2Map):
    """
    Book 3 Map definitions
    """

    book = 3

    def __init__(self, df, ent_df=None):

        # Book 3 specific vars
        self.version = '0.992'
        self.atmos_sound_night = ''
        self.random_sound2 = ''
        self.cloud_offset_x = 0
        self.cloud_offset_y = 0

        # Now the base attributes
        super(B3Map, self).__init__(df, ent_df)

        # Override the parent class - without this B3 maps won't load
        self.loadhook = 2

    def read(self):
        """ Read in the whole map from a file descriptor. """

        try:

            # Open the file
            self.df.open_r()

            # Start processing
            self.version = self.df.readstr()
            self.mapname = self.df.readstr()
            self.entrancescript = self.df.readstr()
            self.returnscript = self.df.readstr()
            self.exitscript = self.df.readstr()
            self.skybox = self.df.readstr()
            self.music1 = self.df.readstr()
            self.music2 = self.df.readstr()
            self.atmos_sound_day = self.df.readstr()
            self.atmos_sound_night = self.df.readstr()
            self.random_sound1 = self.df.readstr()
            self.random_sound2 = self.df.readstr()
            self.loadhook = self.df.readuchar()
            self.unusedc1 = self.df.readuchar()
            self.random_entity_1 = self.df.readuchar()
            self.random_entity_2 = self.df.readuchar()
            self.color_r = self.df.readuchar()
            self.color_g = self.df.readuchar()
            self.color_b = self.df.readuchar()
            self.color_a = self.df.readuchar()
            self.parallax_x = self.df.readint()
            self.parallax_y = self.df.readint()
            self.cloud_offset_x = self.df.readint()
            self.cloud_offset_y = self.df.readint()
            self.map_flags = self.df.readint()
            self.start_tile = self.df.readint()
            self.tree_set = self.df.readint()

            self.last_turn = self.df.readint()

            self.unusedstr1 = self.df.readstr()
            self.unusedstr2 = self.df.readstr()
            self.unusedstr3 = self.df.readstr()

            # Tiles
            self.set_tile_savegame()
            for i in range(200 * 100):
                self.addtile()

            # Tilecontents...  Just keep going until EOF
            try:
                while (self.addtilecontent()):
                    pass
            except FirstItemLoadException, e:
                pass

            # Entities...  Just keep going until EOF (note that this is in a separate file)
            # Also note that we have to support situations where there is no entity file
            if (self.df_ent.exists()):
                self.df_ent.open_r()
                try:
                    while (self.addentity()):
                        pass
                except FirstItemLoadException, e:
                    pass
                self.df_ent.close()

            # If there's extra data at the end, we likely don't have
            # a valid char file
            self.extradata = self.df.read()
            if (len(self.extradata) > 0):
                raise LoadException('Extra data at end of file')

            # Close the file
            self.df.close()

            # This isn't really *proper* but any Book 3 map we load really does need
            # a version of 0.992 and a loadhook of 2.  Override them here, in case we
            # loaded a map which was written by a buggier older version of this
            # utility which might not have set these correctly.
            self.version = '0.992'
            self.loadhook = 2
示例#21
0
class B1Map(Map):
    """
    Book 1 Map definitions
    """

    book = 1

    def __init__(self, df, ent_df=None):

        # Book 1-specific vars
        self.mapid = ''
        self.exit_north = ''
        self.exit_east = ''
        self.exit_south = ''
        self.exit_west = ''
        self.clouds = 0
        self.savegame_1 = 0
        self.savegame_2 = 0
        self.savegame_3 = 0
        self.map_unknownh1 = 0
        self.map_b1_last_xpos = 0
        self.map_b1_last_ypos = 0
        self.map_b1_outsideflag = 0

        # Base class attributes
        super(B1Map, self).__init__(df, ent_df)

    def read(self):
        """ Read in the whole map from a file descriptor. """

        try:

            # Open the file
            self.df.open_r()

            # Start processing
            self.mapid = self.df.readstr()
            self.mapname = self.df.readstr()
            self.music1 = self.df.readstr()
            self.music2 = self.df.readstr()
            self.exit_north = self.df.readstr()
            self.exit_east = self.df.readstr()
            self.exit_south = self.df.readstr()
            self.exit_west = self.df.readstr()
            self.skybox = self.df.readstr()
            self.atmos_sound_day = self.df.readstr()
            self.map_b1_last_xpos = self.df.readuchar()
            self.map_b1_last_ypos = self.df.readuchar()
            self.map_b1_outsideflag = self.df.readshort()
            self.map_unknownh1 = self.df.readshort()

            self.color_r = self.df.readuchar()
            self.color_g = self.df.readuchar()
            self.color_b = self.df.readuchar()
            self.color_a = self.df.readuchar()

            self.parallax_x = self.df.readint()
            self.parallax_y = self.df.readint()
            self.clouds = self.df.readint()
            self.savegame_1 = self.df.readint()
            self.savegame_2 = self.df.readint()
            self.savegame_3 = self.df.readint()

            # Tiles
            self.set_tile_savegame()
            for i in range(200 * 100):
                self.addtile()

            # Tilecontents...  Just keep going until EOF
            try:
                while (self.addtilecontent()):
                    pass
            except FirstItemLoadException, e:
                pass

            # Entities...  Just keep going until EOF (note that this is in a separate file)
            # Also note that we have to support situations where there is no entity file
            if (self.df_ent.exists()):
                self.df_ent.open_r()
                try:
                    while (self.addentity()):
                        pass
                except FirstItemLoadException, e:
                    pass
                self.df_ent.close()

            # If there's extra data at the end, we likely don't have
            # a valid char file
            self.extradata = self.df.read()
            if (len(self.extradata) > 0):
                raise LoadException('Extra data at end of file')

            # Close the file
            self.df.close()
示例#22
0
    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 = {}
for i in range(numfiles):
示例#23
0
class Character(object):
    """
    The base Character class.  Interestingly, some items which are NOT stored in
    the char file:
      * Which map the character's currently on (orientation/position ARE stored here though)
      * Time of day in the game world
      * Total time spent playing the game

    Note that the base class does not define read() and write() methods, which
    are left up to the specific Book classes (the theory being that the actual
    underlying formats can be rather different, and it doesn't really make sense
    to try and work around that.
    """

    book = None
    form_elements = []

    def __init__(self, df):
        """ A fresh object. """

        #self.book = c.book
        self.name = ''
        self.strength = -1
        self.dexterity = -1
        self.endurance = -1
        self.speed = -1
        self.intelligence = -1
        self.wisdom = -1
        self.perception = -1
        self.concentration = -1
        self.skills = {}
        self.maxhp = -1
        self.maxmana = -1
        self.curhp = -1
        self.curmana = -1
        self.experience = -1
        self.level = -1
        self.gold = -1
        self.torches = -1
        self.torchused = -1
        self.readyslots = []
        self.inventory = []
        for i in range(self.inv_rows):
            self.inventory.append([])
            for j in range(self.inv_cols):
                self.inventory[i].append(Item.new(c.book))
        self.readyitems = []
        for i in range(self.ready_rows * self.ready_cols):
            self.readyitems.append(Item.new(c.book))
        self.curinvcol = 0
        self.curinvrow = 0
        self.quiver = Item.new(c.book)
        self.helm = Item.new(c.book)
        self.cloak = Item.new(c.book)
        self.amulet = Item.new(c.book)
        self.torso = Item.new(c.book)
        self.weap_prim = Item.new(c.book)
        self.belt = Item.new(c.book)
        self.gauntlet = Item.new(c.book)
        self.legs = Item.new(c.book)
        self.ring1 = Item.new(c.book)
        self.ring2 = Item.new(c.book)
        self.shield = Item.new(c.book)
        self.feet = Item.new(c.book)
        self.spells = []
        self.orientation = -1
        self.xpos = -1
        self.ypos = -1
        self.fxblock = []
        self.picid = -1
        self.statuses = []
        self.extra_att_points = -1
        self.extra_skill_points = -1
        self.df = df

    def set_inv_size(self, rows, cols, ready_rows, ready_cols):
        """
        Sets the size of the inventory array
        """
        self.inv_rows = rows
        self.inv_cols = cols
        self.ready_rows = ready_rows
        self.ready_cols = ready_cols

    def replicate(self):
        newchar = Character.load(self.df.filename, self.book)

        if self.book == 1:
            newchar = B1Character(Savefile(self.df.filename))
        elif self.book == 2:
            newchar = B2Character(Savefile(self.df.filename))
        elif self.book == 3:
            newchar = B3Character(Savefile(self.df.filename))

        # Single vals (no need to do actual replication)
        #newchar.book = self.book
        newchar.inv_rows = self.inv_rows
        newchar.inv_cols = self.inv_cols
        newchar.name = self.name
        newchar.strength = self.strength
        newchar.dexterity = self.dexterity
        newchar.endurance = self.endurance
        newchar.speed = self.speed
        newchar.intelligence = self.intelligence
        newchar.wisdom = self.wisdom
        newchar.perception = self.perception
        newchar.concentration = self.concentration
        newchar.maxhp = self.maxhp
        newchar.maxmana = self.maxmana
        newchar.curhp = self.curhp
        newchar.curmana = self.curmana
        newchar.experience = self.experience
        newchar.level = self.level
        newchar.gold = self.gold
        newchar.torches = self.torches
        newchar.torchused = self.torchused
        newchar.curinvcol = self.curinvcol
        newchar.curinvrow = self.curinvrow
        newchar.orientation = self.orientation
        newchar.xpos = self.xpos
        newchar.ypos = self.ypos
        newchar.picid = self.picid
        newchar.extra_att_points = self.extra_att_points
        newchar.extra_skill_points = self.extra_skill_points

        # Lists that need copying
        for val in self.spells:
            newchar.spells.append(val)
        for val in self.fxblock:
            newchar.fxblock.append(val)
        for val in self.statuses:
            newchar.statuses.append(val)

        # More complex lists that need copying
        for val in self.readyslots:
            newchar.readyslots.append([val[0], val[1]])

        # Dicts that need copying
        for key, val in self.skills.iteritems():
            newchar.skills[key] = val

        # Objects that need copying
        for i in range(self.inv_rows):
            for j in range(self.inv_cols):
                newchar.inventory[i][j] = self.inventory[i][j].replicate()
        for i in range(self.ready_rows * self.ready_cols):
            newchar.readyitems[i] = self.readyitems[i].replicate()
        newchar.quiver = self.quiver.replicate()
        newchar.helm = self.helm.replicate()
        newchar.cloak = self.cloak.replicate()
        newchar.amulet = self.amulet.replicate()
        newchar.torso = self.torso.replicate()
        newchar.weap_prim = self.weap_prim.replicate()
        newchar.belt = self.belt.replicate()
        newchar.gauntlet = self.gauntlet.replicate()
        newchar.legs = self.legs.replicate()
        newchar.ring1 = self.ring1.replicate()
        newchar.ring2 = self.ring2.replicate()
        newchar.shield = self.shield.replicate()
        newchar.feet = self.feet.replicate()

        # Call out to the subclass replication function
        self._sub_replicate(newchar)

        # Now return our duplicated object
        return newchar

    def _sub_replicate(self, newchar):
        """
        Just a stub function for superclasses to override, to replicate any
        superclass-specific data
        """
        pass

    def setGold(self,goldValue):
        """ Alter gold to new amount. """
        self.gold = goldValue

    def setMaxMana(self,manaValue):
        """
        Alter max mana value & set current to max.
        Note that equipped-item modifiers will raise the actual in-game
        maximums.
        """
        self.maxmana = manaValue
        if (self.curmana < manaValue):
            self.setCurMana(manaValue)

    def setCurMana(self,manaValue):
        """ Replenish mana to input value. """
        self.curmana = manaValue

    def setMaxHp(self,hpValue):
        """
        Alter max HP & set current to max.
        Note that equipped-item modifiers will raise the actual in-game
        maximums.
        """
        self.maxhp = hpValue
        if (self.curhp < hpValue):
            self.setCurHp(hpValue)

    def setCurHp(self,hpValue):
        """ Replenish HP to input value. """
        self.curhp = hpValue

    def clearDiseases(self):
        """
        Clear all diseases.  Also clears out severe injuries/curses/etc on Book 2 chars
        """
        if self.book == 1:
            self.disease = 0x0000
        else:
            self.permstatuses = self.permstatuses & 0xFFFF0000

    def resetHunger(self):
        """
        Resets hunger and thirst; only valid for Book 2 characters, of course.
        """
        if self.book > 1:
            self.hunger = 1000
            self.thirst = 1000

    def addskill(self, skillnum, level):
        """ Add a new skill at a given level. """
        self.skills[skillnum] = level

    def addreadyslot(self, spell, level):
        """ Add a new spell to a 'ready' slot. """
        self.readyslots.append([spell, level])

    def additem(self):
        """ Add a new item, assuming that the items are stored in a
            left-to-right, top-to-bottom format on the inventory screen. """
        self.inventory[self.curinvrow][self.curinvcol].read(self.df)
        self.curinvcol = self.curinvcol + 1
        if (self.curinvcol == self.inv_cols):
            self.curinvcol = 0
            self.curinvrow = self.curinvrow + 1

    @staticmethod
    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

        # 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 B1Character(df)
        elif book == 2:
            c.switch_to_book(2)
            return B2Character(df)
        else:
            c.switch_to_book(3)
            return B3Character(df)