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? {
|
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() }
|
return getScriptInputStream(moduleName) { it?.bufferedReader()?.readText() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -770,6 +770,24 @@
|
|||||||
"name": "Native Hooks",
|
"name": "Native Hooks",
|
||||||
"description": "Unsafe Features that hook into Snapchat's native code",
|
"description": "Unsafe Features that hook into Snapchat's native code",
|
||||||
"properties": {
|
"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": {
|
"disable_bitmoji": {
|
||||||
"name": "Disable Bitmoji",
|
"name": "Disable Bitmoji",
|
||||||
"description": "Disables Friends Profile Bitmoji"
|
"description": "Disables Friends Profile Bitmoji"
|
||||||
|
@ -9,7 +9,14 @@ class Experimental : ConfigContainer() {
|
|||||||
val allowRunningInBackground = boolean("allow_running_in_background", true)
|
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) {
|
class NativeHooks : ConfigContainer(hasGlobalState = true) {
|
||||||
|
val composerHooks = container("composer_hooks", ComposerHooksConfig()) { requireRestart() }
|
||||||
val disableBitmoji = boolean("disable_bitmoji")
|
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(),
|
disableBitmoji = config.experimental.nativeHooks.disableBitmoji.get(),
|
||||||
disableMetrics = config.global.disableMetrics.get(),
|
disableMetrics = config.global.disableMetrics.get(),
|
||||||
hookAssetOpen = config.experimental.disableComposerModules.get().isNotEmpty(),
|
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.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import me.rhunk.snapenhance.core.ModContext
|
import me.rhunk.snapenhance.core.ModContext
|
||||||
import me.rhunk.snapenhance.core.features.impl.COFOverride
|
import me.rhunk.snapenhance.core.features.impl.*
|
||||||
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.downloader.MediaDownloader
|
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.downloader.ProfilePictureDownloader
|
||||||
import me.rhunk.snapenhance.core.features.impl.experiments.*
|
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.messaging.*
|
||||||
import me.rhunk.snapenhance.core.features.impl.spying.HalfSwipeNotifier
|
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.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.spying.StealthMode
|
||||||
import me.rhunk.snapenhance.core.features.impl.tweaks.*
|
import me.rhunk.snapenhance.core.features.impl.tweaks.*
|
||||||
import me.rhunk.snapenhance.core.features.impl.ui.*
|
import me.rhunk.snapenhance.core.features.impl.ui.*
|
||||||
@ -131,6 +126,7 @@ class FeatureManager(
|
|||||||
HideActiveMusic(),
|
HideActiveMusic(),
|
||||||
AutoOpenSnaps(),
|
AutoOpenSnaps(),
|
||||||
CustomStreaksExpirationFormat(),
|
CustomStreaksExpirationFormat(),
|
||||||
|
ComposerHooks(),
|
||||||
)
|
)
|
||||||
|
|
||||||
initializeFeatures()
|
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_bitmoji;
|
||||||
bool disable_metrics;
|
bool disable_metrics;
|
||||||
bool hook_asset_open;
|
bool hook_asset_open;
|
||||||
|
bool composer_hooks;
|
||||||
} native_config_t;
|
} native_config_t;
|
||||||
|
|
||||||
namespace common {
|
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 <string>
|
||||||
#include <dobby.h>
|
#include <dobby.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
@ -10,6 +11,7 @@
|
|||||||
#include "hooks/fstat_hook.h"
|
#include "hooks/fstat_hook.h"
|
||||||
#include "hooks/sqlite_mutex.h"
|
#include "hooks/sqlite_mutex.h"
|
||||||
#include "hooks/duplex_hook.h"
|
#include "hooks/duplex_hook.h"
|
||||||
|
#include "hooks/composer_hook.h"
|
||||||
|
|
||||||
bool JNICALL init(JNIEnv *env, jobject clazz) {
|
bool JNICALL init(JNIEnv *env, jobject clazz) {
|
||||||
LOGD("Initializing native");
|
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);
|
LOGD("client_module offset=0x%lx, size=0x%zx", client_module.base, client_module.size);
|
||||||
|
|
||||||
AssetHook::init(env);
|
auto threads = std::vector<std::thread>();
|
||||||
UnaryCallHook::init(env);
|
|
||||||
FstatHook::init();
|
|
||||||
SqliteMutexHook::init();
|
|
||||||
DuplexHook::init(env);
|
|
||||||
|
|
||||||
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");
|
LOGD("Native initialized");
|
||||||
return true;
|
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_bitmoji = GET_CONFIG_BOOL("disableBitmoji");
|
||||||
native_config->disable_metrics = GET_CONFIG_BOOL("disableMetrics");
|
native_config->disable_metrics = GET_CONFIG_BOOL("disableMetrics");
|
||||||
native_config->hook_asset_open = GET_CONFIG_BOOL("hookAssetOpen");
|
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) {
|
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({"init", "()Z", (void *)init});
|
||||||
methods.push_back({"loadConfig", "(L" BUILD_NAMESPACE "/NativeConfig;)V", (void *)load_config});
|
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({"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());
|
env->RegisterNatives(env->FindClass(std::string(BUILD_NAMESPACE "/NativeLib").c_str()), methods.data(), methods.size());
|
||||||
return JNI_VERSION_1_6;
|
return JNI_VERSION_1_6;
|
||||||
|
@ -4,4 +4,5 @@ data class NativeConfig(
|
|||||||
val disableBitmoji: Boolean = false,
|
val disableBitmoji: Boolean = false,
|
||||||
val disableMetrics: Boolean = false,
|
val disableMetrics: Boolean = false,
|
||||||
val hookAssetOpen: Boolean = false,
|
val hookAssetOpen: Boolean = false,
|
||||||
|
val composerHooks: Boolean = false,
|
||||||
)
|
)
|
@ -62,4 +62,6 @@ class NativeLib {
|
|||||||
private external fun init(): Boolean
|
private external fun init(): Boolean
|
||||||
private external fun loadConfig(config: NativeConfig)
|
private external fun loadConfig(config: NativeConfig)
|
||||||
private external fun lockDatabase(name: String, callback: Runnable)
|
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