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-source7');run=Path('/tmp/erri-takeover-ds7');run.mkdir() shutil.copytree('/tmp/erri-takeover-feature-source4',source) (source/'Scripts/Game/ERR/00_Core/ERR_TerrainValidationProbe.c').write_text('class ERR_DedicatedRestartProof\n{\n static bool Requested;\n}\nmodded class ArmaReforgerScripted\n{\n protected float m_ERRRestartDelay = 5;\n override void OnUpdate(BaseWorld world, float timeslice)\n {\n super.OnUpdate(world, timeslice);\n ERR_GameMode mode = ERR_GameMode.Get();\n if (!Replication.IsServer() || !mode || !mode.Active() || ERR_DedicatedRestartProof.Requested) return;\n m_ERRRestartDelay -= timeslice;\n if (m_ERRRestartDelay > 0) return;\n ERR_DedicatedRestartProof.Requested = true;\n PrintFormat("ERR DEDICATED RESTART: accepted=%1", GameStateTransitions.RequestScenarioRestart());\n }\n override protected void OnBeforeWorldCleanup()\n {\n super.OnBeforeWorldCleanup();\n PrintFormat("ERR DEDICATED CLEANUP: stage=%1 worldClear=%2 oldModeClear=%3 elapsed=%4 admissions=%5 airports=%6 slots=%7", m_ERRTerrainStage, m_ERRTerrainWorld == null, m_ERRTerrainOriginal == null, m_ERRTerrainElapsed, m_ERRTerrainAdmissions.Count(), m_ERRTerrainAirfields.Count(), m_ERRTerrainMarkerSlots.Count());\n }\n}\n') 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 s.count('Reintroduced [mission]: phase 0 -> 1') >= 2:reason='mission-started';break if 'ERR DEDICATED RESTART: accepted=0' in s or '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)