M common.py +16 -8
@@ 34,12 34,6 @@ class PlanetsData(object):
setattr(self, field, None)
self.id = None
- def __setattr__(self, name, value):
- """__setattr__() is modified to rstrip() space padded BASIC strings"""
- if isinstance(value, str):
- value = value.rstrip()
- object.__setattr__(self, name, value)
-
def unpack(self, data):
"""Unpacks binary data into class instance variables"""
if len(data) is not self.PACK_LENGTH:
@@ 54,6 48,14 @@ class PlanetsData(object):
else:
setattr(self, name, value)
+ def fix(self):
+ """Fix object values such padded spaces on BASIC strings"""
+ # Iterate through instance variables
+ for name, value in self.__dict__.items():
+ if isinstance(value, str):
+ object.__setattr__(self, name, value.decode('latin1')
+ .encode('utf8').rstrip())
+
@classmethod
def load(cls, data, count=None):
"""Unpacks binary data into a list of object instances"""
@@ 70,7 72,9 @@ class PlanetsData(object):
instance.unpack(data[start:end])
if instance.id is None:
instance.id = i
- result[instance.id] = instance
+ # Some data may be zeroed out (id = 0), so only add when id > 0
+ if instance.id:
+ result[instance.id] = instance
return result
@classmethod
@@ 86,7 90,11 @@ class PlanetsData(object):
data = f.read(cls.PACK_LENGTH * cls.COUNT)
else:
data = f.read()
- return cls.load(data)
+ result = cls.load(data, count)
+ # Run fix() on returned objects
+ for key in result.keys():
+ result[key].fix()
+ return result
@classmethod
def open(cls, name, has_count=False, count=None):
M player.py +53 -8
@@ 2,6 2,9 @@
Classes for reading player data such as found in RST files.
"""
+# Disable pylint warnings caused by fields not being explicity defined
+# pylint: disable=W0201
+
import struct
from common import PlanetsData
@@ 73,8 76,8 @@ class Planet(PlanetsData):
'temperature',
'build_base')
- def unpack(self, data):
- PlanetsData.unpack(self, data)
+ def fix(self):
+ PlanetsData.fix(self)
# Fix temperature, which is inverted (value 0 = temp 100)
self.temperature = 100 - self.temperature
@@ 128,11 131,51 @@ class Ship(PlanetsData):
'transfer_id',
'intercept_id',
'credits')
-
+
+class Target(PlanetsData):
+ """List of of visual enemy ships (contacts)"""
+ # No FILENAME, but typically: TARGETx.DAT (x=player)
+ # No COUNT, but obviously limited by number of ships
+ PACK_LENGTH = 34
+ PACK_FORMAT = '< h h h h h h h 20s'
+ FIELDS = ('id',
+ 'owner',
+ 'warp',
+ 'x',
+ 'y',
+ 'hull',
+ 'heading',
+ 'name')
+
+ @classmethod
+ def read_result(cls, f, extra_offset=None):
+ """Reads visual enemy ship data from a RST file"""
+ (count,) = struct.unpack('< h', f.read(2))
+ data = f.read(cls.PACK_LENGTH * count)
+ result = cls.load(data, count)
+ # Load extra data if present in WinPlay-style RST file
+ if extra_offset:
+ f.seek(extra_offset)
+ (count,) = struct.unpack('< i', f.read(4))
+ data = f.read(cls.PACK_LENGTH * count)
+ extra = cls.load(data, count)
+ # Extra data has encrypted ship name
+ for k in extra.keys():
+ name = ''
+ for i in range(0, len(extra[k].name)):
+ name += chr(ord(extra[k].name[i]) ^ (154 - i))
+ extra[k].name = name
+ # Merge results
+ result = dict(result.items() + extra.items())
+ # Run fix() on returned objects
+ for key in result.keys():
+ result[key].fix()
+ return result
+
def read_messages(f):
- """Reads and returns list of messages found in RST or MDATAx.DAT files"""
+ """Reads and returns list of messages found in RST and MDATAx.DAT files"""
result = {}
- # Message count should preceed messages (arguments are ignored)
+ # Message count should preceed messages
(count,) = struct.unpack('< h', f.read(2))
# Get pointer + size of each message (if count > 0)
if count:
@@ 142,14 185,16 @@ def read_messages(f):
(pointers[i],) = struct.unpack('< i', f.read(4))
pointers[i] = int(pointers[i]) - 1 # Fix BASIC-style pointer
(sizes[i],) = struct.unpack('< h', f.read(2))
- for key, value in pointers.items():
- f.seek(value)
+ # Read actual messages
+ for key, pointer in pointers.items():
+ f.seek(pointer)
data = f.read(sizes[key])
+ # Message data is encoded: to decode subtract 13 from each character
message = ''
for i in range(0, len(data)):
char = chr(ord(data[i]) - 13)
if ord(char) == 13:
char = chr(10) # Unix-style newline
message += char
- result[key] = message
+ result[key] = message.decode('latin1').encode('utf8').rstrip()
return result
No newline at end of file
M result.py +6 -3
@@ 3,7 3,7 @@ Functions for reading and parsing RST (r
"""
import struct
-from player import Base, Planet, Ship, read_messages
+from player import Base, Planet, Ship, Target, read_messages
class InvalidResultFile(Exception):
"""Exception raised when the RST file is invalid or corrupt"""
@@ 12,10 12,15 @@ class InvalidResultFile(Exception):
def read(f, filesize):
"""Reads from file and parses RST data"""
result = {}
+ if not filesize:
+ raise InvalidResultFile('RST file size zero or not defined')
pointers = _get_sections(f, filesize)
# Read standard sections (should always exist, even if 0 items in section)
f.seek(pointers['ships'])
result['ships'] = Ship.read(f, has_count=True)
+ f.seek(pointers['targets'])
+ result['targets'] = Target.read_result(f,
+ extra_offset=pointers['extratargets'])
f.seek(pointers['planets'])
result['planets'] = Planet.read(f, has_count=True)
f.seek(pointers['bases'])
@@ 26,8 31,6 @@ def read(f, filesize):
def _valid_pointer(pointer, filesize, datasize):
"""Checks if pointer seek location is valid (smaller than filesize)"""
- if not filesize:
- raise InvalidResultFile('RST file size zero or not defined')
if pointer < 0:
raise InvalidResultFile('Pointer should be positive integer')
if pointer + datasize <= filesize: