mirror of
https://github.com/rhunk/SnapEnhance.git
synced 2025-05-23 18:16:15 +02:00
feat: location spoof (#9)
* feat: location spoof --------- Co-authored-by: rhunk <101876869+rhunk@users.noreply.github.com>
This commit is contained in:
parent
7b473871e1
commit
d03f64a62a
@ -143,4 +143,5 @@ dependencies {
|
|||||||
compileOnly files('libs/LSPosed-api-1.0-SNAPSHOT.jar')
|
compileOnly files('libs/LSPosed-api-1.0-SNAPSHOT.jar')
|
||||||
implementation 'com.google.code.gson:gson:2.10.1'
|
implementation 'com.google.code.gson:gson:2.10.1'
|
||||||
implementation 'com.arthenica:ffmpeg-kit-full-gpl:5.1.LTS'
|
implementation 'com.arthenica:ffmpeg-kit-full-gpl:5.1.LTS'
|
||||||
|
implementation 'org.osmdroid:osmdroid-android:6.1.16'
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,10 @@
|
|||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".features.impl.ui.menus.MapActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:excludeFromRecents="true" />
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
@ -7,6 +7,7 @@
|
|||||||
"ui": "UI",
|
"ui": "UI",
|
||||||
"extras": "Extras",
|
"extras": "Extras",
|
||||||
"tweaks": "Tweaks",
|
"tweaks": "Tweaks",
|
||||||
|
"location_spoof": "Location Spoof",
|
||||||
"experimental": "Experimental",
|
"experimental": "Experimental",
|
||||||
"debugging": "Debugging"
|
"debugging": "Debugging"
|
||||||
},
|
},
|
||||||
@ -14,7 +15,8 @@
|
|||||||
"action": {
|
"action": {
|
||||||
"clean_cache": "Clean Cache",
|
"clean_cache": "Clean Cache",
|
||||||
"clear_message_logger": "Clear Message Logger",
|
"clear_message_logger": "Clear Message Logger",
|
||||||
"refresh_mappings": "Refresh Mappings"
|
"refresh_mappings": "Refresh Mappings",
|
||||||
|
"open_map": "Pick a location on the map"
|
||||||
},
|
},
|
||||||
|
|
||||||
"property": {
|
"property": {
|
||||||
@ -57,7 +59,10 @@
|
|||||||
"use_download_manager": "Use Android Download Manager",
|
"use_download_manager": "Use Android Download Manager",
|
||||||
"app_passcode": "Set App Passcode",
|
"app_passcode": "Set App Passcode",
|
||||||
"app_lock_on_resume": "App Lock On Resume",
|
"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": {
|
"option": {
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
package me.rhunk.snapenhance.action
|
package me.rhunk.snapenhance.action
|
||||||
|
|
||||||
import me.rhunk.snapenhance.ModContext
|
import me.rhunk.snapenhance.ModContext
|
||||||
|
import me.rhunk.snapenhance.config.ConfigProperty
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
abstract class AbstractAction(
|
abstract class AbstractAction(
|
||||||
val nameKey: String
|
val nameKey: String,
|
||||||
|
val dependsOnProperty: ConfigProperty? = null,
|
||||||
) {
|
) {
|
||||||
lateinit var context: ModContext
|
lateinit var context: ModContext
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -10,5 +10,6 @@ enum class ConfigCategory(
|
|||||||
UI("category.ui"),
|
UI("category.ui"),
|
||||||
EXTRAS("category.extras"),
|
EXTRAS("category.extras"),
|
||||||
TWEAKS("category.tweaks"),
|
TWEAKS("category.tweaks"),
|
||||||
|
LOCATION_SPOOF("category.location_spoof"),
|
||||||
EXPERIMENTAL("category.experimental");
|
EXPERIMENTAL("category.experimental");
|
||||||
}
|
}
|
||||||
|
@ -213,6 +213,25 @@ enum class ConfigProperty(
|
|||||||
),
|
),
|
||||||
NEW_MAP_UI("property.new_map_ui", "description.new_map_ui", ConfigCategory.TWEAKS, ConfigStateValue(false)),
|
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(
|
USE_DOWNLOAD_MANAGER(
|
||||||
"property.use_download_manager",
|
"property.use_download_manager",
|
||||||
"description.use_download_manager",
|
"description.use_download_manager",
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
@ -173,25 +173,33 @@ class SettingsMenu : AbstractMenu() {
|
|||||||
.count().coerceAtLeast(2).toInt()
|
.count().coerceAtLeast(2).toInt()
|
||||||
addView(titleText)
|
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 {
|
context.config.entries().groupBy {
|
||||||
it.key.category
|
it.key.category
|
||||||
}.forEach { (category, value) ->
|
}.forEach { (category, value) ->
|
||||||
addView(createCategoryTitle(viewModel, category.key))
|
addView(createCategoryTitle(viewModel, category.key))
|
||||||
value.forEach {
|
value.forEach {
|
||||||
addView(createPropertyView(viewModel, it.key))
|
addView(createPropertyView(viewModel, it.key))
|
||||||
|
actions.find { pair -> pair.first.dependsOnProperty == it.key }?.let { pair ->
|
||||||
|
addView(pair.second())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addView(createCategoryTitle(viewModel, "category.debugging"))
|
addView(createCategoryTitle(viewModel, "category.debugging"))
|
||||||
|
actions.filter { it.first.dependsOnProperty == null }.forEach {
|
||||||
context.actionManager.getActions().forEach {
|
addView(it.second())
|
||||||
val button = Button(viewModel.context)
|
|
||||||
button.text = context.translation.get(it.nameKey)
|
|
||||||
button.setOnClickListener { _ ->
|
|
||||||
it.run()
|
|
||||||
}
|
|
||||||
ViewAppearanceHelper.applyTheme(viewModel, button)
|
|
||||||
addView(button)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -4,6 +4,7 @@ import me.rhunk.snapenhance.ModContext
|
|||||||
import me.rhunk.snapenhance.action.AbstractAction
|
import me.rhunk.snapenhance.action.AbstractAction
|
||||||
import me.rhunk.snapenhance.action.impl.CleanCache
|
import me.rhunk.snapenhance.action.impl.CleanCache
|
||||||
import me.rhunk.snapenhance.action.impl.ClearMessageLogger
|
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.action.impl.RefreshMappings
|
||||||
import me.rhunk.snapenhance.manager.Manager
|
import me.rhunk.snapenhance.manager.Manager
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
@ -22,6 +23,7 @@ class ActionManager(
|
|||||||
load(CleanCache::class)
|
load(CleanCache::class)
|
||||||
load(ClearMessageLogger::class)
|
load(ClearMessageLogger::class)
|
||||||
load(RefreshMappings::class)
|
load(RefreshMappings::class)
|
||||||
|
load(OpenMap::class)
|
||||||
|
|
||||||
actions.values.forEach(AbstractAction::init)
|
actions.values.forEach(AbstractAction::init)
|
||||||
}
|
}
|
||||||
|
@ -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.AutoSave
|
||||||
import me.rhunk.snapenhance.features.impl.extras.DisableVideoLengthRestriction
|
import me.rhunk.snapenhance.features.impl.extras.DisableVideoLengthRestriction
|
||||||
import me.rhunk.snapenhance.features.impl.extras.GalleryMediaSendOverride
|
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.MediaQualityLevelOverride
|
||||||
import me.rhunk.snapenhance.features.impl.extras.Notifications
|
import me.rhunk.snapenhance.features.impl.extras.Notifications
|
||||||
import me.rhunk.snapenhance.features.impl.extras.SnapchatPlus
|
import me.rhunk.snapenhance.features.impl.extras.SnapchatPlus
|
||||||
@ -73,6 +74,8 @@ class FeatureManager(private val context: ModContext) : Manager {
|
|||||||
register(MediaQualityLevelOverride::class)
|
register(MediaQualityLevelOverride::class)
|
||||||
register(MeoPasscodeBypass::class)
|
register(MeoPasscodeBypass::class)
|
||||||
register(AppPasscode::class)
|
register(AppPasscode::class)
|
||||||
|
register(LocationSpoofer::class)
|
||||||
|
|
||||||
|
|
||||||
initializeFeatures()
|
initializeFeatures()
|
||||||
}
|
}
|
||||||
|
25
app/src/main/res/layout/map.xml
Normal file
25
app/src/main/res/layout/map.xml
Normal 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>
|
Loading…
x
Reference in New Issue
Block a user