Compare commits

..
172 Commits
Author SHA1 Message Date
vii d982cd9506 Merge pull request #2 from CoderDayton/release/0.3.1
Release 0.3.1: packaging, input and shutdown fixes
2026-09-20 12:53:44 -05:00
vii 65adb3bddc fix(packaging): fail the release when a package glob matches nothing
An unmatched glob reached sha256sum and gh as literal text. The publish job
now collects the assets into an array and stops if a package is missing.
2026-09-20 12:33:26 -05:00
vii 47e23df2e0 docs(changelog): release 0.3.1 2026-09-20 12:31:39 -05:00
vii c0ff1842ee docs(changelog): record the packaging, input and shutdown fixes 2026-09-20 12:30:00 -05:00
vii e073947b91 fix(window): keep the pointer capture owner alive across shutdown
Cross-thread callers take a strong reference under a mutex, so a shutdown
racing SetGameSessionActive no longer frees the owner mid-call.
2026-09-20 12:26:39 -05:00
vii 66de662e5b fix(input): decide pointer capture from state, not coordinates
The router inferred relative mode from SDL reporting zero absolute
coordinates. X11 does; Wayland reports a position that accumulates the
deltas and leaves the window, so every camera-drag event took the
absolute branch and walked Roblox's cursor to the viewport edge.

Motion, button and wheel events now carry the window's relative mouse
mode, and the RMB camera-drag fallback only captures inside an
experience so the app surface keeps a free cursor.
2026-09-20 12:05:48 -05:00
vii 6c3631058c chore(docs): ignore the working plans directory 2026-09-20 12:05:47 -05:00
vii 6829fd00ed perf(graphics): decode ETC2 uploads off the submitting thread
Uploads decode on a background dispatcher and the submit carries a
timeline semaphore wait, so the GPU waits for the texture rather than
the render thread. The gather of the application's compressed bytes
moves there too when they are already mapped; unmapping or freeing that
memory drains the decodes still reading it.

Staged copies gain an explicit host-write barrier. A queue submission
only makes host writes from before it visible to the device, and these
writes land after the submit.

Devices without timelineSemaphore, and submits whose pNext already sizes
arrays by their semaphore counts, decode inline as before.

Blade Ball, median of three: worst vkQueueSubmit 140ms -> 1ms, p99
4.9ms -> 0.05ms, total 2534ms -> 309ms. Frame interval unchanged; its
tail is the engine's own main-thread step.
2026-09-19 18:02:16 -05:00
vii 8be3618cd5 feat(packaging): ship DEB, RPM and pacman packages with each release
The release workflow now builds the three native packages beside the
AppImage and attaches them to the GitHub release with a checksum each.
Nothing is pushed to a distribution repository.

Packages are named nightcap and take their version from the release tag
instead of upstream's project version, so they no longer claim to be
Mocktail 1.0.4. Each variant conflicts with the others because they all own
/usr/bin/mocktail.

The AUR workflow no longer clones or pushes aur.archlinux.org; it refreshes
nightcap-git metadata on demand and uploads it as an artifact. The recipes
move to packaging/aur/nightcap{,-bin,-git}, point at this repository, and
version from the newest release tag.
2026-09-19 01:12:12 -05:00
vii 265b476440 docs(packaging): list Nightcap releases in the AppStream metadata
The releases block carried upstream's 1.0.x history, so a software centre
read the app as newer than every Nightcap tag and would never offer 0.3.x as
an update. Describe the 0.x line instead, and name Nightcap in the project
description.
2026-09-19 01:11:16 -05:00
vii ff905fe5b7 test(input): pin the caret position before a trailing space
SDL_ttf marks only the zero-length substring past the end of the text with
TTF_SUBSTRING_TEXT_END, so a caret left of a trailing space keeps the cluster
position. Cover both carets so the end-of-text branch cannot swallow the one
beside it.
2026-09-19 01:10:54 -05:00
vii 02108a9bb1 perf(input): reuse the TextBox overlay fonts across rasters
Every keystroke re-ran TTF_Init, a fontconfig sort, six TTF_OpenFont calls
and TTF_Quit on the render thread. Hold the opened fonts on the overlay and
reopen them only when the Roblox font or the point size changes, keeping the
current set when a reopen fails. Measured over 100 rasters, 1.59 ms to
0.12 ms per keystroke.
2026-09-19 00:32:31 -05:00
vii 7dd32335ca fix(input): advance the TextBox caret past trailing whitespace
The final cluster rect SDL_ttf reports stops at the last inked glyph, so a
caret at the end of a single-line field did not move when a trailing space
was typed. Use the laid-out width there; wrapped layouts keep the clamp
because their width is the widest line rather than the last one.
2026-09-19 00:32:31 -05:00
vii 5ed6ed5e84 chore(packaging): declare the libpng dependency
Texture overrides decode PNG through libpng, which stubs/CMakeLists.txt
already requires. Lists it in the Arch, AUR, and Nix packages, the thin
dependency installer, and the README build instructions.
2026-09-18 19:30:58 -05:00
vii cef1b3ec4a Merge upstream komaruworld/mocktail into main 2026-09-18 19:29:24 -05:00
vii 8bde1a1c0d Merge pull request #1 from CoderDayton/release/0.3.0
Release 0.3.0
2026-09-18 19:19:48 -05:00
vii 60e9c5c3ef docs(changelog): release 0.3.0 2026-09-18 19:06:08 -05:00
vii d76e1b6f54 docs(roadmap): move profiling and Discord presence to shipped, add first-load stutter 2026-09-18 19:06:02 -05:00
vii 33c06c063e docs(changelog): note the Sober-style Discord card defaults 2026-09-18 18:54:57 -05:00
vii 9e8afb233a feat(discord-rpc): show the Roblox badge as the default small image
Defaults images.small to the roblox_small asset on the bundled Discord
application, with "Roblox" as its hover text.
2026-09-18 18:54:12 -05:00
vii 7fda1eb239 feat(discord-rpc): show the experience creator and title the card Roblox
Default title is Roblox, details "Playing {place_name}", and state
"by {creator_name}". The creator comes from the games API response
already fetched for the place name. A text field that expands an
empty placeholder is left out, so the state never shows a bare "by".
2026-09-18 18:45:11 -05:00
vii f128e28b83 docs(perf): describe the main-thread pump rest modes and GameMode's governor permission 2026-09-18 18:26:32 -05:00
vii 718e67f9e1 perf(runtime): rest the engine pump between polls instead of spinning
The main thread polls nativeCallMessagesFromMainThread 146k times a second
with nothing to do. Between polls it now rests 100 us: nanosleep when the
governor is performance, TPAUSE (C0.2) when the CPU has WAITPKG, a spin
otherwise. Sleeping under a power-saving governor lets the render thread's
core clock down and drops frames, which is why TPAUSE is the default there.
MOCKTAIL_ENGINE_PUMP_REST=sleep|tpause|spin overrides. The profile trace
gains a slice for pumps that ran a message.
2026-09-18 15:36:54 -05:00
vii c6c354fdb1 perf(graphics): emit ETC2 uploads on the decode pool and stop the trace writer after a failed write
EmitPass reuses the decoder's persistent workers through RunOnDecodeWorkers
instead of creating and joining threads per batch. A failed trace write stops
the writer thread rather than leaving it ticking every 250 ms.
2026-09-18 12:42:36 -05:00
vii 25930d14ff fix(graphics, runtime): honor ETC2 source stride and cut main-loop overhead
ETC2 uploads read bufferRowLength/bufferImageHeight instead of assuming
packed rows. MakeFileObject retains the path field. The main-thread pump
caches its JNI attach, but not the GetJNIEnv fallback, so a VM swap retries.
An idle launch-broker drain skips both locks.
2026-09-18 12:21:29 -05:00
komaruworld 854969457f Fix textures and device info from Nightcap
Ported from CoderDayton/nightcap
2026-09-18 16:55:29 +03:00
vii 65030d8df1 fix(input): hold the pointer position during relative mouse capture
Relative-mode motion carries zero absolute coordinates. The router used
to accumulate the deltas into its pointer position, so a long camera turn
parked the reported position at the viewport edge and Roblox drew its
cursor there when the capture ended. The position now stays where it was
captured while deltas pass through. Zero-coordinate wheel and button
events keep it too, and an unknown position defaults to the viewport
center.
2026-09-18 02:51:03 -05:00
vii 0be5e808c5 fix(runtime): restore the affinity of every thread, not just the caller
sched_setaffinity binds one thread, so the fallback that undid a GameMode pin
only freed the thread that called Start(); the worker threads the fix exists to
protect stayed on the daemon's cores. Walk /proc/self/task instead, and treat a
readdir failure partway through as a failed restore rather than a finished one.

Also stop chrome_trace_writer emitting a valueless "ts" on to_chars failure, or
a bare inf/nan, either of which costs the whole trace instead of one field.
2026-09-18 02:00:58 -05:00
vii 1b030a57eb perf(runtime, graphics): release core-pinning gamemode and parallelize texture uploads
Release the GameMode request when the daemon narrows the CPU mask; the pin
concentrates Roblox's worker pool and is re-applied every few seconds, so
ending the request is the only thing that lifts it. `gamemode: on` keeps it.

Emit texture uploads on worker threads instead of serially inside
vkQueueSubmit, in two passes so mip 0 lands before the levels scaled from it.

Decode ETC2 through a persistent pool and lower the parallel floor to one
128x128 image, so small batches stop running single-threaded.

Document all three in docs/PERFORMANCE.md.
2026-09-18 00:47:57 -05:00
vii 2f5527b8b5 perf(libc_shim): cut path translation cost per file call
Mappings are kept sorted longest prefix first, unmapped paths skip allocation, and the path trace switch is read once.
2026-09-16 21:22:31 -05:00
vii 579cedbf1d perf(textures): pool ETC2 staging buffers and speed up resampling
Staging buffers stay mapped and are reused instead of allocated per upload. Box resampling precomputes column spans and copies repeated rows, with identical output.
2026-09-16 21:22:31 -05:00
vii a7b90e5919 fix(jnivm): stop leaking JNI handles until the table fills
FindClass and GetObjectClass reuse one handle per class, detached threads release their local refs, and local ref deletes and singleton checks no longer scan. A full table logs the classes holding handles. Stale handles above slot 4096 no longer read as C strings.
2026-09-16 21:22:31 -05:00
vii 3bba795ea3 feat(profile): add --profile Chrome trace of Vulkan adapter work
Records presents, GPU waits, submits, ETC2 decode, and pipeline and shader creation for ui.perfetto.dev. Adds a benchmark guide and a trace summary script.
2026-09-16 21:22:30 -05:00
vii 5ce4487d4f feat(discord): add custom presence title, state, and icons
text.title sets the activity name, images.* set the large and small icons and hover text, and text.state may be empty to hide it. Text and image fields expand {place_name} and {place_icon}; fields that render empty are omitted. show_place_name: false now also hides the place thumbnail.
2026-09-16 11:45:30 -05:00
vii c544389dd0 fix(textures): make the crosshair arms symmetric
Move the right and bottom arms out 1px so every arm reaches 12px from the center pixel with a 4px gap.
2026-09-16 11:11:06 -05:00
vii bdb84142d7 docs(textures): ship a crosshair override people can copy in
Adds textures/ with the crosshair PNG named by its ETC2 hash and a README
covering install, naming and how to dump hashes for other textures. Nothing
installs or loads it; copying into ~/.config/mocktail/textures is opt-in.
2026-09-16 04:53:53 -05:00
vii 40698efb7d docs(changelog): release 0.2.0 2026-09-16 03:25:39 -05:00
vii 3d2a2b3d98 docs(changelog): record the input, storage, and packaging changes 2026-09-16 02:56:11 -05:00
vii 78a314b761 ci(packages): reduce to a branded nightly AppImage
The workflow built DEB, RPM and pacman packages on every push to main and
every pull request, then published them as "Mocktail continuous" with
assets named Mocktail-* and a continuous tag pushed as komaruworld.

Nightcap ships the AppImage and the Flatpak, so the three native package
jobs are removed along with the APT and RPM repository step, which could
not work here regardless: it downloads .deb and .rpm from this repository's
latest release, and the release workflow publishes neither.

What remains runs nightly and on demand rather than per commit, skips the
build when the continuous tag already points at HEAD, publishes
Nightcap-nightly-x86_64.AppImage with a checksum beside it, and tags as
github-actions[bot]. A manual run from another branch builds and
smoke-tests without touching the release.
2026-09-16 02:51:20 -05:00
vii 2d1dd3788b ci(release): pass GH_TOKEN to the AppImage dependency installer
install_anylinux_dependencies.sh now queries the GitHub API for the rolling
release's asset IDs and digests. Without a token it uses the unauthenticated
rate limit, which shared Actions runner IPs exhaust. packages.yml already
passes the token; the release workflow runs the same script in the same
container and did not.
2026-09-16 02:42:06 -05:00
vii 56d3160641 Merge upstream komaruworld/mocktail into main
Takes upstream's AnyLinux dependency rework: the checked-in digest list is
replaced by a name list, and the installer snapshots GitHub asset IDs and
SHA256 digests together at build time, verifying each archive before
installation. This removes the manual hash refresh a998754 had to do.

Keeps Nightcap's FAQ.md over the upstream copy; ours is the same document
with Nightcap paths, RPC on by default, and the Discord play time section.

Keeps TextureManager2 enabled rather than upstream's revert to TM1. Their
mip regression is described on the mobile MipPackStream path, and this fork
already reports real host memory and a PC device profile. Hours of play
show no texture problem, so the workaround is not adopted without evidence
it is needed here.
2026-09-16 02:37:08 -05:00
vii 5db99041bc docs: rewrite the input roadmap items and trim the donation list
Discord Rich Presence item 1 is custom presence fields; the private-server
join item is dropped. The profiling item names the Chrome trace format
instead of Perfetto.

Item 2 covered hot-plug and input release on disconnect, which already
work, so it is now only the button glyph gap. Raw mouse and the pointer
fix move to Shipped.

Drops the BCH donation address from the README.
2026-09-16 02:29:07 -05:00
vii 9ec56338c3 test: fix umask, dash, and scheduling assumptions in three suites
native_update_test wrote approval artifacts with the process umask, which
candidate_approval rejects when group writable. The helper now chmods 0600.

webview_helper_launcher_test drove its fake helper with `dd <&198` under
#!/bin/sh. dash rejects multi-digit file descriptors, so the script never
ran and the packet file stayed empty. It now reads the control socket in
python3, as the adjacent tests in the file already do.

audio_foundation_test asserted the poller thread had run before stopping
it, without waiting for it to be scheduled; it failed about one run in two.
It now waits for the first poll under a bounded deadline.
2026-09-16 02:28:09 -05:00
vii 894ebf8eb7 fix(services): create BrowserTracker storage directories private and usable
The service created its own credential directory with create_directories
and then refused it, because IsPrivateDirectory rejects a group-writable
path and mkdir masks its mode with the process umask. On a umask of 002
that failed every first run; a umask carrying owner bits left the
directory unwritable by its owner instead.

CreatePrivateDirectories creates each missing component and tightens it to
0700 through a descriptor opened O_DIRECTORY | O_NOFOLLOW. Components that
already exist keep their mode and are still judged by IsPrivateDirectory.
2026-09-16 02:28:02 -05:00
vii 6bec8a6c5c feat(input): add input.raw_mouse and keep the pointer inside the viewport
Relative-mode motion reports zero absolute coordinates, so the router
accumulates deltas. The accumulator was never clamped, only the copy sent
to the engine, so a long turn pushed it far outside the viewport and every
reversal was swallowed until the pointer travelled all the way back.

input.raw_mouse sends look deltas in host units instead of guest surface
units. Those differ only when the desktop display scale and the window's
pixel density disagree, as under fractional scaling. Default off.
2026-09-16 02:27:56 -05:00
vii b9e7bc761b docs(roadmap): swap RPC join items for custom presence fields
Name the Chrome trace format for --profile instead of Perfetto.
2026-09-15 22:53:19 -05:00
komaruworld b54c896498 ci hate me? 2026-09-15 19:52:24 +03:00
komaruworld 1cd7b7db8f Fuck ci 2026-09-15 19:42:56 +03:00
vii f87aaca2fb feat(discord): enable Rich Presence by default under Nightcap's own application
Adds Terms and Privacy pages to the site for the Discord application.
2026-09-15 10:34:50 -05:00
vii 901ba615ab fix(runtime): keep fflags.json values over the performance preset instead of failing launch 2026-09-15 10:34:50 -05:00
komaruworld cdbf9ac1eb revert commit fdce534 2026-09-15 18:26:13 +03:00
vii f098bc1b5d docs(faq): add FAQ from upstream with Nightcap paths and link it
Discord RPC config key, log locations, and the upstream vr branch.
2026-09-15 09:12:38 -05:00
vii e1f7c8c06c docs(changelog): add CHANGELOG.md and use it for GitHub release notes
Release and Flatpak workflows take the release body from the tag's
changelog section and fail early if it is missing.
2026-09-15 08:23:11 -05:00
vii a9987546d5 fix(packaging): refresh AnyLinux debloated package hashes to 2026-09-15
pkgforge rebuilt six of the eight rolling packages, so the AppImage build
failed on the ffmpeg-mini checksum.
2026-09-15 08:10:55 -05:00
vii 6a39887e8f docs(roadmap): add Nightcap roadmap and link it from the README 2026-09-15 08:04:17 -05:00
vii 349e314850 feat(packaging): rename app ID to io.github.CoderDayton.nightcap and publish Flatpak
Rename the desktop entry, metainfo, icons, Flatpak manifest, and SDL app
identity so Nightcap installs side by side with upstream. Build a signed
Flatpak on v* tags, publish the OSTree repo and landing page to GitHub
Pages, and attach the bundle to the release. Release creation tolerates
the AppImage and Flatpak workflows racing on the same tag.
2026-09-15 07:10:26 -05:00
komaruworld f383fbbff4 update faq.md 2026-09-15 12:08:18 +03:00
vii b1112d8cdc feat(graphics): default small texture upscale to 4
Unset or non-numeric MOCKTAIL_SMALL_TEXTURE_UPSCALE now means 4, so the
upscale applies on every launch path. Drop the variable from the desktop
entry Exec line.
2026-09-15 03:49:13 -05:00
vii 27c383a5d9 feat(branding): recolor app icon to the Nightcap purple palette
Indigo #2F2A6B, violet #6C4FD1, lavender #A78BFA. PNGs rendered with resvg.
2026-09-14 22:26:10 -05:00
vii 7cf24feef6 feat(branding): rename fork to Nightcap and add AppImage release workflow
Show Nightcap as the app name in the desktop entry, window title,
dialogs, and metainfo. Binary, config paths, and desktop ID keep the
mocktail name. Publish Nightcap-x86_64.AppImage to GitHub Releases on
v* tags. README points at the fork's releases instead of upstream
packages.
2026-09-14 22:19:19 -05:00
vii 4443766d39 chore(hooks): add lefthook config mirroring CI repository checks 2026-09-14 18:19:15 -05:00
vii ea2166b09a docs(license): mark files modified from upstream per Apache 2.0
Add a modified-by notice to every upstream file this fork changes.
Ignore build-test/.
2026-09-14 18:08:12 -05:00
vii 9e4e69be1c docs(readme): describe fork changes and replace dead FAQ link with settings 2026-09-14 16:48:00 -05:00
vii dd0655bfde Merge branch 'pc-improvements' into main
Resolve src/update/host_abi_deriver.cc: keep upstream's FMOD vtable layout
versions and input-method derivation inside the optional output-device
bridge, so a mismatch omits the bridge instead of failing derivation.
2026-09-14 16:09:33 -05:00
vii d2d275706b perf(graphics): present through fifo-latest-ready by default to cut input latency
Roblox requests FIFO with 3 swapchain images, which queues up to two frames
before display. The default vsync policy (auto, unmanaged frame cap) now
resolves to the vsync policy so the lowest-latency synchronized present mode
the host offers is used.
2026-09-14 15:51:35 -05:00
komaruworld d76d75b7f8 rollback to native login
not fully test
2026-09-14 19:03:36 +03:00
vii b7b63dd30f feat(packaging): launch with MOCKTAIL_SMALL_TEXTURE_UPSCALE=4 from the desktop entry 2026-09-14 10:10:40 -05:00
vii aacab4036c feat(graphics): replace and upscale small ETC2 textures
Level-0 uploads are hashed against the override store; MOCKTAIL_SMALL_TEXTURE_UPSCALE
creates colour images up to 64px N times larger, snaps block-rounded regions to the
host mip, fills every host mip from level 0 and turns image copies touching scaled
images into blits. MOCKTAIL_ADVERTISE_ETC2=0 hides the feature bit.
2026-09-14 10:10:40 -05:00
vii e104b1d936 feat(graphics): add PNG texture override and dump store keyed by upload hash
Adds libpng, MOCKTAIL_TEXTURE_OVERRIDE_DIR (default ~/.config/mocktail/textures)
and MOCKTAIL_TEXTURE_DUMP_DIR.
2026-09-14 10:10:40 -05:00
komaruworld 537828acc2 Fix vc
Fixes #128 Fixes #132
2026-09-14 16:36:35 +03:00
komaruworld 5be2f68a55 Support input devices
Fix ci, some shit with webkit on wayland
2026-09-14 15:24:04 +03:00
komaruworld f7e519bae1 I forgot to add changelog 2026-09-14 14:50:12 +03:00
komaruworld ad8fbfcf65 Add 1.0.4 release to flathub metadata 2026-09-14 14:36:49 +03:00
komaruworld 96e1966d16 sigxcpu crashes fix
Refs #84
2026-09-14 13:15:54 +03:00
komaruworld becaee71c0 Add fleasion support
Close #18
2026-09-14 13:05:24 +03:00
vii bf6a6b417d fix(packaging): write the install path into the installed desktop entry 2026-09-14 04:53:50 -05:00
vii dfd35b8cc8 feat(packaging): launch on Wayland from the desktop entry 2026-09-14 04:44:25 -05:00
vii 1632681e6c fix(jnivm): answer NativeQuoteInterface.requestResponse so servers stop kicking with 319
The client asks Java for a keystore attestation quote after joining. With no
handler the call returned null, the server never got an answer and dropped
the session with error 319. Reply with the no-keystore form [1, 0, message].
2026-09-14 04:44:25 -05:00
komaruworld ab4944a907 Fix shit with sigxcpu
Refs #84
2026-09-14 12:34:09 +03:00
vii 88d10d53f2 perf(graphics): decode ETC2 uploads off mapped memory on worker threads
Compressed bytes are copied to heap scratch, decoded heap to heap across
up to 8 threads in block-row bands, and streamed into the staging buffer.
Decoding straight from uncached Vulkan memory caused 200-460 ms hitches.
The FPS trace now reports the longest frame gap per window.
2026-09-14 04:18:43 -05:00
vii eae9fa7030 feat(graphics): emulate ETC2/EAC textures on Vulkan
Hosts without native ETC2 now report ETC2/EAC support. Uploads into emulated images are decoded to RGBA8/R16 staging buffers at submit. MOCKTAIL_DISABLE_ETC2_EMULATION=1 turns it off.
2026-09-13 11:45:28 -05:00
vii ad76f68224 fix(runtime): report real host memory and display size
DeviceParams now carry host RAM instead of a low-RAM device profile, and screen, window and configuration sizes follow the host display and window with a 1920x1080 fallback.
2026-09-13 11:45:28 -05:00
vii d783793b4e fix(update): make the FMOD output-device bridge optional
Candidate payloads without a matching FMOD output-device signature now derive without the bridge instead of failing activation.
2026-09-13 11:45:28 -05:00
komaruworld 017f79398d Fix crash in resize windows 2026-09-13 14:32:41 +03:00
komaruworld fc120e8285 support rbx 2.738 with fix charts 2026-09-13 13:02:25 +03:00
komaruworld 117b9b2016 Update mocktail package in aur
😢
2026-09-12 23:21:27 +03:00
komaruworld 1376ab2474 Ci fixes sha256
i skoro sha snesy naher
2026-09-12 23:13:55 +03:00
komaruworld fdce53421f Fix low textures
THX HIPPO! <3
2026-09-12 20:52:25 +03:00
komaruworld 770ed086d5 Fix aur bin
Close #132

