Can generate valid empty TRN file (with no commands)
3 files changed, 171 insertions(+), 4 deletions(-)

M common.py
M result.py
A => turn.py
M common.py +11 -1
@@ 4,6 4,9 @@ Common classes & functions
 
 import struct
 
+SIGNATURE = 'VER3.5'
+SUBVERSION = ('00', '01',)
+
 class PlanetsData(object):
     """Base class for all Planets data objects"""
     FILENAME = None         # Default filename

          
@@ 102,4 105,11 @@ class PlanetsData(object):
         f = open(name,'rb')
         result = cls.read(f, has_count, count)
         f.close()
-        return result
  No newline at end of file
+        return result
+
+def get_checksum(data):
+    """Calculates the sum of all individual bytes in the data string"""
+    result = 0
+    for i in range(0, len(data)):
+        result += ord(data[i])
+    return result
  No newline at end of file

          
M result.py +2 -3
@@ 3,6 3,7 @@ Functions for reading and parsing RST (r
 """
 
 import struct
+from common import SIGNATURE, SUBVERSION
 from player import (Base, Planet, Ship, ShipXY, Target,
                     read_general, read_messages)
 

          
@@ 67,13 68,11 @@ def _get_sections(f, filesize):
 
 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',)
     # 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):
+    if (signature != SIGNATURE) or (version not in SUBVERSION):
         return None, None
     # Fix BASIC style pointer
     pointer = int(pointer) - 1

          
A => turn.py +158 -0
@@ 0,0 1,158 @@ 
+"""
+Functions for generating TRN (turn) files.
+"""
+
+import struct
+import random
+from common import SIGNATURE, SUBVERSION, get_checksum
+
+CLIENT = 'VGA Planets v3.x library for Python'
+URL = 'http://github.com/ghdpro/planets'
+
+# VPH35.DLL magic numbers, taken from PCC v2's "trn.cc" source file
+VPH_MAGIC = (1585242373,
+    458484639, 1702713875, 2131768570, 943874411, 1531045611,
+    622829488, 660770929, 473301358, 1868910709, 439267666, 1259778247,
+    187160419, 205520992, 1162432602, 2048525217, 663275107, 1945076761,
+    1912495862, 372583676, 2110506768, 972564220, 1627953855, 1696231547,
+    1825551059, 690525357, 1425805634, 1273009202, 1643106825, 1033503714,
+    1773067018, 1444056607, 841306782, 1311137219, 472310692, 1658228604,
+    214806212, 1638334074, 870981249, 1438230436, 1722981495, 383237037,
+    1014208183, 1950729749, 1381216466, 1149684732, 1475271197, 990158844,
+    659846975, 131158828, 1269952134, 1929873739, 149943298, 94038386,
+    1639179540, 519578396, 649680371, 2139806121, 48126387, 1820750093,
+    2002158429, 834011058, 127330762, 1341047341, 45011247, 1210785240,
+    102394054, 1033444233, 1452787209, 1636216880, 2001004855, 196571844,
+    768753436, 1715639759, 9036553, 550413001, 1195957868, 566073290,
+    1386247611, 725117880, 637842515, 782679024, 614960412, 1259473924,
+    710893647, 137748852, 808495109, 1174108532, 2141228605, 1298353301,
+    1989952843, 607318838, 1868217839, 2046567417, 1297732528, 886928938,
+    533473933, 667670866, 1241783877, 1634258231, 1529167548, 1048674755,
+    108553737, 442206379, 1427828321, 178793040, 57025576, 1886069810,
+    1452681265, 392872129, 1749094387, 1931946557, 610131601, 497923660,
+    800378618, 833787008, 1047995126, 867114247, 108316439, 1889137816,
+    1566927898, 1606954817, 2129997452, 176508207, 1504084876, 781656333,
+    1575411145, 952282888, 1920012969, 725392878, 442033280, 2055008888,
+    125996860, 648896510, 1271579722, 734745843, 457213090, 101154514,
+    1253209494, 649313503, 665663012, 1284757233, 526008074, 1128559135,
+    708376521, 1888247159, 637430572, 1297014774, 84473586, 1938406737,
+    278055502, 2082329430, 784004382, 886858342, 487519681, 979889529,
+    2118032563, 376523135, 2037399162, 494383465, 1744352698, 533745717,
+    752066469, 1518627158, 347571084, 1270232880, 460005993, 1754379254,
+    1431354806, 103810045, 676346171, 948969734, 1270441550, 562587328,
+    305781542, 48494333, 263492952, 1020466270, 190108896, 1009887493,
+    1263640424, 2136294797, 951195719, 1154885409, 533815976, 707619918,
+    1293089160, 1565561820, 1424862457, 2024541688, 1849356050, 804648133,
+    1041775421, 1752468846, 2051572786, 749910457, 1708669854, 1592915884,
+    1123095599, 1460717743, 1948843781, 1082061162, 1152635918,
+    1881839283, 760734026, 1910315568, 1258782923, 2051380841, 1725205147,
+    585278536, 1106219491, 444629203, 1099824661, 734821072, 2025557656,
+    657473172, 255537853, 291983710, 286553905, 42517818, 670349676,
+    870581336, 1127381655, 1839475352, 632654867, 547547534, 1471914002,
+    1512583684, 890892484, 1857789058, 1587065657, 709203658, 1447182906,
+    950862839, 1854232374, 1589606089, 18301536, 700074959, 415606342,
+    1405416566, 1289157530, 1227135268, 340764183, 419122630, 1884968096,
+    326246210, 540566661, 853062096, 1975701318, 1492562570, 1963382636,
+    1075710563, 758982437, 2060895641, 1152739182, 1371354866, 800770398,
+    1598945131, 79563287, 694771023, 1704620086, 248109047, 95128540,
+    1062172273, 810095152, 2013227291, 1998220334, 1498632230, 1836447618,
+    217773428, 986641406, 603013591, 1230144401, 1075426659, 1746848829,
+    817629711, 186988432, 1484074762, 843442591, 776096924, 1024866700,
+    2027642148, 1049701698, 247896996, 387855251, 857506062, 165410039,
+    1748384075, 1958279260, 1593211160, 1998805368, 1633675306,
+    2048559498, 1569149953, 1404385053, 784606841, 1589733669, 373455454,
+    909199500, 1312922206, 408034973, 997233876, 963117498, 742951874,
+    10752697, 574771227, 794412355, 92609016, 392712605, 964282276,
+    1732686549)
+
+def make_turn(general, reginfo, commands):
+    """Generate complete TRN (turn) file"""
+    result = ''
+    # Player
+    result += struct.pack('< h', general['player'])
+    # No. of commands
+    result += struct.pack('< i', len(commands))
+    # Timestamp
+    result += general['timestamp_date'] + general['timestamp_time']
+    # Unused
+    result += struct.pack('< h', 0)
+    # Timestamp checksum
+    checksum_timestamp = get_checksum(general['timestamp_date'] +
+                                      general['timestamp_time'])
+    result += struct.pack('< h', checksum_timestamp)
+    # Include commands (if any)
+    if len(commands):
+        # Unused
+        result += '\x00'
+        # Build pointer + command blocks
+        pointers = ''
+        data = ''
+        for command in commands:
+            pointers += struct.pack('< i', len(data))
+            data += command
+        result += pointers + data
+    # Win-Plan style TRN trailer
+    result += _get_winplan_trailer(general['turn'], reginfo)
+    # DOS style TRN trailer follows
+    checksum_x = get_checksum(result) + (checksum_timestamp * 3) + 13
+    result += struct.pack('< i i', checksum_x, 0)
+    checksum = 0
+    for i in range(0, 25):
+        value = (ord(reginfo[0][i]) * (i+1) * 13)
+        result += struct.pack('< i', value)
+        checksum += value
+    for i in range(0, 25):
+        value = (ord(reginfo[1][i]) * (i+1) * 13)
+        result += struct.pack('< i', value)
+        checksum += value
+    result += struct.pack('< i', checksum + 668)
+    # ID block (checksum from TRN file for specific player)
+    for i in range(0, 11):
+        if i+1 == general['player']:
+            result += struct.pack('< i', checksum_x)
+        elif i+1 == 11: # Oddity when player isn't player 11
+            result += struct.pack('< i', int('0x0F31', 0))           
+        else:
+            result += struct.pack('< i', 0)
+    return result
+
+def get_registration(serial=None, date=None):
+    """Returns registration data"""
+    if serial is None:
+        # Set shareware string
+        serial = 'VGA Planets shareware'
+        date = 'V3.5 for windows'
+    else:
+        serial = serial.encode('latin1')
+        date = date.encode('latin1')
+    return (serial.ljust(25), date.ljust(25),
+            CLIENT.ljust(50), URL.ljust(50))
+
+def _get_winplan_trailer(turn, reginfo):
+    """Assembles WinPlan-style TRN (turn) file trailer"""
+    result = SIGNATURE + SUBVERSION[1]
+    # VPH35.DLL values (encoded similarly as in PCC v2's GTurnfile::setRegInfo)
+    random.seed()
+    vph1 = VPH_MAGIC[turn % len(VPH_MAGIC)]
+    vph2 = random.randint(0, 65535)
+    vph2 |= random.randint(0, 65535)
+    vph1 ^= vph2
+    vph1 &= int('0x7FFFFFFF', 0)
+    vph2 &= int('0x7FFFFFFF', 0)
+    result += struct.pack('< i i', vph1, vph2)
+    # Registration info
+    serial_partb = ''
+    date_partb = ''
+    for i in range(0, 25):
+        serial_partb += chr(random.randint(0, 255))
+        date_partb += chr(random.randint(0, 255))
+    serial_parta = ''
+    date_parta = ''
+    for i in range(0, 25):
+        serial_parta += chr(ord(reginfo[0][i]) ^ ord(serial_partb[i]))
+        date_parta += chr(ord(reginfo[1][i]) ^ ord(date_partb[i]))
+    result += serial_parta + serial_partb + date_parta + date_partb
+    result += reginfo[2] + reginfo[3]
+    # Unused buffer of 100 bytes
+    result += '\x00'*100
+    return result
  No newline at end of file