From 2925b52b9dc4a78cf1e079d9bbe6638dba5fbb09 Mon Sep 17 00:00:00 2001 From: TheVisual <132447890+TheVisual@users.noreply.github.com> Date: Thu, 3 Aug 2023 12:18:14 -0500 Subject: [PATCH 1/4] chore: template improvement --- .github/ISSUE_TEMPLATE/bug_report.yml | 57 ++++++++----------- .github/ISSUE_TEMPLATE/feature_suggestion.yml | 2 +- 2 files changed, 25 insertions(+), 34 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 3bb78673..d845cdda 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,64 +1,55 @@ name: "Bug report" -description: Report an issue to help the project improve. -title: "bug: " -labels: [ - "bug" -] +description: "Report an issue to help the project improve." +title: "bug: TITLE" +labels: + - "bug" body: - type: textarea id: description attributes: label: "Description" - description: Please enter an explicit description of your issue - placeholder: Short and explicit description of your incident... + description: "Please enter an explicit description of your issue" + placeholder: "Short and explicit description of your incident..." validations: required: true - type: textarea id: reprod attributes: label: "Reproduction steps" - description: Steps to reproduce the behavior + description: "Steps to reproduce the behavior" value: | - 1. Go to '...' - 2. Click on '....' - 3. Scroll down to '....' - 4. See error - render: bash + 1. Go to `...` + 2. Click on `....` + 3. Scroll down to `....` + 4. See error. validations: required: true - - type: textarea - id: screenshot - attributes: - label: "Screenshots" - description: If applicable, add screenshots to help explain your problem - value: | - ![DESCRIPTION](LINK.png) - render: bash - validations: - required: false - type: textarea id: logs attributes: label: "Logs" - description: Please copy and paste any relevant log output if available - render: bash + description: "Please copy and paste any relevant log output if available" + render: markdown validations: required: false - type: input id: snapchat-version attributes: label: "Snapchat Version" - description: On which Snapchat version is this happening? - placeholder: ex. 12.35.0.45 + description: "On which Snapchat version is this happening?" + placeholder: "ex. 12.35.0.45" validations: required: true - type: checkboxes id: terms attributes: - label: Agreement - description: By creating this issue I made sure that ... + label: "Agreement" + description: "By creating this issue, I agree to the following terms:" options: - - label: I am using the latest stable SnapEnhance version. - required: true - - label: There is no issue already describing my problem. - required: true + - label: "I am using the latest stable SnapEnhance version." + - label: "This is not a bug regarding Snapchat+." + - label: "I have provided a detailed description of the issue." + - label: "I have attached a log if deemed neccessary." + - label: "This issue is not a duplicate." + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/feature_suggestion.yml b/.github/ISSUE_TEMPLATE/feature_suggestion.yml index 585a5b8f..41ed432a 100644 --- a/.github/ISSUE_TEMPLATE/feature_suggestion.yml +++ b/.github/ISSUE_TEMPLATE/feature_suggestion.yml @@ -1,6 +1,6 @@ name: "Feature suggestion" description: Suggest a new feature to help the project improve. -title: "feat: <title>" +title: "feat: TITLE" labels: [ "enhancement" ] From d93a90c74d643482f9ef0ce8f0df94cfba29ad95 Mon Sep 17 00:00:00 2001 From: auth <64337177+authorisation@users.noreply.github.com> Date: Fri, 4 Aug 2023 16:53:30 +0200 Subject: [PATCH 2/4] refactor: config override (#169) * refactor: config override * perf: mapper * revert(hooker): inline method - hookAll methods * feat: no friend score delay --------- Co-authored-by: rhunk <101876869+rhunk@users.noreply.github.com> --- app/src/main/assets/lang/en_US.json | 4 + .../snapenhance/config/ConfigProperty.kt | 7 +- .../features/impl/ConfigEnumKeys.kt | 113 ------------------ .../features/impl/ConfigurationOverride.kt | 71 +++++++++++ .../impl/experiments/NoFriendScoreDelay.kt | 21 ++++ .../features/impl/tweaks/CameraTweaks.kt | 10 -- .../me/rhunk/snapenhance/hook/Hooker.kt | 80 +++++++------ .../manager/impl/FeatureManager.kt | 6 +- .../manager/impl/MappingManager.kt | 9 +- ...osedHelperMacros.kt => XposedHelperExt.kt} | 0 .../CompositeConfigurationProviderMapper.kt | 55 +++++++++ .../snapmapper/impl/DefaultMediaItemMapper.kt | 1 + .../me/rhunk/snapmapper/impl/EnumMapper.kt | 50 +------- .../impl/FriendsFeedEventDispatcherMapper.kt | 1 + .../impl/MediaQualityLevelProviderMapper.kt | 1 + .../impl/PlatformAnalyticsCreatorMapper.kt | 1 + .../snapmapper/impl/ScCameraSettingsMapper.kt | 1 + .../snapmapper/impl/ScoreUpdateMapper.kt | 25 ++++ .../snapmapper/impl/StoryBoostStateMapper.kt | 1 + .../snapenhance/mapper/tests/TestMappings.kt | 4 +- 20 files changed, 248 insertions(+), 213 deletions(-) delete mode 100644 app/src/main/kotlin/me/rhunk/snapenhance/features/impl/ConfigEnumKeys.kt create mode 100644 app/src/main/kotlin/me/rhunk/snapenhance/features/impl/ConfigurationOverride.kt create mode 100644 app/src/main/kotlin/me/rhunk/snapenhance/features/impl/experiments/NoFriendScoreDelay.kt rename app/src/main/kotlin/me/rhunk/snapenhance/util/{XposedHelperMacros.kt => XposedHelperExt.kt} (100%) create mode 100644 mapper/src/main/kotlin/me/rhunk/snapmapper/impl/CompositeConfigurationProviderMapper.kt create mode 100644 mapper/src/main/kotlin/me/rhunk/snapmapper/impl/ScoreUpdateMapper.kt diff --git a/app/src/main/assets/lang/en_US.json b/app/src/main/assets/lang/en_US.json index b3010e56..256e8ae3 100644 --- a/app/src/main/assets/lang/en_US.json +++ b/app/src/main/assets/lang/en_US.json @@ -223,6 +223,10 @@ "name": "Unlimited Multi Snap", "description": "Allows you to take an unlimited amount of multi snaps" }, + "no_friend_score_delay": { + "name": "No Friend Score Delay", + "description": "Removes the delay in displaying friends scores. It will be updated immediately when you open a profile page" + }, "device_spoof": { "name": "Spoof Device Values", "description": "Spoofs the devices values" diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/config/ConfigProperty.kt b/app/src/main/kotlin/me/rhunk/snapenhance/config/ConfigProperty.kt index 19b3cd75..027eb757 100644 --- a/app/src/main/kotlin/me/rhunk/snapenhance/config/ConfigProperty.kt +++ b/app/src/main/kotlin/me/rhunk/snapenhance/config/ConfigProperty.kt @@ -386,7 +386,12 @@ enum class ConfigProperty( ConfigCategory.EXPERIMENTAL_DEBUGGING, ConfigStateValue(false) ), - + NO_FRIEND_SCORE_DELAY( + "no_friend_score_delay", + ConfigCategory.EXPERIMENTAL_DEBUGGING, + ConfigStateValue(false) + ), + //DEVICE SPOOFER DEVICE_SPOOF( "device_spoof", diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/ConfigEnumKeys.kt b/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/ConfigEnumKeys.kt deleted file mode 100644 index eb04b21e..00000000 --- a/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/ConfigEnumKeys.kt +++ /dev/null @@ -1,113 +0,0 @@ -package me.rhunk.snapenhance.features.impl - -import android.annotation.SuppressLint -import me.rhunk.snapenhance.config.ConfigProperty -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.bool(ConfigProperty.NEW_MAP_UI)) { - 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.bool(ConfigProperty.DISABLE_SNAP_SPLITTING)) set(true) - } - } - - if (context.config.bool(ConfigProperty.STREAK_EXPIRATION_INFO)) { - hookAllEnums(context.mappings.getMappedClass("enums", "FRIENDS_FEED")) { - if (key == "STREAK_EXPIRATION_INFO") set(true) - } - } - - if (context.config.bool(ConfigProperty.BLOCK_ADS)) { - 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.state(ConfigProperty.STORY_VIEWER_OVERRIDE).let { state -> - if (state == "OFF") return@let - - hookAllEnums(context.mappings.getMappedClass("enums", "DISCOVER_FEED")) { - if (key == "DF_ENABLE_SHOWS_PAGE_CONTROLS" && state == "DISCOVER_PLAYBACK_SEEKBAR") { - set(true) - } - if (key == "DF_VOPERA_FOR_STORIES" && state == "VERTICAL_STORY_VIEWER") { - set(true) - } - } - } - - ConfigProperty.ENABLE_APP_APPEARANCE.valueContainer.addPropertyChangeListener { - context.softRestartApp(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.bool(ConfigProperty.ENABLE_APP_APPEARANCE)) param.setResult(true) - "SPOTLIGHT_5TH_TAB_ENABLED" -> if (context.config.bool(ConfigProperty.DISABLE_SPOTLIGHT)) param.setResult(false) - } - } - } -} \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/ConfigurationOverride.kt b/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/ConfigurationOverride.kt new file mode 100644 index 00000000..c7cb60aa --- /dev/null +++ b/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/ConfigurationOverride.kt @@ -0,0 +1,71 @@ +package me.rhunk.snapenhance.features.impl + +import de.robv.android.xposed.XposedHelpers +import me.rhunk.snapenhance.config.ConfigProperty +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.bool(ConfigProperty.STREAK_EXPIRATION_INFO) }, true) + + overrideProperty("FORCE_CAMERA_HIGHEST_FPS", { context.config.bool(ConfigProperty.FORCE_HIGHEST_FRAME_RATE) }, true) + overrideProperty("MEDIA_RECORDER_MAX_QUALITY_LEVEL", { context.config.bool(ConfigProperty.FORCE_CAMERA_SOURCE_ENCODING) }, true) + overrideProperty("REDUCE_MY_PROFILE_UI_COMPLEXITY", { context.config.bool(ConfigProperty.NEW_MAP_UI) }, true) + overrideProperty("ENABLE_LONG_SNAP_SENDING", { context.config.bool(ConfigProperty.DISABLE_SNAP_SPLITTING) }, true) + overrideProperty("BYPASS_AD_FEATURE_GATE", { context.config.bool(ConfigProperty.BLOCK_ADS) }, true) + + context.config.state(ConfigProperty.STORY_VIEWER_OVERRIDE).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.bool(ConfigProperty.ENABLE_APP_APPEARANCE) }, true) + overrideProperty("SPOTLIGHT_5TH_TAB_ENABLED", { context.config.bool(ConfigProperty.DISABLE_SPOTLIGHT) }, false) + + arrayOf("CUSTOM_AD_TRACKER_URL", "CUSTOM_AD_INIT_SERVER_URL", "CUSTOM_AD_SERVER_URL").forEach { + overrideProperty(it, { context.config.bool(ConfigProperty.BLOCK_ADS) }, "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) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/experiments/NoFriendScoreDelay.kt b/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/experiments/NoFriendScoreDelay.kt new file mode 100644 index 00000000..283d65af --- /dev/null +++ b/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/experiments/NoFriendScoreDelay.kt @@ -0,0 +1,21 @@ +package me.rhunk.snapenhance.features.impl.experiments + +import me.rhunk.snapenhance.config.ConfigProperty +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.bool(ConfigProperty.NO_FRIEND_SCORE_DELAY)) 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) + } + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/tweaks/CameraTweaks.kt b/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/tweaks/CameraTweaks.kt index 3f1a1bf6..19dee9fd 100644 --- a/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/tweaks/CameraTweaks.kt +++ b/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/tweaks/CameraTweaks.kt @@ -9,7 +9,6 @@ import me.rhunk.snapenhance.config.ConfigProperty import me.rhunk.snapenhance.data.wrapper.impl.ScSize import me.rhunk.snapenhance.features.Feature import me.rhunk.snapenhance.features.FeatureLoadParams -import me.rhunk.snapenhance.features.impl.ConfigEnumKeys import me.rhunk.snapenhance.hook.HookStage import me.rhunk.snapenhance.hook.hook import me.rhunk.snapenhance.hook.hookConstructor @@ -38,15 +37,6 @@ class CameraTweaks : Feature("Camera Tweaks", loadParams = FeatureLoadParams.ACT } } - ConfigEnumKeys.hookAllEnums(context.mappings.getMappedClass("enums", "CAMERA")) { - if (key == "FORCE_CAMERA_HIGHEST_FPS" && context.config.bool(ConfigProperty.FORCE_HIGHEST_FRAME_RATE)) { - set(true) - } - if (key == "MEDIA_RECORDER_MAX_QUALITY_LEVEL" && context.config.bool(ConfigProperty.FORCE_CAMERA_SOURCE_ENCODING)) { - value!!.javaClass.enumConstants?.let { enumData -> set(enumData.filter { it.toString() == "LEVEL_MAX" }) } - } - } - val previewResolutionConfig = parseResolution(context.config.state(ConfigProperty.OVERRIDE_PREVIEW_RESOLUTION)) val captureResolutionConfig = parseResolution(context.config.state(ConfigProperty.OVERRIDE_PICTURE_RESOLUTION)) diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/hook/Hooker.kt b/app/src/main/kotlin/me/rhunk/snapenhance/hook/Hooker.kt index b3ee2a7a..052d4e0e 100644 --- a/app/src/main/kotlin/me/rhunk/snapenhance/hook/Hooker.kt +++ b/app/src/main/kotlin/me/rhunk/snapenhance/hook/Hooker.kt @@ -3,6 +3,7 @@ package me.rhunk.snapenhance.hook import de.robv.android.xposed.XC_MethodHook import de.robv.android.xposed.XposedBridge import java.lang.reflect.Member +import java.lang.reflect.Method object Hooker { inline fun newMethodHook( @@ -21,29 +22,14 @@ object Hooker { } } - inline fun hook( - clazz: Class<*>, - methodName: String, - stage: HookStage, - crossinline consumer: (HookAdapter) -> Unit - ): Set<XC_MethodHook.Unhook> = hook(clazz, methodName, stage, { true }, consumer) - inline fun hook( clazz: Class<*>, methodName: String, stage: HookStage, crossinline filter: (HookAdapter) -> Boolean, - crossinline consumer: (HookAdapter) -> Unit + noinline consumer: (HookAdapter) -> Unit ): Set<XC_MethodHook.Unhook> = XposedBridge.hookAllMethods(clazz, methodName, newMethodHook(stage, consumer, filter)) - inline fun hook( - member: Member, - stage: HookStage, - crossinline consumer: (HookAdapter) -> Unit - ): XC_MethodHook.Unhook { - return hook(member, stage, { true }, consumer) - } - inline fun hook( member: Member, stage: HookStage, @@ -53,20 +39,34 @@ object Hooker { 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<*>, stage: HookStage, - crossinline consumer: (HookAdapter) -> Unit + consumer: (HookAdapter) -> Unit ) { XposedBridge.hookAllConstructors(clazz, newMethodHook(stage, consumer)) } - inline fun hookConstructor( + fun hookConstructor( clazz: Class<*>, stage: HookStage, - crossinline filter: ((HookAdapter) -> Boolean), - crossinline consumer: (HookAdapter) -> Unit + filter: ((HookAdapter) -> Boolean), + consumer: (HookAdapter) -> Unit ) { XposedBridge.hookAllConstructors(clazz, newMethodHook(stage, consumer, filter)) } @@ -117,37 +117,43 @@ object Hooker { } } -inline fun Class<*>.hookConstructor( +fun Class<*>.hookConstructor( stage: HookStage, - crossinline consumer: (HookAdapter) -> Unit + consumer: (HookAdapter) -> Unit ) = Hooker.hookConstructor(this, stage, consumer) -inline fun Class<*>.hookConstructor( +fun Class<*>.hookConstructor( stage: HookStage, - crossinline filter: ((HookAdapter) -> Boolean), - crossinline consumer: (HookAdapter) -> Unit + filter: ((HookAdapter) -> Boolean), + consumer: (HookAdapter) -> Unit ) = Hooker.hookConstructor(this, stage, filter, consumer) -inline fun Class<*>.hook( +fun Class<*>.hook( methodName: String, stage: HookStage, - crossinline consumer: (HookAdapter) -> Unit + consumer: (HookAdapter) -> Unit ): Set<XC_MethodHook.Unhook> = Hooker.hook(this, methodName, stage, consumer) -inline fun Class<*>.hook( +fun Class<*>.hook( methodName: String, stage: HookStage, - crossinline filter: (HookAdapter) -> Boolean, - crossinline consumer: (HookAdapter) -> Unit + filter: (HookAdapter) -> Boolean, + consumer: (HookAdapter) -> Unit ): Set<XC_MethodHook.Unhook> = Hooker.hook(this, methodName, stage, filter, consumer) -inline fun Member.hook( +fun Member.hook( stage: HookStage, - crossinline consumer: (HookAdapter) -> Unit + consumer: (HookAdapter) -> Unit ): XC_MethodHook.Unhook = Hooker.hook(this, stage, consumer) -inline fun Member.hook( +fun Member.hook( stage: HookStage, - crossinline filter: ((HookAdapter) -> Boolean), - crossinline consumer: (HookAdapter) -> Unit -): XC_MethodHook.Unhook = Hooker.hook(this, stage, filter, consumer) \ No newline at end of file + filter: ((HookAdapter) -> Boolean), + consumer: (HookAdapter) -> Unit +): 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) + } +} diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/manager/impl/FeatureManager.kt b/app/src/main/kotlin/me/rhunk/snapenhance/manager/impl/FeatureManager.kt index 258bb1b9..66219c6d 100644 --- a/app/src/main/kotlin/me/rhunk/snapenhance/manager/impl/FeatureManager.kt +++ b/app/src/main/kotlin/me/rhunk/snapenhance/manager/impl/FeatureManager.kt @@ -5,7 +5,7 @@ import me.rhunk.snapenhance.ModContext import me.rhunk.snapenhance.features.Feature import me.rhunk.snapenhance.features.FeatureLoadParams 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.downloader.AntiAutoDownload 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.InfiniteStoryBoost 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.privacy.DisableMetrics import me.rhunk.snapenhance.features.impl.privacy.PreventMessageSending @@ -74,7 +75,7 @@ class FeatureManager(private val context: ModContext) : Manager { register(Notifications::class) register(AutoSave::class) register(UITweaks::class) - register(ConfigEnumKeys::class) + register(ConfigurationOverride::class) register(AntiAutoDownload::class) register(GalleryMediaSendOverride::class) register(AntiAutoSave::class) @@ -93,6 +94,7 @@ class FeatureManager(private val context: ModContext) : Manager { register(DeviceSpooferHook::class) register(StartupPageOverride::class) register(GooglePlayServicesDialogs::class) + register(NoFriendScoreDelay::class) initializeFeatures() } diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/manager/impl/MappingManager.kt b/app/src/main/kotlin/me/rhunk/snapenhance/manager/impl/MappingManager.kt index 86d8ad65..d0e751d7 100644 --- a/app/src/main/kotlin/me/rhunk/snapenhance/manager/impl/MappingManager.kt +++ b/app/src/main/kotlin/me/rhunk/snapenhance/manager/impl/MappingManager.kt @@ -1,6 +1,5 @@ package me.rhunk.snapenhance.manager.impl -import android.app.AlertDialog import com.google.gson.JsonElement import com.google.gson.JsonParser import me.rhunk.snapenhance.Constants @@ -12,6 +11,7 @@ import me.rhunk.snapenhance.ui.ViewAppearanceHelper import me.rhunk.snapmapper.Mapper import me.rhunk.snapmapper.impl.BCryptClassMapper import me.rhunk.snapmapper.impl.CallbackMapper +import me.rhunk.snapmapper.impl.CompositeConfigurationProviderMapper import me.rhunk.snapmapper.impl.DefaultMediaItemMapper import me.rhunk.snapmapper.impl.EnumMapper import me.rhunk.snapmapper.impl.FriendsFeedEventDispatcherMapper @@ -20,6 +20,7 @@ import me.rhunk.snapmapper.impl.OperaPageViewControllerMapper import me.rhunk.snapmapper.impl.PlatformAnalyticsCreatorMapper import me.rhunk.snapmapper.impl.PlusSubscriptionMapper import me.rhunk.snapmapper.impl.ScCameraSettingsMapper +import me.rhunk.snapmapper.impl.ScoreUpdateMapper import me.rhunk.snapmapper.impl.StoryBoostStateMapper import java.nio.charset.StandardCharsets import java.util.concurrent.ConcurrentHashMap @@ -38,7 +39,9 @@ class MappingManager(private val context: ModContext) : Manager { PlusSubscriptionMapper::class, ScCameraSettingsMapper::class, StoryBoostStateMapper::class, - FriendsFeedEventDispatcherMapper::class + FriendsFeedEventDispatcherMapper::class, + CompositeConfigurationProviderMapper::class, + ScoreUpdateMapper::class ) private val mappings = ConcurrentHashMap<String, Any>() @@ -95,7 +98,7 @@ class MappingManager(private val context: ModContext) : Manager { statusDialogBuilder.show() } } - } + } } } diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/util/XposedHelperMacros.kt b/app/src/main/kotlin/me/rhunk/snapenhance/util/XposedHelperExt.kt similarity index 100% rename from app/src/main/kotlin/me/rhunk/snapenhance/util/XposedHelperMacros.kt rename to app/src/main/kotlin/me/rhunk/snapenhance/util/XposedHelperExt.kt diff --git a/mapper/src/main/kotlin/me/rhunk/snapmapper/impl/CompositeConfigurationProviderMapper.kt b/mapper/src/main/kotlin/me/rhunk/snapmapper/impl/CompositeConfigurationProviderMapper.kt new file mode 100644 index 00000000..f667d4d3 --- /dev/null +++ b/mapper/src/main/kotlin/me/rhunk/snapmapper/impl/CompositeConfigurationProviderMapper.kt @@ -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 + } + } +} \ 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 index 84320e05..60a7c8bb 100644 --- a/mapper/src/main/kotlin/me/rhunk/snapmapper/impl/DefaultMediaItemMapper.kt +++ b/mapper/src/main/kotlin/me/rhunk/snapmapper/impl/DefaultMediaItemMapper.kt @@ -16,6 +16,7 @@ class DefaultMediaItemMapper : AbstractClassMapper() { 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 index 41e3e478..f9eb0624 100644 --- a/mapper/src/main/kotlin/me/rhunk/snapmapper/impl/EnumMapper.kt +++ b/mapper/src/main/kotlin/me/rhunk/snapmapper/impl/EnumMapper.kt @@ -14,59 +14,17 @@ import org.jf.dexlib2.iface.reference.StringReference class EnumMapper : AbstractClassMapper() { override fun run(context: MapperContext) { - var enumQualityLevel : String? = null - val enums = mutableListOf<Pair<String, String>>() + lateinit var enumQualityLevel : String for (enumClass in context.classes) { if (!enumClass.isEnum()) continue - if (enumQualityLevel == null && enumClass.hasStaticConstructorString("LEVEL_MAX")) { + if (enumClass.hasStaticConstructorString("LEVEL_MAX")) { enumQualityLevel = enumClass.getClassName() - } - - 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()) - } - } + break; } } - context.addMapping("EnumQualityLevel", enumQualityLevel!!) - - context.addMapping("enums", *enums.sortedBy { it.first }.toTypedArray()) + context.addMapping("EnumQualityLevel", enumQualityLevel) } } \ 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 index ca5cf4ea..8131a9ce 100644 --- a/mapper/src/main/kotlin/me/rhunk/snapmapper/impl/FriendsFeedEventDispatcherMapper.kt +++ b/mapper/src/main/kotlin/me/rhunk/snapmapper/impl/FriendsFeedEventDispatcherMapper.kt @@ -22,6 +22,7 @@ class FriendsFeedEventDispatcherMapper : AbstractClassMapper() { "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 index 19ab42b6..cd2c7f7c 100644 --- a/mapper/src/main/kotlin/me/rhunk/snapmapper/impl/MediaQualityLevelProviderMapper.kt +++ b/mapper/src/main/kotlin/me/rhunk/snapmapper/impl/MediaQualityLevelProviderMapper.kt @@ -18,6 +18,7 @@ class MediaQualityLevelProviderMapper : AbstractClassMapper(EnumMapper::class) { "class" to clazz.type.replace("L", "").replace(";", ""), "method" to it.name ) + return } } } diff --git a/mapper/src/main/kotlin/me/rhunk/snapmapper/impl/PlatformAnalyticsCreatorMapper.kt b/mapper/src/main/kotlin/me/rhunk/snapmapper/impl/PlatformAnalyticsCreatorMapper.kt index ee340f5d..e55795f1 100644 --- a/mapper/src/main/kotlin/me/rhunk/snapmapper/impl/PlatformAnalyticsCreatorMapper.kt +++ b/mapper/src/main/kotlin/me/rhunk/snapmapper/impl/PlatformAnalyticsCreatorMapper.kt @@ -18,6 +18,7 @@ class PlatformAnalyticsCreatorMapper : AbstractClassMapper() { 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/ScCameraSettingsMapper.kt b/mapper/src/main/kotlin/me/rhunk/snapmapper/impl/ScCameraSettingsMapper.kt index d473a4df..5a34d1e3 100644 --- a/mapper/src/main/kotlin/me/rhunk/snapmapper/impl/ScCameraSettingsMapper.kt +++ b/mapper/src/main/kotlin/me/rhunk/snapmapper/impl/ScCameraSettingsMapper.kt @@ -15,6 +15,7 @@ class ScCameraSettingsMapper : AbstractClassMapper() { 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 new file mode 100644 index 00000000..a91fb8b0 --- /dev/null +++ b/mapper/src/main/kotlin/me/rhunk/snapmapper/impl/ScoreUpdateMapper.kt @@ -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 + } + } +} \ 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 index b6ee421f..a01ee2e8 100644 --- a/mapper/src/main/kotlin/me/rhunk/snapmapper/impl/StoryBoostStateMapper.kt +++ b/mapper/src/main/kotlin/me/rhunk/snapmapper/impl/StoryBoostStateMapper.kt @@ -18,6 +18,7 @@ class StoryBoostStateMapper : AbstractClassMapper() { if (storyBoostEnumClass.getStaticConstructor()?.implementation?.findConstString("NeedSubscriptionCannotSubscribe") != true) continue context.addMapping("StoryBoostStateClass", clazz.type.replace("L", "").replace(";", "")) + return } } } \ 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 index 604c6566..5da213fd 100644 --- a/mapper/src/test/kotlin/me/rhunk/snapenhance/mapper/tests/TestMappings.kt +++ b/mapper/src/test/kotlin/me/rhunk/snapenhance/mapper/tests/TestMappings.kt @@ -21,7 +21,9 @@ class TestMappings { PlusSubscriptionMapper::class, ScCameraSettingsMapper::class, StoryBoostStateMapper::class, - FriendsFeedEventDispatcherMapper::class + FriendsFeedEventDispatcherMapper::class, + CompositeConfigurationProviderMapper::class, + ScoreUpdateMapper::class, ) val gson = GsonBuilder().setPrettyPrinting().create() From e89901a2f6850bec35580bc773066cb9068bf88f Mon Sep 17 00:00:00 2001 From: rhunk <101876869+rhunk@users.noreply.github.com> Date: Fri, 4 Aug 2023 22:45:03 +0200 Subject: [PATCH 3/4] fix: configuration override --- .../features/impl/ConfigurationOverride.kt | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/ConfigurationOverride.kt b/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/ConfigurationOverride.kt index c7cb60aa..f5c4ac9b 100644 --- a/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/ConfigurationOverride.kt +++ b/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/ConfigurationOverride.kt @@ -67,5 +67,18 @@ class ConfigurationOverride : Feature("Configuration Override", loadParams = Fea 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) + } + } + } } } \ No newline at end of file From 4182f53f230aecf71ace0156fbd94ec2f04fdb87 Mon Sep 17 00:00:00 2001 From: TheVisual <132447890+TheVisual@users.noreply.github.com> Date: Sat, 5 Aug 2023 09:59:02 -0500 Subject: [PATCH 4/4] fix: unicode paths * improve sanitization * Fix InputType to allow negative decimals to properly spoof more locations Co-authored-by: auth <64337177+authorisation@users.noreply.github.com> --- README.md | 1 + .../features/impl/downloader/MediaDownloader.kt | 8 ++++---- .../features/impl/tweaks/GalleryMediaSendOverride.kt | 5 ++--- app/src/main/res/layout/precise_location_dialog.xml | 8 ++++---- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 5d0fe558..30bc11e8 100644 --- a/README.md +++ b/README.md @@ -99,3 +99,4 @@ When redistributing the software, it must remain under the same [GPLv3](https:// - [RevealedSoulEven](https://github.com/revealedsouleven) - [iBasim](https://github.com/ibasim) - [xerta555](https://github.com/xerta555) +- [TheVisual](https://github.com/TheVisual) diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/downloader/MediaDownloader.kt b/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/downloader/MediaDownloader.kt index df5308cc..17dcd392 100644 --- a/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/downloader/MediaDownloader.kt +++ b/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/downloader/MediaDownloader.kt @@ -127,7 +127,7 @@ class MediaDownloader : Feature("MediaDownloader", loadParams = FeatureLoadParam val downloadOptions = context.config.options(ConfigProperty.DOWNLOAD_OPTIONS) val sanitizedPathPrefix = pathPrefix .replace(" ", "_") - .replace(Regex("[\\\\:*?\"<>|]"), "") + .replace(Regex("[\\p{Cntrl}]"), "") .ifEmpty { hexHash } val currentDateTime = SimpleDateFormat("yyyy-MM-dd_HH-mm-ss", Locale.ENGLISH).format(System.currentTimeMillis()) @@ -304,7 +304,7 @@ class MediaDownloader : Feature("MediaDownloader", loadParams = FeatureLoadParam if ((snapSource == "PUBLIC_USER" || snapSource == "SAVED_STORY") && (forceDownload || canAutoDownload("public_stories"))) { val userDisplayName = (if (paramMap.containsKey("USER_DISPLAY_NAME")) paramMap["USER_DISPLAY_NAME"].toString() else "").replace( - "[^\\x00-\\x7F]".toRegex(), + "[\\p{Cntrl}]".toRegex(), "") downloadOperaMedia(provideClientDownloadManager( @@ -336,7 +336,7 @@ class MediaDownloader : Feature("MediaDownloader", loadParams = FeatureLoadParam } 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 @@ -548,4 +548,4 @@ class MediaDownloader : Feature("MediaDownloader", loadParams = FeatureLoadParam if (messaging.openedConversationUUID == null) return downloadMessageId(messaging.lastFocusedMessageId, isPreviewMode) } -} \ No newline at end of file +} diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/tweaks/GalleryMediaSendOverride.kt b/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/tweaks/GalleryMediaSendOverride.kt index 344162fc..7339cce6 100644 --- a/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/tweaks/GalleryMediaSendOverride.kt +++ b/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/tweaks/GalleryMediaSendOverride.kt @@ -1,6 +1,5 @@ package me.rhunk.snapenhance.features.impl.tweaks -import android.app.AlertDialog import me.rhunk.snapenhance.config.ConfigProperty import me.rhunk.snapenhance.data.ContentType import me.rhunk.snapenhance.data.MessageSender @@ -28,7 +27,7 @@ class GalleryMediaSendOverride : Feature("Gallery Media Send Override", loadPara }) { param -> val localMessageContent = MessageContent(param.arg(1)) if (localMessageContent.contentType != ContentType.EXTERNAL_MEDIA) return@hook - + //prevent story replies val messageProtoReader = ProtoReader(localMessageContent.content) if (messageProtoReader.exists(7)) return@hook @@ -73,4 +72,4 @@ class GalleryMediaSendOverride : Feature("Gallery Media Send Override", loadPara } } } -} \ No newline at end of file +} diff --git a/app/src/main/res/layout/precise_location_dialog.xml b/app/src/main/res/layout/precise_location_dialog.xml index c9607a8d..a9eae946 100644 --- a/app/src/main/res/layout/precise_location_dialog.xml +++ b/app/src/main/res/layout/precise_location_dialog.xml @@ -11,18 +11,18 @@ android:id="@+id/dialog_latitude" android:layout_width="match_parent" android:layout_height="wrap_content" + android:autofillHints="" android:ems="10" - android:inputType="numberDecimal" android:hint="Latitude" - android:autofillHints="" /> + android:inputType="number|numberDecimal|numberSigned" /> <EditText android:id="@+id/dialog_longitude" android:layout_width="match_parent" android:layout_height="wrap_content" + android:autofillHints="" android:ems="10" - android:inputType="numberDecimal" android:hint="Longitude" - android:autofillHints="" /> + android:inputType="number|numberDecimal|numberSigned" /> </LinearLayout> \ No newline at end of file