diff --git a/app/libs/ffmpeg-kit-full-gpl-6.0-2.LTS.aar b/app/libs/ffmpeg-kit-full-gpl-6.0-2.LTS.aar index 97c88c48..e698a0fe 100644 Binary files a/app/libs/ffmpeg-kit-full-gpl-6.0-2.LTS.aar and b/app/libs/ffmpeg-kit-full-gpl-6.0-2.LTS.aar differ diff --git a/app/src/main/kotlin/me/eternal/purrfectsnap/bridge/BiometricPromptActivity.kt b/app/src/main/kotlin/me/eternal/purrfectsnap/bridge/BiometricPromptActivity.kt index 29940660..c6f3f46e 100644 --- a/app/src/main/kotlin/me/eternal/purrfectsnap/bridge/BiometricPromptActivity.kt +++ b/app/src/main/kotlin/me/eternal/purrfectsnap/bridge/BiometricPromptActivity.kt @@ -3,15 +3,19 @@ package me.eternal.purrfectsnap.bridge import android.content.Intent import android.hardware.biometrics.BiometricManager import android.hardware.biometrics.BiometricPrompt +import android.hardware.fingerprint.FingerprintManager import android.os.Build import android.os.Bundle import android.os.CancellationSignal +import android.app.KeyguardManager import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import me.eternal.purrfectsnap.SharedContextHolder import java.util.concurrent.Executors class BiometricPromptActivity: ComponentActivity() { + private val deviceCredentialRequestCode = 2201 + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -21,16 +25,50 @@ class BiometricPromptActivity: ComponentActivity() { } val remoteSideContext = SharedContextHolder.remote(this) + val executor = Executors.newSingleThreadExecutor() + val negativeText = remoteSideContext.translation.getOrNull("biometric_auth.cancel") + ?.takeIf { it.isNotBlank() } + ?: "Cancel" + val titleText = remoteSideContext.translation.getOrNull("biometric_auth.title") + ?.takeIf { it.isNotBlank() } + ?: "Unlock" + val subtitleText = remoteSideContext.translation.getOrNull("biometric_auth.subtitle") + ?.takeIf { it.isNotBlank() } + ?: "Confirm your screen lock" + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + val fingerprintManager = getSystemService(FingerprintManager::class.java) + val hasFingerprintSupport = runCatching { + fingerprintManager?.isHardwareDetected == true && fingerprintManager.hasEnrolledFingerprints() + }.getOrDefault(false) + if (!hasFingerprintSupport) { + val keyguardManager = getSystemService(KeyguardManager::class.java) + val canUseDeviceCredential = keyguardManager?.isKeyguardSecure == true + if (canUseDeviceCredential) { + val intent = keyguardManager.createConfirmDeviceCredentialIntent(titleText, subtitleText) + if (intent != null) { + startActivityForResult(intent, deviceCredentialRequestCode) + setContent {} + return + } + } + cancel() + setContent {} + return + } + } BiometricPrompt.Builder(this@BiometricPromptActivity) - .setTitle(remoteSideContext.translation["biometric_auth.title"]) - .setSubtitle(remoteSideContext.translation["biometric_auth.subtitle"]) + .setTitle(titleText) + .setSubtitle(subtitleText) .apply { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_WEAK or BiometricManager.Authenticators.DEVICE_CREDENTIAL) } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { @Suppress("DEPRECATION") setDeviceCredentialAllowed(true) + } else { + setNegativeButton(negativeText, mainExecutor) { _, _ -> cancel() } } } .build().authenticate( @@ -39,7 +77,7 @@ class BiometricPromptActivity: ComponentActivity() { cancel() } }, - Executors.newSingleThreadExecutor(), + executor, object: BiometricPrompt.AuthenticationCallback() { override fun onAuthenticationError(errorCode: Int, errString: CharSequence?) { cancel() @@ -54,4 +92,16 @@ class BiometricPromptActivity: ComponentActivity() { setContent {} } -} \ No newline at end of file + + @Suppress("DEPRECATION") + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + if (requestCode != deviceCredentialRequestCode) return + if (resultCode == RESULT_OK) { + setResult(RESULT_OK, Intent()) + } else { + setResult(RESULT_CANCELED, Intent()) + } + finish() + } +} diff --git a/app/src/main/kotlin/me/eternal/purrfectsnap/bridge/BridgeService.kt b/app/src/main/kotlin/me/eternal/purrfectsnap/bridge/BridgeService.kt index d9c2bf21..aedc7f22 100644 --- a/app/src/main/kotlin/me/eternal/purrfectsnap/bridge/BridgeService.kt +++ b/app/src/main/kotlin/me/eternal/purrfectsnap/bridge/BridgeService.kt @@ -7,6 +7,7 @@ import android.os.ParcelFileDescriptor import kotlinx.coroutines.runBlocking import me.eternal.purrfectsnap.RemoteSideContext import me.eternal.purrfectsnap.SharedContextHolder +import me.eternal.purrfectsnap.bridge.call.CallDownloadSession import me.eternal.purrfectsnap.bridge.snapclient.MessagingBridge import me.eternal.purrfectsnap.common.data.MessagingFriendInfo import me.eternal.purrfectsnap.common.data.MessagingGroupInfo @@ -16,6 +17,7 @@ import me.eternal.purrfectsnap.common.ui.OverlayType import me.eternal.purrfectsnap.common.util.toParcelable import me.eternal.purrfectsnap.download.DownloadProcessor import me.eternal.purrfectsnap.download.FFMpegProcessor +import me.eternal.purrfectsnap.download.call.CallDownloadSessionImpl import me.eternal.purrfectsnap.storage.* import me.eternal.purrfectsnap.task.Task import me.eternal.purrfectsnap.task.TaskType @@ -248,5 +250,16 @@ class BridgeService : Service() { override fun getDebugProp(key: String, defaultValue: String?): String? { return remoteSideContext.sharedPreferences.all["debug_$key"]?.toString() ?: defaultValue } + + override fun startCallDownload( + startTimestamp: Long, + author: String + ): CallDownloadSession { + return CallDownloadSessionImpl( + context = remoteSideContext, + callStartTimestamp = startTimestamp, + author = author + ) + } } } diff --git a/app/src/main/kotlin/me/eternal/purrfectsnap/download/FFMpegProcessor.kt b/app/src/main/kotlin/me/eternal/purrfectsnap/download/FFMpegProcessor.kt index 2d893f3a..b9ded183 100644 --- a/app/src/main/kotlin/me/eternal/purrfectsnap/download/FFMpegProcessor.kt +++ b/app/src/main/kotlin/me/eternal/purrfectsnap/download/FFMpegProcessor.kt @@ -59,6 +59,12 @@ class FFMpegProcessor( pendingTask.updateProgress("Processing (frames=${it.videoFrameNumber}, fps=${it.videoFps}, time=${it.time}, bitrate=${it.bitrate}, speed=${it.speed})") } ) + + fun newFFMpegProcessor(context: RemoteSideContext, onStatistics: (Statistics) -> Unit = {}) = FFMpegProcessor( + logManager = context.log, + ffmpegOptions = context.config.root.downloader.ffmpegOptions, + onStatistics = onStatistics + ) } enum class Action { DOWNLOAD_DASH, @@ -66,6 +72,7 @@ class FFMpegProcessor( CONVERSION, MERGE_MEDIA, DOWNLOAD_AUDIO_STREAM, + MERGE_AUDIO_STREAMS, } data class Request( @@ -76,6 +83,7 @@ class FFMpegProcessor( val startTime: Long? = null, //only for DOWNLOAD_DASH val duration: Long? = null, //only for DOWNLOAD_DASH val audioStreamFormat: AudioStreamFormat? = null, //only for DOWNLOAD_AUDIO_STREAM + val inputDelayOffsets: Map? = null, // only for MERGE_AUDIO_STREAMS var videoCodec: String? = null, var audioCodec: String? = null, @@ -225,6 +233,26 @@ class FFMpegProcessor( globalArguments += "-ar" to args.audioStreamFormat.sampleRate.toString() globalArguments += "-ac" to args.audioStreamFormat.channels.toString() } + Action.MERGE_AUDIO_STREAMS -> { + inputArguments.clear() + outputArguments.clear() + val filterParts = StringBuilder() + args.inputs.forEachIndexed { index, input -> + inputArguments += "-i" to input + val offset = args.inputDelayOffsets?.get(input) ?: 0L + if (offset > 0) { + filterParts.append("[$index:a]adelay=$offset|$offset[a$index];") + } else { + filterParts.append("[$index:a]acopy[a$index];") + } + } + args.inputs.indices.forEach { index -> + filterParts.append("[a$index]") + } + filterParts.append("amix=inputs=${args.inputs.size}:duration=longest:normalize=0[aout]") + outputArguments += "-filter_complex" to "\"$filterParts\"" + outputArguments += "-map" to "\"[aout]\"" + } } outputArguments += args.output.absolutePath try { diff --git a/app/src/main/kotlin/me/eternal/purrfectsnap/download/call/CallDownloadSessionImpl.kt b/app/src/main/kotlin/me/eternal/purrfectsnap/download/call/CallDownloadSessionImpl.kt new file mode 100644 index 00000000..28667a50 --- /dev/null +++ b/app/src/main/kotlin/me/eternal/purrfectsnap/download/call/CallDownloadSessionImpl.kt @@ -0,0 +1,175 @@ +package me.eternal.purrfectsnap.download.call + +import android.os.ParcelFileDescriptor +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import me.eternal.purrfectsnap.RemoteSideContext +import me.eternal.purrfectsnap.bridge.DownloadCallback +import me.eternal.purrfectsnap.bridge.call.CallDownloadSession +import me.eternal.purrfectsnap.common.data.download.AudioStreamFormat +import me.eternal.purrfectsnap.common.data.download.DownloadMetadata +import me.eternal.purrfectsnap.common.data.download.MediaDownloadSource +import me.eternal.purrfectsnap.common.data.download.createNewFilePath +import me.eternal.purrfectsnap.download.DownloadProcessor +import me.eternal.purrfectsnap.download.FFMpegProcessor +import me.eternal.purrfectsnap.task.PendingTaskListener +import me.eternal.purrfectsnap.task.Task +import me.eternal.purrfectsnap.task.TaskType +import java.util.UUID +import java.util.concurrent.CopyOnWriteArrayList +import kotlin.math.absoluteValue + +class CallDownloadSessionImpl( + private val context: RemoteSideContext, + private val callStartTimestamp: Long, + private val author: String, +): CallDownloadSession.Stub() { + private val coroutineScope = CoroutineScope(Dispatchers.IO) + private var callEnded: Boolean = false + + private val streams = CopyOnWriteArrayList() + private var mergeJob: Job? = null + + init { + context.log.verbose("Starting call callStartTimestamp=$callStartTimestamp") + } + + inner class CallStream( + val startTimestampMillis: Long, + private val audioStreamFormat: AudioStreamFormat + ) { + val job: Job + val writePfd: ParcelFileDescriptor + + val outputFile = context.androidContext.cacheDir.resolve("call_${UUID.randomUUID()}.mp3").apply { + if (exists()) delete() + } + + init { + val pipe = ParcelFileDescriptor.createPipe() + writePfd = pipe[1] + + job = coroutineScope.launch { + runCatching { + FFMpegProcessor.newFFMpegProcessor(context).execute( + FFMpegProcessor.Request( + action = FFMpegProcessor.Action.DOWNLOAD_AUDIO_STREAM, + inputs = listOf("/proc/self/fd/${pipe[0].fd}"), + output = outputFile, + audioStreamFormat = audioStreamFormat + ) + ) + }.onFailure { + context.log.error("Error converting call audio stream", it) + runCatching { + outputFile.delete() + } + } + + pipe.forEach { runCatching { it.close() } } + + context.log.verbose("Call stream ended startTimestampMillis=$startTimestampMillis") + } + } + } + + override fun createStream( + startTimestampMillis: Long, + channels: Int, + sampleRate: Int, + encoding: Int + ): ParcelFileDescriptor? { + if (callEnded) return null + + context.log.verbose("createFileDescriptor startTimestampMillis=$startTimestampMillis, channels=$channels, sampleRate=$sampleRate, encoding=$encoding") + val callStream = CallStream( + startTimestampMillis = startTimestampMillis, + audioStreamFormat = AudioStreamFormat( + channels = channels, + sampleRate = sampleRate, + encoding = encoding + ) + ) + + synchronized(streams) { + streams.add(callStream) + } + + return callStream.writePfd + } + + override fun end() { + if (callEnded) return + callEnded = true + + if (streams.isEmpty()) { + context.log.verbose("No call chunks to merge") + return + } + + val outputFile = context.androidContext.cacheDir.resolve("call_${UUID.randomUUID()}_final.mp3") + val pendingTask = context.taskManager.createPendingTask( + Task( + type = TaskType.DOWNLOAD, + title = "Call Recording $author", + author = author, + hash = UUID.randomUUID().toString() + ) + ).apply { + addListener(PendingTaskListener( + onCancel = { + mergeJob?.cancel() + outputFile.delete() + streams.forEach { stream -> + stream.outputFile.delete() + } + } + )) + } + + mergeJob = coroutineScope.launch { + try { + streams.forEach { stream -> + stream.job.join() + } + + val sortedStreams = streams.filter { it.outputFile.exists() }.sortedBy { it.startTimestampMillis } + FFMpegProcessor.newFFMpegProcessor(context, pendingTask).execute( + FFMpegProcessor.Request( + action = FFMpegProcessor.Action.MERGE_AUDIO_STREAMS, + inputs = sortedStreams.map { it.outputFile.absolutePath }, + output = outputFile, + inputDelayOffsets = sortedStreams.associate { stream -> stream.outputFile.absolutePath to (stream.startTimestampMillis - callStartTimestamp) } + ) + ) + + DownloadProcessor(context, object: DownloadCallback.Default() { + override fun onSuccess(outputPath: String) { + context.log.verbose("Downloaded call $outputPath") + } + }).saveMediaToGallery(pendingTask, outputFile, DownloadMetadata( + mediaIdentifier = UUID.randomUUID().toString(), + outputPath = createNewFilePath( + context.config.root, + UUID.randomUUID().toString().hashCode().absoluteValue.toString(16), + downloadSource = MediaDownloadSource.VOICE_CALL, + mediaAuthor = author, + creationTimestamp = System.currentTimeMillis() + ), + mediaAuthor = author, + downloadSource = MediaDownloadSource.VOICE_CALL.translate(context.translation), + iconUrl = null + )) + } finally { + streams.forEach { stream -> + stream.outputFile.delete() + } + outputFile.delete() + } + + context.log.verbose("ending call") + } + } +} diff --git a/changelogs-stable.txt b/changelogs-stable.txt index 5ac2f444..4c064293 100644 --- a/changelogs-stable.txt +++ b/changelogs-stable.txt @@ -1,4 +1,4 @@ -## v1.1.1 +## v1.1.2 - Fix: Quick Tiles crash. ## v1.1.0 diff --git a/common/src/main/aidl/me/eternal/purrfectsnap/bridge/BridgeInterface.aidl b/common/src/main/aidl/me/eternal/purrfectsnap/bridge/BridgeInterface.aidl index fbc1e377..f86ac18e 100644 --- a/common/src/main/aidl/me/eternal/purrfectsnap/bridge/BridgeInterface.aidl +++ b/common/src/main/aidl/me/eternal/purrfectsnap/bridge/BridgeInterface.aidl @@ -12,6 +12,7 @@ import me.eternal.purrfectsnap.bridge.snapclient.MessagingBridge; import me.eternal.purrfectsnap.bridge.AccountStorage; import me.eternal.purrfectsnap.bridge.storage.FileHandleManager; import me.eternal.purrfectsnap.bridge.location.LocationManager; +import me.eternal.purrfectsnap.bridge.call.CallDownloadSession; import me.eternal.purrfectsnap.bridge.task.TaskInterface; interface BridgeInterface { @@ -105,4 +106,6 @@ interface BridgeInterface { oneway void registerConfigStateListener(in ConfigStateListener listener); @nullable String getDebugProp(String key, @nullable String defaultValue); + + CallDownloadSession startCallDownload(long startTimestamp, String author); } diff --git a/common/src/main/aidl/me/eternal/purrfectsnap/bridge/call/CallDownloadSession.aidl b/common/src/main/aidl/me/eternal/purrfectsnap/bridge/call/CallDownloadSession.aidl new file mode 100644 index 00000000..7966452e --- /dev/null +++ b/common/src/main/aidl/me/eternal/purrfectsnap/bridge/call/CallDownloadSession.aidl @@ -0,0 +1,6 @@ +package me.eternal.purrfectsnap.bridge.call; + +interface CallDownloadSession { + ParcelFileDescriptor createStream(long startTimestampMillis, int channels, int sampleRate, int encoding); + oneway void end(); +} diff --git a/common/src/main/assets/lang/en_US.json b/common/src/main/assets/lang/en_US.json index 49101afa..3f78dccc 100644 --- a/common/src/main/assets/lang/en_US.json +++ b/common/src/main/assets/lang/en_US.json @@ -765,6 +765,10 @@ "name": "Auto Download Voice Notes", "description": "Automatically downloads voice notes when playing them" }, + "call_recorder": { + "name": "Call Recorder", + "description": "Automatically records audio calls" + }, "download_profile_pictures": { "name": "Download Profile Pictures", "description": "Allows you to download Profile Pictures from the profile page" @@ -1814,10 +1818,6 @@ "name": "Story Logger", "description": "Provides a history of friends stories" }, - "call_recorder": { - "name": "Call Recorder", - "description": "Automatically records audio calls" - }, "account_switcher": { "name": "Account Switcher", "description": "Allows you to switch between accounts without logging out\nLong press on the search icon next to your Bitmoji profile to open the menu\nNote: This feature is experimental and will likely change in the future", @@ -2143,6 +2143,11 @@ "back": "Back Camera", "null": "Remember last used" }, + "call_recorder": { + "only_record_self": "Only Record Self", + "only_record_others": "Only Record Others", + "record_both": "Record Both Sides" + }, "front_custom_frame_rate": { "null": "Device default FPS" }, diff --git a/common/src/main/kotlin/me/eternal/purrfectsnap/common/config/impl/DownloaderConfig.kt b/common/src/main/kotlin/me/eternal/purrfectsnap/common/config/impl/DownloaderConfig.kt index e84f40ac..dc7cc6d7 100644 --- a/common/src/main/kotlin/me/eternal/purrfectsnap/common/config/impl/DownloaderConfig.kt +++ b/common/src/main/kotlin/me/eternal/purrfectsnap/common/config/impl/DownloaderConfig.kt @@ -50,4 +50,5 @@ class DownloaderConfig : ConfigContainer() { set(mutableListOf("success", "progress", "failure")) } val customPathFormat = string("custom_path_format") { addNotices(FeatureNotice.UNSTABLE) } -} \ No newline at end of file + val callRecorder = unique("call_recorder", "only_record_self", "only_record_others", "record_both") { requireRestart(); addNotices(FeatureNotice.UNSTABLE) } +} diff --git a/common/src/main/kotlin/me/eternal/purrfectsnap/common/config/impl/Experimental.kt b/common/src/main/kotlin/me/eternal/purrfectsnap/common/config/impl/Experimental.kt index 67195973..1ec80c73 100644 --- a/common/src/main/kotlin/me/eternal/purrfectsnap/common/config/impl/Experimental.kt +++ b/common/src/main/kotlin/me/eternal/purrfectsnap/common/config/impl/Experimental.kt @@ -66,7 +66,6 @@ class Experimental : ConfigContainer() { val convertMessageLocally = boolean("convert_message_locally") { requireRestart() } val mediaFilePicker = boolean("media_file_picker") { requireRestart(); addNotices(FeatureNotice.UNSTABLE) } val storyLogger = boolean("story_logger") { requireRestart(); addNotices(FeatureNotice.UNSTABLE); } - val callRecorder = boolean("call_recorder") { requireRestart(); addNotices(FeatureNotice.UNSTABLE); } val accountSwitcher = container("account_switcher", AccountSwitcherConfig()) { requireRestart(); addNotices(FeatureNotice.UNSTABLE) } val betterTranscript = container("better_transcript", BetterTranscriptConfig()) { requireRestart() } val voiceNoteAutoPlay = boolean("voice_note_auto_play") { requireRestart() } diff --git a/core/src/main/kotlin/me/eternal/purrfectsnap/core/ModContext.kt b/core/src/main/kotlin/me/eternal/purrfectsnap/core/ModContext.kt index 41c58ff2..0fa846d3 100644 --- a/core/src/main/kotlin/me/eternal/purrfectsnap/core/ModContext.kt +++ b/core/src/main/kotlin/me/eternal/purrfectsnap/core/ModContext.kt @@ -4,6 +4,7 @@ import android.app.Activity import android.content.Context import android.content.Intent import android.content.res.Resources +import android.os.Build import android.os.Handler import android.os.Looper import android.os.Process @@ -161,7 +162,8 @@ class ModContext( NativeConfig( disableBitmoji = config.experimental.nativeHooks.disableBitmoji.get(), disableMetrics = config.global.disableMetrics.get(), - valdiHooks = config.experimental.nativeHooks.valdiHooks.globalState == true, + valdiHooks = config.experimental.nativeHooks.valdiHooks.globalState == true && + Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q, customEmojiFontPath = getCustomEmojiFontPath(this) ) ) diff --git a/core/src/main/kotlin/me/eternal/purrfectsnap/core/PurrfectSnap.kt b/core/src/main/kotlin/me/eternal/purrfectsnap/core/PurrfectSnap.kt index a805c46f..cfd06a2f 100644 --- a/core/src/main/kotlin/me/eternal/purrfectsnap/core/PurrfectSnap.kt +++ b/core/src/main/kotlin/me/eternal/purrfectsnap/core/PurrfectSnap.kt @@ -7,6 +7,7 @@ import android.content.res.Resources import android.os.Build import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Cancel +import java.lang.reflect.Method import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -49,6 +50,8 @@ class PurrfectSnap { } private lateinit var appContext: ModContext private var isBridgeInitialized = false + private var android9ValdiBindDisabled = false + private var android9ValdiBindDisableLogged = false private fun hookMainActivity(methodName: String, stage: HookStage = HookStage.AFTER, block: Activity.(param: HookAdapter) -> Unit) { Activity::class.java.hook(methodName, stage, { isBridgeInitialized }) { param -> @@ -156,6 +159,7 @@ class PurrfectSnap { } reloadConfig() + installAndroid9ValdiBindGuard() initNative() initWidgetListener() scope.launch(Dispatchers.IO) { @@ -244,6 +248,89 @@ class PurrfectSnap { } } + private fun installAndroid9ValdiBindGuard() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) return + val valdiNativeBridge = runCatching { + classLoader.loadClass("com.snapchat.client.valdi.NativeBridge") + }.getOrNull() + val targetClasses = listOf("AXj", "NZj", "MZj") + .mapNotNull { className -> + runCatching { classLoader.loadClass(className) }.getOrNull() + } + .distinct() + if (targetClasses.isEmpty()) return + fun defaultReturn(type: Class<*>): Any? = when (type) { + Boolean::class.javaPrimitiveType -> false + Byte::class.javaPrimitiveType -> 0.toByte() + Short::class.javaPrimitiveType -> 0.toShort() + Int::class.javaPrimitiveType -> 0 + Long::class.javaPrimitiveType -> 0L + Float::class.javaPrimitiveType -> 0f + Double::class.javaPrimitiveType -> 0.0 + Char::class.javaPrimitiveType -> 0.toChar() + Void.TYPE -> null + else -> null + } + + fun installGuard(targetClass: Class<*>, methodName: String) { + targetClass.hook(methodName, HookStage.BEFORE) { param -> + val method = param.method() as? Method ?: return@hook + if (android9ValdiBindDisabled) { + if (!android9ValdiBindDisableLogged) { + android9ValdiBindDisableLogged = true + appContext.log.warn("Skipping Valdi bind on Android 9 due to missing native impl") + } + param.setResult(defaultReturn(method.returnType)) + return@hook + } + runCatching { + param.invokeOriginal() + }.onSuccess { result -> + param.setResult(result) + }.onFailure { throwable -> + val rootCause = throwable.cause ?: throwable + val message = rootCause.message ?: throwable.message + val isMissingNativeImpl = rootCause is UnsatisfiedLinkError && + message?.contains("NativeBridge.createContext") == true + val isValdiContextNpe = rootCause is NullPointerException && + message?.contains("ValdiContext") == true + if (isMissingNativeImpl || isValdiContextNpe) { + android9ValdiBindDisabled = true + if (!android9ValdiBindDisableLogged) { + android9ValdiBindDisableLogged = true + appContext.log.warn("Skipping Valdi bind on Android 9 due to missing native impl") + if (isMissingNativeImpl) { + appContext.log.error("Android 9 Valdi NativeBridge.createContext missing native impl", rootCause) + } + } + param.setResult(defaultReturn(method.returnType)) + return@hook + } + appContext.log.error("Android 9 Valdi bind hook failed", throwable) + param.setResult(defaultReturn(method.returnType)) + } + } + } + + targetClasses.forEach { targetClass -> + installGuard(targetClass, "f") + installGuard(targetClass, "g2") + installGuard(targetClass, "n2") + installGuard(targetClass, "d") + } + + valdiNativeBridge?.hook("createContext", HookStage.AFTER) { param -> + val throwable = param.throwable() as? UnsatisfiedLinkError ?: return@hook + android9ValdiBindDisabled = true + if (!android9ValdiBindDisableLogged) { + android9ValdiBindDisableLogged = true + appContext.log.warn("Skipping Valdi bind on Android 9 due to missing native impl") + appContext.log.error("Android 9 Valdi NativeBridge.createContext missing native impl", throwable) + } + param.setResult(null) + } + } + private fun initNative() { val nativeSigCacheFileHandle = appContext.fileHandlerManager.getFileHandle(FileHandleScope.INTERNAL.key, InternalFileHandleType.NATIVE_SIG_CACHE.key).toWrapper() diff --git a/core/src/main/kotlin/me/eternal/purrfectsnap/core/bridge/BridgeClient.kt b/core/src/main/kotlin/me/eternal/purrfectsnap/core/bridge/BridgeClient.kt index 15274a81..d41aa076 100644 --- a/core/src/main/kotlin/me/eternal/purrfectsnap/core/bridge/BridgeClient.kt +++ b/core/src/main/kotlin/me/eternal/purrfectsnap/core/bridge/BridgeClient.kt @@ -14,6 +14,7 @@ import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.withPermit import kotlinx.coroutines.withTimeoutOrNull import me.eternal.purrfectsnap.bridge.* +import me.eternal.purrfectsnap.bridge.call.CallDownloadSession import me.eternal.purrfectsnap.bridge.e2ee.E2eeInterface import me.eternal.purrfectsnap.bridge.location.LocationManager import me.eternal.purrfectsnap.bridge.logger.LoggerInterface @@ -284,5 +285,12 @@ class BridgeClient( fun registerConfigStateListener(listener: ConfigStateListener) = safeServiceCall { service.registerConfigStateListener(listener) } fun getDebugProp(name: String, defaultValue: String? = null): String? = safeServiceCall { service.getDebugProp(name, defaultValue) } + + fun startCallDownload( + startTimestamp: Long, + author: String, + ): CallDownloadSession { + return safeServiceCall { service.startCallDownload(startTimestamp, author) } + } } diff --git a/core/src/main/kotlin/me/eternal/purrfectsnap/core/event/EventDispatcher.kt b/core/src/main/kotlin/me/eternal/purrfectsnap/core/event/EventDispatcher.kt index 16809046..9765cd33 100644 --- a/core/src/main/kotlin/me/eternal/purrfectsnap/core/event/EventDispatcher.kt +++ b/core/src/main/kotlin/me/eternal/purrfectsnap/core/event/EventDispatcher.kt @@ -5,6 +5,7 @@ import android.content.Intent import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.os.Build import me.eternal.purrfectsnap.common.util.snap.SnapWidgetBroadcastReceiverHelper import me.eternal.purrfectsnap.core.ModContext import me.eternal.purrfectsnap.core.event.events.impl.* @@ -27,6 +28,10 @@ class EventDispatcher( private val context: ModContext ) { private fun hookViewBinder() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + context.log.warn("BindViewEvent hooks disabled on Android 9 and below") + return + } context.mappings.useMapper(ViewBinderMapper::class) { val cachedHooks = mutableListOf() fun cacheHook(clazz: Class<*>, block: Class<*>.() -> Unit) { diff --git a/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/FeatureManager.kt b/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/FeatureManager.kt index c650e4eb..20fade1f 100644 --- a/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/FeatureManager.kt +++ b/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/FeatureManager.kt @@ -6,6 +6,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import me.eternal.purrfectsnap.core.ModContext import me.eternal.purrfectsnap.core.features.impl.* +import me.eternal.purrfectsnap.core.features.impl.downloader.CallRecorder import me.eternal.purrfectsnap.core.features.impl.downloader.MediaDownloader import me.eternal.purrfectsnap.core.features.impl.downloader.ProfilePictureDownloader import me.eternal.purrfectsnap.core.features.impl.experiments.* diff --git a/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/impl/downloader/CallRecorder.kt b/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/impl/downloader/CallRecorder.kt new file mode 100644 index 00000000..53f9a3bc --- /dev/null +++ b/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/impl/downloader/CallRecorder.kt @@ -0,0 +1,178 @@ +package me.eternal.purrfectsnap.core.features.impl.downloader + +import android.media.AudioAttributes +import android.media.AudioFormat +import android.media.AudioRecord +import android.media.AudioTrack +import android.os.ParcelFileDescriptor +import me.eternal.purrfectsnap.bridge.call.CallDownloadSession +import me.eternal.purrfectsnap.core.features.Feature +import me.eternal.purrfectsnap.core.util.hook.HookStage +import me.eternal.purrfectsnap.core.util.hook.hook +import me.eternal.purrfectsnap.core.util.hook.hookConstructor +import me.eternal.purrfectsnap.core.util.ktx.getObjectField +import me.eternal.purrfectsnap.core.util.ktx.getObjectFieldOrNull +import me.eternal.purrfectsnap.core.wrapper.impl.SnapUUID +import java.io.OutputStream +import java.nio.ByteBuffer +import java.util.concurrent.ConcurrentHashMap + +class CallRecorder : Feature("Call Recorder") { + private var wasInCall = false + private var callDownloadSession: CallDownloadSession? = null + + inner class LazyStream( + private val audioFormat: AudioFormat, + private val startTimestamp: Long = System.currentTimeMillis(), + ) { + private var stream: ParcelFileDescriptor.AutoCloseOutputStream? = null + + fun get(): OutputStream? { + if (stream != null) return stream + if (callDownloadSession == null) return null + + stream = ParcelFileDescriptor.AutoCloseOutputStream( + callDownloadSession?.createStream( + startTimestamp, + audioFormat.channelCount, + audioFormat.sampleRate, + audioFormat.encoding + ) ?: return null + ) + + return stream + } + } + + private fun initCallDownloadSession(conversationId: String) { + val author = (if (context.database.getConversationType(conversationId) == 1) { + context.database.getFeedEntryByConversationId(conversationId)?.feedDisplayName + } else { + context.database.getDMOtherParticipant(conversationId)?.let { context.database.getFriendInfo(it)?.mutableUsername } + }) ?: "unknown" + callDownloadSession = context.bridgeClient.startCallDownload(System.currentTimeMillis(), author) + } + + private fun onCallStarted(conversationId: String) { + initCallDownloadSession(conversationId) + } + + private fun onCallEnded(conversationId: String) { + callDownloadSession?.end() + } + + override fun init() { + val callRecorderConfig = context.config.downloader.callRecorder.getNullable() + if (callRecorderConfig == null) return + + val streams = ConcurrentHashMap() // audioTrack -> stream + + runCatching { + findClass("com.snapchat.talkcorev3.CallingSessionState") + }.getOrNull()?.hookConstructor(HookStage.AFTER) { param -> + val instance = param.thisObject() + val callingState = instance.getObjectFieldOrNull("mLocalUser")?.getObjectField("mCallingState") + + if (callingState.toString() == "IN_CALL") { + // TODO: implement for older Snapchat versions + } + } ?: findClass("com.snapchat.talkcorev3.TSCallingStateUpdateParams").hookConstructor( + HookStage.AFTER) { param -> + val instance = param.thisObject() + val conversationId = SnapUUID(instance.getObjectField("mConversationId")).toString() + + if (instance.getObjectFieldOrNull("mInCall") == true) { + if (!wasInCall) { + wasInCall = true + onCallStarted(conversationId) + } + } else { + if (wasInCall) { + wasInCall = false + onCallEnded(conversationId) + } + } + } + + + AudioRecord::class.java.apply { + if (callRecorderConfig == "only_record_others") return@apply + declaredConstructors.first { it.parameterCount > 5 }.hook(HookStage.AFTER) { param -> + val audioAttributes = param.arg(0) + context.log.verbose(audioAttributes.usage) + if (audioAttributes.usage != AudioAttributes.USAGE_UNKNOWN) return@hook + val audioFormat = param.arg(1) + val hashCode = param.thisObject().hashCode() + + streams.put(hashCode, LazyStream(audioFormat)) + context.log.verbose("AudioRecord called usage=${audioAttributes.usage}, format=$audioFormat") + } + + getMethod("read", ByteBuffer::class.java, Int::class.javaPrimitiveType, Int::class.javaPrimitiveType).hook( + HookStage.AFTER) { param -> + val readBytes = param.getResult() as Int + if (readBytes <= 0) return@hook + + streams[param.thisObject().hashCode()]?.let { handlers -> + val byteBuffer = param.arg(0) + val position = byteBuffer.position() + val buffer = ByteArray(readBytes) + byteBuffer.get(buffer) + byteBuffer.position(position) + + runCatching { + handlers.get()?.write(buffer, 0, buffer.size) + }.onFailure { + context.log.error("Failed to record call audio data", it) + } + } + } + + hook("release", HookStage.BEFORE) { + runCatching { + streams.remove(it.thisObject().hashCode())?.get()?.close() + } + } + } + + AudioTrack::class.java.apply { + if (callRecorderConfig == "only_record_self") return@apply + getConstructor( + AudioAttributes::class.java, + AudioFormat::class.java, + Int::class.javaPrimitiveType, + Int::class.javaPrimitiveType, + Int::class.javaPrimitiveType, + ).hook(HookStage.AFTER) { param -> + val audioAttributes = param.arg(0) + if (audioAttributes.usage != AudioAttributes.USAGE_VOICE_COMMUNICATION) return@hook + val audioFormat = param.arg(1) + val hashCode = param.thisObject().hashCode() + + streams.put(hashCode, LazyStream(audioFormat)) + context.log.verbose("AudioTrack called usage=${audioAttributes.usage}, format=$audioFormat") + } + + getMethod("write", ByteBuffer::class.java, Int::class.javaPrimitiveType, Int::class.javaPrimitiveType).hook( + HookStage.BEFORE) { param -> + streams[param.thisObject().hashCode()]?.let { handlers -> + val byteBuffer = param.arg(0) + val position = byteBuffer.position() + val buffer = ByteArray(param.arg(1)) + byteBuffer.get(buffer) + byteBuffer.position(position) + + runCatching { + handlers.get()?.write(buffer, 0, buffer.size) + }.onFailure { + context.log.error("Failed to record call audio data", it) + } + } + } + + hook("release", HookStage.BEFORE) { + runCatching { streams.remove(it.thisObject().hashCode())?.get()?.close() } + } + } + } +} diff --git a/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/impl/downloader/ProfilePictureDownloader.kt b/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/impl/downloader/ProfilePictureDownloader.kt index 27353093..eab6ca9d 100644 --- a/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/impl/downloader/ProfilePictureDownloader.kt +++ b/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/impl/downloader/ProfilePictureDownloader.kt @@ -6,6 +6,7 @@ import android.widget.RelativeLayout import me.eternal.purrfectsnap.common.util.protobuf.ProtoReader import me.eternal.purrfectsnap.core.event.events.impl.AddViewEvent import me.eternal.purrfectsnap.core.event.events.impl.NetworkApiRequestEvent +import me.eternal.purrfectsnap.core.event.events.impl.UnaryCallEvent import me.eternal.purrfectsnap.core.features.Feature import me.eternal.purrfectsnap.core.ui.ViewAppearanceHelper @@ -19,11 +20,23 @@ class ProfilePictureDownloader : Feature("ProfilePictureDownloader") { var avatarUrl: String? = null onNextActivityCreate(defer = true) { + val profileViewClasses = setOf( + "com.snap.unifiedpublicprofile.UnifiedPublicProfileView", + "com.snap.modules.profile3.UserProfileV2RootComponent", + "com.snap.profile.ui.flatland.UnifiedProfileFlatlandProfileView" + ) + context.event.subscribe(AddViewEvent::class) { event -> - if (event.view::class.java.name != "com.snap.unifiedpublicprofile.UnifiedPublicProfileView") return@subscribe + if (event.view::class.java.name !in profileViewClasses) return@subscribe + + val buttonText = this@ProfilePictureDownloader.context.translation["profile_picture_downloader.button"] + if ((0 until event.parent.childCount).any { + val child = event.parent.getChildAt(it) + child is Button && (child as Button).text == buttonText + }) return@subscribe event.parent.addView(Button(event.parent.context).apply { - text = this@ProfilePictureDownloader.context.translation["profile_picture_downloader.button"] + text = buttonText layoutParams = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT).apply { setMargins(0, 200, 0, 0) } @@ -36,16 +49,21 @@ class ProfilePictureDownloader : Feature("ProfilePictureDownloader") { backgroundUrl?.let { choices["background_option"] = it } avatarUrl?.let { choices["avatar_option"] = it } - setItems(choices.keys.map { - this@ProfilePictureDownloader.context.translation["profile_picture_downloader.$it"] - }.toTypedArray()) { _, which -> - runCatching { - this@ProfilePictureDownloader.context.feature(MediaDownloader::class).downloadProfilePicture( - choices.values.elementAt(which), - friendUsername!! - ) - }.onFailure { - this@ProfilePictureDownloader.context.log.error("Failed to download profile picture", it) + if (choices.isEmpty()) { + setMessage("No profile pictures available. Please wait for the profile to load.") + setPositiveButton("OK") { dialog, _ -> dialog.dismiss() } + } else { + setItems(choices.keys.map { + this@ProfilePictureDownloader.context.translation["profile_picture_downloader.$it"] + }.toTypedArray()) { _, which -> + runCatching { + this@ProfilePictureDownloader.context.feature(MediaDownloader::class).downloadProfilePicture( + choices.values.elementAt(which), + friendUsername ?: "unknown" + ) + }.onFailure { + this@ProfilePictureDownloader.context.log.error("Failed to download profile picture", it) + } } } }.show() @@ -54,18 +72,31 @@ class ProfilePictureDownloader : Feature("ProfilePictureDownloader") { } - context.event.subscribe(NetworkApiRequestEvent::class) { event -> - if (!event.url.endsWith("/rpc/getPublicProfile")) return@subscribe - event.onSuccess { buffer -> - ProtoReader(buffer ?: return@onSuccess).followPath(1, 1, 2) { + fun parseProfileData(buffer: ByteArray) { + runCatching { + ProtoReader(buffer).followPath(1, 1, 2) { friendUsername = getString(2) ?: return@followPath followPath(4) { backgroundUrl = getString(2) avatarUrl = getString(100) } } + }.onFailure { + context.log.error("Failed to parse profile picture data", it) + } + } + + context.event.subscribe(NetworkApiRequestEvent::class) { event -> + if (!event.url.contains("getPublicProfile")) return@subscribe + event.onSuccess { buffer -> buffer?.let { parseProfileData(it) } } + } + + context.event.subscribe(UnaryCallEvent::class) { event -> + if (!event.uri.contains("getPublicProfile")) return@subscribe + event.addResponseCallback { + parseProfileData(buffer) } } } } -} \ No newline at end of file +} diff --git a/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/impl/experiments/CallRecorder.kt b/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/impl/experiments/CallRecorder.kt deleted file mode 100644 index 17bb976b..00000000 --- a/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/impl/experiments/CallRecorder.kt +++ /dev/null @@ -1,141 +0,0 @@ -package me.eternal.purrfectsnap.core.features.impl.experiments - -import android.media.AudioAttributes -import android.media.AudioFormat -import android.media.AudioTrack -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.withTimeoutOrNull -import me.eternal.purrfectsnap.common.data.download.AudioStreamFormat -import me.eternal.purrfectsnap.common.data.download.MediaDownloadSource -import me.eternal.purrfectsnap.core.features.Feature -import me.eternal.purrfectsnap.core.features.impl.downloader.MediaDownloader -import me.eternal.purrfectsnap.core.util.hook.HookStage -import me.eternal.purrfectsnap.core.util.hook.hook -import me.eternal.purrfectsnap.core.util.hook.hookConstructor -import me.eternal.purrfectsnap.core.util.ktx.getObjectField -import me.eternal.purrfectsnap.core.util.ktx.getObjectFieldOrNull -import me.eternal.purrfectsnap.core.util.media.HttpServer -import java.io.PipedInputStream -import java.io.PipedOutputStream -import java.nio.ByteBuffer -import java.util.UUID -import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.CopyOnWriteArrayList - -class CallRecorder : Feature("Call Recorder") { - private val httpServer = HttpServer( - timeout = Integer.MAX_VALUE - ) - - override fun init() { - if (!context.config.experimental.callRecorder.get()) return - - val streamHandlers = ConcurrentHashMap Unit>>() // audioTrack -> handlers - val participants = CopyOnWriteArrayList() - - runCatching { - findClass("com.snapchat.talkcorev3.CallingSessionState") - }.getOrNull()?.hookConstructor(HookStage.AFTER) { param -> - val instance = param.thisObject() - val callingState = instance.getObjectFieldOrNull("mLocalUser")?.getObjectField("mCallingState") - - if (callingState.toString() == "IN_CALL") { - participants.clear() - participants.addAll((instance.getObjectField("mParticipants") as Map<*, *>).keys.map { it.toString() }) - } - } ?: findClass("com.snapchat.talkcorev3.TSCallingStateUpdateParams").hookConstructor(HookStage.AFTER) { param -> - val instance = param.thisObject() - - if (instance.getObjectFieldOrNull("mInCall") == true) { - participants.clear() - participants.addAll((instance.getObjectField("mParticipants") as Set<*>).map { it.toString() }) - } - } - - AudioTrack::class.java.apply { - getConstructor( - AudioAttributes::class.java, - AudioFormat::class.java, - Int::class.javaPrimitiveType, - Int::class.javaPrimitiveType, - Int::class.javaPrimitiveType, - ).hook(HookStage.BEFORE) { param -> - val audioAttributes = param.arg(0) - if (audioAttributes.usage != AudioAttributes.USAGE_VOICE_COMMUNICATION) return@hook - val audioFormat = param.arg(1) - val hashCode = param.thisObject().hashCode() - - lateinit var streamUrl: String - streamUrl = httpServer.ensureServerStarted()?.putContent( - object: HttpServer.HttpContent() { - override val contentType: String = "audio/wav" - override val chunked: Boolean = true - override val contentLength: Long? = null - override val newBody: () -> HttpServer.HttpBody = { - object: HttpServer.HttpBody() { - val outputStream = PipedOutputStream() - val inputStream = PipedInputStream(outputStream) - - val handler: (byteArray: ByteArray) -> Unit = handler@{ byteArray -> - if (byteArray.isEmpty()) { - httpServer.removeUrl(streamUrl) - return@handler - } - runCatching { - outputStream.write(byteArray) - outputStream.flush() - }.onFailure { - context.log.warn("Failed to write to streaming url ${it.localizedMessage}") - } - } - - override val onOpen: () -> Unit = { - streamHandlers.getOrPut(hashCode) { CopyOnWriteArrayList() }.add(handler) - } - - override val readBytes: (byteArray: ByteArray) -> Int = { byteArray -> - runBlocking { - withTimeoutOrNull(3000L) { - inputStream.read(byteArray) - } ?: -1 - } - } - - override val onClose: () -> Unit = { - context.log.verbose("Streaming url closed") - streamHandlers[hashCode]?.remove(handler) - outputStream.close() - inputStream.close() - } - } - } - } - ) ?: return@hook - - context.log.verbose("streaming url = $streamUrl, sampleRate = ${audioFormat.sampleRate}, audioFormat = ${audioFormat.encoding}") - - context.feature(MediaDownloader::class).provideDownloadManagerClient( - UUID.randomUUID().toString(), - participants.mapNotNull { context.database.getFriendInfo(it)?.mutableUsername }.joinToString("-"), - System.currentTimeMillis(), - MediaDownloadSource.VOICE_CALL - ).downloadStream(streamUrl, AudioStreamFormat(audioFormat.channelCount, audioFormat.sampleRate, audioFormat.encoding)) - } - - getMethod("write", ByteBuffer::class.java, Int::class.javaPrimitiveType, Int::class.javaPrimitiveType).hook(HookStage.BEFORE) { param -> - streamHandlers[param.thisObject().hashCode()]?.let { handlers -> - val byteBuffer = param.arg(0) - val position = byteBuffer.position() - val buffer = ByteArray(param.arg(1)) - byteBuffer.get(buffer) - byteBuffer.position(position) - handlers.forEach { it(buffer) } - } - } - - hook("release", HookStage.BEFORE) { - streamHandlers.remove(it.thisObject().hashCode())?.forEach { it(ByteArray(0)) } - } - } - } -} \ No newline at end of file diff --git a/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/impl/experiments/ValdiHooks.kt b/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/impl/experiments/ValdiHooks.kt index f36ecf03..1148f66c 100644 --- a/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/impl/experiments/ValdiHooks.kt +++ b/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/impl/experiments/ValdiHooks.kt @@ -17,6 +17,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import kotlinx.coroutines.launch +import android.os.Build import me.eternal.purrfectsnap.common.bridge.FileHandleScope import me.eternal.purrfectsnap.common.bridge.toWrapper import me.eternal.purrfectsnap.common.ui.createComposeAlertDialog @@ -113,6 +114,10 @@ class ValdiHooks: Feature("ValdiHooks") { @Suppress("UNCHECKED_CAST") override fun init() { if (config.globalState != true) return + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + context.log.warn("ValdiHooks disabled on Android 9 and below") + return + } if (PurrfectSnap.classCache.valdiFunction == null) { context.log.warn("ComposerFunction/ValdiFunction class not found, ValdiHooks feature disabled") diff --git a/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/impl/tweaks/VoiceNoteOverride.kt b/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/impl/tweaks/VoiceNoteOverride.kt index 2dd317f9..001b5c42 100644 --- a/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/impl/tweaks/VoiceNoteOverride.kt +++ b/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/impl/tweaks/VoiceNoteOverride.kt @@ -1,5 +1,6 @@ package me.eternal.purrfectsnap.core.features.impl.tweaks +import android.os.Build import android.view.ViewGroup import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -27,6 +28,7 @@ class VoiceNoteOverride: Feature("Voice Note Override") { val playbackMap = sortedMapOf() val classLoader = context.androidContext.classLoader + var valdiCreateContextWarned = false fun tryFallbackCreateContext(param: HookAdapter): Any? { val fallbackClass = runCatching { @@ -149,6 +151,17 @@ class VoiceNoteOverride: Feature("Voice Note Override") { PurrfectSnap.classCache.nativeBridge.hook("createContext", HookStage.AFTER) { param -> val throwable = param.throwable() as? UnsatisfiedLinkError ?: return@hook + val isAndroid9OrBelow = Build.VERSION.SDK_INT < Build.VERSION_CODES.Q + val isValdiBridge = PurrfectSnap.classCache.nativeBridge.name == "com.snapchat.client.valdi.NativeBridge" + if (isAndroid9OrBelow && isValdiBridge) { + if (!valdiCreateContextWarned) { + valdiCreateContextWarned = true + context.log.warn("NativeBridge.createContext missing native impl on Android 9; skipping fallback") + } + param.clearThrowable() + param.setResult(null) + return@hook + } context.log.error("NativeBridge.createContext missing native impl; attempting fallback", throwable) val fallback = tryFallbackCreateContext(param) param.setResult(fallback) diff --git a/native/build.gradle.kts b/native/build.gradle.kts index 8bd273c1..47825965 100644 --- a/native/build.gradle.kts +++ b/native/build.gradle.kts @@ -103,13 +103,24 @@ val nativeAbisProp = (findProperty("nativeAbis") as? String) ?.takeIf { it.isNotBlank() } ?: System.getenv("NATIVE_ABIS") ?: "arm64-v8a,armeabi-v7a" -val enabledNativeAbis = nativeAbisProp +var enabledNativeAbis = nativeAbisProp .split(',', ';') .map { it.trim() } .filter { it.isNotEmpty() } .toSet() .ifEmpty { setOf("arm64-v8a", "armeabi-v7a") } +val requestedTasks = gradle.startParameter.taskNames.joinToString(" ") +val wantsArmv7 = requestedTasks.contains("armv7", ignoreCase = true) +val wantsArmv8 = requestedTasks.contains("armv8", ignoreCase = true) +if (wantsArmv7 && !wantsArmv8) { + enabledNativeAbis = setOf("armeabi-v7a") +} else if (wantsArmv8 && !wantsArmv7) { + enabledNativeAbis = setOf("arm64-v8a") +} else if (wantsArmv7 && wantsArmv8) { + enabledNativeAbis = enabledNativeAbis + setOf("armeabi-v7a", "arm64-v8a") +} + val cargoTargets = listOf( CargoTarget( triple = "aarch64-linux-android", @@ -247,17 +258,16 @@ val syncTasks = cargoTargets.mapIndexed { index, target -> inputs.property("outputLibName", outputLibName) val wslCandidate = File(wslStagingDir, "native/rust/target/${target.triple}/release/libpurrfectsnap.so") val localCandidate = layout.projectDirectory.file("rust/target/${target.triple}/release/libpurrfectsnap.so").asFile - val sourceLibProvider = providers.provider { - if (wslCandidate.exists()) wslCandidate else localCandidate - } - from(sourceLibProvider) { + val sourceLibs = files(wslCandidate, localCandidate) + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + from(sourceLibs) { rename { outputLibName } } - from(sourceLibProvider) { + from(sourceLibs) { rename { "libpurrfectsnap.so" } } into(layout.buildDirectory.dir("rustJniLibs/android/${target.abi}")) - inputs.files(sourceLibProvider) + inputs.files(sourceLibs) val checksumsDir = layout.buildDirectory.dir("checksums") doLast { val file = File(destinationDir, outputLibName) @@ -313,6 +323,7 @@ android { defaultConfig { buildConfigField("String", "NATIVE_NAME", "\"$nativeBuildHash\".toString()") + buildConfigField("String", "MODULE_PACKAGE_NAME", "\"${rootProject.ext["applicationId"]}\"") minSdk = 28 } diff --git a/native/src/main/kotlin/me/eternal/purrfectsnap/nativelib/NativeLib.kt b/native/src/main/kotlin/me/eternal/purrfectsnap/nativelib/NativeLib.kt index 60cb52ea..d0469a16 100644 --- a/native/src/main/kotlin/me/eternal/purrfectsnap/nativelib/NativeLib.kt +++ b/native/src/main/kotlin/me/eternal/purrfectsnap/nativelib/NativeLib.kt @@ -1,6 +1,7 @@ package me.eternal.purrfectsnap.nativelib import android.annotation.SuppressLint +import android.content.Context import android.util.Log import java.io.File import kotlin.math.absoluteValue @@ -15,40 +16,52 @@ class NativeLib { private set private var libraryLoaded = false - private fun findNativeLibraryDir(): File? { + private fun findNativeLibraryDirs(): List { val app = runCatching { val cls = Class.forName("android.app.ActivityThread") val method = cls.getMethod("currentApplication") method.invoke(null) as? android.app.Application - }.getOrNull() ?: return null - val dir = app.applicationInfo.nativeLibraryDir ?: return null - return File(dir) + }.getOrNull() ?: return emptyList() + + val dirs = mutableListOf() + val moduleDir = runCatching { + app.createPackageContext(BuildConfig.MODULE_PACKAGE_NAME, Context.CONTEXT_IGNORE_SECURITY) + .applicationInfo.nativeLibraryDir + }.getOrNull() + moduleDir?.let { dirs.add(File(it)) } + + app.applicationInfo.nativeLibraryDir?.let { dirs.add(File(it)) } + + return dirs.distinctBy { it.absolutePath } } private fun tryLoadFromNativeDir(): Boolean { - val dir = findNativeLibraryDir() ?: return false - if (!dir.isDirectory) return false + val dirs = findNativeLibraryDirs().filter { it.isDirectory } + if (dirs.isEmpty()) return false - val candidates = listOf( - "lib${BuildConfig.NATIVE_NAME}.so", - "libpurrfectsnap.so" - ).map { File(dir, it) } + for (dir in dirs) { + val candidates = listOf( + "lib${BuildConfig.NATIVE_NAME}.so", + "libpurrfectsnap.so" + ).map { File(dir, it) } - val fallback = dir.listFiles()?.firstOrNull { it.name.startsWith("lib") && it.name.endsWith(".so") && it.name.contains("purrfectsnap") } + val fallback = dir.listFiles() + ?.firstOrNull { it.name.startsWith("lib") && it.name.endsWith(".so") && it.name.contains("purrfectsnap") } - val ordered = buildList { - addAll(candidates) - fallback?.let { if (!contains(it)) add(it) } - } + val ordered = buildList { + addAll(candidates) + fallback?.let { if (!contains(it)) add(it) } + } - for (file in ordered) { - if (!file.exists()) continue - val ok = runCatching { - System.load(file.absolutePath) - libraryLoaded = true - true - }.getOrDefault(false) - if (ok) return true + for (file in ordered) { + if (!file.exists()) continue + val ok = runCatching { + System.load(file.absolutePath) + libraryLoaded = true + true + }.getOrDefault(false) + if (ok) return true + } } return false } diff --git a/valdi/node_modules/typescript/lib/_tsc.js b/valdi/node_modules/typescript/lib/_tsc.js index 612a1f7e..29a5a034 100644 --- a/valdi/node_modules/typescript/lib/_tsc.js +++ b/valdi/node_modules/typescript/lib/_tsc.js @@ -3182,7 +3182,7 @@ var SyntaxKind = /* @__PURE__ */ ((SyntaxKind4) => { SyntaxKind4[SyntaxKind4["WhileStatement"] = 248] = "WhileStatement"; SyntaxKind4[SyntaxKind4["ForStatement"] = 249] = "ForStatement"; SyntaxKind4[SyntaxKind4["ForInStatement"] = 250] = "ForInStatement"; - SyntaxKind4[SyntaxKind4["ForOfStatement"] = 251] = "ForOfStatement"; + SyntaxKind4[SyntaxKind4["ForOfStatement"] = 252] = "ForOfStatement"; SyntaxKind4[SyntaxKind4["ContinueStatement"] = 252] = "ContinueStatement"; SyntaxKind4[SyntaxKind4["BreakStatement"] = 253] = "BreakStatement"; SyntaxKind4[SyntaxKind4["ReturnStatement"] = 254] = "ReturnStatement"; @@ -12315,7 +12315,7 @@ function isIterationStatement(node, lookInLabeledStatements) { switch (node.kind) { case 249 /* ForStatement */: case 250 /* ForInStatement */: - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: case 247 /* DoStatement */: case 248 /* WhileStatement */: return true; @@ -12337,7 +12337,7 @@ function isExternalModuleIndicator(result) { return isAnyImportOrReExport(result) || isExportAssignment(result) || hasSyntacticModifier(result, 32 /* Export */); } function isForInOrOfStatement(node) { - return node.kind === 250 /* ForInStatement */ || node.kind === 251 /* ForOfStatement */; + return node.kind === 250 /* ForInStatement */ || node.kind === 252 /* ForOfStatement */; } function isConciseBody(node) { return isBlock(node) || isExpression(node); @@ -12441,7 +12441,7 @@ function canHaveLocals(node) { case 181 /* ConstructSignature */: case 249 /* ForStatement */: case 250 /* ForInStatement */: - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: case 263 /* FunctionDeclaration */: case 219 /* FunctionExpression */: case 185 /* FunctionType */: @@ -12471,7 +12471,7 @@ function isDeclarationStatementKind(kind) { return kind === 263 /* FunctionDeclaration */ || kind === 283 /* MissingDeclaration */ || kind === 264 /* ClassDeclaration */ || kind === 265 /* InterfaceDeclaration */ || kind === 266 /* TypeAliasDeclaration */ || kind === 267 /* EnumDeclaration */ || kind === 268 /* ModuleDeclaration */ || kind === 273 /* ImportDeclaration */ || kind === 272 /* ImportEqualsDeclaration */ || kind === 279 /* ExportDeclaration */ || kind === 278 /* ExportAssignment */ || kind === 271 /* NamespaceExportDeclaration */; } function isStatementKindButNotDeclarationKind(kind) { - return kind === 253 /* BreakStatement */ || kind === 252 /* ContinueStatement */ || kind === 260 /* DebuggerStatement */ || kind === 247 /* DoStatement */ || kind === 245 /* ExpressionStatement */ || kind === 243 /* EmptyStatement */ || kind === 250 /* ForInStatement */ || kind === 251 /* ForOfStatement */ || kind === 249 /* ForStatement */ || kind === 246 /* IfStatement */ || kind === 257 /* LabeledStatement */ || kind === 254 /* ReturnStatement */ || kind === 256 /* SwitchStatement */ || kind === 258 /* ThrowStatement */ || kind === 259 /* TryStatement */ || kind === 244 /* VariableStatement */ || kind === 248 /* WhileStatement */ || kind === 255 /* WithStatement */ || kind === 354 /* NotEmittedStatement */; + return kind === 253 /* BreakStatement */ || kind === 252 /* ContinueStatement */ || kind === 260 /* DebuggerStatement */ || kind === 247 /* DoStatement */ || kind === 245 /* ExpressionStatement */ || kind === 243 /* EmptyStatement */ || kind === 250 /* ForInStatement */ || kind === 252 /* ForOfStatement */ || kind === 249 /* ForStatement */ || kind === 246 /* IfStatement */ || kind === 257 /* LabeledStatement */ || kind === 254 /* ReturnStatement */ || kind === 256 /* SwitchStatement */ || kind === 258 /* ThrowStatement */ || kind === 259 /* TryStatement */ || kind === 244 /* VariableStatement */ || kind === 248 /* WhileStatement */ || kind === 255 /* WithStatement */ || kind === 354 /* NotEmittedStatement */; } function isDeclaration(node) { if (node.kind === 169 /* TypeParameter */) { @@ -12897,7 +12897,7 @@ function isStatementWithLocals(node) { case 270 /* CaseBlock */: case 249 /* ForStatement */: case 250 /* ForInStatement */: - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: return true; } return false; @@ -13754,7 +13754,7 @@ function isBlockScope(node, parentNode) { case 268 /* ModuleDeclaration */: case 249 /* ForStatement */: case 250 /* ForInStatement */: - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: case 177 /* Constructor */: case 175 /* MethodDeclaration */: case 178 /* GetAccessor */: @@ -14248,7 +14248,7 @@ function forEachReturnStatement(body, visitor) { case 248 /* WhileStatement */: case 249 /* ForStatement */: case 250 /* ForInStatement */: - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: case 255 /* WithStatement */: case 256 /* SwitchStatement */: case 297 /* CaseClause */: @@ -14791,7 +14791,7 @@ function isInExpressionContext(node) { const forStatement = parent; return forStatement.initializer === node && forStatement.initializer.kind !== 262 /* VariableDeclarationList */ || forStatement.condition === node || forStatement.incrementor === node; case 250 /* ForInStatement */: - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: const forInOrOfStatement = parent; return forInOrOfStatement.initializer === node && forInOrOfStatement.initializer.kind !== 262 /* VariableDeclarationList */ || forInOrOfStatement.expression === node; case 217 /* TypeAssertionExpression */: @@ -15325,7 +15325,7 @@ function canHaveJSDoc(node) { case 282 /* ExportSpecifier */: case 245 /* ExpressionStatement */: case 250 /* ForInStatement */: - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: case 249 /* ForStatement */: case 263 /* FunctionDeclaration */: case 219 /* FunctionExpression */: @@ -15489,7 +15489,7 @@ function getAssignmentTarget(node) { const unaryOperator = unaryExpression.operator; return unaryOperator === 46 /* PlusPlusToken */ || unaryOperator === 47 /* MinusMinusToken */ ? unaryExpression : void 0; case 250 /* ForInStatement */: - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: const forInOrOfStatement = parent; return forInOrOfStatement.initializer === node ? forInOrOfStatement : void 0; case 218 /* ParenthesizedExpression */: @@ -15532,7 +15532,7 @@ function getAssignmentTargetKind(node) { case 226 /* PostfixUnaryExpression */: return 2 /* Compound */; case 250 /* ForInStatement */: - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: return 1 /* Definite */; } } @@ -15564,7 +15564,7 @@ function isNodeWithPossibleHoistedDeclaration(node) { case 257 /* LabeledStatement */: case 249 /* ForStatement */: case 250 /* ForInStatement */: - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: case 247 /* DoStatement */: case 248 /* WhileStatement */: case 259 /* TryStatement */: @@ -17424,7 +17424,7 @@ function accessKind(node) { case 210 /* ArrayLiteralExpression */: return accessKind(parent); case 250 /* ForInStatement */: - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: return node === parent.initializer ? 1 /* Write */ : 0 /* Read */; default: return 0 /* Read */; @@ -23083,7 +23083,7 @@ function createNodeFactory(flags, baseFactory2) { return node.initializer !== initializer || node.expression !== expression || node.statement !== statement ? update(createForInStatement(initializer, expression, statement), node) : node; } function createForOfStatement(awaitModifier, initializer, expression, statement) { - const node = createBaseNode(251 /* ForOfStatement */); + const node = createBaseNode(252 /* ForOfStatement */); node.awaitModifier = awaitModifier; node.initializer = initializer; node.expression = parenthesizerRules().parenthesizeExpressionForDisallowedComma(expression); @@ -26810,7 +26810,7 @@ function isForInStatement(node) { return node.kind === 250 /* ForInStatement */; } function isForOfStatement(node) { - return node.kind === 251 /* ForOfStatement */; + return node.kind === 252 /* ForOfStatement */; } function isReturnStatement(node) { return node.kind === 254 /* ReturnStatement */; @@ -28447,7 +28447,7 @@ var forEachChildTable = { [250 /* ForInStatement */]: function forEachChildInForInStatement(node, cbNode, _cbNodes) { return visitNode2(cbNode, node.initializer) || visitNode2(cbNode, node.expression) || visitNode2(cbNode, node.statement); }, - [251 /* ForOfStatement */]: function forEachChildInForOfStatement(node, cbNode, _cbNodes) { + [252 /* ForOfStatement */]: function forEachChildInForOfStatement(node, cbNode, _cbNodes) { return visitNode2(cbNode, node.awaitModifier) || visitNode2(cbNode, node.initializer) || visitNode2(cbNode, node.expression) || visitNode2(cbNode, node.statement); }, [252 /* ContinueStatement */]: forEachChildInContinueOrBreakStatement, @@ -30220,7 +30220,7 @@ var Parser; case 253 /* BreakStatement */: case 252 /* ContinueStatement */: case 250 /* ForInStatement */: - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: case 249 /* ForStatement */: case 248 /* WhileStatement */: case 255 /* WithStatement */: @@ -42754,7 +42754,7 @@ function createBinder() { bindForStatement(node); break; case 250 /* ForInStatement */: - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: bindForInOrForOfStatement(node); break; case 246 /* IfStatement */: @@ -43144,7 +43144,7 @@ function createBinder() { bind(node.expression); addAntecedent(preLoopLabel, currentFlow); currentFlow = preLoopLabel; - if (node.kind === 251 /* ForOfStatement */) { + if (node.kind === 252 /* ForOfStatement */) { bind(node.awaitModifier); } addAntecedent(postLoopLabel, currentFlow); @@ -45137,7 +45137,7 @@ function getContainerFlags(node) { case 300 /* CatchClause */: case 249 /* ForStatement */: case 250 /* ForInStatement */: - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: case 270 /* CaseBlock */: return 2 /* IsBlockScopedContainer */ | 32 /* HasLocals */; case 242 /* Block */: @@ -47938,7 +47938,7 @@ function createTypeChecker(host) { switch (declaration2.parent.parent.kind) { case 244 /* VariableStatement */: case 249 /* ForStatement */: - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: if (isSameScopeDescendentOf(usage2, declaration2, declContainer)) { return true; } @@ -55950,7 +55950,7 @@ function createTypeChecker(host) { ))); return indexType.flags & (262144 /* TypeParameter */ | 4194304 /* Index */) ? getExtractStringType(indexType) : stringType; } - if (isVariableDeclaration(declaration) && declaration.parent.parent.kind === 251 /* ForOfStatement */) { + if (isVariableDeclaration(declaration) && declaration.parent.parent.kind === 252 /* ForOfStatement */) { const forOfStatement = declaration.parent.parent; return checkRightHandSideOfForOf(forOfStatement) || anyType; } @@ -69719,7 +69719,7 @@ function createTypeChecker(host) { return isDestructuringDefaultAssignment ? getTypeWithDefault(getAssignedType(node), node.right) : getTypeOfExpression(node.right); } function isDestructuringAssignmentTarget(parent) { - return parent.parent.kind === 227 /* BinaryExpression */ && parent.parent.left === parent || parent.parent.kind === 251 /* ForOfStatement */ && parent.parent.initializer === parent; + return parent.parent.kind === 227 /* BinaryExpression */ && parent.parent.left === parent || parent.parent.kind === 252 /* ForOfStatement */ && parent.parent.initializer === parent; } function getAssignedTypeOfArrayLiteralElement(node, element) { return getTypeOfDestructuredArrayElement(getAssignedType(node), node.elements.indexOf(element)); @@ -69738,7 +69738,7 @@ function createTypeChecker(host) { switch (parent.kind) { case 250 /* ForInStatement */: return stringType; - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: return checkRightHandSideOfForOf(parent) || errorType; case 227 /* BinaryExpression */: return getAssignedTypeOfBinaryExpression(parent); @@ -69772,7 +69772,7 @@ function createTypeChecker(host) { if (node.parent.parent.kind === 250 /* ForInStatement */) { return stringType; } - if (node.parent.parent.kind === 251 /* ForOfStatement */) { + if (node.parent.parent.kind === 252 /* ForOfStatement */) { return checkRightHandSideOfForOf(node.parent.parent) || errorType; } return errorType; @@ -70010,7 +70010,7 @@ function createTypeChecker(host) { if (isDeclarationWithExplicitTypeAnnotation(declaration)) { return getTypeOfSymbol(symbol); } - if (isVariableDeclaration(declaration) && declaration.parent.parent.kind === 251 /* ForOfStatement */) { + if (isVariableDeclaration(declaration) && declaration.parent.parent.kind === 252 /* ForOfStatement */) { const statement = declaration.parent.parent; const expressionType = getTypeOfDottedName( statement.expression, @@ -71449,7 +71449,7 @@ function createTypeChecker(host) { case 248 /* WhileStatement */: case 249 /* ForStatement */: case 250 /* ForInStatement */: - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: case 255 /* WithStatement */: case 256 /* SwitchStatement */: case 259 /* TryStatement */: @@ -82766,7 +82766,7 @@ function createTypeChecker(host) { case 270 /* CaseBlock */: case 249 /* ForStatement */: case 250 /* ForInStatement */: - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: checkUnusedLocalsAndParameters(node, addDiagnostic); break; case 177 /* Constructor */: @@ -86503,7 +86503,7 @@ function createTypeChecker(host) { return checkForStatement(node); case 250 /* ForInStatement */: return checkForInStatement(node); - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: return checkForOfStatement(node); case 252 /* ContinueStatement */: case 253 /* BreakStatement */: @@ -87497,7 +87497,7 @@ function createTypeChecker(host) { } function getTypeOfAssignmentPattern(expr) { Debug.assert(expr.kind === 211 /* ObjectLiteralExpression */ || expr.kind === 210 /* ArrayLiteralExpression */); - if (expr.parent.kind === 251 /* ForOfStatement */) { + if (expr.parent.kind === 252 /* ForOfStatement */) { const iteratedType = checkRightHandSideOfForOf(expr.parent); return checkDestructuringAssignment(expr, iteratedType || errorType); } @@ -89489,7 +89489,7 @@ function createTypeChecker(host) { if (checkGrammarStatementInAmbientContext(forInOrOfStatement)) { return true; } - if (forInOrOfStatement.kind === 251 /* ForOfStatement */ && forInOrOfStatement.awaitModifier) { + if (forInOrOfStatement.kind === 252 /* ForOfStatement */ && forInOrOfStatement.awaitModifier) { if (!(forInOrOfStatement.flags & 65536 /* AwaitContext */)) { const sourceFile = getSourceFileOfNode(forInOrOfStatement); if (isInTopLevelContext(forInOrOfStatement)) { @@ -89798,7 +89798,7 @@ function createTypeChecker(host) { return grammarErrorOnNode(node, Diagnostics._0_declarations_may_not_have_binding_patterns, "using"); } } - if (node.parent.parent.kind !== 250 /* ForInStatement */ && node.parent.parent.kind !== 251 /* ForOfStatement */) { + if (node.parent.parent.kind !== 250 /* ForInStatement */ && node.parent.parent.kind !== 252 /* ForOfStatement */) { if (nodeFlags & 33554432 /* Ambient */) { checkAmbientInitializer(node); } else if (!node.initializer) { @@ -89890,7 +89890,7 @@ function createTypeChecker(host) { case 255 /* WithStatement */: case 249 /* ForStatement */: case 250 /* ForInStatement */: - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: return false; case 257 /* LabeledStatement */: return allowBlockDeclarations(parent.parent); @@ -91350,7 +91350,7 @@ var visitEachChildTable = { visitIterationBody(node.statement, visitor, context, nodeVisitor) ); }, - [251 /* ForOfStatement */]: function visitEachChildOfForOfStatement(node, visitor, context, _nodesVisitor, nodeVisitor, tokenVisitor) { + [252 /* ForOfStatement */]: function visitEachChildOfForOfStatement(node, visitor, context, _nodesVisitor, nodeVisitor, tokenVisitor) { return context.factory.updateForOfStatement( node, tokenVisitor ? nodeVisitor(node.awaitModifier, tokenVisitor, isAwaitKeyword) : node.awaitModifier, @@ -100339,7 +100339,7 @@ function transformES2017(context) { return visitForStatementInAsyncBody(node); case 250 /* ForInStatement */: return visitForInStatementInAsyncBody(node); - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: return visitForOfStatementInAsyncBody(node); case 300 /* CatchClause */: return visitCatchClauseInAsyncBody(node); @@ -101193,7 +101193,7 @@ function transformES2018(context) { 0 /* IterationStatementExcludes */, 2 /* IterationStatementIncludes */ ); - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: return visitForOfStatement( node, /*outermostLabeledStatement*/ @@ -101364,7 +101364,7 @@ function transformES2018(context) { function visitLabeledStatement(node) { if (enclosingFunctionFlags & 2 /* Async */) { const statement = unwrapInnermostStatementOfLabel(node); - if (statement.kind === 251 /* ForOfStatement */ && statement.awaitModifier) { + if (statement.kind === 252 /* ForOfStatement */ && statement.awaitModifier) { return visitForOfStatement(statement, node); } return factory2.restoreEnclosingLabel(visitNode(statement, visitor, isStatement, factory2.liftToBlock), node); @@ -102705,7 +102705,7 @@ function transformESNext(context) { return visitBlock(node); case 249 /* ForStatement */: return visitForStatement(node); - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: return visitForOfStatement(node); case 256 /* SwitchStatement */: return visitSwitchStatement(node); @@ -103943,7 +103943,7 @@ var entities = new Map(Object.entries({ oslash: 248, ugrave: 249, uacute: 250, - ucirc: 251, + ucirc: 252, uuml: 252, yacute: 253, thorn: 254, @@ -104369,7 +104369,7 @@ function transformES2015(context) { /*outermostLabeledStatement*/ void 0 ); - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: return visitForOfStatement( node, /*outermostLabeledStatement*/ @@ -105979,7 +105979,7 @@ function transformES2015(context) { return visitForStatement(node, outermostLabeledStatement); case 250 /* ForInStatement */: return visitForInStatement(node, outermostLabeledStatement); - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: return visitForOfStatement(node, outermostLabeledStatement); } } @@ -106453,7 +106453,7 @@ function transformES2015(context) { return convertForStatement(node, initializerFunction, convertedLoopBody); case 250 /* ForInStatement */: return convertForInStatement(node, convertedLoopBody); - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: return convertForOfStatement(node, convertedLoopBody); case 247 /* DoStatement */: return convertDoStatement(node, convertedLoopBody); @@ -106511,7 +106511,7 @@ function transformES2015(context) { switch (node.kind) { case 249 /* ForStatement */: case 250 /* ForInStatement */: - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: const initializer = node.initializer; if (initializer && initializer.kind === 262 /* VariableDeclarationList */) { loopInitializer = initializer; @@ -110048,7 +110048,7 @@ function transformModule(context) { ); case 250 /* ForInStatement */: return visitForInStatement(node); - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: return visitForOfStatement(node); case 247 /* DoStatement */: return visitDoStatement(node); @@ -112320,7 +112320,7 @@ function transformSystemModule(context) { ); case 250 /* ForInStatement */: return visitForInStatement(node); - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: return visitForOfStatement(node); case 247 /* DoStatement */: return visitDoStatement(node); @@ -116842,7 +116842,7 @@ function createPrinter(printerOptions = {}, handlers = {}) { return emitForStatement(node); case 250 /* ForInStatement */: return emitForInStatement(node); - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: return emitForOfStatement(node); case 252 /* ContinueStatement */: return emitContinueStatement(node); @@ -119983,7 +119983,7 @@ function createPrinter(printerOptions = {}, handlers = {}) { generateNames(node.elseStatement); break; case 249 /* ForStatement */: - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: case 250 /* ForInStatement */: generateNames(node.initializer); generateNames(node.statement); diff --git a/valdi/node_modules/typescript/lib/typescript.d.ts b/valdi/node_modules/typescript/lib/typescript.d.ts index 2c56042e..3d39d2b2 100644 --- a/valdi/node_modules/typescript/lib/typescript.d.ts +++ b/valdi/node_modules/typescript/lib/typescript.d.ts @@ -3927,7 +3927,7 @@ declare namespace ts { WhileStatement = 248, ForStatement = 249, ForInStatement = 250, - ForOfStatement = 251, + ForOfStatement = 252, ContinueStatement = 252, BreakStatement = 253, ReturnStatement = 254, diff --git a/valdi/node_modules/typescript/lib/typescript.js b/valdi/node_modules/typescript/lib/typescript.js index 0554fc3f..b6d61036 100644 --- a/valdi/node_modules/typescript/lib/typescript.js +++ b/valdi/node_modules/typescript/lib/typescript.js @@ -5771,7 +5771,7 @@ var SyntaxKind = /* @__PURE__ */ ((SyntaxKind5) => { SyntaxKind5[SyntaxKind5["WhileStatement"] = 248] = "WhileStatement"; SyntaxKind5[SyntaxKind5["ForStatement"] = 249] = "ForStatement"; SyntaxKind5[SyntaxKind5["ForInStatement"] = 250] = "ForInStatement"; - SyntaxKind5[SyntaxKind5["ForOfStatement"] = 251] = "ForOfStatement"; + SyntaxKind5[SyntaxKind5["ForOfStatement"] = 252] = "ForOfStatement"; SyntaxKind5[SyntaxKind5["ContinueStatement"] = 252] = "ContinueStatement"; SyntaxKind5[SyntaxKind5["BreakStatement"] = 253] = "BreakStatement"; SyntaxKind5[SyntaxKind5["ReturnStatement"] = 254] = "ReturnStatement"; @@ -15881,7 +15881,7 @@ function isIterationStatement(node, lookInLabeledStatements) { switch (node.kind) { case 249 /* ForStatement */: case 250 /* ForInStatement */: - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: case 247 /* DoStatement */: case 248 /* WhileStatement */: return true; @@ -15903,7 +15903,7 @@ function isExternalModuleIndicator(result) { return isAnyImportOrReExport(result) || isExportAssignment(result) || hasSyntacticModifier(result, 32 /* Export */); } function isForInOrOfStatement(node) { - return node.kind === 250 /* ForInStatement */ || node.kind === 251 /* ForOfStatement */; + return node.kind === 250 /* ForInStatement */ || node.kind === 252 /* ForOfStatement */; } function isConciseBody(node) { return isBlock(node) || isExpression(node); @@ -16018,7 +16018,7 @@ function canHaveLocals(node) { case 181 /* ConstructSignature */: case 249 /* ForStatement */: case 250 /* ForInStatement */: - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: case 263 /* FunctionDeclaration */: case 219 /* FunctionExpression */: case 185 /* FunctionType */: @@ -16048,7 +16048,7 @@ function isDeclarationStatementKind(kind) { return kind === 263 /* FunctionDeclaration */ || kind === 283 /* MissingDeclaration */ || kind === 264 /* ClassDeclaration */ || kind === 265 /* InterfaceDeclaration */ || kind === 266 /* TypeAliasDeclaration */ || kind === 267 /* EnumDeclaration */ || kind === 268 /* ModuleDeclaration */ || kind === 273 /* ImportDeclaration */ || kind === 272 /* ImportEqualsDeclaration */ || kind === 279 /* ExportDeclaration */ || kind === 278 /* ExportAssignment */ || kind === 271 /* NamespaceExportDeclaration */; } function isStatementKindButNotDeclarationKind(kind) { - return kind === 253 /* BreakStatement */ || kind === 252 /* ContinueStatement */ || kind === 260 /* DebuggerStatement */ || kind === 247 /* DoStatement */ || kind === 245 /* ExpressionStatement */ || kind === 243 /* EmptyStatement */ || kind === 250 /* ForInStatement */ || kind === 251 /* ForOfStatement */ || kind === 249 /* ForStatement */ || kind === 246 /* IfStatement */ || kind === 257 /* LabeledStatement */ || kind === 254 /* ReturnStatement */ || kind === 256 /* SwitchStatement */ || kind === 258 /* ThrowStatement */ || kind === 259 /* TryStatement */ || kind === 244 /* VariableStatement */ || kind === 248 /* WhileStatement */ || kind === 255 /* WithStatement */ || kind === 354 /* NotEmittedStatement */; + return kind === 253 /* BreakStatement */ || kind === 252 /* ContinueStatement */ || kind === 260 /* DebuggerStatement */ || kind === 247 /* DoStatement */ || kind === 245 /* ExpressionStatement */ || kind === 243 /* EmptyStatement */ || kind === 250 /* ForInStatement */ || kind === 252 /* ForOfStatement */ || kind === 249 /* ForStatement */ || kind === 246 /* IfStatement */ || kind === 257 /* LabeledStatement */ || kind === 254 /* ReturnStatement */ || kind === 256 /* SwitchStatement */ || kind === 258 /* ThrowStatement */ || kind === 259 /* TryStatement */ || kind === 244 /* VariableStatement */ || kind === 248 /* WhileStatement */ || kind === 255 /* WithStatement */ || kind === 354 /* NotEmittedStatement */; } function isDeclaration(node) { if (node.kind === 169 /* TypeParameter */) { @@ -16490,7 +16490,7 @@ function isStatementWithLocals(node) { case 270 /* CaseBlock */: case 249 /* ForStatement */: case 250 /* ForInStatement */: - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: return true; } return false; @@ -17395,7 +17395,7 @@ function isBlockScope(node, parentNode) { case 268 /* ModuleDeclaration */: case 249 /* ForStatement */: case 250 /* ForInStatement */: - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: case 177 /* Constructor */: case 175 /* MethodDeclaration */: case 178 /* GetAccessor */: @@ -17935,7 +17935,7 @@ function forEachReturnStatement(body, visitor) { case 248 /* WhileStatement */: case 249 /* ForStatement */: case 250 /* ForInStatement */: - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: case 255 /* WithStatement */: case 256 /* SwitchStatement */: case 297 /* CaseClause */: @@ -18481,7 +18481,7 @@ function isInExpressionContext(node) { const forStatement = parent2; return forStatement.initializer === node && forStatement.initializer.kind !== 262 /* VariableDeclarationList */ || forStatement.condition === node || forStatement.incrementor === node; case 250 /* ForInStatement */: - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: const forInOrOfStatement = parent2; return forInOrOfStatement.initializer === node && forInOrOfStatement.initializer.kind !== 262 /* VariableDeclarationList */ || forInOrOfStatement.expression === node; case 217 /* TypeAssertionExpression */: @@ -19051,7 +19051,7 @@ function canHaveJSDoc(node) { case 282 /* ExportSpecifier */: case 245 /* ExpressionStatement */: case 250 /* ForInStatement */: - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: case 249 /* ForStatement */: case 263 /* FunctionDeclaration */: case 219 /* FunctionExpression */: @@ -19224,7 +19224,7 @@ function getAssignmentTarget(node) { const unaryOperator = unaryExpression.operator; return unaryOperator === 46 /* PlusPlusToken */ || unaryOperator === 47 /* MinusMinusToken */ ? unaryExpression : void 0; case 250 /* ForInStatement */: - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: const forInOrOfStatement = parent2; return forInOrOfStatement.initializer === node ? forInOrOfStatement : void 0; case 218 /* ParenthesizedExpression */: @@ -19267,7 +19267,7 @@ function getAssignmentTargetKind(node) { case 226 /* PostfixUnaryExpression */: return 2 /* Compound */; case 250 /* ForInStatement */: - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: return 1 /* Definite */; } } @@ -19299,7 +19299,7 @@ function isNodeWithPossibleHoistedDeclaration(node) { case 257 /* LabeledStatement */: case 249 /* ForStatement */: case 250 /* ForInStatement */: - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: case 247 /* DoStatement */: case 248 /* WhileStatement */: case 259 /* TryStatement */: @@ -21316,7 +21316,7 @@ function accessKind(node) { case 210 /* ArrayLiteralExpression */: return accessKind(parent2); case 250 /* ForInStatement */: - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: return node === parent2.initializer ? 1 /* Write */ : 0 /* Read */; default: return 0 /* Read */; @@ -23389,7 +23389,7 @@ function isSourceElement(node) { case 248 /* WhileStatement */: case 249 /* ForStatement */: case 250 /* ForInStatement */: - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: case 252 /* ContinueStatement */: case 253 /* BreakStatement */: case 254 /* ReturnStatement */: @@ -27191,7 +27191,7 @@ function createNodeFactory(flags, baseFactory2) { return node.initializer !== initializer || node.expression !== expression || node.statement !== statement ? update(createForInStatement(initializer, expression, statement), node) : node; } function createForOfStatement(awaitModifier, initializer, expression, statement) { - const node = createBaseNode(251 /* ForOfStatement */); + const node = createBaseNode(252 /* ForOfStatement */); node.awaitModifier = awaitModifier; node.initializer = initializer; node.expression = parenthesizerRules().parenthesizeExpressionForDisallowedComma(expression); @@ -30992,7 +30992,7 @@ function isForInStatement(node) { return node.kind === 250 /* ForInStatement */; } function isForOfStatement(node) { - return node.kind === 251 /* ForOfStatement */; + return node.kind === 252 /* ForOfStatement */; } function isContinueStatement(node) { return node.kind === 252 /* ContinueStatement */; @@ -32693,7 +32693,7 @@ var forEachChildTable = { [250 /* ForInStatement */]: function forEachChildInForInStatement(node, cbNode, _cbNodes) { return visitNode2(cbNode, node.initializer) || visitNode2(cbNode, node.expression) || visitNode2(cbNode, node.statement); }, - [251 /* ForOfStatement */]: function forEachChildInForOfStatement(node, cbNode, _cbNodes) { + [252 /* ForOfStatement */]: function forEachChildInForOfStatement(node, cbNode, _cbNodes) { return visitNode2(cbNode, node.awaitModifier) || visitNode2(cbNode, node.initializer) || visitNode2(cbNode, node.expression) || visitNode2(cbNode, node.statement); }, [252 /* ContinueStatement */]: forEachChildInContinueOrBreakStatement, @@ -34481,7 +34481,7 @@ var Parser; case 253 /* BreakStatement */: case 252 /* ContinueStatement */: case 250 /* ForInStatement */: - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: case 249 /* ForStatement */: case 248 /* WhileStatement */: case 255 /* WithStatement */: @@ -47265,7 +47265,7 @@ function createBinder() { bindForStatement(node); break; case 250 /* ForInStatement */: - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: bindForInOrForOfStatement(node); break; case 246 /* IfStatement */: @@ -47655,7 +47655,7 @@ function createBinder() { bind(node.expression); addAntecedent(preLoopLabel, currentFlow); currentFlow = preLoopLabel; - if (node.kind === 251 /* ForOfStatement */) { + if (node.kind === 252 /* ForOfStatement */) { bind(node.awaitModifier); } addAntecedent(postLoopLabel, currentFlow); @@ -49648,7 +49648,7 @@ function getContainerFlags(node) { case 300 /* CatchClause */: case 249 /* ForStatement */: case 250 /* ForInStatement */: - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: case 270 /* CaseBlock */: return 2 /* IsBlockScopedContainer */ | 32 /* HasLocals */; case 242 /* Block */: @@ -52549,7 +52549,7 @@ function createTypeChecker(host) { switch (declaration2.parent.parent.kind) { case 244 /* VariableStatement */: case 249 /* ForStatement */: - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: if (isSameScopeDescendentOf(usage2, declaration2, declContainer)) { return true; } @@ -60561,7 +60561,7 @@ function createTypeChecker(host) { ))); return indexType.flags & (262144 /* TypeParameter */ | 4194304 /* Index */) ? getExtractStringType(indexType) : stringType; } - if (isVariableDeclaration(declaration) && declaration.parent.parent.kind === 251 /* ForOfStatement */) { + if (isVariableDeclaration(declaration) && declaration.parent.parent.kind === 252 /* ForOfStatement */) { const forOfStatement = declaration.parent.parent; return checkRightHandSideOfForOf(forOfStatement) || anyType; } @@ -74330,7 +74330,7 @@ function createTypeChecker(host) { return isDestructuringDefaultAssignment ? getTypeWithDefault(getAssignedType(node), node.right) : getTypeOfExpression(node.right); } function isDestructuringAssignmentTarget(parent2) { - return parent2.parent.kind === 227 /* BinaryExpression */ && parent2.parent.left === parent2 || parent2.parent.kind === 251 /* ForOfStatement */ && parent2.parent.initializer === parent2; + return parent2.parent.kind === 227 /* BinaryExpression */ && parent2.parent.left === parent2 || parent2.parent.kind === 252 /* ForOfStatement */ && parent2.parent.initializer === parent2; } function getAssignedTypeOfArrayLiteralElement(node, element) { return getTypeOfDestructuredArrayElement(getAssignedType(node), node.elements.indexOf(element)); @@ -74349,7 +74349,7 @@ function createTypeChecker(host) { switch (parent2.kind) { case 250 /* ForInStatement */: return stringType; - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: return checkRightHandSideOfForOf(parent2) || errorType; case 227 /* BinaryExpression */: return getAssignedTypeOfBinaryExpression(parent2); @@ -74383,7 +74383,7 @@ function createTypeChecker(host) { if (node.parent.parent.kind === 250 /* ForInStatement */) { return stringType; } - if (node.parent.parent.kind === 251 /* ForOfStatement */) { + if (node.parent.parent.kind === 252 /* ForOfStatement */) { return checkRightHandSideOfForOf(node.parent.parent) || errorType; } return errorType; @@ -74621,7 +74621,7 @@ function createTypeChecker(host) { if (isDeclarationWithExplicitTypeAnnotation(declaration)) { return getTypeOfSymbol(symbol); } - if (isVariableDeclaration(declaration) && declaration.parent.parent.kind === 251 /* ForOfStatement */) { + if (isVariableDeclaration(declaration) && declaration.parent.parent.kind === 252 /* ForOfStatement */) { const statement = declaration.parent.parent; const expressionType = getTypeOfDottedName( statement.expression, @@ -76060,7 +76060,7 @@ function createTypeChecker(host) { case 248 /* WhileStatement */: case 249 /* ForStatement */: case 250 /* ForInStatement */: - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: case 255 /* WithStatement */: case 256 /* SwitchStatement */: case 259 /* TryStatement */: @@ -87377,7 +87377,7 @@ function createTypeChecker(host) { case 270 /* CaseBlock */: case 249 /* ForStatement */: case 250 /* ForInStatement */: - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: checkUnusedLocalsAndParameters(node, addDiagnostic); break; case 177 /* Constructor */: @@ -91114,7 +91114,7 @@ function createTypeChecker(host) { return checkForStatement(node); case 250 /* ForInStatement */: return checkForInStatement(node); - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: return checkForOfStatement(node); case 252 /* ContinueStatement */: case 253 /* BreakStatement */: @@ -92108,7 +92108,7 @@ function createTypeChecker(host) { } function getTypeOfAssignmentPattern(expr) { Debug.assert(expr.kind === 211 /* ObjectLiteralExpression */ || expr.kind === 210 /* ArrayLiteralExpression */); - if (expr.parent.kind === 251 /* ForOfStatement */) { + if (expr.parent.kind === 252 /* ForOfStatement */) { const iteratedType = checkRightHandSideOfForOf(expr.parent); return checkDestructuringAssignment(expr, iteratedType || errorType); } @@ -94100,7 +94100,7 @@ function createTypeChecker(host) { if (checkGrammarStatementInAmbientContext(forInOrOfStatement)) { return true; } - if (forInOrOfStatement.kind === 251 /* ForOfStatement */ && forInOrOfStatement.awaitModifier) { + if (forInOrOfStatement.kind === 252 /* ForOfStatement */ && forInOrOfStatement.awaitModifier) { if (!(forInOrOfStatement.flags & 65536 /* AwaitContext */)) { const sourceFile = getSourceFileOfNode(forInOrOfStatement); if (isInTopLevelContext(forInOrOfStatement)) { @@ -94409,7 +94409,7 @@ function createTypeChecker(host) { return grammarErrorOnNode(node, Diagnostics._0_declarations_may_not_have_binding_patterns, "using"); } } - if (node.parent.parent.kind !== 250 /* ForInStatement */ && node.parent.parent.kind !== 251 /* ForOfStatement */) { + if (node.parent.parent.kind !== 250 /* ForInStatement */ && node.parent.parent.kind !== 252 /* ForOfStatement */) { if (nodeFlags & 33554432 /* Ambient */) { checkAmbientInitializer(node); } else if (!node.initializer) { @@ -94501,7 +94501,7 @@ function createTypeChecker(host) { case 255 /* WithStatement */: case 249 /* ForStatement */: case 250 /* ForInStatement */: - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: return false; case 257 /* LabeledStatement */: return allowBlockDeclarations(parent2.parent); @@ -95961,7 +95961,7 @@ var visitEachChildTable = { visitIterationBody(node.statement, visitor, context, nodeVisitor) ); }, - [251 /* ForOfStatement */]: function visitEachChildOfForOfStatement(node, visitor, context, _nodesVisitor, nodeVisitor, tokenVisitor) { + [252 /* ForOfStatement */]: function visitEachChildOfForOfStatement(node, visitor, context, _nodesVisitor, nodeVisitor, tokenVisitor) { return context.factory.updateForOfStatement( node, tokenVisitor ? nodeVisitor(node.awaitModifier, tokenVisitor, isAwaitKeyword) : node.awaitModifier, @@ -105132,7 +105132,7 @@ function transformES2017(context) { return visitForStatementInAsyncBody(node); case 250 /* ForInStatement */: return visitForInStatementInAsyncBody(node); - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: return visitForOfStatementInAsyncBody(node); case 300 /* CatchClause */: return visitCatchClauseInAsyncBody(node); @@ -105986,7 +105986,7 @@ function transformES2018(context) { 0 /* IterationStatementExcludes */, 2 /* IterationStatementIncludes */ ); - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: return visitForOfStatement( node, /*outermostLabeledStatement*/ @@ -106157,7 +106157,7 @@ function transformES2018(context) { function visitLabeledStatement(node) { if (enclosingFunctionFlags & 2 /* Async */) { const statement = unwrapInnermostStatementOfLabel(node); - if (statement.kind === 251 /* ForOfStatement */ && statement.awaitModifier) { + if (statement.kind === 252 /* ForOfStatement */ && statement.awaitModifier) { return visitForOfStatement(statement, node); } return factory2.restoreEnclosingLabel(visitNode(statement, visitor, isStatement, factory2.liftToBlock), node); @@ -107498,7 +107498,7 @@ function transformESNext(context) { return visitBlock(node); case 249 /* ForStatement */: return visitForStatement(node); - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: return visitForOfStatement(node); case 256 /* SwitchStatement */: return visitSwitchStatement(node); @@ -108736,7 +108736,7 @@ var entities = new Map(Object.entries({ oslash: 248, ugrave: 249, uacute: 250, - ucirc: 251, + ucirc: 252, uuml: 252, yacute: 253, thorn: 254, @@ -109162,7 +109162,7 @@ function transformES2015(context) { /*outermostLabeledStatement*/ void 0 ); - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: return visitForOfStatement( node, /*outermostLabeledStatement*/ @@ -110772,7 +110772,7 @@ function transformES2015(context) { return visitForStatement(node, outermostLabeledStatement); case 250 /* ForInStatement */: return visitForInStatement(node, outermostLabeledStatement); - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: return visitForOfStatement(node, outermostLabeledStatement); } } @@ -111246,7 +111246,7 @@ function transformES2015(context) { return convertForStatement(node, initializerFunction, convertedLoopBody); case 250 /* ForInStatement */: return convertForInStatement(node, convertedLoopBody); - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: return convertForOfStatement(node, convertedLoopBody); case 247 /* DoStatement */: return convertDoStatement(node, convertedLoopBody); @@ -111304,7 +111304,7 @@ function transformES2015(context) { switch (node.kind) { case 249 /* ForStatement */: case 250 /* ForInStatement */: - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: const initializer = node.initializer; if (initializer && initializer.kind === 262 /* VariableDeclarationList */) { loopInitializer = initializer; @@ -114841,7 +114841,7 @@ function transformModule(context) { ); case 250 /* ForInStatement */: return visitForInStatement(node); - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: return visitForOfStatement(node); case 247 /* DoStatement */: return visitDoStatement(node); @@ -117113,7 +117113,7 @@ function transformSystemModule(context) { ); case 250 /* ForInStatement */: return visitForInStatement(node); - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: return visitForOfStatement(node); case 247 /* DoStatement */: return visitDoStatement(node); @@ -121646,7 +121646,7 @@ function createPrinter(printerOptions = {}, handlers = {}) { return emitForStatement(node); case 250 /* ForInStatement */: return emitForInStatement(node); - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: return emitForOfStatement(node); case 252 /* ContinueStatement */: return emitContinueStatement(node); @@ -124787,7 +124787,7 @@ function createPrinter(printerOptions = {}, handlers = {}) { generateNames(node.elseStatement); break; case 249 /* ForStatement */: - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: case 250 /* ForInStatement */: generateNames(node.initializer); generateNames(node.statement); @@ -139884,7 +139884,7 @@ function isCompletedNode(n, sourceFile) { return false; case 249 /* ForStatement */: case 250 /* ForInStatement */: - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: case 248 /* WhileStatement */: return isCompletedNode(n.statement, sourceFile); case 247 /* DoStatement */: @@ -140674,7 +140674,7 @@ function isArrayLiteralOrObjectLiteralDestructuringPattern(node) { if (node.parent.kind === 227 /* BinaryExpression */ && node.parent.left === node && node.parent.operatorToken.kind === 64 /* EqualsToken */) { return true; } - if (node.parent.kind === 251 /* ForOfStatement */ && node.parent.initializer === node) { + if (node.parent.kind === 252 /* ForOfStatement */ && node.parent.initializer === node) { return true; } if (isArrayLiteralOrObjectLiteralDestructuringPattern(node.parent.kind === 304 /* PropertyAssignment */ ? node.parent.parent : node.parent)) { @@ -143632,7 +143632,7 @@ var DocumentHighlights; // falls through case 249 /* ForStatement */: case 250 /* ForInStatement */: - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: case 248 /* WhileStatement */: case 247 /* DoStatement */: return !statement.label || isLabeledBy(node, statement.label.escapedText); @@ -143712,7 +143712,7 @@ var DocumentHighlights; switch (owner.kind) { case 249 /* ForStatement */: case 250 /* ForInStatement */: - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: case 247 /* DoStatement */: case 248 /* WhileStatement */: return getLoopBreakContinueOccurrences(owner); @@ -154280,7 +154280,7 @@ function spanInSourceFileAtLocation(sourceFile, position) { return spanInForStatement(node); case 250 /* ForInStatement */: return textSpanEndingAtNextToken(node, node.expression); - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: return spanInInitializerOfForLike(node); case 256 /* SwitchStatement */: return textSpanEndingAtNextToken(node, node.expression); @@ -154378,7 +154378,7 @@ function spanInSourceFileAtLocation(sourceFile, position) { case 171 /* Decorator */: return spanInNode(node.parent); case 249 /* ForStatement */: - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: return textSpan(node); case 227 /* BinaryExpression */: if (node.parent.operatorToken.kind === 28 /* CommaToken */) { @@ -154441,7 +154441,7 @@ function spanInSourceFileAtLocation(sourceFile, position) { if (isBindingPattern(variableDeclaration.name)) { return spanInBindingPattern(variableDeclaration.name); } - if (hasOnlyExpressionInitializer(variableDeclaration) && variableDeclaration.initializer || hasSyntacticModifier(variableDeclaration, 32 /* Export */) || parent2.parent.kind === 251 /* ForOfStatement */) { + if (hasOnlyExpressionInitializer(variableDeclaration) && variableDeclaration.initializer || hasSyntacticModifier(variableDeclaration, 32 /* Export */) || parent2.parent.kind === 252 /* ForOfStatement */) { return textSpanFromVariableDeclaration(variableDeclaration); } if (isVariableDeclarationList(variableDeclaration.parent) && variableDeclaration.parent.declarations[0] !== variableDeclaration) { @@ -154500,7 +154500,7 @@ function spanInSourceFileAtLocation(sourceFile, position) { return spanInNodeIfStartsOnSameLine(block.parent, block.statements[0]); // Set span on previous token if it starts on same line otherwise on the first statement of the block case 249 /* ForStatement */: - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: return spanInNodeIfStartsOnSameLine(findPrecedingToken(block.pos, sourceFile, block.parent), block.statements[0]); } return spanInNode(block.statements[0]); @@ -154630,7 +154630,7 @@ function spanInSourceFileAtLocation(sourceFile, position) { case 248 /* WhileStatement */: case 247 /* DoStatement */: case 249 /* ForStatement */: - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: case 214 /* CallExpression */: case 215 /* NewExpression */: case 218 /* ParenthesizedExpression */: @@ -154659,7 +154659,7 @@ function spanInSourceFileAtLocation(sourceFile, position) { return spanInNode(node2.parent); } function spanInOfKeyword(node2) { - if (node2.parent.kind === 251 /* ForOfStatement */) { + if (node2.parent.kind === 252 /* ForOfStatement */) { return spanInNextNode(node2); } return spanInNode(node2.parent); @@ -162485,7 +162485,7 @@ function canPrefix(token) { case 261 /* VariableDeclaration */: { const varDecl = token.parent; switch (varDecl.parent.parent.kind) { - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: case 250 /* ForInStatement */: return true; } @@ -169764,7 +169764,7 @@ function getCompletionData(program, log, sourceFile, compilerOptions, position, isNewIdentifierLocation = false; const rootDeclaration = getRootDeclaration(objectLikeContainer.parent); if (!isVariableLike(rootDeclaration)) return Debug.fail("Root declaration is not variable-like."); - let canGetType = hasInitializer(rootDeclaration) || !!getEffectiveTypeAnnotationNode(rootDeclaration) || rootDeclaration.parent.parent.kind === 251 /* ForOfStatement */; + let canGetType = hasInitializer(rootDeclaration) || !!getEffectiveTypeAnnotationNode(rootDeclaration) || rootDeclaration.parent.parent.kind === 252 /* ForOfStatement */; if (!canGetType && rootDeclaration.kind === 170 /* Parameter */) { if (isExpression(rootDeclaration.parent)) { canGetType = !!typeChecker.getContextualType(rootDeclaration.parent); @@ -172505,7 +172505,7 @@ function getContextNode(node) { return node.parent; case 227 /* BinaryExpression */: return isExpressionStatement(node.parent) ? node.parent : node; - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: case 250 /* ForInStatement */: return { start: node.initializer, @@ -177133,7 +177133,7 @@ function getOutliningSpanForNode(n, sourceFile) { switch (n.parent.kind) { case 247 /* DoStatement */: case 250 /* ForInStatement */: - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: case 249 /* ForStatement */: case 246 /* IfStatement */: case 248 /* WhileStatement */: @@ -180339,7 +180339,7 @@ var deleteDeclaration; } const gp = parent2.parent; switch (gp.kind) { - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: case 250 /* ForInStatement */: changes.replaceNode(sourceFile, node, factory.createObjectLiteralExpression()); break; @@ -181131,7 +181131,7 @@ function isBinaryOpContext(context) { case 169 /* TypeParameter */: return context.currentTokenSpan.kind === 103 /* InKeyword */ || context.nextTokenSpan.kind === 103 /* InKeyword */ || context.currentTokenSpan.kind === 64 /* EqualsToken */ || context.nextTokenSpan.kind === 64 /* EqualsToken */; // Technically, "of" is not a binary operator, but format it the same way as "in" - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: return context.currentTokenSpan.kind === 165 /* OfKeyword */ || context.nextTokenSpan.kind === 165 /* OfKeyword */; } return false; @@ -181262,7 +181262,7 @@ function isControlDeclContext(context) { case 256 /* SwitchStatement */: case 249 /* ForStatement */: case 250 /* ForInStatement */: - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: case 248 /* WhileStatement */: case 259 /* TryStatement */: case 247 /* DoStatement */: @@ -181409,7 +181409,7 @@ function isStatementConditionContext(context) { case 246 /* IfStatement */: case 249 /* ForStatement */: case 250 /* ForInStatement */: - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: case 247 /* DoStatement */: case 248 /* WhileStatement */: return true; @@ -183041,7 +183041,7 @@ var SmartIndenter; case 247 /* DoStatement */: case 248 /* WhileStatement */: case 250 /* ForInStatement */: - case 251 /* ForOfStatement */: + case 252 /* ForOfStatement */: case 249 /* ForStatement */: case 246 /* IfStatement */: case 263 /* FunctionDeclaration */: diff --git a/valdi/node_modules/typescript/package.json b/valdi/node_modules/typescript/package.json index cccb75de..cec59949 100644 --- a/valdi/node_modules/typescript/package.json +++ b/valdi/node_modules/typescript/package.json @@ -43,7 +43,7 @@ "@dprint/typescript": "0.93.4", "@esfx/canceltoken": "^1.0.0", "@eslint/js": "^9.20.0", - "@octokit/rest": "^21.1.1", + "@octokit/rest": "^21.1.2", "@types/chai": "^4.3.20", "@types/diff": "^7.0.1", "@types/minimist": "^1.2.5", @@ -76,7 +76,7 @@ "mocha-fivemat-progress-reporter": "^0.1.0", "monocart-coverage-reports": "^2.12.1", "ms": "^2.1.3", - "picocolors": "^1.1.1", + "picocolors": "^1.1.2", "playwright": "^1.50.1", "source-map-support": "^0.5.21", "tslib": "^2.8.1",