commit d03f64a62a5b3ff3c8b46799175f6ef7b3abf81a parent 7b473871e1c1d86e72b9d98eef65d9ea4baadcfa Author: auth <64337177+authorisation@users.noreply.github.com> Date: Mon, 29 May 2023 17:36:12 +0200 feat: location spoof (#9) * feat: location spoof --------- Co-authored-by: rhunk <101876869+rhunk@users.noreply.github.com> Diffstat:
13 files changed, 244 insertions(+), 13 deletions(-)
diff --git a/app/build.gradle b/app/build.gradle @@ -143,4 +143,5 @@ dependencies { compileOnly files('libs/LSPosed-api-1.0-SNAPSHOT.jar') implementation 'com.google.code.gson:gson:2.10.1' implementation 'com.arthenica:ffmpeg-kit-full-gpl:5.1.LTS' + implementation 'org.osmdroid:osmdroid-android:6.1.16' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml @@ -41,7 +41,10 @@ <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> - + <activity + android:name=".features.impl.ui.menus.MapActivity" + android:exported="true" + android:excludeFromRecents="true" /> </application> </manifest> \ No newline at end of file diff --git a/app/src/main/assets/lang/en_US.json b/app/src/main/assets/lang/en_US.json @@ -7,6 +7,7 @@ "ui": "UI", "extras": "Extras", "tweaks": "Tweaks", + "location_spoof": "Location Spoof", "experimental": "Experimental", "debugging": "Debugging" }, @@ -14,7 +15,8 @@ "action": { "clean_cache": "Clean Cache", "clear_message_logger": "Clear Message Logger", - "refresh_mappings": "Refresh Mappings" + "refresh_mappings": "Refresh Mappings", + "open_map": "Pick a location on the map" }, "property": { @@ -57,7 +59,10 @@ "use_download_manager": "Use Android Download Manager", "app_passcode": "Set App Passcode", "app_lock_on_resume": "App Lock On Resume", - "meo_passcode_bypass": "My Eyes Only Passcode Bypass" + "meo_passcode_bypass": "My Eyes Only Passcode Bypass", + "location_spoof": "Snapmap Location Spoofer", + "latitude_value": "Latitude", + "longitude_value": "Longitude" }, "option": { diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/action/AbstractAction.kt b/app/src/main/kotlin/me/rhunk/snapenhance/action/AbstractAction.kt @@ -1,10 +1,12 @@ package me.rhunk.snapenhance.action import me.rhunk.snapenhance.ModContext +import me.rhunk.snapenhance.config.ConfigProperty import java.io.File abstract class AbstractAction( - val nameKey: String + val nameKey: String, + val dependsOnProperty: ConfigProperty? = null, ) { lateinit var context: ModContext diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/action/impl/OpenMap.kt b/app/src/main/kotlin/me/rhunk/snapenhance/action/impl/OpenMap.kt @@ -0,0 +1,23 @@ +package me.rhunk.snapenhance.action.impl + +import android.content.Intent +import android.os.Bundle +import me.rhunk.snapenhance.BuildConfig +import me.rhunk.snapenhance.action.AbstractAction +import me.rhunk.snapenhance.config.ConfigProperty +import me.rhunk.snapenhance.features.impl.ui.menus.MapActivity + +class OpenMap: AbstractAction("action.open_map", dependsOnProperty = ConfigProperty.LOCATION_SPOOF) { + override fun run() { + context.runOnUiThread { + val mapActivityIntent = Intent() + mapActivityIntent.setClassName(BuildConfig.APPLICATION_ID, MapActivity::class.java.name) + mapActivityIntent.putExtra("location", Bundle().apply { + putDouble("latitude", context.config.string(ConfigProperty.LATITUDE).toDouble()) + putDouble("longitude", context.config.string(ConfigProperty.LONGITUDE).toDouble()) + }) + + context.mainActivity!!.startActivityForResult(mapActivityIntent, 0x1337) + } + } +}+ \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/config/ConfigCategory.kt b/app/src/main/kotlin/me/rhunk/snapenhance/config/ConfigCategory.kt @@ -10,5 +10,6 @@ enum class ConfigCategory( UI("category.ui"), EXTRAS("category.extras"), TWEAKS("category.tweaks"), + LOCATION_SPOOF("category.location_spoof"), EXPERIMENTAL("category.experimental"); } diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/config/ConfigProperty.kt b/app/src/main/kotlin/me/rhunk/snapenhance/config/ConfigProperty.kt @@ -213,6 +213,25 @@ enum class ConfigProperty( ), NEW_MAP_UI("property.new_map_ui", "description.new_map_ui", ConfigCategory.TWEAKS, ConfigStateValue(false)), + LOCATION_SPOOF( + "property.location_spoof", + "description.location_spoof", + ConfigCategory.LOCATION_SPOOF, + ConfigStateValue(false) + ), + LATITUDE( + "property.latitude_value", + "description.latitude_value", + ConfigCategory.LOCATION_SPOOF, + ConfigStringValue("0.0000") + ), + LONGITUDE( + "property.longitude_value", + "description.longitude_value", + ConfigCategory.LOCATION_SPOOF, + ConfigStringValue("0.0000") + ), + USE_DOWNLOAD_MANAGER( "property.use_download_manager", "description.use_download_manager", diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/extras/LocationSpoofer.kt b/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/extras/LocationSpoofer.kt @@ -0,0 +1,64 @@ +package me.rhunk.snapenhance.features.impl.extras + +import android.content.Intent +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.Hooker + +class LocationSpoofer: Feature("LocationSpoof", loadParams = FeatureLoadParams.ACTIVITY_CREATE_ASYNC) { + override fun asyncOnActivityCreate() { + Hooker.hook(context.mainActivity!!.javaClass, "onActivityResult", HookStage.BEFORE) { param -> + val intent = param.argNullable<Intent>(2) ?: return@hook + val bundle = intent.getBundleExtra("location") ?: return@hook + param.setResult(null) + val latitude = bundle.getFloat("latitude") + val longitude = bundle.getFloat("longitude") + + with(context.config) { + get(ConfigProperty.LATITUDE).read(latitude.toString()) + get(ConfigProperty.LONGITUDE).read(longitude.toString()) + writeConfig() + } + context.longToast("Location set to $latitude, $longitude") + } + + if (!context.config.bool(ConfigProperty.LOCATION_SPOOF)) return + val locationClass = android.location.Location::class.java + val locationManagerClass = android.location.LocationManager::class.java + + Hooker.hook(locationClass, "getLatitude", HookStage.BEFORE) { hookAdapter -> + hookAdapter.setResult(getLatitude()) + } + + Hooker.hook(locationClass, "getLongitude", HookStage.BEFORE) { hookAdapter -> + hookAdapter.setResult(getLongitude()) + } + + Hooker.hook(locationClass, "getAccuracy", HookStage.BEFORE) { hookAdapter -> + hookAdapter.setResult(getAccuracy()) + } + + //Might be redundant because it calls isProviderEnabledForUser which we also hook, meaning if isProviderEnabledForUser returns true this will also return true + Hooker.hook(locationManagerClass, "isProviderEnabled", HookStage.BEFORE) { hookAdapter -> + hookAdapter.setResult(true) + } + + Hooker.hook(locationManagerClass, "isProviderEnabledForUser", HookStage.BEFORE) {hookAdapter -> + hookAdapter.setResult(true) + } + } + + private fun getLatitude():Double { + return context.config.string(ConfigProperty.LATITUDE).toDouble() + } + + private fun getLongitude():Double { + return context.config.string(ConfigProperty.LONGITUDE).toDouble() + } + + private fun getAccuracy():Float { + return 0.0f + } +}+ \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/ui/menus/MapActivity.kt b/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/ui/menus/MapActivity.kt @@ -0,0 +1,73 @@ +package me.rhunk.snapenhance.features.impl.ui.menus + +import android.app.Activity +import android.content.Context +import android.os.Bundle +import android.view.MotionEvent +import android.widget.Button +import me.rhunk.snapenhance.R +import org.osmdroid.config.Configuration +import org.osmdroid.tileprovider.tilesource.TileSourceFactory +import org.osmdroid.util.GeoPoint +import org.osmdroid.views.MapView +import org.osmdroid.views.Projection +import org.osmdroid.views.overlay.Marker +import org.osmdroid.views.overlay.Overlay + + +//TODO: Implement correctly +class MapActivity : Activity() { + + private lateinit var mapView: MapView + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val contextBundle = intent.extras?.getBundle("location") ?: return + val latitude = contextBundle.getDouble("latitude") + val longitude = contextBundle.getDouble("longitude") + + Configuration.getInstance().load(applicationContext, getSharedPreferences("osmdroid", Context.MODE_PRIVATE)) + + setContentView(R.layout.map) + + mapView = findViewById(R.id.mapView) + mapView.setMultiTouchControls(true); + mapView.setTileSource(TileSourceFactory.MAPNIK) + + val startPoint = GeoPoint(latitude, longitude) + mapView.controller.setZoom(10.0) + mapView.controller.setCenter(startPoint) + + val marker = Marker(mapView) + marker.isDraggable = true + marker.position = startPoint + marker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM) + + mapView.overlays.add(object: Overlay() { + override fun onSingleTapConfirmed(e: MotionEvent?, mapView: MapView?): Boolean { + val proj: Projection = mapView!!.projection + val loc = proj.fromPixels(e!!.x.toInt(), e.y.toInt()) as GeoPoint + marker.position = loc + mapView.invalidate() + return true + } + }) + + mapView.overlays.add(marker) + + val applyButton = findViewById<Button>(R.id.apply_location_button) + applyButton.setOnClickListener { + val bundle = Bundle() + bundle.putFloat("latitude", marker.position.latitude.toFloat()) + bundle.putFloat("longitude", marker.position.longitude.toFloat()) + setResult(RESULT_OK, intent.putExtra("location", bundle)) + finish() + } + } + + override fun onDestroy() { + super.onDestroy() + mapView.onDetach() + } +} diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/ui/menus/impl/SettingsMenu.kt b/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/ui/menus/impl/SettingsMenu.kt @@ -173,25 +173,33 @@ class SettingsMenu : AbstractMenu() { .count().coerceAtLeast(2).toInt() addView(titleText) + val actions = context.actionManager.getActions().map { + Pair(it) { + val button = Button(viewModel.context) + button.text = context.translation.get(it.nameKey) + button.setOnClickListener { _ -> + it.run() + } + ViewAppearanceHelper.applyTheme(viewModel, button) + button + } + } + context.config.entries().groupBy { it.key.category }.forEach { (category, value) -> addView(createCategoryTitle(viewModel, category.key)) value.forEach { addView(createPropertyView(viewModel, it.key)) + actions.find { pair -> pair.first.dependsOnProperty == it.key }?.let { pair -> + addView(pair.second()) + } } } addView(createCategoryTitle(viewModel, "category.debugging")) - - context.actionManager.getActions().forEach { - val button = Button(viewModel.context) - button.text = context.translation.get(it.nameKey) - button.setOnClickListener { _ -> - it.run() - } - ViewAppearanceHelper.applyTheme(viewModel, button) - addView(button) + actions.filter { it.first.dependsOnProperty == null }.forEach { + addView(it.second()) } } } \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/manager/impl/ActionManager.kt b/app/src/main/kotlin/me/rhunk/snapenhance/manager/impl/ActionManager.kt @@ -4,6 +4,7 @@ import me.rhunk.snapenhance.ModContext import me.rhunk.snapenhance.action.AbstractAction import me.rhunk.snapenhance.action.impl.CleanCache import me.rhunk.snapenhance.action.impl.ClearMessageLogger +import me.rhunk.snapenhance.action.impl.OpenMap import me.rhunk.snapenhance.action.impl.RefreshMappings import me.rhunk.snapenhance.manager.Manager import kotlin.reflect.KClass @@ -22,6 +23,7 @@ class ActionManager( load(CleanCache::class) load(ClearMessageLogger::class) load(RefreshMappings::class) + load(OpenMap::class) actions.values.forEach(AbstractAction::init) } 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 @@ -14,6 +14,7 @@ import me.rhunk.snapenhance.features.impl.experiments.AppPasscode import me.rhunk.snapenhance.features.impl.extras.AutoSave import me.rhunk.snapenhance.features.impl.extras.DisableVideoLengthRestriction import me.rhunk.snapenhance.features.impl.extras.GalleryMediaSendOverride +import me.rhunk.snapenhance.features.impl.extras.LocationSpoofer import me.rhunk.snapenhance.features.impl.extras.MediaQualityLevelOverride import me.rhunk.snapenhance.features.impl.extras.Notifications import me.rhunk.snapenhance.features.impl.extras.SnapchatPlus @@ -73,6 +74,8 @@ class FeatureManager(private val context: ModContext) : Manager { register(MediaQualityLevelOverride::class) register(MeoPasscodeBypass::class) register(AppPasscode::class) + register(LocationSpoofer::class) + initializeFeatures() } diff --git a/app/src/main/res/layout/map.xml b/app/src/main/res/layout/map.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <org.osmdroid.views.MapView + android:id="@+id/mapView" + android:layout_width="match_parent" + android:layout_height="match_parent" > + + </org.osmdroid.views.MapView> + + <Button + android:id="@+id/apply_location_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="bottom|right" + android:layout_marginEnd="20dp" + android:layout_marginBottom="20dp" + android:textSize="20sp" + android:background="@android:color/white" + android:text="Apply" + tools:ignore="HardcodedText,RtlHardcoded" /> +</FrameLayout>