mirror of
https://github.com/lucannez64/darling-lol.git
synced 2026-10-26 19:47:42 +00:00
Merge remote-tracking branch 'origin/main' into pr-fix-60
# Conflicts: # docs/TROUBLESHOOTING.md # tools/riot-launcher-offline-check.py
This commit is contained in:
commit
a31753c548
86 files changed
+9623
-643
No files matched your search
@@ -0,0 +1,76 @@
|
||||
# Scope and compliance guidance
|
||||
|
||||
This project is an unofficial compatibility layer for running the macOS League of Legends
|
||||
client on Linux through Darling. It is not affiliated with, endorsed by, or sponsored by Riot
|
||||
Games, Inc. See [interoperability and compliance scope](INTEROPERABILITY.md) for the project's
|
||||
practices on interoperability, copyright, access controls and trademarks. Neither document is
|
||||
legal advice.
|
||||
|
||||
## Scope
|
||||
|
||||
- The project is about compatibility: making the client run, render and behave correctly on
|
||||
Linux.
|
||||
- It does not aim to circumvent DRM, anti-cheat, licensing, telemetry, authentication, or other
|
||||
platform security controls, and it does not distribute a game client, game assets, or account
|
||||
data.
|
||||
- It does not forge authentication tokens or server-side integrity attestations. The synthetic
|
||||
platform identity is local compatibility metadata, not an Apple-issued identifier or a Riot
|
||||
credential. This repository does not pass it as Riot authentication data; the official
|
||||
client's later handling of guest-visible values is outside this repository's scope.
|
||||
- Do not describe or present the project as a tool for evading detection, preventing bans,
|
||||
bypassing DRM, or defeating anti-cheat. Do not promise that an account will not be banned.
|
||||
|
||||
## Terminology
|
||||
|
||||
Prefer accurate compatibility language:
|
||||
|
||||
| Prefer | Avoid |
|
||||
|---|---|
|
||||
| compatibility issue, client check, platform identity | detection vector, stealth, evasion, bypass |
|
||||
| hardware/platform reporting | spoofing, telemetry leak |
|
||||
| runtime/launcher fix | anti-cheat bypass |
|
||||
|
||||
Technical facts can be described precisely without framing the work as evasion. If a change
|
||||
touches a client check or a platform identity, say what it does and why it is needed for
|
||||
compatibility.
|
||||
|
||||
## Known local limitations
|
||||
|
||||
- Darling has no macOS MAC/Seatbelt/AMFI framework. The compatibility layer reports the local
|
||||
`___mac_syscall` probe as satisfied so Chromium/CEF can start. This does **not** implement a
|
||||
sandbox and does not make the guest secure. Treat the guest as unsandboxed and do not run
|
||||
untrusted content. It does not interact with Riot's servers or bypass a Riot control.
|
||||
- Platform-identity values generated by `tools/configure-darling-platform.py` are synthetic and
|
||||
local. They are not Apple-issued identifiers, credentials or authentication tokens; do not use
|
||||
them with Apple services or to impersonate real hardware.
|
||||
- The opt-in hide helper manages local client windows while a match runs. The opt-in close helper
|
||||
terminates eligible League client UI processes. Neither helper patches client binaries, CEF,
|
||||
anti-cheat or DRM, and both helpers are off by default.
|
||||
|
||||
## Assets and third-party material
|
||||
|
||||
- Do not add or link third-party game assets: logos, icons, fonts, screenshots, gameplay
|
||||
audio/video, client files, game data, or account data.
|
||||
- Project icons and artwork must be original. Forks must use their own name and icon; see the
|
||||
trademark notice in `README.md` and `NOTICE.md`.
|
||||
- Do not commit local prefixes, logs, tokens, captures, or compiled client/runtime binaries.
|
||||
- Keep upstream copyright, license and attribution notices.
|
||||
- Donations are voluntary and support the free tooling. They do not sell the game, a license,
|
||||
access or priority.
|
||||
|
||||
## Pull requests and issues
|
||||
|
||||
- Describe the compatibility problem and the fix. Avoid "stealth", "spoof", "evade",
|
||||
"undetectable", or "unbannable" framing.
|
||||
- Maintainers may ask for wording changes before merging. This is about keeping the project's
|
||||
scope accurate, not about hiding technical details.
|
||||
- Contributions that add third-party assets or present the project as a DRM/anti-cheat
|
||||
circumvention tool are out of scope for this repository.
|
||||
- Sign off commits under the Developer Certificate of Origin; see
|
||||
[`CONTRIBUTING.md`](../CONTRIBUTING.md). By contributing, you confirm that you have the right
|
||||
to submit the work and license it under the project's terms.
|
||||
|
||||
## Note
|
||||
|
||||
This document describes project scope and review expectations. It is not legal advice. See
|
||||
[interoperability and compliance scope](INTEROPERABILITY.md) and [`NOTICE.md`](../NOTICE.md).
|
||||
@@ -0,0 +1,176 @@
|
||||
# Interoperability and compliance scope
|
||||
|
||||
> This document explains how the project stays within interoperability and
|
||||
> responsible-use practices. It is **not legal advice**. Software, contract and
|
||||
> platform rules are fact-specific and differ by country. Consult qualified
|
||||
> counsel before distributing, modifying or using this project.
|
||||
|
||||
## 1. Purpose
|
||||
|
||||
`darling-lol` (<https://github.com/lucannez64/darling-lol/>) is an unofficial
|
||||
interoperability effort. It applies source patches to
|
||||
[Darling](https://github.com/darlinghq/darling), a free-software macOS
|
||||
compatibility layer, so that the official macOS Riot client can run on
|
||||
x86-64 Linux. The user obtains the client from Riot and signs in through Riot's
|
||||
own client and services.
|
||||
|
||||
The project is not a game, a game server, an account service, a launcher for
|
||||
pirated content, or a substitute for Riot's platform. It does not provide Riot
|
||||
accounts, authentication, matchmaking, payments, telemetry or game servers.
|
||||
|
||||
## 2. What we distribute and what we do not
|
||||
|
||||
We keep the project on the interoperability side by distributing only:
|
||||
|
||||
- source patches for Darling, a free-software compatibility layer;
|
||||
- original integration, build and test tooling;
|
||||
- project artwork;
|
||||
- documentation and license notices.
|
||||
|
||||
We do **not** distribute, bundle, mirror or sublicense:
|
||||
|
||||
- the Riot client or game binaries;
|
||||
- game assets, art, audio, video, fonts or data;
|
||||
- account databases, tokens or credentials;
|
||||
- Riot installers or installers for pirated content.
|
||||
|
||||
The user obtains the client from Riot and installs it through Riot's own
|
||||
installer. The project does not provide accounts, authentication, matchmaking,
|
||||
payments, telemetry or game servers, and it does not sell the game, a license,
|
||||
access or priority.
|
||||
|
||||
## 3. Platform identity
|
||||
|
||||
Darling needs macOS-like platform metadata. `tools/configure-darling-platform.py`
|
||||
can generate synthetic serial numbers, UUIDs and related fields, or import
|
||||
values supplied by the user, so that the guest environment is internally
|
||||
consistent.
|
||||
|
||||
These values are **local compatibility metadata**:
|
||||
|
||||
- they are not Apple-issued identifiers, credentials or authentication tokens;
|
||||
- they are not used with Apple services and should not be used to impersonate
|
||||
real hardware;
|
||||
- this repository does not pass them to Riot as authentication data. The values
|
||||
are exposed to guest applications through IOKit, and the official client's
|
||||
later handling of them is outside this repository's scope. The official client
|
||||
authenticates through Riot's own flow.
|
||||
|
||||
See `docs/PLATFORM_IDENTITY.md` for the schema, validation rules and warnings.
|
||||
|
||||
## 4. Local sandbox/AMFI probe
|
||||
|
||||
Darling has no macOS MAC, Seatbelt or AMFI framework. Chromium/CEF, which the
|
||||
Riot client embeds, probes `___mac_syscall`. An `ENOSYS` response is treated as
|
||||
fatal and prevents CEF helper processes from starting. The compatibility layer
|
||||
therefore reports the local probe as satisfied. The relevant code and comments
|
||||
are in `patches/xnu.patch` (`mac_syscall.c`).
|
||||
|
||||
This compatibility shim:
|
||||
|
||||
- does not implement a sandbox and does not make the guest secure;
|
||||
- does not interact with Riot's servers or alter Riot's account, payment or
|
||||
server-side integrity flows;
|
||||
- does not grant access to Riot's content or services.
|
||||
|
||||
Treat the guest as **unsandboxed**. Do not run untrusted content inside it. This
|
||||
is a local compatibility limitation, not a claim of security. See
|
||||
`docs/COMPLIANCE.md` for the project's terminology and scope guidance.
|
||||
|
||||
## 5. Copyright and licenses
|
||||
|
||||
We keep the project's copyright and licensing position clear:
|
||||
|
||||
- the game, client, code and assets remain Riot's; we do not reproduce, store,
|
||||
distribute or commercialise them;
|
||||
- we distribute patches to free-software Darling and original tooling, not a
|
||||
copy of the game or the upstream runtime;
|
||||
- each patch keeps the license and notices of the upstream file it modifies;
|
||||
original tooling is GPL-3.0-only;
|
||||
- we keep the upstream license and attribution notices in the patched files and
|
||||
describe the project's licensing position in `NOTICE.md`;
|
||||
- we do not remove copyright or license notices from patched files;
|
||||
- we review good-faith licensing and removal requests.
|
||||
|
||||
We do not sell the game, a license, access or priority. Donations are voluntary
|
||||
and support development of the free tooling; they do not purchase the game or
|
||||
any Riot service.
|
||||
|
||||
## 6. Access, authentication and integrity controls
|
||||
|
||||
The project is designed for compatibility, not for defeating access,
|
||||
authentication, payment or integrity controls:
|
||||
|
||||
- we do not provide tools or features whose purpose is to defeat those controls;
|
||||
- we do not forge authentication tokens, session data, account data or
|
||||
server-side integrity attestations;
|
||||
- we do not proxy or emulate Riot accounts, matchmaking, payments, telemetry or
|
||||
game servers;
|
||||
- we do not patch Riot's client or game binaries, CEF or anti-cheat;
|
||||
- we do not automate gameplay input or provide cheats;
|
||||
- the opt-in hide helper manages local client windows while a match runs; the
|
||||
opt-in close helper terminates eligible League client UI processes. Both are
|
||||
off by default and do not change client binaries, CEF or anti-cheat.
|
||||
|
||||
The synthetic platform identity in §3 is local metadata; it is not a credential.
|
||||
This repository does not pass it as Riot authentication data.
|
||||
|
||||
## 7. User agreements and account risk
|
||||
|
||||
Use of the project is at your own risk. You must:
|
||||
|
||||
- own a legitimate Riot account and obtain the client from Riot;
|
||||
- comply with Riot's terms and user agreement;
|
||||
- not cheat, exploit, automate gameplay or use the project to gain an unfair
|
||||
advantage;
|
||||
- accept that Riot does not support this environment and may suspend or ban
|
||||
accounts, including accounts used only for testing. Do not use a main or
|
||||
ranked account.
|
||||
|
||||
The project does not authorise any violation of Riot's terms. If you do not
|
||||
agree, do not use the project. Account restrictions are a real risk and are
|
||||
documented in the README announcement.
|
||||
|
||||
## 8. Trademarks and presentation
|
||||
|
||||
League of Legends and Riot Games are trademarks or registered trademarks of Riot
|
||||
Games, Inc. The names are used only to describe the compatibility target
|
||||
(nominative use). The project:
|
||||
|
||||
- does not use Riot logos, game art or other Riot brand assets;
|
||||
- uses an original project mark;
|
||||
- is not affiliated, endorsed or sponsored by Riot Games;
|
||||
- asks forks to use their own name and icon and not to imply endorsement.
|
||||
|
||||
See `NOTICE.md` and `docs/COMPLIANCE.md`.
|
||||
|
||||
## 9. Historical material
|
||||
|
||||
Earlier revisions of the repository included a third-party game logo and an
|
||||
embedded gameplay clip. They were removed from the current tree; the current
|
||||
tree uses an original project mark and contains no gameplay media. Git history
|
||||
and third-party forks may still contain those earlier revisions. They are not
|
||||
part of the current project, are not licensed by this project, and should not be
|
||||
reused. The project will review good-faith removal requests.
|
||||
|
||||
## 10. Notices and contact
|
||||
|
||||
For a licensing, trademark or interoperability question, contact the maintainer
|
||||
using the address in `README.md`, or open an issue in the repository. Include:
|
||||
|
||||
- the repository URL, file or commit at issue;
|
||||
- the right or license you assert;
|
||||
- the action you request;
|
||||
- a way to contact you.
|
||||
|
||||
For a formal copyright notice, use the hosting platform's copyright notice
|
||||
process. This document is not a designation of an agent for any platform and
|
||||
does not replace that process.
|
||||
|
||||
## 11. No legal advice and no warranty
|
||||
|
||||
This document is a technical and scope statement, not legal advice. The project
|
||||
is provided "as is", without warranty of any kind. Nothing here guarantees that
|
||||
a particular use is lawful in a particular country, that Riot will not restrict
|
||||
an account, or that a court or regulator will accept the project's position. If
|
||||
you need legal certainty, consult a lawyer qualified in your country.
|
||||
+53
-5
@@ -57,8 +57,15 @@ for prompts and session output.
|
||||
|
||||
Keep the terminal open while clients are running and until cleanup completes.
|
||||
Default and installer sessions wait for clients to exit, then shut Darling down
|
||||
in the same namespace. A failed shutdown is retried without discarding that
|
||||
namespace.
|
||||
in the same namespace. Crash handlers do not count as clients, because they do
|
||||
not exit on their own under Darling. A failed shutdown is retried without
|
||||
discarding that namespace.
|
||||
|
||||
To end a session while clients are still running, press Ctrl+C in its terminal,
|
||||
close the terminal, or run `darling-lol --stop` from anywhere. Each of these
|
||||
makes the session shut Darling down and exit. If the session does not exit
|
||||
within 30 seconds, `darling-lol --stop` kills the prefix's `darlingserver`
|
||||
itself. It also stops a server that outlived its session.
|
||||
|
||||
## Install permanently
|
||||
|
||||
@@ -84,6 +91,9 @@ darling-lol --installer
|
||||
darling-lol-shell
|
||||
```
|
||||
|
||||
The application menu entry is `Darling Launcher`, and `darling-launcher` is an
|
||||
alias for `darling-lol` for scripts that prefer the neutral name.
|
||||
|
||||
If the desktop entry does not appear, ensure your desktop session includes
|
||||
`~/.nix-profile/share` in `XDG_DATA_DIRS`, then log out and back in. Non-NixOS
|
||||
systems may need this desktop-session integration after installing Nix.
|
||||
@@ -198,13 +208,51 @@ nix run github:lucannez64/darling-lol#shell
|
||||
Use the launcher-provided shell rather than entering the raw FHS environment.
|
||||
If you start Darling manually in this shell, run `darling shutdown` there
|
||||
before exiting. A separately launched shell cannot reconnect to an existing
|
||||
Darling namespace.
|
||||
Darling namespace; `darling-lol --stop` can still stop it.
|
||||
|
||||
### Text fonts
|
||||
|
||||
The FHS environment bundles Liberation and DejaVu text fonts and uses its own
|
||||
fontconfig configuration and standard aliases. It does not depend on fonts in
|
||||
the host's `/usr/share/fonts`, which the FHS environment replaces. Fonts in your
|
||||
user font directories remain available. This also prevents an icon-only user
|
||||
font inventory from becoming the only fallback for ordinary text.
|
||||
|
||||
The FHS environment selects the packaged fontconfig configuration and clears
|
||||
inherited `FONTCONFIG_PATH` and `FONTCONFIG_SYSROOT` overrides. Clearing the
|
||||
sysroot prevents it from redirecting the packaged configuration and font paths.
|
||||
Custom XDG font/configuration directories and legacy
|
||||
`~/.fonts` remain supported, including paths with spaces. The host configuration
|
||||
and user font files are not modified.
|
||||
|
||||
To check font availability without starting Darling or Riot:
|
||||
|
||||
```sh
|
||||
nix run .#default -- fc-match sans-serif
|
||||
nix run .#default -- fc-match Arial
|
||||
```
|
||||
|
||||
These should select DejaVu Sans and Liberation Sans respectively. The standalone
|
||||
`nix build .#checks.x86_64-linux.fonts` check verifies generic and common Windows
|
||||
font aliases with both an empty home directory and icon-only user fonts. This
|
||||
checks font availability; CAPTCHA text rendering still needs manual verification
|
||||
in Riot's UI.
|
||||
|
||||
## Files and operational limits
|
||||
|
||||
- Workspace: `${XDG_DATA_HOME:-$HOME/.local/share}/darling-lol`.
|
||||
- Default guest prefix: `$HOME/.darling`; set `DPREFIX` to use another prefix.
|
||||
Explicit `DPREFIX` and `RIOT_GUEST_HOME` overrides take precedence over saved
|
||||
- Default guest prefix: `$HOME/.darling`. The prefix holds the installed
|
||||
clients and game, about 40 GB. To keep it on another drive, close the clients
|
||||
and run `darling-lol --set-prefix /mnt/games/league`. This moves an existing
|
||||
prefix there and saves the path for later launches. Across filesystems it
|
||||
copies first and removes the old prefix only after the copy completes. If the
|
||||
path already holds a Darling prefix, it switches to it and moves nothing. The
|
||||
path must not contain commas or colons (overlayfs uses them as separators).
|
||||
Across filesystems the target must support `user.*` extended attributes (ext4,
|
||||
btrfs, xfs; not NTFS or exFAT), which is checked before copying and again on
|
||||
the finished copy. The new path is saved before the old prefix is deleted.
|
||||
The next launch sets up the runtime in the new prefix.
|
||||
- Explicit `DPREFIX` and `RIOT_GUEST_HOME` overrides take precedence over saved
|
||||
configuration, so clear stale overrides before switching installations.
|
||||
- Package upgrades refresh managed tools and remove previously tracked obsolete
|
||||
files. Builds, the installed runtime, guest prefixes, and unmanaged files are
|
||||
|
||||
@@ -1,9 +1,21 @@
|
||||
# Explicit platform identity overrides
|
||||
|
||||
By default, Darling keeps the existing host-derived UUID and `DARLING-` serial.
|
||||
No override file is generated by installation or launch. Import mode uses values supplied by the user. Explicit `--provision` mode can
|
||||
generate synthetic identifiers; it does not issue authentic Apple identities
|
||||
or change CPU, GPU, or security metadata.
|
||||
First-time setup (`scripts/install-runtime.sh` creating a new prefix) runs
|
||||
`--provision --write` once, so each new prefix gets its own synthetic identity.
|
||||
A prefix without saved identity files keeps the host-derived UUID and
|
||||
`DARLING-` serial; launching never generates an override. Import mode uses
|
||||
values supplied by the user. `--provision` generates synthetic identifiers; it
|
||||
does not issue authentic Apple identities or change CPU, GPU, or security
|
||||
metadata. The overrides are for application compatibility; they are not intended
|
||||
to bypass anti-cheat, telemetry, licensing, or platform security controls.
|
||||
|
||||
> **Synthetic, local metadata.** These values are not Apple-issued identifiers,
|
||||
> credentials or authentication tokens. Do not use them with Apple services or
|
||||
> to impersonate real hardware. This repository does not pass them to Riot as
|
||||
> authentication data; the values are exposed to guest applications through
|
||||
> IOKit, and the official client's later handling of them is outside this
|
||||
> repository's scope. See the
|
||||
> [interoperability and compliance scope](INTEROPERABILITY.md).
|
||||
|
||||
Preview an OpenCore `config.plist`, a flat GenSMBIOS JSON file, or an existing
|
||||
`darling-smbios.conf` without changing the selected prefix:
|
||||
@@ -89,8 +101,13 @@ python3 tools/configure-darling-platform.py --prefix /absolute/prefix --provisio
|
||||
```
|
||||
|
||||
Launch Darling normally afterwards. This requires the updated iokitd from this
|
||||
PR; the config tool alone cannot update an older running daemon. Provisioning
|
||||
is never called automatically by installation or launch.
|
||||
PR; the config tool alone cannot update an older running daemon. Besides
|
||||
first-time setup above, provisioning runs automatically in one other case:
|
||||
after installing, `scripts/update.sh` runs
|
||||
`configure-darling-platform.py --replace-known-defaults --write`, which
|
||||
provisions the prefix only if its saved identity still contains the shared
|
||||
serial or UUID below. Prefixes without saved identity files, or whose saved
|
||||
values are all custom, are left byte-for-byte unchanged.
|
||||
|
||||
Missing serials use 12 characters and missing MLBs use 17 characters, sampled
|
||||
with Python's `secrets` module from `0123456789ABCDEFGHJKLMNPQRSTUVWXYZ` (Base-34).
|
||||
@@ -106,7 +123,8 @@ example with your chosen unicast OUI). It only affects a missing ROM. An OUI
|
||||
does not make a generated identity Apple-issued.
|
||||
|
||||
Provisioning replaces the known shared serial `C02ZL0GVMD6N` and UUID
|
||||
`6F3B2A1E-8C4D-4E5F-9A7B-1D2C3E4F5A6B`, along with the latter's derived fields.
|
||||
`6F3B2A1E-8C4D-4E5F-9A7B-1D2C3E4F5A6B`, along with the latter's derived fields
|
||||
(including a standalone `system-id` or `provisioning_UDID` derived from it).
|
||||
It preserves other existing custom values, including imported serials and UUIDs
|
||||
that do not follow the generated formats. A standalone custom `system-id` is
|
||||
preserved by deriving its UUID instead of generating a different one. Model,
|
||||
@@ -134,3 +152,15 @@ a per-prefix lock. Repeated writes preserve identifiers and unchanged files.
|
||||
Previewing a fresh identity generates a sample; only `--write` makes it stable.
|
||||
To restore host-derived defaults after provisioning, remove both identity files
|
||||
while stopped, so a later provisioning run cannot reimport the legacy copy.
|
||||
|
||||
## system_profiler
|
||||
|
||||
The prefix's `/usr/sbin/system_profiler` (installed by
|
||||
`tools/install-darling-system-profiler.py`) answers `SPHardwareDataType` and
|
||||
`SPSoftwareDataType` from the running guest: model, serial number, platform UUID
|
||||
and provisioning UDID from IOKit's `IOPlatformExpertDevice` (via `ioreg`), CPU,
|
||||
cache and memory from `sysctl`, and the OS version from `sw_vers` and `uname`. It
|
||||
keeps no identity of its own, so it reports the same values as IOKit, including any
|
||||
override saved by this tool. A field the guest does not provide is left out.
|
||||
`python3 -m unittest tests.test_system_profiler` checks the output with stub
|
||||
commands.
|
||||
+52
-4
@@ -61,10 +61,15 @@ The normal launcher starts neither an Electron inspector nor client API/UI helpe
|
||||
|
||||
## Application-menu diagnostics and recovery options
|
||||
|
||||
`scripts/create-desktop-entry.sh` creates an application-menu entry that runs
|
||||
`scripts/desktop-run.sh`, preserving the normal launcher behavior and saving
|
||||
stdout/stderr in `build/desktop-launch.log` with mode 0600. Logs can contain
|
||||
private session details; do not attach raw launcher logs to a public issue.
|
||||
`scripts/create-desktop-entry.sh` creates a `Darling Launcher` application-menu
|
||||
entry that runs `scripts/desktop-run.sh`, preserving the normal launcher behavior
|
||||
and saving stdout/stderr in `build/desktop-launch.log` with mode 0600. Logs can
|
||||
contain private session details; do not attach raw launcher logs to a public
|
||||
issue. The helper also removes the `league-of-legends.*` entry and icons written
|
||||
by older versions, but only when it can prove this project created them. The
|
||||
entry keeps `StartupWMClass=RiotClientServices`: Darling derives `WM_CLASS` from
|
||||
the basename of `argv[0]`, so that is the window class of the client the entry
|
||||
starts, while `Riot Client` is only its bundle display name.
|
||||
Region/locale remain the existing `RCLOL_REGION`/`RCLOL_LOCALE` options.
|
||||
|
||||
`RCLOL_CDN_IPV4_ONLY` defaults to `1` in the Riot launcher and can be set to `0`
|
||||
@@ -73,6 +78,49 @@ opts in to Hyprland-only recovery of a default-prefix Riot session with no
|
||||
visible window; it is off by default. See [troubleshooting](TROUBLESHOOTING.md#riot-exits-and-league-reports-that-it-lost-its-connection)
|
||||
for the observed failures, recovery guards and NSURLSession limitations.
|
||||
|
||||
`RCLOL_RECOVER_STALE_CLIENT=1` opts in to partial runtime recovery before
|
||||
launch. If `darlingserver` is alive, no game is running, no managed client
|
||||
window is visible and stale Riot/League/CEF processes at least 60 seconds old
|
||||
remain, the launcher stops only that client tree and reuses the runtime. It
|
||||
recognizes `LeagueClientUx`, `Riot Client` and `RiotClientServices` windows and
|
||||
preserves `darlingserver`, launchd/mldr, sockets and `.init.pid`; if X11/Xwayland
|
||||
properties cannot be inspected it does nothing. See
|
||||
[troubleshooting](TROUBLESHOOTING.md#client-remains-running-without-a-window).
|
||||
|
||||
`RCLOL_CLEAN_STALE_HELPERS=1` runs a preflight before the match helper starts.
|
||||
It removes `close-league-client-in-game.py` and
|
||||
`hide-league-client-in-game.py` processes of this prefix whose parent is
|
||||
`init`/`systemd`, which means the launcher that started them has exited. A
|
||||
helper of a live launcher, another prefix, the game, Riot/League/CEF and
|
||||
`darlingserver` are preserved. It fails closed when a command line,
|
||||
environment, stat entry or parent cannot be read. See
|
||||
[troubleshooting](TROUBLESHOOTING.md#multiple-client-in-game-helpers).
|
||||
|
||||
`RCLOL_CLIENT_IN_GAME_TRICKS=1` opts in to automatic client handling during a
|
||||
match. It reads `lol-user-experience` from the installed client's
|
||||
`LCULocalPreferences.yaml`: with `potatoModeEnabled: true`, it closes the client
|
||||
UI when `closeLeagueClientDuringGame: true`, and hides the managed
|
||||
`LeagueClientUx`/`Riot Client` X11 windows when that value is false. Missing or
|
||||
invalid preference values, disabled client optimizations and unreadable files
|
||||
select no action. The low-level `RCLOL_CLOSE_CLIENT_IN_GAME=1` and
|
||||
`RCLOL_HIDE_CLIENT_IN_GAME=1` switches remain available when the automatic
|
||||
switch is not set. The feature is off by default, requires X11/Xwayland, and
|
||||
does not patch client binaries, CEF or the game. The hide helper exits after ten
|
||||
minutes without a client or match once it has seen one. See
|
||||
[the troubleshooting measurement](TROUBLESHOOTING.md#automatic-client-handling-during-a-match)
|
||||
for the measured CPU effect and limitations.
|
||||
|
||||
`RCLOL_FILTER_HOST_ENV=1` opts in to removing host desktop-session variables before
|
||||
the guest starts: `WAYLAND_DISPLAY`, `XDG_SESSION_*`, `XDG_CURRENT_DESKTOP`,
|
||||
GNOME/KDE/GDM, systemd, SSH agent, terminal, toolkit (`QT_*`, `GTK_*`, `GDK_*`),
|
||||
Flatpak/Snap/AppImage and exported shell functions, and `SHELL` becomes `/bin/zsh`.
|
||||
It is off by default. Variables that Darling or the host graphics and audio libraries read
|
||||
are always kept: `DISPLAY`, `XAUTHORITY`, `XDG_RUNTIME_DIR` and the XDG base directories,
|
||||
D-Bus, PulseAudio/PipeWire/ALSA, `DRI_PRIME`, `MESA_*`, `__GL_*`, `__NV_*`, `VK_*`,
|
||||
`LD_PRELOAD` and `LD_LIBRARY_PATH`. List names to keep anyway in `RCLOL_KEEP_HOST_ENV`,
|
||||
for example `RCLOL_KEEP_HOST_ENV=WAYLAND_DISPLAY`. The launcher always closes inherited
|
||||
file descriptors above stderr before the guest starts.
|
||||
|
||||
## 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.
|
||||
|
||||
+215
-4
@@ -23,11 +23,11 @@ c++ -std=c++17 -I build/darling-src/src/external/metal/private-include \
|
||||
build/test-metal-device-info
|
||||
```
|
||||
|
||||
Expected: `METAL_DEVICE_INFO failures=0 pass=1`. It covers how `MTLDevice.name` turns Vulkan names into macOS style (`AMD Radeon RX 6800 XT (RADV NAVI21)` becomes `AMD Radeon RX 6800 XT`, `Intel(R) UHD Graphics 630 (CML GT2)` becomes `Intel UHD Graphics 630`, NVIDIA names stay as they are), the `DARLING_METAL_VRAM_MB` parser, and the working-set sum over Vulkan heaps.
|
||||
Expected: `METAL_DEVICE_INFO failures=0 pass=1`. It covers how `MTLDevice.name` turns Vulkan names into macOS style (`AMD Radeon RX 6800 XT (RADV NAVI21)` becomes `AMD Radeon RX 6800 XT`, `Intel(R) UHD Graphics 630 (CML GT2)` becomes `Intel UHD Graphics 630`, NVIDIA names stay as they are), the `DARLING_METAL_VRAM_MB` parser, the working-set sum over Vulkan heaps, and a non-zero `registryID`. `python3 -m unittest tests.test_metal_device_info` runs the same check against the header in `patches/metal.patch`, so it also works without a prepared source tree; CI runs it.
|
||||
|
||||
The LLVM version check compiles Iridium's actual dynamic loader against small stub libraries. It verifies that a matching major initializes, older/newer majors produce a version mismatch, and a missing library fails initialization. It does not start Darling. The native AIR translator (`python3 tools/build-riot-air-check.py --native`) selects both headers and the shared library from the same `llvm-config`; its build prints those paths so shader results can be attributed to the tested LLVM installation.
|
||||
|
||||
The patch check uses temporary base indexes and does not reset or modify the source checkout. The launcher check uses stub commands and never starts or contacts Riot or Darling. It checks installed-client and first-time-installer launches, strips inherited `DYLD_INSERT_LIBRARIES` even with the old VPX opt-in set, and preserves Darling runtime library/framework paths. The memory-installer check imports the installer and exercises its policy (default source is the prepared artifact; the artifact is never a destination of a different library) in temporary directories. The Foundation-installer check does the same for the overlay installer: it seeds a temporary framework from a stub SDK, replaces `Versions/C/Foundation`, keeps the standard symlinks, stages a backup, treats a re-run as current and refuses a non-Mach-O source. The installer handoff mock covers 28 scenarios, including HTTP 409, interrupted verification and preserving active downloads.
|
||||
The patch check uses temporary base indexes and does not reset or modify the source checkout. The launcher check uses stub commands and never starts or contacts Riot or Darling. It checks installed-client and first-time-installer launches, strips inherited `DYLD_INSERT_LIBRARIES` even with the old VPX opt-in set, preserves Darling runtime library/framework paths, and checks that host file descriptors the launcher inherited (including multi-digit ones) are closed before the guest starts. The memory-installer check imports the installer and exercises its policy (default source is the prepared artifact; the artifact is never a destination of a different library) in temporary directories. The Foundation-installer check does the same for the overlay installer: it seeds a temporary framework from a stub SDK, replaces `Versions/C/Foundation`, keeps the standard symlinks, stages a backup, treats a re-run as current and refuses a non-Mach-O source. The installer handoff mock covers 28 scenarios, including HTTP 409, interrupted verification and preserving active downloads.
|
||||
|
||||
## NSURLSession lifecycle regression
|
||||
|
||||
@@ -78,6 +78,7 @@ Host-only checks, which do not start Darling:
|
||||
python3 tools/riot-cdn-dns-offline-check.py
|
||||
python3 -m unittest discover -s tests -p 'test_hidden_riot_recovery.py'
|
||||
python3 -m unittest discover -s tests -p 'test_desktop_run.py'
|
||||
python3 -m unittest discover -s tests -p 'test_desktop_entry.py'
|
||||
python3 -m unittest discover -s tests -p 'test_urlsession_runner.py'
|
||||
```
|
||||
|
||||
@@ -87,9 +88,25 @@ insensitivity, A queries, non-CDN names and misleading domain suffixes without
|
||||
sending a DNS query. Recovery tests use fake `/proc` entries and mock every
|
||||
compositor/shutdown command. The desktop test checks exit/argument forwarding,
|
||||
append-only output and tightening an existing public log to mode 0600. The
|
||||
runner test uses a fake `darling` executable to verify loopback fixtures and
|
||||
desktop-entry test renders the helper against a disposable `HOME` and checks
|
||||
the neutral fields, Exec quoting, icon paths, that only launcher artifacts
|
||||
this project created are removed, and that the checkout and packaged entries
|
||||
share their identity fields. The runner test uses a fake `darling` executable to verify loopback fixtures and
|
||||
private-prefix-only shutdown/cleanup.
|
||||
|
||||
With the pinned Darling sources available, run the host resolver regression:
|
||||
|
||||
```sh
|
||||
python3 -m unittest discover -s tests -p 'test_host_dns.py' -v
|
||||
```
|
||||
|
||||
It compiles the actual patched vchroot implementation in its Linux test mode
|
||||
and reads temporary resolver files through absolute and relative host symlinks.
|
||||
It checks atomic file replacement, symlink retargeting, guest DNS overrides and
|
||||
guest absolute-link scope. A CMake fixture verifies that both install modes
|
||||
replace an older Google resolver file with the host link. No Darling session or
|
||||
DNS traffic is started.
|
||||
|
||||
## CoreText font-list and AppKit resize regressions
|
||||
|
||||
After preparing the source and configuring the runtime tools:
|
||||
@@ -119,6 +136,121 @@ Expected: `CORETEXT_FONT_LIST pass=1` with nonzero family/face counts (306/1124
|
||||
|
||||
The font probe reproduces Chromium's installed-family enumeration, mandatory family matching and localized-name lookup with a null language output. It checks ownership of the single-match result and that a nonexistent mandatory family produces no substitute. The resize probe checks backing-layer bounds immediately after frame/bounds changes, including a scaled view and a child layer, without relying on input or coordinate conversion to refresh them. It also verifies that native parent size is current during child layout, and that adding an overlay sibling after the content view does not redirect subsequent content resizing into that overlay. The old sibling selection left content at 1600×900 while stretching the 30×20 overlay to 1280×720; the corrected layout resizes the content and preserves the overlay. Before the fixes, these checks report a stale 1280×720 layer and native parent after a 1600×900 resize. The `FRAME_SIZE_HOOK` check uses a subclass whose `setFrameSize:` publishes its viewport size. It checks growth and shrinkage through parent autoresizing, direct `setFrame:`, and direct `setFrameSize:`. Before component-setter dispatch was corrected only the final direct size setter reached the subclass (one callback); afterward all four updates do, with current geometry visible inside the callback. No probe launches or contacts Riot/League. The user confirmed that the language dropdown and League window resizing work after the final X11 correction (2026-10-15). The practice-tool crash remains unresolved.
|
||||
|
||||
### Local event monitors and client dragging
|
||||
|
||||
`python3 tools/build-riot-x11-runtime.py` builds
|
||||
`build/riot-event-monitors-check`. With patched AppKit installed and clients
|
||||
closed, run:
|
||||
|
||||
```sh
|
||||
darling shell env DYLD_LIBRARY_PATH=/Users/me/RuntimeLibraries \
|
||||
DYLD_FRAMEWORK_PATH=/Users/me/RuntimeFrameworks \
|
||||
/Volumes/SystemRoot/path/to/darling-lol/build/riot-event-monitors-check
|
||||
```
|
||||
|
||||
Expected: nine `PASS` lines and exit status 0. This independent probe tests
|
||||
multiple monitors, mask filtering, removal, consumption, event replacement,
|
||||
self-removal, and the in-flight snapshot when another monitor is removed.
|
||||
Monitor order is unspecified and is deliberately not asserted. It dispatches
|
||||
only synthetic events to its own unmapped window.
|
||||
The same source runs against native macOS AppKit:
|
||||
|
||||
```sh
|
||||
clang -fblocks -framework AppKit tools/riot-event-monitors-check.m \
|
||||
-o /tmp/riot-event-monitors-check
|
||||
/tmp/riot-event-monitors-check
|
||||
```
|
||||
|
||||
Native reference: macOS 26.6.2. LeagueClientUx uses local
|
||||
monitors for custom window movement; intercepting a fixed titlebar strip instead
|
||||
would bypass its own drag policy and risk stealing its window-control clicks.
|
||||
For a live regression check, drag its header, drag ordinary content (which must
|
||||
not move the window), minimize and restore it, and check Riot Client header
|
||||
dragging separately. No match is required.
|
||||
|
||||
### X11 window decorations
|
||||
|
||||
`python3 tools/build-riot-x11-runtime.py` also builds
|
||||
`build/riot-window-decorations-check`. Install and activate both the patched
|
||||
AppKit and X11 backend before running it, with clients and the game closed:
|
||||
|
||||
```sh
|
||||
darling shell env DYLD_LIBRARY_PATH=/Users/me/RuntimeLibraries \
|
||||
DYLD_FRAMEWORK_PATH=/Users/me/RuntimeFrameworks \
|
||||
/Volumes/SystemRoot/path/to/darling-lol/build/riot-window-decorations-check
|
||||
```
|
||||
|
||||
Expected: `WINDOW_DECORATIONS_CHECK cases=10 pass=1`. The independent Cocoa
|
||||
probe reads `_MOTIF_WM_HINTS` on its own unmapped native windows. It covers a
|
||||
normal titled window, hidden title alone, hidden plus transparent titlebar,
|
||||
both reverse transitions, untitled masks with additional flags, switching to
|
||||
borderless after creation, native-window recreation, and properties set before
|
||||
the native window exists. It sends no input, maps no windows and does not
|
||||
launch or contact Riot/League. Check the success marker, not only the launcher
|
||||
exit status: some Darling versions do not propagate a guest assertion failure.
|
||||
|
||||
Before the fix the hidden/transparent case fails. With the corrected overlays,
|
||||
the ten cases passed on KDE Wayland/Xwayland. Passive inspection of the reopened
|
||||
Riot Client also confirmed the no-decoration hint and identical compositor frame
|
||||
and client dimensions (1536×864), instead of the previous 28-pixel titlebar.
|
||||
This validates the Riot Client window, not every LeagueClientUx or game mode.
|
||||
|
||||
### NSOpenPanel Open/Cancel (issue #85)
|
||||
|
||||
`python3 tools/build-riot-openpanel-check.py` builds
|
||||
`build/riot-openpanel-check`. Install the patched AppKit first, with clients and
|
||||
the game closed. The probe opens its own directory chooser in a fresh `/tmp`
|
||||
folder containing one subfolder and needs no Riot/League client:
|
||||
|
||||
```sh
|
||||
P=/Volumes/SystemRoot/path/to/darling-lol/build/riot-openpanel-check
|
||||
E="env DYLD_LIBRARY_PATH=/Users/me/RuntimeLibraries DYLD_FRAMEWORK_PATH=/Users/me/RuntimeFrameworks"
|
||||
for m in cancel open-selected open-none; do darling shell $E $P --auto $m; done
|
||||
darling shell $E $P --interactive
|
||||
```
|
||||
|
||||
Each `--auto` mode waits 1.5 s, then clicks the panel's own Cancel or Open
|
||||
button (selecting the subfolder first for `open-selected`), and checks that
|
||||
`runModal` returned, the result code and URL, and that the panel is gone. A
|
||||
15 s watchdog on a separate thread reports a hang. Expected, one line per mode:
|
||||
|
||||
```
|
||||
OPENPANEL_CHECK mode=cancel result=PASS code=0 ...
|
||||
OPENPANEL_CHECK mode=open-selected result=PASS code=1 url=/tmp/openpanel-check.XXXXXX/subfolder ...
|
||||
OPENPANEL_CHECK mode=open-none result=PASS code=1 url=/tmp/openpanel-check.XXXXXX ...
|
||||
```
|
||||
|
||||
A failure prints `result=FAIL ... problems=...` (`HANG` when `runModal` never
|
||||
returned) and exits non-zero. `--interactive` shows the panel for manual clicks
|
||||
and prints `OPENPANEL_CHECK mode=interactive result=RETURNED code=... url=...`
|
||||
once it closes. As with the other probes, check the printed line rather than
|
||||
only the launcher exit status.
|
||||
|
||||
### NSSavePanel Finder tags (`setTagNames:` / `tagNames`)
|
||||
|
||||
`python3 tools/build-riot-save-panel-tag-check.py` builds
|
||||
`build/riot-save-panel-tag-check`. Install the patched AppKit first, with clients
|
||||
and the game closed. The probe checks selector availability, inheritance, and
|
||||
safe no-op semantics without launching any client or creating window fixtures:
|
||||
|
||||
```sh
|
||||
P=/Volumes/SystemRoot/path/to/darling-lol/build/riot-save-panel-tag-check
|
||||
E="env DYLD_LIBRARY_PATH=/Users/me/RuntimeLibraries DYLD_FRAMEWORK_PATH=/Users/me/RuntimeFrameworks"
|
||||
darling shell $E $P
|
||||
```
|
||||
|
||||
Expected output:
|
||||
|
||||
```
|
||||
SAVE_PANEL_TAG class=NSSavePanel selectors=1 inherited=1 values=1 pass=1
|
||||
SAVE_PANEL_TAG class=NSOpenPanel selectors=1 inherited=1 values=1 pass=1
|
||||
SAVE_PANEL_TAG_CHECK pass=1
|
||||
```
|
||||
|
||||
Cocotron does not persist Finder tag metadata or provide a tag UI. `setTagNames:`
|
||||
safely ignores input and `tagNames` returns an empty `NSArray` to prevent
|
||||
unrecognized-selector crashes from callers (such as Electron file dialogs).
|
||||
|
||||
### Presentation publisher handoff
|
||||
|
||||
With clients closed, run `build/riot-client-resize-check --handoff` through Darling with the normal runtime overrides. This creates an owned window and two fixture frame slots. The live software owner first supplies a 1280×720 main frame, then switches to a 262×131 popup; another slot supplies the resized main frame without the software flag. The test samples its own window beyond the original frame extent and checks generation colors across growth and shrinkage. The old selector reports black at the 1600×900 sample; the correction passes all four sizes. Repeat with `RCLOL_XSHM=0` and with `DARLING_XSHM_LIBRARY` pointing to the normal transport to cover both paths. The fixtures remove their own frame slots on exit; they do not read client pixels or send input.
|
||||
@@ -129,6 +261,12 @@ With clients closed, run `build/riot-client-resize-check --handoff` through Darl
|
||||
|
||||
The probe cycles through 1280×720, 1600×900, 1024×576 and 1280×720. Each stage changes the frame color and samples its own X11 window near the bottom-right corner, beyond the original frame's extent when growing. It waits up to 2.5 seconds for presentation and prints `RESIZE_PIXEL ... pass=1` for every stage. This verifies presented pixels rather than only view properties. Success does not reproduce League's own resize callbacks; the reported League UI failure remains open, with the opt-in [geometry trace](TROUBLESHOOTING.md#language-dropdown-closes-riot-or-client-resize-leaves-a-clipped-ui) needed to locate the remaining mismatch.
|
||||
|
||||
### X11 drag and drop
|
||||
|
||||
`python3 build/runtime/tools/build-riot-x11-drag-check.py` builds an independent AppKit check for the in-process drag loop that the X11 backend uses (`-[NSWindow dragImage:at:offset:event:pasteboard:source:slideBack:]`). It needs the cocotron AppKit built with the drag-and-drop patch. With clients closed, run `build/riot-x11-drag-check` through Darling with the same framework/library overrides as the other probes. It opens its own source and destination windows, posts synthetic events to its own application queue, and does not inject X11 input or touch other clients.
|
||||
|
||||
It prints `PASS` or `FAIL` for seven cases: a drop onto a destination without `prepareForDragOperation:` (the default must proceed), a drag ending outside any destination, Escape cancel, two drags back to back with a source lacking `pasteboardChangedOwner:`, main-queue blocks and run loop timers firing during the drag, a mouse-down reaching a view afterwards, and a destination view removed from its window mid-drag. Each drag must call `draggedImage:endedAt:operation:` exactly once. The process exits nonzero on any failure, and a case that makes no progress for 20 seconds is reported as a hang, which is the original failure mode. Success covers the AppKit drag loop only; it does not exercise Chromium's own drag source.
|
||||
|
||||
## Legacy operating-system version query
|
||||
|
||||
```sh
|
||||
@@ -181,7 +319,7 @@ build/riot-host-receive-check
|
||||
python3 tools/riot-launcher-offline-check.py
|
||||
```
|
||||
|
||||
Expected: `warm_empty_allocations=0 single_packet_allocations=2`, correct credentials, descriptor ownership/CLOEXEC, sender addresses, multiple batches, truncation flags, callback reentrancy and exception cleanup. The old receive implementation makes 32 allocations for a warmed empty receive and 64 for a single-packet receive in the same fixture. These are allocation counts, not an FPS benchmark. The launcher check uses stub commands and never starts or contacts Riot or Darling. It checks installed-client and first-time-installer launches, strips inherited `DYLD_INSERT_LIBRARIES` even with the old VPX opt-in set, and preserves Darling runtime library/framework paths.
|
||||
Expected: `warm_empty_allocations=0 single_packet_allocations=2`, correct credentials, descriptor ownership/CLOEXEC, sender addresses, multiple batches, truncation flags, callback reentrancy and exception cleanup. The old receive implementation makes 32 allocations for a warmed empty receive and 64 for a single-packet receive in the same fixture. These are allocation counts, not an FPS benchmark. The launcher check uses stub commands and never starts or contacts Riot or Darling. It checks installed-client and first-time-installer launches, strips inherited `DYLD_INSERT_LIBRARIES` even with the old VPX opt-in set, preserves Darling runtime library/framework paths, and checks that host file descriptors the launcher inherited (including multi-digit ones) are closed before the guest starts.
|
||||
|
||||
The same source passed AddressSanitizer/UndefinedBehaviorSanitizer and leak detection. Allocation counts depend on this fixture/compiler/library; preserve the no-wasted-slot property rather than interpreting them as game performance. See [setup](SETUP.md#updating-an-existing-runtime) for server build/install/activation.
|
||||
|
||||
@@ -527,3 +665,76 @@ launch, window placement, click alignment and edge panning before rollout.
|
||||
|
||||
After installing the tested X11 and CoreGraphics binaries locally, the user
|
||||
reported that everything was working (2026-10-16). Fullscreen policy was unchanged.
|
||||
|
||||
## VRR presentation regression (#46)
|
||||
|
||||
```sh
|
||||
python3 -m unittest discover -s tests -p 'test_x11_vrr.py'
|
||||
```
|
||||
|
||||
The host fixtures exercise the patched eligibility, X11 property updates and
|
||||
EGL surface ownership transitions. They cover hidden/titled/partial views,
|
||||
multiple views, parent recreation, child fallback and current-context handling.
|
||||
|
||||
Build `build-riot-coregraphics-runtime.py`, `build-riot-x11-runtime.py` and
|
||||
`build-riot-metal-presentation.py` together. Stage all three updated frameworks
|
||||
(CoreGraphics, AppKit's X11 backend, QuartzCore) when testing isolated binaries.
|
||||
Run `riot-metal-presentation-check` with `RIOT_PRESENTATION_PARENT=1` to create
|
||||
an owned borderless game-like window. It verifies the parent drawable is
|
||||
borrowed. Add `RIOT_PRESENTATION_RESIZE=1` to verify fallback to a child at
|
||||
half width and borrowing the parent again when restored. Add
|
||||
`RIOT_PRESENTATION_NESTED=1` for nested layers.
|
||||
The default probe still exercises the independent child-window path.
|
||||
|
||||
For physical VRR validation, enable adaptive sync in the monitor and desktop,
|
||||
then manually test fullscreen and borderless League with a frame cap inside
|
||||
the monitor's VRR range (for example 157 FPS on a 165 Hz display), with VSync
|
||||
off. Check the monitor's live refresh display as well as the game FPS counter.
|
||||
Also test leaving fullscreen, restoring it, resizing, and alt-tab. Report GPU,
|
||||
driver, compositor and session type. A stable FPS counter or the X11 property
|
||||
alone does not prove that the monitor is using variable refresh.
|
||||
|
||||
Local native validation on 2026-10-19 passed all three modes (child, parent
|
||||
with resize, nested parent with resize), including Vulkan-to-GL pixel readback
|
||||
and ten presentations per mode, using staged frameworks in a private prefix.
|
||||
Physical VRR and in-game performance on the reporter's Mutter setup remain
|
||||
unverified.
|
||||
|
||||
## Metal VSync regression (#46)
|
||||
|
||||
```sh
|
||||
python3 -m unittest discover -s tests -p 'test_metal_vsync.py'
|
||||
```
|
||||
|
||||
These host checks compile the patched layer-tree policy, flush body and CGL
|
||||
swap methods against inert fixtures. They cover nested layers, VSync toggles,
|
||||
shared-window requests, independent contexts, late attachment and failed
|
||||
context/interval changes. They do not measure physical display refresh.
|
||||
|
||||
For manual validation after updating with the clients closed, compare the
|
||||
same scene with in-game VSync on and off. With VSync on, presentation should
|
||||
follow the display/compositor cadence; with it off, the game's frame cap
|
||||
should continue to work. Record monitor refresh, compositor, X11/Xwayland,
|
||||
GPU/driver and window mode. The window presentation probe above checks the
|
||||
API wiring; an in-game/compositor timing comparison remains necessary.
|
||||
|
||||
## Combined VSync and VRR testing
|
||||
|
||||
The `test/vsync-vrr` branch combines PRs #81 and #88, resolving their shared
|
||||
compositor-patch and presentation-probe changes. The probe toggles
|
||||
`displaySyncEnabled` and checks the CGL interval in child, parent and nested
|
||||
parent modes; the parent modes also support resize fallback/restoration.
|
||||
All three native modes passed locally in a private prefix, together with ten
|
||||
host regression checks and validation of all 31 component patches.
|
||||
|
||||
After closing the game and both clients:
|
||||
|
||||
```sh
|
||||
git fetch origin
|
||||
git switch --track origin/test/vsync-vrr
|
||||
./scripts/update.sh
|
||||
```
|
||||
|
||||
Test VSync with desktop VRR disabled, then test VRR with VSync off and a cap
|
||||
inside the monitor's adaptive range. Finally test both enabled. Report game
|
||||
FPS and the monitor's live refresh reading separately.
|
||||
+316
-6
@@ -57,6 +57,34 @@ The old kernel library returned success for Darwin allocator free/reuse hints wi
|
||||
|
||||
Build/install the kernel library using [the incremental update steps](SETUP.md#updating-an-existing-runtime), then perform the manual restart when finished playing. Linux 5.14+ is required. Do not kill client helpers as an attempted memory optimization during play.
|
||||
|
||||
## Shaders are recompiled on every start
|
||||
|
||||
Mesa resolves its shader disk cache below `$HOME`. Inside the guest, `$HOME` is
|
||||
`/Users/<user>`, which does not exist on the host filesystem, so Mesa cannot create the
|
||||
cache and disables it, once per process:
|
||||
|
||||
```
|
||||
Failed to create /Users/<user> for shader cache (Permission denied)---disabling
|
||||
```
|
||||
|
||||
One launcher log without the fix contained 212 of these lines, and no shader compiled by
|
||||
one process was reused by another process or by the next start: the client and the game
|
||||
recompiled every shader, on every launch. Mesa is host code loaded into the guest and
|
||||
reads `MESA_SHADER_CACHE_DIR` verbatim, so the launcher now passes a host directory:
|
||||
|
||||
```sh
|
||||
MESA_SHADER_CACHE_DIR=${XDG_CACHE_HOME:-${HOME:-$prefix_dir}/.cache}/darling-lol # launcher default
|
||||
```
|
||||
|
||||
Mesa creates `mesa_shader_cache/` (client and game shaders) and `radv_builtin_shaders/`
|
||||
below it. With the change, one client start logged no shader-cache failure and wrote 434
|
||||
files / 5.6 MB, still growing while the client ran (Radeon RX 550, radv). Set
|
||||
`MESA_SHADER_CACHE_DIR` yourself to move the cache, for example to another disk: the
|
||||
launcher preserves a non-empty value, and a directory that cannot be created leaves the
|
||||
previous behaviour. Reusing the cache removes the repeated compilation; the startup-time
|
||||
gain has not been measured separately. Cache entries are keyed by driver and GPU, so a
|
||||
driver update recompiles once and caches again.
|
||||
|
||||
## Installer exits, cannot write temporary files, or Install appears idle
|
||||
|
||||
Use `sh scripts/installer.sh euw` (or your supported region). The first-time launcher must omit the installed client's `--app-root` and pass `--skip-to-install`; the normal wrapper now does this. It also selects `league_of_legends` so the product installation UI appears after login.
|
||||
@@ -121,6 +149,163 @@ ownership or window information cannot be read. A hidden background client or
|
||||
download is restarted when recovery is enabled. Other desktop environments are
|
||||
not supported by this helper; recovery is off by default.
|
||||
|
||||
For a live Darling runtime with no visible client window but stale Riot/League/
|
||||
CEF processes at least 60 seconds old, `RCLOL_RECOVER_STALE_CLIENT=1` performs a
|
||||
partial recovery before the next launch. It keeps `darlingserver`, launchd/mldr,
|
||||
sockets and `.init.pid`, stops only the stale client process tree, and then lets
|
||||
the normal launcher start the product again. It recognizes `LeagueClientUx`,
|
||||
`Riot Client` and `RiotClientServices` windows, does nothing while a game process
|
||||
is running or when a managed client window is visible, and fails closed when
|
||||
X11/Xwayland properties are missing or unreadable. This saves the Darling
|
||||
bootstrap but not the Riot Client/CEF startup itself. If no window appears
|
||||
afterward, use a full runtime shutdown/restart.
|
||||
|
||||
### Multiple client-in-game helpers
|
||||
|
||||
The close/hide helper is a long-lived watcher. If an earlier launcher exits
|
||||
while it remains, the next launch can start another copy; list them with:
|
||||
|
||||
```sh
|
||||
pgrep -af 'close-league-client-in-game.py|hide-league-client-in-game.py'
|
||||
```
|
||||
|
||||
To remove the orphaned copies before the next helper starts:
|
||||
|
||||
```sh
|
||||
RCLOL_CLEAN_STALE_HELPERS=1 ./scripts/run.sh
|
||||
```
|
||||
|
||||
Only helpers of the current prefix whose parent is `init`/`systemd` are
|
||||
terminated. Helpers whose launcher is still alive, other prefixes, the game,
|
||||
Riot/League/CEF, `darlingserver` and the runtime are never touched. A dry run
|
||||
is available from the configured tools directory:
|
||||
|
||||
```sh
|
||||
RCLOL_CLEAN_STALE_HELPERS=1 python3 build/runtime/tools/clean-stale-helpers.py --dry-run
|
||||
```
|
||||
|
||||
### Client window stays black after a match
|
||||
|
||||
The League client keeps its Chromium/CEF UI (`LeagueClientUx` and its helpers)
|
||||
alive during a match. Riot's own "Close client during game" option closes that UI
|
||||
on Windows, but under Darling the pre-match instance keeps running: the client the
|
||||
backend starts when the match ends is a second CEF instance on the same
|
||||
`--user-data-dir`, and its window never paints. Its DOM and audio work and the
|
||||
window stays black. Closing the client by hand during the match leaves the same
|
||||
state for the same reason.
|
||||
|
||||
An opt-in helper closes that UI itself, from the host, and only while a match
|
||||
runs, so the client that appears afterwards is the only instance:
|
||||
|
||||
```sh
|
||||
RCLOL_CLOSE_CLIENT_IN_GAME=1 ./scripts/run.sh
|
||||
```
|
||||
|
||||
This is not a memory measure: the UI is closed because it competes with the game
|
||||
for CPU during the match. Measured on a four-core host with a Radeon RX 550, a
|
||||
practice match reported roughly 120 FPS with the client UI running and roughly
|
||||
180 FPS with it closed. On that host a local supervisor using the same detection
|
||||
closed 13 client processes (2027 MiB resident) when a practice match started, and
|
||||
the client came back as one instance with a painted window when it ended; the
|
||||
helper described here has not been through a live match yet. Riot's own option is
|
||||
the equivalent on Windows, so leave either one disabled if the in-client chat is
|
||||
wanted during a match.
|
||||
|
||||
The helper is off by default and fails closed: it attributes both the game and the
|
||||
client to the launcher's prefix through the loader's `__mldr_sockpath` (guest
|
||||
processes do not carry `DPREFIX`), never closes a client younger than 60 seconds
|
||||
(the one the backend starts after a match), leaves the Riot client, the League
|
||||
backend and the game untouched, and takes no action when ownership, the
|
||||
environment or the stat fields cannot be read. The client UI is recognised by the
|
||||
name of its executable, not by the command line: `LeagueCrashHandler` processes
|
||||
carry the client UI name in their arguments only, so they are left alone. It
|
||||
leaves by itself ten minutes after the last client or match is gone (the clock does
|
||||
not run before the first client appears, so a long patch or login is fine). Diagnose it in one
|
||||
observation, without waiting for a match:
|
||||
|
||||
```sh
|
||||
python3 tools/close-league-client-in-game.py --once --dry-run
|
||||
```
|
||||
|
||||
### Automatic client handling during a match
|
||||
|
||||
A single opt-in switch reads the client's own preferences and chooses the mode:
|
||||
|
||||
```sh
|
||||
RCLOL_CLIENT_IN_GAME_TRICKS=1 ./scripts/run.sh
|
||||
```
|
||||
|
||||
It reads `lol-user-experience` from the installed client's
|
||||
`LCULocalPreferences.yaml`:
|
||||
|
||||
| Client preference | Launcher action |
|
||||
|---|---|
|
||||
| `potatoModeEnabled: false` or unreadable | no client-in-game helper |
|
||||
| `potatoModeEnabled: true`, close preference missing or invalid | no action |
|
||||
| `potatoModeEnabled: true`, `closeLeagueClientDuringGame: true` | close the client UI |
|
||||
| `potatoModeEnabled: true`, `closeLeagueClientDuringGame: false` | hide the client windows |
|
||||
|
||||
The hide path unmaps the `LeagueClientUx` and `Riot Client` X11 windows and maps
|
||||
them again when the game process ends. The client is not closed, the League
|
||||
backend and Riot client keep running, and the game window is never hidden. The
|
||||
close path uses the UI-close helper described above. The hide helper exits after
|
||||
ten minutes without a client or match once it has seen one; a missing or invalid
|
||||
close preference selects no helper.
|
||||
|
||||
The low-level `RCLOL_HIDE_CLIENT_IN_GAME=1` and
|
||||
`RCLOL_CLOSE_CLIENT_IN_GAME=1` switches remain available when the automatic
|
||||
switch is not set. All modes are off by default. The helpers are
|
||||
window-management only: they do not patch client binaries, CEF, anti-cheat or
|
||||
DRM. They attribute the game to this prefix through the loader's
|
||||
`__mldr_sockpath`, and fail closed when ownership, proc state, environment or
|
||||
window information cannot be read. The hide helper requires an X11 or Xwayland
|
||||
desktop; on a native Wayland window manager without Xwayland it cannot find the
|
||||
windows.
|
||||
|
||||
On a CPU-bound host the hide effect appears after CEF has been hidden for a few
|
||||
seconds. In a local 30-second test on an i5-3470 with a Radeon RX 550, hiding
|
||||
`LeagueClientUx` reduced the summed CPU of the matched client/CEF processes from
|
||||
120.7% to 93.4% (about 23%, or 27 percentage points, of one core); showing the
|
||||
window returned it to 119.9%. This is not a universal FPS or percentage claim:
|
||||
a game that is GPU-bound, a client with no active UI work, or a different
|
||||
desktop may show less benefit. The hidden window can also need a moment to
|
||||
repaint after it is shown.
|
||||
|
||||
With client optimizations enabled and close-during-game enabled, the close
|
||||
helper remains the more aggressive option when in-client chat during the match
|
||||
is not needed.
|
||||
|
||||
Diagnose the match detector without changing any window:
|
||||
|
||||
```sh
|
||||
python3 tools/hide-league-client-in-game.py --once
|
||||
```
|
||||
|
||||
### Riot reports NO INTERNET FOUND while the host is online
|
||||
|
||||
Older base runtimes ship Google DNS servers (`8.8.8.8` and `8.8.4.4`), which
|
||||
some mobile and managed networks block. The base resolver now links to the
|
||||
host's `/etc/resolv.conf` through `/Volumes/SystemRoot`, including when the host
|
||||
file is a symlink into `/run` for systemd-resolved or NetworkManager. File and
|
||||
symlink replacements remain visible without copying the resolver on launch.
|
||||
|
||||
Exit Riot and League, then run `./scripts/update.sh` to rebuild and install both
|
||||
the base link and the host symlink handling correction. Existing prefix DNS
|
||||
overrides in `$DPREFIX/private/etc/resolv.conf` (normally
|
||||
`~/.darling/private/etc/resolv.conf`) remain in effect; remove a previous manual
|
||||
override with Darling stopped if you want to follow the host instead. A custom
|
||||
prefix `/etc` directory can also shadow the base `/etc -> private/etc` link.
|
||||
|
||||
Check resolution inside the guest with:
|
||||
|
||||
```sh
|
||||
darling shell curl --noproxy '*' https://clientconfig.rpg.riotgames.com/
|
||||
```
|
||||
|
||||
An HTTP response establishes that resolution succeeded even if that endpoint
|
||||
rejects the request. This change follows the host resolver configuration;
|
||||
already-running clients may cache DNS settings until restarted.
|
||||
|
||||
### Downloads repeatedly fail with an IPv6 CONNECT_TO error
|
||||
|
||||
The bundled downloader was observed retrying CDN chunks with:
|
||||
@@ -155,12 +340,39 @@ reference before callback delivery. This establishes the cancellation fix; a lon
|
||||
Riot session still needs confirmation. With both clients and the game closed,
|
||||
run `./scripts/update.sh` to install it, then `sh scripts/run.sh`.
|
||||
|
||||
## League and Riot UIs crash after clicking Play
|
||||
|
||||
The League UX processes and then the Riot Client renderer stop with `SIGTRAP`
|
||||
a few seconds after the lobby opens. The renderer prints
|
||||
`Check failed: kr == KERN_SUCCESS. mach_make_memory_entry_64: (os/kern) resource shortage (6)`
|
||||
from `platform_shared_memory_region_mac.cc`. darlingserver backs every named
|
||||
memory entry with a memfd, and its open descriptors peak at about 1100 when the
|
||||
lobby opens. Under the rootless NixOS launcher darlingserver cannot raise its
|
||||
hard `RLIMIT_NOFILE` to `nr_open`, so it used to keep the soft limit it
|
||||
inherited. When that limit was 1024 (several Electron apps and desktop launchers
|
||||
pass it on), `memfd_create` failed with `EMFILE`.
|
||||
|
||||
darlingserver now raises its soft limit to the inherited hard limit when it
|
||||
cannot raise the hard one. A failed named-entry creation also logs
|
||||
`MEMDIAG … failed errno=… nofile=soft/hard` to `dserver.log`, and every 1024th
|
||||
memfd logs `MEMDIAG descriptors live=…` so a leak would show as a growing `live`
|
||||
count. With a 1024 soft limit the old server reproduced the crash and logged
|
||||
`errno=24 nofile=1024/524288`. With the fix, the server reports a 524288 soft
|
||||
limit under the same launch. To check a running server, read the `Max open files`
|
||||
line of `/proc/$(pgrep -x darlingserver)/limits`.
|
||||
|
||||
## UI crashes after a button opens an external link
|
||||
|
||||
LaunchServices dereferenced a nullable error-output pointer in six paths. The patch checks the pointer itself and populates supplied outputs; the standalone regression covers missing handlers and null outputs. Electron opens LaunchServices through an absolute CoreServices subframework path, so the paired installer also replaces that binary in the stopped guest prefix. Updating only `RuntimeFrameworks/LaunchServices.framework` can leave the crash unfixed.
|
||||
|
||||
LaunchServices now opens `http`, `https` and `mailto` links on the Linux desktop itself. It loads the host's `libsystemd.so.0` and asks `xdg-desktop-portal` (`org.freedesktop.portal.OpenURI`) on the user session bus to open the link, so your default browser starts outside Darling and survives `darling shutdown`. Without a portal, it starts `xdg-open` in a transient systemd user unit instead. It also reports a handler for these schemes, since Electron checks for one before opening a link. Other schemes, control characters and URLs over 4096 bytes are rejected. If a link still does nothing, check the client's stderr for a `LaunchServices:` line and that `busctl --user status org.freedesktop.portal.Desktop` works on the host. Both LaunchServices and AppKit must be rebuilt. The hand-off passed the host-side D-Bus regression and opened the host browser from a guest through both `open(1)` and `NSWorkspace`; a link clicked in the Riot client is not yet confirmed. The null-pointer fix does not explain every unrelated button crash.
|
||||
|
||||
## Riot client shows "Not Found" instead of its UI
|
||||
|
||||
On affected setups, including the reported NixOS installation, the client window opens but only shows `Not Found`, and its log has `ENOENT, dist/renderer/index.html not found in …/app.asar` although the archive contains that file. Electron's asynchronous file opening can copy an archive entry to a temporary file first (`copyFileOut`), and a failed copy can be reported as "not found". Chromium checks `MAC_CHROMIUM_TMPDIR` before falling back to `NSTemporaryDirectory()`; an unusable default temporary directory can therefore produce this symptom even while synchronous archive reads work.
|
||||
|
||||
The launcher defaults `MAC_CHROMIUM_TMPDIR` to `/private/tmp`, preserving a non-empty override. `scripts/install-runtime.sh` creates that guest directory with mode `1777`. Verify directory creation and temporary-file writes with a standalone guest probe, then launch the client normally and check whether its UI loads. Do not run diagnostic scripts through Riot's executable. This workaround addresses temporary-file failures; an actually missing or damaged archive needs separate investigation.
|
||||
|
||||
## Language dropdown closes Riot, or client resize leaves a clipped UI
|
||||
|
||||
The [report in issue #1](https://github.com/lucannez64/darling-lol/issues/1#issuecomment-6194498757) contains an Electron renderer abort with `Symbol not found: _CTFontDescriptorCreateMatchingFontDescriptors`. CoreText now exports font-descriptor matching and localized-attribute lookup, and enumerates installed font families through fontconfig instead of returning a null stub. Localized lookup currently falls back to canonical font names. The standalone font-list check exercises Chromium's sequence, including mandatory family matching and retained results.
|
||||
@@ -253,7 +465,7 @@ It prints each thread-local access that calls its thunk without the descriptor i
|
||||
|
||||
## Base install refuses to continue while Darling is not running
|
||||
|
||||
`sudo cmake --install` prints `Darling currently running for <user>, shutting it down...` several times and then `Darling launchd PID … remains alive after shutdown`. A guest launchd outlived its darlingserver, so `darling shutdown` sees no container to stop. `python3 build/runtime/tools/ensure-darling-runtime.py` stops such a launchd (your own, only when no darlingserver runs); `scripts/run.sh` and `scripts/install-runtime.sh` run it too.
|
||||
`sudo cmake --install` prints `Darling currently running for <user>, shutting it down...` several times and then `Darling launchd PID … remains alive after shutdown`. A guest launchd outlived its darlingserver, so `darling shutdown` sees no container to stop. Automatic cleanup by process name is disabled because guest names and UIDs do not prove prefix ownership (killing by name risks terminating an active launchd from an unrelated prefix or session). If setup is blocked, shut down the original Darling session from its own namespace (`darling shutdown`), or inspect the leftover PID under `/proc/<PID>` before terminating it manually.
|
||||
|
||||
## Lower fight FPS than Windows
|
||||
|
||||
@@ -272,11 +484,25 @@ On Hyprland, also check that direct scanout is off (`hyprctl getoption render:di
|
||||
On the NVIDIA proprietary driver, check the submit worker's kernel time: a thread of the game process spending most of its time in the kernel (`/proc/<pid>/task/<tid>/stat` system time, or `perf trace -s -p <pid>`) points at driver calls. Each `vkQueueSubmit2` and each CPU-side `vkSignalSemaphore` is an RM control ioctl (command `0xc020462a`) that queues on a driver-wide lock. Before 2026-10-18 every command buffer signalled the event loop's wake-up semaphore, ~25 times per frame; with lazy wake-ups (default) and a 5 ms batch window (`/tmp/metal-submit-batch-us` = `5000`, `/tmp/metal-submit-batch-max` = `64`) an RTX 4090 ran at 206–552 FPS against 91–147 FPS with `/tmp/metal-no-lazy-wakeup`, in alternating 20 s phases of one match (the later phases with wake-ups on were capped at 240 FPS) (see [setup](SETUP.md#metal-and-indium-overlays)).
|
||||
|
||||
The NVIDIA performance figures above were reported on an earlier runtime base;
|
||||
they have not been remeasured after integration with current `main`.
|
||||
they have not been remeasured after integration with current `main`. On `bcb3245` an RTX 5060 Laptop reported ~200–215 FPS in Practice Tool ([#53](https://github.com/lucannez64/darling-lol/issues/53)).
|
||||
|
||||
## NVIDIA
|
||||
|
||||
With the window visual and compositor draw buffer fixes (#22) and the RPC socket fix (#25), the game renders and runs on these NVIDIA GPUs:
|
||||
|
||||
| GPU | Driver | Report | Result |
|
||||
|---|---|---|---|
|
||||
| GTX 1060 6GB | 580.178.04 | [below](#nvidia-580-pascal-and-older-black-game-window) | client and tutorial |
|
||||
| RTX 2070 SUPER | 615.78, proprietary | [#55](https://github.com/lucannez64/darling-lol/issues/55) | tutorials, Co-op vs AI, Swiftplay and Draft games; no crashes |
|
||||
| RTX 3060 | 615.71 | [below](#nvidia-resize-signals-cpu-readback-and-completion-latency) | custom matches through CPU readback |
|
||||
| RTX 5060 Laptop | 615.71.09, open modules | [#53](https://github.com/lucannez64/darling-lol/issues/53) | Practice Tool at ~200–215 FPS on `bcb3245`; MUX set to discrete, PRIME variables set |
|
||||
| RTX 5070 | 615.78.08, open modules | [#69](https://github.com/lucannez64/darling-lol/issues/69) | tutorial |
|
||||
|
||||
If the game window stays black on NVIDIA, check `metal-device.log` as described in [Check which GPU rendered](#check-which-gpu-rendered).
|
||||
|
||||
## Hybrid graphics laptops (AMD/Intel iGPU + NVIDIA)
|
||||
|
||||
Metal renders with Vulkan, and each finished frame is handed to an OpenGL (EGL) context that presents it. The hand-off uses opaque file descriptors, which only work when Vulkan and OpenGL run on the same GPU. Indium takes the first Vulkan device the loader lists, and on a hybrid laptop both that device and the EGL context are normally the integrated GPU, so the discrete GPU stays idle. A report in [issue #3](https://github.com/lucannez64/darling-lol/issues/3) (Ryzen 9 5900HS, RTX 3060 Laptop) measured 60-70 FPS in ARAM on the integrated GPU; on the NVIDIA GPU the game showed a black screen. **Running on an NVIDIA GPU is not validated.**
|
||||
Metal renders with Vulkan, and each finished frame is handed to an OpenGL (EGL) context that presents it. The hand-off uses opaque file descriptors, which only work when Vulkan and OpenGL run on the same GPU. Indium takes the first Vulkan device the loader lists, and on a hybrid laptop both that device and the EGL context are normally the integrated GPU, so the discrete GPU stays idle. A report in [issue #3](https://github.com/lucannez64/darling-lol/issues/3) (Ryzen 9 5900HS, RTX 3060 Laptop) measured 60-70 FPS in ARAM on the integrated GPU; on the NVIDIA GPU the game showed a black screen, since fixed (see [NVIDIA](#nvidia)).
|
||||
|
||||
### Run on the discrete GPU
|
||||
|
||||
@@ -286,7 +512,12 @@ Start the launcher with PRIME render offload so that both Vulkan and OpenGL move
|
||||
prime-run sh scripts/run.sh
|
||||
```
|
||||
|
||||
`prime-run` sets `__NV_PRIME_RENDER_OFFLOAD=1`, `__VK_LAYER_NV_optimus=NVIDIA_only` and `__GLX_VENDOR_LIBRARY_NAME=nvidia`. `darling` copies the caller's environment into the guest, and guest processes inherit it; whether the Riot client passes all of it to the game has not been checked; the log below shows the result. Darling's OpenGL uses EGL, so if the log below still shows the integrated GPU as the presenting renderer, also set `__EGL_VENDOR_LIBRARY_FILENAMES=/usr/share/glvnd/egl_vendor.d/10_nvidia.json`. On two Mesa GPUs, `DRI_PRIME=1` does the same.
|
||||
`prime-run` sets `__NV_PRIME_RENDER_OFFLOAD=1`, `__VK_LAYER_NV_optimus=NVIDIA_only` and `__GLX_VENDOR_LIBRARY_NAME=nvidia`. `darling` copies the caller's environment into the guest, and guest processes inherit it; whether the Riot client passes all of it to the game has not been checked; the log below shows the result. Darling's OpenGL uses EGL, so if the log below still shows the integrated GPU as the presenting renderer, also set `__EGL_VENDOR_LIBRARY_FILENAMES=/usr/share/glvnd/egl_vendor.d/10_nvidia.json`. On two Mesa GPUs, `DRI_PRIME=1` does the same. The RTX 5060 Laptop in [#53](https://github.com/lucannez64/darling-lol/issues/53) rendered with all four variables set:
|
||||
|
||||
```sh
|
||||
__NV_PRIME_RENDER_OFFLOAD=1 __VK_LAYER_NV_optimus=NVIDIA_only __GLX_VENDOR_LIBRARY_NAME=nvidia \
|
||||
__EGL_VENDOR_LIBRARY_FILENAMES=/usr/share/glvnd/egl_vendor.d/10_nvidia.json sh scripts/run.sh
|
||||
```
|
||||
|
||||
The guest file `/tmp/metal-gpu` (a device index or part of its name, for example `nvidia`) moves only the Vulkan side. Use it for diagnosis: on its own it splits the two sides across GPUs, and frames cannot be presented.
|
||||
|
||||
@@ -320,9 +551,9 @@ The first import of a drawable into the presenting OpenGL context, and the first
|
||||
[MetalDevice] pid=… drawable GL semaphore (first) import_error=0x0 wait_error=0x0 ok (checked for the first 600 imports)
|
||||
```
|
||||
|
||||
A non-zero error or `FAILED` means the frames cannot reach the window. Before 2026-10-16 the import did not mark the memory as dedicated, which the extension requires; Mesa ignores it, and it is the suspected cause of the NVIDIA black screen (not yet confirmed). An NVIDIA log of 2026-10-16 showed `glTiling=optimal` with `setup_error=0x501 storage_error=0x501 FAILED`: NVIDIA's GL rejects `GL_LINEAR_TILING_EXT`, so the linear Vulkan image could not be imported. Since then the internal image and the GL texture use optimal tiling when `GL_VENDOR` contains NVIDIA (`vkTiling=optimal glTiling=optimal ok` is the expected line); everything else keeps linear. If the game shows a black screen on NVIDIA, report it with this file.
|
||||
A non-zero error or `FAILED` means the frames cannot reach the window. Before 2026-10-16 the import did not mark the memory as dedicated, which the extension requires; Mesa ignores it. It was suspected of causing the NVIDIA black screen; the causes found later were the window visual and the draw buffer (#22). An NVIDIA log of 2026-10-16 showed `glTiling=optimal` with `setup_error=0x501 storage_error=0x501 FAILED`: NVIDIA's GL rejects `GL_LINEAR_TILING_EXT`, so the linear Vulkan image could not be imported. Since then the internal image and the GL texture use optimal tiling when `GL_VENDOR` contains NVIDIA (`vkTiling=optimal glTiling=optimal ok` is the expected line); everything else keeps linear. If the game shows a black screen on NVIDIA, report it with this file.
|
||||
|
||||
An additional NVIDIA report on `68f816d` showed successful optimal-tiling import but repeated `Metal Device timed out giving us a Drawable to present` messages. Its [presentation trace](https://github.com/lucannez64/darling-lol/issues/3#issuecomment-6214732591) shows all three drawables queued and all three main-thread presentation callbacks running, followed by timeouts with three frames still queued. The compositor only prepared a Metal layer when it was the root layer, skipping nested Metal layers. It now prepares Metal layers throughout the tree. A standalone nested-layer reproduction exhausts the pool before this correction and passes ten presentations with pixel readback after it; the root-layer probe also passes. Confirmation on the reporter's NVIDIA system is still needed.
|
||||
An additional NVIDIA report on `68f816d` showed successful optimal-tiling import but repeated `Metal Device timed out giving us a Drawable to present` messages. Its [presentation trace](https://github.com/lucannez64/darling-lol/issues/3#issuecomment-6214732591) shows all three drawables queued and all three main-thread presentation callbacks running, followed by timeouts with three frames still queued. The compositor only prepared a Metal layer when it was the root layer, skipping nested Metal layers. It now prepares Metal layers throughout the tree. A standalone nested-layer reproduction exhausts the pool before this correction and passes ten presentations with pixel readback after it; the root-layer probe also passes. Issue #3 has since been closed, and the game renders on NVIDIA (see [NVIDIA](#nvidia)).
|
||||
|
||||
### NVIDIA resize signals, CPU readback and completion latency
|
||||
|
||||
@@ -348,6 +579,48 @@ with sway/Xwayland using CPU readback. The initial polling change improved their
|
||||
reported rate from about 20 to 27 FPS. Those observations predate the narrowed
|
||||
integration; they do not establish hybrid-GPU or general NVIDIA support.
|
||||
|
||||
### NVIDIA 580 (Pascal and older): black game window
|
||||
|
||||
Driver 580 is the last branch for Pascal (GTX 10-series) and older GPUs. With
|
||||
`__GL_THREADED_OPTIMIZATIONS=1`, this driver with egl-x11 1.0.6 rejects the X11
|
||||
`EGLDisplay` with `EGL_BAD_DISPLAY` on every thread that has a context current.
|
||||
The display stays valid on other threads, and `eglReleaseThread()` makes it work
|
||||
again on the failing one. A small native EGL program outside Darling shows the
|
||||
same: `eglQueryString(display, EGL_VENDOR)` after `eglMakeCurrent` returns
|
||||
`NVIDIA` without the variable and fails with `EGL_BAD_DISPLAY` with it.
|
||||
|
||||
In the Riot client the main thread loses EGL right after its first window frame;
|
||||
`CGLCreateContext` then fails with CGL error 10004 and, if another display is
|
||||
opened, glvnd hands it to Mesa's software renderer, which cannot present the
|
||||
game's NVIDIA Vulkan frames. The launcher therefore leaves threaded optimizations
|
||||
off (`__GL_THREADED_OPTIMIZATIONS=0`). Setting the variable to `1` before launch
|
||||
restores the old behaviour.
|
||||
|
||||
Tested on a GTX 1060 6GB with driver 580.178.04 on NixOS, Hyprland and Xwayland:
|
||||
the client and the tutorial match render on the NVIDIA GPU. Newer drivers were not
|
||||
retested with the option off.
|
||||
|
||||
### Metal device name and memory budget
|
||||
|
||||
`MTLDevice` reports the host GPU: its Vulkan name in macOS style (for example
|
||||
`AMD Radeon RX 6800 XT (RADV NAVI21)` becomes `AMD Radeon RX 6800 XT`) and a working set
|
||||
(`recommendedMaxWorkingSetSize`) from the GPU's Vulkan memory heaps. Apps size their GPU
|
||||
allocations from the working set, so only override it to match the card's real memory.
|
||||
Both can be overridden per launch:
|
||||
|
||||
```sh
|
||||
DARLING_METAL_GPU_NAME='AMD Radeon Pro 5500M' DARLING_METAL_VRAM_MB=4096 ./scripts/run.sh
|
||||
```
|
||||
|
||||
The Riot launcher sets `DARLING_METAL_MAC_GPU_NAME=1`: the name becomes the GPU that
|
||||
the MacBookPro16,1 IOKit reports shipped with, from the host's vendor: `AMD Radeon Pro 5500M`
|
||||
on AMD and `Intel UHD Graphics 630` on Intel. NVIDIA GPUs keep their own name, so the game
|
||||
never takes AMD or Intel driver paths on an NVIDIA card. The working set is still the host's.
|
||||
`DARLING_METAL_MAC_GPU_NAME=0 ./scripts/run.sh` reports the host name instead; other Darling
|
||||
apps report it unless they set the variable. `DARLING_METAL_GPU_NAME` wins over both.
|
||||
|
||||
`registryID` is a fixed non-zero value, as on a real Mac.
|
||||
|
||||
### Host-side checks
|
||||
|
||||
These are host settings, not game settings. Run on AC power, and use a performance power profile (`powerprofilesctl set performance`, or `asusctl profile -P Performance` on ASUS laptops). On the tested desktop the game is limited mostly by its own main thread on the CPU, not by the GPU (see [status](STATUS.md#game-fps-2026-10-1314)), so laptop CPU power limits matter as much as the GPU choice.
|
||||
@@ -423,6 +696,43 @@ complete Hyprland window record (`class`, `title`, `mapped`, `hidden`). A second
|
||||
process scan after window inspection must find the same client identities and
|
||||
no game. Missing information or shutdown failure leaves normal launch in charge.
|
||||
|
||||
## Variable refresh rate (VRR)
|
||||
|
||||
Full-window borderless game layers now use the opaque managed X11 parent
|
||||
surface for presentation. Previously they swapped an independent child while
|
||||
the managed parent remained static. Using one surface lets the compositor
|
||||
track the game's updates for VRR and avoids scanning out an empty parent.
|
||||
Partial views, titled windows and other applications retain child drawables.
|
||||
The child fallback retains the compositor-bypass prohibition that prevents
|
||||
black screens. The fullscreen parent path advertises `_VARIABLE_REFRESH=1`;
|
||||
leaving fullscreen clears that request. Desktop/driver policy still decides
|
||||
whether to enable adaptive sync.
|
||||
|
||||
With the game and both clients closed, rebuild with `./scripts/update.sh`;
|
||||
CoreGraphics, QuartzCore and the X11 backend must be updated together. Enable
|
||||
VRR in the desktop and monitor, and compare the monitor's live refresh reading
|
||||
to a frame cap below its maximum refresh, initially with VSync off. VRR and
|
||||
VSync are separate: this change does not implement an in-game frame limiter.
|
||||
See [the VRR probe and manual checks](TESTING.md#vrr-presentation-regression-46).
|
||||
|
||||
Standalone child, parent and nested-parent presentation probes passed locally;
|
||||
monitor-level VRR and the reporter's in-game behavior still need confirmation.
|
||||
|
||||
## VSync and frame caps (#46)
|
||||
|
||||
The Metal layer's `displaySyncEnabled` property previously had no effect on
|
||||
the window swap interval. A game that delegates pacing to VSync could run
|
||||
uncapped when VSync was enabled. The compositor now applies that property to
|
||||
the window before swapping, including nested Metal layers and setting changes.
|
||||
If several Metal layers share a window, any layer requesting VSync enables it
|
||||
for that window. Other windows retain independent swap settings.
|
||||
|
||||
Exit the game and both clients, then run `./scripts/update.sh` to rebuild and
|
||||
install the QuartzCore and OpenGL changes. Until updated, disable in-game
|
||||
VSync to use the game's frame cap, as the issue reporter confirmed. Physical
|
||||
scanout timing still depends on the driver and desktop compositor; the fix
|
||||
has standalone regression coverage but needs an in-game timing retest.
|
||||
|
||||
## Installer aborts on `/proc/<pid>/fdinfo`
|
||||
|
||||
[Issue #38](https://github.com/lucannez64/darling-lol/issues/38) reports an
|
||||
|
||||
Reference in new issue
Block a user