From 96da34a63345345d676b29192a4b5e775ae53cab Mon Sep 17 00:00:00 2001 From: lucannez64 <58551020+lucannez64@users.noreply.github.com> Date: Thu, 15 Oct 2026 22:11:26 +0200 Subject: [PATCH] Add an update script that re-applies changed patches After a pull that changed a patch, prepare.py stopped: the patch neither applied nor reverse-applied to a tree holding its earlier version. Now it looks for the earlier version applied in that component -- the copy it records in build/applied-patches, or a version from this repository's history -- reverts exactly it and applies the new one. It never resets a repository; when nothing matches it stops and names the repository with other edits, and it restores the earlier version if the new one does not apply. scripts/update.sh runs build.sh, the base install and install-runtime.sh, and refuses to start while a Riot Client or League process is open, since the install steps stop Darling. Checked on a sandbox Indium checkout holding the previous patch: the result matches a clean apply, a second run is a no-op, an unrelated local edit survives and a conflicting one stops with the error. Co-Authored-By: Claude Opus 5.5 --- README.md | 12 +++++++++++- docs/SETUP.md | 2 ++ scripts/prepare.py | 30 ++++++++++++++++++++++++++++-- scripts/update.sh | 29 +++++++++++++++++++++++++++++ 4 files changed, 70 insertions(+), 3 deletions(-) create mode 100755 scripts/update.sh diff --git a/README.md b/README.md index e4a0b48..8e8475d 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,17 @@ FPS counters, game presentation requests and physical display refresh are differ On the Ryzen 5 5500 / RX 6600 test PC, ARAM on Hexgate went from 84 to 135 FPS average over 2026-10-13/14, with the slowest 10-second stretches around 119 in the best match (one match so far with Hyprland's direct scanout off; [status](docs/STATUS.md#game-fps-2026-10-1314)). The game-process speed switches and their opt-out files are listed in [setup](docs/SETUP.md#game-process-switches), and passive match capture is described in [testing](docs/TESTING.md#passive-match-capture). -For existing installations, [incremental update instructions](docs/SETUP.md#updating-an-existing-runtime) explain the server and memory installers, user-owned backups and the full Darling restart needed to activate replacements. Perform activation only after manually exiting the game and both clients. +To update an existing installation, exit the game and both clients, then: + +```sh +git pull +./scripts/update.sh +sh scripts/run.sh +``` + +`update.sh` re-applies the patches that changed (reverting the earlier version of each, so other edits in the source tree survive), rebuilds incrementally, and reinstalls the base runtime and the per-user overrides. It refuses to run while a Riot or League process is open. + +For updating single components, [incremental update instructions](docs/SETUP.md#updating-an-existing-runtime) explain the server and memory installers, user-owned backups and the full Darling restart needed to activate replacements. Perform activation only after manually exiting the game and both clients. ## Layout diff --git a/docs/SETUP.md b/docs/SETUP.md index 9eb3fc0..660a29c 100644 --- a/docs/SETUP.md +++ b/docs/SETUP.md @@ -28,6 +28,8 @@ The normal launcher starts neither an Electron inspector nor client API/UI helpe ## Updating an existing runtime +`./scripts/update.sh` updates everything after `git pull`: `build.sh`, then the base install, then `install-runtime.sh`. When a pull changes a patch, `prepare.py` finds the earlier version applied in that component (its copy in `build/applied-patches`, or a version from this repository's history), reverts it, and applies the new one. It never resets a repository; if neither version matches, it stops and names the repository with other edits. The steps below update single components instead. + These commands assume the pinned, patched source tree and its configured Makefiles already exist. From the repository root, build the two components with one worker to reduce resource contention: ```sh diff --git a/scripts/prepare.py b/scripts/prepare.py index 5b0e761..0e8bc72 100755 --- a/scripts/prepare.py +++ b/scripts/prepare.py @@ -8,6 +8,21 @@ p.add_argument('--source',type=Path,default=ROOT/'build/darling-src') p.add_argument('--check-only',action='store_true',help='validate against temporary base indexes, without changing existing source') a=p.parse_args();source=a.source.resolve();manifest=json.loads((ROOT/'upstream/manifest.json').read_text()) def run(args,**kwargs):return subprocess.run(args,check=True,**kwargs) +def applies(repo,patch,reverse=False): + return subprocess.run(['git','-C',str(repo),'apply',*(['--reverse'] if reverse else []),'--check','-'],input=patch,text=True, + stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL).returncode==0 +# The rendered text of each patch as last applied, so a later version can revert exactly it. +applied=ROOT/'build/applied-patches' +def record_patch(component,patch): + applied.mkdir(parents=True,exist_ok=True);(applied/Path(component['patch']).name).write_text(patch) +def previous_patches(component): + """Earlier applied versions of a component's patch: the recorded copy, then each version in this repository's history.""" + recorded=applied/Path(component['patch']).name + if recorded.exists():yield recorded.read_text() + history=subprocess.run(['git','-C',str(ROOT),'log','--format=%H','--',component['patch']],text=True,capture_output=True).stdout.split() + for commit in history: + shown=subprocess.run(['git','-C',str(ROOT),'show',f"{commit}:{component['patch']}"],text=True,capture_output=True) + if shown.returncode==0:yield render(shown.stdout) if not (source/'.git').exists(): if a.check_only:p.error('check-only requires an existing pinned source checkout') source.parent.mkdir(parents=True,exist_ok=True) @@ -35,9 +50,20 @@ for component in manifest['components']: run(['git','-C',str(repo),'read-tree',component['revision']],env=env) run(['git','-C',str(repo),'apply','--cached','--check','--whitespace=nowarn','-'],input=patch,text=True,env=env) else: - already=subprocess.run(['git','-C',str(repo),'apply','--reverse','--check','-'],input=patch,text=True,stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL).returncode==0 + already=applies(repo,patch,reverse=True) + if not already and not applies(repo,patch): + # An older version of this patch is applied (the checkout predates a pull). Revert exactly that + # version, never a reset, so other edits in the repository survive. + old=next((text for text in previous_patches(component) if applies(repo,text,reverse=True)),None) + if old is None:raise SystemExit(f"{component['path']}: {component['patch']} neither applies nor matches an earlier applied version; " + f"the checkout has other edits. Commit or discard them in {repo}, then run this again.") + print(f"Replacing the earlier {component['patch']} in {component['path']}",flush=True) + run(['git','-C',str(repo),'apply','--reverse','--whitespace=nowarn','-'],input=old,text=True) + if not applies(repo,patch): + run(['git','-C',str(repo),'apply','--whitespace=nowarn','-'],input=old,text=True) + raise SystemExit(f"{component['path']}: the new {component['patch']} does not apply to the pinned revision; the earlier version was restored") if not already: - run(['git','-C',str(repo),'apply','--check','-'],input=patch,text=True) run(['git','-C',str(repo),'apply','--whitespace=nowarn','-'],input=patch,text=True) + record_patch(component,patch) print(('Checked ' if a.check_only else 'Prepared ')+component['path'],flush=True) print('All',len(manifest['components']),'component patches validated' if a.check_only else 'component patches ready') diff --git a/scripts/update.sh b/scripts/update.sh new file mode 100755 index 0000000..5658d37 --- /dev/null +++ b/scripts/update.sh @@ -0,0 +1,29 @@ +#!/bin/sh +# Bring an existing build and installation up to date after `git pull`: re-apply the patches that changed +# (prepare.py reverts the earlier version it finds applied), rebuild incrementally, and reinstall the base +# runtime and the per-user overrides. Run it with no Riot/League session open; it never stops one. +set -eu +repo=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +cd "$repo" +if [ ! -d build/darling-src/build ]; then + echo 'No existing build; follow the first-time setup in README.md instead.' >&2 + exit 1 +fi +# The install steps shut Darling down and stop leftover Riot processes, so refuse rather than end a session. +python3 - <<'PY' +from pathlib import Path +for entry in Path('/proc').iterdir(): + if not entry.name.isdigit(): + continue + try: + command_line = (entry / 'cmdline').read_bytes() + except OSError: + continue + if b'/Riot Client.app/' in command_line or b'/League of Legends.app/' in command_line: + raise SystemExit('A Riot Client or League process is running; exit the game and both clients, then run this again.') +PY +./scripts/build.sh +python3 build/runtime/tools/stop-darling-runtime.py +sudo cmake --install build/darling-src/build +./scripts/install-runtime.sh +echo 'Update installed. Start Darling with: sh scripts/run.sh'