refactor: mapper

This commit is contained in:
rhunk
2024-01-11 23:10:09 +01:00
parent 1f7f270766
commit ed4334c429
44 changed files with 767 additions and 644 deletions

View File

@ -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)
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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)
}
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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)}")
}
}
}