feat: save overridden snaps in chat
This commit is contained in:
@@ -10,6 +10,7 @@ import me.rhunk.snapenhance.bridge.BridgeClient
|
||||
import me.rhunk.snapenhance.bridge.SyncCallback
|
||||
import me.rhunk.snapenhance.core.BuildConfig
|
||||
import me.rhunk.snapenhance.core.eventbus.events.impl.SnapWidgetBroadcastReceiveEvent
|
||||
import me.rhunk.snapenhance.core.eventbus.events.impl.UnaryCallEvent
|
||||
import me.rhunk.snapenhance.core.messaging.MessagingFriendInfo
|
||||
import me.rhunk.snapenhance.core.messaging.MessagingGroupInfo
|
||||
import me.rhunk.snapenhance.data.SnapClassCache
|
||||
@@ -97,12 +98,6 @@ class SnapEnhance {
|
||||
private suspend fun init() {
|
||||
measureTime {
|
||||
with(appContext) {
|
||||
runCatching {
|
||||
native.initOnce(appContext.androidContext.classLoader)
|
||||
}.onFailure {
|
||||
Logger.xposedLog("Failed to init native", it)
|
||||
return
|
||||
}
|
||||
reloadConfig()
|
||||
withContext(appContext.coroutineDispatcher) {
|
||||
translation.userLocale = getConfigLocale()
|
||||
@@ -126,6 +121,18 @@ class SnapEnhance {
|
||||
private fun onActivityCreate() {
|
||||
measureTime {
|
||||
with(appContext) {
|
||||
runCatching {
|
||||
native.initOnce(appContext.androidContext.classLoader)
|
||||
native.nativeUnaryCallCallback = { request ->
|
||||
event.post(UnaryCallEvent(request.uri, request.buffer))?.also {
|
||||
request.buffer = it.buffer
|
||||
request.canceled = it.canceled
|
||||
}
|
||||
}
|
||||
}.onFailure {
|
||||
Logger.xposedLog("Failed to init native", it)
|
||||
}
|
||||
|
||||
features.onActivityCreate()
|
||||
actionManager.init()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package me.rhunk.snapenhance.core.eventbus.events.impl
|
||||
|
||||
import me.rhunk.snapenhance.core.eventbus.events.AbstractHookEvent
|
||||
|
||||
class UnaryCallEvent(
|
||||
val uri: String,
|
||||
var buffer: ByteArray
|
||||
) : AbstractHookEvent()
|
||||
@@ -1,11 +1,13 @@
|
||||
package me.rhunk.snapenhance.features.impl.tweaks
|
||||
|
||||
import me.rhunk.snapenhance.core.eventbus.events.impl.SendMessageWithContentEvent
|
||||
import me.rhunk.snapenhance.core.eventbus.events.impl.UnaryCallEvent
|
||||
import me.rhunk.snapenhance.data.ContentType
|
||||
import me.rhunk.snapenhance.data.MessageSender
|
||||
import me.rhunk.snapenhance.features.Feature
|
||||
import me.rhunk.snapenhance.features.FeatureLoadParams
|
||||
import me.rhunk.snapenhance.ui.ViewAppearanceHelper
|
||||
import me.rhunk.snapenhance.util.protobuf.ProtoEditor
|
||||
import me.rhunk.snapenhance.util.protobuf.ProtoReader
|
||||
|
||||
class GalleryMediaSendOverride : Feature("Gallery Media Send Override", loadParams = FeatureLoadParams.INIT_SYNC) {
|
||||
@@ -19,6 +21,25 @@ class GalleryMediaSendOverride : Feature("Gallery Media Send Override", loadPara
|
||||
it
|
||||
}
|
||||
|
||||
context.event.subscribe(UnaryCallEvent::class) { event ->
|
||||
if (event.uri != "/messagingcoreservice.MessagingCoreService/CreateContentMessage") return@subscribe
|
||||
event.buffer = ProtoEditor(event.buffer).apply {
|
||||
//remove the mas view time
|
||||
edit(4, 4, 11, 5, 2) {
|
||||
remove(8)
|
||||
addBuffer(6, byteArrayOf())
|
||||
}
|
||||
//make snaps savable in chat
|
||||
edit(4) {
|
||||
val savableState = firstOrNull(7)?.value ?: return@edit
|
||||
if (savableState == 2L) {
|
||||
remove(7)
|
||||
addVarInt(7, 3)
|
||||
}
|
||||
}
|
||||
}.toByteArray()
|
||||
}
|
||||
|
||||
context.event.subscribe(SendMessageWithContentEvent::class, {
|
||||
context.config.messaging.galleryMediaSendOverride.get()
|
||||
}) { event ->
|
||||
|
||||
@@ -15,6 +15,7 @@ class EditorContext(
|
||||
fun addVarInt(id: Int, value: Int) = addVarInt(id, value.toLong())
|
||||
fun addVarInt(id: Int, value: Long) = addWire(Wire(id, WireType.VARINT, value))
|
||||
fun addBuffer(id: Int, value: ByteArray) = addWire(Wire(id, WireType.LENGTH_DELIMITED, value))
|
||||
fun add(id: Int, content: ProtoWriter.() -> Unit) = addBuffer(id, ProtoWriter().apply(content).toByteArray())
|
||||
fun addString(id: Int, value: String) = addBuffer(id, value.toByteArray())
|
||||
fun addFixed64(id: Int, value: Long) = addWire(Wire(id, WireType.FIXED64, value))
|
||||
fun addFixed32(id: Int, value: Int) = addWire(Wire(id, WireType.FIXED32, value))
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include <jni.h>
|
||||
#include <string>
|
||||
#include <dobby.h>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
|
||||
#include "logger.h"
|
||||
@@ -13,6 +12,7 @@ static native_config_t *native_config;
|
||||
static JavaVM *java_vm;
|
||||
|
||||
static auto fstat_original = (int (*)(int, struct stat *)) nullptr;
|
||||
|
||||
static int fstat_hook(int fd, struct stat *buf) {
|
||||
char name[256];
|
||||
memset(name, 0, 256);
|
||||
@@ -49,7 +49,7 @@ unaryCall_hook(void *unk1, const char *uri, grpc::grpc_byte_buffer **buffer_ptr,
|
||||
void *unk5, void *unk6) {
|
||||
auto slice_buffer = (*buffer_ptr)->slice_buffer;
|
||||
// request without reference counter can be hooked using xposed ig
|
||||
if (slice_buffer->ref_counter == nullptr) {
|
||||
if (slice_buffer->ref_counter == 0) {
|
||||
return unaryCall_original(unk1, uri, buffer_ptr, unk4, unk5, unk6);
|
||||
}
|
||||
|
||||
@@ -70,8 +70,8 @@ unaryCall_hook(void *unk1, const char *uri, grpc::grpc_byte_buffer **buffer_ptr,
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -81,34 +81,32 @@ unaryCall_hook(void *unk1, const char *uri, grpc::grpc_byte_buffer **buffer_ptr,
|
||||
auto new_buffer_length = env->GetArrayLength((jbyteArray) new_buffer);
|
||||
auto new_buffer_data = env->GetByteArrayElements((jbyteArray) new_buffer, nullptr);
|
||||
|
||||
LOGD("rewrote request for %s (length: %d)", uri, new_buffer_length);
|
||||
//we need to allocate a new ref_counter struct and copy the old ref_counter and the new_buffer to it
|
||||
const auto ref_counter_struct_size =
|
||||
const static auto ref_counter_struct_size =
|
||||
(uintptr_t) slice_buffer->data - (uintptr_t) slice_buffer->ref_counter;
|
||||
|
||||
auto new_ref_counter = (void *) malloc(ref_counter_struct_size + new_buffer_length);
|
||||
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((void *) slice_buffer->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);
|
||||
|
||||
env->ReleaseByteArrayElements((jbyteArray) native_request_data_object, new_buffer_data, 0);
|
||||
}
|
||||
|
||||
return unaryCall_original(unk1, uri, buffer_ptr, unk4, unk5, unk6);
|
||||
}
|
||||
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
init(JNIEnv *env, jobject clazz, jobject classloader) {
|
||||
LOGD("initializing native");
|
||||
void JNICALL init(JNIEnv *env, jobject clazz, jobject classloader) {
|
||||
LOGD("Initializing native");
|
||||
// config
|
||||
native_config = new native_config_t;
|
||||
|
||||
@@ -125,34 +123,35 @@ init(JNIEnv *env, jobject clazz, jobject classloader) {
|
||||
LOGE("libclient not found");
|
||||
return;
|
||||
}
|
||||
client_module.base -= 0x1000;
|
||||
LOGD("libclient: offset: 0x%x size: 0x%x", client_module.base, client_module.size);
|
||||
//client_module.base -= 0x1000; // debugging purposes
|
||||
LOGD("libclient.so offset=%u, size=%u", client_module.base, client_module.size);
|
||||
|
||||
// hooks
|
||||
DobbyHook((void *) DobbySymbolResolver("libc.so", "fstat"), (void *) fstat_hook,
|
||||
(void **) &fstat_original);
|
||||
//signature might change in the future (unstable for now)
|
||||
DobbyHook((void *) util::find_signature(client_module.base, client_module.size,
|
||||
"FD 7B BA A9 FC 6F 01 A9 FA 67 02 A9 F8 5F 03 A9 F6 57 04 A9 F4 4F 05 A9 FD 03 00 91 FF 43 13 D1"),
|
||||
(void *) unaryCall_hook,
|
||||
(void **) &unaryCall_original);
|
||||
|
||||
LOGD("native initialized");
|
||||
//signature might change in the future (unstable for now)
|
||||
auto unaryCall_func = util::find_signature(client_module.base, client_module.size,
|
||||
"FD 7B BA A9 FC 6F 01 A9 FA 67 02 A9 F8 5F 03 A9 F6 57 04 A9 F4 4F 05 A9 FD 03 00 91 FF 43 13 D1");
|
||||
if (unaryCall_func != 0) {
|
||||
DobbyHook((void *) unaryCall_func, (void *) unaryCall_hook, (void **) &unaryCall_original);
|
||||
} else {
|
||||
LOGE("can't find unaryCall signature");
|
||||
}
|
||||
|
||||
LOGD("Native initialized");
|
||||
}
|
||||
|
||||
void JNICALL load_config(JNIEnv *env, jobject clazz, jobject config_object) {
|
||||
void JNICALL load_config(JNIEnv *env, jobject _, jobject 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"))
|
||||
|
||||
native_config->disable_bitmoji = GET_CONFIG_BOOL("disableBitmoji");
|
||||
native_config->disable_metrics = GET_CONFIG_BOOL("disableMetrics");
|
||||
|
||||
LOGD("config loaded");
|
||||
}
|
||||
|
||||
//jni onload
|
||||
extern "C" JNIEXPORT jint JNICALL
|
||||
JNI_OnLoad(JavaVM *vm, void *reserved) {
|
||||
JNI_OnLoad(JavaVM *vm, void *_) {
|
||||
java_vm = vm;
|
||||
// register native methods
|
||||
JNIEnv *env = nullptr;
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <unistd.h>
|
||||
#include <dlfcn.h>
|
||||
#include <elf.h>
|
||||
|
||||
namespace util {
|
||||
typedef struct {
|
||||
uintptr_t base;
|
||||
size_t size;
|
||||
} ModuleInfo;
|
||||
} module_info_t;
|
||||
|
||||
static void hexDump(void *ptr, uint8_t line_length, uint32_t lines) {
|
||||
auto *p = (unsigned char *) ptr;
|
||||
@@ -24,16 +22,13 @@ namespace util {
|
||||
}
|
||||
}
|
||||
|
||||
static ModuleInfo get_module(const char *libname) {
|
||||
char path[256];
|
||||
static module_info_t get_module(const char *libname) {
|
||||
char buff[256];
|
||||
int len_libname = strlen(libname);
|
||||
FILE *file;
|
||||
uintptr_t addr = 0;
|
||||
size_t size = 0;
|
||||
|
||||
snprintf(path, sizeof path, "/proc/%d/smaps", getpid());
|
||||
file = fopen(path, "rt");
|
||||
auto file = fopen("/proc/self/smaps", "rt");
|
||||
if (file == NULL)
|
||||
return {0, 0};
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ class NativeLib {
|
||||
|
||||
@Suppress("unused")
|
||||
private fun onNativeUnaryCall(uri: String, buffer: ByteArray): NativeRequestData? {
|
||||
Log.d("SnapEnhance", "onNativeUnaryCall: uri=$uri, bufferSize=${buffer.size}, buffer=${buffer.contentToString()}")
|
||||
// Log.d("SnapEnhance", "onNativeUnaryCall: uri=$uri, bufferSize=${buffer.size}, buffer=${buffer.contentToString()}")
|
||||
val nativeRequestData = NativeRequestData(uri, buffer)
|
||||
runCatching {
|
||||
nativeUnaryCallCallback(nativeRequestData)
|
||||
|
||||
Reference in New Issue
Block a user