feat: location spoof (#9)

* feat: location spoof
---------

Co-authored-by: rhunk <101876869+rhunk@users.noreply.github.com>
This commit is contained in:
auth 2023-05-29 17:36:12 +02:00 committed by GitHub
parent 7b473871e1
commit d03f64a62a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 242 additions and 13 deletions

View File

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

View File

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

View File

@ -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": {

View File

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

View File

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

View File

@ -10,5 +10,6 @@ enum class ConfigCategory(
UI("category.ui"),
EXTRAS("category.extras"),
TWEAKS("category.tweaks"),
LOCATION_SPOOF("category.location_spoof"),
EXPERIMENTAL("category.experimental");
}

View File

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

View File

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

View File

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

View File

@ -173,25 +173,33 @@ class SettingsMenu : AbstractMenu() {
.count().coerceAtLeast(2).toInt()
addView(titleText)
context.config.entries().groupBy {
it.key.category
}.forEach { (category, value) ->
addView(createCategoryTitle(viewModel, category.key))
value.forEach {
addView(createPropertyView(viewModel, it.key))
}
}
addView(createCategoryTitle(viewModel, "category.debugging"))
context.actionManager.getActions().forEach {
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)
addView(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"))
actions.filter { it.first.dependsOnProperty == null }.forEach {
addView(it.second())
}
}
}

View File

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

View File

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

View File

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