from pathlib import Path import sys,os,json,subprocess,time,shutil,signal,hashlib ROOT=Path('/home/jakub/devel/escapistsrereintroduced');sys.path.insert(0,str(ROOT/'tools')) from external_addons import policy,prepare_profile runtime=Path('/tmp/erri-takeover-dedicated-runtime');source=Path('/tmp/erri-takeover-ds-source6');run=Path('/tmp/erri-takeover-ds6');run.mkdir() shutil.copytree('/tmp/erri-takeover-feature-source4',source) (source/'Scripts/Game/ERR/00_Core/ERR_TerrainValidationProbe.c').write_text('''modded class ArmaReforgerScripted { protected override void OnWorldPostProcess(World world) { SCR_MissionHeader header = SCR_MissionHeader.Cast(GetMissionHeader()); if (header) PrintFormat("ERR TERRAIN DEDICATED PROBE: dedicated=%1 enabled=%2 airports=%3 saves=%4", RplSession.Mode() == RplMode.Dedicated, header.m_bERR_EnableEscape, header.m_sERR_Airfields, header.m_eSaveTypes); super.OnWorldPostProcess(world); } } ''') shutil.copy2(ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_TerrainTakeoverSettings.c',source/'Scripts/Game/ERR/00_Core/ERR_TerrainTakeoverSettings.c') steam=Path.home()/'.local/share/Steam';game=steam/'steamapps/common/Arma Reforger';addons=run/'addons';addons.mkdir() prepare_profile(addons,steam/'steamapps/compatdata/1874880/pfx/drive_c/users/steamuser/Documents/My Games/ArmaReforger/addons',policy(ROOT),run/'external-cache.json') (addons/'ERR_Core').symlink_to('/tmp/source',target_is_directory=True) (addons/'Nogova').symlink_to('/home/jakub/devel/gabreta/addons/NogovaReforger',target_is_directory=True) if not (runtime/'addons').exists():(runtime/'addons').symlink_to(game/'addons',target_is_directory=True) for name in ('profile','home','tmp'):(run/name).mkdir() config={'bindAddress':'127.0.0.1','bindPort':22991,'game':{'name':'Private terrain takeover test','password':'private-local-probe','scenarioId':'{469880DFE1B48F95}Missions/Nogova_TerrainTest.conf','maxPlayers':8,'visible':False,'supportedPlatforms':['PLATFORM_PC'],'gameProperties':{'battlEye':False,'missionHeader':{'m_bERR_EnableEscape':True,'m_sERR_Airfields':'1525 5075','m_eSaveTypes':0}}}} (run/'server.json').write_text(json.dumps(config,indent=2)) exe=['/tmp/runtime/ArmaReforgerServer','-server','{469880DFE1B48F95}Missions/Nogova_TerrainTest.conf','-errTerrainTakeover','1','-errAirfields','1525 5075','-profile','/tmp/work/profile','-addonsDir','/tmp/work/addons','-addons','2528466DE220D91C,70C981A53E02B64F','-noBackend','-bindIP','127.0.0.1','-bindPort','22991','-maxFPS','30','-logconsole','-noSound','-noSplash','-noThrow','-disableCrashReporter'] from dedicated_native import PROBE (run/'network_probe.py').write_text(PROBE) cmd=['bwrap','--unshare-net','--unshare-pid','--unshare-ipc','--unshare-uts','--die-with-parent','--cap-drop','ALL','--clearenv','--ro-bind','/','/','--tmpfs','/tmp','--proc','/proc','--dev','/dev','--ro-bind',str(runtime),'/tmp/runtime','--bind',str(source),'/tmp/source','--bind',str(run),'/tmp/work','--setenv','HOME','/tmp/work/home','--setenv','TMPDIR','/tmp/work/tmp','--setenv','PATH','/usr/bin:/bin','--setenv','LANG','C.UTF-8','--chdir','/tmp/runtime','/usr/bin/python3','/tmp/work/network_probe.py',os.readlink('/proc/self/ns/net'),*exe] (run/'command.json').write_text(json.dumps(cmd,indent=2)) (run/'source-before.json').write_text(json.dumps({str(p.relative_to(source)):hashlib.sha256(p.read_bytes()).hexdigest() for p in source.rglob('*') if p.is_file() and p.name!='resourceDatabase.rdb'},indent=2)) child=None;started=time.time();reason='deadline' with (run/'stdout.log').open('w') as log: try: child=subprocess.Popen(cmd,stdout=log,stderr=subprocess.STDOUT,start_new_session=True) while time.time()-started<180: if child.poll() is not None:reason='process-exit';break logs=sorted((run/'profile/logs').glob('logs_*/console.log')) if logs: s=logs[-1].read_text(errors='replace') if 'Reintroduced [mission]: phase 0 -> 1' in s:reason='mission-started';break if 'terrain takeover failed' in s or "Can't compile" in s or 'Unable to initialize the game' in s:reason='startup-failed';break time.sleep(1) finally: if child and child.poll() is None: os.killpg(child.pid,signal.SIGTERM) try:child.wait(timeout=10) except subprocess.TimeoutExpired:os.killpg(child.pid,signal.SIGKILL);child.wait(timeout=5) result={'reason':reason,'exit_code':child.returncode if child else None,'supervisor_reaped':child is not None and child.poll() is not None,'seconds':round(time.time()-started,2)} (run/'result.json').write_text(json.dumps(result,indent=2));print(result) print(run)