mirror of
https://github.com/rhunk/SnapEnhance.git
synced 2025-05-29 04:50:15 +02:00
feat(experimental): composer hooks
This commit is contained in:
parent
c8195c5250
commit
17ad43ee92
@ -124,6 +124,13 @@ class RemoteScriptManager(
|
||||
}
|
||||
|
||||
override fun getScriptContent(moduleName: String): String? {
|
||||
if (moduleName.startsWith("composer/")) {
|
||||
return runCatching {
|
||||
context.androidContext.assets.open("composer/${moduleName.removePrefix("composer/")}").use {
|
||||
it.bufferedReader().readText()
|
||||
}
|
||||
}.getOrNull()
|
||||
}
|
||||
return getScriptInputStream(moduleName) { it?.bufferedReader()?.readText() }
|
||||
}
|
||||
|
||||
|
@ -770,6 +770,24 @@
|
||||
"name": "Native Hooks",
|
||||
"description": "Unsafe Features that hook into Snapchat's native code",
|
||||
"properties": {
|
||||
"composer_hooks": {
|
||||
"name": "Composer Hooks",
|
||||
"description": "Injects code into the Composer cross-platform UI framework (arm64 only)",
|
||||
"properties": {
|
||||
"bypass_camera_roll_limit": {
|
||||
"name": "Bypass Camera Roll Limit",
|
||||
"description": "Increases the maximum amount of media you can send from the camera roll"
|
||||
},
|
||||
"composer_console": {
|
||||
"name": "Composer Console",
|
||||
"description": "Allows you to execute JavaScript code in Composer"
|
||||
},
|
||||
"composer_logs": {
|
||||
"name": "Composer Logs",
|
||||
"description": "Redirects console logs of Composer to SnapEnhance"
|
||||
}
|
||||
}
|
||||
},
|
||||
"disable_bitmoji": {
|
||||
"name": "Disable Bitmoji",
|
||||
"description": "Disables Friends Profile Bitmoji"
|
||||
|
@ -9,7 +9,14 @@ class Experimental : ConfigContainer() {
|
||||
val allowRunningInBackground = boolean("allow_running_in_background", true)
|
||||
}
|
||||
|
||||
class ComposerHooksConfig: ConfigContainer(hasGlobalState = true) {
|
||||
val bypassCameraRollLimit = boolean("bypass_camera_roll_limit")
|
||||
val composerConsole = boolean("composer_console")
|
||||
val composerLogs = boolean("composer_logs")
|
||||
}
|
||||
|
||||
class NativeHooks : ConfigContainer(hasGlobalState = true) {
|
||||
val composerHooks = container("composer_hooks", ComposerHooksConfig()) { requireRestart() }
|
||||
val disableBitmoji = boolean("disable_bitmoji")
|
||||
}
|
||||
|
||||
|
44
core/src/main/assets/composer/loader.js
Normal file
44
core/src/main/assets/composer/loader.js
Normal file
@ -0,0 +1,44 @@
|
||||
const deviceBridge = require('composer_core/src/DeviceBridge');
|
||||
|
||||
if (LOADER_CONFIG.logPrefix) {
|
||||
function internalLog(logLevel, args) {
|
||||
deviceBridge.copyToClipBoard(LOADER_CONFIG.logPrefix + "|" + logLevel + "|" + Array.from(args).join(" "));
|
||||
}
|
||||
|
||||
console.log = function() {
|
||||
internalLog("info", arguments);
|
||||
}
|
||||
|
||||
console.error = function() {
|
||||
internalLog("error", arguments);
|
||||
}
|
||||
|
||||
console.warn = function() {
|
||||
internalLog("warn", arguments);
|
||||
}
|
||||
|
||||
console.info = function() {
|
||||
internalLog("info", arguments);
|
||||
}
|
||||
|
||||
console.debug = function() {
|
||||
internalLog("debug", arguments);
|
||||
}
|
||||
|
||||
console.stacktrace = function() {
|
||||
return new Error().stack;
|
||||
}
|
||||
}
|
||||
|
||||
if (LOADER_CONFIG.bypassCameraRollLimit) {
|
||||
((module) => {
|
||||
module.MultiSelectClickHandler = new Proxy(module.MultiSelectClickHandler, {
|
||||
construct: function(target, args) {
|
||||
args[1].selectionLimit = 9999999;
|
||||
return new target(...args);
|
||||
},
|
||||
});
|
||||
})(require('memories_ui/src/clickhandlers/MultiSelectClickHandler'))
|
||||
}
|
||||
|
||||
console.info("loader.js loaded");
|
@ -156,6 +156,7 @@ class ModContext(
|
||||
disableBitmoji = config.experimental.nativeHooks.disableBitmoji.get(),
|
||||
disableMetrics = config.global.disableMetrics.get(),
|
||||
hookAssetOpen = config.experimental.disableComposerModules.get().isNotEmpty(),
|
||||
composerHooks = config.experimental.nativeHooks.composerHooks.globalState == true
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -4,11 +4,7 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import me.rhunk.snapenhance.core.ModContext
|
||||
import me.rhunk.snapenhance.core.features.impl.COFOverride
|
||||
import me.rhunk.snapenhance.core.features.impl.ConfigurationOverride
|
||||
import me.rhunk.snapenhance.core.features.impl.MixerStories
|
||||
import me.rhunk.snapenhance.core.features.impl.OperaViewerParamsOverride
|
||||
import me.rhunk.snapenhance.core.features.impl.ScopeSync
|
||||
import me.rhunk.snapenhance.core.features.impl.*
|
||||
import me.rhunk.snapenhance.core.features.impl.downloader.MediaDownloader
|
||||
import me.rhunk.snapenhance.core.features.impl.downloader.ProfilePictureDownloader
|
||||
import me.rhunk.snapenhance.core.features.impl.experiments.*
|
||||
@ -16,7 +12,6 @@ import me.rhunk.snapenhance.core.features.impl.global.*
|
||||
import me.rhunk.snapenhance.core.features.impl.messaging.*
|
||||
import me.rhunk.snapenhance.core.features.impl.spying.HalfSwipeNotifier
|
||||
import me.rhunk.snapenhance.core.features.impl.spying.MessageLogger
|
||||
import me.rhunk.snapenhance.core.features.impl.FriendMutationObserver
|
||||
import me.rhunk.snapenhance.core.features.impl.spying.StealthMode
|
||||
import me.rhunk.snapenhance.core.features.impl.tweaks.*
|
||||
import me.rhunk.snapenhance.core.features.impl.ui.*
|
||||
@ -131,6 +126,7 @@ class FeatureManager(
|
||||
HideActiveMusic(),
|
||||
AutoOpenSnaps(),
|
||||
CustomStreaksExpirationFormat(),
|
||||
ComposerHooks(),
|
||||
)
|
||||
|
||||
initializeFeatures()
|
||||
|
@ -0,0 +1,189 @@
|
||||
package me.rhunk.snapenhance.core.features.impl.experiments
|
||||
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.widget.FrameLayout
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.BugReport
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.google.gson.JsonObject
|
||||
import kotlinx.coroutines.launch
|
||||
import me.rhunk.snapenhance.common.ui.AppMaterialTheme
|
||||
import me.rhunk.snapenhance.common.ui.createComposeAlertDialog
|
||||
import me.rhunk.snapenhance.common.ui.createComposeView
|
||||
import me.rhunk.snapenhance.core.features.Feature
|
||||
import me.rhunk.snapenhance.core.features.FeatureLoadParams
|
||||
import me.rhunk.snapenhance.core.util.hook.HookStage
|
||||
import me.rhunk.snapenhance.core.util.hook.hook
|
||||
import me.rhunk.snapenhance.nativelib.NativeLib
|
||||
import kotlin.random.Random
|
||||
import kotlin.random.nextInt
|
||||
|
||||
class ComposerHooks: Feature("ComposerHooks", loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC) {
|
||||
private val config by lazy { context.config.experimental.nativeHooks.composerHooks }
|
||||
|
||||
private val composerConsole by lazy {
|
||||
createComposeAlertDialog(context.mainActivity!!) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
var result by remember { mutableStateOf("") }
|
||||
var codeContent by remember { mutableStateOf("") }
|
||||
|
||||
Text("Composer Console", fontSize = 18.sp, fontWeight = FontWeight.Bold)
|
||||
|
||||
TextField(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
textStyle = TextStyle.Default.copy(fontSize = 12.sp),
|
||||
value = codeContent,
|
||||
placeholder = { Text("Enter your JS code here:") },
|
||||
onValueChange = {
|
||||
codeContent = it
|
||||
}
|
||||
)
|
||||
Button(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
onClick = {
|
||||
context.log.verbose("input: $codeContent", "ComposerConsole")
|
||||
result = "Running..."
|
||||
context.coroutineScope.launch {
|
||||
result = (context.native.composerEval("""
|
||||
(() => {
|
||||
try {
|
||||
$codeContent
|
||||
} catch (e) {
|
||||
return e.toString()
|
||||
}
|
||||
})()
|
||||
""".trimIndent()) ?: "(no result)").also {
|
||||
context.log.verbose("result: $it", "ComposerConsole")
|
||||
}
|
||||
}
|
||||
}
|
||||
) {
|
||||
Text("Run")
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
Text(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun injectConsole() {
|
||||
val root = context.mainActivity!!.findViewById<FrameLayout>(android.R.id.content)
|
||||
root.post {
|
||||
root.addView(createComposeView(root.context) {
|
||||
AppMaterialTheme {
|
||||
FilledIconButton(
|
||||
onClick = {
|
||||
composerConsole.show()
|
||||
},
|
||||
modifier = Modifier.padding(top = 100.dp, end = 16.dp)
|
||||
) {
|
||||
Icon(Icons.Default.BugReport, contentDescription = "Debug Console")
|
||||
}
|
||||
}
|
||||
}.apply {
|
||||
layoutParams = FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT).apply {
|
||||
gravity = android.view.Gravity.TOP or android.view.Gravity.END
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadHooks() {
|
||||
val loaderConfig = JsonObject()
|
||||
|
||||
if (config.composerLogs.get()) {
|
||||
val logPrefix = Random.nextInt(100000..999999).toString()
|
||||
val logTag = "ComposerLogs"
|
||||
|
||||
ClipboardManager::class.java.hook("setPrimaryClip", HookStage.BEFORE) { param ->
|
||||
val clipData = param.arg<ClipData>(0).takeIf { it.itemCount == 1 } ?: return@hook
|
||||
val logText = clipData.getItemAt(0).text ?: return@hook
|
||||
if (!logText.startsWith("$logPrefix|")) return@hook
|
||||
|
||||
val logContainer = logText.removePrefix("$logPrefix|").toString()
|
||||
val logType = logContainer.substringBefore("|")
|
||||
val content = logContainer.substringAfter("|")
|
||||
|
||||
when (logType) {
|
||||
"verbose" -> context.log.verbose(content, logTag)
|
||||
"info" -> context.log.info(content, logTag)
|
||||
"debug" -> context.log.debug(content, logTag)
|
||||
"warn" -> context.log.warn(content, logTag)
|
||||
"error" -> context.log.error(content, logTag)
|
||||
else -> context.log.info(logContainer, logTag)
|
||||
}
|
||||
param.setResult(null)
|
||||
}
|
||||
loaderConfig.addProperty("logPrefix", logPrefix)
|
||||
}
|
||||
|
||||
if (config.bypassCameraRollLimit.get()) {
|
||||
loaderConfig.addProperty("bypassCameraRollLimit", true)
|
||||
}
|
||||
|
||||
val loaderScript = context.scriptRuntime.scripting.getScriptContent("composer/loader.js") ?: run {
|
||||
context.log.error("Failed to load composer loader script")
|
||||
return
|
||||
}
|
||||
|
||||
val hookResult = context.native.composerEval("""
|
||||
(() => { try { const LOADER_CONFIG = $loaderConfig; $loaderScript
|
||||
} catch (e) {
|
||||
return e.toString() + "\n" + e.stack;
|
||||
}
|
||||
return "success";
|
||||
})()
|
||||
""".trimIndent().trim())
|
||||
|
||||
if (hookResult != "success") {
|
||||
context.shortToast(("Composer loader failed : $hookResult").also {
|
||||
context.log.error(it)
|
||||
})
|
||||
}
|
||||
|
||||
if (config.composerConsole.get()) {
|
||||
injectConsole()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivityCreate() {
|
||||
if (!NativeLib.initialized || config.globalState != true) return
|
||||
var composerThreadTask: (() -> Unit)? = null
|
||||
|
||||
findClass("com.snap.composer.callable.ComposerFunctionNative").hook("nativePerform", HookStage.BEFORE) {
|
||||
composerThreadTask?.invoke()
|
||||
composerThreadTask = null
|
||||
}
|
||||
|
||||
context.coroutineScope.launch {
|
||||
context.native.waitForComposer()
|
||||
composerThreadTask = ::loadHooks
|
||||
}
|
||||
}
|
||||
}
|
@ -13,6 +13,7 @@ typedef struct {
|
||||
bool disable_bitmoji;
|
||||
bool disable_metrics;
|
||||
bool hook_asset_open;
|
||||
bool composer_hooks;
|
||||
} native_config_t;
|
||||
|
||||
namespace common {
|
||||
|
173
native/jni/src/hooks/composer_hook.h
Normal file
173
native/jni/src/hooks/composer_hook.h
Normal file
@ -0,0 +1,173 @@
|
||||
#pragma once
|
||||
#include <stdio.h>
|
||||
|
||||
namespace ComposerHook {
|
||||
enum {
|
||||
JS_TAG_FIRST = -11,
|
||||
JS_TAG_BIG_DECIMAL = -11,
|
||||
JS_TAG_BIG_INT = -10,
|
||||
JS_TAG_BIG_FLOAT = -9,
|
||||
JS_TAG_SYMBOL = -8,
|
||||
JS_TAG_STRING = -7,
|
||||
JS_TAG_MODULE = -3,
|
||||
JS_TAG_FUNCTION_BYTECODE = -2,
|
||||
JS_TAG_OBJECT = -1,
|
||||
|
||||
JS_TAG_INT = 0,
|
||||
JS_TAG_BOOL = 1,
|
||||
JS_TAG_NULL = 2,
|
||||
JS_TAG_UNDEFINED = 3,
|
||||
JS_TAG_UNINITIALIZED = 4,
|
||||
JS_TAG_CATCH_OFFSET = 5,
|
||||
JS_TAG_EXCEPTION = 6,
|
||||
JS_TAG_FLOAT64 = 7,
|
||||
};
|
||||
|
||||
typedef struct JSRefCountHeader {
|
||||
int ref_count;
|
||||
} JSRefCountHeader;
|
||||
|
||||
struct JSString {
|
||||
JSRefCountHeader header;
|
||||
uint32_t len : 31;
|
||||
uint8_t is_wide_char : 1;
|
||||
uint32_t hash : 30;
|
||||
uint8_t atom_type : 2;
|
||||
uint32_t hash_next;
|
||||
|
||||
union {
|
||||
uint8_t str8[0];
|
||||
uint16_t str16[0];
|
||||
} u;
|
||||
};
|
||||
|
||||
typedef union JSValueUnion {
|
||||
int32_t int32;
|
||||
double float64;
|
||||
void *ptr;
|
||||
} JSValueUnion;
|
||||
|
||||
typedef struct JSValue {
|
||||
JSValueUnion u;
|
||||
int64_t tag;
|
||||
} JSValue;
|
||||
|
||||
typedef struct list_head {
|
||||
struct list_head *next, *prev;
|
||||
} list_head;
|
||||
|
||||
struct JSGCObjectHeader {
|
||||
int ref_count;
|
||||
uint8_t gc_obj_type : 4;
|
||||
uint8_t mark : 4;
|
||||
uint8_t dummy1;
|
||||
uint16_t dummy2;
|
||||
struct list_head link;
|
||||
};
|
||||
|
||||
struct JSContext {
|
||||
JSGCObjectHeader header;
|
||||
void *rt;
|
||||
struct list_head link;
|
||||
|
||||
uint16_t binary_object_count;
|
||||
int binary_object_size;
|
||||
|
||||
JSValue *array_shape;
|
||||
JSValue *class_proto;
|
||||
JSValue function_proto;
|
||||
JSValue function_ctor;
|
||||
JSValue array_ctor;
|
||||
JSValue regexp_ctor;
|
||||
JSValue promise_ctor;
|
||||
JSValue native_error_proto[8];
|
||||
JSValue iterator_proto;
|
||||
JSValue async_iterator_proto;
|
||||
JSValue array_proto_values;
|
||||
JSValue throw_type_error;
|
||||
JSValue eval_obj;
|
||||
|
||||
JSValue global_obj;
|
||||
JSValue global_var_obj;
|
||||
};
|
||||
|
||||
static uintptr_t global_instance;
|
||||
static JSContext *global_ctx;
|
||||
|
||||
HOOK_DEF(JSValue, js_eval, uintptr_t instance, JSContext *ctx, uintptr_t this_obj, uint8_t *input, uintptr_t input_len, const char *filename, unsigned int flags, unsigned int scope_idx) {
|
||||
if (global_instance == 0 || global_ctx == nullptr) {
|
||||
global_instance = instance;
|
||||
global_ctx = ctx;
|
||||
}
|
||||
|
||||
return js_eval_original(instance, ctx, this_obj, input, input_len, filename, flags, scope_idx);
|
||||
}
|
||||
|
||||
void waitForComposer(JNIEnv *, jobject) {
|
||||
while (global_instance == 0 || global_ctx == nullptr) {
|
||||
usleep(10000);
|
||||
}
|
||||
}
|
||||
|
||||
jstring composerEval(JNIEnv *env, jobject, jstring script) {
|
||||
if (!ARM64) return env->NewStringUTF("Architecture not supported");
|
||||
if (global_instance == 0 || global_ctx == nullptr) {
|
||||
return env->NewStringUTF("Composer not ready");
|
||||
}
|
||||
|
||||
auto script_str = env->GetStringUTFChars(script, nullptr);
|
||||
auto length = env->GetStringUTFLength(script);
|
||||
auto jsvalue = js_eval_original(global_instance, global_ctx, (uintptr_t) &global_ctx->global_obj, (uint8_t *) script_str, length, "<input>", 0, 0);
|
||||
env->ReleaseStringUTFChars(script, script_str);
|
||||
|
||||
if (jsvalue.tag == JS_TAG_STRING) {
|
||||
auto str = (JSString *) jsvalue.u.ptr;
|
||||
return env->NewStringUTF((const char *) str->u.str8);
|
||||
}
|
||||
|
||||
std::string result;
|
||||
switch (jsvalue.tag) {
|
||||
case JS_TAG_INT:
|
||||
result = std::to_string(jsvalue.u.int32);
|
||||
break;
|
||||
case JS_TAG_BOOL:
|
||||
result = jsvalue.u.int32 ? "true" : "false";
|
||||
break;
|
||||
case JS_TAG_NULL:
|
||||
result = "null";
|
||||
break;
|
||||
case JS_TAG_UNDEFINED:
|
||||
result = "undefined";
|
||||
break;
|
||||
case JS_TAG_OBJECT:
|
||||
result = "[object Object]";
|
||||
break;
|
||||
case JS_TAG_EXCEPTION:
|
||||
result = "Failed to evaluate script";
|
||||
break;
|
||||
case JS_TAG_FLOAT64:
|
||||
result = std::to_string(jsvalue.u.float64);
|
||||
break;
|
||||
default:
|
||||
result = "[unknown tag " + std::to_string(jsvalue.tag) + "]";
|
||||
break;
|
||||
}
|
||||
|
||||
return env->NewStringUTF(result.c_str());
|
||||
}
|
||||
|
||||
void init() {
|
||||
if (!ARM64) return;
|
||||
auto js_eval_ptr = util::find_signature(
|
||||
common::client_module.base,
|
||||
common::client_module.size,
|
||||
"00 E4 00 6F 29 00 80 52 76 00 04 8B",
|
||||
-0x28
|
||||
);
|
||||
if (js_eval_ptr == 0) {
|
||||
LOGE("js_eval_ptr signature not found");
|
||||
return;
|
||||
}
|
||||
DobbyHook((void*) js_eval_ptr, (void *) js_eval, (void **) &js_eval_original);
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
#include <string>
|
||||
#include <dobby.h>
|
||||
#include <vector>
|
||||
#include <thread>
|
||||
|
||||
#include "logger.h"
|
||||
#include "common.h"
|
||||
@ -10,6 +11,7 @@
|
||||
#include "hooks/fstat_hook.h"
|
||||
#include "hooks/sqlite_mutex.h"
|
||||
#include "hooks/duplex_hook.h"
|
||||
#include "hooks/composer_hook.h"
|
||||
|
||||
bool JNICALL init(JNIEnv *env, jobject clazz) {
|
||||
LOGD("Initializing native");
|
||||
@ -29,13 +31,24 @@ bool JNICALL init(JNIEnv *env, jobject clazz) {
|
||||
|
||||
LOGD("client_module offset=0x%lx, size=0x%zx", client_module.base, client_module.size);
|
||||
|
||||
AssetHook::init(env);
|
||||
UnaryCallHook::init(env);
|
||||
FstatHook::init();
|
||||
SqliteMutexHook::init();
|
||||
DuplexHook::init(env);
|
||||
auto threads = std::vector<std::thread>();
|
||||
|
||||
util::remap_sections(BUILD_PACKAGE);
|
||||
#define RUN(body) \
|
||||
threads.push_back(std::thread([&] { body; }))
|
||||
|
||||
RUN(UnaryCallHook::init(env));
|
||||
RUN(AssetHook::init(env));
|
||||
RUN(FstatHook::init());
|
||||
RUN(SqliteMutexHook::init());
|
||||
RUN(DuplexHook::init(env));
|
||||
if (common::native_config->composer_hooks) {
|
||||
RUN(ComposerHook::init());
|
||||
}
|
||||
RUN(util::remap_sections(BUILD_PACKAGE));
|
||||
|
||||
for (auto &thread : threads) {
|
||||
thread.join();
|
||||
}
|
||||
|
||||
LOGD("Native initialized");
|
||||
return true;
|
||||
@ -49,6 +62,7 @@ void JNICALL load_config(JNIEnv *env, jobject, jobject config_object) {
|
||||
native_config->disable_bitmoji = GET_CONFIG_BOOL("disableBitmoji");
|
||||
native_config->disable_metrics = GET_CONFIG_BOOL("disableMetrics");
|
||||
native_config->hook_asset_open = GET_CONFIG_BOOL("hookAssetOpen");
|
||||
native_config->composer_hooks = GET_CONFIG_BOOL("composerHooks");
|
||||
}
|
||||
|
||||
void JNICALL lock_database(JNIEnv *env, jobject, jstring database_name, jobject runnable) {
|
||||
@ -80,6 +94,8 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *_) {
|
||||
methods.push_back({"init", "()Z", (void *)init});
|
||||
methods.push_back({"loadConfig", "(L" BUILD_NAMESPACE "/NativeConfig;)V", (void *)load_config});
|
||||
methods.push_back({"lockDatabase", "(Ljava/lang/String;Ljava/lang/Runnable;)V", (void *)lock_database});
|
||||
methods.push_back({"waitForComposer", "()V", (void *) ComposerHook::waitForComposer});
|
||||
methods.push_back({"composerEval", "(Ljava/lang/String;)Ljava/lang/String;",(void *) ComposerHook::composerEval});
|
||||
|
||||
env->RegisterNatives(env->FindClass(std::string(BUILD_NAMESPACE "/NativeLib").c_str()), methods.data(), methods.size());
|
||||
return JNI_VERSION_1_6;
|
||||
|
@ -4,4 +4,5 @@ data class NativeConfig(
|
||||
val disableBitmoji: Boolean = false,
|
||||
val disableMetrics: Boolean = false,
|
||||
val hookAssetOpen: Boolean = false,
|
||||
val composerHooks: Boolean = false,
|
||||
)
|
@ -62,4 +62,6 @@ class NativeLib {
|
||||
private external fun init(): Boolean
|
||||
private external fun loadConfig(config: NativeConfig)
|
||||
private external fun lockDatabase(name: String, callback: Runnable)
|
||||
external fun waitForComposer()
|
||||
external fun composerEval(code: String): String?
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user