# HG changeset patch # User Gerard Krijgsman # Date 1347290942 -7200 # Mon Sep 10 17:29:02 2012 +0200 # Node ID 4f7ca7635aad9d6b081649bce343db880fe2930a # Parent 2b8c9fb921bea02b5f386851838dac4cc81bc9de Rewrote result.py to use functions, not a singleton static class contraption diff --git a/result.py b/result.py --- a/result.py +++ b/result.py @@ -1,5 +1,5 @@ """ -Class for parsing RST (result) files. +Functions for reading and parsing RST (result) files. """ import struct @@ -9,82 +9,89 @@ """Exception raised when the RST file is invalid or corrupt""" pass -class ResultFile(object): - """Class for parsing RST (result) files""" +def read(f, filesize): + """Reads from file and parses RST data""" + result = {} + 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['planets']) + result['planets'] = Planet.read(f, has_count=True) + f.seek(pointers['bases']) + result['bases'] = Base.read(f, has_count=True) + return result + +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: + return True + return False + +def get_sections(f, filesize): + """Get pointers for various sections of the RST file""" + result = {} + f.seek(0) # Reset position + (result['ships'],) = struct.unpack('< i', f.read(4)) + (result['targets'],) = struct.unpack('< i', f.read(4)) + (result['planets'],) = struct.unpack('< i', f.read(4)) + (result['bases'],) = struct.unpack('< i', f.read(4)) + (result['messages'],) = struct.unpack('< i', f.read(4)) + (result['shipxy'],) = struct.unpack('< i', f.read(4)) + (result['general'],) = struct.unpack('< i', f.read(4)) + (result['vcr'],) = struct.unpack('< i', f.read(4)) + # Subtract 1 to fix BASIC style pointer (which assumes 1 = start of file) + for key, pointer in result.items(): + result[key] = int(pointer) - 1 + # Check RST version: is WinPlan data present? + (result['winplan'], result['extratargets'],) = get_version(f, filesize) + if result['winplan']: + f.seek(44) # Position of LEECHx.DAT + (result['leech'],) = struct.unpack('< i', f.read(4)) + if not result['leech']: + result['leech'] = None + else: + result['leech'] = int(result['leech']) - 1 + (result['ufo'],) = struct.unpack('< i', f.read(4)) + if not result['ufo']: + result['ufo'] = None + else: + result['ufo'] = int(result['ufo']) - 1 + else: + result['leech'] = None + result['ufo'] = None + # Check if pointers are valid + for pointer in result.values(): + if pointer is not None and not valid_pointer(pointer, filesize, 2): + raise InvalidResultFile('One or more pointers is invalid') + return result + +def get_version(f, filesize): + """Analyses RST file to determine if it is DOS-style or WinPlan-style""" SIGNATURE = 'VER3.5' VERSIONS = ('00', '01',) TARGETSIG = ('1211', '1120',) - size = None - pointers = {} - data = {} - - @classmethod - def setsize(cls, size): - """Sets size of RST file, which is important for checking pointers""" - cls.size = size - - @classmethod - def validpointer(cls, pointer, datasize): - """Checks if pointer seek location is valid (smaller than filesize)""" - if cls.size is None: - raise ValueError('RST file size not defined') - if pointer < 0: - raise ValueError('Pointer should be positive integer') - if pointer + datasize <= cls.size: - return True - return False - - @classmethod - def read(cls, f): - """Reads from file and parses RST data""" - # Reset position - f.seek(0) - cls.getpointers(f) - # Read standard data - f.seek(cls.pointers['ships']) - cls.data['ships'] = Ship.read(f, has_count=True) - f.seek(cls.pointers['planets']) - cls.data['planets'] = Planet.read(f, has_count=True) - f.seek(cls.pointers['bases']) - cls.data['bases'] = Base.read(f, has_count=True) - # Return gathered data - return cls.data - - @classmethod - def getpointers(cls, f): - """Get pointers for various sections of the RST file""" - # Get pointers to standard data - (cls.pointers['ships'],) = struct.unpack('< i', f.read(4)) - (cls.pointers['targets'],) = struct.unpack('< i', f.read(4)) - (cls.pointers['planets'],) = struct.unpack('< i', f.read(4)) - (cls.pointers['bases'],) = struct.unpack('< i', f.read(4)) - (cls.pointers['messages'],) = struct.unpack('< i', f.read(4)) - (cls.pointers['shipxy'],) = struct.unpack('< i', f.read(4)) - (cls.pointers['general'],) = struct.unpack('< i', f.read(4)) - (cls.pointers['vcr'],) = struct.unpack('< i', f.read(4)) - # Subtract 1 to fix BASIC style pointer - for key, pointer in cls.pointers.items(): - cls.pointers[key] = int(pointer) - 1 - # Check if pointers are valid - for pointer in cls.pointers.values(): - if not cls.validpointer(pointer, 2): - raise InvalidResultFile('One or more pointers is invalid') - - @classmethod - def getversion(cls, f): - """Analyses RST file to determine if it is DOS-style or WinPlan-style""" - # Check first signature (SIGNATURE) at location +32 of RST file - f.seek(32) - (signature, version, pointer) = struct.unpack('< 6s 2s i', f.read(12)) - if (signature != cls.SIGNATURE) or (version not in cls.VERSIONS): - return False - cls.pointers['winplan'] = int(pointer) - 1 - # Check if location of WinPlan data exists in file - if not cls.validpointer(cls.pointers['winplan'], 13281+4): - return False - # Check second signature (TARGETSIG) at location +13282 of WinPlan data - f.seek(cls.pointers['winplan'] + 13282) - (targetsig,) = struct.unpack('< 4s', f.read(4)) - if targetsig not in cls.TARGETSIG: - return False - return True # True = WinPlan-style; False = otherwise \ No newline at end of file + # Check first signature (SIGNATURE) at location +32 of RST file + f.seek(32) + (signature, version, pointer) = struct.unpack('< 6s 2s i', f.read(12)) + if (signature != SIGNATURE) or (version not in VERSIONS): + return None, None + # Fix BASIC style pointer + pointer = int(pointer) - 1 + # Check if location of WinPlan data (and TARGETSIG) exists in file + if not valid_pointer(pointer, filesize, 13282+4): + return None, None + # Check second signature (TARGETSIG) at location +13282 of WinPlan data + f.seek(pointer + 13282) + (targetsig,) = struct.unpack('< 4s', f.read(4)) + if targetsig not in TARGETSIG: + return None, None + # Second TARGETSIG value (1120) indicates whether extra targets follow it + if targetsig == TARGETSIG[1]: + return pointer, (pointer + 13282 + 4) + else: + return pointer, None \ No newline at end of file