Compare commits

...
102 Commits
Author SHA1 Message Date
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
komaruworld 9bdd737d10 Release 1.0.2 2026-08-14 20:58:03 +03:00
komaruworld df253e786f Upd flatpak metainfo 2026-08-14 20:40:38 +03:00
komaruworld 53cd79b7da fix ci 2026-08-14 20:18:26 +03:00
komaruworld febebef554 Update website and add apt,rpm repository 2026-08-14 20:12:36 +03:00
komaruworld 23fe2eeca5 improved Discord rpc, autoupdate
fixes #4
2026-08-14 00:34:46 +03:00
komaruworld 6f5de3a31d improved ci 2026-08-13 22:36:42 +03:00
komaruworld ea5929faf6 improved the logs 2026-08-13 18:37:30 +03:00
komaruworld 220c083e83 Fix SDL3_ttf CMake linkage 2026-08-13 13:29:38 +03:00
komaruworld d778a5bc8a Update AUR packages for 1.0.1 2026-08-13 12:42:13 +03:00
komaruworld 0a039628db Sync Discord RPC configuration help 2026-08-13 12:24:02 +03:00
komaruworld b40fd5d2d6 Release 1.0.1 2026-08-13 12:20:31 +03:00
komaruworld 5fb44c66e6 upd flatpak description 2026-08-12 21:40:58 +03:00
komaruworld 5d636749fa rewrite some text 2026-08-12 20:02:54 +03:00
komaruworld 88b7382577 Fix some issues with discord rpc 2026-08-12 18:18:51 +03:00
komaruworld 3ce39c1f39 Fix autoupdate
Fixed a bug where the payload was being checked even though it had already been checked
2026-08-12 18:16:26 +03:00
komaruworld 6aa22c257d add discord rpc and fix issues with gamemode 2026-08-12 17:42:34 +03:00
komaruworld e5cf5e3de4 add mocktail.bigrat.space domian to github pages 2026-08-12 16:00:27 +03:00
komaruworld 36733cfb75 make better pkgbuild
thx strz
2026-08-12 13:31:15 +03:00
komaruworld ca760f111b fix flatpak
fix issues with "rbx_bin/libroblox.so: No such file"
2026-08-12 13:30:24 +03:00
komaruworld bfb27b4104 upd ci 2026-08-12 10:59:46 +03:00
komaruworld 5663fe7d25 upd shit 2026-08-12 10:50:43 +03:00
516 changed files with 28525 additions and 40859 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
+102
View File
@@ -0,0 +1,102 @@
name: Bug report
description: Report a reproducible problem in Mocktail
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/space.bigrat.mocktail/.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 #
+114
View File
@@ -0,0 +1,114 @@
name: Publish AUR packages
on:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: aur-packages
cancel-in-progress: true
jobs:
publish:
name: Publish AUR packages
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/mocktail-git"
version="$(sed -n \
's/^[[:space:]]*VERSION[[:space:]]\+\([0-9][0-9.]*\).*/\1/p' \
CMakeLists.txt | head -n 1)"
[[ "$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/mocktail-git
install -o builder -g builder -m 0644 \
/pkg/PKGBUILD /tmp/mocktail-git/PKGBUILD
runuser -u builder -- bash -c \
"cd /tmp/mocktail-git && makepkg --printsrcinfo" \
> /pkg/.SRCINFO
'
grep -Fqx "pkgver=${pkgver}" "$package_dir/PKGBUILD"
grep -Fqx $'\tpkgver = '"${pkgver}" "$package_dir/.SRCINFO"
printf 'AUR_PKGVER=%s\n' "$pkgver" >> "$GITHUB_ENV"
- name: Publish to the AUR
shell: bash
env:
AUR_SSH_PRIVATE_KEY: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
AUR_SSH_KEY_PASSPHRASE: ${{ secrets.AUR_SSH_KEY_PASSPHRASE }}
run: |
set -Eeuo pipefail
[[ -n "$AUR_SSH_PRIVATE_KEY" ]]
[[ -n "$AUR_SSH_KEY_PASSPHRASE" ]]
private_key="$RUNNER_TEMP/aur_ed25519"
askpass="$RUNNER_TEMP/aur_askpass"
known_hosts="$RUNNER_TEMP/aur_known_hosts"
printf '%s\n' "$AUR_SSH_PRIVATE_KEY" > "$private_key"
unset AUR_SSH_PRIVATE_KEY
chmod 0600 "$private_key"
printf '%s\n' \
'#!/usr/bin/env bash' \
'printf '\''%s\n'\'' "$AUR_SSH_KEY_PASSPHRASE"' \
> "$askpass"
chmod 0700 "$askpass"
eval "$(ssh-agent -s)"
trap 'ssh-agent -k >/dev/null; rm -f -- "$private_key" "$askpass"' EXIT
DISPLAY=:0 SSH_ASKPASS="$askpass" SSH_ASKPASS_REQUIRE=force \
ssh-add "$private_key" </dev/null
unset AUR_SSH_KEY_PASSPHRASE
ssh-keyscan -T 10 -t ed25519 aur.archlinux.org > "$known_hosts"
fingerprint="$(ssh-keygen -lf "$known_hosts" -E sha256 | awk '{print $2}')"
[[ "$fingerprint" == \
'SHA256:RFzBCUItH9LZS0cKB5UE6ceAYhBD5C8GeOBip8Z11+4' ]]
export GIT_SSH_COMMAND="ssh -F /dev/null -i $private_key \
-o BatchMode=yes -o IdentitiesOnly=yes \
-o StrictHostKeyChecking=yes \
-o UserKnownHostsFile=$known_hosts"
publish_aur_package() {
local package_name="$1"
local commit_message="$2"
local source_dir="packaging/aur/$package_name"
local aur_dir="$RUNNER_TEMP/${package_name}-aur"
git clone --branch master --single-branch \
"ssh://aur@aur.archlinux.org/${package_name}.git" "$aur_dir"
install -m 0644 "$source_dir/PKGBUILD" "$aur_dir/PKGBUILD"
install -m 0644 "$source_dir/.SRCINFO" "$aur_dir/.SRCINFO"
git -C "$aur_dir" config user.name komaruworld
git -C "$aur_dir" config user.email rtysus228@gmail.com
git -C "$aur_dir" add PKGBUILD .SRCINFO
if git -C "$aur_dir" diff --cached --quiet; then
printf '%s is already published\n' "$package_name"
return
fi
git -C "$aur_dir" commit -m "$commit_message"
git -C "$aur_dir" push origin HEAD:master
}
publish_aur_package mocktail-git "Update to ${AUR_PKGVER}"
publish_aur_package mocktail 'make builds stable'
publish_aur_package mocktail-bin 'make builds stable'
+26 -115
View File
@@ -3,7 +3,27 @@ name: CI
on:
push:
branches: [main]
paths:
- '.github/workflows/**'
- 'CMakeLists.txt'
- 'Makefile'
- 'README.md'
- 'config/**'
- 'packaging/**'
- 'scripts/**'
- 'stubs/**'
- 'tests/**'
pull_request:
paths:
- '.github/workflows/**'
- 'CMakeLists.txt'
- 'Makefile'
- 'README.md'
- 'config/**'
- 'packaging/**'
- 'scripts/**'
- 'stubs/**'
- 'tests/**'
workflow_dispatch:
permissions:
@@ -32,124 +52,15 @@ 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 .
flatpak:
name: Flatpak build
runs-on: ubuntu-24.04
timeout-minutes: 120
steps:
- name: Check out source
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
submodules: recursive
- name: Install Flatpak builder
shell: bash
run: |
sudo apt-get update
sudo apt-get install --yes flatpak flatpak-builder gnupg
flatpak remote-add --user --if-not-exists flathub \
https://dl.flathub.org/repo/flathub.flatpakrepo
- name: Build Flatpak repository and bundle
shell: bash
env:
FLATPAK_GPG_PRIVATE_KEY: ${{ secrets.FLATPAK_GPG_PRIVATE_KEY }}
FLATPAK_GPG_KEY_ID: ${{ secrets.FLATPAK_GPG_KEY_ID }}
run: |
set -Eeuo pipefail
git update-ref refs/heads/main "$GITHUB_SHA"
builder_signing=()
bundle_keys=()
publish=false
if [[ "$GITHUB_REF" == refs/heads/main && \
"$GITHUB_EVENT_NAME" != pull_request ]]; then
publish=true
[[ -n "$FLATPAK_GPG_PRIVATE_KEY" && \
-n "$FLATPAK_GPG_KEY_ID" ]]
install -d -m 0700 "$RUNNER_TEMP/mocktail-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
builder_signing=(
"--gpg-sign=$FLATPAK_GPG_KEY_ID"
"--gpg-homedir=$RUNNER_TEMP/mocktail-gnupg"
)
bundle_keys=(--gpg-keys=mocktail-flatpak.gpg)
fi
flatpak-builder \
--arch=x86_64 \
--jobs=4 \
--user \
--install-deps-from=flathub \
--disable-rofiles-fuse \
--force-clean \
--default-branch=stable \
--repo=repo \
"${builder_signing[@]}" \
build-flatpak-ci \
packaging/flatpak/space.bigrat.mocktail.json
if [[ "$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" \
repo
fi
flatpak build-bundle \
--arch=x86_64 \
"${bundle_keys[@]}" \
repo \
Mocktail-x86_64.flatpak \
space.bigrat.mocktail \
stable
if [[ "$publish" == true ]]; then
./scripts/assemble_flatpak_pages.sh \
repo Mocktail-x86_64.flatpak mocktail-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
if-no-files-found: error
retention-days: 14
- name: Configure GitHub Pages
if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0
- name: Upload GitHub Pages repository
if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
with:
path: public
deploy-pages:
name: Publish Flatpak repository
if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
needs: flatpak
runs-on: ubuntu-24.04
timeout-minutes: 10
permissions:
contents: read
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy GitHub Pages
id: deployment
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0
+193
View File
@@ -0,0 +1,193 @@
name: Flatpak
on:
workflow_run:
workflows: [Native packages]
types: [completed]
pull_request:
paths:
- '.github/workflows/flatpak.yml'
- '.gitmodules'
- 'CMakeLists.txt'
- 'LICENSE'
- 'assets/screenshots/**'
- 'cmake/**'
- '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'
- 'scripts/assemble_flatpak_pages.sh'
- 'scripts/assemble_native_repositories.sh'
- 'site/**'
- 'src/**'
- 'stubs/**'
- 'third_party/**'
workflow_dispatch:
permissions:
actions: read
contents: read
concurrency:
group: flatpak-${{ github.event.workflow_run.head_sha || github.ref }}
cancel-in-progress: true
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 }}
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: |
sudo apt-get update
sudo apt-get install --yes flatpak flatpak-builder gnupg
flatpak remote-add --user --if-not-exists flathub \
https://dl.flathub.org/repo/flathub.flatpakrepo
- name: Build Flatpak repository and bundle
shell: bash
env:
FLATPAK_GPG_PRIVATE_KEY: ${{ secrets.FLATPAK_GPG_PRIVATE_KEY }}
FLATPAK_GPG_KEY_ID: ${{ secrets.FLATPAK_GPG_KEY_ID }}
run: |
set -Eeuo pipefail
git update-ref refs/heads/main "$MOCKTAIL_SOURCE_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"
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
builder_signing=(
"--gpg-sign=$FLATPAK_GPG_KEY_ID"
"--gpg-homedir=$RUNNER_TEMP/mocktail-gnupg"
)
bundle_keys=(--gpg-keys=mocktail-flatpak.gpg)
fi
flatpak-builder \
--arch=x86_64 \
--jobs=4 \
--user \
--install-deps-from=flathub \
--disable-rofiles-fuse \
--force-clean \
--default-branch=stable \
--repo=repo \
"${builder_signing[@]}" \
build-flatpak-ci \
packaging/flatpak/space.bigrat.mocktail.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" \
repo
fi
flatpak build-bundle \
--arch=x86_64 \
"${bundle_keys[@]}" \
repo \
Mocktail-x86_64.flatpak \
space.bigrat.mocktail \
stable
if [[ "$MOCKTAIL_PUBLISH" == true ]]; then
./scripts/assemble_flatpak_pages.sh \
repo Mocktail-x86_64.flatpak mocktail-flatpak.gpg \
public native-repositories
fi
- name: Upload Flatpak
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: Mocktail-x86_64-flatpak
path: Mocktail-x86_64.flatpak
if-no-files-found: error
retention-days: 14
- name: Configure GitHub Pages
if: env.MOCKTAIL_PUBLISH == 'true'
uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0
- name: Upload GitHub Pages repository
if: env.MOCKTAIL_PUBLISH == 'true'
uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
with:
path: public
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')
needs: flatpak
runs-on: ubuntu-24.04
timeout-minutes: 10
permissions:
contents: read
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy GitHub Pages
id: deployment
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0
+156 -34
View File
@@ -3,7 +3,25 @@ name: Native packages
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/**'
workflow_dispatch:
permissions:
@@ -15,7 +33,7 @@ concurrency:
jobs:
appimage:
name: AppImage (Ubuntu 26.04)
name: AppImage (AnyLinux)
runs-on: ubuntu-24.04
timeout-minutes: 90
steps:
@@ -31,50 +49,89 @@ jobs:
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
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:
@@ -97,14 +154,16 @@ jobs:
- 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 \
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 \
@@ -114,6 +173,8 @@ jobs:
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 \
@@ -143,12 +204,14 @@ jobs:
- 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 \
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 \
@@ -158,6 +221,9 @@ jobs:
cmake -S . -B build-rpm -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=/usr \
-DCMAKE_INSTALL_LIBDIR=lib \
-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 \
@@ -192,7 +258,7 @@ jobs:
--workdir /workspace \
archlinux:base-devel bash -Eeuo pipefail -c '
pacman -Syu --needed --noconfirm \
cmake git ninja pkgconf sdl3 sdl3_ttf curl openssl \
cmake git ninja pkgconf lld 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
@@ -219,7 +285,7 @@ jobs:
if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
needs: [appimage, deb, rpm, arch]
runs-on: ubuntu-24.04
timeout-minutes: 15
timeout-minutes: 30
permissions:
contents: write
steps:
@@ -235,13 +301,52 @@ jobs:
path: release
merge-multiple: true
- name: Prepare continuous packages
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
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
find release -maxdepth 1 -type f -print
git config user.name komaruworld
git config user.email rtysus228@gmail.com
git tag --force continuous "$GITHUB_SHA"
@@ -253,3 +358,20 @@ jobs:
--notes "Automated development packages from main."
fi
gh release upload continuous release/* --clobber
while IFS= read -r asset; do
case "$asset" in
mocktail-nightly.AppImage|mocktail-nightly.deb|mocktail-nightly.rpm|mocktail-nightly.pkg.tar.zst)
;;
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
+7
View File
@@ -2,6 +2,7 @@
/build/
/build_*/
/build-flatpak/
/build-anylinux*/
/.flatpak-builder/
/cmake-build-*/
/out/
@@ -20,6 +21,12 @@ Testing/
CMakeUserPresets.json
*.cmake.user
# local direnv development shell cache
.direnv/
# clangd cache
.cache
*.o
*.obj
*.a
+150 -14
View File
@@ -8,7 +8,7 @@
cmake_minimum_required(VERSION 3.20)
project(Mocktail
VERSION 0.1.0
VERSION 1.0.3
DESCRIPTION "Android x86-64 Roblox compatibility runtime for Linux"
LANGUAGES CXX
)
@@ -16,6 +16,40 @@ project(Mocktail
include(GNUInstallDirs)
find_package(Threads REQUIRED)
set(MOCKTAIL_BUILD_GIT_COMMIT "unknown")
set(MOCKTAIL_BUILD_GIT_DIRTY "unknown")
find_package(Git QUIET)
if(Git_FOUND AND EXISTS "${CMAKE_SOURCE_DIR}/.git")
execute_process(
COMMAND "${GIT_EXECUTABLE}" rev-parse --short=12 HEAD
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
RESULT_VARIABLE MOCKTAIL_GIT_REV_PARSE_RESULT
OUTPUT_VARIABLE MOCKTAIL_GIT_REV_PARSE_OUTPUT
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
)
if(MOCKTAIL_GIT_REV_PARSE_RESULT EQUAL 0 AND
MOCKTAIL_GIT_REV_PARSE_OUTPUT MATCHES "^[0-9a-fA-F]+$")
string(TOLOWER "${MOCKTAIL_GIT_REV_PARSE_OUTPUT}"
MOCKTAIL_BUILD_GIT_COMMIT)
execute_process(
COMMAND "${GIT_EXECUTABLE}" status --porcelain --untracked-files=normal
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
RESULT_VARIABLE MOCKTAIL_GIT_STATUS_RESULT
OUTPUT_VARIABLE MOCKTAIL_GIT_STATUS_OUTPUT
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
)
if(MOCKTAIL_GIT_STATUS_RESULT EQUAL 0)
if(MOCKTAIL_GIT_STATUS_OUTPUT STREQUAL "")
set(MOCKTAIL_BUILD_GIT_DIRTY "false")
else()
set(MOCKTAIL_BUILD_GIT_DIRTY "true")
endif()
endif()
endif()
endif()
set(MOCKTAIL_DEFAULT_COMPATIBILITY_MANIFEST
"${CMAKE_SOURCE_DIR}/config/roblox_compatibility.json"
CACHE FILEPATH
@@ -26,6 +60,18 @@ 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
"Discord application ID baked into Mocktail Rich Presence releases"
)
string(LENGTH "${MOCKTAIL_DISCORD_APPLICATION_ID}"
MOCKTAIL_DISCORD_APPLICATION_ID_LENGTH)
if(NOT MOCKTAIL_DISCORD_APPLICATION_ID STREQUAL "" AND
(NOT MOCKTAIL_DISCORD_APPLICATION_ID MATCHES "^[0-9]+$" OR
MOCKTAIL_DISCORD_APPLICATION_ID_LENGTH LESS 17 OR
MOCKTAIL_DISCORD_APPLICATION_ID_LENGTH GREATER 20))
message(FATAL_ERROR
"MOCKTAIL_DISCORD_APPLICATION_ID must be empty or a Discord snowflake")
endif()
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
@@ -92,12 +138,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
@@ -155,12 +196,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)
@@ -168,6 +209,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
@@ -177,6 +219,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
@@ -185,7 +228,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
@@ -221,6 +263,7 @@ target_include_directories(mocktail_legacy_jnivm PUBLIC
add_library(mocktail_runtime STATIC
src/runtime/command_line.cc
src/runtime/crash_report_policy.cc
src/runtime/discord_rpc.cc
src/runtime/device_profile.cc
src/runtime/environment.cc
src/runtime/external_launch_broker.cc
@@ -241,21 +284,39 @@ 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/runtime_config.cc
src/runtime/runtime_config_bootstrap.cc
src/runtime/runtime_config_file.cc
src/runtime/runtime_paths.cc
src/runtime/session_log.cc
src/runtime/single_instance_lock.cc
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
)
add_library(Mocktail::Runtime ALIAS mocktail_runtime)
target_include_directories(mocktail_runtime PUBLIC include)
target_include_directories(mocktail_runtime PUBLIC
${MOCKTAIL_JNI_INCLUDE_DIRS}
)
target_compile_definitions(mocktail_runtime PRIVATE
"MOCKTAIL_DISCORD_APPLICATION_ID=\"${MOCKTAIL_DISCORD_APPLICATION_ID}\""
)
set_property(SOURCE src/runtime/session_log.cc APPEND PROPERTY
COMPILE_DEFINITIONS
"MOCKTAIL_PROJECT_VERSION=\"${PROJECT_VERSION}\""
"MOCKTAIL_BUILD_GIT_COMMIT=\"${MOCKTAIL_BUILD_GIT_COMMIT}\""
"MOCKTAIL_BUILD_GIT_DIRTY=\"${MOCKTAIL_BUILD_GIT_DIRTY}\""
"MOCKTAIL_BUILD_TYPE=\"$<CONFIG>\""
"MOCKTAIL_BUILD_COMPILER_ID=\"${CMAKE_CXX_COMPILER_ID}\""
"MOCKTAIL_BUILD_COMPILER_VERSION=\"${CMAKE_CXX_COMPILER_VERSION}\""
"MOCKTAIL_BUILD_TARGET_SYSTEM=\"${CMAKE_SYSTEM_NAME}\""
"MOCKTAIL_BUILD_TARGET_PROCESSOR=\"${CMAKE_SYSTEM_PROCESSOR}\""
)
mocktail_apply_compile_options(mocktail_runtime)
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND
CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 16)
@@ -266,9 +327,9 @@ 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
CURL::libcurl
nlohmann_json::nlohmann_json
PkgConfig::GIO
PkgConfig::LIBYAML
${CMAKE_DL_LIBS}
)
@@ -348,9 +409,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
@@ -362,6 +420,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)
@@ -390,6 +449,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
@@ -402,6 +462,7 @@ 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
@@ -466,6 +527,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
@@ -568,6 +642,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"
@@ -600,6 +708,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
@@ -655,7 +768,20 @@ install(CODE "
endif()
")
set(CPACK_PACKAGE_NAME "mocktail")
set(MOCKTAIL_PACKAGE_NAME "mocktail" CACHE STRING
"Native package name"
)
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")
message(FATAL_ERROR
"MOCKTAIL_PACKAGE_NAME must be mocktail or mocktail-nightly")
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")
@@ -677,6 +803,16 @@ set(CPACK_RPM_FILE_NAME RPM-DEFAULT)
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()
include(CPack)
add_dependencies(mocktail all_stubs)
+20
View File
@@ -0,0 +1,20 @@
# Frequently asked questions
[How do I change my FPS?](/FAQ.md#how-do-i-change-my-fps)
### How do I change my FPS?
Go to $HOME/.config/mocktail, there is a file called config.yaml (appears after opening for the first time mocktail)
<img width="460" height="241" alt="image" src="https://github.com/user-attachments/assets/b30b1fea-4937-4858-937a-3096a9e8b2ad" />
with your text editor of preference search for "graphics"
<img width="1022" height="619" alt="image" src="https://github.com/user-attachments/assets/f86791e9-7ac3-4d13-b81b-3cf9e3e64c3d" />
change the display value to your desired fps, also turn off vsync if you want more fps than your display supports
+1 -1
View File
@@ -187,7 +187,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Copyright 2026 komaruworld
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
+10 -12
View File
@@ -1,5 +1,5 @@
.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 install-updater payload-status payload-rollback support-bundle clean submodules help
.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
BUILD_DIR := build
BINARY := $(BUILD_DIR)/mocktail
@@ -14,6 +14,9 @@ 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
@@ -73,13 +76,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)"
@@ -134,16 +135,13 @@ run-angle: ## Run with ANGLE/Vulkan compatibility backend
update-roblox: ## Validate and import the current x86_64 Roblox bundle cached by Sober
@./scripts/update_roblox_payload.sh
update-auto: build ## Download latest Roblox, derive its ABI, canary twice and activate safely
update-auto: build ## Download latest Roblox, derive its ABI, run two canaries, then activate
@./$(BUILD_DIR)/mocktail_updater update
update-auto-launch: build ## Update Roblox and launch the verified current payload
@./$(BUILD_DIR)/mocktail_updater update
@./$(BINARY)
install-updater: ## Install and enable the six-hour systemd user update timer
@./scripts/install_user_updater.sh
payload-status: build ## Show current and previous-good immutable payloads
@./$(BUILD_DIR)/mocktail_updater status
+85 -34
View File
@@ -4,13 +4,15 @@
[![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)
[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)
[![Discord](https://img.shields.io/discord/1543955607869063269?label=discord)](https://discord.gg/rhgpfcmFSD)
Mocktail runs the Android `x86_64` Roblox client on Linux. It provides the
Android ABI and JNI pieces the client expects, then uses SDL3 and Vulkan or
OpenGL for the Linux side.
Mocktail 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 still experimental and may break after a Roblox update. It is not
affiliated with Roblox Corporation or VinegarHQ.
Mocktail is an independent community project. It is not affiliated with Roblox
Corporation or VinegarHQ and does not distribute the Roblox client.
## How it works
@@ -24,8 +26,17 @@ an update fails.
## Install with Flatpak
Install the stable release from Flathub:
```bash
flatpak install --user https://komaruworld.github.io/mocktail/mocktail.flatpakref
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
```
@@ -44,52 +55,86 @@ 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`.
## Screenshots
## 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>Flatpak build</summary>
<summary>Screenshots</summary>
![Roblox home in Mocktail](docs/screenshots/flatpak-home.png)
![Roblox home in Mocktail](assets/screenshots/flatpak-home.png)
![Roblox gameplay in Mocktail](docs/screenshots/flatpak-gameplay-tower.png)
![Roblox gameplay in Mocktail](assets/screenshots/flatpak-gameplay-tower.png)
![Roblox experience in Mocktail](docs/screenshots/flatpak-gameplay-lobby.png)
![Roblox experience in Mocktail](assets/screenshots/flatpak-gameplay-lobby.png)
</details>
## Other Linux packages
## FFlag overrides
The latest AppImage, DEB, RPM, and Arch packages are attached to the
[continuous release](https://github.com/komaruworld/mocktail/releases/tag/continuous).
Put a JSON object in `$XDG_CONFIG_HOME/mocktail/fflags.json` (usually
`~/.config/mocktail/fflags.json`). Mocktail applies it on the next launch.
```bash
# AppImage
chmod +x Mocktail-x86_64.AppImage
./Mocktail-x86_64.AppImage
# Ubuntu/Debian
sudo apt install ./mocktail_*_amd64.deb
# Fedora
sudo dnf install ./mocktail-*.x86_64.rpm
# Arch Linux
sudo pacman -U ./mocktail-*-x86_64.pkg.tar.zst
```json
{
"FFlagExample": "True",
"DFIntExample": "120"
}
```
## FAQ
To see the most Frequently Asked Question go check [FAQ.md](FAQ.md)
## 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, Mocktail 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, 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 \
libcapstone-dev libgtk-4-dev libadwaita-1-dev libwebkitgtk-6.0-dev \
@@ -102,7 +147,7 @@ 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 \
sudo pacman -S --needed base-devel cmake git ninja pkgconf lld sdl3 sdl3_ttf \
curl openssl nlohmann-json libyaml libelf minizip capstone gtk4 \
libadwaita webkitgtk-6.0 libutf8proc fontconfig libglvnd \
libplacebo vulkan-headers vulkan-icd-loader zlib
@@ -113,7 +158,7 @@ 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 \
capstone-devel gtk4-devel libadwaita-devel webkitgtk6.0-devel \
@@ -129,6 +174,12 @@ make build
./build/mocktail
```
## Known limitations
Mocktail does not currently work with `hardened_malloc`. Using
`hardened_malloc` may cause Mocktail to fail to start or crash during runtime.
Run Mocktail without `hardened_malloc` enabled.
## License
[Apache License 2.0](LICENSE). Third-party components keep their own licenses.
File renamed without changes.
+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)
+16 -5
View File
@@ -5,7 +5,7 @@ include_guard(GLOBAL)
find_package(PkgConfig REQUIRED)
pkg_check_modules(UTF8PROC REQUIRED IMPORTED_TARGET libutf8proc)
pkg_check_modules(SDL3_TTF REQUIRED IMPORTED_TARGET sdl3-ttf)
find_package(SDL3_ttf REQUIRED CONFIG)
pkg_check_modules(FONTCONFIG REQUIRED IMPORTED_TARGET fontconfig)
get_filename_component(MOCKTAIL_INPUT_ROOT "${CMAKE_CURRENT_LIST_DIR}/.."
@@ -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
)
@@ -35,13 +37,15 @@ target_link_libraries(mocktail_input_runtime PUBLIC
PkgConfig::UTF8PROC
PRIVATE
PkgConfig::FONTCONFIG
PkgConfig::SDL3_TTF
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()
+1
View File
@@ -69,6 +69,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
-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_
+63 -16
View File
@@ -31,17 +31,23 @@ 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.
# vsync: off
performance:
# Boolean (default: false): size Roblox scheduler/render queues from every
@@ -63,22 +69,61 @@ 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
integrations:
discord_rpc:
# Boolean (default: false): publish Mocktail activity to Discord Desktop.
# This never signs in to Discord and never reads an account token.
enabled: false
# Boolean (default: true): show the current Roblox experience name.
show_place_name: true
# Boolean (default: true): show how long the current session has run.
show_elapsed_time: true
join:
# Boolean (default: true): let friends open the current experience. When
# Roblox provides a public server ID, the button targets that server.
enabled: true
# Boolean (default: true): never expose private or reserved joins.
public_servers_only: true
# button_label: Join Server
# English defaults. Uncomment only the lines you want to customize.
# text:
# browsing: Browsing experiences
# joining: Joining an experience
# playing: "{place_name}"
# state: Playing Roblox
# unknown_place: Unknown experience
# 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
@@ -86,7 +131,9 @@ updates:
# backend, and promote only on success. An existing current payload is
# preserved when probation fails.
automatic: 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.
+40
View File
@@ -42,6 +42,46 @@
"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"
},
"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"
}
-15
View File
@@ -1,15 +0,0 @@
[Unit]
Description=Check, canary, and install the latest working Roblox for Mocktail
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
EnvironmentFile=%h/.config/mocktail/updater.env
ExecStart=/usr/bin/env ${MOCKTAIL_UPDATE_HELPER} update
Nice=10
IOSchedulingClass=best-effort
IOSchedulingPriority=7
[Install]
WantedBy=default.target
-12
View File
@@ -1,12 +0,0 @@
[Unit]
Description=Periodically check Roblox updates for Mocktail
[Timer]
OnBootSec=5m
OnUnitActiveSec=6h
RandomizedDelaySec=15m
Persistent=true
Unit=mocktail-update.service
[Install]
WantedBy=timers.target
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;
+2 -26
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_
@@ -19,16 +5,8 @@ namespace mocktail::compat {
using BionicAtForkCallback = void (*)(void);
// Registers Android's __register_atfork callback contract with the host
// pthread runtime. Bionic associates registrations with dso_handle and
// removes them when that DSO unloads. POSIX pthread_atfork has no matching
// unregister operation, so the host bridge accepts dso_handle for ABI
// compatibility but cannot use it for cleanup.
//
// Callback code must remain mapped for every later fork. In practice, a guest
// DSO that has registered callbacks must stay loaded until process exit;
// unloading is safe only when the process guarantees that it will never fork
// again.
// POSIX cannot unregister by dso_handle, so registered guest callbacks must
// remain mapped for every later fork.
int RegisterBionicAtFork(BionicAtForkCallback prepare,
BionicAtForkCallback parent,
BionicAtForkCallback child, void* dso_handle) noexcept;
@@ -37,8 +15,6 @@ int RegisterBionicAtFork(BionicAtForkCallback prepare,
extern "C" {
// Exact LP64 Android/Bionic __register_atfork signature. The prefixed host
// symbol is exported to the guest as "__register_atfork" by synthetic libc.so.
int mocktail_bionic_register_atfork(
mocktail::compat::BionicAtForkCallback prepare,
mocktail::compat::BionicAtForkCallback parent,
-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_
+24 -39
View File
@@ -1,32 +1,18 @@
// 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>
namespace mocktail::compat {
// Android x86-64's malloc.h declares ten size_t fields in this order. This is
// not the host's struct mallinfo: glibc's legacy type uses int,
// and musl exposes no allocator-telemetry API. On hosts without a compatible
// size_t-based telemetry API, BionicMallinfo returns an all-zero "unknown"
// snapshot. Callers must not interpret that fallback as proof of zero usage.
// Android uses size_t fields here; glibc uses int and musl has no equivalent.
// An all-zero fallback means unknown, not zero usage.
struct BionicMallinfoSnapshot {
size_t arena = 0;
size_t ordblks = 0;
@@ -44,10 +30,7 @@ struct BionicLocaleState;
using BionicLocale = BionicLocaleState*;
using BionicThreadDestructor = void (*)(void*);
// Do not use the host socket structs here. Although glibc happens to match
// Android LP64, musl declares cmsg_len and msg_controllen as 32-bit socklen_t.
// Bionic x86-64 uses size_t for both, as audited from the vendored
// bionic/libc/include/sys/socket.h.
// Bionic uses size_t where musl uses 32-bit socklen_t.
struct BionicMessageHeader {
void* msg_name = nullptr;
uint32_t msg_namelen = 0;
@@ -64,35 +47,33 @@ struct BionicControlMessageHeader {
int cmsg_type = 0;
};
// Implements Android's exported CMSG_NXTHDR helper over the explicit Bionic
// x86-64 msghdr/cmsghdr layout above. Malformed or truncated control buffers
// fail closed with nullptr.
// Malformed or truncated control buffers return nullptr.
BionicControlMessageHeader* BionicCmsgNextHeader(
BionicMessageHeader* message, BionicControlMessageHeader* current) noexcept;
// Registers one destructor for the current thread using libstdc++'s Itanium
// C++ ABI implementation. That implementation is present on both supported
// glibc and musl toolchains and preserves destructor execution at thread exit.
// Uses libstdc++'s Itanium ABI on both supported libc implementations.
int BionicCxaThreadAtExit(BionicThreadDestructor destructor, void* argument,
void* dso_handle) noexcept;
// Fills the complete buffer from the Linux kernel RNG. The implementation
// falls back to /dev/urandom only when getrandom is unavailable or blocked and
// aborts rather than returning predictable or partially initialized bytes.
// Falls back to /dev/urandom; never returns partial or predictable bytes.
void BionicArc4RandomBuffer(void* buffer, size_t size) noexcept;
BionicMallinfoSnapshot BionicMallinfo() noexcept;
bool BionicMallinfoHasHostTelemetry() noexcept;
// Formats one Linux errno value using Bionic's POSIX strerror_r contract.
// Unlike glibc's GNU variant, success is reported as zero; truncation returns
// -1 and sets errno to ERANGE while preserving the caller's errno on success.
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;
// Bionic supports only C and C.UTF-8, whose integer parsing is identical. A
// host C locale is installed only for the duration of each call, so ambient
// process locale cannot change guest parsing and a Bionic locale pointer is
// never mistaken for an incompatible host locale_t.
// Parses under host C locale without passing an incompatible Bionic locale_t.
long long BionicStrToLongLongLocale(const char* text, char** end, int base,
BionicLocale locale) noexcept;
unsigned long long BionicStrToUnsignedLongLongLocale(
@@ -110,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_
+4 -27
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_PTHREAD_CREATE_RUNTIME_H_
#define MOCKTAIL_COMPAT_BIONIC_PTHREAD_CREATE_RUNTIME_H_
@@ -23,30 +9,21 @@ namespace mocktail::compat {
using NativeThreadInitializer = void (*)();
// Pinned Bionic LP64 pthread_internal.h keeps total per-thread reservation
// near 1 MiB by subtracting its 32 KiB alternate signal stack from the normal
// thread stack. Host defaults are not ABI substitutes: musl's default is only
// 128 KiB on the supported Void runtime.
// Bionic reserves 32 KiB of its 1 MiB thread allocation for the signal stack.
inline constexpr size_t kBionicLp64DefaultThreadStackSize =
(1U * 1024U * 1024U) - (32U * 1024U);
static_assert(kBionicLp64DefaultThreadStackSize == 1015808U);
// Used only after a Bionic-sized stack is rejected by the host at thread
// creation time. The retry takes the largest of this floor, the requested
// guest size, and the host default. That retains enough stack on musl without
// shrinking glibc's default.
// Retry floor when the host rejects the Bionic-sized stack.
inline constexpr size_t kBionicLp64FallbackThreadStackSize = 2U * 1024U * 1024U;
// Installs the payload-owned per-thread initializer used by subsequently
// created guest threads. Passing nullptr restores ordinary host behavior.
// nullptr restores host behavior.
void ConfigureBionicPthreadThreadInitializer(
NativeThreadInitializer initializer) noexcept;
} // namespace mocktail::compat
// Creates a host thread after rebuilding the supported POSIX attributes.
// Rebuilding avoids passing ABI-private pthread_attr_t state from the Android
// payload directly into glibc.
// Rebuilds attributes instead of passing Android pthread_attr_t to glibc.
extern "C" int mocktail_bionic_pthread_create(pthread_t* thread,
const pthread_attr_t* attr,
void* (*start_routine)(void*),
+2 -17
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_PTHREAD_KEY_RUNTIME_H_
#define MOCKTAIL_COMPAT_BIONIC_PTHREAD_KEY_RUNTIME_H_
@@ -31,9 +17,8 @@ constexpr uint32_t kBionicPthreadKeyValidFlag = uint32_t{1} << 31;
using BionicPthreadKeyDestructor = void (*)(void*);
// implementation of Bionic's pthread_key_* ABI. It owns only guest
// keys registered through the synthetic Android libc.so, while ordinary host
// code continues to use glibc pthread keys.
// Implements Bionic's pthread_key_* ABI for keys registered through the
// synthetic libc.so. Host code continues to use glibc pthread keys.
class BionicPthreadKeyRuntime {
public:
static BionicPthreadKeyRuntime& Instance() noexcept;
+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_
-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_BUILD_PROFILE_H_
#define MOCKTAIL_COMPAT_BUILD_PROFILE_H_
-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_
+1 -15
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_
@@ -62,7 +48,7 @@ enum class NativePreJniBootstrapStatus {
kFailed,
};
// Installs one exact-Build-ID experiment before libroblox constructors run.
// Installs one exact-Build-ID profile before libroblox constructors run.
// The caller owns policy: this function never enables itself from environment
// variables and must only receive a profile selected from the loaded ELF ID.
HostAbiExperimentResult
+1 -15
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_
@@ -81,7 +67,7 @@ struct NativeAllocatorProfile {
inline constexpr size_t kMaxHostBridgeEntries = 8;
inline constexpr size_t kMaxConstructorRunRanges = 26;
// Build-ID-scoped native ABI knowledge for supported and experimental payload
// Build-ID-scoped native ABI knowledge for supported and candidate payload
// profiles. Keeping these addresses outside the legacy startup graph makes the
// version boundary reviewable and independently testable.
struct HostAbiProfile {
+8 -31
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_
@@ -28,11 +14,8 @@ struct FileSha256Result {
explicit operator bool() const noexcept { return error.empty(); }
};
// Computes a lowercase SHA-256 digest without invoking a shell or an external
// utility. The external ABI approval receipt binds exact profile,
// compatibility-manifest, and payload bytes through this implementation; the
// runtime itself is bound by its stable GNU Build ID because installation may
// rewrite RPATH bytes.
// Approval receipts bind the profile, manifest, and payload to this digest.
// Runtime identity uses its Build ID because installation may rewrite RPATH.
FileSha256Result ComputeFileSha256(const std::string& path);
struct ExternalHostAbiProfileRequest {
@@ -55,30 +38,24 @@ struct ExternalHostAbiProfileResult {
explicit operator bool() const noexcept { return error.empty(); }
};
// Loads one exact, payload-bound ABI profile into the process registry. A
// candidate is accepted only by an explicitly authorized canary process. A
// normal launch instead requires a separate approval receipt produced after
// repeated Tier C readiness. Built-in profiles are immutable and always win.
// Candidates require canary authorization; normal launches require an
// approval receipt. Built-in profiles always win.
ExternalHostAbiProfileResult LoadExternalHostAbiProfile(
const ExternalHostAbiProfileRequest& request);
// Environment integration used by the pre-native compatibility gate. An
// unset MOCKTAIL_HOST_ABI_PROFILE_FILE is a successful no-op. If it is set,
// candidate authorization requires MOCKTAIL_HOST_ABI_CANARY=1,
// MOCKTAIL_ALLOW_CANDIDATE_HOST_ABI=1, and the explicit research command-line
// marker. Approved profiles require MOCKTAIL_HOST_ABI_APPROVAL_RECEIPT and
// neither canary environment marker.
// An unset profile is a no-op. Candidates require both canary flags and the
// research CLI marker; approved profiles require a receipt and no canary flag.
ExternalHostAbiProfileResult InitializeHostAbiProfileFromEnvironment(
const std::string& payload_path,
const std::string& compatibility_manifest_path,
const std::string& expected_build_id,
bool explicit_unverified_authorization);
// Registry lookup used by FindHostAbiProfile after its immutable built-in
// table. Callers should use FindHostAbiProfile rather than this function.
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_
+193 -116
View File
@@ -1,33 +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.
// jnivm/jnivm.h — Pseudo Java Native Interface Virtual Machine.
//
// Provides a lightweight C++ implementation of a JVM stub for use with
// Android-native shared libraries (e.g., libroblox.so) running on Linux.
// No real Java bytecode is executed; instead, all JNI calls are intercepted
// and dispatched to registered C++ callbacks.
//
// Usage:
// auto vm = std::make_shared<jnivm::VM>();
// auto cls = vm->RegisterClass("android/content/Context");
// cls->RegisterMethod("getSystemService",
// "(Ljava/lang/String;)Ljava/lang/Object;",
// [](JNIEnv*, jobject, ...) { return nullptr; });
#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>
@@ -42,29 +14,20 @@
namespace jnivm {
// Forward declarations
class VM;
class Class;
// Object — base type for all pseudo-Java objects managed by the pseudo-JVM.
// Analogous to java.lang.Object in the real JVM.
class Object {
public:
explicit Object(std::shared_ptr<Class> klass) : klass_(std::move(klass)) {}
virtual ~Object() = default;
// Returns the pseudo-class descriptor of this object.
std::shared_ptr<Class> GetClass() const { return klass_; }
private:
std::shared_ptr<Class> klass_;
};
// MethodCallback — signature for registered JNI method stubs.
// The callback receives the raw JNIEnv pointer and the receiver jobject.
using MethodCallback = std::function<void(JNIEnv *, jobject)>;
// Resolved Roblox account identity supplied by the host runtime. This
@@ -75,8 +38,6 @@ struct RobloxAuthIdentity {
std::string display_name;
};
// Host device capabilities exposed through the pseudo Android framework.
// The guest ABI remains Android, while the default profile // describes a Linux desktop with physical mouse and keyboard input.
struct PlatformIdentity {
bool touch_enabled = false;
bool mouse_enabled = true;
@@ -98,24 +59,19 @@ struct RobloxCredentialView {
};
using RobloxCredentialProvider = RobloxCredentialView (*)(const void *context);
using RobloxCookieGetter = jstring (*)(JNIEnv* env, jclass clazz,
jstring domain);
// Creates a Configuration object consistent with the VM's immutable platform
// identity snapshot. This is the replacement for legacy construction of
// contradictory Android keyboard/touch fields.
jobject CreateAndroidConfiguration(JNIEnv *env);
// Narrow write-only boundary for credentials produced by Roblox after the
// native runtime has started. A successfully persisted value supersedes the
// bound provider for the remainder of the current VM lifetime so native sign-in
// can become authoritative without reopening an untrusted credential source.
// A stored credential replaces the bound provider for the rest of this VM's
// lifetime.
struct RobloxCredentialSinkCallbacks {
bool (*store)(void *context, const char *data, std::size_t size) = nullptr;
};
// Narrow host callback boundary for org/fmod/AudioDevice. The pseudo-JVM owns
// only an opaque shared context and never depends on the SDL audio runtime.
// Each Java receiver is passed as a stable identity so the host can keep
// independent device lifecycles without retaining a JNI local reference.
// The VM keeps the host context opaque. Receiver identities let the backend
// track devices without retaining JNI local references.
struct FmodAudioDeviceCallbacks {
bool (*init)(void *context, const void *identity, int channels,
int sample_rate_hz, int block_size_frames,
@@ -126,17 +82,73 @@ struct FmodAudioDeviceCallbacks {
void (*shutdown)(void *context) = nullptr;
};
// Narrow host callback boundary for the activity.setWindowFlags(II)V contract.
// Production LuaApp retains its activity through the android.app.Activity
// interface, while GameActivity startup uses the concrete subclass. Guest JNI
// threads only publish Android flags; the platform callback must marshal any
// SDL work onto the SDL main thread.
// 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 {
bool (*set_flags)(void *context, int flags, int mask) = nullptr;
};
// Immutable snapshot of the Android NativeTextBoxInfo payload supplied by
// Roblox when a native text field receives focus.
struct RobloxTextBoxInfo {
float x = 0.0f;
float y = 0.0f;
@@ -161,8 +173,7 @@ struct RobloxTextInputShowRequest {
RobloxTextBoxInfo info;
};
// Narrow callback boundary for the Roblox native text-input callbacks. The
// pseudo-JVM snapshots all JNI-owned arguments before entering this boundary.
// JNI-owned arguments are copied before the host callback runs.
struct RobloxTextInputCallbacks {
void (*show)(void *context,
const RobloxTextInputShowRequest &request) = nullptr;
@@ -172,9 +183,8 @@ struct RobloxTextInputCallbacks {
void (*shutdown)(void *context) = nullptr;
};
// Narrow boundary for a native MessageBus RawCallback object. The VM
// retains context while a callback is in flight and dispatches only the exact
// run(Ljava/lang/String;)V method on the exact created receiver.
// Dispatch accepts run(String) only on a receiver created by this VM. The
// context stays alive while the callback runs.
struct MessageBusRawCallbacks {
void (*run)(void *context, JNIEnv *env, jstring message) = nullptr;
};
@@ -183,18 +193,19 @@ struct MessageBusRequestHandlerCallbacks {
std::string (*run)(void *context, JNIEnv *env, jstring message) = nullptr;
};
// Narrow boundary for the current APK's legacy BrowserService transport.
// Dispatch accepts only Callback.onItemSet(Ljava/lang/String;)V on the exact
// pseudo receiver created by CreateMemStorageCallback.
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 {
void (*on_item_set)(void *context, JNIEnv *env, jstring value) = nullptr;
};
// Narrow boundary for the exact APK callbacks that update host web
// state: NativeGLJavaInterface notifications, web activities, cookie sync,
// and JNICookieProtocol.OnSetCookieHandler. The VM retains the shared callback
// context while a callback is in flight; consumers must copy JNI strings
// before returning.
// Web callbacks share a retained context while in flight. Consumers must copy
// JNI strings before returning.
struct RobloxDataModelNotificationCallbacks {
void (*on_notification)(void *context, JNIEnv *env, jstring type,
jstring data) = nullptr;
@@ -209,37 +220,22 @@ struct RobloxDataModelNotificationCallbacks {
jstring url) = nullptr;
};
// Narrow boundary for the exact NativeHelper lifecycle notification
// emitted when Roblox returns from an experience to LuaApp.
struct RobloxExperienceLifecycleCallbacks {
void (*on_lua_app_did_return)(void *context) = nullptr;
};
// Class — represents a pseudo Java class registered in the pseudo-JVM.
// Stores the class name (JNI descriptor format) and a map of method stubs.
class Class {
public:
explicit Class(std::string name) : name_(std::move(name)) {}
// Returns the JNI-style class descriptor, e.g. "android/content/Context".
const std::string &GetName() const { return name_; }
// Registers a JNI method stub.
//
// Args:
// method_name: Simple method name (e.g. "getSystemService").
// signature: JNI type descriptor (e.g. "(Ljava/lang/String;)V").
// callback: C++ lambda invoked when the method is called via JNI.
void RegisterMethod(const std::string &method_name,
const std::string &signature, MethodCallback callback);
// Looks up a method stub by name and signature.
// Returns nullptr if not found.
const MethodCallback *FindMethod(const std::string &method_name,
const std::string &signature) const;
// Returns all registered methods (name → callback).
const std::unordered_map<std::string, MethodCallback> &GetMethods() const {
return methods_;
}
@@ -247,57 +243,39 @@ public:
private:
std::string name_;
// Key format: "methodName:descriptor"
std::unordered_map<std::string, MethodCallback> methods_;
};
// VM — the top-level pseudo Java Virtual Machine.
//
// Owns a registry of pseudo-classes and exposes a JavaVM* compatible
// pointer for passing to JNI_OnLoad in the loaded shared library.
class VM {
public:
VM();
~VM();
// Disallow copy and assign.
VM(const VM &) = delete;
VM &operator=(const VM &) = delete;
// Registers a new pseudo-class by its JNI descriptor.
// Repeated registration returns the existing instance.
//
// Args:
// class_name: JNI-style descriptor, e.g.
// "com/roblox/client/RobloxActivity".
//
// Returns:
// Shared pointer to the registered (or pre-existing) Class object.
// Repeated registration returns the existing class.
std::shared_ptr<Class> RegisterClass(const std::string &class_name);
// Finds a previously registered class.
// Returns nullptr if the class has not been registered.
std::shared_ptr<Class> FindClass(const std::string &class_name) const;
// Returns the raw JavaVM pointer compatible with JNI_OnLoad signature.
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);
// Returns the attached JNIEnv pointer for the current host thread.
// Binds this thread's JNI environment to this VM. Switching owners must
// not reuse the previous VM's native function table.
JNIEnv *GetJNIEnv();
// Returns the number of registered classes.
std::size_t GetClassCount() const { return class_registry_.size(); }
// Replaces the resolved account identity with an independent copy. Invalid
// (non-positive) identities are normalized to the unresolved state.
void SetRobloxAuthIdentity(const RobloxAuthIdentity &identity);
// Returns the VM to its unresolved account state.
void ClearRobloxAuthIdentity();
// Returns an immutable-by-value snapshot safe for use by JNI callbacks on
@@ -320,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();
@@ -342,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();
@@ -370,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.
@@ -405,12 +445,10 @@ public:
void ClearRobloxExperienceLifecycleCallbacks();
bool DispatchRobloxExperienceLuaAppDidReturn();
// Restores env->functions to &native_interface_ in case libroblox replaced
// it.
// Restores the pseudo-JNI table after guest code replaces env->functions.
void RestoreFunctions();
private:
// Internal JNI function tables populated with stub implementations.
JNIInvokeInterface_ invoke_interface_ = {};
JavaVM java_vm_storage_ = {};
JavaVM *java_vm_ = nullptr;
@@ -419,7 +457,6 @@ private:
JNIEnv jni_env_storage_ = {};
JNIEnv *jni_env_ = nullptr;
// Registry of pseudo-classes indexed by their JNI descriptor.
std::unordered_map<std::string, std::shared_ptr<Class>> class_registry_;
mutable std::mutex roblox_auth_identity_mutex_;
@@ -433,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;
@@ -447,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;
@@ -474,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;
@@ -498,11 +577,9 @@ private:
std::shared_ptr<RobloxExperienceLifecycleBinding>
roblox_experience_lifecycle_binding_;
// Initialises the JNIInvokeInterface_ and JNINativeInterface_ function tables
// with stub implementations that forward calls to this VM instance.
void InitJNIFunctionTables();
};
} // namespace jnivm
#endif // SOBER_TEST_JNIVM_JNIVM_H_
#endif // MOCKTAIL_JNIVM_JNIVM_H_
+11 -9
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,10 +84,11 @@ 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;
};
// Transitional reverse-engineering runtime. New subsystems must expose // interfaces and be composed outside this implementation.
// Transitional runtime kept for the reverse-engineered startup path.
int Run(const runtime::CommandLineOptions& options,
RuntimeDependencies dependencies);
+7 -50
View File
@@ -1,30 +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.
// libc_shim/libc_shim.h — Bionic libc → Glibc call translation layer.
//
// Intercepts calls made by Bionic-compiled code (libroblox.so) to
// Bionic-specific libc functions and forwards them to their Glibc equivalents
// on the host Linux system.
//
// Key responsibilities:
// - pthread_mutex_t size/layout differences between Bionic and Glibc.
// - Virtual filesystem path mapping (APK sandbox → Flatpak paths).
// - Bionic-only syscall wrappers missing in Glibc.
#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>
@@ -57,10 +32,8 @@ struct HostCaBundleResolution {
// boundary as the caller that will later free them.
void ConfigureGuestAllocator(GuestAllocator allocator) noexcept;
// Installs all shim hooks. Must be called before the target .so is loaded.
//
// After this call, dlopen(3) symbol resolution for known Bionic symbols will
// be intercepted and redirected to the shim implementations.
// Must run before loading the target library so its Bionic imports resolve to
// these shims.
void Install();
// Resolves the host TLS trust bundle without weakening certificate
@@ -76,29 +49,13 @@ HostCaBundleResolution ConfigureHostCaBundlePathMappings();
const char* HostCaBundleStatusName(HostCaBundleStatus status);
// Maps an Android-style virtual path to its Flatpak sandbox equivalent.
//
// Example:
// "/data/user/0/com.roblox.client/files/logs" →
// "/home/user/.var/app/org.vinegarhq.Sober/data/sober/logs"
//
// Args:
// android_path: Absolute path as seen by the Bionic library.
//
// Returns:
// Translated host path. Returns android_path unchanged if no mapping
// is registered for it.
// Applies the longest matching Android-to-host path prefix. Unmapped paths are
// returned unchanged.
std::string TranslatePath(const std::string& android_path);
// Registers a path prefix mapping used by TranslatePath.
//
// Args:
// android_prefix: Android path prefix to intercept.
// host_prefix: Replacement prefix on the host filesystem.
void RegisterPathMapping(const std::string& android_prefix,
const std::string& host_prefix);
// Removes all registered path mappings. Primarily for use in tests.
void ClearPathMappings();
} // namespace libc_shim
@@ -123,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_
+5 -81
View File
@@ -1,29 +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.
// linker/linker.h — ELF Bionic-compatible shared library loader.
//
// Provides a minimal interface for loading Android ELF shared libraries
// (.so files compiled for Bionic libc / Android ABI) into a Linux process
// address space, resolving dependencies and exporting a symbol table.
//
// Design notes:
// - Uses dlopen(3) under the hood for system libraries.
// - For Bionic-ABI libraries (libroblox.so), manual ELF parsing and
// mmap(2)-based loading is required; this header exposes that interface.
// - All paths follow POSIX conventions.
#ifndef MOCKTAIL_LINKER_LINKER_H_
#define MOCKTAIL_LINKER_LINKER_H_
@@ -34,9 +8,7 @@
namespace linker {
// Opaque handle returned by LoadLibrary. Callers must not interpret the
// pointer value; it is only valid as an argument to ResolveSymbol or
// UnloadLibrary.
// Opaque; valid only with this linker's resolve/unload functions.
using LibraryHandle = void*;
using SymbolMap = std::unordered_map<std::string, void*>;
using AndroidLibraryLoadObserver = bool (*)(void* context,
@@ -50,59 +22,27 @@ struct ProgramHeaderValidation {
explicit operator bool() const noexcept { return valid; }
};
// AddSyntheticLibrary — registers one Android SONAME and only the exports
// owned by that library. Dependencies of subsequently loaded Android ELF
// files resolve these symbols through mcpelauncher-linker.
LibraryHandle AddSyntheticLibrary(const std::string& soname,
const SymbolMap& exports);
// RegisterSyntheticSymbol — stages or adds one export to a specific Android
// SONAME. If the synthetic library is already loaded, its symbol table is
// updated in place.
// Updates an already loaded library in place.
void RegisterSyntheticSymbol(const std::string& soname,
const std::string& name, void* address);
// Registers Android Bionic thread-local and synchronization ABIs only
// for synthetic libc.so. Host code keeps its glibc implementations.
// These adapters belong to their synthetic SONAME, never the host namespace.
void RegisterBionicPthreadKeyRuntimeForLibc();
// Registers Android's DSO-aware __register_atfork entrypoint only for
// synthetic libc.so. The host pthread bridge preserves callback order but
// cannot unregister callbacks when a guest DSO unloads.
void RegisterBionicAtForkRuntimeForLibc();
// Registers Android LP64 positioned I/O and seek entrypoints only for
// synthetic libc.so. The bridge relies on the x86-64 host's 64-bit
// off_t contract and never exposes host large-file feature-macro aliases.
void RegisterBionicLargeFileRuntimeForLibc();
// Registers host-independent implementations of Android libc exports that
// are not uniformly supplied by glibc and musl. Every symbol remains owned by
// synthetic libc.so and preserves the Android x86-64 ABI.
void RegisterBionicHostLibcRuntimeForLibc();
// Registers Bionic sysconf name translation only for synthetic libc.so.
// Bionic and glibc use different integer values for sysconf names.
void RegisterBionicSysconfRuntimeForLibc();
// Registers the Android LP64 sigaction layout adapter only for libc.so.
void RegisterBionicSignalRuntimeForLibc();
// Registers the LP64 Bionic FILE/__sF ABI and host stdio translation only for
// synthetic libc.so.
void RegisterBionicStdioRuntimeForLibc();
// Restores mcpelauncher-linker's own libdl exports after legacy host-symbol
// discovery. Android dlopen/dlsym must never escape into glibc.
// Android dlopen/dlsym must not escape into glibc.
void RegisterBionicDynamicLoaderForLibdl();
// GetSyntheticLibrarySymbols — returns a snapshot of one SONAME's exports.
// The copy is safe to inspect while other libraries are being registered.
SymbolMap GetSyntheticLibrarySymbols(const std::string& soname);
// UpdateAndroidLibraryPath — configures the Bionic linker's dependency search
// path. Multiple directories use the same colon-separated syntax as
// LD_LIBRARY_PATH.
void UpdateAndroidLibraryPath(const std::string& search_path);
// Installs the single process owner that validates or adapts a real Android
@@ -112,18 +52,12 @@ bool SetAndroidLibraryLoadObserver(AndroidLibraryLoadObserver observer,
void* context);
void ClearAndroidLibraryLoadObserver(void* context);
// OpenAndroidLibrary — loads a real Android ELF and registers its handle under
// logical_name. The containing directory is added to the Bionic dependency
// search path before loading.
LibraryHandle OpenAndroidLibrary(const std::string& real_path,
const std::string& logical_name);
// Legacy compatibility bridge. New code should use AddSyntheticLibrary or
// OpenAndroidLibrary so real files and synthetic SONAMEs cannot be confused.
LibraryHandle LoadLibrary(const std::string& real_path,
const std::string& host_alias);
// ResolveSymbol — looks up an exported symbol in a loaded library.
void* ResolveSymbol(LibraryHandle handle, const std::string& symbol_name);
// Crashpad execution is intentionally outside Mocktail's supported JNI
@@ -132,24 +66,15 @@ void* ResolveSymbol(LibraryHandle handle, const std::string& symbol_name);
bool IsBlockedRobloxCrashReportNativeSymbol(
std::string_view symbol_name) noexcept;
// UnloadLibrary — releases resources associated with a loaded library.
void UnloadLibrary(LibraryHandle handle);
// Legacy compatibility bridge used by the current monolithic startup path.
// New code should use RegisterSyntheticSymbol and assign every export to its
// owning SONAME.
void RegisterSymbol(const std::string& name, void* addr);
// Returns the legacy accumulated symbol table. It remains available until the
// existing startup path is migrated to per-SONAME maps.
const SymbolMap& GetBionicSymbols();
// Returns mcpelauncher-linker's Bionic dl_iterate_phdr entrypoint. Android
// unwinders must not receive glibc's host implementation/layout.
// Android unwinders must not receive glibc's implementation or layout.
void* BionicDlIteratePhdrAddress();
// Confirms that the Bionic program-header iterator can see a manually loaded
// Android ELF, including the unwind metadata needed for C++ exceptions.
ProgramHeaderValidation ValidateBionicProgramHeaders(LibraryHandle handle);
// Returns the mapped ELF base for a loaded Android library alias. The lookup
@@ -157,7 +82,6 @@ ProgramHeaderValidation ValidateBionicProgramHeaders(LibraryHandle handle);
// alias or a host-dlopen library without Bionic image metadata.
std::uintptr_t FindLoadedAndroidLibraryBase(const std::string& logical_name);
// SymbolTable — global registry of alias → handle mappings.
const std::unordered_map<std::string, LibraryHandle>& GetSymbolTable();
} // namespace linker
+8 -29
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_
@@ -22,9 +8,7 @@
namespace mocktail::audio {
// Interleaved PCM formats accepted at the host audio boundary. Endianness is
// explicit because Android x86-64 producers use little-endian PCM regardless
// of the host implementation chosen by SDL.
// Android producers supply little-endian interleaved PCM.
enum class PcmSampleFormat {
kUnsigned8,
kSigned16LittleEndian,
@@ -41,11 +25,9 @@ struct PcmSpec {
using AudioBufferReleaseCallback = void (*)(void* context, const void* data,
std::size_t size_bytes);
// A PCM submission. With no release callback, Enqueue() copies the bytes
// before returning. With a release callback, the sink may borrow the buffer;
// the caller must keep it valid until the callback runs. A successful
// submission invokes the callback exactly once, including when Clear() or
// Shutdown() discards queued audio. A failed submission never invokes it.
// Without a callback Enqueue copies the bytes. With one, a successful Enqueue
// borrows them until exactly one release callback, including on Clear or
// Shutdown. On failure the callback is not invoked.
struct PcmBuffer {
const void* data = nullptr;
std::size_t size_bytes = 0;
@@ -57,9 +39,7 @@ Status ValidatePcmSpec(const PcmSpec& spec);
std::size_t BytesPerSample(PcmSampleFormat format);
std::size_t BytesPerFrame(const PcmSpec& spec);
// Thread-safe PCM playback contract. Implementations perform device format
// conversion and resampling through their host audio library; callers always
// submit data in source_spec().
// Thread-safe; callers submit source_spec(), and the backend converts it.
class AudioSink {
public:
virtual ~AudioSink() = default;
@@ -68,15 +48,14 @@ class AudioSink {
virtual Status Enqueue(const PcmBuffer& buffer) = 0;
virtual Status Pause() = 0;
virtual Status Resume() = 0;
// Linear gain is delegated to the host audio library. Zero mutes, one is
// unity gain, and negative or non-finite values are rejected.
// Zero mutes; one is unity; negative and non-finite values are invalid.
virtual Status SetGain(float linear_gain) = 0;
virtual Status Flush() = 0;
virtual Status Clear() = 0;
virtual Status GetQueuedBytes(std::size_t* size_bytes) const = 0;
// Idempotent. It waits for in-flight API calls and releases all borrowed
// buffers before returning. Calls started after shutdown fail closed.
// Idempotent. Waits for in-flight calls and borrowed buffers; later calls
// fail closed.
virtual void Shutdown() = 0;
};
@@ -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_
@@ -23,14 +9,10 @@
namespace mocktail::audio {
// org.fmod.AudioDevice is an ordinary Java object. The JNI boundary passes
// its jobject value through this opaque key without making the audio layer
// depend on JNI headers or retain a JNI reference.
// Opaque jobject identity; the audio layer never retains a JNI reference.
using FmodJavaAudioDeviceIdentity = const void*;
// A factory returns a paused sink with exactly source_spec. Production uses
// SDL3; tests can inject a deterministic sink without opening a host device.
// factory_context must remain valid until the runtime is shut down.
// Returns a paused sink. factory_context must outlive the runtime.
using FmodJavaAudioSinkFactory = Status (*)(
void* factory_context, const PcmSpec& source_spec,
std::unique_ptr<AudioSink>* sink);
@@ -44,8 +26,7 @@ struct FmodJavaAudioRuntimeOptions {
std::size_t max_buffer_bytes_per_device = 64U * 1024U * 1024U;
};
// Content-free counters suitable for readiness logs. They never expose PCM,
// JNI identities, or guest pointers.
// Contains no PCM, JNI identities, or guest pointers.
struct FmodJavaAudioRuntimeStats {
std::uint64_t init_attempts = 0;
std::uint64_t initialized_devices = 0;
@@ -65,18 +46,9 @@ struct FmodJavaAudioRuntimeStats {
std::size_t pending_bytes = 0;
};
// Thread-safe implementation of the APK's exact Java playback contract:
//
// AudioDevice.init(channels, sampleRate, blockSize, blockCount)Z
// AudioDevice.write(byte[], length)V
// AudioDevice.close()V
//
// Init() allocates blockCount fixed slots of blockSize frames. Every successful
// Write() copies guest bytes synchronously into one slot, then lends that stable
// storage to AudioSink without a second PCM copy. FmodJavaAudioRuntime performs
// no per-write allocation for buffer storage or release ownership. A producer
// waits with AudioTrack-style blocking backpressure when all slots are borrowed
// instead of growing memory or dropping a block.
// Fixed slots avoid per-write allocation. Write copies into a slot, then lends
// it to AudioSink; a full pool applies blocking backpressure instead of
// growing memory or dropping audio.
class FmodJavaAudioRuntime final {
public:
explicit FmodJavaAudioRuntime(
@@ -94,8 +66,7 @@ class FmodJavaAudioRuntime final {
FmodJavaAudioRuntimeStats GetStats() const;
// Idempotent. Prevents new devices/writes, shuts down every sink, and waits
// for every borrowed owned buffer to be released before returning.
// Waits for every borrowed buffer to be released.
void Shutdown();
private:
@@ -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_
-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,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_ROBLOX_OUTPUT_DEVICE_BRIDGE_H_
#define MOCKTAIL_AUDIO_ROBLOX_OUTPUT_DEVICE_BRIDGE_H_
@@ -0,0 +1,76 @@
#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);
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_
+14 -36
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_
@@ -25,9 +11,7 @@
namespace mocktail::audio {
// SDL documents subsystem initialization and shutdown as main-thread-only.
// The process composition root owns this lifetime; audio workers only create
// streams while the subsystem is active.
// SDL requires subsystem startup and shutdown on the main thread.
Status InitializeSdlAudioSubsystem();
Status ShutdownSdlAudioSubsystem();
@@ -36,50 +20,44 @@ struct SdlPlaybackDevice {
std::string name;
};
// Lists physical SDL playback devices. The abstract `default` target is not a
// physical entry and is therefore intentionally absent from this vector.
// The virtual `default` target is not returned.
Status ListSdlPlaybackDevices(std::vector<SdlPlaybackDevice>* devices);
// Resolves `default` or one exact physical device name. This pure selection
// step is separate from SDL enumeration so configuration behavior is directly
// testable.
Status ResolveSdlPlaybackDevice(
std::string_view requested,
const std::vector<SdlPlaybackDevice>& available_devices,
std::uint32_t* playback_device_id, std::string* resolved_name);
// Sets the process playback target used by every subsequently created FMOD
// Java and OpenSL sink. It must run after SDL audio initialization and before
// the first sink is opened.
// Must run after SDL audio initialization and before the first sink opens.
Status ConfigureSdlPlaybackDevice(
std::string_view requested,
std::vector<SdlPlaybackDevice>* available_devices,
std::string* resolved_name);
// Returns the process playback target currently used by new sinks. The
// physical ID is zero for SDL's system-default route.
// Device ID zero means the system default.
Status GetConfiguredSdlPlaybackDevice(std::uint32_t* playback_device_id,
std::string* resolved_name);
// Atomically migrates every live FMOD Java and OpenSL stream to one physical
// SDL device, or to the system default when playback_device_id is zero. New
// sinks inherit the same target. Existing queued PCM stays in its stream.
// Migrates live streams without dropping queued PCM. New sinks inherit it.
Status SwitchSdlPlaybackDevice(std::uint32_t playback_device_id,
std::string* resolved_name);
struct SdlAudioSinkOptions {
PcmSpec source_spec;
// Zero selects the process playback target configured above (the SDL
// default until explicitly configured). A nonzero ID bypasses that target.
// 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;
};
// Opens a real SDL 3.4+ playback device and its bound SDL_AudioStream. The SDL
// audio subsystem must already be owned by InitializeSdlAudioSubsystem(). SDL
// owns all format conversion, resampling, and borrowed-buffer consumption.
// On failure, *sink remains null.
// SDL owns conversion, resampling, and borrowed-buffer consumption.
Status CreateSdlAudioSink(const SdlAudioSinkOptions& options,
std::unique_ptr<AudioSink>* sink);
@@ -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_
@@ -26,60 +12,28 @@
namespace mocktail {
namespace graphics {
// Vulkan VkResult values are a stable signed 32-bit ABI. Keeping the Android
// boundary independent of the optional Vulkan SDK lets capability-only builds
// continue to compile with SDL's opaque Vulkan handles.
// Stable signed 32-bit ABI, independent of the optional Vulkan SDK.
using AndroidVulkanResult = VkResult;
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";
// Desktop compositors can report a valid acquired/presented image as
// suboptimal while a fullscreen configure settles. The Android client path
// used by Roblox treats that positive success code as a hard error, so the
// compatibility boundary exposes it as the equivalent VK_SUCCESS result.
// 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);
// Android requests an infinite image-acquire wait, but a lost Wayland/X11
// compositor wakeup would then block Roblox's RenderJob forever. Only the
// infinite guest request is bounded; explicit finite Vulkan timeouts retain
// their original contract.
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 fence/timeline waits retain their guest-visible semantics, but are
// issued to the host in finite slices so a stalled renderer produces evidence
// while it remains blocked. VK_TIMEOUT from a slice is never returned to the
// Android client as a completed wait.
std::uint64_t BoundHostSynchronizationWaitTimeout(
std::uint64_t requested_timeout);
bool ShouldContinueHostSynchronizationWait(std::uint64_t requested_timeout,
VkResult host_result);
// Rewrites an Android VkInstance extension request for the host WSI. The
// returned storage owns its strings and can be used to build the
// ppEnabledExtensionNames array passed to the host vkCreateInstance call.
// Returned storage owns the translated extension names.
Status TranslateAndroidVulkanInstanceExtensions(
const std::vector<std::string>& android_extensions,
const std::vector<std::string>& host_wsi_extensions,
std::vector<std::string>* host_extensions);
// Android WSI boundary. vkCreateAndroidSurfaceKHR-compatible exports can
// delegate here after vkCreateInstance has used the translated extension list.
// The Android native-window member is intentionally ignored: SDL owns the real
// host window and is the only component allowed to create its VkSurfaceKHR.
// Ignores the guest native-window field; SDL alone creates the host surface.
class AndroidVulkanWsiAdapter final {
public:
using PresentCallback = void (*)(void* user_data);
-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_
+3 -25
View File
@@ -1,25 +1,5 @@
// 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.
// Exact-path loader for Mocktail's Bionic libEGL adapter.
//
// A host EGL implementation may have the Android SONAME "libEGL.so" too
// (Chromium ANGLE does). Loading by SONAME after SDL/ANGLE starts can then
// return the host implementation and make Android code create a second EGL
// surface for one native window. This adapter opens the Mocktail
// bridge beside the executable and exposes only its EGL exports for the
// synthetic Bionic libEGL.so dependency.
// Loading by SONAME can select host ANGLE instead of the Bionic adapter. This
// bridge therefore opens the library beside the executable by exact path.
#ifndef MOCKTAIL_GRAPHICS_BIONIC_EGL_BRIDGE_H_
#define MOCKTAIL_GRAPHICS_BIONIC_EGL_BRIDGE_H_
@@ -37,9 +17,7 @@ public:
BionicEglBridge(const BionicEglBridge &) = delete;
BionicEglBridge &operator=(const BionicEglBridge &) = delete;
// Loads $ORIGIN/libEGL.so by its exact absolute path. On success, the
// handle intentionally remains live for the process lifetime because the
// Bionic linker retains the exported function addresses.
// The handle stays live because the Bionic linker retains its exports.
bool Load();
bool IsLoaded() const { return handle_ != nullptr; }
@@ -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
+2 -17
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_SDL_VULKAN_WSI_H_
#define MOCKTAIL_GRAPHICS_SDL_VULKAN_WSI_H_
@@ -30,9 +16,8 @@
namespace mocktail {
namespace graphics {
// Thin host WSI scaffold. It does not create a VkInstance, device, swapchain,
// renderer, or allocator. A future Android Vulkan adapter may translate
// vkCreateAndroidSurfaceKHR into CreateSurface after rewriting extensions.
// Provides SDL's host WSI entry points. Instance, device, swapchain, renderer,
// and allocator ownership stays with the caller.
class SdlVulkanWsi final {
public:
SdlVulkanWsi() = default;
+4 -24
View File
@@ -1,36 +1,18 @@
// 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>
// Stable process-local ABI between the host input runtime in the executable
// and libvulkan.so, which is loaded as a separate Android compatibility DSO.
// Pixels are tightly packed RGBA8. The producer owns and securely clears its
// storage; consumers must copy a matching revision before returning.
// Process-local ABI with libvulkan.so. Pixels are RGBA8; consumers must copy a
// matching revision before the producer clears its storage.
struct MocktailTextOverlayFrameInfo {
static constexpr std::uint32_t kAbiVersion = 2;
std::uint32_t abi_version = kAbiVersion;
std::uint32_t visible = 0;
std::uint64_t revision = 0;
// x/y/width/height use the host's logical input coordinate space. These
// dimensions let a Vulkan consumer map that space to the swapchain's pixel
// extent without assuming a 1:1 high-DPI scale.
// Coordinates are logical, not swapchain pixels.
std::uint32_t coordinate_width = 0;
std::uint32_t coordinate_height = 0;
std::int32_t x = 0;
@@ -43,9 +25,7 @@ struct MocktailTextOverlayFrameInfo {
extern "C" {
// Lock-free conservative hint for the Vulkan present fast path. A false
// result guarantees that no drawable overlay is currently published. A true
// result only means that the synchronized query/copy path must decide.
// false guarantees no overlay; true still requires a synchronized query.
bool mocktail_text_overlay_may_present();
bool mocktail_text_overlay_query(MocktailTextOverlayFrameInfo* frame);
bool mocktail_text_overlay_copy(std::uint64_t revision, void* rgba,
-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_
@@ -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_
@@ -22,13 +8,8 @@
namespace mocktail {
namespace graphics {
// Composites the process-local RGBA text surface into application-owned
// swapchain images. The adapter retains ownership of every Vulkan object it
// registers. DestroySwapchain and DestroyDevice must be called before the
// corresponding host Vulkan destroy function.
//
// An unsupported device or swapchain remains a valid registration failure:
// QueuePresent then forwards the unmodified VkPresentInfoKHR to fallback.
// Registered Vulkan objects remain caller-owned and must be unregistered
// before host destruction. Unsupported registrations fall back cleanly.
class VulkanTextOverlayCompositor final {
public:
VulkanTextOverlayCompositor();
@@ -38,10 +19,8 @@ class VulkanTextOverlayCompositor final {
VulkanTextOverlayCompositor& operator=(const VulkanTextOverlayCompositor&) =
delete;
// enabled_features describes the features actually enabled by
// vkCreateDevice, not merely the features supported by the physical device.
// The supplied proc-address callbacks must bypass the Android-facing Vulkan
// adapter and resolve host functions directly.
// enabled_features must match vkCreateDevice. Proc callbacks resolve host
// functions directly, bypassing the Android adapter.
bool RegisterDevice(VkInstance instance, VkPhysicalDevice physical_device,
VkDevice device, std::uint32_t api_version,
const char* const* enabled_extensions,
@@ -61,10 +40,7 @@ class VulkanTextOverlayCompositor final {
const VkImage* images, std::uint32_t image_count);
void DestroySwapchain(VkDevice device, VkSwapchainKHR swapchain);
// Serializes adapter-mediated submit, sparse-bind, present, and idle
// operations on the exact queue range imported by libplacebo. Other
// VkQueues remain independent, including the inactive-overlay fast path on
// a non-imported queue.
// Adapter operations serialize only queues imported by libplacebo.
VkResult QueueSubmit(VkQueue queue, std::uint32_t submit_count,
const VkSubmitInfo* submits, VkFence fence,
PFN_vkQueueSubmit fallback);
@@ -77,10 +53,7 @@ class VulkanTextOverlayCompositor final {
VkResult QueueWaitIdle(VkQueue queue, PFN_vkQueueWaitIdle fallback);
VkResult DeviceWaitIdle(VkDevice device, PFN_vkDeviceWaitIdle fallback);
// Always invokes fallback when it is non-null. The original wait semaphores
// are forwarded unchanged until the compositor successfully submits the
// wait-collapse bridge; after that point only compositor-owned semaphores
// are passed to fallback.
// After wait collapse, fallback receives only compositor-owned semaphores.
VkResult QueuePresent(VkQueue queue, const VkPresentInfoKHR* present_info,
PFN_vkQueuePresentKHR fallback);
@@ -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_
+78 -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 {
@@ -142,9 +187,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,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_APPLICATION_METADATA_H_
#define MOCKTAIL_PLATFORM_SDL_APPLICATION_METADATA_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_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_
+13 -37
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>
@@ -40,9 +27,7 @@ enum class AuthRuntimeStatus {
kUnavailable,
};
// Process-local ownership for the canonical .ROBLOSECURITY header. The class
// is move-only and clears its backing allocation before release.
// Callers must never log the value returned by view() or c_str().
// Move-only credential storage that clears its allocation. Never log its view.
class SecureRobloxCredential final {
public:
SecureRobloxCredential() = default;
@@ -64,14 +49,10 @@ class SecureRobloxCredential final {
std::vector<char> bytes_;
};
// Clears a transient string that contained derived credential material.
void SecurelyClearString(std::string* value);
// Binds the production pseudo-VM to the exact credential resolved by the
// authentication preflight. The binding is intentionally non-movable:
// it must be created only after its credential reaches its final address and
// destroyed before that credential or the VM. Guest credentials block legacy
// env/disk fallback until the sink durably accepts a native sign-in value.
// Must be created after the credential reaches its final address and destroyed
// before the credential or VM. Guest bindings block legacy env/disk fallback.
class ScopedRobloxCredentialBinding final {
public:
ScopedRobloxCredentialBinding(jnivm::VM* jni_vm,
@@ -93,40 +74,35 @@ class ScopedRobloxCredentialBinding final {
jnivm::VM* jni_vm_ = nullptr;
};
// Result of resolving host credentials before the Android runtime starts.
// Credential ownership is retained only so the exact credential validated by
// AuthService can be moved into native startup without reopening its source.
// Retains the validated credential without reopening its source.
struct AuthRuntimeComposition {
AuthRuntimeStatus status = AuthRuntimeStatus::kUnavailable;
std::shared_ptr<jnivm::VM> jni_vm;
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; }
};
// Resolves the saved Roblox session and constructs the production pseudo-VM.
// A VM is returned only for an authenticated session or when guest startup is
// explicitly enabled with MOCKTAIL_ALLOW_NO_COOKIE_LUA_APP. An exact HTTP 401
// from a managed Mocktail file clears only the rejected .ROBLOSECURITY. A
// rejected default Sober credential is suppressed by a private fingerprint
// without modifying Sober's file. The guest policy then allows native sign-in
// to continue. Explicit overrides, unsafe recovery, other invalid credentials,
// and unavailable authentication still fail closed before native startup.
// 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);
// Production overload retaining the HTTP transport for credentials delivered
// by native sign-in after startup. A persisted credential is validated before
// its account identity is promoted into the running pseudo-VM.
// Retains HTTP transport for native sign-in and validates persisted credentials.
AuthRuntimeComposition ComposeAuthRuntime(
const Environment& environment, const RuntimePaths& paths,
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
+3 -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,9 @@ 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;
};
struct CommandLineParseResult {
+2 -23
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_
@@ -21,19 +7,12 @@
namespace mocktail {
namespace runtime {
// Applies Mocktail's mandatory privacy boundary to the flat Roblox
// client-settings override object. The policy disables native Crashpad, Java
// crash/ANR uploads, Lua Backtrace errors, and inferred-crash submission while
// preserving unrelated caller overrides. Unsafe values are replaced rather
// than treated as conflicts because crash upload is not a user-configurable
// runtime capability.
// Disables Roblox crash uploads while preserving unrelated overrides.
bool MergeCrashReportClientSettingsOverrides(std::string_view base_json,
std::string* merged_json,
std::string* error);
// Applies the same mandatory values to Roblox's separate AppBridge
// fastFlagsJson channel. Only FastVariable names are inserted here;
// Java-only client settings remain on the client-settings boundary above.
// AppBridge accepts only the FastVariable subset of this policy.
bool MergeCrashReportFastFlagsOverrides(std::string_view base_json,
std::string* merged_json,
std::string* error);
+4 -24
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_DEVICE_PROFILE_H_
#define MOCKTAIL_RUNTIME_DEVICE_PROFILE_H_
@@ -28,9 +14,7 @@ enum class DeviceClass {
kConsole,
};
// One immutable profile keeps Roblox admission, pseudo-Android hardware
// identity, and built-in input capabilities consistent. The guest ABI and
// client-settings group remain Android/GoogleAndroidApp.
// Admission changes do not change the Android guest ABI.
struct DeviceProfile {
std::string name;
std::string cache_key;
@@ -51,18 +35,14 @@ struct DeviceProfile {
};
inline constexpr std::string_view kDefaultDeviceProfileName = "pc-windows-11";
// These identifiers select Roblox server-side admission only. They never
// replace the Android APK ABI or its GoogleAndroidApp client-settings group.
// Transport classifier only; the guest remains GoogleAndroidApp.
inline constexpr std::string_view kRobloxDesktopHttpUserAgent =
"Roblox/WinInet";
// The current Android-host playability endpoint recognizes this as console.
// PlayStation-labelled user agents currently fall back to desktop admission,
// so the PS5 marketing profile keeps this transport-only classifier.
// The playability endpoint recognizes this classifier as console.
inline constexpr std::string_view kRobloxConsoleAdmissionUserAgent =
"Roblox/XboxOne";
// Accepts canonical names and the convenient pc/mobile/console aliases.
// Returned profiles have static lifetime.
// Accepts canonical names and pc/mobile/console aliases; result is static.
const DeviceProfile* FindDeviceProfile(std::string_view name);
bool IsValidDeviceProfileValue(std::string_view value, std::size_t maximum);
std::string BuildCustomDeviceProfileCacheKey(const DeviceProfile& profile);
+58
View File
@@ -0,0 +1,58 @@
#ifndef MOCKTAIL_RUNTIME_DISCORD_RPC_H_
#define MOCKTAIL_RUNTIME_DISCORD_RPC_H_
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include "runtime/roblox_experience_presence.h"
#include "runtime/runtime_config.h"
namespace mocktail {
namespace runtime {
struct DiscordRpcActivity {
std::string details;
std::string state;
std::optional<int64_t> start_timestamp;
std::string large_image;
std::string large_text;
std::string button_label;
std::string button_url;
};
bool IsPublicDiscordJoin(const RobloxExperienceLaunchRequest& request);
std::string BuildDiscordJoinUrl(const RobloxExperienceLaunchRequest& request);
DiscordRpcActivity BuildDiscordRpcActivity(
const DiscordRpcConfig& config, RobloxExperiencePresencePhase phase,
const RobloxExperienceLaunchRequest* request, std::string place_name,
int64_t session_started_at, std::string place_icon_url = {});
// Maintains a direct, unauthenticated Discord Desktop RPC connection. The
// worker owns all network and IPC work; lifecycle notifications only replace
// a small in-memory snapshot and never block the Roblox main thread.
class DiscordRpcSession final {
public:
explicit DiscordRpcSession(DiscordRpcConfig config);
~DiscordRpcSession();
DiscordRpcSession(const DiscordRpcSession&) = delete;
DiscordRpcSession& operator=(const DiscordRpcSession&) = delete;
bool Start(std::string* detail = nullptr);
void Stop();
RobloxExperiencePresenceObserver observer();
private:
class Impl;
static void Notify(void* context, RobloxExperiencePresencePhase phase,
const RobloxExperienceLaunchRequest* request);
std::unique_ptr<Impl> impl_;
};
} // namespace runtime
} // namespace mocktail
#endif // MOCKTAIL_RUNTIME_DISCORD_RPC_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_RUNTIME_ENVIRONMENT_H_
#define MOCKTAIL_RUNTIME_ENVIRONMENT_H_
+9 -39
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_EXTERNAL_LAUNCH_BROKER_H_
#define MOCKTAIL_RUNTIME_EXTERNAL_LAUNCH_BROKER_H_
@@ -28,14 +14,10 @@ namespace runtime {
inline constexpr std::size_t kMaximumPendingExternalLaunches = 8;
struct ExternalLaunchBrokerOptions {
// Empty selects the current user's protected runtime endpoint. Tests and
// isolated launchers may provide an explicit socket under a private
// directory owned by the current user.
// Empty selects the current user's protected endpoint.
std::filesystem::path socket_path;
std::size_t maximum_pending_launches = kMaximumPendingExternalLaunches;
// The owner crosses configuration/bootstrap and a possible cgroup
// re-exec before it can safely ACK. A concurrent browser click waits through
// that bounded startup window instead of racing a not-yet-created socket.
// Covers bootstrap and a possible cgroup re-exec.
int forwarding_timeout_ms = 30000;
};
@@ -49,16 +31,11 @@ struct ExternalLaunchSink {
bool valid() const { return dispatch != nullptr; }
};
// Resolves the per-user, per-installation endpoint. Owner resolution may
// create only the private fallback directory; it never removes a stale
// socket. Socket cleanup belongs exclusively to StartOwnerAfterLockAcquired
// after the caller owns the matching Mocktail single-instance lock.
// Resolution never removes stale sockets; only a lock-owning broker may do so.
Status ResolveExternalLaunchSocketPath(bool owner,
std::filesystem::path* socket_path);
// Owns a bounded process-local launch queue and its same-user AF_UNIX endpoint.
// Wire requests contain only already-normalized ExperienceProtocol JSON; the
// raw browser URI and its gameinfo ticket never cross this boundary.
// The wire format contains normalized requests, never raw browser tickets.
class ExternalLaunchBroker final {
public:
~ExternalLaunchBroker();
@@ -68,25 +45,20 @@ class ExternalLaunchBroker final {
ExternalLaunchBroker(ExternalLaunchBroker&&) = delete;
ExternalLaunchBroker& operator=(ExternalLaunchBroker&&) = delete;
// The caller must hold the single-instance lock. Only this operation may
// remove a stale socket left by a crashed owner. An optional initial request
// is normalized and queued before the listener worker can accept later
// browser clicks, preserving click order without a startup race.
// Requires the single-instance lock. The initial request is queued before
// the listener starts, preserving click order.
static Status StartOwnerAfterLockAcquired(
ExternalLaunchBrokerOptions options,
std::shared_ptr<ExternalLaunchBroker>* broker,
const RobloxExperienceLaunchRequest* initial_request = nullptr);
// Forwards one normalized request and waits for an explicit owner ACK. The
// operation never starts a second runtime and never logs request contents.
// Waits for an owner ACK and never logs the request.
static Status ForwardToOwner(const ExternalLaunchBrokerOptions& options,
const RobloxExperienceLaunchRequest& request);
Status QueueInitialRequest(RobloxExperienceLaunchRequest request);
// Dispatches at most maximum_requests in FIFO order. A failed dispatch is
// returned to the front of the queue, so temporary downstream backpressure
// never drops a browser launch.
// A failed dispatch returns to the front of the FIFO queue.
Status Drain(const ExternalLaunchSink& sink, std::size_t maximum_requests);
Status Shutdown();
@@ -100,9 +72,7 @@ class ExternalLaunchBroker final {
std::unique_ptr<Impl> impl_;
};
// Process-global attachment used by the composition root and the dynamic
// ExperienceProtocol runtime. shared_ptr acquisition keeps the broker alive
// while an external request is being drained.
// shared_ptr acquisition keeps the active broker alive during a drain.
Status InstallActiveExternalLaunchBroker(
const std::shared_ptr<ExternalLaunchBroker>& broker);
void ClearActiveExternalLaunchBroker(const ExternalLaunchBroker* broker);
+5 -22
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_FAILURE_DIALOG_H_
#define MOCKTAIL_RUNTIME_FAILURE_DIALOG_H_
@@ -22,19 +8,16 @@
namespace mocktail {
namespace runtime {
// Returns whether an interactive launch may present a desktop failure dialog.
// Readiness canaries and headless sessions remain strictly non-interactive.
// Canaries and headless sessions remain non-interactive.
bool FailureDialogsEnabled(const Environment& environment);
// Presents one synchronous GTK failure dialog. This is intended for startup
// failures that happen before the crash monitor can be armed.
bool ShowFailureDialog(const Environment& environment,
std::string_view message);
bool ShowWarningDialog(const Environment& environment,
std::string_view message);
// Owns a small out-of-process GTK monitor. The helper is started before guest
// threads exist and receives an explicit success disposition. If Mocktail
// returns an error, calls _Exit(), or dies from a fatal signal, the helper
// remains able to present the last safe user-facing message.
// The helper starts before guest threads, so it can report errors and fatal
// exits even when the main process can no longer show a dialog.
class FailureDialogMonitor final {
public:
FailureDialogMonitor() = default;
+7 -16
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_FRAME_RATE_POLICY_H_
#define MOCKTAIL_RUNTIME_FRAME_RATE_POLICY_H_
@@ -24,6 +10,7 @@ namespace runtime {
inline constexpr int kMaximumSupportedRobloxSchedulerFps = 240;
enum class FrameRateLimitMode {
kUnmanaged,
kDisplay,
kFixed,
kUnlimited,
@@ -31,7 +18,7 @@ enum class FrameRateLimitMode {
};
struct FrameRatePolicy {
FrameRateLimitMode mode = FrameRateLimitMode::kDisplay;
FrameRateLimitMode mode = FrameRateLimitMode::kUnmanaged;
int fixed_fps = 0;
bool valid() const { return mode != FrameRateLimitMode::kInvalid; }
@@ -39,7 +26,11 @@ struct FrameRatePolicy {
FrameRatePolicy ParseFrameRatePolicy(std::string_view value);
// Merges the scheduler policy into Roblox's supported client-settings ingress.
// Enables Roblox's Basic Settings frame-rate control and, when explicitly
// requested, merges a Mocktail scheduler policy into client settings.
// Unmanaged (-1) does not add a scheduler target, so Roblox owns the cap.
// Every positive fixed value is forwarded verbatim to
// DFIntTaskSchedulerTargetFps; it is not restricted to common refresh rates.
// Unlimited selects the unmodified payload's maximum scheduler target (240).
// With VSync auto/off, graphics policy separately requests an unthrottled
// Vulkan present mode when the host exposes one.
-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_GAME_MODE_H_
#define MOCKTAIL_RUNTIME_GAME_MODE_H_
Loaded 100 of 516 files, more files were not shown because too many files have changed in this diff. Show more