Package pyffi :: Package formats :: Package esp
[hide private]
[frames] | no frames]

Source Code for Package pyffi.formats.esp

  1  """ 
  2  :mod:`pyffi.formats.esp` --- Elder Scrolls plugin/master/save files (.esp, .esm, and .ess) 
  3  ========================================================================================== 
  4   
  5  Implementation 
  6  -------------- 
  7   
  8  .. autoclass:: EspFormat 
  9     :show-inheritance: 
 10     :members: 
 11   
 12  Regression tests 
 13  ---------------- 
 14   
 15  Read a ESP file 
 16  ^^^^^^^^^^^^^^^ 
 17   
 18  >>> # check and read esp file 
 19  >>> stream = open('tests/esp/test.esp', 'rb') 
 20  >>> data = EspFormat.Data() 
 21  >>> data.inspect(stream) 
 22  >>> # do some stuff with header? 
 23  >>> #data.header.... 
 24  >>> data.read(stream) 
 25  >>> # do some stuff... 
 26   
 27  Parse all ESP files in a directory tree 
 28  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 
 29   
 30  >>> for stream, data in EspFormat.walkData('tests/esp'): 
 31  ...     print(stream.name) 
 32  tests/esp/test.esp 
 33   
 34  Create an ESP file from scratch and write to file 
 35  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 
 36   
 37  >>> data = EspFormat.Data() 
 38  >>> from tempfile import TemporaryFile 
 39  >>> stream = TemporaryFile() 
 40  >>> data.write(stream) 
 41  """ 
 42   
 43  # ***** BEGIN LICENSE BLOCK ***** 
 44  # 
 45  # Copyright (c) 2007-2011, Python File Format Interface 
 46  # All rights reserved. 
 47  # 
 48  # Redistribution and use in source and binary forms, with or without 
 49  # modification, are permitted provided that the following conditions 
 50  # are met: 
 51  # 
 52  #    * Redistributions of source code must retain the above copyright 
 53  #      notice, this list of conditions and the following disclaimer. 
 54  # 
 55  #    * Redistributions in binary form must reproduce the above 
 56  #      copyright notice, this list of conditions and the following 
 57  #      disclaimer in the documentation and/or other materials provided 
 58  #      with the distribution. 
 59  # 
 60  #    * Neither the name of the Python File Format Interface 
 61  #      project nor the names of its contributors may be used to endorse 
 62  #      or promote products derived from this software without specific 
 63  #      prior written permission. 
 64  # 
 65  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
 66  # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
 67  # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 
 68  # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 
 69  # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
 70  # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
 71  # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
 72  # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
 73  # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
 74  # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
 75  # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 76  # POSSIBILITY OF SUCH DAMAGE. 
 77  # 
 78  # ***** END LICENSE BLOCK ***** 
 79   
 80  import struct 
 81  import os 
 82  import re 
 83   
 84  import pyffi.object_models.xml 
 85  import pyffi.object_models.common 
 86  from pyffi.object_models.xml.basic import BasicBase 
 87  import pyffi.object_models 
 88  from pyffi.utils.graph import EdgeFilter 
