From 5d636749fa1c8a8f0965ce453c6281fea3339055 Mon Sep 17 00:00:00 2001 From: komaruworld Date: Wed, 12 Aug 2026 20:02:54 +0300 Subject: [PATCH] rewrite some text --- Makefile | 2 +- README.md | 8 +- include/compat/bionic_atfork_runtime.h | 14 +- include/compat/bionic_host_libc_runtime.h | 33 +- .../compat/bionic_pthread_create_runtime.h | 17 +- include/compat/bionic_pthread_key_runtime.h | 5 +- include/compat/host_abi_experiment.h | 2 +- include/compat/host_abi_profile.h | 2 +- include/compat/host_abi_profile_loader.h | 23 +- include/jnivm/jnivm.h | 113 +----- include/legacy/legacy_runtime.h | 2 +- include/libc_shim/libc_shim.h | 37 +- include/linker/linker.h | 72 +--- include/mocktail/audio/audio_sink.h | 23 +- .../mocktail/audio/fmod_java_audio_runtime.h | 29 +- include/mocktail/audio/sdl_audio_sink.h | 29 +- .../graphics/android_vulkan_wsi_adapter.h | 28 +- include/mocktail/graphics/bionic_egl_bridge.h | 14 +- include/mocktail/graphics/sdl_vulkan_wsi.h | 5 +- .../mocktail/graphics/text_overlay_frame.h | 14 +- .../graphics/vulkan_text_overlay_compositor.h | 25 +- include/runtime/auth_runtime_composition.h | 31 +- include/runtime/crash_report_policy.h | 11 +- include/runtime/device_profile.h | 14 +- include/runtime/external_launch_broker.h | 34 +- include/runtime/failure_dialog.h | 11 +- include/runtime/game_session_coordinator.h | 21 +- .../runtime/jnivm_platform_web_callbacks.h | 3 +- include/runtime/performance_policy.h | 18 +- .../runtime/roblox_browser_service_bridge.h | 9 +- include/runtime/roblox_launch_uri.h | 3 +- .../roblox_native_text_box_info_reader.h | 3 +- include/runtime/runtime_config.h | 2 +- include/update/host_abi_deriver.h | 2 +- packaging/space.bigrat.mocktail.metainfo.xml | 29 +- scripts/apk_providers/direct_apkpure.py | 2 +- scripts/apk_providers/direct_uptodown.py | 4 +- scripts/assemble_flatpak_pages.sh | 4 +- scripts/build.sh | 48 --- scripts/derive_roblox_host_abi_profile.py | 17 +- scripts/real_bringup_smoke.sh | 4 +- src/compat/host_abi_experiment.cc | 4 +- src/compat/payload_compatibility.cc | 2 +- src/graphics/graphics_backend.cc | 1 - src/jnivm/jnivm.cc | 30 +- src/legacy/legacy_runtime.cc | 180 +++------- src/libc_shim/libc_shim.cc | 9 +- src/linker/linker.cc | 34 +- src/main.cc | 23 +- src/runtime/command_line.cc | 4 +- src/runtime/discord_rpc.cc | 11 +- src/runtime/roblox_experience_composition.cc | 41 +-- src/runtime/roblox_text_input_jni_bridge.h | 17 +- src/window/window.cc | 64 +--- src/window/window.h | 94 ++--- stubs/empty.cc | 5 +- stubs/libandroid_stub.cc | 49 ++- stubs/libegl_stub.cc | 14 +- stubs/libgles_stub.cc | 192 +++++----- stubs/liblog_stub.cc | 5 +- stubs/libmediandk_stub.cc | 82 ++--- stubs/libopensl_stub.cc | 336 +++++++++--------- stubs/libvulkan_stub.cc | 30 +- tests/audio_foundation_test.cc | 4 +- tests/auto_update_orchestration_test.sh | 5 +- tests/fixtures/unknown_roblox_fixture.cc | 2 +- tests/fixtures/upstream_jni_fixture.cc | 4 +- tests/fmod_java_audio_runtime_test.cc | 2 +- tests/jnivm_test.cc | 41 +-- tests/libc_shim_test.cc | 26 +- tests/opensl_playback_runtime_test.cc | 2 +- ...roblox_native_text_box_info_reader_test.cc | 2 +- tests/roblox_text_input_jni_bridge_test.cc | 2 +- tests/startup_defaults_test.sh | 8 +- 74 files changed, 672 insertions(+), 1420 deletions(-) diff --git a/Makefile b/Makefile index c90090e..f86814e 100644 --- a/Makefile +++ b/Makefile @@ -134,7 +134,7 @@ 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 diff --git a/README.md b/README.md index 57d6dbb..e87fda0 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,11 @@ [![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE) Mocktail runs the Android `x86_64` Roblox client on Linux. It provides the -Android ABI and JNI pieces the client expects, then uses SDL3 and Vulkan or -OpenGL for the Linux side. +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 diff --git a/include/compat/bionic_atfork_runtime.h b/include/compat/bionic_atfork_runtime.h index 784975b..45626c3 100644 --- a/include/compat/bionic_atfork_runtime.h +++ b/include/compat/bionic_atfork_runtime.h @@ -19,16 +19,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 +29,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, diff --git a/include/compat/bionic_host_libc_runtime.h b/include/compat/bionic_host_libc_runtime.h index 360b8fc..79b46a7 100644 --- a/include/compat/bionic_host_libc_runtime.h +++ b/include/compat/bionic_host_libc_runtime.h @@ -22,11 +22,8 @@ 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 +41,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 +58,24 @@ 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. +// 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( diff --git a/include/compat/bionic_pthread_create_runtime.h b/include/compat/bionic_pthread_create_runtime.h index bddbe08..ef38bb8 100644 --- a/include/compat/bionic_pthread_create_runtime.h +++ b/include/compat/bionic_pthread_create_runtime.h @@ -23,30 +23,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*), diff --git a/include/compat/bionic_pthread_key_runtime.h b/include/compat/bionic_pthread_key_runtime.h index b471711..53d8bfc 100644 --- a/include/compat/bionic_pthread_key_runtime.h +++ b/include/compat/bionic_pthread_key_runtime.h @@ -31,9 +31,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; diff --git a/include/compat/host_abi_experiment.h b/include/compat/host_abi_experiment.h index db371ac..a4ff254 100644 --- a/include/compat/host_abi_experiment.h +++ b/include/compat/host_abi_experiment.h @@ -62,7 +62,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 diff --git a/include/compat/host_abi_profile.h b/include/compat/host_abi_profile.h index 69db78a..81624f9 100644 --- a/include/compat/host_abi_profile.h +++ b/include/compat/host_abi_profile.h @@ -81,7 +81,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 { diff --git a/include/compat/host_abi_profile_loader.h b/include/compat/host_abi_profile_loader.h index 92bc097..23f7540 100644 --- a/include/compat/host_abi_profile_loader.h +++ b/include/compat/host_abi_profile_loader.h @@ -28,11 +28,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,27 +52,19 @@ 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; diff --git a/include/jnivm/jnivm.h b/include/jnivm/jnivm.h index 225b00d..550b31f 100644 --- a/include/jnivm/jnivm.h +++ b/include/jnivm/jnivm.h @@ -12,20 +12,6 @@ // 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(); -// 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_ @@ -42,29 +28,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 klass) : klass_(std::move(klass)) {} virtual ~Object() = default; - // Returns the pseudo-class descriptor of this object. std::shared_ptr GetClass() const { return klass_; } private: std::shared_ptr klass_; }; -// MethodCallback — signature for registered JNI method stubs. -// The callback receives the raw JNIEnv pointer and the receiver jobject. - using MethodCallback = std::function; // Resolved Roblox account identity supplied by the host runtime. This @@ -75,8 +52,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; @@ -99,23 +74,16 @@ struct RobloxCredentialView { using RobloxCredentialProvider = RobloxCredentialView (*)(const void *context); -// 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 +94,12 @@ 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. +// 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 +124,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 +134,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 +144,14 @@ 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. +// 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 +166,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 &GetMethods() const { return methods_; } @@ -247,57 +189,37 @@ public: private: std::string name_; - // Key format: "methodName:descriptor" std::unordered_map 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 RegisterClass(const std::string &class_name); - // Finds a previously registered class. // Returns nullptr if the class has not been registered. std::shared_ptr 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 // 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. 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 @@ -405,12 +327,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 +339,6 @@ private: JNIEnv jni_env_storage_ = {}; JNIEnv *jni_env_ = nullptr; - // Registry of pseudo-classes indexed by their JNI descriptor. std::unordered_map> class_registry_; mutable std::mutex roblox_auth_identity_mutex_; @@ -498,8 +417,6 @@ private: std::shared_ptr roblox_experience_lifecycle_binding_; - // Initialises the JNIInvokeInterface_ and JNINativeInterface_ function tables - // with stub implementations that forward calls to this VM instance. void InitJNIFunctionTables(); }; diff --git a/include/legacy/legacy_runtime.h b/include/legacy/legacy_runtime.h index 55b1cd8..ff9ac7b 100644 --- a/include/legacy/legacy_runtime.h +++ b/include/legacy/legacy_runtime.h @@ -86,7 +86,7 @@ class RuntimeDependencies final { 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); diff --git a/include/libc_shim/libc_shim.h b/include/libc_shim/libc_shim.h index adc26b6..9980601 100644 --- a/include/libc_shim/libc_shim.h +++ b/include/libc_shim/libc_shim.h @@ -12,17 +12,6 @@ // 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_ @@ -57,10 +46,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 +63,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 diff --git a/include/linker/linker.h b/include/linker/linker.h index d5a2380..0a7a489 100644 --- a/include/linker/linker.h +++ b/include/linker/linker.h @@ -12,18 +12,6 @@ // 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 +22,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; using AndroidLibraryLoadObserver = bool (*)(void* context, @@ -50,59 +36,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 +66,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 +80,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 +96,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& GetSymbolTable(); } // namespace linker diff --git a/include/mocktail/audio/audio_sink.h b/include/mocktail/audio/audio_sink.h index 0a911e5..5c2c212 100644 --- a/include/mocktail/audio/audio_sink.h +++ b/include/mocktail/audio/audio_sink.h @@ -22,9 +22,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 +39,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 +53,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 +62,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; }; diff --git a/include/mocktail/audio/fmod_java_audio_runtime.h b/include/mocktail/audio/fmod_java_audio_runtime.h index adcf887..7b0723a 100644 --- a/include/mocktail/audio/fmod_java_audio_runtime.h +++ b/include/mocktail/audio/fmod_java_audio_runtime.h @@ -23,14 +23,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* sink); @@ -44,8 +40,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 +60,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 +80,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: diff --git a/include/mocktail/audio/sdl_audio_sink.h b/include/mocktail/audio/sdl_audio_sink.h index a54b8e6..fddf091 100644 --- a/include/mocktail/audio/sdl_audio_sink.h +++ b/include/mocktail/audio/sdl_audio_sink.h @@ -25,9 +25,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 +34,37 @@ 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* 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& 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* 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; }; -// 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* sink); diff --git a/include/mocktail/graphics/android_vulkan_wsi_adapter.h b/include/mocktail/graphics/android_vulkan_wsi_adapter.h index e42276c..1e41e8e 100644 --- a/include/mocktail/graphics/android_vulkan_wsi_adapter.h +++ b/include/mocktail/graphics/android_vulkan_wsi_adapter.h @@ -26,9 +26,7 @@ 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 = @@ -43,43 +41,29 @@ inline constexpr std::uint64_t kHostImageAcquireWatchdogTimeoutNs = 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. +// Roblox treats VK_SUBOPTIMAL_KHR as a hard error, so expose it as success. 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. +// Bounds only infinite acquire waits so a lost compositor wakeup cannot hang. std::uint64_t BoundHostImageAcquireTimeout(std::uint64_t requested_timeout); bool IsHostImageAcquireWatchdogTimeout(std::uint64_t requested_timeout, VkResult host_result); VkResult NormalizeHostImageAcquireResult(std::uint64_t requested_timeout, VkResult host_result); -// Infinite 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. +// Infinite guest waits use finite host slices without exposing slice timeouts. std::uint64_t BoundHostSynchronizationWaitTimeout( std::uint64_t requested_timeout); bool ShouldContinueHostSynchronizationWait(std::uint64_t requested_timeout, VkResult host_result); -// 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& android_extensions, const std::vector& host_wsi_extensions, std::vector* 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); diff --git a/include/mocktail/graphics/bionic_egl_bridge.h b/include/mocktail/graphics/bionic_egl_bridge.h index 0c15050..6453876 100644 --- a/include/mocktail/graphics/bionic_egl_bridge.h +++ b/include/mocktail/graphics/bionic_egl_bridge.h @@ -12,14 +12,8 @@ // 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 +31,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; } diff --git a/include/mocktail/graphics/sdl_vulkan_wsi.h b/include/mocktail/graphics/sdl_vulkan_wsi.h index fc77902..36243a8 100644 --- a/include/mocktail/graphics/sdl_vulkan_wsi.h +++ b/include/mocktail/graphics/sdl_vulkan_wsi.h @@ -30,9 +30,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; diff --git a/include/mocktail/graphics/text_overlay_frame.h b/include/mocktail/graphics/text_overlay_frame.h index db96c72..2b70ed1 100644 --- a/include/mocktail/graphics/text_overlay_frame.h +++ b/include/mocktail/graphics/text_overlay_frame.h @@ -18,19 +18,15 @@ #include #include -// 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 +39,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, diff --git a/include/mocktail/graphics/vulkan_text_overlay_compositor.h b/include/mocktail/graphics/vulkan_text_overlay_compositor.h index c30717d..80eecb6 100644 --- a/include/mocktail/graphics/vulkan_text_overlay_compositor.h +++ b/include/mocktail/graphics/vulkan_text_overlay_compositor.h @@ -22,13 +22,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 +33,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 +54,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 +67,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); diff --git a/include/runtime/auth_runtime_composition.h b/include/runtime/auth_runtime_composition.h index 9a0b296..1edefea 100644 --- a/include/runtime/auth_runtime_composition.h +++ b/include/runtime/auth_runtime_composition.h @@ -40,9 +40,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 +62,10 @@ class SecureRobloxCredential final { std::vector 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,9 +87,7 @@ 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 jni_vm; @@ -107,21 +99,14 @@ struct AuthRuntimeComposition { 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 clears only a +// managed credential; rejected Sober credentials are suppressed without +// changing Sober's file. 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, diff --git a/include/runtime/crash_report_policy.h b/include/runtime/crash_report_policy.h index 799bb06..bb7ccbe 100644 --- a/include/runtime/crash_report_policy.h +++ b/include/runtime/crash_report_policy.h @@ -21,19 +21,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); diff --git a/include/runtime/device_profile.h b/include/runtime/device_profile.h index 7bec166..34ba2b9 100644 --- a/include/runtime/device_profile.h +++ b/include/runtime/device_profile.h @@ -28,9 +28,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 +49,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); diff --git a/include/runtime/external_launch_broker.h b/include/runtime/external_launch_broker.h index 6a3f2bb..b8f1693 100644 --- a/include/runtime/external_launch_broker.h +++ b/include/runtime/external_launch_broker.h @@ -28,14 +28,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 +45,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 +59,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* 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 +86,7 @@ class ExternalLaunchBroker final { std::unique_ptr 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& broker); void ClearActiveExternalLaunchBroker(const ExternalLaunchBroker* broker); diff --git a/include/runtime/failure_dialog.h b/include/runtime/failure_dialog.h index fb005cf..57eb737 100644 --- a/include/runtime/failure_dialog.h +++ b/include/runtime/failure_dialog.h @@ -22,19 +22,14 @@ 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); -// 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; diff --git a/include/runtime/game_session_coordinator.h b/include/runtime/game_session_coordinator.h index 789c441..f18a676 100644 --- a/include/runtime/game_session_coordinator.h +++ b/include/runtime/game_session_coordinator.h @@ -87,9 +87,9 @@ using ResumeGameFn = Status (*)(void* context, const GameSurface& surface); using LeaveGameFn = Status (*)(void* context); using ShutdownGameRuntimeFn = Status (*)(void* context); -// native boundary. All callbacks are synchronous and non-throwing. The -// coordinator serializes lifecycle invocation, matching MainGameActivity's -// single-thread executor without embedding JNI details in the policy layer. +// These callbacks form the native boundary. They are synchronous and +// non-throwing; the coordinator serializes lifecycle calls to match +// MainGameActivity's single-thread executor. struct GameSessionCapabilities { void* context = nullptr; SetGameForegroundFn set_game_foreground = nullptr; @@ -171,16 +171,11 @@ struct GameSessionSnapshot { std::string failure_reason; }; -// Fail-closed policy for the MainGameActivity game-session branch. -// -// SurfaceCreated must prepare a generation before SurfaceChanged can supply its -// native window and dimensions. QueueJoinRequest and the prepared surface are -// independent initial prerequisites; whichever completes last starts the game -// with the current candidate surface. Initial start never calls ResumeGame. -// Once running, SurfaceChanged updates the active surface, SurfaceDestroyed -// pauses, and SurfaceCreated(new generation) plus SurfaceChanged resumes. -// Candidate surfaces become active only after the corresponding native call -// succeeds. +// SurfaceCreated must open a generation before its matching SurfaceChanged can +// supply a window and size. A join request and the first surface may arrive in +// either order; initial startup calls StartGame, never ResumeGame. Once running, +// new generations pause and resume the session. A candidate surface becomes +// active only after its native callback succeeds. class GameSessionCoordinator final { public: explicit GameSessionCoordinator(GameSessionCapabilities capabilities, diff --git a/include/runtime/jnivm_platform_web_callbacks.h b/include/runtime/jnivm_platform_web_callbacks.h index 7f91f1a..b510120 100644 --- a/include/runtime/jnivm_platform_web_callbacks.h +++ b/include/runtime/jnivm_platform_web_callbacks.h @@ -22,8 +22,7 @@ namespace mocktail { namespace runtime { -// adapter kept outside the research runtime. The opaque context must be -// the production jnivm::VM owned by the startup composition. +// The opaque context must be the jnivm::VM owned by startup composition. bool SetJnivmPlatformWebCallbacks( void* context, std::shared_ptr callback_context, void (*on_data_model_notification)(void*, JNIEnv*, jstring, jstring), diff --git a/include/runtime/performance_policy.h b/include/runtime/performance_policy.h index 848a549..3dc7c17 100644 --- a/include/runtime/performance_policy.h +++ b/include/runtime/performance_policy.h @@ -34,8 +34,7 @@ enum class PhysicsWorkerMode { bool ParsePhysicsWorkerMode(std::string_view value, PhysicsWorkerMode* mode); std::string_view PhysicsWorkerModeName(PhysicsWorkerMode mode); -// Makes every available physical core eligible for Roblox scheduler work. -// At least one scheduler worker remains on malformed topology input. +// Malformed topology still yields one worker. int CalculateThroughputWorkerCount(int available_physical_cores); struct PerformancePolicy { @@ -61,25 +60,16 @@ PerformancePolicy ParsePerformancePolicy( std::string_view memory_limit_mb = "0", std::string_view game_mode = "auto", std::string_view physics_worker_mode = "auto"); -// Counts unique physical cores available to the current process. Linux CPU -// affinity is respected; systems without readable topology fall back to the -// available logical CPU count. +// Respects CPU affinity and falls back to the available logical count. int DetectAvailablePhysicalCoreCount(); -// Merges the supported Roblox scheduler/rendering mode into the flat -// client-settings override object. Disabled policy leaves existing overrides -// intact. Conflicting explicit values fail closed instead of silently -// replacing the caller's settings. +// Disabled policy preserves overrides; conflicting values fail. bool MergePerformanceClientSettingsOverrides(const PerformancePolicy& policy, std::string_view base_json, std::string* merged_json, std::string* error); -// Applies every runtime policy in a stable order before the resulting -// override object crosses the legacy startup boundary. The mandatory -// HttpClient compatibility policy is applied before the mandatory crash-report -// upload block. Neither policy can be overridden by a caller-provided client -// setting. +// Applies mandatory HTTP and crash policies after caller settings. bool MergeRuntimeClientSettingsOverrides(const FrameRatePolicy& frame_rate, const PerformancePolicy& performance, std::string_view base_json, diff --git a/include/runtime/roblox_browser_service_bridge.h b/include/runtime/roblox_browser_service_bridge.h index 572e0a8..111335f 100644 --- a/include/runtime/roblox_browser_service_bridge.h +++ b/include/runtime/roblox_browser_service_bridge.h @@ -143,11 +143,10 @@ struct RobloxBrowserServiceCallbackFactory { } }; -// equivalent of the current APK's yg.c and gi.a BrowserService bridges. -// It binds the four exact MemStorage keys and owns every Callback and -// Connection until a disconnect-and-release shutdown has completed. Outgoing -// WebView events are queued without JNI and must be drained by the platform -// main thread. +// Host counterpart to the APK's yg.c and gi.a BrowserService bridges. It binds +// the four expected MemStorage keys. Shutdown disconnects before releasing its +// callbacks and connections. The platform main thread drains outgoing WebView +// events without holding JNI references. class RobloxBrowserServiceBridge final { public: RobloxBrowserServiceBridge(JniEnvironmentProvider environment, diff --git a/include/runtime/roblox_launch_uri.h b/include/runtime/roblox_launch_uri.h index 23cef98..d6bb918 100644 --- a/include/runtime/roblox_launch_uri.h +++ b/include/runtime/roblox_launch_uri.h @@ -28,7 +28,8 @@ inline constexpr std::size_t kMaximumRobloxLaunchUriBytes = 64 * 1024; // Converts the two Roblox website launch protocols into the owned // ExperienceProtocol contract used by the supported game-session pipeline. -// Browser authentication tickets and tracker identifiers are // discarded: the runtime uses its already validated account identity. +// Authentication tickets and tracker IDs are discarded; the runtime already +// has a validated account identity. Status ParseRobloxLaunchUri(std::string_view uri, RobloxExperienceLaunchRequest* request); diff --git a/include/runtime/roblox_native_text_box_info_reader.h b/include/runtime/roblox_native_text_box_info_reader.h index 1931fcb..8f701db 100644 --- a/include/runtime/roblox_native_text_box_info_reader.h +++ b/include/runtime/roblox_native_text_box_info_reader.h @@ -22,8 +22,7 @@ namespace mocktail { namespace runtime { -// host copy of the current APK's NativeTextBoxInfo Java value. The copy -// contains no text and remains valid after the JNI local reference is released. +// Host-owned NativeTextBoxInfo copy that outlives the JNI local reference. struct RobloxNativeTextBoxInfoSnapshot { float x = 0.0F; float y = 0.0F; diff --git a/include/runtime/runtime_config.h b/include/runtime/runtime_config.h index 9acbc8a..672152f 100644 --- a/include/runtime/runtime_config.h +++ b/include/runtime/runtime_config.h @@ -78,7 +78,7 @@ std::optional ParseNetworkProxyConfig( std::string_view host, std::string_view port); std::string BuildNetworkProxyUrl(const NetworkProxyConfig& proxy); -// Immutable, supported runtime options. Experimental MOCKTAIL_PATCH_* knobs +// Immutable, supported runtime options. Advanced MOCKTAIL_PATCH_* controls // intentionally do not belong here; they remain isolated in the legacy path. class RuntimeConfig { public: diff --git a/include/update/host_abi_deriver.h b/include/update/host_abi_deriver.h index 3e081f4..bf9894a 100644 --- a/include/update/host_abi_deriver.h +++ b/include/update/host_abi_deriver.h @@ -37,7 +37,7 @@ struct HostAbiDerivationResult { } }; -// Derives an exact, experimental HostAbi profile by matching normalized x86-64 +// Derives an exact candidate HostAbi profile by matching normalized x86-64 // instruction signatures against one already approved reference payload. This // is static analysis only; the result still requires two real Tier C canaries // before normal runtime activation. diff --git a/packaging/space.bigrat.mocktail.metainfo.xml b/packaging/space.bigrat.mocktail.metainfo.xml index 806f493..21a00ba 100644 --- a/packaging/space.bigrat.mocktail.metainfo.xml +++ b/packaging/space.bigrat.mocktail.metainfo.xml @@ -4,7 +4,7 @@ space.bigrat.mocktail Mocktail - Experimental Roblox Android compatibility runtime for Linux + Play Roblox on Linux CC0-1.0 Apache-2.0 @@ -13,29 +13,30 @@ https://github.com/komaruworld/mocktail

- Mocktail is an experimental compatibility runtime for running the - Android x86-64 Roblox client on a native Linux desktop. It provides the - pseudo-JVM, Android ABI adapters, SDL window/input/audio integration and - Vulkan presentation needed by the client. + Sign in to Roblox, browse experiences, and play them in a desktop window. + Mocktail handles graphics, audio, mouse, and keyboard input on Linux.

- The project is a prototype and is not affiliated with or endorsed by - Roblox Corporation. The verified Roblox payload is downloaded on first - launch and is not distributed with Mocktail. + On first launch, Mocktail downloads the Roblox client and checks its + signature. It keeps the last working version in case an update fails. +

+

+ Mocktail is an independent community project. It is not affiliated with + or endorsed by Roblox Corporation, and the Roblox client is not included.

- https://raw.githubusercontent.com/komaruworld/mocktail/main/assets/screenshots/flatpak-home.png - Browse Roblox experiences in the desktop client + https://raw.githubusercontent.com/komaruworld/mocktail/88b7382577aa138a2d16619b091f69abcdf7da15/assets/screenshots/flatpak-home.png + Browse Roblox experiences - https://raw.githubusercontent.com/komaruworld/mocktail/main/assets/screenshots/flatpak-gameplay-tower.png - Roblox gameplay running through Mocktail + https://raw.githubusercontent.com/komaruworld/mocktail/88b7382577aa138a2d16619b091f69abcdf7da15/assets/screenshots/flatpak-gameplay-tower.png + Play Tower of Hell on Linux - https://raw.githubusercontent.com/komaruworld/mocktail/main/assets/screenshots/flatpak-gameplay-lobby.png - A Roblox experience running through Mocktail + https://raw.githubusercontent.com/komaruworld/mocktail/88b7382577aa138a2d16619b091f69abcdf7da15/assets/screenshots/flatpak-gameplay-lobby.png + Play DOORS on Linux space.bigrat.mocktail.desktop diff --git a/scripts/apk_providers/direct_apkpure.py b/scripts/apk_providers/direct_apkpure.py index f2f334b..88e1dda 100755 --- a/scripts/apk_providers/direct_apkpure.py +++ b/scripts/apk_providers/direct_apkpure.py @@ -59,7 +59,7 @@ VERSION_MARKER_PATTERN = re.compile( class ProviderError(RuntimeError): - """A safe, user-facing provider failure.""" + """Raised when a provider response cannot be downloaded or validated.""" def _has_version_record(metadata: bytes, version: str) -> bool: diff --git a/scripts/apk_providers/direct_uptodown.py b/scripts/apk_providers/direct_uptodown.py index 6206156..b335c78 100755 --- a/scripts/apk_providers/direct_uptodown.py +++ b/scripts/apk_providers/direct_uptodown.py @@ -7,7 +7,7 @@ # # http://www.apache.org/licenses/LICENSE-2.0 -"""Download a provenance-pinned Uptodown XAPK bootstrap archive.""" +"""Download a pinned Uptodown XAPK for Mocktail's first-run setup.""" from __future__ import annotations @@ -47,7 +47,7 @@ DOWNLOAD_ORIGIN = "https://dw.uptodown.com/dwn/" class ProviderError(RuntimeError): - """A safe, user-facing provider failure.""" + """Raised when a provider response cannot be downloaded or validated.""" class DownloadPageParser(HTMLParser): diff --git a/scripts/assemble_flatpak_pages.sh b/scripts/assemble_flatpak_pages.sh index 9ca2e2c..c314d80 100755 --- a/scripts/assemble_flatpak_pages.sh +++ b/scripts/assemble_flatpak_pages.sh @@ -60,7 +60,7 @@ Branch=stable Url=${BASE_URL}/repo/ RuntimeRepo=https://dl.flathub.org/repo/flathub.flatpakrepo Homepage=https://github.com/komaruworld/mocktail -Comment=Experimental Linux compatibility runtime +Comment=Play Roblox on Linux Icon=${BASE_URL}/mocktail.svg GPGKey=${GPG_KEY} IsRuntime=false @@ -96,7 +96,7 @@ cat >"${OUTPUT}/index.html" <<'EOF' Install with Flatpak Source flatpak install --user https://mocktail.bigrat.space/mocktail.flatpakref -

Mocktail is experimental and is not affiliated with Roblox Corporation.

+

Mocktail is an independent community project and is not affiliated with Roblox Corporation.

EOF diff --git a/scripts/build.sh b/scripts/build.sh index 07b5486..495b501 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -6,47 +6,9 @@ # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 -# -# scripts/build.sh — Mocktail one-shot native build script. -# -# Usage: -# ./scripts/build.sh [OPTIONS] -# -# Options: -# --apk Explicitly extract a local Roblox x86_64 APK. -# --apk-url Explicitly download and extract an APK URL. -# --skip-apk Compatibility alias for the default managed-payload mode. -# --build-dir CMake build directory. Relative paths use project root. -# Default: build. -# --cmake-toolchain -# Explicit CMake toolchain file. -# --cmake-sysroot -# Explicit compiler/CMake sysroot. -# --build-type CMake build type: Debug | Release | RelWithDebInfo. -# Default: Release. -# --jobs Parallel build jobs. Default: nproc. -# --run-tests Run CTest after build. -# --clean Remove the build/ directory before configuring. -# --help Show this help and exit. -# -# Examples: -# # Build with a local APK file: -# ./scripts/build.sh --apk ~/Downloads/roblox-x86_64.apk -# -# # Build with a direct download URL: -# ./scripts/build.sh --apk-url "https://example.com/roblox-x86_64.apk" -# -# # Rebuild the C++ project; the first launch obtains the managed payload: -# ./scripts/build.sh --skip-apk --build-type Debug --run-tests -# -# With no APK option, Mocktail builds only the native runtime. Its first normal -# launch downloads, validates, canaries, and activates a supported x86_64 -# payload in the user's XDG data directory. ROBLOX_LIB_PATH remains an explicit -# research override and disables APK extraction when already set. set -euo pipefail -# Colour helpers RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' @@ -91,7 +53,6 @@ Examples: EOF } -# Default configuration SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" BUILD_DIR="${PROJECT_ROOT}/build" @@ -107,7 +68,6 @@ CLEAN=false CMAKE_TOOLCHAIN_FILE="" CMAKE_SYSROOT="" -# Argument parsing while [[ $# -gt 0 ]]; do case "$1" in --apk) @@ -180,7 +140,6 @@ info "Mocktail build: ${BUILD_TYPE}, -j${JOBS}" info "Project root: ${PROJECT_ROOT}" info "Build directory: ${BUILD_DIR}" -# Step 1 — Check required tools step "1/5 checking dependencies" check_tool() { @@ -208,7 +167,6 @@ if [[ "${EXTRACT_APK}" == true && -n "${APK_URL}" ]] && check_tool wget "sudo apt install wget" fi -# Step 2 — Download APK (if URL provided) if [[ "${EXTRACT_APK}" == true && -n "${APK_URL}" ]]; then step "2/5 downloading Roblox APK" APK_PATH="${PROJECT_ROOT}/rbx_bin/roblox-x86_64.apk" @@ -223,7 +181,6 @@ if [[ "${EXTRACT_APK}" == true && -n "${APK_URL}" ]]; then success "APK downloaded." fi -# Step 3 — Extract libroblox.so from APK LIBROBLOX_DST="${RBX_BIN_DIR}/libroblox.so" if [[ -n "${ROBLOX_LIB_PATH:-}" ]]; then @@ -251,7 +208,6 @@ else success "No APK or libroblox.so is required at build time." fi -# Step 4 — Init git submodules step "4/5 initialising git submodules" cd "${PROJECT_ROOT}" if [[ -f ".gitmodules" ]]; then @@ -262,7 +218,6 @@ else info "To add submodules run: ./scripts/add_submodules.sh" fi -# Step 5 — CMake configure step "5/5 configuring CMake" if [[ "${CLEAN}" == true && -d "${BUILD_DIR}" ]]; then @@ -293,21 +248,18 @@ cmake "${cmake_arguments[@]}" success "CMake configuration complete." -# Step 6 — Build step "building Mocktail" cmake --build "${BUILD_DIR}" -j"${JOBS}" success "Build complete → ${BUILD_DIR}/mocktail" -# Step 7 — Run tests (optional) if [[ "${RUN_TESTS}" == true ]]; then step "running unit tests" ctest --test-dir "${BUILD_DIR}" --output-on-failure -j"${JOBS}" success "All tests passed." fi -# Done echo success "Build successful." echo "Run:" diff --git a/scripts/derive_roblox_host_abi_profile.py b/scripts/derive_roblox_host_abi_profile.py index 7b5d16d..07e0db2 100755 --- a/scripts/derive_roblox_host_abi_profile.py +++ b/scripts/derive_roblox_host_abi_profile.py @@ -13,12 +13,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Derive a fail-closed HostAbi profile from one exact approved payload. +"""Build a HostAbi profile for one approved payload without running it. -The analyzer never executes guest code and never marks a payload supported. -It accepts only an exact reference sidecar bound to the reference ELF digest, -matches native boundaries structurally, and emits an experimental manifest for -an isolated updater probation run. +The reference sidecar must match the reference ELF digest. The resulting +manifest is meant for an updater probation run; it does not mark the payload as +supported. """ from __future__ import annotations @@ -40,7 +39,7 @@ from typing import Any, Iterator, Sequence try: import capstone from capstone import x86 as capstone_x86 -except ImportError: # The CLI turns this into one explicit fail-closed error. +except ImportError: capstone = None capstone_x86 = None @@ -107,7 +106,7 @@ PRIMARY_TOOLCHAIN_MARKERS = ( class AnalyzerError(RuntimeError): - """One deterministic fail-closed analysis error.""" + """Raised when HostAbi analysis rejects an input.""" @dataclass(frozen=True) @@ -1636,9 +1635,7 @@ def analyze(arguments: argparse.Namespace) -> tuple[dict[str, Any], dict[str, An def parse_arguments(argv: Sequence[str]) -> argparse.Namespace: parser = argparse.ArgumentParser( - description=( - "Derive an exact experimental Roblox HostAbi sidecar for isolated probation" - ) + description="Derive a probationary Roblox HostAbi sidecar" ) parser.add_argument("--reference-lib", required=True, metavar="REF") parser.add_argument("--reference-profile", required=True, metavar="REFJSON") diff --git a/scripts/real_bringup_smoke.sh b/scripts/real_bringup_smoke.sh index 8b767f1..beccfc3 100755 --- a/scripts/real_bringup_smoke.sh +++ b/scripts/real_bringup_smoke.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # Progressive real-startup harness for Mocktail. -# Tiers A, B, and LEGACY are research experiments. Tier C verifies the +# Tiers A, B, and LEGACY are diagnostic modes. Tier C verifies the # supported LuaApp graphics path. GAME verifies the local UGCGame graphics # path. INPUT adds a real SDL-to-JNI click to that gate. NETWORK verifies an # authenticated public-place join without exposing @@ -360,7 +360,7 @@ SUPPORT_BUNDLE_DISABLED="${MOCKTAIL_DISABLE_SUPPORT_BUNDLE:-0}" export MOCKTAIL_DISABLE_SUPPORT_BUNDLE=1 set +e # Roblox may ignore SIGTERM while a native engine thread is -# active. Bound the smoke experiment even in that state so the caller gets a +# active. Bound the smoke run even in that state so the caller gets a # deterministic result and no stale window/process is left behind. Tier C with # an explicitly disabled present timer is the supported interactive launch, # however, and must remain alive until the user closes it. diff --git a/src/compat/host_abi_experiment.cc b/src/compat/host_abi_experiment.cc index 4046d45..14c8048 100644 --- a/src/compat/host_abi_experiment.cc +++ b/src/compat/host_abi_experiment.cc @@ -200,7 +200,7 @@ InstallHostAbiExperiment(uintptr_t library_base, const HostAbiProfile &profile, result.uses_native_mimalloc = !options.install_allocator_bridges; if (library_base == 0 || profile.bridge_entry_count == 0 || profile.bridge_entry_count > profile.bridge_entries.size()) { - result.error = "invalid host ABI experiment profile"; + result.error = "invalid host ABI profile"; return result; } @@ -240,7 +240,7 @@ InstallHostAbiExperiment(uintptr_t library_base, const HostAbiProfile &profile, std::cout << std::flush; result.installed = ok; if (!ok) { - result.error = "one or more host ABI experiment actions failed"; + result.error = "one or more host ABI actions failed"; } return result; } diff --git a/src/compat/payload_compatibility.cc b/src/compat/payload_compatibility.cc index 9b721f1..7a85d14 100644 --- a/src/compat/payload_compatibility.cc +++ b/src/compat/payload_compatibility.cc @@ -62,7 +62,7 @@ PayloadCompatibilityResult CheckPayloadCompatibility( result.error = "This Roblox build is not enabled for normal runs: " + result.profile.reason + "\n Use --allow-unverified-build only for an explicit " - "compatibility experiment."; + "compatibility run."; result.profile = {}; return result; } diff --git a/src/graphics/graphics_backend.cc b/src/graphics/graphics_backend.cc index 298725a..6f68cf5 100644 --- a/src/graphics/graphics_backend.cc +++ b/src/graphics/graphics_backend.cc @@ -82,7 +82,6 @@ BackendSelection SelectGraphicsBackend( reason}; } - // Fallback order remains Vulkan-first and deterministic. constexpr GraphicsBackendKind kFallbackOrder[] = { GraphicsBackendKind::kAngleVulkan, GraphicsBackendKind::kDirectVulkan, diff --git a/src/jnivm/jnivm.cc b/src/jnivm/jnivm.cc index 9f5e71d..86de5ec 100644 --- a/src/jnivm/jnivm.cc +++ b/src/jnivm/jnivm.cc @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -// src/jnivm/jnivm.cc — Implementation of the Pseudo Java Native Interface VM. - #include "jnivm/jnivm.h" #include @@ -35,8 +33,6 @@ namespace jnivm { -// Class implementation - void Class::RegisterMethod(const std::string& method_name, const std::string& signature, MethodCallback callback) { @@ -71,7 +67,6 @@ thread_local JNIEnv* g_thread_local_env = nullptr; thread_local JNIEnv g_thread_env_storage = {}; thread_local VM* g_thread_vm_instance = nullptr; -// Thread-local pointer to the active VM instance for JNI callback resolution. VM* g_vm_instance = nullptr; std::recursive_mutex g_jni_state_mutex; @@ -495,8 +490,7 @@ RobloxAuthIdentity ResearchRobloxIdentityFromEnvironment() { return identity; } - // Temporary research-only compatibility path. Production startup should - // inject a resolved identity through VM::SetRobloxAuthIdentity instead. + // Normal startup injects identity through VM::SetRobloxAuthIdentity. identity.user_id = static_cast(parsed); const char* username = std::getenv("MOCKTAIL_ROBLOX_USERNAME"); if (username && username[0] != '\0') { @@ -557,8 +551,7 @@ std::shared_ptr ClassFromJClass(jclass clazz) { return FallbackClassForName(cls->GetName()); } -// Stores a shared_ptr into my_segment, keeping it alive in g_segment_owners. -// Returns the encoded segment-table handle cast to jclass. +// g_segment_owners keeps the encoded class handle alive. static jclass StoreClass(std::shared_ptr cls) { std::lock_guard lock(g_jni_state_mutex); Class* raw_ptr = cls.get(); @@ -593,9 +586,8 @@ PseudoJavaObject* PseudoObjectFromRef(jobject obj) { raw_ptr = reinterpret_cast(obj); } if (!raw_ptr) return nullptr; - // Perform a static cast instead of dynamic_cast to avoid calling the host - // __dynamic_cast against potentially-Bionic RTTI structures. All objects - // stored in our segment table are PseudoJavaObjects by construction. + // Avoid host __dynamic_cast on potentially Bionic RTTI; segment entries are + // PseudoJavaObjects by construction. return static_cast(raw_ptr); } @@ -840,8 +832,7 @@ std::string JavaStringUtf8Bytes(const std::vector& utf16) { code_point = 0x10000 + ((code_point - 0xd800) << 10) + (utf16[++index] - 0xdc00); } else if (code_point >= 0xd800 && code_point <= 0xdfff) { - // java.nio.charset.CharsetEncoder replaces each malformed UTF-16 input - // unit with its configured UTF-8 replacement, the single byte '?'. + // CharsetEncoder replaces each malformed UTF-16 unit with '?'. output.push_back('?'); continue; } @@ -5082,9 +5073,7 @@ JNIEnv* VM::GetJNIEnv() { } void VM::RestoreFunctions() { - // Force env->functions back to our pseudo-VM table on ALL known JNIEnv*. - // libroblox.so replaces env->functions with a Bionic JNI table during - // JNI_OnLoad; we need to restore it before Stage 6 calls. + // JNI_OnLoad replaces env->functions; restore all known environments. java_vm_storage_.functions = &invoke_interface_; java_vm_ = &java_vm_storage_; if (jni_env_) { @@ -5121,10 +5110,6 @@ std::shared_ptr VM::FindClass(const std::string& class_name) const { } void VM::InitJNIFunctionTables() { - // JNIInvokeInterface_ — the JavaVM function table. - // Most functions are no-ops in this pseudo-VM; only AttachCurrentThread - // is meaningful for single-threaded prototype use. - invoke_interface_.AttachCurrentThread = [](JavaVM* vm, void** env, void* /*args*/) -> jint { if (JniVmTraceEnabled()) { @@ -5207,9 +5192,6 @@ void VM::InitJNIFunctionTables() { java_vm_storage_.functions = &invoke_interface_; java_vm_ = &java_vm_storage_; - // JNINativeInterface_ — the JNIEnv function table. - // Only the minimal subset required by Roblox's JNI_OnLoad is stubbed here. - native_interface_.FindClass = [](JNIEnv* /*env*/, const char* name) -> jclass { if (JniVmTraceEnabled()) { diff --git a/src/legacy/legacy_runtime.cc b/src/legacy/legacy_runtime.cc index dacbc38..d188976 100644 --- a/src/legacy/legacy_runtime.cc +++ b/src/legacy/legacy_runtime.cc @@ -12,18 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Transitional Sober compatibility runtime. This file preserves the current -// reverse-engineered startup path while upstream-backed modules replace -// it one boundary at a time. -// -// Implements the preserved 5-stage initialisation pipeline: -// -// Stage 1: Initialise Pseudo-JVM (libjnivm). -// Stage 2: Register mandatory Android SDK JNI classes. -// Stage 3: Load host system libraries under Bionic alias names. -// Stage 4: Map libroblox.so into process address space via the linker. -// Stage 5: Invoke JNI_OnLoad and hand control to the game engine. - #include #include #include @@ -400,7 +388,6 @@ extern "C" int mocktail_recover_stack_chk_fail() { return 0; } -// JNI_OnLoad function pointer type as specified by the JNI standard. using JniOnLoadFn = jint (*)(JavaVM*, void*); using NativeGameGlobalInitFn = void (*)(JNIEnv*, jclass); using NativeInitClientSettingsFn = jint (*)(JNIEnv*, jclass, jstring, jstring, @@ -2519,10 +2506,7 @@ static void* GetThreadScratchBuffer(pid_t tid) { return g_thread_scratch_pool[0].buffer; } -// Fallback vtable used when a null/invalid object pointer is dereferenced -// during a vtable dispatch in Stage 6. Each slot is a no-op stub that -// returns 0 so that the caller can continue execution. The table covers -// all vtable offsets seen in libroblox (currently up to +0xf8 = slot 31). +// Covers every libroblox vtable offset observed through slot 31 (+0xf8). static uintptr_t NullVtableStub() { return 0; } static const uintptr_t kFallbackVtable[32] = { reinterpret_cast(&NullVtableStub), @@ -2558,8 +2542,7 @@ static const uintptr_t kFallbackVtable[32] = { reinterpret_cast(&NullVtableStub), reinterpret_cast(&NullVtableStub), }; -// Fake object whose first 8 bytes point to kFallbackVtable. -// Used to satisfy `mov rax,[rsi]; call [rax+offset]` when RSI=null. +// Lets a recovered `mov rax,[rsi]` continue through the fallback vtable. static const uintptr_t kFallbackObject[4] = { reinterpret_cast(kFallbackVtable), 0, 0, 0, }; @@ -2810,12 +2793,10 @@ void ApplyRuntimeDefaults() { SetEnvDefault("MOCKTAIL_ENGINE_DETACH", "0"); SetEnvDefault("MOCKTAIL_INIT_CLIENT_SETTINGS", "0"); SetEnvDefault("MOCKTAIL_POST_CLIENT_SETTINGS", "0"); - // Sober-compatible fallback: allow unauthenticated startup (LuaApp path) - // unless explicitly disabled. SetEnvDefault("MOCKTAIL_ALLOW_NO_COOKIE_LUA_APP", "1"); // Current Roblox Android builds drive app startup through NativeGLInterface's // V2 app bridge path. The legacy NativeAppBridgeInterface entry point stays - // available for experiments, but running it in parallel can block V2 init. + // available by opt-in, but running it in parallel can block V2 init. SetEnvDefault("MOCKTAIL_APP_BRIDGE_APP_START", "0"); SetEnvDefault("MOCKTAIL_APP_BRIDGE_APP_START_THREAD", "0"); SetEnvDefault("MOCKTAIL_START_LUA_APP_DM", "1"); @@ -5824,8 +5805,8 @@ extern "C" void* mocktail_roblox_allocator_object_alloc_bridge(void*, return mocktail_roblox_aligned_alloc_bridge(size, align); } -// Retained only for the legacy fixed-offset allocator patch below. Current -// Build-ID experiments own their seed storage in compat/host_abi_experiment. +// Retained for the legacy fixed-offset allocator patch. Build-ID profiles keep +// their seed storage in compat/host_abi_experiment. uintptr_t g_mocktail_roblox_allocator_object_vtable[4] = {}; uintptr_t g_mocktail_roblox_allocator_object[1] = { reinterpret_cast(g_mocktail_roblox_allocator_object_vtable)}; @@ -5902,7 +5883,7 @@ bool InstallActiveHostAbiExperiment(uintptr_t libroblox_base) { } g_host_abi_install_attempted = true; if (!HostAbiExperimentRequested()) { - std::cout << " [compat] host ABI experiment disabled by policy\n" + std::cout << " [compat] host ABI profile disabled by policy\n" << std::flush; return false; } @@ -5910,7 +5891,7 @@ bool InstallActiveHostAbiExperiment(uintptr_t libroblox_base) { const mocktail::compat::HostAbiProfile* profile = g_active_host_abi_profile.load(std::memory_order_acquire); if (libroblox_base == 0 || profile == nullptr) { - std::cerr << " [compat] host ABI experiment has no active profile/base\n" + std::cerr << " [compat] host ABI install has no active profile/base\n" << std::flush; return false; } @@ -6534,7 +6515,7 @@ extern "C" void mocktail_before_soinfo_constructors(const char* realpath, // Host alloc bridges must be in place before any constructor executes. if (HostAbiExperimentRequested() && !InstallActiveHostAbiExperiment(base)) { - std::cerr << " [compat] host ABI experiment failed before constructors\n" + std::cerr << " [compat] host ABI install failed before constructors\n" << std::flush; return; } @@ -6634,7 +6615,6 @@ extern "C" void mocktail_before_soinfo_constructors(const char* realpath, uintptr_t* init_array = reinterpret_cast(base + init_array_offset); - // Make the page writable just in case long ctor_page_size = sysconf(_SC_PAGESIZE); if (ctor_page_size > 0) { uintptr_t addr = reinterpret_cast(init_array); @@ -6648,7 +6628,6 @@ extern "C" void mocktail_before_soinfo_constructors(const char* realpath, g_original_ctors.reserve(kCtorCount); for (size_t i = 0; i < kCtorCount; ++i) { g_original_ctors.push_back(init_array[i]); - // Skip the first constructor if env flag is set, otherwise wrap it if (i == 0 && IsEnabled("MOCKTAIL_PATCH_FIRST_CONSTRUCTOR")) { static void (*noop_fn)() = []() {}; g_original_ctors[i] = reinterpret_cast(noop_fn); @@ -26926,26 +26905,21 @@ void HeadlessSegvHandler(int signo, siginfo_t* info, void* context) { return; } - // libGLdispatch crash recovery (handles Bionic TLS incompatibility in Glibc threads). - // Matches any SIGSEGV occurring inside libGLdispatch.so.0. + // libGLdispatch may access Bionic TLS keys through glibc thread state. if (g_current_stage >= 6 && module_name && std::strstr(module_name, "libGLdispatch") != nullptr) { - // If the instruction is a 64-bit store (mov [reg], reg) or load (mov reg, [reg]): if (instruction[0] == 0x48 && (instruction[1] == 0x89 || instruction[1] == 0x8b)) { int rm = instruction[2] & 7; int reg_map[] = { REG_RAX, REG_RCX, REG_RDX, REG_RBX, -1, -1, REG_RSI, REG_RDI }; if (rm < 8 && reg_map[rm] != -1) { uintptr_t old_val = ucontext->uc_mcontext.gregs[reg_map[rm]]; - // Only redirect if the register is currently NULL, very low, or points to a read-only stub/vtable. if (old_val < kStage5LowAddressThreshold) { - // Get or allocate a thread-specific writable buffer from our pool. - // This avoids calling malloc (which corrupts the heap if interrupted) - // and avoids sharing the same static buffer across multiple threads (which causes corruption). + // Signal handlers cannot call malloc; each thread gets fixed scratch + // storage to avoid sharing writes across recovered threads. pid_t tid = static_cast(syscall(SYS_gettid)); void* thread_scratch = GetThreadScratchBuffer(tid); ucontext->uc_mcontext.gregs[reg_map[rm]] = reinterpret_cast(thread_scratch); } else if (old_val == reinterpret_cast(&NullVtableStub) || old_val == reinterpret_cast(kFallbackVtable)) { - // If it was redirected to a read-only stub/vtable, redirect it to a thread-specific writable buffer. pid_t tid = static_cast(syscall(SYS_gettid)); void* thread_scratch = GetThreadScratchBuffer(tid); ucontext->uc_mcontext.gregs[reg_map[rm]] = reinterpret_cast(thread_scratch); @@ -27016,7 +26990,7 @@ void HeadlessSegvHandler(int signo, siginfo_t* info, void* context) { } skip_stage6_null_object_immediate_store: - // Broader: any `48 89 ??` store with low dest-addr (catches all register combos). + // Catch the remaining `48 89 ??` stores to low addresses. if (g_current_stage >= 6 && info && reinterpret_cast(info->si_addr) < kStage5LowAddressThreshold && instruction[0] == 0x48 && instruction[1] == 0x89) { @@ -27026,10 +27000,9 @@ skip_stage6_null_object_immediate_store: write(2, msg, sizeof(msg) - 1); } ++g_skipped_headless_null_writes; - // Determine instruction length (modrm) — skip the store. - // 48 89 /r — 3 or 4 bytes depending on displacement. uint8_t modrm = instruction[2]; uint8_t mod = (modrm >> 6) & 0x3; + // The ModR/M displacement determines how many instruction bytes to skip. int instr_len = (mod == 1) ? 4 : (mod == 2) ? 7 : 3; ucontext->uc_mcontext.gregs[REG_RIP] += instr_len; return; @@ -27054,9 +27027,7 @@ skip_stage6_null_object_immediate_store: reinterpret_cast(info->si_addr) == 0 && instruction[0] == 0x48 && instruction[1] == 0x8b && instruction[2] == 0x06) { - // Pattern: mov rax,[rsi] with RSI=null, often followed by call [rax+N]. - // Point RSI at a fake object whose vtable is kFallbackVtable so that the - // load succeeds and subsequent indirect calls land in NullVtableStub. + // Redirect the null receiver so a following vtable call reaches the stub. if (g_skipped_headless_null_writes == 0) { const char msg[] = " [patch] redirected Stage6 mov rax,[rsi] (RSI=null) to fallback object\n"; @@ -27065,13 +27036,11 @@ skip_stage6_null_object_immediate_store: ++g_skipped_headless_null_writes; ucontext->uc_mcontext.gregs[REG_RSI] = reinterpret_cast(kFallbackObject); - // Do NOT advance RIP — re-execute the mov against the now-valid RSI. + // Re-run the load against the repaired receiver. return; } - // Broader fallback: any Stage6 null/low-address load through RSI into RAX - // that is the exact 3-byte form `48 8b 06` — recover by pointing RSI at - // fallback vtable object so the call chain can continue. + // The same recovery applies to nonzero addresses below the valid threshold. if (g_current_stage >= 6 && info && reinterpret_cast(info->si_addr) < kStage5LowAddressThreshold && instruction[0] == 0x48 && instruction[1] == 0x8b && @@ -27107,8 +27076,7 @@ skip_stage6_null_object_immediate_store: return; } - // Also handle `mov rax,[rax]` (48 8b 00) / `mov rax,[rax+N]` (48 8b 40 NN) - // with low RAX — redirect RAX to kFallbackVtable. + // Redirect low `mov rax,[rax+N]` bases to the fallback vtable. if (g_current_stage >= 6 && info && reinterpret_cast(info->si_addr) < kStage5LowAddressThreshold && instruction[0] == 0x48 && instruction[1] == 0x8b && @@ -27280,12 +27248,8 @@ skip_stage6_null_object_immediate_store: return; } - // SI_KERNEL SIGTRAP (si_code=128): kernel-generated trap, commonly a - // shadow-stack (CET/SHSTK) return-address mismatch on Roblox's internal - // threads. The instruction at RIP is not an int3 — it's the instruction - // that would have executed after the bad return. We cannot safely continue - // from here; if a recovery jmp_buf is armed on this thread use it, otherwise - // just exit the offending thread so the rest of the engine keeps running. + // SI_KERNEL SIGTRAP commonly follows a CET shadow-stack return mismatch. + // Use a thread-local recovery target when one is armed. if (signo == SIGTRAP && info && info->si_code == SI_KERNEL && g_current_stage >= 6) { if (libroblox_base != 0 && @@ -27324,17 +27288,11 @@ skip_stage6_null_object_immediate_store: write(2, msg, sizeof(msg) - 1); siglongjmp(g_start_app_with_params_jmp_buf, 1); } - // No recovery target on this thread — skip the trap by advancing RIP past - // the current instruction and returning. Killing the thread via - // pthread_exit would hang the engine since Roblox waits on these threads. - // SI_KERNEL SIGTRAP here is a shadow-stack (CET) mismatch on Roblox's - // internal threads; skipping it lets execution continue from the instruction - // after the bad return address. + // Roblox may wait on this thread, so exiting it can hang the engine. const char msg[] = " [patch] SI_KERNEL SIGTRAP: no recovery, skipping instruction\n"; write(2, msg, sizeof(msg) - 1); - // Advance past the current instruction — for SI_KERNEL traps the faulting - // instruction is still at RIP (not executed yet), so +1 moves past it. + // SI_KERNEL leaves RIP on the instruction that has not run yet. ucontext->uc_mcontext.gregs[REG_RIP] += 1; return; } @@ -28488,9 +28446,8 @@ bool PatchStartAppDebugTrap(void* native_start_app_with_params) { return false; } - // NOTE: We intentionally do NOT patch the function entry point with `ret`. - // Doing so would silently kill the real startup path. Only the known-bad - // diagnostic tail block inside the function is bypassed here. + // Patch only the diagnostic tail; replacing the entry point would disable + // real startup. constexpr uintptr_t kNativeStartAppOffset = 0x250436d; constexpr uintptr_t kTailPatchOffset = 0x2504567; constexpr unsigned char kTailJumpPatch[] = { @@ -30533,9 +30490,7 @@ void* EngineStartupThread(void* arg) { << std::flush; mocktail::window::MakeCurrentOnThread(); } - // Force our pseudo-VM function table on the env that EngineStartupThread will use. - // libroblox.so replaces env->functions during JNI_OnLoad; we restore it here so - // that our NewStringUTF / GetStringUTFChars helpers work correctly in Stage 6. + // JNI_OnLoad replaces env->functions; Stage 6 needs the pseudo-VM table. if (env && context->vm) { context->vm->RestoreFunctions(); env = context->vm->GetJNIEnv(); @@ -32050,7 +32005,7 @@ int mocktail::legacy::Run(const runtime::CommandLineOptions& options, std::cerr << "[FATAL] This Roblox build is not enabled for normal runs: " << build_profile.reason << '\n' << " Use --allow-unverified-build only for an explicit " - "compatibility experiment.\n"; + "compatibility check.\n"; return EXIT_FAILURE; } @@ -32064,10 +32019,8 @@ int mocktail::legacy::Run(const runtime::CommandLineOptions& options, std::cout << " [trace] full tracing enabled\n" << std::flush; } - // Disable shadow-stack (CET/SHSTK) for this process before loading - // libroblox. Roblox's internal threads trigger SIGTRAP SI_KERNEL from - // return-address mismatches on the shadow stack; disabling it here prevents - // those spurious traps. This is a no-op on kernels that don't support CET. + // Roblox internal threads trigger SI_KERNEL traps from CET shadow-stack + // return mismatches. Unsupported kernels ignore this request. { long r = syscall(SYS_arch_prctl, ARCH_SHSTK_DISABLE, ARCH_SHSTK_SHSTK); if (r == 0) { @@ -32086,7 +32039,6 @@ int mocktail::legacy::Run(const runtime::CommandLineOptions& options, setenv("MOCKTAIL_APP_BRIDGE_HEADLESS_INIT_PARAMS", is_headless ? "1" : "0", 1); } - // Stage 1: Initialise the Bionic-compatible ELF linker + Pseudo-JVM. PrintStage(1, "Initialising Bionic linker + Pseudo-JVM"); #ifdef MOCKTAIL_USE_BIONIC_LINKER @@ -32101,8 +32053,7 @@ int mocktail::legacy::Run(const runtime::CommandLineOptions& options, } std::cout << " Pseudo-JVM instance created successfully.\n"; - // Stage 2: Register mandatory Android SDK JNI classes. - // Roblox's JNI_OnLoad will call FindClass for these at startup. + // FindClass needs these descriptors before JNI_OnLoad. PrintStage(2, "Registering Android SDK JNI classes"); auto context_class = jni_vm->RegisterClass("android/content/Context"); @@ -32116,7 +32067,6 @@ int mocktail::legacy::Run(const runtime::CommandLineOptions& options, std::cout << " [JNI callback] nativeInitClientSettings invoked\n"; }); - // Classes required by Stage 6 startup entry points. auto native_gl_interface_class = jni_vm->RegisterClass("com/roblox/engine/jni/NativeGLInterface"); g_native_gl_class_for_main_thread = @@ -32190,10 +32140,8 @@ int mocktail::legacy::Run(const runtime::CommandLineOptions& options, std::cout << " Registered " << jni_vm->GetClassCount() << " JNI class(es)\n"; - // Window init (non-headless mode): create the SDL3 + EGL window before - // Android/EGL stubs are preloaded, so SDL resolves the real host graphics - // backend. By default the window stays hidden until Roblox presents a real - // frame. + // Create SDL/EGL before loading Android stubs so SDL binds the host graphics + // backend. The window stays hidden until the first real frame. bool window_initialised = false; if (!is_headless) { const int win_w = runtime_config.window().width; @@ -32250,15 +32198,6 @@ int mocktail::legacy::Run(const runtime::CommandLineOptions& options, } } - // Stage 3: Populate the Bionic symbol table with all stub + system - // symbols so that mcpelauncher-linker can resolve libroblox.so's - // DT_NEEDED entries without calling the host Glibc dynamic linker. - // - // Strategy: - // 1. dlopen each stub .so with RTLD_GLOBAL (host linker finds them - // via RPATH=$ORIGIN). - // 2. Iterate over known stub symbols and register them into the - // Bionic symbol table via linker::RegisterSymbol(). PrintStage(3, "Building Bionic symbol table from stubs"); libc_shim::Install(); @@ -32299,7 +32238,6 @@ int mocktail::legacy::Run(const runtime::CommandLineOptions& options, } } - // Stub libraries to preload. All located via RPATH=$ORIGIN. std::vector stub_names = { "libc.so", "libdl.so", "libm.so", "libz.so", "libandroid.so", "liblog.so", "libmediandk.so", @@ -32343,15 +32281,9 @@ int mocktail::legacy::Run(const runtime::CommandLineOptions& options, real_gles_handle = OpenRealGlesLibrary(); } - // Register every known Android NDK / system symbol into the Bionic - // symbol table so mcpelauncher-linker resolves them correctly. - // - // We prioritize resolving from our loaded stub libraries first. - // This ensures Bionic shims (like custom pthread structure handlers - // or __sF stdio compatibility) are preferred over host glibc ones, - // preventing layout incompatibility and crashes. + // Prefer Bionic shims over glibc for guest layouts such as pthread and FILE. static const char* kSymbolsToRegister[] = { - // Exported stub functions and structures + // Symbols exported by the Android compatibility stubs. "AAsset_close", "AAsset_getBuffer", "AAsset_getLength", @@ -32609,7 +32541,7 @@ int mocktail::legacy::Run(const runtime::CommandLineOptions& options, "__system_property_get", "__write_chk", - // Binary-compatible standard pthread symbols not explicitly shimmed + // pthread functions whose host ABI is already compatible. "pthread_once", "pthread_self", "pthread_equal", @@ -32626,7 +32558,7 @@ int mocktail::legacy::Run(const runtime::CommandLineOptions& options, "pthread_sigmask", "pthread_setname_np", - // Standard C library functions needed by libroblox.so + // Standard C library functions resolved from host libc. "strcmp", "strncmp", "strcpy", @@ -32938,13 +32870,8 @@ int mocktail::legacy::Run(const runtime::CommandLineOptions& options, } } - // Stage 4: Load libroblox.so via the Bionic-compatible linker. - - // Auto-register ALL undefined symbols from the target library BEFORE - // loading stub libraries into the Bionic linker. This ensures the stubs' - // injected symbol maps include every symbol libroblox.so will need at - // relocation time. Auto-registration must run first because load_library - // takes a snapshot of g_bionic_symbols at call time. + // Register imports before loading synthetic SONAMEs; load_library snapshots + // g_bionic_symbols. { int fd = ::open(library_path.c_str(), O_RDONLY); if (fd >= 0) { @@ -32954,7 +32881,6 @@ int mocktail::legacy::Run(const runtime::CommandLineOptions& options, PROT_READ, MAP_PRIVATE, fd, 0); if (map != MAP_FAILED) { auto* ehdr = reinterpret_cast(map); - // Walk section headers to find .dynsym and .dynstr auto* shdr = reinterpret_cast( reinterpret_cast(map) + ehdr->e_shoff); Elf64_Shdr* dynsym_hdr = nullptr; @@ -32975,7 +32901,6 @@ int mocktail::legacy::Run(const runtime::CommandLineOptions& options, if (syms[si].st_shndx != SHN_UNDEF) continue; const char* name = strtab + syms[si].st_name; if (!name || name[0] == '\0') continue; - // Skip if already registered if (linker::GetBionicSymbols().count(name)) continue; auto result = ResolveSymbolForBionic(name, has_window, real_gles_handle, @@ -32996,9 +32921,7 @@ int mocktail::legacy::Run(const runtime::CommandLineOptions& options, } } - // Register stub libraries inside Bionic linker. This must happen AFTER - // auto-registration so that every symbol libroblox needs is already in - // g_bionic_symbols when load_library snapshots the map. + // Load synthetic libraries only after the import map is complete. linker::RegisterBionicPthreadKeyRuntimeForLibc(); linker::RegisterBionicSysconfRuntimeForLibc(); linker::RegisterBionicSignalRuntimeForLibc(); @@ -33038,12 +32961,11 @@ int mocktail::legacy::Run(const runtime::CommandLineOptions& options, std::cout << " [linker] Bionic unwind metadata validated for libroblox\n" << std::flush; - // The pre-constructor linker hook is the only installation phase. A second - // arena initialization here would not be idempotent; post-load only verifies - // that the experiment completed. + // The pre-constructor hook owns installation; repeating arena setup here is + // not idempotent. if (HostAbiExperimentRequested() && (!g_host_abi_install_attempted || !g_host_abi_install_result)) { - std::cerr << "\n[FATAL] Host ABI experiment did not install before " + std::cerr << "\n[FATAL] Host ABI profile did not install before " "libroblox constructors: " << (g_host_abi_install_result.error != nullptr ? g_host_abi_install_result.error @@ -33052,7 +32974,6 @@ int mocktail::legacy::Run(const runtime::CommandLineOptions& options, return EXIT_FAILURE; } - // Stage 5: Invoke JNI_OnLoad — hand control to the game engine. PrintStage(5, "Invoking JNI_OnLoad in libroblox.so"); auto* jni_onload = @@ -33230,9 +33151,7 @@ int mocktail::legacy::Run(const runtime::CommandLineOptions& options, } } - // libroblox.so may have replaced env->functions during JNI_OnLoad with a - // Bionic JNI table. Restore our pseudo-VM function table so that our Stage 6 - // helpers (NewStringUTF, GetStringUTFChars, etc.) work correctly. + // Restore the pseudo-JNI table if JNI_OnLoad replaced it. { JNIEnv* main_env = jni_vm->GetJNIEnv(); if (main_env) { @@ -33855,13 +33774,8 @@ int mocktail::legacy::Run(const runtime::CommandLineOptions& options, std::cout << " [compat] skipped fixed-offset Stage 6 patch set\n" << std::flush; } - // By default, all startup steps are enabled when MOCKTAIL_START_ENGINE=1. - // Real (non-mock) calls to nativeSetAssetPath, - // nativeAppBridgeV2InitWithParams and nativeAppBridgeV2StartAppWithParams - // are now the default path — the mock/bypass path only activates when the - // real call crashes and recovery fires via siglongjmp. The old - // MOCKTAIL_UNSAFE_NATIVE_STARTUP guard is removed so users do not need to - // set it explicitly. + // Real startup calls run by default; signal recovery falls back to the + // Mocktail implementations. const bool requested_set_asset_path = ShouldRunStartupStep("MOCKTAIL_STEP_SET_ASSET_PATH", true); const bool requested_init_with_params = @@ -33869,8 +33783,6 @@ int mocktail::legacy::Run(const runtime::CommandLineOptions& options, const bool requested_start_app_with_params = ShouldRunStartupStep("MOCKTAIL_STEP_START_APP_WITH_PARAMS", true); const bool run_set_asset_path = requested_set_asset_path; - // call_real_set_asset_path: enabled by default unless explicitly disabled. - // The signal handler will catch any crash and fall back to MocktailSetAssetPath. const bool call_real_set_asset_path = run_set_asset_path && !IsDisabled("MOCKTAIL_CALL_REAL_NATIVE_SET_ASSET_PATH"); @@ -33888,7 +33800,6 @@ int mocktail::legacy::Run(const runtime::CommandLineOptions& options, IsEnabled("MOCKTAIL_APP_BRIDGE_APP_START")) && native_app_bridge_app_start != nullptr; const bool run_init_with_params = requested_init_with_params; - // call_real_init_with_params: real call is the default; mock only on crash. const bool call_real_init_with_params = run_init_with_params && !IsDisabled("MOCKTAIL_CALL_REAL_APP_BRIDGE_INIT"); @@ -33934,8 +33845,7 @@ int mocktail::legacy::Run(const runtime::CommandLineOptions& options, (native_pass_current_display_refresh_rate != nullptr || native_pass_supported_refresh_rates != nullptr); const bool run_start_app_with_params = requested_start_app_with_params; - // call_real_start_app_with_params: opt-in because the real call can block - // startup before any rendering path becomes visible. + // Keep the real call opt-in because it can block before rendering starts. const bool call_real_start_app_with_params = run_start_app_with_params && IsEnabled("MOCKTAIL_CALL_REAL_APP_BRIDGE_START"); @@ -34547,7 +34457,7 @@ int mocktail::legacy::Run(const runtime::CommandLineOptions& options, if (!build_profile.default_allowed) { std::cerr - << "[FATAL] Compatibility experiment returned, but Build ID " + << "[FATAL] Compatibility run returned, but Build ID " << build_profile.elf_build_id << " has not passed the readiness gates; refusing a success exit.\n"; return EXIT_FAILURE; diff --git a/src/libc_shim/libc_shim.cc b/src/libc_shim/libc_shim.cc index d293a28..1dcd0e2 100644 --- a/src/libc_shim/libc_shim.cc +++ b/src/libc_shim/libc_shim.cc @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -// src/libc_shim/libc_shim.cc — Bionic libc → Glibc shim implementation. - #include "libc_shim/libc_shim.h" #include @@ -33,7 +31,6 @@ namespace libc_shim { namespace { -// Registered prefix mappings: android_prefix → host_prefix. std::unordered_map g_path_mappings; bool g_installed = false; @@ -504,10 +501,8 @@ extern "C" ssize_t mocktail_readlink(const char* path, char* buf, extern "C" ssize_t mocktail___readlink_chk(const char* path, char* buf, size_t len, size_t buf_len) { - // Match Bionic's fortify.cpp contract: both an impossible ssize_t result - // and a compiler-proven destination overrun are fatal programming errors, - // not recoverable readlink failures. Returning EINVAL here used to let the - // guest continue after a detected buffer overflow. + // Bionic treats impossible ssize_t results and proven overruns as fatal; + // returning EINVAL would continue after a detected buffer overflow. if (len > static_cast(SSIZE_MAX)) { std::fprintf(stderr, "FORTIFY: readlink: size %zu > SSIZE_MAX\n", len); diff --git a/src/linker/linker.cc b/src/linker/linker.cc index 2f88000..64b0283 100644 --- a/src/linker/linker.cc +++ b/src/linker/linker.cc @@ -6,11 +6,7 @@ // // http://www.apache.org/licenses/LICENSE-2.0 // -// src/linker/linker.cc — ELF loader implementation. -// -// Stage A (no mcpelauncher-linker): delegates to host dlopen/dlsym. -// Stage B (MOCKTAIL_USE_BIONIC_LINKER=1): delegates to mcpelauncher-linker -// which loads Bionic-ABI ELF files without Glibc version conflicts. +// Without the Bionic linker, loading falls back to host dlopen/dlsym. #include "linker/linker.h" @@ -50,16 +46,13 @@ namespace linker { namespace { -// Global alias → handle map maintained across all LoadLibrary calls. std::unordered_map g_symbol_table; -// New registrations retain symbol ownership per Android SONAME. The legacy -// global map remains isolated below until src/main.cc is migrated. +// Synthetic exports retain ownership per Android SONAME. std::unordered_map g_synthetic_symbols; std::unordered_map g_synthetic_libraries; std::unordered_set g_synthetic_handles; -// Compatibility symbol map used only by RegisterSymbol/LoadLibrary. SymbolMap g_legacy_bionic_symbols; // Keep string memory alive for Bionic linker sonames to avoid dangling pointers. @@ -121,11 +114,8 @@ int BionicDlcloseBoundary(void* handle) { char* BionicDlerrorBoundary() { return ::linker::dlerror(); } #endif -// The legacy startup path still discovers most imports in one untyped map. -// Bionic libc APIs with host-incompatible signatures or integer values must -// have exactly one owner, synthetic libc.so. Re-exporting those host functions -// from an earlier DT_NEEDED SONAME lets the guest bind to glibc before it -// reaches libc.so. +// Host-incompatible Bionic APIs belong only to synthetic libc.so. Exporting +// them from an earlier dependency could bind the guest to glibc instead. bool IsBionicLibcOnlySymbol(std::string_view symbol) { static constexpr std::string_view kStdioSymbols[] = { "__fread_chk", "__fwrite_chk", "__sF", "clearerr", @@ -189,8 +179,6 @@ void RemoveHandleFromRegistry(LibraryHandle handle) { } // namespace -// Public API - void RegisterSymbol(const std::string& name, void* addr) { std::lock_guard lock(g_registry_mutex); g_legacy_bionic_symbols[name] = addr; @@ -351,9 +339,8 @@ void RegisterBionicHostLibcRuntimeForLibc() { RegisterSyntheticSymbol( "libc.so", "gethostbyname", reinterpret_cast(mocktail_bionic_gethostbyname)); - // The preserved startup path publishes older DNS wrappers through its - // legacy global map. Replace those addresses before libc.so snapshots that - // map so every supported payload crosses the upload deny boundary. + // Replace legacy DNS wrappers before libc.so snapshots the global map, so + // every payload crosses the upload deny boundary. RegisterSymbol("getaddrinfo", reinterpret_cast(mocktail_bionic_getaddrinfo)); RegisterSymbol("freeaddrinfo", @@ -375,9 +362,8 @@ void RegisterBionicHostLibcRuntimeForLibc() { reinterpret_cast(mocktail_bionic_mallinfo)); RegisterSyntheticSymbol("libc.so", "strerror_r", reinterpret_cast(mocktail_bionic_strerror_r)); - // The preserved startup path scans libroblox imports through the legacy - // global map before relocation. Publish the same address there so it - // cannot auto-register glibc's GNU char*-returning strerror_r instead. + // Publish this before relocation to prevent binding glibc's incompatible + // GNU char*-returning strerror_r. RegisterSymbol("strerror_r", reinterpret_cast(mocktail_bionic_strerror_r)); RegisterSyntheticSymbol("libc.so", "strtoll_l", @@ -643,9 +629,7 @@ LibraryHandle LoadLibrary(const std::string& real_path, return OpenAndroidLibrary(real_path, host_alias); } - // The legacy composition already reaches this per-SONAME boundary for - // every Android dependency. Register host adapters here so libc.so owns the - // guest ABI without adding calls to the preserved legacy startup TU. + // Register adapters here so libc.so owns the guest ABI per SONAME. if (real_path == "libc.so") { RegisterBionicAtForkRuntimeForLibc(); RegisterBionicHostLibcRuntimeForLibc(); diff --git a/src/main.cc b/src/main.cc index 658ab99..f5ca9a4 100644 --- a/src/main.cc +++ b/src/main.cc @@ -6,10 +6,6 @@ // // http://www.apache.org/licenses/LICENSE-2.0 -// Mocktail's composition root stays intentionally small. During the -// upstream-first migration it delegates to the isolated legacy runtime; new -// platform, JNI, loader, and service modules are linked as explicit targets. - #include #include #include @@ -454,10 +450,8 @@ int main(int argc, char* argv[]) { << active_build_id.error << '\n'; return EXIT_FAILURE; } - // An old local probation receipt can outlive the point where the - // exact Build ID becomes shipped support. Built-in profiles are - // immutable and must then use the repository manifest instead of - // attempting an external override. + // Built-in profiles supersede stale probation receipts and never + // use external overrides. use_external_profile = mocktail::compat::FindHostAbiProfile( active_build_id.build_id) == nullptr; } @@ -759,11 +753,8 @@ int main(int argc, char* argv[]) { } if (command_line.options.mode == mocktail::runtime::CommandMode::kRun && !isolated_canary) { - // Start accepting browser requests only after cgroup re-exec, payload - // preflight and host bridge setup have succeeded. An ACK therefore cannot - // be invalidated by a known host-side - // startup or exec boundary. Isolated canaries cannot receive browser - // launches and own no public per-user endpoint. + // Listen only after re-exec, preflight, and bridge setup, so an ACK cannot + // precede a known startup failure. Isolated canaries expose no endpoint. mocktail::Status broker_status = mocktail::runtime::ExternalLaunchBroker::StartOwnerAfterLockAcquired( broker_options, &external_launch_broker.broker(), @@ -808,10 +799,8 @@ int main(int argc, char* argv[]) { command_line.options.mode == mocktail::runtime::CommandMode::kRun) { failure_dialog.MarkSuccessful(); support_bundle_guard.Disarm(); - // The embedded Android payload registers process-global atexit handlers - // for workers that it does not expose for joining. The supported runtime - // reaches this boundary only after explicit lifecycle, audio, input, and - // SDL shutdown; do not re-enter guest teardown from the host C runtime. + // Guest atexit handlers target workers that cannot be joined. Host + // shutdown is already complete here, so do not re-enter guest teardown. std::cout.flush(); std::cerr.flush(); std::_Exit(EXIT_SUCCESS); diff --git a/src/runtime/command_line.cc b/src/runtime/command_line.cc index 71abdf7..f45af00 100644 --- a/src/runtime/command_line.cc +++ b/src/runtime/command_line.cc @@ -264,8 +264,8 @@ std::string CommandLineUsage(const std::string& program_name) { "and\n" << " uses its native sign-in flow. New credentials are stored " "privately.\n\n" - << "Debug and experimental toggles remain available as MOCKTAIL_* env " - "vars.\n"; + << "Additional runtime options are available as MOCKTAIL_* environment " + "variables.\n"; return usage.str(); } diff --git a/src/runtime/discord_rpc.cc b/src/runtime/discord_rpc.cc index c36c794..fef8e4d 100644 --- a/src/runtime/discord_rpc.cc +++ b/src/runtime/discord_rpc.cc @@ -653,9 +653,7 @@ class DiscordRpcSession::Impl final { phase_ = phase; request_ = {}; if (request != nullptr) { - // Retain only the public join coordinates and private-field presence. - // Access codes and canonical launch JSON never cross into the Discord - // worker, even when public_servers_only is manually disabled. + // Never pass access codes or canonical launch JSON to the Discord worker. request_.place_id = request->place_id; request_.game_instance_id = request->game_instance_id; if (!request->reserved_server_access_code.empty()) { @@ -743,9 +741,7 @@ class DiscordRpcSession::Impl final { place_icon_url = cached.icon_url; if ((cached.name.empty() || cached.icon_url.empty()) && std::chrono::steady_clock::now() >= cached.next_attempt) { - // Never leave Discord showing "Joining" while a Roblox metadata - // request is in flight. Publish the available/fallback activity - // first, then refine it when metadata becomes available. + // Publish fallback activity while metadata is in flight. resolve_metadata_after_publish = true; } } @@ -829,8 +825,7 @@ class DiscordRpcSession::Impl final { continue; } if (changed) { - // Re-enter the normal rate-limited publication path with the cached - // metadata. The fallback activity is already visible during lookup. + // Publish cached metadata through the normal rate limiter. published_revision = 0; continue; } diff --git a/src/runtime/roblox_experience_composition.cc b/src/runtime/roblox_experience_composition.cc index 373378f..b67846c 100644 --- a/src/runtime/roblox_experience_composition.cc +++ b/src/runtime/roblox_experience_composition.cc @@ -33,10 +33,7 @@ namespace runtime { namespace { constexpr size_t kMaxPendingLaunchRequests = 8; -// Loading libroblox contributes a large static-TLS image. A one-megabyte -// pthread stack is accepted in unit tests before the guest is loaded, but -// pthread_create returns EINVAL in production once that TLS image is present. -// Keep this above the runtime's proven 16 MiB guest-thread floor. +// libroblox's static TLS requires the proven 16 MiB guest-thread stack floor. constexpr size_t kLaunchWorkerStackSize = 64ULL * 1024 * 1024; constexpr std::chrono::milliseconds kWebSurfaceReadyTimeout{3000}; constexpr char kRobloxBaseUrl[] = "https://www.roblox.com/"; @@ -512,9 +509,7 @@ Status RobloxExperienceComposition::OpenWebSurface( } } - // Opening and configuring the singleton is one serialized transaction. - // Optional fields are normalized before this point, so presentation state - // from the previous route can never leak into the new page. + // Serialize setup so presentation state cannot leak between routes. if (!current->SetTitle(presentation.title) || !current->SetVisible(presentation.visible) || !current->SetBackNavigationDisabled( @@ -626,9 +621,8 @@ Status RobloxExperienceComposition::DispatchWebViewOpen( presentation.visible = request.is_visible.value_or(true); presentation.show_domain_as_title = request.show_domain_as_title.value_or(false); - // The host helper has no Android toolbar. Mapping a hidden Roblox back - // button to disabled host back navigation preserves the protocol's only - // actionable navigation policy without inventing a replacement header. + // The host helper has no toolbar, so a hidden Roblox back button disables + // host back navigation too. presentation.back_navigation_disabled = request.back_button_visible.has_value() && !*request.back_button_visible; return composition->OpenWebSurface(request.url, "webview", @@ -802,11 +796,8 @@ Status RobloxExperienceComposition::RouteWebSurfaceEvent( RobloxWebViewBridge* web_view_bridge) { switch (event.type) { case WebViewHelperEventType::kExecuteRoblox: - // EnableAndroidWebViewService4 installs WebViewProtocol's listener on - // the shared gi.a fragment regardless of which protocol opened it. The - // MemStorage callback in gi.a is only the APK's disabled legacy - // fallback. Keeping the raw command intact is required for - // RequestGameJob fields such as instanceId and joinAttemptId. + // WebViewProtocol listens on the shared gi.a fragment. Preserve the raw + // command, including RequestGameJob instance and attempt IDs. if (route == WebSurfaceRoute::kNone) { return FailedPrecondition( "WebView helper event has no active logical route"); @@ -842,9 +833,7 @@ Status RobloxExperienceComposition::RouteCurrentWebSurfaceEvent( logical_generation == 0 || logical_generation != web_surface_logical_generation_ || web_surface_route_ == WebSurfaceRoute::kNone) { - // A close, helper replacement, or logical page reuse invalidates every - // event already buffered for the old surface. This is normal teardown, - // not a platform delivery failure. + // Closing or replacing the helper invalidates buffered surface events. return Status::Ok(); } route = web_surface_route_; @@ -954,8 +943,7 @@ Status RobloxExperienceComposition::PromoteAuthenticatedSession() { if (consumes_window_surface_events_) { window::WindowSurfaceEvent stale_event; while (window::PollWindowSurfaceEvent(&stale_event)) { - // Readiness already captured the latest snapshot. Older events were - // queued before a GAME consumer existed and must not replay stale state. + // Readiness already captured the latest snapshot; discard older events. } } vm->SetRobloxExperienceLifecycleCallbacks( @@ -1112,9 +1100,8 @@ Status RobloxExperienceComposition::DrainExternalLaunchRequests() { Status status = DrainActiveExternalLaunchRequests( ExternalLaunchSink{this, &RobloxExperienceComposition::DispatchLaunch}, available); - // The MessageBus can fill the composition queue after the capacity snapshot - // above. Keep the external request at the broker's front and retry on the - // next drain instead of turning ordinary backpressure into runtime failure. + // A MessageBus race can fill the queue; leave the request at the broker's + // front and retry on the next drain. return status.code() == StatusCode::kUnavailable ? Status::Ok() : status; } @@ -1161,8 +1148,7 @@ Status RobloxExperienceComposition::DrainLaunchRequests() { std::lock_guard lock(mutex_); if (lua_app_return_pending_ && !launch_in_progress_ && active_launch_ == nullptr) { - // Consume before invoking the controller so a concurrent callback sets - // a new pending observation instead of being overwritten on return. + // Consume first so a concurrent callback can set a new observation. lua_app_return_pending_ = false; returned_controller = controller_; } @@ -1198,9 +1184,8 @@ Status RobloxExperienceComposition::DrainLaunchRequests() { if (controlled_switch_waiting_for_return_) return Status::Ok(); if (!launch_in_progress_ && active_launch_ == nullptr && game_active_ && !pending_launch_requests_.empty()) { - // Prevent a stale gameDidLeave callback from being applied to the next - // controller. The queued launch starts only after the exact LuaApp - // return callback for this controlled leave has been consumed. + // Start the queued launch only after consuming this leave callback, so + // it cannot reach the next controller. controlled_switch_waiting_for_return_ = true; switch_active_game = true; } diff --git a/src/runtime/roblox_text_input_jni_bridge.h b/src/runtime/roblox_text_input_jni_bridge.h index 6bf09e4..1b878d5 100644 --- a/src/runtime/roblox_text_input_jni_bridge.h +++ b/src/runtime/roblox_text_input_jni_bridge.h @@ -32,9 +32,7 @@ namespace runtime { class RobloxWindowInputRuntime; -// Narrow seam between the cross-thread JNI queue and its main-thread owners. -// Production uses RobloxWindowInputRuntime and the SDL window module. Tests -// provide a deterministic implementation without creating an SDL window. +// Testable boundary between the JNI queue and its main-thread owners. class RobloxTextInputJniBridgeBackend { public: virtual ~RobloxTextInputJniBridgeBackend() = default; @@ -59,19 +57,15 @@ class RobloxTextInputJniBridgeBackend { virtual bool RequestHideTextInput(uint64_t generation) = 0; }; -// Connects pseudo-JVM keyboard callbacks to the input runtime without -// invoking SDL or Roblox native functions from an engine callback thread. -// Guest callbacks enqueue owned commands; the registered window pre-pump -// callback drains them on SDL's main thread. +// Guest callbacks enqueue commands that the SDL pre-pump drains on its main +// thread. class RobloxTextInputJniBridge final { public: static Status Create(jnivm::VM* vm, std::shared_ptr input_runtime, std::unique_ptr* bridge); - // Deterministic construction seam. The backend is retained through every - // in-flight VM callback and must obey the same main-thread contract as the - // production window implementation. + // The test backend remains alive through in-flight VM callbacks. static Status CreateForTesting( jnivm::VM* vm, std::shared_ptr backend, std::unique_ptr* bridge); @@ -81,8 +75,7 @@ class RobloxTextInputJniBridge final { RobloxTextInputJniBridge(const RobloxTextInputJniBridge&) = delete; RobloxTextInputJniBridge& operator=(const RobloxTextInputJniBridge&) = delete; - // Idempotently stops callbacks and clears pending sensitive text before the - // input runtime and SDL window are destroyed. + // Stops callbacks and clears pending sensitive text before window teardown. Status Shutdown(); private: diff --git a/src/window/window.cc b/src/window/window.cc index 8d6a2ec..19d8221 100644 --- a/src/window/window.cc +++ b/src/window/window.cc @@ -12,13 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -// src/window/window.cc — SDL3 window + real EGL context. -// -// Creates a real OS window via SDL3 and obtains a real EGL display + context -// from the selected host EGL/GLES driver (system GLVND or ANGLE/Vulkan). -// libroblox.so's EGL calls are intercepted in libegl_stub and redirected to -// these handles so the game engine renders into a real window. - #include "window/window.h" #include @@ -55,7 +48,7 @@ namespace mocktail { namespace window { -// EGL type declarations (no system EGL headers to avoid conflicts with stubs) +// Local EGL declarations avoid conflicts with the stub headers. using EGLDisplay = void*; using EGLConfig = void*; @@ -98,8 +91,6 @@ static constexpr EGLDisplay EGL_NO_DISPLAY = nullptr; static constexpr EGLContext EGL_NO_CONTEXT = nullptr; static constexpr EGLSurface EGL_NO_SURFACE = nullptr; -// EGL function pointer types - using PFN_eglGetDisplay = EGLDisplay (*)(EGLNativeDisplayType); using PFN_eglInitialize = EGLBoolean (*)(EGLDisplay, EGLint*, EGLint*); using PFN_eglBindAPI = EGLBoolean (*)(EGLenum); @@ -121,8 +112,6 @@ using PFN_eglDestroySurface = EGLBoolean (*)(EGLDisplay, EGLSurface); using PFN_eglTerminate = EGLBoolean (*)(EGLDisplay); using PFN_eglGetProcAddress = __eglMustCastFP (*)(const char*); -// Module state - struct WindowState { SDL_Window* sdl_window = nullptr; EGLDisplay egl_display = nullptr; @@ -551,10 +540,8 @@ void ConfigureGraphicsBackendBeforeSDL() { SDL_SetHint(SDL_HINT_VIDEO_FORCE_EGL, "1"); if (ShouldUseAngleVulkanBackend() || ShouldUseNativeVulkanBackend()) { - // Mesa's implicit device-select layer is useful on desktop, but it can - // crash when ANGLE/Roblox initialise Vulkan through the Android compat - // path. The layer's JSON advertises NODEVICE_SELECT=1 as the supported - // opt-out, so use the real Vulkan loader/ICDs without that implicit layer. + // Mesa's implicit device selector can crash in the Android Vulkan path; + // its supported opt-out is NODEVICE_SELECT=1. setenv("NODEVICE_SELECT", "1", 0); setenv("DISABLE_LAYER_MESA_ANTI_LAG", "1", 0); } @@ -563,8 +550,7 @@ void ConfigureGraphicsBackendBeforeSDL() { ResolveConfiguredVideoDriverChoice(); const char* video_driver = VideoDriverChoiceName(video_driver_choice); if (video_driver != nullptr) { - // ResolveConfiguredVideoDriverChoice already preserves every non-empty - // user override. Overwrite only an inherited empty variable here. + // Preserve any non-empty user override. setenv("SDL_VIDEODRIVER", video_driver, 1); SDL_SetHint(SDL_HINT_VIDEO_DRIVER, video_driver); } @@ -654,9 +640,7 @@ void* QueryNativeWindowHandle() { return wl_egl_win; } - // Direct Vulkan creates VkSurfaceKHR from SDL_Window rather than an EGL - // window. Its SDL object is therefore the stable host-native identity when - // the backend exposes no EGL-specific Wayland handle. + // Direct Vulkan uses SDL_Window as its stable native identity. return g_state.direct_vulkan ? g_state.sdl_window : nullptr; } @@ -865,8 +849,6 @@ bool RetryWithAutoAngleFallback(int width, int height, const char* title, return Init(width, height, title); } -// Init - Status ConfigureWindowStatePersistence(const std::filesystem::path& path) { if (g_state.initialised) { return Status::Error( @@ -893,8 +875,7 @@ bool Init(int width, int height, const char* title) { return false; } - // The same-surface Vulkan overlay renders SDL_EVENT_TEXT_EDITING - // composition text. Candidate lists remain owned by the platform IME. + // The Vulkan overlay renders preedit text; the platform IME owns candidates. SDL_SetHint(SDL_HINT_IME_IMPLEMENTED_UI, "composition"); g_state.state_persistence_active = @@ -967,15 +948,12 @@ bool Init(int width, int height, const char* title) { g_state.direct_vulkan = true; g_state.initialised = true; ResolveNativeWindowHandle(); - // The Android handle is opaque to the guest-facing adapter. Keep it - // non-null even when SDL does not expose an EGL-specific Wayland handle; - // SDL_Vulkan_CreateSurface uses sdl_window directly. + // Direct Vulkan needs a non-null opaque Android handle even without an + // EGL-specific Wayland handle. if (g_state.native_window == nullptr) { g_state.native_window = g_state.sdl_window; } - // Wayland does not configure a surface (and Vulkan reports undefined - // currentExtent) until the window is mapped. Direct Vulkan therefore - // cannot use the EGL-era "show on first swap" policy. + // Wayland reports undefined currentExtent until the window is mapped. if (!SDL_ShowWindow(g_state.sdl_window)) { fprintf(stderr, " [window] SDL_ShowWindow failed: %s\n", SDL_GetError()); SDL_DestroyWindow(g_state.sdl_window); @@ -1062,7 +1040,6 @@ bool Init(int width, int height, const char* title) { width, height, title ? title : "(null)"); } - // ----- SDL3 init ----- if (!SDL_Init(SDL_INIT_VIDEO)) { fprintf(stderr, " [window] SDL_Init failed: %s\n", SDL_GetError()); return false; @@ -1073,10 +1050,9 @@ bool Init(int width, int height, const char* title) { ConfigureGraphicsBackendAfterSDLInit(); - // Keep the hint set in case SDL rechecks it during context creation. + // SDL may recheck this hint during context creation. SDL_SetHint(SDL_HINT_VIDEO_FORCE_EGL, "1"); - // Configure SDL3 GL context for OpenGL ES 3.0 / EGL SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); @@ -1088,7 +1064,6 @@ bool Init(int width, int height, const char* title) { SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); - // Create the window SDL_WindowFlags window_flags = SDL_WINDOW_OPENGL | SDL_WINDOW_HIGH_PIXEL_DENSITY | SDL_WINDOW_RESIZABLE; if (g_state.state_persistence_active && g_state.persisted_window.fullscreen) { @@ -1119,11 +1094,9 @@ bool Init(int width, int height, const char* title) { fprintf(stderr, " [window] SDL3 window created (%dx%d)\n", width, height); ShowWindowAccordingToStartupMode(); - // Create SDL GL/EGL context SDL_GLContext sdl_context = SDL_GL_CreateContext(g_state.sdl_window); if (!sdl_context && IsEnabledEnv("MOCKTAIL_ALLOW_GLES2_RESEARCH")) { - // Current production Roblox client settings reject GLES2. Retain this - // only as an explicit research probe for old payloads. + // Current Roblox clients reject GLES2; this path supports older payloads. SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); sdl_context = SDL_GL_CreateContext(g_state.sdl_window); } @@ -1145,7 +1118,6 @@ bool Init(int width, int height, const char* title) { return CreateSoftwareWaitingWindow(width, height, title); } - // Make the context current to initialize internal structures if (!SDL_GL_MakeCurrent(g_state.sdl_window, sdl_context)) { fprintf(stderr, " [window] SDL_GL_MakeCurrent failed: %s\n", SDL_GetError()); @@ -1198,7 +1170,6 @@ bool Init(int width, int height, const char* title) { return CreateSoftwareWaitingWindow(width, height, title); } - // Query EGL handles from SDL3 g_state.egl_display = SDL_EGL_GetCurrentDisplay(); g_state.egl_config = SDL_EGL_GetCurrentConfig(); g_state.egl_surface = SDL_EGL_GetWindowSurface(g_state.sdl_window); @@ -1237,7 +1208,7 @@ bool Init(int width, int height, const char* title) { ResolveNativeWindowHandle(); - // Unbind context on main thread so that engine thread can bind it + // The render thread binds the context after the main thread releases it. SDL_GL_MakeCurrent(g_state.sdl_window, nullptr); if (WindowTraceEnabled()) { fprintf(stderr, " [window] SDL_GL_MakeCurrent(nullptr) on main thread\n"); @@ -1259,8 +1230,6 @@ bool Init(int width, int height, const char* title) { return true; } -// MakeCurrentOnThread — call from the engine rendering thread to bind the -// EGL context on that thread. bool MakeCurrentOnThread() { if (!g_state.initialised || !g_state.egl_context) { if (WindowTraceEnabled()) { @@ -1299,8 +1268,6 @@ bool ReleaseCurrentOnThread() { return true; } -// Public accessors - bool IsInitialised() { return g_state.initialised; } bool HasPresentedFrame() { return g_real_swap_count.load() > 0; } @@ -1670,9 +1637,7 @@ void MaybeQueueInputReadinessSequence() { SDL_Event focus{}; focus.type = SDL_EVENT_WINDOW_FOCUS_GAINED; focus.window.windowID = window_id; - // A non-interactive readiness runner may not receive compositor focus. Send - // the same focus event before its opt-in click; production runs never - // enter this path unless MOCKTAIL_INPUT_TEST_CLICK is exactly 1. + // An opt-in readiness click may need a synthetic focus event. const bool needs_focus = (SDL_GetWindowFlags(g_state.sdl_window) & SDL_WINDOW_INPUT_FOCUS) == 0; @@ -1759,8 +1724,7 @@ bool RequestFullscreenState(bool fullscreen, const char* reason) { const bool current_fullscreen = (SDL_GetWindowFlags(g_state.sdl_window) & SDL_WINDOW_FULLSCREEN) != 0; if (current_fullscreen != fullscreen) { - // Capture the normal restore rectangle before SDL replaces it with the - // monitor extent. + // Save the restore rectangle before SDL replaces it with monitor bounds. CaptureWindowState(); if (!SDL_SetWindowFullscreen(g_state.sdl_window, fullscreen)) { fprintf(stderr, " [fullscreen] SDL request failed: %s\n", diff --git a/src/window/window.h b/src/window/window.h index d21c662..01202a3 100644 --- a/src/window/window.h +++ b/src/window/window.h @@ -12,12 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -// src/window/window.h — SDL3 window + real EGL context management. -// -// Provides a real OS window and EGL surface that Roblox can render into. -// EGL calls from libroblox.so are intercepted by libegl_stub and forwarded -// to the handles created here via thread-local / global state. - #ifndef MOCKTAIL_WINDOW_WINDOW_H_ #define MOCKTAIL_WINDOW_WINDOW_H_ @@ -38,53 +32,37 @@ namespace window { using PreTextInputPumpCallback = bool (*)(void* context); using FullscreenStateSyncCallback = bool (*)(void* context, bool fullscreen); -// Configures the XDG-backed state record used to restore the last windowed -// geometry, maximized state, and F11 fullscreen state. Must be called before -// Init(); readiness runs suppress persistence internally. +// Configures persistent geometry and fullscreen state before Init(). Status ConfigureWindowStatePersistence(const std::filesystem::path& path); -// Initialises SDL3, creates the main window and the EGL display. -// Must be called before Stage 6 (before nativeAppBridgeV2InitWithParams). -// Returns true on success. bool Init(int width, int height, const char* title); -// Pumps pending SDL events. Call this from a main-loop or keepalive thread. -// Returns false when the user closes the window (quit requested). +// Returns false after the user requests quit. bool PumpEvents(); -// Paces the next main-thread SDL event drain at 240 Hz. Raw mouse/touch events -// remain unsmoothed; this only prevents the host loop from batching them at -// the old 60 Hz cadence. +// Paces SDL polling at 240 Hz without smoothing raw input. void PaceInputPump(); -// Accepts GameActivity.setWindowFlags from any guest JNI thread and queues the -// relevant Android fullscreen state for SDL's next main-thread event pump. -// Returns false when the mask contains no supported fullscreen flag. +// Queues supported GameActivity fullscreen flags for the SDL thread. bool RequestFullscreenFromAndroidWindowFlags(int flags, int mask); -// Recognizes the current Android CoreScript's otherwise-unhandled fullscreen -// menu request and queues one toggle for the SDL thread. +// Converts the Android fullscreen menu request into an SDL-thread toggle. bool RequestFullscreenFromRobloxMenuLog(const char* tag, const char* message); -// Registers the exact-build adapter that mirrors successful SDL fullscreen -// requests into UserGameSettings.InFullScreen(). +// Mirrors SDL fullscreen changes into UserGameSettings.InFullScreen(). bool SetFullscreenStateSyncCallback(FullscreenStateSyncCallback callback, void* context); void ClearFullscreenStateSyncCallback(); -// Returns SDL-derived surface lifetime events from the same polling loop used -// by PumpEvents. The initial mapped surface is generation 1 and is available -// through GetWindowSurfaceSnapshot without a synthetic Created event. +// The initial mapped surface is generation 1 and has no synthetic Created +// event. bool PollWindowSurfaceEvent(WindowSurfaceEvent* event); WindowSurfaceSnapshot GetWindowSurfaceSnapshot(); -// Records a compositor-observed event only after the GAME/JNI bridge has -// committed it. The default-off resize readiness gate uses this to distinguish -// a real rebind from an SDL configure notification that native code rejected. +// Records an event only after the GAME/JNI bridge commits it. Status RecordResizeReadinessSurfaceCommit(const WindowSurfaceEvent& event); -// Completes the opt-in resize gate after Roblox reaches Stopped. Disabled -// production runs return OK without adding any work to the hot path. +// Completes the optional resize gate after Roblox reaches Stopped. Status StopResizeReadiness(); Status ResizeReadinessCompletionStatus(); @@ -100,81 +78,60 @@ struct WindowViewportSnapshot { } }; -// Input uses logical SDL coordinates while the native surface uses pixels. -// Keep both extents explicit so high-DPI touch normalization cannot mix them. +// Keep logical input and pixel surface extents separate on high-DPI displays. WindowViewportSnapshot GetWindowViewportSnapshot(); -// Returns the immutable SDL display snapshot captured on the window thread. platform::DisplayRefreshCapabilities GetDisplayRefreshCapabilities(); -// Registers the sole consumer of converted SDL input/focus events. -// Surface bookkeeping is internal and does not consume this observer slot. +// Registers the sole consumer of converted input and focus events. bool SetPlatformEventObserver(PlatformEventObserver observer, void* context); void ClearPlatformEventObserver(); -// Applies Roblox's exact MouseBehavior.LockCenter state through SDL relative -// mouse mode. The callback is queried only from the SDL main thread. +// Applies MouseBehavior.LockCenter through SDL relative mouse mode. bool SetMouseLockQueryCallback(MouseLockQueryCallback callback, void* context); void ClearMouseLockQueryCallback(); -// Registers a generic main-thread command drain that runs immediately before -// the SDL text-input owner. Clear waits for any invocation already in flight. +// Clear waits for a pre-input callback already in flight. bool SetPreTextInputPumpCallback(PreTextInputPumpCallback callback, void* context); void ClearPreTextInputPumpCallback(); -// Guest text callbacks only enqueue state. SDL's main-thread-only text-input -// APIs are applied by PumpEvents. The owner remains disabled until the // focused-editor bridge explicitly enables it. +// Guest callbacks enqueue state; PumpEvents applies it on SDL's main thread. void SetWindowTextInputOwnerEnabled(bool enabled); bool RequestShowTextInput(uint64_t generation, const TextInputArea& area, const TextInputOptions& options); bool RequestHideTextInput(uint64_t generation); -// Returns the native EGL display handle (EGLDisplay, opaque void*). void* GetEGLDisplay(); -// Returns the native EGL surface handle (EGLSurface, opaque void*) that -// was created from the SDL3 window. This is the surface Roblox renders into. void* GetEGLSurface(); -// Returns the native EGL context handle (EGLContext, opaque void*). void* GetEGLContext(); -// Returns the native EGL config handle (EGLConfig, opaque void*). void* GetEGLConfig(); -// Returns the raw ANativeWindow pointer used as the Android surface. -// This is what ANativeWindow_fromSurface() should return. +// Raw Android surface returned by ANativeWindow_fromSurface(). void* GetNativeWindow(); -// Returns the SDL_Window as an opaque pointer for the Vulkan WSI -// adapter. Android guest code must continue to use GetNativeWindow(). +// Host-only SDL_Window handle for Vulkan WSI. void* GetBackendWindow(); -// True when the window was created with SDL_WINDOW_VULKAN and no EGL context. bool UsesDirectVulkan(); -// Resolves an OpenGL ES function from SDL's currently loaded GL library. -// Returns nullptr before Init() or when no real GL backend is available. +// Returns nullptr before Init() or without a GL backend. void* GetGLProcAddress(const char* name); -// Swaps front and back buffers (calls eglSwapBuffers on the real surface). bool SwapBuffers(); -// Returns true once the engine has presented at least one frame. bool HasPresentedFrame(); -// Records a successful host vkQueuePresentKHR. The Vulkan loader adapter is -// the only production caller; tests must not fabricate readiness with it. +// Records a real vkQueuePresentKHR; tests must not fabricate readiness. void NoteVulkanPresent(); -// Records VK_ERROR_OUT_OF_DATE_KHR without invoking SDL or JNI on the render -// thread. PumpEvents coalesces this evidence into surface rebinds. +// PumpEvents coalesces render-thread out-of-date reports into surface rebinds. void NoteVulkanSurfaceOutOfDate(); -// Registers one synchronous observer for successful host presentation through -// either vkQueuePresentKHR or SDL_GL_SwapWindow. ClearPresentObserver waits for -// an in-flight callback, allowing its context to be destroyed safely. +// ClearPresentObserver waits for an in-flight host-present callback. bool SetPresentObserver(PresentObserver observer, void* context); void ClearPresentObserver(); @@ -193,24 +150,17 @@ class ScopedPresentObserver final { bool registered_ = false; }; -// Shows the SDL window if it was created hidden for startup. void ShowIfHidden(); -// Binds the EGL context to the calling thread. -// Must be called from the engine rendering thread before any GL calls. -// GLdispatch (Mesa) initialises TLS on the first MakeCurrent per thread. +// The render thread must bind EGL before GLdispatch initializes its TLS. bool MakeCurrentOnThread(); -// Releases the EGL context from the calling thread. bool ReleaseCurrentOnThread(); -// Destroys the window and shuts down SDL3. void Shutdown(); -// Returns true if Init() was called successfully. bool IsInitialised(); -// Returns current window dimensions. int GetWidth(); int GetHeight(); diff --git a/stubs/empty.cc b/stubs/empty.cc index 6ba3c80..5b40469 100644 --- a/stubs/empty.cc +++ b/stubs/empty.cc @@ -1,6 +1,5 @@ // Copyright 2026 Mocktail Project Authors // Apache 2.0 License // -// stubs/empty.cc — empty translation unit. -// Used as a placeholder source file for stub .so targets that only need -// to set SONAME and pull in a real system library via DT_NEEDED. +// Placeholder for stub libraries that only set SONAME and add a system +// dependency through DT_NEEDED. diff --git a/stubs/libandroid_stub.cc b/stubs/libandroid_stub.cc index 0e928d7..b629737 100644 --- a/stubs/libandroid_stub.cc +++ b/stubs/libandroid_stub.cc @@ -1,11 +1,8 @@ // Copyright 2026 Mocktail Project Authors // Apache 2.0 License // -// stubs/libandroid_stub.cc — Stub implementations of Android NDK functions. -// -// libroblox.so links against libandroid.so which does not exist on Linux. -// This stub satisfies the dynamic linker so dlopen() can load libroblox.so. -// All functions are no-ops or return safe sentinel values. +// Android NDK compatibility symbols used by libroblox.so on Linux. Asset calls +// can read from an extracted directory or directly from an APK. // // Symbols sourced from Android NDK r28 android/native_activity.h, // android/asset_manager.h, android/looper.h, android/configuration.h. @@ -30,7 +27,7 @@ #include #include -// AAssetManager / AAsset — read-only asset access +// Read-only asset access struct AAssetManager {}; struct AAsset { @@ -260,7 +257,7 @@ bool MocktailUsesDirectVulkan() { extern "C" { AAsset* AAssetManager_open(AAssetManager* mgr, const char* filename, - int /*mode*/) { + int) { if (mgr == nullptr) { return nullptr; } @@ -418,7 +415,7 @@ int AAsset_openFileDescriptor64(AAsset* asset, off_t* outStart, return AAsset_openFileDescriptor(asset, outStart, outLength); } -AAssetManager* AAssetManager_fromJava(void* /*env*/, void* /*assetManager*/) { +AAssetManager* AAssetManager_fromJava(void*, void*) { return &g_asset_manager; } @@ -432,36 +429,36 @@ AConfiguration* AConfiguration_new() { void AConfiguration_delete(AConfiguration* config) { free(config); } -void AConfiguration_fromAssetManager(AConfiguration* /*config*/, - AAssetManager* /*am*/) {} +void AConfiguration_fromAssetManager(AConfiguration*, + AAssetManager*) {} -void AConfiguration_getLanguage(AConfiguration* /*config*/, char* out) { +void AConfiguration_getLanguage(AConfiguration*, char* out) { if (out) { out[0] = 'e'; out[1] = 'n'; } } -void AConfiguration_getCountry(AConfiguration* /*config*/, char* out) { +void AConfiguration_getCountry(AConfiguration*, char* out) { if (out) { out[0] = 'U'; out[1] = 'S'; } } -int32_t AConfiguration_getScreenSize(AConfiguration* /*config*/) { +int32_t AConfiguration_getScreenSize(AConfiguration*) { return 2; // ACONFIGURATION_SCREENSIZE_NORMAL } -int32_t AConfiguration_getScreenWidthDp(AConfiguration* /*config*/) { +int32_t AConfiguration_getScreenWidthDp(AConfiguration*) { return 360; } -int32_t AConfiguration_getScreenHeightDp(AConfiguration* /*config*/) { +int32_t AConfiguration_getScreenHeightDp(AConfiguration*) { return 640; } -int32_t AConfiguration_getNavHidden(AConfiguration* /*config*/) { +int32_t AConfiguration_getNavHidden(AConfiguration*) { return 1; // ACONFIGURATION_NAVHIDDEN_YES } @@ -469,7 +466,7 @@ int32_t AConfiguration_getNavHidden(AConfiguration* /*config*/) { struct ALooper {}; -ALooper* ALooper_prepare(int /*opts*/) { +ALooper* ALooper_prepare(int) { static ALooper kLooper; return &kLooper; } @@ -479,18 +476,18 @@ ALooper* ALooper_forThread() { return &kLooper; } -void ALooper_acquire(ALooper* /*looper*/) {} -void ALooper_release(ALooper* /*looper*/) {} +void ALooper_acquire(ALooper*) {} +void ALooper_release(ALooper*) {} -int ALooper_addFd(ALooper* /*looper*/, int /*fd*/, int /*ident*/, - int /*events*/, void* /*callback*/, void* /*data*/) { +int ALooper_addFd(ALooper*, int, int, + int, void*, void*) { return 1; } -int ALooper_removeFd(ALooper* /*looper*/, int /*fd*/) { return 1; } +int ALooper_removeFd(ALooper*, int) { return 1; } -int ALooper_pollOnce(int /*timeoutMillis*/, int* /*outFd*/, int* /*outEvents*/, - void** /*outData*/) { +int ALooper_pollOnce(int, int*, int*, + void**) { return -2; // ALOOPER_POLL_TIMEOUT } @@ -498,8 +495,8 @@ int ALooper_pollOnce(int /*timeoutMillis*/, int* /*outFd*/, int* /*outEvents*/, struct ANativeWindow {}; -void ANativeWindow_acquire(ANativeWindow* /*window*/) {} -void ANativeWindow_release(ANativeWindow* /*window*/) {} +void ANativeWindow_acquire(ANativeWindow*) {} +void ANativeWindow_release(ANativeWindow*) {} ANativeWindow* ANativeWindow_fromSurface(void* env, void* surface) { void* native_window = MocktailNativeWindow(); diff --git a/stubs/libegl_stub.cc b/stubs/libegl_stub.cc index 6fd0f6f..646f839 100644 --- a/stubs/libegl_stub.cc +++ b/stubs/libegl_stub.cc @@ -220,7 +220,7 @@ void SetError(EGLint error) { extern "C" { -EGLDisplay eglGetDisplay(EGLNativeDisplayType /*display_id*/) { +EGLDisplay eglGetDisplay(EGLNativeDisplayType) { EGLDisplay display = ActiveDisplay(); SetError(display != nullptr ? kEglSuccess : kEglNotInitialized); return display; @@ -250,7 +250,7 @@ EGLBoolean eglBindAPI(EGLenum api) { return kEglTrue; } -EGLBoolean eglChooseConfig(EGLDisplay display, const EGLint* /*attrib_list*/, +EGLBoolean eglChooseConfig(EGLDisplay display, const EGLint*, EGLConfig* configs, EGLint config_size, EGLint* num_config) { if (!IsActiveDisplay(display)) { @@ -292,7 +292,7 @@ EGLBoolean eglGetConfigAttrib(EGLDisplay display, EGLConfig config, EGLSurface eglCreateWindowSurface(EGLDisplay display, EGLConfig config, EGLNativeWindowType native_window, - const EGLint* /*attrib_list*/) { + const EGLint*) { if (!IsActiveDisplay(display) || config == nullptr || config != ActiveConfig()) { SetError(kEglBadDisplay); @@ -314,7 +314,7 @@ EGLSurface eglCreateWindowSurface(EGLDisplay display, EGLConfig config, } EGLSurface eglCreatePbufferSurface(EGLDisplay display, EGLConfig config, - const EGLint* /*attrib_list*/) { + const EGLint*) { if (!IsActiveDisplay(display) || config == nullptr || config != ActiveConfig()) { SetError(kEglBadDisplay); @@ -329,8 +329,8 @@ EGLSurface eglCreatePbufferSurface(EGLDisplay display, EGLConfig config, } EGLContext eglCreateContext(EGLDisplay display, EGLConfig config, - EGLContext /*share_context*/, - const EGLint* /*attrib_list*/) { + EGLContext, + const EGLint*) { if (!IsActiveDisplay(display) || config == nullptr || config != ActiveConfig()) { SetError(kEglBadDisplay); @@ -427,7 +427,7 @@ EGLBoolean eglSwapBuffers(EGLDisplay display, EGLSurface surface) { return swapped ? kEglTrue : kEglFalse; } -EGLBoolean eglSwapInterval(EGLDisplay display, EGLint /*interval*/) { +EGLBoolean eglSwapInterval(EGLDisplay display, EGLint) { if (!IsActiveDisplay(display) || g_current_context == nullptr) { SetError(kEglBadDisplay); return kEglFalse; diff --git a/stubs/libgles_stub.cc b/stubs/libgles_stub.cc index 6462c97..eb8e6dc 100644 --- a/stubs/libgles_stub.cc +++ b/stubs/libgles_stub.cc @@ -176,10 +176,10 @@ void glGenRenderbuffers(GLsizei n, GLuint* renderbuffers) { FillObjects(n, renderbuffers); } -GLuint glCreateShader(GLenum /*type*/) { return NextObject(); } +GLuint glCreateShader(GLenum) { return NextObject(); } GLuint glCreateProgram() { return NextObject(); } -void glGetShaderiv(GLuint /*shader*/, GLenum pname, GLint* params) { +void glGetShaderiv(GLuint, GLenum pname, GLint* params) { if (params == nullptr) { return; } @@ -196,7 +196,7 @@ void glGetShaderiv(GLuint /*shader*/, GLenum pname, GLint* params) { } } -void glGetProgramiv(GLuint /*program*/, GLenum pname, GLint* params) { +void glGetProgramiv(GLuint, GLenum pname, GLint* params) { if (params == nullptr) { return; } @@ -213,7 +213,7 @@ void glGetProgramiv(GLuint /*program*/, GLenum pname, GLint* params) { } } -void glGetShaderInfoLog(GLuint /*shader*/, GLsizei buf_size, GLsizei* length, +void glGetShaderInfoLog(GLuint, GLsizei buf_size, GLsizei* length, GLchar* info_log) { if (!RequireTestGraphicsStub()) { CopyString("real GLES backend unavailable", buf_size, length, info_log); @@ -222,7 +222,7 @@ void glGetShaderInfoLog(GLuint /*shader*/, GLsizei buf_size, GLsizei* length, CopyString("", buf_size, length, info_log); } -void glGetProgramInfoLog(GLuint /*program*/, GLsizei buf_size, GLsizei* length, +void glGetProgramInfoLog(GLuint, GLsizei buf_size, GLsizei* length, GLchar* info_log) { if (!RequireTestGraphicsStub()) { CopyString("real GLES backend unavailable", buf_size, length, info_log); @@ -231,7 +231,7 @@ void glGetProgramInfoLog(GLuint /*program*/, GLsizei buf_size, GLsizei* length, CopyString("", buf_size, length, info_log); } -void glGetActiveUniform(GLuint /*program*/, GLuint /*index*/, GLsizei buf_size, +void glGetActiveUniform(GLuint, GLuint, GLsizei buf_size, GLsizei* length, GLint* size, GLenum* type, GLchar* name) { if (!RequireTestGraphicsStub()) { @@ -253,11 +253,11 @@ void glGetActiveUniform(GLuint /*program*/, GLuint /*index*/, GLsizei buf_size, CopyString("", buf_size, length, name); } -GLint glGetUniformLocation(GLuint /*program*/, const GLchar* /*name*/) { +GLint glGetUniformLocation(GLuint, const GLchar*) { return RequireTestGraphicsStub() ? 0 : -1; } -GLenum glCheckFramebufferStatus(GLenum /*target*/) { +GLenum glCheckFramebufferStatus(GLenum) { return RequireTestGraphicsStub() ? kGlFramebufferComplete : kGlFramebufferUnsupported; } @@ -272,119 +272,119 @@ void glViewport(GLint x, GLint y, GLsizei width, GLsizei height) { g_viewport[3] = height; } -void glActiveTexture(GLenum /*texture*/) { RequireTestGraphicsStub(); } -void glAttachShader(GLuint /*program*/, GLuint /*shader*/) { +void glActiveTexture(GLenum) { RequireTestGraphicsStub(); } +void glAttachShader(GLuint, GLuint) { RequireTestGraphicsStub(); } -void glBindAttribLocation(GLuint /*program*/, GLuint /*index*/, - const GLchar* /*name*/) { +void glBindAttribLocation(GLuint, GLuint, + const GLchar*) { RequireTestGraphicsStub(); } -void glBindBuffer(GLenum /*target*/, GLuint /*buffer*/) { +void glBindBuffer(GLenum, GLuint) { RequireTestGraphicsStub(); } -void glBindFramebuffer(GLenum /*target*/, GLuint /*framebuffer*/) { +void glBindFramebuffer(GLenum, GLuint) { RequireTestGraphicsStub(); } -void glBindRenderbuffer(GLenum /*target*/, GLuint /*renderbuffer*/) { +void glBindRenderbuffer(GLenum, GLuint) { RequireTestGraphicsStub(); } -void glBindTexture(GLenum /*target*/, GLuint /*texture*/) { +void glBindTexture(GLenum, GLuint) { RequireTestGraphicsStub(); } -void glBlendFunc(GLenum /*sfactor*/, GLenum /*dfactor*/) { +void glBlendFunc(GLenum, GLenum) { RequireTestGraphicsStub(); } -void glBlendFuncSeparate(GLenum /*src_rgb*/, GLenum /*dst_rgb*/, - GLenum /*src_alpha*/, GLenum /*dst_alpha*/) { +void glBlendFuncSeparate(GLenum, GLenum, + GLenum, GLenum) { RequireTestGraphicsStub(); } -void glBufferData(GLenum /*target*/, GLsizeiptr /*size*/, const void* /*data*/, - GLenum /*usage*/) { +void glBufferData(GLenum, GLsizeiptr, const void*, + GLenum) { RequireTestGraphicsStub(); } -void glBufferSubData(GLenum /*target*/, GLintptr /*offset*/, - GLsizeiptr /*size*/, const void* /*data*/) { +void glBufferSubData(GLenum, GLintptr, + GLsizeiptr, const void*) { RequireTestGraphicsStub(); } -void glClear(GLbitfield /*mask*/) { RequireTestGraphicsStub(); } -void glClearColor(GLfloat /*red*/, GLfloat /*green*/, GLfloat /*blue*/, - GLfloat /*alpha*/) { +void glClear(GLbitfield) { RequireTestGraphicsStub(); } +void glClearColor(GLfloat, GLfloat, GLfloat, + GLfloat) { RequireTestGraphicsStub(); } -void glClearDepthf(GLfloat /*depth*/) { RequireTestGraphicsStub(); } -void glClearStencil(GLint /*s*/) { RequireTestGraphicsStub(); } -void glColorMask(GLboolean /*red*/, GLboolean /*green*/, GLboolean /*blue*/, - GLboolean /*alpha*/) { +void glClearDepthf(GLfloat) { RequireTestGraphicsStub(); } +void glClearStencil(GLint) { RequireTestGraphicsStub(); } +void glColorMask(GLboolean, GLboolean, GLboolean, + GLboolean) { RequireTestGraphicsStub(); } -void glCompileShader(GLuint /*shader*/) { RequireTestGraphicsStub(); } -void glCompressedTexImage2D(GLenum /*target*/, GLint /*level*/, - GLenum /*internalformat*/, GLsizei /*width*/, - GLsizei /*height*/, GLint /*border*/, - GLsizei /*image_size*/, const void* /*data*/) { +void glCompileShader(GLuint) { RequireTestGraphicsStub(); } +void glCompressedTexImage2D(GLenum, GLint, + GLenum, GLsizei, + GLsizei, GLint, + GLsizei, const void*) { RequireTestGraphicsStub(); } -void glCompressedTexSubImage2D(GLenum /*target*/, GLint /*level*/, - GLint /*xoffset*/, GLint /*yoffset*/, - GLsizei /*width*/, GLsizei /*height*/, - GLenum /*format*/, GLsizei /*image_size*/, - const void* /*data*/) { +void glCompressedTexSubImage2D(GLenum, GLint, + GLint, GLint, + GLsizei, GLsizei, + GLenum, GLsizei, + const void*) { RequireTestGraphicsStub(); } -void glCopyTexSubImage2D(GLenum /*target*/, GLint /*level*/, GLint /*xoffset*/, - GLint /*yoffset*/, GLint /*x*/, GLint /*y*/, - GLsizei /*width*/, GLsizei /*height*/) { +void glCopyTexSubImage2D(GLenum, GLint, GLint, + GLint, GLint, GLint, + GLsizei, GLsizei) { RequireTestGraphicsStub(); } -void glCullFace(GLenum /*mode*/) { RequireTestGraphicsStub(); } -void glDeleteBuffers(GLsizei /*n*/, const GLuint* /*buffers*/) { +void glCullFace(GLenum) { RequireTestGraphicsStub(); } +void glDeleteBuffers(GLsizei, const GLuint*) { RequireTestGraphicsStub(); } -void glDeleteFramebuffers(GLsizei /*n*/, const GLuint* /*framebuffers*/) { +void glDeleteFramebuffers(GLsizei, const GLuint*) { RequireTestGraphicsStub(); } -void glDeleteProgram(GLuint /*program*/) { RequireTestGraphicsStub(); } -void glDeleteRenderbuffers(GLsizei /*n*/, const GLuint* /*renderbuffers*/) { +void glDeleteProgram(GLuint) { RequireTestGraphicsStub(); } +void glDeleteRenderbuffers(GLsizei, const GLuint*) { RequireTestGraphicsStub(); } -void glDeleteShader(GLuint /*shader*/) { RequireTestGraphicsStub(); } -void glDeleteTextures(GLsizei /*n*/, const GLuint* /*textures*/) { +void glDeleteShader(GLuint) { RequireTestGraphicsStub(); } +void glDeleteTextures(GLsizei, const GLuint*) { RequireTestGraphicsStub(); } -void glDepthFunc(GLenum /*func*/) { RequireTestGraphicsStub(); } -void glDepthMask(GLboolean /*flag*/) { RequireTestGraphicsStub(); } -void glDisable(GLenum /*cap*/) { RequireTestGraphicsStub(); } -void glDisableVertexAttribArray(GLuint /*index*/) { RequireTestGraphicsStub(); } -void glDrawArrays(GLenum /*mode*/, GLint /*first*/, GLsizei /*count*/) { +void glDepthFunc(GLenum) { RequireTestGraphicsStub(); } +void glDepthMask(GLboolean) { RequireTestGraphicsStub(); } +void glDisable(GLenum) { RequireTestGraphicsStub(); } +void glDisableVertexAttribArray(GLuint) { RequireTestGraphicsStub(); } +void glDrawArrays(GLenum, GLint, GLsizei) { RequireTestGraphicsStub(); } -void glDrawElements(GLenum /*mode*/, GLsizei /*count*/, GLenum /*type*/, - const void* /*indices*/) { +void glDrawElements(GLenum, GLsizei, GLenum, + const void*) { RequireTestGraphicsStub(); } -void glEnable(GLenum /*cap*/) { RequireTestGraphicsStub(); } -void glEnableVertexAttribArray(GLuint /*index*/) { RequireTestGraphicsStub(); } -void glFramebufferRenderbuffer(GLenum /*target*/, GLenum /*attachment*/, - GLenum /*renderbuffertarget*/, - GLuint /*renderbuffer*/) { +void glEnable(GLenum) { RequireTestGraphicsStub(); } +void glEnableVertexAttribArray(GLuint) { RequireTestGraphicsStub(); } +void glFramebufferRenderbuffer(GLenum, GLenum, + GLenum, + GLuint) { RequireTestGraphicsStub(); } -void glFramebufferTexture2D(GLenum /*target*/, GLenum /*attachment*/, - GLenum /*textarget*/, GLuint /*texture*/, - GLint /*level*/) { +void glFramebufferTexture2D(GLenum, GLenum, + GLenum, GLuint, + GLint) { RequireTestGraphicsStub(); } -void glGenerateMipmap(GLenum /*target*/) { RequireTestGraphicsStub(); } -void glLinkProgram(GLuint /*program*/) { RequireTestGraphicsStub(); } -void glPixelStorei(GLenum /*pname*/, GLint /*param*/) { +void glGenerateMipmap(GLenum) { RequireTestGraphicsStub(); } +void glLinkProgram(GLuint) { RequireTestGraphicsStub(); } +void glPixelStorei(GLenum, GLint) { RequireTestGraphicsStub(); } -void glPolygonOffset(GLfloat /*factor*/, GLfloat /*units*/) { +void glPolygonOffset(GLfloat, GLfloat) { RequireTestGraphicsStub(); } -void glReadPixels(GLint /*x*/, GLint /*y*/, GLsizei width, GLsizei height, - GLenum /*format*/, GLenum /*type*/, void* pixels) { +void glReadPixels(GLint, GLint, GLsizei width, GLsizei height, + GLenum, GLenum, void* pixels) { if (!RequireTestGraphicsStub()) { return; } @@ -394,53 +394,53 @@ void glReadPixels(GLint /*x*/, GLint /*y*/, GLsizei width, GLsizei height, } } void glReleaseShaderCompiler() { RequireTestGraphicsStub(); } -void glRenderbufferStorage(GLenum /*target*/, GLenum /*internalformat*/, - GLsizei /*width*/, GLsizei /*height*/) { +void glRenderbufferStorage(GLenum, GLenum, + GLsizei, GLsizei) { RequireTestGraphicsStub(); } -void glScissor(GLint /*x*/, GLint /*y*/, GLsizei /*width*/, - GLsizei /*height*/) { +void glScissor(GLint, GLint, GLsizei, + GLsizei) { RequireTestGraphicsStub(); } -void glShaderSource(GLuint /*shader*/, GLsizei /*count*/, - const GLchar* const* /*string*/, const GLint* /*length*/) { +void glShaderSource(GLuint, GLsizei, + const GLchar* const*, const GLint*) { RequireTestGraphicsStub(); } -void glStencilFunc(GLenum /*func*/, GLint /*ref*/, GLuint /*mask*/) { +void glStencilFunc(GLenum, GLint, GLuint) { RequireTestGraphicsStub(); } -void glStencilMask(GLuint /*mask*/) { RequireTestGraphicsStub(); } -void glStencilOp(GLenum /*fail*/, GLenum /*zfail*/, GLenum /*zpass*/) { +void glStencilMask(GLuint) { RequireTestGraphicsStub(); } +void glStencilOp(GLenum, GLenum, GLenum) { RequireTestGraphicsStub(); } -void glTexImage2D(GLenum /*target*/, GLint /*level*/, GLint /*internalformat*/, - GLsizei /*width*/, GLsizei /*height*/, GLint /*border*/, - GLenum /*format*/, GLenum /*type*/, const void* /*pixels*/) { +void glTexImage2D(GLenum, GLint, GLint, + GLsizei, GLsizei, GLint, + GLenum, GLenum, const void*) { RequireTestGraphicsStub(); } -void glTexParameterf(GLenum /*target*/, GLenum /*pname*/, GLfloat /*param*/) { +void glTexParameterf(GLenum, GLenum, GLfloat) { RequireTestGraphicsStub(); } -void glTexParameterfv(GLenum /*target*/, GLenum /*pname*/, - const GLfloat* /*params*/) { +void glTexParameterfv(GLenum, GLenum, + const GLfloat*) { RequireTestGraphicsStub(); } -void glTexParameteri(GLenum /*target*/, GLenum /*pname*/, GLint /*param*/) { +void glTexParameteri(GLenum, GLenum, GLint) { RequireTestGraphicsStub(); } -void glTexSubImage2D(GLenum /*target*/, GLint /*level*/, GLint /*xoffset*/, - GLint /*yoffset*/, GLsizei /*width*/, GLsizei /*height*/, - GLenum /*format*/, GLenum /*type*/, - const void* /*pixels*/) { +void glTexSubImage2D(GLenum, GLint, GLint, + GLint, GLsizei, GLsizei, + GLenum, GLenum, + const void*) { RequireTestGraphicsStub(); } -void glUniform1i(GLint /*location*/, GLint /*v0*/) { +void glUniform1i(GLint, GLint) { RequireTestGraphicsStub(); } -void glUseProgram(GLuint /*program*/) { RequireTestGraphicsStub(); } -void glVertexAttribPointer(GLuint /*index*/, GLint /*size*/, GLenum /*type*/, - GLboolean /*normalized*/, GLsizei /*stride*/, - const void* /*pointer*/) { +void glUseProgram(GLuint) { RequireTestGraphicsStub(); } +void glVertexAttribPointer(GLuint, GLint, GLenum, + GLboolean, GLsizei, + const void*) { RequireTestGraphicsStub(); } diff --git a/stubs/liblog_stub.cc b/stubs/liblog_stub.cc index 9c55739..9a4724f 100644 --- a/stubs/liblog_stub.cc +++ b/stubs/liblog_stub.cc @@ -1,8 +1,7 @@ // Copyright 2026 Mocktail Project Authors // Apache 2.0 License // -// stubs/liblog_stub.cc — Stub for Android liblog.so. -// Redirects __android_log_print et al. to stderr on the host. +// Redirects Android logging calls to stderr on the host. #include #include @@ -95,7 +94,7 @@ void __android_log_assert(const char* cond, const char* tag, __builtin_trap(); } -int __android_log_buf_write(int /*bufID*/, int prio, const char* tag, +int __android_log_buf_write(int, int prio, const char* tag, const char* text) { return __android_log_write(prio, tag, text); } diff --git a/stubs/libmediandk_stub.cc b/stubs/libmediandk_stub.cc index 0f4f557..09504e6 100644 --- a/stubs/libmediandk_stub.cc +++ b/stubs/libmediandk_stub.cc @@ -1,8 +1,7 @@ // Copyright 2026 Mocktail Project Authors // Apache 2.0 License // -// stubs/libmediandk_stub.cc — Stub for Android libmediandk.so. -// AMediaCodec / AMediaFormat are no-ops; Roblox uses them for video decoding. +// Minimal libmediandk shim for video APIs imported by Roblox. #include #include @@ -14,7 +13,6 @@ struct AMediaFormat { char mime[64]; }; -// Forward-declared Android media status type. using media_status_t = int32_t; static constexpr media_status_t AMEDIA_OK = 0; static constexpr media_status_t AMEDIA_ERROR_UNKNOWN = -1; @@ -33,71 +31,69 @@ const char* AMEDIAFORMAT_KEY_SAMPLE_RATE = "sample-rate"; const char* AMEDIAFORMAT_KEY_I_FRAME_INTERVAL = "i-frame-interval"; const char* AMEDIAFORMAT_KEY_STRIDE = "stride"; -// AMediaFormat AMediaFormat* AMediaFormat_new() { return static_cast(calloc(1, sizeof(AMediaFormat))); } void AMediaFormat_delete(AMediaFormat* fmt) { free(fmt); } -bool AMediaFormat_getInt32(AMediaFormat* /*f*/, const char* /*k*/, int32_t* v) { +bool AMediaFormat_getInt32(AMediaFormat*, const char*, int32_t* v) { if (v) *v = 0; return false; } -bool AMediaFormat_getBuffer(AMediaFormat* /*f*/, const char* /*k*/, - void** /*d*/, size_t* /*s*/) { return false; } -void AMediaFormat_setInt32(AMediaFormat* /*f*/, const char* /*k*/, - int32_t /*v*/) {} -void AMediaFormat_setFloat(AMediaFormat* /*f*/, const char* /*k*/, - float /*v*/) {} -void AMediaFormat_setString(AMediaFormat* /*f*/, const char* k, +bool AMediaFormat_getBuffer(AMediaFormat*, const char*, + void**, size_t*) { return false; } +void AMediaFormat_setInt32(AMediaFormat*, const char*, + int32_t) {} +void AMediaFormat_setFloat(AMediaFormat*, const char*, + float) {} +void AMediaFormat_setString(AMediaFormat*, const char* k, const char* v) { (void)k; (void)v; } -void AMediaFormat_setBuffer(AMediaFormat* /*f*/, const char* /*k*/, - const void* /*d*/, size_t /*s*/) {} -const char* AMediaFormat_toString(AMediaFormat* /*f*/) { return "{}"; } +void AMediaFormat_setBuffer(AMediaFormat*, const char*, + const void*, size_t) {} +const char* AMediaFormat_toString(AMediaFormat*) { return "{}"; } -// AMediaCodec -AMediaCodec* AMediaCodec_createDecoderByType(const char* /*mime*/) { +AMediaCodec* AMediaCodec_createDecoderByType(const char*) { return nullptr; } -AMediaCodec* AMediaCodec_createEncoderByType(const char* /*mime*/) { +AMediaCodec* AMediaCodec_createEncoderByType(const char*) { return nullptr; } -media_status_t AMediaCodec_delete(AMediaCodec* /*codec*/) { +media_status_t AMediaCodec_delete(AMediaCodec*) { return AMEDIA_OK; } -media_status_t AMediaCodec_configure(AMediaCodec* /*codec*/, - const AMediaFormat* /*format*/, - void* /*surface*/, void* /*crypto*/, - uint32_t /*flags*/) { +media_status_t AMediaCodec_configure(AMediaCodec*, + const AMediaFormat*, + void*, void*, + uint32_t) { return AMEDIA_ERROR_UNKNOWN; } -media_status_t AMediaCodec_start(AMediaCodec* /*codec*/) { +media_status_t AMediaCodec_start(AMediaCodec*) { return AMEDIA_ERROR_UNKNOWN; } -media_status_t AMediaCodec_stop(AMediaCodec* /*codec*/) { return AMEDIA_OK; } -media_status_t AMediaCodec_flush(AMediaCodec* /*codec*/) { return AMEDIA_OK; } -uint8_t* AMediaCodec_getInputBuffer(AMediaCodec* /*codec*/, size_t /*idx*/, - size_t* /*sz*/) { return nullptr; } -uint8_t* AMediaCodec_getOutputBuffer(AMediaCodec* /*codec*/, size_t /*idx*/, - size_t* /*sz*/) { return nullptr; } -ssize_t AMediaCodec_dequeueInputBuffer(AMediaCodec* /*codec*/, - int64_t /*timeoutUs*/) { return -1; } -ssize_t AMediaCodec_dequeueOutputBuffer(AMediaCodec* /*codec*/, - void* /*info*/, - int64_t /*timeoutUs*/) { return -1; } -media_status_t AMediaCodec_queueInputBuffer(AMediaCodec* /*codec*/, - size_t /*idx*/, size_t /*offset*/, - size_t /*size*/, uint64_t /*pts*/, - uint32_t /*flags*/) { +media_status_t AMediaCodec_stop(AMediaCodec*) { return AMEDIA_OK; } +media_status_t AMediaCodec_flush(AMediaCodec*) { return AMEDIA_OK; } +uint8_t* AMediaCodec_getInputBuffer(AMediaCodec*, size_t, + size_t*) { return nullptr; } +uint8_t* AMediaCodec_getOutputBuffer(AMediaCodec*, size_t, + size_t*) { return nullptr; } +ssize_t AMediaCodec_dequeueInputBuffer(AMediaCodec*, + int64_t) { return -1; } +ssize_t AMediaCodec_dequeueOutputBuffer(AMediaCodec*, + void*, + int64_t) { return -1; } +media_status_t AMediaCodec_queueInputBuffer(AMediaCodec*, + size_t, size_t, + size_t, uint64_t, + uint32_t) { return AMEDIA_ERROR_UNKNOWN; } -media_status_t AMediaCodec_releaseOutputBuffer(AMediaCodec* /*codec*/, - size_t /*idx*/, - bool /*render*/) { +media_status_t AMediaCodec_releaseOutputBuffer(AMediaCodec*, + size_t, + bool) { return AMEDIA_OK; } -AMediaFormat* AMediaCodec_getOutputFormat(AMediaCodec* /*codec*/) { +AMediaFormat* AMediaCodec_getOutputFormat(AMediaCodec*) { return AMediaFormat_new(); } diff --git a/stubs/libopensl_stub.cc b/stubs/libopensl_stub.cc index ac209bb..c660d7f 100644 --- a/stubs/libopensl_stub.cc +++ b/stubs/libopensl_stub.cc @@ -1,12 +1,8 @@ // Copyright 2026 Mocktail Project Authors // Apache 2.0 License // -// stubs/libopensl_stub.cc — test fixture for libOpenSLES.so/libOpenMAXAL.so. -// -// Production audio must use the SDL-backed Mocktail audio foundation. This -// synthetic object graph is available only when the exact opt-in -// MOCKTAIL_ENABLE_TEST_AUDIO_STUBS=1 is present; otherwise engine creation -// fails closed. Audio capture is unsupported in both modes. +// Test fixture for libOpenSLES.so and libOpenMAXAL.so. Engine creation requires +// MOCKTAIL_ENABLE_TEST_AUDIO_STUBS=1; audio capture is not implemented. #include #include @@ -239,37 +235,37 @@ SLresult SuccessObject(SLObjectItf* object, SLObjectItf fake_object) { return SL_RESULT_SUCCESS; } -void ObjectDestroy(SLObjectItf /*self*/) {} +void ObjectDestroy(SLObjectItf) {} -SLresult ObjectRealize(SLObjectItf /*self*/, SLboolean /*async*/) { +SLresult ObjectRealize(SLObjectItf, SLboolean) { return SL_RESULT_SUCCESS; } -SLresult ObjectResume(SLObjectItf /*self*/, SLboolean /*async*/) { +SLresult ObjectResume(SLObjectItf, SLboolean) { return SL_RESULT_SUCCESS; } -SLresult ObjectGetState(SLObjectItf /*self*/, SLuint32* state) { +SLresult ObjectGetState(SLObjectItf, SLuint32* state) { if (state) { *state = SL_OBJECT_STATE_REALIZED; } return SL_RESULT_SUCCESS; } -SLresult ObjectRegisterCallback(SLObjectItf /*self*/, - SLObjectCallback /*callback*/, - void* /*context*/) { +SLresult ObjectRegisterCallback(SLObjectItf, + SLObjectCallback, + void*) { return SL_RESULT_SUCCESS; } -void ObjectAbortAsyncOperation(SLObjectItf /*self*/) {} +void ObjectAbortAsyncOperation(SLObjectItf) {} -SLresult ObjectSetPriority(SLObjectItf /*self*/, SLint32 /*priority*/, - SLboolean /*preemptable*/) { +SLresult ObjectSetPriority(SLObjectItf, SLint32, + SLboolean) { return SL_RESULT_SUCCESS; } -SLresult ObjectGetPriority(SLObjectItf /*self*/, SLint32* priority, +SLresult ObjectGetPriority(SLObjectItf, SLint32* priority, SLboolean* preemptable) { if (priority) { *priority = 0; @@ -280,20 +276,20 @@ SLresult ObjectGetPriority(SLObjectItf /*self*/, SLint32* priority, return SL_RESULT_SUCCESS; } -SLresult ObjectSetLossOfControlInterfaces(SLObjectItf /*self*/, - SLuint32 /*num_interfaces*/, - const SLInterfaceID* /*ids*/, - SLboolean /*enabled*/) { +SLresult ObjectSetLossOfControlInterfaces(SLObjectItf, + SLuint32, + const SLInterfaceID*, + SLboolean) { return SL_RESULT_SUCCESS; } SLresult ObjectGetInterface(SLObjectItf self, SLInterfaceID iid, void* interface_out); -SLresult CreateUnsupportedObject(SLEngineItf /*self*/, SLObjectItf* object, - SLuint32 /*device_id*/, - SLuint32 /*num_interfaces*/, - const SLInterfaceID* /*ids*/, - const SLboolean* /*required*/); +SLresult CreateUnsupportedObject(SLEngineItf, SLObjectItf* object, + SLuint32, + SLuint32, + const SLInterfaceID*, + const SLboolean*); SLresult CreateAudioPlayer(SLEngineItf self, SLObjectItf* player, const void* source, const void* sink, SLuint32 num_interfaces, const SLInterfaceID* ids, @@ -306,46 +302,46 @@ SLresult CreateOutputMix(SLEngineItf self, SLObjectItf* mix, SLuint32 num_interfaces, const SLInterfaceID* ids, const SLboolean* required); -SLresult CreateMidiPlayer(SLEngineItf /*self*/, SLObjectItf* player, - const void* /*source*/, const void* /*sink*/, - const void* /*bank_source*/, - const void* /*bank_sink*/, - SLuint32 /*num_interfaces*/, - const SLInterfaceID* /*ids*/, - const SLboolean* /*required*/); -SLresult CreateListener(SLEngineItf /*self*/, SLObjectItf* listener, - SLuint32 /*num_interfaces*/, - const SLInterfaceID* /*ids*/, - const SLboolean* /*required*/); -SLresult Create3DGroup(SLEngineItf /*self*/, SLObjectItf* group, - SLuint32 /*num_interfaces*/, const SLInterfaceID* /*ids*/, - const SLboolean* /*required*/); -SLresult CreateMetadataExtractor(SLEngineItf /*self*/, SLObjectItf* extractor, - const void* /*source*/, - SLuint32 /*num_interfaces*/, - const SLInterfaceID* /*ids*/, - const SLboolean* /*required*/); -SLresult CreateExtensionObject(SLEngineItf /*self*/, SLObjectItf* object, - void* /*parameters*/, SLuint32 /*object_id*/, - SLuint32 /*num_interfaces*/, - const SLInterfaceID* /*ids*/, - const SLboolean* /*required*/); -SLresult QueryNumSupportedInterfaces(SLEngineItf /*self*/, - SLuint32 /*object_id*/, SLuint32* count); -SLresult QuerySupportedInterfaces(SLEngineItf /*self*/, SLuint32 /*object_id*/, - SLuint32 /*index*/, SLInterfaceID* iid); -SLresult QueryNumSupportedExtensions(SLEngineItf /*self*/, SLuint32* count); -SLresult QuerySupportedExtension(SLEngineItf /*self*/, SLuint32 /*index*/, +SLresult CreateMidiPlayer(SLEngineItf, SLObjectItf* player, + const void*, const void*, + const void*, + const void*, + SLuint32, + const SLInterfaceID*, + const SLboolean*); +SLresult CreateListener(SLEngineItf, SLObjectItf* listener, + SLuint32, + const SLInterfaceID*, + const SLboolean*); +SLresult Create3DGroup(SLEngineItf, SLObjectItf* group, + SLuint32, const SLInterfaceID*, + const SLboolean*); +SLresult CreateMetadataExtractor(SLEngineItf, SLObjectItf* extractor, + const void*, + SLuint32, + const SLInterfaceID*, + const SLboolean*); +SLresult CreateExtensionObject(SLEngineItf, SLObjectItf* object, + void*, SLuint32, + SLuint32, + const SLInterfaceID*, + const SLboolean*); +SLresult QueryNumSupportedInterfaces(SLEngineItf, + SLuint32, SLuint32* count); +SLresult QuerySupportedInterfaces(SLEngineItf, SLuint32, + SLuint32, SLInterfaceID* iid); +SLresult QueryNumSupportedExtensions(SLEngineItf, SLuint32* count); +SLresult QuerySupportedExtension(SLEngineItf, SLuint32, char* name, SLint32* name_length); -SLresult IsExtensionSupported(SLEngineItf /*self*/, const char* /*name*/, +SLresult IsExtensionSupported(SLEngineItf, const char*, SLboolean* supported); -SLresult PlaySetState(SLPlayItf /*self*/, SLuint32 state) { +SLresult PlaySetState(SLPlayItf, SLuint32 state) { g_play_state = state; return SL_RESULT_SUCCESS; } -SLresult PlayGetState(SLPlayItf /*self*/, SLuint32* state) { +SLresult PlayGetState(SLPlayItf, SLuint32* state) { if (state) { *state = g_play_state; } @@ -359,115 +355,115 @@ SLresult ReturnZeroU32(SLuint32* value) { return SL_RESULT_SUCCESS; } -SLresult PlayGetDuration(SLPlayItf /*self*/, SLuint32* duration) { +SLresult PlayGetDuration(SLPlayItf, SLuint32* duration) { return ReturnZeroU32(duration); } -SLresult PlayGetPosition(SLPlayItf /*self*/, SLuint32* position) { +SLresult PlayGetPosition(SLPlayItf, SLuint32* position) { return ReturnZeroU32(position); } -SLresult PlayRegisterCallback(SLPlayItf /*self*/, SLPlayCallback /*callback*/, - void* /*context*/) { +SLresult PlayRegisterCallback(SLPlayItf, SLPlayCallback, + void*) { return SL_RESULT_SUCCESS; } -SLresult PlaySetEventsMask(SLPlayItf /*self*/, SLuint32 /*mask*/) { +SLresult PlaySetEventsMask(SLPlayItf, SLuint32) { return SL_RESULT_SUCCESS; } -SLresult PlayGetEventsMask(SLPlayItf /*self*/, SLuint32* mask) { +SLresult PlayGetEventsMask(SLPlayItf, SLuint32* mask) { return ReturnZeroU32(mask); } -SLresult PlaySetMarkerPosition(SLPlayItf /*self*/, SLuint32 /*position*/) { +SLresult PlaySetMarkerPosition(SLPlayItf, SLuint32) { return SL_RESULT_SUCCESS; } -SLresult PlayClearMarkerPosition(SLPlayItf /*self*/) { +SLresult PlayClearMarkerPosition(SLPlayItf) { return SL_RESULT_SUCCESS; } -SLresult PlayGetMarkerPosition(SLPlayItf /*self*/, SLuint32* position) { +SLresult PlayGetMarkerPosition(SLPlayItf, SLuint32* position) { return ReturnZeroU32(position); } -SLresult PlaySetPositionUpdatePeriod(SLPlayItf /*self*/, SLuint32 /*period*/) { +SLresult PlaySetPositionUpdatePeriod(SLPlayItf, SLuint32) { return SL_RESULT_SUCCESS; } -SLresult PlayGetPositionUpdatePeriod(SLPlayItf /*self*/, SLuint32* period) { +SLresult PlayGetPositionUpdatePeriod(SLPlayItf, SLuint32* period) { return ReturnZeroU32(period); } -SLresult RecordSetState(SLRecordItf /*self*/, SLuint32 state) { +SLresult RecordSetState(SLRecordItf, SLuint32 state) { g_record_state = state; return SL_RESULT_SUCCESS; } -SLresult RecordGetState(SLRecordItf /*self*/, SLuint32* state) { +SLresult RecordGetState(SLRecordItf, SLuint32* state) { if (state) { *state = g_record_state; } return SL_RESULT_SUCCESS; } -SLresult RecordSetDurationLimit(SLRecordItf /*self*/, - SLuint32 /*milliseconds*/) { +SLresult RecordSetDurationLimit(SLRecordItf, + SLuint32) { return SL_RESULT_SUCCESS; } -SLresult RecordGetPosition(SLRecordItf /*self*/, SLuint32* position) { +SLresult RecordGetPosition(SLRecordItf, SLuint32* position) { return ReturnZeroU32(position); } -SLresult RecordRegisterCallback(SLRecordItf /*self*/, - SLRecordCallback /*callback*/, - void* /*context*/) { +SLresult RecordRegisterCallback(SLRecordItf, + SLRecordCallback, + void*) { return SL_RESULT_SUCCESS; } -SLresult RecordSetEventsMask(SLRecordItf /*self*/, SLuint32 /*mask*/) { +SLresult RecordSetEventsMask(SLRecordItf, SLuint32) { return SL_RESULT_SUCCESS; } -SLresult RecordGetEventsMask(SLRecordItf /*self*/, SLuint32* mask) { +SLresult RecordGetEventsMask(SLRecordItf, SLuint32* mask) { return ReturnZeroU32(mask); } -SLresult RecordSetMarkerPosition(SLRecordItf /*self*/, SLuint32 /*position*/) { +SLresult RecordSetMarkerPosition(SLRecordItf, SLuint32) { return SL_RESULT_SUCCESS; } -SLresult RecordClearMarkerPosition(SLRecordItf /*self*/) { +SLresult RecordClearMarkerPosition(SLRecordItf) { return SL_RESULT_SUCCESS; } -SLresult RecordGetMarkerPosition(SLRecordItf /*self*/, SLuint32* position) { +SLresult RecordGetMarkerPosition(SLRecordItf, SLuint32* position) { return ReturnZeroU32(position); } -SLresult RecordSetPositionUpdatePeriod(SLRecordItf /*self*/, - SLuint32 /*period*/) { +SLresult RecordSetPositionUpdatePeriod(SLRecordItf, + SLuint32) { return SL_RESULT_SUCCESS; } -SLresult RecordGetPositionUpdatePeriod(SLRecordItf /*self*/, SLuint32* period) { +SLresult RecordGetPositionUpdatePeriod(SLRecordItf, SLuint32* period) { return ReturnZeroU32(period); } -SLresult BufferQueueEnqueue(SLBufferQueueItf /*self*/, const void* /*buffer*/, - SLuint32 /*size*/) { +SLresult BufferQueueEnqueue(SLBufferQueueItf, const void*, + SLuint32) { ++g_buffer_queue_state.count; return SL_RESULT_SUCCESS; } -SLresult BufferQueueClear(SLBufferQueueItf /*self*/) { +SLresult BufferQueueClear(SLBufferQueueItf) { g_buffer_queue_state = {}; return SL_RESULT_SUCCESS; } -SLresult BufferQueueGetState(SLBufferQueueItf /*self*/, +SLresult BufferQueueGetState(SLBufferQueueItf, SLBufferQueueState* state) { if (state) { *state = g_buffer_queue_state; @@ -475,7 +471,7 @@ SLresult BufferQueueGetState(SLBufferQueueItf /*self*/, return SL_RESULT_SUCCESS; } -SLresult BufferQueueRegisterCallback(SLBufferQueueItf /*self*/, +SLresult BufferQueueRegisterCallback(SLBufferQueueItf, SLBufferQueueCallback callback, void* context) { g_buffer_queue_callback = callback; @@ -483,18 +479,18 @@ SLresult BufferQueueRegisterCallback(SLBufferQueueItf /*self*/, return SL_RESULT_SUCCESS; } -SLresult SimpleBufferQueueEnqueue(SLAndroidSimpleBufferQueueItf /*self*/, - const void* /*buffer*/, SLuint32 /*size*/) { +SLresult SimpleBufferQueueEnqueue(SLAndroidSimpleBufferQueueItf, + const void*, SLuint32) { ++g_simple_queue_state.count; return SL_RESULT_SUCCESS; } -SLresult SimpleBufferQueueClear(SLAndroidSimpleBufferQueueItf /*self*/) { +SLresult SimpleBufferQueueClear(SLAndroidSimpleBufferQueueItf) { g_simple_queue_state = {}; return SL_RESULT_SUCCESS; } -SLresult SimpleBufferQueueGetState(SLAndroidSimpleBufferQueueItf /*self*/, +SLresult SimpleBufferQueueGetState(SLAndroidSimpleBufferQueueItf, SLBufferQueueState* state) { if (state) { *state = g_simple_queue_state; @@ -503,40 +499,40 @@ SLresult SimpleBufferQueueGetState(SLAndroidSimpleBufferQueueItf /*self*/, } SLresult SimpleBufferQueueRegisterCallback( - SLAndroidSimpleBufferQueueItf /*self*/, + SLAndroidSimpleBufferQueueItf, SLAndroidSimpleBufferQueueCallback callback, void* context) { g_simple_queue_callback = callback; g_simple_queue_context = context; return SL_RESULT_SUCCESS; } -SLresult VolumeSetLevel(SLVolumeItf /*self*/, SLint32 /*level*/) { +SLresult VolumeSetLevel(SLVolumeItf, SLint32) { return SL_RESULT_SUCCESS; } -SLresult VolumeGetLevel(SLVolumeItf /*self*/, SLint32* level) { +SLresult VolumeGetLevel(SLVolumeItf, SLint32* level) { if (level) { *level = 0; } return SL_RESULT_SUCCESS; } -SLresult VolumeSetMute(SLVolumeItf /*self*/, SLboolean /*mute*/) { +SLresult VolumeSetMute(SLVolumeItf, SLboolean) { return SL_RESULT_SUCCESS; } -SLresult VolumeGetMute(SLVolumeItf /*self*/, SLboolean* mute) { +SLresult VolumeGetMute(SLVolumeItf, SLboolean* mute) { if (mute) { *mute = 0; } return SL_RESULT_SUCCESS; } -SLresult VolumeEnableStereoPosition(SLVolumeItf /*self*/, SLboolean /*enable*/) { +SLresult VolumeEnableStereoPosition(SLVolumeItf, SLboolean) { return SL_RESULT_SUCCESS; } -SLresult VolumeIsStereoPositionEnabled(SLVolumeItf /*self*/, +SLresult VolumeIsStereoPositionEnabled(SLVolumeItf, SLboolean* enabled) { if (enabled) { *enabled = 0; @@ -544,23 +540,23 @@ SLresult VolumeIsStereoPositionEnabled(SLVolumeItf /*self*/, return SL_RESULT_SUCCESS; } -SLresult VolumeSetStereoPosition(SLVolumeItf /*self*/, SLint32 /*position*/) { +SLresult VolumeSetStereoPosition(SLVolumeItf, SLint32) { return SL_RESULT_SUCCESS; } -SLresult VolumeGetStereoPosition(SLVolumeItf /*self*/, SLint32* position) { +SLresult VolumeGetStereoPosition(SLVolumeItf, SLint32* position) { if (position) { *position = 0; } return SL_RESULT_SUCCESS; } -SLresult ConfigSet(SLAndroidConfigurationItf /*self*/, const char* /*key*/, - const void* /*value*/, SLuint32 /*value_size*/) { +SLresult ConfigSet(SLAndroidConfigurationItf, const char*, + const void*, SLuint32) { return SL_RESULT_SUCCESS; } -SLresult ConfigGet(SLAndroidConfigurationItf /*self*/, const char* /*key*/, +SLresult ConfigGet(SLAndroidConfigurationItf, const char*, SLuint32* value_size, void* value) { if (value_size) { if (value && *value_size > 0) { @@ -709,7 +705,7 @@ void* SL_IID_VOLUME = reinterpret_cast(0x1007); namespace { -SLresult ObjectGetInterface(SLObjectItf /*self*/, SLInterfaceID iid, +SLresult ObjectGetInterface(SLObjectItf, SLInterfaceID iid, void* interface_out) { if (!interface_out) { return SL_RESULT_SUCCESS; @@ -749,107 +745,107 @@ SLresult ObjectGetInterface(SLObjectItf /*self*/, SLInterfaceID iid, return SL_RESULT_FEATURE_UNSUPPORTED; } -SLresult CreateUnsupportedObject(SLEngineItf /*self*/, SLObjectItf* object, - SLuint32 /*device_id*/, - SLuint32 /*num_interfaces*/, - const SLInterfaceID* /*ids*/, - const SLboolean* /*required*/) { +SLresult CreateUnsupportedObject(SLEngineItf, SLObjectItf* object, + SLuint32, + SLuint32, + const SLInterfaceID*, + const SLboolean*) { return SuccessObject(object, GenericObject()); } -SLresult CreateAudioPlayer(SLEngineItf /*self*/, SLObjectItf* player, - const void* /*source*/, const void* /*sink*/, - SLuint32 /*num_interfaces*/, - const SLInterfaceID* /*ids*/, - const SLboolean* /*required*/) { +SLresult CreateAudioPlayer(SLEngineItf, SLObjectItf* player, + const void*, const void*, + SLuint32, + const SLInterfaceID*, + const SLboolean*) { g_play_state = SL_PLAYSTATE_STOPPED; g_buffer_queue_state = {}; g_simple_queue_state = {}; return SuccessObject(player, PlayerObject()); } -SLresult CreateAudioRecorder(SLEngineItf /*self*/, SLObjectItf* recorder, - const void* /*source*/, const void* /*sink*/, - SLuint32 /*num_interfaces*/, - const SLInterfaceID* /*ids*/, - const SLboolean* /*required*/) { +SLresult CreateAudioRecorder(SLEngineItf, SLObjectItf* recorder, + const void*, const void*, + SLuint32, + const SLInterfaceID*, + const SLboolean*) { if (recorder != nullptr) { *recorder = nullptr; } return SL_RESULT_FEATURE_UNSUPPORTED; } -SLresult CreateMidiPlayer(SLEngineItf /*self*/, SLObjectItf* player, - const void* /*source*/, const void* /*sink*/, - const void* /*bank_source*/, - const void* /*bank_sink*/, - SLuint32 /*num_interfaces*/, - const SLInterfaceID* /*ids*/, - const SLboolean* /*required*/) { +SLresult CreateMidiPlayer(SLEngineItf, SLObjectItf* player, + const void*, const void*, + const void*, + const void*, + SLuint32, + const SLInterfaceID*, + const SLboolean*) { return SuccessObject(player, PlayerObject()); } -SLresult CreateListener(SLEngineItf /*self*/, SLObjectItf* listener, - SLuint32 /*num_interfaces*/, - const SLInterfaceID* /*ids*/, - const SLboolean* /*required*/) { +SLresult CreateListener(SLEngineItf, SLObjectItf* listener, + SLuint32, + const SLInterfaceID*, + const SLboolean*) { return SuccessObject(listener, GenericObject()); } -SLresult Create3DGroup(SLEngineItf /*self*/, SLObjectItf* group, - SLuint32 /*num_interfaces*/, - const SLInterfaceID* /*ids*/, - const SLboolean* /*required*/) { +SLresult Create3DGroup(SLEngineItf, SLObjectItf* group, + SLuint32, + const SLInterfaceID*, + const SLboolean*) { return SuccessObject(group, GenericObject()); } -SLresult CreateOutputMix(SLEngineItf /*self*/, SLObjectItf* mix, - SLuint32 /*num_interfaces*/, - const SLInterfaceID* /*ids*/, - const SLboolean* /*required*/) { +SLresult CreateOutputMix(SLEngineItf, SLObjectItf* mix, + SLuint32, + const SLInterfaceID*, + const SLboolean*) { return SuccessObject(mix, OutputMixObject()); } -SLresult CreateMetadataExtractor(SLEngineItf /*self*/, SLObjectItf* extractor, - const void* /*source*/, - SLuint32 /*num_interfaces*/, - const SLInterfaceID* /*ids*/, - const SLboolean* /*required*/) { +SLresult CreateMetadataExtractor(SLEngineItf, SLObjectItf* extractor, + const void*, + SLuint32, + const SLInterfaceID*, + const SLboolean*) { return SuccessObject(extractor, GenericObject()); } -SLresult CreateExtensionObject(SLEngineItf /*self*/, SLObjectItf* object, - void* /*parameters*/, SLuint32 /*object_id*/, - SLuint32 /*num_interfaces*/, - const SLInterfaceID* /*ids*/, - const SLboolean* /*required*/) { +SLresult CreateExtensionObject(SLEngineItf, SLObjectItf* object, + void*, SLuint32, + SLuint32, + const SLInterfaceID*, + const SLboolean*) { return SuccessObject(object, GenericObject()); } -SLresult QueryNumSupportedInterfaces(SLEngineItf /*self*/, - SLuint32 /*object_id*/, SLuint32* count) { +SLresult QueryNumSupportedInterfaces(SLEngineItf, + SLuint32, SLuint32* count) { if (count) { *count = 0; } return SL_RESULT_SUCCESS; } -SLresult QuerySupportedInterfaces(SLEngineItf /*self*/, SLuint32 /*object_id*/, - SLuint32 /*index*/, SLInterfaceID* iid) { +SLresult QuerySupportedInterfaces(SLEngineItf, SLuint32, + SLuint32, SLInterfaceID* iid) { if (iid) { *iid = nullptr; } return SL_RESULT_FEATURE_UNSUPPORTED; } -SLresult QueryNumSupportedExtensions(SLEngineItf /*self*/, SLuint32* count) { +SLresult QueryNumSupportedExtensions(SLEngineItf, SLuint32* count) { if (count) { *count = 0; } return SL_RESULT_SUCCESS; } -SLresult QuerySupportedExtension(SLEngineItf /*self*/, SLuint32 /*index*/, +SLresult QuerySupportedExtension(SLEngineItf, SLuint32, char* name, SLint32* name_length) { if (name_length) { *name_length = 0; @@ -860,7 +856,7 @@ SLresult QuerySupportedExtension(SLEngineItf /*self*/, SLuint32 /*index*/, return SL_RESULT_FEATURE_UNSUPPORTED; } -SLresult IsExtensionSupported(SLEngineItf /*self*/, const char* /*name*/, +SLresult IsExtensionSupported(SLEngineItf, const char*, SLboolean* supported) { if (supported) { *supported = 0; @@ -872,12 +868,11 @@ SLresult IsExtensionSupported(SLEngineItf /*self*/, const char* /*name*/, extern "C" { -// Test-only OpenSL ES entry point. Production fails closed. -SLresult slCreateEngine(void** pEngine, uint32_t /*numOptions*/, - const void* /*pEngineOptions*/, - uint32_t /*numInterfaces*/, - const void* /*pInterfaceIds*/, - const void* /*pInterfaceRequired*/) { +SLresult slCreateEngine(void** pEngine, uint32_t, + const void*, + uint32_t, + const void*, + const void*) { if (pEngine == nullptr) { return SL_RESULT_PARAMETER_INVALID; } @@ -890,12 +885,11 @@ SLresult slCreateEngine(void** pEngine, uint32_t /*numOptions*/, return SL_RESULT_SUCCESS; } -// Test-only OpenMAX AL entry point. Production fails closed. -SLresult alCreateEngine(void** pEngine, uint32_t /*numOptions*/, - const void* /*pEngineOptions*/, - uint32_t /*numInterfaces*/, - const void* /*pInterfaceIds*/, - const void* /*pInterfaceRequired*/) { +SLresult alCreateEngine(void** pEngine, uint32_t, + const void*, + uint32_t, + const void*, + const void*) { if (pEngine == nullptr) { return SL_RESULT_PARAMETER_INVALID; } diff --git a/stubs/libvulkan_stub.cc b/stubs/libvulkan_stub.cc index ee958e5..faf86c5 100644 --- a/stubs/libvulkan_stub.cc +++ b/stubs/libvulkan_stub.cc @@ -35,8 +35,8 @@ bool TestVulkanStubEnabled() { extern "C" { -VkResult vkCreateInstance(const void* /*create_info*/, - const VkAllocationCallbacks* /*allocator*/, +VkResult vkCreateInstance(const void*, + const VkAllocationCallbacks*, VkInstance* instance) { if (!TestVulkanStubEnabled()) { if (instance != nullptr) { @@ -50,12 +50,12 @@ VkResult vkCreateInstance(const void* /*create_info*/, return kVkSuccess; } -void vkDestroyInstance(VkInstance /*instance*/, - const VkAllocationCallbacks* /*allocator*/) {} +void vkDestroyInstance(VkInstance, + const VkAllocationCallbacks*) {} -VkResult vkCreateAndroidSurfaceKHR(VkInstance /*instance*/, - const void* /*create_info*/, - const VkAllocationCallbacks* /*allocator*/, +VkResult vkCreateAndroidSurfaceKHR(VkInstance, + const void*, + const VkAllocationCallbacks*, VkSurfaceKHR* surface) { if (!TestVulkanStubEnabled()) { if (surface != nullptr) { @@ -69,12 +69,12 @@ VkResult vkCreateAndroidSurfaceKHR(VkInstance /*instance*/, return kVkSuccess; } -void vkDestroySurfaceKHR(VkInstance /*instance*/, VkSurfaceKHR /*surface*/, - const VkAllocationCallbacks* /*allocator*/) {} +void vkDestroySurfaceKHR(VkInstance, VkSurfaceKHR, + const VkAllocationCallbacks*) {} -VkResult vkEnumerateInstanceExtensionProperties(const char* /*layer_name*/, +VkResult vkEnumerateInstanceExtensionProperties(const char*, uint32_t* property_count, - void* /*properties*/) { + void*) { if (property_count != nullptr) { *property_count = 0; } @@ -82,19 +82,19 @@ VkResult vkEnumerateInstanceExtensionProperties(const char* /*layer_name*/, } VkResult vkEnumerateInstanceLayerProperties(uint32_t* property_count, - void* /*properties*/) { + void*) { if (property_count != nullptr) { *property_count = 0; } return kVkSuccess; } -PFN_vkVoidFunction vkGetDeviceProcAddr(VkDevice /*device*/, - const char* /*name*/) { +PFN_vkVoidFunction vkGetDeviceProcAddr(VkDevice, + const char*) { return nullptr; } -PFN_vkVoidFunction vkGetInstanceProcAddr(VkInstance /*instance*/, +PFN_vkVoidFunction vkGetInstanceProcAddr(VkInstance, const char* name) { if (name == nullptr) { return nullptr; diff --git a/tests/audio_foundation_test.cc b/tests/audio_foundation_test.cc index c7d67a9..24759cd 100644 --- a/tests/audio_foundation_test.cc +++ b/tests/audio_foundation_test.cc @@ -168,7 +168,7 @@ struct CallbackProbe { int calls = 0; }; -void OpenSlQueueCallback(opensl_abi::AndroidSimpleBufferQueue /*queue*/, +void OpenSlQueueCallback(opensl_abi::AndroidSimpleBufferQueue, void* context) { auto* probe = static_cast(context); { @@ -193,7 +193,7 @@ struct BlockingCallbackProbe { bool release = false; }; -void BlockingOpenSlQueueCallback(opensl_abi::AndroidSimpleBufferQueue /*queue*/, +void BlockingOpenSlQueueCallback(opensl_abi::AndroidSimpleBufferQueue, void* context) { auto* probe = static_cast(context); std::unique_lock lock(probe->mutex); diff --git a/tests/auto_update_orchestration_test.sh b/tests/auto_update_orchestration_test.sh index 094af34..5a17c4f 100755 --- a/tests/auto_update_orchestration_test.sh +++ b/tests/auto_update_orchestration_test.sh @@ -981,9 +981,8 @@ rm -f -- "${FAKE_CALLS}" "8888-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ]] export FAKE_REMOTE_VERSION=9999 -# A candidate staged while experimental must be reconsidered after its exact -# Build-ID profile is approved. It proceeds directly to canary without a -# second provider download and is still promoted only after smoke succeeds. +# Reconsider a staged candidate after its exact Build-ID profile is approved, +# without downloading the same payload again. fetch_calls_before_approval="$(wc -l < "${FAKE_FETCH_CALLS}")" jq '(.profiles[] | select(.version_code == 9999)) |= (.status = "supported" | .default_allowed = true)' \ diff --git a/tests/fixtures/unknown_roblox_fixture.cc b/tests/fixtures/unknown_roblox_fixture.cc index 1a738b3..888b0cb 100644 --- a/tests/fixtures/unknown_roblox_fixture.cc +++ b/tests/fixtures/unknown_roblox_fixture.cc @@ -15,4 +15,4 @@ __attribute__((constructor)) void FailIfLoaded() { } // namespace -extern "C" int JNI_OnLoad(void* /*vm*/, void* /*reserved*/) { return 0x10006; } +extern "C" int JNI_OnLoad(void*, void*) { return 0x10006; } diff --git a/tests/fixtures/upstream_jni_fixture.cc b/tests/fixtures/upstream_jni_fixture.cc index 26d4fa0..05c112e 100644 --- a/tests/fixtures/upstream_jni_fixture.cc +++ b/tests/fixtures/upstream_jni_fixture.cc @@ -10,13 +10,13 @@ namespace { -jint NativeIncrement(JNIEnv* /*env*/, jclass /*clazz*/, jint value) { +jint NativeIncrement(JNIEnv*, jclass, jint value) { return value + 1; } } // namespace -extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* /*reserved*/) { +extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) { if (vm == nullptr) { return JNI_ERR; } diff --git a/tests/fmod_java_audio_runtime_test.cc b/tests/fmod_java_audio_runtime_test.cc index d0a6b62..c6dad36 100644 --- a/tests/fmod_java_audio_runtime_test.cc +++ b/tests/fmod_java_audio_runtime_test.cc @@ -82,7 +82,7 @@ class ControlledSink final : public AudioSink { return Status::Ok(); } - Status SetGain(float /*linear_gain*/) override { return Status::Ok(); } + Status SetGain(float) override { return Status::Ok(); } Status Flush() override { return Status::Ok(); } Status Clear() override { diff --git a/tests/jnivm_test.cc b/tests/jnivm_test.cc index 2ebf642..9b4eaea 100644 --- a/tests/jnivm_test.cc +++ b/tests/jnivm_test.cc @@ -12,14 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -// tests/jnivm_test.cc — Unit tests for the Pseudo-JVM component. -// -// Tests cover: -// 1. VM construction and JavaVM pointer validity. -// 2. Class registration and deduplication. -// 3. Method registration and lookup. -// 4. Method callback invocation. - #include "jnivm/jnivm.h" #include @@ -38,7 +30,6 @@ namespace jnivm { namespace { -// Fixture: provides a freshly constructed VM instance for each test. class JniVmTest : public ::testing::Test { protected: void SetUp() override { @@ -384,14 +375,10 @@ jobject MakeTextBoxInfo(JNIEnv *env) { return info; } -// VM construction - -// A newly constructed VM must expose a non-null JavaVM pointer. TEST_F(JniVmTest, JavaVmPointerIsNotNull) { EXPECT_NE(vm_->GetJavaVM(), nullptr); } -// A newly constructed VM must expose a non-null JNIEnv pointer. TEST_F(JniVmTest, JniEnvPointerIsNotNull) { EXPECT_NE(vm_->GetJNIEnv(), nullptr); } @@ -422,20 +409,15 @@ TEST_F(JniVmTest, TraceAllNeverPrintsJStringContentOrPrefix) { EXPECT_EQ(trace_output.find("SECRET_CANARY_PREFIX"), std::string::npos); } -// A newly constructed VM has zero registered classes. TEST_F(JniVmTest, InitialClassCountIsZero) { EXPECT_EQ(vm_->GetClassCount(), 0u); } -// Class registration - -// Registering a class returns a non-null shared_ptr. TEST_F(JniVmTest, RegisterClassReturnsNonNull) { auto cls = vm_->RegisterClass("android/content/Context"); EXPECT_NE(cls, nullptr); } -// The class name is preserved exactly as provided. TEST_F(JniVmTest, RegisterClassPreservesName) { const std::string kName = "com/roblox/client/RobloxActivity"; auto cls = vm_->RegisterClass(kName); @@ -443,44 +425,34 @@ TEST_F(JniVmTest, RegisterClassPreservesName) { EXPECT_EQ(cls->GetName(), kName); } -// Registering the same class twice returns the same instance (deduplication). TEST_F(JniVmTest, RegisterClassIsDeduplicated) { auto cls1 = vm_->RegisterClass("rbx/JNIRobloxSettings"); auto cls2 = vm_->RegisterClass("rbx/JNIRobloxSettings"); EXPECT_EQ(cls1.get(), cls2.get()); } -// Class count increments correctly with distinct registrations. TEST_F(JniVmTest, ClassCountIncrements) { vm_->RegisterClass("android/content/Context"); vm_->RegisterClass("android/app/Activity"); EXPECT_EQ(vm_->GetClassCount(), 2u); } -// Registering the same class multiple times does not inflate the count. TEST_F(JniVmTest, DuplicateRegistrationDoesNotInflateCount) { vm_->RegisterClass("android/content/Context"); vm_->RegisterClass("android/content/Context"); EXPECT_EQ(vm_->GetClassCount(), 1u); } -// Class lookup (FindClass) - -// FindClass returns nullptr for an unregistered class. TEST_F(JniVmTest, FindClassReturnsNullForUnknownClass) { EXPECT_EQ(vm_->FindClass("not/Registered"), nullptr); } -// FindClass returns the correct instance for a registered class. TEST_F(JniVmTest, FindClassReturnsRegisteredInstance) { auto registered = vm_->RegisterClass("android/os/Bundle"); auto found = vm_->FindClass("android/os/Bundle"); EXPECT_EQ(registered.get(), found.get()); } -// Method registration and lookup - -// A method registered on a class can be looked up by name + signature. TEST_F(JniVmTest, RegisteredMethodIsFound) { auto cls = vm_->RegisterClass("rbx/JNIRobloxSettings"); cls->RegisterMethod("nativeInitClientSettings", "()V", @@ -488,7 +460,6 @@ TEST_F(JniVmTest, RegisteredMethodIsFound) { EXPECT_NE(cls->FindMethod("nativeInitClientSettings", "()V"), nullptr); } -// Looking up a method with a wrong signature returns nullptr. TEST_F(JniVmTest, MethodLookupWithWrongSignatureReturnsNull) { auto cls = vm_->RegisterClass("rbx/JNIRobloxSettings"); cls->RegisterMethod("nativeInitClientSettings", "()V", @@ -496,7 +467,6 @@ TEST_F(JniVmTest, MethodLookupWithWrongSignatureReturnsNull) { EXPECT_EQ(cls->FindMethod("nativeInitClientSettings", "(I)V"), nullptr); } -// Looking up a non-existent method returns nullptr. TEST_F(JniVmTest, LookupOfNonExistentMethodReturnsNull) { auto cls = vm_->RegisterClass("android/content/Context"); EXPECT_EQ(cls->FindMethod("getSystemService", @@ -504,9 +474,6 @@ TEST_F(JniVmTest, LookupOfNonExistentMethodReturnsNull) { nullptr); } -// Method callback invocation - -// The registered callback is actually invoked when called. TEST_F(JniVmTest, MethodCallbackIsInvoked) { auto cls = vm_->RegisterClass("rbx/JNIRobloxSettings"); bool invoked = false; @@ -520,7 +487,6 @@ TEST_F(JniVmTest, MethodCallbackIsInvoked) { EXPECT_TRUE(invoked); } -// The callback receives the correct JNIEnv pointer. TEST_F(JniVmTest, CallbackReceivesCorrectJniEnv) { auto cls = vm_->RegisterClass("android/content/Context"); JNIEnv *received_env = nullptr; @@ -678,7 +644,7 @@ TEST_F(JniVmTest, NativeUserJavaInterfaceReturnsInjectedIdentityExactly) { identity.display_name = "Display Name"; vm_->SetRobloxAuthIdentity(identity); - // The identity has priority over research environment fallbacks. + // Injected identity takes precedence over environment fallbacks. setenv("MOCKTAIL_ROBLOX_USER_ID", "77", 1); setenv("MOCKTAIL_ROBLOX_USERNAME", "EnvironmentName", 1); @@ -1543,9 +1509,8 @@ TEST_F(JniVmTest, GameActivityDispatchesTypedAndroidWindowFlags) { EXPECT_EQ(probe->flags, 0x800); EXPECT_EQ(probe->mask, 0x800); - // Production LuaApp retains the concrete Android object behind the - // android.app.Activity interface. The inherited GameActivity method ID must - // still reach the same host boundary. + // LuaApp passes the concrete object as android.app.Activity. The inherited + // method ID must still dispatch to the host callback. env->CallVoidMethod(activity_interface, set_flags, 0x400, 0x400); EXPECT_EQ(probe->calls, 4); EXPECT_EQ(probe->flags, 0x400); diff --git a/tests/libc_shim_test.cc b/tests/libc_shim_test.cc index c316753..1600ba5 100644 --- a/tests/libc_shim_test.cc +++ b/tests/libc_shim_test.cc @@ -12,13 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -// tests/libc_shim_test.cc — Unit tests for the Bionic → Glibc path shim. -// -// Tests cover: -// 1. Path translation with registered prefixes. -// 2. Unchanged return for unregistered paths. -// 3. Multiple prefix registrations (longest-prefix matching). - #include "libc_shim/libc_shim.h" #include @@ -39,7 +32,6 @@ void* CountingGuestAllocator(size_t size) { return std::malloc(size); } -// Fixture: clears all path mappings before and after each test. class LibcShimTest : public ::testing::Test { protected: void SetUp() override { @@ -79,9 +71,6 @@ class LibcShimTest : public ::testing::Test { std::string original_asset_path_override_; }; -// TranslatePath — basic mapping - -// An Android path with a registered prefix is correctly translated. TEST_F(LibcShimTest, TranslatesRegisteredPrefix) { RegisterPathMapping("/data/user/0/com.roblox.client", "/home/user/.var/app/org.vinegarhq.Sober/data/sober"); @@ -93,7 +82,6 @@ TEST_F(LibcShimTest, TranslatesRegisteredPrefix) { EXPECT_EQ(TranslatePath(input), expected); } -// A path with no registered prefix is returned unchanged. TEST_F(LibcShimTest, UnregisteredPathIsReturnedUnchanged) { const std::string input = "/proc/self/maps"; EXPECT_EQ(TranslatePath(input), input); @@ -111,14 +99,10 @@ TEST_F(LibcShimTest, TranslatesRelativeRobloxAssetPrefixes) { "/payload/assets/shaders/mobile.pack"); } -// An empty path is returned unchanged. TEST_F(LibcShimTest, EmptyPathIsReturnedUnchanged) { EXPECT_EQ(TranslatePath(""), ""); } -// TranslatePath — edge cases - -// Exact prefix match (path == prefix, no trailing component) is translated. TEST_F(LibcShimTest, ExactPrefixMatchIsTranslated) { RegisterPathMapping("/data/user/0/com.roblox.client", "/host/sober"); @@ -126,12 +110,10 @@ TEST_F(LibcShimTest, ExactPrefixMatchIsTranslated) { EXPECT_EQ(TranslatePath("/data/user/0/com.roblox.client"), "/host/sober"); } -// A path that starts with a substring of the registered prefix is NOT -// translated (e.g. "/data/user" must not match "/data/user/0/..."). +// Prefix matching stops at path-component boundaries. TEST_F(LibcShimTest, ShortPathIsNotTranslated) { RegisterPathMapping("/data/user/0/com.roblox.client", "/host/sober"); - // Shorter path: no match expected. EXPECT_EQ(TranslatePath("/data/user"), "/data/user"); } @@ -142,9 +124,6 @@ TEST_F(LibcShimTest, SimilarPrefixWithoutPathBoundaryIsNotTranslated) { "/data/user/0/com.roblox.client.beta/cache"); } -// RegisterPathMapping — multiple registrations - -// Two different prefixes can coexist and each translates its own paths. TEST_F(LibcShimTest, MultipleRegistrationsCoexist) { RegisterPathMapping("/data/user/0/com.roblox.client", "/host/sober"); RegisterPathMapping("/sdcard/Android/data/com.roblox.client", @@ -305,9 +284,6 @@ TEST_F(LibcShimTest, RealpathUsesConfiguredGuestAllocatorForOwnedResult) { std::free(result); } -// ClearPathMappings - -// After ClearPathMappings, previously registered prefixes no longer match. TEST_F(LibcShimTest, ClearRemovesAllMappings) { RegisterPathMapping("/data/user/0/com.roblox.client", "/host/sober"); ClearPathMappings(); diff --git a/tests/opensl_playback_runtime_test.cc b/tests/opensl_playback_runtime_test.cc index 22811be..882b25f 100644 --- a/tests/opensl_playback_runtime_test.cc +++ b/tests/opensl_playback_runtime_test.cc @@ -36,7 +36,7 @@ struct QueueCallbackProbe { int calls = 0; }; -void QueueCallback(abi::AndroidSimpleBufferQueue /*queue*/, void* context) { +void QueueCallback(abi::AndroidSimpleBufferQueue, void* context) { auto* probe = static_cast(context); { std::lock_guard lock(probe->mutex); diff --git a/tests/roblox_native_text_box_info_reader_test.cc b/tests/roblox_native_text_box_info_reader_test.cc index 09a2c36..42de674 100644 --- a/tests/roblox_native_text_box_info_reader_test.cc +++ b/tests/roblox_native_text_box_info_reader_test.cc @@ -25,7 +25,7 @@ namespace { jobject g_query_result = nullptr; int g_query_calls = 0; -jobject QueryFixture(JNIEnv* /*env*/, jclass /*native_gl_class*/) { +jobject QueryFixture(JNIEnv*, jclass) { ++g_query_calls; return g_query_result; } diff --git a/tests/roblox_text_input_jni_bridge_test.cc b/tests/roblox_text_input_jni_bridge_test.cc index 8898881..f090c5b 100644 --- a/tests/roblox_text_input_jni_bridge_test.cc +++ b/tests/roblox_text_input_jni_bridge_test.cc @@ -176,7 +176,7 @@ struct ExistingBindingProbe { }; void ExistingShow(void* context, - const jnivm::RobloxTextInputShowRequest& /*request*/) { + const jnivm::RobloxTextInputShowRequest&) { ++static_cast(context)->shows; } diff --git a/tests/startup_defaults_test.sh b/tests/startup_defaults_test.sh index 228f285..c046a8a 100644 --- a/tests/startup_defaults_test.sh +++ b/tests/startup_defaults_test.sh @@ -735,7 +735,7 @@ if ! rg -Uq 'SetEnvDefault\(\s*"MOCKTAIL_START_GAME_WITH_PARAM",\s*"0"\s*\)' "${ fi if rg -q 'MOCKTAIL_UPDATE_SURFACE_GAME_BEFORE_START_GAME|MOCKTAIL_RESUME_GAME_WITH_PLATFORM_PARAMS_AFTER_START_GAME' "${main_cc}"; then - echo "typed GAME startup must not retain pre-Start UpdateSurface or post-Start Resume experiment switches" >&2 + echo "typed GAME startup must not retain pre-Start UpdateSurface or post-Start Resume diagnostic switches" >&2 exit 1 fi @@ -858,7 +858,7 @@ fi if ! rg -q 'mocktail_stage6_start_lua_return_size_40000' "${main_cc}" || ! rg -q 'MOCKTAIL_STAGE6_START_LUA_TARGET_TABLE_SLOT6_SOURCE' "${main_cc}"; then - echo "Stage6 synthetic StartLua target table slot 6 needs an opt-in size-return experiment because resolver treats the slot result as a memory-size request" >&2 + echo "Stage6 synthetic StartLua target table slot 6 needs an opt-in size-return mode because resolver treats the slot result as a memory-size request" >&2 exit 1 fi @@ -898,7 +898,7 @@ if ! rg -q 'MOCKTAIL_PATCH_STAGE6_START_LUA_RESOLVER_RESULT20_CALLBACK' "${main_ ! rg -Fq 'Stage6 StartLua post-apply task thunk after callback' "${main_cc}" || ! rg -Fq 'Stage6 StartLua resolver result20 split callback context' "${main_cc}" || ! rg -Fq 'Stage6 StartLua resolver result20 callback materialized' "${main_cc}"; then - echo "Stage6 resolver experiments must be able to materialize result+0x20 callback pairs under an explicit flag and split callback context/source args at the native call site" >&2 + echo "Stage6 resolver diagnostics must be able to materialize result+0x20 callback pairs under an explicit flag and split callback context/source args at the native call site" >&2 exit 1 fi @@ -943,7 +943,7 @@ fi if ! rg -q 'MOCKTAIL_PATCH_STAGE6_START_LUA_POST_APPLY_PAIR_ARGUMENT' "${main_cc}" || ! rg -Fq 'force_pair_argument=%d force_null_argument=%d' "${main_cc}"; then - echo "Stage6 post-apply callback experiments must keep pair-argument and null-argument paths explicit in trace logs" >&2 + echo "Stage6 post-apply callback diagnostics must keep pair-argument and null-argument paths explicit in trace logs" >&2 exit 1 fi