commit c0225919e977a3e29a46737b348babc3bfeae5b8
parent b004f92b9cd9751fa0f22403b8b07ae426cd58cd
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date:   Fri, 25 Aug 2023 19:44:42 +0200

feat: experimental native hooks

Diffstat:
A.gitmodules | 3+++
Mcore/build.gradle.kts | 1+
Mcore/src/main/assets/lang/en_US.json | 4++++
Mcore/src/main/kotlin/me/rhunk/snapenhance/ModContext.kt | 13+++++++++++++
Mcore/src/main/kotlin/me/rhunk/snapenhance/SnapEnhance.kt | 6++++++
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/config/impl/Global.kt | 1+
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/eventbus/EventBus.kt | 2+-
Anative/.gitignore | 2++
Anative/build.gradle.kts | 28++++++++++++++++++++++++++++
Anative/jni/CMakeLists.txt | 16++++++++++++++++
Anative/jni/external/dobby | 1+
Anative/jni/src/config.h | 7+++++++
Anative/jni/src/library.cpp | 76++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Anative/jni/src/logger.h | 10++++++++++
Anative/src/main/kotlin/me/rhunk/snapenhance/nativelib/NativeConfig.kt | 6++++++
Anative/src/main/kotlin/me/rhunk/snapenhance/nativelib/NativeLib.kt | 11+++++++++++
Msettings.gradle.kts | 4++--
17 files changed, 188 insertions(+), 3 deletions(-)

