(bot): fix github workflows

This commit is contained in:
ΞTΞRNAL
2026-01-16 12:35:01 +05:30
committed by GitHub
9 changed files with 8 additions and 772 deletions

View File

@@ -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

View File

@@ -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(
}

View File

@@ -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
}
}
}
}
}

View File

@@ -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()
})
}
}

View File

@@ -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)
}

View File

@@ -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() ?: ""
}
}

View File

@@ -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

View File

@@ -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");
}
}
}

View File

@@ -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;
}
}