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()
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
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 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))
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
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))
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)
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 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))
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))
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))
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))
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()
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))
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()
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)
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()
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))
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)
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
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()
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):
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)