fix(native): composer loader hook
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
import { getConfig, log } from "./imports";
|
||||
import { Module } from "./types";
|
||||
import { Module, modules } from "./types";
|
||||
|
||||
import operaDownloadButton from "./modules/operaDownloadButton";
|
||||
import firstCreatedUsername from "./modules/firstCreatedUsername";
|
||||
import bypassCameraRollSelectionLimit from "./modules/bypassCameraRollSelectionLimit";
|
||||
import "./modules/operaDownloadButton";
|
||||
import "./modules/firstCreatedUsername";
|
||||
import "./modules/bypassCameraRollSelectionLimit";
|
||||
|
||||
|
||||
try {
|
||||
@@ -15,22 +15,18 @@ try {
|
||||
})
|
||||
}
|
||||
|
||||
const modules: Module[] = [
|
||||
operaDownloadButton,
|
||||
firstCreatedUsername,
|
||||
bypassCameraRollSelectionLimit
|
||||
];
|
||||
|
||||
modules.forEach(module => {
|
||||
if (!module.enabled(config)) return
|
||||
modules.forEach(m => {
|
||||
if (!m.enabled(config)) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
module.init();
|
||||
m.init();
|
||||
} catch (e) {
|
||||
console.error(`failed to initialize module ${module.name}`, e, e.stack);
|
||||
console.error(`failed to initialize module ${m.name}`, e, e.stack);
|
||||
}
|
||||
});
|
||||
|
||||
console.log("composer modules loaded!");
|
||||
console.log("modules loaded!");
|
||||
} catch (e) {
|
||||
log("error", "Failed to load composer modules\n" + e + "\n" + e.stack)
|
||||
}
|
||||
|
||||
@@ -39,6 +39,9 @@ export interface Module {
|
||||
init: () => void
|
||||
}
|
||||
|
||||
export const modules: Module[] = []
|
||||
|
||||
export function defineModule<T extends Module>(module: T & Record<string, any>): T {
|
||||
modules.push(module)
|
||||
return module
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ abstract class Feature(
|
||||
runCatching {
|
||||
block()
|
||||
}.onFailure {
|
||||
context.log.error("Failed to run onNextActivityCreate callback", it)
|
||||
context.log.error("Failed to run defer callback", it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,22 @@
|
||||
package me.rhunk.snapenhance.core.features.impl.experiments
|
||||
|
||||
import android.os.ParcelFileDescriptor
|
||||
import android.app.Activity
|
||||
import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
import androidx.compose.foundation.layout.*
|
||||
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.material3.Button
|
||||
import androidx.compose.material3.FilledIconButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextField
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
@@ -48,7 +56,7 @@ class ComposerHooks: Feature("ComposerHooks") {
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
var result by remember { mutableStateOf("") }
|
||||
var codeContent by remember { mutableStateOf("") }
|
||||
var codeContent by remember { mutableStateOf("return 1 + 2") }
|
||||
|
||||
Text("Composer Console", fontSize = 18.sp, fontWeight = FontWeight.Bold)
|
||||
|
||||
@@ -95,8 +103,11 @@ class ComposerHooks: Feature("ComposerHooks") {
|
||||
|
||||
private val composerConsoleTag = Random.nextLong().toString()
|
||||
|
||||
private fun injectConsole() {
|
||||
val root = context.mainActivity!!.findViewById<FrameLayout>(android.R.id.content)
|
||||
private fun injectConsole(activity: Activity) {
|
||||
val root = activity.findViewById<FrameLayout>(android.R.id.content) ?: run {
|
||||
context.log.warn("Unable to find root view. Can't inject console.")
|
||||
return
|
||||
}
|
||||
root.post {
|
||||
if (root.findViewWithTag<View>(composerConsoleTag) != null) return@post
|
||||
root.addView(createComposeView(root.context) {
|
||||
@@ -202,16 +213,20 @@ class ComposerHooks: Feature("ComposerHooks") {
|
||||
context.native.setComposerLoader("""
|
||||
(() => { const _getImportsFunctionName = "$getImportsFunctionName"; $loaderScript })();
|
||||
""".trimIndent().trim())
|
||||
}
|
||||
|
||||
loadHooks()
|
||||
|
||||
onNextActivityCreate { activity ->
|
||||
if (config.composerConsole.get()) {
|
||||
injectConsole()
|
||||
injectConsole(activity)
|
||||
}
|
||||
}
|
||||
|
||||
findClass("com.snapchat.client.composer.NativeBridge").apply {
|
||||
hook("registerNativeModuleFactory", HookStage.BEFORE) { param ->
|
||||
val moduleFactory = param.argNullable<Any>(1) ?: return@hook
|
||||
if (moduleFactory.javaClass.getMethod("getModulePath").invoke(moduleFactory)?.toString() != "DeviceBridge") return@hook
|
||||
if (moduleFactory.javaClass.getMethod("getModulePath").invoke(moduleFactory)?.toString()?.contains("DeviceBridge") != true) return@hook
|
||||
Hooker.ephemeralHookObjectMethod(moduleFactory.javaClass, moduleFactory, "loadModule", HookStage.AFTER) { methodParam ->
|
||||
val result = methodParam.getResult() as? MutableMap<String, Any?> ?: return@ephemeralHookObjectMethod
|
||||
result[getImportsFunctionName] = newComposerFunction {
|
||||
@@ -219,7 +234,6 @@ class ComposerHooks: Feature("ComposerHooks") {
|
||||
true
|
||||
}
|
||||
}
|
||||
loadHooks()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
48
native/rust/Cargo.lock
generated
48
native/rust/Cargo.lock
generated
@@ -78,6 +78,10 @@ name = "cc"
|
||||
version = "1.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2aba8f4e9906c7ce3c73463f62a7f0c65183ada1a2d47e397cc8810827f9694f"
|
||||
dependencies = [
|
||||
"jobserver",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cesu8"
|
||||
@@ -237,6 +241,15 @@ version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
version = "0.1.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.69"
|
||||
@@ -318,6 +331,12 @@ version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.86"
|
||||
@@ -463,6 +482,7 @@ dependencies = [
|
||||
"paste",
|
||||
"procfs",
|
||||
"serde_json",
|
||||
"zstd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -722,3 +742,31 @@ name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "zstd"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9"
|
||||
dependencies = [
|
||||
"zstd-safe",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd-safe"
|
||||
version = "7.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa556e971e7b568dc775c136fc9de8c779b1c2fc3a63defaafadffdbd3181afa"
|
||||
dependencies = [
|
||||
"zstd-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd-sys"
|
||||
version = "2.0.12+zstd.1.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a4e40c320c3cb459d9a9ff6de98cff88f4751ee9275d140e2be94a2b74e4c13"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
@@ -17,3 +17,4 @@ once_cell = "1.19.0"
|
||||
paste = "1.0.15"
|
||||
procfs = "0.16.0"
|
||||
serde_json = "1.0.120"
|
||||
zstd = "0.13.2"
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#![allow(dead_code, unused_mut)]
|
||||
#![allow(dead_code, unused_imports)]
|
||||
|
||||
use std::{cell::Cell, ffi::{c_void, CStr}, sync::Mutex};
|
||||
use super::util::composer_utils::ComposerModule;
|
||||
use std::{collections::HashMap, ffi::{c_void, CStr}, sync::Mutex};
|
||||
use jni::{objects::JString, sys::jobject, JNIEnv};
|
||||
use crate::{common, config, def_hook, dobby_hook, sig, util::get_jni_string};
|
||||
use once_cell::sync::Lazy;
|
||||
use crate::{common, config, def_hook, dobby_hook, dobby_hook_sym, sig, util::get_jni_string};
|
||||
|
||||
const JS_TAG_BIG_DECIMAL: i64 = -11;
|
||||
const JS_TAG_BIG_INT: i64 = -10;
|
||||
@@ -61,124 +63,181 @@ struct JsValue {
|
||||
tag: i64,
|
||||
}
|
||||
|
||||
static mut GLOBAL_INSTANCE: Option<*mut c_void> = None;
|
||||
static mut GLOBAL_CTX: Option<*mut c_void> = None;
|
||||
static COMPOSER_LOADER_DATA: Mutex<Cell<Option<Box<String>>>> = Mutex::new(Cell::new(None));
|
||||
static AASSET_MAP: Lazy<Mutex<HashMap<usize, Vec<u8>>>> = Lazy::new(|| Mutex::new(HashMap::new()));
|
||||
static COMPOSER_LOADER_DATA: Mutex<Option<String>> = Mutex::new(None);
|
||||
|
||||
def_hook!(
|
||||
aasset_get_length,
|
||||
i32,
|
||||
|arg0: *mut c_void| {
|
||||
if let Some(buffer) = AASSET_MAP.lock().unwrap().get(&(arg0 as usize)) {
|
||||
return buffer.len() as i32;
|
||||
}
|
||||
aasset_get_length_original.unwrap()(arg0)
|
||||
}
|
||||
);
|
||||
|
||||
def_hook!(
|
||||
aasset_get_buffer,
|
||||
*const c_void,
|
||||
|arg0: *mut c_void| {
|
||||
if let Some(buffer) = AASSET_MAP.lock().unwrap().get(&(arg0 as usize)) {
|
||||
return buffer.as_ptr() as *const c_void;
|
||||
}
|
||||
aasset_get_buffer_original.unwrap()(arg0)
|
||||
}
|
||||
);
|
||||
|
||||
def_hook!(
|
||||
aasset_manager_open,
|
||||
*mut c_void,
|
||||
|arg0: *mut c_void, arg1: *const u8, arg2: i32| {
|
||||
let handle = aasset_manager_open_original.unwrap()(arg0, arg1, arg2);
|
||||
|
||||
if !handle.is_null() && CStr::from_ptr(arg1).to_str().unwrap().ends_with("blizzard.composermodule") {
|
||||
let asset_buffer = aasset_get_buffer_original.unwrap()(handle);
|
||||
let asset_length = aasset_get_length_original.unwrap()(handle);
|
||||
debug!("asset buffer: {:p}, length: {}", asset_buffer, asset_length);
|
||||
|
||||
let composer_loader = COMPOSER_LOADER_DATA.lock().unwrap().clone().expect("No composer loader data");
|
||||
|
||||
let archive_buffer: Vec<u8> = std::slice::from_raw_parts(asset_buffer as *const u8, asset_length as usize).to_vec();
|
||||
let decompressed = zstd::stream::decode_all(&archive_buffer[..]).expect("Failed to decompress composer archive");
|
||||
let mut composer_module = ComposerModule::parse(decompressed).expect("Failed to parse composer module");
|
||||
|
||||
let mut tags = composer_module.get_tags();
|
||||
|
||||
for (tag1, tag2) in tags.iter_mut() {
|
||||
if tag1.to_string().unwrap().ends_with("BlizzardEventLogger.js") {
|
||||
let mut buffer = Vec::new();
|
||||
buffer.extend(composer_loader.as_bytes());
|
||||
buffer.extend(tag2.get_buffer());
|
||||
tag2.set_buffer(buffer);
|
||||
debug!("composer loader injected in {}", tag1.to_string().unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
composer_module.set_tags(tags);
|
||||
|
||||
let compressed = composer_module.to_bytes();
|
||||
let compressed = zstd::stream::encode_all(&compressed[..], 3).expect("Failed to compress");
|
||||
|
||||
AASSET_MAP.lock().unwrap().insert(handle as usize, compressed);
|
||||
}
|
||||
handle
|
||||
}
|
||||
);
|
||||
|
||||
def_hook!(
|
||||
aasset_close,
|
||||
c_void,
|
||||
|handle: *mut c_void| {
|
||||
AASSET_MAP.lock().unwrap().remove(&(handle as usize));
|
||||
aasset_close_original.unwrap()(handle)
|
||||
}
|
||||
);
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
static mut GLOBAL_INSTANCE: Option<*mut c_void> = None;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
static mut GLOBAL_CTX: Option<*mut c_void> = None;
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
static mut JS_EVAL_ORIGINAL2: Option<unsafe extern "C" fn(*mut c_void, *mut c_void, *mut c_void, *mut u8, usize, *const u8, u32) -> JsValue> = None;
|
||||
|
||||
def_hook!(
|
||||
js_eval,
|
||||
*mut c_void,
|
||||
|arg0: *mut c_void, arg1: *mut c_void, arg2: *mut c_void, arg3: *const u8, arg4: *const u8, arg5: *const u8, arg6: *mut c_void, arg7: u32| {
|
||||
let mut arg3 = arg3;
|
||||
let mut arg4 = arg4;
|
||||
let mut arg5 = arg5;
|
||||
|
||||
if GLOBAL_INSTANCE.is_none() || GLOBAL_CTX.is_none() {
|
||||
GLOBAL_INSTANCE = Some(arg0);
|
||||
GLOBAL_CTX = Some(arg1);
|
||||
|
||||
let mut loader_data = COMPOSER_LOADER_DATA.lock().unwrap();
|
||||
let mut loader_data = loader_data.get_mut();
|
||||
|
||||
let loader_data = loader_data.as_mut().unwrap();
|
||||
loader_data.push_str("\n");
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
{
|
||||
loader_data.push_str(CStr::from_ptr(arg3).to_str().unwrap());
|
||||
arg3 = loader_data.as_mut_ptr();
|
||||
arg4 = loader_data.len() as *const u8;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
{
|
||||
if GLOBAL_INSTANCE.is_none() || GLOBAL_CTX.is_none() {
|
||||
GLOBAL_INSTANCE = Some(arg0);
|
||||
GLOBAL_CTX = Some(arg1);
|
||||
}
|
||||
|
||||
// On arm the original JS_Eval function is inlined so the arguments are shifted
|
||||
#[cfg(target_arch = "arm")]
|
||||
{
|
||||
loader_data.push_str(CStr::from_ptr(arg4).to_str().unwrap());
|
||||
arg4 = loader_data.as_mut_ptr();
|
||||
arg5 = loader_data.len() as *const u8;
|
||||
}
|
||||
|
||||
debug!("injected composer loader!");
|
||||
} else {
|
||||
COMPOSER_LOADER_DATA.lock().unwrap().take();
|
||||
}
|
||||
|
||||
js_eval_original.unwrap()(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7)
|
||||
}
|
||||
);
|
||||
|
||||
pub fn set_composer_loader(mut env: JNIEnv, _: *mut c_void, code: JString) {
|
||||
let new_code = get_jni_string(&mut env, code).expect("Failed to get code");
|
||||
|
||||
COMPOSER_LOADER_DATA.lock().unwrap().replace(Some(Box::new(new_code)));
|
||||
let new_code = get_jni_string(&mut env, code).expect("Failed to get composer loader code");
|
||||
COMPOSER_LOADER_DATA.lock().unwrap().replace(new_code);
|
||||
}
|
||||
|
||||
#[allow(unreachable_code, unused_variables)]
|
||||
pub unsafe fn composer_eval(env: JNIEnv, _: *mut c_void, script: JString) -> jobject {
|
||||
#[cfg(not(target_arch = "aarch64"))]
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
{
|
||||
return env.new_string("Architecture not supported").unwrap().into_raw();
|
||||
let mut env = env;
|
||||
|
||||
let script_str = get_jni_string(&mut env, script).expect("Failed to get script");
|
||||
let script_length = script_str.len();
|
||||
|
||||
let js_value = JS_EVAL_ORIGINAL2.expect("No js eval found")(
|
||||
GLOBAL_INSTANCE.expect("No global instance found"),
|
||||
GLOBAL_CTX.expect("No global context found"),
|
||||
std::ptr::null_mut(),
|
||||
(script_str + "\0").as_ptr() as *mut u8,
|
||||
script_length,
|
||||
"<eval>\0".as_ptr(),
|
||||
0
|
||||
);
|
||||
|
||||
let result: String = if js_value.tag == JS_TAG_STRING {
|
||||
let string = js_value.u.ptr as *mut JsString;
|
||||
CStr::from_ptr((*string).str8.as_ptr() as *const u8).to_str().unwrap().into()
|
||||
} else if js_value.tag == JS_TAG_INT {
|
||||
js_value.u.int32.to_string()
|
||||
} else if js_value.tag == JS_TAG_BOOL {
|
||||
if js_value.u.int32 == 1 { "true" } else { "false" }.into()
|
||||
} else if js_value.tag == JS_TAG_NULL {
|
||||
"null".into()
|
||||
} else if js_value.tag == JS_TAG_UNDEFINED {
|
||||
"undefined".into()
|
||||
} else if js_value.tag == JS_TAG_OBJECT {
|
||||
"[object]".into()
|
||||
} else if js_value.tag == JS_TAG_FLOAT64 {
|
||||
js_value.u.float64.to_string()
|
||||
} else if js_value.tag == JS_TAG_EXCEPTION {
|
||||
"Failed to evaluate script".into()
|
||||
} else {
|
||||
"[unknown tag ".to_owned() + &js_value.tag.to_string() + "]".into()
|
||||
};
|
||||
|
||||
return env.new_string(result).unwrap().into_raw()
|
||||
}
|
||||
|
||||
let mut env = env;
|
||||
|
||||
let script_str = get_jni_string(&mut env, script).expect("Failed to get script");
|
||||
let script_length = script_str.len();
|
||||
|
||||
let js_value = JS_EVAL_ORIGINAL2.expect("No js eval found")(
|
||||
GLOBAL_INSTANCE.expect("No global instance found"),
|
||||
GLOBAL_CTX.expect("No global context found"),
|
||||
std::ptr::null_mut(),
|
||||
(script_str + "\0").as_ptr() as *mut u8,
|
||||
script_length,
|
||||
"<eval>\0".as_ptr(),
|
||||
0
|
||||
);
|
||||
|
||||
let result: String = if js_value.tag == JS_TAG_STRING {
|
||||
let string = js_value.u.ptr as *mut JsString;
|
||||
CStr::from_ptr((*string).str8.as_ptr() as *const u8).to_str().unwrap().into()
|
||||
} else if js_value.tag == JS_TAG_INT {
|
||||
js_value.u.int32.to_string()
|
||||
} else if js_value.tag == JS_TAG_BOOL {
|
||||
if js_value.u.int32 == 1 { "true" } else { "false" }.into()
|
||||
} else if js_value.tag == JS_TAG_NULL {
|
||||
"null".into()
|
||||
} else if js_value.tag == JS_TAG_UNDEFINED {
|
||||
"undefined".into()
|
||||
} else if js_value.tag == JS_TAG_OBJECT {
|
||||
"[object]".into()
|
||||
} else if js_value.tag == JS_TAG_FLOAT64 {
|
||||
js_value.u.float64.to_string()
|
||||
} else if js_value.tag == JS_TAG_EXCEPTION {
|
||||
"Failed to evaluate script".into()
|
||||
} else {
|
||||
"[unknown tag ".to_owned() + &js_value.tag.to_string() + "]".into()
|
||||
};
|
||||
|
||||
env.new_string(result).unwrap().into_raw()
|
||||
return env.new_string("Architecture not supported").unwrap().into_raw();
|
||||
}
|
||||
|
||||
pub fn init() {
|
||||
if !config::native_config().composer_hooks {
|
||||
return
|
||||
}
|
||||
|
||||
if let Some(signature) = sig::find_signature(
|
||||
&common::CLIENT_MODULE,
|
||||
"00 E4 00 6F 29 00 80 52 76 00 04 8B", -0x28,
|
||||
"A1 B0 07 92 81 46", -0x7
|
||||
) {
|
||||
dobby_hook!(signature as *mut c_void, js_eval);
|
||||
|
||||
unsafe {
|
||||
JS_EVAL_ORIGINAL2 = Some(std::mem::transmute(js_eval_original.unwrap()));
|
||||
}
|
||||
|
||||
debug!("js_eval {:#x}", signature);
|
||||
} else {
|
||||
warn!("Unable to find js_eval signature");
|
||||
dobby_hook_sym!("libandroid.so", "AAsset_getBuffer", aasset_get_buffer);
|
||||
dobby_hook_sym!("libandroid.so", "AAsset_getLength", aasset_get_length);
|
||||
dobby_hook_sym!("libandroid.so", "AAsset_close", aasset_close);
|
||||
dobby_hook_sym!("libandroid.so", "AAssetManager_open", aasset_manager_open);
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
{
|
||||
if let Some(signature) = sig::find_signature(
|
||||
&common::CLIENT_MODULE,
|
||||
"00 E4 00 6F 29 00 80 52 76 00 04 8B", -0x28,
|
||||
"A1 B0 07 92 81 46", -0x7
|
||||
) {
|
||||
dobby_hook!(signature as *mut c_void, js_eval);
|
||||
|
||||
unsafe {
|
||||
JS_EVAL_ORIGINAL2 = Some(std::mem::transmute(js_eval_original.unwrap()));
|
||||
}
|
||||
|
||||
debug!("js_eval {:#x}", signature);
|
||||
} else {
|
||||
warn!("Unable to find js_eval signature");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod util;
|
||||
pub mod linker_hook;
|
||||
pub mod duplex_hook;
|
||||
pub mod sqlite_hook;
|
||||
|
||||
141
native/rust/src/modules/util/composer_utils.rs
Normal file
141
native/rust/src/modules/util/composer_utils.rs
Normal file
@@ -0,0 +1,141 @@
|
||||
use std::{io::Error, string::FromUtf8Error};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ModuleTag {
|
||||
tag_type: u8,
|
||||
buffer: Vec<u8>,
|
||||
}
|
||||
|
||||
impl ModuleTag {
|
||||
pub fn new(module_type: u8, buffer: Vec<u8>) -> ModuleTag {
|
||||
ModuleTag {
|
||||
tag_type: module_type,
|
||||
buffer,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_string(&self) -> Result<String, FromUtf8Error> {
|
||||
Ok(String::from_utf8(self.buffer.clone())?)
|
||||
}
|
||||
|
||||
pub fn get_tag_type(&self) -> u8 {
|
||||
self.tag_type
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> usize {
|
||||
self.buffer.len()
|
||||
}
|
||||
|
||||
pub fn get_buffer(&self) -> &Vec<u8> {
|
||||
&self.buffer
|
||||
}
|
||||
|
||||
pub fn set_buffer(&mut self, buffer: Vec<u8>) {
|
||||
self.buffer = buffer;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ComposerModule {
|
||||
tags: Vec<(ModuleTag, ModuleTag)>, // file name => file content
|
||||
}
|
||||
|
||||
impl ComposerModule {
|
||||
pub fn parse(buffer: Vec<u8>) -> Result<ComposerModule, Error> {
|
||||
let mut offset = 0;
|
||||
let magic = u32::from_be_bytes([buffer[offset], buffer[offset + 1], buffer[offset + 2], buffer[offset + 3]]);
|
||||
|
||||
offset += 4;
|
||||
|
||||
if magic != 0x33c60001 {
|
||||
return Err(Error::new(std::io::ErrorKind::InvalidData, "Invalid magic"));
|
||||
}
|
||||
|
||||
// skip content length
|
||||
offset += 4;
|
||||
|
||||
let mut tags = Vec::new();
|
||||
|
||||
loop {
|
||||
if offset >= buffer.len() {
|
||||
break;
|
||||
}
|
||||
|
||||
fn read_u24(buffer: &Vec<u8>, offset: &mut usize) -> Result<u32, Error> {
|
||||
let b1 = buffer[*offset] as u32;
|
||||
let b2 = buffer[*offset + 1] as u32;
|
||||
let b3 = buffer[*offset + 2] as u32;
|
||||
*offset += 3;
|
||||
Ok(b1 | (b2 << 8) | (b3 << 16))
|
||||
}
|
||||
|
||||
let tag_size = read_u24(&buffer, &mut offset)?;
|
||||
let tag_type = buffer[offset];
|
||||
offset += 1;
|
||||
let tag_buffer = buffer[offset..offset + tag_size as usize].to_vec();
|
||||
offset += tag_size as usize;
|
||||
|
||||
let padding = 4 - (tag_size % 4);
|
||||
|
||||
if padding != 4 {
|
||||
offset += padding as usize;
|
||||
}
|
||||
|
||||
tags.push(ModuleTag::new(tag_type, tag_buffer));
|
||||
}
|
||||
|
||||
let tags = tags.chunks(2).map(|chunk| {
|
||||
(chunk[0].clone(), chunk[1].clone())
|
||||
}).collect();
|
||||
|
||||
Ok(ComposerModule {
|
||||
tags,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
let mut tag_buffer = Vec::new();
|
||||
|
||||
fn write_u24(buffer: &mut Vec<u8>, value: u32) {
|
||||
buffer.push((value & 0xff) as u8);
|
||||
buffer.push(((value >> 8) & 0xff) as u8);
|
||||
buffer.push(((value >> 16) & 0xff) as u8);
|
||||
}
|
||||
|
||||
fn write_tag(buffer: &mut Vec<u8>, tag: ModuleTag) {
|
||||
write_u24(buffer, tag.get_size() as u32);
|
||||
buffer.push(tag.get_tag_type());
|
||||
buffer.extend(tag.get_buffer());
|
||||
|
||||
let padding = 4 - (tag.get_size() % 4);
|
||||
|
||||
if padding != 4 {
|
||||
for _ in 0..padding {
|
||||
buffer.push(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (tag1, tag2) in &self.tags {
|
||||
write_tag(&mut tag_buffer, tag1.clone());
|
||||
write_tag(&mut tag_buffer, tag2.clone());
|
||||
}
|
||||
|
||||
let mut buffer = Vec::new();
|
||||
|
||||
buffer.extend_from_slice(&[0x33, 0xc6, 0, 1]);
|
||||
buffer.extend_from_slice(&(tag_buffer.len() as u32).to_le_bytes());
|
||||
buffer.extend(tag_buffer);
|
||||
|
||||
buffer
|
||||
}
|
||||
|
||||
pub fn get_tags(&self) -> Vec<(ModuleTag, ModuleTag)> {
|
||||
self.tags.clone()
|
||||
}
|
||||
|
||||
pub fn set_tags(&mut self, tags: Vec<(ModuleTag, ModuleTag)>) {
|
||||
self.tags = tags;
|
||||
}
|
||||
}
|
||||
|
||||
1
native/rust/src/modules/util/mod.rs
Normal file
1
native/rust/src/modules/util/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod composer_utils;
|
||||
Reference in New Issue
Block a user