Count occupied restarts as failed missions; sync published Workshop version
continuous-integration/drone/push Build is passing
continuous-integration/drone/push Build is passing
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user