"""Decode every VTV's loadout and six-preset control table from RPL4.RES. Everything here is resolved by resource id rather than by guesswork: * RPL4TOOL -l gives the authoritative id -> description table. * The resource directory is walked in file order (an 8-byte prefix, a 32-byte name, addr/size at +0x30, data at +0x38, next descriptor after the data) and aligned against that listing, skipping the ids the listing marks "Not Used". Sizes are cross-checked on every row. * A vehicle's "ControlsMappings List of 2 elements" holds the resource ids of its Thrustmaster and L4 streams, so each stream is attributed to its owner exactly - no nearest-name proximity, which quietly mis-attributed the community vehicles. * Subsystem names come from the vehicle's own "Stream of N Subsystems". VTV::BasicSubsystemCount = 9, so list index i is subsystem id 9+i. Usage: extract_presets.py """ import json, re, struct, sys ROOT = 'c:/VWE/RP412' RES = open(ROOT + '/assets/RP411/RPL4.RES', 'rb').read() CFG = open(ROOT + '/assets/RP411/GAUGE/L4GAUGE.CFG', 'r', errors='replace').read() NRES = len(RES) OUT, LISTING = sys.argv[1], sys.argv[2] BASIC_SUBSYSTEM_COUNT = 9 # VTV::BasicSubsystemCount PRESET_BITS = 0x1F8 # ModePreset1..6 STICK = {0x40: 'trigger', 0x45: 'pinky', 0x46: 'thumb low', 0x47: 'thumb high'} CM_ATTR = {2: 'stick', 3: 'throttle', 4: 'pedals', 5: 'reverse thrust', 6: 'LIFT CUT', 7: 'SIDESLIP'} CM_MSG = {10: 'HORN', 14: 'reticle'} # Authored bindings whose hardware never shipped past prototype cockpits. NO_HARDWARE = {13} # VTVControlsMapper ActivatePTT SILHOUETTE = {'l': 'lepton', 'p': 'puck', 's': 'speck', 'm': 'mule', 'b': 'bull'} # ------------------------------------------------------------------ the CFG blocks = dict(re.findall(r'^([A-Za-z0-9_]+)\s*\n\{(.*?)^\}', CFG, re.S | re.M)) hull_art = {} for name, body in blocks.items(): if len(name) == 2 and name[0] in 'blpsm': pccs = re.findall(r'([a-z0-9]+\.pcc)', body) if pccs: hull_art[name] = pccs[0] # ------------------------------------------------- the console's own names # TeslaConsole's RPConfig.xml is the only place the vehicles and their hull # classes are named in words. The class comes from the picture the console # showed for each machine ("images/red planet vehicles/Bull.bmp"), so every # vehicle sharing a picture shares a class. CONSOLE = {} xml = open(ROOT + '/tools/console-config/RPConfig.xml', encoding='utf-8-sig').read() for m in re.finditer(r' NRES: break walk.append((name, addr, size)) o = addr + size table, i, mismatched = {}, 0, 0 for rid, rsize, desc in rows: if desc == 'Not Used': continue # an id the file never assigned if i >= len(walk): break name, addr, size = walk[i] if rsize is not None and int(rsize) != size: mismatched += 1 table[rid] = {'name': name, 'addr': addr, 'size': size, 'desc': desc} i += 1 if mismatched: print('!! %d id/size mismatches - the walk and the listing disagree' % mismatched, file=sys.stderr) return table TABLE = resource_table() # ------------------------------------------------------- subsystem names RAWNAME = re.compile(rb'([A-Za-z][A-Za-z0-9_]{2,23})\x00') SUBNAME = re.compile(r'(Booster\d*|Chute\d*|RivetGun\d*|LaserGun\d*|' r'Demo[Pp]ackDropper\d*|Slaver\d*|Stinger\d*|Eject\d*)$') def clean(raw): """Records are variable length; a float tail can leave printable junk glued to the front of the next name. Longest valid suffix wins.""" for i in range(len(raw)): if SUBNAME.fullmatch(raw[i:]): return raw[i:] return None def subsystems(veh, warn): want = re.compile(r'^%s: Stream of (\d+) Subsystems$' % re.escape(veh)) for res in TABLE.values(): m = want.match(res['desc']) if not m: continue expect = int(m.group(1)) body = RES[res['addr'] + 4:res['addr'] + res['size']] names = [clean(x.group(1).decode()) for x in RAWNAME.finditer(body)] names = [n for n in names if n] if len(names) != expect: warn.append('subsystem stream lists %d names, header says %d' % (len(names), expect)) return names return [] # ------------------------------------------------------- mapping streams def control_streams(veh): """The vehicle's ControlsMappings List names its streams by id.""" want = '%s: ControlsMappings List of' % veh found = {} for res in TABLE.values(): if not res['desc'].startswith(want): continue count = struct.unpack_from('