commit 09002821432e44b93061bcb88ed3b9e7409be488
parent 39e23564d7e5b9ce7468aa4f5605817298f4cec5
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date:   Thu, 25 May 2023 23:17:32 +0200

feat!: unsafe bridge for sdk 28

Diffstat:
Mapp/src/main/kotlin/me/rhunk/snapenhance/ModContext.kt | 4++--
Mapp/src/main/kotlin/me/rhunk/snapenhance/SnapEnhance.kt | 39+++++++++++++++++++++++++++++----------
Aapp/src/main/kotlin/me/rhunk/snapenhance/bridge/AbstractBridgeClient.kt | 96+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aapp/src/main/kotlin/me/rhunk/snapenhance/bridge/MessageLoggerWrapper.kt | 48++++++++++++++++++++++++++++++++++++++++++++++++
Dapp/src/main/kotlin/me/rhunk/snapenhance/bridge/client/BridgeClient.kt | 254-------------------------------------------------------------------------------
Aapp/src/main/kotlin/me/rhunk/snapenhance/bridge/client/RootBridgeClient.kt | 134+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aapp/src/main/kotlin/me/rhunk/snapenhance/bridge/client/ServiceBridgeClient.kt | 226+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dapp/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/DownloadContentRequest.kt | 20--------------------
Dapp/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/DownloadContentResult.kt | 17-----------------
Dapp/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/FileAccessRequest.kt | 43-------------------------------------------
Dapp/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/FileAccessResult.kt | 20--------------------
Dapp/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/LocaleRequest.kt | 13-------------
Dapp/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/LocaleResult.kt | 20--------------------
Dapp/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/MessageLoggerRequest.kt | 30------------------------------
Dapp/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/MessageLoggerResult.kt | 21---------------------
Aapp/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/download/DownloadContentRequest.kt | 20++++++++++++++++++++
Aapp/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/download/DownloadContentResult.kt | 17+++++++++++++++++
Aapp/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/file/BridgeFileType.kt | 18++++++++++++++++++
Aapp/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/file/FileAccessRequest.kt | 33+++++++++++++++++++++++++++++++++
Aapp/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/file/FileAccessResult.kt | 20++++++++++++++++++++
Aapp/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/locale/LocaleRequest.kt | 13+++++++++++++
Aapp/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/locale/LocaleResult.kt | 20++++++++++++++++++++
Aapp/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/messagelogger/MessageLoggerRequest.kt | 30++++++++++++++++++++++++++++++
Aapp/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/messagelogger/MessageLoggerResult.kt | 21+++++++++++++++++++++
Mapp/src/main/kotlin/me/rhunk/snapenhance/bridge/service/BridgeService.kt | 84++++++++++++++++++++++++++++++-------------------------------------------------
Mapp/src/main/kotlin/me/rhunk/snapenhance/data/wrapper/impl/SnapUUID.kt | 2+-
Aapp/src/main/kotlin/me/rhunk/snapenhance/features/BridgeFileFeature.kt | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mapp/src/main/kotlin/me/rhunk/snapenhance/features/impl/downloader/AntiAutoDownload.kt | 48++++++------------------------------------------
Mapp/src/main/kotlin/me/rhunk/snapenhance/features/impl/privacy/DisableMetrics.kt | 2+-
Mapp/src/main/kotlin/me/rhunk/snapenhance/features/impl/spy/StealthMode.kt | 54++++++------------------------------------------------
Mapp/src/main/kotlin/me/rhunk/snapenhance/features/impl/ui/menus/impl/FriendFeedInfoMenu.kt | 4+++-
Mapp/src/main/kotlin/me/rhunk/snapenhance/manager/impl/ConfigManager.kt | 8++++----
Mapp/src/main/kotlin/me/rhunk/snapenhance/manager/impl/MappingManager.kt | 12++++++------
Mapp/src/main/kotlin/me/rhunk/snapenhance/manager/impl/TranslationManager.kt | 2+-
Mapp/src/main/kotlin/me/rhunk/snapenhance/mapping/impl/EnumMapper.kt | 2+-
Mapp/src/main/kotlin/me/rhunk/snapenhance/mapping/impl/OperaPageViewControllerMapper.kt | 2+-
Mapp/src/main/kotlin/me/rhunk/snapenhance/util/EncryptionHelper.kt | 2+-
Mapp/src/main/kotlin/me/rhunk/snapenhance/util/ReflectionHelper.kt | 3++-
Mapp/src/main/kotlin/me/rhunk/snapenhance/util/download/DownloadServer.kt | 3++-
39 files changed, 849 insertions(+), 611 deletions(-)

diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ModContext.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ModContext.kt @@ -10,7 +10,7 @@ import android.os.Process import android.widget.Toast import com.google.gson.Gson import com.google.gson.GsonBuilder -import me.rhunk.snapenhance.bridge.client.BridgeClient +import me.rhunk.snapenhance.bridge.AbstractBridgeClient import me.rhunk.snapenhance.database.DatabaseAccess import me.rhunk.snapenhance.features.Feature import me.rhunk.snapenhance.manager.impl.ActionManager @@ -29,10 +29,10 @@ class ModContext { lateinit var androidContext: Context var mainActivity: Activity? = null + lateinit var bridgeClient: AbstractBridgeClient val gson: Gson = GsonBuilder().create() - val bridgeClient = BridgeClient(this) val translation = TranslationManager(this) val features = FeatureManager(this) val mappings = MappingManager(this) diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/SnapEnhance.kt b/app/src/main/kotlin/me/rhunk/snapenhance/SnapEnhance.kt @@ -1,8 +1,13 @@ package me.rhunk.snapenhance +import android.annotation.SuppressLint import android.app.Activity import android.app.Application import android.content.Context +import android.os.Build +import me.rhunk.snapenhance.bridge.AbstractBridgeClient +import me.rhunk.snapenhance.bridge.client.RootBridgeClient +import me.rhunk.snapenhance.bridge.client.ServiceBridgeClient import me.rhunk.snapenhance.data.SnapClassCache import me.rhunk.snapenhance.hook.HookStage import me.rhunk.snapenhance.hook.Hooker @@ -17,21 +22,26 @@ class SnapEnhance { private val appContext = ModContext() init { + Hooker.hook(Application::class.java, "attach", HookStage.BEFORE) { param -> appContext.androidContext = param.arg<Context>(0).also { classLoader = it.classLoader } + appContext.bridgeClient = provideBridgeClient() - appContext.bridgeClient.start { bridgeResult -> - if (!bridgeResult) { - Logger.xposedLog("Cannot connect to bridge service") - appContext.restartApp() - return@start - } - runCatching { - init() - }.onFailure { - Logger.xposedLog("Failed to initialize", it) + appContext.bridgeClient.apply { + this.context = appContext + start { bridgeResult -> + if (!bridgeResult) { + Logger.xposedLog("Cannot connect to bridge service") + appContext.restartApp() + return@start + } + runCatching { + init() + }.onFailure { + Logger.xposedLog("Failed to initialize", it) + } } } } @@ -46,6 +56,15 @@ class SnapEnhance { } } + @SuppressLint("ObsoleteSdkInt") + private fun provideBridgeClient(): AbstractBridgeClient { + //unsafe way for Android 9 devices + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) { + return RootBridgeClient() + } + return ServiceBridgeClient() + } + private fun init() { val time = System.currentTimeMillis() with(appContext) { diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/bridge/AbstractBridgeClient.kt b/app/src/main/kotlin/me/rhunk/snapenhance/bridge/AbstractBridgeClient.kt @@ -0,0 +1,95 @@ +package me.rhunk.snapenhance.bridge + +import me.rhunk.snapenhance.ModContext +import me.rhunk.snapenhance.bridge.common.impl.file.BridgeFileType +import me.rhunk.snapenhance.bridge.common.impl.locale.LocaleResult + +abstract class AbstractBridgeClient { + lateinit var context: ModContext + + /** + * Start the bridge client + * + * @param callback the callback to call when the initialization is done + */ + abstract fun start(callback: (Boolean) -> Unit = {}) + + /** + * Create a file if it doesn't exist, and read it + * + * @param fileType the type of file to create and read + * @param defaultContent the default content to write to the file if it doesn't exist + * @return the content of the file + */ + abstract fun createAndReadFile(fileType: BridgeFileType, defaultContent: ByteArray): ByteArray + + /** + * Read a file + * + * @param fileType the type of file to read + * @return the content of the file + */ + abstract fun readFile(fileType: BridgeFileType): ByteArray + + /** + * Write a file + * + * @param fileType the type of file to write + * @param content the content to write to the file + * @return true if the file was written successfully + */ + abstract fun writeFile(fileType: BridgeFileType, content: ByteArray?): Boolean + + /** + * Delete a file + * + * @param fileType the type of file to delete + * @return true if the file was deleted successfully + */ + abstract fun deleteFile(fileType: BridgeFileType): Boolean + + /** + * Check if a file exists + * + * @param fileType the type of file to check + * @return true if the file exists + */ + abstract fun isFileExists(fileType: BridgeFileType): Boolean + + /** + * Download content from a URL and save it to a file + * + * @param url the URL to download content from + * @param path the path to save the content to + * @return true if the content was downloaded successfully + */ + abstract fun downloadContent(url: String, path: String): Boolean + + /** + * Get the content of a logged message from the database + * + * @param id the ID of the message logger message + * @return the content of the message + */ + abstract fun getMessageLoggerMessage(id: Long): ByteArray? + + /** + * Add a message to the message logger database + * + * @param id the ID of the message logger message + * @param message the content of the message + */ + abstract fun addMessageLoggerMessage(id: Long, message: ByteArray) + + /** + * Clear the message logger database + */ + abstract fun clearMessageLogger() + + /** + * Fetch the translations + * + * @return the translations result + */ + abstract fun fetchTranslations(): LocaleResult +}+ \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/bridge/MessageLoggerWrapper.kt b/app/src/main/kotlin/me/rhunk/snapenhance/bridge/MessageLoggerWrapper.kt @@ -0,0 +1,47 @@ +package me.rhunk.snapenhance.bridge + +import android.content.ContentValues +import android.database.sqlite.SQLiteDatabase +import java.io.File + +class MessageLoggerWrapper( + private val databaseFile: File +) { + + lateinit var database: SQLiteDatabase + + fun init() { + database = SQLiteDatabase.openDatabase(databaseFile.absolutePath, null, SQLiteDatabase.CREATE_IF_NECESSARY or SQLiteDatabase.OPEN_READWRITE) + database.execSQL("CREATE TABLE IF NOT EXISTS messages (message_id INTEGER PRIMARY KEY, serialized_message BLOB)") + } + + fun addMessage(messageId: Long, serializedMessage: ByteArray): Boolean { + val cursor = database.rawQuery("SELECT message_id FROM messages WHERE message_id = ?", arrayOf(messageId.toString())) + val state = cursor.moveToFirst() + cursor.close() + if (state) { + return false + } + database.insert("messages", null, ContentValues().apply { + put("message_id", messageId) + put("serialized_message", serializedMessage) + }) + return true + } + + fun clearMessages() { + database.execSQL("DELETE FROM messages") + } + + fun getMessage(messageId: Long): Pair<Boolean, ByteArray?> { + val cursor = database.rawQuery("SELECT serialized_message FROM messages WHERE message_id = ?", arrayOf(messageId.toString())) + val state = cursor.moveToFirst() + val message: ByteArray? = if (state) { + cursor.getBlob(0) + } else { + null + } + cursor.close() + return Pair(state, message) + } +}+ \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/bridge/client/BridgeClient.kt b/app/src/main/kotlin/me/rhunk/snapenhance/bridge/client/BridgeClient.kt @@ -1,254 +0,0 @@ -package me.rhunk.snapenhance.bridge.client - - -import android.content.ComponentName -import android.content.Context -import android.content.Intent -import android.content.ServiceConnection -import android.os.* -import me.rhunk.snapenhance.BuildConfig -import me.rhunk.snapenhance.Logger.xposedLog -import me.rhunk.snapenhance.ModContext -import me.rhunk.snapenhance.bridge.common.BridgeMessage -import me.rhunk.snapenhance.bridge.common.BridgeMessageType -import me.rhunk.snapenhance.bridge.common.impl.* -import me.rhunk.snapenhance.bridge.service.BridgeService -import java.util.concurrent.CompletableFuture -import java.util.concurrent.Executors -import kotlin.reflect.KClass -import kotlin.system.exitProcess - - -class BridgeClient( - private val context: ModContext -) : ServiceConnection { - private val handlerThread = HandlerThread("BridgeClient") - - private lateinit var messenger: Messenger - private lateinit var future: CompletableFuture<Boolean> - - fun start(callback: (Boolean) -> Unit = {}) { - this.future = CompletableFuture() - this.handlerThread.start() - - with(context.androidContext) { - val intent = Intent() - .setClassName(BuildConfig.APPLICATION_ID, BridgeService::class.java.name) - bindService( - intent, - Context.BIND_AUTO_CREATE, - Executors.newSingleThreadExecutor(), - this@BridgeClient - ) - } - callback(future.get()) - } - - private fun handleResponseMessage( - msg: Message, - future: CompletableFuture<BridgeMessage> - ) { - val message: BridgeMessage = when (BridgeMessageType.fromValue(msg.what)) { - BridgeMessageType.FILE_ACCESS_RESULT -> FileAccessResult() - BridgeMessageType.DOWNLOAD_CONTENT_RESULT -> DownloadContentResult() - BridgeMessageType.MESSAGE_LOGGER_RESULT -> MessageLoggerResult() - BridgeMessageType.LOCALE_RESULT -> LocaleResult() - else -> { - future.completeExceptionally(IllegalStateException("Unknown message type: ${msg.what}")) - return - } - } - - with(message) { - read(msg.data) - future.complete(this) - } - } - - @Suppress("UNCHECKED_CAST", "UNUSED_PARAMETER") - private fun <T : BridgeMessage> sendMessage( - messageType: BridgeMessageType, - message: BridgeMessage, - resultType: KClass<T>? = null - ): T { - val future = CompletableFuture<BridgeMessage>() - - val replyMessenger = Messenger(object : Handler(handlerThread.looper) { - override fun handleMessage(msg: Message) { - handleResponseMessage(msg, future) - } - }) - - runCatching { - with(Message.obtain()) { - what = messageType.value - replyTo = replyMessenger - data = Bundle() - message.write(data) - messenger.send(this) - } - } - - return future.get() as T - } - - /** - * Create a file if it doesn't exist, and read it - * - * @param fileType the type of file to create and read - * @param defaultContent the default content to write to the file if it doesn't exist - * @return the content of the file - */ - fun createAndReadFile( - fileType: FileAccessRequest.FileType, - defaultContent: ByteArray - ): ByteArray { - sendMessage( - BridgeMessageType.FILE_ACCESS_REQUEST, - FileAccessRequest(FileAccessRequest.FileAccessAction.EXISTS, fileType, null), - FileAccessResult::class - ).run { - if (state!!) { - return readFile(fileType) - } - writeFile(fileType, defaultContent) - return defaultContent - } - } - - /** - * Read a file - * - * @param fileType the type of file to read - * @return the content of the file - */ - fun readFile(fileType: FileAccessRequest.FileType): ByteArray { - sendMessage( - BridgeMessageType.FILE_ACCESS_REQUEST, - FileAccessRequest(FileAccessRequest.FileAccessAction.READ, fileType, null), - FileAccessResult::class - ).run { - return content!! - } - } - - /** - * Write a file - * - * @param fileType the type of file to write - * @param content the content to write to the file - * @return true if the file was written successfully - */ - fun writeFile( - fileType: FileAccessRequest.FileType, - content: ByteArray? - ): Boolean { - sendMessage( - BridgeMessageType.FILE_ACCESS_REQUEST, - FileAccessRequest(FileAccessRequest.FileAccessAction.WRITE, fileType, content), - FileAccessResult::class - ).run { - return state!! - } - } - - /** - * Delete a file - * - * @param fileType the type of file to delete - * @return true if the file was deleted successfully - */ - fun deleteFile(fileType: FileAccessRequest.FileType): Boolean { - sendMessage( - BridgeMessageType.FILE_ACCESS_REQUEST, - FileAccessRequest(FileAccessRequest.FileAccessAction.DELETE, fileType, null), - FileAccessResult::class - ).run { - return state!! - } - } - - /** - * Check if a file exists - * - * @param fileType the type of file to check - * @return true if the file exists - */ - - fun isFileExists(fileType: FileAccessRequest.FileType): Boolean { - sendMessage( - BridgeMessageType.FILE_ACCESS_REQUEST, - FileAccessRequest(FileAccessRequest.FileAccessAction.EXISTS, fileType, null), - FileAccessResult::class - ).run { - return state!! - } - } - - /** - * Download content from a URL and save it to a file - * - * @param url the URL to download content from - * @param path the path to save the content to - * @return true if the content was downloaded successfully - */ - fun downloadContent(url: String, path: String): Boolean { - sendMessage( - BridgeMessageType.DOWNLOAD_CONTENT_REQUEST, - DownloadContentRequest(url, path), - DownloadContentResult::class - ).run { - return state!! - } - } - - fun getMessageLoggerMessage(id: Long): ByteArray? { - sendMessage( - BridgeMessageType.MESSAGE_LOGGER_REQUEST, - MessageLoggerRequest(MessageLoggerRequest.Action.GET, id), - MessageLoggerResult::class - ).run { - return message - } - } - - fun addMessageLoggerMessage(id: Long, message: ByteArray) { - sendMessage( - BridgeMessageType.MESSAGE_LOGGER_REQUEST, - MessageLoggerRequest(MessageLoggerRequest.Action.ADD, id, message), - MessageLoggerResult::class - ) - } - - fun clearMessageLogger() { - sendMessage( - BridgeMessageType.MESSAGE_LOGGER_REQUEST, - MessageLoggerRequest(MessageLoggerRequest.Action.CLEAR, 0), - MessageLoggerResult::class - ) - } - - fun fetchTranslations(): LocaleResult { - sendMessage( - BridgeMessageType.LOCALE_REQUEST, - LocaleRequest(), - LocaleResult::class - ).run { - return this - } - } - - override fun onServiceConnected(name: ComponentName, service: IBinder) { - messenger = Messenger(service) - future.complete(true) - } - - override fun onNullBinding(name: ComponentName) { - xposedLog("failed to connect to bridge service") - future.complete(false) - } - - override fun onServiceDisconnected(name: ComponentName) { - exitProcess(0) - } -} diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/bridge/client/RootBridgeClient.kt b/app/src/main/kotlin/me/rhunk/snapenhance/bridge/client/RootBridgeClient.kt @@ -0,0 +1,133 @@ +package me.rhunk.snapenhance.bridge.client + +import android.os.Environment +import me.rhunk.snapenhance.Logger +import me.rhunk.snapenhance.bridge.AbstractBridgeClient +import me.rhunk.snapenhance.bridge.MessageLoggerWrapper +import me.rhunk.snapenhance.bridge.common.impl.file.BridgeFileType +import me.rhunk.snapenhance.bridge.common.impl.locale.LocaleResult +import java.io.File +import java.io.FileInputStream +import java.io.FileOutputStream +import java.io.OutputStream +import java.util.zip.ZipInputStream + +class RootBridgeClient : AbstractBridgeClient() { + private lateinit var messageLoggerWrapper: MessageLoggerWrapper + companion object { + private val MOD_FOLDER = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS),"SnapEnhance") + } + + override fun start(callback: (Boolean) -> Unit) { + if (!MOD_FOLDER.exists()) { + MOD_FOLDER.mkdirs() + } + messageLoggerWrapper = MessageLoggerWrapper(File(MOD_FOLDER, BridgeFileType.MESSAGE_LOGGER_DATABASE.fileName)).also { it.init() } + callback(true) + } + + override fun createAndReadFile(fileType: BridgeFileType, defaultContent: ByteArray): ByteArray { + val file = File(MOD_FOLDER, fileType.fileName) + if (file.exists()) { + return readFile(fileType) + } + val outputStream = openFileWritable(file) + outputStream.write(defaultContent) + outputStream.close() + return defaultContent + } + + override fun readFile(fileType: BridgeFileType): ByteArray { + return File(MOD_FOLDER, fileType.fileName).readBytes() + } + + override fun writeFile(fileType: BridgeFileType, content: ByteArray?): Boolean { + val outputStream = openFileWritable(File(MOD_FOLDER, fileType.fileName)) + outputStream.write(content) + outputStream.close() + return true + } + + override fun deleteFile(fileType: BridgeFileType): Boolean { + val file = File(MOD_FOLDER, fileType.fileName) + val exists = file.exists() + if (exists) { + rootOperation("rm ${file.absolutePath}") + } + return exists + } + + override fun isFileExists(fileType: BridgeFileType): Boolean { + return File(MOD_FOLDER, fileType.fileName).exists() + } + + override fun downloadContent(url: String, path: String): Boolean { + return true + } + + override fun getMessageLoggerMessage(id: Long): ByteArray? { + val (state, messageData) = messageLoggerWrapper.getMessage(id) + if (state) { + return messageData + } + return null + } + + override fun addMessageLoggerMessage(id: Long, message: ByteArray) { + messageLoggerWrapper.addMessage(id, message) + } + + override fun clearMessageLogger() { + messageLoggerWrapper.clearMessages() + } + + override fun fetchTranslations(): LocaleResult { + val locale = "en_US"//Locale.getDefault().toString() + + //https://github.com/LSPosed/LSPosed/blob/master/core/src/main/java/org/lsposed/lspd/util/LspModuleClassLoader.java#L36 + val moduleApk = javaClass.classLoader.javaClass.declaredFields.first { it.type == String::class.java }.let { + it.isAccessible = true + it.get(javaClass.classLoader) as String + } + + val langJsonData: ByteArray? = ZipInputStream(FileInputStream(moduleApk)).let { zip -> + while (true) { + val entry = zip.nextEntry ?: break + if (entry.name == "assets/lang/$locale.json") { + return@let zip.readBytes() + } + } + return@let null + } + + if (langJsonData != null) { + Logger.debug("Fetched translations for $locale") + return LocaleResult(locale, langJsonData) + } + + throw Throwable("Failed to fetch translations for $locale") + } + + private fun rootOperation(command: String): String { + val process = Runtime.getRuntime().exec("su -c $command") + process.waitFor() + process.errorStream?.bufferedReader()?.let { + val error = it.readText() + if (error.isNotEmpty()) { + throw Throwable("Failed to execute root operation: $error") + } + } + Logger.debug("Root operation executed: $command") + return process.inputStream.bufferedReader().readText() + } + + private fun openFileWritable(file: File): OutputStream { + runCatching { + if (!file.exists()) rootOperation("touch ${file.absolutePath}") + }.onFailure { + Logger.error("Failed to set file permissions: ${it.message}") + } + + return FileOutputStream(file) + } +}+ \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/bridge/client/ServiceBridgeClient.kt b/app/src/main/kotlin/me/rhunk/snapenhance/bridge/client/ServiceBridgeClient.kt @@ -0,0 +1,226 @@ +package me.rhunk.snapenhance.bridge.client + + +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.ServiceConnection +import android.os.Bundle +import android.os.Handler +import android.os.HandlerThread +import android.os.IBinder +import android.os.Message +import android.os.Messenger +import me.rhunk.snapenhance.BuildConfig +import me.rhunk.snapenhance.Logger.xposedLog +import me.rhunk.snapenhance.bridge.AbstractBridgeClient +import me.rhunk.snapenhance.bridge.common.BridgeMessage +import me.rhunk.snapenhance.bridge.common.BridgeMessageType +import me.rhunk.snapenhance.bridge.common.impl.download.DownloadContentRequest +import me.rhunk.snapenhance.bridge.common.impl.download.DownloadContentResult +import me.rhunk.snapenhance.bridge.common.impl.file.BridgeFileType +import me.rhunk.snapenhance.bridge.common.impl.file.FileAccessRequest +import me.rhunk.snapenhance.bridge.common.impl.file.FileAccessResult +import me.rhunk.snapenhance.bridge.common.impl.locale.LocaleRequest +import me.rhunk.snapenhance.bridge.common.impl.locale.LocaleResult +import me.rhunk.snapenhance.bridge.common.impl.messagelogger.MessageLoggerRequest +import me.rhunk.snapenhance.bridge.common.impl.messagelogger.MessageLoggerResult +import me.rhunk.snapenhance.bridge.service.BridgeService +import java.util.concurrent.CompletableFuture +import java.util.concurrent.Executors +import kotlin.reflect.KClass +import kotlin.system.exitProcess + + +class ServiceBridgeClient: AbstractBridgeClient(), ServiceConnection { + private val handlerThread = HandlerThread("BridgeClient") + + private lateinit var messenger: Messenger + private lateinit var future: CompletableFuture<Boolean> + + override fun start(callback: (Boolean) -> Unit) { + this.future = CompletableFuture() + this.handlerThread.start() + + with(context.androidContext) { + val intent = Intent() + .setClassName(BuildConfig.APPLICATION_ID, BridgeService::class.java.name) + bindService( + intent, + Context.BIND_AUTO_CREATE, + Executors.newSingleThreadExecutor(), + this@ServiceBridgeClient + ) + } + callback(future.get()) + } + + private fun handleResponseMessage( + msg: Message, + future: CompletableFuture<BridgeMessage> + ) { + val message: BridgeMessage = when (BridgeMessageType.fromValue(msg.what)) { + BridgeMessageType.FILE_ACCESS_RESULT -> FileAccessResult() + BridgeMessageType.DOWNLOAD_CONTENT_RESULT -> DownloadContentResult() + BridgeMessageType.MESSAGE_LOGGER_RESULT -> MessageLoggerResult() + BridgeMessageType.LOCALE_RESULT -> LocaleResult() + else -> { + future.completeExceptionally(IllegalStateException("Unknown message type: ${msg.what}")) + return + } + } + + with(message) { + read(msg.data) + future.complete(this) + } + } + + @Suppress("UNCHECKED_CAST", "UNUSED_PARAMETER") + private fun <T : BridgeMessage> sendMessage( + messageType: BridgeMessageType, + message: BridgeMessage, + resultType: KClass<T>? = null + ): T { + val future = CompletableFuture<BridgeMessage>() + + val replyMessenger = Messenger(object : Handler(handlerThread.looper) { + override fun handleMessage(msg: Message) { + handleResponseMessage(msg, future) + } + }) + + runCatching { + with(Message.obtain()) { + what = messageType.value + replyTo = replyMessenger + data = Bundle() + message.write(data) + messenger.send(this) + } + } + + return future.get() as T + } + + override fun createAndReadFile( + fileType: BridgeFileType, + defaultContent: ByteArray + ): ByteArray { + sendMessage( + BridgeMessageType.FILE_ACCESS_REQUEST, + FileAccessRequest(FileAccessRequest.FileAccessAction.EXISTS, fileType, null), + FileAccessResult::class + ).run { + if (state!!) { + return readFile(fileType) + } + writeFile(fileType, defaultContent) + return defaultContent + } + } + + override fun readFile(fileType: BridgeFileType): ByteArray { + sendMessage( + BridgeMessageType.FILE_ACCESS_REQUEST, + FileAccessRequest(FileAccessRequest.FileAccessAction.READ, fileType, null), + FileAccessResult::class + ).run { + return content!! + } + } + + override fun writeFile( + fileType: BridgeFileType, + content: ByteArray? + ): Boolean { + sendMessage( + BridgeMessageType.FILE_ACCESS_REQUEST, + FileAccessRequest(FileAccessRequest.FileAccessAction.WRITE, fileType, content), + FileAccessResult::class + ).run { + return state!! + } + } + + override fun deleteFile(fileType: BridgeFileType): Boolean { + sendMessage( + BridgeMessageType.FILE_ACCESS_REQUEST, + FileAccessRequest(FileAccessRequest.FileAccessAction.DELETE, fileType, null), + FileAccessResult::class + ).run { + return state!! + } + } + + + override fun isFileExists(fileType: BridgeFileType): Boolean { + sendMessage( + BridgeMessageType.FILE_ACCESS_REQUEST, + FileAccessRequest(FileAccessRequest.FileAccessAction.EXISTS, fileType, null), + FileAccessResult::class + ).run { + return state!! + } + } + + override fun downloadContent(url: String, path: String): Boolean { + sendMessage( + BridgeMessageType.DOWNLOAD_CONTENT_REQUEST, + DownloadContentRequest(url, path), + DownloadContentResult::class + ).run { + return state!! + } + } + + override fun getMessageLoggerMessage(id: Long): ByteArray? { + sendMessage( + BridgeMessageType.MESSAGE_LOGGER_REQUEST, + MessageLoggerRequest(MessageLoggerRequest.Action.GET, id), + MessageLoggerResult::class + ).run { + return message + } + } + + override fun addMessageLoggerMessage(id: Long, message: ByteArray) { + sendMessage( + BridgeMessageType.MESSAGE_LOGGER_REQUEST, + MessageLoggerRequest(MessageLoggerRequest.Action.ADD, id, message), + MessageLoggerResult::class + ) + } + + override fun clearMessageLogger() { + sendMessage( + BridgeMessageType.MESSAGE_LOGGER_REQUEST, + MessageLoggerRequest(MessageLoggerRequest.Action.CLEAR, 0), + MessageLoggerResult::class + ) + } + + override fun fetchTranslations(): LocaleResult { + sendMessage( + BridgeMessageType.LOCALE_REQUEST, + LocaleRequest(), + LocaleResult::class + ).run { + return this + } + } + + override fun onServiceConnected(name: ComponentName, service: IBinder) { + messenger = Messenger(service) + future.complete(true) + } + + override fun onNullBinding(name: ComponentName) { + xposedLog("failed to connect to bridge service") + future.complete(false) + } + + override fun onServiceDisconnected(name: ComponentName) { + exitProcess(0) + } +} diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/DownloadContentRequest.kt b/app/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/DownloadContentRequest.kt @@ -1,20 +0,0 @@ -package me.rhunk.snapenhance.bridge.common.impl - -import android.os.Bundle -import me.rhunk.snapenhance.bridge.common.BridgeMessage - -class DownloadContentRequest( - var url: String? = null, - var path: String? = null -) : BridgeMessage() { - - override fun write(bundle: Bundle) { - bundle.putString("url", url) - bundle.putString("path", path) - } - - override fun read(bundle: Bundle) { - url = bundle.getString("url") - path = bundle.getString("path") - } -} diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/DownloadContentResult.kt b/app/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/DownloadContentResult.kt @@ -1,17 +0,0 @@ -package me.rhunk.snapenhance.bridge.common.impl - -import android.os.Bundle -import me.rhunk.snapenhance.bridge.common.BridgeMessage - -class DownloadContentResult( - var state: Boolean? = null -) : BridgeMessage() { - - override fun write(bundle: Bundle) { - bundle.putBoolean("state", state!!) - } - - override fun read(bundle: Bundle) { - state = bundle.getBoolean("state") - } -} diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/FileAccessRequest.kt b/app/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/FileAccessRequest.kt @@ -1,43 +0,0 @@ -package me.rhunk.snapenhance.bridge.common.impl - -import android.os.Bundle -import me.rhunk.snapenhance.bridge.common.BridgeMessage - -class FileAccessRequest( - var action: FileAccessAction? = null, - var fileType: FileType? = null, - var content: ByteArray? = null -) : BridgeMessage() { - - override fun write(bundle: Bundle) { - bundle.putInt("action", action!!.value) - bundle.putInt("fileType", fileType!!.value) - bundle.putByteArray("content", content) - } - - override fun read(bundle: Bundle) { - action = FileAccessAction.fromValue(bundle.getInt("action")) - fileType = FileType.fromValue(bundle.getInt("fileType")) - content = bundle.getByteArray("content") - } - - enum class FileType(val value: Int) { - CONFIG(0), MAPPINGS(1), STEALTH(2), ANTI_AUTO_DOWNLOAD(3); - - companion object { - fun fromValue(value: Int): FileType? { - return values().firstOrNull { it.value == value } - } - } - } - - enum class FileAccessAction(val value: Int) { - READ(0), WRITE(1), DELETE(2), EXISTS(3); - - companion object { - fun fromValue(value: Int): FileAccessAction? { - return values().firstOrNull { it.value == value } - } - } - } -} diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/FileAccessResult.kt b/app/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/FileAccessResult.kt @@ -1,20 +0,0 @@ -package me.rhunk.snapenhance.bridge.common.impl - -import android.os.Bundle -import me.rhunk.snapenhance.bridge.common.BridgeMessage - -class FileAccessResult( - var state: Boolean? = null, - var content: ByteArray? = null -) : BridgeMessage() { - - override fun write(bundle: Bundle) { - bundle.putBoolean("state", state!!) - bundle.putByteArray("content", content) - } - - override fun read(bundle: Bundle) { - state = bundle.getBoolean("state") - content = bundle.getByteArray("content") - } -} diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/LocaleRequest.kt b/app/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/LocaleRequest.kt @@ -1,12 +0,0 @@ -package me.rhunk.snapenhance.bridge.common.impl - -import android.os.Bundle -import me.rhunk.snapenhance.bridge.common.BridgeMessage - -class LocaleRequest() : BridgeMessage() { - override fun write(bundle: Bundle) { - } - - override fun read(bundle: Bundle) { - } -}- \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/LocaleResult.kt b/app/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/LocaleResult.kt @@ -1,19 +0,0 @@ -package me.rhunk.snapenhance.bridge.common.impl - -import android.os.Bundle -import me.rhunk.snapenhance.bridge.common.BridgeMessage - -class LocaleResult( - var locale: String? = null, - var content: ByteArray? = null -) : BridgeMessage(){ - override fun write(bundle: Bundle) { - bundle.putString("locale", locale) - bundle.putByteArray("content", content) - } - - override fun read(bundle: Bundle) { - locale = bundle.getString("locale") - content = bundle.getByteArray("content") - } -}- \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/MessageLoggerRequest.kt b/app/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/MessageLoggerRequest.kt @@ -1,29 +0,0 @@ -package me.rhunk.snapenhance.bridge.common.impl - -import android.os.Bundle -import me.rhunk.snapenhance.bridge.common.BridgeMessage - -class MessageLoggerRequest( - var action: Action? = null, - var messageId: Long? = null, - var message: ByteArray? = null -) : BridgeMessage(){ - - override fun write(bundle: Bundle) { - bundle.putString("action", action!!.name) - bundle.putLong("messageId", messageId!!) - bundle.putByteArray("message", message) - } - - override fun read(bundle: Bundle) { - action = Action.valueOf(bundle.getString("action")!!) - messageId = bundle.getLong("messageId") - message = bundle.getByteArray("message") - } - - enum class Action { - ADD, - GET, - CLEAR - } -}- \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/MessageLoggerResult.kt b/app/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/MessageLoggerResult.kt @@ -1,20 +0,0 @@ -package me.rhunk.snapenhance.bridge.common.impl - -import android.os.Bundle -import me.rhunk.snapenhance.bridge.common.BridgeMessage - -class MessageLoggerResult( - var state: Boolean? = null, - var message: ByteArray? = null -) : BridgeMessage() { - - override fun write(bundle: Bundle) { - bundle.putBoolean("state", state!!) - bundle.putByteArray("message", message) - } - - override fun read(bundle: Bundle) { - state = bundle.getBoolean("state") - message = bundle.getByteArray("message") - } -}- \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/download/DownloadContentRequest.kt b/app/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/download/DownloadContentRequest.kt @@ -0,0 +1,20 @@ +package me.rhunk.snapenhance.bridge.common.impl.download + +import android.os.Bundle +import me.rhunk.snapenhance.bridge.common.BridgeMessage + +class DownloadContentRequest( + var url: String? = null, + var path: String? = null +) : BridgeMessage() { + + override fun write(bundle: Bundle) { + bundle.putString("url", url) + bundle.putString("path", path) + } + + override fun read(bundle: Bundle) { + url = bundle.getString("url") + path = bundle.getString("path") + } +} diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/download/DownloadContentResult.kt b/app/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/download/DownloadContentResult.kt @@ -0,0 +1,17 @@ +package me.rhunk.snapenhance.bridge.common.impl.download + +import android.os.Bundle +import me.rhunk.snapenhance.bridge.common.BridgeMessage + +class DownloadContentResult( + var state: Boolean? = null +) : BridgeMessage() { + + override fun write(bundle: Bundle) { + bundle.putBoolean("state", state!!) + } + + override fun read(bundle: Bundle) { + state = bundle.getBoolean("state") + } +} diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/file/BridgeFileType.kt b/app/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/file/BridgeFileType.kt @@ -0,0 +1,17 @@ +package me.rhunk.snapenhance.bridge.common.impl.file + + +enum class BridgeFileType(val value: Int, val fileName: String, val isDatabase: Boolean = false) { + CONFIG(0, "config.json"), + MAPPINGS(1, "mappings.json"), + MESSAGE_LOGGER_DATABASE(2, "message_logger.db", true), + STEALTH(3, "stealth.txt"), + ANTI_AUTO_DOWNLOAD(4, "anti_auto_download.txt"), + ANTI_AUTO_SAVE(5, "anti_auto_save.txt"); + + companion object { + fun fromValue(value: Int): BridgeFileType? { + return values().firstOrNull { it.value == value } + } + } +}+ \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/file/FileAccessRequest.kt b/app/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/file/FileAccessRequest.kt @@ -0,0 +1,33 @@ +package me.rhunk.snapenhance.bridge.common.impl.file + +import android.os.Bundle +import me.rhunk.snapenhance.bridge.common.BridgeMessage + +class FileAccessRequest( + var action: FileAccessAction? = null, + var fileType: BridgeFileType? = null, + var content: ByteArray? = null +) : BridgeMessage() { + + override fun write(bundle: Bundle) { + bundle.putInt("action", action!!.value) + bundle.putInt("fileType", fileType!!.value) + bundle.putByteArray("content", content) + } + + override fun read(bundle: Bundle) { + action = FileAccessAction.fromValue(bundle.getInt("action")) + fileType = BridgeFileType.fromValue(bundle.getInt("fileType")) + content = bundle.getByteArray("content") + } + + enum class FileAccessAction(val value: Int) { + READ(0), WRITE(1), DELETE(2), EXISTS(3); + + companion object { + fun fromValue(value: Int): FileAccessAction? { + return values().firstOrNull { it.value == value } + } + } + } +} diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/file/FileAccessResult.kt b/app/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/file/FileAccessResult.kt @@ -0,0 +1,20 @@ +package me.rhunk.snapenhance.bridge.common.impl.file + +import android.os.Bundle +import me.rhunk.snapenhance.bridge.common.BridgeMessage + +class FileAccessResult( + var state: Boolean? = null, + var content: ByteArray? = null +) : BridgeMessage() { + + override fun write(bundle: Bundle) { + bundle.putBoolean("state", state!!) + bundle.putByteArray("content", content) + } + + override fun read(bundle: Bundle) { + state = bundle.getBoolean("state") + content = bundle.getByteArray("content") + } +} diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/locale/LocaleRequest.kt b/app/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/locale/LocaleRequest.kt @@ -0,0 +1,12 @@ +package me.rhunk.snapenhance.bridge.common.impl.locale + +import android.os.Bundle +import me.rhunk.snapenhance.bridge.common.BridgeMessage + +class LocaleRequest() : BridgeMessage() { + override fun write(bundle: Bundle) { + } + + override fun read(bundle: Bundle) { + } +}+ \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/locale/LocaleResult.kt b/app/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/locale/LocaleResult.kt @@ -0,0 +1,19 @@ +package me.rhunk.snapenhance.bridge.common.impl.locale + +import android.os.Bundle +import me.rhunk.snapenhance.bridge.common.BridgeMessage + +class LocaleResult( + var locale: String? = null, + var content: ByteArray? = null +) : BridgeMessage(){ + override fun write(bundle: Bundle) { + bundle.putString("locale", locale) + bundle.putByteArray("content", content) + } + + override fun read(bundle: Bundle) { + locale = bundle.getString("locale") + content = bundle.getByteArray("content") + } +}+ \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/messagelogger/MessageLoggerRequest.kt b/app/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/messagelogger/MessageLoggerRequest.kt @@ -0,0 +1,29 @@ +package me.rhunk.snapenhance.bridge.common.impl.messagelogger + +import android.os.Bundle +import me.rhunk.snapenhance.bridge.common.BridgeMessage + +class MessageLoggerRequest( + var action: Action? = null, + var messageId: Long? = null, + var message: ByteArray? = null +) : BridgeMessage(){ + + override fun write(bundle: Bundle) { + bundle.putString("action", action!!.name) + bundle.putLong("messageId", messageId!!) + bundle.putByteArray("message", message) + } + + override fun read(bundle: Bundle) { + action = Action.valueOf(bundle.getString("action")!!) + messageId = bundle.getLong("messageId") + message = bundle.getByteArray("message") + } + + enum class Action { + ADD, + GET, + CLEAR + } +}+ \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/messagelogger/MessageLoggerResult.kt b/app/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/messagelogger/MessageLoggerResult.kt @@ -0,0 +1,20 @@ +package me.rhunk.snapenhance.bridge.common.impl.messagelogger + +import android.os.Bundle +import me.rhunk.snapenhance.bridge.common.BridgeMessage + +class MessageLoggerResult( + var state: Boolean? = null, + var message: ByteArray? = null +) : BridgeMessage() { + + override fun write(bundle: Bundle) { + bundle.putBoolean("state", state!!) + bundle.putByteArray("message", message) + } + + override fun read(bundle: Bundle) { + state = bundle.getBoolean("state") + message = bundle.getByteArray("message") + } +}+ \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/bridge/service/BridgeService.kt b/app/src/main/kotlin/me/rhunk/snapenhance/bridge/service/BridgeService.kt @@ -4,31 +4,29 @@ import android.annotation.SuppressLint import android.app.DownloadManager import android.app.Service import android.content.* -import android.database.sqlite.SQLiteDatabase import android.net.Uri import android.os.* import me.rhunk.snapenhance.Logger +import me.rhunk.snapenhance.bridge.MessageLoggerWrapper import me.rhunk.snapenhance.bridge.common.BridgeMessageType import me.rhunk.snapenhance.bridge.common.impl.* +import me.rhunk.snapenhance.bridge.common.impl.download.DownloadContentRequest +import me.rhunk.snapenhance.bridge.common.impl.download.DownloadContentResult +import me.rhunk.snapenhance.bridge.common.impl.file.BridgeFileType +import me.rhunk.snapenhance.bridge.common.impl.file.FileAccessRequest +import me.rhunk.snapenhance.bridge.common.impl.file.FileAccessResult +import me.rhunk.snapenhance.bridge.common.impl.locale.LocaleRequest +import me.rhunk.snapenhance.bridge.common.impl.locale.LocaleResult +import me.rhunk.snapenhance.bridge.common.impl.messagelogger.MessageLoggerRequest +import me.rhunk.snapenhance.bridge.common.impl.messagelogger.MessageLoggerResult import java.io.File import java.util.* class BridgeService : Service() { - companion object { - const val CONFIG_FILE = "config.json" - const val MAPPINGS_FILE = "mappings.json" - const val STEALTH_FILE = "stealth.txt" - const val MESSAGE_LOGGER_DATABASE = "message_logger" - const val ANTI_AUTO_DOWNLOAD_FILE = "anti_auto_download.txt" - } - - lateinit var messageLoggerDatabase: SQLiteDatabase + private lateinit var messageLoggerWrapper: MessageLoggerWrapper override fun onBind(intent: Intent): IBinder { - with(openOrCreateDatabase(MESSAGE_LOGGER_DATABASE, Context.MODE_PRIVATE, null)) { - messageLoggerDatabase = this - execSQL("CREATE TABLE IF NOT EXISTS messages (message_id INTEGER PRIMARY KEY, serialized_message BLOB)") - } + messageLoggerWrapper = MessageLoggerWrapper(getDatabasePath(BridgeFileType.MESSAGE_LOGGER_DATABASE.fileName)).also { it.init() } return Messenger(object : Handler(Looper.getMainLooper()) { override fun handleMessage(msg: Message) { @@ -84,33 +82,16 @@ class BridgeService : Service() { private fun handleMessageLoggerRequest(msg: MessageLoggerRequest, reply: (Message) -> Unit) { when (msg.action) { MessageLoggerRequest.Action.ADD -> { - //check if message already exists - val cursor = messageLoggerDatabase.rawQuery("SELECT message_id FROM messages WHERE message_id = ?", arrayOf(msg.messageId.toString())) - val state = cursor.moveToFirst() - cursor.close() - if (state) { - reply(MessageLoggerResult(false).toMessage(BridgeMessageType.MESSAGE_LOGGER_RESULT.value)) - return - } - messageLoggerDatabase.insert("messages", null, ContentValues().apply { - put("message_id", msg.messageId) - put("serialized_message", msg.message) - }) + val isSuccess = messageLoggerWrapper.addMessage(msg.messageId!!, msg.message!!) + reply(MessageLoggerResult(isSuccess).toMessage(BridgeMessageType.MESSAGE_LOGGER_RESULT.value)) + return } MessageLoggerRequest.Action.CLEAR -> { - messageLoggerDatabase.execSQL("DELETE FROM messages") + messageLoggerWrapper.clearMessages() } MessageLoggerRequest.Action.GET -> { - val messageId = msg.messageId - val cursor = messageLoggerDatabase.rawQuery("SELECT serialized_message FROM messages WHERE message_id = ?", arrayOf(messageId.toString())) - val state = cursor.moveToFirst() - val message: ByteArray? = if (state) { - cursor.getBlob(0) - } else { - null - } - cursor.close() - reply(MessageLoggerResult(state, message).toMessage(BridgeMessageType.MESSAGE_LOGGER_RESULT.value)) + val (state, messageData) = messageLoggerWrapper.getMessage(msg.messageId!!) + reply(MessageLoggerResult(state, messageData).toMessage(BridgeMessageType.MESSAGE_LOGGER_RESULT.value)) } else -> { Logger.log(Exception("Unknown message logger action: ${msg.action}")) @@ -155,38 +136,37 @@ class BridgeService : Service() { } private fun handleFileAccess(msg: FileAccessRequest, reply: (Message) -> Unit) { - val file = when (msg.fileType) { - FileAccessRequest.FileType.CONFIG -> CONFIG_FILE - FileAccessRequest.FileType.MAPPINGS -> MAPPINGS_FILE - FileAccessRequest.FileType.STEALTH -> STEALTH_FILE - FileAccessRequest.FileType.ANTI_AUTO_DOWNLOAD -> ANTI_AUTO_DOWNLOAD_FILE - else -> throw Exception("Unknown file type: " + msg.fileType) - }.let { File(filesDir, it) } + val fileFolder = if (msg.fileType!!.isDatabase) { + File(dataDir, "databases") + } else { + File(filesDir.absolutePath) + } + val requestFile = File(fileFolder, msg.fileType!!.fileName) val result: FileAccessResult = when (msg.action) { FileAccessRequest.FileAccessAction.READ -> { - if (!file.exists()) { + if (!requestFile.exists()) { FileAccessResult(false, null) } else { - FileAccessResult(true, file.readBytes()) + FileAccessResult(true, requestFile.readBytes()) } } FileAccessRequest.FileAccessAction.WRITE -> { - if (!file.exists()) { - file.createNewFile() + if (!requestFile.exists()) { + requestFile.createNewFile() } - file.writeBytes(msg.content!!) + requestFile.writeBytes(msg.content!!) FileAccessResult(true, null) } FileAccessRequest.FileAccessAction.DELETE -> { - if (!file.exists()) { + if (!requestFile.exists()) { FileAccessResult(false, null) } else { - file.delete() + requestFile.delete() FileAccessResult(true, null) } } - FileAccessRequest.FileAccessAction.EXISTS -> FileAccessResult(file.exists(), null) + FileAccessRequest.FileAccessAction.EXISTS -> FileAccessResult(requestFile.exists(), null) else -> throw Exception("Unknown action: " + msg.action) } diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/data/wrapper/impl/SnapUUID.kt b/app/src/main/kotlin/me/rhunk/snapenhance/data/wrapper/impl/SnapUUID.kt @@ -4,7 +4,7 @@ import me.rhunk.snapenhance.SnapEnhance import me.rhunk.snapenhance.data.wrapper.AbstractWrapper import me.rhunk.snapenhance.util.getObjectField import java.nio.ByteBuffer -import java.util.* +import java.util.UUID class SnapUUID(obj: Any?) : AbstractWrapper(obj) { private val uuidString by lazy { toUUID().toString() } diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/features/BridgeFileFeature.kt b/app/src/main/kotlin/me/rhunk/snapenhance/features/BridgeFileFeature.kt @@ -0,0 +1,54 @@ +package me.rhunk.snapenhance.features + +import me.rhunk.snapenhance.bridge.common.impl.file.BridgeFileType +import java.io.BufferedReader +import java.io.ByteArrayInputStream +import java.io.InputStreamReader +import java.nio.charset.StandardCharsets + +abstract class BridgeFileFeature(name: String, private val bridgeFileType: BridgeFileType, loadParams: Int) : Feature(name, loadParams) { + private val fileLines = mutableListOf<String>() + + protected fun readFile() { + val temporaryLines = mutableListOf<String>() + val fileData: ByteArray = context.bridgeClient.createAndReadFile(bridgeFileType, ByteArray(0)) + with(BufferedReader(InputStreamReader(ByteArrayInputStream(fileData), StandardCharsets.UTF_8))) { + var line = "" + while (readLine()?.also { line = it } != null) temporaryLines.add(line) + close() + } + fileLines.clear() + fileLines.addAll(temporaryLines) + } + + private fun updateFile() { + val sb = StringBuilder() + fileLines.forEach { + sb.append(it).append("\n") + } + context.bridgeClient.writeFile(bridgeFileType, sb.toString().toByteArray(Charsets.UTF_8)) + } + + protected fun exists(line: String) = fileLines.contains(line) + + protected fun toggle(line: String) { + if (exists(line)) fileLines.remove(line) else fileLines.add(line) + updateFile() + } + + protected fun setState(line: String, state: Boolean) { + if (state) { + if (!exists(line)) fileLines.add(line) + } else { + if (exists(line)) fileLines.remove(line) + } + updateFile() + } + + protected fun reload() = readFile() + + protected fun put(line: String) { + fileLines.add(line) + updateFile() + } +}+ \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/downloader/AntiAutoDownload.kt b/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/downloader/AntiAutoDownload.kt @@ -1,55 +1,19 @@ package me.rhunk.snapenhance.features.impl.downloader -import me.rhunk.snapenhance.bridge.common.impl.FileAccessRequest -import me.rhunk.snapenhance.features.Feature +import me.rhunk.snapenhance.bridge.common.impl.file.BridgeFileType +import me.rhunk.snapenhance.features.BridgeFileFeature import me.rhunk.snapenhance.features.FeatureLoadParams -import java.io.BufferedReader -import java.io.ByteArrayInputStream -import java.io.InputStreamReader -import java.nio.charset.StandardCharsets - -class AntiAutoDownload : Feature("AntiAutoDownload", loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC) { - private val excludedUsers = mutableListOf<String>() +class AntiAutoDownload : BridgeFileFeature("AntiAutoDownload", BridgeFileType.ANTI_AUTO_DOWNLOAD, loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC) { override fun onActivityCreate() { - readExclusionFile() - } - - private fun readExclusionFile() { - val userIds = mutableListOf<String>() - val exclusionFileData: ByteArray = context.bridgeClient.createAndReadFile(FileAccessRequest.FileType.ANTI_AUTO_DOWNLOAD, ByteArray(0)) - with(BufferedReader(InputStreamReader(ByteArrayInputStream(exclusionFileData), StandardCharsets.UTF_8))) { - var line = "" - while (readLine()?.also { line = it } != null) userIds.add(line) - close() - } - excludedUsers.clear() - excludedUsers.addAll(userIds) - } - - private fun writeExclusionFile() { - val sb = StringBuilder() - excludedUsers.forEach { - sb.append(it).append("\n") - } - context.bridgeClient.writeFile( - FileAccessRequest.FileType.ANTI_AUTO_DOWNLOAD, - sb.toString().toByteArray(Charsets.UTF_8) - ) + readFile() } fun setUserIgnored(userId: String, state: Boolean) { - userId.hashCode().toLong().toString(16).let { - if (state) { - excludedUsers.add(it) - } else { - excludedUsers.remove(it) - } - } - writeExclusionFile() + setState(userId.hashCode().toLong().toString(16), state) } fun isUserIgnored(userId: String): Boolean { - return excludedUsers.contains(userId.hashCode().toLong().toString(16)) + return exists(userId.hashCode().toLong().toString(16)) } } \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/privacy/DisableMetrics.kt b/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/privacy/DisableMetrics.kt @@ -9,7 +9,7 @@ import me.rhunk.snapenhance.hook.HookAdapter import me.rhunk.snapenhance.hook.HookStage import me.rhunk.snapenhance.hook.Hooker import java.nio.charset.StandardCharsets -import java.util.* +import java.util.Base64 class DisableMetrics : Feature("DisableMetrics", loadParams = FeatureLoadParams.INIT_SYNC) { override fun init() { diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/spy/StealthMode.kt b/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/spy/StealthMode.kt @@ -1,62 +1,20 @@ package me.rhunk.snapenhance.features.impl.spy -import me.rhunk.snapenhance.bridge.common.impl.FileAccessRequest -import me.rhunk.snapenhance.features.Feature +import me.rhunk.snapenhance.bridge.common.impl.file.BridgeFileType +import me.rhunk.snapenhance.features.BridgeFileFeature import me.rhunk.snapenhance.features.FeatureLoadParams -import java.io.BufferedReader -import java.io.ByteArrayInputStream -import java.io.InputStreamReader -import java.nio.charset.StandardCharsets -class StealthMode : Feature("StealthMode", loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC) { - private val stealthConversations = mutableListOf<String>() - +class StealthMode : BridgeFileFeature("StealthMode", BridgeFileType.STEALTH, loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC) { override fun onActivityCreate() { - readStealthFile() - } - - private fun writeStealthFile() { - val sb = StringBuilder() - for (stealthConversation in stealthConversations) { - sb.append(stealthConversation).append("\n") - } - context.bridgeClient.writeFile( - FileAccessRequest.FileType.STEALTH, - sb.toString().toByteArray(StandardCharsets.UTF_8) - ) - } - - private fun readStealthFile() { - val conversations = mutableListOf<String>() - val stealthFileData: ByteArray = context.bridgeClient.createAndReadFile(FileAccessRequest.FileType.STEALTH, ByteArray(0)) - //read conversations - with(BufferedReader(InputStreamReader( - ByteArrayInputStream(stealthFileData), - StandardCharsets.UTF_8 - ))) { - var line: String = "" - while (readLine()?.also { line = it } != null) { - conversations.add(line) - } - close() - } - stealthConversations.clear() - stealthConversations.addAll(conversations) + readFile() } fun setStealth(conversationId: String, stealth: Boolean) { - conversationId.hashCode().toLong().toString(16).let { - if (stealth) { - stealthConversations.add(it) - } else { - stealthConversations.remove(it) - } - } - writeStealthFile() + setState(conversationId.hashCode().toLong().toString(16), stealth) } fun isStealth(conversationId: String): Boolean { - return stealthConversations.contains(conversationId.hashCode().toLong().toString(16)) + return exists(conversationId.hashCode().toLong().toString(16)) } } diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/ui/menus/impl/FriendFeedInfoMenu.kt b/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/ui/menus/impl/FriendFeedInfoMenu.kt @@ -28,7 +28,9 @@ import java.net.HttpURLConnection import java.net.URL import java.text.DateFormat import java.text.SimpleDateFormat -import java.util.* +import java.util.Calendar +import java.util.Date +import java.util.Locale class FriendFeedInfoMenu : AbstractMenu() { private fun getImageDrawable(url: String): Drawable { diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/manager/impl/ConfigManager.kt b/app/src/main/kotlin/me/rhunk/snapenhance/manager/impl/ConfigManager.kt @@ -3,7 +3,7 @@ package me.rhunk.snapenhance.manager.impl import com.google.gson.JsonObject import me.rhunk.snapenhance.Logger import me.rhunk.snapenhance.ModContext -import me.rhunk.snapenhance.bridge.common.impl.FileAccessRequest +import me.rhunk.snapenhance.bridge.common.impl.file.BridgeFileType import me.rhunk.snapenhance.config.ConfigAccessor import me.rhunk.snapenhance.config.ConfigProperty import me.rhunk.snapenhance.manager.Manager @@ -22,7 +22,7 @@ class ConfigManager( set(key, key.defaultValue) } - if (!context.bridgeClient.isFileExists(FileAccessRequest.FileType.CONFIG)) { + if (!context.bridgeClient.isFileExists(BridgeFileType.CONFIG)) { writeConfig() return } @@ -37,7 +37,7 @@ class ConfigManager( private fun loadConfig() { val configContent = context.bridgeClient.createAndReadFile( - FileAccessRequest.FileType.CONFIG, + BridgeFileType.CONFIG, "{}".toByteArray(Charsets.UTF_8) ) val configObject: JsonObject = context.gson.fromJson( @@ -56,7 +56,7 @@ class ConfigManager( configObject.add(key.name, context.gson.toJsonTree(get(key))) } context.bridgeClient.writeFile( - FileAccessRequest.FileType.CONFIG, + BridgeFileType.CONFIG, context.gson.toJson(configObject).toByteArray(Charsets.UTF_8) ) } diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/manager/impl/MappingManager.kt b/app/src/main/kotlin/me/rhunk/snapenhance/manager/impl/MappingManager.kt @@ -9,7 +9,7 @@ import kotlinx.coroutines.runBlocking import me.rhunk.snapenhance.Constants import me.rhunk.snapenhance.Logger import me.rhunk.snapenhance.ModContext -import me.rhunk.snapenhance.bridge.common.impl.FileAccessRequest +import me.rhunk.snapenhance.bridge.common.impl.file.BridgeFileType import me.rhunk.snapenhance.manager.Manager import me.rhunk.snapenhance.mapping.Mapper import me.rhunk.snapenhance.mapping.impl.CallbackMapper @@ -40,7 +40,7 @@ class MappingManager(private val context: ModContext) : Manager { ).longVersionCode.toInt() snapBuildNumber = currentBuildNumber - if (context.bridgeClient.isFileExists(FileAccessRequest.FileType.MAPPINGS)) { + if (context.bridgeClient.isFileExists(BridgeFileType.MAPPINGS)) { runCatching { loadCached() }.onFailure { @@ -48,7 +48,7 @@ class MappingManager(private val context: ModContext) : Manager { } if (snapBuildNumber != currentBuildNumber) { - context.bridgeClient.deleteFile(FileAccessRequest.FileType.MAPPINGS) + context.bridgeClient.deleteFile(BridgeFileType.MAPPINGS) context.softRestartApp() } return @@ -63,13 +63,13 @@ class MappingManager(private val context: ModContext) : Manager { } private fun loadCached() { - if (!context.bridgeClient.isFileExists(FileAccessRequest.FileType.MAPPINGS)) { + if (!context.bridgeClient.isFileExists(BridgeFileType.MAPPINGS)) { Logger.xposedLog("Mappings file does not exist") return } val mappingsObject = JsonParser.parseString( String( - context.bridgeClient.readFile(FileAccessRequest.FileType.MAPPINGS), + context.bridgeClient.readFile(BridgeFileType.MAPPINGS), StandardCharsets.UTF_8 ) ).asJsonObject.also { @@ -146,7 +146,7 @@ class MappingManager(private val context: ModContext) : Manager { } context.bridgeClient.writeFile( - FileAccessRequest.FileType.MAPPINGS, + BridgeFileType.MAPPINGS, mappingsObject.toString().toByteArray() ) } diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/manager/impl/TranslationManager.kt b/app/src/main/kotlin/me/rhunk/snapenhance/manager/impl/TranslationManager.kt @@ -4,7 +4,7 @@ import com.google.gson.JsonParser import me.rhunk.snapenhance.Logger import me.rhunk.snapenhance.ModContext import me.rhunk.snapenhance.manager.Manager -import java.util.* +import java.util.Locale class TranslationManager( private val context: ModContext diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/mapping/impl/EnumMapper.kt b/app/src/main/kotlin/me/rhunk/snapenhance/mapping/impl/EnumMapper.kt @@ -3,7 +3,7 @@ package me.rhunk.snapenhance.mapping.impl import me.rhunk.snapenhance.Logger.debug import me.rhunk.snapenhance.mapping.Mapper import java.lang.reflect.Method -import java.util.* +import java.util.Objects class EnumMapper : Mapper() { diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/mapping/impl/OperaPageViewControllerMapper.kt b/app/src/main/kotlin/me/rhunk/snapenhance/mapping/impl/OperaPageViewControllerMapper.kt @@ -5,7 +5,7 @@ import me.rhunk.snapenhance.util.ReflectionHelper import java.lang.reflect.Field import java.lang.reflect.Method import java.lang.reflect.Modifier -import java.util.* +import java.util.Arrays class OperaPageViewControllerMapper : Mapper() { diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/util/EncryptionHelper.kt b/app/src/main/kotlin/me/rhunk/snapenhance/util/EncryptionHelper.kt @@ -4,7 +4,7 @@ import me.rhunk.snapenhance.Constants import me.rhunk.snapenhance.data.ContentType import me.rhunk.snapenhance.util.protobuf.ProtoReader import java.io.InputStream -import java.util.* +import java.util.Base64 import javax.crypto.Cipher import javax.crypto.CipherInputStream import javax.crypto.spec.IvParameterSpec diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/util/ReflectionHelper.kt b/app/src/main/kotlin/me/rhunk/snapenhance/util/ReflectionHelper.kt @@ -2,7 +2,8 @@ package me.rhunk.snapenhance.util import java.lang.reflect.Field import java.lang.reflect.Method -import java.util.* +import java.util.Arrays +import java.util.Objects object ReflectionHelper { /** diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/util/download/DownloadServer.kt b/app/src/main/kotlin/me/rhunk/snapenhance/util/download/DownloadServer.kt @@ -9,7 +9,8 @@ import java.io.InputStreamReader import java.io.PrintWriter import java.net.ServerSocket import java.net.Socket -import java.util.* +import java.util.Locale +import java.util.StringTokenizer import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ThreadLocalRandom import java.util.function.Consumer