#!/usr/bin/env python3 """Prepare reviewable private dedicated-server inputs; never launch or download.""" import argparse import hashlib import json import os from pathlib import Path import re import shutil from source_manifest import addon_manifest APP_ID = '1874900' HEADERS = {'core': 'Missions/ERR_Everon.conf', 'chat': 'Missions/ERR_Everon.conf'} SYSTEMS = '{C50579A2EC48E8EF}Configs/Systems/MissionSystems.conf' WORLD = '{B6C516F86302C679}Worlds/ERR_Everon.ent' def digest(path): value = hashlib.sha256() with path.open('rb') as stream: for chunk in iter(lambda: stream.read(1024 * 1024), b''): value.update(chunk) return value.hexdigest() def selected_scenario(source, project): addon = source / 'addons' / 'core' text = (addon / 'addon.gproj').read_text() guid = re.search(r'\bGUID "([A-F0-9]{16})"', text) if not guid: raise ValueError('Selected addon has no exact GUID') relative = HEADERS[project] header = addon / relative meta = header.with_suffix('.conf.meta').read_text() resource = re.search(r'\bName "(\{[A-F0-9]{16}\}[^"\n]+)"', meta) if not resource or resource[1][18:] != relative: raise ValueError('Selected header resource identity/path mismatch') data = header.read_text() for key, expected in [('World', WORLD), ('SystemsConfig', SYSTEMS)]: match = re.search(r'\b' + key + r' "([^"\n]+)"', data) if not match or match[1] != expected: raise ValueError('Selected header has unexpected ' + key) return {'project': project, 'addon_guid': guid[1], 'header': resource[1], 'world': WORLD, 'systems_config': SYSTEMS, 'addon_directory': str(source / 'addons')} def inspect_runtime(runtime): binary = runtime / 'server' / 'ArmaReforgerServer' manifest = runtime / 'server' / 'steamapps' / ('appmanifest_' + APP_ID + '.acf') result = {'runtime_directory': str(runtime), 'binary': str(binary), 'app_id': APP_ID, 'platform': 'linux', 'branch': 'public', 'binary_present': binary.is_file(), 'binary_elf_x86_64': False, 'binary_executable': False, 'steam_build_id': None, 'install_complete': False, 'engine_version': None, 'native_runtime_executed': False} if manifest.is_file(): values = dict(re.findall(r'"(appid|buildid|StateFlags)"\s+"([^"]*)"', manifest.read_text())) if values.get('appid') != APP_ID: raise ValueError('Runtime manifest is not the stable dedicated-server app') result.update(steam_build_id=values.get('buildid'), install_complete=values.get('StateFlags') == '4', manifest_sha256=digest(manifest)) if binary.is_file(): if binary.is_symlink(): raise ValueError('Private runtime binary must not be a symlink to another installation') with binary.open('rb') as stream: header = stream.read(20) result['binary_elf_x86_64'] = len(header) == 20 and header[:6] == b'\x7fELF\x02\x01' and int.from_bytes(header[18:20], 'little') == 62 result['binary_executable'] = os.access(binary, os.X_OK) result['binary_sha256'] = digest(binary) return result def build_plan(source, runtime, project='core', port=2001): source, runtime = source.resolve(), runtime.resolve() if not 1024 <= port <= 65535: raise ValueError('Use an unprivileged port in 1024..65535') if source == runtime or source in runtime.parents or runtime in source.parents: raise ValueError('Runtime must be separate from the authored source tree') manifest = addon_manifest(source) scenario = selected_scenario(source, project) installation = inspect_runtime(runtime) config = { 'bindAddress': '127.0.0.1', 'bindPort': port, 'publicAddress': '127.0.0.1', 'publicPort': port, 'game': {'name': 'Reintroduced private validation', 'scenarioId': scenario['header'], 'maxPlayers': 2, 'visible': False, 'gameProperties': {'fastValidation': True, 'battlEye': True}, 'mods': []}, 'operating': {'disableCrashReporter': True}, } blockers = [] if not (installation['install_complete'] and installation['binary_elf_x86_64'] and installation['binary_executable']): blockers.append('Stable native Linux dedicated-server installation is missing or incomplete.') blockers.extend([ 'Server binary dependencies and exact engine-version compatibility have not been validated by a native run.', 'Unpublished local-addon loading with authenticated server configuration/header selection needs native verification; do not combine -config and -addons.', 'Loopback server configuration must be reviewed before any listener is started.', 'Two genuine accepted native client identities/controllers are not available as evidence; copied profiles and synthetic actors do not qualify.', 'Existing ordinary runner refuses concurrent game instances; it is not a multi-client supervisor.', ]) result = {'schema': 1, 'scope': 'no-launch dedicated preparation only', 'launch_allowed': False, 'listener_started': False, 'genuine_clients_verified': 0, 'multiplayer_passed': False, 'source_directory': str(source), 'scenario': scenario, 'runtime': installation, 'network': {'bind': '127.0.0.1', 'port': port, 'public_listing': False, 'rcon_configured': False, 'a2s_configured': False}, 'source_manifest_sha256': hashlib.sha256((json.dumps(manifest, indent=2, sort_keys=True) + '\n').encode()).hexdigest(), 'disk_free_bytes': shutil.disk_usage(runtime if runtime.exists() else runtime.parent).free, 'blockers': blockers, 'evidence_required': [ 'Server and both clients: exact version, role, source/addon manifest and complete sealed raw logs.', 'Server: native dedicated mode, selected header and actual MissionSystems config, two distinct accepted connections/current controlled prisoners.', 'Client1 and Client2: separate native ownership/local controller and actual replicated state; record no account credentials.', 'Both clients: JIP, same-account reconnect/reservation expiry, death/unconsciousness, spectator and extraction lifecycle.', 'No synthetic-client substitution and no multiplayer/FPS claim from headless startup alone.', ]} return result, config, manifest def write_plan(destination, result, config, manifest): destination = destination.absolute() runtime = Path(result['runtime']['runtime_directory']) if destination.is_symlink() or destination.exists(): raise ValueError('Plan directory must be new; existing evidence is never overwritten') if not destination.parent.is_dir() or destination.parent.resolve() != runtime: raise ValueError('Plan directory must be a new direct child of the private runtime') marker = runtime / 'runtime-origin.json' if not marker.is_file() or marker.is_symlink(): raise ValueError('Private runtime origin marker is required before writing plans') origin = json.loads(marker.read_text()) if origin.get('app_id') != int(APP_ID) or origin.get('purpose') != 'private anonymous Reforger dedicated server preparation; no server launched': raise ValueError('Runtime origin marker is not the authorized private preparation') destination.mkdir(mode=0o700) for filename, value in [('preflight.json', result), ('server-config.review.json', config), ('source-manifest.json', manifest)]: (destination / filename).write_text(json.dumps(value, indent=2, sort_keys=True) + '\n') # A human-readable plan is deliberately not an executable launch script. (destination / 'REVIEW.txt').write_text('NO SERVER STARTED. DO NOT LAUNCH BEFORE CONFIGURATION REVIEW.\n' 'server-config.review.json binds loopback and preserves native fast validation/BattlEye.\n' 'Local unpublished-addon/config compatibility, engine dependencies/version and two genuine clients remain unverified.\n' 'Do not add -addons to a -config command or treat this plan as runtime/MP evidence.\n') def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--source', type=Path, required=True) parser.add_argument('--runtime', type=Path, default=Path('/tmp/erri-dedicated-runtime')) parser.add_argument('--project', choices=HEADERS, default='core') parser.add_argument('--port', type=int, default=2001) parser.add_argument('--write-plan', type=Path, help='Write review files into a NEW direct child of the private runtime; never launch') args = parser.parse_args() try: result, config, manifest = build_plan(args.source, args.runtime, args.project, args.port) if args.write_plan: write_plan(args.write_plan, result, config, manifest) print(json.dumps(result, indent=2, sort_keys=True)) return 0 # success means inspection completed, never launch/MP qualification except (OSError, ValueError) as error: parser.error(str(error)) if __name__ == '__main__': raise SystemExit(main())