mirror of
https://github.com/rhunk/SnapEnhance.git
synced 2025-06-12 13:17:42 +02:00
refactor: mapper
This commit is contained in:
@ -1,8 +1,103 @@
|
||||
package me.rhunk.snapenhance.mapper
|
||||
|
||||
abstract class AbstractClassMapper {
|
||||
import android.util.Log
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonObject
|
||||
|
||||
abstract class AbstractClassMapper(
|
||||
val mapperName: String
|
||||
) {
|
||||
lateinit var classLoader: ClassLoader
|
||||
|
||||
private val gson = Gson()
|
||||
private val values = mutableMapOf<String, Any?>()
|
||||
private val mappers = mutableListOf<MapperContext.() -> Unit>()
|
||||
|
||||
private fun findClassSafe(className: String?) = runCatching {
|
||||
classLoader.loadClass(className)
|
||||
}.onFailure {
|
||||
Log.e("Mapper", it.stackTraceToString())
|
||||
}.getOrNull()
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
inner class PropertyDelegate<T>(
|
||||
private val key: String,
|
||||
defaultValue: Any? = null,
|
||||
private val setter: (Any?) -> Unit = { values[key] = it },
|
||||
private val getter: (Any?) -> T? = { it as? T }
|
||||
) {
|
||||
init {
|
||||
values[key] = defaultValue
|
||||
}
|
||||
|
||||
operator fun getValue(thisRef: Any?, property: Any?): T? {
|
||||
return getter(values[key])
|
||||
}
|
||||
|
||||
operator fun setValue(thisRef: Any?, property: Any?, value: Any?) {
|
||||
setter(value)
|
||||
}
|
||||
|
||||
fun set(value: String?) {
|
||||
values[key] = value
|
||||
}
|
||||
|
||||
fun get(): T? {
|
||||
return getter(values[key])
|
||||
}
|
||||
|
||||
fun getAsClass(): Class<*>? {
|
||||
return getter(values[key]) as? Class<*>
|
||||
}
|
||||
|
||||
fun getAsString(): String? {
|
||||
return getter(values[key])?.toString()
|
||||
}
|
||||
|
||||
fun getClass(key: String): Class<*>? {
|
||||
return (get() as? Map<String, String?>)?.let {
|
||||
findClassSafe(it[key].toString())
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString() = getter(values[key]).toString()
|
||||
}
|
||||
|
||||
fun string(key: String): PropertyDelegate<String> = PropertyDelegate(key, null)
|
||||
|
||||
fun classReference(key: String): PropertyDelegate<Class<*>> = PropertyDelegate(key, getter = { findClassSafe(it as? String) })
|
||||
|
||||
fun map(key: String, value: MutableMap<String, String?> = mutableMapOf()): PropertyDelegate<MutableMap<String, String?>> = PropertyDelegate(key, value)
|
||||
|
||||
fun readFromJson(json: JsonObject) {
|
||||
values.forEach { (key, _) ->
|
||||
runCatching {
|
||||
val jsonElement = json.get(key) ?: return@forEach
|
||||
when (jsonElement) {
|
||||
is JsonObject -> values[key] = gson.fromJson(jsonElement, HashMap::class.java)
|
||||
else -> values[key] = jsonElement.asString
|
||||
}
|
||||
}.onFailure {
|
||||
Log.e("Mapper","Failed to deserialize property $key")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun writeFromJson(json: JsonObject) {
|
||||
values.forEach { (key, value) ->
|
||||
runCatching {
|
||||
when (value) {
|
||||
is String -> json.addProperty(key, value)
|
||||
is Class<*> -> json.addProperty(key, value.name)
|
||||
is Map<*, *> -> json.add(key, gson.toJsonTree(value))
|
||||
else -> json.addProperty(key, value.toString())
|
||||
}
|
||||
}.onFailure {
|
||||
Log.e("Mapper","Failed to serialize property $key")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun mapper(task: MapperContext.() -> Unit) {
|
||||
mappers.add(task)
|
||||
}
|
||||
|
@ -3,8 +3,8 @@ package me.rhunk.snapenhance.mapper
|
||||
import com.google.gson.JsonObject
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import me.rhunk.snapenhance.mapper.impl.*
|
||||
import org.jf.dexlib2.Opcodes
|
||||
import org.jf.dexlib2.dexbacked.DexBackedDexFile
|
||||
import org.jf.dexlib2.iface.ClassDef
|
||||
@ -12,12 +12,33 @@ import java.io.BufferedInputStream
|
||||
import java.io.InputStream
|
||||
import java.util.zip.ZipFile
|
||||
import java.util.zip.ZipInputStream
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
class Mapper(
|
||||
private vararg val mappers: KClass<out AbstractClassMapper> = arrayOf()
|
||||
class ClassMapper(
|
||||
private vararg val mappers: AbstractClassMapper = DEFAULT_MAPPERS,
|
||||
) {
|
||||
private val classes = mutableListOf<ClassDef>()
|
||||
|
||||
companion object {
|
||||
val DEFAULT_MAPPERS get() = arrayOf(
|
||||
BCryptClassMapper(),
|
||||
CallbackMapper(),
|
||||
DefaultMediaItemMapper(),
|
||||
MediaQualityLevelProviderMapper(),
|
||||
OperaPageViewControllerMapper(),
|
||||
PlusSubscriptionMapper(),
|
||||
ScCameraSettingsMapper(),
|
||||
StoryBoostStateMapper(),
|
||||
FriendsFeedEventDispatcherMapper(),
|
||||
CompositeConfigurationProviderMapper(),
|
||||
ScoreUpdateMapper(),
|
||||
FriendRelationshipChangerMapper(),
|
||||
ViewBinderMapper(),
|
||||
FriendingDataSourcesMapper(),
|
||||
OperaViewerParamsMapper(),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
fun loadApk(path: String) {
|
||||
val apkFile = ZipFile(path)
|
||||
val apkEntries = apkFile.entries().toList()
|
||||
@ -50,20 +71,23 @@ class Mapper(
|
||||
}
|
||||
}
|
||||
|
||||
fun start(): JsonObject {
|
||||
val mappers = mappers.map { it.java.constructors.first().newInstance() as AbstractClassMapper }
|
||||
suspend fun run(): JsonObject {
|
||||
val context = MapperContext(classes.associateBy { it.type })
|
||||
|
||||
runBlocking {
|
||||
withContext(Dispatchers.IO) {
|
||||
mappers.forEach { mapper ->
|
||||
launch {
|
||||
mapper.run(context)
|
||||
}
|
||||
withContext(Dispatchers.IO) {
|
||||
mappers.forEach { mapper ->
|
||||
launch {
|
||||
mapper.run(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return context.exportToJson()
|
||||
val outputJson = JsonObject()
|
||||
mappers.forEach { mapper ->
|
||||
outputJson.add(mapper.mapperName, JsonObject().apply {
|
||||
mapper.writeFromJson(this)
|
||||
})
|
||||
}
|
||||
return outputJson
|
||||
}
|
||||
}
|
@ -1,7 +1,5 @@
|
||||
package me.rhunk.snapenhance.mapper
|
||||
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.JsonObject
|
||||
import org.jf.dexlib2.iface.ClassDef
|
||||
|
||||
class MapperContext(
|
||||
@ -19,40 +17,4 @@ class MapperContext(
|
||||
if (name == null) return null
|
||||
return classMap[name.toString()]
|
||||
}
|
||||
|
||||
private val mappings = mutableMapOf<String, Any?>()
|
||||
|
||||
fun addMapping(key: String, vararg array: Pair<String, Any?>) {
|
||||
mappings[key] = array.toMap()
|
||||
}
|
||||
|
||||
fun addMapping(key: String, value: String) {
|
||||
mappings[key] = value
|
||||
}
|
||||
|
||||
fun getStringMapping(key: String): String? {
|
||||
return mappings[key] as? String
|
||||
}
|
||||
|
||||
fun getMapMapping(key: String): Map<*, *>? {
|
||||
return mappings[key] as? Map<*, *>
|
||||
}
|
||||
|
||||
fun exportToJson(): JsonObject {
|
||||
val gson = GsonBuilder().setPrettyPrinting().create()
|
||||
val json = JsonObject()
|
||||
for ((key, value) in mappings) {
|
||||
when (value) {
|
||||
is String -> json.addProperty(key, value)
|
||||
is Map<*, *> -> {
|
||||
val obj = JsonObject()
|
||||
for ((k, v) in value) {
|
||||
obj.add(k.toString(), gson.toJsonTree(v))
|
||||
}
|
||||
json.add(key, obj)
|
||||
}
|
||||
}
|
||||
}
|
||||
return json
|
||||
}
|
||||
}
|
@ -6,7 +6,10 @@ import me.rhunk.snapenhance.mapper.ext.getStaticConstructor
|
||||
import me.rhunk.snapenhance.mapper.ext.isFinal
|
||||
import org.jf.dexlib2.iface.instruction.formats.ArrayPayload
|
||||
|
||||
class BCryptClassMapper : AbstractClassMapper() {
|
||||
class BCryptClassMapper : AbstractClassMapper("BCryptClass") {
|
||||
val classReference = classReference("class")
|
||||
val hashMethod = string("hashMethod")
|
||||
|
||||
init {
|
||||
mapper {
|
||||
for (clazz in classes) {
|
||||
@ -17,17 +20,15 @@ class BCryptClassMapper : AbstractClassMapper() {
|
||||
}
|
||||
|
||||
if (isBcryptClass == true) {
|
||||
val hashMethod = clazz.methods.first {
|
||||
val hashDexMethod = clazz.methods.first {
|
||||
it.parameterTypes.size == 2 &&
|
||||
it.parameterTypes[0] == "Ljava/lang/String;" &&
|
||||
it.parameterTypes[1] == "Ljava/lang/String;" &&
|
||||
it.returnType == "Ljava/lang/String;"
|
||||
}
|
||||
|
||||
addMapping("BCrypt",
|
||||
"class" to clazz.getClassName(),
|
||||
"hashMethod" to hashMethod.name
|
||||
)
|
||||
hashMethod.set(hashDexMethod.name)
|
||||
classReference.set(clazz.getClassName())
|
||||
return@mapper
|
||||
}
|
||||
}
|
||||
|
@ -4,11 +4,12 @@ import me.rhunk.snapenhance.mapper.AbstractClassMapper
|
||||
import me.rhunk.snapenhance.mapper.ext.getClassName
|
||||
import me.rhunk.snapenhance.mapper.ext.getSuperClassName
|
||||
import me.rhunk.snapenhance.mapper.ext.isFinal
|
||||
import org.jf.dexlib2.Opcode
|
||||
import org.jf.dexlib2.iface.instruction.formats.Instruction21t
|
||||
import org.jf.dexlib2.iface.instruction.formats.Instruction22t
|
||||
|
||||
class CallbackMapper : AbstractClassMapper() {
|
||||
class CallbackMapper : AbstractClassMapper("Callbacks") {
|
||||
val callbacks = map("callbacks")
|
||||
|
||||
init {
|
||||
mapper {
|
||||
val callbackClasses = classes.filter { clazz ->
|
||||
@ -32,7 +33,7 @@ class CallbackMapper : AbstractClassMapper() {
|
||||
it.getSuperClassName()!!.substringAfterLast("/") to it.getClassName()
|
||||
}
|
||||
|
||||
addMapping("callbacks", *callbackClasses.toTypedArray())
|
||||
callbacks.get()?.putAll(callbackClasses)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,14 +1,32 @@
|
||||
package me.rhunk.snapenhance.mapper.impl
|
||||
|
||||
import me.rhunk.snapenhance.mapper.AbstractClassMapper
|
||||
import me.rhunk.snapenhance.mapper.ext.*
|
||||
import me.rhunk.snapenhance.mapper.ext.findConstString
|
||||
import me.rhunk.snapenhance.mapper.ext.getClassName
|
||||
import me.rhunk.snapenhance.mapper.ext.hasStaticConstructorString
|
||||
import me.rhunk.snapenhance.mapper.ext.isEnum
|
||||
import org.jf.dexlib2.iface.instruction.formats.Instruction21c
|
||||
import org.jf.dexlib2.iface.instruction.formats.Instruction35c
|
||||
import org.jf.dexlib2.iface.reference.FieldReference
|
||||
import org.jf.dexlib2.iface.reference.MethodReference
|
||||
import java.lang.reflect.Modifier
|
||||
|
||||
class CompositeConfigurationProviderMapper : AbstractClassMapper() {
|
||||
class CompositeConfigurationProviderMapper : AbstractClassMapper("CompositeConfigurationProvider") {
|
||||
val classReference = classReference("class")
|
||||
val observeProperty = string("observeProperty")
|
||||
val getProperty = string("getProperty")
|
||||
val configEnumMapping = mapOf(
|
||||
"class" to classReference("enumClass"),
|
||||
"getValue" to string("enumGetValue"),
|
||||
"getCategory" to string("enumGetCategory"),
|
||||
"defaultValueField" to string("enumDefaultValueField")
|
||||
)
|
||||
val appExperimentProvider = mapOf(
|
||||
"class" to classReference("appExperimentProviderClass"),
|
||||
"getBooleanAppExperimentClass" to classReference("getBooleanAppExperimentClass"),
|
||||
"hasExperimentMethod" to string("hasExperimentMethod")
|
||||
)
|
||||
|
||||
init {
|
||||
mapper {
|
||||
for (classDef in classes) {
|
||||
@ -68,24 +86,21 @@ class CompositeConfigurationProviderMapper : AbstractClassMapper() {
|
||||
it.type == "Ljava/lang/Object;"
|
||||
}
|
||||
|
||||
addMapping("CompositeConfigurationProvider",
|
||||
"class" to classDef.getClassName(),
|
||||
"observeProperty" to observePropertyMethod.name,
|
||||
"getProperty" to getPropertyMethod.name,
|
||||
"enum" to mapOf(
|
||||
"class" to configEnumInterface.getClassName(),
|
||||
"getValue" to enumGetDefaultValueMethod.name,
|
||||
"getCategory" to enumGetCategoryMethod.name,
|
||||
"defaultValueField" to defaultValueField.name
|
||||
),
|
||||
"appExperimentProvider" to (hasExperimentMethodReference?.let {
|
||||
mapOf(
|
||||
"class" to getClass(it.definingClass)?.getClassName(),
|
||||
"GetBooleanAppExperimentClass" to getBooleanAppExperimentClass,
|
||||
"hasExperimentMethod" to hasExperimentMethodReference.name
|
||||
)
|
||||
})
|
||||
)
|
||||
classReference.set(classDef.getClassName())
|
||||
observeProperty.set(observePropertyMethod.name)
|
||||
getProperty.set(getPropertyMethod.name)
|
||||
|
||||
configEnumMapping["class"]?.set(configEnumInterface.getClassName())
|
||||
configEnumMapping["getValue"]?.set(enumGetDefaultValueMethod.name)
|
||||
configEnumMapping["getCategory"]?.set(enumGetCategoryMethod.name)
|
||||
configEnumMapping["defaultValueField"]?.set(defaultValueField.name)
|
||||
|
||||
hasExperimentMethodReference?.let {
|
||||
appExperimentProvider["class"]?.set(getClass(it.definingClass)?.getClassName())
|
||||
appExperimentProvider["getBooleanAppExperimentClass"]?.set(getBooleanAppExperimentClass)
|
||||
appExperimentProvider["hasExperimentMethod"]?.set(hasExperimentMethodReference.name)
|
||||
}
|
||||
|
||||
return@mapper
|
||||
}
|
||||
}
|
||||
|
@ -5,16 +5,21 @@ import me.rhunk.snapenhance.mapper.ext.findConstString
|
||||
import me.rhunk.snapenhance.mapper.ext.getClassName
|
||||
import me.rhunk.snapenhance.mapper.ext.isAbstract
|
||||
|
||||
class DefaultMediaItemMapper : AbstractClassMapper() {
|
||||
class DefaultMediaItemMapper : AbstractClassMapper("DefaultMediaItem") {
|
||||
val cameraRollMediaId = classReference("cameraRollMediaIdClass")
|
||||
val durationMsField = string("durationMsField")
|
||||
val defaultMediaItem = classReference("defaultMediaItemClass")
|
||||
|
||||
init {
|
||||
mapper {
|
||||
for (clazz in classes) {
|
||||
if (clazz.methods.find { it.name == "toString" }?.implementation?.findConstString("CameraRollMediaId", contains = true) != true) {
|
||||
continue
|
||||
}
|
||||
val durationMsField = clazz.fields.firstOrNull { it.type == "J" } ?: continue
|
||||
val durationMsDexField = clazz.fields.firstOrNull { it.type == "J" } ?: continue
|
||||
|
||||
addMapping("CameraRollMediaId", "class" to clazz.getClassName(), "durationMsField" to durationMsField.name)
|
||||
cameraRollMediaId.set(clazz.getClassName())
|
||||
durationMsField.set(durationMsDexField.name)
|
||||
return@mapper
|
||||
}
|
||||
}
|
||||
@ -29,7 +34,7 @@ class DefaultMediaItemMapper : AbstractClassMapper() {
|
||||
val constructorParameters = clazz.directMethods.firstOrNull { it.name == "<init>" }?.parameterTypes ?: continue
|
||||
if (constructorParameters.size < 6 || constructorParameters[5] != "J") continue
|
||||
|
||||
addMapping("DefaultMediaItem", clazz.getClassName())
|
||||
defaultMediaItem.set(clazz.getClassName())
|
||||
return@mapper
|
||||
}
|
||||
}
|
||||
|
@ -5,12 +5,16 @@ import me.rhunk.snapenhance.mapper.ext.findConstString
|
||||
import me.rhunk.snapenhance.mapper.ext.getClassName
|
||||
import me.rhunk.snapenhance.mapper.ext.isEnum
|
||||
|
||||
class FriendRelationshipChangerMapper : AbstractClassMapper() {
|
||||
class FriendRelationshipChangerMapper : AbstractClassMapper("FriendRelationshipChanger") {
|
||||
val classReference = classReference("class")
|
||||
val addFriendMethod = string("addFriendMethod")
|
||||
val removeFriendMethod = string("removeFriendMethod")
|
||||
|
||||
init {
|
||||
mapper {
|
||||
for (classDef in classes) {
|
||||
classDef.methods.firstOrNull { it.name == "<init>" }?.implementation?.findConstString("FriendRelationshipChangerImpl")?.takeIf { it } ?: continue
|
||||
val addFriendMethod = classDef.methods.first {
|
||||
val addFriendDexMethod = classDef.methods.first {
|
||||
it.parameterTypes.size > 4 &&
|
||||
getClass(it.parameterTypes[1])?.isEnum() == true &&
|
||||
getClass(it.parameterTypes[2])?.isEnum() == true &&
|
||||
@ -18,7 +22,7 @@ class FriendRelationshipChangerMapper : AbstractClassMapper() {
|
||||
it.parameters[4].type == "Ljava/lang/String;"
|
||||
}
|
||||
|
||||
val removeFriendMethod = classDef.methods.first {
|
||||
val removeFriendDexMethod = classDef.methods.firstOrNull {
|
||||
it.parameterTypes.size == 5 &&
|
||||
it.parameterTypes[0] == "Ljava/lang/String;" &&
|
||||
getClass(it.parameterTypes[1])?.isEnum() == true &&
|
||||
@ -26,11 +30,12 @@ class FriendRelationshipChangerMapper : AbstractClassMapper() {
|
||||
it.parameterTypes[3] == "Ljava/lang/String;"
|
||||
}
|
||||
|
||||
addMapping("FriendRelationshipChanger",
|
||||
"class" to classDef.getClassName(),
|
||||
"addFriendMethod" to addFriendMethod.name,
|
||||
"removeFriendMethod" to removeFriendMethod.name
|
||||
)
|
||||
this@FriendRelationshipChangerMapper.apply {
|
||||
classReference.set(classDef.getClassName())
|
||||
addFriendMethod.set(addFriendDexMethod.name)
|
||||
removeFriendMethod.set(removeFriendDexMethod?.name)
|
||||
}
|
||||
|
||||
return@mapper
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,10 @@ import me.rhunk.snapenhance.mapper.ext.findConstString
|
||||
import me.rhunk.snapenhance.mapper.ext.getClassName
|
||||
import me.rhunk.snapenhance.mapper.ext.searchNextFieldReference
|
||||
|
||||
class FriendingDataSourcesMapper: AbstractClassMapper() {
|
||||
class FriendingDataSourcesMapper: AbstractClassMapper("FriendingDataSources") {
|
||||
val classReference = classReference("class")
|
||||
val quickAddSourceListField = string("quickAddSourceListField")
|
||||
|
||||
init {
|
||||
mapper {
|
||||
for (classDef in classes) {
|
||||
@ -15,13 +18,11 @@ class FriendingDataSourcesMapper: AbstractClassMapper() {
|
||||
val toStringMethod = classDef.methods.firstOrNull { it.name == "toString" } ?: continue
|
||||
if (toStringMethod.implementation?.findConstString("quickaddSource", contains = true) != true) continue
|
||||
|
||||
val quickAddSourceListField = toStringMethod.implementation?.searchNextFieldReference("quickaddSource", contains = true)
|
||||
val quickAddSourceListDexField = toStringMethod.implementation?.searchNextFieldReference("quickaddSource", contains = true)
|
||||
?: continue
|
||||
|
||||
addMapping("FriendingDataSources",
|
||||
"class" to classDef.getClassName(),
|
||||
"quickAddSourceListField" to quickAddSourceListField.name
|
||||
)
|
||||
classReference.set(classDef.getClassName())
|
||||
quickAddSourceListField.set(quickAddSourceListDexField.name)
|
||||
return@mapper
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,10 @@ import me.rhunk.snapenhance.mapper.ext.findConstString
|
||||
import me.rhunk.snapenhance.mapper.ext.getClassName
|
||||
|
||||
|
||||
class FriendsFeedEventDispatcherMapper : AbstractClassMapper() {
|
||||
class FriendsFeedEventDispatcherMapper : AbstractClassMapper("FriendsFeedEventDispatcher") {
|
||||
val classReference = classReference("class")
|
||||
val viewModelField = string("viewModelField")
|
||||
|
||||
init {
|
||||
mapper {
|
||||
for (clazz in classes) {
|
||||
@ -13,15 +16,13 @@ class FriendsFeedEventDispatcherMapper : AbstractClassMapper() {
|
||||
val onItemLongPress = clazz.methods.first { it.name == "onItemLongPress" }
|
||||
val viewHolderContainerClass = getClass(onItemLongPress.parameterTypes[0]) ?: continue
|
||||
|
||||
val viewModelField = viewHolderContainerClass.fields.firstOrNull { field ->
|
||||
val viewModelDexField = viewHolderContainerClass.fields.firstOrNull { field ->
|
||||
val typeClass = getClass(field.type) ?: return@firstOrNull false
|
||||
typeClass.methods.firstOrNull {it.name == "toString"}?.implementation?.findConstString("FriendFeedItemViewModel", contains = true) == true
|
||||
}?.name ?: continue
|
||||
|
||||
addMapping("FriendsFeedEventDispatcher",
|
||||
"class" to clazz.getClassName(),
|
||||
"viewModelField" to viewModelField
|
||||
)
|
||||
classReference.set(clazz.getClassName())
|
||||
viewModelField.set(viewModelDexField)
|
||||
return@mapper
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,10 @@ import me.rhunk.snapenhance.mapper.ext.isAbstract
|
||||
import me.rhunk.snapenhance.mapper.ext.isEnum
|
||||
import org.jf.dexlib2.AccessFlags
|
||||
|
||||
class MediaQualityLevelProviderMapper : AbstractClassMapper() {
|
||||
class MediaQualityLevelProviderMapper : AbstractClassMapper("MediaQualityLevelProvider") {
|
||||
val mediaQualityLevelProvider = classReference("mediaQualityLevelProvider")
|
||||
val mediaQualityLevelProviderMethod = string("mediaQualityLevelProviderMethod")
|
||||
|
||||
init {
|
||||
var enumQualityLevel : String? = null
|
||||
|
||||
@ -20,7 +23,6 @@ class MediaQualityLevelProviderMapper : AbstractClassMapper() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
addMapping("EnumQualityLevel", enumQualityLevel ?: return@mapper)
|
||||
}
|
||||
|
||||
mapper {
|
||||
@ -31,10 +33,8 @@ class MediaQualityLevelProviderMapper : AbstractClassMapper() {
|
||||
if (clazz.fields.none { it.accessFlags and AccessFlags.TRANSIENT.value != 0 }) continue
|
||||
|
||||
clazz.methods.firstOrNull { it.returnType == "L$enumQualityLevel;" }?.let {
|
||||
addMapping("MediaQualityLevelProvider",
|
||||
"class" to clazz.getClassName(),
|
||||
"method" to it.name
|
||||
)
|
||||
mediaQualityLevelProvider.set(clazz.getClassName())
|
||||
mediaQualityLevelProviderMethod.set(it.name)
|
||||
return@mapper
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,13 @@ import me.rhunk.snapenhance.mapper.ext.hasStaticConstructorString
|
||||
import me.rhunk.snapenhance.mapper.ext.isAbstract
|
||||
import me.rhunk.snapenhance.mapper.ext.isEnum
|
||||
|
||||
class OperaPageViewControllerMapper : AbstractClassMapper() {
|
||||
class OperaPageViewControllerMapper : AbstractClassMapper("OperaPageViewController") {
|
||||
val classReference = classReference("class")
|
||||
val viewStateField = string("viewStateField")
|
||||
val layerListField = string("layerListField")
|
||||
val onDisplayStateChange = string("onDisplayStateChange")
|
||||
val onDisplayStateChangeGesture = string("onDisplayStateChangeGesture")
|
||||
|
||||
init {
|
||||
mapper {
|
||||
for (clazz in classes) {
|
||||
@ -16,37 +22,35 @@ class OperaPageViewControllerMapper : AbstractClassMapper() {
|
||||
continue
|
||||
}
|
||||
|
||||
val viewStateField = clazz.fields.first { field ->
|
||||
val viewStateDexField = clazz.fields.first { field ->
|
||||
val fieldClass = getClass(field.type) ?: return@first false
|
||||
fieldClass.isEnum() && fieldClass.hasStaticConstructorString("FULLY_DISPLAYED")
|
||||
}
|
||||
|
||||
val layerListField = clazz.fields.first { it.type == "Ljava/util/ArrayList;" }
|
||||
val layerListDexField = clazz.fields.first { it.type == "Ljava/util/ArrayList;" }
|
||||
|
||||
val onDisplayStateChange = clazz.methods.firstOrNull {
|
||||
val onDisplayStateChangeDexMethod = clazz.methods.firstOrNull {
|
||||
if (it.returnType != "V" || it.parameterTypes.size != 1) return@firstOrNull false
|
||||
val firstParameterType = getClass(it.parameterTypes[0]) ?: return@firstOrNull false
|
||||
if (firstParameterType.type == clazz.type || !firstParameterType.isAbstract()) return@firstOrNull false
|
||||
//check if the class contains a field with the enumViewStateClass type
|
||||
firstParameterType.fields.any { field ->
|
||||
field.type == viewStateField.type
|
||||
field.type == viewStateDexField.type
|
||||
}
|
||||
}
|
||||
|
||||
val onDisplayStateChangeGesture = clazz.methods.first {
|
||||
val onDisplayStateChangeGestureDexMethod = clazz.methods.first {
|
||||
if (it.returnType != "V" || it.parameterTypes.size != 2) return@first false
|
||||
val firstParameterType = getClass(it.parameterTypes[0]) ?: return@first false
|
||||
val secondParameterType = getClass(it.parameterTypes[1]) ?: return@first false
|
||||
firstParameterType.isEnum() && secondParameterType.isEnum()
|
||||
}
|
||||
|
||||
addMapping("OperaPageViewController",
|
||||
"class" to clazz.getClassName(),
|
||||
"viewStateField" to viewStateField.name,
|
||||
"layerListField" to layerListField.name,
|
||||
"onDisplayStateChange" to onDisplayStateChange?.name,
|
||||
"onDisplayStateChangeGesture" to onDisplayStateChangeGesture.name
|
||||
)
|
||||
classReference.set(clazz.getClassName())
|
||||
viewStateField.set(viewStateDexField.name)
|
||||
layerListField.set(layerListDexField.name)
|
||||
onDisplayStateChange.set(onDisplayStateChangeDexMethod?.name)
|
||||
onDisplayStateChangeGesture.set(onDisplayStateChangeGestureDexMethod.name)
|
||||
|
||||
return@mapper
|
||||
}
|
||||
|
@ -6,14 +6,17 @@ import me.rhunk.snapenhance.mapper.ext.getClassName
|
||||
import org.jf.dexlib2.iface.instruction.formats.Instruction35c
|
||||
import org.jf.dexlib2.iface.reference.MethodReference
|
||||
|
||||
class OperaViewerParamsMapper : AbstractClassMapper() {
|
||||
class OperaViewerParamsMapper : AbstractClassMapper("OperaViewerParams") {
|
||||
val classReference = classReference("class")
|
||||
val putMethod = string("putMethod")
|
||||
|
||||
init {
|
||||
mapper {
|
||||
for (classDef in classes) {
|
||||
classDef.fields.firstOrNull { it.type == "Ljava/util/concurrent/ConcurrentHashMap;" } ?: continue
|
||||
if (classDef.methods.firstOrNull { it.name == "toString" }?.implementation?.findConstString("Params") != true) continue
|
||||
|
||||
val putMethod = classDef.methods.firstOrNull { method ->
|
||||
val putDexMethod = classDef.methods.firstOrNull { method ->
|
||||
method.implementation?.instructions?.any {
|
||||
val instruction = it as? Instruction35c ?: return@any false
|
||||
val reference = instruction.reference as? MethodReference ?: return@any false
|
||||
@ -21,10 +24,9 @@ class OperaViewerParamsMapper : AbstractClassMapper() {
|
||||
} == true
|
||||
} ?: return@mapper
|
||||
|
||||
addMapping("OperaViewerParams",
|
||||
"class" to classDef.getClassName(),
|
||||
"putMethod" to putMethod.name
|
||||
)
|
||||
classReference.set(classDef.getClassName())
|
||||
putMethod.set(putDexMethod.name)
|
||||
|
||||
return@mapper
|
||||
}
|
||||
}
|
||||
|
@ -4,24 +4,26 @@ import me.rhunk.snapenhance.mapper.AbstractClassMapper
|
||||
import me.rhunk.snapenhance.mapper.ext.findConstString
|
||||
import me.rhunk.snapenhance.mapper.ext.getClassName
|
||||
|
||||
class PlusSubscriptionMapper : AbstractClassMapper(){
|
||||
class PlusSubscriptionMapper : AbstractClassMapper("PlusSubscription"){
|
||||
val classReference = classReference("class")
|
||||
|
||||
init {
|
||||
mapper {
|
||||
for (clazz in classes) {
|
||||
if (clazz.directMethods.filter { it.name == "<init>" }.none {
|
||||
it.parameters.size == 4 &&
|
||||
it.parameterTypes[0] == "I" &&
|
||||
it.parameterTypes[1] == "I" &&
|
||||
it.parameterTypes[2] == "J" &&
|
||||
it.parameterTypes[3] == "J"
|
||||
}) continue
|
||||
it.parameterTypes.size > 3 &&
|
||||
it.parameterTypes[0] == "I" &&
|
||||
it.parameterTypes[1] == "I" &&
|
||||
it.parameterTypes[2] == "J" &&
|
||||
it.parameterTypes[3] == "J"
|
||||
}) continue
|
||||
|
||||
val isPlusSubscriptionInfoClass = clazz.virtualMethods.firstOrNull { it.name == "toString" }?.implementation?.let {
|
||||
it.findConstString("SubscriptionInfo", contains = true) && it.findConstString("expirationTimeMillis", contains = true)
|
||||
}
|
||||
|
||||
if (isPlusSubscriptionInfoClass == true) {
|
||||
addMapping("SubscriptionInfoClass", clazz.getClassName())
|
||||
classReference.set(clazz.getClassName())
|
||||
return@mapper
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,9 @@ import me.rhunk.snapenhance.mapper.ext.getClassName
|
||||
import me.rhunk.snapenhance.mapper.ext.getStaticConstructor
|
||||
import me.rhunk.snapenhance.mapper.ext.isEnum
|
||||
|
||||
class ScCameraSettingsMapper : AbstractClassMapper() {
|
||||
class ScCameraSettingsMapper : AbstractClassMapper("ScCameraSettings") {
|
||||
val classReference = classReference("class")
|
||||
|
||||
init {
|
||||
mapper {
|
||||
for (clazz in classes) {
|
||||
@ -15,7 +17,7 @@ class ScCameraSettingsMapper : AbstractClassMapper() {
|
||||
val firstParameter = getClass(firstConstructor.parameterTypes[0]) ?: continue
|
||||
if (!firstParameter.isEnum() || firstParameter.getStaticConstructor()?.implementation?.findConstString("CONTINUOUS_PICTURE") != true) continue
|
||||
|
||||
addMapping("ScCameraSettings", clazz.getClassName())
|
||||
classReference.set(clazz.getClassName())
|
||||
return@mapper
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,9 @@ import me.rhunk.snapenhance.mapper.AbstractClassMapper
|
||||
import me.rhunk.snapenhance.mapper.ext.findConstString
|
||||
import me.rhunk.snapenhance.mapper.ext.getClassName
|
||||
|
||||
class ScoreUpdateMapper : AbstractClassMapper() {
|
||||
class ScoreUpdateMapper : AbstractClassMapper("ScoreUpdate") {
|
||||
val classReference = classReference("class")
|
||||
|
||||
init {
|
||||
mapper {
|
||||
for (classDef in classes) {
|
||||
@ -18,7 +20,7 @@ class ScoreUpdateMapper : AbstractClassMapper() {
|
||||
it.name == "toString"
|
||||
}?.implementation?.findConstString("Friend.sq:selectFriendUserScoresNeedToUpdate") != true) continue
|
||||
|
||||
addMapping("ScoreUpdate", classDef.getClassName())
|
||||
classReference.set(classDef.getClassName())
|
||||
return@mapper
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,9 @@ import me.rhunk.snapenhance.mapper.AbstractClassMapper
|
||||
import me.rhunk.snapenhance.mapper.ext.findConstString
|
||||
import me.rhunk.snapenhance.mapper.ext.getClassName
|
||||
|
||||
class StoryBoostStateMapper : AbstractClassMapper() {
|
||||
class StoryBoostStateMapper : AbstractClassMapper("StoryBoostState") {
|
||||
val classReference = classReference("class")
|
||||
|
||||
init {
|
||||
mapper {
|
||||
for (clazz in classes) {
|
||||
@ -14,7 +16,7 @@ class StoryBoostStateMapper : AbstractClassMapper() {
|
||||
|
||||
if (clazz.methods.firstOrNull { it.name == "toString" }?.implementation?.findConstString("StoryBoostState", contains = true) != true) continue
|
||||
|
||||
addMapping("StoryBoostStateClass", clazz.getClassName())
|
||||
classReference.set(clazz.getClassName())
|
||||
return@mapper
|
||||
}
|
||||
}
|
||||
|
@ -6,13 +6,17 @@ import me.rhunk.snapenhance.mapper.ext.isAbstract
|
||||
import me.rhunk.snapenhance.mapper.ext.isInterface
|
||||
import java.lang.reflect.Modifier
|
||||
|
||||
class ViewBinderMapper : AbstractClassMapper() {
|
||||
class ViewBinderMapper : AbstractClassMapper("ViewBinder") {
|
||||
val classReference = classReference("class")
|
||||
val bindMethod = string("bindMethod")
|
||||
val getViewMethod = string("getViewMethod")
|
||||
|
||||
init {
|
||||
mapper {
|
||||
for (clazz in classes) {
|
||||
if (!clazz.isAbstract() || clazz.isInterface()) continue
|
||||
|
||||
val getViewMethod = clazz.methods.firstOrNull { it.returnType == "Landroid/view/View;" && it.parameterTypes.size == 0 } ?: continue
|
||||
val getViewDexMethod = clazz.methods.firstOrNull { it.returnType == "Landroid/view/View;" && it.parameterTypes.size == 0 } ?: continue
|
||||
|
||||
// update view
|
||||
clazz.methods.filter {
|
||||
@ -21,17 +25,15 @@ class ViewBinderMapper : AbstractClassMapper() {
|
||||
if (it.size != 1) return@also
|
||||
}.firstOrNull() ?: continue
|
||||
|
||||
val bindMethod = clazz.methods.filter {
|
||||
val bindDexMethod = clazz.methods.filter {
|
||||
Modifier.isAbstract(it.accessFlags) && it.parameterTypes.size == 2 && it.parameterTypes[0] == it.parameterTypes[1] && it.returnType == "V"
|
||||
}.also {
|
||||
if (it.size != 1) return@also
|
||||
}.firstOrNull() ?: continue
|
||||
|
||||
addMapping("ViewBinder",
|
||||
"class" to clazz.getClassName(),
|
||||
"bindMethod" to bindMethod.name,
|
||||
"getViewMethod" to getViewMethod.name
|
||||
)
|
||||
classReference.set(clazz.getClassName())
|
||||
bindMethod.set(bindDexMethod.name)
|
||||
getViewMethod.set(getViewDexMethod.name)
|
||||
return@mapper
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
package me.rhunk.snapenhance.mapper.tests
|
||||
|
||||
import com.google.gson.GsonBuilder
|
||||
import me.rhunk.snapenhance.mapper.Mapper
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import me.rhunk.snapenhance.mapper.ClassMapper
|
||||
import me.rhunk.snapenhance.mapper.impl.*
|
||||
import org.junit.Test
|
||||
import java.io.File
|
||||
@ -10,28 +11,14 @@ import java.io.File
|
||||
class TestMappings {
|
||||
@Test
|
||||
fun testMappings() {
|
||||
val mapper = Mapper(
|
||||
BCryptClassMapper::class,
|
||||
CallbackMapper::class,
|
||||
DefaultMediaItemMapper::class,
|
||||
MediaQualityLevelProviderMapper::class,
|
||||
OperaPageViewControllerMapper::class,
|
||||
PlusSubscriptionMapper::class,
|
||||
ScCameraSettingsMapper::class,
|
||||
StoryBoostStateMapper::class,
|
||||
FriendsFeedEventDispatcherMapper::class,
|
||||
CompositeConfigurationProviderMapper::class,
|
||||
ScoreUpdateMapper::class,
|
||||
FriendRelationshipChangerMapper::class,
|
||||
ViewBinderMapper::class,
|
||||
FriendingDataSourcesMapper::class,
|
||||
OperaViewerParamsMapper::class,
|
||||
)
|
||||
val classMapper = ClassMapper()
|
||||
|
||||
val gson = GsonBuilder().setPrettyPrinting().create()
|
||||
val apkFile = File(System.getenv("SNAPCHAT_APK")!!)
|
||||
mapper.loadApk(apkFile.absolutePath)
|
||||
val result = mapper.start()
|
||||
println("Mappings: ${gson.toJson(result)}")
|
||||
classMapper.loadApk(apkFile.absolutePath)
|
||||
runBlocking {
|
||||
val result = classMapper.run()
|
||||
println("Mappings: ${gson.toJson(result)}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user