:(
2026-09-12 19:27:53 +03:00
komaruworld c48db09f13 Fix chat text render on opengl 2026-09-12 15:59:03 +03:00
komaruworld 0548ebd253 Fix chat text render and chat shiftlock issues
Fixes 106, Fixes #127, Refs #116
2026-09-12 14:45:29 +03:00
komaruworld c523048518 Update sha256 in anylinux 2026-09-11 15:54:14 +03:00
glook9001andglook9001 ffe3fba8af refactor(crypto): remove standalone sha256 and migrate all callers to OpenSSL EVP (#122)
* refactor(crypto): deprecate custom sha256 in favor of OpenSSL libcrypto

- Remove standalone src/foundation/sha256.cc, include/mocktail/sha256.h, and tests/sha256_test.cc
- Remove mocktail_crypto_foundation library and sha256_test targets from CMake
- Export clean FileDigest and HashText helpers via include/update/payload_integrity.h
- Deduplicate byte-to-hex formatting across host ABI loader and updater modules
- Support direct generation hash sizing to avoid repetitive string slicing
- Validate all EVP return codes across streaming downloads and asset tree hashing

* fix(compat): abort candidate canary registration when profile hashing fails

- Return Failure before profile validation and registration if ComputeSha256Hex fails
- Propagate canary attestation hashing failures in ValidateCanaryAttestation
- Add SetHostAbiProfileHashingFaultForTesting hook and regression test in host_abi_profile_test

---------

Co-authored-by: glook9001 <310372091+glook9001@users.noreply.github.com>
2026-09-10 18:11:02 +03:00
komaruworld 5d475e03d8 Fix low textures...
Fixes #126
2026-09-08 21:53:00 +03:00
komaruworld 240dc1ee63 Fix text issues 2026-09-08 20:53:37 +03:00
komaruworld 569980ecf2 roblox 2.736.1408 (2998) 2026-09-06 20:06:15 +03:00
komaruworld 2f80333c5d Fixes some issues with low texture buttons 2026-09-06 19:43:59 +03:00
komaruworld be7d542d49 Fix webkit on linuxulator
Refs; #121
2026-09-06 12:36:40 +03:00
komaruworld 266f917f49 texture memory budget fix from #76 2026-09-06 11:45:32 +03:00
komaruworld 859f8f2b8f Fix vc?
I've already lost count of how many times I've tried to fix the vc.
2026-09-06 11:45:32 +03:00
hxehex.real 8447bf3f1a add discord badge to readme (#117) 2026-09-05 23:09:08 +03:00
komaruworld b71873f2a5 Maybe fix vc 2026-09-05 20:06:02 +03:00
komaruworld 92c7d7b12b Add xbox,playstation gamepade support
Close #16
2026-09-05 19:13:55 +03:00
komaruworld cc3893d422 Improve appimage build
Close #113
2026-09-05 18:29:04 +03:00
komaruworld a6fdb5b0cd Fix Vulkan icd selection
#105 burmalda ili govnolda
2026-09-04 18:08:36 +03:00
komaruworld 3890efbaaa Fix payload restaging
#110, 20gb payloads bruh
2026-09-04 17:38:55 +03:00
komaruworld 9f7b09e197 Fix payload download
#95
2026-09-04 17:17:50 +03:00
farwhile 3284cf9fc1 fix(vulkan): accept suboptimal swapchain images (#109) 2026-09-04 15:11:05 +03:00
komaruworld 035c7fe1d4 Import theme from roblox and fix x11
vkAcquireNextImageKHR(): A swapchain no longer matches the surface properties exactly, but can still be used to present to the surface successfully. (VK_SUBOPTIMAL_KHR) (1000001003)
2026-08-31 21:42:35 +03:00
komaruworld d5f9c91358 Fix input, text and add fps in esc menu
Refs #101
2026-08-31 18:50:57 +03:00
farwhile 0cc9ab2e32 fix(window): respect logical display resolution by default (#99) 2026-08-31 14:11:11 +03:00
komaruworld 7127ad1bf7 Fix scaling on high dpi display
Refs #88
2026-08-30 16:57:19 +03:00
komaruworld 54a302dc27 Fix issues with low-res textures
Refs #97
2026-08-30 16:44:36 +03:00
komaruworld 23a5c96e3d Fix sdl3 not avaible
Fixes #93
2026-08-30 16:43:42 +03:00
komaruworld ae417490f1 Fix gamemode on nixos
Fixes #94
2026-08-30 15:35:18 +03:00
komaruworld 35298050b8 2.736.1408 support 2026-08-30 15:17:05 +03:00
glook9001andglook9001 703af11d17 perf(runtime, asset, jni): lock-free JNI resolution, in-memory ETC1 transcode caching, and spin guard optimization (#89)
* perf(runtime, asset, jni): lock-free JNI object resolution, in-memory ETC1 transcode caching, one-time spin guard classification, and host driver environment tuning

* fix(jnivm): preserve precise g_known_objects check in PseudoObjectFromRef

* perf(jni, linker): fast-path method name matching before class queries and binary search libc symbols

* perf(jni, runtime): pre-tagged O(1) jump table for JNI method dispatch and direct main loop message pump without sigsetjmp

* perf(legacy_runtime): remove redundant sigsetjmp wrappers across surface updates and memory trimming

* Revert "perf(legacy_runtime): remove redundant sigsetjmp wrappers across surface updates and memory trimming"

This reverts commit 82e4019f68aa0d2968716a2dc066bc26961d7755.

* refactor(legacy_runtime): remove dead alpha DelayedUpdateSurfaceAppThread and redundant post-start surface calls

* perf(jnivm, android): lock-free JNI object resolution and zero-allocation asset URI prefixes

* perf(runtime, compat): use cached ::gettid and eliminate all remaining detached pthread spawns and busy waits

* perf(vulkan, jnivm, legacy): mailbox present rewrite, JNI handle reuse, and dead-code cull

Rewrite guest swapchain presentMode at vkCreateSwapchainKHR so vsync:auto
and a fixed FPS cap stay on MAILBOX/FIFO_LATEST_READY instead of host-default
FIFO. Prefer FIFO_LATEST_READY then MAILBOX, bump vsync minImageCount, treat
SUBOPTIMAL as OUT_OF_DATE, and drop the 1s acquire watchdog.

Fix JNI local-frame handle leaks (unsigned 32-bit handles, freelist reuse,
no intern pool wrap-stomp). Default native_set_roblox_channel on so injected
production channel is applied. Remove TM2 .* deny-list, unused OpenGL trim,
and leftover legacy_runtime dead code.

Leave untracked docs, vulkaninfo dumps, rbx_bin assets, and bpftrace scripts
out of this commit.

---------

Co-authored-by: glook9001 <310372091+glook9001@users.noreply.github.com>
2026-08-30 13:46:47 +03:00
komaruworld 3a26ecc83e Add github templates 2026-08-28 22:26:59 +03:00
komaruworld 278d15e41a refactor(legacy): split runtime into focused modules
Closes #91
2026-08-28 20:31:28 +03:00
komaruworld 968df46605 Remove python 2026-08-28 17:54:52 +03:00
komaruworld 409d8a6319 Fix AnyLinux JRE dependency closure 2026-08-28 17:26:09 +03:00
komaruworld 9812fbceab Fix AnyLinux ci 2026-08-28 17:08:31 +03:00
komaruworld 61a467cadc Port fullscreen and audio device bridges to latest roblox 2026-08-28 16:57:04 +03:00
komaruworld 497481e8fa Fix Roblox text input rendering
Fixes #68
Fixes #70
Follow-up to #21
2026-08-28 16:29:50 +03:00
5f2be59192 perf(runtime): lock-free window/EGL symbol caching (36x faster), fast epoll ALooper, and SMT core affinity (#73)
* perf(graphics/math): lock-free window symbol caching and single-cycle libm builtins

- Cache dlsym function lookups with atomic acquire/release in libandroid and libegl stubs, eliminating dynamic linker lock contention on hot ANativeWindow_getWidth/Height and eglSwapBuffers paths (26x speedup)
- Export direct compiler builtins (__builtin_floorf, __builtin_fmaf, __builtin_roundf, __builtin_fminf/fmaxf) in libm_stub.cc with -O3 -fno-math-errno for single-cycle hardware execution in physics and skeletal transforms
- Zero-delay event pumping and lock-free mouse dispatch path

* perf(looper/affinity): epoll batch-draining looper and SMT core affinity

- Implemented Nuah's high-performance Linux epoll+eventfd ALooper batch draining, eliminating event starvation and delayed multi-shot bursts
- Auto-detect physical CPU core topology and apply thread affinity in pthread_setname_np, pinning Render/Vulkan/Present to physical Core 0 and background Worker/Physics/Asset threads to Cores 1..N
- Benchmark confirms 36.6x speedup on lock-free window symbol queries

* docs: add guide on clean branch-isolated PR workflow (howtosubmitPR.md)

* ci(packages): add minizip package to AnyLinux AppImage build environment

* perf(core): asset probe RAM cache (181x faster), fast logging bypass (4000ms saved), and MADV_HUGEPAGE for large allocations

* perf(stdio): inline Bionic FILE stream translation and branch prediction

* perf(input/stub): lock-free direct dispatch for mouse clicks, scroll, and keys (8.9x faster) and cached trace env lookups

* perf(graphics): cache test graphics stub env checks (118.7x faster, 938ms saved per 10M calls)

Benchmark proof (10,000,000 graphics stub calls):
- Dynamic getenv per draw call: 946.88 ms (94.69 ns/op)
- Static Cached Boolean:        7.97 ms ( 0.80 ns/op)
- Speedup:                      118.75x faster (938.91 ms saved)

* perf(http): cache network proxy configuration and eliminate repeated environment parsing

* perf(window): cache hot-path environment lookups in PaceInputPump and PumpEvents

* perf(window): cache WindowTraceEnabled and persistence flags (825x faster, 3675ms saved per 10M frames)

Benchmark proof (10,000,000 per-frame trace queries):
- Uncached per-frame getenv: 3,679.64 ms (367.96 ns/op)
- Cached static bool:            4.46 ms (  0.45 ns/op)
- Speedup:                    825.88x faster (3,675.18 ms saved)

* perf(config): enable multithreaded rendering and throughput worker mode by default

* perf(vulkan): enable quad-buffered swapchain depth (minImageCount=4) to prevent Wayland/FIFO timeline stalls

* perf(bionic): add thread-local direct cache for condvar translation (4.2x faster, 103ms saved per 10M calls)

Benchmark proof (10,000,000 pthread_cond translation lookups):
- Uncached Mutex Sharded Lookup: 136.02 ms (13.60 ns/op)
- Thread-Local Cached Lookup:    32.26 ms ( 3.23 ns/op)
- Speedup:                       4.22x faster (103.75 ms saved)

* perf(input): extend lock-free direct dispatch to ReturnPressed keyboard handling

* perf(jnivm): cache TraceEnabled and StringTraceEnabled (211x faster, 4390ms saved per 10M calls)

Benchmark proof (10,000,000 JNI trace queries):
- Uncached JNI getenv queries: 4,411.36 ms (441.14 ns/op)
- Static Cached Boolean:          20.88 ms (  2.09 ns/op)
- Speedup:                       211.24x faster (4,390.47 ms saved)

* perf(vulkan): make HostDeviceProc and vkGetDeviceProcAddr lock-free (8.4x faster, 143ms saved per 10M calls)

Benchmark proof (10,000,000 HostDeviceProc queries):
- Mutex-Locked HostDeviceProc: 163.31 ms (16.33 ns/op)
- Lock-Free HostDeviceProc:     19.45 ms ( 1.94 ns/op)
- Speedup:                      8.40x faster (143.86 ms saved)

* perf(vulkan): add lock-free direct dispatch cache for queues and command buffers (37.4x faster, 141ms saved per 10M calls)

Benchmark proof (10,000,000 HostDispatchForQueue lookups):
- Uncached Mutex + Vector Scan: 145.28 ms (14.53 ns/op)
- Lock-Free Cached Dispatch:      3.88 ms ( 0.39 ns/op)
- Speedup:                       37.41x faster (141.40 ms saved)

* fix(runtime): prevent infinite hang on application exit by bounding Discord RPC worker join timeout

* perf(jnivm): cache JavaStringClass and JavaObjectClass pointers (6.1x faster, 584ms saved per 10M calls)

Benchmark proof (10,000,000 class lookups):
- Uncached Map Lookup + Alloc: 698.41 ms (69.84 ns/op)
- Static Class Pointer Cache:  114.34 ms (11.43 ns/op)
- Speedup:                     6.11x faster (584.07 ms saved)

* perf(window): cache per-frame readiness environment variables in event pump (107x faster, 2087ms saved per 10M frames)

Benchmark proof (10,000,000 PumpEvents checks):
- Uncached Per-Frame getenv: 2,107.32 ms (210.73 ns/frame)
- Cached Static Per-Frame:     19.65 ms (  1.97 ns/frame)
- Speedup:                    107.22x faster (2,087.67 ms saved)

* perf(vulkan): direct zero-lock driver passthrough for queue submit and present when overlay is dormant (129x faster, 274ms saved per 10M frames)

Benchmark proof (10,000,000 QueuePresent / QueueSubmit calls):
- Old (Double Mutex Lock per Present): 276.91 ms (27.69 ns/present)
- New (Zero-Lock Direct Passthrough):    2.15 ms ( 0.21 ns/present)
- Speedup:                             128.99x faster (274.76 ms saved)

* perf(jnivm): make GetMethodID and GetFieldID zero-lock when tracing is disabled (571ms saved per 10M calls)

Benchmark proof (10,000,000 lookups):
- Old (Redundant Class Resolution): 571.84 ms (57.18 ns/op)
- New (Zero-Lock Direct Fast Path): 0.000047 ms (0.0047 ns/op)
- Speedup:                          12,000,000x faster (571.84 ms saved)

* fix(jnivm): eliminate permanent memory leaks by implementing proper DeleteLocalRef reclamation and capture onTrimMemoryNative

- Eliminated unbounded g_object_storage and g_array_storage vectors that retained all JNI objects and byte arrays forever.
- Implemented DeleteLocalRef and DeleteGlobalRef to immediately erase allocated PseudoArray buffers and release shared segment object owners.
- Segment slot overwrites now automatically destruct recycled object instances.
- Captured onTrimMemoryNative handler from GameActivity registration.

Benchmark proof (1,000,000 JNI allocations):
- Old Storage Retained Objects: 1,000,000 (100% Leaked)
- New Storage Retained Objects:    67,232 (Reclaimed via DeleteLocalRef / recycling)

* Revert "fix(jnivm): eliminate permanent memory leaks by implementing proper DeleteLocalRef reclamation and capture onTrimMemoryNative"

This reverts commit 7cb1b1025a8f20e01d24f1e4e3ba5dcb2620c97a.

* perf(runtime): enable texture manager flush and dynamic texture/mesh streaming

- Enable FFlagRenderTextureManagerFlushUnused to purge unused textures from RAM instead of retaining them permanently.
- Enable FFlagEnableTextureStreaming2 and FFlagRenderEnableTextureStreaming to stream textures dynamically based on camera distance.
- Enable FFlagRenderMeshLodStreaming and FFlagRenderEnableDecalStreaming to avoid loading full-map assets into RAM simultaneously.
- Enable FFlagRenderEnableTextureAtlas for batched texture memory efficiency.

* perf(runtime): bound texture pool size to 256MB and enable aggressive Luau incremental GC

- Add FIntTexturePoolSizeMB=256 to prevent Roblox from expanding its texture memory cache to 1536MB+ on desktop RAM.
- Enable FFlagLuauIncrementalGC=True with FIntLuauGcStepMultiplier=100 and FIntLuauGcGoalRatio=200 to reclaim Luau VM heap memory eagerly on each step.

* revert: rollback experimental FFlags in performance_policy.cc

* fix(legacy_runtime): free heap storage before re-seeding stage6 string scratch buffer

- In SeedStage6StringFieldValueScratch, check if previous scratch string used heap allocation (words[0] & 1) and free words[2] before zeroing out and writing new libc++ string.
- Prevents unbounded heap memory leak during repeated Stage6 string map lookup recovery.

* perf(runtime): add FIntRenderTextureMipBias=1 and non-blocking texture transcode

- Apply FIntRenderTextureMipBias=1 in rendering settings to drop top uncompressed mip levels, cutting uncompressed texture memory by ~75% (matching Nuah's benchmark configuration).
- Set FFlagSimRuntimeContentTranscodeBlockingCall=False to move Basis/KTX2 transcoding off the critical render loop.

* perf(runtime): enable distant geometry LOD, voxel terrain chunk bounding, GPU lightgrid, and particle culling

- Enable FFlagRenderEnableLowEndLOD to downscale distant player avatars and meshes in populated multiplayer lobbies.
- Set FIntTerrainArraySliceSize=4 to shrink voxel terrain chunk array allocations in RAM.
- Enable FFlagFastGPULightGrid and FFlagRenderOptimizeLightGrid to move lighting staging out of CPU system memory to GPU.
- Enable FFlagRenderFixParticlesCulling to purge off-screen particle vertex allocations from memory.

* fix(jnivm): implement dual reference-counted JNI memory reclamation and eliminate unbounded storage vectors

- Replaced unbounded g_object_storage vector with shared_ptr slot owners in g_segment_owners.
- Added g_segment_refcount[100000] to track dual local/global reference lifecycles.
- NewGlobalRef and NewLocalRef increment refcount; DeleteLocalRef and DeleteGlobalRef decrement refcount and free memory only when all references are released.
- Converted g_arrays to own unique_ptr<PseudoArray> with internal ref counting, eliminating g_array_storage.
- Captured onTrimMemoryNative handler from GameActivity registration table.

* Revert "fix(jnivm): implement dual reference-counted JNI memory reclamation and eliminate unbounded storage vectors"

This reverts commit 27f84e52e362338e78c91e8ad05edf2c8d7e1ec4.

* perf(runtime): optimize JNI string storage and enable Luau GC pacing + asset worker limits

- Optimize PseudoStringObject in jnivm to lazily initialize UTF-16 representation, eliminating redundant UTF-16 vector allocations and duplicate string transcoding on every JNI string creation.
- Remove dead unused g_string_storage in jnivm.
- Enable FFlagLuauIncrementalGC to prevent script heap spikes.
- Set FIntLuauGcStepMultiplier=100 and FIntLuauGcGoalRatio=200 to reclaim temporary Luau script tables promptly.
- Set FIntAssetProviderThreads=2 to shrink concurrent HTTP and decompression memory staging queues.

* perf(runtime): cache environment flags in PumpRobloxMainThreadMessagesOnce

Eliminate getenv() and string comparison overhead on every iteration of the main thread message pump loop.

* perf(runtime): force hardware texture, voxel, and mesh compression

- Enable FFlagUITextureCompressionDesktop and FFlagTCTextureCompressionDesktop to transcode UI and texture cache assets to compressed GPU formats instead of raw 32-bit RGBA8.
- Set FFlagUITextureUncompressed=False to disable uncompressed fallback.
- Enable FFlagGpuVoxelCompression to compress voxel terrain representations on the GPU.
- Enable FFlagMeshCompression and FFlagPhysicsMeshCompression to compress 3D geometry and collision vertex buffers.

* perf(runtime): clamp geometry and physics caches, force compressed terrain textures, and limit texture budgets

- Enable FFlagSupportMeshLOD, FFlagMeshLOD, and FFlagMeshLodApplyGeomMeshOptimizer to simplify 3D polygon geometry and apply geometry mesh optimization.
- Enable FFlagAdaptiveMeshCacheSizeForAndroid and set FIntDefaultMeshCacheSizeMB=128 to bound 3D mesh caches.
- Set FIntRenderMeshOrphanBudgetMB=16 to immediately purge out-of-view 3D meshes.
- Set FIntAvatarMeshMemoryMax=67108864 (64 MB) to bound player avatar cosmetics memory.
- Set FIntTerrainGpuMeshMemoryTargetMib=64 to cap terrain geometry memory.
- Set FIntSmoothTerrainPhysicsCacheSize=64 to clamp physics collision chunk RAM.
- Set FIntRenderTextureTotalBudgetMB=256 and FIntRenderTextureOrphanBudgetMB=16 to enforce strict texture cache ceilings in RAM.
- Enable FFlagDebugTerrainVTCompressedTextures to force compressed virtual textures on terrain.

* perf(runtime): emulate low-RAM Android device profile and set ANV_SYS_MEM_LIMIT=1024

- Set isLowRamDevice=JNI_TRUE, deviceTotalMemoryMB=2048, and standard low-RAM heap bounds in BuildSystemInfoParams to force the Roblox Android engine into lean mobile operating mode.
- Set default ANV_SYS_MEM_LIMIT=1024 to instruct Mesa Intel Vulkan driver to unmap and free idle buffer objects instead of hoarding hundreds of megabytes in system RAM.

* perf(runtime): add periodic malloc_trim and optimize DOM tree streaming GC

- Call malloc_trim(0) periodically in the main thread message pump loop to consolidate glibc free bins and release fragmented heap pages back to the Linux OS.
- Enable FFlagStreamingObserverMemoryOptimization to optimize DataModel DOM observer memory overhead.
- Set FIntNetworkStreamingGCMaxMicroSecondLimit=5000 to aggressively destroy streamed-out DOM instances.
- Enable FFlagfeatureFastClusterCacheEnabled to cache flattened GPU cluster geometry and bypass recurring DOM tree traversals.

* Fix PR 73 performance optimizations

Revert unsafe fast paths, fix ALooper and asset caching, restore AppImage packaging, and add regression tests.

* perf(runtime): enable texture manager sharing, Vulkan memory aliasing, and mesh deduplication

- Enable FFlagEnableMultiVETextureManagerSharing to share texture instances across visual elements.
- Enable FFlagVulkanCompressedTextureAliasing to alias compressed textures into shared GPU device memory allocations.
- Enable FFlagCSGPublishDedupSolidMesh to deduplicate identical 3D solid geometry meshes.
- Enable FFlagDeduplicateLodRemovalRegions to deduplicate LOD regions.
- Enable FFlagCurlUsePools to pool network download buffers.

* perf(runtime): wire onTrimMemoryNative into GameActivity lifecycle and message pump

- Register and capture mocktail_gameactivity_on_trim_memory_native in jnivm RegisterNatives.
- Export mocktail_gameactivity_on_trim_memory_native in bionic_abi_exports.
- Store g_saved_game_activity in run_game_activity_initialize.
- Implement MocktailTrimEngineMemory(int level) invoking Roblox's internal RBX::NativeDataModelManager::nativeActivity_onTrimMemory.
- Trigger periodic TRIM_MEMORY_RUNNING_CRITICAL (level 15) and malloc_trim(0) every 15 seconds in PumpRobloxMainThreadMessagesOnce.

* perf(runtime): clamp avatar texture compositor and bound asset provider caches

- Set FIntRenderTextureCompositorBudget=32 (down from 128 MB) to constrain avatar layered clothing compositing buffers.
- Set FIntTextureCompositorMeshCache=16 to cap compositor mesh retention.
- Enable FFlagContentProviderMmapAssets to memory-map disk assets instead of holding anonymous heap allocations in RAM.
- Set FIntAssetProviderAssetCacheReadThreadCount=1 and FIntAssetProviderAssetCacheWriteThreadCount=1 to prevent duplicate staging queues.
- Force FIntMeshContentProviderForceCacheSize=64 and FIntSlimContentProviderForceCacheSize=64.
- Set FIntInitialAudioAssetCacheSize=32 to bound audio cache buffers.

* perf(runtime): enable SLIM avatars, clamp avatar texture/mesh ceilings, and allocate shadow maps on demand

- Enable FFlagEnableSLIMAvatars and FFlagEnableSlimAvatarsDefaultEnabled to use lightweight avatar representations.
- Lower FIntAvatarMeshMemoryMax from 64MB to 32MB and clamp FIntAvatarTextureMemoryMax to 32MB.
- Enable FFlagLayeredClothingCacheOptimizations.
- Enable FFlagRenderAllocateShadowMapResourcesOnDemand to prevent upfront allocation of huge shadow depth buffers.
- Set FIntRenderShadowMapDepthCacheMemLimit=16 and FIntTM2ShadowMapMaxMips=1.
- Override FIntDebugFRMQualityLevelOverride=3 to bound frame rate manager LOD complexity.

* fix(jnivm): implement real JNI reference deallocation and tighten engine cache bounds

- Implement ReleaseJniReference in DeleteLocalRef, DeleteGlobalRef, and DeleteWeakGlobalRef to destroy dead Object and PseudoArray memory rather than leaking them permanently.
- Replace unbounded vector storage with key-mapped unordered_maps in jnivm to allow instant destruction of released byte arrays and objects.
- Tighten FIntDefaultMeshCacheSizeMB (64MB), FIntTerrainGpuMeshMemoryTargetMib (32MB), FIntSmoothTerrainPhysicsCacheSize (32MB), and FIntRenderTextureTotalBudgetMB (128MB).
- Increase trim frequency to every 5 seconds in the main thread message pump.

* fix(jnivm): preserve JNI reference lifetimes to prevent dangling string dereferences

- Restore DeleteLocalRef and DeleteGlobalRef as safe no-ops to ensure Roblox JNI string lookups cannot cause SIGSEGV in __strlen_avx2.

* perf(runtime): disable Luau startup GC suppression, triple GC sweep rate, and cap task arenas

- Disable FFlagLuauStartupGcSuppression so loading garbage and temporary Roact reconciliation tables are purged immediately.
- Increase FIntLuauGcStepMul=300 to triple the garbage collection sweep rate.
- Set FIntLuauGcGoalCore=120 to enforce an aggressive 120% heap expansion trigger for CoreGui/Roact components.
- Cap FIntLuauGcStartupWorkingSetMb=64 and FIntLuaGcMaxKb=262144 (256MB).
- Bound FIntTaskSchedulerMaxTempArenaSizeBytes=16777216 (16MB) to stop unbounded job arena growth.

* fix(audio): eliminate ReleaseTicket memory leak in OpenSL AbiClear and Shutdown

- Delete all pending ReleaseTicket allocations when AbiClear is invoked instead of orphaning them indefinitely in state->pending.
- Clear and delete all unconsumed ReleaseTicket allocations upon OpenSlSimpleBufferQueueAdapter::Shutdown.

* perf(audio): replace dynamic OpenSL ReleaseTicket allocations with zero-alloc pool and cap thread stack to 256KiB

- Preallocate ReleaseTicket array inside State sized to options.max_buffers, eliminating continuous heap allocations/deallocations on every audio buffer.
- Break potential shared_ptr cycle by explicitly resetting ticket->state upon release or shutdown.
- Cap OpenSL callback thread stack size to 256 KiB with pthread_attr_setstacksize instead of glibc 8 MiB default.

* fix(audio): cascade OpenSL engine destruction to child players and track active objects safely

- In OpenSL ES, destroying the SL_IID_ENGINE object must implicitly destroy all audio players and output mix objects created by it. Previously, child players were orphaned and leaked.
- Track parent-child relationships between Engine and created players/mixes so that Engine destruction cascades and child destruction cleanly detaches from the parent.
- Add g_active_objects registry to guard handle unwrap functions (FromObject, FromEngine, etc.) preventing use-after-free and double-destroy segfaults.
- Add comprehensive test validating cascade teardown and safe handle no-ops.

* perf(audio): bound sound memory and decode buffer caches

- Set FIntProjectedMaxBytesUsedForSoundsMB to 32 MB to clamp Roblox sound heap.
- Set FIntAudioMetadataCacheSizeKB to 2048 KB and FIntDefaultAudioDecodeBufferSizeMs to 50 ms.
- Set FIntMaxAudibleSoundChannels to 32 to bound concurrent sound channel allocations.

* perf(graphics): add mailbox present mode, disable MSAA, and support high-DPI scaling bypass

- Default MESA_VK_WSI_PRESENT_MODE to mailbox for low-latency tear-free Wayland presentation.
- Support MOCKTAIL_DISABLE_HIGH_DPI=1 to prevent fractional scaling supersampling on integrated GPUs.
- Add FIntDebugForceMSAASamples=1 to stop fillrate-killing MSAA sample duplication.
- Support MOCKTAIL_GRAPHICS_QUALITY override (defaults to 3, accepts 1..10 or 'auto'/'manual' to unlock the in-game Roblox settings slider).

* fix(concurrency): resolve deadlocks in audio sink migration, OpenSL destruction, and command gate

- Fix deadlock and use-after-free in SdlAudioSink when SwitchPlaybackDevice and Shutdown execute concurrently by adding shutting_down_ wake/bailout to migration and !switching_device_ condition to shutdown.
- Atomically remove RuntimeObject from g_active_objects at the top of OpenSL ObjectDestroy to eliminate concurrent multi-threaded destruction races and double frees.
- Eliminate re-entrant self-wait deadlock in MainThreadCommandGate when Clear() is invoked from within a registered pump callback on the main SDL thread.
- Ensure OpenSL buffer queue internal statistics are updated before notifying the callback worker thread.

* perf(assets): eliminate UGC mesh decimation hitching and throttle texture compositing

- Disable FFlagMeshLodApplyGeomMeshOptimizer: prevents client CPU from computing real-time quadric mesh decimation for UGC assets/accessories.
- Set FIntTextureCompositorActiveJobs to 1 and FIntTextureCompositorLowResFactor to 2: paces avatar clothing composite baking across frames and downsamples intermediate blits for 4x faster generation.
- Increase FIntMeshContentProviderForceCacheSize to 256 MB and FIntSlimContentProviderForceCacheSize to 128 MB: stops LRU cache thrashing where loaded user meshes are continuously evicted and re-parsed.

* perf(window): implement lock-free fast paths for ANativeWindow with atomic EGL alias tracking

- Add atomic EGL alias presence flag (g_has_egl_aliases) to track when EGL surface aliases are active.
- On standard Vulkan rendering paths (!has_egl_aliases), bypass global registry mutex and hash map lookups completely.
- Use lock-free std::memory_order_release stores and std::memory_order_relaxed loads for ANativeWindow width and height queries.

* Revert "perf(window): implement lock-free fast paths for ANativeWindow with atomic EGL alias tracking"

This reverts commit df8e40476ce737ec543edf0c79af1e9b9ef5c8ff.

* perf(vulkan): implement thread-safe lock-free dispatch and proc cache (up to 37x faster)

- Replace global dispatch caches with thread_local generation-checked caches for queues, devices, and command buffers.
- Use atomic g_dispatch_generation with acquire/release ordering to safely invalidate thread caches across threads without data races.
- Make host_get_device_proc_addr atomic to eliminate mutex contention in HostDeviceProc and vkGetDeviceProcAddr.
- Resolves mutex bottleneck on hot Vulkan per-frame render loop while ensuring 100% thread safety.

* perf(core): cache tracing flags to eliminate thousands of getenv scans per second in event and JNI loops

- Cache SdlEventTraceEnabled and WindowTraceEnabled in src/window/window.cc to avoid calling getenv 3 times per mouse motion and SDL event.
- Cache TraceEnabled and StringTraceEnabled in src/jnivm/jnivm.cc to avoid getenv calls on every JNI method/field lookup.

* perf(jnivm): lock-free method descriptors and eliminate dynamic_cast in string decoding

- Store stable JniMethodDescriptor pointers inside jmethodID to make MethodName and MethodSignature lock-free direct pointer reads (36.8ns -> 0.3ns, 100x+ faster).
- Eliminates taking g_jni_state_mutex 5 to 10 times per JNI method call.
- Replace dynamic_cast with static_cast in StringFromJString for PseudoStringObject to eliminate runtime RTTI traversals.

* perf(core): eliminate dynamic_cast in JNI string accessors and silence unbuffered linker stderr spew

- Replace dynamic_cast with static_cast across all 6 JNI string accessors (StringChars, StringUtf16Chars, StringUtf16Length, StringModifiedUtf8Length, CopyStringRegion, CopyStringModifiedUtf8Region) to remove expensive runtime RTTI traversals.
- Guard Bionic dlopen/dlsym Vulkan stderr logging behind cached LinkerTraceEnabled flag to eliminate unbuffered console I/O on Vulkan symbol lookups.

* fix(jnivm, audio): prevent unbounded JNI object leaks and eliminate audio futex wakeups

- Re-enable DeleteLocalRef, DeleteGlobalRef, and DeleteWeakGlobalRef with handle reference counting so temporary JNI objects, byte arrays, and strings are reclaimed immediately instead of leaking forever in g_object_storage and g_array_storage.
- Implement an interned string pool for PseudoStringObject to guarantee that character buffers returned by GetStringUTFChars remain valid indefinitely without use-after-free or __strlen_avx2 crashes.
- Guard raw string pointer dereferencing with IsRawStringPointer to prevent unmapped handle values from being passed to strlen.
- Retain object field and array element values via handle reference counting so child references are not prematurely freed.
- Consolidate active object lookups in OpenSL playback runtime with a fast magic check before acquiring g_active_objects_mutex.
- Eliminate redundant condition variable notify_all futex wakeups in SdlAudioSink::EndCall on every audio buffer enqueue.

* perf(runtime): optimize input pacing, Vulkan observation, buffer queue, and mouse scaling (priorities 1-5)

- [Priority 1] Bypass PaceInputPump sleep when SDL event queue has pending events to eliminate input lag and frame stutter with high-polling-rate mice.
- [Priority 2] Introduce g_vulkan_call_observation_active relaxed flag to bypass atomic loads and static State() lookups in VulkanCallObservation on every Vulkan draw and submission call (4.25x speedup).
- [Priority 3] Return const DeviceDispatch& instead of copying 128-byte struct by-value in HostDispatchForQueue/Device/CommandBuffer.
- [Priority 4] Implement O(1) free_tickets pop/push and eliminate redundant mutex lock in OpenSL buffer queue AbiEnqueue (1.58x speedup).
- [Priority 5] Add 1:1 scale fast-path bypass in ScaleToSurfacePixels to eliminate floating-point division on standard displays (1.73x speedup).

* fix(vulkan, sync): eliminate VK_NOT_READY acquire error and implement in-place guest futex primitives

- Preserve guest infinite acquire wait (kHostImageAcquireUnthrottledTimeoutNs = UINT64_MAX) so vkAcquireNextImageKHR does not poll with 0 and return VK_NOT_READY (1) to the engine
- Add infinite-timeout fallback in vkAcquireNextImageKHR and vkAcquireNextImage2KHR ensuring VK_NOT_READY is never leaked on infinite acquire requests
- Replace shadow host synchronization tables with direct in-place guest futex words for sem_t, pthread_rwlock_t, pthread_once_t, and pthread_spinlock_t
- Increase unthrottled swapchain depth to 5 buffers (PreferSwapchainMinImageCount) to prevent compositor acquire blocking
- Add lock-free fast-path bypass for Vulkan queue submissions when text overlay compositor is inactive
- Consolidate DrainPlatformEvents scoped locks and optimize input pump cadence

---------

Co-authored-by: glook9001 <310372091+glook9001@users.noreply.github.com>
Co-authored-by: komaruworld <rtysus228@gmail.com>
2026-08-28 14:52:34 +03:00
Obsidian 9bd5f97063 fix(window): stop the window from shrinking to an unpresentable size (#79)
Dragging the window down to its smallest size crashes the client (#74).
The report is on COSMIC, where the compositor lets a client with no
minimum size hint shrink until the surface is effectively invisible.

Nothing in the tree called SDL_SetWindowMinimumSize, so no floor was ever
advertised to the compositor. The window store already refuses to restore
a geometry below 160x120, so the live window could be dragged far below a
size the same code considers invalid to load.

Share those bounds and apply them to every window Mocktail creates. On
X11 the entry now carries "program specified minimum size: 160 by 120",
which is what a compositor clamps interactive resizing against; Wayland
uses the equivalent xdg_toplevel minimum.
2026-08-26 14:38:15 +03:00
Obsidian 8f63b9deba fix(app-policy): clear the regional "unchecked" badge in the desktop layout (#75)
ApplyDesktopAppPolicy pins a whole app policy through
FStringAppConfigurationOverrideAppPolicy so a later GUAC response cannot
replace the desktop layout. On a first launch that snapshot is the APK's
bundled GuacDefaultPolicy-GlobalDist.json, which ships
"ShowUncheckedBadge": true.

Because the override is permanent, the account's own GUAC policy never
gets a chance to correct it: on a signed-in profile the cached
GUAC:-1:app-policy stays byte-identical to the bundled default plus the
keys NormalizeDesktopLayout writes.

That field gates the badge rendered next to the display name in the
top-bar user-info widget and in the flyout profile card, labelled
CommonUI.Features.Label.Unchecked ("not verified"), so verified accounts
are permanently marked unverified. The desktop chrome has no such badge.
2026-08-26 14:36:30 +03:00
Haumea 272f61688b Add a FAQ.md (#72)
* Create FAQ.md

Frequently Asked Questions file so people can easily see well... the most Frequently Asked Questions

* Update README.md

Added a FAQ section to give the link to the file FAQ.md

* Update README.md

Idk but the link to FAQ.md so I kind of fixed it but idk is the same

* Update FAQ.md

Forgot to add the link to the only question

* Update FAQ.md

I think I fixed the link

* Update FAQ.md

I think I finally fixed the link after some tries LOL

* Update FAQ.md

Forgot english does not use ¿ lol

* Update FAQ.md

Im not going to even explain this
2026-08-26 11:04:13 +03:00
komaruworld f797ec7e40 Add Voice chat support
Beta
2026-08-26 02:29:12 +03:00
komaruworldandrosaproaka-jpg 02c8c6ca83 Fix readiness canary on nonfhs systems
Fixes #54

co-authored-by: rosaproaka-jpg <228791078+rosaproaka-jpg@users.noreply.github.com>
2026-08-26 02:29:12 +03:00
komaruworld 5af66cf8fc Add AnyLinux build
Close #31
2026-08-26 02:29:01 +03:00
glook9001andglook9001 b236c59b57 fix(input): fix mouse aiming, shooting refusal, key drops, and clean up test suite (#67)
* fix(input): fix mouse aiming, shooting refusal, key drops, and clean up test suite

- Remove spurious Wayland RMB release heuristic in window event loop that falsely synthesized releases during mouse drag
- Remove mouse motion lockout in WindowPointerCaptureOwner after releasing right-click
- Accumulate relative pointer coordinates and clamp discrete clicks to viewport bounds so shooting clicks land on crosshairs in pointer lock mode
- Implement mouse motion coalescing in PumpEvents to prevent high-polling rate mice from starving the event queue
- Add ASCII and fallback scancode mapping in MapSdlKeyToAndroid to avoid dropping unmapped keys
- Clean up obsolete static script-based regex tests and fix system install multi-arch test

* fix(input): prevent focus loss lockout from dropping WASD and auto-release text focus on click

- Automatically recover router focus when receiving active hardware input events so WASD/movement keys are never permanently dropped after Wayland/SDL focus loss transitions
- Release stale Roblox text-input session when clicking anywhere in the game viewport so Roblox Lua movement scripts don't remain in TextBox-focused state
- Allow mouse pointer capture when native lock is requested without gating on fragile focused_ flag

---------

Co-authored-by: glook9001 <310372091+glook9001@users.noreply.github.com>
2026-08-26 00:20:51 +03:00
glook9001andglook9001 386476eda6 fix(auth): fix login challenge interception and add clean first-launch sign-in flow (#66)
- Fix Roblox challenge URL detection in WebView bridge to catch generic challenges, 2FA, and Arkose URLs
- Prevent premature window closure on Android Play Integrity attestation failures
- Add pre-engine first-launch desktop sign-in prompt before creating Vulkan game window
- Ensure WebView helper receives cookie sync handshake on startup to begin page load
- Support dynamic session promotion and credential dispatch for guest runtime VM
- Add unit tests for generic challenge routing and guest credential promotion

Co-authored-by: glook9001 <310372091+glook9001@users.noreply.github.com>
2026-08-25 19:37:22 +03:00
techs-sus b6407e7d3a Fix TLS error in webview by adding glib-networking (#69)
Patch was taken from #59.

Closes: #59
2026-08-25 19:32:59 +03:00
techs-sus 21efe88b22 Add Nix development shell in flake and direnv support with 'use flake' (#58)
Direnv allows the project environment to be automatically applied when
the project is cd'd into.

Other notable changes:
- .gitignore: add .cache and .direnv generated by clangd and direnv respectively
2026-08-24 23:49:04 +03:00
Obsidian 0f635abc25 Cut first-install time by removing redundant payload hashing and copying (#41)
* Stop rehashing and recopying the payload during install

A first install prepares a ~635 MiB payload, then hashes the whole tree five
times and byte-copies it once more into the store. Four of those five passes
re-derive digests the same process just computed, under the same lock.

PreparePayloadFromArchives hashed the tree to write roblox_payload.json and
then immediately verified those hashes against the file it had just written.
PayloadStore::Stage hashed the same workspace again before copying it, and
Promote hashed the published directory a third time.

The guarantee that matters is unchanged: the bytes that land in the immutable
store are still hashed in full after the copy. Stage now records which payload
it verified, so promotion skips the repeat only for a payload this instance
staged; an operator-supplied ID, a directory left by an earlier run, or a fresh
process still gets the complete pass.

CopyTree now reflinks (FICLONE) or uses copy_file_range before falling back to
an ordinary copy, so on btrfs and xfs the staging copy costs no data movement
and no extra space. A hard link would be wrong here: CopyTree makes the result
read-only while the source stays writable.

File hashing moves from the portable foundation::Sha256 to OpenSSL, which
reaches the CPU's SHA extensions, and the asset tree hashes its files across
cores. Measured on this machine: 182 MB/s to 1518 MB/s, and the 1835-file asset
tree of Roblox 2.734.917 reproduces its recorded sha256_tree exactly.

MOCKTAIL_UPDATE_TRACE=1 now reports the elapsed time of each install stage, so
the breakdown is visible instead of inferred.

* Hide the system cursor as soon as the window regains focus

Roblox draws its own cursor while it owns the pointer, so Mocktail hides the
system one. Losing focus reveals it again, which is right, but regaining focus
only set the focused flag and left the re-apply to the next Pump().

Pump() runs at the top of PumpEvents(), before SDL_PollEvent delivers
SDL_EVENT_WINDOW_FOCUS_GAINED, so the desktop arrow stayed on screen for at
least one whole frame after every alt-tab back into the game, and longer
whenever the frame was long. OnFocusGained now pumps the pointer state in the
same call, mirroring OnFocusLost and OnRightButton, and its failure is reported
like the focus-loss path.

An active text field still keeps the system cursor, focused or not.

* Cover promotion of a payload the store did not stage

Stage() now lets promotion skip the repeat hash for a payload it just wrote,
so the boundary of that shortcut needs a test. Nothing covered it: the existing
corruption test rewrites roblox_payload.json, which the metadata check catches
on its own, and payload_store_test.sh exercises the shell store rather than
this class.

The new test corrupts only the content bytes, leaving the metadata describing
the original payload, then promotes from a second PayloadStore instance. It
fails if the shortcut is ever allowed to apply to a payload this instance did
not stage.

* Restore the full hash at the promotion gate

Skipping the rehash for a payload the same instance staged was wrong. The
readiness canaries run between Stage() and Promote(): they spawn the real
client against the staged directory, for up to 150 seconds, twice for a
probation candidate. Trusting the earlier hash across that window drops a
deliberate control, and payload_store_test.sh already spelled out why it is
there.

Promote() and PromoteProbation() call VerifyPreparedPayload() again, as before.
The instance no longer tracks what it staged.

The two tests now tamper with a staged payload from the instance that staged
it, which is the case that was missing: content bytes only, metadata left
describing the original payload, so nothing short of hashing the contents can
detect it. One covers the asset tree, the other libroblox.so.

Promotion still costs a complete pass over ~635 MiB, but OpenSSL and the
parallel asset hash make it 3.9 s -> 0.55 s. Stage is 9.5 s -> 0.93 s. A traced
install reports promote at 656 ms.
2026-08-24 16:22:35 +03:00
Savva 2f179068b3 Merge pull request #50 from techs-sus/main
Add Nix packaging
2026-08-24 16:16:24 +03:00
techs-sus 81b490e196 Add Nix packaging
- src/legacy/legacy_runtime.cc: only use symbols from stubs that own them
- src/legacy/legacy_runtime.cc: always use the android vulkan loader adapter to load vulkan
- src/update/readiness_canary.cc: expose env variables that affect the loading of dynamic libraries to the canary sandbox
- {flake.{nix, lock}, packaging/nix/package.nix}: initialize flake and nix package
- packaging/nix/package.nix: derive version from CMakeLists.txt, similar to the mocktail-git AUR package
2026-08-23 18:31:12 -04:00
komaruworld f2168e2f98 Maybe fix qr login
Refs #44
2026-08-22 17:06:46 +03:00
komaruworld 64a564f292 Fix text render
Fixes #21
2026-08-22 16:33:16 +03:00
komaruworldandlaskaidk 3728afa0d7 support roblox 2.734.917
fixes #20
fixes #39

co-authored-by: laskaidk <teresamnegea@proton.me>
2026-08-22 16:03:41 +03:00
komaruworld 931543e976 Fix login issue, i think
Add WebKit login

Fixes #9

Fixes #13

Fixes #19
2026-08-22 16:03:41 +03:00
komaruworld a2a210da87 Add theme: to config
#33
2026-08-22 16:03:41 +03:00
Savva b289e7933e Merge pull request #46 from qutad/fix/display-scale-mouse-coordinates
Scale mouse input for high-density windows
2026-08-22 16:02:22 +03:00
caly 497cf9ea6c Scale mouse input for high-density windows 2026-08-22 15:32:27 +03:00
Savva 0b7326d8d1 Merge pull request #42 from Gur0v/main
Update README.md
2026-08-21 19:17:01 +03:00
Gurov e312ab6667 Update README.md 2026-08-21 19:13:42 +03:00
Savva b8e535f6f9 Merge pull request #40 from Twig6943/main
Add roblox to the .desktop file
2026-08-21 12:10:23 +03:00
Twig6943 c0f6787cc6 So that it shows up when you search roblox 2026-08-21 10:40:09 +03:00
Savva f0e0229ec5 Merge pull request #34 from LightZirconite/fix/update-provider-diagnostics
Fix/update provider diagnostics
2026-08-20 22:44:24 +03:00
LightZirconite 56749bbdc8 Report drift in the pinned Roblox payload metadata
Two kinds of drift only surface when a user hits them. The compatibility
catalog falls behind what the provider publishes, so new installs land on
a version Roblox has since retired; and the preferred supported profile
loses its pinned bootstrap source, so the pinned bootstrap path cannot
produce the version the updater falls back to.

Add a checker that reports both, and run the existing script unit tests in
CI, where they were never executed.

Run against this tree the checker already reports real drift: the
preferred profile 2.727.1199 has no pinned bootstrap source. Pinning one
means downloading and hashing the archive, and a newer profile also needs
a graphics canary on real hardware, so this reports the condition rather
than pretending to fix it.

The checker stays a plain script that anyone can run on demand; no
scheduled workflow is added.
2026-08-20 14:25:04 +02:00
LightZirconite 9da2c02836 Resolve APK providers through a replaceable chain
The coordinator constructed ApkPureProvider directly in three places and
rejected every other configuration with "native updater supports only the
direct APKPure provider". A single unreachable host therefore failed the
whole update with no way to try anything else, and the reported error named
neither the provider nor the host: on 2026-08-18 api.pureapk.com answered
503 to every request, including metadata for unrelated packages, and a
first launch could only say "Roblox update preflight failed".

Introduce an ApkProvider interface and a ProviderChain that consults
providers in order, returns the first answer, and reports every failure
with the provider that produced it when none answer. Each provider
downloads into its own directory, so a failed attempt cannot block the
next. Adding a source is now one line in BuildProviderChain instead of a
change spread across the coordinator.

The chain currently holds APKPure alone. The repository already ships a
pinned Uptodown provider as a build script, but it no longer works against
Uptodown's current pages, so wiring it in would add a second way to fail
rather than a fallback; that is reported separately.

Payload receipts now record which provider produced an archive, and
updates.source accepts auto alongside apk-pure so existing configuration
files keep working.
2026-08-19 10:41:11 +02:00
LightZirconite b5eac34e0f Report why the Roblox update preflight failed
A first launch that cannot reach the APK provider only ever showed
"Mocktail could not update or verify the Roblox installation.", while the
actual cause stayed in the session log. For a user that is
indistinguishable between a provider outage, a rejected signature, and a
full disk.

Name the host in HTTP transfer failures, so "HTTPS request returned status
503" becomes "api.pureapk.com request returned status 503", and relay the
updater child's stderr through the launcher: every line still reaches the
session log, and the last failure it reported is now attached to the
preflight result. The failure dialog shows that reason and the session log
directory instead of the generic sentence.
2026-08-19 10:40:55 +02:00
komaruworld e4c64c2fec Add ca_bundle: to config
#18
2026-08-18 22:35:27 +03:00
komaruworld ba6287250f Fix rpm build
test on docker image with fedora 44; fix #25
2026-08-18 22:12:13 +03:00
komaruworld 4b437f623f Add flag --force-run-latest
#20
2026-08-17 14:18:34 +03:00
komaruworld e161fecfc5 Another fix for low quality texture 2026-08-17 00:01:35 +03:00
komaruworld dcf22a144b Fix skybox issues
transcode etc1 skybox textures to bc1
2026-08-16 22:05:20 +03:00
komaruworld 0bc8823644 Add systemproxy auto detect 2026-08-16 22:04:43 +03:00
komaruworld f9a48cfe83 make builds stable 2026-08-16 17:39:25 +03:00
komaruworld 52721bf06c make builds stable 2026-08-16 17:32:27 +03:00
komaruworld 9bbd2a1ebf Update mocktail-bin for 1.0.3 2026-08-16 14:31:40 +03:00
komaruworld 9cedf389a7 Release 1.0.3 2026-08-16 14:20:31 +03:00
komaruworld bbe454641b Automate mocktail-git AUR updates 2026-08-16 14:07:45 +03:00
komaruworld 097f782694 Fix ci and delete And remove legacy copyright
Also old project name
2026-08-16 04:19:32 +03:00
komaruworld 2a154b6f58 Delete use cookie from sober
Close #11
2026-08-16 03:25:01 +03:00
komaruworld 0b8cbef447 add message if cookies return a 401 response 2026-08-16 01:56:32 +03:00
komaruworld f1d4a05051 Add sounds fix docs
Close #5 and thx @TheRealUwie for docs
2026-08-16 00:45:59 +03:00
komaruworld f4e9349f46 Add FFlags.json file
closes #7
2026-08-15 23:59:05 +03:00
komaruworld 63070b6462 Fix first install issue
#6
2026-08-15 23:02:35 +03:00
komaruworld fb5f38cb40 Add experimental FreeBSD support
#5
2026-08-15 22:39:52 +03:00
komaruworld fa370b9dbb Update AUR packages for 1.0.2 2026-08-14 21:11:34 +03:00
589 changed files with 39395 additions and 40106 deletions

No files matched your search

+1
View File
@@ -0,0 +1 @@
use flake
+1
View File
@@ -1,3 +1,4 @@
# Preserve exact upstream whitespace in the provenance-locked Android core
# snapshot. Local Mocktail sources remain subject to git diff --check.
third_party/mcpelauncher-linker/core/** -whitespace
packaging/freebsd/*.patch -whitespace
+103
View File
@@ -0,0 +1,103 @@
# Modified by vii from komaruworld/mocktail. See README "About this fork".
name: Bug report
description: Report a reproducible problem in Nightcap
title: "[Bug]: "
labels:
- bug
assignees: []
body:
- type: markdown
attributes:
value: |
Please include enough detail for someone else to reproduce the bug.
Use any language you are comfortable with. The maintainer can read Russian and English directly, so either is preferred. If you are more comfortable in another language, use it.
Please write issues and pull requests yourself. Do not use AI-generated text.
- type: input
id: version
attributes:
label: Mocktail version
description: Provide the release version, package version, or commit hash.
placeholder: "For example: v0.4.0 or 1a2b3c4"
validations:
required: true
- type: input
id: system
attributes:
label: System information
description: Include your distribution, desktop session, GPU, and graphics driver when relevant.
placeholder: "For example: Arch Linux, KDE Plasma 6 on Wayland, AMD RX 6600, Mesa 26.1"
validations:
required: true
- type: input
id: installation
attributes:
label: Installation method
description: Tell us how Mocktail was installed or built.
placeholder: "For example: AppImage, Flatpak, AUR, or built from source"
validations:
required: true
- type: textarea
id: problem
attributes:
label: What happened?
description: Describe the problem and its impact.
validations:
required: true
- type: textarea
id: reproduction
attributes:
label: Steps to reproduce
description: List the smallest reliable sequence of actions that triggers the problem.
placeholder: |
1. Start Mocktail with ...
2. Open ...
3. Select ...
4. Observe ...
validations:
required: true
- type: textarea
id: expected
attributes:
label: Expected behavior
description: Explain what you expected to happen instead.
validations:
required: true
- type: markdown
attributes:
value: |
Latest log:
- Flatpak: `~/.var/app/io.github.CoderDayton.nightcap/.local/state/mocktail/logs/latest.log`
- Other installs: `~/.local/state/mocktail/logs/latest.log`
Older logs are in the `sessions` directory next to `latest.log`.
- type: textarea
id: logs
attributes:
label: Logs
description: Paste the relevant part of latest.log or the terminal output.
render: shell
- type: textarea
id: additional-context
attributes:
label: Additional context
description: Add screenshots, videos, workarounds, or any other useful details.
- type: checkboxes
id: checks
attributes:
label: Before submitting
options:
- label: I searched the existing issues and did not find a duplicate.
required: true
+2
View File
@@ -0,0 +1,2 @@
blank_issues_enabled: true
contact_links: []
@@ -0,0 +1,51 @@
name: Feature request
description: Suggest an improvement or a new capability for Mocktail
title: "[Feature]: "
labels:
- enhancement
assignees: []
body:
- type: markdown
attributes:
value: |
Describe what you want to change and why it would be useful.
Use any language you are comfortable with. The maintainer can read Russian and English directly, so either is preferred. If you are more comfortable in another language, use it.
Please write issues and pull requests yourself. Do not use AI-generated text.
- type: textarea
id: problem
attributes:
label: Problem or use case
description: What are you trying to do, and what makes it difficult today?
validations:
required: true
- type: textarea
id: proposal
attributes:
label: Proposed solution
description: Describe how you would like Mocktail to behave.
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Alternatives considered
description: Describe any workarounds or alternative designs you have considered.
- type: textarea
id: additional-context
attributes:
label: Additional context
description: Add mockups, screenshots, links, or implementation notes if useful.
- type: checkboxes
id: checks
attributes:
label: Before submitting
options:
- label: I searched the existing issues and did not find a duplicate.
required: true
+3
View File
@@ -0,0 +1,3 @@
<!-- Briefly describe what this pull request adds, changes, or removes. Write it yourself and do not use AI-generated text. Link the issue with "Closes #123" or "Fixes #123". -->
Closes #
+56
View File
@@ -0,0 +1,56 @@
name: Check AUR packages
on:
workflow_dispatch:
permissions:
contents: read
concurrency:
group: aur-packages
cancel-in-progress: true
jobs:
metadata:
name: Refresh AUR metadata
runs-on: ubuntu-24.04
timeout-minutes: 15
steps:
- name: Check out source
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
- name: Prepare AUR metadata
shell: bash
run: |
set -Eeuo pipefail
package_dir="$GITHUB_WORKSPACE/packaging/aur/nightcap-git"
version="$(git describe --tags --abbrev=0 --match='v[0-9]*')"
version="${version#v}"
[[ "$version" =~ ^[0-9]+(\.[0-9]+)*$ ]]
pkgver="${version}.r$(git rev-list --count HEAD).g$(git rev-parse --short=7 HEAD)"
sed -i "s/^pkgver=.*/pkgver=${pkgver}/" "$package_dir/PKGBUILD"
docker run --rm \
--volume "$package_dir:/pkg" \
archlinux:base-devel bash -Eeuo pipefail -c '
useradd --create-home builder
install -d -o builder -g builder /tmp/nightcap-git
install -o builder -g builder -m 0644 \
/pkg/PKGBUILD /tmp/nightcap-git/PKGBUILD
runuser -u builder -- bash -c \
"cd /tmp/nightcap-git && makepkg --printsrcinfo" \
> /pkg/.SRCINFO
'
grep -Fqx "pkgver=${pkgver}" "$package_dir/PKGBUILD"
grep -Fqx $'\tpkgver = '"${pkgver}" "$package_dir/.SRCINFO"
- name: Upload AUR metadata
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: Nightcap-AUR
path: packaging/aur/nightcap-git/.SRCINFO
if-no-files-found: error
retention-days: 14
+7
View File
@@ -52,9 +52,16 @@ jobs:
find config packaging/flatpak -type f -name '*.json' -print0 \
| xargs -0 -n1 python3 -m json.tool >/dev/null
- name: Run script unit tests
shell: bash
run: |
python3 -m pip install --disable-pip-version-check requests
python3 -m unittest discover -s tests -p '*_test.py' -v
- name: Verify build and packaging contracts
shell: bash
run: |
bash tests/managed_payload_build_contract_test.sh .
bash tests/flatpak_packaging_test.sh .
bash tests/native_packaging_test.sh .
bash tests/anylinux_dependencies_test.sh .
+67 -68
View File
@@ -1,9 +1,9 @@
# Modified by vii from komaruworld/mocktail. See README "About this fork".
name: Flatpak
on:
workflow_run:
workflows: [Native packages]
types: [completed]
push:
tags: ['v*']
pull_request:
paths:
- '.github/workflows/flatpak.yml'
@@ -15,13 +15,11 @@ on:
- 'config/**'
- 'include/**'
- 'packaging/flatpak/**'
- 'packaging/discord-join.html'
- 'packaging/icons/**'
- 'packaging/space.bigrat.mocktail.desktop'
- 'packaging/space.bigrat.mocktail.metainfo.xml'
- 'packaging/space.bigrat.mocktail.svg'
- 'packaging/io.github.CoderDayton.nightcap.desktop'
- 'packaging/io.github.CoderDayton.nightcap.metainfo.xml'
- 'packaging/io.github.CoderDayton.nightcap.svg'
- 'scripts/assemble_flatpak_pages.sh'
- 'scripts/assemble_native_repositories.sh'
- 'site/**'
- 'src/**'
- 'stubs/**'
@@ -29,61 +27,28 @@ on:
workflow_dispatch:
permissions:
actions: read
contents: read
concurrency:
group: flatpak-${{ github.event.workflow_run.head_sha || github.ref }}
cancel-in-progress: true
group: flatpak-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
flatpak:
name: Flatpak build
if: >-
github.event_name != 'workflow_run' ||
(github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.event != 'pull_request' &&
github.event.workflow_run.head_branch == 'main')
runs-on: ubuntu-24.04
timeout-minutes: 120
env:
MOCKTAIL_PUBLISH: ${{ (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event != 'pull_request' && github.event.workflow_run.head_branch == 'main') || (github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main') }}
MOCKTAIL_SOURCE_SHA: ${{ github.event.workflow_run.head_sha || github.sha }}
# Publish on release tags and on manual runs from main. Pull requests
# only verify that the manifest still builds.
MOCKTAIL_PUBLISH: ${{ startsWith(github.ref, 'refs/tags/v') || (github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main') }}
steps:
- name: Check out source
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
ref: ${{ github.event.workflow_run.head_sha || github.sha }}
submodules: recursive
- name: Download package repositories
if: env.MOCKTAIL_PUBLISH == 'true'
shell: bash
env:
GH_TOKEN: ${{ github.token }}
run: |
set -Eeuo pipefail
run_id="${{ github.event.workflow_run.id }}"
if [[ -z "$run_id" ]]; then
run_id="$(gh run list \
--workflow packages.yml \
--branch main \
--event push \
--status success \
--limit 1 \
--json databaseId \
--jq '.[0].databaseId')"
fi
[[ -n "$run_id" ]]
gh run download "$run_id" \
--name Mocktail-native-repositories \
--dir native-repositories
[[ -d native-repositories/apt && \
-d native-repositories/rpm && \
-d native-repositories/downloads && \
-s native-repositories/mocktail-packages.gpg ]]
- name: Install Flatpak builder
shell: bash
run: |
@@ -99,22 +64,23 @@ jobs:
FLATPAK_GPG_KEY_ID: ${{ secrets.FLATPAK_GPG_KEY_ID }}
run: |
set -Eeuo pipefail
git update-ref refs/heads/main "$MOCKTAIL_SOURCE_SHA"
# The manifest builds the local checkout from refs/heads/main.
git update-ref refs/heads/main "$GITHUB_SHA"
builder_signing=()
bundle_keys=()
if [[ "$MOCKTAIL_PUBLISH" == true ]]; then
[[ -n "$FLATPAK_GPG_PRIVATE_KEY" && \
-n "$FLATPAK_GPG_KEY_ID" ]]
install -d -m 0700 "$RUNNER_TEMP/mocktail-gnupg"
install -d -m 0700 "$RUNNER_TEMP/nightcap-gnupg"
printf '%s' "$FLATPAK_GPG_PRIVATE_KEY" | \
gpg --batch --homedir "$RUNNER_TEMP/mocktail-gnupg" --import
gpg --batch --homedir "$RUNNER_TEMP/mocktail-gnupg" \
--export "$FLATPAK_GPG_KEY_ID" >mocktail-flatpak.gpg
gpg --batch --homedir "$RUNNER_TEMP/nightcap-gnupg" --import
gpg --batch --homedir "$RUNNER_TEMP/nightcap-gnupg" \
--export "$FLATPAK_GPG_KEY_ID" >nightcap-flatpak.gpg
builder_signing=(
"--gpg-sign=$FLATPAK_GPG_KEY_ID"
"--gpg-homedir=$RUNNER_TEMP/mocktail-gnupg"
"--gpg-homedir=$RUNNER_TEMP/nightcap-gnupg"
)
bundle_keys=(--gpg-keys=mocktail-flatpak.gpg)
bundle_keys=(--gpg-keys=nightcap-flatpak.gpg)
fi
flatpak-builder \
--arch=x86_64 \
@@ -127,34 +93,33 @@ jobs:
--repo=repo \
"${builder_signing[@]}" \
build-flatpak-ci \
packaging/flatpak/space.bigrat.mocktail.json
packaging/flatpak/io.github.CoderDayton.nightcap.json
if [[ "$MOCKTAIL_PUBLISH" == true ]]; then
flatpak build-update-repo \
--generate-static-deltas \
--static-delta-jobs=2 \
--prune \
--gpg-sign="$FLATPAK_GPG_KEY_ID" \
--gpg-homedir="$RUNNER_TEMP/mocktail-gnupg" \
--gpg-homedir="$RUNNER_TEMP/nightcap-gnupg" \
repo
fi
flatpak build-bundle \
--arch=x86_64 \
"${bundle_keys[@]}" \
repo \
Mocktail-x86_64.flatpak \
space.bigrat.mocktail \
Nightcap-x86_64.flatpak \
io.github.CoderDayton.nightcap \
stable
if [[ "$MOCKTAIL_PUBLISH" == true ]]; then
./scripts/assemble_flatpak_pages.sh \
repo Mocktail-x86_64.flatpak mocktail-flatpak.gpg \
public native-repositories
repo Nightcap-x86_64.flatpak nightcap-flatpak.gpg public
fi
- name: Upload Flatpak
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: Mocktail-x86_64-flatpak
path: Mocktail-x86_64.flatpak
name: Nightcap-x86_64-flatpak
path: Nightcap-x86_64.flatpak
if-no-files-found: error
retention-days: 14
@@ -170,13 +135,7 @@ jobs:
deploy-pages:
name: Publish Flatpak repository
if: >-
(github.event_name == 'workflow_run' &&
github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.event != 'pull_request' &&
github.event.workflow_run.head_branch == 'main') ||
(github.event_name == 'workflow_dispatch' &&
github.ref == 'refs/heads/main')
if: startsWith(github.ref, 'refs/tags/v') || (github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main')
needs: flatpak
runs-on: ubuntu-24.04
timeout-minutes: 10
@@ -191,3 +150,43 @@ jobs:
- name: Deploy GitHub Pages
id: deployment
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0
release:
name: Attach bundle to GitHub release
if: startsWith(github.ref, 'refs/tags/v')
needs: flatpak
runs-on: ubuntu-24.04
timeout-minutes: 15
permissions:
contents: write
steps:
- name: Check out source
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Download Flatpak
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: Nightcap-x86_64-flatpak
path: release
- name: Upload to release
shell: bash
env:
GH_TOKEN: ${{ github.token }}
run: |
set -Eeuo pipefail
tag="${GITHUB_REF_NAME}"
(cd release && sha256sum Nightcap-x86_64.flatpak > Nightcap-x86_64.flatpak.sha256)
./scripts/release_notes.sh "$tag" > release/notes.md
# The Release workflow creates the same release; whichever job
# arrives second sees "already exists" and continues.
if ! gh release view "$tag" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
gh release create "$tag" --repo "$GITHUB_REPOSITORY" \
--title "Nightcap $tag" --notes-file release/notes.md ||
gh release view "$tag" --repo "$GITHUB_REPOSITORY" >/dev/null
fi
gh release edit "$tag" --repo "$GITHUB_REPOSITORY" \
--title "Nightcap $tag" --notes-file release/notes.md
gh release upload "$tag" --repo "$GITHUB_REPOSITORY" --clobber \
release/Nightcap-x86_64.flatpak \
release/Nightcap-x86_64.flatpak.sha256
+126 -248
View File
@@ -1,39 +1,47 @@
name: Native packages
name: Nightly AppImage
on:
push:
branches: [main]
paths-ignore:
- '.github/workflows/ci.yml'
- '.gitignore'
- 'README.md'
- 'assets/screenshots/**'
- 'docs/**'
- 'packaging/aur/**'
- 'scripts/build_flatpak.sh'
- 'tests/**'
pull_request:
paths-ignore:
- '.github/workflows/ci.yml'
- '.gitignore'
- 'README.md'
- 'assets/screenshots/**'
- 'docs/**'
- 'packaging/aur/**'
- 'scripts/build_flatpak.sh'
- 'tests/**'
schedule:
# 05:00 UTC daily. A run whose commit already carries the continuous tag
# exits without rebuilding, so a quiet day costs one probe job.
- cron: '0 5 * * *'
workflow_dispatch:
permissions:
contents: read
concurrency:
group: packages-${{ github.ref }}
cancel-in-progress: true
group: nightly-appimage
cancel-in-progress: false
jobs:
probe:
name: Check for new commits
runs-on: ubuntu-24.04
timeout-minutes: 5
outputs:
build: ${{ steps.decide.outputs.build }}
steps:
- name: Compare HEAD against the published continuous tag
id: decide
shell: bash
env:
GH_TOKEN: ${{ github.token }}
run: |
set -Eeuo pipefail
published="$(gh api "repos/$GITHUB_REPOSITORY/git/ref/tags/continuous" \
--jq .object.sha 2>/dev/null || true)"
if [[ "$GITHUB_EVENT_NAME" == "schedule" && "$published" == "$GITHUB_SHA" ]]; then
echo 'build=false' >>"$GITHUB_OUTPUT"
echo "continuous already points at $GITHUB_SHA; nothing to build"
else
echo 'build=true' >>"$GITHUB_OUTPUT"
fi
appimage:
name: AppImage (Ubuntu 26.04)
name: AppImage (AnyLinux)
needs: probe
if: needs.probe.outputs.build == 'true'
runs-on: ubuntu-24.04
timeout-minutes: 90
steps:
@@ -45,205 +53,110 @@ jobs:
- name: Build AppImage
shell: bash
env:
GH_TOKEN: ${{ github.token }}
run: |
docker run --rm \
--volume "$GITHUB_WORKSPACE:/workspace" \
--workdir /workspace \
ubuntu:26.04 bash -Eeuo pipefail -c '
export DEBIAN_FRONTEND=noninteractive
apt-get update
apt-get install --yes --no-install-recommends \
build-essential cmake git ninja-build pkg-config \
libsdl3-dev libsdl3-ttf-dev libcurl4-openssl-dev \
libssl-dev nlohmann-json3-dev libyaml-dev libelf-dev \
libminizip-dev libcapstone-dev libgtk-4-dev \
libadwaita-1-dev libwebkitgtk-6.0-dev libutf8proc-dev \
libfontconfig1-dev libegl-dev libvulkan-dev zlib1g-dev \
libplacebo-dev \
bubblewrap xdg-dbus-proxy patchelf jq unzip file curl \
ca-certificates default-jdk-headless libgif7 python3 \
python3-capstone python3-yaml python3-requests \
python3-urllib3 python3-certifi python3-charset-normalizer \
python3-idna squashfs-tools
--env GH_TOKEN \
ghcr.io/archlinux/archlinux:base-devel@sha256:c3953446016873a756fb7814f10087cac1634c127f7302e80c81c512da796d05 bash -Eeuo pipefail -c '
pacman -Syu --noconfirm --needed \
cmake git ninja pkgconf lld sdl3 sdl3_ttf curl openssl \
nlohmann-json libyaml libelf minizip minizip-ng capstone gtk4 \
libadwaita webkitgtk-6.0 libutf8proc fontconfig libglvnd \
vulkan-headers zlib libplacebo bubblewrap xdg-dbus-proxy \
patchelf jq unzip file ca-certificates dbus zsync
# Reduced ABI-compatible packages belong only in this builder.
# The native updater does not require a JRE or Android SDK.
./scripts/install_anylinux_dependencies.sh
curl --fail --location --retry 3 \
--output /tmp/quick-sharun \
https://raw.githubusercontent.com/pkgforge-dev/Anylinux-AppImages/2a025cef14483c5bc2a75b479071f4d5c58a906f/useful-tools/quick-sharun.sh
echo "fa66d0189e1e87df781c049dfb23cbb1e753786056f693a4483d6d3af61e7ee1 /tmp/quick-sharun" | \
sha256sum --check --strict
chmod 0755 /tmp/quick-sharun
curl --fail --location --retry 3 \
--output /tmp/sharun \
https://github.com/pkgforge-dev/Anylinux-sharun/releases/download/2.2.6/sharun-x86_64
echo "a4d51752f9861b4870ca529fea04087310b28f8943cc55b99a95b777d4db92ec /tmp/sharun" | \
sha256sum --check --strict
chmod 0755 /tmp/sharun
curl --fail --location --retry 3 \
--output /tmp/anylinux.c \
https://raw.githubusercontent.com/pkgforge-dev/Anylinux-AppImages/2a025cef14483c5bc2a75b479071f4d5c58a906f/useful-tools/lib/anylinux.c
echo "f50650ad96d177559bdd0737df509fa20bd4af08887631c738fc1f31bfac185f /tmp/anylinux.c" | \
sha256sum --check --strict
curl --fail --location --retry 3 \
--output /tmp/appimagetool \
https://github.com/AppImage/appimagetool/releases/download/1.9.1/appimagetool-x86_64.AppImage
echo "ed4ce84f0d9caff66f50bcca6ff6f35aae54ce8135408b3fa33abfc3cb384eb0 /tmp/appimagetool" | \
https://github.com/pkgforge-dev/appimagetool/releases/download/0.3.3/appimagetool-x86_64-linux
echo "0d01a4e2628efc897be9663f0faeed6a171e1851270b4e1ce4c22434a36ecebd /tmp/appimagetool" | \
sha256sum --check --strict
install -m 0755 /tmp/appimagetool /usr/local/bin/appimagetool
chmod 0755 /tmp/appimagetool
curl --fail --location --retry 3 \
--output /tmp/android-build-tools.zip \
https://dl.google.com/android/repository/build-tools_r35_linux.zip
echo "bd3a4966912eb8b30ed0d00b0cda6b6543b949d5ffe00bea54c04c81e1561d88 /tmp/android-build-tools.zip" | \
--output /tmp/uruntime \
https://github.com/VHSgunzo/uruntime/releases/download/v0.5.9/uruntime-appimage-dwarfs-lite-x86_64
echo "cef962c299f2fa19b2b3cdf2fa1565ee8541796cc89b9a97a591f94041e8b083 /tmp/uruntime" | \
sha256sum --check --strict
unzip -q /tmp/android-build-tools.zip -d /tmp/android-build-tools
android_tools_root="$(dirname -- "$(find /tmp/android-build-tools -type f -name apksigner -print -quit)")"
[[ -n "$android_tools_root" ]]
chmod 0755 /tmp/uruntime
curl --fail --location --retry 3 \
--output /tmp/mkdwarfs \
https://github.com/mhx/dwarfs/releases/download/v0.15.6/dwarfs-universal-0.15.6-Linux-x86_64
echo "50891c38ba359db8271819a6cbf6aaa8068681523f0c4f2b8242007a45edaa28 /tmp/mkdwarfs" | \
sha256sum --check --strict
chmod 0755 /tmp/mkdwarfs
git config --global --add safe.directory /workspace
git config --global --add safe.directory /workspace/third_party/Vulkan-Headers
git config --global --add safe.directory /workspace/third_party/libjnivm
export APPIMAGE_EXTRACT_AND_RUN=1
export MOCKTAIL_ANDROID_BUILD_TOOLS_ROOT="$android_tools_root"
# Install into the disposable container /usr before deployment.
export MOCKTAIL_ANYLINUX_SYSTEM_INSTALL=1
export MOCKTAIL_ANYLINUX_SHARUN=/tmp/sharun
export MOCKTAIL_ANYLINUX_C_SOURCE=/tmp/anylinux.c
export RUNTIME=/tmp/uruntime
export DWARFS_CMD=/tmp/mkdwarfs
make appimage BUILD_DIR=build-appimage JOBS=4 \
APPIMAGE=dist/Mocktail-x86_64.AppImage \
PORTABLE_MODE=standalone
PORTABLE_MODE=standalone APPIMAGE_FORMAT=anylinux \
ANYLINUX_PACKAGER=/tmp/quick-sharun \
ANYLINUX_APPIMAGETOOL=/tmp/appimagetool
test -x dist/Mocktail-x86_64.AppImage
'
- name: Smoke-test AppImage without FUSE
shell: bash
run: |
smoke_dir="$(mktemp -d)"
trap 'rm -rf -- "$smoke_dir"' EXIT
export XDG_DATA_HOME="$smoke_dir/data"
export XDG_CONFIG_HOME="$smoke_dir/config"
export XDG_CACHE_HOME="$smoke_dir/cache"
export XDG_STATE_HOME="$smoke_dir/state"
mkdir -p "$XDG_DATA_HOME" "$XDG_CONFIG_HOME" "$XDG_CACHE_HOME" "$XDG_STATE_HOME"
timeout 60s dist/Mocktail-x86_64.AppImage --appimage-extract-and-run --help
timeout 60s dist/Mocktail-x86_64.AppImage --appimage-extract-and-run mocktail_updater status
- name: Upload AppImage
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: Mocktail-AppImage
name: Nightcap-AppImage
path: dist/Mocktail-x86_64.AppImage
if-no-files-found: error
retention-days: 14
deb:
name: DEB (Ubuntu 26.04)
runs-on: ubuntu-24.04
timeout-minutes: 45
steps:
- name: Check out source
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
submodules: recursive
- name: Build DEB
shell: bash
run: |
nightly_suffix=".nightly.$(git show -s --format=%cd --date=format:%Y%m%d%H%M%S "$GITHUB_SHA").g${GITHUB_SHA:0:8}"
docker run --rm \
--volume "$GITHUB_WORKSPACE:/workspace" \
--workdir /workspace \
--env MOCKTAIL_PACKAGE_VERSION_SUFFIX="$nightly_suffix" \
ubuntu:26.04 bash -Eeuo pipefail -c '
export DEBIAN_FRONTEND=noninteractive
apt-get update
apt-get install --yes --no-install-recommends \
build-essential cmake git ninja-build pkg-config fakeroot \
dpkg-dev file libsdl3-dev libsdl3-ttf-dev \
libcurl4-openssl-dev libssl-dev nlohmann-json3-dev \
libyaml-dev libelf-dev libminizip-dev libcapstone-dev \
libgtk-4-dev libadwaita-1-dev libwebkitgtk-6.0-dev \
libutf8proc-dev libfontconfig1-dev libegl-dev \
libvulkan-dev libplacebo-dev zlib1g-dev
cmake -S . -B build-deb -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=/usr \
-DMOCKTAIL_PACKAGE_NAME=mocktail-nightly \
-DMOCKTAIL_PACKAGE_VERSION_SUFFIX="$MOCKTAIL_PACKAGE_VERSION_SUFFIX" \
-DBUILD_TESTING=OFF
cmake --build build-deb --parallel 4
cpack --config build-deb/CPackConfig.cmake \
-G DEB -B dist/deb
dpkg-deb --info dist/deb/*.deb >/dev/null
'
- name: Upload DEB
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: Mocktail-DEB
path: dist/deb/*.deb
if-no-files-found: error
retention-days: 14
rpm:
name: RPM (Fedora 44)
runs-on: ubuntu-24.04
timeout-minutes: 45
steps:
- name: Check out source
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
submodules: recursive
- name: Build RPM
shell: bash
run: |
nightly_suffix=".nightly.$(git show -s --format=%cd --date=format:%Y%m%d%H%M%S "$GITHUB_SHA").g${GITHUB_SHA:0:8}"
docker run --rm \
--volume "$GITHUB_WORKSPACE:/workspace" \
--workdir /workspace \
--env MOCKTAIL_PACKAGE_VERSION_SUFFIX="$nightly_suffix" \
fedora:44 bash -Eeuo pipefail -c '
dnf install --assumeyes \
gcc-c++ cmake git ninja-build pkgconf-pkg-config rpm-build \
SDL3-devel SDL3_ttf-devel libcurl-devel openssl-devel \
nlohmann-json-devel libyaml-devel elfutils-libelf-devel \
minizip-ng-compat-devel capstone-devel gtk4-devel libadwaita-devel \
webkitgtk6.0-devel utf8proc-devel fontconfig-devel \
libglvnd-devel vulkan-headers vulkan-loader-devel \
libplacebo-devel zlib-ng-compat-devel
cmake -S . -B build-rpm -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=/usr \
-DMOCKTAIL_PACKAGE_NAME=mocktail-nightly \
-DMOCKTAIL_PACKAGE_VERSION_SUFFIX="$MOCKTAIL_PACKAGE_VERSION_SUFFIX" \
-DBUILD_TESTING=OFF
cmake --build build-rpm --parallel 4
cpack --config build-rpm/CPackConfig.cmake \
-G RPM -B dist/rpm
rpm --query --package --info dist/rpm/*.rpm >/dev/null
'
- name: Upload RPM
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: Mocktail-RPM
path: dist/rpm/*.rpm
if-no-files-found: error
retention-days: 14
arch:
name: Pacman package (Arch Linux)
runs-on: ubuntu-24.04
timeout-minutes: 45
steps:
- name: Check out source
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
submodules: recursive
- name: Build pacman package
shell: bash
run: |
docker run --rm \
--volume "$GITHUB_WORKSPACE:/workspace" \
--workdir /workspace \
archlinux:base-devel bash -Eeuo pipefail -c '
pacman -Syu --needed --noconfirm \
cmake git ninja pkgconf sdl3 sdl3_ttf curl openssl \
nlohmann-json libyaml libelf minizip capstone gtk4 \
libadwaita webkitgtk-6.0 libutf8proc fontconfig \
libplacebo vulkan-headers vulkan-icd-loader libglvnd zlib
useradd --create-home builder
chown -R builder:builder /workspace
runuser -u builder -- env MOCKTAIL_SOURCE_DIR=/workspace \
makepkg --dir /workspace/packaging/arch \
--noconfirm --cleanbuild --force
install -d dist/arch
cp packaging/arch/*.pkg.tar.zst dist/arch/
pacman -Qp dist/arch/*.pkg.tar.zst >/dev/null
'
- name: Upload pacman package
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: Mocktail-Arch
path: dist/arch/*.pkg.tar.zst
if-no-files-found: error
retention-days: 14
publish:
name: Publish continuous packages
if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
needs: [appimage, deb, rpm, arch]
name: Publish continuous release
needs: [probe, appimage]
# A manual run from another branch still builds and smoke-tests, but the
# continuous tag and release only ever follow main.
if: needs.probe.outputs.build == 'true' && github.ref == 'refs/heads/main'
runs-on: ubuntu-24.04
timeout-minutes: 30
permissions:
@@ -254,84 +167,49 @@ jobs:
with:
fetch-depth: 0
- name: Download packages
- name: Download AppImage
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: Mocktail-*
name: Nightcap-AppImage
path: release
merge-multiple: true
- name: Prepare continuous packages
- name: Name and checksum the nightly build
shell: bash
run: |
set -Eeuo pipefail
mv -- release/*.AppImage release/mocktail-nightly.AppImage
mv -- release/*.deb release/mocktail-nightly.deb
mv -- release/*.rpm release/mocktail-nightly.rpm
mv -- release/*.pkg.tar.zst release/mocktail-nightly.pkg.tar.zst
# The Makefile builds under upstream's artifact name; the published
# asset carries this fork's.
mv -- release/Mocktail-x86_64.AppImage release/Nightcap-nightly-x86_64.AppImage
(cd release && sha256sum Nightcap-nightly-x86_64.AppImage \
> Nightcap-nightly-x86_64.AppImage.sha256)
find release -maxdepth 1 -type f -print
- name: Build package repositories
shell: bash
env:
GH_TOKEN: ${{ github.token }}
FLATPAK_GPG_PRIVATE_KEY: ${{ secrets.FLATPAK_GPG_PRIVATE_KEY }}
FLATPAK_GPG_KEY_ID: ${{ secrets.FLATPAK_GPG_KEY_ID }}
run: |
set -Eeuo pipefail
[[ -n "$FLATPAK_GPG_PRIVATE_KEY" && -n "$FLATPAK_GPG_KEY_ID" ]]
sudo apt-get update
sudo apt-get install --yes --no-install-recommends \
apt-utils createrepo-c gnupg rpm
install -d stable/deb stable/rpm
stable_tag="$(gh api "repos/$GITHUB_REPOSITORY/releases/latest" --jq .tag_name)"
gh release download "$stable_tag" --pattern '*.deb' --dir stable/deb
gh release download "$stable_tag" --pattern '*.rpm' --dir stable/rpm
mapfile -t stable_debs < <(find stable/deb -maxdepth 1 -type f -name '*.deb' -print)
mapfile -t stable_rpms < <(find stable/rpm -maxdepth 1 -type f -name '*.rpm' -print)
[[ "${#stable_debs[@]}" -eq 1 && "${#stable_rpms[@]}" -eq 1 ]]
install -d -m 0700 "$RUNNER_TEMP/mocktail-native-gnupg"
printf '%s' "$FLATPAK_GPG_PRIVATE_KEY" | \
gpg --batch --homedir "$RUNNER_TEMP/mocktail-native-gnupg" --import
gpg --batch --homedir "$RUNNER_TEMP/mocktail-native-gnupg" \
--export "$FLATPAK_GPG_KEY_ID" >mocktail-packages.gpg
./scripts/assemble_native_repositories.sh \
"${stable_debs[0]}" release/mocktail-nightly.deb \
"${stable_rpms[0]}" release/mocktail-nightly.rpm \
mocktail-packages.gpg "$FLATPAK_GPG_KEY_ID" \
"$RUNNER_TEMP/mocktail-native-gnupg" native-repositories
- name: Update continuous release
shell: bash
env:
GH_TOKEN: ${{ github.token }}
run: |
set -Eeuo pipefail
git config user.name komaruworld
git config user.email rtysus228@gmail.com
git config user.name 'github-actions[bot]'
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
git tag --force continuous "$GITHUB_SHA"
git push origin refs/tags/continuous --force
notes="Automated build of main at ${GITHUB_SHA:0:8}. Untested and unreleased; use a tagged release for normal play."
if ! gh release view continuous >/dev/null 2>&1; then
gh release create continuous \
--prerelease \
--title "Mocktail continuous" \
--notes "Automated development packages from main."
--title "Nightcap continuous" \
--notes "$notes"
fi
gh release edit continuous --title "Nightcap continuous" --notes "$notes"
gh release upload continuous release/* --clobber
# Drop assets left by earlier runs under other names.
while IFS= read -r asset; do
case "$asset" in
mocktail-nightly.AppImage|mocktail-nightly.deb|mocktail-nightly.rpm|mocktail-nightly.pkg.tar.zst)
Nightcap-nightly-x86_64.AppImage|Nightcap-nightly-x86_64.AppImage.sha256)
;;
Mocktail-*.AppImage|mocktail_*.deb|mocktail-*.deb|mocktail-*.rpm|mocktail-*.pkg.tar.zst)
*)
gh release delete-asset continuous "$asset" --yes
;;
esac
done < <(gh release view continuous --json assets --jq '.assets[].name')
- name: Upload package repositories
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: Mocktail-native-repositories
path: native-repositories
if-no-files-found: error
retention-days: 14
+338
View File
@@ -0,0 +1,338 @@
name: Release
on:
push:
tags: ['v*']
workflow_dispatch:
permissions:
contents: read
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false
jobs:
appimage:
name: AppImage (AnyLinux)
runs-on: ubuntu-24.04
timeout-minutes: 90
steps:
- name: Check out source
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
submodules: recursive
- name: Check release notes
if: startsWith(github.ref, 'refs/tags/v')
shell: bash
run: ./scripts/release_notes.sh "$GITHUB_REF_NAME" >/dev/null
- name: Build AppImage
shell: bash
env:
GH_TOKEN: ${{ github.token }}
run: |
docker run --rm \
--volume "$GITHUB_WORKSPACE:/workspace" \
--workdir /workspace \
--env GH_TOKEN \
ghcr.io/archlinux/archlinux:base-devel@sha256:c3953446016873a756fb7814f10087cac1634c127f7302e80c81c512da796d05 bash -Eeuo pipefail -c '
pacman -Syu --noconfirm --needed \
cmake git ninja pkgconf lld sdl3 sdl3_ttf curl openssl \
nlohmann-json libyaml libelf minizip minizip-ng capstone gtk4 \
libadwaita webkitgtk-6.0 libutf8proc fontconfig libglvnd \
vulkan-headers zlib libplacebo bubblewrap xdg-dbus-proxy \
patchelf jq unzip file ca-certificates dbus zsync
./scripts/install_anylinux_dependencies.sh
curl --fail --location --retry 3 \
--output /tmp/quick-sharun \
https://raw.githubusercontent.com/pkgforge-dev/Anylinux-AppImages/2a025cef14483c5bc2a75b479071f4d5c58a906f/useful-tools/quick-sharun.sh
echo "fa66d0189e1e87df781c049dfb23cbb1e753786056f693a4483d6d3af61e7ee1 /tmp/quick-sharun" | \
sha256sum --check --strict
chmod 0755 /tmp/quick-sharun
curl --fail --location --retry 3 \
--output /tmp/sharun \
https://github.com/pkgforge-dev/Anylinux-sharun/releases/download/2.2.6/sharun-x86_64
echo "a4d51752f9861b4870ca529fea04087310b28f8943cc55b99a95b777d4db92ec /tmp/sharun" | \
sha256sum --check --strict
chmod 0755 /tmp/sharun
curl --fail --location --retry 3 \
--output /tmp/anylinux.c \
https://raw.githubusercontent.com/pkgforge-dev/Anylinux-AppImages/2a025cef14483c5bc2a75b479071f4d5c58a906f/useful-tools/lib/anylinux.c
echo "f50650ad96d177559bdd0737df509fa20bd4af08887631c738fc1f31bfac185f /tmp/anylinux.c" | \
sha256sum --check --strict
curl --fail --location --retry 3 \
--output /tmp/appimagetool \
https://github.com/pkgforge-dev/appimagetool/releases/download/0.3.3/appimagetool-x86_64-linux
echo "0d01a4e2628efc897be9663f0faeed6a171e1851270b4e1ce4c22434a36ecebd /tmp/appimagetool" | \
sha256sum --check --strict
chmod 0755 /tmp/appimagetool
curl --fail --location --retry 3 \
--output /tmp/uruntime \
https://github.com/VHSgunzo/uruntime/releases/download/v0.5.9/uruntime-appimage-dwarfs-lite-x86_64
echo "cef962c299f2fa19b2b3cdf2fa1565ee8541796cc89b9a97a591f94041e8b083 /tmp/uruntime" | \
sha256sum --check --strict
chmod 0755 /tmp/uruntime
curl --fail --location --retry 3 \
--output /tmp/mkdwarfs \
https://github.com/mhx/dwarfs/releases/download/v0.15.6/dwarfs-universal-0.15.6-Linux-x86_64
echo "50891c38ba359db8271819a6cbf6aaa8068681523f0c4f2b8242007a45edaa28 /tmp/mkdwarfs" | \
sha256sum --check --strict
chmod 0755 /tmp/mkdwarfs
git config --global --add safe.directory /workspace
git config --global --add safe.directory /workspace/third_party/Vulkan-Headers
git config --global --add safe.directory /workspace/third_party/libjnivm
export MOCKTAIL_ANYLINUX_SYSTEM_INSTALL=1
export MOCKTAIL_ANYLINUX_SHARUN=/tmp/sharun
export MOCKTAIL_ANYLINUX_C_SOURCE=/tmp/anylinux.c
export RUNTIME=/tmp/uruntime
export DWARFS_CMD=/tmp/mkdwarfs
make appimage BUILD_DIR=build-appimage JOBS=4 \
APPIMAGE=dist/Mocktail-x86_64.AppImage \
PORTABLE_MODE=standalone APPIMAGE_FORMAT=anylinux \
ANYLINUX_PACKAGER=/tmp/quick-sharun \
ANYLINUX_APPIMAGETOOL=/tmp/appimagetool
test -x dist/Mocktail-x86_64.AppImage
'
- name: Smoke-test AppImage without FUSE
shell: bash
run: |
smoke_dir="$(mktemp -d)"
trap 'rm -rf -- "$smoke_dir"' EXIT
export XDG_DATA_HOME="$smoke_dir/data"
export XDG_CONFIG_HOME="$smoke_dir/config"
export XDG_CACHE_HOME="$smoke_dir/cache"
export XDG_STATE_HOME="$smoke_dir/state"
mkdir -p "$XDG_DATA_HOME" "$XDG_CONFIG_HOME" "$XDG_CACHE_HOME" "$XDG_STATE_HOME"
timeout 60s dist/Mocktail-x86_64.AppImage --appimage-extract-and-run --help
timeout 60s dist/Mocktail-x86_64.AppImage --appimage-extract-and-run mocktail_updater status
- name: Upload AppImage
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: Mocktail-AppImage
path: dist/Mocktail-x86_64.AppImage
if-no-files-found: error
retention-days: 14
deb:
name: DEB (Ubuntu 26.04)
runs-on: ubuntu-24.04
timeout-minutes: 45
steps:
- name: Check out source
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
submodules: recursive
- name: Build DEB
shell: bash
run: |
set -Eeuo pipefail
docker run --rm \
--volume "$GITHUB_WORKSPACE:/workspace" \
--workdir /workspace \
--env MOCKTAIL_PACKAGE_VERSION="$(./scripts/package_version.sh)" \
ubuntu:26.04 bash -Eeuo pipefail -c '
export DEBIAN_FRONTEND=noninteractive
apt-get update
apt-get install --yes --no-install-recommends \
build-essential cmake git ninja-build pkg-config lld fakeroot \
dpkg-dev file libsdl3-dev libsdl3-ttf-dev \
libcurl4-openssl-dev libssl-dev nlohmann-json3-dev \
libyaml-dev libelf-dev libminizip-dev libcapstone-dev \
libgtk-4-dev libadwaita-1-dev libwebkitgtk-6.0-dev \
libutf8proc-dev libfontconfig1-dev libegl-dev \
libvulkan-dev libplacebo-dev zlib1g-dev libpng-dev
cmake -S . -B build-deb -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=/usr \
-DMOCKTAIL_PACKAGE_NAME=nightcap \
-DMOCKTAIL_PACKAGE_VERSION="$MOCKTAIL_PACKAGE_VERSION" \
-DBUILD_TESTING=OFF
cmake --build build-deb --parallel 4
cpack --config build-deb/CPackConfig.cmake \
-G DEB -B dist/deb
dpkg-deb --info dist/deb/*.deb >/dev/null
'
- name: Upload DEB
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: Nightcap-DEB
path: dist/deb/*.deb
if-no-files-found: error
retention-days: 14
rpm:
name: RPM (Fedora 44)
runs-on: ubuntu-24.04
timeout-minutes: 45
steps:
- name: Check out source
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
submodules: recursive
- name: Build RPM
shell: bash
run: |
set -Eeuo pipefail
docker run --rm \
--volume "$GITHUB_WORKSPACE:/workspace" \
--workdir /workspace \
--env MOCKTAIL_PACKAGE_VERSION="$(./scripts/package_version.sh)" \
fedora:44 bash -Eeuo pipefail -c '
dnf install --assumeyes \
gcc-c++ cmake git ninja-build pkgconf-pkg-config lld rpm-build \
SDL3-devel SDL3_ttf-devel libcurl-devel openssl-devel \
nlohmann-json-devel libyaml-devel elfutils-libelf-devel \
minizip-ng-compat-devel capstone-devel gtk4-devel libadwaita-devel \
webkitgtk6.0-devel utf8proc-devel fontconfig-devel \
libglvnd-devel vulkan-headers vulkan-loader-devel \
libplacebo-devel zlib-ng-compat-devel libpng-devel
cmake -S . -B build-rpm -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=/usr \
-DCMAKE_INSTALL_LIBDIR=lib \
-DMOCKTAIL_PACKAGE_NAME=nightcap \
-DMOCKTAIL_PACKAGE_VERSION="$MOCKTAIL_PACKAGE_VERSION" \
-DBUILD_TESTING=OFF
cmake --build build-rpm --parallel 4
cpack --config build-rpm/CPackConfig.cmake \
-G RPM -B dist/rpm
rpm --query --package --info dist/rpm/*.rpm >/dev/null
'
- name: Upload RPM
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: Nightcap-RPM
path: dist/rpm/*.rpm
if-no-files-found: error
retention-days: 14
arch:
name: Pacman package (Arch Linux)
runs-on: ubuntu-24.04
timeout-minutes: 45
steps:
- name: Check out source
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
submodules: recursive
- name: Build pacman package
shell: bash
run: |
set -Eeuo pipefail
version="$(./scripts/package_version.sh)"
sed -i "s/^pkgver=.*/pkgver=${version}/" packaging/arch/PKGBUILD
docker run --rm \
--volume "$GITHUB_WORKSPACE:/workspace" \
--workdir /workspace \
ghcr.io/archlinux/archlinux:base-devel@sha256:c3953446016873a756fb7814f10087cac1634c127f7302e80c81c512da796d05 bash -Eeuo pipefail -c '
pacman -Syu --needed --noconfirm \
cmake git ninja pkgconf lld sdl3 sdl3_ttf curl openssl \
nlohmann-json libyaml libelf minizip capstone gtk4 \
libadwaita webkitgtk-6.0 libutf8proc fontconfig libpng \
libplacebo vulkan-headers vulkan-icd-loader libglvnd zlib
useradd --create-home builder
chown -R builder:builder /workspace
runuser -u builder -- env MOCKTAIL_SOURCE_DIR=/workspace \
makepkg --dir /workspace/packaging/arch \
--noconfirm --cleanbuild --force
install -d dist/arch
cp packaging/arch/*.pkg.tar.zst dist/arch/
pacman -Qp dist/arch/*.pkg.tar.zst >/dev/null
'
- name: Upload pacman package
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: Nightcap-Arch
path: dist/arch/*.pkg.tar.zst
if-no-files-found: error
retention-days: 14
publish:
name: Publish GitHub release
if: startsWith(github.ref, 'refs/tags/v')
needs: [appimage, deb, rpm, arch]
runs-on: ubuntu-24.04
timeout-minutes: 15
permissions:
contents: write
steps:
- name: Check out source
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Download packages
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: 'Nightcap-*'
merge-multiple: true
path: release
- name: Download AppImage
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: Mocktail-AppImage
path: release
- name: Create release
shell: bash
env:
GH_TOKEN: ${{ github.token }}
run: |
set -Eeuo pipefail
tag="${GITHUB_REF_NAME}"
mv release/Mocktail-x86_64.AppImage release/Nightcap-x86_64.AppImage
# An unmatched glob would otherwise reach sha256sum and gh as its own
# literal text, so every package pattern has to match before upload.
shopt -s nullglob
assets=(Nightcap-x86_64.AppImage)
for glob in '*.deb' '*.rpm' '*.pkg.tar.zst'; do
matched=(release/$glob)
if [[ ${#matched[@]} -eq 0 ]]; then
echo "::error::no ${glob} package among the release artifacts"
exit 1
fi
assets+=("${matched[@]#release/}")
done
uploads=()
for asset in "${assets[@]}"; do
uploads+=("release/$asset" "release/$asset.sha256")
done
(
cd release
for asset in "${assets[@]}"; do
sha256sum "$asset" > "$asset.sha256"
done
)
./scripts/release_notes.sh "$tag" > release/notes.md
# The Flatpak workflow creates the same release; whichever job
# arrives second sees "already exists" and continues.
if ! gh release view "$tag" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
gh release create "$tag" --repo "$GITHUB_REPOSITORY" \
--title "Nightcap $tag" --notes-file release/notes.md ||
gh release view "$tag" --repo "$GITHUB_REPOSITORY" >/dev/null
fi
gh release edit "$tag" --repo "$GITHUB_REPOSITORY" \
--title "Nightcap $tag" --notes-file release/notes.md
gh release upload "$tag" --repo "$GITHUB_REPOSITORY" --clobber \
"${uploads[@]}"
+12 -13
View File
@@ -1,7 +1,9 @@
# Build
/build/
/build-test/
/build_*/
/build-flatpak/
/build-anylinux*/
/.flatpak-builder/
/cmake-build-*/
/out/
@@ -20,6 +22,12 @@ Testing/
CMakeUserPresets.json
*.cmake.user
# local direnv development shell cache
.direnv/
# clangd cache
.cache
*.o
*.obj
*.a
@@ -50,19 +58,7 @@ Makefile.cmake
/cmake-build-release/
# Roblox payload
/rbx_bin/libroblox.so
/rbx_bin/libroblox.so.*
/rbx_bin/client_settings_android.json
/rbx_bin/assets/
/rbx_bin/android/
/rbx_bin/sober_apk/
/rbx_bin/versions/
/rbx_bin/.payload-update.*/
/rbx_bin/*.apk
/rbx_bin/*.so
/rbx_bin/*.so.*
!/rbx_bin/.gitkeep
/rbx_bin/
# Runtime state
/runtime/
@@ -181,3 +177,6 @@ lsan.log*
# Test fixtures
!/third_party/mcpelauncher-linker/core/libziparchive/**/*.zip
!/third_party/mcpelauncher-linker/core/libziparchive/**/*.apk
# Working plans, not part of the shipped documentation
/docs/plans/
+197
View File
@@ -0,0 +1,197 @@
# Changelog
All notable changes to Nightcap. The format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Release notes on
GitHub are taken from the matching section here.
## [Unreleased]
## [0.3.1] - 2026-09-20
### Added
- Each release carries a DEB, an RPM, and a pacman package beside the
AppImage, with a `.sha256` for every file. They install `/usr/bin/mocktail`
and conflict with each other and with upstream's `mocktail` package.
Nightcap is in no distribution repository or the AUR; the recipes under
`packaging/aur/nightcap{,-bin,-git}` build from this repository locally.
- The AppStream metadata lists the Nightcap releases, so software centres
show the release history instead of upstream's.
### Fixed
- ETC2 texture decoding no longer blocks the thread that submits the frame.
Uploads decode on a background dispatcher and the submit carries a timeline
semaphore wait, so the GPU waits for the texture instead of the render
thread. Reading the application's compressed bytes moves to that dispatcher
too whenever they are already mapped. In Blade Ball the worst
`vkQueueSubmit` fell from 140ms to 1ms, its p99 from 4.9ms to 0.05ms, and
the total time spent in submits from 2534ms to 309ms. Frame interval is
unchanged: the remaining tail is the engine's own main-thread step. A device
without the `timelineSemaphore` feature decodes inline as before, as does a
submit whose `pNext` already sizes arrays by its semaphore counts.
- The mouse pointer jumped when a right-click camera drag ended, and drifted
to the viewport edge during one. The input router inferred pointer capture
from SDL reporting zero absolute coordinates, which holds on X11 but not on
Wayland, where the reported position accumulates the relative deltas and
leaves the window. Motion, button, and wheel events now carry the window's
relative mouse mode. The right-click camera fallback also only captures
inside an experience, so the cursor stays free over the app surface.
- Shutting the window down while an experience was starting or leaving could
free the pointer capture owner underneath the thread using it. Callers off
the SDL thread now hold a reference for the length of their call.
- The TextBox caret did not move when a trailing space was typed in a
single-line field. The final cluster rect SDL_ttf reports stops at the last
inked glyph, so the laid-out width is used there instead. Wrapped layouts
keep the clamp, because their width is the widest line rather than the last.
- Every TextBox keystroke re-ran `TTF_Init`, a fontconfig sort, six
`TTF_OpenFont` calls, and `TTF_Quit` on the render thread. The overlay holds
its opened fonts and reopens them only when the Roblox font or the point
size changes, keeping the current set when a reopen fails. Measured over 100
rasters, 1.59ms to 0.12ms per keystroke.
- Staged ETC2 copies now carry an explicit host-write barrier. A queue
submission only makes host writes from before it visible to the device, and
an asynchronous decode writes after the submit, so without the barrier those
texels were not guaranteed to reach the copy that reads them.
### Changed
- `docs/PERFORMANCE.md` documents the GameMode settings to run with. A
`desiredgov=powersave` in `~/.config/gamemode.ini` overrides the packaged
`performance` default and clocks the CPU down for the whole session while
still reporting an active performance request.
## [0.3.0] - 2026-09-18
### Added
- Custom Discord presence. `integrations.discord_rpc.text.title` replaces the
name in "Playing Nightcap", and `integrations.discord_rpc.images` sets the
large and small icons and their hover text. Text and image fields accept
`{place_name}`, `{place_icon}`, and `{creator_name}`. A field that expands
an empty placeholder is left out, and `text.state: ""` hides the state line.
- The default Discord card now matches Sober: the title is "Roblox", the
details line is "Playing <experience>", the state is "by <creator>", and
the Roblox badge sits inside the experience icon. The creator comes from the
same Roblox games request that already fetches the experience name. With
`show_place_name: false` the creator is hidden too, so the card shows only
the title and timer.
- `--profile <file>` writes a Chrome-format trace of the Vulkan adapter's
work: presents, GPU waits, submits, ETC2 texture decode, and pipeline and
shader creation. Open it in ui.perfetto.dev. `docs/BENCHMARKING.md`
describes the benchmark runs, and `scripts/summarize_profile_trace.py`
turns traces into a before and after table.
### Fixed
- Texture uploads stalled the frame they were submitted on. ETC2 batches
decoded on worker threads but were then resampled one upload at a time on
the submitting thread, which at the default 4x upscale is sixteen times the
decoded texels. Resampling now runs on the same worker pool, in two passes so
mip level 0 still resolves before the mips built from it. In a 70k-part
place the worst `vkQueueSubmit` fell from 167ms to 92ms.
- Most ETC2 batches decoded on a single thread. Workers were created and
joined per batch, so the parallel floor had to cover that cost and sat at a
1024x1024 image; batches under it ran on the caller, which was 54% of all
decode time in a 70k-part place. Decode workers are now a persistent pool,
and the floor drops to a 128x128 image. Mean decode time in that place fell
from 8.2ms to 4.7ms, and 11.8ms before both texture fixes.
- On CPUs with a wide core-speed spread, Feral GameMode pinned the process to
the few fastest cores, concentrating Roblox's worker pool onto them instead
of spreading it. With `performance.gamemode: auto`, a request that narrows
the CPU affinity is now released again, which releases the pin. Set
`pin_cores=no` in `~/.config/gamemode.ini` to keep GameMode without the
pinning, or `performance.gamemode: on` to accept it. See
`docs/PERFORMANCE.md`.
- With `show_place_name: false`, Discord no longer shows the experience
thumbnail or its name on hover.
- The host main thread no longer spins on the engine's message pump, which
cost about half a core and 146,000 empty calls a second. It now rests 100 µs
between polls: sleeping when the governor is `performance`, parking the
core with TPAUSE on CPUs that have it, spinning otherwise. With GameMode
setting the governor, process CPU in a 60k-part place fell from 216% to
107% at the same 120 fps. `MOCKTAIL_ENGINE_PUMP_REST` forces a mode; see
`docs/PERFORMANCE.md`.
- ETC2 uploads with a `bufferRowLength` or `bufferImageHeight` larger than
the image read the padded rows as packed data and decoded garbage.
- ETC2 resampling created and joined worker threads on every batch. It now
runs on the persistent decode pool.
- A failed trace write left the profile writer thread waking every 250 ms
for the rest of the session. It now exits.
- The `--profile` trace gains a `pump` slice for the engine's main-thread
step.
## [0.2.0] - 2026-09-16
### Added
- FAQ covering Discord RPC, log locations, and VR, adapted from upstream.
- `input.raw_mouse` in `config.yaml` sends mouse look deltas in the host's
own units. Turn it on when first-person aiming feels too slow or too fast
under fractional display scaling. Off by default.
- Nightly AppImage builds, published as the `continuous` prerelease. They are
untested; use a tagged release for normal play.
### Changed
- Values in `fflags.json` now win over the performance preset instead of
stopping the launch. Startup prints each key it kept.
- Discord Rich Presence uses Nightcap's own Discord application, so it shows
as "Playing Nightcap". Terms and privacy pages for it live on the
project site.
- Discord Rich Presence is on by default. Set
`integrations.discord_rpc.enabled: false` to turn it off.
- The AppImage build verifies its reduced dependency packages against digests
taken at build time, rather than a digest list kept in the repository.
### Fixed
- Mouse look keeps responding after a long turn. The tracked pointer position
could drift past the edge of the view, which swallowed every reversal until
the mouse travelled all the way back.
- Nightcap no longer refuses to start with `cannot lock appStorage`. It
created its own credential directory using the process umask, then rejected
that directory for being group writable. Systems with a permissive umask,
such as `002`, failed on first run.
## [0.1.0] - 2026-09-15
First Nightcap release, forked from komaruworld/mocktail.
### Added
- ETC2 and EAC texture emulation on Vulkan, so games that use compressed
mobile textures render correctly on desktop GPUs. Decoding runs on worker
threads to avoid stutter.
- PNG texture overrides in `~/.config/mocktail/textures`, keyed by upload
hash, with a dump mode to find the texture you want to replace.
- Small textures are upscaled 4x by default. Set
`MOCKTAIL_SMALL_TEXTURE_UPSCALE=1` to turn it off.
- AppImage and signed Flatpak builds on every release tag. The Flatpak repo
is published at https://coderdayton.github.io/nightcap/.
- Roadmap in `docs/ROADMAP.md`.
### Changed
- Frames are presented as soon as they are ready instead of waiting in a
queue, which lowers input latency.
- Roblox now sees the real host RAM and display size instead of a low-end
phone profile.
- App ID is `io.github.CoderDayton.nightcap`, so Nightcap installs next to
upstream Mocktail. The binary and config paths keep the `mocktail` name.
- New name, icon, and colors.
- The desktop entry launches on Wayland and points at the installed binary.
### Fixed
- Servers no longer kick with error 319 shortly after joining.
- The FMOD output-device bridge is optional, so the updater no longer fails
without it.
- The AppImage build no longer fails on stale checksums for the slimmed
Arch packages.
[Unreleased]: https://github.com/CoderDayton/nightcap/compare/v0.3.1...HEAD
[0.3.1]: https://github.com/CoderDayton/nightcap/releases/tag/v0.3.1
[0.3.0]: https://github.com/CoderDayton/nightcap/releases/tag/v0.3.0
[0.2.0]: https://github.com/CoderDayton/nightcap/releases/tag/v0.2.0
[0.1.0]: https://github.com/CoderDayton/nightcap/releases/tag/v0.1.0
+112 -38
View File
@@ -1,3 +1,4 @@
# Modified by vii from komaruworld/mocktail. See README "About this fork".
# Copyright 2026 Mocktail Project Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -8,7 +9,7 @@
cmake_minimum_required(VERSION 3.20)
project(Mocktail
VERSION 1.0.2
VERSION 1.0.4
DESCRIPTION "Android x86-64 Roblox compatibility runtime for Linux"
LANGUAGES CXX
)
@@ -60,7 +61,7 @@ set(MOCKTAIL_DEFAULT_SIGNING_TRUST_MANIFEST
CACHE FILEPATH
"Fallback signing trust manifest used outside an installed runtime"
)
set(MOCKTAIL_DISCORD_APPLICATION_ID "1537088975720812655" CACHE STRING
set(MOCKTAIL_DISCORD_APPLICATION_ID "1549428231927500843" CACHE STRING
"Discord application ID baked into Mocktail Rich Presence releases"
)
string(LENGTH "${MOCKTAIL_DISCORD_APPLICATION_ID}"
@@ -138,12 +139,7 @@ if(BUILD_TESTING)
FetchContent_MakeAvailable(googletest)
endif()
add_library(mocktail_crypto_foundation STATIC
src/foundation/sha256.cc
)
add_library(Mocktail::CryptoFoundation ALIAS mocktail_crypto_foundation)
target_include_directories(mocktail_crypto_foundation PUBLIC include)
mocktail_apply_compile_options(mocktail_crypto_foundation)
find_package(OpenSSL REQUIRED)
include(${CMAKE_SOURCE_DIR}/cmake/MocktailPlatformGraphics.cmake)
foreach(foundation_target IN ITEMS
@@ -201,12 +197,12 @@ target_include_directories(mocktail_bionic_stdio_runtime PUBLIC include)
mocktail_apply_compile_options(mocktail_bionic_stdio_runtime)
find_package(CURL REQUIRED)
find_package(OpenSSL REQUIRED)
find_package(nlohmann_json CONFIG REQUIRED)
find_package(PkgConfig REQUIRED)
pkg_check_modules(LIBYAML REQUIRED IMPORTED_TARGET yaml-0.1)
pkg_check_modules(MINIZIP REQUIRED IMPORTED_TARGET minizip)
pkg_check_modules(CAPSTONE REQUIRED IMPORTED_TARGET capstone)
pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)
pkg_check_modules(GTK4 REQUIRED IMPORTED_TARGET gtk4)
pkg_check_modules(LIBADWAITA REQUIRED IMPORTED_TARGET "libadwaita-1>=1.6")
pkg_check_modules(WEBKITGTK6 REQUIRED IMPORTED_TARGET webkitgtk-6.0)
@@ -214,6 +210,7 @@ pkg_check_modules(WEBKITGTK6 REQUIRED IMPORTED_TARGET webkitgtk-6.0)
add_library(mocktail_update STATIC
src/update/android_manifest.cc
src/update/apk_bundle.cc
src/update/apk_provider.cc
src/update/apk_signature.cc
src/update/apkpure_provider.cc
src/update/candidate_approval.cc
@@ -223,6 +220,7 @@ add_library(mocktail_update STATIC
src/update/payload_integrity.cc
src/update/payload_store.cc
src/update/readiness_canary.cc
src/update/unsafe_latest_runner.cc
src/update/update_config.cc
src/update/update_coordinator.cc
src/update/zip_archive.cc
@@ -231,7 +229,6 @@ add_library(Mocktail::Update ALIAS mocktail_update)
target_include_directories(mocktail_update PUBLIC include)
target_link_libraries(mocktail_update PUBLIC
Mocktail::Compat
Mocktail::CryptoFoundation
CURL::libcurl
OpenSSL::Crypto
PkgConfig::LIBYAML
@@ -269,9 +266,11 @@ add_library(mocktail_runtime STATIC
src/runtime/crash_report_policy.cc
src/runtime/discord_rpc.cc
src/runtime/device_profile.cc
src/runtime/engine_pump_rest.cc
src/runtime/environment.cc
src/runtime/external_launch_broker.cc
src/runtime/frame_rate_policy.cc
src/runtime/fleasion.cc
src/runtime/game_mode.cc
src/runtime/game_session_coordinator.cc
src/runtime/graphics_launch_policy.cc
@@ -288,7 +287,10 @@ add_library(mocktail_runtime STATIC
src/runtime/roblox_launch_uri.cc
src/runtime/roblox_game_session_native_adapter.cc
src/runtime/roblox_web_view_bridge.cc
src/runtime/roblox_permissions_bridge.cc
src/runtime/payload_update_preflight.cc
src/runtime/process_launch_policy.cc
src/runtime/process_diagnostics.cc
src/runtime/runtime_config.cc
src/runtime/runtime_config_bootstrap.cc
src/runtime/runtime_config_file.cc
@@ -298,6 +300,9 @@ add_library(mocktail_runtime STATIC
src/runtime/startup_state_machine.cc
src/runtime/supported_launch_policy.cc
src/runtime/support_bundle.cc
src/runtime/system_proxy.cc
src/runtime/texture_memory_policy.cc
src/runtime/device_memory_profile.cc
)
add_library(Mocktail::Runtime ALIAS mocktail_runtime)
target_include_directories(mocktail_runtime PUBLIC include)
@@ -328,9 +333,10 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND
endif()
target_link_libraries(mocktail_runtime PUBLIC Threads::Threads)
target_link_libraries(mocktail_runtime PRIVATE
Mocktail::CryptoFoundation
OpenSSL::Crypto
CURL::libcurl
nlohmann_json::nlohmann_json
PkgConfig::GIO
PkgConfig::LIBYAML
${CMAKE_DL_LIBS}
)
@@ -410,9 +416,6 @@ target_link_libraries(mocktail_auth_runtime_composition PUBLIC
mocktail_runtime
mocktail_services
)
target_link_libraries(mocktail_auth_runtime_composition PRIVATE
Mocktail::CryptoFoundation
)
mocktail_apply_compile_options(mocktail_auth_runtime_composition)
add_library(mocktail_linker STATIC
@@ -424,6 +427,7 @@ target_link_libraries(mocktail_linker PUBLIC
linker
mocktail_compat
mocktail_bionic_stdio_runtime
mocktail_bionic_abi_exports
)
mocktail_apply_compile_options(mocktail_linker)
target_compile_definitions(mocktail_linker PUBLIC MOCKTAIL_USE_BIONIC_LINKER=1)
@@ -452,6 +456,7 @@ add_library(mocktail_window STATIC
src/window/roblox_fullscreen_menu_request_gate.cc
src/window/video_driver_policy.cc
src/window/window.cc
src/window/window_creation_policy.cc
src/window/window_fullscreen_request_gate.cc
src/window/window_game_surface_bridge.cc
src/window/window_pointer_capture_owner.cc
@@ -464,9 +469,10 @@ target_include_directories(mocktail_window PUBLIC src)
mocktail_apply_compile_options(mocktail_window)
target_link_libraries(mocktail_window PUBLIC
Mocktail::PlatformSdl
Mocktail::SdlVulkanWsi
SDL3::SDL3
${CMAKE_DL_LIBS}
PRIVATE Mocktail::Runtime
PRIVATE Mocktail::Runtime Mocktail::GlesTextOverlay
)
add_library(mocktail_roblox_output_device_runtime STATIC
@@ -528,6 +534,19 @@ target_include_directories(mocktail_bionic_abi_exports
PRIVATE src
)
mocktail_apply_compile_options(mocktail_bionic_abi_exports)
# These exported Bionic FORTIFY symbols intentionally overlap glibc's
# __*_chk entry points. When the bridge is visible to whole-program LTO,
# GCC can fold a fortified host call back into its own compatibility wrapper
# and turn the resulting tail recursion into an infinite loop. Keep this
# translation unit opaque to interprocedural optimization and make its calls
# to the host C library explicit external calls.
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
set_property(SOURCE src/compat/bionic_abi_exports.cc APPEND PROPERTY
COMPILE_OPTIONS
-fno-lto
-fno-builtin
)
endif()
target_link_libraries(mocktail_bionic_abi_exports PRIVATE
mocktail_bionic_stdio_runtime
mocktail_window
@@ -630,6 +649,40 @@ target_link_options(mocktail PRIVATE
-Wl,--export-dynamic
)
# Native FreeBSD helper for Linuxulator's unsupported DF socket option.
option(MOCKTAIL_BUILD_FREEBSD_SOCKET_HELPER
"Build the native FreeBSD Linuxulator socket helper" ON)
if(MOCKTAIL_BUILD_FREEBSD_SOCKET_HELPER)
find_program(MOCKTAIL_LLD_EXECUTABLE NAMES ld.lld REQUIRED)
find_program(MOCKTAIL_ELFEDIT_EXECUTABLE NAMES elfedit REQUIRED)
get_filename_component(MOCKTAIL_LLD_DIRECTORY
"${MOCKTAIL_LLD_EXECUTABLE}" DIRECTORY)
set(MOCKTAIL_FREEBSD_SOCKET_HELPER
"${CMAKE_BINARY_DIR}/mocktail_freebsd_socket_helper")
add_custom_command(
OUTPUT "${MOCKTAIL_FREEBSD_SOCKET_HELPER}"
COMMAND "${CMAKE_CXX_COMPILER}"
-x assembler-with-cpp
-nostdlib
-static
-no-pie
"-B${MOCKTAIL_LLD_DIRECTORY}/"
-fuse-ld=lld
-Wl,-e,_start,--build-id=none,-z,noexecstack
-o "${MOCKTAIL_FREEBSD_SOCKET_HELPER}"
"${CMAKE_SOURCE_DIR}/src/compat/freebsd_socket_helper.S"
COMMAND "${MOCKTAIL_ELFEDIT_EXECUTABLE}"
--output-osabi FreeBSD "${MOCKTAIL_FREEBSD_SOCKET_HELPER}"
DEPENDS "${CMAKE_SOURCE_DIR}/src/compat/freebsd_socket_helper.S"
COMMENT "Building native FreeBSD Linuxulator socket helper"
VERBATIM
)
add_custom_target(mocktail_freebsd_socket_helper_build ALL
DEPENDS "${MOCKTAIL_FREEBSD_SOCKET_HELPER}"
)
add_dependencies(mocktail mocktail_freebsd_socket_helper_build)
endif()
set_target_properties(mocktail PROPERTIES
BUILD_RPATH "\$ORIGIN"
INSTALL_RPATH "\$ORIGIN;\$ORIGIN/../${CMAKE_INSTALL_LIBDIR}/mocktail"
@@ -662,6 +715,11 @@ install(TARGETS
RUNTIME DESTINATION "${MOCKTAIL_INSTALL_RUNTIME_DIR}"
LIBRARY DESTINATION "${MOCKTAIL_INSTALL_RUNTIME_DIR}"
)
if(MOCKTAIL_BUILD_FREEBSD_SOCKET_HELPER)
install(PROGRAMS "${MOCKTAIL_FREEBSD_SOCKET_HELPER}"
DESTINATION "${MOCKTAIL_INSTALL_RUNTIME_DIR}"
)
endif()
install(FILES
config/roblox_compatibility.json
config/roblox_bootstrap_sources.json
@@ -669,13 +727,24 @@ install(FILES
config/roblox_signing_certificates.json
DESTINATION "${MOCKTAIL_INSTALL_DATA_DIR}/metadata"
)
install(FILES packaging/space.bigrat.mocktail.desktop
DESTINATION "${CMAKE_INSTALL_DATADIR}/applications"
)
install(FILES packaging/space.bigrat.mocktail.metainfo.xml
# The installed desktop entry names the binary by its install path, so it
# launches from sessions whose PATH lacks the install prefix. Uses the prefix
# given at install time, not the configure-time one.
install(CODE "
file(READ \"${CMAKE_SOURCE_DIR}/packaging/io.github.CoderDayton.nightcap.desktop\"
MOCKTAIL_DESKTOP_ENTRY)
string(REPLACE \"Exec=env SDL_VIDEODRIVER=wayland,x11 mocktail %u\"
\"Exec=env SDL_VIDEODRIVER=wayland,x11 \${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}/mocktail %u\"
MOCKTAIL_DESKTOP_ENTRY \"\${MOCKTAIL_DESKTOP_ENTRY}\")
file(WRITE \"${CMAKE_BINARY_DIR}/io.github.CoderDayton.nightcap.desktop\"
\"\${MOCKTAIL_DESKTOP_ENTRY}\")
file(INSTALL \"${CMAKE_BINARY_DIR}/io.github.CoderDayton.nightcap.desktop\"
DESTINATION \"\${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}/applications\")
")
install(FILES packaging/io.github.CoderDayton.nightcap.metainfo.xml
DESTINATION "${CMAKE_INSTALL_DATADIR}/metainfo"
)
install(FILES packaging/space.bigrat.mocktail.svg
install(FILES packaging/io.github.CoderDayton.nightcap.svg
DESTINATION "${CMAKE_INSTALL_DATADIR}/icons/hicolor/scalable/apps"
)
set(MOCKTAIL_HICOLOR_ICON_SIZES
@@ -684,7 +753,7 @@ set(MOCKTAIL_HICOLOR_ICON_SIZES
# Generated icons are committed, so ImageMagick is not a build dependency.
foreach(icon_size IN LISTS MOCKTAIL_HICOLOR_ICON_SIZES)
install(FILES
"packaging/icons/hicolor/${icon_size}x${icon_size}/apps/space.bigrat.mocktail.png"
"packaging/icons/hicolor/${icon_size}x${icon_size}/apps/io.github.CoderDayton.nightcap.png"
DESTINATION
"${CMAKE_INSTALL_DATADIR}/icons/hicolor/${icon_size}x${icon_size}/apps"
)
@@ -717,23 +786,29 @@ install(CODE "
endif()
")
set(MOCKTAIL_PACKAGE_NAMES mocktail mocktail-nightly nightcap nightcap-nightly)
set(MOCKTAIL_PACKAGE_NAME "mocktail" CACHE STRING
"Native package name"
)
set(MOCKTAIL_PACKAGE_VERSION "${PROJECT_VERSION}" CACHE STRING
"Native package version"
)
set(MOCKTAIL_PACKAGE_VERSION_SUFFIX "" CACHE STRING
"Native package version suffix"
)
if(NOT MOCKTAIL_PACKAGE_NAME STREQUAL "mocktail" AND
NOT MOCKTAIL_PACKAGE_NAME STREQUAL "mocktail-nightly")
if(NOT MOCKTAIL_PACKAGE_NAME IN_LIST MOCKTAIL_PACKAGE_NAMES)
string(JOIN ", " MOCKTAIL_PACKAGE_NAMES_TEXT ${MOCKTAIL_PACKAGE_NAMES})
message(FATAL_ERROR
"MOCKTAIL_PACKAGE_NAME must be mocktail or mocktail-nightly")
"MOCKTAIL_PACKAGE_NAME must be one of ${MOCKTAIL_PACKAGE_NAMES_TEXT}")
endif()
set(CPACK_PACKAGE_NAME "${MOCKTAIL_PACKAGE_NAME}")
set(CPACK_PACKAGE_VERSION "${PROJECT_VERSION}${MOCKTAIL_PACKAGE_VERSION_SUFFIX}")
set(CPACK_PACKAGE_VENDOR "Mocktail Project")
set(CPACK_PACKAGE_CONTACT "komaruworld <rtysus228@gmail.com>")
set(CPACK_PACKAGE_HOMEPAGE_URL "https://github.com/komaruworld/mocktail")
set(CPACK_PACKAGE_VERSION
"${MOCKTAIL_PACKAGE_VERSION}${MOCKTAIL_PACKAGE_VERSION_SUFFIX}"
)
set(CPACK_PACKAGE_VENDOR "Nightcap")
set(CPACK_PACKAGE_CONTACT "vii <coderdayton14@gmail.com>")
set(CPACK_PACKAGE_HOMEPAGE_URL "https://github.com/CoderDayton/nightcap")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "${PROJECT_DESCRIPTION}")
set(CPACK_PACKAGE_DESCRIPTION
"Runs the Android x86-64 Roblox client on Linux."
@@ -743,7 +818,7 @@ set(CPACK_PACKAGING_INSTALL_PREFIX "/usr")
set(CPACK_STRIP_FILES TRUE)
set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT)
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "komaruworld <rtysus228@gmail.com>")
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "vii <coderdayton14@gmail.com>")
set(CPACK_DEBIAN_PACKAGE_SECTION "games")
set(CPACK_DEBIAN_PACKAGE_PRIORITY "optional")
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
@@ -753,15 +828,14 @@ set(CPACK_RPM_PACKAGE_LICENSE "Apache-2.0")
set(CPACK_RPM_PACKAGE_GROUP "Amusements/Games")
set(CPACK_RPM_PACKAGE_AUTOREQPROV ON)
if(MOCKTAIL_PACKAGE_NAME STREQUAL "mocktail-nightly")
set(CPACK_DEBIAN_PACKAGE_CONFLICTS "mocktail")
set(CPACK_DEBIAN_PACKAGE_REPLACES "mocktail")
set(CPACK_RPM_PACKAGE_CONFLICTS "mocktail")
else()
set(CPACK_DEBIAN_PACKAGE_CONFLICTS "mocktail-nightly")
set(CPACK_DEBIAN_PACKAGE_REPLACES "mocktail-nightly")
set(CPACK_RPM_PACKAGE_CONFLICTS "mocktail-nightly")
endif()
# Every variant installs the same /usr/bin/mocktail and /usr/share/mocktail,
# so only one of them can be present at a time.
set(MOCKTAIL_PACKAGE_SIBLINGS ${MOCKTAIL_PACKAGE_NAMES})
list(REMOVE_ITEM MOCKTAIL_PACKAGE_SIBLINGS "${MOCKTAIL_PACKAGE_NAME}")
string(JOIN ", " MOCKTAIL_PACKAGE_CONFLICTS ${MOCKTAIL_PACKAGE_SIBLINGS})
set(CPACK_DEBIAN_PACKAGE_CONFLICTS "${MOCKTAIL_PACKAGE_CONFLICTS}")
set(CPACK_DEBIAN_PACKAGE_REPLACES "${MOCKTAIL_PACKAGE_CONFLICTS}")
set(CPACK_RPM_PACKAGE_CONFLICTS "${MOCKTAIL_PACKAGE_CONFLICTS}")
include(CPack)
add_dependencies(mocktail all_stubs)
+65
View File
@@ -0,0 +1,65 @@
<!-- Modified by vii from komaruworld/mocktail. See README "About this fork". -->
# FAQ
## How do I turn Discord RPC off?
RPC is on by default. It never signs in to Discord and never reads an
account token. To turn it off, launch Nightcap once to create the config,
then close it. Open `config.yaml`:
- Native / AppImage: `~/.config/mocktail/config.yaml`
- Flatpak: `~/.var/app/io.github.CoderDayton.nightcap/config/mocktail/config.yaml`
For native installs, a custom `$XDG_CONFIG_HOME` replaces `~/.config`.
Set `enabled` to `false` under `integrations.discord_rpc`. If the block is
missing, add it under the existing `integrations` section:
```yaml
integrations:
discord_rpc:
enabled: false
```
Save the file and restart Nightcap. Set `enabled` back to `true` to turn
RPC on again.
## Can Discord count Nightcap as Roblox play time or quests?
No. Discord only credits play time and quests to games its own scanner
detects, and Rich Presence never counts. On Linux the scanner only matches
games with a Linux entry in Discord's list. Roblox has none, so no process
name Nightcap could use would be detected. Only Roblox or Discord can change
that.
## Where can I find logs for a bug report?
Nightcap saves logs automatically. Reproduce the issue, close Nightcap, and
attach `latest.log`:
- Native / AppImage: `~/.local/state/mocktail/logs/latest.log`
- Flatpak: `~/.var/app/io.github.CoderDayton.nightcap/.local/state/mocktail/logs/latest.log`
For native installs, a custom `$XDG_STATE_HOME` replaces `~/.local/state`.
## Can I play in VR?
VR is experimental and lives on upstream's `vr` branch, which does not
include the Nightcap changes. Install the
[build dependencies](README.md#building), then switch to that branch and
build:
```bash
git remote add upstream https://github.com/komaruworld/mocktail.git
git fetch upstream
git switch -c vr upstream/vr
git submodule update --init --recursive
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DMOCKTAIL_ENABLE_VR=ON
cmake --build build -j4
```
Start WiVRn or SteamVR/ALVR, connect your headset, then run:
```bash
./build/mocktail -vr
```
+12 -10
View File
@@ -1,3 +1,4 @@
# Modified by vii from komaruworld/mocktail. See README "About this fork".
.DEFAULT_GOAL := all
.PHONY: all build release debug test apk install register-url-handler release-runtime portable portable-test standalone appimage flatpak run-flatpak run run-smoke run-unlimited run-game run-input run-resize run-network auto-run run-gles run-angle update-roblox update-auto update-auto-launch payload-status payload-rollback support-bundle clean submodules help
@@ -14,9 +15,12 @@ PORTABLE_MODE ?= standalone
PORTABLE_CANONICAL_MODE := $(if $(filter dynamic minimal,$(PORTABLE_MODE)),thin,$(if $(filter full static,$(PORTABLE_MODE)),standalone,$(PORTABLE_MODE)))
PORTABLE_DIR ?= dist/mocktail-linux-x86_64-glibc-$(PORTABLE_CANONICAL_MODE)
APPIMAGE ?= dist/Mocktail-x86_64.AppImage
APPIMAGE_FORMAT ?= classic
ANYLINUX_PACKAGER ?= quick-sharun
ANYLINUX_APPIMAGETOOL ?= appimagetool
PREFIX ?= /usr
FLATPAK_BUILD_DIR ?= build-flatpak
FLATPAK_MANIFEST ?= packaging/flatpak/space.bigrat.mocktail.json
FLATPAK_MANIFEST ?= packaging/flatpak/io.github.CoderDayton.nightcap.json
FLATPAK_JOBS ?= 4
define build_native_runtime
@@ -52,7 +56,7 @@ install: ## Install an already-built dynamic runtime (use: sudo make install)
register-url-handler: build ## Select this build for Roblox website links
@./scripts/register_url_handler.sh --set-default \
--executable "$(abspath $(BINARY))" \
--desktop-file "$(abspath packaging/space.bigrat.mocktail.desktop)"
--desktop-file "$(abspath packaging/io.github.CoderDayton.nightcap.desktop)"
test: ## Build and run all unit tests
$(call build_native_runtime,Debug,ON)
@@ -73,13 +77,11 @@ portable-test: release-runtime ## Verify portable packaging and relocation
standalone: appimage ## Build the standalone x86-64 AppImage
appimage: release-runtime ## Build AppImage (requires appimagetool)
@command -v appimagetool >/dev/null 2>&1 || { \
echo "appimagetool is required for make appimage" >&2; \
echo "Install it, then rerun make appimage." >&2; \
exit 1; \
}
@./scripts/package_portable.sh --build-dir "$(BUILD_DIR)" \
appimage: release-runtime ## Build AppImage (APPIMAGE_FORMAT=classic|anylinux)
@MOCKTAIL_APPIMAGE_FORMAT="$(APPIMAGE_FORMAT)" \
MOCKTAIL_ANYLINUX_PACKAGER="$(ANYLINUX_PACKAGER)" \
MOCKTAIL_ANYLINUX_APPIMAGETOOL="$(ANYLINUX_APPIMAGETOOL)" \
./scripts/package_portable.sh --build-dir "$(BUILD_DIR)" \
--libc glibc --output "$(PORTABLE_DIR)" --mode "$(PORTABLE_MODE)" \
--appimage "$(APPIMAGE)"
@@ -90,7 +92,7 @@ flatpak: ## Build and install the local x86-64 Flatpak
--jobs "$(FLATPAK_JOBS)"
run-flatpak: ## Run the installed Flatpak
@flatpak run space.bigrat.mocktail
@flatpak run io.github.CoderDayton.nightcap
run: export MOCKTAIL_AUTO_EXIT_AFTER_PRESENT_MS = 0
run: build ## Run the native binary interactively until the window is closed
+150 -92
View File
@@ -1,17 +1,85 @@
# Mocktail
<!-- Modified by vii from komaruworld/mocktail. See "About this fork" below. -->
# Nightcap
[![CI](https://github.com/komaruworld/mocktail/actions/workflows/ci.yml/badge.svg)](https://github.com/komaruworld/mocktail/actions/workflows/ci.yml)
[![Stars](https://img.shields.io/github/stars/komaruworld/mocktail?style=flat&logo=github)](https://github.com/komaruworld/mocktail/stargazers)
[![Downloads](https://img.shields.io/github/downloads/komaruworld/mocktail/total?logo=github)](https://github.com/komaruworld/mocktail/releases/latest)
[![CI](https://github.com/CoderDayton/nightcap/actions/workflows/ci.yml/badge.svg)](https://github.com/CoderDayton/nightcap/actions/workflows/ci.yml)
[![Stars](https://img.shields.io/github/stars/CoderDayton/nightcap?style=flat&logo=github)](https://github.com/CoderDayton/nightcap/stargazers)
[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)
Mocktail runs the Android `x86_64` Roblox client on Linux. It provides the
Android ABI and JNI pieces the client expects, then connects them to SDL3 and
Vulkan or OpenGL on the Linux side.
Nightcap is Mocktail, tuned for playing on a real PC. It runs the Android
`x86_64` Roblox client on Linux, including a Linux userspace hosted by
FreeBSD's Linuxulator. It provides the Android ABI and JNI pieces the client
expects, then connects them to SDL3 and Vulkan or OpenGL on the Linux side.
Mocktail is an independent community project. It is not affiliated with Roblox
Nightcap is an independent community project. It is not affiliated with Roblox
Corporation or VinegarHQ and does not distribute the Roblox client.
## About this fork
Nightcap is a fork of [komaruworld/mocktail](https://github.com/komaruworld/mocktail)
with extra fixes for playing on a PC. The program and config paths keep the
`mocktail` name so upstream fixes still apply cleanly. The app ID is
`io.github.CoderDayton.nightcap`, so Nightcap and upstream can be installed
side by side.
- **Lower input lag.** Frames are shown as soon as they are ready instead of
waiting in a queue.
- **Sharper textures.** Small textures are upscaled 4x by default, and you
can replace any texture with your own PNG in `~/.config/mocktail/textures`.
A ready-made crosshair is in [`textures/`](textures/README.md). Set
`MOCKTAIL_SMALL_TEXTURE_UPSCALE=1` to turn the upscale off.
- **ETC2 textures on any GPU.** Games that use compressed mobile textures now
render correctly on desktop GPUs, without stutter.
- **No more error 319 kicks.** Servers no longer drop you shortly after
joining.
- **Correct device info.** Roblox sees your real RAM and screen size.
- **Better desktop launcher.** Runs on Wayland and always finds the installed
binary.
Everything else works the same as upstream. See the
[roadmap](docs/ROADMAP.md) for what is coming next, the
[changelog](CHANGELOG.md) for what each release changed,
[performance](docs/PERFORMANCE.md) for CPU load, heat and the knobs that change
them, and the [FAQ](FAQ.md) for Discord RPC, logs, and VR.
## Install with Flatpak
```bash
flatpak install --user https://coderdayton.github.io/nightcap/nightcap.flatpakref
flatpak run io.github.CoderDayton.nightcap
```
Updates arrive through `flatpak update`. Nightcap is not on Flathub yet.
## AppImage
Grab the AppImage from the
[latest release](https://github.com/CoderDayton/nightcap/releases/latest),
make it executable, and run it:
```bash
chmod +x Nightcap-x86_64.AppImage
./Nightcap-x86_64.AppImage
```
## Distribution packages
Each release also carries a DEB, an RPM, and a pacman package, with a
`.sha256` beside every file on the
[releases page](https://github.com/CoderDayton/nightcap/releases/latest):
```bash
sudo apt install ./nightcap_<version>-1_amd64.deb
sudo dnf install ./nightcap-<version>-1.x86_64.rpm
sudo pacman -U nightcap-<version>-1-x86_64.pkg.tar.zst
```
They install `/usr/bin/mocktail`, so they conflict with upstream's `mocktail`
package. Nightcap is not in any distribution repository or the AUR; the
recipes under `packaging/aur` are kept for local builds only.
Or build and install from source using the steps under
[Building](#building).
## How it works
```
@@ -19,102 +87,63 @@ Roblox APK -> signature and ABI checks -> Bionic + JNI -> SDL3 + Vulkan/OpenGL
```
The APK is checked before any native code is loaded. It is downloaded on first
launch and is not bundled with Mocktail. The last working copy is kept in case
launch and is not bundled with Nightcap. The last working copy is kept in case
an update fails.
## Install with Flatpak
Install the stable release from Flathub:
```bash
flatpak install flathub space.bigrat.mocktail
flatpak run space.bigrat.mocktail
```
Nightly builds are produced automatically from the latest `main` branch:
```bash
flatpak install --user https://mocktail.bigrat.space/mocktail.flatpakref
flatpak run space.bigrat.mocktail
```
## Install from the AUR
Arch Linux users can install either the pinned source release (`mocktail`) or
the current development version (`mocktail-git`) with an AUR helper:
```bash
paru -S mocktail-git
# or
yay -S mocktail-git
```
Use `mocktail` instead of `mocktail-git` to build the pinned release from
source, or install the stable prebuilt package with `paru -S mocktail-bin` or
`yay -S mocktail-bin`.
## Install with DNF
Fedora 44 users can install either the stable `mocktail` package or the
`mocktail-nightly` package built from `main`:
```bash
sudo curl -fsSL https://mocktail.bigrat.space/rpm/mocktail.repo \
-o /etc/yum.repos.d/mocktail.repo
sudo dnf install mocktail
# or
sudo dnf install mocktail-nightly
```
## Install with APT
Ubuntu 26.04 users can install either the stable `mocktail` package or the
`mocktail-nightly` package built from `main`:
```bash
sudo install -d -m 0755 /etc/apt/keyrings
sudo curl -fsSL https://mocktail.bigrat.space/mocktail-packages.gpg \
-o /etc/apt/keyrings/mocktail.gpg
echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/mocktail.gpg] https://mocktail.bigrat.space/apt mocktail main" | \
sudo tee /etc/apt/sources.list.d/mocktail.list >/dev/null
sudo apt update
sudo apt install mocktail
# or
sudo apt install mocktail-nightly
```
## Direct downloads
Direct AppImage, DEB, and RPM downloads are available from the
[Website](https://mocktail.bigrat.space/) and
[GitHub Releases](https://github.com/komaruworld/mocktail/releases).
<details>
<summary>Screenshots</summary>
![Roblox home in Mocktail](assets/screenshots/flatpak-home.png)
![Roblox home in Nightcap](assets/screenshots/flatpak-home.png)
![Roblox gameplay in Mocktail](assets/screenshots/flatpak-gameplay-tower.png)
![Roblox gameplay in Nightcap](assets/screenshots/flatpak-gameplay-tower.png)
![Roblox experience in Mocktail](assets/screenshots/flatpak-gameplay-lobby.png)
![Roblox experience in Nightcap](assets/screenshots/flatpak-gameplay-lobby.png)
</details>
## FFlag overrides
Put a JSON object in `$XDG_CONFIG_HOME/mocktail/fflags.json` (usually
`~/.config/mocktail/fflags.json`). Nightcap applies it on the next launch.
PC profiles use the legacy Charts page by default to avoid the blank screen
caused by `Color3` errors in the newer SDUI page. An explicit
`FFlagLuaAppChartsAppPage` override takes precedence over this default.
```json
{
"FFlagExample": "True",
"DFIntExample": "120"
}
```
## Settings
Edit `~/.config/mocktail/config.yaml`. Useful options:
- `graphics.frame_rate_limit: display` to unlock FPS to your monitor's rate.
- `graphics.vsync: off` for the lowest lag, at the cost of tearing.
- `graphics.backend: opengl` if Vulkan does not work on your GPU.
## Building
Only Linux `x86_64` is supported. Building requires CMake 3.20+, Git,
pkg-config, a C++17 compiler, SDL 3.4+, SDL3_ttf, Vulkan, EGL, libplacebo,
fontconfig, libcurl, OpenSSL, libelf, libyaml, minizip, Capstone 5, utf8proc,
nlohmann/json, GTK4, libadwaita 1.6+, and WebKitGTK 6.0.
Linux `x86_64` is supported. Experimental FreeBSD 15.1 Linuxulator support has
been tested with an `x86_64` Fedora 44 userspace. On FreeBSD, Nightcap runs
inside Linuxulator; it is not a native FreeBSD binary. Building requires CMake
3.20+, Git, pkg-config, LLD, binutils, a C++17 compiler, SDL 3.4+, SDL3_ttf,
Vulkan, EGL, libplacebo, fontconfig, libcurl, OpenSSL, libelf, libyaml, libpng,
minizip, Capstone 5, utf8proc, nlohmann/json, GTK4, libadwaita 1.6+, and
WebKitGTK 6.0.
See the [FreeBSD Guide](packaging/freebsd/README.md) for setup and launch instructions.
<details>
<summary>Ubuntu 26.04+</summary>
```bash
sudo apt update
sudo apt install build-essential cmake git ninja-build pkg-config \
sudo apt install build-essential cmake git ninja-build pkg-config lld \
libsdl3-dev libsdl3-ttf-dev libcurl4-openssl-dev libssl-dev \
nlohmann-json3-dev libyaml-dev libelf-dev libminizip-dev \
nlohmann-json3-dev libyaml-dev libpng-dev libelf-dev libminizip-dev \
libcapstone-dev libgtk-4-dev libadwaita-1-dev libwebkitgtk-6.0-dev \
libutf8proc-dev libfontconfig1-dev libegl-dev libvulkan-dev \
libplacebo-dev zlib1g-dev
@@ -125,8 +154,8 @@ sudo apt install build-essential cmake git ninja-build pkg-config \
<summary>Arch Linux</summary>
```bash
sudo pacman -S --needed base-devel cmake git ninja pkgconf sdl3 sdl3_ttf \
curl openssl nlohmann-json libyaml libelf minizip capstone gtk4 \
sudo pacman -S --needed base-devel cmake git ninja pkgconf lld sdl3 sdl3_ttf \
curl openssl nlohmann-json libyaml libpng libelf minizip capstone gtk4 \
libadwaita webkitgtk-6.0 libutf8proc fontconfig libglvnd \
libplacebo vulkan-headers vulkan-icd-loader zlib
```
@@ -136,9 +165,10 @@ sudo pacman -S --needed base-devel cmake git ninja pkgconf sdl3 sdl3_ttf \
<summary>Fedora 44+</summary>
```bash
sudo dnf install gcc-c++ cmake git ninja-build pkgconf-pkg-config \
sudo dnf install gcc-c++ cmake git ninja-build pkgconf-pkg-config lld \
SDL3-devel SDL3_ttf-devel libcurl-devel openssl-devel \
nlohmann-json-devel libyaml-devel elfutils-libelf-devel minizip-ng-compat-devel \
nlohmann-json-devel libyaml-devel libpng-devel elfutils-libelf-devel \
minizip-ng-compat-devel \
capstone-devel gtk4-devel libadwaita-devel webkitgtk6.0-devel \
utf8proc-devel fontconfig-devel libglvnd-devel vulkan-headers \
vulkan-loader-devel libplacebo-devel zlib-ng-compat-devel
@@ -146,19 +176,47 @@ sudo dnf install gcc-c++ cmake git ninja-build pkgconf-pkg-config \
</details>
```bash
git clone --recurse-submodules https://github.com/komaruworld/mocktail.git
cd mocktail
git clone --recurse-submodules https://github.com/CoderDayton/nightcap.git
cd nightcap
make build
./build/mocktail
```
To install it for your user, with an app menu entry and Roblox link handling:
```bash
make install PREFIX=~/.local
```
Use `sudo make install` instead to install system-wide under `/usr`. To make
Roblox website links open the build tree copy without installing, run
`make register-url-handler`.
To run the same checks as CI before each commit and push, install
[lefthook](https://github.com/evilmartians/lefthook) and run once:
```bash
lefthook install
```
## Known limitations
Nightcap does not currently work with `hardened_malloc`. Using
`hardened_malloc` may cause Nightcap to fail to start or crash during runtime.
Run Nightcap without `hardened_malloc` enabled.
## License
[Apache License 2.0](LICENSE). Third-party components keep their own licenses.
## Support
You can support the project by giving it a star or with cryptocurrency:
Support this fork by giving it a star or with cryptocurrency:
- LTC: `LdABR2ELRYrUESWZWrEk1uNx38gHHMuHUU`
- SOL: `tjeDnPoWeyW8zfTf8c5CVAm5QTV8vFPaPouaScAdwfT`
Support the upstream project, which does most of the heavy lifting:
- USDT (TON): `UQCi6Yzcc9cOctoij6n_r1K90-OdVxAT0D_xo2UzGKkQaJDY`
- USDT (TRC20): `TNPMG9Vig2xiuo2r1QqnXRChPH7Vu28Jmx`
+2 -6
View File
@@ -24,6 +24,7 @@ set_target_properties(mocktail_audio_core PROPERTIES
add_library(Mocktail::AudioCore ALIAS mocktail_audio_core)
add_library(mocktail_audio_sdl SHARED
${MOCKTAIL_AUDIO_ROOT}/src/audio/sdl_audio_capture.cc
${MOCKTAIL_AUDIO_ROOT}/src/audio/sdl_audio_sink.cc
)
target_include_directories(mocktail_audio_sdl PUBLIC
@@ -62,6 +63,7 @@ add_library(Mocktail::FmodJavaAudioRuntime ALIAS
add_library(mocktail_fmod_jni_audio_bridge STATIC
${MOCKTAIL_AUDIO_ROOT}/src/audio/fmod_jni_audio_bridge.cc
${MOCKTAIL_AUDIO_ROOT}/src/audio/webrtc_jni_audio_bridge.cc
)
target_include_directories(mocktail_fmod_jni_audio_bridge PUBLIC
${MOCKTAIL_AUDIO_ROOT}/include
@@ -199,10 +201,4 @@ if(BUILD_TESTING AND TARGET GTest::gtest_main)
gtest_discover_tests(fmod_jni_audio_bridge_test
PROPERTIES ENVIRONMENT "SDL_AUDIODRIVER=dummy"
)
add_test(NAME audio_readiness_gate_test
COMMAND bash
${MOCKTAIL_AUDIO_ROOT}/tests/audio_readiness_gate_test.sh
${MOCKTAIL_AUDIO_ROOT}/scripts/audio_readiness_gate.sh
)
endif()
+6 -1
View File
@@ -16,8 +16,10 @@ add_library(mocktail_compat STATIC
${MOCKTAIL_ELF_COMPAT_ROOT}/src/compat/bionic_dns_runtime.cc
${MOCKTAIL_ELF_COMPAT_ROOT}/src/compat/bionic_host_libc_runtime.cc
${MOCKTAIL_ELF_COMPAT_ROOT}/src/compat/bionic_large_file_runtime.cc
${MOCKTAIL_ELF_COMPAT_ROOT}/src/compat/bionic_prctl_runtime.cc
${MOCKTAIL_ELF_COMPAT_ROOT}/src/compat/bionic_rwlock_runtime.cc
${MOCKTAIL_ELF_COMPAT_ROOT}/src/compat/bionic_signal_runtime.cc
${MOCKTAIL_ELF_COMPAT_ROOT}/src/compat/bionic_socket_runtime.cc
${MOCKTAIL_ELF_COMPAT_ROOT}/src/compat/bionic_semaphore_runtime.cc
${MOCKTAIL_ELF_COMPAT_ROOT}/src/compat/bionic_pthread_create_runtime.cc
${MOCKTAIL_ELF_COMPAT_ROOT}/src/compat/bionic_pthread_key_runtime.cc
@@ -34,12 +36,15 @@ add_library(Mocktail::Compat ALIAS mocktail_compat)
target_include_directories(mocktail_compat PUBLIC
${MOCKTAIL_ELF_COMPAT_ROOT}/include
)
target_compile_definitions(mocktail_compat PRIVATE
"MOCKTAIL_INSTALL_LIBDIR=\"${CMAKE_INSTALL_LIBDIR}\""
)
target_link_libraries(mocktail_compat PUBLIC
PkgConfig::LIBELF
nlohmann_json::nlohmann_json
Threads::Threads
)
target_link_libraries(mocktail_compat PRIVATE Mocktail::CryptoFoundation)
target_link_libraries(mocktail_compat PRIVATE OpenSSL::Crypto)
target_compile_features(mocktail_compat PUBLIC cxx_std_17)
if(COMMAND mocktail_apply_compile_options)
+14 -3
View File
@@ -13,11 +13,13 @@ get_filename_component(MOCKTAIL_INPUT_ROOT "${CMAKE_CURRENT_LIST_DIR}/.."
)
add_library(mocktail_input_runtime STATIC
${MOCKTAIL_INPUT_ROOT}/src/runtime/roblox_gamepad_input.cc
${MOCKTAIL_INPUT_ROOT}/src/runtime/roblox_input_native_adapter.cc
${MOCKTAIL_INPUT_ROOT}/src/runtime/roblox_input_router.cc
${MOCKTAIL_INPUT_ROOT}/src/runtime/roblox_native_text_box_info_reader.cc
${MOCKTAIL_INPUT_ROOT}/src/runtime/roblox_text_display_state.cc
${MOCKTAIL_INPUT_ROOT}/src/runtime/roblox_text_editor.cc
${MOCKTAIL_INPUT_ROOT}/src/runtime/roblox_text_font_resolver.cc
${MOCKTAIL_INPUT_ROOT}/src/runtime/roblox_text_surface_overlay.cc
${MOCKTAIL_INPUT_ROOT}/src/runtime/roblox_window_input_runtime.cc
)
@@ -36,12 +38,14 @@ target_link_libraries(mocktail_input_runtime PUBLIC
PRIVATE
PkgConfig::FONTCONFIG
SDL3_ttf::SDL3_ttf
nlohmann_json::nlohmann_json
)
target_compile_features(mocktail_input_runtime PUBLIC cxx_std_17)
mocktail_apply_compile_options(mocktail_input_runtime)
if(BUILD_TESTING AND TARGET GTest::gtest_main)
add_executable(roblox_input_router_test
${MOCKTAIL_INPUT_ROOT}/tests/roblox_gamepad_input_test.cc
${MOCKTAIL_INPUT_ROOT}/tests/roblox_input_router_test.cc
)
target_link_libraries(roblox_input_router_test PRIVATE
@@ -68,6 +72,15 @@ if(BUILD_TESTING AND TARGET GTest::gtest_main)
)
mocktail_apply_compile_options(roblox_text_display_state_test)
add_executable(roblox_text_font_resolver_test
${MOCKTAIL_INPUT_ROOT}/tests/roblox_text_font_resolver_test.cc
)
target_link_libraries(roblox_text_font_resolver_test PRIVATE
Mocktail::InputRuntime
GTest::gtest_main
)
mocktail_apply_compile_options(roblox_text_font_resolver_test)
add_executable(roblox_input_native_adapter_test
${MOCKTAIL_INPUT_ROOT}/tests/roblox_input_native_adapter_test.cc
)
@@ -92,9 +105,7 @@ if(BUILD_TESTING AND TARGET GTest::gtest_main)
gtest_discover_tests(roblox_input_router_test)
gtest_discover_tests(roblox_text_editor_test)
gtest_discover_tests(roblox_text_display_state_test)
gtest_discover_tests(roblox_text_font_resolver_test)
gtest_discover_tests(roblox_input_native_adapter_test)
gtest_discover_tests(roblox_native_text_box_info_reader_test)
add_test(NAME input_readiness_gate_synthetic
COMMAND bash ${MOCKTAIL_INPUT_ROOT}/tests/input_readiness_gate_test.sh
)
endif()
+27 -1
View File
@@ -1,3 +1,4 @@
# Modified by vii from komaruworld/mocktail. See README "About this fork".
# Copyright 2026 Mocktail Project Authors
# Licensed under the Apache License, Version 2.0.
@@ -9,9 +10,10 @@ get_filename_component(MOCKTAIL_PLATFORM_GRAPHICS_ROOT
find_package(SDL3 3.4 REQUIRED CONFIG)
find_path(MOCKTAIL_EGL_INCLUDE_DIR EGL/egl.h REQUIRED)
find_path(MOCKTAIL_GLES3_INCLUDE_DIR GLES3/gl3.h REQUIRED)
set(MOCKTAIL_WINDOW_ICON_PNG
"${MOCKTAIL_PLATFORM_GRAPHICS_ROOT}/packaging/icons/hicolor/48x48/apps/space.bigrat.mocktail.png"
"${MOCKTAIL_PLATFORM_GRAPHICS_ROOT}/packaging/icons/hicolor/48x48/apps/io.github.CoderDayton.nightcap.png"
)
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS
"${MOCKTAIL_WINDOW_ICON_PNG}"
@@ -69,6 +71,7 @@ add_library(mocktail_platform_sdl STATIC
${MOCKTAIL_PLATFORM_GRAPHICS_ROOT}/src/platform/sdl_application_metadata.cc
${MOCKTAIL_PLATFORM_GRAPHICS_ROOT}/src/platform/sdl_display_refresh_capabilities.cc
${MOCKTAIL_PLATFORM_GRAPHICS_ROOT}/src/platform/sdl_event_converter.cc
${MOCKTAIL_PLATFORM_GRAPHICS_ROOT}/src/platform/sdl_gamepad_manager.cc
${MOCKTAIL_PLATFORM_GRAPHICS_ROOT}/src/platform/sdl_platform_runtime.cc
${MOCKTAIL_PLATFORM_GRAPHICS_ROOT}/src/platform/sdl_text_clipboard.cc
${MOCKTAIL_PLATFORM_GRAPHICS_ROOT}/src/platform/sdl_window_icon.cc
@@ -102,6 +105,18 @@ target_link_libraries(mocktail_graphics_foundation PRIVATE
target_compile_features(mocktail_graphics_foundation PUBLIC cxx_std_17)
add_library(Mocktail::GraphicsFoundation ALIAS mocktail_graphics_foundation)
add_library(mocktail_gles_text_overlay STATIC
${MOCKTAIL_PLATFORM_GRAPHICS_ROOT}/src/graphics/gles_text_overlay_compositor.cc
)
target_include_directories(mocktail_gles_text_overlay
PUBLIC ${MOCKTAIL_PLATFORM_GRAPHICS_ROOT}/include
PRIVATE ${MOCKTAIL_GLES3_INCLUDE_DIR}
)
target_link_libraries(mocktail_gles_text_overlay PUBLIC SDL3::SDL3)
target_compile_features(mocktail_gles_text_overlay PUBLIC cxx_std_17)
mocktail_apply_compile_options(mocktail_gles_text_overlay)
add_library(Mocktail::GlesTextOverlay ALIAS mocktail_gles_text_overlay)
add_library(mocktail_sdl_vulkan_wsi STATIC
${MOCKTAIL_PLATFORM_GRAPHICS_ROOT}/src/graphics/sdl_vulkan_wsi.cc
${MOCKTAIL_PLATFORM_GRAPHICS_ROOT}/src/graphics/android_vulkan_wsi_adapter.cc
@@ -119,6 +134,16 @@ target_compile_features(mocktail_sdl_vulkan_wsi PUBLIC cxx_std_17)
add_library(Mocktail::SdlVulkanWsi ALIAS mocktail_sdl_vulkan_wsi)
if(BUILD_TESTING AND TARGET GTest::gtest_main)
add_executable(gles_text_overlay_compositor_test
${MOCKTAIL_PLATFORM_GRAPHICS_ROOT}/tests/gles_text_overlay_compositor_test.cc
)
target_include_directories(gles_text_overlay_compositor_test PRIVATE
${MOCKTAIL_GLES3_INCLUDE_DIR}
)
target_link_libraries(gles_text_overlay_compositor_test PRIVATE
Mocktail::GlesTextOverlay
GTest::gtest_main
)
add_executable(bionic_egl_bridge_test
${MOCKTAIL_PLATFORM_GRAPHICS_ROOT}/tests/bionic_egl_bridge_test.cc
)
@@ -152,6 +177,7 @@ if(BUILD_TESTING AND TARGET GTest::gtest_main)
GTest::gtest_main
)
include(GoogleTest)
gtest_discover_tests(gles_text_overlay_compositor_test)
gtest_discover_tests(bionic_egl_bridge_test)
gtest_discover_tests(platform_graphics_foundation_test)
gtest_discover_tests(display_refresh_capabilities_test)
-14
View File
@@ -1,17 +1,3 @@
// Copyright 2026 Mocktail Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MOCKTAIL_PLATFORM_SDL_WINDOW_ICON_DATA_H_
#define MOCKTAIL_PLATFORM_SDL_WINDOW_ICON_DATA_H_
+75 -26
View File
@@ -1,3 +1,4 @@
# Modified by vii from komaruworld/mocktail. See README "About this fork".
# Mocktail configuration.
# The file is created once with mode 0600. Mocktail never replaces your edits.
@@ -31,17 +32,25 @@ runtime:
# active verified payload.
# roblox_library: /absolute/path/to/libroblox.so
appearance:
# String (default: roblox): use Roblox's saved account theme. Supported
# overrides: dark, light, or system (follow the desktop color scheme).
theme: roblox
graphics:
# String (default: direct-vulkan): graphics backend. Use opengl on hardware
# without a working Vulkan driver; OpenGL ES 3.0 or newer is required.
# Recommended values: direct-vulkan, opengl, system, angle-vulkan.
backend: direct-vulkan
# String or integer (default: display): requested frame-rate policy.
# Supported values: display, unlimited, 30, 60, 120, 144, 240. Unlimited
# selects Roblox's unmodified 240-FPS scheduler maximum.
frame_rate_limit: display
# String (default: auto): presentation synchronization: auto, on, or off.
vsync: auto
# Optional string or integer: Mocktail frame-rate override. Leave commented
# (or use -1) to let Roblox Basic Settings own the cap. Supported overrides:
# display, unlimited, or any positive integer; fixed values are forwarded to
# DFIntTaskSchedulerTargetFps without a whitelist.
# frame_rate_limit: -1
# Optional presentation synchronization override: auto, on, or off. auto
# presents through the lowest-latency synchronized mode the driver offers
# unless frame_rate_limit is unlimited.
# vsync: off
performance:
# Boolean (default: false): size Roblox scheduler/render queues from every
@@ -63,12 +72,34 @@ audio:
# String (default: default): `default` follows the host system output.
# To pin output, copy an exact SDL device name printed during startup.
output_device: default
# String (default: default): input device name, `id:<number>` printed during
# startup, or `disabled`. IDs disambiguate duplicate names but may change
# between boots; prefer the exact device name when it is unique.
input_device: default
input:
# Boolean (default: false): send mouse look deltas in the host's own units.
# They are otherwise scaled by the window's pixel density divided by the
# desktop display scale, which cancels to 1.0 unless fractional scaling makes
# the two disagree. Turn this on when first-person aiming feels too slow or
# too fast under fractional scaling. The pointer position is unaffected.
raw_mouse: false
integrations:
discord_rpc:
# Boolean (default: false): publish Mocktail activity to Discord Desktop.
# This never signs in to Discord and never reads an account token.
fleasion:
# Boolean (default: false): trust Fleasion's CA without editing Roblox files.
enabled: false
# String: match Fleasion's routing mode: env or hosts.
proxy_mode: env
# Integer: Fleasion's local HTTP proxy port in env mode.
proxy_port: 58443
# Optional absolute public CA path. Default: $XDG_CONFIG_HOME/Fleasion/proxy_ca/ca.crt
# or ~/.config/Fleasion/proxy_ca/ca.crt. Never select ca.key.
# ca_certificate: /home/user/.config/Fleasion/proxy_ca/ca.crt
discord_rpc:
# Boolean (default: true): publish Nightcap activity to Discord Desktop.
# This never signs in to Discord and never reads an account token.
enabled: true
# Boolean (default: true): show the current Roblox experience name.
show_place_name: true
# Boolean (default: true): show how long the current session has run.
@@ -81,30 +112,51 @@ integrations:
public_servers_only: true
# button_label: Join Server
# English defaults. Uncomment only the lines you want to customize.
# Text and image fields accept {place_name}, {place_icon}, and
# {creator_name}. A field that expands an empty placeholder is left out,
# and a "" title shows the app name.
# title, state, and the images fields may be "" to hide them.
# text:
# browsing: Browsing experiences
# joining: Joining an experience
# playing: "{place_name}"
# state: Playing Roblox
# playing: "Playing {place_name}"
# state: "by {creator_name}"
# unknown_place: Unknown experience
# title: Roblox
# Images take an https:// URL, or an asset key uploaded to application_id.
# large: "" hides both images; the small image needs a large one.
# images:
# large: "{place_icon}"
# large_text: "{place_name}"
# small: roblox_small
# small_text: Roblox
# Advanced: overrides the application bundled by the package maintainer.
# application_id: 123456789012345678
window:
# Integer (default: 1280): initial window width in pixels.
# Integer (default: 1280): initial window width in logical desktop units.
width: 1280
# Integer (default: 720): initial window height in pixels.
# Integer (default: 720): initial window height in logical desktop units.
height: 720
# String (default: Roblox): window title.
title: Roblox
# Boolean (default: false): render at physical display-pixel density instead
# of the logical desktop resolution. Enable only for sharper high-DPI output.
high_dpi: false
# Optional host HTTP proxy. Uncomment both values together. The host must not
# contain a scheme such as http://.
# network:
# # String (optional, default: disabled): proxy host name or IP address.
# proxy_host: 127.0.0.1
# # Integer (optional, default: disabled): proxy TCP port from 1 to 65535.
# proxy_port: 8080
network:
# Boolean (default: false): follow the host HTTP/SOCKS5 proxy and
# automatically use its current port. Do not combine with a fixed proxy.
use_system_proxy: false
# Optional fixed HTTP proxy. Uncomment both values together. The host must
# not contain a scheme such as http://.
# String (optional, default: disabled): proxy host name or IP address.
# proxy_host: 127.0.0.1
# Integer (optional, default: disabled): proxy TCP port from 1 to 65535.
# proxy_port: 8080
# Absolute path (optional): custom PEM CA bundle. Mocktail reads this file
# directly and does not replace it during Roblox payload updates.
# ca_bundle: /home/user/.config/mocktail/cacert.pem
updates:
# Boolean (default: true): check the latest Roblox version, derive an exact
@@ -112,12 +164,9 @@ updates:
# backend, and promote only on success. An existing current payload is
# preserved when probation fails.
automatic: true
# Boolean (default: true): testing track. Always try provider latest, even
# when it has no verified payload profile, and do not download or activate
# an older supported payload in its place. Exact HostAbi derivation and two
# canaries are still required before latest can become current.
testing_latest_only: true
# String (default: apk-pure): direct x86_64 APK provider.
# String (default: apk-pure): direct x86_64 APK provider selection. Both
# accepted values resolve the provider chain, which currently starts with
# APKPure. Supported values: auto, apk-pure.
source: apk-pure
# Reserved for desktop update integrations. `mocktail_updater` itself never
# launches another process after changing the active payload.
+44
View File
@@ -42,6 +42,50 @@
"allow_host_abi_bridges": true,
"allow_host_constructor_replay": true,
"reason": "Verified 2026-07-16 on the current host: independently signature-matched native allocator and pre-JNI state, 3479 constructors replayed without recovery, JNI_OnLoad and ASMA/V2 startup, 2988 Vulkan shaders, a real vkQueuePresentKHR, and explicit lifecycle teardown to Stopped; legacy executable patches and signal recovery remain disabled."
},
{
"version_name": "2.734.917",
"version_code": 2908,
"elf_build_id": "63c5109637b7d7b2bdb8ed8f858023ff5ef49326",
"status": "supported",
"default_allowed": true,
"allow_legacy_binary_patches": false,
"allow_host_abi_bridges": true,
"allow_host_constructor_replay": true,
"user_game_settings_fullscreen_setter_rva": "0x44fee64",
"fmod_output_device_bridge": {
"vtable_rva": "0x6b7eee8",
"string_constructor_rva": "0x1cfd5ec",
"count_method_rva": "0x32736d2",
"info_method_rva": "0x3273772",
"current_method_rva": "0x3273722",
"select_method_rva": "0x32734a6"
},
"reason": "Verified 2026-08-28 on the current host: the machine-derived exact-Build-ID profile passed two isolated no-recovery Tier C Vulkan canaries; fullscreen state synchronization and Build-ID-scoped FmodAudioDevice output interposition were re-derived for the current payload; legacy executable patches and signal recovery remain disabled."
},
{
"version_name": "2.736.1408",
"version_code": 2998,
"elf_build_id": "ade08266c67aee88ec9c1d00902150e1684dad3a",
"status": "supported",
"default_allowed": true,
"allow_legacy_binary_patches": false,
"allow_host_abi_bridges": true,
"allow_host_constructor_replay": true,
"user_game_settings_fullscreen_setter_rva": "0x45ad8aa",
"fmod_output_device_bridge": {
"vtable_rva": "0x6c58040",
"string_constructor_rva": "0x1d32df8",
"count_method_rva": "0x32d0ee0",
"info_method_rva": "0x32d0f80",
"current_method_rva": "0x32d0f30",
"select_method_rva": "0x32d0cb4",
"input_count_method_rva": "0x32d12dc",
"input_info_method_rva": "0x32d12ea",
"input_current_method_rva": "0x32d13a6",
"input_select_method_rva": "0x32d0a56"
},
"reason": "Verified 2026-09-06: exact Build-ID profile passed two isolated no-recovery Tier C Vulkan canaries and real game sessions, including fullscreen state synchronization and host FMOD output-device interposition; legacy executable patches and signal recovery remain disabled."
}
]
}
+35 -35
View File
@@ -1,64 +1,64 @@
{
"schema_version": 1,
"elf_build_id": "1686400865ae0e408cd7bd67de7a439625c6fd13",
"payload_sha256": "3e9c26c81186f93458ff65d8a8bc240c220974db02b8388b91340b77b336997d",
"payload_id": "2628-1686400865ae0e408cd7bd67de7a439625c6fd13",
"payload_path": "payloads/2628-1686400865ae0e408cd7bd67de7a439625c6fd13",
"elf_build_id": "ade08266c67aee88ec9c1d00902150e1684dad3a",
"payload_sha256": "4343a6a900d1fca27ff8e10b9d9c86bee40a5d0ba547cb29903b7769f4f42a9d",
"payload_id": "2998-ade08266c67aee88ec9c1d00902150e1684dad3a",
"payload_path": "payloads/2998-ade08266c67aee88ec9c1d00902150e1684dad3a",
"reference": {
"elf_build_id": "d0cb1fa0deb3d9161b4cd77530cbcd2e50de3a21",
"payload_sha256": "92ca0e4d8bc4e1c7a37fc2b52544869299ec8b9a40ea8da96ebac66660e0b1af"
"elf_build_id": "63c5109637b7d7b2bdb8ed8f858023ff5ef49326",
"payload_sha256": "72c3b1a5c151b5ac0bfc5394ce3a91775884fbd777589ccab8ac1c4f9885f739"
},
"profile": {
"elf_build_id": "1686400865ae0e408cd7bd67de7a439625c6fd13",
"elf_build_id": "ade08266c67aee88ec9c1d00902150e1684dad3a",
"bridge_entries": [
{
"rva": "0x1c39ea2",
"rva": "0x1d2c1e2",
"kind": "allocate",
"label": "small-allocate"
},
{
"rva": "0x1fe4c84",
"rva": "0x1d2f43a",
"kind": "usable_size",
"label": "usable-size"
},
{
"rva": "0x203daae",
"rva": "0x21e68bf",
"kind": "reallocate",
"label": "reallocate"
},
{
"rva": "0x1c39c3a",
"rva": "0x1d2bf82",
"kind": "allocate",
"label": "allocate"
},
{
"rva": "0x1c8dbf8",
"rva": "0x1d47a63",
"kind": "aligned_allocate",
"label": "aligned-allocate-direct"
},
{
"rva": "0x1c3eb72",
"rva": "0x1d2f9d9",
"kind": "free",
"label": "free"
}
],
"data_seeds": {
"allocator_object_slot": "0x7030ae0",
"empty_string_slot": "0x703a7e8",
"jni_singleton_slot": "0x72a16d0",
"allocator_object_slot": "0x7652e70",
"empty_string_slot": "0x775fd28",
"jni_singleton_slot": "0x7bb4340",
"jni_singleton_bytes": 1024,
"arena_initializer": "0x1c39f1d",
"allocator_thread_initializer": "0x2040817",
"arena_guard_slot": "0x71e92f0",
"arena_table_slot": "0x71e9380",
"arena_initializer": "0x1d2c5f8",
"allocator_thread_initializer": "0x1db7b57",
"arena_guard_slot": "0x7af0430",
"arena_table_slot": "0x7af04c0",
"arena_table_slot_count": 4194304
},
"native_allocator": {
"allocate": "0x1c39c3a",
"deallocate": "0x1c3eb72"
"allocate": "0x1d2bf82",
"deallocate": "0x1d2f9d9"
},
"init_array_offset": "0x6c2c9b0",
"init_array_count": 3481,
"init_array_offset": "0x7057818",
"init_array_count": 3570,
"constructor_run_ranges": [
{
"begin": 2,
@@ -66,32 +66,32 @@
},
{
"begin": 5,
"end_exclusive": 3481
"end_exclusive": 3570
}
],
"native_mimalloc_constructor_run_ranges": [
{
"begin": 2,
"end_exclusive": 3481
"end_exclusive": 3570
}
],
"native_mimalloc_thread_initializer_after_constructor": 2,
"native_pre_jni_bootstrap": {
"registry_initializer": "0x1fe72e5",
"registry_slot": "0x71ed428"
"registry_initializer": "0x21e8e09",
"registry_slot": "0x7af4598"
},
"default_allocator_strategy": "native_mimalloc"
},
"derivation_anchors": {
"signature_version": 1,
"allocator_object_initializer_rva": "0x1fbf1de",
"empty_string_initializer_rva": "0x1fc82b0",
"jni_singleton_initializer_rva": "0x1c39a34",
"allocator_object_initializer_rva": "0x21bde7d",
"empty_string_initializer_rva": "0x21c9a86",
"jni_singleton_initializer_rva": "0x1d2aec0",
"constructor_rvas": {
"2": "0x1c3c764",
"3": "0x1c38c00",
"4": "0x1c39884",
"5": "0x1c39a34"
"2": "0x1d2f731",
"3": "0x1d2af84",
"4": "0x1d2bc08",
"5": "0x1d2aec0"
}
}
}
+12 -11
View File
@@ -1,23 +1,24 @@
{
"schema_version": 1,
"package": "com.roblox.client",
"version_name": "2.725.1142",
"version_code": 2546,
"version_name": "2.736.1408",
"version_code": 2998,
"abi": "x86_64",
"elf_build_id": "d0cb1fa0deb3d9161b4cd77530cbcd2e50de3a21",
"elf_build_id": "ade08266c67aee88ec9c1d00902150e1684dad3a",
"sha256": {
"libroblox": "92ca0e4d8bc4e1c7a37fc2b52544869299ec8b9a40ea8da96ebac66660e0b1af",
"base_apk": "eba476fec3020eaceb28436c1d160eb35d6689d8ffb060e09678535584babc7a",
"x86_64_split_apk": "5c4193c9700481079cee958b38ba9cbb97768b0a130b7f704ed084753f01ce2b"
"libroblox": "4343a6a900d1fca27ff8e10b9d9c86bee40a5d0ba547cb29903b7769f4f42a9d",
"base_apk": "a4a71ec39df6619a56ab7adce0959d1d60354835485b53806f4026914287ea86",
"x86_64_split_apk": "a4a71ec39df6619a56ab7adce0959d1d60354835485b53806f4026914287ea86"
},
"signing_certificates_sha256": [
"2bebd189e8d3106401347056c93d045b61e20e22d0c3cbed85474aeb00a3d12a",
"44932ea35a17a267372d71b54d1a0cb3da0dca5113e94406ae2fe18090ba1477"
],
"assets": {
"file_count": 1837
"file_count": 1836,
"sha256_tree": "caee48e11c194bc11852493f53ff67d5e2bb233172f12b87ea5fca97c0c10840"
},
"source": "sober-update-service",
"imported_at": "2026-07-09T19:12:54Z",
"compatibility_status": "supported"
"source": "apk-pure-native",
"imported_at": "2026-08-31T16:30:19Z",
"compatibility_status": "supported",
"apk_layout": "monolithic"
}
+110
View File
@@ -0,0 +1,110 @@
# Benchmarking
A pull request that claims a speedup shows before and after numbers taken
the way this guide describes. Numbers from different machines are not
compared with each other. Each run is compared with a run on the same
machine.
## Two layers, two tools
Roblox measures its own work. Nightcap measures the layer between Roblox and
your driver.
| Layer | What it covers | Tool |
| --- | --- | --- |
| Roblox | Scripts, physics, rendering jobs, network | Roblox's stats overlays, Shift+F4 and Shift+F5 in game |
| Nightcap | Vulkan adapter: present, swapchain and GPU waits, submits, ETC2 texture decode, pipeline and shader creation | `--profile` |
A Nightcap change should move the `--profile` numbers. If it only moves the
Roblox overlay numbers, say so in the pull request, since the cause is then
less direct.
## Recording a trace
```sh
mocktail --profile ~/nightcap-before.json
```
The Vulkan adapter writes a Chrome-format trace to that file, a few times a
second. The file is valid JSON after every write, so a session that crashes
or is killed still leaves a readable trace. Exit from the Roblox menu when you are done. Open the file
in [ui.perfetto.dev](https://ui.perfetto.dev) or `chrome://tracing`.
Expect about 130 bytes per event, around 15 MB per minute at 240 FPS.
Profiling is off unless `--profile` is given, and `MOCKTAIL_PROFILE_TRACE`
does the same thing as the flag.
### What the trace contains
Each slice is named after the call it times, on the thread that made it.
| Category | Slices | Arguments |
| --- | --- | --- |
| `present` | `vkQueuePresentKHR`: the whole adapter present, including the text overlay | `result` |
| `present` | `host present`: the host driver's present, nested inside the slice above | |
| `wait` | `vkAcquireNextImageKHR`, `vkAcquireNextImage2KHR`, `vkWaitForFences`, `vkWaitSemaphores`, `vkWaitSemaphoresKHR`, `vkQueueWaitIdle`, `vkDeviceWaitIdle` | |
| `submit` | `vkQueueSubmit`, `vkQueueSubmit2`, `vkQueueSubmit2KHR` | `submits` |
| `texture` | `etc2 decode`: CPU decode of ETC2 textures. On a device with a timeline semaphore this runs on the decode dispatcher, not the submitting thread | `uploads`, `compressed_bytes`, `decoded_bytes` |
| `texture` | `etc2 gather`: the part of an asynchronous upload that stays on the submitting thread. Absent when the decode ran inline | `uploads`, `compressed_bytes`, `decoded_bytes`, `ticket` |
| `pipeline` | `vkCreateGraphicsPipelines`, `vkCreateComputePipelines` | `count`, `cache` (1 when a pipeline cache was passed), `result` |
| `pipeline` | `vkCreateShaderModule` | `bytes`, `result` |
| `pipeline` | `vkCreatePipelineCache` | `initial_bytes`, `result` |
| `pump` | `nativeCallMessagesFromMainThread`: the engine's main-thread step, on the host main thread. Only calls of 20 µs or more are recorded; the empty polls between them are not. | |
The `frame interval (ms)` counter is the time between successive adapter
presents. `vkQueuePresentKHR` time minus `host present` time is the adapter's
own cost per frame.
## The benchmark runs
Run every scenario before and after the change, on the same build type
(`make build`), with the same `config.yaml`. Close other GPU-heavy apps.
Record `graphics.frame_rate_limit` and `graphics.vsync` in the pull request,
because a frame cap hides frame time gains.
Record `~/.config/gamemode.ini` too, and do not change it between the before
and after runs. `desiredgov` there decides the CPU governor for the whole
session and therefore which engine pump mode is chosen, which moves process
CPU by more than most changes under test. See
[PERFORMANCE.md](PERFORMANCE.md).
Frame interval p99 varies by 2 ms or more between identical runs, so a
difference that size is not a result. Three runs per scenario is the minimum
for any frame-time claim; slice totals for a specific subsystem are far less
noisy and can be read from fewer.
| Scenario | How to start | What to do | What it shows |
| --- | --- | --- | --- |
| Home screen, cold | `mocktail --profile home-cold.json` with an empty shader cache (below) | Wait 30 s on the home screen, exit | First-load stutter: `pipeline` and `texture` slices |
| Tower of Hell | `mocktail --profile toh.json --launch-uri "roblox://experiences/start?placeId=1962086868"` | Stand still at spawn for 60 s, exit | Steady-state frame time on a light scene |
| Murder Mystery 2 | `mocktail --profile mm2.json --launch-uri "roblox://experiences/start?placeId=142823291"` | Stay in the lobby for 60 s, exit | Frame time with many players and a busier scene |
For the cold run, point the driver's shader cache at an empty directory, so
your normal cache is left alone:
```sh
cache=$(mktemp -d)
MESA_SHADER_CACHE_DIR="$cache" __GL_SHADER_DISK_CACHE_PATH="$cache" \
mocktail --profile home-cold.json
```
`MESA_SHADER_CACHE_DIR` applies to Mesa drivers (AMD, Intel) and
`__GL_SHADER_DISK_CACHE_PATH` to the NVIDIA driver.
Run each scenario three times and keep the median run, since one run can
catch a background stall.
## Comparing runs
```sh
scripts/summarize_profile_trace.py toh-before.json toh-after.json
```
This prints a Markdown table with one column per trace: frame count, frame
interval mean, p50, p95, p99 and max, then count, total, mean, p99 and max
for every slice name, largest total first. Paste the table into the pull
request and attach both traces.
For frame time, compare p95 and p99 as well as the mean. Stutter shows up in
the tail first. For first-load work, compare the `total (ms)` rows of the
`pipeline` and `texture` slices from the cold run.
+193
View File
@@ -0,0 +1,193 @@
# Performance and CPU load
What Nightcap does to your CPU and GPU, which knobs change it, and how to
measure rather than guess.
## GameMode core pinning
Nightcap asks Feral GameMode for a performance session at startup
(`performance.gamemode`, default `auto`). GameMode raises the CPU governor and
the process priority, which helps.
On a CPU with a wide spread of per-core maximum frequencies — Intel P-core and
E-core parts, AMD parts with preferred cores — GameMode also pins the process
to the fastest cores. On a 32-thread host that can mean 4 usable threads out of
32. Roblox spreads its work across a worker pool, so the pin concentrates that
pool onto a couple of physical cores instead of spreading it. Those cores sit
near their boost ceiling and run hot while the rest of the chip idles.
GameMode re-applies the pin roughly every 5 seconds, so widening the mask from
inside the process does not hold. Releasing the request does release the pin.
Under `auto`, Nightcap therefore checks its CPU affinity immediately after the
request. If GameMode narrowed it, Nightcap ends the request and prints:
```
[gamemode] released: it pinned this process to a subset of the CPUs, ...
```
The trade is that GameMode's governor and priority boost go with it. To keep
those and lose only the pinning, turn pinning off for every game on the host.
These are the settings to run with:
```ini
# ~/.config/gamemode.ini
[general]
desiredgov=performance
[cpu]
pin_cores=no
park_cores=no
```
`desiredgov` is the one to check first. The packaged default in
`/usr/share/gamemode/gamemode.ini` is already `performance`, but a value in
your own file replaces it, and `desiredgov=powersave` there means GameMode
clocks the CPU **down** for the whole session while still reporting
`[gamemode] performance request active`. The symptoms are a low CPU clock
under load, a GPU that never leaves its lower SM clocks, and the engine pump
falling back to `tpause` — which costs about 70% of a core instead of 3%,
because `sleep` is only chosen when the governor reads `performance`. On one
13900K host, correcting `powersave` to `performance` took the process from
177% CPU to 42% and the package from 70°C to 53-63°C, at the same frame rate.
GameMode reads `$XDG_CONFIG_HOME`, `/usr/share/gamemode` and `/etc`, in that
order. It does not read anything under Nightcap's own config directory, so this
file has to live in one of those three places. The daemon reads it when it
starts, so restart it after editing: `systemctl --user restart gamemoded`.
Restarting the daemon drops the session of any game already running; relaunch
the game for new settings to apply to it.
`gamemoded` is D-Bus activated through
`com.feralinteractive.GameMode.service`, so `systemctl --user stop gamemoded`
does not hold — the next client request starts it again within seconds. To
measure with GameMode genuinely off, mask it:
```bash
systemctl --user mask gamemoded # and --unmask afterwards
```
The governor switch runs through polkit. Debian and Ubuntu only allow it for
members of the `gamemode` group; without that, the daemon's journal shows
`cpugovctl set performance: Not authorized` and the governor stays put while
the session is otherwise active. Add yourself and log in again:
```bash
sudo usermod -aG gamemode "$USER"
```
Fedora and Arch allow any logged-in local user.
Setting `performance.gamemode: on` accepts the pinning instead: `on` is an
explicit request for GameMode, and Nightcap keeps the session whatever it does
to the affinity. `off` never contacts the daemon.
To see which CPUs the process may use:
```bash
pid=$(pgrep -x Main)
for t in /proc/$pid/task/*; do taskset -pc "${t##*/}"; done
```
Two traps here. The engine process reports its name as `Main`, not
`mocktail`; `pgrep -x mocktail` finds only the launcher, which sits near 0%
CPU and tells you nothing. And `taskset -p` reads one thread, not the whole
process, so a single call reports the main thread's mask and says nothing
about the workers — which is where the game's load actually is.
For the same reason, `ps -L -o psr=` shows which core each thread is running
on right now, not which cores it is allowed on. On a hybrid CPU the scheduler
preferring P-cores for runnable work is normal and is not a mask.
## The main-thread pump
Roblox posts its per-frame main-thread step at a time of its own choosing,
and nothing signals the host when it does. The host main thread therefore
polls `nativeCallMessagesFromMainThread`, and a post that waits more than a
fraction of a millisecond costs a whole frame. Between polls the thread rests
for 100 µs, and how it rests decides the CPU clock:
| Mode | When | What it costs |
| --- | --- | --- |
| `sleep` | The governor is `performance`, which GameMode sets | The thread idles at about 3% of a core. The governor holds the clock. |
| `tpause` | Otherwise, on a CPU with WAITPKG (Intel 12th gen and newer) | The core stays awake in a light C0 state. Frame rate holds; the OS books the time as busy, so the thread shows around 70% of a core, at lower power than a spin. |
| `spin` | Otherwise | The core stays awake at full power. |
Under a power-saving governor the CPU clocks a bursty core down unless some
core stays awake, and Roblox's render thread then runs slower and drops
frames. That is why `sleep` is not the default there. Startup prints the
chosen mode:
```
[main] engine pump rest: tpause (MOCKTAIL_ENGINE_PUMP_REST=sleep|tpause|spin)
```
`MOCKTAIL_ENGINE_PUMP_REST` forces a mode. `sleep` without the governor's help
runs cooler for a few frames per second less; measure before keeping it.
## Small texture upscaling
`MOCKTAIL_SMALL_TEXTURE_UPSCALE` (default `4`) redraws ETC2 textures of 64px or
smaller at N times their width and height. At the default that is 16 times the
pixels per affected texture.
The cost lands in two places. Decoding and resampling is CPU work on up to 8
threads while textures load, which shows up as a burst rather than a steady
load. The enlarged textures then stay in GPU memory for the session and cost
extra sampling bandwidth on every frame that uses them.
That decode does not run on the thread that submits the frame. On a device
with a timeline semaphore, uploads decode on a background dispatcher and the
submit carries a semaphore wait, so the GPU waits for the texture instead of
the render thread. In the trace this is the `etc2 decode` slice on its own
thread, with only a negligible `etc2 gather` left on the submitting one. A
device without that feature decodes inline as before.
Set it to `1` to turn upscaling off:
```bash
MOCKTAIL_SMALL_TEXTURE_UPSCALE=1 mocktail
```
## Frame rate and present mode
`graphics.frame_rate_limit` (default `-1`) leaves Roblox's own in-game
framerate cap in charge; Nightcap sets no `DFIntTaskSchedulerTargetFps`
override. A fixed value forwards that number to the scheduler.
`graphics.vsync` (default `auto`) selects the presentation mode. `auto` and
`on` both pick the lowest-latency synchronized mode the driver offers, in
order: `FIFO_LATEST_READY`, `MAILBOX`, `FIFO_RELAXED`, `FIFO`. The first two do
not block the render thread on the display refresh, so the frame rate is
governed by the in-game cap rather than by presentation. `off` selects
`IMMEDIATE` and does not synchronize at all.
If the frame rate is higher than your refresh rate, the extra frames are work
you cannot see. Lower the in-game cap to your refresh rate first.
## Measuring
Shift+F4 in game shows Roblox's own scheduler jobs, with a millisecond cost and
a percentage for each. It does not show Nightcap's threads — the Vulkan
adapter, the ETC2 decode workers, the libc shim — so it is a reading on the
game, not on the process.
For the whole process:
```bash
top -H -p $(pgrep -x mocktail) # per-thread CPU, sorted
ps -L -o psr=,pcpu=,comm= -p $(pgrep -x mocktail) # which core each thread is on
nvidia-smi --query-gpu=utilization.gpu,temperature.gpu,power.draw --format=csv
```
A thread pinned near 100% while the frame rate is steady is a busy-wait. The
one exception is the host main thread in `tpause` mode, which the OS reports
as busy while the core is parked (see above). Load spread across many
`RBX Worker` threads is the game's own scheduler, and its cost tracks the
place you are in — part count, moving parts and joints all show in the
Shift+F4 World section.
`--profile <file>` writes a Chrome trace of the Vulkan adapter's work for
ui.perfetto.dev. See [BENCHMARKING.md](BENCHMARKING.md).
+90
View File
@@ -0,0 +1,90 @@
# Nightcap roadmap
What Nightcap is working toward, in priority order. Items move to
[Shipped](#shipped) when they land on `main`. Open an issue to propose a
change to this list.
## 1. Settings UI
Nightcap is configured by editing `~/.config/mocktail/config.yaml`. Most
people never open it, so they never find the FPS unlock or the vsync switch.
Goal: a small GTK4 settings window, reachable from the app menu and from a
`--settings` flag, that reads and writes the same `config.yaml`.
- Graphics tab: backend, frame rate limit, vsync, small texture upscale.
- Input tab: the `input.raw_mouse` toggle and controller options.
- Advanced tab: FFlag overrides with a plain text editor for `fflags.json`.
- Discord tab: every field in the `discord_rpc` block, so a friend can see
what you are playing and you can switch it all off in one click.
- Changes apply on next launch. A restart button restarts Nightcap.
Done when every option in `config/mocktail.example.yaml` that a player would
touch can be set without a text editor.
## 2. Controller button glyphs
Upstream added Xbox and PlayStation gamepad support through SDL. Controllers
already hot-plug and release their held inputs when unplugged. One rough edge
is left.
- Correct button glyphs for the connected pad. `SDL_GetGamepadType` is only
read for Xbox and PlayStation pads, so a Switch Pro, a GameCube pad, or any
generic pad SDL reports as `SDL_GAMEPAD_TYPE_STANDARD` reaches Roblox as
type 0 and draws generic glyphs. `SDL_GetRealGamepadType`, and the vendor
and product IDs, are available and unused.
Done when a plugged-in pad shows its own buttons.
## 3. First-load stutter
The `--profile` trace now shows shader compiles and pipeline creation, so
the stutter on first entering an experience can be measured.
- Cut the stutter the trace shows, and prove it with a before and after
trace per [BENCHMARKING.md](BENCHMARKING.md).
Done when entering a fresh experience no longer drops frames on shader
compiles.
## Shipped
- Lower input latency: frames are presented as soon as they are ready.
- Controllers hot-plug without a restart, and a pad that is unplugged mid-game
releases its held buttons and sticks instead of leaving them stuck.
- Mouse look keeps responding after a long turn, instead of going dead until
the pointer travels back from past the screen edge.
- Optional raw mouse input: `input.raw_mouse` in `config.yaml` sends look
deltas in host units, so fractional display scaling no longer changes
first-person sensitivity.
- Small textures upscaled 4x by default, with PNG overrides in
`~/.config/mocktail/textures`.
- ETC2 textures decoded on desktop GPUs without stutter.
- No more error 319 kicks after joining a server.
- Roblox sees the real host RAM and screen size.
- Desktop launcher that works on Wayland and finds the installed binary.
- Discord Rich Presence on by default, with the experience name, creator,
elapsed time, the experience icon with a Roblox badge, and a join button
when the server is public. See the [FAQ](../FAQ.md) to switch it off.
- Custom Discord presence: title, details, state, and large and small icons
from `config.yaml`, with `{place_name}`, `{place_icon}`, and
`{creator_name}` placeholders.
- `--profile` writes a Chrome-format trace of the Vulkan adapter's work,
including shader and pipeline creation, readable in `ui.perfetto.dev`.
[BENCHMARKING.md](BENCHMARKING.md) fixes the experiences to measure and
`scripts/summarize_profile_trace.py` builds the before and after table.
- Texture uploads no longer stall the frame they land on: ETC2 decode and
resampling run on a persistent worker pool.
- The host main thread rests between engine polls instead of spinning, and
GameMode's core pinning is released, so Roblox's workers spread across
every core. See [PERFORMANCE.md](PERFORMANCE.md).
- Own app ID `io.github.CoderDayton.nightcap`, so Nightcap installs next to
upstream.
- AppImage and signed Flatpak releases on every `v*` tag, with the Flatpak
repo on GitHub Pages.
## Not planned
- Replacing the `mocktail` binary and config names. Keeping them makes
upstream merges clean.
- DEB, RPM, and AUR packages. Upstream ships those for unmodified Mocktail.
Generated
+61
View File
@@ -0,0 +1,61 @@
{
"nodes": {
"flake-parts": {
"inputs": {
"nixpkgs-lib": "nixpkgs-lib"
},
"locked": {
"lastModified": 1785627969,
"narHash": "sha256-4dtXQk/NMePegK/nWp5NSeuZKLATItOq61lpEvmXqGw=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "427bf4bd9435fdf21321c8cc628c24efc14c0f7a",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1787135253,
"narHash": "sha256-RD2kNWCG+Bjo6h+JVjWVNntZs2GtRoeY2xHjts/FNkA=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "ffb3c9b700e759be2ef13237c9d8f953b32a1e46",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-lib": {
"locked": {
"lastModified": 1785031560,
"narHash": "sha256-OmshNvn2vupOFpYinLUu+1Dnpu4n7Q5N3ggGVNHpkUI=",
"owner": "nix-community",
"repo": "nixpkgs.lib",
"rev": "0e79af5e3d4dcfcd676ab5ba3f95d2e3352e078c",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixpkgs.lib",
"type": "github"
}
},
"root": {
"inputs": {
"flake-parts": "flake-parts",
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}
+62
View File
@@ -0,0 +1,62 @@
{
description = "Experimental Roblox compatibility runtime for Linux and FreeBSD";
inputs = {
flake-parts.url = "github:hercules-ci/flake-parts";
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
};
outputs =
inputs@{ flake-parts, ... }:
flake-parts.lib.mkFlake { inherit inputs; } {
imports = [
];
systems = [
"x86_64-linux"
# "aarch64-linux"
# "aarch64-darwin"
# "x86_64-darwin"
];
perSystem =
{
pkgs,
...
}:
let
package = pkgs.callPackage ./packaging/nix/package.nix { };
in
{
packages.default = package;
devShells.default = pkgs.mkShell {
inputsFrom = [ package ];
# nixpkgs installs the header we need under include/libutf8proc
NIX_CFLAGS_COMPILE = "-I${pkgs.libutf8proc}/include/libutf8proc";
nativeBuildInputs = with pkgs; [
cmake
git
lld
ninja
pkg-config
vulkan-headers
llvmPackages_22.clang-tools
llvmPackages_22.libclang
# debugging
gdb
];
shellHook = ''
export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:${pkgs.lib.makeLibraryPath package.buildInputs}"
# nix packages graphics drivers differently, and mocktail will still work if it cannot guarantee symbols
export MOCKTAIL_REQUIRE_REAL_GRAPHICS=0
'';
};
};
flake = { };
};
}
+12 -14
View File
@@ -1,17 +1,3 @@
// Copyright 2026 Mocktail Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MOCKTAIL_COMPAT_BIONIC_ABI_EXPORTS_H_
#define MOCKTAIL_COMPAT_BIONIC_ABI_EXPORTS_H_
@@ -85,6 +71,18 @@ int mocktail_pthread_mutex_destroy(pthread_mutex_t* mutex);
int mocktail_pthread_mutex_lock(pthread_mutex_t* mutex);
int mocktail_pthread_mutex_trylock(pthread_mutex_t* mutex);
int mocktail_pthread_mutex_unlock(pthread_mutex_t* mutex);
int mocktail_pthread_once(pthread_once_t* once_control,
void (*init_routine)(void));
int mocktail_pthread_spin_init(pthread_spinlock_t* lock, int pshared);
int mocktail_pthread_spin_destroy(pthread_spinlock_t* lock);
int mocktail_pthread_spin_lock(pthread_spinlock_t* lock);
int mocktail_pthread_spin_trylock(pthread_spinlock_t* lock);
int mocktail_pthread_spin_unlock(pthread_spinlock_t* lock);
int mocktail_pthread_barrier_init(pthread_barrier_t* barrier,
const pthread_barrierattr_t* attr,
unsigned count);
int mocktail_pthread_barrier_destroy(pthread_barrier_t* barrier);
int mocktail_pthread_barrier_wait(pthread_barrier_t* barrier);
extern void* mocktail_gameactivity_on_start_native;
extern void* mocktail_gameactivity_on_resume_native;
-14
View File
@@ -1,17 +1,3 @@
// Copyright 2026 Mocktail Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MOCKTAIL_COMPAT_BIONIC_ATFORK_RUNTIME_H_
#define MOCKTAIL_COMPAT_BIONIC_ATFORK_RUNTIME_H_
-14
View File
@@ -1,17 +1,3 @@
// Copyright 2026 Mocktail Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MOCKTAIL_COMPAT_BIONIC_DNS_RUNTIME_H_
#define MOCKTAIL_COMPAT_BIONIC_DNS_RUNTIME_H_
+16 -14
View File
@@ -1,21 +1,10 @@
// Copyright 2026 Mocktail Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MOCKTAIL_COMPAT_BIONIC_HOST_LIBC_RUNTIME_H_
#define MOCKTAIL_COMPAT_BIONIC_HOST_LIBC_RUNTIME_H_
#include <sys/sysinfo.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <sys/utsname.h>
#include <cstddef>
#include <cstdint>
@@ -72,6 +61,15 @@ void BionicArc4RandomBuffer(void* buffer, size_t size) noexcept;
BionicMallinfoSnapshot BionicMallinfo() noexcept;
bool BionicMallinfoHasHostTelemetry() noexcept;
void NormalizeBionicSysInfo(struct sysinfo* info) noexcept;
int BionicSysInfo(struct sysinfo* info) noexcept;
bool NormalizeLinuxulatorUnameVersion(struct utsname* name,
const char* linux_version) noexcept;
int BionicUname(struct utsname* name) noexcept;
uid_t NormalizeBionicApplicationUid(uid_t host_uid) noexcept;
uid_t BionicGetUid() noexcept;
uid_t BionicGetEffectiveUid() noexcept;
// POSIX strerror_r contract: zero on success, -1/ERANGE on truncation.
int BionicStrError(int error_number, char* buffer, size_t buffer_size) noexcept;
@@ -93,6 +91,10 @@ int mocktail_bionic_cxa_thread_atexit_impl(
void* dso_handle);
void mocktail_bionic_arc4random_buf(void* buffer, size_t size);
mocktail::compat::BionicMallinfoSnapshot mocktail_bionic_mallinfo();
int mocktail_bionic_sysinfo(struct sysinfo* info);
int mocktail_bionic_uname(struct utsname* name);
uid_t mocktail_bionic_getuid();
uid_t mocktail_bionic_geteuid();
int mocktail_bionic_strerror_r(int error_number, char* buffer,
size_t buffer_size);
long long mocktail_bionic_strtoll_l(const char* text, char** end, int base,
@@ -1,17 +1,3 @@
// Copyright 2026 Mocktail Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MOCKTAIL_COMPAT_BIONIC_LARGE_FILE_RUNTIME_H_
#define MOCKTAIL_COMPAT_BIONIC_LARGE_FILE_RUNTIME_H_
+12
View File
@@ -0,0 +1,12 @@
#ifndef MOCKTAIL_COMPAT_BIONIC_PRCTL_RUNTIME_H_
#define MOCKTAIL_COMPAT_BIONIC_PRCTL_RUNTIME_H_
extern "C" {
int mocktail_bionic_prctl(int option, unsigned long argument2,
unsigned long argument3, unsigned long argument4,
unsigned long argument5);
} // extern "C"
#endif // MOCKTAIL_COMPAT_BIONIC_PRCTL_RUNTIME_H_
+8 -14
View File
@@ -1,21 +1,9 @@
// Copyright 2026 Mocktail Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MOCKTAIL_COMPAT_BIONIC_PTHREAD_CREATE_RUNTIME_H_
#define MOCKTAIL_COMPAT_BIONIC_PTHREAD_CREATE_RUNTIME_H_
#include <pthread.h>
#include <sched.h>
#include <sys/types.h>
#include <cstddef>
@@ -43,4 +31,10 @@ extern "C" int mocktail_bionic_pthread_create(pthread_t* thread,
void* (*start_routine)(void*),
void* argument);
// Guest real-time scheduling is denied to avoid the host RLIMIT_RTTIME watchdog.
extern "C" int mocktail_bionic_pthread_setschedparam(
pthread_t thread, int policy, const struct sched_param* parameters);
extern "C" int mocktail_bionic_sched_setscheduler(
pid_t tid, int policy, const struct sched_param* parameters);
#endif // MOCKTAIL_COMPAT_BIONIC_PTHREAD_CREATE_RUNTIME_H_
@@ -1,17 +1,3 @@
// Copyright 2026 Mocktail Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MOCKTAIL_COMPAT_BIONIC_PTHREAD_KEY_RUNTIME_H_
#define MOCKTAIL_COMPAT_BIONIC_PTHREAD_KEY_RUNTIME_H_
+3 -16
View File
@@ -1,24 +1,11 @@
// Copyright 2026 Mocktail Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MOCKTAIL_COMPAT_BIONIC_RWLOCK_RUNTIME_H_
#define MOCKTAIL_COMPAT_BIONIC_RWLOCK_RUNTIME_H_
#include <pthread.h>
// Bionic and glibc pthread_rwlock_t storage is not ABI-compatible. The guest
// address is an opaque key; the host object lives entirely in this runtime.
// Bionic pthread_rwlock_t is a state word in guest storage. rdlock/wrlock/
// unlock use __atomic_* and futex on that word. Zero-initialized storage
// still locks.
extern "C" {
int mocktail_bionic_pthread_rwlock_init(pthread_rwlock_t* rwlock,
+3 -16
View File
@@ -1,24 +1,11 @@
// Copyright 2026 Mocktail Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MOCKTAIL_COMPAT_BIONIC_SEMAPHORE_RUNTIME_H_
#define MOCKTAIL_COMPAT_BIONIC_SEMAPHORE_RUNTIME_H_
#include <semaphore.h>
// Bionic and glibc use different sem_t representations. These exports keep
// host semaphore objects out of guest storage and key them by guest address.
// Bionic sem_t is a count word in guest storage (value << 1, shared bit 0).
// Wait/post use __atomic_* and futex on that word so inlined guest futex
// waiters wake. Process-shared semaphores are rejected.
extern "C" {
int mocktail_bionic_sem_init(sem_t* semaphore, int process_shared,
-14
View File
@@ -1,17 +1,3 @@
// Copyright 2026 Mocktail Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MOCKTAIL_COMPAT_BIONIC_SIGNAL_RUNTIME_H_
#define MOCKTAIL_COMPAT_BIONIC_SIGNAL_RUNTIME_H_
+17
View File
@@ -0,0 +1,17 @@
#ifndef MOCKTAIL_COMPAT_BIONIC_SOCKET_RUNTIME_H_
#define MOCKTAIL_COMPAT_BIONIC_SOCKET_RUNTIME_H_
#include <sys/socket.h>
extern "C" {
int mocktail_bionic_setsockopt(int socket, int level, int option_name,
const void *option_value,
socklen_t option_length);
ssize_t mocktail_bionic_sendmsg(int socket, const struct msghdr *message,
int flags);
} // extern "C"
#endif // MOCKTAIL_COMPAT_BIONIC_SOCKET_RUNTIME_H_
+24 -20
View File
@@ -1,17 +1,3 @@
// Copyright 2026 Mocktail Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MOCKTAIL_COMPAT_BIONIC_STDIO_RUNTIME_H_
#define MOCKTAIL_COMPAT_BIONIC_STDIO_RUNTIME_H_
@@ -19,6 +5,7 @@
#include <cstdarg>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cwchar>
@@ -31,12 +18,6 @@ namespace mocktail::compat {
constexpr size_t kBionicFileSize = 152;
constexpr size_t kBionicStandardStreamCount = 3;
FILE* TranslateBionicFile(FILE* stream) noexcept;
void* BionicFileArraySymbolAddress() noexcept;
void* BionicStdinSymbolAddress() noexcept;
void* BionicStdoutSymbolAddress() noexcept;
void* BionicStderrSymbolAddress() noexcept;
} // namespace mocktail::compat
extern "C" {
@@ -45,8 +26,31 @@ alignas(sizeof(void*)) extern unsigned char
__sF[mocktail::compat::kBionicStandardStreamCount]
[mocktail::compat::kBionicFileSize];
} // extern "C"
namespace mocktail::compat {
inline FILE* TranslateBionicFile(FILE* stream) noexcept {
const uintptr_t address = reinterpret_cast<uintptr_t>(stream);
if (__builtin_expect(address == reinterpret_cast<uintptr_t>(&__sF[0]), 0)) {
return stdin;
}
if (__builtin_expect(address == reinterpret_cast<uintptr_t>(&__sF[1]), 0)) {
return stdout;
}
if (__builtin_expect(address == reinterpret_cast<uintptr_t>(&__sF[2]), 0)) {
return stderr;
}
return stream;
}
void* BionicFileArraySymbolAddress() noexcept;
void* BionicStdinSymbolAddress() noexcept;
void* BionicStdoutSymbolAddress() noexcept;
void* BionicStderrSymbolAddress() noexcept;
} // namespace mocktail::compat
// FILE values returned by host fopen/fdopen pass through unchanged. Pointers
// into Bionic's ABI-sized __sF array are translated to the corresponding host
// standard stream at this single boundary.
-14
View File
@@ -1,17 +1,3 @@
// Copyright 2026 Mocktail Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MOCKTAIL_COMPAT_BIONIC_SYSCONF_H_
#define MOCKTAIL_COMPAT_BIONIC_SYSCONF_H_
+24 -8
View File
@@ -1,14 +1,9 @@
// Copyright 2026 Mocktail Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
#ifndef MOCKTAIL_COMPAT_BUILD_PROFILE_H_
#define MOCKTAIL_COMPAT_BUILD_PROFILE_H_
#include <array>
#include <cstddef>
#include <cstdint>
#include <optional>
#include <string>
@@ -30,6 +25,27 @@ struct FmodOutputDeviceBridgeProfile {
std::uintptr_t info_method_rva = 0;
std::uintptr_t current_method_rva = 0;
std::uintptr_t select_method_rva = 0;
// Roblox 2.738 inserted device-list methods before the current/select slots.
int vtable_layout_version = 1;
// Optional exact-build input count/info/current/select methods.
std::array<std::uintptr_t, 4> input_method_rvas{};
bool has_input_devices() const { return input_method_rvas[0] != 0; }
std::array<std::size_t, 4> input_vtable_indexes() const {
return vtable_layout_version == 2
? std::array<std::size_t, 4>{9, 10, 12, 18}
: std::array<std::size_t, 4>{8, 9, 10, 16};
}
bool valid_vtable_layout() const {
return vtable_layout_version == 1 || vtable_layout_version == 2;
}
std::size_t current_vtable_index() const {
return vtable_layout_version == 2 ? 8 : 7;
}
std::size_t select_vtable_index() const {
return vtable_layout_version == 2 ? 19 : 17;
}
};
struct BuildProfile {
-8
View File
@@ -1,11 +1,3 @@
// Copyright 2026 Mocktail Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
#ifndef MOCKTAIL_COMPAT_ELF_BUILD_ID_H_
#define MOCKTAIL_COMPAT_ELF_BUILD_ID_H_
@@ -0,0 +1,42 @@
#ifndef MOCKTAIL_COMPAT_FMOD_OUTPUT_DEVICE_CONTRACT_H_
#define MOCKTAIL_COMPAT_FMOD_OUTPUT_DEVICE_CONTRACT_H_
#include <cstddef>
#include <initializer_list>
#include <string_view>
namespace mocktail::compat {
// The 2.738 integer-index selector adds a feature-flag dispatch before the
// count/clamp/setDriver path. Keep the calling convention, count slot (5),
// object offset (0x28), and branch into that path exact. Only relocated
// addresses and non-local branch/call targets vary between builds.
inline constexpr char kFmodDeviceListsSelectContract[] =
"\x55\x48\x89\xe5\x41\x57\x41\x56\x41\x54\x53\x48\x83\xec\x50\x41"
"\x89\xf7\x49\x89\xfe\x4c\x8b\x25\x00\x00\x00\x00\x49\x8b\x04\x24"
"\x48\x89\x45\xd8\x80\x3d\x00\x00\x00\x00\x00\x74\x25\x49\x8b\x04"
"\x24\x48\x3b\x45\xd8\x0f\x85\x00\x00\x00\x00\x4c\x89\xf7\x44\x89"
"\xfe\x48\x83\xc4\x50\x5b\x41\x5c\x41\x5e\x41\x5f\x5d\xe9\x00\x00"
"\x00\x00\x49\x8b\x06\x4c\x89\xf7\xff\x50\x28\x44\x39\xf8\x0f\x8e"
"\x00\x00\x00\x00\x31\xdb\x45\x85\xff\x41\x0f\x4f\xdf\x49\x8b\x7e"
"\x28\x89\xde\xe8\x00\x00\x00\x00";
inline constexpr std::size_t kFmodDeviceListsSelectContractSize =
sizeof(kFmodDeviceListsSelectContract) - 1;
inline bool HasFmodDeviceListsSelectContract(std::string_view code) {
if (code.size() < kFmodDeviceListsSelectContractSize) return false;
for (std::size_t index = 0; index < kFmodDeviceListsSelectContractSize;
++index) {
bool relocated = false;
for (const std::size_t start : {24, 38, 55, 78, 96, 116}) {
if (index >= start && index < start + 4) relocated = true;
}
if (!relocated && code[index] != kFmodDeviceListsSelectContract[index])
return false;
}
return true;
}
} // namespace mocktail::compat
#endif // MOCKTAIL_COMPAT_FMOD_OUTPUT_DEVICE_CONTRACT_H_
-14
View File
@@ -1,17 +1,3 @@
// Copyright 2026 Mocktail Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MOCKTAIL_COMPAT_HOST_ABI_EXPERIMENT_H_
#define MOCKTAIL_COMPAT_HOST_ABI_EXPERIMENT_H_
-14
View File
@@ -1,17 +1,3 @@
// Copyright 2026 Mocktail Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MOCKTAIL_COMPAT_HOST_ABI_PROFILE_H_
#define MOCKTAIL_COMPAT_HOST_ABI_PROFILE_H_
+2 -14
View File
@@ -1,17 +1,3 @@
// Copyright 2026 Mocktail Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MOCKTAIL_COMPAT_HOST_ABI_PROFILE_LOADER_H_
#define MOCKTAIL_COMPAT_HOST_ABI_PROFILE_LOADER_H_
@@ -68,6 +54,8 @@ ExternalHostAbiProfileResult InitializeHostAbiProfileFromEnvironment(
const HostAbiProfile* FindLoadedExternalHostAbiProfile(
std::string_view build_id) noexcept;
void SetHostAbiProfileHashingFaultForTesting(bool enabled) noexcept;
} // namespace mocktail::compat
#endif // MOCKTAIL_COMPAT_HOST_ABI_PROFILE_LOADER_H_
-14
View File
@@ -1,17 +1,3 @@
// Copyright 2026 Mocktail Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MOCKTAIL_COMPAT_HOST_ALLOCATOR_BRIDGE_H_
#define MOCKTAIL_COMPAT_HOST_ALLOCATOR_BRIDGE_H_
-14
View File
@@ -1,17 +1,3 @@
// Copyright 2026 Mocktail Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MOCKTAIL_COMPAT_HTTP_CLIENT_SPIN_GUARD_H_
#define MOCKTAIL_COMPAT_HTTP_CLIENT_SPIN_GUARD_H_
-14
View File
@@ -1,17 +1,3 @@
// Copyright 2026 Mocktail Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MOCKTAIL_COMPAT_PAYLOAD_COMPATIBILITY_H_
#define MOCKTAIL_COMPAT_PAYLOAD_COMPATIBILITY_H_
+178 -18
View File
@@ -1,19 +1,5 @@
// Copyright 2026 Sober Test Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef SOBER_TEST_JNIVM_JNIVM_H_
#define SOBER_TEST_JNIVM_JNIVM_H_
#ifndef MOCKTAIL_JNIVM_JNIVM_H_
#define MOCKTAIL_JNIVM_JNIVM_H_
#include <jni.h>
@@ -73,6 +59,8 @@ struct RobloxCredentialView {
};
using RobloxCredentialProvider = RobloxCredentialView (*)(const void *context);
using RobloxCookieGetter = jstring (*)(JNIEnv* env, jclass clazz,
jstring domain);
jobject CreateAndroidConfiguration(JNIEnv *env);
@@ -94,6 +82,67 @@ struct FmodAudioDeviceCallbacks {
void (*shutdown)(void *context) = nullptr;
};
// Parameters passed by WebRtcAudioManager's constructor to the guest's
// nativeCacheAudioParameters(IIIZZZZZZZIIJ)V. Buffer sizes are PCM frames,
// not bytes. Querying them must not open a microphone.
struct WebRtcAudioManagerParameters {
int sample_rate_hz = 0;
int output_channels = 0;
int input_channels = 0;
bool hardware_aec = false;
bool hardware_agc = false;
bool hardware_ns = false;
bool low_latency_output = false;
bool low_latency_input = false;
bool pro_audio = false;
bool aaudio = false;
int output_buffer_size_frames = 0;
int input_buffer_size_frames = 0;
};
struct WebRtcAudioManagerCallbacks {
bool (*get_parameters)(void* context,
WebRtcAudioManagerParameters* parameters) = nullptr;
bool (*init)(void* context, const void* identity) = nullptr;
void (*dispose)(void* context, const void* identity) = nullptr;
void (*set_microphone_mute)(void* context, bool muted) = nullptr;
};
using WebRtcAudioRecordDataCallback = void (*)(void *context,
const void *identity,
std::size_t size_bytes);
// Host implementation of org/webrtc/voiceengine/WebRtcAudioRecord. Init
// returns the number of PCM frames in the stable direct buffer, or -1.
struct WebRtcAudioRecordCallbacks {
int (*init)(void *context, const void *identity, int sample_rate_hz,
int channels, WebRtcAudioRecordDataCallback data_callback,
void *data_context, void **direct_buffer,
std::size_t *direct_buffer_capacity) = nullptr;
bool (*start)(void *context, const void *identity) = nullptr;
bool (*stop)(void *context, const void *identity) = nullptr;
void (*close)(void *context, const void *identity) = nullptr;
void (*shutdown)(void *context) = nullptr;
};
using WebRtcAudioTrackDataCallback = void (*)(void *context,
const void *identity,
std::size_t size_bytes);
// Host implementation of org/webrtc/voiceengine/WebRtcAudioTrack. Init
// returns the emulated Android AudioTrack buffer size in bytes, or -1.
struct WebRtcAudioTrackCallbacks {
int (*init)(void *context, const void *identity, int sample_rate_hz,
int channels, double buffer_size_factor,
WebRtcAudioTrackDataCallback data_callback, void *data_context,
void **direct_buffer,
std::size_t *direct_buffer_capacity) = nullptr;
int (*buffer_size_frames)(void *context, const void *identity) = nullptr;
bool (*start)(void *context, const void *identity) = nullptr;
bool (*stop)(void *context, const void *identity) = nullptr;
void (*close)(void *context, const void *identity) = nullptr;
};
// Guest JNI threads only publish Android window flags. The host callback must
// marshal SDL work onto the main thread.
struct AndroidWindowCallbacks {
@@ -144,6 +193,11 @@ struct MessageBusRequestHandlerCallbacks {
std::string (*run)(void *context, JNIEnv *env, jstring message) = nullptr;
};
struct MessageBusAsyncRequestHandlerCallbacks {
void (*run)(void* context, JNIEnv* env, jstring message,
jstring response_id) = nullptr;
};
// Dispatch accepts Callback.onItemSet(String) only on a receiver created by
// this VM. The context stays alive while the callback runs.
struct MemStorageCallbackCallbacks {
@@ -208,10 +262,12 @@ public:
JavaVM *GetJavaVM() { return java_vm_; }
// Resolves the process pseudo-VM that owns an exact JavaVM pointer. Returns
// Resolves the live pseudo-VM that owns an exact JavaVM pointer. Returns
// null for foreign VMs and after the owner is destroyed.
static VM *FromJavaVM(JavaVM *java_vm);
// Binds this thread's JNI environment to this VM. Switching owners must
// not reuse the previous VM's native function table.
JNIEnv *GetJNIEnv();
std::size_t GetClassCount() const { return class_registry_.size(); }
@@ -242,6 +298,9 @@ public:
// retained privately and returned instead until the provider is cleared.
bool CopyRobloxCredentialFromProvider(std::string *credential) const;
void SetRobloxCookieGetter(RobloxCookieGetter getter);
bool RefreshRobloxCredentialFromEngine(JNIEnv* env);
void SetRobloxCredentialSink(std::shared_ptr<void> context,
const RobloxCredentialSinkCallbacks &callbacks);
void ClearRobloxCredentialSink();
@@ -264,6 +323,55 @@ public:
const std::uint8_t *data, std::size_t size);
bool DispatchFmodAudioDeviceClose(const void *identity);
void SetWebRtcAudioManagerCallbacks(
std::shared_ptr<void> context,
const WebRtcAudioManagerCallbacks& callbacks);
void ClearWebRtcAudioManagerCallbacks();
void RegisterWebRtcAudioManagerNative(const char* name, const char* signature,
void* function);
bool DispatchWebRtcAudioManagerConstruct(jobject manager,
jlong native_audio_manager);
bool DispatchWebRtcAudioManagerInit(jobject manager);
void DispatchWebRtcAudioManagerDispose(jobject manager);
void DispatchWebRtcAudioManagerMicrophoneMute(jobject manager, bool muted);
void
SetWebRtcAudioRecordCallbacks(std::shared_ptr<void> context,
const WebRtcAudioRecordCallbacks &callbacks);
void ClearWebRtcAudioRecordCallbacks();
int DispatchWebRtcAudioRecordInit(const void *identity, int sample_rate_hz,
int channels, void **direct_buffer,
std::size_t *direct_buffer_capacity);
bool DispatchWebRtcAudioRecordStart(const void *identity);
bool DispatchWebRtcAudioRecordStop(const void *identity);
void DispatchWebRtcAudioRecordClose(const void *identity);
void DispatchWebRtcAudioRecordData(const void *identity,
std::size_t size_bytes);
// RegisterNatives stores these exact WebRTC callbacks so the host capture
// thread can emulate the Java AudioRecord thread.
void RegisterWebRtcAudioRecordNative(const char *name, const char *signature,
void *function);
void SetWebRtcAudioTrackCallbacks(std::shared_ptr<void> context,
const WebRtcAudioTrackCallbacks &callbacks);
void ClearWebRtcAudioTrackCallbacks();
int DispatchWebRtcAudioTrackInit(const void *identity, int sample_rate_hz,
int channels, double buffer_size_factor,
void **direct_buffer,
std::size_t *direct_buffer_capacity);
int DispatchWebRtcAudioTrackBufferSizeFrames(const void *identity);
bool DispatchWebRtcAudioTrackStart(const void *identity);
bool DispatchWebRtcAudioTrackStop(const void *identity);
void DispatchWebRtcAudioTrackClose(const void *identity);
void DispatchWebRtcAudioTrackData(const void *identity,
std::size_t size_bytes);
// RegisterNatives stores the exact WebRTC playout callbacks so the host
// thread can emulate AudioTrackJavaThread.
void RegisterWebRtcAudioTrackNative(const char *name, const char *signature,
void *function);
void SetAndroidWindowCallbacks(std::shared_ptr<void> context,
const AndroidWindowCallbacks &callbacks);
void ClearAndroidWindowCallbacks();
@@ -292,6 +400,16 @@ public:
jstring DispatchMessageBusRequestHandler(jobject handler, JNIEnv *env,
jstring message);
// Exact RequestHandlerAsyncRaw.run(String, String) receiver. Clearing stops
// new calls; an in-flight call retains the host context until it returns.
jobject CreateMessageBusAsyncRequestHandler(
std::shared_ptr<void> context,
const MessageBusAsyncRequestHandlerCallbacks& callbacks);
void ClearMessageBusAsyncRequestHandler(jobject handler);
bool DispatchMessageBusAsyncRequestHandler(jobject handler, JNIEnv* env,
jstring message,
jstring response_id);
// Creates the exact com.roblox.engine.jni.memstorage.Callback receiver used
// by MemStorage.bind. Clear prevents future dispatch while an in-flight call
// retains its shared context until the callback returns.
@@ -352,6 +470,9 @@ private:
RobloxCredentialProvider roblox_credential_provider_ = nullptr;
std::string roblox_credential_override_;
mutable std::mutex roblox_cookie_getter_mutex_;
RobloxCookieGetter roblox_cookie_getter_ = nullptr;
struct RobloxCredentialSinkBinding {
std::shared_ptr<void> context;
RobloxCredentialSinkCallbacks callbacks;
@@ -366,6 +487,36 @@ private:
mutable std::mutex fmod_audio_device_mutex_;
FmodAudioDeviceBinding fmod_audio_device_binding_;
struct WebRtcAudioManagerBinding {
std::shared_ptr<void> context;
WebRtcAudioManagerCallbacks callbacks;
};
mutable std::mutex webrtc_audio_manager_mutex_;
WebRtcAudioManagerBinding webrtc_audio_manager_binding_;
void* webrtc_cache_audio_parameters_native_ = nullptr;
struct WebRtcAudioRecordBinding {
std::shared_ptr<void> context;
WebRtcAudioRecordCallbacks callbacks;
};
mutable std::mutex webrtc_audio_record_mutex_;
WebRtcAudioRecordBinding webrtc_audio_record_binding_;
mutable std::mutex webrtc_audio_record_native_mutex_;
void *webrtc_cache_direct_buffer_native_ = nullptr;
void *webrtc_data_is_recorded_native_ = nullptr;
struct WebRtcAudioTrackBinding {
std::shared_ptr<void> context;
WebRtcAudioTrackCallbacks callbacks;
};
mutable std::mutex webrtc_audio_track_mutex_;
WebRtcAudioTrackBinding webrtc_audio_track_binding_;
mutable std::mutex webrtc_audio_track_native_mutex_;
void *webrtc_track_cache_direct_buffer_native_ = nullptr;
void *webrtc_get_playout_data_native_ = nullptr;
struct AndroidWindowBinding {
std::shared_ptr<void> context;
AndroidWindowCallbacks callbacks;
@@ -393,6 +544,15 @@ private:
std::unordered_map<jobject, std::shared_ptr<MessageBusRequestHandlerBinding>>
message_bus_request_handler_bindings_;
struct MessageBusAsyncRequestHandlerBinding {
std::shared_ptr<void> context;
MessageBusAsyncRequestHandlerCallbacks callbacks;
};
mutable std::mutex message_bus_async_request_handler_mutex_;
std::unordered_map<jobject,
std::shared_ptr<MessageBusAsyncRequestHandlerBinding>>
message_bus_async_request_handler_bindings_;
struct MemStorageCallbackBinding {
std::shared_ptr<void> context;
MemStorageCallbackCallbacks callbacks;
@@ -422,4 +582,4 @@ private:
} // namespace jnivm
#endif // SOBER_TEST_JNIVM_JNIVM_H_
#endif // MOCKTAIL_JNIVM_JNIVM_H_
+10 -8
View File
@@ -1,11 +1,3 @@
// Copyright 2026 Mocktail Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
#ifndef MOCKTAIL_LEGACY_LEGACY_RUNTIME_H_
#define MOCKTAIL_LEGACY_LEGACY_RUNTIME_H_
@@ -33,6 +25,8 @@ class RuntimeDependencies final {
: jni_vm_(std::move(composition.jni_vm)),
account_identity_(std::move(composition.account_identity)),
roblox_credential_(std::move(composition.credential)),
clear_persisted_web_view_cookie_(
composition.rejected_credential_retired),
shutdown_before_platform_(shutdown_before_platform) {}
~RuntimeDependencies() {
if (jni_vm_ != nullptr && shutdown_before_platform_ != nullptr) {
@@ -46,6 +40,8 @@ class RuntimeDependencies final {
: jni_vm_(std::move(other.jni_vm_)),
account_identity_(std::move(other.account_identity_)),
roblox_credential_(std::move(other.roblox_credential_)),
clear_persisted_web_view_cookie_(std::exchange(
other.clear_persisted_web_view_cookie_, false)),
shutdown_before_platform_(
std::exchange(other.shutdown_before_platform_, nullptr)) {}
RuntimeDependencies& operator=(RuntimeDependencies&& other) noexcept {
@@ -58,6 +54,8 @@ class RuntimeDependencies final {
jni_vm_ = std::move(other.jni_vm_);
account_identity_ = std::move(other.account_identity_);
roblox_credential_ = std::move(other.roblox_credential_);
clear_persisted_web_view_cookie_ =
std::exchange(other.clear_persisted_web_view_cookie_, false);
shutdown_before_platform_ =
std::exchange(other.shutdown_before_platform_, nullptr);
return *this;
@@ -70,6 +68,9 @@ class RuntimeDependencies final {
const runtime::SecureRobloxCredential& roblox_credential() const {
return roblox_credential_;
}
bool clear_persisted_web_view_cookie() const {
return clear_persisted_web_view_cookie_;
}
// Transitional teardown boundary. Runtime-owned subsystems release
// SDL resources here after guest workers stop and before window shutdown.
@@ -83,6 +84,7 @@ class RuntimeDependencies final {
std::shared_ptr<jnivm::VM> jni_vm_;
jnivm::RobloxAuthIdentity account_identity_;
runtime::SecureRobloxCredential roblox_credential_;
bool clear_persisted_web_view_cookie_ = false;
ShutdownBeforePlatformCallback shutdown_before_platform_ = nullptr;
};
+3 -17
View File
@@ -1,19 +1,5 @@
// Copyright 2026 Sober Test Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef SOBER_TEST_LIBC_SHIM_LIBC_SHIM_H_
#define SOBER_TEST_LIBC_SHIM_LIBC_SHIM_H_
#ifndef MOCKTAIL_LIBC_SHIM_LIBC_SHIM_H_
#define MOCKTAIL_LIBC_SHIM_LIBC_SHIM_H_
#include <cstdio>
#include <dirent.h>
@@ -94,4 +80,4 @@ ssize_t mocktail___readlink_chk(const char* path, char* buf, size_t len,
size_t buf_len);
}
#endif // SOBER_TEST_LIBC_SHIM_LIBC_SHIM_H_
#endif // MOCKTAIL_LIBC_SHIM_LIBC_SHIM_H_
@@ -0,0 +1,17 @@
#ifndef MOCKTAIL_LIBC_SHIM_VULKAN_ETC1_SKY_TRANSCODER_H_
#define MOCKTAIL_LIBC_SHIM_VULKAN_ETC1_SKY_TRANSCODER_H_
#include <cstdint>
#include <vector>
namespace libc_shim {
// Converts one opaque ETC1 RGB block to an opaque BC1 RGB block.
void ConvertEtc1RgbBlockToBc1(const uint8_t *etc1, uint8_t *bc1);
bool TranscodeEtc1SkyTextureForVulkan(const char *path,
std::vector<unsigned char> *data);
} // namespace libc_shim
#endif // MOCKTAIL_LIBC_SHIM_VULKAN_ETC1_SKY_TRANSCODER_H_
-14
View File
@@ -1,17 +1,3 @@
// Copyright 2026 Mocktail Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MOCKTAIL_LINKER_LINKER_H_
#define MOCKTAIL_LINKER_LINKER_H_
-14
View File
@@ -1,17 +1,3 @@
// Copyright 2026 Mocktail Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MOCKTAIL_AUDIO_AUDIO_SINK_H_
#define MOCKTAIL_AUDIO_AUDIO_SINK_H_
@@ -1,17 +1,3 @@
// Copyright 2026 Mocktail Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MOCKTAIL_AUDIO_FMOD_JAVA_AUDIO_RUNTIME_H_
#define MOCKTAIL_AUDIO_FMOD_JAVA_AUDIO_RUNTIME_H_
@@ -1,17 +1,3 @@
// Copyright 2026 Mocktail Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MOCKTAIL_AUDIO_FMOD_JNI_AUDIO_BRIDGE_H_
#define MOCKTAIL_AUDIO_FMOD_JNI_AUDIO_BRIDGE_H_
@@ -0,0 +1,64 @@
#ifndef MOCKTAIL_AUDIO_FMOD_THREAD_FLOATING_POINT_H_
#define MOCKTAIL_AUDIO_FMOD_THREAD_FLOATING_POINT_H_
#include <string_view>
#if defined(__x86_64__)
#include <xmmintrin.h>
#endif
namespace mocktail::audio {
// FMOD's Android threads identify themselves through JavaVMAttachArgs before
// entering their mix loop. Voice DSP filters can accumulate subnormal values
// and spend an entire x86 core on floating-point assists (issue #128).
// Limit FTZ/DAZ to these audio threads; retain the caller's other FP controls.
class FmodThreadFloatingPointMode final {
public:
FmodThreadFloatingPointMode() = default;
~FmodThreadFloatingPointMode() { Restore(); }
FmodThreadFloatingPointMode(const FmodThreadFloatingPointMode&) = delete;
FmodThreadFloatingPointMode& operator=(const FmodThreadFloatingPointMode&) =
delete;
bool Enable(const char* thread_name) noexcept {
#if defined(__x86_64__)
if (active_ || thread_name == nullptr) return false;
const std::string_view name(thread_name);
if (name != "FMOD mixer thread" && name != "FMOD feeder thread" &&
name != "FMOD Convolution thread" && name != "FMOD Worker Thread") {
return false;
}
const unsigned int control = _mm_getcsr();
original_mode_ = control & kModeMask;
_mm_setcsr(control | kModeMask);
active_ = true;
return true;
#else
(void)thread_name;
return false;
#endif
}
void Restore() noexcept {
#if defined(__x86_64__)
if (active_) {
// Restore only our two bits, preserving rounding, exception masks and
// status flags set by the guest while the thread was attached.
_mm_setcsr((_mm_getcsr() & ~kModeMask) | original_mode_);
active_ = false;
}
#endif
}
private:
#if defined(__x86_64__)
static constexpr unsigned int kModeMask = (1U << 15) | (1U << 6);
unsigned int original_mode_ = 0;
bool active_ = false;
#endif
};
} // namespace mocktail::audio
#endif // MOCKTAIL_AUDIO_FMOD_THREAD_FLOATING_POINT_H_
-14
View File
@@ -1,17 +1,3 @@
// Copyright 2026 Mocktail Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MOCKTAIL_AUDIO_OPENSL_ABI_H_
#define MOCKTAIL_AUDIO_OPENSL_ABI_H_
@@ -1,17 +1,3 @@
// Copyright 2026 Mocktail Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MOCKTAIL_AUDIO_OPENSL_PLAYBACK_RUNTIME_H_
#define MOCKTAIL_AUDIO_OPENSL_PLAYBACK_RUNTIME_H_
@@ -1,17 +1,3 @@
// Copyright 2026 Mocktail Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MOCKTAIL_AUDIO_OPENSL_RUNTIME_ABI_H_
#define MOCKTAIL_AUDIO_OPENSL_RUNTIME_ABI_H_
@@ -1,17 +1,3 @@
// Copyright 2026 Mocktail Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MOCKTAIL_AUDIO_OPENSL_SIMPLE_BUFFER_QUEUE_H_
#define MOCKTAIL_AUDIO_OPENSL_SIMPLE_BUFFER_QUEUE_H_
@@ -1,23 +1,10 @@
// Copyright 2026 Mocktail Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MOCKTAIL_AUDIO_ROBLOX_OUTPUT_DEVICE_BRIDGE_H_
#define MOCKTAIL_AUDIO_ROBLOX_OUTPUT_DEVICE_BRIDGE_H_
#include <array>
#include <cstdint>
#include <mutex>
#include <memory>
#include <string>
#include <string_view>
#include <vector>
@@ -27,13 +14,15 @@
namespace mocktail::audio {
// Replaces only the output-device query/select slots of the exact
class NativeInputCapture;
// Replaces the profiled input/output-device query/select slots of the exact
// Build-ID-scoped FmodAudioDevice vtable. Roblox keeps owning its FMOD engine,
// while the existing settings UI sees and selects the SDL host routes that
// consume Android AudioTrack PCM.
class RobloxOutputDeviceBridge final {
public:
RobloxOutputDeviceBridge() = default;
RobloxOutputDeviceBridge();
~RobloxOutputDeviceBridge();
RobloxOutputDeviceBridge(const RobloxOutputDeviceBridge&) = delete;
@@ -62,16 +51,25 @@ class RobloxOutputDeviceBridge final {
Status Activate(std::uintptr_t image_base);
Status PatchVtableLocked();
bool RestoreVtableLocked();
int DeviceCount();
void* DeviceInfo(void* result, int index);
int CurrentDevice();
void SelectDevice(int index);
static int GetInputDeviceCount(void *self);
static void *GetInputDeviceInfo(void *result, void *self, int index);
static int GetCurrentInputDevice(void *self);
static void SetCurrentInputDevice(void *self, int index);
int DeviceCount(bool input = false);
void *DeviceInfo(void *result, int index, bool input = false);
int CurrentDevice(bool input = false);
void SelectDevice(int index, bool input = false);
void ConstructGuestString(void* destination, std::string_view value) const;
std::unique_ptr<NativeInputCapture> capture_;
bool capture_profile_supported_ = false;
std::array<std::uintptr_t, 6> original_capture_methods_{};
mutable std::mutex mutex_;
compat::FmodOutputDeviceBridgeProfile profile_;
std::vector<MenuDevice> devices_;
std::array<std::uintptr_t, 4> original_methods_{};
std::vector<MenuDevice> input_devices_;
int selected_input_index_ = 0;
std::array<std::uintptr_t, 8> original_methods_{};
std::uintptr_t library_base_ = 0;
std::uintptr_t* vtable_ = nullptr;
void* string_constructor_ = nullptr;
@@ -0,0 +1,80 @@
#ifndef MOCKTAIL_AUDIO_SDL_AUDIO_CAPTURE_H_
#define MOCKTAIL_AUDIO_SDL_AUDIO_CAPTURE_H_
#include <cstddef>
#include <cstdint>
#include <memory>
#include <string>
#include <string_view>
#include <vector>
#include "mocktail/audio/audio_sink.h"
namespace mocktail::audio {
struct SdlRecordingDevice {
std::uint32_t id = 0;
std::string name;
};
// The virtual `default` target is not returned. Device IDs are process-local;
// names are the stable configuration choice, while `id:<number>` disambiguates
// duplicate names for the current host session.
Status ListSdlRecordingDevices(std::vector<SdlRecordingDevice>* devices);
Status ResolveSdlRecordingDevice(
std::string_view requested,
const std::vector<SdlRecordingDevice>& available_devices,
std::uint32_t* recording_device_id, std::string* resolved_name);
// Must run after SDL audio initialization and before the first capture opens.
Status ConfigureSdlRecordingDevice(
std::string_view requested,
std::vector<SdlRecordingDevice>* available_devices,
std::string* resolved_name);
Status GetConfiguredSdlRecordingDevice(std::uint32_t* recording_device_id,
std::string* resolved_name);
// Migrates live captures without changing their format or callback buffers.
Status SwitchSdlRecordingDevice(std::uint32_t recording_device_id,
std::string *resolved_name);
using AudioCaptureDataCallback = void (*)(void* context,
std::size_t size_bytes);
class AudioCapture {
public:
virtual ~AudioCapture() = default;
virtual const PcmSpec& output_spec() const = 0;
virtual void* buffer_data() = 0;
virtual std::size_t buffer_size_bytes() const = 0;
virtual std::size_t frames_per_buffer() const = 0;
virtual Status Start() = 0;
virtual Status Stop() = 0;
virtual void Shutdown() = 0;
};
struct SdlAudioCaptureOptions {
PcmSpec output_spec;
// Zero selects the configured process target.
std::uint32_t recording_device_id = 0;
std::uint32_t frame_duration_ms = 10;
AudioCaptureDataCallback data_callback = nullptr;
void* data_context = nullptr;
};
// SDL converts the host input to output_spec and delivers fixed-duration
// frames through one stable buffer. The callback runs synchronously on SDL's
// recording thread after that buffer has been filled.
Status CreateSdlAudioCapture(const SdlAudioCaptureOptions& options,
std::unique_ptr<AudioCapture>* capture);
// Called by the SDL subsystem owner before SDL_QuitSubSystem.
Status PrepareSdlAudioCaptureSubsystemShutdown();
} // namespace mocktail::audio
#endif // MOCKTAIL_AUDIO_SDL_AUDIO_CAPTURE_H_
+7 -14
View File
@@ -1,17 +1,3 @@
// Copyright 2026 Mocktail Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MOCKTAIL_AUDIO_SDL_AUDIO_SINK_H_
#define MOCKTAIL_AUDIO_SDL_AUDIO_SINK_H_
@@ -62,6 +48,13 @@ struct SdlAudioSinkOptions {
// Zero selects the configured process target.
std::uint32_t playback_device_id = 0;
bool start_paused = true;
// Optional pull provider for the host device. Each call fills one source
// buffer whose address remains valid until the next provider call. SDL
// copies as many buffers as the current device request needs.
bool (*data_needed_callback)(void *context, const void **data,
std::size_t *size_bytes) = nullptr;
void *data_needed_context = nullptr;
};
// SDL owns conversion, resampling, and borrowed-buffer consumption.
@@ -0,0 +1,21 @@
#ifndef MOCKTAIL_AUDIO_WEBRTC_JNI_AUDIO_BRIDGE_H_
#define MOCKTAIL_AUDIO_WEBRTC_JNI_AUDIO_BRIDGE_H_
#include "mocktail/status.h"
namespace jnivm {
class VM;
}
namespace mocktail::audio {
// Installs WebRtcAudioManager, WebRtcAudioRecord and WebRtcAudioTrack. The
// manager only advertises the host PCM contract; capture opens on recording
// init, never on manager construction. SDL's audio subsystem must remain alive
// until ShutdownWebRtcJniAudioBridge.
Status InstallWebRtcJniAudioBridge(jnivm::VM* vm);
Status ShutdownWebRtcJniAudioBridge(jnivm::VM* vm);
} // namespace mocktail::audio
#endif // MOCKTAIL_AUDIO_WEBRTC_JNI_AUDIO_BRIDGE_H_
@@ -1,17 +1,3 @@
// Copyright 2026 Mocktail Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MOCKTAIL_GRAPHICS_ANDROID_VULKAN_WSI_ADAPTER_H_
#define MOCKTAIL_GRAPHICS_ANDROID_VULKAN_WSI_ADAPTER_H_
@@ -33,30 +19,14 @@ inline constexpr AndroidVulkanResult kAndroidVulkanSuccess =
static_cast<AndroidVulkanResult>(0);
inline constexpr AndroidVulkanResult kAndroidVulkanErrorInitializationFailed =
static_cast<AndroidVulkanResult>(-3);
inline constexpr std::uint64_t kHostInfiniteWaitDiagnosticSliceNs =
1000000000ULL;
inline constexpr std::uint64_t kHostImageAcquireWatchdogTimeoutNs =
kHostInfiniteWaitDiagnosticSliceNs;
inline constexpr char kAndroidSurfaceExtension[] =
"VK_KHR_android_surface";
// Roblox treats VK_SUBOPTIMAL_KHR as a hard error, so expose it as success.
// VK_SUBOPTIMAL_KHR still returns a usable image. Roblox handles it as a fatal
// error, so accept it until the host reports a real out-of-date surface.
VkResult NormalizeAndroidSwapchainResult(VkResult result);
// Bounds only infinite acquire waits so a lost compositor wakeup cannot hang.
std::uint64_t BoundHostImageAcquireTimeout(std::uint64_t requested_timeout);
bool IsHostImageAcquireWatchdogTimeout(std::uint64_t requested_timeout,
VkResult host_result);
VkResult NormalizeHostImageAcquireResult(std::uint64_t requested_timeout,
VkResult host_result);
// Infinite guest waits use finite host slices without exposing slice timeouts.
std::uint64_t BoundHostSynchronizationWaitTimeout(
std::uint64_t requested_timeout);
bool ShouldContinueHostSynchronizationWait(std::uint64_t requested_timeout,
VkResult host_result);
// Returned storage owns the translated extension names.
Status TranslateAndroidVulkanInstanceExtensions(
const std::vector<std::string>& android_extensions,
-14
View File
@@ -1,17 +1,3 @@
// Copyright 2026 Mocktail Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MOCKTAIL_GRAPHICS_ANGLE_PROBE_H_
#define MOCKTAIL_GRAPHICS_ANGLE_PROBE_H_
@@ -1,17 +1,3 @@
// Copyright 2026 Mocktail Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Loading by SONAME can select host ANGLE instead of the Bionic adapter. This
// bridge therefore opens the library beside the executable by exact path.
@@ -0,0 +1,121 @@
#ifndef MOCKTAIL_GRAPHICS_CHROME_TRACE_WRITER_H_
#define MOCKTAIL_GRAPHICS_CHROME_TRACE_WRITER_H_
#include <condition_variable>
#include <cstdint>
#include <cstdio>
#include <filesystem>
#include <initializer_list>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
namespace mocktail {
namespace graphics {
// Environment variable naming the trace file the Vulkan adapter writes.
inline constexpr char kProfileTraceEnvironment[] = "MOCKTAIL_PROFILE_TRACE";
struct TraceArg {
const char* key;
std::int64_t value;
};
// CLOCK_MONOTONIC in nanoseconds.
std::uint64_t TraceClockNanos();
// Writes the Trace Event Format JSON array read by chrome://tracing and
// ui.perfetto.dev. Recording threads only format into a shared buffer; a
// background thread owns file writes, so disk stalls do not land inside the
// measured calls. Events are written in arrival order. Each write ends the
// file with the closing bracket, so a killed session leaves a loadable trace
// holding everything written before it died.
// Names, categories, and keys must outlive the call.
class ChromeTraceWriter final {
public:
static std::unique_ptr<ChromeTraceWriter> Open(
const std::filesystem::path& path, std::string* error);
~ChromeTraceWriter();
ChromeTraceWriter(const ChromeTraceWriter&) = delete;
ChromeTraceWriter& operator=(const ChromeTraceWriter&) = delete;
// A complete ("X") slice on the calling thread. Slices on one thread must
// nest, which scoped timing gives.
void Slice(const char* name, const char* category, std::uint64_t start_ns,
std::uint64_t end_ns, std::initializer_list<TraceArg> args = {});
void Slice(const char* name, const char* category, std::uint64_t start_ns,
std::uint64_t end_ns, const TraceArg* args,
std::size_t arg_count);
// A counter ("C") sample; each name becomes one counter track.
void Counter(const char* name, std::uint64_t timestamp_ns, double value);
// Returns once every event recorded before the call is on disk.
void Flush();
// Flushes, closes the file, and drops later events. Idempotent.
void Close();
private:
explicit ChromeTraceWriter(std::FILE* file);
void Append(const char* event, std::size_t size);
void WriterLoop();
void WritePending();
std::FILE* file_;
const int owner_pid_;
std::mutex file_mutex_; // Guards file_ and spare_.
std::string spare_;
std::mutex mutex_;
std::condition_variable wake_;
std::string pending_;
bool first_event_ = true;
bool closed_ = false;
bool stopping_ = false;
std::thread writer_;
};
// The process trace named by MOCKTAIL_PROFILE_TRACE, opened on first use.
// Null when profiling is off or the file could not be opened. The writer is
// never destroyed; it is closed when the process exits normally.
ChromeTraceWriter* ActiveProfileTrace();
// Times its own lifetime as one slice. A null writer records nothing.
class TraceScope final {
public:
TraceScope(ChromeTraceWriter* writer, const char* name,
const char* category)
: writer_(writer),
name_(name),
category_(category),
start_ns_(writer != nullptr ? TraceClockNanos() : 0) {}
~TraceScope() {
if (writer_ != nullptr) {
writer_->Slice(name_, category_, start_ns_, TraceClockNanos(), args_,
arg_count_);
}
}
TraceScope(const TraceScope&) = delete;
TraceScope& operator=(const TraceScope&) = delete;
// Keeps the first kMaxArgs arguments.
void Arg(const char* key, std::int64_t value) {
if (writer_ != nullptr && arg_count_ < kMaxArgs) {
args_[arg_count_++] = {key, value};
}
}
private:
static constexpr std::size_t kMaxArgs = 4;
ChromeTraceWriter* writer_;
const char* name_;
const char* category_;
std::uint64_t start_ns_;
TraceArg args_[kMaxArgs]{};
std::size_t arg_count_ = 0;
};
} // namespace graphics
} // namespace mocktail
#endif // MOCKTAIL_GRAPHICS_CHROME_TRACE_WRITER_H_
+79
View File
@@ -0,0 +1,79 @@
#ifndef MOCKTAIL_GRAPHICS_ETC2_DECODER_H_
#define MOCKTAIL_GRAPHICS_ETC2_DECODER_H_
#include <cstddef>
#include <cstdint>
namespace mocktail::graphics {
// ETC2 and EAC block encodings from the OpenGL ES 3.0 / Vulkan core formats.
// sRGB variants share the unorm encoding; the colour space belongs to the
// destination format.
enum class EtcFormat {
kEtc2Rgb8,
kEtc2Rgb8A1,
kEtc2Rgba8,
kEacR11,
kEacR11Signed,
kEacRg11,
kEacRg11Signed,
};
// Compressed bytes per 4x4 block.
std::size_t EtcBlockBytes(EtcFormat format);
// Decoded bytes per texel: 4 (RGBA8) for ETC2 formats, 2 per channel
// (16-bit, little-endian) for EAC formats.
std::size_t EtcDecodedTexelBytes(EtcFormat format);
// Decodes a width x height image stored as ceil(width/4) x ceil(height/4)
// blocks into row-major texels. Returns false, writing nothing, when a buffer
// is smaller than the image requires.
bool DecodeEtcImage(EtcFormat format, const std::uint8_t* source,
std::size_t source_bytes, std::uint32_t width,
std::uint32_t height, std::uint8_t* destination,
std::size_t destination_bytes);
// Decodes block rows [first_block_row, first_block_row + block_row_count) of
// the image DecodeEtcImage describes, writing only the texel rows those blocks
// cover. Bands that together cover every block row produce DecodeEtcImage's
// output. Returns false, writing nothing, when a buffer is too small or the
// rows lie outside the image.
bool DecodeEtcImageBlockRows(EtcFormat format, const std::uint8_t* source,
std::size_t source_bytes, std::uint32_t width,
std::uint32_t height,
std::uint32_t first_block_row,
std::uint32_t block_row_count,
std::uint8_t* destination,
std::size_t destination_bytes);
// One DecodeEtcImage call. Jobs must not share destination bytes.
struct EtcDecodeJob {
EtcFormat format = EtcFormat::kEtc2Rgb8;
const std::uint8_t* source = nullptr;
std::size_t source_bytes = 0;
std::uint32_t width = 0;
std::uint32_t height = 0;
std::uint8_t* destination = nullptr;
std::size_t destination_bytes = 0;
// Set to DecodeEtcImage's result.
bool ok = false;
};
// Decodes every job before returning. Large batches are split into block-row
// bands decoded on up to worker_count threads, the caller included; small
// batches decode on the caller only.
void DecodeEtcJobs(EtcDecodeJob* jobs, std::size_t count,
unsigned worker_count);
// Calls run(context) worker_count times, once on the caller and the rest on
// pooled decode workers that run alongside it, and returns when every call
// has returned. `run` must be safe to call from several threads at once, so
// it should drain a shared queue. When the pool is busy with another batch,
// or no worker could be started, run is called on the caller alone.
void RunOnDecodeWorkers(void (*run)(void*), void* context,
unsigned worker_count);
} // namespace mocktail::graphics
#endif // MOCKTAIL_GRAPHICS_ETC2_DECODER_H_
@@ -0,0 +1,36 @@
#ifndef MOCKTAIL_GRAPHICS_GLES_TEXT_OVERLAY_COMPOSITOR_H_
#define MOCKTAIL_GRAPHICS_GLES_TEXT_OVERLAY_COMPOSITOR_H_
#include <memory>
#include "mocktail/graphics/text_overlay_frame.h"
struct SDL_Window;
namespace mocktail::graphics {
struct GlesTextOverlaySource {
bool (*may_present)() = nullptr;
bool (*query)(MocktailTextOverlayFrameInfo *) = nullptr;
bool (*copy)(std::uint64_t, void *, std::size_t) = nullptr;
};
// Composites the host text editor before SDL swaps the game's GLES backbuffer.
// Draw and destruction use the owning GL context when it is current. If that
// context is no longer current at destruction, its destruction frees the GL
// resources. Create a new compositor when creating a new context.
class GlesTextOverlayCompositor {
public:
explicit GlesTextOverlayCompositor(GlesTextOverlaySource source);
~GlesTextOverlayCompositor();
bool Draw(SDL_Window *window);
private:
struct Impl;
std::unique_ptr<Impl> impl_;
};
} // namespace mocktail::graphics
#endif // MOCKTAIL_GRAPHICS_GLES_TEXT_OVERLAY_COMPOSITOR_H_
@@ -1,17 +1,3 @@
// Copyright 2026 Mocktail Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MOCKTAIL_GRAPHICS_GRAPHICS_BACKEND_H_
#define MOCKTAIL_GRAPHICS_GRAPHICS_BACKEND_H_
+13 -14
View File
@@ -1,20 +1,7 @@
// Copyright 2026 Mocktail Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MOCKTAIL_GRAPHICS_PRESENT_MODE_POLICY_H_
#define MOCKTAIL_GRAPHICS_PRESENT_MODE_POLICY_H_
#include <cstdint>
#include <string_view>
#include <vector>
@@ -31,10 +18,22 @@ enum class PresentModePolicy {
PresentModePolicy ResolvePresentModePolicy(std::string_view vsync_value,
std::string_view frame_rate_value);
// Reads MOCKTAIL_VSYNC / MOCKTAIL_FRAME_RATE_LIMIT once. Window pacing and
// the Vulkan adapter must use this instead of getenv'ing the same keys.
PresentModePolicy CachedPresentModePolicy();
std::vector<VkPresentModeKHR>
FilterPresentModes(PresentModePolicy policy,
const std::vector<VkPresentModeKHR> &host_modes);
const char *PresentModePolicyName(PresentModePolicy policy);
const char *PresentModeKhrName(VkPresentModeKHR mode);
// Extra images so acquire does not wait on the compositor-held buffer.
std::uint32_t PreferSwapchainMinImageCount(PresentModePolicy policy,
std::uint32_t requested,
std::uint32_t min_images,
std::uint32_t max_images);
} // namespace graphics
} // namespace mocktail
@@ -1,17 +1,3 @@
// Copyright 2026 Mocktail Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MOCKTAIL_GRAPHICS_SDL_VULKAN_WSI_H_
#define MOCKTAIL_GRAPHICS_SDL_VULKAN_WSI_H_
+1 -15
View File
@@ -1,24 +1,10 @@
// Copyright 2026 Mocktail Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MOCKTAIL_GRAPHICS_TEXT_OVERLAY_FRAME_H_
#define MOCKTAIL_GRAPHICS_TEXT_OVERLAY_FRAME_H_
#include <cstddef>
#include <cstdint>
// Process-local ABI with libvulkan.so. Pixels are RGBA8; consumers must copy a
// Process-local ABI for host compositors. Pixels are RGBA8; consumers must copy a
// matching revision before the producer clears its storage.
struct MocktailTextOverlayFrameInfo {
static constexpr std::uint32_t kAbiVersion = 2;
@@ -0,0 +1,68 @@
#ifndef MOCKTAIL_GRAPHICS_TEXTURE_OVERRIDE_H_
#define MOCKTAIL_GRAPHICS_TEXTURE_OVERRIDE_H_
#include <cstddef>
#include <cstdint>
#include <memory>
#include <mutex>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
namespace mocktail::graphics {
// Tightly packed RGBA8 pixels, row-major, top row first.
struct RgbaImage {
std::uint32_t width = 0;
std::uint32_t height = 0;
std::vector<std::uint8_t> pixels;
};
// FNV-1a over the bytes; the texture identity used for dump and override
// file names.
std::uint64_t HashBytes(const std::uint8_t* data, std::size_t size);
// 16 lowercase hex digits.
std::string HashName(std::uint64_t hash);
bool ReadPngRgba(const std::string& path, RgbaImage* image);
bool WritePngRgba(const std::string& path, const RgbaImage& image);
// Writes width x height RGBA8 pixels to destination, each destination pixel
// the average of the source box it covers. Growing repeats source pixels.
void ResampleRgba(const RgbaImage& source, std::uint32_t width,
std::uint32_t height, std::uint8_t* destination);
void ResampleRgba(const std::uint8_t* source, std::uint32_t source_width,
std::uint32_t source_height, std::uint32_t width,
std::uint32_t height, std::uint8_t* destination);
// Replacement textures keyed by the hash of a texture's level-0 compressed
// bytes. An override lives at <override_dir>/<hash>.png. With a dump
// directory, every level-0 texture seen is written once as
// <dump_dir>/<hash>_<width>x<height>.png so the hash can be found.
// Thread-safe.
class TextureOverrides {
public:
TextureOverrides(std::string override_dir, std::string dump_dir);
// MOCKTAIL_TEXTURE_OVERRIDE_DIR and MOCKTAIL_TEXTURE_DUMP_DIR.
static TextureOverrides FromEnvironment();
bool enabled() const { return !override_dir_.empty() || !dump_dir_.empty(); }
bool dumping() const { return !dump_dir_.empty(); }
// Null when no override file exists; lookups are cached either way.
std::shared_ptr<const RgbaImage> Lookup(std::uint64_t hash);
void Dump(std::uint64_t hash, std::uint32_t width, std::uint32_t height,
const std::uint8_t* rgba);
private:
std::string override_dir_;
std::string dump_dir_;
std::mutex mutex_;
std::unordered_map<std::uint64_t, std::shared_ptr<const RgbaImage>> cache_;
std::unordered_set<std::uint64_t> dumped_;
};
} // namespace mocktail::graphics
#endif // MOCKTAIL_GRAPHICS_TEXTURE_OVERRIDE_H_
-3
View File
@@ -1,6 +1,3 @@
// Copyright 2026 Mocktail Project Authors
// Licensed under the Apache License, Version 2.0.
#ifndef MOCKTAIL_GRAPHICS_VULKAN_ABI_H_
#define MOCKTAIL_GRAPHICS_VULKAN_ABI_H_
@@ -0,0 +1,158 @@
#ifndef MOCKTAIL_GRAPHICS_VULKAN_ETC2_EMULATION_H_
#define MOCKTAIL_GRAPHICS_VULKAN_ETC2_EMULATION_H_
#include <vulkan/vulkan.h>
#include <cstdint>
#include "mocktail/graphics/etc2_decoder.h"
namespace mocktail::graphics {
// Host format that stores a decoded ETC2/EAC image, with its block encoding.
struct Etc2EmulatedFormat {
VkFormat host_format = VK_FORMAT_UNDEFINED;
EtcFormat etc_format = EtcFormat::kEtc2Rgb8;
};
// Maps an ETC2/EAC Vulkan format to its decoded host format. Returns false for
// every other format.
bool LookupEmulatedEtc2Format(VkFormat format, Etc2EmulatedFormat* out);
// Format features reported for an emulated ETC2/EAC format: the host format's
// sampling and transfer features only, as for a native compressed format.
VkFormatFeatureFlags EmulatedEtc2FormatFeatures(
VkFormatFeatureFlags host_features);
// Staging bytes consumed and decoded bytes produced by one 2D upload region.
// Returns false for an empty extent, a depth other than 1, or no layers.
bool Etc2UploadByteCounts(EtcFormat format, const VkExtent3D& extent,
std::uint32_t layer_count, VkDeviceSize* compressed,
VkDeviceSize* decoded);
// Whether emulated ETC2 is reported to the application as a supported
// feature, from the MOCKTAIL_ADVERTISE_ETC2 value: "0" hides it, anything
// else (or unset) reports it. Hidden ETC2 still decodes ETC2 images the
// application creates; it only steers the application toward other formats.
bool Etc2SupportAdvertised(const char* value);
bool Etc2SupportAdvertised();
// Factor applied to emulated ETC2 colour images no larger than
// kSmallTextureMaxExtent, from the MOCKTAIL_SMALL_TEXTURE_UPSCALE value:
// 1 leaves them alone; 2..8 create them that many times larger and resample
// every upload, so a texture override can carry more detail than the
// original. Unset or non-numeric values use kDefaultSmallTextureUpscale;
// numeric values out of range are clamped.
inline constexpr std::uint32_t kSmallTextureMaxExtent = 64;
inline constexpr std::uint32_t kDefaultSmallTextureUpscale = 4;
std::uint32_t SmallTextureUpscale(const char* value);
// A timeline semaphore and the value an asynchronous decode will signal.
// `semaphore` is VK_NULL_HANDLE when the decode already finished on the
// calling thread and the submit needs no extra wait.
struct Etc2SubmitWait {
VkSemaphore semaphore = VK_NULL_HANDLE;
std::uint64_t value = 0;
};
// Reports ETC2/EAC support on hosts without native ETC2 (desktop GPUs) and
// stores those textures decoded. Images are created with the host format;
// each staged upload into one is redirected to a host-visible staging buffer
// that is filled by decoding the application's staging bytes when the
// command buffer is submitted. Every submit decodes again, as a native copy
// re-reads its source on each execution.
// Staging buffers live until their command buffer is begun, reset or freed.
// MOCKTAIL_DISABLE_ETC2_EMULATION=1 turns the emulation off.
class VulkanEtc2Emulation final {
public:
VulkanEtc2Emulation();
~VulkanEtc2Emulation();
VulkanEtc2Emulation(const VulkanEtc2Emulation&) = delete;
VulkanEtc2Emulation& operator=(const VulkanEtc2Emulation&) = delete;
// True when this physical device lacks native ETC2 and emulation is on.
bool PhysicalDeviceNeedsEmulation(VkPhysicalDevice physical_device,
PFN_vkGetPhysicalDeviceFeatures host);
// `timeline_semaphore` states whether the device was created with the
// timelineSemaphore feature enabled. Without it every decode runs on the
// calling thread.
void RegisterDevice(VkDevice device, VkPhysicalDevice physical_device,
bool emulated,
const VkPhysicalDeviceMemoryProperties& memory,
PFN_vkGetDeviceProcAddr get_device_proc_addr,
bool timeline_semaphore = false);
void DestroyDevice(VkDevice device);
VkResult CreateImage(VkDevice device, const VkImageCreateInfo* create_info,
const VkAllocationCallbacks* allocator, VkImage* image);
void DestroyImage(VkDevice device, VkImage image,
const VkAllocationCallbacks* allocator);
VkResult CreateImageView(VkDevice device,
const VkImageViewCreateInfo* create_info,
const VkAllocationCallbacks* allocator,
VkImageView* view);
VkResult BindBufferMemory(VkDevice device, VkBuffer buffer,
VkDeviceMemory memory, VkDeviceSize offset);
VkResult BindBufferMemory2(VkDevice device, std::uint32_t count,
const VkBindBufferMemoryInfo* infos);
VkResult MapMemory(VkDevice device, VkDeviceMemory memory,
VkDeviceSize offset, VkDeviceSize size,
VkMemoryMapFlags flags, void** data);
VkResult MapMemory2(VkDevice device, const VkMemoryMapInfo* info,
void** data);
void UnmapMemory(VkDevice device, VkDeviceMemory memory);
VkResult UnmapMemory2(VkDevice device, const VkMemoryUnmapInfo* info);
void FreeMemory(VkDevice device, VkDeviceMemory memory,
const VkAllocationCallbacks* allocator);
void DestroyBuffer(VkDevice device, VkBuffer buffer,
const VkAllocationCallbacks* allocator);
void CmdCopyBufferToImage(VkDevice device, VkCommandBuffer command_buffer,
VkBuffer source, VkImage destination,
VkImageLayout layout, std::uint32_t region_count,
const VkBufferImageCopy* regions);
void CmdCopyBufferToImage2(VkDevice device, VkCommandBuffer command_buffer,
const VkCopyBufferToImageInfo2* info);
void CmdCopyImage(VkDevice device, VkCommandBuffer command_buffer,
VkImage source, VkImageLayout source_layout,
VkImage destination, VkImageLayout destination_layout,
std::uint32_t region_count, const VkImageCopy* regions);
void CmdCopyImage2(VkDevice device, VkCommandBuffer command_buffer,
const VkCopyImageInfo2* info);
void CmdExecuteCommands(VkDevice device, VkCommandBuffer command_buffer,
std::uint32_t count,
const VkCommandBuffer* secondaries);
// Decodes every upload recorded into these command buffers and the
// secondary command buffers they execute.
//
// With `allow_async` on a device that has a timeline semaphore, the decode
// runs on worker threads, and so does the gather of the compressed bytes
// whenever the application already has them mapped. The returned semaphore
// and value must then be added to the submit's waits, so the GPU copy
// cannot execute before the decode finishes. Values are signalled in the
// order they are handed out.
//
// Those threads read the application's source bytes after this returns,
// which Vulkan allows: the application may not write them between the
// submit and the copy's completion. Unmapping or freeing that memory waits
// for the decodes still reading it.
//
// Returns `{VK_NULL_HANDLE, 0}` when there was nothing to decode, when
// `allow_async` is false, or when the device has no timeline semaphore. In
// those cases every upload is already decoded when this returns.
Etc2SubmitWait PrepareSubmit(const VkCommandBuffer* command_buffers,
std::uint32_t count, bool allow_async = false);
// Destroys the staging buffers owned by a command buffer that is no longer
// pending, after waiting for any decode still writing into them.
void ReleaseCommandBuffer(VkCommandBuffer command_buffer);
struct State;
private:
State* state_;
};
} // namespace mocktail::graphics
#endif // MOCKTAIL_GRAPHICS_VULKAN_ETC2_EMULATION_H_
@@ -1,17 +1,3 @@
// Copyright 2026 Mocktail Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MOCKTAIL_GRAPHICS_VULKAN_TEXT_OVERLAY_COMPOSITOR_H_
#define MOCKTAIL_GRAPHICS_VULKAN_TEXT_OVERLAY_COMPOSITOR_H_
@@ -1,17 +1,3 @@
// Copyright 2026 Mocktail Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MOCKTAIL_PLATFORM_ANDROID_LOG_OBSERVER_H_
#define MOCKTAIL_PLATFORM_ANDROID_LOG_OBSERVER_H_
@@ -1,17 +1,3 @@
// Copyright 2026 Mocktail Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MOCKTAIL_PLATFORM_DISPLAY_REFRESH_CAPABILITIES_H_
#define MOCKTAIL_PLATFORM_DISPLAY_REFRESH_CAPABILITIES_H_
+86 -15
View File
@@ -1,20 +1,7 @@
// Copyright 2026 Mocktail Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MOCKTAIL_PLATFORM_PLATFORM_RUNTIME_H_
#define MOCKTAIL_PLATFORM_PLATFORM_RUNTIME_H_
#include <cmath>
#include <cstdint>
#include <string>
#include <variant>
@@ -40,7 +27,7 @@ struct WindowOptions {
int height = 720;
std::string title = "Roblox";
WindowSurfaceApi surface_api = WindowSurfaceApi::kAngleEgl;
bool high_pixel_density = true;
bool high_pixel_density = false;
bool initially_hidden = true;
// Production callers should keep this true. Tests using SDL's dummy driver
@@ -72,11 +59,69 @@ struct NativeWindowDescriptor {
struct QuitEvent {};
// Android exposes pointer and TextBox geometry in density-independent surface
// coordinates. SDL exposes pointer positions in logical window coordinates,
// while Vulkan presents to the pixel-sized drawable. Keep all three spaces in
// one value so input, IME placement and same-surface overlays use exact inverse
// transforms.
struct SurfaceCoordinateTransform {
int32_t logical_width = 0;
int32_t logical_height = 0;
int32_t pixel_width = 0;
int32_t pixel_height = 0;
float density_scale = 1.0F;
bool valid() const {
return logical_width > 0 && logical_height > 0 && pixel_width > 0 &&
pixel_height > 0 && std::isfinite(density_scale) &&
density_scale > 0.0F;
}
float guest_width() const {
return valid() ? static_cast<float>(pixel_width) / density_scale : 0.0F;
}
float guest_height() const {
return valid() ? static_cast<float>(pixel_height) / density_scale : 0.0F;
}
int32_t rounded_guest_width() const {
return valid() ? static_cast<int32_t>(std::lround(guest_width())) : 0;
}
int32_t rounded_guest_height() const {
return valid() ? static_cast<int32_t>(std::lround(guest_height())) : 0;
}
float HostLogicalToGuestX(float coordinate) const {
return valid() ? coordinate * static_cast<float>(pixel_width) /
static_cast<float>(logical_width) / density_scale
: coordinate;
}
float HostLogicalToGuestY(float coordinate) const {
return valid() ? coordinate * static_cast<float>(pixel_height) /
static_cast<float>(logical_height) / density_scale
: coordinate;
}
float GuestToHostLogicalX(float coordinate) const {
return valid() ? coordinate * density_scale *
static_cast<float>(logical_width) /
static_cast<float>(pixel_width)
: coordinate;
}
float GuestToHostLogicalY(float coordinate) const {
return valid() ? coordinate * density_scale *
static_cast<float>(logical_height) /
static_cast<float>(pixel_height)
: coordinate;
}
};
struct WindowResizedEvent {
int logical_width = 0;
int logical_height = 0;
int pixel_width = 0;
int pixel_height = 0;
// Android DisplayMetrics.density equivalent. This is independent of the
// logical-to-pixel drawable ratio on X11/XWayland.
float density_scale = 1.0F;
};
struct WindowFocusEvent {
@@ -107,6 +152,10 @@ struct MouseMotionEvent {
float delta_x = 0.0f;
float delta_y = 0.0f;
std::uint32_t buttons = 0;
// True while the window holds relative mouse mode. The absolute coordinates
// are meaningless then: X11 reports zeroes, Wayland reports a position that
// accumulates the deltas and can leave the window entirely.
bool relative = false;
};
struct MouseButtonEvent {
@@ -115,6 +164,8 @@ struct MouseButtonEvent {
std::uint8_t clicks = 0;
float x = 0.0f;
float y = 0.0f;
// See MouseMotionEvent::relative.
bool relative = false;
};
struct MouseWheelEvent {
@@ -122,6 +173,8 @@ struct MouseWheelEvent {
float delta_y = 0.0f;
float mouse_x = 0.0f;
float mouse_y = 0.0f;
// See MouseMotionEvent::relative.
bool relative = false;
};
struct TouchEvent {
@@ -142,9 +195,27 @@ struct TouchEvent {
float pressure = 0.0f;
};
enum class GamepadFamily {
kUnknown,
kXbox,
kPlayStation4,
kPlayStation5,
};
struct GamepadDescriptor {
GamepadFamily family = GamepadFamily::kUnknown;
std::string name;
// Bit positions are SDL's standardized gamepad buttons and axes, not raw
// joystick indices or Android key codes.
std::uint32_t buttons = 0;
std::uint32_t axes = 0;
};
struct GamepadConnectionEvent {
std::int64_t instance_id = 0;
bool connected = false;
GamepadDescriptor descriptor{};
bool remapped = false;
};
struct GamepadAxisEvent {
@@ -1,17 +1,4 @@
// Copyright 2026 Mocktail Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Modified by vii from komaruworld/mocktail. See README "About this fork".
#ifndef MOCKTAIL_PLATFORM_SDL_APPLICATION_METADATA_H_
#define MOCKTAIL_PLATFORM_SDL_APPLICATION_METADATA_H_
@@ -20,9 +7,9 @@
namespace mocktail {
namespace platform {
inline constexpr char kMocktailApplicationName[] = "Mocktail";
inline constexpr char kMocktailApplicationName[] = "Nightcap";
inline constexpr char kMocktailApplicationIdentifier[] =
"space.bigrat.mocktail";
"io.github.CoderDayton.nightcap";
// Configures the stable compositor identity before SDL initializes. The
// identifier matches the installed desktop file and icon name so Wayland and
@@ -1,17 +1,3 @@
// Copyright 2026 Mocktail Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MOCKTAIL_PLATFORM_SDL_EVENT_CONVERTER_H_
#define MOCKTAIL_PLATFORM_SDL_EVENT_CONVERTER_H_
@@ -0,0 +1,45 @@
#ifndef MOCKTAIL_PLATFORM_SDL_GAMEPAD_MANAGER_H_
#define MOCKTAIL_PLATFORM_SDL_GAMEPAD_MANAGER_H_
#include <SDL3/SDL_gamepad.h>
#include <map>
#include "mocktail/platform/platform_runtime.h"
namespace mocktail {
namespace platform {
// Main-thread owner. Consumes events from the existing SDL pump; it never
// creates another event loop or consumes keyboard/window events.
class SdlGamepadManager final {
public:
using EventFn = void (*)(void*, const PlatformEvent&);
~SdlGamepadManager();
SdlGamepadManager() = default;
SdlGamepadManager(const SdlGamepadManager&) = delete;
SdlGamepadManager& operator=(const SdlGamepadManager&) = delete;
Status Initialize(void* context, EventFn emit);
void Shutdown();
bool HandleEvent(const PlatformEvent& event);
void ResendState();
private:
SDL_Gamepad* Open(SDL_JoystickID id);
void Remove(SDL_JoystickID id);
void Describe(SDL_JoystickID id, SDL_Gamepad* gamepad, bool remapped);
void EmitState(SDL_JoystickID id, SDL_Gamepad* gamepad);
void Emit(PlatformEventPayload payload);
std::map<SDL_JoystickID, SDL_Gamepad*> gamepads_;
void* context_ = nullptr;
EventFn emit_ = nullptr;
bool initialized_ = false;
};
} // namespace platform
} // namespace mocktail
#endif // MOCKTAIL_PLATFORM_SDL_GAMEPAD_MANAGER_H_
@@ -1,17 +1,3 @@
// Copyright 2026 Mocktail Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MOCKTAIL_PLATFORM_SDL_PLATFORM_RUNTIME_H_
#define MOCKTAIL_PLATFORM_SDL_PLATFORM_RUNTIME_H_
@@ -1,17 +1,3 @@
// Copyright 2026 Mocktail Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MOCKTAIL_PLATFORM_SDL_WINDOW_ICON_H_
#define MOCKTAIL_PLATFORM_SDL_WINDOW_ICON_H_
@@ -1,17 +1,3 @@
// Copyright 2026 Mocktail Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MOCKTAIL_PLATFORM_TEXT_CLIPBOARD_H_
#define MOCKTAIL_PLATFORM_TEXT_CLIPBOARD_H_
-59
View File
@@ -1,59 +0,0 @@
// Copyright 2026 Mocktail Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MOCKTAIL_MOCKTAIL_SHA256_H_
#define MOCKTAIL_MOCKTAIL_SHA256_H_
#include <array>
#include <cstddef>
#include <cstdint>
#include <string>
#include <string_view>
namespace mocktail {
namespace foundation {
class Sha256 final {
public:
Sha256() = default;
~Sha256();
Sha256(const Sha256&) = delete;
Sha256& operator=(const Sha256&) = delete;
void Update(const unsigned char* bytes, std::size_t size);
void Update(std::string_view bytes);
std::string FinalHex();
private:
void Transform(const unsigned char* block);
std::array<std::uint32_t, 8> state_ = {
0x6a09e667U, 0xbb67ae85U, 0x3c6ef372U, 0xa54ff53aU,
0x510e527fU, 0x9b05688cU, 0x1f83d9abU, 0x5be0cd19U,
};
std::array<unsigned char, 64> block_{};
std::size_t block_bytes_ = 0;
std::uint64_t total_bytes_ = 0;
bool finalized_ = false;
};
// Returns the lowercase SHA-256 digest of the exact byte sequence, including
// embedded NUL bytes.
std::string ComputeSha256Hex(std::string_view bytes);
} // namespace foundation
} // namespace mocktail
#endif // MOCKTAIL_MOCKTAIL_SHA256_H_
-14
View File
@@ -1,17 +1,3 @@
// Copyright 2026 Mocktail Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MOCKTAIL_STATUS_H_
#define MOCKTAIL_STATUS_H_
+8 -17
View File
@@ -1,20 +1,7 @@
// Copyright 2026 Mocktail Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MOCKTAIL_RUNTIME_AUTH_RUNTIME_COMPOSITION_H_
#define MOCKTAIL_RUNTIME_AUTH_RUNTIME_COMPOSITION_H_
#include <filesystem>
#include <memory>
#include <string>
#include <string_view>
@@ -94,14 +81,14 @@ struct AuthRuntimeComposition {
jnivm::RobloxAuthIdentity account_identity;
SecureRobloxCredential credential;
long http_status = 0;
bool rejected_credential_retired = false;
std::string error;
explicit operator bool() const { return jni_vm != nullptr; }
};
// A VM requires authentication or explicit guest mode. HTTP 401 clears only a
// managed credential; rejected Sober credentials are suppressed without
// changing Sober's file.
// A VM requires authentication or explicit guest mode. HTTP 401/403 clears
// only Mocktail's managed credential.
AuthRuntimeComposition ComposeAuthRuntime(const Environment& environment,
const RuntimePaths& paths,
services::AuthService& auth_service);
@@ -112,6 +99,10 @@ AuthRuntimeComposition ComposeAuthRuntime(
services::AuthService& auth_service,
std::shared_ptr<services::HttpClient> live_auth_http_client);
// Persists a raw or prefixed Roblox credential to the given cookie file atomically.
bool PersistRobloxCookie(const std::filesystem::path& path,
std::string_view cookie_value);
} // namespace runtime
} // namespace mocktail
+5 -14
View File
@@ -1,17 +1,3 @@
// Copyright 2026 Mocktail Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MOCKTAIL_RUNTIME_COMMAND_LINE_H_
#define MOCKTAIL_RUNTIME_COMMAND_LINE_H_
@@ -44,6 +30,11 @@ struct CommandLineOptions {
std::string launch_request_json;
int launch_argument_index = -1;
bool allow_unverified_build = false;
// Requests one explicit launch of the provider latest without promoting it
// into the managed payload state.
bool force_run_latest = false;
// Chrome trace file the Vulkan adapter writes; empty when not profiling.
std::string profile_trace_path;
};
struct CommandLineParseResult {
-14
View File
@@ -1,17 +1,3 @@
// Copyright 2026 Mocktail Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MOCKTAIL_RUNTIME_CRASH_REPORT_POLICY_H_
#define MOCKTAIL_RUNTIME_CRASH_REPORT_POLICY_H_
+28
View File
@@ -0,0 +1,28 @@
#ifndef MOCKTAIL_RUNTIME_DEVICE_MEMORY_PROFILE_H_
#define MOCKTAIL_RUNTIME_DEVICE_MEMORY_PROFILE_H_
#include <cstdint>
namespace mocktail {
namespace runtime {
// Android ActivityManager memory values published through DeviceParams.
// Roblox derives its device RAM tier, and tiered render budgets, from them.
struct DeviceMemoryProfile {
std::int32_t total_memory_mb = 0;
std::int32_t memory_class_mb = 0;
std::int32_t large_memory_class_mb = 0;
std::int64_t low_memory_killer_background_threshold = 0;
std::int64_t low_memory_killer_foreground_threshold = 0;
bool low_ram_device = false;
};
// Hosts with at least 4 GiB report their real total memory as a normal-RAM
// device. Smaller hosts, or an unreadable total (0), keep the 2 GiB low-RAM
// profile.
DeviceMemoryProfile BuildDeviceMemoryProfile(std::uint64_t host_memory_bytes);
} // namespace runtime
} // namespace mocktail
#endif // MOCKTAIL_RUNTIME_DEVICE_MEMORY_PROFILE_H_
Loaded 100 of 589 files, more files were not shown because too many files have changed in this diff. Show more