Count occupied restarts as failed missions; sync published Workshop version
continuous-integration/drone/push Build is passing

This commit is contained in:
2026-11-07 01:05:43 +01:00
parent 161100cf08
commit e421dcfc15
14 changed files with 640 additions and 50 deletions
+104
View File
@@ -0,0 +1,104 @@
"""Query the public Workshop catalogue using a disposable, silent game session."""
import fcntl
import os
from pathlib import Path
import re
import subprocess
import tempfile
import time
from headless_game import assert_prefix_idle, cleanup_owned_run
SCRIPT = '''class ERR_VersionQuery : PageParams
{
override void OnPack() { StoreString("search", "Co10 escape Reintroduced"); }
}
modded class SCR_AddonManager
{
protected ref ERR_VersionQuery m_Query;
protected ref BackendCallback m_Callback;
override void EOnInit(IEntity owner)
{
super.EOnInit(owner);GetGame().GetCallqueue().CallLater(QueryVersion,10000,false);
}
void QueryVersion()
{
m_Query=new ERR_VersionQuery();m_Query.limit=100;m_Query.offset=0;
m_Callback=new BackendCallback();m_Callback.SetOnSuccess(VersionReady);
GetGame().GetBackendApi().GetWorkshop().RequestPage(m_Callback,m_Query,true);
}
void VersionReady(BackendCallback callback)
{
array<WorkshopItem> items={};GetGame().GetBackendApi().GetWorkshop().GetPageItems(items);
foreach(WorkshopItem item:items)
{
if(item.Id()!="2528466DE220D91C")continue;
Revision revision=item.GetLatestRevision();
if(revision)PrintFormat("ERR_WORKSHOP_PUBLISHED %1 %2",item.Id(),revision.GetVersion());
}
}
}
'''
def published_from_log(text, guid):
versions = set(re.findall(r'\bERR_WORKSHOP_PUBLISHED ' + re.escape(guid) +
r' (\d+\.\d+\.\d+)\s*$', text, re.MULTILINE))
if len(versions) > 1:
raise ValueError('Workshop returned conflicting published versions.')
return next(iter(versions), None)
def native_version(root, compat, guid):
if guid != '2528466DE220D91C':
raise ValueError('Native query is restricted to the Escape addon.')
compat = Path(compat).resolve()
steam = Path.home() / '.local/share/Steam'
if not compat.is_relative_to(root / '.local') or not (compat / 'pfx').is_dir():
raise ValueError('Use an existing disposable compat prefix under this checkout/.local.')
proton = steam / 'steamapps/common/Proton - Experimental/proton'
game = steam / 'steamapps/common/Arma Reforger/ArmaReforgerSteam.exe'
if not proton.is_file() or not game.is_file():
raise ValueError('Installed Reforger and Proton Experimental are required for the native query.')
with (compat / '.erri-workbench.lock').open('a') as lock:
fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
assert_prefix_idle(compat)
scratch = root / '.local/workshop-version'
scratch.mkdir(parents=True, exist_ok=True)
with tempfile.TemporaryDirectory(prefix='query-', dir=scratch) as temp:
work = Path(temp)
addon = work / 'addons/query'
(addon / 'Scripts/Game').mkdir(parents=True)
(addon / 'addon.gproj').write_text('GameProject { ID "ERR_VersionQuery" GUID "8F729DB72D635EF1" Dependencies { "58D0FB3206B6F859" } }\n')
(addon / 'Scripts/Game/VersionQuery.c').write_text(SCRIPT)
profile = work / 'profile'
profile.mkdir()
env = os.environ.copy()
env.update(STEAM_COMPAT_DATA_PATH=str(compat), STEAM_COMPAT_CLIENT_INSTALL_PATH=str(steam),
WINEPREFIX=str(compat / 'pfx'), SteamAppId='1874880', SteamGameId='1874880')
command = ['gamescope', '--backend', 'headless', '-W', '1280', '-H', '720', '-r', '30', '--',
str(proton), 'run', str(game), '-profile', 'Z:' + str(profile),
'-addonDownloadDir', 'Z:' + str(profile), '-addonsDir', 'Z:' + str(work / 'addons'),
'-addons', '8F729DB72D635EF1', '-noSound', '-noSplash', '-noThrow',
'-disableCrashReporter', '-logconsole']
version = None
with (work / 'launch.log').open('w') as output:
child = subprocess.Popen(command, cwd=game.parent, env=env, stdout=output,
stderr=subprocess.STDOUT, start_new_session=True)
try:
deadline = time.monotonic() + 60
while child.poll() is None and time.monotonic() < deadline:
for log in (profile / 'logs').glob('*/console.log'):
version = published_from_log(log.read_text(errors='replace'), guid)
if version:
break
if version:
break
time.sleep(.5)
finally:
cleanup = cleanup_owned_run(child, proton, env, compat)
if not cleanup.get('cleanup_confirmed'):
raise ValueError('Workshop query process cleanup could not be confirmed.')
if not version:
raise ValueError('Live Workshop query did not return a published version within 60 seconds.')
return version