main branch merge

This commit is contained in:
rhunk
2023-08-06 17:04:18 +02:00
24 changed files with 292 additions and 263 deletions

View File

@ -8,4 +8,5 @@ class Experimental : ConfigContainer() {
val infiniteStoryBoost = boolean("infinite_story_boost")
val meoPasscodeBypass = boolean("meo_passcode_bypass")
val unlimitedMultiSnap = boolean("unlimited_multi_snap")
val noFriendScoreDelay = boolean("no_friend_score_delay")
}

View File

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

View File

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

View File

@ -117,7 +117,7 @@ class MediaDownloader : Feature("MediaDownloader", loadParams = FeatureLoadParam
val pathFormat by context.config.downloader.pathFormat
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())
@ -294,7 +294,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(provideDownloadManagerClient(
@ -321,7 +321,7 @@ class MediaDownloader : Feature("MediaDownloader", loadParams = FeatureLoadParam
//TODO: option to download multiple chapters
if (paramMap.containsKey("LONGFORM_VIDEO_PLAYLIST_ITEM") && forceDownload) {
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
@ -535,4 +535,4 @@ class MediaDownloader : Feature("MediaDownloader", loadParams = FeatureLoadParam
if (messaging.openedConversationUUID == null) return
downloadMessageId(messaging.lastFocusedMessageId, isPreviewMode)
}
}
}

View File

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

View File

@ -8,7 +8,6 @@ import android.hardware.camera2.CameraManager
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
@ -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 captureResolutionConfig = context.config.camera.overridePictureResolution.getNullable()?.let { parseResolution(it) }

View File

@ -3,15 +3,13 @@ package me.rhunk.snapenhance.hook
import de.robv.android.xposed.XC_MethodHook
import de.robv.android.xposed.XposedBridge
import java.lang.reflect.Member
typealias HookFilter = (HookAdapter) -> Boolean
typealias HookConsumer = (HookAdapter) -> Unit
import java.lang.reflect.Method
object Hooker {
inline fun newMethodHook(
stage: HookStage,
crossinline consumer: HookConsumer,
crossinline filter: HookFilter = { true }
crossinline consumer: (HookAdapter) -> Unit,
crossinline filter: ((HookAdapter) -> Boolean) = { true }
): XC_MethodHook {
return if (stage == HookStage.BEFORE) object : XC_MethodHook() {
override fun beforeHookedMethod(param: MethodHookParam<*>) {
@ -28,48 +26,47 @@ object Hooker {
clazz: Class<*>,
methodName: String,
stage: HookStage,
crossinline consumer: HookConsumer
): Set<XC_MethodHook.Unhook> = hook(clazz, methodName, stage, { true }, consumer)
inline fun hook(
clazz: Class<*>,
methodName: String,
stage: HookStage,
crossinline filter: HookFilter,
crossinline consumer: HookConsumer
crossinline filter: (HookAdapter) -> Boolean,
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: HookConsumer
): XC_MethodHook.Unhook {
return hook(member, stage, { true }, consumer)
}
inline fun hook(
member: Member,
stage: HookStage,
crossinline filter: HookFilter,
crossinline consumer: HookConsumer
crossinline filter: ((HookAdapter) -> Boolean),
crossinline consumer: (HookAdapter) -> Unit
): XC_MethodHook.Unhook {
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: HookConsumer
consumer: (HookAdapter) -> Unit
) {
XposedBridge.hookAllConstructors(clazz, newMethodHook(stage, consumer))
}
inline fun hookConstructor(
fun hookConstructor(
clazz: Class<*>,
stage: HookStage,
crossinline filter: HookFilter,
crossinline consumer: HookConsumer
filter: ((HookAdapter) -> Boolean),
consumer: (HookAdapter) -> Unit
) {
XposedBridge.hookAllConstructors(clazz, newMethodHook(stage, consumer, filter))
}
@ -79,7 +76,7 @@ object Hooker {
instance: Any,
methodName: String,
stage: HookStage,
crossinline hookConsumer: HookConsumer
crossinline hookConsumer: (HookAdapter) -> Unit
) {
val unhooks: MutableSet<XC_MethodHook.Unhook> = HashSet()
hook(clazz, methodName, stage) { param->
@ -95,7 +92,7 @@ object Hooker {
clazz: Class<*>,
methodName: String,
stage: HookStage,
crossinline hookConsumer: HookConsumer
crossinline hookConsumer: (HookAdapter) -> Unit
) {
val unhooks: MutableSet<XC_MethodHook.Unhook> = HashSet()
hook(clazz, methodName, stage) { param->
@ -109,7 +106,7 @@ object Hooker {
instance: Any,
methodName: String,
stage: HookStage,
crossinline hookConsumer: HookConsumer
crossinline hookConsumer: (HookAdapter) -> Unit
) {
val unhooks: MutableSet<XC_MethodHook.Unhook> = HashSet()
hook(clazz, methodName, stage) { param->
@ -120,37 +117,43 @@ object Hooker {
}
}
inline fun Class<*>.hookConstructor(
fun Class<*>.hookConstructor(
stage: HookStage,
crossinline consumer: HookConsumer
consumer: (HookAdapter) -> Unit
) = Hooker.hookConstructor(this, stage, consumer)
inline fun Class<*>.hookConstructor(
fun Class<*>.hookConstructor(
stage: HookStage,
crossinline filter: HookFilter,
crossinline consumer: HookConsumer
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: HookConsumer
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: HookFilter,
crossinline consumer: HookConsumer
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: HookConsumer
consumer: (HookAdapter) -> Unit
): XC_MethodHook.Unhook = Hooker.hook(this, stage, consumer)
inline fun Member.hook(
fun Member.hook(
stage: HookStage,
crossinline filter: HookFilter,
crossinline consumer: HookConsumer
): XC_MethodHook.Unhook = Hooker.hook(this, stage, filter, consumer)
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)
}
}

View File

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

View File

@ -11,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
@ -19,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
@ -37,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>()
@ -94,7 +98,7 @@ class MappingManager(private val context: ModContext) : Manager {
statusDialogBuilder.show()
}
}
}
}
}
}

View File

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