(bot): fix github workflows
This commit is contained in:
16
.github/workflows/debug.yml
vendored
16
.github/workflows/debug.yml
vendored
@@ -38,10 +38,10 @@ jobs:
|
||||
uses: SnapEnhance/sign-android-release@master
|
||||
with:
|
||||
releaseDirectory: app/build/outputs/apk/armv8/debug/
|
||||
signingKeyBase64: ${{ secrets.JAVA_KEYSTORE_DATA }}
|
||||
alias: ${{ secrets.KEY_ALIAS }}
|
||||
keyStorePassword: ${{ secrets.KEYSTORE_PASSWORD }}
|
||||
keyPassword: ${{ secrets.KEY_PASSWORD }}
|
||||
signingKeyBase64: ${{ secrets.PS_BASE_64 }}
|
||||
alias: ${{ secrets.PS_RELEASE_KEY_ALIAS }}
|
||||
keyStorePassword: ${{ secrets.PS_RELEASE_STORE_PASSWORD }}
|
||||
keyPassword: ${{ secrets.PS_RELEASE_KEY_PASSWORD }}
|
||||
env:
|
||||
BUILD_TOOLS_VERSION: ${{ env.BUILD_TOOL_VERSION }}
|
||||
- name: Get current build version
|
||||
@@ -90,10 +90,10 @@ jobs:
|
||||
uses: SnapEnhance/sign-android-release@master
|
||||
with:
|
||||
releaseDirectory: app/build/outputs/apk/armv7/debug/
|
||||
signingKeyBase64: ${{ secrets.JAVA_KEYSTORE_DATA }}
|
||||
alias: ${{ secrets.KEY_ALIAS }}
|
||||
keyStorePassword: ${{ secrets.KEYSTORE_PASSWORD }}
|
||||
keyPassword: ${{ secrets.KEY_PASSWORD }}
|
||||
signingKeyBase64: ${{ secrets.PS_BASE_64 }}
|
||||
alias: ${{ secrets.PS_RELEASE_KEY_ALIAS }}
|
||||
keyStorePassword: ${{ secrets.PS_RELEASE_STORE_PASSWORD }}
|
||||
keyPassword: ${{ secrets.PS_RELEASE_KEY_PASSWORD }}
|
||||
env:
|
||||
BUILD_TOOLS_VERSION: ${{ env.BUILD_TOOL_VERSION }}
|
||||
- name: Get current build version
|
||||
|
||||
@@ -5,8 +5,6 @@ import android.os.ParcelFileDescriptor
|
||||
import android.os.ParcelFileDescriptor.AutoCloseInputStream
|
||||
import android.os.ParcelFileDescriptor.AutoCloseOutputStream
|
||||
import me.eternal.purrfectsnap.bridge.storage.FileHandle
|
||||
import me.eternal.purrfectsnap.common.bridge.FileHandleScope.entries
|
||||
import me.eternal.purrfectsnap.common.bridge.InternalFileHandleType.entries
|
||||
import me.eternal.purrfectsnap.common.util.LazyBridgeValue
|
||||
import me.eternal.purrfectsnap.common.util.lazyBridge
|
||||
import java.io.File
|
||||
@@ -96,4 +94,3 @@ open class FileHandleWrapper(
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,228 +0,0 @@
|
||||
package me.eternal.purrfectsnap.core.features.impl.experiments
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
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.Alignment
|
||||
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 kotlinx.coroutines.launch
|
||||
import me.eternal.purrfectsnap.common.bridge.FileHandleScope
|
||||
import me.eternal.purrfectsnap.common.bridge.toWrapper
|
||||
import me.eternal.purrfectsnap.common.ui.createComposeAlertDialog
|
||||
import me.eternal.purrfectsnap.core.PurrfectSnap
|
||||
import me.eternal.purrfectsnap.core.features.Feature
|
||||
import me.eternal.purrfectsnap.core.features.impl.downloader.MediaDownloader
|
||||
import me.eternal.purrfectsnap.core.util.hook.HookStage
|
||||
import me.eternal.purrfectsnap.core.util.hook.Hooker
|
||||
import me.eternal.purrfectsnap.core.util.hook.hook
|
||||
import me.eternal.purrfectsnap.core.wrapper.impl.composer.ComposerMarshaller
|
||||
import me.eternal.purrfectsnap.nativelib.NativeLib
|
||||
import java.lang.reflect.Proxy
|
||||
import kotlin.math.absoluteValue
|
||||
import kotlin.random.Random
|
||||
|
||||
class ComposerHooks: Feature("ComposerHooks") {
|
||||
private val config by lazy { context.config.experimental.nativeHooks.composerHooks }
|
||||
private val getImportsFunctionName = Random.nextLong().absoluteValue.toString(16)
|
||||
|
||||
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("return 1 + 2") }
|
||||
|
||||
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 {
|
||||
const retVal = (() => { $codeContent })();
|
||||
if (typeof retVal === 'undefined') {
|
||||
return 'undefined';
|
||||
}
|
||||
if (retVal === null) {
|
||||
return 'null';
|
||||
}
|
||||
try {
|
||||
return JSON.stringify(retVal, null, 2);
|
||||
} catch {
|
||||
return retVal.toString();
|
||||
}
|
||||
} catch (e) {
|
||||
return e.toString()
|
||||
}
|
||||
})()
|
||||
""".trimIndent()) ?: "(no result)").toString().also {
|
||||
context.log.verbose("result: $it", "ComposerConsole")
|
||||
}
|
||||
}
|
||||
}
|
||||
) {
|
||||
Text("Run")
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
Text(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun newComposerFunction(block: ComposerMarshaller.() -> Boolean): Any? {
|
||||
val composerFunctionClass = findClass("com.snap.composer.callable.ComposerFunction")
|
||||
return Proxy.newProxyInstance(
|
||||
composerFunctionClass.classLoader,
|
||||
arrayOf(composerFunctionClass)
|
||||
) { _, method, args ->
|
||||
if (method.name != "perform") return@newProxyInstance null
|
||||
block(ComposerMarshaller(args?.get(0) ?: return@newProxyInstance false))
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun init() {
|
||||
if (config.globalState != true) return
|
||||
|
||||
val importedFunctions = mutableMapOf<String, Any?>()
|
||||
|
||||
fun composerFunction(name: String, block: ComposerMarshaller.() -> Unit) {
|
||||
importedFunctions[name] = newComposerFunction {
|
||||
block(this)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
composerFunction("getConfig") {
|
||||
pushUntyped(mapOf<String, Any>(
|
||||
"operaDownloadButton" to context.config.downloader.operaDownloadButton.get(),
|
||||
"bypassCameraRollLimit" to config.bypassCameraRollLimit.get(),
|
||||
"showFirstCreatedUsername" to config.showFirstCreatedUsername.get(),
|
||||
"composerLogs" to config.composerLogs.get(),
|
||||
"customSelfDestructSnapDelay" to config.customSelfDestructSnapDelay.get(),
|
||||
))
|
||||
}
|
||||
|
||||
composerFunction("showToast") {
|
||||
if (getSize() < 1) return@composerFunction
|
||||
context.shortToast(getUntyped(0) as? String ?: return@composerFunction)
|
||||
}
|
||||
|
||||
composerFunction("downloadLastOperaMedia") {
|
||||
context.feature(MediaDownloader::class).downloadLastOperaMediaAsync(getUntyped(0) == true)
|
||||
}
|
||||
|
||||
composerFunction("getFriendOriginalUsername") {
|
||||
if (getSize() < 1) return@composerFunction
|
||||
val username = getUntyped(0) as? String ?: return@composerFunction
|
||||
|
||||
runCatching {
|
||||
pushUntyped(context.database.getFriendOriginalUsername(username))
|
||||
}.onFailure {
|
||||
pushUntyped(null)
|
||||
}
|
||||
}
|
||||
|
||||
composerFunction("log") {
|
||||
if (getSize() < 2) return@composerFunction
|
||||
val logLevel = getUntyped(0) as? String ?: return@composerFunction
|
||||
val message = getUntyped(1) as? String ?: return@composerFunction
|
||||
|
||||
val tag = "ComposerLogs"
|
||||
|
||||
when (logLevel) {
|
||||
"log" -> context.log.verbose(message, tag)
|
||||
"debug" -> context.log.debug(message, tag)
|
||||
"info" -> context.log.info(message, tag)
|
||||
"warn" -> context.log.warn(message, tag)
|
||||
"error" -> context.log.error(message, tag)
|
||||
}
|
||||
}
|
||||
|
||||
fun loadHooks() {
|
||||
if (!NativeLib.initialized) {
|
||||
context.log.error("ComposerHooks cannot be loaded without NativeLib")
|
||||
return
|
||||
}
|
||||
val loaderScript = runCatching {
|
||||
context.fileHandlerManager.getFileHandle(FileHandleScope.COMPOSER.key, "loader.js").toWrapper().readBytes().toString(Charsets.UTF_8)
|
||||
}.onFailure {
|
||||
context.log.error("Failed to load composer loader script", it)
|
||||
}.getOrNull() ?: return
|
||||
context.native.setComposerLoader("""
|
||||
const i = setInterval(() => {
|
||||
try {
|
||||
const _runtimeName = "${if (PurrfectSnap.classCache.nativeBridge.name == "com.snapchat.client.valdi.NativeBridge") "valdi" else "composer"}";
|
||||
require(_runtimeName + '_core/DeviceBridge').getDisplayWidth();
|
||||
clearInterval(i);
|
||||
(() => { const _getImportsFunctionName = "$getImportsFunctionName"; $loaderScript })();
|
||||
} catch (e) {}
|
||||
}, 200)
|
||||
""".trimIndent().trim())
|
||||
}
|
||||
|
||||
loadHooks()
|
||||
|
||||
if (config.composerConsole.get()) {
|
||||
context.inAppOverlay.addCustomComposable {
|
||||
FilledIconButton(
|
||||
onClick = {
|
||||
composerConsole.show()
|
||||
},
|
||||
modifier = Modifier.align(Alignment.TopEnd).padding(top = 100.dp, end = 16.dp)
|
||||
) {
|
||||
Icon(Icons.Default.BugReport, contentDescription = "Debug Console")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PurrfectSnap.classCache.nativeBridge.hook("registerNativeModuleFactory", HookStage.BEFORE) { param ->
|
||||
val moduleFactory = param.argNullable<Any>(1) ?: return@hook
|
||||
|
||||
Hooker.ephemeralHookObjectMethod(moduleFactory.javaClass, moduleFactory, "loadModule", HookStage.AFTER) { methodParam ->
|
||||
val result = methodParam.getResult() as? MutableMap<String, Any?> ?: return@ephemeralHookObjectMethod
|
||||
|
||||
val looksLikeDeviceBridge = result.containsKey("getDisplayWidth") || result.containsKey("getDisplayHeight")
|
||||
if (!looksLikeDeviceBridge) return@ephemeralHookObjectMethod
|
||||
if (result.containsKey(getImportsFunctionName)) return@ephemeralHookObjectMethod
|
||||
|
||||
result[getImportsFunctionName] = newComposerFunction {
|
||||
pushUntyped(importedFunctions)
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package me.eternal.purrfectsnap.core.wrapper.impl.composer
|
||||
|
||||
import de.robv.android.xposed.XposedHelpers
|
||||
import me.eternal.purrfectsnap.core.wrapper.AbstractWrapper
|
||||
import java.lang.ref.WeakReference
|
||||
import java.lang.reflect.Proxy
|
||||
|
||||
class ComposerContext(obj: Any): AbstractWrapper(obj) {
|
||||
val componentPath by field<String>("componentPath")
|
||||
val moduleName by field<String>("moduleName")
|
||||
val componentContext by field<WeakReference<Any?>>("componentContext")
|
||||
|
||||
val viewModel: Any?
|
||||
get() = runCatching { XposedHelpers.getObjectField(instanceNonNull(), "innerViewModel") }.getOrNull()
|
||||
?: runCatching { XposedHelpers.getObjectField(instanceNonNull(), "viewModel") }.getOrNull()
|
||||
?: instanceNonNull()::class.java.methods.firstOrNull { it.name == "getViewModel" && it.parameterTypes.isEmpty() }?.invoke(instanceNonNull())
|
||||
|
||||
fun enqueueNextRenderCallback(callback: () -> Unit) {
|
||||
val method = instanceNonNull()::class.java.methods.firstOrNull {
|
||||
it.name == "onNextLayout"
|
||||
}
|
||||
method?.invoke(instanceNonNull(), Proxy.newProxyInstance(
|
||||
instanceNonNull()::class.java.classLoader,
|
||||
arrayOf(method.parameterTypes[0])
|
||||
) { _, _, _ ->
|
||||
callback()
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package me.eternal.purrfectsnap.core.wrapper.impl.composer
|
||||
|
||||
import me.eternal.purrfectsnap.core.wrapper.AbstractWrapper
|
||||
|
||||
class ComposerMarshaller(obj: Any): AbstractWrapper(obj) {
|
||||
private val getUntypedMethod by lazy { instanceNonNull().javaClass.methods.first { it.name == "getUntyped" } }
|
||||
private val getSizeMethod by lazy { instanceNonNull().javaClass.methods.first { it.name == "getSize" } }
|
||||
private val pushUntypedMethod by lazy { instanceNonNull().javaClass.methods.first { it.name == "pushUntyped" } }
|
||||
|
||||
fun getUntyped(index: Int): Any? = getUntypedMethod.invoke(instanceNonNull(), index)
|
||||
fun getSize() = getSizeMethod.invoke(instanceNonNull()) as Int
|
||||
fun pushUntyped(value: Any?): Any? = pushUntypedMethod.invoke(instanceNonNull(), value)
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
package me.eternal.purrfectsnap.core.wrapper.impl.composer
|
||||
|
||||
import me.eternal.purrfectsnap.core.PurrfectSnap
|
||||
import me.eternal.purrfectsnap.core.wrapper.AbstractWrapper
|
||||
import java.lang.reflect.Proxy
|
||||
|
||||
fun createComposerFunction(block: (args: Array<*>) -> Any?): Any {
|
||||
val adapter = PurrfectSnap.classCache.composerFunctionActionAdapter
|
||||
?: throw IllegalStateException("composerFunctionActionAdapter not available")
|
||||
val action = PurrfectSnap.classCache.composerAction
|
||||
?: throw IllegalStateException("composerAction not available")
|
||||
|
||||
return adapter.constructors.first().newInstance(
|
||||
Proxy.newProxyInstance(
|
||||
action.classLoader,
|
||||
arrayOf(action),
|
||||
) { _, _, args ->
|
||||
block(args?.get(0) as Array<*>)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
class ComposerViewNode(obj: Long) : AbstractWrapper(obj) {
|
||||
companion object {
|
||||
fun fromNode(composerViewNode: Any?): ComposerViewNode? {
|
||||
return (composerViewNode?.javaClass?.methods?.firstOrNull {
|
||||
it.name == "getNativeHandle"
|
||||
}?.invoke(composerViewNode) as? Long)?.let { ComposerViewNode(it) } ?: return null
|
||||
}
|
||||
}
|
||||
|
||||
fun getAttribute(name: String): Any? {
|
||||
return PurrfectSnap.classCache.nativeBridge?.methods?.firstOrNull {
|
||||
it.name == "getValueForAttribute"
|
||||
}?.invoke(null, instanceNonNull(), name)
|
||||
}
|
||||
|
||||
fun setAttribute(name: String, value: Any) {
|
||||
PurrfectSnap.classCache.nativeBridge?.methods?.firstOrNull {
|
||||
it.name == "setValueForAttribute"
|
||||
}?.invoke(null, instanceNonNull(), name, value, false)
|
||||
}
|
||||
|
||||
fun getChildren(): List<ComposerViewNode> {
|
||||
val children = PurrfectSnap.classCache.nativeBridge?.methods?.firstOrNull {
|
||||
it.name == "getRetainedViewNodeChildren"
|
||||
}?.invoke(null, instanceNonNull(), 1) as? LongArray ?: return emptyList()
|
||||
return children.map { ComposerViewNode(it) }
|
||||
}
|
||||
|
||||
fun getClassName(): String {
|
||||
return PurrfectSnap.classCache.nativeBridge?.methods?.firstOrNull {
|
||||
it.name == "getViewClassName"
|
||||
}?.invoke(null, instanceNonNull())?.toString() ?: ""
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return PurrfectSnap.classCache.nativeBridge?.methods?.firstOrNull {
|
||||
it.name == "getViewNodeDebugDescription"
|
||||
}?.invoke(null, instanceNonNull())?.toString() ?: ""
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ kotlin.code.style=official
|
||||
android.nonTransitiveRClass=true
|
||||
org.gradle.configuration-cache=true
|
||||
org.gradle.configuration-cache.problems=warn
|
||||
org.gradle.java.home=C\:\\Program\ Files\\Java\\jdk-21
|
||||
nativeAbis=arm64-v8a
|
||||
|
||||
APP_VERSION_NAME=1.1.0
|
||||
|
||||
@@ -1,287 +0,0 @@
|
||||
#![allow(dead_code, unused_imports)]
|
||||
|
||||
use super::util::composer_utils::{ComposerModule, ModuleTag};
|
||||
use std::{collections::HashMap, ffi::{c_void, CStr}, sync::{atomic::{AtomicPtr, Ordering}, Mutex}};
|
||||
use jni::{objects::JString, sys::jobject, JNIEnv};
|
||||
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;
|
||||
const JS_TAG_BIG_FLOAT: i64 = -9;
|
||||
const JS_TAG_SYMBOL: i64 = -8;
|
||||
const JS_TAG_STRING: i64 = -7;
|
||||
const JS_TAG_MODULE: i64 = -3;
|
||||
const JS_TAG_FUNCTION_BYTECODE: i64 = -2;
|
||||
const JS_TAG_OBJECT: i64 = -1;
|
||||
const JS_TAG_INT: i64 = 0;
|
||||
const JS_TAG_BOOL: i64 = 1;
|
||||
const JS_TAG_NULL: i64 = 2;
|
||||
const JS_TAG_UNDEFINED: i64 = 3;
|
||||
const JS_TAG_UNINITIALIZED: i64 = 4;
|
||||
const JS_TAG_CATCH_OFFSET: i64 = 5;
|
||||
const JS_TAG_EXCEPTION: i64 = 6;
|
||||
const JS_TAG_FLOAT64: i64 = 7;
|
||||
|
||||
#[repr(C)]
|
||||
struct JsString {
|
||||
/*
|
||||
original structure :
|
||||
struct JSString {
|
||||
struct JSRefCountHeader {
|
||||
int ref_count;
|
||||
};
|
||||
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;
|
||||
};
|
||||
*/
|
||||
pad: [u32; 4],
|
||||
str8: [u8; 0],
|
||||
str16: [u16; 0],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
union JsValueUnion {
|
||||
int32: i32,
|
||||
float64: f64,
|
||||
ptr: *mut c_void,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
struct JsValue {
|
||||
u: JsValueUnion,
|
||||
tag: i64,
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
let path = Lazy::new(|| CStr::from_ptr(arg1).to_str().unwrap());
|
||||
if !handle.is_null() && path.starts_with("bridge_observables") {
|
||||
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();
|
||||
let mut new_tags = Vec::new();
|
||||
|
||||
for (tag1, _) in tags.iter_mut() {
|
||||
let name = tag1.to_string().unwrap();
|
||||
if !name.ends_with("src/utils/converter.js") {
|
||||
continue;
|
||||
}
|
||||
|
||||
let old_file_name = name.split_once(".").unwrap().0.to_owned() + rand::random::<u32>().to_string().as_str();
|
||||
tag1.set_buffer((old_file_name.to_owned() + ".js").as_bytes().to_vec());
|
||||
let original_module_path = path.split_once(".").unwrap().0.to_owned() + "/" + &old_file_name;
|
||||
|
||||
let hooked_module = format!("{};module.exports = require(\"{}\");", composer_loader, original_module_path);
|
||||
|
||||
new_tags.push(
|
||||
(
|
||||
ModuleTag::new(128, name.as_bytes().to_vec()),
|
||||
ModuleTag::new(128, hooked_module.as_bytes().to_vec())
|
||||
)
|
||||
);
|
||||
|
||||
debug!("composer loader injected in {}", name);
|
||||
break;
|
||||
}
|
||||
|
||||
tags.extend(new_tags);
|
||||
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 GLOBAL_INSTANCE: AtomicPtr<c_void> = AtomicPtr::new(std::ptr::null_mut());
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
static GLOBAL_CTX: AtomicPtr<c_void> = AtomicPtr::new(std::ptr::null_mut());
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
static JS_EVAL_ORIGINAL2: Lazy<Mutex<Option<unsafe extern "C" fn(*mut c_void, *mut c_void, *mut c_void, *mut u8, usize, *const u8, u32) -> JsValue>>> = Lazy::new(|| Mutex::new(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| {
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
{
|
||||
GLOBAL_INSTANCE.store(arg0, Ordering::Relaxed);
|
||||
GLOBAL_CTX.store(arg1, Ordering::Relaxed);
|
||||
}
|
||||
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 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(target_arch = "aarch64")]
|
||||
{
|
||||
let mut env = env;
|
||||
|
||||
let script_str = match get_jni_string(&mut env, script) {
|
||||
Ok(s) => s,
|
||||
Err(_) => return std::ptr::null_mut(),
|
||||
};
|
||||
|
||||
if JS_EVAL_ORIGINAL2.lock().unwrap().is_none() || GLOBAL_INSTANCE.load(Ordering::Relaxed).is_null() || GLOBAL_CTX.load(Ordering::Relaxed).is_null() {
|
||||
if let Ok(s) = env.new_string("Composer hook not initialized") {
|
||||
return s.into_raw();
|
||||
}
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
|
||||
let script_length = script_str.len();
|
||||
|
||||
let js_value = {
|
||||
let js_eval_fn = JS_EVAL_ORIGINAL2.lock().unwrap();
|
||||
let global_instance = GLOBAL_INSTANCE.load(Ordering::Relaxed);
|
||||
let global_ctx = GLOBAL_CTX.load(Ordering::Relaxed);
|
||||
|
||||
js_eval_fn.unwrap()(
|
||||
global_instance,
|
||||
global_ctx,
|
||||
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;
|
||||
match CStr::from_ptr((*string).str8.as_ptr() as *const u8).to_str() {
|
||||
Ok(s) => s.to_string(),
|
||||
Err(_) => "[invalid string]".to_string(),
|
||||
}
|
||||
} 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" }.to_string()
|
||||
} else if js_value.tag == JS_TAG_NULL {
|
||||
"null".to_string()
|
||||
} else if js_value.tag == JS_TAG_UNDEFINED {
|
||||
"undefined".to_string()
|
||||
} else if js_value.tag == JS_TAG_OBJECT {
|
||||
"[object]".to_string()
|
||||
} 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".to_string()
|
||||
} else {
|
||||
format!("[unknown tag {}]", js_value.tag)
|
||||
};
|
||||
|
||||
if let Ok(s) = env.new_string(result) {
|
||||
return s.into_raw();
|
||||
}
|
||||
}
|
||||
|
||||
std::ptr::null_mut()
|
||||
}
|
||||
|
||||
pub fn init() {
|
||||
if !config::native_config().composer_hooks {
|
||||
return
|
||||
}
|
||||
|
||||
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")]
|
||||
{
|
||||
let 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
|
||||
).or_else(|| {
|
||||
warn!("primary js_eval signature failed, trying fallback");
|
||||
sig::find_signature(
|
||||
&common::CLIENT_MODULE,
|
||||
"00 E4 00 6F ?? ?? ?? ?? ?? ?? ?? ??", -0x28,
|
||||
"?? ?? ?? ?? ?? ??", -0x7
|
||||
)
|
||||
});
|
||||
|
||||
if let Some(signature) = signature {
|
||||
dobby_hook!(signature as *mut c_void, js_eval);
|
||||
|
||||
unsafe {
|
||||
*JS_EVAL_ORIGINAL2.lock().unwrap() = Some(std::mem::transmute(js_eval_original.unwrap()));
|
||||
}
|
||||
|
||||
debug!("js_eval {:#x}", signature);
|
||||
} else {
|
||||
warn!("Unable to find js_eval signature");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user