mirror of
https://github.com/rhunk/SnapEnhance.git
synced 2025-05-03 16:04:30 +02:00
main branch merge
This commit is contained in:
commit
8e87e7c84d
57
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
57
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -1,64 +1,55 @@
|
|||||||
name: "Bug report"
|
name: "Bug report"
|
||||||
description: Report an issue to help the project improve.
|
description: "Report an issue to help the project improve."
|
||||||
title: "bug: <title>"
|
title: "bug: TITLE"
|
||||||
labels: [
|
labels:
|
||||||
"bug"
|
- "bug"
|
||||||
]
|
|
||||||
body:
|
body:
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: description
|
id: description
|
||||||
attributes:
|
attributes:
|
||||||
label: "Description"
|
label: "Description"
|
||||||
description: Please enter an explicit description of your issue
|
description: "Please enter an explicit description of your issue"
|
||||||
placeholder: Short and explicit description of your incident...
|
placeholder: "Short and explicit description of your incident..."
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: reprod
|
id: reprod
|
||||||
attributes:
|
attributes:
|
||||||
label: "Reproduction steps"
|
label: "Reproduction steps"
|
||||||
description: Steps to reproduce the behavior
|
description: "Steps to reproduce the behavior"
|
||||||
value: |
|
value: |
|
||||||
1. Go to '...'
|
1. Go to `...`
|
||||||
2. Click on '....'
|
2. Click on `....`
|
||||||
3. Scroll down to '....'
|
3. Scroll down to `....`
|
||||||
4. See error
|
4. See error.
|
||||||
render: bash
|
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
|
||||||
id: screenshot
|
|
||||||
attributes:
|
|
||||||
label: "Screenshots"
|
|
||||||
description: If applicable, add screenshots to help explain your problem
|
|
||||||
value: |
|
|
||||||

