commit 5702420f89061d79c4ecc2af560f3fcf0fb2b5b5
parent 56e434cb15307c6bf53a2acd7eb62e725c0902fb
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date:   Sat,  5 Jul 2025 16:40:36 +0200

refactor: security features
- remove remote shared library manager
- prevent plugin from loading in newer versions

Signed-off-by: rhunk <101876869+rhunk@users.noreply.github.com>

Diffstat:
Dapp/src/main/kotlin/me/rhunk/snapenhance/RemoteSharedLibraryManager.kt | 124-------------------------------------------------------------------------------
Mapp/src/main/kotlin/me/rhunk/snapenhance/RemoteSideContext.kt | 12------------
Mapp/src/main/kotlin/me/rhunk/snapenhance/ui/manager/pages/home/HomeSettings.kt | 5+----
Mapp/src/main/kotlin/me/rhunk/snapenhance/ui/setup/SetupActivity.kt | 8++++----
Dapp/src/main/kotlin/me/rhunk/snapenhance/ui/setup/screens/impl/SecurityScreen.kt | 133-------------------------------------------------------------------------------
Dapp/src/main/kotlin/me/rhunk/snapenhance/ui/setup/screens/impl/WelcomeScreen.kt | 18------------------
Mcommon/src/main/assets/lang/en_US.json | 4----
Mcommon/src/main/kotlin/me/rhunk/snapenhance/common/bridge/BridgeFiles.kt | 3+--
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/ModContext.kt | 2+-
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/SecurityFeatures.kt | 83++++++++++++++++++++++++++++++++++---------------------------------------------
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/SnapEnhance.kt | 28+++++++++++++---------------
Mmapper/src/main/kotlin/me/rhunk/snapenhance/mapper/ClassMapper.kt | 1+
Amapper/src/main/kotlin/me/rhunk/snapenhance/mapper/impl/PlatformClientAttestationMapper.kt | 29+++++++++++++++++++++++++++++
13 files changed, 86 insertions(+), 364 deletions(-)

diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/RemoteSharedLibraryManager.kt b/app/src/main/kotlin/me/rhunk/snapenhance/RemoteSharedLibraryManager.kt @@ -1,123 +0,0 @@ -package me.rhunk.snapenhance - -import android.annotation.SuppressLint -import android.app.Notification -import android.app.NotificationChannel -import android.app.NotificationManager -import android.app.PendingIntent -import android.content.Intent -import android.os.Build -import androidx.core.net.toUri -import me.rhunk.snapenhance.common.BuildConfig -import me.rhunk.snapenhance.common.bridge.InternalFileHandleType -import okhttp3.OkHttpClient -import okhttp3.Request -import java.io.File - -class RemoteSharedLibraryManager( - private val remoteSideContext: RemoteSideContext -) { - private val okHttpClient = OkHttpClient() - - private fun getVersion(): String? { - return runCatching { - okHttpClient.newCall( - Request.Builder() - .url("${BuildConfig.SIF_ENDPOINT}/version") - .build() - ).execute().use { response -> - if (!response.isSuccessful) { - return null - } - response.body.string() - } - }.getOrNull() - } - - private fun downloadLatest(outputFile: File): Boolean { - val abi = Build.SUPPORTED_ABIS.firstOrNull() ?: return false - val request = Request.Builder() - .url("${BuildConfig.SIF_ENDPOINT}/$abi.so") - .build() - runCatching { - okHttpClient.newCall(request).execute().use { response -> - if (!response.isSuccessful) { - return false - } - response.body.byteStream().use { input -> - outputFile.outputStream().use { output -> - input.copyTo(output) - } - } - return true - } - }.onFailure { - remoteSideContext.log.error("Failed to download latest sif", it) - } - return false - } - - @SuppressLint("ApplySharedPref") - fun init() { - val libraryFile = InternalFileHandleType.SIF.resolve(remoteSideContext.androidContext) - val currentVersion = remoteSideContext.sharedPreferences.getString("sif", null)?.trim() - if (currentVersion == null || currentVersion == "false") { - libraryFile.takeIf { it.exists() }?.delete() - remoteSideContext.log.info("sif can't be loaded due to user preference") - return - } - val latestVersion = getVersion()?.trim() ?: run { - throw Exception("Failed to get latest sif version") - } - - if (currentVersion == latestVersion) { - remoteSideContext.log.info("sif is up to date ($currentVersion)") - return - } - - remoteSideContext.log.info("Updating sif from $currentVersion to $latestVersion") - if (downloadLatest(libraryFile)) { - remoteSideContext.sharedPreferences.edit().putString("sif", latestVersion).commit() - remoteSideContext.shortToast("SIF updated to $latestVersion!") - - if (currentVersion.isNotEmpty()) { - val notificationManager = remoteSideContext.androidContext.getSystemService(NotificationManager::class.java) - val channelId = "sif_update" - - notificationManager.createNotificationChannel( - NotificationChannel( - channelId, - "SIF Updates", - NotificationManager.IMPORTANCE_DEFAULT - ) - ) - - notificationManager.notify( - System.nanoTime().toInt(), - Notification.Builder(remoteSideContext.androidContext, channelId) - .setContentTitle("SnapEnhance") - .setContentText("Security Features have been updated to version $latestVersion") - .setSmallIcon(android.R.drawable.stat_sys_download_done) - .setContentIntent(PendingIntent.getActivity( - remoteSideContext.androidContext, - 0, - Intent().apply { - action = Intent.ACTION_VIEW - data = "https://github.com/SnapEnhance/resources".toUri() - flags = Intent.FLAG_ACTIVITY_NEW_TASK - }, - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE - )).build() - ) - } - - // force restart snapchat - runCatching { - remoteSideContext.config.configStateListener?.takeIf { it.asBinder().pingBinder() }?.onRestartRequired() - } - } else { - remoteSideContext.log.warn("Failed to download latest sif") - throw Exception("Failed to download latest sif") - } - } -}- \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/RemoteSideContext.kt b/app/src/main/kotlin/me/rhunk/snapenhance/RemoteSideContext.kt @@ -78,7 +78,6 @@ class RemoteSideContext( val tracker = RemoteTracker(this) val accountStorage = RemoteAccountStorage(this) val locationManager = RemoteLocationManager(this) - val remoteSharedLibraryManager = RemoteSharedLibraryManager(this) //used to load bitmoji selfies and download previews val imageLoader by lazy { @@ -132,13 +131,6 @@ class RemoteSideContext( messageLogger.purgeTrackerLogs(it) } } - coroutineScope.launch { - runCatching { - remoteSharedLibraryManager.init() - }.onFailure { - log.error("Failed to init RemoteSharedLibraryManager", it) - } - } } }.onFailure { log.error("Failed to load RemoteSideContext", it) @@ -220,10 +212,6 @@ class RemoteSideContext( requirements = requirements or Requirements.MAPPINGS } - if (sharedPreferences.getString("sif", null) == null) { - requirements = requirements or Requirements.SIF - } - if (requirements == 0) return false val currentContext = activity ?: androidContext diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/pages/home/HomeSettings.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/pages/home/HomeSettings.kt @@ -154,9 +154,6 @@ class HomeSettings : Routes.Route() { RowAction(key = "change_language") { context.checkForRequirements(Requirements.LANGUAGE) } - RowAction(key = "security_features") { - context.checkForRequirements(Requirements.SIF) - } RowTitle(title = translation["message_logger_title"]) ShiftedRow { Column( @@ -285,7 +282,7 @@ class HomeSettings : Routes.Route() { Column( verticalArrangement = Arrangement.spacedBy(4.dp), ) { - PreferenceToggle(context.sharedPreferences, key = "enable_security_features", text = "Enable Security Features") + PreferenceToggle(context.sharedPreferences, key = "test_mode", text = "Test Mode (Debugging)") PreferenceToggle(context.sharedPreferences, key = "disable_feature_loading", text = "Disable Feature Loading") PreferenceToggle(context.sharedPreferences, key = "disable_mapper", text = "Disable Auto Mapper") } diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ui/setup/SetupActivity.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ui/setup/SetupActivity.kt @@ -29,7 +29,10 @@ import androidx.navigation.compose.rememberNavController import me.rhunk.snapenhance.SharedContextHolder import me.rhunk.snapenhance.common.ui.AppMaterialTheme import me.rhunk.snapenhance.ui.setup.screens.SetupScreen -import me.rhunk.snapenhance.ui.setup.screens.impl.* +import me.rhunk.snapenhance.ui.setup.screens.impl.MappingsScreen +import me.rhunk.snapenhance.ui.setup.screens.impl.PermissionsScreen +import me.rhunk.snapenhance.ui.setup.screens.impl.PickLanguageScreen +import me.rhunk.snapenhance.ui.setup.screens.impl.SaveFolderScreen class SetupActivity : ComponentActivity() { @@ -66,9 +69,6 @@ class SetupActivity : ComponentActivity() { if (isFirstRun || hasRequirement(Requirements.MAPPINGS)) { add(MappingsScreen().apply { route = "mappings" }) } - if (isFirstRun || hasRequirement(Requirements.SIF)) { - add(SecurityScreen().apply { route = "security" }) - } } // If there are no required screens, we can just finish the activity diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ui/setup/screens/impl/SecurityScreen.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ui/setup/screens/impl/SecurityScreen.kt @@ -1,132 +0,0 @@ -package me.rhunk.snapenhance.ui.setup.screens.impl - -import android.annotation.SuppressLint -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.WarningAmber -import androidx.compose.material3.* -import androidx.compose.runtime.* -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import me.rhunk.snapenhance.ui.setup.screens.SetupScreen - -class SecurityScreen : SetupScreen() { - @SuppressLint("ApplySharedPref") - @Composable - override fun Content() { - Icon( - imageVector = Icons.Default.WarningAmber, - contentDescription = null, - modifier = Modifier - .padding(16.dp) - .size(30.dp), - ) - - DialogText( - "Since Snapchat has implemented additional security measures against third-party applications such as SnapEnhance, we offer a non-opensource solution that reduces the risk of banning and prevents Snapchat from detecting SnapEnhance. " + - "\nPlease note that this solution does not provide a ban bypass or spoofer for anything, and does not take any personal data or communicate with the network." + - "\nWe also encourage you to use official signed builds to avoid compromising the security of your account." + - "\nIf you're having trouble using the solution, or are experiencing crashes, join the Telegram Group for help: https://t.me/snapenhance_chat" - ) - - var denyDialog by remember { mutableStateOf(false) } - - if (denyDialog) { - AlertDialog( - onDismissRequest = { - denyDialog = false - }, - text = { - Text("Are you sure you don't want to use this solution? You can always change this later in the settings in the SnapEnhance app.") - }, - dismissButton = { - Button(onClick = { - denyDialog = false - }) { - Text("Go back") - } - }, - confirmButton = { - Button(onClick = { - context.sharedPreferences.edit().putString("sif", "false").apply() - goNext() - }) { - Text("Yes, I'm sure") - } - } - ) - } - - var downloadJob by remember { mutableStateOf(null as Job?) } - var jobError by remember { mutableStateOf(null as Throwable?) } - - if (downloadJob != null) { - AlertDialog(onDismissRequest = { - downloadJob?.cancel() - downloadJob = null - }, confirmButton = {}, text = { - Column( - modifier = Modifier.verticalScroll(rememberScrollState()).fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(10.dp), - ) { - if (jobError != null) { - Text("Failed to download the required files.\n\n${jobError?.message}") - } else { - Text("Downloading the required files...") - CircularProgressIndicator(modifier = Modifier.padding(16.dp)) - } - } - }) - } - - fun newDownloadJob() { - downloadJob?.cancel() - downloadJob = context.coroutineScope.launch { - context.sharedPreferences.edit().putString("sif", "").commit() - runCatching { - context.remoteSharedLibraryManager.init() - }.onFailure { - jobError = it - context.log.error("Failed to download the required files", it) - }.onSuccess { - downloadJob = null - withContext(Dispatchers.Main) { - goNext() - } - } - } - } - - Column ( - modifier = Modifier.padding(16.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(10.dp) - ) { - Button( - onClick = { - newDownloadJob() - } - ) { - Text("Accept and continue", fontSize = 18.sp, fontWeight = FontWeight.Bold) - } - Button( - colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.error), - onClick = { - denyDialog = true - } - ) { - Text("I don't want to use this solution") - } - } - } -}- \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ui/setup/screens/impl/WelcomeScreen.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ui/setup/screens/impl/WelcomeScreen.kt @@ -1,17 +0,0 @@ -package me.rhunk.snapenhance.ui.setup.screens.impl - -import androidx.compose.material3.Button -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import me.rhunk.snapenhance.ui.setup.screens.SetupScreen - -class WelcomeScreen : SetupScreen() { - - @Composable - override fun Content() { - Text(text = "Welcome") - Button(onClick = { allowNext(true) }) { - Text(text = "Next") - } - } -}- \ No newline at end of file diff --git a/common/src/main/assets/lang/en_US.json b/common/src/main/assets/lang/en_US.json @@ -316,10 +316,6 @@ "name": "Change Language", "description": "Change the language of SnapEnhance" }, - "security_features": { - "name": "Security Features", - "description": "Change security features preferences" - }, "file_imports": { "name": "File Imports", "description": "Import files for use in Snapchat" diff --git a/common/src/main/kotlin/me/rhunk/snapenhance/common/bridge/BridgeFiles.kt b/common/src/main/kotlin/me/rhunk/snapenhance/common/bridge/BridgeFiles.kt @@ -34,8 +34,7 @@ enum class InternalFileHandleType( MAPPINGS("mappings", "mappings.json"), MESSAGE_LOGGER("message_logger", "message_logger.db", isDatabase = true), PINNED_BEST_FRIEND("pinned_best_friend", "pinned_best_friend.txt"), - NATIVE_SIG_CACHE("native_sig_cache", "native_sig_cache.txt"), - SIF("sif", "libsif.so"); + NATIVE_SIG_CACHE("native_sig_cache", "native_sig_cache.txt"); fun resolve(context: Context): File = if (isDatabase) { context.getDatabasePath(fileName) diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/ModContext.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/ModContext.kt @@ -75,7 +75,7 @@ class ModContext( val isDeveloper by lazy { config.scripting.developerMode.get() } var isMainActivityPaused = true - var isSafeMode = false + var disablePlugin = false fun <T : Feature> feature(featureClass: KClass<T>): T { return features.get(featureClass)!! diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/SecurityFeatures.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/SecurityFeatures.kt @@ -18,12 +18,13 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import kotlinx.coroutines.delay import me.rhunk.snapenhance.common.bridge.FileHandleScope -import me.rhunk.snapenhance.common.bridge.InternalFileHandleType import me.rhunk.snapenhance.common.bridge.toWrapper import me.rhunk.snapenhance.common.config.MOD_DETECTION_VERSION_CHECK import me.rhunk.snapenhance.common.config.VersionRequirement import me.rhunk.snapenhance.common.ui.createComposeView +import me.rhunk.snapenhance.core.event.events.impl.UnaryCallEvent import me.rhunk.snapenhance.core.ui.CustomComposable +import me.rhunk.snapenhance.core.util.ktx.getObjectField class SecurityFeatures( private val context: ModContext @@ -38,67 +39,55 @@ class SecurityFeatures( transact(this, 0)?.toString(2)?.padStart(32, '0')?.count { it == '1' } } - private fun isSafeMode(): Boolean { + fun init() { val snapchatVersionCode = context.androidContext.packageManager?.getPackageInfo(context.androidContext.packageName, 0)?.longVersionCode ?: throw IllegalStateException("Failed to get version code") - val shouldUseSafeMode = MOD_DETECTION_VERSION_CHECK.checkVersion(snapchatVersionCode)?.second == VersionRequirement.OLDER_REQUIRED + var shouldDisablePlugin = MOD_DETECTION_VERSION_CHECK.checkVersion(snapchatVersionCode)?.second == VersionRequirement.OLDER_REQUIRED + // load user shared library context.config.experimental.nativeHooks.customSharedLibrary.get().takeIf { it.isNotEmpty() }?.let { runCatching { context.native.loadSharedLibrary( context.fileHandlerManager.getFileHandle(FileHandleScope.USER_IMPORT.key, it).toWrapper().readBytes() ) context.log.verbose("loaded custom shared library") + shouldDisablePlugin = false + + lateinit var composable: CustomComposable + composable = { + Row( + modifier = Modifier + .padding(16.dp) + .align(Alignment.TopCenter), + ) { + Icon(Icons.Filled.Check, contentDescription = null, tint = Color(0xFF85A947)) + } + + LaunchedEffect(Unit) { + delay(2500) + context.inAppOverlay.removeCustomComposable(composable) + } + } + + context.inAppOverlay.addCustomComposable(composable) }.onFailure { context.log.error("Failed to load custom shared library", it) - return true } - } ?: context.bridgeClient.getDebugProp("enable_security_features", "false").takeIf { it == "true" }?.runCatching { - context.native.loadSharedLibrary( - context.fileHandlerManager.getFileHandle(FileHandleScope.INTERNAL.key, InternalFileHandleType.SIF.key) - .toWrapper() - .readBytes() - .takeIf { - it.isNotEmpty() - } ?: throw IllegalStateException("Binary is empty") - ) - context.log.verbose("loaded sif") - }?.onFailure { - context.log.error("Failed to load sif: " + it.message) - return shouldUseSafeMode - } ?: context.log.warn("Security features are disabled") - - token // pre init token - - val status = getStatus() - val safeMode = shouldUseSafeMode && (status == null || status < 2) - - if (status != null && status >= 2) { - context.log.verbose("status=$status") - lateinit var composable: CustomComposable - composable = { - Row( - modifier = Modifier - .padding(16.dp) - .align(Alignment.TopCenter), - ) { - Icon(Icons.Filled.Check, contentDescription = null, tint = Color(0xFF85A947)) - } + } - LaunchedEffect(Unit) { - delay(2500) - context.inAppOverlay.removeCustomComposable(composable) - } - } - context.inAppOverlay.addCustomComposable(composable) + if (context.bridgeClient.getDebugProp("test_mode", "false") == "true") { + shouldDisablePlugin = false } - return safeMode - } + context.disablePlugin = shouldDisablePlugin + context.log.verbose("disablePlugin=${context.disablePlugin}") + if (!context.disablePlugin) return - fun init() { - context.isSafeMode = isSafeMode() - context.log.verbose("isSafeMode=${context.isSafeMode}") - if (!context.isSafeMode) return + context.event.subscribe(UnaryCallEvent::class) { event -> + val callOptions = event.adapter.arg<Any>(2).let { it.javaClass.getMethod("build").invoke(it) } ?: return@subscribe + if (callOptions.getObjectField("mAttestation") != null) { + event.canceled = true + } + } context.features.addActivityCreateListener { activity -> if (!activity.javaClass.name.endsWith("LoginSignupActivity")) return@addActivityCreateListener diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/SnapEnhance.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/SnapEnhance.kt @@ -18,7 +18,6 @@ import me.rhunk.snapenhance.common.action.EnumAction import me.rhunk.snapenhance.common.bridge.FileHandleScope import me.rhunk.snapenhance.common.bridge.InternalFileHandleType import me.rhunk.snapenhance.common.bridge.toWrapper -import me.rhunk.snapenhance.common.config.MOD_DETECTION_VERSION_CHECK import me.rhunk.snapenhance.common.data.FriendStreaks import me.rhunk.snapenhance.common.data.MessagingFriendInfo import me.rhunk.snapenhance.common.data.MessagingGroupInfo @@ -33,6 +32,7 @@ import me.rhunk.snapenhance.core.util.hook.HookAdapter import me.rhunk.snapenhance.core.util.hook.HookStage import me.rhunk.snapenhance.core.util.hook.findRestrictedMethod import me.rhunk.snapenhance.core.util.hook.hook +import me.rhunk.snapenhance.mapper.impl.PlatformClientAttestationMapper import kotlin.reflect.KClass import kotlin.system.exitProcess import kotlin.system.measureTimeMillis @@ -231,24 +231,22 @@ class SnapEnhance { else arrayOf(ClassLoader::class.java, String::class.java) ) }!!.apply { - if (appContext.isSafeMode) { + if (appContext.disablePlugin) { hook(HookStage.BEFORE) { param -> if (param.arg<String>(1) != "scplugin") return@hook param.setResult(null) - appContext.runOnUiThread { - appContext.inAppOverlay.showStatusToast( - Icons.Outlined.Cancel, - "SnapEnhance is not compatible with this version of Snapchat and will result in a ban.\nUse Snapchat ${MOD_DETECTION_VERSION_CHECK.maxVersion?.first ?: "0.0.0"} or older to avoid detections.", - durationMs = 7000, - maxLines = 6 - ) + appContext.log.verbose("skipped scplugin load") + + appContext.mappings.useMapper(PlatformClientAttestationMapper::class) { + pluginNativeClass.getAsClass()?.methods?.filter { + it.declaringClass == pluginNativeClass.getAsClass() + }?.forEach { method -> + method.hook(HookStage.BEFORE) { + appContext.log.verbose("called $method") + it.setResult(null) + } + } ?: error("Failed to get pluginNativeClass class") } - runCatching { - Thread.sleep(Long.MAX_VALUE) - }.onFailure { - appContext.log.error(it) - } - exitProcess(1) } } diff --git a/mapper/src/main/kotlin/me/rhunk/snapenhance/mapper/ClassMapper.kt b/mapper/src/main/kotlin/me/rhunk/snapenhance/mapper/ClassMapper.kt @@ -39,6 +39,7 @@ class ClassMapper( StreaksExpirationMapper(), COFObservableMapper(), FoldingLayoutMapper(), + PlatformClientAttestationMapper(), ) } diff --git a/mapper/src/main/kotlin/me/rhunk/snapenhance/mapper/impl/PlatformClientAttestationMapper.kt b/mapper/src/main/kotlin/me/rhunk/snapenhance/mapper/impl/PlatformClientAttestationMapper.kt @@ -0,0 +1,28 @@ +package me.rhunk.snapenhance.mapper.impl + +import com.android.tools.smali.dexlib2.Opcode +import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction35c +import com.android.tools.smali.dexlib2.iface.reference.MethodReference +import me.rhunk.snapenhance.mapper.AbstractClassMapper +import me.rhunk.snapenhance.mapper.ext.getSuperClassName + +class PlatformClientAttestationMapper: AbstractClassMapper("PlatformClientAttestationMapper") { + val pluginNativeClass = classReference("pluginNativeClass") + + init { + mapper { + for (clazz in classes) { + if (clazz.getSuperClassName()?.endsWith("PlatformClientAttestation") != true) continue + val getSignatureMethod = clazz.methods.firstOrNull { it.name == "getSignature" } ?: continue + + getSignatureMethod.implementation?.instructions?.firstOrNull { instruction -> + instruction.opcode == Opcode.INVOKE_STATIC && instruction is Instruction35c && (instruction.reference as? MethodReference)?.takeIf { it.definingClass.count { it == '/' } == 1 }?.returnType == "[B" + }?.let { instruction -> + val method = (instruction as Instruction35c).reference as MethodReference + pluginNativeClass.set(method.definingClass.replaceFirst("L", "").replaceFirst(";", "").replace("/", ".")) + } + return@mapper + } + } + } +}+ \ No newline at end of file