mirror of
https://github.com/rhunk/SnapEnhance.git
synced 2025-06-13 05:37:48 +02:00
feat(scripting): module system
This commit is contained in:
@ -118,7 +118,7 @@ class RemoteSideContext(
|
|||||||
}
|
}
|
||||||
|
|
||||||
scriptManager.runtime.eachModule {
|
scriptManager.runtime.eachModule {
|
||||||
callFunction("module.onManagerLoad", androidContext)
|
callFunction("module.onSnapEnhanceLoad", androidContext)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -268,7 +268,7 @@ class ModDatabase(
|
|||||||
version = cursor.getStringOrNull("version")!!,
|
version = cursor.getStringOrNull("version")!!,
|
||||||
description = cursor.getStringOrNull("description"),
|
description = cursor.getStringOrNull("description"),
|
||||||
author = cursor.getStringOrNull("author"),
|
author = cursor.getStringOrNull("author"),
|
||||||
grantPermissions = null
|
grantedPermissions = emptyList()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -11,8 +11,8 @@ import me.rhunk.snapenhance.common.scripting.impl.ConfigInterface
|
|||||||
import me.rhunk.snapenhance.common.scripting.impl.ConfigTransactionType
|
import me.rhunk.snapenhance.common.scripting.impl.ConfigTransactionType
|
||||||
import me.rhunk.snapenhance.common.scripting.type.ModuleInfo
|
import me.rhunk.snapenhance.common.scripting.type.ModuleInfo
|
||||||
import me.rhunk.snapenhance.scripting.impl.IPCListeners
|
import me.rhunk.snapenhance.scripting.impl.IPCListeners
|
||||||
import me.rhunk.snapenhance.scripting.impl.RemoteManagerIPC
|
import me.rhunk.snapenhance.scripting.impl.ManagerIPC
|
||||||
import me.rhunk.snapenhance.scripting.impl.RemoteScriptConfig
|
import me.rhunk.snapenhance.scripting.impl.ManagerScriptConfig
|
||||||
import me.rhunk.snapenhance.scripting.impl.ui.InterfaceManager
|
import me.rhunk.snapenhance.scripting.impl.ui.InterfaceManager
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
@ -21,7 +21,9 @@ import kotlin.system.exitProcess
|
|||||||
class RemoteScriptManager(
|
class RemoteScriptManager(
|
||||||
val context: RemoteSideContext,
|
val context: RemoteSideContext,
|
||||||
) : IScripting.Stub() {
|
) : IScripting.Stub() {
|
||||||
val runtime = ScriptRuntime(context.androidContext, context.log)
|
val runtime = ScriptRuntime(context.androidContext, context.log).apply {
|
||||||
|
scripting = this@RemoteScriptManager
|
||||||
|
}
|
||||||
|
|
||||||
private var autoReloadListener: AutoReloadListener? = null
|
private var autoReloadListener: AutoReloadListener? = null
|
||||||
private val autoReloadHandler by lazy {
|
private val autoReloadHandler by lazy {
|
||||||
@ -61,11 +63,11 @@ class RemoteScriptManager(
|
|||||||
|
|
||||||
fun init() {
|
fun init() {
|
||||||
runtime.buildModuleObject = { module ->
|
runtime.buildModuleObject = { module ->
|
||||||
module.extras["ipc"] = RemoteManagerIPC(module.moduleInfo, context.log, ipcListeners)
|
module.registerBindings(
|
||||||
module.extras["im"] = InterfaceManager(module.moduleInfo, context.log)
|
ManagerIPC(ipcListeners),
|
||||||
module.extras["config"] = RemoteScriptConfig(this@RemoteScriptManager, module.moduleInfo, context.log).also {
|
InterfaceManager(),
|
||||||
it.load()
|
ManagerScriptConfig(this@RemoteScriptManager)
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
sync()
|
sync()
|
||||||
@ -74,12 +76,20 @@ class RemoteScriptManager(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadScript(name: String) {
|
fun getModulePath(name: String): String? {
|
||||||
val content = getScriptContent(name) ?: return
|
return cachedModuleInfo.entries.find { it.value.name == name }?.key
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadScript(path: String) {
|
||||||
|
val content = getScriptContent(path) ?: return
|
||||||
if (context.config.root.scripting.autoReload.getNullable() != null) {
|
if (context.config.root.scripting.autoReload.getNullable() != null) {
|
||||||
autoReloadHandler.addFile(getScriptsFolder()?.findFile(name) ?: return)
|
autoReloadHandler.addFile(getScriptsFolder()?.findFile(path) ?: return)
|
||||||
}
|
}
|
||||||
runtime.load(name, content)
|
runtime.load(path, content)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun unloadScript(scriptPath: String) {
|
||||||
|
runtime.unload(scriptPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <R> getScriptInputStream(name: String, callback: (InputStream?) -> R): R {
|
private fun <R> getScriptInputStream(name: String, callback: (InputStream?) -> R): R {
|
||||||
@ -140,7 +150,7 @@ class RemoteScriptManager(
|
|||||||
value: String?,
|
value: String?,
|
||||||
save: Boolean
|
save: Boolean
|
||||||
): String? {
|
): String? {
|
||||||
val scriptConfig = runtime.getModuleByName(module ?: return null)?.extras?.get("config") as? ConfigInterface ?: return null.also {
|
val scriptConfig = runtime.getModuleByName(module ?: return null)?.getBinding(ConfigInterface::class) ?: return null.also {
|
||||||
context.log.warn("Failed to get config interface for $module")
|
context.log.warn("Failed to get config interface for $module")
|
||||||
}
|
}
|
||||||
val transactionType = ConfigTransactionType.fromKey(action)
|
val transactionType = ConfigTransactionType.fromKey(action)
|
||||||
@ -154,7 +164,7 @@ class RemoteScriptManager(
|
|||||||
ConfigTransactionType.SET -> set(key ?: return@runCatching null, value, save)
|
ConfigTransactionType.SET -> set(key ?: return@runCatching null, value, save)
|
||||||
ConfigTransactionType.SAVE -> save()
|
ConfigTransactionType.SAVE -> save()
|
||||||
ConfigTransactionType.LOAD -> load()
|
ConfigTransactionType.LOAD -> load()
|
||||||
ConfigTransactionType.DELETE -> delete()
|
ConfigTransactionType.DELETE -> deleteConfig()
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
null
|
null
|
||||||
|
@ -2,17 +2,13 @@ package me.rhunk.snapenhance.scripting.impl
|
|||||||
|
|
||||||
import android.os.DeadObjectException
|
import android.os.DeadObjectException
|
||||||
import me.rhunk.snapenhance.bridge.scripting.IPCListener
|
import me.rhunk.snapenhance.bridge.scripting.IPCListener
|
||||||
import me.rhunk.snapenhance.common.logger.AbstractLogger
|
|
||||||
import me.rhunk.snapenhance.common.scripting.impl.IPCInterface
|
import me.rhunk.snapenhance.common.scripting.impl.IPCInterface
|
||||||
import me.rhunk.snapenhance.common.scripting.impl.Listener
|
import me.rhunk.snapenhance.common.scripting.impl.Listener
|
||||||
import me.rhunk.snapenhance.common.scripting.type.ModuleInfo
|
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
typealias IPCListeners = ConcurrentHashMap<String, MutableMap<String, MutableSet<IPCListener>>> // channel, eventName -> listeners
|
typealias IPCListeners = ConcurrentHashMap<String, MutableMap<String, MutableSet<IPCListener>>> // channel, eventName -> listeners
|
||||||
|
|
||||||
class RemoteManagerIPC(
|
class ManagerIPC(
|
||||||
private val moduleInfo: ModuleInfo,
|
|
||||||
private val logger: AbstractLogger,
|
|
||||||
private val ipcListeners: IPCListeners = ConcurrentHashMap(),
|
private val ipcListeners: IPCListeners = ConcurrentHashMap(),
|
||||||
) : IPCInterface() {
|
) : IPCInterface() {
|
||||||
companion object {
|
companion object {
|
||||||
@ -20,22 +16,22 @@ class RemoteManagerIPC(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun on(eventName: String, listener: Listener) {
|
override fun on(eventName: String, listener: Listener) {
|
||||||
onBroadcast(moduleInfo.name, eventName, listener)
|
onBroadcast(context.moduleInfo.name, eventName, listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun emit(eventName: String, vararg args: String?) {
|
override fun emit(eventName: String, vararg args: String?) {
|
||||||
emit(moduleInfo.name, eventName, *args)
|
emit(context.moduleInfo.name, eventName, *args)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBroadcast(channel: String, eventName: String, listener: Listener) {
|
override fun onBroadcast(channel: String, eventName: String, listener: Listener) {
|
||||||
ipcListeners.getOrPut(channel) { mutableMapOf() }.getOrPut(eventName) { mutableSetOf() }.add(object: IPCListener.Stub() {
|
ipcListeners.getOrPut(channel) { mutableMapOf() }.getOrPut(eventName) { mutableSetOf() }.add(object: IPCListener.Stub() {
|
||||||
override fun onMessage(args: Array<out String?>) {
|
override fun onMessage(args: Array<out String?>) {
|
||||||
try {
|
try {
|
||||||
listener(args)
|
listener(args.toList())
|
||||||
} catch (doe: DeadObjectException) {
|
} catch (doe: DeadObjectException) {
|
||||||
ipcListeners[channel]?.get(eventName)?.remove(this)
|
ipcListeners[channel]?.get(eventName)?.remove(this)
|
||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
logger.error("Failed to receive message for channel: $channel, event: $eventName", t, TAG)
|
context.runtime.logger.error("Failed to receive message for channel: $channel, event: $eventName", t, TAG)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -48,7 +44,7 @@ class RemoteManagerIPC(
|
|||||||
} catch (doe: DeadObjectException) {
|
} catch (doe: DeadObjectException) {
|
||||||
ipcListeners[channel]?.get(eventName)?.remove(it)
|
ipcListeners[channel]?.get(eventName)?.remove(it)
|
||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
logger.error("Failed to send message for channel: $channel, event: $eventName", t, TAG)
|
context.runtime.logger.error("Failed to send message for channel: $channel, event: $eventName", t, TAG)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,18 +1,14 @@
|
|||||||
package me.rhunk.snapenhance.scripting.impl
|
package me.rhunk.snapenhance.scripting.impl
|
||||||
|
|
||||||
import com.google.gson.JsonObject
|
import com.google.gson.JsonObject
|
||||||
import me.rhunk.snapenhance.common.logger.AbstractLogger
|
|
||||||
import me.rhunk.snapenhance.common.scripting.impl.ConfigInterface
|
import me.rhunk.snapenhance.common.scripting.impl.ConfigInterface
|
||||||
import me.rhunk.snapenhance.common.scripting.type.ModuleInfo
|
|
||||||
import me.rhunk.snapenhance.scripting.RemoteScriptManager
|
import me.rhunk.snapenhance.scripting.RemoteScriptManager
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
class RemoteScriptConfig(
|
class ManagerScriptConfig(
|
||||||
private val remoteScriptManager: RemoteScriptManager,
|
private val remoteScriptManager: RemoteScriptManager
|
||||||
moduleInfo: ModuleInfo,
|
|
||||||
private val logger: AbstractLogger,
|
|
||||||
) : ConfigInterface() {
|
) : ConfigInterface() {
|
||||||
private val configFile = File(remoteScriptManager.getModuleDataFolder(moduleInfo.name), "config.json")
|
private val configFile by lazy { File(remoteScriptManager.getModuleDataFolder(context.moduleInfo.name), "config.json") }
|
||||||
private var config = JsonObject()
|
private var config = JsonObject()
|
||||||
|
|
||||||
override fun get(key: String, defaultValue: Any?): String? {
|
override fun get(key: String, defaultValue: Any?): String? {
|
||||||
@ -46,12 +42,16 @@ class RemoteScriptConfig(
|
|||||||
}
|
}
|
||||||
config = remoteScriptManager.context.gson.fromJson(configFile.readText(), JsonObject::class.java)
|
config = remoteScriptManager.context.gson.fromJson(configFile.readText(), JsonObject::class.java)
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
logger.error("Failed to load config file", it)
|
context.runtime.logger.error("Failed to load config file", it)
|
||||||
save()
|
save()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun delete() {
|
override fun deleteConfig() {
|
||||||
configFile.delete()
|
configFile.delete()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onInit() {
|
||||||
|
load()
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,13 +1,13 @@
|
|||||||
package me.rhunk.snapenhance.scripting.impl.ui
|
package me.rhunk.snapenhance.scripting.impl.ui
|
||||||
|
|
||||||
import me.rhunk.snapenhance.common.logger.AbstractLogger
|
import me.rhunk.snapenhance.common.scripting.bindings.AbstractBinding
|
||||||
import me.rhunk.snapenhance.common.scripting.type.ModuleInfo
|
import me.rhunk.snapenhance.common.scripting.bindings.BindingSide
|
||||||
|
import me.rhunk.snapenhance.common.scripting.ktx.contextScope
|
||||||
import me.rhunk.snapenhance.scripting.impl.ui.components.Node
|
import me.rhunk.snapenhance.scripting.impl.ui.components.Node
|
||||||
import me.rhunk.snapenhance.scripting.impl.ui.components.NodeType
|
import me.rhunk.snapenhance.scripting.impl.ui.components.NodeType
|
||||||
import me.rhunk.snapenhance.scripting.impl.ui.components.impl.ActionNode
|
import me.rhunk.snapenhance.scripting.impl.ui.components.impl.ActionNode
|
||||||
import me.rhunk.snapenhance.scripting.impl.ui.components.impl.ActionType
|
import me.rhunk.snapenhance.scripting.impl.ui.components.impl.ActionType
|
||||||
import me.rhunk.snapenhance.scripting.impl.ui.components.impl.RowColumnNode
|
import me.rhunk.snapenhance.scripting.impl.ui.components.impl.RowColumnNode
|
||||||
import org.mozilla.javascript.Context
|
|
||||||
import org.mozilla.javascript.Function
|
import org.mozilla.javascript.Function
|
||||||
import org.mozilla.javascript.annotations.JSFunction
|
import org.mozilla.javascript.annotations.JSFunction
|
||||||
|
|
||||||
@ -73,27 +73,31 @@ class InterfaceBuilder {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
class InterfaceManager(
|
class InterfaceManager : AbstractBinding("interface-manager", BindingSide.MANAGER) {
|
||||||
private val moduleInfo: ModuleInfo,
|
|
||||||
private val logger: AbstractLogger
|
|
||||||
) {
|
|
||||||
private val interfaces = mutableMapOf<String, () -> InterfaceBuilder?>()
|
private val interfaces = mutableMapOf<String, () -> InterfaceBuilder?>()
|
||||||
|
|
||||||
fun buildInterface(name: String): InterfaceBuilder? {
|
fun buildInterface(name: String): InterfaceBuilder? {
|
||||||
return interfaces[name]?.invoke()
|
return interfaces[name]?.invoke()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDispose() {
|
||||||
|
interfaces.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
@JSFunction fun create(name: String, callback: Function) {
|
@JSFunction fun create(name: String, callback: Function) {
|
||||||
interfaces[name] = {
|
interfaces[name] = {
|
||||||
val interfaceBuilder = InterfaceBuilder()
|
val interfaceBuilder = InterfaceBuilder()
|
||||||
runCatching {
|
runCatching {
|
||||||
Context.enter()
|
contextScope {
|
||||||
callback.call(Context.getCurrentContext(), callback, callback, arrayOf(interfaceBuilder))
|
callback.call(this, callback, callback, arrayOf(interfaceBuilder))
|
||||||
Context.exit()
|
}
|
||||||
interfaceBuilder
|
interfaceBuilder
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
logger.error("Failed to create interface $name for ${moduleInfo.name}", it)
|
context.runtime.logger.error("Failed to create interface $name for ${context.moduleInfo.name}", it)
|
||||||
}.getOrNull()
|
}.getOrNull()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getObject() = this
|
||||||
}
|
}
|
@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.*
|
|||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.FolderOpen
|
import androidx.compose.material.icons.filled.FolderOpen
|
||||||
|
import androidx.compose.material.icons.filled.LibraryBooks
|
||||||
import androidx.compose.material.icons.filled.Link
|
import androidx.compose.material.icons.filled.Link
|
||||||
import androidx.compose.material.icons.filled.Settings
|
import androidx.compose.material.icons.filled.Settings
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
@ -14,6 +15,7 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.core.net.toUri
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@ -70,12 +72,27 @@ class ScriptsSection : Section() {
|
|||||||
}
|
}
|
||||||
Switch(
|
Switch(
|
||||||
checked = enabled,
|
checked = enabled,
|
||||||
onCheckedChange = {
|
onCheckedChange = { isChecked ->
|
||||||
context.modDatabase.setScriptEnabled(script.name, it)
|
context.modDatabase.setScriptEnabled(script.name, isChecked)
|
||||||
if (it) {
|
enabled = isChecked
|
||||||
context.scriptManager.loadScript(script.name)
|
runCatching {
|
||||||
|
val modulePath = context.scriptManager.getModulePath(script.name)!!
|
||||||
|
context.scriptManager.unloadScript(modulePath)
|
||||||
|
if (isChecked) {
|
||||||
|
context.scriptManager.loadScript(modulePath)
|
||||||
|
context.scriptManager.runtime.getModuleByName(script.name)
|
||||||
|
?.callFunction("module.onSnapEnhanceLoad")
|
||||||
|
context.shortToast("Loaded script ${script.name}")
|
||||||
|
} else {
|
||||||
|
context.shortToast("Unloaded script ${script.name}")
|
||||||
|
}
|
||||||
|
}.onFailure { throwable ->
|
||||||
|
enabled = !isChecked
|
||||||
|
("Failed to ${if (isChecked) "enable" else "disable"} script").let {
|
||||||
|
context.log.error(it, throwable)
|
||||||
|
context.shortToast(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
enabled = it
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -130,7 +147,7 @@ class ScriptsSection : Section() {
|
|||||||
val settingsInterface = remember {
|
val settingsInterface = remember {
|
||||||
val module = context.scriptManager.runtime.getModuleByName(script.name) ?: return@remember null
|
val module = context.scriptManager.runtime.getModuleByName(script.name) ?: return@remember null
|
||||||
runCatching {
|
runCatching {
|
||||||
(module.extras["im"] as? InterfaceManager)?.buildInterface("settings")
|
(module.getBinding(InterfaceManager::class))?.buildInterface("settings")
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
settingsError = it
|
settingsError = it
|
||||||
}.getOrNull()
|
}.getOrNull()
|
||||||
@ -228,4 +245,18 @@ class ScriptsSection : Section() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun TopBarActions(rowScope: RowScope) {
|
||||||
|
rowScope.apply {
|
||||||
|
IconButton(onClick = {
|
||||||
|
context.androidContext.startActivity(Intent(Intent.ACTION_VIEW).apply {
|
||||||
|
data = "https://github.com/SnapEnhance/docs".toUri()
|
||||||
|
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
|
})
|
||||||
|
}) {
|
||||||
|
Icon(imageVector = Icons.Default.LibraryBooks, contentDescription = "Documentation")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -5,6 +5,8 @@ enum class LogChannel(
|
|||||||
val shortName: String
|
val shortName: String
|
||||||
) {
|
) {
|
||||||
CORE("SnapEnhanceCore", "core"),
|
CORE("SnapEnhanceCore", "core"),
|
||||||
|
COMMON("SnapEnhanceCommon", "common"),
|
||||||
|
SCRIPTING("Scripting", "scripting"),
|
||||||
NATIVE("SnapEnhanceNative", "native"),
|
NATIVE("SnapEnhanceNative", "native"),
|
||||||
MANAGER("SnapEnhanceManager", "manager"),
|
MANAGER("SnapEnhanceManager", "manager"),
|
||||||
XPOSED("LSPosed-Bridge", "xposed");
|
XPOSED("LSPosed-Bridge", "xposed");
|
||||||
|
@ -2,6 +2,8 @@ package me.rhunk.snapenhance.common.scripting
|
|||||||
|
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import me.rhunk.snapenhance.common.scripting.bindings.AbstractBinding
|
||||||
|
import me.rhunk.snapenhance.common.scripting.bindings.BindingsContext
|
||||||
import me.rhunk.snapenhance.common.scripting.ktx.contextScope
|
import me.rhunk.snapenhance.common.scripting.ktx.contextScope
|
||||||
import me.rhunk.snapenhance.common.scripting.ktx.putFunction
|
import me.rhunk.snapenhance.common.scripting.ktx.putFunction
|
||||||
import me.rhunk.snapenhance.common.scripting.ktx.scriptableObject
|
import me.rhunk.snapenhance.common.scripting.ktx.scriptableObject
|
||||||
@ -12,15 +14,23 @@ import org.mozilla.javascript.ScriptableObject
|
|||||||
import org.mozilla.javascript.Undefined
|
import org.mozilla.javascript.Undefined
|
||||||
import org.mozilla.javascript.Wrapper
|
import org.mozilla.javascript.Wrapper
|
||||||
import java.lang.reflect.Modifier
|
import java.lang.reflect.Modifier
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
class JSModule(
|
class JSModule(
|
||||||
val scriptRuntime: ScriptRuntime,
|
val scriptRuntime: ScriptRuntime,
|
||||||
val moduleInfo: ModuleInfo,
|
val moduleInfo: ModuleInfo,
|
||||||
val content: String,
|
val content: String,
|
||||||
) {
|
) {
|
||||||
val extras = mutableMapOf<String, Any>()
|
private val moduleBindings = mutableMapOf<String, AbstractBinding>()
|
||||||
private lateinit var moduleObject: ScriptableObject
|
private lateinit var moduleObject: ScriptableObject
|
||||||
|
|
||||||
|
private val moduleBindingContext by lazy {
|
||||||
|
BindingsContext(
|
||||||
|
moduleInfo = moduleInfo,
|
||||||
|
runtime = scriptRuntime
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fun load(block: ScriptableObject.() -> Unit) {
|
fun load(block: ScriptableObject.() -> Unit) {
|
||||||
contextScope {
|
contextScope {
|
||||||
val classLoader = scriptRuntime.androidContext.classLoader
|
val classLoader = scriptRuntime.androidContext.classLoader
|
||||||
@ -33,7 +43,7 @@ class JSModule(
|
|||||||
putConst("author", this, moduleInfo.author)
|
putConst("author", this, moduleInfo.author)
|
||||||
putConst("minSnapchatVersion", this, moduleInfo.minSnapchatVersion)
|
putConst("minSnapchatVersion", this, moduleInfo.minSnapchatVersion)
|
||||||
putConst("minSEVersion", this, moduleInfo.minSEVersion)
|
putConst("minSEVersion", this, moduleInfo.minSEVersion)
|
||||||
putConst("grantPermissions", this, moduleInfo.grantPermissions)
|
putConst("grantedPermissions", this, moduleInfo.grantedPermissions)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -62,12 +72,16 @@ class JSModule(
|
|||||||
|
|
||||||
moduleObject.putFunction("findClass") {
|
moduleObject.putFunction("findClass") {
|
||||||
val className = it?.get(0).toString()
|
val className = it?.get(0).toString()
|
||||||
classLoader.loadClass(className)
|
runCatching {
|
||||||
|
classLoader.loadClass(className)
|
||||||
|
}.onFailure { throwable ->
|
||||||
|
scriptRuntime.logger.error("Failed to load class $className", throwable)
|
||||||
|
}.getOrNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
moduleObject.putFunction("type") { args ->
|
moduleObject.putFunction("type") { args ->
|
||||||
val className = args?.get(0).toString()
|
val className = args?.get(0).toString()
|
||||||
val clazz = classLoader.loadClass(className)
|
val clazz = runCatching { classLoader.loadClass(className) }.getOrNull() ?: return@putFunction Undefined.instance
|
||||||
|
|
||||||
scriptableObject("JavaClassWrapper") {
|
scriptableObject("JavaClassWrapper") {
|
||||||
putFunction("newInstance") newInstance@{ args ->
|
putFunction("newInstance") newInstance@{ args ->
|
||||||
@ -95,12 +109,12 @@ class JSModule(
|
|||||||
}
|
}
|
||||||
|
|
||||||
moduleObject.putFunction("logInfo") { args ->
|
moduleObject.putFunction("logInfo") { args ->
|
||||||
scriptRuntime.logger.info(args?.joinToString(" ") {
|
scriptRuntime.logger.info(argsToString(args))
|
||||||
when (it) {
|
Undefined.instance
|
||||||
is Wrapper -> it.unwrap().toString()
|
}
|
||||||
else -> it.toString()
|
|
||||||
}
|
moduleObject.putFunction("logError") { args ->
|
||||||
} ?: "null")
|
scriptRuntime.logger.error(argsToString(arrayOf(args?.get(0))), args?.get(1) as? Throwable ?: Throwable())
|
||||||
Undefined.instance
|
Undefined.instance
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,16 +130,38 @@ class JSModule(
|
|||||||
Undefined.instance
|
Undefined.instance
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
block(moduleObject)
|
block(moduleObject)
|
||||||
extras.forEach { (key, value) ->
|
|
||||||
moduleObject.putConst(key, moduleObject, value)
|
moduleBindings.forEach { (_, instance) ->
|
||||||
|
instance.context = moduleBindingContext
|
||||||
|
|
||||||
|
runCatching {
|
||||||
|
instance.onInit()
|
||||||
|
}.onFailure {
|
||||||
|
scriptRuntime.logger.error("Failed to init binding ${instance.name}", it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
moduleObject.putFunction("require") { args ->
|
||||||
|
val bindingName = args?.get(0).toString()
|
||||||
|
moduleBindings[bindingName]?.getObject()
|
||||||
|
}
|
||||||
|
|
||||||
evaluateString(moduleObject, content, moduleInfo.name, 1, null)
|
evaluateString(moduleObject, content, moduleInfo.name, 1, null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun unload() {
|
fun unload() {
|
||||||
callFunction("module.onUnload")
|
callFunction("module.onUnload")
|
||||||
|
moduleBindings.entries.removeIf { (name, binding) ->
|
||||||
|
runCatching {
|
||||||
|
binding.onDispose()
|
||||||
|
}.onFailure {
|
||||||
|
scriptRuntime.logger.error("Failed to dispose binding $name", it)
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun callFunction(name: String, vararg args: Any?) {
|
fun callFunction(name: String, vararg args: Any?) {
|
||||||
@ -143,4 +179,25 @@ class JSModule(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fun registerBindings(vararg bindings: AbstractBinding) {
|
||||||
|
bindings.forEach {
|
||||||
|
moduleBindings[it.name] = it.apply {
|
||||||
|
context = moduleBindingContext
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
fun <T : Any> getBinding(clazz: KClass<T>): T? {
|
||||||
|
return moduleBindings.values.find { clazz.isInstance(it) } as? T
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun argsToString(args: Array<out Any?>?): String {
|
||||||
|
return args?.joinToString(" ") {
|
||||||
|
when (it) {
|
||||||
|
is Wrapper -> it.unwrap().toString()
|
||||||
|
else -> it.toString()
|
||||||
|
}
|
||||||
|
} ?: "null"
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package me.rhunk.snapenhance.common.scripting
|
package me.rhunk.snapenhance.common.scripting
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import me.rhunk.snapenhance.bridge.scripting.IScripting
|
||||||
import me.rhunk.snapenhance.common.logger.AbstractLogger
|
import me.rhunk.snapenhance.common.logger.AbstractLogger
|
||||||
import me.rhunk.snapenhance.common.scripting.type.ModuleInfo
|
import me.rhunk.snapenhance.common.scripting.type.ModuleInfo
|
||||||
import org.mozilla.javascript.ScriptableObject
|
import org.mozilla.javascript.ScriptableObject
|
||||||
@ -10,9 +11,13 @@ import java.io.InputStream
|
|||||||
|
|
||||||
open class ScriptRuntime(
|
open class ScriptRuntime(
|
||||||
val androidContext: Context,
|
val androidContext: Context,
|
||||||
val logger: AbstractLogger,
|
logger: AbstractLogger,
|
||||||
) {
|
) {
|
||||||
|
val logger = ScriptingLogger(logger)
|
||||||
|
|
||||||
|
lateinit var scripting: IScripting
|
||||||
var buildModuleObject: ScriptableObject.(JSModule) -> Unit = {}
|
var buildModuleObject: ScriptableObject.(JSModule) -> Unit = {}
|
||||||
|
|
||||||
private val modules = mutableMapOf<String, JSModule>()
|
private val modules = mutableMapOf<String, JSModule>()
|
||||||
|
|
||||||
fun eachModule(f: JSModule.() -> Unit) {
|
fun eachModule(f: JSModule.() -> Unit) {
|
||||||
@ -55,7 +60,7 @@ open class ScriptRuntime(
|
|||||||
author = properties["author"],
|
author = properties["author"],
|
||||||
minSnapchatVersion = properties["minSnapchatVersion"]?.toLong(),
|
minSnapchatVersion = properties["minSnapchatVersion"]?.toLong(),
|
||||||
minSEVersion = properties["minSEVersion"]?.toLong(),
|
minSEVersion = properties["minSEVersion"]?.toLong(),
|
||||||
grantPermissions = properties["permissions"]?.split(",")?.map { it.trim() },
|
grantedPermissions = properties["permissions"]?.split(",")?.map { it.trim() } ?: emptyList(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,19 +68,15 @@ open class ScriptRuntime(
|
|||||||
return readModuleInfo(inputStream.bufferedReader())
|
return readModuleInfo(inputStream.bufferedReader())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun reload(path: String, content: String) {
|
fun unload(scriptPath: String) {
|
||||||
unload(path)
|
val module = modules[scriptPath] ?: return
|
||||||
load(path, content)
|
logger.info("Unloading module $scriptPath")
|
||||||
}
|
|
||||||
|
|
||||||
private fun unload(path: String) {
|
|
||||||
val module = modules[path] ?: return
|
|
||||||
module.unload()
|
module.unload()
|
||||||
modules.remove(path)
|
modules.remove(scriptPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun load(path: String, content: String): JSModule? {
|
fun load(scriptPath: String, content: String): JSModule? {
|
||||||
logger.info("Loading module $path")
|
logger.info("Loading module $scriptPath")
|
||||||
return runCatching {
|
return runCatching {
|
||||||
JSModule(
|
JSModule(
|
||||||
scriptRuntime = this,
|
scriptRuntime = this,
|
||||||
@ -85,10 +86,10 @@ open class ScriptRuntime(
|
|||||||
load {
|
load {
|
||||||
buildModuleObject(this, this@apply)
|
buildModuleObject(this, this@apply)
|
||||||
}
|
}
|
||||||
modules[path] = this
|
modules[scriptPath] = this
|
||||||
}
|
}
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
logger.error("Failed to load module $path", it)
|
logger.error("Failed to load module $scriptPath", it)
|
||||||
}.getOrNull()
|
}.getOrNull()
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
package me.rhunk.snapenhance.common.scripting
|
||||||
|
|
||||||
|
import me.rhunk.snapenhance.common.logger.AbstractLogger
|
||||||
|
import me.rhunk.snapenhance.common.logger.LogChannel
|
||||||
|
|
||||||
|
class ScriptingLogger(
|
||||||
|
private val logger: AbstractLogger
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
private val TAG = LogChannel.SCRIPTING.channel
|
||||||
|
}
|
||||||
|
|
||||||
|
fun debug(message: Any?, tag: String = TAG) {
|
||||||
|
logger.debug(message, tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun error(message: Any?, tag: String = TAG) {
|
||||||
|
logger.error(message, tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun error(message: Any?, throwable: Throwable, tag: String = TAG) {
|
||||||
|
logger.error(message, throwable, tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun info(message: Any?, tag: String = TAG) {
|
||||||
|
logger.info(message, tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun verbose(message: Any?, tag: String = TAG) {
|
||||||
|
logger.verbose(message, tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun warn(message: Any?, tag: String = TAG) {
|
||||||
|
logger.warn(message, tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun assert(message: Any?, tag: String = TAG) {
|
||||||
|
logger.assert(message, tag)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
package me.rhunk.snapenhance.common.scripting.bindings
|
||||||
|
|
||||||
|
abstract class AbstractBinding(
|
||||||
|
val name: String,
|
||||||
|
val side: BindingSide
|
||||||
|
) {
|
||||||
|
lateinit var context: BindingsContext
|
||||||
|
|
||||||
|
open fun onInit() {}
|
||||||
|
|
||||||
|
open fun onDispose() {}
|
||||||
|
|
||||||
|
abstract fun getObject(): Any
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
package me.rhunk.snapenhance.common.scripting.bindings
|
||||||
|
|
||||||
|
enum class BindingSide(
|
||||||
|
val key: String
|
||||||
|
) {
|
||||||
|
COMMON("common"),
|
||||||
|
CORE("core"),
|
||||||
|
MANAGER("manager");
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromKey(key: String): BindingSide {
|
||||||
|
return entries.firstOrNull { it.key == key } ?: COMMON
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
package me.rhunk.snapenhance.common.scripting.bindings
|
||||||
|
|
||||||
|
import me.rhunk.snapenhance.common.scripting.ScriptRuntime
|
||||||
|
import me.rhunk.snapenhance.common.scripting.type.ModuleInfo
|
||||||
|
|
||||||
|
class BindingsContext(
|
||||||
|
val moduleInfo: ModuleInfo,
|
||||||
|
val runtime: ScriptRuntime
|
||||||
|
)
|
@ -1,5 +1,7 @@
|
|||||||
package me.rhunk.snapenhance.common.scripting.impl
|
package me.rhunk.snapenhance.common.scripting.impl
|
||||||
|
|
||||||
|
import me.rhunk.snapenhance.common.scripting.bindings.AbstractBinding
|
||||||
|
import me.rhunk.snapenhance.common.scripting.bindings.BindingSide
|
||||||
import org.mozilla.javascript.annotations.JSFunction
|
import org.mozilla.javascript.annotations.JSFunction
|
||||||
|
|
||||||
|
|
||||||
@ -18,7 +20,8 @@ enum class ConfigTransactionType(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
abstract class ConfigInterface {
|
@Suppress("unused")
|
||||||
|
abstract class ConfigInterface : AbstractBinding("config", BindingSide.COMMON) {
|
||||||
@JSFunction fun get(key: String): String? = get(key, null)
|
@JSFunction fun get(key: String): String? = get(key, null)
|
||||||
@JSFunction abstract fun get(key: String, defaultValue: Any?): String?
|
@JSFunction abstract fun get(key: String, defaultValue: Any?): String?
|
||||||
|
|
||||||
@ -70,5 +73,7 @@ abstract class ConfigInterface {
|
|||||||
|
|
||||||
@JSFunction abstract fun save()
|
@JSFunction abstract fun save()
|
||||||
@JSFunction abstract fun load()
|
@JSFunction abstract fun load()
|
||||||
@JSFunction abstract fun delete()
|
@JSFunction abstract fun deleteConfig()
|
||||||
|
|
||||||
|
override fun getObject() = this
|
||||||
}
|
}
|
@ -1,8 +1,11 @@
|
|||||||
package me.rhunk.snapenhance.common.scripting.impl
|
package me.rhunk.snapenhance.common.scripting.impl
|
||||||
|
|
||||||
typealias Listener = (Array<out String?>) -> Unit
|
import me.rhunk.snapenhance.common.scripting.bindings.AbstractBinding
|
||||||
|
import me.rhunk.snapenhance.common.scripting.bindings.BindingSide
|
||||||
|
|
||||||
abstract class IPCInterface {
|
typealias Listener = (List<String?>) -> Unit
|
||||||
|
|
||||||
|
abstract class IPCInterface : AbstractBinding("ipc", BindingSide.COMMON) {
|
||||||
abstract fun on(eventName: String, listener: Listener)
|
abstract fun on(eventName: String, listener: Listener)
|
||||||
|
|
||||||
abstract fun onBroadcast(channel: String, eventName: String, listener: Listener)
|
abstract fun onBroadcast(channel: String, eventName: String, listener: Listener)
|
||||||
@ -13,5 +16,8 @@ abstract class IPCInterface {
|
|||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
fun emit(eventName: String) = emit(eventName, *emptyArray())
|
fun emit(eventName: String) = emit(eventName, *emptyArray())
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
fun emit(channel: String, eventName: String) = broadcast(channel, eventName)
|
fun broadcast(channel: String, eventName: String) =
|
||||||
|
broadcast(channel, eventName, *emptyArray())
|
||||||
|
|
||||||
|
override fun getObject() = this
|
||||||
}
|
}
|
@ -7,5 +7,5 @@ data class ModuleInfo(
|
|||||||
val author: String? = null,
|
val author: String? = null,
|
||||||
val minSnapchatVersion: Long? = null,
|
val minSnapchatVersion: Long? = null,
|
||||||
val minSEVersion: Long? = null,
|
val minSEVersion: Long? = null,
|
||||||
val grantPermissions: List<String>? = null,
|
val grantedPermissions: List<String>,
|
||||||
)
|
)
|
@ -17,8 +17,8 @@ import me.rhunk.snapenhance.common.data.MessagingGroupInfo
|
|||||||
import me.rhunk.snapenhance.core.bridge.BridgeClient
|
import me.rhunk.snapenhance.core.bridge.BridgeClient
|
||||||
import me.rhunk.snapenhance.core.bridge.loadFromBridge
|
import me.rhunk.snapenhance.core.bridge.loadFromBridge
|
||||||
import me.rhunk.snapenhance.core.data.SnapClassCache
|
import me.rhunk.snapenhance.core.data.SnapClassCache
|
||||||
import me.rhunk.snapenhance.core.event.events.impl.SnapWidgetBroadcastReceiveEvent
|
|
||||||
import me.rhunk.snapenhance.core.event.events.impl.NativeUnaryCallEvent
|
import me.rhunk.snapenhance.core.event.events.impl.NativeUnaryCallEvent
|
||||||
|
import me.rhunk.snapenhance.core.event.events.impl.SnapWidgetBroadcastReceiveEvent
|
||||||
import me.rhunk.snapenhance.core.util.LSPatchUpdater
|
import me.rhunk.snapenhance.core.util.LSPatchUpdater
|
||||||
import me.rhunk.snapenhance.core.util.hook.HookStage
|
import me.rhunk.snapenhance.core.util.hook.HookStage
|
||||||
import me.rhunk.snapenhance.core.util.hook.hook
|
import me.rhunk.snapenhance.core.util.hook.hook
|
||||||
@ -142,7 +142,7 @@ class SnapEnhance {
|
|||||||
bridgeClient.registerMessagingBridge(messagingBridge)
|
bridgeClient.registerMessagingBridge(messagingBridge)
|
||||||
features.init()
|
features.init()
|
||||||
scriptRuntime.connect(bridgeClient.getScriptingInterface())
|
scriptRuntime.connect(bridgeClient.getScriptingInterface())
|
||||||
scriptRuntime.eachModule { callFunction("module.onBeforeApplicationLoad", androidContext) }
|
scriptRuntime.eachModule { callFunction("module.onSnapApplicationLoad", androidContext) }
|
||||||
syncRemote()
|
syncRemote()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -151,7 +151,7 @@ class SnapEnhance {
|
|||||||
measureTimeMillis {
|
measureTimeMillis {
|
||||||
with(appContext) {
|
with(appContext) {
|
||||||
features.onActivityCreate()
|
features.onActivityCreate()
|
||||||
scriptRuntime.eachModule { callFunction("module.onSnapActivity", mainActivity!!) }
|
scriptRuntime.eachModule { callFunction("module.onSnapMainActivityCreate", mainActivity!!) }
|
||||||
}
|
}
|
||||||
}.also { time ->
|
}.also { time ->
|
||||||
appContext.log.verbose("onActivityCreate took $time")
|
appContext.log.verbose("onActivityCreate took $time")
|
||||||
|
@ -7,22 +7,21 @@ import me.rhunk.snapenhance.common.scripting.ScriptRuntime
|
|||||||
import me.rhunk.snapenhance.core.ModContext
|
import me.rhunk.snapenhance.core.ModContext
|
||||||
import me.rhunk.snapenhance.core.scripting.impl.CoreIPC
|
import me.rhunk.snapenhance.core.scripting.impl.CoreIPC
|
||||||
import me.rhunk.snapenhance.core.scripting.impl.CoreScriptConfig
|
import me.rhunk.snapenhance.core.scripting.impl.CoreScriptConfig
|
||||||
import me.rhunk.snapenhance.core.scripting.impl.ScriptHooker
|
import me.rhunk.snapenhance.core.scripting.impl.CoreScriptHooker
|
||||||
|
|
||||||
class CoreScriptRuntime(
|
class CoreScriptRuntime(
|
||||||
private val modContext: ModContext,
|
private val modContext: ModContext,
|
||||||
logger: AbstractLogger,
|
logger: AbstractLogger,
|
||||||
): ScriptRuntime(modContext.androidContext, logger) {
|
): ScriptRuntime(modContext.androidContext, logger) {
|
||||||
private val scriptHookers = mutableListOf<ScriptHooker>()
|
|
||||||
|
|
||||||
fun connect(scriptingInterface: IScripting) {
|
fun connect(scriptingInterface: IScripting) {
|
||||||
|
scripting = scriptingInterface
|
||||||
scriptingInterface.apply {
|
scriptingInterface.apply {
|
||||||
buildModuleObject = { module ->
|
buildModuleObject = { module ->
|
||||||
module.extras["ipc"] = CoreIPC(this@apply, module.moduleInfo)
|
module.registerBindings(
|
||||||
module.extras["hooker"] = ScriptHooker(module.moduleInfo, logger, androidContext.classLoader).also {
|
CoreScriptConfig(),
|
||||||
scriptHookers.add(it)
|
CoreIPC(),
|
||||||
}
|
CoreScriptHooker(),
|
||||||
module.extras["config"] = CoreScriptConfig(this@apply, module.moduleInfo)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
enabledScripts.forEach { path ->
|
enabledScripts.forEach { path ->
|
||||||
|
@ -1,32 +1,27 @@
|
|||||||
package me.rhunk.snapenhance.core.scripting.impl
|
package me.rhunk.snapenhance.core.scripting.impl
|
||||||
|
|
||||||
import me.rhunk.snapenhance.bridge.scripting.IPCListener
|
import me.rhunk.snapenhance.bridge.scripting.IPCListener
|
||||||
import me.rhunk.snapenhance.bridge.scripting.IScripting
|
|
||||||
import me.rhunk.snapenhance.common.scripting.impl.IPCInterface
|
import me.rhunk.snapenhance.common.scripting.impl.IPCInterface
|
||||||
import me.rhunk.snapenhance.common.scripting.impl.Listener
|
import me.rhunk.snapenhance.common.scripting.impl.Listener
|
||||||
import me.rhunk.snapenhance.common.scripting.type.ModuleInfo
|
|
||||||
|
|
||||||
class CoreIPC(
|
class CoreIPC : IPCInterface() {
|
||||||
private val scripting: IScripting,
|
|
||||||
private val moduleInfo: ModuleInfo
|
|
||||||
) : IPCInterface() {
|
|
||||||
override fun onBroadcast(channel: String, eventName: String, listener: Listener) {
|
override fun onBroadcast(channel: String, eventName: String, listener: Listener) {
|
||||||
scripting.registerIPCListener(channel, eventName, object: IPCListener.Stub() {
|
context.runtime.scripting.registerIPCListener(channel, eventName, object: IPCListener.Stub() {
|
||||||
override fun onMessage(args: Array<out String?>) {
|
override fun onMessage(args: Array<out String?>) {
|
||||||
listener(args)
|
listener(args.toList())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun on(eventName: String, listener: Listener) {
|
override fun on(eventName: String, listener: Listener) {
|
||||||
onBroadcast(moduleInfo.name, eventName, listener)
|
onBroadcast(context.moduleInfo.name, eventName, listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun emit(eventName: String, vararg args: String?) {
|
override fun emit(eventName: String, vararg args: String?) {
|
||||||
broadcast(moduleInfo.name, eventName, *args)
|
broadcast(context.moduleInfo.name, eventName, *args)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun broadcast(channel: String, eventName: String, vararg args: String?) {
|
override fun broadcast(channel: String, eventName: String, vararg args: String?) {
|
||||||
scripting.sendIPCMessage(channel, eventName, args)
|
context.runtime.scripting.sendIPCMessage(channel, eventName, args)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,31 +1,26 @@
|
|||||||
package me.rhunk.snapenhance.core.scripting.impl
|
package me.rhunk.snapenhance.core.scripting.impl
|
||||||
|
|
||||||
import me.rhunk.snapenhance.bridge.scripting.IScripting
|
|
||||||
import me.rhunk.snapenhance.common.scripting.impl.ConfigInterface
|
import me.rhunk.snapenhance.common.scripting.impl.ConfigInterface
|
||||||
import me.rhunk.snapenhance.common.scripting.impl.ConfigTransactionType
|
import me.rhunk.snapenhance.common.scripting.impl.ConfigTransactionType
|
||||||
import me.rhunk.snapenhance.common.scripting.type.ModuleInfo
|
|
||||||
|
|
||||||
class CoreScriptConfig(
|
class CoreScriptConfig: ConfigInterface() {
|
||||||
private val scripting: IScripting,
|
|
||||||
private val moduleInfo: ModuleInfo
|
|
||||||
): ConfigInterface() {
|
|
||||||
override fun get(key: String, defaultValue: Any?): String? {
|
override fun get(key: String, defaultValue: Any?): String? {
|
||||||
return scripting.configTransaction(moduleInfo.name, ConfigTransactionType.GET.key, key, defaultValue.toString(), false)
|
return context.runtime.scripting.configTransaction(context.moduleInfo.name, ConfigTransactionType.GET.key, key, defaultValue.toString(), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun set(key: String, value: Any?, save: Boolean) {
|
override fun set(key: String, value: Any?, save: Boolean) {
|
||||||
scripting.configTransaction(moduleInfo.name, ConfigTransactionType.SET.key, key, value.toString(), save)
|
context.runtime.scripting.configTransaction(context.moduleInfo.name, ConfigTransactionType.SET.key, key, value.toString(), save)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun save() {
|
override fun save() {
|
||||||
scripting.configTransaction(moduleInfo.name, ConfigTransactionType.SAVE.key, null, null, false)
|
context.runtime.scripting.configTransaction(context.moduleInfo.name, ConfigTransactionType.SAVE.key, null, null, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun load() {
|
override fun load() {
|
||||||
scripting.configTransaction(moduleInfo.name, ConfigTransactionType.LOAD.key, null, null, false)
|
context.runtime.scripting.configTransaction(context.moduleInfo.name, ConfigTransactionType.LOAD.key, null, null, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun delete() {
|
override fun deleteConfig() {
|
||||||
scripting.configTransaction(moduleInfo.name, ConfigTransactionType.DELETE.key, null, null, false)
|
context.runtime.scripting.configTransaction(context.moduleInfo.name, ConfigTransactionType.DELETE.key, null, null, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,8 +1,9 @@
|
|||||||
package me.rhunk.snapenhance.core.scripting.impl
|
package me.rhunk.snapenhance.core.scripting.impl
|
||||||
|
|
||||||
import me.rhunk.snapenhance.common.logger.AbstractLogger
|
import me.rhunk.snapenhance.common.scripting.bindings.AbstractBinding
|
||||||
|
import me.rhunk.snapenhance.common.scripting.bindings.BindingSide
|
||||||
|
import me.rhunk.snapenhance.common.scripting.ktx.scriptableObject
|
||||||
import me.rhunk.snapenhance.common.scripting.toPrimitiveValue
|
import me.rhunk.snapenhance.common.scripting.toPrimitiveValue
|
||||||
import me.rhunk.snapenhance.common.scripting.type.ModuleInfo
|
|
||||||
import me.rhunk.snapenhance.core.util.hook.HookAdapter
|
import me.rhunk.snapenhance.core.util.hook.HookAdapter
|
||||||
import me.rhunk.snapenhance.core.util.hook.HookStage
|
import me.rhunk.snapenhance.core.util.hook.HookStage
|
||||||
import me.rhunk.snapenhance.core.util.hook.Hooker
|
import me.rhunk.snapenhance.core.util.hook.Hooker
|
||||||
@ -71,21 +72,20 @@ class ScriptHookCallback(
|
|||||||
typealias HookCallback = (ScriptHookCallback) -> Unit
|
typealias HookCallback = (ScriptHookCallback) -> Unit
|
||||||
typealias HookUnhook = () -> Unit
|
typealias HookUnhook = () -> Unit
|
||||||
|
|
||||||
@Suppress("unused", "MemberVisibilityCanBePrivate")
|
@Suppress("unused")
|
||||||
class ScriptHooker(
|
class CoreScriptHooker: AbstractBinding("hooker", BindingSide.CORE) {
|
||||||
private val moduleInfo: ModuleInfo,
|
|
||||||
private val logger: AbstractLogger,
|
|
||||||
private val classLoader: ClassLoader
|
|
||||||
) {
|
|
||||||
private val hooks = mutableListOf<HookUnhook>()
|
private val hooks = mutableListOf<HookUnhook>()
|
||||||
|
|
||||||
// -- search for class members
|
val stage = scriptableObject {
|
||||||
|
putConst("BEFORE", this, "before")
|
||||||
|
putConst("AFTER", this, "after")
|
||||||
|
}
|
||||||
|
|
||||||
private fun findClassSafe(className: String): Class<*>? {
|
private fun findClassSafe(className: String): Class<*>? {
|
||||||
return runCatching {
|
return runCatching {
|
||||||
classLoader.loadClass(className)
|
context.runtime.androidContext.classLoader.loadClass(className)
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
logger.warn("Failed to load class $className")
|
context.runtime.logger.warn("Failed to load class $className")
|
||||||
}.getOrNull()
|
}.getOrNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,4 +158,11 @@ class ScriptHooker(
|
|||||||
|
|
||||||
fun hookAllConstructors(className: String, stage: String, callback: HookCallback)
|
fun hookAllConstructors(className: String, stage: String, callback: HookCallback)
|
||||||
= findClassSafe(className)?.let { hookAllConstructors(it, stage, callback) }
|
= findClassSafe(className)?.let { hookAllConstructors(it, stage, callback) }
|
||||||
|
|
||||||
|
override fun onDispose() {
|
||||||
|
hooks.forEach { it() }
|
||||||
|
hooks.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getObject() = this
|
||||||
}
|
}
|
Reference in New Issue
Block a user