Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
55 lines
4.7 KiB
Plaintext
55 lines
4.7 KiB
Plaintext
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'])))
|