M common.py +28 -25
@@ 9,16 9,16 @@ SUBVERSION = ('00', '01',)
class PlanetsData(object):
"""Base class for all Planets data objects"""
- FILENAME = None # Default filename
- COUNT = None # Default number of objects in a data file
- PACK_LENGTH = None # Length of binary data for single object
- PACK_FORMAT = None # Format of binary data for single object
- FIELDS = () # Fields for single object
+ filename = None # Default filename
+ count = None # Default number of objects in a data file
+ pack_length = None # Length of binary data for single object
+ pack_format = None # Format of binary data for single object
+ fields = () # Fields for single object
def __init__(self):
# Expand field list where necessary (in case of dictionary fields)
fields = []
- for field in self.FIELDS:
+ for field in self.fields:
# tuple format: ('name', start, end,) start/end must be 'int'
if isinstance(field, tuple) and (len(field) == 3):
for i in range(field[1], field[1]+field[2]):
@@ 26,10 26,10 @@ class PlanetsData(object):
else:
fields.append(field)
fields = tuple(fields)
- if self.__class__.FIELDS is not fields:
- self.__class__.FIELDS = fields
+ if self.__class__.fields is not fields:
+ self.__class__.fields = fields
# Initialize fields
- for field in self.FIELDS:
+ for field in self.fields:
# Dictionary field: see EngSpec class in fixed.py for an example
if isinstance(field, tuple):
setattr(self, str(field[0])+'['+str(field[1])+']', None)
@@ 39,12 39,12 @@ class PlanetsData(object):
def unpack(self, data):
"""Unpacks binary data into class instance variables"""
- if len(data) is not self.PACK_LENGTH:
+ if len(data) is not self.pack_length:
raise ValueError('Binary data passed has incorrect length')
- values = struct.unpack(self.PACK_FORMAT, data)
- if len(self.FIELDS) is not len(values):
+ values = struct.unpack(self.pack_format, data)
+ if len(self.fields) is not len(values):
raise ValueError('Incorrect number of variables in pack format')
- for name, value in zip(self.FIELDS, values):
+ for name, value in zip(self.fields, values):
# Dictionary field: see EngSpec class in fixed.py for an example
if isinstance(name, tuple):
setattr(self, str(name[0])+'['+str(name[1])+']', value)
@@ 64,14 64,14 @@ class PlanetsData(object):
"""Unpacks binary data into a list of object instances"""
result = {}
if count is None:
- if cls.COUNT is not None:
- count = cls.COUNT
- elif len(data) >= cls.PACK_LENGTH:
- count = int(len(data) / cls.PACK_LENGTH)
+ if cls.count is not None:
+ count = cls.count
+ elif len(data) >= cls.pack_length:
+ count = int(len(data) / cls.pack_length)
for i in range(1, count+1): # For compatibility, count from 1 and not 0
instance = cls()
- start = cls.PACK_LENGTH*(i-1)
- end = cls.PACK_LENGTH*i
+ start = cls.pack_length*(i-1)
+ end = cls.pack_length*i
instance.unpack(data[start:end])
if instance.id is None:
instance.id = i
@@ 81,16 81,19 @@ class PlanetsData(object):
return result
@classmethod
- def read(cls, f, has_count=False, count=None):
+ def read(cls, f, offset=None, has_count=False, count=None):
"""Reads from file and loads contents in list of object instances"""
+ # Seek to offset if set. Not recommended if has_count & count are None
+ if offset is not None:
+ f.seek(offset)
# Data may be preceeded by a WORD indicating how much objects follow
if has_count:
(count,) = struct.unpack('< h', f.read(2))
- # Load "count" objects or predetermined "COUNT" objects or whole file
+ # Load "count" objects or predetermined "count" objects or whole file
if count is not None:
- data = f.read(cls.PACK_LENGTH * count)
- elif cls.COUNT is not None:
- data = f.read(cls.PACK_LENGTH * cls.COUNT)
+ data = f.read(cls.pack_length * count)
+ elif cls.count is not None:
+ data = f.read(cls.pack_length * cls.count)
else:
data = f.read()
result = cls.load(data, count)
@@ 103,7 106,7 @@ class PlanetsData(object):
def open(cls, name, has_count=False, count=None):
"""Opens file and loads contents in list of object instances"""
f = open(name,'rb')
- result = cls.read(f, has_count, count)
+ result = cls.read(f, offset=None, has_count=has_count, count=count)
f.close()
return result
M fixed.py +20 -20
@@ 7,11 7,11 @@ from common import PlanetsData
class BeamSpec(PlanetsData):
"""List of starship beam weapons"""
- FILENAME = 'BEAMSPEC.DAT'
- COUNT = 10
- PACK_LENGTH = 36
- PACK_FORMAT = '< 20s h h h h h h h h'
- FIELDS = ('name',
+ filename = 'BEAMSPEC.DAT'
+ count = 10
+ pack_length = 36
+ pack_format = '< 20s h h h h h h h h'
+ fields = ('name',
'cost',
'tritanium',
'duranium',
@@ 23,11 23,11 @@ class BeamSpec(PlanetsData):
class EngSpec(PlanetsData):
"""List of starship engines"""
- FILENAME = 'ENGSPEC.DAT'
- COUNT = 9
- PACK_LENGTH = 66
- PACK_FORMAT = '< 20s h h h h h 9i'
- FIELDS = ('name',
+ filename = 'ENGSPEC.DAT'
+ count = 9
+ pack_length = 66
+ pack_format = '< 20s h h h h h 9i'
+ fields = ('name',
'cost',
'tritanium',
'duranium',
@@ 37,11 37,11 @@ class EngSpec(PlanetsData):
class HullSpec(PlanetsData):
"""List of starship hulls"""
- FILENAME = 'HULLSPEC.DAT'
- # No COUNT, but typically 105 objects in default HULLSPEC.DAT
- PACK_LENGTH = 60
- PACK_FORMAT = '< 30s h h h h h h h h h h h h h h h'
- FIELDS = ('name',
+ filename = 'HULLSPEC.DAT'
+ # No count, but typically 105 objects in default HULLSPEC.DAT
+ pack_length = 60
+ pack_format = '< 30s h h h h h h h h h h h h h h h'
+ fields = ('name',
'picture',
'damaged', # Unused field for picture of damaged ship
'tritanium',
@@ 60,11 60,11 @@ class HullSpec(PlanetsData):
class TorpSpec(PlanetsData):
"""List of starship torpedo weapons"""
- FILENAME = 'TORPSPEC.DAT'
- COUNT = 10
- PACK_LENGTH = 38
- PACK_FORMAT = '< 20s h h h h h h h h h'
- FIELDS = ('name',
+ filename = 'TORPSPEC.DAT'
+ count = 10
+ pack_length = 38
+ pack_format = '< 20s h h h h h h h h h'
+ fields = ('name',
'torpedo_cost',
'launcher_cost',
'tritanium',
M player.py +37 -31
@@ 11,11 11,11 @@ from common import PlanetsData
class Base(PlanetsData):
"""List of player's starbases"""
- # No FILENAME, but typically: BDATAx.DAT or BDATAx.DIS (x=player)
- # No COUNT, but obviously limited by number of planets
- PACK_LENGTH = 156
- PACK_FORMAT = '< h h h h h h h h 9h 20h 10h 10h 10h h h h h h h h h h h h'
- FIELDS = ('id',
+ # No filename, but typically: BDATAx.DAT or BDATAx.DIS (x=player)
+ # No count, but obviously limited by number of planets
+ pack_length = 156
+ pack_format = '< h h h h h h h h 9h 20h 10h 10h 10h h h h h h h h h h h h'
+ fields = ('id',
'owner',
'defense',
'damage',
@@ 42,11 42,11 @@ class Base(PlanetsData):
class Planet(PlanetsData):
"""List of player's planets"""
- # No FILENAME, but typically: PDATAx.DAT or PDATAx.DIS (x=player)
- # No COUNT, but maximum 500
- PACK_LENGTH = 85
- PACK_FORMAT = '<hh 3s hhh iiiiiiiiiii hhhhhhhhh i hhh'
- FIELDS = ('owner',
+ # No filename, but typically: PDATAx.DAT or PDATAx.DIS (x=player)
+ # No count, but maximum 500
+ pack_length = 85
+ pack_format = '<hh 3s hhh iiiiiiiiiii hhhhhhhhh i hhh'
+ fields = ('owner',
'id',
'friendlycode',
'mines',
@@ 84,11 84,11 @@ class Planet(PlanetsData):
class Ship(PlanetsData):
"""List of player's ships"""
- # No FILENAME, but typically: SHIPx.DAT or SHIPx.DIS (x=player)
- # No COUNT, but normally maximum 500 and absolute max of 999 (host999)
- PACK_LENGTH = 107
- PACK_FORMAT = '<hh 3s hhhhhhhhhhhhhhhhhhh 20s hhhhhhhhhhhhhhhhhhhhh'
- FIELDS = ('id',
+ # No filename, but typically: SHIPx.DAT or SHIPx.DIS (x=player)
+ # No count, but normally maximum 500 and absolute max of 999 (host999)
+ pack_length = 107
+ pack_format = '<hh 3s hhhhhhhhhhhhhhhhhhh 20s hhhhhhhhhhhhhhhhhhhhh'
+ fields = ('id',
'owner',
'friendlycode',
'warp',
@@ 135,11 135,11 @@ class Ship(PlanetsData):
class ShipXY(PlanetsData):
"""List of ship coordinates"""
- # No FILENAME, byt typically: SHIPXYx.DAT (x=player)
- COUNT = 500 # Not compatible with host999
- PACK_LENGTH = 8
- PACK_FORMAT = '< h h h h'
- FIELDS = ('x',
+ # No filename, byt typically: SHIPXYx.DAT (x=player)
+ count = 500 # Not compatible with host999
+ pack_length = 8
+ pack_format = '< h h h h'
+ fields = ('x',
'y',
'owner',
'mass')
@@ 155,11 155,11 @@ class ShipXY(PlanetsData):
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',
+ # 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',
@@ 176,16 176,18 @@ class Target(PlanetsData):
self.name = name
@classmethod
- def read_result(cls, f, extra_offset=None):
+ def read_result(cls, f, offset=None, extra_offset=None):
"""Reads visual enemy ship data from a RST file"""
+ if offset is not None:
+ f.seek(offset)
(count,) = struct.unpack('< h', f.read(2))
- data = f.read(cls.PACK_LENGTH * count)
+ 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:
+ if extra_offset is not None:
f.seek(extra_offset)
(count,) = struct.unpack('< i', f.read(4))
- data = f.read(cls.PACK_LENGTH * count)
+ data = f.read(cls.pack_length * count)
extra = cls.load(data, count)
# Extra data has encrypted ship name
for k in extra.keys():
@@ 197,9 199,11 @@ class Target(PlanetsData):
result[key].fix()
return result
-def read_general(f, from_result=True):
+def read_general(f, offset=None, from_result=True):
"""Reads general turn data found in RST and GENx.DAT files"""
result = {}
+ if offset is not None:
+ f.seek(offset)
# Timestamp
(result['timestamp_date'], result['timestamp_time'], ) = struct.unpack(
'< 10s 8s', f.read(18))
@@ 235,9 239,11 @@ def read_general(f, from_result=True):
f.read(4))
return result
-def read_messages(f):
+def read_messages(f, offset=None):
"""Reads and returns list of messages found in RST and MDATAx.DAT files"""
result = {}
+ if offset is not None:
+ f.seek(offset)
# Message count should preceed messages
(count,) = struct.unpack('< h', f.read(2))
# Get pointer + size of each message (if count > 0)
M result.py +9 -15
@@ 18,21 18,15 @@ def read(f, 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'])
- result['bases'] = Base.read(f, has_count=True)
- f.seek(pointers['messages'])
- result['messages'] = read_messages(f)
- f.seek(pointers['shipxy'])
- result['shipxy'] = ShipXY.read(f)
- f.seek(pointers['general'])
- result['general'] = read_general(f)
+ result['ships'] = Ship.read(f, offset=pointers['ships'], has_count=True)
+ result['targets'] = Target.read_result(
+ f, offset=pointers['targets'], extra_offset=pointers['extratargets'])
+ result['planets'] = Planet.read(
+ f, offset=pointers['planets'], has_count=True)
+ result['bases'] = Base.read(f, offset=pointers['bases'], has_count=True)
+ result['messages'] = read_messages(f, offset=pointers['messages'])
+ result['shipxy'] = ShipXY.read(f, offset=pointers['shipxy'])
+ result['general'] = read_general(f, offset=pointers['general'])
return result
def _valid_pointer(pointer, filesize, datasize):
M turn.py +1 -1
@@ 7,7 7,7 @@ import random
from common import SIGNATURE, SUBVERSION, get_checksum
CLIENT = 'VGA Planets v3.x library for Python'
-URL = 'http://github.com/ghdpro/planets'
+URL = 'https://bitbucket.org/ghdpro/planets'
# VPH35.DLL magic numbers, taken from PCC v2's "trn.cc" source file
VPH_MAGIC = (1585242373,