diff --git a/.gitmodules b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "dobby"] + path = native/jni/external/dobby + url = https://github.com/jmpews/Dobby diff --git a/core/build.gradle.kts b/core/build.gradle.kts @@ -42,4 +42,5 @@ dependencies { implementation(libs.androidx.documentfile) implementation(project(":mapper")) + implementation(project(":native")) } \ No newline at end of file diff --git a/core/src/main/assets/lang/en_US.json b/core/src/main/assets/lang/en_US.json @@ -249,6 +249,10 @@ "name": "Disable Metrics", "description": "Disables some analytics data sent to Snapchat" }, + "disable_bitmoji": { + "name": "Disable Bitmoji", + "description": "Disables friends profile bitmoji for the whole app" + }, "block_ads": { "name": "Block Ads", "description": "Prevent ads from being displayed" diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/ModContext.kt b/core/src/main/kotlin/me/rhunk/snapenhance/ModContext.kt @@ -21,6 +21,8 @@ import me.rhunk.snapenhance.database.DatabaseAccess import me.rhunk.snapenhance.features.Feature import me.rhunk.snapenhance.manager.impl.ActionManager import me.rhunk.snapenhance.manager.impl.FeatureManager +import me.rhunk.snapenhance.nativelib.NativeConfig +import me.rhunk.snapenhance.nativelib.NativeLib import me.rhunk.snapenhance.util.download.HttpServer import java.util.concurrent.ExecutorService import java.util.concurrent.Executors @@ -44,6 +46,7 @@ class ModContext { val config by modConfig val event = EventBus(this) val eventDispatcher = EventDispatcher(this) + val native = NativeLib() val translation = LocaleWrapper() val features = FeatureManager(this) @@ -121,6 +124,16 @@ class ModContext { fun reloadConfig() { modConfig.loadFromBridge(bridgeClient) + runCatching { + native.loadConfig( + NativeConfig( + disableBitmoji = config.global.disableBitmoji.get(), + disableMetrics = config.global.disableMetrics.get() + ) + ) + }.onFailure { + Logger.xposedLog("Failed to load native config", it) + } } fun getConfigLocale(): String { diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/SnapEnhance.kt b/core/src/main/kotlin/me/rhunk/snapenhance/SnapEnhance.kt @@ -97,6 +97,12 @@ class SnapEnhance { private suspend fun init() { measureTime { with(appContext) { + runCatching { + native.init() + }.onFailure { + Logger.xposedLog("Failed to init native", it) + return + } reloadConfig() withContext(appContext.coroutineDispatcher) { translation.userLocale = getConfigLocale() diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/config/impl/Global.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/config/impl/Global.kt @@ -8,6 +8,7 @@ class Global : ConfigContainer() { val snapchatPlus = boolean("snapchat_plus") { addNotices(FeatureNotice.MAY_BAN) } val autoUpdater = unique("auto_updater", "EVERY_LAUNCH", "DAILY", "WEEKLY").apply { set("DAILY") } val disableMetrics = boolean("disable_metrics") + val disableBitmoji = boolean("disable_bitmoji") { addNotices(FeatureNotice.UNSTABLE) } val blockAds = boolean("block_ads") val disableVideoLengthRestrictions = boolean("disable_video_length_restrictions") { addNotices(FeatureNotice.MAY_BAN) } val disableGooglePlayDialogs = boolean("disable_google_play_dialogs") diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/eventbus/EventBus.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/eventbus/EventBus.kt @@ -56,7 +56,7 @@ class EventBus( event.context = context - subscribers[event::class]?.forEach { listener -> + subscribers[event::class]?.toTypedArray()?.forEach { listener -> @Suppress("UNCHECKED_CAST") runCatching { (listener as IListener<T>).handle(event) diff --git a/native/.gitignore b/native/.gitignore @@ -0,0 +1 @@ +/build+ \ No newline at end of file diff --git a/native/build.gradle.kts b/native/build.gradle.kts @@ -0,0 +1,28 @@ +@Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed +plugins { + alias(libs.plugins.androidLibrary) + alias(libs.plugins.kotlinAndroid) +} + +android { + namespace = "me.rhunk.snapenhance.nativelib" + compileSdk = 34 + + defaultConfig { + externalNativeBuild { + cmake { + cppFlags("") + } + } + } + + externalNativeBuild { + cmake { + path("jni/CMakeLists.txt") + version = "3.22.1" + } + } + kotlinOptions { + jvmTarget = "1.8" + } +} diff --git a/native/jni/CMakeLists.txt b/native/jni/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 3.22.1) + +project("nativelib") + +set(DOBBY_GENERATE_SHARED OFF) +add_subdirectory(external/dobby) + +add_library(${CMAKE_PROJECT_NAME} SHARED + src/library.cpp + ) + +target_link_libraries(${CMAKE_PROJECT_NAME} + android + log + dobby_static + ) diff --git a/native/jni/external/dobby b/native/jni/external/dobby @@ -0,0 +1 @@ +Subproject commit b0176de574104726bb68dff3b77ee666300fc338 diff --git a/native/jni/src/config.h b/native/jni/src/config.h @@ -0,0 +1,6 @@ +#pragma once + +typedef struct { + bool disable_bitmoji; + bool disable_metrics; +} native_config_t;+ \ No newline at end of file diff --git a/native/jni/src/library.cpp b/native/jni/src/library.cpp @@ -0,0 +1,76 @@ +#include <jni.h> +#include <string> +#include <dobby.h> +#include <sys/stat.h> +#include <unistd.h> +#include <fstream> +#include <vector> + +#include "logger.h" +#include "config.h" + +static native_config_t *native_config; + +static auto fstat_original = fstat; +static int fstat_hook(int fd, struct stat *buf) { + char name[256]; + memset(name, 0, 256); + snprintf(name, sizeof(name), "/proc/self/fd/%d", fd); + readlink(name, name, sizeof(name)); + + auto fileName = std::string(name); + + //prevent blizzardv2 metrics + if (native_config->disable_metrics && fileName.find("files/blizzardv2/queues") != std::string::npos) { + unlink(name); + return -1; + } + + //prevent bitmoji to load + if (native_config->disable_bitmoji && fileName.find("com.snap.file_manager_4_SCContent") != std::string::npos) { + return -1; + } + + return fstat_original(fd, buf); +} + + +#define GET_BOOL_FIELD(env, clazz, field) env->GetBooleanField(clazz, env->GetFieldID(clazz, field, "Z")) + +extern "C" JNIEXPORT void JNICALL +loadConfig(JNIEnv *env, jobject clazz, jobject config_object) { + auto native_config_class = env->GetObjectClass(config_object); + + native_config->disable_bitmoji = GET_BOOL_FIELD(env, native_config_class, "disableBitmoji"); + native_config->disable_metrics = GET_BOOL_FIELD(env, native_config_class, "disableMetrics"); + + LOGD("config loaded"); +} + +//jni onload +extern "C" JNIEXPORT jint JNICALL +JNI_OnLoad(JavaVM *vm, void *reserved) { + LOGD("initializing native"); + // config + native_config = new native_config_t; + + // hooks + DobbyHook((void *) fstat_original,(void *) fstat_hook,(void **) &fstat_original); + + // register native methods + JNIEnv *env = nullptr; + vm->GetEnv((void **) &env, JNI_VERSION_1_6); + + auto methods = std::vector<JNINativeMethod>(); + methods.push_back({"loadConfig", "(Lme/rhunk/snapenhance/nativelib/NativeConfig;)V", (void *) loadConfig}); + + env->RegisterNatives( + env->FindClass("me/rhunk/snapenhance/nativelib/NativeLib"), + methods.data(), + methods.size() + ); + + LOGD("native initialized"); + + return JNI_VERSION_1_6; +} diff --git a/native/jni/src/logger.h b/native/jni/src/logger.h @@ -0,0 +1,10 @@ +#pragma once + +#include <android/log.h> + +#define LOG_TAG "SnapEnhanceNative" + +#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) +#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__) + diff --git a/native/src/main/kotlin/me/rhunk/snapenhance/nativelib/NativeConfig.kt b/native/src/main/kotlin/me/rhunk/snapenhance/nativelib/NativeConfig.kt @@ -0,0 +1,6 @@ +package me.rhunk.snapenhance.nativelib + +data class NativeConfig( + val disableBitmoji: Boolean = false, + val disableMetrics: Boolean = false +) diff --git a/native/src/main/kotlin/me/rhunk/snapenhance/nativelib/NativeLib.kt b/native/src/main/kotlin/me/rhunk/snapenhance/nativelib/NativeLib.kt @@ -0,0 +1,10 @@ +package me.rhunk.snapenhance.nativelib + +class NativeLib { + fun init() { + System.loadLibrary("nativelib") + } + + + external fun loadConfig(config: NativeConfig) +}+ \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts @@ -19,4 +19,5 @@ dependencyResolutionManagement { rootProject.name = "SnapEnhance" include(":core") include(":app") -include(":mapper")- \ No newline at end of file +include(":mapper") +include(":native")