Files
jakub 6a667d8deb Capture current mission, AI, map and administration development
Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
2026-10-13 22:52:53 +02:00

164 lines
9.1 KiB
Python

#!/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())