A long patch or login can keep the League client closed for more than ten
minutes after launch; the helper then left before the first match.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Mesa derives its shader disk cache from $HOME. Inside the guest that is
/Users/<user>, which does not exist on the host filesystem, so Mesa could not
create the cache and disabled it, once per process:
Failed to create /Users/<user> for shader cache (Permission denied)---disabling
One launcher log contained 212 of those 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, spending CPU at startup for no
gain.
Mesa is host code loaded into the guest and reads MESA_SHADER_CACHE_DIR verbatim,
so point it at a host directory; Mesa creates mesa_shader_cache/ (client and game
shaders) and radv_builtin_shaders/ below it. A non-empty value from the caller is
preserved, and a directory that cannot be created leaves the previous behaviour
instead of ending the launch.
Observed on a Radeon RX 550 with radv: no shader-cache failure after the change,
and 434 files / 5.6 MB written during one client start, still growing while the
client ran. The cache is reused from the second start on, which lowers the
client's CPU use while it starts; the startup-time gain itself has not been
measured separately.
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.
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]>
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]>
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]>
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.
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]>
Security reads the system trust domain from
/System/Library/Keychains/SystemRootCertificates.keychain and
SystemTrustSettings.plist (TrustSettingsSchema.h). Darling ships neither, so
SecTrustSettingsCopyCertificates(kSecTrustSettingsDomainSystem) returns
errSecNoTrustSettings (-25263); Chromium's known_roots_mac and Riot's
CertBundle then report that they cannot read the system roots.
ensure-darling-root-certificates.py parses each host root with a minimal DER
walker and writes the trust record keyed by the uppercase SHA-1, with the
encoded issuer Name, the serial INTEGER's content octets, modDate and an
empty (unrestricted) trustSettings array. The keychain is built with the
guest security tool in one darling shell, in a private temporary directory,
and published from inside the guest so a running Darling sees it, falling
back to the host when the guest cannot write the directory. A digest stamp
skips later runs without starting Darling. The launcher warns and continues
on failure; install-runtime.sh runs it before stopping Darling.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-[MTLDevice name] returned "Darling Indium Device" for every GPU. It now
returns the selected Vulkan device's name in macOS Metal style: the
trailing driver note in brackets and the (R)/(TM) marks are removed, and
NVIDIA names are unchanged. The name is never replaced with another
vendor's. recommendedMaxWorkingSetSize is the device-local heap size on
discrete GPUs and all heaps on integrated/CPU devices, with the old 1 GiB
as fallback. Both are computed once per device.
hasUnifiedMemory stays YES by default: synchronizeResource:/
synchronizeTexture: are missing and Private buffers created with data
abort in Indium, so reporting NO on discrete GPUs would send apps down
unimplemented paths. DARLING_METAL_HOST_UNIFIED_MEMORY=1 opts in to the
GPU-type answer. DARLING_METAL_GPU_NAME and DARLING_METAL_VRAM_MB
override the name and working set.
The cleaning logic lives in private-include/Metal/MTLDeviceInfo.h and is
covered by the host check tools/test-metal-device-info.cpp. Metal now
links indium_private for the Vulkan properties, and the runtime build
script adds Indium's private headers before compiling MTLDevice.mm.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
League enters relative mouse mode on GNOME (Xwayland) and inside gamescope, but
keeps a visible cursor and warps it every frame at its own mouse speed. Xwayland
cannot move a visible pointer, so those warps never took effect: the virtual
cursor, where clicks land, drifted hundreds of pixels from the cursor on screen
and never reached the screen edges, which also stopped edge panning.
Where warps are not honoured, the virtual cursor now follows the physical
pointer and the game's warps leave it in place. With a hidden cursor, or on a
server that honours warps, nothing changes. The host model test gains a case
replaying the drift from an in-game trace.
Tested manually in the practice tool under gamescope 3.16.31 on GNOME 51 with
AMD RADV: clicks land on the cursor and edge panning works.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Port the fullscreen policy omitted when adapting PR #12. Track requested fullscreen state across frame and style changes, exclude unmanaged windows, and use EWMH REMOVE when leaving fullscreen.
Extend the native geometry probe and document passive tracing for the reported vertical click offset, whose cause remains unconfirmed.
CGAssociateMouseAndMouseCursorPosition(false) used to warp the pointer to the
window center, freeze the reported position there and treat the game's own
CGWarpMouseCursorPosition calls as motion. On Xwayland, which ignores warps
while the cursor is visible, this left League's cursor stuck in the middle.
The backend now keeps a frozen virtual cursor: physical motion only produces
deltas (deltaY positive downwards), application warps move the virtual cursor
and their echoes are dropped, mouseLocation returns it, and the pointer is only
warped back where the server honours warps. The grab no longer uses the
invalid FocusChangeMask and is released and retaken across focus changes.
setCursor: no longer re-sends XDefineCursor/XFlush when the window already has
that cursor; League set it about 190 times per second.
Also carries the opt-in input trace (/tmp/rclol-input-trace in the guest).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Traverse the layer tree before compositing so nested Metal layers consume queued drawables and recycle their pool. Add a nested presentation probe covering pool reuse and Vulkan-to-OpenGL pixel readback.
NVIDIA's GL rejects GL_LINEAR_TILING_EXT with GL_INVALID_VALUE, so the
linear internal image could not be imported and the window stayed black.
Pick the tiling by GL_VENDOR; other drivers keep linear.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
League sets framebufferOnly=NO on its CAMetalLayer, and QuartzCore read
that as needing host-visible coherent memory, so the game's final
render target and the linear image handed to OpenGL both lived in
system RAM (type 2 on the RX 6600) and crossed PCIe every frame.
framebufferOnly=NO only lets Metal sample or blit the texture: nothing
maps either image (replaceRegion aborts, getBytes is a stub, Indium
maps only Shared/Managed buffers).
Drawables now take the first allowed DEVICE_LOCAL type whatever
framebufferOnly says; the guest file /tmp/metal-no-drawable-vram
restores the old placement. metal-device.log shows drawable-vram=on and
storage=vram.
A native EGL/RADV test exported the drawable's linear image from VRAM
and imported it into GL with no errors and identical pixels. In a
practice-tool baron fight on the RX 6600 with the same champion it ran
about 20 FPS faster and the same when idle; not yet measured in an ARAM.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Indium and QuartzCore took the first memory type a resource allowed. The
Vulkan spec orders a type with no flags before every DEVICE_LOCAL type,
and NVIDIA exposes such system-RAM types, so on NVIDIA private buffers,
textures and framebufferOnly drawables could land in system RAM. They
now take the first allowed DEVICE_LOCAL type and fall back to the old
choice; shared and managed resources are unchanged.
The drawable's exported image is a dedicated Vulkan allocation, but the
OpenGL import never set GL_DEDICATED_MEMORY_OBJECT_EXT, which
EXT_external_objects requires. Mesa ignores it; it is the suspected
cause of the black screen on NVIDIA in issue #3. The import's GL errors
were never checked (reportGLErrors is compiled out), so a failure was
silent.
/tmp/metal-device.log now lists the selected device's memory heaps and
types, the memory type chosen per resource kind (with the old choice and
changed=yes/no), and the result of the first drawable and semaphore
imports into OpenGL (later ones only on failure).
Practice match on the RX 6600: all choices changed=no, import
dedicated=yes with no GL errors, semaphore ok, match=yes. Not run on
NVIDIA.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
On a hybrid laptop (issue #3) Indium took whichever Vulkan device the
loader listed first, usually the integrated GPU, and still created a
VkDevice on every other supported GPU, waking a discrete GPU it never
used. Drawables reach the presenting OpenGL context as opaque FDs, which
only work on one physical device, and nothing checked that.
Indium now appends the device list, the selected device and the reason
to guest /tmp/metal-device.log, and creates a VkDevice for the selected
device only (/tmp/metal-all-devices restores the old behaviour).
/tmp/metal-gpu or METAL_INDIUM_GPU selects a device by index or name
for diagnosis. QuartzCore logs the presenting GL renderer and compares
its device UUID with the Vulkan device's (match=yes / MISMATCH); it
queries Vulkan itself, so it needs no new libindium export.
On the RX 6600 desktop the game selected RADV, created 1 VkDevice of 2
supported (lavapipe no longer gets one) and logged match=yes in a match.
Running on NVIDIA through prime-run is documented but not validated.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
LLVM 23 represents vector splats such as `<3 x float> zeroinitializer`
as vector-typed ConstantFP/ConstantInt values instead of
ConstantAggregateZero/ConstantDataVector. Iridium only handled scalar
ConstantFP/ConstantInt, so on hosts with LLVM 23 nearly every League
shader failed to translate: the r3dlog fills with "Failed to compile
shader", the loading screen hits "Material Missing Pipeline" and the
game crashes. Our matches had only run the overlay pinned to LLVM 22.
Declare the scalar element and splat it into a constant composite.
Offline, on the 74,112 metallibs in ShaderCache.metal.wad.client,
LLVM 23 went from ~2% to 74,108 translated, the same set as LLVM 22
(the 4 remaining failures are pre-existing on both); a sample of the
LLVM 23 SPIR-V passes spirv-val for Vulkan 1.3. Confirmed in a match
with the Iridium overlay built against the system LLVM 23.
RIOT_IRIDIUM_SYSTEM_LLVM=1 makes build-riot-indium-runtime.py ignore
the LLVM 22 pin, to build what a fresh checkout gets.
Refs #1
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Fix X11 presentation handoff after resizing, preserve NSView size callbacks, and correct content and backing-layer geometry. Add CoreText font enumeration for the language dropdown and rebuild/install the matching runtime overlays.
Correct CarbonCore Gestalt version queries and roll back proc_listpids for Vanguard testing. Document that VAN -101 can still recur. Include accumulated CoreAudio stream and AudioUnit corrections.
Add standalone font, version, resize and publisher-handoff regressions. Record manual confirmation of the language dropdown and window resizing; all 29 pinned patches and the source/script audit validate.
Drop the tooling that drives, automates, inspects, profiles, captures or
patches the Riot client and League game process, plus the historical
research/tools probes. These were never part of the supported workflow and
involve synthetic input, attaching to Riot's process, driving its local API,
or patching its binaries.
Removed from tools/: the baron-* and practice helpers, uinput_click.py,
lcu-practice-game.py, check-riot-vanguard.py, the riot-league-* / riot-open-url-*
inspector hooks and API/state helpers, the riot-*-watch.py observers,
riot-match-capture/ profilers, riot-cursor-watch/, the analyze-*/symbolize-*
profilers, capture-game-perf.py, the native VPX bridge and the client
log-dir/autofill shims. Removed all of research/tools/.
The supported launch/install pipeline is unchanged: scripts/, the launcher and
ensure-* helpers, fetch-installer.sh, stop-darling-runtime.py, the
build-*-runtime / build-m58f3* runtime builders, the install-* overlays and
offline installer checks, and the standalone operator/-check regression probes
are all retained. Docs that referenced the removed tools were updated.
iokitd passed an Xlib Display to eglGetDisplay(), so Mesa chose the
platform from EGL_PLATFORM. Wayland sessions often set it to "wayland",
and the first darling command passes its environment to every guest
service; Mesa then treated the Display as a wl_display, iokitd crashed
in pthread_mutex_lock and launchd restarted it every 10 seconds. Use
eglGetPlatformDisplay(EGL_PLATFORM_X11_KHR, ...) as OpenGL already does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
LaunchServices now hands http, https and mailto URLs to the host desktop
itself instead of NSWorkspace writing them to a spool that nothing in
the normal workflow drains. It loads the host libsystemd through the ELF
loader and calls xdg-desktop-portal OpenURI on the session bus, falling
back to xdg-open in a transient systemd user unit, so the browser starts
outside Darling's namespaces. LSGetApplicationForURL reports the host
opener for these schemes because Electron checks for a handler before
opening a link. NSWorkspace goes through LSOpenCFURLRef.
Tested with a host-side D-Bus regression (79 checks) and in a fresh
guest prefix through open(1) and NSWorkspace, both reaching the portal
with the exact URL.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Riot's installer puts the client in /Users/Shared/Riot Games and exits;
its LaunchServices hand-off to that client does not start it under
Darling, so the window just closes. The installer wrapper now launches
the installed client itself, and the runner links it to
/Applications/Riot Client.app when that link is missing.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The pinned src/external/swift/.lfsconfig points lfs.url at the retired
git-lfs.darlinghq.org, so a fresh `git submodule update --init --recursive`
blocks with no output right after src/external/sqlite is checked out: swift is
the next submodule in sorted order and its checkout waits on that host.
Override lfs.url to GitHub's endpoint, which still serves the objects (verified
44 objects / 16 MB, real Mach-O dylibs), and print explicit submodule progress so
the last completed submodule is not mistaken for the stalled one.
Two matches with the Indium hand-off round (narrow present wait, deferred
blits, worker-side command-buffer lease, skipping CPU Vulkan devices) ran
slower than before, with more main-thread futex waiting. Each change is now
behind a guest flag file and off by default, so the default libindium matches
the round-3 build (vertex-descriptor dirty masks, cheaper resource setters).
The wait counters stay opt-in, write a "waits enabled" line when they are on
and keep their state per image. The docs explain that flag files created from
the host while Darling runs may stay invisible through the overlay mount.
Also adds a passive cursor watcher. The stuck in-game cursor is Hyprland's
direct scanout, not Darling. The first match with direct scanout off averaged
135 FPS. STATUS, TROUBLESHOOTING and README are updated with the new matches.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Game process (ARAM Hexgate: 84 -> 127 FPS average, slowest stretches ~110):
- Local pthread mutexes, condition variables and Mach semaphores are on by
default in the game process only, with /tmp/rclol-no-local-* opt-outs.
The main thread's darlingserver waits fell from 1.36 to 0.10 ms per frame.
- Local semaphores: the count and the slot generation change in one
compare-exchange, a destroy leaves a negative marker, the table is reset
after fork, and names the table did not create go to the server.
- Metal: drop identical re-binds (keyed on the Indium object, depth-stencil
retained), pass blit resources by reference, and stop resetting vertex
descriptors twice.
- Core isolation of the main thread is opt-in (/tmp/rclol-core-isolation);
it lowered FPS in its only match.
- Optimized libc/libc++/Foundation overlays, AVX2 string primitives, real CPU
topology and their installers and offline checks.
Tools and docs:
- tools/riot-match-capture: passive /proc sampler, user-space IP profiler and
reports, plus a comparison against the game's netstats.
- tools/riot-lsem-model and tools/riot-metal-bind-model: host models that
extract the shipped code and check it.
- tools/install-riot-metal-pair.sh installs Metal and Indium together.
- Status, setup, testing and troubleshooting docs updated with the measured
matches and the switch table; manifest file counts match the patches.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Preserve floating-point arguments in GL resolvers, retain presentation across differently sized windows, export AppKit click and URL fixes, and guard nullable LaunchServices errors. Include compiler/build and installer corrections for the new PC.
Reuse server receive buffers with descriptor-safe exception cleanup and avoid disabled debug-string allocation. Translate Darwin free/reuse memory hints, add atomic server/library installers with separate user-owned memory backups, and preserve active processes until a manual namespace restart.
Remove automatic inspectors, client helpers and forced unlimited UI frames from normal launches. Retain historical hooks with offline mocks and document their exclusion, unresolved installer handoff, activation requirements and unmeasured FPS/RAM benefits.
Validation: all 29 pinned patches apply; all 461 exported source files match; host receive and memory checks, launcher and JavaScript mocks, syntax and documentation-link checks pass. No live client/game was controlled or restarted.