89 90 -class EspFormat(pyffi.object_models.xml.FileFormat):
91 """This class implements the ESP format.""" 92 xml_file_name = 'esp.xml' 93 # where to look for esp.xml and in what order: 94 # ESPXMLPATH env var, or EspFormat module directory 95 xml_file_path = [os.getenv('ESPXMLPATH'), os.path.dirname(__file__)] 96 # filter for recognizing esp files by extension 97 # .ess are users save games encoded similarly to esp files 98 # .esm are esp files with an bit set in the header. 99 RE_FILENAME = re.compile(r'^.*\.(esp|ess|esm)$', re.IGNORECASE) 100 # used for comparing floats 101 _EPSILON = 0.0001 102 103 # basic types 104 int = pyffi.object_models.common.Int 105 uint = pyffi.object_models.common.UInt 106 byte = pyffi.object_models.common.Byte 107 ubyte = pyffi.object_models.common.UByte 108 char = pyffi.object_models.common.Char 109 short = pyffi.object_models.common.Short 110 ushort = pyffi.object_models.common.UShort 111 float = pyffi.object_models.common.Float 112 uint64 = pyffi.object_models.common.UInt64 113 ZString = pyffi.object_models.common.ZString
114 - class RecordType(pyffi.object_models.common.FixedString):
115 _len = 4
116 117 # implementation of esp-specific basic types 118 119 # XXX nothing here yet... 120 121 @staticmethod
122 - def version_number(version_str):
123 """Converts version string into an integer. 124 125 :param version_str: The version string. 126 :type version_str: str 127 :return: A version integer. 128 129 >>> hex(EspFormat.version_number('1.2')) 130 '0x102' 131 """ 132 high, low = version_str.split(".") 133 return (int(high) << 8) + int(low)
134 135 @classmethod
136 - def _read_records(cls, stream, data, 137 parent=None, size=None, num_records=None):
138 """Read records by data size or by number.""" 139 records = [] 140 while (size > 0) if size is not None else (num_records > 0): 141 pos = stream.tell() 142 record_type = stream.read(4).decode() 143 if parent: 144 record_type = parent.__class__.__name__ + "_" + record_type 145 stream.seek(pos) 146 try: 147 record = getattr(cls, record_type)() 148 except AttributeError: 149 print("unknown record type %s; aborting" % record_type) 150 break 151 records.append(record) 152 record.read(stream, data) 153 if size is not None: 154 size -= stream.tell() - pos #slower: record.get_size() 155 else: 156 num_records -= 1 157 return records
158
159 - class Data(pyffi.object_models.FileFormat.Data):
160 """A class to contain the actual esp data."""
161 - def __init__(self):
162 self.tes4 = EspFormat.TES4() 163 self.records = []
164
165 - def inspect_quick(self, stream):
166 """Quickly checks if stream contains ESP data, and gets the 167 version, by looking at the first 8 bytes. 168 169 :param stream: The stream to inspect. 170 :type stream: file 171 """ 172 pos = stream.tell() 173 try: 174 # XXX check that file is ESP 175 if (stream.read(4) != 'TES4'): 176 raise ValueError("Not an ESP file.") 177 finally: 178 stream.seek(pos)
179 180 # overriding pyffi.object_models.FileFormat.Data methods 181
182 - def inspect(self, stream):
183 """Quickly checks if stream contains ESP data, and reads the 184 header. 185 186 :param stream: The stream to inspect. 187 :type stream: file 188 """ 189 pos = stream.tell() 190 try: 191 self.inspect_quick(stream) 192 # XXX read header 193 finally: 194 stream.seek(pos)
195 196
197 - def read(self, stream):
198 """Read a esp file. 199 200 :param stream: The stream from which to read. 201 :type stream: ``file`` 202 """ 203 self.inspect_quick(stream) 204 # read header record 205 self.tes4.read(stream, self) 206 hedr = self.tes4.get_sub_record("HEDR") 207 if not hedr: 208 print("esp file has no HEDR; aborting") 209 return 210 self.records = EspFormat._read_records( 211 stream, self, num_records=hedr.num_records) 212 213 # check if we are at the end of the file 214 if stream.read(1): 215 #raise ValueError( 216 print( 217 'end of file not reached: corrupt esp file?')
218
219 - def write(self, stream):
220 """Write a esp file. 221 222 :param stream: The stream to which to write. 223 :type stream: ``file`` 224 """ 225 self.tes4.write(stream, self)
226 227 # DetailNode 228
229 - def get_detail_child_nodes(self, edge_filter=EdgeFilter()):
230 return self.tes4.get_detail_child_nodes(edge_filter=edge_filter)
231
232 - def get_detail_child_names(self, edge_filter=EdgeFilter()):
233 return self.tes4.get_detail_child_names(edge_filter=edge_filter)
234 235 # GlobalNode 236
237 - def get_global_child_nodes(self, edge_filter=EdgeFilter()):
238 return self.tes4.sub_records + self.records
239
240 - class Record:
241 - def __init__(self):
242 pyffi.object_models.xml.struct_.StructBase.__init__(self) 243 self.sub_records = []
244
245 - def read(self, stream, data):
246 # read all fields 247 pyffi.object_models.xml.struct_.StructBase.read( 248 self, stream, data) 249 # read all subrecords 250 self.sub_records = EspFormat._read_records( 251 stream, data, parent=self, size=self.data_size)
252
253 - def write(self, stream, data):
254 raise NotImplementedError
255 256 # GlobalNode 257
258 - def get_global_child_nodes(self, edge_filter=EdgeFilter()):
259 return self.sub_records
260 261 # other functions 262
263 - def get_sub_record(self, sub_record_type):
264 """Find first subrecord of given type.""" 265 for sub_record in self.sub_records: 266 if sub_record.type == sub_record_type: 267 return sub_record 268 # not found 269 return None
270
271 - class GRUP:
272 - def __init__(self):
273 pyffi.object_models.xml.struct_.StructBase.__init__(self) 274 self.records = []
275
276 - def read(self, stream, data):
277 # read all fields 278 pyffi.object_models.xml.struct_.StructBase.read( 279 self, stream, data) 280 # read all subrecords 281 self.records = EspFormat._read_records( 282 stream, data, size=self.data_size - 20)
283
284 - def write(self, stream, data):
285 raise NotImplementedError
286 287 # GlobalNode 288
289 - def get_global_child_nodes(self, edge_filter=EdgeFilter()):
290 return self.records
291