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: |
- 
- 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: "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(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 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(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(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 = 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 = 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 = 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 = 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 = 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.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()
@@ -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 == "" } ?: 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>()
+ 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 == "" }) ?: continue
-
- //search field name in the 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 == "" &&
+ 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(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" />
+ android:inputType="number|numberDecimal|numberSigned" />
\ No newline at end of file