from pathlib import Path import subprocess,sys,base64,shlex,tarfile,io,json,os ROOT=Path('/home/jakub/devel/escapistsrereintroduced');sys.path.insert(0,str(ROOT/'tools')) from source_manifest import addon_manifest name=sys.argv[1];mode=sys.argv[2] if len(sys.argv)>2 else 'both';target='/tmp/erri-compat-'+name buf=io.BytesIO() with tarfile.open(fileobj=buf,mode='w:gz') as tar: for rel in addon_manifest(ROOT): if mode in ('reaper','neither') and not rel.endswith('/ERR_ReaperRHSCompat.c'):continue tar.add(ROOT/rel,arcname='source/'+str(Path(rel).relative_to('addons/core'))) if mode in ('reaper','neither'): project=b'GameProject { ID "ERR_CompatFixture" GUID "2528466DE220D91C" TITLE "Private compatibility fixture" Dependencies { "58D0FB3206B6F859" } }' info=tarfile.TarInfo('source/addon.gproj');info.size=len(project);tar.addfile(info,io.BytesIO(project)) probe=Path('/tmp/erri-compat-container-probe.c').read_bytes() if mode in ('both','rhs'):probe=probe.replace(b'// MISSION_READY',b'if (!ERR_GameMode.Get() || !ERR_GameMode.Get().Active()) return;') info=tarfile.TarInfo('source/Scripts/Game/ERR/00_Core/ERR_ZCompatProbe.c');info.size=len(probe);tar.addfile(info,io.BytesIO(probe)) code='''from pathlib import Path import io,tarfile,base64,subprocess,os,time,signal,json run=Path(TARGET);run.mkdir(mode=0o755) with tarfile.open(fileobj=io.BytesIO(base64.b64decode(PAYLOAD)),mode='r:gz') as t:t.extractall(run) runtime=Path('/home/amp/.ampdata/instances/RKSserver201/arma-reforger/1874900') installed=runtime/'AReforgerMaster/addons';source=run/'source' (source/'resourceDatabase.rdb').write_bytes((installed/'EscapistsReintroduced_2528466DE220D91C/resourceDatabase.rdb').read_bytes()) addons=run/'addons';addons.mkdir();(addons/'ERR_Core').symlink_to(source,target_is_directory=True) allowed=['595F2BF2F44836FB','1337C0DE5DABBEEF','BADC0DEDABBEDA5E','629B2BA37EFFD577','5E389BB9F58B79A6','5E524E4FEECCA92B','65930CB4CD0237B2','5EB139459EBF5C16','61B8FA7B3BF8656B'] for addon in installed.iterdir(): if any(addon.name.endswith('_'+key) for key in allowed):(addons/addon.name).symlink_to(addon,target_is_directory=True) (run/'profile').mkdir() engine=[str(runtime/'ArmaReforgerServer'),'-server',('{C1E20667DCE324ED}Missions/ERR_Everon.conf' if MODE in ('both','rhs') else 'Missions/21_GM_Eden.conf'),'-addonsDir',str(addons),'-addons',('2528466DE220D91C,5EB139459EBF5C16' if MODE in ('both','reaper') else '2528466DE220D91C'),'-profile',str(run/'profile'),'-noBackend','-noSound','-bindIP','127.0.0.1','-bindPort','2001','-maxFPS','20','-logconsole','-noSplash','-noThrow','-disableCrashReporter'] inner="import subprocess,os;subprocess.run(['mount','--make-rprivate','/'],check=True);subprocess.run(['mount','--bind',RUNTIME,RUNTIME],check=True);subprocess.run(['mount','-o','remount,bind,ro',RUNTIME],check=True);os.chdir(RUNTIME);os.nice(15);os.execv(ENGINE[0],ENGINE)".replace('RUNTIME',repr(str(runtime))).replace('ENGINE',repr(engine)) cmd=['unshare','--mount','--net','--pid','--fork','--mount-proc','python3','-c',inner] with (run/'stdout.log').open('w') as log: p=subprocess.Popen(cmd,stdout=log,stderr=subprocess.STDOUT,start_new_session=True);start=time.monotonic();reason='timeout';text='' try: while time.monotonic()-start<150: files=sorted((run/'profile/logs').glob('logs_*/console.log'));text=files[-1].read_text(errors='replace') if files else (run/'stdout.log').read_text(errors='replace') if 'ERR COMPAT COMPLETE' in text:reason='complete';break if "Can't compile" in text:reason='compile-failed';break if p.poll() is not None:reason='exit';break time.sleep(.5) finally: if p.poll() is None: os.killpg(p.pid,signal.SIGTERM) try:p.wait(timeout=8) except subprocess.TimeoutExpired:os.killpg(p.pid,signal.SIGKILL);p.wait() result={'reason':reason,'exit':p.returncode,'log':text,'stdout':(run/'stdout.log').read_text(errors='replace')};(run/'result.json').write_text(json.dumps(result));print(json.dumps(result)) '''.replace('MODE',repr(mode)).replace('TARGET',repr(target)).replace('PAYLOAD',repr(base64.b64encode(buf.getvalue()).decode())) # Feed the Python program through stdin after sudo's password; no large command-line payload. cmd='sudo -S -p "" python3 -' p=subprocess.run(['ssh','-o','ControlPath=/tmp/erri-crash-check/socket','jakub@192.168.52.4',cmd],input=os.environ['ERR_TEST_SSH_PASSWORD']+'\n'+code,text=True,capture_output=True) if p.returncode:print(p.stderr);raise SystemExit(p.returncode) data=json.loads(p.stdout);local=Path('/tmp/erri-compat-results')/name;local.mkdir(parents=True,exist_ok=True) (local/'result.json').write_text(json.dumps(data,indent=2));(local/'console.log').write_text(data['log']) print(data['reason']);print('\n'.join(l for l in data['log'].splitlines() if any(t in l for t in ['ERR COMPAT','SCRIPT (E)','cannot be combined'])))