multiple fixes
This commit is contained in:
3
announcements.txt
Normal file
3
announcements.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# PurrfectSnap Announcements
|
||||||
|
|
||||||
|
- Welcome to PurrfectSnap announcements.
|
||||||
@@ -149,6 +149,7 @@ class HomeRootSection : Routes.Route() {
|
|||||||
private val changelogClient by lazy { OkHttpClient() }
|
private val changelogClient by lazy { OkHttpClient() }
|
||||||
private val changelogStableUrl = "https://raw.githubusercontent.com/particle-box/PurrfectSnap/dev/changelogs-stable.txt"
|
private val changelogStableUrl = "https://raw.githubusercontent.com/particle-box/PurrfectSnap/dev/changelogs-stable.txt"
|
||||||
private val changelogPrereleaseUrl = "https://raw.githubusercontent.com/particle-box/PurrfectSnap/dev/changelogs-prerelease.txt"
|
private val changelogPrereleaseUrl = "https://raw.githubusercontent.com/particle-box/PurrfectSnap/dev/changelogs-prerelease.txt"
|
||||||
|
private val announcementsUrl = "https://raw.githubusercontent.com/particle-box/PurrfectSnap/dev/announcements.txt"
|
||||||
|
|
||||||
private val heroGradientColors = listOf(
|
private val heroGradientColors = listOf(
|
||||||
Color(0xFF5C4B99),
|
Color(0xFF5C4B99),
|
||||||
@@ -748,6 +749,10 @@ class HomeRootSection : Routes.Route() {
|
|||||||
var changelogError by remember { mutableStateOf<String?>(null) }
|
var changelogError by remember { mutableStateOf<String?>(null) }
|
||||||
var changelogText by remember { mutableStateOf<String?>(null) }
|
var changelogText by remember { mutableStateOf<String?>(null) }
|
||||||
var changelogVersion by remember { mutableStateOf<String?>(null) }
|
var changelogVersion by remember { mutableStateOf<String?>(null) }
|
||||||
|
var showAnnouncementsDialog by remember { mutableStateOf(false) }
|
||||||
|
var announcementsLoading by remember { mutableStateOf(false) }
|
||||||
|
var announcementsError by remember { mutableStateOf<String?>(null) }
|
||||||
|
var announcementsText by remember { mutableStateOf<String?>(null) }
|
||||||
|
|
||||||
val handleUpdateAction: () -> Unit = {
|
val handleUpdateAction: () -> Unit = {
|
||||||
latestUpdate?.let { latest ->
|
latestUpdate?.let { latest ->
|
||||||
@@ -827,6 +832,31 @@ class HomeRootSection : Routes.Route() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun loadAnnouncements() {
|
||||||
|
if (announcementsText != null) return
|
||||||
|
announcementsLoading = true
|
||||||
|
announcementsError = null
|
||||||
|
coroutineScope.launch(Dispatchers.IO) {
|
||||||
|
runCatching {
|
||||||
|
changelogClient.newCall(Request.Builder().url(announcementsUrl).build()).execute().use { response ->
|
||||||
|
if (!response.isSuccessful) throw IllegalStateException("Failed to fetch announcements (${response.code})")
|
||||||
|
val body = response.body?.string() ?: throw IllegalStateException("Empty announcements body")
|
||||||
|
body.trim()
|
||||||
|
}
|
||||||
|
}.onSuccess { text ->
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
announcementsText = text
|
||||||
|
announcementsLoading = false
|
||||||
|
}
|
||||||
|
}.onFailure { error ->
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
announcementsError = error.message ?: "Failed to load announcements"
|
||||||
|
announcementsLoading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val onUpdateButtonClick: () -> Unit = {
|
val onUpdateButtonClick: () -> Unit = {
|
||||||
latestUpdate?.let {
|
latestUpdate?.let {
|
||||||
showChangelogDialog = true
|
showChangelogDialog = true
|
||||||
@@ -859,6 +889,13 @@ class HomeRootSection : Routes.Route() {
|
|||||||
horizontalArrangement = Arrangement.spacedBy(10.dp),
|
horizontalArrangement = Arrangement.spacedBy(10.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
|
TopBarActionChip(
|
||||||
|
icon = Icons.Filled.Info,
|
||||||
|
label = "Announcements"
|
||||||
|
) {
|
||||||
|
showAnnouncementsDialog = true
|
||||||
|
loadAnnouncements()
|
||||||
|
}
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
HomeActionChips()
|
HomeActionChips()
|
||||||
}
|
}
|
||||||
@@ -1143,6 +1180,68 @@ class HomeRootSection : Routes.Route() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (showAnnouncementsDialog) {
|
||||||
|
AestheticDialog(
|
||||||
|
onDismissRequest = { showAnnouncementsDialog = false },
|
||||||
|
title = "Announcements",
|
||||||
|
text = "",
|
||||||
|
icon = Icons.Filled.Info,
|
||||||
|
confirmButtonText = "Close",
|
||||||
|
onConfirm = { showAnnouncementsDialog = false },
|
||||||
|
dismissButtonText = "Dismiss",
|
||||||
|
onDismiss = { showAnnouncementsDialog = false },
|
||||||
|
confirmEnabled = !announcementsLoading,
|
||||||
|
customContent = {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.heightIn(min = 120.dp, max = 340.dp)
|
||||||
|
.verticalScroll(rememberScrollState()),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(10.dp)
|
||||||
|
) {
|
||||||
|
when {
|
||||||
|
announcementsLoading -> {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.Center,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
CircularProgressIndicator(
|
||||||
|
modifier = Modifier.size(28.dp),
|
||||||
|
strokeWidth = 3.dp,
|
||||||
|
color = Color.White
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(10.dp))
|
||||||
|
Text(
|
||||||
|
text = "Loading announcements...",
|
||||||
|
color = Color.White,
|
||||||
|
fontWeight = FontWeight.SemiBold
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
announcementsError != null -> {
|
||||||
|
Text(
|
||||||
|
text = announcementsError ?: "Failed to load announcements",
|
||||||
|
color = MaterialTheme.colorScheme.error,
|
||||||
|
fontWeight = FontWeight.SemiBold
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
Text(
|
||||||
|
text = announcementsText ?: "Announcements not available",
|
||||||
|
color = PurrfectPalette.textPrimary,
|
||||||
|
fontSize = 14.sp,
|
||||||
|
lineHeight = 20.sp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (showQuickActionsMenu) {
|
if (showQuickActionsMenu) {
|
||||||
QuickActionsDialog(
|
QuickActionsDialog(
|
||||||
quickActions = cards,
|
quickActions = cards,
|
||||||
|
|||||||
@@ -339,7 +339,7 @@ class PatchSnapchatScreen : SetupScreen() {
|
|||||||
textAlign = TextAlign.Start
|
textAlign = TextAlign.Start
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = "Fix: Download and install JingMatrix LSPatch, then patch Snapchat 13.64.0.52 in Integrated mode. Select Embed Modules and embed the PurrfectSnap APK. Then choose Skip auto setup during PurrfectSnap setup to skip Auto Patcher.",
|
text = "Fix: Download and install JingMatrix LSPatch, then patch Snapchat 13.65.1.0 to 13.71.0.51 in Integrated mode. Select Embed Modules and embed the PurrfectSnap APK. Then choose Skip auto setup during PurrfectSnap setup to skip Auto Patcher.",
|
||||||
style = bodyStyle,
|
style = bodyStyle,
|
||||||
textAlign = TextAlign.Start
|
textAlign = TextAlign.Start
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,40 +4,36 @@ import android.content.Intent
|
|||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.compose.foundation.BorderStroke
|
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.BorderStroke
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.border
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
|
||||||
import androidx.compose.foundation.lazy.items
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.text.BasicText
|
import androidx.compose.foundation.text.BasicText
|
||||||
import androidx.compose.foundation.text.BasicTextField
|
import androidx.compose.foundation.text.BasicTextField
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
|
||||||
import androidx.compose.material.icons.filled.People
|
import androidx.compose.material.icons.filled.People
|
||||||
import androidx.compose.material.icons.filled.Search
|
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.Surface
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.*
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.draw.alpha
|
import androidx.compose.ui.draw.alpha
|
||||||
import androidx.compose.ui.graphics.Brush
|
import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.SolidColor
|
import androidx.compose.ui.graphics.SolidColor
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
|
import androidx.compose.material.icons.filled.Search
|
||||||
|
import androidx.compose.material3.*
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.asImageBitmap
|
import androidx.compose.ui.graphics.asImageBitmap
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
@@ -48,30 +44,17 @@ import me.eternal.purrfectsnap.common.data.FriendLinkType
|
|||||||
import me.eternal.purrfectsnap.common.ui.createComposeAlertDialog
|
import me.eternal.purrfectsnap.common.ui.createComposeAlertDialog
|
||||||
import me.eternal.purrfectsnap.core.action.AbstractAction
|
import me.eternal.purrfectsnap.core.action.AbstractAction
|
||||||
import me.eternal.purrfectsnap.core.event.events.impl.ActivityResultEvent
|
import me.eternal.purrfectsnap.core.event.events.impl.ActivityResultEvent
|
||||||
import me.eternal.purrfectsnap.common.util.snap.BitmojiSelfie
|
|
||||||
import me.eternal.purrfectsnap.common.util.snap.RemoteMediaResolver
|
|
||||||
import me.eternal.purrfectsnap.core.features.impl.experiments.AddFriendSourceSpoof
|
import me.eternal.purrfectsnap.core.features.impl.experiments.AddFriendSourceSpoof
|
||||||
import me.eternal.purrfectsnap.core.features.impl.messaging.Messaging
|
import me.eternal.purrfectsnap.core.features.impl.messaging.Messaging
|
||||||
|
import me.eternal.purrfectsnap.core.util.EvictingMap
|
||||||
import me.eternal.purrfectsnap.core.wrapper.impl.Snapchatter
|
import me.eternal.purrfectsnap.core.wrapper.impl.Snapchatter
|
||||||
|
import me.eternal.purrfectsnap.common.util.snap.BitmojiSelfie
|
||||||
|
import me.eternal.purrfectsnap.common.util.snap.RemoteMediaResolver
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
class ManageFriendList : AbstractAction() {
|
class ManageFriendList : AbstractAction() {
|
||||||
companion object {
|
|
||||||
private var openSuggestedOnLaunch = false
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
fun requestOpenSuggestedOnLaunch() {
|
|
||||||
openSuggestedOnLaunch = true
|
|
||||||
}
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
private fun consumeOpenSuggestedOnLaunch(): Boolean {
|
|
||||||
val shouldOpen = openSuggestedOnLaunch
|
|
||||||
openSuggestedOnLaunch = false
|
|
||||||
return shouldOpen
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val translation by lazy { context.translation.getCategory("friend_list") }
|
private val translation by lazy { context.translation.getCategory("friend_list") }
|
||||||
private val dialogBackground = Brush.verticalGradient(
|
private val dialogBackground = Brush.verticalGradient(
|
||||||
listOf(
|
listOf(
|
||||||
@@ -92,211 +75,7 @@ class ManageFriendList : AbstractAction() {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
private var pendingPickerAction: Pair<Int, (data: Uri) -> Unit>? = null
|
private var pendingPickerAction: Pair<Int, (data: Uri) -> Unit>? = null
|
||||||
|
private val uuidRegex = Regex("[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}")
|
||||||
private val uuidRegex by lazy {
|
|
||||||
Regex("[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}")
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun loadStaticDefaultField(classLoader: ClassLoader, className: String, fieldName: String): Any? {
|
|
||||||
return try {
|
|
||||||
val clazz = classLoader.loadClass(className)
|
|
||||||
try {
|
|
||||||
val field = clazz.getDeclaredField(fieldName)
|
|
||||||
field.isAccessible = true
|
|
||||||
field.get(null)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
clazz.declaredFields
|
|
||||||
.filter { java.lang.reflect.Modifier.isStatic(it.modifiers) }
|
|
||||||
.firstOrNull()
|
|
||||||
?.apply { isAccessible = true }
|
|
||||||
?.get(null)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
context.log.warn("Could not load $className: ${e.message}")
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addFriend(userId: String) {
|
|
||||||
val friendRelationshipChangerInstance = context.feature(AddFriendSourceSpoof::class).friendRelationshipChangerInstance
|
|
||||||
if (friendRelationshipChangerInstance == null) {
|
|
||||||
context.log.error("friendRelationshipChangerInstance is null")
|
|
||||||
context.longToast("Failed to add friend: FriendRelationshipChanger instance not available")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
runCatching {
|
|
||||||
val classLoader = context.androidContext.classLoader
|
|
||||||
|
|
||||||
val classNamesToTry = listOf("EnumC3559qC", "qC", "com.snapchat.android.EnumC3559qC", "LC", "EnumC0886LC", "EnumC1539TC", "TC")
|
|
||||||
|
|
||||||
val enumC3559qCClass = classNamesToTry.firstNotNullOfOrNull { className ->
|
|
||||||
try {
|
|
||||||
classLoader.loadClass(className).takeIf { it.isEnum }?.also {
|
|
||||||
context.log.verbose("Successfully loaded enum class: $className")
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (enumC3559qCClass == null) {
|
|
||||||
context.log.error("Friend source enum class not found after trying: ${classNamesToTry.joinToString()}")
|
|
||||||
context.longToast("Failed to add friend: Required enum class not found")
|
|
||||||
return@runCatching
|
|
||||||
}
|
|
||||||
|
|
||||||
val enumConstants = enumC3559qCClass.enumConstants
|
|
||||||
?: enumC3559qCClass.getMethod("values").invoke(null) as? Array<*>
|
|
||||||
?: run {
|
|
||||||
context.log.error("Could not retrieve enum constants from ${enumC3559qCClass.name}")
|
|
||||||
context.longToast("Failed to add friend: Enum constants not accessible")
|
|
||||||
return@runCatching
|
|
||||||
}
|
|
||||||
|
|
||||||
context.log.verbose("Found ${enumConstants.size} enum constants")
|
|
||||||
|
|
||||||
val addedByUsername = enumConstants.firstOrNull { it.toString() == "ADDED_BY_USERNAME" }
|
|
||||||
?: enumConstants.firstOrNull { it.toString().contains("USERNAME", ignoreCase = true) }
|
|
||||||
?: enumConstants.firstOrNull()
|
|
||||||
?: run {
|
|
||||||
context.log.error("No enum constants available")
|
|
||||||
context.longToast("Failed to add friend: No valid enum constant found")
|
|
||||||
return@runCatching
|
|
||||||
}
|
|
||||||
|
|
||||||
context.log.verbose("Using enum constant: $addedByUsername")
|
|
||||||
|
|
||||||
val sQ7Default = loadStaticDefaultField(classLoader, "sQ7", "f288251e0")
|
|
||||||
val zQ7Default = loadStaticDefaultField(classLoader, "ZQ7", "f159958F0")
|
|
||||||
|
|
||||||
val iBgClass = try {
|
|
||||||
classLoader.loadClass("iBg")
|
|
||||||
} catch (e: Exception) {
|
|
||||||
context.log.error("Could not load iBg class: ${e.message}")
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (iBgClass == null) {
|
|
||||||
context.log.error("iBg class not found")
|
|
||||||
return@runCatching
|
|
||||||
}
|
|
||||||
|
|
||||||
val m51157aMethod = iBgClass.declaredMethods.firstOrNull { it.name == "m51157a" }
|
|
||||||
?: iBgClass.methods.firstOrNull { it.name == "m51157a" }
|
|
||||||
?: iBgClass.declaredMethods.firstOrNull { method ->
|
|
||||||
java.lang.reflect.Modifier.isStatic(method.modifiers) &&
|
|
||||||
method.parameterTypes.size == 14 &&
|
|
||||||
method.parameterTypes[0].isAssignableFrom(friendRelationshipChangerInstance.javaClass) &&
|
|
||||||
method.parameterTypes[1] == String::class.java &&
|
|
||||||
method.parameterTypes[2] == enumC3559qCClass
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m51157aMethod == null) {
|
|
||||||
context.log.error("Could not find iBg.m51157a method")
|
|
||||||
context.log.error("Available static methods with 14 params:")
|
|
||||||
iBgClass.declaredMethods.filter {
|
|
||||||
java.lang.reflect.Modifier.isStatic(it.modifiers) && it.parameterTypes.size == 14
|
|
||||||
}.forEach { method ->
|
|
||||||
context.log.error(" ${method.name}: ${method.parameterTypes.joinToString { it.simpleName }}")
|
|
||||||
}
|
|
||||||
return@runCatching
|
|
||||||
}
|
|
||||||
m51157aMethod.isAccessible = true
|
|
||||||
|
|
||||||
try {
|
|
||||||
m51157aMethod.invoke(
|
|
||||||
null,
|
|
||||||
friendRelationshipChangerInstance,
|
|
||||||
userId,
|
|
||||||
addedByUsername,
|
|
||||||
sQ7Default,
|
|
||||||
zQ7Default,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
4064
|
|
||||||
)?.also { result ->
|
|
||||||
result.javaClass.methods
|
|
||||||
.find { it.name == "subscribe" && it.parameterCount == 0 }
|
|
||||||
?.apply { isAccessible = true }
|
|
||||||
?.invoke(result)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
context.log.error("Exception during method invocation: ${e.javaClass.name}: ${e.message}")
|
|
||||||
e.cause?.let { cause ->
|
|
||||||
context.log.error("Cause: ${cause.javaClass.name}: ${cause.message}")
|
|
||||||
cause.stackTrace?.take(5)?.forEach {
|
|
||||||
context.log.error(" at $it")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
}.onFailure {
|
|
||||||
context.log.error("Failed to add friend $userId", it)
|
|
||||||
context.longToast("Failed to add friend: ${it.message}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun loadSuggestedFriends(
|
|
||||||
coroutineScope: CoroutineScope,
|
|
||||||
onLoaded: (List<String>) -> Unit
|
|
||||||
) {
|
|
||||||
coroutineScope.launch(Dispatchers.IO) {
|
|
||||||
val blacklist = getUserIdBlacklist()
|
|
||||||
val suggestedFriends = context.database.getAllFriends()
|
|
||||||
.filter { it.userId !in blacklist && it.friendLinkType == FriendLinkType.SUGGESTED.value }
|
|
||||||
.sortedByDescending { it.addedTimestamp }
|
|
||||||
.mapNotNull { it.userId }
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
onLoaded(suggestedFriends)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onActivityCreate() {
|
|
||||||
context.event.subscribe(ActivityResultEvent::class) { event ->
|
|
||||||
if (event.requestCode == pendingPickerAction?.first) {
|
|
||||||
val pendingAction = pendingPickerAction ?: return@subscribe
|
|
||||||
this.pendingPickerAction = null
|
|
||||||
event.canceled = true
|
|
||||||
pendingAction.second(event.intent.data!!)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun exportFriends(
|
|
||||||
userIds: List<String>
|
|
||||||
) {
|
|
||||||
pendingPickerAction = Random.nextInt(0, 65535) to { data ->
|
|
||||||
context.androidContext.contentResolver.openOutputStream(data).use { output ->
|
|
||||||
output?.bufferedWriter()?.use { writer ->
|
|
||||||
userIds.forEach {
|
|
||||||
writer.write(it)
|
|
||||||
writer.newLine()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
context.longToast("Exported ${userIds.size} friends!")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
context.mainActivity?.startActivityForResult(
|
|
||||||
Intent.createChooser(
|
|
||||||
Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
|
|
||||||
type = "text/plain"
|
|
||||||
putExtra(Intent.EXTRA_TITLE, "my_friends.txt")
|
|
||||||
},
|
|
||||||
"Select a location to save the file"
|
|
||||||
),
|
|
||||||
pendingPickerAction!!.first
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val userIdToSnapchatter = mutableMapOf<String, Snapchatter>()
|
|
||||||
|
|
||||||
private fun getUserIdBlacklist() = arrayOf(
|
private fun getUserIdBlacklist() = arrayOf(
|
||||||
context.database.myUserId,
|
context.database.myUserId,
|
||||||
@@ -304,21 +83,131 @@ class ManageFriendList : AbstractAction() {
|
|||||||
"84ee8839-3911-492d-8b94-72dd80f3713a",
|
"84ee8839-3911-492d-8b94-72dd80f3713a",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private fun addFriend(userId: String) {
|
||||||
|
val friendRelationshipChangerInstance = context.feature(AddFriendSourceSpoof::class).friendRelationshipChangerInstance
|
||||||
|
?: run {
|
||||||
|
context.longToast("Failed to add friend: FriendRelationshipChanger instance not available")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
runCatching {
|
||||||
|
val classLoader = context.androidContext.classLoader
|
||||||
|
val friendRelationshipChangerClass = friendRelationshipChangerInstance.javaClass
|
||||||
|
|
||||||
|
// Helper function to find static field by trying multiple field names
|
||||||
|
fun findStaticField(clazz: Class<*>, fieldNames: List<String>): Any? {
|
||||||
|
fieldNames.forEach { fieldName ->
|
||||||
|
runCatching {
|
||||||
|
clazz.getDeclaredField(fieldName).apply { isAccessible = true }.get(null)
|
||||||
|
}.getOrNull()?.let { return it }
|
||||||
|
}
|
||||||
|
// Fallback: find any static field of the same type
|
||||||
|
return clazz.declaredFields.firstOrNull { field ->
|
||||||
|
java.lang.reflect.Modifier.isStatic(field.modifiers) && field.type == clazz
|
||||||
|
}?.let { field ->
|
||||||
|
runCatching {
|
||||||
|
field.isAccessible = true
|
||||||
|
field.get(null)?.takeIf { it.javaClass == clazz }
|
||||||
|
}.getOrNull()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load F9l class
|
||||||
|
val f9lClass = classLoader.loadClass("F9l")
|
||||||
|
|
||||||
|
// Find the add friend method by matching signature
|
||||||
|
val method = (f9lClass.declaredMethods + f9lClass.methods).firstOrNull { method ->
|
||||||
|
if (!java.lang.reflect.Modifier.isStatic(method.modifiers) || method.parameterTypes.size != 14) return@firstOrNull false
|
||||||
|
|
||||||
|
val params = method.parameterTypes
|
||||||
|
params[0].isAssignableFrom(friendRelationshipChangerClass) &&
|
||||||
|
params[1] == String::class.java &&
|
||||||
|
params[2].isEnum
|
||||||
|
} ?: return@runCatching context.log.error("Could not find F9l.m8344a method")
|
||||||
|
|
||||||
|
// Extract classes from method signature
|
||||||
|
val enumClass = method.parameterTypes[2]
|
||||||
|
val tZ7Class = method.parameterTypes[3]
|
||||||
|
val g08Class = method.parameterTypes[4]
|
||||||
|
|
||||||
|
// Get enum constant for USERNAME
|
||||||
|
val enumConstants = enumClass.enumConstants ?: enumClass.getMethod("values").invoke(null) as? Array<*>
|
||||||
|
?: return@runCatching context.log.error("Could not get enum constants")
|
||||||
|
val addedByUsername = enumConstants.firstOrNull { it.toString().contains("USERNAME", ignoreCase = true) }
|
||||||
|
?: return@runCatching context.log.error("Could not find ADDED_BY_USERNAME enum")
|
||||||
|
|
||||||
|
// Get static field instances
|
||||||
|
val tZ7Default = findStaticField(tZ7Class, listOf("f301161j0", "f301158a", "f301165n0", "f301160c", "f301171t"))
|
||||||
|
?: return@runCatching context.log.error("Could not find tZ7 static field")
|
||||||
|
|
||||||
|
val g08Default = findStaticField(g08Class, listOf("f211750K0", "f211807x1", "f211805w1", "f211774f1", "f211775g1", "f211760U0", "f211783l1", "f211785m1", "f211787n1", "f211772d1"))
|
||||||
|
?: return@runCatching context.log.error("Could not find g08 static field")
|
||||||
|
|
||||||
|
// Invoke method with 14 parameters
|
||||||
|
method.isAccessible = true
|
||||||
|
val result = method.invoke(
|
||||||
|
null,
|
||||||
|
friendRelationshipChangerInstance,
|
||||||
|
userId,
|
||||||
|
addedByUsername,
|
||||||
|
tZ7Default,
|
||||||
|
g08Default,
|
||||||
|
null, null, null, null, null, // String params 6-10
|
||||||
|
null, // InteractionPlacementInfo
|
||||||
|
null, // String str7
|
||||||
|
null, // Integer num
|
||||||
|
4064 // int flags
|
||||||
|
)
|
||||||
|
|
||||||
|
// Subscribe to the Completable result
|
||||||
|
result?.javaClass?.methods?.firstOrNull {
|
||||||
|
it.name == "subscribe" && it.parameterCount == 0
|
||||||
|
}?.let { subscribeMethod ->
|
||||||
|
subscribeMethod.isAccessible = true
|
||||||
|
subscribeMethod.invoke(result)
|
||||||
|
}
|
||||||
|
}.onFailure {
|
||||||
|
context.log.error("Failed to add friend $userId", it)
|
||||||
|
context.longToast("Failed to add friend: ${it.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityCreate() {
|
||||||
|
context.event.subscribe(ActivityResultEvent::class) { event ->
|
||||||
|
pendingPickerAction?.takeIf { it.first == event.requestCode }?.let {
|
||||||
|
pendingPickerAction = null
|
||||||
|
event.canceled = true
|
||||||
|
it.second(event.intent.data!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun exportFriends(userIds: List<String>) {
|
||||||
|
pendingPickerAction = Random.nextInt(0, 65535) to { data ->
|
||||||
|
context.androidContext.contentResolver.openOutputStream(data)?.bufferedWriter()?.use { writer ->
|
||||||
|
userIds.forEach { writer.write(it); writer.newLine() }
|
||||||
|
}
|
||||||
|
context.longToast("Exported ${userIds.size} friends!")
|
||||||
|
}
|
||||||
|
context.mainActivity?.startActivityForResult(
|
||||||
|
Intent.createChooser(Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
|
||||||
|
type = "text/plain"
|
||||||
|
putExtra(Intent.EXTRA_TITLE, "my_friends.txt")
|
||||||
|
}, "Select a location to save the file"),
|
||||||
|
pendingPickerAction!!.first
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val userIdToSnapchatter = mutableMapOf<String, Snapchatter>()
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ManagerDialog() {
|
private fun ManagerDialog() {
|
||||||
val pendingFriendRequests = remember { mutableStateMapOf<String, Job>() }
|
val pendingFriendRequests = remember { mutableStateMapOf<String, Job>() }
|
||||||
var fetchedFriends by remember { mutableStateOf<List<String>?>(null) }
|
var fetchedFriends by remember { mutableStateOf<List<String>?>(null) }
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
val openSuggestedOnLaunch = remember { consumeOpenSuggestedOnLaunch() }
|
val bitmojiCache = remember { EvictingMap<String, Bitmap>(50) }
|
||||||
val bitmojiCache = remember { me.eternal.purrfectsnap.core.util.EvictingMap<String, Bitmap>(50) }
|
|
||||||
val noBitmojiBitmap = remember { BitmapFactory.decodeResource(context.resources, android.R.drawable.ic_menu_report_image).asImageBitmap() }
|
val noBitmojiBitmap = remember { BitmapFactory.decodeResource(context.resources, android.R.drawable.ic_menu_report_image).asImageBitmap() }
|
||||||
|
|
||||||
LaunchedEffect(openSuggestedOnLaunch) {
|
|
||||||
if (openSuggestedOnLaunch) {
|
|
||||||
loadSuggestedFriends(coroutineScope) { fetchedFriends = it }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@@ -425,50 +314,38 @@ class ManageFriendList : AbstractAction() {
|
|||||||
) {
|
) {
|
||||||
pendingPickerAction = Random.nextInt(0, 65535) to { data ->
|
pendingPickerAction = Random.nextInt(0, 65535) to { data ->
|
||||||
runCatching {
|
runCatching {
|
||||||
fetchedFriends = null
|
fetchedFriends = context.androidContext.contentResolver.openInputStream(data)?.bufferedReader()?.readLines()?.filter { it.matches(uuidRegex) }?.map { it.trim() }?.toMutableList() ?: mutableListOf()
|
||||||
context.androidContext.contentResolver.openInputStream(data).use { input ->
|
|
||||||
fetchedFriends = input?.bufferedReader()?.readLines()?.filter {
|
|
||||||
it.matches(uuidRegex)
|
|
||||||
}?.map { it.trim() }?.toMutableList() ?: mutableListOf()
|
|
||||||
}
|
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
context.log.error("Failed to import friends", it)
|
context.log.error("Failed to import friends", it)
|
||||||
context.longToast("Failed to import friends: ${it.message}")
|
context.longToast("Failed to import friends: ${it.message}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
context.mainActivity?.startActivityForResult(
|
context.mainActivity?.startActivityForResult(Intent.createChooser(Intent(Intent.ACTION_GET_CONTENT).apply { type = "*/*" }, "Select a file"), pendingPickerAction!!.first)
|
||||||
Intent.createChooser(
|
|
||||||
Intent(Intent.ACTION_GET_CONTENT).apply { type = "*/*" },
|
|
||||||
"Select a file"
|
|
||||||
),
|
|
||||||
pendingPickerAction!!.first
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PrimaryButton(
|
PrimaryButton(
|
||||||
text = "Load Suggested Friends",
|
text = translation.get("load_suggested_friends"),
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
) {
|
) {
|
||||||
loadSuggestedFriends(coroutineScope) { fetchedFriends = it }
|
coroutineScope.launch(Dispatchers.IO) {
|
||||||
|
val blacklist = getUserIdBlacklist()
|
||||||
|
val suggestedFriends = context.database.getAllFriends().filter { it.userId !in blacklist && it.friendLinkType == FriendLinkType.SUGGESTED.value }.sortedByDescending { it.addedTimestamp }.mapNotNull { it.userId }
|
||||||
|
withContext(Dispatchers.Main) { fetchedFriends = suggestedFriends }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var searchQuery by remember { mutableStateOf("") }
|
var searchQuery by remember { mutableStateOf("") }
|
||||||
|
|
||||||
val filteredFriends = remember(fetchedFriends, searchQuery) {
|
val filteredFriends = remember(fetchedFriends, searchQuery) {
|
||||||
val friends = fetchedFriends ?: emptyList()
|
val friends = fetchedFriends ?: emptyList()
|
||||||
if (searchQuery.isBlank()) {
|
val sorted = { list: List<String> -> list.sortedByDescending { context.database.getFriendInfo(it)?.addedTimestamp ?: 0L } }
|
||||||
friends.sortedByDescending { context.database.getFriendInfo(it)?.addedTimestamp ?: 0L }
|
if (searchQuery.isBlank()) sorted(friends) else sorted(friends.filter { userId ->
|
||||||
} else {
|
val info = context.database.getFriendInfo(userId)
|
||||||
friends.filter { userId ->
|
info?.mutableUsername?.contains(searchQuery, ignoreCase = true) == true || info?.displayName?.contains(searchQuery, ignoreCase = true) == true || userId.contains(searchQuery, ignoreCase = true)
|
||||||
val friendInfo = context.database.getFriendInfo(userId)
|
})
|
||||||
friendInfo?.mutableUsername?.contains(searchQuery, ignoreCase = true) == true ||
|
|
||||||
friendInfo?.displayName?.contains(searchQuery, ignoreCase = true) == true ||
|
|
||||||
userId.contains(searchQuery, ignoreCase = true)
|
|
||||||
}.sortedByDescending { context.database.getFriendInfo(it)?.addedTimestamp ?: 0L }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@@ -511,7 +388,7 @@ class ManageFriendList : AbstractAction() {
|
|||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.size(46.dp))
|
Spacer(modifier = Modifier.size(46.dp))
|
||||||
}
|
}
|
||||||
|
|
||||||
BasicTextField(
|
BasicTextField(
|
||||||
value = searchQuery,
|
value = searchQuery,
|
||||||
onValueChange = { searchQuery = it },
|
onValueChange = { searchQuery = it },
|
||||||
@@ -572,64 +449,52 @@ class ManageFriendList : AbstractAction() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
items(filteredFriends) { userId ->
|
items(filteredFriends) { userId ->
|
||||||
val friendInfo = remember(userId) { context.database.getFriendInfo(userId) }
|
var friendInfo by remember(userId) { mutableStateOf(context.database.getFriendInfo(userId)) }
|
||||||
val linkType = remember(friendInfo) {
|
var friendLinkType by remember(userId) { mutableStateOf(friendInfo?.let { FriendLinkType.fromValue(it.friendLinkType) }) }
|
||||||
friendInfo?.friendLinkType?.let { FriendLinkType.fromValue(it) }
|
|
||||||
}
|
val isActuallyAdded = friendInfo?.let { info ->
|
||||||
val isActuallyAdded = remember(friendInfo, linkType) {
|
friendLinkType != null && info.addedTimestamp > 0 &&
|
||||||
friendInfo != null && linkType != null && friendInfo.addedTimestamp > 0 &&
|
(friendLinkType == FriendLinkType.MUTUAL || friendLinkType == FriendLinkType.OUTGOING)
|
||||||
(linkType == FriendLinkType.MUTUAL || linkType == FriendLinkType.OUTGOING)
|
} ?: false
|
||||||
}
|
|
||||||
|
|
||||||
var friendSnapchatter by remember(userId) { mutableStateOf<Snapchatter?>(null) }
|
var friendSnapchatter by remember(userId) { mutableStateOf<Snapchatter?>(null) }
|
||||||
var friendLinkType by remember(userId) { mutableStateOf(linkType) }
|
|
||||||
var actuallyAdded by remember(userId) { mutableStateOf(isActuallyAdded) }
|
|
||||||
|
|
||||||
var bitmojiBitmap by remember(userId, friendInfo?.bitmojiAvatarId) {
|
|
||||||
mutableStateOf(friendInfo?.bitmojiAvatarId?.let { bitmojiCache[it] })
|
|
||||||
}
|
|
||||||
|
|
||||||
LaunchedEffect(userId) {
|
LaunchedEffect(userId) {
|
||||||
if (friendSnapchatter == null && !userIdToSnapchatter.containsKey(userId)) {
|
friendSnapchatter = userIdToSnapchatter[userId] ?: run {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
context.feature(Messaging::class).fetchSnapchatterInfos(listOf(userId)).firstOrNull()?.let {
|
context.feature(Messaging::class).fetchSnapchatterInfos(listOf(userId)).firstOrNull()?.also {
|
||||||
userIdToSnapchatter[userId] = it
|
userIdToSnapchatter[userId] = it
|
||||||
friendSnapchatter = it
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
friendSnapchatter = userIdToSnapchatter[userId]
|
|
||||||
}
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
delay(2000)
|
|
||||||
val newLinkType = friendInfo?.friendLinkType?.let { FriendLinkType.fromValue(it) }
|
|
||||||
if (newLinkType != friendLinkType) {
|
|
||||||
friendLinkType = newLinkType
|
|
||||||
}
|
|
||||||
val newActuallyAdded = friendInfo != null && linkType != null && friendInfo.addedTimestamp > 0 &&
|
|
||||||
(linkType == FriendLinkType.MUTUAL || linkType == FriendLinkType.OUTGOING)
|
|
||||||
if (newActuallyAdded != actuallyAdded) {
|
|
||||||
actuallyAdded = newActuallyAdded
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
while (true) {
|
||||||
|
delay(2000)
|
||||||
|
context.database.getFriendInfo(userId)?.let {
|
||||||
|
friendInfo = it
|
||||||
|
FriendLinkType.fromValue(it.friendLinkType)?.let { newType ->
|
||||||
|
if (newType != friendLinkType) friendLinkType = newType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var bitmojiBitmap by remember(userId, friendInfo?.bitmojiAvatarId) {
|
||||||
|
mutableStateOf(friendInfo?.bitmojiAvatarId?.let { bitmojiCache[it] })
|
||||||
|
}
|
||||||
|
|
||||||
LaunchedEffect(userId, friendInfo?.bitmojiAvatarId, friendInfo?.bitmojiSelfieId) {
|
LaunchedEffect(userId, friendInfo?.bitmojiAvatarId, friendInfo?.bitmojiSelfieId) {
|
||||||
if (bitmojiBitmap != null || friendInfo?.bitmojiAvatarId == null || friendInfo?.bitmojiSelfieId == null) return@LaunchedEffect
|
val info = friendInfo ?: return@LaunchedEffect
|
||||||
|
val avatarId = info.bitmojiAvatarId ?: return@LaunchedEffect
|
||||||
withContext(Dispatchers.IO) {
|
val selfieId = info.bitmojiSelfieId ?: return@LaunchedEffect
|
||||||
val bitmojiUrl = BitmojiSelfie.getBitmojiSelfie(
|
if (bitmojiBitmap != null) return@LaunchedEffect
|
||||||
friendInfo.bitmojiSelfieId,
|
BitmojiSelfie.getBitmojiSelfie(selfieId, avatarId, BitmojiSelfie.BitmojiSelfieType.NEW_THREE_D)?.let { url ->
|
||||||
friendInfo.bitmojiAvatarId,
|
withContext(Dispatchers.IO) {
|
||||||
BitmojiSelfie.BitmojiSelfieType.NEW_THREE_D
|
runCatching {
|
||||||
) ?: return@withContext
|
RemoteMediaResolver.downloadMedia(url) { inputStream, _ ->
|
||||||
|
bitmojiCache[avatarId] = BitmapFactory.decodeStream(inputStream).also { bitmojiBitmap = it }
|
||||||
runCatching {
|
|
||||||
RemoteMediaResolver.downloadMedia(bitmojiUrl) { inputStream, length ->
|
|
||||||
val avatarId = friendInfo.bitmojiAvatarId ?: return@downloadMedia
|
|
||||||
bitmojiCache[avatarId] = BitmapFactory.decodeStream(inputStream).also {
|
|
||||||
bitmojiBitmap = it
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -652,7 +517,7 @@ class ManageFriendList : AbstractAction() {
|
|||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier.size(35.dp)
|
modifier = Modifier.size(35.dp)
|
||||||
)
|
)
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
verticalArrangement = Arrangement.spacedBy(6.dp)
|
verticalArrangement = Arrangement.spacedBy(6.dp)
|
||||||
@@ -680,39 +545,38 @@ class ManageFriendList : AbstractAction() {
|
|||||||
color = if (type == FriendLinkType.MUTUAL) Color(0xFF8EF0F3) else Color(0xFFD9D3FF)
|
color = if (type == FriendLinkType.MUTUAL) Color(0xFF8EF0F3) else Color(0xFFD9D3FF)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (friendSnapchatter != null) {
|
friendSnapchatter?.let {
|
||||||
val isPending = pendingFriendRequests.containsKey(userId) && pendingFriendRequests[userId]?.isActive != false
|
|
||||||
val isFollowing = friendLinkType == FriendLinkType.FOLLOWING
|
val isFollowing = friendLinkType == FriendLinkType.FOLLOWING
|
||||||
|
val isPending = pendingFriendRequests[userId]?.isActive == true
|
||||||
|
val canAdd = !isActuallyAdded && !isPending && !isFollowing
|
||||||
|
|
||||||
PrimaryButton(
|
PrimaryButton(
|
||||||
text = when {
|
text = when {
|
||||||
isFollowing -> "Following"
|
isFollowing -> "Following"
|
||||||
actuallyAdded -> context.translation["common.added"]
|
isActuallyAdded -> context.translation["common.added"]
|
||||||
isPending -> translation.get("adding") ?: "Adding..."
|
isPending -> translation.get("adding")
|
||||||
else -> translation.get("add")
|
else -> translation.get("add")
|
||||||
},
|
},
|
||||||
modifier = Modifier.widthIn(min = 110.dp),
|
modifier = Modifier.widthIn(min = 110.dp),
|
||||||
enabled = !actuallyAdded && !isPending && !isFollowing
|
enabled = canAdd
|
||||||
) {
|
) {
|
||||||
if (actuallyAdded || isPending || isFollowing) return@PrimaryButton
|
if (!canAdd) return@PrimaryButton
|
||||||
|
|
||||||
val prevLinkType = friendLinkType
|
val prevLinkType = friendLinkType
|
||||||
addFriend(userId)
|
addFriend(userId)
|
||||||
val job = coroutineScope.launch {
|
pendingFriendRequests[userId] = coroutineScope.launch(Dispatchers.IO) {
|
||||||
withTimeout(10000) {
|
withTimeout(10000) {
|
||||||
while (friendInfo?.friendLinkType?.let { FriendLinkType.fromValue(it) }?.value == prevLinkType?.value) {
|
while (true) {
|
||||||
|
context.database.getFriendInfo(userId)?.let { updated ->
|
||||||
|
FriendLinkType.fromValue(updated.friendLinkType)?.takeIf { it != prevLinkType }?.let {
|
||||||
|
friendInfo = updated
|
||||||
|
friendLinkType = it
|
||||||
|
return@withTimeout
|
||||||
|
}
|
||||||
|
}
|
||||||
delay(500)
|
delay(500)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.apply {
|
}.apply { invokeOnCompletion { pendingFriendRequests.remove(userId) } }
|
||||||
invokeOnCompletion {
|
|
||||||
pendingFriendRequests.remove(userId)
|
|
||||||
friendLinkType = friendInfo?.friendLinkType?.let { FriendLinkType.fromValue(it) }
|
|
||||||
actuallyAdded = isActuallyAdded || (friendInfo != null && linkType != null && friendInfo.addedTimestamp > 0 &&
|
|
||||||
(linkType == FriendLinkType.MUTUAL || linkType == FriendLinkType.OUTGOING))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pendingFriendRequests[userId] = job
|
|
||||||
}
|
}
|
||||||
if (isPending) {
|
if (isPending) {
|
||||||
CircularProgressIndicator(
|
CircularProgressIndicator(
|
||||||
@@ -805,4 +669,4 @@ class ManageFriendList : AbstractAction() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,9 @@ package me.eternal.purrfectsnap.core.features.impl.messaging
|
|||||||
|
|
||||||
import me.eternal.purrfectsnap.common.data.NotificationType
|
import me.eternal.purrfectsnap.common.data.NotificationType
|
||||||
import me.eternal.purrfectsnap.common.util.protobuf.ProtoEditor
|
import me.eternal.purrfectsnap.common.util.protobuf.ProtoEditor
|
||||||
|
import me.eternal.purrfectsnap.common.util.protobuf.ProtoReader
|
||||||
import me.eternal.purrfectsnap.core.event.events.impl.NativeUnaryCallEvent
|
import me.eternal.purrfectsnap.core.event.events.impl.NativeUnaryCallEvent
|
||||||
|
import me.eternal.purrfectsnap.core.event.events.impl.UnaryCallEvent
|
||||||
import me.eternal.purrfectsnap.core.event.events.impl.SendMessageWithContentEvent
|
import me.eternal.purrfectsnap.core.event.events.impl.SendMessageWithContentEvent
|
||||||
import me.eternal.purrfectsnap.core.features.Feature
|
import me.eternal.purrfectsnap.core.features.Feature
|
||||||
import me.eternal.purrfectsnap.core.util.hook.HookStage
|
import me.eternal.purrfectsnap.core.util.hook.HookStage
|
||||||
@@ -12,9 +14,9 @@ class PreventMessageSending : Feature("Prevent message sending") {
|
|||||||
override fun init() {
|
override fun init() {
|
||||||
val preventMessageSending by context.config.messaging.preventMessageSending
|
val preventMessageSending by context.config.messaging.preventMessageSending
|
||||||
|
|
||||||
context.event.subscribe(NativeUnaryCallEvent::class, { preventMessageSending.contains("snap_replay") }) { event ->
|
fun handleUpdateContentMessage(uri: String, buffer: ByteArray): ByteArray? {
|
||||||
if (event.uri != "/messagingcoreservice.MessagingCoreService/UpdateContentMessage") return@subscribe
|
if (uri != "/messagingcoreservice.MessagingCoreService/UpdateContentMessage") return null
|
||||||
event.buffer = ProtoEditor(event.buffer).apply {
|
return ProtoEditor(buffer).apply {
|
||||||
edit(3) {
|
edit(3) {
|
||||||
// replace replayed to read receipt
|
// replace replayed to read receipt
|
||||||
if (firstOrNull(13) != null) {
|
if (firstOrNull(13) != null) {
|
||||||
@@ -25,6 +27,36 @@ class PreventMessageSending : Feature("Prevent message sending") {
|
|||||||
}.toByteArray()
|
}.toByteArray()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun handleCreateContentMessage(uri: String, buffer: ByteArray): Boolean {
|
||||||
|
if (uri != "/messagingcoreservice.MessagingCoreService/CreateContentMessage") return false
|
||||||
|
val reader = ProtoReader(buffer)
|
||||||
|
// check for missed audio/video call in MessageContent (field 4)
|
||||||
|
val contentReader = reader.followPath(4) ?: return false
|
||||||
|
|
||||||
|
// try both possible field IDs based on SnapEnums and ContentType.java
|
||||||
|
val contentType = contentReader.getVarInt(2)
|
||||||
|
|
||||||
|
val isMissedAudio = contentType == 13L || contentType == 18L
|
||||||
|
val isMissedVideo = contentType == 12L || contentType == 17L
|
||||||
|
|
||||||
|
if (isMissedAudio && preventMessageSending.contains("abandon_audio")) return true
|
||||||
|
if (isMissedVideo && preventMessageSending.contains("abandon_video")) return true
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
arrayOf(NativeUnaryCallEvent::class, UnaryCallEvent::class).forEach { eventClass ->
|
||||||
|
context.event.subscribe(eventClass) { event ->
|
||||||
|
val uri = if (event is NativeUnaryCallEvent) event.uri else (event as UnaryCallEvent).uri
|
||||||
|
if (!uri.startsWith("/messagingcoreservice.MessagingCoreService/")) return@subscribe
|
||||||
|
|
||||||
|
if (handleCreateContentMessage(uri, event.buffer)) {
|
||||||
|
event.canceled = true
|
||||||
|
}
|
||||||
|
handleUpdateContentMessage(uri, event.buffer)?.let { event.buffer = it }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
context.classCache.conversationManager.hook("updateMessage", HookStage.BEFORE) { param ->
|
context.classCache.conversationManager.hook("updateMessage", HookStage.BEFORE) { param ->
|
||||||
val messageUpdate = param.arg<Any>(2).toString()
|
val messageUpdate = param.arg<Any>(2).toString()
|
||||||
if (messageUpdate == "SCREENSHOT" && preventMessageSending.contains("chat_screenshot")) {
|
if (messageUpdate == "SCREENSHOT" && preventMessageSending.contains("chat_screenshot")) {
|
||||||
@@ -34,6 +66,10 @@ class PreventMessageSending : Feature("Prevent message sending") {
|
|||||||
if (messageUpdate == "SCREEN_RECORD" && preventMessageSending.contains("chat_screen_record")) {
|
if (messageUpdate == "SCREEN_RECORD" && preventMessageSending.contains("chat_screen_record")) {
|
||||||
param.setResult(null)
|
param.setResult(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((messageUpdate == "REPLAY" || messageUpdate == "replay") && preventMessageSending.contains("snap_replay")) {
|
||||||
|
param.setResult(null)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
context.event.subscribe(SendMessageWithContentEvent::class) { event ->
|
context.event.subscribe(SendMessageWithContentEvent::class) { event ->
|
||||||
@@ -41,9 +77,8 @@ class PreventMessageSending : Feature("Prevent message sending") {
|
|||||||
val associatedType = NotificationType.fromContentType(contentType ?: return@subscribe) ?: return@subscribe
|
val associatedType = NotificationType.fromContentType(contentType ?: return@subscribe) ?: return@subscribe
|
||||||
|
|
||||||
if (preventMessageSending.contains(associatedType.key)) {
|
if (preventMessageSending.contains(associatedType.key)) {
|
||||||
context.log.verbose("Preventing message sending for $associatedType")
|
|
||||||
event.canceled = true
|
event.canceled = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ class MessageSender(
|
|||||||
it.conversations = conversations.toCollection(ArrayList())
|
it.conversations = conversations.toCollection(ArrayList())
|
||||||
it.mPhoneNumbers = arrayListOf<Any>()
|
it.mPhoneNumbers = arrayListOf<Any>()
|
||||||
it.stories = arrayListOf<Any>()
|
it.stories = arrayListOf<Any>()
|
||||||
|
it.massSnaps = arrayListOf<Any>()
|
||||||
}
|
}
|
||||||
|
|
||||||
sendMessageWithContentMethod.invoke(context.feature(Messaging::class).conversationManager?.instanceNonNull(), messageDestinations.instanceNonNull(), localMessageContent, callback)
|
sendMessageWithContentMethod.invoke(context.feature(Messaging::class).conversationManager?.instanceNonNull(), messageDestinations.instanceNonNull(), localMessageContent, callback)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package me.eternal.purrfectsnap.core.wrapper.impl
|
package me.eternal.purrfectsnap.core.wrapper.impl
|
||||||
|
|
||||||
import me.eternal.purrfectsnap.core.PurrfectSnap
|
import me.eternal.purrfectsnap.core.purrfectsnap
|
||||||
import me.eternal.purrfectsnap.core.util.ktx.getObjectField
|
import me.eternal.purrfectsnap.core.util.ktx.getObjectField
|
||||||
import me.eternal.purrfectsnap.core.wrapper.AbstractWrapper
|
import me.eternal.purrfectsnap.core.wrapper.AbstractWrapper
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
@@ -29,12 +29,14 @@ class SnapUUID(
|
|||||||
obj
|
obj
|
||||||
}
|
}
|
||||||
obj is UUID -> obj.toBytes()
|
obj is UUID -> obj.toBytes()
|
||||||
PurrfectSnap.classCache.snapUUID.isInstance(obj) -> {
|
purrfectsnap.classCache.snapUUID.isInstance(obj) -> {
|
||||||
obj?.getObjectField("mId") as ByteArray
|
obj?.getObjectField("mId") as ByteArray
|
||||||
}
|
}
|
||||||
PurrfectSnap.classCache.snapShimsUUID?.isInstance(obj) == true -> {
|
purrfectsnap.classCache.snapShimsUUID?.isInstance(obj) == true -> {
|
||||||
val any = obj as Any
|
val any = obj as Any
|
||||||
any::class.java.methods.firstOrNull { it.name == "getId" }?.invoke(any) as? ByteArray ?: ByteArray(16)
|
runCatching { any.javaClass.getMethod("getId").invoke(any) as ByteArray }.getOrElse {
|
||||||
|
any.getObjectField("mId") as ByteArray
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else -> ByteArray(16)
|
else -> ByteArray(16)
|
||||||
}
|
}
|
||||||
@@ -44,7 +46,7 @@ class SnapUUID(
|
|||||||
|
|
||||||
override var instance: Any?
|
override var instance: Any?
|
||||||
set(_) {}
|
set(_) {}
|
||||||
get() = PurrfectSnap.classCache.snapUUID.getConstructor(ByteArray::class.java).newInstance(uuidBytes)
|
get() = purrfectsnap.classCache.snapUUID.getConstructor(ByteArray::class.java).newInstance(uuidBytes)
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return uuidString
|
return uuidString
|
||||||
@@ -60,3 +62,4 @@ class SnapUUID(
|
|||||||
return uuidBytes.contentHashCode()
|
return uuidBytes.contentHashCode()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
436
native/rust/Cargo.lock
generated
436
native/rust/Cargo.lock
generated
@@ -4,9 +4,9 @@ version = 4
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "adler2"
|
name = "adler2"
|
||||||
version = "2.0.0"
|
version = "2.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
|
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aes"
|
name = "aes"
|
||||||
@@ -21,24 +21,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "1.1.3"
|
version = "1.1.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "android-tzdata"
|
|
||||||
version = "0.1.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "android_log-sys"
|
name = "android_log-sys"
|
||||||
version = "0.3.1"
|
version = "0.3.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5ecc8056bf6ab9892dcd53216c83d1597487d7dacac16c8df6b877d127df9937"
|
checksum = "84521a3cf562bc62942e294181d9eef17eb38ceb8c68677bc49f144e4c3d4f8d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "android_logger"
|
name = "android_logger"
|
||||||
@@ -62,9 +56,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.4.0"
|
version = "1.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
@@ -74,15 +68,15 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64ct"
|
name = "base64ct"
|
||||||
version = "1.8.0"
|
version = "1.8.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba"
|
checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.7.0"
|
version = "2.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1be3f42a67d6d345ecd59f675f3f012d6974981560836e938c22b424b85ce1be"
|
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "block-buffer"
|
name = "block-buffer"
|
||||||
@@ -95,9 +89,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bumpalo"
|
name = "bumpalo"
|
||||||
version = "3.16.0"
|
version = "3.19.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
|
checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "byteorder"
|
name = "byteorder"
|
||||||
@@ -107,9 +101,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "bytes"
|
||||||
version = "1.9.0"
|
version = "1.11.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b"
|
checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bzip2"
|
name = "bzip2"
|
||||||
@@ -133,10 +127,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.9"
|
version = "1.2.53"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c8293772165d9345bdaaa39b45b2109591e63fe5e6fbc23c6ff930a048aa310b"
|
checksum = "755d2fce177175ffca841e9a06afdb2c4ab0f593d53b4dee48147dfaade85932"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"find-msvc-tools",
|
||||||
"jobserver",
|
"jobserver",
|
||||||
"libc",
|
"libc",
|
||||||
"shlex",
|
"shlex",
|
||||||
@@ -150,9 +145,9 @@ checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
version = "1.0.0"
|
version = "1.0.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg_aliases"
|
name = "cfg_aliases"
|
||||||
@@ -162,14 +157,13 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "chrono"
|
name = "chrono"
|
||||||
version = "0.4.39"
|
version = "0.4.43"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825"
|
checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"android-tzdata",
|
|
||||||
"iana-time-zone",
|
"iana-time-zone",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"windows-targets 0.52.6",
|
"windows-link",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -221,9 +215,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crc32fast"
|
name = "crc32fast"
|
||||||
version = "1.4.2"
|
version = "1.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
|
checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
@@ -268,7 +262,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.96",
|
"syn 2.0.114",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -343,9 +337,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "env_filter"
|
name = "env_filter"
|
||||||
version = "0.1.3"
|
version = "0.1.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0"
|
checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"regex",
|
"regex",
|
||||||
@@ -353,12 +347,12 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "errno"
|
name = "errno"
|
||||||
version = "0.3.10"
|
version = "0.3.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
|
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -368,10 +362,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
|
checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flate2"
|
name = "find-msvc-tools"
|
||||||
version = "1.0.35"
|
version = "0.1.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c"
|
checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "flate2"
|
||||||
|
version = "1.1.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b375d6465b98090a5f25b1c7703f3859783755aa9a80433b36e0379a3ec2f369"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crc32fast",
|
"crc32fast",
|
||||||
"miniz_oxide",
|
"miniz_oxide",
|
||||||
@@ -389,15 +389,38 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.2.15"
|
version = "0.2.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
|
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
"wasi",
|
"wasi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.3.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"r-efi",
|
||||||
|
"wasip2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "goblin"
|
||||||
|
version = "0.10.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4db6758c546e6f81f265638c980e5e84dfbda80cfd8e89e02f83454c8e8124bd"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"plain",
|
||||||
|
"scroll",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "goldberg"
|
name = "goldberg"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -428,14 +451,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iana-time-zone"
|
name = "iana-time-zone"
|
||||||
version = "0.1.61"
|
version = "0.1.64"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220"
|
checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"android_system_properties",
|
"android_system_properties",
|
||||||
"core-foundation-sys",
|
"core-foundation-sys",
|
||||||
"iana-time-zone-haiku",
|
"iana-time-zone-haiku",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
|
"log",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"windows-core",
|
"windows-core",
|
||||||
]
|
]
|
||||||
@@ -460,9 +484,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.14"
|
version = "1.0.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
|
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jni"
|
name = "jni"
|
||||||
@@ -488,18 +512,19 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jobserver"
|
name = "jobserver"
|
||||||
version = "0.1.32"
|
version = "0.1.34"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0"
|
checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"getrandom 0.3.4",
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
version = "0.3.77"
|
version = "0.3.85"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
|
checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
@@ -507,9 +532,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.169"
|
version = "0.2.180"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
|
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
@@ -519,23 +544,24 @@ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.25"
|
version = "0.4.29"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f"
|
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.7.4"
|
version = "2.7.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "miniz_oxide"
|
name = "miniz_oxide"
|
||||||
version = "0.8.3"
|
version = "0.8.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924"
|
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"adler2",
|
"adler2",
|
||||||
|
"simd-adler32",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -578,9 +604,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.20.2"
|
version = "1.21.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
|
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "password-hash"
|
name = "password-hash"
|
||||||
@@ -623,9 +649,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pkg-config"
|
name = "pkg-config"
|
||||||
version = "0.3.31"
|
version = "0.3.32"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
|
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "plain"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "powerfmt"
|
name = "powerfmt"
|
||||||
@@ -635,18 +667,18 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ppv-lite86"
|
name = "ppv-lite86"
|
||||||
version = "0.2.20"
|
version = "0.2.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
|
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"zerocopy",
|
"zerocopy",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.93"
|
version = "1.0.106"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
|
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
@@ -686,6 +718,7 @@ dependencies = [
|
|||||||
"crc32fast",
|
"crc32fast",
|
||||||
"dobby-rs",
|
"dobby-rs",
|
||||||
"ed25519-dalek",
|
"ed25519-dalek",
|
||||||
|
"goblin",
|
||||||
"goldberg",
|
"goldberg",
|
||||||
"jni",
|
"jni",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -699,18 +732,24 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"sha2",
|
"sha2",
|
||||||
"zip",
|
"zip",
|
||||||
"zstd 0.13.2",
|
"zstd 0.13.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.38"
|
version = "1.0.43"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
|
checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "r-efi"
|
||||||
|
version = "5.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand"
|
name = "rand"
|
||||||
version = "0.8.5"
|
version = "0.8.5"
|
||||||
@@ -738,14 +777,14 @@ version = "0.6.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom",
|
"getrandom 0.2.17",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.11.1"
|
version = "1.12.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
|
checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
@@ -755,9 +794,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-automata"
|
name = "regex-automata"
|
||||||
version = "0.4.9"
|
version = "0.4.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
|
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
@@ -766,9 +805,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-syntax"
|
name = "regex-syntax"
|
||||||
version = "0.8.5"
|
version = "0.8.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc_version"
|
name = "rustc_version"
|
||||||
@@ -781,9 +820,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "0.38.43"
|
version = "0.38.44"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a78891ee6bf2340288408954ac787aa063d8e8817e9f53abb37c695c6d834ef6"
|
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"errno",
|
"errno",
|
||||||
@@ -794,15 +833,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustversion"
|
name = "rustversion"
|
||||||
version = "1.0.19"
|
version = "1.0.22"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4"
|
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ryu"
|
|
||||||
version = "1.0.18"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "same-file"
|
name = "same-file"
|
||||||
@@ -813,6 +846,26 @@ dependencies = [
|
|||||||
"winapi-util",
|
"winapi-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "scroll"
|
||||||
|
version = "0.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c1257cd4248b4132760d6524d6dda4e053bc648c9070b960929bf50cfb1e7add"
|
||||||
|
dependencies = [
|
||||||
|
"scroll_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "scroll_derive"
|
||||||
|
version = "0.13.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ed76efe62313ab6610570951494bdaa81568026e0318eaa55f167de70eeea67d"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.114",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "semver"
|
name = "semver"
|
||||||
version = "1.0.27"
|
version = "1.0.27"
|
||||||
@@ -821,34 +874,45 @@ checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.217"
|
version = "1.0.228"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
|
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
||||||
|
dependencies = [
|
||||||
|
"serde_core",
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_core"
|
||||||
|
version = "1.0.228"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.217"
|
version = "1.0.228"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
|
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.96",
|
"syn 2.0.114",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.135"
|
version = "1.0.149"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2b0d7ba2887406110130a978386c4e1befb98c674b4fba677954e4db976630d9"
|
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
"memchr",
|
"memchr",
|
||||||
"ryu",
|
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_core",
|
||||||
|
"zmij",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -888,6 +952,12 @@ dependencies = [
|
|||||||
"rand_core",
|
"rand_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "simd-adler32"
|
||||||
|
version = "0.3.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "spki"
|
name = "spki"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
@@ -917,9 +987,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.96"
|
version = "2.0.114"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80"
|
checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -943,27 +1013,27 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.96",
|
"syn 2.0.114",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time"
|
name = "time"
|
||||||
version = "0.3.44"
|
version = "0.3.45"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d"
|
checksum = "f9e442fc33d7fdb45aa9bfeb312c095964abdf596f7567261062b2a7107aaabd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"deranged",
|
"deranged",
|
||||||
"num-conv",
|
"num-conv",
|
||||||
"powerfmt",
|
"powerfmt",
|
||||||
"serde",
|
"serde_core",
|
||||||
"time-core",
|
"time-core",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time-core"
|
name = "time-core"
|
||||||
version = "0.1.6"
|
version = "0.1.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b"
|
checksum = "8b36ee98fd31ec7426d599183e8fe26932a8dc1fb76ddb6214d05493377d34ca"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typenum"
|
name = "typenum"
|
||||||
@@ -973,9 +1043,9 @@ checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.14"
|
version = "1.0.22"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
|
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "version_check"
|
name = "version_check"
|
||||||
@@ -995,41 +1065,37 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasi"
|
name = "wasi"
|
||||||
version = "0.11.0+wasi-snapshot-preview1"
|
version = "0.11.1+wasi-snapshot-preview1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasip2"
|
||||||
|
version = "1.0.2+wasi-0.2.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
|
||||||
|
dependencies = [
|
||||||
|
"wit-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen"
|
name = "wasm-bindgen"
|
||||||
version = "0.2.100"
|
version = "0.2.108"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
|
checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustversion",
|
"rustversion",
|
||||||
"wasm-bindgen-macro",
|
"wasm-bindgen-macro",
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasm-bindgen-backend"
|
|
||||||
version = "0.2.100"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
|
|
||||||
dependencies = [
|
|
||||||
"bumpalo",
|
|
||||||
"log",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.96",
|
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro"
|
name = "wasm-bindgen-macro"
|
||||||
version = "0.2.100"
|
version = "0.2.108"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
|
checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"wasm-bindgen-macro-support",
|
"wasm-bindgen-macro-support",
|
||||||
@@ -1037,42 +1103,92 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro-support"
|
name = "wasm-bindgen-macro-support"
|
||||||
version = "0.2.100"
|
version = "0.2.108"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
|
checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"bumpalo",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.96",
|
"syn 2.0.114",
|
||||||
"wasm-bindgen-backend",
|
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-shared"
|
name = "wasm-bindgen-shared"
|
||||||
version = "0.2.100"
|
version = "0.2.108"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
|
checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi-util"
|
name = "winapi-util"
|
||||||
version = "0.1.9"
|
version = "0.1.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-core"
|
name = "windows-core"
|
||||||
version = "0.52.0"
|
version = "0.62.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
|
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-targets 0.52.6",
|
"windows-implement",
|
||||||
|
"windows-interface",
|
||||||
|
"windows-link",
|
||||||
|
"windows-result",
|
||||||
|
"windows-strings",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-implement"
|
||||||
|
version = "0.60.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.114",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-interface"
|
||||||
|
version = "0.59.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.114",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-link"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-result"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
|
||||||
|
dependencies = [
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-strings"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
|
||||||
|
dependencies = [
|
||||||
|
"windows-link",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1093,6 +1209,15 @@ dependencies = [
|
|||||||
"windows-targets 0.52.6",
|
"windows-targets 0.52.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.61.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||||
|
dependencies = [
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-targets"
|
name = "windows-targets"
|
||||||
version = "0.42.2"
|
version = "0.42.2"
|
||||||
@@ -1215,24 +1340,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy"
|
name = "wit-bindgen"
|
||||||
version = "0.7.35"
|
version = "0.51.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
|
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerocopy"
|
||||||
|
version = "0.8.33"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"byteorder",
|
|
||||||
"zerocopy-derive",
|
"zerocopy-derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy-derive"
|
name = "zerocopy-derive"
|
||||||
version = "0.7.35"
|
version = "0.8.33"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
|
checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.96",
|
"syn 2.0.114",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1261,6 +1391,12 @@ dependencies = [
|
|||||||
"zstd 0.11.2+zstd.1.5.2",
|
"zstd 0.11.2+zstd.1.5.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zmij"
|
||||||
|
version = "1.0.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dfcd145825aace48cff44a8844de64bf75feec3080e0aa5cdbde72961ae51a65"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zstd"
|
name = "zstd"
|
||||||
version = "0.11.2+zstd.1.5.2"
|
version = "0.11.2+zstd.1.5.2"
|
||||||
@@ -1272,11 +1408,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zstd"
|
name = "zstd"
|
||||||
version = "0.13.2"
|
version = "0.13.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9"
|
checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"zstd-safe 7.2.1",
|
"zstd-safe 7.2.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1291,18 +1427,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zstd-safe"
|
name = "zstd-safe"
|
||||||
version = "7.2.1"
|
version = "7.2.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059"
|
checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"zstd-sys",
|
"zstd-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zstd-sys"
|
name = "zstd-sys"
|
||||||
version = "2.0.13+zstd.1.5.6"
|
version = "2.0.16+zstd.1.5.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa"
|
checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"pkg-config",
|
"pkg-config",
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ sha2 = "0.10"
|
|||||||
zip = "0.6.6"
|
zip = "0.6.6"
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
ed25519-dalek = { version = "2.1.1", default-features = true, features = ["std"] }
|
ed25519-dalek = { version = "2.1.1", default-features = true, features = ["std"] }
|
||||||
|
goblin = "0.10.4"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
cc = "1.0"
|
cc = "1.0"
|
||||||
|
|||||||
@@ -24,11 +24,16 @@ pub static CLIENT_MODULE: Lazy<MappedLib> = Lazy::new(|| {
|
|||||||
|
|
||||||
|
|
||||||
pub fn set_native_lib_instance(instance: GlobalRef) {
|
pub fn set_native_lib_instance(instance: GlobalRef) {
|
||||||
NATIVE_LIB_INSTANCE.set(instance).expect("NativeLib instance already set");
|
NATIVE_LIB_INSTANCE
|
||||||
|
.set(instance)
|
||||||
|
.expect("NativeLib instance already set");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn native_lib_instance() -> GlobalRef {
|
pub fn native_lib_instance() -> GlobalRef {
|
||||||
NATIVE_LIB_INSTANCE.get().expect("NativeLib instance not set").clone()
|
NATIVE_LIB_INSTANCE
|
||||||
|
.get()
|
||||||
|
.expect("NativeLib instance not set")
|
||||||
|
.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_java_vm(vm: *mut jni::sys::JavaVM) {
|
pub fn set_java_vm(vm: *mut jni::sys::JavaVM) {
|
||||||
@@ -37,13 +42,16 @@ pub fn set_java_vm(vm: *mut jni::sys::JavaVM) {
|
|||||||
|
|
||||||
pub fn java_vm() -> JavaVM {
|
pub fn java_vm() -> JavaVM {
|
||||||
unsafe {
|
unsafe {
|
||||||
JavaVM::from_raw(*JAVA_VM.get().expect("JavaVM not set") as *mut jni::sys::JavaVM).expect("Failed to get JavaVM")
|
JavaVM::from_raw(*JAVA_VM.get().expect("JavaVM not set") as *mut jni::sys::JavaVM)
|
||||||
|
.expect("Failed to get JavaVM")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn attach_jni_env(block: impl FnOnce(&mut jni::JNIEnv)) {
|
pub fn attach_jni_env(block: impl FnOnce(&mut jni::JNIEnv)) {
|
||||||
let jvm = java_vm();
|
let jvm = java_vm();
|
||||||
let mut env: jni::AttachGuard = jvm.attach_current_thread().expect("Failed to attach to current thread");
|
let mut env: jni::AttachGuard = jvm
|
||||||
|
.attach_current_thread()
|
||||||
|
.expect("Failed to attach to current thread");
|
||||||
|
|
||||||
block(&mut env);
|
block(&mut env);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
use std::{error::Error, sync::Mutex};
|
|
||||||
use jni::{objects::JObject, JNIEnv};
|
|
||||||
use crate::{secstrings, util::get_jni_string};
|
use crate::{secstrings, util::get_jni_string};
|
||||||
|
use jni::{objects::JObject, JNIEnv};
|
||||||
|
use std::{error::Error, sync::Mutex};
|
||||||
|
|
||||||
static NATIVE_CONFIG: Mutex<Option<NativeConfig>> = Mutex::new(None);
|
static NATIVE_CONFIG: Mutex<Option<NativeConfig>> = Mutex::new(None);
|
||||||
|
|
||||||
pub fn native_config() -> NativeConfig {
|
pub fn native_config() -> NativeConfig {
|
||||||
NATIVE_CONFIG.lock().unwrap().as_ref().expect("NativeConfig not loaded").clone()
|
NATIVE_CONFIG
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.as_ref()
|
||||||
|
.expect("NativeConfig not loaded")
|
||||||
|
.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -27,11 +32,13 @@ impl NativeConfig {
|
|||||||
macro_rules! get_string {
|
macro_rules! get_string {
|
||||||
($field:expr) => {
|
($field:expr) => {
|
||||||
match env.get_field(&obj, $field, "Ljava/lang/String;")?.l()? {
|
match env.get_field(&obj, $field, "Ljava/lang/String;")?.l()? {
|
||||||
jstring => if !jstring.is_null() {
|
jstring => {
|
||||||
Some(get_jni_string(env, jstring.into())?)
|
if !jstring.is_null() {
|
||||||
} else {
|
Some(get_jni_string(env, jstring.into())?)
|
||||||
None
|
} else {
|
||||||
},
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -73,10 +80,11 @@ pub fn get_blocker_config() -> BlockerConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_config(mut env: JNIEnv, _class: JObject, obj: JObject) {
|
pub fn load_config(mut env: JNIEnv, _class: JObject, obj: JObject) {
|
||||||
NATIVE_CONFIG.lock().unwrap().replace(
|
NATIVE_CONFIG
|
||||||
NativeConfig::new(&mut env, obj).expect("Failed to load NativeConfig")
|
.lock()
|
||||||
);
|
.unwrap()
|
||||||
|
.replace(NativeConfig::new(&mut env, obj).expect("Failed to load NativeConfig"));
|
||||||
|
|
||||||
info!("Config loaded {:?}", native_config());
|
info!("Config loaded {:?}", native_config());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,15 +36,3 @@ macro_rules! dobby_hook {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! dobby_hook_sym {
|
|
||||||
($lib:expr, $sym:expr, $hook:expr) => {
|
|
||||||
if let Some(hook_symbol) = dobby_rs::resolve_symbol($lib, $sym) {
|
|
||||||
crate::dobby_hook!(hook_symbol, $hook);
|
|
||||||
debug!("hooked symbol: {}", $sym);
|
|
||||||
} else {
|
|
||||||
panic!("Failed to resolve symbol: {}", $sym);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ extern crate log;
|
|||||||
|
|
||||||
mod common;
|
mod common;
|
||||||
|
|
||||||
mod hook;
|
|
||||||
mod util;
|
|
||||||
mod mapped_lib;
|
|
||||||
mod config;
|
mod config;
|
||||||
|
mod hook;
|
||||||
|
mod mapped_lib;
|
||||||
mod sig;
|
mod sig;
|
||||||
|
mod util;
|
||||||
|
|
||||||
mod modules;
|
mod modules;
|
||||||
mod security;
|
mod security;
|
||||||
@@ -15,7 +15,10 @@ mod secstrings;
|
|||||||
|
|
||||||
use android_logger::Config;
|
use android_logger::Config;
|
||||||
use log::LevelFilter;
|
use log::LevelFilter;
|
||||||
use modules::{valdi_hook, custom_font_hook, duplex_hook, fstat_hook, linker_hook, sqlite_hook, unary_call_hook};
|
use modules::{
|
||||||
|
custom_font_hook, duplex_hook, fstat_hook, linker_hook, sqlite_hook, unary_call_hook,
|
||||||
|
valdi_hook,
|
||||||
|
};
|
||||||
|
|
||||||
use jni::{JNIEnv, JavaVM, NativeMethod};
|
use jni::{JNIEnv, JavaVM, NativeMethod};
|
||||||
use jni::objects::{JObject, JString, JClass, JValue};
|
use jni::objects::{JObject, JString, JClass, JValue};
|
||||||
@@ -166,10 +169,11 @@ fn init(mut env: JNIEnv, _class: JObject, signature_cache: JString) -> jstring {
|
|||||||
let start_time = std::time::Instant::now();
|
let start_time = std::time::Instant::now();
|
||||||
|
|
||||||
// load signature cache
|
// load signature cache
|
||||||
|
|
||||||
if !signature_cache.is_null() {
|
if !signature_cache.is_null() {
|
||||||
let sig_cache_str = util::get_jni_string(&mut env, signature_cache).expect("Failed to convert mappings to string");
|
let sig_cache_str = util::get_jni_string(&mut env, signature_cache)
|
||||||
|
.expect("Failed to convert mappings to string");
|
||||||
|
|
||||||
if let Ok(signature_cache) = serde_json::from_str(sig_cache_str.as_str()) {
|
if let Ok(signature_cache) = serde_json::from_str(sig_cache_str.as_str()) {
|
||||||
sig::add_signatures(signature_cache);
|
sig::add_signatures(signature_cache);
|
||||||
} else {
|
} else {
|
||||||
@@ -177,7 +181,11 @@ fn init(mut env: JNIEnv, _class: JObject, signature_cache: JString) -> jstring {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
common::set_native_lib_instance(env.new_global_ref(_class).ok().expect("Failed to create global ref"));
|
common::set_native_lib_instance(
|
||||||
|
env.new_global_ref(_class)
|
||||||
|
.ok()
|
||||||
|
.expect("Failed to create global ref"),
|
||||||
|
);
|
||||||
|
|
||||||
let _ = common::CLIENT_MODULE;
|
let _ = common::CLIENT_MODULE;
|
||||||
|
|
||||||
|
|||||||
@@ -24,21 +24,24 @@ impl MappedLib {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn search(&mut self) -> Result<&Self, Box<dyn Error>> {
|
pub fn search(&mut self) -> Result<&Self, Box<dyn Error>> {
|
||||||
procfs::process::Process::myself()?.maps()?.iter().for_each(|map| {
|
procfs::process::Process::myself()?
|
||||||
let pathname = &map.pathname;
|
.maps()?
|
||||||
|
.iter()
|
||||||
|
.for_each(|map| {
|
||||||
|
let pathname = &map.pathname;
|
||||||
|
|
||||||
if let MMapPath::Path(path_buffer) = pathname {
|
if let MMapPath::Path(path_buffer) = pathname {
|
||||||
let path = path_buffer.to_string_lossy();
|
let path = path_buffer.to_string_lossy();
|
||||||
|
|
||||||
if path.contains(&self.name) {
|
if path.contains(&self.name) {
|
||||||
self.regions.push(MappedRegion {
|
self.regions.push(MappedRegion {
|
||||||
start: map.address.0,
|
start: map.address.0,
|
||||||
end: map.address.1,
|
end: map.address.1,
|
||||||
perms: map.perms,
|
perms: map.perms,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
|
||||||
|
|
||||||
if self.regions.is_empty() {
|
if self.regions.is_empty() {
|
||||||
return Err(format!("No regions found for {}", self.name).into());
|
return Err(format!("No regions found for {}", self.name).into());
|
||||||
|
|||||||
@@ -2,33 +2,41 @@ use std::{ffi::CStr, fs};
|
|||||||
|
|
||||||
use nix::libc::{self, c_uint};
|
use nix::libc::{self, c_uint};
|
||||||
|
|
||||||
use crate::{config, def_hook, dobby_hook_sym};
|
use crate::{config, def_hook, dobby_hook, modules::util::elf};
|
||||||
|
|
||||||
def_hook!(
|
def_hook!(open_hook, i32, |path: *const u8,
|
||||||
open_hook,
|
flags: i32,
|
||||||
i32,
|
mode: c_uint| {
|
||||||
|path: *const u8, flags: i32, mode: c_uint| {
|
if let Ok(pathname) = CStr::from_ptr(path).to_str() {
|
||||||
if let Ok(pathname) = CStr::from_ptr(path).to_str() {
|
if pathname == "/system/fonts/NotoColorEmoji.ttf" {
|
||||||
if pathname == "/system/fonts/NotoColorEmoji.ttf" {
|
if let Some(font_path) = config::native_config().custom_emoji_font_path {
|
||||||
if let Some(font_path) = config::native_config().custom_emoji_font_path {
|
if fs::metadata(&font_path).is_ok() {
|
||||||
if fs::metadata(&font_path).is_ok() {
|
return libc::openat(
|
||||||
return libc::openat(libc::AT_FDCWD, font_path.as_ptr() as *const u8, flags, mode);
|
libc::AT_FDCWD,
|
||||||
} else {
|
font_path.as_ptr() as *const u8,
|
||||||
warn!("custom emoji font path does not exist: {}", font_path);
|
flags,
|
||||||
}
|
mode,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
warn!("custom emoji font path does not exist: {}", font_path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open_hook_original.unwrap()(path, flags, mode)
|
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
|
||||||
|
open_hook_original.unwrap()(path, flags, mode)
|
||||||
|
});
|
||||||
|
|
||||||
pub fn init() {
|
pub fn init() {
|
||||||
if config::native_config().custom_emoji_font_path.is_none() {
|
if config::native_config().custom_emoji_font_path.is_none() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
dobby_hook_sym!("libc.so", "open", open_hook);
|
let libc = elf::Elf::from_maps("/libc.so").expect("Failed to find libc.so in maps");
|
||||||
}
|
|
||||||
|
if let Some(ptr) = libc.get_symbol_address("open") {
|
||||||
|
dobby_hook!(ptr as _, open_hook);
|
||||||
|
} else {
|
||||||
|
panic!("Failed to find open symbol");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ use jni::{objects::JObject, sys::jboolean, JNIEnv};
|
|||||||
|
|
||||||
use crate::{common, def_hook, dobby_hook, util::get_jni_string};
|
use crate::{common, def_hook, dobby_hook, util::get_jni_string};
|
||||||
|
|
||||||
|
|
||||||
def_hook!(
|
def_hook!(
|
||||||
is_same_object,
|
is_same_object,
|
||||||
jboolean,
|
jboolean,
|
||||||
@@ -20,9 +19,15 @@ def_hook!(
|
|||||||
if !env.is_instance_of(&obj1, class).unwrap() {
|
if !env.is_instance_of(&obj1, class).unwrap() {
|
||||||
return is_same_object_original.unwrap()(env, obj1, obj2);
|
return is_same_object_original.unwrap()(env, obj1, obj2);
|
||||||
}
|
}
|
||||||
|
|
||||||
let obj1_class_name = env.call_method(&obj1, "getName", "()Ljava/lang/String;", &[]).unwrap().l().unwrap().into();
|
let obj1_class_name = env
|
||||||
let class_name = get_jni_string(&mut env, obj1_class_name).expect("Failed to get class name");
|
.call_method(&obj1, "getName", "()Ljava/lang/String;", &[])
|
||||||
|
.unwrap()
|
||||||
|
.l()
|
||||||
|
.unwrap()
|
||||||
|
.into();
|
||||||
|
let class_name =
|
||||||
|
get_jni_string(&mut env, obj1_class_name).expect("Failed to get class name");
|
||||||
|
|
||||||
if class_name.contains("com.snapchat.client.duplex.MessageHandler") {
|
if class_name.contains("com.snapchat.client.duplex.MessageHandler") {
|
||||||
debug!("is_same_object hook: MessageHandler");
|
debug!("is_same_object hook: MessageHandler");
|
||||||
@@ -33,9 +38,11 @@ def_hook!(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
pub fn init() {
|
pub fn init() {
|
||||||
common::attach_jni_env(|env| {
|
common::attach_jni_env(|env| {
|
||||||
dobby_hook!((**env.get_native_interface()).IsSameObject.unwrap() as *mut c_void, is_same_object);
|
dobby_hook!(
|
||||||
|
(**env.get_native_interface()).IsSameObject.unwrap() as *mut c_void,
|
||||||
|
is_same_object
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,44 +1,48 @@
|
|||||||
|
|
||||||
|
use crate::{
|
||||||
|
config::{self, native_config},
|
||||||
|
def_hook, dobby_hook,
|
||||||
|
modules::util::elf,
|
||||||
|
};
|
||||||
|
use nix::libc;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
|
||||||
use nix::libc;
|
def_hook!(fstat_hook, i32, |fd: i32, statbuf: *mut libc::stat| {
|
||||||
|
if let Ok(link) = fs::read_link("/proc/self/fd/".to_owned() + &fd.to_string()) {
|
||||||
|
let link_str = link.to_string_lossy();
|
||||||
|
let config = native_config();
|
||||||
|
if config.disable_metrics && link_str.contains("files/blizzardv2/queues") {
|
||||||
|
if libc::unlink((link_str.to_string() + "\0").as_ptr()) == -1 {
|
||||||
|
warn!("Failed to unlink {}", link_str);
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
use crate::{config::{self, native_config}, def_hook, dobby_hook_sym};
|
if config.disable_bitmoji {
|
||||||
|
if link_str.contains("com.snap.file_manager_4_SCContent") {
|
||||||
def_hook!(
|
|
||||||
fstat_hook,
|
|
||||||
i32,
|
|
||||||
|fd: i32, statbuf: *mut libc::stat| {
|
|
||||||
if let Ok(link) = fs::read_link("/proc/self/fd/".to_owned() + &fd.to_string()) {
|
|
||||||
let link_str = link.to_string_lossy();
|
|
||||||
let config = native_config();
|
|
||||||
if config.disable_metrics && link_str.contains("files/blizzardv2/queues") {
|
|
||||||
if libc::unlink((link_str.to_string() + "\0").as_ptr()) == -1 {
|
|
||||||
warn!("Failed to unlink {}", link_str);
|
|
||||||
}
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
if link_str.contains("/files/file_manager/") {
|
||||||
if config.disable_bitmoji {
|
let lower = link_str.to_lowercase();
|
||||||
if link_str.contains("com.snap.file_manager_4_SCContent") {
|
if lower.contains("bitmoji") {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if link_str.contains("/files/file_manager/") {
|
|
||||||
let lower = link_str.to_lowercase();
|
|
||||||
if lower.contains("bitmoji") {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fstat_hook_original.unwrap()(fd, statbuf)
|
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
fstat_hook_original.unwrap()(fd, statbuf)
|
||||||
|
});
|
||||||
|
|
||||||
pub fn init() {
|
pub fn init() {
|
||||||
let config = config::native_config();
|
let config = config::native_config();
|
||||||
if config.disable_metrics || config.disable_bitmoji {
|
if config.disable_metrics || config.disable_bitmoji {
|
||||||
dobby_hook_sym!("libc.so", "fstat", fstat_hook);
|
let libc = elf::Elf::from_maps("/libc.so").expect("Failed to find libc.so in maps");
|
||||||
|
|
||||||
|
if let Some(ptr) = libc.get_symbol_address("fstat") {
|
||||||
|
dobby_hook!(ptr as _, fstat_hook);
|
||||||
|
} else {
|
||||||
|
panic!("Failed to find fstat symbol");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,20 @@
|
|||||||
use std::{collections::HashMap, ffi::{c_void, CStr}, sync::Mutex};
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
ffi::{c_void, CStr},
|
||||||
|
sync::Mutex,
|
||||||
|
};
|
||||||
|
|
||||||
use jni::{objects::{JByteArray, JString}, JNIEnv};
|
use jni::{
|
||||||
|
objects::{JByteArray, JString},
|
||||||
|
JNIEnv,
|
||||||
|
};
|
||||||
use nix::libc;
|
use nix::libc;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
use crate::{def_hook, dobby_hook_sym};
|
use crate::{def_hook, dobby_hook, modules::util::elf};
|
||||||
|
|
||||||
static SHARED_LIBRARIES: Lazy<Mutex<HashMap<String, Box<Vec<i8>>>>> = Lazy::new(|| Mutex::new(HashMap::new()));
|
static SHARED_LIBRARIES: Lazy<Mutex<HashMap<String, Box<Vec<i8>>>>> =
|
||||||
|
Lazy::new(|| Mutex::new(HashMap::new()));
|
||||||
|
|
||||||
def_hook!(
|
def_hook!(
|
||||||
linker_openat,
|
linker_openat,
|
||||||
@@ -17,8 +25,13 @@ def_hook!(
|
|||||||
if let Some(content) = SHARED_LIBRARIES.lock().unwrap().remove(&pathname_str) {
|
if let Some(content) = SHARED_LIBRARIES.lock().unwrap().remove(&pathname_str) {
|
||||||
let memfd = libc::syscall(libc::SYS_memfd_create, "jit-cache\0".as_ptr(), 0) as i32;
|
let memfd = libc::syscall(libc::SYS_memfd_create, "jit-cache\0".as_ptr(), 0) as i32;
|
||||||
let content = content.into_boxed_slice();
|
let content = content.into_boxed_slice();
|
||||||
|
|
||||||
if libc::write(memfd, content.as_ptr() as *const c_void, content.len() as libc::size_t) == -1 {
|
if libc::write(
|
||||||
|
memfd,
|
||||||
|
content.as_ptr() as *const c_void,
|
||||||
|
content.len() as libc::size_t,
|
||||||
|
) == -1
|
||||||
|
{
|
||||||
panic!("failed to write to memfd");
|
panic!("failed to write to memfd");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,25 +49,44 @@ def_hook!(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
pub fn add_linker_shared_library(mut env: JNIEnv, _: *mut c_void, path: JString, content: JByteArray) {
|
pub fn add_linker_shared_library(
|
||||||
|
mut env: JNIEnv,
|
||||||
|
_: *mut c_void,
|
||||||
|
path: JString,
|
||||||
|
content: JByteArray,
|
||||||
|
) {
|
||||||
let path = env.get_string(&path).unwrap().to_str().unwrap().to_string();
|
let path = env.get_string(&path).unwrap().to_str().unwrap().to_string();
|
||||||
let content_length = env.get_array_length(&content).expect("Failed to get array length");
|
let content_length = env
|
||||||
|
.get_array_length(&content)
|
||||||
|
.expect("Failed to get array length");
|
||||||
let mut content_buffer = Box::new(vec![0i8; content_length as usize]);
|
let mut content_buffer = Box::new(vec![0i8; content_length as usize]);
|
||||||
|
|
||||||
env.get_byte_array_region(content, 0, content_buffer.as_mut_slice()).expect("Failed to get byte array region");
|
env.get_byte_array_region(content, 0, content_buffer.as_mut_slice())
|
||||||
|
.expect("Failed to get byte array region");
|
||||||
|
|
||||||
debug!("added shared library: {}", path);
|
debug!("added shared library: {}", path);
|
||||||
|
|
||||||
SHARED_LIBRARIES.lock().unwrap().insert(path, content_buffer);
|
SHARED_LIBRARIES
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.insert(path, content_buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init() {
|
pub fn init() {
|
||||||
#[cfg(target_arch = "aarch64")]
|
let linker = {
|
||||||
{
|
#[cfg(target_arch = "aarch64")]
|
||||||
dobby_hook_sym!("linker64", "__dl___openat", linker_openat);
|
{
|
||||||
}
|
elf::Elf::from_maps("/linker64").expect("Failed to find linker64 in maps")
|
||||||
#[cfg(target_arch = "arm")]
|
}
|
||||||
{
|
#[cfg(target_arch = "arm")]
|
||||||
dobby_hook_sym!("linker", "__dl___openat", linker_openat);
|
{
|
||||||
|
elf::Elf::from_maps("/linker").expect("Failed to find linker in maps")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(ptr) = linker.get_symbol_address("__dl___openat") {
|
||||||
|
dobby_hook!(ptr as _, linker_openat);
|
||||||
|
} else {
|
||||||
|
panic!("Failed to find open symbol");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
pub mod util;
|
|
||||||
pub mod linker_hook;
|
|
||||||
pub mod duplex_hook;
|
|
||||||
pub mod sqlite_hook;
|
|
||||||
pub mod fstat_hook;
|
|
||||||
pub mod unary_call_hook;
|
|
||||||
pub mod valdi_hook;
|
|
||||||
pub mod custom_font_hook;
|
pub mod custom_font_hook;
|
||||||
|
pub mod duplex_hook;
|
||||||
|
pub mod fstat_hook;
|
||||||
|
pub mod linker_hook;
|
||||||
|
pub mod sqlite_hook;
|
||||||
|
pub mod unary_call_hook;
|
||||||
|
pub mod util;
|
||||||
|
pub mod valdi_hook;
|
||||||
|
|||||||
@@ -1,25 +1,34 @@
|
|||||||
use std::{collections::HashMap, ffi::{c_void, CStr}, mem::size_of, ptr::addr_of_mut, sync::Mutex};
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
ffi::{c_void, CStr},
|
||||||
|
mem::size_of,
|
||||||
|
ptr::addr_of_mut,
|
||||||
|
sync::Mutex,
|
||||||
|
};
|
||||||
|
|
||||||
use jni::{objects::{JObject, JString}, JNIEnv};
|
use jni::{
|
||||||
|
objects::{JObject, JString},
|
||||||
|
JNIEnv,
|
||||||
|
};
|
||||||
use nix::libc::{self, pthread_mutex_t};
|
use nix::libc::{self, pthread_mutex_t};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
use crate::{common, def_hook, dobby_hook, sig, util::get_jni_string};
|
use crate::{common, def_hook, dobby_hook, sig, util::get_jni_string};
|
||||||
|
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
struct Sqlite3Mutex {
|
struct Sqlite3Mutex {
|
||||||
mutex: pthread_mutex_t
|
mutex: pthread_mutex_t,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
struct Sqlite3 {
|
struct Sqlite3 {
|
||||||
pad: [u8; 3 * size_of::<usize>()],
|
pad: [u8; 3 * size_of::<usize>()],
|
||||||
mutex: *mut Sqlite3Mutex
|
mutex: *mut Sqlite3Mutex,
|
||||||
}
|
}
|
||||||
|
|
||||||
static SQLITE3_MUTEX_MAP: Lazy<Mutex<HashMap<String, pthread_mutex_t>>> = Lazy::new(|| Mutex::new(HashMap::new()));
|
static SQLITE3_MUTEX_MAP: Lazy<Mutex<HashMap<String, pthread_mutex_t>>> =
|
||||||
|
Lazy::new(|| Mutex::new(HashMap::new()));
|
||||||
|
|
||||||
def_hook!(
|
def_hook!(
|
||||||
sqlite3_open,
|
sqlite3_open,
|
||||||
@@ -31,27 +40,38 @@ def_hook!(
|
|||||||
let sqlite3_mutex = (**pp_db).mutex;
|
let sqlite3_mutex = (**pp_db).mutex;
|
||||||
|
|
||||||
if sqlite3_mutex != std::ptr::null_mut() {
|
if sqlite3_mutex != std::ptr::null_mut() {
|
||||||
let filename = CStr::from_ptr(filename).to_string_lossy().to_string().split("/").last().expect("Failed to get filename").to_string();
|
let filename = CStr::from_ptr(filename)
|
||||||
|
.to_string_lossy()
|
||||||
|
.to_string()
|
||||||
|
.split("/")
|
||||||
|
.last()
|
||||||
|
.expect("Failed to get filename")
|
||||||
|
.to_string();
|
||||||
debug!("sqlite3_open hook {:?}", filename);
|
debug!("sqlite3_open hook {:?}", filename);
|
||||||
|
|
||||||
SQLITE3_MUTEX_MAP.lock().unwrap().insert(
|
SQLITE3_MUTEX_MAP
|
||||||
filename,
|
.lock()
|
||||||
(*sqlite3_mutex).mutex
|
.unwrap()
|
||||||
);
|
.insert(filename, (*sqlite3_mutex).mutex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
pub fn lock_database(mut env: JNIEnv, _: *mut c_void, filename: JString, runnable: JObject) {
|
pub fn lock_database(mut env: JNIEnv, _: *mut c_void, filename: JString, runnable: JObject) {
|
||||||
let database_filename = get_jni_string(&mut env, filename).expect("Failed to get database filename");
|
let database_filename =
|
||||||
let mutex = SQLITE3_MUTEX_MAP.lock().unwrap().get(&database_filename).map(|mutex| *mutex);
|
get_jni_string(&mut env, filename).expect("Failed to get database filename");
|
||||||
|
let mutex = SQLITE3_MUTEX_MAP
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.get(&database_filename)
|
||||||
|
.map(|mutex| *mutex);
|
||||||
|
|
||||||
let call_runnable = || {
|
let call_runnable = || {
|
||||||
env.call_method(runnable, "run", "()V", &[]).expect("Failed to call run method");
|
env.call_method(runnable, "run", "()V", &[])
|
||||||
|
.expect("Failed to call run method");
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(mut mutex) = mutex {
|
if let Some(mut mutex) = mutex {
|
||||||
@@ -71,16 +91,17 @@ pub fn lock_database(mut env: JNIEnv, _: *mut c_void, filename: JString, runnabl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn init() {
|
pub fn init() {
|
||||||
if let Some(signature) = sig::find_signature(
|
if let Some(signature) = sig::find_signature(
|
||||||
&common::CLIENT_MODULE,
|
&common::CLIENT_MODULE,
|
||||||
"FF FF 00 A9 3F 00 00 F9", -0x3C,
|
"FF FF 00 A9 3F 00 00 F9",
|
||||||
"9A 46 90 46 78 44 89 46 05 68",-0xd
|
-0x3C,
|
||||||
|
"9A 46 90 46 78 44 89 46 05 68",
|
||||||
|
-0xd,
|
||||||
) {
|
) {
|
||||||
debug!("Found sqlite3_open signature: {:#x}", signature);
|
debug!("Found sqlite3_open signature: {:#x}", signature);
|
||||||
dobby_hook!(signature as *mut c_void, sqlite3_open);
|
dobby_hook!(signature as *mut c_void, sqlite3_open);
|
||||||
} else {
|
} else {
|
||||||
warn!("Failed to find sqlite3_open signature");
|
panic!("Failed to find sqlite3_open signature");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,23 @@
|
|||||||
use std::ffi::{c_void, CStr};
|
use std::ffi::{c_void, CStr};
|
||||||
|
|
||||||
use jni::{objects::{JByteArray, JMethodID, JValue}, signature::ReturnType};
|
use jni::{
|
||||||
|
objects::{JByteArray, JMethodID, JValue},
|
||||||
|
signature::ReturnType,
|
||||||
|
};
|
||||||
use nix::libc;
|
use nix::libc;
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
|
|
||||||
use crate::{common::{self}, def_hook, dobby_hook, sig};
|
use crate::{
|
||||||
|
common::{self},
|
||||||
|
def_hook, dobby_hook, sig,
|
||||||
|
};
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
struct RefCountedSliceByteBuffer {
|
struct SliceByteBuffer {
|
||||||
ref_counter: *mut c_void,
|
ref_counter: *mut c_void,
|
||||||
length: usize,
|
length: usize,
|
||||||
data: *mut u8
|
data: *mut u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
@@ -19,7 +25,7 @@ struct GrpcByteBuffer {
|
|||||||
reserved: *mut c_void,
|
reserved: *mut c_void,
|
||||||
type_: *mut c_void,
|
type_: *mut c_void,
|
||||||
compression: *mut c_void,
|
compression: *mut c_void,
|
||||||
slice_buffer: *mut RefCountedSliceByteBuffer
|
slice_buffer: *mut SliceByteBuffer,
|
||||||
}
|
}
|
||||||
|
|
||||||
static NATIVE_LIB_ON_UNARY_CALL_METHOD: OnceCell<JMethodID> = OnceCell::new();
|
static NATIVE_LIB_ON_UNARY_CALL_METHOD: OnceCell<JMethodID> = OnceCell::new();
|
||||||
@@ -27,7 +33,12 @@ static NATIVE_LIB_ON_UNARY_CALL_METHOD: OnceCell<JMethodID> = OnceCell::new();
|
|||||||
def_hook!(
|
def_hook!(
|
||||||
unary_call,
|
unary_call,
|
||||||
*mut c_void,
|
*mut c_void,
|
||||||
|unk1: *mut c_void, uri: *const u8, grpc_byte_buffer: *mut *mut GrpcByteBuffer, unk4: *mut c_void, unk5: *mut c_void, unk6: *mut c_void| {
|
|unk1: *mut c_void,
|
||||||
|
uri: *const u8,
|
||||||
|
grpc_byte_buffer: *mut *mut GrpcByteBuffer,
|
||||||
|
unk4: *mut c_void,
|
||||||
|
unk5: *mut c_void,
|
||||||
|
unk6: *mut c_void| {
|
||||||
macro_rules! call_original {
|
macro_rules! call_original {
|
||||||
() => {
|
() => {
|
||||||
unary_call_original.unwrap()(unk1, uri, grpc_byte_buffer, unk4, unk5, unk6)
|
unary_call_original.unwrap()(unk1, uri, grpc_byte_buffer, unk4, unk5, unk6)
|
||||||
@@ -45,45 +56,73 @@ def_hook!(
|
|||||||
let mut env = java_vm.get_env().expect("Failed to get JNIEnv");
|
let mut env = java_vm.get_env().expect("Failed to get JNIEnv");
|
||||||
|
|
||||||
let slice_buffer_length = slice_buffer.length as usize;
|
let slice_buffer_length = slice_buffer.length as usize;
|
||||||
let jni_buffer = env.new_byte_array(slice_buffer_length as i32).expect("Failed to create new byte array");
|
let jni_buffer = env
|
||||||
env.set_byte_array_region(&jni_buffer, 0, std::slice::from_raw_parts(slice_buffer.data as *const i8, slice_buffer_length)).expect("Failed to set byte array region");
|
.new_byte_array(slice_buffer_length as i32)
|
||||||
|
.expect("Failed to create new byte array");
|
||||||
|
env.set_byte_array_region(
|
||||||
|
&jni_buffer,
|
||||||
|
0,
|
||||||
|
std::slice::from_raw_parts(slice_buffer.data as *const i8, slice_buffer_length),
|
||||||
|
)
|
||||||
|
.expect("Failed to set byte array region");
|
||||||
|
|
||||||
let uri_str = CStr::from_ptr(uri).to_str().unwrap();
|
let uri_str = CStr::from_ptr(uri).to_str().unwrap();
|
||||||
|
|
||||||
let native_request_data_object = env.call_method_unchecked(
|
let native_request_data_object = env
|
||||||
common::native_lib_instance(),
|
.call_method_unchecked(
|
||||||
NATIVE_LIB_ON_UNARY_CALL_METHOD.get().unwrap(),
|
common::native_lib_instance(),
|
||||||
ReturnType::Object,
|
NATIVE_LIB_ON_UNARY_CALL_METHOD.get().unwrap(),
|
||||||
&[
|
ReturnType::Object,
|
||||||
JValue::from(&env.new_string(uri_str).unwrap()).as_jni(),
|
&[
|
||||||
JValue::from(&jni_buffer).as_jni()
|
JValue::from(&env.new_string(uri_str).unwrap()).as_jni(),
|
||||||
]
|
JValue::from(&jni_buffer).as_jni(),
|
||||||
).expect("Failed to call onNativeUnaryCall method").l().unwrap();
|
],
|
||||||
|
)
|
||||||
|
.expect("Failed to call onNativeUnaryCall method")
|
||||||
|
.l()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
if native_request_data_object.is_null() {
|
if native_request_data_object.is_null() {
|
||||||
return call_original!();
|
return call_original!();
|
||||||
}
|
}
|
||||||
|
|
||||||
let is_canceled = env.get_field(&native_request_data_object, "canceled", "Z").expect("Failed to get canceled field").z().unwrap();
|
let is_canceled = env
|
||||||
|
.get_field(&native_request_data_object, "canceled", "Z")
|
||||||
|
.expect("Failed to get canceled field")
|
||||||
|
.z()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
if is_canceled {
|
if is_canceled {
|
||||||
info!("canceled request for {}", uri_str);
|
info!("canceled request for {}", uri_str);
|
||||||
return std::ptr::null_mut();
|
return std::ptr::null_mut();
|
||||||
}
|
}
|
||||||
|
|
||||||
let new_buffer: JByteArray = env.get_field(&native_request_data_object, "buffer", "[B").expect("Failed to get buffer field").l().unwrap().into();
|
let new_buffer: JByteArray = env
|
||||||
let new_buffer_length = env.get_array_length(&new_buffer).expect("Failed to get array length") as usize;
|
.get_field(&native_request_data_object, "buffer", "[B")
|
||||||
|
.expect("Failed to get buffer field")
|
||||||
|
.l()
|
||||||
|
.unwrap()
|
||||||
|
.into();
|
||||||
|
let new_buffer_length = env
|
||||||
|
.get_array_length(&new_buffer)
|
||||||
|
.expect("Failed to get array length") as usize;
|
||||||
|
|
||||||
let mut new_buffer_data = Box::new(vec![0i8; new_buffer_length]);
|
let mut new_buffer_data = Box::new(vec![0i8; new_buffer_length]);
|
||||||
env.get_byte_array_region(&new_buffer, 0, new_buffer_data.as_mut_slice()).expect("Failed to get byte array region");
|
env.get_byte_array_region(&new_buffer, 0, new_buffer_data.as_mut_slice())
|
||||||
|
.expect("Failed to get byte array region");
|
||||||
|
|
||||||
let ref_counter_struct_size = (slice_buffer.data as usize) - (slice_buffer.ref_counter as usize);
|
let ref_counter_struct_size =
|
||||||
|
(slice_buffer.data as usize) - (slice_buffer.ref_counter as usize);
|
||||||
|
|
||||||
//we need to allocate a new ref_counter struct and copy the old ref_counter and the new_buffer to it
|
//we need to allocate a new ref_counter struct and copy the old ref_counter and the new_buffer to it
|
||||||
let new_ref = {
|
let new_ref = {
|
||||||
let new_ref = libc::malloc(ref_counter_struct_size + new_buffer_length) as *mut c_void;
|
let new_ref = libc::malloc(ref_counter_struct_size + new_buffer_length) as *mut c_void;
|
||||||
libc::memcpy(new_ref, slice_buffer.ref_counter, ref_counter_struct_size);
|
libc::memcpy(new_ref, slice_buffer.ref_counter, ref_counter_struct_size);
|
||||||
libc::memcpy(new_ref.offset(ref_counter_struct_size as isize), new_buffer_data.as_ptr() as *const c_void, new_buffer_length);
|
libc::memcpy(
|
||||||
|
new_ref.offset(ref_counter_struct_size as isize),
|
||||||
|
new_buffer_data.as_ptr() as *const c_void,
|
||||||
|
new_buffer_length,
|
||||||
|
);
|
||||||
libc::free(slice_buffer.ref_counter);
|
libc::free(slice_buffer.ref_counter);
|
||||||
new_ref
|
new_ref
|
||||||
};
|
};
|
||||||
@@ -104,20 +143,25 @@ def_hook!(
|
|||||||
pub fn init() {
|
pub fn init() {
|
||||||
if let Some(signature) = sig::find_signature(
|
if let Some(signature) = sig::find_signature(
|
||||||
&common::CLIENT_MODULE,
|
&common::CLIENT_MODULE,
|
||||||
"A8 03 1F F8 ?? ?? 00 94 ?? ?? ?? 91 ?? ?? ?? A9", -0x48,
|
"AA A8 03 1F F8 ?? ?? 00 94 ?? ?? 05 91",
|
||||||
"0A 90 00 F0 3F F9", -0x37
|
-0x47,
|
||||||
|
"0A 90 00 F0 3F F9",
|
||||||
|
-0x37,
|
||||||
) {
|
) {
|
||||||
dobby_hook!(signature as *mut c_void, unary_call);
|
dobby_hook!(signature as *mut c_void, unary_call);
|
||||||
common::attach_jni_env(|env| {
|
common::attach_jni_env(|env| {
|
||||||
NATIVE_LIB_ON_UNARY_CALL_METHOD.set(
|
NATIVE_LIB_ON_UNARY_CALL_METHOD
|
||||||
env.get_method_id(
|
.set(
|
||||||
env.get_object_class(common::native_lib_instance()).unwrap(),
|
env.get_method_id(
|
||||||
"onNativeUnaryCall",
|
env.get_object_class(common::native_lib_instance()).unwrap(),
|
||||||
"(Ljava/lang/String;[B)Lme/eternal/purrfectsnap/nativelib/NativeRequestData;"
|
"onNativeUnaryCall",
|
||||||
).expect("Failed to get onNativeUnaryCall method id")
|
"(Ljava/lang/String;[B)Lme/eternal/purrfectsnap/nativelib/NativeRequestData;",
|
||||||
).expect("unary call method already set");
|
)
|
||||||
|
.expect("Failed to get onNativeUnaryCall method id"),
|
||||||
|
)
|
||||||
|
.expect("unary call method already set");
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
error!("Can't find unaryCall signature");
|
panic!("Can't find unaryCall signature");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
61
native/rust/src/modules/util/elf.rs
Normal file
61
native/rust/src/modules/util/elf.rs
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
use procfs::process::MMapPath;
|
||||||
|
|
||||||
|
pub struct Elf<'elf> {
|
||||||
|
base_address: usize,
|
||||||
|
elf: goblin::elf::Elf<'elf>,
|
||||||
|
_buffer: &'elf [u8],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'elf> Elf<'elf> {
|
||||||
|
pub fn from_maps(lib: &str) -> Option<Self> {
|
||||||
|
let maps = procfs::process::Process::myself().ok()?.maps().ok()?;
|
||||||
|
|
||||||
|
for memory_map in maps.iter() {
|
||||||
|
if let MMapPath::Path(path) = &memory_map.pathname {
|
||||||
|
let path = path.to_string_lossy();
|
||||||
|
|
||||||
|
if !path.contains(lib) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let file_data = std::fs::read(path.to_string()).ok()?;
|
||||||
|
let file_buffer = Box::leak(file_data.into_boxed_slice());
|
||||||
|
|
||||||
|
if let Ok(elf) = goblin::elf::Elf::parse(file_buffer) {
|
||||||
|
return Some(Elf {
|
||||||
|
base_address: memory_map.address.0 as usize,
|
||||||
|
elf,
|
||||||
|
_buffer: file_buffer,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
warn!(
|
||||||
|
"Failed to parse ELF for library {} at address {:x}",
|
||||||
|
lib, memory_map.address.0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_symbol_address(&self, symbol: &str) -> Option<usize> {
|
||||||
|
for sym in &self.elf.dynsyms {
|
||||||
|
if let Some(name) = self.elf.dynstrtab.get_at(sym.st_name) {
|
||||||
|
if name == symbol {
|
||||||
|
return Some(self.base_address + sym.st_value as usize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for sym in &self.elf.syms {
|
||||||
|
if let Some(name) = self.elf.strtab.get_at(sym.st_name) {
|
||||||
|
if name == symbol {
|
||||||
|
return Some(self.base_address + sym.st_value as usize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1 +1,2 @@
|
|||||||
|
pub mod elf;
|
||||||
pub mod valdi_utils;
|
pub mod valdi_utils;
|
||||||
|
|||||||
@@ -43,7 +43,12 @@ pub struct ValdiModule {
|
|||||||
impl ValdiModule {
|
impl ValdiModule {
|
||||||
pub fn parse(buffer: Vec<u8>) -> Result<ValdiModule, Error> {
|
pub fn parse(buffer: Vec<u8>) -> Result<ValdiModule, Error> {
|
||||||
let mut offset = 0;
|
let mut offset = 0;
|
||||||
let magic = u32::from_be_bytes([buffer[offset], buffer[offset + 1], buffer[offset + 2], buffer[offset + 3]]);
|
let magic = u32::from_be_bytes([
|
||||||
|
buffer[offset],
|
||||||
|
buffer[offset + 1],
|
||||||
|
buffer[offset + 2],
|
||||||
|
buffer[offset + 3],
|
||||||
|
]);
|
||||||
|
|
||||||
offset += 4;
|
offset += 4;
|
||||||
|
|
||||||
@@ -85,13 +90,12 @@ impl ValdiModule {
|
|||||||
tags.push(ModuleTag::new(has_padding, tag_buffer));
|
tags.push(ModuleTag::new(has_padding, tag_buffer));
|
||||||
}
|
}
|
||||||
|
|
||||||
let tags = tags.chunks(2).map(|chunk| {
|
let tags = tags
|
||||||
(chunk[0].clone(), chunk[1].clone())
|
.chunks(2)
|
||||||
}).collect();
|
.map(|chunk| (chunk[0].clone(), chunk[1].clone()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
Ok(ValdiModule {
|
Ok(ValdiModule { tags })
|
||||||
tags,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_bytes(&self) -> Vec<u8> {
|
pub fn to_bytes(&self) -> Vec<u8> {
|
||||||
|
|||||||
@@ -1,35 +1,29 @@
|
|||||||
#![allow(dead_code, unused_imports)]
|
use super::util::valdi_utils::{ModuleTag, ValdiModule};
|
||||||
|
use crate::{config, def_hook, dobby_hook, modules::util::elf, util::get_jni_string};
|
||||||
use super::util::valdi_utils::{ValdiModule, ModuleTag};
|
|
||||||
use std::{collections::HashMap, ffi::c_void, sync::Mutex};
|
|
||||||
use jni::{objects::JString, JNIEnv};
|
use jni::{objects::JString, JNIEnv};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use crate::{common, config, def_hook, dobby_hook, dobby_hook_sym, util::get_jni_string};
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
ffi::c_void,
|
||||||
|
sync::Mutex,
|
||||||
|
};
|
||||||
|
|
||||||
static AASSET_MAP: Lazy<Mutex<HashMap<usize, Vec<u8>>>> = Lazy::new(|| Mutex::new(HashMap::new()));
|
static AASSET_MAP: Lazy<Mutex<HashMap<usize, Vec<u8>>>> = Lazy::new(|| Mutex::new(HashMap::new()));
|
||||||
static LOADER_DATA: Mutex<Option<String>> = Mutex::new(None);
|
static LOADER_DATA: Mutex<Option<String>> = Mutex::new(None);
|
||||||
|
|
||||||
def_hook!(
|
def_hook!(aasset_get_length, i32, |arg0: *mut c_void| {
|
||||||
aasset_get_length,
|
if let Some(buffer) = AASSET_MAP.lock().unwrap().get(&(arg0 as usize)) {
|
||||||
i32,
|
return buffer.len() as i32;
|
||||||
|arg0: *mut c_void| {
|
|
||||||
if let Some(buffer) = AASSET_MAP.lock().unwrap().get(&(arg0 as usize)) {
|
|
||||||
return buffer.len() as i32;
|
|
||||||
}
|
|
||||||
aasset_get_length_original.unwrap()(arg0)
|
|
||||||
}
|
}
|
||||||
);
|
aasset_get_length_original.unwrap()(arg0)
|
||||||
|
});
|
||||||
|
|
||||||
def_hook!(
|
def_hook!(aasset_get_buffer, *const c_void, |arg0: *mut c_void| {
|
||||||
aasset_get_buffer,
|
if let Some(buffer) = AASSET_MAP.lock().unwrap().get(&(arg0 as usize)) {
|
||||||
*const c_void,
|
return buffer.as_ptr() as *const c_void;
|
||||||
|arg0: *mut c_void| {
|
|
||||||
if let Some(buffer) = AASSET_MAP.lock().unwrap().get(&(arg0 as usize)) {
|
|
||||||
return buffer.as_ptr() as *const c_void;
|
|
||||||
}
|
|
||||||
aasset_get_buffer_original.unwrap()(arg0)
|
|
||||||
}
|
}
|
||||||
);
|
aasset_get_buffer_original.unwrap()(arg0)
|
||||||
|
});
|
||||||
|
|
||||||
def_hook!(
|
def_hook!(
|
||||||
aasset_manager_open,
|
aasset_manager_open,
|
||||||
@@ -45,9 +39,13 @@ def_hook!(
|
|||||||
|
|
||||||
let loader_data = LOADER_DATA.lock().unwrap().clone().expect("No loader data");
|
let loader_data = LOADER_DATA.lock().unwrap().clone().expect("No loader data");
|
||||||
|
|
||||||
let archive_buffer: Vec<u8> = std::slice::from_raw_parts(asset_buffer as *const u8, asset_length as usize).to_vec();
|
let archive_buffer: Vec<u8> =
|
||||||
let decompressed = zstd::stream::decode_all(&archive_buffer[..]).expect("Failed to decompress valdi archive");
|
std::slice::from_raw_parts(asset_buffer as *const u8, asset_length as usize)
|
||||||
let mut valdi_module = ValdiModule::parse(decompressed).expect("Failed to parse valdi module");
|
.to_vec();
|
||||||
|
let decompressed = zstd::stream::decode_all(&archive_buffer[..])
|
||||||
|
.expect("Failed to decompress valdi archive");
|
||||||
|
let mut valdi_module =
|
||||||
|
ValdiModule::parse(decompressed).expect("Failed to parse valdi module");
|
||||||
|
|
||||||
let mut tags = valdi_module.get_tags();
|
let mut tags = valdi_module.get_tags();
|
||||||
let mut new_tags = Vec::new();
|
let mut new_tags = Vec::new();
|
||||||
@@ -58,19 +56,22 @@ def_hook!(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let old_file_name = name.split_once(".").unwrap().0.to_owned() + rand::random::<u32>().to_string().as_str();
|
let old_file_name = name.split_once(".").unwrap().0.to_owned()
|
||||||
|
+ rand::random::<u32>().to_string().as_str();
|
||||||
tag1.set_buffer((old_file_name.to_owned() + ".js").as_bytes().to_vec());
|
tag1.set_buffer((old_file_name.to_owned() + ".js").as_bytes().to_vec());
|
||||||
let original_module_path = path.split_once(".").unwrap().0.to_owned() + "/" + &old_file_name;
|
let original_module_path =
|
||||||
|
path.split_once(".").unwrap().0.to_owned() + "/" + &old_file_name;
|
||||||
|
|
||||||
let hooked_module = format!("{};module.exports = require(\"{}\");", loader_data, original_module_path);
|
let hooked_module = format!(
|
||||||
|
"{};module.exports = require(\"{}\");",
|
||||||
new_tags.push(
|
loader_data, original_module_path
|
||||||
(
|
|
||||||
ModuleTag::new(true, name.as_bytes().to_vec()),
|
|
||||||
ModuleTag::new(true, hooked_module.as_bytes().to_vec())
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
new_tags.push((
|
||||||
|
ModuleTag::new(true, name.as_bytes().to_vec()),
|
||||||
|
ModuleTag::new(true, hooked_module.as_bytes().to_vec()),
|
||||||
|
));
|
||||||
|
|
||||||
debug!("Valdi loader injected in {}", name);
|
debug!("Valdi loader injected in {}", name);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -79,22 +80,22 @@ def_hook!(
|
|||||||
valdi_module.set_tags(tags);
|
valdi_module.set_tags(tags);
|
||||||
|
|
||||||
let compressed = valdi_module.to_bytes();
|
let compressed = valdi_module.to_bytes();
|
||||||
let compressed = zstd::stream::encode_all(&compressed[..], 3).expect("Failed to compress");
|
let compressed =
|
||||||
|
zstd::stream::encode_all(&compressed[..], 3).expect("Failed to compress");
|
||||||
|
|
||||||
AASSET_MAP.lock().unwrap().insert(handle as usize, compressed);
|
AASSET_MAP
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.insert(handle as usize, compressed);
|
||||||
}
|
}
|
||||||
handle
|
handle
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
def_hook!(
|
def_hook!(aasset_close, c_void, |handle: *mut c_void| {
|
||||||
aasset_close,
|
AASSET_MAP.lock().unwrap().remove(&(handle as usize));
|
||||||
c_void,
|
aasset_close_original.unwrap()(handle)
|
||||||
|handle: *mut c_void| {
|
});
|
||||||
AASSET_MAP.lock().unwrap().remove(&(handle as usize));
|
|
||||||
aasset_close_original.unwrap()(handle)
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
pub fn set_valdi_loader(mut env: JNIEnv, _: *mut c_void, code: JString) {
|
pub fn set_valdi_loader(mut env: JNIEnv, _: *mut c_void, code: JString) {
|
||||||
let new_code = get_jni_string(&mut env, code).expect("Failed to get loader code");
|
let new_code = get_jni_string(&mut env, code).expect("Failed to get loader code");
|
||||||
@@ -103,11 +104,33 @@ pub fn set_valdi_loader(mut env: JNIEnv, _: *mut c_void, code: JString) {
|
|||||||
|
|
||||||
pub fn init() {
|
pub fn init() {
|
||||||
if !config::native_config().valdi_hooks {
|
if !config::native_config().valdi_hooks {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
dobby_hook_sym!("libandroid.so", "AAsset_getBuffer", aasset_get_buffer);
|
let lib_android =
|
||||||
dobby_hook_sym!("libandroid.so", "AAsset_getLength", aasset_get_length);
|
elf::Elf::from_maps("/libandroid.so").expect("Failed to find libandroid.so in maps");
|
||||||
dobby_hook_sym!("libandroid.so", "AAsset_close", aasset_close);
|
|
||||||
dobby_hook_sym!("libandroid.so", "AAssetManager_open", aasset_manager_open);
|
if let Some(ptr) = lib_android.get_symbol_address("AAsset_getBuffer") {
|
||||||
|
dobby_hook!(ptr as _, aasset_get_buffer);
|
||||||
|
} else {
|
||||||
|
panic!("Failed to find AAsset_getBuffer symbol");
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ptr) = lib_android.get_symbol_address("AAsset_getLength") {
|
||||||
|
dobby_hook!(ptr as _, aasset_get_length);
|
||||||
|
} else {
|
||||||
|
panic!("Failed to find AAsset_getLength symbol");
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ptr) = lib_android.get_symbol_address("AAsset_close") {
|
||||||
|
dobby_hook!(ptr as _, aasset_close);
|
||||||
|
} else {
|
||||||
|
panic!("Failed to find AAsset_close symbol");
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ptr) = lib_android.get_symbol_address("AAssetManager_open") {
|
||||||
|
dobby_hook!(ptr as _, aasset_manager_open);
|
||||||
|
} else {
|
||||||
|
panic!("Failed to find AAssetManager_open symbol");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,8 +61,18 @@ pub fn find_signatures(module_base: usize, bytes_buffer: &[u8], pattern: &str, o
|
|||||||
let mut mask = Vec::new();
|
let mut mask = Vec::new();
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
|
|
||||||
if let Some(cache) = SIGNATURE_CACHE.lock().unwrap().iter().find(|(sig, _)| sig == pattern) {
|
if let Some(cache) = SIGNATURE_CACHE
|
||||||
return cache.1.clone().into_iter().map(|offset| module_base + offset).collect();
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.iter()
|
||||||
|
.find(|(sig, _)| sig == pattern)
|
||||||
|
{
|
||||||
|
return cache
|
||||||
|
.1
|
||||||
|
.clone()
|
||||||
|
.into_iter()
|
||||||
|
.map(|offset| module_base + offset)
|
||||||
|
.collect();
|
||||||
}
|
}
|
||||||
|
|
||||||
while i < pattern.len() {
|
while i < pattern.len() {
|
||||||
@@ -70,7 +80,7 @@ pub fn find_signatures(module_base: usize, bytes_buffer: &[u8], pattern: &str, o
|
|||||||
bytes.push(0);
|
bytes.push(0);
|
||||||
mask.push('?');
|
mask.push('?');
|
||||||
} else {
|
} else {
|
||||||
bytes.push(u8::from_str_radix(&pattern[i..i+2], 16).unwrap());
|
bytes.push(u8::from_str_radix(&pattern[i..i + 2], 16).unwrap());
|
||||||
mask.push('x');
|
mask.push('x');
|
||||||
}
|
}
|
||||||
i += 3;
|
i += 3;
|
||||||
@@ -92,7 +102,10 @@ pub fn find_signatures(module_base: usize, bytes_buffer: &[u8], pattern: &str, o
|
|||||||
}
|
}
|
||||||
if found {
|
if found {
|
||||||
if once {
|
if once {
|
||||||
SIGNATURE_CACHE.lock().unwrap().push((pattern.to_string(), vec![i]));
|
SIGNATURE_CACHE
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.push((pattern.to_string(), vec![i]));
|
||||||
return vec![module_base + i];
|
return vec![module_base + i];
|
||||||
}
|
}
|
||||||
results.push(module_base + i);
|
results.push(module_base + i);
|
||||||
@@ -100,14 +113,22 @@ pub fn find_signatures(module_base: usize, bytes_buffer: &[u8], pattern: &str, o
|
|||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
SIGNATURE_CACHE.lock().unwrap().push((pattern.to_string(), results.clone()));
|
SIGNATURE_CACHE
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.push((pattern.to_string(), results.clone()));
|
||||||
results
|
results
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_signature_executable(mapped_lib: &MappedLib, pattern: &str) -> Option<usize> {
|
pub fn find_signature_executable(mapped_lib: &MappedLib, pattern: &str) -> Option<usize> {
|
||||||
let executable_regions = mapped_lib.regions.iter().filter(|region| {
|
let executable_regions = mapped_lib
|
||||||
region.perms.contains(MMPermissions::EXECUTE)
|
.regions
|
||||||
}).collect::<Vec<_>>();
|
.iter()
|
||||||
|
.filter(|region| {
|
||||||
|
region.perms.contains(MMPermissions::EXECUTE)
|
||||||
|
&& region.perms.contains(MMPermissions::READ)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
for region in executable_regions {
|
for region in executable_regions {
|
||||||
let size = (region.end - region.start) as usize;
|
let size = (region.end - region.start) as usize;
|
||||||
@@ -117,16 +138,27 @@ pub fn find_signature_executable(mapped_lib: &MappedLib, pattern: &str) -> Optio
|
|||||||
let bytes_buffer = match read_region_bytes(module_base, size) {
|
let bytes_buffer = match read_region_bytes(module_base, size) {
|
||||||
Some(buffer) => buffer,
|
Some(buffer) => buffer,
|
||||||
None => {
|
None => {
|
||||||
warn!("Unable to read executable region: {:#x} - {:#x}", region.start, region.end);
|
warn!(
|
||||||
|
"Unable to read executable region: {:#x} - {:#x}",
|
||||||
|
region.start, region.end
|
||||||
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let results = find_signatures(module_base, &bytes_buffer, pattern, true);
|
let results = find_signatures(module_base, &bytes_buffer, pattern, true);
|
||||||
|
|
||||||
if results.is_empty() {
|
if results.is_empty() {
|
||||||
warn!("Signature not found in region: {:#x} - {:#x}", region.start, region.end);
|
warn!(
|
||||||
|
"Signature not found in region: {:#x} - {:#x}",
|
||||||
|
region.start, region.end
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
debug!("Found {} results in region: {:#x} - {:#x}", results.len(), region.start, region.end);
|
debug!(
|
||||||
|
"Found {} results in region: {:#x} - {:#x}",
|
||||||
|
results.len(),
|
||||||
|
region.start,
|
||||||
|
region.end
|
||||||
|
);
|
||||||
return Some(results[0]);
|
return Some(results[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -135,13 +167,21 @@ pub fn find_signature_executable(mapped_lib: &MappedLib, pattern: &str) -> Optio
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_signature(mapped_lib: &MappedLib, _arm64_pattern: &str, _arm64_offset: i64, _arm32_pattern: &str, _arm32_offset: i64) -> Option<usize> {
|
pub fn find_signature(
|
||||||
|
mapped_lib: &MappedLib,
|
||||||
|
_arm64_pattern: &str,
|
||||||
|
_arm64_offset: i64,
|
||||||
|
_arm32_pattern: &str,
|
||||||
|
_arm32_offset: i64,
|
||||||
|
) -> Option<usize> {
|
||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(target_arch = "aarch64")]
|
||||||
{
|
{
|
||||||
return find_signature_executable(mapped_lib, _arm64_pattern).map(|address| (address as i64 + _arm64_offset) as usize);
|
return find_signature_executable(mapped_lib, _arm64_pattern)
|
||||||
|
.map(|address| (address as i64 + _arm64_offset) as usize);
|
||||||
}
|
}
|
||||||
#[cfg(target_arch = "arm")]
|
#[cfg(target_arch = "arm")]
|
||||||
{
|
{
|
||||||
return find_signature_executable(mapped_lib, _arm32_pattern).map(|address| (address as i64 + _arm32_offset) as usize);
|
return find_signature_executable(mapped_lib, _arm32_pattern)
|
||||||
|
.map(|address| (address as i64 + _arm32_offset) as usize);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user