mirror of
https://github.com/lucannez64/darling-lol.git
synced 2026-10-26 19:47:42 +00:00
Fix Darling runtime performance and new-PC compatibility
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.
This commit is contained in:
1 parent
a96cdc62b1
commit
5609d96f16
31 files changed
+1530
-187
No files matched your search
@@ -2,21 +2,22 @@
|
||||
|
||||
Experimental source patches and tools for running the **macOS League of Legends client on x86-64 Linux through Darling**.
|
||||
|
||||
This is a cleaned snapshot of a working development setup, not a finished launcher. Riot and League clients can launch, practice/ARAM games have run, and graphics, audio, input and post-game freezes have received compatibility fixes. Fight FPS still varies: **90 FPS at the 99th percentile has not been achieved**. A clean build on another PC has not yet been validated.
|
||||
This is a development snapshot, not a finished launcher. Riot and League clients can launch and manually played ARAM games have run. Graphics, audio, input, startup crashes and memory reclamation have received compatibility fixes. On the Ryzen 5 5500 / RX 6600 PC, reported fight FPS was 70–80 versus 170–190 on Windows. The latest server and memory changes have native regression coverage, but their effect on game FPS and client RAM remains unmeasured. A fresh checkout has not been validated end to end on another machine.
|
||||
|
||||
## Included
|
||||
|
||||
- Pinned upstream revisions and patches for 29 Darling components, including Metal/Indium, AppKit, QuartzCore, CoreAudio, the kernel shim and darlingserver.
|
||||
- The latest event-poll presentation fix and compositor-thread semaphore import fix. The latter stopped one leaked GPU synchronization handle per frame in native tests.
|
||||
- Build/install/launch wrappers with configurable machine paths, native regression probes and performance tools.
|
||||
- Older experiments under `research/tools/`; these are retained for reference and are outside the normal launch path.
|
||||
- Event-poll presentation and GPU synchronization fixes, floating-point-safe GL resolvers, multi-window presentation and nullable LaunchServices error handling.
|
||||
- Reusable server receive buffers and Darwin allocator hints translated to Linux lazy reclamation.
|
||||
- Build/install/launch wrappers, atomic incremental installers and standalone regression probes.
|
||||
- Historical experiments in `tools/` and `research/tools/`, explicitly excluded from the normal workflow where they automate or inspect the client/game.
|
||||
|
||||
Game files, Riot installers, account databases, tokens, local prefixes, captures and compiled binaries are not included. Install the game through Riot on the second PC.
|
||||
Game files, Riot installers, account databases, tokens, local prefixes, captures and compiled binaries are not included. Install the game manually through Riot. The normal launcher does not enable an Electron inspector or start UI automation helpers. Existing runtime compatibility shims remain part of this experimental setup; this is not a claim of approval by Riot.
|
||||
|
||||
## Requirements
|
||||
|
||||
- x86-64 Linux, an X11 session or Xwayland, and a working hardware Vulkan/OpenGL driver. The development PC used AMD RADV; other drivers are not validated.
|
||||
- Git + Git LFS, CMake, Make, Clang/LLD, LLVM development files, a C/C++ compiler, Python 3.12+ and the Darling build dependencies. LLVM 22 was used for the tested shader translator.
|
||||
- x86-64 Linux **5.14 or newer**, an X11 session or Xwayland, and a working hardware Vulkan/OpenGL driver. AMD RADV has been exercised; other drivers are not validated.
|
||||
- Git + Git LFS, Bash, curl, unzip, CMake, Make, Clang/LLD, LLVM development files, a C/C++ compiler, Python 3.12+ and the Darling build dependencies. The patch includes LLVM 22/23 branch-opcode compatibility; the new PC used LLVM/Clang 23. Node.js is needed only for the offline JavaScript checks.
|
||||
- libvpx development files and X11/XCB/SHM development files for the current runtime helpers.
|
||||
- Enough free disk for Darling sources/build, Git LFS assets and the game: budget at least 50 GB. Building is substantially larger than this source-only repository.
|
||||
|
||||
@@ -47,31 +48,31 @@ After the runtime is installed:
|
||||
./scripts/installer.sh euw
|
||||
```
|
||||
|
||||
Sign in through Riot's own UI and install League. Then stop the client, apply the guarded visibility correction for the installed Electron build, and launch normally:
|
||||
Sign in and install League through Riot's own UI. For subsequent launches:
|
||||
|
||||
```sh
|
||||
python3 build/runtime/tools/stop-darling-runtime.py
|
||||
python3 build/runtime/tools/patch-riot-electron-visibility.py
|
||||
./scripts/run.sh
|
||||
```
|
||||
|
||||
The visibility correction verifies the exact original instruction bytes and refuses an unknown version. A future Riot update can require adapting this and other compatibility hooks.
|
||||
First installation and future Riot updates can still encounter compatibility gaps. An accepted Install action can fail native LaunchAgent registration and leave the download idle. The archived inspector-based workaround and Electron visibility binary patch are excluded from the manual workflow. See [troubleshooting](docs/TROUBLESHOOTING.md) for the known limitation and fixes that are in the runtime.
|
||||
|
||||
For another region, set `RCLOL_REGION` and `RCLOL_LOCALE` before launch. For example `RCLOL_REGION=NA RCLOL_LOCALE=en_US ./scripts/run.sh`. Linux display selection follows `$DISPLAY`; the legacy launcher fallback is `:1`.
|
||||
|
||||
## Testing
|
||||
|
||||
Start with native regression checks, then a practice game. See [testing](docs/TESTING.md). The practice helpers recognize two verified UI panel sizes, preserve dynamic camera settings and stop on lost focus/unknown geometry. They still drive real input: avoid controlling the game at the same time.
|
||||
Start with standalone regression checks, then play a match manually. See [testing](docs/TESTING.md). Do not attach a debugger, query client APIs, inject diagnostic hooks or automate client/game input. Legacy practice, profiling and inspector helpers are retained as historical source, not recommended launch or testing steps.
|
||||
|
||||
FPS counters, game presentation requests and physical display refresh are different measurements. Record average and p99 frame times, the active scene, graphics settings, charger/power state and whether the game stayed focused. See [current status](docs/STATUS.md).
|
||||
|
||||
For existing installations, [incremental update instructions](docs/SETUP.md#updating-an-existing-runtime) explain the server and memory installers, user-owned backups and the full Darling restart needed to activate replacements. Perform activation only after manually exiting the game and both clients.
|
||||
|
||||
## Layout
|
||||
|
||||
```
|
||||
scripts/ portable entry points
|
||||
upstream/manifest.json exact base revisions and patch ownership
|
||||
patches/ current source changes, one patch per repository
|
||||
tools/ active source templates and regression probes
|
||||
tools/ runtime templates, regression probes and legacy helpers
|
||||
research/tools/ historical experiments, not a supported workflow
|
||||
docs/ setup, testing, status and licensing notes
|
||||
build/ local sources, outputs and rendered tools (ignored)
|
||||
|
||||
+44
-2
@@ -16,8 +16,50 @@ The normal wrappers currently support the default `~/.darling` prefix and `/usr/
|
||||
|
||||
The build uses Unix Makefiles because the incremental runtime helpers read CMake `flags.make` and `link.txt`. Ninja is not a drop-in replacement for those helpers.
|
||||
|
||||
The supported build is x86-64 only (`TARGET_i386=OFF`). The patched OpenDirectory uses Objective-C ivars that the 32-bit fragile ABI cannot compile. Keep the empty `CMAKE_BUILD_TYPE` used by `scripts/build.sh`: selecting `Release` introduces `NDEBUG` while objc4 defines `OBJC_IS_DEBUG_BUILD=1`, causing a debug-macro mismatch. The base patch requests the macOS 10.15 Objective-C runtime code generation to avoid undefined `__NSArray0__struct` / `__NSDictionary0__struct` symbols with newer Clang. Indium handles both the LLVM 22 and LLVM 23 branch opcode names.
|
||||
|
||||
Linux 5.14 or newer is required for `MADV_POPULATE_WRITE`, used when Darwin's allocator reuses lazily reclaimable pages. The memory installer checks this requirement; do not bypass it.
|
||||
|
||||
The runtime installer requires native `pkg-config` entries for `vpx`, `x11`, `x11-xcb`, `xcb-shm` and `xext`, plus the base Darling SDK. Some bridge guards intentionally fall back to Chromium's own implementation when the installed client changes.
|
||||
|
||||
The launcher enables loopback-only Electron inspection on `127.0.0.1:9229` for its startup compatibility hooks. It reads local Riot API credentials from the running client when necessary; no credentials are stored in this repository. Keep logs and local captures private.
|
||||
Run configuration, builds and the per-user runtime installation as your ordinary account. Use sudo only for the base CMake installation and the incremental server installer. Running the whole build or per-user install under sudo can create root-owned files and configure the wrong guest home. `scripts/install-runtime.sh` initializes the default user prefix and creates `~/.darling/private/tmp` with mode `1777`, so the installer can write guest temporary files. It then shuts down Darling before replacing the paired runtime: first exit the game and both clients manually.
|
||||
|
||||
Build/install steps need substantial time and storage. The patch set was checked against all pinned base indexes on the development machine. An entirely fresh build and a game launch on the second PC remain to be tested; do not interpret patch applicability as proof that every distro/driver works.
|
||||
The normal launcher starts neither an Electron inspector nor client API/UI helpers. It retains the existing runtime libraries and compatibility shims, disables incompatible Chromium features, supplies an absolute log directory, and selects the League product. The first-time installer uses `--skip-to-install` and omits the installed client's `--app-root`, avoiding an immediate exit or inaccessible intro screen. Legacy inspector hooks, API helpers, input automation and client binary patches remain historical tools; do not use them in the manual workflow. Keep account data, logs and captures private.
|
||||
|
||||
## Updating an existing runtime
|
||||
|
||||
These commands assume the pinned, patched source tree and its configured Makefiles already exist. From the repository root, build the two components with one worker to reduce resource contention:
|
||||
|
||||
```sh
|
||||
cmake --build build/darling-src/build --target darlingserver system_kernel --parallel 1
|
||||
sudo python3 tools/install-riot-darling-server.py
|
||||
python3 tools/install-riot-memory-runtime.py
|
||||
```
|
||||
|
||||
These two Python installers derive paths from the checkout/configuration and may be run directly from `tools/`; tokenized framework builders still require their rendered copies. The server installer defaults to `/usr/local/bin/darlingserver`, saves a checksum-named backup under `build/backups/darlingserver`, and preserves active server mappings. The memory installer updates the guest `RuntimeLibraries` and per-prefix bootstrap copy of `libsystem_kernel.dylib`, plus `build/riot-libsystem_kernel.dylib` if present. It leaves the system SDK alone. Each replacement is staged and verified before installation; the memory installer prepares every destination before replacing any. Replacement is atomic per file, not a transaction across all files.
|
||||
|
||||
Memory backups default to **`build/memory-runtime-backups`**, separate from the potentially root-owned directory created by the sudo server installer. If a custom backup directory is unwritable, choose another with `--backup-dir /path/to/writable/backups`. See [permission troubleshooting](TROUBLESHOOTING.md#memory-installer-permission-denied). Neither installer stops or starts any process.
|
||||
|
||||
An on-disk update is not activation. Existing processes keep their old executable/library mappings, and Darling's active overlay can expose an old library even to a new guest process. After manually exiting the game and both Riot/League clients, restart the entire namespace:
|
||||
|
||||
```sh
|
||||
darling shutdown
|
||||
sh scripts/run.sh
|
||||
```
|
||||
|
||||
`darling shutdown` terminates the guest namespace; never run it during a match or while applications have work to save. Reopening only the client is insufficient. `scripts/run.sh` renders the current launcher templates for the next launch. No FPS/RAM improvement has yet been established for these incremental changes; see [status](STATUS.md) and [standalone checks](TESTING.md).
|
||||
|
||||
For GL resolvers, multi-window presentation and the LaunchServices crash fix, use the full paired installation from the README after exiting clients. It rebuilds the native GL wrapper and installs both GL library copies, as well as the LaunchServices absolute subframework path used by Electron. Copying only the OpenGL framework or only the flat LaunchServices override leaves an old library reachable.
|
||||
|
||||
Build/install steps need substantial time and storage. All 29 component patches were checked against their pinned base indexes. The runtime has been exercised on the new RX 6600 PC, but an entirely fresh checkout/build/install remains to be validated on another machine; patch applicability alone does not establish distro/driver support. Use `JOBS=2 ./scripts/build.sh` if the default `nproc` parallelism exceeds your available RAM. See [troubleshooting](TROUBLESHOOTING.md) before changing compiler flags or prefix ownership.
|
||||
|
||||
## Committing from a new PC
|
||||
|
||||
Building does not require a Git author identity, but committing does. If Git reports `Author identity unknown`, configure your own name and email in this repository (use your GitHub noreply address if desired):
|
||||
|
||||
```sh
|
||||
git config user.name "Your Name"
|
||||
git config user.email "your-commit-email"
|
||||
```
|
||||
|
||||
Omitting `--global` keeps these settings local to this checkout. Commit source patches, tools and docs; keep `build/`, binaries, backups, captures and account data ignored.
|
||||
+41
-7
@@ -1,13 +1,47 @@
|
||||
# Snapshot status — 2026-10-12
|
||||
# Snapshot status — 2026-10-13
|
||||
|
||||
This source snapshot includes current local modifications, including experiments that have not all been compared against a fresh clean build. It preserves a development runtime, not an upstream-ready patch series.
|
||||
This repository preserves a development runtime, including historical experiments. All 29 patches apply to their pinned revisions and the exported source matches the development tree for the patched files. A fresh checkout/build/install has not been validated end to end on another machine. The normal workflow uses manual client installation and play, without debugger attachment, client API calls or input automation.
|
||||
|
||||
Confirmed fixes include a lost wake in kernel workqueue semaphore handling, callback-safe CoreAudio stop, AppKit event handling/post-game controls, and delivering ready main-queue callbacks on every event poll while keeping expensive CF run-loop work throttled.
|
||||
## New PC: verified runtime changes
|
||||
|
||||
The presentation callback regression changed from 180 callbacks during 200 polls to 200 callbacks with zero late polls. Native pixel and input checks passed. The user subsequently reported no visible stutter, but still reported sharp fight FPS drops.
|
||||
The current machine uses a Ryzen 5 5500, RX 6600 and Linux 7.2.9. The source snapshot includes the build corrections needed with its newer Clang/LLVM: x86-64-only OpenDirectory, consistent objc4 debug macros, older Objective-C collection code generation and LLVM 22/23 branch opcode compatibility.
|
||||
|
||||
A separate presentation defect exported a GPU synchronization FD on the deferred worker while the GL context belonged to the main thread. The live game accumulated over 45,000 syncobj FDs. The corrected import runs on the compositor thread; native stress leaked zero handles over 120 frames, versus 120 on the old build. The installed game was checked at zero leaked syncobj handles and about 111 total open files.
|
||||
- **Video scaling:** generated ELF GL resolvers lost floating-point arguments on the first call. Chromium's first linear sampler request failed, leaving nearest sampling. Preserving registers fixes the native first-call sampler/color probe; the old wrapper fails both checks. An 800×450 checkerboard scaled to 640×360 changes to bilinear sampling.
|
||||
- **Startup presentation:** drawing another differently sized surface in the same GL context stopped the active async presenter. Those surfaces now fall back to readback without stopping it. The old build fails after three visible frames; the corrected native probe retains the presenter through 357 fallback frames and presents all 360 frames at about 60 FPS. User confirmation of the splash remains pending.
|
||||
- **UI clicks:** window dragging now examines the frontmost view by geometry, including Electron's control-region overlays that deliberately return nil from hit testing. Previously a click could reach the underlying draggable web view and start a drag instead of activating a control.
|
||||
- **External-link crashes:** six LaunchServices paths dereferenced nullable error-output pointers. They now check the pointers and populate supplied outputs. AppKit also implements the missing URL-opening configuration/completion methods. Native nullable-output checks passed. The paired installer covers Electron's absolute LaunchServices subframework path as well as the flat override.
|
||||
- **Installation:** the default prefix gets a writable `/private/tmp`; the first-time launcher skips the inaccessible native intro and avoids an installed-client `--app-root`. The fetcher accepts missing informational `Info.plist` metadata. A separate HTTP 409 LaunchAgent handoff failure remains unresolved in the manual launch path.
|
||||
|
||||
A post-fix, limited-ability Baron run measured 99.6 FPS average and 16.11 ms p99 (62.1 reciprocal FPS). Another spell-heavy attempt measured 85.7 FPS average and 23.51 ms p99 (42.5 reciprocal FPS); practice refresh/camera differences prevent treating these as a clean improvement comparison. These are short practice measurements, not an ARAM guarantee or a 90 FPS p99 result. Historical comparisons using a reset helper that alternated refresh toggles are unreliable.
|
||||
The historical update hook handles HTTP 409, interrupted `pending_verify` and active downloads in 28 mocked scenarios. Historical external-link forwarding was exercised before the workflow changed. Both inspector-based helpers are now disabled in normal launches. Native URL methods can queue requests, but no normal host spool watcher consumes them; host-browser forwarding is not established by the crash fix. Legacy Electron visibility patches and automated practice/profiling tools are excluded from recommended setup/testing.
|
||||
|
||||
The development system uses an AMD Barcelo integrated GPU, RADV, Xwayland/Hyprland and two 60 Hz displays without active VRR. Driver capabilities, CPU power sharing and display refresh can differ on another PC. No CPU-affinity or compositor configuration is installed automatically: the legacy `pin-game.sh` assumes this laptop's CPUs and must be adapted before use elsewhere.
|
||||
## FPS observation and server optimization
|
||||
|
||||
An 18-minute host observation found substantial Darling request overhead and repeated main-thread socket waits consistent with its RPC path. The user reported 100–120 FPS in quieter moments and 70–80 during fights, versus 170–190 on Windows. GPU utilization averaged about 61% with an 85% maximum. CPU temperatures were 92–94°C, with reported clocks averaging 4.13 GHz. Background clients remained busy; the server still used about 1.5 logical CPUs after the observed game exited.
|
||||
|
||||
Server-only CPU samples identified worker context switching and receive/message allocation as substantial costs. These observations do not establish the entire FPS gap, distinguish every intentional game wait from added RPC latency, or prove a per-frame cause.
|
||||
|
||||
The receive queue now retains unused receive slots, transfers real messages into the existing queue and adopts every returned control header before potentially throwing. This preserves received descriptor ownership even if deque allocation fails. Disabled debug logging no longer prepares PID/TID strings. Independent host socket checks reduce warmed empty receive allocations from 32 to zero and single-packet allocations from 64 to two. Normal and address/undefined-behavior sanitizer checks passed, including exception cleanup and descriptor ownership.
|
||||
|
||||
The normal launcher no longer enables a client inspector, starts scheduling/update/link helpers or forces unlimited UI frames. The user installed the new server atomically while the old server remained active. **No post-activation FPS gain has been measured.** Validate through manually played matches after a full Darling restart when the game and clients are closed.
|
||||
|
||||
## Memory reclamation and activation
|
||||
|
||||
A memory audit attributed roughly 4.3 GiB of proportional resident memory to the clients/runtime, with 50 crash-handler processes present. About 3 GiB was private anonymous memory/native heap, and graphics mappings accounted for about 238 MiB. A five-minute idle observation showed no rapid growth (Riot changed about 1 MiB, League about 10 MiB). All observed processes reported zero `LazyFree` memory.
|
||||
|
||||
Installed-library disassembly confirmed that Darwin `MADV_FREE`, `MADV_FREE_REUSABLE` and `MADV_FREE_REUSE` were successful no-ops. Apple malloc uses the free/reusable hints for freed page ranges. The patch maps free/reusable to Linux `MADV_FREE` and reuse to `MADV_POPULATE_WRITE`, cancelling lazy reclamation without changing surviving contents. Linux 5.14+ is required.
|
||||
|
||||
The kernel library built; host and independent guest probes marked all 4 MiB of their own buffer reclaimable and cancelled that state on reuse while preserving contents. The old implementation marked zero pages. Exported symbols and library dependencies remained unchanged. This establishes the reclamation fix, not the fraction of the clients' footprint it will recover. Lazy reclamation may leave RSS unchanged while RAM is available.
|
||||
|
||||
The memory installer initially failed because the sudo server installation had made `build/backups` root-owned. Its default is now user-owned `build/memory-runtime-backups`, and it stages every replacement before changing any destination. The corrected installer replaced the runtime library, per-prefix bootstrap copy and prepared artifact without stopping processes; all 75 existing process mappings retained their old inode.
|
||||
|
||||
A fresh standalone guest probe still saw the old implementation: the active overlay exposed the old library despite the host file having the new checksum. **The fixes are installed on disk and require a full namespace restart to activate.** After manually exiting the game and both clients, run `darling shutdown`, then `sh scripts/run.sh`. Neither installer performs that restart. Real-client RAM improvement remains unmeasured. See [setup](SETUP.md) and [troubleshooting](TROUBLESHOOTING.md).
|
||||
|
||||
## Earlier laptop results (historical)
|
||||
|
||||
The earlier AMD Barcelo laptop used RADV, Xwayland/Hyprland and two 60 Hz displays without active VRR. Results from its historical automated practice experiments are not current validation and those workflows are no longer recommended.
|
||||
|
||||
Confirmed earlier fixes include a lost wake in kernel workqueue semaphore handling, callback-safe CoreAudio stop, AppKit event handling/post-game controls and delivery of ready main-queue callbacks on every event poll while throttling expensive CF run-loop work. The presentation callback regression improved from 180 callbacks during 200 polls to 200 with zero late polls. Native pixel and input checks passed.
|
||||
|
||||
A deferred compositor path used to export one GPU synchronization FD per frame and lose ownership. Scoped ownership reduced a 120-frame native probe from 120 leaked FDs to zero; extended compositor probes also remained flat. A later practice capture had higher GPU utilization and flatter presentation intervals, without isolating a clean before/after performance comparison.
|
||||
|
||||
Historical short Baron runs measured 99.6 FPS average / 16.11 ms p99 and 85.7 FPS average / 23.51 ms p99 in a spell-heavy attempt. Refresh/camera differences and old reset helpers make those comparisons unreliable. They do not demonstrate a 90 FPS p99 result or an ARAM guarantee. Laptop-specific CPU affinity is not installed automatically; do not use `pin-game.sh` unchanged on another CPU.
|
||||
+68
-6
@@ -1,5 +1,22 @@
|
||||
# Testing on another PC
|
||||
|
||||
Run standalone probes and offline mocks; launch and play matches manually. Do not attach a debugger, query client APIs, inject diagnostic client/game hooks or automate input. Do not stop a running client/game to test a runtime change. Presentation probes should use their own windows with clients closed, since the native presentation fixtures use shared per-user target names.
|
||||
|
||||
## Patch and offline checks
|
||||
|
||||
After preparing the pinned source tree:
|
||||
|
||||
```sh
|
||||
python3 scripts/prepare.py --check-only
|
||||
python3 tools/riot-launcher-offline-check.py
|
||||
node tools/riot-league-update-check.js
|
||||
node tools/riot-open-url-check.js
|
||||
```
|
||||
|
||||
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. The JavaScript checks use mock Electron/filesystem/HTTP objects; they do not enable the legacy hooks in a real client. The installer handoff mock covers 28 scenarios, including HTTP 409, interrupted verification and preserving active downloads.
|
||||
|
||||
## Standalone guest probes
|
||||
|
||||
Build and install the base/runtime first. Native probes are built by the incremental runtime scripts. With the default guest username, run a probe like this (replace `me` and the host checkout path):
|
||||
|
||||
```sh
|
||||
@@ -15,21 +32,66 @@ Build additional probes when needed, for example:
|
||||
python3 build/runtime/tools/build-riot-metal-concurrency-check.py
|
||||
```
|
||||
|
||||
Useful probes:
|
||||
Useful probes (build tokenized Python helpers from `build/runtime/tools/`):
|
||||
|
||||
- `riot-launchservices-null-error-check`: nullable LaunchServices error outputs must not crash, and a supplied empty output must receive an error. Build with `build-riot-launchservices-check.py`; expected `nullable_outputs=1 error_populated=1 missing_handlers=1`.
|
||||
- `riot-appkit-pump-check`: every queued callback must arrive in its own event poll; expected `events=200 callbacks=200 late_polls=0`.
|
||||
- `riot-event-coalescing-check`: preserve key events and latest mouse motion.
|
||||
- `m327-cgl-resolver-check`: first calls through dynamically looked-up GL functions retain floating-point arguments; expected `first_sampler_linear=1 first_color_arguments=1 errors=0,0`. Build with `build-m58f327-opengl.py` and run with `run-riot-native-check.py m327-cgl-resolver-check`.
|
||||
- `m327-cgl-direct-check`: with `RCLOL_GPU_DIRECT_OTHER_WINDOW_TEST=1` and an owned 337x339 native target, interleave 338x339 fallback surfaces; expected `active_presenter_preserved=1 fallback_frames=357`. This needs the native presentation fixture; it is not a `run-riot-native-check.py` choice.
|
||||
- `riot-metal-presentation-check`: Vulkan clear reaches the GL texture, including late window/context attachment.
|
||||
- `riot-metal-concurrency-check`: submission ordering, resource lifetime and concurrent recording.
|
||||
|
||||
`RIOT_PRESENTATION_STRESS=1` makes the presentation probe run 120 frames. Force async encoding for a non-game probe with the guest flag `/tmp/metal-async-encode`, then remove it after testing. The game enables async encoding by default. The tested old path leaked 120 synchronization FDs over 120 frames; the fixed path leaked zero. Inspect the host process's `/proc/PID/fd` for `anon_inode:syncobj_file` if reproducing that test.
|
||||
`RIOT_PRESENTATION_STRESS=1` makes the presentation probe run 120 frames. With the game and clients closed, force async encoding for this standalone probe using the guest flag `/tmp/metal-async-encode`, then remove it. The tested old path leaked 120 synchronization FDs over 120 frames; the fixed path leaked zero. Inspect the probe's host `/proc/PID/fd` for `anon_inode:syncobj_file` if reproducing that test.
|
||||
|
||||
For gameplay, start a practice game through Riot. Check sound, keyboard/mouse buttons, fullscreen confinement, camera motion and returning to the client. Use the real UI; an unknown version/layout is a reason to stop automation and inspect it.
|
||||
## Host receive queue regression
|
||||
|
||||
The rendered `baron-setup.py` is for a fresh practice game. `baron-reset.py` is for an already-configured scene and deliberately preserves refresh toggles. The helpers expect a focused 1920x1080 game at global 0,0 and identify the two verified panel sizes before clicking. Auto-refresh settings are toggles; visually verify them rather than blindly repeating setup on an existing match.
|
||||
The receive-buffer regression runs entirely on independent host sockets:
|
||||
|
||||
Passive captures are opt-in. `CYCLE_FRAME_INTERVALS=1 sh build/runtime/tools/profile-cycle.sh` restarts the runtime and creates/reconnects to practice; do not run it during a real match. It enables bounded timing blocks after a 90-second warmup. Native capture files under the guest `/tmp` contain request/render/flush timestamps, not physical scanout times. Re-arm through the guest `/tmp/metal-frame-interval-repeat` flag. The current bounded recorder writes a block synchronously; its file-write boundary can distort isolated worst frames. Treat it as diagnostic instrumentation.
|
||||
```sh
|
||||
c++ -std=c++17 -O2 -pthread \
|
||||
-I build/darling-src/src/external/darlingserver/internal-include \
|
||||
tools/riot-host-receive-check.cpp \
|
||||
build/darling-src/src/external/darlingserver/src/message.cpp \
|
||||
-Wl,--wrap=malloc -o build/riot-host-receive-check
|
||||
build/riot-host-receive-check
|
||||
python3 tools/riot-launcher-offline-check.py
|
||||
```
|
||||
|
||||
Compare the same scene, ability activity and camera behavior with the correct charger connected. Do not report a paused, dead, unfocused or empty scene as a fight benchmark. Report p99 frame time as milliseconds; the reciprocal is a percentile-equivalent FPS, not the mean of the slowest 1% of frames.
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
## Memory reclamation regression
|
||||
|
||||
The memory-reclamation check links the actual Darling `sys_madvise` implementation to host syscall hooks and uses only its own 4 MiB anonymous allocation:
|
||||
|
||||
```sh
|
||||
python3 tools/build-riot-host-madvise-check.py
|
||||
build/logs/madvise-fix/host-madvise-check
|
||||
```
|
||||
|
||||
Expected: `reclaimable_KiB=4096 reuse_reclaimable_KiB=0 contents=preserved`. The old implementation returns success without marking any pages reclaimable. Darwin `MADV_FREE` and `MADV_FREE_REUSABLE` now map to Linux `MADV_FREE`; Darwin `MADV_FREE_REUSE` uses `MADV_POPULATE_WRITE` to cancel lazy reclamation while preserving surviving contents. The reuse operation requires Linux 5.14 or newer; an unsupported or invalid operation returns the translated kernel error rather than reporting false success. Reclamation is lazy and does not promise an immediate RSS decrease when RAM is available.
|
||||
|
||||
For the actual guest ABI, `tools/build-riot-madvise-check.py` builds an independent executable. After a full restart has activated the installed library, run it as follows; replace `me` and the checkout path:
|
||||
|
||||
```sh
|
||||
python3 tools/build-riot-madvise-check.py
|
||||
darling shell env \
|
||||
DYLD_LIBRARY_PATH=/Users/me/RuntimeLibraries \
|
||||
/Volumes/SystemRoot/path/to/darling-lol/build/logs/madvise-fix/riot-madvise-check
|
||||
```
|
||||
|
||||
Expected: `DARLING_MADVISE pass=1 reclaimable_KiB=4096 reuse_reclaimable_KiB=0 contents=preserved`. To test a candidate before installation, place only its `libsystem_kernel.dylib` in a private directory and prepend its guest-visible path to `DYLD_LIBRARY_PATH`. The candidate passed; the previous installed implementation failed with zero reclaimable pages. The probe uses only its own allocation and Linux memory counters, and never contacts a client/game. It tests free/reuse hints directly, not complete Apple malloc or client behavior under memory pressure.
|
||||
|
||||
Install the tested library with `python3 tools/install-riot-memory-runtime.py`. It uses user-owned `build/memory-runtime-backups` instead of the shared `build/backups` directory, which an earlier sudo server install may have made root-owned. It atomically replaces the two per-user kernel-library copies and the prepared runtime artifact, without stopping any process or changing the system SDK. The active Darling overlay can retain the old file even for new guest processes after an update to its backing directory; this was verified by differing host versus namespace checksums. After exiting the game and both clients manually, run `darling shutdown`, then `sh scripts/run.sh` to start a new namespace. Do not restart Darling during a match. Verify reclamation and client memory use after that restart.
|
||||
|
||||
## Manual game observation
|
||||
|
||||
Play matches manually through Riot. Check sound, keyboard/mouse buttons, fullscreen confinement, camera motion and returning to the client. The legacy `baron-setup.py`, `baron-reset.py`, `profile-cycle.sh`, inspector scheduling/update/URL helpers and Electron binary patching tools are excluded from this workflow. Normal launches enable none of these helpers and do not force unlimited UI frames. Automatic installer handoff and host external-link forwarding are consequently unavailable.
|
||||
|
||||
Observe host CPU/thread scheduling, GPU counters, temperatures and memory pressure without controlling the client/game. Record manual FPS readings with timestamps and comparable graphics settings. Do not interpret a socket wait as avoidable RPC latency without distinguishing intentional synchronization. Server-only CPU sampling identifies costs but does not measure per-frame causality.
|
||||
|
||||
Compare similar scene, ability and camera activity with the same power settings (and correct charger on a laptop). Do not report a paused, dead, unfocused or empty scene as a fight benchmark. If an allowed host measurement supplies frame times, report p99 in milliseconds; its reciprocal is percentile-equivalent FPS, not the mean of the slowest 1% of frames. Manual FPS readings alone do not establish p99 frame times.
|
||||
|
||||
The CPU symbolizer offers `--leaf-only` because the host DWARF unwinder can drop guest Mach-O stacks. Successfully unwound samples alone are not a representative profile. Local API lockfiles, account data and raw logs must stay outside version control.
|
||||
@@ -0,0 +1,83 @@
|
||||
# Troubleshooting
|
||||
|
||||
Use the [setup instructions](SETUP.md) from the repository root as your ordinary user. Exit the game and both clients manually before a full installation or Darling restart. Incremental server/memory installers can replace files without interrupting processes, but their changes need a later full restart to activate.
|
||||
|
||||
## Memory installer: permission denied
|
||||
|
||||
An earlier sudo server installation can create root-owned `build/backups`. The old memory installer tried to create `build/backups/memory-runtime` there and failed at `backup_folder.mkdir(...)`. The corrected default is a separate user-owned `build/memory-runtime-backups` directory:
|
||||
|
||||
```sh
|
||||
python3 tools/install-riot-memory-runtime.py
|
||||
```
|
||||
|
||||
For an explicitly chosen backup location:
|
||||
|
||||
```sh
|
||||
python3 tools/install-riot-memory-runtime.py --backup-dir "$HOME/darling-memory-backups"
|
||||
```
|
||||
|
||||
If the destination library itself is root-owned, inspect the named path and its ownership before retrying with sudo. Do not run all setup/build steps as root or recursively change ownership of the system SDK. Backups and binaries are ignored by Git and must stay local.
|
||||
|
||||
## Updated library or server still behaves like the old version
|
||||
|
||||
Atomic replacement preserves active process mappings. Darling also uses an overlay filesystem; updating its backing prefix from the host can leave an old file visible inside the active namespace. A fresh standalone guest check continued to report the old memory behavior despite the host path having the new checksum. Reopening only Riot does not replace this namespace.
|
||||
|
||||
After exiting the game and both clients manually:
|
||||
|
||||
```sh
|
||||
darling shutdown
|
||||
sh scripts/run.sh
|
||||
```
|
||||
|
||||
Do not run these commands during a match. Use the independent reclamation probe in [testing](TESTING.md) to verify the new guest behavior after activation. An installer result with `installed: true` and `restart_performed: false` confirms the disk update, not activation.
|
||||
|
||||
## Around 5 GB of RAM with just the clients open
|
||||
|
||||
The observed proportional resident footprint was roughly 4.3 GiB across Riot, League and Darling, including 50 crash-handler processes. Roughly 3 GiB came from private anonymous mappings/native heaps; graphics mappings accounted for about 238 MiB. Large virtual address reservations are not equivalent to resident RAM, and summed RSS double-counts shared pages. Use proportional set size (PSS), swap and host memory pressure to assess the actual footprint.
|
||||
|
||||
The old kernel library returned success for Darwin allocator free/reuse hints without making any pages reclaimable. The replacement maps free/reusable hints to Linux `MADV_FREE`, and reuse to `MADV_POPULATE_WRITE`. The standalone 4 MiB buffer becomes reclaimable and returns to normal on reuse while preserving surviving data. This fixes a demonstrated runtime behavior, but does not explain all client allocations or prove a specific RAM reduction. Linux may retain lazily reclaimable pages in RSS while RAM is available. A five-minute idle observation did not show rapid client memory growth.
|
||||
|
||||
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.
|
||||
|
||||
## 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.
|
||||
|
||||
If logs report guest `/private/tmp` permission errors, the default prefix needs its own temporary directory. The corrected full runtime installer creates it. To repair just this default-prefix directory as your ordinary user:
|
||||
|
||||
```sh
|
||||
mkdir -p "$HOME/.darling/private/tmp"
|
||||
chmod 1777 "$HOME/.darling/private/tmp"
|
||||
```
|
||||
|
||||
The installer fetcher no longer treats a missing archive `Info.plist` or the informational `unzip | head` listing as a fatal download failure. Download and extraction errors still fail. The wrapper invokes this fetcher with Bash, since its `pipefail` setting is unsupported by some `/bin/sh` implementations.
|
||||
|
||||
There is also a separate known compatibility gap: native LaunchAgent watch registration can return HTTP 409 after the League install path is accepted, leaving the download idle. A historical inspector hook handles this and interrupted `pending_verify` states in 28 mock cases, but that hook is disabled in normal launches and excluded from the manual workflow. There is no verified runtime-only replacement for that handoff in this snapshot. Do not assume the native permission and launcher fixes resolve every idle Install case, or enable debugger/API automation to work around it.
|
||||
|
||||
## 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.
|
||||
|
||||
Historical host-browser forwarding depended on an inspector hook and is disabled in normal launches. The native null-pointer fix prevents this demonstrated crash; it does not guarantee that every external link opens on the host or explain every unrelated button crash.
|
||||
|
||||
## Blocky video or flickering startup logo
|
||||
|
||||
The first dynamically resolved GL call could lose floating-point arguments during ELF symbol lookup. Chromium's linear sampler request then failed and left nearest sampling. The generated resolver now preserves those registers. Install both the rebuilt native `libGL.dylib` in `RuntimeLibraries` and the OpenGL framework's library copy using the paired runtime installer; the framework alone is insufficient.
|
||||
|
||||
A separate multi-window path stopped the active async presenter when another differently sized surface was drawn in the same context. Such surfaces now use readback without stopping it. Native regressions cover both fixes; user confirmation of the startup splash remains pending. See [testing](TESTING.md) for the first-call sampler and interleaved-window probes.
|
||||
|
||||
## Build fails on a new compiler or runs out of memory
|
||||
|
||||
- **objc4 debug macro mismatch:** use the empty build type from `scripts/build.sh`, rather than `Release`/`NDEBUG`.
|
||||
- **OpenDirectory 32-bit ivar errors:** keep `TARGET_i386=OFF`; the supported client is x86-64.
|
||||
- **Undefined empty Objective-C collection symbols:** retain the patched macOS 10.15 Objective-C runtime code generation.
|
||||
- **LLVM branch opcode errors:** retain the Indium LLVM 22/23 compatibility patch; do not replace its branch handling with an unconditional version-specific opcode.
|
||||
- **Build process killed:** lower parallelism, for example `JOBS=2 ./scripts/build.sh` or `--parallel 1` for an incremental component build.
|
||||
- **Incremental helper cannot find `flags.make` or `link.txt`:** use Unix Makefiles, not Ninja.
|
||||
- **Prebuilt Swift file contains Git LFS pointer text:** install Git LFS and fetch the original upstream assets; pointer text is not a usable library.
|
||||
|
||||
## Lower fight FPS than Windows
|
||||
|
||||
Host observation found substantial Darling CPU/RPC costs and main-thread socket waits, with reported fight FPS of 70–80 and quieter moments at 100–120 on the Ryzen 5 5500 / RX 6600. GPU utilization averaged about 61%; the CPU reached 92–94°C. These observations identify areas to investigate, not the full cause of every slow frame.
|
||||
|
||||
The server now retains unused receive buffers and avoids preparing disabled debug strings. The host socket fixture reduces warmed empty receive allocations from 32 to zero and single-packet allocations from 64 to two. Normal launches also stop forcing unlimited UI frames and inspector scheduling hooks. An FPS gain has not yet been measured after activation. Compare manually played matches with the same graphics settings and record CPU/GPU utilization, temperature and memory pressure. Do not use legacy automated practice or profiling cycles.
|
||||
+216
-25
@@ -20,7 +20,7 @@ index 393a1eb3..6a47ac89 100644
|
||||
xkbfile
|
||||
)
|
||||
diff --git a/AppKit/MacWorkspace.m b/AppKit/MacWorkspace.m
|
||||
index ba52cd3b..ad8ba2a2 100644
|
||||
index ba52cd3b..423c0716 100644
|
||||
--- a/AppKit/MacWorkspace.m
|
||||
+++ b/AppKit/MacWorkspace.m
|
||||
@@ -13,6 +13,32 @@
|
||||
@@ -56,6 +56,20 @@ index ba52cd3b..ad8ba2a2 100644
|
||||
- (NSImage *) iconForFile: (NSString *) path {
|
||||
// TODO: call GetIconRefFromFileInfo()
|
||||
NSUnimplementedMethod();
|
||||
@@ -94,10 +120,10 @@
|
||||
return NO;
|
||||
}
|
||||
|
||||
+extern BOOL RCLOLOpenURLViaHost(NSURL *url);
|
||||
+
|
||||
- (BOOL) openURL: (NSURL *) url {
|
||||
- // TODO: Call LSOpenFromURLSpec()
|
||||
- NSUnimplementedMethod();
|
||||
- return NO;
|
||||
+ return RCLOLOpenURLViaHost(url);
|
||||
}
|
||||
|
||||
- (BOOL) selectFile: (NSString *) path
|
||||
diff --git a/AppKit/NSAccessibility.m b/AppKit/NSAccessibility.m
|
||||
index 39d1b104..3662bc4b 100644
|
||||
--- a/AppKit/NSAccessibility.m
|
||||
@@ -1409,7 +1423,7 @@ index 8ac2bbdc..7486ebf5 100644
|
||||
|
||||
[_subviews
|
||||
diff --git a/AppKit/NSWindow.m b/AppKit/NSWindow.m
|
||||
index d545120a..9b995b5a 100644
|
||||
index d545120a..db2c12d9 100644
|
||||
--- a/AppKit/NSWindow.m
|
||||
+++ b/AppKit/NSWindow.m
|
||||
@@ -45,6 +45,20 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
@@ -1433,7 +1447,33 @@ index d545120a..9b995b5a 100644
|
||||
|
||||
const NSNotificationName NSWindowDidBecomeKeyNotification =
|
||||
@"NSWindowDidBecomeKeyNotification";
|
||||
@@ -218,7 +232,8 @@ static BOOL _allowsAutomaticWindowTabbing;
|
||||
@@ -204,6 +218,25 @@ NSInteger NSBitsPerPixelFromDepth(NSWindowDepth depth) {
|
||||
|
||||
@end
|
||||
|
||||
+/* darling-lol: whether a mouse-down may move the window must be decided by the frontmost view
|
||||
+ * under the pointer by GEOMETRY, not by -hitTest:. Electron implements draggable regions as a
|
||||
+ * web view that can move the window plus ControlRegionView overlays (mouseDownCanMoveWindow == NO)
|
||||
+ * on top of everything that must not drag; those overlays return nil from -hitTest: so the click
|
||||
+ * still reaches the page. Using the hitTest result here always found the web view, so every click
|
||||
+ * started a window drag and never reached the page. `point` is in `view`'s own coordinates. */
|
||||
+static NSView *DarlingFrontmostViewAtPoint(NSView *view, NSPoint point) {
|
||||
+ if (view == nil || [view isHidden] || !NSPointInRect(point, [view bounds]))
|
||||
+ return nil;
|
||||
+ NSArray *subviews = [view subviews];
|
||||
+ for (NSInteger i = (NSInteger)[subviews count] - 1; i >= 0; i--) {
|
||||
+ NSView *subview = [subviews objectAtIndex: i];
|
||||
+ NSView *found = DarlingFrontmostViewAtPoint(subview, [subview convertPoint: point fromView: view]);
|
||||
+ if (found != nil)
|
||||
+ return found;
|
||||
+ }
|
||||
+ return view;
|
||||
+}
|
||||
+
|
||||
@implementation NSWindow
|
||||
|
||||
@synthesize identifier = _identifier;
|
||||
@@ -218,7 +251,8 @@ static BOOL _allowsAutomaticWindowTabbing;
|
||||
/* This method is Cococtron specific and can be override by subclasses, do not
|
||||
* change method name */
|
||||
+ (BOOL) hasMainMenuForStyleMask: (NSWindowStyleMask) styleMask {
|
||||
@@ -1443,7 +1483,7 @@ index d545120a..9b995b5a 100644
|
||||
}
|
||||
|
||||
/* This method is Cococtron specific and can be override by subclasses, do not
|
||||
@@ -318,6 +333,7 @@ static BOOL _allowsAutomaticWindowTabbing;
|
||||
@@ -318,6 +352,7 @@ static BOOL _allowsAutomaticWindowTabbing;
|
||||
[NSApplication sharedApplication];
|
||||
}
|
||||
_styleMask = styleMask;
|
||||
@@ -1451,7 +1491,7 @@ index d545120a..9b995b5a 100644
|
||||
_backingType = backing;
|
||||
_isDeferred = defer;
|
||||
|
||||
@@ -351,7 +367,7 @@ static BOOL _allowsAutomaticWindowTabbing;
|
||||
@@ -351,7 +386,7 @@ static BOOL _allowsAutomaticWindowTabbing;
|
||||
[_backgroundView _setWindow: self];
|
||||
[_backgroundView setNextResponder: self];
|
||||
|
||||
@@ -1460,7 +1500,7 @@ index d545120a..9b995b5a 100644
|
||||
_contentView = [[NSView alloc] initWithFrame: contentViewFrame];
|
||||
[_contentView setAutoresizesSubviews: YES];
|
||||
[_contentView
|
||||
@@ -399,6 +415,8 @@ static BOOL _allowsAutomaticWindowTabbing;
|
||||
@@ -399,6 +434,8 @@ static BOOL _allowsAutomaticWindowTabbing;
|
||||
}
|
||||
|
||||
- (void) dealloc {
|
||||
@@ -1469,7 +1509,7 @@ index d545120a..9b995b5a 100644
|
||||
[[NSNotificationCenter defaultCenter] removeObserver: self];
|
||||
[_childWindows release];
|
||||
[_title release];
|
||||
@@ -1025,7 +1043,34 @@ static BOOL _allowsAutomaticWindowTabbing;
|
||||
@@ -1025,7 +1062,34 @@ static BOOL _allowsAutomaticWindowTabbing;
|
||||
}
|
||||
|
||||
- (void) setMovable: (BOOL) movable {
|
||||
@@ -1505,7 +1545,7 @@ index d545120a..9b995b5a 100644
|
||||
}
|
||||
|
||||
- (void) setBackingType: (NSBackingStoreType) value {
|
||||
@@ -1234,6 +1279,7 @@ static BOOL _allowsAutomaticWindowTabbing;
|
||||
@@ -1234,6 +1298,7 @@ static BOOL _allowsAutomaticWindowTabbing;
|
||||
|
||||
- (void) setIgnoresMouseEvents: (BOOL) value {
|
||||
_ignoresMouseEvents = value;
|
||||
@@ -1513,7 +1553,7 @@ index d545120a..9b995b5a 100644
|
||||
}
|
||||
|
||||
- (void) setAspectRatio: (NSSize) value {
|
||||
@@ -1413,8 +1459,14 @@ static BOOL _allowsAutomaticWindowTabbing;
|
||||
@@ -1413,8 +1478,14 @@ static BOOL _allowsAutomaticWindowTabbing;
|
||||
}
|
||||
|
||||
- (NSButton *) standardWindowButton: (NSWindowButton) value {
|
||||
@@ -1530,7 +1570,7 @@ index d545120a..9b995b5a 100644
|
||||
}
|
||||
|
||||
- (NSButtonCell *) defaultButtonCell {
|
||||
@@ -1425,6 +1477,17 @@ static BOOL _allowsAutomaticWindowTabbing;
|
||||
@@ -1425,6 +1496,17 @@ static BOOL _allowsAutomaticWindowTabbing;
|
||||
return [_sheetContext sheet];
|
||||
}
|
||||
|
||||
@@ -1548,7 +1588,7 @@ index d545120a..9b995b5a 100644
|
||||
- (id) windowController {
|
||||
return _windowController;
|
||||
}
|
||||
@@ -1511,10 +1574,7 @@ static BOOL _allowsAutomaticWindowTabbing;
|
||||
@@ -1511,10 +1593,7 @@ static BOOL _allowsAutomaticWindowTabbing;
|
||||
return [_platformWindow isMiniaturized];
|
||||
}
|
||||
|
||||
@@ -1560,7 +1600,7 @@ index d545120a..9b995b5a 100644
|
||||
|
||||
- (BOOL) inLiveResize {
|
||||
return _isInLiveResize;
|
||||
@@ -1561,6 +1621,108 @@ static BOOL _allowsAutomaticWindowTabbing;
|
||||
@@ -1561,6 +1640,108 @@ static BOOL _allowsAutomaticWindowTabbing;
|
||||
return point;
|
||||
}
|
||||
|
||||
@@ -1669,7 +1709,7 @@ index d545120a..9b995b5a 100644
|
||||
- (NSRect) frameRectForContentRect: (NSRect) contentRect {
|
||||
// hasMainMenu is an instance method so we can't just use the class method
|
||||
// frameRectForContentRect:styleMask:
|
||||
@@ -1580,6 +1742,10 @@ static BOOL _allowsAutomaticWindowTabbing;
|
||||
@@ -1580,6 +1761,10 @@ static BOOL _allowsAutomaticWindowTabbing;
|
||||
}
|
||||
|
||||
- (NSRect) contentRectForFrameRect: (NSRect) frameRect {
|
||||
@@ -1680,7 +1720,7 @@ index d545120a..9b995b5a 100644
|
||||
NSRect result = [[NSDisplay currentDisplay] insetRect: frameRect
|
||||
forNativeWindowBorderWithStyle: [self styleMask]];
|
||||
|
||||
@@ -2155,6 +2321,7 @@ static BOOL _allowsAutomaticWindowTabbing;
|
||||
@@ -2155,6 +2340,7 @@ static BOOL _allowsAutomaticWindowTabbing;
|
||||
- (void) orderWindow: (NSWindowOrderingMode) place
|
||||
relativeTo: (NSInteger) relativeTo
|
||||
{
|
||||
@@ -1688,7 +1728,7 @@ index d545120a..9b995b5a 100644
|
||||
// The move notifications are sent under unknown conditions around
|
||||
// orderFront: in the Apple AppKit, we do them all the time here until it's
|
||||
// figured out. I suspect it is a side effect of off-screen windows being at
|
||||
@@ -2208,6 +2375,8 @@ static BOOL _allowsAutomaticWindowTabbing;
|
||||
@@ -2208,6 +2394,8 @@ static BOOL _allowsAutomaticWindowTabbing;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1697,7 +1737,7 @@ index d545120a..9b995b5a 100644
|
||||
[self postNotificationName: NSWindowDidMoveNotification];
|
||||
}
|
||||
|
||||
@@ -2257,6 +2426,12 @@ static BOOL _allowsAutomaticWindowTabbing;
|
||||
@@ -2257,6 +2445,12 @@ static BOOL _allowsAutomaticWindowTabbing;
|
||||
}
|
||||
|
||||
- (void) sendEvent: (NSEvent *) event {
|
||||
@@ -1710,7 +1750,7 @@ index d545120a..9b995b5a 100644
|
||||
// Some events can cause our window to be destroyed
|
||||
// So make sure self lives at least through this current run loop...
|
||||
[[self retain] autorelease];
|
||||
@@ -2300,6 +2475,10 @@ static BOOL _allowsAutomaticWindowTabbing;
|
||||
@@ -2300,6 +2494,10 @@ static BOOL _allowsAutomaticWindowTabbing;
|
||||
|
||||
case NSLeftMouseDown: {
|
||||
NSView *view = [_backgroundView hitTest: [event locationInWindow]];
|
||||
@@ -1721,7 +1761,7 @@ index d545120a..9b995b5a 100644
|
||||
|
||||
if ([view acceptsFirstResponder]) {
|
||||
if ([view needsPanelToBecomeKey]) {
|
||||
@@ -2307,6 +2486,14 @@ static BOOL _allowsAutomaticWindowTabbing;
|
||||
@@ -2307,6 +2505,27 @@ static BOOL _allowsAutomaticWindowTabbing;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1729,14 +1769,27 @@ index d545120a..9b995b5a 100644
|
||||
+ // drag. Only windows opting into background dragging intercept it;
|
||||
+ // CEF otherwise handles clicks and invokes performWindowDragWithEvent:
|
||||
+ // itself for its native titlebar regions.
|
||||
+ if (_isMovable && _isMovableByWindowBackground && [view mouseDownCanMoveWindow]) {
|
||||
+ [self performWindowDragWithEvent:event];
|
||||
+ break;
|
||||
+ if (_isMovable && _isMovableByWindowBackground) {
|
||||
+ NSView *dragView = DarlingFrontmostViewAtPoint(_backgroundView,
|
||||
+ [_backgroundView convertPoint: [event locationInWindow] fromView: nil]);
|
||||
+ if (dragView == nil)
|
||||
+ dragView = view;
|
||||
+ if (inputTrace) {
|
||||
+ static int dragTrace = -1;
|
||||
+ if (dragTrace < 0) dragTrace = getenv("RCLOL_INPUT_TRACE") != NULL;
|
||||
+ if (dragTrace)
|
||||
+ NSLog(@"LOLDRAG hit=%s dragView=%s canMove=%d", class_getName([view class]),
|
||||
+ class_getName([dragView class]), (int)[dragView mouseDownCanMoveWindow]);
|
||||
+ }
|
||||
+ if ([dragView mouseDownCanMoveWindow]) {
|
||||
+ [self performWindowDragWithEvent:event];
|
||||
+ break;
|
||||
+ }
|
||||
+ }
|
||||
// Event goes to view, not first responder.
|
||||
[view mouseDown: event];
|
||||
_mouseDownLocationInWindow = [event locationInWindow];
|
||||
@@ -2330,6 +2517,31 @@ static BOOL _allowsAutomaticWindowTabbing;
|
||||
@@ -2330,6 +2549,31 @@ static BOOL _allowsAutomaticWindowTabbing;
|
||||
_mouseDownLocationInWindow = NSMakePoint(NAN, NAN);
|
||||
break;
|
||||
|
||||
@@ -1768,7 +1821,7 @@ index d545120a..9b995b5a 100644
|
||||
case NSMouseMoved: {
|
||||
NSView *hit = [_backgroundView hitTest: [event locationInWindow]];
|
||||
|
||||
@@ -2536,6 +2748,7 @@ static BOOL _allowsAutomaticWindowTabbing;
|
||||
@@ -2536,6 +2780,7 @@ static BOOL _allowsAutomaticWindowTabbing;
|
||||
} else {
|
||||
[[self platformWindow] hideWindow];
|
||||
}
|
||||
@@ -1776,7 +1829,7 @@ index d545120a..9b995b5a 100644
|
||||
}
|
||||
|
||||
- (void) orderFront: (id) sender {
|
||||
@@ -3260,8 +3473,7 @@ static BOOL _allowsAutomaticWindowTabbing;
|
||||
@@ -3260,8 +3505,7 @@ static BOOL _allowsAutomaticWindowTabbing;
|
||||
mouseWasInside && !mouseIsInside) {
|
||||
[exited addObject: area];
|
||||
}
|
||||
@@ -1786,7 +1839,7 @@ index d545120a..9b995b5a 100644
|
||||
[moved addObject: area];
|
||||
}
|
||||
if (options & NSTrackingCursorUpdate && !mouseWasInside &&
|
||||
@@ -3459,7 +3671,7 @@ static BOOL _allowsAutomaticWindowTabbing;
|
||||
@@ -3459,7 +3703,7 @@ static BOOL _allowsAutomaticWindowTabbing;
|
||||
}
|
||||
|
||||
- (CGSubWindow *) _createSubWindowWithFrame: (CGRect) frame {
|
||||
@@ -7682,3 +7735,141 @@ new file mode 100644
|
||||
+- (instancetype) init;
|
||||
+
|
||||
+@end
|
||||
diff --git a/AppKit/NSWorkspace.m b/AppKit/NSWorkspace.m
|
||||
index ad5e4f9d..304cd8b5 100644
|
||||
--- a/AppKit/NSWorkspace.m
|
||||
+++ b/AppKit/NSWorkspace.m
|
||||
@@ -20,9 +20,90 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
#import <AppKit/NSRaise.h>
|
||||
#import <AppKit/NSWorkspace.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
+#import <CoreFoundation/CoreFoundation.h>
|
||||
+#include <dispatch/dispatch.h>
|
||||
+#include <stdio.h>
|
||||
+#include <unistd.h>
|
||||
|
||||
NSString *const NSWorkspaceApplicationKey = @"NSWorkspaceApplicationKey";
|
||||
|
||||
+/* darling-lol: opening a URL from the guest.
|
||||
+ *
|
||||
+ * LaunchServices cannot start a Linux browser from inside the prefix, and Electron's
|
||||
+ * shell.openExternal() uses -openURL:configuration:completionHandler: (macOS 10.15) with an
|
||||
+ * NSWorkspaceOpenConfiguration, neither of which existed: the unrecognized selector crashed the
|
||||
+ * client ("Can't sign in?", links, ...). Hand the URL to the host through a spool directory in
|
||||
+ * the shared /private/tmp. The legacy tools/riot-open-url-bridge.py can drain it, but is excluded
|
||||
+ * from normal launches because it also installs a client inspector hook.
|
||||
+ * Only http, https and mailto URLs are queued. */
|
||||
+@interface NSWorkspaceOpenConfiguration () {
|
||||
+ BOOL _activates, _hides, _hidesOthers, _addsToRecentItems, _createsNewApplicationInstance;
|
||||
+ BOOL _allowsRunningApplicationSubstitution, _forPrinting, _promptsUserIfNeeded, _requiresUniversalLinks;
|
||||
+ NSAppleEventDescriptor *_appleEvent;
|
||||
+ NSArray *_arguments;
|
||||
+ NSDictionary *_environment;
|
||||
+ cpu_type_t _architecture;
|
||||
+}
|
||||
+@end
|
||||
+
|
||||
+@implementation NSWorkspaceOpenConfiguration
|
||||
+@synthesize activates = _activates, hides = _hides, hidesOthers = _hidesOthers;
|
||||
+@synthesize addsToRecentItems = _addsToRecentItems, createsNewApplicationInstance = _createsNewApplicationInstance;
|
||||
+@synthesize allowsRunningApplicationSubstitution = _allowsRunningApplicationSubstitution;
|
||||
+@synthesize forPrinting = _forPrinting, promptsUserIfNeeded = _promptsUserIfNeeded;
|
||||
+@synthesize requiresUniversalLinks = _requiresUniversalLinks;
|
||||
+@synthesize appleEvent = _appleEvent, arguments = _arguments, environment = _environment;
|
||||
+@synthesize architecture = _architecture;
|
||||
++ (instancetype) configuration {
|
||||
+ NSWorkspaceOpenConfiguration *c = [[[self alloc] init] autorelease];
|
||||
+ c->_activates = YES;
|
||||
+ c->_addsToRecentItems = YES;
|
||||
+ c->_allowsRunningApplicationSubstitution = YES;
|
||||
+ return c;
|
||||
+}
|
||||
+- (id) copyWithZone: (NSZone *) zone {
|
||||
+ NSWorkspaceOpenConfiguration *c = [[[self class] allocWithZone: zone] init];
|
||||
+ c->_activates = _activates; c->_hides = _hides; c->_hidesOthers = _hidesOthers;
|
||||
+ c->_addsToRecentItems = _addsToRecentItems;
|
||||
+ c->_createsNewApplicationInstance = _createsNewApplicationInstance;
|
||||
+ c->_allowsRunningApplicationSubstitution = _allowsRunningApplicationSubstitution;
|
||||
+ c->_forPrinting = _forPrinting; c->_promptsUserIfNeeded = _promptsUserIfNeeded;
|
||||
+ c->_requiresUniversalLinks = _requiresUniversalLinks; c->_architecture = _architecture;
|
||||
+ c->_appleEvent = [_appleEvent retain];
|
||||
+ c->_arguments = [_arguments copy]; c->_environment = [_environment copy];
|
||||
+ return c;
|
||||
+}
|
||||
+- (void) dealloc {
|
||||
+ [_appleEvent release];
|
||||
+ [_arguments release];
|
||||
+ [_environment release];
|
||||
+ [super dealloc];
|
||||
+}
|
||||
+@end
|
||||
+
|
||||
+BOOL RCLOLOpenURLViaHost(NSURL *url) {
|
||||
+ NSString *scheme = [[url scheme] lowercaseString];
|
||||
+ if (![scheme isEqualToString: @"http"] && ![scheme isEqualToString: @"https"] &&
|
||||
+ ![scheme isEqualToString: @"mailto"])
|
||||
+ return NO;
|
||||
+ NSString *absolute = [url absoluteString];
|
||||
+ if ([absolute length] == 0 || [absolute length] > 4096)
|
||||
+ return NO;
|
||||
+ NSString *directory = @"/private/tmp/rclol-open";
|
||||
+ [[NSFileManager defaultManager] createDirectoryAtPath: directory
|
||||
+ withIntermediateDirectories: YES
|
||||
+ attributes: nil
|
||||
+ error: NULL];
|
||||
+ static unsigned sequence = 0;
|
||||
+ NSString *path = [NSString stringWithFormat: @"%@/%d-%u-%llu.url", directory, (int) getpid(),
|
||||
+ ++sequence, (unsigned long long) (CFAbsoluteTimeGetCurrent() * 1000)];
|
||||
+ NSString *temporary = [path stringByAppendingString: @".part"];
|
||||
+ if (![absolute writeToFile: temporary atomically: NO encoding: NSUTF8StringEncoding error: NULL])
|
||||
+ return NO;
|
||||
+ return rename([temporary fileSystemRepresentation], [path fileSystemRepresentation]) == 0;
|
||||
+}
|
||||
+
|
||||
+
|
||||
NSString *const NSWorkspaceWillPowerOffNotification =
|
||||
@"NSWorkspaceWillPowerOffNotification";
|
||||
|
||||
@@ -157,8 +238,22 @@ NSNotificationName NSWorkspaceWillUnmountNotification = @"NSWorkspaceWillUnmount
|
||||
}
|
||||
|
||||
- (BOOL) openURL: (NSURL *) url {
|
||||
- NSInvalidAbstractInvocation();
|
||||
- return 0;
|
||||
+ return RCLOLOpenURLViaHost(url);
|
||||
+}
|
||||
+
|
||||
+- (void) openURL: (NSURL *) url
|
||||
+ configuration: (NSWorkspaceOpenConfiguration *) configuration
|
||||
+ completionHandler: (void (^)(id application, NSError *error)) completionHandler
|
||||
+{
|
||||
+ BOOL opened = [self openURL: url];
|
||||
+ if (completionHandler != NULL) {
|
||||
+ NSError *error = nil;
|
||||
+ if (!opened)
|
||||
+ error = [NSError errorWithDomain: NSCocoaErrorDomain code: 256 userInfo: nil];
|
||||
+ dispatch_async(dispatch_get_main_queue(), ^{
|
||||
+ completionHandler(nil, error);
|
||||
+ });
|
||||
+ }
|
||||
}
|
||||
|
||||
- (BOOL) selectFile: (NSString *) path
|
||||
@@ -317,16 +412,3 @@ NSNotificationName NSWorkspaceWillUnmountNotification = @"NSWorkspaceWillUnmount
|
||||
}
|
||||
|
||||
@end
|
||||
-
|
||||
-static dispatch_once_t _initOnceNsWorkspaceOpenConfig;
|
||||
-static NSWorkspaceOpenConfiguration* _singletonNsWorkspaceOpenConfig;
|
||||
-
|
||||
-@implementation NSWorkspaceOpenConfiguration
|
||||
-+ (instancetype)configuration {
|
||||
- dispatch_once(&_initOnceNsWorkspaceOpenConfig, ^{
|
||||
- _singletonNsWorkspaceOpenConfig = [[NSWorkspaceOpenConfiguration alloc] init];
|
||||
- });
|
||||
- return _singletonNsWorkspaceOpenConfig;
|
||||
-}
|
||||
-
|
||||
-@end
|
||||
\ No newline at end of file
|
||||
+103
-5
@@ -5588,7 +5588,7 @@ index deca16b87..d75d5e22d 100644
|
||||
|
||||
set_property(TARGET OpenGL APPEND_STRING PROPERTY LINK_FLAGS
|
||||
diff --git a/src/frameworks/OpenGL/OpenGL.c b/src/frameworks/OpenGL/OpenGL.c
|
||||
index ffd7fe054..4801e5521 100644
|
||||
index ffd7fe054..a74ffc63f 100644
|
||||
--- a/src/frameworks/OpenGL/OpenGL.c
|
||||
+++ b/src/frameworks/OpenGL/OpenGL.c
|
||||
@@ -2,11 +2,22 @@
|
||||
@@ -6065,7 +6065,7 @@ index ffd7fe054..4801e5521 100644
|
||||
|
||||
free(context);
|
||||
}
|
||||
@@ -383,9 +608,323 @@ CGLError CGLUnlockContext(CGLContextObj context) {
|
||||
@@ -383,9 +608,329 @@ CGLError CGLUnlockContext(CGLContextObj context) {
|
||||
return kCGLNoError;
|
||||
}
|
||||
|
||||
@@ -6235,8 +6235,14 @@ index ffd7fe054..4801e5521 100644
|
||||
+ }
|
||||
+ }
|
||||
+ DarlingNativePresentTarget *target=ctx->native_present_target;
|
||||
+ /* A Chromium context renders several windows. An unrelated surface must
|
||||
+ * use readback without stopping the active window's async presenter. */
|
||||
+ if(target && __atomic_load_n(&target->magic,__ATOMIC_ACQUIRE)==DARLING_NATIVE_PRESENT_MAGIC &&
|
||||
+ target->target && (target->width!=width || target->height!=height)) {
|
||||
+ CGLReleaseContext(ctx);return 0;
|
||||
+ }
|
||||
+ if(!target || __atomic_load_n(&target->magic,__ATOMIC_ACQUIRE)!=DARLING_NATIVE_PRESENT_MAGIC ||
|
||||
+ target->width!=width || target->height!=height || !target->target){
|
||||
+ !target->target){
|
||||
+ stopAsyncPresent(ctx);
|
||||
+ if(target){__lolsys_syscall((long)target,sizeof(DarlingNativePresentTarget),0,0,0,0,11);ctx->native_present_target=NULL;ctx->native_present_window=0;}
|
||||
+ CGLReleaseContext(ctx);return 0;
|
||||
@@ -6391,7 +6397,7 @@ index ffd7fe054..4801e5521 100644
|
||||
}
|
||||
|
||||
CGLError CGLSetParameter(CGLContextObj context, CGLContextParameter parameter, const GLint *value) {
|
||||
@@ -394,8 +933,9 @@ CGLError CGLSetParameter(CGLContextObj context, CGLContextParameter parameter, c
|
||||
@@ -394,8 +939,9 @@ CGLError CGLSetParameter(CGLContextObj context, CGLContextParameter parameter, c
|
||||
|
||||
if (parameter == kCGLCPSwapInterval)
|
||||
{
|
||||
@@ -6402,7 +6408,7 @@ index ffd7fe054..4801e5521 100644
|
||||
if (success)
|
||||
context->swap_interval = v;
|
||||
return success ? kCGLNoError : kCGLBadValue;
|
||||
@@ -417,14 +957,19 @@ CGLError CGLGetParameter(CGLContextObj context, CGLContextParameter parameter, G
|
||||
@@ -417,14 +963,19 @@ CGLError CGLGetParameter(CGLContextObj context, CGLContextParameter parameter, G
|
||||
return kCGLNoError;
|
||||
}
|
||||
|
||||
@@ -7984,3 +7990,95 @@ new file mode 100644
|
||||
+ return caps;
|
||||
+}
|
||||
+#endif
|
||||
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
|
||||
index 5c924b34d..54f134873 100644
|
||||
--- a/src/CMakeLists.txt
|
||||
+++ b/src/CMakeLists.txt
|
||||
@@ -119,6 +119,13 @@ SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_
|
||||
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET}")
|
||||
set(CMAKE_OBJC_FLAGS "${CMAKE_OBJC_FLAGS} -mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET}")
|
||||
|
||||
+# Recent clang compiles an empty @[] / @{} for a macOS 11+ runtime into a reference to
|
||||
+# __NSArray0__struct / __NSDictionary0__struct, which Darling's CoreFoundation does not define
|
||||
+# (it provides __NSArray0__ / __NSDictionary0__). Keep the earlier code generation.
|
||||
+SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fobjc-runtime=macosx-10.15")
|
||||
+SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fobjc-runtime=macosx-10.15")
|
||||
+set(CMAKE_OBJC_FLAGS "${CMAKE_OBJC_FLAGS} -fobjc-runtime=macosx-10.15")
|
||||
+
|
||||
add_definitions(-target ${APPLE_TARGET_TRIPLET_PRIMARY})
|
||||
include(darling_lib)
|
||||
include(darling_static_lib)
|
||||
diff --git a/src/libelfloader/wrapgen/wrapgen.cpp b/src/libelfloader/wrapgen/wrapgen.cpp
|
||||
index 7079e613e..397ec6de0 100644
|
||||
--- a/src/libelfloader/wrapgen/wrapgen.cpp
|
||||
+++ b/src/libelfloader/wrapgen/wrapgen.cpp
|
||||
@@ -268,7 +268,10 @@ void generate_wrapper(std::ofstream& output, const char* soname, const std::set<
|
||||
|
||||
for (const std::string& sym : symbols)
|
||||
{
|
||||
- output << "void* " << sym << "() {\n"
|
||||
+ // ld64's x86-64 resolver stub saves integer argument registers only.
|
||||
+ // ELF lookup can clobber XMM arguments before the stub jumps to its
|
||||
+ // resolved function. Preserve them in the resolver itself.
|
||||
+ output << "__attribute__((preserve_all)) void* " << sym << "() {\n"
|
||||
"\t__asm__(\".symbol_resolver _" << sym << "\");\n"
|
||||
"\treturn _elfcalls->dlsym_fatal(lib_handle, \"" << sym << "\");\n"
|
||||
"}\n\n";
|
||||
diff --git a/src/frameworks/CoreServices/src/LaunchServices/LSInfo.m b/src/frameworks/CoreServices/src/LaunchServices/LSInfo.m
|
||||
index 4cb2a4941..f08d05a2b 100644
|
||||
--- a/src/frameworks/CoreServices/src/LaunchServices/LSInfo.m
|
||||
+++ b/src/frameworks/CoreServices/src/LaunchServices/LSInfo.m
|
||||
@@ -106,7 +106,7 @@ CFArrayRef LSCopyApplicationURLsForBundleIdentifier(CFStringRef inBundleIdentifi
|
||||
{
|
||||
if (!inBundleIdentifier)
|
||||
{
|
||||
- if (*outError)
|
||||
+ if (outError)
|
||||
*outError = CFErrorCreate(NULL, kCFErrorDomainOSStatus, paramErr, NULL);
|
||||
return NULL;
|
||||
}
|
||||
@@ -116,7 +116,7 @@ CFArrayRef LSCopyApplicationURLsForBundleIdentifier(CFStringRef inBundleIdentifi
|
||||
|
||||
if (dq == nil)
|
||||
{
|
||||
- if (*outError)
|
||||
+ if (outError)
|
||||
*outError = CFErrorCreate(NULL, kCFErrorDomainOSStatus, fnfErr, NULL);
|
||||
return NULL;
|
||||
}
|
||||
@@ -138,7 +138,7 @@ CFArrayRef LSCopyApplicationURLsForBundleIdentifier(CFStringRef inBundleIdentifi
|
||||
}
|
||||
else
|
||||
{
|
||||
- if (*outError)
|
||||
+ if (outError)
|
||||
*outError = CFErrorCreate(NULL, kCFErrorDomainOSStatus, kLSApplicationNotFoundErr, NULL);
|
||||
retval = NULL;
|
||||
}
|
||||
@@ -247,7 +247,7 @@ CFURLRef LSCopyDefaultApplicationURLForContentType(CFStringRef inContentType, LS
|
||||
|
||||
if (dq == nil)
|
||||
{
|
||||
- if (*outError)
|
||||
+ if (outError)
|
||||
*outError = CFErrorCreate(NULL, kCFErrorDomainOSStatus, fnfErr, NULL);
|
||||
return NULL;
|
||||
}
|
||||
@@ -275,7 +275,7 @@ CFURLRef LSCopyDefaultApplicationURLForContentType(CFStringRef inContentType, LS
|
||||
}
|
||||
else
|
||||
{
|
||||
- if (*outError)
|
||||
+ if (outError)
|
||||
*outError = CFErrorCreate(NULL, kCFErrorDomainOSStatus, kLSApplicationNotFoundErr, NULL);
|
||||
retval = NULL;
|
||||
}
|
||||
@@ -292,7 +292,7 @@ CFURLRef LSCopyDefaultApplicationURLForURL(CFURLRef inURL, LSRolesMask inRoleMas
|
||||
|
||||
if (status != noErr)
|
||||
{
|
||||
- if (*outError)
|
||||
+ if (outError)
|
||||
*outError = CFErrorCreate(NULL, kCFErrorDomainOSStatus, status, NULL);
|
||||
return NULL;
|
||||
}
|
||||
+106
-22
@@ -1273,10 +1273,18 @@ index d46bdb4..9a58727 100644
|
||||
};
|
||||
};
|
||||
diff --git a/internal-include/darlingserver/message.hpp b/internal-include/darlingserver/message.hpp
|
||||
index 708f3f3..56218a5 100644
|
||||
index 708f3f3..e68cd28 100644
|
||||
--- a/internal-include/darlingserver/message.hpp
|
||||
+++ b/internal-include/darlingserver/message.hpp
|
||||
@@ -71,6 +71,11 @@ namespace DarlingServer {
|
||||
@@ -28,6 +28,7 @@
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <functional>
|
||||
+#include <array>
|
||||
|
||||
namespace DarlingServer {
|
||||
class Address {
|
||||
@@ -71,6 +72,11 @@ namespace DarlingServer {
|
||||
void _ensureDescriptorHeader(size_t descriptorSpace);
|
||||
|
||||
public:
|
||||
@@ -1288,6 +1296,15 @@ index 708f3f3..56218a5 100644
|
||||
/**
|
||||
* Default-initializes a Message.
|
||||
*
|
||||
@@ -161,6 +167,8 @@ namespace DarlingServer {
|
||||
class MessageQueue {
|
||||
private:
|
||||
std::deque<Message> _messages;
|
||||
+ // Retain unused receive storage; queued Messages own their buffers and FDs.
|
||||
+ std::array<std::optional<Message>, 16> _receiveMessages;
|
||||
mutable std::mutex _lock;
|
||||
std::function<void()> _messageArrivalNotificationCallback = nullptr;
|
||||
|
||||
diff --git a/internal-include/darlingserver/process.hpp b/internal-include/darlingserver/process.hpp
|
||||
index c97be30..31772dc 100644
|
||||
--- a/internal-include/darlingserver/process.hpp
|
||||
@@ -1334,7 +1351,7 @@ index e35221c..1068426 100755
|
||||
# pthread cancelation
|
||||
#
|
||||
diff --git a/src/call.cpp b/src/call.cpp
|
||||
index cc10f1d..8c8d6b3 100644
|
||||
index cc10f1d..7df0056 100644
|
||||
--- a/src/call.cpp
|
||||
+++ b/src/call.cpp
|
||||
@@ -18,9 +18,20 @@
|
||||
@@ -1435,10 +1452,19 @@ index cc10f1d..8c8d6b3 100644
|
||||
std::shared_ptr<Call> result = nullptr;
|
||||
std::shared_ptr<Process> process = nullptr;
|
||||
std::shared_ptr<Thread> thread = nullptr;
|
||||
@@ -127,6 +198,38 @@ std::shared_ptr<DarlingServer::Call> DarlingServer::Call::callFromMessage(Messag
|
||||
auto tidString = (thread) ? (std::to_string(thread->id()) + " (" + std::to_string(thread->nsid()) + ")") : (std::to_string(header->tid) + " (-1)");
|
||||
callLog.debug() << "Received call #" << header->number << " (" << dserver_callnum_to_string(header->number) << ") from PID " << pidString << ", TID " << tidString << callLog.endLog;
|
||||
@@ -123,9 +194,43 @@ std::shared_ptr<DarlingServer::Call> DarlingServer::Call::callFromMessage(Messag
|
||||
}
|
||||
}
|
||||
|
||||
- auto pidString = (process) ? (std::to_string(process->id()) + " (" + std::to_string(process->nsid()) + ")") : (std::to_string(header->pid) + " (-1)");
|
||||
- auto tidString = (thread) ? (std::to_string(thread->id()) + " (" + std::to_string(thread->nsid()) + ")") : (std::to_string(header->tid) + " (-1)");
|
||||
- callLog.debug() << "Received call #" << header->number << " (" << dserver_callnum_to_string(header->number) << ") from PID " << pidString << ", TID " << tidString << callLog.endLog;
|
||||
+ if (callLog.levelEnabled(1)) {
|
||||
+ auto pidString = (process) ? (std::to_string(process->id()) + " (" + std::to_string(process->nsid()) + ")") : (std::to_string(header->pid) + " (-1)");
|
||||
+ auto tidString = (thread) ? (std::to_string(thread->id()) + " (" + std::to_string(thread->nsid()) + ")") : (std::to_string(header->tid) + " (-1)");
|
||||
+ callLog.debug() << "Received call #" << header->number << " (" << dserver_callnum_to_string(header->number) << ") from PID " << pidString << ", TID " << tidString << callLog.endLog;
|
||||
+ }
|
||||
+
|
||||
+ /* darling-lol M634: rate-limited trace of EVERY call through dserver, to
|
||||
+ * see which RPCs the launcher's blocked service threads are waiting on
|
||||
+ * and whether an S2C/interrupt round-trip is wedged. */
|
||||
@@ -1470,11 +1496,10 @@ index cc10f1d..8c8d6b3 100644
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
|
||||
if (header->number == dserver_callnum_s2c) {
|
||||
// this is an S2C reply
|
||||
|
||||
@@ -694,6 +797,60 @@ void DarlingServer::Call::KqchanProcOpen::processCall() {
|
||||
@@ -694,6 +799,60 @@ void DarlingServer::Call::KqchanProcOpen::processCall() {
|
||||
_sendReply(code, socket);
|
||||
};
|
||||
|
||||
@@ -1535,7 +1560,7 @@ index cc10f1d..8c8d6b3 100644
|
||||
void DarlingServer::Call::ForkWaitForChild::processCall() {
|
||||
int code = 0;
|
||||
|
||||
@@ -714,6 +871,24 @@ void DarlingServer::Call::Sigprocess::processCall() {
|
||||
@@ -714,6 +873,24 @@ void DarlingServer::Call::Sigprocess::processCall() {
|
||||
int code = 0;
|
||||
int newBSDSignal = 0;
|
||||
|
||||
@@ -1846,14 +1871,16 @@ index 7b8000f..dbaf832 100644
|
||||
}
|
||||
|
||||
diff --git a/src/message.cpp b/src/message.cpp
|
||||
index 414c1d6..995e184 100644
|
||||
index 414c1d6..06b3f67 100644
|
||||
--- a/src/message.cpp
|
||||
+++ b/src/message.cpp
|
||||
@@ -24,6 +24,23 @@
|
||||
@@ -24,6 +24,25 @@
|
||||
#include <new>
|
||||
#include <mutex>
|
||||
#include <system_error>
|
||||
+#include <pthread.h>
|
||||
+#include <cstdio>
|
||||
+#include <cstring>
|
||||
+
|
||||
+namespace {
|
||||
+ std::optional<struct ucred> serverCredentials;
|
||||
@@ -1873,7 +1900,16 @@ index 414c1d6..995e184 100644
|
||||
|
||||
DarlingServer::Address::Address() {
|
||||
_address.sun_family = AF_UNIX;
|
||||
@@ -65,10 +82,7 @@ DarlingServer::Message::Message(size_t bufferSpace, size_t descriptorSpace, std:
|
||||
@@ -55,6 +74,8 @@ void DarlingServer::Address::setRawSize(size_t newRawSize) {
|
||||
DarlingServer::Message::Message(size_t bufferSpace, size_t descriptorSpace, std::function<void()> sendNotificationCallback):
|
||||
_sendNotificationCallback(sendNotificationCallback)
|
||||
{
|
||||
+ // Allocate the vector first: if it throws, there is no raw control buffer to leak.
|
||||
+ _buffer.resize(bufferSpace);
|
||||
size_t controlLen = CMSG_SPACE(sizeof(struct ucred)) + (descriptorSpace > 0 ? CMSG_SPACE(sizeof(int) * descriptorSpace) : 0);
|
||||
_controlHeader = static_cast<decltype(_controlHeader)>(malloc(controlLen));
|
||||
|
||||
@@ -65,10 +86,7 @@ DarlingServer::Message::Message(size_t bufferSpace, size_t descriptorSpace, std:
|
||||
_controlHeader->cmsg_len = CMSG_LEN(sizeof(struct ucred));
|
||||
_controlHeader->cmsg_level = SOL_SOCKET;
|
||||
_controlHeader->cmsg_type = SCM_CREDENTIALS;
|
||||
@@ -1885,7 +1921,16 @@ index 414c1d6..995e184 100644
|
||||
// the cmsg man page says memcpy should be used with CMSG_DATA instead of direct pointer access
|
||||
memcpy(CMSG_DATA(_controlHeader), &ourCreds, sizeof(ourCreds));
|
||||
|
||||
@@ -348,10 +362,7 @@ void DarlingServer::Message::_ensureCredentialsHeader() {
|
||||
@@ -83,8 +101,6 @@ DarlingServer::Message::Message(size_t bufferSpace, size_t descriptorSpace, std:
|
||||
}
|
||||
}
|
||||
|
||||
- _buffer.resize(bufferSpace);
|
||||
-
|
||||
_header.msg_name = &_socketAddress.raw();
|
||||
_header.msg_namelen = _socketAddress.rawSize();
|
||||
_header.msg_iov = &_dataDescriptor;
|
||||
@@ -348,10 +364,7 @@ void DarlingServer::Message::_ensureCredentialsHeader() {
|
||||
credHeader->cmsg_level = SOL_SOCKET;
|
||||
credHeader->cmsg_type = SCM_CREDENTIALS;
|
||||
|
||||
@@ -1897,7 +1942,7 @@ index 414c1d6..995e184 100644
|
||||
memcpy(CMSG_DATA(credHeader), &creds, sizeof(struct ucred));
|
||||
};
|
||||
|
||||
@@ -496,13 +507,47 @@ std::optional<DarlingServer::Message> DarlingServer::MessageQueue::pop() {
|
||||
@@ -496,13 +509,47 @@ std::optional<DarlingServer::Message> DarlingServer::MessageQueue::pop() {
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1946,7 +1991,7 @@ index 414c1d6..995e184 100644
|
||||
while (ret >= 0) {
|
||||
len = 0;
|
||||
for (size_t i = 0; i < _messages.size() && i < sizeof(mmsgs) / sizeof(*mmsgs); ++i) {
|
||||
@@ -549,6 +594,7 @@ bool DarlingServer::MessageQueue::sendMany(int socket) {
|
||||
@@ -549,6 +596,7 @@ bool DarlingServer::MessageQueue::sendMany(int socket) {
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < ret; ++i) {
|
||||
@@ -1954,14 +1999,53 @@ index 414c1d6..995e184 100644
|
||||
auto cb = _messages.front().sendNotificationCallback();
|
||||
_messages.pop_front();
|
||||
if (cb) {
|
||||
@@ -591,6 +637,7 @@ bool DarlingServer::MessageQueue::receiveMany(int socket) {
|
||||
messages[i].rawHeader() = mmsgs[i].msg_hdr;
|
||||
messages[i].data().resize(mmsgs[i].msg_len);
|
||||
messages[i].setAddress(Address(*(const struct sockaddr_un*)mmsgs[i].msg_hdr.msg_name, mmsgs[i].msg_hdr.msg_namelen));
|
||||
+ m58ak_log_message("recv", socket, &messages[i].address().raw(), messages[i].data().data(), messages[i].data().size());
|
||||
_messages.push_back(std::move(messages[i]));
|
||||
@@ -567,10 +615,11 @@ bool DarlingServer::MessageQueue::receiveMany(int socket) {
|
||||
int ret = 0;
|
||||
|
||||
while (ret >= 0) {
|
||||
- std::array<Message, sizeof(mmsgs) / sizeof(*mmsgs)> messages;
|
||||
-
|
||||
- for (size_t i = 0; i < messages.size(); ++i) {
|
||||
- mmsgs[i].msg_hdr = messages[i].rawHeader();
|
||||
+ for (size_t i = 0; i < _receiveMessages.size(); ++i) {
|
||||
+ if (!_receiveMessages[i]) {
|
||||
+ _receiveMessages[i].emplace();
|
||||
+ }
|
||||
+ mmsgs[i].msg_hdr = _receiveMessages[i]->rawHeader();
|
||||
mmsgs[i].msg_len = 0;
|
||||
}
|
||||
|
||||
@@ -587,11 +636,25 @@ bool DarlingServer::MessageQueue::receiveMany(int socket) {
|
||||
}
|
||||
}
|
||||
|
||||
- for (size_t i = 0; i < ret; ++i) {
|
||||
- messages[i].rawHeader() = mmsgs[i].msg_hdr;
|
||||
- messages[i].data().resize(mmsgs[i].msg_len);
|
||||
- messages[i].setAddress(Address(*(const struct sockaddr_un*)mmsgs[i].msg_hdr.msg_name, mmsgs[i].msg_hdr.msg_namelen));
|
||||
- _messages.push_back(std::move(messages[i]));
|
||||
+ // Adopt every returned control header before anything can throw, so all
|
||||
+ // received descriptors have an owner even if deque allocation later fails.
|
||||
+ for (size_t i = 0; i < static_cast<size_t>(ret); ++i) {
|
||||
+ _receiveMessages[i]->rawHeader() = mmsgs[i].msg_hdr;
|
||||
+ }
|
||||
+ struct ReceivedMessages {
|
||||
+ decltype(_receiveMessages)& slots;
|
||||
+ size_t count;
|
||||
+ ~ReceivedMessages() {
|
||||
+ for (size_t i = 0; i < count; ++i) slots[i].reset();
|
||||
+ }
|
||||
+ } receivedMessages{_receiveMessages, static_cast<size_t>(ret)};
|
||||
+
|
||||
+ for (size_t i = 0; i < static_cast<size_t>(ret); ++i) {
|
||||
+ auto& message = *_receiveMessages[i];
|
||||
+ message.data().resize(mmsgs[i].msg_len);
|
||||
+ message.setAddress(Address(*(const struct sockaddr_un*)mmsgs[i].msg_hdr.msg_name, mmsgs[i].msg_hdr.msg_namelen));
|
||||
+ m58ak_log_message("recv", socket, &message.address().raw(), message.data().data(), message.data().size());
|
||||
+ _messages.push_back(std::move(message));
|
||||
}
|
||||
}
|
||||
|
||||
diff --git a/src/process.cpp b/src/process.cpp
|
||||
index 9cd7b81..180f372 100644
|
||||
--- a/src/process.cpp
|
||||
|
||||
@@ -4351,16 +4351,26 @@ index 1e3e971..0870d92 100644
|
||||
- DynamicVK::vkFreeCommandBuffers(_device->device(), _device->oneshotCommandPool(), 1, &cmdBuf);
|
||||
};
|
||||
diff --git a/src/iridium/air.cpp b/src/iridium/air.cpp
|
||||
index 21c501f..d052cf4 100644
|
||||
index 21c501f..24423e3 100644
|
||||
--- a/src/iridium/air.cpp
|
||||
+++ b/src/iridium/air.cpp
|
||||
@@ -4,9 +4,36 @@
|
||||
@@ -4,9 +4,46 @@
|
||||
#include <iridium/dynamic-llvm.hpp>
|
||||
|
||||
#include <llvm-c/BitReader.h>
|
||||
+#include <set>
|
||||
+#include <algorithm>
|
||||
+#include <functional>
|
||||
+#include <llvm/Config/llvm-config.h>
|
||||
+
|
||||
+// LLVM 23 split the C API's LLVMBr opcode into LLVMUncondBr and LLVMCondBr.
|
||||
+#if LLVM_VERSION_MAJOR >= 23
|
||||
+#define IRIDIUM_BR_CASES case LLVMUncondBr: case LLVMCondBr
|
||||
+#define IRIDIUM_IS_BR(op) ((op) == LLVMUncondBr || (op) == LLVMCondBr)
|
||||
+#else
|
||||
+#define IRIDIUM_BR_CASES case LLVMBr
|
||||
+#define IRIDIUM_IS_BR(op) ((op) == LLVMBr)
|
||||
+#endif
|
||||
|
||||
namespace DynamicLLVM = Iridium::DynamicLLVM;
|
||||
|
||||
@@ -4391,7 +4401,7 @@ index 21c501f..d052cf4 100644
|
||||
// special thanks to https://github.com/YuAo/MetalLibraryArchive for information on the library format
|
||||
|
||||
// we use LLVM's C API because it's much more stable than the C++ API and it's good enough for our needs
|
||||
@@ -29,6 +56,16 @@ Iridium::AIR::Function::Function(Type type, const std::string& name, const void*
|
||||
@@ -29,6 +66,16 @@ Iridium::AIR::Function::Function(Type type, const std::string& name, const void*
|
||||
}
|
||||
|
||||
_module = LLVMSupport::Module(moduleRef, DynamicLLVM::LLVMDisposeModule);
|
||||
@@ -4408,7 +4418,7 @@ index 21c501f..d052cf4 100644
|
||||
|
||||
_function = DynamicLLVM::LLVMGetNamedFunction(_module.get(), _name.c_str());
|
||||
|
||||
@@ -41,7 +78,86 @@ Iridium::AIR::Function::Function(Type type, const std::string& name, const void*
|
||||
@@ -41,7 +88,86 @@ Iridium::AIR::Function::Function(Type type, const std::string& name, const void*
|
||||
// TEST
|
||||
#include <iostream>
|
||||
|
||||
@@ -4496,7 +4506,7 @@ index 21c501f..d052cf4 100644
|
||||
|
||||
static Iridium::SPIRV::ResultID llvmTypeToSPIRVType(Iridium::SPIRV::Builder& builder, LLVMTypeRef llvmType) {
|
||||
using namespace Iridium::SPIRV;
|
||||
@@ -68,6 +184,10 @@ static Iridium::SPIRV::ResultID llvmTypeToSPIRVType(Iridium::SPIRV::Builder& bui
|
||||
@@ -68,6 +194,10 @@ static Iridium::SPIRV::ResultID llvmTypeToSPIRVType(Iridium::SPIRV::Builder& bui
|
||||
return builder.declareType(Type(Type::FloatTag {}, 128));
|
||||
|
||||
case LLVMIntegerTypeKind:
|
||||
@@ -4507,7 +4517,7 @@ index 21c501f..d052cf4 100644
|
||||
return builder.declareType(Type(Type::IntegerTag {}, DynamicLLVM::LLVMGetIntTypeWidth(llvmType), /* TODO: how to determine this? */ true));
|
||||
|
||||
case LLVMFunctionTypeKind: {
|
||||
@@ -117,28 +237,35 @@ static Iridium::SPIRV::ResultID llvmTypeToSPIRVType(Iridium::SPIRV::Builder& bui
|
||||
@@ -117,28 +247,35 @@ static Iridium::SPIRV::ResultID llvmTypeToSPIRVType(Iridium::SPIRV::Builder& bui
|
||||
}
|
||||
// align the final offset to determine the structure size
|
||||
offset = (offset + (structAlignment - 1)) & ~(structAlignment - 1);
|
||||
@@ -4547,7 +4557,7 @@ index 21c501f..d052cf4 100644
|
||||
} break;
|
||||
|
||||
default:
|
||||
@@ -162,14 +289,32 @@ static Iridium::SPIRV::ResultID llvmValueToResultID(Iridium::SPIRV::Builder& bui
|
||||
@@ -162,14 +299,32 @@ static Iridium::SPIRV::ResultID llvmValueToResultID(Iridium::SPIRV::Builder& bui
|
||||
{
|
||||
ResultID maybe = builder.lookupResultID(reinterpret_cast<uintptr_t>(llvmValue));
|
||||
if (maybe != ResultIDInvalid) {
|
||||
@@ -4580,7 +4590,7 @@ index 21c501f..d052cf4 100644
|
||||
// TODO: how to determine if it's signed or not?
|
||||
return (width <= 32) ? builder.declareConstantScalar<int32_t>(val) : builder.declareConstantScalar<int64_t>(val);
|
||||
} break;
|
||||
@@ -191,13 +336,15 @@ static Iridium::SPIRV::ResultID llvmValueToResultID(Iridium::SPIRV::Builder& bui
|
||||
@@ -191,13 +346,15 @@ static Iridium::SPIRV::ResultID llvmValueToResultID(Iridium::SPIRV::Builder& bui
|
||||
} break;
|
||||
|
||||
default:
|
||||
@@ -4599,7 +4609,7 @@ index 21c501f..d052cf4 100644
|
||||
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
auto constant = DynamicLLVM::LLVMGetOperand(llvmValue, i);
|
||||
@@ -207,10 +354,11 @@ static Iridium::SPIRV::ResultID llvmValueToResultID(Iridium::SPIRV::Builder& bui
|
||||
@@ -207,10 +364,11 @@ static Iridium::SPIRV::ResultID llvmValueToResultID(Iridium::SPIRV::Builder& bui
|
||||
return builder.declareConstantComposite(llvmTypeToSPIRVType(builder, type), vals);
|
||||
} break;
|
||||
|
||||
@@ -4613,7 +4623,7 @@ index 21c501f..d052cf4 100644
|
||||
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
auto constant = DynamicLLVM::LLVMGetElementAsConstant(llvmValue, i);
|
||||
@@ -220,7 +368,8 @@ static Iridium::SPIRV::ResultID llvmValueToResultID(Iridium::SPIRV::Builder& bui
|
||||
@@ -220,7 +378,8 @@ static Iridium::SPIRV::ResultID llvmValueToResultID(Iridium::SPIRV::Builder& bui
|
||||
return builder.declareConstantComposite(llvmTypeToSPIRVType(builder, type), vals);
|
||||
} break;
|
||||
|
||||
@@ -4623,7 +4633,7 @@ index 21c501f..d052cf4 100644
|
||||
return builder.declareUndefinedValue(llvmTypeToSPIRVType(builder, type));
|
||||
} break;
|
||||
|
||||
@@ -231,6 +380,21 @@ static Iridium::SPIRV::ResultID llvmValueToResultID(Iridium::SPIRV::Builder& bui
|
||||
@@ -231,6 +390,21 @@ static Iridium::SPIRV::ResultID llvmValueToResultID(Iridium::SPIRV::Builder& bui
|
||||
|
||||
case LLVMConstantExprValueKind: {
|
||||
switch (DynamicLLVM::LLVMGetConstOpcode(llvmValue)) {
|
||||
@@ -4645,7 +4655,7 @@ index 21c501f..d052cf4 100644
|
||||
case LLVMBitCast: {
|
||||
auto target = DynamicLLVM::LLVMGetOperand(llvmValue, 0);
|
||||
auto targetID = llvmValueToResultID(builder, target);
|
||||
@@ -254,13 +418,315 @@ static Iridium::SPIRV::ResultID llvmValueToResultID(Iridium::SPIRV::Builder& bui
|
||||
@@ -254,13 +428,315 @@ static Iridium::SPIRV::ResultID llvmValueToResultID(Iridium::SPIRV::Builder& bui
|
||||
} break;
|
||||
|
||||
default:
|
||||
@@ -4899,7 +4909,7 @@ index 21c501f..d052cf4 100644
|
||||
+ if (parts.size() == 3 && (fn == "all" || fn == "any")) {
|
||||
+ resID = builder.encodeRawOp(fn == "all" ? SPIRVOp::All : SPIRVOp::Any, type, { operand(0) });
|
||||
+ return true;
|
||||
}
|
||||
+ }
|
||||
+
|
||||
+ // integer functions: air.<fn>.<s|u>.<type>
|
||||
+ if (parts.size() == 4 && (parts[2] == "s" || parts[2] == "u")) {
|
||||
@@ -4957,13 +4967,13 @@ index 21c501f..d052cf4 100644
|
||||
+ return false;
|
||||
+ }
|
||||
+ return true;
|
||||
+ }
|
||||
}
|
||||
+
|
||||
+ return false;
|
||||
};
|
||||
|
||||
void Iridium::AIR::Function::analyze(SPIRV::Builder& builder, OutputInfo& outputInfo) {
|
||||
@@ -312,7 +778,7 @@ void Iridium::AIR::Function::analyze(SPIRV::Builder& builder, OutputInfo& output
|
||||
@@ -312,7 +788,7 @@ void Iridium::AIR::Function::analyze(SPIRV::Builder& builder, OutputInfo& output
|
||||
});
|
||||
|
||||
// declare the GLSL PerVertex structure
|
||||
@@ -4972,7 +4982,7 @@ index 21c501f..d052cf4 100644
|
||||
SPIRV::Type::Member {
|
||||
vec4Type,
|
||||
0,
|
||||
@@ -321,13 +787,21 @@ void Iridium::AIR::Function::analyze(SPIRV::Builder& builder, OutputInfo& output
|
||||
@@ -321,13 +797,21 @@ void Iridium::AIR::Function::analyze(SPIRV::Builder& builder, OutputInfo& output
|
||||
}
|
||||
},
|
||||
// TODO: declare the rest of the structure
|
||||
@@ -4995,7 +5005,7 @@ index 21c501f..d052cf4 100644
|
||||
|
||||
// declare the GLSL VertexIndex input variable
|
||||
vertexIndexVar = builder.addGlobalVariable(ptrIntType, SPIRV::StorageClass::Input);
|
||||
@@ -425,7 +899,7 @@ void Iridium::AIR::Function::analyze(SPIRV::Builder& builder, OutputInfo& output
|
||||
@@ -425,7 +909,7 @@ void Iridium::AIR::Function::analyze(SPIRV::Builder& builder, OutputInfo& output
|
||||
// if the function returns a structure, we need to separate the components
|
||||
// into separate output variables.
|
||||
|
||||
@@ -5004,7 +5014,7 @@ index 21c501f..d052cf4 100644
|
||||
auto funcRetType = DynamicLLVM::LLVMGetReturnType(llfuncType);
|
||||
std::vector<LLVMTypeRef> funcParamTypes(DynamicLLVM::LLVMCountParamTypes(llfuncType));
|
||||
DynamicLLVM::LLVMGetParamTypes(llfuncType, funcParamTypes.data());
|
||||
@@ -460,7 +934,7 @@ void Iridium::AIR::Function::analyze(SPIRV::Builder& builder, OutputInfo& output
|
||||
@@ -460,7 +944,7 @@ void Iridium::AIR::Function::analyze(SPIRV::Builder& builder, OutputInfo& output
|
||||
auto type = llvmTypeToSPIRVType(builder, DynamicLLVM::LLVMStructGetTypeAtIndex(funcRetType, i));
|
||||
auto ptrType = builder.declareType(SPIRV::Type(SPIRV::Type::PointerTag {}, SPIRV::StorageClass::Output, type, 8));
|
||||
auto var = builder.addGlobalVariable(ptrType, SPIRV::StorageClass::Output);
|
||||
@@ -5013,7 +5023,7 @@ index 21c501f..d052cf4 100644
|
||||
++location;
|
||||
_outputValueIDs.push_back(var);
|
||||
builder.referenceGlobalVariable(var);
|
||||
@@ -472,13 +946,60 @@ void Iridium::AIR::Function::analyze(SPIRV::Builder& builder, OutputInfo& output
|
||||
@@ -472,13 +956,60 @@ void Iridium::AIR::Function::analyze(SPIRV::Builder& builder, OutputInfo& output
|
||||
auto type = llvmTypeToSPIRVType(builder, funcRetType);
|
||||
auto ptrType = builder.declareType(SPIRV::Type(SPIRV::Type::PointerTag {}, SPIRV::StorageClass::Output, type, 8));
|
||||
auto var = builder.addGlobalVariable(ptrType, SPIRV::StorageClass::Output);
|
||||
@@ -5075,7 +5085,7 @@ index 21c501f..d052cf4 100644
|
||||
|
||||
//
|
||||
// analyze parameters and mark special values (like the vertex ID)
|
||||
@@ -519,7 +1040,9 @@ void Iridium::AIR::Function::analyze(SPIRV::Builder& builder, OutputInfo& output
|
||||
@@ -519,7 +1050,9 @@ void Iridium::AIR::Function::analyze(SPIRV::Builder& builder, OutputInfo& output
|
||||
auto kind = llvmMDStringToStringView(parameterInfo[1]);
|
||||
|
||||
if (kind == "air.buffer") {
|
||||
@@ -5086,7 +5096,7 @@ index 21c501f..d052cf4 100644
|
||||
auto addrPtrTypeInst = SPIRV::Type(SPIRV::Type::PointerTag {}, SPIRV::StorageClass::PhysicalStorageBuffer, type, 8);
|
||||
auto addrPtrType = builder.declareType(addrPtrTypeInst);
|
||||
bufferMembers.push_back(SPIRV::Type::Member { addrPtrType, 8 * bufferIndex, {} });
|
||||
@@ -575,7 +1098,21 @@ void Iridium::AIR::Function::analyze(SPIRV::Builder& builder, OutputInfo& output
|
||||
@@ -575,7 +1108,21 @@ void Iridium::AIR::Function::analyze(SPIRV::Builder& builder, OutputInfo& output
|
||||
_parameterIDs.push_back(load);
|
||||
builder.associateExistingResultID(load, llparamVal);
|
||||
builder.setResultType(load, intType);
|
||||
@@ -5109,7 +5119,7 @@ index 21c501f..d052cf4 100644
|
||||
// find the location index info
|
||||
size_t infoIdx = 0;
|
||||
for (; infoIdx < parameterInfo.size(); ++infoIdx) {
|
||||
@@ -620,7 +1157,7 @@ void Iridium::AIR::Function::analyze(SPIRV::Builder& builder, OutputInfo& output
|
||||
@@ -620,7 +1167,7 @@ void Iridium::AIR::Function::analyze(SPIRV::Builder& builder, OutputInfo& output
|
||||
auto var = builder.addGlobalVariable(ptrType, SPIRV::StorageClass::Input);
|
||||
auto load = builder.encodeLoad(type, var);
|
||||
|
||||
@@ -5118,7 +5128,7 @@ index 21c501f..d052cf4 100644
|
||||
++paramLocation;
|
||||
|
||||
builder.referenceGlobalVariable(var);
|
||||
@@ -631,9 +1168,18 @@ void Iridium::AIR::Function::analyze(SPIRV::Builder& builder, OutputInfo& output
|
||||
@@ -631,9 +1178,18 @@ void Iridium::AIR::Function::analyze(SPIRV::Builder& builder, OutputInfo& output
|
||||
builder.setResultType(load, type);
|
||||
} else if (kind == "air.vertex_input") {
|
||||
auto type = llvmTypeToSPIRVType(builder, funcParamTypes[i]);
|
||||
@@ -5139,7 +5149,7 @@ index 21c501f..d052cf4 100644
|
||||
|
||||
// find the location index info
|
||||
size_t infoIdx = 0;
|
||||
@@ -761,14 +1307,19 @@ void Iridium::AIR::Function::analyze(SPIRV::Builder& builder, OutputInfo& output
|
||||
@@ -761,14 +1317,19 @@ void Iridium::AIR::Function::analyze(SPIRV::Builder& builder, OutputInfo& output
|
||||
realSampleType = fakeSampleType;
|
||||
}
|
||||
|
||||
@@ -5167,7 +5177,7 @@ index 21c501f..d052cf4 100644
|
||||
auto imagePtrType = builder.declareType(SPIRV::Type(SPIRV::Type::PointerTag {}, SPIRV::StorageClass::UniformConstant, imageType, 8));
|
||||
auto var = builder.addGlobalVariable(imagePtrType, SPIRV::StorageClass::UniformConstant);
|
||||
//auto load = builder.encodeLoad(imageType, var);
|
||||
@@ -959,68 +1510,98 @@ void Iridium::AIR::Function::analyze(SPIRV::Builder& builder, OutputInfo& output
|
||||
@@ -959,68 +1520,98 @@ void Iridium::AIR::Function::analyze(SPIRV::Builder& builder, OutputInfo& output
|
||||
enum class CFGType {
|
||||
None = 0,
|
||||
Selection,
|
||||
@@ -5244,7 +5254,7 @@ index 21c501f..d052cf4 100644
|
||||
+ next.insert(bb);if(next!=postdom[bb]){postdom[bb]=std::move(next);changed=true;}
|
||||
+ }}
|
||||
+ for(auto bb:blocks){auto term=DynamicLLVM::LLVMGetLastInstruction(bb);
|
||||
+ if(DynamicLLVM::LLVMGetInstructionOpcode(term)!=LLVMBr || !DynamicLLVM::LLVMIsConditional(term))continue;
|
||||
+ if(!IRIDIUM_IS_BR(DynamicLLVM::LLVMGetInstructionOpcode(term)) || !DynamicLLVM::LLVMIsConditional(term))continue;
|
||||
+ LLVMBasicBlockRef merge=nullptr;
|
||||
+ for(auto candidate:postdom[bb])if(candidate!=bb && (!merge || postdom[candidate].size()>postdom[merge].size()))merge=candidate;
|
||||
+ if(merge)cfgInfos[builder.lookupResultID(reinterpret_cast<uintptr_t>(bb))]={CFGType::Selection,builder.lookupResultID(reinterpret_cast<uintptr_t>(merge))};
|
||||
@@ -5277,7 +5287,7 @@ index 21c501f..d052cf4 100644
|
||||
+ next.insert(block);if(next!=local[block]){local[block]=std::move(next);localChanged=true;}
|
||||
+ }}
|
||||
+ auto headerTerm=DynamicLLVM::LLVMGetLastInstruction(header);
|
||||
+ if(DynamicLLVM::LLVMGetInstructionOpcode(headerTerm)==LLVMBr && DynamicLLVM::LLVMIsConditional(headerTerm) && loop.count(DynamicLLVM::LLVMGetSuccessor(headerTerm,0)) && loop.count(DynamicLLVM::LLVMGetSuccessor(headerTerm,1))){
|
||||
+ if(IRIDIUM_IS_BR(DynamicLLVM::LLVMGetInstructionOpcode(headerTerm)) && DynamicLLVM::LLVMIsConditional(headerTerm) && loop.count(DynamicLLVM::LLVMGetSuccessor(headerTerm,0)) && loop.count(DynamicLLVM::LLVMGetSuccessor(headerTerm,1))){
|
||||
+ LLVMBasicBlockRef innerMerge=nullptr;for(auto candidate:local[header])if(candidate!=header && local[candidate]!=loop && (!innerMerge || local[candidate].size()>local[innerMerge].size()))innerMerge=candidate;
|
||||
+ if(!innerMerge)throw std::runtime_error("AIR loop header selection has no merge");
|
||||
+ auto selectionBlock=builder.reserveResultID();splitLoopSelections[header]={selectionBlock,builder.lookupResultID(reinterpret_cast<uintptr_t>(innerMerge))};
|
||||
@@ -5315,7 +5325,7 @@ index 21c501f..d052cf4 100644
|
||||
if (isFirst) {
|
||||
isFirst = false;
|
||||
|
||||
@@ -1030,16 +1611,54 @@ void Iridium::AIR::Function::analyze(SPIRV::Builder& builder, OutputInfo& output
|
||||
@@ -1030,16 +1621,54 @@ void Iridium::AIR::Function::analyze(SPIRV::Builder& builder, OutputInfo& output
|
||||
builder.insertLabel(builder.lookupResultID(reinterpret_cast<uintptr_t>(bb)));
|
||||
}
|
||||
|
||||
@@ -5371,7 +5381,7 @@ index 21c501f..d052cf4 100644
|
||||
|
||||
builder.associateExistingResultID(resID, reinterpret_cast<uintptr_t>(inst));
|
||||
builder.setResultType(resID, type);
|
||||
@@ -1048,6 +1667,18 @@ void Iridium::AIR::Function::analyze(SPIRV::Builder& builder, OutputInfo& output
|
||||
@@ -1048,6 +1677,18 @@ void Iridium::AIR::Function::analyze(SPIRV::Builder& builder, OutputInfo& output
|
||||
// TODO: look into using physical addressing instead of logical addressing, which should simplify the translation process
|
||||
// (since i *think* that's the same addressing mode used by Metal code)
|
||||
|
||||
@@ -5390,7 +5400,7 @@ index 21c501f..d052cf4 100644
|
||||
case LLVMGetElementPtr: {
|
||||
auto base = DynamicLLVM::LLVMGetOperand(inst, 0);
|
||||
auto targetType = DynamicLLVM::LLVMTypeOf(inst);
|
||||
@@ -1060,17 +1691,32 @@ void Iridium::AIR::Function::analyze(SPIRV::Builder& builder, OutputInfo& output
|
||||
@@ -1060,17 +1701,32 @@ void Iridium::AIR::Function::analyze(SPIRV::Builder& builder, OutputInfo& output
|
||||
indices.push_back(llvmValueToResultID(builder, llindex));
|
||||
}
|
||||
|
||||
@@ -5424,7 +5434,7 @@ index 21c501f..d052cf4 100644
|
||||
// strangely enough, SPIR-V provides no instruction that can do an initial pointer offset like LLVM's GEP does.
|
||||
// there's OpPtrAccessChain, which is tantalizingly named, but unfortunately that instruction doesn't work as expected
|
||||
// either. i've tried using the raw index (i.e. in element units) and the multiplied index (i.e. in bytes), but no dice.
|
||||
@@ -1112,6 +1758,9 @@ void Iridium::AIR::Function::analyze(SPIRV::Builder& builder, OutputInfo& output
|
||||
@@ -1112,6 +1768,9 @@ void Iridium::AIR::Function::analyze(SPIRV::Builder& builder, OutputInfo& output
|
||||
op = access;
|
||||
}
|
||||
|
||||
@@ -5434,7 +5444,7 @@ index 21c501f..d052cf4 100644
|
||||
auto resID = builder.encodeLoad(type, op, alignment);
|
||||
|
||||
builder.associateExistingResultID(resID, reinterpret_cast<uintptr_t>(inst));
|
||||
@@ -1149,50 +1798,8 @@ void Iridium::AIR::Function::analyze(SPIRV::Builder& builder, OutputInfo& output
|
||||
@@ -1149,50 +1808,8 @@ void Iridium::AIR::Function::analyze(SPIRV::Builder& builder, OutputInfo& output
|
||||
auto arg = DynamicLLVM::LLVMGetOperand(inst, 0);
|
||||
|
||||
resID = builder.encodeConvertUToF(type, llvmValueToResultID(builder, arg));
|
||||
@@ -5487,7 +5497,7 @@ index 21c501f..d052cf4 100644
|
||||
auto arg = DynamicLLVM::LLVMGetOperand(inst, 0);
|
||||
|
||||
resID = builder.encodeFConvert(type, llvmValueToResultID(builder, arg));
|
||||
@@ -1223,14 +1830,33 @@ void Iridium::AIR::Function::analyze(SPIRV::Builder& builder, OutputInfo& output
|
||||
@@ -1223,14 +1840,33 @@ void Iridium::AIR::Function::analyze(SPIRV::Builder& builder, OutputInfo& output
|
||||
auto operand2 = DynamicLLVM::LLVMGetOperand(inst, 1);
|
||||
|
||||
resID = builder.encodeFMax(type, llvmValueToResultID(builder, operand1), llvmValueToResultID(builder, operand2));
|
||||
@@ -5524,7 +5534,7 @@ index 21c501f..d052cf4 100644
|
||||
case LLVMFMul:
|
||||
case LLVMFDiv:
|
||||
case LLVMFAdd:
|
||||
@@ -1365,28 +1991,81 @@ void Iridium::AIR::Function::analyze(SPIRV::Builder& builder, OutputInfo& output
|
||||
@@ -1365,28 +2001,81 @@ void Iridium::AIR::Function::analyze(SPIRV::Builder& builder, OutputInfo& output
|
||||
auto op1 = DynamicLLVM::LLVMGetOperand(inst, 0);
|
||||
auto op2 = DynamicLLVM::LLVMGetOperand(inst, 1);
|
||||
auto predicate = DynamicLLVM::LLVMGetFCmpPredicate(inst);
|
||||
@@ -5590,10 +5600,11 @@ index 21c501f..d052cf4 100644
|
||||
|
||||
+ auto resID = builder.encodeRawOp(spirvOpcode, type, { llvmValueToResultID(builder, op1), llvmValueToResultID(builder, op2) });
|
||||
+
|
||||
+ builder.associateExistingResultID(resID, reinterpret_cast<uintptr_t>(inst));
|
||||
+ builder.setResultType(resID, type);
|
||||
+ } break;
|
||||
+
|
||||
builder.associateExistingResultID(resID, reinterpret_cast<uintptr_t>(inst));
|
||||
builder.setResultType(resID, type);
|
||||
} break;
|
||||
|
||||
- case LLVMBr: {
|
||||
+ case LLVMSelect: {
|
||||
+ auto type = llvmTypeToSPIRVType(builder, DynamicLLVM::LLVMTypeOf(inst));
|
||||
+ auto resID = builder.encodeRawOp(SPIRVOp::Select, type, {
|
||||
@@ -5602,11 +5613,11 @@ index 21c501f..d052cf4 100644
|
||||
+ llvmValueToResultID(builder, DynamicLLVM::LLVMGetOperand(inst, 2)),
|
||||
+ });
|
||||
+
|
||||
builder.associateExistingResultID(resID, reinterpret_cast<uintptr_t>(inst));
|
||||
builder.setResultType(resID, type);
|
||||
} break;
|
||||
|
||||
case LLVMBr: {
|
||||
+ builder.associateExistingResultID(resID, reinterpret_cast<uintptr_t>(inst));
|
||||
+ builder.setResultType(resID, type);
|
||||
+ } break;
|
||||
+
|
||||
+ IRIDIUM_BR_CASES: {
|
||||
+ auto loop=cfgInfos.find(builder.lookupResultID(reinterpret_cast<uintptr_t>(bb)));
|
||||
+ if(loop!=cfgInfos.end() && loop->second.type==CFGType::Loop){
|
||||
+ builder.encodeRawOpNoResult(246,{loop->second.selectionMergeBlock,loop->second.continueBlock,0});
|
||||
@@ -5616,7 +5627,7 @@ index 21c501f..d052cf4 100644
|
||||
if (DynamicLLVM::LLVMIsConditional(inst)) {
|
||||
auto condition = DynamicLLVM::LLVMGetCondition(inst);
|
||||
auto trueLabel = DynamicLLVM::LLVMBasicBlockAsValue(DynamicLLVM::LLVMGetSuccessor(inst, 0));
|
||||
@@ -1422,25 +2101,38 @@ void Iridium::AIR::Function::analyze(SPIRV::Builder& builder, OutputInfo& output
|
||||
@@ -1422,25 +2111,38 @@ void Iridium::AIR::Function::analyze(SPIRV::Builder& builder, OutputInfo& output
|
||||
auto type = llvmTypeToSPIRVType(builder, lltype);
|
||||
|
||||
std::vector<std::pair<SPIRV::ResultID, SPIRV::ResultID>> variablesAndBlocks;
|
||||
@@ -5658,7 +5669,7 @@ index 21c501f..d052cf4 100644
|
||||
|
||||
// ensure the resulting pointer storage class is the same as the input pointer storage class
|
||||
auto typeInst = *builder.reverseLookupType(type);
|
||||
@@ -1474,6 +2166,7 @@ void Iridium::AIR::Function::analyze(SPIRV::Builder& builder, OutputInfo& output
|
||||
@@ -1474,6 +2176,7 @@ void Iridium::AIR::Function::analyze(SPIRV::Builder& builder, OutputInfo& output
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+58
-2
@@ -4706,10 +4706,29 @@ index b998456..3e643c1 100644
|
||||
__linux_nanosleep(&tv, &tv);
|
||||
|
||||
diff --git a/libsyscall/CMakeLists.txt b/libsyscall/CMakeLists.txt
|
||||
index 57cebad..145ce0d 100755
|
||||
index 57cebad..3014255 100755
|
||||
--- a/libsyscall/CMakeLists.txt
|
||||
+++ b/libsyscall/CMakeLists.txt
|
||||
@@ -179,6 +179,7 @@ set(syscall_sources
|
||||
@@ -104,6 +104,18 @@ foreach(MIG_FILENAME IN LISTS LIBSYSCALL_MIGS)
|
||||
)
|
||||
endforeach()
|
||||
|
||||
+# emulation/src/xnu_syscall/mach/impl/mach_traps.c wraps semaphore_create/semaphore_destroy
|
||||
+# (process-local semaphores) and mach_vm_region_recurse (own task). The MIG stubs get private
|
||||
+# names, which the wrappers call.
|
||||
+foreach(MIG_ARCH i386 x86_64)
|
||||
+ set_source_files_properties("${CMAKE_CURRENT_BINARY_DIR}/mach/task-${MIG_ARCH}-User.c" PROPERTIES
|
||||
+ COMPILE_DEFINITIONS "semaphore_create=__darling_rpc_semaphore_create;semaphore_destroy=__darling_rpc_semaphore_destroy"
|
||||
+ )
|
||||
+ set_source_files_properties("${CMAKE_CURRENT_BINARY_DIR}/mach/mach_vm-${MIG_ARCH}-User.c" PROPERTIES
|
||||
+ COMPILE_DEFINITIONS "mach_vm_region_recurse=__darling_rpc_mach_vm_region_recurse"
|
||||
+ )
|
||||
+endforeach()
|
||||
+
|
||||
# mach/exc also needs the server component
|
||||
list(APPEND syscall_mig_sources_32 "${CMAKE_CURRENT_BINARY_DIR}/mach/exc-i386-Server.c")
|
||||
list(APPEND syscall_mig_sources_64 "${CMAKE_CURRENT_BINARY_DIR}/mach/exc-x86_64-Server.c")
|
||||
@@ -179,6 +191,7 @@ set(syscall_sources
|
||||
wrappers/csr.c
|
||||
wrappers/fs_snapshot.c
|
||||
wrappers/__get_cpu_capabilities.S
|
||||
@@ -4911,3 +4930,40 @@ new file mode 100644
|
||||
+__sysctlbyname:
|
||||
+ /* Just jump to the syscall stub */
|
||||
+ jmp ___sysctlbyname
|
||||
diff --git a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/mman/madvise.c b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/mman/madvise.c
|
||||
index 10c412b..8c49bd9 100644
|
||||
--- a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/mman/madvise.c
|
||||
+++ b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/mman/madvise.c
|
||||
@@ -9,18 +9,26 @@
|
||||
#define MADV_FREE 5
|
||||
#define MADV_FREE_REUSABLE 7
|
||||
#define MADV_FREE_REUSE 8
|
||||
+#define LINUX_MADV_FREE 8
|
||||
+#define LINUX_MADV_POPULATE_WRITE 23
|
||||
|
||||
long sys_madvise(void* addr, unsigned long len, int advice)
|
||||
{
|
||||
int ret;
|
||||
|
||||
- if (advice == MADV_FREE_REUSABLE || advice == MADV_FREE_REUSE)
|
||||
- advice = MADV_FREE;
|
||||
-
|
||||
- // advice < 0 are identical between OS X and Linux
|
||||
- // advice >= 5 are specific to OS X/BSD
|
||||
- if (advice >= 5)
|
||||
+ if (advice == MADV_FREE || advice == MADV_FREE_REUSABLE) {
|
||||
+ // The allocator no longer needs these private anonymous pages. Linux
|
||||
+ // may reclaim them without swapping; subsequent writes cancel reclaim.
|
||||
+ advice = LINUX_MADV_FREE;
|
||||
+ } else if (advice == MADV_FREE_REUSE) {
|
||||
+ // Reused pages must stop being lazily discardable. A write-prefault
|
||||
+ // cancels MADV_FREE without changing the surviving page contents.
|
||||
+ // This operation is available on Linux 5.14 and later.
|
||||
+ advice = LINUX_MADV_POPULATE_WRITE;
|
||||
+ } else if (advice >= 5) {
|
||||
+ // Other OS X/BSD-specific hints do not share Linux's numeric values.
|
||||
return 0;
|
||||
+ }
|
||||
|
||||
ret = LINUX_SYSCALL(__NR_madvise, addr, len, advice);
|
||||
|
||||
+5
-1
@@ -5,7 +5,11 @@ cd "$repo"
|
||||
python3 scripts/configure.py
|
||||
if [ -d build/venv/bin ]; then PATH="$repo/build/venv/bin:$PATH"; export PATH; fi
|
||||
python3 scripts/prepare.py
|
||||
# Empty build type, as upstream Darling uses: objc4 always defines OBJC_IS_DEBUG_BUILD=1,
|
||||
# so Release's -DNDEBUG trips its "mismatch in debug-ness macros" #error.
|
||||
# x86-64 only: the patched OpenDirectory declares ivars in @implementation, which the
|
||||
# 32-bit (fragile ABI) slice cannot compile, and the macOS client is 64-bit.
|
||||
cmake -S build/darling-src -B build/darling-src/build -G 'Unix Makefiles' \
|
||||
-DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr/local -DENABLE_METAL=ON
|
||||
-DCMAKE_BUILD_TYPE= -DTARGET_i386=OFF -DCMAKE_INSTALL_PREFIX=/usr/local -DENABLE_METAL=ON
|
||||
cmake --build build/darling-src/build --parallel "${JOBS:-$(nproc)}"
|
||||
echo 'Build complete. Install the base runtime with: sudo cmake --install build/darling-src/build'
|
||||
@@ -7,6 +7,10 @@ python3 scripts/configure.py
|
||||
if [ -d build/venv/bin ]; then PATH="$repo/build/venv/bin:$PATH"; export PATH; fi
|
||||
# Initialize the normal user-owned prefix before installing overrides.
|
||||
darling shell /usr/bin/true
|
||||
# The prefix has no /private/tmp of its own, so the guest falls through to the root-owned
|
||||
# installed layer and every write there (the Riot installer's temp files) fails with EACCES.
|
||||
mkdir -p "$HOME/.darling/private/tmp"
|
||||
chmod 1777 "$HOME/.darling/private/tmp"
|
||||
python3 build/runtime/tools/stop-darling-runtime.py
|
||||
sh build/runtime/tools/install-riot-presentation-runtime.sh
|
||||
sh build/runtime/tools/install-riot-gpu-runtime.sh
|
||||
|
||||
@@ -5,7 +5,7 @@ cd "$repo"
|
||||
region=${1:-euw}
|
||||
case "$region" in euw|eune|na|br|kr|jp|lan|las|oce|ru|tr) ;; *) echo 'Use a Riot installer region code' >&2; exit 1;; esac
|
||||
python3 scripts/configure.py >/dev/null
|
||||
sh build/runtime/tools/fetch-installer.sh "$region" build/installer
|
||||
bash build/runtime/tools/fetch-installer.sh "$region" build/installer
|
||||
unzip -q -o "build/installer/live.$region.zip" -d build/installer
|
||||
RIOT_LAUNCHER_HOST="$repo/build/installer/Install League of Legends $region.app/Contents/MacOS/RiotClientServices"
|
||||
RCLOL_REGION=$(printf '%s' "$region" | tr '[:lower:]' '[:upper:]')
|
||||
|
||||
@@ -21,9 +21,9 @@ link = shlex.split((framework/'CMakeFiles/OpenGL.dir/link.txt').read_text())
|
||||
link[link.index('CMakeFiles/OpenGL.dir/OpenGL.c.o')] = str(object_file)
|
||||
link[link.index('-o')+1] = str(project/'build/m327-OpenGL')
|
||||
run(link, framework)
|
||||
for name in ['context', 'render', 'planes', 'direct']:
|
||||
for name in ['context', 'render', 'planes', 'direct', 'resolver']:
|
||||
obj = project/f'build/m327-{name}-check.o'
|
||||
source = project/'tools/m58f352-cgl-direct-check.c' if name == 'direct' else project/'tools/m58f340-cgl-planes-check.c' if name == 'planes' else project/f'tools/m58f327-cgl-{name}-check.c'
|
||||
source = project/'tools/riot-gl-resolver-check.c' if name == 'resolver' else project/'tools/m58f352-cgl-direct-check.c' if name == 'direct' else project/'tools/m58f340-cgl-planes-check.c' if name == 'planes' else project/f'tools/m58f327-cgl-{name}-check.c'
|
||||
run([*base, '-c', str(source), '-o', str(obj)])
|
||||
run(['clang', '-target', 'x86_64-apple-macosx11.0', '-fuse-ld=lld', '-nostdlib',
|
||||
'-isysroot', '@DARLING_SDK@', str(obj), '-lSystem', '-framework', 'OpenGL',
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build the host-only check against Darling's actual madvise implementation."""
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
|
||||
project = Path(__file__).resolve().parent.parent
|
||||
folder = project / "build/logs/madvise-fix"
|
||||
headers = project / "build/darling-src/src/external/xnu/darling/src/libsystem_kernel/emulation/include"
|
||||
alias = folder / "include/darling/emulation"
|
||||
alias.parent.mkdir(parents=True, exist_ok=True)
|
||||
if not alias.exists():
|
||||
alias.symlink_to(headers, target_is_directory=True)
|
||||
if alias.resolve() != headers.resolve():
|
||||
raise RuntimeError("Unexpected host-test header alias")
|
||||
source = project / "build/darling-src/src/external/xnu/darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/mman/madvise.c"
|
||||
binary = folder / "host-madvise-check"
|
||||
subprocess.run(["cc", "-std=c11", "-O2", "-I", str(folder / "include"),
|
||||
str(project / "tools/riot-host-madvise-check.c"), str(source), "-o", str(binary)], check=True)
|
||||
print("Built", binary)
|
||||
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build the nullable LaunchServices error-output regression probe."""
|
||||
from pathlib import Path
|
||||
import shlex
|
||||
import subprocess
|
||||
|
||||
project = Path(__file__).resolve().parent.parent
|
||||
framework = project / 'build/darling-src/build/src/frameworks/CoreServices/src/LaunchServices'
|
||||
flags = {}
|
||||
for line in (framework / 'CMakeFiles/LaunchServices.dir/flags.make').read_text().splitlines():
|
||||
if line.startswith('C_') and ' = ' in line:
|
||||
name, value = line.split(' = ', 1)
|
||||
flags[name] = shlex.split(value)
|
||||
name = 'riot-launchservices-null-error-check'
|
||||
obj = project / ('build/' + name + '.o')
|
||||
subprocess.run(['clang', *flags['C_DEFINES'], *flags['C_INCLUDES'], *flags['C_FLAGS'],
|
||||
'-O2', '-Wno-macro-redefined', '-c', str(project / ('tools/' + name + '.c')),
|
||||
'-o', str(obj)], check=True)
|
||||
subprocess.run(['clang', '-target', 'x86_64-apple-macosx11.0', '-fuse-ld=lld', '-nostdlib',
|
||||
'-isysroot', '@DARLING_SDK@', str(obj), '-lSystem', '-framework', 'CoreServices',
|
||||
'-framework', 'CoreFoundation', '-framework', 'Foundation',
|
||||
'-o', str(project / ('build/' + name))], check=True)
|
||||
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build a standalone guest reclamation check; do not run or install it."""
|
||||
from pathlib import Path
|
||||
import json
|
||||
import shlex
|
||||
import subprocess
|
||||
|
||||
project = Path(__file__).resolve().parent.parent
|
||||
folder = project / "build/logs/madvise-fix"
|
||||
folder.mkdir(parents=True, exist_ok=True)
|
||||
flags_path = project / "build/darling-src/build/src/frameworks/CoreServices/src/LaunchServices/CMakeFiles/LaunchServices.dir/flags.make"
|
||||
flags = {}
|
||||
for line in flags_path.read_text().splitlines():
|
||||
if line.startswith("C_") and " = " in line:
|
||||
name, value = line.split(" = ", 1)
|
||||
flags[name] = shlex.split(value)
|
||||
object_file = folder / "riot-madvise-check.o"
|
||||
subprocess.run(["clang", *flags["C_DEFINES"], *flags["C_INCLUDES"], *flags["C_FLAGS"],
|
||||
"-O2", "-Wno-macro-redefined", "-c", str(project / "tools/riot-madvise-check.c"),
|
||||
"-o", str(object_file)], check=True)
|
||||
sdk = json.loads((project / "build/config.json").read_text())["sdk"]
|
||||
subprocess.run(["clang", "-target", "x86_64-apple-macosx11.0", "-fuse-ld=lld", "-nostdlib",
|
||||
"-isysroot", sdk, str(object_file), "-lSystem", "-o", str(folder / "riot-madvise-check")], check=True)
|
||||
print("Built", folder / "riot-madvise-check")
|
||||
@@ -8,6 +8,8 @@ URL="https://lol.secure.dyn.riotcdn.net/channels/public/x/installer/current/live
|
||||
echo "GET $URL"
|
||||
curl -L --fail -o "$OUT/live.${REGION}.zip" "$URL"
|
||||
ls -lh "$OUT/live.${REGION}.zip"
|
||||
unzip -l "$OUT/live.${REGION}.zip" | head -15
|
||||
unzip -p "$OUT/live.${REGION}.zip" "Install League of Legends ${REGION}.app/Contents/Info.plist" | grep -A1 FileVersion
|
||||
# head closes the pipe early; unzip then dies of SIGPIPE, which pipefail would turn into a failure.
|
||||
{ unzip -l "$OUT/live.${REGION}.zip" | head -15; } || true
|
||||
# Informational only: current installers ship no Info.plist.
|
||||
unzip -p "$OUT/live.${REGION}.zip" "Install League of Legends ${REGION}.app/Contents/Info.plist" 2>/dev/null | grep -A1 FileVersion || true
|
||||
echo "saved to $OUT/live.${REGION}.zip"
|
||||
@@ -0,0 +1,100 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Atomically install a built Darling server; never stop or start any process."""
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import stat
|
||||
import tempfile
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
|
||||
|
||||
def digest(path):
|
||||
with path.open("rb") as input:
|
||||
return hashlib.file_digest(input, "sha256").hexdigest()
|
||||
|
||||
|
||||
def live_binaries(destination):
|
||||
result = {}
|
||||
for process in Path("/proc").iterdir():
|
||||
if not process.name.isdigit():
|
||||
continue
|
||||
executable = process / "exe"
|
||||
try:
|
||||
if str(executable.readlink()).removesuffix(" (deleted)") == str(destination):
|
||||
result[process.name] = digest(executable)
|
||||
except (OSError, ProcessLookupError):
|
||||
continue
|
||||
return result
|
||||
|
||||
|
||||
def install(source, destination, backups):
|
||||
source = source.resolve()
|
||||
destination = destination.absolute()
|
||||
if destination.is_symlink():
|
||||
raise ValueError("The server destination must be a regular file, not a symlink")
|
||||
installed = destination.stat()
|
||||
if not stat.S_ISREG(installed.st_mode):
|
||||
raise ValueError("The server destination is not a regular file")
|
||||
with source.open("rb") as input:
|
||||
if input.read(4) != b"\x7fELF":
|
||||
raise ValueError("The source is not an ELF binary")
|
||||
new_hash, old_hash = digest(source), digest(destination)
|
||||
before = live_binaries(destination)
|
||||
if new_hash == old_hash:
|
||||
return {"installed": True, "already_current": True, "sha256": new_hash, "restart_performed": False}
|
||||
backups.mkdir(parents=True, exist_ok=True)
|
||||
backup = backups / ("darlingserver-" + old_hash)
|
||||
if backup.exists():
|
||||
if digest(backup) != old_hash:
|
||||
raise ValueError("The existing backup has unexpected contents")
|
||||
else:
|
||||
shutil.copy2(destination, backup)
|
||||
fd, temporary = tempfile.mkstemp(prefix=".darlingserver-staged-", dir=destination.parent)
|
||||
try:
|
||||
with os.fdopen(fd, "wb") as output, source.open("rb") as input:
|
||||
shutil.copyfileobj(input, output)
|
||||
output.flush()
|
||||
os.fchmod(output.fileno(), installed.st_mode & 0o777)
|
||||
os.fchown(output.fileno(), installed.st_uid, installed.st_gid)
|
||||
os.fsync(output.fileno())
|
||||
if digest(Path(temporary)) != new_hash:
|
||||
raise ValueError("The staged binary does not match the build")
|
||||
os.replace(temporary, destination)
|
||||
directory = os.open(destination.parent, os.O_DIRECTORY)
|
||||
try:
|
||||
os.fsync(directory)
|
||||
finally:
|
||||
os.close(directory)
|
||||
finally:
|
||||
if os.path.exists(temporary):
|
||||
os.unlink(temporary)
|
||||
if digest(destination) != new_hash:
|
||||
raise ValueError("The installed binary does not match the build")
|
||||
after = live_binaries(destination)
|
||||
if any(after[pid] != old for pid, old in before.items() if pid in after):
|
||||
raise ValueError("An active process no longer maps its original binary")
|
||||
return {
|
||||
"installed": True, "sha256": new_hash, "backup": str(backup),
|
||||
"active_servers_preserved": len(before.keys() & after.keys()), "restart_performed": False,
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--binary", type=Path, default=ROOT / "build/darling-src/build/src/external/darlingserver/darlingserver")
|
||||
parser.add_argument("--destination", type=Path, default=Path("/usr/local/bin/darlingserver"))
|
||||
parser.add_argument("--backups", type=Path, default=ROOT / "build/backups/darlingserver")
|
||||
args = parser.parse_args()
|
||||
try:
|
||||
result = install(args.binary, args.destination, args.backups)
|
||||
except PermissionError:
|
||||
parser.exit(1, "Installation requires permission to write the server directory; run this installer with sudo in your terminal.\n")
|
||||
print(json.dumps(result))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -5,6 +5,9 @@ project_dir=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
||||
guest_home=${RIOT_GUEST_HOME:-$HOME/.darling@GUEST_HOME@}
|
||||
cd "$project_dir"
|
||||
python3 tools/build-m58f333-iosurface.py
|
||||
# Rebuild the native wrapper with floating-point-safe symbol resolvers.
|
||||
make -C build/darling-src/build/src/native GL -j4
|
||||
make -C build/darling-src/build/src/frameworks/CoreServices/src/LaunchServices LaunchServices -j4
|
||||
python3 tools/build-m58f327-opengl.py
|
||||
python3 tools/build-m58f336-quartzcore.py
|
||||
python3 tools/build-m58f341-corevideo.py
|
||||
@@ -58,6 +61,38 @@ for name in IOSurface OpenGL QuartzCore CoreVideo CoreAudio AudioToolbox CoreGra
|
||||
# Dyld resolves Versions/A. Preserve the standard outer symlink as well.
|
||||
if [ ! -L "$framework/$name" ]; then rm -f "$framework/$name"; ln -s "Versions/Current/$name" "$framework/$name"; fi
|
||||
done
|
||||
# Re-exported GL symbols use the native library's absolute install name.
|
||||
# DYLD_LIBRARY_PATH must see this rebuilt wrapper as well as the framework copy.
|
||||
mkdir -p "$guest_home/RuntimeLibraries"
|
||||
gl_wrapper="$guest_home/RuntimeLibraries/libGL.dylib"
|
||||
if [ ! -f "$gl_wrapper.before-float-resolver" ]; then
|
||||
if [ -f "$gl_wrapper" ]; then cp -p "$gl_wrapper" "$gl_wrapper.before-float-resolver"
|
||||
else cp -p @DARLING_SDK@/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGL.dylib "$gl_wrapper.before-float-resolver"; fi
|
||||
fi
|
||||
install -m755 build/darling-src/build/src/native/libGL.dylib "$gl_wrapper.next"
|
||||
mv "$gl_wrapper.next" "$gl_wrapper"
|
||||
install -m755 build/darling-src/build/src/native/libGL.dylib "$guest_home/RuntimeFrameworks/OpenGL.framework/Versions/A/Libraries/libGL.dylib.next"
|
||||
mv "$guest_home/RuntimeFrameworks/OpenGL.framework/Versions/A/Libraries/libGL.dylib.next" "$guest_home/RuntimeFrameworks/OpenGL.framework/Versions/A/Libraries/libGL.dylib"
|
||||
launchservices="$guest_home/RuntimeFrameworks/LaunchServices.framework"
|
||||
if [ ! -d "$launchservices" ]; then
|
||||
cp -a @DARLING_SDK@/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework "$launchservices"
|
||||
fi
|
||||
launchservices_binary="$launchservices/Versions/A/LaunchServices"
|
||||
if [ ! -f "$launchservices_binary.before-null-error" ]; then cp -p "$launchservices_binary" "$launchservices_binary.before-null-error"; fi
|
||||
install -m755 build/darling-src/build/src/frameworks/CoreServices/src/LaunchServices/LaunchServices "$launchservices_binary.next"
|
||||
mv "$launchservices_binary.next" "$launchservices_binary"
|
||||
# Electron opens this subframework by its absolute path, bypassing the
|
||||
# framework search override. Replace the same binary in the stopped prefix.
|
||||
prefix_dir="${DPREFIX:-$HOME/.darling}"
|
||||
launchservices_prefix="$prefix_dir/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/LaunchServices"
|
||||
mkdir -p "$(dirname "$launchservices_prefix")"
|
||||
if [ ! -f "$launchservices_prefix.before-null-error" ]; then
|
||||
if [ -f "$launchservices_prefix" ]; then cp -p "$launchservices_prefix" "$launchservices_prefix.before-null-error"
|
||||
else cp -p @DARLING_SDK@/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/LaunchServices "$launchservices_prefix.before-null-error"; fi
|
||||
fi
|
||||
install -m755 build/darling-src/build/src/frameworks/CoreServices/src/LaunchServices/LaunchServices "$launchservices_prefix.next"
|
||||
mv "$launchservices_prefix.next" "$launchservices_prefix"
|
||||
|
||||
component="$guest_home/Library/Audio/Plug-Ins/Components/DarlingRiotAudio.component"
|
||||
if [ ! -d "$component" ]; then
|
||||
mkdir -p "$(dirname "$component")"
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Install the tested reclamation library atomically, without restarting clients."""
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import shutil
|
||||
import stat
|
||||
import tempfile
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
|
||||
|
||||
def digest(path):
|
||||
with path.open("rb") as input:
|
||||
return hashlib.file_digest(input, "sha256").hexdigest()
|
||||
|
||||
|
||||
def install(binary, destinations, backup_folder):
|
||||
with binary.open("rb") as input:
|
||||
if input.read(4) != b"\xcf\xfa\xed\xfe":
|
||||
raise ValueError("Expected the tested 64-bit Mach-O kernel library")
|
||||
version = re.match(r"(\d+)\.(\d+)", os.uname().release)
|
||||
if not version or tuple(map(int, version.groups())) < (5, 14):
|
||||
raise ValueError("The reuse translation requires Linux 5.14 or newer")
|
||||
expected = digest(binary)
|
||||
records, staged = [], []
|
||||
# Prepare every backup and replacement before changing any destination.
|
||||
try:
|
||||
for index, destination in enumerate(destinations):
|
||||
if destination.is_symlink() or not destination.is_file():
|
||||
raise ValueError("Expected a regular installed library: " + str(destination))
|
||||
metadata = destination.stat()
|
||||
old_hash = digest(destination)
|
||||
if old_hash == expected:
|
||||
records.append({"path": str(destination), "already_current": True})
|
||||
continue
|
||||
backup = backup_folder / (str(index) + "-" + old_hash + ".dylib")
|
||||
backup_folder.mkdir(parents=True, exist_ok=True)
|
||||
if backup.exists():
|
||||
if digest(backup) != old_hash:
|
||||
raise ValueError("The existing backup has unexpected contents")
|
||||
else:
|
||||
shutil.copy2(destination, backup)
|
||||
fd, temporary = tempfile.mkstemp(prefix=".libsystem_kernel-staged-", dir=destination.parent)
|
||||
staged.append((Path(temporary), destination))
|
||||
with os.fdopen(fd, "wb") as output, binary.open("rb") as input:
|
||||
shutil.copyfileobj(input, output)
|
||||
output.flush()
|
||||
os.fchmod(output.fileno(), stat.S_IMODE(metadata.st_mode))
|
||||
os.fchown(output.fileno(), metadata.st_uid, metadata.st_gid)
|
||||
os.fsync(output.fileno())
|
||||
if digest(Path(temporary)) != expected:
|
||||
raise ValueError("The staged library does not match the build")
|
||||
records.append({"path": str(destination), "backup": str(backup)})
|
||||
for temporary, destination in staged:
|
||||
os.replace(temporary, destination)
|
||||
directory = os.open(destination.parent, os.O_DIRECTORY)
|
||||
try:
|
||||
os.fsync(directory)
|
||||
finally:
|
||||
os.close(directory)
|
||||
if any(digest(path) != expected for path in destinations):
|
||||
raise ValueError("Installed library verification failed")
|
||||
finally:
|
||||
for temporary, _ in staged:
|
||||
if temporary.exists():
|
||||
temporary.unlink()
|
||||
return {"installed": True, "sha256": expected, "libraries": records,
|
||||
"restart_performed": False,
|
||||
"activation": "Exit the game and clients, then restart Darling; an active overlay may still expose the old library."}
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--binary", type=Path, default=ROOT / "build/darling-src/build/src/external/xnu/darling/src/libsystem_kernel/libsystem_kernel.dylib")
|
||||
# A previous sudo server install can make build/backups root-owned.
|
||||
parser.add_argument("--backup-dir", type=Path, default=ROOT / "build/memory-runtime-backups")
|
||||
args = parser.parse_args()
|
||||
config = json.loads((ROOT / "build/config.json").read_text())
|
||||
prefix = Path(config["prefix"])
|
||||
destinations = [prefix / config["guest_home"].lstrip("/") / "RuntimeLibraries/libsystem_kernel.dylib"]
|
||||
# Keep the per-prefix bootstrap copy consistent; leave the system SDK alone.
|
||||
destinations.append(prefix / "usr/lib/system/libsystem_kernel.dylib")
|
||||
artifact = ROOT / "build/riot-libsystem_kernel.dylib"
|
||||
if artifact.exists():
|
||||
destinations.append(artifact)
|
||||
try:
|
||||
result = install(args.binary, destinations, args.backup_dir)
|
||||
except PermissionError as error:
|
||||
parser.exit(1, "Cannot write " + str(error.filename) + "; choose a writable --backup-dir or run the installer with sudo.\n")
|
||||
print(json.dumps(result))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -27,16 +27,39 @@ int main(int argc,char **argv){
|
||||
assert(!CGLTexImageIOSurface2D(ctx,GL_TEXTURE_2D,GL_RGBA8,337,339,GL_RGBA,GL_UNSIGNED_BYTE,s,0));
|
||||
glGenFramebuffers(1,&fbo);glBindFramebuffer(GL_FRAMEBUFFER,fbo);glFramebufferTexture2D(GL_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,GL_TEXTURE_2D,tex,0);assert(glCheckFramebufferStatus(GL_FRAMEBUFFER)==GL_FRAMEBUFFER_COMPLETE);
|
||||
puts("DIRECT_STAGE framebuffer");fflush(stdout);
|
||||
IOSurfaceRef other=NULL;unsigned *targetState=NULL;long targetMapping=-1;
|
||||
if(getenv("RCLOL_GPU_DIRECT_OTHER_WINDOW_TEST")) {
|
||||
int otherWidth=338;CFNumberRef otherNumber=CFNumberCreate(0,kCFNumberIntType,&otherWidth);
|
||||
CFMutableDictionaryRef otherOptions=CFDictionaryCreateMutableCopy(0,0,options);
|
||||
CFDictionarySetValue(otherOptions,kIOSurfaceWidth,otherNumber);
|
||||
int otherStride=338*4;CFNumberRef strideNumber=CFNumberCreate(0,kCFNumberIntType,&otherStride);
|
||||
CFDictionarySetValue(otherOptions,kIOSurfaceBytesPerRow,strideNumber);
|
||||
other=IOSurfaceCreate(otherOptions);assert(other);
|
||||
GLuint otherTexture;glGenTextures(1,&otherTexture);glBindTexture(GL_TEXTURE_2D,otherTexture);
|
||||
assert(!CGLTexImageIOSurface2D(ctx,GL_TEXTURE_2D,GL_RGBA8,338,339,GL_RGBA,GL_UNSIGNED_BYTE,other,0));
|
||||
CFRelease(otherOptions);CFRelease(otherNumber);CFRelease(strideNumber);
|
||||
char path[128];snprintf(path,sizeof(path),"/dev/shm/darling-ca-gpu-target-%u-337-339",getuid());
|
||||
int fd=(int)__lolsys_syscall((long)path,2,0,0,0,0,2);assert(fd>=0);
|
||||
targetMapping=__lolsys_syscall(0,64,1,1,fd,0,9);__lolsys_syscall(fd,0,0,0,0,0,3);
|
||||
assert(targetMapping>0 || targetMapping < -4095);targetState=(unsigned*)targetMapping;
|
||||
}
|
||||
sleep(2);
|
||||
unsigned success=0;struct {long sec,nsec;} deadline;assert(__lolsys_syscall(1,(long)&deadline,0,0,0,0,228)==0);
|
||||
for(unsigned n=0;n<360;n++){
|
||||
glClearColor((n%256)/255.f,100/255.f,200/255.f,1);glClear(GL_COLOR_BUFFER_BIT);
|
||||
if(n==10){unsigned char rgba[4];glReadPixels(0,0,1,1,GL_RGBA,GL_UNSIGNED_BYTE,rgba);printf("DIRECT_SOURCE rgba=%u,%u,%u,%u error=%u\n",rgba[0],rgba[1],rgba[2],rgba[3],glGetError());fflush(stdout);}
|
||||
success+=present(s)!=0;
|
||||
if(other && n>2) {
|
||||
assert(!present(other));
|
||||
if(!__atomic_load_n(&targetState[4],__ATOMIC_ACQUIRE)) {
|
||||
puts("DIRECT_OTHER_WINDOW active_presenter_preserved=0");fflush(stdout);return 3;
|
||||
}
|
||||
}
|
||||
GLint read=0,draw=0;glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING,&read);glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING,&draw);assert(read==fbo&&draw==fbo&&CGLGetCurrentContext()==ctx);
|
||||
deadline.nsec+=1000000000/rate;if(deadline.nsec>=1000000000){deadline.nsec-=1000000000;++deadline.sec;}
|
||||
if(!getenv("RCLOL_GPU_DIRECT_PACE")){long wait;do{wait=__lolsys_syscall(1,1,(long)&deadline,0,0,0,230);}while(wait==-4);assert(wait==0);}
|
||||
}
|
||||
printf("DIRECT_PRESENT successful=%u requested=360 gl_state_restored=1 error=%u\n",success,glGetError());fflush(stdout);
|
||||
if(other){puts("DIRECT_OTHER_WINDOW active_presenter_preserved=1 fallback_frames=357");CFRelease(other);__lolsys_syscall(targetMapping,64,0,0,0,0,11);}
|
||||
CGLSetCurrentContext(0);CGLReleaseContext(ctx);CGLReleasePixelFormat(pf);CFRelease(s);CFRelease(options);for(int i=0;i<4;i++)CFRelease(values[i]);return success==360?0:1;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/* Exercise the first floating-point calls through generated ELF GL resolvers. */
|
||||
#define GL_GLEXT_PROTOTYPES 1
|
||||
#include <OpenGL/OpenGL.h>
|
||||
#include <OpenGL/glext.h>
|
||||
#include <stdio.h>
|
||||
#include <dlfcn.h>
|
||||
int main(void) {
|
||||
CGLPixelFormatAttribute attrs[]={kCGLPFAOpenGLProfile,0x3200,0};
|
||||
CGLPixelFormatObj pf=0;CGLContextObj ctx=0;GLint screens=0;
|
||||
if(CGLChoosePixelFormat(attrs,&pf,&screens) || CGLCreateContext(pf,0,&ctx) || CGLSetCurrentContext(ctx))return 2;
|
||||
while(glGetError()!=GL_NO_ERROR){}
|
||||
GLuint sampler=0;glGenSamplers(1,&sampler);
|
||||
void (*samplerParameterf)(GLuint,GLenum,GLfloat)=(void*)dlsym(RTLD_DEFAULT,"glSamplerParameterf");
|
||||
void (*clearColor)(GLfloat,GLfloat,GLfloat,GLfloat)=(void*)dlsym(RTLD_DEFAULT,"glClearColor");
|
||||
if(!samplerParameterf || !clearColor)return 2;
|
||||
samplerParameterf(sampler,GL_TEXTURE_MIN_FILTER,(GLfloat)GL_LINEAR);
|
||||
GLint filter=0;glGetSamplerParameteriv(sampler,GL_TEXTURE_MIN_FILTER,&filter);
|
||||
GLenum samplerError=glGetError();
|
||||
clearColor(.125f,.25f,.5f,.75f);
|
||||
GLfloat rgba[4]={0};glGetFloatv(GL_COLOR_CLEAR_VALUE,rgba);
|
||||
GLenum colorError=glGetError();
|
||||
int colorOK=rgba[0]==.125f && rgba[1]==.25f && rgba[2]==.5f && rgba[3]==.75f;
|
||||
printf("GL_RESOLVER first_sampler_linear=%d first_color_arguments=%d errors=%u,%u\n",filter==GL_LINEAR,colorOK,samplerError,colorError);
|
||||
glDeleteSamplers(1,&sampler);CGLSetCurrentContext(0);CGLReleaseContext(ctx);CGLReleasePixelFormat(pf);
|
||||
return filter==GL_LINEAR && colorOK && !samplerError && !colorError?0:1;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
#define _GNU_SOURCE
|
||||
#include <sys/mman.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
// Links the actual Darling sys_madvise source against host syscall hooks.
|
||||
// All memory belongs to this standalone check; no client/server is contacted.
|
||||
long sys_madvise(void* address, unsigned long size, int advice);
|
||||
static int lastAdvice = -1, conversions = 0;
|
||||
long __lolsys_syscall(long a, long b, long c, long d, long e, long f, int nr) {
|
||||
if (nr == SYS_madvise) lastAdvice = (int)c;
|
||||
long result = syscall(nr, a, b, c, d, e, f);
|
||||
return result == -1 ? -errno : result;
|
||||
}
|
||||
int errno_linux_to_bsd(int error) {
|
||||
// EINVAL and ENOMEM have the same numeric values on these two systems.
|
||||
++conversions;
|
||||
return error;
|
||||
}
|
||||
static void require(int condition, const char* message) {
|
||||
if (!condition) { fprintf(stderr, "HOST_MADVISE pass=0 error=%s\n", message); exit(1); }
|
||||
}
|
||||
static unsigned long lazyFreeKB(void) {
|
||||
FILE* file = fopen("/proc/self/smaps_rollup", "r");
|
||||
require(file != NULL, "smaps_rollup");
|
||||
char line[256]; unsigned long value = 0;
|
||||
while (fgets(line, sizeof(line), file)) if (sscanf(line, "LazyFree: %lu kB", &value) == 1) break;
|
||||
fclose(file); return value;
|
||||
}
|
||||
int main(int argc, char** argv) {
|
||||
const int oldImplementation = argc > 1;
|
||||
const size_t size = 4 * 1024 * 1024;
|
||||
unsigned char* memory = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
require(memory != MAP_FAILED, "anonymous mapping");
|
||||
memset(memory, 0x5a, size);
|
||||
unsigned long before = lazyFreeKB();
|
||||
require(sys_madvise(memory, size, 7) == 0, "Darwin free reusable");
|
||||
unsigned long marked = lazyFreeKB();
|
||||
if (!oldImplementation) {
|
||||
require(lastAdvice == 8, "Linux free translation");
|
||||
require(marked >= before + size / 1024, "pages not marked reclaimable");
|
||||
} else require(marked == before && lastAdvice == -1, "baseline behavior changed");
|
||||
require(sys_madvise(memory, size, 8) == 0, "Darwin reuse");
|
||||
unsigned long reused = lazyFreeKB();
|
||||
if (!oldImplementation) require(lastAdvice == 23 && reused == before, "reuse did not cancel lazy reclaim");
|
||||
for (size_t i = 0; i < size; ++i) require(memory[i] == 0x5a, "reuse changed surviving contents");
|
||||
memset(memory, 0x37, size);
|
||||
require(sys_madvise(memory, size, 5) == 0, "Darwin free");
|
||||
if (!oldImplementation) require(lastAdvice == 8 && lazyFreeKB() >= before + size / 1024, "plain free not reclaimable");
|
||||
require(sys_madvise(memory, size, 8) == 0, "second reuse");
|
||||
for (size_t i = 0; i < size; ++i) require(memory[i] == 0x37, "second reuse corrupted contents");
|
||||
require(sys_madvise(memory, size, 0) == 0 && lastAdvice == 0, "normal hint passthrough");
|
||||
if (!oldImplementation) {
|
||||
require(sys_madvise(memory + 1, size - 1, 7) == -EINVAL, "unaligned free error");
|
||||
require(sys_madvise(memory + 1, size - 1, 8) == -EINVAL, "unaligned reuse error");
|
||||
require(conversions == 2, "error translation hook");
|
||||
}
|
||||
require(munmap(memory, size) == 0, "unmap");
|
||||
printf("HOST_MADVISE pass=1 reclaimable_KiB=%lu reuse_reclaimable_KiB=%lu contents=preserved errors=ok\n", marked - before, reused - before);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
#include <darlingserver/message.hpp>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <dirent.h>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <new>
|
||||
#include <stdexcept>
|
||||
|
||||
// Standalone host sockets only. Never connects to Darling, Riot or the game.
|
||||
static bool countAllocations = false, failNextNew = false;
|
||||
static unsigned allocations = 0;
|
||||
extern "C" void* __real_malloc(size_t);
|
||||
extern "C" void* __wrap_malloc(size_t n) {
|
||||
if (countAllocations) ++allocations;
|
||||
return __real_malloc(n);
|
||||
}
|
||||
void* operator new(size_t n) {
|
||||
if (failNextNew) { failNextNew = false; throw std::bad_alloc(); }
|
||||
if (countAllocations) ++allocations;
|
||||
if (void* p = __real_malloc(n ? n : 1)) return p;
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
void operator delete(void* p) noexcept { std::free(p); }
|
||||
void operator delete(void* p, size_t) noexcept { std::free(p); }
|
||||
|
||||
using DarlingServer::Message;
|
||||
using DarlingServer::MessageQueue;
|
||||
static void require(bool pass, const char* what) {
|
||||
if (!pass) throw std::runtime_error(what);
|
||||
}
|
||||
struct Sockets {
|
||||
int fd[2];
|
||||
Sockets() {
|
||||
require(socketpair(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0, fd) == 0, "socketpair");
|
||||
int enabled = 1;
|
||||
require(setsockopt(fd[1], SOL_SOCKET, SO_PASSCRED, &enabled, sizeof(enabled)) == 0, "credentials");
|
||||
sa_family_t family = AF_UNIX;
|
||||
require(bind(fd[0], reinterpret_cast<sockaddr*>(&family), sizeof(family)) == 0, "sender address");
|
||||
}
|
||||
~Sockets() { close(fd[0]); close(fd[1]); }
|
||||
};
|
||||
static unsigned openFDs() {
|
||||
DIR* directory = opendir("/proc/self/fd");
|
||||
require(directory, "fd directory");
|
||||
unsigned n = 0;
|
||||
while (auto entry = readdir(directory)) if (entry->d_name[0] != '.') ++n;
|
||||
closedir(directory);
|
||||
return n;
|
||||
}
|
||||
static void sendPacket(int fd, unsigned tag, size_t size, unsigned descriptors = 0) {
|
||||
Message packet(size, descriptors);
|
||||
for (size_t i = 0; i < size; ++i) packet.data()[i] = (tag + i) & 255;
|
||||
for (unsigned i = 0; i < descriptors; ++i) {
|
||||
int pipeFDs[2]; require(pipe(pipeFDs) == 0, "pipe");
|
||||
unsigned char byte = tag + i;
|
||||
require(write(pipeFDs[1], &byte, 1) == 1, "pipe write"); close(pipeFDs[1]);
|
||||
packet.pushDescriptor(pipeFDs[0]);
|
||||
}
|
||||
packet.rawHeader().msg_name = nullptr;
|
||||
packet.rawHeader().msg_namelen = 0;
|
||||
require(sendmsg(fd, &packet.rawHeader(), 0) == static_cast<ssize_t>(size), "send packet");
|
||||
}
|
||||
static void checkPacket(MessageQueue& queue, unsigned tag, size_t size, unsigned descriptors, int sender, unsigned flags = 0) {
|
||||
auto packet = queue.pop(); require(packet.has_value(), "missing packet");
|
||||
require(packet->data().size() == size, "packet size");
|
||||
for (size_t i = 0; i < size; ++i) require(packet->data()[i] == ((tag + i) & 255), "packet payload");
|
||||
ucred credentials{};
|
||||
require(packet->copyCredentialsOut(credentials) && credentials.pid == getpid()
|
||||
&& credentials.uid == getuid() && credentials.gid == getgid(), "packet credentials");
|
||||
require(packet->descriptors().size() == descriptors, "descriptor count");
|
||||
require((packet->rawHeader().msg_flags & flags) == flags, "receive flags");
|
||||
sockaddr_un address{}; socklen_t length = sizeof(address);
|
||||
require(getsockname(sender, reinterpret_cast<sockaddr*>(&address), &length) == 0, "sender name");
|
||||
auto receivedAddress = packet->address();
|
||||
require(receivedAddress.rawSize() == length && std::memcmp(&receivedAddress.raw(), &address, length) == 0, "sender preserved");
|
||||
for (unsigned i = 0; i < descriptors; ++i) {
|
||||
int fd = packet->extractDescriptorAtIndex(0);
|
||||
require(fd >= 0 && (fcntl(fd, F_GETFD) & FD_CLOEXEC), "descriptor CLOEXEC");
|
||||
unsigned char byte = 0;
|
||||
require(read(fd, &byte, 1) == 1 && byte == static_cast<unsigned char>(tag + i), "descriptor payload");
|
||||
close(fd);
|
||||
}
|
||||
}
|
||||
int main(int argc, char**) {
|
||||
try {
|
||||
const bool measureBaseline = argc > 1;
|
||||
Sockets sockets;
|
||||
unsigned baselineFDs = openFDs(), emptyAllocations = 0, oneAllocations = 0;
|
||||
{
|
||||
MessageQueue queue;
|
||||
unsigned callbacks = 0;
|
||||
queue.setMessageArrivalNotificationCallback([&] {
|
||||
++callbacks;
|
||||
require(!queue.empty(), "callback reentrant lock");
|
||||
});
|
||||
require(!queue.receiveMany(sockets.fd[1]), "warm empty drain");
|
||||
allocations = 0; countAllocations = true;
|
||||
bool drained = !queue.receiveMany(sockets.fd[1]);
|
||||
countAllocations = false; emptyAllocations = allocations;
|
||||
require(drained && callbacks == 0, "empty drain notification");
|
||||
if (!measureBaseline) require(emptyAllocations == 0, "warm empty receive allocated");
|
||||
|
||||
sendPacket(sockets.fd[0], 100, 19, 4);
|
||||
allocations = 0; countAllocations = true;
|
||||
drained = !queue.receiveMany(sockets.fd[1]);
|
||||
countAllocations = false; oneAllocations = allocations;
|
||||
require(drained && callbacks == 1, "single receive notification");
|
||||
checkPacket(queue, 100, 19, 4, sockets.fd[0]);
|
||||
if (!measureBaseline) require(oneAllocations <= 3, "single receive wasted allocations");
|
||||
|
||||
// More than one batch, alternating descriptors and changing payload sizes.
|
||||
for (unsigned i = 0; i < 19; ++i) sendPacket(sockets.fd[0], 100 + i, i * 11, i % 2);
|
||||
require(!queue.receiveMany(sockets.fd[1]) && callbacks == 2, "multi batch notification");
|
||||
for (unsigned i = 0; i < 19; ++i) checkPacket(queue, 100 + i, i * 11, i % 2, sockets.fd[0]);
|
||||
require(queue.empty(), "extra packet");
|
||||
require(!queue.receiveMany(sockets.fd[1]) && callbacks == 2, "post drain notification");
|
||||
sendPacket(sockets.fd[0], 150, 300, 6);
|
||||
require(!queue.receiveMany(sockets.fd[1]), "truncated receive");
|
||||
checkPacket(queue, 150, 256, 4, sockets.fd[0], MSG_TRUNC | MSG_CTRUNC);
|
||||
}
|
||||
require(openFDs() == baselineFDs, "normal receive descriptor leak");
|
||||
{
|
||||
// A deque allocation failure after a kernel receive must close every
|
||||
// received descriptor, including the ones not yet visited by the loop.
|
||||
MessageQueue queue;
|
||||
require(!queue.receiveMany(sockets.fd[1]), "exception warmup");
|
||||
for (unsigned i = 0; i < 12; ++i) sendPacket(sockets.fd[0], 100 + i, 8, i % 4 + 1);
|
||||
failNextNew = true;
|
||||
bool threw = false;
|
||||
try { queue.receiveMany(sockets.fd[1]); } catch (const std::bad_alloc&) { threw = true; }
|
||||
failNextNew = false;
|
||||
require(threw, "failure injection did not throw");
|
||||
while (queue.pop()) {}
|
||||
if (!measureBaseline) {
|
||||
sendPacket(sockets.fd[0], 140, 256);
|
||||
require(!queue.receiveMany(sockets.fd[1]), "recovery drain");
|
||||
checkPacket(queue, 140, 256, 0, sockets.fd[0]);
|
||||
}
|
||||
}
|
||||
require(openFDs() == baselineFDs, "exception receive descriptor leak");
|
||||
std::printf("HOST_RECEIVE pass=1 warm_empty_allocations=%u single_packet_allocations=%u credentials=ok descriptors=ok batches=ok callback=ok exception_cleanup=ok\n", emptyAllocations, oneAllocations);
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
countAllocations = false;
|
||||
std::fprintf(stderr, "HOST_RECEIVE pass=0 error=%s\n", error.what());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check launch behavior using stub commands; never start or contact a client."""
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
|
||||
|
||||
def check():
|
||||
with tempfile.TemporaryDirectory(prefix="riot-launcher-offline-") as directory:
|
||||
folder = Path(directory)
|
||||
launcher = folder / "launch.sh"
|
||||
launcher.write_text((ROOT / "tools/run-riot-ux-with-logdir-shim.sh").read_text().replace("@GUEST_HOME@", "/Users/offline-check"))
|
||||
binary_folder = folder / "bin"
|
||||
binary_folder.mkdir()
|
||||
log = folder / "calls.jsonl"
|
||||
stub = f'''#!{sys.executable}
|
||||
import json, os, sys
|
||||
record = {{"command": os.path.basename(sys.argv[0]), "args": sys.argv[1:]}}
|
||||
if record["command"] == "darling":
|
||||
record["flags"] = {{key: os.environ.get(key) for key in
|
||||
("RCLOL_APP_ARG3", "RCLOL_APP_ARG3_SLOT", "RCLOL_UX_ARG2", "RCLOL_UX_ARG2_SLOT", "RCLOL_UX_ARG1")}}
|
||||
with open({str(log)!r}, "a") as output:
|
||||
output.write(json.dumps(record) + "\\n")
|
||||
'''
|
||||
for name in ("python3", "darling"):
|
||||
path = binary_folder / name
|
||||
path.write_text(stub)
|
||||
path.chmod(0o755)
|
||||
env = dict(os.environ)
|
||||
env.pop("RIOT_LAUNCHER_HOST", None)
|
||||
env.pop("RCLOL_LAUNCH_PRODUCT", None)
|
||||
env["PATH"] = str(binary_folder) + os.pathsep + env.get("PATH", "")
|
||||
env["RCLOL_NATIVE_VPX"] = "0"
|
||||
env["RCLOL_LEAGUE_UX_TUNING"] = "1"
|
||||
# Inherited legacy settings must not re-enable inspection or unlimited frames.
|
||||
env.update({
|
||||
"RCLOL_APP_ARG3": "--inspect=127.0.0.1:9229",
|
||||
"RCLOL_APP_ARG3_SLOT": "--session-id=",
|
||||
"RCLOL_UX_ARG2": "--disable-frame-rate-limit",
|
||||
"RCLOL_UX_ARG2_SLOT": "--crash-environment=",
|
||||
"RCLOL_UNTHROTTLED_BEGIN_FRAMES": "1",
|
||||
"RCLOL_FOREGROUND_SCHEDULING": "1",
|
||||
"RCLOL_LEAGUE_UPDATE_HANDOFF": "1",
|
||||
"RCLOL_OPEN_URL_BRIDGE": "1",
|
||||
"RCLOL_DISABLE_GPU_VSYNC": "1",
|
||||
})
|
||||
subprocess.run(["sh", str(launcher)], env=env, check=True, capture_output=True, text=True)
|
||||
records = [json.loads(line) for line in log.read_text().splitlines()]
|
||||
maintenance = [r for r in records if r["command"] == "python3"]
|
||||
assert [Path(r["args"][0]).name for r in maintenance] == [
|
||||
"ensure-darling-runtime.py", "ensure-darling-service-accounts.py", "ensure-league-runtime-layout.py"
|
||||
], "unexpected automatic helper"
|
||||
launches = [r for r in records if r["command"] == "darling"]
|
||||
assert len(launches) == 1, "unexpected launch count"
|
||||
launch = launches[0]
|
||||
assert all(launch["flags"][key] is None for key in (
|
||||
"RCLOL_APP_ARG3", "RCLOL_APP_ARG3_SLOT", "RCLOL_UX_ARG2", "RCLOL_UX_ARG2_SLOT"
|
||||
)), "inherited inspector/unlimited-frame argument survived"
|
||||
assert launch["flags"]["RCLOL_UX_ARG1"], "feature compatibility setting lost"
|
||||
assert "--launch-product=league_of_legends" in launch["args"], "product launch lost"
|
||||
assert "--app-root=/Applications/Riot Client.app" in launch["args"], "application root lost"
|
||||
assert "--log-dir=/Users/offline-check/Library/Logs/Riot Games/Riot Client" in launch["args"], "absolute log directory lost"
|
||||
print("LAUNCHER_OFFLINE pass=1 automatic_inspector_helpers=0 inspector_argument=absent unlimited_frames=absent compatibility=preserved")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
check()
|
||||
@@ -0,0 +1,17 @@
|
||||
/* Nullable CFErrorRef output arguments must be safe and receive errors. */
|
||||
#include <CoreServices/CoreServices.h>
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include <stdio.h>
|
||||
int main(void) {
|
||||
CFArrayRef apps=LSCopyApplicationURLsForBundleIdentifier(NULL,NULL);
|
||||
CFErrorRef error=NULL;
|
||||
CFArrayRef withError=LSCopyApplicationURLsForBundleIdentifier(NULL,&error);
|
||||
int populated=error!=NULL;
|
||||
if(error)CFRelease(error);
|
||||
CFURLRef url=CFURLCreateWithString(NULL,CFSTR("darling-no-handler://test"),NULL);
|
||||
CFURLRef handler=LSCopyDefaultApplicationURLForURL(url,kLSRolesAll,NULL);
|
||||
CFURLRef content=LSCopyDefaultApplicationURLForContentType(CFSTR("org.darling.test.no-handler"),kLSRolesAll,NULL);
|
||||
printf("LAUNCHSERVICES_NULL_ERROR nullable_outputs=1 error_populated=%d missing_handlers=%d\n",populated,!handler&&!content);
|
||||
CFRelease(url);if(apps)CFRelease(apps);if(withError)CFRelease(withError);if(handler)CFRelease(handler);if(content)CFRelease(content);
|
||||
return !apps&&!withError&&populated&&!handler&&!content?0:1;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#include <sys/mman.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
// A standalone Darling process. Reads only its own Linux memory counters.
|
||||
extern int __linux_syscall(int number, ...);
|
||||
static unsigned long lazy_free_kib(void) {
|
||||
char text[4096];
|
||||
int fd = __linux_syscall(257, -100, "/proc/self/smaps_rollup", 0, 0);
|
||||
if (fd < 0) return (unsigned long)-1;
|
||||
int size = __linux_syscall(0, fd, text, sizeof(text) - 1);
|
||||
__linux_syscall(3, fd);
|
||||
if (size <= 0) return (unsigned long)-1;
|
||||
text[size] = 0;
|
||||
char* field = strstr(text, "LazyFree:");
|
||||
unsigned long value = 0;
|
||||
return field && sscanf(field, "LazyFree: %lu kB", &value) == 1 ? value : (unsigned long)-1;
|
||||
}
|
||||
int main(void) {
|
||||
const size_t size = 4 * 1024 * 1024;
|
||||
unsigned char* memory = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
|
||||
if (memory == MAP_FAILED) return 1;
|
||||
memset(memory, 0x5a, size);
|
||||
unsigned long before = lazy_free_kib();
|
||||
if (before == (unsigned long)-1 || madvise(memory, size, 7) != 0) return 2;
|
||||
unsigned long freed = lazy_free_kib();
|
||||
if (madvise(memory, size, 8) != 0) return 3;
|
||||
unsigned long reused = lazy_free_kib();
|
||||
int contents = 1;
|
||||
for (size_t i = 0; i < size; ++i) if (memory[i] != 0x5a) { contents = 0; break; }
|
||||
munmap(memory, size);
|
||||
int pass = freed >= before + size / 1024 && reused <= before && contents;
|
||||
printf("DARLING_MADVISE pass=%d reclaimable_KiB=%lu reuse_reclaimable_KiB=%lu contents=%s\n", pass, freed - before, reused - before, contents ? "preserved" : "changed");
|
||||
return !pass;
|
||||
}
|
||||
@@ -9,6 +9,8 @@ import tempfile
|
||||
|
||||
root = Path(__file__).resolve().parent.parent
|
||||
checks = {
|
||||
"riot-launchservices-null-error-check": "LAUNCHSERVICES_NULL_ERROR",
|
||||
"m327-cgl-resolver-check": "GL_RESOLVER",
|
||||
"riot-mach-exception-state-check": "MACH_EXCEPTION_STATE",
|
||||
"riot-running-application-check": "RUNNING_APP",
|
||||
"riot-task-info-check": "TASK_INFO",
|
||||
@@ -34,7 +36,7 @@ parser.add_argument("--timeout", type=int, default=30)
|
||||
args = parser.parse_args()
|
||||
if not 1 <= args.timeout <= 120:
|
||||
parser.error("timeout must be between 1 and 120 seconds")
|
||||
binary = root / "build" / args.check
|
||||
binary = (root / "build" / args.check).resolve()
|
||||
if not binary.is_file():
|
||||
parser.error("probe has not been built")
|
||||
|
||||
|
||||
@@ -44,22 +44,13 @@ export RCLOL_APP_ARGS RCLOL_APP_ARGS_SLOT
|
||||
RCLOL_APP_ARG2=${RCLOL_APP_ARG2-'--disable-features=RemoteCoreAnimationAPI,AudioServiceOutOfProcess,UseVideoFrameSinkBundle'}
|
||||
RCLOL_APP_ARG2_SLOT=${RCLOL_APP_ARG2_SLOT-'--crashpad-environment='}
|
||||
export RCLOL_APP_ARG2 RCLOL_APP_ARG2_SLOT
|
||||
# The startup/update hooks below connect to Electron's local main inspector.
|
||||
# Reserve the telemetry session slot for that switch, as with the two existing
|
||||
# compatibility switches. Bind it to loopback explicitly.
|
||||
RCLOL_APP_ARG3=${RCLOL_APP_ARG3-'--inspect=127.0.0.1:9229'}
|
||||
RCLOL_APP_ARG3_SLOT=${RCLOL_APP_ARG3_SLOT-'--session-id='}
|
||||
export RCLOL_APP_ARG3 RCLOL_APP_ARG3_SLOT
|
||||
# League's UI (LeagueClientUx, CEF) gets the Riot Client UI's tuning through the shim, applied to its
|
||||
# GPU helper: the Darling-incompatible Chromium features off and frame pacing unthrottled. Each
|
||||
# replacement takes over one existing, expendable switch of that helper's command line.
|
||||
# RCLOL_LEAGUE_UX_TUNING=0 leaves the League UI untouched.
|
||||
# Client inspectors and automatic UI hooks are excluded from normal launches.
|
||||
unset RCLOL_APP_ARG3 RCLOL_APP_ARG3_SLOT RCLOL_UX_ARG2 RCLOL_UX_ARG2_SLOT
|
||||
# Keep the existing feature compatibility switch without forcing unlimited UI frames.
|
||||
if [ "${RCLOL_LEAGUE_UX_TUNING:-1}" != 0 ]; then
|
||||
RCLOL_UX_ARG1='--disable-features=HardwareMediaKeyHandling,RemoteCoreAnimationAPI,UseVideoFrameSinkBundle'
|
||||
RCLOL_UX_ARG1_SLOT='--disable-features='
|
||||
RCLOL_UX_ARG2='--disable-frame-rate-limit'
|
||||
RCLOL_UX_ARG2_SLOT='--crash-environment='
|
||||
export RCLOL_UX_ARG1 RCLOL_UX_ARG1_SLOT RCLOL_UX_ARG2 RCLOL_UX_ARG2_SLOT
|
||||
export RCLOL_UX_ARG1 RCLOL_UX_ARG1_SLOT
|
||||
fi
|
||||
# The paired runtime presents completed GPU textures without CPU readback.
|
||||
# Explicit 0 or an empty value selects the CPU fallback for diagnostics.
|
||||
@@ -67,39 +58,8 @@ for option in RCLOL_GPU_DIRECT_PRESENT RCLOL_GPU_DIRECT_ASYNC RCLOL_GPU_DIRECT_V
|
||||
eval 'value=${'"$option"'-1}'
|
||||
case "$value" in 0|'') unset "$option";; *) export "$option=$value";; esac
|
||||
done
|
||||
# Bound software decode workers before the renderer is created. Native video
|
||||
# playback remains enabled; avoid excess emulated worker/RPC synchronization.
|
||||
# 0 restores Chromium's default thread policy for controlled comparisons.
|
||||
decoder_threads=${RCLOL_DECODER_THREADS:-1}
|
||||
case "$decoder_threads" in
|
||||
0|1|2|4) ;;
|
||||
*) echo "RCLOL_DECODER_THREADS must be 0, 1, 2, or 4" >&2; exit 1;;
|
||||
esac
|
||||
raster_threads=${RCLOL_RASTER_THREADS:-0}
|
||||
case "$raster_threads" in
|
||||
0|1|2|4) ;;
|
||||
*) echo "RCLOL_RASTER_THREADS must be 0, 1, 2, or 4" >&2; exit 1;;
|
||||
esac
|
||||
if [ "${RCLOL_DISABLE_GPU_VSYNC:-0}" = 1 ]; then
|
||||
if [ "${RCLOL_UNTHROTTLED_BEGIN_FRAMES:-1}" = 1 ]; then
|
||||
python3 "$runner_dir/riot-decoder-scheduling.py" --threads "$decoder_threads" --raster-threads "$raster_threads" --unthrottled --disable-gpu-vsync > /tmp/riot-decoder-scheduling.log 2>&1 &
|
||||
else
|
||||
python3 "$runner_dir/riot-decoder-scheduling.py" --threads "$decoder_threads" --raster-threads "$raster_threads" --disable-gpu-vsync > /tmp/riot-decoder-scheduling.log 2>&1 &
|
||||
fi
|
||||
elif [ "${RCLOL_UNTHROTTLED_BEGIN_FRAMES:-1}" = 1 ]; then
|
||||
python3 "$runner_dir/riot-decoder-scheduling.py" --threads "$decoder_threads" --raster-threads "$raster_threads" --unthrottled > /tmp/riot-decoder-scheduling.log 2>&1 &
|
||||
elif [ "$decoder_threads" != 0 ] || [ "$raster_threads" != 0 ]; then
|
||||
python3 "$runner_dir/riot-decoder-scheduling.py" --threads "$decoder_threads" --raster-threads "$raster_threads" > /tmp/riot-decoder-scheduling.log 2>&1 &
|
||||
fi
|
||||
# This existing local main-process inspector exposes no account data here.
|
||||
# Install a window hook once, then disconnect instead of polling the app.
|
||||
if [ "${RCLOL_FOREGROUND_SCHEDULING:-1}" != 0 ]; then
|
||||
python3 "$runner_dir/riot-foreground-scheduling.py" > /tmp/riot-foreground-scheduling.log 2>&1 &
|
||||
fi
|
||||
# Recover accepted League Update actions that leave the legacy patch request empty.
|
||||
if [ "${RCLOL_LEAGUE_UPDATE_HANDOFF:-1}" != 0 ]; then
|
||||
python3 "$runner_dir/riot-league-update.py" > /tmp/riot-league-update.log 2>&1 &
|
||||
fi
|
||||
# Use Chromium's normal decoder, frame and background scheduling policies.
|
||||
# Launching the client does not start inspector helpers or inject UI hooks.
|
||||
SHIM_GUEST="@GUEST_HOME@/zzlogdir.dylib"
|
||||
RIOT_INSERT_LIBRARIES="$SHIM_GUEST:@GUEST_HOME@/libRiotEmptyAutofill.dylib"
|
||||
# Use the verified bridge when the paired host/guest artifacts are installed.
|
||||
@@ -116,6 +76,21 @@ A=${RIOT_LAUNCHER_HOST:-"$HOME/.darling/Applications/Riot Client.app/Contents/Ma
|
||||
# Use Xlib's local input method; allow explicit testing of another modifier.
|
||||
# Linux C.UTF-8 is not a supported codecvt locale in the macOS client.
|
||||
# Keep this scoped to the guest; product region/locale arguments remain explicit.
|
||||
# The first-time installer (RIOT_LAUNCHER_HOST) exits immediately when given --app-root for a
|
||||
# Riot Client that is not installed yet; only pass it for the installed client.
|
||||
# Its native intro screen renders image layers with a blue cover and does not take clicks
|
||||
# under Darling, so start the install directly (the Riot Client UI that follows works).
|
||||
if [ -n "${RIOT_LAUNCHER_HOST:-}" ]; then
|
||||
set -- --skip-to-install --launch-product=league_of_legends --launch-patchline=live
|
||||
else
|
||||
set -- --app-root="/Applications/Riot Client.app"
|
||||
# The League login theme (video background, Install/Play button) is only shown when the client
|
||||
# is started for a product. RCLOL_LAUNCH_PRODUCT= (empty) starts the bare client instead.
|
||||
if [ -n "${RCLOL_LAUNCH_PRODUCT-league_of_legends}" ]; then
|
||||
set -- "$@" --launch-product="${RCLOL_LAUNCH_PRODUCT-league_of_legends}" \
|
||||
--launch-patchline="${RCLOL_LAUNCH_PATCHLINE-live}"
|
||||
fi
|
||||
fi
|
||||
exec env -u ELECTRON_RUN_AS_NODE EGL_PLATFORM=x11 \
|
||||
LANG="${RCLOL_GUEST_LOCALE:-en_US.UTF-8}" LC_ALL="${RCLOL_GUEST_LOCALE:-en_US.UTF-8}" \
|
||||
mesa_glthread="${RCLOL_MESA_GLTHREAD-${mesa_glthread-false}}" \
|
||||
@@ -125,5 +100,5 @@ exec env -u ELECTRON_RUN_AS_NODE EGL_PLATFORM=x11 \
|
||||
DARLING_XSHM_LIBRARY="${DARLING_XSHM_LIBRARY:-@GUEST_HOME@/libDarlingXShm.dylib}" \
|
||||
DYLD_INSERT_LIBRARIES="$RIOT_INSERT_LIBRARIES" \
|
||||
darling "$A" --region="${RCLOL_REGION:-EUW}" --locale="${RCLOL_LOCALE:-en_GB}" \
|
||||
--app-root="/Applications/Riot Client.app" \
|
||||
"$@" \
|
||||
--log-dir="@GUEST_HOME@/Library/Logs/Riot Games/Riot Client"
|
||||
+13
-6
@@ -8,8 +8,9 @@
|
||||
"revision": "60ba801decee7a00782f74f6be4c8ffb013f79ff",
|
||||
"repository": "https://github.com/darlinghq/darling.git",
|
||||
"patch": "patches/darling.patch",
|
||||
"modified_files": 84,
|
||||
"modified_files": 86,
|
||||
"new_files": [
|
||||
"src/CoreAudio/CoreAudio/AudioConverterShim.c",
|
||||
"src/CoreAudio/AudioTrace.h",
|
||||
"src/CoreAudio/CoreAudioComponent/MatrixMixerAU.cpp",
|
||||
"src/frameworks/Carbon/HIToolbox/src/SecureInput.c",
|
||||
@@ -51,7 +52,7 @@
|
||||
"revision": "c8d38d16a9f613d300157bebbab2b9501bc0c274",
|
||||
"repository": "https://github.com/darlinghq/darling-cocotron.git",
|
||||
"patch": "patches/cocotron.patch",
|
||||
"modified_files": 102,
|
||||
"modified_files": 103,
|
||||
"new_files": [
|
||||
"QuartzCore/include/QuartzCore/CATransformLayer.h"
|
||||
]
|
||||
@@ -101,8 +102,10 @@
|
||||
"revision": "89751e64bc6c2082f7725061824ee0e33395b0de",
|
||||
"repository": "https://github.com/darlinghq/darlingserver.git",
|
||||
"patch": "patches/darlingserver.patch",
|
||||
"modified_files": 35,
|
||||
"new_files": []
|
||||
"modified_files": 34,
|
||||
"new_files": [
|
||||
"duct-tape/src/fileport.c"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "src/external/foundation",
|
||||
@@ -117,8 +120,9 @@
|
||||
"revision": "5549ac4cd2db923c256f016a1381b3bfb716730a",
|
||||
"repository": "https://github.com/darlinghq/darling-iokitd.git",
|
||||
"patch": "patches/iokitd.patch",
|
||||
"modified_files": 8,
|
||||
"modified_files": 7,
|
||||
"new_files": [
|
||||
"src/PowerSources.mm",
|
||||
"src/IOPlatformExpertDevice.h",
|
||||
"src/IOPlatformExpertDevice.mm"
|
||||
]
|
||||
@@ -252,8 +256,11 @@
|
||||
"revision": "fa29287aa2f0115271e091f1031f53c9e024005d",
|
||||
"repository": "https://github.com/darlinghq/darling-xnu.git",
|
||||
"patch": "patches/xnu.patch",
|
||||
"modified_files": 65,
|
||||
"modified_files": 63,
|
||||
"new_files": [
|
||||
"darling/src/libsystem_kernel/emulation/include/conversion/xattr/name.h",
|
||||
"darling/src/libsystem_kernel/emulation/include/xnu_syscall/bsd/impl/misc/mac_syscall.h",
|
||||
"darling/src/libsystem_kernel/emulation/src/xnu_syscall/bsd/impl/misc/mac_syscall.c",
|
||||
"darling/src/libsystem_kernel/emulation/include/common/private_mutex.h",
|
||||
"darling/src/libsystem_kernel/emulation/include/common/slowwait.h",
|
||||
"darling/src/libsystem_kernel/emulation/include/xnu_syscall/bsd/impl/guarded/change_fdguard_np.h",
|
||||
|
||||
Reference in new issue
Block a user