commit d4d0362b0e85cb4cb4d9c5f0749b923e53e29d1e parent 640f413bce8ff84719907fc3c9ff72875b2d51ce Author: rhunk <101876869+rhunk@users.noreply.github.com> Date: Wed, 4 Oct 2023 21:04:26 +0200 refactor: package name Diffstat:
45 files changed, 700 insertions(+), 705 deletions(-)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml @@ -13,7 +13,6 @@ <application android:usesCleartextTraffic="true" - android:requestLegacyExternalStorage="true" android:label="@string/app_name" tools:targetApi="34" android:enableOnBackInvokedCallback="true" @@ -23,7 +22,7 @@ android:value="true" /> <meta-data android:name="xposeddescription" - android:value="Enhanced Snapchat" /> + android:value="SnapEnhance by rhunk" /> <meta-data android:name="xposedminversion" android:value="93" /> diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/MainActivity.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/MainActivity.kt @@ -47,7 +47,7 @@ class MainActivity : ComponentActivity() { } sections = EnumSection.values().toList().associateWith { - it.section.constructors.first().call() + it.section.java.constructors.first().newInstance() as Section }.onEach { (section, instance) -> with(instance) { enumSection = section diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ui/setup/screens/impl/PickLanguageScreen.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ui/setup/screens/impl/PickLanguageScreen.kt @@ -14,11 +14,7 @@ import androidx.compose.material3.Button import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/bridge/wrapper/MappingsWrapper.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/bridge/wrapper/MappingsWrapper.kt @@ -8,8 +8,8 @@ import me.rhunk.snapenhance.Constants import me.rhunk.snapenhance.core.Logger import me.rhunk.snapenhance.core.bridge.FileLoaderWrapper import me.rhunk.snapenhance.core.bridge.types.BridgeFileType -import me.rhunk.snapmapper.Mapper -import me.rhunk.snapmapper.impl.* +import me.rhunk.snapenhance.mapper.Mapper +import me.rhunk.snapenhance.mapper.impl.* import java.util.concurrent.ConcurrentHashMap import kotlin.system.measureTimeMillis diff --git a/mapper/src/main/kotlin/me/rhunk/snapenhance/mapper/AbstractClassMapper.kt b/mapper/src/main/kotlin/me/rhunk/snapenhance/mapper/AbstractClassMapper.kt @@ -0,0 +1,9 @@ +package me.rhunk.snapenhance.mapper + +import kotlin.reflect.KClass + +abstract class AbstractClassMapper( + vararg val dependsOn: KClass<out AbstractClassMapper> = arrayOf() +) { + abstract fun run(context: MapperContext) +}+ \ No newline at end of file diff --git a/mapper/src/main/kotlin/me/rhunk/snapenhance/mapper/Mapper.kt b/mapper/src/main/kotlin/me/rhunk/snapenhance/mapper/Mapper.kt @@ -0,0 +1,90 @@ +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 org.jf.dexlib2.Opcodes +import org.jf.dexlib2.dexbacked.DexBackedDexFile +import org.jf.dexlib2.iface.ClassDef +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() +) { + private val classes = mutableListOf<ClassDef>() + fun loadApk(path: String) { + val apkFile = ZipFile(path) + val apkEntries = apkFile.entries().toList() + + fun readClass(stream: InputStream) = runCatching { + classes.addAll( + DexBackedDexFile.fromInputStream(Opcodes.getDefault(), BufferedInputStream(stream)).classes + ) + }.onFailure { + throw Throwable("Failed to load dex file", it) + } + + fun filterDexClasses(name: String) = name.startsWith("classes") && name.endsWith(".dex") + + apkEntries.firstOrNull { it.name.endsWith("lspatch/origin.apk") }?.let { origin -> + val originApk = ZipInputStream(apkFile.getInputStream(origin)) + var nextEntry = originApk.nextEntry + while (nextEntry != null) { + if (filterDexClasses(nextEntry.name)) { + readClass(originApk) + } + originApk.closeEntry() + nextEntry = originApk.nextEntry + } + return + } + + apkEntries.toList().filter { filterDexClasses(it.name) }.forEach { + readClass(apkFile.getInputStream(it)) + } + } + + fun start(): JsonObject { + val mappers = mappers.map { it.java.constructors.first().newInstance() as AbstractClassMapper } + val context = MapperContext(classes.associateBy { it.type }) + + runBlocking { + withContext(Dispatchers.IO) { + val finishedJobs = mutableListOf<Class<*>>() + val dependentsMappers = mappers.filter { it.dependsOn.isNotEmpty() } + + fun onJobFinished(mapper: AbstractClassMapper) { + finishedJobs.add(mapper.javaClass) + + dependentsMappers.filter { it -> + !finishedJobs.contains(it.javaClass) && + it.dependsOn.all { + finishedJobs.contains(it.java) + } + }.forEach { + launch { + it.run(context) + onJobFinished(it) + } + } + } + + mappers.forEach { mapper -> + if (mapper.dependsOn.isNotEmpty()) return@forEach + launch { + mapper.run(context) + onJobFinished(mapper) + } + } + } + } + + return context.exportToJson() + } +}+ \ No newline at end of file diff --git a/mapper/src/main/kotlin/me/rhunk/snapenhance/mapper/MapperContext.kt b/mapper/src/main/kotlin/me/rhunk/snapenhance/mapper/MapperContext.kt @@ -0,0 +1,58 @@ +package me.rhunk.snapenhance.mapper + +import com.google.gson.GsonBuilder +import com.google.gson.JsonObject +import org.jf.dexlib2.iface.ClassDef + +class MapperContext( + private val classMap: Map<String, ClassDef> +) { + val classes: Collection<ClassDef> + get() = classMap.values + + fun getClass(name: String?): ClassDef? { + if (name == null) return null + return classMap[name] + } + + fun getClass(name: CharSequence?): ClassDef? { + 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 + } +}+ \ No newline at end of file diff --git a/mapper/src/main/kotlin/me/rhunk/snapenhance/mapper/ext/DexClassDef.kt b/mapper/src/main/kotlin/me/rhunk/snapenhance/mapper/ext/DexClassDef.kt @@ -0,0 +1,24 @@ +package me.rhunk.snapenhance.mapper.ext + +import org.jf.dexlib2.AccessFlags +import org.jf.dexlib2.iface.ClassDef + +fun ClassDef.isEnum(): Boolean = accessFlags and AccessFlags.ENUM.value != 0 +fun ClassDef.isAbstract(): Boolean = accessFlags and AccessFlags.ABSTRACT.value != 0 +fun ClassDef.isInterface(): Boolean = accessFlags and AccessFlags.INTERFACE.value != 0 +fun ClassDef.isFinal(): Boolean = accessFlags and AccessFlags.FINAL.value != 0 + +fun ClassDef.hasStaticConstructorString(string: String): Boolean = methods.any { + it.name == "<clinit>" && it.implementation?.findConstString(string) == true +} + +fun ClassDef.hasConstructorString(string: String): Boolean = methods.any { + it.name == "<init>" && it.implementation?.findConstString(string) == true +} + +fun ClassDef.getStaticConstructor() = methods.firstOrNull { + it.name == "<clinit>" +} + +fun ClassDef.getClassName() = type.replaceFirst("L", "").replaceFirst(";", "") +fun ClassDef.getSuperClassName() = superclass?.replaceFirst("L", "")?.replaceFirst(";", "") diff --git a/mapper/src/main/kotlin/me/rhunk/snapenhance/mapper/ext/DexMethod.kt b/mapper/src/main/kotlin/me/rhunk/snapenhance/mapper/ext/DexMethod.kt @@ -0,0 +1,44 @@ +package me.rhunk.snapenhance.mapper.ext + +import org.jf.dexlib2.iface.MethodImplementation +import org.jf.dexlib2.iface.instruction.formats.Instruction21c +import org.jf.dexlib2.iface.instruction.formats.Instruction22c +import org.jf.dexlib2.iface.reference.FieldReference +import org.jf.dexlib2.iface.reference.StringReference + +fun MethodImplementation.findConstString(string: String, contains: Boolean = false): Boolean = instructions.filterIsInstance(Instruction21c::class.java).any { + (it.reference as? StringReference)?.string?.let { str -> + if (contains) { + str.contains(string) + } else { + str == string + } + } == true +} + +fun MethodImplementation.getAllConstStrings(): List<String> = instructions.filterIsInstance<Instruction21c>().mapNotNull { + it.reference as? StringReference +}.map { + it.string +} + +fun MethodImplementation.searchNextFieldReference(constString: String, contains: Boolean = false): FieldReference? = this.instructions.let { + var found = false + for (instruction in it) { + if (instruction is Instruction21c && instruction.reference is StringReference) { + val str = (instruction.reference as StringReference).string + if (if (contains) str.contains(constString) else str == constString) { + found = true + } + } + + if (!found) continue + + if (instruction is Instruction22c && + instruction.reference is FieldReference + ) { + return@let (instruction.reference as FieldReference) + } + } + null +} diff --git a/mapper/src/main/kotlin/me/rhunk/snapenhance/mapper/impl/BCryptClassMapper.kt b/mapper/src/main/kotlin/me/rhunk/snapenhance/mapper/impl/BCryptClassMapper.kt @@ -0,0 +1,34 @@ +package me.rhunk.snapenhance.mapper.impl + +import me.rhunk.snapenhance.mapper.AbstractClassMapper +import me.rhunk.snapenhance.mapper.MapperContext +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() { + override fun run(context: MapperContext) { + for (clazz in context.classes) { + if (!clazz.isFinal()) continue + + val isBcryptClass = clazz.getStaticConstructor()?.let { constructor -> + constructor.implementation?.instructions?.filterIsInstance<ArrayPayload>()?.any { it.arrayElements.size == 18 && it.arrayElements[0] == 608135816 } + } + + if (isBcryptClass == true) { + val hashMethod = clazz.methods.first { + it.parameterTypes.size == 2 && + it.parameterTypes[0] == "Ljava/lang/String;" && + it.parameterTypes[1] == "Ljava/lang/String;" && + it.returnType == "Ljava/lang/String;" + } + + context.addMapping("BCrypt", + "class" to clazz.type.replace("L", "").replace(";", ""), + "hashMethod" to hashMethod.name + ) + return + } + } + } +}+ \ No newline at end of file diff --git a/mapper/src/main/kotlin/me/rhunk/snapenhance/mapper/impl/CallbackMapper.kt b/mapper/src/main/kotlin/me/rhunk/snapenhance/mapper/impl/CallbackMapper.kt @@ -0,0 +1,28 @@ +package me.rhunk.snapenhance.mapper.impl + +import me.rhunk.snapenhance.mapper.AbstractClassMapper +import me.rhunk.snapenhance.mapper.MapperContext +import me.rhunk.snapenhance.mapper.ext.getClassName +import me.rhunk.snapenhance.mapper.ext.getSuperClassName +import me.rhunk.snapenhance.mapper.ext.isFinal + +class CallbackMapper : AbstractClassMapper() { + override fun run(context: MapperContext) { + val callbackClasses = context.classes.filter { clazz -> + if (clazz.superclass == null) return@filter false + + val superclassName = clazz.getSuperClassName()!! + if ((!superclassName.endsWith("Callback") && !superclassName.endsWith("Delegate")) + || superclassName.endsWith("\$Callback")) return@filter false + + if (clazz.getClassName().endsWith("\$CppProxy")) return@filter false + + val superClass = context.getClass(clazz.superclass) ?: return@filter false + !superClass.isFinal() + }.map { + it.getSuperClassName()!!.substringAfterLast("/") to it.getClassName() + } + + context.addMapping("callbacks", *callbackClasses.toTypedArray()) + } +}+ \ No newline at end of file diff --git a/mapper/src/main/kotlin/me/rhunk/snapenhance/mapper/impl/CompositeConfigurationProviderMapper.kt b/mapper/src/main/kotlin/me/rhunk/snapenhance/mapper/impl/CompositeConfigurationProviderMapper.kt @@ -0,0 +1,55 @@ +package me.rhunk.snapenhance.mapper.impl + +import me.rhunk.snapenhance.mapper.AbstractClassMapper +import me.rhunk.snapenhance.mapper.MapperContext +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 java.lang.reflect.Modifier + +class CompositeConfigurationProviderMapper : AbstractClassMapper() { + override fun run(context: MapperContext) { + for (classDef in context.classes) { + val constructor = classDef.methods.firstOrNull { it.name == "<init>" } ?: continue + if (constructor.parameterTypes.size == 0 || constructor.parameterTypes[0] != "Ljava/util/List;") continue + if (constructor.implementation?.findConstString("CompositeConfigurationProvider") != true) continue + + val getPropertyMethod = classDef.methods.first { method -> + method.parameterTypes.size > 1 && + method.returnType == "Ljava/lang/Object;" && + context.getClass(method.parameterTypes[0])?.interfaces?.contains("Ljava/io/Serializable;") == true && + context.getClass(method.parameterTypes[1])?.let { it.isEnum() && it.hasStaticConstructorString("BOOLEAN") } == true + } + + val configEnumInterface = context.getClass(getPropertyMethod.parameterTypes[0])!! + val enumType = context.getClass(getPropertyMethod.parameterTypes[1])!! + + val observePropertyMethod = classDef.methods.first { + it.parameterTypes.size > 2 && + it.parameterTypes[0] == configEnumInterface.type && + it.parameterTypes[1] == "Ljava/lang/String;" && + it.parameterTypes[2] == enumType.type + } + + val enumGetDefaultValueMethod = configEnumInterface.methods.first { context.getClass(it.returnType)?.interfaces?.contains("Ljava/io/Serializable;") == true } + val defaultValueField = context.getClass(enumGetDefaultValueMethod.returnType)!!.fields.first { + Modifier.isFinal(it.accessFlags) && + Modifier.isPublic(it.accessFlags) && + it.type == "Ljava/lang/Object;" + } + + context.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, + "defaultValueField" to defaultValueField.name + ) + ) + return + } + } +}+ \ No newline at end of file diff --git a/mapper/src/main/kotlin/me/rhunk/snapenhance/mapper/impl/DefaultMediaItemMapper.kt b/mapper/src/main/kotlin/me/rhunk/snapenhance/mapper/impl/DefaultMediaItemMapper.kt @@ -0,0 +1,22 @@ +package me.rhunk.snapenhance.mapper.impl + +import me.rhunk.snapenhance.mapper.AbstractClassMapper +import me.rhunk.snapenhance.mapper.MapperContext +import me.rhunk.snapenhance.mapper.ext.isAbstract + +class DefaultMediaItemMapper : AbstractClassMapper() { + override fun run(context: MapperContext) { + for (clazz in context.classes) { + val superClass = context.getClass(clazz.superclass) ?: continue + + if (!superClass.isAbstract() || superClass.interfaces.isEmpty() || superClass.interfaces[0] != "Ljava/lang/Comparable;") continue + if (clazz.methods.none { it.returnType == "Landroid/net/Uri;" }) continue + + val constructorParameters = clazz.directMethods.firstOrNull { it.name == "<init>" }?.parameterTypes ?: continue + if (constructorParameters.size < 6 || constructorParameters[5] != "J") continue + + context.addMapping("DefaultMediaItem", clazz.type.replace("L", "").replace(";", "")) + return + } + } +}+ \ No newline at end of file diff --git a/mapper/src/main/kotlin/me/rhunk/snapenhance/mapper/impl/EnumMapper.kt b/mapper/src/main/kotlin/me/rhunk/snapenhance/mapper/impl/EnumMapper.kt @@ -0,0 +1,24 @@ +package me.rhunk.snapenhance.mapper.impl + +import me.rhunk.snapenhance.mapper.AbstractClassMapper +import me.rhunk.snapenhance.mapper.MapperContext +import me.rhunk.snapenhance.mapper.ext.getClassName +import me.rhunk.snapenhance.mapper.ext.hasStaticConstructorString +import me.rhunk.snapenhance.mapper.ext.isEnum + +class EnumMapper : AbstractClassMapper() { + override fun run(context: MapperContext) { + lateinit var enumQualityLevel : String + + for (enumClass in context.classes) { + if (!enumClass.isEnum()) continue + + if (enumClass.hasStaticConstructorString("LEVEL_MAX")) { + enumQualityLevel = enumClass.getClassName() + break; + } + } + + context.addMapping("EnumQualityLevel", enumQualityLevel) + } +}+ \ No newline at end of file diff --git a/mapper/src/main/kotlin/me/rhunk/snapenhance/mapper/impl/FriendRelationshipChangerMapper.kt b/mapper/src/main/kotlin/me/rhunk/snapenhance/mapper/impl/FriendRelationshipChangerMapper.kt @@ -0,0 +1,27 @@ +package me.rhunk.snapenhance.mapper.impl + +import me.rhunk.snapenhance.mapper.AbstractClassMapper +import me.rhunk.snapenhance.mapper.MapperContext +import me.rhunk.snapenhance.mapper.ext.findConstString +import me.rhunk.snapenhance.mapper.ext.getClassName +import me.rhunk.snapenhance.mapper.ext.isEnum + +class FriendRelationshipChangerMapper : AbstractClassMapper() { + override fun run(context: MapperContext) { + for (classDef in context.classes) { + classDef.methods.firstOrNull { it.name == "<init>" }?.implementation?.findConstString("FriendRelationshipChangerImpl")?.takeIf { it } ?: continue + val addFriendMethod = classDef.methods.first { + it.parameterTypes.size > 4 && + context.getClass(it.parameterTypes[1])?.isEnum() == true && + context.getClass(it.parameterTypes[2])?.isEnum() == true && + context.getClass(it.parameterTypes[3])?.isEnum() == true && + it.parameters[4].type == "Ljava/lang/String;" + } + + context.addMapping("FriendRelationshipChanger", + "class" to classDef.getClassName(), + "addFriendMethod" to addFriendMethod.name + ) + } + } +}+ \ No newline at end of file diff --git a/mapper/src/main/kotlin/me/rhunk/snapenhance/mapper/impl/FriendsFeedEventDispatcherMapper.kt b/mapper/src/main/kotlin/me/rhunk/snapenhance/mapper/impl/FriendsFeedEventDispatcherMapper.kt @@ -0,0 +1,28 @@ +package me.rhunk.snapenhance.mapper.impl + +import me.rhunk.snapenhance.mapper.AbstractClassMapper +import me.rhunk.snapenhance.mapper.MapperContext +import me.rhunk.snapenhance.mapper.ext.findConstString +import me.rhunk.snapenhance.mapper.ext.getClassName + + +class FriendsFeedEventDispatcherMapper : AbstractClassMapper() { + override fun run(context: MapperContext) { + for (clazz in context.classes) { + if (clazz.methods.count { it.name == "onClickFeed" || it.name == "onItemLongPress" } != 2) continue + val onItemLongPress = clazz.methods.first { it.name == "onItemLongPress" } + val viewHolderContainerClass = context.getClass(onItemLongPress.parameterTypes[0]) ?: continue + + val viewModelField = viewHolderContainerClass.fields.firstOrNull { field -> + val typeClass = context.getClass(field.type) ?: return@firstOrNull false + typeClass.methods.firstOrNull {it.name == "toString"}?.implementation?.findConstString("FriendFeedItemViewModel", contains = true) == true + }?.name ?: continue + + context.addMapping("FriendsFeedEventDispatcher", + "class" to clazz.getClassName(), + "viewModelField" to viewModelField + ) + return + } + } +}+ \ No newline at end of file diff --git a/mapper/src/main/kotlin/me/rhunk/snapenhance/mapper/impl/MediaQualityLevelProviderMapper.kt b/mapper/src/main/kotlin/me/rhunk/snapenhance/mapper/impl/MediaQualityLevelProviderMapper.kt @@ -0,0 +1,25 @@ +package me.rhunk.snapenhance.mapper.impl + +import me.rhunk.snapenhance.mapper.AbstractClassMapper +import me.rhunk.snapenhance.mapper.MapperContext +import me.rhunk.snapenhance.mapper.ext.isAbstract +import org.jf.dexlib2.AccessFlags + +class MediaQualityLevelProviderMapper : AbstractClassMapper(EnumMapper::class) { + override fun run(context: MapperContext) { + val mediaQualityLevelClass = context.getStringMapping("EnumQualityLevel") ?: return + + for (clazz in context.classes) { + if (!clazz.isAbstract()) continue + if (clazz.fields.none { it.accessFlags and AccessFlags.TRANSIENT.value != 0 }) continue + + clazz.methods.firstOrNull { it.returnType == "L$mediaQualityLevelClass;" }?.let { + context.addMapping("MediaQualityLevelProvider", + "class" to clazz.type.replace("L", "").replace(";", ""), + "method" to it.name + ) + return + } + } + } +}+ \ No newline at end of file diff --git a/mapper/src/main/kotlin/me/rhunk/snapenhance/mapper/impl/OperaPageViewControllerMapper.kt b/mapper/src/main/kotlin/me/rhunk/snapenhance/mapper/impl/OperaPageViewControllerMapper.kt @@ -0,0 +1,52 @@ +package me.rhunk.snapenhance.mapper.impl + +import me.rhunk.snapenhance.mapper.AbstractClassMapper +import me.rhunk.snapenhance.mapper.MapperContext +import me.rhunk.snapenhance.mapper.ext.hasConstructorString +import me.rhunk.snapenhance.mapper.ext.hasStaticConstructorString +import me.rhunk.snapenhance.mapper.ext.isAbstract +import me.rhunk.snapenhance.mapper.ext.isEnum + +class OperaPageViewControllerMapper : AbstractClassMapper() { + override fun run(context: MapperContext) { + for (clazz in context.classes) { + if (!clazz.isAbstract()) continue + if (!clazz.hasConstructorString("OperaPageViewController") || !clazz.hasStaticConstructorString("ad_product_type")) { + continue + } + + val viewStateField = clazz.fields.first { field -> + val fieldClass = context.getClass(field.type) ?: return@first false + fieldClass.isEnum() && fieldClass.hasStaticConstructorString("FULLY_DISPLAYED") + } + + val layerListField = clazz.fields.first { it.type == "Ljava/util/ArrayList;" } + + val onDisplayStateChange = clazz.methods.first { + if (it.returnType != "V" || it.parameterTypes.size != 1) return@first false + val firstParameterType = context.getClass(it.parameterTypes[0]) ?: return@first false + //check if the class contains a field with the enumViewStateClass type + firstParameterType.fields.any { field -> + field.type == viewStateField.type + } + } + + val onDisplayStateChangeGesture = clazz.methods.first { + if (it.returnType != "V" || it.parameterTypes.size != 2) return@first false + val firstParameterType = context.getClass(it.parameterTypes[0]) ?: return@first false + val secondParameterType = context.getClass(it.parameterTypes[1]) ?: return@first false + firstParameterType.isEnum() && secondParameterType.isEnum() + } + + context.addMapping("OperaPageViewController", + "class" to clazz.type.replace("L", "").replace(";", ""), + "viewStateField" to viewStateField.name, + "layerListField" to layerListField.name, + "onDisplayStateChange" to onDisplayStateChange.name, + "onDisplayStateChangeGesture" to onDisplayStateChangeGesture.name + ) + + return + } + } +}+ \ No newline at end of file diff --git a/mapper/src/main/kotlin/me/rhunk/snapenhance/mapper/impl/PlatformAnalyticsCreatorMapper.kt b/mapper/src/main/kotlin/me/rhunk/snapenhance/mapper/impl/PlatformAnalyticsCreatorMapper.kt @@ -0,0 +1,24 @@ +package me.rhunk.snapenhance.mapper.impl + +import me.rhunk.snapenhance.mapper.AbstractClassMapper +import me.rhunk.snapenhance.mapper.MapperContext +import me.rhunk.snapenhance.mapper.ext.findConstString +import me.rhunk.snapenhance.mapper.ext.getStaticConstructor +import me.rhunk.snapenhance.mapper.ext.isEnum + +class PlatformAnalyticsCreatorMapper : AbstractClassMapper() { + override fun run(context: MapperContext) { + for (clazz in context.classes) { + val firstConstructor = clazz.directMethods.firstOrNull { it.name == "<init>" } ?: continue + // 47 is the number of parameters of the constructor + // it may change in future versions + if (firstConstructor.parameters.size != 47) continue + val firstParameterClass = context.getClass(firstConstructor.parameterTypes[0]) ?: continue + if (!firstParameterClass.isEnum()) continue + if (firstParameterClass.getStaticConstructor()?.implementation?.findConstString("IN_APP_NOTIFICATION") != true) continue + + context.addMapping("PlatformAnalyticsCreator", clazz.type.replace("L", "").replace(";", "")) + return + } + } +}+ \ No newline at end of file diff --git a/mapper/src/main/kotlin/me/rhunk/snapenhance/mapper/impl/PlusSubscriptionMapper.kt b/mapper/src/main/kotlin/me/rhunk/snapenhance/mapper/impl/PlusSubscriptionMapper.kt @@ -0,0 +1,28 @@ +package me.rhunk.snapenhance.mapper.impl + +import me.rhunk.snapenhance.mapper.AbstractClassMapper +import me.rhunk.snapenhance.mapper.MapperContext +import me.rhunk.snapenhance.mapper.ext.findConstString + +class PlusSubscriptionMapper : AbstractClassMapper(){ + override fun run(context: MapperContext) { + for (clazz in context.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 + + val isPlusSubscriptionInfoClass = clazz.virtualMethods.firstOrNull { it.name == "toString" }?.implementation?.let { + it.findConstString("SubscriptionInfo", contains = true) && it.findConstString("expirationTimeMillis", contains = true) + } + + if (isPlusSubscriptionInfoClass == true) { + context.addMapping("SubscriptionInfoClass", clazz.type.replace("L", "").replace(";", "")) + return + } + } + } +}+ \ No newline at end of file diff --git a/mapper/src/main/kotlin/me/rhunk/snapenhance/mapper/impl/ScCameraSettingsMapper.kt b/mapper/src/main/kotlin/me/rhunk/snapenhance/mapper/impl/ScCameraSettingsMapper.kt @@ -0,0 +1,21 @@ +package me.rhunk.snapenhance.mapper.impl + +import me.rhunk.snapenhance.mapper.AbstractClassMapper +import me.rhunk.snapenhance.mapper.MapperContext +import me.rhunk.snapenhance.mapper.ext.findConstString +import me.rhunk.snapenhance.mapper.ext.getStaticConstructor +import me.rhunk.snapenhance.mapper.ext.isEnum + +class ScCameraSettingsMapper : AbstractClassMapper() { + override fun run(context: MapperContext) { + for (clazz in context.classes) { + val firstConstructor = clazz.directMethods.firstOrNull { it.name == "<init>" } ?: continue + if (firstConstructor.parameterTypes.size < 27) continue + val firstParameter = context.getClass(firstConstructor.parameterTypes[0]) ?: continue + if (!firstParameter.isEnum() || firstParameter.getStaticConstructor()?.implementation?.findConstString("CONTINUOUS_PICTURE") != true) continue + + context.addMapping("ScCameraSettings", clazz.type.replace("L", "").replace(";", "")) + return + } + } +}+ \ No newline at end of file diff --git a/mapper/src/main/kotlin/me/rhunk/snapenhance/mapper/impl/ScoreUpdateMapper.kt b/mapper/src/main/kotlin/me/rhunk/snapenhance/mapper/impl/ScoreUpdateMapper.kt @@ -0,0 +1,25 @@ +package me.rhunk.snapenhance.mapper.impl + +import me.rhunk.snapenhance.mapper.AbstractClassMapper +import me.rhunk.snapenhance.mapper.MapperContext +import me.rhunk.snapenhance.mapper.ext.findConstString +import me.rhunk.snapenhance.mapper.ext.getClassName + +class ScoreUpdateMapper : AbstractClassMapper() { + override fun run(context: MapperContext) { + for (classDef in context.classes) { + classDef.methods.firstOrNull { + it.name == "<init>" && + it.parameterTypes.size > 4 && + it.parameterTypes[1] == "Ljava/lang/Long;" && + it.parameterTypes[3] == "Ljava/util/Collection;" + } ?: continue + if (classDef.methods.firstOrNull { + it.name == "toString" + }?.implementation?.findConstString("Friend.sq:selectFriendUserScoresNeedToUpdate") != true) continue + + context.addMapping("ScoreUpdate", classDef.getClassName()) + return + } + } +}+ \ No newline at end of file diff --git a/mapper/src/main/kotlin/me/rhunk/snapenhance/mapper/impl/StoryBoostStateMapper.kt b/mapper/src/main/kotlin/me/rhunk/snapenhance/mapper/impl/StoryBoostStateMapper.kt @@ -0,0 +1,20 @@ +package me.rhunk.snapenhance.mapper.impl + +import me.rhunk.snapenhance.mapper.AbstractClassMapper +import me.rhunk.snapenhance.mapper.MapperContext +import me.rhunk.snapenhance.mapper.ext.findConstString + +class StoryBoostStateMapper : AbstractClassMapper() { + override fun run(context: MapperContext) { + for (clazz in context.classes) { + val firstConstructor = clazz.directMethods.firstOrNull { it.name == "<init>" } ?: continue + if (firstConstructor.parameters.size != 3) continue + if (firstConstructor.parameterTypes[1] != "J" || firstConstructor.parameterTypes[2] != "J") continue + + if (clazz.methods.firstOrNull { it.name == "toString" }?.implementation?.findConstString("StoryBoostState", contains = true) != true) continue + + context.addMapping("StoryBoostStateClass", clazz.type.replace("L", "").replace(";", "")) + return + } + } +}+ \ No newline at end of file diff --git a/mapper/src/main/kotlin/me/rhunk/snapenhance/mapper/impl/ViewBinderMapper.kt b/mapper/src/main/kotlin/me/rhunk/snapenhance/mapper/impl/ViewBinderMapper.kt @@ -0,0 +1,37 @@ +package me.rhunk.snapenhance.mapper.impl + +import me.rhunk.snapenhance.mapper.AbstractClassMapper +import me.rhunk.snapenhance.mapper.MapperContext +import me.rhunk.snapenhance.mapper.ext.getClassName +import me.rhunk.snapenhance.mapper.ext.isAbstract +import me.rhunk.snapenhance.mapper.ext.isInterface +import java.lang.reflect.Modifier + +class ViewBinderMapper : AbstractClassMapper() { + override fun run(context: MapperContext) { + for (clazz in context.classes) { + if (!clazz.isAbstract() || clazz.isInterface()) continue + + val getViewMethod = clazz.methods.firstOrNull { it.returnType == "Landroid/view/View;" && it.parameterTypes.size == 0 } ?: continue + + // update view + clazz.methods.filter { + Modifier.isAbstract(it.accessFlags) && it.parameterTypes.size == 1 && it.parameterTypes[0] == "Landroid/view/View;" && it.returnType == "V" + }.also { + if (it.size != 1) return@also + }.firstOrNull() ?: continue + + val bindMethod = 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 + + context.addMapping("ViewBinder", + "class" to clazz.getClassName(), + "bindMethod" to bindMethod.name, + "getViewMethod" to getViewMethod.name + ) + } + } +}+ \ No newline at end of file diff --git a/mapper/src/main/kotlin/me/rhunk/snapmapper/AbstractClassMapper.kt b/mapper/src/main/kotlin/me/rhunk/snapmapper/AbstractClassMapper.kt @@ -1,9 +0,0 @@ -package me.rhunk.snapmapper - -import kotlin.reflect.KClass - -abstract class AbstractClassMapper( - vararg val dependsOn: KClass<out AbstractClassMapper> = arrayOf() -) { - abstract fun run(context: MapperContext) -}- \ No newline at end of file diff --git a/mapper/src/main/kotlin/me/rhunk/snapmapper/Mapper.kt b/mapper/src/main/kotlin/me/rhunk/snapmapper/Mapper.kt @@ -1,90 +0,0 @@ -package me.rhunk.snapmapper - -import com.google.gson.JsonObject -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.withContext -import org.jf.dexlib2.Opcodes -import org.jf.dexlib2.dexbacked.DexBackedDexFile -import org.jf.dexlib2.iface.ClassDef -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() -) { - private val classes = mutableListOf<ClassDef>() - fun loadApk(path: String) { - val apkFile = ZipFile(path) - val apkEntries = apkFile.entries().toList() - - fun readClass(stream: InputStream) = runCatching { - classes.addAll( - DexBackedDexFile.fromInputStream(Opcodes.getDefault(), BufferedInputStream(stream)).classes - ) - }.onFailure { - throw Throwable("Failed to load dex file", it) - } - - fun filterDexClasses(name: String) = name.startsWith("classes") && name.endsWith(".dex") - - apkEntries.firstOrNull { it.name.endsWith("lspatch/origin.apk") }?.let { origin -> - val originApk = ZipInputStream(apkFile.getInputStream(origin)) - var nextEntry = originApk.nextEntry - while (nextEntry != null) { - if (filterDexClasses(nextEntry.name)) { - readClass(originApk) - } - originApk.closeEntry() - nextEntry = originApk.nextEntry - } - return - } - - apkEntries.toList().filter { filterDexClasses(it.name) }.forEach { - readClass(apkFile.getInputStream(it)) - } - } - - fun start(): JsonObject { - val mappers = mappers.map { it.java.constructors.first().newInstance() as AbstractClassMapper } - val context = MapperContext(classes.associateBy { it.type }) - - runBlocking { - withContext(Dispatchers.IO) { - val finishedJobs = mutableListOf<Class<*>>() - val dependentsMappers = mappers.filter { it.dependsOn.isNotEmpty() } - - fun onJobFinished(mapper: AbstractClassMapper) { - finishedJobs.add(mapper.javaClass) - - dependentsMappers.filter { it -> - !finishedJobs.contains(it.javaClass) && - it.dependsOn.all { - finishedJobs.contains(it.java) - } - }.forEach { - launch { - it.run(context) - onJobFinished(it) - } - } - } - - mappers.forEach { mapper -> - if (mapper.dependsOn.isNotEmpty()) return@forEach - launch { - mapper.run(context) - onJobFinished(mapper) - } - } - } - } - - return context.exportToJson() - } -}- \ No newline at end of file diff --git a/mapper/src/main/kotlin/me/rhunk/snapmapper/MapperContext.kt b/mapper/src/main/kotlin/me/rhunk/snapmapper/MapperContext.kt @@ -1,58 +0,0 @@ -package me.rhunk.snapmapper - -import com.google.gson.GsonBuilder -import com.google.gson.JsonObject -import org.jf.dexlib2.iface.ClassDef - -class MapperContext( - private val classMap: Map<String, ClassDef> -) { - val classes: Collection<ClassDef> - get() = classMap.values - - fun getClass(name: String?): ClassDef? { - if (name == null) return null - return classMap[name] - } - - fun getClass(name: CharSequence?): ClassDef? { - 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 - } -}- \ No newline at end of file diff --git a/mapper/src/main/kotlin/me/rhunk/snapmapper/ext/DexClassDef.kt b/mapper/src/main/kotlin/me/rhunk/snapmapper/ext/DexClassDef.kt @@ -1,24 +0,0 @@ -package me.rhunk.snapmapper.ext - -import org.jf.dexlib2.AccessFlags -import org.jf.dexlib2.iface.ClassDef - -fun ClassDef.isEnum(): Boolean = accessFlags and AccessFlags.ENUM.value != 0 -fun ClassDef.isAbstract(): Boolean = accessFlags and AccessFlags.ABSTRACT.value != 0 -fun ClassDef.isInterface(): Boolean = accessFlags and AccessFlags.INTERFACE.value != 0 -fun ClassDef.isFinal(): Boolean = accessFlags and AccessFlags.FINAL.value != 0 - -fun ClassDef.hasStaticConstructorString(string: String): Boolean = methods.any { - it.name == "<clinit>" && it.implementation?.findConstString(string) == true -} - -fun ClassDef.hasConstructorString(string: String): Boolean = methods.any { - it.name == "<init>" && it.implementation?.findConstString(string) == true -} - -fun ClassDef.getStaticConstructor() = methods.firstOrNull { - it.name == "<clinit>" -} - -fun ClassDef.getClassName() = type.replaceFirst("L", "").replaceFirst(";", "") -fun ClassDef.getSuperClassName() = superclass?.replaceFirst("L", "")?.replaceFirst(";", "") diff --git a/mapper/src/main/kotlin/me/rhunk/snapmapper/ext/DexMethod.kt b/mapper/src/main/kotlin/me/rhunk/snapmapper/ext/DexMethod.kt @@ -1,44 +0,0 @@ -package me.rhunk.snapmapper.ext - -import org.jf.dexlib2.iface.MethodImplementation -import org.jf.dexlib2.iface.instruction.formats.Instruction21c -import org.jf.dexlib2.iface.instruction.formats.Instruction22c -import org.jf.dexlib2.iface.reference.FieldReference -import org.jf.dexlib2.iface.reference.StringReference - -fun MethodImplementation.findConstString(string: String, contains: Boolean = false): Boolean = instructions.filterIsInstance(Instruction21c::class.java).any { - (it.reference as? StringReference)?.string?.let { str -> - if (contains) { - str.contains(string) - } else { - str == string - } - } == true -} - -fun MethodImplementation.getAllConstStrings(): List<String> = instructions.filterIsInstance<Instruction21c>().mapNotNull { - it.reference as? StringReference -}.map { - it.string -} - -fun MethodImplementation.searchNextFieldReference(constString: String, contains: Boolean = false): FieldReference? = this.instructions.let { - var found = false - for (instruction in it) { - if (instruction is Instruction21c && instruction.reference is StringReference) { - val str = (instruction.reference as StringReference).string - if (if (contains) str.contains(constString) else str == constString) { - found = true - } - } - - if (!found) continue - - if (instruction is Instruction22c && - instruction.reference is FieldReference - ) { - return@let (instruction.reference as FieldReference) - } - } - null -} diff --git a/mapper/src/main/kotlin/me/rhunk/snapmapper/impl/BCryptClassMapper.kt b/mapper/src/main/kotlin/me/rhunk/snapmapper/impl/BCryptClassMapper.kt @@ -1,34 +0,0 @@ -package me.rhunk.snapmapper.impl - -import me.rhunk.snapmapper.AbstractClassMapper -import me.rhunk.snapmapper.MapperContext -import me.rhunk.snapmapper.ext.getStaticConstructor -import me.rhunk.snapmapper.ext.isFinal -import org.jf.dexlib2.iface.instruction.formats.ArrayPayload - -class BCryptClassMapper : AbstractClassMapper() { - override fun run(context: MapperContext) { - for (clazz in context.classes) { - if (!clazz.isFinal()) continue - - val isBcryptClass = clazz.getStaticConstructor()?.let { constructor -> - constructor.implementation?.instructions?.filterIsInstance<ArrayPayload>()?.any { it.arrayElements.size == 18 && it.arrayElements[0] == 608135816 } - } - - if (isBcryptClass == true) { - val hashMethod = clazz.methods.first { - it.parameterTypes.size == 2 && - it.parameterTypes[0] == "Ljava/lang/String;" && - it.parameterTypes[1] == "Ljava/lang/String;" && - it.returnType == "Ljava/lang/String;" - } - - context.addMapping("BCrypt", - "class" to clazz.type.replace("L", "").replace(";", ""), - "hashMethod" to hashMethod.name - ) - return - } - } - } -}- \ No newline at end of file diff --git a/mapper/src/main/kotlin/me/rhunk/snapmapper/impl/CallbackMapper.kt b/mapper/src/main/kotlin/me/rhunk/snapmapper/impl/CallbackMapper.kt @@ -1,28 +0,0 @@ -package me.rhunk.snapmapper.impl - -import me.rhunk.snapmapper.AbstractClassMapper -import me.rhunk.snapmapper.MapperContext -import me.rhunk.snapmapper.ext.getClassName -import me.rhunk.snapmapper.ext.getSuperClassName -import me.rhunk.snapmapper.ext.isFinal - -class CallbackMapper : AbstractClassMapper() { - override fun run(context: MapperContext) { - val callbackClasses = context.classes.filter { clazz -> - if (clazz.superclass == null) return@filter false - - val superclassName = clazz.getSuperClassName()!! - if ((!superclassName.endsWith("Callback") && !superclassName.endsWith("Delegate")) - || superclassName.endsWith("\$Callback")) return@filter false - - if (clazz.getClassName().endsWith("\$CppProxy")) return@filter false - - val superClass = context.getClass(clazz.superclass) ?: return@filter false - !superClass.isFinal() - }.map { - it.getSuperClassName()!!.substringAfterLast("/") to it.getClassName() - } - - context.addMapping("callbacks", *callbackClasses.toTypedArray()) - } -}- \ No newline at end of file diff --git a/mapper/src/main/kotlin/me/rhunk/snapmapper/impl/CompositeConfigurationProviderMapper.kt b/mapper/src/main/kotlin/me/rhunk/snapmapper/impl/CompositeConfigurationProviderMapper.kt @@ -1,55 +0,0 @@ -package me.rhunk.snapmapper.impl - -import me.rhunk.snapmapper.AbstractClassMapper -import me.rhunk.snapmapper.MapperContext -import me.rhunk.snapmapper.ext.findConstString -import me.rhunk.snapmapper.ext.getClassName -import me.rhunk.snapmapper.ext.hasStaticConstructorString -import me.rhunk.snapmapper.ext.isEnum -import java.lang.reflect.Modifier - -class CompositeConfigurationProviderMapper : AbstractClassMapper() { - override fun run(context: MapperContext) { - for (classDef in context.classes) { - val constructor = classDef.methods.firstOrNull { it.name == "<init>" } ?: continue - if (constructor.parameterTypes.size == 0 || constructor.parameterTypes[0] != "Ljava/util/List;") continue - if (constructor.implementation?.findConstString("CompositeConfigurationProvider") != true) continue - - val getPropertyMethod = classDef.methods.first { method -> - method.parameterTypes.size > 1 && - method.returnType == "Ljava/lang/Object;" && - context.getClass(method.parameterTypes[0])?.interfaces?.contains("Ljava/io/Serializable;") == true && - context.getClass(method.parameterTypes[1])?.let { it.isEnum() && it.hasStaticConstructorString("BOOLEAN") } == true - } - - val configEnumInterface = context.getClass(getPropertyMethod.parameterTypes[0])!! - val enumType = context.getClass(getPropertyMethod.parameterTypes[1])!! - - val observePropertyMethod = classDef.methods.first { - it.parameterTypes.size > 2 && - it.parameterTypes[0] == configEnumInterface.type && - it.parameterTypes[1] == "Ljava/lang/String;" && - it.parameterTypes[2] == enumType.type - } - - val enumGetDefaultValueMethod = configEnumInterface.methods.first { context.getClass(it.returnType)?.interfaces?.contains("Ljava/io/Serializable;") == true } - val defaultValueField = context.getClass(enumGetDefaultValueMethod.returnType)!!.fields.first { - Modifier.isFinal(it.accessFlags) && - Modifier.isPublic(it.accessFlags) && - it.type == "Ljava/lang/Object;" - } - - context.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, - "defaultValueField" to defaultValueField.name - ) - ) - return - } - } -}- \ No newline at end of file diff --git a/mapper/src/main/kotlin/me/rhunk/snapmapper/impl/DefaultMediaItemMapper.kt b/mapper/src/main/kotlin/me/rhunk/snapmapper/impl/DefaultMediaItemMapper.kt @@ -1,22 +0,0 @@ -package me.rhunk.snapmapper.impl - -import me.rhunk.snapmapper.AbstractClassMapper -import me.rhunk.snapmapper.MapperContext -import me.rhunk.snapmapper.ext.isAbstract - -class DefaultMediaItemMapper : AbstractClassMapper() { - override fun run(context: MapperContext) { - for (clazz in context.classes) { - val superClass = context.getClass(clazz.superclass) ?: continue - - if (!superClass.isAbstract() || superClass.interfaces.isEmpty() || superClass.interfaces[0] != "Ljava/lang/Comparable;") continue - if (clazz.methods.none { it.returnType == "Landroid/net/Uri;" }) continue - - val constructorParameters = clazz.directMethods.firstOrNull { it.name == "<init>" }?.parameterTypes ?: continue - if (constructorParameters.size < 6 || constructorParameters[5] != "J") continue - - context.addMapping("DefaultMediaItem", clazz.type.replace("L", "").replace(";", "")) - return - } - } -}- \ No newline at end of file diff --git a/mapper/src/main/kotlin/me/rhunk/snapmapper/impl/EnumMapper.kt b/mapper/src/main/kotlin/me/rhunk/snapmapper/impl/EnumMapper.kt @@ -1,24 +0,0 @@ -package me.rhunk.snapmapper.impl - -import me.rhunk.snapmapper.AbstractClassMapper -import me.rhunk.snapmapper.MapperContext -import me.rhunk.snapmapper.ext.getClassName -import me.rhunk.snapmapper.ext.hasStaticConstructorString -import me.rhunk.snapmapper.ext.isEnum - -class EnumMapper : AbstractClassMapper() { - override fun run(context: MapperContext) { - lateinit var enumQualityLevel : String - - for (enumClass in context.classes) { - if (!enumClass.isEnum()) continue - - if (enumClass.hasStaticConstructorString("LEVEL_MAX")) { - enumQualityLevel = enumClass.getClassName() - break; - } - } - - context.addMapping("EnumQualityLevel", enumQualityLevel) - } -}- \ No newline at end of file diff --git a/mapper/src/main/kotlin/me/rhunk/snapmapper/impl/FriendRelationshipChangerMapper.kt b/mapper/src/main/kotlin/me/rhunk/snapmapper/impl/FriendRelationshipChangerMapper.kt @@ -1,27 +0,0 @@ -package me.rhunk.snapmapper.impl - -import me.rhunk.snapmapper.AbstractClassMapper -import me.rhunk.snapmapper.MapperContext -import me.rhunk.snapmapper.ext.findConstString -import me.rhunk.snapmapper.ext.getClassName -import me.rhunk.snapmapper.ext.isEnum - -class FriendRelationshipChangerMapper : AbstractClassMapper() { - override fun run(context: MapperContext) { - for (classDef in context.classes) { - classDef.methods.firstOrNull { it.name == "<init>" }?.implementation?.findConstString("FriendRelationshipChangerImpl")?.takeIf { it } ?: continue - val addFriendMethod = classDef.methods.first { - it.parameterTypes.size > 4 && - context.getClass(it.parameterTypes[1])?.isEnum() == true && - context.getClass(it.parameterTypes[2])?.isEnum() == true && - context.getClass(it.parameterTypes[3])?.isEnum() == true && - it.parameters[4].type == "Ljava/lang/String;" - } - - context.addMapping("FriendRelationshipChanger", - "class" to classDef.getClassName(), - "addFriendMethod" to addFriendMethod.name - ) - } - } -}- \ No newline at end of file diff --git a/mapper/src/main/kotlin/me/rhunk/snapmapper/impl/FriendsFeedEventDispatcherMapper.kt b/mapper/src/main/kotlin/me/rhunk/snapmapper/impl/FriendsFeedEventDispatcherMapper.kt @@ -1,28 +0,0 @@ -package me.rhunk.snapmapper.impl - -import me.rhunk.snapmapper.AbstractClassMapper -import me.rhunk.snapmapper.MapperContext -import me.rhunk.snapmapper.ext.findConstString -import me.rhunk.snapmapper.ext.getClassName - - -class FriendsFeedEventDispatcherMapper : AbstractClassMapper() { - override fun run(context: MapperContext) { - for (clazz in context.classes) { - if (clazz.methods.count { it.name == "onClickFeed" || it.name == "onItemLongPress" } != 2) continue - val onItemLongPress = clazz.methods.first { it.name == "onItemLongPress" } - val viewHolderContainerClass = context.getClass(onItemLongPress.parameterTypes[0]) ?: continue - - val viewModelField = viewHolderContainerClass.fields.firstOrNull { field -> - val typeClass = context.getClass(field.type) ?: return@firstOrNull false - typeClass.methods.firstOrNull {it.name == "toString"}?.implementation?.findConstString("FriendFeedItemViewModel", contains = true) == true - }?.name ?: continue - - context.addMapping("FriendsFeedEventDispatcher", - "class" to clazz.getClassName(), - "viewModelField" to viewModelField - ) - return - } - } -}- \ No newline at end of file diff --git a/mapper/src/main/kotlin/me/rhunk/snapmapper/impl/MediaQualityLevelProviderMapper.kt b/mapper/src/main/kotlin/me/rhunk/snapmapper/impl/MediaQualityLevelProviderMapper.kt @@ -1,25 +0,0 @@ -package me.rhunk.snapmapper.impl - -import me.rhunk.snapmapper.AbstractClassMapper -import me.rhunk.snapmapper.MapperContext -import me.rhunk.snapmapper.ext.isAbstract -import org.jf.dexlib2.AccessFlags - -class MediaQualityLevelProviderMapper : AbstractClassMapper(EnumMapper::class) { - override fun run(context: MapperContext) { - val mediaQualityLevelClass = context.getStringMapping("EnumQualityLevel") ?: return - - for (clazz in context.classes) { - if (!clazz.isAbstract()) continue - if (clazz.fields.none { it.accessFlags and AccessFlags.TRANSIENT.value != 0 }) continue - - clazz.methods.firstOrNull { it.returnType == "L$mediaQualityLevelClass;" }?.let { - context.addMapping("MediaQualityLevelProvider", - "class" to clazz.type.replace("L", "").replace(";", ""), - "method" to it.name - ) - return - } - } - } -}- \ No newline at end of file diff --git a/mapper/src/main/kotlin/me/rhunk/snapmapper/impl/OperaPageViewControllerMapper.kt b/mapper/src/main/kotlin/me/rhunk/snapmapper/impl/OperaPageViewControllerMapper.kt @@ -1,52 +0,0 @@ -package me.rhunk.snapmapper.impl - -import me.rhunk.snapmapper.AbstractClassMapper -import me.rhunk.snapmapper.MapperContext -import me.rhunk.snapmapper.ext.hasConstructorString -import me.rhunk.snapmapper.ext.hasStaticConstructorString -import me.rhunk.snapmapper.ext.isAbstract -import me.rhunk.snapmapper.ext.isEnum - -class OperaPageViewControllerMapper : AbstractClassMapper() { - override fun run(context: MapperContext) { - for (clazz in context.classes) { - if (!clazz.isAbstract()) continue - if (!clazz.hasConstructorString("OperaPageViewController") || !clazz.hasStaticConstructorString("ad_product_type")) { - continue - } - - val viewStateField = clazz.fields.first { field -> - val fieldClass = context.getClass(field.type) ?: return@first false - fieldClass.isEnum() && fieldClass.hasStaticConstructorString("FULLY_DISPLAYED") - } - - val layerListField = clazz.fields.first { it.type == "Ljava/util/ArrayList;" } - - val onDisplayStateChange = clazz.methods.first { - if (it.returnType != "V" || it.parameterTypes.size != 1) return@first false - val firstParameterType = context.getClass(it.parameterTypes[0]) ?: return@first false - //check if the class contains a field with the enumViewStateClass type - firstParameterType.fields.any { field -> - field.type == viewStateField.type - } - } - - val onDisplayStateChangeGesture = clazz.methods.first { - if (it.returnType != "V" || it.parameterTypes.size != 2) return@first false - val firstParameterType = context.getClass(it.parameterTypes[0]) ?: return@first false - val secondParameterType = context.getClass(it.parameterTypes[1]) ?: return@first false - firstParameterType.isEnum() && secondParameterType.isEnum() - } - - context.addMapping("OperaPageViewController", - "class" to clazz.type.replace("L", "").replace(";", ""), - "viewStateField" to viewStateField.name, - "layerListField" to layerListField.name, - "onDisplayStateChange" to onDisplayStateChange.name, - "onDisplayStateChangeGesture" to onDisplayStateChangeGesture.name - ) - - return - } - } -}- \ No newline at end of file diff --git a/mapper/src/main/kotlin/me/rhunk/snapmapper/impl/PlatformAnalyticsCreatorMapper.kt b/mapper/src/main/kotlin/me/rhunk/snapmapper/impl/PlatformAnalyticsCreatorMapper.kt @@ -1,24 +0,0 @@ -package me.rhunk.snapmapper.impl - -import me.rhunk.snapmapper.AbstractClassMapper -import me.rhunk.snapmapper.MapperContext -import me.rhunk.snapmapper.ext.findConstString -import me.rhunk.snapmapper.ext.getStaticConstructor -import me.rhunk.snapmapper.ext.isEnum - -class PlatformAnalyticsCreatorMapper : AbstractClassMapper() { - override fun run(context: MapperContext) { - for (clazz in context.classes) { - val firstConstructor = clazz.directMethods.firstOrNull { it.name == "<init>" } ?: continue - // 47 is the number of parameters of the constructor - // it may change in future versions - if (firstConstructor.parameters.size != 47) continue - val firstParameterClass = context.getClass(firstConstructor.parameterTypes[0]) ?: continue - if (!firstParameterClass.isEnum()) continue - if (firstParameterClass.getStaticConstructor()?.implementation?.findConstString("IN_APP_NOTIFICATION") != true) continue - - context.addMapping("PlatformAnalyticsCreator", clazz.type.replace("L", "").replace(";", "")) - return - } - } -}- \ No newline at end of file diff --git a/mapper/src/main/kotlin/me/rhunk/snapmapper/impl/PlusSubscriptionMapper.kt b/mapper/src/main/kotlin/me/rhunk/snapmapper/impl/PlusSubscriptionMapper.kt @@ -1,28 +0,0 @@ -package me.rhunk.snapmapper.impl - -import me.rhunk.snapmapper.AbstractClassMapper -import me.rhunk.snapmapper.MapperContext -import me.rhunk.snapmapper.ext.findConstString - -class PlusSubscriptionMapper : AbstractClassMapper(){ - override fun run(context: MapperContext) { - for (clazz in context.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 - - val isPlusSubscriptionInfoClass = clazz.virtualMethods.firstOrNull { it.name == "toString" }?.implementation?.let { - it.findConstString("SubscriptionInfo", contains = true) && it.findConstString("expirationTimeMillis", contains = true) - } - - if (isPlusSubscriptionInfoClass == true) { - context.addMapping("SubscriptionInfoClass", clazz.type.replace("L", "").replace(";", "")) - return - } - } - } -}- \ No newline at end of file diff --git a/mapper/src/main/kotlin/me/rhunk/snapmapper/impl/ScCameraSettingsMapper.kt b/mapper/src/main/kotlin/me/rhunk/snapmapper/impl/ScCameraSettingsMapper.kt @@ -1,21 +0,0 @@ -package me.rhunk.snapmapper.impl - -import me.rhunk.snapmapper.AbstractClassMapper -import me.rhunk.snapmapper.MapperContext -import me.rhunk.snapmapper.ext.findConstString -import me.rhunk.snapmapper.ext.getStaticConstructor -import me.rhunk.snapmapper.ext.isEnum - -class ScCameraSettingsMapper : AbstractClassMapper() { - override fun run(context: MapperContext) { - for (clazz in context.classes) { - val firstConstructor = clazz.directMethods.firstOrNull { it.name == "<init>" } ?: continue - if (firstConstructor.parameterTypes.size < 27) continue - val firstParameter = context.getClass(firstConstructor.parameterTypes[0]) ?: continue - if (!firstParameter.isEnum() || firstParameter.getStaticConstructor()?.implementation?.findConstString("CONTINUOUS_PICTURE") != true) continue - - context.addMapping("ScCameraSettings", clazz.type.replace("L", "").replace(";", "")) - return - } - } -}- \ No newline at end of file diff --git a/mapper/src/main/kotlin/me/rhunk/snapmapper/impl/ScoreUpdateMapper.kt b/mapper/src/main/kotlin/me/rhunk/snapmapper/impl/ScoreUpdateMapper.kt @@ -1,25 +0,0 @@ -package me.rhunk.snapmapper.impl - -import me.rhunk.snapmapper.AbstractClassMapper -import me.rhunk.snapmapper.MapperContext -import me.rhunk.snapmapper.ext.findConstString -import me.rhunk.snapmapper.ext.getClassName - -class ScoreUpdateMapper : AbstractClassMapper() { - override fun run(context: MapperContext) { - for (classDef in context.classes) { - classDef.methods.firstOrNull { - it.name == "<init>" && - it.parameterTypes.size > 4 && - it.parameterTypes[1] == "Ljava/lang/Long;" && - it.parameterTypes[3] == "Ljava/util/Collection;" - } ?: continue - if (classDef.methods.firstOrNull { - it.name == "toString" - }?.implementation?.findConstString("Friend.sq:selectFriendUserScoresNeedToUpdate") != true) continue - - context.addMapping("ScoreUpdate", classDef.getClassName()) - return - } - } -}- \ No newline at end of file diff --git a/mapper/src/main/kotlin/me/rhunk/snapmapper/impl/StoryBoostStateMapper.kt b/mapper/src/main/kotlin/me/rhunk/snapmapper/impl/StoryBoostStateMapper.kt @@ -1,20 +0,0 @@ -package me.rhunk.snapmapper.impl - -import me.rhunk.snapmapper.AbstractClassMapper -import me.rhunk.snapmapper.MapperContext -import me.rhunk.snapmapper.ext.findConstString - -class StoryBoostStateMapper : AbstractClassMapper() { - override fun run(context: MapperContext) { - for (clazz in context.classes) { - val firstConstructor = clazz.directMethods.firstOrNull { it.name == "<init>" } ?: continue - if (firstConstructor.parameters.size != 3) continue - if (firstConstructor.parameterTypes[1] != "J" || firstConstructor.parameterTypes[2] != "J") continue - - if (clazz.methods.firstOrNull { it.name == "toString" }?.implementation?.findConstString("StoryBoostState", contains = true) != true) continue - - context.addMapping("StoryBoostStateClass", clazz.type.replace("L", "").replace(";", "")) - return - } - } -}- \ No newline at end of file diff --git a/mapper/src/main/kotlin/me/rhunk/snapmapper/impl/ViewBinderMapper.kt b/mapper/src/main/kotlin/me/rhunk/snapmapper/impl/ViewBinderMapper.kt @@ -1,37 +0,0 @@ -package me.rhunk.snapmapper.impl - -import me.rhunk.snapmapper.AbstractClassMapper -import me.rhunk.snapmapper.MapperContext -import me.rhunk.snapmapper.ext.getClassName -import me.rhunk.snapmapper.ext.isAbstract -import me.rhunk.snapmapper.ext.isInterface -import java.lang.reflect.Modifier - -class ViewBinderMapper : AbstractClassMapper() { - override fun run(context: MapperContext) { - for (clazz in context.classes) { - if (!clazz.isAbstract() || clazz.isInterface()) continue - - val getViewMethod = clazz.methods.firstOrNull { it.returnType == "Landroid/view/View;" && it.parameterTypes.size == 0 } ?: continue - - // update view - clazz.methods.filter { - Modifier.isAbstract(it.accessFlags) && it.parameterTypes.size == 1 && it.parameterTypes[0] == "Landroid/view/View;" && it.returnType == "V" - }.also { - if (it.size != 1) return@also - }.firstOrNull() ?: continue - - val bindMethod = 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 - - context.addMapping("ViewBinder", - "class" to clazz.getClassName(), - "bindMethod" to bindMethod.name, - "getViewMethod" to getViewMethod.name - ) - } - } -}- \ No newline at end of file diff --git a/mapper/src/test/kotlin/me/rhunk/snapenhance/mapper/tests/TestMappings.kt b/mapper/src/test/kotlin/me/rhunk/snapenhance/mapper/tests/TestMappings.kt @@ -1,8 +1,8 @@ package me.rhunk.snapenhance.mapper.tests import com.google.gson.GsonBuilder -import me.rhunk.snapmapper.Mapper -import me.rhunk.snapmapper.impl.* +import me.rhunk.snapenhance.mapper.Mapper +import me.rhunk.snapenhance.mapper.impl.* import org.junit.Test import java.io.File