mirror of
https://github.com/rhunk/SnapEnhance.git
synced 2025-06-13 13:47:47 +02:00
feat(native): database lock
- refactor native
This commit is contained in:
@ -183,7 +183,7 @@ class SnapEnhance {
|
|||||||
val libName = param.arg<String>(2)
|
val libName = param.arg<String>(2)
|
||||||
if (libName != "client") return@hook
|
if (libName != "client") return@hook
|
||||||
unhook()
|
unhook()
|
||||||
appContext.native.initOnce(appContext.androidContext.classLoader)
|
appContext.native.initOnce()
|
||||||
appContext.native.nativeUnaryCallCallback = { request ->
|
appContext.native.nativeUnaryCallCallback = { request ->
|
||||||
appContext.event.post(NativeUnaryCallEvent(request.uri, request.buffer)) {
|
appContext.event.post(NativeUnaryCallEvent(request.uri, request.buffer)) {
|
||||||
request.buffer = buffer
|
request.buffer = buffer
|
||||||
|
@ -17,6 +17,7 @@ import me.rhunk.snapenhance.common.util.ktx.getStringOrNull
|
|||||||
import me.rhunk.snapenhance.common.util.protobuf.ProtoReader
|
import me.rhunk.snapenhance.common.util.protobuf.ProtoReader
|
||||||
import me.rhunk.snapenhance.core.ModContext
|
import me.rhunk.snapenhance.core.ModContext
|
||||||
import me.rhunk.snapenhance.core.manager.Manager
|
import me.rhunk.snapenhance.core.manager.Manager
|
||||||
|
import me.rhunk.snapenhance.nativelib.NativeLib
|
||||||
|
|
||||||
|
|
||||||
enum class DatabaseType(
|
enum class DatabaseType(
|
||||||
@ -59,9 +60,15 @@ class DatabaseAccess(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private inline fun <T> SQLiteDatabase.performOperation(crossinline query: SQLiteDatabase.() -> T?): T? {
|
private fun <T> SQLiteDatabase.performOperation(query: SQLiteDatabase.() -> T?): T? {
|
||||||
return runCatching {
|
return runCatching {
|
||||||
synchronized(this) {
|
if (NativeLib.initialized && openedDatabases[DatabaseType.ARROYO] == this) {
|
||||||
|
var result: T? = null
|
||||||
|
context.native.lockNativeDatabase(DatabaseType.ARROYO.fileName) {
|
||||||
|
result = query()
|
||||||
|
}
|
||||||
|
result
|
||||||
|
} else synchronized(this) {
|
||||||
query()
|
query()
|
||||||
}
|
}
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
|
24
native/jni/src/common.h
Normal file
24
native/jni/src/common.h
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
#ifdef __aarch64__
|
||||||
|
#define ARM64 true
|
||||||
|
#else
|
||||||
|
#define ARM64 false
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
bool disable_bitmoji;
|
||||||
|
bool disable_metrics;
|
||||||
|
bool hook_asset_open;
|
||||||
|
} native_config_t;
|
||||||
|
|
||||||
|
namespace common {
|
||||||
|
static JavaVM *java_vm;
|
||||||
|
static jobject native_lib_object;
|
||||||
|
|
||||||
|
static util::module_info_t client_module;
|
||||||
|
static native_config_t *native_config = new native_config_t;
|
||||||
|
}
|
@ -1,7 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
bool disable_bitmoji;
|
|
||||||
bool disable_metrics;
|
|
||||||
bool hook_asset_open;
|
|
||||||
} native_config_t;
|
|
@ -1,16 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
namespace grpc {
|
|
||||||
typedef struct {
|
|
||||||
void* ref_counter;
|
|
||||||
size_t length;
|
|
||||||
uint8_t* data;
|
|
||||||
} ref_counted_slice_byte_buffer;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
void* reserved;
|
|
||||||
void* type;
|
|
||||||
void* compression;
|
|
||||||
ref_counted_slice_byte_buffer *slice_buffer;
|
|
||||||
} grpc_byte_buffer;
|
|
||||||
}
|
|
25
native/jni/src/hooks/asset_hook.h
Normal file
25
native/jni/src/hooks/asset_hook.h
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
|
||||||
|
namespace AssetHook {
|
||||||
|
jmethodID native_lib_on_asset_load;
|
||||||
|
AAsset* (*AAssetManager_open_original)(AAssetManager*, const char*, int) = nullptr;
|
||||||
|
|
||||||
|
AAsset* AAssetManager_open_hook(AAssetManager* mgr, const char* filename, int mode) {
|
||||||
|
if (common::native_config->hook_asset_open) {
|
||||||
|
JNIEnv *env = nullptr;
|
||||||
|
common::java_vm->GetEnv((void **)&env, JNI_VERSION_1_6);
|
||||||
|
|
||||||
|
if (!env->CallBooleanMethod(common::native_lib_object, native_lib_on_asset_load, env->NewStringUTF(filename))) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return AAssetManager_open_original(mgr, filename, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
void init(JNIEnv *env) {
|
||||||
|
native_lib_on_asset_load = env->GetMethodID(env->GetObjectClass(common::native_lib_object), "shouldLoadAsset", "(Ljava/lang/String;)Z");
|
||||||
|
DobbyHook((void *) AAssetManager_open, (void *) AAssetManager_open_hook, (void **) &AAssetManager_open_original);
|
||||||
|
}
|
||||||
|
}
|
29
native/jni/src/hooks/fstat_hook.h
Normal file
29
native/jni/src/hooks/fstat_hook.h
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace FstatHook {
|
||||||
|
auto fstat_original = (int (*)(int, struct stat *)) nullptr;
|
||||||
|
|
||||||
|
int fstat_hook(int fd, struct stat *buf) {
|
||||||
|
char name[256];
|
||||||
|
memset(name, 0, sizeof(name));
|
||||||
|
snprintf(name, sizeof(name), "/proc/self/fd/%d", fd);
|
||||||
|
readlink(name, name, sizeof(name));
|
||||||
|
|
||||||
|
std::string fileName(name);
|
||||||
|
|
||||||
|
if (common::native_config->disable_metrics && fileName.find("files/blizzardv2/queues") != std::string::npos) {
|
||||||
|
unlink(name);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (common::native_config->disable_bitmoji && fileName.find("com.snap.file_manager_4_SCContent") != std::string::npos) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fstat_original(fd, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
void init() {
|
||||||
|
DobbyHook((void *)DobbySymbolResolver("libc.so", "fstat"), (void *)fstat_hook, (void **)&fstat_original);
|
||||||
|
}
|
||||||
|
}
|
47
native/jni/src/hooks/sqlite_mutex.h
Normal file
47
native/jni/src/hooks/sqlite_mutex.h
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
namespace SqliteMutexHook {
|
||||||
|
typedef struct {
|
||||||
|
pthread_mutex_t mutex;
|
||||||
|
} sqlite3_mutex;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
void* pVfs;
|
||||||
|
void* pVdbe;
|
||||||
|
void* pDfltColl;
|
||||||
|
sqlite3_mutex* mutex;
|
||||||
|
} sqlite3;
|
||||||
|
|
||||||
|
static std::map<std::string, sqlite3_mutex *> mutex_map = {};
|
||||||
|
static int (*sqlite3_open_original)(const char *, sqlite3 **, unsigned int, const char *) = nullptr;
|
||||||
|
|
||||||
|
int sqlite3_open_hook(const char *filename, sqlite3 **ppDb, unsigned int flags, const char *zVfs) {
|
||||||
|
auto result = sqlite3_open_original(filename, ppDb, flags, zVfs);
|
||||||
|
if (result == 0) {
|
||||||
|
auto mutex = (*ppDb)->mutex;
|
||||||
|
if (mutex == nullptr) return result;
|
||||||
|
|
||||||
|
auto last_slash = strrchr(filename, '/') + 1;
|
||||||
|
if (last_slash > filename) {
|
||||||
|
LOGD("sqlite3_open_hook: %s", last_slash);
|
||||||
|
mutex_map[last_slash] = mutex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void init() {
|
||||||
|
auto open_database_sig = util::find_signature(
|
||||||
|
common::client_module.base, common::client_module.size,
|
||||||
|
ARM64 ? "FF FF 00 A9 3F 00 00 F9" : "8D B0 06 46 E7 48",
|
||||||
|
ARM64 ? -0x3C : -0x7
|
||||||
|
);
|
||||||
|
if (open_database_sig == 0) {
|
||||||
|
LOGE("sqlite3 openDatabase sig not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
DobbyHook((void *) open_database_sig, (void *) sqlite3_open_hook, (void **) &sqlite3_open_original);
|
||||||
|
}
|
||||||
|
}
|
88
native/jni/src/hooks/unary_call.h
Normal file
88
native/jni/src/hooks/unary_call.h
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../common.h"
|
||||||
|
#include "../util.h"
|
||||||
|
|
||||||
|
namespace UnaryCallHook {
|
||||||
|
namespace grpc {
|
||||||
|
typedef struct {
|
||||||
|
void* ref_counter;
|
||||||
|
size_t length;
|
||||||
|
uint8_t* data;
|
||||||
|
} ref_counted_slice_byte_buffer;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
void* reserved;
|
||||||
|
void* type;
|
||||||
|
void* compression;
|
||||||
|
ref_counted_slice_byte_buffer *slice_buffer;
|
||||||
|
} grpc_byte_buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *(*unaryCall_original)(void *, const char *, grpc::grpc_byte_buffer **, void *, void *, void *);
|
||||||
|
static jmethodID native_lib_on_unary_call_method;
|
||||||
|
|
||||||
|
void *unaryCall_hook(void *unk1, const char *uri, grpc::grpc_byte_buffer **buffer_ptr, void *unk4, void *unk5, void *unk6) {
|
||||||
|
// request without reference counter can be hooked using xposed ig
|
||||||
|
auto slice_buffer = (*buffer_ptr)->slice_buffer;
|
||||||
|
if (slice_buffer->ref_counter == 0) {
|
||||||
|
return unaryCall_original(unk1, uri, buffer_ptr, unk4, unk5, unk6);
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEnv *env = nullptr;
|
||||||
|
common::java_vm->GetEnv((void **)&env, JNI_VERSION_1_6);
|
||||||
|
|
||||||
|
auto jni_buffer_array = env->NewByteArray(slice_buffer->length);
|
||||||
|
env->SetByteArrayRegion(jni_buffer_array, 0, slice_buffer->length, (jbyte *)slice_buffer->data);
|
||||||
|
|
||||||
|
auto native_request_data_object = env->CallObjectMethod(common::native_lib_object, native_lib_on_unary_call_method, env->NewStringUTF(uri), jni_buffer_array);
|
||||||
|
|
||||||
|
if (native_request_data_object != nullptr) {
|
||||||
|
auto native_request_data_class = env->GetObjectClass(native_request_data_object);
|
||||||
|
auto is_canceled = env->GetBooleanField(native_request_data_object, env->GetFieldID(native_request_data_class, "canceled", "Z"));
|
||||||
|
|
||||||
|
if (is_canceled) {
|
||||||
|
LOGD("canceled request for %s", uri);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto new_buffer = (jbyteArray)env->GetObjectField(native_request_data_object, env->GetFieldID(native_request_data_class, "buffer", "[B"));
|
||||||
|
auto new_buffer_length = env->GetArrayLength(new_buffer);
|
||||||
|
auto new_buffer_data = env->GetByteArrayElements(new_buffer, nullptr);
|
||||||
|
|
||||||
|
//we need to allocate a new ref_counter struct and copy the old ref_counter and the new_buffer to it
|
||||||
|
const static auto ref_counter_struct_size = (uintptr_t)slice_buffer->data - (uintptr_t)slice_buffer->ref_counter;
|
||||||
|
|
||||||
|
auto new_ref_counter = malloc(ref_counter_struct_size + new_buffer_length);
|
||||||
|
//copy the old ref_counter and the native_request_data_object
|
||||||
|
memcpy(new_ref_counter, slice_buffer->ref_counter, ref_counter_struct_size);
|
||||||
|
memcpy((void *)((uintptr_t)new_ref_counter + ref_counter_struct_size), new_buffer_data, new_buffer_length);
|
||||||
|
|
||||||
|
//free the old ref_counter
|
||||||
|
free(slice_buffer->ref_counter);
|
||||||
|
|
||||||
|
//update the slice_buffer
|
||||||
|
slice_buffer->ref_counter = new_ref_counter;
|
||||||
|
slice_buffer->length = new_buffer_length;
|
||||||
|
slice_buffer->data = (uint8_t *)((uintptr_t)new_ref_counter + ref_counter_struct_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
return unaryCall_original(unk1, uri, buffer_ptr, unk4, unk5, unk6);
|
||||||
|
}
|
||||||
|
|
||||||
|
void init(JNIEnv *env) {
|
||||||
|
auto unaryCall_func = util::find_signature(
|
||||||
|
common::client_module.base, common::client_module.size,
|
||||||
|
ARM64 ? "A8 03 1F F8 C2 00 00 94" : "0A 90 00 F0 3F F9",
|
||||||
|
ARM64 ? -0x48 : -0x37
|
||||||
|
);
|
||||||
|
|
||||||
|
native_lib_on_unary_call_method = env->GetMethodID(env->GetObjectClass(common::native_lib_object), "onNativeUnaryCall", "(Ljava/lang/String;[B)L" BUILD_NAMESPACE "/NativeRequestData;");
|
||||||
|
|
||||||
|
if (unaryCall_func != 0) {
|
||||||
|
DobbyHook((void *)unaryCall_func, (void *)unaryCall_hook, (void **)&unaryCall_original);
|
||||||
|
} else {
|
||||||
|
LOGE("can't find unaryCall signature");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,120 +5,19 @@
|
|||||||
#include <android/asset_manager.h>
|
#include <android/asset_manager.h>
|
||||||
|
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
#include "config.h"
|
#include "common.h"
|
||||||
#include "util.h"
|
#include "hooks/asset_hook.h"
|
||||||
#include "grpc.h"
|
#include "hooks/unary_call.h"
|
||||||
|
#include "hooks/fstat_hook.h"
|
||||||
|
#include "hooks/sqlite_mutex.h"
|
||||||
|
|
||||||
#ifdef __aarch64__
|
void JNICALL init(JNIEnv *env, jobject clazz) {
|
||||||
#define ARM64 true
|
|
||||||
#else
|
|
||||||
#define ARM64 false
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static native_config_t *native_config;
|
|
||||||
static JavaVM *java_vm;
|
|
||||||
static jobject native_lib_object;
|
|
||||||
static jmethodID native_lib_on_unary_call_method;
|
|
||||||
static jmethodID native_lib_on_asset_load;
|
|
||||||
|
|
||||||
// original functions
|
|
||||||
static void *(*unaryCall_original)(void *, const char *, grpc::grpc_byte_buffer **, void *, void *, void *);
|
|
||||||
static auto fstat_original = (int (*)(int, struct stat *)) nullptr;
|
|
||||||
static AAsset* (*AAssetManager_open_original)(AAssetManager*, const char*, int) = nullptr;
|
|
||||||
|
|
||||||
static int fstat_hook(int fd, struct stat *buf) {
|
|
||||||
char name[256];
|
|
||||||
memset(name, 0, sizeof(name));
|
|
||||||
snprintf(name, sizeof(name), "/proc/self/fd/%d", fd);
|
|
||||||
readlink(name, name, sizeof(name));
|
|
||||||
|
|
||||||
std::string fileName(name);
|
|
||||||
|
|
||||||
if (native_config->disable_metrics && fileName.find("files/blizzardv2/queues") != std::string::npos) {
|
|
||||||
unlink(name);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (native_config->disable_bitmoji && fileName.find("com.snap.file_manager_4_SCContent") != std::string::npos) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return fstat_original(fd, buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static void *unaryCall_hook(void *unk1, const char *uri, grpc::grpc_byte_buffer **buffer_ptr, void *unk4, void *unk5, void *unk6) {
|
|
||||||
// request without reference counter can be hooked using xposed ig
|
|
||||||
auto slice_buffer = (*buffer_ptr)->slice_buffer;
|
|
||||||
if (slice_buffer->ref_counter == 0) {
|
|
||||||
return unaryCall_original(unk1, uri, buffer_ptr, unk4, unk5, unk6);
|
|
||||||
}
|
|
||||||
|
|
||||||
JNIEnv *env = nullptr;
|
|
||||||
java_vm->GetEnv((void **)&env, JNI_VERSION_1_6);
|
|
||||||
|
|
||||||
auto jni_buffer_array = env->NewByteArray(slice_buffer->length);
|
|
||||||
env->SetByteArrayRegion(jni_buffer_array, 0, slice_buffer->length, (jbyte *)slice_buffer->data);
|
|
||||||
|
|
||||||
auto native_request_data_object = env->CallObjectMethod(native_lib_object, native_lib_on_unary_call_method, env->NewStringUTF(uri), jni_buffer_array);
|
|
||||||
|
|
||||||
if (native_request_data_object != nullptr) {
|
|
||||||
auto native_request_data_class = env->GetObjectClass(native_request_data_object);
|
|
||||||
auto is_canceled = env->GetBooleanField(native_request_data_object, env->GetFieldID(native_request_data_class, "canceled", "Z"));
|
|
||||||
|
|
||||||
if (is_canceled) {
|
|
||||||
LOGD("canceled request for %s", uri);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto new_buffer = (jbyteArray)env->GetObjectField(native_request_data_object, env->GetFieldID(native_request_data_class, "buffer", "[B"));
|
|
||||||
auto new_buffer_length = env->GetArrayLength(new_buffer);
|
|
||||||
auto new_buffer_data = env->GetByteArrayElements(new_buffer, nullptr);
|
|
||||||
|
|
||||||
//we need to allocate a new ref_counter struct and copy the old ref_counter and the new_buffer to it
|
|
||||||
const static auto ref_counter_struct_size = (uintptr_t)slice_buffer->data - (uintptr_t)slice_buffer->ref_counter;
|
|
||||||
|
|
||||||
auto new_ref_counter = malloc(ref_counter_struct_size + new_buffer_length);
|
|
||||||
//copy the old ref_counter and the native_request_data_object
|
|
||||||
memcpy(new_ref_counter, slice_buffer->ref_counter, ref_counter_struct_size);
|
|
||||||
memcpy((void *)((uintptr_t)new_ref_counter + ref_counter_struct_size), new_buffer_data, new_buffer_length);
|
|
||||||
|
|
||||||
//free the old ref_counter
|
|
||||||
free(slice_buffer->ref_counter);
|
|
||||||
|
|
||||||
//update the slice_buffer
|
|
||||||
slice_buffer->ref_counter = new_ref_counter;
|
|
||||||
slice_buffer->length = new_buffer_length;
|
|
||||||
slice_buffer->data = (uint8_t *)((uintptr_t)new_ref_counter + ref_counter_struct_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
return unaryCall_original(unk1, uri, buffer_ptr, unk4, unk5, unk6);
|
|
||||||
}
|
|
||||||
|
|
||||||
static AAsset* AAssetManager_open_hook(AAssetManager* mgr, const char* filename, int mode) {
|
|
||||||
if (native_config->hook_asset_open) {
|
|
||||||
JNIEnv *env = nullptr;
|
|
||||||
java_vm->GetEnv((void **)&env, JNI_VERSION_1_6);
|
|
||||||
|
|
||||||
if (!env->CallBooleanMethod(native_lib_object, native_lib_on_asset_load, env->NewStringUTF(filename))) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return AAssetManager_open_original(mgr, filename, mode);
|
|
||||||
}
|
|
||||||
|
|
||||||
void JNICALL init(JNIEnv *env, jobject clazz, jobject classloader) {
|
|
||||||
LOGD("Initializing native");
|
LOGD("Initializing native");
|
||||||
// config
|
using namespace common;
|
||||||
|
|
||||||
native_config = new native_config_t;
|
native_config = new native_config_t;
|
||||||
|
|
||||||
// native lib object
|
|
||||||
native_lib_object = env->NewGlobalRef(clazz);
|
native_lib_object = env->NewGlobalRef(clazz);
|
||||||
native_lib_on_unary_call_method = env->GetMethodID(env->GetObjectClass(clazz), "onNativeUnaryCall", "(Ljava/lang/String;[B)L" BUILD_NAMESPACE "/NativeRequestData;");
|
client_module = util::get_module("libclient.so");
|
||||||
native_lib_on_asset_load = env->GetMethodID(env->GetObjectClass(clazz), "shouldLoadAsset", "(Ljava/lang/String;)Z");
|
|
||||||
|
|
||||||
auto client_module = util::get_module("libclient.so");
|
|
||||||
|
|
||||||
if (client_module.base == 0) {
|
if (client_module.base == 0) {
|
||||||
LOGE("libclient not loaded!");
|
LOGE("libclient not loaded!");
|
||||||
@ -127,44 +26,53 @@ void JNICALL init(JNIEnv *env, jobject clazz, jobject classloader) {
|
|||||||
|
|
||||||
LOGD("libclient.so base=0x%0lx, size=0x%0lx", client_module.base, client_module.size);
|
LOGD("libclient.so base=0x%0lx, size=0x%0lx", client_module.base, client_module.size);
|
||||||
|
|
||||||
// hooks
|
AssetHook::init(env);
|
||||||
DobbyHook((void *)DobbySymbolResolver("libc.so", "fstat"), (void *)fstat_hook, (void **)&fstat_original);
|
UnaryCallHook::init(env);
|
||||||
|
FstatHook::init();
|
||||||
|
SqliteMutexHook::init();
|
||||||
|
|
||||||
auto unaryCall_func = util::find_signature(
|
|
||||||
client_module.base, client_module.size,
|
|
||||||
ARM64 ? "A8 03 1F F8 C2 00 00 94" : "0A 90 00 F0 3F F9",
|
|
||||||
ARM64 ? -0x48 : -0x37
|
|
||||||
);
|
|
||||||
|
|
||||||
if (unaryCall_func != 0) {
|
|
||||||
LOGD("found unaryCall at 0x%0lx", unaryCall_func);
|
|
||||||
DobbyHook((void *)unaryCall_func, (void *)unaryCall_hook, (void **)&unaryCall_original);
|
|
||||||
} else {
|
|
||||||
LOGE("can't find unaryCall signature");
|
|
||||||
}
|
|
||||||
|
|
||||||
DobbyHook((void *) AAssetManager_open, (void *) AAssetManager_open_hook, (void **) &AAssetManager_open_original);
|
|
||||||
LOGD("Native initialized");
|
LOGD("Native initialized");
|
||||||
}
|
}
|
||||||
|
|
||||||
void JNICALL load_config(JNIEnv *env, jobject _, jobject config_object) {
|
void JNICALL load_config(JNIEnv *env, jobject _, jobject config_object) {
|
||||||
auto native_config_clazz = env->GetObjectClass(config_object);
|
auto native_config_clazz = env->GetObjectClass(config_object);
|
||||||
#define GET_CONFIG_BOOL(name) env->GetBooleanField(config_object, env->GetFieldID(native_config_clazz, name, "Z"))
|
#define GET_CONFIG_BOOL(name) env->GetBooleanField(config_object, env->GetFieldID(native_config_clazz, name, "Z"))
|
||||||
|
auto native_config = common::native_config;
|
||||||
|
|
||||||
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");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void JNICALL lock_database(JNIEnv *env, jobject _, jstring database_name, jobject runnable) {
|
||||||
|
auto database_name_str = env->GetStringUTFChars(database_name, nullptr);
|
||||||
|
auto mutex = SqliteMutexHook::mutex_map[database_name_str];
|
||||||
|
env->ReleaseStringUTFChars(database_name, database_name_str);
|
||||||
|
|
||||||
|
if (mutex != nullptr) {
|
||||||
|
auto lock_result = pthread_mutex_lock(&mutex->mutex);
|
||||||
|
if (lock_result != 0) {
|
||||||
|
LOGE("pthread_mutex_lock failed: %d", lock_result);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
env->CallVoidMethod(runnable, env->GetMethodID(env->GetObjectClass(runnable), "run", "()V"));
|
||||||
|
|
||||||
|
if (mutex != nullptr) {
|
||||||
|
pthread_mutex_unlock(&mutex->mutex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *_) {
|
extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *_) {
|
||||||
// register native methods
|
common::java_vm = vm;
|
||||||
java_vm = vm;
|
|
||||||
JNIEnv *env = nullptr;
|
JNIEnv *env = nullptr;
|
||||||
vm->GetEnv((void **)&env, JNI_VERSION_1_6);
|
vm->GetEnv((void **)&env, JNI_VERSION_1_6);
|
||||||
|
|
||||||
auto methods = std::vector<JNINativeMethod>();
|
auto methods = std::vector<JNINativeMethod>();
|
||||||
methods.push_back({"init", "(Ljava/lang/ClassLoader;)V", (void *)init});
|
methods.push_back({"init", "()V", (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});
|
||||||
|
|
||||||
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;
|
||||||
|
@ -11,11 +11,11 @@ class NativeLib {
|
|||||||
private set
|
private set
|
||||||
}
|
}
|
||||||
|
|
||||||
fun initOnce(classloader: ClassLoader) {
|
fun initOnce() {
|
||||||
if (initialized) throw IllegalStateException("NativeLib already initialized")
|
if (initialized) throw IllegalStateException("NativeLib already initialized")
|
||||||
runCatching {
|
runCatching {
|
||||||
System.loadLibrary(BuildConfig.NATIVE_NAME)
|
System.loadLibrary(BuildConfig.NATIVE_NAME)
|
||||||
init(classloader)
|
init()
|
||||||
initialized = true
|
initialized = true
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
Log.e("SnapEnhance", "NativeLib init failed")
|
Log.e("SnapEnhance", "NativeLib init failed")
|
||||||
@ -24,7 +24,6 @@ class NativeLib {
|
|||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
private fun onNativeUnaryCall(uri: String, buffer: ByteArray): NativeRequestData? {
|
private fun onNativeUnaryCall(uri: String, buffer: ByteArray): NativeRequestData? {
|
||||||
// Log.d("SnapEnhance", "onNativeUnaryCall: uri=$uri, bufferSize=${buffer.size}, buffer=${buffer.contentToString()}")
|
|
||||||
val nativeRequestData = NativeRequestData(uri, buffer)
|
val nativeRequestData = NativeRequestData(uri, buffer)
|
||||||
runCatching {
|
runCatching {
|
||||||
nativeUnaryCallCallback(nativeRequestData)
|
nativeUnaryCallCallback(nativeRequestData)
|
||||||
@ -45,6 +44,18 @@ class NativeLib {
|
|||||||
loadConfig(config)
|
loadConfig(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
private external fun init(classLoader: ClassLoader)
|
fun lockNativeDatabase(name: String, callback: () -> Unit) {
|
||||||
|
if (!initialized) return
|
||||||
|
lockDatabase(name) {
|
||||||
|
runCatching {
|
||||||
|
callback()
|
||||||
|
}.onFailure {
|
||||||
|
Log.e("SnapEnhance", "lockNativeDatabase callback failed", it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private external fun init()
|
||||||
private external fun loadConfig(config: NativeConfig)
|
private external fun loadConfig(config: NativeConfig)
|
||||||
|
private external fun lockDatabase(name: String, callback: Runnable)
|
||||||
}
|
}
|
Reference in New Issue
Block a user