|
|
||||||
render: bash
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: logs
|
id: logs
|
||||||
attributes:
|
attributes:
|
||||||
label: "Logs"
|
label: "Logs"
|
||||||
description: Please copy and paste any relevant log output if available
|
description: "Please copy and paste any relevant log output if available"
|
||||||
render: bash
|
render: markdown
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: false
|
||||||
- type: input
|
- type: input
|
||||||
id: snapchat-version
|
id: snapchat-version
|
||||||
attributes:
|
attributes:
|
||||||
label: "Snapchat Version"
|
label: "Snapchat Version"
|
||||||
description: On which Snapchat version is this happening?
|
description: "On which Snapchat version is this happening?"
|
||||||
placeholder: ex. 12.35.0.45
|
placeholder: "ex. 12.35.0.45"
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
id: terms
|
id: terms
|
||||||
attributes:
|
attributes:
|
||||||
label: Agreement
|
label: "Agreement"
|
||||||
description: By creating this issue I made sure that ...
|
description: "By creating this issue, I agree to the following terms:"
|
||||||
options:
|
options:
|
||||||
- label: I am using the latest stable SnapEnhance version.
|
- label: "I am using the latest stable SnapEnhance version."
|
||||||
required: true
|
- label: "This is not a bug regarding Snapchat+."
|
||||||
- label: There is no issue already describing my problem.
|
- label: "I have provided a detailed description of the issue."
|
||||||
required: true
|
- label: "I have attached a log if deemed neccessary."
|
||||||
|
- label: "This issue is not a duplicate."
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
name: "Feature suggestion"
|
name: "Feature suggestion"
|
||||||
description: Suggest a new feature to help the project improve.
|
description: Suggest a new feature to help the project improve.
|
||||||
title: "feat: <title>"
|
title: "feat: TITLE"
|
||||||
labels: [
|
labels: [
|
||||||
"enhancement"
|
"enhancement"
|
||||||
]
|
]
|
||||||
|
@ -99,3 +99,4 @@ When redistributing the software, it must remain under the same [GPLv3](https://
|
|||||||
- [RevealedSoulEven](https://github.com/revealedsouleven)
|
- [RevealedSoulEven](https://github.com/revealedsouleven)
|
||||||
- [iBasim](https://github.com/ibasim)
|
- [iBasim](https://github.com/ibasim)
|
||||||
- [xerta555](https://github.com/xerta555)
|
- [xerta555](https://github.com/xerta555)
|
||||||
|
- [TheVisual](https://github.com/TheVisual)
|
||||||
|
@ -8,4 +8,5 @@ class Experimental : ConfigContainer() {
|
|||||||
val infiniteStoryBoost = boolean("infinite_story_boost")
|
val infiniteStoryBoost = boolean("infinite_story_boost")
|
||||||
val meoPasscodeBypass = boolean("meo_passcode_bypass")
|
val meoPasscodeBypass = boolean("meo_passcode_bypass")
|
||||||
val unlimitedMultiSnap = boolean("unlimited_multi_snap")
|
val unlimitedMultiSnap = boolean("unlimited_multi_snap")
|
||||||
|
val noFriendScoreDelay = boolean("no_friend_score_delay")
|
||||||
}
|
}
|
@ -1,106 +0,0 @@
|
|||||||
package me.rhunk.snapenhance.features.impl
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import me.rhunk.snapenhance.features.Feature
|
|
||||||
import me.rhunk.snapenhance.features.FeatureLoadParams
|
|
||||||
import me.rhunk.snapenhance.hook.HookStage
|
|
||||||
import me.rhunk.snapenhance.hook.hook
|
|
||||||
import me.rhunk.snapenhance.util.getObjectField
|
|
||||||
import me.rhunk.snapenhance.util.setObjectField
|
|
||||||
import java.lang.reflect.Field
|
|
||||||
import java.lang.reflect.Modifier
|
|
||||||
import java.lang.reflect.Type
|
|
||||||
|
|
||||||
class ConfigEnumKeys : Feature("Config enum keys", loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC) {
|
|
||||||
|
|
||||||
data class HookEnumContext(
|
|
||||||
val key: String,
|
|
||||||
val type: Type?,
|
|
||||||
val value: Any?,
|
|
||||||
val set: (Any) -> Unit
|
|
||||||
)
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun hookAllEnums(enumClass: Class<*>, callback: HookEnumContext.() -> Unit) {
|
|
||||||
//Enum(String, int, ?)
|
|
||||||
//or Enum(?)
|
|
||||||
val enumDataClass = enumClass.constructors[0].parameterTypes.first { clazz: Class<*> -> clazz != String::class.java && !clazz.isPrimitive }
|
|
||||||
|
|
||||||
//get the field which contains the enum data class
|
|
||||||
val enumDataField = enumClass.declaredFields.first { field: Field -> field.type == enumDataClass }
|
|
||||||
|
|
||||||
val typeField = enumDataClass.declaredFields.first { field: Field -> field.type == Type::class.java }
|
|
||||||
|
|
||||||
//get the field value of the enum data class (the first field of the class with the desc Object)
|
|
||||||
val objectDataField = enumDataField.type.fields.first { field: Field ->
|
|
||||||
field.type == Any::class.java && Modifier.isPublic(
|
|
||||||
field.modifiers
|
|
||||||
) && Modifier.isFinal(field.modifiers)
|
|
||||||
}
|
|
||||||
|
|
||||||
enumClass.enumConstants.forEach { enum ->
|
|
||||||
enumDataField.get(enum)?.let { enumData ->
|
|
||||||
val key = enum.toString()
|
|
||||||
val type = typeField.get(enumData) as Type?
|
|
||||||
val value = enumData.getObjectField(objectDataField.name)
|
|
||||||
val set = { newValue: Any ->
|
|
||||||
enumData.setObjectField(objectDataField.name, newValue)
|
|
||||||
}
|
|
||||||
callback(HookEnumContext(key, type, value, set))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("PrivateApi")
|
|
||||||
override fun onActivityCreate() {
|
|
||||||
if (context.config.userInterface.mapFriendNameTags.get()) {
|
|
||||||
hookAllEnums(context.mappings.getMappedClass("enums", "PLUS")) {
|
|
||||||
if (key == "REDUCE_MY_PROFILE_UI_COMPLEXITY") set(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
hookAllEnums(context.mappings.getMappedClass("enums", "ARROYO")) {
|
|
||||||
if (key == "ENABLE_LONG_SNAP_SENDING") {
|
|
||||||
if (context.config.global.disableSnapSplitting.get()) set(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (context.config.userInterface.streakExpirationInfo.get()) {
|
|
||||||
hookAllEnums(context.mappings.getMappedClass("enums", "FRIENDS_FEED")) {
|
|
||||||
if (key == "STREAK_EXPIRATION_INFO") set(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (context.config.userInterface.blockAds.get()) {
|
|
||||||
hookAllEnums(context.mappings.getMappedClass("enums", "SNAPADS")) {
|
|
||||||
if (key == "BYPASS_AD_FEATURE_GATE") {
|
|
||||||
set(true)
|
|
||||||
}
|
|
||||||
if (key == "CUSTOM_AD_SERVER_URL" || key == "CUSTOM_AD_INIT_SERVER_URL" || key == "CUSTOM_AD_TRACKER_URL") {
|
|
||||||
set("http://127.0.0.1")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
context.config.userInterface.storyViewerOverride.getNullable()?.let { value ->
|
|
||||||
hookAllEnums(context.mappings.getMappedClass("enums", "DISCOVER_FEED")) {
|
|
||||||
if (key == "DF_ENABLE_SHOWS_PAGE_CONTROLS" && value == "DISCOVER_PLAYBACK_SEEKBAR") {
|
|
||||||
set(true)
|
|
||||||
}
|
|
||||||
if (key == "DF_VOPERA_FOR_STORIES" && value == "VERTICAL_STORY_VIEWER") {
|
|
||||||
set(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val sharedPreferencesImpl = context.androidContext.classLoader.loadClass("android.app.SharedPreferencesImpl")
|
|
||||||
|
|
||||||
sharedPreferencesImpl.methods.first { it.name == "getBoolean" }.hook(HookStage.BEFORE) { param ->
|
|
||||||
when (param.arg<String>(0)) {
|
|
||||||
"SIG_APP_APPEARANCE_SETTING" -> if (context.config.userInterface.enableAppAppearance.get()) param.setResult(true)
|
|
||||||
"SPOTLIGHT_5TH_TAB_ENABLED" -> if (context.config.userInterface.disableSpotlight.get()) param.setResult(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,83 @@
|
|||||||
|
package me.rhunk.snapenhance.features.impl
|
||||||
|
|
||||||
|
import de.robv.android.xposed.XposedHelpers
|
||||||
|
import me.rhunk.snapenhance.features.Feature
|
||||||
|
import me.rhunk.snapenhance.features.FeatureLoadParams
|
||||||
|
import me.rhunk.snapenhance.hook.HookStage
|
||||||
|
import me.rhunk.snapenhance.hook.hook
|
||||||
|
import me.rhunk.snapenhance.util.setObjectField
|
||||||
|
|
||||||
|
class ConfigurationOverride : Feature("Configuration Override", loadParams = FeatureLoadParams.INIT_SYNC) {
|
||||||
|
override fun init() {
|
||||||
|
val propertyOverrides = mutableMapOf<String, Pair<(() -> Boolean), Any>>()
|
||||||
|
|
||||||
|
fun overrideProperty(key: String, filter: () -> Boolean, value: Any) {
|
||||||
|
propertyOverrides[key] = Pair(filter, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
overrideProperty("STREAK_EXPIRATION_INFO", { context.config.userInterface.streakExpirationInfo.get() }, true)
|
||||||
|
|
||||||
|
overrideProperty("FORCE_CAMERA_HIGHEST_FPS", { context.config.camera.forceHighestFrameRate.get() }, true)
|
||||||
|
overrideProperty("MEDIA_RECORDER_MAX_QUALITY_LEVEL", { context.config.camera.forceCameraSourceEncoding.get() }, true)
|
||||||
|
overrideProperty("REDUCE_MY_PROFILE_UI_COMPLEXITY", { context.config.userInterface.mapFriendNameTags.get() }, true)
|
||||||
|
overrideProperty("ENABLE_LONG_SNAP_SENDING", { context.config.global.disableSnapSplitting.get() }, true)
|
||||||
|
|
||||||
|
context.config.userInterface.storyViewerOverride.getNullable()?.let { state ->
|
||||||
|
overrideProperty("DF_ENABLE_SHOWS_PAGE_CONTROLS", { state == "DISCOVER_PLAYBACK_SEEKBAR" }, true)
|
||||||
|
overrideProperty("DF_VOPERA_FOR_STORIES", { state == "VERTICAL_STORY_VIEWER" }, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
overrideProperty("SIG_APP_APPEARANCE_SETTING", { context.config.userInterface.enableAppAppearance.get() }, true)
|
||||||
|
overrideProperty("SPOTLIGHT_5TH_TAB_ENABLED", { context.config.userInterface.disableSpotlight.get() }, false)
|
||||||
|
|
||||||
|
overrideProperty("BYPASS_AD_FEATURE_GATE", { context.config.userInterface.blockAds.get() }, true)
|
||||||
|
arrayOf("CUSTOM_AD_TRACKER_URL", "CUSTOM_AD_INIT_SERVER_URL", "CUSTOM_AD_SERVER_URL").forEach {
|
||||||
|
overrideProperty(it, { context.config.userInterface.blockAds.get() }, "http://127.0.0.1")
|
||||||
|
}
|
||||||
|
|
||||||
|
val compositeConfigurationProviderMappings = context.mappings.getMappedMap("CompositeConfigurationProvider")
|
||||||
|
val enumMappings = compositeConfigurationProviderMappings["enum"] as Map<*, *>
|
||||||
|
|
||||||
|
findClass(compositeConfigurationProviderMappings["class"].toString()).hook(
|
||||||
|
compositeConfigurationProviderMappings["observeProperty"].toString(),
|
||||||
|
HookStage.BEFORE
|
||||||
|
) { param ->
|
||||||
|
val enumData = param.arg<Any>(0)
|
||||||
|
val key = enumData.toString()
|
||||||
|
val setValue: (Any?) -> Unit = { value ->
|
||||||
|
val valueHolder = XposedHelpers.callMethod(enumData, enumMappings["getValue"].toString())
|
||||||
|
valueHolder.setObjectField(enumMappings["defaultValueField"].toString(), value)
|
||||||
|
}
|
||||||
|
|
||||||
|
propertyOverrides[key]?.let { (filter, value) ->
|
||||||
|
if (!filter()) return@let
|
||||||
|
setValue(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
findClass(compositeConfigurationProviderMappings["class"].toString()).hook(
|
||||||
|
compositeConfigurationProviderMappings["getProperty"].toString(),
|
||||||
|
HookStage.AFTER
|
||||||
|
) { param ->
|
||||||
|
val propertyKey = param.arg<Any>(0).toString()
|
||||||
|
|
||||||
|
propertyOverrides[propertyKey]?.let { (filter, value) ->
|
||||||
|
if (!filter()) return@let
|
||||||
|
param.setResult(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
arrayOf("getBoolean", "getInt", "getLong", "getFloat", "getString").forEach { methodName ->
|
||||||
|
findClass("android.app.SharedPreferencesImpl").hook(
|
||||||
|
methodName,
|
||||||
|
HookStage.BEFORE
|
||||||
|
) { param ->
|
||||||
|
val key = param.argNullable<Any>(0).toString()
|
||||||
|
propertyOverrides[key]?.let { (filter, value) ->
|
||||||
|
if (!filter()) return@let
|
||||||
|
param.setResult(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -117,7 +117,7 @@ class MediaDownloader : Feature("MediaDownloader", loadParams = FeatureLoadParam
|
|||||||
val pathFormat by context.config.downloader.pathFormat
|
val pathFormat by context.config.downloader.pathFormat
|
||||||
val sanitizedPathPrefix = pathPrefix
|
val sanitizedPathPrefix = pathPrefix
|
||||||
.replace(" ", "_")
|
.replace(" ", "_")
|
||||||
.replace(Regex("[\\\\:*?\"<>|]"), "")
|
.replace(Regex("[\\p{Cntrl}]"), "")
|
||||||
.ifEmpty { hexHash }
|
.ifEmpty { hexHash }
|
||||||
|
|
||||||
val currentDateTime = SimpleDateFormat("yyyy-MM-dd_HH-mm-ss", Locale.ENGLISH).format(System.currentTimeMillis())
|
val currentDateTime = SimpleDateFormat("yyyy-MM-dd_HH-mm-ss", Locale.ENGLISH).format(System.currentTimeMillis())
|
||||||
@ -294,7 +294,7 @@ class MediaDownloader : Feature("MediaDownloader", loadParams = FeatureLoadParam
|
|||||||
if ((snapSource == "PUBLIC_USER" || snapSource == "SAVED_STORY") &&
|
if ((snapSource == "PUBLIC_USER" || snapSource == "SAVED_STORY") &&
|
||||||
(forceDownload || canAutoDownload("public_stories"))) {
|
(forceDownload || canAutoDownload("public_stories"))) {
|
||||||
val userDisplayName = (if (paramMap.containsKey("USER_DISPLAY_NAME")) paramMap["USER_DISPLAY_NAME"].toString() else "").replace(
|
val userDisplayName = (if (paramMap.containsKey("USER_DISPLAY_NAME")) paramMap["USER_DISPLAY_NAME"].toString() else "").replace(
|
||||||
"[^\\x00-\\x7F]".toRegex(),
|
"[\\p{Cntrl}]".toRegex(),
|
||||||
"")
|
"")
|
||||||
|
|
||||||
downloadOperaMedia(provideDownloadManagerClient(
|
downloadOperaMedia(provideDownloadManagerClient(
|
||||||
@ -321,7 +321,7 @@ class MediaDownloader : Feature("MediaDownloader", loadParams = FeatureLoadParam
|
|||||||
//TODO: option to download multiple chapters
|
//TODO: option to download multiple chapters
|
||||||
if (paramMap.containsKey("LONGFORM_VIDEO_PLAYLIST_ITEM") && forceDownload) {
|
if (paramMap.containsKey("LONGFORM_VIDEO_PLAYLIST_ITEM") && forceDownload) {
|
||||||
val storyName = paramMap["STORY_NAME"].toString().replace(
|
val storyName = paramMap["STORY_NAME"].toString().replace(
|
||||||
"[^\\x00-\\x7F]".toRegex(),
|
"[\\p{Cntrl}]".toRegex(),
|
||||||
"")
|
"")
|
||||||
|
|
||||||
//get the position of the media in the playlist and the duration
|
//get the position of the media in the playlist and the duration
|
||||||
@ -535,4 +535,4 @@ class MediaDownloader : Feature("MediaDownloader", loadParams = FeatureLoadParam
|
|||||||
if (messaging.openedConversationUUID == null) return
|
if (messaging.openedConversationUUID == null) return
|
||||||
downloadMessageId(messaging.lastFocusedMessageId, isPreviewMode)
|
downloadMessageId(messaging.lastFocusedMessageId, isPreviewMode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
package me.rhunk.snapenhance.features.impl.experiments
|
||||||
|
|
||||||
|
import me.rhunk.snapenhance.features.Feature
|
||||||
|
import me.rhunk.snapenhance.features.FeatureLoadParams
|
||||||
|
import me.rhunk.snapenhance.hook.HookStage
|
||||||
|
import me.rhunk.snapenhance.hook.hookConstructor
|
||||||
|
import java.lang.reflect.Constructor
|
||||||
|
|
||||||
|
class NoFriendScoreDelay : Feature("NoFriendScoreDelay", loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC) {
|
||||||
|
override fun onActivityCreate() {
|
||||||
|
if (!context.config.experimental.noFriendScoreDelay.get()) return
|
||||||
|
val scoreUpdateClass = context.mappings.getMappedClass("ScoreUpdate")
|
||||||
|
|
||||||
|
scoreUpdateClass.hookConstructor(HookStage.BEFORE) { param ->
|
||||||
|
val constructor = param.method() as Constructor<*>
|
||||||
|
if (constructor.parameterTypes.size < 3 || constructor.parameterTypes[3] != java.util.Collection::class.java) return@hookConstructor
|
||||||
|
param.setArg(2, 0L)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -8,7 +8,6 @@ import android.hardware.camera2.CameraManager
|
|||||||
import me.rhunk.snapenhance.data.wrapper.impl.ScSize
|
import me.rhunk.snapenhance.data.wrapper.impl.ScSize
|
||||||
import me.rhunk.snapenhance.features.Feature
|
import me.rhunk.snapenhance.features.Feature
|
||||||
import me.rhunk.snapenhance.features.FeatureLoadParams
|
import me.rhunk.snapenhance.features.FeatureLoadParams
|
||||||
import me.rhunk.snapenhance.features.impl.ConfigEnumKeys
|
|
||||||
import me.rhunk.snapenhance.hook.HookStage
|
import me.rhunk.snapenhance.hook.HookStage
|
||||||
import me.rhunk.snapenhance.hook.hook
|
import me.rhunk.snapenhance.hook.hook
|
||||||
import me.rhunk.snapenhance.hook.hookConstructor
|
import me.rhunk.snapenhance.hook.hookConstructor
|
||||||
@ -37,15 +36,6 @@ class CameraTweaks : Feature("Camera Tweaks", loadParams = FeatureLoadParams.ACT
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ConfigEnumKeys.hookAllEnums(context.mappings.getMappedClass("enums", "CAMERA")) {
|
|
||||||
if (key == "FORCE_CAMERA_HIGHEST_FPS" && context.config.camera.forceHighestFrameRate.get()) {
|
|
||||||
set(true)
|
|
||||||
}
|
|
||||||
if (key == "MEDIA_RECORDER_MAX_QUALITY_LEVEL" && context.config.camera.forceCameraSourceEncoding.get()) {
|
|
||||||
value!!.javaClass.enumConstants?.let { enumData -> set(enumData.filter { it.toString() == "LEVEL_MAX" }) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val previewResolutionConfig = context.config.camera.overridePreviewResolution.getNullable()?.let { parseResolution(it) }
|
val previewResolutionConfig = context.config.camera.overridePreviewResolution.getNullable()?.let { parseResolution(it) }
|
||||||
val captureResolutionConfig = context.config.camera.overridePictureResolution.getNullable()?.let { parseResolution(it) }
|
val captureResolutionConfig = context.config.camera.overridePictureResolution.getNullable()?.let { parseResolution(it) }
|
||||||
|
|
||||||
|
@ -3,15 +3,13 @@ package me.rhunk.snapenhance.hook
|
|||||||
import de.robv.android.xposed.XC_MethodHook
|
import de.robv.android.xposed.XC_MethodHook
|
||||||
import de.robv.android.xposed.XposedBridge
|
import de.robv.android.xposed.XposedBridge
|
||||||
import java.lang.reflect.Member
|
import java.lang.reflect.Member
|
||||||
|
import java.lang.reflect.Method
|
||||||
typealias HookFilter = (HookAdapter) -> Boolean
|
|
||||||
typealias HookConsumer = (HookAdapter) -> Unit
|
|
||||||
|
|
||||||
object Hooker {
|
object Hooker {
|
||||||
inline fun newMethodHook(
|
inline fun newMethodHook(
|
||||||
stage: HookStage,
|
stage: HookStage,
|
||||||
crossinline consumer: HookConsumer,
|
crossinline consumer: (HookAdapter) -> Unit,
|
||||||
crossinline filter: HookFilter = { true }
|
crossinline filter: ((HookAdapter) -> Boolean) = { true }
|
||||||
): XC_MethodHook {
|
): XC_MethodHook {
|
||||||
return if (stage == HookStage.BEFORE) object : XC_MethodHook() {
|
return if (stage == HookStage.BEFORE) object : XC_MethodHook() {
|
||||||
override fun beforeHookedMethod(param: MethodHookParam<*>) {
|
override fun beforeHookedMethod(param: MethodHookParam<*>) {
|
||||||
@ -28,48 +26,47 @@ object Hooker {
|
|||||||
clazz: Class<*>,
|
clazz: Class<*>,
|
||||||
methodName: String,
|
methodName: String,
|
||||||
stage: HookStage,
|
stage: HookStage,
|
||||||
crossinline consumer: HookConsumer
|
crossinline filter: (HookAdapter) -> Boolean,
|
||||||
): Set<XC_MethodHook.Unhook> = hook(clazz, methodName, stage, { true }, consumer)
|
noinline consumer: (HookAdapter) -> Unit
|
||||||
|
|
||||||
inline fun hook(
|
|
||||||
clazz: Class<*>,
|
|
||||||
methodName: String,
|
|
||||||
stage: HookStage,
|
|
||||||
crossinline filter: HookFilter,
|
|
||||||
crossinline consumer: HookConsumer
|
|
||||||
): Set<XC_MethodHook.Unhook> = XposedBridge.hookAllMethods(clazz, methodName, newMethodHook(stage, consumer, filter))
|
): Set<XC_MethodHook.Unhook> = XposedBridge.hookAllMethods(clazz, methodName, newMethodHook(stage, consumer, filter))
|
||||||
|
|
||||||
inline fun hook(
|
inline fun hook(
|
||||||
member: Member,
|
member: Member,
|
||||||
stage: HookStage,
|
stage: HookStage,
|
||||||
crossinline consumer: HookConsumer
|
crossinline filter: ((HookAdapter) -> Boolean),
|
||||||
): XC_MethodHook.Unhook {
|
crossinline consumer: (HookAdapter) -> Unit
|
||||||
return hook(member, stage, { true }, consumer)
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun hook(
|
|
||||||
member: Member,
|
|
||||||
stage: HookStage,
|
|
||||||
crossinline filter: HookFilter,
|
|
||||||
crossinline consumer: HookConsumer
|
|
||||||
): XC_MethodHook.Unhook {
|
): XC_MethodHook.Unhook {
|
||||||
return XposedBridge.hookMethod(member, newMethodHook(stage, consumer, filter))
|
return XposedBridge.hookMethod(member, newMethodHook(stage, consumer, filter))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun hook(
|
||||||
|
clazz: Class<*>,
|
||||||
|
methodName: String,
|
||||||
|
stage: HookStage,
|
||||||
|
consumer: (HookAdapter) -> Unit
|
||||||
|
): Set<XC_MethodHook.Unhook> = hook(clazz, methodName, stage, { true }, consumer)
|
||||||
|
|
||||||
inline fun hookConstructor(
|
fun hook(
|
||||||
|
member: Member,
|
||||||
|
stage: HookStage,
|
||||||
|
consumer: (HookAdapter) -> Unit
|
||||||
|
): XC_MethodHook.Unhook {
|
||||||
|
return hook(member, stage, { true }, consumer)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hookConstructor(
|
||||||
clazz: Class<*>,
|
clazz: Class<*>,
|
||||||
stage: HookStage,
|
stage: HookStage,
|
||||||
crossinline consumer: HookConsumer
|
consumer: (HookAdapter) -> Unit
|
||||||
) {
|
) {
|
||||||
XposedBridge.hookAllConstructors(clazz, newMethodHook(stage, consumer))
|
XposedBridge.hookAllConstructors(clazz, newMethodHook(stage, consumer))
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun hookConstructor(
|
fun hookConstructor(
|
||||||
clazz: Class<*>,
|
clazz: Class<*>,
|
||||||
stage: HookStage,
|
stage: HookStage,
|
||||||
crossinline filter: HookFilter,
|
filter: ((HookAdapter) -> Boolean),
|
||||||
crossinline consumer: HookConsumer
|
consumer: (HookAdapter) -> Unit
|
||||||
) {
|
) {
|
||||||
XposedBridge.hookAllConstructors(clazz, newMethodHook(stage, consumer, filter))
|
XposedBridge.hookAllConstructors(clazz, newMethodHook(stage, consumer, filter))
|
||||||
}
|
}
|
||||||
@ -79,7 +76,7 @@ object Hooker {
|
|||||||
instance: Any,
|
instance: Any,
|
||||||
methodName: String,
|
methodName: String,
|
||||||
stage: HookStage,
|
stage: HookStage,
|
||||||
crossinline hookConsumer: HookConsumer
|
crossinline hookConsumer: (HookAdapter) -> Unit
|
||||||
) {
|
) {
|
||||||
val unhooks: MutableSet<XC_MethodHook.Unhook> = HashSet()
|
val unhooks: MutableSet<XC_MethodHook.Unhook> = HashSet()
|
||||||
hook(clazz, methodName, stage) { param->
|
hook(clazz, methodName, stage) { param->
|
||||||
@ -95,7 +92,7 @@ object Hooker {
|
|||||||
clazz: Class<*>,
|
clazz: Class<*>,
|
||||||
methodName: String,
|
methodName: String,
|
||||||
stage: HookStage,
|
stage: HookStage,
|
||||||
crossinline hookConsumer: HookConsumer
|
crossinline hookConsumer: (HookAdapter) -> Unit
|
||||||
) {
|
) {
|
||||||
val unhooks: MutableSet<XC_MethodHook.Unhook> = HashSet()
|
val unhooks: MutableSet<XC_MethodHook.Unhook> = HashSet()
|
||||||
hook(clazz, methodName, stage) { param->
|
hook(clazz, methodName, stage) { param->
|
||||||
@ -109,7 +106,7 @@ object Hooker {
|
|||||||
instance: Any,
|
instance: Any,
|
||||||
methodName: String,
|
methodName: String,
|
||||||
stage: HookStage,
|
stage: HookStage,
|
||||||
crossinline hookConsumer: HookConsumer
|
crossinline hookConsumer: (HookAdapter) -> Unit
|
||||||
) {
|
) {
|
||||||
val unhooks: MutableSet<XC_MethodHook.Unhook> = HashSet()
|
val unhooks: MutableSet<XC_MethodHook.Unhook> = HashSet()
|
||||||
hook(clazz, methodName, stage) { param->
|
hook(clazz, methodName, stage) { param->
|
||||||
@ -120,37 +117,43 @@ object Hooker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun Class<*>.hookConstructor(
|
fun Class<*>.hookConstructor(
|
||||||
stage: HookStage,
|
stage: HookStage,
|
||||||
crossinline consumer: HookConsumer
|
consumer: (HookAdapter) -> Unit
|
||||||
) = Hooker.hookConstructor(this, stage, consumer)
|
) = Hooker.hookConstructor(this, stage, consumer)
|
||||||
|
|
||||||
inline fun Class<*>.hookConstructor(
|
fun Class<*>.hookConstructor(
|
||||||
stage: HookStage,
|
stage: HookStage,
|
||||||
crossinline filter: HookFilter,
|
filter: ((HookAdapter) -> Boolean),
|
||||||
crossinline consumer: HookConsumer
|
consumer: (HookAdapter) -> Unit
|
||||||
) = Hooker.hookConstructor(this, stage, filter, consumer)
|
) = Hooker.hookConstructor(this, stage, filter, consumer)
|
||||||
|
|
||||||
inline fun Class<*>.hook(
|
fun Class<*>.hook(
|
||||||
methodName: String,
|
methodName: String,
|
||||||
stage: HookStage,
|
stage: HookStage,
|
||||||
crossinline consumer: HookConsumer
|
consumer: (HookAdapter) -> Unit
|
||||||
): Set<XC_MethodHook.Unhook> = Hooker.hook(this, methodName, stage, consumer)
|
): Set<XC_MethodHook.Unhook> = Hooker.hook(this, methodName, stage, consumer)
|
||||||
|
|
||||||
inline fun Class<*>.hook(
|
fun Class<*>.hook(
|
||||||
methodName: String,
|
methodName: String,
|
||||||
stage: HookStage,
|
stage: HookStage,
|
||||||
crossinline filter: HookFilter,
|
filter: (HookAdapter) -> Boolean,
|
||||||
crossinline consumer: HookConsumer
|
consumer: (HookAdapter) -> Unit
|
||||||
): Set<XC_MethodHook.Unhook> = Hooker.hook(this, methodName, stage, filter, consumer)
|
): Set<XC_MethodHook.Unhook> = Hooker.hook(this, methodName, stage, filter, consumer)
|
||||||
|
|
||||||
inline fun Member.hook(
|
fun Member.hook(
|
||||||
stage: HookStage,
|
stage: HookStage,
|
||||||
crossinline consumer: HookConsumer
|
consumer: (HookAdapter) -> Unit
|
||||||
): XC_MethodHook.Unhook = Hooker.hook(this, stage, consumer)
|
): XC_MethodHook.Unhook = Hooker.hook(this, stage, consumer)
|
||||||
|
|
||||||
inline fun Member.hook(
|
fun Member.hook(
|
||||||
stage: HookStage,
|
stage: HookStage,
|
||||||
crossinline filter: HookFilter,
|
filter: ((HookAdapter) -> Boolean),
|
||||||
crossinline consumer: HookConsumer
|
consumer: (HookAdapter) -> Unit
|
||||||
): XC_MethodHook.Unhook = Hooker.hook(this, stage, filter, consumer)
|
): XC_MethodHook.Unhook = Hooker.hook(this, stage, filter, consumer)
|
||||||
|
|
||||||
|
fun Array<Method>.hookAll(stage: HookStage, param: (HookAdapter) -> Unit) {
|
||||||
|
filter { it.declaringClass != Object::class.java }.forEach {
|
||||||
|
it.hook(stage, param)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -5,7 +5,7 @@ import me.rhunk.snapenhance.ModContext
|
|||||||
import me.rhunk.snapenhance.features.Feature
|
import me.rhunk.snapenhance.features.Feature
|
||||||
import me.rhunk.snapenhance.features.FeatureLoadParams
|
import me.rhunk.snapenhance.features.FeatureLoadParams
|
||||||
import me.rhunk.snapenhance.features.impl.AutoUpdater
|
import me.rhunk.snapenhance.features.impl.AutoUpdater
|
||||||
import me.rhunk.snapenhance.features.impl.ConfigEnumKeys
|
import me.rhunk.snapenhance.features.impl.ConfigurationOverride
|
||||||
import me.rhunk.snapenhance.features.impl.Messaging
|
import me.rhunk.snapenhance.features.impl.Messaging
|
||||||
import me.rhunk.snapenhance.features.impl.downloader.AntiAutoDownload
|
import me.rhunk.snapenhance.features.impl.downloader.AntiAutoDownload
|
||||||
import me.rhunk.snapenhance.features.impl.downloader.MediaDownloader
|
import me.rhunk.snapenhance.features.impl.downloader.MediaDownloader
|
||||||
@ -14,6 +14,7 @@ import me.rhunk.snapenhance.features.impl.experiments.AppPasscode
|
|||||||
import me.rhunk.snapenhance.features.impl.experiments.DeviceSpooferHook
|
import me.rhunk.snapenhance.features.impl.experiments.DeviceSpooferHook
|
||||||
import me.rhunk.snapenhance.features.impl.experiments.InfiniteStoryBoost
|
import me.rhunk.snapenhance.features.impl.experiments.InfiniteStoryBoost
|
||||||
import me.rhunk.snapenhance.features.impl.experiments.MeoPasscodeBypass
|
import me.rhunk.snapenhance.features.impl.experiments.MeoPasscodeBypass
|
||||||
|
import me.rhunk.snapenhance.features.impl.experiments.NoFriendScoreDelay
|
||||||
import me.rhunk.snapenhance.features.impl.experiments.UnlimitedMultiSnap
|
import me.rhunk.snapenhance.features.impl.experiments.UnlimitedMultiSnap
|
||||||
import me.rhunk.snapenhance.features.impl.privacy.DisableMetrics
|
import me.rhunk.snapenhance.features.impl.privacy.DisableMetrics
|
||||||
import me.rhunk.snapenhance.features.impl.privacy.PreventMessageSending
|
import me.rhunk.snapenhance.features.impl.privacy.PreventMessageSending
|
||||||
@ -74,7 +75,7 @@ class FeatureManager(private val context: ModContext) : Manager {
|
|||||||
register(Notifications::class)
|
register(Notifications::class)
|
||||||
register(AutoSave::class)
|
register(AutoSave::class)
|
||||||
register(UITweaks::class)
|
register(UITweaks::class)
|
||||||
register(ConfigEnumKeys::class)
|
register(ConfigurationOverride::class)
|
||||||
register(AntiAutoDownload::class)
|
register(AntiAutoDownload::class)
|
||||||
register(GalleryMediaSendOverride::class)
|
register(GalleryMediaSendOverride::class)
|
||||||
register(AntiAutoSave::class)
|
register(AntiAutoSave::class)
|
||||||
@ -93,6 +94,7 @@ class FeatureManager(private val context: ModContext) : Manager {
|
|||||||
register(DeviceSpooferHook::class)
|
register(DeviceSpooferHook::class)
|
||||||
register(StartupPageOverride::class)
|
register(StartupPageOverride::class)
|
||||||
register(GooglePlayServicesDialogs::class)
|
register(GooglePlayServicesDialogs::class)
|
||||||
|
register(NoFriendScoreDelay::class)
|
||||||
|
|
||||||
initializeFeatures()
|
initializeFeatures()
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ import me.rhunk.snapenhance.ui.ViewAppearanceHelper
|
|||||||
import me.rhunk.snapmapper.Mapper
|
import me.rhunk.snapmapper.Mapper
|
||||||
import me.rhunk.snapmapper.impl.BCryptClassMapper
|
import me.rhunk.snapmapper.impl.BCryptClassMapper
|
||||||
import me.rhunk.snapmapper.impl.CallbackMapper
|
import me.rhunk.snapmapper.impl.CallbackMapper
|
||||||
|
import me.rhunk.snapmapper.impl.CompositeConfigurationProviderMapper
|
||||||
import me.rhunk.snapmapper.impl.DefaultMediaItemMapper
|
import me.rhunk.snapmapper.impl.DefaultMediaItemMapper
|
||||||
import me.rhunk.snapmapper.impl.EnumMapper
|
import me.rhunk.snapmapper.impl.EnumMapper
|
||||||
import me.rhunk.snapmapper.impl.FriendsFeedEventDispatcherMapper
|
import me.rhunk.snapmapper.impl.FriendsFeedEventDispatcherMapper
|
||||||
@ -19,6 +20,7 @@ import me.rhunk.snapmapper.impl.OperaPageViewControllerMapper
|
|||||||
import me.rhunk.snapmapper.impl.PlatformAnalyticsCreatorMapper
|
import me.rhunk.snapmapper.impl.PlatformAnalyticsCreatorMapper
|
||||||
import me.rhunk.snapmapper.impl.PlusSubscriptionMapper
|
import me.rhunk.snapmapper.impl.PlusSubscriptionMapper
|
||||||
import me.rhunk.snapmapper.impl.ScCameraSettingsMapper
|
import me.rhunk.snapmapper.impl.ScCameraSettingsMapper
|
||||||
|
import me.rhunk.snapmapper.impl.ScoreUpdateMapper
|
||||||
import me.rhunk.snapmapper.impl.StoryBoostStateMapper
|
import me.rhunk.snapmapper.impl.StoryBoostStateMapper
|
||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
@ -37,7 +39,9 @@ class MappingManager(private val context: ModContext) : Manager {
|
|||||||
PlusSubscriptionMapper::class,
|
PlusSubscriptionMapper::class,
|
||||||
ScCameraSettingsMapper::class,
|
ScCameraSettingsMapper::class,
|
||||||
StoryBoostStateMapper::class,
|
StoryBoostStateMapper::class,
|
||||||
FriendsFeedEventDispatcherMapper::class
|
FriendsFeedEventDispatcherMapper::class,
|
||||||
|
CompositeConfigurationProviderMapper::class,
|
||||||
|
ScoreUpdateMapper::class
|
||||||
)
|
)
|
||||||
|
|
||||||
private val mappings = ConcurrentHashMap<String, Any>()
|
private val mappings = ConcurrentHashMap<String, Any>()
|
||||||
@ -94,7 +98,7 @@ class MappingManager(private val context: ModContext) : Manager {
|
|||||||
statusDialogBuilder.show()
|
statusDialogBuilder.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,18 +11,18 @@
|
|||||||
android:id="@+id/dialog_latitude"
|
android:id="@+id/dialog_latitude"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:autofillHints=""
|
||||||
android:ems="10"
|
android:ems="10"
|
||||||
android:inputType="numberDecimal"
|
|
||||||
android:hint="Latitude"
|
android:hint="Latitude"
|
||||||
android:autofillHints="" />
|
android:inputType="number|numberDecimal|numberSigned" />
|
||||||
|
|
||||||
<EditText
|
<EditText
|
||||||
android:id="@+id/dialog_longitude"
|
android:id="@+id/dialog_longitude"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:autofillHints=""
|
||||||
android:ems="10"
|
android:ems="10"
|
||||||
android:inputType="numberDecimal"
|
|
||||||
android:hint="Longitude"
|
android:hint="Longitude"
|
||||||
android:autofillHints="" />
|
android:inputType="number|numberDecimal|numberSigned" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
@ -0,0 +1,55 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -16,6 +16,7 @@ class DefaultMediaItemMapper : AbstractClassMapper() {
|
|||||||
if (constructorParameters.size < 6 || constructorParameters[5] != "J") continue
|
if (constructorParameters.size < 6 || constructorParameters[5] != "J") continue
|
||||||
|
|
||||||
context.addMapping("DefaultMediaItem", clazz.type.replace("L", "").replace(";", ""))
|
context.addMapping("DefaultMediaItem", clazz.type.replace("L", "").replace(";", ""))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -3,70 +3,22 @@ package me.rhunk.snapmapper.impl
|
|||||||
import me.rhunk.snapmapper.AbstractClassMapper
|
import me.rhunk.snapmapper.AbstractClassMapper
|
||||||
import me.rhunk.snapmapper.MapperContext
|
import me.rhunk.snapmapper.MapperContext
|
||||||
import me.rhunk.snapmapper.ext.getClassName
|
import me.rhunk.snapmapper.ext.getClassName
|
||||||
import me.rhunk.snapmapper.ext.getStaticConstructor
|
|
||||||
import me.rhunk.snapmapper.ext.hasStaticConstructorString
|
import me.rhunk.snapmapper.ext.hasStaticConstructorString
|
||||||
import me.rhunk.snapmapper.ext.isEnum
|
import me.rhunk.snapmapper.ext.isEnum
|
||||||
import org.jf.dexlib2.Opcode
|
|
||||||
import org.jf.dexlib2.iface.Method
|
|
||||||
import org.jf.dexlib2.iface.instruction.formats.Instruction21c
|
|
||||||
import org.jf.dexlib2.iface.reference.FieldReference
|
|
||||||
import org.jf.dexlib2.iface.reference.StringReference
|
|
||||||
|
|
||||||
class EnumMapper : AbstractClassMapper() {
|
class EnumMapper : AbstractClassMapper() {
|
||||||
override fun run(context: MapperContext) {
|
override fun run(context: MapperContext) {
|
||||||
var enumQualityLevel : String? = null
|
lateinit var enumQualityLevel : String
|
||||||
val enums = mutableListOf<Pair<String, String>>()
|
|
||||||
|
|
||||||
for (enumClass in context.classes) {
|
for (enumClass in context.classes) {
|
||||||
if (!enumClass.isEnum()) continue
|
if (!enumClass.isEnum()) continue
|
||||||
|
|
||||||
if (enumQualityLevel == null && enumClass.hasStaticConstructorString("LEVEL_MAX")) {
|
if (enumClass.hasStaticConstructorString("LEVEL_MAX")) {
|
||||||
enumQualityLevel = enumClass.getClassName()
|
enumQualityLevel = enumClass.getClassName()
|
||||||
}
|
break;
|
||||||
|
|
||||||
if (enumClass.interfaces.isEmpty()) continue
|
|
||||||
|
|
||||||
//check if it's a config enum
|
|
||||||
val serializableInterfaceClass = context.getClass(enumClass.interfaces.first()) ?: continue
|
|
||||||
if (serializableInterfaceClass.methods.none {it.name == "getName" && it.returnType == "Ljava/lang/String;" }) continue
|
|
||||||
|
|
||||||
//find the method which returns the enum name
|
|
||||||
val getEnumMethod = enumClass.virtualMethods.firstOrNull { context.getClass(it.returnType)?.isEnum() == true } ?: continue
|
|
||||||
|
|
||||||
//search for constant field instruction sget-object
|
|
||||||
|
|
||||||
fun getFirstFieldReference21c(opcode: Opcode, method: Method) = method.implementation!!.instructions.firstOrNull {
|
|
||||||
it.opcode == opcode && it is Instruction21c
|
|
||||||
}.let { it as? Instruction21c }?.let {
|
|
||||||
it.reference as? FieldReference
|
|
||||||
}
|
|
||||||
|
|
||||||
val fieldReference = getFirstFieldReference21c(Opcode.SGET_OBJECT, getEnumMethod) ?:
|
|
||||||
getFirstFieldReference21c(Opcode.SGET_OBJECT,enumClass.directMethods.first { it.name == "<init>" }) ?: continue
|
|
||||||
|
|
||||||
//search field name in the <clinit> class
|
|
||||||
val enumClassListEnum = context.getClass(fieldReference.definingClass) ?: continue
|
|
||||||
|
|
||||||
enumClassListEnum.getStaticConstructor()?.let { constructor ->
|
|
||||||
var lastEnumClassName = ""
|
|
||||||
constructor.implementation!!.instructions.forEach {
|
|
||||||
if (it.opcode == Opcode.CONST_STRING) {
|
|
||||||
lastEnumClassName = ((it as Instruction21c).reference as StringReference).string
|
|
||||||
return@forEach
|
|
||||||
}
|
|
||||||
|
|
||||||
if (it.opcode == Opcode.SPUT_OBJECT && it is Instruction21c) {
|
|
||||||
val field = it.reference as? FieldReference ?: return@forEach
|
|
||||||
if (field.name != fieldReference.name || field.type != fieldReference.type) return@forEach
|
|
||||||
|
|
||||||
enums.add(lastEnumClassName to enumClass.getClassName())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
context.addMapping("EnumQualityLevel", enumQualityLevel!!)
|
context.addMapping("EnumQualityLevel", enumQualityLevel)
|
||||||
|
|
||||||
context.addMapping("enums", *enums.sortedBy { it.first }.toTypedArray())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -22,6 +22,7 @@ class FriendsFeedEventDispatcherMapper : AbstractClassMapper() {
|
|||||||
"class" to clazz.getClassName(),
|
"class" to clazz.getClassName(),
|
||||||
"viewModelField" to viewModelField
|
"viewModelField" to viewModelField
|
||||||
)
|
)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -18,6 +18,7 @@ class MediaQualityLevelProviderMapper : AbstractClassMapper(EnumMapper::class) {
|
|||||||
"class" to clazz.type.replace("L", "").replace(";", ""),
|
"class" to clazz.type.replace("L", "").replace(";", ""),
|
||||||
"method" to it.name
|
"method" to it.name
|
||||||
)
|
)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ class PlatformAnalyticsCreatorMapper : AbstractClassMapper() {
|
|||||||
if (firstParameterClass.getStaticConstructor()?.implementation?.findConstString("IN_APP_NOTIFICATION") != true) continue
|
if (firstParameterClass.getStaticConstructor()?.implementation?.findConstString("IN_APP_NOTIFICATION") != true) continue
|
||||||
|
|
||||||
context.addMapping("PlatformAnalyticsCreator", clazz.type.replace("L", "").replace(";", ""))
|
context.addMapping("PlatformAnalyticsCreator", clazz.type.replace("L", "").replace(";", ""))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -15,6 +15,7 @@ class ScCameraSettingsMapper : AbstractClassMapper() {
|
|||||||
if (!firstParameter.isEnum() || firstParameter.getStaticConstructor()?.implementation?.findConstString("CONTINUOUS_PICTURE") != true) continue
|
if (!firstParameter.isEnum() || firstParameter.getStaticConstructor()?.implementation?.findConstString("CONTINUOUS_PICTURE") != true) continue
|
||||||
|
|
||||||
context.addMapping("ScCameraSettings", clazz.type.replace("L", "").replace(";", ""))
|
context.addMapping("ScCameraSettings", clazz.type.replace("L", "").replace(";", ""))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -18,6 +18,7 @@ class StoryBoostStateMapper : AbstractClassMapper() {
|
|||||||
if (storyBoostEnumClass.getStaticConstructor()?.implementation?.findConstString("NeedSubscriptionCannotSubscribe") != true) continue
|
if (storyBoostEnumClass.getStaticConstructor()?.implementation?.findConstString("NeedSubscriptionCannotSubscribe") != true) continue
|
||||||
|
|
||||||
context.addMapping("StoryBoostStateClass", clazz.type.replace("L", "").replace(";", ""))
|
context.addMapping("StoryBoostStateClass", clazz.type.replace("L", "").replace(";", ""))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -21,7 +21,9 @@ class TestMappings {
|
|||||||
PlusSubscriptionMapper::class,
|
PlusSubscriptionMapper::class,
|
||||||
ScCameraSettingsMapper::class,
|
ScCameraSettingsMapper::class,
|
||||||
StoryBoostStateMapper::class,
|
StoryBoostStateMapper::class,
|
||||||
FriendsFeedEventDispatcherMapper::class
|
FriendsFeedEventDispatcherMapper::class,
|
||||||
|
CompositeConfigurationProviderMapper::class,
|
||||||
|
ScoreUpdateMapper::class,
|
||||||
)
|
)
|
||||||
|
|
||||||
val gson = GsonBuilder().setPrettyPrinting().create()
|
val gson = GsonBuilder().setPrettyPrinting().create()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user