mirror of
https://github.com/lucannez64/darling-lol.git
synced 2026-10-27 19:54:38 +00:00
Game process (ARAM Hexgate: 84 -> 127 FPS average, slowest stretches ~110): - Local pthread mutexes, condition variables and Mach semaphores are on by default in the game process only, with /tmp/rclol-no-local-* opt-outs. The main thread's darlingserver waits fell from 1.36 to 0.10 ms per frame. - Local semaphores: the count and the slot generation change in one compare-exchange, a destroy leaves a negative marker, the table is reset after fork, and names the table did not create go to the server. - Metal: drop identical re-binds (keyed on the Indium object, depth-stencil retained), pass blit resources by reference, and stop resetting vertex descriptors twice. - Core isolation of the main thread is opt-in (/tmp/rclol-core-isolation); it lowered FPS in its only match. - Optimized libc/libc++/Foundation overlays, AVX2 string primitives, real CPU topology and their installers and offline checks. Tools and docs: - tools/riot-match-capture: passive /proc sampler, user-space IP profiler and reports, plus a comparison against the game's netstats. - tools/riot-lsem-model and tools/riot-metal-bind-model: host models that extract the shipped code and check it. - tools/install-riot-metal-pair.sh installs Metal and Indium together. - Status, setup, testing and troubleshooting docs updated with the measured matches and the switch table; manifest file counts match the patches. Co-Authored-By: Claude Opus 5.5 <[email protected]>
214 lines
10 KiB
Python
214 lines
10 KiB
Python
#!/usr/bin/env python3
|
|
"""Install the optimized libc, libc++ and libsystem_platform overlays.
|
|
|
|
The guest finds a library by leaf name in DYLD_LIBRARY_PATH before its install
|
|
path (dyld3/PathOverrides.cpp:forEachPathVariant), so a file in the per-user
|
|
RuntimeLibraries directory replaces the copy in the prefix and in the installed
|
|
SDK without touching either. The overlays installed here are exactly the
|
|
artifacts built by tools/build-riot-*.py; each is staged next to its
|
|
destination, verified against the built file, and renamed over the old one
|
|
after the old one is backed up. --rollback restores every file this installer
|
|
replaced and removes the ones it created.
|
|
|
|
This installer was checked offline (tools/riot-libs-installer-offline-check.py)
|
|
and is never run against the real prefix by the build. Nothing is activated
|
|
until the guest processes are restarted.
|
|
"""
|
|
import argparse
|
|
import hashlib
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import shutil
|
|
import stat
|
|
import tempfile
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
|
|
# (built artifact, file name in the guest's RuntimeLibraries directory). The
|
|
# artifacts are the ones tools/build-riot-libs-runtime.sh builds.
|
|
# libsystem_m is deliberately absent: its C sources are built at -O0 upstream and an
|
|
# -O2 rebuild made the Riot client UI render a black window (2026-10-13).
|
|
LIBRARIES = [
|
|
("build/riot-libsystem_c.dylib", "libsystem_c.dylib"),
|
|
("build/riot-libc++.1.dylib", "libc++.1.dylib"),
|
|
("build/riot-libc++abi.dylib", "libc++abi.dylib"),
|
|
("build/riot-libsystem_platform.dylib", "libsystem_platform.dylib"),
|
|
]
|
|
|
|
|
|
def digest(path):
|
|
with path.open("rb") as input:
|
|
return hashlib.file_digest(input, "sha256").hexdigest()
|
|
|
|
|
|
def destinations(prefix, guest_home):
|
|
folder = Path(prefix) / guest_home.lstrip("/") / "RuntimeLibraries"
|
|
return [(ROOT / artifact, folder / name) for artifact, name in LIBRARIES]
|
|
|
|
|
|
def existing_records(backup_folder):
|
|
"""What the previous installation recorded, so a second run keeps the
|
|
state from before the first one instead of overwriting it."""
|
|
manifest = backup_folder / "manifest.json"
|
|
if not manifest.is_file():
|
|
return {}
|
|
return {record["path"]: record for record in json.loads(manifest.read_text())["libraries"]}
|
|
|
|
|
|
def install(libraries, backup_folder):
|
|
previous_records = existing_records(backup_folder)
|
|
expected = {}
|
|
for artifact, destination in libraries:
|
|
if not artifact.is_file():
|
|
raise ValueError("The artifact does not exist: " + str(artifact))
|
|
with artifact.open("rb") as input:
|
|
if input.read(4) not in (b"\xcf\xfa\xed\xfe", b"\xce\xfa\xed\xfe"):
|
|
raise ValueError("Expected a 64-bit Mach-O library: " + str(artifact))
|
|
expected[destination] = digest(artifact)
|
|
records, staged = [], []
|
|
try:
|
|
for index, (artifact, destination) in enumerate(libraries):
|
|
earlier = previous_records.get(str(destination))
|
|
current = digest(destination) if destination.is_file() else None
|
|
if current == expected[destination]:
|
|
# keep the state from before the first installation, if any
|
|
records.append({"path": str(destination), "already_current": True,
|
|
"existed": earlier["existed"] if earlier else destination.is_file(),
|
|
"previous_sha256": earlier["previous_sha256"] if earlier else current,
|
|
"backup": earlier.get("backup") if earlier else None,
|
|
"sha256": expected[destination]})
|
|
continue
|
|
existed = earlier["existed"] if earlier else destination.is_file()
|
|
previous = earlier["previous_sha256"] if earlier else current
|
|
backup = Path(earlier["backup"]) if earlier and earlier.get("backup") else None
|
|
if backup is None and current is not None:
|
|
backup_folder.mkdir(parents=True, exist_ok=True)
|
|
backup = backup_folder / ("%d-%s.dylib" % (index, current))
|
|
if backup.exists():
|
|
if digest(backup) != current:
|
|
raise ValueError("The existing backup has unexpected contents: " + str(backup))
|
|
else:
|
|
shutil.copy2(destination, backup)
|
|
metadata = destination.stat() if destination.is_file() else None
|
|
descriptor, temporary = tempfile.mkstemp(prefix="." + destination.name + "-staged-",
|
|
dir=destination.parent)
|
|
staged.append((Path(temporary), destination))
|
|
with os.fdopen(descriptor, "wb") as output, artifact.open("rb") as input:
|
|
shutil.copyfileobj(input, output)
|
|
output.flush()
|
|
if metadata is not None:
|
|
os.fchmod(output.fileno(), stat.S_IMODE(metadata.st_mode))
|
|
os.fchown(output.fileno(), metadata.st_uid, metadata.st_gid)
|
|
else:
|
|
os.fchmod(output.fileno(), 0o755)
|
|
os.fsync(output.fileno())
|
|
if digest(Path(temporary)) != expected[destination]:
|
|
raise ValueError("The staged library does not match the build: " + str(artifact))
|
|
records.append({"path": str(destination), "backup": str(backup) if backup else None,
|
|
"existed": existed, "sha256": expected[destination],
|
|
"previous_sha256": previous})
|
|
for temporary, destination in staged:
|
|
os.replace(temporary, destination)
|
|
directory = os.open(destination.parent, os.O_DIRECTORY)
|
|
try:
|
|
os.fsync(directory)
|
|
finally:
|
|
os.close(directory)
|
|
for record in records:
|
|
path = Path(record["path"])
|
|
if not path.is_file() or digest(path) != record["sha256"]:
|
|
raise ValueError("Installed library verification failed: " + str(path))
|
|
backup_folder.mkdir(parents=True, exist_ok=True)
|
|
manifest = backup_folder / "manifest.json"
|
|
manifest.write_text(json.dumps({"libraries": records}, indent=1) + "\n")
|
|
finally:
|
|
for temporary, _ in staged:
|
|
if temporary.exists():
|
|
temporary.unlink()
|
|
return {"installed": True, "libraries": records, "restart_performed": False,
|
|
"manifest": str(backup_folder / "manifest.json"),
|
|
"activation": "Exit the game and the clients, then start them again; a running guest keeps the library it already loaded."}
|
|
|
|
|
|
def rollback(backup_folder):
|
|
manifest = backup_folder / "manifest.json"
|
|
if not manifest.is_file():
|
|
raise ValueError("No installation manifest in " + str(backup_folder))
|
|
records = json.loads(manifest.read_text())["libraries"]
|
|
restored, removed = [], []
|
|
for record in reversed(records):
|
|
destination = Path(record["path"])
|
|
if record.get("previous_sha256") is None:
|
|
# the installer created this file; removing it restores the SDK copy
|
|
if destination.is_file() and digest(destination) == record["sha256"]:
|
|
destination.unlink()
|
|
removed.append(str(destination))
|
|
else:
|
|
backup = Path(record["backup"])
|
|
if not backup.is_file():
|
|
raise ValueError("The backup is missing: " + str(backup))
|
|
if digest(backup) != record["previous_sha256"]:
|
|
raise ValueError("The backup has unexpected contents: " + str(backup))
|
|
descriptor, temporary = tempfile.mkstemp(prefix="." + destination.name + "-restore-",
|
|
dir=destination.parent)
|
|
with os.fdopen(descriptor, "wb") as output, backup.open("rb") as input:
|
|
shutil.copyfileobj(input, output)
|
|
output.flush()
|
|
os.fchmod(output.fileno(), 0o755)
|
|
os.fsync(output.fileno())
|
|
os.replace(temporary, destination)
|
|
restored.append(str(destination))
|
|
return {"rolled_back": True, "restored": restored, "removed": removed,
|
|
"activation": "Exit the game and the clients, then start them again."}
|
|
|
|
|
|
def parse_arguments(argv=None):
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--prefix", type=Path, default=None,
|
|
help="guest prefix (default: build/config.json)")
|
|
parser.add_argument("--guest-home", default=None)
|
|
parser.add_argument("--backup-dir", type=Path, default=ROOT / "build/libs-runtime-backups")
|
|
parser.add_argument("--only", default=None,
|
|
help="comma-separated library names to install instead of all of them "
|
|
"(to isolate a faulty overlay; run --rollback before changing the set)")
|
|
parser.add_argument("--rollback", action="store_true",
|
|
help="restore every file this installer replaced and remove the ones it created")
|
|
return parser, parser.parse_args(argv)
|
|
|
|
|
|
def main(argv=None):
|
|
parser, arguments = parse_arguments(argv)
|
|
if arguments.rollback:
|
|
try:
|
|
print(json.dumps(rollback(arguments.backup_dir)))
|
|
except (ValueError, OSError) as error:
|
|
parser.exit(1, str(error) + "\n")
|
|
return
|
|
config = json.loads((ROOT / "build/config.json").read_text())
|
|
prefix = arguments.prefix or config["prefix"]
|
|
guest_home = arguments.guest_home or config["guest_home"]
|
|
libraries = destinations(prefix, guest_home)
|
|
if arguments.only:
|
|
wanted = set(arguments.only.split(","))
|
|
unknown = wanted - {name for _, name in LIBRARIES}
|
|
if unknown:
|
|
parser.exit(1, "Unknown library: " + ", ".join(sorted(unknown)) + "\n")
|
|
libraries = [(artifact, destination) for artifact, destination in libraries
|
|
if destination.name in wanted]
|
|
for artifact, _ in libraries:
|
|
if not artifact.is_file():
|
|
parser.exit(1, "The library to install does not exist: " + str(artifact)
|
|
+ "; build it first: sh build/runtime/tools/build-riot-libs-runtime.sh\n")
|
|
try:
|
|
result = install(libraries, arguments.backup_dir)
|
|
except PermissionError as error:
|
|
parser.exit(1, "Cannot write " + str(error.filename) + "; choose a writable --backup-dir or run "
|
|
"the installer with sudo.\n")
|
|
result["prefix"] = str(prefix)
|
|
print(json.dumps(result))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|