diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index f1b8675b6..a3c0ca863 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -15,7 +15,7 @@ jobs: - uses: subosito/flutter-action@v1 with: channel: beta - flutter-version: '2.1.0-12.2.pre' + flutter-version: '2.2.0-10.1.pre' - name: Clone the repository. uses: actions/checkout@v2 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7f17426e2..cc38288d9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,7 +17,7 @@ jobs: - uses: subosito/flutter-action@v1 with: channel: beta - flutter-version: '2.1.0-12.2.pre' + flutter-version: '2.2.0-10.1.pre' # Workaround for this Android Gradle Plugin issue (supposedly fixed in AGP 4.1): # https://issuetracker.google.com/issues/144111441 @@ -50,8 +50,8 @@ jobs: echo "${{ secrets.KEY_JKS }}" > release.keystore.asc gpg -d --passphrase "${{ secrets.KEY_JKS_PASSPHRASE }}" --batch release.keystore.asc > $AVES_STORE_FILE rm release.keystore.asc - flutter build apk --bundle-sksl-path shaders_2.1.0-12.2.pre.sksl.json - flutter build appbundle --bundle-sksl-path shaders_2.1.0-12.2.pre.sksl.json + flutter build apk --bundle-sksl-path shaders_2.2.0-10.1.pre.sksl.json + flutter build appbundle --bundle-sksl-path shaders_2.2.0-10.1.pre.sksl.json rm $AVES_STORE_FILE env: AVES_STORE_FILE: ${{ github.workspace }}/key.jks diff --git a/CHANGELOG.md b/CHANGELOG.md index 93f8d47c9..f8dceea69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,19 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +## [v1.4.1] - 2021-04-29 +### Added +- Motion photo support +- Viewer: play videos in multi-track HEIC +- Handle share intent + +### Changed +- Upgraded Flutter to beta v2.2.0-10.1.pre + +### Fixed +- fixed crash when cataloguing large MP4/PSD +- prevent videos playing in the background when quickly switching entries + ## [v1.4.0] - 2021-04-16 ### Added - Viewer: support for videos with EAC3/FLAC/OPUS audio diff --git a/android/app/libs/fijkplayer-full-release.aar b/android/app/libs/fijkplayer-full-release.aar index 72547c9dc..d325ae6f0 100644 Binary files a/android/app/libs/fijkplayer-full-release.aar and b/android/app/libs/fijkplayer-full-release.aar differ diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index bde50c227..1aa2beace 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -62,6 +62,14 @@ + + + + + + + + @@ -69,6 +77,7 @@ + @@ -88,6 +97,7 @@ + @@ -96,6 +106,7 @@ + diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt b/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt index a29d60cf7..e41bb1d0c 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt @@ -4,6 +4,7 @@ import android.content.Intent import android.net.Uri import android.os.Build import android.os.Bundle +import android.os.Parcelable import android.util.Log import androidx.annotation.RequiresApi import androidx.core.content.pm.ShortcutInfoCompat @@ -12,6 +13,7 @@ import androidx.core.graphics.drawable.IconCompat import app.loup.streams_channel.StreamsChannel import deckers.thibault.aves.channel.calls.* import deckers.thibault.aves.channel.streams.* +import deckers.thibault.aves.model.provider.MediaStoreImageProvider import deckers.thibault.aves.utils.LogUtils import deckers.thibault.aves.utils.PermissionManager import io.flutter.embedding.android.FlutterActivity @@ -33,6 +35,7 @@ class MainActivity : FlutterActivity() { MethodChannel(messenger, AppAdapterHandler.CHANNEL).setMethodCallHandler(AppAdapterHandler(this)) MethodChannel(messenger, AppShortcutHandler.CHANNEL).setMethodCallHandler(AppShortcutHandler(this)) MethodChannel(messenger, DebugHandler.CHANNEL).setMethodCallHandler(DebugHandler(this)) + MethodChannel(messenger, EmbeddedDataHandler.CHANNEL).setMethodCallHandler(EmbeddedDataHandler(this)) MethodChannel(messenger, ImageFileHandler.CHANNEL).setMethodCallHandler(ImageFileHandler(this)) MethodChannel(messenger, GeocodingHandler.CHANNEL).setMethodCallHandler(GeocodingHandler(this)) MethodChannel(messenger, MediaStoreHandler.CHANNEL).setMethodCallHandler(MediaStoreHandler(this)) @@ -83,21 +86,29 @@ class MainActivity : FlutterActivity() { } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - if (requestCode == PermissionManager.VOLUME_ACCESS_REQUEST_CODE) { - val treeUri = data?.data - if (resultCode != RESULT_OK || treeUri == null) { - PermissionManager.onPermissionResult(requestCode, null) - return + when (requestCode) { + VOLUME_ACCESS_REQUEST -> { + val treeUri = data?.data + if (resultCode != RESULT_OK || treeUri == null) { + PermissionManager.onPermissionResult(requestCode, null) + return + } + + // save access permissions across reboots + val takeFlags = (data.flags + and (Intent.FLAG_GRANT_READ_URI_PERMISSION + or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)) + contentResolver.takePersistableUriPermission(treeUri, takeFlags) + + // resume pending action + PermissionManager.onPermissionResult(requestCode, treeUri) + } + DELETE_PERMISSION_REQUEST -> { + // delete permission may be requested on Android 10+ only + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + MediaStoreImageProvider.pendingDeleteCompleter?.complete(resultCode == RESULT_OK) + } } - - // save access permissions across reboots - val takeFlags = (data.flags - and (Intent.FLAG_GRANT_READ_URI_PERMISSION - or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)) - contentResolver.takePersistableUriPermission(treeUri, takeFlags) - - // resume pending action - PermissionManager.onPermissionResult(requestCode, treeUri) } } @@ -111,8 +122,8 @@ class MainActivity : FlutterActivity() { ) } } - Intent.ACTION_VIEW, "com.android.camera.action.REVIEW" -> { - intent.data?.let { uri -> + Intent.ACTION_VIEW, Intent.ACTION_SEND, "com.android.camera.action.REVIEW" -> { + (intent.data ?: (intent.getParcelableExtra(Intent.EXTRA_STREAM) as? Uri))?.let { uri -> return hashMapOf( "action" to "view", "uri" to uri.toString(), @@ -171,7 +182,9 @@ class MainActivity : FlutterActivity() { } companion object { - private val LOG_TAG = LogUtils.createTag(MainActivity::class.java) + private val LOG_TAG = LogUtils.createTag() const val VIEWER_CHANNEL = "deckers.thibault/aves/viewer" + const val VOLUME_ACCESS_REQUEST = 1 + const val DELETE_PERMISSION_REQUEST = 2 } } \ No newline at end of file diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/AppAdapterHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/AppAdapterHandler.kt index 9df5b08c6..f3c8e551c 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/AppAdapterHandler.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/AppAdapterHandler.kt @@ -12,6 +12,7 @@ import com.bumptech.glide.Glide import com.bumptech.glide.load.DecodeFormat import com.bumptech.glide.request.RequestOptions import deckers.thibault.aves.channel.calls.Coresult.Companion.safe +import deckers.thibault.aves.channel.calls.Coresult.Companion.safesus import deckers.thibault.aves.model.FieldMap import deckers.thibault.aves.utils.BitmapUtils.getBytes import deckers.thibault.aves.utils.LogUtils @@ -30,7 +31,7 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler { override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { when (call.method) { "getPackages" -> GlobalScope.launch(Dispatchers.IO) { safe(call, result, ::getPackages) } - "getAppIcon" -> GlobalScope.launch(Dispatchers.IO) { safe(call, result, ::getAppIcon) } + "getAppIcon" -> GlobalScope.launch(Dispatchers.IO) { safesus(call, result, ::getAppIcon) } "edit" -> { val title = call.argument("title") val uri = call.argument("uri")?.let { Uri.parse(it) } @@ -109,7 +110,7 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler { result.success(ArrayList(packages.values)) } - private fun getAppIcon(call: MethodCall, result: MethodChannel.Result) { + private suspend fun getAppIcon(call: MethodCall, result: MethodChannel.Result) { val packageName = call.argument("packageName") val sizeDip = call.argument("sizeDip") if (packageName == null || sizeDip == null) { @@ -254,8 +255,8 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler { return when (uri.scheme?.toLowerCase(Locale.ROOT)) { ContentResolver.SCHEME_FILE -> { uri.path?.let { path -> - val applicationId = context.applicationContext.packageName - FileProvider.getUriForFile(context, "$applicationId.fileprovider", File(path)) + val authority = "${context.applicationContext.packageName}.fileprovider" + FileProvider.getUriForFile(context, authority, File(path)) } } else -> uri @@ -263,7 +264,7 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler { } companion object { - private val LOG_TAG = LogUtils.createTag(AppAdapterHandler::class.java) + private val LOG_TAG = LogUtils.createTag() const val CHANNEL = "deckers.thibault/aves/app" } } \ No newline at end of file diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/DebugHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/DebugHandler.kt index 70e7a46f5..be5cbdb78 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/DebugHandler.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/DebugHandler.kt @@ -305,7 +305,7 @@ class DebugHandler(private val context: Context) : MethodCallHandler { ) companion object { - private val LOG_TAG = LogUtils.createTag(DebugHandler::class.java) + private val LOG_TAG = LogUtils.createTag() const val CHANNEL = "deckers.thibault/aves/debug" } } \ No newline at end of file diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/EmbeddedDataHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/EmbeddedDataHandler.kt new file mode 100644 index 000000000..534f5c520 --- /dev/null +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/EmbeddedDataHandler.kt @@ -0,0 +1,237 @@ +package deckers.thibault.aves.channel.calls + +import android.content.Context +import android.net.Uri +import android.util.Log +import androidx.core.content.FileProvider +import androidx.exifinterface.media.ExifInterface +import com.adobe.internal.xmp.XMPException +import com.adobe.internal.xmp.XMPUtils +import com.bumptech.glide.load.resource.bitmap.TransformationUtils +import com.drew.imaging.ImageMetadataReader +import com.drew.metadata.file.FileTypeDirectory +import com.drew.metadata.xmp.XmpDirectory +import deckers.thibault.aves.channel.calls.Coresult.Companion.safe +import deckers.thibault.aves.channel.calls.Coresult.Companion.safesus +import deckers.thibault.aves.metadata.Metadata +import deckers.thibault.aves.metadata.MetadataExtractorHelper.getSafeString +import deckers.thibault.aves.metadata.MultiPage +import deckers.thibault.aves.metadata.XMP +import deckers.thibault.aves.model.FieldMap +import deckers.thibault.aves.model.provider.ContentImageProvider +import deckers.thibault.aves.model.provider.ImageProvider +import deckers.thibault.aves.utils.BitmapUtils +import deckers.thibault.aves.utils.BitmapUtils.getBytes +import deckers.thibault.aves.utils.LogUtils +import deckers.thibault.aves.utils.MimeTypes +import deckers.thibault.aves.utils.MimeTypes.extensionFor +import deckers.thibault.aves.utils.MimeTypes.isImage +import deckers.thibault.aves.utils.MimeTypes.isSupportedByExifInterface +import deckers.thibault.aves.utils.MimeTypes.isSupportedByMetadataExtractor +import deckers.thibault.aves.utils.MimeTypes.isVideo +import deckers.thibault.aves.utils.StorageUtils +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import io.flutter.plugin.common.MethodChannel.MethodCallHandler +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import java.io.File +import java.io.InputStream +import java.util.* + +class EmbeddedDataHandler(private val context: Context) : MethodCallHandler { + override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { + when (call.method) { + "getExifThumbnails" -> GlobalScope.launch(Dispatchers.IO) { safesus(call, result, ::getExifThumbnails) } + "extractMotionPhotoVideo" -> GlobalScope.launch(Dispatchers.IO) { safe(call, result, ::extractMotionPhotoVideo) } + "extractVideoEmbeddedPicture" -> GlobalScope.launch(Dispatchers.IO) { safe(call, result, ::extractVideoEmbeddedPicture) } + "extractXmpDataProp" -> GlobalScope.launch(Dispatchers.IO) { safe(call, result, ::extractXmpDataProp) } + else -> result.notImplemented() + } + } + + private suspend fun getExifThumbnails(call: MethodCall, result: MethodChannel.Result) { + val mimeType = call.argument("mimeType") + val uri = call.argument("uri")?.let { Uri.parse(it) } + val sizeBytes = call.argument("sizeBytes")?.toLong() + if (mimeType == null || uri == null) { + result.error("getExifThumbnails-args", "failed because of missing arguments", null) + return + } + + val thumbnails = ArrayList() + if (isSupportedByExifInterface(mimeType)) { + try { + Metadata.openSafeInputStream(context, uri, mimeType, sizeBytes)?.use { input -> + val exif = ExifInterface(input) + val orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL) + exif.thumbnailBitmap?.let { bitmap -> + TransformationUtils.rotateImageExif(BitmapUtils.getBitmapPool(context), bitmap, orientation)?.let { + it.getBytes(canHaveAlpha = false, recycle = false)?.let { bytes -> thumbnails.add(bytes) } + } + } + } + } catch (e: Exception) { + // ExifInterface initialization can fail with a RuntimeException + // caused by an internal MediaMetadataRetriever failure + } + } + result.success(thumbnails) + } + + private fun extractMotionPhotoVideo(call: MethodCall, result: MethodChannel.Result) { + val mimeType = call.argument("mimeType") + val uri = call.argument("uri")?.let { Uri.parse(it) } + val sizeBytes = call.argument("sizeBytes")?.toLong() + val displayName = call.argument("displayName") + if (mimeType == null || uri == null || sizeBytes == null) { + result.error("extractMotionPhotoVideo-args", "failed because of missing arguments", null) + return + } + + MultiPage.getMotionPhotoOffset(context, uri, mimeType, sizeBytes)?.let { videoSizeBytes -> + val videoStartOffset = sizeBytes - videoSizeBytes + StorageUtils.openInputStream(context, uri)?.let { input -> + input.skip(videoStartOffset) + copyEmbeddedBytes(result, MimeTypes.MP4, displayName, input) + } + return + } + + result.error("extractMotionPhotoVideo-empty", "failed to extract video from motion photo at uri=$uri", null) + } + + private fun extractVideoEmbeddedPicture(call: MethodCall, result: MethodChannel.Result) { + val uri = call.argument("uri")?.let { Uri.parse(it) } + val displayName = call.argument("displayName") + if (uri == null) { + result.error("extractVideoEmbeddedPicture-args", "failed because of missing arguments", null) + return + } + + val retriever = StorageUtils.openMetadataRetriever(context, uri) + if (retriever != null) { + try { + retriever.embeddedPicture?.let { bytes -> + var embedMimeType: String? = null + bytes.inputStream().use { input -> + val metadata = ImageMetadataReader.readMetadata(input) + metadata.getFirstDirectoryOfType(FileTypeDirectory::class.java)?.let { dir -> + dir.getSafeString(FileTypeDirectory.TAG_DETECTED_FILE_MIME_TYPE) { embedMimeType = it } + } + } + embedMimeType?.let { mime -> + copyEmbeddedBytes(result, mime, displayName, bytes.inputStream()) + return + } + } + } catch (e: Exception) { + result.error("extractVideoEmbeddedPicture-fetch", "failed to fetch picture for uri=$uri", e.message) + } finally { + // cannot rely on `MediaMetadataRetriever` being `AutoCloseable` on older APIs + retriever.release() + } + } + result.error("extractVideoEmbeddedPicture-empty", "failed to extract picture for uri=$uri", null) + } + + private fun extractXmpDataProp(call: MethodCall, result: MethodChannel.Result) { + val mimeType = call.argument("mimeType") + val uri = call.argument("uri")?.let { Uri.parse(it) } + val sizeBytes = call.argument("sizeBytes")?.toLong() + val displayName = call.argument("displayName") + val dataPropPath = call.argument("propPath") + val embedMimeType = call.argument("propMimeType") + if (mimeType == null || uri == null || dataPropPath == null || embedMimeType == null) { + result.error("extractXmpDataProp-args", "failed because of missing arguments", null) + return + } + + if (isSupportedByMetadataExtractor(mimeType)) { + try { + Metadata.openSafeInputStream(context, uri, mimeType, sizeBytes)?.use { input -> + val metadata = ImageMetadataReader.readMetadata(input) + // data can be large and stored in "Extended XMP", + // which is returned as a second XMP directory + val xmpDirs = metadata.getDirectoriesOfType(XmpDirectory::class.java) + try { + val pathParts = dataPropPath.split('/') + + val embedBytes: ByteArray = if (pathParts.size == 1) { + val propName = pathParts[0] + val propNs = XMP.namespaceForPropPath(propName) + xmpDirs.map { it.xmpMeta.getPropertyBase64(propNs, propName) }.first { it != null } + } else { + val structName = pathParts[0] + val structNs = XMP.namespaceForPropPath(structName) + val fieldName = pathParts[1] + val fieldNs = XMP.namespaceForPropPath(fieldName) + xmpDirs.map { it.xmpMeta.getStructField(structNs, structName, fieldNs, fieldName) }.first { it != null }.let { + XMPUtils.decodeBase64(it.value) + } + } + + copyEmbeddedBytes(result, embedMimeType, displayName, embedBytes.inputStream()) + return + } catch (e: XMPException) { + result.error("extractXmpDataProp-xmp", "failed to read XMP directory for uri=$uri prop=$dataPropPath", e.message) + return + } + } + } catch (e: Exception) { + Log.w(LOG_TAG, "failed to extract file from XMP", e) + } catch (e: NoClassDefFoundError) { + Log.w(LOG_TAG, "failed to extract file from XMP", e) + } + } + result.error("extractXmpDataProp-empty", "failed to extract file from XMP uri=$uri prop=$dataPropPath", null) + } + + private fun copyEmbeddedBytes(result: MethodChannel.Result, mimeType: String, displayName: String?, embeddedByteStream: InputStream) { + val extension = extensionFor(mimeType) + val file = File.createTempFile("aves", extension, context.cacheDir).apply { + deleteOnExit() + outputStream().use { output -> + embeddedByteStream.use { input -> + input.copyTo(output) + } + } + } + val authority = "${context.applicationContext.packageName}.fileprovider" + val uri = if (displayName != null) { + // add extension to ease type identification when sharing this content + val displayNameWithExtension = if (extension == null || displayName.endsWith(extension, ignoreCase = true)) { + displayName + } else { + "$displayName$extension" + } + FileProvider.getUriForFile(context, authority, file, displayNameWithExtension) + } else { + FileProvider.getUriForFile(context, authority, file) + } + val resultFields: FieldMap = hashMapOf( + "uri" to uri.toString(), + "mimeType" to mimeType, + ) + if (isImage(mimeType) || isVideo(mimeType)) { + GlobalScope.launch(Dispatchers.IO) { + ContentImageProvider().fetchSingle(context, uri, mimeType, object : ImageProvider.ImageOpCallback { + override fun onSuccess(fields: FieldMap) { + resultFields.putAll(fields) + result.success(resultFields) + } + + override fun onFailure(throwable: Throwable) = result.error("copyEmbeddedBytes-failure", "failed to get entry for uri=$uri mime=$mimeType", throwable.message) + }) + } + } else { + result.success(resultFields) + } + } + + companion object { + private val LOG_TAG = LogUtils.createTag() + const val CHANNEL = "deckers.thibault/aves/embedded" + } +} \ No newline at end of file diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/ImageFileHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/ImageFileHandler.kt index 67f6afec8..ff76d43b0 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/ImageFileHandler.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/ImageFileHandler.kt @@ -3,7 +3,6 @@ package deckers.thibault.aves.channel.calls import android.app.Activity import android.graphics.Rect import android.net.Uri -import android.util.Size import com.bumptech.glide.Glide import deckers.thibault.aves.channel.calls.Coresult.Companion.safe import deckers.thibault.aves.channel.calls.Coresult.Companion.safesus @@ -31,8 +30,8 @@ class ImageFileHandler(private val activity: Activity) : MethodCallHandler { override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { when (call.method) { "getEntry" -> GlobalScope.launch(Dispatchers.IO) { safesus(call, result, ::getEntry) } - "getThumbnail" -> GlobalScope.launch(Dispatchers.IO) { safe(call, result, ::getThumbnail) } - "getRegion" -> GlobalScope.launch(Dispatchers.IO) { safe(call, result, ::getRegion) } + "getThumbnail" -> GlobalScope.launch(Dispatchers.IO) { safesus(call, result, ::getThumbnail) } + "getRegion" -> GlobalScope.launch(Dispatchers.IO) { safesus(call, result, ::getRegion) } "rename" -> GlobalScope.launch(Dispatchers.IO) { safesus(call, result, ::rename) } "rotate" -> GlobalScope.launch(Dispatchers.IO) { safe(call, result, ::rotate) } "flip" -> GlobalScope.launch(Dispatchers.IO) { safe(call, result, ::flip) } @@ -61,7 +60,7 @@ class ImageFileHandler(private val activity: Activity) : MethodCallHandler { }) } - private fun getThumbnail(call: MethodCall, result: MethodChannel.Result) { + private suspend fun getThumbnail(call: MethodCall, result: MethodChannel.Result) { val uri = call.argument("uri") val mimeType = call.argument("mimeType") val dateModifiedSecs = call.argument("dateModifiedSecs")?.toLong() @@ -93,7 +92,7 @@ class ImageFileHandler(private val activity: Activity) : MethodCallHandler { ).fetch() } - private fun getRegion(call: MethodCall, result: MethodChannel.Result) { + private suspend fun getRegion(call: MethodCall, result: MethodChannel.Result) { val uri = call.argument("uri")?.let { Uri.parse(it) } val mimeType = call.argument("mimeType") val pageId = call.argument("pageId") @@ -185,7 +184,8 @@ class ImageFileHandler(private val activity: Activity) : MethodCallHandler { val uri = (entryMap["uri"] as String?)?.let { Uri.parse(it) } val path = entryMap["path"] as String? val mimeType = entryMap["mimeType"] as String? - if (uri == null || path == null || mimeType == null) { + val sizeBytes = (entryMap["sizeBytes"] as Number?)?.toLong() + if (uri == null || path == null || mimeType == null || sizeBytes == null) { result.error("changeOrientation-args", "failed because entry fields are missing", null) return } @@ -196,7 +196,7 @@ class ImageFileHandler(private val activity: Activity) : MethodCallHandler { return } - provider.changeOrientation(activity, path, uri, mimeType, op, object : ImageOpCallback { + provider.changeOrientation(activity, path, uri, mimeType, sizeBytes, op, object : ImageOpCallback { override fun onSuccess(fields: FieldMap) = result.success(fields) override fun onFailure(throwable: Throwable) = result.error("changeOrientation-failure", "failed to change orientation", throwable.message) }) diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataHandler.kt index 0aa6ce5e1..573777615 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataHandler.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataHandler.kt @@ -4,17 +4,13 @@ import android.content.ContentResolver import android.content.ContentUris import android.content.Context import android.database.Cursor -import android.media.MediaExtractor -import android.media.MediaFormat import android.media.MediaMetadataRetriever import android.net.Uri import android.provider.MediaStore import android.util.Log import androidx.exifinterface.media.ExifInterface import com.adobe.internal.xmp.XMPException -import com.adobe.internal.xmp.XMPUtils import com.adobe.internal.xmp.properties.XMPPropertyInfo -import com.bumptech.glide.load.resource.bitmap.TransformationUtils import com.drew.imaging.ImageMetadataReader import com.drew.lang.Rational import com.drew.metadata.Tag @@ -51,12 +47,9 @@ import deckers.thibault.aves.metadata.XMP.getSafeDateMillis import deckers.thibault.aves.metadata.XMP.getSafeInt import deckers.thibault.aves.metadata.XMP.getSafeLocalizedText import deckers.thibault.aves.metadata.XMP.getSafeString +import deckers.thibault.aves.metadata.XMP.isMotionPhoto import deckers.thibault.aves.metadata.XMP.isPanorama import deckers.thibault.aves.model.FieldMap -import deckers.thibault.aves.model.provider.FileImageProvider -import deckers.thibault.aves.model.provider.ImageProvider -import deckers.thibault.aves.utils.BitmapUtils -import deckers.thibault.aves.utils.BitmapUtils.getBytes import deckers.thibault.aves.utils.LogUtils import deckers.thibault.aves.utils.MimeTypes import deckers.thibault.aves.utils.MimeTypes.isHeic @@ -73,8 +66,6 @@ import io.flutter.plugin.common.MethodChannel.MethodCallHandler import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch -import org.beyka.tiffbitmapfactory.TiffBitmapFactory -import java.io.File import java.text.ParseException import java.util.* import kotlin.math.roundToLong @@ -88,9 +79,6 @@ class MetadataHandler(private val context: Context) : MethodCallHandler { "getMultiPageInfo" -> GlobalScope.launch(Dispatchers.IO) { safe(call, result, ::getMultiPageInfo) } "getPanoramaInfo" -> GlobalScope.launch(Dispatchers.IO) { safe(call, result, ::getPanoramaInfo) } "getContentResolverProp" -> GlobalScope.launch(Dispatchers.IO) { safe(call, result, ::getContentResolverProp) } - "getExifThumbnails" -> GlobalScope.launch(Dispatchers.IO) { safe(call, result, ::getExifThumbnails) } - "extractVideoEmbeddedPicture" -> GlobalScope.launch(Dispatchers.IO) { safe(call, result, ::extractVideoEmbeddedPicture) } - "extractXmpDataProp" -> GlobalScope.launch(Dispatchers.IO) { safe(call, result, ::extractXmpDataProp) } else -> result.notImplemented() } } @@ -315,10 +303,10 @@ class MetadataHandler(private val context: Context) : MethodCallHandler { // File type for (dir in metadata.getDirectoriesOfType(FileTypeDirectory::class.java)) { - // * `metadata-extractor` sometimes detect the the wrong mime type (e.g. `pef` file as `tiff`) - // * the content resolver / media store sometimes report the wrong mime type (e.g. `png` file as `jpeg`, `tiff` as `srw`) - // * `context.getContentResolver().getType()` sometimes return incorrect value - // * `MediaMetadataRetriever.setDataSource()` sometimes fail with `status = 0x80000000` + // * `metadata-extractor` sometimes detects the wrong mime type (e.g. `pef` file as `tiff`) + // * the content resolver / media store sometimes reports the wrong mime type (e.g. `png` file as `jpeg`, `tiff` as `srw`) + // * `context.getContentResolver().getType()` sometimes returns an incorrect value + // * `MediaMetadataRetriever.setDataSource()` sometimes fails with `status = 0x80000000` // * file extension is unreliable // In the end, `metadata-extractor` is the most reliable, except for `tiff` (false positives, false negatives), // in which case we trust the file extension @@ -382,6 +370,11 @@ class MetadataHandler(private val context: Context) : MethodCallHandler { if (xmpMeta.isPanorama()) { flags = flags or MASK_IS_360 } + + // identification of motion photo + if (xmpMeta.isMotionPhoto()) { + flags = flags or MASK_IS_MULTIPAGE + } } catch (e: XMPException) { Log.w(LOG_TAG, "failed to read XMP directory for uri=$uri", e) } @@ -471,7 +464,7 @@ class MetadataHandler(private val context: Context) : MethodCallHandler { } } - if (mimeType == MimeTypes.TIFF && isMultiPageTiff(uri)) flags = flags or MASK_IS_MULTIPAGE + if (mimeType == MimeTypes.TIFF && MultiPage.isMultiPageTiff(context, uri)) flags = flags or MASK_IS_MULTIPAGE metadataMap[KEY_FLAGS] = flags } @@ -591,67 +584,23 @@ class MetadataHandler(private val context: Context) : MethodCallHandler { private fun getMultiPageInfo(call: MethodCall, result: MethodChannel.Result) { val mimeType = call.argument("mimeType") val uri = call.argument("uri")?.let { Uri.parse(it) } - if (mimeType == null || uri == null) { + val sizeBytes = call.argument("sizeBytes")?.toLong() + if (mimeType == null || uri == null || sizeBytes == null) { result.error("getMultiPageInfo-args", "failed because of missing arguments", null) return } - val pages = ArrayList>() - if (mimeType == MimeTypes.TIFF) { - fun toMap(page: Int, options: TiffBitmapFactory.Options): HashMap { - return hashMapOf( - KEY_PAGE to page, - KEY_MIME_TYPE to mimeType, - KEY_WIDTH to options.outWidth, - KEY_HEIGHT to options.outHeight, - ) - } - getTiffPageInfo(uri, 0)?.let { first -> - pages.add(toMap(0, first)) - val pageCount = first.outDirectoryCount - for (i in 1 until pageCount) { - getTiffPageInfo(uri, i)?.let { pages.add(toMap(i, it)) } - } - } - } else if (isHeic(mimeType)) { - fun MediaFormat.getSafeInt(key: String, save: (value: Int) -> Unit) { - if (this.containsKey(key)) save(this.getInteger(key)) - } - - fun MediaFormat.getSafeLong(key: String, save: (value: Long) -> Unit) { - if (this.containsKey(key)) save(this.getLong(key)) - } - - val extractor = MediaExtractor() - extractor.setDataSource(context, uri, null) - for (i in 0 until extractor.trackCount) { - try { - val format = extractor.getTrackFormat(i) - format.getString(MediaFormat.KEY_MIME)?.let { mime -> - val trackMime = if (mime == MediaFormat.MIMETYPE_IMAGE_ANDROID_HEIC) MimeTypes.HEIC else mime - val page = hashMapOf( - KEY_PAGE to i, - KEY_MIME_TYPE to trackMime, - ) - - // do not use `MediaFormat.KEY_TRACK_ID` as it is actually not unique between tracks - // e.g. there could be both a video track and an image track with KEY_TRACK_ID == 1 - - format.getSafeInt(MediaFormat.KEY_IS_DEFAULT) { page[KEY_IS_DEFAULT] = it != 0 } - format.getSafeInt(MediaFormat.KEY_WIDTH) { page[KEY_WIDTH] = it } - format.getSafeInt(MediaFormat.KEY_HEIGHT) { page[KEY_HEIGHT] = it } - if (isVideo(trackMime)) { - format.getSafeLong(MediaFormat.KEY_DURATION) { page[KEY_DURATION] = it / 1000 } - } - pages.add(page) - } - } catch (e: Exception) { - Log.w(LOG_TAG, "failed to get track information for uri=$uri, track num=$i", e) - } - } - extractor.release() + val pages: ArrayList? = when (mimeType) { + MimeTypes.HEIC, MimeTypes.HEIF -> MultiPage.getHeicTracks(context, uri) + MimeTypes.JPEG -> MultiPage.getMotionPhotoPages(context, uri, mimeType, sizeBytes = sizeBytes) + MimeTypes.TIFF -> MultiPage.getTiffPages(context, uri) + else -> null + } + if (pages?.isEmpty() == true) { + result.error("getMultiPageInfo-empty", "failed to get pages for uri=$uri", null) + } else { + result.success(pages) } - result.success(pages) } private fun getPanoramaInfo(call: MethodCall, result: MethodChannel.Result) { @@ -745,176 +694,13 @@ class MetadataHandler(private val context: Context) : MethodCallHandler { result.success(value?.toString()) } - private fun getExifThumbnails(call: MethodCall, result: MethodChannel.Result) { - val mimeType = call.argument("mimeType") - val uri = call.argument("uri")?.let { Uri.parse(it) } - val sizeBytes = call.argument("sizeBytes")?.toLong() - if (mimeType == null || uri == null) { - result.error("getExifThumbnails-args", "failed because of missing arguments", null) - return - } - - val thumbnails = ArrayList() - if (isSupportedByExifInterface(mimeType)) { - try { - Metadata.openSafeInputStream(context, uri, mimeType, sizeBytes)?.use { input -> - val exif = ExifInterface(input) - val orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL) - exif.thumbnailBitmap?.let { bitmap -> - TransformationUtils.rotateImageExif(BitmapUtils.getBitmapPool(context), bitmap, orientation)?.let { - it.getBytes(canHaveAlpha = false, recycle = false)?.let { bytes -> thumbnails.add(bytes) } - } - } - } - } catch (e: Exception) { - // ExifInterface initialization can fail with a RuntimeException - // caused by an internal MediaMetadataRetriever failure - } - } - result.success(thumbnails) - } - - private fun extractVideoEmbeddedPicture(call: MethodCall, result: MethodChannel.Result) { - val uri = call.argument("uri")?.let { Uri.parse(it) } - if (uri == null) { - result.error("extractVideoEmbeddedPicture-args", "failed because of missing arguments", null) - return - } - - val retriever = StorageUtils.openMetadataRetriever(context, uri) - if (retriever != null) { - try { - retriever.embeddedPicture?.let { bytes -> - var embedMimeType: String? = null - bytes.inputStream().use { input -> - val metadata = ImageMetadataReader.readMetadata(input) - metadata.getFirstDirectoryOfType(FileTypeDirectory::class.java)?.let { dir -> - dir.getSafeString(FileTypeDirectory.TAG_DETECTED_FILE_MIME_TYPE) { embedMimeType = it } - } - } - embedMimeType?.let { mime -> - copyEmbeddedBytes(bytes, mime, result) - return - } - } - } catch (e: Exception) { - result.error("extractVideoEmbeddedPicture-fetch", "failed to fetch picture for uri=$uri", e.message) - } finally { - // cannot rely on `MediaMetadataRetriever` being `AutoCloseable` on older APIs - retriever.release() - } - } - result.error("extractVideoEmbeddedPicture-empty", "failed to extract picture for uri=$uri", null) - } - - private fun extractXmpDataProp(call: MethodCall, result: MethodChannel.Result) { - val mimeType = call.argument("mimeType") - val uri = call.argument("uri")?.let { Uri.parse(it) } - val sizeBytes = call.argument("sizeBytes")?.toLong() - val dataPropPath = call.argument("propPath") - val embedMimeType = call.argument("propMimeType") - if (mimeType == null || uri == null || dataPropPath == null || embedMimeType == null) { - result.error("extractXmpDataProp-args", "failed because of missing arguments", null) - return - } - - if (isSupportedByMetadataExtractor(mimeType)) { - try { - Metadata.openSafeInputStream(context, uri, mimeType, sizeBytes)?.use { input -> - val metadata = ImageMetadataReader.readMetadata(input) - // data can be large and stored in "Extended XMP", - // which is returned as a second XMP directory - val xmpDirs = metadata.getDirectoriesOfType(XmpDirectory::class.java) - try { - val pathParts = dataPropPath.split('/') - - val embedBytes: ByteArray = if (pathParts.size == 1) { - val propName = pathParts[0] - val propNs = XMP.namespaceForPropPath(propName) - xmpDirs.map { it.xmpMeta.getPropertyBase64(propNs, propName) }.first { it != null } - } else { - val structName = pathParts[0] - val structNs = XMP.namespaceForPropPath(structName) - val fieldName = pathParts[1] - val fieldNs = XMP.namespaceForPropPath(fieldName) - xmpDirs.map { it.xmpMeta.getStructField(structNs, structName, fieldNs, fieldName) }.first { it != null }.let { - XMPUtils.decodeBase64(it.value) - } - } - - copyEmbeddedBytes(embedBytes, embedMimeType, result) - return - } catch (e: XMPException) { - result.error("extractXmpDataProp-xmp", "failed to read XMP directory for uri=$uri prop=$dataPropPath", e.message) - return - } - } - } catch (e: Exception) { - Log.w(LOG_TAG, "failed to extract file from XMP", e) - } catch (e: NoClassDefFoundError) { - Log.w(LOG_TAG, "failed to extract file from XMP", e) - } - } - result.error("extractXmpDataProp-empty", "failed to extract file from XMP uri=$uri prop=$dataPropPath", null) - } - - private fun copyEmbeddedBytes(embedBytes: ByteArray, embedMimeType: String, result: MethodChannel.Result) { - val embedFile = File.createTempFile("aves", null, context.cacheDir).apply { - deleteOnExit() - outputStream().use { outputStream -> - embedBytes.inputStream().use { inputStream -> - inputStream.copyTo(outputStream) - } - } - } - val embedUri = Uri.fromFile(embedFile) - val embedFields: FieldMap = hashMapOf( - "uri" to embedUri.toString(), - "mimeType" to embedMimeType, - ) - if (isImage(embedMimeType) || isVideo(embedMimeType)) { - GlobalScope.launch(Dispatchers.IO) { - FileImageProvider().fetchSingle(context, embedUri, embedMimeType, object : ImageProvider.ImageOpCallback { - override fun onSuccess(fields: FieldMap) { - embedFields.putAll(fields) - result.success(embedFields) - } - - override fun onFailure(throwable: Throwable) = result.error("copyEmbeddedBytes-failure", "failed to get entry for uri=$embedUri mime=$embedMimeType", throwable.message) - }) - } - } else { - result.success(embedFields) - } - } - - private fun isMultiPageTiff(uri: Uri) = getTiffPageInfo(uri, 0)?.outDirectoryCount ?: 1 > 1 - - private fun getTiffPageInfo(uri: Uri, page: Int): TiffBitmapFactory.Options? { - try { - val fd = context.contentResolver.openFileDescriptor(uri, "r")?.detachFd() - if (fd == null) { - Log.w(LOG_TAG, "failed to get file descriptor for uri=$uri") - return null - } - val options = TiffBitmapFactory.Options().apply { - inJustDecodeBounds = true - inDirectoryNumber = page - } - TiffBitmapFactory.decodeFileDescriptor(fd, options) - return options - } catch (e: Exception) { - Log.w(LOG_TAG, "failed to get TIFF page info for uri=$uri page=$page", e) - } - return null - } - companion object { - private val LOG_TAG = LogUtils.createTag(MetadataHandler::class.java) + private val LOG_TAG = LogUtils.createTag() const val CHANNEL = "deckers.thibault/aves/metadata" private val allMetadataRedundantDirNames = setOf( "MP4", + "MP4 Metadata", "MP4 Sound", "MP4 Video", "QuickTime", @@ -922,7 +708,7 @@ class MetadataHandler(private val context: Context) : MethodCallHandler { "QuickTime Video", ) - // catalog metadata & page info + // catalog metadata private const val KEY_MIME_TYPE = "mimeType" private const val KEY_DATE_MILLIS = "dateMillis" private const val KEY_FLAGS = "flags" @@ -931,11 +717,6 @@ class MetadataHandler(private val context: Context) : MethodCallHandler { private const val KEY_LONGITUDE = "longitude" private const val KEY_XMP_SUBJECTS = "xmpSubjects" private const val KEY_XMP_TITLE_DESCRIPTION = "xmpTitleDescription" - private const val KEY_HEIGHT = "height" - private const val KEY_WIDTH = "width" - private const val KEY_PAGE = "page" - private const val KEY_IS_DEFAULT = "isDefault" - private const val KEY_DURATION = "durationMillis" private const val MASK_IS_ANIMATED = 1 shl 0 private const val MASK_IS_FLIPPED = 1 shl 1 diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/RegionFetcher.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/RegionFetcher.kt index 47d7a60f7..01a4785b1 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/RegionFetcher.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/RegionFetcher.kt @@ -30,7 +30,7 @@ class RegionFetcher internal constructor( .diskCacheStrategy(DiskCacheStrategy.NONE) .skipMemoryCache(true) - fun fetch( + suspend fun fetch( uri: Uri, mimeType: String, pageId: Int?, @@ -114,8 +114,8 @@ class RegionFetcher internal constructor( val bitmap = target.get() val tempFile = File.createTempFile("aves", null, context.cacheDir).apply { deleteOnExit() - outputStream().use { outputStream -> - bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream) + outputStream().use { output -> + bitmap.compress(Bitmap.CompressFormat.JPEG, 100, output) } } return Uri.fromFile(tempFile) diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/ThumbnailFetcher.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/ThumbnailFetcher.kt index 5b0b1f8c7..2696f59b7 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/ThumbnailFetcher.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/ThumbnailFetcher.kt @@ -45,7 +45,7 @@ class ThumbnailFetcher internal constructor( private val multiTrackFetch = isHeic(mimeType) && pageId != null private val customFetch = tiffFetch || multiTrackFetch - fun fetch() { + suspend fun fetch() { var bitmap: Bitmap? = null var exception: Exception? = null diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/TiffRegionFetcher.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/TiffRegionFetcher.kt index 502553422..12666d371 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/TiffRegionFetcher.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/TiffRegionFetcher.kt @@ -11,7 +11,7 @@ import org.beyka.tiffbitmapfactory.TiffBitmapFactory class TiffRegionFetcher internal constructor( private val context: Context, ) { - fun fetch( + suspend fun fetch( uri: Uri, page: Int, sampleSize: Int, diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/ContentChangeStreamHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/ContentChangeStreamHandler.kt index fc9a0114a..582abbe49 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/ContentChangeStreamHandler.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/ContentChangeStreamHandler.kt @@ -58,7 +58,7 @@ class ContentChangeStreamHandler(private val context: Context) : EventChannel.St } companion object { - private val LOG_TAG = LogUtils.createTag(ContentChangeStreamHandler::class.java) + private val LOG_TAG = LogUtils.createTag() const val CHANNEL = "deckers.thibault/aves/contentchange" } } \ No newline at end of file diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/ImageByteStreamHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/ImageByteStreamHandler.kt index 58746c997..9d4e73edc 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/ImageByteStreamHandler.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/ImageByteStreamHandler.kt @@ -76,7 +76,7 @@ class ImageByteStreamHandler(private val activity: Activity, private val argumen // - Flutter (as of v1.20): JPEG, PNG, GIF, Animated GIF, WebP, Animated WebP, BMP, and WBMP // - Android: https://developer.android.com/guide/topics/media/media-formats#image-formats // - Glide: https://github.com/bumptech/glide/blob/master/library/src/main/java/com/bumptech/glide/load/ImageHeaderParser.java - private fun streamImage() { + private suspend fun streamImage() { if (arguments !is Map<*, *>) { endOfStream() return @@ -114,7 +114,7 @@ class ImageByteStreamHandler(private val activity: Activity, private val argumen } } - private fun streamImageByGlide(uri: Uri, pageId: Int?, mimeType: String, rotationDegrees: Int, isFlipped: Boolean) { + private suspend fun streamImageByGlide(uri: Uri, pageId: Int?, mimeType: String, rotationDegrees: Int, isFlipped: Boolean) { val model: Any = if (isHeic(mimeType) && pageId != null) { MultiTrackImage(activity, uri, pageId) } else if (mimeType == MimeTypes.TIFF) { @@ -145,7 +145,7 @@ class ImageByteStreamHandler(private val activity: Activity, private val argumen } } - private fun streamVideoByGlide(uri: Uri) { + private suspend fun streamVideoByGlide(uri: Uri) { val target = Glide.with(activity) .asBitmap() .apply(glideOptions) @@ -175,7 +175,7 @@ class ImageByteStreamHandler(private val activity: Activity, private val argumen } private fun streamBytes(inputStream: InputStream) { - val buffer = ByteArray(bufferSize) + val buffer = ByteArray(BUFFER_SIZE) var len: Int while (inputStream.read(buffer).also { len = it } != -1) { // cannot decode image on Flutter side when using `buffer` directly @@ -184,10 +184,10 @@ class ImageByteStreamHandler(private val activity: Activity, private val argumen } companion object { - private val LOG_TAG = LogUtils.createTag(ImageByteStreamHandler::class.java) + private val LOG_TAG = LogUtils.createTag() const val CHANNEL = "deckers.thibault/aves/imagebytestream" - const val bufferSize = 2 shl 17 // 256kB + const val BUFFER_SIZE = 2 shl 17 // 256kB // request a fresh image with the highest quality format val glideOptions = RequestOptions() diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/ImageOpStreamHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/ImageOpStreamHandler.kt index 86e8d0650..25f872da4 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/ImageOpStreamHandler.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/ImageOpStreamHandler.kt @@ -1,6 +1,6 @@ package deckers.thibault.aves.channel.streams -import android.content.Context +import android.app.Activity import android.net.Uri import android.os.Handler import android.os.Looper @@ -18,7 +18,7 @@ import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import java.util.* -class ImageOpStreamHandler(private val context: Context, private val arguments: Any?) : EventChannel.StreamHandler { +class ImageOpStreamHandler(private val activity: Activity, private val arguments: Any?) : EventChannel.StreamHandler { private lateinit var eventSink: EventSink private lateinit var handler: Handler @@ -103,7 +103,7 @@ class ImageOpStreamHandler(private val context: Context, private val arguments: "uri" to uri.toString(), ) try { - provider.delete(context, uri, path) + provider.delete(activity, uri, path) result["success"] = true } catch (e: Exception) { Log.w(LOG_TAG, "failed to delete entry with path=$path", e) @@ -138,7 +138,7 @@ class ImageOpStreamHandler(private val context: Context, private val arguments: destinationDir = StorageUtils.ensureTrailingSeparator(destinationDir) val entries = entryMapList.map(::AvesEntry) - provider.exportMultiple(context, mimeType, destinationDir, entries, object : ImageOpCallback { + provider.exportMultiple(activity, mimeType, destinationDir, entries, object : ImageOpCallback { override fun onSuccess(fields: FieldMap) = success(fields) override fun onFailure(throwable: Throwable) = error("export-failure", "failed to export entries", throwable) }) @@ -168,7 +168,7 @@ class ImageOpStreamHandler(private val context: Context, private val arguments: destinationDir = StorageUtils.ensureTrailingSeparator(destinationDir) val entries = entryMapList.map(::AvesEntry) - provider.moveMultiple(context, copy, destinationDir, entries, object : ImageOpCallback { + provider.moveMultiple(activity, copy, destinationDir, entries, object : ImageOpCallback { override fun onSuccess(fields: FieldMap) = success(fields) override fun onFailure(throwable: Throwable) = error("move-failure", "failed to move entries", throwable) }) @@ -176,7 +176,7 @@ class ImageOpStreamHandler(private val context: Context, private val arguments: } companion object { - private val LOG_TAG = LogUtils.createTag(ImageOpStreamHandler::class.java) + private val LOG_TAG = LogUtils.createTag() const val CHANNEL = "deckers.thibault/aves/imageopstream" } } \ No newline at end of file diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/MediaStoreStreamHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/MediaStoreStreamHandler.kt index 69e130e33..dc5a0caa8 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/MediaStoreStreamHandler.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/MediaStoreStreamHandler.kt @@ -61,7 +61,7 @@ class MediaStoreStreamHandler(private val context: Context, arguments: Any?) : E } companion object { - private val LOG_TAG = LogUtils.createTag(MediaStoreStreamHandler::class.java) + private val LOG_TAG = LogUtils.createTag() const val CHANNEL = "deckers.thibault/aves/mediastorestream" } } \ No newline at end of file diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/StorageAccessStreamHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/StorageAccessStreamHandler.kt index f22e7b684..649425255 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/StorageAccessStreamHandler.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/StorageAccessStreamHandler.kt @@ -75,7 +75,7 @@ class StorageAccessStreamHandler(private val activity: Activity, arguments: Any? } companion object { - private val LOG_TAG = LogUtils.createTag(StorageAccessStreamHandler::class.java) + private val LOG_TAG = LogUtils.createTag() const val CHANNEL = "deckers.thibault/aves/storageaccessstream" } } \ No newline at end of file diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/decoder/VideoThumbnailGlideModule.kt b/android/app/src/main/kotlin/deckers/thibault/aves/decoder/VideoThumbnailGlideModule.kt index 278e55cc6..37bc13071 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/decoder/VideoThumbnailGlideModule.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/decoder/VideoThumbnailGlideModule.kt @@ -19,6 +19,9 @@ import com.bumptech.glide.module.LibraryGlideModule import com.bumptech.glide.signature.ObjectKey import deckers.thibault.aves.utils.BitmapUtils.getBytes import deckers.thibault.aves.utils.StorageUtils.openMetadataRetriever +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch import java.io.ByteArrayInputStream import java.io.InputStream @@ -47,40 +50,42 @@ internal class VideoThumbnailLoader : ModelLoader { internal class VideoThumbnailFetcher(private val model: VideoThumbnail) : DataFetcher { override fun loadData(priority: Priority, callback: DataCallback) { - val retriever = openMetadataRetriever(model.context, model.uri) - if (retriever != null) { - try { - var bytes = retriever.embeddedPicture - if (bytes == null) { - // try to match the thumbnails returned by the content resolver / Media Store - // the following strategies are from empirical evidence from a few test devices: - // - API 29: sync frame closest to the middle - // - API 26/27: default representative frame at any time position - var timeMillis: Long? = null - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - val durationMillis = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)?.toLongOrNull() - if (durationMillis != null) { - timeMillis = durationMillis / 2 + GlobalScope.launch(Dispatchers.IO) { + val retriever = openMetadataRetriever(model.context, model.uri) + if (retriever != null) { + try { + var bytes = retriever.embeddedPicture + if (bytes == null) { + // try to match the thumbnails returned by the content resolver / Media Store + // the following strategies are from empirical evidence from a few test devices: + // - API 29: sync frame closest to the middle + // - API 26/27: default representative frame at any time position + var timeMillis: Long? = null + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + val durationMillis = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)?.toLongOrNull() + if (durationMillis != null) { + timeMillis = durationMillis / 2 + } } + val frame = if (timeMillis != null) { + retriever.getFrameAtTime(timeMillis * 1000) + } else { + retriever.frameAtTime + } + bytes = frame?.getBytes(canHaveAlpha = false, recycle = false) } - val frame = if (timeMillis != null) { - retriever.getFrameAtTime(timeMillis * 1000) - } else { - retriever.frameAtTime - } - bytes = frame?.getBytes(canHaveAlpha = false, recycle = false) - } - if (bytes != null) { - callback.onDataReady(ByteArrayInputStream(bytes)) - } else { - callback.onLoadFailed(Exception("failed to get embedded picture or any frame")) + if (bytes != null) { + callback.onDataReady(ByteArrayInputStream(bytes)) + } else { + callback.onLoadFailed(Exception("failed to get embedded picture or any frame")) + } + } catch (e: Exception) { + callback.onLoadFailed(e) + } finally { + // cannot rely on `MediaMetadataRetriever` being `AutoCloseable` on older APIs + retriever.release() } - } catch (e: Exception) { - callback.onLoadFailed(e) - } finally { - // cannot rely on `MediaMetadataRetriever` being `AutoCloseable` on older APIs - retriever.release() } } } diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/ExifInterfaceHelper.kt b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/ExifInterfaceHelper.kt index f43001cda..dd449e012 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/ExifInterfaceHelper.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/ExifInterfaceHelper.kt @@ -17,7 +17,7 @@ import kotlin.math.floor import kotlin.math.roundToLong object ExifInterfaceHelper { - private val LOG_TAG = LogUtils.createTag(ExifInterfaceHelper::class.java) + private val LOG_TAG = LogUtils.createTag() private val DATETIME_FORMAT = SimpleDateFormat("yyyy:MM:dd hh:mm:ss", Locale.ROOT) private const val precisionErrorTolerance = 1e-10 diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/Metadata.kt b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/Metadata.kt index cba217c0e..98f80393f 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/Metadata.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/Metadata.kt @@ -2,7 +2,9 @@ package deckers.thibault.aves.metadata import android.content.Context import android.net.Uri +import android.util.Log import androidx.exifinterface.media.ExifInterface +import deckers.thibault.aves.utils.LogUtils import deckers.thibault.aves.utils.MimeTypes import deckers.thibault.aves.utils.StorageUtils import java.io.File @@ -13,6 +15,8 @@ import java.util.* import java.util.regex.Pattern object Metadata { + private val LOG_TAG = LogUtils.createTag() + // Pattern to extract latitude & longitude from a video location tag (cf ISO 6709) // Examples: // "+37.5090+127.0243/" (Samsung) @@ -96,10 +100,10 @@ object Metadata { return dateMillis } - // opening large TIFF files yields an OOM (both with `metadata-extractor` v2.15.0 and `ExifInterface` v1.3.1), + // opening large PSD/TIFF files yields an OOM (both with `metadata-extractor` v2.15.0 and `ExifInterface` v1.3.1), // so we define an arbitrary threshold to avoid a crash on launch. // It is not clear whether it is because of the file itself or its metadata. - private const val tiffSizeBytesMax = 100 * (1 shl 20) // MB + private const val fileSizeBytesMax = 100 * (1 shl 20) // MB // we try and read metadata from large files by copying an arbitrary amount from its beginning // to a temporary file, and reusing that preview file for all metadata reading purposes @@ -108,25 +112,39 @@ object Metadata { private val previewFiles = HashMap() private fun getSafeUri(context: Context, uri: Uri, mimeType: String, sizeBytes: Long?): Uri { - if (mimeType != MimeTypes.TIFF) return uri - - if (sizeBytes != null && sizeBytes < tiffSizeBytesMax) return uri - - var previewFile = previewFiles[uri] - if (previewFile == null) { - previewFile = File.createTempFile("aves", null, context.cacheDir).apply { - deleteOnExit() - outputStream().use { outputStream -> - StorageUtils.openInputStream(context, uri)?.use { inputStream -> - val b = ByteArray(previewSize) - inputStream.read(b, 0, previewSize) - outputStream.write(b) + return when (mimeType) { + // formats known to yield OOM for large files + MimeTypes.MP4, + MimeTypes.PSD_VND, + MimeTypes.PSD_X, + MimeTypes.TIFF -> { + if (sizeBytes != null && sizeBytes < fileSizeBytesMax) { + // small enough to be safe as it is + uri + } else { + // make a preview from the beginning of the file, + // hoping the metadata is accessible in the copied chunk + Log.d(LOG_TAG, "use a preview for uri=$uri mimeType=$mimeType size=$sizeBytes") + var previewFile = previewFiles[uri] + if (previewFile == null) { + previewFile = File.createTempFile("aves", null, context.cacheDir).apply { + deleteOnExit() + outputStream().use { output -> + StorageUtils.openInputStream(context, uri)?.use { input -> + val b = ByteArray(previewSize) + input.read(b, 0, previewSize) + output.write(b) + } + } + } + previewFiles[uri] = previewFile } + Uri.fromFile(previewFile) } } - previewFiles[uri] = previewFile + // *probably* safe + else -> uri } - return Uri.fromFile(previewFile) } fun openSafeInputStream(context: Context, uri: Uri, mimeType: String, sizeBytes: Long?): InputStream? { diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/MultiPage.kt b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/MultiPage.kt new file mode 100644 index 000000000..eb9597b7d --- /dev/null +++ b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/MultiPage.kt @@ -0,0 +1,196 @@ +package deckers.thibault.aves.metadata + +import android.content.Context +import android.media.MediaExtractor +import android.media.MediaFormat +import android.net.Uri +import android.os.Build +import android.os.ParcelFileDescriptor +import android.util.Log +import com.drew.imaging.ImageMetadataReader +import com.drew.metadata.xmp.XmpDirectory +import deckers.thibault.aves.metadata.XMP.getSafeLong +import deckers.thibault.aves.model.FieldMap +import deckers.thibault.aves.utils.LogUtils +import deckers.thibault.aves.utils.MimeTypes +import org.beyka.tiffbitmapfactory.TiffBitmapFactory +import java.util.* + +object MultiPage { + private val LOG_TAG = LogUtils.createTag() + + // page info + private const val KEY_MIME_TYPE = "mimeType" + private const val KEY_HEIGHT = "height" + private const val KEY_WIDTH = "width" + private const val KEY_PAGE = "page" + private const val KEY_IS_DEFAULT = "isDefault" + private const val KEY_DURATION = "durationMillis" + private const val KEY_ROTATION_DEGREES = "rotationDegrees" + + fun getHeicTracks(context: Context, uri: Uri): ArrayList { + fun MediaFormat.getSafeInt(key: String, save: (value: Int) -> Unit) { + if (this.containsKey(key)) save(this.getInteger(key)) + } + + fun MediaFormat.getSafeLong(key: String, save: (value: Long) -> Unit) { + if (this.containsKey(key)) save(this.getLong(key)) + } + + val tracks = ArrayList() + val extractor = MediaExtractor() + extractor.setDataSource(context, uri, null) + for (i in 0 until extractor.trackCount) { + try { + val format = extractor.getTrackFormat(i) + format.getString(MediaFormat.KEY_MIME)?.let { mime -> + val trackMime = if (mime == MediaFormat.MIMETYPE_IMAGE_ANDROID_HEIC) MimeTypes.HEIC else mime + val track = hashMapOf( + KEY_PAGE to i, + KEY_MIME_TYPE to trackMime, + ) + + // do not use `MediaFormat.KEY_TRACK_ID` as it is actually not unique between tracks + // e.g. there could be both a video track and an image track with KEY_TRACK_ID == 1 + + format.getSafeInt(MediaFormat.KEY_IS_DEFAULT) { track[KEY_IS_DEFAULT] = it != 0 } + format.getSafeInt(MediaFormat.KEY_WIDTH) { track[KEY_WIDTH] = it } + format.getSafeInt(MediaFormat.KEY_HEIGHT) { track[KEY_HEIGHT] = it } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + format.getSafeInt(MediaFormat.KEY_ROTATION) { track[KEY_ROTATION_DEGREES] = it } + } + if (MimeTypes.isVideo(trackMime)) { + format.getSafeLong(MediaFormat.KEY_DURATION) { track[KEY_DURATION] = it / 1000 } + } + tracks.add(track) + } + } catch (e: Exception) { + Log.w(LOG_TAG, "failed to get HEIC track information for uri=$uri, track num=$i", e) + } + } + extractor.release() + return tracks + } + + fun getMotionPhotoPages(context: Context, uri: Uri, mimeType: String, sizeBytes: Long): ArrayList { + fun MediaFormat.getSafeInt(key: String, save: (value: Int) -> Unit) { + if (this.containsKey(key)) save(this.getInteger(key)) + } + + fun MediaFormat.getSafeLong(key: String, save: (value: Long) -> Unit) { + if (this.containsKey(key)) save(this.getLong(key)) + } + + val tracks = ArrayList() + val extractor = MediaExtractor() + var pfd: ParcelFileDescriptor? = null + try { + getMotionPhotoOffset(context, uri, mimeType, sizeBytes)?.let { videoSizeBytes -> + val videoStartOffset = sizeBytes - videoSizeBytes + pfd = context.contentResolver.openFileDescriptor(uri, "r") + pfd?.fileDescriptor?.let { fd -> + extractor.setDataSource(fd, videoStartOffset, videoSizeBytes) + // set the original image as the first and default track + var trackCount = 0 + tracks.add( + hashMapOf( + KEY_PAGE to trackCount++, + KEY_MIME_TYPE to mimeType, + KEY_IS_DEFAULT to true, + ) + ) + // add video tracks from the appended video + for (i in 0 until extractor.trackCount) { + try { + val format = extractor.getTrackFormat(i) + format.getString(MediaFormat.KEY_MIME)?.let { mime -> + if (MimeTypes.isVideo(mime)) { + val track = hashMapOf( + KEY_PAGE to trackCount++, + KEY_MIME_TYPE to MimeTypes.MP4, + KEY_IS_DEFAULT to false, + ) + format.getSafeInt(MediaFormat.KEY_WIDTH) { track[KEY_WIDTH] = it } + format.getSafeInt(MediaFormat.KEY_HEIGHT) { track[KEY_HEIGHT] = it } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + format.getSafeInt(MediaFormat.KEY_ROTATION) { track[KEY_ROTATION_DEGREES] = it } + } + format.getSafeLong(MediaFormat.KEY_DURATION) { track[KEY_DURATION] = it / 1000 } + tracks.add(track) + } + } + } catch (e: Exception) { + Log.w(LOG_TAG, "failed to get motion photo track information for uri=$uri, track num=$i", e) + } + } + } + } + } catch (e: Exception) { + Log.w(LOG_TAG, "failed to open motion photo for uri=$uri", e) + } finally { + extractor.release() + pfd?.close() + } + return tracks + } + + fun getMotionPhotoOffset(context: Context, uri: Uri, mimeType: String, sizeBytes: Long): Long? { + try { + Metadata.openSafeInputStream(context, uri, mimeType, sizeBytes)?.use { input -> + val metadata = ImageMetadataReader.readMetadata(input) + for (dir in metadata.getDirectoriesOfType(XmpDirectory::class.java)) { + var offsetFromEnd: Long? = null + dir.xmpMeta.getSafeLong(XMP.GCAMERA_SCHEMA_NS, XMP.GCAMERA_VIDEO_OFFSET_PROP_NAME) { offsetFromEnd = it } + return offsetFromEnd + } + } + } catch (e: Exception) { + Log.w(LOG_TAG, "failed to get motion photo offset from uri=$uri", e) + } catch (e: NoClassDefFoundError) { + Log.w(LOG_TAG, "failed to get motion photo offset from uri=$uri", e) + } + return null + } + + fun getTiffPages(context: Context, uri: Uri): ArrayList { + fun toMap(page: Int, options: TiffBitmapFactory.Options): FieldMap { + return hashMapOf( + KEY_PAGE to page, + KEY_MIME_TYPE to MimeTypes.TIFF, + KEY_WIDTH to options.outWidth, + KEY_HEIGHT to options.outHeight, + ) + } + + val pages = ArrayList() + getTiffPageInfo(context, uri, 0)?.let { first -> + pages.add(toMap(0, first)) + val pageCount = first.outDirectoryCount + for (i in 1 until pageCount) { + getTiffPageInfo(context, uri, i)?.let { pages.add(toMap(i, it)) } + } + } + return pages + } + + fun isMultiPageTiff(context: Context, uri: Uri) = getTiffPageInfo(context, uri, 0)?.outDirectoryCount ?: 1 > 1 + + private fun getTiffPageInfo(context: Context, uri: Uri, page: Int): TiffBitmapFactory.Options? { + try { + val fd = context.contentResolver.openFileDescriptor(uri, "r")?.detachFd() + if (fd == null) { + Log.w(LOG_TAG, "failed to get TIFF file descriptor for uri=$uri") + return null + } + val options = TiffBitmapFactory.Options().apply { + inJustDecodeBounds = true + inDirectoryNumber = page + } + TiffBitmapFactory.decodeFileDescriptor(fd, options) + return options + } catch (e: Exception) { + Log.w(LOG_TAG, "failed to get TIFF page info for uri=$uri page=$page", e) + } + return null + } +} \ No newline at end of file diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/MultiTrackMedia.kt b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/MultiTrackMedia.kt index acd72a5d5..d73bbe8fe 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/MultiTrackMedia.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/MultiTrackMedia.kt @@ -13,7 +13,7 @@ import deckers.thibault.aves.utils.MimeTypes import deckers.thibault.aves.utils.StorageUtils object MultiTrackMedia { - private val LOG_TAG = LogUtils.createTag(MultiTrackMedia::class.java) + private val LOG_TAG = LogUtils.createTag() @RequiresApi(Build.VERSION_CODES.P) fun getImage(context: Context, uri: Uri, trackIndex: Int?): Bitmap? { diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/SphericalVideo.kt b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/SphericalVideo.kt index 5aec0dba7..23f5e9020 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/SphericalVideo.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/SphericalVideo.kt @@ -82,7 +82,7 @@ class GSpherical(xmlBytes: ByteArray) { ).filterValues { it != null } companion object SphericalVideo { - private val LOG_TAG = LogUtils.createTag(SphericalVideo::class.java) + private val LOG_TAG = LogUtils.createTag() // cf https://github.com/google/spatial-media const val SPHERICAL_VIDEO_V1_UUID = "ffcc8263-f855-4a93-8814-587a02521fdd" diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/XMP.kt b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/XMP.kt index 6d411f1e5..d1d24bd87 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/XMP.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/XMP.kt @@ -8,7 +8,7 @@ import deckers.thibault.aves.utils.LogUtils import java.util.* object XMP { - private val LOG_TAG = LogUtils.createTag(XMP::class.java) + private val LOG_TAG = LogUtils.createTag() // standard namespaces // cf com.adobe.internal.xmp.XMPConst @@ -42,6 +42,12 @@ object XMP { fun isDataPath(path: String) = knownDataPaths.contains(path) + // motion photo + + const val GCAMERA_SCHEMA_NS = "http://ns.google.com/photos/1.0/camera/" + + const val GCAMERA_VIDEO_OFFSET_PROP_NAME = "GCamera:MicroVideoOffset" + // panorama // cf https://developers.google.com/streetview/spherical-metadata @@ -71,6 +77,19 @@ object XMP { // extensions + fun XMPMeta.isMotionPhoto(): Boolean { + try { + return doesPropertyExist(GCAMERA_SCHEMA_NS, GCAMERA_VIDEO_OFFSET_PROP_NAME) + } catch (e: XMPException) { + if (e.errorCode != XMPError.BADSCHEMA) { + // `BADSCHEMA` code is reported when we check a property + // from a non standard namespace, and that namespace is not declared in the XMP + Log.w(LOG_TAG, "failed to check Google motion photo props from XMP", e) + } + } + return false + } + fun XMPMeta.isPanorama(): Boolean { // Google try { @@ -111,6 +130,20 @@ object XMP { } } + fun XMPMeta.getSafeLong(schema: String, propName: String, save: (value: Long) -> Unit) { + try { + if (doesPropertyExist(schema, propName)) { + val item = getPropertyLong(schema, propName) + // double check retrieved items as the property sometimes is reported to exist but it is actually null + if (item != null) { + save(item) + } + } + } catch (e: XMPException) { + Log.w(LOG_TAG, "failed to get long for XMP schema=$schema, propName=$propName", e) + } + } + fun XMPMeta.getSafeString(schema: String, propName: String, save: (value: String) -> Unit) { try { if (doesPropertyExist(schema, propName)) { diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ContentImageProvider.kt b/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ContentImageProvider.kt index 9fc5a7f66..0f85cc768 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ContentImageProvider.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ContentImageProvider.kt @@ -3,6 +3,7 @@ package deckers.thibault.aves.model.provider import android.content.Context import android.net.Uri import android.provider.MediaStore +import android.provider.OpenableColumns import deckers.thibault.aves.model.SourceEntry internal class ContentImageProvider : ImageProvider() { @@ -19,8 +20,9 @@ internal class ContentImageProvider : ImageProvider() { try { val cursor = context.contentResolver.query(uri, projection, null, null, null) if (cursor != null && cursor.moveToFirst()) { - cursor.getColumnIndex(MediaStore.MediaColumns.SIZE).let { if (it != -1) map["sizeBytes"] = cursor.getLong(it) } - cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME).let { if (it != -1) map["title"] = cursor.getString(it) } + cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME).let { if (it != -1) map["title"] = cursor.getString(it) } + cursor.getColumnIndex(OpenableColumns.SIZE).let { if (it != -1) map["sizeBytes"] = cursor.getLong(it) } + cursor.getColumnIndex(PATH).let { if (it != -1) map["path"] = cursor.getString(it) } cursor.close() } } catch (e: Exception) { @@ -37,9 +39,15 @@ internal class ContentImageProvider : ImageProvider() { } companion object { + @Suppress("DEPRECATION") + const val PATH = MediaStore.MediaColumns.DATA + private val projection = arrayOf( - MediaStore.MediaColumns.SIZE, - MediaStore.MediaColumns.DISPLAY_NAME + // standard columns for openable URI + OpenableColumns.DISPLAY_NAME, + OpenableColumns.SIZE, + // optional path underlying media content + PATH, ) } } \ No newline at end of file diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ImageProvider.kt b/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ImageProvider.kt index fcde6e512..f5337cf97 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ImageProvider.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ImageProvider.kt @@ -1,5 +1,6 @@ package deckers.thibault.aves.model.provider +import android.app.Activity import android.content.ContentUris import android.content.Context import android.graphics.Bitmap @@ -16,16 +17,18 @@ import com.bumptech.glide.request.RequestOptions import com.commonsware.cwac.document.DocumentFileCompat import deckers.thibault.aves.decoder.MultiTrackImage import deckers.thibault.aves.decoder.TiffImage +import deckers.thibault.aves.metadata.MultiPage import deckers.thibault.aves.model.AvesEntry import deckers.thibault.aves.model.ExifOrientationOp import deckers.thibault.aves.model.FieldMap -import deckers.thibault.aves.utils.BitmapUtils -import deckers.thibault.aves.utils.LogUtils -import deckers.thibault.aves.utils.MimeTypes -import deckers.thibault.aves.utils.StorageUtils.copyFileToTemp +import deckers.thibault.aves.utils.* +import deckers.thibault.aves.utils.MimeTypes.extensionFor +import deckers.thibault.aves.utils.MimeTypes.isImage +import deckers.thibault.aves.utils.MimeTypes.isVideo import deckers.thibault.aves.utils.StorageUtils.createDirectoryIfAbsent import deckers.thibault.aves.utils.StorageUtils.getDocumentFile import deckers.thibault.aves.utils.UriUtils.tryParseId +import java.io.ByteArrayInputStream import java.io.File import java.io.FileNotFoundException import java.io.IOException @@ -39,21 +42,25 @@ abstract class ImageProvider { callback.onFailure(UnsupportedOperationException()) } - open suspend fun delete(context: Context, uri: Uri, path: String?) { + open suspend fun delete(activity: Activity, uri: Uri, path: String?) { throw UnsupportedOperationException() } - open suspend fun moveMultiple(context: Context, copy: Boolean, destinationDir: String, entries: List<AvesEntry>, callback: ImageOpCallback) { + open suspend fun moveMultiple(activity: Activity, copy: Boolean, destinationDir: String, entries: List<AvesEntry>, callback: ImageOpCallback) { callback.onFailure(UnsupportedOperationException()) } suspend fun exportMultiple( context: Context, - mimeType: String, + imageExportMimeType: String, destinationDir: String, entries: List<AvesEntry>, callback: ImageOpCallback, ) { + if (!supportedExportMimeTypes.contains(imageExportMimeType)) { + throw Exception("unsupported export MIME type=$imageExportMimeType") + } + val destinationDirDocFile = createDirectoryIfAbsent(context, destinationDir) if (destinationDirDocFile == null) { callback.onFailure(Exception("failed to create directory at path=$destinationDir")) @@ -71,13 +78,15 @@ abstract class ImageProvider { "success" to false, ) + val sourceMimeType = entry.mimeType + val exportMimeType = if (isVideo(sourceMimeType)) sourceMimeType else imageExportMimeType try { val newFields = exportSingleByTreeDocAndScan( context = context, sourceEntry = entry, destinationDir = destinationDir, destinationDirDocFile = destinationDirDocFile, - exportMimeType = mimeType, + exportMimeType = exportMimeType, ) result["newFields"] = newFields result["success"] = true @@ -111,12 +120,7 @@ abstract class ImageProvider { val page = if (sourceMimeType == MimeTypes.TIFF) pageId + 1 else pageId desiredNameWithoutExtension += "_${page.toString().padStart(3, '0')}" } - val desiredFileName = desiredNameWithoutExtension + when (exportMimeType) { - MimeTypes.JPEG -> ".jpg" - MimeTypes.PNG -> ".png" - MimeTypes.WEBP -> ".webp" - else -> throw Exception("unsupported export MIME type=$exportMimeType") - } + val desiredFileName = desiredNameWithoutExtension + extensionFor(exportMimeType) if (File(destinationDir, desiredFileName).exists()) { throw Exception("file with name=$desiredFileName already exists in destination directory") @@ -130,56 +134,65 @@ abstract class ImageProvider { val destinationTreeFile = destinationDirDocFile.createFile(exportMimeType, desiredNameWithoutExtension) val destinationDocFile = DocumentFileCompat.fromSingleUri(context, destinationTreeFile.uri) - val model: Any = if (MimeTypes.isHeic(sourceMimeType) && pageId != null) { - MultiTrackImage(context, sourceUri, pageId) - } else if (sourceMimeType == MimeTypes.TIFF) { - TiffImage(context, sourceUri, pageId) + if (isVideo(sourceMimeType)) { + val sourceDocFile = DocumentFileCompat.fromSingleUri(context, sourceUri) + @Suppress("BlockingMethodInNonBlockingContext") + sourceDocFile.copyTo(destinationDocFile) } else { - sourceUri - } - - // request a fresh image with the highest quality format - val glideOptions = RequestOptions() - .format(DecodeFormat.PREFER_ARGB_8888) - .diskCacheStrategy(DiskCacheStrategy.NONE) - .skipMemoryCache(true) - - val target = Glide.with(context) - .asBitmap() - .apply(glideOptions) - .load(model) - .submit() - try { - @Suppress("BlockingMethodInNonBlockingContext") - var bitmap = target.get() - if (MimeTypes.needRotationAfterGlide(sourceMimeType)) { - bitmap = BitmapUtils.applyExifOrientation(context, bitmap, sourceEntry.rotationDegrees, sourceEntry.isFlipped) + val model: Any = if (MimeTypes.isHeic(sourceMimeType) && pageId != null) { + MultiTrackImage(context, sourceUri, pageId) + } else if (sourceMimeType == MimeTypes.TIFF) { + TiffImage(context, sourceUri, pageId) + } else { + sourceUri } - bitmap ?: throw Exception("failed to get image from uri=$sourceUri page=$pageId") - val quality = 100 - val format = when (exportMimeType) { - MimeTypes.JPEG -> Bitmap.CompressFormat.JPEG - MimeTypes.PNG -> Bitmap.CompressFormat.PNG - MimeTypes.WEBP -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - if (quality == 100) { - Bitmap.CompressFormat.WEBP_LOSSLESS - } else { - Bitmap.CompressFormat.WEBP_LOSSY - } - } else { - @Suppress("DEPRECATION") - Bitmap.CompressFormat.WEBP + // request a fresh image with the highest quality format + val glideOptions = RequestOptions() + .format(DecodeFormat.PREFER_ARGB_8888) + .diskCacheStrategy(DiskCacheStrategy.NONE) + .skipMemoryCache(true) + + val target = Glide.with(context) + .asBitmap() + .apply(glideOptions) + .load(model) + .submit() + try { + @Suppress("BlockingMethodInNonBlockingContext") + var bitmap = target.get() + if (MimeTypes.needRotationAfterGlide(sourceMimeType)) { + bitmap = BitmapUtils.applyExifOrientation(context, bitmap, sourceEntry.rotationDegrees, sourceEntry.isFlipped) } - else -> throw Exception("unsupported export MIME type=$exportMimeType") - } + bitmap ?: throw Exception("failed to get image from uri=$sourceUri page=$pageId") - @Suppress("BlockingMethodInNonBlockingContext") - destinationDocFile.openOutputStream().use { - bitmap.compress(format, quality, it) + @Suppress("BlockingMethodInNonBlockingContext") + destinationDocFile.openOutputStream().use { output -> + if (exportMimeType == MimeTypes.BMP) { + BmpWriter.writeRGB24(bitmap, output) + } else { + val quality = 100 + val format = when (exportMimeType) { + MimeTypes.JPEG -> Bitmap.CompressFormat.JPEG + MimeTypes.PNG -> Bitmap.CompressFormat.PNG + MimeTypes.WEBP -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + if (quality == 100) { + Bitmap.CompressFormat.WEBP_LOSSLESS + } else { + Bitmap.CompressFormat.WEBP_LOSSY + } + } else { + @Suppress("DEPRECATION") + Bitmap.CompressFormat.WEBP + } + else -> throw Exception("unsupported export MIME type=$exportMimeType") + } + bitmap.compress(format, quality, output) + } + } + } finally { + Glide.with(context).clear(target) } - } finally { - Glide.with(context).clear(target) } val fileName = destinationDocFile.name @@ -218,7 +231,7 @@ abstract class ImageProvider { } } - fun changeOrientation(context: Context, path: String, uri: Uri, mimeType: String, op: ExifOrientationOp, callback: ImageOpCallback) { + fun changeOrientation(context: Context, path: String, uri: Uri, mimeType: String, sizeBytes: Long, op: ExifOrientationOp, callback: ImageOpCallback) { if (!canEditExif(mimeType)) { callback.onFailure(UnsupportedOperationException("unsupported mimeType=$mimeType")) return @@ -230,16 +243,44 @@ abstract class ImageProvider { return } - // copy original file to a temporary file for editing - val editablePath = copyFileToTemp(originalDocumentFile, path) - if (editablePath == null) { - callback.onFailure(Exception("failed to create a temporary file for path=$path")) - return + val videoSizeBytes = MultiPage.getMotionPhotoOffset(context, uri, mimeType, sizeBytes)?.toInt() + var videoBytes: ByteArray? = null + val editableFile = File.createTempFile("aves", null).apply { + deleteOnExit() + try { + outputStream().use { output -> + if (videoSizeBytes != null) { + // handle motion photo and embedded video separately + val imageSizeBytes = (sizeBytes - videoSizeBytes).toInt() + videoBytes = ByteArray(videoSizeBytes) + + StorageUtils.openInputStream(context, uri)?.let { input -> + val imageBytes = ByteArray(imageSizeBytes) + input.read(imageBytes, 0, imageSizeBytes) + input.read(videoBytes, 0, videoSizeBytes) + + // copy only the image to a temporary file for editing + // video will be appended after EXIF modification + ByteArrayInputStream(imageBytes).use { imageInput -> + imageInput.copyTo(output) + } + } + } else { + // copy original file to a temporary file for editing + originalDocumentFile.openInputStream().use { imageInput -> + imageInput.copyTo(output) + } + } + } + } catch (e: Exception) { + callback.onFailure(e) + return + } } val newFields = HashMap<String, Any?>() try { - val exif = ExifInterface(editablePath) + val exif = ExifInterface(editableFile) // when the orientation is not defined, it returns `undefined (0)` instead of the orientation default value `normal (1)` // in that case we explicitly set it to `normal` first // because ExifInterface fails to rotate an image with undefined orientation @@ -255,8 +296,12 @@ abstract class ImageProvider { } exif.saveAttributes() + if (videoBytes != null) { + // append motion photo video, if any + editableFile.appendBytes(videoBytes!!) + } // copy the edited temporary file back to the original - DocumentFileCompat.fromFile(File(editablePath)).copyTo(originalDocumentFile) + DocumentFileCompat.fromFile(editableFile).copyTo(originalDocumentFile) newFields["rotationDegrees"] = exif.rotationDegrees newFields["isFlipped"] = exif.isFlipped @@ -285,7 +330,7 @@ abstract class ImageProvider { // as of androidx.exifinterface:exifinterface:1.3.0 private fun canEditExif(mimeType: String): Boolean { return when (mimeType) { - "image/jpeg", "image/png", "image/webp" -> true + MimeTypes.JPEG, MimeTypes.PNG, MimeTypes.WEBP -> true else -> false } } @@ -300,9 +345,9 @@ abstract class ImageProvider { // but we need an image/video media URI (e.g. "content://media/external/images/media/62872") contentId = newUri.tryParseId() if (contentId != null) { - if (MimeTypes.isImage(mimeType)) { + if (isImage(mimeType)) { contentUri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentId) - } else if (MimeTypes.isVideo(mimeType)) { + } else if (isVideo(mimeType)) { contentUri = ContentUris.withAppendedId(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, contentId) } } @@ -349,6 +394,8 @@ abstract class ImageProvider { } companion object { - private val LOG_TAG = LogUtils.createTag(ImageProvider::class.java) + private val LOG_TAG = LogUtils.createTag<ImageProvider>() + + val supportedExportMimeTypes = listOf(MimeTypes.BMP, MimeTypes.JPEG, MimeTypes.PNG, MimeTypes.WEBP) } } diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/MediaStoreImageProvider.kt b/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/MediaStoreImageProvider.kt index 183d16391..6f386f116 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/MediaStoreImageProvider.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/MediaStoreImageProvider.kt @@ -1,6 +1,8 @@ package deckers.thibault.aves.model.provider import android.annotation.SuppressLint +import android.app.Activity +import android.app.RecoverableSecurityException import android.content.ContentUris import android.content.Context import android.net.Uri @@ -8,6 +10,7 @@ import android.os.Build import android.provider.MediaStore import android.util.Log import com.commonsware.cwac.document.DocumentFileCompat +import deckers.thibault.aves.MainActivity.Companion.DELETE_PERMISSION_REQUEST import deckers.thibault.aves.model.AvesEntry import deckers.thibault.aves.model.FieldMap import deckers.thibault.aves.model.SourceEntry @@ -22,6 +25,7 @@ import deckers.thibault.aves.utils.StorageUtils.requireAccessPermission import deckers.thibault.aves.utils.UriUtils.tryParseId import java.io.File import java.util.* +import java.util.concurrent.CompletableFuture import kotlin.collections.ArrayList class MediaStoreImageProvider : ImageProvider() { @@ -205,31 +209,55 @@ class MediaStoreImageProvider : ImageProvider() { private fun needSize(mimeType: String) = MimeTypes.SVG != mimeType // `uri` is a media URI, not a document URI - override suspend fun delete(context: Context, uri: Uri, path: String?) { + override suspend fun delete(activity: Activity, uri: Uri, path: String?) { path ?: throw Exception("failed to delete file because path is null") - if (File(path).exists() && requireAccessPermission(context, path)) { + if (File(path).exists() && requireAccessPermission(activity, path)) { // if the file is on SD card, calling the content resolver `delete()` removes the entry from the Media Store // but it doesn't delete the file, even if the app has the permission - val df = getDocumentFile(context, path, uri) + val df = getDocumentFile(activity, path, uri) @Suppress("BlockingMethodInNonBlockingContext") if (df != null && df.delete()) return throw Exception("failed to delete file with df=$df") } - if (context.contentResolver.delete(uri, null, null) > 0) return + try { + if (activity.contentResolver.delete(uri, null, null) > 0) return + } catch (securityException: SecurityException) { + // even if the app has access permission granted on the containing directory, + // the delete request may yield a `RecoverableSecurityException` on Android 10+ + // when the underlying file no longer exists and this is an orphaned entry in the Media Store + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + val rse = securityException as? RecoverableSecurityException ?: throw securityException + val intentSender = rse.userAction.actionIntent.intentSender + + // request user permission for this item + pendingDeleteCompleter = CompletableFuture<Boolean>() + activity.startIntentSenderForResult(intentSender, DELETE_PERMISSION_REQUEST, null, 0, 0, 0, null) + val granted = pendingDeleteCompleter!!.join() + + pendingDeleteCompleter = null + if (granted) { + delete(activity, uri, path) + } else { + throw Exception("failed to get delete permission") + } + } else { + throw securityException + } + } throw Exception("failed to delete row from content provider") } override suspend fun moveMultiple( - context: Context, + activity: Activity, copy: Boolean, destinationDir: String, entries: List<AvesEntry>, callback: ImageOpCallback, ) { - val destinationDirDocFile = createDirectoryIfAbsent(context, destinationDir) + val destinationDirDocFile = createDirectoryIfAbsent(activity, destinationDir) if (destinationDirDocFile == null) { callback.onFailure(Exception("failed to create directory at path=$destinationDir")) return @@ -262,7 +290,7 @@ class MediaStoreImageProvider : ImageProvider() { // - the Media Store only allows inserting in specific primary directories ("DCIM", "Pictures") when using scoped storage try { val newFields = moveSingleByTreeDocAndScan( - context, sourcePath, sourceUri, destinationDir, destinationDirDocFile, mimeType, copy, + activity, sourcePath, sourceUri, destinationDir, destinationDirDocFile, mimeType, copy, ) result["newFields"] = newFields result["success"] = true @@ -275,7 +303,7 @@ class MediaStoreImageProvider : ImageProvider() { } private suspend fun moveSingleByTreeDocAndScan( - context: Context, + activity: Activity, sourcePath: String, sourceUri: Uri, destinationDir: String, @@ -303,12 +331,12 @@ class MediaStoreImageProvider : ImageProvider() { // note that `DocumentFile.getParentFile()` returns null if we did not pick a tree first @Suppress("BlockingMethodInNonBlockingContext") val destinationTreeFile = destinationDirDocFile.createFile(mimeType, desiredNameWithoutExtension) - val destinationDocFile = DocumentFileCompat.fromSingleUri(context, destinationTreeFile.uri) + val destinationDocFile = DocumentFileCompat.fromSingleUri(activity, destinationTreeFile.uri) // `DocumentsContract.moveDocument()` needs `sourceParentDocumentUri`, which could be different for each entry // `DocumentsContract.copyDocument()` yields "Unsupported call: android:copyDocument" // when used with entry URI as `sourceDocumentUri`, and destinationDirDocFile URI as `targetParentDocumentUri` - val source = DocumentFileCompat.fromSingleUri(context, sourceUri) + val source = DocumentFileCompat.fromSingleUri(activity, sourceUri) @Suppress("BlockingMethodInNonBlockingContext") source.copyTo(destinationDocFile) @@ -322,20 +350,20 @@ class MediaStoreImageProvider : ImageProvider() { if (!copy) { // delete original entry try { - delete(context, sourceUri, sourcePath) + delete(activity, sourceUri, sourcePath) deletedSource = true } catch (e: Exception) { Log.w(LOG_TAG, "failed to delete entry with path=$sourcePath", e) } } - return scanNewPath(context, destinationFullPath, mimeType).apply { + return scanNewPath(activity, destinationFullPath, mimeType).apply { put("deletedSource", deletedSource) } } companion object { - private val LOG_TAG = LogUtils.createTag(MediaStoreImageProvider::class.java) + private val LOG_TAG = LogUtils.createTag<MediaStoreImageProvider>() private val IMAGE_CONTENT_URI = MediaStore.Images.Media.EXTERNAL_CONTENT_URI private val VIDEO_CONTENT_URI = MediaStore.Video.Media.EXTERNAL_CONTENT_URI @@ -366,6 +394,8 @@ class MediaStoreImageProvider : ImageProvider() { MediaStore.MediaColumns.ORIENTATION, ) else emptyArray() ) + + var pendingDeleteCompleter: CompletableFuture<Boolean>? = null } } diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/utils/BitmapUtils.kt b/android/app/src/main/kotlin/deckers/thibault/aves/utils/BitmapUtils.kt index 2f71186ee..8a07860de 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/utils/BitmapUtils.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/utils/BitmapUtils.kt @@ -6,24 +6,46 @@ import android.util.Log import com.bumptech.glide.Glide import com.bumptech.glide.load.resource.bitmap.TransformationUtils import deckers.thibault.aves.metadata.Metadata.getExifCode +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import java.io.ByteArrayOutputStream object BitmapUtils { - private val LOG_TAG = LogUtils.createTag(BitmapUtils::class.java) + private val LOG_TAG = LogUtils.createTag<BitmapUtils>() + private const val INITIAL_BUFFER_SIZE = 2 shl 17 // 256kB - fun Bitmap.getBytes(canHaveAlpha: Boolean = false, quality: Int = 100, recycle: Boolean = true): ByteArray? { + private val freeBaos = ArrayList<ByteArrayOutputStream>() + private val mutex = Mutex() + + suspend fun Bitmap.getBytes(canHaveAlpha: Boolean = false, quality: Int = 100, recycle: Boolean): ByteArray? { + val stream: ByteArrayOutputStream + mutex.withLock { + // this method is called a lot, so we try and reuse output streams + // to reduce inner array allocations, and make the GC run less frequently + stream = if (freeBaos.isNotEmpty()) { + freeBaos.removeAt(0) + } else { + ByteArrayOutputStream(INITIAL_BUFFER_SIZE) + } + } try { - val stream = ByteArrayOutputStream() - // we compress the bitmap because Flutter cannot decode the raw bytes + // the Bitmap raw bytes are not decodable by Flutter + // we need to format them (compress, or add a BMP header) before sending them // `Bitmap.CompressFormat.PNG` is slower than `JPEG`, but it allows transparency - if (canHaveAlpha) { + // the BMP format allows an alpha channel, but Android decoding seems to ignore it + if (canHaveAlpha && hasAlpha()) { this.compress(Bitmap.CompressFormat.PNG, quality, stream) } else { this.compress(Bitmap.CompressFormat.JPEG, quality, stream) } if (recycle) this.recycle() - return stream.toByteArray() - } catch (e: IllegalStateException) { + val byteArray = stream.toByteArray() + stream.reset() + mutex.withLock { + freeBaos.add(stream) + } + return byteArray + } catch (e: Exception) { Log.e(LOG_TAG, "failed to get bytes from bitmap", e) } return null @@ -42,4 +64,4 @@ object BitmapUtils { } fun getBitmapPool(context: Context) = Glide.get(context).bitmapPool -} \ No newline at end of file +} diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/utils/BmpWriter.kt b/android/app/src/main/kotlin/deckers/thibault/aves/utils/BmpWriter.kt new file mode 100644 index 000000000..1409eecfd --- /dev/null +++ b/android/app/src/main/kotlin/deckers/thibault/aves/utils/BmpWriter.kt @@ -0,0 +1,111 @@ +package deckers.thibault.aves.utils + +import android.graphics.Bitmap +import java.io.OutputStream +import java.nio.ByteBuffer + +object BmpWriter { + private const val FILE_HEADER_SIZE = 14 + private const val INFO_HEADER_SIZE = 40 + private const val BYTE_PER_PIXEL = 3 + private val pad = ByteArray(3) + + // file header + private val bfType = byteArrayOf('B'.toByte(), 'M'.toByte()) + private val bfReserved1 = intToWord(0) + private val bfReserved2 = intToWord(0) + private val bfOffBits = intToDWord(FILE_HEADER_SIZE + INFO_HEADER_SIZE) + + // info header + private val biSize = intToDWord(INFO_HEADER_SIZE) + private val biPlanes = intToWord(1) + private val biBitCount = intToWord(BYTE_PER_PIXEL * 8) + private val biCompression = intToDWord(0) + private val biXPelsPerMeter = intToDWord(0) + private val biYPelsPerMeter = intToDWord(0) + private val biClrUsed = intToDWord(0) + private val biClrImportant = intToDWord(0) + + // converts an int to a word (2-byte array) + private fun intToWord(v: Int): ByteArray { + val retValue = ByteArray(2) + retValue[0] = (v and 0xFF).toByte() + retValue[1] = (v shr 8 and 0xFF).toByte() + return retValue + } + + // converts an int to a double word (4-byte array) + private fun intToDWord(v: Int): ByteArray { + val retValue = ByteArray(4) + retValue[0] = (v and 0xFF).toByte() + retValue[1] = (v shr 8 and 0xFF).toByte() + retValue[2] = (v shr 16 and 0xFF).toByte() + retValue[3] = (v shr 24 and 0xFF).toByte() + return retValue + } + + fun writeRGB24( + bitmap: Bitmap, + outputStream: OutputStream + ) { + // init + val biWidth = bitmap.width + val biHeight = bitmap.height + val padPerRow = (4 - (biWidth * BYTE_PER_PIXEL) % 4) % 4 + val biSizeImage = (biWidth * BYTE_PER_PIXEL + padPerRow) * biHeight + val bfSize = FILE_HEADER_SIZE + INFO_HEADER_SIZE + biSizeImage + val buffer = ByteBuffer.allocate(bfSize) + val pixels = IntArray(biWidth * biHeight) + bitmap.getPixels(pixels, 0, biWidth, 0, 0, biWidth, biHeight) + + // file header + buffer.put(bfType) + buffer.put(intToDWord(bfSize)) + buffer.put(bfReserved1) + buffer.put(bfReserved2) + buffer.put(bfOffBits) + + // info header + buffer.put(biSize) + buffer.put(intToDWord(biWidth)) + buffer.put(intToDWord(biHeight)) + buffer.put(biPlanes) + buffer.put(biBitCount) + buffer.put(biCompression) + buffer.put(intToDWord(biSizeImage)) + buffer.put(biXPelsPerMeter) + buffer.put(biYPelsPerMeter) + buffer.put(biClrUsed) + buffer.put(biClrImportant) + + // pixels + val rgb = ByteArray(BYTE_PER_PIXEL) + var value: Int + var row = biHeight - 1 + while (row >= 0) { + var column = 0 + while (column < biWidth) { + /* + alpha: (value shr 24 and 0xFF).toByte() + red: (value shr 16 and 0xFF).toByte() + green: (value shr 8 and 0xFF).toByte() + blue: (value and 0xFF).toByte() + */ + value = pixels[row * biWidth + column] + // blue: [0], green: [1], red: [2] + rgb[0] = (value and 0xFF).toByte() + rgb[1] = (value shr 8 and 0xFF).toByte() + rgb[2] = (value shr 16 and 0xFF).toByte() + buffer.put(rgb) + column++ + } + if (padPerRow > 0) { + buffer.put(pad, 0, padPerRow) + } + row-- + } + + // write to output stream + outputStream.write(buffer.array()) + } +} diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/utils/LogUtils.kt b/android/app/src/main/kotlin/deckers/thibault/aves/utils/LogUtils.kt index c2e60a019..eb4d5bd93 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/utils/LogUtils.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/utils/LogUtils.kt @@ -3,16 +3,17 @@ package deckers.thibault.aves.utils import java.util.regex.Pattern object LogUtils { - private const val LOG_TAG_MAX_LENGTH = 23 - private val LOG_TAG_PACKAGE_PATTERN = Pattern.compile("(\\w)(\\w*)\\.") + const val LOG_TAG_MAX_LENGTH = 23 + val LOG_TAG_PACKAGE_PATTERN: Pattern = Pattern.compile("(\\w)(\\w*)\\.") // create an Android logger friendly log tag for the specified class - fun createTag(clazz: Class<*>): String { + inline fun <reified T> createTag(): String { + val kClass = T::class // shorten class name to "a.b.CccDdd" - var logTag = LOG_TAG_PACKAGE_PATTERN.matcher(clazz.name).replaceAll("$1.") + var logTag = LOG_TAG_PACKAGE_PATTERN.matcher(kClass.qualifiedName!!).replaceAll("$1.") if (logTag.length > LOG_TAG_MAX_LENGTH) { // shorten class name to "a.b.CD" - val simpleName = clazz.simpleName + val simpleName = kClass.simpleName!! val shortSimpleName = simpleName.replace("[a-z]".toRegex(), "") logTag = logTag.replace(simpleName, shortSimpleName) if (logTag.length > LOG_TAG_MAX_LENGTH) { diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/utils/MimeTypes.kt b/android/app/src/main/kotlin/deckers/thibault/aves/utils/MimeTypes.kt index 59661cf8b..eb8a24780 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/utils/MimeTypes.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/utils/MimeTypes.kt @@ -6,14 +6,16 @@ object MimeTypes { private const val IMAGE = "image" // generic raster - private const val BMP = "image/bmp" + const val BMP = "image/bmp" private const val DJVU = "image/vnd.djvu" const val GIF = "image/gif" const val HEIC = "image/heic" - private const val HEIF = "image/heif" + const val HEIF = "image/heif" private const val ICO = "image/x-icon" const val JPEG = "image/jpeg" const val PNG = "image/png" + const val PSD_VND = "image/vnd.adobe.photoshop" + const val PSD_X = "image/x-photoshop" const val TIFF = "image/tiff" private const val WBMP = "image/vnd.wap.wbmp" const val WEBP = "image/webp" @@ -37,6 +39,7 @@ object MimeTypes { private const val MP2T = "video/mp2t" private const val MP2TS = "video/mp2ts" + const val MP4 = "video/mp4" private const val WEBM = "video/webm" fun isImage(mimeType: String?) = mimeType != null && mimeType.startsWith(IMAGE) @@ -95,5 +98,17 @@ object MimeTypes { // extensions + fun extensionFor(mimeType: String): String? = when (mimeType) { + BMP -> ".bmp" + GIF -> ".gif" + HEIC, HEIF -> ".heif" + JPEG -> ".jpg" + MP4 -> ".mp4" + PNG -> ".png" + TIFF -> ".tiff" + WEBP -> ".webp" + else -> null + } + val tiffExtensionPattern = Regex(".*\\.tiff?", RegexOption.IGNORE_CASE) } diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/utils/PermissionManager.kt b/android/app/src/main/kotlin/deckers/thibault/aves/utils/PermissionManager.kt index 13472aeae..83c9692ab 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/utils/PermissionManager.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/utils/PermissionManager.kt @@ -9,6 +9,7 @@ import android.os.Environment import android.os.storage.StorageManager import android.util.Log import androidx.annotation.RequiresApi +import deckers.thibault.aves.MainActivity.Companion.VOLUME_ACCESS_REQUEST import deckers.thibault.aves.utils.StorageUtils.PathSegments import java.io.File import java.util.* @@ -16,9 +17,7 @@ import java.util.concurrent.ConcurrentHashMap import kotlin.collections.ArrayList object PermissionManager { - private val LOG_TAG = LogUtils.createTag(PermissionManager::class.java) - - const val VOLUME_ACCESS_REQUEST_CODE = 1 + private val LOG_TAG = LogUtils.createTag<PermissionManager>() // permission request code to pending runnable private val pendingPermissionMap = ConcurrentHashMap<Int, PendingPermissionHandler>() @@ -39,8 +38,8 @@ object PermissionManager { } if (intent.resolveActivity(activity.packageManager) != null) { - pendingPermissionMap[VOLUME_ACCESS_REQUEST_CODE] = PendingPermissionHandler(path, onGranted, onDenied) - activity.startActivityForResult(intent, VOLUME_ACCESS_REQUEST_CODE, null) + pendingPermissionMap[VOLUME_ACCESS_REQUEST] = PendingPermissionHandler(path, onGranted, onDenied) + activity.startActivityForResult(intent, VOLUME_ACCESS_REQUEST, null) } else { Log.e(LOG_TAG, "failed to resolve activity for intent=$intent") onDenied() diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/utils/StorageUtils.kt b/android/app/src/main/kotlin/deckers/thibault/aves/utils/StorageUtils.kt index 6651f784f..dbcac15a7 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/utils/StorageUtils.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/utils/StorageUtils.kt @@ -11,19 +11,17 @@ import android.provider.DocumentsContract import android.provider.MediaStore import android.text.TextUtils import android.util.Log -import android.webkit.MimeTypeMap import androidx.annotation.RequiresApi import com.commonsware.cwac.document.DocumentFileCompat import deckers.thibault.aves.utils.PermissionManager.getGrantedDirForPath import java.io.File import java.io.FileNotFoundException -import java.io.IOException import java.io.InputStream import java.util.* import java.util.regex.Pattern object StorageUtils { - private val LOG_TAG = LogUtils.createTag(StorageUtils::class.java) + private val LOG_TAG = LogUtils.createTag<StorageUtils>() /** * Volume paths @@ -350,19 +348,6 @@ object StorageUtils { } } - fun copyFileToTemp(documentFile: DocumentFileCompat, path: String): String? { - val extension = MimeTypeMap.getFileExtensionFromUrl(Uri.fromFile(File(path)).toString()) - try { - val temp = File.createTempFile("aves", ".$extension") - documentFile.copyTo(DocumentFileCompat.fromFile(temp)) - temp.deleteOnExit() - return temp.path - } catch (e: IOException) { - Log.e(LOG_TAG, "failed to copy file from path=$path") - } - return null - } - private fun getDocumentFileFromVolumeTree(context: Context, rootTreeUri: Uri, anyPath: String): DocumentFileCompat? { var documentFile: DocumentFileCompat? = DocumentFileCompat.fromTreeUri(context, rootTreeUri) ?: return null diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/utils/UriUtils.kt b/android/app/src/main/kotlin/deckers/thibault/aves/utils/UriUtils.kt index 6ac38aeb6..9e109272d 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/utils/UriUtils.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/utils/UriUtils.kt @@ -5,7 +5,7 @@ import android.net.Uri import android.util.Log object UriUtils { - private val LOG_TAG = LogUtils.createTag(UriUtils::class.java) + private val LOG_TAG = LogUtils.createTag<UriUtils>() fun Uri.tryParseId(): Long? { try { diff --git a/android/app/src/main/res/xml/provider_paths.xml b/android/app/src/main/res/xml/provider_paths.xml index 2a7a21e70..bdf6728a3 100644 --- a/android/app/src/main/res/xml/provider_paths.xml +++ b/android/app/src/main/res/xml/provider_paths.xml @@ -4,9 +4,8 @@ name="external_files" path="." /> - <!-- for images & other media embedded in XMP - and exported for viewing and sharing --> + <!-- embedded images & other media that are exported for viewing and sharing --> <cache-path - name="xmp_props" + name="embedded" path="." /> </paths> \ No newline at end of file diff --git a/lib/geo/countries.dart b/lib/geo/countries.dart index 941f5204c..dfea76cb9 100644 --- a/lib/geo/countries.dart +++ b/lib/geo/countries.dart @@ -4,7 +4,7 @@ import 'package:aves/geo/topojson.dart'; import 'package:country_code/country_code.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; -import 'package:latlong/latlong.dart'; +import 'package:latlong2/latlong.dart'; final CountryTopology countryTopology = CountryTopology._private(); diff --git a/lib/geo/format.dart b/lib/geo/format.dart index eb9c601ac..517a74c2f 100644 --- a/lib/geo/format.dart +++ b/lib/geo/format.dart @@ -1,6 +1,6 @@ import 'package:aves/utils/math_utils.dart'; import 'package:intl/intl.dart'; -import 'package:latlong/latlong.dart'; +import 'package:latlong2/latlong.dart'; String _decimal2sexagesimal(final double degDecimal) { List<int> _split(final double value) { diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 2b2e4ded7..5cbeedb7d 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -99,6 +99,8 @@ "@filterTagEmptyLabel": {}, "filterTypeAnimatedLabel": "Animated", "@filterTypeAnimatedLabel": {}, + "filterTypeMotionPhotoLabel": "Motion Photo", + "@filterTypeMotionPhotoLabel": {}, "filterTypePanoramaLabel": "Panorama", "@filterTypePanoramaLabel": {}, "filterTypeSphericalVideoLabel": "360° Video", diff --git a/lib/l10n/app_ko.arb b/lib/l10n/app_ko.arb index cc48063c5..391f5e0bd 100644 --- a/lib/l10n/app_ko.arb +++ b/lib/l10n/app_ko.arb @@ -50,6 +50,7 @@ "filterLocationEmptyLabel": "장소 없음", "filterTagEmptyLabel": "태그 없음", "filterTypeAnimatedLabel": "애니메이션", + "filterTypeMotionPhotoLabel": "모션 포토", "filterTypePanoramaLabel": "파노라마", "filterTypeSphericalVideoLabel": "360° 동영상", "filterTypeGeotiffLabel": "GeoTIFF", diff --git a/lib/model/actions/entry_actions.dart b/lib/model/actions/entry_actions.dart index 2b92511dc..04e4865e1 100644 --- a/lib/model/actions/entry_actions.dart +++ b/lib/model/actions/entry_actions.dart @@ -44,6 +44,12 @@ class EntryActions { EntryAction.setAs, EntryAction.openMap, ]; + + static const pageActions = [ + EntryAction.rotateCCW, + EntryAction.rotateCW, + EntryAction.flip, + ]; } extension ExtraEntryAction on EntryAction { diff --git a/lib/model/entry.dart b/lib/model/entry.dart index 5b69c138c..e2aaccb58 100644 --- a/lib/model/entry.dart +++ b/lib/model/entry.dart @@ -4,7 +4,6 @@ import 'package:aves/geo/countries.dart'; import 'package:aves/model/entry_cache.dart'; import 'package:aves/model/favourites.dart'; import 'package:aves/model/metadata.dart'; -import 'package:aves/model/multipage.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/services/geocoding_service.dart'; import 'package:aves/services/service_policy.dart'; @@ -17,7 +16,7 @@ import 'package:collection/collection.dart'; import 'package:country_code/country_code.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:latlong/latlong.dart'; +import 'package:latlong2/latlong.dart'; import '../ref/mime_types.dart'; @@ -43,7 +42,13 @@ class AvesEntry { final AChangeNotifier imageChangeNotifier = AChangeNotifier(), metadataChangeNotifier = AChangeNotifier(), addressChangeNotifier = AChangeNotifier(); // TODO TLAD make it dynamic if it depends on OS/lib versions - static const List<String> undecodable = [MimeTypes.crw, MimeTypes.djvu, MimeTypes.psd]; + static const List<String> undecodable = [ + MimeTypes.art, + MimeTypes.crw, + MimeTypes.djvu, + MimeTypes.psdVnd, + MimeTypes.psdX, + ]; AvesEntry({ this.uri, @@ -97,36 +102,6 @@ class AvesEntry { return copied; } - AvesEntry getPageEntry(SinglePageInfo pageInfo, {bool eraseDefaultPageId = true}) { - if (pageInfo == null) return this; - - // do not provide the page ID for the default page, - // so that we can treat this page like the main entry - // and retrieve cached images for it - final pageId = eraseDefaultPageId && pageInfo.isDefault ? null : pageInfo.pageId; - - return AvesEntry( - uri: uri, - path: path, - contentId: contentId, - pageId: pageId, - sourceMimeType: pageInfo.mimeType ?? sourceMimeType, - width: pageInfo.width ?? width, - height: pageInfo.height ?? height, - sourceRotationDegrees: sourceRotationDegrees, - sizeBytes: sizeBytes, - sourceTitle: sourceTitle, - dateModifiedSecs: dateModifiedSecs, - sourceDateTakenMillis: sourceDateTakenMillis, - durationMillis: pageInfo.durationMillis ?? durationMillis, - ) - ..catalogMetadata = _catalogMetadata?.copyWith( - mimeType: pageInfo.mimeType, - isMultipage: false, - ) - ..addressDetails = _addressDetails?.copyWith(); - } - // from DB or platform source entry factory AvesEntry.fromMap(Map map) { return AvesEntry( @@ -251,7 +226,9 @@ class AvesEntry { bool get is360 => _catalogMetadata?.is360 ?? false; - bool get isMultipage => _catalogMetadata?.isMultipage ?? false; + bool get isMultiPage => _catalogMetadata?.isMultiPage ?? false; + + bool get isMotionPhoto => isMultiPage && mimeType == MimeTypes.jpeg; bool get canEdit => path != null; diff --git a/lib/model/entry_cache.dart b/lib/model/entry_cache.dart index 1e794f202..b535c0973 100644 --- a/lib/model/entry_cache.dart +++ b/lib/model/entry_cache.dart @@ -12,7 +12,7 @@ class EntryCache { int oldRotationDegrees, bool oldIsFlipped, ) async { - // TODO TLAD provide pageId parameter for multipage items, if someday image editing features are added for them + // TODO TLAD provide pageId parameter for multi page items, if someday image editing features are added for them int pageId; // evict fullscreen image diff --git a/lib/model/filters/type.dart b/lib/model/filters/type.dart index 361ac27c4..9087f1879 100644 --- a/lib/model/filters/type.dart +++ b/lib/model/filters/type.dart @@ -9,6 +9,7 @@ class TypeFilter extends CollectionFilter { static const _animated = 'animated'; // subset of `image/gif` and `image/webp` static const _geotiff = 'geotiff'; // subset of `image/tiff` + static const _motionPhoto = 'motion_photo'; // subset of `image/jpeg` static const _panorama = 'panorama'; // subset of images static const _sphericalVideo = 'spherical_video'; // subset of videos @@ -18,6 +19,7 @@ class TypeFilter extends CollectionFilter { static final animated = TypeFilter._private(_animated); static final geotiff = TypeFilter._private(_geotiff); + static final motionPhoto = TypeFilter._private(_motionPhoto); static final panorama = TypeFilter._private(_panorama); static final sphericalVideo = TypeFilter._private(_sphericalVideo); @@ -27,13 +29,17 @@ class TypeFilter extends CollectionFilter { _test = (entry) => entry.isAnimated; _icon = AIcons.animated; break; + case _motionPhoto: + _test = (entry) => entry.isMotionPhoto; + _icon = AIcons.motionPhoto; + break; case _panorama: _test = (entry) => entry.isImage && entry.is360; - _icon = AIcons.threesixty; + _icon = AIcons.threeSixty; break; case _sphericalVideo: _test = (entry) => entry.isVideo && entry.is360; - _icon = AIcons.threesixty; + _icon = AIcons.threeSixty; break; case _geotiff: _test = (entry) => entry.isGeotiff; @@ -64,6 +70,8 @@ class TypeFilter extends CollectionFilter { switch (itemType) { case _animated: return context.l10n.filterTypeAnimatedLabel; + case _motionPhoto: + return context.l10n.filterTypeMotionPhotoLabel; case _panorama: return context.l10n.filterTypePanoramaLabel; case _sphericalVideo: diff --git a/lib/model/metadata.dart b/lib/model/metadata.dart index 9a045d36d..2d8157faf 100644 --- a/lib/model/metadata.dart +++ b/lib/model/metadata.dart @@ -29,7 +29,7 @@ class DateMetadata { class CatalogMetadata { final int contentId, dateMillis; - final bool isAnimated, isGeotiff, is360, isMultipage; + final bool isAnimated, isGeotiff, is360, isMultiPage; bool isFlipped; int rotationDegrees; final String mimeType, xmpSubjects, xmpTitleDescription; @@ -41,7 +41,7 @@ class CatalogMetadata { static const _isFlippedMask = 1 << 1; static const _isGeotiffMask = 1 << 2; static const _is360Mask = 1 << 3; - static const _isMultipageMask = 1 << 4; + static const _isMultiPageMask = 1 << 4; CatalogMetadata({ this.contentId, @@ -51,7 +51,7 @@ class CatalogMetadata { this.isFlipped = false, this.isGeotiff = false, this.is360 = false, - this.isMultipage = false, + this.isMultiPage = false, this.rotationDegrees, this.xmpSubjects, this.xmpTitleDescription, @@ -70,7 +70,8 @@ class CatalogMetadata { CatalogMetadata copyWith({ int contentId, String mimeType, - bool isMultipage, + bool isMultiPage, + int rotationDegrees, }) { return CatalogMetadata( contentId: contentId ?? this.contentId, @@ -80,8 +81,8 @@ class CatalogMetadata { isFlipped: isFlipped, isGeotiff: isGeotiff, is360: is360, - isMultipage: isMultipage ?? this.isMultipage, - rotationDegrees: rotationDegrees, + isMultiPage: isMultiPage ?? this.isMultiPage, + rotationDegrees: rotationDegrees ?? this.rotationDegrees, xmpSubjects: xmpSubjects, xmpTitleDescription: xmpTitleDescription, latitude: latitude, @@ -99,7 +100,7 @@ class CatalogMetadata { isFlipped: flags & _isFlippedMask != 0, isGeotiff: flags & _isGeotiffMask != 0, is360: flags & _is360Mask != 0, - isMultipage: flags & _isMultipageMask != 0, + isMultiPage: flags & _isMultiPageMask != 0, // `rotationDegrees` should default to `sourceRotationDegrees`, not 0 rotationDegrees: map['rotationDegrees'], xmpSubjects: map['xmpSubjects'] ?? '', @@ -113,7 +114,7 @@ class CatalogMetadata { 'contentId': contentId, 'mimeType': mimeType, 'dateMillis': dateMillis, - 'flags': (isAnimated ? _isAnimatedMask : 0) | (isFlipped ? _isFlippedMask : 0) | (isGeotiff ? _isGeotiffMask : 0) | (is360 ? _is360Mask : 0) | (isMultipage ? _isMultipageMask : 0), + 'flags': (isAnimated ? _isAnimatedMask : 0) | (isFlipped ? _isFlippedMask : 0) | (isGeotiff ? _isGeotiffMask : 0) | (is360 ? _is360Mask : 0) | (isMultiPage ? _isMultiPageMask : 0), 'rotationDegrees': rotationDegrees, 'xmpSubjects': xmpSubjects, 'xmpTitleDescription': xmpTitleDescription, @@ -122,7 +123,7 @@ class CatalogMetadata { }; @override - String toString() => '$runtimeType#${shortHash(this)}{contentId=$contentId, mimeType=$mimeType, dateMillis=$dateMillis, isAnimated=$isAnimated, isFlipped=$isFlipped, isGeotiff=$isGeotiff, is360=$is360, isMultipage=$isMultipage, rotationDegrees=$rotationDegrees, latitude=$latitude, longitude=$longitude, xmpSubjects=$xmpSubjects, xmpTitleDescription=$xmpTitleDescription}'; + String toString() => '$runtimeType#${shortHash(this)}{contentId=$contentId, mimeType=$mimeType, dateMillis=$dateMillis, isAnimated=$isAnimated, isFlipped=$isFlipped, isGeotiff=$isGeotiff, is360=$is360, isMultiPage=$isMultiPage, rotationDegrees=$rotationDegrees, latitude=$latitude, longitude=$longitude, xmpSubjects=$xmpSubjects, xmpTitleDescription=$xmpTitleDescription}'; } class OverlayMetadata { diff --git a/lib/model/multipage.dart b/lib/model/multipage.dart index 2006b24ee..bc92ef4d7 100644 --- a/lib/model/multipage.dart +++ b/lib/model/multipage.dart @@ -1,69 +1,144 @@ +import 'package:aves/model/entry.dart'; +import 'package:aves/ref/mime_types.dart'; +import 'package:aves/services/services.dart'; import 'package:flutter/foundation.dart'; class MultiPageInfo { - final String uri; - final List<SinglePageInfo> pages; + final AvesEntry mainEntry; + final List<SinglePageInfo> _pages; + final Map<SinglePageInfo, AvesEntry> _pageEntries = {}; - int get pageCount => pages.length; + int get pageCount => _pages.length; MultiPageInfo({ - @required this.uri, - this.pages, - }) { - if (pages.isNotEmpty) { - pages.sort(); + @required this.mainEntry, + List<SinglePageInfo> pages, + }) : _pages = pages { + if (_pages.isNotEmpty) { + _pages.sort(); // make sure there is a page marked as default if (defaultPage == null) { - final firstPage = pages.removeAt(0); - pages.insert(0, firstPage.copyWith(isDefault: true)); + final firstPage = _pages.removeAt(0); + _pages.insert(0, firstPage.copyWith(isDefault: true)); } } } - factory MultiPageInfo.fromPageMaps(String uri, List<Map> pageMaps) { + factory MultiPageInfo.fromPageMaps(AvesEntry mainEntry, List<Map> pageMaps) { return MultiPageInfo( - uri: uri, + mainEntry: mainEntry, pages: pageMaps.map((page) => SinglePageInfo.fromMap(page)).toList(), ); } - SinglePageInfo get defaultPage => pages.firstWhere((page) => page.isDefault, orElse: () => null); + SinglePageInfo get defaultPage => _pages.firstWhere((page) => page.isDefault, orElse: () => null); - SinglePageInfo getByIndex(int index) => pages.firstWhere((page) => page.index == index, orElse: () => null); + SinglePageInfo getById(int pageId) => _pages.firstWhere((page) => page.pageId == pageId, orElse: () => null); - SinglePageInfo getById(int pageId) => pages.firstWhere((page) => page.pageId == pageId, orElse: () => null); + SinglePageInfo getByIndex(int pageIndex) => _pages.firstWhere((page) => page.index == pageIndex, orElse: () => null); + + AvesEntry getPageEntryByIndex(int pageIndex) => _getPageEntry(getByIndex(pageIndex)); + + AvesEntry _getPageEntry(SinglePageInfo pageInfo) { + if (pageInfo != null) { + return _pageEntries.putIfAbsent(pageInfo, () => _createPageEntry(pageInfo)); + } else { + return mainEntry; + } + } + + Set<AvesEntry> get videoPageEntries => _pages.where((page) => page.isVideo).map(_getPageEntry).toSet(); + + List<AvesEntry> get exportEntries => _pages.map((pageInfo) => _createPageEntry(pageInfo, eraseDefaultPageId: false)).toList(); + + Future<void> extractMotionPhotoVideo() async { + final videoPage = _pages.firstWhere((page) => page.isVideo, orElse: () => null); + if (videoPage != null && videoPage.uri == null) { + final fields = await embeddedDataService.extractMotionPhotoVideo(mainEntry); + if (fields != null) { + final pageIndex = _pages.indexOf(videoPage); + _pages.removeAt(pageIndex); + _pages.insert( + pageIndex, + videoPage.copyWith( + uri: fields['uri'] as String, + // the initial fake page may contain inaccurate values for the following fields + // so we override them with values from the extracted standalone video + rotationDegrees: fields['sourceRotationDegrees'] as int, + durationMillis: fields['durationMillis'] as int, + )); + _pageEntries.remove(videoPage); + } + } + } + + AvesEntry _createPageEntry(SinglePageInfo pageInfo, {bool eraseDefaultPageId = true}) { + // do not provide the page ID for the default page, + // so that we can treat this page like the main entry + // and retrieve cached images for it + final pageId = eraseDefaultPageId && pageInfo.isDefault ? null : pageInfo.pageId; + + return AvesEntry( + uri: pageInfo.uri ?? mainEntry.uri, + path: mainEntry.path, + contentId: mainEntry.contentId, + pageId: pageId, + sourceMimeType: pageInfo.mimeType ?? mainEntry.sourceMimeType, + width: pageInfo.width ?? mainEntry.width, + height: pageInfo.height ?? mainEntry.height, + sourceRotationDegrees: pageInfo.rotationDegrees ?? mainEntry.sourceRotationDegrees, + sizeBytes: mainEntry.sizeBytes, + sourceTitle: mainEntry.sourceTitle, + dateModifiedSecs: mainEntry.dateModifiedSecs, + sourceDateTakenMillis: mainEntry.sourceDateTakenMillis, + durationMillis: pageInfo.durationMillis ?? mainEntry.durationMillis, + ) + ..catalogMetadata = mainEntry.catalogMetadata?.copyWith( + mimeType: pageInfo.mimeType, + isMultiPage: false, + rotationDegrees: pageInfo.rotationDegrees, + ) + ..addressDetails = mainEntry.addressDetails?.copyWith(); + } @override - String toString() => '$runtimeType#${shortHash(this)}{uri=$uri, pages=$pages}'; + String toString() => '$runtimeType#${shortHash(this)}{mainEntry=$mainEntry, pages=$_pages}'; } class SinglePageInfo implements Comparable<SinglePageInfo> { final int index, pageId; - final String mimeType; final bool isDefault; - final int width, height, durationMillis; + final String uri, mimeType; + final int width, height, rotationDegrees, durationMillis; const SinglePageInfo({ this.index, this.pageId, - this.mimeType, this.isDefault, + this.uri, + this.mimeType, this.width, this.height, + this.rotationDegrees, this.durationMillis, }); SinglePageInfo copyWith({ bool isDefault, + String uri, + int rotationDegrees, + int durationMillis, }) { return SinglePageInfo( index: index, pageId: pageId, - mimeType: mimeType, isDefault: isDefault ?? this.isDefault, + uri: uri ?? this.uri, + mimeType: mimeType, width: width, height: height, - durationMillis: durationMillis, + rotationDegrees: rotationDegrees ?? this.rotationDegrees, + durationMillis: durationMillis ?? this.durationMillis, ); } @@ -72,16 +147,19 @@ class SinglePageInfo implements Comparable<SinglePageInfo> { return SinglePageInfo( index: index, pageId: index, - mimeType: map['mimeType'] as String, isDefault: map['isDefault'] as bool ?? false, + mimeType: map['mimeType'] as String, width: map['width'] as int ?? 0, height: map['height'] as int ?? 0, + rotationDegrees: map['rotationDegrees'] as int, durationMillis: map['durationMillis'] as int, ); } + bool get isVideo => MimeTypes.isVideo(mimeType); + @override - String toString() => '$runtimeType#${shortHash(this)}{index=$index, pageId=$pageId, mimeType=$mimeType, isDefault=$isDefault, width=$width, height=$height, durationMillis=$durationMillis}'; + String toString() => '$runtimeType#${shortHash(this)}{index=$index, pageId=$pageId, isDefault=$isDefault, uri=$uri, mimeType=$mimeType, width=$width, height=$height, rotationDegrees=$rotationDegrees, durationMillis=$durationMillis}'; @override int compareTo(SinglePageInfo other) => index.compareTo(other.index); diff --git a/lib/model/settings/coordinate_format.dart b/lib/model/settings/coordinate_format.dart index 159991ab8..a417ad056 100644 --- a/lib/model/settings/coordinate_format.dart +++ b/lib/model/settings/coordinate_format.dart @@ -1,7 +1,7 @@ import 'package:aves/geo/format.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:flutter/widgets.dart'; -import 'package:latlong/latlong.dart'; +import 'package:latlong2/latlong.dart'; import 'enums.dart'; diff --git a/lib/model/video/metadata.dart b/lib/model/video/metadata.dart index 8e3edde97..c1851231f 100644 --- a/lib/model/video/metadata.dart +++ b/lib/model/video/metadata.dart @@ -10,7 +10,7 @@ import 'package:aves/utils/file_utils.dart'; import 'package:aves/utils/math_utils.dart'; import 'package:aves/utils/string_utils.dart'; import 'package:aves/utils/time_utils.dart'; -import 'package:aves/widgets/common/video/fijkplayer.dart'; +import 'package:aves/widgets/viewer/video/fijkplayer.dart'; import 'package:fijkplayer/fijkplayer.dart'; import 'package:flutter/foundation.dart'; @@ -33,8 +33,12 @@ class VideoMetadataFormatter { static Future<Map> getVideoMetadata(AvesEntry entry) async { final player = FijkPlayer(); - await player.setDataSourceUntilPrepared(entry.uri); - final info = await player.getInfo(); + final info = await player.setDataSourceUntilPrepared(entry.uri).then((v) { + return player.getInfo(); + }).catchError((error) { + debugPrint('failed to get video metadata for entry=$entry, error=$error'); + return {}; + }); await player.release(); return info; } @@ -111,7 +115,9 @@ class VideoMetadataFormatter { save('Channel Layout', _formatChannelLayout(value)); break; case Keys.codecName: - save('Format', _formatCodecName(value)); + if (value != 'none') { + save('Format', _formatCodecName(value)); + } break; case Keys.codecPixelFormat: if (streamType == StreamTypes.video) { @@ -292,6 +298,7 @@ class VideoMetadataFormatter { } class StreamTypes { + static const attachment = 'attachment'; static const audio = 'audio'; static const metadata = 'metadata'; static const subtitle = 'subtitle'; diff --git a/lib/ref/mime_types.dart b/lib/ref/mime_types.dart index c82db0619..aa14e05bd 100644 --- a/lib/ref/mime_types.dart +++ b/lib/ref/mime_types.dart @@ -12,13 +12,15 @@ class MimeTypes { static const tiff = 'image/tiff'; static const webp = 'image/webp'; - static const psd = 'image/vnd.adobe.photoshop'; + static const art = 'image/x-jg'; + static const djvu = 'image/vnd.djvu'; + static const psdVnd = 'image/vnd.adobe.photoshop'; + static const psdX = 'image/x-photoshop'; static const arw = 'image/x-sony-arw'; static const cr2 = 'image/x-canon-cr2'; static const crw = 'image/x-canon-crw'; static const dcr = 'image/x-kodak-dcr'; - static const djvu = 'image/vnd.djvu'; static const dng = 'image/x-adobe-dng'; static const erf = 'image/x-epson-erf'; static const k25 = 'image/x-kodak-k25'; diff --git a/lib/ref/xmp.dart b/lib/ref/xmp.dart index 02a3bfa9e..2dd85b45c 100644 --- a/lib/ref/xmp.dart +++ b/lib/ref/xmp.dart @@ -16,6 +16,7 @@ class XMP { 'GettyImagesGIFT': 'Getty Images', 'GIMP': 'GIMP', 'GCamera': 'Google Camera', + 'GCreations': 'Google Creations', 'GFocus': 'Google Focus', 'GPano': 'Google Panorama', 'illustrator': 'Illustrator', diff --git a/lib/services/embedded_data_service.dart b/lib/services/embedded_data_service.dart new file mode 100644 index 000000000..b92aa2410 --- /dev/null +++ b/lib/services/embedded_data_service.dart @@ -0,0 +1,82 @@ +import 'dart:typed_data'; + +import 'package:aves/model/entry.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; + +abstract class EmbeddedDataService { + Future<List<Uint8List>> getExifThumbnails(AvesEntry entry); + + Future<Map> extractMotionPhotoVideo(AvesEntry entry); + + Future<Map> extractVideoEmbeddedPicture(AvesEntry entry); + + Future<Map> extractXmpDataProp(AvesEntry entry, String propPath, String propMimeType); +} + +class PlatformEmbeddedDataService implements EmbeddedDataService { + static const platform = MethodChannel('deckers.thibault/aves/embedded'); + + @override + Future<List<Uint8List>> getExifThumbnails(AvesEntry entry) async { + try { + final result = await platform.invokeMethod('getExifThumbnails', <String, dynamic>{ + 'mimeType': entry.mimeType, + 'uri': entry.uri, + 'sizeBytes': entry.sizeBytes, + }); + return (result as List).cast<Uint8List>(); + } on PlatformException catch (e) { + debugPrint('getExifThumbnail failed with code=${e.code}, exception=${e.message}, details=${e.details}'); + } + return []; + } + + @override + Future<Map> extractMotionPhotoVideo(AvesEntry entry) async { + try { + final result = await platform.invokeMethod('extractMotionPhotoVideo', <String, dynamic>{ + 'mimeType': entry.mimeType, + 'uri': entry.uri, + 'sizeBytes': entry.sizeBytes, + 'displayName': '${entry.bestTitle} • Video', + }); + return result; + } on PlatformException catch (e) { + debugPrint('extractMotionPhotoVideo failed with code=${e.code}, exception=${e.message}, details=${e.details}'); + } + return null; + } + + @override + Future<Map> extractVideoEmbeddedPicture(AvesEntry entry) async { + try { + final result = await platform.invokeMethod('extractVideoEmbeddedPicture', <String, dynamic>{ + 'uri': entry.uri, + 'displayName': '${entry.bestTitle} • Cover', + }); + return result; + } on PlatformException catch (e) { + debugPrint('extractVideoEmbeddedPicture failed with code=${e.code}, exception=${e.message}, details=${e.details}'); + } + return null; + } + + @override + Future<Map> extractXmpDataProp(AvesEntry entry, String propPath, String propMimeType) async { + try { + final result = await platform.invokeMethod('extractXmpDataProp', <String, dynamic>{ + 'mimeType': entry.mimeType, + 'uri': entry.uri, + 'sizeBytes': entry.sizeBytes, + 'displayName': '${entry.bestTitle} • $propPath', + 'propPath': propPath, + 'propMimeType': propMimeType, + }); + return result; + } on PlatformException catch (e) { + debugPrint('extractXmpDataProp failed with code=${e.code}, exception=${e.message}, details=${e.details}'); + } + return null; + } +} diff --git a/lib/services/geocoding_service.dart b/lib/services/geocoding_service.dart index 6ad6f23cf..3d69a01cf 100644 --- a/lib/services/geocoding_service.dart +++ b/lib/services/geocoding_service.dart @@ -3,7 +3,7 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; -import 'package:latlong/latlong.dart'; +import 'package:latlong2/latlong.dart'; class GeocodingService { static const platform = MethodChannel('deckers.thibault/aves/geocoding'); diff --git a/lib/services/image_file_service.dart b/lib/services/image_file_service.dart index e5d60f95e..4bf6238e8 100644 --- a/lib/services/image_file_service.dart +++ b/lib/services/image_file_service.dart @@ -75,7 +75,7 @@ abstract class ImageFileService { Stream<ExportOpEvent> export( Iterable<AvesEntry> entries, { - String mimeType = MimeTypes.jpeg, + @required String mimeType, @required String destinationAlbum, }); @@ -103,6 +103,7 @@ class PlatformImageFileService implements ImageFileService { 'rotationDegrees': entry.rotationDegrees, 'isFlipped': entry.isFlipped, 'dateModifiedSecs': entry.dateModifiedSecs, + 'sizeBytes': entry.sizeBytes, }; } @@ -316,7 +317,7 @@ class PlatformImageFileService implements ImageFileService { @override Stream<ExportOpEvent> export( Iterable<AvesEntry> entries, { - String mimeType = MimeTypes.jpeg, + @required String mimeType, @required String destinationAlbum, }) { try { diff --git a/lib/services/metadata_service.dart b/lib/services/metadata_service.dart index 690738336..e5f2eabe6 100644 --- a/lib/services/metadata_service.dart +++ b/lib/services/metadata_service.dart @@ -1,5 +1,3 @@ -import 'dart:typed_data'; - import 'package:aves/model/entry.dart'; import 'package:aves/model/metadata.dart'; import 'package:aves/model/multipage.dart'; @@ -21,12 +19,6 @@ abstract class MetadataService { Future<PanoramaInfo> getPanoramaInfo(AvesEntry entry); Future<String> getContentResolverProp(AvesEntry entry, String prop); - - Future<List<Uint8List>> getExifThumbnails(AvesEntry entry); - - Future<Map> extractVideoEmbeddedPicture(String uri); - - Future<Map> extractXmpDataProp(AvesEntry entry, String propPath, String propMimeType); } class PlatformMetadataService implements MetadataService { @@ -111,9 +103,16 @@ class PlatformMetadataService implements MetadataService { final result = await platform.invokeMethod('getMultiPageInfo', <String, dynamic>{ 'mimeType': entry.mimeType, 'uri': entry.uri, + 'sizeBytes': entry.sizeBytes, }); final pageMaps = (result as List).cast<Map>(); - return MultiPageInfo.fromPageMaps(entry.uri, pageMaps); + if (entry.isMotionPhoto && pageMaps.isNotEmpty) { + final imagePage = pageMaps[0]; + imagePage['width'] = entry.width; + imagePage['height'] = entry.height; + imagePage['rotationDegrees'] = entry.rotationDegrees; + } + return MultiPageInfo.fromPageMaps(entry, pageMaps); } on PlatformException catch (e) { debugPrint('getMultiPageInfo failed with code=${e.code}, exception=${e.message}, details=${e.details}'); } @@ -151,49 +150,4 @@ class PlatformMetadataService implements MetadataService { } return null; } - - @override - Future<List<Uint8List>> getExifThumbnails(AvesEntry entry) async { - try { - final result = await platform.invokeMethod('getExifThumbnails', <String, dynamic>{ - 'mimeType': entry.mimeType, - 'uri': entry.uri, - 'sizeBytes': entry.sizeBytes, - }); - return (result as List).cast<Uint8List>(); - } on PlatformException catch (e) { - debugPrint('getExifThumbnail failed with code=${e.code}, exception=${e.message}, details=${e.details}'); - } - return []; - } - - @override - Future<Map> extractVideoEmbeddedPicture(String uri) async { - try { - final result = await platform.invokeMethod('extractVideoEmbeddedPicture', <String, dynamic>{ - 'uri': uri, - }); - return result; - } on PlatformException catch (e) { - debugPrint('extractVideoEmbeddedPicture failed with code=${e.code}, exception=${e.message}, details=${e.details}'); - } - return null; - } - - @override - Future<Map> extractXmpDataProp(AvesEntry entry, String propPath, String propMimeType) async { - try { - final result = await platform.invokeMethod('extractXmpDataProp', <String, dynamic>{ - 'mimeType': entry.mimeType, - 'uri': entry.uri, - 'sizeBytes': entry.sizeBytes, - 'propPath': propPath, - 'propMimeType': propMimeType, - }); - return result; - } on PlatformException catch (e) { - debugPrint('extractXmpDataProp failed with code=${e.code}, exception=${e.message}, details=${e.details}'); - } - return null; - } } diff --git a/lib/services/services.dart b/lib/services/services.dart index bf1ebc7ee..c863b0a36 100644 --- a/lib/services/services.dart +++ b/lib/services/services.dart @@ -1,5 +1,6 @@ import 'package:aves/model/availability.dart'; import 'package:aves/model/metadata_db.dart'; +import 'package:aves/services/embedded_data_service.dart'; import 'package:aves/services/image_file_service.dart'; import 'package:aves/services/media_store_service.dart'; import 'package:aves/services/metadata_service.dart'; @@ -14,6 +15,7 @@ final pContext = getIt<p.Context>(); final availability = getIt<AvesAvailability>(); final metadataDb = getIt<MetadataDb>(); +final embeddedDataService = getIt<EmbeddedDataService>(); final imageFileService = getIt<ImageFileService>(); final mediaStoreService = getIt<MediaStoreService>(); final metadataService = getIt<MetadataService>(); @@ -25,6 +27,7 @@ void initPlatformServices() { getIt.registerLazySingleton<AvesAvailability>(() => LiveAvesAvailability()); getIt.registerLazySingleton<MetadataDb>(() => SqfliteMetadataDb()); + getIt.registerLazySingleton<EmbeddedDataService>(() => PlatformEmbeddedDataService()); getIt.registerLazySingleton<ImageFileService>(() => PlatformImageFileService()); getIt.registerLazySingleton<MediaStoreService>(() => PlatformMediaStoreService()); getIt.registerLazySingleton<MetadataService>(() => PlatformMetadataService()); diff --git a/lib/theme/durations.dart b/lib/theme/durations.dart index a1300a711..03fde8263 100644 --- a/lib/theme/durations.dart +++ b/lib/theme/durations.dart @@ -4,6 +4,7 @@ class Durations { // Flutter animations (with margin) static const popupMenuAnimation = Duration(milliseconds: 300 + 10); // ref `_kMenuDuration` used in `_PopupMenuRoute` static const dialogTransitionAnimation = Duration(milliseconds: 150 + 10); // ref `transitionDuration` used in `DialogRoute` + static const drawerTransitionAnimation = Duration(milliseconds: 246 + 10); // ref `_kBaseSettleDuration` used in `DrawerControllerState` static const toggleableTransitionAnimation = Duration(milliseconds: 200 + 10); // ref `_kToggleDuration` used in `ToggleableStateMixin` // common animations @@ -12,12 +13,15 @@ class Durations { static const sweepingAnimation = Duration(milliseconds: 650); static const staggeredAnimation = Duration(milliseconds: 375); - static const staggeredAnimationPageTarget = Duration(milliseconds: 900); + static const staggeredAnimationPageTarget = Duration(milliseconds: 800); static const dialogFieldReachAnimation = Duration(milliseconds: 300); static const appBarTitleAnimation = Duration(milliseconds: 300); static const appBarActionChangeAnimation = Duration(milliseconds: 200); + // drawer + static const newsBadgeAnimation = Duration(milliseconds: 200); + // filter grids animations static const chipDecorationAnimation = Duration(milliseconds: 200); static const highlightScrollAnimationMinMillis = 400; @@ -60,7 +64,7 @@ class Durations { static const doubleBackTimerDelay = Duration(milliseconds: 1000); static const softKeyboardDisplayDelay = Duration(milliseconds: 300); static const searchDebounceDelay = Duration(milliseconds: 250); - static const contentChangeDebounceDelay = Duration(milliseconds: 500); + static const contentChangeDebounceDelay = Duration(milliseconds: 1000); // app life static const lastVersionCheckInterval = Duration(days: 7); diff --git a/lib/theme/icons.dart b/lib/theme/icons.dart index b4a14c7cf..61315ca85 100644 --- a/lib/theme/icons.dart +++ b/lib/theme/icons.dart @@ -8,6 +8,7 @@ class AIcons { static const IconData vector = Icons.code_outlined; static const IconData android = Icons.android; + static const IconData broken = Icons.broken_image_outlined; static const IconData checked = Icons.done_outlined; static const IconData date = Icons.calendar_today_outlined; static const IconData disc = Icons.fiber_manual_record; @@ -71,9 +72,10 @@ class AIcons { // thumbnail overlay static const IconData animated = Icons.slideshow; static const IconData geo = Icons.language_outlined; - static const IconData multipage = Icons.burst_mode_outlined; + static const IconData motionPhoto = Icons.motion_photos_on_outlined; + static const IconData multiPage = Icons.burst_mode_outlined; static const IconData play = Icons.play_circle_outline; - static const IconData threesixty = Icons.threesixty_outlined; + static const IconData threeSixty = Icons.threesixty_outlined; static const IconData selected = Icons.check_circle_outline; static const IconData unselected = Icons.radio_button_unchecked; } diff --git a/lib/theme/themes.dart b/lib/theme/themes.dart index 84d02ddf3..9cf6f14bd 100644 --- a/lib/theme/themes.dart +++ b/lib/theme/themes.dart @@ -10,7 +10,6 @@ class Themes { brightness: Brightness.dark, accentColor: _accentColor, scaffoldBackgroundColor: Colors.grey[900], - buttonColor: _accentColor, dialogBackgroundColor: Colors.grey[850], toggleableActiveColor: _accentColor, tooltipTheme: TooltipThemeData( @@ -25,6 +24,12 @@ class Themes { ), ), ), + colorScheme: ColorScheme.dark( + primary: _accentColor, + secondary: _accentColor, + onPrimary: Colors.white, + onSecondary: Colors.white, + ), snackBarTheme: SnackBarThemeData( backgroundColor: Colors.grey[800], contentTextStyle: TextStyle( @@ -32,16 +37,6 @@ class Themes { ), behavior: SnackBarBehavior.floating, ), - elevatedButtonTheme: ElevatedButtonThemeData( - style: ElevatedButton.styleFrom( - primary: _accentColor, - ), - ), - outlinedButtonTheme: OutlinedButtonThemeData( - style: OutlinedButton.styleFrom( - primary: _accentColor, - ), - ), textButtonTheme: TextButtonThemeData( style: TextButton.styleFrom( primary: Colors.white, diff --git a/lib/utils/constants.dart b/lib/utils/constants.dart index a4ef8caf7..731dcbec7 100644 --- a/lib/utils/constants.dart +++ b/lib/utils/constants.dart @@ -2,7 +2,7 @@ import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter/painting.dart'; -import 'package:latlong/latlong.dart'; +import 'package:latlong2/latlong.dart'; class Constants { // as of Flutter v1.22.3, overflowing `Text` miscalculates height and some text (e.g. 'Å') is clipped @@ -42,8 +42,8 @@ class Constants { Dependency( name: 'Android-TiffBitmapFactory', license: 'MIT', - licenseUrl: 'https://github.com/Beyka/Android-TiffBitmapFactory/blob/master/license.txt', - sourceUrl: 'https://github.com/Beyka/Android-TiffBitmapFactory', + licenseUrl: 'https://github.com/deckerst/Android-TiffBitmapFactory/blob/master/license.txt', + sourceUrl: 'https://github.com/deckerst/Android-TiffBitmapFactory', ), Dependency( name: 'CWAC-Document', diff --git a/lib/widgets/collection/thumbnail/error.dart b/lib/widgets/collection/thumbnail/error.dart index 6b531adeb..21849a5c3 100644 --- a/lib/widgets/collection/thumbnail/error.dart +++ b/lib/widgets/collection/thumbnail/error.dart @@ -1,8 +1,13 @@ +import 'dart:io'; + import 'package:aves/model/entry.dart'; +import 'package:aves/theme/icons.dart'; import 'package:aves/utils/mime_utils.dart'; +import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -class ErrorThumbnail extends StatelessWidget { +class ErrorThumbnail extends StatefulWidget { final AvesEntry entry; final double extent; final String tooltip; @@ -13,23 +18,53 @@ class ErrorThumbnail extends StatelessWidget { @required this.tooltip, }); + @override + _ErrorThumbnailState createState() => _ErrorThumbnailState(); +} + +class _ErrorThumbnailState extends State<ErrorThumbnail> { + Future<bool> _exists; + + AvesEntry get entry => widget.entry; + + double get extent => widget.extent; + + @override + void initState() { + super.initState(); + _exists = entry.path != null ? File(entry.path).exists() : SynchronousFuture(true); + } + @override Widget build(BuildContext context) { - return Container( - alignment: Alignment.center, - color: Colors.black, - child: Tooltip( - message: tooltip, - preferBelow: false, - child: Text( - MimeUtils.displayType(entry.mimeType), - style: TextStyle( - color: Colors.blueGrey, - fontSize: extent / 5, - ), - textAlign: TextAlign.center, - ), - ), - ); + const color = Colors.blueGrey; + return FutureBuilder<bool>( + future: _exists, + builder: (context, snapshot) { + if (snapshot.connectionState != ConnectionState.done) return SizedBox(); + final exists = snapshot.data; + return Container( + alignment: Alignment.center, + color: Colors.black, + child: Tooltip( + message: exists ? widget.tooltip : context.l10n.viewerErrorDoesNotExist, + preferBelow: false, + child: exists + ? Text( + MimeUtils.displayType(entry.mimeType), + style: TextStyle( + color: color, + fontSize: extent / 5, + ), + textAlign: TextAlign.center, + ) + : Icon( + AIcons.broken, + size: extent / 2, + color: color, + ), + ), + ); + }); } } diff --git a/lib/widgets/collection/thumbnail/overlay.dart b/lib/widgets/collection/thumbnail/overlay.dart index 26ee3c736..133088e95 100644 --- a/lib/widgets/collection/thumbnail/overlay.dart +++ b/lib/widgets/collection/thumbnail/overlay.dart @@ -34,7 +34,7 @@ class ThumbnailEntryOverlay extends StatelessWidget { AnimatedImageIcon() else ...[ if (entry.isRaw && context.select<ThumbnailThemeData, bool>((t) => t.showRaw)) RawIcon(), - if (entry.isMultipage) MultipageIcon(), + if (entry.isMultiPage) MultiPageIcon(entry: entry), if (entry.isGeotiff) GeotiffIcon(), if (entry.is360) SphericalImageIcon(), ] diff --git a/lib/widgets/collection/thumbnail/theme.dart b/lib/widgets/collection/thumbnail/theme.dart index 83bd64c28..fa85901f4 100644 --- a/lib/widgets/collection/thumbnail/theme.dart +++ b/lib/widgets/collection/thumbnail/theme.dart @@ -6,10 +6,12 @@ import 'package:provider/provider.dart'; class ThumbnailTheme extends StatelessWidget { final double extent; + final bool showLocation; final Widget child; const ThumbnailTheme({ @required this.extent, + this.showLocation, @required this.child, }); @@ -22,7 +24,7 @@ class ThumbnailTheme extends StatelessWidget { return ThumbnailThemeData( iconSize: iconSize, fontSize: fontSize, - showLocation: settings.showThumbnailLocation, + showLocation: showLocation ?? settings.showThumbnailLocation, showRaw: settings.showThumbnailRaw, showVideoDuration: settings.showThumbnailVideoDuration, ); diff --git a/lib/widgets/common/identity/aves_icons.dart b/lib/widgets/common/identity/aves_icons.dart index efba5b527..bec2db615 100644 --- a/lib/widgets/common/identity/aves_icons.dart +++ b/lib/widgets/common/identity/aves_icons.dart @@ -23,7 +23,7 @@ class VideoIcon extends StatelessWidget { final thumbnailTheme = context.watch<ThumbnailThemeData>(); final showDuration = thumbnailTheme.showVideoDuration; Widget child = OverlayIcon( - icon: entry.is360 ? AIcons.threesixty : AIcons.play, + icon: entry.is360 ? AIcons.threeSixty : AIcons.play, size: thumbnailTheme.iconSize, text: showDuration ? entry.durationText : null, iconScale: entry.is360 && showDuration ? .9 : 1, @@ -72,7 +72,7 @@ class SphericalImageIcon extends StatelessWidget { @override Widget build(BuildContext context) { return OverlayIcon( - icon: AIcons.threesixty, + icon: AIcons.threeSixty, size: context.select<ThumbnailThemeData, double>((t) => t.iconSize), ); } @@ -102,13 +102,18 @@ class RawIcon extends StatelessWidget { } } -class MultipageIcon extends StatelessWidget { - const MultipageIcon({Key key}) : super(key: key); +class MultiPageIcon extends StatelessWidget { + final AvesEntry entry; + + const MultiPageIcon({ + Key key, + this.entry, + }) : super(key: key); @override Widget build(BuildContext context) { return OverlayIcon( - icon: AIcons.multipage, + icon: entry.isMotionPhoto ? AIcons.motionPhoto : AIcons.multiPage, size: context.select<ThumbnailThemeData, double>((t) => t.iconSize), iconScale: .8, ); diff --git a/lib/widgets/debug/cache.dart b/lib/widgets/debug/cache.dart index ed37531ac..cc9842fe4 100644 --- a/lib/widgets/debug/cache.dart +++ b/lib/widgets/debug/cache.dart @@ -40,12 +40,12 @@ class _DebugCacheSectionState extends State<DebugCacheSection> with AutomaticKee Row( children: [ Expanded( - child: Text('SVG cache: ${PictureProvider.cacheCount} items'), + child: Text('SVG cache: ${PictureProvider.cache.count} items'), ), SizedBox(width: 8), ElevatedButton( onPressed: () { - PictureProvider.clearCache(); + PictureProvider.cache.clear(); setState(() {}); }, diff --git a/lib/widgets/drawer/app_drawer.dart b/lib/widgets/drawer/app_drawer.dart index e3c3ff760..e11c4b497 100644 --- a/lib/widgets/drawer/app_drawer.dart +++ b/lib/widgets/drawer/app_drawer.dart @@ -8,10 +8,10 @@ import 'package:aves/model/source/collection_source.dart'; import 'package:aves/model/source/location.dart'; import 'package:aves/model/source/tag.dart'; import 'package:aves/services/services.dart'; +import 'package:aves/theme/durations.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/utils/android_file_utils.dart'; import 'package:aves/widgets/about/about_page.dart'; -import 'package:aves/widgets/about/news_badge.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/extensions/media_query.dart'; import 'package:aves/widgets/common/identity/aves_logo.dart'; @@ -58,9 +58,6 @@ class _AppDrawerState extends State<AppDrawer> { albumListTile, countryListTile, tagListTile, - Divider(), - settingsTile, - aboutTile, if (kDebugMode) ...[ Divider(), debugTile, @@ -92,8 +89,19 @@ class _AppDrawerState extends State<AppDrawer> { } Widget _buildHeader(BuildContext context) { + Future<void> goTo(String routeName, WidgetBuilder pageBuilder) async { + Navigator.pop(context); + await Future.delayed(Durations.drawerTransitionAnimation); + await Navigator.push( + context, + MaterialPageRoute( + settings: RouteSettings(name: routeName), + builder: pageBuilder, + )); + } + return Container( - padding: EdgeInsets.all(16), + padding: EdgeInsets.only(left: 16, top: 16, right: 16, bottom: 8), color: Theme.of(context).accentColor, child: SafeArea( bottom: false, @@ -119,6 +127,61 @@ class _AppDrawerState extends State<AppDrawer> { ], ), ), + SizedBox(height: 8), + OutlinedButtonTheme( + data: OutlinedButtonThemeData( + style: ButtonStyle( + foregroundColor: MaterialStateProperty.all<Color>(Colors.white), + overlayColor: MaterialStateProperty.all<Color>(Colors.white24), + ), + ), + child: Wrap( + spacing: 8, + runSpacing: 8, + children: [ + OutlinedButton.icon( + onPressed: () => goTo(AboutPage.routeName, (_) => AboutPage()), + icon: Icon(AIcons.info), + label: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(context.l10n.aboutPageTitle), + FutureBuilder<bool>( + future: _newVersionLoader, + builder: (context, snapshot) { + final newVersion = snapshot.data == true; + final badgeSize = 8.0 * MediaQuery.textScaleFactorOf(context); + return AnimatedOpacity( + duration: Durations.newsBadgeAnimation, + opacity: newVersion ? 1 : 0, + child: Padding( + padding: EdgeInsetsDirectional.only(start: 2), + child: DecoratedBox( + decoration: BoxDecoration( + border: Border.all(color: Colors.white70), + borderRadius: BorderRadius.circular(badgeSize), + ), + child: Icon( + Icons.circle, + size: badgeSize, + color: Colors.red, + ), + ), + ), + ); + }, + ), + ], + ), + ), + OutlinedButton.icon( + onPressed: () => goTo(SettingsPage.routeName, (_) => SettingsPage()), + icon: Icon(AIcons.settings), + label: Text(context.l10n.settingsPageTitle), + ), + ], + ), + ) ], ), ), @@ -198,29 +261,6 @@ class _AppDrawerState extends State<AppDrawer> { pageBuilder: (_) => TagListPage(), ); - Widget get settingsTile => NavTile( - icon: AIcons.settings, - title: context.l10n.settingsPageTitle, - topLevel: false, - routeName: SettingsPage.routeName, - pageBuilder: (_) => SettingsPage(), - ); - - Widget get aboutTile => FutureBuilder<bool>( - future: _newVersionLoader, - builder: (context, snapshot) { - final newVersion = snapshot.data == true; - return NavTile( - icon: AIcons.info, - title: context.l10n.aboutPageTitle, - trailing: newVersion ? AboutNewsBadge() : null, - topLevel: false, - routeName: AboutPage.routeName, - pageBuilder: (_) => AboutPage(), - ); - }, - ); - Widget get debugTile => NavTile( icon: AIcons.debug, title: 'Debug', diff --git a/lib/widgets/drawer/tile.dart b/lib/widgets/drawer/tile.dart index be6b4f8b6..47602c4fd 100644 --- a/lib/widgets/drawer/tile.dart +++ b/lib/widgets/drawer/tile.dart @@ -1,5 +1,3 @@ -import 'dart:ui'; - import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; diff --git a/lib/widgets/filter_grids/common/decorated_filter_chip.dart b/lib/widgets/filter_grids/common/decorated_filter_chip.dart index fb01a0460..841745d44 100644 --- a/lib/widgets/filter_grids/common/decorated_filter_chip.dart +++ b/lib/widgets/filter_grids/common/decorated_filter_chip.dart @@ -1,5 +1,4 @@ import 'dart:math'; -import 'dart:ui'; import 'package:aves/model/entry.dart'; import 'package:aves/model/filters/album.dart'; diff --git a/lib/widgets/search/search_delegate.dart b/lib/widgets/search/search_delegate.dart index 1708746c6..f0f9adfc1 100644 --- a/lib/widgets/search/search_delegate.dart +++ b/lib/widgets/search/search_delegate.dart @@ -34,6 +34,7 @@ class CollectionSearchDelegate { MimeFilter.image, MimeFilter.video, TypeFilter.animated, + TypeFilter.motionPhoto, TypeFilter.panorama, TypeFilter.sphericalVideo, TypeFilter.geotiff, diff --git a/lib/widgets/viewer/entry_action_delegate.dart b/lib/widgets/viewer/entry_action_delegate.dart index 232740c53..cf2ff7d53 100644 --- a/lib/widgets/viewer/entry_action_delegate.dart +++ b/lib/widgets/viewer/entry_action_delegate.dart @@ -5,6 +5,7 @@ import 'package:aves/model/actions/move_type.dart'; import 'package:aves/model/entry.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_source.dart'; +import 'package:aves/ref/mime_types.dart'; import 'package:aves/services/android_app_service.dart'; import 'package:aves/services/image_op_events.dart'; import 'package:aves/services/services.dart'; @@ -168,13 +169,13 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix if (!await checkFreeSpaceForMove(context, {entry}, destinationAlbum, MoveType.export)) return; final selection = <AvesEntry>{}; - if (entry.isMultipage) { + if (entry.isMultiPage) { final multiPageInfo = await metadataService.getMultiPageInfo(entry); + if (entry.isMotionPhoto) { + await multiPageInfo.extractMotionPhotoVideo(); + } if (multiPageInfo.pageCount > 1) { - for (final page in multiPageInfo.pages) { - final pageEntry = entry.getPageEntry(page, eraseDefaultPageId: false); - selection.add(pageEntry); - } + selection.addAll(multiPageInfo.exportEntries); } } else { selection.add(entry); @@ -183,7 +184,11 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix final selectionCount = selection.length; showOpReport<ExportOpEvent>( context: context, - opStream: imageFileService.export(selection, destinationAlbum: destinationAlbum), + opStream: imageFileService.export( + selection, + mimeType: MimeTypes.jpeg, + destinationAlbum: destinationAlbum, + ), itemCount: selectionCount, onDone: (processed) { final movedOps = processed.where((e) => e.success); diff --git a/lib/widgets/viewer/entry_horizontal_pager.dart b/lib/widgets/viewer/entry_horizontal_pager.dart index f688df0c2..96dcd75cc 100644 --- a/lib/widgets/viewer/entry_horizontal_pager.dart +++ b/lib/widgets/viewer/entry_horizontal_pager.dart @@ -3,27 +3,21 @@ import 'package:aves/model/multipage.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/widgets/common/magnifier/pan/gesture_detector_scope.dart'; import 'package:aves/widgets/common/magnifier/pan/scroll_physics.dart'; -import 'package:aves/widgets/common/video/controller.dart'; -import 'package:aves/widgets/viewer/multipage.dart'; +import 'package:aves/widgets/viewer/multipage/conductor.dart'; import 'package:aves/widgets/viewer/visual/entry_page_view.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -import 'package:tuple/tuple.dart'; class MultiEntryScroller extends StatefulWidget { final CollectionLens collection; final PageController pageController; final ValueChanged<int> onPageChanged; - final List<Tuple2<String, AvesVideoController>> videoControllers; - final List<Tuple2<String, MultiPageController>> multiPageControllers; final void Function(String uri) onViewDisposed; const MultiEntryScroller({ this.collection, this.pageController, this.onPageChanged, - this.videoControllers, - this.multiPageControllers, this.onViewDisposed, }); @@ -50,17 +44,17 @@ class _MultiEntryScrollerState extends State<MultiEntryScroller> with AutomaticK final entry = entries[index]; Widget child; - if (entry.isMultipage) { - final multiPageController = _getMultiPageController(entry); + if (entry.isMultiPage) { + final multiPageController = context.read<MultiPageConductor>().getController(entry); if (multiPageController != null) { - child = FutureBuilder<MultiPageInfo>( - future: multiPageController.info, + child = StreamBuilder<MultiPageInfo>( + stream: multiPageController.infoStream, builder: (context, snapshot) { - final multiPageInfo = snapshot.data; + final multiPageInfo = multiPageController.info; return ValueListenableBuilder<int>( valueListenable: multiPageController.pageNotifier, builder: (context, page, child) { - return _buildViewer(entry, page: multiPageInfo?.getByIndex(page)); + return _buildViewer(entry, pageEntry: multiPageInfo?.getPageEntryByIndex(page)); }, ); }, @@ -78,39 +72,30 @@ class _MultiEntryScrollerState extends State<MultiEntryScroller> with AutomaticK ); } - Widget _buildViewer(AvesEntry entry, {SinglePageInfo page}) { + Widget _buildViewer(AvesEntry mainEntry, {AvesEntry pageEntry}) { return Selector<MediaQueryData, Size>( selector: (c, mq) => mq.size, builder: (c, mqSize, child) { return EntryPageView( key: Key('imageview'), - mainEntry: entry, - page: page, + mainEntry: mainEntry, + pageEntry: pageEntry ?? mainEntry, viewportSize: mqSize, - videoControllers: widget.videoControllers, - onDisposed: () => widget.onViewDisposed?.call(entry.uri), + onDisposed: () => widget.onViewDisposed?.call(mainEntry.uri), ); }, ); } - MultiPageController _getMultiPageController(AvesEntry entry) { - return widget.multiPageControllers.firstWhere((kv) => kv.item1 == entry.uri, orElse: () => null)?.item2; - } - @override bool get wantKeepAlive => true; } class SingleEntryScroller extends StatefulWidget { final AvesEntry entry; - final List<Tuple2<String, AvesVideoController>> videoControllers; - final List<Tuple2<String, MultiPageController>> multiPageControllers; const SingleEntryScroller({ this.entry, - this.videoControllers, - this.multiPageControllers, }); @override @@ -118,24 +103,24 @@ class SingleEntryScroller extends StatefulWidget { } class _SingleEntryScrollerState extends State<SingleEntryScroller> with AutomaticKeepAliveClientMixin { - AvesEntry get entry => widget.entry; + AvesEntry get mainEntry => widget.entry; @override Widget build(BuildContext context) { super.build(context); Widget child; - if (entry.isMultipage) { - final multiPageController = _getMultiPageController(entry); + if (mainEntry.isMultiPage) { + final multiPageController = context.read<MultiPageConductor>().getController(mainEntry); if (multiPageController != null) { - child = FutureBuilder<MultiPageInfo>( - future: multiPageController.info, + child = StreamBuilder<MultiPageInfo>( + stream: multiPageController.infoStream, builder: (context, snapshot) { - final multiPageInfo = snapshot.data; + final multiPageInfo = multiPageController.info; return ValueListenableBuilder<int>( valueListenable: multiPageController.pageNotifier, builder: (context, page, child) { - return _buildViewer(page: multiPageInfo?.getByIndex(page)); + return _buildViewer(pageEntry: multiPageInfo?.getPageEntryByIndex(page)); }, ); }, @@ -150,24 +135,19 @@ class _SingleEntryScrollerState extends State<SingleEntryScroller> with Automati ); } - Widget _buildViewer({SinglePageInfo page}) { + Widget _buildViewer({AvesEntry pageEntry}) { return Selector<MediaQueryData, Size>( selector: (c, mq) => mq.size, builder: (c, mqSize, child) { return EntryPageView( - mainEntry: entry, - page: page, + mainEntry: mainEntry, + pageEntry: pageEntry ?? mainEntry, viewportSize: mqSize, - videoControllers: widget.videoControllers, ); }, ); } - MultiPageController _getMultiPageController(AvesEntry entry) { - return widget.multiPageControllers.firstWhere((kv) => kv.item1 == entry.uri, orElse: () => null)?.item2; - } - @override bool get wantKeepAlive => true; } diff --git a/lib/widgets/viewer/entry_vertical_pager.dart b/lib/widgets/viewer/entry_vertical_pager.dart index 1732bbe2a..1a4b00540 100644 --- a/lib/widgets/viewer/entry_vertical_pager.dart +++ b/lib/widgets/viewer/entry_vertical_pager.dart @@ -3,21 +3,16 @@ import 'dart:math'; import 'package:aves/model/entry.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/widgets/common/magnifier/pan/scroll_physics.dart'; -import 'package:aves/widgets/common/video/controller.dart'; import 'package:aves/widgets/viewer/entry_horizontal_pager.dart'; import 'package:aves/widgets/viewer/info/info_page.dart'; import 'package:aves/widgets/viewer/info/notifications.dart'; -import 'package:aves/widgets/viewer/multipage.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; -import 'package:tuple/tuple.dart'; class ViewerVerticalPageView extends StatefulWidget { final CollectionLens collection; final ValueNotifier<AvesEntry> entryNotifier; - final List<Tuple2<String, AvesVideoController>> videoControllers; - final List<Tuple2<String, MultiPageController>> multiPageControllers; final PageController horizontalPager, verticalPager; final void Function(int page) onVerticalPageChanged, onHorizontalPageChanged; final VoidCallback onImagePageRequested; @@ -26,8 +21,6 @@ class ViewerVerticalPageView extends StatefulWidget { const ViewerVerticalPageView({ @required this.collection, @required this.entryNotifier, - @required this.videoControllers, - @required this.multiPageControllers, @required this.verticalPager, @required this.horizontalPager, @required this.onVerticalPageChanged, @@ -92,14 +85,10 @@ class _ViewerVerticalPageViewState extends State<ViewerVerticalPageView> { collection: collection, pageController: widget.horizontalPager, onPageChanged: widget.onHorizontalPageChanged, - videoControllers: widget.videoControllers, - multiPageControllers: widget.multiPageControllers, onViewDisposed: widget.onViewDisposed, ) : SingleEntryScroller( entry: entry, - videoControllers: widget.videoControllers, - multiPageControllers: widget.multiPageControllers, ), NotificationListener( onNotification: (notification) { @@ -152,6 +141,9 @@ class _ViewerVerticalPageViewState extends State<ViewerVerticalPageView> { } else { Navigator.pop(context); } + + // needed to refresh when entry changes but the page does not (e.g. on page deletion) + setState(() {}); } // when the entry image itself changed (e.g. after rotation) diff --git a/lib/widgets/viewer/entry_viewer_page.dart b/lib/widgets/viewer/entry_viewer_page.dart index f285d8292..8fd6512aa 100644 --- a/lib/widgets/viewer/entry_viewer_page.dart +++ b/lib/widgets/viewer/entry_viewer_page.dart @@ -2,7 +2,10 @@ import 'package:aves/model/entry.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/widgets/common/providers/media_query_data_provider.dart'; import 'package:aves/widgets/viewer/entry_viewer_stack.dart'; +import 'package:aves/widgets/viewer/multipage/conductor.dart'; +import 'package:aves/widgets/viewer/video/conductor.dart'; import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; class EntryViewerPage extends StatelessWidget { static const routeName = '/viewer'; @@ -20,9 +23,17 @@ class EntryViewerPage extends StatelessWidget { Widget build(BuildContext context) { return MediaQueryDataProvider( child: Scaffold( - body: EntryViewerStack( - collection: collection, - initialEntry: initialEntry, + body: Provider<VideoConductor>( + create: (context) => VideoConductor(), + dispose: (context, value) => value.dispose(), + child: Provider<MultiPageConductor>( + create: (context) => MultiPageConductor(), + dispose: (context, value) => value.dispose(), + child: EntryViewerStack( + collection: collection, + initialEntry: initialEntry, + ), + ), ), backgroundColor: Navigator.canPop(context) ? Colors.transparent : Colors.black, resizeToAvoidBottomInset: false, diff --git a/lib/widgets/viewer/entry_viewer_stack.dart b/lib/widgets/viewer/entry_viewer_stack.dart index c8aa61092..03aa7df4a 100644 --- a/lib/widgets/viewer/entry_viewer_stack.dart +++ b/lib/widgets/viewer/entry_viewer_stack.dart @@ -1,7 +1,9 @@ import 'dart:math'; +import 'package:aves/model/actions/entry_actions.dart'; import 'package:aves/model/entry.dart'; import 'package:aves/model/filters/filters.dart'; +import 'package:aves/model/multipage.dart'; import 'package:aves/model/settings/enums.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_lens.dart'; @@ -11,18 +13,18 @@ import 'package:aves/theme/durations.dart'; import 'package:aves/utils/change_notifier.dart'; import 'package:aves/widgets/collection/collection_page.dart'; import 'package:aves/widgets/common/basic/insets.dart'; -import 'package:aves/widgets/common/video/controller.dart'; -import 'package:aves/widgets/common/video/fijkplayer.dart'; import 'package:aves/widgets/viewer/entry_action_delegate.dart'; import 'package:aves/widgets/viewer/entry_vertical_pager.dart'; import 'package:aves/widgets/viewer/hero.dart'; import 'package:aves/widgets/viewer/info/notifications.dart'; -import 'package:aves/widgets/viewer/multipage.dart'; -import 'package:aves/widgets/viewer/overlay/bottom.dart'; +import 'package:aves/widgets/viewer/multipage/conductor.dart'; +import 'package:aves/widgets/viewer/overlay/bottom/common.dart'; +import 'package:aves/widgets/viewer/overlay/bottom/panorama.dart'; +import 'package:aves/widgets/viewer/overlay/bottom/video.dart'; import 'package:aves/widgets/viewer/overlay/notifications.dart'; -import 'package:aves/widgets/viewer/overlay/panorama.dart'; import 'package:aves/widgets/viewer/overlay/top.dart'; -import 'package:aves/widgets/viewer/overlay/video.dart'; +import 'package:aves/widgets/viewer/video/conductor.dart'; +import 'package:aves/widgets/viewer/video/controller.dart'; import 'package:aves/widgets/viewer/visual/state.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -57,8 +59,6 @@ class _EntryViewerStackState extends State<EntryViewerStack> with SingleTickerPr Animation<Offset> _bottomOverlayOffset; EdgeInsets _frozenViewInsets, _frozenViewPadding; EntryActionDelegate _actionDelegate; - final List<Tuple2<String, AvesVideoController>> _videoControllers = []; - final List<Tuple2<String, MultiPageController>> _multiPageControllers = []; final List<Tuple2<String, ValueNotifier<ViewState>>> _viewStateNotifiers = []; final ValueNotifier<HeroInfo> _heroInfoNotifier = ValueNotifier(null); @@ -108,7 +108,7 @@ class _EntryViewerStackState extends State<EntryViewerStack> with SingleTickerPr collection: collection, showInfo: () => _goToVerticalPage(infoPage), ); - _initViewStateControllers(); + _initEntryControllers(); _registerWidget(widget); WidgetsBinding.instance.addObserver(this); WidgetsBinding.instance.addPostFrameCallback((_) => _initOverlay()); @@ -128,10 +128,6 @@ class _EntryViewerStackState extends State<EntryViewerStack> with SingleTickerPr void dispose() { _overlayAnimationController.dispose(); _overlayVisible.removeListener(_onOverlayVisibleChange); - _videoControllers.forEach((kv) => kv.item2.dispose()); - _videoControllers.clear(); - _multiPageControllers.forEach((kv) => kv.item2.dispose()); - _multiPageControllers.clear(); _verticalPager.removeListener(_onVerticalPageControllerChange); WidgetsBinding.instance.removeObserver(this); _unregisterWidget(widget); @@ -198,8 +194,6 @@ class _EntryViewerStackState extends State<EntryViewerStack> with SingleTickerPr ViewerVerticalPageView( collection: collection, entryNotifier: _entryNotifier, - videoControllers: _videoControllers, - multiPageControllers: _multiPageControllers, verticalPager: _verticalPager, horizontalPager: _horizontalPager, onVerticalPageChanged: _onVerticalPageChanged, @@ -226,21 +220,31 @@ class _EntryViewerStackState extends State<EntryViewerStack> with SingleTickerPr Widget _buildTopOverlay() { final child = ValueListenableBuilder<AvesEntry>( valueListenable: _entryNotifier, - builder: (context, entry, child) { - if (entry == null) return SizedBox.shrink(); + builder: (context, mainEntry, child) { + if (mainEntry == null) return SizedBox.shrink(); - final multiPageController = _getMultiPageController(entry); - - final viewStateNotifier = _viewStateNotifiers.firstWhere((kv) => kv.item1 == entry.uri, orElse: () => null)?.item2; + final viewStateNotifier = _viewStateNotifiers.firstWhere((kv) => kv.item1 == mainEntry.uri, orElse: () => null)?.item2; return ViewerTopOverlay( - entry: entry, + mainEntry: mainEntry, scale: _topOverlayScale, canToggleFavourite: hasCollection, viewInsets: _frozenViewInsets, viewPadding: _frozenViewPadding, - onActionSelected: (action) => _actionDelegate.onActionSelected(context, entry, action), + onActionSelected: (action) { + var targetEntry = mainEntry; + if (mainEntry.isMultiPage && EntryActions.pageActions.contains(action)) { + final multiPageController = context.read<MultiPageConductor>().getController(mainEntry); + if (multiPageController != null) { + final multiPageInfo = multiPageController.info; + final pageEntry = multiPageInfo?.getPageEntryByIndex(multiPageController.page); + if (pageEntry != null) { + targetEntry = pageEntry; + } + } + } + _actionDelegate.onActionSelected(context, targetEntry, action); + }, viewStateNotifier: viewStateNotifier, - multiPageController: multiPageController, ); }, ); @@ -262,25 +266,43 @@ class _EntryViewerStackState extends State<EntryViewerStack> with SingleTickerPr builder: (context, entry, child) { if (entry == null) return SizedBox.shrink(); - final multiPageController = _getMultiPageController(entry); - - Widget extraBottomOverlay; - if (entry.isVideo) { - final videoController = _videoControllers.firstWhere((kv) => kv.item1 == entry.uri, orElse: () => null)?.item2; - if (videoController != null) { - extraBottomOverlay = VideoControlOverlay( - entry: entry, - controller: videoController, + Widget _buildExtraBottomOverlay(AvesEntry pageEntry) { + // a 360 video is both a video and a panorama but only the video controls are displayed + if (pageEntry.isVideo) { + return Selector<VideoConductor, AvesVideoController>( + selector: (context, vc) => vc.getController(pageEntry), + builder: (context, videoController, child) => VideoControlOverlay( + entry: pageEntry, + controller: videoController, + scale: _bottomOverlayScale, + ), + ); + } else if (pageEntry.is360) { + return PanoramaOverlay( + entry: pageEntry, scale: _bottomOverlayScale, ); } - } else if (entry.is360) { - extraBottomOverlay = PanoramaOverlay( - entry: entry, - scale: _bottomOverlayScale, - ); + return null; } + final multiPageController = entry.isMultiPage ? context.read<MultiPageConductor>().getController(entry) : null; + final extraBottomOverlay = multiPageController != null + ? StreamBuilder<MultiPageInfo>( + stream: multiPageController.infoStream, + builder: (context, snapshot) { + final multiPageInfo = multiPageController.info; + if (multiPageInfo == null) return SizedBox.shrink(); + return ValueListenableBuilder<int>( + valueListenable: multiPageController.pageNotifier, + builder: (context, page, child) { + final pageEntry = multiPageInfo.getPageEntryByIndex(page); + return _buildExtraBottomOverlay(pageEntry) ?? SizedBox(); + }, + ); + }) + : _buildExtraBottomOverlay(entry); + final child = Column( children: [ if (extraBottomOverlay != null) @@ -335,10 +357,6 @@ class _EntryViewerStackState extends State<EntryViewerStack> with SingleTickerPr return bottomOverlay; } - MultiPageController _getMultiPageController(AvesEntry entry) { - return entry.isMultipage ? _multiPageControllers.firstWhere((kv) => kv.item1 == entry.uri, orElse: () => null)?.item2 : null; - } - void _onVerticalPageControllerChange() { _verticalScrollNotifier.notifyListeners(); } @@ -405,7 +423,7 @@ class _EntryViewerStackState extends State<EntryViewerStack> with SingleTickerPr } } - void _updateEntry() { + Future<void> _updateEntry() async { if (_currentHorizontalPage != null && entries.isNotEmpty && _currentHorizontalPage >= entries.length) { // as of Flutter v1.22.2, `PageView` does not call `onPageChanged` when the last page is deleted // so we manually track the page change, and let the entry update follow @@ -416,8 +434,8 @@ class _EntryViewerStackState extends State<EntryViewerStack> with SingleTickerPr final newEntry = _currentHorizontalPage != null && _currentHorizontalPage < entries.length ? entries[_currentHorizontalPage] : null; if (_entryNotifier.value == newEntry) return; _entryNotifier.value = newEntry; - _pauseVideoControllers(); - _initViewStateControllers(); + await _pauseVideoControllers(); + await _initEntryControllers(); } void _popVisual() { @@ -494,68 +512,92 @@ class _EntryViewerStackState extends State<EntryViewerStack> with SingleTickerPr // state controllers/monitors - void _initViewStateControllers() { + Future<void> _initEntryControllers() async { final entry = _entryNotifier.value; if (entry == null) return; - final uri = entry.uri; - _initViewSpecificController<ValueNotifier<ViewState>>( - uri, - _viewStateNotifiers, - () => ValueNotifier<ViewState>(ViewState.zero), - (_) => _.dispose(), - ); + _initViewStateController(entry); if (entry.isVideo) { - _initViewSpecificController<AvesVideoController>( - uri, - _videoControllers, - () => IjkPlayerAvesVideoController(entry), - (_) => _.dispose(), - ); - if (settings.enableVideoAutoPlay) { - _playVideo(); - } + await _initVideoController(entry); } - if (entry.isMultipage) { - _initViewSpecificController<MultiPageController>( - uri, - _multiPageControllers, - () => MultiPageController(entry), - (_) => _.dispose(), - ); - } - - setState(() {}); - } - - Future<void> _playVideo() async { - await Future.delayed(Duration(milliseconds: 300)); - - final entry = _entryNotifier.value; - if (entry == null) return; - - final videoController = _videoControllers.firstWhere((kv) => kv.item1 == entry.uri, orElse: () => null)?.item2; - if (videoController != null) { - if (videoController.isPlayable) { - await videoController.play(); - } else { - await videoController.setDataSource(entry.uri); - } + if (entry.isMultiPage) { + await _initMultiPageController(entry); } } - void _initViewSpecificController<T>(String uri, List<Tuple2<String, T>> controllers, T Function() builder, void Function(T controller) disposer) { - var controller = controllers.firstWhere((kv) => kv.item1 == uri, orElse: () => null); + void _initViewStateController(AvesEntry entry) { + final uri = entry.uri; + var controller = _viewStateNotifiers.firstWhere((kv) => kv.item1 == uri, orElse: () => null); if (controller != null) { - controllers.remove(controller); + _viewStateNotifiers.remove(controller); } else { - controller = Tuple2(uri, builder()); + controller = Tuple2(uri, ValueNotifier<ViewState>(ViewState.zero)); } - controllers.insert(0, controller); - while (controllers.length > 3) { - disposer?.call(controllers.removeLast().item2); + _viewStateNotifiers.insert(0, controller); + while (_viewStateNotifiers.length > 3) { + _viewStateNotifiers.removeLast().item2.dispose(); } } - void _pauseVideoControllers() => _videoControllers.forEach((e) => e.item2.pause()); + Future<void> _initVideoController(AvesEntry entry) async { + final controller = context.read<VideoConductor>().getOrCreateController(entry); + setState(() {}); + + if (settings.enableVideoAutoPlay) { + await _playVideo(controller, () => entry == _entryNotifier.value); + } + } + + Future<void> _initMultiPageController(AvesEntry entry) async { + final multiPageController = context.read<MultiPageConductor>().getOrCreateController(entry); + setState(() {}); + + final multiPageInfo = multiPageController.info ?? await multiPageController.infoStream.first; + if (entry.isMotionPhoto) { + await multiPageInfo.extractMotionPhotoVideo(); + } + + final videoPageEntries = multiPageInfo.videoPageEntries; + if (videoPageEntries.isNotEmpty) { + // init video controllers for all pages that could need it + final videoConductor = context.read<VideoConductor>(); + videoPageEntries.forEach(videoConductor.getOrCreateController); + + // auto play/pause when changing page + Future<void> _onPageChange() async { + await _pauseVideoControllers(); + if (settings.enableVideoAutoPlay) { + final page = multiPageController.page; + final pageInfo = multiPageInfo.getByIndex(page); + if (pageInfo.isVideo) { + final pageEntry = multiPageInfo.getPageEntryByIndex(page); + final pageVideoController = videoConductor.getController(pageEntry); + assert(pageVideoController != null); + await _playVideo(pageVideoController, () => entry == _entryNotifier.value && page == multiPageController.page); + } + } + } + + multiPageController.pageNotifier.addListener(_onPageChange); + await _onPageChange(); + } + } + + Future<void> _playVideo(AvesVideoController videoController, bool Function() isCurrent) async { + // video decoding may fail or have initial artifacts when the player initializes + // during this widget initialization (because of the page transition and hero animation?) + // so we play after a delay for increased stability + await Future.delayed(Duration(milliseconds: 300) * timeDilation); + + await videoController.play(); + + // playing controllers are paused when the entry changes, + // but the controller may still be preparing (not yet playing) when this happens + // so we make sure the current entry is still the same to keep playing + if (!isCurrent()) { + await videoController.pause(); + } + } + + Future<void> _pauseVideoControllers() => context.read<VideoConductor>().pauseAll(); } diff --git a/lib/widgets/viewer/info/basic_section.dart b/lib/widgets/viewer/info/basic_section.dart index 56dc53e97..cae16dfa1 100644 --- a/lib/widgets/viewer/info/basic_section.dart +++ b/lib/widgets/viewer/info/basic_section.dart @@ -79,6 +79,7 @@ class BasicSection extends StatelessWidget { MimeFilter(entry.mimeType), if (entry.isAnimated) TypeFilter.animated, if (entry.isGeotiff) TypeFilter.geotiff, + if (entry.isMotionPhoto) TypeFilter.motionPhoto, if (entry.isImage && entry.is360) TypeFilter.panorama, if (entry.isVideo && entry.is360) TypeFilter.sphericalVideo, if (entry.isVideo && !entry.is360) MimeFilter.video, diff --git a/lib/widgets/viewer/info/maps/leaflet_map.dart b/lib/widgets/viewer/info/maps/leaflet_map.dart index 631678837..7780b1475 100644 --- a/lib/widgets/viewer/info/maps/leaflet_map.dart +++ b/lib/widgets/viewer/info/maps/leaflet_map.dart @@ -7,7 +7,7 @@ import 'package:aves/widgets/viewer/info/maps/scale_layer.dart'; import 'package:flutter/material.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; -import 'package:latlong/latlong.dart'; +import 'package:latlong2/latlong.dart'; import 'package:provider/provider.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -71,7 +71,7 @@ class _EntryLeafletMapState extends State<EntryLeafletMap> with AutomaticKeepAli options: MapOptions( center: widget.latLng, zoom: widget.initialZoom, - interactive: false, + interactiveFlags: InteractiveFlag.none, ), mapController: _mapController, children: [ diff --git a/lib/widgets/viewer/info/maps/scale_layer.dart b/lib/widgets/viewer/info/maps/scale_layer.dart index 0c9aa1360..0a216c00e 100644 --- a/lib/widgets/viewer/info/maps/scale_layer.dart +++ b/lib/widgets/viewer/info/maps/scale_layer.dart @@ -31,8 +31,8 @@ class ScaleLayerWidget extends StatelessWidget { @override Widget build(BuildContext context) { - final mapState = MapState.of(context); - return ScaleLayer(options, mapState, mapState.onMoved); + final mapState = MapState.maybeOf(context); + return mapState != null ? ScaleLayer(options, mapState, mapState.onMoved) : SizedBox(); } } diff --git a/lib/widgets/viewer/info/maps/scalebar_utils.dart b/lib/widgets/viewer/info/maps/scalebar_utils.dart index 5503c6b61..8a3df22c7 100644 --- a/lib/widgets/viewer/info/maps/scalebar_utils.dart +++ b/lib/widgets/viewer/info/maps/scalebar_utils.dart @@ -1,7 +1,7 @@ import 'dart:math'; import 'package:aves/utils/math_utils.dart'; -import 'package:latlong/latlong.dart'; +import 'package:latlong2/latlong.dart'; LatLng calculateEndingGlobalCoordinates(LatLng start, double startBearing, double distance) { var mSemiMajorAxis = 6378137.0; //WGS84 major axis diff --git a/lib/widgets/viewer/info/metadata/metadata_dir_tile.dart b/lib/widgets/viewer/info/metadata/metadata_dir_tile.dart index 5e76ff780..078a39143 100644 --- a/lib/widgets/viewer/info/metadata/metadata_dir_tile.dart +++ b/lib/widgets/viewer/info/metadata/metadata_dir_tile.dart @@ -121,11 +121,14 @@ class MetadataDirTile extends StatelessWidget with FeedbackMixin { Future<void> _openEmbeddedData(BuildContext context, OpenEmbeddedDataNotification notification) async { Map fields; switch (notification.source) { + case EmbeddedDataSource.motionPhotoVideo: + fields = await embeddedDataService.extractMotionPhotoVideo(entry); + break; case EmbeddedDataSource.videoCover: - fields = await metadataService.extractVideoEmbeddedPicture(entry.uri); + fields = await embeddedDataService.extractVideoEmbeddedPicture(entry); break; case EmbeddedDataSource.xmp: - fields = await metadataService.extractXmpDataProp(entry, notification.propPath, notification.mimeType); + fields = await embeddedDataService.extractXmpDataProp(entry, notification.propPath, notification.mimeType); break; } if (fields == null || !fields.containsKey('mimeType') || !fields.containsKey('uri')) { diff --git a/lib/widgets/viewer/info/metadata/metadata_section.dart b/lib/widgets/viewer/info/metadata/metadata_section.dart index 1fa504c1e..307a05343 100644 --- a/lib/widgets/viewer/info/metadata/metadata_section.dart +++ b/lib/widgets/viewer/info/metadata/metadata_section.dart @@ -158,7 +158,7 @@ class _MetadataSectionSliverState extends State<MetadataSectionSliver> with Auto return MetadataDirectory(directoryName, parent, _toSortedTags(rawTags)); }).toList(); - if (entry.isVideo || (entry.mimeType == MimeTypes.heif && entry.isMultipage)) { + if (entry.isVideo || (entry.mimeType == MimeTypes.heif && entry.isMultiPage)) { directories.addAll(await _getStreamDirectories()); } @@ -193,6 +193,8 @@ class _MetadataSectionSliverState extends State<MetadataSectionSliver> with Auto String getTypeText(Map stream) { final type = stream[Keys.streamType] ?? StreamTypes.unknown; switch (type) { + case StreamTypes.attachment: + return 'Attachment'; case StreamTypes.audio: return 'Audio'; case StreamTypes.metadata: @@ -209,8 +211,8 @@ class _MetadataSectionSliverState extends State<MetadataSectionSliver> with Auto } final allStreams = (mediaInfo[Keys.streams] as List).cast<Map>(); - final unknownStreams = allStreams.where((stream) => stream[Keys.streamType] == StreamTypes.unknown).toList(); - final knownStreams = allStreams.whereNot(unknownStreams.contains); + final attachmentStreams = allStreams.where((stream) => stream[Keys.streamType] == StreamTypes.attachment).toList(); + final knownStreams = allStreams.whereNot(attachmentStreams.contains); // display known streams as separate directories (e.g. video, audio, subs) if (knownStreams.isNotEmpty) { @@ -228,18 +230,18 @@ class _MetadataSectionSliverState extends State<MetadataSectionSliver> with Auto } } - // display unknown streams as attachments (e.g. fonts) - if (unknownStreams.isNotEmpty) { - final unknownCodecCount = <String, List<String>>{}; - for (final stream in unknownStreams) { + // group attachments by format (e.g. TTF fonts) + if (attachmentStreams.isNotEmpty) { + final formatCount = <String, List<String>>{}; + for (final stream in attachmentStreams) { final codec = (stream[Keys.codecName] as String ?? 'unknown').toUpperCase(); - if (!unknownCodecCount.containsKey(codec)) { - unknownCodecCount[codec] = []; + if (!formatCount.containsKey(codec)) { + formatCount[codec] = []; } - unknownCodecCount[codec].add(stream[Keys.filename]); + formatCount[codec].add(stream[Keys.filename]); } - if (unknownCodecCount.isNotEmpty) { - final rawTags = unknownCodecCount.map((key, value) { + if (formatCount.isNotEmpty) { + final rawTags = formatCount.map((key, value) { final count = value.length; // remove duplicate names, so number of displayed names may not match displayed count final names = value.where((v) => v != null).toSet().toList()..sort(compareAsciiUpperCase); diff --git a/lib/widgets/viewer/info/metadata/metadata_thumbnail.dart b/lib/widgets/viewer/info/metadata/metadata_thumbnail.dart index 170601146..3c2c55005 100644 --- a/lib/widgets/viewer/info/metadata/metadata_thumbnail.dart +++ b/lib/widgets/viewer/info/metadata/metadata_thumbnail.dart @@ -28,7 +28,7 @@ class _MetadataThumbnailsState extends State<MetadataThumbnails> { @override void initState() { super.initState(); - _loader = metadataService.getExifThumbnails(entry); + _loader = embeddedDataService.getExifThumbnails(entry); } @override diff --git a/lib/widgets/viewer/info/metadata/xmp_namespaces.dart b/lib/widgets/viewer/info/metadata/xmp_namespaces.dart index 7ee8a308f..613bd66ff 100644 --- a/lib/widgets/viewer/info/metadata/xmp_namespaces.dart +++ b/lib/widgets/viewer/info/metadata/xmp_namespaces.dart @@ -4,21 +4,61 @@ import 'package:aves/utils/constants.dart'; import 'package:aves/utils/string_utils.dart'; import 'package:aves/widgets/common/identity/highlight_title.dart'; import 'package:aves/widgets/viewer/info/common.dart'; +import 'package:aves/widgets/viewer/info/metadata/xmp_ns/exif.dart'; +import 'package:aves/widgets/viewer/info/metadata/xmp_ns/google.dart'; +import 'package:aves/widgets/viewer/info/metadata/xmp_ns/iptc.dart'; +import 'package:aves/widgets/viewer/info/metadata/xmp_ns/mwg.dart'; +import 'package:aves/widgets/viewer/info/metadata/xmp_ns/photoshop.dart'; +import 'package:aves/widgets/viewer/info/metadata/xmp_ns/tiff.dart'; +import 'package:aves/widgets/viewer/info/metadata/xmp_ns/xmp.dart'; import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; class XmpNamespace { final String namespace; + final Map<String, String> rawProps; - const XmpNamespace(this.namespace); + const XmpNamespace(this.namespace, this.rawProps); + + factory XmpNamespace.create(String namespace, Map<String, String> rawProps) { + switch (namespace) { + case XmpBasicNamespace.ns: + return XmpBasicNamespace(rawProps); + case XmpExifNamespace.ns: + return XmpExifNamespace(rawProps); + case XmpGAudioNamespace.ns: + return XmpGAudioNamespace(rawProps); + case XmpGCameraNamespace.ns: + return XmpGCameraNamespace(rawProps); + case XmpGDepthNamespace.ns: + return XmpGDepthNamespace(rawProps); + case XmpGImageNamespace.ns: + return XmpGImageNamespace(rawProps); + case XmpIptcCoreNamespace.ns: + return XmpIptcCoreNamespace(rawProps); + case XmpMgwRegionsNamespace.ns: + return XmpMgwRegionsNamespace(rawProps); + case XmpMMNamespace.ns: + return XmpMMNamespace(rawProps); + case XmpNoteNamespace.ns: + return XmpNoteNamespace(rawProps); + case XmpPhotoshopNamespace.ns: + return XmpPhotoshopNamespace(rawProps); + case XmpTiffNamespace.ns: + return XmpTiffNamespace(rawProps); + default: + return XmpNamespace(namespace, rawProps); + } + } String get displayTitle => XMP.namespaces[namespace] ?? namespace; - List<Widget> buildNamespaceSection({ - @required List<MapEntry<String, String>> rawProps, - }) { - final props = rawProps + Map<String, String> get buildProps => rawProps; + + List<Widget> buildNamespaceSection() { + final props = buildProps + .entries .map((kv) { final prop = XmpProp(kv.key, kv.value); return extractData(prop) ? null : prop; diff --git a/lib/widgets/viewer/info/metadata/xmp_ns/exif.dart b/lib/widgets/viewer/info/metadata/xmp_ns/exif.dart index 76ac8c865..4ec4f1cee 100644 --- a/lib/widgets/viewer/info/metadata/xmp_ns/exif.dart +++ b/lib/widgets/viewer/info/metadata/xmp_ns/exif.dart @@ -5,7 +5,7 @@ import 'package:aves/widgets/viewer/info/metadata/xmp_namespaces.dart'; class XmpExifNamespace extends XmpNamespace { static const ns = 'exif'; - XmpExifNamespace() : super(ns); + XmpExifNamespace(Map<String, String> rawProps) : super(ns, rawProps); @override String get displayTitle => 'Exif'; diff --git a/lib/widgets/viewer/info/metadata/xmp_ns/google.dart b/lib/widgets/viewer/info/metadata/xmp_ns/google.dart index 1e7e82808..24db7a4da 100644 --- a/lib/widgets/viewer/info/metadata/xmp_ns/google.dart +++ b/lib/widgets/viewer/info/metadata/xmp_ns/google.dart @@ -5,7 +5,7 @@ import 'package:aves/widgets/viewer/info/notifications.dart'; import 'package:tuple/tuple.dart'; abstract class XmpGoogleNamespace extends XmpNamespace { - XmpGoogleNamespace(String ns) : super(ns); + XmpGoogleNamespace(String ns, Map<String, String> rawProps) : super(ns, rawProps); List<Tuple2<String, String>> get dataProps; @@ -34,7 +34,7 @@ abstract class XmpGoogleNamespace extends XmpNamespace { class XmpGAudioNamespace extends XmpGoogleNamespace { static const ns = 'GAudio'; - XmpGAudioNamespace() : super(ns); + XmpGAudioNamespace(Map<String, String> rawProps) : super(ns, rawProps); @override List<Tuple2<String, String>> get dataProps => [Tuple2('$ns:Data', '$ns:Mime')]; @@ -46,7 +46,7 @@ class XmpGAudioNamespace extends XmpGoogleNamespace { class XmpGDepthNamespace extends XmpGoogleNamespace { static const ns = 'GDepth'; - XmpGDepthNamespace() : super(ns); + XmpGDepthNamespace(Map<String, String> rawProps) : super(ns, rawProps); @override List<Tuple2<String, String>> get dataProps => [ @@ -61,7 +61,7 @@ class XmpGDepthNamespace extends XmpGoogleNamespace { class XmpGImageNamespace extends XmpGoogleNamespace { static const ns = 'GImage'; - XmpGImageNamespace() : super(ns); + XmpGImageNamespace(Map<String, String> rawProps) : super(ns, rawProps); @override List<Tuple2<String, String>> get dataProps => [Tuple2('$ns:Data', '$ns:Mime')]; @@ -69,3 +69,35 @@ class XmpGImageNamespace extends XmpGoogleNamespace { @override String get displayTitle => 'Google Image'; } + +class XmpGCameraNamespace extends XmpNamespace { + static const ns = 'GCamera'; + static const videoOffsetKey = 'GCamera:MicroVideoOffset'; + static const videoDataKey = 'Data'; + + bool _isMotionPhoto; + + XmpGCameraNamespace(Map<String, String> rawProps) : super(ns, rawProps) { + _isMotionPhoto = rawProps.keys.any((key) => key == videoOffsetKey); + } + + @override + Map<String, String> get buildProps { + return _isMotionPhoto + ? Map.fromEntries({ + MapEntry(videoDataKey, '[skipped]'), + ...rawProps.entries, + }) + : rawProps; + } + + @override + Map<String, InfoLinkHandler> linkifyValues(List<XmpProp> props) { + return { + videoDataKey: InfoLinkHandler( + linkText: (context) => context.l10n.viewerInfoOpenLinkText, + onTap: (context) => OpenEmbeddedDataNotification.motionPhotoVideo().dispatch(context), + ), + }; + } +} diff --git a/lib/widgets/viewer/info/metadata/xmp_ns/iptc.dart b/lib/widgets/viewer/info/metadata/xmp_ns/iptc.dart index 7ccd3feaf..50ddf0385 100644 --- a/lib/widgets/viewer/info/metadata/xmp_ns/iptc.dart +++ b/lib/widgets/viewer/info/metadata/xmp_ns/iptc.dart @@ -9,7 +9,7 @@ class XmpIptcCoreNamespace extends XmpNamespace { final creatorContactInfo = <String, String>{}; - XmpIptcCoreNamespace() : super(ns); + XmpIptcCoreNamespace(Map<String, String> rawProps) : super(ns, rawProps); @override String get displayTitle => 'IPTC Core'; diff --git a/lib/widgets/viewer/info/metadata/xmp_ns/mwg.dart b/lib/widgets/viewer/info/metadata/xmp_ns/mwg.dart index 321452173..8516dde69 100644 --- a/lib/widgets/viewer/info/metadata/xmp_ns/mwg.dart +++ b/lib/widgets/viewer/info/metadata/xmp_ns/mwg.dart @@ -12,7 +12,7 @@ class XmpMgwRegionsNamespace extends XmpNamespace { final dimensions = <String, String>{}; final regionList = <int, Map<String, String>>{}; - XmpMgwRegionsNamespace() : super(ns); + XmpMgwRegionsNamespace(Map<String, String> rawProps) : super(ns, rawProps); @override String get displayTitle => 'Regions'; diff --git a/lib/widgets/viewer/info/metadata/xmp_ns/photoshop.dart b/lib/widgets/viewer/info/metadata/xmp_ns/photoshop.dart index 0b0399bab..73897fe13 100644 --- a/lib/widgets/viewer/info/metadata/xmp_ns/photoshop.dart +++ b/lib/widgets/viewer/info/metadata/xmp_ns/photoshop.dart @@ -5,7 +5,7 @@ import 'package:aves/widgets/viewer/info/metadata/xmp_namespaces.dart'; class XmpPhotoshopNamespace extends XmpNamespace { static const ns = 'photoshop'; - XmpPhotoshopNamespace() : super(ns); + XmpPhotoshopNamespace(Map<String, String> rawProps) : super(ns, rawProps); @override String get displayTitle => 'Photoshop'; diff --git a/lib/widgets/viewer/info/metadata/xmp_ns/tiff.dart b/lib/widgets/viewer/info/metadata/xmp_ns/tiff.dart index fbf712f6e..5c901413f 100644 --- a/lib/widgets/viewer/info/metadata/xmp_ns/tiff.dart +++ b/lib/widgets/viewer/info/metadata/xmp_ns/tiff.dart @@ -8,7 +8,7 @@ class XmpTiffNamespace extends XmpNamespace { @override String get displayTitle => 'TIFF'; - XmpTiffNamespace() : super(ns); + XmpTiffNamespace(Map<String, String> rawProps) : super(ns, rawProps); @override String formatValue(XmpProp prop) { diff --git a/lib/widgets/viewer/info/metadata/xmp_ns/xmp.dart b/lib/widgets/viewer/info/metadata/xmp_ns/xmp.dart index ce6459333..d39e5c1d4 100644 --- a/lib/widgets/viewer/info/metadata/xmp_ns/xmp.dart +++ b/lib/widgets/viewer/info/metadata/xmp_ns/xmp.dart @@ -14,7 +14,7 @@ class XmpBasicNamespace extends XmpNamespace { final thumbnails = <int, Map<String, String>>{}; - XmpBasicNamespace() : super(ns); + XmpBasicNamespace(Map<String, String> rawProps) : super(ns, rawProps); @override String get displayTitle => 'Basic'; @@ -61,7 +61,7 @@ class XmpMMNamespace extends XmpNamespace { final ingredients = <int, Map<String, String>>{}; final pantry = <int, Map<String, String>>{}; - XmpMMNamespace() : super(ns); + XmpMMNamespace(Map<String, String> rawProps) : super(ns, rawProps); @override String get displayTitle => 'Media Management'; @@ -114,7 +114,7 @@ class XmpNoteNamespace extends XmpNamespace { // `xmpNote:HasExtendedXMP` is structural and should not be displayed to users static const hasExtendedXmp = '$ns:HasExtendedXMP'; - XmpNoteNamespace() : super(ns); + XmpNoteNamespace(Map<String, String> rawProps) : super(ns, rawProps); @override bool extractData(XmpProp prop) { diff --git a/lib/widgets/viewer/info/metadata/xmp_tile.dart b/lib/widgets/viewer/info/metadata/xmp_tile.dart index ef9383dfe..1663aac44 100644 --- a/lib/widgets/viewer/info/metadata/xmp_tile.dart +++ b/lib/widgets/viewer/info/metadata/xmp_tile.dart @@ -4,13 +4,6 @@ import 'package:aves/model/entry.dart'; import 'package:aves/ref/xmp.dart'; import 'package:aves/widgets/common/identity/aves_expansion_tile.dart'; import 'package:aves/widgets/viewer/info/metadata/xmp_namespaces.dart'; -import 'package:aves/widgets/viewer/info/metadata/xmp_ns/exif.dart'; -import 'package:aves/widgets/viewer/info/metadata/xmp_ns/google.dart'; -import 'package:aves/widgets/viewer/info/metadata/xmp_ns/iptc.dart'; -import 'package:aves/widgets/viewer/info/metadata/xmp_ns/mwg.dart'; -import 'package:aves/widgets/viewer/info/metadata/xmp_ns/photoshop.dart'; -import 'package:aves/widgets/viewer/info/metadata/xmp_ns/tiff.dart'; -import 'package:aves/widgets/viewer/info/metadata/xmp_ns/xmp.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; @@ -36,40 +29,13 @@ class _XmpDirTileState extends State<XmpDirTile> { @override Widget build(BuildContext context) { - final sections = SplayTreeMap<XmpNamespace, List<MapEntry<String, String>>>.of( - groupBy(widget.tags.entries, (kv) { - final fullKey = kv.key; - final i = fullKey.indexOf(XMP.propNamespaceSeparator); - final namespace = i == -1 ? '' : fullKey.substring(0, i); - switch (namespace) { - case XmpBasicNamespace.ns: - return XmpBasicNamespace(); - case XmpExifNamespace.ns: - return XmpExifNamespace(); - case XmpGAudioNamespace.ns: - return XmpGAudioNamespace(); - case XmpGDepthNamespace.ns: - return XmpGDepthNamespace(); - case XmpGImageNamespace.ns: - return XmpGImageNamespace(); - case XmpIptcCoreNamespace.ns: - return XmpIptcCoreNamespace(); - case XmpMgwRegionsNamespace.ns: - return XmpMgwRegionsNamespace(); - case XmpMMNamespace.ns: - return XmpMMNamespace(); - case XmpNoteNamespace.ns: - return XmpNoteNamespace(); - case XmpPhotoshopNamespace.ns: - return XmpPhotoshopNamespace(); - case XmpTiffNamespace.ns: - return XmpTiffNamespace(); - default: - return XmpNamespace(namespace); - } - }), - (a, b) => compareAsciiUpperCase(a.displayTitle, b.displayTitle), - ); + final sections = groupBy(widget.tags.entries, (kv) { + final fullKey = kv.key; + final i = fullKey.indexOf(XMP.propNamespaceSeparator); + final namespace = i == -1 ? '' : fullKey.substring(0, i); + return namespace; + }).entries.map((kv) => XmpNamespace.create(kv.key, Map.fromEntries(kv.value))).toList() + ..sort((a, b) => compareAsciiUpperCase(a.displayTitle, b.displayTitle)); return AvesExpansionTile( title: 'XMP', expandedNotifier: widget.expandedNotifier, @@ -79,11 +45,7 @@ class _XmpDirTileState extends State<XmpDirTile> { padding: EdgeInsets.only(left: 8, right: 8, bottom: 8), child: Column( crossAxisAlignment: CrossAxisAlignment.start, - children: sections.entries - .expand((kv) => kv.key.buildNamespaceSection( - rawProps: kv.value, - )) - .toList(), + children: sections.expand((section) => section.buildNamespaceSection()).toList(), ), ), ], diff --git a/lib/widgets/viewer/info/notifications.dart b/lib/widgets/viewer/info/notifications.dart index fffebe903..c5d23c7a3 100644 --- a/lib/widgets/viewer/info/notifications.dart +++ b/lib/widgets/viewer/info/notifications.dart @@ -28,7 +28,7 @@ class OpenTempEntryNotification extends Notification { String toString() => '$runtimeType#${shortHash(this)}{entry=$entry}'; } -enum EmbeddedDataSource { videoCover, xmp } +enum EmbeddedDataSource { motionPhotoVideo, videoCover, xmp } class OpenEmbeddedDataNotification extends Notification { final EmbeddedDataSource source; @@ -41,6 +41,10 @@ class OpenEmbeddedDataNotification extends Notification { this.mimeType, }); + factory OpenEmbeddedDataNotification.motionPhotoVideo() => OpenEmbeddedDataNotification._private( + source: EmbeddedDataSource.motionPhotoVideo, + ); + factory OpenEmbeddedDataNotification.videoCover() => OpenEmbeddedDataNotification._private( source: EmbeddedDataSource.videoCover, ); diff --git a/lib/widgets/viewer/multipage.dart b/lib/widgets/viewer/multipage.dart deleted file mode 100644 index c599f09de..000000000 --- a/lib/widgets/viewer/multipage.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'dart:async'; - -import 'package:aves/model/entry.dart'; -import 'package:aves/model/multipage.dart'; -import 'package:aves/services/services.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; - -class MultiPageController extends ChangeNotifier { - Future<MultiPageInfo> info; - final ValueNotifier<int> pageNotifier = ValueNotifier(null); - - MultiPageController(AvesEntry entry) { - info = metadataService.getMultiPageInfo(entry).then((value) { - pageNotifier.value = value.defaultPage.index; - return value; - }); - } - - int get page => pageNotifier.value; - - set page(int page) => pageNotifier.value = page; - - @override - void dispose() { - pageNotifier.dispose(); - super.dispose(); - } -} diff --git a/lib/widgets/viewer/multipage/conductor.dart b/lib/widgets/viewer/multipage/conductor.dart new file mode 100644 index 000000000..709c3a711 --- /dev/null +++ b/lib/widgets/viewer/multipage/conductor.dart @@ -0,0 +1,31 @@ +import 'package:aves/model/entry.dart'; +import 'package:aves/widgets/viewer/multipage/controller.dart'; + +class MultiPageConductor { + final List<MultiPageController> _controllers = []; + + static const maxControllerCount = 3; + + Future<void> dispose() async { + await Future.forEach(_controllers, (controller) => controller.dispose()); + _controllers.clear(); + } + + MultiPageController getOrCreateController(AvesEntry entry) { + var controller = getController(entry); + if (controller != null) { + _controllers.remove(controller); + } else { + controller = MultiPageController(entry); + } + _controllers.insert(0, controller); + while (_controllers.length > maxControllerCount) { + _controllers.removeLast().dispose(); + } + return controller; + } + + MultiPageController getController(AvesEntry entry) { + return _controllers.firstWhere((c) => c.entry.uri == entry.uri && c.entry.pageId == entry.pageId, orElse: () => null); + } +} diff --git a/lib/widgets/viewer/multipage/controller.dart b/lib/widgets/viewer/multipage/controller.dart new file mode 100644 index 000000000..00224f8f5 --- /dev/null +++ b/lib/widgets/viewer/multipage/controller.dart @@ -0,0 +1,39 @@ +import 'dart:async'; + +import 'package:aves/model/entry.dart'; +import 'package:aves/model/multipage.dart'; +import 'package:aves/services/services.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +class MultiPageController { + final AvesEntry entry; + final ValueNotifier<int> pageNotifier = ValueNotifier(null); + + MultiPageInfo _info; + + final StreamController<MultiPageInfo> _infoStreamController = StreamController.broadcast(); + + Stream<MultiPageInfo> get infoStream => _infoStreamController.stream; + + MultiPageInfo get info => _info; + + int get page => pageNotifier.value; + + set page(int page) => pageNotifier.value = page; + + MultiPageController(this.entry) { + metadataService.getMultiPageInfo(entry).then((value) { + pageNotifier.value = value.defaultPage.index; + _info = value; + _infoStreamController.add(_info); + }); + } + + void dispose() { + pageNotifier.dispose(); + } + + @override + String toString() => '$runtimeType#${shortHash(this)}{entry=$entry, page=$page, info=$info}'; +} diff --git a/lib/widgets/viewer/overlay/bottom.dart b/lib/widgets/viewer/overlay/bottom/common.dart similarity index 92% rename from lib/widgets/viewer/overlay/bottom.dart rename to lib/widgets/viewer/overlay/bottom/common.dart index a36837652..ae870bd3b 100644 --- a/lib/widgets/viewer/overlay/bottom.dart +++ b/lib/widgets/viewer/overlay/bottom/common.dart @@ -11,9 +11,9 @@ import 'package:aves/theme/icons.dart'; import 'package:aves/utils/constants.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/fx/blurred.dart'; -import 'package:aves/widgets/viewer/multipage.dart'; +import 'package:aves/widgets/viewer/multipage/controller.dart'; +import 'package:aves/widgets/viewer/overlay/bottom/multipage.dart'; import 'package:aves/widgets/viewer/overlay/common.dart'; -import 'package:aves/widgets/viewer/overlay/multipage.dart'; import 'package:decorated_icon/decorated_icon.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; @@ -102,7 +102,7 @@ class _ViewerBottomOverlayState extends State<ViewerBottomOverlay> { Widget _buildContent({MultiPageInfo multiPageInfo, int page}) => _BottomOverlayContent( mainEntry: _lastEntry, - page: multiPageInfo?.getByIndex(page), + pageEntry: multiPageInfo?.getPageEntryByIndex(page) ?? _lastEntry, details: _lastDetails, position: widget.showPosition ? '${widget.index + 1}/${widget.entries.length}' : null, availableWidth: availableWidth, @@ -111,10 +111,10 @@ class _ViewerBottomOverlayState extends State<ViewerBottomOverlay> { if (multiPageController == null) return _buildContent(); - return FutureBuilder<MultiPageInfo>( - future: multiPageController.info, + return StreamBuilder<MultiPageInfo>( + stream: multiPageController.infoStream, builder: (context, snapshot) { - final multiPageInfo = snapshot.data; + final multiPageInfo = multiPageController.info; return ValueListenableBuilder<int>( valueListenable: multiPageController.pageNotifier, builder: (context, page, child) { @@ -138,8 +138,7 @@ const double _interRowPadding = 2.0; const double _subRowMinWidth = 300.0; class _BottomOverlayContent extends AnimatedWidget { - final AvesEntry mainEntry, entry; - final SinglePageInfo page; + final AvesEntry mainEntry, pageEntry; final OverlayMetadata details; final String position; final double availableWidth; @@ -150,13 +149,18 @@ class _BottomOverlayContent extends AnimatedWidget { _BottomOverlayContent({ Key key, this.mainEntry, - this.page, + this.pageEntry, this.details, this.position, this.availableWidth, this.multiPageController, - }) : entry = mainEntry.getPageEntry(page), - super(key: key, listenable: mainEntry.metadataChangeNotifier); + }) : super( + key: key, + listenable: Listenable.merge([ + mainEntry.metadataChangeNotifier, + pageEntry.metadataChangeNotifier, + ]), + ); @override Widget build(BuildContext context) { @@ -178,13 +182,12 @@ class _BottomOverlayContent extends AnimatedWidget { infoColumn = _buildInfoColumn(orientation); } - if (mainEntry.isMultipage && multiPageController != null) { + if (mainEntry.isMultiPage && multiPageController != null) { infoColumn = Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ MultiPageOverlay( - mainEntry: mainEntry, controller: multiPageController, availableWidth: availableWidth, ), @@ -204,7 +207,7 @@ class _BottomOverlayContent extends AnimatedWidget { final infoMaxWidth = availableWidth - infoPadding.horizontal; final twoColumns = orientation == Orientation.landscape && infoMaxWidth / 2 > _subRowMinWidth; final subRowWidth = twoColumns ? min(_subRowMinWidth, infoMaxWidth / 2) : infoMaxWidth; - final positionTitle = _PositionTitleRow(entry: entry, collectionPosition: position, multiPageController: multiPageController); + final positionTitle = _PositionTitleRow(entry: pageEntry, collectionPosition: position, multiPageController: multiPageController); final hasShootingDetails = details != null && !details.isEmpty && settings.showOverlayShootingDetails; return Padding( @@ -223,7 +226,7 @@ class _BottomOverlayContent extends AnimatedWidget { Container( width: subRowWidth, child: _DateRow( - entry: entry, + entry: pageEntry, multiPageController: multiPageController, )), _buildDuoShootingRow(subRowWidth, hasShootingDetails), @@ -235,7 +238,7 @@ class _BottomOverlayContent extends AnimatedWidget { padding: EdgeInsets.only(top: _interRowPadding), width: subRowWidth, child: _DateRow( - entry: entry, + entry: pageEntry, multiPageController: multiPageController, ), ), @@ -251,10 +254,10 @@ class _BottomOverlayContent extends AnimatedWidget { switchInCurve: Curves.easeInOutCubic, switchOutCurve: Curves.easeInOutCubic, transitionBuilder: _soloTransition, - child: entry.hasGps + child: pageEntry.hasGps ? Container( padding: EdgeInsets.only(top: _interRowPadding), - child: _LocationRow(entry: entry), + child: _LocationRow(entry: pageEntry), ) : SizedBox.shrink(), ); @@ -354,10 +357,10 @@ class _PositionTitleRow extends StatelessWidget { if (multiPageController == null) return toText(); - return FutureBuilder<MultiPageInfo>( - future: multiPageController.info, + return StreamBuilder<MultiPageInfo>( + stream: multiPageController.infoStream, builder: (context, snapshot) { - final multiPageInfo = snapshot.data; + final multiPageInfo = multiPageController.info; String pagePosition; if (multiPageInfo != null) { // page count may be 0 when we know an entry to have multiple pages diff --git a/lib/widgets/viewer/overlay/multipage.dart b/lib/widgets/viewer/overlay/bottom/multipage.dart similarity index 71% rename from lib/widgets/viewer/overlay/multipage.dart rename to lib/widgets/viewer/overlay/bottom/multipage.dart index b40adda34..0ac68df62 100644 --- a/lib/widgets/viewer/overlay/multipage.dart +++ b/lib/widgets/viewer/overlay/bottom/multipage.dart @@ -1,26 +1,22 @@ import 'dart:math'; -import 'package:aves/model/entry.dart'; import 'package:aves/model/multipage.dart'; import 'package:aves/theme/durations.dart'; import 'package:aves/widgets/collection/thumbnail/decorated.dart'; import 'package:aves/widgets/collection/thumbnail/theme.dart'; -import 'package:aves/widgets/viewer/multipage.dart'; +import 'package:aves/widgets/viewer/multipage/controller.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; class MultiPageOverlay extends StatefulWidget { - final AvesEntry mainEntry; final MultiPageController controller; final double availableWidth; - MultiPageOverlay({ + const MultiPageOverlay({ Key key, - @required this.mainEntry, @required this.controller, @required this.availableWidth, - }) : assert(mainEntry.isMultipage), - assert(controller != null), + }) : assert(controller != null), super(key: key); @override @@ -31,12 +27,11 @@ class _MultiPageOverlayState extends State<MultiPageOverlay> { final _cancellableNotifier = ValueNotifier(true); ScrollController _scrollController; bool _syncScroll = true; + int _initControllerPage; static const double extent = 48; static const double separatorWidth = 2; - AvesEntry get mainEntry => widget.mainEntry; - MultiPageController get controller => widget.controller; double get availableWidth => widget.availableWidth; @@ -64,10 +59,26 @@ class _MultiPageOverlayState extends State<MultiPageOverlay> { } void _registerWidget() { - final page = controller.page ?? 0; - final scrollOffset = pageToScrollOffset(page); + _initControllerPage = controller.page; + final scrollOffset = pageToScrollOffset(_initControllerPage ?? 0); _scrollController = ScrollController(initialScrollOffset: scrollOffset); _scrollController.addListener(_onScrollChange); + + if (_initControllerPage == null) { + _correctDefaultPageScroll(); + } + } + + // correct scroll offset to match default page + // if default page was unknown when the scroll controller was created + void _correctDefaultPageScroll() async { + await controller.infoStream.first; + if (_initControllerPage == null) { + _initControllerPage = controller.page; + if (_initControllerPage != 0) { + WidgetsBinding.instance.addPostFrameCallback((_) => _goToPage(_initControllerPage)); + } + } } void _unregisterWidget() { @@ -83,39 +94,30 @@ class _MultiPageOverlayState extends State<MultiPageOverlay> { return ThumbnailTheme( extent: extent, - child: FutureBuilder<MultiPageInfo>( - future: controller.info, + showLocation: false, + child: StreamBuilder<MultiPageInfo>( + stream: controller.infoStream, builder: (context, snapshot) { - final multiPageInfo = snapshot.data; - if ((multiPageInfo?.pageCount ?? 0) <= 1) return SizedBox(); - if (multiPageInfo.uri != mainEntry.uri) return SizedBox(); + final multiPageInfo = controller.info; + final pageCount = multiPageInfo?.pageCount ?? 0; return SizedBox( height: extent, child: ListView.separated( - key: ValueKey(mainEntry), + key: ValueKey(multiPageInfo), scrollDirection: Axis.horizontal, controller: _scrollController, // default padding in scroll direction matches `MediaQuery.viewPadding`, // but we already accommodate for it, so make sure horizontal padding is 0 padding: EdgeInsets.zero, itemBuilder: (context, index) { - if (index == 0 || index == multiPageInfo.pageCount + 1) return horizontalMargin; + if (index == 0 || index == pageCount + 1) return horizontalMargin; final page = index - 1; - final pageEntry = mainEntry.getPageEntry(multiPageInfo.getByIndex(page)); + final pageEntry = multiPageInfo.getPageEntryByIndex(page); return Stack( children: [ GestureDetector( - onTap: () async { - _syncScroll = false; - controller.page = page; - await _scrollController.animateTo( - pageToScrollOffset(page), - duration: Durations.viewerOverlayPageScrollAnimation, - curve: Curves.easeOutCubic, - ); - _syncScroll = true; - }, + onTap: () => _goToPage(page), child: DecoratedThumbnail( entry: pageEntry, extent: extent, @@ -139,7 +141,7 @@ class _MultiPageOverlayState extends State<MultiPageOverlay> { ); }, separatorBuilder: (context, index) => separator, - itemCount: multiPageInfo.pageCount + 2, + itemCount: pageCount + 2, ), ); }, @@ -147,6 +149,17 @@ class _MultiPageOverlayState extends State<MultiPageOverlay> { ); } + Future<void> _goToPage(int page) async { + _syncScroll = false; + controller.page = page; + await _scrollController.animateTo( + pageToScrollOffset(page), + duration: Durations.viewerOverlayPageScrollAnimation, + curve: Curves.easeOutCubic, + ); + _syncScroll = true; + } + void _onScrollChange() { if (_syncScroll) { controller.page = scrollOffsetToPage(_scrollController.offset); diff --git a/lib/widgets/viewer/overlay/panorama.dart b/lib/widgets/viewer/overlay/bottom/panorama.dart similarity index 100% rename from lib/widgets/viewer/overlay/panorama.dart rename to lib/widgets/viewer/overlay/bottom/panorama.dart diff --git a/lib/widgets/viewer/overlay/video.dart b/lib/widgets/viewer/overlay/bottom/video.dart similarity index 78% rename from lib/widgets/viewer/overlay/video.dart rename to lib/widgets/viewer/overlay/bottom/video.dart index 6ab8cd8a9..8327a1c74 100644 --- a/lib/widgets/viewer/overlay/video.dart +++ b/lib/widgets/viewer/overlay/bottom/video.dart @@ -8,9 +8,9 @@ import 'package:aves/utils/time_utils.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/fx/blurred.dart'; import 'package:aves/widgets/common/fx/borders.dart'; -import 'package:aves/widgets/common/video/controller.dart'; import 'package:aves/widgets/viewer/overlay/common.dart'; import 'package:aves/widgets/viewer/overlay/notifications.dart'; +import 'package:aves/widgets/viewer/video/controller.dart'; import 'package:flutter/material.dart'; class VideoControlOverlay extends StatefulWidget { @@ -34,7 +34,6 @@ class _VideoControlOverlayState extends State<VideoControlOverlay> with SingleTi bool _playingOnDragStart = false; AnimationController _playPauseAnimation; final List<StreamSubscription> _subscriptions = []; - double _seekTargetPercent; AvesEntry get entry => widget.entry; @@ -42,9 +41,11 @@ class _VideoControlOverlayState extends State<VideoControlOverlay> with SingleTi AvesVideoController get controller => widget.controller; - bool get isPlayable => controller.isPlayable; + Stream<VideoStatus> get statusStream => controller?.statusStream ?? Stream.value(VideoStatus.idle); - bool get isPlaying => controller.isPlaying; + Stream<int> get positionStream => controller?.positionStream ?? Stream.value(0); + + bool get isPlaying => controller?.isPlaying ?? false; @override void initState() { @@ -71,8 +72,10 @@ class _VideoControlOverlayState extends State<VideoControlOverlay> with SingleTi } void _registerWidget(VideoControlOverlay widget) { - _subscriptions.add(widget.controller.statusStream.listen(_onStatusChange)); - _onStatusChange(widget.controller.status); + if (widget.controller != null) { + _subscriptions.add(widget.controller.statusStream.listen(_onStatusChange)); + _onStatusChange(widget.controller.status); + } } void _unregisterWidget(VideoControlOverlay widget) { @@ -84,10 +87,10 @@ class _VideoControlOverlayState extends State<VideoControlOverlay> with SingleTi @override Widget build(BuildContext context) { return StreamBuilder<VideoStatus>( - stream: controller.statusStream, + stream: statusStream, builder: (context, snapshot) { // do not use stream snapshot because it is obsolete when switching between videos - final status = controller.status; + final status = controller?.status ?? VideoStatus.idle; return TooltipTheme( data: TooltipTheme.of(context).copyWith( preferBelow: false, @@ -160,10 +163,10 @@ class _VideoControlOverlayState extends State<VideoControlOverlay> with SingleTi Row( children: [ StreamBuilder<int>( - stream: controller.positionStream, + stream: positionStream, builder: (context, snapshot) { // do not use stream snapshot because it is obsolete when switching between videos - final position = controller.currentPosition?.floor() ?? 0; + final position = controller?.currentPosition?.floor() ?? 0; return Text(formatFriendlyDuration(Duration(milliseconds: position))); }), Spacer(), @@ -173,12 +176,15 @@ class _VideoControlOverlayState extends State<VideoControlOverlay> with SingleTi ClipRRect( borderRadius: BorderRadius.circular(4), child: StreamBuilder<int>( - stream: controller.positionStream, + stream: positionStream, builder: (context, snapshot) { // do not use stream snapshot because it is obsolete when switching between videos - var progress = controller.progress; + var progress = controller?.progress ?? 0.0; if (!progress.isFinite) progress = 0.0; - return LinearProgressIndicator(value: progress); + return LinearProgressIndicator( + value: progress, + backgroundColor: Colors.grey[700], + ); }), ), ], @@ -190,33 +196,6 @@ class _VideoControlOverlayState extends State<VideoControlOverlay> with SingleTi } void _onStatusChange(VideoStatus status) { - if (status == VideoStatus.playing && _seekTargetPercent != null) { - _seekFromTarget(); - } - _updatePlayPauseIcon(); - } - - Future<void> _togglePlayPause() async { - if (isPlaying) { - await controller.pause(); - } else { - await _play(); - } - } - - Future<void> _play() async { - if (isPlayable) { - await controller.play(); - } else { - await controller.setDataSource(entry.uri); - } - - // hide overlay - await Future.delayed(Durations.iconAnimation); - ToggleOverlayNotification().dispatch(context); - } - - void _updatePlayPauseIcon() { final status = _playPauseAnimation.status; if (isPlaying && status != AnimationStatus.forward && status != AnimationStatus.completed) { _playPauseAnimation.forward(); @@ -225,28 +204,23 @@ class _VideoControlOverlayState extends State<VideoControlOverlay> with SingleTi } } + Future<void> _togglePlayPause() async { + if (controller == null) return; + if (isPlaying) { + await controller.pause(); + } else { + await controller.play(); + // hide overlay + await Future.delayed(Durations.iconAnimation); + ToggleOverlayNotification().dispatch(context); + } + } + void _seekFromTap(Offset globalPosition) async { + if (controller == null) return; final keyContext = _progressBarKey.currentContext; final RenderBox box = keyContext.findRenderObject(); final localPosition = box.globalToLocal(globalPosition); - _seekTargetPercent = (localPosition.dx / box.size.width); - - if (isPlayable) { - await _seekFromTarget(); - } else { - // controller duration is not set yet, so we use the expected duration instead - final seekTargetMillis = (entry.durationMillis * _seekTargetPercent).toInt(); - await controller.setDataSource(entry.uri, startMillis: seekTargetMillis); - _seekTargetPercent = null; - } - } - - Future _seekFromTarget() async { - // `seekToProgress` is not safe as it can be called when the `duration` is not set yet - // so we make sure the video info is up to date first - if (controller.duration != null) { - await controller.seekToProgress(_seekTargetPercent); - _seekTargetPercent = null; - } + await controller.seekToProgress(localPosition.dx / box.size.width); } } diff --git a/lib/widgets/viewer/overlay/minimap.dart b/lib/widgets/viewer/overlay/minimap.dart index 3aea62b92..0f1431e39 100644 --- a/lib/widgets/viewer/overlay/minimap.dart +++ b/lib/widgets/viewer/overlay/minimap.dart @@ -1,68 +1,46 @@ import 'dart:math'; import 'package:aves/model/entry.dart'; -import 'package:aves/model/multipage.dart'; -import 'package:aves/widgets/viewer/multipage.dart'; import 'package:aves/widgets/viewer/visual/state.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; class Minimap extends StatelessWidget { - final AvesEntry mainEntry; + final AvesEntry entry; final ValueNotifier<ViewState> viewStateNotifier; - final MultiPageController multiPageController; final Size size; static const defaultSize = Size(96, 96); const Minimap({ - @required this.mainEntry, + @required this.entry, @required this.viewStateNotifier, - @required this.multiPageController, this.size = defaultSize, }); @override Widget build(BuildContext context) { return IgnorePointer( - child: multiPageController != null - ? FutureBuilder<MultiPageInfo>( - future: multiPageController.info, - builder: (context, snapshot) { - final multiPageInfo = snapshot.data; - if (multiPageInfo == null) return SizedBox.shrink(); - return ValueListenableBuilder<int>( - valueListenable: multiPageController.pageNotifier, - builder: (context, page, child) { - final pageEntry = mainEntry.getPageEntry(multiPageInfo?.getByIndex(page)); - return _buildForEntrySize(pageEntry); - }, - ); - }) - : _buildForEntrySize(mainEntry), - ); - } - - Widget _buildForEntrySize(AvesEntry entry) { - return ValueListenableBuilder<ViewState>( - valueListenable: viewStateNotifier, - builder: (context, viewState, child) { - final viewportSize = viewState.viewportSize; - if (viewportSize == null) return SizedBox.shrink(); - return AnimatedBuilder( - animation: entry.imageChangeNotifier, - builder: (context, child) => CustomPaint( - painter: MinimapPainter( - viewportSize: viewportSize, - entrySize: entry.displaySize, - viewCenterOffset: viewState.position, - viewScale: viewState.scale, - minimapBorderColor: Colors.white30, + child: ValueListenableBuilder<ViewState>( + valueListenable: viewStateNotifier, + builder: (context, viewState, child) { + final viewportSize = viewState.viewportSize; + if (viewportSize == null) return SizedBox.shrink(); + return AnimatedBuilder( + animation: entry.imageChangeNotifier, + builder: (context, child) => CustomPaint( + painter: MinimapPainter( + viewportSize: viewportSize, + entrySize: entry.displaySize, + viewCenterOffset: viewState.position, + viewScale: viewState.scale, + minimapBorderColor: Colors.white30, + ), + size: size, ), - size: size, - ), - ); - }); + ); + }), + ); } } diff --git a/lib/widgets/viewer/overlay/top.dart b/lib/widgets/viewer/overlay/top.dart index 08f4631c0..e04854eb6 100644 --- a/lib/widgets/viewer/overlay/top.dart +++ b/lib/widgets/viewer/overlay/top.dart @@ -1,13 +1,14 @@ import 'package:aves/model/actions/entry_actions.dart'; import 'package:aves/model/entry.dart'; import 'package:aves/model/favourites.dart'; +import 'package:aves/model/multipage.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/theme/durations.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/widgets/common/basic/menu_row.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/fx/sweeper.dart'; -import 'package:aves/widgets/viewer/multipage.dart'; +import 'package:aves/widgets/viewer/multipage/conductor.dart'; import 'package:aves/widgets/viewer/overlay/common.dart'; import 'package:aves/widgets/viewer/overlay/minimap.dart'; import 'package:aves/widgets/viewer/visual/state.dart'; @@ -17,26 +18,24 @@ import 'package:flutter/scheduler.dart'; import 'package:provider/provider.dart'; class ViewerTopOverlay extends StatelessWidget { - final AvesEntry entry; + final AvesEntry mainEntry; final Animation<double> scale; final EdgeInsets viewInsets, viewPadding; final Function(EntryAction value) onActionSelected; final bool canToggleFavourite; final ValueNotifier<ViewState> viewStateNotifier; - final MultiPageController multiPageController; static const double padding = 8; const ViewerTopOverlay({ Key key, - @required this.entry, + @required this.mainEntry, @required this.scale, @required this.canToggleFavourite, @required this.viewInsets, @required this.viewPadding, @required this.onActionSelected, @required this.viewStateNotifier, - @required this.multiPageController, }) : super(key: key); @override @@ -49,78 +48,103 @@ class ViewerTopOverlay extends StatelessWidget { selector: (c, mq) => mq.size.width - mq.padding.horizontal, builder: (c, mqWidth, child) { final availableCount = (mqWidth / (OverlayButton.getSize(context) + padding)).floor() - 2; - final quickActions = settings.viewerQuickActions.where(_canDo).take(availableCount).toList(); - final inAppActions = EntryActions.inApp.where((action) => !quickActions.contains(action)).where(_canDo).toList(); - final externalAppActions = EntryActions.externalApp.where(_canDo).toList(); - final buttonRow = _TopOverlayRow( - quickActions: quickActions, - inAppActions: inAppActions, - externalAppActions: externalAppActions, - scale: scale, - entry: entry, - onActionSelected: onActionSelected, - ); - return settings.showOverlayMinimap && viewStateNotifier != null - ? Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - buttonRow, - SizedBox(height: 8), - FadeTransition( - opacity: scale, - child: Minimap( - mainEntry: entry, - viewStateNotifier: viewStateNotifier, - multiPageController: multiPageController, - ), - ) - ], - ) - : buttonRow; + Widget child; + if (mainEntry.isMultiPage) { + final multiPageController = context.read<MultiPageConductor>().getController(mainEntry); + if (multiPageController != null) { + child = StreamBuilder<MultiPageInfo>( + stream: multiPageController.infoStream, + builder: (context, snapshot) { + final multiPageInfo = multiPageController.info; + return ValueListenableBuilder<int>( + valueListenable: multiPageController.pageNotifier, + builder: (context, page, child) { + return _buildOverlay(availableCount, mainEntry, pageEntry: multiPageInfo?.getPageEntryByIndex(page)); + }, + ); + }, + ); + } + } + + return child ??= _buildOverlay(availableCount, mainEntry); }, ), ), ); } - bool _canDo(EntryAction action) { - switch (action) { - case EntryAction.toggleFavourite: - return canToggleFavourite; - case EntryAction.delete: - case EntryAction.rename: - return entry.canEdit; - case EntryAction.rotateCCW: - case EntryAction.rotateCW: - case EntryAction.flip: - return entry.canRotateAndFlip; - case EntryAction.export: - case EntryAction.print: - return !entry.isVideo; - case EntryAction.openMap: - return entry.hasGps; - case EntryAction.viewSource: - return entry.isSvg; - case EntryAction.share: - case EntryAction.info: - case EntryAction.open: - case EntryAction.edit: - case EntryAction.setAs: - return true; - case EntryAction.debug: - return kDebugMode; + Widget _buildOverlay(int availableCount, AvesEntry mainEntry, {AvesEntry pageEntry}) { + pageEntry ??= mainEntry; + + bool _canDo(EntryAction action) { + final targetEntry = EntryActions.pageActions.contains(action) ? pageEntry : mainEntry; + switch (action) { + case EntryAction.toggleFavourite: + return canToggleFavourite; + case EntryAction.delete: + case EntryAction.rename: + return targetEntry.canEdit; + case EntryAction.rotateCCW: + case EntryAction.rotateCW: + case EntryAction.flip: + return targetEntry.canRotateAndFlip; + case EntryAction.export: + case EntryAction.print: + return !targetEntry.isVideo; + case EntryAction.openMap: + return targetEntry.hasGps; + case EntryAction.viewSource: + return targetEntry.isSvg; + case EntryAction.share: + case EntryAction.info: + case EntryAction.open: + case EntryAction.edit: + case EntryAction.setAs: + return true; + case EntryAction.debug: + return kDebugMode; + } + return false; } - return false; + + final quickActions = settings.viewerQuickActions.where(_canDo).take(availableCount).toList(); + final inAppActions = EntryActions.inApp.where((action) => !quickActions.contains(action)).where(_canDo).toList(); + final externalAppActions = EntryActions.externalApp.where(_canDo).toList(); + final buttonRow = _TopOverlayRow( + quickActions: quickActions, + inAppActions: inAppActions, + externalAppActions: externalAppActions, + scale: scale, + mainEntry: mainEntry, + pageEntry: pageEntry, + onActionSelected: onActionSelected, + ); + + return settings.showOverlayMinimap && viewStateNotifier != null + ? Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + buttonRow, + SizedBox(height: 8), + FadeTransition( + opacity: scale, + child: Minimap( + entry: pageEntry, + viewStateNotifier: viewStateNotifier, + ), + ) + ], + ) + : buttonRow; } } class _TopOverlayRow extends StatelessWidget { - final List<EntryAction> quickActions; - final List<EntryAction> inAppActions; - final List<EntryAction> externalAppActions; + final List<EntryAction> quickActions, inAppActions, externalAppActions; final Animation<double> scale; - final AvesEntry entry; + final AvesEntry mainEntry, pageEntry; final Function(EntryAction value) onActionSelected; const _TopOverlayRow({ @@ -129,7 +153,8 @@ class _TopOverlayRow extends StatelessWidget { @required this.inAppActions, @required this.externalAppActions, @required this.scale, - @required this.entry, + @required this.mainEntry, + @required this.pageEntry, @required this.onActionSelected, }) : super(key: key); @@ -151,7 +176,7 @@ class _TopOverlayRow extends StatelessWidget { key: Key('entry-menu-button'), itemBuilder: (context) => [ ...inAppActions.map((action) => _buildPopupMenuItem(context, action)), - if (entry.canRotateAndFlip) _buildRotateAndFlipMenuItems(context), + if (pageEntry.canRotateAndFlip) _buildRotateAndFlipMenuItems(context), PopupMenuDivider(), ...externalAppActions.map((action) => _buildPopupMenuItem(context, action)), if (kDebugMode) ...[ @@ -175,7 +200,7 @@ class _TopOverlayRow extends StatelessWidget { switch (action) { case EntryAction.toggleFavourite: child = _FavouriteToggler( - entry: entry, + entry: mainEntry, onPressed: onPressed, ); break; @@ -219,7 +244,7 @@ class _TopOverlayRow extends StatelessWidget { // in app actions case EntryAction.toggleFavourite: child = _FavouriteToggler( - entry: entry, + entry: mainEntry, isMenuItem: true, ); break; diff --git a/lib/widgets/viewer/printer.dart b/lib/widgets/viewer/printer.dart index f8e35323c..da21d2174 100644 --- a/lib/widgets/viewer/printer.dart +++ b/lib/widgets/viewer/printer.dart @@ -47,17 +47,18 @@ class EntryPrinter with FeedbackMixin { )); } - if (entry.isMultipage) { + if (entry.isMultiPage && !entry.isMotionPhoto) { final multiPageInfo = await metadataService.getMultiPageInfo(entry); - if (multiPageInfo.pageCount > 1) { + final pageCount = multiPageInfo.pageCount; + if (pageCount > 1) { final streamController = StreamController<AvesEntry>.broadcast(); showOpReport<AvesEntry>( context: context, opStream: streamController.stream, - itemCount: multiPageInfo.pageCount, + itemCount: pageCount, ); - for (final page in multiPageInfo.pages) { - final pageEntry = entry.getPageEntry(page); + for (var page = 0; page < pageCount; page++) { + final pageEntry = multiPageInfo.getPageEntryByIndex(page); _addPdfPage(await _buildPageImage(pageEntry)); streamController.sink.add(pageEntry); } diff --git a/lib/widgets/viewer/video/conductor.dart b/lib/widgets/viewer/video/conductor.dart new file mode 100644 index 000000000..7a026aac5 --- /dev/null +++ b/lib/widgets/viewer/video/conductor.dart @@ -0,0 +1,39 @@ +import 'package:aves/model/entry.dart'; +import 'package:aves/widgets/viewer/video/controller.dart'; +import 'package:aves/widgets/viewer/video/fijkplayer.dart'; +import 'package:fijkplayer/fijkplayer.dart'; + +class VideoConductor { + final List<AvesVideoController> _controllers = []; + + static const maxControllerCount = 3; + + VideoConductor() { + FijkLog.setLevel(FijkLogLevel.Warn); + } + + Future<void> dispose() async { + await Future.forEach(_controllers, (controller) => controller.dispose()); + _controllers.clear(); + } + + AvesVideoController getOrCreateController(AvesEntry entry) { + var controller = getController(entry); + if (controller != null) { + _controllers.remove(controller); + } else { + controller = IjkPlayerAvesVideoController(entry); + } + _controllers.insert(0, controller); + while (_controllers.length > maxControllerCount) { + _controllers.removeLast().dispose(); + } + return controller; + } + + AvesVideoController getController(AvesEntry entry) { + return _controllers.firstWhere((c) => c.entry.uri == entry.uri && c.entry.pageId == entry.pageId, orElse: () => null); + } + + Future<void> pauseAll() => Future.forEach(_controllers, (controller) => controller.pause()); +} diff --git a/lib/widgets/common/video/controller.dart b/lib/widgets/viewer/video/controller.dart similarity index 56% rename from lib/widgets/common/video/controller.dart rename to lib/widgets/viewer/video/controller.dart index b6101af09..3b586908b 100644 --- a/lib/widgets/common/video/controller.dart +++ b/lib/widgets/viewer/video/controller.dart @@ -1,12 +1,15 @@ import 'package:aves/model/entry.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; abstract class AvesVideoController { - AvesVideoController(); + final AvesEntry _entry; - void dispose(); + AvesEntry get entry => _entry; - Future<void> setDataSource(String uri, {int startMillis = 0}); + AvesVideoController(AvesEntry entry) : _entry = entry; + + Future<void> dispose(); Future<void> play(); @@ -14,11 +17,7 @@ abstract class AvesVideoController { Future<void> seekTo(int targetMillis); - Future<void> seekToProgress(double progress) async { - if (duration != null) { - await seekTo((duration * progress).toInt()); - } - } + Future<void> seekToProgress(double progress) => seekTo((duration * progress).toInt()); Listenable get playCompletedListenable; @@ -26,7 +25,7 @@ abstract class AvesVideoController { Stream<VideoStatus> get statusStream; - bool get isPlayable; + bool get isReady; bool get isPlaying => status == VideoStatus.playing; @@ -34,11 +33,11 @@ abstract class AvesVideoController { int get currentPosition; - double get progress => duration == null ? 0 : (currentPosition ?? 0).toDouble() / duration; + double get progress => (currentPosition ?? 0).toDouble() / duration; Stream<int> get positionStream; - Widget buildPlayerWidget(BuildContext context, AvesEntry entry); + Widget buildPlayerWidget(BuildContext context); } enum VideoStatus { diff --git a/lib/widgets/common/video/fijkplayer.dart b/lib/widgets/viewer/video/fijkplayer.dart similarity index 80% rename from lib/widgets/common/video/fijkplayer.dart rename to lib/widgets/viewer/video/fijkplayer.dart index 53f891cfc..11fc20ba6 100644 --- a/lib/widgets/common/video/fijkplayer.dart +++ b/lib/widgets/viewer/video/fijkplayer.dart @@ -7,7 +7,7 @@ import 'package:aves/model/settings/video_loop_mode.dart'; import 'package:aves/model/video/keys.dart'; import 'package:aves/model/video/metadata.dart'; import 'package:aves/utils/change_notifier.dart'; -import 'package:aves/widgets/common/video/controller.dart'; +import 'package:aves/widgets/viewer/video/controller.dart'; import 'package:fijkplayer/fijkplayer.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -23,7 +23,7 @@ class IjkPlayerAvesVideoController extends AvesVideoController { final ValueNotifier<StreamSummary> _selectedVideoStream = ValueNotifier(null); final ValueNotifier<StreamSummary> _selectedAudioStream = ValueNotifier(null); final ValueNotifier<StreamSummary> _selectedTextStream = ValueNotifier(null); - final ValueNotifier<Tuple2<int, int>> _sar = ValueNotifier(Tuple2(1, 1)); + final ValueNotifier<Tuple2<int, int>> _sar = ValueNotifier(null); Timer _initialPlayTimer; Stream<FijkValue> get _valueStream => _valueStreamController.stream; @@ -31,15 +31,47 @@ class IjkPlayerAvesVideoController extends AvesVideoController { static const initialPlayDelay = Duration(milliseconds: 100); static const gifLikeVideoDurationThreshold = Duration(seconds: 10); - IjkPlayerAvesVideoController(AvesEntry entry) { - FijkLog.setLevel(FijkLogLevel.Warn); + IjkPlayerAvesVideoController(AvesEntry entry) : super(entry) { _instance = FijkPlayer(); + _startListening(); + } + @override + Future<void> dispose() async { + _initialPlayTimer?.cancel(); + _stopListening(); + await _valueStreamController.close(); + await _instance.release(); + } + + void _startListening() { + _instance.addListener(_onValueChanged); + _subscriptions.add(_valueStream.where((value) => value.state == FijkState.completed).listen((_) => _completedNotifier.notifyListeners())); + } + + void _stopListening() { + _instance.removeListener(_onValueChanged); + _subscriptions + ..forEach((sub) => sub.cancel()) + ..clear(); + } + + Future<void> _init({int startMillis = 0}) async { + _sar.value = Tuple2(1, 1); + _applyOptions(startMillis); + + // calling `setDataSource()` with `autoPlay` starts as soon as possible, but often yields initial artifacts + // so we introduce a small delay after the player is declared `prepared`, before playing + await _instance.setDataSourceUntilPrepared(entry.uri); + _initialPlayTimer = Timer(initialPlayDelay, play); + } + + void _applyOptions(int startMillis) { // FFmpeg options // cf https://github.com/Bilibili/ijkplayer/blob/master/ijkmedia/ijkplayer/ff_ffplay_options.h // cf https://www.jianshu.com/p/843c86a9e9ad - final option = FijkOption(); + final options = FijkOption(); // when accurate seek is enabled and seeking fails, it takes time (cf `accurate-seek-timeout`) to acknowledge the error and proceed // failure seems to happen when pause-seeking videos with an audio stream, whatever container or video stream @@ -62,42 +94,34 @@ class IjkPlayerAvesVideoController extends AvesVideoController { // `fastseek`: enable fast, but inaccurate seeks for some formats // in practice the flag seems ineffective, but harmless too - option.setFormatOption('fflags', 'fastseek'); + options.setFormatOption('fflags', 'fastseek'); // `enable-accurate-seek`: enable accurate seek, default: 0, in [0, 1] - option.setPlayerOption('enable-accurate-seek', accurateSeekEnabled ? 1 : 0); + options.setPlayerOption('enable-accurate-seek', accurateSeekEnabled ? 1 : 0); // `accurate-seek-timeout`: accurate seek timeout, default: 5000 ms, in [0, 5000] - option.setPlayerOption('accurate-seek-timeout', 1000); + options.setPlayerOption('accurate-seek-timeout', 1000); // `framedrop`: drop frames when cpu is too slow, default: 0, in [-1, 120] - option.setPlayerOption('framedrop', 5); + options.setPlayerOption('framedrop', 5); // `loop`: set number of times the playback shall be looped, default: 1, in [INT_MIN, INT_MAX] - option.setPlayerOption('loop', loopEnabled ? -1 : 1); + options.setPlayerOption('loop', loopEnabled ? -1 : 1); // `mediacodec-all-videos`: MediaCodec: enable all videos, default: 0, in [0, 1] - option.setPlayerOption('mediacodec-all-videos', hwAccelerationEnabled ? 1 : 0); + options.setPlayerOption('mediacodec-all-videos', hwAccelerationEnabled ? 1 : 0); + + // `seek-at-start`: set offset of player should be seeked, default: 0, in [0, INT_MAX] + options.setPlayerOption('seek-at-start', startMillis); + + // `cover-after-prepared`: show cover provided to `FijkView` when player is `prepared` without auto play, default: 0, in [0, 1] + options.setPlayerOption('cover-after-prepared', 0); // TODO TLAD try subs // `subtitle`: decode subtitle stream, default: 0, in [0, 1] // option.setPlayerOption('subtitle', 1); - _instance.applyOptions(option); - - _instance.addListener(_onValueChanged); - _subscriptions.add(_valueStream.where((value) => value.state == FijkState.completed).listen((_) => _completedNotifier.notifyListeners())); - } - - @override - void dispose() { - _initialPlayTimer?.cancel(); - _instance.removeListener(_onValueChanged); - _valueStreamController.close(); - _subscriptions - ..forEach((sub) => sub.cancel()) - ..clear(); - _instance.release(); + _instance.applyOptions(options); } void _fetchSelectedStreams() async { @@ -151,39 +175,33 @@ class IjkPlayerAvesVideoController extends AvesVideoController { _valueStreamController.add(_instance.value); } - // always start playing, even when seeking on uninitialized player, otherwise the texture is not updated - // as a workaround, pausing after a brief duration is possible, but fiddly @override - Future<void> setDataSource(String uri, {int startMillis = 0}) async { - if (startMillis > 0) { - // `seek-at-start`: set offset of player should be seeked, default: 0, in [0, INT_MAX] - await _instance.setOption(FijkOption.playerCategory, 'seek-at-start', startMillis); + Future<void> play() async { + if (isReady) { + await _instance.start(); + } else { + await _init(); } - // calling `setDataSource()` with `autoPlay` starts as soon as possible, but often yields initial artifacts - // so we introduce a small delay after the player is declared `prepared`, before playing - await _instance.setDataSourceUntilPrepared(uri); - _initialPlayTimer = Timer(initialPlayDelay, play); } @override - Future<void> play() { - if (_instance.isPlayable()) { - _instance.start(); - } - return SynchronousFuture(null); - } - - @override - Future<void> pause() { - if (_instance.isPlayable()) { + Future<void> pause() async { + if (isReady) { _initialPlayTimer?.cancel(); - _instance.pause(); + await _instance.pause(); } - return SynchronousFuture(null); } @override - Future<void> seekTo(int targetMillis) => _instance.seekTo(targetMillis); + Future<void> seekTo(int targetMillis) async { + if (isReady) { + await _instance.seekTo(targetMillis); + } else { + // always start playing, even when seeking on uninitialized player, otherwise the texture is not updated + // as a workaround, pausing after a brief duration is possible, but fiddly + await _init(startMillis: targetMillis); + } + } @override Listenable get playCompletedListenable => _completedNotifier; @@ -195,10 +213,14 @@ class IjkPlayerAvesVideoController extends AvesVideoController { Stream<VideoStatus> get statusStream => _valueStream.map((value) => value.state.toAves); @override - bool get isPlayable => _instance.isPlayable(); + bool get isReady => _instance.isPlayable(); @override - int get duration => _instance.value.duration.inMilliseconds; + int get duration { + final controllerDuration = _instance.value.duration.inMilliseconds; + // use expected duration when controller duration is not set yet + return (controllerDuration == null || controllerDuration == 0) ? entry.durationMillis : controllerDuration; + } @override int get currentPosition => _instance.currentPos.inMilliseconds; @@ -207,7 +229,7 @@ class IjkPlayerAvesVideoController extends AvesVideoController { Stream<int> get positionStream => _instance.onCurrentPosUpdate.map((pos) => pos.inMilliseconds); @override - Widget buildPlayerWidget(BuildContext context, AvesEntry entry) { + Widget buildPlayerWidget(BuildContext context) { return ValueListenableBuilder<Tuple2<int, int>>( valueListenable: _sar, builder: (context, sar, child) { @@ -272,7 +294,7 @@ extension ExtraIjkStatus on FijkState { extension ExtraFijkPlayer on FijkPlayer { Future<void> setDataSourceUntilPrepared(String uri) async { - await setDataSource(uri, autoPlay: false); + await setDataSource(uri, autoPlay: false, showCover: false); final completer = Completer(); void onChange() { diff --git a/lib/widgets/common/video/flutter_vlc_player.dart b/lib/widgets/viewer/video/flutter_vlc_player.dart similarity index 98% rename from lib/widgets/common/video/flutter_vlc_player.dart rename to lib/widgets/viewer/video/flutter_vlc_player.dart index 6dfdfcefa..d8f4dcaf4 100644 --- a/lib/widgets/common/video/flutter_vlc_player.dart +++ b/lib/widgets/viewer/video/flutter_vlc_player.dart @@ -3,7 +3,7 @@ // // import 'package:aves/model/entry.dart'; // import 'package:aves/utils/change_notifier.dart'; -// import 'package:aves/widgets/common/video/controller.dart'; +// import 'package:aves/widgets/viewer/video/controller.dart'; // import 'package:flutter/material.dart'; // import 'package:flutter/src/foundation/change_notifier.dart'; // import 'package:flutter/src/widgets/framework.dart'; diff --git a/lib/widgets/common/video/video_player.dart b/lib/widgets/viewer/video/video_player.dart similarity index 97% rename from lib/widgets/common/video/video_player.dart rename to lib/widgets/viewer/video/video_player.dart index 6e0fb35af..ee6d438c7 100644 --- a/lib/widgets/common/video/video_player.dart +++ b/lib/widgets/viewer/video/video_player.dart @@ -2,7 +2,7 @@ // // import 'package:aves/model/entry.dart'; // import 'package:aves/utils/change_notifier.dart'; -// import 'package:aves/widgets/common/video/controller.dart'; +// import 'package:aves/widgets/viewer/video/controller.dart'; // import 'package:flutter/src/foundation/change_notifier.dart'; // import 'package:flutter/src/widgets/framework.dart'; // import 'package:video_player/video_player.dart'; diff --git a/lib/widgets/viewer/visual/entry_page_view.dart b/lib/widgets/viewer/visual/entry_page_view.dart index 78ad051eb..92f960cea 100644 --- a/lib/widgets/viewer/visual/entry_page_view.dart +++ b/lib/widgets/viewer/visual/entry_page_view.dart @@ -3,7 +3,6 @@ import 'dart:async'; import 'package:aves/image_providers/uri_picture_provider.dart'; import 'package:aves/model/entry.dart'; import 'package:aves/model/entry_images.dart'; -import 'package:aves/model/multipage.dart'; import 'package:aves/model/settings/entry_background.dart'; import 'package:aves/model/settings/enums.dart'; import 'package:aves/model/settings/settings.dart'; @@ -15,9 +14,10 @@ import 'package:aves/widgets/common/magnifier/magnifier.dart'; import 'package:aves/widgets/common/magnifier/scale/scale_boundaries.dart'; import 'package:aves/widgets/common/magnifier/scale/scale_level.dart'; import 'package:aves/widgets/common/magnifier/scale/state.dart'; -import 'package:aves/widgets/common/video/controller.dart'; import 'package:aves/widgets/viewer/hero.dart'; import 'package:aves/widgets/viewer/overlay/notifications.dart'; +import 'package:aves/widgets/viewer/video/conductor.dart'; +import 'package:aves/widgets/viewer/video/controller.dart'; import 'package:aves/widgets/viewer/visual/error.dart'; import 'package:aves/widgets/viewer/visual/raster.dart'; import 'package:aves/widgets/viewer/visual/state.dart'; @@ -27,27 +27,21 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:provider/provider.dart'; -import 'package:tuple/tuple.dart'; class EntryPageView extends StatefulWidget { - final AvesEntry mainEntry; - final AvesEntry entry; - final SinglePageInfo page; + final AvesEntry mainEntry, pageEntry; final Size viewportSize; - final List<Tuple2<String, AvesVideoController>> videoControllers; final VoidCallback onDisposed; static const decorationCheckSize = 20.0; - EntryPageView({ + const EntryPageView({ Key key, this.mainEntry, - this.page, + this.pageEntry, this.viewportSize, - @required this.videoControllers, this.onDisposed, - }) : entry = mainEntry.getPageEntry(page) ?? mainEntry, - super(key: key); + }) : super(key: key); @override _EntryPageViewState createState() => _EntryPageViewState(); @@ -60,7 +54,7 @@ class _EntryPageViewState extends State<EntryPageView> { AvesEntry get mainEntry => widget.mainEntry; - AvesEntry get entry => widget.entry; + AvesEntry get entry => widget.pageEntry; Size get viewportSize => widget.viewportSize; @@ -78,7 +72,7 @@ class _EntryPageViewState extends State<EntryPageView> { void didUpdateWidget(covariant EntryPageView oldWidget) { super.didUpdateWidget(oldWidget); - if (oldWidget.entry.displaySize != entry.displaySize) { + if (oldWidget.pageEntry.displaySize != widget.pageEntry.displaySize) { // do not reset the magnifier view state unless page dimensions change, // in effect locking the zoom & position when browsing entry pages of the same size _unregisterWidget(); @@ -195,7 +189,7 @@ class _EntryPageViewState extends State<EntryPageView> { } Widget _buildVideoView() { - final videoController = widget.videoControllers.firstWhere((kv) => kv.item1 == entry.uri, orElse: () => null)?.item2; + final videoController = context.read<VideoConductor>().getController(entry); if (videoController == null) return SizedBox(); return Stack( fit: StackFit.expand, @@ -210,11 +204,11 @@ class _EntryPageViewState extends State<EntryPageView> { StreamBuilder<VideoStatus>( stream: videoController.statusStream, builder: (context, snapshot) { - final showCover = videoController.isPlayable; + final showCover = !videoController.isReady; return IgnorePointer( - ignoring: showCover, + ignoring: !showCover, child: AnimatedOpacity( - opacity: showCover ? 0 : 1, + opacity: showCover ? 1 : 0, curve: Curves.easeInCirc, duration: Durations.viewerVideoPlayerTransition, child: GestureDetector( diff --git a/lib/widgets/viewer/visual/error.dart b/lib/widgets/viewer/visual/error.dart index 34d3cf97b..1cbcc81ce 100644 --- a/lib/widgets/viewer/visual/error.dart +++ b/lib/widgets/viewer/visual/error.dart @@ -45,7 +45,7 @@ class _ErrorViewState extends State<ErrorView> { if (snapshot.connectionState != ConnectionState.done) return SizedBox(); final exists = snapshot.data; return EmptyContent( - icon: AIcons.error, + icon: exists ? AIcons.error : AIcons.broken, text: exists ? context.l10n.viewerErrorUnknown : context.l10n.viewerErrorDoesNotExist, alignment: Alignment.center, ); diff --git a/lib/widgets/viewer/visual/video.dart b/lib/widgets/viewer/visual/video.dart index 779015295..eda8c6d2a 100644 --- a/lib/widgets/viewer/visual/video.dart +++ b/lib/widgets/viewer/visual/video.dart @@ -1,5 +1,5 @@ import 'package:aves/model/entry.dart'; -import 'package:aves/widgets/common/video/controller.dart'; +import 'package:aves/widgets/viewer/video/controller.dart'; import 'package:flutter/material.dart'; class VideoView extends StatefulWidget { @@ -54,7 +54,7 @@ class _VideoViewState extends State<VideoView> { return StreamBuilder<VideoStatus>( stream: controller.statusStream, builder: (context, snapshot) { - return controller.isPlayable ? controller.buildPlayerWidget(context, entry) : SizedBox(); + return controller.isReady ? controller.buildPlayerWidget(context) : SizedBox(); }); } diff --git a/lib/widgets/welcome_page.dart b/lib/widgets/welcome_page.dart index a2c27dee0..5b8fe9181 100644 --- a/lib/widgets/welcome_page.dart +++ b/lib/widgets/welcome_page.dart @@ -157,15 +157,27 @@ class _WelcomePageState extends State<WelcomePage> { constraints: BoxConstraints(maxWidth: 460), child: ClipRRect( borderRadius: BorderRadius.circular(16), - child: Markdown( - data: terms, - selectable: true, - onTapLink: (text, href, title) async { - if (await canLaunch(href)) { - await launch(href); - } - }, - shrinkWrap: true, + child: Theme( + data: Theme.of(context).copyWith( + scrollbarTheme: ScrollbarThemeData( + radius: Radius.circular(16), + crossAxisMargin: 6, + mainAxisMargin: 16, + interactive: true, + ), + ), + child: Scrollbar( + child: Markdown( + data: terms, + selectable: true, + onTapLink: (text, href, title) async { + if (await canLaunch(href)) { + await launch(href); + } + }, + shrinkWrap: true, + ), + ), ), ), ); diff --git a/pubspec.lock b/pubspec.lock index 1e8fb5f47..b264a4827 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -7,21 +7,14 @@ packages: name: _fe_analyzer_shared url: "https://pub.dartlang.org" source: hosted - version: "19.0.0" + version: "21.0.0" analyzer: dependency: transitive description: name: analyzer url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" - ansicolor: - dependency: transitive - description: - name: ansicolor - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.1" + version: "1.5.0" archive: dependency: transitive description: @@ -35,7 +28,7 @@ packages: name: args url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.1.0" async: dependency: transitive description: @@ -134,13 +127,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.1" - console_log_handler: - dependency: transitive - description: - name: console_log_handler - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.6" convert: dependency: transitive description: @@ -161,7 +147,7 @@ packages: name: coverage url: "https://pub.dartlang.org" source: hosted - version: "0.15.2" + version: "1.0.2" crypto: dependency: transitive description: @@ -175,7 +161,7 @@ packages: name: decorated_icon url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.2.0" event_bus: dependency: "direct main" description: @@ -211,7 +197,7 @@ packages: description: path: "." ref: aves - resolved-ref: "0f25874db46d1af6fcfbeb8722915cbc211a10fb" + resolved-ref: "7bb26e681ceef9d7b311f0b0f51eab99c3590474" url: "git://github.com/deckerst/fijkplayer.git" source: git version: "0.8.7" @@ -235,28 +221,28 @@ packages: name: firebase_analytics url: "https://pub.dartlang.org" source: hosted - version: "7.1.1" + version: "8.0.2" firebase_analytics_platform_interface: dependency: transitive description: name: firebase_analytics_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "2.0.0" firebase_analytics_web: dependency: transitive description: name: firebase_analytics_web url: "https://pub.dartlang.org" source: hosted - version: "0.2.0+1" + version: "0.3.0" firebase_core: dependency: "direct main" description: name: firebase_core url: "https://pub.dartlang.org" source: hosted - version: "1.0.2" + version: "1.1.0" firebase_core_platform_interface: dependency: transitive description: @@ -277,14 +263,14 @@ packages: name: firebase_crashlytics url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" + version: "2.0.2" firebase_crashlytics_platform_interface: dependency: transitive description: name: firebase_crashlytics_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "3.0.2" flutter: dependency: "direct main" description: flutter @@ -324,9 +310,11 @@ packages: flutter_map: dependency: "direct main" description: - name: flutter_map - url: "https://pub.dartlang.org" - source: hosted + path: "." + ref: "issues/829-nullsafety" + resolved-ref: "622d7e894034681c46470a4e4c94964d0ccd6454" + url: "git://github.com/fleaflet/flutter_map.git" + source: git version: "0.12.0" flutter_markdown: dependency: "direct main" @@ -355,7 +343,7 @@ packages: name: flutter_svg url: "https://pub.dartlang.org" source: hosted - version: "0.21.0-nullsafety.0" + version: "0.22.0" flutter_test: dependency: "direct dev" description: flutter @@ -377,14 +365,14 @@ packages: name: get_it url: "https://pub.dartlang.org" source: hosted - version: "6.0.0" + version: "6.1.1" github: dependency: "direct main" description: name: github url: "https://pub.dartlang.org" source: hosted - version: "8.0.1" + version: "8.1.0" glob: dependency: transitive description: @@ -405,7 +393,7 @@ packages: name: google_maps_flutter url: "https://pub.dartlang.org" source: hosted - version: "2.0.2" + version: "2.0.3" google_maps_flutter_platform_interface: dependency: transitive description: @@ -433,7 +421,7 @@ packages: name: http_multi_server url: "https://pub.dartlang.org" source: hosted - version: "3.0.0" + version: "3.0.1" http_parser: dependency: transitive description: @@ -476,27 +464,27 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "4.0.1" - latlong: + latlong2: dependency: "direct main" description: - name: latlong + name: latlong2 url: "https://pub.dartlang.org" source: hosted - version: "0.6.1" + version: "0.8.0" lists: dependency: transitive description: name: lists url: "https://pub.dartlang.org" source: hosted - version: "0.1.6" + version: "1.0.0" logging: dependency: transitive description: name: logging url: "https://pub.dartlang.org" source: hosted - version: "0.11.4" + version: "1.0.1" markdown: dependency: transitive description: @@ -531,7 +519,7 @@ packages: name: mgrs_dart url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" + version: "2.0.0" mime: dependency: transitive description: @@ -559,14 +547,14 @@ packages: name: node_preamble url: "https://pub.dartlang.org" source: hosted - version: "1.4.13" + version: "2.0.0" overlay_support: dependency: "direct main" description: name: overlay_support url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.2.1" package_config: dependency: transitive description: @@ -608,14 +596,14 @@ packages: name: path_drawing url: "https://pub.dartlang.org" source: hosted - version: "0.5.0-nullsafety.0" + version: "0.5.0" path_parsing: dependency: transitive description: name: path_parsing url: "https://pub.dartlang.org" source: hosted - version: "0.2.0-nullsafety.0" + version: "0.2.0" path_provider_linux: dependency: transitive description: @@ -636,14 +624,14 @@ packages: name: path_provider_windows url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.0.1" pdf: dependency: "direct main" description: name: pdf url: "https://pub.dartlang.org" source: hosted - version: "3.1.0" + version: "3.3.0" pedantic: dependency: "direct main" description: @@ -664,21 +652,21 @@ packages: name: permission_handler url: "https://pub.dartlang.org" source: hosted - version: "6.1.1" + version: "7.0.0" permission_handler_platform_interface: dependency: transitive description: name: permission_handler_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "3.1.1" + version: "3.3.0" petitparser: dependency: transitive description: name: petitparser url: "https://pub.dartlang.org" source: hosted - version: "4.0.2" + version: "4.1.0" platform: dependency: transitive description: @@ -700,34 +688,34 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.5.0" - positioned_tap_detector: + positioned_tap_detector_2: dependency: transitive description: - name: positioned_tap_detector + name: positioned_tap_detector_2 url: "https://pub.dartlang.org" source: hosted - version: "1.0.3" + version: "1.0.0" printing: dependency: "direct main" description: name: printing url: "https://pub.dartlang.org" source: hosted - version: "5.0.4" + version: "5.2.1" process: dependency: transitive description: name: process url: "https://pub.dartlang.org" source: hosted - version: "4.1.0" + version: "4.2.1" proj4dart: dependency: transitive description: name: proj4dart url: "https://pub.dartlang.org" source: hosted - version: "1.0.5" + version: "2.0.0" provider: dependency: "direct main" description: @@ -755,7 +743,7 @@ packages: name: quiver url: "https://pub.dartlang.org" source: hosted - version: "2.1.5" + version: "3.0.1" shared_preferences: dependency: "direct main" description: @@ -804,7 +792,7 @@ packages: name: shelf url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" shelf_packages_handler: dependency: transitive description: @@ -928,35 +916,35 @@ packages: name: test url: "https://pub.dartlang.org" source: hosted - version: "1.16.5" + version: "1.16.8" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.19" + version: "0.3.0" test_core: dependency: transitive description: name: test_core url: "https://pub.dartlang.org" source: hosted - version: "0.3.15" + version: "0.3.19" transparent_image: dependency: transitive description: name: transparent_image url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" + version: "2.0.0" tuple: dependency: "direct main" description: name: tuple url: "https://pub.dartlang.org" source: hosted - version: "1.0.3" + version: "2.0.0" typed_data: dependency: transitive description: @@ -970,7 +958,7 @@ packages: name: unicode url: "https://pub.dartlang.org" source: hosted - version: "0.2.4" + version: "0.3.0" url_launcher: dependency: "direct main" description: @@ -1013,13 +1001,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.0" - validate: - dependency: transitive - description: - name: validate - url: "https://pub.dartlang.org" - source: hosted - version: "1.7.0" vector_math: dependency: transitive description: @@ -1040,7 +1021,7 @@ packages: name: vm_service url: "https://pub.dartlang.org" source: hosted - version: "6.0.1-nullsafety.1" + version: "6.1.0+1" watcher: dependency: transitive description: @@ -1068,7 +1049,7 @@ packages: name: webkit_inspection_protocol url: "https://pub.dartlang.org" source: hosted - version: "0.7.5" + version: "1.0.0" win32: dependency: transitive description: @@ -1082,7 +1063,7 @@ packages: name: wkt_parser url: "https://pub.dartlang.org" source: hosted - version: "1.0.7" + version: "2.0.0" xdg_directories: dependency: transitive description: @@ -1096,7 +1077,7 @@ packages: name: xml url: "https://pub.dartlang.org" source: hosted - version: "5.0.2" + version: "5.1.0" yaml: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index ebcd36d6a..51670fcd7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: aves description: A visual media gallery and metadata explorer app. repository: https://github.com/deckerst/aves -version: 1.4.0+44 +version: 1.4.1+45 publish_to: none environment: @@ -13,11 +13,10 @@ environment: # bump `crypto` and others - 2021/02/05 https://github.com/flutter/flutter/commit/bc1cf4945841ba5874f5262b8146d52750e7c11f # bump `archive` from 3.0.0 to 3.1.2 - 2021/03/04 https://github.com/flutter/flutter/commit/ddcb8d7d6d3fcedc906b2f1bf26b73c018d3dc28 -# not null safe, as of 2021/03/13 +# not null safe, as of 2021/04/28 # `charts_flutter` - https://github.com/google/charts/issues/579 # `fijkplayer` - https://github.com/befovy/fijkplayer/issues/381 # `flutter_map` - https://github.com/fleaflet/flutter_map/issues/829 -# `latlong` - archived - migrate to maps_toolkit? cf https://github.com/fleaflet/flutter_map/pull/750 # `streams_channel` - unmaintained? - no issue/PR dependencies: @@ -43,6 +42,9 @@ dependencies: firebase_crashlytics: flutter_highlight: flutter_map: + git: + url: git://github.com/fleaflet/flutter_map.git + ref: issues/829-nullsafety flutter_markdown: flutter_staggered_animations: flutter_svg: @@ -51,12 +53,13 @@ dependencies: google_api_availability: google_maps_flutter: intl: - latlong: + latlong2: material_design_icons_flutter: overlay_support: package_info: palette_generator: - panorama: +# TODO TLAD upgrade panorama when this is fixed: https://github.com/zesage/panorama/issues/25 (bug in v0.4.1) + panorama: 0.4.0 pdf: pedantic: percent_indicator: diff --git a/shaders_2.1.0-12.2.pre.sksl.json b/shaders_2.1.0-12.2.pre.sksl.json deleted file mode 100644 index f438b97fa..000000000 --- a/shaders_2.1.0-12.2.pre.sksl.json +++ /dev/null @@ -1 +0,0 @@ -{"platform":"android","name":"SM G970N","engineRevision":"711ab3fda05004ee5f6035f2a0bf099fca39a129","data":{"LIIAAAAAAAGAAAAAAIWP577774BSZ7X7777QCAAAAAAAAAAAHIRQSAAAAAAACAQAAAAHQBRAIAAAAAEAKMAYIDAAAACAAAAAAC4AEAAAAAIAAAAAQCIBCAAAAA":"AwAAAExTS1MFAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdkxvY2FsQ29vcmRfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCXZMb2NhbENvb3JkX1N0YWdlMCA9IGxvY2FsQ29vcmQ7Cglza19Qb3NpdGlvbiA9IGZsb2F0NChwb3NpdGlvbi54ICwgcG9zaXRpb24ueSwgMCwgMSk7Cn0KAAAAAAAAAAAAAAAAAAAAgw8AAHVuaWZvcm0gaGFsZjIgdV8wX0luY3JlbWVudF9TdGFnZTFfYzA7CnVuaWZvcm0gaGFsZjQgdV8xX0tlcm5lbF9TdGFnZTFfYzBbNV07CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TdGFnZTFfYzBfYzA7CnVuaWZvcm0gZmxvYXQ0IHVjbGFtcF9TdGFnZTFfYzBfYzBfYzA7CnVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1N0YWdlMTsKaW4gZmxvYXQyIHZMb2NhbENvb3JkX1N0YWdlMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgVGV4dHVyZUVmZmVjdF9TdGFnZTFfYzBfYzBfYzAoaGFsZjQgX2lucHV0LCBmbG9hdDIgX2Nvb3JkcykgCnsKCWZsb2F0MiBpbkNvb3JkID0gX2Nvb3JkczsKCWZsb2F0MiBzdWJzZXRDb29yZDsKCXN1YnNldENvb3JkLnggPSBpbkNvb3JkLng7CglzdWJzZXRDb29yZC55ID0gaW5Db29yZC55OwoJZmxvYXQyIGNsYW1wZWRDb29yZDsKCWNsYW1wZWRDb29yZC54ID0gc3Vic2V0Q29vcmQueDsKCWNsYW1wZWRDb29yZC55ID0gY2xhbXAoc3Vic2V0Q29vcmQueSwgdWNsYW1wX1N0YWdlMV9jMF9jMF9jMC55LCB1Y2xhbXBfU3RhZ2UxX2MwX2MwX2MwLncpOwoJaGFsZjQgdGV4dHVyZUNvbG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1N0YWdlMSwgY2xhbXBlZENvb3JkKTsKCXJldHVybiB0ZXh0dXJlQ29sb3I7Cn0KaGFsZjQgTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChoYWxmNCBfaW5wdXQsIGZsb2F0MiBfY29vcmRzKSAKewoJcmV0dXJuIFRleHR1cmVFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKF9pbnB1dCwgKCh1bWF0cml4X1N0YWdlMV9jMF9jMCkgKiBfY29vcmRzLnh5MSkueHkpOwp9CmlubGluZSBoYWxmNCBHYXVzc2lhbkNvbnZvbHV0aW9uX1N0YWdlMV9jMChoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBfMl9jb2xvciA9IGhhbGY0KDAuMCk7CglmbG9hdDIgXzNfY29vcmQgPSAodkxvY2FsQ29vcmRfU3RhZ2UwIC0gZmxvYXQyKCg5LjAgKiB1XzBfSW5jcmVtZW50X1N0YWdlMV9jMCkpKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzBdLngpKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzBdLnkpKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzBdLnopKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzBdLncpKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzFdLngpKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzFdLnkpKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzFdLnopKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzFdLncpKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzJdLngpKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzJdLnkpKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzJdLnopKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzJdLncpKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzNdLngpKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzNdLnkpKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzNdLnopKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzNdLncpKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzRdLngpKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzRdLnkpKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzRdLnopKTsKCXJldHVybiBfMl9jb2xvcjsKfQp2b2lkIG1haW4oKSAKewoJaGFsZjQgb3V0cHV0Q29sb3JfU3RhZ2UwOwoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfU3RhZ2UwOwoJewoJCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJCW91dHB1dENvbG9yX1N0YWdlMCA9IGhhbGY0KDEpOwoJCW91dHB1dENvdmVyYWdlX1N0YWdlMCA9IGhhbGY0KDEpOwoJfQoJaGFsZjQgb3V0cHV0X1N0YWdlMTsKCW91dHB1dF9TdGFnZTEgPSBHYXVzc2lhbkNvbnZvbHV0aW9uX1N0YWdlMV9jMChvdXRwdXRDb2xvcl9TdGFnZTApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dF9TdGFnZTEgKiBvdXRwdXRDb3ZlcmFnZV9TdGFnZTA7Cgl9Cn0KAAAAAAABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAACAAAACAAAAHBvc2l0aW9uCgAAAGxvY2FsQ29vcmQAAAEAAAAAAAAA","LENQAAAABCYIR6AYYAAAAAAAAAAAAAHIR4AOAAPAAHQADYBZAAAAAACECYAAAACIDIAAAAAADYEQAAAAYAQQAAAAQAVQAAAAAAAQAAAABAMQC":"AwAAAExTS1OgAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TdGFnZTA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TdGFnZTFfYzBfYzBfYzA7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiBmbG9hdDQgaW5RdWFkRWRnZTsKb3V0IGZsb2F0NCB2UXVhZEVkZ2VfU3RhZ2UwOwpvdXQgaGFsZjQgdmluQ29sb3JfU3RhZ2UwOwpvdXQgZmxvYXQyIHZUcmFuc2Zvcm1lZENvb3Jkc18wX1N0YWdlMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZEVkZ2UKCXZRdWFkRWRnZV9TdGFnZTAgPSBpblF1YWRFZGdlOwoJdmluQ29sb3JfU3RhZ2UwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfU3RhZ2UwLnh6ICogaW5Qb3NpdGlvbiArIHVsb2NhbE1hdHJpeF9TdGFnZTAueXc7Cglza19Qb3NpdGlvbiA9IGZsb2F0NChfdG1wXzBfaW5Qb3NpdGlvbi54ICwgX3RtcF8wX2luUG9zaXRpb24ueSwgMCwgMSk7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzBfU3RhZ2UwID0gKCgodW1hdHJpeF9TdGFnZTFfYzBfYzBfYzApKSAqIF90bXBfMV9pblBvc2l0aW9uLnh5MSkueHk7Cgl9Cn0KAAAAAAAAAAAAAAAAZgkAAHVuaWZvcm0gaGFsZjQgdWxlZnRCb3JkZXJDb2xvcl9TdGFnZTFfYzBfYzA7CnVuaWZvcm0gaGFsZjQgdXJpZ2h0Qm9yZGVyQ29sb3JfU3RhZ2UxX2MwX2MwOwp1bmlmb3JtIGZsb2F0M3gzIHVtYXRyaXhfU3RhZ2UxX2MwX2MwX2MwOwp1bmlmb3JtIGhhbGY0IHVzdGFydF9TdGFnZTFfYzBfYzBfYzE7CnVuaWZvcm0gaGFsZjQgdWVuZF9TdGFnZTFfYzBfYzBfYzE7CmluIGZsb2F0NCB2UXVhZEVkZ2VfU3RhZ2UwOwppbiBoYWxmNCB2aW5Db2xvcl9TdGFnZTA7CmluIGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMF9TdGFnZTA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IExpbmVhckdyYWRpZW50TGF5b3V0X1N0YWdlMV9jMF9jMF9jMF9jMChoYWxmNCBfaW5wdXQpIAp7CgloYWxmIHQgPSBoYWxmKHZUcmFuc2Zvcm1lZENvb3Jkc18wX1N0YWdlMC54KSArIDkuOTk5OTk5NzQ3Mzc4NzUxNmUtMDY7CglyZXR1cm4gaGFsZjQodCwgMS4wLCAwLjAsIDAuMCk7Cn0KaGFsZjQgTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMF9jMChoYWxmNCBfaW5wdXQpIAp7CglyZXR1cm4gTGluZWFyR3JhZGllbnRMYXlvdXRfU3RhZ2UxX2MwX2MwX2MwX2MwKF9pbnB1dCk7Cn0KaGFsZjQgU2luZ2xlSW50ZXJ2YWxHcmFkaWVudENvbG9yaXplcl9TdGFnZTFfYzBfYzBfYzEoaGFsZjQgX2lucHV0LCBmbG9hdDIgX2Nvb3JkcykgCnsKCWhhbGYgdCA9IGhhbGYoX2Nvb3Jkcy54KTsKCXJldHVybiBtaXgodXN0YXJ0X1N0YWdlMV9jMF9jMF9jMSwgdWVuZF9TdGFnZTFfYzBfYzBfYzEsIHQpOwp9CmhhbGY0IENsYW1wZWRHcmFkaWVudEVmZmVjdF9TdGFnZTFfYzBfYzAoaGFsZjQgX2lucHV0KSAKewoJaGFsZjQgdCA9IE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzBfYzAoX2lucHV0KTsKCWhhbGY0IG91dENvbG9yOwoJaWYgKCF0cnVlICYmIHQueSA8IDAuMCkgCgl7CgkJb3V0Q29sb3IgPSBoYWxmNCgwLjApOwoJfQoJZWxzZSBpZiAodC54IDwgMC4wKSAKCXsKCQlvdXRDb2xvciA9IHVsZWZ0Qm9yZGVyQ29sb3JfU3RhZ2UxX2MwX2MwOwoJfQoJZWxzZSBpZiAodC54ID4gMS4wKSAKCXsKCQlvdXRDb2xvciA9IHVyaWdodEJvcmRlckNvbG9yX1N0YWdlMV9jMF9jMDsKCX0KCWVsc2UgCgl7CgkJb3V0Q29sb3IgPSBTaW5nbGVJbnRlcnZhbEdyYWRpZW50Q29sb3JpemVyX1N0YWdlMV9jMF9jMF9jMShfaW5wdXQsIGZsb2F0MihoYWxmMih0LngsIDAuMCkpKTsKCX0KCUBpZiAodHJ1ZSkgCgl7CgkJb3V0Q29sb3IueHl6ICo9IG91dENvbG9yLnc7Cgl9CglyZXR1cm4gb3V0Q29sb3I7Cn0KaW5saW5lIGhhbGY0IE92ZXJyaWRlSW5wdXRGcmFnbWVudFByb2Nlc3Nvcl9TdGFnZTFfYzAoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIENsYW1wZWRHcmFkaWVudEVmZmVjdF9TdGFnZTFfYzBfYzAoZmFsc2UgPyBoYWxmNCgwKSA6IGhhbGY0KDEuMDAwMDAwLCAxLjAwMDAwMCwgMS4wMDAwMDAsIDEuMDAwMDAwKSk7Cn0Kdm9pZCBtYWluKCkgCnsKCWhhbGY0IG91dHB1dENvbG9yX1N0YWdlMDsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1N0YWdlMDsKCXsKCQkvLyBTdGFnZSAwLCBRdWFkRWRnZQoJCW91dHB1dENvbG9yX1N0YWdlMCA9IHZpbkNvbG9yX1N0YWdlMDsKCQloYWxmIGVkZ2VBbHBoYTsKCQloYWxmMiBkdXZkeCA9IGhhbGYyKGRGZHgodlF1YWRFZGdlX1N0YWdlMC54eSkpOwoJCWhhbGYyIGR1dmR5ID0gaGFsZjIoZEZkeSh2UXVhZEVkZ2VfU3RhZ2UwLnh5KSk7CgkJaWYgKHZRdWFkRWRnZV9TdGFnZTAueiA+IDAuMCAmJiB2UXVhZEVkZ2VfU3RhZ2UwLncgPiAwLjApIAoJCXsKCQkJZWRnZUFscGhhID0gaGFsZihtaW4obWluKHZRdWFkRWRnZV9TdGFnZTAueiwgdlF1YWRFZGdlX1N0YWdlMC53KSArIDAuNSwgMS4wKSk7CgkJfQoJCWVsc2UgCgkJewoJCQloYWxmMiBnRiA9IGhhbGYyKGhhbGYoMi4wKnZRdWFkRWRnZV9TdGFnZTAueCpkdXZkeC54IC0gZHV2ZHgueSksICAgICAgICAgICAgICAgICBoYWxmKDIuMCp2UXVhZEVkZ2VfU3RhZ2UwLngqZHV2ZHkueCAtIGR1dmR5LnkpKTsKCQkJZWRnZUFscGhhID0gaGFsZih2UXVhZEVkZ2VfU3RhZ2UwLngqdlF1YWRFZGdlX1N0YWdlMC54IC0gdlF1YWRFZGdlX1N0YWdlMC55KTsKCQkJZWRnZUFscGhhID0gc2F0dXJhdGUoMC41IC0gZWRnZUFscGhhIC8gbGVuZ3RoKGdGKSk7CgkJfQoJCW91dHB1dENvdmVyYWdlX1N0YWdlMCA9IGhhbGY0KGVkZ2VBbHBoYSk7Cgl9CgloYWxmNCBvdXRwdXRfU3RhZ2UxOwoJb3V0cHV0X1N0YWdlMSA9IE92ZXJyaWRlSW5wdXRGcmFnbWVudFByb2Nlc3Nvcl9TdGFnZTFfYzAob3V0cHV0Q29sb3JfU3RhZ2UwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRfU3RhZ2UxICogb3V0cHV0Q292ZXJhZ2VfU3RhZ2UwOwoJfQp9CgAAAAAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IACgAAAGluUXVhZEVkZ2UAAAEAAAAAAAAA","AWAAGAAAQAAIXCEPAGGP777777777737AAAAAAAAAAAAAXABAAAAACAAAAAEBSAI":"AwAAAExTS1OzAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiBmbG9hdDQgaW5DaXJjbGVFZGdlOwpvdXQgZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfU3RhZ2UwOwpvdXQgaGFsZjQgdmluQ29sb3JfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TdGFnZTAgPSBpbkNpcmNsZUVkZ2U7Cgl2aW5Db2xvcl9TdGFnZTAgPSBpbkNvbG9yOwoJZmxvYXQyIF90bXBfMF9pblBvc2l0aW9uID0gaW5Qb3NpdGlvbjsKCWZsb2F0MiBfdG1wXzFfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247Cglza19Qb3NpdGlvbiA9IGZsb2F0NChfdG1wXzBfaW5Qb3NpdGlvbi54ICwgX3RtcF8wX2luUG9zaXRpb24ueSwgMCwgMSk7Cn0KAAAAAAAAAAAAAAAAAEwCAABpbiBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TdGFnZTA7CmluIGhhbGY0IHZpbkNvbG9yX1N0YWdlMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKdm9pZCBtYWluKCkgCnsKCWhhbGY0IG91dHB1dENvbG9yX1N0YWdlMDsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1N0YWdlMDsKCXsKCQkvLyBTdGFnZSAwLCBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJCWZsb2F0NCBjaXJjbGVFZGdlOwoJCWNpcmNsZUVkZ2UgPSB2aW5DaXJjbGVFZGdlX1N0YWdlMDsKCQlvdXRwdXRDb2xvcl9TdGFnZTAgPSB2aW5Db2xvcl9TdGFnZTA7CgkJZmxvYXQgZCA9IGxlbmd0aChjaXJjbGVFZGdlLnh5KTsKCQloYWxmIGRpc3RhbmNlVG9PdXRlckVkZ2UgPSBoYWxmKGNpcmNsZUVkZ2UueiAqICgxLjAgLSBkKSk7CgkJaGFsZiBlZGdlQWxwaGEgPSBzYXR1cmF0ZShkaXN0YW5jZVRvT3V0ZXJFZGdlKTsKCQlvdXRwdXRDb3ZlcmFnZV9TdGFnZTAgPSBoYWxmNChlZGdlQWxwaGEpOwoJfQoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1N0YWdlMCAqIG91dHB1dENvdmVyYWdlX1N0YWdlMDsKCX0KfQoAAAAAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAwAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAMAAAAaW5DaXJjbGVFZGdlAQAAAAAAAAA=","AWQQGAAAQAAIXCEPAGGP777777777737AAAAAAAAAAAIAXCIAEAAAAAAK4AAAAAAAIAAAAAQGIBAA":"AwAAAExTS1MJAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TdGFnZTA7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiBmbG9hdDQgaW5DaXJjbGVFZGdlOwpvdXQgZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfU3RhZ2UwOwpvdXQgaGFsZjQgdmluQ29sb3JfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TdGFnZTAgPSBpbkNpcmNsZUVkZ2U7Cgl2aW5Db2xvcl9TdGFnZTAgPSBpbkNvbG9yOwoJZmxvYXQyIF90bXBfMF9pblBvc2l0aW9uID0gaW5Qb3NpdGlvbjsKCWZsb2F0MiBfdG1wXzFfaW5Qb3NpdGlvbiA9IHVsb2NhbE1hdHJpeF9TdGFnZTAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1N0YWdlMC55dzsKCXNrX1Bvc2l0aW9uID0gZmxvYXQ0KF90bXBfMF9pblBvc2l0aW9uLnggLCBfdG1wXzBfaW5Qb3NpdGlvbi55LCAwLCAxKTsKfQoAAAAAAQEAAAAAAAABAQDbBQAAdW5pZm9ybSBmbG9hdDQgdXJlY3RVbmlmb3JtX1N0YWdlMV9jMDsKaW4gZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfU3RhZ2UwOwppbiBoYWxmNCB2aW5Db2xvcl9TdGFnZTA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmlubGluZSBoYWxmNCBBQVJlY3RFZmZlY3RfU3RhZ2UxX2MwKGhhbGY0IF9pbnB1dCkgCnsKCWZsb2F0NCBwcmV2UmVjdCA9IGZsb2F0NCgtMS4wMDAwMDAsIC0xLjAwMDAwMCwgLTEuMDAwMDAwLCAtMS4wMDAwMDApOwoJaGFsZiBjb3ZlcmFnZTsKCUBzd2l0Y2ggKDEpIAoJewoJCWNhc2UgMDogICAgY2FzZSAyOiAgICAgICAgY292ZXJhZ2UgPSBoYWxmKGFsbChncmVhdGVyVGhhbihmbG9hdDQoc2tfRnJhZ0Nvb3JkLnh5LCB1cmVjdFVuaWZvcm1fU3RhZ2UxX2MwLnp3KSwgZmxvYXQ0KHVyZWN0VW5pZm9ybV9TdGFnZTFfYzAueHksIHNrX0ZyYWdDb29yZC54eSkpKSA/IDEgOiAwKTsKCQlicmVhazsKCQlkZWZhdWx0OiAgICAgICAgaGFsZjQgZGlzdHM0ID0gY2xhbXAoaGFsZjQoMS4wLCAxLjAsIC0xLjAsIC0xLjApICogaGFsZjQoc2tfRnJhZ0Nvb3JkLnh5eHkgLSB1cmVjdFVuaWZvcm1fU3RhZ2UxX2MwKSwgMC4wLCAxLjApOwoJCWhhbGYyIGRpc3RzMiA9IChkaXN0czQueHkgKyBkaXN0czQuencpIC0gMS4wOwoJCWNvdmVyYWdlID0gZGlzdHMyLnggKiBkaXN0czIueTsKCX0KCUBpZiAoMSA9PSAyIHx8IDEgPT0gMykgCgl7CgkJY292ZXJhZ2UgPSAxLjAgLSBjb3ZlcmFnZTsKCX0KCXJldHVybiBfaW5wdXQgKiBjb3ZlcmFnZTsKfQp2b2lkIG1haW4oKSAKewoJaGFsZjQgb3V0cHV0Q29sb3JfU3RhZ2UwOwoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfU3RhZ2UwOwoJewoJCS8vIFN0YWdlIDAsIENpcmNsZUdlb21ldHJ5UHJvY2Vzc29yCgkJZmxvYXQ0IGNpcmNsZUVkZ2U7CgkJY2lyY2xlRWRnZSA9IHZpbkNpcmNsZUVkZ2VfU3RhZ2UwOwoJCW91dHB1dENvbG9yX1N0YWdlMCA9IHZpbkNvbG9yX1N0YWdlMDsKCQlmbG9hdCBkID0gbGVuZ3RoKGNpcmNsZUVkZ2UueHkpOwoJCWhhbGYgZGlzdGFuY2VUb091dGVyRWRnZSA9IGhhbGYoY2lyY2xlRWRnZS56ICogKDEuMCAtIGQpKTsKCQloYWxmIGVkZ2VBbHBoYSA9IHNhdHVyYXRlKGRpc3RhbmNlVG9PdXRlckVkZ2UpOwoJCWhhbGYgZGlzdGFuY2VUb0lubmVyRWRnZSA9IGhhbGYoY2lyY2xlRWRnZS56ICogKGQgLSBjaXJjbGVFZGdlLncpKTsKCQloYWxmIGlubmVyQWxwaGEgPSBzYXR1cmF0ZShkaXN0YW5jZVRvSW5uZXJFZGdlKTsKCQllZGdlQWxwaGEgKj0gaW5uZXJBbHBoYTsKCQlvdXRwdXRDb3ZlcmFnZV9TdGFnZTAgPSBoYWxmNChlZGdlQWxwaGEpOwoJfQoJaGFsZjQgb3V0cHV0X1N0YWdlMTsKCW91dHB1dF9TdGFnZTEgPSBBQVJlY3RFZmZlY3RfU3RhZ2UxX2MwKG91dHB1dENvdmVyYWdlX1N0YWdlMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfU3RhZ2UwICogb3V0cHV0X1N0YWdlMTsKCX0KfQoAAAEBAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IADAAAAGluQ2lyY2xlRWRnZQEAAAAAAAAA","BYIBQAAAAQAAAAABCYIR7777777QAAAAAAAAAAAAXABAAAAACAAAAAEASAIQAAAA":"AwAAAExTS1NnAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwpvdXQgaGFsZjQgdmNvbG9yX1N0YWdlMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgRGVmYXVsdEdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBjb2xvciA9IGluQ29sb3I7Cgl2Y29sb3JfU3RhZ2UwID0gY29sb3I7CglmbG9hdDIgX3RtcF8wX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJZmxvYXQyIF90bXBfMV9pblBvc2l0aW9uID0gaW5Qb3NpdGlvbjsKCXNrX1Bvc2l0aW9uID0gZmxvYXQ0KF90bXBfMF9pblBvc2l0aW9uLnggLCBfdG1wXzBfaW5Qb3NpdGlvbi55LCAwLCAxKTsKfQoAAAAAAAAAAAAAAAAAVQEAAGluIGhhbGY0IHZjb2xvcl9TdGFnZTA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CnZvaWQgbWFpbigpIAp7CgloYWxmNCBvdXRwdXRDb2xvcl9TdGFnZTA7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TdGFnZTA7Cgl7CgkJLy8gU3RhZ2UgMCwgRGVmYXVsdEdlb21ldHJ5UHJvY2Vzc29yCgkJb3V0cHV0Q29sb3JfU3RhZ2UwID0gdmNvbG9yX1N0YWdlMDsKCQlvdXRwdXRDb3ZlcmFnZV9TdGFnZTAgPSBoYWxmNCgxKTsKCX0KCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TdGFnZTAgKiBvdXRwdXRDb3ZlcmFnZV9TdGFnZTA7Cgl9Cn0KAAAAAAAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAIAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IAAQAAAAAAAAA=","GJQQAAAAMAAGGADDACRQAAAAMAACHQDCABRQAI7CAMAAAAAAK4AAAAAAAIAAAAAQGIBAAAA":"AwAAAExTS1PfCwAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0NCByYWRpaV9zZWxlY3RvcjsKaW4gZmxvYXQ0IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHM7CmluIGZsb2F0NCBhYV9ibG9hdF9hbmRfY292ZXJhZ2U7CmluIGZsb2F0NCBza2V3OwppbiBmbG9hdDIgdHJhbnNsYXRlOwppbiBmbG9hdDQgcmFkaWlfeDsKaW4gZmxvYXQ0IHJhZGlpX3k7CmluIGhhbGY0IGNvbG9yOwpmbGF0IG91dCBoYWxmNCB2Y29sb3JfU3RhZ2UwOwpvdXQgZmxvYXQyIHZhcmNjb29yZF9TdGFnZTA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIEdyRmlsbFJSZWN0T3A6OlByb2Nlc3NvcgoJdmNvbG9yX1N0YWdlMCA9IGNvbG9yOwoJZmxvYXQgYWFfYmxvYXRfbXVsdGlwbGllciA9IDE7CglmbG9hdDIgY29ybmVyID0gY29ybmVyX2FuZF9yYWRpdXNfb3V0c2V0cy54eTsKCWZsb2F0MiByYWRpdXNfb3V0c2V0ID0gY29ybmVyX2FuZF9yYWRpdXNfb3V0c2V0cy56dzsKCWZsb2F0MiBhYV9ibG9hdF9kaXJlY3Rpb24gPSBhYV9ibG9hdF9hbmRfY292ZXJhZ2UueHk7CglmbG9hdCBpc19saW5lYXJfY292ZXJhZ2UgPSBhYV9ibG9hdF9hbmRfY292ZXJhZ2UudzsKCWZsb2F0MiBwaXhlbGxlbmd0aCA9IGludmVyc2VzcXJ0KGZsb2F0Mihkb3Qoc2tldy54eiwgc2tldy54eiksIGRvdChza2V3Lnl3LCBza2V3Lnl3KSkpOwoJZmxvYXQ0IG5vcm1hbGl6ZWRfYXhpc19kaXJzID0gc2tldyAqIHBpeGVsbGVuZ3RoLnh5eHk7CglmbG9hdDIgYXhpc3dpZHRocyA9IChhYnMobm9ybWFsaXplZF9heGlzX2RpcnMueHkpICsgYWJzKG5vcm1hbGl6ZWRfYXhpc19kaXJzLnp3KSk7CglmbG9hdDIgYWFfYmxvYXRyYWRpdXMgPSBheGlzd2lkdGhzICogcGl4ZWxsZW5ndGggKiAuNTsKCWZsb2F0NCByYWRpaV9hbmRfbmVpZ2hib3JzID0gcmFkaWlfc2VsZWN0b3IqIGZsb2F0NHg0KHJhZGlpX3gsIHJhZGlpX3ksIHJhZGlpX3gueXh3eiwgcmFkaWlfeS53enl4KTsKCWZsb2F0MiByYWRpaSA9IHJhZGlpX2FuZF9uZWlnaGJvcnMueHk7CglmbG9hdDIgbmVpZ2hib3JfcmFkaWkgPSByYWRpaV9hbmRfbmVpZ2hib3JzLnp3OwoJZmxvYXQgY292ZXJhZ2VfbXVsdGlwbGllciA9IDE7CglpZiAoYW55KGdyZWF0ZXJUaGFuKGFhX2Jsb2F0cmFkaXVzLCBmbG9hdDIoMSkpKSkgCgl7CgkJY29ybmVyID0gbWF4KGFicyhjb3JuZXIpLCBhYV9ibG9hdHJhZGl1cykgKiBzaWduKGNvcm5lcik7CgkJY292ZXJhZ2VfbXVsdGlwbGllciA9IDEgLyAobWF4KGFhX2Jsb2F0cmFkaXVzLngsIDEpICogbWF4KGFhX2Jsb2F0cmFkaXVzLnksIDEpKTsKCQlyYWRpaSA9IGZsb2F0MigwKTsKCX0KCWZsb2F0IGNvdmVyYWdlID0gYWFfYmxvYXRfYW5kX2NvdmVyYWdlLno7CglpZiAoYW55KGxlc3NUaGFuKHJhZGlpLCBhYV9ibG9hdHJhZGl1cyAqIDEuNSkpKSAKCXsKCQlyYWRpaSA9IGZsb2F0MigwKTsKCQlhYV9ibG9hdF9kaXJlY3Rpb24gPSBzaWduKGNvcm5lcik7CgkJaWYgKGNvdmVyYWdlID4gLjUpIAoJCXsKCQkJYWFfYmxvYXRfZGlyZWN0aW9uID0gLWFhX2Jsb2F0X2RpcmVjdGlvbjsKCQl9CgkJaXNfbGluZWFyX2NvdmVyYWdlID0gMTsKCX0KCWVsc2UgCgl7CgkJcmFkaWkgPSBjbGFtcChyYWRpaSwgcGl4ZWxsZW5ndGggKiAxLjUsIDIgLSBwaXhlbGxlbmd0aCAqIDEuNSk7CgkJbmVpZ2hib3JfcmFkaWkgPSBjbGFtcChuZWlnaGJvcl9yYWRpaSwgcGl4ZWxsZW5ndGggKiAxLjUsIDIgLSBwaXhlbGxlbmd0aCAqIDEuNSk7CgkJZmxvYXQyIHNwYWNpbmcgPSAyIC0gcmFkaWkgLSBuZWlnaGJvcl9yYWRpaTsKCQlmbG9hdDIgZXh0cmFfcGFkID0gbWF4KHBpeGVsbGVuZ3RoICogLjA2MjUgLSBzcGFjaW5nLCBmbG9hdDIoMCkpOwoJCXJhZGlpIC09IGV4dHJhX3BhZCAqIC41OwoJfQoJZmxvYXQyIGFhX291dHNldCA9IGFhX2Jsb2F0X2RpcmVjdGlvbiAqIGFhX2Jsb2F0cmFkaXVzICogYWFfYmxvYXRfbXVsdGlwbGllcjsKCWZsb2F0MiB2ZXJ0ZXhwb3MgPSBjb3JuZXIgKyByYWRpdXNfb3V0c2V0ICogcmFkaWkgKyBhYV9vdXRzZXQ7CglpZiAoY292ZXJhZ2UgPiAuNSkgCgl7CgkJaWYgKGFhX2Jsb2F0X2RpcmVjdGlvbi54ICE9IDAgJiYgdmVydGV4cG9zLnggKiBjb3JuZXIueCA8IDApIAoJCXsKCQkJZmxvYXQgYmFja3NldCA9IGFicyh2ZXJ0ZXhwb3MueCk7CgkJCXZlcnRleHBvcy54ID0gMDsKCQkJdmVydGV4cG9zLnkgKz0gYmFja3NldCAqIHNpZ24oY29ybmVyLnkpICogcGl4ZWxsZW5ndGgueS9waXhlbGxlbmd0aC54OwoJCQljb3ZlcmFnZSA9IChjb3ZlcmFnZSAtIC41KSAqIGFicyhjb3JuZXIueCkgLyAoYWJzKGNvcm5lci54KSArIGJhY2tzZXQpICsgLjU7CgkJfQoJCWlmIChhYV9ibG9hdF9kaXJlY3Rpb24ueSAhPSAwICYmIHZlcnRleHBvcy55ICogY29ybmVyLnkgPCAwKSAKCQl7CgkJCWZsb2F0IGJhY2tzZXQgPSBhYnModmVydGV4cG9zLnkpOwoJCQl2ZXJ0ZXhwb3MueSA9IDA7CgkJCXZlcnRleHBvcy54ICs9IGJhY2tzZXQgKiBzaWduKGNvcm5lci54KSAqIHBpeGVsbGVuZ3RoLngvcGl4ZWxsZW5ndGgueTsKCQkJY292ZXJhZ2UgPSAoY292ZXJhZ2UgLSAuNSkgKiBhYnMoY29ybmVyLnkpIC8gKGFicyhjb3JuZXIueSkgKyBiYWNrc2V0KSArIC41OwoJCX0KCX0KCWZsb2F0MngyIHNrZXdtYXRyaXggPSBmbG9hdDJ4Mihza2V3Lnh5LCBza2V3Lnp3KTsKCWZsb2F0MiBkZXZjb29yZCA9IHZlcnRleHBvcyAqIHNrZXdtYXRyaXggKyB0cmFuc2xhdGU7CglpZiAoMCAhPSBpc19saW5lYXJfY292ZXJhZ2UpIAoJewoJCXZhcmNjb29yZF9TdGFnZTAueHkgPSBmbG9hdDIoMCwgY292ZXJhZ2UgKiBjb3ZlcmFnZV9tdWx0aXBsaWVyKTsKCX0KCWVsc2UgCgl7CgkJZmxvYXQyIGFyY2Nvb3JkID0gMSAtIGFicyhyYWRpdXNfb3V0c2V0KSArIGFhX291dHNldC9yYWRpaSAqIGNvcm5lcjsKCQl2YXJjY29vcmRfU3RhZ2UwLnh5ID0gZmxvYXQyKGFyY2Nvb3JkLngrMSwgYXJjY29vcmQueSk7Cgl9Cglza19Qb3NpdGlvbiA9IGZsb2F0NChkZXZjb29yZC54ICwgZGV2Y29vcmQueSwgMCwgMSk7Cn0KAAAAAAAAAAAAAAAAALYCAABmbGF0IGluIGhhbGY0IHZjb2xvcl9TdGFnZTA7CmluIGZsb2F0MiB2YXJjY29vcmRfU3RhZ2UwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJaGFsZjQgb3V0cHV0Q29sb3JfU3RhZ2UwOwoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfU3RhZ2UwOwoJewoJCS8vIFN0YWdlIDAsIEdyRmlsbFJSZWN0T3A6OlByb2Nlc3NvcgoJCW91dHB1dENvbG9yX1N0YWdlMCA9IHZjb2xvcl9TdGFnZTA7CgkJZmxvYXQgeF9wbHVzXzE9dmFyY2Nvb3JkX1N0YWdlMC54LCB5PXZhcmNjb29yZF9TdGFnZTAueTsKCQloYWxmIGNvdmVyYWdlOwoJCWlmICgwID09IHhfcGx1c18xKSAKCQl7CgkJCWNvdmVyYWdlID0gaGFsZih5KTsKCQl9CgkJZWxzZSAKCQl7CgkJCWZsb2F0IGZuID0geF9wbHVzXzEgKiAoeF9wbHVzXzEgLSAyKTsKCQkJZm4gPSBmbWEoeSx5LCBmbik7CgkJCWZsb2F0IGZud2lkdGggPSBmd2lkdGgoZm4pOwoJCQljb3ZlcmFnZSA9IC41IC0gaGFsZihmbi9mbndpZHRoKTsKCQkJY292ZXJhZ2UgPSBjbGFtcChjb3ZlcmFnZSwgMCwgMSk7CgkJfQoJCW91dHB1dENvdmVyYWdlX1N0YWdlMCA9IGhhbGY0KGNvdmVyYWdlKTsKCX0KCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TdGFnZTAgKiBvdXRwdXRDb3ZlcmFnZV9TdGFnZTA7Cgl9Cn0KAAAAAAAAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAACAAAAA4AAAByYWRpaV9zZWxlY3RvcgAAGQAAAGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHMAAAAVAAAAYWFfYmxvYXRfYW5kX2NvdmVyYWdlAAAABAAAAHNrZXcJAAAAdHJhbnNsYXRlAAAABwAAAHJhZGlpX3gABwAAAHJhZGlpX3kABQAAAGNvbG9yAAAAAQAAAAAAAAA=","LIJAAAAAAAGAAAAAAIWP577774BSZ7X7777QCAAAAABAAAAAABBAMAHSEECQAAAAABOACAAAAAEAAAAAIDEAQAAAAA":"AwAAAExTS1MFAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdmxvY2FsQ29vcmRfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCXZsb2NhbENvb3JkX1N0YWdlMCA9IGxvY2FsQ29vcmQ7Cglza19Qb3NpdGlvbiA9IGZsb2F0NChwb3NpdGlvbi54ICwgcG9zaXRpb24ueSwgMCwgMSk7Cn0KAAAAAAEBAAAAAAAAAQEApwQAAHVuaWZvcm0gZmxvYXQ0IHVjaXJjbGVfU3RhZ2UxX2MwOwp1bmlmb3JtIHNhbXBsZXIyRCB1VGV4dHVyZVNhbXBsZXJfMF9TdGFnZTA7CmluIGZsb2F0MiB2bG9jYWxDb29yZF9TdGFnZTA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmlubGluZSBoYWxmNCBDaXJjbGVFZmZlY3RfU3RhZ2UxX2MwKGhhbGY0IF9pbnB1dCkgCnsKCWZsb2F0MiBwcmV2Q2VudGVyOwoJZmxvYXQgcHJldlJhZGl1cyA9IC0xLjAwMDAwMDsKCWhhbGYgZDsKCUBpZiAoMSA9PSAyIHx8IDEgPT0gMykgCgl7CgkJZCA9IGhhbGYoKGxlbmd0aCgodWNpcmNsZV9TdGFnZTFfYzAueHkgLSBza19GcmFnQ29vcmQueHkpICogdWNpcmNsZV9TdGFnZTFfYzAudykgLSAxLjApICogdWNpcmNsZV9TdGFnZTFfYzAueik7Cgl9CgllbHNlIAoJewoJCWQgPSBoYWxmKCgxLjAgLSBsZW5ndGgoKHVjaXJjbGVfU3RhZ2UxX2MwLnh5IC0gc2tfRnJhZ0Nvb3JkLnh5KSAqIHVjaXJjbGVfU3RhZ2UxX2MwLncpKSAqIHVjaXJjbGVfU3RhZ2UxX2MwLnopOwoJfQoJaGFsZjQgaW5wdXRDb2xvciA9IF9pbnB1dDsKCUBpZiAoMSA9PSAxIHx8IDEgPT0gMykgCgl7CgkJcmV0dXJuIGlucHV0Q29sb3IgKiBjbGFtcChkLCAwLjAsIDEuMCk7Cgl9CgllbHNlIAoJewoJCXJldHVybiBkID4gMC41ID8gaW5wdXRDb2xvciA6IGhhbGY0KDAuMCk7Cgl9Cn0Kdm9pZCBtYWluKCkgCnsKCWhhbGY0IG91dHB1dENvbG9yX1N0YWdlMDsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1N0YWdlMDsKCXsKCQkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCQlvdXRwdXRDb2xvcl9TdGFnZTAgPSBoYWxmNCgxKTsKCQlmbG9hdDIgdGV4Q29vcmQ7CgkJdGV4Q29vcmQgPSB2bG9jYWxDb29yZF9TdGFnZTA7CgkJb3V0cHV0Q29sb3JfU3RhZ2UwID0gKHNhbXBsZSh1VGV4dHVyZVNhbXBsZXJfMF9TdGFnZTAsIHRleENvb3JkKSAqIG91dHB1dENvbG9yX1N0YWdlMCk7CgkJb3V0cHV0Q292ZXJhZ2VfU3RhZ2UwID0gaGFsZjQoMSk7Cgl9CgloYWxmNCBvdXRwdXRfU3RhZ2UxOwoJb3V0cHV0X1N0YWdlMSA9IENpcmNsZUVmZmVjdF9TdGFnZTFfYzAob3V0cHV0Q292ZXJhZ2VfU3RhZ2UwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TdGFnZTAgKiBvdXRwdXRfU3RhZ2UxOwoJfQp9CgAAAQEAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","DISAAAAAMAAAAABAYDRP7H2CAIAAAABAAAAAAABAMQAAAVYAAAAAAAQAAAABAMQC":"AwAAAExTS1MHAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQyIHVBdGxhc1NpemVJbnZfU3RhZ2UwOwppbiBmbG9hdDIgaW5Qb3NpdGlvbjsKaW4gdXNob3J0MiBpblRleHR1cmVDb29yZHM7Cm91dCBmbG9hdDIgdlRleHR1cmVDb29yZHNfU3RhZ2UwOwpmbGF0IG91dCBmbG9hdCB2VGV4SW5kZXhfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBCaXRtYXBUZXh0CglpbnQgdGV4SWR4ID0gMDsKCWZsb2F0MiB1bm9ybVRleENvb3JkcyA9IGZsb2F0MihpblRleHR1cmVDb29yZHMueCwgaW5UZXh0dXJlQ29vcmRzLnkpOwoJdlRleHR1cmVDb29yZHNfU3RhZ2UwID0gdW5vcm1UZXhDb29yZHMgKiB1QXRsYXNTaXplSW52X1N0YWdlMDsKCXZUZXhJbmRleF9TdGFnZTAgPSBmbG9hdCh0ZXhJZHgpOwoJZmxvYXQyIF90bXBfMF9pblBvc2l0aW9uID0gaW5Qb3NpdGlvbjsKCXNrX1Bvc2l0aW9uID0gZmxvYXQ0KGluUG9zaXRpb24ueCAsIGluUG9zaXRpb24ueSwgMCwgMSk7Cn0KAAAAAAAAAAAAAAAAAFACAAB1bmlmb3JtIGhhbGY0IHVDb2xvcl9TdGFnZTA7CnVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1N0YWdlMDsKaW4gZmxvYXQyIHZUZXh0dXJlQ29vcmRzX1N0YWdlMDsKZmxhdCBpbiBmbG9hdCB2VGV4SW5kZXhfU3RhZ2UwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJaGFsZjQgb3V0cHV0Q29sb3JfU3RhZ2UwOwoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfU3RhZ2UwOwoJewoJCS8vIFN0YWdlIDAsIEJpdG1hcFRleHQKCQlvdXRwdXRDb2xvcl9TdGFnZTAgPSB1Q29sb3JfU3RhZ2UwOwoJCWhhbGY0IHRleENvbG9yOwoJCXsKCQkJdGV4Q29sb3IgPSBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfU3RhZ2UwLCB2VGV4dHVyZUNvb3Jkc19TdGFnZTApOwoJCX0KCQlvdXRwdXRDb2xvcl9TdGFnZTAgPSBvdXRwdXRDb2xvcl9TdGFnZTAgKiB0ZXhDb2xvcjsKCQlvdXRwdXRDb3ZlcmFnZV9TdGFnZTAgPSBoYWxmNCgxKTsKCX0KCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TdGFnZTAgKiBvdXRwdXRDb3ZlcmFnZV9TdGFnZTA7Cgl9Cn0KAAAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAIAAAAKAAAAaW5Qb3NpdGlvbgAADwAAAGluVGV4dHVyZUNvb3JkcwABAAAAAAAAAA==","B2AAQAAAAQAAAAABC3777777AAOAAAAAAAAAAAAAXABAAAAACAAAAAEASAIQAAAA":"AwAAAExTS1M3AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmIGluQ292ZXJhZ2U7Cm91dCBoYWxmIHZpbkNvdmVyYWdlX1N0YWdlMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgRGVmYXVsdEdlb21ldHJ5UHJvY2Vzc29yCglmbG9hdDIgX3RtcF8wX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJdmluQ292ZXJhZ2VfU3RhZ2UwID0gaW5Db3ZlcmFnZTsKCXNrX1Bvc2l0aW9uID0gZmxvYXQ0KF90bXBfMF9pblBvc2l0aW9uLnggLCBfdG1wXzBfaW5Qb3NpdGlvbi55LCAwLCAxKTsKfQoAAAAAAAAAAAAAAAAArAEAAHVuaWZvcm0gaGFsZjQgdUNvbG9yX1N0YWdlMDsKaW4gaGFsZiB2aW5Db3ZlcmFnZV9TdGFnZTA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CnZvaWQgbWFpbigpIAp7CgloYWxmNCBvdXRwdXRDb2xvcl9TdGFnZTA7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TdGFnZTA7Cgl7CgkJLy8gU3RhZ2UgMCwgRGVmYXVsdEdlb21ldHJ5UHJvY2Vzc29yCgkJb3V0cHV0Q29sb3JfU3RhZ2UwID0gdUNvbG9yX1N0YWdlMDsKCQloYWxmIGFscGhhID0gMS4wOwoJCWFscGhhID0gdmluQ292ZXJhZ2VfU3RhZ2UwOwoJCW91dHB1dENvdmVyYWdlX1N0YWdlMCA9IGhhbGY0KGFscGhhKTsKCX0KCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TdGFnZTAgKiBvdXRwdXRDb3ZlcmFnZV9TdGFnZTA7Cgl9Cn0KAAAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAIAAAAKAAAAaW5Qb3NpdGlvbgAACgAAAGluQ292ZXJhZ2UAAAEAAAAAAAAA","AWQQGAAAQAAIXCEPAGGP777777777737AAAAAAAAAAAAAXABAAAAACAAAAAEBSAI":"AwAAAExTS1MJAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TdGFnZTA7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiBmbG9hdDQgaW5DaXJjbGVFZGdlOwpvdXQgZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfU3RhZ2UwOwpvdXQgaGFsZjQgdmluQ29sb3JfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TdGFnZTAgPSBpbkNpcmNsZUVkZ2U7Cgl2aW5Db2xvcl9TdGFnZTAgPSBpbkNvbG9yOwoJZmxvYXQyIF90bXBfMF9pblBvc2l0aW9uID0gaW5Qb3NpdGlvbjsKCWZsb2F0MiBfdG1wXzFfaW5Qb3NpdGlvbiA9IHVsb2NhbE1hdHJpeF9TdGFnZTAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1N0YWdlMC55dzsKCXNrX1Bvc2l0aW9uID0gZmxvYXQ0KF90bXBfMF9pblBvc2l0aW9uLnggLCBfdG1wXzBfaW5Qb3NpdGlvbi55LCAwLCAxKTsKfQoAAAAAAAAAAAAAAAAAAADgAgAAaW4gZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfU3RhZ2UwOwppbiBoYWxmNCB2aW5Db2xvcl9TdGFnZTA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CnZvaWQgbWFpbigpIAp7CgloYWxmNCBvdXRwdXRDb2xvcl9TdGFnZTA7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TdGFnZTA7Cgl7CgkJLy8gU3RhZ2UgMCwgQ2lyY2xlR2VvbWV0cnlQcm9jZXNzb3IKCQlmbG9hdDQgY2lyY2xlRWRnZTsKCQljaXJjbGVFZGdlID0gdmluQ2lyY2xlRWRnZV9TdGFnZTA7CgkJb3V0cHV0Q29sb3JfU3RhZ2UwID0gdmluQ29sb3JfU3RhZ2UwOwoJCWZsb2F0IGQgPSBsZW5ndGgoY2lyY2xlRWRnZS54eSk7CgkJaGFsZiBkaXN0YW5jZVRvT3V0ZXJFZGdlID0gaGFsZihjaXJjbGVFZGdlLnogKiAoMS4wIC0gZCkpOwoJCWhhbGYgZWRnZUFscGhhID0gc2F0dXJhdGUoZGlzdGFuY2VUb091dGVyRWRnZSk7CgkJaGFsZiBkaXN0YW5jZVRvSW5uZXJFZGdlID0gaGFsZihjaXJjbGVFZGdlLnogKiAoZCAtIGNpcmNsZUVkZ2UudykpOwoJCWhhbGYgaW5uZXJBbHBoYSA9IHNhdHVyYXRlKGRpc3RhbmNlVG9Jbm5lckVkZ2UpOwoJCWVkZ2VBbHBoYSAqPSBpbm5lckFscGhhOwoJCW91dHB1dENvdmVyYWdlX1N0YWdlMCA9IGhhbGY0KGVkZ2VBbHBoYSk7Cgl9Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfU3RhZ2UwICogb3V0cHV0Q292ZXJhZ2VfU3RhZ2UwOwoJfQp9CgAAAAABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAADAAAACgAAAGluUG9zaXRpb24AAAcAAABpbkNvbG9yAAwAAABpbkNpcmNsZUVkZ2UBAAAAAAAAAA==","LJIAAAAAAAMAAAAAARMPZ72HPQCFR7H7777QGAAAAAAAAAAA6RDQB4AA6AAPAAHQDQAAAAAAEIFQAAAAEQGQAAAAACHQIAAAADQBAAAAADABKAAAACAAAAAAACCIYAAA":"AwAAAExTS1O1AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TdGFnZTFfYzBfYzBfYzA7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7CmluIGZsb2F0MiBsb2NhbENvb3JkOwpmbGF0IG91dCBoYWxmNCB2Y29sb3JfU3RhZ2UwOwpvdXQgZmxvYXQyIHZUcmFuc2Zvcm1lZENvb3Jkc18wX1N0YWdlMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgl2Y29sb3JfU3RhZ2UwID0gY29sb3I7Cglza19Qb3NpdGlvbiA9IGZsb2F0NChwb3NpdGlvbi54ICwgcG9zaXRpb24ueSwgMCwgMSk7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzBfU3RhZ2UwID0gKCgodW1hdHJpeF9TdGFnZTFfYzBfYzBfYzApKSAqIGxvY2FsQ29vcmQueHkxKS54eTsKCX0KfQoAAAAAAAAAAAAAAAAAAAAvBwAAdW5pZm9ybSBoYWxmNCB1bGVmdEJvcmRlckNvbG9yX1N0YWdlMV9jMF9jMDsKdW5pZm9ybSBoYWxmNCB1cmlnaHRCb3JkZXJDb2xvcl9TdGFnZTFfYzBfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TdGFnZTFfYzBfYzBfYzA7CnVuaWZvcm0gaGFsZjQgdXN0YXJ0X1N0YWdlMV9jMF9jMF9jMTsKdW5pZm9ybSBoYWxmNCB1ZW5kX1N0YWdlMV9jMF9jMF9jMTsKZmxhdCBpbiBoYWxmNCB2Y29sb3JfU3RhZ2UwOwppbiBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzBfU3RhZ2UwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBMaW5lYXJHcmFkaWVudExheW91dF9TdGFnZTFfYzBfYzBfYzBfYzAoaGFsZjQgX2lucHV0KSAKewoJaGFsZiB0ID0gaGFsZih2VHJhbnNmb3JtZWRDb29yZHNfMF9TdGFnZTAueCkgKyA5Ljk5OTk5OTc0NzM3ODc1MTZlLTA2OwoJcmV0dXJuIGhhbGY0KHQsIDEuMCwgMC4wLCAwLjApOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzBfYzAoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIExpbmVhckdyYWRpZW50TGF5b3V0X1N0YWdlMV9jMF9jMF9jMF9jMChfaW5wdXQpOwp9CmhhbGY0IFNpbmdsZUludGVydmFsR3JhZGllbnRDb2xvcml6ZXJfU3RhZ2UxX2MwX2MwX2MxKGhhbGY0IF9pbnB1dCwgZmxvYXQyIF9jb29yZHMpIAp7CgloYWxmIHQgPSBoYWxmKF9jb29yZHMueCk7CglyZXR1cm4gbWl4KHVzdGFydF9TdGFnZTFfYzBfYzBfYzEsIHVlbmRfU3RhZ2UxX2MwX2MwX2MxLCB0KTsKfQpoYWxmNCBDbGFtcGVkR3JhZGllbnRFZmZlY3RfU3RhZ2UxX2MwX2MwKGhhbGY0IF9pbnB1dCkgCnsKCWhhbGY0IHQgPSBNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKF9pbnB1dCk7CgloYWxmNCBvdXRDb2xvcjsKCWlmICghdHJ1ZSAmJiB0LnkgPCAwLjApIAoJewoJCW91dENvbG9yID0gaGFsZjQoMC4wKTsKCX0KCWVsc2UgaWYgKHQueCA8IDAuMCkgCgl7CgkJb3V0Q29sb3IgPSB1bGVmdEJvcmRlckNvbG9yX1N0YWdlMV9jMF9jMDsKCX0KCWVsc2UgaWYgKHQueCA+IDEuMCkgCgl7CgkJb3V0Q29sb3IgPSB1cmlnaHRCb3JkZXJDb2xvcl9TdGFnZTFfYzBfYzA7Cgl9CgllbHNlIAoJewoJCW91dENvbG9yID0gU2luZ2xlSW50ZXJ2YWxHcmFkaWVudENvbG9yaXplcl9TdGFnZTFfYzBfYzBfYzEoX2lucHV0LCBmbG9hdDIoaGFsZjIodC54LCAwLjApKSk7Cgl9CglAaWYgKHRydWUpIAoJewoJCW91dENvbG9yLnh5eiAqPSBvdXRDb2xvci53OwoJfQoJcmV0dXJuIG91dENvbG9yOwp9CmlubGluZSBoYWxmNCBPdmVycmlkZUlucHV0RnJhZ21lbnRQcm9jZXNzb3JfU3RhZ2UxX2MwKGhhbGY0IF9pbnB1dCkgCnsKCXJldHVybiBDbGFtcGVkR3JhZGllbnRFZmZlY3RfU3RhZ2UxX2MwX2MwKGZhbHNlID8gaGFsZjQoMCkgOiBoYWxmNCgxLjAwMDAwMCwgMS4wMDAwMDAsIDEuMDAwMDAwLCAxLjAwMDAwMCkpOwp9CnZvaWQgbWFpbigpIAp7CgloYWxmNCBvdXRwdXRDb2xvcl9TdGFnZTA7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TdGFnZTA7Cgl7CgkJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgkJb3V0cHV0Q29sb3JfU3RhZ2UwID0gdmNvbG9yX1N0YWdlMDsKCQlvdXRwdXRDb3ZlcmFnZV9TdGFnZTAgPSBoYWxmNCgxKTsKCX0KCWhhbGY0IG91dHB1dF9TdGFnZTE7CglvdXRwdXRfU3RhZ2UxID0gT3ZlcnJpZGVJbnB1dEZyYWdtZW50UHJvY2Vzc29yX1N0YWdlMV9jMChvdXRwdXRDb2xvcl9TdGFnZTApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dF9TdGFnZTEgKiBvdXRwdXRDb3ZlcmFnZV9TdGFnZTA7Cgl9Cn0KAAAAAAABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAADAAAACAAAAHBvc2l0aW9uBQAAAGNvbG9yAAAACgAAAGxvY2FsQ29vcmQAAAEAAAAAAAAA","LIQACAAAAAGAAAAAAIWP57ZDH37P7777777QCAAAAAAAAAAAMIQHSAAAAAAQAAAAABLQAAAAAABAAAAACAZAEAAAAA":"AwAAAExTS1PvAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7Cm91dCBoYWxmNCB2Y29sb3JfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCXZjb2xvcl9TdGFnZTAgPSBjb2xvcjsKCXNrX1Bvc2l0aW9uID0gZmxvYXQ0KHBvc2l0aW9uLnggLCBwb3NpdGlvbi55LCAwLCAxKTsKfQoAAAEBAAAAAAAAAQEALgMAAHVuaWZvcm0gZmxvYXQ0IHVpbm5lclJlY3RfU3RhZ2UxX2MwOwp1bmlmb3JtIGhhbGYyIHVyYWRpdXNQbHVzSGFsZl9TdGFnZTFfYzA7CmluIGhhbGY0IHZjb2xvcl9TdGFnZTA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmlubGluZSBoYWxmNCBDaXJjdWxhclJSZWN0X1N0YWdlMV9jMChoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgZHh5MCA9IHVpbm5lclJlY3RfU3RhZ2UxX2MwLkxUIC0gc2tfRnJhZ0Nvb3JkLnh5OwoJZmxvYXQyIGR4eTEgPSBza19GcmFnQ29vcmQueHkgLSB1aW5uZXJSZWN0X1N0YWdlMV9jMC5SQjsKCWZsb2F0MiBkeHkgPSBtYXgobWF4KGR4eTAsIGR4eTEpLCAwLjApOwoJaGFsZiBhbHBoYSA9IGhhbGYoc2F0dXJhdGUodXJhZGl1c1BsdXNIYWxmX1N0YWdlMV9jMC54IC0gbGVuZ3RoKGR4eSkpKTsKCXJldHVybiBfaW5wdXQgKiBhbHBoYTsKfQp2b2lkIG1haW4oKSAKewoJaGFsZjQgb3V0cHV0Q29sb3JfU3RhZ2UwOwoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfU3RhZ2UwOwoJewoJCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJCW91dHB1dENvbG9yX1N0YWdlMCA9IHZjb2xvcl9TdGFnZTA7CgkJb3V0cHV0Q292ZXJhZ2VfU3RhZ2UwID0gaGFsZjQoMSk7Cgl9CgloYWxmNCBvdXRwdXRfU3RhZ2UxOwoJb3V0cHV0X1N0YWdlMSA9IENpcmN1bGFyUlJlY3RfU3RhZ2UxX2MwKG91dHB1dENvdmVyYWdlX1N0YWdlMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfU3RhZ2UwICogb3V0cHV0X1N0YWdlMTsKCX0KfQoAAAABAQABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAACAAAACAAAAHBvc2l0aW9uBQAAAGNvbG9yAAAAAQAAAAAAAAA=","LIIAAAAAAAGAAAAAAIWP577774BSZ7X7777QCAAAAAAAAAAAHIRQYAAAAAAAAAQAAAAHQBQAIAAAAAEAKMAYIDAAAAAAAAAAAC4AEAAAAAIAAAAAQCIBCAAAAA":"AwAAAExTS1MFAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdkxvY2FsQ29vcmRfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCXZMb2NhbENvb3JkX1N0YWdlMCA9IGxvY2FsQ29vcmQ7Cglza19Qb3NpdGlvbiA9IGZsb2F0NChwb3NpdGlvbi54ICwgcG9zaXRpb24ueSwgMCwgMSk7Cn0KAAAAAAAAAAAAAAAAAAAAdREAAHVuaWZvcm0gaGFsZjIgdV8wX0luY3JlbWVudF9TdGFnZTFfYzA7CnVuaWZvcm0gaGFsZjQgdV8xX0tlcm5lbF9TdGFnZTFfYzBbN107CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TdGFnZTFfYzBfYzA7CnVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1N0YWdlMTsKaW4gZmxvYXQyIHZMb2NhbENvb3JkX1N0YWdlMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgVGV4dHVyZUVmZmVjdF9TdGFnZTFfYzBfYzBfYzAoaGFsZjQgX2lucHV0LCBmbG9hdDIgX2Nvb3JkcykgCnsKCXJldHVybiBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfU3RhZ2UxLCBfY29vcmRzKTsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwKGhhbGY0IF9pbnB1dCwgZmxvYXQyIF9jb29yZHMpIAp7CglyZXR1cm4gVGV4dHVyZUVmZmVjdF9TdGFnZTFfYzBfYzBfYzAoX2lucHV0LCAoKHVtYXRyaXhfU3RhZ2UxX2MwX2MwKSAqIF9jb29yZHMueHkxKS54eSk7Cn0KaW5saW5lIGhhbGY0IEdhdXNzaWFuQ29udm9sdXRpb25fU3RhZ2UxX2MwKGhhbGY0IF9pbnB1dCkgCnsKCWhhbGY0IF8yX2NvbG9yID0gaGFsZjQoMC4wKTsKCWZsb2F0MiBfM19jb29yZCA9ICh2TG9jYWxDb29yZF9TdGFnZTAgLSBmbG9hdDIoKDEyLjAgKiB1XzBfSW5jcmVtZW50X1N0YWdlMV9jMCkpKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzBdLngpKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzBdLnkpKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzBdLnopKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzBdLncpKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzFdLngpKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzFdLnkpKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzFdLnopKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzFdLncpKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzJdLngpKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzJdLnkpKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzJdLnopKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzJdLncpKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzNdLngpKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzNdLnkpKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzNdLnopKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzNdLncpKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzRdLngpKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzRdLnkpKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzRdLnopKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzRdLncpKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzVdLngpKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzVdLnkpKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzVdLnopKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzVdLncpKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzZdLngpKTsKCXJldHVybiBfMl9jb2xvcjsKfQp2b2lkIG1haW4oKSAKewoJaGFsZjQgb3V0cHV0Q29sb3JfU3RhZ2UwOwoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfU3RhZ2UwOwoJewoJCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJCW91dHB1dENvbG9yX1N0YWdlMCA9IGhhbGY0KDEpOwoJCW91dHB1dENvdmVyYWdlX1N0YWdlMCA9IGhhbGY0KDEpOwoJfQoJaGFsZjQgb3V0cHV0X1N0YWdlMTsKCW91dHB1dF9TdGFnZTEgPSBHYXVzc2lhbkNvbnZvbHV0aW9uX1N0YWdlMV9jMChvdXRwdXRDb2xvcl9TdGFnZTApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dF9TdGFnZTEgKiBvdXRwdXRDb3ZlcmFnZV9TdGFnZTA7Cgl9Cn0KAAAAAAAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24KAAAAbG9jYWxDb29yZAAAAQAAAAAAAAA=","LIJAAAAAAAGAAAAAAIWP577774BSZ7X7777QCAAAAABAAAAAABBAMADCEB4QAAAAAEAAAAAAK4AAAAAAAIAAAAAQGIBAAAAA":"AwAAAExTS1MFAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdmxvY2FsQ29vcmRfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCXZsb2NhbENvb3JkX1N0YWdlMCA9IGxvY2FsQ29vcmQ7Cglza19Qb3NpdGlvbiA9IGZsb2F0NChwb3NpdGlvbi54ICwgcG9zaXRpb24ueSwgMCwgMSk7Cn0KAAAAAAEBAAAAAAAAAQEA6QMAAHVuaWZvcm0gZmxvYXQ0IHVpbm5lclJlY3RfU3RhZ2UxX2MwOwp1bmlmb3JtIGhhbGYyIHVyYWRpdXNQbHVzSGFsZl9TdGFnZTFfYzA7CnVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1N0YWdlMDsKaW4gZmxvYXQyIHZsb2NhbENvb3JkX1N0YWdlMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaW5saW5lIGhhbGY0IENpcmN1bGFyUlJlY3RfU3RhZ2UxX2MwKGhhbGY0IF9pbnB1dCkgCnsKCWZsb2F0MiBkeHkwID0gdWlubmVyUmVjdF9TdGFnZTFfYzAuTFQgLSBza19GcmFnQ29vcmQueHk7CglmbG9hdDIgZHh5MSA9IHNrX0ZyYWdDb29yZC54eSAtIHVpbm5lclJlY3RfU3RhZ2UxX2MwLlJCOwoJZmxvYXQyIGR4eSA9IG1heChtYXgoZHh5MCwgZHh5MSksIDAuMCk7CgloYWxmIGFscGhhID0gaGFsZihzYXR1cmF0ZSh1cmFkaXVzUGx1c0hhbGZfU3RhZ2UxX2MwLnggLSBsZW5ndGgoZHh5KSkpOwoJcmV0dXJuIF9pbnB1dCAqIGFscGhhOwp9CnZvaWQgbWFpbigpIAp7CgloYWxmNCBvdXRwdXRDb2xvcl9TdGFnZTA7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TdGFnZTA7Cgl7CgkJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgkJb3V0cHV0Q29sb3JfU3RhZ2UwID0gaGFsZjQoMSk7CgkJZmxvYXQyIHRleENvb3JkOwoJCXRleENvb3JkID0gdmxvY2FsQ29vcmRfU3RhZ2UwOwoJCW91dHB1dENvbG9yX1N0YWdlMCA9IChzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfU3RhZ2UwLCB0ZXhDb29yZCkgKiBvdXRwdXRDb2xvcl9TdGFnZTApOwoJCW91dHB1dENvdmVyYWdlX1N0YWdlMCA9IGhhbGY0KDEpOwoJfQoJaGFsZjQgb3V0cHV0X1N0YWdlMTsKCW91dHB1dF9TdGFnZTEgPSBDaXJjdWxhclJSZWN0X1N0YWdlMV9jMChvdXRwdXRDb3ZlcmFnZV9TdGFnZTApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1N0YWdlMCAqIG91dHB1dF9TdGFnZTE7Cgl9Cn0KAAAAAAEBAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24KAAAAbG9jYWxDb29yZAAAAQAAAAAAAAA=","B2IASAAAAQAAAAABCYIR7777AAOAAAAAAAAAAAAAXABAAAAACAAAAAEASAIQAAAA":"AwAAAExTS1NwAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiBoYWxmIGluQ292ZXJhZ2U7Cm91dCBoYWxmNCB2Y29sb3JfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBEZWZhdWx0R2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IGNvbG9yID0gaW5Db2xvcjsKCWNvbG9yID0gY29sb3IgKiBpbkNvdmVyYWdlOwoJdmNvbG9yX1N0YWdlMCA9IGNvbG9yOwoJZmxvYXQyIF90bXBfMF9pblBvc2l0aW9uID0gaW5Qb3NpdGlvbjsKCXNrX1Bvc2l0aW9uID0gZmxvYXQ0KF90bXBfMF9pblBvc2l0aW9uLnggLCBfdG1wXzBfaW5Qb3NpdGlvbi55LCAwLCAxKTsKfQoAAAAAAAAAAAAAAABVAQAAaW4gaGFsZjQgdmNvbG9yX1N0YWdlMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKdm9pZCBtYWluKCkgCnsKCWhhbGY0IG91dHB1dENvbG9yX1N0YWdlMDsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1N0YWdlMDsKCXsKCQkvLyBTdGFnZSAwLCBEZWZhdWx0R2VvbWV0cnlQcm9jZXNzb3IKCQlvdXRwdXRDb2xvcl9TdGFnZTAgPSB2Y29sb3JfU3RhZ2UwOwoJCW91dHB1dENvdmVyYWdlX1N0YWdlMCA9IGhhbGY0KDEpOwoJfQoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1N0YWdlMCAqIG91dHB1dENvdmVyYWdlX1N0YWdlMDsKCX0KfQoAAAAAAAAAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAwAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAKAAAAaW5Db3ZlcmFnZQAAAQAAAAAAAAA=","GJQQAAAAMAAGGADDACRQAAAAMAACHQDCABRQAI7CAMAAAABAC5JAAAAAADABKAAAACAAAAAAACCIYAAAAAAA":"AwAAAExTS1PfCwAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0NCByYWRpaV9zZWxlY3RvcjsKaW4gZmxvYXQ0IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHM7CmluIGZsb2F0NCBhYV9ibG9hdF9hbmRfY292ZXJhZ2U7CmluIGZsb2F0NCBza2V3OwppbiBmbG9hdDIgdHJhbnNsYXRlOwppbiBmbG9hdDQgcmFkaWlfeDsKaW4gZmxvYXQ0IHJhZGlpX3k7CmluIGhhbGY0IGNvbG9yOwpmbGF0IG91dCBoYWxmNCB2Y29sb3JfU3RhZ2UwOwpvdXQgZmxvYXQyIHZhcmNjb29yZF9TdGFnZTA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIEdyRmlsbFJSZWN0T3A6OlByb2Nlc3NvcgoJdmNvbG9yX1N0YWdlMCA9IGNvbG9yOwoJZmxvYXQgYWFfYmxvYXRfbXVsdGlwbGllciA9IDE7CglmbG9hdDIgY29ybmVyID0gY29ybmVyX2FuZF9yYWRpdXNfb3V0c2V0cy54eTsKCWZsb2F0MiByYWRpdXNfb3V0c2V0ID0gY29ybmVyX2FuZF9yYWRpdXNfb3V0c2V0cy56dzsKCWZsb2F0MiBhYV9ibG9hdF9kaXJlY3Rpb24gPSBhYV9ibG9hdF9hbmRfY292ZXJhZ2UueHk7CglmbG9hdCBpc19saW5lYXJfY292ZXJhZ2UgPSBhYV9ibG9hdF9hbmRfY292ZXJhZ2UudzsKCWZsb2F0MiBwaXhlbGxlbmd0aCA9IGludmVyc2VzcXJ0KGZsb2F0Mihkb3Qoc2tldy54eiwgc2tldy54eiksIGRvdChza2V3Lnl3LCBza2V3Lnl3KSkpOwoJZmxvYXQ0IG5vcm1hbGl6ZWRfYXhpc19kaXJzID0gc2tldyAqIHBpeGVsbGVuZ3RoLnh5eHk7CglmbG9hdDIgYXhpc3dpZHRocyA9IChhYnMobm9ybWFsaXplZF9heGlzX2RpcnMueHkpICsgYWJzKG5vcm1hbGl6ZWRfYXhpc19kaXJzLnp3KSk7CglmbG9hdDIgYWFfYmxvYXRyYWRpdXMgPSBheGlzd2lkdGhzICogcGl4ZWxsZW5ndGggKiAuNTsKCWZsb2F0NCByYWRpaV9hbmRfbmVpZ2hib3JzID0gcmFkaWlfc2VsZWN0b3IqIGZsb2F0NHg0KHJhZGlpX3gsIHJhZGlpX3ksIHJhZGlpX3gueXh3eiwgcmFkaWlfeS53enl4KTsKCWZsb2F0MiByYWRpaSA9IHJhZGlpX2FuZF9uZWlnaGJvcnMueHk7CglmbG9hdDIgbmVpZ2hib3JfcmFkaWkgPSByYWRpaV9hbmRfbmVpZ2hib3JzLnp3OwoJZmxvYXQgY292ZXJhZ2VfbXVsdGlwbGllciA9IDE7CglpZiAoYW55KGdyZWF0ZXJUaGFuKGFhX2Jsb2F0cmFkaXVzLCBmbG9hdDIoMSkpKSkgCgl7CgkJY29ybmVyID0gbWF4KGFicyhjb3JuZXIpLCBhYV9ibG9hdHJhZGl1cykgKiBzaWduKGNvcm5lcik7CgkJY292ZXJhZ2VfbXVsdGlwbGllciA9IDEgLyAobWF4KGFhX2Jsb2F0cmFkaXVzLngsIDEpICogbWF4KGFhX2Jsb2F0cmFkaXVzLnksIDEpKTsKCQlyYWRpaSA9IGZsb2F0MigwKTsKCX0KCWZsb2F0IGNvdmVyYWdlID0gYWFfYmxvYXRfYW5kX2NvdmVyYWdlLno7CglpZiAoYW55KGxlc3NUaGFuKHJhZGlpLCBhYV9ibG9hdHJhZGl1cyAqIDEuNSkpKSAKCXsKCQlyYWRpaSA9IGZsb2F0MigwKTsKCQlhYV9ibG9hdF9kaXJlY3Rpb24gPSBzaWduKGNvcm5lcik7CgkJaWYgKGNvdmVyYWdlID4gLjUpIAoJCXsKCQkJYWFfYmxvYXRfZGlyZWN0aW9uID0gLWFhX2Jsb2F0X2RpcmVjdGlvbjsKCQl9CgkJaXNfbGluZWFyX2NvdmVyYWdlID0gMTsKCX0KCWVsc2UgCgl7CgkJcmFkaWkgPSBjbGFtcChyYWRpaSwgcGl4ZWxsZW5ndGggKiAxLjUsIDIgLSBwaXhlbGxlbmd0aCAqIDEuNSk7CgkJbmVpZ2hib3JfcmFkaWkgPSBjbGFtcChuZWlnaGJvcl9yYWRpaSwgcGl4ZWxsZW5ndGggKiAxLjUsIDIgLSBwaXhlbGxlbmd0aCAqIDEuNSk7CgkJZmxvYXQyIHNwYWNpbmcgPSAyIC0gcmFkaWkgLSBuZWlnaGJvcl9yYWRpaTsKCQlmbG9hdDIgZXh0cmFfcGFkID0gbWF4KHBpeGVsbGVuZ3RoICogLjA2MjUgLSBzcGFjaW5nLCBmbG9hdDIoMCkpOwoJCXJhZGlpIC09IGV4dHJhX3BhZCAqIC41OwoJfQoJZmxvYXQyIGFhX291dHNldCA9IGFhX2Jsb2F0X2RpcmVjdGlvbiAqIGFhX2Jsb2F0cmFkaXVzICogYWFfYmxvYXRfbXVsdGlwbGllcjsKCWZsb2F0MiB2ZXJ0ZXhwb3MgPSBjb3JuZXIgKyByYWRpdXNfb3V0c2V0ICogcmFkaWkgKyBhYV9vdXRzZXQ7CglpZiAoY292ZXJhZ2UgPiAuNSkgCgl7CgkJaWYgKGFhX2Jsb2F0X2RpcmVjdGlvbi54ICE9IDAgJiYgdmVydGV4cG9zLnggKiBjb3JuZXIueCA8IDApIAoJCXsKCQkJZmxvYXQgYmFja3NldCA9IGFicyh2ZXJ0ZXhwb3MueCk7CgkJCXZlcnRleHBvcy54ID0gMDsKCQkJdmVydGV4cG9zLnkgKz0gYmFja3NldCAqIHNpZ24oY29ybmVyLnkpICogcGl4ZWxsZW5ndGgueS9waXhlbGxlbmd0aC54OwoJCQljb3ZlcmFnZSA9IChjb3ZlcmFnZSAtIC41KSAqIGFicyhjb3JuZXIueCkgLyAoYWJzKGNvcm5lci54KSArIGJhY2tzZXQpICsgLjU7CgkJfQoJCWlmIChhYV9ibG9hdF9kaXJlY3Rpb24ueSAhPSAwICYmIHZlcnRleHBvcy55ICogY29ybmVyLnkgPCAwKSAKCQl7CgkJCWZsb2F0IGJhY2tzZXQgPSBhYnModmVydGV4cG9zLnkpOwoJCQl2ZXJ0ZXhwb3MueSA9IDA7CgkJCXZlcnRleHBvcy54ICs9IGJhY2tzZXQgKiBzaWduKGNvcm5lci54KSAqIHBpeGVsbGVuZ3RoLngvcGl4ZWxsZW5ndGgueTsKCQkJY292ZXJhZ2UgPSAoY292ZXJhZ2UgLSAuNSkgKiBhYnMoY29ybmVyLnkpIC8gKGFicyhjb3JuZXIueSkgKyBiYWNrc2V0KSArIC41OwoJCX0KCX0KCWZsb2F0MngyIHNrZXdtYXRyaXggPSBmbG9hdDJ4Mihza2V3Lnh5LCBza2V3Lnp3KTsKCWZsb2F0MiBkZXZjb29yZCA9IHZlcnRleHBvcyAqIHNrZXdtYXRyaXggKyB0cmFuc2xhdGU7CglpZiAoMCAhPSBpc19saW5lYXJfY292ZXJhZ2UpIAoJewoJCXZhcmNjb29yZF9TdGFnZTAueHkgPSBmbG9hdDIoMCwgY292ZXJhZ2UgKiBjb3ZlcmFnZV9tdWx0aXBsaWVyKTsKCX0KCWVsc2UgCgl7CgkJZmxvYXQyIGFyY2Nvb3JkID0gMSAtIGFicyhyYWRpdXNfb3V0c2V0KSArIGFhX291dHNldC9yYWRpaSAqIGNvcm5lcjsKCQl2YXJjY29vcmRfU3RhZ2UwLnh5ID0gZmxvYXQyKGFyY2Nvb3JkLngrMSwgYXJjY29vcmQueSk7Cgl9Cglza19Qb3NpdGlvbiA9IGZsb2F0NChkZXZjb29yZC54ICwgZGV2Y29vcmQueSwgMCwgMSk7Cn0KAAABAQAAAAAAAAEBALEFAAB1bmlmb3JtIGZsb2F0NCB1cmVjdFVuaWZvcm1fU3RhZ2UxX2MwOwpmbGF0IGluIGhhbGY0IHZjb2xvcl9TdGFnZTA7CmluIGZsb2F0MiB2YXJjY29vcmRfU3RhZ2UwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwppbmxpbmUgaGFsZjQgQUFSZWN0RWZmZWN0X1N0YWdlMV9jMChoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDQgcHJldlJlY3QgPSBmbG9hdDQoLTEuMDAwMDAwLCAtMS4wMDAwMDAsIC0xLjAwMDAwMCwgLTEuMDAwMDAwKTsKCWhhbGYgY292ZXJhZ2U7CglAc3dpdGNoICgxKSAKCXsKCQljYXNlIDA6ICAgIGNhc2UgMjogICAgICAgIGNvdmVyYWdlID0gaGFsZihhbGwoZ3JlYXRlclRoYW4oZmxvYXQ0KHNrX0ZyYWdDb29yZC54eSwgdXJlY3RVbmlmb3JtX1N0YWdlMV9jMC56dyksIGZsb2F0NCh1cmVjdFVuaWZvcm1fU3RhZ2UxX2MwLnh5LCBza19GcmFnQ29vcmQueHkpKSkgPyAxIDogMCk7CgkJYnJlYWs7CgkJZGVmYXVsdDogICAgICAgIGhhbGY0IGRpc3RzNCA9IGNsYW1wKGhhbGY0KDEuMCwgMS4wLCAtMS4wLCAtMS4wKSAqIGhhbGY0KHNrX0ZyYWdDb29yZC54eXh5IC0gdXJlY3RVbmlmb3JtX1N0YWdlMV9jMCksIDAuMCwgMS4wKTsKCQloYWxmMiBkaXN0czIgPSAoZGlzdHM0Lnh5ICsgZGlzdHM0Lnp3KSAtIDEuMDsKCQljb3ZlcmFnZSA9IGRpc3RzMi54ICogZGlzdHMyLnk7Cgl9CglAaWYgKDEgPT0gMiB8fCAxID09IDMpIAoJewoJCWNvdmVyYWdlID0gMS4wIC0gY292ZXJhZ2U7Cgl9CglyZXR1cm4gX2lucHV0ICogY292ZXJhZ2U7Cn0Kdm9pZCBtYWluKCkgCnsKCWhhbGY0IG91dHB1dENvbG9yX1N0YWdlMDsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1N0YWdlMDsKCXsKCQkvLyBTdGFnZSAwLCBHckZpbGxSUmVjdE9wOjpQcm9jZXNzb3IKCQlvdXRwdXRDb2xvcl9TdGFnZTAgPSB2Y29sb3JfU3RhZ2UwOwoJCWZsb2F0IHhfcGx1c18xPXZhcmNjb29yZF9TdGFnZTAueCwgeT12YXJjY29vcmRfU3RhZ2UwLnk7CgkJaGFsZiBjb3ZlcmFnZTsKCQlpZiAoMCA9PSB4X3BsdXNfMSkgCgkJewoJCQljb3ZlcmFnZSA9IGhhbGYoeSk7CgkJfQoJCWVsc2UgCgkJewoJCQlmbG9hdCBmbiA9IHhfcGx1c18xICogKHhfcGx1c18xIC0gMik7CgkJCWZuID0gZm1hKHkseSwgZm4pOwoJCQlmbG9hdCBmbndpZHRoID0gZndpZHRoKGZuKTsKCQkJY292ZXJhZ2UgPSAuNSAtIGhhbGYoZm4vZm53aWR0aCk7CgkJCWNvdmVyYWdlID0gY2xhbXAoY292ZXJhZ2UsIDAsIDEpOwoJCX0KCQlvdXRwdXRDb3ZlcmFnZV9TdGFnZTAgPSBoYWxmNChjb3ZlcmFnZSk7Cgl9CgloYWxmNCBvdXRwdXRfU3RhZ2UxOwoJb3V0cHV0X1N0YWdlMSA9IEFBUmVjdEVmZmVjdF9TdGFnZTFfYzAob3V0cHV0Q292ZXJhZ2VfU3RhZ2UwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TdGFnZTAgKiBvdXRwdXRfU3RhZ2UxOwoJfQp9CgAAAAABAQABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAIAAAADgAAAHJhZGlpX3NlbGVjdG9yAAAZAAAAY29ybmVyX2FuZF9yYWRpdXNfb3V0c2V0cwAAABUAAABhYV9ibG9hdF9hbmRfY292ZXJhZ2UAAAAEAAAAc2tldwkAAAB0cmFuc2xhdGUAAAAHAAAAcmFkaWlfeAAHAAAAcmFkaWlfeQAFAAAAY29sb3IAAAABAAAAAAAAAA==","LIQAAAAAAAGAAAAAAIWP57ZDH37P7777777QCAAAAAAAAAAAMIQHSAAAAAAQAAAAABLQAAAAAABAAAAACAZAEAAAAA":"AwAAAExTS1P0AAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7CmZsYXQgb3V0IGhhbGY0IHZjb2xvcl9TdGFnZTA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmNvbG9yX1N0YWdlMCA9IGNvbG9yOwoJc2tfUG9zaXRpb24gPSBmbG9hdDQocG9zaXRpb24ueCAsIHBvc2l0aW9uLnksIDAsIDEpOwp9CgABAQAAAAAAAAEBADMDAAB1bmlmb3JtIGZsb2F0NCB1aW5uZXJSZWN0X1N0YWdlMV9jMDsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfU3RhZ2UxX2MwOwpmbGF0IGluIGhhbGY0IHZjb2xvcl9TdGFnZTA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmlubGluZSBoYWxmNCBDaXJjdWxhclJSZWN0X1N0YWdlMV9jMChoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgZHh5MCA9IHVpbm5lclJlY3RfU3RhZ2UxX2MwLkxUIC0gc2tfRnJhZ0Nvb3JkLnh5OwoJZmxvYXQyIGR4eTEgPSBza19GcmFnQ29vcmQueHkgLSB1aW5uZXJSZWN0X1N0YWdlMV9jMC5SQjsKCWZsb2F0MiBkeHkgPSBtYXgobWF4KGR4eTAsIGR4eTEpLCAwLjApOwoJaGFsZiBhbHBoYSA9IGhhbGYoc2F0dXJhdGUodXJhZGl1c1BsdXNIYWxmX1N0YWdlMV9jMC54IC0gbGVuZ3RoKGR4eSkpKTsKCXJldHVybiBfaW5wdXQgKiBhbHBoYTsKfQp2b2lkIG1haW4oKSAKewoJaGFsZjQgb3V0cHV0Q29sb3JfU3RhZ2UwOwoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfU3RhZ2UwOwoJewoJCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJCW91dHB1dENvbG9yX1N0YWdlMCA9IHZjb2xvcl9TdGFnZTA7CgkJb3V0cHV0Q292ZXJhZ2VfU3RhZ2UwID0gaGFsZjQoMSk7Cgl9CgloYWxmNCBvdXRwdXRfU3RhZ2UxOwoJb3V0cHV0X1N0YWdlMSA9IENpcmN1bGFyUlJlY3RfU3RhZ2UxX2MwKG91dHB1dENvdmVyYWdlX1N0YWdlMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfU3RhZ2UwICogb3V0cHV0X1N0YWdlMTsKCX0KfQoAAAEBAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24FAAAAY29sb3IAAAABAAAAAAAAAA==","LJIACAAAAAMAAAAAARMAAVCEPQCFR7H7777QGAAAAAAAAAAA6RDQB4AA6AAPAAHQDQAAAAAAEIFQAAAAEQGQAAAAACHQIAAAADQBAAAAADABKAAAABAACAAAACCIYAAA":"AwAAAExTS1MiAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TdGFnZTFfYzBfYzBfYzA7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQgY292ZXJhZ2U7CmluIGhhbGY0IGNvbG9yOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKZmxhdCBvdXQgaGFsZjQgdmNvbG9yX1N0YWdlMDsKb3V0IGZsb2F0IHZjb3ZlcmFnZV9TdGFnZTA7Cm91dCBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzBfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCWZsb2F0MiBwb3NpdGlvbiA9IHBvc2l0aW9uLnh5OwoJdmNvbG9yX1N0YWdlMCA9IGNvbG9yOwoJdmNvdmVyYWdlX1N0YWdlMCA9IGNvdmVyYWdlOwoJc2tfUG9zaXRpb24gPSBmbG9hdDQocG9zaXRpb24ueCAsIHBvc2l0aW9uLnksIDAsIDEpOwoJewoJCXZUcmFuc2Zvcm1lZENvb3Jkc18wX1N0YWdlMCA9ICgoKHVtYXRyaXhfU3RhZ2UxX2MwX2MwX2MwKSkgKiBsb2NhbENvb3JkLnh5MSkueHk7Cgl9Cn0KAAAAAAAAAAAAAAAAAACLBwAAdW5pZm9ybSBoYWxmNCB1bGVmdEJvcmRlckNvbG9yX1N0YWdlMV9jMF9jMDsKdW5pZm9ybSBoYWxmNCB1cmlnaHRCb3JkZXJDb2xvcl9TdGFnZTFfYzBfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TdGFnZTFfYzBfYzBfYzA7CnVuaWZvcm0gaGFsZjQgdXN0YXJ0X1N0YWdlMV9jMF9jMF9jMTsKdW5pZm9ybSBoYWxmNCB1ZW5kX1N0YWdlMV9jMF9jMF9jMTsKZmxhdCBpbiBoYWxmNCB2Y29sb3JfU3RhZ2UwOwppbiBmbG9hdCB2Y292ZXJhZ2VfU3RhZ2UwOwppbiBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzBfU3RhZ2UwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBMaW5lYXJHcmFkaWVudExheW91dF9TdGFnZTFfYzBfYzBfYzBfYzAoaGFsZjQgX2lucHV0KSAKewoJaGFsZiB0ID0gaGFsZih2VHJhbnNmb3JtZWRDb29yZHNfMF9TdGFnZTAueCkgKyA5Ljk5OTk5OTc0NzM3ODc1MTZlLTA2OwoJcmV0dXJuIGhhbGY0KHQsIDEuMCwgMC4wLCAwLjApOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzBfYzAoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIExpbmVhckdyYWRpZW50TGF5b3V0X1N0YWdlMV9jMF9jMF9jMF9jMChfaW5wdXQpOwp9CmhhbGY0IFNpbmdsZUludGVydmFsR3JhZGllbnRDb2xvcml6ZXJfU3RhZ2UxX2MwX2MwX2MxKGhhbGY0IF9pbnB1dCwgZmxvYXQyIF9jb29yZHMpIAp7CgloYWxmIHQgPSBoYWxmKF9jb29yZHMueCk7CglyZXR1cm4gbWl4KHVzdGFydF9TdGFnZTFfYzBfYzBfYzEsIHVlbmRfU3RhZ2UxX2MwX2MwX2MxLCB0KTsKfQpoYWxmNCBDbGFtcGVkR3JhZGllbnRFZmZlY3RfU3RhZ2UxX2MwX2MwKGhhbGY0IF9pbnB1dCkgCnsKCWhhbGY0IHQgPSBNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKF9pbnB1dCk7CgloYWxmNCBvdXRDb2xvcjsKCWlmICghdHJ1ZSAmJiB0LnkgPCAwLjApIAoJewoJCW91dENvbG9yID0gaGFsZjQoMC4wKTsKCX0KCWVsc2UgaWYgKHQueCA8IDAuMCkgCgl7CgkJb3V0Q29sb3IgPSB1bGVmdEJvcmRlckNvbG9yX1N0YWdlMV9jMF9jMDsKCX0KCWVsc2UgaWYgKHQueCA+IDEuMCkgCgl7CgkJb3V0Q29sb3IgPSB1cmlnaHRCb3JkZXJDb2xvcl9TdGFnZTFfYzBfYzA7Cgl9CgllbHNlIAoJewoJCW91dENvbG9yID0gU2luZ2xlSW50ZXJ2YWxHcmFkaWVudENvbG9yaXplcl9TdGFnZTFfYzBfYzBfYzEoX2lucHV0LCBmbG9hdDIoaGFsZjIodC54LCAwLjApKSk7Cgl9CglAaWYgKHRydWUpIAoJewoJCW91dENvbG9yLnh5eiAqPSBvdXRDb2xvci53OwoJfQoJcmV0dXJuIG91dENvbG9yOwp9CmlubGluZSBoYWxmNCBPdmVycmlkZUlucHV0RnJhZ21lbnRQcm9jZXNzb3JfU3RhZ2UxX2MwKGhhbGY0IF9pbnB1dCkgCnsKCXJldHVybiBDbGFtcGVkR3JhZGllbnRFZmZlY3RfU3RhZ2UxX2MwX2MwKGZhbHNlID8gaGFsZjQoMCkgOiBoYWxmNCgxLjAwMDAwMCwgMS4wMDAwMDAsIDEuMDAwMDAwLCAxLjAwMDAwMCkpOwp9CnZvaWQgbWFpbigpIAp7CgloYWxmNCBvdXRwdXRDb2xvcl9TdGFnZTA7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TdGFnZTA7Cgl7CgkJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgkJb3V0cHV0Q29sb3JfU3RhZ2UwID0gdmNvbG9yX1N0YWdlMDsKCQlmbG9hdCBjb3ZlcmFnZSA9IHZjb3ZlcmFnZV9TdGFnZTA7CgkJb3V0cHV0Q292ZXJhZ2VfU3RhZ2UwID0gaGFsZjQoaGFsZihjb3ZlcmFnZSkpOwoJfQoJaGFsZjQgb3V0cHV0X1N0YWdlMTsKCW91dHB1dF9TdGFnZTEgPSBPdmVycmlkZUlucHV0RnJhZ21lbnRQcm9jZXNzb3JfU3RhZ2UxX2MwKG91dHB1dENvbG9yX1N0YWdlMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gKGhhbGY0KDEuMCkgLSBvdXRwdXRfU3RhZ2UxKSAqIG91dHB1dENvdmVyYWdlX1N0YWdlMDsKCX0KfQoAAAAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAQAAAAIAAAAcG9zaXRpb24IAAAAY292ZXJhZ2UFAAAAY29sb3IAAAAKAAAAbG9jYWxDb29yZAAAAQAAAAAAAAA=","LJJAAAAAAAMAAAAAARMPZ72HPQCFR7H7777QGAAAAACAAAAAACCAYAGEIDZAAAAAAIAAAAAAVYAAAAAAAQAAAABAMQCAAAAA":"AwAAAExTS1NLAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7CmluIGZsb2F0MiBsb2NhbENvb3JkOwpmbGF0IG91dCBoYWxmNCB2Y29sb3JfU3RhZ2UwOwpvdXQgZmxvYXQyIHZsb2NhbENvb3JkX1N0YWdlMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgl2Y29sb3JfU3RhZ2UwID0gY29sb3I7Cgl2bG9jYWxDb29yZF9TdGFnZTAgPSBsb2NhbENvb3JkOwoJc2tfUG9zaXRpb24gPSBmbG9hdDQocG9zaXRpb24ueCAsIHBvc2l0aW9uLnksIDAsIDEpOwp9CgAAAQEAAAAAAAABAQALBAAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TdGFnZTFfYzA7CnVuaWZvcm0gaGFsZjIgdXJhZGl1c1BsdXNIYWxmX1N0YWdlMV9jMDsKdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfU3RhZ2UwOwpmbGF0IGluIGhhbGY0IHZjb2xvcl9TdGFnZTA7CmluIGZsb2F0MiB2bG9jYWxDb29yZF9TdGFnZTA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmlubGluZSBoYWxmNCBDaXJjdWxhclJSZWN0X1N0YWdlMV9jMChoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgZHh5MCA9IHVpbm5lclJlY3RfU3RhZ2UxX2MwLkxUIC0gc2tfRnJhZ0Nvb3JkLnh5OwoJZmxvYXQyIGR4eTEgPSBza19GcmFnQ29vcmQueHkgLSB1aW5uZXJSZWN0X1N0YWdlMV9jMC5SQjsKCWZsb2F0MiBkeHkgPSBtYXgobWF4KGR4eTAsIGR4eTEpLCAwLjApOwoJaGFsZiBhbHBoYSA9IGhhbGYoc2F0dXJhdGUodXJhZGl1c1BsdXNIYWxmX1N0YWdlMV9jMC54IC0gbGVuZ3RoKGR4eSkpKTsKCXJldHVybiBfaW5wdXQgKiBhbHBoYTsKfQp2b2lkIG1haW4oKSAKewoJaGFsZjQgb3V0cHV0Q29sb3JfU3RhZ2UwOwoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfU3RhZ2UwOwoJewoJCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJCW91dHB1dENvbG9yX1N0YWdlMCA9IHZjb2xvcl9TdGFnZTA7CgkJZmxvYXQyIHRleENvb3JkOwoJCXRleENvb3JkID0gdmxvY2FsQ29vcmRfU3RhZ2UwOwoJCW91dHB1dENvbG9yX1N0YWdlMCA9IChzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfU3RhZ2UwLCB0ZXhDb29yZCkgKiBvdXRwdXRDb2xvcl9TdGFnZTApOwoJCW91dHB1dENvdmVyYWdlX1N0YWdlMCA9IGhhbGY0KDEpOwoJfQoJaGFsZjQgb3V0cHV0X1N0YWdlMTsKCW91dHB1dF9TdGFnZTEgPSBDaXJjdWxhclJSZWN0X1N0YWdlMV9jMChvdXRwdXRDb3ZlcmFnZV9TdGFnZTApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1N0YWdlMCAqIG91dHB1dF9TdGFnZTE7Cgl9Cn0KAAABAQABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAADAAAACAAAAHBvc2l0aW9uBQAAAGNvbG9yAAAACgAAAGxvY2FsQ29vcmQAAAEAAAAAAAAA","GJQQAAAAMAAGGADDACRQAAAAMAACHQDCABRQAI7CAMAAAABAAYJAMAAACAAAAAAAOACQAAAAEAAAAAAAEERQAAAAAA":"AwAAAExTS1PfCwAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0NCByYWRpaV9zZWxlY3RvcjsKaW4gZmxvYXQ0IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHM7CmluIGZsb2F0NCBhYV9ibG9hdF9hbmRfY292ZXJhZ2U7CmluIGZsb2F0NCBza2V3OwppbiBmbG9hdDIgdHJhbnNsYXRlOwppbiBmbG9hdDQgcmFkaWlfeDsKaW4gZmxvYXQ0IHJhZGlpX3k7CmluIGhhbGY0IGNvbG9yOwpmbGF0IG91dCBoYWxmNCB2Y29sb3JfU3RhZ2UwOwpvdXQgZmxvYXQyIHZhcmNjb29yZF9TdGFnZTA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIEdyRmlsbFJSZWN0T3A6OlByb2Nlc3NvcgoJdmNvbG9yX1N0YWdlMCA9IGNvbG9yOwoJZmxvYXQgYWFfYmxvYXRfbXVsdGlwbGllciA9IDE7CglmbG9hdDIgY29ybmVyID0gY29ybmVyX2FuZF9yYWRpdXNfb3V0c2V0cy54eTsKCWZsb2F0MiByYWRpdXNfb3V0c2V0ID0gY29ybmVyX2FuZF9yYWRpdXNfb3V0c2V0cy56dzsKCWZsb2F0MiBhYV9ibG9hdF9kaXJlY3Rpb24gPSBhYV9ibG9hdF9hbmRfY292ZXJhZ2UueHk7CglmbG9hdCBpc19saW5lYXJfY292ZXJhZ2UgPSBhYV9ibG9hdF9hbmRfY292ZXJhZ2UudzsKCWZsb2F0MiBwaXhlbGxlbmd0aCA9IGludmVyc2VzcXJ0KGZsb2F0Mihkb3Qoc2tldy54eiwgc2tldy54eiksIGRvdChza2V3Lnl3LCBza2V3Lnl3KSkpOwoJZmxvYXQ0IG5vcm1hbGl6ZWRfYXhpc19kaXJzID0gc2tldyAqIHBpeGVsbGVuZ3RoLnh5eHk7CglmbG9hdDIgYXhpc3dpZHRocyA9IChhYnMobm9ybWFsaXplZF9heGlzX2RpcnMueHkpICsgYWJzKG5vcm1hbGl6ZWRfYXhpc19kaXJzLnp3KSk7CglmbG9hdDIgYWFfYmxvYXRyYWRpdXMgPSBheGlzd2lkdGhzICogcGl4ZWxsZW5ndGggKiAuNTsKCWZsb2F0NCByYWRpaV9hbmRfbmVpZ2hib3JzID0gcmFkaWlfc2VsZWN0b3IqIGZsb2F0NHg0KHJhZGlpX3gsIHJhZGlpX3ksIHJhZGlpX3gueXh3eiwgcmFkaWlfeS53enl4KTsKCWZsb2F0MiByYWRpaSA9IHJhZGlpX2FuZF9uZWlnaGJvcnMueHk7CglmbG9hdDIgbmVpZ2hib3JfcmFkaWkgPSByYWRpaV9hbmRfbmVpZ2hib3JzLnp3OwoJZmxvYXQgY292ZXJhZ2VfbXVsdGlwbGllciA9IDE7CglpZiAoYW55KGdyZWF0ZXJUaGFuKGFhX2Jsb2F0cmFkaXVzLCBmbG9hdDIoMSkpKSkgCgl7CgkJY29ybmVyID0gbWF4KGFicyhjb3JuZXIpLCBhYV9ibG9hdHJhZGl1cykgKiBzaWduKGNvcm5lcik7CgkJY292ZXJhZ2VfbXVsdGlwbGllciA9IDEgLyAobWF4KGFhX2Jsb2F0cmFkaXVzLngsIDEpICogbWF4KGFhX2Jsb2F0cmFkaXVzLnksIDEpKTsKCQlyYWRpaSA9IGZsb2F0MigwKTsKCX0KCWZsb2F0IGNvdmVyYWdlID0gYWFfYmxvYXRfYW5kX2NvdmVyYWdlLno7CglpZiAoYW55KGxlc3NUaGFuKHJhZGlpLCBhYV9ibG9hdHJhZGl1cyAqIDEuNSkpKSAKCXsKCQlyYWRpaSA9IGZsb2F0MigwKTsKCQlhYV9ibG9hdF9kaXJlY3Rpb24gPSBzaWduKGNvcm5lcik7CgkJaWYgKGNvdmVyYWdlID4gLjUpIAoJCXsKCQkJYWFfYmxvYXRfZGlyZWN0aW9uID0gLWFhX2Jsb2F0X2RpcmVjdGlvbjsKCQl9CgkJaXNfbGluZWFyX2NvdmVyYWdlID0gMTsKCX0KCWVsc2UgCgl7CgkJcmFkaWkgPSBjbGFtcChyYWRpaSwgcGl4ZWxsZW5ndGggKiAxLjUsIDIgLSBwaXhlbGxlbmd0aCAqIDEuNSk7CgkJbmVpZ2hib3JfcmFkaWkgPSBjbGFtcChuZWlnaGJvcl9yYWRpaSwgcGl4ZWxsZW5ndGggKiAxLjUsIDIgLSBwaXhlbGxlbmd0aCAqIDEuNSk7CgkJZmxvYXQyIHNwYWNpbmcgPSAyIC0gcmFkaWkgLSBuZWlnaGJvcl9yYWRpaTsKCQlmbG9hdDIgZXh0cmFfcGFkID0gbWF4KHBpeGVsbGVuZ3RoICogLjA2MjUgLSBzcGFjaW5nLCBmbG9hdDIoMCkpOwoJCXJhZGlpIC09IGV4dHJhX3BhZCAqIC41OwoJfQoJZmxvYXQyIGFhX291dHNldCA9IGFhX2Jsb2F0X2RpcmVjdGlvbiAqIGFhX2Jsb2F0cmFkaXVzICogYWFfYmxvYXRfbXVsdGlwbGllcjsKCWZsb2F0MiB2ZXJ0ZXhwb3MgPSBjb3JuZXIgKyByYWRpdXNfb3V0c2V0ICogcmFkaWkgKyBhYV9vdXRzZXQ7CglpZiAoY292ZXJhZ2UgPiAuNSkgCgl7CgkJaWYgKGFhX2Jsb2F0X2RpcmVjdGlvbi54ICE9IDAgJiYgdmVydGV4cG9zLnggKiBjb3JuZXIueCA8IDApIAoJCXsKCQkJZmxvYXQgYmFja3NldCA9IGFicyh2ZXJ0ZXhwb3MueCk7CgkJCXZlcnRleHBvcy54ID0gMDsKCQkJdmVydGV4cG9zLnkgKz0gYmFja3NldCAqIHNpZ24oY29ybmVyLnkpICogcGl4ZWxsZW5ndGgueS9waXhlbGxlbmd0aC54OwoJCQljb3ZlcmFnZSA9IChjb3ZlcmFnZSAtIC41KSAqIGFicyhjb3JuZXIueCkgLyAoYWJzKGNvcm5lci54KSArIGJhY2tzZXQpICsgLjU7CgkJfQoJCWlmIChhYV9ibG9hdF9kaXJlY3Rpb24ueSAhPSAwICYmIHZlcnRleHBvcy55ICogY29ybmVyLnkgPCAwKSAKCQl7CgkJCWZsb2F0IGJhY2tzZXQgPSBhYnModmVydGV4cG9zLnkpOwoJCQl2ZXJ0ZXhwb3MueSA9IDA7CgkJCXZlcnRleHBvcy54ICs9IGJhY2tzZXQgKiBzaWduKGNvcm5lci54KSAqIHBpeGVsbGVuZ3RoLngvcGl4ZWxsZW5ndGgueTsKCQkJY292ZXJhZ2UgPSAoY292ZXJhZ2UgLSAuNSkgKiBhYnMoY29ybmVyLnkpIC8gKGFicyhjb3JuZXIueSkgKyBiYWNrc2V0KSArIC41OwoJCX0KCX0KCWZsb2F0MngyIHNrZXdtYXRyaXggPSBmbG9hdDJ4Mihza2V3Lnh5LCBza2V3Lnp3KTsKCWZsb2F0MiBkZXZjb29yZCA9IHZlcnRleHBvcyAqIHNrZXdtYXRyaXggKyB0cmFuc2xhdGU7CglpZiAoMCAhPSBpc19saW5lYXJfY292ZXJhZ2UpIAoJewoJCXZhcmNjb29yZF9TdGFnZTAueHkgPSBmbG9hdDIoMCwgY292ZXJhZ2UgKiBjb3ZlcmFnZV9tdWx0aXBsaWVyKTsKCX0KCWVsc2UgCgl7CgkJZmxvYXQyIGFyY2Nvb3JkID0gMSAtIGFicyhyYWRpdXNfb3V0c2V0KSArIGFhX291dHNldC9yYWRpaSAqIGNvcm5lcjsKCQl2YXJjY29vcmRfU3RhZ2UwLnh5ID0gZmxvYXQyKGFyY2Nvb3JkLngrMSwgYXJjY29vcmQueSk7Cgl9Cglza19Qb3NpdGlvbiA9IGZsb2F0NChkZXZjb29yZC54ICwgZGV2Y29vcmQueSwgMCwgMSk7Cn0KAAABAQAAAAAAAAEBAOsEAAB1bmlmb3JtIGZsb2F0NCB1aW5uZXJSZWN0X1N0YWdlMV9jMDsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfU3RhZ2UxX2MwOwpmbGF0IGluIGhhbGY0IHZjb2xvcl9TdGFnZTA7CmluIGZsb2F0MiB2YXJjY29vcmRfU3RhZ2UwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwppbmxpbmUgaGFsZjQgQ2lyY3VsYXJSUmVjdF9TdGFnZTFfYzAoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQgZHgwID0gdWlubmVyUmVjdF9TdGFnZTFfYzAuTCAtIHNrX0ZyYWdDb29yZC54OwoJZmxvYXQyIGR4eTEgPSBza19GcmFnQ29vcmQueHkgLSB1aW5uZXJSZWN0X1N0YWdlMV9jMC5SQjsKCWZsb2F0MiBkeHkgPSBtYXgoZmxvYXQyKG1heChkeDAsIGR4eTEueCksIGR4eTEueSksIDAuMCk7CgloYWxmIHRvcEFscGhhID0gaGFsZihzYXR1cmF0ZShza19GcmFnQ29vcmQueSAtIHVpbm5lclJlY3RfU3RhZ2UxX2MwLlQpKTsKCWhhbGYgYWxwaGEgPSB0b3BBbHBoYSAqIGhhbGYoc2F0dXJhdGUodXJhZGl1c1BsdXNIYWxmX1N0YWdlMV9jMC54IC0gbGVuZ3RoKGR4eSkpKTsKCXJldHVybiBfaW5wdXQgKiBhbHBoYTsKfQp2b2lkIG1haW4oKSAKewoJaGFsZjQgb3V0cHV0Q29sb3JfU3RhZ2UwOwoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfU3RhZ2UwOwoJewoJCS8vIFN0YWdlIDAsIEdyRmlsbFJSZWN0T3A6OlByb2Nlc3NvcgoJCW91dHB1dENvbG9yX1N0YWdlMCA9IHZjb2xvcl9TdGFnZTA7CgkJZmxvYXQgeF9wbHVzXzE9dmFyY2Nvb3JkX1N0YWdlMC54LCB5PXZhcmNjb29yZF9TdGFnZTAueTsKCQloYWxmIGNvdmVyYWdlOwoJCWlmICgwID09IHhfcGx1c18xKSAKCQl7CgkJCWNvdmVyYWdlID0gaGFsZih5KTsKCQl9CgkJZWxzZSAKCQl7CgkJCWZsb2F0IGZuID0geF9wbHVzXzEgKiAoeF9wbHVzXzEgLSAyKTsKCQkJZm4gPSBmbWEoeSx5LCBmbik7CgkJCWZsb2F0IGZud2lkdGggPSBmd2lkdGgoZm4pOwoJCQljb3ZlcmFnZSA9IC41IC0gaGFsZihmbi9mbndpZHRoKTsKCQkJY292ZXJhZ2UgPSBjbGFtcChjb3ZlcmFnZSwgMCwgMSk7CgkJfQoJCW91dHB1dENvdmVyYWdlX1N0YWdlMCA9IGhhbGY0KGNvdmVyYWdlKTsKCX0KCWhhbGY0IG91dHB1dF9TdGFnZTE7CglvdXRwdXRfU3RhZ2UxID0gQ2lyY3VsYXJSUmVjdF9TdGFnZTFfYzAob3V0cHV0Q292ZXJhZ2VfU3RhZ2UwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TdGFnZTAgKiBvdXRwdXRfU3RhZ2UxOwoJfQp9CgAAAQEAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAACAAAAA4AAAByYWRpaV9zZWxlY3RvcgAAGQAAAGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHMAAAAVAAAAYWFfYmxvYXRfYW5kX2NvdmVyYWdlAAAABAAAAHNrZXcJAAAAdHJhbnNsYXRlAAAABwAAAHJhZGlpX3gABwAAAHJhZGlpX3kABQAAAGNvbG9yAAAAAQAAAAAAAAA=","JABQAAAAAELBCHYCDYAAAAAAAEAAAACAIQAABOACAAAAAEAAAAAIBEARAA":"AwAAAExTS1OFAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiBoYWxmMyBpblNoYWRvd1BhcmFtczsKb3V0IGhhbGYzIHZpblNoYWRvd1BhcmFtc19TdGFnZTA7Cm91dCBoYWxmNCB2aW5Db2xvcl9TdGFnZTA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFJSZWN0U2hhZG93Cgl2aW5TaGFkb3dQYXJhbXNfU3RhZ2UwID0gaW5TaGFkb3dQYXJhbXM7Cgl2aW5Db2xvcl9TdGFnZTAgPSBpbkNvbG9yOwoJZmxvYXQyIF90bXBfMF9pblBvc2l0aW9uID0gaW5Qb3NpdGlvbjsKCXNrX1Bvc2l0aW9uID0gZmxvYXQ0KF90bXBfMF9pblBvc2l0aW9uLnggLCBfdG1wXzBfaW5Qb3NpdGlvbi55LCAwLCAxKTsKfQoAAAAAAAAAAAAAAAAAAAB1AgAAdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfU3RhZ2UwOwppbiBoYWxmMyB2aW5TaGFkb3dQYXJhbXNfU3RhZ2UwOwppbiBoYWxmNCB2aW5Db2xvcl9TdGFnZTA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CnZvaWQgbWFpbigpIAp7CgloYWxmNCBvdXRwdXRDb2xvcl9TdGFnZTA7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TdGFnZTA7Cgl7CgkJLy8gU3RhZ2UgMCwgUlJlY3RTaGFkb3cKCQloYWxmMyBzaGFkb3dQYXJhbXM7CgkJc2hhZG93UGFyYW1zID0gdmluU2hhZG93UGFyYW1zX1N0YWdlMDsKCQlvdXRwdXRDb2xvcl9TdGFnZTAgPSB2aW5Db2xvcl9TdGFnZTA7CgkJaGFsZiBkID0gbGVuZ3RoKHNoYWRvd1BhcmFtcy54eSk7CgkJZmxvYXQyIHV2ID0gZmxvYXQyKHNoYWRvd1BhcmFtcy56ICogKDEuMCAtIGQpLCAwLjUpOwoJCWhhbGYgZmFjdG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1N0YWdlMCwgdXYpLjAwMHIuYTsKCQlvdXRwdXRDb3ZlcmFnZV9TdGFnZTAgPSBoYWxmNChmYWN0b3IpOwoJfQoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1N0YWdlMCAqIG91dHB1dENvdmVyYWdlX1N0YWdlMDsKCX0KfQoAAAAAAAAAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAwAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAOAAAAaW5TaGFkb3dQYXJhbXMAAAEAAAAAAAAA","LJJAAAAAAAMAAAAAARMPZ72HPQCFR7H7777QGAAAAACAAAAAACCAYAHABIAAAACAAAAAAACCIYAAA":"AwAAAExTS1NLAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7CmluIGZsb2F0MiBsb2NhbENvb3JkOwpmbGF0IG91dCBoYWxmNCB2Y29sb3JfU3RhZ2UwOwpvdXQgZmxvYXQyIHZsb2NhbENvb3JkX1N0YWdlMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgl2Y29sb3JfU3RhZ2UwID0gY29sb3I7Cgl2bG9jYWxDb29yZF9TdGFnZTAgPSBsb2NhbENvb3JkOwoJc2tfUG9zaXRpb24gPSBmbG9hdDQocG9zaXRpb24ueCAsIHBvc2l0aW9uLnksIDAsIDEpOwp9CgAAAAAAAAAAAAAAAAA4AgAAdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfU3RhZ2UwOwpmbGF0IGluIGhhbGY0IHZjb2xvcl9TdGFnZTA7CmluIGZsb2F0MiB2bG9jYWxDb29yZF9TdGFnZTA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CnZvaWQgbWFpbigpIAp7CgloYWxmNCBvdXRwdXRDb2xvcl9TdGFnZTA7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TdGFnZTA7Cgl7CgkJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgkJb3V0cHV0Q29sb3JfU3RhZ2UwID0gdmNvbG9yX1N0YWdlMDsKCQlmbG9hdDIgdGV4Q29vcmQ7CgkJdGV4Q29vcmQgPSB2bG9jYWxDb29yZF9TdGFnZTA7CgkJb3V0cHV0Q29sb3JfU3RhZ2UwID0gKHNhbXBsZSh1VGV4dHVyZVNhbXBsZXJfMF9TdGFnZTAsIHRleENvb3JkKSAqIG91dHB1dENvbG9yX1N0YWdlMCk7CgkJb3V0cHV0Q292ZXJhZ2VfU3RhZ2UwID0gaGFsZjQoMSk7Cgl9Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfU3RhZ2UwICogb3V0cHV0Q292ZXJhZ2VfU3RhZ2UwOwoJfQp9CgAAAAABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAADAAAACAAAAHBvc2l0aW9uBQAAAGNvbG9yAAAACgAAAGxvY2FsQ29vcmQAAAEAAAAAAAAA","AWQAGAAAQAAIXCEPAGGP777777777737AAAAAAAAAAAIAXCIAEAAAAAAK4AAAAAAAIAAAAAQGIBAA":"AwAAAExTS1MJAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TdGFnZTA7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiBmbG9hdDQgaW5DaXJjbGVFZGdlOwpvdXQgZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfU3RhZ2UwOwpvdXQgaGFsZjQgdmluQ29sb3JfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TdGFnZTAgPSBpbkNpcmNsZUVkZ2U7Cgl2aW5Db2xvcl9TdGFnZTAgPSBpbkNvbG9yOwoJZmxvYXQyIF90bXBfMF9pblBvc2l0aW9uID0gaW5Qb3NpdGlvbjsKCWZsb2F0MiBfdG1wXzFfaW5Qb3NpdGlvbiA9IHVsb2NhbE1hdHJpeF9TdGFnZTAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1N0YWdlMC55dzsKCXNrX1Bvc2l0aW9uID0gZmxvYXQ0KF90bXBfMF9pblBvc2l0aW9uLnggLCBfdG1wXzBfaW5Qb3NpdGlvbi55LCAwLCAxKTsKfQoAAAAAAQEAAAAAAAABAQBHBQAAdW5pZm9ybSBmbG9hdDQgdXJlY3RVbmlmb3JtX1N0YWdlMV9jMDsKaW4gZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfU3RhZ2UwOwppbiBoYWxmNCB2aW5Db2xvcl9TdGFnZTA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmlubGluZSBoYWxmNCBBQVJlY3RFZmZlY3RfU3RhZ2UxX2MwKGhhbGY0IF9pbnB1dCkgCnsKCWZsb2F0NCBwcmV2UmVjdCA9IGZsb2F0NCgtMS4wMDAwMDAsIC0xLjAwMDAwMCwgLTEuMDAwMDAwLCAtMS4wMDAwMDApOwoJaGFsZiBjb3ZlcmFnZTsKCUBzd2l0Y2ggKDEpIAoJewoJCWNhc2UgMDogICAgY2FzZSAyOiAgICAgICAgY292ZXJhZ2UgPSBoYWxmKGFsbChncmVhdGVyVGhhbihmbG9hdDQoc2tfRnJhZ0Nvb3JkLnh5LCB1cmVjdFVuaWZvcm1fU3RhZ2UxX2MwLnp3KSwgZmxvYXQ0KHVyZWN0VW5pZm9ybV9TdGFnZTFfYzAueHksIHNrX0ZyYWdDb29yZC54eSkpKSA/IDEgOiAwKTsKCQlicmVhazsKCQlkZWZhdWx0OiAgICAgICAgaGFsZjQgZGlzdHM0ID0gY2xhbXAoaGFsZjQoMS4wLCAxLjAsIC0xLjAsIC0xLjApICogaGFsZjQoc2tfRnJhZ0Nvb3JkLnh5eHkgLSB1cmVjdFVuaWZvcm1fU3RhZ2UxX2MwKSwgMC4wLCAxLjApOwoJCWhhbGYyIGRpc3RzMiA9IChkaXN0czQueHkgKyBkaXN0czQuencpIC0gMS4wOwoJCWNvdmVyYWdlID0gZGlzdHMyLnggKiBkaXN0czIueTsKCX0KCUBpZiAoMSA9PSAyIHx8IDEgPT0gMykgCgl7CgkJY292ZXJhZ2UgPSAxLjAgLSBjb3ZlcmFnZTsKCX0KCXJldHVybiBfaW5wdXQgKiBjb3ZlcmFnZTsKfQp2b2lkIG1haW4oKSAKewoJaGFsZjQgb3V0cHV0Q29sb3JfU3RhZ2UwOwoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfU3RhZ2UwOwoJewoJCS8vIFN0YWdlIDAsIENpcmNsZUdlb21ldHJ5UHJvY2Vzc29yCgkJZmxvYXQ0IGNpcmNsZUVkZ2U7CgkJY2lyY2xlRWRnZSA9IHZpbkNpcmNsZUVkZ2VfU3RhZ2UwOwoJCW91dHB1dENvbG9yX1N0YWdlMCA9IHZpbkNvbG9yX1N0YWdlMDsKCQlmbG9hdCBkID0gbGVuZ3RoKGNpcmNsZUVkZ2UueHkpOwoJCWhhbGYgZGlzdGFuY2VUb091dGVyRWRnZSA9IGhhbGYoY2lyY2xlRWRnZS56ICogKDEuMCAtIGQpKTsKCQloYWxmIGVkZ2VBbHBoYSA9IHNhdHVyYXRlKGRpc3RhbmNlVG9PdXRlckVkZ2UpOwoJCW91dHB1dENvdmVyYWdlX1N0YWdlMCA9IGhhbGY0KGVkZ2VBbHBoYSk7Cgl9CgloYWxmNCBvdXRwdXRfU3RhZ2UxOwoJb3V0cHV0X1N0YWdlMSA9IEFBUmVjdEVmZmVjdF9TdGFnZTFfYzAob3V0cHV0Q292ZXJhZ2VfU3RhZ2UwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TdGFnZTAgKiBvdXRwdXRfU3RhZ2UxOwoJfQp9CgAAAQEAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAwAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAMAAAAaW5DaXJjbGVFZGdlAQAAAAAAAAA=","LIIAAAAAAAGAAAAAAIWP577774BSZ7X7777QCAAAAAAAAAAAHIRQYAAAAAAACAQAAAAHQBRAIAAAAAEAKMAYIDAAAACAAAAAAC4AEAAAAAIAAAAAQCIBCAAAAA":"AwAAAExTS1MFAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdkxvY2FsQ29vcmRfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCXZMb2NhbENvb3JkX1N0YWdlMCA9IGxvY2FsQ29vcmQ7Cglza19Qb3NpdGlvbiA9IGZsb2F0NChwb3NpdGlvbi54ICwgcG9zaXRpb24ueSwgMCwgMSk7Cn0KAAAAAAAAAAAAAAAAAAAAwBIAAHVuaWZvcm0gaGFsZjIgdV8wX0luY3JlbWVudF9TdGFnZTFfYzA7CnVuaWZvcm0gaGFsZjQgdV8xX0tlcm5lbF9TdGFnZTFfYzBbN107CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TdGFnZTFfYzBfYzA7CnVuaWZvcm0gZmxvYXQ0IHVjbGFtcF9TdGFnZTFfYzBfYzBfYzA7CnVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1N0YWdlMTsKaW4gZmxvYXQyIHZMb2NhbENvb3JkX1N0YWdlMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgVGV4dHVyZUVmZmVjdF9TdGFnZTFfYzBfYzBfYzAoaGFsZjQgX2lucHV0LCBmbG9hdDIgX2Nvb3JkcykgCnsKCWZsb2F0MiBpbkNvb3JkID0gX2Nvb3JkczsKCWZsb2F0MiBzdWJzZXRDb29yZDsKCXN1YnNldENvb3JkLnggPSBpbkNvb3JkLng7CglzdWJzZXRDb29yZC55ID0gaW5Db29yZC55OwoJZmxvYXQyIGNsYW1wZWRDb29yZDsKCWNsYW1wZWRDb29yZC54ID0gc3Vic2V0Q29vcmQueDsKCWNsYW1wZWRDb29yZC55ID0gY2xhbXAoc3Vic2V0Q29vcmQueSwgdWNsYW1wX1N0YWdlMV9jMF9jMF9jMC55LCB1Y2xhbXBfU3RhZ2UxX2MwX2MwX2MwLncpOwoJaGFsZjQgdGV4dHVyZUNvbG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1N0YWdlMSwgY2xhbXBlZENvb3JkKTsKCXJldHVybiB0ZXh0dXJlQ29sb3I7Cn0KaGFsZjQgTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChoYWxmNCBfaW5wdXQsIGZsb2F0MiBfY29vcmRzKSAKewoJcmV0dXJuIFRleHR1cmVFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKF9pbnB1dCwgKCh1bWF0cml4X1N0YWdlMV9jMF9jMCkgKiBfY29vcmRzLnh5MSkueHkpOwp9CmlubGluZSBoYWxmNCBHYXVzc2lhbkNvbnZvbHV0aW9uX1N0YWdlMV9jMChoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBfMl9jb2xvciA9IGhhbGY0KDAuMCk7CglmbG9hdDIgXzNfY29vcmQgPSAodkxvY2FsQ29vcmRfU3RhZ2UwIC0gZmxvYXQyKCgxMi4wICogdV8wX0luY3JlbWVudF9TdGFnZTFfYzApKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMFswXS54KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMFswXS55KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMFswXS56KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMFswXS53KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMFsxXS54KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMFsxXS55KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMFsxXS56KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMFsxXS53KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMFsyXS54KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMFsyXS55KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMFsyXS56KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMFsyXS53KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMFszXS54KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMFszXS55KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMFszXS56KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMFszXS53KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMFs0XS54KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMFs0XS55KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMFs0XS56KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMFs0XS53KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMFs1XS54KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMFs1XS55KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMFs1XS56KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMFs1XS53KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMFs2XS54KSk7CglyZXR1cm4gXzJfY29sb3I7Cn0Kdm9pZCBtYWluKCkgCnsKCWhhbGY0IG91dHB1dENvbG9yX1N0YWdlMDsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1N0YWdlMDsKCXsKCQkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCQlvdXRwdXRDb2xvcl9TdGFnZTAgPSBoYWxmNCgxKTsKCQlvdXRwdXRDb3ZlcmFnZV9TdGFnZTAgPSBoYWxmNCgxKTsKCX0KCWhhbGY0IG91dHB1dF9TdGFnZTE7CglvdXRwdXRfU3RhZ2UxID0gR2F1c3NpYW5Db252b2x1dGlvbl9TdGFnZTFfYzAob3V0cHV0Q29sb3JfU3RhZ2UwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRfU3RhZ2UxICogb3V0cHV0Q292ZXJhZ2VfU3RhZ2UwOwoJfQp9CgAAAAABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAACAAAACAAAAHBvc2l0aW9uCgAAAGxvY2FsQ29vcmQAAAEAAAAAAAAA","LIIAAAAAAAGAAAAAAIWP577774BSZ7X7777QCAAAAAAAAAAAZIRQCAICAAAAA6BIEBAAAAAAQAJQDBAMAACAIAAAAAALQAQAAAABAAAAACAJAEIAAAAA":"AwAAAExTS1OjAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TdGFnZTFfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TdGFnZTFfYzBfYzA7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzBfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCXNrX1Bvc2l0aW9uID0gZmxvYXQ0KHBvc2l0aW9uLnggLCBwb3NpdGlvbi55LCAwLCAxKTsKCXsKCQl2VHJhbnNmb3JtZWRDb29yZHNfMF9TdGFnZTAgPSAoKCh1bWF0cml4X1N0YWdlMV9jMF9jMCkgKiAodW1hdHJpeF9TdGFnZTFfYzApKSAqIGxvY2FsQ29vcmQueHkxKS54eTsKCX0KfQoAAAAAAAAAAAAAAAAAlAQAAHVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TdGFnZTFfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TdGFnZTFfYzBfYzA7CnVuaWZvcm0gZmxvYXQ0IHVjbGFtcF9TdGFnZTFfYzBfYzBfYzA7CnVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1N0YWdlMTsKaW4gZmxvYXQyIHZUcmFuc2Zvcm1lZENvb3Jkc18wX1N0YWdlMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgVGV4dHVyZUVmZmVjdF9TdGFnZTFfYzBfYzBfYzAoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGluQ29vcmQgPSB2VHJhbnNmb3JtZWRDb29yZHNfMF9TdGFnZTA7CglmbG9hdDIgc3Vic2V0Q29vcmQ7CglzdWJzZXRDb29yZC54ID0gaW5Db29yZC54OwoJc3Vic2V0Q29vcmQueSA9IGluQ29vcmQueTsKCWZsb2F0MiBjbGFtcGVkQ29vcmQ7CgljbGFtcGVkQ29vcmQgPSBjbGFtcChzdWJzZXRDb29yZCwgdWNsYW1wX1N0YWdlMV9jMF9jMF9jMC54eSwgdWNsYW1wX1N0YWdlMV9jMF9jMF9jMC56dyk7CgloYWxmNCB0ZXh0dXJlQ29sb3IgPSBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfU3RhZ2UxLCBjbGFtcGVkQ29vcmQpOwoJcmV0dXJuIHRleHR1cmVDb2xvcjsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwKGhhbGY0IF9pbnB1dCkgCnsKCXJldHVybiBUZXh0dXJlRWZmZWN0X1N0YWdlMV9jMF9jMF9jMChfaW5wdXQpOwp9CmlubGluZSBoYWxmNCBNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwKGhhbGY0IF9pbnB1dCkgCnsKCXJldHVybiBNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwKF9pbnB1dCk7Cn0Kdm9pZCBtYWluKCkgCnsKCWhhbGY0IG91dHB1dENvbG9yX1N0YWdlMDsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1N0YWdlMDsKCXsKCQkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCQlvdXRwdXRDb2xvcl9TdGFnZTAgPSBoYWxmNCgxKTsKCQlvdXRwdXRDb3ZlcmFnZV9TdGFnZTAgPSBoYWxmNCgxKTsKCX0KCWhhbGY0IG91dHB1dF9TdGFnZTE7CglvdXRwdXRfU3RhZ2UxID0gTWF0cml4RWZmZWN0X1N0YWdlMV9jMChvdXRwdXRDb2xvcl9TdGFnZTApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dF9TdGFnZTEgKiBvdXRwdXRDb3ZlcmFnZV9TdGFnZTA7Cgl9Cn0KAAAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24KAAAAbG9jYWxDb29yZAAAAQAAAAAAAAA=","B2ABSAAAAQAAAAABC3777777AAOAAAAAAAAAAAAAXABAAAAACAAAAAEASAIQAAAA":"AwAAAExTS1OpAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gaGFsZjQgdUNvbG9yX1N0YWdlMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGYgaW5Db3ZlcmFnZTsKb3V0IGhhbGY0IHZjb2xvcl9TdGFnZTA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIERlZmF1bHRHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgY29sb3IgPSB1Q29sb3JfU3RhZ2UwOwoJY29sb3IgPSBjb2xvciAqIGluQ292ZXJhZ2U7Cgl2Y29sb3JfU3RhZ2UwID0gY29sb3I7CglmbG9hdDIgX3RtcF8wX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJZmxvYXQyIF90bXBfMV9pblBvc2l0aW9uID0gaW5Qb3NpdGlvbjsKCXNrX1Bvc2l0aW9uID0gZmxvYXQ0KF90bXBfMF9pblBvc2l0aW9uLnggLCBfdG1wXzBfaW5Qb3NpdGlvbi55LCAwLCAxKTsKfQoAAAAAAAAAAAAAAAAAAABVAQAAaW4gaGFsZjQgdmNvbG9yX1N0YWdlMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKdm9pZCBtYWluKCkgCnsKCWhhbGY0IG91dHB1dENvbG9yX1N0YWdlMDsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1N0YWdlMDsKCXsKCQkvLyBTdGFnZSAwLCBEZWZhdWx0R2VvbWV0cnlQcm9jZXNzb3IKCQlvdXRwdXRDb2xvcl9TdGFnZTAgPSB2Y29sb3JfU3RhZ2UwOwoJCW91dHB1dENvdmVyYWdlX1N0YWdlMCA9IGhhbGY0KDEpOwoJfQoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1N0YWdlMCAqIG91dHB1dENvdmVyYWdlX1N0YWdlMDsKCX0KfQoAAAAAAAAAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAgAAAAoAAABpblBvc2l0aW9uAAAKAAAAaW5Db3ZlcmFnZQAAAQAAAAAAAAA=","LIIAAAAAAAGAAAAAAIWP577774BSZ7X7777QCAAAAAAAAAAAZIRQCAACAAAABHAIEBSAAIAAAAAAAAGACUAAAAEAAAAAAAEERQAAAAA":"AwAAAExTS1NjAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TdGFnZTFfYzA7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzBfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCXNrX1Bvc2l0aW9uID0gZmxvYXQ0KHBvc2l0aW9uLnggLCBwb3NpdGlvbi55LCAwLCAxKTsKCXsKCQl2VHJhbnNmb3JtZWRDb29yZHNfMF9TdGFnZTAgPSAoKCh1bWF0cml4X1N0YWdlMV9jMCkpICogbG9jYWxDb29yZC54eTEpLnh5OwoJfQp9CgAAAAAAAAAAAAAAAAAjBAAAdW5pZm9ybSBmbG9hdDN4MyB1bWF0cml4X1N0YWdlMV9jMDsKdW5pZm9ybSBmbG9hdDQgdWNsYW1wX1N0YWdlMV9jMF9jMDsKdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfU3RhZ2UxOwppbiBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzBfU3RhZ2UwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBUZXh0dXJlRWZmZWN0X1N0YWdlMV9jMF9jMChoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgaW5Db29yZCA9IHZUcmFuc2Zvcm1lZENvb3Jkc18wX1N0YWdlMDsKCWZsb2F0MiBzdWJzZXRDb29yZDsKCXN1YnNldENvb3JkLnggPSBpbkNvb3JkLng7CglzdWJzZXRDb29yZC55ID0gaW5Db29yZC55OwoJZmxvYXQyIGNsYW1wZWRDb29yZDsKCWNsYW1wZWRDb29yZC54ID0gY2xhbXAoc3Vic2V0Q29vcmQueCwgdWNsYW1wX1N0YWdlMV9jMF9jMC54LCB1Y2xhbXBfU3RhZ2UxX2MwX2MwLnopOwoJY2xhbXBlZENvb3JkLnkgPSBzdWJzZXRDb29yZC55OwoJaGFsZjQgdGV4dHVyZUNvbG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1N0YWdlMSwgY2xhbXBlZENvb3JkKTsKCXJldHVybiB0ZXh0dXJlQ29sb3I7Cn0KaW5saW5lIGhhbGY0IE1hdHJpeEVmZmVjdF9TdGFnZTFfYzAoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIFRleHR1cmVFZmZlY3RfU3RhZ2UxX2MwX2MwKF9pbnB1dCk7Cn0Kdm9pZCBtYWluKCkgCnsKCWhhbGY0IG91dHB1dENvbG9yX1N0YWdlMDsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1N0YWdlMDsKCXsKCQkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCQlvdXRwdXRDb2xvcl9TdGFnZTAgPSBoYWxmNCgxKTsKCQlvdXRwdXRDb3ZlcmFnZV9TdGFnZTAgPSBoYWxmNCgxKTsKCX0KCWhhbGY0IG91dHB1dF9TdGFnZTE7CglvdXRwdXRfU3RhZ2UxID0gTWF0cml4RWZmZWN0X1N0YWdlMV9jMChvdXRwdXRDb2xvcl9TdGFnZTApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dF9TdGFnZTEgKiBvdXRwdXRDb3ZlcmFnZV9TdGFnZTA7Cgl9Cn0KAAAAAAABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAACAAAACAAAAHBvc2l0aW9uCgAAAGxvY2FsQ29vcmQAAAEAAAAAAAAA","DIQAAAAAMAAAAABAYAROFA2CAIAAAABAAAAAAAAAAAAAAVYAAAAAAAQAAAABAMQC":"AwAAAExTS1NQAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQyIHVBdGxhc1NpemVJbnZfU3RhZ2UwOwppbiBmbG9hdDIgaW5Qb3NpdGlvbjsKaW4gaGFsZjQgaW5Db2xvcjsKaW4gdXNob3J0MiBpblRleHR1cmVDb29yZHM7Cm91dCBmbG9hdDIgdlRleHR1cmVDb29yZHNfU3RhZ2UwOwpmbGF0IG91dCBmbG9hdCB2VGV4SW5kZXhfU3RhZ2UwOwpvdXQgaGFsZjQgdmluQ29sb3JfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBCaXRtYXBUZXh0CglpbnQgdGV4SWR4ID0gMDsKCWZsb2F0MiB1bm9ybVRleENvb3JkcyA9IGZsb2F0MihpblRleHR1cmVDb29yZHMueCwgaW5UZXh0dXJlQ29vcmRzLnkpOwoJdlRleHR1cmVDb29yZHNfU3RhZ2UwID0gdW5vcm1UZXhDb29yZHMgKiB1QXRsYXNTaXplSW52X1N0YWdlMDsKCXZUZXhJbmRleF9TdGFnZTAgPSBmbG9hdCh0ZXhJZHgpOwoJdmluQ29sb3JfU3RhZ2UwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247Cglza19Qb3NpdGlvbiA9IGZsb2F0NChpblBvc2l0aW9uLnggLCBpblBvc2l0aW9uLnksIDAsIDEpOwp9CgAAAAAAAAAAAAAAAB4CAAB1bmlmb3JtIHNhbXBsZXIyRCB1VGV4dHVyZVNhbXBsZXJfMF9TdGFnZTA7CmluIGZsb2F0MiB2VGV4dHVyZUNvb3Jkc19TdGFnZTA7CmZsYXQgaW4gZmxvYXQgdlRleEluZGV4X1N0YWdlMDsKaW4gaGFsZjQgdmluQ29sb3JfU3RhZ2UwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJaGFsZjQgb3V0cHV0Q29sb3JfU3RhZ2UwOwoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfU3RhZ2UwOwoJewoJCS8vIFN0YWdlIDAsIEJpdG1hcFRleHQKCQlvdXRwdXRDb2xvcl9TdGFnZTAgPSB2aW5Db2xvcl9TdGFnZTA7CgkJaGFsZjQgdGV4Q29sb3I7CgkJewoJCQl0ZXhDb2xvciA9IHNhbXBsZSh1VGV4dHVyZVNhbXBsZXJfMF9TdGFnZTAsIHZUZXh0dXJlQ29vcmRzX1N0YWdlMCkucnJycjsKCQl9CgkJb3V0cHV0Q292ZXJhZ2VfU3RhZ2UwID0gdGV4Q29sb3I7Cgl9Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfU3RhZ2UwICogb3V0cHV0Q292ZXJhZ2VfU3RhZ2UwOwoJfQp9CgAAAAAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IADwAAAGluVGV4dHVyZUNvb3JkcwABAAAAAAAAAA==","LIIAAAAAAAGAAAAAAIWP577774BSZ7X7777QCAAAAAAAAAAAHIRQSAAAAAAQAAQAAAAHQJQAIAAAAAEAKMAYIDAAAQAAAAAAAC4AEAAAAAIAAAAAQCIBCAAAAA":"AwAAAExTS1MFAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdkxvY2FsQ29vcmRfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCXZMb2NhbENvb3JkX1N0YWdlMCA9IGxvY2FsQ29vcmQ7Cglza19Qb3NpdGlvbiA9IGZsb2F0NChwb3NpdGlvbi54ICwgcG9zaXRpb24ueSwgMCwgMSk7Cn0KAAAAAAAAAAAAAAAAAAAAgw8AAHVuaWZvcm0gaGFsZjIgdV8wX0luY3JlbWVudF9TdGFnZTFfYzA7CnVuaWZvcm0gaGFsZjQgdV8xX0tlcm5lbF9TdGFnZTFfYzBbNV07CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TdGFnZTFfYzBfYzA7CnVuaWZvcm0gZmxvYXQ0IHVjbGFtcF9TdGFnZTFfYzBfYzBfYzA7CnVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1N0YWdlMTsKaW4gZmxvYXQyIHZMb2NhbENvb3JkX1N0YWdlMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgVGV4dHVyZUVmZmVjdF9TdGFnZTFfYzBfYzBfYzAoaGFsZjQgX2lucHV0LCBmbG9hdDIgX2Nvb3JkcykgCnsKCWZsb2F0MiBpbkNvb3JkID0gX2Nvb3JkczsKCWZsb2F0MiBzdWJzZXRDb29yZDsKCXN1YnNldENvb3JkLnggPSBpbkNvb3JkLng7CglzdWJzZXRDb29yZC55ID0gaW5Db29yZC55OwoJZmxvYXQyIGNsYW1wZWRDb29yZDsKCWNsYW1wZWRDb29yZC54ID0gY2xhbXAoc3Vic2V0Q29vcmQueCwgdWNsYW1wX1N0YWdlMV9jMF9jMF9jMC54LCB1Y2xhbXBfU3RhZ2UxX2MwX2MwX2MwLnopOwoJY2xhbXBlZENvb3JkLnkgPSBzdWJzZXRDb29yZC55OwoJaGFsZjQgdGV4dHVyZUNvbG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1N0YWdlMSwgY2xhbXBlZENvb3JkKTsKCXJldHVybiB0ZXh0dXJlQ29sb3I7Cn0KaGFsZjQgTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChoYWxmNCBfaW5wdXQsIGZsb2F0MiBfY29vcmRzKSAKewoJcmV0dXJuIFRleHR1cmVFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKF9pbnB1dCwgKCh1bWF0cml4X1N0YWdlMV9jMF9jMCkgKiBfY29vcmRzLnh5MSkueHkpOwp9CmlubGluZSBoYWxmNCBHYXVzc2lhbkNvbnZvbHV0aW9uX1N0YWdlMV9jMChoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBfMl9jb2xvciA9IGhhbGY0KDAuMCk7CglmbG9hdDIgXzNfY29vcmQgPSAodkxvY2FsQ29vcmRfU3RhZ2UwIC0gZmxvYXQyKCg5LjAgKiB1XzBfSW5jcmVtZW50X1N0YWdlMV9jMCkpKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzBdLngpKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzBdLnkpKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzBdLnopKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzBdLncpKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzFdLngpKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzFdLnkpKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzFdLnopKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzFdLncpKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzJdLngpKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzJdLnkpKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzJdLnopKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzJdLncpKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzNdLngpKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzNdLnkpKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzNdLnopKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzNdLncpKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzRdLngpKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzRdLnkpKTsKCShfM19jb29yZCArPSBmbG9hdDIodV8wX0luY3JlbWVudF9TdGFnZTFfYzApKTsKCShfMl9jb2xvciArPSAoTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQsIF8zX2Nvb3JkKSAqIHVfMV9LZXJuZWxfU3RhZ2UxX2MwWzRdLnopKTsKCXJldHVybiBfMl9jb2xvcjsKfQp2b2lkIG1haW4oKSAKewoJaGFsZjQgb3V0cHV0Q29sb3JfU3RhZ2UwOwoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfU3RhZ2UwOwoJewoJCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJCW91dHB1dENvbG9yX1N0YWdlMCA9IGhhbGY0KDEpOwoJCW91dHB1dENvdmVyYWdlX1N0YWdlMCA9IGhhbGY0KDEpOwoJfQoJaGFsZjQgb3V0cHV0X1N0YWdlMTsKCW91dHB1dF9TdGFnZTEgPSBHYXVzc2lhbkNvbnZvbHV0aW9uX1N0YWdlMV9jMChvdXRwdXRDb2xvcl9TdGFnZTApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dF9TdGFnZTEgKiBvdXRwdXRDb3ZlcmFnZV9TdGFnZTA7Cgl9Cn0KAAAAAAABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAACAAAACAAAAHBvc2l0aW9uCgAAAGxvY2FsQ29vcmQAAAEAAAAAAAAA","LIQAAAAAAAGAAAAAAIWP57ZDH37P7777777QCAAAAAAAAAAAMIQHSAAAAADQAAAALTEACAAAAAAFOAAAAAAAEAAAAAIDEAQA":"AwAAAExTS1P0AAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7CmZsYXQgb3V0IGhhbGY0IHZjb2xvcl9TdGFnZTA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmNvbG9yX1N0YWdlMCA9IGNvbG9yOwoJc2tfUG9zaXRpb24gPSBmbG9hdDQocG9zaXRpb24ueCAsIHBvc2l0aW9uLnksIDAsIDEpOwp9CgABAQAAAAAAAAEBAAMGAAB1bmlmb3JtIGZsb2F0NCB1aW5uZXJSZWN0X1N0YWdlMV9jMDsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfU3RhZ2UxX2MwOwp1bmlmb3JtIGZsb2F0NCB1cmVjdFVuaWZvcm1fU3RhZ2UxX2MwX2MwOwpmbGF0IGluIGhhbGY0IHZjb2xvcl9TdGFnZTA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IEFBUmVjdEVmZmVjdF9TdGFnZTFfYzBfYzAoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQ0IHByZXZSZWN0ID0gZmxvYXQ0KC0xLjAwMDAwMCwgLTEuMDAwMDAwLCAtMS4wMDAwMDAsIC0xLjAwMDAwMCk7CgloYWxmIGNvdmVyYWdlOwoJQHN3aXRjaCAoMykgCgl7CgkJY2FzZSAwOiAgICBjYXNlIDI6ICAgICAgICBjb3ZlcmFnZSA9IGhhbGYoYWxsKGdyZWF0ZXJUaGFuKGZsb2F0NChza19GcmFnQ29vcmQueHksIHVyZWN0VW5pZm9ybV9TdGFnZTFfYzBfYzAuencpLCBmbG9hdDQodXJlY3RVbmlmb3JtX1N0YWdlMV9jMF9jMC54eSwgc2tfRnJhZ0Nvb3JkLnh5KSkpID8gMSA6IDApOwoJCWJyZWFrOwoJCWRlZmF1bHQ6ICAgICAgICBoYWxmNCBkaXN0czQgPSBjbGFtcChoYWxmNCgxLjAsIDEuMCwgLTEuMCwgLTEuMCkgKiBoYWxmNChza19GcmFnQ29vcmQueHl4eSAtIHVyZWN0VW5pZm9ybV9TdGFnZTFfYzBfYzApLCAwLjAsIDEuMCk7CgkJaGFsZjIgZGlzdHMyID0gKGRpc3RzNC54eSArIGRpc3RzNC56dykgLSAxLjA7CgkJY292ZXJhZ2UgPSBkaXN0czIueCAqIGRpc3RzMi55OwoJfQoJQGlmICgzID09IDIgfHwgMyA9PSAzKSAKCXsKCQljb3ZlcmFnZSA9IDEuMCAtIGNvdmVyYWdlOwoJfQoJcmV0dXJuIF9pbnB1dCAqIGNvdmVyYWdlOwp9CmlubGluZSBoYWxmNCBDaXJjdWxhclJSZWN0X1N0YWdlMV9jMChoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgZHh5MCA9IHVpbm5lclJlY3RfU3RhZ2UxX2MwLkxUIC0gc2tfRnJhZ0Nvb3JkLnh5OwoJZmxvYXQyIGR4eTEgPSBza19GcmFnQ29vcmQueHkgLSB1aW5uZXJSZWN0X1N0YWdlMV9jMC5SQjsKCWZsb2F0MiBkeHkgPSBtYXgobWF4KGR4eTAsIGR4eTEpLCAwLjApOwoJaGFsZiBhbHBoYSA9IGhhbGYoc2F0dXJhdGUodXJhZGl1c1BsdXNIYWxmX1N0YWdlMV9jMC54IC0gbGVuZ3RoKGR4eSkpKTsKCXJldHVybiBBQVJlY3RFZmZlY3RfU3RhZ2UxX2MwX2MwKF9pbnB1dCkgKiBhbHBoYTsKfQp2b2lkIG1haW4oKSAKewoJaGFsZjQgb3V0cHV0Q29sb3JfU3RhZ2UwOwoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfU3RhZ2UwOwoJewoJCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJCW91dHB1dENvbG9yX1N0YWdlMCA9IHZjb2xvcl9TdGFnZTA7CgkJb3V0cHV0Q292ZXJhZ2VfU3RhZ2UwID0gaGFsZjQoMSk7Cgl9CgloYWxmNCBvdXRwdXRfU3RhZ2UxOwoJb3V0cHV0X1N0YWdlMSA9IENpcmN1bGFyUlJlY3RfU3RhZ2UxX2MwKG91dHB1dENvdmVyYWdlX1N0YWdlMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfU3RhZ2UwICogb3V0cHV0X1N0YWdlMTsKCX0KfQoAAAEBAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24FAAAAY29sb3IAAAABAAAAAAAAAA==","LEMAAAAABCYIR6AYYAAAAAAAAAAAAAGIQUKAAAAAABYAKAAAAAQAAAAAAAQSGAAA":"AwAAAExTS1NwAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiBmbG9hdDQgaW5RdWFkRWRnZTsKb3V0IGZsb2F0NCB2UXVhZEVkZ2VfU3RhZ2UwOwpvdXQgaGFsZjQgdmluQ29sb3JfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBRdWFkRWRnZQoJdlF1YWRFZGdlX1N0YWdlMCA9IGluUXVhZEVkZ2U7Cgl2aW5Db2xvcl9TdGFnZTAgPSBpbkNvbG9yOwoJZmxvYXQyIF90bXBfMF9pblBvc2l0aW9uID0gaW5Qb3NpdGlvbjsKCXNrX1Bvc2l0aW9uID0gZmxvYXQ0KF90bXBfMF9pblBvc2l0aW9uLnggLCBfdG1wXzBfaW5Qb3NpdGlvbi55LCAwLCAxKTsKfQoAAQEAAAAAAAABAQCSBgAAdW5pZm9ybSBmbG9hdDQgdXJlY3RVbmlmb3JtX1N0YWdlMV9jMDsKaW4gZmxvYXQ0IHZRdWFkRWRnZV9TdGFnZTA7CmluIGhhbGY0IHZpbkNvbG9yX1N0YWdlMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaW5saW5lIGhhbGY0IEFBUmVjdEVmZmVjdF9TdGFnZTFfYzAoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQ0IHByZXZSZWN0ID0gZmxvYXQ0KC0xLjAwMDAwMCwgLTEuMDAwMDAwLCAtMS4wMDAwMDAsIC0xLjAwMDAwMCk7CgloYWxmIGNvdmVyYWdlOwoJQHN3aXRjaCAoMSkgCgl7CgkJY2FzZSAwOiAgICBjYXNlIDI6ICAgICAgICBjb3ZlcmFnZSA9IGhhbGYoYWxsKGdyZWF0ZXJUaGFuKGZsb2F0NChza19GcmFnQ29vcmQueHksIHVyZWN0VW5pZm9ybV9TdGFnZTFfYzAuencpLCBmbG9hdDQodXJlY3RVbmlmb3JtX1N0YWdlMV9jMC54eSwgc2tfRnJhZ0Nvb3JkLnh5KSkpID8gMSA6IDApOwoJCWJyZWFrOwoJCWRlZmF1bHQ6ICAgICAgICBoYWxmNCBkaXN0czQgPSBjbGFtcChoYWxmNCgxLjAsIDEuMCwgLTEuMCwgLTEuMCkgKiBoYWxmNChza19GcmFnQ29vcmQueHl4eSAtIHVyZWN0VW5pZm9ybV9TdGFnZTFfYzApLCAwLjAsIDEuMCk7CgkJaGFsZjIgZGlzdHMyID0gKGRpc3RzNC54eSArIGRpc3RzNC56dykgLSAxLjA7CgkJY292ZXJhZ2UgPSBkaXN0czIueCAqIGRpc3RzMi55OwoJfQoJQGlmICgxID09IDIgfHwgMSA9PSAzKSAKCXsKCQljb3ZlcmFnZSA9IDEuMCAtIGNvdmVyYWdlOwoJfQoJcmV0dXJuIF9pbnB1dCAqIGNvdmVyYWdlOwp9CnZvaWQgbWFpbigpIAp7CgloYWxmNCBvdXRwdXRDb2xvcl9TdGFnZTA7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TdGFnZTA7Cgl7CgkJLy8gU3RhZ2UgMCwgUXVhZEVkZ2UKCQlvdXRwdXRDb2xvcl9TdGFnZTAgPSB2aW5Db2xvcl9TdGFnZTA7CgkJaGFsZiBlZGdlQWxwaGE7CgkJaGFsZjIgZHV2ZHggPSBoYWxmMihkRmR4KHZRdWFkRWRnZV9TdGFnZTAueHkpKTsKCQloYWxmMiBkdXZkeSA9IGhhbGYyKGRGZHkodlF1YWRFZGdlX1N0YWdlMC54eSkpOwoJCWlmICh2UXVhZEVkZ2VfU3RhZ2UwLnogPiAwLjAgJiYgdlF1YWRFZGdlX1N0YWdlMC53ID4gMC4wKSAKCQl7CgkJCWVkZ2VBbHBoYSA9IGhhbGYobWluKG1pbih2UXVhZEVkZ2VfU3RhZ2UwLnosIHZRdWFkRWRnZV9TdGFnZTAudykgKyAwLjUsIDEuMCkpOwoJCX0KCQllbHNlIAoJCXsKCQkJaGFsZjIgZ0YgPSBoYWxmMihoYWxmKDIuMCp2UXVhZEVkZ2VfU3RhZ2UwLngqZHV2ZHgueCAtIGR1dmR4LnkpLCAgICAgICAgICAgICAgICAgaGFsZigyLjAqdlF1YWRFZGdlX1N0YWdlMC54KmR1dmR5LnggLSBkdXZkeS55KSk7CgkJCWVkZ2VBbHBoYSA9IGhhbGYodlF1YWRFZGdlX1N0YWdlMC54KnZRdWFkRWRnZV9TdGFnZTAueCAtIHZRdWFkRWRnZV9TdGFnZTAueSk7CgkJCWVkZ2VBbHBoYSA9IHNhdHVyYXRlKDAuNSAtIGVkZ2VBbHBoYSAvIGxlbmd0aChnRikpOwoJCX0KCQlvdXRwdXRDb3ZlcmFnZV9TdGFnZTAgPSBoYWxmNChlZGdlQWxwaGEpOwoJfQoJaGFsZjQgb3V0cHV0X1N0YWdlMTsKCW91dHB1dF9TdGFnZTEgPSBBQVJlY3RFZmZlY3RfU3RhZ2UxX2MwKG91dHB1dENvdmVyYWdlX1N0YWdlMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfU3RhZ2UwICogb3V0cHV0X1N0YWdlMTsKCX0KfQoAAAABAQABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAADAAAACgAAAGluUG9zaXRpb24AAAcAAABpbkNvbG9yAAoAAABpblF1YWRFZGdlAAABAAAAAAAAAA==","AWQAGAAAQAAIXCEPAGGP777777777737AAAAAAAAAAAAAXABAAAAACAAAAAEBSAI":"AwAAAExTS1MJAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TdGFnZTA7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiBmbG9hdDQgaW5DaXJjbGVFZGdlOwpvdXQgZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfU3RhZ2UwOwpvdXQgaGFsZjQgdmluQ29sb3JfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TdGFnZTAgPSBpbkNpcmNsZUVkZ2U7Cgl2aW5Db2xvcl9TdGFnZTAgPSBpbkNvbG9yOwoJZmxvYXQyIF90bXBfMF9pblBvc2l0aW9uID0gaW5Qb3NpdGlvbjsKCWZsb2F0MiBfdG1wXzFfaW5Qb3NpdGlvbiA9IHVsb2NhbE1hdHJpeF9TdGFnZTAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1N0YWdlMC55dzsKCXNrX1Bvc2l0aW9uID0gZmxvYXQ0KF90bXBfMF9pblBvc2l0aW9uLnggLCBfdG1wXzBfaW5Qb3NpdGlvbi55LCAwLCAxKTsKfQoAAAAAAAAAAAAAAAAAAABMAgAAaW4gZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfU3RhZ2UwOwppbiBoYWxmNCB2aW5Db2xvcl9TdGFnZTA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CnZvaWQgbWFpbigpIAp7CgloYWxmNCBvdXRwdXRDb2xvcl9TdGFnZTA7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TdGFnZTA7Cgl7CgkJLy8gU3RhZ2UgMCwgQ2lyY2xlR2VvbWV0cnlQcm9jZXNzb3IKCQlmbG9hdDQgY2lyY2xlRWRnZTsKCQljaXJjbGVFZGdlID0gdmluQ2lyY2xlRWRnZV9TdGFnZTA7CgkJb3V0cHV0Q29sb3JfU3RhZ2UwID0gdmluQ29sb3JfU3RhZ2UwOwoJCWZsb2F0IGQgPSBsZW5ndGgoY2lyY2xlRWRnZS54eSk7CgkJaGFsZiBkaXN0YW5jZVRvT3V0ZXJFZGdlID0gaGFsZihjaXJjbGVFZGdlLnogKiAoMS4wIC0gZCkpOwoJCWhhbGYgZWRnZUFscGhhID0gc2F0dXJhdGUoZGlzdGFuY2VUb091dGVyRWRnZSk7CgkJb3V0cHV0Q292ZXJhZ2VfU3RhZ2UwID0gaGFsZjQoZWRnZUFscGhhKTsKCX0KCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TdGFnZTAgKiBvdXRwdXRDb3ZlcmFnZV9TdGFnZTA7Cgl9Cn0KAAAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IADAAAAGluQ2lyY2xlRWRnZQEAAAAAAAAA","IOIAAAAAAIAAAAABCYBR6AAAAAAAAAAAAC4AEAAAAAIAAAAAQCIBCAAAAA":"AwAAAExTS1MxAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkhhaXJRdWFkRWRnZTsKb3V0IGhhbGY0IHZIYWlyUXVhZEVkZ2VfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBRdWFkCgl2SGFpclF1YWRFZGdlX1N0YWdlMCA9IGluSGFpclF1YWRFZGdlOwoJZmxvYXQyIF90bXBfMF9pblBvc2l0aW9uID0gaW5Qb3NpdGlvbjsKCXNrX1Bvc2l0aW9uID0gZmxvYXQ0KF90bXBfMF9pblBvc2l0aW9uLnggLCBfdG1wXzBfaW5Qb3NpdGlvbi55LCAwLCAxKTsKfQoAAAAAAAAAAAAAAAAAAABjAwAAdW5pZm9ybSBoYWxmNCB1Q29sb3JfU3RhZ2UwOwp1bmlmb3JtIGhhbGYgdUNvdmVyYWdlX1N0YWdlMDsKaW4gaGFsZjQgdkhhaXJRdWFkRWRnZV9TdGFnZTA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CnZvaWQgbWFpbigpIAp7CgloYWxmNCBvdXRwdXRDb2xvcl9TdGFnZTA7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TdGFnZTA7Cgl7CgkJLy8gU3RhZ2UgMCwgUXVhZAoJCW91dHB1dENvbG9yX1N0YWdlMCA9IHVDb2xvcl9TdGFnZTA7CgkJaGFsZiBlZGdlQWxwaGE7CgkJaGFsZjIgZHV2ZHggPSBoYWxmMihkRmR4KHZIYWlyUXVhZEVkZ2VfU3RhZ2UwLnh5KSk7CgkJaGFsZjIgZHV2ZHkgPSBoYWxmMihkRmR5KHZIYWlyUXVhZEVkZ2VfU3RhZ2UwLnh5KSk7CgkJaGFsZjIgZ0YgPSBoYWxmMigyLjAgKiB2SGFpclF1YWRFZGdlX1N0YWdlMC54ICogZHV2ZHgueCAtIGR1dmR4LnksICAgICAgICAgICAgICAgMi4wICogdkhhaXJRdWFkRWRnZV9TdGFnZTAueCAqIGR1dmR5LnggLSBkdXZkeS55KTsKCQllZGdlQWxwaGEgPSBoYWxmKHZIYWlyUXVhZEVkZ2VfU3RhZ2UwLnggKiB2SGFpclF1YWRFZGdlX1N0YWdlMC54IC0gdkhhaXJRdWFkRWRnZV9TdGFnZTAueSk7CgkJZWRnZUFscGhhID0gc3FydChlZGdlQWxwaGEgKiBlZGdlQWxwaGEgLyBkb3QoZ0YsIGdGKSk7CgkJZWRnZUFscGhhID0gbWF4KDEuMCAtIGVkZ2VBbHBoYSwgMC4wKTsKCQlvdXRwdXRDb3ZlcmFnZV9TdGFnZTAgPSBoYWxmNCh1Q292ZXJhZ2VfU3RhZ2UwICogZWRnZUFscGhhKTsKCX0KCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TdGFnZTAgKiBvdXRwdXRDb3ZlcmFnZV9TdGFnZTA7Cgl9Cn0KAAAAAAABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAACAAAACgAAAGluUG9zaXRpb24AAA4AAABpbkhhaXJRdWFkRWRnZQAAAQAAAAAAAAA=","DIQAAAAAMAAAAABAYAROFA2CAIAAAABAAAAAAAAAAAACAF2SAAAAAAGACUAAAAEAAAAAAAEERQAAA":"AwAAAExTS1NQAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQyIHVBdGxhc1NpemVJbnZfU3RhZ2UwOwppbiBmbG9hdDIgaW5Qb3NpdGlvbjsKaW4gaGFsZjQgaW5Db2xvcjsKaW4gdXNob3J0MiBpblRleHR1cmVDb29yZHM7Cm91dCBmbG9hdDIgdlRleHR1cmVDb29yZHNfU3RhZ2UwOwpmbGF0IG91dCBmbG9hdCB2VGV4SW5kZXhfU3RhZ2UwOwpvdXQgaGFsZjQgdmluQ29sb3JfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBCaXRtYXBUZXh0CglpbnQgdGV4SWR4ID0gMDsKCWZsb2F0MiB1bm9ybVRleENvb3JkcyA9IGZsb2F0MihpblRleHR1cmVDb29yZHMueCwgaW5UZXh0dXJlQ29vcmRzLnkpOwoJdlRleHR1cmVDb29yZHNfU3RhZ2UwID0gdW5vcm1UZXhDb29yZHMgKiB1QXRsYXNTaXplSW52X1N0YWdlMDsKCXZUZXhJbmRleF9TdGFnZTAgPSBmbG9hdCh0ZXhJZHgpOwoJdmluQ29sb3JfU3RhZ2UwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247Cglza19Qb3NpdGlvbiA9IGZsb2F0NChpblBvc2l0aW9uLnggLCBpblBvc2l0aW9uLnksIDAsIDEpOwp9CgABAQAAAAAAAAEBABkFAAB1bmlmb3JtIGZsb2F0NCB1cmVjdFVuaWZvcm1fU3RhZ2UxX2MwOwp1bmlmb3JtIHNhbXBsZXIyRCB1VGV4dHVyZVNhbXBsZXJfMF9TdGFnZTA7CmluIGZsb2F0MiB2VGV4dHVyZUNvb3Jkc19TdGFnZTA7CmZsYXQgaW4gZmxvYXQgdlRleEluZGV4X1N0YWdlMDsKaW4gaGFsZjQgdmluQ29sb3JfU3RhZ2UwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwppbmxpbmUgaGFsZjQgQUFSZWN0RWZmZWN0X1N0YWdlMV9jMChoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDQgcHJldlJlY3QgPSBmbG9hdDQoLTEuMDAwMDAwLCAtMS4wMDAwMDAsIC0xLjAwMDAwMCwgLTEuMDAwMDAwKTsKCWhhbGYgY292ZXJhZ2U7CglAc3dpdGNoICgxKSAKCXsKCQljYXNlIDA6ICAgIGNhc2UgMjogICAgICAgIGNvdmVyYWdlID0gaGFsZihhbGwoZ3JlYXRlclRoYW4oZmxvYXQ0KHNrX0ZyYWdDb29yZC54eSwgdXJlY3RVbmlmb3JtX1N0YWdlMV9jMC56dyksIGZsb2F0NCh1cmVjdFVuaWZvcm1fU3RhZ2UxX2MwLnh5LCBza19GcmFnQ29vcmQueHkpKSkgPyAxIDogMCk7CgkJYnJlYWs7CgkJZGVmYXVsdDogICAgICAgIGhhbGY0IGRpc3RzNCA9IGNsYW1wKGhhbGY0KDEuMCwgMS4wLCAtMS4wLCAtMS4wKSAqIGhhbGY0KHNrX0ZyYWdDb29yZC54eXh5IC0gdXJlY3RVbmlmb3JtX1N0YWdlMV9jMCksIDAuMCwgMS4wKTsKCQloYWxmMiBkaXN0czIgPSAoZGlzdHM0Lnh5ICsgZGlzdHM0Lnp3KSAtIDEuMDsKCQljb3ZlcmFnZSA9IGRpc3RzMi54ICogZGlzdHMyLnk7Cgl9CglAaWYgKDEgPT0gMiB8fCAxID09IDMpIAoJewoJCWNvdmVyYWdlID0gMS4wIC0gY292ZXJhZ2U7Cgl9CglyZXR1cm4gX2lucHV0ICogY292ZXJhZ2U7Cn0Kdm9pZCBtYWluKCkgCnsKCWhhbGY0IG91dHB1dENvbG9yX1N0YWdlMDsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1N0YWdlMDsKCXsKCQkvLyBTdGFnZSAwLCBCaXRtYXBUZXh0CgkJb3V0cHV0Q29sb3JfU3RhZ2UwID0gdmluQ29sb3JfU3RhZ2UwOwoJCWhhbGY0IHRleENvbG9yOwoJCXsKCQkJdGV4Q29sb3IgPSBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfU3RhZ2UwLCB2VGV4dHVyZUNvb3Jkc19TdGFnZTApLnJycnI7CgkJfQoJCW91dHB1dENvdmVyYWdlX1N0YWdlMCA9IHRleENvbG9yOwoJfQoJaGFsZjQgb3V0cHV0X1N0YWdlMTsKCW91dHB1dF9TdGFnZTEgPSBBQVJlY3RFZmZlY3RfU3RhZ2UxX2MwKG91dHB1dENvdmVyYWdlX1N0YWdlMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfU3RhZ2UwICogb3V0cHV0X1N0YWdlMTsKCX0KfQoAAAAAAQEAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAwAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAPAAAAaW5UZXh0dXJlQ29vcmRzAAEAAAAAAAAA","LIQAAAAAAAGAAAAAAIWP57ZDH37P7777777QCAAAAAAAAAAAOACQAAAAEAAAAAAAEERQAAA":"AwAAAExTS1P0AAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7CmZsYXQgb3V0IGhhbGY0IHZjb2xvcl9TdGFnZTA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmNvbG9yX1N0YWdlMCA9IGNvbG9yOwoJc2tfUG9zaXRpb24gPSBmbG9hdDQocG9zaXRpb24ueCAsIHBvc2l0aW9uLnksIDAsIDEpOwp9CgAAAAAAAAAAAAAAAGABAABmbGF0IGluIGhhbGY0IHZjb2xvcl9TdGFnZTA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CnZvaWQgbWFpbigpIAp7CgloYWxmNCBvdXRwdXRDb2xvcl9TdGFnZTA7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TdGFnZTA7Cgl7CgkJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgkJb3V0cHV0Q29sb3JfU3RhZ2UwID0gdmNvbG9yX1N0YWdlMDsKCQlvdXRwdXRDb3ZlcmFnZV9TdGFnZTAgPSBoYWxmNCgxKTsKCX0KCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TdGFnZTAgKiBvdXRwdXRDb3ZlcmFnZV9TdGFnZTA7Cgl9Cn0KAAAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24FAAAAY29sb3IAAAABAAAAAAAAAA==","LEMAAAAABCYIR6AYYAAAAAAAAAAAAAGACUAAAAEAAAAAAAEERQAA":"AwAAAExTS1NwAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiBmbG9hdDQgaW5RdWFkRWRnZTsKb3V0IGZsb2F0NCB2UXVhZEVkZ2VfU3RhZ2UwOwpvdXQgaGFsZjQgdmluQ29sb3JfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBRdWFkRWRnZQoJdlF1YWRFZGdlX1N0YWdlMCA9IGluUXVhZEVkZ2U7Cgl2aW5Db2xvcl9TdGFnZTAgPSBpbkNvbG9yOwoJZmxvYXQyIF90bXBfMF9pblBvc2l0aW9uID0gaW5Qb3NpdGlvbjsKCXNrX1Bvc2l0aW9uID0gZmxvYXQ0KF90bXBfMF9pblBvc2l0aW9uLnggLCBfdG1wXzBfaW5Qb3NpdGlvbi55LCAwLCAxKTsKfQoAAAAAAAAAAAAAAACXAwAAaW4gZmxvYXQ0IHZRdWFkRWRnZV9TdGFnZTA7CmluIGhhbGY0IHZpbkNvbG9yX1N0YWdlMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKdm9pZCBtYWluKCkgCnsKCWhhbGY0IG91dHB1dENvbG9yX1N0YWdlMDsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1N0YWdlMDsKCXsKCQkvLyBTdGFnZSAwLCBRdWFkRWRnZQoJCW91dHB1dENvbG9yX1N0YWdlMCA9IHZpbkNvbG9yX1N0YWdlMDsKCQloYWxmIGVkZ2VBbHBoYTsKCQloYWxmMiBkdXZkeCA9IGhhbGYyKGRGZHgodlF1YWRFZGdlX1N0YWdlMC54eSkpOwoJCWhhbGYyIGR1dmR5ID0gaGFsZjIoZEZkeSh2UXVhZEVkZ2VfU3RhZ2UwLnh5KSk7CgkJaWYgKHZRdWFkRWRnZV9TdGFnZTAueiA+IDAuMCAmJiB2UXVhZEVkZ2VfU3RhZ2UwLncgPiAwLjApIAoJCXsKCQkJZWRnZUFscGhhID0gaGFsZihtaW4obWluKHZRdWFkRWRnZV9TdGFnZTAueiwgdlF1YWRFZGdlX1N0YWdlMC53KSArIDAuNSwgMS4wKSk7CgkJfQoJCWVsc2UgCgkJewoJCQloYWxmMiBnRiA9IGhhbGYyKGhhbGYoMi4wKnZRdWFkRWRnZV9TdGFnZTAueCpkdXZkeC54IC0gZHV2ZHgueSksICAgICAgICAgICAgICAgICBoYWxmKDIuMCp2UXVhZEVkZ2VfU3RhZ2UwLngqZHV2ZHkueCAtIGR1dmR5LnkpKTsKCQkJZWRnZUFscGhhID0gaGFsZih2UXVhZEVkZ2VfU3RhZ2UwLngqdlF1YWRFZGdlX1N0YWdlMC54IC0gdlF1YWRFZGdlX1N0YWdlMC55KTsKCQkJZWRnZUFscGhhID0gc2F0dXJhdGUoMC41IC0gZWRnZUFscGhhIC8gbGVuZ3RoKGdGKSk7CgkJfQoJCW91dHB1dENvdmVyYWdlX1N0YWdlMCA9IGhhbGY0KGVkZ2VBbHBoYSk7Cgl9Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfU3RhZ2UwICogb3V0cHV0Q292ZXJhZ2VfU3RhZ2UwOwoJfQp9CgAAAAAAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAwAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAKAAAAaW5RdWFkRWRnZQAAAQAAAAAAAAA=","GJYQAAAAMAAGGADDACRQAAAAMAACHQDCABRQAI7CAMAAAAAAK4AAAAAAAIAAAAAQGIBAAAA":"AwAAAExTS1PfCwAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0NCByYWRpaV9zZWxlY3RvcjsKaW4gZmxvYXQ0IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHM7CmluIGZsb2F0NCBhYV9ibG9hdF9hbmRfY292ZXJhZ2U7CmluIGZsb2F0NCBza2V3OwppbiBmbG9hdDIgdHJhbnNsYXRlOwppbiBmbG9hdDQgcmFkaWlfeDsKaW4gZmxvYXQ0IHJhZGlpX3k7CmluIGhhbGY0IGNvbG9yOwpmbGF0IG91dCBoYWxmNCB2Y29sb3JfU3RhZ2UwOwpvdXQgZmxvYXQyIHZhcmNjb29yZF9TdGFnZTA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIEdyRmlsbFJSZWN0T3A6OlByb2Nlc3NvcgoJdmNvbG9yX1N0YWdlMCA9IGNvbG9yOwoJZmxvYXQgYWFfYmxvYXRfbXVsdGlwbGllciA9IDA7CglmbG9hdDIgY29ybmVyID0gY29ybmVyX2FuZF9yYWRpdXNfb3V0c2V0cy54eTsKCWZsb2F0MiByYWRpdXNfb3V0c2V0ID0gY29ybmVyX2FuZF9yYWRpdXNfb3V0c2V0cy56dzsKCWZsb2F0MiBhYV9ibG9hdF9kaXJlY3Rpb24gPSBhYV9ibG9hdF9hbmRfY292ZXJhZ2UueHk7CglmbG9hdCBpc19saW5lYXJfY292ZXJhZ2UgPSBhYV9ibG9hdF9hbmRfY292ZXJhZ2UudzsKCWZsb2F0MiBwaXhlbGxlbmd0aCA9IGludmVyc2VzcXJ0KGZsb2F0Mihkb3Qoc2tldy54eiwgc2tldy54eiksIGRvdChza2V3Lnl3LCBza2V3Lnl3KSkpOwoJZmxvYXQ0IG5vcm1hbGl6ZWRfYXhpc19kaXJzID0gc2tldyAqIHBpeGVsbGVuZ3RoLnh5eHk7CglmbG9hdDIgYXhpc3dpZHRocyA9IChhYnMobm9ybWFsaXplZF9heGlzX2RpcnMueHkpICsgYWJzKG5vcm1hbGl6ZWRfYXhpc19kaXJzLnp3KSk7CglmbG9hdDIgYWFfYmxvYXRyYWRpdXMgPSBheGlzd2lkdGhzICogcGl4ZWxsZW5ndGggKiAuNTsKCWZsb2F0NCByYWRpaV9hbmRfbmVpZ2hib3JzID0gcmFkaWlfc2VsZWN0b3IqIGZsb2F0NHg0KHJhZGlpX3gsIHJhZGlpX3ksIHJhZGlpX3gueXh3eiwgcmFkaWlfeS53enl4KTsKCWZsb2F0MiByYWRpaSA9IHJhZGlpX2FuZF9uZWlnaGJvcnMueHk7CglmbG9hdDIgbmVpZ2hib3JfcmFkaWkgPSByYWRpaV9hbmRfbmVpZ2hib3JzLnp3OwoJZmxvYXQgY292ZXJhZ2VfbXVsdGlwbGllciA9IDE7CglpZiAoYW55KGdyZWF0ZXJUaGFuKGFhX2Jsb2F0cmFkaXVzLCBmbG9hdDIoMSkpKSkgCgl7CgkJY29ybmVyID0gbWF4KGFicyhjb3JuZXIpLCBhYV9ibG9hdHJhZGl1cykgKiBzaWduKGNvcm5lcik7CgkJY292ZXJhZ2VfbXVsdGlwbGllciA9IDEgLyAobWF4KGFhX2Jsb2F0cmFkaXVzLngsIDEpICogbWF4KGFhX2Jsb2F0cmFkaXVzLnksIDEpKTsKCQlyYWRpaSA9IGZsb2F0MigwKTsKCX0KCWZsb2F0IGNvdmVyYWdlID0gYWFfYmxvYXRfYW5kX2NvdmVyYWdlLno7CglpZiAoYW55KGxlc3NUaGFuKHJhZGlpLCBhYV9ibG9hdHJhZGl1cyAqIDEuNSkpKSAKCXsKCQlyYWRpaSA9IGZsb2F0MigwKTsKCQlhYV9ibG9hdF9kaXJlY3Rpb24gPSBzaWduKGNvcm5lcik7CgkJaWYgKGNvdmVyYWdlID4gLjUpIAoJCXsKCQkJYWFfYmxvYXRfZGlyZWN0aW9uID0gLWFhX2Jsb2F0X2RpcmVjdGlvbjsKCQl9CgkJaXNfbGluZWFyX2NvdmVyYWdlID0gMTsKCX0KCWVsc2UgCgl7CgkJcmFkaWkgPSBjbGFtcChyYWRpaSwgcGl4ZWxsZW5ndGggKiAxLjUsIDIgLSBwaXhlbGxlbmd0aCAqIDEuNSk7CgkJbmVpZ2hib3JfcmFkaWkgPSBjbGFtcChuZWlnaGJvcl9yYWRpaSwgcGl4ZWxsZW5ndGggKiAxLjUsIDIgLSBwaXhlbGxlbmd0aCAqIDEuNSk7CgkJZmxvYXQyIHNwYWNpbmcgPSAyIC0gcmFkaWkgLSBuZWlnaGJvcl9yYWRpaTsKCQlmbG9hdDIgZXh0cmFfcGFkID0gbWF4KHBpeGVsbGVuZ3RoICogLjA2MjUgLSBzcGFjaW5nLCBmbG9hdDIoMCkpOwoJCXJhZGlpIC09IGV4dHJhX3BhZCAqIC41OwoJfQoJZmxvYXQyIGFhX291dHNldCA9IGFhX2Jsb2F0X2RpcmVjdGlvbiAqIGFhX2Jsb2F0cmFkaXVzICogYWFfYmxvYXRfbXVsdGlwbGllcjsKCWZsb2F0MiB2ZXJ0ZXhwb3MgPSBjb3JuZXIgKyByYWRpdXNfb3V0c2V0ICogcmFkaWkgKyBhYV9vdXRzZXQ7CglpZiAoY292ZXJhZ2UgPiAuNSkgCgl7CgkJaWYgKGFhX2Jsb2F0X2RpcmVjdGlvbi54ICE9IDAgJiYgdmVydGV4cG9zLnggKiBjb3JuZXIueCA8IDApIAoJCXsKCQkJZmxvYXQgYmFja3NldCA9IGFicyh2ZXJ0ZXhwb3MueCk7CgkJCXZlcnRleHBvcy54ID0gMDsKCQkJdmVydGV4cG9zLnkgKz0gYmFja3NldCAqIHNpZ24oY29ybmVyLnkpICogcGl4ZWxsZW5ndGgueS9waXhlbGxlbmd0aC54OwoJCQljb3ZlcmFnZSA9IChjb3ZlcmFnZSAtIC41KSAqIGFicyhjb3JuZXIueCkgLyAoYWJzKGNvcm5lci54KSArIGJhY2tzZXQpICsgLjU7CgkJfQoJCWlmIChhYV9ibG9hdF9kaXJlY3Rpb24ueSAhPSAwICYmIHZlcnRleHBvcy55ICogY29ybmVyLnkgPCAwKSAKCQl7CgkJCWZsb2F0IGJhY2tzZXQgPSBhYnModmVydGV4cG9zLnkpOwoJCQl2ZXJ0ZXhwb3MueSA9IDA7CgkJCXZlcnRleHBvcy54ICs9IGJhY2tzZXQgKiBzaWduKGNvcm5lci54KSAqIHBpeGVsbGVuZ3RoLngvcGl4ZWxsZW5ndGgueTsKCQkJY292ZXJhZ2UgPSAoY292ZXJhZ2UgLSAuNSkgKiBhYnMoY29ybmVyLnkpIC8gKGFicyhjb3JuZXIueSkgKyBiYWNrc2V0KSArIC41OwoJCX0KCX0KCWZsb2F0MngyIHNrZXdtYXRyaXggPSBmbG9hdDJ4Mihza2V3Lnh5LCBza2V3Lnp3KTsKCWZsb2F0MiBkZXZjb29yZCA9IHZlcnRleHBvcyAqIHNrZXdtYXRyaXggKyB0cmFuc2xhdGU7CglpZiAoMCAhPSBpc19saW5lYXJfY292ZXJhZ2UpIAoJewoJCXZhcmNjb29yZF9TdGFnZTAueHkgPSBmbG9hdDIoMCwgY292ZXJhZ2UgKiBjb3ZlcmFnZV9tdWx0aXBsaWVyKTsKCX0KCWVsc2UgCgl7CgkJZmxvYXQyIGFyY2Nvb3JkID0gMSAtIGFicyhyYWRpdXNfb3V0c2V0KSArIGFhX291dHNldC9yYWRpaSAqIGNvcm5lcjsKCQl2YXJjY29vcmRfU3RhZ2UwLnh5ID0gZmxvYXQyKGFyY2Nvb3JkLngrMSwgYXJjY29vcmQueSk7Cgl9Cglza19Qb3NpdGlvbiA9IGZsb2F0NChkZXZjb29yZC54ICwgZGV2Y29vcmQueSwgMCwgMSk7Cn0KAAAAAAAAAAAAAAAAAN0CAABmbGF0IGluIGhhbGY0IHZjb2xvcl9TdGFnZTA7CmluIGZsb2F0MiB2YXJjY29vcmRfU3RhZ2UwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJaGFsZjQgb3V0cHV0Q29sb3JfU3RhZ2UwOwoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfU3RhZ2UwOwoJewoJCS8vIFN0YWdlIDAsIEdyRmlsbFJSZWN0T3A6OlByb2Nlc3NvcgoJCW91dHB1dENvbG9yX1N0YWdlMCA9IHZjb2xvcl9TdGFnZTA7CgkJZmxvYXQgeF9wbHVzXzE9dmFyY2Nvb3JkX1N0YWdlMC54LCB5PXZhcmNjb29yZF9TdGFnZTAueTsKCQloYWxmIGNvdmVyYWdlOwoJCWlmICgwID09IHhfcGx1c18xKSAKCQl7CgkJCWNvdmVyYWdlID0gaGFsZih5KTsKCQl9CgkJZWxzZSAKCQl7CgkJCWZsb2F0IGZuID0geF9wbHVzXzEgKiAoeF9wbHVzXzEgLSAyKTsKCQkJZm4gPSBmbWEoeSx5LCBmbik7CgkJCWZsb2F0IGZud2lkdGggPSBmd2lkdGgoZm4pOwoJCQljb3ZlcmFnZSA9IC41IC0gaGFsZihmbi9mbndpZHRoKTsKCQkJY292ZXJhZ2UgPSBjbGFtcChjb3ZlcmFnZSwgMCwgMSk7CgkJfQoJCWNvdmVyYWdlID0gKGNvdmVyYWdlID49IC41KSA/IDEgOiAwOwoJCW91dHB1dENvdmVyYWdlX1N0YWdlMCA9IGhhbGY0KGNvdmVyYWdlKTsKCX0KCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TdGFnZTAgKiBvdXRwdXRDb3ZlcmFnZV9TdGFnZTA7Cgl9Cn0KAAAAAAAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAgAAAAOAAAAcmFkaWlfc2VsZWN0b3IAABkAAABjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzAAAAFQAAAGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZQAAAAQAAABza2V3CQAAAHRyYW5zbGF0ZQAAAAcAAAByYWRpaV94AAcAAAByYWRpaV95AAUAAABjb2xvcgAAAAEAAAAAAAAA","LJIAAAAAAAMAAAAAARMPZ72HPQCFR7H7777QGAAAAAAAAAAAQRDQAAAEAAAAAOARAAAAAAAAAAAAAAEAFMAAAAAAAEAAAAAIDEAQAAA":"AwAAAExTS1OpAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TdGFnZTFfYzA7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7CmluIGZsb2F0MiBsb2NhbENvb3JkOwpmbGF0IG91dCBoYWxmNCB2Y29sb3JfU3RhZ2UwOwpvdXQgZmxvYXQyIHZUcmFuc2Zvcm1lZENvb3Jkc18wX1N0YWdlMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgl2Y29sb3JfU3RhZ2UwID0gY29sb3I7Cglza19Qb3NpdGlvbiA9IGZsb2F0NChwb3NpdGlvbi54ICwgcG9zaXRpb24ueSwgMCwgMSk7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzBfU3RhZ2UwID0gKCgodW1hdHJpeF9TdGFnZTFfYzApKSAqIGxvY2FsQ29vcmQueHkxKS54eTsKCX0KfQoAAAAAAAAAAAAAAAAAAAAIAwAAdW5pZm9ybSBmbG9hdDN4MyB1bWF0cml4X1N0YWdlMV9jMDsKdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfU3RhZ2UxOwpmbGF0IGluIGhhbGY0IHZjb2xvcl9TdGFnZTA7CmluIGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMF9TdGFnZTA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IFRleHR1cmVFZmZlY3RfU3RhZ2UxX2MwX2MwKGhhbGY0IF9pbnB1dCkgCnsKCXJldHVybiBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfU3RhZ2UxLCB2VHJhbnNmb3JtZWRDb29yZHNfMF9TdGFnZTApLnJycnI7Cn0KaW5saW5lIGhhbGY0IE1hdHJpeEVmZmVjdF9TdGFnZTFfYzAoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIFRleHR1cmVFZmZlY3RfU3RhZ2UxX2MwX2MwKF9pbnB1dCk7Cn0Kdm9pZCBtYWluKCkgCnsKCWhhbGY0IG91dHB1dENvbG9yX1N0YWdlMDsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1N0YWdlMDsKCXsKCQkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCQlvdXRwdXRDb2xvcl9TdGFnZTAgPSB2Y29sb3JfU3RhZ2UwOwoJCW91dHB1dENvdmVyYWdlX1N0YWdlMCA9IGhhbGY0KDEpOwoJfQoJaGFsZjQgb3V0cHV0X1N0YWdlMTsKCW91dHB1dF9TdGFnZTEgPSBNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwKG91dHB1dENvdmVyYWdlX1N0YWdlMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfU3RhZ2UwICogb3V0cHV0X1N0YWdlMTsKCX0KfQoAAAAAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAwAAAAgAAABwb3NpdGlvbgUAAABjb2xvcgAAAAoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","LKQACAAAAAGAAAAAAIWAAKRCH37P6BZQ737QCAAAAAAAAAAAOACQAAAAEAAAAAAAEERQAAA":"AwAAAExTS1O9AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQgY292ZXJhZ2U7CmluIGhhbGY0IGNvbG9yOwppbiBmbG9hdDQgZ2VvbVN1YnNldDsKZmxhdCBvdXQgaGFsZjQgdmNvbG9yX1N0YWdlMDsKb3V0IGZsb2F0IHZjb3ZlcmFnZV9TdGFnZTA7CmZsYXQgb3V0IGZsb2F0NCB2Z2VvbVN1YnNldF9TdGFnZTA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQyIHBvc2l0aW9uID0gcG9zaXRpb24ueHk7Cgl2Y29sb3JfU3RhZ2UwID0gY29sb3I7Cgl2Y292ZXJhZ2VfU3RhZ2UwID0gY292ZXJhZ2U7Cgl2Z2VvbVN1YnNldF9TdGFnZTAgPSBnZW9tU3Vic2V0OwoJc2tfUG9zaXRpb24gPSBmbG9hdDQocG9zaXRpb24ueCAsIHBvc2l0aW9uLnksIDAsIDEpOwp9CgAAAAABAQAAAAAAAAEBAOUCAABmbGF0IGluIGhhbGY0IHZjb2xvcl9TdGFnZTA7CmluIGZsb2F0IHZjb3ZlcmFnZV9TdGFnZTA7CmZsYXQgaW4gZmxvYXQ0IHZnZW9tU3Vic2V0X1N0YWdlMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKdm9pZCBtYWluKCkgCnsKCWhhbGY0IG91dHB1dENvbG9yX1N0YWdlMDsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1N0YWdlMDsKCXsKCQkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCQlvdXRwdXRDb2xvcl9TdGFnZTAgPSB2Y29sb3JfU3RhZ2UwOwoJCWZsb2F0IGNvdmVyYWdlID0gdmNvdmVyYWdlX1N0YWdlMDsKCQlmbG9hdDQgZ2VvU3Vic2V0OwoJCWdlb1N1YnNldCA9IHZnZW9tU3Vic2V0X1N0YWdlMDsKCQloYWxmNCBkaXN0czQgPSBjbGFtcChoYWxmNCgxLCAxLCAtMSwgLTEpICogaGFsZjQoc2tfRnJhZ0Nvb3JkLnh5eHkgLSBnZW9TdWJzZXQpLCAwLCAxKTsKCQloYWxmMiBkaXN0czIgPSBkaXN0czQueHkgKyBkaXN0czQuencgLSAxOwoJCWhhbGYgc3Vic2V0Q292ZXJhZ2UgPSBkaXN0czIueCAqIGRpc3RzMi55OwoJCWNvdmVyYWdlID0gbWluKGNvdmVyYWdlLCBzdWJzZXRDb3ZlcmFnZSk7CgkJb3V0cHV0Q292ZXJhZ2VfU3RhZ2UwID0gaGFsZjQoaGFsZihjb3ZlcmFnZSkpOwoJfQoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1N0YWdlMCAqIG91dHB1dENvdmVyYWdlX1N0YWdlMDsKCX0KfQoAAAAAAQEAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAABAAAAAgAAABwb3NpdGlvbggAAABjb3ZlcmFnZQUAAABjb2xvcgAAAAoAAABnZW9tU3Vic2V0AAABAAAAAAAAAA==","LIQAAAAAAAGAAAAAAIWP57ZDH37P7777777QCAAAAAAAAAAAMIQHSAAAAB5QAAAAAEAAAAAGWIDQAAAQAAAAAADQAUAAAABAAAAAAABBEMAAA":"AwAAAExTS1P0AAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7CmZsYXQgb3V0IGhhbGY0IHZjb2xvcl9TdGFnZTA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmNvbG9yX1N0YWdlMCA9IGNvbG9yOwoJc2tfUG9zaXRpb24gPSBmbG9hdDQocG9zaXRpb24ueCAsIHBvc2l0aW9uLnksIDAsIDEpOwp9CgABAQAAAAAAAAEBAPQEAAB1bmlmb3JtIGZsb2F0NCB1aW5uZXJSZWN0X1N0YWdlMV9jMDsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfU3RhZ2UxX2MwOwp1bmlmb3JtIGZsb2F0NCB1aW5uZXJSZWN0X1N0YWdlMV9jMF9jMDsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfU3RhZ2UxX2MwX2MwOwpmbGF0IGluIGhhbGY0IHZjb2xvcl9TdGFnZTA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IENpcmN1bGFyUlJlY3RfU3RhZ2UxX2MwX2MwKGhhbGY0IF9pbnB1dCkgCnsKCWZsb2F0MiBkeHkwID0gdWlubmVyUmVjdF9TdGFnZTFfYzBfYzAuTFQgLSBza19GcmFnQ29vcmQueHk7CglmbG9hdDIgZHh5MSA9IHNrX0ZyYWdDb29yZC54eSAtIHVpbm5lclJlY3RfU3RhZ2UxX2MwX2MwLlJCOwoJZmxvYXQyIGR4eSA9IG1heChtYXgoZHh5MCwgZHh5MSksIDAuMCk7CgloYWxmIGFscGhhID0gaGFsZihzYXR1cmF0ZSh1cmFkaXVzUGx1c0hhbGZfU3RhZ2UxX2MwX2MwLnggLSBsZW5ndGgoZHh5KSkpOwoJYWxwaGEgPSAxLjAgLSBhbHBoYTsKCXJldHVybiBfaW5wdXQgKiBhbHBoYTsKfQppbmxpbmUgaGFsZjQgQ2lyY3VsYXJSUmVjdF9TdGFnZTFfYzAoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGR4eTAgPSB1aW5uZXJSZWN0X1N0YWdlMV9jMC5MVCAtIHNrX0ZyYWdDb29yZC54eTsKCWZsb2F0MiBkeHkxID0gc2tfRnJhZ0Nvb3JkLnh5IC0gdWlubmVyUmVjdF9TdGFnZTFfYzAuUkI7CglmbG9hdDIgZHh5ID0gbWF4KG1heChkeHkwLCBkeHkxKSwgMC4wKTsKCWhhbGYgYWxwaGEgPSBoYWxmKHNhdHVyYXRlKHVyYWRpdXNQbHVzSGFsZl9TdGFnZTFfYzAueCAtIGxlbmd0aChkeHkpKSk7CglyZXR1cm4gQ2lyY3VsYXJSUmVjdF9TdGFnZTFfYzBfYzAoX2lucHV0KSAqIGFscGhhOwp9CnZvaWQgbWFpbigpIAp7CgloYWxmNCBvdXRwdXRDb2xvcl9TdGFnZTA7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TdGFnZTA7Cgl7CgkJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgkJb3V0cHV0Q29sb3JfU3RhZ2UwID0gdmNvbG9yX1N0YWdlMDsKCQlvdXRwdXRDb3ZlcmFnZV9TdGFnZTAgPSBoYWxmNCgxKTsKCX0KCWhhbGY0IG91dHB1dF9TdGFnZTE7CglvdXRwdXRfU3RhZ2UxID0gQ2lyY3VsYXJSUmVjdF9TdGFnZTFfYzAob3V0cHV0Q292ZXJhZ2VfU3RhZ2UwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TdGFnZTAgKiBvdXRwdXRfU3RhZ2UxOwoJfQp9CgABAQABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAACAAAACAAAAHBvc2l0aW9uBQAAAGNvbG9yAAAAAQAAAAAAAAA=","LIIAAAAAAAGAAAAAAIWP577774BSZ7X7777QCAAAAAAAAAAAHIRQYAAAAAAQAAQAAAAHQJQAIAAAAAEAKMAYIDAAAQAAAAAAAC4AEAAAAAIAAAAAQCIBCAAAAA":"AwAAAExTS1MFAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdkxvY2FsQ29vcmRfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCXZMb2NhbENvb3JkX1N0YWdlMCA9IGxvY2FsQ29vcmQ7Cglza19Qb3NpdGlvbiA9IGZsb2F0NChwb3NpdGlvbi54ICwgcG9zaXRpb24ueSwgMCwgMSk7Cn0KAAAAAAAAAAAAAAAAAAAAwBIAAHVuaWZvcm0gaGFsZjIgdV8wX0luY3JlbWVudF9TdGFnZTFfYzA7CnVuaWZvcm0gaGFsZjQgdV8xX0tlcm5lbF9TdGFnZTFfYzBbN107CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TdGFnZTFfYzBfYzA7CnVuaWZvcm0gZmxvYXQ0IHVjbGFtcF9TdGFnZTFfYzBfYzBfYzA7CnVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1N0YWdlMTsKaW4gZmxvYXQyIHZMb2NhbENvb3JkX1N0YWdlMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgVGV4dHVyZUVmZmVjdF9TdGFnZTFfYzBfYzBfYzAoaGFsZjQgX2lucHV0LCBmbG9hdDIgX2Nvb3JkcykgCnsKCWZsb2F0MiBpbkNvb3JkID0gX2Nvb3JkczsKCWZsb2F0MiBzdWJzZXRDb29yZDsKCXN1YnNldENvb3JkLnggPSBpbkNvb3JkLng7CglzdWJzZXRDb29yZC55ID0gaW5Db29yZC55OwoJZmxvYXQyIGNsYW1wZWRDb29yZDsKCWNsYW1wZWRDb29yZC54ID0gY2xhbXAoc3Vic2V0Q29vcmQueCwgdWNsYW1wX1N0YWdlMV9jMF9jMF9jMC54LCB1Y2xhbXBfU3RhZ2UxX2MwX2MwX2MwLnopOwoJY2xhbXBlZENvb3JkLnkgPSBzdWJzZXRDb29yZC55OwoJaGFsZjQgdGV4dHVyZUNvbG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1N0YWdlMSwgY2xhbXBlZENvb3JkKTsKCXJldHVybiB0ZXh0dXJlQ29sb3I7Cn0KaGFsZjQgTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChoYWxmNCBfaW5wdXQsIGZsb2F0MiBfY29vcmRzKSAKewoJcmV0dXJuIFRleHR1cmVFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKF9pbnB1dCwgKCh1bWF0cml4X1N0YWdlMV9jMF9jMCkgKiBfY29vcmRzLnh5MSkueHkpOwp9CmlubGluZSBoYWxmNCBHYXVzc2lhbkNvbnZvbHV0aW9uX1N0YWdlMV9jMChoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBfMl9jb2xvciA9IGhhbGY0KDAuMCk7CglmbG9hdDIgXzNfY29vcmQgPSAodkxvY2FsQ29vcmRfU3RhZ2UwIC0gZmxvYXQyKCgxMi4wICogdV8wX0luY3JlbWVudF9TdGFnZTFfYzApKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMFswXS54KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMFswXS55KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMFswXS56KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMFswXS53KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMFsxXS54KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMFsxXS55KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMFsxXS56KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMFsxXS53KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMFsyXS54KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMFsyXS55KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMFsyXS56KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMFsyXS53KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMFszXS54KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMFszXS55KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMFszXS56KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMFszXS53KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMFs0XS54KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMFs0XS55KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMFs0XS56KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMFs0XS53KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMFs1XS54KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMFs1XS55KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMFs1XS56KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMFs1XS53KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMFs2XS54KSk7CglyZXR1cm4gXzJfY29sb3I7Cn0Kdm9pZCBtYWluKCkgCnsKCWhhbGY0IG91dHB1dENvbG9yX1N0YWdlMDsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1N0YWdlMDsKCXsKCQkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCQlvdXRwdXRDb2xvcl9TdGFnZTAgPSBoYWxmNCgxKTsKCQlvdXRwdXRDb3ZlcmFnZV9TdGFnZTAgPSBoYWxmNCgxKTsKCX0KCWhhbGY0IG91dHB1dF9TdGFnZTE7CglvdXRwdXRfU3RhZ2UxID0gR2F1c3NpYW5Db252b2x1dGlvbl9TdGFnZTFfYzAob3V0cHV0Q29sb3JfU3RhZ2UwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRfU3RhZ2UxICogb3V0cHV0Q292ZXJhZ2VfU3RhZ2UwOwoJfQp9CgAAAAABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAACAAAACAAAAHBvc2l0aW9uCgAAAGxvY2FsQ29vcmQAAAEAAAAAAAAA","LIQACAAAAAGAAAAAAIWP57ZDH37P7777777QCAAAAAAAAAAAOACQAAAAEAAAAAAAEERQAAA":"AwAAAExTS1PvAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7Cm91dCBoYWxmNCB2Y29sb3JfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCXZjb2xvcl9TdGFnZTAgPSBjb2xvcjsKCXNrX1Bvc2l0aW9uID0gZmxvYXQ0KHBvc2l0aW9uLnggLCBwb3NpdGlvbi55LCAwLCAxKTsKfQoAAAAAAAAAAAAAAAAAWwEAAGluIGhhbGY0IHZjb2xvcl9TdGFnZTA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CnZvaWQgbWFpbigpIAp7CgloYWxmNCBvdXRwdXRDb2xvcl9TdGFnZTA7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TdGFnZTA7Cgl7CgkJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgkJb3V0cHV0Q29sb3JfU3RhZ2UwID0gdmNvbG9yX1N0YWdlMDsKCQlvdXRwdXRDb3ZlcmFnZV9TdGFnZTAgPSBoYWxmNCgxKTsKCX0KCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TdGFnZTAgKiBvdXRwdXRDb3ZlcmFnZV9TdGFnZTA7Cgl9Cn0KAAAAAAABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAACAAAACAAAAHBvc2l0aW9uBQAAAGNvbG9yAAAAAQAAAAAAAAA=","GJQQAAAAMAAGGADDACRQAAAAMAACHQDCABRQAI7CAMAAAABAA2JAOAAACAAAAAAAOACQAAAAEAAAAAAAEERQAAAAAA":"AwAAAExTS1PfCwAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0NCByYWRpaV9zZWxlY3RvcjsKaW4gZmxvYXQ0IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHM7CmluIGZsb2F0NCBhYV9ibG9hdF9hbmRfY292ZXJhZ2U7CmluIGZsb2F0NCBza2V3OwppbiBmbG9hdDIgdHJhbnNsYXRlOwppbiBmbG9hdDQgcmFkaWlfeDsKaW4gZmxvYXQ0IHJhZGlpX3k7CmluIGhhbGY0IGNvbG9yOwpmbGF0IG91dCBoYWxmNCB2Y29sb3JfU3RhZ2UwOwpvdXQgZmxvYXQyIHZhcmNjb29yZF9TdGFnZTA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIEdyRmlsbFJSZWN0T3A6OlByb2Nlc3NvcgoJdmNvbG9yX1N0YWdlMCA9IGNvbG9yOwoJZmxvYXQgYWFfYmxvYXRfbXVsdGlwbGllciA9IDE7CglmbG9hdDIgY29ybmVyID0gY29ybmVyX2FuZF9yYWRpdXNfb3V0c2V0cy54eTsKCWZsb2F0MiByYWRpdXNfb3V0c2V0ID0gY29ybmVyX2FuZF9yYWRpdXNfb3V0c2V0cy56dzsKCWZsb2F0MiBhYV9ibG9hdF9kaXJlY3Rpb24gPSBhYV9ibG9hdF9hbmRfY292ZXJhZ2UueHk7CglmbG9hdCBpc19saW5lYXJfY292ZXJhZ2UgPSBhYV9ibG9hdF9hbmRfY292ZXJhZ2UudzsKCWZsb2F0MiBwaXhlbGxlbmd0aCA9IGludmVyc2VzcXJ0KGZsb2F0Mihkb3Qoc2tldy54eiwgc2tldy54eiksIGRvdChza2V3Lnl3LCBza2V3Lnl3KSkpOwoJZmxvYXQ0IG5vcm1hbGl6ZWRfYXhpc19kaXJzID0gc2tldyAqIHBpeGVsbGVuZ3RoLnh5eHk7CglmbG9hdDIgYXhpc3dpZHRocyA9IChhYnMobm9ybWFsaXplZF9heGlzX2RpcnMueHkpICsgYWJzKG5vcm1hbGl6ZWRfYXhpc19kaXJzLnp3KSk7CglmbG9hdDIgYWFfYmxvYXRyYWRpdXMgPSBheGlzd2lkdGhzICogcGl4ZWxsZW5ndGggKiAuNTsKCWZsb2F0NCByYWRpaV9hbmRfbmVpZ2hib3JzID0gcmFkaWlfc2VsZWN0b3IqIGZsb2F0NHg0KHJhZGlpX3gsIHJhZGlpX3ksIHJhZGlpX3gueXh3eiwgcmFkaWlfeS53enl4KTsKCWZsb2F0MiByYWRpaSA9IHJhZGlpX2FuZF9uZWlnaGJvcnMueHk7CglmbG9hdDIgbmVpZ2hib3JfcmFkaWkgPSByYWRpaV9hbmRfbmVpZ2hib3JzLnp3OwoJZmxvYXQgY292ZXJhZ2VfbXVsdGlwbGllciA9IDE7CglpZiAoYW55KGdyZWF0ZXJUaGFuKGFhX2Jsb2F0cmFkaXVzLCBmbG9hdDIoMSkpKSkgCgl7CgkJY29ybmVyID0gbWF4KGFicyhjb3JuZXIpLCBhYV9ibG9hdHJhZGl1cykgKiBzaWduKGNvcm5lcik7CgkJY292ZXJhZ2VfbXVsdGlwbGllciA9IDEgLyAobWF4KGFhX2Jsb2F0cmFkaXVzLngsIDEpICogbWF4KGFhX2Jsb2F0cmFkaXVzLnksIDEpKTsKCQlyYWRpaSA9IGZsb2F0MigwKTsKCX0KCWZsb2F0IGNvdmVyYWdlID0gYWFfYmxvYXRfYW5kX2NvdmVyYWdlLno7CglpZiAoYW55KGxlc3NUaGFuKHJhZGlpLCBhYV9ibG9hdHJhZGl1cyAqIDEuNSkpKSAKCXsKCQlyYWRpaSA9IGZsb2F0MigwKTsKCQlhYV9ibG9hdF9kaXJlY3Rpb24gPSBzaWduKGNvcm5lcik7CgkJaWYgKGNvdmVyYWdlID4gLjUpIAoJCXsKCQkJYWFfYmxvYXRfZGlyZWN0aW9uID0gLWFhX2Jsb2F0X2RpcmVjdGlvbjsKCQl9CgkJaXNfbGluZWFyX2NvdmVyYWdlID0gMTsKCX0KCWVsc2UgCgl7CgkJcmFkaWkgPSBjbGFtcChyYWRpaSwgcGl4ZWxsZW5ndGggKiAxLjUsIDIgLSBwaXhlbGxlbmd0aCAqIDEuNSk7CgkJbmVpZ2hib3JfcmFkaWkgPSBjbGFtcChuZWlnaGJvcl9yYWRpaSwgcGl4ZWxsZW5ndGggKiAxLjUsIDIgLSBwaXhlbGxlbmd0aCAqIDEuNSk7CgkJZmxvYXQyIHNwYWNpbmcgPSAyIC0gcmFkaWkgLSBuZWlnaGJvcl9yYWRpaTsKCQlmbG9hdDIgZXh0cmFfcGFkID0gbWF4KHBpeGVsbGVuZ3RoICogLjA2MjUgLSBzcGFjaW5nLCBmbG9hdDIoMCkpOwoJCXJhZGlpIC09IGV4dHJhX3BhZCAqIC41OwoJfQoJZmxvYXQyIGFhX291dHNldCA9IGFhX2Jsb2F0X2RpcmVjdGlvbiAqIGFhX2Jsb2F0cmFkaXVzICogYWFfYmxvYXRfbXVsdGlwbGllcjsKCWZsb2F0MiB2ZXJ0ZXhwb3MgPSBjb3JuZXIgKyByYWRpdXNfb3V0c2V0ICogcmFkaWkgKyBhYV9vdXRzZXQ7CglpZiAoY292ZXJhZ2UgPiAuNSkgCgl7CgkJaWYgKGFhX2Jsb2F0X2RpcmVjdGlvbi54ICE9IDAgJiYgdmVydGV4cG9zLnggKiBjb3JuZXIueCA8IDApIAoJCXsKCQkJZmxvYXQgYmFja3NldCA9IGFicyh2ZXJ0ZXhwb3MueCk7CgkJCXZlcnRleHBvcy54ID0gMDsKCQkJdmVydGV4cG9zLnkgKz0gYmFja3NldCAqIHNpZ24oY29ybmVyLnkpICogcGl4ZWxsZW5ndGgueS9waXhlbGxlbmd0aC54OwoJCQljb3ZlcmFnZSA9IChjb3ZlcmFnZSAtIC41KSAqIGFicyhjb3JuZXIueCkgLyAoYWJzKGNvcm5lci54KSArIGJhY2tzZXQpICsgLjU7CgkJfQoJCWlmIChhYV9ibG9hdF9kaXJlY3Rpb24ueSAhPSAwICYmIHZlcnRleHBvcy55ICogY29ybmVyLnkgPCAwKSAKCQl7CgkJCWZsb2F0IGJhY2tzZXQgPSBhYnModmVydGV4cG9zLnkpOwoJCQl2ZXJ0ZXhwb3MueSA9IDA7CgkJCXZlcnRleHBvcy54ICs9IGJhY2tzZXQgKiBzaWduKGNvcm5lci54KSAqIHBpeGVsbGVuZ3RoLngvcGl4ZWxsZW5ndGgueTsKCQkJY292ZXJhZ2UgPSAoY292ZXJhZ2UgLSAuNSkgKiBhYnMoY29ybmVyLnkpIC8gKGFicyhjb3JuZXIueSkgKyBiYWNrc2V0KSArIC41OwoJCX0KCX0KCWZsb2F0MngyIHNrZXdtYXRyaXggPSBmbG9hdDJ4Mihza2V3Lnh5LCBza2V3Lnp3KTsKCWZsb2F0MiBkZXZjb29yZCA9IHZlcnRleHBvcyAqIHNrZXdtYXRyaXggKyB0cmFuc2xhdGU7CglpZiAoMCAhPSBpc19saW5lYXJfY292ZXJhZ2UpIAoJewoJCXZhcmNjb29yZF9TdGFnZTAueHkgPSBmbG9hdDIoMCwgY292ZXJhZ2UgKiBjb3ZlcmFnZV9tdWx0aXBsaWVyKTsKCX0KCWVsc2UgCgl7CgkJZmxvYXQyIGFyY2Nvb3JkID0gMSAtIGFicyhyYWRpdXNfb3V0c2V0KSArIGFhX291dHNldC9yYWRpaSAqIGNvcm5lcjsKCQl2YXJjY29vcmRfU3RhZ2UwLnh5ID0gZmxvYXQyKGFyY2Nvb3JkLngrMSwgYXJjY29vcmQueSk7Cgl9Cglza19Qb3NpdGlvbiA9IGZsb2F0NChkZXZjb29yZC54ICwgZGV2Y29vcmQueSwgMCwgMSk7Cn0KAAABAQAAAAAAAAEBAIkEAAB1bmlmb3JtIGZsb2F0NCB1aW5uZXJSZWN0X1N0YWdlMV9jMDsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfU3RhZ2UxX2MwOwpmbGF0IGluIGhhbGY0IHZjb2xvcl9TdGFnZTA7CmluIGZsb2F0MiB2YXJjY29vcmRfU3RhZ2UwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwppbmxpbmUgaGFsZjQgQ2lyY3VsYXJSUmVjdF9TdGFnZTFfYzAoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGR4eTAgPSB1aW5uZXJSZWN0X1N0YWdlMV9jMC5MVCAtIHNrX0ZyYWdDb29yZC54eTsKCWZsb2F0MiBkeHkxID0gc2tfRnJhZ0Nvb3JkLnh5IC0gdWlubmVyUmVjdF9TdGFnZTFfYzAuUkI7CglmbG9hdDIgZHh5ID0gbWF4KG1heChkeHkwLCBkeHkxKSwgMC4wKTsKCWhhbGYgYWxwaGEgPSBoYWxmKHNhdHVyYXRlKHVyYWRpdXNQbHVzSGFsZl9TdGFnZTFfYzAueCAtIGxlbmd0aChkeHkpKSk7CglyZXR1cm4gX2lucHV0ICogYWxwaGE7Cn0Kdm9pZCBtYWluKCkgCnsKCWhhbGY0IG91dHB1dENvbG9yX1N0YWdlMDsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1N0YWdlMDsKCXsKCQkvLyBTdGFnZSAwLCBHckZpbGxSUmVjdE9wOjpQcm9jZXNzb3IKCQlvdXRwdXRDb2xvcl9TdGFnZTAgPSB2Y29sb3JfU3RhZ2UwOwoJCWZsb2F0IHhfcGx1c18xPXZhcmNjb29yZF9TdGFnZTAueCwgeT12YXJjY29vcmRfU3RhZ2UwLnk7CgkJaGFsZiBjb3ZlcmFnZTsKCQlpZiAoMCA9PSB4X3BsdXNfMSkgCgkJewoJCQljb3ZlcmFnZSA9IGhhbGYoeSk7CgkJfQoJCWVsc2UgCgkJewoJCQlmbG9hdCBmbiA9IHhfcGx1c18xICogKHhfcGx1c18xIC0gMik7CgkJCWZuID0gZm1hKHkseSwgZm4pOwoJCQlmbG9hdCBmbndpZHRoID0gZndpZHRoKGZuKTsKCQkJY292ZXJhZ2UgPSAuNSAtIGhhbGYoZm4vZm53aWR0aCk7CgkJCWNvdmVyYWdlID0gY2xhbXAoY292ZXJhZ2UsIDAsIDEpOwoJCX0KCQlvdXRwdXRDb3ZlcmFnZV9TdGFnZTAgPSBoYWxmNChjb3ZlcmFnZSk7Cgl9CgloYWxmNCBvdXRwdXRfU3RhZ2UxOwoJb3V0cHV0X1N0YWdlMSA9IENpcmN1bGFyUlJlY3RfU3RhZ2UxX2MwKG91dHB1dENvdmVyYWdlX1N0YWdlMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfU3RhZ2UwICogb3V0cHV0X1N0YWdlMTsKCX0KfQoAAAAAAQEAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAACAAAAA4AAAByYWRpaV9zZWxlY3RvcgAAGQAAAGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHMAAAAVAAAAYWFfYmxvYXRfYW5kX2NvdmVyYWdlAAAABAAAAHNrZXcJAAAAdHJhbnNsYXRlAAAABwAAAHJhZGlpX3gABwAAAHJhZGlpX3kABQAAAGNvbG9yAAAAAQAAAAAAAAA=","AWQAGAAAQAAIXCEPAGGP777777777737AAAAAAAAAAAIAGCIDYAAAQAAAAAABQAVAAAABAAAAAAABBEMAAAA":"AwAAAExTS1MJAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TdGFnZTA7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiBmbG9hdDQgaW5DaXJjbGVFZGdlOwpvdXQgZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfU3RhZ2UwOwpvdXQgaGFsZjQgdmluQ29sb3JfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TdGFnZTAgPSBpbkNpcmNsZUVkZ2U7Cgl2aW5Db2xvcl9TdGFnZTAgPSBpbkNvbG9yOwoJZmxvYXQyIF90bXBfMF9pblBvc2l0aW9uID0gaW5Qb3NpdGlvbjsKCWZsb2F0MiBfdG1wXzFfaW5Qb3NpdGlvbiA9IHVsb2NhbE1hdHJpeF9TdGFnZTAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1N0YWdlMC55dzsKCXNrX1Bvc2l0aW9uID0gZmxvYXQ0KF90bXBfMF9pblBvc2l0aW9uLnggLCBfdG1wXzBfaW5Qb3NpdGlvbi55LCAwLCAxKTsKfQoAAAAAAQEAAAAAAAABAQAfBAAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TdGFnZTFfYzA7CnVuaWZvcm0gaGFsZjIgdXJhZGl1c1BsdXNIYWxmX1N0YWdlMV9jMDsKaW4gZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfU3RhZ2UwOwppbiBoYWxmNCB2aW5Db2xvcl9TdGFnZTA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmlubGluZSBoYWxmNCBDaXJjdWxhclJSZWN0X1N0YWdlMV9jMChoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgZHh5MCA9IHVpbm5lclJlY3RfU3RhZ2UxX2MwLkxUIC0gc2tfRnJhZ0Nvb3JkLnh5OwoJZmxvYXQyIGR4eTEgPSBza19GcmFnQ29vcmQueHkgLSB1aW5uZXJSZWN0X1N0YWdlMV9jMC5SQjsKCWZsb2F0MiBkeHkgPSBtYXgobWF4KGR4eTAsIGR4eTEpLCAwLjApOwoJaGFsZiBhbHBoYSA9IGhhbGYoc2F0dXJhdGUodXJhZGl1c1BsdXNIYWxmX1N0YWdlMV9jMC54IC0gbGVuZ3RoKGR4eSkpKTsKCXJldHVybiBfaW5wdXQgKiBhbHBoYTsKfQp2b2lkIG1haW4oKSAKewoJaGFsZjQgb3V0cHV0Q29sb3JfU3RhZ2UwOwoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfU3RhZ2UwOwoJewoJCS8vIFN0YWdlIDAsIENpcmNsZUdlb21ldHJ5UHJvY2Vzc29yCgkJZmxvYXQ0IGNpcmNsZUVkZ2U7CgkJY2lyY2xlRWRnZSA9IHZpbkNpcmNsZUVkZ2VfU3RhZ2UwOwoJCW91dHB1dENvbG9yX1N0YWdlMCA9IHZpbkNvbG9yX1N0YWdlMDsKCQlmbG9hdCBkID0gbGVuZ3RoKGNpcmNsZUVkZ2UueHkpOwoJCWhhbGYgZGlzdGFuY2VUb091dGVyRWRnZSA9IGhhbGYoY2lyY2xlRWRnZS56ICogKDEuMCAtIGQpKTsKCQloYWxmIGVkZ2VBbHBoYSA9IHNhdHVyYXRlKGRpc3RhbmNlVG9PdXRlckVkZ2UpOwoJCW91dHB1dENvdmVyYWdlX1N0YWdlMCA9IGhhbGY0KGVkZ2VBbHBoYSk7Cgl9CgloYWxmNCBvdXRwdXRfU3RhZ2UxOwoJb3V0cHV0X1N0YWdlMSA9IENpcmN1bGFyUlJlY3RfU3RhZ2UxX2MwKG91dHB1dENvdmVyYWdlX1N0YWdlMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfU3RhZ2UwICogb3V0cHV0X1N0YWdlMTsKCX0KfQoAAAEBAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IADAAAAGluQ2lyY2xlRWRnZQEAAAAAAAAA","LIIAAAAAAAGAAAAAAIWP577774BSZ7X7777QCAAAAAAAAAAAZIRQCAICAAAABHAIEBSAAIBAAAAAAAGACUAAAAEAAAAAAAEERQAAAAA":"AwAAAExTS1NjAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TdGFnZTFfYzA7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzBfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCXNrX1Bvc2l0aW9uID0gZmxvYXQ0KHBvc2l0aW9uLnggLCBwb3NpdGlvbi55LCAwLCAxKTsKCXsKCQl2VHJhbnNmb3JtZWRDb29yZHNfMF9TdGFnZTAgPSAoKCh1bWF0cml4X1N0YWdlMV9jMCkpICogbG9jYWxDb29yZC54eTEpLnh5OwoJfQp9CgAAAAAAAAAAAAAAAAAABAAAdW5pZm9ybSBmbG9hdDN4MyB1bWF0cml4X1N0YWdlMV9jMDsKdW5pZm9ybSBmbG9hdDQgdWNsYW1wX1N0YWdlMV9jMF9jMDsKdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfU3RhZ2UxOwppbiBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzBfU3RhZ2UwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBUZXh0dXJlRWZmZWN0X1N0YWdlMV9jMF9jMChoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgaW5Db29yZCA9IHZUcmFuc2Zvcm1lZENvb3Jkc18wX1N0YWdlMDsKCWZsb2F0MiBzdWJzZXRDb29yZDsKCXN1YnNldENvb3JkLnggPSBpbkNvb3JkLng7CglzdWJzZXRDb29yZC55ID0gaW5Db29yZC55OwoJZmxvYXQyIGNsYW1wZWRDb29yZDsKCWNsYW1wZWRDb29yZCA9IGNsYW1wKHN1YnNldENvb3JkLCB1Y2xhbXBfU3RhZ2UxX2MwX2MwLnh5LCB1Y2xhbXBfU3RhZ2UxX2MwX2MwLnp3KTsKCWhhbGY0IHRleHR1cmVDb2xvciA9IHNhbXBsZSh1VGV4dHVyZVNhbXBsZXJfMF9TdGFnZTEsIGNsYW1wZWRDb29yZCk7CglyZXR1cm4gdGV4dHVyZUNvbG9yOwp9CmlubGluZSBoYWxmNCBNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwKGhhbGY0IF9pbnB1dCkgCnsKCXJldHVybiBUZXh0dXJlRWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQpOwp9CnZvaWQgbWFpbigpIAp7CgloYWxmNCBvdXRwdXRDb2xvcl9TdGFnZTA7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TdGFnZTA7Cgl7CgkJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgkJb3V0cHV0Q29sb3JfU3RhZ2UwID0gaGFsZjQoMSk7CgkJb3V0cHV0Q292ZXJhZ2VfU3RhZ2UwID0gaGFsZjQoMSk7Cgl9CgloYWxmNCBvdXRwdXRfU3RhZ2UxOwoJb3V0cHV0X1N0YWdlMSA9IE1hdHJpeEVmZmVjdF9TdGFnZTFfYzAob3V0cHV0Q29sb3JfU3RhZ2UwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRfU3RhZ2UxICogb3V0cHV0Q292ZXJhZ2VfU3RhZ2UwOwoJfQp9CgAAAAABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAACAAAACAAAAHBvc2l0aW9uCgAAAGxvY2FsQ29vcmQAAAEAAAAAAAAA","AWAQGAAAQAAIXCEPAGGP777777777737AAAAAAAAAAAAAXABAAAAACAAAAAEBSAI":"AwAAAExTS1OzAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiBmbG9hdDQgaW5DaXJjbGVFZGdlOwpvdXQgZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfU3RhZ2UwOwpvdXQgaGFsZjQgdmluQ29sb3JfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TdGFnZTAgPSBpbkNpcmNsZUVkZ2U7Cgl2aW5Db2xvcl9TdGFnZTAgPSBpbkNvbG9yOwoJZmxvYXQyIF90bXBfMF9pblBvc2l0aW9uID0gaW5Qb3NpdGlvbjsKCWZsb2F0MiBfdG1wXzFfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247Cglza19Qb3NpdGlvbiA9IGZsb2F0NChfdG1wXzBfaW5Qb3NpdGlvbi54ICwgX3RtcF8wX2luUG9zaXRpb24ueSwgMCwgMSk7Cn0KAAAAAAAAAAAAAAAAAOACAABpbiBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TdGFnZTA7CmluIGhhbGY0IHZpbkNvbG9yX1N0YWdlMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKdm9pZCBtYWluKCkgCnsKCWhhbGY0IG91dHB1dENvbG9yX1N0YWdlMDsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1N0YWdlMDsKCXsKCQkvLyBTdGFnZSAwLCBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJCWZsb2F0NCBjaXJjbGVFZGdlOwoJCWNpcmNsZUVkZ2UgPSB2aW5DaXJjbGVFZGdlX1N0YWdlMDsKCQlvdXRwdXRDb2xvcl9TdGFnZTAgPSB2aW5Db2xvcl9TdGFnZTA7CgkJZmxvYXQgZCA9IGxlbmd0aChjaXJjbGVFZGdlLnh5KTsKCQloYWxmIGRpc3RhbmNlVG9PdXRlckVkZ2UgPSBoYWxmKGNpcmNsZUVkZ2UueiAqICgxLjAgLSBkKSk7CgkJaGFsZiBlZGdlQWxwaGEgPSBzYXR1cmF0ZShkaXN0YW5jZVRvT3V0ZXJFZGdlKTsKCQloYWxmIGRpc3RhbmNlVG9Jbm5lckVkZ2UgPSBoYWxmKGNpcmNsZUVkZ2UueiAqIChkIC0gY2lyY2xlRWRnZS53KSk7CgkJaGFsZiBpbm5lckFscGhhID0gc2F0dXJhdGUoZGlzdGFuY2VUb0lubmVyRWRnZSk7CgkJZWRnZUFscGhhICo9IGlubmVyQWxwaGE7CgkJb3V0cHV0Q292ZXJhZ2VfU3RhZ2UwID0gaGFsZjQoZWRnZUFscGhhKTsKCX0KCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TdGFnZTAgKiBvdXRwdXRDb3ZlcmFnZV9TdGFnZTA7Cgl9Cn0KAAAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IADAAAAGluQ2lyY2xlRWRnZQEAAAAAAAAA","LIJAAAAAAAGAAAAAAIWP577774BSZ7X7777QCAAAAABAAAAAABBAMADQAUAAAABAAAAAAABBEMAAA":"AwAAAExTS1MFAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdmxvY2FsQ29vcmRfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCXZsb2NhbENvb3JkX1N0YWdlMCA9IGxvY2FsQ29vcmQ7Cglza19Qb3NpdGlvbiA9IGZsb2F0NChwb3NpdGlvbi54ICwgcG9zaXRpb24ueSwgMCwgMSk7Cn0KAAAAAAAAAAAAAAAAAAAAFgIAAHVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1N0YWdlMDsKaW4gZmxvYXQyIHZsb2NhbENvb3JkX1N0YWdlMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKdm9pZCBtYWluKCkgCnsKCWhhbGY0IG91dHB1dENvbG9yX1N0YWdlMDsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1N0YWdlMDsKCXsKCQkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCQlvdXRwdXRDb2xvcl9TdGFnZTAgPSBoYWxmNCgxKTsKCQlmbG9hdDIgdGV4Q29vcmQ7CgkJdGV4Q29vcmQgPSB2bG9jYWxDb29yZF9TdGFnZTA7CgkJb3V0cHV0Q29sb3JfU3RhZ2UwID0gKHNhbXBsZSh1VGV4dHVyZVNhbXBsZXJfMF9TdGFnZTAsIHRleENvb3JkKSAqIG91dHB1dENvbG9yX1N0YWdlMCk7CgkJb3V0cHV0Q292ZXJhZ2VfU3RhZ2UwID0gaGFsZjQoMSk7Cgl9Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfU3RhZ2UwICogb3V0cHV0Q292ZXJhZ2VfU3RhZ2UwOwoJfQp9CgAAAAAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24KAAAAbG9jYWxDb29yZAAAAQAAAAAAAAA=","DIQAAAAAMAAAAABAYAROFA2CAIAAAABAAAAAAAAAAAACABUSA4AAAEAAAAAAA4AFAAAAAIAAAAAAAIJDAAAA":"AwAAAExTS1NQAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQyIHVBdGxhc1NpemVJbnZfU3RhZ2UwOwppbiBmbG9hdDIgaW5Qb3NpdGlvbjsKaW4gaGFsZjQgaW5Db2xvcjsKaW4gdXNob3J0MiBpblRleHR1cmVDb29yZHM7Cm91dCBmbG9hdDIgdlRleHR1cmVDb29yZHNfU3RhZ2UwOwpmbGF0IG91dCBmbG9hdCB2VGV4SW5kZXhfU3RhZ2UwOwpvdXQgaGFsZjQgdmluQ29sb3JfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBCaXRtYXBUZXh0CglpbnQgdGV4SWR4ID0gMDsKCWZsb2F0MiB1bm9ybVRleENvb3JkcyA9IGZsb2F0MihpblRleHR1cmVDb29yZHMueCwgaW5UZXh0dXJlQ29vcmRzLnkpOwoJdlRleHR1cmVDb29yZHNfU3RhZ2UwID0gdW5vcm1UZXhDb29yZHMgKiB1QXRsYXNTaXplSW52X1N0YWdlMDsKCXZUZXhJbmRleF9TdGFnZTAgPSBmbG9hdCh0ZXhJZHgpOwoJdmluQ29sb3JfU3RhZ2UwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247Cglza19Qb3NpdGlvbiA9IGZsb2F0NChpblBvc2l0aW9uLnggLCBpblBvc2l0aW9uLnksIDAsIDEpOwp9CgABAQAAAAAAAAEBAPEDAAB1bmlmb3JtIGZsb2F0NCB1aW5uZXJSZWN0X1N0YWdlMV9jMDsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfU3RhZ2UxX2MwOwp1bmlmb3JtIHNhbXBsZXIyRCB1VGV4dHVyZVNhbXBsZXJfMF9TdGFnZTA7CmluIGZsb2F0MiB2VGV4dHVyZUNvb3Jkc19TdGFnZTA7CmZsYXQgaW4gZmxvYXQgdlRleEluZGV4X1N0YWdlMDsKaW4gaGFsZjQgdmluQ29sb3JfU3RhZ2UwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwppbmxpbmUgaGFsZjQgQ2lyY3VsYXJSUmVjdF9TdGFnZTFfYzAoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGR4eTAgPSB1aW5uZXJSZWN0X1N0YWdlMV9jMC5MVCAtIHNrX0ZyYWdDb29yZC54eTsKCWZsb2F0MiBkeHkxID0gc2tfRnJhZ0Nvb3JkLnh5IC0gdWlubmVyUmVjdF9TdGFnZTFfYzAuUkI7CglmbG9hdDIgZHh5ID0gbWF4KG1heChkeHkwLCBkeHkxKSwgMC4wKTsKCWhhbGYgYWxwaGEgPSBoYWxmKHNhdHVyYXRlKHVyYWRpdXNQbHVzSGFsZl9TdGFnZTFfYzAueCAtIGxlbmd0aChkeHkpKSk7CglyZXR1cm4gX2lucHV0ICogYWxwaGE7Cn0Kdm9pZCBtYWluKCkgCnsKCWhhbGY0IG91dHB1dENvbG9yX1N0YWdlMDsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1N0YWdlMDsKCXsKCQkvLyBTdGFnZSAwLCBCaXRtYXBUZXh0CgkJb3V0cHV0Q29sb3JfU3RhZ2UwID0gdmluQ29sb3JfU3RhZ2UwOwoJCWhhbGY0IHRleENvbG9yOwoJCXsKCQkJdGV4Q29sb3IgPSBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfU3RhZ2UwLCB2VGV4dHVyZUNvb3Jkc19TdGFnZTApLnJycnI7CgkJfQoJCW91dHB1dENvdmVyYWdlX1N0YWdlMCA9IHRleENvbG9yOwoJfQoJaGFsZjQgb3V0cHV0X1N0YWdlMTsKCW91dHB1dF9TdGFnZTEgPSBDaXJjdWxhclJSZWN0X1N0YWdlMV9jMChvdXRwdXRDb3ZlcmFnZV9TdGFnZTApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1N0YWdlMCAqIG91dHB1dF9TdGFnZTE7Cgl9Cn0KAAAAAAEBAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IADwAAAGluVGV4dHVyZUNvb3JkcwABAAAAAAAAAA==","BYAAQAAAAQAAAAABC3777777777QAAAAAAAAAAAAXABAAAAACAAAAAEASAIQAAAA":"AwAAAExTS1PkAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBEZWZhdWx0R2VvbWV0cnlQcm9jZXNzb3IKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247Cglza19Qb3NpdGlvbiA9IGZsb2F0NChfdG1wXzBfaW5Qb3NpdGlvbi54ICwgX3RtcF8wX2luUG9zaXRpb24ueSwgMCwgMSk7Cn0KAAAAAAAAAAAAAAAAWgEAAHVuaWZvcm0gaGFsZjQgdUNvbG9yX1N0YWdlMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKdm9pZCBtYWluKCkgCnsKCWhhbGY0IG91dHB1dENvbG9yX1N0YWdlMDsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1N0YWdlMDsKCXsKCQkvLyBTdGFnZSAwLCBEZWZhdWx0R2VvbWV0cnlQcm9jZXNzb3IKCQlvdXRwdXRDb2xvcl9TdGFnZTAgPSB1Q29sb3JfU3RhZ2UwOwoJCW91dHB1dENvdmVyYWdlX1N0YWdlMCA9IGhhbGY0KDEpOwoJfQoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1N0YWdlMCAqIG91dHB1dENvdmVyYWdlX1N0YWdlMDsKCX0KfQoAAAAAAAABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAABAAAACgAAAGluUG9zaXRpb24AAAEAAAAAAAAA","LIIAAAAAAAGAAAAAAIWP577774BSZ7X7777QCAAAAAAAAAAAFIQA2AAAAAAQCBAAAAAHQJBAIAAAAAEACMAYIDAAAQCAAAAAAAALQAQAAAABAAAAACAJAEIAAA":"AwAAAExTS1NpAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TdGFnZTFfYzBfYzA7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzBfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCXNrX1Bvc2l0aW9uID0gZmxvYXQ0KHBvc2l0aW9uLnggLCBwb3NpdGlvbi55LCAwLCAxKTsKCXsKCQl2VHJhbnNmb3JtZWRDb29yZHNfMF9TdGFnZTAgPSAoKCh1bWF0cml4X1N0YWdlMV9jMF9jMCkpICogbG9jYWxDb29yZC54eTEpLnh5OwoJfQp9CgAAAAAAAAAAAAAAAAAAAKwEAAB1bmlmb3JtIGZsb2F0M3gzIHVtYXRyaXhfU3RhZ2UxX2MwX2MwOwp1bmlmb3JtIGZsb2F0NCB1Y2xhbXBfU3RhZ2UxX2MwX2MwX2MwOwp1bmlmb3JtIHNhbXBsZXIyRCB1VGV4dHVyZVNhbXBsZXJfMF9TdGFnZTE7CmluIGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMF9TdGFnZTA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IFRleHR1cmVFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKGhhbGY0IF9pbnB1dCkgCnsKCWZsb2F0MiBpbkNvb3JkID0gdlRyYW5zZm9ybWVkQ29vcmRzXzBfU3RhZ2UwOwoJZmxvYXQyIHN1YnNldENvb3JkOwoJc3Vic2V0Q29vcmQueCA9IGluQ29vcmQueDsKCXN1YnNldENvb3JkLnkgPSBpbkNvb3JkLnk7CglmbG9hdDIgY2xhbXBlZENvb3JkOwoJY2xhbXBlZENvb3JkID0gY2xhbXAoc3Vic2V0Q29vcmQsIHVjbGFtcF9TdGFnZTFfYzBfYzBfYzAueHksIHVjbGFtcF9TdGFnZTFfYzBfYzBfYzAuencpOwoJaGFsZjQgdGV4dHVyZUNvbG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1N0YWdlMSwgY2xhbXBlZENvb3JkKTsKCXJldHVybiB0ZXh0dXJlQ29sb3I7Cn0KaGFsZjQgTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChoYWxmNCBfaW5wdXQpIAp7CglyZXR1cm4gVGV4dHVyZUVmZmVjdF9TdGFnZTFfYzBfYzBfYzAoX2lucHV0KTsKfQppbmxpbmUgaGFsZjQgQmxlbmRfU3RhZ2UxX2MwKGhhbGY0IF9pbnB1dCkgCnsKCS8vIEJsZW5kIG1vZGU6IE1vZHVsYXRlIChDb21wb3NlLU9uZSBiZWhhdmlvcikKCXJldHVybiBibGVuZF9tb2R1bGF0ZShNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwKGhhbGY0KDEpKSwgX2lucHV0KTsKfQp2b2lkIG1haW4oKSAKewoJaGFsZjQgb3V0cHV0Q29sb3JfU3RhZ2UwOwoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfU3RhZ2UwOwoJewoJCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJCW91dHB1dENvbG9yX1N0YWdlMCA9IGhhbGY0KDEpOwoJCW91dHB1dENvdmVyYWdlX1N0YWdlMCA9IGhhbGY0KDEpOwoJfQoJaGFsZjQgb3V0cHV0X1N0YWdlMTsKCW91dHB1dF9TdGFnZTEgPSBCbGVuZF9TdGFnZTFfYzAob3V0cHV0Q29sb3JfU3RhZ2UwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRfU3RhZ2UxICogb3V0cHV0Q292ZXJhZ2VfU3RhZ2UwOwoJfQp9CgAAAAABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAACAAAACAAAAHBvc2l0aW9uCgAAAGxvY2FsQ29vcmQAAAEAAAAAAAAA","LIIAAAAAAAGAAAAAAIWP577774BSZ7X7777QCAAAAAAAAAAAZIRQCAICAAAAA6BIEBAAAAAAQAJQDBAMAACAIAAAAAALQAQAAAABAAAAACAJACIAAAAA":"AwAAAExTS1OjAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TdGFnZTFfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TdGFnZTFfYzBfYzA7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzBfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCXNrX1Bvc2l0aW9uID0gZmxvYXQ0KHBvc2l0aW9uLnggLCBwb3NpdGlvbi55LCAwLCAxKTsKCXsKCQl2VHJhbnNmb3JtZWRDb29yZHNfMF9TdGFnZTAgPSAoKCh1bWF0cml4X1N0YWdlMV9jMF9jMCkgKiAodW1hdHJpeF9TdGFnZTFfYzApKSAqIGxvY2FsQ29vcmQueHkxKS54eTsKCX0KfQoAAAAAAAAAAAAAAAAAlAQAAHVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TdGFnZTFfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TdGFnZTFfYzBfYzA7CnVuaWZvcm0gZmxvYXQ0IHVjbGFtcF9TdGFnZTFfYzBfYzBfYzA7CnVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1N0YWdlMTsKaW4gZmxvYXQyIHZUcmFuc2Zvcm1lZENvb3Jkc18wX1N0YWdlMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgVGV4dHVyZUVmZmVjdF9TdGFnZTFfYzBfYzBfYzAoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGluQ29vcmQgPSB2VHJhbnNmb3JtZWRDb29yZHNfMF9TdGFnZTA7CglmbG9hdDIgc3Vic2V0Q29vcmQ7CglzdWJzZXRDb29yZC54ID0gaW5Db29yZC54OwoJc3Vic2V0Q29vcmQueSA9IGluQ29vcmQueTsKCWZsb2F0MiBjbGFtcGVkQ29vcmQ7CgljbGFtcGVkQ29vcmQgPSBjbGFtcChzdWJzZXRDb29yZCwgdWNsYW1wX1N0YWdlMV9jMF9jMF9jMC54eSwgdWNsYW1wX1N0YWdlMV9jMF9jMF9jMC56dyk7CgloYWxmNCB0ZXh0dXJlQ29sb3IgPSBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfU3RhZ2UxLCBjbGFtcGVkQ29vcmQpOwoJcmV0dXJuIHRleHR1cmVDb2xvcjsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwKGhhbGY0IF9pbnB1dCkgCnsKCXJldHVybiBUZXh0dXJlRWZmZWN0X1N0YWdlMV9jMF9jMF9jMChfaW5wdXQpOwp9CmlubGluZSBoYWxmNCBNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwKGhhbGY0IF9pbnB1dCkgCnsKCXJldHVybiBNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwKF9pbnB1dCk7Cn0Kdm9pZCBtYWluKCkgCnsKCWhhbGY0IG91dHB1dENvbG9yX1N0YWdlMDsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1N0YWdlMDsKCXsKCQkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCQlvdXRwdXRDb2xvcl9TdGFnZTAgPSBoYWxmNCgxKTsKCQlvdXRwdXRDb3ZlcmFnZV9TdGFnZTAgPSBoYWxmNCgxKTsKCX0KCWhhbGY0IG91dHB1dF9TdGFnZTE7CglvdXRwdXRfU3RhZ2UxID0gTWF0cml4RWZmZWN0X1N0YWdlMV9jMChvdXRwdXRDb2xvcl9TdGFnZTApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dF9TdGFnZTEgKiBvdXRwdXRDb3ZlcmFnZV9TdGFnZTA7Cgl9Cn0KAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24KAAAAbG9jYWxDb29yZAAAAQAAAAAAAAA=","GJQQAAAAMAAGGADDACRQAAAAMAACHQDCABRQAI7CAMAAAABAA2JAOAAAKAAAAAGAQUKAAAAAABYAKAAAAAQAAAAAAAQSGAAA":"AwAAAExTS1PfCwAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0NCByYWRpaV9zZWxlY3RvcjsKaW4gZmxvYXQ0IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHM7CmluIGZsb2F0NCBhYV9ibG9hdF9hbmRfY292ZXJhZ2U7CmluIGZsb2F0NCBza2V3OwppbiBmbG9hdDIgdHJhbnNsYXRlOwppbiBmbG9hdDQgcmFkaWlfeDsKaW4gZmxvYXQ0IHJhZGlpX3k7CmluIGhhbGY0IGNvbG9yOwpmbGF0IG91dCBoYWxmNCB2Y29sb3JfU3RhZ2UwOwpvdXQgZmxvYXQyIHZhcmNjb29yZF9TdGFnZTA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIEdyRmlsbFJSZWN0T3A6OlByb2Nlc3NvcgoJdmNvbG9yX1N0YWdlMCA9IGNvbG9yOwoJZmxvYXQgYWFfYmxvYXRfbXVsdGlwbGllciA9IDE7CglmbG9hdDIgY29ybmVyID0gY29ybmVyX2FuZF9yYWRpdXNfb3V0c2V0cy54eTsKCWZsb2F0MiByYWRpdXNfb3V0c2V0ID0gY29ybmVyX2FuZF9yYWRpdXNfb3V0c2V0cy56dzsKCWZsb2F0MiBhYV9ibG9hdF9kaXJlY3Rpb24gPSBhYV9ibG9hdF9hbmRfY292ZXJhZ2UueHk7CglmbG9hdCBpc19saW5lYXJfY292ZXJhZ2UgPSBhYV9ibG9hdF9hbmRfY292ZXJhZ2UudzsKCWZsb2F0MiBwaXhlbGxlbmd0aCA9IGludmVyc2VzcXJ0KGZsb2F0Mihkb3Qoc2tldy54eiwgc2tldy54eiksIGRvdChza2V3Lnl3LCBza2V3Lnl3KSkpOwoJZmxvYXQ0IG5vcm1hbGl6ZWRfYXhpc19kaXJzID0gc2tldyAqIHBpeGVsbGVuZ3RoLnh5eHk7CglmbG9hdDIgYXhpc3dpZHRocyA9IChhYnMobm9ybWFsaXplZF9heGlzX2RpcnMueHkpICsgYWJzKG5vcm1hbGl6ZWRfYXhpc19kaXJzLnp3KSk7CglmbG9hdDIgYWFfYmxvYXRyYWRpdXMgPSBheGlzd2lkdGhzICogcGl4ZWxsZW5ndGggKiAuNTsKCWZsb2F0NCByYWRpaV9hbmRfbmVpZ2hib3JzID0gcmFkaWlfc2VsZWN0b3IqIGZsb2F0NHg0KHJhZGlpX3gsIHJhZGlpX3ksIHJhZGlpX3gueXh3eiwgcmFkaWlfeS53enl4KTsKCWZsb2F0MiByYWRpaSA9IHJhZGlpX2FuZF9uZWlnaGJvcnMueHk7CglmbG9hdDIgbmVpZ2hib3JfcmFkaWkgPSByYWRpaV9hbmRfbmVpZ2hib3JzLnp3OwoJZmxvYXQgY292ZXJhZ2VfbXVsdGlwbGllciA9IDE7CglpZiAoYW55KGdyZWF0ZXJUaGFuKGFhX2Jsb2F0cmFkaXVzLCBmbG9hdDIoMSkpKSkgCgl7CgkJY29ybmVyID0gbWF4KGFicyhjb3JuZXIpLCBhYV9ibG9hdHJhZGl1cykgKiBzaWduKGNvcm5lcik7CgkJY292ZXJhZ2VfbXVsdGlwbGllciA9IDEgLyAobWF4KGFhX2Jsb2F0cmFkaXVzLngsIDEpICogbWF4KGFhX2Jsb2F0cmFkaXVzLnksIDEpKTsKCQlyYWRpaSA9IGZsb2F0MigwKTsKCX0KCWZsb2F0IGNvdmVyYWdlID0gYWFfYmxvYXRfYW5kX2NvdmVyYWdlLno7CglpZiAoYW55KGxlc3NUaGFuKHJhZGlpLCBhYV9ibG9hdHJhZGl1cyAqIDEuNSkpKSAKCXsKCQlyYWRpaSA9IGZsb2F0MigwKTsKCQlhYV9ibG9hdF9kaXJlY3Rpb24gPSBzaWduKGNvcm5lcik7CgkJaWYgKGNvdmVyYWdlID4gLjUpIAoJCXsKCQkJYWFfYmxvYXRfZGlyZWN0aW9uID0gLWFhX2Jsb2F0X2RpcmVjdGlvbjsKCQl9CgkJaXNfbGluZWFyX2NvdmVyYWdlID0gMTsKCX0KCWVsc2UgCgl7CgkJcmFkaWkgPSBjbGFtcChyYWRpaSwgcGl4ZWxsZW5ndGggKiAxLjUsIDIgLSBwaXhlbGxlbmd0aCAqIDEuNSk7CgkJbmVpZ2hib3JfcmFkaWkgPSBjbGFtcChuZWlnaGJvcl9yYWRpaSwgcGl4ZWxsZW5ndGggKiAxLjUsIDIgLSBwaXhlbGxlbmd0aCAqIDEuNSk7CgkJZmxvYXQyIHNwYWNpbmcgPSAyIC0gcmFkaWkgLSBuZWlnaGJvcl9yYWRpaTsKCQlmbG9hdDIgZXh0cmFfcGFkID0gbWF4KHBpeGVsbGVuZ3RoICogLjA2MjUgLSBzcGFjaW5nLCBmbG9hdDIoMCkpOwoJCXJhZGlpIC09IGV4dHJhX3BhZCAqIC41OwoJfQoJZmxvYXQyIGFhX291dHNldCA9IGFhX2Jsb2F0X2RpcmVjdGlvbiAqIGFhX2Jsb2F0cmFkaXVzICogYWFfYmxvYXRfbXVsdGlwbGllcjsKCWZsb2F0MiB2ZXJ0ZXhwb3MgPSBjb3JuZXIgKyByYWRpdXNfb3V0c2V0ICogcmFkaWkgKyBhYV9vdXRzZXQ7CglpZiAoY292ZXJhZ2UgPiAuNSkgCgl7CgkJaWYgKGFhX2Jsb2F0X2RpcmVjdGlvbi54ICE9IDAgJiYgdmVydGV4cG9zLnggKiBjb3JuZXIueCA8IDApIAoJCXsKCQkJZmxvYXQgYmFja3NldCA9IGFicyh2ZXJ0ZXhwb3MueCk7CgkJCXZlcnRleHBvcy54ID0gMDsKCQkJdmVydGV4cG9zLnkgKz0gYmFja3NldCAqIHNpZ24oY29ybmVyLnkpICogcGl4ZWxsZW5ndGgueS9waXhlbGxlbmd0aC54OwoJCQljb3ZlcmFnZSA9IChjb3ZlcmFnZSAtIC41KSAqIGFicyhjb3JuZXIueCkgLyAoYWJzKGNvcm5lci54KSArIGJhY2tzZXQpICsgLjU7CgkJfQoJCWlmIChhYV9ibG9hdF9kaXJlY3Rpb24ueSAhPSAwICYmIHZlcnRleHBvcy55ICogY29ybmVyLnkgPCAwKSAKCQl7CgkJCWZsb2F0IGJhY2tzZXQgPSBhYnModmVydGV4cG9zLnkpOwoJCQl2ZXJ0ZXhwb3MueSA9IDA7CgkJCXZlcnRleHBvcy54ICs9IGJhY2tzZXQgKiBzaWduKGNvcm5lci54KSAqIHBpeGVsbGVuZ3RoLngvcGl4ZWxsZW5ndGgueTsKCQkJY292ZXJhZ2UgPSAoY292ZXJhZ2UgLSAuNSkgKiBhYnMoY29ybmVyLnkpIC8gKGFicyhjb3JuZXIueSkgKyBiYWNrc2V0KSArIC41OwoJCX0KCX0KCWZsb2F0MngyIHNrZXdtYXRyaXggPSBmbG9hdDJ4Mihza2V3Lnh5LCBza2V3Lnp3KTsKCWZsb2F0MiBkZXZjb29yZCA9IHZlcnRleHBvcyAqIHNrZXdtYXRyaXggKyB0cmFuc2xhdGU7CglpZiAoMCAhPSBpc19saW5lYXJfY292ZXJhZ2UpIAoJewoJCXZhcmNjb29yZF9TdGFnZTAueHkgPSBmbG9hdDIoMCwgY292ZXJhZ2UgKiBjb3ZlcmFnZV9tdWx0aXBsaWVyKTsKCX0KCWVsc2UgCgl7CgkJZmxvYXQyIGFyY2Nvb3JkID0gMSAtIGFicyhyYWRpdXNfb3V0c2V0KSArIGFhX291dHNldC9yYWRpaSAqIGNvcm5lcjsKCQl2YXJjY29vcmRfU3RhZ2UwLnh5ID0gZmxvYXQyKGFyY2Nvb3JkLngrMSwgYXJjY29vcmQueSk7Cgl9Cglza19Qb3NpdGlvbiA9IGZsb2F0NChkZXZjb29yZC54ICwgZGV2Y29vcmQueSwgMCwgMSk7Cn0KAAABAQAAAAAAAAEBAFkHAAB1bmlmb3JtIGZsb2F0NCB1aW5uZXJSZWN0X1N0YWdlMV9jMDsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfU3RhZ2UxX2MwOwp1bmlmb3JtIGZsb2F0NCB1cmVjdFVuaWZvcm1fU3RhZ2UxX2MwX2MwOwpmbGF0IGluIGhhbGY0IHZjb2xvcl9TdGFnZTA7CmluIGZsb2F0MiB2YXJjY29vcmRfU3RhZ2UwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBBQVJlY3RFZmZlY3RfU3RhZ2UxX2MwX2MwKGhhbGY0IF9pbnB1dCkgCnsKCWZsb2F0NCBwcmV2UmVjdCA9IGZsb2F0NCgtMS4wMDAwMDAsIC0xLjAwMDAwMCwgLTEuMDAwMDAwLCAtMS4wMDAwMDApOwoJaGFsZiBjb3ZlcmFnZTsKCUBzd2l0Y2ggKDEpIAoJewoJCWNhc2UgMDogICAgY2FzZSAyOiAgICAgICAgY292ZXJhZ2UgPSBoYWxmKGFsbChncmVhdGVyVGhhbihmbG9hdDQoc2tfRnJhZ0Nvb3JkLnh5LCB1cmVjdFVuaWZvcm1fU3RhZ2UxX2MwX2MwLnp3KSwgZmxvYXQ0KHVyZWN0VW5pZm9ybV9TdGFnZTFfYzBfYzAueHksIHNrX0ZyYWdDb29yZC54eSkpKSA/IDEgOiAwKTsKCQlicmVhazsKCQlkZWZhdWx0OiAgICAgICAgaGFsZjQgZGlzdHM0ID0gY2xhbXAoaGFsZjQoMS4wLCAxLjAsIC0xLjAsIC0xLjApICogaGFsZjQoc2tfRnJhZ0Nvb3JkLnh5eHkgLSB1cmVjdFVuaWZvcm1fU3RhZ2UxX2MwX2MwKSwgMC4wLCAxLjApOwoJCWhhbGYyIGRpc3RzMiA9IChkaXN0czQueHkgKyBkaXN0czQuencpIC0gMS4wOwoJCWNvdmVyYWdlID0gZGlzdHMyLnggKiBkaXN0czIueTsKCX0KCUBpZiAoMSA9PSAyIHx8IDEgPT0gMykgCgl7CgkJY292ZXJhZ2UgPSAxLjAgLSBjb3ZlcmFnZTsKCX0KCXJldHVybiBfaW5wdXQgKiBjb3ZlcmFnZTsKfQppbmxpbmUgaGFsZjQgQ2lyY3VsYXJSUmVjdF9TdGFnZTFfYzAoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGR4eTAgPSB1aW5uZXJSZWN0X1N0YWdlMV9jMC5MVCAtIHNrX0ZyYWdDb29yZC54eTsKCWZsb2F0MiBkeHkxID0gc2tfRnJhZ0Nvb3JkLnh5IC0gdWlubmVyUmVjdF9TdGFnZTFfYzAuUkI7CglmbG9hdDIgZHh5ID0gbWF4KG1heChkeHkwLCBkeHkxKSwgMC4wKTsKCWhhbGYgYWxwaGEgPSBoYWxmKHNhdHVyYXRlKHVyYWRpdXNQbHVzSGFsZl9TdGFnZTFfYzAueCAtIGxlbmd0aChkeHkpKSk7CglyZXR1cm4gQUFSZWN0RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQpICogYWxwaGE7Cn0Kdm9pZCBtYWluKCkgCnsKCWhhbGY0IG91dHB1dENvbG9yX1N0YWdlMDsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1N0YWdlMDsKCXsKCQkvLyBTdGFnZSAwLCBHckZpbGxSUmVjdE9wOjpQcm9jZXNzb3IKCQlvdXRwdXRDb2xvcl9TdGFnZTAgPSB2Y29sb3JfU3RhZ2UwOwoJCWZsb2F0IHhfcGx1c18xPXZhcmNjb29yZF9TdGFnZTAueCwgeT12YXJjY29vcmRfU3RhZ2UwLnk7CgkJaGFsZiBjb3ZlcmFnZTsKCQlpZiAoMCA9PSB4X3BsdXNfMSkgCgkJewoJCQljb3ZlcmFnZSA9IGhhbGYoeSk7CgkJfQoJCWVsc2UgCgkJewoJCQlmbG9hdCBmbiA9IHhfcGx1c18xICogKHhfcGx1c18xIC0gMik7CgkJCWZuID0gZm1hKHkseSwgZm4pOwoJCQlmbG9hdCBmbndpZHRoID0gZndpZHRoKGZuKTsKCQkJY292ZXJhZ2UgPSAuNSAtIGhhbGYoZm4vZm53aWR0aCk7CgkJCWNvdmVyYWdlID0gY2xhbXAoY292ZXJhZ2UsIDAsIDEpOwoJCX0KCQlvdXRwdXRDb3ZlcmFnZV9TdGFnZTAgPSBoYWxmNChjb3ZlcmFnZSk7Cgl9CgloYWxmNCBvdXRwdXRfU3RhZ2UxOwoJb3V0cHV0X1N0YWdlMSA9IENpcmN1bGFyUlJlY3RfU3RhZ2UxX2MwKG91dHB1dENvdmVyYWdlX1N0YWdlMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfU3RhZ2UwICogb3V0cHV0X1N0YWdlMTsKCX0KfQoAAAAAAQEAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAACAAAAA4AAAByYWRpaV9zZWxlY3RvcgAAGQAAAGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHMAAAAVAAAAYWFfYmxvYXRfYW5kX2NvdmVyYWdlAAAABAAAAHNrZXcJAAAAdHJhbnNsYXRlAAAABwAAAHJhZGlpX3gABwAAAHJhZGlpX3kABQAAAGNvbG9yAAAAAQAAAAAAAAA="}} \ No newline at end of file diff --git a/shaders_2.2.0-10.1.pre.sksl.json b/shaders_2.2.0-10.1.pre.sksl.json new file mode 100644 index 000000000..2c591b909 --- /dev/null +++ b/shaders_2.2.0-10.1.pre.sksl.json @@ -0,0 +1 @@ +{"platform":"android","name":"SM G970N","engineRevision":"d2a2e93510ad6cfc3d62a90d903b7056e4da8264","data":{"K4IAAAAAAAGAAAAAAIWP577774BSZ7X7777QCAAAAAAAAAAAVIRQYAAAAAAACAQAAAAGJCABAAAAAICAAAAABAGOAACAQAAAABQCVAEQAEAIAAAAAAAAAVAAAAAAAAQAAAABAMQCAAAAA":"BAAAAExTS1NjAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TdGFnZTFfYzA7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzBfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCXNrX1Bvc2l0aW9uID0gZmxvYXQ0KHBvc2l0aW9uLnggLCBwb3NpdGlvbi55LCAwLCAxKTsKCXsKCQl2VHJhbnNmb3JtZWRDb29yZHNfMF9TdGFnZTAgPSAoKCh1bWF0cml4X1N0YWdlMV9jMCkpICogbG9jYWxDb29yZC54eTEpLnh5OwoJfQp9CgAAAAAAAAAAAAAAAAAcFAAAdW5pZm9ybSBmbG9hdDN4MyB1bWF0cml4X1N0YWdlMV9jMDsKdW5pZm9ybSBoYWxmMiB1XzBfSW5jcmVtZW50X1N0YWdlMV9jMF9jMDsKdW5pZm9ybSBoYWxmNCB1XzFfS2VybmVsX1N0YWdlMV9jMF9jMFs3XTsKdW5pZm9ybSBmbG9hdDN4MyB1bWF0cml4X1N0YWdlMV9jMF9jMF9jMDsKdW5pZm9ybSBmbG9hdDQgdWNsYW1wX1N0YWdlMV9jMF9jMF9jMF9jMDsKdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfU3RhZ2UxOwppbiBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzBfU3RhZ2UwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBUZXh0dXJlRWZmZWN0X1N0YWdlMV9jMF9jMF9jMF9jMChoYWxmNCBfaW5wdXQsIGZsb2F0MiBfY29vcmRzKSAKewoJZmxvYXQyIGluQ29vcmQgPSBfY29vcmRzOwoJZmxvYXQyIHN1YnNldENvb3JkOwoJc3Vic2V0Q29vcmQueCA9IGluQ29vcmQueDsKCXN1YnNldENvb3JkLnkgPSBpbkNvb3JkLnk7CglmbG9hdDIgY2xhbXBlZENvb3JkOwoJY2xhbXBlZENvb3JkLnggPSBzdWJzZXRDb29yZC54OwoJY2xhbXBlZENvb3JkLnkgPSBjbGFtcChzdWJzZXRDb29yZC55LCB1Y2xhbXBfU3RhZ2UxX2MwX2MwX2MwX2MwLnksIHVjbGFtcF9TdGFnZTFfYzBfYzBfYzBfYzAudyk7CgloYWxmNCB0ZXh0dXJlQ29sb3IgPSBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfU3RhZ2UxLCBjbGFtcGVkQ29vcmQpOwoJcmV0dXJuIHRleHR1cmVDb2xvcjsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKGhhbGY0IF9pbnB1dCwgZmxvYXQyIF9jb29yZHMpIAp7CglyZXR1cm4gVGV4dHVyZUVmZmVjdF9TdGFnZTFfYzBfYzBfYzBfYzAoX2lucHV0LCAoKHVtYXRyaXhfU3RhZ2UxX2MwX2MwX2MwKSAqIF9jb29yZHMueHkxKS54eSk7Cn0KaGFsZjQgR2F1c3NpYW5Db252b2x1dGlvbl9TdGFnZTFfYzBfYzAoaGFsZjQgX2lucHV0KSAKewoJaGFsZjQgXzJfY29sb3IgPSBoYWxmNCgwLjApOwoJZmxvYXQyIF8zX2Nvb3JkID0gKHZUcmFuc2Zvcm1lZENvb3Jkc18wX1N0YWdlMCAtIGZsb2F0MigoMTIuMCAqIHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwX2MwKSkpOwoJKF8yX2NvbG9yICs9IChNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKF9pbnB1dCwgXzNfY29vcmQpICogdV8xX0tlcm5lbF9TdGFnZTFfYzBfYzBbMF0ueCkpOwoJKF8zX2Nvb3JkICs9IGZsb2F0Mih1XzBfSW5jcmVtZW50X1N0YWdlMV9jMF9jMCkpOwoJKF8yX2NvbG9yICs9IChNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKF9pbnB1dCwgXzNfY29vcmQpICogdV8xX0tlcm5lbF9TdGFnZTFfYzBfYzBbMF0ueSkpOwoJKF8zX2Nvb3JkICs9IGZsb2F0Mih1XzBfSW5jcmVtZW50X1N0YWdlMV9jMF9jMCkpOwoJKF8yX2NvbG9yICs9IChNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKF9pbnB1dCwgXzNfY29vcmQpICogdV8xX0tlcm5lbF9TdGFnZTFfYzBfYzBbMF0ueikpOwoJKF8zX2Nvb3JkICs9IGZsb2F0Mih1XzBfSW5jcmVtZW50X1N0YWdlMV9jMF9jMCkpOwoJKF8yX2NvbG9yICs9IChNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKF9pbnB1dCwgXzNfY29vcmQpICogdV8xX0tlcm5lbF9TdGFnZTFfYzBfYzBbMF0udykpOwoJKF8zX2Nvb3JkICs9IGZsb2F0Mih1XzBfSW5jcmVtZW50X1N0YWdlMV9jMF9jMCkpOwoJKF8yX2NvbG9yICs9IChNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKF9pbnB1dCwgXzNfY29vcmQpICogdV8xX0tlcm5lbF9TdGFnZTFfYzBfYzBbMV0ueCkpOwoJKF8zX2Nvb3JkICs9IGZsb2F0Mih1XzBfSW5jcmVtZW50X1N0YWdlMV9jMF9jMCkpOwoJKF8yX2NvbG9yICs9IChNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKF9pbnB1dCwgXzNfY29vcmQpICogdV8xX0tlcm5lbF9TdGFnZTFfYzBfYzBbMV0ueSkpOwoJKF8zX2Nvb3JkICs9IGZsb2F0Mih1XzBfSW5jcmVtZW50X1N0YWdlMV9jMF9jMCkpOwoJKF8yX2NvbG9yICs9IChNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKF9pbnB1dCwgXzNfY29vcmQpICogdV8xX0tlcm5lbF9TdGFnZTFfYzBfYzBbMV0ueikpOwoJKF8zX2Nvb3JkICs9IGZsb2F0Mih1XzBfSW5jcmVtZW50X1N0YWdlMV9jMF9jMCkpOwoJKF8yX2NvbG9yICs9IChNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKF9pbnB1dCwgXzNfY29vcmQpICogdV8xX0tlcm5lbF9TdGFnZTFfYzBfYzBbMV0udykpOwoJKF8zX2Nvb3JkICs9IGZsb2F0Mih1XzBfSW5jcmVtZW50X1N0YWdlMV9jMF9jMCkpOwoJKF8yX2NvbG9yICs9IChNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKF9pbnB1dCwgXzNfY29vcmQpICogdV8xX0tlcm5lbF9TdGFnZTFfYzBfYzBbMl0ueCkpOwoJKF8zX2Nvb3JkICs9IGZsb2F0Mih1XzBfSW5jcmVtZW50X1N0YWdlMV9jMF9jMCkpOwoJKF8yX2NvbG9yICs9IChNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKF9pbnB1dCwgXzNfY29vcmQpICogdV8xX0tlcm5lbF9TdGFnZTFfYzBfYzBbMl0ueSkpOwoJKF8zX2Nvb3JkICs9IGZsb2F0Mih1XzBfSW5jcmVtZW50X1N0YWdlMV9jMF9jMCkpOwoJKF8yX2NvbG9yICs9IChNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKF9pbnB1dCwgXzNfY29vcmQpICogdV8xX0tlcm5lbF9TdGFnZTFfYzBfYzBbMl0ueikpOwoJKF8zX2Nvb3JkICs9IGZsb2F0Mih1XzBfSW5jcmVtZW50X1N0YWdlMV9jMF9jMCkpOwoJKF8yX2NvbG9yICs9IChNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKF9pbnB1dCwgXzNfY29vcmQpICogdV8xX0tlcm5lbF9TdGFnZTFfYzBfYzBbMl0udykpOwoJKF8zX2Nvb3JkICs9IGZsb2F0Mih1XzBfSW5jcmVtZW50X1N0YWdlMV9jMF9jMCkpOwoJKF8yX2NvbG9yICs9IChNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKF9pbnB1dCwgXzNfY29vcmQpICogdV8xX0tlcm5lbF9TdGFnZTFfYzBfYzBbM10ueCkpOwoJKF8zX2Nvb3JkICs9IGZsb2F0Mih1XzBfSW5jcmVtZW50X1N0YWdlMV9jMF9jMCkpOwoJKF8yX2NvbG9yICs9IChNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKF9pbnB1dCwgXzNfY29vcmQpICogdV8xX0tlcm5lbF9TdGFnZTFfYzBfYzBbM10ueSkpOwoJKF8zX2Nvb3JkICs9IGZsb2F0Mih1XzBfSW5jcmVtZW50X1N0YWdlMV9jMF9jMCkpOwoJKF8yX2NvbG9yICs9IChNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKF9pbnB1dCwgXzNfY29vcmQpICogdV8xX0tlcm5lbF9TdGFnZTFfYzBfYzBbM10ueikpOwoJKF8zX2Nvb3JkICs9IGZsb2F0Mih1XzBfSW5jcmVtZW50X1N0YWdlMV9jMF9jMCkpOwoJKF8yX2NvbG9yICs9IChNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKF9pbnB1dCwgXzNfY29vcmQpICogdV8xX0tlcm5lbF9TdGFnZTFfYzBfYzBbM10udykpOwoJKF8zX2Nvb3JkICs9IGZsb2F0Mih1XzBfSW5jcmVtZW50X1N0YWdlMV9jMF9jMCkpOwoJKF8yX2NvbG9yICs9IChNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKF9pbnB1dCwgXzNfY29vcmQpICogdV8xX0tlcm5lbF9TdGFnZTFfYzBfYzBbNF0ueCkpOwoJKF8zX2Nvb3JkICs9IGZsb2F0Mih1XzBfSW5jcmVtZW50X1N0YWdlMV9jMF9jMCkpOwoJKF8yX2NvbG9yICs9IChNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKF9pbnB1dCwgXzNfY29vcmQpICogdV8xX0tlcm5lbF9TdGFnZTFfYzBfYzBbNF0ueSkpOwoJKF8zX2Nvb3JkICs9IGZsb2F0Mih1XzBfSW5jcmVtZW50X1N0YWdlMV9jMF9jMCkpOwoJKF8yX2NvbG9yICs9IChNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKF9pbnB1dCwgXzNfY29vcmQpICogdV8xX0tlcm5lbF9TdGFnZTFfYzBfYzBbNF0ueikpOwoJKF8zX2Nvb3JkICs9IGZsb2F0Mih1XzBfSW5jcmVtZW50X1N0YWdlMV9jMF9jMCkpOwoJKF8yX2NvbG9yICs9IChNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKF9pbnB1dCwgXzNfY29vcmQpICogdV8xX0tlcm5lbF9TdGFnZTFfYzBfYzBbNF0udykpOwoJKF8zX2Nvb3JkICs9IGZsb2F0Mih1XzBfSW5jcmVtZW50X1N0YWdlMV9jMF9jMCkpOwoJKF8yX2NvbG9yICs9IChNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKF9pbnB1dCwgXzNfY29vcmQpICogdV8xX0tlcm5lbF9TdGFnZTFfYzBfYzBbNV0ueCkpOwoJKF8zX2Nvb3JkICs9IGZsb2F0Mih1XzBfSW5jcmVtZW50X1N0YWdlMV9jMF9jMCkpOwoJKF8yX2NvbG9yICs9IChNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKF9pbnB1dCwgXzNfY29vcmQpICogdV8xX0tlcm5lbF9TdGFnZTFfYzBfYzBbNV0ueSkpOwoJKF8zX2Nvb3JkICs9IGZsb2F0Mih1XzBfSW5jcmVtZW50X1N0YWdlMV9jMF9jMCkpOwoJKF8yX2NvbG9yICs9IChNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKF9pbnB1dCwgXzNfY29vcmQpICogdV8xX0tlcm5lbF9TdGFnZTFfYzBfYzBbNV0ueikpOwoJKF8zX2Nvb3JkICs9IGZsb2F0Mih1XzBfSW5jcmVtZW50X1N0YWdlMV9jMF9jMCkpOwoJKF8yX2NvbG9yICs9IChNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKF9pbnB1dCwgXzNfY29vcmQpICogdV8xX0tlcm5lbF9TdGFnZTFfYzBfYzBbNV0udykpOwoJKF8zX2Nvb3JkICs9IGZsb2F0Mih1XzBfSW5jcmVtZW50X1N0YWdlMV9jMF9jMCkpOwoJKF8yX2NvbG9yICs9IChNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKF9pbnB1dCwgXzNfY29vcmQpICogdV8xX0tlcm5lbF9TdGFnZTFfYzBfYzBbNl0ueCkpOwoJcmV0dXJuIF8yX2NvbG9yOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TdGFnZTFfYzAoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIEdhdXNzaWFuQ29udm9sdXRpb25fU3RhZ2UxX2MwX2MwKF9pbnB1dCk7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfU3RhZ2UwID0gaGFsZjQoMSk7Cgljb25zdCBoYWxmNCBvdXRwdXRDb3ZlcmFnZV9TdGFnZTAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TdGFnZTE7CglvdXRwdXRfU3RhZ2UxID0gTWF0cml4RWZmZWN0X1N0YWdlMV9jMChvdXRwdXRDb2xvcl9TdGFnZTApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dF9TdGFnZTEgKiBvdXRwdXRDb3ZlcmFnZV9TdGFnZTA7Cgl9Cn0KAAAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24KAAAAbG9jYWxDb29yZAAAAQAAAAAAAAA=","K4IAAAAAAAGAAAAAAIWP577774BSZ7X7777QCAAAAAAAAAAAVIRQCAACAAAAA5BIABAAAAAAAAJQDBAMAACAAAAAAAAKAAQAAAABAAAAACAJAEIAAAAA":"BAAAAExTS1OjAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TdGFnZTFfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TdGFnZTFfYzBfYzA7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzBfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCXNrX1Bvc2l0aW9uID0gZmxvYXQ0KHBvc2l0aW9uLnggLCBwb3NpdGlvbi55LCAwLCAxKTsKCXsKCQl2VHJhbnNmb3JtZWRDb29yZHNfMF9TdGFnZTAgPSAoKCh1bWF0cml4X1N0YWdlMV9jMF9jMCkgKiAodW1hdHJpeF9TdGFnZTFfYzApKSAqIGxvY2FsQ29vcmQueHkxKS54eTsKCX0KfQoAAAAAAAAAAAAAAAAAgAQAAHVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TdGFnZTFfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TdGFnZTFfYzBfYzA7CnVuaWZvcm0gZmxvYXQ0IHVjbGFtcF9TdGFnZTFfYzBfYzBfYzA7CnVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1N0YWdlMTsKaW4gZmxvYXQyIHZUcmFuc2Zvcm1lZENvb3Jkc18wX1N0YWdlMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgVGV4dHVyZUVmZmVjdF9TdGFnZTFfYzBfYzBfYzAoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGluQ29vcmQgPSB2VHJhbnNmb3JtZWRDb29yZHNfMF9TdGFnZTA7CglmbG9hdDIgc3Vic2V0Q29vcmQ7CglzdWJzZXRDb29yZC54ID0gaW5Db29yZC54OwoJc3Vic2V0Q29vcmQueSA9IGluQ29vcmQueTsKCWZsb2F0MiBjbGFtcGVkQ29vcmQ7CgljbGFtcGVkQ29vcmQueCA9IGNsYW1wKHN1YnNldENvb3JkLngsIHVjbGFtcF9TdGFnZTFfYzBfYzBfYzAueCwgdWNsYW1wX1N0YWdlMV9jMF9jMF9jMC56KTsKCWNsYW1wZWRDb29yZC55ID0gc3Vic2V0Q29vcmQueTsKCWhhbGY0IHRleHR1cmVDb2xvciA9IHNhbXBsZSh1VGV4dHVyZVNhbXBsZXJfMF9TdGFnZTEsIGNsYW1wZWRDb29yZCk7CglyZXR1cm4gdGV4dHVyZUNvbG9yOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzAoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIFRleHR1cmVFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKF9pbnB1dCk7Cn0KaGFsZjQgTWF0cml4RWZmZWN0X1N0YWdlMV9jMChoYWxmNCBfaW5wdXQpIAp7CglyZXR1cm4gTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChfaW5wdXQpOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1N0YWdlMCA9IGhhbGY0KDEpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfU3RhZ2UwID0gaGFsZjQoMSk7CgloYWxmNCBvdXRwdXRfU3RhZ2UxOwoJb3V0cHV0X1N0YWdlMSA9IE1hdHJpeEVmZmVjdF9TdGFnZTFfYzAob3V0cHV0Q29sb3JfU3RhZ2UwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRfU3RhZ2UxICogb3V0cHV0Q292ZXJhZ2VfU3RhZ2UwOwoJfQp9CgAAAAABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAACAAAACAAAAHBvc2l0aW9uCgAAAGxvY2FsQ29vcmQAAAEAAAAAAAAA","AWQAGAAAQAAIXCEPAGGP777777777737AAAAAAAAAAAIAGCIDYAAAQAAAAAAAAAVAAAABAAAAAAABBEMAAAA":"BAAAAExTS1MJAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TdGFnZTA7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiBmbG9hdDQgaW5DaXJjbGVFZGdlOwpvdXQgZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfU3RhZ2UwOwpvdXQgaGFsZjQgdmluQ29sb3JfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TdGFnZTAgPSBpbkNpcmNsZUVkZ2U7Cgl2aW5Db2xvcl9TdGFnZTAgPSBpbkNvbG9yOwoJZmxvYXQyIF90bXBfMF9pblBvc2l0aW9uID0gaW5Qb3NpdGlvbjsKCWZsb2F0MiBfdG1wXzFfaW5Qb3NpdGlvbiA9IHVsb2NhbE1hdHJpeF9TdGFnZTAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1N0YWdlMC55dzsKCXNrX1Bvc2l0aW9uID0gZmxvYXQ0KF90bXBfMF9pblBvc2l0aW9uLnggLCBfdG1wXzBfaW5Qb3NpdGlvbi55LCAwLCAxKTsKfQoAAAAAAQEAAAAAAAABAQDyAwAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TdGFnZTFfYzA7CnVuaWZvcm0gaGFsZjIgdXJhZGl1c1BsdXNIYWxmX1N0YWdlMV9jMDsKaW4gZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfU3RhZ2UwOwppbiBoYWxmNCB2aW5Db2xvcl9TdGFnZTA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IENpcmN1bGFyUlJlY3RfU3RhZ2UxX2MwKGhhbGY0IF9pbnB1dCkgCnsKCWZsb2F0MiBkeHkwID0gdWlubmVyUmVjdF9TdGFnZTFfYzAuTFQgLSBza19GcmFnQ29vcmQueHk7CglmbG9hdDIgZHh5MSA9IHNrX0ZyYWdDb29yZC54eSAtIHVpbm5lclJlY3RfU3RhZ2UxX2MwLlJCOwoJZmxvYXQyIGR4eSA9IG1heChtYXgoZHh5MCwgZHh5MSksIDAuMCk7CgloYWxmIGFscGhhID0gaGFsZihzYXR1cmF0ZSh1cmFkaXVzUGx1c0hhbGZfU3RhZ2UxX2MwLnggLSBsZW5ndGgoZHh5KSkpOwoJcmV0dXJuIF9pbnB1dCAqIGFscGhhOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQ0IGNpcmNsZUVkZ2U7CgljaXJjbGVFZGdlID0gdmluQ2lyY2xlRWRnZV9TdGFnZTA7CgloYWxmNCBvdXRwdXRDb2xvcl9TdGFnZTA7CglvdXRwdXRDb2xvcl9TdGFnZTAgPSB2aW5Db2xvcl9TdGFnZTA7CglmbG9hdCBkID0gbGVuZ3RoKGNpcmNsZUVkZ2UueHkpOwoJaGFsZiBkaXN0YW5jZVRvT3V0ZXJFZGdlID0gaGFsZihjaXJjbGVFZGdlLnogKiAoMS4wIC0gZCkpOwoJaGFsZiBlZGdlQWxwaGEgPSBzYXR1cmF0ZShkaXN0YW5jZVRvT3V0ZXJFZGdlKTsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1N0YWdlMCA9IGhhbGY0KGVkZ2VBbHBoYSk7CgloYWxmNCBvdXRwdXRfU3RhZ2UxOwoJb3V0cHV0X1N0YWdlMSA9IENpcmN1bGFyUlJlY3RfU3RhZ2UxX2MwKG91dHB1dENvdmVyYWdlX1N0YWdlMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfU3RhZ2UwICogb3V0cHV0X1N0YWdlMTsKCX0KfQoAAAABAQABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAADAAAACgAAAGluUG9zaXRpb24AAAcAAABpbkNvbG9yAAwAAABpbkNpcmNsZUVkZ2UBAAAAAAAAAA==","K4QAAAAAAAGAAAAAAIWP57ZDH37P7777777QCAAAAAAAAAAAMIQHSAAAAAAQAAAAABKAAAAAAABAAAAACAZAEAAAAA":"BAAAAExTS1P0AAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7CmZsYXQgb3V0IGhhbGY0IHZjb2xvcl9TdGFnZTA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmNvbG9yX1N0YWdlMCA9IGNvbG9yOwoJc2tfUG9zaXRpb24gPSBmbG9hdDQocG9zaXRpb24ueCAsIHBvc2l0aW9uLnksIDAsIDEpOwp9CgABAQAAAAAAAAEBABEDAAB1bmlmb3JtIGZsb2F0NCB1aW5uZXJSZWN0X1N0YWdlMV9jMDsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfU3RhZ2UxX2MwOwpmbGF0IGluIGhhbGY0IHZjb2xvcl9TdGFnZTA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IENpcmN1bGFyUlJlY3RfU3RhZ2UxX2MwKGhhbGY0IF9pbnB1dCkgCnsKCWZsb2F0MiBkeHkwID0gdWlubmVyUmVjdF9TdGFnZTFfYzAuTFQgLSBza19GcmFnQ29vcmQueHk7CglmbG9hdDIgZHh5MSA9IHNrX0ZyYWdDb29yZC54eSAtIHVpbm5lclJlY3RfU3RhZ2UxX2MwLlJCOwoJZmxvYXQyIGR4eSA9IG1heChtYXgoZHh5MCwgZHh5MSksIDAuMCk7CgloYWxmIGFscGhhID0gaGFsZihzYXR1cmF0ZSh1cmFkaXVzUGx1c0hhbGZfU3RhZ2UxX2MwLnggLSBsZW5ndGgoZHh5KSkpOwoJcmV0dXJuIF9pbnB1dCAqIGFscGhhOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1N0YWdlMDsKCW91dHB1dENvbG9yX1N0YWdlMCA9IHZjb2xvcl9TdGFnZTA7Cgljb25zdCBoYWxmNCBvdXRwdXRDb3ZlcmFnZV9TdGFnZTAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TdGFnZTE7CglvdXRwdXRfU3RhZ2UxID0gQ2lyY3VsYXJSUmVjdF9TdGFnZTFfYzAob3V0cHV0Q292ZXJhZ2VfU3RhZ2UwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TdGFnZTAgKiBvdXRwdXRfU3RhZ2UxOwoJfQp9CgAAAAABAQABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAACAAAACAAAAHBvc2l0aW9uBQAAAGNvbG9yAAAAAQAAAAAAAAA=","DIQAAAAAMAAAAABAYAROFA2CAIAAAABAAAAAAAAAAAAAAVAAAAAAAAQAAAABAMQC":"BAAAAExTS1NQAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQyIHVBdGxhc1NpemVJbnZfU3RhZ2UwOwppbiBmbG9hdDIgaW5Qb3NpdGlvbjsKaW4gaGFsZjQgaW5Db2xvcjsKaW4gdXNob3J0MiBpblRleHR1cmVDb29yZHM7Cm91dCBmbG9hdDIgdlRleHR1cmVDb29yZHNfU3RhZ2UwOwpmbGF0IG91dCBmbG9hdCB2VGV4SW5kZXhfU3RhZ2UwOwpvdXQgaGFsZjQgdmluQ29sb3JfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBCaXRtYXBUZXh0CglpbnQgdGV4SWR4ID0gMDsKCWZsb2F0MiB1bm9ybVRleENvb3JkcyA9IGZsb2F0MihpblRleHR1cmVDb29yZHMueCwgaW5UZXh0dXJlQ29vcmRzLnkpOwoJdlRleHR1cmVDb29yZHNfU3RhZ2UwID0gdW5vcm1UZXhDb29yZHMgKiB1QXRsYXNTaXplSW52X1N0YWdlMDsKCXZUZXhJbmRleF9TdGFnZTAgPSBmbG9hdCh0ZXhJZHgpOwoJdmluQ29sb3JfU3RhZ2UwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247Cglza19Qb3NpdGlvbiA9IGZsb2F0NChpblBvc2l0aW9uLnggLCBpblBvc2l0aW9uLnksIDAsIDEpOwp9CgAAAAAAAAAAAAAAAPkBAAB1bmlmb3JtIHNhbXBsZXIyRCB1VGV4dHVyZVNhbXBsZXJfMF9TdGFnZTA7CmluIGZsb2F0MiB2VGV4dHVyZUNvb3Jkc19TdGFnZTA7CmZsYXQgaW4gZmxvYXQgdlRleEluZGV4X1N0YWdlMDsKaW4gaGFsZjQgdmluQ29sb3JfU3RhZ2UwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgQml0bWFwVGV4dAoJaGFsZjQgb3V0cHV0Q29sb3JfU3RhZ2UwOwoJb3V0cHV0Q29sb3JfU3RhZ2UwID0gdmluQ29sb3JfU3RhZ2UwOwoJaGFsZjQgdGV4Q29sb3I7Cgl7CgkJdGV4Q29sb3IgPSBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfU3RhZ2UwLCB2VGV4dHVyZUNvb3Jkc19TdGFnZTApLnJycnI7Cgl9CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TdGFnZTAgPSB0ZXhDb2xvcjsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TdGFnZTAgKiBvdXRwdXRDb3ZlcmFnZV9TdGFnZTA7Cgl9Cn0KAAAAAAAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IADwAAAGluVGV4dHVyZUNvb3JkcwABAAAAAAAAAA==","K4JAAAAAAAGAAAAAAIWP577774BSZ7X7777QCAAAAABAAAAAABBAMACAAUAAAABAAAAAAABBEMAAA":"BAAAAExTS1MFAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdmxvY2FsQ29vcmRfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCXZsb2NhbENvb3JkX1N0YWdlMCA9IGxvY2FsQ29vcmQ7Cglza19Qb3NpdGlvbiA9IGZsb2F0NChwb3NpdGlvbi54ICwgcG9zaXRpb24ueSwgMCwgMSk7Cn0KAAAAAAAAAAAAAAAAAAAA2wEAAHVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1N0YWdlMDsKaW4gZmxvYXQyIHZsb2NhbENvb3JkX1N0YWdlMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfU3RhZ2UwID0gaGFsZjQoMSk7CglmbG9hdDIgdGV4Q29vcmQ7Cgl0ZXhDb29yZCA9IHZsb2NhbENvb3JkX1N0YWdlMDsKCW91dHB1dENvbG9yX1N0YWdlMCA9ICgoc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1N0YWdlMCwgdGV4Q29vcmQpICogaGFsZjQoMSkpKTsKCWNvbnN0IGhhbGY0IG91dHB1dENvdmVyYWdlX1N0YWdlMCA9IGhhbGY0KDEpOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1N0YWdlMCAqIG91dHB1dENvdmVyYWdlX1N0YWdlMDsKCX0KfQoAAAAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24KAAAAbG9jYWxDb29yZAAAAQAAAAAAAAA=","KYMAAAAABCYIR6AYYAAAAAAAAAAAAAAACUAAAAEAAAAAAAEERQAA":"BAAAAExTS1NwAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiBmbG9hdDQgaW5RdWFkRWRnZTsKb3V0IGZsb2F0NCB2UXVhZEVkZ2VfU3RhZ2UwOwpvdXQgaGFsZjQgdmluQ29sb3JfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBRdWFkRWRnZQoJdlF1YWRFZGdlX1N0YWdlMCA9IGluUXVhZEVkZ2U7Cgl2aW5Db2xvcl9TdGFnZTAgPSBpbkNvbG9yOwoJZmxvYXQyIF90bXBfMF9pblBvc2l0aW9uID0gaW5Qb3NpdGlvbjsKCXNrX1Bvc2l0aW9uID0gZmxvYXQ0KF90bXBfMF9pblBvc2l0aW9uLnggLCBfdG1wXzBfaW5Qb3NpdGlvbi55LCAwLCAxKTsKfQoAAAAAAAAAAAAAAABpAwAAaW4gZmxvYXQ0IHZRdWFkRWRnZV9TdGFnZTA7CmluIGhhbGY0IHZpbkNvbG9yX1N0YWdlMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRFZGdlCgloYWxmNCBvdXRwdXRDb2xvcl9TdGFnZTA7CglvdXRwdXRDb2xvcl9TdGFnZTAgPSB2aW5Db2xvcl9TdGFnZTA7CgloYWxmIGVkZ2VBbHBoYTsKCWhhbGYyIGR1dmR4ID0gaGFsZjIoZEZkeCh2UXVhZEVkZ2VfU3RhZ2UwLnh5KSk7CgloYWxmMiBkdXZkeSA9IGhhbGYyKGRGZHkodlF1YWRFZGdlX1N0YWdlMC54eSkpOwoJaWYgKHZRdWFkRWRnZV9TdGFnZTAueiA+IDAuMCAmJiB2UXVhZEVkZ2VfU3RhZ2UwLncgPiAwLjApIAoJewoJCWVkZ2VBbHBoYSA9IGhhbGYobWluKG1pbih2UXVhZEVkZ2VfU3RhZ2UwLnosIHZRdWFkRWRnZV9TdGFnZTAudykgKyAwLjUsIDEuMCkpOwoJfQoJZWxzZSAKCXsKCQloYWxmMiBnRiA9IGhhbGYyKGhhbGYoMi4wKnZRdWFkRWRnZV9TdGFnZTAueCpkdXZkeC54IC0gZHV2ZHgueSksICAgICAgICAgICAgICAgICBoYWxmKDIuMCp2UXVhZEVkZ2VfU3RhZ2UwLngqZHV2ZHkueCAtIGR1dmR5LnkpKTsKCQllZGdlQWxwaGEgPSBoYWxmKHZRdWFkRWRnZV9TdGFnZTAueCp2UXVhZEVkZ2VfU3RhZ2UwLnggLSB2UXVhZEVkZ2VfU3RhZ2UwLnkpOwoJCWVkZ2VBbHBoYSA9IHNhdHVyYXRlKDAuNSAtIGVkZ2VBbHBoYSAvIGxlbmd0aChnRikpOwoJfQoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfU3RhZ2UwID0gaGFsZjQoZWRnZUFscGhhKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TdGFnZTAgKiBvdXRwdXRDb3ZlcmFnZV9TdGFnZTA7Cgl9Cn0KAAAAAAAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IACgAAAGluUXVhZEVkZ2UAAAEAAAAAAAAA","K4JBYAQCAAGAAAAAAIWP577774BSZ7X7777QCAAAAABAAAAAABBAMACAAUAAAABAAAAAAABBEMAAA":"BAAAAExTS1MFAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdmxvY2FsQ29vcmRfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCXZsb2NhbENvb3JkX1N0YWdlMCA9IGxvY2FsQ29vcmQ7Cglza19Qb3NpdGlvbiA9IGZsb2F0NChwb3NpdGlvbi54ICwgcG9zaXRpb24ueSwgMCwgMSk7Cn0KAAAAAAAAAAAAAAAAAAAAqwYAAHVuaWZvcm0gaGFsZiB1U3JjVEZfU3RhZ2UwWzddOwp1bmlmb3JtIGhhbGYzeDMgdUNvbG9yWGZvcm1fU3RhZ2UwOwp1bmlmb3JtIGhhbGYgdURzdFRGX1N0YWdlMFs3XTsKdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfU3RhZ2UwOwppbiBmbG9hdDIgdmxvY2FsQ29vcmRfU3RhZ2UwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmIHNyY190Zl9TdGFnZTAoaGFsZiB4KSAKewoJaGFsZiBHID0gdVNyY1RGX1N0YWdlMFswXTsKCWhhbGYgQSA9IHVTcmNURl9TdGFnZTBbMV07CgloYWxmIEIgPSB1U3JjVEZfU3RhZ2UwWzJdOwoJaGFsZiBDID0gdVNyY1RGX1N0YWdlMFszXTsKCWhhbGYgRCA9IHVTcmNURl9TdGFnZTBbNF07CgloYWxmIEUgPSB1U3JjVEZfU3RhZ2UwWzVdOwoJaGFsZiBGID0gdVNyY1RGX1N0YWdlMFs2XTsKCWhhbGYgcyA9IHNpZ24oeCk7Cgl4ID0gYWJzKHgpOwoJeCA9ICh4IDwgRCkgPyAoQyAqIHgpICsgRiA6IHBvdyhBICogeCArIEIsIEcpICsgRTsKCXJldHVybiBzICogeDsKfQpoYWxmIGRzdF90Zl9TdGFnZTAoaGFsZiB4KSAKewoJaGFsZiBHID0gdURzdFRGX1N0YWdlMFswXTsKCWhhbGYgQSA9IHVEc3RURl9TdGFnZTBbMV07CgloYWxmIEIgPSB1RHN0VEZfU3RhZ2UwWzJdOwoJaGFsZiBDID0gdURzdFRGX1N0YWdlMFszXTsKCWhhbGYgRCA9IHVEc3RURl9TdGFnZTBbNF07CgloYWxmIEUgPSB1RHN0VEZfU3RhZ2UwWzVdOwoJaGFsZiBGID0gdURzdFRGX1N0YWdlMFs2XTsKCWhhbGYgcyA9IHNpZ24oeCk7Cgl4ID0gYWJzKHgpOwoJeCA9ICh4IDwgRCkgPyAoQyAqIHgpICsgRiA6IHBvdyhBICogeCArIEIsIEcpICsgRTsKCXJldHVybiBzICogeDsKfQpoYWxmNCBnYW11dF94Zm9ybV9TdGFnZTAoaGFsZjQgY29sb3IpIAp7Cgljb2xvci5yZ2IgPSAodUNvbG9yWGZvcm1fU3RhZ2UwICogY29sb3IucmdiKTsKCXJldHVybiBjb2xvcjsKfQpoYWxmNCBjb2xvcl94Zm9ybV9TdGFnZTAoZmxvYXQ0IGNvbG9yKSAKewoJY29sb3IuciA9IHNyY190Zl9TdGFnZTAoaGFsZihjb2xvci5yKSk7Cgljb2xvci5nID0gc3JjX3RmX1N0YWdlMChoYWxmKGNvbG9yLmcpKTsKCWNvbG9yLmIgPSBzcmNfdGZfU3RhZ2UwKGhhbGYoY29sb3IuYikpOwoJY29sb3IgPSBnYW11dF94Zm9ybV9TdGFnZTAoaGFsZjQoY29sb3IpKTsKCWNvbG9yLnIgPSBkc3RfdGZfU3RhZ2UwKGhhbGYoY29sb3IucikpOwoJY29sb3IuZyA9IGRzdF90Zl9TdGFnZTAoaGFsZihjb2xvci5nKSk7Cgljb2xvci5iID0gZHN0X3RmX1N0YWdlMChoYWxmKGNvbG9yLmIpKTsKCXJldHVybiBoYWxmNChjb2xvcik7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfU3RhZ2UwID0gaGFsZjQoMSk7CglmbG9hdDIgdGV4Q29vcmQ7Cgl0ZXhDb29yZCA9IHZsb2NhbENvb3JkX1N0YWdlMDsKCW91dHB1dENvbG9yX1N0YWdlMCA9ICgoY29sb3JfeGZvcm1fU3RhZ2UwKHNhbXBsZSh1VGV4dHVyZVNhbXBsZXJfMF9TdGFnZTAsIHRleENvb3JkKSkgKiBoYWxmNCgxKSkpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfU3RhZ2UwID0gaGFsZjQoMSk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfU3RhZ2UwICogb3V0cHV0Q292ZXJhZ2VfU3RhZ2UwOwoJfQp9CgAAAAAAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","GFQQAAAAMAAGGADDACRQAAAAMAACHQDCABRQAI7CAMAAAABAA2JAOAAACAAAAAAAIACQAAAAEAAAAAAAEERQAAAAAA":"BAAAAExTS1PfCwAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0NCByYWRpaV9zZWxlY3RvcjsKaW4gZmxvYXQ0IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHM7CmluIGZsb2F0NCBhYV9ibG9hdF9hbmRfY292ZXJhZ2U7CmluIGZsb2F0NCBza2V3OwppbiBmbG9hdDIgdHJhbnNsYXRlOwppbiBmbG9hdDQgcmFkaWlfeDsKaW4gZmxvYXQ0IHJhZGlpX3k7CmluIGhhbGY0IGNvbG9yOwpmbGF0IG91dCBoYWxmNCB2Y29sb3JfU3RhZ2UwOwpvdXQgZmxvYXQyIHZhcmNjb29yZF9TdGFnZTA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIEdyRmlsbFJSZWN0T3A6OlByb2Nlc3NvcgoJdmNvbG9yX1N0YWdlMCA9IGNvbG9yOwoJZmxvYXQgYWFfYmxvYXRfbXVsdGlwbGllciA9IDE7CglmbG9hdDIgY29ybmVyID0gY29ybmVyX2FuZF9yYWRpdXNfb3V0c2V0cy54eTsKCWZsb2F0MiByYWRpdXNfb3V0c2V0ID0gY29ybmVyX2FuZF9yYWRpdXNfb3V0c2V0cy56dzsKCWZsb2F0MiBhYV9ibG9hdF9kaXJlY3Rpb24gPSBhYV9ibG9hdF9hbmRfY292ZXJhZ2UueHk7CglmbG9hdCBpc19saW5lYXJfY292ZXJhZ2UgPSBhYV9ibG9hdF9hbmRfY292ZXJhZ2UudzsKCWZsb2F0MiBwaXhlbGxlbmd0aCA9IGludmVyc2VzcXJ0KGZsb2F0Mihkb3Qoc2tldy54eiwgc2tldy54eiksIGRvdChza2V3Lnl3LCBza2V3Lnl3KSkpOwoJZmxvYXQ0IG5vcm1hbGl6ZWRfYXhpc19kaXJzID0gc2tldyAqIHBpeGVsbGVuZ3RoLnh5eHk7CglmbG9hdDIgYXhpc3dpZHRocyA9IChhYnMobm9ybWFsaXplZF9heGlzX2RpcnMueHkpICsgYWJzKG5vcm1hbGl6ZWRfYXhpc19kaXJzLnp3KSk7CglmbG9hdDIgYWFfYmxvYXRyYWRpdXMgPSBheGlzd2lkdGhzICogcGl4ZWxsZW5ndGggKiAuNTsKCWZsb2F0NCByYWRpaV9hbmRfbmVpZ2hib3JzID0gcmFkaWlfc2VsZWN0b3IqIGZsb2F0NHg0KHJhZGlpX3gsIHJhZGlpX3ksIHJhZGlpX3gueXh3eiwgcmFkaWlfeS53enl4KTsKCWZsb2F0MiByYWRpaSA9IHJhZGlpX2FuZF9uZWlnaGJvcnMueHk7CglmbG9hdDIgbmVpZ2hib3JfcmFkaWkgPSByYWRpaV9hbmRfbmVpZ2hib3JzLnp3OwoJZmxvYXQgY292ZXJhZ2VfbXVsdGlwbGllciA9IDE7CglpZiAoYW55KGdyZWF0ZXJUaGFuKGFhX2Jsb2F0cmFkaXVzLCBmbG9hdDIoMSkpKSkgCgl7CgkJY29ybmVyID0gbWF4KGFicyhjb3JuZXIpLCBhYV9ibG9hdHJhZGl1cykgKiBzaWduKGNvcm5lcik7CgkJY292ZXJhZ2VfbXVsdGlwbGllciA9IDEgLyAobWF4KGFhX2Jsb2F0cmFkaXVzLngsIDEpICogbWF4KGFhX2Jsb2F0cmFkaXVzLnksIDEpKTsKCQlyYWRpaSA9IGZsb2F0MigwKTsKCX0KCWZsb2F0IGNvdmVyYWdlID0gYWFfYmxvYXRfYW5kX2NvdmVyYWdlLno7CglpZiAoYW55KGxlc3NUaGFuKHJhZGlpLCBhYV9ibG9hdHJhZGl1cyAqIDEuNSkpKSAKCXsKCQlyYWRpaSA9IGZsb2F0MigwKTsKCQlhYV9ibG9hdF9kaXJlY3Rpb24gPSBzaWduKGNvcm5lcik7CgkJaWYgKGNvdmVyYWdlID4gLjUpIAoJCXsKCQkJYWFfYmxvYXRfZGlyZWN0aW9uID0gLWFhX2Jsb2F0X2RpcmVjdGlvbjsKCQl9CgkJaXNfbGluZWFyX2NvdmVyYWdlID0gMTsKCX0KCWVsc2UgCgl7CgkJcmFkaWkgPSBjbGFtcChyYWRpaSwgcGl4ZWxsZW5ndGggKiAxLjUsIDIgLSBwaXhlbGxlbmd0aCAqIDEuNSk7CgkJbmVpZ2hib3JfcmFkaWkgPSBjbGFtcChuZWlnaGJvcl9yYWRpaSwgcGl4ZWxsZW5ndGggKiAxLjUsIDIgLSBwaXhlbGxlbmd0aCAqIDEuNSk7CgkJZmxvYXQyIHNwYWNpbmcgPSAyIC0gcmFkaWkgLSBuZWlnaGJvcl9yYWRpaTsKCQlmbG9hdDIgZXh0cmFfcGFkID0gbWF4KHBpeGVsbGVuZ3RoICogLjA2MjUgLSBzcGFjaW5nLCBmbG9hdDIoMCkpOwoJCXJhZGlpIC09IGV4dHJhX3BhZCAqIC41OwoJfQoJZmxvYXQyIGFhX291dHNldCA9IGFhX2Jsb2F0X2RpcmVjdGlvbiAqIGFhX2Jsb2F0cmFkaXVzICogYWFfYmxvYXRfbXVsdGlwbGllcjsKCWZsb2F0MiB2ZXJ0ZXhwb3MgPSBjb3JuZXIgKyByYWRpdXNfb3V0c2V0ICogcmFkaWkgKyBhYV9vdXRzZXQ7CglpZiAoY292ZXJhZ2UgPiAuNSkgCgl7CgkJaWYgKGFhX2Jsb2F0X2RpcmVjdGlvbi54ICE9IDAgJiYgdmVydGV4cG9zLnggKiBjb3JuZXIueCA8IDApIAoJCXsKCQkJZmxvYXQgYmFja3NldCA9IGFicyh2ZXJ0ZXhwb3MueCk7CgkJCXZlcnRleHBvcy54ID0gMDsKCQkJdmVydGV4cG9zLnkgKz0gYmFja3NldCAqIHNpZ24oY29ybmVyLnkpICogcGl4ZWxsZW5ndGgueS9waXhlbGxlbmd0aC54OwoJCQljb3ZlcmFnZSA9IChjb3ZlcmFnZSAtIC41KSAqIGFicyhjb3JuZXIueCkgLyAoYWJzKGNvcm5lci54KSArIGJhY2tzZXQpICsgLjU7CgkJfQoJCWlmIChhYV9ibG9hdF9kaXJlY3Rpb24ueSAhPSAwICYmIHZlcnRleHBvcy55ICogY29ybmVyLnkgPCAwKSAKCQl7CgkJCWZsb2F0IGJhY2tzZXQgPSBhYnModmVydGV4cG9zLnkpOwoJCQl2ZXJ0ZXhwb3MueSA9IDA7CgkJCXZlcnRleHBvcy54ICs9IGJhY2tzZXQgKiBzaWduKGNvcm5lci54KSAqIHBpeGVsbGVuZ3RoLngvcGl4ZWxsZW5ndGgueTsKCQkJY292ZXJhZ2UgPSAoY292ZXJhZ2UgLSAuNSkgKiBhYnMoY29ybmVyLnkpIC8gKGFicyhjb3JuZXIueSkgKyBiYWNrc2V0KSArIC41OwoJCX0KCX0KCWZsb2F0MngyIHNrZXdtYXRyaXggPSBmbG9hdDJ4Mihza2V3Lnh5LCBza2V3Lnp3KTsKCWZsb2F0MiBkZXZjb29yZCA9IHZlcnRleHBvcyAqIHNrZXdtYXRyaXggKyB0cmFuc2xhdGU7CglpZiAoMCAhPSBpc19saW5lYXJfY292ZXJhZ2UpIAoJewoJCXZhcmNjb29yZF9TdGFnZTAueHkgPSBmbG9hdDIoMCwgY292ZXJhZ2UgKiBjb3ZlcmFnZV9tdWx0aXBsaWVyKTsKCX0KCWVsc2UgCgl7CgkJZmxvYXQyIGFyY2Nvb3JkID0gMSAtIGFicyhyYWRpdXNfb3V0c2V0KSArIGFhX291dHNldC9yYWRpaSAqIGNvcm5lcjsKCQl2YXJjY29vcmRfU3RhZ2UwLnh5ID0gZmxvYXQyKGFyY2Nvb3JkLngrMSwgYXJjY29vcmQueSk7Cgl9Cglza19Qb3NpdGlvbiA9IGZsb2F0NChkZXZjb29yZC54ICwgZGV2Y29vcmQueSwgMCwgMSk7Cn0KAAABAQAAAAAAAAEBAFMEAAB1bmlmb3JtIGZsb2F0NCB1aW5uZXJSZWN0X1N0YWdlMV9jMDsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfU3RhZ2UxX2MwOwpmbGF0IGluIGhhbGY0IHZjb2xvcl9TdGFnZTA7CmluIGZsb2F0MiB2YXJjY29vcmRfU3RhZ2UwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBDaXJjdWxhclJSZWN0X1N0YWdlMV9jMChoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgZHh5MCA9IHVpbm5lclJlY3RfU3RhZ2UxX2MwLkxUIC0gc2tfRnJhZ0Nvb3JkLnh5OwoJZmxvYXQyIGR4eTEgPSBza19GcmFnQ29vcmQueHkgLSB1aW5uZXJSZWN0X1N0YWdlMV9jMC5SQjsKCWZsb2F0MiBkeHkgPSBtYXgobWF4KGR4eTAsIGR4eTEpLCAwLjApOwoJaGFsZiBhbHBoYSA9IGhhbGYoc2F0dXJhdGUodXJhZGl1c1BsdXNIYWxmX1N0YWdlMV9jMC54IC0gbGVuZ3RoKGR4eSkpKTsKCXJldHVybiBfaW5wdXQgKiBhbHBoYTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgR3JGaWxsUlJlY3RPcDo6UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TdGFnZTA7CglvdXRwdXRDb2xvcl9TdGFnZTAgPSB2Y29sb3JfU3RhZ2UwOwoJZmxvYXQgeF9wbHVzXzE9dmFyY2Nvb3JkX1N0YWdlMC54LCB5PXZhcmNjb29yZF9TdGFnZTAueTsKCWhhbGYgY292ZXJhZ2U7CglpZiAoMCA9PSB4X3BsdXNfMSkgCgl7CgkJY292ZXJhZ2UgPSBoYWxmKHkpOwoJfQoJZWxzZSAKCXsKCQlmbG9hdCBmbiA9IHhfcGx1c18xICogKHhfcGx1c18xIC0gMik7CgkJZm4gPSBmbWEoeSx5LCBmbik7CgkJZmxvYXQgZm53aWR0aCA9IGZ3aWR0aChmbik7CgkJY292ZXJhZ2UgPSAuNSAtIGhhbGYoZm4vZm53aWR0aCk7CgkJY292ZXJhZ2UgPSBjbGFtcChjb3ZlcmFnZSwgMCwgMSk7Cgl9CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TdGFnZTAgPSBoYWxmNChjb3ZlcmFnZSk7CgloYWxmNCBvdXRwdXRfU3RhZ2UxOwoJb3V0cHV0X1N0YWdlMSA9IENpcmN1bGFyUlJlY3RfU3RhZ2UxX2MwKG91dHB1dENvdmVyYWdlX1N0YWdlMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfU3RhZ2UwICogb3V0cHV0X1N0YWdlMTsKCX0KfQoAAAEBAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAgAAAAOAAAAcmFkaWlfc2VsZWN0b3IAABkAAABjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzAAAAFQAAAGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZQAAAAQAAABza2V3CQAAAHRyYW5zbGF0ZQAAAAcAAAByYWRpaV94AAcAAAByYWRpaV95AAUAAABjb2xvcgAAAAEAAAAAAAAA","K4JAAAAAAAGAAAAAAIWP577774BSZ7X7777QCAAAAABAAAAAABBAMADCEB4QAAAAAEAAAAAAKQAAAAAAAIAAAAAQGIBAAAAA":"BAAAAExTS1MFAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdmxvY2FsQ29vcmRfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCXZsb2NhbENvb3JkX1N0YWdlMCA9IGxvY2FsQ29vcmQ7Cglza19Qb3NpdGlvbiA9IGZsb2F0NChwb3NpdGlvbi54ICwgcG9zaXRpb24ueSwgMCwgMSk7Cn0KAAAAAAEBAAAAAAAAAQEApwMAAHVuaWZvcm0gZmxvYXQ0IHVpbm5lclJlY3RfU3RhZ2UxX2MwOwp1bmlmb3JtIGhhbGYyIHVyYWRpdXNQbHVzSGFsZl9TdGFnZTFfYzA7CnVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1N0YWdlMDsKaW4gZmxvYXQyIHZsb2NhbENvb3JkX1N0YWdlMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgQ2lyY3VsYXJSUmVjdF9TdGFnZTFfYzAoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGR4eTAgPSB1aW5uZXJSZWN0X1N0YWdlMV9jMC5MVCAtIHNrX0ZyYWdDb29yZC54eTsKCWZsb2F0MiBkeHkxID0gc2tfRnJhZ0Nvb3JkLnh5IC0gdWlubmVyUmVjdF9TdGFnZTFfYzAuUkI7CglmbG9hdDIgZHh5ID0gbWF4KG1heChkeHkwLCBkeHkxKSwgMC4wKTsKCWhhbGYgYWxwaGEgPSBoYWxmKHNhdHVyYXRlKHVyYWRpdXNQbHVzSGFsZl9TdGFnZTFfYzAueCAtIGxlbmd0aChkeHkpKSk7CglyZXR1cm4gX2lucHV0ICogYWxwaGE7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfU3RhZ2UwID0gaGFsZjQoMSk7CglmbG9hdDIgdGV4Q29vcmQ7Cgl0ZXhDb29yZCA9IHZsb2NhbENvb3JkX1N0YWdlMDsKCW91dHB1dENvbG9yX1N0YWdlMCA9ICgoc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1N0YWdlMCwgdGV4Q29vcmQpICogaGFsZjQoMSkpKTsKCWNvbnN0IGhhbGY0IG91dHB1dENvdmVyYWdlX1N0YWdlMCA9IGhhbGY0KDEpOwoJaGFsZjQgb3V0cHV0X1N0YWdlMTsKCW91dHB1dF9TdGFnZTEgPSBDaXJjdWxhclJSZWN0X1N0YWdlMV9jMChvdXRwdXRDb3ZlcmFnZV9TdGFnZTApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1N0YWdlMCAqIG91dHB1dF9TdGFnZTE7Cgl9Cn0KAAABAQABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAACAAAACAAAAHBvc2l0aW9uCgAAAGxvY2FsQ29vcmQAAAEAAAAAAAAA","DIQAAAAAMAAAAABAYAROFA2CAIAAAABAAAAAAAAAAAACABUSA4AAAEAAAAAAAQAFAAAAAIAAAAAAAIJDAAAA":"BAAAAExTS1NQAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQyIHVBdGxhc1NpemVJbnZfU3RhZ2UwOwppbiBmbG9hdDIgaW5Qb3NpdGlvbjsKaW4gaGFsZjQgaW5Db2xvcjsKaW4gdXNob3J0MiBpblRleHR1cmVDb29yZHM7Cm91dCBmbG9hdDIgdlRleHR1cmVDb29yZHNfU3RhZ2UwOwpmbGF0IG91dCBmbG9hdCB2VGV4SW5kZXhfU3RhZ2UwOwpvdXQgaGFsZjQgdmluQ29sb3JfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBCaXRtYXBUZXh0CglpbnQgdGV4SWR4ID0gMDsKCWZsb2F0MiB1bm9ybVRleENvb3JkcyA9IGZsb2F0MihpblRleHR1cmVDb29yZHMueCwgaW5UZXh0dXJlQ29vcmRzLnkpOwoJdlRleHR1cmVDb29yZHNfU3RhZ2UwID0gdW5vcm1UZXhDb29yZHMgKiB1QXRsYXNTaXplSW52X1N0YWdlMDsKCXZUZXhJbmRleF9TdGFnZTAgPSBmbG9hdCh0ZXhJZHgpOwoJdmluQ29sb3JfU3RhZ2UwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247Cglza19Qb3NpdGlvbiA9IGZsb2F0NChpblBvc2l0aW9uLnggLCBpblBvc2l0aW9uLnksIDAsIDEpOwp9CgABAQAAAAAAAAEBAMUDAAB1bmlmb3JtIGZsb2F0NCB1aW5uZXJSZWN0X1N0YWdlMV9jMDsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfU3RhZ2UxX2MwOwp1bmlmb3JtIHNhbXBsZXIyRCB1VGV4dHVyZVNhbXBsZXJfMF9TdGFnZTA7CmluIGZsb2F0MiB2VGV4dHVyZUNvb3Jkc19TdGFnZTA7CmZsYXQgaW4gZmxvYXQgdlRleEluZGV4X1N0YWdlMDsKaW4gaGFsZjQgdmluQ29sb3JfU3RhZ2UwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBDaXJjdWxhclJSZWN0X1N0YWdlMV9jMChoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgZHh5MCA9IHVpbm5lclJlY3RfU3RhZ2UxX2MwLkxUIC0gc2tfRnJhZ0Nvb3JkLnh5OwoJZmxvYXQyIGR4eTEgPSBza19GcmFnQ29vcmQueHkgLSB1aW5uZXJSZWN0X1N0YWdlMV9jMC5SQjsKCWZsb2F0MiBkeHkgPSBtYXgobWF4KGR4eTAsIGR4eTEpLCAwLjApOwoJaGFsZiBhbHBoYSA9IGhhbGYoc2F0dXJhdGUodXJhZGl1c1BsdXNIYWxmX1N0YWdlMV9jMC54IC0gbGVuZ3RoKGR4eSkpKTsKCXJldHVybiBfaW5wdXQgKiBhbHBoYTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgQml0bWFwVGV4dAoJaGFsZjQgb3V0cHV0Q29sb3JfU3RhZ2UwOwoJb3V0cHV0Q29sb3JfU3RhZ2UwID0gdmluQ29sb3JfU3RhZ2UwOwoJaGFsZjQgdGV4Q29sb3I7Cgl7CgkJdGV4Q29sb3IgPSBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfU3RhZ2UwLCB2VGV4dHVyZUNvb3Jkc19TdGFnZTApLnJycnI7Cgl9CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TdGFnZTAgPSB0ZXhDb2xvcjsKCWhhbGY0IG91dHB1dF9TdGFnZTE7CglvdXRwdXRfU3RhZ2UxID0gQ2lyY3VsYXJSUmVjdF9TdGFnZTFfYzAob3V0cHV0Q292ZXJhZ2VfU3RhZ2UwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TdGFnZTAgKiBvdXRwdXRfU3RhZ2UxOwoJfQp9CgAAAAABAQABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAADAAAACgAAAGluUG9zaXRpb24AAAcAAABpbkNvbG9yAA8AAABpblRleHR1cmVDb29yZHMAAQAAAAAAAAA=","K5JAAAAAAAMAAAAAARMPZ72HPQCFR7H7777QGAAAAACAAAAAACCAYAEABIAAAACAAAAAAACCIYAAA":"BAAAAExTS1NLAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7CmluIGZsb2F0MiBsb2NhbENvb3JkOwpmbGF0IG91dCBoYWxmNCB2Y29sb3JfU3RhZ2UwOwpvdXQgZmxvYXQyIHZsb2NhbENvb3JkX1N0YWdlMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgl2Y29sb3JfU3RhZ2UwID0gY29sb3I7Cgl2bG9jYWxDb29yZF9TdGFnZTAgPSBsb2NhbENvb3JkOwoJc2tfUG9zaXRpb24gPSBmbG9hdDQocG9zaXRpb24ueCAsIHBvc2l0aW9uLnksIDAsIDEpOwp9CgAAAAAAAAAAAAAAAAAcAgAAdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfU3RhZ2UwOwpmbGF0IGluIGhhbGY0IHZjb2xvcl9TdGFnZTA7CmluIGZsb2F0MiB2bG9jYWxDb29yZF9TdGFnZTA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1N0YWdlMDsKCW91dHB1dENvbG9yX1N0YWdlMCA9IHZjb2xvcl9TdGFnZTA7CglmbG9hdDIgdGV4Q29vcmQ7Cgl0ZXhDb29yZCA9IHZsb2NhbENvb3JkX1N0YWdlMDsKCW91dHB1dENvbG9yX1N0YWdlMCA9ICgoc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1N0YWdlMCwgdGV4Q29vcmQpICogb3V0cHV0Q29sb3JfU3RhZ2UwKSk7Cgljb25zdCBoYWxmNCBvdXRwdXRDb3ZlcmFnZV9TdGFnZTAgPSBoYWxmNCgxKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TdGFnZTAgKiBvdXRwdXRDb3ZlcmFnZV9TdGFnZTA7Cgl9Cn0KAAAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAMAAAAIAAAAcG9zaXRpb24FAAAAY29sb3IAAAAKAAAAbG9jYWxDb29yZAAAAQAAAAAAAAA=","K4IAAAAAAAGAAAAAAIWP577774BSZ7X7777QCAAAAAAAAAAAVIRQYAAAAAAQAAQAAAAGJCABAAACAACAAAAABAGOAQAAQAAAABQCVAEQAGAAAAAAAAAAAVAAAAAAAAQAAAABAMQCAAAAA":"BAAAAExTS1NjAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TdGFnZTFfYzA7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzBfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCXNrX1Bvc2l0aW9uID0gZmxvYXQ0KHBvc2l0aW9uLnggLCBwb3NpdGlvbi55LCAwLCAxKTsKCXsKCQl2VHJhbnNmb3JtZWRDb29yZHNfMF9TdGFnZTAgPSAoKCh1bWF0cml4X1N0YWdlMV9jMCkpICogbG9jYWxDb29yZC54eTEpLnh5OwoJfQp9CgAAAAAAAAAAAAAAAAAcFAAAdW5pZm9ybSBmbG9hdDN4MyB1bWF0cml4X1N0YWdlMV9jMDsKdW5pZm9ybSBoYWxmMiB1XzBfSW5jcmVtZW50X1N0YWdlMV9jMF9jMDsKdW5pZm9ybSBoYWxmNCB1XzFfS2VybmVsX1N0YWdlMV9jMF9jMFs3XTsKdW5pZm9ybSBmbG9hdDN4MyB1bWF0cml4X1N0YWdlMV9jMF9jMF9jMDsKdW5pZm9ybSBmbG9hdDQgdWNsYW1wX1N0YWdlMV9jMF9jMF9jMF9jMDsKdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfU3RhZ2UxOwppbiBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzBfU3RhZ2UwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBUZXh0dXJlRWZmZWN0X1N0YWdlMV9jMF9jMF9jMF9jMChoYWxmNCBfaW5wdXQsIGZsb2F0MiBfY29vcmRzKSAKewoJZmxvYXQyIGluQ29vcmQgPSBfY29vcmRzOwoJZmxvYXQyIHN1YnNldENvb3JkOwoJc3Vic2V0Q29vcmQueCA9IGluQ29vcmQueDsKCXN1YnNldENvb3JkLnkgPSBpbkNvb3JkLnk7CglmbG9hdDIgY2xhbXBlZENvb3JkOwoJY2xhbXBlZENvb3JkLnggPSBjbGFtcChzdWJzZXRDb29yZC54LCB1Y2xhbXBfU3RhZ2UxX2MwX2MwX2MwX2MwLngsIHVjbGFtcF9TdGFnZTFfYzBfYzBfYzBfYzAueik7CgljbGFtcGVkQ29vcmQueSA9IHN1YnNldENvb3JkLnk7CgloYWxmNCB0ZXh0dXJlQ29sb3IgPSBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfU3RhZ2UxLCBjbGFtcGVkQ29vcmQpOwoJcmV0dXJuIHRleHR1cmVDb2xvcjsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKGhhbGY0IF9pbnB1dCwgZmxvYXQyIF9jb29yZHMpIAp7CglyZXR1cm4gVGV4dHVyZUVmZmVjdF9TdGFnZTFfYzBfYzBfYzBfYzAoX2lucHV0LCAoKHVtYXRyaXhfU3RhZ2UxX2MwX2MwX2MwKSAqIF9jb29yZHMueHkxKS54eSk7Cn0KaGFsZjQgR2F1c3NpYW5Db252b2x1dGlvbl9TdGFnZTFfYzBfYzAoaGFsZjQgX2lucHV0KSAKewoJaGFsZjQgXzJfY29sb3IgPSBoYWxmNCgwLjApOwoJZmxvYXQyIF8zX2Nvb3JkID0gKHZUcmFuc2Zvcm1lZENvb3Jkc18wX1N0YWdlMCAtIGZsb2F0MigoMTIuMCAqIHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwX2MwKSkpOwoJKF8yX2NvbG9yICs9IChNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKF9pbnB1dCwgXzNfY29vcmQpICogdV8xX0tlcm5lbF9TdGFnZTFfYzBfYzBbMF0ueCkpOwoJKF8zX2Nvb3JkICs9IGZsb2F0Mih1XzBfSW5jcmVtZW50X1N0YWdlMV9jMF9jMCkpOwoJKF8yX2NvbG9yICs9IChNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKF9pbnB1dCwgXzNfY29vcmQpICogdV8xX0tlcm5lbF9TdGFnZTFfYzBfYzBbMF0ueSkpOwoJKF8zX2Nvb3JkICs9IGZsb2F0Mih1XzBfSW5jcmVtZW50X1N0YWdlMV9jMF9jMCkpOwoJKF8yX2NvbG9yICs9IChNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKF9pbnB1dCwgXzNfY29vcmQpICogdV8xX0tlcm5lbF9TdGFnZTFfYzBfYzBbMF0ueikpOwoJKF8zX2Nvb3JkICs9IGZsb2F0Mih1XzBfSW5jcmVtZW50X1N0YWdlMV9jMF9jMCkpOwoJKF8yX2NvbG9yICs9IChNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKF9pbnB1dCwgXzNfY29vcmQpICogdV8xX0tlcm5lbF9TdGFnZTFfYzBfYzBbMF0udykpOwoJKF8zX2Nvb3JkICs9IGZsb2F0Mih1XzBfSW5jcmVtZW50X1N0YWdlMV9jMF9jMCkpOwoJKF8yX2NvbG9yICs9IChNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKF9pbnB1dCwgXzNfY29vcmQpICogdV8xX0tlcm5lbF9TdGFnZTFfYzBfYzBbMV0ueCkpOwoJKF8zX2Nvb3JkICs9IGZsb2F0Mih1XzBfSW5jcmVtZW50X1N0YWdlMV9jMF9jMCkpOwoJKF8yX2NvbG9yICs9IChNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKF9pbnB1dCwgXzNfY29vcmQpICogdV8xX0tlcm5lbF9TdGFnZTFfYzBfYzBbMV0ueSkpOwoJKF8zX2Nvb3JkICs9IGZsb2F0Mih1XzBfSW5jcmVtZW50X1N0YWdlMV9jMF9jMCkpOwoJKF8yX2NvbG9yICs9IChNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKF9pbnB1dCwgXzNfY29vcmQpICogdV8xX0tlcm5lbF9TdGFnZTFfYzBfYzBbMV0ueikpOwoJKF8zX2Nvb3JkICs9IGZsb2F0Mih1XzBfSW5jcmVtZW50X1N0YWdlMV9jMF9jMCkpOwoJKF8yX2NvbG9yICs9IChNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKF9pbnB1dCwgXzNfY29vcmQpICogdV8xX0tlcm5lbF9TdGFnZTFfYzBfYzBbMV0udykpOwoJKF8zX2Nvb3JkICs9IGZsb2F0Mih1XzBfSW5jcmVtZW50X1N0YWdlMV9jMF9jMCkpOwoJKF8yX2NvbG9yICs9IChNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKF9pbnB1dCwgXzNfY29vcmQpICogdV8xX0tlcm5lbF9TdGFnZTFfYzBfYzBbMl0ueCkpOwoJKF8zX2Nvb3JkICs9IGZsb2F0Mih1XzBfSW5jcmVtZW50X1N0YWdlMV9jMF9jMCkpOwoJKF8yX2NvbG9yICs9IChNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKF9pbnB1dCwgXzNfY29vcmQpICogdV8xX0tlcm5lbF9TdGFnZTFfYzBfYzBbMl0ueSkpOwoJKF8zX2Nvb3JkICs9IGZsb2F0Mih1XzBfSW5jcmVtZW50X1N0YWdlMV9jMF9jMCkpOwoJKF8yX2NvbG9yICs9IChNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKF9pbnB1dCwgXzNfY29vcmQpICogdV8xX0tlcm5lbF9TdGFnZTFfYzBfYzBbMl0ueikpOwoJKF8zX2Nvb3JkICs9IGZsb2F0Mih1XzBfSW5jcmVtZW50X1N0YWdlMV9jMF9jMCkpOwoJKF8yX2NvbG9yICs9IChNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKF9pbnB1dCwgXzNfY29vcmQpICogdV8xX0tlcm5lbF9TdGFnZTFfYzBfYzBbMl0udykpOwoJKF8zX2Nvb3JkICs9IGZsb2F0Mih1XzBfSW5jcmVtZW50X1N0YWdlMV9jMF9jMCkpOwoJKF8yX2NvbG9yICs9IChNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKF9pbnB1dCwgXzNfY29vcmQpICogdV8xX0tlcm5lbF9TdGFnZTFfYzBfYzBbM10ueCkpOwoJKF8zX2Nvb3JkICs9IGZsb2F0Mih1XzBfSW5jcmVtZW50X1N0YWdlMV9jMF9jMCkpOwoJKF8yX2NvbG9yICs9IChNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKF9pbnB1dCwgXzNfY29vcmQpICogdV8xX0tlcm5lbF9TdGFnZTFfYzBfYzBbM10ueSkpOwoJKF8zX2Nvb3JkICs9IGZsb2F0Mih1XzBfSW5jcmVtZW50X1N0YWdlMV9jMF9jMCkpOwoJKF8yX2NvbG9yICs9IChNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKF9pbnB1dCwgXzNfY29vcmQpICogdV8xX0tlcm5lbF9TdGFnZTFfYzBfYzBbM10ueikpOwoJKF8zX2Nvb3JkICs9IGZsb2F0Mih1XzBfSW5jcmVtZW50X1N0YWdlMV9jMF9jMCkpOwoJKF8yX2NvbG9yICs9IChNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKF9pbnB1dCwgXzNfY29vcmQpICogdV8xX0tlcm5lbF9TdGFnZTFfYzBfYzBbM10udykpOwoJKF8zX2Nvb3JkICs9IGZsb2F0Mih1XzBfSW5jcmVtZW50X1N0YWdlMV9jMF9jMCkpOwoJKF8yX2NvbG9yICs9IChNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKF9pbnB1dCwgXzNfY29vcmQpICogdV8xX0tlcm5lbF9TdGFnZTFfYzBfYzBbNF0ueCkpOwoJKF8zX2Nvb3JkICs9IGZsb2F0Mih1XzBfSW5jcmVtZW50X1N0YWdlMV9jMF9jMCkpOwoJKF8yX2NvbG9yICs9IChNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKF9pbnB1dCwgXzNfY29vcmQpICogdV8xX0tlcm5lbF9TdGFnZTFfYzBfYzBbNF0ueSkpOwoJKF8zX2Nvb3JkICs9IGZsb2F0Mih1XzBfSW5jcmVtZW50X1N0YWdlMV9jMF9jMCkpOwoJKF8yX2NvbG9yICs9IChNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKF9pbnB1dCwgXzNfY29vcmQpICogdV8xX0tlcm5lbF9TdGFnZTFfYzBfYzBbNF0ueikpOwoJKF8zX2Nvb3JkICs9IGZsb2F0Mih1XzBfSW5jcmVtZW50X1N0YWdlMV9jMF9jMCkpOwoJKF8yX2NvbG9yICs9IChNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKF9pbnB1dCwgXzNfY29vcmQpICogdV8xX0tlcm5lbF9TdGFnZTFfYzBfYzBbNF0udykpOwoJKF8zX2Nvb3JkICs9IGZsb2F0Mih1XzBfSW5jcmVtZW50X1N0YWdlMV9jMF9jMCkpOwoJKF8yX2NvbG9yICs9IChNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKF9pbnB1dCwgXzNfY29vcmQpICogdV8xX0tlcm5lbF9TdGFnZTFfYzBfYzBbNV0ueCkpOwoJKF8zX2Nvb3JkICs9IGZsb2F0Mih1XzBfSW5jcmVtZW50X1N0YWdlMV9jMF9jMCkpOwoJKF8yX2NvbG9yICs9IChNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKF9pbnB1dCwgXzNfY29vcmQpICogdV8xX0tlcm5lbF9TdGFnZTFfYzBfYzBbNV0ueSkpOwoJKF8zX2Nvb3JkICs9IGZsb2F0Mih1XzBfSW5jcmVtZW50X1N0YWdlMV9jMF9jMCkpOwoJKF8yX2NvbG9yICs9IChNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKF9pbnB1dCwgXzNfY29vcmQpICogdV8xX0tlcm5lbF9TdGFnZTFfYzBfYzBbNV0ueikpOwoJKF8zX2Nvb3JkICs9IGZsb2F0Mih1XzBfSW5jcmVtZW50X1N0YWdlMV9jMF9jMCkpOwoJKF8yX2NvbG9yICs9IChNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKF9pbnB1dCwgXzNfY29vcmQpICogdV8xX0tlcm5lbF9TdGFnZTFfYzBfYzBbNV0udykpOwoJKF8zX2Nvb3JkICs9IGZsb2F0Mih1XzBfSW5jcmVtZW50X1N0YWdlMV9jMF9jMCkpOwoJKF8yX2NvbG9yICs9IChNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKF9pbnB1dCwgXzNfY29vcmQpICogdV8xX0tlcm5lbF9TdGFnZTFfYzBfYzBbNl0ueCkpOwoJcmV0dXJuIF8yX2NvbG9yOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TdGFnZTFfYzAoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIEdhdXNzaWFuQ29udm9sdXRpb25fU3RhZ2UxX2MwX2MwKF9pbnB1dCk7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfU3RhZ2UwID0gaGFsZjQoMSk7Cgljb25zdCBoYWxmNCBvdXRwdXRDb3ZlcmFnZV9TdGFnZTAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TdGFnZTE7CglvdXRwdXRfU3RhZ2UxID0gTWF0cml4RWZmZWN0X1N0YWdlMV9jMChvdXRwdXRDb2xvcl9TdGFnZTApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dF9TdGFnZTEgKiBvdXRwdXRDb3ZlcmFnZV9TdGFnZTA7Cgl9Cn0KAAAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24KAAAAbG9jYWxDb29yZAAAAQAAAAAAAAA=","K5IACAAAAAMAAAAAARMAAVCEPQCFR7H7777QGAAAAAAAAAAAWRDQB4AA6AAPAAHQDQAAAAAAEIFQAAAADQGQAAAAQCHAIAAAADMBAAAAAAABKAAAABAACAAAACCIYAAA":"BAAAAExTS1MiAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TdGFnZTFfYzBfYzBfYzA7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQgY292ZXJhZ2U7CmluIGhhbGY0IGNvbG9yOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKZmxhdCBvdXQgaGFsZjQgdmNvbG9yX1N0YWdlMDsKb3V0IGZsb2F0IHZjb3ZlcmFnZV9TdGFnZTA7Cm91dCBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzBfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCWZsb2F0MiBwb3NpdGlvbiA9IHBvc2l0aW9uLnh5OwoJdmNvbG9yX1N0YWdlMCA9IGNvbG9yOwoJdmNvdmVyYWdlX1N0YWdlMCA9IGNvdmVyYWdlOwoJc2tfUG9zaXRpb24gPSBmbG9hdDQocG9zaXRpb24ueCAsIHBvc2l0aW9uLnksIDAsIDEpOwoJewoJCXZUcmFuc2Zvcm1lZENvb3Jkc18wX1N0YWdlMCA9ICgoKHVtYXRyaXhfU3RhZ2UxX2MwX2MwX2MwKSkgKiBsb2NhbENvb3JkLnh5MSkueHk7Cgl9Cn0KAAAAAAAAAAAAAAAAAABIBwAAdW5pZm9ybSBoYWxmNCB1bGVmdEJvcmRlckNvbG9yX1N0YWdlMV9jMF9jMDsKdW5pZm9ybSBoYWxmNCB1cmlnaHRCb3JkZXJDb2xvcl9TdGFnZTFfYzBfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TdGFnZTFfYzBfYzBfYzA7CnVuaWZvcm0gaGFsZjQgdXN0YXJ0X1N0YWdlMV9jMF9jMF9jMTsKdW5pZm9ybSBoYWxmNCB1ZW5kX1N0YWdlMV9jMF9jMF9jMTsKZmxhdCBpbiBoYWxmNCB2Y29sb3JfU3RhZ2UwOwppbiBmbG9hdCB2Y292ZXJhZ2VfU3RhZ2UwOwppbiBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzBfU3RhZ2UwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBMaW5lYXJHcmFkaWVudExheW91dF9TdGFnZTFfYzBfYzBfYzBfYzAoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIGhhbGY0KGhhbGYodlRyYW5zZm9ybWVkQ29vcmRzXzBfU3RhZ2UwLngpICsgOS45OTk5OTk3NDczNzg3NTE2ZS0wNiwgMS4wLCAwLjAsIDAuMCk7Cn0KaGFsZjQgTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMF9jMChoYWxmNCBfaW5wdXQpIAp7CglyZXR1cm4gTGluZWFyR3JhZGllbnRMYXlvdXRfU3RhZ2UxX2MwX2MwX2MwX2MwKF9pbnB1dCk7Cn0KaGFsZjQgU2luZ2xlSW50ZXJ2YWxHcmFkaWVudENvbG9yaXplcl9TdGFnZTFfYzBfYzBfYzEoaGFsZjQgX2lucHV0LCBmbG9hdDIgX2Nvb3JkcykgCnsKCXJldHVybiBtaXgodXN0YXJ0X1N0YWdlMV9jMF9jMF9jMSwgdWVuZF9TdGFnZTFfYzBfYzBfYzEsIGhhbGYoX2Nvb3Jkcy54KSk7Cn0KaGFsZjQgQ2xhbXBlZEdyYWRpZW50RWZmZWN0X1N0YWdlMV9jMF9jMChoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCB0ID0gTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMF9jMChfaW5wdXQpOwoJaGFsZjQgb3V0Q29sb3I7CglpZiAoIXRydWUgJiYgdC55IDwgMC4wKSAKCXsKCQlvdXRDb2xvciA9IGhhbGY0KDAuMCk7Cgl9CgllbHNlIGlmICh0LnggPCAwLjApIAoJewoJCW91dENvbG9yID0gdWxlZnRCb3JkZXJDb2xvcl9TdGFnZTFfYzBfYzA7Cgl9CgllbHNlIGlmICh0LnggPiAxLjApIAoJewoJCW91dENvbG9yID0gdXJpZ2h0Qm9yZGVyQ29sb3JfU3RhZ2UxX2MwX2MwOwoJfQoJZWxzZSAKCXsKCQlvdXRDb2xvciA9IFNpbmdsZUludGVydmFsR3JhZGllbnRDb2xvcml6ZXJfU3RhZ2UxX2MwX2MwX2MxKF9pbnB1dCwgZmxvYXQyKGhhbGYyKHQueCwgMC4wKSkpOwoJfQoJQGlmICh0cnVlKSAKCXsKCQlvdXRDb2xvci54eXogKj0gb3V0Q29sb3IudzsKCX0KCXJldHVybiBvdXRDb2xvcjsKfQpoYWxmNCBPdmVycmlkZUlucHV0RnJhZ21lbnRQcm9jZXNzb3JfU3RhZ2UxX2MwKGhhbGY0IF9pbnB1dCkgCnsKCXJldHVybiBDbGFtcGVkR3JhZGllbnRFZmZlY3RfU3RhZ2UxX2MwX2MwKGZhbHNlID8gaGFsZjQoMCkgOiBoYWxmNCgxLjAwMDAwMCwgMS4wMDAwMDAsIDEuMDAwMDAwLCAxLjAwMDAwMCkpOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1N0YWdlMDsKCW91dHB1dENvbG9yX1N0YWdlMCA9IHZjb2xvcl9TdGFnZTA7CglmbG9hdCBjb3ZlcmFnZSA9IHZjb3ZlcmFnZV9TdGFnZTA7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TdGFnZTAgPSBoYWxmNChoYWxmKGNvdmVyYWdlKSk7CgloYWxmNCBvdXRwdXRfU3RhZ2UxOwoJb3V0cHV0X1N0YWdlMSA9IE92ZXJyaWRlSW5wdXRGcmFnbWVudFByb2Nlc3Nvcl9TdGFnZTFfYzAob3V0cHV0Q29sb3JfU3RhZ2UwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSAoaGFsZjQoMS4wKSAtIG91dHB1dF9TdGFnZTEpICogb3V0cHV0Q292ZXJhZ2VfU3RhZ2UwOwoJfQp9CgAAAAABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAEAAAACAAAAHBvc2l0aW9uCAAAAGNvdmVyYWdlBQAAAGNvbG9yAAAACgAAAGxvY2FsQ29vcmQAAAEAAAAAAAAA","K4IAAAAAAAGAAAAAAIWP577774BSZ7X7777QCAAAAAAAAAAAVIRQCAACAAAABGAIEBSAAIAAAAAAAAAACUAAAAEAAAAAAAEERQAAAAA":"BAAAAExTS1NjAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TdGFnZTFfYzA7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzBfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCXNrX1Bvc2l0aW9uID0gZmxvYXQ0KHBvc2l0aW9uLnggLCBwb3NpdGlvbi55LCAwLCAxKTsKCXsKCQl2VHJhbnNmb3JtZWRDb29yZHNfMF9TdGFnZTAgPSAoKCh1bWF0cml4X1N0YWdlMV9jMCkpICogbG9jYWxDb29yZC54eTEpLnh5OwoJfQp9CgAAAAAAAAAAAAAAAADsAwAAdW5pZm9ybSBmbG9hdDN4MyB1bWF0cml4X1N0YWdlMV9jMDsKdW5pZm9ybSBmbG9hdDQgdWNsYW1wX1N0YWdlMV9jMF9jMDsKdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfU3RhZ2UxOwppbiBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzBfU3RhZ2UwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBUZXh0dXJlRWZmZWN0X1N0YWdlMV9jMF9jMChoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgaW5Db29yZCA9IHZUcmFuc2Zvcm1lZENvb3Jkc18wX1N0YWdlMDsKCWZsb2F0MiBzdWJzZXRDb29yZDsKCXN1YnNldENvb3JkLnggPSBpbkNvb3JkLng7CglzdWJzZXRDb29yZC55ID0gaW5Db29yZC55OwoJZmxvYXQyIGNsYW1wZWRDb29yZDsKCWNsYW1wZWRDb29yZC54ID0gY2xhbXAoc3Vic2V0Q29vcmQueCwgdWNsYW1wX1N0YWdlMV9jMF9jMC54LCB1Y2xhbXBfU3RhZ2UxX2MwX2MwLnopOwoJY2xhbXBlZENvb3JkLnkgPSBzdWJzZXRDb29yZC55OwoJaGFsZjQgdGV4dHVyZUNvbG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1N0YWdlMSwgY2xhbXBlZENvb3JkKTsKCXJldHVybiB0ZXh0dXJlQ29sb3I7Cn0KaGFsZjQgTWF0cml4RWZmZWN0X1N0YWdlMV9jMChoYWxmNCBfaW5wdXQpIAp7CglyZXR1cm4gVGV4dHVyZUVmZmVjdF9TdGFnZTFfYzBfYzAoX2lucHV0KTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TdGFnZTAgPSBoYWxmNCgxKTsKCWNvbnN0IGhhbGY0IG91dHB1dENvdmVyYWdlX1N0YWdlMCA9IGhhbGY0KDEpOwoJaGFsZjQgb3V0cHV0X1N0YWdlMTsKCW91dHB1dF9TdGFnZTEgPSBNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwKG91dHB1dENvbG9yX1N0YWdlMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0X1N0YWdlMSAqIG91dHB1dENvdmVyYWdlX1N0YWdlMDsKCX0KfQoAAAAAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","BYIBQAAAAQAAAAABCYIR7777777QAAAAAAAAAAAAUABAAAAACAAAAAEASAIQAAAA":"BAAAAExTS1NnAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwpvdXQgaGFsZjQgdmNvbG9yX1N0YWdlMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgRGVmYXVsdEdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBjb2xvciA9IGluQ29sb3I7Cgl2Y29sb3JfU3RhZ2UwID0gY29sb3I7CglmbG9hdDIgX3RtcF8wX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJZmxvYXQyIF90bXBfMV9pblBvc2l0aW9uID0gaW5Qb3NpdGlvbjsKCXNrX1Bvc2l0aW9uID0gZmxvYXQ0KF90bXBfMF9pblBvc2l0aW9uLnggLCBfdG1wXzBfaW5Qb3NpdGlvbi55LCAwLCAxKTsKfQoAAAAAAAAAAAAAAAAAOgEAAGluIGhhbGY0IHZjb2xvcl9TdGFnZTA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBEZWZhdWx0R2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1N0YWdlMDsKCW91dHB1dENvbG9yX1N0YWdlMCA9IHZjb2xvcl9TdGFnZTA7Cgljb25zdCBoYWxmNCBvdXRwdXRDb3ZlcmFnZV9TdGFnZTAgPSBoYWxmNCgxKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TdGFnZTAgKiBvdXRwdXRDb3ZlcmFnZV9TdGFnZTA7Cgl9Cn0KAAAAAAAAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAgAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgABAAAAAAAAAA==","GFQQAAAAMAAGGADDACRQAAAAMAACHQDCABRQAI7CAMAAAABAC5JAAAAAAAABKAAAACAAAAAAACCIYAAAAAAA":"BAAAAExTS1PfCwAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0NCByYWRpaV9zZWxlY3RvcjsKaW4gZmxvYXQ0IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHM7CmluIGZsb2F0NCBhYV9ibG9hdF9hbmRfY292ZXJhZ2U7CmluIGZsb2F0NCBza2V3OwppbiBmbG9hdDIgdHJhbnNsYXRlOwppbiBmbG9hdDQgcmFkaWlfeDsKaW4gZmxvYXQ0IHJhZGlpX3k7CmluIGhhbGY0IGNvbG9yOwpmbGF0IG91dCBoYWxmNCB2Y29sb3JfU3RhZ2UwOwpvdXQgZmxvYXQyIHZhcmNjb29yZF9TdGFnZTA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIEdyRmlsbFJSZWN0T3A6OlByb2Nlc3NvcgoJdmNvbG9yX1N0YWdlMCA9IGNvbG9yOwoJZmxvYXQgYWFfYmxvYXRfbXVsdGlwbGllciA9IDE7CglmbG9hdDIgY29ybmVyID0gY29ybmVyX2FuZF9yYWRpdXNfb3V0c2V0cy54eTsKCWZsb2F0MiByYWRpdXNfb3V0c2V0ID0gY29ybmVyX2FuZF9yYWRpdXNfb3V0c2V0cy56dzsKCWZsb2F0MiBhYV9ibG9hdF9kaXJlY3Rpb24gPSBhYV9ibG9hdF9hbmRfY292ZXJhZ2UueHk7CglmbG9hdCBpc19saW5lYXJfY292ZXJhZ2UgPSBhYV9ibG9hdF9hbmRfY292ZXJhZ2UudzsKCWZsb2F0MiBwaXhlbGxlbmd0aCA9IGludmVyc2VzcXJ0KGZsb2F0Mihkb3Qoc2tldy54eiwgc2tldy54eiksIGRvdChza2V3Lnl3LCBza2V3Lnl3KSkpOwoJZmxvYXQ0IG5vcm1hbGl6ZWRfYXhpc19kaXJzID0gc2tldyAqIHBpeGVsbGVuZ3RoLnh5eHk7CglmbG9hdDIgYXhpc3dpZHRocyA9IChhYnMobm9ybWFsaXplZF9heGlzX2RpcnMueHkpICsgYWJzKG5vcm1hbGl6ZWRfYXhpc19kaXJzLnp3KSk7CglmbG9hdDIgYWFfYmxvYXRyYWRpdXMgPSBheGlzd2lkdGhzICogcGl4ZWxsZW5ndGggKiAuNTsKCWZsb2F0NCByYWRpaV9hbmRfbmVpZ2hib3JzID0gcmFkaWlfc2VsZWN0b3IqIGZsb2F0NHg0KHJhZGlpX3gsIHJhZGlpX3ksIHJhZGlpX3gueXh3eiwgcmFkaWlfeS53enl4KTsKCWZsb2F0MiByYWRpaSA9IHJhZGlpX2FuZF9uZWlnaGJvcnMueHk7CglmbG9hdDIgbmVpZ2hib3JfcmFkaWkgPSByYWRpaV9hbmRfbmVpZ2hib3JzLnp3OwoJZmxvYXQgY292ZXJhZ2VfbXVsdGlwbGllciA9IDE7CglpZiAoYW55KGdyZWF0ZXJUaGFuKGFhX2Jsb2F0cmFkaXVzLCBmbG9hdDIoMSkpKSkgCgl7CgkJY29ybmVyID0gbWF4KGFicyhjb3JuZXIpLCBhYV9ibG9hdHJhZGl1cykgKiBzaWduKGNvcm5lcik7CgkJY292ZXJhZ2VfbXVsdGlwbGllciA9IDEgLyAobWF4KGFhX2Jsb2F0cmFkaXVzLngsIDEpICogbWF4KGFhX2Jsb2F0cmFkaXVzLnksIDEpKTsKCQlyYWRpaSA9IGZsb2F0MigwKTsKCX0KCWZsb2F0IGNvdmVyYWdlID0gYWFfYmxvYXRfYW5kX2NvdmVyYWdlLno7CglpZiAoYW55KGxlc3NUaGFuKHJhZGlpLCBhYV9ibG9hdHJhZGl1cyAqIDEuNSkpKSAKCXsKCQlyYWRpaSA9IGZsb2F0MigwKTsKCQlhYV9ibG9hdF9kaXJlY3Rpb24gPSBzaWduKGNvcm5lcik7CgkJaWYgKGNvdmVyYWdlID4gLjUpIAoJCXsKCQkJYWFfYmxvYXRfZGlyZWN0aW9uID0gLWFhX2Jsb2F0X2RpcmVjdGlvbjsKCQl9CgkJaXNfbGluZWFyX2NvdmVyYWdlID0gMTsKCX0KCWVsc2UgCgl7CgkJcmFkaWkgPSBjbGFtcChyYWRpaSwgcGl4ZWxsZW5ndGggKiAxLjUsIDIgLSBwaXhlbGxlbmd0aCAqIDEuNSk7CgkJbmVpZ2hib3JfcmFkaWkgPSBjbGFtcChuZWlnaGJvcl9yYWRpaSwgcGl4ZWxsZW5ndGggKiAxLjUsIDIgLSBwaXhlbGxlbmd0aCAqIDEuNSk7CgkJZmxvYXQyIHNwYWNpbmcgPSAyIC0gcmFkaWkgLSBuZWlnaGJvcl9yYWRpaTsKCQlmbG9hdDIgZXh0cmFfcGFkID0gbWF4KHBpeGVsbGVuZ3RoICogLjA2MjUgLSBzcGFjaW5nLCBmbG9hdDIoMCkpOwoJCXJhZGlpIC09IGV4dHJhX3BhZCAqIC41OwoJfQoJZmxvYXQyIGFhX291dHNldCA9IGFhX2Jsb2F0X2RpcmVjdGlvbiAqIGFhX2Jsb2F0cmFkaXVzICogYWFfYmxvYXRfbXVsdGlwbGllcjsKCWZsb2F0MiB2ZXJ0ZXhwb3MgPSBjb3JuZXIgKyByYWRpdXNfb3V0c2V0ICogcmFkaWkgKyBhYV9vdXRzZXQ7CglpZiAoY292ZXJhZ2UgPiAuNSkgCgl7CgkJaWYgKGFhX2Jsb2F0X2RpcmVjdGlvbi54ICE9IDAgJiYgdmVydGV4cG9zLnggKiBjb3JuZXIueCA8IDApIAoJCXsKCQkJZmxvYXQgYmFja3NldCA9IGFicyh2ZXJ0ZXhwb3MueCk7CgkJCXZlcnRleHBvcy54ID0gMDsKCQkJdmVydGV4cG9zLnkgKz0gYmFja3NldCAqIHNpZ24oY29ybmVyLnkpICogcGl4ZWxsZW5ndGgueS9waXhlbGxlbmd0aC54OwoJCQljb3ZlcmFnZSA9IChjb3ZlcmFnZSAtIC41KSAqIGFicyhjb3JuZXIueCkgLyAoYWJzKGNvcm5lci54KSArIGJhY2tzZXQpICsgLjU7CgkJfQoJCWlmIChhYV9ibG9hdF9kaXJlY3Rpb24ueSAhPSAwICYmIHZlcnRleHBvcy55ICogY29ybmVyLnkgPCAwKSAKCQl7CgkJCWZsb2F0IGJhY2tzZXQgPSBhYnModmVydGV4cG9zLnkpOwoJCQl2ZXJ0ZXhwb3MueSA9IDA7CgkJCXZlcnRleHBvcy54ICs9IGJhY2tzZXQgKiBzaWduKGNvcm5lci54KSAqIHBpeGVsbGVuZ3RoLngvcGl4ZWxsZW5ndGgueTsKCQkJY292ZXJhZ2UgPSAoY292ZXJhZ2UgLSAuNSkgKiBhYnMoY29ybmVyLnkpIC8gKGFicyhjb3JuZXIueSkgKyBiYWNrc2V0KSArIC41OwoJCX0KCX0KCWZsb2F0MngyIHNrZXdtYXRyaXggPSBmbG9hdDJ4Mihza2V3Lnh5LCBza2V3Lnp3KTsKCWZsb2F0MiBkZXZjb29yZCA9IHZlcnRleHBvcyAqIHNrZXdtYXRyaXggKyB0cmFuc2xhdGU7CglpZiAoMCAhPSBpc19saW5lYXJfY292ZXJhZ2UpIAoJewoJCXZhcmNjb29yZF9TdGFnZTAueHkgPSBmbG9hdDIoMCwgY292ZXJhZ2UgKiBjb3ZlcmFnZV9tdWx0aXBsaWVyKTsKCX0KCWVsc2UgCgl7CgkJZmxvYXQyIGFyY2Nvb3JkID0gMSAtIGFicyhyYWRpdXNfb3V0c2V0KSArIGFhX291dHNldC9yYWRpaSAqIGNvcm5lcjsKCQl2YXJjY29vcmRfU3RhZ2UwLnh5ID0gZmxvYXQyKGFyY2Nvb3JkLngrMSwgYXJjY29vcmQueSk7Cgl9Cglza19Qb3NpdGlvbiA9IGZsb2F0NChkZXZjb29yZC54ICwgZGV2Y29vcmQueSwgMCwgMSk7Cn0KAAABAQAAAAAAAAEBAHsFAAB1bmlmb3JtIGZsb2F0NCB1cmVjdFVuaWZvcm1fU3RhZ2UxX2MwOwpmbGF0IGluIGhhbGY0IHZjb2xvcl9TdGFnZTA7CmluIGZsb2F0MiB2YXJjY29vcmRfU3RhZ2UwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBBQVJlY3RFZmZlY3RfU3RhZ2UxX2MwKGhhbGY0IF9pbnB1dCkgCnsKCWZsb2F0NCBwcmV2UmVjdCA9IGZsb2F0NCgtMS4wMDAwMDAsIC0xLjAwMDAwMCwgLTEuMDAwMDAwLCAtMS4wMDAwMDApOwoJaGFsZiBjb3ZlcmFnZTsKCUBzd2l0Y2ggKDEpIAoJewoJCWNhc2UgMDogICAgY2FzZSAyOiAgICAgICAgY292ZXJhZ2UgPSBoYWxmKGFsbChncmVhdGVyVGhhbihmbG9hdDQoc2tfRnJhZ0Nvb3JkLnh5LCB1cmVjdFVuaWZvcm1fU3RhZ2UxX2MwLnp3KSwgZmxvYXQ0KHVyZWN0VW5pZm9ybV9TdGFnZTFfYzAueHksIHNrX0ZyYWdDb29yZC54eSkpKSA/IDEgOiAwKTsKCQlicmVhazsKCQlkZWZhdWx0OiAgICAgICAgaGFsZjQgZGlzdHM0ID0gY2xhbXAoaGFsZjQoMS4wLCAxLjAsIC0xLjAsIC0xLjApICogaGFsZjQoc2tfRnJhZ0Nvb3JkLnh5eHkgLSB1cmVjdFVuaWZvcm1fU3RhZ2UxX2MwKSwgMC4wLCAxLjApOwoJCWhhbGYyIGRpc3RzMiA9IChkaXN0czQueHkgKyBkaXN0czQuencpIC0gMS4wOwoJCWNvdmVyYWdlID0gZGlzdHMyLnggKiBkaXN0czIueTsKCX0KCUBpZiAoMSA9PSAyIHx8IDEgPT0gMykgCgl7CgkJY292ZXJhZ2UgPSAxLjAgLSBjb3ZlcmFnZTsKCX0KCXJldHVybiBfaW5wdXQgKiBjb3ZlcmFnZTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgR3JGaWxsUlJlY3RPcDo6UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TdGFnZTA7CglvdXRwdXRDb2xvcl9TdGFnZTAgPSB2Y29sb3JfU3RhZ2UwOwoJZmxvYXQgeF9wbHVzXzE9dmFyY2Nvb3JkX1N0YWdlMC54LCB5PXZhcmNjb29yZF9TdGFnZTAueTsKCWhhbGYgY292ZXJhZ2U7CglpZiAoMCA9PSB4X3BsdXNfMSkgCgl7CgkJY292ZXJhZ2UgPSBoYWxmKHkpOwoJfQoJZWxzZSAKCXsKCQlmbG9hdCBmbiA9IHhfcGx1c18xICogKHhfcGx1c18xIC0gMik7CgkJZm4gPSBmbWEoeSx5LCBmbik7CgkJZmxvYXQgZm53aWR0aCA9IGZ3aWR0aChmbik7CgkJY292ZXJhZ2UgPSAuNSAtIGhhbGYoZm4vZm53aWR0aCk7CgkJY292ZXJhZ2UgPSBjbGFtcChjb3ZlcmFnZSwgMCwgMSk7Cgl9CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TdGFnZTAgPSBoYWxmNChjb3ZlcmFnZSk7CgloYWxmNCBvdXRwdXRfU3RhZ2UxOwoJb3V0cHV0X1N0YWdlMSA9IEFBUmVjdEVmZmVjdF9TdGFnZTFfYzAob3V0cHV0Q292ZXJhZ2VfU3RhZ2UwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TdGFnZTAgKiBvdXRwdXRfU3RhZ2UxOwoJfQp9CgAAAQEAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAACAAAAA4AAAByYWRpaV9zZWxlY3RvcgAAGQAAAGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHMAAAAVAAAAYWFfYmxvYXRfYW5kX2NvdmVyYWdlAAAABAAAAHNrZXcJAAAAdHJhbnNsYXRlAAAABwAAAHJhZGlpX3gABwAAAHJhZGlpX3kABQAAAGNvbG9yAAAAAQAAAAAAAAA=","K4QAAAAAAAGAAAAAAIWP57ZDH37P7777777QCAAAAAAAAAAAIACQAAAAEAAAAAAAEERQAAA":"BAAAAExTS1P0AAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7CmZsYXQgb3V0IGhhbGY0IHZjb2xvcl9TdGFnZTA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmNvbG9yX1N0YWdlMCA9IGNvbG9yOwoJc2tfUG9zaXRpb24gPSBmbG9hdDQocG9zaXRpb24ueCAsIHBvc2l0aW9uLnksIDAsIDEpOwp9CgAAAAAAAAAAAAAAAEUBAABmbGF0IGluIGhhbGY0IHZjb2xvcl9TdGFnZTA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1N0YWdlMDsKCW91dHB1dENvbG9yX1N0YWdlMCA9IHZjb2xvcl9TdGFnZTA7Cgljb25zdCBoYWxmNCBvdXRwdXRDb3ZlcmFnZV9TdGFnZTAgPSBoYWxmNCgxKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TdGFnZTAgKiBvdXRwdXRDb3ZlcmFnZV9TdGFnZTA7Cgl9Cn0KAAAAAAAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24FAAAAY29sb3IAAAABAAAAAAAAAA==","KYNQAAAABCYIR6AYYAAAAAAAAAAAAADIR4AOAAPAAHQADYBZAAAAAACECYAAAABYDIAAAAAADUEQAAAAWAQQAAAAAAVAAAAAAAAQAAAABAMQC":"BAAAAExTS1OgAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TdGFnZTA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TdGFnZTFfYzBfYzBfYzA7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiBmbG9hdDQgaW5RdWFkRWRnZTsKb3V0IGZsb2F0NCB2UXVhZEVkZ2VfU3RhZ2UwOwpvdXQgaGFsZjQgdmluQ29sb3JfU3RhZ2UwOwpvdXQgZmxvYXQyIHZUcmFuc2Zvcm1lZENvb3Jkc18wX1N0YWdlMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZEVkZ2UKCXZRdWFkRWRnZV9TdGFnZTAgPSBpblF1YWRFZGdlOwoJdmluQ29sb3JfU3RhZ2UwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfU3RhZ2UwLnh6ICogaW5Qb3NpdGlvbiArIHVsb2NhbE1hdHJpeF9TdGFnZTAueXc7Cglza19Qb3NpdGlvbiA9IGZsb2F0NChfdG1wXzBfaW5Qb3NpdGlvbi54ICwgX3RtcF8wX2luUG9zaXRpb24ueSwgMCwgMSk7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzBfU3RhZ2UwID0gKCgodW1hdHJpeF9TdGFnZTFfYzBfYzBfYzApKSAqIF90bXBfMV9pblBvc2l0aW9uLnh5MSkueHk7Cgl9Cn0KAAAAAAAAAAAAAAAAFwkAAHVuaWZvcm0gaGFsZjQgdWxlZnRCb3JkZXJDb2xvcl9TdGFnZTFfYzBfYzA7CnVuaWZvcm0gaGFsZjQgdXJpZ2h0Qm9yZGVyQ29sb3JfU3RhZ2UxX2MwX2MwOwp1bmlmb3JtIGZsb2F0M3gzIHVtYXRyaXhfU3RhZ2UxX2MwX2MwX2MwOwp1bmlmb3JtIGhhbGY0IHVzdGFydF9TdGFnZTFfYzBfYzBfYzE7CnVuaWZvcm0gaGFsZjQgdWVuZF9TdGFnZTFfYzBfYzBfYzE7CmluIGZsb2F0NCB2UXVhZEVkZ2VfU3RhZ2UwOwppbiBoYWxmNCB2aW5Db2xvcl9TdGFnZTA7CmluIGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMF9TdGFnZTA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IExpbmVhckdyYWRpZW50TGF5b3V0X1N0YWdlMV9jMF9jMF9jMF9jMChoYWxmNCBfaW5wdXQpIAp7CglyZXR1cm4gaGFsZjQoaGFsZih2VHJhbnNmb3JtZWRDb29yZHNfMF9TdGFnZTAueCkgKyA5Ljk5OTk5OTc0NzM3ODc1MTZlLTA2LCAxLjAsIDAuMCwgMC4wKTsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKGhhbGY0IF9pbnB1dCkgCnsKCXJldHVybiBMaW5lYXJHcmFkaWVudExheW91dF9TdGFnZTFfYzBfYzBfYzBfYzAoX2lucHV0KTsKfQpoYWxmNCBTaW5nbGVJbnRlcnZhbEdyYWRpZW50Q29sb3JpemVyX1N0YWdlMV9jMF9jMF9jMShoYWxmNCBfaW5wdXQsIGZsb2F0MiBfY29vcmRzKSAKewoJcmV0dXJuIG1peCh1c3RhcnRfU3RhZ2UxX2MwX2MwX2MxLCB1ZW5kX1N0YWdlMV9jMF9jMF9jMSwgaGFsZihfY29vcmRzLngpKTsKfQpoYWxmNCBDbGFtcGVkR3JhZGllbnRFZmZlY3RfU3RhZ2UxX2MwX2MwKGhhbGY0IF9pbnB1dCkgCnsKCWhhbGY0IHQgPSBNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKF9pbnB1dCk7CgloYWxmNCBvdXRDb2xvcjsKCWlmICghdHJ1ZSAmJiB0LnkgPCAwLjApIAoJewoJCW91dENvbG9yID0gaGFsZjQoMC4wKTsKCX0KCWVsc2UgaWYgKHQueCA8IDAuMCkgCgl7CgkJb3V0Q29sb3IgPSB1bGVmdEJvcmRlckNvbG9yX1N0YWdlMV9jMF9jMDsKCX0KCWVsc2UgaWYgKHQueCA+IDEuMCkgCgl7CgkJb3V0Q29sb3IgPSB1cmlnaHRCb3JkZXJDb2xvcl9TdGFnZTFfYzBfYzA7Cgl9CgllbHNlIAoJewoJCW91dENvbG9yID0gU2luZ2xlSW50ZXJ2YWxHcmFkaWVudENvbG9yaXplcl9TdGFnZTFfYzBfYzBfYzEoX2lucHV0LCBmbG9hdDIoaGFsZjIodC54LCAwLjApKSk7Cgl9CglAaWYgKHRydWUpIAoJewoJCW91dENvbG9yLnh5eiAqPSBvdXRDb2xvci53OwoJfQoJcmV0dXJuIG91dENvbG9yOwp9CmhhbGY0IE92ZXJyaWRlSW5wdXRGcmFnbWVudFByb2Nlc3Nvcl9TdGFnZTFfYzAoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIENsYW1wZWRHcmFkaWVudEVmZmVjdF9TdGFnZTFfYzBfYzAoZmFsc2UgPyBoYWxmNCgwKSA6IGhhbGY0KDEuMDAwMDAwLCAxLjAwMDAwMCwgMS4wMDAwMDAsIDEuMDAwMDAwKSk7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRFZGdlCgloYWxmNCBvdXRwdXRDb2xvcl9TdGFnZTA7CglvdXRwdXRDb2xvcl9TdGFnZTAgPSB2aW5Db2xvcl9TdGFnZTA7CgloYWxmIGVkZ2VBbHBoYTsKCWhhbGYyIGR1dmR4ID0gaGFsZjIoZEZkeCh2UXVhZEVkZ2VfU3RhZ2UwLnh5KSk7CgloYWxmMiBkdXZkeSA9IGhhbGYyKGRGZHkodlF1YWRFZGdlX1N0YWdlMC54eSkpOwoJaWYgKHZRdWFkRWRnZV9TdGFnZTAueiA+IDAuMCAmJiB2UXVhZEVkZ2VfU3RhZ2UwLncgPiAwLjApIAoJewoJCWVkZ2VBbHBoYSA9IGhhbGYobWluKG1pbih2UXVhZEVkZ2VfU3RhZ2UwLnosIHZRdWFkRWRnZV9TdGFnZTAudykgKyAwLjUsIDEuMCkpOwoJfQoJZWxzZSAKCXsKCQloYWxmMiBnRiA9IGhhbGYyKGhhbGYoMi4wKnZRdWFkRWRnZV9TdGFnZTAueCpkdXZkeC54IC0gZHV2ZHgueSksICAgICAgICAgICAgICAgICBoYWxmKDIuMCp2UXVhZEVkZ2VfU3RhZ2UwLngqZHV2ZHkueCAtIGR1dmR5LnkpKTsKCQllZGdlQWxwaGEgPSBoYWxmKHZRdWFkRWRnZV9TdGFnZTAueCp2UXVhZEVkZ2VfU3RhZ2UwLnggLSB2UXVhZEVkZ2VfU3RhZ2UwLnkpOwoJCWVkZ2VBbHBoYSA9IHNhdHVyYXRlKDAuNSAtIGVkZ2VBbHBoYSAvIGxlbmd0aChnRikpOwoJfQoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfU3RhZ2UwID0gaGFsZjQoZWRnZUFscGhhKTsKCWhhbGY0IG91dHB1dF9TdGFnZTE7CglvdXRwdXRfU3RhZ2UxID0gT3ZlcnJpZGVJbnB1dEZyYWdtZW50UHJvY2Vzc29yX1N0YWdlMV9jMChvdXRwdXRDb2xvcl9TdGFnZTApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dF9TdGFnZTEgKiBvdXRwdXRDb3ZlcmFnZV9TdGFnZTA7Cgl9Cn0KAAAAAAABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAADAAAACgAAAGluUG9zaXRpb24AAAcAAABpbkNvbG9yAAoAAABpblF1YWRFZGdlAAABAAAAAAAAAA==","AWAQGAAAQAAIXCEPAGGP777777777737AAAAAAAAAAAAAUABAAAAACAAAAAEBSAI":"BAAAAExTS1OzAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiBmbG9hdDQgaW5DaXJjbGVFZGdlOwpvdXQgZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfU3RhZ2UwOwpvdXQgaGFsZjQgdmluQ29sb3JfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TdGFnZTAgPSBpbkNpcmNsZUVkZ2U7Cgl2aW5Db2xvcl9TdGFnZTAgPSBpbkNvbG9yOwoJZmxvYXQyIF90bXBfMF9pblBvc2l0aW9uID0gaW5Qb3NpdGlvbjsKCWZsb2F0MiBfdG1wXzFfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247Cglza19Qb3NpdGlvbiA9IGZsb2F0NChfdG1wXzBfaW5Qb3NpdGlvbi54ICwgX3RtcF8wX2luUG9zaXRpb24ueSwgMCwgMSk7Cn0KAAAAAAAAAAAAAAAAALcCAABpbiBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TdGFnZTA7CmluIGhhbGY0IHZpbkNvbG9yX1N0YWdlMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIENpcmNsZUdlb21ldHJ5UHJvY2Vzc29yCglmbG9hdDQgY2lyY2xlRWRnZTsKCWNpcmNsZUVkZ2UgPSB2aW5DaXJjbGVFZGdlX1N0YWdlMDsKCWhhbGY0IG91dHB1dENvbG9yX1N0YWdlMDsKCW91dHB1dENvbG9yX1N0YWdlMCA9IHZpbkNvbG9yX1N0YWdlMDsKCWZsb2F0IGQgPSBsZW5ndGgoY2lyY2xlRWRnZS54eSk7CgloYWxmIGRpc3RhbmNlVG9PdXRlckVkZ2UgPSBoYWxmKGNpcmNsZUVkZ2UueiAqICgxLjAgLSBkKSk7CgloYWxmIGVkZ2VBbHBoYSA9IHNhdHVyYXRlKGRpc3RhbmNlVG9PdXRlckVkZ2UpOwoJaGFsZiBkaXN0YW5jZVRvSW5uZXJFZGdlID0gaGFsZihjaXJjbGVFZGdlLnogKiAoZCAtIGNpcmNsZUVkZ2UudykpOwoJaGFsZiBpbm5lckFscGhhID0gc2F0dXJhdGUoZGlzdGFuY2VUb0lubmVyRWRnZSk7CgllZGdlQWxwaGEgKj0gaW5uZXJBbHBoYTsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1N0YWdlMCA9IGhhbGY0KGVkZ2VBbHBoYSk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfU3RhZ2UwICogb3V0cHV0Q292ZXJhZ2VfU3RhZ2UwOwoJfQp9CgAAAAAAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAwAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAMAAAAaW5DaXJjbGVFZGdlAQAAAAAAAAA=","GFQQAAAAMAAGGADDACRQAAAAMAACHQDCABRQAI7CAMAAAABAA2JAOAAAKAAAAAGAQUKAAAAAABAAKAAAAAQAAAAAAAQSGAAA":"BAAAAExTS1PfCwAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0NCByYWRpaV9zZWxlY3RvcjsKaW4gZmxvYXQ0IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHM7CmluIGZsb2F0NCBhYV9ibG9hdF9hbmRfY292ZXJhZ2U7CmluIGZsb2F0NCBza2V3OwppbiBmbG9hdDIgdHJhbnNsYXRlOwppbiBmbG9hdDQgcmFkaWlfeDsKaW4gZmxvYXQ0IHJhZGlpX3k7CmluIGhhbGY0IGNvbG9yOwpmbGF0IG91dCBoYWxmNCB2Y29sb3JfU3RhZ2UwOwpvdXQgZmxvYXQyIHZhcmNjb29yZF9TdGFnZTA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIEdyRmlsbFJSZWN0T3A6OlByb2Nlc3NvcgoJdmNvbG9yX1N0YWdlMCA9IGNvbG9yOwoJZmxvYXQgYWFfYmxvYXRfbXVsdGlwbGllciA9IDE7CglmbG9hdDIgY29ybmVyID0gY29ybmVyX2FuZF9yYWRpdXNfb3V0c2V0cy54eTsKCWZsb2F0MiByYWRpdXNfb3V0c2V0ID0gY29ybmVyX2FuZF9yYWRpdXNfb3V0c2V0cy56dzsKCWZsb2F0MiBhYV9ibG9hdF9kaXJlY3Rpb24gPSBhYV9ibG9hdF9hbmRfY292ZXJhZ2UueHk7CglmbG9hdCBpc19saW5lYXJfY292ZXJhZ2UgPSBhYV9ibG9hdF9hbmRfY292ZXJhZ2UudzsKCWZsb2F0MiBwaXhlbGxlbmd0aCA9IGludmVyc2VzcXJ0KGZsb2F0Mihkb3Qoc2tldy54eiwgc2tldy54eiksIGRvdChza2V3Lnl3LCBza2V3Lnl3KSkpOwoJZmxvYXQ0IG5vcm1hbGl6ZWRfYXhpc19kaXJzID0gc2tldyAqIHBpeGVsbGVuZ3RoLnh5eHk7CglmbG9hdDIgYXhpc3dpZHRocyA9IChhYnMobm9ybWFsaXplZF9heGlzX2RpcnMueHkpICsgYWJzKG5vcm1hbGl6ZWRfYXhpc19kaXJzLnp3KSk7CglmbG9hdDIgYWFfYmxvYXRyYWRpdXMgPSBheGlzd2lkdGhzICogcGl4ZWxsZW5ndGggKiAuNTsKCWZsb2F0NCByYWRpaV9hbmRfbmVpZ2hib3JzID0gcmFkaWlfc2VsZWN0b3IqIGZsb2F0NHg0KHJhZGlpX3gsIHJhZGlpX3ksIHJhZGlpX3gueXh3eiwgcmFkaWlfeS53enl4KTsKCWZsb2F0MiByYWRpaSA9IHJhZGlpX2FuZF9uZWlnaGJvcnMueHk7CglmbG9hdDIgbmVpZ2hib3JfcmFkaWkgPSByYWRpaV9hbmRfbmVpZ2hib3JzLnp3OwoJZmxvYXQgY292ZXJhZ2VfbXVsdGlwbGllciA9IDE7CglpZiAoYW55KGdyZWF0ZXJUaGFuKGFhX2Jsb2F0cmFkaXVzLCBmbG9hdDIoMSkpKSkgCgl7CgkJY29ybmVyID0gbWF4KGFicyhjb3JuZXIpLCBhYV9ibG9hdHJhZGl1cykgKiBzaWduKGNvcm5lcik7CgkJY292ZXJhZ2VfbXVsdGlwbGllciA9IDEgLyAobWF4KGFhX2Jsb2F0cmFkaXVzLngsIDEpICogbWF4KGFhX2Jsb2F0cmFkaXVzLnksIDEpKTsKCQlyYWRpaSA9IGZsb2F0MigwKTsKCX0KCWZsb2F0IGNvdmVyYWdlID0gYWFfYmxvYXRfYW5kX2NvdmVyYWdlLno7CglpZiAoYW55KGxlc3NUaGFuKHJhZGlpLCBhYV9ibG9hdHJhZGl1cyAqIDEuNSkpKSAKCXsKCQlyYWRpaSA9IGZsb2F0MigwKTsKCQlhYV9ibG9hdF9kaXJlY3Rpb24gPSBzaWduKGNvcm5lcik7CgkJaWYgKGNvdmVyYWdlID4gLjUpIAoJCXsKCQkJYWFfYmxvYXRfZGlyZWN0aW9uID0gLWFhX2Jsb2F0X2RpcmVjdGlvbjsKCQl9CgkJaXNfbGluZWFyX2NvdmVyYWdlID0gMTsKCX0KCWVsc2UgCgl7CgkJcmFkaWkgPSBjbGFtcChyYWRpaSwgcGl4ZWxsZW5ndGggKiAxLjUsIDIgLSBwaXhlbGxlbmd0aCAqIDEuNSk7CgkJbmVpZ2hib3JfcmFkaWkgPSBjbGFtcChuZWlnaGJvcl9yYWRpaSwgcGl4ZWxsZW5ndGggKiAxLjUsIDIgLSBwaXhlbGxlbmd0aCAqIDEuNSk7CgkJZmxvYXQyIHNwYWNpbmcgPSAyIC0gcmFkaWkgLSBuZWlnaGJvcl9yYWRpaTsKCQlmbG9hdDIgZXh0cmFfcGFkID0gbWF4KHBpeGVsbGVuZ3RoICogLjA2MjUgLSBzcGFjaW5nLCBmbG9hdDIoMCkpOwoJCXJhZGlpIC09IGV4dHJhX3BhZCAqIC41OwoJfQoJZmxvYXQyIGFhX291dHNldCA9IGFhX2Jsb2F0X2RpcmVjdGlvbiAqIGFhX2Jsb2F0cmFkaXVzICogYWFfYmxvYXRfbXVsdGlwbGllcjsKCWZsb2F0MiB2ZXJ0ZXhwb3MgPSBjb3JuZXIgKyByYWRpdXNfb3V0c2V0ICogcmFkaWkgKyBhYV9vdXRzZXQ7CglpZiAoY292ZXJhZ2UgPiAuNSkgCgl7CgkJaWYgKGFhX2Jsb2F0X2RpcmVjdGlvbi54ICE9IDAgJiYgdmVydGV4cG9zLnggKiBjb3JuZXIueCA8IDApIAoJCXsKCQkJZmxvYXQgYmFja3NldCA9IGFicyh2ZXJ0ZXhwb3MueCk7CgkJCXZlcnRleHBvcy54ID0gMDsKCQkJdmVydGV4cG9zLnkgKz0gYmFja3NldCAqIHNpZ24oY29ybmVyLnkpICogcGl4ZWxsZW5ndGgueS9waXhlbGxlbmd0aC54OwoJCQljb3ZlcmFnZSA9IChjb3ZlcmFnZSAtIC41KSAqIGFicyhjb3JuZXIueCkgLyAoYWJzKGNvcm5lci54KSArIGJhY2tzZXQpICsgLjU7CgkJfQoJCWlmIChhYV9ibG9hdF9kaXJlY3Rpb24ueSAhPSAwICYmIHZlcnRleHBvcy55ICogY29ybmVyLnkgPCAwKSAKCQl7CgkJCWZsb2F0IGJhY2tzZXQgPSBhYnModmVydGV4cG9zLnkpOwoJCQl2ZXJ0ZXhwb3MueSA9IDA7CgkJCXZlcnRleHBvcy54ICs9IGJhY2tzZXQgKiBzaWduKGNvcm5lci54KSAqIHBpeGVsbGVuZ3RoLngvcGl4ZWxsZW5ndGgueTsKCQkJY292ZXJhZ2UgPSAoY292ZXJhZ2UgLSAuNSkgKiBhYnMoY29ybmVyLnkpIC8gKGFicyhjb3JuZXIueSkgKyBiYWNrc2V0KSArIC41OwoJCX0KCX0KCWZsb2F0MngyIHNrZXdtYXRyaXggPSBmbG9hdDJ4Mihza2V3Lnh5LCBza2V3Lnp3KTsKCWZsb2F0MiBkZXZjb29yZCA9IHZlcnRleHBvcyAqIHNrZXdtYXRyaXggKyB0cmFuc2xhdGU7CglpZiAoMCAhPSBpc19saW5lYXJfY292ZXJhZ2UpIAoJewoJCXZhcmNjb29yZF9TdGFnZTAueHkgPSBmbG9hdDIoMCwgY292ZXJhZ2UgKiBjb3ZlcmFnZV9tdWx0aXBsaWVyKTsKCX0KCWVsc2UgCgl7CgkJZmxvYXQyIGFyY2Nvb3JkID0gMSAtIGFicyhyYWRpdXNfb3V0c2V0KSArIGFhX291dHNldC9yYWRpaSAqIGNvcm5lcjsKCQl2YXJjY29vcmRfU3RhZ2UwLnh5ID0gZmxvYXQyKGFyY2Nvb3JkLngrMSwgYXJjY29vcmQueSk7Cgl9Cglza19Qb3NpdGlvbiA9IGZsb2F0NChkZXZjb29yZC54ICwgZGV2Y29vcmQueSwgMCwgMSk7Cn0KAAABAQAAAAAAAAEBACMHAAB1bmlmb3JtIGZsb2F0NCB1aW5uZXJSZWN0X1N0YWdlMV9jMDsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfU3RhZ2UxX2MwOwp1bmlmb3JtIGZsb2F0NCB1cmVjdFVuaWZvcm1fU3RhZ2UxX2MwX2MwOwpmbGF0IGluIGhhbGY0IHZjb2xvcl9TdGFnZTA7CmluIGZsb2F0MiB2YXJjY29vcmRfU3RhZ2UwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBBQVJlY3RFZmZlY3RfU3RhZ2UxX2MwX2MwKGhhbGY0IF9pbnB1dCkgCnsKCWZsb2F0NCBwcmV2UmVjdCA9IGZsb2F0NCgtMS4wMDAwMDAsIC0xLjAwMDAwMCwgLTEuMDAwMDAwLCAtMS4wMDAwMDApOwoJaGFsZiBjb3ZlcmFnZTsKCUBzd2l0Y2ggKDEpIAoJewoJCWNhc2UgMDogICAgY2FzZSAyOiAgICAgICAgY292ZXJhZ2UgPSBoYWxmKGFsbChncmVhdGVyVGhhbihmbG9hdDQoc2tfRnJhZ0Nvb3JkLnh5LCB1cmVjdFVuaWZvcm1fU3RhZ2UxX2MwX2MwLnp3KSwgZmxvYXQ0KHVyZWN0VW5pZm9ybV9TdGFnZTFfYzBfYzAueHksIHNrX0ZyYWdDb29yZC54eSkpKSA/IDEgOiAwKTsKCQlicmVhazsKCQlkZWZhdWx0OiAgICAgICAgaGFsZjQgZGlzdHM0ID0gY2xhbXAoaGFsZjQoMS4wLCAxLjAsIC0xLjAsIC0xLjApICogaGFsZjQoc2tfRnJhZ0Nvb3JkLnh5eHkgLSB1cmVjdFVuaWZvcm1fU3RhZ2UxX2MwX2MwKSwgMC4wLCAxLjApOwoJCWhhbGYyIGRpc3RzMiA9IChkaXN0czQueHkgKyBkaXN0czQuencpIC0gMS4wOwoJCWNvdmVyYWdlID0gZGlzdHMyLnggKiBkaXN0czIueTsKCX0KCUBpZiAoMSA9PSAyIHx8IDEgPT0gMykgCgl7CgkJY292ZXJhZ2UgPSAxLjAgLSBjb3ZlcmFnZTsKCX0KCXJldHVybiBfaW5wdXQgKiBjb3ZlcmFnZTsKfQpoYWxmNCBDaXJjdWxhclJSZWN0X1N0YWdlMV9jMChoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgZHh5MCA9IHVpbm5lclJlY3RfU3RhZ2UxX2MwLkxUIC0gc2tfRnJhZ0Nvb3JkLnh5OwoJZmxvYXQyIGR4eTEgPSBza19GcmFnQ29vcmQueHkgLSB1aW5uZXJSZWN0X1N0YWdlMV9jMC5SQjsKCWZsb2F0MiBkeHkgPSBtYXgobWF4KGR4eTAsIGR4eTEpLCAwLjApOwoJaGFsZiBhbHBoYSA9IGhhbGYoc2F0dXJhdGUodXJhZGl1c1BsdXNIYWxmX1N0YWdlMV9jMC54IC0gbGVuZ3RoKGR4eSkpKTsKCXJldHVybiBBQVJlY3RFZmZlY3RfU3RhZ2UxX2MwX2MwKF9pbnB1dCkgKiBhbHBoYTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgR3JGaWxsUlJlY3RPcDo6UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TdGFnZTA7CglvdXRwdXRDb2xvcl9TdGFnZTAgPSB2Y29sb3JfU3RhZ2UwOwoJZmxvYXQgeF9wbHVzXzE9dmFyY2Nvb3JkX1N0YWdlMC54LCB5PXZhcmNjb29yZF9TdGFnZTAueTsKCWhhbGYgY292ZXJhZ2U7CglpZiAoMCA9PSB4X3BsdXNfMSkgCgl7CgkJY292ZXJhZ2UgPSBoYWxmKHkpOwoJfQoJZWxzZSAKCXsKCQlmbG9hdCBmbiA9IHhfcGx1c18xICogKHhfcGx1c18xIC0gMik7CgkJZm4gPSBmbWEoeSx5LCBmbik7CgkJZmxvYXQgZm53aWR0aCA9IGZ3aWR0aChmbik7CgkJY292ZXJhZ2UgPSAuNSAtIGhhbGYoZm4vZm53aWR0aCk7CgkJY292ZXJhZ2UgPSBjbGFtcChjb3ZlcmFnZSwgMCwgMSk7Cgl9CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TdGFnZTAgPSBoYWxmNChjb3ZlcmFnZSk7CgloYWxmNCBvdXRwdXRfU3RhZ2UxOwoJb3V0cHV0X1N0YWdlMSA9IENpcmN1bGFyUlJlY3RfU3RhZ2UxX2MwKG91dHB1dENvdmVyYWdlX1N0YWdlMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfU3RhZ2UwICogb3V0cHV0X1N0YWdlMTsKCX0KfQoAAAEBAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAgAAAAOAAAAcmFkaWlfc2VsZWN0b3IAABkAAABjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzAAAAFQAAAGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZQAAAAQAAABza2V3CQAAAHRyYW5zbGF0ZQAAAAcAAAByYWRpaV94AAcAAAByYWRpaV95AAUAAABjb2xvcgAAAAEAAAAAAAAA","GFQQAAAAMAAGGADDACRQAAAAMAACHQDCABRQAI7CAMAAAABAAYJAMAAACAAAAAAAIACQAAAAEAAAAAAAEERQAAAAAA":"BAAAAExTS1PfCwAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0NCByYWRpaV9zZWxlY3RvcjsKaW4gZmxvYXQ0IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHM7CmluIGZsb2F0NCBhYV9ibG9hdF9hbmRfY292ZXJhZ2U7CmluIGZsb2F0NCBza2V3OwppbiBmbG9hdDIgdHJhbnNsYXRlOwppbiBmbG9hdDQgcmFkaWlfeDsKaW4gZmxvYXQ0IHJhZGlpX3k7CmluIGhhbGY0IGNvbG9yOwpmbGF0IG91dCBoYWxmNCB2Y29sb3JfU3RhZ2UwOwpvdXQgZmxvYXQyIHZhcmNjb29yZF9TdGFnZTA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIEdyRmlsbFJSZWN0T3A6OlByb2Nlc3NvcgoJdmNvbG9yX1N0YWdlMCA9IGNvbG9yOwoJZmxvYXQgYWFfYmxvYXRfbXVsdGlwbGllciA9IDE7CglmbG9hdDIgY29ybmVyID0gY29ybmVyX2FuZF9yYWRpdXNfb3V0c2V0cy54eTsKCWZsb2F0MiByYWRpdXNfb3V0c2V0ID0gY29ybmVyX2FuZF9yYWRpdXNfb3V0c2V0cy56dzsKCWZsb2F0MiBhYV9ibG9hdF9kaXJlY3Rpb24gPSBhYV9ibG9hdF9hbmRfY292ZXJhZ2UueHk7CglmbG9hdCBpc19saW5lYXJfY292ZXJhZ2UgPSBhYV9ibG9hdF9hbmRfY292ZXJhZ2UudzsKCWZsb2F0MiBwaXhlbGxlbmd0aCA9IGludmVyc2VzcXJ0KGZsb2F0Mihkb3Qoc2tldy54eiwgc2tldy54eiksIGRvdChza2V3Lnl3LCBza2V3Lnl3KSkpOwoJZmxvYXQ0IG5vcm1hbGl6ZWRfYXhpc19kaXJzID0gc2tldyAqIHBpeGVsbGVuZ3RoLnh5eHk7CglmbG9hdDIgYXhpc3dpZHRocyA9IChhYnMobm9ybWFsaXplZF9heGlzX2RpcnMueHkpICsgYWJzKG5vcm1hbGl6ZWRfYXhpc19kaXJzLnp3KSk7CglmbG9hdDIgYWFfYmxvYXRyYWRpdXMgPSBheGlzd2lkdGhzICogcGl4ZWxsZW5ndGggKiAuNTsKCWZsb2F0NCByYWRpaV9hbmRfbmVpZ2hib3JzID0gcmFkaWlfc2VsZWN0b3IqIGZsb2F0NHg0KHJhZGlpX3gsIHJhZGlpX3ksIHJhZGlpX3gueXh3eiwgcmFkaWlfeS53enl4KTsKCWZsb2F0MiByYWRpaSA9IHJhZGlpX2FuZF9uZWlnaGJvcnMueHk7CglmbG9hdDIgbmVpZ2hib3JfcmFkaWkgPSByYWRpaV9hbmRfbmVpZ2hib3JzLnp3OwoJZmxvYXQgY292ZXJhZ2VfbXVsdGlwbGllciA9IDE7CglpZiAoYW55KGdyZWF0ZXJUaGFuKGFhX2Jsb2F0cmFkaXVzLCBmbG9hdDIoMSkpKSkgCgl7CgkJY29ybmVyID0gbWF4KGFicyhjb3JuZXIpLCBhYV9ibG9hdHJhZGl1cykgKiBzaWduKGNvcm5lcik7CgkJY292ZXJhZ2VfbXVsdGlwbGllciA9IDEgLyAobWF4KGFhX2Jsb2F0cmFkaXVzLngsIDEpICogbWF4KGFhX2Jsb2F0cmFkaXVzLnksIDEpKTsKCQlyYWRpaSA9IGZsb2F0MigwKTsKCX0KCWZsb2F0IGNvdmVyYWdlID0gYWFfYmxvYXRfYW5kX2NvdmVyYWdlLno7CglpZiAoYW55KGxlc3NUaGFuKHJhZGlpLCBhYV9ibG9hdHJhZGl1cyAqIDEuNSkpKSAKCXsKCQlyYWRpaSA9IGZsb2F0MigwKTsKCQlhYV9ibG9hdF9kaXJlY3Rpb24gPSBzaWduKGNvcm5lcik7CgkJaWYgKGNvdmVyYWdlID4gLjUpIAoJCXsKCQkJYWFfYmxvYXRfZGlyZWN0aW9uID0gLWFhX2Jsb2F0X2RpcmVjdGlvbjsKCQl9CgkJaXNfbGluZWFyX2NvdmVyYWdlID0gMTsKCX0KCWVsc2UgCgl7CgkJcmFkaWkgPSBjbGFtcChyYWRpaSwgcGl4ZWxsZW5ndGggKiAxLjUsIDIgLSBwaXhlbGxlbmd0aCAqIDEuNSk7CgkJbmVpZ2hib3JfcmFkaWkgPSBjbGFtcChuZWlnaGJvcl9yYWRpaSwgcGl4ZWxsZW5ndGggKiAxLjUsIDIgLSBwaXhlbGxlbmd0aCAqIDEuNSk7CgkJZmxvYXQyIHNwYWNpbmcgPSAyIC0gcmFkaWkgLSBuZWlnaGJvcl9yYWRpaTsKCQlmbG9hdDIgZXh0cmFfcGFkID0gbWF4KHBpeGVsbGVuZ3RoICogLjA2MjUgLSBzcGFjaW5nLCBmbG9hdDIoMCkpOwoJCXJhZGlpIC09IGV4dHJhX3BhZCAqIC41OwoJfQoJZmxvYXQyIGFhX291dHNldCA9IGFhX2Jsb2F0X2RpcmVjdGlvbiAqIGFhX2Jsb2F0cmFkaXVzICogYWFfYmxvYXRfbXVsdGlwbGllcjsKCWZsb2F0MiB2ZXJ0ZXhwb3MgPSBjb3JuZXIgKyByYWRpdXNfb3V0c2V0ICogcmFkaWkgKyBhYV9vdXRzZXQ7CglpZiAoY292ZXJhZ2UgPiAuNSkgCgl7CgkJaWYgKGFhX2Jsb2F0X2RpcmVjdGlvbi54ICE9IDAgJiYgdmVydGV4cG9zLnggKiBjb3JuZXIueCA8IDApIAoJCXsKCQkJZmxvYXQgYmFja3NldCA9IGFicyh2ZXJ0ZXhwb3MueCk7CgkJCXZlcnRleHBvcy54ID0gMDsKCQkJdmVydGV4cG9zLnkgKz0gYmFja3NldCAqIHNpZ24oY29ybmVyLnkpICogcGl4ZWxsZW5ndGgueS9waXhlbGxlbmd0aC54OwoJCQljb3ZlcmFnZSA9IChjb3ZlcmFnZSAtIC41KSAqIGFicyhjb3JuZXIueCkgLyAoYWJzKGNvcm5lci54KSArIGJhY2tzZXQpICsgLjU7CgkJfQoJCWlmIChhYV9ibG9hdF9kaXJlY3Rpb24ueSAhPSAwICYmIHZlcnRleHBvcy55ICogY29ybmVyLnkgPCAwKSAKCQl7CgkJCWZsb2F0IGJhY2tzZXQgPSBhYnModmVydGV4cG9zLnkpOwoJCQl2ZXJ0ZXhwb3MueSA9IDA7CgkJCXZlcnRleHBvcy54ICs9IGJhY2tzZXQgKiBzaWduKGNvcm5lci54KSAqIHBpeGVsbGVuZ3RoLngvcGl4ZWxsZW5ndGgueTsKCQkJY292ZXJhZ2UgPSAoY292ZXJhZ2UgLSAuNSkgKiBhYnMoY29ybmVyLnkpIC8gKGFicyhjb3JuZXIueSkgKyBiYWNrc2V0KSArIC41OwoJCX0KCX0KCWZsb2F0MngyIHNrZXdtYXRyaXggPSBmbG9hdDJ4Mihza2V3Lnh5LCBza2V3Lnp3KTsKCWZsb2F0MiBkZXZjb29yZCA9IHZlcnRleHBvcyAqIHNrZXdtYXRyaXggKyB0cmFuc2xhdGU7CglpZiAoMCAhPSBpc19saW5lYXJfY292ZXJhZ2UpIAoJewoJCXZhcmNjb29yZF9TdGFnZTAueHkgPSBmbG9hdDIoMCwgY292ZXJhZ2UgKiBjb3ZlcmFnZV9tdWx0aXBsaWVyKTsKCX0KCWVsc2UgCgl7CgkJZmxvYXQyIGFyY2Nvb3JkID0gMSAtIGFicyhyYWRpdXNfb3V0c2V0KSArIGFhX291dHNldC9yYWRpaSAqIGNvcm5lcjsKCQl2YXJjY29vcmRfU3RhZ2UwLnh5ID0gZmxvYXQyKGFyY2Nvb3JkLngrMSwgYXJjY29vcmQueSk7Cgl9Cglza19Qb3NpdGlvbiA9IGZsb2F0NChkZXZjb29yZC54ICwgZGV2Y29vcmQueSwgMCwgMSk7Cn0KAAABAQAAAAAAAAEBALUEAAB1bmlmb3JtIGZsb2F0NCB1aW5uZXJSZWN0X1N0YWdlMV9jMDsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfU3RhZ2UxX2MwOwpmbGF0IGluIGhhbGY0IHZjb2xvcl9TdGFnZTA7CmluIGZsb2F0MiB2YXJjY29vcmRfU3RhZ2UwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBDaXJjdWxhclJSZWN0X1N0YWdlMV9jMChoYWxmNCBfaW5wdXQpIAp7CglmbG9hdCBkeDAgPSB1aW5uZXJSZWN0X1N0YWdlMV9jMC5MIC0gc2tfRnJhZ0Nvb3JkLng7CglmbG9hdDIgZHh5MSA9IHNrX0ZyYWdDb29yZC54eSAtIHVpbm5lclJlY3RfU3RhZ2UxX2MwLlJCOwoJZmxvYXQyIGR4eSA9IG1heChmbG9hdDIobWF4KGR4MCwgZHh5MS54KSwgZHh5MS55KSwgMC4wKTsKCWhhbGYgdG9wQWxwaGEgPSBoYWxmKHNhdHVyYXRlKHNrX0ZyYWdDb29yZC55IC0gdWlubmVyUmVjdF9TdGFnZTFfYzAuVCkpOwoJaGFsZiBhbHBoYSA9IHRvcEFscGhhICogaGFsZihzYXR1cmF0ZSh1cmFkaXVzUGx1c0hhbGZfU3RhZ2UxX2MwLnggLSBsZW5ndGgoZHh5KSkpOwoJcmV0dXJuIF9pbnB1dCAqIGFscGhhOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBHckZpbGxSUmVjdE9wOjpQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1N0YWdlMDsKCW91dHB1dENvbG9yX1N0YWdlMCA9IHZjb2xvcl9TdGFnZTA7CglmbG9hdCB4X3BsdXNfMT12YXJjY29vcmRfU3RhZ2UwLngsIHk9dmFyY2Nvb3JkX1N0YWdlMC55OwoJaGFsZiBjb3ZlcmFnZTsKCWlmICgwID09IHhfcGx1c18xKSAKCXsKCQljb3ZlcmFnZSA9IGhhbGYoeSk7Cgl9CgllbHNlIAoJewoJCWZsb2F0IGZuID0geF9wbHVzXzEgKiAoeF9wbHVzXzEgLSAyKTsKCQlmbiA9IGZtYSh5LHksIGZuKTsKCQlmbG9hdCBmbndpZHRoID0gZndpZHRoKGZuKTsKCQljb3ZlcmFnZSA9IC41IC0gaGFsZihmbi9mbndpZHRoKTsKCQljb3ZlcmFnZSA9IGNsYW1wKGNvdmVyYWdlLCAwLCAxKTsKCX0KCWhhbGY0IG91dHB1dENvdmVyYWdlX1N0YWdlMCA9IGhhbGY0KGNvdmVyYWdlKTsKCWhhbGY0IG91dHB1dF9TdGFnZTE7CglvdXRwdXRfU3RhZ2UxID0gQ2lyY3VsYXJSUmVjdF9TdGFnZTFfYzAob3V0cHV0Q292ZXJhZ2VfU3RhZ2UwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TdGFnZTAgKiBvdXRwdXRfU3RhZ2UxOwoJfQp9CgAAAAABAQABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAIAAAADgAAAHJhZGlpX3NlbGVjdG9yAAAZAAAAY29ybmVyX2FuZF9yYWRpdXNfb3V0c2V0cwAAABUAAABhYV9ibG9hdF9hbmRfY292ZXJhZ2UAAAAEAAAAc2tldwkAAAB0cmFuc2xhdGUAAAAHAAAAcmFkaWlfeAAHAAAAcmFkaWlfeQAFAAAAY29sb3IAAAABAAAAAAAAAA==","K4IAAAAAAAGAAAAAAIWP577774BSZ7X7777QCAAAAAAAAAAAVIRQCAICAAAAA5BIEBAAAAAAAAJQDBAMAACAIAAAAAAKAAQAAAABAAAAACAJACIAAAAA":"BAAAAExTS1OjAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TdGFnZTFfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TdGFnZTFfYzBfYzA7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzBfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCXNrX1Bvc2l0aW9uID0gZmxvYXQ0KHBvc2l0aW9uLnggLCBwb3NpdGlvbi55LCAwLCAxKTsKCXsKCQl2VHJhbnNmb3JtZWRDb29yZHNfMF9TdGFnZTAgPSAoKCh1bWF0cml4X1N0YWdlMV9jMF9jMCkgKiAodW1hdHJpeF9TdGFnZTFfYzApKSAqIGxvY2FsQ29vcmQueHkxKS54eTsKCX0KfQoAAAAAAAAAAAAAAAAAXQQAAHVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TdGFnZTFfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TdGFnZTFfYzBfYzA7CnVuaWZvcm0gZmxvYXQ0IHVjbGFtcF9TdGFnZTFfYzBfYzBfYzA7CnVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1N0YWdlMTsKaW4gZmxvYXQyIHZUcmFuc2Zvcm1lZENvb3Jkc18wX1N0YWdlMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgVGV4dHVyZUVmZmVjdF9TdGFnZTFfYzBfYzBfYzAoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGluQ29vcmQgPSB2VHJhbnNmb3JtZWRDb29yZHNfMF9TdGFnZTA7CglmbG9hdDIgc3Vic2V0Q29vcmQ7CglzdWJzZXRDb29yZC54ID0gaW5Db29yZC54OwoJc3Vic2V0Q29vcmQueSA9IGluQ29vcmQueTsKCWZsb2F0MiBjbGFtcGVkQ29vcmQ7CgljbGFtcGVkQ29vcmQgPSBjbGFtcChzdWJzZXRDb29yZCwgdWNsYW1wX1N0YWdlMV9jMF9jMF9jMC54eSwgdWNsYW1wX1N0YWdlMV9jMF9jMF9jMC56dyk7CgloYWxmNCB0ZXh0dXJlQ29sb3IgPSBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfU3RhZ2UxLCBjbGFtcGVkQ29vcmQpOwoJcmV0dXJuIHRleHR1cmVDb2xvcjsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwKGhhbGY0IF9pbnB1dCkgCnsKCXJldHVybiBUZXh0dXJlRWZmZWN0X1N0YWdlMV9jMF9jMF9jMChfaW5wdXQpOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TdGFnZTFfYzAoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzAoX2lucHV0KTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TdGFnZTAgPSBoYWxmNCgxKTsKCWNvbnN0IGhhbGY0IG91dHB1dENvdmVyYWdlX1N0YWdlMCA9IGhhbGY0KDEpOwoJaGFsZjQgb3V0cHV0X1N0YWdlMTsKCW91dHB1dF9TdGFnZTEgPSBNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwKG91dHB1dENvbG9yX1N0YWdlMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0X1N0YWdlMSAqIG91dHB1dENvdmVyYWdlX1N0YWdlMDsKCX0KfQoAAAAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","GFYQAAAAMAAGGADDACRQAAAAMAACHQDCABRQAI7CAMAAAAAAKQAAAAAAAIAAAAAQGIBAAAA":"BAAAAExTS1PfCwAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0NCByYWRpaV9zZWxlY3RvcjsKaW4gZmxvYXQ0IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHM7CmluIGZsb2F0NCBhYV9ibG9hdF9hbmRfY292ZXJhZ2U7CmluIGZsb2F0NCBza2V3OwppbiBmbG9hdDIgdHJhbnNsYXRlOwppbiBmbG9hdDQgcmFkaWlfeDsKaW4gZmxvYXQ0IHJhZGlpX3k7CmluIGhhbGY0IGNvbG9yOwpmbGF0IG91dCBoYWxmNCB2Y29sb3JfU3RhZ2UwOwpvdXQgZmxvYXQyIHZhcmNjb29yZF9TdGFnZTA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIEdyRmlsbFJSZWN0T3A6OlByb2Nlc3NvcgoJdmNvbG9yX1N0YWdlMCA9IGNvbG9yOwoJZmxvYXQgYWFfYmxvYXRfbXVsdGlwbGllciA9IDA7CglmbG9hdDIgY29ybmVyID0gY29ybmVyX2FuZF9yYWRpdXNfb3V0c2V0cy54eTsKCWZsb2F0MiByYWRpdXNfb3V0c2V0ID0gY29ybmVyX2FuZF9yYWRpdXNfb3V0c2V0cy56dzsKCWZsb2F0MiBhYV9ibG9hdF9kaXJlY3Rpb24gPSBhYV9ibG9hdF9hbmRfY292ZXJhZ2UueHk7CglmbG9hdCBpc19saW5lYXJfY292ZXJhZ2UgPSBhYV9ibG9hdF9hbmRfY292ZXJhZ2UudzsKCWZsb2F0MiBwaXhlbGxlbmd0aCA9IGludmVyc2VzcXJ0KGZsb2F0Mihkb3Qoc2tldy54eiwgc2tldy54eiksIGRvdChza2V3Lnl3LCBza2V3Lnl3KSkpOwoJZmxvYXQ0IG5vcm1hbGl6ZWRfYXhpc19kaXJzID0gc2tldyAqIHBpeGVsbGVuZ3RoLnh5eHk7CglmbG9hdDIgYXhpc3dpZHRocyA9IChhYnMobm9ybWFsaXplZF9heGlzX2RpcnMueHkpICsgYWJzKG5vcm1hbGl6ZWRfYXhpc19kaXJzLnp3KSk7CglmbG9hdDIgYWFfYmxvYXRyYWRpdXMgPSBheGlzd2lkdGhzICogcGl4ZWxsZW5ndGggKiAuNTsKCWZsb2F0NCByYWRpaV9hbmRfbmVpZ2hib3JzID0gcmFkaWlfc2VsZWN0b3IqIGZsb2F0NHg0KHJhZGlpX3gsIHJhZGlpX3ksIHJhZGlpX3gueXh3eiwgcmFkaWlfeS53enl4KTsKCWZsb2F0MiByYWRpaSA9IHJhZGlpX2FuZF9uZWlnaGJvcnMueHk7CglmbG9hdDIgbmVpZ2hib3JfcmFkaWkgPSByYWRpaV9hbmRfbmVpZ2hib3JzLnp3OwoJZmxvYXQgY292ZXJhZ2VfbXVsdGlwbGllciA9IDE7CglpZiAoYW55KGdyZWF0ZXJUaGFuKGFhX2Jsb2F0cmFkaXVzLCBmbG9hdDIoMSkpKSkgCgl7CgkJY29ybmVyID0gbWF4KGFicyhjb3JuZXIpLCBhYV9ibG9hdHJhZGl1cykgKiBzaWduKGNvcm5lcik7CgkJY292ZXJhZ2VfbXVsdGlwbGllciA9IDEgLyAobWF4KGFhX2Jsb2F0cmFkaXVzLngsIDEpICogbWF4KGFhX2Jsb2F0cmFkaXVzLnksIDEpKTsKCQlyYWRpaSA9IGZsb2F0MigwKTsKCX0KCWZsb2F0IGNvdmVyYWdlID0gYWFfYmxvYXRfYW5kX2NvdmVyYWdlLno7CglpZiAoYW55KGxlc3NUaGFuKHJhZGlpLCBhYV9ibG9hdHJhZGl1cyAqIDEuNSkpKSAKCXsKCQlyYWRpaSA9IGZsb2F0MigwKTsKCQlhYV9ibG9hdF9kaXJlY3Rpb24gPSBzaWduKGNvcm5lcik7CgkJaWYgKGNvdmVyYWdlID4gLjUpIAoJCXsKCQkJYWFfYmxvYXRfZGlyZWN0aW9uID0gLWFhX2Jsb2F0X2RpcmVjdGlvbjsKCQl9CgkJaXNfbGluZWFyX2NvdmVyYWdlID0gMTsKCX0KCWVsc2UgCgl7CgkJcmFkaWkgPSBjbGFtcChyYWRpaSwgcGl4ZWxsZW5ndGggKiAxLjUsIDIgLSBwaXhlbGxlbmd0aCAqIDEuNSk7CgkJbmVpZ2hib3JfcmFkaWkgPSBjbGFtcChuZWlnaGJvcl9yYWRpaSwgcGl4ZWxsZW5ndGggKiAxLjUsIDIgLSBwaXhlbGxlbmd0aCAqIDEuNSk7CgkJZmxvYXQyIHNwYWNpbmcgPSAyIC0gcmFkaWkgLSBuZWlnaGJvcl9yYWRpaTsKCQlmbG9hdDIgZXh0cmFfcGFkID0gbWF4KHBpeGVsbGVuZ3RoICogLjA2MjUgLSBzcGFjaW5nLCBmbG9hdDIoMCkpOwoJCXJhZGlpIC09IGV4dHJhX3BhZCAqIC41OwoJfQoJZmxvYXQyIGFhX291dHNldCA9IGFhX2Jsb2F0X2RpcmVjdGlvbiAqIGFhX2Jsb2F0cmFkaXVzICogYWFfYmxvYXRfbXVsdGlwbGllcjsKCWZsb2F0MiB2ZXJ0ZXhwb3MgPSBjb3JuZXIgKyByYWRpdXNfb3V0c2V0ICogcmFkaWkgKyBhYV9vdXRzZXQ7CglpZiAoY292ZXJhZ2UgPiAuNSkgCgl7CgkJaWYgKGFhX2Jsb2F0X2RpcmVjdGlvbi54ICE9IDAgJiYgdmVydGV4cG9zLnggKiBjb3JuZXIueCA8IDApIAoJCXsKCQkJZmxvYXQgYmFja3NldCA9IGFicyh2ZXJ0ZXhwb3MueCk7CgkJCXZlcnRleHBvcy54ID0gMDsKCQkJdmVydGV4cG9zLnkgKz0gYmFja3NldCAqIHNpZ24oY29ybmVyLnkpICogcGl4ZWxsZW5ndGgueS9waXhlbGxlbmd0aC54OwoJCQljb3ZlcmFnZSA9IChjb3ZlcmFnZSAtIC41KSAqIGFicyhjb3JuZXIueCkgLyAoYWJzKGNvcm5lci54KSArIGJhY2tzZXQpICsgLjU7CgkJfQoJCWlmIChhYV9ibG9hdF9kaXJlY3Rpb24ueSAhPSAwICYmIHZlcnRleHBvcy55ICogY29ybmVyLnkgPCAwKSAKCQl7CgkJCWZsb2F0IGJhY2tzZXQgPSBhYnModmVydGV4cG9zLnkpOwoJCQl2ZXJ0ZXhwb3MueSA9IDA7CgkJCXZlcnRleHBvcy54ICs9IGJhY2tzZXQgKiBzaWduKGNvcm5lci54KSAqIHBpeGVsbGVuZ3RoLngvcGl4ZWxsZW5ndGgueTsKCQkJY292ZXJhZ2UgPSAoY292ZXJhZ2UgLSAuNSkgKiBhYnMoY29ybmVyLnkpIC8gKGFicyhjb3JuZXIueSkgKyBiYWNrc2V0KSArIC41OwoJCX0KCX0KCWZsb2F0MngyIHNrZXdtYXRyaXggPSBmbG9hdDJ4Mihza2V3Lnh5LCBza2V3Lnp3KTsKCWZsb2F0MiBkZXZjb29yZCA9IHZlcnRleHBvcyAqIHNrZXdtYXRyaXggKyB0cmFuc2xhdGU7CglpZiAoMCAhPSBpc19saW5lYXJfY292ZXJhZ2UpIAoJewoJCXZhcmNjb29yZF9TdGFnZTAueHkgPSBmbG9hdDIoMCwgY292ZXJhZ2UgKiBjb3ZlcmFnZV9tdWx0aXBsaWVyKTsKCX0KCWVsc2UgCgl7CgkJZmxvYXQyIGFyY2Nvb3JkID0gMSAtIGFicyhyYWRpdXNfb3V0c2V0KSArIGFhX291dHNldC9yYWRpaSAqIGNvcm5lcjsKCQl2YXJjY29vcmRfU3RhZ2UwLnh5ID0gZmxvYXQyKGFyY2Nvb3JkLngrMSwgYXJjY29vcmQueSk7Cgl9Cglza19Qb3NpdGlvbiA9IGZsb2F0NChkZXZjb29yZC54ICwgZGV2Y29vcmQueSwgMCwgMSk7Cn0KAAAAAAAAAAAAAAAAAK0CAABmbGF0IGluIGhhbGY0IHZjb2xvcl9TdGFnZTA7CmluIGZsb2F0MiB2YXJjY29vcmRfU3RhZ2UwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgR3JGaWxsUlJlY3RPcDo6UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TdGFnZTA7CglvdXRwdXRDb2xvcl9TdGFnZTAgPSB2Y29sb3JfU3RhZ2UwOwoJZmxvYXQgeF9wbHVzXzE9dmFyY2Nvb3JkX1N0YWdlMC54LCB5PXZhcmNjb29yZF9TdGFnZTAueTsKCWhhbGYgY292ZXJhZ2U7CglpZiAoMCA9PSB4X3BsdXNfMSkgCgl7CgkJY292ZXJhZ2UgPSBoYWxmKHkpOwoJfQoJZWxzZSAKCXsKCQlmbG9hdCBmbiA9IHhfcGx1c18xICogKHhfcGx1c18xIC0gMik7CgkJZm4gPSBmbWEoeSx5LCBmbik7CgkJZmxvYXQgZm53aWR0aCA9IGZ3aWR0aChmbik7CgkJY292ZXJhZ2UgPSAuNSAtIGhhbGYoZm4vZm53aWR0aCk7CgkJY292ZXJhZ2UgPSBjbGFtcChjb3ZlcmFnZSwgMCwgMSk7Cgl9Cgljb3ZlcmFnZSA9IChjb3ZlcmFnZSA+PSAuNSkgPyAxIDogMDsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1N0YWdlMCA9IGhhbGY0KGNvdmVyYWdlKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TdGFnZTAgKiBvdXRwdXRDb3ZlcmFnZV9TdGFnZTA7Cgl9Cn0KAAAAAAAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAgAAAAOAAAAcmFkaWlfc2VsZWN0b3IAABkAAABjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzAAAAFQAAAGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZQAAAAQAAABza2V3CQAAAHRyYW5zbGF0ZQAAAAcAAAByYWRpaV94AAcAAAByYWRpaV95AAUAAABjb2xvcgAAAAEAAAAAAAAA","K4JAAAAAAAGAAAAAAIWP577774BSZ7X7777QCAAAAABAAAAAABBAMAHSEECQAAAAABIACAAAAAEAAAAAIDEAQAAAAA":"BAAAAExTS1MFAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdmxvY2FsQ29vcmRfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCXZsb2NhbENvb3JkX1N0YWdlMCA9IGxvY2FsQ29vcmQ7Cglza19Qb3NpdGlvbiA9IGZsb2F0NChwb3NpdGlvbi54ICwgcG9zaXRpb24ueSwgMCwgMSk7Cn0KAAAAAAEBAAAAAAAAAQEAZQQAAHVuaWZvcm0gZmxvYXQ0IHVjaXJjbGVfU3RhZ2UxX2MwOwp1bmlmb3JtIHNhbXBsZXIyRCB1VGV4dHVyZVNhbXBsZXJfMF9TdGFnZTA7CmluIGZsb2F0MiB2bG9jYWxDb29yZF9TdGFnZTA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IENpcmNsZUVmZmVjdF9TdGFnZTFfYzAoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIHByZXZDZW50ZXI7CglmbG9hdCBwcmV2UmFkaXVzID0gLTEuMDAwMDAwOwoJaGFsZiBkOwoJQGlmICgxID09IDIgfHwgMSA9PSAzKSAKCXsKCQlkID0gaGFsZigobGVuZ3RoKCh1Y2lyY2xlX1N0YWdlMV9jMC54eSAtIHNrX0ZyYWdDb29yZC54eSkgKiB1Y2lyY2xlX1N0YWdlMV9jMC53KSAtIDEuMCkgKiB1Y2lyY2xlX1N0YWdlMV9jMC56KTsKCX0KCWVsc2UgCgl7CgkJZCA9IGhhbGYoKDEuMCAtIGxlbmd0aCgodWNpcmNsZV9TdGFnZTFfYzAueHkgLSBza19GcmFnQ29vcmQueHkpICogdWNpcmNsZV9TdGFnZTFfYzAudykpICogdWNpcmNsZV9TdGFnZTFfYzAueik7Cgl9CgloYWxmNCBpbnB1dENvbG9yID0gX2lucHV0OwoJQGlmICgxID09IDEgfHwgMSA9PSAzKSAKCXsKCQlyZXR1cm4gaW5wdXRDb2xvciAqIGNsYW1wKGQsIDAuMCwgMS4wKTsKCX0KCWVsc2UgCgl7CgkJcmV0dXJuIGQgPiAwLjUgPyBpbnB1dENvbG9yIDogaGFsZjQoMC4wKTsKCX0KfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TdGFnZTAgPSBoYWxmNCgxKTsKCWZsb2F0MiB0ZXhDb29yZDsKCXRleENvb3JkID0gdmxvY2FsQ29vcmRfU3RhZ2UwOwoJb3V0cHV0Q29sb3JfU3RhZ2UwID0gKChzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfU3RhZ2UwLCB0ZXhDb29yZCkgKiBoYWxmNCgxKSkpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfU3RhZ2UwID0gaGFsZjQoMSk7CgloYWxmNCBvdXRwdXRfU3RhZ2UxOwoJb3V0cHV0X1N0YWdlMSA9IENpcmNsZUVmZmVjdF9TdGFnZTFfYzAob3V0cHV0Q292ZXJhZ2VfU3RhZ2UwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TdGFnZTAgKiBvdXRwdXRfU3RhZ2UxOwoJfQp9CgAAAAABAQABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAACAAAACAAAAHBvc2l0aW9uCgAAAGxvY2FsQ29vcmQAAAEAAAAAAAAA","B2ABSAAAAQAAAAABC3777777AAOAAAAAAAAAAAAAUABAAAAACAAAAAEASAIQAAAA":"BAAAAExTS1OpAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gaGFsZjQgdUNvbG9yX1N0YWdlMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGYgaW5Db3ZlcmFnZTsKb3V0IGhhbGY0IHZjb2xvcl9TdGFnZTA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIERlZmF1bHRHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgY29sb3IgPSB1Q29sb3JfU3RhZ2UwOwoJY29sb3IgPSBjb2xvciAqIGluQ292ZXJhZ2U7Cgl2Y29sb3JfU3RhZ2UwID0gY29sb3I7CglmbG9hdDIgX3RtcF8wX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJZmxvYXQyIF90bXBfMV9pblBvc2l0aW9uID0gaW5Qb3NpdGlvbjsKCXNrX1Bvc2l0aW9uID0gZmxvYXQ0KF90bXBfMF9pblBvc2l0aW9uLnggLCBfdG1wXzBfaW5Qb3NpdGlvbi55LCAwLCAxKTsKfQoAAAAAAAAAAAAAAAAAAAA6AQAAaW4gaGFsZjQgdmNvbG9yX1N0YWdlMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIERlZmF1bHRHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfU3RhZ2UwOwoJb3V0cHV0Q29sb3JfU3RhZ2UwID0gdmNvbG9yX1N0YWdlMDsKCWNvbnN0IGhhbGY0IG91dHB1dENvdmVyYWdlX1N0YWdlMCA9IGhhbGY0KDEpOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1N0YWdlMCAqIG91dHB1dENvdmVyYWdlX1N0YWdlMDsKCX0KfQoAAAAAAAABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAACAAAACgAAAGluUG9zaXRpb24AAAoAAABpbkNvdmVyYWdlAAABAAAAAAAAAA==","K6QACAAAAAGAAAAAAIWAAKRCH37P6BZQ737QCAAAAAAAAAAAIACQAAAAEAAAAAAAEERQAAA":"BAAAAExTS1O9AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQgY292ZXJhZ2U7CmluIGhhbGY0IGNvbG9yOwppbiBmbG9hdDQgZ2VvbVN1YnNldDsKZmxhdCBvdXQgaGFsZjQgdmNvbG9yX1N0YWdlMDsKb3V0IGZsb2F0IHZjb3ZlcmFnZV9TdGFnZTA7CmZsYXQgb3V0IGZsb2F0NCB2Z2VvbVN1YnNldF9TdGFnZTA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQyIHBvc2l0aW9uID0gcG9zaXRpb24ueHk7Cgl2Y29sb3JfU3RhZ2UwID0gY29sb3I7Cgl2Y292ZXJhZ2VfU3RhZ2UwID0gY292ZXJhZ2U7Cgl2Z2VvbVN1YnNldF9TdGFnZTAgPSBnZW9tU3Vic2V0OwoJc2tfUG9zaXRpb24gPSBmbG9hdDQocG9zaXRpb24ueCAsIHBvc2l0aW9uLnksIDAsIDEpOwp9CgAAAAABAQAAAAAAAAEBAL0CAABmbGF0IGluIGhhbGY0IHZjb2xvcl9TdGFnZTA7CmluIGZsb2F0IHZjb3ZlcmFnZV9TdGFnZTA7CmZsYXQgaW4gZmxvYXQ0IHZnZW9tU3Vic2V0X1N0YWdlMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfU3RhZ2UwOwoJb3V0cHV0Q29sb3JfU3RhZ2UwID0gdmNvbG9yX1N0YWdlMDsKCWZsb2F0IGNvdmVyYWdlID0gdmNvdmVyYWdlX1N0YWdlMDsKCWZsb2F0NCBnZW9TdWJzZXQ7CglnZW9TdWJzZXQgPSB2Z2VvbVN1YnNldF9TdGFnZTA7CgloYWxmNCBkaXN0czQgPSBjbGFtcChoYWxmNCgxLCAxLCAtMSwgLTEpICogaGFsZjQoc2tfRnJhZ0Nvb3JkLnh5eHkgLSBnZW9TdWJzZXQpLCAwLCAxKTsKCWhhbGYyIGRpc3RzMiA9IGRpc3RzNC54eSArIGRpc3RzNC56dyAtIDE7CgloYWxmIHN1YnNldENvdmVyYWdlID0gZGlzdHMyLnggKiBkaXN0czIueTsKCWNvdmVyYWdlID0gbWluKGNvdmVyYWdlLCBzdWJzZXRDb3ZlcmFnZSk7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TdGFnZTAgPSBoYWxmNChoYWxmKGNvdmVyYWdlKSk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfU3RhZ2UwICogb3V0cHV0Q292ZXJhZ2VfU3RhZ2UwOwoJfQp9CgAAAAABAQABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAEAAAACAAAAHBvc2l0aW9uCAAAAGNvdmVyYWdlBQAAAGNvbG9yAAAACgAAAGdlb21TdWJzZXQAAAEAAAAAAAAA","AWQQGAAAQAAIXCEPAGGP777777777737AAAAAAAAAAAIAXCIAEAAAAAAKQAAAAAAAIAAAAAQGIBAA":"BAAAAExTS1MJAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TdGFnZTA7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiBmbG9hdDQgaW5DaXJjbGVFZGdlOwpvdXQgZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfU3RhZ2UwOwpvdXQgaGFsZjQgdmluQ29sb3JfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TdGFnZTAgPSBpbkNpcmNsZUVkZ2U7Cgl2aW5Db2xvcl9TdGFnZTAgPSBpbkNvbG9yOwoJZmxvYXQyIF90bXBfMF9pblBvc2l0aW9uID0gaW5Qb3NpdGlvbjsKCWZsb2F0MiBfdG1wXzFfaW5Qb3NpdGlvbiA9IHVsb2NhbE1hdHJpeF9TdGFnZTAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1N0YWdlMC55dzsKCXNrX1Bvc2l0aW9uID0gZmxvYXQ0KF90bXBfMF9pblBvc2l0aW9uLnggLCBfdG1wXzBfaW5Qb3NpdGlvbi55LCAwLCAxKTsKfQoAAAAAAQEAAAAAAAABAQCrBQAAdW5pZm9ybSBmbG9hdDQgdXJlY3RVbmlmb3JtX1N0YWdlMV9jMDsKaW4gZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfU3RhZ2UwOwppbiBoYWxmNCB2aW5Db2xvcl9TdGFnZTA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IEFBUmVjdEVmZmVjdF9TdGFnZTFfYzAoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQ0IHByZXZSZWN0ID0gZmxvYXQ0KC0xLjAwMDAwMCwgLTEuMDAwMDAwLCAtMS4wMDAwMDAsIC0xLjAwMDAwMCk7CgloYWxmIGNvdmVyYWdlOwoJQHN3aXRjaCAoMSkgCgl7CgkJY2FzZSAwOiAgICBjYXNlIDI6ICAgICAgICBjb3ZlcmFnZSA9IGhhbGYoYWxsKGdyZWF0ZXJUaGFuKGZsb2F0NChza19GcmFnQ29vcmQueHksIHVyZWN0VW5pZm9ybV9TdGFnZTFfYzAuencpLCBmbG9hdDQodXJlY3RVbmlmb3JtX1N0YWdlMV9jMC54eSwgc2tfRnJhZ0Nvb3JkLnh5KSkpID8gMSA6IDApOwoJCWJyZWFrOwoJCWRlZmF1bHQ6ICAgICAgICBoYWxmNCBkaXN0czQgPSBjbGFtcChoYWxmNCgxLjAsIDEuMCwgLTEuMCwgLTEuMCkgKiBoYWxmNChza19GcmFnQ29vcmQueHl4eSAtIHVyZWN0VW5pZm9ybV9TdGFnZTFfYzApLCAwLjAsIDEuMCk7CgkJaGFsZjIgZGlzdHMyID0gKGRpc3RzNC54eSArIGRpc3RzNC56dykgLSAxLjA7CgkJY292ZXJhZ2UgPSBkaXN0czIueCAqIGRpc3RzMi55OwoJfQoJQGlmICgxID09IDIgfHwgMSA9PSAzKSAKCXsKCQljb3ZlcmFnZSA9IDEuMCAtIGNvdmVyYWdlOwoJfQoJcmV0dXJuIF9pbnB1dCAqIGNvdmVyYWdlOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQ0IGNpcmNsZUVkZ2U7CgljaXJjbGVFZGdlID0gdmluQ2lyY2xlRWRnZV9TdGFnZTA7CgloYWxmNCBvdXRwdXRDb2xvcl9TdGFnZTA7CglvdXRwdXRDb2xvcl9TdGFnZTAgPSB2aW5Db2xvcl9TdGFnZTA7CglmbG9hdCBkID0gbGVuZ3RoKGNpcmNsZUVkZ2UueHkpOwoJaGFsZiBkaXN0YW5jZVRvT3V0ZXJFZGdlID0gaGFsZihjaXJjbGVFZGdlLnogKiAoMS4wIC0gZCkpOwoJaGFsZiBlZGdlQWxwaGEgPSBzYXR1cmF0ZShkaXN0YW5jZVRvT3V0ZXJFZGdlKTsKCWhhbGYgZGlzdGFuY2VUb0lubmVyRWRnZSA9IGhhbGYoY2lyY2xlRWRnZS56ICogKGQgLSBjaXJjbGVFZGdlLncpKTsKCWhhbGYgaW5uZXJBbHBoYSA9IHNhdHVyYXRlKGRpc3RhbmNlVG9Jbm5lckVkZ2UpOwoJZWRnZUFscGhhICo9IGlubmVyQWxwaGE7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TdGFnZTAgPSBoYWxmNChlZGdlQWxwaGEpOwoJaGFsZjQgb3V0cHV0X1N0YWdlMTsKCW91dHB1dF9TdGFnZTEgPSBBQVJlY3RFZmZlY3RfU3RhZ2UxX2MwKG91dHB1dENvdmVyYWdlX1N0YWdlMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfU3RhZ2UwICogb3V0cHV0X1N0YWdlMTsKCX0KfQoAAAEBAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IADAAAAGluQ2lyY2xlRWRnZQEAAAAAAAAA","BYAAQAAAAQAAAAABC3777777777QAAAAAAAAAAAAUABAAAAACAAAAAEASAIQAAAA":"BAAAAExTS1PkAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBEZWZhdWx0R2VvbWV0cnlQcm9jZXNzb3IKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247Cglza19Qb3NpdGlvbiA9IGZsb2F0NChfdG1wXzBfaW5Qb3NpdGlvbi54ICwgX3RtcF8wX2luUG9zaXRpb24ueSwgMCwgMSk7Cn0KAAAAAAAAAAAAAAAAPwEAAHVuaWZvcm0gaGFsZjQgdUNvbG9yX1N0YWdlMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIERlZmF1bHRHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfU3RhZ2UwOwoJb3V0cHV0Q29sb3JfU3RhZ2UwID0gdUNvbG9yX1N0YWdlMDsKCWNvbnN0IGhhbGY0IG91dHB1dENvdmVyYWdlX1N0YWdlMCA9IGhhbGY0KDEpOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1N0YWdlMCAqIG91dHB1dENvdmVyYWdlX1N0YWdlMDsKCX0KfQoAAAAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAKAAAAaW5Qb3NpdGlvbgAAAQAAAAAAAAA=","AWQQGAAAQAAIXCEPAGGP777777777737AAAAAAAAAAAAAUABAAAAACAAAAAEBSAI":"BAAAAExTS1MJAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TdGFnZTA7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiBmbG9hdDQgaW5DaXJjbGVFZGdlOwpvdXQgZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfU3RhZ2UwOwpvdXQgaGFsZjQgdmluQ29sb3JfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TdGFnZTAgPSBpbkNpcmNsZUVkZ2U7Cgl2aW5Db2xvcl9TdGFnZTAgPSBpbkNvbG9yOwoJZmxvYXQyIF90bXBfMF9pblBvc2l0aW9uID0gaW5Qb3NpdGlvbjsKCWZsb2F0MiBfdG1wXzFfaW5Qb3NpdGlvbiA9IHVsb2NhbE1hdHJpeF9TdGFnZTAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1N0YWdlMC55dzsKCXNrX1Bvc2l0aW9uID0gZmxvYXQ0KF90bXBfMF9pblBvc2l0aW9uLnggLCBfdG1wXzBfaW5Qb3NpdGlvbi55LCAwLCAxKTsKfQoAAAAAAAAAAAAAAAAAAAC3AgAAaW4gZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfU3RhZ2UwOwppbiBoYWxmNCB2aW5Db2xvcl9TdGFnZTA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQ0IGNpcmNsZUVkZ2U7CgljaXJjbGVFZGdlID0gdmluQ2lyY2xlRWRnZV9TdGFnZTA7CgloYWxmNCBvdXRwdXRDb2xvcl9TdGFnZTA7CglvdXRwdXRDb2xvcl9TdGFnZTAgPSB2aW5Db2xvcl9TdGFnZTA7CglmbG9hdCBkID0gbGVuZ3RoKGNpcmNsZUVkZ2UueHkpOwoJaGFsZiBkaXN0YW5jZVRvT3V0ZXJFZGdlID0gaGFsZihjaXJjbGVFZGdlLnogKiAoMS4wIC0gZCkpOwoJaGFsZiBlZGdlQWxwaGEgPSBzYXR1cmF0ZShkaXN0YW5jZVRvT3V0ZXJFZGdlKTsKCWhhbGYgZGlzdGFuY2VUb0lubmVyRWRnZSA9IGhhbGYoY2lyY2xlRWRnZS56ICogKGQgLSBjaXJjbGVFZGdlLncpKTsKCWhhbGYgaW5uZXJBbHBoYSA9IHNhdHVyYXRlKGRpc3RhbmNlVG9Jbm5lckVkZ2UpOwoJZWRnZUFscGhhICo9IGlubmVyQWxwaGE7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TdGFnZTAgPSBoYWxmNChlZGdlQWxwaGEpOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1N0YWdlMCAqIG91dHB1dENvdmVyYWdlX1N0YWdlMDsKCX0KfQoAAAAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IADAAAAGluQ2lyY2xlRWRnZQEAAAAAAAAA","K4QACAAAAAGAAAAAAIWP57ZDH37P7777777QCAAAAAAAAAAAIACQAAAAEAAAAAAAEERQAAA":"BAAAAExTS1PvAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7Cm91dCBoYWxmNCB2Y29sb3JfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCXZjb2xvcl9TdGFnZTAgPSBjb2xvcjsKCXNrX1Bvc2l0aW9uID0gZmxvYXQ0KHBvc2l0aW9uLnggLCBwb3NpdGlvbi55LCAwLCAxKTsKfQoAAAAAAAAAAAAAAAAAQAEAAGluIGhhbGY0IHZjb2xvcl9TdGFnZTA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1N0YWdlMDsKCW91dHB1dENvbG9yX1N0YWdlMCA9IHZjb2xvcl9TdGFnZTA7Cgljb25zdCBoYWxmNCBvdXRwdXRDb3ZlcmFnZV9TdGFnZTAgPSBoYWxmNCgxKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TdGFnZTAgKiBvdXRwdXRDb3ZlcmFnZV9TdGFnZTA7Cgl9Cn0KAAAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24FAAAAY29sb3IAAAABAAAAAAAAAA==","AWQAGAAAQAAIXCEPAGGP777777777737AAAAAAAAAAAIAGGIDYAAAQAAAAAAAAAVAAAABAAAAAAABBEMAAAA":"BAAAAExTS1MJAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TdGFnZTA7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiBmbG9hdDQgaW5DaXJjbGVFZGdlOwpvdXQgZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfU3RhZ2UwOwpvdXQgaGFsZjQgdmluQ29sb3JfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TdGFnZTAgPSBpbkNpcmNsZUVkZ2U7Cgl2aW5Db2xvcl9TdGFnZTAgPSBpbkNvbG9yOwoJZmxvYXQyIF90bXBfMF9pblBvc2l0aW9uID0gaW5Qb3NpdGlvbjsKCWZsb2F0MiBfdG1wXzFfaW5Qb3NpdGlvbiA9IHVsb2NhbE1hdHJpeF9TdGFnZTAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1N0YWdlMC55dzsKCXNrX1Bvc2l0aW9uID0gZmxvYXQ0KF90bXBfMF9pblBvc2l0aW9uLnggLCBfdG1wXzBfaW5Qb3NpdGlvbi55LCAwLCAxKTsKfQoAAAAAAQEAAAAAAAABAQAIBAAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TdGFnZTFfYzA7CnVuaWZvcm0gaGFsZjIgdXJhZGl1c1BsdXNIYWxmX1N0YWdlMV9jMDsKaW4gZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfU3RhZ2UwOwppbiBoYWxmNCB2aW5Db2xvcl9TdGFnZTA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IENpcmN1bGFyUlJlY3RfU3RhZ2UxX2MwKGhhbGY0IF9pbnB1dCkgCnsKCWZsb2F0MiBkeHkwID0gdWlubmVyUmVjdF9TdGFnZTFfYzAuTFQgLSBza19GcmFnQ29vcmQueHk7CglmbG9hdDIgZHh5MSA9IHNrX0ZyYWdDb29yZC54eSAtIHVpbm5lclJlY3RfU3RhZ2UxX2MwLlJCOwoJZmxvYXQyIGR4eSA9IG1heChtYXgoZHh5MCwgZHh5MSksIDAuMCk7CgloYWxmIGFscGhhID0gaGFsZihzYXR1cmF0ZSh1cmFkaXVzUGx1c0hhbGZfU3RhZ2UxX2MwLnggLSBsZW5ndGgoZHh5KSkpOwoJYWxwaGEgPSAxLjAgLSBhbHBoYTsKCXJldHVybiBfaW5wdXQgKiBhbHBoYTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgQ2lyY2xlR2VvbWV0cnlQcm9jZXNzb3IKCWZsb2F0NCBjaXJjbGVFZGdlOwoJY2lyY2xlRWRnZSA9IHZpbkNpcmNsZUVkZ2VfU3RhZ2UwOwoJaGFsZjQgb3V0cHV0Q29sb3JfU3RhZ2UwOwoJb3V0cHV0Q29sb3JfU3RhZ2UwID0gdmluQ29sb3JfU3RhZ2UwOwoJZmxvYXQgZCA9IGxlbmd0aChjaXJjbGVFZGdlLnh5KTsKCWhhbGYgZGlzdGFuY2VUb091dGVyRWRnZSA9IGhhbGYoY2lyY2xlRWRnZS56ICogKDEuMCAtIGQpKTsKCWhhbGYgZWRnZUFscGhhID0gc2F0dXJhdGUoZGlzdGFuY2VUb091dGVyRWRnZSk7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TdGFnZTAgPSBoYWxmNChlZGdlQWxwaGEpOwoJaGFsZjQgb3V0cHV0X1N0YWdlMTsKCW91dHB1dF9TdGFnZTEgPSBDaXJjdWxhclJSZWN0X1N0YWdlMV9jMChvdXRwdXRDb3ZlcmFnZV9TdGFnZTApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1N0YWdlMCAqIG91dHB1dF9TdGFnZTE7Cgl9Cn0KAAEBAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IADAAAAGluQ2lyY2xlRWRnZQEAAAAAAAAA","K4IAAAAAAAGAAAAAAIWP577774BSZ7X7777QCAAAAAAAAAAAVIRQYAAAAAAAAAQAAAAGJCABAAAAAACAAAAABAGOAAAAQAAAABQCVAEQAEAAAAAAAAAAAVAAAAAAAAQAAAABAMQCAAAAA":"BAAAAExTS1NjAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TdGFnZTFfYzA7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzBfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCXNrX1Bvc2l0aW9uID0gZmxvYXQ0KHBvc2l0aW9uLnggLCBwb3NpdGlvbi55LCAwLCAxKTsKCXsKCQl2VHJhbnNmb3JtZWRDb29yZHNfMF9TdGFnZTAgPSAoKCh1bWF0cml4X1N0YWdlMV9jMCkpICogbG9jYWxDb29yZC54eTEpLnh5OwoJfQp9CgAAAAAAAAAAAAAAAADIEgAAdW5pZm9ybSBmbG9hdDN4MyB1bWF0cml4X1N0YWdlMV9jMDsKdW5pZm9ybSBoYWxmMiB1XzBfSW5jcmVtZW50X1N0YWdlMV9jMF9jMDsKdW5pZm9ybSBoYWxmNCB1XzFfS2VybmVsX1N0YWdlMV9jMF9jMFs3XTsKdW5pZm9ybSBmbG9hdDN4MyB1bWF0cml4X1N0YWdlMV9jMF9jMF9jMDsKdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfU3RhZ2UxOwppbiBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzBfU3RhZ2UwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBUZXh0dXJlRWZmZWN0X1N0YWdlMV9jMF9jMF9jMF9jMChoYWxmNCBfaW5wdXQsIGZsb2F0MiBfY29vcmRzKSAKewoJcmV0dXJuIHNhbXBsZSh1VGV4dHVyZVNhbXBsZXJfMF9TdGFnZTEsIF9jb29yZHMpOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzBfYzAoaGFsZjQgX2lucHV0LCBmbG9hdDIgX2Nvb3JkcykgCnsKCXJldHVybiBUZXh0dXJlRWZmZWN0X1N0YWdlMV9jMF9jMF9jMF9jMChfaW5wdXQsICgodW1hdHJpeF9TdGFnZTFfYzBfYzBfYzApICogX2Nvb3Jkcy54eTEpLnh5KTsKfQpoYWxmNCBHYXVzc2lhbkNvbnZvbHV0aW9uX1N0YWdlMV9jMF9jMChoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBfMl9jb2xvciA9IGhhbGY0KDAuMCk7CglmbG9hdDIgXzNfY29vcmQgPSAodlRyYW5zZm9ybWVkQ29vcmRzXzBfU3RhZ2UwIC0gZmxvYXQyKCgxMi4wICogdV8wX0luY3JlbWVudF9TdGFnZTFfYzBfYzApKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMF9jMFswXS54KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMF9jMFswXS55KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMF9jMFswXS56KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMF9jMFswXS53KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMF9jMFsxXS54KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMF9jMFsxXS55KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMF9jMFsxXS56KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMF9jMFsxXS53KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMF9jMFsyXS54KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMF9jMFsyXS55KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMF9jMFsyXS56KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMF9jMFsyXS53KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMF9jMFszXS54KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMF9jMFszXS55KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMF9jMFszXS56KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMF9jMFszXS53KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMF9jMFs0XS54KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMF9jMFs0XS55KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMF9jMFs0XS56KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMF9jMFs0XS53KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMF9jMFs1XS54KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMF9jMFs1XS55KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMF9jMFs1XS56KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMF9jMFs1XS53KSk7CgkoXzNfY29vcmQgKz0gZmxvYXQyKHVfMF9JbmNyZW1lbnRfU3RhZ2UxX2MwX2MwKSk7CgkoXzJfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzBfYzAoX2lucHV0LCBfM19jb29yZCkgKiB1XzFfS2VybmVsX1N0YWdlMV9jMF9jMFs2XS54KSk7CglyZXR1cm4gXzJfY29sb3I7Cn0KaGFsZjQgTWF0cml4RWZmZWN0X1N0YWdlMV9jMChoYWxmNCBfaW5wdXQpIAp7CglyZXR1cm4gR2F1c3NpYW5Db252b2x1dGlvbl9TdGFnZTFfYzBfYzAoX2lucHV0KTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TdGFnZTAgPSBoYWxmNCgxKTsKCWNvbnN0IGhhbGY0IG91dHB1dENvdmVyYWdlX1N0YWdlMCA9IGhhbGY0KDEpOwoJaGFsZjQgb3V0cHV0X1N0YWdlMTsKCW91dHB1dF9TdGFnZTEgPSBNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwKG91dHB1dENvbG9yX1N0YWdlMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0X1N0YWdlMSAqIG91dHB1dENvdmVyYWdlX1N0YWdlMDsKCX0KfQoAAAAAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","IYBQAAAAAELBCHYCDYAAAAAAAEAAAACAIQAABIACAAAAAEAAAAAIBEARAA":"BAAAAExTS1OFAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiBoYWxmMyBpblNoYWRvd1BhcmFtczsKb3V0IGhhbGYzIHZpblNoYWRvd1BhcmFtc19TdGFnZTA7Cm91dCBoYWxmNCB2aW5Db2xvcl9TdGFnZTA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFJSZWN0U2hhZG93Cgl2aW5TaGFkb3dQYXJhbXNfU3RhZ2UwID0gaW5TaGFkb3dQYXJhbXM7Cgl2aW5Db2xvcl9TdGFnZTAgPSBpbkNvbG9yOwoJZmxvYXQyIF90bXBfMF9pblBvc2l0aW9uID0gaW5Qb3NpdGlvbjsKCXNrX1Bvc2l0aW9uID0gZmxvYXQ0KF90bXBfMF9pblBvc2l0aW9uLnggLCBfdG1wXzBfaW5Qb3NpdGlvbi55LCAwLCAxKTsKfQoAAAAAAAAAAAAAAAAAAABPAgAAdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfU3RhZ2UwOwppbiBoYWxmMyB2aW5TaGFkb3dQYXJhbXNfU3RhZ2UwOwppbiBoYWxmNCB2aW5Db2xvcl9TdGFnZTA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBSUmVjdFNoYWRvdwoJaGFsZjMgc2hhZG93UGFyYW1zOwoJc2hhZG93UGFyYW1zID0gdmluU2hhZG93UGFyYW1zX1N0YWdlMDsKCWhhbGY0IG91dHB1dENvbG9yX1N0YWdlMDsKCW91dHB1dENvbG9yX1N0YWdlMCA9IHZpbkNvbG9yX1N0YWdlMDsKCWhhbGYgZCA9IGxlbmd0aChzaGFkb3dQYXJhbXMueHkpOwoJZmxvYXQyIHV2ID0gZmxvYXQyKHNoYWRvd1BhcmFtcy56ICogKDEuMCAtIGQpLCAwLjUpOwoJaGFsZiBmYWN0b3IgPSBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfU3RhZ2UwLCB1dikuMDAwci5hOwoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfU3RhZ2UwID0gaGFsZjQoZmFjdG9yKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TdGFnZTAgKiBvdXRwdXRDb3ZlcmFnZV9TdGFnZTA7Cgl9Cn0KAAAAAAABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAADAAAACgAAAGluUG9zaXRpb24AAAcAAABpbkNvbG9yAA4AAABpblNoYWRvd1BhcmFtcwAAAQAAAAAAAAA=","B2IASAAAAQAAAAABCYIR7777AAOAAAAAAAAAAAAAUABAAAAACAAAAAEASAIQAAAA":"BAAAAExTS1NwAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiBoYWxmIGluQ292ZXJhZ2U7Cm91dCBoYWxmNCB2Y29sb3JfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBEZWZhdWx0R2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IGNvbG9yID0gaW5Db2xvcjsKCWNvbG9yID0gY29sb3IgKiBpbkNvdmVyYWdlOwoJdmNvbG9yX1N0YWdlMCA9IGNvbG9yOwoJZmxvYXQyIF90bXBfMF9pblBvc2l0aW9uID0gaW5Qb3NpdGlvbjsKCXNrX1Bvc2l0aW9uID0gZmxvYXQ0KF90bXBfMF9pblBvc2l0aW9uLnggLCBfdG1wXzBfaW5Qb3NpdGlvbi55LCAwLCAxKTsKfQoAAAAAAAAAAAAAAAA6AQAAaW4gaGFsZjQgdmNvbG9yX1N0YWdlMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIERlZmF1bHRHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfU3RhZ2UwOwoJb3V0cHV0Q29sb3JfU3RhZ2UwID0gdmNvbG9yX1N0YWdlMDsKCWNvbnN0IGhhbGY0IG91dHB1dENvdmVyYWdlX1N0YWdlMCA9IGhhbGY0KDEpOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1N0YWdlMCAqIG91dHB1dENvdmVyYWdlX1N0YWdlMDsKCX0KfQoAAAAAAAABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAADAAAACgAAAGluUG9zaXRpb24AAAcAAABpbkNvbG9yAAoAAABpbkNvdmVyYWdlAAABAAAAAAAAAA==","K4IAAAAAAAGAAAAAAIWP577774BSZ7X7777QCAAAAAAAAAAAVIRQCAICAAAAA5BIEBAAAAAAAAJQDBAMAACAIAAAAAAKAAQAAAABAAAAACAJAEIAAAAA":"BAAAAExTS1OjAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TdGFnZTFfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TdGFnZTFfYzBfYzA7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzBfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCXNrX1Bvc2l0aW9uID0gZmxvYXQ0KHBvc2l0aW9uLnggLCBwb3NpdGlvbi55LCAwLCAxKTsKCXsKCQl2VHJhbnNmb3JtZWRDb29yZHNfMF9TdGFnZTAgPSAoKCh1bWF0cml4X1N0YWdlMV9jMF9jMCkgKiAodW1hdHJpeF9TdGFnZTFfYzApKSAqIGxvY2FsQ29vcmQueHkxKS54eTsKCX0KfQoAAAAAAAAAAAAAAAAAXQQAAHVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TdGFnZTFfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TdGFnZTFfYzBfYzA7CnVuaWZvcm0gZmxvYXQ0IHVjbGFtcF9TdGFnZTFfYzBfYzBfYzA7CnVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1N0YWdlMTsKaW4gZmxvYXQyIHZUcmFuc2Zvcm1lZENvb3Jkc18wX1N0YWdlMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgVGV4dHVyZUVmZmVjdF9TdGFnZTFfYzBfYzBfYzAoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGluQ29vcmQgPSB2VHJhbnNmb3JtZWRDb29yZHNfMF9TdGFnZTA7CglmbG9hdDIgc3Vic2V0Q29vcmQ7CglzdWJzZXRDb29yZC54ID0gaW5Db29yZC54OwoJc3Vic2V0Q29vcmQueSA9IGluQ29vcmQueTsKCWZsb2F0MiBjbGFtcGVkQ29vcmQ7CgljbGFtcGVkQ29vcmQgPSBjbGFtcChzdWJzZXRDb29yZCwgdWNsYW1wX1N0YWdlMV9jMF9jMF9jMC54eSwgdWNsYW1wX1N0YWdlMV9jMF9jMF9jMC56dyk7CgloYWxmNCB0ZXh0dXJlQ29sb3IgPSBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfU3RhZ2UxLCBjbGFtcGVkQ29vcmQpOwoJcmV0dXJuIHRleHR1cmVDb2xvcjsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwX2MwKGhhbGY0IF9pbnB1dCkgCnsKCXJldHVybiBUZXh0dXJlRWZmZWN0X1N0YWdlMV9jMF9jMF9jMChfaW5wdXQpOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TdGFnZTFfYzAoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzAoX2lucHV0KTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TdGFnZTAgPSBoYWxmNCgxKTsKCWNvbnN0IGhhbGY0IG91dHB1dENvdmVyYWdlX1N0YWdlMCA9IGhhbGY0KDEpOwoJaGFsZjQgb3V0cHV0X1N0YWdlMTsKCW91dHB1dF9TdGFnZTEgPSBNYXRyaXhFZmZlY3RfU3RhZ2UxX2MwKG91dHB1dENvbG9yX1N0YWdlMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0X1N0YWdlMSAqIG91dHB1dENvdmVyYWdlX1N0YWdlMDsKCX0KfQoAAAAAAAAAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","DISAAAAAMAAAAABAYDRP7H2CAIAAAABAAAAAAABAMQAAAVAAAAAAAAQAAAABAMQC":"BAAAAExTS1MHAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQyIHVBdGxhc1NpemVJbnZfU3RhZ2UwOwppbiBmbG9hdDIgaW5Qb3NpdGlvbjsKaW4gdXNob3J0MiBpblRleHR1cmVDb29yZHM7Cm91dCBmbG9hdDIgdlRleHR1cmVDb29yZHNfU3RhZ2UwOwpmbGF0IG91dCBmbG9hdCB2VGV4SW5kZXhfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBCaXRtYXBUZXh0CglpbnQgdGV4SWR4ID0gMDsKCWZsb2F0MiB1bm9ybVRleENvb3JkcyA9IGZsb2F0MihpblRleHR1cmVDb29yZHMueCwgaW5UZXh0dXJlQ29vcmRzLnkpOwoJdlRleHR1cmVDb29yZHNfU3RhZ2UwID0gdW5vcm1UZXhDb29yZHMgKiB1QXRsYXNTaXplSW52X1N0YWdlMDsKCXZUZXhJbmRleF9TdGFnZTAgPSBmbG9hdCh0ZXhJZHgpOwoJZmxvYXQyIF90bXBfMF9pblBvc2l0aW9uID0gaW5Qb3NpdGlvbjsKCXNrX1Bvc2l0aW9uID0gZmxvYXQ0KGluUG9zaXRpb24ueCAsIGluUG9zaXRpb24ueSwgMCwgMSk7Cn0KAAAAAAAAAAAAAAAAADACAAB1bmlmb3JtIGhhbGY0IHVDb2xvcl9TdGFnZTA7CnVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1N0YWdlMDsKaW4gZmxvYXQyIHZUZXh0dXJlQ29vcmRzX1N0YWdlMDsKZmxhdCBpbiBmbG9hdCB2VGV4SW5kZXhfU3RhZ2UwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgQml0bWFwVGV4dAoJaGFsZjQgb3V0cHV0Q29sb3JfU3RhZ2UwOwoJb3V0cHV0Q29sb3JfU3RhZ2UwID0gdUNvbG9yX1N0YWdlMDsKCWhhbGY0IHRleENvbG9yOwoJewoJCXRleENvbG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1N0YWdlMCwgdlRleHR1cmVDb29yZHNfU3RhZ2UwKTsKCX0KCW91dHB1dENvbG9yX1N0YWdlMCA9IG91dHB1dENvbG9yX1N0YWdlMCAqIHRleENvbG9yOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfU3RhZ2UwID0gaGFsZjQoMSk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfU3RhZ2UwICogb3V0cHV0Q292ZXJhZ2VfU3RhZ2UwOwoJfQp9CgAAAAABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAACAAAACgAAAGluUG9zaXRpb24AAA8AAABpblRleHR1cmVDb29yZHMAAQAAAAAAAAA=","AWQAGAAAQAAIXCEPAGGP777777777737AAAAAAAAAAAAAUABAAAAACAAAAAEBSAI":"BAAAAExTS1MJAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TdGFnZTA7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiBmbG9hdDQgaW5DaXJjbGVFZGdlOwpvdXQgZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfU3RhZ2UwOwpvdXQgaGFsZjQgdmluQ29sb3JfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TdGFnZTAgPSBpbkNpcmNsZUVkZ2U7Cgl2aW5Db2xvcl9TdGFnZTAgPSBpbkNvbG9yOwoJZmxvYXQyIF90bXBfMF9pblBvc2l0aW9uID0gaW5Qb3NpdGlvbjsKCWZsb2F0MiBfdG1wXzFfaW5Qb3NpdGlvbiA9IHVsb2NhbE1hdHJpeF9TdGFnZTAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1N0YWdlMC55dzsKCXNrX1Bvc2l0aW9uID0gZmxvYXQ0KF90bXBfMF9pblBvc2l0aW9uLnggLCBfdG1wXzBfaW5Qb3NpdGlvbi55LCAwLCAxKTsKfQoAAAAAAAAAAAAAAAAAAAAmAgAAaW4gZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfU3RhZ2UwOwppbiBoYWxmNCB2aW5Db2xvcl9TdGFnZTA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQ0IGNpcmNsZUVkZ2U7CgljaXJjbGVFZGdlID0gdmluQ2lyY2xlRWRnZV9TdGFnZTA7CgloYWxmNCBvdXRwdXRDb2xvcl9TdGFnZTA7CglvdXRwdXRDb2xvcl9TdGFnZTAgPSB2aW5Db2xvcl9TdGFnZTA7CglmbG9hdCBkID0gbGVuZ3RoKGNpcmNsZUVkZ2UueHkpOwoJaGFsZiBkaXN0YW5jZVRvT3V0ZXJFZGdlID0gaGFsZihjaXJjbGVFZGdlLnogKiAoMS4wIC0gZCkpOwoJaGFsZiBlZGdlQWxwaGEgPSBzYXR1cmF0ZShkaXN0YW5jZVRvT3V0ZXJFZGdlKTsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1N0YWdlMCA9IGhhbGY0KGVkZ2VBbHBoYSk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfU3RhZ2UwICogb3V0cHV0Q292ZXJhZ2VfU3RhZ2UwOwoJfQp9CgAAAAAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IADAAAAGluQ2lyY2xlRWRnZQEAAAAAAAAA","K4IAAAAAAAGAAAAAAIWP577774BSZ7X7777QCAAAAAAAAAAAVIRQCAICAAAABGAIEBSAAIBAAAAAAAAACUAAAAEAAAAAAAEERQAAAAA":"BAAAAExTS1NjAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TdGFnZTFfYzA7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzBfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCXNrX1Bvc2l0aW9uID0gZmxvYXQ0KHBvc2l0aW9uLnggLCBwb3NpdGlvbi55LCAwLCAxKTsKCXsKCQl2VHJhbnNmb3JtZWRDb29yZHNfMF9TdGFnZTAgPSAoKCh1bWF0cml4X1N0YWdlMV9jMCkpICogbG9jYWxDb29yZC54eTEpLnh5OwoJfQp9CgAAAAAAAAAAAAAAAADJAwAAdW5pZm9ybSBmbG9hdDN4MyB1bWF0cml4X1N0YWdlMV9jMDsKdW5pZm9ybSBmbG9hdDQgdWNsYW1wX1N0YWdlMV9jMF9jMDsKdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfU3RhZ2UxOwppbiBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzBfU3RhZ2UwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBUZXh0dXJlRWZmZWN0X1N0YWdlMV9jMF9jMChoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgaW5Db29yZCA9IHZUcmFuc2Zvcm1lZENvb3Jkc18wX1N0YWdlMDsKCWZsb2F0MiBzdWJzZXRDb29yZDsKCXN1YnNldENvb3JkLnggPSBpbkNvb3JkLng7CglzdWJzZXRDb29yZC55ID0gaW5Db29yZC55OwoJZmxvYXQyIGNsYW1wZWRDb29yZDsKCWNsYW1wZWRDb29yZCA9IGNsYW1wKHN1YnNldENvb3JkLCB1Y2xhbXBfU3RhZ2UxX2MwX2MwLnh5LCB1Y2xhbXBfU3RhZ2UxX2MwX2MwLnp3KTsKCWhhbGY0IHRleHR1cmVDb2xvciA9IHNhbXBsZSh1VGV4dHVyZVNhbXBsZXJfMF9TdGFnZTEsIGNsYW1wZWRDb29yZCk7CglyZXR1cm4gdGV4dHVyZUNvbG9yOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TdGFnZTFfYzAoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIFRleHR1cmVFZmZlY3RfU3RhZ2UxX2MwX2MwKF9pbnB1dCk7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfU3RhZ2UwID0gaGFsZjQoMSk7Cgljb25zdCBoYWxmNCBvdXRwdXRDb3ZlcmFnZV9TdGFnZTAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TdGFnZTE7CglvdXRwdXRfU3RhZ2UxID0gTWF0cml4RWZmZWN0X1N0YWdlMV9jMChvdXRwdXRDb2xvcl9TdGFnZTApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dF9TdGFnZTEgKiBvdXRwdXRDb3ZlcmFnZV9TdGFnZTA7Cgl9Cn0KAAAAAAAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24KAAAAbG9jYWxDb29yZAAAAQAAAAAAAAA=","K5JAAAAAAAMAAAAAARMPZ72HPQCFR7H7777QGAAAAACAAAAAACCAYAGEIDZAAAAAAIAAAAAAVAAAAAAAAQAAAABAMQCAAAAA":"BAAAAExTS1NLAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7CmluIGZsb2F0MiBsb2NhbENvb3JkOwpmbGF0IG91dCBoYWxmNCB2Y29sb3JfU3RhZ2UwOwpvdXQgZmxvYXQyIHZsb2NhbENvb3JkX1N0YWdlMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgl2Y29sb3JfU3RhZ2UwID0gY29sb3I7Cgl2bG9jYWxDb29yZF9TdGFnZTAgPSBsb2NhbENvb3JkOwoJc2tfUG9zaXRpb24gPSBmbG9hdDQocG9zaXRpb24ueCAsIHBvc2l0aW9uLnksIDAsIDEpOwp9CgAAAQEAAAAAAAABAQDoAwAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TdGFnZTFfYzA7CnVuaWZvcm0gaGFsZjIgdXJhZGl1c1BsdXNIYWxmX1N0YWdlMV9jMDsKdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfU3RhZ2UwOwpmbGF0IGluIGhhbGY0IHZjb2xvcl9TdGFnZTA7CmluIGZsb2F0MiB2bG9jYWxDb29yZF9TdGFnZTA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IENpcmN1bGFyUlJlY3RfU3RhZ2UxX2MwKGhhbGY0IF9pbnB1dCkgCnsKCWZsb2F0MiBkeHkwID0gdWlubmVyUmVjdF9TdGFnZTFfYzAuTFQgLSBza19GcmFnQ29vcmQueHk7CglmbG9hdDIgZHh5MSA9IHNrX0ZyYWdDb29yZC54eSAtIHVpbm5lclJlY3RfU3RhZ2UxX2MwLlJCOwoJZmxvYXQyIGR4eSA9IG1heChtYXgoZHh5MCwgZHh5MSksIDAuMCk7CgloYWxmIGFscGhhID0gaGFsZihzYXR1cmF0ZSh1cmFkaXVzUGx1c0hhbGZfU3RhZ2UxX2MwLnggLSBsZW5ndGgoZHh5KSkpOwoJcmV0dXJuIF9pbnB1dCAqIGFscGhhOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1N0YWdlMDsKCW91dHB1dENvbG9yX1N0YWdlMCA9IHZjb2xvcl9TdGFnZTA7CglmbG9hdDIgdGV4Q29vcmQ7Cgl0ZXhDb29yZCA9IHZsb2NhbENvb3JkX1N0YWdlMDsKCW91dHB1dENvbG9yX1N0YWdlMCA9ICgoc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1N0YWdlMCwgdGV4Q29vcmQpICogb3V0cHV0Q29sb3JfU3RhZ2UwKSk7Cgljb25zdCBoYWxmNCBvdXRwdXRDb3ZlcmFnZV9TdGFnZTAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TdGFnZTE7CglvdXRwdXRfU3RhZ2UxID0gQ2lyY3VsYXJSUmVjdF9TdGFnZTFfYzAob3V0cHV0Q292ZXJhZ2VfU3RhZ2UwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TdGFnZTAgKiBvdXRwdXRfU3RhZ2UxOwoJfQp9CgABAQABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAADAAAACAAAAHBvc2l0aW9uBQAAAGNvbG9yAAAACgAAAGxvY2FsQ29vcmQAAAEAAAAAAAAA","K5IAAAAAAAMAAAAAARMPZ72HPQCFR7H7777QGAAAAAAAAAAAWRDQB4AA6AAPAAHQDQAAAAAAEIFQAAAADQGQAAAAQCHAIAAAADMBAAAAAAABKAAAACAAAAAAACCIYAAA":"BAAAAExTS1O1AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TdGFnZTFfYzBfYzBfYzA7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7CmluIGZsb2F0MiBsb2NhbENvb3JkOwpmbGF0IG91dCBoYWxmNCB2Y29sb3JfU3RhZ2UwOwpvdXQgZmxvYXQyIHZUcmFuc2Zvcm1lZENvb3Jkc18wX1N0YWdlMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgl2Y29sb3JfU3RhZ2UwID0gY29sb3I7Cglza19Qb3NpdGlvbiA9IGZsb2F0NChwb3NpdGlvbi54ICwgcG9zaXRpb24ueSwgMCwgMSk7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzBfU3RhZ2UwID0gKCgodW1hdHJpeF9TdGFnZTFfYzBfYzBfYzApKSAqIGxvY2FsQ29vcmQueHkxKS54eTsKCX0KfQoAAAAAAAAAAAAAAAAAAADzBgAAdW5pZm9ybSBoYWxmNCB1bGVmdEJvcmRlckNvbG9yX1N0YWdlMV9jMF9jMDsKdW5pZm9ybSBoYWxmNCB1cmlnaHRCb3JkZXJDb2xvcl9TdGFnZTFfYzBfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TdGFnZTFfYzBfYzBfYzA7CnVuaWZvcm0gaGFsZjQgdXN0YXJ0X1N0YWdlMV9jMF9jMF9jMTsKdW5pZm9ybSBoYWxmNCB1ZW5kX1N0YWdlMV9jMF9jMF9jMTsKZmxhdCBpbiBoYWxmNCB2Y29sb3JfU3RhZ2UwOwppbiBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzBfU3RhZ2UwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBMaW5lYXJHcmFkaWVudExheW91dF9TdGFnZTFfYzBfYzBfYzBfYzAoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIGhhbGY0KGhhbGYodlRyYW5zZm9ybWVkQ29vcmRzXzBfU3RhZ2UwLngpICsgOS45OTk5OTk3NDczNzg3NTE2ZS0wNiwgMS4wLCAwLjAsIDAuMCk7Cn0KaGFsZjQgTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMF9jMChoYWxmNCBfaW5wdXQpIAp7CglyZXR1cm4gTGluZWFyR3JhZGllbnRMYXlvdXRfU3RhZ2UxX2MwX2MwX2MwX2MwKF9pbnB1dCk7Cn0KaGFsZjQgU2luZ2xlSW50ZXJ2YWxHcmFkaWVudENvbG9yaXplcl9TdGFnZTFfYzBfYzBfYzEoaGFsZjQgX2lucHV0LCBmbG9hdDIgX2Nvb3JkcykgCnsKCXJldHVybiBtaXgodXN0YXJ0X1N0YWdlMV9jMF9jMF9jMSwgdWVuZF9TdGFnZTFfYzBfYzBfYzEsIGhhbGYoX2Nvb3Jkcy54KSk7Cn0KaGFsZjQgQ2xhbXBlZEdyYWRpZW50RWZmZWN0X1N0YWdlMV9jMF9jMChoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCB0ID0gTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMF9jMChfaW5wdXQpOwoJaGFsZjQgb3V0Q29sb3I7CglpZiAoIXRydWUgJiYgdC55IDwgMC4wKSAKCXsKCQlvdXRDb2xvciA9IGhhbGY0KDAuMCk7Cgl9CgllbHNlIGlmICh0LnggPCAwLjApIAoJewoJCW91dENvbG9yID0gdWxlZnRCb3JkZXJDb2xvcl9TdGFnZTFfYzBfYzA7Cgl9CgllbHNlIGlmICh0LnggPiAxLjApIAoJewoJCW91dENvbG9yID0gdXJpZ2h0Qm9yZGVyQ29sb3JfU3RhZ2UxX2MwX2MwOwoJfQoJZWxzZSAKCXsKCQlvdXRDb2xvciA9IFNpbmdsZUludGVydmFsR3JhZGllbnRDb2xvcml6ZXJfU3RhZ2UxX2MwX2MwX2MxKF9pbnB1dCwgZmxvYXQyKGhhbGYyKHQueCwgMC4wKSkpOwoJfQoJQGlmICh0cnVlKSAKCXsKCQlvdXRDb2xvci54eXogKj0gb3V0Q29sb3IudzsKCX0KCXJldHVybiBvdXRDb2xvcjsKfQpoYWxmNCBPdmVycmlkZUlucHV0RnJhZ21lbnRQcm9jZXNzb3JfU3RhZ2UxX2MwKGhhbGY0IF9pbnB1dCkgCnsKCXJldHVybiBDbGFtcGVkR3JhZGllbnRFZmZlY3RfU3RhZ2UxX2MwX2MwKGZhbHNlID8gaGFsZjQoMCkgOiBoYWxmNCgxLjAwMDAwMCwgMS4wMDAwMDAsIDEuMDAwMDAwLCAxLjAwMDAwMCkpOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1N0YWdlMDsKCW91dHB1dENvbG9yX1N0YWdlMCA9IHZjb2xvcl9TdGFnZTA7Cgljb25zdCBoYWxmNCBvdXRwdXRDb3ZlcmFnZV9TdGFnZTAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TdGFnZTE7CglvdXRwdXRfU3RhZ2UxID0gT3ZlcnJpZGVJbnB1dEZyYWdtZW50UHJvY2Vzc29yX1N0YWdlMV9jMChvdXRwdXRDb2xvcl9TdGFnZTApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dF9TdGFnZTEgKiBvdXRwdXRDb3ZlcmFnZV9TdGFnZTA7Cgl9Cn0KAAAAAAABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAADAAAACAAAAHBvc2l0aW9uBQAAAGNvbG9yAAAACgAAAGxvY2FsQ29vcmQAAAEAAAAAAAAA","AWQAGAAAQAAIXCEPAGGP777777777737AAAAAAAAAAAIAXGIAEAAAAAAKQAAAAAAAIAAAAAQGIBAA":"BAAAAExTS1MJAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TdGFnZTA7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiBmbG9hdDQgaW5DaXJjbGVFZGdlOwpvdXQgZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfU3RhZ2UwOwpvdXQgaGFsZjQgdmluQ29sb3JfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TdGFnZTAgPSBpbkNpcmNsZUVkZ2U7Cgl2aW5Db2xvcl9TdGFnZTAgPSBpbkNvbG9yOwoJZmxvYXQyIF90bXBfMF9pblBvc2l0aW9uID0gaW5Qb3NpdGlvbjsKCWZsb2F0MiBfdG1wXzFfaW5Qb3NpdGlvbiA9IHVsb2NhbE1hdHJpeF9TdGFnZTAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1N0YWdlMC55dzsKCXNrX1Bvc2l0aW9uID0gZmxvYXQ0KF90bXBfMF9pblBvc2l0aW9uLnggLCBfdG1wXzBfaW5Qb3NpdGlvbi55LCAwLCAxKTsKfQoAAAAAAQEAAAAAAAABAQAaBQAAdW5pZm9ybSBmbG9hdDQgdXJlY3RVbmlmb3JtX1N0YWdlMV9jMDsKaW4gZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfU3RhZ2UwOwppbiBoYWxmNCB2aW5Db2xvcl9TdGFnZTA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IEFBUmVjdEVmZmVjdF9TdGFnZTFfYzAoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQ0IHByZXZSZWN0ID0gZmxvYXQ0KC0xLjAwMDAwMCwgLTEuMDAwMDAwLCAtMS4wMDAwMDAsIC0xLjAwMDAwMCk7CgloYWxmIGNvdmVyYWdlOwoJQHN3aXRjaCAoMykgCgl7CgkJY2FzZSAwOiAgICBjYXNlIDI6ICAgICAgICBjb3ZlcmFnZSA9IGhhbGYoYWxsKGdyZWF0ZXJUaGFuKGZsb2F0NChza19GcmFnQ29vcmQueHksIHVyZWN0VW5pZm9ybV9TdGFnZTFfYzAuencpLCBmbG9hdDQodXJlY3RVbmlmb3JtX1N0YWdlMV9jMC54eSwgc2tfRnJhZ0Nvb3JkLnh5KSkpID8gMSA6IDApOwoJCWJyZWFrOwoJCWRlZmF1bHQ6ICAgICAgICBoYWxmNCBkaXN0czQgPSBjbGFtcChoYWxmNCgxLjAsIDEuMCwgLTEuMCwgLTEuMCkgKiBoYWxmNChza19GcmFnQ29vcmQueHl4eSAtIHVyZWN0VW5pZm9ybV9TdGFnZTFfYzApLCAwLjAsIDEuMCk7CgkJaGFsZjIgZGlzdHMyID0gKGRpc3RzNC54eSArIGRpc3RzNC56dykgLSAxLjA7CgkJY292ZXJhZ2UgPSBkaXN0czIueCAqIGRpc3RzMi55OwoJfQoJQGlmICgzID09IDIgfHwgMyA9PSAzKSAKCXsKCQljb3ZlcmFnZSA9IDEuMCAtIGNvdmVyYWdlOwoJfQoJcmV0dXJuIF9pbnB1dCAqIGNvdmVyYWdlOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQ0IGNpcmNsZUVkZ2U7CgljaXJjbGVFZGdlID0gdmluQ2lyY2xlRWRnZV9TdGFnZTA7CgloYWxmNCBvdXRwdXRDb2xvcl9TdGFnZTA7CglvdXRwdXRDb2xvcl9TdGFnZTAgPSB2aW5Db2xvcl9TdGFnZTA7CglmbG9hdCBkID0gbGVuZ3RoKGNpcmNsZUVkZ2UueHkpOwoJaGFsZiBkaXN0YW5jZVRvT3V0ZXJFZGdlID0gaGFsZihjaXJjbGVFZGdlLnogKiAoMS4wIC0gZCkpOwoJaGFsZiBlZGdlQWxwaGEgPSBzYXR1cmF0ZShkaXN0YW5jZVRvT3V0ZXJFZGdlKTsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1N0YWdlMCA9IGhhbGY0KGVkZ2VBbHBoYSk7CgloYWxmNCBvdXRwdXRfU3RhZ2UxOwoJb3V0cHV0X1N0YWdlMSA9IEFBUmVjdEVmZmVjdF9TdGFnZTFfYzAob3V0cHV0Q292ZXJhZ2VfU3RhZ2UwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TdGFnZTAgKiBvdXRwdXRfU3RhZ2UxOwoJfQp9CgAAAAEBAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IADAAAAGluQ2lyY2xlRWRnZQEAAAAAAAAA","KYMAAAAABCYIR6AYYAAAAAAAAAAAAAGIQUKAAAAAABAAKAAAAAQAAAAAAAQSGAAA":"BAAAAExTS1NwAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiBmbG9hdDQgaW5RdWFkRWRnZTsKb3V0IGZsb2F0NCB2UXVhZEVkZ2VfU3RhZ2UwOwpvdXQgaGFsZjQgdmluQ29sb3JfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBRdWFkRWRnZQoJdlF1YWRFZGdlX1N0YWdlMCA9IGluUXVhZEVkZ2U7Cgl2aW5Db2xvcl9TdGFnZTAgPSBpbkNvbG9yOwoJZmxvYXQyIF90bXBfMF9pblBvc2l0aW9uID0gaW5Qb3NpdGlvbjsKCXNrX1Bvc2l0aW9uID0gZmxvYXQ0KF90bXBfMF9pblBvc2l0aW9uLnggLCBfdG1wXzBfaW5Qb3NpdGlvbi55LCAwLCAxKTsKfQoAAQEAAAAAAAABAQBdBgAAdW5pZm9ybSBmbG9hdDQgdXJlY3RVbmlmb3JtX1N0YWdlMV9jMDsKaW4gZmxvYXQ0IHZRdWFkRWRnZV9TdGFnZTA7CmluIGhhbGY0IHZpbkNvbG9yX1N0YWdlMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgQUFSZWN0RWZmZWN0X1N0YWdlMV9jMChoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDQgcHJldlJlY3QgPSBmbG9hdDQoLTEuMDAwMDAwLCAtMS4wMDAwMDAsIC0xLjAwMDAwMCwgLTEuMDAwMDAwKTsKCWhhbGYgY292ZXJhZ2U7CglAc3dpdGNoICgxKSAKCXsKCQljYXNlIDA6ICAgIGNhc2UgMjogICAgICAgIGNvdmVyYWdlID0gaGFsZihhbGwoZ3JlYXRlclRoYW4oZmxvYXQ0KHNrX0ZyYWdDb29yZC54eSwgdXJlY3RVbmlmb3JtX1N0YWdlMV9jMC56dyksIGZsb2F0NCh1cmVjdFVuaWZvcm1fU3RhZ2UxX2MwLnh5LCBza19GcmFnQ29vcmQueHkpKSkgPyAxIDogMCk7CgkJYnJlYWs7CgkJZGVmYXVsdDogICAgICAgIGhhbGY0IGRpc3RzNCA9IGNsYW1wKGhhbGY0KDEuMCwgMS4wLCAtMS4wLCAtMS4wKSAqIGhhbGY0KHNrX0ZyYWdDb29yZC54eXh5IC0gdXJlY3RVbmlmb3JtX1N0YWdlMV9jMCksIDAuMCwgMS4wKTsKCQloYWxmMiBkaXN0czIgPSAoZGlzdHM0Lnh5ICsgZGlzdHM0Lnp3KSAtIDEuMDsKCQljb3ZlcmFnZSA9IGRpc3RzMi54ICogZGlzdHMyLnk7Cgl9CglAaWYgKDEgPT0gMiB8fCAxID09IDMpIAoJewoJCWNvdmVyYWdlID0gMS4wIC0gY292ZXJhZ2U7Cgl9CglyZXR1cm4gX2lucHV0ICogY292ZXJhZ2U7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRFZGdlCgloYWxmNCBvdXRwdXRDb2xvcl9TdGFnZTA7CglvdXRwdXRDb2xvcl9TdGFnZTAgPSB2aW5Db2xvcl9TdGFnZTA7CgloYWxmIGVkZ2VBbHBoYTsKCWhhbGYyIGR1dmR4ID0gaGFsZjIoZEZkeCh2UXVhZEVkZ2VfU3RhZ2UwLnh5KSk7CgloYWxmMiBkdXZkeSA9IGhhbGYyKGRGZHkodlF1YWRFZGdlX1N0YWdlMC54eSkpOwoJaWYgKHZRdWFkRWRnZV9TdGFnZTAueiA+IDAuMCAmJiB2UXVhZEVkZ2VfU3RhZ2UwLncgPiAwLjApIAoJewoJCWVkZ2VBbHBoYSA9IGhhbGYobWluKG1pbih2UXVhZEVkZ2VfU3RhZ2UwLnosIHZRdWFkRWRnZV9TdGFnZTAudykgKyAwLjUsIDEuMCkpOwoJfQoJZWxzZSAKCXsKCQloYWxmMiBnRiA9IGhhbGYyKGhhbGYoMi4wKnZRdWFkRWRnZV9TdGFnZTAueCpkdXZkeC54IC0gZHV2ZHgueSksICAgICAgICAgICAgICAgICBoYWxmKDIuMCp2UXVhZEVkZ2VfU3RhZ2UwLngqZHV2ZHkueCAtIGR1dmR5LnkpKTsKCQllZGdlQWxwaGEgPSBoYWxmKHZRdWFkRWRnZV9TdGFnZTAueCp2UXVhZEVkZ2VfU3RhZ2UwLnggLSB2UXVhZEVkZ2VfU3RhZ2UwLnkpOwoJCWVkZ2VBbHBoYSA9IHNhdHVyYXRlKDAuNSAtIGVkZ2VBbHBoYSAvIGxlbmd0aChnRikpOwoJfQoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfU3RhZ2UwID0gaGFsZjQoZWRnZUFscGhhKTsKCWhhbGY0IG91dHB1dF9TdGFnZTE7CglvdXRwdXRfU3RhZ2UxID0gQUFSZWN0RWZmZWN0X1N0YWdlMV9jMChvdXRwdXRDb3ZlcmFnZV9TdGFnZTApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1N0YWdlMCAqIG91dHB1dF9TdGFnZTE7Cgl9Cn0KAAAAAAEBAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IACgAAAGluUXVhZEVkZ2UAAAEAAAAAAAAA","IGIAAAAAAIAAAAABCYBR6AAAAAAAAAAAACQAEAAAAAIAAAAAQCIBCAAAAA":"BAAAAExTS1MxAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkhhaXJRdWFkRWRnZTsKb3V0IGhhbGY0IHZIYWlyUXVhZEVkZ2VfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBRdWFkCgl2SGFpclF1YWRFZGdlX1N0YWdlMCA9IGluSGFpclF1YWRFZGdlOwoJZmxvYXQyIF90bXBfMF9pblBvc2l0aW9uID0gaW5Qb3NpdGlvbjsKCXNrX1Bvc2l0aW9uID0gZmxvYXQ0KF90bXBfMF9pblBvc2l0aW9uLnggLCBfdG1wXzBfaW5Qb3NpdGlvbi55LCAwLCAxKTsKfQoAAAAAAAAAAAAAAAAAAAA7AwAAdW5pZm9ybSBoYWxmNCB1Q29sb3JfU3RhZ2UwOwp1bmlmb3JtIGhhbGYgdUNvdmVyYWdlX1N0YWdlMDsKaW4gaGFsZjQgdkhhaXJRdWFkRWRnZV9TdGFnZTA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBRdWFkCgloYWxmNCBvdXRwdXRDb2xvcl9TdGFnZTA7CglvdXRwdXRDb2xvcl9TdGFnZTAgPSB1Q29sb3JfU3RhZ2UwOwoJaGFsZiBlZGdlQWxwaGE7CgloYWxmMiBkdXZkeCA9IGhhbGYyKGRGZHgodkhhaXJRdWFkRWRnZV9TdGFnZTAueHkpKTsKCWhhbGYyIGR1dmR5ID0gaGFsZjIoZEZkeSh2SGFpclF1YWRFZGdlX1N0YWdlMC54eSkpOwoJaGFsZjIgZ0YgPSBoYWxmMigyLjAgKiB2SGFpclF1YWRFZGdlX1N0YWdlMC54ICogZHV2ZHgueCAtIGR1dmR4LnksICAgICAgICAgICAgICAgMi4wICogdkhhaXJRdWFkRWRnZV9TdGFnZTAueCAqIGR1dmR5LnggLSBkdXZkeS55KTsKCWVkZ2VBbHBoYSA9IGhhbGYodkhhaXJRdWFkRWRnZV9TdGFnZTAueCAqIHZIYWlyUXVhZEVkZ2VfU3RhZ2UwLnggLSB2SGFpclF1YWRFZGdlX1N0YWdlMC55KTsKCWVkZ2VBbHBoYSA9IHNxcnQoZWRnZUFscGhhICogZWRnZUFscGhhIC8gZG90KGdGLCBnRikpOwoJZWRnZUFscGhhID0gbWF4KDEuMCAtIGVkZ2VBbHBoYSwgMC4wKTsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1N0YWdlMCA9IGhhbGY0KHVDb3ZlcmFnZV9TdGFnZTAgKiBlZGdlQWxwaGEpOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1N0YWdlMCAqIG91dHB1dENvdmVyYWdlX1N0YWdlMDsKCX0KfQoAAAAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAIAAAAKAAAAaW5Qb3NpdGlvbgAADgAAAGluSGFpclF1YWRFZGdlAAABAAAAAAAAAA==","K4IAAAAAAAGAAAAAAIWP577774BSZ7X7777QCAAAAAAAAAAAFIQA2AAAAAAQCBAAAAAHIJBAIAAAAAAACMAYIDAAAQCAAAAAAAAKAAQAAAABAAAAACAJAEIAAA":"BAAAAExTS1NpAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TdGFnZTFfYzBfYzA7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzBfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCXNrX1Bvc2l0aW9uID0gZmxvYXQ0KHBvc2l0aW9uLnggLCBwb3NpdGlvbi55LCAwLCAxKTsKCXsKCQl2VHJhbnNmb3JtZWRDb29yZHNfMF9TdGFnZTAgPSAoKCh1bWF0cml4X1N0YWdlMV9jMF9jMCkpICogbG9jYWxDb29yZC54eTEpLnh5OwoJfQp9CgAAAAAAAAAAAAAAAAAAAHUEAAB1bmlmb3JtIGZsb2F0M3gzIHVtYXRyaXhfU3RhZ2UxX2MwX2MwOwp1bmlmb3JtIGZsb2F0NCB1Y2xhbXBfU3RhZ2UxX2MwX2MwX2MwOwp1bmlmb3JtIHNhbXBsZXIyRCB1VGV4dHVyZVNhbXBsZXJfMF9TdGFnZTE7CmluIGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMF9TdGFnZTA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IFRleHR1cmVFZmZlY3RfU3RhZ2UxX2MwX2MwX2MwKGhhbGY0IF9pbnB1dCkgCnsKCWZsb2F0MiBpbkNvb3JkID0gdlRyYW5zZm9ybWVkQ29vcmRzXzBfU3RhZ2UwOwoJZmxvYXQyIHN1YnNldENvb3JkOwoJc3Vic2V0Q29vcmQueCA9IGluQ29vcmQueDsKCXN1YnNldENvb3JkLnkgPSBpbkNvb3JkLnk7CglmbG9hdDIgY2xhbXBlZENvb3JkOwoJY2xhbXBlZENvb3JkID0gY2xhbXAoc3Vic2V0Q29vcmQsIHVjbGFtcF9TdGFnZTFfYzBfYzBfYzAueHksIHVjbGFtcF9TdGFnZTFfYzBfYzBfYzAuencpOwoJaGFsZjQgdGV4dHVyZUNvbG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1N0YWdlMSwgY2xhbXBlZENvb3JkKTsKCXJldHVybiB0ZXh0dXJlQ29sb3I7Cn0KaGFsZjQgTWF0cml4RWZmZWN0X1N0YWdlMV9jMF9jMChoYWxmNCBfaW5wdXQpIAp7CglyZXR1cm4gVGV4dHVyZUVmZmVjdF9TdGFnZTFfYzBfYzBfYzAoX2lucHV0KTsKfQpoYWxmNCBCbGVuZF9TdGFnZTFfYzAoaGFsZjQgX2lucHV0KSAKewoJLy8gQmxlbmQgbW9kZTogTW9kdWxhdGUgKENvbXBvc2UtT25lIGJlaGF2aW9yKQoJcmV0dXJuIGJsZW5kX21vZHVsYXRlKE1hdHJpeEVmZmVjdF9TdGFnZTFfYzBfYzAoaGFsZjQoMSkpLCBfaW5wdXQpOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1N0YWdlMCA9IGhhbGY0KDEpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfU3RhZ2UwID0gaGFsZjQoMSk7CgloYWxmNCBvdXRwdXRfU3RhZ2UxOwoJb3V0cHV0X1N0YWdlMSA9IEJsZW5kX1N0YWdlMV9jMChvdXRwdXRDb2xvcl9TdGFnZTApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dF9TdGFnZTEgKiBvdXRwdXRDb3ZlcmFnZV9TdGFnZTA7Cgl9Cn0KAAAAAAAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24KAAAAbG9jYWxDb29yZAAAAQAAAAAAAAA=","AWAAGAAAQAAIXCEPAGGP777777777737AAAAAAAAAAAAAUABAAAAACAAAAAEBSAI":"BAAAAExTS1OzAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiBmbG9hdDQgaW5DaXJjbGVFZGdlOwpvdXQgZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfU3RhZ2UwOwpvdXQgaGFsZjQgdmluQ29sb3JfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TdGFnZTAgPSBpbkNpcmNsZUVkZ2U7Cgl2aW5Db2xvcl9TdGFnZTAgPSBpbkNvbG9yOwoJZmxvYXQyIF90bXBfMF9pblBvc2l0aW9uID0gaW5Qb3NpdGlvbjsKCWZsb2F0MiBfdG1wXzFfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247Cglza19Qb3NpdGlvbiA9IGZsb2F0NChfdG1wXzBfaW5Qb3NpdGlvbi54ICwgX3RtcF8wX2luUG9zaXRpb24ueSwgMCwgMSk7Cn0KAAAAAAAAAAAAAAAAACYCAABpbiBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TdGFnZTA7CmluIGhhbGY0IHZpbkNvbG9yX1N0YWdlMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIENpcmNsZUdlb21ldHJ5UHJvY2Vzc29yCglmbG9hdDQgY2lyY2xlRWRnZTsKCWNpcmNsZUVkZ2UgPSB2aW5DaXJjbGVFZGdlX1N0YWdlMDsKCWhhbGY0IG91dHB1dENvbG9yX1N0YWdlMDsKCW91dHB1dENvbG9yX1N0YWdlMCA9IHZpbkNvbG9yX1N0YWdlMDsKCWZsb2F0IGQgPSBsZW5ndGgoY2lyY2xlRWRnZS54eSk7CgloYWxmIGRpc3RhbmNlVG9PdXRlckVkZ2UgPSBoYWxmKGNpcmNsZUVkZ2UueiAqICgxLjAgLSBkKSk7CgloYWxmIGVkZ2VBbHBoYSA9IHNhdHVyYXRlKGRpc3RhbmNlVG9PdXRlckVkZ2UpOwoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfU3RhZ2UwID0gaGFsZjQoZWRnZUFscGhhKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TdGFnZTAgKiBvdXRwdXRDb3ZlcmFnZV9TdGFnZTA7Cgl9Cn0KAAAAAAAAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAwAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAMAAAAaW5DaXJjbGVFZGdlAQAAAAAAAAA=","B2AAQAAAAQAAAAABC3777777AAOAAAAAAAAAAAAAUABAAAAACAAAAAEASAIQAAAA":"BAAAAExTS1M3AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmIGluQ292ZXJhZ2U7Cm91dCBoYWxmIHZpbkNvdmVyYWdlX1N0YWdlMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgRGVmYXVsdEdlb21ldHJ5UHJvY2Vzc29yCglmbG9hdDIgX3RtcF8wX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJdmluQ292ZXJhZ2VfU3RhZ2UwID0gaW5Db3ZlcmFnZTsKCXNrX1Bvc2l0aW9uID0gZmxvYXQ0KF90bXBfMF9pblBvc2l0aW9uLnggLCBfdG1wXzBfaW5Qb3NpdGlvbi55LCAwLCAxKTsKfQoAAAAAAAAAAAAAAAAAiQEAAHVuaWZvcm0gaGFsZjQgdUNvbG9yX1N0YWdlMDsKaW4gaGFsZiB2aW5Db3ZlcmFnZV9TdGFnZTA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBEZWZhdWx0R2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1N0YWdlMDsKCW91dHB1dENvbG9yX1N0YWdlMCA9IHVDb2xvcl9TdGFnZTA7CgloYWxmIGFscGhhID0gMS4wOwoJYWxwaGEgPSB2aW5Db3ZlcmFnZV9TdGFnZTA7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TdGFnZTAgPSBoYWxmNChhbHBoYSk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfU3RhZ2UwICogb3V0cHV0Q292ZXJhZ2VfU3RhZ2UwOwoJfQp9CgAAAAAAAAABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAACAAAACgAAAGluUG9zaXRpb24AAAoAAABpbkNvdmVyYWdlAAABAAAAAAAAAA==","DIQAAAAAMAAAAABAYAROFA2CAIAAAABAAAAAAAAAAAACAF2SAAAAAAAACUAAAAEAAAAAAAEERQAAA":"BAAAAExTS1NQAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQyIHVBdGxhc1NpemVJbnZfU3RhZ2UwOwppbiBmbG9hdDIgaW5Qb3NpdGlvbjsKaW4gaGFsZjQgaW5Db2xvcjsKaW4gdXNob3J0MiBpblRleHR1cmVDb29yZHM7Cm91dCBmbG9hdDIgdlRleHR1cmVDb29yZHNfU3RhZ2UwOwpmbGF0IG91dCBmbG9hdCB2VGV4SW5kZXhfU3RhZ2UwOwpvdXQgaGFsZjQgdmluQ29sb3JfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBCaXRtYXBUZXh0CglpbnQgdGV4SWR4ID0gMDsKCWZsb2F0MiB1bm9ybVRleENvb3JkcyA9IGZsb2F0MihpblRleHR1cmVDb29yZHMueCwgaW5UZXh0dXJlQ29vcmRzLnkpOwoJdlRleHR1cmVDb29yZHNfU3RhZ2UwID0gdW5vcm1UZXhDb29yZHMgKiB1QXRsYXNTaXplSW52X1N0YWdlMDsKCXZUZXhJbmRleF9TdGFnZTAgPSBmbG9hdCh0ZXhJZHgpOwoJdmluQ29sb3JfU3RhZ2UwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247Cglza19Qb3NpdGlvbiA9IGZsb2F0NChpblBvc2l0aW9uLnggLCBpblBvc2l0aW9uLnksIDAsIDEpOwp9CgABAQAAAAAAAAEBAO0EAAB1bmlmb3JtIGZsb2F0NCB1cmVjdFVuaWZvcm1fU3RhZ2UxX2MwOwp1bmlmb3JtIHNhbXBsZXIyRCB1VGV4dHVyZVNhbXBsZXJfMF9TdGFnZTA7CmluIGZsb2F0MiB2VGV4dHVyZUNvb3Jkc19TdGFnZTA7CmZsYXQgaW4gZmxvYXQgdlRleEluZGV4X1N0YWdlMDsKaW4gaGFsZjQgdmluQ29sb3JfU3RhZ2UwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBBQVJlY3RFZmZlY3RfU3RhZ2UxX2MwKGhhbGY0IF9pbnB1dCkgCnsKCWZsb2F0NCBwcmV2UmVjdCA9IGZsb2F0NCgtMS4wMDAwMDAsIC0xLjAwMDAwMCwgLTEuMDAwMDAwLCAtMS4wMDAwMDApOwoJaGFsZiBjb3ZlcmFnZTsKCUBzd2l0Y2ggKDEpIAoJewoJCWNhc2UgMDogICAgY2FzZSAyOiAgICAgICAgY292ZXJhZ2UgPSBoYWxmKGFsbChncmVhdGVyVGhhbihmbG9hdDQoc2tfRnJhZ0Nvb3JkLnh5LCB1cmVjdFVuaWZvcm1fU3RhZ2UxX2MwLnp3KSwgZmxvYXQ0KHVyZWN0VW5pZm9ybV9TdGFnZTFfYzAueHksIHNrX0ZyYWdDb29yZC54eSkpKSA/IDEgOiAwKTsKCQlicmVhazsKCQlkZWZhdWx0OiAgICAgICAgaGFsZjQgZGlzdHM0ID0gY2xhbXAoaGFsZjQoMS4wLCAxLjAsIC0xLjAsIC0xLjApICogaGFsZjQoc2tfRnJhZ0Nvb3JkLnh5eHkgLSB1cmVjdFVuaWZvcm1fU3RhZ2UxX2MwKSwgMC4wLCAxLjApOwoJCWhhbGYyIGRpc3RzMiA9IChkaXN0czQueHkgKyBkaXN0czQuencpIC0gMS4wOwoJCWNvdmVyYWdlID0gZGlzdHMyLnggKiBkaXN0czIueTsKCX0KCUBpZiAoMSA9PSAyIHx8IDEgPT0gMykgCgl7CgkJY292ZXJhZ2UgPSAxLjAgLSBjb3ZlcmFnZTsKCX0KCXJldHVybiBfaW5wdXQgKiBjb3ZlcmFnZTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgQml0bWFwVGV4dAoJaGFsZjQgb3V0cHV0Q29sb3JfU3RhZ2UwOwoJb3V0cHV0Q29sb3JfU3RhZ2UwID0gdmluQ29sb3JfU3RhZ2UwOwoJaGFsZjQgdGV4Q29sb3I7Cgl7CgkJdGV4Q29sb3IgPSBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfU3RhZ2UwLCB2VGV4dHVyZUNvb3Jkc19TdGFnZTApLnJycnI7Cgl9CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TdGFnZTAgPSB0ZXhDb2xvcjsKCWhhbGY0IG91dHB1dF9TdGFnZTE7CglvdXRwdXRfU3RhZ2UxID0gQUFSZWN0RWZmZWN0X1N0YWdlMV9jMChvdXRwdXRDb3ZlcmFnZV9TdGFnZTApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1N0YWdlMCAqIG91dHB1dF9TdGFnZTE7Cgl9Cn0KAAAAAAEBAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IADwAAAGluVGV4dHVyZUNvb3JkcwABAAAAAAAAAA==","GFQQAAAAMAAGGADDACRQAAAAMAACHQDCABRQAI7CAMAAAAAAKQAAAAAAAIAAAAAQGIBAAAA":"BAAAAExTS1PfCwAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0NCByYWRpaV9zZWxlY3RvcjsKaW4gZmxvYXQ0IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHM7CmluIGZsb2F0NCBhYV9ibG9hdF9hbmRfY292ZXJhZ2U7CmluIGZsb2F0NCBza2V3OwppbiBmbG9hdDIgdHJhbnNsYXRlOwppbiBmbG9hdDQgcmFkaWlfeDsKaW4gZmxvYXQ0IHJhZGlpX3k7CmluIGhhbGY0IGNvbG9yOwpmbGF0IG91dCBoYWxmNCB2Y29sb3JfU3RhZ2UwOwpvdXQgZmxvYXQyIHZhcmNjb29yZF9TdGFnZTA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIEdyRmlsbFJSZWN0T3A6OlByb2Nlc3NvcgoJdmNvbG9yX1N0YWdlMCA9IGNvbG9yOwoJZmxvYXQgYWFfYmxvYXRfbXVsdGlwbGllciA9IDE7CglmbG9hdDIgY29ybmVyID0gY29ybmVyX2FuZF9yYWRpdXNfb3V0c2V0cy54eTsKCWZsb2F0MiByYWRpdXNfb3V0c2V0ID0gY29ybmVyX2FuZF9yYWRpdXNfb3V0c2V0cy56dzsKCWZsb2F0MiBhYV9ibG9hdF9kaXJlY3Rpb24gPSBhYV9ibG9hdF9hbmRfY292ZXJhZ2UueHk7CglmbG9hdCBpc19saW5lYXJfY292ZXJhZ2UgPSBhYV9ibG9hdF9hbmRfY292ZXJhZ2UudzsKCWZsb2F0MiBwaXhlbGxlbmd0aCA9IGludmVyc2VzcXJ0KGZsb2F0Mihkb3Qoc2tldy54eiwgc2tldy54eiksIGRvdChza2V3Lnl3LCBza2V3Lnl3KSkpOwoJZmxvYXQ0IG5vcm1hbGl6ZWRfYXhpc19kaXJzID0gc2tldyAqIHBpeGVsbGVuZ3RoLnh5eHk7CglmbG9hdDIgYXhpc3dpZHRocyA9IChhYnMobm9ybWFsaXplZF9heGlzX2RpcnMueHkpICsgYWJzKG5vcm1hbGl6ZWRfYXhpc19kaXJzLnp3KSk7CglmbG9hdDIgYWFfYmxvYXRyYWRpdXMgPSBheGlzd2lkdGhzICogcGl4ZWxsZW5ndGggKiAuNTsKCWZsb2F0NCByYWRpaV9hbmRfbmVpZ2hib3JzID0gcmFkaWlfc2VsZWN0b3IqIGZsb2F0NHg0KHJhZGlpX3gsIHJhZGlpX3ksIHJhZGlpX3gueXh3eiwgcmFkaWlfeS53enl4KTsKCWZsb2F0MiByYWRpaSA9IHJhZGlpX2FuZF9uZWlnaGJvcnMueHk7CglmbG9hdDIgbmVpZ2hib3JfcmFkaWkgPSByYWRpaV9hbmRfbmVpZ2hib3JzLnp3OwoJZmxvYXQgY292ZXJhZ2VfbXVsdGlwbGllciA9IDE7CglpZiAoYW55KGdyZWF0ZXJUaGFuKGFhX2Jsb2F0cmFkaXVzLCBmbG9hdDIoMSkpKSkgCgl7CgkJY29ybmVyID0gbWF4KGFicyhjb3JuZXIpLCBhYV9ibG9hdHJhZGl1cykgKiBzaWduKGNvcm5lcik7CgkJY292ZXJhZ2VfbXVsdGlwbGllciA9IDEgLyAobWF4KGFhX2Jsb2F0cmFkaXVzLngsIDEpICogbWF4KGFhX2Jsb2F0cmFkaXVzLnksIDEpKTsKCQlyYWRpaSA9IGZsb2F0MigwKTsKCX0KCWZsb2F0IGNvdmVyYWdlID0gYWFfYmxvYXRfYW5kX2NvdmVyYWdlLno7CglpZiAoYW55KGxlc3NUaGFuKHJhZGlpLCBhYV9ibG9hdHJhZGl1cyAqIDEuNSkpKSAKCXsKCQlyYWRpaSA9IGZsb2F0MigwKTsKCQlhYV9ibG9hdF9kaXJlY3Rpb24gPSBzaWduKGNvcm5lcik7CgkJaWYgKGNvdmVyYWdlID4gLjUpIAoJCXsKCQkJYWFfYmxvYXRfZGlyZWN0aW9uID0gLWFhX2Jsb2F0X2RpcmVjdGlvbjsKCQl9CgkJaXNfbGluZWFyX2NvdmVyYWdlID0gMTsKCX0KCWVsc2UgCgl7CgkJcmFkaWkgPSBjbGFtcChyYWRpaSwgcGl4ZWxsZW5ndGggKiAxLjUsIDIgLSBwaXhlbGxlbmd0aCAqIDEuNSk7CgkJbmVpZ2hib3JfcmFkaWkgPSBjbGFtcChuZWlnaGJvcl9yYWRpaSwgcGl4ZWxsZW5ndGggKiAxLjUsIDIgLSBwaXhlbGxlbmd0aCAqIDEuNSk7CgkJZmxvYXQyIHNwYWNpbmcgPSAyIC0gcmFkaWkgLSBuZWlnaGJvcl9yYWRpaTsKCQlmbG9hdDIgZXh0cmFfcGFkID0gbWF4KHBpeGVsbGVuZ3RoICogLjA2MjUgLSBzcGFjaW5nLCBmbG9hdDIoMCkpOwoJCXJhZGlpIC09IGV4dHJhX3BhZCAqIC41OwoJfQoJZmxvYXQyIGFhX291dHNldCA9IGFhX2Jsb2F0X2RpcmVjdGlvbiAqIGFhX2Jsb2F0cmFkaXVzICogYWFfYmxvYXRfbXVsdGlwbGllcjsKCWZsb2F0MiB2ZXJ0ZXhwb3MgPSBjb3JuZXIgKyByYWRpdXNfb3V0c2V0ICogcmFkaWkgKyBhYV9vdXRzZXQ7CglpZiAoY292ZXJhZ2UgPiAuNSkgCgl7CgkJaWYgKGFhX2Jsb2F0X2RpcmVjdGlvbi54ICE9IDAgJiYgdmVydGV4cG9zLnggKiBjb3JuZXIueCA8IDApIAoJCXsKCQkJZmxvYXQgYmFja3NldCA9IGFicyh2ZXJ0ZXhwb3MueCk7CgkJCXZlcnRleHBvcy54ID0gMDsKCQkJdmVydGV4cG9zLnkgKz0gYmFja3NldCAqIHNpZ24oY29ybmVyLnkpICogcGl4ZWxsZW5ndGgueS9waXhlbGxlbmd0aC54OwoJCQljb3ZlcmFnZSA9IChjb3ZlcmFnZSAtIC41KSAqIGFicyhjb3JuZXIueCkgLyAoYWJzKGNvcm5lci54KSArIGJhY2tzZXQpICsgLjU7CgkJfQoJCWlmIChhYV9ibG9hdF9kaXJlY3Rpb24ueSAhPSAwICYmIHZlcnRleHBvcy55ICogY29ybmVyLnkgPCAwKSAKCQl7CgkJCWZsb2F0IGJhY2tzZXQgPSBhYnModmVydGV4cG9zLnkpOwoJCQl2ZXJ0ZXhwb3MueSA9IDA7CgkJCXZlcnRleHBvcy54ICs9IGJhY2tzZXQgKiBzaWduKGNvcm5lci54KSAqIHBpeGVsbGVuZ3RoLngvcGl4ZWxsZW5ndGgueTsKCQkJY292ZXJhZ2UgPSAoY292ZXJhZ2UgLSAuNSkgKiBhYnMoY29ybmVyLnkpIC8gKGFicyhjb3JuZXIueSkgKyBiYWNrc2V0KSArIC41OwoJCX0KCX0KCWZsb2F0MngyIHNrZXdtYXRyaXggPSBmbG9hdDJ4Mihza2V3Lnh5LCBza2V3Lnp3KTsKCWZsb2F0MiBkZXZjb29yZCA9IHZlcnRleHBvcyAqIHNrZXdtYXRyaXggKyB0cmFuc2xhdGU7CglpZiAoMCAhPSBpc19saW5lYXJfY292ZXJhZ2UpIAoJewoJCXZhcmNjb29yZF9TdGFnZTAueHkgPSBmbG9hdDIoMCwgY292ZXJhZ2UgKiBjb3ZlcmFnZV9tdWx0aXBsaWVyKTsKCX0KCWVsc2UgCgl7CgkJZmxvYXQyIGFyY2Nvb3JkID0gMSAtIGFicyhyYWRpdXNfb3V0c2V0KSArIGFhX291dHNldC9yYWRpaSAqIGNvcm5lcjsKCQl2YXJjY29vcmRfU3RhZ2UwLnh5ID0gZmxvYXQyKGFyY2Nvb3JkLngrMSwgYXJjY29vcmQueSk7Cgl9Cglza19Qb3NpdGlvbiA9IGZsb2F0NChkZXZjb29yZC54ICwgZGV2Y29vcmQueSwgMCwgMSk7Cn0KAAAAAAAAAAAAAAAAAIcCAABmbGF0IGluIGhhbGY0IHZjb2xvcl9TdGFnZTA7CmluIGZsb2F0MiB2YXJjY29vcmRfU3RhZ2UwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgR3JGaWxsUlJlY3RPcDo6UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TdGFnZTA7CglvdXRwdXRDb2xvcl9TdGFnZTAgPSB2Y29sb3JfU3RhZ2UwOwoJZmxvYXQgeF9wbHVzXzE9dmFyY2Nvb3JkX1N0YWdlMC54LCB5PXZhcmNjb29yZF9TdGFnZTAueTsKCWhhbGYgY292ZXJhZ2U7CglpZiAoMCA9PSB4X3BsdXNfMSkgCgl7CgkJY292ZXJhZ2UgPSBoYWxmKHkpOwoJfQoJZWxzZSAKCXsKCQlmbG9hdCBmbiA9IHhfcGx1c18xICogKHhfcGx1c18xIC0gMik7CgkJZm4gPSBmbWEoeSx5LCBmbik7CgkJZmxvYXQgZm53aWR0aCA9IGZ3aWR0aChmbik7CgkJY292ZXJhZ2UgPSAuNSAtIGhhbGYoZm4vZm53aWR0aCk7CgkJY292ZXJhZ2UgPSBjbGFtcChjb3ZlcmFnZSwgMCwgMSk7Cgl9CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TdGFnZTAgPSBoYWxmNChjb3ZlcmFnZSk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfU3RhZ2UwICogb3V0cHV0Q292ZXJhZ2VfU3RhZ2UwOwoJfQp9CgAAAAAAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAACAAAAA4AAAByYWRpaV9zZWxlY3RvcgAAGQAAAGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHMAAAAVAAAAYWFfYmxvYXRfYW5kX2NvdmVyYWdlAAAABAAAAHNrZXcJAAAAdHJhbnNsYXRlAAAABwAAAHJhZGlpX3gABwAAAHJhZGlpX3kABQAAAGNvbG9yAAAAAQAAAAAAAAA=","AWQAGAAAQAAIXCEPAGGP777777777737AAAAAAAAAAAIAXCIAEAAAAAAKQAAAAAAAIAAAAAQGIBAA":"BAAAAExTS1MJAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TdGFnZTA7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiBmbG9hdDQgaW5DaXJjbGVFZGdlOwpvdXQgZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfU3RhZ2UwOwpvdXQgaGFsZjQgdmluQ29sb3JfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TdGFnZTAgPSBpbkNpcmNsZUVkZ2U7Cgl2aW5Db2xvcl9TdGFnZTAgPSBpbkNvbG9yOwoJZmxvYXQyIF90bXBfMF9pblBvc2l0aW9uID0gaW5Qb3NpdGlvbjsKCWZsb2F0MiBfdG1wXzFfaW5Qb3NpdGlvbiA9IHVsb2NhbE1hdHJpeF9TdGFnZTAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1N0YWdlMC55dzsKCXNrX1Bvc2l0aW9uID0gZmxvYXQ0KF90bXBfMF9pblBvc2l0aW9uLnggLCBfdG1wXzBfaW5Qb3NpdGlvbi55LCAwLCAxKTsKfQoAAAAAAQEAAAAAAAABAQAaBQAAdW5pZm9ybSBmbG9hdDQgdXJlY3RVbmlmb3JtX1N0YWdlMV9jMDsKaW4gZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfU3RhZ2UwOwppbiBoYWxmNCB2aW5Db2xvcl9TdGFnZTA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IEFBUmVjdEVmZmVjdF9TdGFnZTFfYzAoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQ0IHByZXZSZWN0ID0gZmxvYXQ0KC0xLjAwMDAwMCwgLTEuMDAwMDAwLCAtMS4wMDAwMDAsIC0xLjAwMDAwMCk7CgloYWxmIGNvdmVyYWdlOwoJQHN3aXRjaCAoMSkgCgl7CgkJY2FzZSAwOiAgICBjYXNlIDI6ICAgICAgICBjb3ZlcmFnZSA9IGhhbGYoYWxsKGdyZWF0ZXJUaGFuKGZsb2F0NChza19GcmFnQ29vcmQueHksIHVyZWN0VW5pZm9ybV9TdGFnZTFfYzAuencpLCBmbG9hdDQodXJlY3RVbmlmb3JtX1N0YWdlMV9jMC54eSwgc2tfRnJhZ0Nvb3JkLnh5KSkpID8gMSA6IDApOwoJCWJyZWFrOwoJCWRlZmF1bHQ6ICAgICAgICBoYWxmNCBkaXN0czQgPSBjbGFtcChoYWxmNCgxLjAsIDEuMCwgLTEuMCwgLTEuMCkgKiBoYWxmNChza19GcmFnQ29vcmQueHl4eSAtIHVyZWN0VW5pZm9ybV9TdGFnZTFfYzApLCAwLjAsIDEuMCk7CgkJaGFsZjIgZGlzdHMyID0gKGRpc3RzNC54eSArIGRpc3RzNC56dykgLSAxLjA7CgkJY292ZXJhZ2UgPSBkaXN0czIueCAqIGRpc3RzMi55OwoJfQoJQGlmICgxID09IDIgfHwgMSA9PSAzKSAKCXsKCQljb3ZlcmFnZSA9IDEuMCAtIGNvdmVyYWdlOwoJfQoJcmV0dXJuIF9pbnB1dCAqIGNvdmVyYWdlOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQ0IGNpcmNsZUVkZ2U7CgljaXJjbGVFZGdlID0gdmluQ2lyY2xlRWRnZV9TdGFnZTA7CgloYWxmNCBvdXRwdXRDb2xvcl9TdGFnZTA7CglvdXRwdXRDb2xvcl9TdGFnZTAgPSB2aW5Db2xvcl9TdGFnZTA7CglmbG9hdCBkID0gbGVuZ3RoKGNpcmNsZUVkZ2UueHkpOwoJaGFsZiBkaXN0YW5jZVRvT3V0ZXJFZGdlID0gaGFsZihjaXJjbGVFZGdlLnogKiAoMS4wIC0gZCkpOwoJaGFsZiBlZGdlQWxwaGEgPSBzYXR1cmF0ZShkaXN0YW5jZVRvT3V0ZXJFZGdlKTsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1N0YWdlMCA9IGhhbGY0KGVkZ2VBbHBoYSk7CgloYWxmNCBvdXRwdXRfU3RhZ2UxOwoJb3V0cHV0X1N0YWdlMSA9IEFBUmVjdEVmZmVjdF9TdGFnZTFfYzAob3V0cHV0Q292ZXJhZ2VfU3RhZ2UwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TdGFnZTAgKiBvdXRwdXRfU3RhZ2UxOwoJfQp9CgAAAAEBAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IADAAAAGluQ2lyY2xlRWRnZQEAAAAAAAAA","K5IAAAAAAAMAAAAAARMPZ72HPQCFR7H7777QGAAAAAAAAAAAIRDQAAAEAAAAAMARAAAAAAAAAAAAAAAAFIAAAAAAAEAAAAAIDEAQAAA":"BAAAAExTS1OpAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TdGFnZTFfYzA7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7CmluIGZsb2F0MiBsb2NhbENvb3JkOwpmbGF0IG91dCBoYWxmNCB2Y29sb3JfU3RhZ2UwOwpvdXQgZmxvYXQyIHZUcmFuc2Zvcm1lZENvb3Jkc18wX1N0YWdlMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgl2Y29sb3JfU3RhZ2UwID0gY29sb3I7Cglza19Qb3NpdGlvbiA9IGZsb2F0NChwb3NpdGlvbi54ICwgcG9zaXRpb24ueSwgMCwgMSk7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzBfU3RhZ2UwID0gKCgodW1hdHJpeF9TdGFnZTFfYzApKSAqIGxvY2FsQ29vcmQueHkxKS54eTsKCX0KfQoAAAAAAAAAAAAAAAAAAADmAgAAdW5pZm9ybSBmbG9hdDN4MyB1bWF0cml4X1N0YWdlMV9jMDsKdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfU3RhZ2UxOwpmbGF0IGluIGhhbGY0IHZjb2xvcl9TdGFnZTA7CmluIGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMF9TdGFnZTA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IFRleHR1cmVFZmZlY3RfU3RhZ2UxX2MwX2MwKGhhbGY0IF9pbnB1dCkgCnsKCXJldHVybiBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfU3RhZ2UxLCB2VHJhbnNmb3JtZWRDb29yZHNfMF9TdGFnZTApLnJycnI7Cn0KaGFsZjQgTWF0cml4RWZmZWN0X1N0YWdlMV9jMChoYWxmNCBfaW5wdXQpIAp7CglyZXR1cm4gVGV4dHVyZUVmZmVjdF9TdGFnZTFfYzBfYzAoX2lucHV0KTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TdGFnZTA7CglvdXRwdXRDb2xvcl9TdGFnZTAgPSB2Y29sb3JfU3RhZ2UwOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfU3RhZ2UwID0gaGFsZjQoMSk7CgloYWxmNCBvdXRwdXRfU3RhZ2UxOwoJb3V0cHV0X1N0YWdlMSA9IE1hdHJpeEVmZmVjdF9TdGFnZTFfYzAob3V0cHV0Q292ZXJhZ2VfU3RhZ2UwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TdGFnZTAgKiBvdXRwdXRfU3RhZ2UxOwoJfQp9CgAAAAAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAMAAAAIAAAAcG9zaXRpb24FAAAAY29sb3IAAAAKAAAAbG9jYWxDb29yZAAAAQAAAAAAAAA=","K4QACAAAAAGAAAAAAIWP57ZDH37P7777777QCAAAAAAAAAAAMIQHSAAAAAAQAAAAABKAAAAAAABAAAAACAZAEAAAAA":"BAAAAExTS1PvAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7Cm91dCBoYWxmNCB2Y29sb3JfU3RhZ2UwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCXZjb2xvcl9TdGFnZTAgPSBjb2xvcjsKCXNrX1Bvc2l0aW9uID0gZmxvYXQ0KHBvc2l0aW9uLnggLCBwb3NpdGlvbi55LCAwLCAxKTsKfQoAAAEBAAAAAAAAAQEADAMAAHVuaWZvcm0gZmxvYXQ0IHVpbm5lclJlY3RfU3RhZ2UxX2MwOwp1bmlmb3JtIGhhbGYyIHVyYWRpdXNQbHVzSGFsZl9TdGFnZTFfYzA7CmluIGhhbGY0IHZjb2xvcl9TdGFnZTA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IENpcmN1bGFyUlJlY3RfU3RhZ2UxX2MwKGhhbGY0IF9pbnB1dCkgCnsKCWZsb2F0MiBkeHkwID0gdWlubmVyUmVjdF9TdGFnZTFfYzAuTFQgLSBza19GcmFnQ29vcmQueHk7CglmbG9hdDIgZHh5MSA9IHNrX0ZyYWdDb29yZC54eSAtIHVpbm5lclJlY3RfU3RhZ2UxX2MwLlJCOwoJZmxvYXQyIGR4eSA9IG1heChtYXgoZHh5MCwgZHh5MSksIDAuMCk7CgloYWxmIGFscGhhID0gaGFsZihzYXR1cmF0ZSh1cmFkaXVzUGx1c0hhbGZfU3RhZ2UxX2MwLnggLSBsZW5ndGgoZHh5KSkpOwoJcmV0dXJuIF9pbnB1dCAqIGFscGhhOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1N0YWdlMDsKCW91dHB1dENvbG9yX1N0YWdlMCA9IHZjb2xvcl9TdGFnZTA7Cgljb25zdCBoYWxmNCBvdXRwdXRDb3ZlcmFnZV9TdGFnZTAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TdGFnZTE7CglvdXRwdXRfU3RhZ2UxID0gQ2lyY3VsYXJSUmVjdF9TdGFnZTFfYzAob3V0cHV0Q292ZXJhZ2VfU3RhZ2UwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TdGFnZTAgKiBvdXRwdXRfU3RhZ2UxOwoJfQp9CgABAQABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAACAAAACAAAAHBvc2l0aW9uBQAAAGNvbG9yAAAAAQAAAAAAAAA="}} \ No newline at end of file diff --git a/test/geo/countries_test.dart b/test/geo/countries_test.dart index 127053522..e81d5e7c2 100644 --- a/test/geo/countries_test.dart +++ b/test/geo/countries_test.dart @@ -1,7 +1,7 @@ import 'package:aves/geo/countries.dart'; import 'package:aves/geo/topojson.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:latlong/latlong.dart'; +import 'package:latlong2/latlong.dart'; void main() { // [lng, lat, z] diff --git a/test/geo/format_test.dart b/test/geo/format_test.dart index 66181ba60..4c97054ab 100644 --- a/test/geo/format_test.dart +++ b/test/geo/format_test.dart @@ -1,5 +1,5 @@ import 'package:aves/geo/format.dart'; -import 'package:latlong/latlong.dart'; +import 'package:latlong2/latlong.dart'; import 'package:test/test.dart'; void main() { diff --git a/whatsnew/whatsnew-en-US b/whatsnew/whatsnew-en-US index be5251a35..3cbdf3c75 100644 --- a/whatsnew/whatsnew-en-US +++ b/whatsnew/whatsnew-en-US @@ -1,6 +1,6 @@ Thanks for using Aves! -v1.4.0: -- improved video support -- more consistent and comprehensive info for videos -- options to auto play and loop videos +v1.4.1: +- motion photo support +- handle share intent +- fixes to handle large MP4 and PSD files Full changelog available on Github \ No newline at end of file