The client does not come back properly after a match when its own "Close client
during game" option is enabled. The UI (LeagueClientUx and its helpers) is a
Chromium/CEF instance and, 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 a match leaves the same
state for the same reason.
RCLOL_CLOSE_CLIENT_IN_GAME=1 opts in to closing that UI from the host, and only
while a match runs, so the client that appears afterwards is the only instance.
The UI also competes with the game for CPU: on a four-core host with a Radeon
RX 550 a practice match reported roughly 120 FPS with the UI running and roughly
180 FPS with it closed. This is not the helper-killing memory optimization the
RAM section warns against, and the Riot client, the League backend and the game
are never touched.
The helper fails closed. It attributes the game and the client to the launcher's
prefix through the loader's __mldr_sockpath, since guest processes do not carry
DPREFIX (checked on a live session). It never closes a client younger than 60
seconds (the one the backend starts after a match), preserves a process of
another installation or owner, acts only when the environment and the stat fields
can be read, gives up after three closes in one match, and leaves after ten
minutes without a client or a match. It is off by default.
Tests use fake /proc entries and mock every signal.
Stage a cross-filesystem copy in a private directory instead of deleting a
pre-existing PATH.partial, reject destinations inside the overlay work
directory, save the new path before deleting the old prefix, and verify that
the target keeps user.* xattrs before and after copying. Add tests.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Stage a cross-filesystem copy in a private directory instead of deleting a
pre-existing PATH.partial, reject destinations inside the overlay work
directory, save the new path before deleting the old prefix, and verify that
the target keeps user.* xattrs before and after copying. Add tests.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The prefix holds the installed clients and game (about 40 GB). The Nix
launcher always exported DPREFIX=${DPREFIX:-~/.darling}, so the prefix saved
in build/config.json was never used and the only way to move it was an
environment variable in every launch context.
- The launcher uses DPREFIX if set, then the saved prefix, then ~/.darling.
- darling-lol --set-prefix PATH runs scripts/nix-prefix.py in the namespace,
whose root owns the prefix files, so cp -a keeps their owners and xattrs.
It renames the prefix when possible; across filesystems it copies to
PATH.partial and removes the old prefix and its overlay work directory
only after the copy completes. A PATH that already holds a Darling prefix
is used as it is. It refuses non-empty directories that are not prefixes,
nested paths and paths with commas or colons (overlayfs separators), then
saves the path with configure.py --prefix.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The prefix holds the installed clients and game (about 40 GB). The Nix
launcher always exported DPREFIX=${DPREFIX:-~/.darling}, so the prefix saved
in build/config.json was never used and the only way to move it was an
environment variable in every launch context.
- The launcher uses DPREFIX if set, then the saved prefix, then ~/.darling.
- darling-lol --set-prefix PATH runs scripts/nix-prefix.py in the namespace,
whose root owns the prefix files, so cp -a keeps their owners and xattrs.
It renames the prefix when possible; across filesystems it copies to
PATH.partial and removes the old prefix and its overlay work directory
only after the copy completes. A PATH that already holds a Darling prefix
is used as it is. It refuses non-empty directories that are not prefixes,
nested paths and paths with commas or colons (overlayfs separators), then
saves the path with configure.py --prefix.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
With DARLING_METAL_MAC_GPU_NAME=1, MTLDevice.name is the GPU the
MacBookPro16,1 that IOKit reports shipped with, from the host's vendor:
"AMD Radeon Pro 5500M" (vendor 0x1002) or "Intel UHD Graphics 630"
(0x8086). NVIDIA keeps its own name so the game never takes another
vendor's driver paths. recommendedMaxWorkingSetSize and every other value
stay the host's, which was the risky part of #61's identity.
The Riot launcher turns it on by default (0 or empty turns it off); other
Darling apps keep the host name. DARLING_METAL_GPU_NAME still wins.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
With DARLING_METAL_MAC_GPU_NAME=1, MTLDevice.name is the GPU the
MacBookPro16,1 that IOKit reports shipped with, from the host's vendor:
"AMD Radeon Pro 5500M" (vendor 0x1002) or "Intel UHD Graphics 630"
(0x8086). NVIDIA keeps its own name so the game never takes another
vendor's driver paths. recommendedMaxWorkingSetSize and every other value
stay the host's, which was the risky part of #61's identity.
The Riot launcher turns it on by default (0 or empty turns it off); other
Darling apps keep the host name. DARLING_METAL_GPU_NAME still wins.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
system_profiler now answers SPHardwareDataType and SPSoftwareDataType (and
lists them in -listDataTypes). Model, serial number, platform UUID and
provisioning UDID come from IOKit's IOPlatformExpertDevice via ioreg; CPU,
cache and memory from sysctl; the OS version from sw_vers and uname. A
field the guest does not provide is left out.
#61 instead read a separate darling-smbios.conf written by a new
generator (gen-darling-smbios.py), with hardcoded fallbacks (an Intel Core
i9, macOS 14.7, SIP enabled). iokitd reads darling-platform.plist, so
system_profiler and IOKit could report different serials and models on the
same prefix, and the generator overwrote identities imported with
configure-darling-platform.py. Reading the guest's own APIs keeps a single
source of truth: configure-darling-platform.py remains the only way to set
or provision an identity, and it is still never run by install or launch.
tests/test_system_profiler.py runs the script under sh with stub guest
commands and runs in CI.
Split from #61.
Co-authored-by: DevWebeloper <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
system_profiler now answers SPHardwareDataType and SPSoftwareDataType (and
lists them in -listDataTypes). Model, serial number, platform UUID and
provisioning UDID come from IOKit's IOPlatformExpertDevice via ioreg; CPU,
cache and memory from sysctl; the OS version from sw_vers and uname. A
field the guest does not provide is left out.
#61 instead read a separate darling-smbios.conf written by a new
generator (gen-darling-smbios.py), with hardcoded fallbacks (an Intel Core
i9, macOS 14.7, SIP enabled). iokitd reads darling-platform.plist, so
system_profiler and IOKit could report different serials and models on the
same prefix, and the generator overwrote identities imported with
configure-darling-platform.py. Reading the guest's own APIs keeps a single
source of truth: configure-darling-platform.py remains the only way to set
or provision an identity, and it is still never run by install or launch.
tests/test_system_profiler.py runs the script under sh with stub guest
commands and runs in CI.
Split from #61.
Co-authored-by: DevWebeloper <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
From #61, without the default "AMD Radeon Pro 5500M" / 4 GB identity: the
host GPU's name and Vulkan working set stay the default for every app, and
the existing DARLING_METAL_GPU_NAME and DARLING_METAL_VRAM_MB overrides
remain the opt-in. Reporting 4 GB on a 2 GB card invites allocation
failures, and under-reports larger cards.
Kept from #61:
- registryID returns MTLDeviceInfo::kDefaultRegistryID instead of 0; a
real Mac never reports 0 and apps key devices by it.
- Vertex buffer layout and attribute arrays release the old item after
retiring it; the retire step read the item after the release could free it.
- newDepthStencilState, newSamplerState and the device name return nil (or
the fallback name) when Indium throws, instead of unwinding through
Objective-C.
- MTLDepthStencilGetIndium is declared only with DARLING_METAL_ENABLED.
- The replaceRegion trace probes at most `width` bytes, which stays inside
the first row for every pixel format.
metal.patch is regenerated in its existing file order, so the diff is
these changes only. tests/test_metal_device_info.py compiles the host
check against MTLDeviceInfo.h as shipped in the patch and runs in CI.
Split from #61.
Co-authored-by: DevWebeloper <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
From #61, without the default "AMD Radeon Pro 5500M" / 4 GB identity: the
host GPU's name and Vulkan working set stay the default for every app, and
the existing DARLING_METAL_GPU_NAME and DARLING_METAL_VRAM_MB overrides
remain the opt-in. Reporting 4 GB on a 2 GB card invites allocation
failures, and under-reports larger cards.
Kept from #61:
- registryID returns MTLDeviceInfo::kDefaultRegistryID instead of 0; a
real Mac never reports 0 and apps key devices by it.
- Vertex buffer layout and attribute arrays release the old item after
retiring it; the retire step read the item after the release could free it.
- newDepthStencilState, newSamplerState and the device name return nil (or
the fallback name) when Indium throws, instead of unwinding through
Objective-C.
- MTLDepthStencilGetIndium is declared only with DARLING_METAL_ENABLED.
- The replaceRegion trace probes at most `width` bytes, which stays inside
the first row for every pixel format.
metal.patch is regenerated in its existing file order, so the diff is
these changes only. tests/test_metal_device_info.py compiles the host
check against MTLDeviceInfo.h as shipped in the patch and runs in CI.
Split from #61.
Co-authored-by: DevWebeloper <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
After the clients quit, the session kept Darling running: Riot and League
crash handlers never exit under Darling, and the idle check counted them as
clients. Ctrl+C or closing the terminal killed bubblewrap, whose
--die-with-parent then killed nix-lifecycle.py before it could shut Darling
down, which left a live server marker behind.
- nix-idle.py ignores crash handlers when deciding whether a client still
uses the prefix.
- The launcher starts bubblewrap with INT, QUIT, HUP and TERM ignored, and
env restores them inside. The supervisor turns them into a stop request.
- nix-lifecycle.py polls for a signal or stop request and then shuts Darling
down without waiting for clients. It stops retrying a failed shutdown once
the server is gone, and waits for or kills its own command afterwards.
- darling-lol --stop makes the stop request and waits for the session. If
the session does not exit within 30 seconds, or no session holds the lock,
it kills the prefix's darlingserver and launchd like darling shutdown,
after checking that the marker PID is this user's darlingserver for this
prefix.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
After the clients quit, the session kept Darling running: Riot and League
crash handlers never exit under Darling, and the idle check counted them as
clients. Ctrl+C or closing the terminal killed bubblewrap, whose
--die-with-parent then killed nix-lifecycle.py before it could shut Darling
down, which left a live server marker behind.
- nix-idle.py ignores crash handlers when deciding whether a client still
uses the prefix.
- The launcher starts bubblewrap with INT, QUIT, HUP and TERM ignored, and
env restores them inside. The supervisor turns them into a stop request.
- nix-lifecycle.py polls for a signal or stop request and then shuts Darling
down without waiting for clients. It stops retrying a failed shutdown once
the server is gone, and waits for or kills its own command afterwards.
- darling-lol --stop makes the stop request and waits for the session. If
the session does not exit within 30 seconds, or no session holds the lock,
it kills the prefix's darlingserver and launchd like darling shutdown,
after checking that the marker PID is this user's darlingserver for this
prefix.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
RCLOL_FILTER_HOST_ENV=1 makes exec-guest.py drop host session metadata
(WAYLAND_DISPLAY, XDG_SESSION_*, XDG_CURRENT_DESKTOP, GNOME/KDE/GDM,
systemd, SSH agent, terminal, toolkit, Flatpak/Snap/AppImage, exported
shell functions) and set SHELL to the guest's /bin/zsh. It is off by
default; RCLOL_KEEP_HOST_ENV lists names to keep anyway.
Unlike the denylist in #61, the filter runs after the launcher sets its
GPU tuning, keeps everything Darling and the host graphics and audio
libraries read (DISPLAY, XAUTHORITY, XDG_RUNTIME_DIR and the XDG base
directories, D-Bus, PulseAudio/PipeWire/ALSA, DRI_PRIME, MESA_*, __GL_*,
__NV_*, VK_*, LD_PRELOAD for gamemoderun, LD_LIBRARY_PATH), and lives in
one place instead of three.
tests/test_exec_guest.py runs in CI; the launcher check also verifies
that MESA_NO_ERROR=1 and __GL_THREADED_OPTIMIZATIONS=0 reach the guest
with the filter on.
Split from #61.
Co-authored-by: DevWebeloper <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
RCLOL_FILTER_HOST_ENV=1 makes exec-guest.py drop host session metadata
(WAYLAND_DISPLAY, XDG_SESSION_*, XDG_CURRENT_DESKTOP, GNOME/KDE/GDM,
systemd, SSH agent, terminal, toolkit, Flatpak/Snap/AppImage, exported
shell functions) and set SHELL to the guest's /bin/zsh. It is off by
default; RCLOL_KEEP_HOST_ENV lists names to keep anyway.
Unlike the denylist in #61, the filter runs after the launcher sets its
GPU tuning, keeps everything Darling and the host graphics and audio
libraries read (DISPLAY, XAUTHORITY, XDG_RUNTIME_DIR and the XDG base
directories, D-Bus, PulseAudio/PipeWire/ALSA, DRI_PRIME, MESA_*, __GL_*,
__NV_*, VK_*, LD_PRELOAD for gamemoderun, LD_LIBRARY_PATH), and lives in
one place instead of three.
tests/test_exec_guest.py runs in CI; the launcher check also verifies
that MESA_NO_ERROR=1 and __GL_THREADED_OPTIMIZATIONS=0 reach the guest
with the filter on.
Split from #61.
Co-authored-by: DevWebeloper <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A terminal, IDE or service manager can leave descriptors open in the
launcher, and the guest inherited all of them. tools/exec-guest.py closes
every descriptor above stderr and then execs the guest command.
A shell loop over /proc/self/fd cannot do this portably: dash only parses
single-digit descriptors in redirections, so "exec 10>&-" runs a command
named 10 and ends the launcher on Debian and Ubuntu.
The launcher offline check now leaks descriptors 3, 4, 12 and 200 into the
launcher and asserts the guest sees only 0, 1 and 2. It runs in CI.
Split from #61.
Co-authored-by: DevWebeloper <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A terminal, IDE or service manager can leave descriptors open in the
launcher, and the guest inherited all of them. tools/exec-guest.py closes
every descriptor above stderr and then execs the guest command.
A shell loop over /proc/self/fd cannot do this portably: dash only parses
single-digit descriptors in redirections, so "exec 10>&-" runs a command
named 10 and ends the launcher on Debian and Ubuntu.
The launcher offline check now leaks descriptors 3, 4, 12 and 200 into the
launcher and asserts the guest sees only 0, 1 and 2. It runs in CI.
Split from #61.
Co-authored-by: DevWebeloper <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Chromium on macOS takes its temp directory from NSTemporaryDirectory(), which is
unusable under Darling. Electron opens files in app.asar asynchronously by
copying them to a temp file, and reports the failed copy as "not found", so the
Riot client showed a "Not Found" page instead of its UI. Running the client
binary as Node reproduces it: fs.readFileSync on app.asar/dist/renderer/index.html
works, fs.createReadStream fails with ENOENT, and MAC_CHROMIUM_TMPDIR fixes it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Chromium on macOS takes its temp directory from NSTemporaryDirectory(), which is
unusable under Darling. Electron opens files in app.asar asynchronously by
copying them to a temp file, and reports the failed copy as "not found", so the
Riot client showed a "Not Found" page instead of its UI. Running the client
binary as Node reproduces it: fs.readFileSync on app.asar/dist/renderer/index.html
works, fs.createReadStream fails with ENOENT, and MAC_CHROMIUM_TMPDIR fixes it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
With __GL_THREADED_OPTIMIZATIONS=1, NVIDIA 580 (the last branch for Pascal
GPUs) with egl-x11 1.0.6 rejects the X11 EGLDisplay with EGL_BAD_DISPLAY on
every thread that has a context current. A native EGL program reproduces it
outside Darling. In the Riot client the main thread loses EGL after its first
window frame, so CGLCreateContext fails with error 10004 and the game window
stays black. Default the launcher to 0; setting the variable restores 1.
CGLDestroyWindow also returns early for the NULL window CGLGetWindow gives
when eglCreateWindowSurface fails, instead of passing EGL_NO_SURFACE on.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
With __GL_THREADED_OPTIMIZATIONS=1, NVIDIA 580 (the last branch for Pascal
GPUs) with egl-x11 1.0.6 rejects the X11 EGLDisplay with EGL_BAD_DISPLAY on
every thread that has a context current. A native EGL program reproduces it
outside Darling. In the Riot client the main thread loses EGL after its first
window frame, so CGLCreateContext fails with error 10004 and the game window
stays black. Default the launcher to 0; setting the variable restores 1.
CGLDestroyWindow also returns early for the NULL window CGLGetWindow gives
when eglCreateWindowSurface fails, instead of passing EGL_NO_SURFACE on.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Keep the merged per-device completion budget, bounded parsing, idle blocking and timeout accounting. Scope the registration/blocking handshake to each device, restrict live batch windows to the timer resolution, and test registration interleavings, spin-disabled polling, shutdown, opt-out and independent devices with actual patched methods.
Co-authored-by: SypherrDev <[email protected]>
Keep the merged per-device completion budget, bounded parsing, idle blocking and timeout accounting. Scope the registration/blocking handshake to each device, restrict live batch windows to the timer resolution, and test registration interleavings, spin-disabled polling, shutdown, opt-out and independent devices with actual patched methods.
Co-authored-by: SypherrDev <[email protected]>
Ignore both invalidation methods on the shared session; deliver task completion through the owning session delegate queue. Extend the standalone probe to exercise singleton reuse and a suspended custom queue. Require stable old-client process identities and complete window data before optional recovery, and continue launch after shutdown failures.
Ignore both invalidation methods on the shared session; deliver task completion through the owning session delegate queue. Extend the standalone probe to exercise singleton reuse and a suspended custom queue. Require stable old-client process identities and complete window data before optional recovery, and continue launch after shutdown failures.
Rebuild the remaining NVIDIA changes on current main, preserving the merged visual, draw-buffer, descriptor ownership and mixed Metal-tree fixes. Scope native-thread forwarding to resize notifications and make completion polling per device, bounded and part of the caller timeout budget.
Co-authored-by: Pirrandi <[email protected]>
Rebuild the remaining NVIDIA changes on current main, preserving the merged visual, draw-buffer, descriptor ownership and mixed Metal-tree fixes. Scope native-thread forwarding to resize notifications and make completion polling per device, bounded and part of the caller timeout budget.
Co-authored-by: Pirrandi <[email protected]>