105 lines
4.8 KiB
Python
105 lines
4.8 KiB
Python
"""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
|