diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 483894b13..02c18dd4d 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -17,7 +17,7 @@ jobs: # Available versions may lag behind https://github.com/flutter/flutter.git - uses: subosito/flutter-action@v2 with: - flutter-version: '3.0.1' + flutter-version: '3.0.2' channel: 'stable' - name: Clone the repository. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cae6d2637..273a45cba 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,7 +19,7 @@ jobs: # Available versions may lag behind https://github.com/flutter/flutter.git - uses: subosito/flutter-action@v2 with: - flutter-version: '3.0.1' + flutter-version: '3.0.2' channel: 'stable' # Workaround for this Android Gradle Plugin issue (supposedly fixed in AGP 4.1): @@ -56,15 +56,15 @@ jobs: rm release.keystore.asc mkdir outputs (cd scripts/; ./apply_flavor_play.sh) - flutter build appbundle -t lib/main_play.dart --flavor play --bundle-sksl-path shaders_3.0.1.sksl.json + flutter build appbundle -t lib/main_play.dart --flavor play --bundle-sksl-path shaders_3.0.2.sksl.json cp build/app/outputs/bundle/playRelease/*.aab outputs - flutter build apk -t lib/main_play.dart --flavor play --bundle-sksl-path shaders_3.0.1.sksl.json + flutter build apk -t lib/main_play.dart --flavor play --bundle-sksl-path shaders_3.0.2.sksl.json cp build/app/outputs/apk/play/release/*.apk outputs (cd scripts/; ./apply_flavor_huawei.sh) - flutter build apk -t lib/main_huawei.dart --flavor huawei --bundle-sksl-path shaders_3.0.1.sksl.json + flutter build apk -t lib/main_huawei.dart --flavor huawei --bundle-sksl-path shaders_3.0.2.sksl.json cp build/app/outputs/apk/huawei/release/*.apk outputs (cd scripts/; ./apply_flavor_izzy.sh) - flutter build apk -t lib/main_izzy.dart --flavor izzy --split-per-abi --bundle-sksl-path shaders_3.0.1.sksl.json + flutter build apk -t lib/main_izzy.dart --flavor izzy --split-per-abi --bundle-sksl-path shaders_3.0.2.sksl.json cp build/app/outputs/apk/izzy/release/*.apk outputs rm $AVES_STORE_FILE env: diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ad280197..ac8faa429 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,26 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +## [v1.6.9] - 2022-06-18 + +### Added + +- slideshow +- set wallpaper from any media +- optional dynamic accent color on Android 12+ +- Search: date/dimension/size field equality (undocumented) +- support Android 13 (API 33) +- Turkish translation (thanks metezd) + +### Changed + +- do not force quit on storage permission denial +- upgraded Flutter to stable v3.0.2 + +### Fixed + +- merge ambiguously cased directories + ## [v1.6.8] - 2022-05-27 ### Fixed diff --git a/README.md b/README.md index 271e29520..9af7d5a6a 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,9 @@ Aves is a gallery and metadata explorer app. It is built for Android, with Flutt [Get it on Google Play](https://play.google.com/store/apps/details?id=deckers.thibault.aves&pcampaignid=pcampaignidMKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1) +[Get it on Huawei AppGallery](https://appgallery.huawei.com/app/C106014023) [Get it on Amazon Appstore](https://www.amazon.com/dp/B09XQHQQ72) @@ -90,7 +93,7 @@ At this stage this project does *not* accept PRs, except for translations. ### Translations -If you want to translate this app in your language and share the result, [there is a guide](https://github.com/deckerst/aves/wiki/Contributing-to-Translations). English, Korean and French are already handled by me. Russian, German, Spanish, Portuguese, Indonesian, Japanese, Italian & Chinese are handled by generous volunteers. +If you want to translate this app in your language and share the result, [there is a guide](https://github.com/deckerst/aves/wiki/Contributing-to-Translations). English, Korean and French are already handled by me. Russian, German, Spanish, Portuguese, Indonesian, Japanese, Italian, Chinese & Turkish are handled by generous volunteers. ### Donations diff --git a/android/app/build.gradle b/android/app/build.gradle index a8d4c0b0a..78f84d8c2 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -41,7 +41,7 @@ if (keystorePropertiesFile.exists()) { } android { - compileSdkVersion 32 + compileSdkVersion 33 sourceSets { main.java.srcDirs += 'src/main/kotlin' @@ -57,7 +57,7 @@ android { // which implementation `DocumentBuilderImpl` is provided by the OS and is not customizable on Android, // but the implementation on API <19 is not robust enough and fails to build XMP documents minSdkVersion 19 - targetSdkVersion 32 + targetSdkVersion 33 versionCode flutterVersionCode.toInteger() versionName flutterVersionName manifestPlaceholders = [googleApiKey: keystoreProperties['googleApiKey']] @@ -154,7 +154,7 @@ repositories { dependencies { implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2' - implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.core:core-ktx:1.8.0' implementation 'androidx.exifinterface:exifinterface:1.3.3' implementation 'androidx.multidex:multidex:2.0.1' implementation 'com.caverock:androidsvg-aar:1.4' diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 055de7299..68c90244b 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -6,16 +6,21 @@ Scoped storage on Android Q is inconvenient because users need to confirm edition on each individual file. So we request `WRITE_EXTERNAL_STORAGE` until Q (29), and enable `requestLegacyExternalStorage` --> - - - - - + + + + + + + + @@ -128,6 +133,26 @@ android:name="android.app.searchable" android:resource="@xml/searchable" /> + + + + + + + + + + + + + + + + { runBlocking { FlutterUtils.runOnUiThread { - val entryIds = data.get(KEY_ENTRY_IDS)?.takeIf { it is IntArray }?.let { (it as IntArray).toList() } + val entryIds = data.getIntArray(KEY_ENTRY_IDS)?.toList() backgroundChannel?.invokeMethod( "start", hashMapOf( "entryIds" to entryIds, 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 e1b78809a..f7f1a5b4b 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt @@ -15,6 +15,7 @@ import app.loup.streams_channel.StreamsChannel import deckers.thibault.aves.channel.calls.* import deckers.thibault.aves.channel.streams.* import deckers.thibault.aves.utils.LogUtils +import deckers.thibault.aves.utils.getParcelableExtraCompat import io.flutter.embedding.android.FlutterActivity import io.flutter.plugin.common.EventChannel import io.flutter.plugin.common.MethodCall @@ -215,7 +216,7 @@ class MainActivity : FlutterActivity() { } } Intent.ACTION_VIEW, Intent.ACTION_SEND, "com.android.camera.action.REVIEW" -> { - (intent.data ?: (intent.getParcelableExtra(Intent.EXTRA_STREAM) as? Uri))?.let { uri -> + (intent.data ?: intent.getParcelableExtraCompat(Intent.EXTRA_STREAM))?.let { uri -> // MIME type is optional val type = intent.type ?: intent.resolveType(context) return hashMapOf( @@ -332,6 +333,7 @@ class MainActivity : FlutterActivity() { const val INTENT_ACTION_PICK = "pick" const val INTENT_ACTION_SEARCH = "search" + const val INTENT_ACTION_SET_WALLPAPER = "set_wallpaper" const val INTENT_ACTION_VIEW = "view" const val SHORTCUT_KEY_PAGE = "page" diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/WallpaperActivity.kt b/android/app/src/main/kotlin/deckers/thibault/aves/WallpaperActivity.kt new file mode 100644 index 000000000..d7f28867e --- /dev/null +++ b/android/app/src/main/kotlin/deckers/thibault/aves/WallpaperActivity.kt @@ -0,0 +1,107 @@ +package deckers.thibault.aves + +import android.content.Intent +import android.net.Uri +import android.os.* +import android.util.Log +import app.loup.streams_channel.StreamsChannel +import deckers.thibault.aves.channel.calls.* +import deckers.thibault.aves.channel.streams.ImageByteStreamHandler +import deckers.thibault.aves.utils.LogUtils +import deckers.thibault.aves.utils.getParcelableExtraCompat +import io.flutter.embedding.android.FlutterActivity +import io.flutter.plugin.common.MethodChannel + +class WallpaperActivity : FlutterActivity() { + private lateinit var intentDataMap: MutableMap + + override fun onCreate(savedInstanceState: Bundle?) { + Log.i(LOG_TAG, "onCreate intent=$intent") + intent.extras?.takeUnless { it.isEmpty }?.let { + Log.i(LOG_TAG, "onCreate intent extras=$it") + } + + super.onCreate(savedInstanceState) + + val messenger = flutterEngine!!.dartExecutor.binaryMessenger + + // dart -> platform -> dart + // - need Context + MethodChannel(messenger, DeviceHandler.CHANNEL).setMethodCallHandler(DeviceHandler(this)) + MethodChannel(messenger, EmbeddedDataHandler.CHANNEL).setMethodCallHandler(EmbeddedDataHandler(this)) + MethodChannel(messenger, MetadataFetchHandler.CHANNEL).setMethodCallHandler(MetadataFetchHandler(this)) + // - need Activity + MethodChannel(messenger, AccessibilityHandler.CHANNEL).setMethodCallHandler(AccessibilityHandler(this)) + MethodChannel(messenger, MediaFileHandler.CHANNEL).setMethodCallHandler(MediaFileHandler(this)) + MethodChannel(messenger, WallpaperHandler.CHANNEL).setMethodCallHandler(WallpaperHandler(this)) + MethodChannel(messenger, WindowHandler.CHANNEL).setMethodCallHandler(WindowHandler(this)) + + // result streaming: dart -> platform ->->-> dart + // - need Context + StreamsChannel(messenger, ImageByteStreamHandler.CHANNEL).setStreamHandlerFactory { args -> ImageByteStreamHandler(this, args) } + + // intent handling + // detail fetch: dart -> platform + intentDataMap = extractIntentData(intent) + MethodChannel(messenger, VIEWER_CHANNEL).setMethodCallHandler { call, result -> + when (call.method) { + "getIntentData" -> { + result.success(intentDataMap) + intentDataMap.clear() + } + } + } + } + + override fun onStart() { + Log.i(LOG_TAG, "onStart") + super.onStart() + + // as of Flutter v3.0.1, the window `viewInsets` and `viewPadding` + // are incorrect on startup in some environments (e.g. API 29 emulator), + // so we manually request to apply the insets to update the window metrics + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) { + Handler(Looper.getMainLooper()).postDelayed({ + window.decorView.requestApplyInsets() + }, 100) + } + } + + override fun onStop() { + Log.i(LOG_TAG, "onStop") + super.onStop() + } + + override fun onDestroy() { + Log.i(LOG_TAG, "onDestroy") + super.onDestroy() + } + + private fun extractIntentData(intent: Intent?): MutableMap { + when (intent?.action) { + Intent.ACTION_ATTACH_DATA, Intent.ACTION_SET_WALLPAPER -> { + (intent.data ?: intent.getParcelableExtraCompat(Intent.EXTRA_STREAM))?.let { uri -> + // MIME type is optional + val type = intent.type ?: intent.resolveType(context) + return hashMapOf( + MainActivity.INTENT_DATA_KEY_ACTION to MainActivity.INTENT_ACTION_SET_WALLPAPER, + MainActivity.INTENT_DATA_KEY_MIME_TYPE to type, + MainActivity.INTENT_DATA_KEY_URI to uri.toString(), + ) + } + } + Intent.ACTION_RUN -> { + // flutter run + } + else -> { + Log.w(LOG_TAG, "unhandled intent action=${intent?.action}") + } + } + return HashMap() + } + + companion object { + private val LOG_TAG = LogUtils.createTag() + const val VIEWER_CHANNEL = "deckers.thibault/aves/viewer" + } +} 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 9437745a3..c5b2d2223 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 @@ -30,6 +30,8 @@ import deckers.thibault.aves.model.FieldMap import deckers.thibault.aves.utils.BitmapUtils import deckers.thibault.aves.utils.BitmapUtils.getBytes import deckers.thibault.aves.utils.LogUtils +import deckers.thibault.aves.utils.getApplicationInfoCompat +import deckers.thibault.aves.utils.queryIntentActivitiesCompat import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel.MethodCallHandler @@ -77,7 +79,7 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler { } val pm = context.packageManager - for (resolveInfo in pm.queryIntentActivities(intent, 0)) { + for (resolveInfo in pm.queryIntentActivitiesCompat(intent, 0)) { val appInfo = resolveInfo.activityInfo.applicationInfo val packageName = appInfo.packageName if (!packages.containsKey(packageName)) { @@ -149,7 +151,7 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler { val size = (sizeDip * density).roundToInt() var data: ByteArray? = null try { - val iconResourceId = context.packageManager.getApplicationInfo(packageName, 0).icon + val iconResourceId = context.packageManager.getApplicationInfoCompat(packageName, 0).icon if (iconResourceId != Resources.ID_NULL) { val uri = Uri.Builder() .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) @@ -444,4 +446,4 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler { } } } -} \ No newline at end of file +} diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/DeviceHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/DeviceHandler.kt index 7bb635ad6..eda3f1d32 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/DeviceHandler.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/DeviceHandler.kt @@ -32,6 +32,8 @@ class DeviceHandler(private val context: Context) : MethodCallHandler { "canPinShortcut" to ShortcutManagerCompat.isRequestPinShortcutSupported(context), "canPrint" to (sdkInt >= Build.VERSION_CODES.KITKAT), "canRenderFlagEmojis" to (sdkInt >= Build.VERSION_CODES.LOLLIPOP), + "canSetLockScreenWallpaper" to (sdkInt >= Build.VERSION_CODES.N), + "isDynamicColorAvailable" to (sdkInt >= Build.VERSION_CODES.S), "showPinShortcutFeedback" to (sdkInt >= Build.VERSION_CODES.O), "supportEdgeToEdgeUIMode" to (sdkInt >= Build.VERSION_CODES.Q), ) diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/GeocodingHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/GeocodingHandler.kt index 0821058f3..1c16e2b02 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/GeocodingHandler.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/GeocodingHandler.kt @@ -1,12 +1,17 @@ package deckers.thibault.aves.channel.calls import android.content.Context +import android.location.Address import android.location.Geocoder +import android.os.Build import deckers.thibault.aves.channel.calls.Coresult.Companion.safe import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel.MethodCallHandler -import kotlinx.coroutines.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.launch import java.io.IOException import java.util.* @@ -48,36 +53,48 @@ class GeocodingHandler(private val context: Context) : MethodCallHandler { Geocoder(context) } - val addresses = try { - geocoder!!.getFromLocation(latitude, longitude, maxResults) ?: ArrayList() - } catch (e: IOException) { - // `grpc failed`, etc. - result.error("getAddress-network", "failed to get address because of network issues", e.message) - return - } catch (e: Exception) { - result.error("getAddress-exception", "failed to get address", e.message) - return + fun processAddresses(addresses: List
) { + if (addresses.isEmpty()) { + result.error("getAddress-empty", "failed to find any address for latitude=$latitude, longitude=$longitude", null) + } else { + val addressMapList: ArrayList> = ArrayList(addresses.map { address -> + hashMapOf( + "addressLine" to (0..address.maxAddressLineIndex).joinToString(", ") { i -> address.getAddressLine(i) }, + "adminArea" to address.adminArea, + "countryCode" to address.countryCode, + "countryName" to address.countryName, + "featureName" to address.featureName, + "locality" to address.locality, + "postalCode" to address.postalCode, + "subAdminArea" to address.subAdminArea, + "subLocality" to address.subLocality, + "subThoroughfare" to address.subThoroughfare, + "thoroughfare" to address.thoroughfare, + ) + }) + result.success(addressMapList) + } } - if (addresses.isEmpty()) { - result.error("getAddress-empty", "failed to find any address for latitude=$latitude, longitude=$longitude", null) - } else { - val addressMapList: ArrayList> = ArrayList(addresses.map { address -> - hashMapOf( - "addressLine" to (0..address.maxAddressLineIndex).joinToString(", ") { i -> address.getAddressLine(i) }, - "adminArea" to address.adminArea, - "countryCode" to address.countryCode, - "countryName" to address.countryName, - "featureName" to address.featureName, - "locality" to address.locality, - "postalCode" to address.postalCode, - "subAdminArea" to address.subAdminArea, - "subLocality" to address.subLocality, - "subThoroughfare" to address.subThoroughfare, - "thoroughfare" to address.thoroughfare, - ) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + geocoder!!.getFromLocation(latitude, longitude, maxResults, object : Geocoder.GeocodeListener { + override fun onGeocode(addresses: List) = processAddresses(addresses.filterNotNull()) + + override fun onError(errorMessage: String?) { + result.error("getAddress-asyncerror", "failed to get address", errorMessage) + } }) - result.success(addressMapList) + } else { + try { + @Suppress("deprecation") + val addresses = geocoder!!.getFromLocation(latitude, longitude, maxResults) ?: ArrayList() + processAddresses(addresses) + } catch (e: IOException) { + // `grpc failed`, etc. + result.error("getAddress-network", "failed to get address because of network issues", e.message) + } catch (e: Exception) { + result.error("getAddress-exception", "failed to get address", e.message) + } } } diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/WallpaperHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/WallpaperHandler.kt new file mode 100644 index 000000000..c4f1eab9d --- /dev/null +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/WallpaperHandler.kt @@ -0,0 +1,58 @@ +package deckers.thibault.aves.channel.calls + +import android.app.Activity +import android.app.WallpaperManager +import android.app.WallpaperManager.FLAG_LOCK +import android.app.WallpaperManager.FLAG_SYSTEM +import android.os.Build +import deckers.thibault.aves.channel.calls.Coresult.Companion.safe +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import io.flutter.plugin.common.MethodChannel.MethodCallHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.launch + +class WallpaperHandler(private val activity: Activity) : MethodCallHandler { + private val ioScope = CoroutineScope(SupervisorJob() + Dispatchers.IO) + + override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { + when (call.method) { + "setWallpaper" -> ioScope.launch { safe(call, result, ::setWallpaper) } + else -> result.notImplemented() + } + } + + private fun setWallpaper(call: MethodCall, result: MethodChannel.Result) { + val bytes = call.argument("bytes") + val home = call.argument("home") + val lock = call.argument("lock") + if (bytes == null || home == null || lock == null) { + result.error("setWallpaper-args", "failed because of missing arguments", null) + return + } + + val manager = WallpaperManager.getInstance(activity) + val supported = Build.VERSION.SDK_INT < Build.VERSION_CODES.M || manager.isWallpaperSupported + val allowed = Build.VERSION.SDK_INT < Build.VERSION_CODES.N || manager.isSetWallpaperAllowed + if (!supported || !allowed) { + result.error("setWallpaper-unsupported", "failed because setting wallpaper is not allowed", null) + return + } + + bytes.inputStream().use { input -> + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + val flags = (if (home) FLAG_SYSTEM else 0) or (if (lock) FLAG_LOCK else 0) + manager.setStream(input, null, true, flags) + } else { + manager.setStream(input) + } + } + result.success(true) + } + + companion object { + const val CHANNEL = "deckers.thibault/aves/wallpaper" + } +} \ No newline at end of file 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 7b6317506..a9ae20d6d 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 @@ -220,7 +220,7 @@ object ExifInterfaceHelper { // initialize metadata-extractor directories that we will fill // by tags converted from the ExifInterface attributes // so that we can rely on metadata-extractor descriptions - val dirs = DirType.values().associate { Pair(it, it.createDirectory()) } + val dirs = DirType.values().associateWith { it.createDirectory() } // exclude Exif directory when it only includes image size val isUselessExif = fun(it: Map): Boolean { diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/utils/Compat.kt b/android/app/src/main/kotlin/deckers/thibault/aves/utils/Compat.kt new file mode 100644 index 000000000..cd7dffcfc --- /dev/null +++ b/android/app/src/main/kotlin/deckers/thibault/aves/utils/Compat.kt @@ -0,0 +1,37 @@ +package deckers.thibault.aves.utils + +import android.content.Intent +import android.content.pm.ApplicationInfo +import android.content.pm.PackageManager +import android.content.pm.ResolveInfo +import android.os.Build +import android.os.Parcelable + +inline fun Intent.getParcelableExtraCompat(name: String): T? { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + getParcelableExtra(name, T::class.java) + } else { + @Suppress("deprecation") + getParcelableExtra(name) as? T + } +} + + +fun PackageManager.getApplicationInfoCompat(packageName: String, flags: Int): ApplicationInfo { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + getApplicationInfo(packageName, PackageManager.ApplicationInfoFlags.of(flags.toLong())) + } else { + @Suppress("deprecation") + getApplicationInfo(packageName, flags) + } + +} + +fun PackageManager.queryIntentActivitiesCompat(intent: Intent, flags: Int): List { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + queryIntentActivities(intent, PackageManager.ResolveInfoFlags.of(flags.toLong())) + } else { + @Suppress("deprecation") + queryIntentActivities(intent, flags) + } +} diff --git a/android/app/src/main/res/drawable/ic_launcher_mono.xml b/android/app/src/main/res/drawable/ic_launcher_mono.xml new file mode 100644 index 000000000..738ecc287 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_launcher_mono.xml @@ -0,0 +1,32 @@ + + + + + + + + diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index 7353dbd1f..fe5fad39c 100644 --- a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -2,4 +2,5 @@ + \ No newline at end of file diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml index 7353dbd1f..fe5fad39c 100644 --- a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -2,4 +2,5 @@ + \ No newline at end of file diff --git a/android/app/src/main/res/values-de/strings.xml b/android/app/src/main/res/values-de/strings.xml index f14128004..c74efc923 100644 --- a/android/app/src/main/res/values-de/strings.xml +++ b/android/app/src/main/res/values-de/strings.xml @@ -1,6 +1,7 @@  Aves + Hintergrundbild Suche Videos Analyse von Medien diff --git a/android/app/src/main/res/values-es/strings.xml b/android/app/src/main/res/values-es/strings.xml index aadf5be08..d501be2f1 100644 --- a/android/app/src/main/res/values-es/strings.xml +++ b/android/app/src/main/res/values-es/strings.xml @@ -1,6 +1,7 @@ Aves + Fondo de pantalla Búsqueda Videos Explorar medios diff --git a/android/app/src/main/res/values-fr/strings.xml b/android/app/src/main/res/values-fr/strings.xml index 46f2079d0..54e4d386f 100644 --- a/android/app/src/main/res/values-fr/strings.xml +++ b/android/app/src/main/res/values-fr/strings.xml @@ -1,6 +1,7 @@  Aves + Fond d’écran Recherche Vidéos Analyse des images diff --git a/android/app/src/main/res/values-id/strings.xml b/android/app/src/main/res/values-id/strings.xml index f750d1a0f..dee1fe89f 100644 --- a/android/app/src/main/res/values-id/strings.xml +++ b/android/app/src/main/res/values-id/strings.xml @@ -1,6 +1,7 @@ Aves + Wallpaper Cari Video Pindai media diff --git a/android/app/src/main/res/values-it/strings.xml b/android/app/src/main/res/values-it/strings.xml index 7acc2985c..0574f5b49 100644 --- a/android/app/src/main/res/values-it/strings.xml +++ b/android/app/src/main/res/values-it/strings.xml @@ -1,6 +1,7 @@ Aves + Sfondo Ricerca Video Scansione media diff --git a/android/app/src/main/res/values-ja/strings.xml b/android/app/src/main/res/values-ja/strings.xml index 90be621c5..c7b8be013 100644 --- a/android/app/src/main/res/values-ja/strings.xml +++ b/android/app/src/main/res/values-ja/strings.xml @@ -1,10 +1,11 @@  -Aves -検索 -動画 -メディアスキャン -画像と動画をスキャン -メディアをスキャン中 -停止 + Aves + 壁紙 + 検索 + 動画 + メディアスキャン + 画像と動画をスキャン + メディアをスキャン中 + 停止 \ No newline at end of file diff --git a/android/app/src/main/res/values-ko/strings.xml b/android/app/src/main/res/values-ko/strings.xml index 29988cddf..2680d4a88 100644 --- a/android/app/src/main/res/values-ko/strings.xml +++ b/android/app/src/main/res/values-ko/strings.xml @@ -1,6 +1,7 @@  아베스 + 배경화면 검색 동영상 미디어 분석 diff --git a/android/app/src/main/res/values-pt/strings.xml b/android/app/src/main/res/values-pt/strings.xml index 336c769db..dd30d7117 100644 --- a/android/app/src/main/res/values-pt/strings.xml +++ b/android/app/src/main/res/values-pt/strings.xml @@ -1,6 +1,7 @@ Aves + Papel de parede Procurar Vídeos Digitalização de mídia diff --git a/android/app/src/main/res/values-ru/strings.xml b/android/app/src/main/res/values-ru/strings.xml index 3ccabcd2a..bb46342b8 100644 --- a/android/app/src/main/res/values-ru/strings.xml +++ b/android/app/src/main/res/values-ru/strings.xml @@ -1,6 +1,7 @@  Aves + Обои Поиск Видео Сканировать медия diff --git a/android/app/src/main/res/values-tr/strings.xml b/android/app/src/main/res/values-tr/strings.xml new file mode 100644 index 000000000..d5f43548a --- /dev/null +++ b/android/app/src/main/res/values-tr/strings.xml @@ -0,0 +1,11 @@ + + + Aves + Duvar kağıdı + Arama + Videolar + Medya tarama + Görüntüleri ve videoları tarayın + Medya taranıyor + Durdur + \ No newline at end of file diff --git a/android/app/src/main/res/values-zh/strings.xml b/android/app/src/main/res/values-zh/strings.xml index ea3ace11b..7db4775d2 100644 --- a/android/app/src/main/res/values-zh/strings.xml +++ b/android/app/src/main/res/values-zh/strings.xml @@ -1,6 +1,7 @@ Aves + 壁纸 搜索 视频 媒体扫描 diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index a37cd760c..5c24af947 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -1,6 +1,7 @@ Aves + Wallpaper Search Videos Media scan diff --git a/android/build.gradle b/android/build.gradle index 52ec57d77..4d7683bf2 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,17 +1,17 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.6.21' + ext.kotlin_version = '1.7.0' repositories { google() mavenCentral() maven { url 'https://developer.huawei.com/repo/' } } dependencies { - classpath 'com.android.tools.build:gradle:7.2.0' + classpath 'com.android.tools.build:gradle:7.2.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // GMS & Firebase Crashlytics (used by some flavors only) classpath 'com.google.gms:google-services:4.3.10' - classpath 'com.google.firebase:firebase-crashlytics-gradle:2.8.1' + classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.0' // HMS (used by some flavors only) classpath 'com.huawei.agconnect:agcp:1.5.2.300' } diff --git a/fastlane/metadata/android/de/full_description.txt b/fastlane/metadata/android/de/full_description.txt index 6f57ace2b..7cfc6826c 100644 --- a/fastlane/metadata/android/de/full_description.txt +++ b/fastlane/metadata/android/de/full_description.txt @@ -2,4 +2,4 @@ Navigation und Suche ist ein wichtiger Bestandteil von Aves. Das Ziel besteht darin, dass Benutzer problemlos von Alben zu Fotos zu Tags zu Karten usw. wechseln können. -Aves lässt sich mit Android (von API 19 bis 32, d. h. von KitKat bis Android 12L) mit Funktionen wie App-Verknüpfungen und globaler Suche integrieren. Es funktioniert auch als Medienbetrachter und -auswahl. \ No newline at end of file +Aves lässt sich mit Android (von API 19 bis 33, d. h. von KitKat bis Android 13) mit Funktionen wie App-Verknüpfungen und globaler Suche integrieren. Es funktioniert auch als Medienbetrachter und -auswahl. \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/changelogs/1075.txt b/fastlane/metadata/android/en-US/changelogs/1075.txt new file mode 100644 index 000000000..c852da8ed --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/1075.txt @@ -0,0 +1,5 @@ +In v1.6.9: +- start slideshows +- change your wallpaper +- enjoy the app in Turkish +Full changelog available on GitHub diff --git a/fastlane/metadata/android/en-US/full_description.txt b/fastlane/metadata/android/en-US/full_description.txt index eba12d85e..4b84e8ccd 100644 --- a/fastlane/metadata/android/en-US/full_description.txt +++ b/fastlane/metadata/android/en-US/full_description.txt @@ -2,4 +2,4 @@ Navigation and search is an important part of Aves. The goal is for users to easily flow from albums to photos to tags to maps, etc. -Aves integrates with Android (from API 19 to 32, i.e. from KitKat to Android 12L) with features such as app shortcuts and global search handling. It also works as a media viewer and picker. \ No newline at end of file +Aves integrates with Android (from API 19 to 33, i.e. from KitKat to Android 13) with features such as app shortcuts and global search handling. It also works as a media viewer and picker. \ No newline at end of file diff --git a/fastlane/metadata/android/es-MX/full_description.txt b/fastlane/metadata/android/es-MX/full_description.txt index 68859bb5d..510b47759 100644 --- a/fastlane/metadata/android/es-MX/full_description.txt +++ b/fastlane/metadata/android/es-MX/full_description.txt @@ -2,4 +2,4 @@ La navegación y búsqueda son partes importantes de Aves. Su propósito es que los usuarios puedan fácimente ir de álbumes a fotos, etiquetas, mapas, etc. -Aves se integra con Android (desde API 19 a 32, por ej. desde KitKat hasta Android 12L) con características como vínculos de aplicación y manejo de búsqueda global. También funciona como un visor y seleccionador multimedia. \ No newline at end of file +Aves se integra con Android (desde API 19 a 33, por ej. desde KitKat hasta Android 13) con características como vínculos de aplicación y manejo de búsqueda global. También funciona como un visor y seleccionador multimedia. \ No newline at end of file diff --git a/fastlane/metadata/android/id/full_description.txt b/fastlane/metadata/android/id/full_description.txt index 60d4c271f..8eda6d0ba 100644 --- a/fastlane/metadata/android/id/full_description.txt +++ b/fastlane/metadata/android/id/full_description.txt @@ -2,4 +2,4 @@ Navigasi dan pencarian merupakan bagian penting dari Aves. Tujuannya adalah agar pengguna dengan mudah mengalir dari album ke foto ke tag ke peta, dll. -Aves terintegrasi dengan Android (dari API 19 ke 32, yaitu dari KitKat ke Android 12L) dengan fitur-fitur seperti pintasan aplikasi dan pencarian global penanganan. Ini juga berfungsi sebagai penampil dan pemilih media. \ No newline at end of file +Aves terintegrasi dengan Android (dari API 19 ke 33, yaitu dari KitKat ke Android 13) dengan fitur-fitur seperti pintasan aplikasi dan pencarian global penanganan. Ini juga berfungsi sebagai penampil dan pemilih media. \ No newline at end of file diff --git a/fastlane/metadata/android/it/full_description.txt b/fastlane/metadata/android/it/full_description.txt index d903944f7..502df17e5 100644 --- a/fastlane/metadata/android/it/full_description.txt +++ b/fastlane/metadata/android/it/full_description.txt @@ -2,4 +2,4 @@ Navigazione e ricerca sono una parte importante di Aves. L'obiettivo è che gli utenti passino facilmente dagli album alle foto, ai tag, alle mappe, ecc. -Aves si integra con Android (da API 19 a 32, cioè da KitKat ad Android 12L) con caratteristiche come collegamenti alle app e la gestione della ricerca globale. Funziona anche come visualizzazione e raccolta di media. +Aves si integra con Android (da API 19 a 33, cioè da KitKat ad Android 13) con caratteristiche come collegamenti alle app e la gestione della ricerca globale. Funziona anche come visualizzazione e raccolta di media. diff --git a/fastlane/metadata/android/ja/full_description.txt b/fastlane/metadata/android/ja/full_description.txt index f489d7765..07e57595b 100644 --- a/fastlane/metadata/android/ja/full_description.txt +++ b/fastlane/metadata/android/ja/full_description.txt @@ -4,4 +4,4 @@ ナビゲーションと検索は、Avesの重要な部分です。アルバムから写真、タグ、地図などへ簡単に移動できます。 -Avesは、アプリショートカットグローバル検索などの機能を、Android(API 19から32まで、つまりAndroid 4.4から12 Lまで)と統合しています。また、メディアビューワーメディアピッカーとしても機能します。 \ No newline at end of file +Avesは、アプリショートカットグローバル検索などの機能を、Android(API 19から33まで、つまりAndroid 4.4から13 Lまで)と統合しています。また、メディアビューワーメディアピッカーとしても機能します。 \ No newline at end of file diff --git a/fastlane/metadata/android/pt-BR/full_description.txt b/fastlane/metadata/android/pt-BR/full_description.txt index 76ce49ba4..657c4fa54 100644 --- a/fastlane/metadata/android/pt-BR/full_description.txt +++ b/fastlane/metadata/android/pt-BR/full_description.txt @@ -2,4 +2,4 @@ Navegação e pesquisa é uma parte importante do Aves. O objetivo é que os usuários fluam facilmente de álbuns para fotos, etiquetas, mapas, etc. -Aves integra com Android (de API 19 para 32, i.e. de KitKat para Android 12L) com recursos como atalhos de apps e pesquisa global manipulação. Também funciona como um visualizador e selecionador de mídia. +Aves integra com Android (de API 19 para 33, i.e. de KitKat para Android 13) com recursos como atalhos de apps e pesquisa global manipulação. Também funciona como um visualizador e selecionador de mídia. diff --git a/fastlane/metadata/android/tr/full_description.txt b/fastlane/metadata/android/tr/full_description.txt new file mode 100644 index 000000000..507df09bf --- /dev/null +++ b/fastlane/metadata/android/tr/full_description.txt @@ -0,0 +1,5 @@ +Aves tipik JPEG ve MP4'lerin yanı sıra çok sayfalı TIFF'ler, SVG'ler, eski AVI'ler ve daha fazlası gibi daha egzotik şeyler de dahil olmak üzere her türlü görüntü ve videoyu işleyebilir! Hareketli fotoğrafları, panoramaları (fotoğraf küreleri olarak da bilinir), 360° videoları ve GeoTIFF dosyalarını tanımlamak için medya koleksiyonunuzu tarar. + +Gezinme ve arama Aves'in önemli bir parçasıdır. Amaç, kullanıcıların albümlerden fotoğraflara, etiketlerden haritalara vb. kolayca geçmesini sağlamaktır. + +Aves, uygulama kısayolları ve global arama işleme gibi özelliklerle Android (API 19'dan 33'ye, yani KitKat'tan Android 13'ye kadar) ile entegre olur. Ayrıca bir medya görüntüleyici ve alıcı olarak da çalışır. \ No newline at end of file diff --git a/fastlane/metadata/android/tr/images/featureGraphic.png b/fastlane/metadata/android/tr/images/featureGraphic.png new file mode 100644 index 000000000..11d0ca7af Binary files /dev/null and b/fastlane/metadata/android/tr/images/featureGraphic.png differ diff --git a/fastlane/metadata/android/tr/images/phoneScreenshots/1.png b/fastlane/metadata/android/tr/images/phoneScreenshots/1.png new file mode 100644 index 000000000..a8ee2dbe5 Binary files /dev/null and b/fastlane/metadata/android/tr/images/phoneScreenshots/1.png differ diff --git a/fastlane/metadata/android/tr/images/phoneScreenshots/2.png b/fastlane/metadata/android/tr/images/phoneScreenshots/2.png new file mode 100644 index 000000000..2d8ce44ea Binary files /dev/null and b/fastlane/metadata/android/tr/images/phoneScreenshots/2.png differ diff --git a/fastlane/metadata/android/tr/images/phoneScreenshots/3.png b/fastlane/metadata/android/tr/images/phoneScreenshots/3.png new file mode 100644 index 000000000..4daa50af6 Binary files /dev/null and b/fastlane/metadata/android/tr/images/phoneScreenshots/3.png differ diff --git a/fastlane/metadata/android/tr/images/phoneScreenshots/4.png b/fastlane/metadata/android/tr/images/phoneScreenshots/4.png new file mode 100644 index 000000000..94f569bd6 Binary files /dev/null and b/fastlane/metadata/android/tr/images/phoneScreenshots/4.png differ diff --git a/fastlane/metadata/android/tr/images/phoneScreenshots/5.png b/fastlane/metadata/android/tr/images/phoneScreenshots/5.png new file mode 100644 index 000000000..2f293b32d Binary files /dev/null and b/fastlane/metadata/android/tr/images/phoneScreenshots/5.png differ diff --git a/fastlane/metadata/android/tr/images/phoneScreenshots/6.png b/fastlane/metadata/android/tr/images/phoneScreenshots/6.png new file mode 100644 index 000000000..afaff9751 Binary files /dev/null and b/fastlane/metadata/android/tr/images/phoneScreenshots/6.png differ diff --git a/fastlane/metadata/android/tr/short_description.txt b/fastlane/metadata/android/tr/short_description.txt new file mode 100644 index 000000000..073624650 --- /dev/null +++ b/fastlane/metadata/android/tr/short_description.txt @@ -0,0 +1 @@ +Galeri ve meta veri gezgini \ No newline at end of file diff --git a/fastlane/metadata/android/zh-CN/full_description.txt b/fastlane/metadata/android/zh-CN/full_description.txt index 02b2b4e57..ad4a076d4 100644 --- a/fastlane/metadata/android/zh-CN/full_description.txt +++ b/fastlane/metadata/android/zh-CN/full_description.txt @@ -2,4 +2,4 @@ 导航与搜索Aves 的核心功能之一,旨在帮助用户在相册、照片、标签、地图等之间轻松切换。 - Aves 与 Android(API 19-32,即从 KitKat 到 Android 12L)集成,具有快捷方式全局搜索等功能。它还可用作媒体查看器和选择器。 + Aves 与 Android(API 19-33,即从 KitKat 到 Android 13)集成,具有快捷方式全局搜索等功能。它还可用作媒体查看器和选择器。 diff --git a/lib/app_mode.dart b/lib/app_mode.dart index 70ed305da..0c4840ba3 100644 --- a/lib/app_mode.dart +++ b/lib/app_mode.dart @@ -1,4 +1,13 @@ -enum AppMode { main, pickSingleMediaExternal, pickMultipleMediaExternal, pickMediaInternal, pickFilterInternal, view } +enum AppMode { + main, + pickSingleMediaExternal, + pickMultipleMediaExternal, + pickMediaInternal, + pickFilterInternal, + setWallpaper, + slideshow, + view, +} extension ExtraAppMode on AppMode { bool get canSearch => this == AppMode.main || this == AppMode.pickSingleMediaExternal || this == AppMode.pickMultipleMediaExternal; diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index f2acda38c..893f94f9f 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -50,6 +50,7 @@ "entryActionDelete": "Löschen", "entryActionConvert": "Konvertieren", "entryActionExport": "Exportieren", + "entryActionInfo": "Info", "entryActionRename": "Umbenennen", "entryActionRestore": "Wiederherstellen", "entryActionRotateCCW": "Drehen gegen den Uhrzeigersinn", @@ -80,6 +81,9 @@ "videoActionSetSpeed": "Wiedergabegeschwindigkeit", "videoActionSettings": "Einstellungen", + "slideshowActionResume": "Wiedergabe", + "slideshowActionShowInCollection": "In Sammlung anzeigen", + "entryInfoActionEditDate": "Datum & Uhrzeit bearbeiten", "entryInfoActionEditLocation": "Standort bearbeiten", "entryInfoActionEditRating": "Bewertung bearbeiten", @@ -144,10 +148,23 @@ "displayRefreshRatePreferHighest": "Höchste Rate", "displayRefreshRatePreferLowest": "Niedrigste Rate", + "slideshowVideoPlaybackSkip": "Überspringen", + "slideshowVideoPlaybackMuted": "Stumm abspielen", + "slideshowVideoPlaybackWithSound": "Mit Ton abspielen", + "themeBrightnessLight": "Hell", "themeBrightnessDark": "Dunkel", "themeBrightnessBlack": "Schwarz", + "viewerTransitionSlide": "Dia", + "viewerTransitionParallax": "Parallaxe", + "viewerTransitionFade": "Ausblenden", + "viewerTransitionZoomIn": "Heranzoomen", + + "wallpaperTargetHome": "Startbildschirm", + "wallpaperTargetLock": "Sperrbildschirm", + "wallpaperTargetHomeLock": "Start- und Sperrbildschirm", + "albumTierNew": "Neu", "albumTierPinned": "Angeheftet", "albumTierSpecial": "Häufig verwendet", @@ -262,6 +279,7 @@ "menuActionSelectAll": "Alle auswählen", "menuActionSelectNone": "Keine auswählen", "menuActionMap": "Karte", + "menuActionSlideshow": "Diashow", "menuActionStats": "Statistiken", "viewDialogTabSort": "Sortieren", @@ -349,6 +367,7 @@ "collectionEmptyFavourites": "Keine Favoriten", "collectionEmptyVideos": "Keine Videos", "collectionEmptyImages": "Keine Bilder", + "collectionEmptyGrantAccessButtonLabel": "Zugriff gewähren", "collectionSelectSectionTooltip": "Bereich auswählen", "collectionDeselectSectionTooltip": "Bereich abwählen", @@ -479,6 +498,17 @@ "settingsViewerShowOverlayThumbnails": "Vorschaubilder anzeigen", "settingsViewerEnableOverlayBlurEffect": "Unschärfe-Effekt", + "settingsViewerSlideshowTile": "Diashow", + "settingsViewerSlideshowTitle": "Diashow", + "settingsSlideshowRepeat": "Wiederholung", + "settingsSlideshowShuffle": "Mischen", + "settingsSlideshowTransitionTile": "Übergang", + "settingsSlideshowTransitionTitle": "Übergang", + "settingsSlideshowIntervalTile": "Intervall", + "settingsSlideshowIntervalTitle": "Intervall", + "settingsSlideshowVideoPlaybackTile": "Videowiedergabe", + "settingsSlideshowVideoPlaybackTitle": "Videowiedergabe", + "settingsVideoPageTitle": "Video-Einstellungen", "settingsSectionVideo": "Video", "settingsVideoShowVideos": "Videos anzeigen", @@ -543,6 +573,7 @@ "settingsSectionDisplay": "Anzeige", "settingsThemeBrightness": "Thema", "settingsThemeColorHighlights": "Farbige Highlights", + "settingsThemeEnableDynamicColor": "Dynamische Farben", "settingsDisplayRefreshRateModeTile": "Bildwiederholrate der Anzeige", "settingsDisplayRefreshRateModeTitle": "Bildwiederholrate", @@ -560,6 +591,7 @@ "statsTopTags": "Top-Tags", "viewerOpenPanoramaButtonLabel": "ÖFFNE PANORAMA", + "viewerSetWallpaperButtonLabel": "HINTERGRUNDBILD EINSTELLEN", "viewerErrorUnknown": "Ups!", "viewerErrorDoesNotExist": "Die Datei existiert nicht mehr.", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 0dc847cfa..5657ade65 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -78,6 +78,7 @@ "entryActionDelete": "Delete", "entryActionConvert": "Convert", "entryActionExport": "Export", + "entryActionInfo": "Info", "entryActionRename": "Rename", "entryActionRestore": "Restore", "entryActionRotateCCW": "Rotate counterclockwise", @@ -108,6 +109,9 @@ "videoActionSetSpeed": "Playback speed", "videoActionSettings": "Settings", + "slideshowActionResume": "Resume", + "slideshowActionShowInCollection": "Show in Collection", + "entryInfoActionEditDate": "Edit date & time", "entryInfoActionEditLocation": "Edit location", "entryInfoActionEditRating": "Edit rating", @@ -184,10 +188,23 @@ "displayRefreshRatePreferHighest": "Highest rate", "displayRefreshRatePreferLowest": "Lowest rate", + "slideshowVideoPlaybackSkip": "Skip", + "slideshowVideoPlaybackMuted": "Play muted", + "slideshowVideoPlaybackWithSound": "Play with sound", + "themeBrightnessLight": "Light", "themeBrightnessDark": "Dark", "themeBrightnessBlack": "Black", + "viewerTransitionSlide": "Slide", + "viewerTransitionParallax": "Parallax", + "viewerTransitionFade": "Fade", + "viewerTransitionZoomIn": "Zoom in", + + "wallpaperTargetHome": "Home screen", + "wallpaperTargetLock": "Lock screen", + "wallpaperTargetHomeLock": "Home and lock screens", + "albumTierNew": "New", "albumTierPinned": "Pinned", "albumTierSpecial": "Common", @@ -392,6 +409,7 @@ "menuActionSelectAll": "Select all", "menuActionSelectNone": "Select none", "menuActionMap": "Map", + "menuActionSlideshow": "Slideshow", "menuActionStats": "Stats", "viewDialogTabSort": "Sort", @@ -529,6 +547,7 @@ "collectionEmptyFavourites": "No favorites", "collectionEmptyVideos": "No videos", "collectionEmptyImages": "No images", + "collectionEmptyGrantAccessButtonLabel": "Grant access", "collectionSelectSectionTooltip": "Select section", "collectionDeselectSectionTooltip": "Deselect section", @@ -659,6 +678,17 @@ "settingsViewerShowOverlayThumbnails": "Show thumbnails", "settingsViewerEnableOverlayBlurEffect": "Blur effect", + "settingsViewerSlideshowTile": "Slideshow", + "settingsViewerSlideshowTitle": "Slideshow", + "settingsSlideshowRepeat": "Repeat", + "settingsSlideshowShuffle": "Shuffle", + "settingsSlideshowTransitionTile": "Transition", + "settingsSlideshowTransitionTitle": "Transition", + "settingsSlideshowIntervalTile": "Interval", + "settingsSlideshowIntervalTitle": "Interval", + "settingsSlideshowVideoPlaybackTile": "Video playback", + "settingsSlideshowVideoPlaybackTitle": "Video Playback", + "settingsVideoPageTitle": "Video Settings", "settingsSectionVideo": "Video", "settingsVideoShowVideos": "Show videos", @@ -723,6 +753,7 @@ "settingsSectionDisplay": "Display", "settingsThemeBrightness": "Theme", "settingsThemeColorHighlights": "Color highlights", + "settingsThemeEnableDynamicColor": "Dynamic color", "settingsDisplayRefreshRateModeTile": "Display refresh rate", "settingsDisplayRefreshRateModeTitle": "Refresh Rate", @@ -745,6 +776,7 @@ "statsTopTags": "Top Tags", "viewerOpenPanoramaButtonLabel": "OPEN PANORAMA", + "viewerSetWallpaperButtonLabel": "SET WALLPAPER", "viewerErrorUnknown": "Oops!", "viewerErrorDoesNotExist": "The file no longer exists.", diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index d117a025f..f99ee17d3 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -50,6 +50,7 @@ "entryActionDelete": "Borrar", "entryActionConvert": "Convertir", "entryActionExport": "Exportar", + "entryActionInfo": "Información", "entryActionRename": "Renombrar", "entryActionRestore": "Restaurar", "entryActionRotateCCW": "Rotar en sentido antihorario", @@ -80,6 +81,9 @@ "videoActionSetSpeed": "Velocidad de reproducción", "videoActionSettings": "Ajustes", + "slideshowActionResume": "Reanudar", + "slideshowActionShowInCollection": "Mostrar en Colección", + "entryInfoActionEditDate": "Editar fecha y hora", "entryInfoActionEditLocation": "Editar ubicación", "entryInfoActionEditRating": "Editar clasificación", @@ -144,10 +148,23 @@ "displayRefreshRatePreferHighest": "Alta tasa", "displayRefreshRatePreferLowest": "Baja tasa", + "slideshowVideoPlaybackSkip": "Saltear", + "slideshowVideoPlaybackMuted": "Reproducir sin sonido", + "slideshowVideoPlaybackWithSound": "Reproducir con sonido", + "themeBrightnessLight": "Claro", "themeBrightnessDark": "Obscuro", "themeBrightnessBlack": "Negro", + "viewerTransitionSlide": "Diapositiva", + "viewerTransitionParallax": "Paralaje", + "viewerTransitionFade": "Desvanecer", + "viewerTransitionZoomIn": "Acercar", + + "wallpaperTargetHome": "Pantalla de inicio", + "wallpaperTargetLock": "Pantalla de bloqueo", + "wallpaperTargetHomeLock": "Pantallas de inicio y bloqueo", + "albumTierNew": "Nuevo", "albumTierPinned": "Fijado", "albumTierSpecial": "Común", @@ -262,6 +279,7 @@ "menuActionSelectAll": "Seleccionar todo", "menuActionSelectNone": "Deseleccionar", "menuActionMap": "Mapa", + "menuActionSlideshow": "Presentación", "menuActionStats": "Estadísticas", "viewDialogTabSort": "Ordenar", @@ -278,7 +296,6 @@ "appPickDialogTitle": "Escoger aplicación", "appPickDialogNone": "Ninguna", - "aboutPageTitle": "Acerca de", "aboutLinkSources": "Fuentes", "aboutLinkLicense": "Licencia", @@ -296,7 +313,6 @@ "aboutCreditsWorldAtlas1": "Esta aplicación usa un archivo TopoJSON de", "aboutCreditsWorldAtlas2": "bajo licencia ISC.", "aboutCreditsTranslators": "Traductores:", - "aboutCreditsTranslatorLine": "{language}: {names}", "aboutLicenses": "Licencias de código abierto", "aboutLicensesBanner": "Esta aplicación usa los siguientes paquetes y librerías de código abierto.", @@ -351,6 +367,7 @@ "collectionEmptyFavourites": "Sin favoritos", "collectionEmptyVideos": "Sin videos", "collectionEmptyImages": "Sin imágenes", + "collectionEmptyGrantAccessButtonLabel": "Otorgar accceso", "collectionSelectSectionTooltip": "Seleccionar sección", "collectionDeselectSectionTooltip": "Deseleccionar sección", @@ -421,6 +438,7 @@ "settingsSectionNavigation": "Navegación", "settingsHome": "Inicio", + "settingsShowBottomNavigationBar": "Mostrar barra de navegación inferior", "settingsKeepScreenOnTile": "Mantener pantalla encendida", "settingsKeepScreenOnTitle": "Mantener pantalla encendida", "settingsDoubleBackExit": "Presione «atrás» dos veces para salir", @@ -443,6 +461,7 @@ "settingsThumbnailOverlayTile": "Incrustaciones", "settingsThumbnailOverlayTitle": "Incrustaciones", "settingsThumbnailShowFavouriteIcon": "Mostrar icono de favoritos", + "settingsThumbnailShowTagIcon": "Mostrar ícono de etiqueta", "settingsThumbnailShowLocationIcon": "Mostrar icono de ubicación", "settingsThumbnailShowMotionPhotoIcon": "Mostrar icono de foto en movimiento", "settingsThumbnailShowRating": "Mostrar clasificación", @@ -476,9 +495,20 @@ "settingsViewerShowInformation": "Mostrar información", "settingsViewerShowInformationSubtitle": "Mostrar título, fecha, ubicación, etc.", "settingsViewerShowShootingDetails": "Mostrar detalles de toma", - "settingsViewerShowOverlayThumbnails": "Mostrar miniaturas", + "settingsViewerShowOverlayThumbnails": "Mostrar miniaturas", "settingsViewerEnableOverlayBlurEffect": "Efecto de difuminado", + "settingsViewerSlideshowTile": "Presentación", + "settingsViewerSlideshowTitle": "Presentación", + "settingsSlideshowRepeat": "Repetir", + "settingsSlideshowShuffle": "Mezclar", + "settingsSlideshowTransitionTile": "Transición", + "settingsSlideshowTransitionTitle": "Transición", + "settingsSlideshowIntervalTile": "Intervalo", + "settingsSlideshowIntervalTitle": "Intervalo", + "settingsSlideshowVideoPlaybackTile": "Reproducción de video", + "settingsSlideshowVideoPlaybackTitle": "Reproducción de video", + "settingsVideoPageTitle": "Ajustes de video", "settingsSectionVideo": "Video", "settingsVideoShowVideos": "Mostrar videos", @@ -486,8 +516,6 @@ "settingsVideoEnableAutoPlay": "Reproducción automática", "settingsVideoLoopModeTile": "Modo bucle", "settingsVideoLoopModeTitle": "Modo bucle", - "settingsVideoQuickActionsTile": "Acciones rápidas para videos", - "settingsVideoQuickActionEditorTitle": "Acciones rápidas", "settingsSubtitleThemeTile": "Subtítulos", "settingsSubtitleThemeTitle": "Subtítulos", @@ -545,6 +573,7 @@ "settingsSectionDisplay": "Pantalla", "settingsThemeBrightness": "Tema", "settingsThemeColorHighlights": "Acentos de color", + "settingsThemeEnableDynamicColor": "Color dinámico", "settingsDisplayRefreshRateModeTile": "Tasa de refresco de la pantalla", "settingsDisplayRefreshRateModeTitle": "Tasa de refresco", @@ -562,6 +591,7 @@ "statsTopTags": "Etiquetas principales", "viewerOpenPanoramaButtonLabel": "ABRIR PANORÁMICA", + "viewerSetWallpaperButtonLabel": "ESTABLECER FONDO", "viewerErrorUnknown": "¡Ups!", "viewerErrorDoesNotExist": "El archivo no existe.", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index de2480249..7c7437f10 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -50,6 +50,7 @@ "entryActionDelete": "Supprimer", "entryActionConvert": "Convertir", "entryActionExport": "Exporter", + "entryActionInfo": "Détails", "entryActionRename": "Renommer", "entryActionRestore": "Restaurer", "entryActionRotateCCW": "Pivoter à gauche", @@ -80,6 +81,9 @@ "videoActionSetSpeed": "Vitesse de lecture", "videoActionSettings": "Préférences", + "slideshowActionResume": "Reprendre", + "slideshowActionShowInCollection": "Afficher dans Collection", + "entryInfoActionEditDate": "Modifier la date", "entryInfoActionEditLocation": "Modifier le lieu", "entryInfoActionEditRating": "Modifier la notation", @@ -144,10 +148,23 @@ "displayRefreshRatePreferHighest": "Fréquence maximale", "displayRefreshRatePreferLowest": "Fréquence minimale", + "slideshowVideoPlaybackSkip": "Passer", + "slideshowVideoPlaybackMuted": "Jouer sans son", + "slideshowVideoPlaybackWithSound": "Jouer avec son", + "themeBrightnessLight": "Clair", "themeBrightnessDark": "Sombre", "themeBrightnessBlack": "Noir", + "viewerTransitionSlide": "Défilement", + "viewerTransitionParallax": "Parallaxe", + "viewerTransitionFade": "Fondu", + "viewerTransitionZoomIn": "Zoom", + + "wallpaperTargetHome": "Écran d’accueil", + "wallpaperTargetLock": "Écran de verrouillage", + "wallpaperTargetHomeLock": "Écrans accueil et verrouillage", + "albumTierNew": "Nouveaux", "albumTierPinned": "Épinglés", "albumTierSpecial": "Standards", @@ -262,6 +279,7 @@ "menuActionSelectAll": "Tout sélectionner", "menuActionSelectNone": "Tout désélectionner", "menuActionMap": "Carte", + "menuActionSlideshow": "Diaporama", "menuActionStats": "Statistiques", "viewDialogTabSort": "Tri", @@ -349,6 +367,7 @@ "collectionEmptyFavourites": "Aucun favori", "collectionEmptyVideos": "Aucune vidéo", "collectionEmptyImages": "Aucune image", + "collectionEmptyGrantAccessButtonLabel": "Autoriser l’accès", "collectionSelectSectionTooltip": "Sélectionner la section", "collectionDeselectSectionTooltip": "Désélectionner la section", @@ -479,6 +498,17 @@ "settingsViewerShowOverlayThumbnails": "Afficher les vignettes", "settingsViewerEnableOverlayBlurEffect": "Effets de flou", + "settingsViewerSlideshowTile": "Diaporama", + "settingsViewerSlideshowTitle": "Diaporama", + "settingsSlideshowRepeat": "Répéter", + "settingsSlideshowShuffle": "Aléatoire", + "settingsSlideshowTransitionTile": "Transition", + "settingsSlideshowTransitionTitle": "Transition", + "settingsSlideshowIntervalTile": "Intervalle", + "settingsSlideshowIntervalTitle": "Intervalle", + "settingsSlideshowVideoPlaybackTile": "Lecture de vidéos", + "settingsSlideshowVideoPlaybackTitle": "Lecture de vidéos", + "settingsVideoPageTitle": "Réglages vidéo", "settingsSectionVideo": "Vidéo", "settingsVideoShowVideos": "Afficher les vidéos", @@ -543,6 +573,7 @@ "settingsSectionDisplay": "Affichage", "settingsThemeBrightness": "Thème", "settingsThemeColorHighlights": "Surlignages colorés", + "settingsThemeEnableDynamicColor": "Couleur dynamique", "settingsDisplayRefreshRateModeTile": "Fréquence d’actualisation de l'écran", "settingsDisplayRefreshRateModeTitle": "Fréquence d’actualisation", @@ -560,6 +591,7 @@ "statsTopTags": "Top libellés", "viewerOpenPanoramaButtonLabel": "OUVRIR LE PANORAMA", + "viewerSetWallpaperButtonLabel": "APPLIQUER", "viewerErrorUnknown": "Zut !", "viewerErrorDoesNotExist": "Le fichier n’existe plus.", diff --git a/lib/l10n/app_id.arb b/lib/l10n/app_id.arb index b94a56778..295d70ad8 100644 --- a/lib/l10n/app_id.arb +++ b/lib/l10n/app_id.arb @@ -50,6 +50,7 @@ "entryActionDelete": "Hapus", "entryActionConvert": "Ubah", "entryActionExport": "Ekspor", + "entryActionInfo": "Info", "entryActionRename": "Ganti nama", "entryActionRestore": "Pulihkan", "entryActionRotateCCW": "Putar berlawanan arah jarum jam", diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index 756339ddd..33b957412 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -50,6 +50,7 @@ "entryActionDelete": "Elimina", "entryActionConvert": "Converti", "entryActionExport": "Esportazione", + "entryActionInfo": "Info", "entryActionRename": "Rinomina", "entryActionRestore": "Ripristina", "entryActionRotateCCW": "Ruota in senso antiorario", @@ -80,6 +81,9 @@ "videoActionSetSpeed": "Velocità di riproduzione", "videoActionSettings": "Impostazioni", + "slideshowActionResume": "Riprendi", + "slideshowActionShowInCollection": "Mostra nella Collezione", + "entryInfoActionEditDate": "Modifica data e ora", "entryInfoActionEditLocation": "Modifica posizione", "entryInfoActionEditRating": "Modifica valutazione", @@ -144,10 +148,23 @@ "displayRefreshRatePreferHighest": "Frequenza massima", "displayRefreshRatePreferLowest": "Frequenza minima", + "slideshowVideoPlaybackSkip": "Salta", + "slideshowVideoPlaybackMuted": "Riproduci senza audio", + "slideshowVideoPlaybackWithSound": "Riproduci con audio", + "themeBrightnessLight": "Chiaro", "themeBrightnessDark": "Scuro", "themeBrightnessBlack": "Nero", + "viewerTransitionSlide": "Diapositiva", + "viewerTransitionParallax": "Parallasse", + "viewerTransitionFade": "Dissolvenza", + "viewerTransitionZoomIn": "Ingrandisci", + + "wallpaperTargetHome": "Schermata iniziale", + "wallpaperTargetLock": "Schermata di blocco", + "wallpaperTargetHomeLock": "Schermata iniziale e di blocco", + "albumTierNew": "Nuovi", "albumTierPinned": "Fissati", "albumTierSpecial": "Frequenti", @@ -262,6 +279,7 @@ "menuActionSelectAll": "Seleziona tutto", "menuActionSelectNone": "Deseleziona tutto", "menuActionMap": "Mappa", + "menuActionSlideshow": "Presentazione", "menuActionStats": "Statistiche", "viewDialogTabSort": "Ordina", @@ -349,6 +367,7 @@ "collectionEmptyFavourites": "Nessun preferito", "collectionEmptyVideos": "Nessun video", "collectionEmptyImages": "Nessuna immagine", + "collectionEmptyGrantAccessButtonLabel": "Consenti accesso", "collectionSelectSectionTooltip": "Seleziona sezione", "collectionDeselectSectionTooltip": "Deseleziona sezione", @@ -479,6 +498,17 @@ "settingsViewerShowOverlayThumbnails": "Mostra le miniature", "settingsViewerEnableOverlayBlurEffect": "Effetto sfocatura", + "settingsViewerSlideshowTile": "Presentazione", + "settingsViewerSlideshowTitle": "Presentazione", + "settingsSlideshowRepeat": "Ripeti", + "settingsSlideshowShuffle": "Ordine casuale", + "settingsSlideshowTransitionTile": "Transizione", + "settingsSlideshowTransitionTitle": "Transizione", + "settingsSlideshowIntervalTile": "Intervallo", + "settingsSlideshowIntervalTitle": "Intervallo", + "settingsSlideshowVideoPlaybackTile": "Riproduzione video", + "settingsSlideshowVideoPlaybackTitle": "Riproduzione video", + "settingsVideoPageTitle": "Impostazioni video", "settingsSectionVideo": "Video", "settingsVideoShowVideos": "Mostra video", @@ -543,6 +573,7 @@ "settingsSectionDisplay": "Schermo", "settingsThemeBrightness": "Tema", "settingsThemeColorHighlights": "Colori evidenziati", + "settingsThemeEnableDynamicColor": "Colori dinamici", "settingsDisplayRefreshRateModeTile": "Frequenza di aggiornamento dello schermo", "settingsDisplayRefreshRateModeTitle": "Frequenza di aggiornamento", @@ -560,6 +591,7 @@ "statsTopTags": "Etichette più frequenti", "viewerOpenPanoramaButtonLabel": "APRI PANORAMA", + "viewerSetWallpaperButtonLabel": "IMPOSTA SFONDO", "viewerErrorUnknown": "Ops!", "viewerErrorDoesNotExist": "Il file non esiste più", diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb index 268aa39e9..1e5881ae2 100644 --- a/lib/l10n/app_ja.arb +++ b/lib/l10n/app_ja.arb @@ -50,6 +50,7 @@ "entryActionDelete": "削除", "entryActionConvert": "変換", "entryActionExport": "エクスポート", + "entryActionInfo": "情報", "entryActionRename": "名前を変更", "entryActionRestore": "元に戻す", "entryActionRotateCCW": "反時計回りに回転", diff --git a/lib/l10n/app_ko.arb b/lib/l10n/app_ko.arb index 57658eff1..91bc57c73 100644 --- a/lib/l10n/app_ko.arb +++ b/lib/l10n/app_ko.arb @@ -50,6 +50,7 @@ "entryActionDelete": "삭제", "entryActionConvert": "변환", "entryActionExport": "내보내기", + "entryActionInfo": "상세정보", "entryActionRename": "이름 변경", "entryActionRestore": "복원", "entryActionRotateCCW": "좌회전", @@ -80,6 +81,9 @@ "videoActionSetSpeed": "재생 배속", "videoActionSettings": "설정", + "slideshowActionResume": "이어서", + "slideshowActionShowInCollection": "미디어 페이지에서 보기", + "entryInfoActionEditDate": "날짜 및 시간 수정", "entryInfoActionEditLocation": "위치 수정", "entryInfoActionEditRating": "별점 수정", @@ -144,10 +148,23 @@ "displayRefreshRatePreferHighest": "가장 높은 재생률", "displayRefreshRatePreferLowest": "가장 낮은 재생률", + "slideshowVideoPlaybackSkip": "생략", + "slideshowVideoPlaybackMuted": "음소거 재생", + "slideshowVideoPlaybackWithSound": "일반 재생", + "themeBrightnessLight": "라이트", "themeBrightnessDark": "다크", "themeBrightnessBlack": "검은색", + "viewerTransitionSlide": "좌우", + "viewerTransitionParallax": "시차", + "viewerTransitionFade": "페이드", + "viewerTransitionZoomIn": "확대", + + "wallpaperTargetHome": "홈 화면", + "wallpaperTargetLock": "잠금화면", + "wallpaperTargetHomeLock": "홈 및 잠금화면", + "albumTierNew": "신규", "albumTierPinned": "고정", "albumTierSpecial": "기본", @@ -262,6 +279,7 @@ "menuActionSelectAll": "모두 선택", "menuActionSelectNone": "모두 해제", "menuActionMap": "지도", + "menuActionSlideshow": "슬라이드쇼", "menuActionStats": "통계", "viewDialogTabSort": "정렬", @@ -349,6 +367,7 @@ "collectionEmptyFavourites": "즐겨찾기가 없습니다", "collectionEmptyVideos": "동영상이 없습니다", "collectionEmptyImages": "사진이 없습니다", + "collectionEmptyGrantAccessButtonLabel": "접근 허용", "collectionSelectSectionTooltip": "묶음 선택", "collectionDeselectSectionTooltip": "묶음 선택 해제", @@ -479,6 +498,17 @@ "settingsViewerShowOverlayThumbnails": "섬네일 표시", "settingsViewerEnableOverlayBlurEffect": "흐림 효과", + "settingsViewerSlideshowTile": "슬라이드쇼", + "settingsViewerSlideshowTitle": "슬라이드쇼", + "settingsSlideshowRepeat": "반복", + "settingsSlideshowShuffle": "순서섞기", + "settingsSlideshowTransitionTile": "전환 효과", + "settingsSlideshowTransitionTitle": "전환 효과", + "settingsSlideshowIntervalTile": "교체 주기", + "settingsSlideshowIntervalTitle": "교체 주기", + "settingsSlideshowVideoPlaybackTile": "동영상 재생", + "settingsSlideshowVideoPlaybackTitle": "동영상 재생", + "settingsVideoPageTitle": "동영상 설정", "settingsSectionVideo": "동영상", "settingsVideoShowVideos": "미디어에 동영상 표시", @@ -543,6 +573,7 @@ "settingsSectionDisplay": "디스플레이", "settingsThemeBrightness": "테마", "settingsThemeColorHighlights": "색 강조", + "settingsThemeEnableDynamicColor": "동적 색상", "settingsDisplayRefreshRateModeTile": "화면 재생률", "settingsDisplayRefreshRateModeTitle": "화면 재생률", @@ -560,6 +591,7 @@ "statsTopTags": "태그 랭킹", "viewerOpenPanoramaButtonLabel": "파노라마 열기", + "viewerSetWallpaperButtonLabel": "설정", "viewerErrorUnknown": "아이구!", "viewerErrorDoesNotExist": "파일이 존재하지 않습니다.", diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb index 0bae2a926..de05648f1 100644 --- a/lib/l10n/app_pt.arb +++ b/lib/l10n/app_pt.arb @@ -49,6 +49,7 @@ "entryActionCopyToClipboard": "Copiar para área de transferência", "entryActionDelete": "Excluir", "entryActionExport": "Exportar", + "entryActionInfo": "Informações", "entryActionConvert": "Converter", "entryActionRename": "Renomear", "entryActionRestore": "Restaurar", @@ -80,6 +81,9 @@ "videoActionSetSpeed": "Velocidade de reprodução", "videoActionSettings": "Configurações", + "slideshowActionResume": "Retomar", + "slideshowActionShowInCollection": "Mostrar na Coleção", + "entryInfoActionEditDate": "Editar data e hora", "entryInfoActionEditLocation": "Editar localização", "entryInfoActionEditRating": "Editar classificação", @@ -144,10 +148,23 @@ "displayRefreshRatePreferHighest": "Taxa mais alta", "displayRefreshRatePreferLowest": "Taxa mais baixa", + "slideshowVideoPlaybackSkip": "Pular", + "slideshowVideoPlaybackMuted": "Reproduzir sem som", + "slideshowVideoPlaybackWithSound": "Reproduzir com som", + "themeBrightnessLight": "Claro", "themeBrightnessDark": "Escuro", "themeBrightnessBlack": "Preto", + "viewerTransitionSlide": "Deslizar", + "viewerTransitionParallax": "Parallax", + "viewerTransitionFade": "Desvaneça", + "viewerTransitionZoomIn": "Mais zoom", + + "wallpaperTargetHome": "Tela inicial", + "wallpaperTargetLock": "Tela de bloqueio", + "wallpaperTargetHomeLock": "Telas iniciais e de bloqueio", + "albumTierNew": "Novo", "albumTierPinned": "Fixada", "albumTierSpecial": "Comum", @@ -262,6 +279,7 @@ "menuActionSelectAll": "Selecionar tudo", "menuActionSelectNone": "Selecione nenhum", "menuActionMap": "Mapa", + "menuActionSlideshow": "Apresentação de slides", "menuActionStats": "Estatísticas", "viewDialogTabSort": "Organizar", @@ -349,6 +367,7 @@ "collectionEmptyFavourites": "Nenhum favorito", "collectionEmptyVideos": "Nenhum video", "collectionEmptyImages": "Nenhuma image", + "collectionEmptyGrantAccessButtonLabel": "Garantir acesso", "collectionSelectSectionTooltip": "Selecionar seção", "collectionDeselectSectionTooltip": "Desmarcar seção", @@ -479,6 +498,17 @@ "settingsViewerShowOverlayThumbnails": "Mostrar miniaturas", "settingsViewerEnableOverlayBlurEffect": "Efeito de desfoque", + "settingsViewerSlideshowTile": "Apresentação de slides", + "settingsViewerSlideshowTitle": "Apresentação de slides", + "settingsSlideshowRepeat": "Repetir", + "settingsSlideshowShuffle": "Embaralhar", + "settingsSlideshowTransitionTile": "Transição", + "settingsSlideshowTransitionTitle": "Transição", + "settingsSlideshowIntervalTile": "Intervalo", + "settingsSlideshowIntervalTitle": "Intervalo", + "settingsSlideshowVideoPlaybackTile": "Reprodução de vídeo", + "settingsSlideshowVideoPlaybackTitle": "Reprodução de vídeo", + "settingsVideoPageTitle": "Configurações de vídeo", "settingsSectionVideo": "Vídeo", "settingsVideoShowVideos": "Mostrar vídeos", @@ -543,6 +573,7 @@ "settingsSectionDisplay": "Tela", "settingsThemeBrightness": "Tema", "settingsThemeColorHighlights": "Destaques de cores", + "settingsThemeEnableDynamicColor": "Cor dinâmica", "settingsDisplayRefreshRateModeTile": "Taxa de atualização de exibição", "settingsDisplayRefreshRateModeTitle": "Taxa de atualização", @@ -560,6 +591,7 @@ "statsTopTags": "Principais Etiquetas", "viewerOpenPanoramaButtonLabel": "ABRIR PANORAMA", + "viewerSetWallpaperButtonLabel": "DEFINIR PAPEL DE PAREDE", "viewerErrorUnknown": "Algo não está certo!", "viewerErrorDoesNotExist": "O arquivo não existe mais.", diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb index 87b28c143..b8f7efa3a 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -50,6 +50,7 @@ "entryActionDelete": "Удалить", "entryActionConvert": "Конвертировать", "entryActionExport": "Экспорт", + "entryActionInfo": "Информация", "entryActionRename": "Переименовать", "entryActionRestore": "Восстановить", "entryActionRotateCCW": "Повернуть против часовой стрелки", diff --git a/lib/l10n/app_tr.arb b/lib/l10n/app_tr.arb new file mode 100644 index 000000000..84c152411 --- /dev/null +++ b/lib/l10n/app_tr.arb @@ -0,0 +1,639 @@ +{ + "appName": "Aves", + "welcomeMessage": "Aves'e Hoş Geldiniz", + "welcomeOptional": "İsteğe bağlı", + "welcomeTermsToggle": "Hüküm ve koşulları kabul ediyorum", + "itemCount": "{count, plural, =1{1 öğe} other{{count} öğe}}", + + "timeSeconds": "{seconds, plural, =1{1 saniye} other{{seconds} saniye}}", + "timeMinutes": "{minutes, plural, =1{1 dakika} other{{minutes} dakika}}", + "timeDays": "{days, plural, =1{1 gün} other{{days} gün}}", + "focalLength": "{length} mm", + + "applyButtonLabel": "UYGULA", + "deleteButtonLabel": "SİL", + "nextButtonLabel": "SONRAKİ", + "showButtonLabel": "GÖSTER", + "hideButtonLabel": "GİZLE", + "continueButtonLabel": "DEVAM ET", + + "cancelTooltip": "İptal et", + "changeTooltip": "Değiştir", + "clearTooltip": "Temizle", + "previousTooltip": "Önceki", + "nextTooltip": "Sonraki", + "showTooltip": "Göster", + "hideTooltip": "Gizle", + "actionRemove": "Kaldır", + "resetButtonTooltip": "Sıfırla", + + "doubleBackExitMessage": "Çıkmak için tekrar “geri”, düğmesine dokunun.", + "doNotAskAgain": "Bir daha sorma", + + "sourceStateLoading": "Yükleniyor", + "sourceStateCataloguing": "Kataloglanıyor", + "sourceStateLocatingCountries": "Ülkeler konumlandırılıyor", + "sourceStateLocatingPlaces": "Konum belirleniyor", + + "chipActionDelete": "Sil", + "chipActionGoToAlbumPage": "Albümlerde göster", + "chipActionGoToCountryPage": "Ülkelerde göster", + "chipActionGoToTagPage": "Etiketlerde göster", + "chipActionHide": "Gizle", + "chipActionPin": "Başa sabitle", + "chipActionUnpin": "Baştan çıkar", + "chipActionRename": "Yeniden adlandır", + "chipActionSetCover": "Kapağı ayarla", + "chipActionCreateAlbum": "Albüm oluştur", + + "entryActionCopyToClipboard": "Panoya kopyala", + "entryActionDelete": "Sil", + "entryActionConvert": "Dönüştür", + "entryActionExport": "Dışa aktar", + "entryActionInfo": "Bilgi", + "entryActionRename": "Yeniden adlandır", + "entryActionRestore": "Dışa aktar", + "entryActionRotateCCW": "Saat yönünün tersine döndür", + "entryActionRotateCW": "Saat yönünde döndür", + "entryActionFlip": "Yatay olarak çevir", + "entryActionPrint": "Yazdır", + "entryActionShare": "Paylaş", + "entryActionViewSource": "Kaynağı görüntüle", + "entryActionShowGeoTiffOnMap": "Harita katmanı olarak göster", + "entryActionConvertMotionPhotoToStillImage": "Hareketsiz görüntüye dönüştür", + "entryActionViewMotionPhotoVideo": "Videoyu aç", + "entryActionEdit": "Düzenle", + "entryActionOpen": "Şununla aç", + "entryActionSetAs": "Olarak ayarla", + "entryActionOpenMap": "Harita uygulamasında göster", + "entryActionRotateScreen": "Ekranı döndür", + "entryActionAddFavourite": "Favorilere ekle", + "entryActionRemoveFavourite": "Favorilerden kaldır", + + "videoActionCaptureFrame": "Çerçeve yakala", + "videoActionMute": "Sustur", + "videoActionUnmute": "Susturmayı kaldır", + "videoActionPause": "Duraklat", + "videoActionPlay": "Oynat", + "videoActionReplay10": "10 saniye geri git", + "videoActionSkip10": "10 saniye ileri git", + "videoActionSelectStreams": "Parça seç", + "videoActionSetSpeed": "Oynatma hızı", + "videoActionSettings": "Ayarlar", + + "entryInfoActionEditDate": "Tarih ve saati düzenle", + "entryInfoActionEditLocation": "Konumu düzenle", + "entryInfoActionEditRating": "Derecelendirmeyi düzenle", + "entryInfoActionEditTags": "Etiketleri düzenle", + "entryInfoActionRemoveMetadata": "Meta verileri kaldır", + + "filterBinLabel": "Geri dönüşüm kutusu", + "filterFavouriteLabel": "Favori", + "filterLocationEmptyLabel": "Konumsuz", + "filterTagEmptyLabel": "Etiketsiz", + "filterRatingUnratedLabel": "Derecelendirilmemiş", + "filterRatingRejectedLabel": "Reddedilmiş", + "filterTypeAnimatedLabel": "Hareketli", + "filterTypeMotionPhotoLabel": "Hareketli Fotoğraf", + "filterTypePanoramaLabel": "Panorama", + "filterTypeRawLabel": "Raw", + "filterTypeSphericalVideoLabel": "360° Video", + "filterTypeGeotiffLabel": "GeoTIFF", + "filterMimeImageLabel": "Resim", + "filterMimeVideoLabel": "Video", + + "coordinateFormatDms": "DMS", + "coordinateFormatDecimal": "Ondalık dereceler", + "coordinateDms": "{coordinate} {direction}", + + "coordinateDmsNorth": "K", + "coordinateDmsSouth": "G", + "coordinateDmsEast": "D", + "coordinateDmsWest": "B", + + "unitSystemMetric": "Metrik", + "unitSystemImperial": "İngiliz", + + "videoLoopModeNever": "Asla", + "videoLoopModeShortOnly": "Yalnızca kısa videolar", + "videoLoopModeAlways": "Her zaman", + + "videoControlsPlay": "Oynat", + "videoControlsPlaySeek": "Oynat ve ileri/geri git", + "videoControlsPlayOutside": "Başka bir oynatıcı ile aç", + "videoControlsNone": "Hiçbiri", + + "mapStyleGoogleNormal": "Google Haritalar", + "mapStyleGoogleHybrid": "Google Haritalar (Hibrit)", + "mapStyleGoogleTerrain": "Google Haritalar (Arazi)", + "mapStyleHuaweiNormal": "Petal Haritalar", + "mapStyleHuaweiTerrain": "Petal Haritalar (Arazi)", + "mapStyleOsmHot": "İnsancıl OSM", + "mapStyleStamenToner": "Stamen Tonik", + "mapStyleStamenWatercolor": "Stamen Suluboya", + + "nameConflictStrategyRename": "Yeniden adlandır", + "nameConflictStrategyReplace": "Değiştir", + "nameConflictStrategySkip": "Atla", + + "keepScreenOnNever": "Asla", + "keepScreenOnViewerOnly": "Yalnızca görüntüleyici sayfası", + "keepScreenOnAlways": "Her zaman", + + "accessibilityAnimationsRemove": "Ekran efektlerini önle", + "accessibilityAnimationsKeep": "Ekran efektlerini koru", + + "displayRefreshRatePreferHighest": "En yüksek oran", + "displayRefreshRatePreferLowest": "En düşük oran", + + "themeBrightnessLight": "Açık", + "themeBrightnessDark": "Koyu", + "themeBrightnessBlack": "Siyah", + + "albumTierNew": "Yeni", + "albumTierPinned": "Sabitlenmiş", + "albumTierSpecial": "Genel", + "albumTierApps": "Uygulamalar", + "albumTierRegular": "Diğer", + + "storageVolumeDescriptionFallbackPrimary": "Dahili depolama", + "storageVolumeDescriptionFallbackNonPrimary": "SD kart", + "rootDirectoryDescription": "kök dizin", + "otherDirectoryDescription": "“{name}” dizin", + + "storageAccessDialogTitle": "Depolama Erişimi", + "storageAccessDialogMessage": "Bu uygulamaya erişim sağlamak için lütfen bir sonraki ekranda “{volume}” öğesinin {directory} dizinini seçin.", + + "restrictedAccessDialogTitle": "Kısıtlı Erişim", + "restrictedAccessDialogMessage": "Bu uygulamanın “{volume}” içindeki {directory} dosyaları değiştirmesine izin verilmiyor.\n\nÖğeleri başka bir dizine taşımak için lütfen önceden yüklenmiş bir dosya yöneticisi veya galeri uygulaması kullanın.", + + "notEnoughSpaceDialogTitle": "Yeterli Yer Yok", + "notEnoughSpaceDialogMessage": "Bu işlemin tamamlanması için “{volume}” üzerinde {needSize} boş alana ihtiyaç var, ancak yalnızca {freeSize} kaldı.", + + "missingSystemFilePickerDialogTitle": "Eksik Sistem Dosya Seçicisi", + "missingSystemFilePickerDialogMessage": "Sistem dosya seçicisi eksik veya devre dışı. Lütfen etkinleştirin ve tekrar deneyin.", + + "unsupportedTypeDialogTitle": "Desteklenmeyen Türler", + "unsupportedTypeDialogMessage": "{count, plural, =1{Bu işlem aşağıdaki türdeki öğeler için desteklenmez: {types}.} other{Bu işlem aşağıdaki türlerdeki öğeler için desteklenmez: {types}.}}", + + "nameConflictDialogSingleSourceMessage": "Hedef klasördeki bazı dosyalar aynı ada sahip.", + "nameConflictDialogMultipleSourceMessage": "Bazı dosyalar aynı ada sahip.", + + "addShortcutDialogLabel": "Kısayol etiketi", + "addShortcutButtonLabel": "EKLE", + + "noMatchingAppDialogTitle": "Eşleşen Uygulama Yok", + "noMatchingAppDialogMessage": "Bununla ilgilenebilecek bir uygulama yok.", + + "binEntriesConfirmationDialogMessage": "{count, plural, =1{Bu öğe geri dönüşüm kutusuna taşınsın mı?} other{Bu {count} madde geri dönüşüm kutusuna atılsın mı?}}", + + "deleteEntriesConfirmationDialogMessage": "{count, plural, =1{Bu öğe silinsin mi?} other{Bu {count} öğe silinsin mi?}}", + + "moveUndatedConfirmationDialogMessage": "Devam etmeden önce öğe tarihleri ​​kaydedilsin mi?", + "moveUndatedConfirmationDialogSetDate": "Tarihleri kaydet", + + "videoResumeDialogMessage": "{time} itibarıyla oynatmaya devam etmek istiyor musunuz?", + + "videoStartOverButtonLabel": "BAŞTAN BAŞLA", + "videoResumeButtonLabel": "SÜRDÜR", + + "setCoverDialogLatest": "Son öğe", + "setCoverDialogAuto": "Otomatik", + "setCoverDialogCustom": "Özel", + + "hideFilterConfirmationDialogMessage": "Eşleşen fotoğraf ve videolar koleksiyonunuzdan gizlenecektir. Bunları “Gizlilik”, ayarlarından tekrar gösterebilirsiniz.\n\nBunları gizlemek istediğinizden emin misiniz?", + + "newAlbumDialogTitle": "Yeni Albüm", + "newAlbumDialogNameLabel": "Albüm adı", + "newAlbumDialogNameLabelAlreadyExistsHelper": "Dizin zaten var", + "newAlbumDialogStorageLabel": "Depolama:", + + "renameAlbumDialogLabel": "Yeni ad", + "renameAlbumDialogLabelAlreadyExistsHelper": "Dizin zaten var", + + "renameEntrySetPageTitle": "Yeniden adlandır", + "renameEntrySetPagePatternFieldLabel": "İsimlendirme şekli", + "renameEntrySetPageInsertTooltip": "Alan ekle", + "renameEntrySetPagePreview": "Önizleme", + + "renameProcessorCounter": "Sayaç", + "renameProcessorName": "Ad", + + "deleteSingleAlbumConfirmationDialogMessage": "{count, plural, =1{Bu albüm ve öğesi silinsin mi?} other{Bu albüm ve {count} öğesi silinsin mi?}}", + + "deleteMultiAlbumConfirmationDialogMessage": "{count, plural, =1{Bu albümler ve öğeleri silinsin mi?} other{Bu albümler ve {count} öğesi silinsin mi?}}", + + "exportEntryDialogFormat": "Biçim:", + "exportEntryDialogWidth": "Genişlik", + "exportEntryDialogHeight": "Yükseklik", + + "renameEntryDialogLabel": "Yeni ad", + + "editEntryDateDialogTitle": "Tarih ve Saat", + "editEntryDateDialogSetCustom": "Özel tarih ayarla", + "editEntryDateDialogCopyField": "Başka bir tarihten kopyala", + "editEntryDateDialogCopyItem": "Başka bir öğeden kopyala", + "editEntryDateDialogExtractFromTitle": "Başlıktan ayıkla", + "editEntryDateDialogShift": "Değişim", + "editEntryDateDialogSourceFileModifiedDate": "Dosya değiştirilme tarihi", + "editEntryDateDialogTargetFieldsHeader": "Değiştirilecek alanlar", + "editEntryDateDialogHours": "Saat", + "editEntryDateDialogMinutes": "Dakika", + + "editEntryLocationDialogTitle": "Konum", + "editEntryLocationDialogChooseOnMapTooltip": "Harita üzerinde seç", + "editEntryLocationDialogLatitude": "Enlem", + "editEntryLocationDialogLongitude": "Boylam", + + "locationPickerUseThisLocationButton": "Bu konumu kullan", + + "editEntryRatingDialogTitle": "Derecelendirme", + + "removeEntryMetadataDialogTitle": "Meta veri kaldırma", + "removeEntryMetadataDialogMore": "Daha fazla", + + "removeEntryMetadataMotionPhotoXmpWarningDialogMessage": "Hareketli bir fotoğrafın içindeki videoyu oynatmak için XMP gereklidir.\n\nKaldırmak istediğinizden emin misiniz?", + "convertMotionPhotoToStillImageWarningDialogMessage": "Emin misiniz?", + + "videoSpeedDialogLabel": "Oynatma hızı", + + "videoStreamSelectionDialogVideo": "Video", + "videoStreamSelectionDialogAudio": "Ses", + "videoStreamSelectionDialogText": "Altyazı", + "videoStreamSelectionDialogOff": "Kapalı", + "videoStreamSelectionDialogTrack": "Parça", + "videoStreamSelectionDialogNoSelection": "Başka parça yok.", + + "genericSuccessFeedback": "Başarılı!", + "genericFailureFeedback": "Başarısız", + + "menuActionConfigureView": "Görünüm", + "menuActionSelect": "Seç", + "menuActionSelectAll": "Hepsini seç", + "menuActionSelectNone": "Hiçbirini seçme", + "menuActionMap": "Harita", + "menuActionStats": "İstatistikler", + + "viewDialogTabSort": "Sırala", + "viewDialogTabGroup": "Grup", + "viewDialogTabLayout": "Düzen", + + "tileLayoutGrid": "Izgara", + "tileLayoutList": "Liste", + + "coverDialogTabCover": "Kapak", + "coverDialogTabApp": "Uygulama", + "coverDialogTabColor": "Renk", + + "appPickDialogTitle": "Uygulama seç", + "appPickDialogNone": "Yok", + + "aboutPageTitle": "Hakkında", + "aboutLinkSources": "Kaynaklar", + "aboutLinkLicense": "Lisans", + "aboutLinkPolicy": "Gizlilik Politikası", + + "aboutBug": "Hata Bildirimi", + "aboutBugSaveLogInstruction": "Uygulama günlüklerini bir dosyaya kaydet", + "aboutBugSaveLogButton": "Kaydet", + "aboutBugCopyInfoInstruction": "Sistem bilgilerini kopyala", + "aboutBugCopyInfoButton": "Kopyala", + "aboutBugReportInstruction": "GitHub'da günlükleri ve sistem bilgilerini içeren bir rapor oluştur", + "aboutBugReportButton": "Raporla", + + "aboutCredits": "Kredi", + "aboutCreditsWorldAtlas1": "Bu uygulama bir TopoJSON dosyası kullanır", + "aboutCreditsWorldAtlas2": "ISC Lisansı kapsamında.", + "aboutCreditsTranslators": "Tercümanlar", + + "aboutLicenses": "Açık Kaynak Lisansları", + "aboutLicensesBanner": "Bu uygulama aşağıdaki açık kaynaklı paketleri ve kütüphaneleri kullanır.", + "aboutLicensesAndroidLibraries": "Android Kütüphaneleri", + "aboutLicensesFlutterPlugins": "Flutter Eklentileri", + "aboutLicensesFlutterPackages": "Flutter Paketleri", + "aboutLicensesDartPackages": "Dart Paketleri", + "aboutLicensesShowAllButtonLabel": "Tüm Lisansları Göster", + + "policyPageTitle": "Gizlilik Politikası", + + "collectionPageTitle": "Koleksiyon", + "collectionPickPageTitle": "Seç", + "collectionSelectPageTitle": "Öğeleri seç", + + "collectionActionShowTitleSearch": "Başlık filtresini göster", + "collectionActionHideTitleSearch": "Başlık filtresini gizle", + "collectionActionAddShortcut": "Kısayol ekle", + "collectionActionEmptyBin": "Boş çöp kutusu", + "collectionActionCopy": "Albüme kopyala", + "collectionActionMove": "Albüme taşı", + "collectionActionRescan": "Yeniden tara", + "collectionActionEdit": "Düzenle", + + "collectionSearchTitlesHintText": "Başlıkları ara", + + "collectionSortDate": "Tarihe göre", + "collectionSortSize": "Boyuta göre", + "collectionSortName": "Albüm ve dosya adına göre", + "collectionSortRating": "Derecelendirmeye göre", + + "collectionGroupAlbum": "Albüme göre", + "collectionGroupMonth": "Aya göre", + "collectionGroupDay": "Güne göre", + "collectionGroupNone": "Gruplama", + + "sectionUnknown": "Bilinmeyen", + "dateToday": "Bugün", + "dateYesterday": "Dün", + "dateThisMonth": "Bu ay", + "collectionDeleteFailureFeedback": "{count, plural, =1{1 öğe silinemedi} other{{count} öğe silinemedi}}", + + "collectionCopyFailureFeedback": "{count, plural, =1{1 öğe kopyalanamadı} other{{count} öğe kopyalanamadı}}", + + "collectionMoveFailureFeedback": "{count, plural, =1{1 öğe taşınamadı} other{{count} öğe taşınamadı}}", + + "collectionRenameFailureFeedback": "{count, plural, =1{1 öğenin adı değiştirilemedi} other{{count} öğenin adı değiştirilemedi}}", + + "collectionEditFailureFeedback": "{count, plural, =1{1 öğe düzenlenemedi} other{{count} öğe düzenlenemedi}}", + + "collectionExportFailureFeedback": "{count, plural, =1{1 sayfa dışa aktarılamadı} other{{count} sayfa dışa aktarılamadı}", + + "collectionCopySuccessFeedback": "{count, plural, =1{1 öğe kopyalandı} other{{count} öğe kopyalandı}}", + + "collectionMoveSuccessFeedback": "{count, plural, =1{1 öğe taşındı} other{{count} öğe taşındı}}", + + "collectionRenameSuccessFeedback": "{count, plural, =1{1 öğenin adı değiştirildi} other{{count} öğenin adı değiştirildi}}", + + "collectionEditSuccessFeedback": "{count, plural, =1{1 öğe düzenlendi} other{{count} öğe düzenlendi}}", + + "collectionEmptyFavourites": "Favori yok", + "collectionEmptyVideos": "Video yok", + "collectionEmptyImages": "Resim yok", + "collectionEmptyGrantAccessButtonLabel": "Erişim izni", + + "collectionSelectSectionTooltip": "Bölüm seç", + "collectionDeselectSectionTooltip": "Bölüm seçimini kaldır", + + "drawerCollectionAll": "Tüm koleksiyon", + "drawerCollectionFavourites": "Favoriler", + "drawerCollectionImages": "Resimler", + "drawerCollectionVideos": "Videolar", + "drawerCollectionAnimated": "Hareketli", + "drawerCollectionMotionPhotos": "Hareketli fotoğraflar", + "drawerCollectionPanoramas": "Panoramalar", + "drawerCollectionRaws": "Raw fotoğraflar", + "drawerCollectionSphericalVideos": "360° Videolar", + + "chipSortDate": "Tarihe göre", + "chipSortName": "Adına göre", + "chipSortCount": "Öğe sayısına göre", + + "albumGroupTier": "Kademeye göre", + "albumGroupVolume": "Depolama hacmine göre", + "albumGroupNone": "Gruplama", + + "albumPickPageTitleCopy": "Albüme kopyala", + "albumPickPageTitleExport": "Albüme aktar", + "albumPickPageTitleMove": "Albüme taşı", + "albumPickPageTitlePick": "Albüm seç", + + "albumCamera": "Kamera", + "albumDownload": "İndir", + "albumScreenshots": "Ekran görüntüleri", + "albumScreenRecordings": "Ekran kayıtları", + "albumVideoCaptures": "Video çekimleri", + + "albumPageTitle": "Albümler", + "albumEmpty": "Albüm yok", + "createAlbumTooltip": "Albüm oluştur", + "createAlbumButtonLabel": "OLUŞTUR", + "newFilterBanner": "yeni", + + "countryPageTitle": "Ülkeler", + "countryEmpty": "Ülke yok", + + "tagPageTitle": "Etiketler", + "tagEmpty": "Etiket yok", + + "binPageTitle": "Geri Dönüşüm Kutusu", + + "searchCollectionFieldHint": "Koleksiyonu ara", + "searchSectionRecent": "Yakın zamanda", + "searchSectionAlbums": "Albümler", + "searchSectionCountries": "Ülkeler", + "searchSectionPlaces": "Yerler", + "searchSectionTags": "Etiketler", + "searchSectionRating": "Derecelendirmeler", + + "settingsPageTitle": "Ayarlar", + "settingsSystemDefault": "Sistem", + "settingsDefault": "Varsayılan", + + "settingsSearchFieldLabel": "Ayarlarda ara", + "settingsSearchEmpty": "Eşleşen ayar bulunamadı", + "settingsActionExport": "Dışa aktar", + "settingsActionImport": "İçe aktar", + + "appExportCovers": "Kapaklar", + "appExportFavourites": "Favoriler", + "appExportSettings": "Ayarlar", + + "settingsSectionNavigation": "Gezinti", + "settingsHome": "Anasayfa", + "settingsShowBottomNavigationBar": "Alt gezinti çubuğunu göster", + "settingsKeepScreenOnTile": "Ekranı açık tut", + "settingsKeepScreenOnTitle": "Ekranı Açık Tut", + "settingsDoubleBackExit": "Çıkmak için iki kez “geri” düğmesine dokunun", + + "settingsConfirmationDialogTile": "Onaylama diyalogları", + "settingsConfirmationDialogTitle": "Onaylama Diyalogları", + "settingsConfirmationDialogDeleteItems": "Öğeleri sonsuza dek silmeden önce sor", + "settingsConfirmationDialogMoveToBinItems": "Eşyaları geri dönüşüm kutusuna atmadan önce sor", + "settingsConfirmationDialogMoveUndatedItems": "Tarihsiz eşyaları taşımadan önce sor", + + "settingsNavigationDrawerTile": "Gezinti menüsü", + "settingsNavigationDrawerEditorTitle": "Gezinti Menüsü", + "settingsNavigationDrawerBanner": "Menü öğelerini taşımak ve yeniden sıralamak için dokunun ve basılı tutun.", + "settingsNavigationDrawerTabTypes": "Türler", + "settingsNavigationDrawerTabAlbums": "Albümler", + "settingsNavigationDrawerTabPages": "Sayfalar", + "settingsNavigationDrawerAddAlbum": "Albüm ekle", + + "settingsSectionThumbnails": "Küçük resimler", + "settingsThumbnailOverlayTile": "Kaplama", + "settingsThumbnailOverlayTitle": "Kaplama", + "settingsThumbnailShowFavouriteIcon": "Favori simgeyi göster", + "settingsThumbnailShowTagIcon": "Etiket simgesini göster", + "settingsThumbnailShowLocationIcon": "Konum simgesini göster", + "settingsThumbnailShowMotionPhotoIcon": "Hareketli fotoğraf simgesini göster", + "settingsThumbnailShowRating": "Derecelendirmeyi göster", + "settingsThumbnailShowRawIcon": "Raw simgesini göster", + "settingsThumbnailShowVideoDuration": "Video süresini göster", + + "settingsCollectionQuickActionsTile": "Hızlı eylemler", + "settingsCollectionQuickActionEditorTitle": "Hızlı Eylemler", + "settingsCollectionQuickActionTabBrowsing": "Gözatma", + "settingsCollectionQuickActionTabSelecting": "Seçme", + "settingsCollectionBrowsingQuickActionEditorBanner": "Düğmeleri hareket ettirmek ve öğelere göz atarken hangi eylemlerin görüntüleneceğini seçmek için dokunun ve basılı tutun.", + "settingsCollectionSelectionQuickActionEditorBanner": "Düğmeleri hareket ettirmek ve öğeleri seçerken hangi eylemlerin görüntüleneceğini seçmek için dokunun ve basılı tutun.", + + "settingsSectionViewer": "Görüntüleyici", + "settingsViewerUseCutout": "Kesim alanını kullan", + "settingsViewerMaximumBrightness": "Maksimum parlaklık", + "settingsMotionPhotoAutoPlay": "Hareketli fotoğrafları otomatik oynat", + "settingsImageBackground": "Resim arka planı", + + "settingsViewerQuickActionsTile": "Hızlı eylemler", + "settingsViewerQuickActionEditorTitle": "Hızlı Eylemler", + "settingsViewerQuickActionEditorBanner": "Düğmeleri hareket ettirmek ve görüntüleyicide hangi eylemlerin görüntüleneceğini seçmek için dokunun ve basılı tutun.", + "settingsViewerQuickActionEditorDisplayedButtons": "Gösterilen Düğmeler", + "settingsViewerQuickActionEditorAvailableButtons": "Mevcut Düğmeler", + "settingsViewerQuickActionEmpty": "Düğme yok", + + "settingsViewerOverlayTile": "Kaplama", + "settingsViewerOverlayTitle": "Kaplama", + "settingsViewerShowOverlayOnOpening": "Açılışta göster", + "settingsViewerShowMinimap": "Mini haritayı göster", + "settingsViewerShowInformation": "Bilgileri göster", + "settingsViewerShowInformationSubtitle": "Başlığı, tarihi, konumu vb. göster.", + "settingsViewerShowShootingDetails": "Çekim ayrıntılarını göster", + "settingsViewerShowOverlayThumbnails": "Küçük resimleri göster", + "settingsViewerEnableOverlayBlurEffect": "Bulanıklık efekti", + + "settingsVideoPageTitle": "Video Ayarları", + "settingsSectionVideo": "Video", + "settingsVideoShowVideos": "Videoları göster", + "settingsVideoEnableHardwareAcceleration": "Donanım hızlandırma", + "settingsVideoEnableAutoPlay": "Otomatik oynat", + "settingsVideoLoopModeTile": "Döngü modu", + "settingsVideoLoopModeTitle": "Döngü Modu", + + "settingsSubtitleThemeTile": "Altyazılar", + "settingsSubtitleThemeTitle": "Altyazılar", + "settingsSubtitleThemeSample": "Bu bir örnek.", + "settingsSubtitleThemeTextAlignmentTile": "Metin hizalama", + "settingsSubtitleThemeTextAlignmentTitle": "Metin Hizalama", + "settingsSubtitleThemeTextSize": "Metin boyutu", + "settingsSubtitleThemeShowOutline": "Dış çizgiyi ve gölgeyi göster", + "settingsSubtitleThemeTextColor": "Metin rengi", + "settingsSubtitleThemeTextOpacity": "Metin opaklığı", + "settingsSubtitleThemeBackgroundColor": "Arka plan rengi", + "settingsSubtitleThemeBackgroundOpacity": "Arka plan opaklığı", + "settingsSubtitleThemeTextAlignmentLeft": "Sol", + "settingsSubtitleThemeTextAlignmentCenter": "Merkez", + "settingsSubtitleThemeTextAlignmentRight": "Sağ", + + "settingsVideoControlsTile": "Kontroller", + "settingsVideoControlsTitle": "Kontroller", + "settingsVideoButtonsTile": "Düğmeler", + "settingsVideoButtonsTitle": "Düğmeler", + "settingsVideoGestureDoubleTapTogglePlay": "Oynatmak/duraklatmak için çift dokunun", + "settingsVideoGestureSideDoubleTapSeek": "Geri/ileri aramak için ekran kenarlarına çift dokunun", + + "settingsSectionPrivacy": "Gizlilik", + "settingsAllowInstalledAppAccess": "Uygulama envanterine erişime izin ver", + "settingsAllowInstalledAppAccessSubtitle": "Albüm görüntüsünü iyileştirmek için kullanılır", + "settingsAllowErrorReporting": "Anonim hata raporlamasına izin ver", + "settingsSaveSearchHistory": "Arama geçmişini kaydet", + "settingsEnableBin": "Geri dönüşüm kutusunu kullan", + "settingsEnableBinSubtitle": "Silinen öğeleri 30 gün boyunca saklar", + + "settingsHiddenItemsTile": "Gizli öğeler", + "settingsHiddenItemsTitle": "Gizli Öğeler", + + "settingsHiddenFiltersTitle": "Gizli Filtreler", + "settingsHiddenFiltersBanner": "Gizli filtrelerle eşleşen fotoğraflar ve videolar koleksiyonunuzda görünmeyecektir.", + "settingsHiddenFiltersEmpty": "Gizli filtre yok", + + "settingsHiddenPathsTitle": "Gizli Yollar", + "settingsHiddenPathsBanner": "Bu klasörlerdeki veya alt klasörlerindeki fotoğraflar ve videolar koleksiyonunuzda görünmeyecektir.", + "addPathTooltip": "Yol ekle", + + "settingsStorageAccessTile": "Depolama erişimi", + "settingsStorageAccessTitle": "Depolama Erişimi", + "settingsStorageAccessBanner": "Bazı dizinler, içlerindeki dosyaları değiştirmek için açık bir erişim izni gerektirir. Daha önce erişim izni verdiğiniz dizinleri buradan inceleyebilirsiniz.", + "settingsStorageAccessEmpty": "Erişim izni yok", + "settingsStorageAccessRevokeTooltip": "Geri al", + + "settingsSectionAccessibility": "Erişilebilirlik", + "settingsRemoveAnimationsTile": "Animasyonları kaldır", + "settingsRemoveAnimationsTitle": "Animasyonları Kaldır", + "settingsTimeToTakeActionTile": "Harekete geçme zamanı", + "settingsTimeToTakeActionTitle": "Harekete Geçme Zamanı", + + "settingsSectionDisplay": "Ekran", + "settingsThemeBrightness": "Tema", + "settingsThemeColorHighlights": "Renk vurguları", + "settingsThemeEnableDynamicColor": "Dinamik renk", + "settingsDisplayRefreshRateModeTile": "Görüntü yenileme hızı", + "settingsDisplayRefreshRateModeTitle": "Yenileme Hızı", + + "settingsSectionLanguage": "Dil ve Biçimler", + "settingsLanguage": "Dil", + "settingsCoordinateFormatTile": "Koordinat formatı", + "settingsCoordinateFormatTitle": "Koordinat Formatı", + "settingsUnitSystemTile": "Birimler", + "settingsUnitSystemTitle": "Birimler", + + "statsPageTitle": "İstatistikler", + "statsWithGps": "{count, plural, =1{1 konuma sahip öğe} other{{count} konuma sahip öğe}}", + "statsTopCountries": "Başlıca Ülkeler", + "statsTopPlaces": "Başlıca Yerler", + "statsTopTags": "Başlıca Etiketler", + + "viewerOpenPanoramaButtonLabel": "PANORAMAYI AÇ", + "viewerErrorUnknown": "Tüh!", + "viewerErrorDoesNotExist": "Dosya artık mevcut değil.", + + "viewerInfoPageTitle": "Bilgi", + "viewerInfoBackToViewerTooltip": "Görüntüleyiciye geri dön", + + "viewerInfoUnknown": "bilinmeyen", + "viewerInfoLabelTitle": "Başlık", + "viewerInfoLabelDate": "Tarih", + "viewerInfoLabelResolution": "Çözünürlük", + "viewerInfoLabelSize": "Boyut", + "viewerInfoLabelUri": "URI", + "viewerInfoLabelPath": "Yol", + "viewerInfoLabelDuration": "Süre", + "viewerInfoLabelOwner": "Sahibi", + "viewerInfoLabelCoordinates": "Koordinatlar", + "viewerInfoLabelAddress": "Adres", + + "mapStyleTitle": "Harita Şekli", + "mapStyleTooltip": "Harita şeklini seç", + "mapZoomInTooltip": "Yakınlaştır", + "mapZoomOutTooltip": "Uzaklaştır", + "mapPointNorthUpTooltip": "Kuzeyi göster", + "mapAttributionOsmHot": "Harita verileri © [OpenStreetMap](https://www.openstreetmap.org/copyright) katkıda bulunanlar - Kutucuklar [HOT](https://www.hotosm.org/) tarafından hazırlanmıştır - [OSM France](https://openstreetmap.fr/) tarafından barındırılmaktadır", + "mapAttributionStamen": "Harita verileri © [OpenStreetMap](https://www.openstreetmap.org/copyright) katkıda bulunanlar - Döşemeler [Stamen Design](http://stamen.com), [CC BY 3.0](http://creativecommons.org/licenses/by/3.0)", + "openMapPageTooltip": "Harita sayfasında görüntüle", + "mapEmptyRegion": "Bu bölgede resim yok", + + "viewerInfoOpenEmbeddedFailureFeedback": "Gömülü veriler ayıklanamadı", + "viewerInfoOpenLinkText": "Aç", + "viewerInfoViewXmlLinkText": "XML'i Görüntüle", + + "viewerInfoSearchFieldLabel": "Meta verileri ara", + "viewerInfoSearchEmpty": "Eşleşen anahtar yok", + "viewerInfoSearchSuggestionDate": "Tarih ve saat", + "viewerInfoSearchSuggestionDescription": "Açıklama", + "viewerInfoSearchSuggestionDimensions": "Boyutlar", + "viewerInfoSearchSuggestionResolution": "Çözünürlük", + "viewerInfoSearchSuggestionRights": "Haklar", + + "tagEditorPageTitle": "Etiketleri Düzenle", + "tagEditorPageNewTagFieldLabel": "Yeni etiket", + "tagEditorPageAddTagTooltip": "Etiket ekle", + "tagEditorSectionRecent": "Yakın zamanda", + + "panoramaEnableSensorControl": "Sensör kontrolünü etkinleştir", + "panoramaDisableSensorControl": "Sensör kontrolünü devre dışı bırak", + + "sourceViewerPageTitle": "Kaynak", + + "filePickerShowHiddenFiles": "Gizli dosyaları göster", + "filePickerDoNotShowHiddenFiles": "Gizli dosyaları gösterme", + "filePickerOpenFrom": "Şuradan aç", + "filePickerNoItems": "Öğe yok", + "filePickerUseThisFolder": "Bu klasörü kullan" +} diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 68503af3f..1cadd97fd 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -50,6 +50,7 @@ "entryActionDelete": "删除", "entryActionConvert": "转换", "entryActionExport": "导出", + "entryActionInfo": "信息", "entryActionRename": "重命名", "entryActionRestore": "恢复", "entryActionRotateCCW": "逆时针旋转", @@ -80,6 +81,9 @@ "videoActionSetSpeed": "播放速度", "videoActionSettings": "设置", + "slideshowActionResume": "继续", + "slideshowActionShowInCollection": "在媒体集中显示", + "entryInfoActionEditDate": "编辑日期和时间", "entryInfoActionEditLocation": "编辑位置", "entryInfoActionEditRating": "修改评分", @@ -144,10 +148,23 @@ "displayRefreshRatePreferHighest": "最高刷新率", "displayRefreshRatePreferLowest": "最低刷新率", + "slideshowVideoPlaybackSkip": "跳过", + "slideshowVideoPlaybackMuted": "静音播放", + "slideshowVideoPlaybackWithSound": "带音播放", + "themeBrightnessLight": "浅色", "themeBrightnessDark": "深色", "themeBrightnessBlack": "黑色", + "viewerTransitionSlide": "滑动", + "viewerTransitionParallax": "视差滚动", + "viewerTransitionFade": "淡入淡出", + "viewerTransitionZoomIn": "放大", + + "wallpaperTargetHome": "主屏幕", + "wallpaperTargetLock": "锁屏界面", + "wallpaperTargetHomeLock": "主屏幕 + 锁屏界面", + "albumTierNew": "新的", "albumTierPinned": "钉选", "albumTierSpecial": "普通", @@ -262,6 +279,7 @@ "menuActionSelectAll": "全选", "menuActionSelectNone": "全不选", "menuActionMap": "地图", + "menuActionSlideshow": "幻灯片", "menuActionStats": "统计", "viewDialogTabSort": "排序", @@ -349,6 +367,7 @@ "collectionEmptyFavourites": "无收藏项", "collectionEmptyVideos": "无视频", "collectionEmptyImages": "无图像", + "collectionEmptyGrantAccessButtonLabel": "授予访问权限", "collectionSelectSectionTooltip": "选择部分", "collectionDeselectSectionTooltip": "取消选择部分", @@ -479,6 +498,17 @@ "settingsViewerShowOverlayThumbnails": "显示缩略图", "settingsViewerEnableOverlayBlurEffect": "模糊特效", + "settingsViewerSlideshowTile": "幻灯片", + "settingsViewerSlideshowTitle": "幻灯片", + "settingsSlideshowRepeat": "重复", + "settingsSlideshowShuffle": "随机播放", + "settingsSlideshowTransitionTile": "过渡动画", + "settingsSlideshowTransitionTitle": "过渡动画", + "settingsSlideshowIntervalTile": "时间间隔", + "settingsSlideshowIntervalTitle": "时间间隔", + "settingsSlideshowVideoPlaybackTile": "视频回放", + "settingsSlideshowVideoPlaybackTitle": "视频回放", + "settingsVideoPageTitle": "视频设置", "settingsSectionVideo": "视频", "settingsVideoShowVideos": "显示视频", @@ -543,6 +573,7 @@ "settingsSectionDisplay": "显示", "settingsThemeBrightness": "主题", "settingsThemeColorHighlights": "色彩强调", + "settingsThemeEnableDynamicColor": "动态色彩", "settingsDisplayRefreshRateModeTile": "显示刷新率", "settingsDisplayRefreshRateModeTitle": "刷新率", @@ -560,6 +591,7 @@ "statsTopTags": "热门标签", "viewerOpenPanoramaButtonLabel": "打开全景", + "viewerSetWallpaperButtonLabel": "设置壁纸", "viewerErrorUnknown": "糟糕!", "viewerErrorDoesNotExist": "该文件不存在", diff --git a/lib/model/actions/chip_set_actions.dart b/lib/model/actions/chip_set_actions.dart index f78daabeb..43fb11ecf 100644 --- a/lib/model/actions/chip_set_actions.dart +++ b/lib/model/actions/chip_set_actions.dart @@ -13,6 +13,7 @@ enum ChipSetAction { createAlbum, // browsing or selecting map, + slideshow, stats, // selecting (single/multiple filters) delete, @@ -36,6 +37,7 @@ class ChipSetActions { ChipSetAction.search, ChipSetAction.createAlbum, ChipSetAction.map, + ChipSetAction.slideshow, ChipSetAction.stats, ]; @@ -47,6 +49,7 @@ class ChipSetActions { ChipSetAction.rename, ChipSetAction.hide, ChipSetAction.map, + ChipSetAction.slideshow, ChipSetAction.stats, ]; } @@ -71,6 +74,8 @@ extension ExtraChipSetAction on ChipSetAction { // browsing or selecting case ChipSetAction.map: return context.l10n.menuActionMap; + case ChipSetAction.slideshow: + return context.l10n.menuActionSlideshow; case ChipSetAction.stats: return context.l10n.menuActionStats; // selecting (single/multiple filters) @@ -111,6 +116,8 @@ extension ExtraChipSetAction on ChipSetAction { // browsing or selecting case ChipSetAction.map: return AIcons.map; + case ChipSetAction.slideshow: + return AIcons.slideshow; case ChipSetAction.stats: return AIcons.stats; // selecting (single/multiple filters) diff --git a/lib/model/actions/entry_actions.dart b/lib/model/actions/entry_actions.dart index d43f54cf3..3a8f1fff5 100644 --- a/lib/model/actions/entry_actions.dart +++ b/lib/model/actions/entry_actions.dart @@ -4,6 +4,7 @@ import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:flutter/widgets.dart'; enum EntryAction { + info, addShortcut, copyToClipboard, delete, @@ -43,6 +44,7 @@ enum EntryAction { class EntryActions { static const topLevel = [ + EntryAction.info, EntryAction.share, EntryAction.edit, EntryAction.rename, @@ -102,6 +104,8 @@ class EntryActions { extension ExtraEntryAction on EntryAction { String getText(BuildContext context) { switch (this) { + case EntryAction.info: + return context.l10n.entryActionInfo; case EntryAction.addShortcut: return context.l10n.collectionActionAddShortcut; case EntryAction.copyToClipboard: @@ -188,6 +192,8 @@ extension ExtraEntryAction on EntryAction { IconData getIconData() { switch (this) { + case EntryAction.info: + return AIcons.info; case EntryAction.addShortcut: return AIcons.addShortcut; case EntryAction.copyToClipboard: diff --git a/lib/model/actions/entry_set_actions.dart b/lib/model/actions/entry_set_actions.dart index 1a9ad51b5..997f819ec 100644 --- a/lib/model/actions/entry_set_actions.dart +++ b/lib/model/actions/entry_set_actions.dart @@ -15,6 +15,7 @@ enum EntrySetAction { emptyBin, // browsing or selecting map, + slideshow, stats, rescan, // selecting @@ -48,6 +49,7 @@ class EntrySetActions { EntrySetAction.toggleTitleSearch, EntrySetAction.addShortcut, EntrySetAction.map, + EntrySetAction.slideshow, EntrySetAction.stats, EntrySetAction.rescan, EntrySetAction.emptyBin, @@ -59,6 +61,7 @@ class EntrySetActions { EntrySetAction.toggleTitleSearch, EntrySetAction.addShortcut, EntrySetAction.map, + EntrySetAction.slideshow, EntrySetAction.stats, EntrySetAction.rescan, ]; @@ -72,6 +75,7 @@ class EntrySetActions { EntrySetAction.rename, EntrySetAction.toggleFavourite, EntrySetAction.map, + EntrySetAction.slideshow, EntrySetAction.stats, EntrySetAction.rescan, // editing actions are in their subsection @@ -86,6 +90,7 @@ class EntrySetActions { EntrySetAction.rename, EntrySetAction.toggleFavourite, EntrySetAction.map, + EntrySetAction.slideshow, EntrySetAction.stats, EntrySetAction.rescan, // editing actions are in their subsection @@ -125,6 +130,8 @@ extension ExtraEntrySetAction on EntrySetAction { // browsing or selecting case EntrySetAction.map: return context.l10n.menuActionMap; + case EntrySetAction.slideshow: + return context.l10n.menuActionSlideshow; case EntrySetAction.stats: return context.l10n.menuActionStats; case EntrySetAction.rescan: @@ -190,6 +197,8 @@ extension ExtraEntrySetAction on EntrySetAction { // browsing or selecting case EntrySetAction.map: return AIcons.map; + case EntrySetAction.slideshow: + return AIcons.slideshow; case EntrySetAction.stats: return AIcons.stats; case EntrySetAction.rescan: diff --git a/lib/model/actions/slideshow_actions.dart b/lib/model/actions/slideshow_actions.dart new file mode 100644 index 000000000..6e0b51fd8 --- /dev/null +++ b/lib/model/actions/slideshow_actions.dart @@ -0,0 +1,30 @@ +import 'package:aves/theme/icons.dart'; +import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:flutter/material.dart'; + +enum SlideshowAction { + resume, + showInCollection, +} + +extension ExtraSlideshowAction on SlideshowAction { + String getText(BuildContext context) { + switch (this) { + case SlideshowAction.resume: + return context.l10n.slideshowActionResume; + case SlideshowAction.showInCollection: + return context.l10n.slideshowActionShowInCollection; + } + } + + Widget getIcon() => Icon(_getIconData()); + + IconData _getIconData() { + switch (this) { + case SlideshowAction.resume: + return AIcons.play; + case SlideshowAction.showInCollection: + return AIcons.allCollection; + } + } +} diff --git a/lib/model/device.dart b/lib/model/device.dart index ab288f972..c94b0adf2 100644 --- a/lib/model/device.dart +++ b/lib/model/device.dart @@ -5,8 +5,8 @@ final Device device = Device._private(); class Device { late final String _userAgent; - late final bool _canGrantDirectoryAccess, _canPinShortcut, _canPrint, _canRenderFlagEmojis; - late final bool _showPinShortcutFeedback, _supportEdgeToEdgeUIMode; + late final bool _canGrantDirectoryAccess, _canPinShortcut, _canPrint, _canRenderFlagEmojis, _canSetLockScreenWallpaper; + late final bool _isDynamicColorAvailable, _showPinShortcutFeedback, _supportEdgeToEdgeUIMode; String get userAgent => _userAgent; @@ -18,6 +18,10 @@ class Device { bool get canRenderFlagEmojis => _canRenderFlagEmojis; + bool get canSetLockScreenWallpaper => _canSetLockScreenWallpaper; + + bool get isDynamicColorAvailable => _isDynamicColorAvailable; + bool get showPinShortcutFeedback => _showPinShortcutFeedback; bool get supportEdgeToEdgeUIMode => _supportEdgeToEdgeUIMode; @@ -33,6 +37,8 @@ class Device { _canPinShortcut = capabilities['canPinShortcut'] ?? false; _canPrint = capabilities['canPrint'] ?? false; _canRenderFlagEmojis = capabilities['canRenderFlagEmojis'] ?? false; + _canSetLockScreenWallpaper = capabilities['canSetLockScreenWallpaper'] ?? false; + _isDynamicColorAvailable = capabilities['isDynamicColorAvailable'] ?? false; _showPinShortcutFeedback = capabilities['showPinShortcutFeedback'] ?? false; _supportEdgeToEdgeUIMode = capabilities['supportEdgeToEdgeUIMode'] ?? false; } diff --git a/lib/model/entry.dart b/lib/model/entry.dart index 7e9d89805..8071699f1 100644 --- a/lib/model/entry.dart +++ b/lib/model/entry.dart @@ -4,6 +4,7 @@ import 'dart:ui'; import 'package:aves/geo/countries.dart'; import 'package:aves/model/entry_cache.dart'; +import 'package:aves/model/entry_dirs.dart'; import 'package:aves/model/favourites.dart'; import 'package:aves/model/geotiff.dart'; import 'package:aves/model/metadata/address.dart'; @@ -31,7 +32,8 @@ class AvesEntry { // `sizeBytes`, `dateModifiedSecs` can be missing in viewer mode int id; String uri; - String? _path, _directory, _filename, _extension, _sourceTitle; + String? _path, _filename, _extension, _sourceTitle; + EntryDir? _directory; int? pageId, contentId; final String sourceMimeType; int width, height, sourceRotationDegrees; @@ -175,8 +177,8 @@ class AvesEntry { // directory path, without the trailing separator String? get directory { - _directory ??= path != null ? pContext.dirname(path!) : null; - return _directory; + _directory ??= entryDirRepo.getOrCreate(path != null ? pContext.dirname(path!) : null); + return _directory!.resolved; } String? get filenameWithoutExtension { diff --git a/lib/model/entry_dirs.dart b/lib/model/entry_dirs.dart new file mode 100644 index 000000000..d18ff2dd8 --- /dev/null +++ b/lib/model/entry_dirs.dart @@ -0,0 +1,68 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:aves/services/common/services.dart'; +import 'package:aves/utils/android_file_utils.dart'; +import 'package:collection/collection.dart'; + +final entryDirRepo = EntryDirRepo._private(); + +class EntryDirRepo { + EntryDirRepo._private(); + + // mapping between the raw entry directory path to a resolvable directory + final Map _dirs = {}; + final StreamController _ambiguousDirStreamController = StreamController.broadcast(); + + Stream get ambiguousDirStream => _ambiguousDirStreamController.stream; + + // get a resolvable directory for a raw entry directory path + EntryDir getOrCreate(String? asIs) { + var entryDir = _dirs[asIs]; + if (entryDir != null) return entryDir; + + final asIsLower = asIs?.toLowerCase(); + entryDir = _dirs.values.firstWhereOrNull((dir) => dir.asIsLower == asIsLower); + if (entryDir != null && !entryDir.ambiguous) { + entryDir.ambiguous = true; + _ambiguousDirStreamController.add(entryDir); + } + + return _dirs.putIfAbsent(asIs, () => entryDir ?? EntryDir(asIs)); + } +} + +// Some directories are ambiguous because they use different cases, +// but the OS merge and present them as one directory. +// This class resolves ambiguous directories to get the directory path +// with the right case, as presented by the OS. +class EntryDir { + final String? asIs, asIsLower; + bool ambiguous = false; + String? _resolved; + + EntryDir(this.asIs) : asIsLower = asIs?.toLowerCase(); + + String? get resolved { + if (!ambiguous) return asIs; + if (asIs == null) return null; + + _resolved ??= _resolve(); + return _resolved; + } + + String? _resolve() { + final vrl = VolumeRelativeDirectory.fromPath(asIs!); + if (vrl == null || vrl.relativeDir.isEmpty) return asIs; + + var resolved = vrl.volumePath; + final parts = pContext.split(vrl.relativeDir); + for (final part in parts) { + final partLower = part.toLowerCase(); + final childrenDirs = Directory(resolved).listSync().where((v) => v.absolute is Directory).toSet(); + final found = childrenDirs.firstWhereOrNull((v) => pContext.basename(v.path).toLowerCase() == partLower); + resolved = found?.path ?? '$resolved${pContext.separator}$part'; + } + return resolved; + } +} diff --git a/lib/model/entry_images.dart b/lib/model/entry_images.dart index 2edd131ce..b0f44359f 100644 --- a/lib/model/entry_images.dart +++ b/lib/model/entry_images.dart @@ -5,6 +5,7 @@ import 'package:aves/image_providers/thumbnail_provider.dart'; import 'package:aves/image_providers/uri_image_provider.dart'; import 'package:aves/model/entry.dart'; import 'package:aves/model/entry_cache.dart'; +import 'package:aves/utils/math_utils.dart'; import 'package:collection/collection.dart'; import 'package:flutter/widgets.dart'; @@ -63,4 +64,15 @@ extension ExtraAvesEntryImages on AvesEntry { final sizedThumbnailKey = EntryCache.thumbnailRequestExtents.map(_getThumbnailProviderKey).firstWhereOrNull(_isReady); return sizedThumbnailKey != null ? ThumbnailProvider(sizedThumbnailKey) : getThumbnail(); } + + // magic number used to derive sample size from scale + static const scaleFactor = 2.0; + + static int sampleSizeForScale(double scale) { + var sample = 0; + if (0 < scale && scale < 1) { + sample = highestPowerOf2((1 / scale) / scaleFactor); + } + return max(1, sample); + } } diff --git a/lib/model/filters/query.dart b/lib/model/filters/query.dart index b16ba2484..8d73336fd 100644 --- a/lib/model/filters/query.dart +++ b/lib/model/filters/query.dart @@ -2,6 +2,7 @@ import 'package:aves/model/entry.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/theme/colors.dart'; import 'package:aves/theme/icons.dart'; +import 'package:aves/utils/file_utils.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; @@ -19,11 +20,25 @@ class QueryFilter extends CollectionFilter { @override List get props => [query, live]; + static final _fieldPattern = RegExp(r'(.+)([=<>])(.+)'); + static final _fileSizePattern = RegExp(r'(\d+)([KMG])?'); + static const keyContentId = 'ID'; + static const keyContentYear = 'YEAR'; + static const keyContentMonth = 'MONTH'; + static const keyContentDay = 'DAY'; + static const keyContentWidth = 'WIDTH'; + static const keyContentHeight = 'HEIGHT'; + static const keyContentSize = 'SIZE'; + static const opEqual = '='; + static const opLower = '<'; + static const opGreater = '>'; + QueryFilter(this.query, {this.colorful = true, this.live = false}) { var upQuery = query.toUpperCase(); - if (upQuery.startsWith('ID:')) { - final id = int.tryParse(upQuery.substring(3)); - _test = (entry) => entry.id == id; + + final test = fieldTest(upQuery); + if (test != null) { + _test = test; return; } @@ -82,4 +97,114 @@ class QueryFilter extends CollectionFilter { @override String get key => '$type-$query'; + + EntryFilter? fieldTest(String upQuery) { + var match = _fieldPattern.firstMatch(upQuery); + if (match == null) return null; + + final key = match.group(1)?.trim(); + final op = match.group(2)?.trim(); + var valueString = match.group(3)?.trim(); + if (key == null || op == null || valueString == null) return null; + + final valueInt = int.tryParse(valueString); + + switch (key) { + case keyContentId: + if (valueInt == null) return null; + if (op == opEqual) { + return (entry) => entry.contentId == valueInt; + } + break; + case keyContentYear: + if (valueInt == null) return null; + switch (op) { + case opEqual: + return (entry) => (entry.bestDate?.year ?? 0) == valueInt; + case opLower: + return (entry) => (entry.bestDate?.year ?? 0) < valueInt; + case opGreater: + return (entry) => (entry.bestDate?.year ?? 0) > valueInt; + } + break; + case keyContentMonth: + if (valueInt == null) return null; + switch (op) { + case opEqual: + return (entry) => (entry.bestDate?.month ?? 0) == valueInt; + case opLower: + return (entry) => (entry.bestDate?.month ?? 0) < valueInt; + case opGreater: + return (entry) => (entry.bestDate?.month ?? 0) > valueInt; + } + break; + case keyContentDay: + if (valueInt == null) return null; + switch (op) { + case opEqual: + return (entry) => (entry.bestDate?.day ?? 0) == valueInt; + case opLower: + return (entry) => (entry.bestDate?.day ?? 0) < valueInt; + case opGreater: + return (entry) => (entry.bestDate?.day ?? 0) > valueInt; + } + break; + case keyContentWidth: + if (valueInt == null) return null; + switch (op) { + case opEqual: + return (entry) => entry.displaySize.width == valueInt; + case opLower: + return (entry) => entry.displaySize.width < valueInt; + case opGreater: + return (entry) => entry.displaySize.width > valueInt; + } + break; + case keyContentHeight: + if (valueInt == null) return null; + switch (op) { + case opEqual: + return (entry) => entry.displaySize.height == valueInt; + case opLower: + return (entry) => entry.displaySize.height < valueInt; + case opGreater: + return (entry) => entry.displaySize.height > valueInt; + } + break; + case keyContentSize: + match = _fileSizePattern.firstMatch(valueString); + if (match == null) return null; + + valueString = match.group(1)?.trim(); + if (valueString == null) return null; + final valueInt = int.tryParse(valueString); + if (valueInt == null) return null; + + var bytes = valueInt; + final multiplierString = match.group(2)?.trim(); + switch (multiplierString) { + case 'K': + bytes *= kilo; + break; + case 'M': + bytes *= mega; + break; + case 'G': + bytes *= giga; + break; + } + + switch (op) { + case opEqual: + return (entry) => (entry.sizeBytes ?? 0) == bytes; + case opLower: + return (entry) => (entry.sizeBytes ?? 0) < bytes; + case opGreater: + return (entry) => (entry.sizeBytes ?? 0) > bytes; + } + break; + } + + return null; + } } diff --git a/lib/model/settings/defaults.dart b/lib/model/settings/defaults.dart index ac2fdfc35..473e1fbb7 100644 --- a/lib/model/settings/defaults.dart +++ b/lib/model/settings/defaults.dart @@ -17,11 +17,15 @@ class SettingsDefaults { static const canUseAnalysisService = true; static const isInstalledAppAccessAllowed = false; static const isErrorReportingAllowed = false; + static const tileLayout = TileLayout.grid; + static const entryRenamingPattern = '<${DateNamingProcessor.key}, yyyyMMdd-HHmmss> <${NameNamingProcessor.key}>'; + + // display static const displayRefreshRateMode = DisplayRefreshRateMode.auto; static const themeBrightness = AvesThemeBrightness.system; static const themeColorMode = AvesThemeColorMode.polychrome; - static const tileLayout = TileLayout.grid; - static const entryRenamingPattern = '<${DateNamingProcessor.key}, yyyyMMdd-HHmmss> <${NameNamingProcessor.key}>'; + static const enableDynamicColor = false; + static const enableBlurEffect = true; // `enableBlurEffect` has a contextual default value // navigation static const mustBackTwiceToExit = true; @@ -79,7 +83,6 @@ class SettingsDefaults { static const showOverlayInfo = true; static const showOverlayShootingDetails = false; static const showOverlayThumbnailPreview = false; - static const enableOverlayBlurEffect = true; // `enableOverlayBlurEffect` has a contextual default value static const viewerUseCutout = true; static const viewerMaxBrightness = false; static const enableMotionPhotoAutoPlay = false; @@ -122,6 +125,13 @@ class SettingsDefaults { // file picker static const filePickerShowHiddenFiles = false; + // slideshow + static const slideshowRepeat = false; + static const slideshowShuffle = false; + static const slideshowTransition = ViewerTransition.fade; + static const slideshowVideoPlayback = SlideshowVideoPlayback.playMuted; + static const slideshowInterval = SlideshowInterval.s5; + // platform settings static const isRotationLocked = false; static const areAnimationsRemoved = false; diff --git a/lib/model/settings/enums/enums.dart b/lib/model/settings/enums/enums.dart index 6ec4c8714..b16349ef2 100644 --- a/lib/model/settings/enums/enums.dart +++ b/lib/model/settings/enums/enums.dart @@ -2,24 +2,30 @@ enum AccessibilityAnimations { system, disabled, enabled } enum AccessibilityTimeout { system, appDefault, s3, s10, s30, s60, s120 } -enum AvesThemeColorMode { monochrome, polychrome } - enum AvesThemeBrightness { system, light, dark, black } +enum AvesThemeColorMode { monochrome, polychrome } + enum ConfirmationDialog { deleteForever, moveToBin, moveUndatedItems } enum CoordinateFormat { dms, decimal } +enum DisplayRefreshRateMode { auto, highest, lowest } + enum EntryBackground { black, white, checkered } enum HomePageSetting { collection, albums } enum KeepScreenOn { never, viewerOnly, always } -enum DisplayRefreshRateMode { auto, highest, lowest } +enum SlideshowInterval { s3, s5, s10, s30, s60 } + +enum SlideshowVideoPlayback { skip, playMuted, playWithSound } enum UnitSystem { metric, imperial } +enum VideoControls { play, playSeek, playOutside, none } + enum VideoLoopMode { never, shortOnly, always } -enum VideoControls { play, playSeek, playOutside, none } +enum ViewerTransition { slide, parallax, fade, zoomIn } diff --git a/lib/model/settings/enums/slideshow_interval.dart b/lib/model/settings/enums/slideshow_interval.dart new file mode 100644 index 000000000..ad9562d29 --- /dev/null +++ b/lib/model/settings/enums/slideshow_interval.dart @@ -0,0 +1,36 @@ +import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:flutter/widgets.dart'; + +import 'enums.dart'; + +extension ExtraSlideshowInterval on SlideshowInterval { + String getName(BuildContext context) { + switch (this) { + case SlideshowInterval.s3: + return context.l10n.timeSeconds(3); + case SlideshowInterval.s5: + return context.l10n.timeSeconds(5); + case SlideshowInterval.s10: + return context.l10n.timeSeconds(10); + case SlideshowInterval.s30: + return context.l10n.timeSeconds(30); + case SlideshowInterval.s60: + return context.l10n.timeMinutes(1); + } + } + + Duration getDuration() { + switch (this) { + case SlideshowInterval.s3: + return const Duration(seconds: 3); + case SlideshowInterval.s5: + return const Duration(seconds: 5); + case SlideshowInterval.s10: + return const Duration(seconds: 10); + case SlideshowInterval.s30: + return const Duration(seconds: 30); + case SlideshowInterval.s60: + return const Duration(minutes: 1); + } + } +} diff --git a/lib/model/settings/enums/slideshow_video_playback.dart b/lib/model/settings/enums/slideshow_video_playback.dart new file mode 100644 index 000000000..beda9a052 --- /dev/null +++ b/lib/model/settings/enums/slideshow_video_playback.dart @@ -0,0 +1,17 @@ +import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:flutter/widgets.dart'; + +import 'enums.dart'; + +extension ExtraSlideshowVideoPlayback on SlideshowVideoPlayback { + String getName(BuildContext context) { + switch (this) { + case SlideshowVideoPlayback.skip: + return context.l10n.slideshowVideoPlaybackSkip; + case SlideshowVideoPlayback.playMuted: + return context.l10n.slideshowVideoPlaybackMuted; + case SlideshowVideoPlayback.playWithSound: + return context.l10n.slideshowVideoPlaybackWithSound; + } + } +} diff --git a/lib/model/settings/enums/viewer_transition.dart b/lib/model/settings/enums/viewer_transition.dart new file mode 100644 index 000000000..504474851 --- /dev/null +++ b/lib/model/settings/enums/viewer_transition.dart @@ -0,0 +1,33 @@ +import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:aves/widgets/viewer/controller.dart'; +import 'package:flutter/widgets.dart'; + +import 'enums.dart'; + +extension ExtraViewerTransition on ViewerTransition { + String getName(BuildContext context) { + switch (this) { + case ViewerTransition.slide: + return context.l10n.viewerTransitionSlide; + case ViewerTransition.parallax: + return context.l10n.viewerTransitionParallax; + case ViewerTransition.fade: + return context.l10n.viewerTransitionFade; + case ViewerTransition.zoomIn: + return context.l10n.viewerTransitionZoomIn; + } + } + + TransitionBuilder builder(PageController pageController, int index) { + switch (this) { + case ViewerTransition.slide: + return PageTransitionEffects.slide(pageController, index, parallax: false); + case ViewerTransition.parallax: + return PageTransitionEffects.slide(pageController, index, parallax: true); + case ViewerTransition.fade: + return PageTransitionEffects.fade(pageController, index, zoomIn: false); + case ViewerTransition.zoomIn: + return PageTransitionEffects.fade(pageController, index, zoomIn: true); + } + } +} diff --git a/lib/model/settings/settings.dart b/lib/model/settings/settings.dart index 6c0617863..624c359b4 100644 --- a/lib/model/settings/settings.dart +++ b/lib/model/settings/settings.dart @@ -10,6 +10,7 @@ import 'package:aves/model/settings/enums/enums.dart'; import 'package:aves/model/settings/enums/map_style.dart'; import 'package:aves/model/source/enums.dart'; import 'package:aves/services/accessibility_service.dart'; +import 'package:aves/services/common/optional_event_channel.dart'; import 'package:aves/services/common/services.dart'; import 'package:aves_map/aves_map.dart'; import 'package:collection/collection.dart'; @@ -19,7 +20,7 @@ import 'package:flutter/services.dart'; final Settings settings = Settings._private(); class Settings extends ChangeNotifier { - final EventChannel _platformSettingsChangeChannel = const EventChannel('deckers.thibault/aves/settings_change'); + final EventChannel _platformSettingsChangeChannel = const OptionalEventChannel('deckers.thibault/aves/settings_change'); final StreamController _updateStreamController = StreamController.broadcast(); Stream get updateStream => _updateStreamController.stream; @@ -42,15 +43,19 @@ class Settings extends ChangeNotifier { static const isInstalledAppAccessAllowedKey = 'is_installed_app_access_allowed'; static const isErrorReportingAllowedKey = 'is_crashlytics_enabled'; static const localeKey = 'locale'; - static const displayRefreshRateModeKey = 'display_refresh_rate_mode'; - static const themeBrightnessKey = 'theme_brightness'; - static const themeColorModeKey = 'theme_color_mode'; static const catalogTimeZoneKey = 'catalog_time_zone'; static const tileExtentPrefixKey = 'tile_extent_'; static const tileLayoutPrefixKey = 'tile_layout_'; static const entryRenamingPatternKey = 'entry_renaming_pattern'; static const topEntryIdsKey = 'top_entry_ids'; + // display + static const displayRefreshRateModeKey = 'display_refresh_rate_mode'; + static const themeBrightnessKey = 'theme_brightness'; + static const themeColorModeKey = 'theme_color_mode'; + static const enableDynamicColorKey = 'dynamic_color'; + static const enableBlurEffectKey = 'enable_overlay_blur_effect'; + // navigation static const mustBackTwiceToExitKey = 'must_back_twice_to_exit'; static const keepScreenOnKey = 'keep_screen_on'; @@ -92,7 +97,6 @@ class Settings extends ChangeNotifier { static const showOverlayInfoKey = 'show_overlay_info'; static const showOverlayShootingDetailsKey = 'show_overlay_shooting_details'; static const showOverlayThumbnailPreviewKey = 'show_overlay_thumbnail_preview'; - static const enableOverlayBlurEffectKey = 'enable_overlay_blur_effect'; static const viewerUseCutoutKey = 'viewer_use_cutout'; static const viewerMaxBrightnessKey = 'viewer_max_brightness'; static const enableMotionPhotoAutoPlayKey = 'motion_photo_auto_play'; @@ -134,6 +138,13 @@ class Settings extends ChangeNotifier { // file picker static const filePickerShowHiddenFilesKey = 'file_picker_show_hidden_files'; + // slideshow + static const slideshowRepeatKey = 'slideshow_loop'; + static const slideshowShuffleKey = 'slideshow_shuffle'; + static const slideshowTransitionKey = 'slideshow_transition'; + static const slideshowVideoPlaybackKey = 'slideshow_video_playback'; + static const slideshowIntervalKey = 'slideshow_interval'; + // platform settings // cf Android `Settings.System.ACCELEROMETER_ROTATION` static const platformAccelerometerRotationKey = 'accelerometer_rotation'; @@ -161,7 +172,7 @@ class Settings extends ChangeNotifier { Future setContextualDefaults() async { // performance final performanceClass = await deviceService.getPerformanceClass(); - enableOverlayBlurEffect = performanceClass >= 29; + enableBlurEffect = performanceClass >= 29; // availability final defaultMapStyle = mobileServices.defaultMapStyle; @@ -187,8 +198,7 @@ class Settings extends ChangeNotifier { set canUseAnalysisService(bool newValue) => setAndNotify(canUseAnalysisServiceKey, newValue); - // TODO TLAD use `true` for transition (it's unset in v1.5.4), but replace by `SettingsDefaults.isInstalledAppAccessAllowed` in a later release - bool get isInstalledAppAccessAllowed => getBoolOrDefault(isInstalledAppAccessAllowedKey, true); + bool get isInstalledAppAccessAllowed => getBoolOrDefault(isInstalledAppAccessAllowedKey, SettingsDefaults.isInstalledAppAccessAllowed); set isInstalledAppAccessAllowed(bool newValue) => setAndNotify(isInstalledAppAccessAllowedKey, newValue); @@ -249,18 +259,6 @@ class Settings extends ChangeNotifier { return _appliedLocale!; } - DisplayRefreshRateMode get displayRefreshRateMode => getEnumOrDefault(displayRefreshRateModeKey, SettingsDefaults.displayRefreshRateMode, DisplayRefreshRateMode.values); - - set displayRefreshRateMode(DisplayRefreshRateMode newValue) => setAndNotify(displayRefreshRateModeKey, newValue.toString()); - - AvesThemeBrightness get themeBrightness => getEnumOrDefault(themeBrightnessKey, SettingsDefaults.themeBrightness, AvesThemeBrightness.values); - - set themeBrightness(AvesThemeBrightness newValue) => setAndNotify(themeBrightnessKey, newValue.toString()); - - AvesThemeColorMode get themeColorMode => getEnumOrDefault(themeColorModeKey, SettingsDefaults.themeColorMode, AvesThemeColorMode.values); - - set themeColorMode(AvesThemeColorMode newValue) => setAndNotify(themeColorModeKey, newValue.toString()); - String get catalogTimeZone => getString(catalogTimeZoneKey) ?? ''; set catalogTimeZone(String newValue) => setAndNotify(catalogTimeZoneKey, newValue); @@ -281,6 +279,28 @@ class Settings extends ChangeNotifier { set topEntryIds(List? newValue) => setAndNotify(topEntryIdsKey, newValue?.map((id) => id.toString()).whereNotNull().toList()); + // display + + DisplayRefreshRateMode get displayRefreshRateMode => getEnumOrDefault(displayRefreshRateModeKey, SettingsDefaults.displayRefreshRateMode, DisplayRefreshRateMode.values); + + set displayRefreshRateMode(DisplayRefreshRateMode newValue) => setAndNotify(displayRefreshRateModeKey, newValue.toString()); + + AvesThemeBrightness get themeBrightness => getEnumOrDefault(themeBrightnessKey, SettingsDefaults.themeBrightness, AvesThemeBrightness.values); + + set themeBrightness(AvesThemeBrightness newValue) => setAndNotify(themeBrightnessKey, newValue.toString()); + + AvesThemeColorMode get themeColorMode => getEnumOrDefault(themeColorModeKey, SettingsDefaults.themeColorMode, AvesThemeColorMode.values); + + set themeColorMode(AvesThemeColorMode newValue) => setAndNotify(themeColorModeKey, newValue.toString()); + + bool get enableDynamicColor => getBoolOrDefault(enableDynamicColorKey, SettingsDefaults.enableDynamicColor); + + set enableDynamicColor(bool newValue) => setAndNotify(enableDynamicColorKey, newValue); + + bool get enableBlurEffect => getBoolOrDefault(enableBlurEffectKey, SettingsDefaults.enableBlurEffect); + + set enableBlurEffect(bool newValue) => setAndNotify(enableBlurEffectKey, newValue); + // navigation bool get mustBackTwiceToExit => getBoolOrDefault(mustBackTwiceToExitKey, SettingsDefaults.mustBackTwiceToExit); @@ -441,10 +461,6 @@ class Settings extends ChangeNotifier { set showOverlayThumbnailPreview(bool newValue) => setAndNotify(showOverlayThumbnailPreviewKey, newValue); - bool get enableOverlayBlurEffect => getBoolOrDefault(enableOverlayBlurEffectKey, SettingsDefaults.enableOverlayBlurEffect); - - set enableOverlayBlurEffect(bool newValue) => setAndNotify(enableOverlayBlurEffectKey, newValue); - bool get viewerUseCutout => getBoolOrDefault(viewerUseCutoutKey, SettingsDefaults.viewerUseCutout); set viewerUseCutout(bool newValue) => setAndNotify(viewerUseCutoutKey, newValue); @@ -567,6 +583,28 @@ class Settings extends ChangeNotifier { set filePickerShowHiddenFiles(bool newValue) => setAndNotify(filePickerShowHiddenFilesKey, newValue); + // slideshow + + bool get slideshowRepeat => getBoolOrDefault(slideshowRepeatKey, SettingsDefaults.slideshowRepeat); + + set slideshowRepeat(bool newValue) => setAndNotify(slideshowRepeatKey, newValue); + + bool get slideshowShuffle => getBoolOrDefault(slideshowShuffleKey, SettingsDefaults.slideshowShuffle); + + set slideshowShuffle(bool newValue) => setAndNotify(slideshowShuffleKey, newValue); + + ViewerTransition get slideshowTransition => getEnumOrDefault(slideshowTransitionKey, SettingsDefaults.slideshowTransition, ViewerTransition.values); + + set slideshowTransition(ViewerTransition newValue) => setAndNotify(slideshowTransitionKey, newValue.toString()); + + SlideshowVideoPlayback get slideshowVideoPlayback => getEnumOrDefault(slideshowVideoPlaybackKey, SettingsDefaults.slideshowVideoPlayback, SlideshowVideoPlayback.values); + + set slideshowVideoPlayback(SlideshowVideoPlayback newValue) => setAndNotify(slideshowVideoPlaybackKey, newValue.toString()); + + SlideshowInterval get slideshowInterval => getEnumOrDefault(slideshowIntervalKey, SettingsDefaults.slideshowInterval, SlideshowInterval.values); + + set slideshowInterval(SlideshowInterval newValue) => setAndNotify(slideshowIntervalKey, newValue.toString()); + // convenience methods int? getInt(String key) => settingsStore.getInt(key); @@ -695,6 +733,8 @@ class Settings extends ChangeNotifier { break; case isInstalledAppAccessAllowedKey: case isErrorReportingAllowedKey: + case enableDynamicColorKey: + case enableBlurEffectKey: case showBottomNavigationBarKey: case mustBackTwiceToExitKey: case confirmDeleteForeverKey: @@ -713,7 +753,6 @@ class Settings extends ChangeNotifier { case showOverlayInfoKey: case showOverlayShootingDetailsKey: case showOverlayThumbnailPreviewKey: - case enableOverlayBlurEffectKey: case viewerUseCutoutKey: case viewerMaxBrightnessKey: case enableMotionPhotoAutoPlayKey: @@ -724,6 +763,8 @@ class Settings extends ChangeNotifier { case subtitleShowOutlineKey: case saveSearchHistoryKey: case filePickerShowHiddenFilesKey: + case slideshowRepeatKey: + case slideshowShuffleKey: if (newValue is bool) { settingsStore.setBool(key, newValue); } else { @@ -751,6 +792,9 @@ class Settings extends ChangeNotifier { case unitSystemKey: case accessibilityAnimationsKey: case timeToTakeActionKey: + case slideshowTransitionKey: + case slideshowVideoPlaybackKey: + case slideshowIntervalKey: if (newValue is String) { settingsStore.setString(key, newValue); } else { diff --git a/lib/model/source/collection_lens.dart b/lib/model/source/collection_lens.dart index 95eb0a33e..d0598213e 100644 --- a/lib/model/source/collection_lens.dart +++ b/lib/model/source/collection_lens.dart @@ -32,7 +32,7 @@ class CollectionLens with ChangeNotifier { final AChangeNotifier filterChangeNotifier = AChangeNotifier(), sortSectionChangeNotifier = AChangeNotifier(); final List _subscriptions = []; int? id; - bool listenToSource, groupBursts; + bool listenToSource, groupBursts, fixedSort; List? fixedSelection; List _filteredSortedEntries = []; @@ -45,6 +45,7 @@ class CollectionLens with ChangeNotifier { this.id, this.listenToSource = true, this.groupBursts = true, + this.fixedSort = false, this.fixedSelection, }) : filters = (filters ?? {}).whereNotNull().toSet(), sectionFactor = settings.collectionSectionFactor, @@ -203,6 +204,8 @@ class CollectionLens with ChangeNotifier { } void _applySort() { + if (fixedSort) return; + switch (sortFactor) { case EntrySortFactor.date: _filteredSortedEntries.sort(AvesEntry.compareByDate); @@ -220,37 +223,43 @@ class CollectionLens with ChangeNotifier { } void _applySection() { - switch (sortFactor) { - case EntrySortFactor.date: - switch (sectionFactor) { - case EntryGroupFactor.album: - sections = groupBy(_filteredSortedEntries, (entry) => EntryAlbumSectionKey(entry.directory)); - break; - case EntryGroupFactor.month: - sections = groupBy(_filteredSortedEntries, (entry) => EntryDateSectionKey(entry.monthTaken)); - break; - case EntryGroupFactor.day: - sections = groupBy(_filteredSortedEntries, (entry) => EntryDateSectionKey(entry.dayTaken)); - break; - case EntryGroupFactor.none: - sections = Map.fromEntries([ - MapEntry(const SectionKey(), _filteredSortedEntries), - ]); - break; - } - break; - case EntrySortFactor.name: - final byAlbum = groupBy(_filteredSortedEntries, (entry) => EntryAlbumSectionKey(entry.directory)); - sections = SplayTreeMap>.of(byAlbum, (a, b) => source.compareAlbumsByName(a.directory!, b.directory!)); - break; - case EntrySortFactor.rating: - sections = groupBy(_filteredSortedEntries, (entry) => EntryRatingSectionKey(entry.rating)); - break; - case EntrySortFactor.size: - sections = Map.fromEntries([ - MapEntry(const SectionKey(), _filteredSortedEntries), - ]); - break; + if (fixedSort) { + sections = Map.fromEntries([ + MapEntry(const SectionKey(), _filteredSortedEntries), + ]); + } else { + switch (sortFactor) { + case EntrySortFactor.date: + switch (sectionFactor) { + case EntryGroupFactor.album: + sections = groupBy(_filteredSortedEntries, (entry) => EntryAlbumSectionKey(entry.directory)); + break; + case EntryGroupFactor.month: + sections = groupBy(_filteredSortedEntries, (entry) => EntryDateSectionKey(entry.monthTaken)); + break; + case EntryGroupFactor.day: + sections = groupBy(_filteredSortedEntries, (entry) => EntryDateSectionKey(entry.dayTaken)); + break; + case EntryGroupFactor.none: + sections = Map.fromEntries([ + MapEntry(const SectionKey(), _filteredSortedEntries), + ]); + break; + } + break; + case EntrySortFactor.name: + final byAlbum = groupBy(_filteredSortedEntries, (entry) => EntryAlbumSectionKey(entry.directory)); + sections = SplayTreeMap>.of(byAlbum, (a, b) => source.compareAlbumsByName(a.directory!, b.directory!)); + break; + case EntrySortFactor.rating: + sections = groupBy(_filteredSortedEntries, (entry) => EntryRatingSectionKey(entry.rating)); + break; + case EntrySortFactor.size: + sections = Map.fromEntries([ + MapEntry(const SectionKey(), _filteredSortedEntries), + ]); + break; + } } sections = Map.unmodifiable(sections); _sortedEntries = null; diff --git a/lib/model/source/collection_source.dart b/lib/model/source/collection_source.dart index 2ce5a6f79..02b6c0b9f 100644 --- a/lib/model/source/collection_source.dart +++ b/lib/model/source/collection_source.dart @@ -416,6 +416,7 @@ abstract class CollectionSource with SourceBase, AlbumMixin, LocationMixin, TagM } } if (startAnalysisService) { + // TODO TLAD [tiramisu] explain foreground service and request POST_NOTIFICATIONS permission await AnalysisService.startService( force: force, entryIds: entries?.map((entry) => entry.id).toList(), diff --git a/lib/model/wallpaper_target.dart b/lib/model/wallpaper_target.dart new file mode 100644 index 000000000..882bfb9b8 --- /dev/null +++ b/lib/model/wallpaper_target.dart @@ -0,0 +1,17 @@ +import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:flutter/material.dart'; + +enum WallpaperTarget { home, lock, homeLock } + +extension ExtraWallpaperTarget on WallpaperTarget { + String getName(BuildContext context) { + switch (this) { + case WallpaperTarget.home: + return context.l10n.wallpaperTargetHome; + case WallpaperTarget.lock: + return context.l10n.wallpaperTargetLock; + case WallpaperTarget.homeLock: + return context.l10n.wallpaperTargetHomeLock; + } + } +} diff --git a/lib/ref/mime_types.dart b/lib/ref/mime_types.dart index cec770cae..63ac92ee7 100644 --- a/lib/ref/mime_types.dart +++ b/lib/ref/mime_types.dart @@ -44,6 +44,8 @@ class MimeTypes { static const anyVideo = 'video/*'; + static const v3gpp = 'video/3gpp'; + static const asf = 'video/x-ms-asf'; static const avi = 'video/avi'; static const aviVnd = 'video/vnd.avi'; static const flv = 'video/flv'; @@ -56,6 +58,7 @@ class MimeTypes { static const mpeg = 'video/mpeg'; static const ogv = 'video/ogg'; static const webm = 'video/webm'; + static const wmv = 'video/x-ms-wmv'; static const json = 'application/json'; static const plainText = 'text/plain'; @@ -76,7 +79,7 @@ class MimeTypes { static const Set _knownOpaqueImages = {jpeg}; - static const Set _knownVideos = {avi, aviVnd, flv, flvX, mkv, mov, mp2t, mp2ts, mp4, mpeg, ogv, webm}; + static const Set _knownVideos = {v3gpp, asf, avi, aviVnd, flv, flvX, mkv, mov, mp2t, mp2ts, mp4, mpeg, ogv, webm, wmv}; static final Set knownMediaTypes = { anyImage, diff --git a/lib/services/common/optional_event_channel.dart b/lib/services/common/optional_event_channel.dart new file mode 100644 index 000000000..7ce14fe5a --- /dev/null +++ b/lib/services/common/optional_event_channel.dart @@ -0,0 +1,53 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; + +// adapted from Flutter `EventChannel` in `/services/platform_channel.dart` +// to use an `OptionalMethodChannel` when subscribing to events +class OptionalEventChannel extends EventChannel { + const OptionalEventChannel(super.name, [super.codec = const StandardMethodCodec(), super.binaryMessenger]); + + @override + Stream receiveBroadcastStream([dynamic arguments]) { + final MethodChannel methodChannel = OptionalMethodChannel(name, codec); + late StreamController controller; + controller = StreamController.broadcast(onListen: () async { + binaryMessenger.setMessageHandler(name, (reply) async { + if (reply == null) { + await controller.close(); + } else { + try { + controller.add(codec.decodeEnvelope(reply)); + } on PlatformException catch (e) { + controller.addError(e); + } + } + return null; + }); + try { + await methodChannel.invokeMethod('listen', arguments); + } catch (exception, stack) { + FlutterError.reportError(FlutterErrorDetails( + exception: exception, + stack: stack, + library: 'services library', + context: ErrorDescription('while activating platform stream on channel $name'), + )); + } + }, onCancel: () async { + binaryMessenger.setMessageHandler(name, null); + try { + await methodChannel.invokeMethod('cancel', arguments); + } catch (exception, stack) { + FlutterError.reportError(FlutterErrorDetails( + exception: exception, + stack: stack, + library: 'services library', + context: ErrorDescription('while de-activating platform stream on channel $name'), + )); + } + }); + return controller.stream; + } +} diff --git a/lib/services/geocoding_service.dart b/lib/services/geocoding_service.dart index 28f6990d3..41512449e 100644 --- a/lib/services/geocoding_service.dart +++ b/lib/services/geocoding_service.dart @@ -22,7 +22,11 @@ class GeocodingService { }); return (result as List).cast().map(Address.fromMap).toList(); } on PlatformException catch (e, stack) { - if (e.code != 'getAddress-empty' && e.code != 'getAddress-network') { + if (!{ + 'getAddress-empty', + 'getAddress-network', + 'getAddress-unavailable', + }.contains(e.code)) { await reportService.recordError(e, stack); } } diff --git a/lib/services/wallpaper_service.dart b/lib/services/wallpaper_service.dart new file mode 100644 index 000000000..78d3e1d00 --- /dev/null +++ b/lib/services/wallpaper_service.dart @@ -0,0 +1,23 @@ +import 'dart:typed_data'; + +import 'package:aves/model/wallpaper_target.dart'; +import 'package:aves/services/common/services.dart'; +import 'package:flutter/services.dart'; + +class WallpaperService { + static const platform = MethodChannel('deckers.thibault/aves/wallpaper'); + + static Future set(Uint8List bytes, WallpaperTarget target) async { + try { + await platform.invokeMethod('setWallpaper', { + 'bytes': bytes, + 'home': {WallpaperTarget.home, WallpaperTarget.homeLock}.contains(target), + 'lock': {WallpaperTarget.lock, WallpaperTarget.homeLock}.contains(target), + }); + return true; + } on PlatformException catch (e, stack) { + await reportService.recordError(e, stack); + } + return false; + } +} diff --git a/lib/theme/icons.dart b/lib/theme/icons.dart index 3bc9c645f..f4179c89f 100644 --- a/lib/theme/icons.dart +++ b/lib/theme/icons.dart @@ -104,6 +104,7 @@ class AIcons { static const IconData setCover = MdiIcons.imageEditOutline; static const IconData share = Icons.share_outlined; static const IconData show = Icons.visibility_outlined; + static const IconData slideshow = Icons.slideshow_outlined; static const IconData speed = Icons.speed_outlined; static const IconData stats = Icons.pie_chart_outline_outlined; static const IconData streams = Icons.translate_outlined; diff --git a/lib/theme/themes.dart b/lib/theme/themes.dart index 034874289..0cb4e1fd0 100644 --- a/lib/theme/themes.dart +++ b/lib/theme/themes.dart @@ -7,7 +7,7 @@ import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; class Themes { - static const _accentColor = Colors.indigoAccent; + static const defaultAccent = Colors.indigoAccent; static const _tooltipTheme = TooltipThemeData( verticalOffset: 32, @@ -19,10 +19,10 @@ class Themes { fontFeatures: [FontFeature.enable('smcp')], ); - static const _snackBarTheme = SnackBarThemeData( - actionTextColor: _accentColor, - behavior: SnackBarBehavior.floating, - ); + static SnackBarThemeData _snackBarTheme(Color accentColor) => SnackBarThemeData( + actionTextColor: accentColor, + behavior: SnackBarBehavior.floating, + ); static final _typography = Typography.material2018(platform: TargetPlatform.android); @@ -35,49 +35,49 @@ class Themes { static const _lightSecondLayer = Color(0xFFF5F5F5); // aka `Colors.grey[100]` static const _lightThirdLayer = Color(0xFFEEEEEE); // aka `Colors.grey[200]` - static final lightTheme = ThemeData( - colorScheme: ColorScheme.light( - primary: _accentColor, - secondary: _accentColor, - onPrimary: _lightBodyColor, - onSecondary: _lightBodyColor, - ), - brightness: Brightness.light, - // `canvasColor` is used by `Drawer`, `DropdownButton` and `ExpansionTileCard` - canvasColor: _lightSecondLayer, - scaffoldBackgroundColor: _lightFirstLayer, - // `cardColor` is used by `ExpansionPanel` - cardColor: _lightSecondLayer, - dialogBackgroundColor: _lightSecondLayer, - indicatorColor: _accentColor, - toggleableActiveColor: _accentColor, - typography: _typography, - appBarTheme: AppBarTheme( - backgroundColor: _lightFirstLayer, - // `foregroundColor` is used by icons - foregroundColor: _lightActionIconColor, - // `titleTextStyle.color` is used by text - titleTextStyle: _appBarTitleTextStyle.copyWith(color: _lightTitleColor), - systemOverlayStyle: SystemUiOverlayStyle.dark, - ), - listTileTheme: const ListTileThemeData( - iconColor: _lightActionIconColor, - ), - popupMenuTheme: const PopupMenuThemeData( - color: _lightSecondLayer, - ), - snackBarTheme: _snackBarTheme, - tabBarTheme: TabBarTheme( - labelColor: _lightTitleColor, - unselectedLabelColor: Colors.black54, - ), - textButtonTheme: TextButtonThemeData( - style: TextButton.styleFrom( - primary: _lightLabelColor, - ), - ), - tooltipTheme: _tooltipTheme, - ); + static ThemeData lightTheme(Color accentColor) => ThemeData( + colorScheme: ColorScheme.light( + primary: accentColor, + secondary: accentColor, + onPrimary: _lightBodyColor, + onSecondary: _lightBodyColor, + ), + brightness: Brightness.light, + // `canvasColor` is used by `Drawer`, `DropdownButton` and `ExpansionTileCard` + canvasColor: _lightSecondLayer, + scaffoldBackgroundColor: _lightFirstLayer, + // `cardColor` is used by `ExpansionPanel` + cardColor: _lightSecondLayer, + dialogBackgroundColor: _lightSecondLayer, + indicatorColor: accentColor, + toggleableActiveColor: accentColor, + typography: _typography, + appBarTheme: AppBarTheme( + backgroundColor: _lightFirstLayer, + // `foregroundColor` is used by icons + foregroundColor: _lightActionIconColor, + // `titleTextStyle.color` is used by text + titleTextStyle: _appBarTitleTextStyle.copyWith(color: _lightTitleColor), + systemOverlayStyle: SystemUiOverlayStyle.dark, + ), + listTileTheme: const ListTileThemeData( + iconColor: _lightActionIconColor, + ), + popupMenuTheme: const PopupMenuThemeData( + color: _lightSecondLayer, + ), + snackBarTheme: _snackBarTheme(accentColor), + tabBarTheme: TabBarTheme( + labelColor: _lightTitleColor, + unselectedLabelColor: Colors.black54, + ), + textButtonTheme: TextButtonThemeData( + style: TextButton.styleFrom( + primary: _lightLabelColor, + ), + ), + tooltipTheme: _tooltipTheme, + ); static final _darkThemeTypo = _typography.white; static final _darkTitleColor = _darkThemeTypo.titleMedium!.color!; @@ -87,71 +87,74 @@ class Themes { static const _darkSecondLayer = Color(0xFF363636); static const _darkThirdLayer = Color(0xFF424242); // aka `Colors.grey[800]` - static final darkTheme = ThemeData( - colorScheme: ColorScheme.dark( - primary: _accentColor, - secondary: _accentColor, - // surface color is used by the date/time pickers - surface: Colors.grey.shade800, - onPrimary: _darkBodyColor, - onSecondary: _darkBodyColor, - ), - brightness: Brightness.dark, - // `canvasColor` is used by `Drawer`, `DropdownButton` and `ExpansionTileCard` - canvasColor: _darkSecondLayer, - scaffoldBackgroundColor: _darkFirstLayer, - // `cardColor` is used by `ExpansionPanel` - cardColor: _darkSecondLayer, - dialogBackgroundColor: _darkSecondLayer, - indicatorColor: _accentColor, - toggleableActiveColor: _accentColor, - typography: _typography, - appBarTheme: AppBarTheme( - backgroundColor: _darkFirstLayer, - // `foregroundColor` is used by icons - foregroundColor: _darkTitleColor, - // `titleTextStyle.color` is used by text - titleTextStyle: _appBarTitleTextStyle.copyWith(color: _darkTitleColor), - systemOverlayStyle: SystemUiOverlayStyle.light, - ), - popupMenuTheme: const PopupMenuThemeData( - color: _darkSecondLayer, - ), - snackBarTheme: _snackBarTheme.copyWith( - backgroundColor: Colors.grey.shade800, - contentTextStyle: TextStyle( - color: _darkBodyColor, - ), - ), - tabBarTheme: TabBarTheme( - labelColor: _darkTitleColor, - ), - textButtonTheme: TextButtonThemeData( - style: TextButton.styleFrom( - primary: _darkLabelColor, - ), - ), - tooltipTheme: _tooltipTheme, - ); + static ThemeData darkTheme(Color accentColor) => ThemeData( + colorScheme: ColorScheme.dark( + primary: accentColor, + secondary: accentColor, + // surface color is used by the date/time pickers + surface: Colors.grey.shade800, + onPrimary: _darkBodyColor, + onSecondary: _darkBodyColor, + ), + brightness: Brightness.dark, + // `canvasColor` is used by `Drawer`, `DropdownButton` and `ExpansionTileCard` + canvasColor: _darkSecondLayer, + scaffoldBackgroundColor: _darkFirstLayer, + // `cardColor` is used by `ExpansionPanel` + cardColor: _darkSecondLayer, + dialogBackgroundColor: _darkSecondLayer, + indicatorColor: accentColor, + toggleableActiveColor: accentColor, + typography: _typography, + appBarTheme: AppBarTheme( + backgroundColor: _darkFirstLayer, + // `foregroundColor` is used by icons + foregroundColor: _darkTitleColor, + // `titleTextStyle.color` is used by text + titleTextStyle: _appBarTitleTextStyle.copyWith(color: _darkTitleColor), + systemOverlayStyle: SystemUiOverlayStyle.light, + ), + popupMenuTheme: const PopupMenuThemeData( + color: _darkSecondLayer, + ), + snackBarTheme: _snackBarTheme(accentColor).copyWith( + backgroundColor: Colors.grey.shade800, + contentTextStyle: TextStyle( + color: _darkBodyColor, + ), + ), + tabBarTheme: TabBarTheme( + labelColor: _darkTitleColor, + ), + textButtonTheme: TextButtonThemeData( + style: TextButton.styleFrom( + primary: _darkLabelColor, + ), + ), + tooltipTheme: _tooltipTheme, + ); static const _blackFirstLayer = Colors.black; static const _blackSecondLayer = Color(0xFF212121); // aka `Colors.grey[900]` static const _blackThirdLayer = Color(0xFF303030); // aka `Colors.grey[850]` - static final blackTheme = darkTheme.copyWith( - // `canvasColor` is used by `Drawer`, `DropdownButton` and `ExpansionTileCard` - canvasColor: _blackSecondLayer, - scaffoldBackgroundColor: _blackFirstLayer, - // `cardColor` is used by `ExpansionPanel` - cardColor: _blackSecondLayer, - dialogBackgroundColor: _blackSecondLayer, - appBarTheme: darkTheme.appBarTheme.copyWith( - backgroundColor: _blackFirstLayer, - ), - popupMenuTheme: darkTheme.popupMenuTheme.copyWith( - color: _blackSecondLayer, - ), - ); + static ThemeData blackTheme(Color accentColor) { + final baseTheme = darkTheme(accentColor); + return baseTheme.copyWith( + // `canvasColor` is used by `Drawer`, `DropdownButton` and `ExpansionTileCard` + canvasColor: _blackSecondLayer, + scaffoldBackgroundColor: _blackFirstLayer, + // `cardColor` is used by `ExpansionPanel` + cardColor: _blackSecondLayer, + dialogBackgroundColor: _blackSecondLayer, + appBarTheme: baseTheme.appBarTheme.copyWith( + backgroundColor: _blackFirstLayer, + ), + popupMenuTheme: baseTheme.popupMenuTheme.copyWith( + color: _blackSecondLayer, + ), + ); + } static Color overlayBackgroundColor({ required Brightness brightness, diff --git a/lib/utils/constants.dart b/lib/utils/constants.dart index 443e47935..ec9e2a8c2 100644 --- a/lib/utils/constants.dart +++ b/lib/utils/constants.dart @@ -108,6 +108,11 @@ class Constants { licenseUrl: 'https://github.com/fluttercommunity/plus_plugins/blob/main/packages/device_info_plus/device_info_plus/LICENSE', sourceUrl: 'https://github.com/fluttercommunity/plus_plugins/tree/main/packages/device_info_plus', ), + Dependency( + name: 'Dynamic Color', + license: 'BSD 3-Clause', + sourceUrl: 'https://github.com/material-foundation/material-dynamic-color-flutter', + ), Dependency( name: 'fijkplayer (Aves fork)', license: 'MIT', @@ -337,6 +342,12 @@ class Constants { license: 'Apache 2.0', sourceUrl: 'https://github.com/jifalops/dart-latlong', ), + Dependency( + name: 'Material Color Utilities', + license: 'Apache 2.0', + licenseUrl: 'https://github.com/material-foundation/material-color-utilities/tree/main/dart/LICENSE', + sourceUrl: 'https://github.com/material-foundation/material-color-utilities/tree/main/dart', + ), Dependency( name: 'Path', license: 'BSD 3-Clause', diff --git a/lib/utils/file_utils.dart b/lib/utils/file_utils.dart index ca707920f..5ed77aaf5 100644 --- a/lib/utils/file_utils.dart +++ b/lib/utils/file_utils.dart @@ -1,16 +1,16 @@ import 'package:intl/intl.dart'; -const _kiloDivider = 1024; -const _megaDivider = _kiloDivider * _kiloDivider; -const _gigaDivider = _megaDivider * _kiloDivider; -const _teraDivider = _gigaDivider * _kiloDivider; +const kilo = 1024; +const mega = kilo * kilo; +const giga = mega * kilo; +const tera = giga * kilo; String formatFileSize(String locale, int size, {int round = 2}) { - if (size < _kiloDivider) return '$size B'; + if (size < kilo) return '$size B'; final formatter = NumberFormat('0${round > 0 ? '.${'0' * round}' : ''}', locale); - if (size < _megaDivider) return '${formatter.format(size / _kiloDivider)} KB'; - if (size < _gigaDivider) return '${formatter.format(size / _megaDivider)} MB'; - if (size < _teraDivider) return '${formatter.format(size / _gigaDivider)} GB'; - return '${formatter.format(size / _teraDivider)} TB'; + if (size < mega) return '${formatter.format(size / kilo)} KB'; + if (size < giga) return '${formatter.format(size / mega)} MB'; + if (size < tera) return '${formatter.format(size / giga)} GB'; + return '${formatter.format(size / tera)} TB'; } diff --git a/lib/widgets/about/credits.dart b/lib/widgets/about/credits.dart index 2e5595ea2..c3b690a40 100644 --- a/lib/widgets/about/credits.dart +++ b/lib/widgets/about/credits.dart @@ -13,6 +13,7 @@ class AboutCredits extends StatelessWidget { 'Español (México)': 'n-berenice', 'Italiano': 'glemco', 'Português (Brasil)': 'Jonatas De Almeida Barros', + 'Türkçe': 'metezd', 'Русский': 'D3ZOXY', '日本語': 'Maki', '简体中文': '小默, Aerowolf', diff --git a/lib/widgets/aves_app.dart b/lib/widgets/aves_app.dart index 14a878eac..5b7c64661 100644 --- a/lib/widgets/aves_app.dart +++ b/lib/widgets/aves_app.dart @@ -5,6 +5,7 @@ import 'package:aves/app_flavor.dart'; import 'package:aves/app_mode.dart'; import 'package:aves/l10n/l10n.dart'; import 'package:aves/model/device.dart'; +import 'package:aves/model/settings/defaults.dart'; import 'package:aves/model/settings/enums/accessibility_animations.dart'; import 'package:aves/model/settings/enums/display_refresh_rate_mode.dart'; import 'package:aves/model/settings/enums/enums.dart'; @@ -15,6 +16,7 @@ import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_source.dart'; import 'package:aves/model/source/media_store_source.dart'; import 'package:aves/services/accessibility_service.dart'; +import 'package:aves/services/common/optional_event_channel.dart'; import 'package:aves/services/common/services.dart'; import 'package:aves/theme/colors.dart'; import 'package:aves/theme/durations.dart'; @@ -30,11 +32,13 @@ import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/providers/highlight_info_provider.dart'; import 'package:aves/widgets/home_page.dart'; import 'package:aves/widgets/welcome_page.dart'; +import 'package:dynamic_color/dynamic_color.dart'; import 'package:equatable/equatable.dart'; import 'package:fijkplayer/fijkplayer.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:material_color_utilities/material_color_utilities.dart'; import 'package:overlay_support/overlay_support.dart'; import 'package:provider/provider.dart'; import 'package:tuple/tuple.dart'; @@ -70,6 +74,7 @@ class AvesApp extends StatefulWidget { class _AvesAppState extends State with WidgetsBindingObserver { final ValueNotifier appModeNotifier = ValueNotifier(AppMode.main); late Future _appSetup; + late Future _dynamicColorPaletteLoader; final _mediaStoreSource = MediaStoreSource(); final Debouncer _mediaStoreChangeDebouncer = Debouncer(delay: Durations.mediaContentChangeDebounceDelay); final Set changedUris = {}; @@ -77,10 +82,10 @@ class _AvesAppState extends State with WidgetsBindingObserver { // observers are not registered when using the same list object with different items // the list itself needs to be reassigned List _navigatorObservers = [AvesApp.pageRouteObserver]; - final EventChannel _mediaStoreChangeChannel = const EventChannel('deckers.thibault/aves/media_store_change'); - final EventChannel _newIntentChannel = const EventChannel('deckers.thibault/aves/intent'); - final EventChannel _analysisCompletionChannel = const EventChannel('deckers.thibault/aves/analysis_events'); - final EventChannel _errorChannel = const EventChannel('deckers.thibault/aves/error'); + final EventChannel _mediaStoreChangeChannel = const OptionalEventChannel('deckers.thibault/aves/media_store_change'); + final EventChannel _newIntentChannel = const OptionalEventChannel('deckers.thibault/aves/intent'); + final EventChannel _analysisCompletionChannel = const OptionalEventChannel('deckers.thibault/aves/analysis_events'); + final EventChannel _errorChannel = const OptionalEventChannel('deckers.thibault/aves/error'); Widget getFirstPage({Map? intentData}) => settings.hasAcceptedTerms ? HomePage(intentData: intentData) : const WelcomePage(); @@ -89,6 +94,7 @@ class _AvesAppState extends State with WidgetsBindingObserver { super.initState(); EquatableConfig.stringify = true; _appSetup = _setup(); + _dynamicColorPaletteLoader = DynamicColorPlugin.getCorePalette(); _mediaStoreChangeChannel.receiveBroadcastStream().listen((event) => _onMediaStoreChange(event as String?)); _newIntentChannel.receiveBroadcastStream().listen((event) => _onNewIntent(event as Map?)); _analysisCompletionChannel.receiveBroadcastStream().listen((event) => _onAnalysisCompletion()); @@ -120,16 +126,18 @@ class _AvesAppState extends State with WidgetsBindingObserver { : Scaffold( body: snapshot.hasError ? _buildError(snapshot.error!) : const SizedBox(), ); - return Selector>( - selector: (context, s) => Tuple3( + return Selector>( + selector: (context, s) => Tuple4( s.locale, s.initialized ? s.accessibilityAnimations.animate : true, - s.initialized ? s.themeBrightness : AvesThemeBrightness.system, + s.initialized ? s.themeBrightness : SettingsDefaults.themeBrightness, + s.initialized ? s.enableDynamicColor : SettingsDefaults.enableDynamicColor, ), builder: (context, s, child) { final settingsLocale = s.item1; final areAnimationsEnabled = s.item2; final themeBrightness = s.item3; + final enableDynamicColor = s.item4; final pageTransitionsTheme = areAnimationsEnabled // Flutter has various page transition implementations for Android: @@ -144,27 +152,42 @@ class _AvesAppState extends State with WidgetsBindingObserver { // strip page transitions used by `MaterialPageRoute` : const DirectPageTransitionsTheme(); - return MaterialApp( - navigatorKey: AvesApp.navigatorKey, - home: home, - navigatorObservers: _navigatorObservers, - builder: (context, child) => AvesColorsProvider( - child: Theme( - data: Theme.of(context).copyWith( - pageTransitionsTheme: pageTransitionsTheme, + return FutureBuilder( + future: _dynamicColorPaletteLoader, + builder: (context, snapshot) { + const defaultAccent = Themes.defaultAccent; + Color lightAccent = defaultAccent, darkAccent = defaultAccent; + if (enableDynamicColor) { + // `DynamicColorBuilder` from package `dynamic_color` provides light/dark + // palettes with a primary color from tones too dark/light (40/80), + // so we derive the color with adjusted tones (60/70) + final tonalPalette = snapshot.data?.primary; + lightAccent = Color(tonalPalette?.get(60) ?? defaultAccent.value); + darkAccent = Color(tonalPalette?.get(70) ?? defaultAccent.value); + } + return MaterialApp( + navigatorKey: AvesApp.navigatorKey, + home: home, + navigatorObservers: _navigatorObservers, + builder: (context, child) => AvesColorsProvider( + child: Theme( + data: Theme.of(context).copyWith( + pageTransitionsTheme: pageTransitionsTheme, + ), + child: child!, + ), ), - child: child!, - ), - ), - onGenerateTitle: (context) => context.l10n.appName, - theme: Themes.lightTheme, - darkTheme: themeBrightness == AvesThemeBrightness.black ? Themes.blackTheme : Themes.darkTheme, - themeMode: themeBrightness.appThemeMode, - locale: settingsLocale, - localizationsDelegates: AppLocalizations.localizationsDelegates, - supportedLocales: AppLocalizations.supportedLocales, - // TODO TLAD remove custom scroll behavior when this is fixed: https://github.com/flutter/flutter/issues/82906 - scrollBehavior: StretchMaterialScrollBehavior(), + onGenerateTitle: (context) => context.l10n.appName, + theme: Themes.lightTheme(lightAccent), + darkTheme: themeBrightness == AvesThemeBrightness.black ? Themes.blackTheme(darkAccent) : Themes.darkTheme(darkAccent), + themeMode: themeBrightness.appThemeMode, + locale: settingsLocale, + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, + // TODO TLAD remove custom scroll behavior when this is fixed: https://github.com/flutter/flutter/issues/82906 + scrollBehavior: StretchMaterialScrollBehavior(), + ); + }, ); }, ); @@ -207,6 +230,8 @@ class _AvesAppState extends State with WidgetsBindingObserver { break; case AppMode.pickMediaInternal: case AppMode.pickFilterInternal: + case AppMode.setWallpaper: + case AppMode.slideshow: case AppMode.view: break; } @@ -223,7 +248,14 @@ class _AvesAppState extends State with WidgetsBindingObserver { if (!settings.initialized) return; final stopwatch = Stopwatch()..start(); - final screenSize = window.physicalSize / window.devicePixelRatio; + final Size screenSize; + try { + screenSize = window.physicalSize / window.devicePixelRatio; + } catch (error) { + // view may no longer be usable + return; + } + var tileExtent = settings.getTileExtent(CollectionPage.routeName); if (tileExtent == 0) { tileExtent = screenSize.shortestSide / CollectionGrid.columnCountDefault; diff --git a/lib/widgets/collection/app_bar.dart b/lib/widgets/collection/app_bar.dart index d531a1438..5d1624d68 100644 --- a/lib/widgets/collection/app_bar.dart +++ b/lib/widgets/collection/app_bar.dart @@ -284,6 +284,7 @@ class _CollectionAppBarState extends State with SingleTickerPr padding: EdgeInsets.zero, child: PopupMenuItemExpansionPanel( enabled: canApplyEditActions, + value: 'edit', icon: AIcons.edit, title: context.l10n.collectionActionEdit, items: [ @@ -477,6 +478,7 @@ class _CollectionAppBarState extends State with SingleTickerPr case EntrySetAction.addShortcut: // browsing or selecting case EntrySetAction.map: + case EntrySetAction.slideshow: case EntrySetAction.stats: case EntrySetAction.rescan: case EntrySetAction.emptyBin: diff --git a/lib/widgets/collection/collection_grid.dart b/lib/widgets/collection/collection_grid.dart index 30a9c8da0..30005f025 100644 --- a/lib/widgets/collection/collection_grid.dart +++ b/lib/widgets/collection/collection_grid.dart @@ -29,6 +29,7 @@ import 'package:aves/widgets/common/grid/section_layout.dart'; import 'package:aves/widgets/common/grid/selector.dart'; import 'package:aves/widgets/common/grid/sliver.dart'; import 'package:aves/widgets/common/grid/theme.dart'; +import 'package:aves/widgets/common/identity/buttons.dart'; import 'package:aves/widgets/common/identity/empty.dart'; import 'package:aves/widgets/common/identity/scroll_thumb.dart'; import 'package:aves/widgets/common/providers/tile_extent_controller_provider.dart'; @@ -39,6 +40,7 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:intl/intl.dart'; +import 'package:permission_handler/permission_handler.dart'; import 'package:provider/provider.dart'; import 'package:tuple/tuple.dart'; @@ -97,6 +99,7 @@ class _CollectionGridContent extends StatelessWidget { final sectionedListLayoutProvider = ValueListenableBuilder( valueListenable: context.select>((controller) => controller.extentNotifier), builder: (context, thumbnailExtent, child) { + assert(thumbnailExtent > 0); return Selector>( selector: (context, c) => Tuple4(c.viewportSize.width, c.columnCount, c.spacing, c.horizontalPadding), builder: (context, c, child) { @@ -305,13 +308,15 @@ class _CollectionScrollView extends StatefulWidget { State<_CollectionScrollView> createState() => _CollectionScrollViewState(); } -class _CollectionScrollViewState extends State<_CollectionScrollView> { +class _CollectionScrollViewState extends State<_CollectionScrollView> with WidgetsBindingObserver { Timer? _scrollMonitoringTimer; + bool _checkingStoragePermission = false; @override void initState() { super.initState(); _registerWidget(widget); + WidgetsBinding.instance.addObserver(this); } @override @@ -323,6 +328,7 @@ class _CollectionScrollViewState extends State<_CollectionScrollView> { @override void dispose() { + WidgetsBinding.instance.removeObserver(this); _unregisterWidget(widget); _stopScrollMonitoringTimer(); super.dispose(); @@ -340,6 +346,26 @@ class _CollectionScrollViewState extends State<_CollectionScrollView> { widget.scrollController.removeListener(_onScrollChange); } + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + switch (state) { + case AppLifecycleState.inactive: + case AppLifecycleState.paused: + case AppLifecycleState.detached: + break; + case AppLifecycleState.resumed: + if (_checkingStoragePermission) { + _checkingStoragePermission = false; + _isStoragePermissionGranted.then((granted) { + if (granted) { + widget.collection.source.init(); + } + }); + } + break; + } + } + @override Widget build(BuildContext context) { final scrollView = _buildScrollView(widget.appBar, widget.collection); @@ -423,23 +449,47 @@ class _CollectionScrollViewState extends State<_CollectionScrollView> { valueListenable: collection.source.stateNotifier, builder: (context, sourceState, child) { if (sourceState == SourceState.loading) { - return const SizedBox.shrink(); + return const SizedBox(); } - if (collection.filters.any((filter) => filter is FavouriteFilter)) { - return EmptyContent( - icon: AIcons.favourite, - text: context.l10n.collectionEmptyFavourites, - ); - } - if (collection.filters.any((filter) => filter is MimeFilter && filter.mime == MimeTypes.anyVideo)) { - return EmptyContent( - icon: AIcons.video, - text: context.l10n.collectionEmptyVideos, - ); - } - return EmptyContent( - icon: AIcons.image, - text: context.l10n.collectionEmptyImages, + + return FutureBuilder( + future: _isStoragePermissionGranted, + builder: (context, snapshot) { + final granted = snapshot.data ?? true; + Widget? bottom = granted + ? null + : Padding( + padding: const EdgeInsets.only(top: 16), + child: AvesOutlinedButton( + label: context.l10n.collectionEmptyGrantAccessButtonLabel, + onPressed: () async { + if (await openAppSettings()) { + _checkingStoragePermission = true; + } + }, + ), + ); + + if (collection.filters.any((filter) => filter is FavouriteFilter)) { + return EmptyContent( + icon: AIcons.favourite, + text: context.l10n.collectionEmptyFavourites, + bottom: bottom, + ); + } + if (collection.filters.any((filter) => filter is MimeFilter && filter.mime == MimeTypes.anyVideo)) { + return EmptyContent( + icon: AIcons.video, + text: context.l10n.collectionEmptyVideos, + bottom: bottom, + ); + } + return EmptyContent( + icon: AIcons.image, + text: context.l10n.collectionEmptyImages, + bottom: bottom, + ); + }, ); }, ); @@ -519,4 +569,6 @@ class _CollectionScrollViewState extends State<_CollectionScrollView> { } return crumbs; } + + Future get _isStoragePermissionGranted => Permission.storage.status.then((status) => status.isGranted); } diff --git a/lib/widgets/collection/entry_set_action_delegate.dart b/lib/widgets/collection/entry_set_action_delegate.dart index b839a0843..09a719122 100644 --- a/lib/widgets/collection/entry_set_action_delegate.dart +++ b/lib/widgets/collection/entry_set_action_delegate.dart @@ -35,6 +35,7 @@ import 'package:aves/widgets/dialogs/entry_editors/rename_entry_set_dialog.dart' import 'package:aves/widgets/map/map_page.dart'; import 'package:aves/widgets/search/search_delegate.dart'; import 'package:aves/widgets/stats/stats_page.dart'; +import 'package:aves/widgets/viewer/slideshow_page.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; @@ -73,6 +74,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware return appMode == AppMode.main && isTrash; // browsing or selecting case EntrySetAction.map: + case EntrySetAction.slideshow: case EntrySetAction.stats: return appMode == AppMode.main; case EntrySetAction.rescan: @@ -124,6 +126,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware case EntrySetAction.emptyBin: return !isSelecting && hasItems; case EntrySetAction.map: + case EntrySetAction.slideshow: case EntrySetAction.stats: case EntrySetAction.rescan: return (!isSelecting && hasItems) || (isSelecting && hasSelection); @@ -169,6 +172,9 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware case EntrySetAction.map: _goToMap(context); break; + case EntrySetAction.slideshow: + _goToSlideshow(context); + break; case EntrySetAction.stats: _goToStats(context); break; @@ -543,6 +549,27 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware ); } + void _goToSlideshow(BuildContext context) { + final collection = context.read(); + final entries = _getTargetItems(context); + + Navigator.push( + context, + MaterialPageRoute( + settings: const RouteSettings(name: SlideshowPage.routeName), + builder: (context) { + return SlideshowPage( + collection: CollectionLens( + source: collection.source, + filters: collection.filters, + fixedSelection: entries.toList(), + ), + ); + }, + ), + ); + } + void _goToStats(BuildContext context) { final collection = context.read(); final entries = _getTargetItems(context); diff --git a/lib/widgets/collection/grid/tile.dart b/lib/widgets/collection/grid/tile.dart index bebcb6d70..3eef2ef9c 100644 --- a/lib/widgets/collection/grid/tile.dart +++ b/lib/widgets/collection/grid/tile.dart @@ -54,6 +54,8 @@ class InteractiveTile extends StatelessWidget { Navigator.pop(context, entry); break; case AppMode.pickFilterInternal: + case AppMode.setWallpaper: + case AppMode.slideshow: case AppMode.view: break; } diff --git a/lib/widgets/common/action_mixins/feedback.dart b/lib/widgets/common/action_mixins/feedback.dart index c077cb420..5ed284731 100644 --- a/lib/widgets/common/action_mixins/feedback.dart +++ b/lib/widgets/common/action_mixins/feedback.dart @@ -122,7 +122,7 @@ mixin FeedbackMixin { Future showOpReport({ required BuildContext context, required Stream opStream, - required int itemCount, + int? itemCount, VoidCallback? onCancel, void Function(Set processed)? onDone, }) { @@ -144,7 +144,7 @@ mixin FeedbackMixin { class ReportOverlay extends StatefulWidget { final Stream opStream; - final int itemCount; + final int? itemCount; final VoidCallback? onCancel; final void Function(Set processed) onDone; @@ -212,7 +212,7 @@ class _ReportOverlayState extends State> with SingleTickerPr builder: (context, snapshot) { final processedCount = processed.length.toDouble(); final total = widget.itemCount; - final percent = total != 0 ? min(1.0, processedCount / total) : 1.0; + final percent = total == null || total == 0 ? 0.0 : min(1.0, processedCount / total); return FadeTransition( opacity: _animation, child: Stack( @@ -243,10 +243,12 @@ class _ReportOverlayState extends State> with SingleTickerPr backgroundColor: Theme.of(context).colorScheme.onSurface.withOpacity(.2), progressColor: progressColor, animation: animate, - center: Text( - NumberFormat.percentPattern().format(percent), - style: const TextStyle(fontSize: fontSize), - ), + center: total != null + ? Text( + NumberFormat.percentPattern().format(percent), + style: const TextStyle(fontSize: fontSize), + ) + : null, animateFromLastPercent: true, ), if (widget.onCancel != null) @@ -305,12 +307,13 @@ class _FeedbackMessageState extends State<_FeedbackMessage> with SingleTickerPro if (start != null && stop != null) { _totalDurationMillis = stop.difference(start).inMilliseconds; final remainingDuration = stop.difference(DateTime.now()); + final effectiveDuration = remainingDuration > Duration.zero ? remainingDuration : const Duration(milliseconds: 1); _animationController = AnimationController( - duration: remainingDuration, + duration: effectiveDuration, vsync: this, ); _remainingDurationMillis = IntTween( - begin: remainingDuration.inMilliseconds, + begin: effectiveDuration.inMilliseconds, end: 0, ).animate(CurvedAnimation( parent: _animationController!, diff --git a/lib/widgets/common/aves_highlight.dart b/lib/widgets/common/aves_highlight.dart index 2f1894ce4..3a2f4f0c6 100644 --- a/lib/widgets/common/aves_highlight.dart +++ b/lib/widgets/common/aves_highlight.dart @@ -38,7 +38,7 @@ class AvesHighlightView extends StatelessWidget { this.padding, this.textStyle, int tabSize = 8, // TODO: https://github.com/flutter/flutter/issues/50087 - }) : source = input.replaceAll('\t', ' ' * tabSize); + }) : source = input.replaceAll('\t', ' ' * tabSize); List _convert(List nodes) { final spans = []; diff --git a/lib/widgets/common/basic/menu.dart b/lib/widgets/common/basic/menu.dart index 695e2c73e..ff589e4e6 100644 --- a/lib/widgets/common/basic/menu.dart +++ b/lib/widgets/common/basic/menu.dart @@ -55,25 +55,27 @@ class MenuIconTheme extends StatelessWidget { class PopupMenuItemExpansionPanel extends StatefulWidget { final bool enabled; + final String value; + final ValueNotifier expandedNotifier; final IconData icon; final String title; final List> items; - const PopupMenuItemExpansionPanel({ + PopupMenuItemExpansionPanel({ super.key, this.enabled = true, + required this.value, + ValueNotifier? expandedNotifier, required this.icon, required this.title, required this.items, - }); + }) : expandedNotifier = expandedNotifier ?? ValueNotifier(null); @override State> createState() => _PopupMenuItemExpansionPanelState(); } class _PopupMenuItemExpansionPanelState extends State> { - bool _isExpanded = false; - // ref `_kMenuHorizontalPadding` used in `PopupMenuItem` static const double _horizontalPadding = 16; @@ -86,38 +88,43 @@ class _PopupMenuItemExpansionPanelState extends State((v) => v.expansionTileAnimation); - Widget child = ExpansionPanelList( - expansionCallback: (index, isExpanded) { - setState(() => _isExpanded = !isExpanded); - }, - animationDuration: animationDuration, - expandedHeaderPadding: EdgeInsets.zero, - elevation: 0, - children: [ - ExpansionPanel( - headerBuilder: (context, isExpanded) => DefaultTextStyle( - style: style, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: _horizontalPadding), - child: MenuRow( - text: widget.title, - icon: Icon(widget.icon), + Widget child = ValueListenableBuilder( + valueListenable: widget.expandedNotifier, + builder: (context, expandedValue, child) { + return ExpansionPanelList( + expansionCallback: (index, isExpanded) { + widget.expandedNotifier.value = isExpanded ? null : widget.value; + }, + animationDuration: animationDuration, + expandedHeaderPadding: EdgeInsets.zero, + elevation: 0, + children: [ + ExpansionPanel( + headerBuilder: (context, isExpanded) => DefaultTextStyle( + style: style, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: _horizontalPadding), + child: MenuRow( + text: widget.title, + icon: Icon(widget.icon), + ), + ), ), + body: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const PopupMenuDivider(height: 0), + ...widget.items, + const PopupMenuDivider(height: 0), + ], + ), + isExpanded: expandedValue == widget.value, + canTapOnHeader: true, + backgroundColor: Colors.transparent, ), - ), - body: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const PopupMenuDivider(height: 0), - ...widget.items, - const PopupMenuDivider(height: 0), - ], - ), - isExpanded: _isExpanded, - canTapOnHeader: true, - backgroundColor: Colors.transparent, - ), - ], + ], + ); + }, ); if (!widget.enabled) { child = IgnorePointer(child: child); diff --git a/lib/widgets/common/basic/query_bar.dart b/lib/widgets/common/basic/query_bar.dart index a19d0e813..d525edeb8 100644 --- a/lib/widgets/common/basic/query_bar.dart +++ b/lib/widgets/common/basic/query_bar.dart @@ -7,6 +7,7 @@ import 'package:flutter/material.dart'; class QueryBar extends StatefulWidget { final ValueNotifier queryNotifier; final FocusNode? focusNode; + final EdgeInsetsGeometry? leadingPadding; final IconData? icon; final String? hintText; final bool editable; @@ -15,6 +16,7 @@ class QueryBar extends StatefulWidget { super.key, required this.queryNotifier, this.focusNode, + this.leadingPadding, this.icon, this.hintText, this.editable = true, @@ -60,7 +62,7 @@ class _QueryBarState extends State { focusNode: widget.focusNode ?? FocusNode(), decoration: InputDecoration( icon: Padding( - padding: const EdgeInsetsDirectional.only(start: 16), + padding: widget.leadingPadding ?? const EdgeInsetsDirectional.only(start: 16), child: Icon(widget.icon ?? AIcons.filter), ), hintText: widget.hintText ?? MaterialLocalizations.of(context).searchFieldLabel, diff --git a/lib/widgets/common/basic/reselectable_radio_list_tile.dart b/lib/widgets/common/basic/reselectable_radio_list_tile.dart index 5d88833c3..b3d456627 100644 --- a/lib/widgets/common/basic/reselectable_radio_list_tile.dart +++ b/lib/widgets/common/basic/reselectable_radio_list_tile.dart @@ -35,7 +35,7 @@ class ReselectableRadioListTile extends StatelessWidget { this.selected = false, this.controlAffinity = ListTileControlAffinity.platform, this.autofocus = false, - }) : assert(!isThreeLine || subtitle != null); + }) : assert(!isThreeLine || subtitle != null); @override Widget build(BuildContext context) { diff --git a/lib/widgets/common/identity/aves_app_bar.dart b/lib/widgets/common/identity/aves_app_bar.dart index 8dcadd5f0..6f36bd821 100644 --- a/lib/widgets/common/identity/aves_app_bar.dart +++ b/lib/widgets/common/identity/aves_app_bar.dart @@ -206,7 +206,7 @@ class _AvesFloatingBarState extends State with RouteAware { return ValueListenableBuilder( valueListenable: _isBlurAllowedNotifier, builder: (context, isBlurAllowed, child) { - final blurred = isBlurAllowed && context.select((s) => s.enableOverlayBlurEffect); + final blurred = isBlurAllowed && context.select((s) => s.enableBlurEffect); return Container( foregroundDecoration: BoxDecoration( border: Border.all( diff --git a/lib/widgets/common/identity/aves_expansion_tile.dart b/lib/widgets/common/identity/aves_expansion_tile.dart index c929a1b73..ac09287ea 100644 --- a/lib/widgets/common/identity/aves_expansion_tile.dart +++ b/lib/widgets/common/identity/aves_expansion_tile.dart @@ -23,7 +23,7 @@ class AvesExpansionTile extends StatelessWidget { this.initiallyExpanded = false, this.showHighlight = true, required this.children, - }) : value = value ?? title; + }) : value = value ?? title; @override Widget build(BuildContext context) { diff --git a/lib/widgets/common/identity/aves_filter_chip.dart b/lib/widgets/common/identity/aves_filter_chip.dart index 21dfe3297..637cca171 100644 --- a/lib/widgets/common/identity/aves_filter_chip.dart +++ b/lib/widgets/common/identity/aves_filter_chip.dart @@ -140,6 +140,7 @@ class _AvesFilterChipState extends State { _subscriptions.add(settings.updateStream.where((event) => event.key == Settings.themeColorModeKey).listen((_) { // delay so that contextual colors reflect the new settings WidgetsBinding.instance.addPostFrameCallback((_) { + if (!mounted) return; _onCoverColorChange(null); }); })); diff --git a/lib/widgets/common/identity/empty.dart b/lib/widgets/common/identity/empty.dart index 9c40675df..0d617e9ea 100644 --- a/lib/widgets/common/identity/empty.dart +++ b/lib/widgets/common/identity/empty.dart @@ -5,6 +5,7 @@ import 'package:provider/provider.dart'; class EmptyContent extends StatelessWidget { final IconData? icon; final String text; + final Widget? bottom; final AlignmentGeometry alignment; final double fontSize; final bool safeBottom; @@ -13,6 +14,7 @@ class EmptyContent extends StatelessWidget { super.key, this.icon, required this.text, + this.bottom, this.alignment = const FractionalOffset(.5, .35), this.fontSize = 22, this.safeBottom = true, @@ -20,7 +22,7 @@ class EmptyContent extends StatelessWidget { @override Widget build(BuildContext context) { - const color = Colors.blueGrey; + final color = Theme.of(context).colorScheme.secondary.withOpacity(.5); return Padding( padding: safeBottom ? EdgeInsets.only( @@ -48,6 +50,7 @@ class EmptyContent extends StatelessWidget { ), textAlign: TextAlign.center, ), + if (bottom != null) bottom!, ], ), ), diff --git a/lib/widgets/common/magnifier/magnifier.dart b/lib/widgets/common/magnifier/magnifier.dart index 98d5b6d35..4f2ed8fec 100644 --- a/lib/widgets/common/magnifier/magnifier.dart +++ b/lib/widgets/common/magnifier/magnifier.dart @@ -23,6 +23,7 @@ class Magnifier extends StatelessWidget { super.key, required this.controller, required this.childSize, + this.allowOriginalScaleBeyondRange = true, this.minScale = const ScaleLevel(factor: .0), this.maxScale = const ScaleLevel(factor: double.infinity), this.initialScale = const ScaleLevel(ref: ScaleReference.contained), @@ -38,6 +39,8 @@ class Magnifier extends StatelessWidget { // The size of the custom [child]. This value is used to compute the relation between the child and the container's size to calculate the scale value. final Size childSize; + final bool allowOriginalScaleBeyondRange; + // Defines the minimum size in which the image will be allowed to assume, it is proportional to the original image size. final ScaleLevel minScale; @@ -58,6 +61,7 @@ class Magnifier extends StatelessWidget { return LayoutBuilder( builder: (context, constraints) { controller.setScaleBoundaries(ScaleBoundaries( + allowOriginalScaleBeyondRange: allowOriginalScaleBeyondRange, minScale: minScale, maxScale: maxScale, initialScale: initialScale, diff --git a/lib/widgets/common/magnifier/scale/scale_boundaries.dart b/lib/widgets/common/magnifier/scale/scale_boundaries.dart index 986fc82ec..e2190afac 100644 --- a/lib/widgets/common/magnifier/scale/scale_boundaries.dart +++ b/lib/widgets/common/magnifier/scale/scale_boundaries.dart @@ -10,6 +10,7 @@ import 'package:flutter/foundation.dart'; /// Also, stores values regarding the two sizes: the container and the child. @immutable class ScaleBoundaries extends Equatable { + final bool _allowOriginalScaleBeyondRange; final ScaleLevel _minScale; final ScaleLevel _maxScale; final ScaleLevel _initialScale; @@ -17,18 +18,33 @@ class ScaleBoundaries extends Equatable { final Size childSize; @override - List get props => [_minScale, _maxScale, _initialScale, viewportSize, childSize]; + List get props => [_allowOriginalScaleBeyondRange, _minScale, _maxScale, _initialScale, viewportSize, childSize]; const ScaleBoundaries({ + required bool allowOriginalScaleBeyondRange, required ScaleLevel minScale, required ScaleLevel maxScale, required ScaleLevel initialScale, required this.viewportSize, required this.childSize, - }) : _minScale = minScale, + }) : _allowOriginalScaleBeyondRange = allowOriginalScaleBeyondRange, + _minScale = minScale, _maxScale = maxScale, _initialScale = initialScale; + ScaleBoundaries copyWith({ + Size? childSize, + }) { + return ScaleBoundaries( + allowOriginalScaleBeyondRange: _allowOriginalScaleBeyondRange, + minScale: _minScale, + maxScale: _maxScale, + initialScale: _initialScale, + viewportSize: viewportSize, + childSize: childSize ?? this.childSize, + ); + } + double _scaleForLevel(ScaleLevel level) { final factor = level.factor; switch (level.ref) { @@ -44,9 +60,17 @@ class ScaleBoundaries extends Equatable { double get originalScale => 1.0 / window.devicePixelRatio; - double get minScale => {_scaleForLevel(_minScale), originalScale, initialScale}.fold(double.infinity, min); + double get minScale => { + _scaleForLevel(_minScale), + _allowOriginalScaleBeyondRange ? originalScale : double.infinity, + initialScale, + }.fold(double.infinity, min); - double get maxScale => {_scaleForLevel(_maxScale), originalScale, initialScale}.fold(0, max); + double get maxScale => { + _scaleForLevel(_maxScale), + _allowOriginalScaleBeyondRange ? originalScale : double.negativeInfinity, + initialScale, + }.fold(0, max); double get initialScale => _scaleForLevel(_initialScale); diff --git a/lib/widgets/common/map/buttons/button.dart b/lib/widgets/common/map/buttons/button.dart index 7bd435ce8..60aa8c56f 100644 --- a/lib/widgets/common/map/buttons/button.dart +++ b/lib/widgets/common/map/buttons/button.dart @@ -20,7 +20,7 @@ class MapOverlayButton extends StatelessWidget { @override Widget build(BuildContext context) { - final blurred = settings.enableOverlayBlurEffect; + final blurred = settings.enableBlurEffect; return Selector>( selector: (context, v) => v.scale, builder: (context, scale, child) => ScaleTransition( diff --git a/lib/widgets/common/map/buttons/coordinate_filter.dart b/lib/widgets/common/map/buttons/coordinate_filter.dart index 3056ace31..5c984c4dc 100644 --- a/lib/widgets/common/map/buttons/coordinate_filter.dart +++ b/lib/widgets/common/map/buttons/coordinate_filter.dart @@ -57,7 +57,7 @@ class _OverlayCoordinateFilterChipState extends State stream; + final Stream stream; static const List scaleMeters = [ 25000000, 15000000, @@ -75,8 +74,7 @@ class ScaleLayer extends StatelessWidget { @override Widget build(BuildContext context) { - // ignore: prefer_void_to_null - return StreamBuilder( + return StreamBuilder( stream: stream, builder: (context, snapshot) { final center = map.center; diff --git a/lib/widgets/common/search/route.dart b/lib/widgets/common/search/route.dart index c2904d8ba..59448ace5 100644 --- a/lib/widgets/common/search/route.dart +++ b/lib/widgets/common/search/route.dart @@ -40,6 +40,8 @@ class SearchPageRoute extends PageRoute { Animation secondaryAnimation, Widget child, ) { + // a simple fade is usually more fitting for a search page, + // instead of the `pageTransitionsTheme` used by the rest of the app return FadeTransition( opacity: animation, child: child, diff --git a/lib/widgets/dialogs/app_pick_dialog.dart b/lib/widgets/dialogs/app_pick_dialog.dart index 13a0e22cc..a48215e94 100644 --- a/lib/widgets/dialogs/app_pick_dialog.dart +++ b/lib/widgets/dialogs/app_pick_dialog.dart @@ -63,15 +63,16 @@ class _AppPickDialogState extends State { ValueListenableBuilder( valueListenable: _queryNotifier, builder: (context, query, child) { + final upQuery = query.toUpperCase().trim(); final visiblePackages = packages.where((package) { return { package.packageName, package.currentLabel, package.englishLabel, ...package.potentialDirs, - }.any((v) => v != null && v.toLowerCase().contains(query.toLowerCase())); + }.any((v) => v != null && v.toUpperCase().contains(upQuery)); }).toList(); - final showNoneOption = query.isEmpty; + final showNoneOption = upQuery.isEmpty; final itemCount = visiblePackages.length + (showNoneOption ? 1 : 0); return Expanded( child: ListView.builder( diff --git a/lib/widgets/filter_grids/common/action_delegates/chip_set.dart b/lib/widgets/filter_grids/common/action_delegates/chip_set.dart index f6c0b626b..45bbc0831 100644 --- a/lib/widgets/filter_grids/common/action_delegates/chip_set.dart +++ b/lib/widgets/filter_grids/common/action_delegates/chip_set.dart @@ -22,6 +22,7 @@ import 'package:aves/widgets/dialogs/tile_view_dialog.dart'; import 'package:aves/widgets/map/map_page.dart'; import 'package:aves/widgets/search/search_delegate.dart'; import 'package:aves/widgets/stats/stats_page.dart'; +import 'package:aves/widgets/viewer/slideshow_page.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; @@ -65,6 +66,7 @@ abstract class ChipSetActionDelegate with FeedbackMi return false; // browsing or selecting case ChipSetAction.map: + case ChipSetAction.slideshow: case ChipSetAction.stats: return appMode == AppMode.main; // selecting (single/multiple filters) @@ -106,6 +108,7 @@ abstract class ChipSetActionDelegate with FeedbackMi return true; // browsing or selecting case ChipSetAction.map: + case ChipSetAction.slideshow: case ChipSetAction.stats: return (!isSelecting && hasItems) || (isSelecting && hasSelection); // selecting (single/multiple filters) @@ -146,6 +149,9 @@ abstract class ChipSetActionDelegate with FeedbackMi case ChipSetAction.map: _goToMap(context, filters); break; + case ChipSetAction.slideshow: + _goToSlideshow(context, filters); + break; case ChipSetAction.stats: _goToStats(context, filters); break; @@ -227,6 +233,23 @@ abstract class ChipSetActionDelegate with FeedbackMi ); } + void _goToSlideshow(BuildContext context, Set filters) { + Navigator.push( + context, + MaterialPageRoute( + settings: const RouteSettings(name: SlideshowPage.routeName), + builder: (context) { + return SlideshowPage( + collection: CollectionLens( + source: context.read(), + fixedSelection: _selectedEntries(context, filters).toList(), + ), + ); + }, + ), + ); + } + void _goToStats(BuildContext context, Set filters) { Navigator.push( context, diff --git a/lib/widgets/filter_grids/common/app_bar.dart b/lib/widgets/filter_grids/common/app_bar.dart index 108a0cfd9..0edf0b257 100644 --- a/lib/widgets/filter_grids/common/app_bar.dart +++ b/lib/widgets/filter_grids/common/app_bar.dart @@ -115,9 +115,14 @@ class _FilterGridAppBarState extends State>, int>( - selector: (context, selection) => selection.selectedItems.length, - builder: (context, count, child) => Text(count == 0 ? l10n.collectionSelectPageTitle : l10n.itemCount(count)), + return Selector>?, int>( + selector: (context, selection) => selection?.selectedItems.length ?? 0, + builder: (context, count, child) => Text( + count == 0 ? l10n.collectionSelectPageTitle : l10n.itemCount(count), + softWrap: false, + overflow: TextOverflow.fade, + maxLines: 1, + ), ); } else { final appMode = context.watch>().value; diff --git a/lib/widgets/filter_grids/common/filter_tile.dart b/lib/widgets/filter_grids/common/filter_tile.dart index 34ece3225..60a165d51 100644 --- a/lib/widgets/filter_grids/common/filter_tile.dart +++ b/lib/widgets/filter_grids/common/filter_tile.dart @@ -63,6 +63,8 @@ class _InteractiveFilterTileState extends State(context, filter); break; case AppMode.pickMediaInternal: + case AppMode.setWallpaper: + case AppMode.slideshow: case AppMode.view: break; } @@ -90,6 +92,7 @@ class _InteractiveFilterTileState extends State _heroTypeOverride = HeroType.always); } WidgetsBinding.instance.addPostFrameCallback((_) { + if (!mounted) return; Navigator.push( context, MaterialPageRoute( diff --git a/lib/widgets/home_page.dart b/lib/widgets/home_page.dart index 24472508f..039cea723 100644 --- a/lib/widgets/home_page.dart +++ b/lib/widgets/home_page.dart @@ -22,9 +22,9 @@ import 'package:aves/widgets/common/search/route.dart'; import 'package:aves/widgets/filter_grids/albums_page.dart'; import 'package:aves/widgets/search/search_delegate.dart'; import 'package:aves/widgets/viewer/entry_viewer_page.dart'; +import 'package:aves/widgets/wallpaper_page.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:provider/provider.dart'; @@ -48,6 +48,11 @@ class _HomePageState extends State { String? _shortcutRouteName, _shortcutSearchQuery; Set? _shortcutFilters; + static const actionPick = 'pick'; + static const actionSearch = 'search'; + static const actionSetWallpaper = 'set_wallpaper'; + static const actionView = 'view'; + static const allowedShortcutRoutes = [ CollectionPage.routeName, AlbumListPage.routeName, @@ -66,31 +71,30 @@ class _HomePageState extends State { Future _setup() async { final stopwatch = Stopwatch()..start(); - final permissions = await [ + // do not check whether permission was granted, + // as some app stores hide in some countries + // apps that force quit on permission denial + await [ Permission.storage, // to access media with unredacted metadata with scoped storage (Android 10+) Permission.accessMediaLocation, ].request(); - if (permissions[Permission.storage] != PermissionStatus.granted) { - unawaited(SystemNavigator.pop()); - return; - } - - await androidFileUtils.init(); - if (settings.isInstalledAppAccessAllowed) { - // TODO TLAD transition code (it's unset in v1.5.4), remove in a later release - settings.isInstalledAppAccessAllowed = settings.isInstalledAppAccessAllowed; - - unawaited(androidFileUtils.initAppNames()); - } var appMode = AppMode.main; final intentData = widget.intentData ?? await ViewerService.getIntentData(); + final intentAction = intentData['action']; + + if (intentAction != actionSetWallpaper) { + await androidFileUtils.init(); + if (settings.isInstalledAppAccessAllowed) { + unawaited(androidFileUtils.initAppNames()); + } + } + if (intentData.isNotEmpty) { - final action = intentData['action']; await reportService.log('Intent data=$intentData'); - switch (action) { - case 'view': + switch (intentAction) { + case actionView: _viewerEntry = await _initViewerEntry( uri: intentData['uri'], mimeType: intentData['mimeType'], @@ -99,7 +103,7 @@ class _HomePageState extends State { appMode = AppMode.view; } break; - case 'pick': + case actionPick: // TODO TLAD apply pick mimetype(s) // some apps define multiple types, separated by a space (maybe other signs too, like `,` `;`?) String? pickMimeTypes = intentData['mimeType']; @@ -107,10 +111,17 @@ class _HomePageState extends State { debugPrint('pick mimeType=$pickMimeTypes multiple=$multiple'); appMode = multiple ? AppMode.pickMultipleMediaExternal : AppMode.pickSingleMediaExternal; break; - case 'search': + case actionSearch: _shortcutRouteName = CollectionSearchDelegate.pageRouteName; _shortcutSearchQuery = intentData['query']; break; + case actionSetWallpaper: + appMode = AppMode.setWallpaper; + _viewerEntry = await _initViewerEntry( + uri: intentData['uri'], + mimeType: intentData['mimeType'], + ); + break; default: // do not use 'route' as extra key, as the Flutter framework acts on it final extraRoute = intentData['page']; @@ -150,6 +161,8 @@ class _HomePageState extends State { break; case AppMode.pickMediaInternal: case AppMode.pickFilterInternal: + case AppMode.setWallpaper: + case AppMode.slideshow: break; } @@ -180,6 +193,17 @@ class _HomePageState extends State { } Future _getRedirectRoute(AppMode appMode) async { + if (appMode == AppMode.setWallpaper) { + return DirectMaterialPageRoute( + settings: const RouteSettings(name: WallpaperPage.routeName), + builder: (_) { + return WallpaperPage( + entry: _viewerEntry, + ); + }, + ); + } + if (appMode == AppMode.view) { AvesEntry viewerEntry = _viewerEntry!; CollectionLens? collection; @@ -263,7 +287,7 @@ class _HomePageState extends State { default: return DirectMaterialPageRoute( settings: const RouteSettings(name: CollectionPage.routeName), - builder: (_) => CollectionPage( + builder: (context) => CollectionPage( source: source, filters: filters, ), diff --git a/lib/widgets/map/map_page.dart b/lib/widgets/map/map_page.dart index 955cddb05..e8ba5b67f 100644 --- a/lib/widgets/map/map_page.dart +++ b/lib/widgets/map/map_page.dart @@ -414,12 +414,10 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin context, MaterialPageRoute( settings: const RouteSettings(name: CollectionPage.routeName), - builder: (context) { - return CollectionPage( - source: openingCollection.source, - filters: {...openingCollection.filters, filter}, - ); - }, + builder: (context) => CollectionPage( + source: openingCollection.source, + filters: {...openingCollection.filters, filter}, + ), ), (route) => false, ); diff --git a/lib/widgets/navigation/drawer/app_drawer.dart b/lib/widgets/navigation/drawer/app_drawer.dart index 559593d15..b2aeaed5f 100644 --- a/lib/widgets/navigation/drawer/app_drawer.dart +++ b/lib/widgets/navigation/drawer/app_drawer.dart @@ -129,7 +129,7 @@ class _AppDrawerState extends State { spacing: 16, crossAxisAlignment: WrapCrossAlignment.center, children: [ - const AvesLogo(size: 64), + const AvesLogo(size: 56), Text( context.l10n.appName, style: const TextStyle( diff --git a/lib/widgets/settings/display/display.dart b/lib/widgets/settings/display/display.dart index 909b7c689..69a4e36f3 100644 --- a/lib/widgets/settings/display/display.dart +++ b/lib/widgets/settings/display/display.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:aves/model/device.dart'; import 'package:aves/model/settings/enums/display_refresh_rate_mode.dart'; import 'package:aves/model/settings/enums/enums.dart'; import 'package:aves/model/settings/enums/theme_brightness.dart'; @@ -30,8 +31,9 @@ class DisplaySection extends SettingsSection { FutureOr> tiles(BuildContext context) => [ SettingsTileDisplayThemeBrightness(), SettingsTileDisplayThemeColorMode(), - SettingsTileDisplayDisplayRefreshRateMode(), + if (device.isDynamicColorAvailable) SettingsTileDisplayEnableDynamicColor(), SettingsTileDisplayEnableBlurEffect(), + SettingsTileDisplayDisplayRefreshRateMode(), ]; } @@ -62,6 +64,30 @@ class SettingsTileDisplayThemeColorMode extends SettingsTile { ); } +class SettingsTileDisplayEnableDynamicColor extends SettingsTile { + @override + String title(BuildContext context) => context.l10n.settingsThemeEnableDynamicColor; + + @override + Widget build(BuildContext context) => SettingsSwitchListTile( + selector: (context, s) => s.enableDynamicColor, + onChanged: (v) => settings.enableDynamicColor = v, + title: title(context), + ); +} + +class SettingsTileDisplayEnableBlurEffect extends SettingsTile { + @override + String title(BuildContext context) => context.l10n.settingsViewerEnableOverlayBlurEffect; + + @override + Widget build(BuildContext context) => SettingsSwitchListTile( + selector: (context, s) => s.enableBlurEffect, + onChanged: (v) => settings.enableBlurEffect = v, + title: title(context), + ); +} + class SettingsTileDisplayDisplayRefreshRateMode extends SettingsTile { @override String title(BuildContext context) => context.l10n.settingsDisplayRefreshRateModeTile; @@ -76,15 +102,3 @@ class SettingsTileDisplayDisplayRefreshRateMode extends SettingsTile { dialogTitle: context.l10n.settingsDisplayRefreshRateModeTitle, ); } - -class SettingsTileDisplayEnableBlurEffect extends SettingsTile { - @override - String title(BuildContext context) => context.l10n.settingsViewerEnableOverlayBlurEffect; - - @override - Widget build(BuildContext context) => SettingsSwitchListTile( - selector: (context, s) => s.enableOverlayBlurEffect, - onChanged: (v) => settings.enableOverlayBlurEffect = v, - title: title(context), - ); -} diff --git a/lib/widgets/settings/language/locale.dart b/lib/widgets/settings/language/locale.dart index 882c54f9c..b3ffbb93c 100644 --- a/lib/widgets/settings/language/locale.dart +++ b/lib/widgets/settings/language/locale.dart @@ -3,6 +3,7 @@ import 'dart:collection'; import 'package:aves/l10n/l10n.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/theme/durations.dart'; +import 'package:aves/widgets/common/basic/query_bar.dart'; import 'package:aves/widgets/common/basic/reselectable_radio_list_tile.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/providers/media_query_data_provider.dart'; @@ -64,6 +65,7 @@ class LocaleSelectionPage extends StatefulWidget { class _LocaleSelectionPageState extends State { late Locale _selectedValue; + final ValueNotifier _queryNotifier = ValueNotifier(''); @override void initState() { @@ -79,25 +81,41 @@ class _LocaleSelectionPageState extends State { title: Text(context.l10n.settingsLanguage), ), body: SafeArea( - child: ListView( - children: _getLocaleOptions(context).entries.map((kv) { - final value = kv.key; - final title = kv.value; - return ReselectableRadioListTile( - // key is expected by test driver - key: Key(value.toString()), - value: value, - groupValue: _selectedValue, - onChanged: (v) => Navigator.pop(context, v), - reselectable: true, - title: Text( - title, - softWrap: false, - overflow: TextOverflow.fade, - maxLines: 1, - ), + child: ValueListenableBuilder( + valueListenable: _queryNotifier, + builder: (context, query, child) { + final upQuery = query.toUpperCase().trim(); + return ListView( + children: [ + QueryBar( + queryNotifier: _queryNotifier, + leadingPadding: const EdgeInsetsDirectional.only(start: 24, end: 8), + ), + ..._getLocaleOptions(context).entries.where((kv) { + if (upQuery.isEmpty) return true; + final title = kv.value; + return title.toUpperCase().contains(upQuery); + }).map((kv) { + final value = kv.key; + final title = kv.value; + return ReselectableRadioListTile( + // key is expected by test driver + key: Key(value.toString()), + value: value, + groupValue: _selectedValue, + onChanged: (v) => Navigator.pop(context, v), + reselectable: true, + title: Text( + title, + softWrap: false, + overflow: TextOverflow.fade, + maxLines: 1, + ), + ); + }), + ], ); - }).toList(), + }, ), ), ), diff --git a/lib/widgets/settings/language/locales.dart b/lib/widgets/settings/language/locales.dart index 1103c5e7f..24e26b128 100644 --- a/lib/widgets/settings/language/locales.dart +++ b/lib/widgets/settings/language/locales.dart @@ -12,6 +12,7 @@ class SupportedLocales { 'ko': '한국어', 'pt': 'Português (Brasil)', 'ru': 'Русский', + 'tr': 'Türkçe', 'zh': '简体中文', }; } diff --git a/lib/widgets/settings/viewer/overlay.dart b/lib/widgets/settings/viewer/overlay.dart index 431050a89..b41f34d6c 100644 --- a/lib/widgets/settings/viewer/overlay.dart +++ b/lib/widgets/settings/viewer/overlay.dart @@ -6,7 +6,7 @@ import 'package:provider/provider.dart'; import 'package:tuple/tuple.dart'; class ViewerOverlayPage extends StatelessWidget { - static const routeName = '/settings/viewer_overlay'; + static const routeName = '/settings/viewer/overlay'; const ViewerOverlayPage({super.key}); diff --git a/lib/widgets/settings/viewer/slideshow.dart b/lib/widgets/settings/viewer/slideshow.dart new file mode 100644 index 000000000..71bb3d811 --- /dev/null +++ b/lib/widgets/settings/viewer/slideshow.dart @@ -0,0 +1,63 @@ +import 'package:aves/model/settings/enums/enums.dart'; +import 'package:aves/model/settings/enums/slideshow_interval.dart'; +import 'package:aves/model/settings/enums/slideshow_video_playback.dart'; +import 'package:aves/model/settings/enums/viewer_transition.dart'; +import 'package:aves/model/settings/settings.dart'; +import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:aves/widgets/settings/common/tiles.dart'; +import 'package:flutter/material.dart'; + +class ViewerSlideshowPage extends StatelessWidget { + static const routeName = '/settings/viewer/slideshow'; + + const ViewerSlideshowPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(context.l10n.settingsViewerSlideshowTitle), + ), + body: SafeArea( + child: ListView( + children: [ + SettingsSwitchListTile( + selector: (context, s) => s.slideshowRepeat, + onChanged: (v) => settings.slideshowRepeat = v, + title: context.l10n.settingsSlideshowRepeat, + ), + SettingsSwitchListTile( + selector: (context, s) => s.slideshowShuffle, + onChanged: (v) => settings.slideshowShuffle = v, + title: context.l10n.settingsSlideshowShuffle, + ), + SettingsSelectionListTile( + values: ViewerTransition.values, + getName: (context, v) => v.getName(context), + selector: (context, s) => s.slideshowTransition, + onSelection: (v) => settings.slideshowTransition = v, + tileTitle: context.l10n.settingsSlideshowTransitionTile, + dialogTitle: context.l10n.settingsSlideshowTransitionTitle, + ), + SettingsSelectionListTile( + values: SlideshowInterval.values, + getName: (context, v) => v.getName(context), + selector: (context, s) => s.slideshowInterval, + onSelection: (v) => settings.slideshowInterval = v, + tileTitle: context.l10n.settingsSlideshowIntervalTile, + dialogTitle: context.l10n.settingsSlideshowIntervalTitle, + ), + SettingsSelectionListTile( + values: SlideshowVideoPlayback.values, + getName: (context, v) => v.getName(context), + selector: (context, s) => s.slideshowVideoPlayback, + onSelection: (v) => settings.slideshowVideoPlayback = v, + tileTitle: context.l10n.settingsSlideshowVideoPlaybackTile, + dialogTitle: context.l10n.settingsSlideshowVideoPlaybackTitle, + ), + ], + ), + ), + ); + } +} diff --git a/lib/widgets/settings/viewer/viewer.dart b/lib/widgets/settings/viewer/viewer.dart index 35176bd92..a59c9e19a 100644 --- a/lib/widgets/settings/viewer/viewer.dart +++ b/lib/widgets/settings/viewer/viewer.dart @@ -11,6 +11,7 @@ import 'package:aves/widgets/settings/common/tiles.dart'; import 'package:aves/widgets/settings/settings_definition.dart'; import 'package:aves/widgets/settings/viewer/entry_background.dart'; import 'package:aves/widgets/settings/viewer/overlay.dart'; +import 'package:aves/widgets/settings/viewer/slideshow.dart'; import 'package:aves/widgets/settings/viewer/viewer_actions_editor.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -34,6 +35,7 @@ class ViewerSection extends SettingsSection { return [ SettingsTileViewerQuickActions(), SettingsTileViewerOverlay(), + SettingsTileViewerSlideshow(), if (canSetCutoutMode) SettingsTileViewerCutoutMode(), SettingsTileViewerMaxBrightness(), SettingsTileViewerMotionPhotoAutoPlay(), @@ -66,6 +68,18 @@ class SettingsTileViewerOverlay extends SettingsTile { ); } +class SettingsTileViewerSlideshow extends SettingsTile { + @override + String title(BuildContext context) => context.l10n.settingsViewerSlideshowTile; + + @override + Widget build(BuildContext context) => SettingsSubPageTile( + title: title(context), + routeName: ViewerSlideshowPage.routeName, + builder: (context) => const ViewerSlideshowPage(), + ); +} + class SettingsTileViewerCutoutMode extends SettingsTile { @override String title(BuildContext context) => context.l10n.settingsViewerUseCutout; diff --git a/lib/widgets/settings/viewer/viewer_actions_editor.dart b/lib/widgets/settings/viewer/viewer_actions_editor.dart index 795a2eddd..d98fbc985 100644 --- a/lib/widgets/settings/viewer/viewer_actions_editor.dart +++ b/lib/widgets/settings/viewer/viewer_actions_editor.dart @@ -5,7 +5,7 @@ import 'package:aves/widgets/settings/common/quick_actions/editor_page.dart'; import 'package:flutter/material.dart'; class ViewerActionEditorPage extends StatelessWidget { - static const routeName = '/settings/viewer_actions'; + static const routeName = '/settings/viewer/actions'; const ViewerActionEditorPage({super.key}); diff --git a/lib/widgets/viewer/action/entry_action_delegate.dart b/lib/widgets/viewer/action/entry_action_delegate.dart index b5b477cad..a874fb942 100644 --- a/lib/widgets/viewer/action/entry_action_delegate.dart +++ b/lib/widgets/viewer/action/entry_action_delegate.dart @@ -50,6 +50,9 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix void onActionSelected(BuildContext context, EntryAction action) { switch (action) { + case EntryAction.info: + ShowInfoNotification().dispatch(context); + break; case EntryAction.addShortcut: _addShortcut(context); break; diff --git a/lib/widgets/viewer/controller.dart b/lib/widgets/viewer/controller.dart new file mode 100644 index 000000000..5b102e906 --- /dev/null +++ b/lib/widgets/viewer/controller.dart @@ -0,0 +1,119 @@ +import 'dart:async'; + +import 'package:aves/model/entry.dart'; +import 'package:aves/model/settings/enums/enums.dart'; +import 'package:flutter/widgets.dart'; + +class ViewerController { + final ValueNotifier entryNotifier = ValueNotifier(null); + final ViewerTransition transition; + final Duration? autopilotInterval; + final bool repeat; + + late final ValueNotifier _autopilotNotifier; + Timer? _playTimer; + final StreamController _streamController = StreamController.broadcast(); + + Stream get _events => _streamController.stream; + + Stream get showNextCommands => _events.where((event) => event is ViewerShowNextEvent).cast(); + + Stream get overlayCommands => _events.where((event) => event is ViewerOverlayToggleEvent).cast(); + + bool get autopilot => _autopilotNotifier.value; + + set autopilot(bool enabled) => _autopilotNotifier.value = enabled; + + ViewerController({ + this.transition = ViewerTransition.parallax, + this.repeat = false, + bool autopilot = false, + this.autopilotInterval, + }) { + _autopilotNotifier = ValueNotifier(autopilot); + _autopilotNotifier.addListener(_onAutopilotChange); + _onAutopilotChange(); + } + + void dispose() { + _autopilotNotifier.removeListener(_onAutopilotChange); + _stopPlayTimer(); + _streamController.close(); + } + + void _stopPlayTimer() { + _playTimer?.cancel(); + } + + void _onAutopilotChange() { + _stopPlayTimer(); + if (autopilot && autopilotInterval != null) { + _playTimer = Timer.periodic(autopilotInterval!, (_) => _streamController.add(ViewerShowNextEvent())); + _streamController.add(const ViewerOverlayToggleEvent(visible: false)); + } + } +} + +@immutable +class ViewerShowNextEvent {} + +@immutable +class ViewerOverlayToggleEvent { + final bool? visible; + + const ViewerOverlayToggleEvent({required this.visible}); +} + +class PageTransitionEffects { + static TransitionBuilder fade( + PageController pageController, + int index, { + required bool zoomIn, + }) => + (context, child) { + double opacity = 0; + double dx = 0; + double scale = 1; + if (pageController.hasClients && pageController.position.haveDimensions) { + final position = (pageController.page! - index).clamp(-1.0, 1.0); + final width = pageController.position.viewportDimension; + opacity = (1 - position.abs()).clamp(0, 1); + dx = position * width; + if (zoomIn) { + scale = 1 + position; + } + } + return Opacity( + opacity: opacity, + child: Transform.translate( + offset: Offset(dx, 0), + child: Transform.scale( + scale: scale, + child: child, + ), + ), + ); + }; + + static TransitionBuilder slide( + PageController pageController, + int index, { + required bool parallax, + }) => + (context, child) { + double dx = 0; + if (pageController.hasClients && pageController.position.haveDimensions) { + final position = (pageController.page! - index).clamp(-1.0, 1.0); + final width = pageController.position.viewportDimension; + if (parallax) { + dx = position * width / 2; + } + } + return ClipRect( + child: Transform.translate( + offset: Offset(dx, 0), + child: child, + ), + ); + }; +} diff --git a/lib/widgets/viewer/entry_horizontal_pager.dart b/lib/widgets/viewer/entry_horizontal_pager.dart index 1c27b0c03..a25307be6 100644 --- a/lib/widgets/viewer/entry_horizontal_pager.dart +++ b/lib/widgets/viewer/entry_horizontal_pager.dart @@ -1,9 +1,11 @@ import 'package:aves/model/entry.dart'; import 'package:aves/model/settings/enums/accessibility_animations.dart'; +import 'package:aves/model/settings/enums/viewer_transition.dart'; import 'package:aves/model/settings/settings.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/viewer/controller.dart'; import 'package:aves/widgets/viewer/multipage/conductor.dart'; import 'package:aves/widgets/viewer/page_entry_builder.dart'; import 'package:aves/widgets/viewer/visual/entry_page_view.dart'; @@ -13,6 +15,7 @@ import 'package:provider/provider.dart'; class MultiEntryScroller extends StatefulWidget { final CollectionLens collection; + final ViewerController viewerController; final PageController pageController; final ValueChanged onPageChanged; final void Function(AvesEntry mainEntry, AvesEntry? pageEntry) onViewDisposed; @@ -20,6 +23,7 @@ class MultiEntryScroller extends StatefulWidget { const MultiEntryScroller({ super.key, required this.collection, + required this.viewerController, required this.pageController, required this.onPageChanged, required this.onViewDisposed, @@ -32,6 +36,8 @@ class MultiEntryScroller extends StatefulWidget { class _MultiEntryScrollerState extends State with AutomaticKeepAliveClientMixin { List get entries => widget.collection.sortedEntries; + ViewerController get viewerController => widget.viewerController; + PageController get pageController => widget.pageController; @override @@ -51,45 +57,29 @@ class _MultiEntryScrollerState extends State with AutomaticK ), onPageChanged: widget.onPageChanged, itemBuilder: (context, index) { - final mainEntry = entries[index]; + final mainEntry = entries[index % entries.length]; - var child = mainEntry.isMultiPage + final child = mainEntry.isMultiPage ? PageEntryBuilder( multiPageController: context.read().getController(mainEntry), builder: (pageEntry) => _buildViewer(mainEntry, pageEntry: pageEntry), ) : _buildViewer(mainEntry); - child = Selector( + return Selector( selector: (context, s) => s.accessibilityAnimations.animate, builder: (context, animate, child) { - return animate - ? AnimatedBuilder( - animation: pageController, - builder: (context, child) { - // parallax scrolling - double dx = 0; - if (pageController.hasClients && pageController.position.haveDimensions) { - final delta = pageController.page! - index; - dx = delta * pageController.position.viewportDimension / 2; - } - return Transform.translate( - offset: Offset(dx, 0), - child: child, - ); - }, - child: child, - ) - : child!; + if (!animate) return child!; + return AnimatedBuilder( + animation: pageController, + builder: viewerController.transition.builder(pageController, index), + child: child, + ); }, child: child, ); - - return ClipRect( - child: child, - ); }, - itemCount: entries.length, + itemCount: viewerController.repeat ? null : entries.length, ), ); } diff --git a/lib/widgets/viewer/entry_vertical_pager.dart b/lib/widgets/viewer/entry_vertical_pager.dart index ed08f48ae..b7e310155 100644 --- a/lib/widgets/viewer/entry_vertical_pager.dart +++ b/lib/widgets/viewer/entry_vertical_pager.dart @@ -2,14 +2,17 @@ import 'dart:async'; import 'dart:math'; import 'dart:ui'; +import 'package:aves/app_mode.dart'; import 'package:aves/model/entry.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/theme/durations.dart'; import 'package:aves/widgets/common/magnifier/pan/scroll_physics.dart'; +import 'package:aves/widgets/viewer/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/notifications.dart'; +import 'package:aves/widgets/viewer/overlay/notifications.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -19,6 +22,7 @@ import 'package:screen_brightness/screen_brightness.dart'; class ViewerVerticalPageView extends StatefulWidget { final CollectionLens? collection; final ValueNotifier entryNotifier; + final ViewerController viewerController; final PageController horizontalPager, verticalPager; final void Function(int page) onVerticalPageChanged, onHorizontalPageChanged; final VoidCallback onImagePageRequested; @@ -28,6 +32,7 @@ class ViewerVerticalPageView extends StatefulWidget { super.key, required this.collection, required this.entryNotifier, + required this.viewerController, required this.verticalPager, required this.horizontalPager, required this.onVerticalPageChanged, @@ -41,6 +46,7 @@ class ViewerVerticalPageView extends StatefulWidget { } class _ViewerVerticalPageViewState extends State { + final List _subscriptions = []; final ValueNotifier _backgroundOpacityNotifier = ValueNotifier(1); final ValueNotifier _isVerticallyScrollingNotifier = ValueNotifier(false); Timer? _verticalScrollMonitoringTimer; @@ -80,12 +86,21 @@ class _ViewerVerticalPageViewState extends State { } void _registerWidget(ViewerVerticalPageView widget) { + _subscriptions.add(widget.viewerController.showNextCommands.listen((event) { + _goToHorizontalPage(1, animate: true); + })); + _subscriptions.add(widget.viewerController.overlayCommands.listen((event) { + ToggleOverlayNotification(visible: event.visible).dispatch(context); + })); widget.verticalPager.addListener(_onVerticalPageControllerChanged); widget.entryNotifier.addListener(_onEntryChanged); if (_oldEntry != entry) _onEntryChanged(); } void _unregisterWidget(ViewerVerticalPageView widget) { + _subscriptions + ..forEach((sub) => sub.cancel()) + ..clear(); widget.verticalPager.removeListener(_onVerticalPageControllerChanged); widget.entryNotifier.removeListener(_onEntryChanged); _oldEntry?.imageChangeNotifier.removeListener(_onImageChanged); @@ -96,34 +111,36 @@ class _ViewerVerticalPageViewState extends State { // fake page for opacity transition between collection and viewer const transitionPage = SizedBox(); - final imagePage = _buildImagePage(); - - final infoPage = NotificationListener( - onNotification: (notification) { - widget.onImagePageRequested(); - return true; - }, - child: AnimatedBuilder( - animation: widget.verticalPager, - builder: (context, child) { - return Visibility( - visible: widget.verticalPager.page! > 1, - child: child!, - ); - }, - child: InfoPage( - collection: collection, - entryNotifier: widget.entryNotifier, - isScrollingNotifier: _isVerticallyScrollingNotifier, - ), - ), - ); - final pages = [ transitionPage, - imagePage, - infoPage, + _buildImagePage(), ]; + + if (context.read>().value != AppMode.slideshow) { + final infoPage = NotificationListener( + onNotification: (notification) { + widget.onImagePageRequested(); + return true; + }, + child: AnimatedBuilder( + animation: widget.verticalPager, + builder: (context, child) { + return Visibility( + visible: widget.verticalPager.page! > 1, + child: child!, + ); + }, + child: InfoPage( + collection: collection, + entryNotifier: widget.entryNotifier, + isScrollingNotifier: _isVerticallyScrollingNotifier, + ), + ), + ); + + pages.add(infoPage); + } + return ValueListenableBuilder( valueListenable: _backgroundOpacityNotifier, builder: (context, backgroundOpacity, child) { @@ -155,6 +172,7 @@ class _ViewerVerticalPageViewState extends State { if (hasCollection) { child = MultiEntryScroller( collection: collection!, + viewerController: widget.viewerController, pageController: widget.horizontalPager, onPageChanged: widget.onHorizontalPageChanged, onViewDisposed: widget.onViewDisposed, @@ -179,8 +197,8 @@ class _ViewerVerticalPageViewState extends State { autofocus: true, shortcuts: shortcuts, actions: { - ShowPreviousIntent: CallbackAction(onInvoke: (intent) => _jumpHorizontalPage(-1)), - ShowNextIntent: CallbackAction(onInvoke: (intent) => _jumpHorizontalPage(1)), + ShowPreviousIntent: CallbackAction(onInvoke: (intent) => _goToHorizontalPage(-1, animate: false)), + ShowNextIntent: CallbackAction(onInvoke: (intent) => _goToHorizontalPage(1, animate: false)), LeaveIntent: CallbackAction(onInvoke: (intent) => Navigator.pop(context)), ShowInfoIntent: CallbackAction(onInvoke: (intent) => ShowInfoNotification().dispatch(context)), }, @@ -190,13 +208,24 @@ class _ViewerVerticalPageViewState extends State { return const SizedBox(); } - void _jumpHorizontalPage(int delta) { + void _goToHorizontalPage(int delta, {required bool animate}) { final pageController = widget.horizontalPager; final page = pageController.page?.round(); final _collection = collection; if (page != null && _collection != null) { - final target = (page + delta).clamp(0, _collection.entryCount - 1); - pageController.jumpToPage(target); + var target = page + delta; + if (!widget.viewerController.repeat) { + target = target.clamp(0, _collection.entryCount - 1); + } + if (animate) { + pageController.animateToPage( + target, + duration: const Duration(seconds: 1), + curve: Curves.easeInOutCubic, + ); + } else { + pageController.jumpToPage(target); + } } } diff --git a/lib/widgets/viewer/entry_viewer_page.dart b/lib/widgets/viewer/entry_viewer_page.dart index 814e1b703..c990971b5 100644 --- a/lib/widgets/viewer/entry_viewer_page.dart +++ b/lib/widgets/viewer/entry_viewer_page.dart @@ -2,6 +2,7 @@ import 'package:aves/app_mode.dart'; 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/controller.dart'; import 'package:aves/widgets/viewer/entry_viewer_stack.dart'; import 'package:aves/widgets/viewer/multipage/conductor.dart'; import 'package:aves/widgets/viewer/overlay/bottom.dart'; @@ -10,7 +11,7 @@ import 'package:aves/widgets/viewer/visual/conductor.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -class EntryViewerPage extends StatelessWidget { +class EntryViewerPage extends StatefulWidget { static const routeName = '/viewer'; final CollectionLens? collection; @@ -22,6 +23,23 @@ class EntryViewerPage extends StatelessWidget { required this.initialEntry, }); + @override + State createState() => _EntryViewerPageState(); + + static EdgeInsets snackBarMargin(BuildContext context) { + return EdgeInsets.only(bottom: ViewerBottomOverlay.actionSafeHeight(context)); + } +} + +class _EntryViewerPageState extends State { + final ViewerController _viewerController = ViewerController(); + + @override + void dispose() { + _viewerController.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { return MediaQueryDataProvider( @@ -30,8 +48,9 @@ class EntryViewerPage extends StatelessWidget { child: VideoConductorProvider( child: MultiPageConductorProvider( child: EntryViewerStack( - collection: collection, - initialEntry: initialEntry, + collection: widget.collection, + initialEntry: widget.initialEntry, + viewerController: _viewerController, ), ), ), @@ -45,10 +64,6 @@ class EntryViewerPage extends StatelessWidget { ), ); } - - static EdgeInsets snackBarMargin(BuildContext context) { - return EdgeInsets.only(bottom: ViewerBottomOverlay.actionSafeHeight(context)); - } } class ViewStateConductorProvider extends StatelessWidget { diff --git a/lib/widgets/viewer/entry_viewer_stack.dart b/lib/widgets/viewer/entry_viewer_stack.dart index ed16e8c82..12b9eee24 100644 --- a/lib/widgets/viewer/entry_viewer_stack.dart +++ b/lib/widgets/viewer/entry_viewer_stack.dart @@ -16,14 +16,15 @@ import 'package:aves/widgets/aves_app.dart'; import 'package:aves/widgets/collection/collection_page.dart'; import 'package:aves/widgets/common/action_mixins/feedback.dart'; import 'package:aves/widgets/common/basic/insets.dart'; +import 'package:aves/widgets/viewer/controller.dart'; import 'package:aves/widgets/viewer/entry_vertical_pager.dart'; import 'package:aves/widgets/viewer/hero.dart'; import 'package:aves/widgets/viewer/multipage/conductor.dart'; -import 'package:aves/widgets/viewer/multipage/controller.dart'; import 'package:aves/widgets/viewer/notifications.dart'; import 'package:aves/widgets/viewer/overlay/bottom.dart'; import 'package:aves/widgets/viewer/overlay/notifications.dart'; import 'package:aves/widgets/viewer/overlay/panorama.dart'; +import 'package:aves/widgets/viewer/overlay/slideshow_buttons.dart'; import 'package:aves/widgets/viewer/overlay/top.dart'; import 'package:aves/widgets/viewer/overlay/video/video.dart'; import 'package:aves/widgets/viewer/page_entry_builder.dart'; @@ -31,6 +32,7 @@ import 'package:aves/widgets/viewer/video/conductor.dart'; import 'package:aves/widgets/viewer/video/controller.dart'; import 'package:aves/widgets/viewer/video_action_delegate.dart'; import 'package:aves/widgets/viewer/visual/conductor.dart'; +import 'package:aves/widgets/viewer/visual/controller_mixin.dart'; import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -42,20 +44,21 @@ import 'package:screen_brightness/screen_brightness.dart'; class EntryViewerStack extends StatefulWidget { final CollectionLens? collection; final AvesEntry initialEntry; + final ViewerController viewerController; const EntryViewerStack({ super.key, this.collection, required this.initialEntry, + required this.viewerController, }); @override State createState() => _EntryViewerStackState(); } -class _EntryViewerStackState extends State with FeedbackMixin, SingleTickerProviderStateMixin, WidgetsBindingObserver { - final ValueNotifier _entryNotifier = ValueNotifier(null); - late int _currentHorizontalPage; +class _EntryViewerStackState extends State with EntryViewControllerMixin, FeedbackMixin, SingleTickerProviderStateMixin, WidgetsBindingObserver { + late int _currentEntryIndex; late ValueNotifier _currentVerticalPage; late PageController _horizontalPager, _verticalPager; final AChangeNotifier _verticalScrollNotifier = AChangeNotifier(); @@ -65,10 +68,14 @@ class _EntryViewerStackState extends State with FeedbackMixin, late Animation _overlayTopOffset; EdgeInsets? _frozenViewInsets, _frozenViewPadding; late VideoActionDelegate _videoActionDelegate; - final Map Function()> _multiPageControllerPageListeners = {}; final ValueNotifier _heroInfoNotifier = ValueNotifier(null); bool _isEntryTracked = true; + @override + late final ValueNotifier entryNotifier; + + ViewerController get viewerController => widget.viewerController; + CollectionLens? get collection => widget.collection; bool get hasCollection => collection != null; @@ -102,10 +109,11 @@ class _EntryViewerStackState extends State with FeedbackMixin, final entry = entries.firstWhereOrNull((entry) => entry.id == initialEntry.id) ?? entries.firstOrNull; // opening hero, with viewer as target _heroInfoNotifier.value = HeroInfo(collection?.id, entry); - _entryNotifier.value = entry; - _currentHorizontalPage = max(0, entry != null ? entries.indexOf(entry) : -1); + entryNotifier = viewerController.entryNotifier; + entryNotifier.value = entry; + _currentEntryIndex = max(0, entry != null ? entries.indexOf(entry) : -1); _currentVerticalPage = ValueNotifier(imagePage); - _horizontalPager = PageController(initialPage: _currentHorizontalPage); + _horizontalPager = PageController(initialPage: _currentEntryIndex); _verticalPager = PageController(initialPage: _currentVerticalPage.value)..addListener(_onVerticalPageControllerChange); _overlayAnimationController = AnimationController( duration: context.read().viewerOverlayAnimation, @@ -125,12 +133,12 @@ class _EntryViewerStackState extends State with FeedbackMixin, parent: _overlayAnimationController, curve: Curves.easeOutQuad, )); - _overlayVisible.value = settings.showOverlayOnOpening; + _overlayVisible.value = settings.showOverlayOnOpening && !viewerController.autopilot; _overlayVisible.addListener(_onOverlayVisibleChange); _videoActionDelegate = VideoActionDelegate( collection: collection, ); - _initEntryControllers(entry); + initEntryControllers(entry); _registerWidget(widget); WidgetsBinding.instance.addObserver(this); WidgetsBinding.instance.addPostFrameCallback((_) => _initOverlay()); @@ -145,7 +153,7 @@ class _EntryViewerStackState extends State with FeedbackMixin, @override void dispose() { - _cleanEntryControllers(_entryNotifier.value); + cleanEntryControllers(entryNotifier.value); _videoActionDelegate.dispose(); _overlayAnimationController.dispose(); _overlayVisible.removeListener(_onOverlayVisibleChange); @@ -169,7 +177,7 @@ class _EntryViewerStackState extends State with FeedbackMixin, case AppLifecycleState.inactive: case AppLifecycleState.paused: case AppLifecycleState.detached: - _pauseVideoControllers(); + pauseVideoControllers(); break; case AppLifecycleState.resumed: availability.onResume(); @@ -232,7 +240,7 @@ class _EntryViewerStackState extends State with FeedbackMixin, _goToVerticalPage(infoPage); } else if (notification is ViewEntryNotification) { final index = notification.index; - if (_currentHorizontalPage != index) { + if (_currentEntryIndex != index) { _horizontalPager.jumpToPage(index); } } else if (notification is VideoActionNotification) { @@ -248,7 +256,8 @@ class _EntryViewerStackState extends State with FeedbackMixin, children: [ ViewerVerticalPageView( collection: collection, - entryNotifier: _entryNotifier, + entryNotifier: entryNotifier, + viewerController: viewerController, verticalPager: _verticalPager, horizontalPager: _horizontalPager, onVerticalPageChanged: _onVerticalPageChanged, @@ -256,8 +265,7 @@ class _EntryViewerStackState extends State with FeedbackMixin, onImagePageRequested: () => _goToVerticalPage(imagePage), onViewDisposed: (mainEntry, pageEntry) => viewStateConductor.reset(pageEntry ?? mainEntry), ), - _buildTopOverlay(), - _buildBottomOverlay(), + ..._buildOverlays(), const SideGestureAreaProtector(), const BottomGestureAreaProtector(), ], @@ -267,9 +275,42 @@ class _EntryViewerStackState extends State with FeedbackMixin, ); } - Widget _buildTopOverlay() { + List _buildOverlays() { + if (context.read>().value == AppMode.slideshow) { + return [_buildSlideshowBottomOverlay()]; + } + + return [ + _buildViewerTopOverlay(), + _buildViewerBottomOverlay(), + ]; + } + + Widget _buildSlideshowBottomOverlay() { + return Selector( + selector: (context, mq) => mq.size, + builder: (context, mqSize, child) { + return SizedBox.fromSize( + size: mqSize, + child: Align( + alignment: AlignmentDirectional.bottomEnd, + child: TooltipTheme( + data: TooltipTheme.of(context).copyWith( + preferBelow: false, + ), + child: SlideshowButtons( + scale: _overlayButtonScale, + ), + ), + ), + ); + }, + ); + } + + Widget _buildViewerTopOverlay() { Widget child = ValueListenableBuilder( - valueListenable: _entryNotifier, + valueListenable: entryNotifier, builder: (context, mainEntry, child) { if (mainEntry == null) return const SizedBox(); @@ -277,7 +318,7 @@ class _EntryViewerStackState extends State with FeedbackMixin, position: _overlayTopOffset, child: ViewerTopOverlay( entries: entries, - index: _currentHorizontalPage, + index: _currentEntryIndex, hasCollection: hasCollection, mainEntry: mainEntry, scale: _overlayButtonScale, @@ -313,9 +354,9 @@ class _EntryViewerStackState extends State with FeedbackMixin, return child; } - Widget _buildBottomOverlay() { + Widget _buildViewerBottomOverlay() { Widget child = ValueListenableBuilder( - valueListenable: _entryNotifier, + valueListenable: entryNotifier, builder: (context, mainEntry, child) { if (mainEntry == null) return const SizedBox(); @@ -377,7 +418,7 @@ class _EntryViewerStackState extends State with FeedbackMixin, if (extraBottomOverlay != null) extraBottomOverlay, ViewerBottomOverlay( entries: entries, - index: _currentHorizontalPage, + index: _currentEntryIndex, hasCollection: hasCollection, animationController: _overlayAnimationController, viewInsets: _frozenViewInsets, @@ -399,7 +440,7 @@ class _EntryViewerStackState extends State with FeedbackMixin, return AnimatedBuilder( animation: _verticalScrollNotifier, builder: (context, child) => Positioned( - bottom: (_verticalPager.position.hasPixels ? _verticalPager.offset : 0) - mqHeight, + bottom: (_verticalPager.hasClients && _verticalPager.position.hasPixels ? _verticalPager.offset : 0) - mqHeight, child: child!, ), child: child, @@ -421,7 +462,7 @@ class _EntryViewerStackState extends State with FeedbackMixin, } void _onVerticalPageControllerChange() { - if (!_isEntryTracked && _verticalPager.page?.floor() == transitionPage) { + if (!_isEntryTracked && _verticalPager.hasClients && _verticalPager.page?.floor() == transitionPage) { _trackEntry(); } _verticalScrollNotifier.notify(); @@ -439,12 +480,10 @@ class _EntryViewerStackState extends State with FeedbackMixin, context, MaterialPageRoute( settings: const RouteSettings(name: CollectionPage.routeName), - builder: (context) { - return CollectionPage( - source: baseCollection.source, - filters: {...baseCollection.filters, filter}, - ); - }, + builder: (context) => CollectionPage( + source: baseCollection.source, + filters: {...baseCollection.filters, filter}, + ), ), (route) => false, ); @@ -476,7 +515,10 @@ class _EntryViewerStackState extends State with FeedbackMixin, } void _onHorizontalPageChanged(int page) { - _currentHorizontalPage = page; + _currentEntryIndex = page; + if (viewerController.repeat) { + _currentEntryIndex %= entries.length; + } _updateEntry(); } @@ -520,20 +562,20 @@ class _EntryViewerStackState extends State with FeedbackMixin, } Future _updateEntry() async { - if (entries.isNotEmpty && _currentHorizontalPage >= entries.length) { + if (entries.isNotEmpty && _currentEntryIndex >= 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 _onHorizontalPageChanged(entries.length - 1); return; } - final newEntry = _currentHorizontalPage < entries.length ? entries[_currentHorizontalPage] : null; - if (_entryNotifier.value == newEntry) return; - _cleanEntryControllers(_entryNotifier.value); - _entryNotifier.value = newEntry; + final newEntry = _currentEntryIndex < entries.length ? entries[_currentEntryIndex] : null; + if (entryNotifier.value == newEntry) return; + cleanEntryControllers(entryNotifier.value); + entryNotifier.value = newEntry; _isEntryTracked = false; - await _pauseVideoControllers(); - await _initEntryControllers(newEntry); + await pauseVideoControllers(); + await initEntryControllers(newEntry); } void _popVisual() { @@ -544,7 +586,7 @@ class _EntryViewerStackState extends State with FeedbackMixin, } // closing hero, with viewer as source - final heroInfo = HeroInfo(collection?.id, _entryNotifier.value); + final heroInfo = HeroInfo(collection?.id, entryNotifier.value); if (_heroInfoNotifier.value != heroInfo) { _heroInfoNotifier.value = heroInfo; // we post closing the viewer page so that hero animation source is ready @@ -563,7 +605,7 @@ class _EntryViewerStackState extends State with FeedbackMixin, // if they are not fully visible already void _trackEntry() { _isEntryTracked = true; - final entry = _entryNotifier.value; + final entry = entryNotifier.value; if (entry != null && hasCollection) { context.read().trackItem( entry, @@ -605,6 +647,7 @@ class _EntryViewerStackState extends State with FeedbackMixin, } else { _overlayAnimationController.value = _overlayAnimationController.upperBound; } + viewerController.autopilot = false; } else { final mediaQuery = context.read(); setState(() { @@ -623,115 +666,4 @@ class _EntryViewerStackState extends State with FeedbackMixin, }); } } - - // state controllers/monitors - - Future _initEntryControllers(AvesEntry? entry) async { - if (entry == null) return; - - if (entry.isVideo) { - await _initVideoController(entry); - } - if (entry.isMultiPage) { - await _initMultiPageController(entry); - } - } - - void _cleanEntryControllers(AvesEntry? entry) { - if (entry == null) return; - - if (entry.isMultiPage) { - _cleanMultiPageController(entry); - } - } - - Future _initVideoController(AvesEntry entry) async { - final controller = context.read().getOrCreateController(entry); - setState(() {}); - - if (settings.enableVideoAutoPlay) { - final resumeTimeMillis = await controller.getResumeTime(context); - await _playVideo(controller, () => entry == _entryNotifier.value, resumeTimeMillis: resumeTimeMillis); - } - } - - Future _initMultiPageController(AvesEntry entry) async { - final multiPageController = context.read().getOrCreateController(entry); - setState(() {}); - - final multiPageInfo = multiPageController.info ?? await multiPageController.infoStream.first; - assert(multiPageInfo != null); - if (multiPageInfo == null) return; - - 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(); - videoPageEntries.forEach((entry) => videoConductor.getOrCreateController(entry, maxControllerCount: videoPageEntries.length)); - - // auto play/pause when changing page - Future _onPageChange() async { - await _pauseVideoControllers(); - if (settings.enableVideoAutoPlay || (entry.isMotionPhoto && settings.enableMotionPhotoAutoPlay)) { - 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); - if (pageVideoController != null) { - await _playVideo(pageVideoController, () => entry == _entryNotifier.value && page == multiPageController.page); - } - } - } - } - - _multiPageControllerPageListeners[multiPageController] = _onPageChange; - multiPageController.pageNotifier.addListener(_onPageChange); - await _onPageChange(); - - if (entry.isMotionPhoto && settings.enableMotionPhotoAutoPlay) { - await Future.delayed(Durations.motionPhotoAutoPlayDelay); - if (entry == _entryNotifier.value) { - multiPageController.page = 1; - } - } - } - } - - Future _cleanMultiPageController(AvesEntry entry) async { - final multiPageController = _multiPageControllerPageListeners.keys.firstWhereOrNull((v) => v.entry == entry); - if (multiPageController != null) { - final _onPageChange = _multiPageControllerPageListeners.remove(multiPageController); - if (_onPageChange != null) { - multiPageController.pageNotifier.removeListener(_onPageChange); - } - } - } - - Future _playVideo(AvesVideoController videoController, bool Function() isCurrent, {int? resumeTimeMillis}) 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(const Duration(milliseconds: 300) * timeDilation); - - if (resumeTimeMillis != null) { - await videoController.seekTo(resumeTimeMillis); - } else { - 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 _pauseVideoControllers() => context.read().pauseAll(); } diff --git a/lib/widgets/viewer/overlay/bottom.dart b/lib/widgets/viewer/overlay/bottom.dart index 8c72175a2..3adfb4576 100644 --- a/lib/widgets/viewer/overlay/bottom.dart +++ b/lib/widgets/viewer/overlay/bottom.dart @@ -1,12 +1,14 @@ import 'dart:math'; +import 'package:aves/app_mode.dart'; import 'package:aves/model/entry.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/widgets/common/extensions/media_query.dart'; import 'package:aves/widgets/viewer/multipage/controller.dart'; import 'package:aves/widgets/viewer/overlay/multipage.dart'; import 'package:aves/widgets/viewer/overlay/thumbnail_preview.dart'; -import 'package:aves/widgets/viewer/overlay/viewer_button_row.dart'; +import 'package:aves/widgets/viewer/overlay/viewer_buttons.dart'; +import 'package:aves/widgets/viewer/overlay/wallpaper_buttons.dart'; import 'package:aves/widgets/viewer/page_entry_builder.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -35,7 +37,7 @@ class ViewerBottomOverlay extends StatefulWidget { State createState() => _ViewerBottomOverlayState(); static double actionSafeHeight(BuildContext context) { - return ViewerButtonRow.preferredHeight(context) + (settings.showOverlayThumbnailPreview ? ViewerThumbnailPreview.preferredHeight : 0); + return ViewerButtons.preferredHeight(context) + (settings.showOverlayThumbnailPreview ? ViewerThumbnailPreview.preferredHeight : 0); } } @@ -134,6 +136,7 @@ class _BottomOverlayContentState extends State<_BottomOverlayContent> { final mainEntry = widget.mainEntry; final pageEntry = widget.pageEntry; final multiPageController = widget.multiPageController; + final isWallpaperMode = context.read>().value == AppMode.setWallpaper; return AnimatedBuilder( animation: Listenable.merge([ @@ -152,12 +155,17 @@ class _BottomOverlayContentState extends State<_BottomOverlayContent> { left: viewInsetsPadding.left, right: viewInsetsPadding.right, ), - child: ViewerButtonRow( - mainEntry: mainEntry, - pageEntry: pageEntry, - scale: _buttonScale, - canToggleFavourite: widget.hasCollection, - ), + child: isWallpaperMode + ? WallpaperButtons( + entry: pageEntry, + scale: _buttonScale, + ) + : ViewerButtons( + mainEntry: mainEntry, + pageEntry: pageEntry, + scale: _buttonScale, + canToggleFavourite: widget.hasCollection, + ), ); final showMultiPageOverlay = mainEntry.isMultiPage && multiPageController != null; @@ -201,7 +209,7 @@ class _BottomOverlayContentState extends State<_BottomOverlayContent> { ], ) : viewerButtonRow, - if (settings.showOverlayThumbnailPreview) + if (settings.showOverlayThumbnailPreview && !isWallpaperMode) FadeTransition( opacity: _thumbnailOpacity, child: ViewerThumbnailPreview( diff --git a/lib/widgets/viewer/overlay/common.dart b/lib/widgets/viewer/overlay/common.dart index 980ef34ca..523cd5fb9 100644 --- a/lib/widgets/viewer/overlay/common.dart +++ b/lib/widgets/viewer/overlay/common.dart @@ -19,7 +19,7 @@ class OverlayButton extends StatelessWidget { @override Widget build(BuildContext context) { final brightness = Theme.of(context).brightness; - final blurred = settings.enableOverlayBlurEffect; + final blurred = settings.enableBlurEffect; return ScaleTransition( scale: scale, child: borderRadius != null @@ -77,7 +77,7 @@ class OverlayTextButton extends StatelessWidget { @override Widget build(BuildContext context) { - final blurred = settings.enableOverlayBlurEffect; + final blurred = settings.enableBlurEffect; final theme = Theme.of(context); return SizeTransition( sizeFactor: scale, diff --git a/lib/widgets/viewer/overlay/slideshow_buttons.dart b/lib/widgets/viewer/overlay/slideshow_buttons.dart new file mode 100644 index 000000000..091332761 --- /dev/null +++ b/lib/widgets/viewer/overlay/slideshow_buttons.dart @@ -0,0 +1,43 @@ +import 'package:aves/model/actions/slideshow_actions.dart'; +import 'package:aves/widgets/viewer/overlay/common.dart'; +import 'package:aves/widgets/viewer/overlay/viewer_buttons.dart'; +import 'package:aves/widgets/viewer/slideshow_page.dart'; +import 'package:flutter/material.dart'; + +class SlideshowButtons extends StatelessWidget { + final Animation scale; + + const SlideshowButtons({ + super.key, + required this.scale, + }); + + @override + Widget build(BuildContext context) { + const padding = ViewerButtonRowContent.padding; + return SafeArea( + child: Padding( + padding: const EdgeInsets.only(left: padding / 2, right: padding / 2, bottom: padding), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + SlideshowAction.resume, + SlideshowAction.showInCollection, + ] + .map((action) => Padding( + padding: const EdgeInsets.symmetric(horizontal: padding / 2), + child: OverlayButton( + scale: scale, + child: IconButton( + icon: action.getIcon(), + onPressed: () => SlideshowActionNotification(action).dispatch(context), + tooltip: action.getText(context), + ), + ), + )) + .toList(), + ), + ), + ); + } +} diff --git a/lib/widgets/viewer/overlay/top.dart b/lib/widgets/viewer/overlay/top.dart index d680f431a..6842773c5 100644 --- a/lib/widgets/viewer/overlay/top.dart +++ b/lib/widgets/viewer/overlay/top.dart @@ -42,7 +42,7 @@ class ViewerTopOverlay extends StatelessWidget { final viewStateConductor = context.read(); final viewStateNotifier = viewStateConductor.getOrCreateController(pageEntry); - final blurred = settings.enableOverlayBlurEffect; + final blurred = settings.enableBlurEffect; final viewInsetsPadding = (viewInsets ?? EdgeInsets.zero) + (viewPadding ?? EdgeInsets.zero); return Column( mainAxisSize: MainAxisSize.min, diff --git a/lib/widgets/viewer/overlay/video/progress_bar.dart b/lib/widgets/viewer/overlay/video/progress_bar.dart index ef244e45d..06d12f9c9 100644 --- a/lib/widgets/viewer/overlay/video/progress_bar.dart +++ b/lib/widgets/viewer/overlay/video/progress_bar.dart @@ -39,7 +39,7 @@ class _VideoProgressBarState extends State { @override Widget build(BuildContext context) { - final blurred = settings.enableOverlayBlurEffect; + final blurred = settings.enableBlurEffect; final brightness = Theme.of(context).brightness; final textStyle = TextStyle( shadows: brightness == Brightness.dark ? Constants.embossShadows : null, diff --git a/lib/widgets/viewer/overlay/viewer_button_row.dart b/lib/widgets/viewer/overlay/viewer_buttons.dart similarity index 95% rename from lib/widgets/viewer/overlay/viewer_button_row.dart rename to lib/widgets/viewer/overlay/viewer_buttons.dart index 5091cb0e1..47a8e6ce6 100644 --- a/lib/widgets/viewer/overlay/viewer_button_row.dart +++ b/lib/widgets/viewer/overlay/viewer_buttons.dart @@ -22,7 +22,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:provider/provider.dart'; -class ViewerButtonRow extends StatelessWidget { +class ViewerButtons extends StatelessWidget { final AvesEntry mainEntry; final AvesEntry pageEntry; final Animation scale; @@ -35,7 +35,7 @@ class ViewerButtonRow extends StatelessWidget { static double _buttonSize(BuildContext context) => OverlayButton.getSize(context); - const ViewerButtonRow({ + const ViewerButtons({ super.key, required this.mainEntry, required this.pageEntry, @@ -92,6 +92,7 @@ class ViewerButtonRow extends StatelessWidget { return settings.isRotationLocked; case EntryAction.addShortcut: return device.canPinShortcut; + case EntryAction.info: case EntryAction.copyToClipboard: case EntryAction.edit: case EntryAction.open: @@ -141,12 +142,13 @@ class ViewerButtonRowContent extends StatelessWidget { final List quickActions, topLevelActions, exportActions, videoActions; final Animation scale; final AvesEntry mainEntry, pageEntry; + final ValueNotifier _popupExpandedNotifier = ValueNotifier(null); AvesEntry get favouriteTargetEntry => mainEntry.isBurst ? pageEntry : mainEntry; static const double padding = 8; - const ViewerButtonRowContent({ + ViewerButtonRowContent({ super.key, required this.quickActions, required this.topLevelActions, @@ -187,6 +189,8 @@ class ViewerButtonRowContent extends StatelessWidget { PopupMenuItem( padding: EdgeInsets.zero, child: PopupMenuItemExpansionPanel( + value: 'export', + expandedNotifier: _popupExpandedNotifier, icon: AIcons.export, title: context.l10n.entryActionExport, items: [ @@ -200,6 +204,8 @@ class ViewerButtonRowContent extends StatelessWidget { PopupMenuItem( padding: EdgeInsets.zero, child: PopupMenuItemExpansionPanel( + value: 'video', + expandedNotifier: _popupExpandedNotifier, icon: AIcons.video, title: context.l10n.settingsSectionVideo, items: [ @@ -214,9 +220,13 @@ class ViewerButtonRowContent extends StatelessWidget { ]; }, onSelected: (action) { + _popupExpandedNotifier.value = null; // wait for the popup menu to hide before proceeding with the action Future.delayed(Durations.popupMenuAnimation * timeDilation, () => _onActionSelected(context, action)); }, + onCanceled: () { + _popupExpandedNotifier.value = null; + }, onMenuOpened: () { // if the menu is opened while overlay is hiding, // the popup menu button is disposed and menu items are ineffective, diff --git a/lib/widgets/viewer/overlay/wallpaper_buttons.dart b/lib/widgets/viewer/overlay/wallpaper_buttons.dart new file mode 100644 index 000000000..8b8506773 --- /dev/null +++ b/lib/widgets/viewer/overlay/wallpaper_buttons.dart @@ -0,0 +1,246 @@ +import 'dart:async'; +import 'dart:math'; +import 'dart:typed_data'; +import 'dart:ui' as ui; + +import 'package:aves/model/device.dart'; +import 'package:aves/model/entry.dart'; +import 'package:aves/model/entry_images.dart'; +import 'package:aves/model/wallpaper_target.dart'; +import 'package:aves/services/wallpaper_service.dart'; +import 'package:aves/widgets/common/action_mixins/feedback.dart'; +import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:aves/widgets/dialogs/aves_selection_dialog.dart'; +import 'package:aves/widgets/viewer/overlay/common.dart'; +import 'package:aves/widgets/viewer/overlay/viewer_buttons.dart'; +import 'package:aves/widgets/viewer/video/conductor.dart'; +import 'package:aves/widgets/viewer/visual/conductor.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:latlong2/latlong.dart'; +import 'package:provider/provider.dart'; + +class WallpaperButtons extends StatelessWidget with FeedbackMixin { + final AvesEntry entry; + final Animation scale; + + const WallpaperButtons({ + super.key, + required this.entry, + required this.scale, + }); + + @override + Widget build(BuildContext context) { + const padding = ViewerButtonRowContent.padding; + return SafeArea( + top: false, + bottom: false, + child: Padding( + padding: const EdgeInsets.only(left: padding / 2, right: padding / 2, bottom: padding), + child: Row( + children: [ + const Spacer(), + Padding( + padding: const EdgeInsets.symmetric(horizontal: padding / 2), + child: OverlayTextButton( + scale: scale, + buttonLabel: context.l10n.viewerSetWallpaperButtonLabel, + onPressed: () => _setWallpaper(context), + ), + ), + ], + ), + ), + ); + } + + Future _setWallpaper(BuildContext context) async { + final l10n = context.l10n; + var target = WallpaperTarget.home; + if (device.canSetLockScreenWallpaper) { + final value = await showDialog( + context: context, + builder: (context) => AvesSelectionDialog( + initialValue: WallpaperTarget.home, + options: Map.fromEntries(WallpaperTarget.values.map((v) => MapEntry(v, v.getName(context)))), + confirmationButtonLabel: l10n.continueButtonLabel, + ), + ); + if (value == null) return; + target = value; + } + + final reportController = StreamController.broadcast(); + unawaited(showOpReport( + context: context, + opStream: reportController.stream, + )); + + final viewState = context.read().getOrCreateController(entry).value; + final viewportSize = viewState.viewportSize; + final contentSize = viewState.contentSize; + final scale = viewState.scale; + if (viewportSize == null || contentSize == null || contentSize.isEmpty || scale == null) return; + + final center = (contentSize / 2 - viewState.position / scale) as Size; + final regionSize = viewportSize / scale; + final regionTopLeft = (center - regionSize / 2) as Offset; + final region = Rect.fromLTWH(regionTopLeft.dx, regionTopLeft.dy, regionSize.width, regionSize.height); + final bytes = await _getBytes(context, scale, region); + + final success = bytes != null && await WallpaperService.set(bytes, target); + unawaited(reportController.close()); + + if (success) { + await SystemNavigator.pop(); + } else { + showFeedback(context, l10n.genericFailureFeedback); + } + } + + Future _getBytes(BuildContext context, double scale, Rect displayRegion) async { + final displaySize = entry.displaySize; + if (displaySize.isEmpty) return null; + + var storageRegion = Rectangle( + displayRegion.left, + displayRegion.top, + displayRegion.width, + displayRegion.height, + ); + + final rotationDegrees = entry.rotationDegrees; + final isFlipped = entry.isFlipped; + var needCrop = false, needOrientation = false; + + ImageProvider? provider; + if (entry.isSvg) { + provider = entry.getRegion( + scale: scale, + region: storageRegion, + ); + } else if (entry.isVideo) { + final videoController = context.read().getController(entry); + if (videoController != null) { + final bytes = await videoController.captureFrame(); + needOrientation = rotationDegrees != 0 || isFlipped; + needCrop = true; + provider = MemoryImage(bytes); + } + } else if (entry.canDecode) { + if (entry.useTiles) { + // provider image is already cropped, but not rotated + needOrientation = rotationDegrees != 0 || isFlipped; + if (needOrientation) { + final transform = Matrix4.identity() + ..translate(entry.width / 2.0, entry.height / 2.0) + ..scale(isFlipped ? -1.0 : 1.0, 1.0, 1.0) + ..rotateZ(-degToRadian(rotationDegrees.toDouble())) + ..translate(-displaySize.width / 2.0, -displaySize.height / 2.0); + + // apply EXIF orientation + final regionRectDouble = Rect.fromLTWH(displayRegion.left.toDouble(), displayRegion.top.toDouble(), displayRegion.width.toDouble(), displayRegion.height.toDouble()); + final tl = MatrixUtils.transformPoint(transform, regionRectDouble.topLeft); + final br = MatrixUtils.transformPoint(transform, regionRectDouble.bottomRight); + storageRegion = Rectangle.fromPoints( + Point(tl.dx, tl.dy), + Point(br.dx, br.dy), + ); + } + + final sampleSize = ExtraAvesEntryImages.sampleSizeForScale(scale); + provider = entry.getRegion(sampleSize: sampleSize, region: storageRegion); + displayRegion = Rect.fromLTWH( + displayRegion.left / sampleSize, + displayRegion.top / sampleSize, + displayRegion.width / sampleSize, + displayRegion.height / sampleSize, + ); + } else { + // provider image is already rotated, but not cropped + needCrop = true; + provider = entry.uriImage; + } + } + if (provider == null) return null; + + final imageInfoCompleter = Completer(); + final imageStream = provider.resolve(ImageConfiguration.empty); + final imageStreamListener = ImageStreamListener((image, synchronousCall) async { + imageInfoCompleter.complete(image); + }, onError: imageInfoCompleter.completeError); + imageStream.addListener(imageStreamListener); + ImageInfo? regionImageInfo; + try { + regionImageInfo = await imageInfoCompleter.future; + } catch (error) { + debugPrint('failed to get image for region=$displayRegion with error=$error'); + } + imageStream.removeListener(imageStreamListener); + var image = regionImageInfo?.image; + if (image == null) return null; + + if (needCrop || needOrientation) { + final recorder = ui.PictureRecorder(); + final canvas = Canvas(recorder); + + final w = image.width.toDouble(); + final h = image.height.toDouble(); + final imageDisplaySize = entry.isRotated ? Size(h, w) : Size(w, h); + var cropDx = -displayRegion.left.toDouble(); + var cropDy = -displayRegion.top.toDouble(); + + if (needOrientation) { + // apply EXIF orientation + if (isFlipped) { + canvas.scale(-1, 1); + switch (rotationDegrees) { + case 90: + canvas.translate(-imageDisplaySize.width, imageDisplaySize.height); + canvas.rotate(degToRadian(270)); + break; + case 180: + canvas.translate(0, imageDisplaySize.height); + canvas.rotate(degToRadian(180)); + break; + case 270: + canvas.rotate(degToRadian(90)); + break; + } + } else { + switch (rotationDegrees) { + case 90: + canvas.translate(imageDisplaySize.width, 0); + cropDx = -displayRegion.top.toDouble(); + cropDy = displayRegion.left.toDouble(); + break; + case 180: + canvas.translate(imageDisplaySize.width, imageDisplaySize.height); + break; + case 270: + canvas.translate(0, imageDisplaySize.height); + break; + } + if (rotationDegrees != 0) { + canvas.rotate(degToRadian(rotationDegrees.toDouble())); + } + } + } + + if (needCrop) { + canvas.translate(cropDx, cropDy); + } + canvas.drawImage(image, Offset.zero, Paint()); + final picture = recorder.endRecording(); + final renderSize = Size( + displayRegion.width.toDouble(), + displayRegion.height.toDouble(), + ); + image = await picture.toImage(renderSize.width.round(), renderSize.height.round()); + } + + // bytes should be compressed to be decodable on the platform side + return await image.toByteData(format: ui.ImageByteFormat.png).then((v) => v?.buffer.asUint8List()); + } +} diff --git a/lib/widgets/viewer/slideshow_page.dart b/lib/widgets/viewer/slideshow_page.dart new file mode 100644 index 000000000..c21b238bf --- /dev/null +++ b/lib/widgets/viewer/slideshow_page.dart @@ -0,0 +1,142 @@ +import 'package:aves/app_mode.dart'; +import 'package:aves/model/actions/slideshow_actions.dart'; +import 'package:aves/model/filters/album.dart'; +import 'package:aves/model/filters/mime.dart'; +import 'package:aves/model/settings/enums/enums.dart'; +import 'package:aves/model/settings/enums/slideshow_interval.dart'; +import 'package:aves/model/settings/settings.dart'; +import 'package:aves/model/source/collection_lens.dart'; +import 'package:aves/theme/icons.dart'; +import 'package:aves/widgets/collection/collection_page.dart'; +import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:aves/widgets/common/identity/empty.dart'; +import 'package:aves/widgets/common/providers/media_query_data_provider.dart'; +import 'package:aves/widgets/viewer/controller.dart'; +import 'package:aves/widgets/viewer/entry_viewer_page.dart'; +import 'package:aves/widgets/viewer/entry_viewer_stack.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +class SlideshowPage extends StatefulWidget { + static const routeName = '/collection/slideshow'; + + final CollectionLens collection; + + const SlideshowPage({ + super.key, + required this.collection, + }); + + @override + State createState() => _SlideshowPageState(); +} + +class _SlideshowPageState extends State { + late final CollectionLens _slideshowCollection; + late final ViewerController _viewerController; + + @override + void initState() { + super.initState(); + final originalCollection = widget.collection; + var entries = originalCollection.sortedEntries; + if (settings.slideshowVideoPlayback == SlideshowVideoPlayback.skip) { + entries = entries.where((entry) => !MimeFilter.video.test(entry)).toList(); + } + if (settings.slideshowShuffle) { + entries.shuffle(); + } + _slideshowCollection = CollectionLens( + source: originalCollection.source, + listenToSource: false, + fixedSort: true, + fixedSelection: entries, + ); + _viewerController = ViewerController( + transition: settings.slideshowTransition, + repeat: settings.slideshowRepeat, + autopilot: true, + autopilotInterval: settings.slideshowInterval.getDuration(), + ); + } + + @override + void dispose() { + _viewerController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final entries = _slideshowCollection.sortedEntries; + return ListenableProvider>.value( + value: ValueNotifier(AppMode.slideshow), + child: MediaQueryDataProvider( + child: Scaffold( + body: entries.isEmpty + ? EmptyContent( + icon: AIcons.image, + text: context.l10n.collectionEmptyImages, + alignment: Alignment.center, + ) + : ViewStateConductorProvider( + child: VideoConductorProvider( + child: MultiPageConductorProvider( + child: NotificationListener( + onNotification: (notification) { + _onActionSelected(notification.action); + return true; + }, + child: EntryViewerStack( + collection: _slideshowCollection, + initialEntry: entries.first, + viewerController: _viewerController, + ), + ), + ), + ), + ), + ), + ), + ); + } + + void _onActionSelected(SlideshowAction action) { + switch (action) { + case SlideshowAction.resume: + _viewerController.autopilot = true; + break; + case SlideshowAction.showInCollection: + _showInCollection(); + break; + } + } + + void _showInCollection() { + final entry = _viewerController.entryNotifier.value; + if (entry == null) return; + + final source = _slideshowCollection.source; + final album = entry.directory; + final uri = entry.uri; + + Navigator.pushAndRemoveUntil( + context, + MaterialPageRoute( + settings: const RouteSettings(name: CollectionPage.routeName), + builder: (context) => CollectionPage( + source: source, + filters: album != null ? {AlbumFilter(album, source.getAlbumDisplayName(context, album))} : null, + highlightTest: (entry) => entry.uri == uri, + ), + ), + (route) => false, + ); + } +} + +class SlideshowActionNotification extends Notification { + final SlideshowAction action; + + SlideshowActionNotification(this.action); +} diff --git a/lib/widgets/viewer/visual/conductor.dart b/lib/widgets/viewer/visual/conductor.dart index c66004bf4..04814e187 100644 --- a/lib/widgets/viewer/visual/conductor.dart +++ b/lib/widgets/viewer/visual/conductor.dart @@ -31,6 +31,7 @@ class ViewStateConductor { final initialValue = ViewState( position: Offset.zero, scale: ScaleBoundaries( + allowOriginalScaleBeyondRange: true, minScale: initialScale, maxScale: initialScale, initialScale: initialScale, diff --git a/lib/widgets/viewer/visual/controller_mixin.dart b/lib/widgets/viewer/visual/controller_mixin.dart new file mode 100644 index 000000000..bf3aff9d7 --- /dev/null +++ b/lib/widgets/viewer/visual/controller_mixin.dart @@ -0,0 +1,149 @@ +import 'package:aves/app_mode.dart'; +import 'package:aves/model/entry.dart'; +import 'package:aves/model/settings/enums/enums.dart'; +import 'package:aves/model/settings/settings.dart'; +import 'package:aves/theme/durations.dart'; +import 'package:aves/widgets/viewer/multipage/conductor.dart'; +import 'package:aves/widgets/viewer/multipage/controller.dart'; +import 'package:aves/widgets/viewer/video/conductor.dart'; +import 'package:aves/widgets/viewer/video/controller.dart'; +import 'package:collection/collection.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:provider/provider.dart'; + +// state controllers/monitors +mixin EntryViewControllerMixin on State { + final Map Function()> _multiPageControllerPageListeners = {}; + + ValueNotifier get entryNotifier; + + Future initEntryControllers(AvesEntry? entry) async { + if (entry == null) return; + + if (entry.isVideo) { + await _initVideoController(entry); + } + if (entry.isMultiPage) { + await _initMultiPageController(entry); + } + } + + void cleanEntryControllers(AvesEntry? entry) { + if (entry == null) return; + + if (entry.isMultiPage) { + _cleanMultiPageController(entry); + } + } + + bool _isSlideshow(BuildContext context) => context.read>().value == AppMode.slideshow; + + bool _shouldAutoPlay(BuildContext context) { + if (_isSlideshow(context)) { + switch (settings.slideshowVideoPlayback) { + case SlideshowVideoPlayback.skip: + return false; + case SlideshowVideoPlayback.playMuted: + case SlideshowVideoPlayback.playWithSound: + return true; + } + } + + return settings.enableVideoAutoPlay; + } + + Future _initVideoController(AvesEntry entry) async { + final controller = context.read().getOrCreateController(entry); + setState(() {}); + + if (_shouldAutoPlay(context)) { + final resumeTimeMillis = await controller.getResumeTime(context); + await _playVideo(controller, () => entry == entryNotifier.value, resumeTimeMillis: resumeTimeMillis); + } + } + + Future _initMultiPageController(AvesEntry entry) async { + final multiPageController = context.read().getOrCreateController(entry); + setState(() {}); + + final multiPageInfo = multiPageController.info ?? await multiPageController.infoStream.first; + assert(multiPageInfo != null); + if (multiPageInfo == null) return; + + 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(); + videoPageEntries.forEach((entry) => videoConductor.getOrCreateController(entry, maxControllerCount: videoPageEntries.length)); + + // auto play/pause when changing page + Future _onPageChange() async { + await pauseVideoControllers(); + if (_shouldAutoPlay(context) || (entry.isMotionPhoto && settings.enableMotionPhotoAutoPlay)) { + 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); + if (pageVideoController != null) { + await _playVideo(pageVideoController, () => entry == entryNotifier.value && page == multiPageController.page); + } + } + } + } + + _multiPageControllerPageListeners[multiPageController] = _onPageChange; + multiPageController.pageNotifier.addListener(_onPageChange); + await _onPageChange(); + + if (entry.isMotionPhoto && settings.enableMotionPhotoAutoPlay) { + await Future.delayed(Durations.motionPhotoAutoPlayDelay); + if (entry == entryNotifier.value) { + multiPageController.page = 1; + } + } + } + } + + Future _cleanMultiPageController(AvesEntry entry) async { + final multiPageController = _multiPageControllerPageListeners.keys.firstWhereOrNull((v) => v.entry == entry); + if (multiPageController != null) { + final _onPageChange = _multiPageControllerPageListeners.remove(multiPageController); + if (_onPageChange != null) { + multiPageController.pageNotifier.removeListener(_onPageChange); + } + } + } + + Future _playVideo(AvesVideoController videoController, bool Function() isCurrent, {int? resumeTimeMillis}) 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(const Duration(milliseconds: 300) * timeDilation); + + if (_isSlideshow(context) && settings.slideshowVideoPlayback == SlideshowVideoPlayback.playMuted && !videoController.isMuted) { + await videoController.toggleMute(); + } + + if (resumeTimeMillis != null) { + await videoController.seekTo(resumeTimeMillis); + } else { + 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 pauseVideoControllers() => context.read().pauseAll(); +} diff --git a/lib/widgets/viewer/visual/entry_page_view.dart b/lib/widgets/viewer/visual/entry_page_view.dart index f642dd8d2..3d2ed7c7f 100644 --- a/lib/widgets/viewer/visual/entry_page_view.dart +++ b/lib/widgets/viewer/visual/entry_page_view.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:aves/app_mode.dart'; import 'package:aves/model/actions/entry_actions.dart'; import 'package:aves/model/entry.dart'; import 'package:aves/model/entry_images.dart'; @@ -72,10 +73,6 @@ class _EntryPageViewState extends State { // use the high res photo as cover for the video part of a motion photo ImageProvider get videoCoverUriImage => mainEntry.isMotionPhoto ? mainEntry.uriImage : entry.uriImage; - static const initialScale = ScaleLevel(ref: ScaleReference.contained); - static const minScale = ScaleLevel(ref: ScaleReference.contained); - static const maxScale = ScaleLevel(factor: 2.0); - @override void initState() { super.initState(); @@ -307,6 +304,16 @@ class _EntryPageViewState extends State { opacity: showCover ? 1 : 0, curve: Curves.easeInCirc, duration: Durations.viewerVideoPlayerTransition, + onEnd: () { + // while cover is fading out, the same controller is used for both the cover and the video, + // and both fire scale boundaries events, so we make sure that in the end + // the scale boundaries from the video are used after the cover is gone + _magnifierController.setScaleBoundaries( + _magnifierController.scaleBoundaries.copyWith( + childSize: videoDisplaySize, + ), + ); + }, child: ValueListenableBuilder( valueListenable: _videoCoverInfoNotifier, builder: (context, videoCoverInfo, child) { @@ -356,20 +363,24 @@ class _EntryPageViewState extends State { Widget _buildMagnifier({ MagnifierController? controller, Size? displaySize, - ScaleLevel maxScale = maxScale, + ScaleLevel maxScale = const ScaleLevel(factor: 2.0), ScaleStateCycle scaleStateCycle = defaultScaleStateCycle, bool applyScale = true, MagnifierDoubleTapCallback? onDoubleTap, required Widget child, }) { + final isWallpaperMode = context.read>().value == AppMode.setWallpaper; + final minScale = isWallpaperMode ? const ScaleLevel(ref: ScaleReference.covered) : const ScaleLevel(ref: ScaleReference.contained); + return Magnifier( // key includes modified date to refresh when the image is modified by metadata (e.g. rotated) key: ValueKey('${entry.uri}_${entry.pageId}_${entry.dateModifiedSecs}'), controller: controller ?? _magnifierController, childSize: displaySize ?? entry.displaySize, + allowOriginalScaleBeyondRange: !isWallpaperMode, minScale: minScale, maxScale: maxScale, - initialScale: initialScale, + initialScale: minScale, scaleStateCycle: scaleStateCycle, applyScale: applyScale, onTap: (c, d, s, o) => _onTap(), diff --git a/lib/widgets/viewer/visual/raster.dart b/lib/widgets/viewer/visual/raster.dart index 6a8dec32d..bcbc78a90 100644 --- a/lib/widgets/viewer/visual/raster.dart +++ b/lib/widgets/viewer/visual/raster.dart @@ -6,7 +6,6 @@ import 'package:aves/model/entry_images.dart'; import 'package:aves/model/settings/enums/entry_background.dart'; import 'package:aves/model/settings/enums/enums.dart'; import 'package:aves/model/settings/settings.dart'; -import 'package:aves/utils/math_utils.dart'; import 'package:aves/widgets/common/fx/checkered_decoration.dart'; import 'package:aves/widgets/viewer/visual/entry_page_view.dart'; import 'package:aves/widgets/viewer/visual/state.dart'; @@ -64,9 +63,6 @@ class _RasterImageViewState extends State { } } - // magic number used to derive sample size from scale - static const scaleFactor = 2.0; - @override void initState() { super.initState(); @@ -145,10 +141,10 @@ class _RasterImageViewState extends State { } void _initTiling(Size viewportSize) { - _tileSide = viewportSize.shortestSide * scaleFactor; + _tileSide = viewportSize.shortestSide * ExtraAvesEntryImages.scaleFactor; // scale for initial state `contained` final containedScale = min(viewportSize.width / _displaySize.width, viewportSize.height / _displaySize.height); - _maxSampleSize = _sampleSizeForScale(containedScale); + _maxSampleSize = ExtraAvesEntryImages.sampleSizeForScale(containedScale); final rotationDegrees = entry.rotationDegrees; final isFlipped = entry.isFlipped; @@ -244,7 +240,7 @@ class _RasterImageViewState extends State { ); final tiles = [fullImageRegionTile]; - var minSampleSize = min(_sampleSizeForScale(scale), _maxSampleSize); + var minSampleSize = min(ExtraAvesEntryImages.sampleSizeForScale(scale), _maxSampleSize); int nextSampleSize(int sampleSize) => (sampleSize / 2).floor(); for (var sampleSize = nextSampleSize(_maxSampleSize); sampleSize >= minSampleSize; sampleSize = nextSampleSize(sampleSize)) { final regionSide = (_tileSide * sampleSize).round(); @@ -317,14 +313,6 @@ class _RasterImageViewState extends State { } return Tuple2>(tileRect, regionRect); } - - int _sampleSizeForScale(double scale) { - var sample = 0; - if (0 < scale && scale < 1) { - sample = highestPowerOf2((1 / scale) / scaleFactor); - } - return max(1, sample); - } } class _RegionTile extends StatefulWidget { diff --git a/lib/widgets/wallpaper_page.dart b/lib/widgets/wallpaper_page.dart new file mode 100644 index 000000000..a3346e3f9 --- /dev/null +++ b/lib/widgets/wallpaper_page.dart @@ -0,0 +1,252 @@ +import 'package:aves/model/entry.dart'; +import 'package:aves/model/settings/enums/enums.dart'; +import 'package:aves/model/settings/settings.dart'; +import 'package:aves/services/common/services.dart'; +import 'package:aves/theme/durations.dart'; +import 'package:aves/widgets/aves_app.dart'; +import 'package:aves/widgets/common/basic/insets.dart'; +import 'package:aves/widgets/common/providers/media_query_data_provider.dart'; +import 'package:aves/widgets/viewer/entry_horizontal_pager.dart'; +import 'package:aves/widgets/viewer/entry_viewer_page.dart'; +import 'package:aves/widgets/viewer/multipage/conductor.dart'; +import 'package:aves/widgets/viewer/overlay/bottom.dart'; +import 'package:aves/widgets/viewer/overlay/notifications.dart'; +import 'package:aves/widgets/viewer/overlay/video/video.dart'; +import 'package:aves/widgets/viewer/page_entry_builder.dart'; +import 'package:aves/widgets/viewer/video/conductor.dart'; +import 'package:aves/widgets/viewer/video/controller.dart'; +import 'package:aves/widgets/viewer/video_action_delegate.dart'; +import 'package:aves/widgets/viewer/visual/controller_mixin.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:screen_brightness/screen_brightness.dart'; + +class WallpaperPage extends StatelessWidget { + static const routeName = '/set_wallpaper'; + + final AvesEntry? entry; + + const WallpaperPage({ + Key? key, + required this.entry, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return MediaQueryDataProvider( + child: Scaffold( + body: entry != null + ? ViewStateConductorProvider( + child: VideoConductorProvider( + child: MultiPageConductorProvider( + child: EntryEditor( + entry: entry!, + ), + ), + ), + ) + : const SizedBox(), + backgroundColor: Theme.of(context).brightness == Brightness.dark ? Colors.black : Colors.white, + resizeToAvoidBottomInset: false, + ), + ); + } +} + +class EntryEditor extends StatefulWidget { + final AvesEntry entry; + + const EntryEditor({ + Key? key, + required this.entry, + }) : super(key: key); + + @override + State createState() => _EntryEditorState(); +} + +class _EntryEditorState extends State with EntryViewControllerMixin, SingleTickerProviderStateMixin { + final ValueNotifier _overlayVisible = ValueNotifier(true); + late AnimationController _overlayAnimationController; + late Animation _overlayVideoControlScale; + EdgeInsets? _frozenViewInsets, _frozenViewPadding; + late VideoActionDelegate _videoActionDelegate; + + @override + final ValueNotifier entryNotifier = ValueNotifier(null); + + AvesEntry get entry => widget.entry; + + @override + void initState() { + super.initState(); + if (!settings.viewerUseCutout) { + windowService.setCutoutMode(false); + } + if (settings.viewerMaxBrightness) { + ScreenBrightness().setScreenBrightness(1); + } + if (settings.keepScreenOn == KeepScreenOn.viewerOnly) { + windowService.keepScreenOn(true); + } + + entryNotifier.value = entry; + _overlayAnimationController = AnimationController( + duration: context.read().viewerOverlayAnimation, + vsync: this, + ); + _overlayVideoControlScale = CurvedAnimation( + parent: _overlayAnimationController, + // no bounce at the bottom, to avoid video controller displacement + curve: Curves.easeOutQuad, + ); + _overlayVisible.addListener(_onOverlayVisibleChange); + _videoActionDelegate = VideoActionDelegate( + collection: null, + ); + + initEntryControllers(entry); + _onOverlayVisibleChange(); + } + + @override + void dispose() { + cleanEntryControllers(entry); + _videoActionDelegate.dispose(); + _overlayAnimationController.dispose(); + _overlayVisible.removeListener(_onOverlayVisibleChange); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return NotificationListener( + onNotification: (dynamic notification) { + if (notification is ToggleOverlayNotification) { + _overlayVisible.value = notification.visible ?? !_overlayVisible.value; + } + return true; + }, + child: Stack( + children: [ + SingleEntryScroller( + entry: entry, + ), + Positioned( + bottom: 0, + child: _buildBottomOverlay(), + ), + const SideGestureAreaProtector(), + const BottomGestureAreaProtector(), + ], + ), + ); + } + + Widget _buildBottomOverlay() { + final mainEntry = entry; + final multiPageController = mainEntry.isMultiPage ? context.read().getController(mainEntry) : null; + + Widget? _buildExtraBottomOverlay({AvesEntry? pageEntry}) { + final targetEntry = pageEntry ?? mainEntry; + Widget? child; + // a 360 video is both a video and a panorama but only the video controls are displayed + if (targetEntry.isVideo) { + child = Selector( + selector: (context, vc) => vc.getController(targetEntry), + builder: (context, videoController, child) => VideoControlOverlay( + entry: targetEntry, + controller: videoController, + scale: _overlayVideoControlScale, + onActionSelected: (action) { + if (videoController != null) { + _videoActionDelegate.onActionSelected(context, videoController, action); + } + }, + onActionMenuOpened: () { + // if the menu is opened while overlay is hiding, + // the popup menu button is disposed and menu items are ineffective, + // so we make sure overlay stays visible + _videoActionDelegate.stopOverlayHidingTimer(); + const ToggleOverlayNotification(visible: true).dispatch(context); + }, + ), + ); + } + return child != null + ? ExtraBottomOverlay( + viewInsets: _frozenViewInsets, + viewPadding: _frozenViewPadding, + child: child, + ) + : null; + } + + final extraBottomOverlay = mainEntry.isMultiPage + ? PageEntryBuilder( + multiPageController: multiPageController, + builder: (pageEntry) => _buildExtraBottomOverlay(pageEntry: pageEntry) ?? const SizedBox(), + ) + : _buildExtraBottomOverlay(); + + final child = TooltipTheme( + data: TooltipTheme.of(context).copyWith( + preferBelow: false, + ), + child: Column( + children: [ + if (extraBottomOverlay != null) extraBottomOverlay, + ViewerBottomOverlay( + entries: [widget.entry], + index: 0, + hasCollection: false, + animationController: _overlayAnimationController, + viewInsets: _frozenViewInsets, + viewPadding: _frozenViewPadding, + multiPageController: multiPageController, + ), + ], + ), + ); + + return ValueListenableBuilder( + valueListenable: _overlayAnimationController, + builder: (context, animation, child) { + return Visibility( + visible: !_overlayAnimationController.isDismissed, + child: child!, + ); + }, + child: child, + ); + } + + // overlay + + Future _onOverlayVisibleChange({bool animate = true}) async { + if (_overlayVisible.value) { + AvesApp.showSystemUI(); + if (animate) { + await _overlayAnimationController.forward(); + } else { + _overlayAnimationController.value = _overlayAnimationController.upperBound; + } + } else { + final mediaQuery = context.read(); + setState(() { + _frozenViewInsets = mediaQuery.viewInsets; + _frozenViewPadding = mediaQuery.viewPadding; + }); + AvesApp.hideSystemUI(); + if (animate) { + await _overlayAnimationController.reverse(); + } else { + _overlayAnimationController.reset(); + } + setState(() { + _frozenViewInsets = null; + _frozenViewPadding = null; + }); + } + } +} diff --git a/pubspec.lock b/pubspec.lock index 81ed62b51..ae64ed458 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -133,49 +133,49 @@ packages: name: connectivity_plus url: "https://pub.dartlang.org" source: hosted - version: "2.3.0" + version: "2.3.3" connectivity_plus_linux: dependency: transitive description: name: connectivity_plus_linux url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.3.1" connectivity_plus_macos: dependency: transitive description: name: connectivity_plus_macos url: "https://pub.dartlang.org" source: hosted - version: "1.2.2" + version: "1.2.3" connectivity_plus_platform_interface: dependency: transitive description: name: connectivity_plus_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.2.1" connectivity_plus_web: dependency: transitive description: name: connectivity_plus_web url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.2.1" connectivity_plus_windows: dependency: transitive description: name: connectivity_plus_windows url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.2.2" convert: dependency: transitive description: name: convert url: "https://pub.dartlang.org" source: hosted - version: "3.0.1" + version: "3.0.2" country_code: dependency: "direct main" description: @@ -210,7 +210,7 @@ packages: name: dbus url: "https://pub.dartlang.org" source: hosted - version: "0.7.3" + version: "0.7.4" decorated_icon: dependency: "direct main" description: @@ -224,7 +224,7 @@ packages: name: device_info_plus url: "https://pub.dartlang.org" source: hosted - version: "3.2.3" + version: "3.2.4" device_info_plus_linux: dependency: transitive description: @@ -260,6 +260,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.1" + dynamic_color: + dependency: "direct main" + description: + name: dynamic_color + url: "https://pub.dartlang.org" + source: hosted + version: "1.4.0" equatable: dependency: "direct main" description: @@ -319,7 +326,7 @@ packages: name: firebase_core url: "https://pub.dartlang.org" source: hosted - version: "1.17.0" + version: "1.17.1" firebase_core_platform_interface: dependency: transitive description: @@ -340,14 +347,14 @@ packages: name: firebase_crashlytics url: "https://pub.dartlang.org" source: hosted - version: "2.8.0" + version: "2.8.1" firebase_crashlytics_platform_interface: dependency: transitive description: name: firebase_crashlytics_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "3.2.6" + version: "3.2.7" flex_color_picker: dependency: "direct main" description: @@ -411,14 +418,14 @@ packages: name: flutter_map url: "https://pub.dartlang.org" source: hosted - version: "0.14.0" + version: "1.0.0" flutter_markdown: dependency: "direct main" description: name: flutter_markdown url: "https://pub.dartlang.org" source: hosted - version: "0.6.10+1" + version: "0.6.10+2" flutter_plugin_android_lifecycle: dependency: transitive description: @@ -482,7 +489,7 @@ packages: name: google_maps_flutter url: "https://pub.dartlang.org" source: hosted - version: "2.1.6" + version: "2.1.7" google_maps_flutter_platform_interface: dependency: transitive description: @@ -524,7 +531,7 @@ packages: name: image url: "https://pub.dartlang.org" source: hosted - version: "3.1.3" + version: "3.2.0" intl: dependency: "direct main" description: @@ -589,7 +596,7 @@ packages: source: hosted version: "0.12.11" material_color_utilities: - dependency: transitive + dependency: "direct main" description: name: material_color_utilities url: "https://pub.dartlang.org" @@ -657,7 +664,7 @@ packages: name: overlay_support url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.0.1" package_config: dependency: transitive description: @@ -713,7 +720,7 @@ packages: name: palette_generator url: "https://pub.dartlang.org" source: hosted - version: "0.3.3" + version: "0.3.3+1" panorama: dependency: "direct main" description: @@ -741,7 +748,7 @@ packages: name: path_provider_linux url: "https://pub.dartlang.org" source: hosted - version: "2.1.6" + version: "2.1.7" path_provider_platform_interface: dependency: transitive description: @@ -755,7 +762,7 @@ packages: name: path_provider_windows url: "https://pub.dartlang.org" source: hosted - version: "2.0.6" + version: "2.0.7" pdf: dependency: "direct main" description: @@ -778,11 +785,13 @@ packages: source: hosted version: "9.2.0" permission_handler_android: - dependency: transitive + dependency: "direct overridden" description: - name: permission_handler_android - url: "https://pub.dartlang.org" - source: hosted + path: permission_handler_android + ref: HEAD + resolved-ref: "279cf44656272c6b89c73b16097108f3c973c31f" + url: "https://github.com/deckerst/flutter-permission-handler" + source: git version: "9.0.2+1" permission_handler_apple: dependency: transitive @@ -826,6 +835,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.2" + polylabel: + dependency: transitive + description: + name: polylabel + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" pool: dependency: transitive description: @@ -1161,7 +1177,7 @@ packages: name: url_launcher url: "https://pub.dartlang.org" source: hosted - version: "6.1.2" + version: "6.1.3" url_launcher_android: dependency: transitive description: @@ -1280,7 +1296,7 @@ packages: name: xml url: "https://pub.dartlang.org" source: hosted - version: "5.4.1" + version: "6.1.0" yaml: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 5b6dd1b14..a75030aa9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -6,7 +6,7 @@ repository: https://github.com/deckerst/aves # - github changelog: /CHANGELOG.md # - play changelog: /whatsnew/whatsnew-en-US # - izzy changelog: /fastlane/metadata/android/en-US/changelogs/1XXX.txt -version: 1.6.8+74 +version: 1.6.9+75 publish_to: none environment: @@ -37,6 +37,7 @@ dependencies: country_code: decorated_icon: device_info_plus: + dynamic_color: equatable: event_bus: expansion_tile_card: @@ -56,6 +57,7 @@ dependencies: get_it: intl: latlong2: + material_color_utilities: material_design_icons_flutter: overlay_support: package_info_plus: @@ -82,6 +84,13 @@ dependencies: url_launcher: xml: +dependency_overrides: + # TODO TLAD as of 2022/06/11, latest version (v9.0.2+1) does not support Android 13 storage permissions + permission_handler_android: + git: + url: https://github.com/deckerst/flutter-permission-handler + path: permission_handler_android + dev_dependencies: flutter_test: sdk: flutter diff --git a/shaders_3.0.1.sksl.json b/shaders_3.0.1.sksl.json deleted file mode 100644 index 542a6a498..000000000 --- a/shaders_3.0.1.sksl.json +++ /dev/null @@ -1 +0,0 @@ -{"platform":"android","name":"SM G970N","engineRevision":"caaafc5604ee9172293eb84a381be6aadd660317","data":{"HVIACAAAABQAAGAAAQ4AAAAAGQQAARC4GAAAIOCAAD6P7777777777YDAAAAAAAAAAAFIBTWV34ISAIAAAAEYT7ZOFIQAAAADAAAAABQAAAAAIFGB7HB2BAAAAAFQRH6PYAAAAEAAAAAAAAZGE66LR2FAEAAAYAAAAAMAAAAACAJQPRYO4IAAAAAMAI7T6YBAAAAABAAAAAGIMFGB7HB2BAAAAAAAAAAQAKYCRPE54DQAAAABAAAAAEQEFMEJ7T6AAAAAAAAAAAAAHIAAAAIAAQAAAAAQGIA":"CAAAAExTS1PlAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMF9jMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdCBjb3ZlcmFnZTsKaW4gaGFsZjQgY29sb3I7CmluIGZsb2F0MiBsb2NhbENvb3JkOwpmbGF0IG91dCBoYWxmNCB2Y29sb3JfUzA7Cm91dCBmbG9hdCB2Y292ZXJhZ2VfUzA7Cm91dCBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzVfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQyIHBvc2l0aW9uID0gcG9zaXRpb24ueHk7Cgl2Y29sb3JfUzAgPSBjb2xvcjsKCXZjb3ZlcmFnZV9TMCA9IGNvdmVyYWdlOwoJc2tfUG9zaXRpb24gPSBwb3NpdGlvbi54eTAxOwoJewoJCXZUcmFuc2Zvcm1lZENvb3Jkc181X1MwID0gZmxvYXQzeDIodW1hdHJpeF9TMV9jMF9jMSkgKiBsb2NhbENvb3JkLnh5MTsKCX0KfQoAAAAAAAAAMAcAAHVuaWZvcm0gaGFsZjQgdXN0YXJ0X1MxX2MwX2MwOwp1bmlmb3JtIGhhbGY0IHVlbmRfUzFfYzBfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMF9jMTsKdW5pZm9ybSBoYWxmNCB1bGVmdEJvcmRlckNvbG9yX1MxX2MwOwp1bmlmb3JtIGhhbGY0IHVyaWdodEJvcmRlckNvbG9yX1MxX2MwOwpmbGF0IGluIGhhbGY0IHZjb2xvcl9TMDsKaW4gZmxvYXQgdmNvdmVyYWdlX1MwOwppbiBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzVfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IFNpbmdsZUludGVydmFsQ29sb3JpemVyX1MxX2MwX2MwKGhhbGY0IF9pbnB1dCwgZmxvYXQyIF9jb29yZHMpIAp7CgloYWxmNCBfdG1wXzBfaW5Db2xvciA9IF9pbnB1dDsKCWZsb2F0MiBfdG1wXzFfY29vcmRzID0gX2Nvb3JkczsKCXJldHVybiBoYWxmNChtaXgodXN0YXJ0X1MxX2MwX2MwLCB1ZW5kX1MxX2MwX2MwLCBoYWxmKF90bXBfMV9jb29yZHMueCkpKTsKfQpoYWxmNCBMaW5lYXJMYXlvdXRfUzFfYzBfYzFfYzAoaGFsZjQgX2lucHV0KSAKewoJaGFsZjQgX3RtcF8yX2luQ29sb3IgPSBfaW5wdXQ7CglmbG9hdDIgX3RtcF8zX2Nvb3JkcyA9IHZUcmFuc2Zvcm1lZENvb3Jkc181X1MwOwoJcmV0dXJuIGhhbGY0KGhhbGY0KGhhbGYoX3RtcF8zX2Nvb3Jkcy54KSArIDkuOTk5OTk5NzQ3Mzc4NzUxNmUtMDYsIDEuMCwgMC4wLCAwLjApKTsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfUzFfYzBfYzEoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIExpbmVhckxheW91dF9TMV9jMF9jMV9jMChfaW5wdXQpOwp9CmhhbGY0IENsYW1wZWRHcmFkaWVudF9TMV9jMChoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBfdG1wXzRfaW5Db2xvciA9IF9pbnB1dDsKCWhhbGY0IHQgPSBNYXRyaXhFZmZlY3RfUzFfYzBfYzEoX3RtcF80X2luQ29sb3IpOwoJaGFsZjQgb3V0Q29sb3I7CglpZiAoIWJvb2woaW50KDEpKSAmJiB0LnkgPCAwLjApIAoJewoJCW91dENvbG9yID0gaGFsZjQoMC4wKTsKCX0KCWVsc2UgaWYgKHQueCA8IDAuMCkgCgl7CgkJb3V0Q29sb3IgPSB1bGVmdEJvcmRlckNvbG9yX1MxX2MwOwoJfQoJZWxzZSBpZiAodC54ID4gMS4wKSAKCXsKCQlvdXRDb2xvciA9IHVyaWdodEJvcmRlckNvbG9yX1MxX2MwOwoJfQoJZWxzZSAKCXsKCQlvdXRDb2xvciA9IFNpbmdsZUludGVydmFsQ29sb3JpemVyX1MxX2MwX2MwKF90bXBfNF9pbkNvbG9yLCBmbG9hdDIoaGFsZjIodC54LCAwLjApKSk7Cgl9CglpZiAoYm9vbChpbnQoMSkpKSAKCXsKCQlvdXRDb2xvci54eXogKj0gb3V0Q29sb3IudzsKCX0KCXJldHVybiBoYWxmNChvdXRDb2xvcik7Cn0KaGFsZjQgRGlzYWJsZUNvdmVyYWdlQXNBbHBoYV9TMShoYWxmNCBfaW5wdXQpIAp7CglfaW5wdXQgPSBDbGFtcGVkR3JhZGllbnRfUzFfYzAoX2lucHV0KTsKCWhhbGY0IF90bXBfNV9pbkNvbG9yID0gX2lucHV0OwoJcmV0dXJuIGhhbGY0KF9pbnB1dCk7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZjb2xvcl9TMDsKCWZsb2F0IGNvdmVyYWdlID0gdmNvdmVyYWdlX1MwOwoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNChoYWxmKGNvdmVyYWdlKSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBEaXNhYmxlQ292ZXJhZ2VBc0FscGhhX1MxKG91dHB1dENvbG9yX1MwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSAoaGFsZjQoMS4wKSAtIG91dHB1dF9TMSkgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAEAAAACAAAAHBvc2l0aW9uCAAAAGNvdmVyYWdlBQAAAGNvbG9yAAAACgAAAGxvY2FsQ29vcmQAAAEAAAAAAAAA","AYQA5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAAAAOQAAAAAAAQAAAABAMQAAAAA":"CAAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgAAAAACAgAAaW4gZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfUzA7CmluIGhhbGY0IHZpbkNvbG9yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgQ2lyY2xlR2VvbWV0cnlQcm9jZXNzb3IKCWZsb2F0NCBjaXJjbGVFZGdlOwoJY2lyY2xlRWRnZSA9IHZpbkNpcmNsZUVkZ2VfUzA7CgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmluQ29sb3JfUzA7CglmbG9hdCBkID0gbGVuZ3RoKGNpcmNsZUVkZ2UueHkpOwoJaGFsZiBkaXN0YW5jZVRvT3V0ZXJFZGdlID0gaGFsZihjaXJjbGVFZGdlLnogKiAoMS4wIC0gZCkpOwoJaGFsZiBlZGdlQWxwaGEgPSBzYXR1cmF0ZShkaXN0YW5jZVRvT3V0ZXJFZGdlKTsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoZWRnZUFscGhhKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAwAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAMAAAAaW5DaXJjbGVFZGdlAQAAAAAAAAA=","BYIBQAAABQAAIAABBYAAAEIXBAAP777777777777AAAAAAAAAAAABUABAAAAAEAAAAAIBEABAAAAA":"CAAAAExTS1M+AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwpvdXQgaGFsZjQgdmNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBEZWZhdWx0R2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IGNvbG9yID0gaW5Db2xvcjsKCXZjb2xvcl9TMCA9IGNvbG9yOwoJZmxvYXQyIF90bXBfMV9pblBvc2l0aW9uID0gaW5Qb3NpdGlvbjsKCWZsb2F0MiBfdG1wXzNfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247Cglza19Qb3NpdGlvbiA9IF90bXBfMV9pblBvc2l0aW9uLnh5MDE7Cn0KAAAAAAAAHgEAAGluIGhhbGY0IHZjb2xvcl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIERlZmF1bHRHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZjb2xvcl9TMDsKCWNvbnN0IGhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoMSk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAIAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IAAQAAAAAAAAA=","AYQQ5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAAAAOQAAAAAAAQAAAABAMQAAAAA":"CAAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgAAAACTAgAAaW4gZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfUzA7CmluIGhhbGY0IHZpbkNvbG9yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgQ2lyY2xlR2VvbWV0cnlQcm9jZXNzb3IKCWZsb2F0NCBjaXJjbGVFZGdlOwoJY2lyY2xlRWRnZSA9IHZpbkNpcmNsZUVkZ2VfUzA7CgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmluQ29sb3JfUzA7CglmbG9hdCBkID0gbGVuZ3RoKGNpcmNsZUVkZ2UueHkpOwoJaGFsZiBkaXN0YW5jZVRvT3V0ZXJFZGdlID0gaGFsZihjaXJjbGVFZGdlLnogKiAoMS4wIC0gZCkpOwoJaGFsZiBlZGdlQWxwaGEgPSBzYXR1cmF0ZShkaXN0YW5jZVRvT3V0ZXJFZGdlKTsKCWhhbGYgZGlzdGFuY2VUb0lubmVyRWRnZSA9IGhhbGYoY2lyY2xlRWRnZS56ICogKGQgLSBjaXJjbGVFZGdlLncpKTsKCWhhbGYgaW5uZXJBbHBoYSA9IHNhdHVyYXRlKGRpc3RhbmNlVG9Jbm5lckVkZ2UpOwoJZWRnZUFscGhhICo9IGlubmVyQWxwaGE7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KGVkZ2VBbHBoYSk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAwAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAMAAAAaW5DaXJjbGVFZGdlAQAAAAAAAAA=","GEMAAAYAAEHAAAARC4EAAAQWBQAAAAAAAAAQAAAAIBCAAAGQAEAAAAAQAAAABAEQAEAAAAA":"CAAAAExTS1NUAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiBoYWxmMyBpblNoYWRvd1BhcmFtczsKb3V0IGhhbGYzIHZpblNoYWRvd1BhcmFtc19TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBSUmVjdFNoYWRvdwoJdmluU2hhZG93UGFyYW1zX1MwID0gaW5TaGFkb3dQYXJhbXM7Cgl2aW5Db2xvcl9TMCA9IGluQ29sb3I7CglmbG9hdDIgX3RtcF8wX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgAAAAAjAgAAdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzA7CmluIGhhbGYzIHZpblNoYWRvd1BhcmFtc19TMDsKaW4gaGFsZjQgdmluQ29sb3JfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBSUmVjdFNoYWRvdwoJaGFsZjMgc2hhZG93UGFyYW1zOwoJc2hhZG93UGFyYW1zID0gdmluU2hhZG93UGFyYW1zX1MwOwoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZpbkNvbG9yX1MwOwoJaGFsZiBkID0gbGVuZ3RoKHNoYWRvd1BhcmFtcy54eSk7CglmbG9hdDIgdXYgPSBmbG9hdDIoc2hhZG93UGFyYW1zLnogKiAoMS4wIC0gZCksIDAuNSk7CgloYWxmIGZhY3RvciA9IHNhbXBsZSh1VGV4dHVyZVNhbXBsZXJfMF9TMCwgdXYpLjAwMHIuYTsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoZmFjdG9yKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAADAAAACgAAAGluUG9zaXRpb24AAAcAAABpbkNvbG9yAA4AAABpblNoYWRvd1BhcmFtcwAAAQAAAAAAAAA=","HUQAAAAAAAMAADAAAIOAAAH677776IZOCAAP577777777777777777YBAAAAAAAAAAAKAAYAAAACAAAAAAYEIBAAAA":"CAAAAExTS1PUAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7CmZsYXQgb3V0IGhhbGY0IHZjb2xvcl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgl2Y29sb3JfUzAgPSBjb2xvcjsKCXNrX1Bvc2l0aW9uID0gcG9zaXRpb24ueHkwMTsKfQoAAAAATQEAAGZsYXQgaW4gaGFsZjQgdmNvbG9yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJCXNrX0ZyYWdDb2xvciA9IHNrX0ZyYWdDb2xvci5hMDAwOwoJfQp9CgAAAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24FAAAAY29sb3IAAAABAAAAAAAAAA==","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAALUAQMAAAAAAIAAEAAAABJYQAAAAAQAAIAAAAAWCBACAABAAAAANAECAZAAEAAAAAAAAFAAMAAAABAAAAAAABBAM":"CAAAAExTS1M2AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAAAAADEGAAB1bmlmb3JtIGZsb2F0NCB1Y2xhbXBfUzFfYzBfYzBfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMF9jMDsKdW5pZm9ybSBoYWxmMiB1XzBfSW5jcmVtZW50X1MxX2MwOwp1bmlmb3JtIGhhbGY0IHVfMV9LZXJuZWxfUzFfYzBbNF07CnVuaWZvcm0gaGFsZjQgdV8yX09mZnNldHNfUzFfYzBbNF07CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzE7CmluIGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgVGV4dHVyZUVmZmVjdF9TMV9jMF9jMF9jMChoYWxmNCBfaW5wdXQsIGZsb2F0MiBfY29vcmRzKSAKewoJZmxvYXQyIGluQ29vcmQgPSBfY29vcmRzOwoJZmxvYXQyIHN1YnNldENvb3JkOwoJc3Vic2V0Q29vcmQueCA9IGluQ29vcmQueDsKCXN1YnNldENvb3JkLnkgPSBpbkNvb3JkLnk7CglmbG9hdDIgY2xhbXBlZENvb3JkOwoJY2xhbXBlZENvb3JkLnggPSBjbGFtcChzdWJzZXRDb29yZC54LCB1Y2xhbXBfUzFfYzBfYzBfYzAueCwgdWNsYW1wX1MxX2MwX2MwX2MwLnopOwoJY2xhbXBlZENvb3JkLnkgPSBzdWJzZXRDb29yZC55OwoJaGFsZjQgdGV4dHVyZUNvbG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MxLCBjbGFtcGVkQ29vcmQpOwoJcmV0dXJuIHRleHR1cmVDb2xvcjsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfUzFfYzBfYzAoaGFsZjQgX2lucHV0LCBmbG9hdDIgX2Nvb3JkcykgCnsKCXJldHVybiBUZXh0dXJlRWZmZWN0X1MxX2MwX2MwX2MwKF9pbnB1dCwgZmxvYXQzeDIodW1hdHJpeF9TMV9jMF9jMCkgKiBfY29vcmRzLnh5MSk7Cn0KaGFsZjQgR2F1c3NpYW5Db252b2x1dGlvbl9TMV9jMChoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBfM19jb2xvciA9IGhhbGY0KDAuMCk7CglmbG9hdDIgXzVfY29vcmQgPSB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKCWZvciAoaW50IF82X2kgPSAwOyAoXzZfaSA8IDEzKTsgXzZfaSsrKSAoXzNfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TMV9jMF9jMChfaW5wdXQsIChfNV9jb29yZCArIGZsb2F0MigodV8yX09mZnNldHNfUzFfYzBbKF82X2kgLyA0KV1bKF82X2kgJiAzKV0gKiB1XzBfSW5jcmVtZW50X1MxX2MwKSkpKSAqIHVfMV9LZXJuZWxfUzFfYzBbKF82X2kgLyA0KV1bKF82X2kgJiAzKV0pKTsKCXJldHVybiBfM19jb2xvcjsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIEdhdXNzaWFuQ29udm9sdXRpb25fUzFfYzAoX2lucHV0KTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMCA9IGhhbGY0KDEpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IE1hdHJpeEVmZmVjdF9TMShvdXRwdXRDb2xvcl9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0X1MxICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","HUQAAAAAAAMAADAAAIOAAAH677776IZOCAAP577777777777777777YBAAAAAAAAAAACEA2X4PLOGEAAAAAAAAACAAAAAVQQAAQAAAAAQCDIBCAAAAAAAAAAAAAHIAAAAAAAIAAAAAQGIAAAAAAA":"CAAAAExTS1PUAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7CmZsYXQgb3V0IGhhbGY0IHZjb2xvcl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgl2Y29sb3JfUzAgPSBjb2xvcjsKCXNrX1Bvc2l0aW9uID0gcG9zaXRpb24ueHkwMTsKfQoBAAAACwQAAHVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMDsKdW5pZm9ybSBoYWxmNCB1Y2lyY2xlRGF0YV9TMTsKdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzE7CmZsYXQgaW4gaGFsZjQgdmNvbG9yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBUZXh0dXJlRWZmZWN0X1MxX2MwX2MwKGhhbGY0IF9pbnB1dCwgZmxvYXQyIF9jb29yZHMpIAp7CglyZXR1cm4gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MxLCBfY29vcmRzKS4wMDByOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TMV9jMChoYWxmNCBfaW5wdXQsIGZsb2F0MiBfY29vcmRzKSAKewoJcmV0dXJuIFRleHR1cmVFZmZlY3RfUzFfYzBfYzAoX2lucHV0LCBmbG9hdDN4Mih1bWF0cml4X1MxX2MwKSAqIF9jb29yZHMueHkxKTsKfQpoYWxmNCBDaXJjbGVCbHVyX1MxKGhhbGY0IF9pbnB1dCkgCnsKCWhhbGY0IF90bXBfMF9pbkNvbG9yID0gX2lucHV0OwoJaGFsZjIgdmVjID0gaGFsZjIoKHNrX0ZyYWdDb29yZC54eSAtIGZsb2F0Mih1Y2lyY2xlRGF0YV9TMS54eSkpICogZmxvYXQodWNpcmNsZURhdGFfUzEudykpOwoJaGFsZiBkaXN0ID0gbGVuZ3RoKHZlYykgKyAoMC41IC0gdWNpcmNsZURhdGFfUzEueikgKiB1Y2lyY2xlRGF0YV9TMS53OwoJcmV0dXJuIGhhbGY0KF9pbnB1dCAqIE1hdHJpeEVmZmVjdF9TMV9jMChfdG1wXzBfaW5Db2xvciwgZmxvYXQyKGhhbGYyKGRpc3QsIDAuNSkpKS53KTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IENpcmNsZUJsdXJfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAACAAAACAAAAHBvc2l0aW9uBQAAAGNvbG9yAAAAAQAAAAAAAAA=","B2AAQAAABQAAIAABBYAAB7777777777774ABICAAAAAAAAAAAAAABUABAAAAAEAAAAAIBEABAAAAA":"CAAAAExTS1MOAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmIGluQ292ZXJhZ2U7Cm91dCBoYWxmIHZpbkNvdmVyYWdlX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBEZWZhdWx0R2VvbWV0cnlQcm9jZXNzb3IKCWZsb2F0MiBfdG1wXzFfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247Cgl2aW5Db3ZlcmFnZV9TMCA9IGluQ292ZXJhZ2U7Cglza19Qb3NpdGlvbiA9IF90bXBfMV9pblBvc2l0aW9uLnh5MDE7Cn0KAAAAAAAAZQEAAHVuaWZvcm0gaGFsZjQgdUNvbG9yX1MwOwppbiBoYWxmIHZpbkNvdmVyYWdlX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgRGVmYXVsdEdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdUNvbG9yX1MwOwoJaGFsZiBhbHBoYSA9IDEuMDsKCWFscGhhID0gdmluQ292ZXJhZ2VfUzA7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KGFscGhhKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAIAAAAKAAAAaW5Qb3NpdGlvbgAACgAAAGluQ292ZXJhZ2UAAAEAAAAAAAAA","DAQAAAAAAABGAABAYAAQAIHCAIAYAQUBAEAAAAAAEAAAAAAAAAAAAAB2AAAAAAACAAAAAEBSAAAAA":"CAAAAExTS1MWAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQyIHVBdGxhc1NpemVJbnZfUzA7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiB1c2hvcnQyIGluVGV4dHVyZUNvb3JkczsKb3V0IGZsb2F0MiB2VGV4dHVyZUNvb3Jkc19TMDsKZmxhdCBvdXQgZmxvYXQgdlRleEluZGV4X1MwOwpvdXQgaGFsZjQgdmluQ29sb3JfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIEJpdG1hcFRleHQKCWludCB0ZXhJZHggPSAwOwoJZmxvYXQyIHVub3JtVGV4Q29vcmRzID0gZmxvYXQyKGluVGV4dHVyZUNvb3Jkcy54LCBpblRleHR1cmVDb29yZHMueSk7Cgl2VGV4dHVyZUNvb3Jkc19TMCA9IHVub3JtVGV4Q29vcmRzICogdUF0bGFzU2l6ZUludl9TMDsKCXZUZXhJbmRleF9TMCA9IGZsb2F0KHRleElkeCk7Cgl2aW5Db2xvcl9TMCA9IGluQ29sb3I7CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJc2tfUG9zaXRpb24gPSBpblBvc2l0aW9uLnh5MDE7Cn0KAAAAAAAAyQEAAHVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1MwOwppbiBmbG9hdDIgdlRleHR1cmVDb29yZHNfUzA7CmZsYXQgaW4gZmxvYXQgdlRleEluZGV4X1MwOwppbiBoYWxmNCB2aW5Db2xvcl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIEJpdG1hcFRleHQKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2aW5Db2xvcl9TMDsKCWhhbGY0IHRleENvbG9yOwoJewoJCXRleENvbG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MwLCB2VGV4dHVyZUNvb3Jkc19TMCkucnJycjsKCX0KCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gdGV4Q29sb3I7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAADAAAACgAAAGluUG9zaXRpb24AAAcAAABpbkNvbG9yAA8AAABpblRleHR1cmVDb29yZHMAAQAAAAAAAAA=","HWQACAAAABAAADAAAIOAAAAADIIAAIRODAAP577774DSAIAA737777YBAAAAAAAAAAAKAAYAAAACAAAAAAACCAYAAA":"CAAAAExTS1ONAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQgY292ZXJhZ2U7CmluIGhhbGY0IGNvbG9yOwppbiBmbG9hdDQgZ2VvbVN1YnNldDsKZmxhdCBvdXQgaGFsZjQgdmNvbG9yX1MwOwpvdXQgZmxvYXQgdmNvdmVyYWdlX1MwOwpmbGF0IG91dCBmbG9hdDQgdmdlb21TdWJzZXRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQyIHBvc2l0aW9uID0gcG9zaXRpb24ueHk7Cgl2Y29sb3JfUzAgPSBjb2xvcjsKCXZjb3ZlcmFnZV9TMCA9IGNvdmVyYWdlOwoJdmdlb21TdWJzZXRfUzAgPSBnZW9tU3Vic2V0OwoJc2tfUG9zaXRpb24gPSBwb3NpdGlvbi54eTAxOwp9CgAAAAEAAACRAgAAZmxhdCBpbiBoYWxmNCB2Y29sb3JfUzA7CmluIGZsb2F0IHZjb3ZlcmFnZV9TMDsKZmxhdCBpbiBmbG9hdDQgdmdlb21TdWJzZXRfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2Y29sb3JfUzA7CglmbG9hdCBjb3ZlcmFnZSA9IHZjb3ZlcmFnZV9TMDsKCWZsb2F0NCBnZW9TdWJzZXQ7CglnZW9TdWJzZXQgPSB2Z2VvbVN1YnNldF9TMDsKCWhhbGY0IGRpc3RzNCA9IGNsYW1wKGhhbGY0KDEsIDEsIC0xLCAtMSkgKiBoYWxmNChza19GcmFnQ29vcmQueHl4eSAtIGdlb1N1YnNldCksIDAsIDEpOwoJaGFsZjIgZGlzdHMyID0gZGlzdHM0Lnh5ICsgZGlzdHM0Lnp3IC0gMTsKCWhhbGYgc3Vic2V0Q292ZXJhZ2UgPSBkaXN0czIueCAqIGRpc3RzMi55OwoJY292ZXJhZ2UgPSBtaW4oY292ZXJhZ2UsIHN1YnNldENvdmVyYWdlKTsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoaGFsZihjb3ZlcmFnZSkpOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAABAAAAAgAAABwb3NpdGlvbggAAABjb3ZlcmFnZQUAAABjb2xvcgAAAAoAAABnZW9tU3Vic2V0AAABAAAAAAAAAA==","AYAA5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAAAAOQAAAAAAAQAAAABAMQAAAAA":"CAAAAExTS1OCAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiBmbG9hdDQgaW5DaXJjbGVFZGdlOwpvdXQgZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfUzA7Cm91dCBoYWxmNCB2aW5Db2xvcl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgQ2lyY2xlR2VvbWV0cnlQcm9jZXNzb3IKCXZpbkNpcmNsZUVkZ2VfUzAgPSBpbkNpcmNsZUVkZ2U7Cgl2aW5Db2xvcl9TMCA9IGluQ29sb3I7CglmbG9hdDIgX3RtcF8wX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJZmxvYXQyIF90bXBfMl9pblBvc2l0aW9uID0gaW5Qb3NpdGlvbjsKCXNrX1Bvc2l0aW9uID0gX3RtcF8wX2luUG9zaXRpb24ueHkwMTsKfQoAAAAAAAACAgAAaW4gZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfUzA7CmluIGhhbGY0IHZpbkNvbG9yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgQ2lyY2xlR2VvbWV0cnlQcm9jZXNzb3IKCWZsb2F0NCBjaXJjbGVFZGdlOwoJY2lyY2xlRWRnZSA9IHZpbkNpcmNsZUVkZ2VfUzA7CgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmluQ29sb3JfUzA7CglmbG9hdCBkID0gbGVuZ3RoKGNpcmNsZUVkZ2UueHkpOwoJaGFsZiBkaXN0YW5jZVRvT3V0ZXJFZGdlID0gaGFsZihjaXJjbGVFZGdlLnogKiAoMS4wIC0gZCkpOwoJaGFsZiBlZGdlQWxwaGEgPSBzYXR1cmF0ZShkaXN0YW5jZVRvT3V0ZXJFZGdlKTsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoZWRnZUFscGhhKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAwAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAMAAAAaW5DaXJjbGVFZGdlAQAAAAAAAAA=","FAAQMYAAMAAAEADAAABAEYAAAICIAB5AABQAAAQAMAAAEATAAABAIIGAAEDCBYQCA4AAAAAAEAZIA62YSBDACAAAGAAAAAAAAAAABAAOAAAABAAAAAAABBAMAA":"CAAAAExTS1PUCwAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0NCByYWRpaV9zZWxlY3RvcjsKaW4gZmxvYXQ0IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHM7CmluIGZsb2F0NCBhYV9ibG9hdF9hbmRfY292ZXJhZ2U7CmluIGZsb2F0NCByYWRpaV94OwppbiBmbG9hdDQgcmFkaWlfeTsKaW4gZmxvYXQ0IHNrZXc7CmluIGZsb2F0MiB0cmFuc2xhdGVfYW5kX2xvY2Fscm90YXRlOwppbiBoYWxmNCBjb2xvcjsKZmxhdCBvdXQgaGFsZjQgdmNvbG9yX1MwOwpvdXQgZmxvYXQyIHZhcmNjb29yZF9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgRmlsbFJSZWN0T3A6OlByb2Nlc3NvcgoJdmNvbG9yX1MwID0gY29sb3I7CglmbG9hdCBhYV9ibG9hdF9tdWx0aXBsaWVyID0gMTsKCWZsb2F0MiBjb3JuZXIgPSBjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzLnh5OwoJZmxvYXQyIHJhZGl1c19vdXRzZXQgPSBjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzLnp3OwoJZmxvYXQyIGFhX2Jsb2F0X2RpcmVjdGlvbiA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS54eTsKCWZsb2F0IGlzX2xpbmVhcl9jb3ZlcmFnZSA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS53OwoJZmxvYXQyIHBpeGVsbGVuZ3RoID0gaW52ZXJzZXNxcnQoZmxvYXQyKGRvdChza2V3Lnh6LCBza2V3Lnh6KSwgZG90KHNrZXcueXcsIHNrZXcueXcpKSk7CglmbG9hdDQgbm9ybWFsaXplZF9heGlzX2RpcnMgPSBza2V3ICogcGl4ZWxsZW5ndGgueHl4eTsKCWZsb2F0MiBheGlzd2lkdGhzID0gKGFicyhub3JtYWxpemVkX2F4aXNfZGlycy54eSkgKyBhYnMobm9ybWFsaXplZF9heGlzX2RpcnMuencpKTsKCWZsb2F0MiBhYV9ibG9hdHJhZGl1cyA9IGF4aXN3aWR0aHMgKiBwaXhlbGxlbmd0aCAqIC41OwoJZmxvYXQ0IHJhZGlpX2FuZF9uZWlnaGJvcnMgPSByYWRpaV9zZWxlY3RvciogZmxvYXQ0eDQocmFkaWlfeCwgcmFkaWlfeSwgcmFkaWlfeC55eHd6LCByYWRpaV95Lnd6eXgpOwoJZmxvYXQyIHJhZGlpID0gcmFkaWlfYW5kX25laWdoYm9ycy54eTsKCWZsb2F0MiBuZWlnaGJvcl9yYWRpaSA9IHJhZGlpX2FuZF9uZWlnaGJvcnMuenc7CglmbG9hdCBjb3ZlcmFnZV9tdWx0aXBsaWVyID0gMTsKCWlmIChhbnkoZ3JlYXRlclRoYW4oYWFfYmxvYXRyYWRpdXMsIGZsb2F0MigxKSkpKSAKCXsKCQljb3JuZXIgPSBtYXgoYWJzKGNvcm5lciksIGFhX2Jsb2F0cmFkaXVzKSAqIHNpZ24oY29ybmVyKTsKCQljb3ZlcmFnZV9tdWx0aXBsaWVyID0gMSAvIChtYXgoYWFfYmxvYXRyYWRpdXMueCwgMSkgKiBtYXgoYWFfYmxvYXRyYWRpdXMueSwgMSkpOwoJCXJhZGlpID0gZmxvYXQyKDApOwoJfQoJZmxvYXQgY292ZXJhZ2UgPSBhYV9ibG9hdF9hbmRfY292ZXJhZ2UuejsKCWlmIChhbnkobGVzc1RoYW4ocmFkaWksIGFhX2Jsb2F0cmFkaXVzICogMS41KSkpIAoJewoJCXJhZGlpID0gZmxvYXQyKDApOwoJCWFhX2Jsb2F0X2RpcmVjdGlvbiA9IHNpZ24oY29ybmVyKTsKCQlpZiAoY292ZXJhZ2UgPiAuNSkgCgkJewoJCQlhYV9ibG9hdF9kaXJlY3Rpb24gPSAtYWFfYmxvYXRfZGlyZWN0aW9uOwoJCX0KCQlpc19saW5lYXJfY292ZXJhZ2UgPSAxOwoJfQoJZWxzZSAKCXsKCQlyYWRpaSA9IGNsYW1wKHJhZGlpLCBwaXhlbGxlbmd0aCAqIDEuNSwgMiAtIHBpeGVsbGVuZ3RoICogMS41KTsKCQluZWlnaGJvcl9yYWRpaSA9IGNsYW1wKG5laWdoYm9yX3JhZGlpLCBwaXhlbGxlbmd0aCAqIDEuNSwgMiAtIHBpeGVsbGVuZ3RoICogMS41KTsKCQlmbG9hdDIgc3BhY2luZyA9IDIgLSByYWRpaSAtIG5laWdoYm9yX3JhZGlpOwoJCWZsb2F0MiBleHRyYV9wYWQgPSBtYXgocGl4ZWxsZW5ndGggKiAuMDYyNSAtIHNwYWNpbmcsIGZsb2F0MigwKSk7CgkJcmFkaWkgLT0gZXh0cmFfcGFkICogLjU7Cgl9CglmbG9hdDIgYWFfb3V0c2V0ID0gYWFfYmxvYXRfZGlyZWN0aW9uICogYWFfYmxvYXRyYWRpdXMgKiBhYV9ibG9hdF9tdWx0aXBsaWVyOwoJZmxvYXQyIHZlcnRleHBvcyA9IGNvcm5lciArIHJhZGl1c19vdXRzZXQgKiByYWRpaSArIGFhX291dHNldDsKCWlmIChjb3ZlcmFnZSA+IC41KSAKCXsKCQlpZiAoYWFfYmxvYXRfZGlyZWN0aW9uLnggIT0gMCAmJiB2ZXJ0ZXhwb3MueCAqIGNvcm5lci54IDwgMCkgCgkJewoJCQlmbG9hdCBiYWNrc2V0ID0gYWJzKHZlcnRleHBvcy54KTsKCQkJdmVydGV4cG9zLnggPSAwOwoJCQl2ZXJ0ZXhwb3MueSArPSBiYWNrc2V0ICogc2lnbihjb3JuZXIueSkgKiBwaXhlbGxlbmd0aC55L3BpeGVsbGVuZ3RoLng7CgkJCWNvdmVyYWdlID0gKGNvdmVyYWdlIC0gLjUpICogYWJzKGNvcm5lci54KSAvIChhYnMoY29ybmVyLngpICsgYmFja3NldCkgKyAuNTsKCQl9CgkJaWYgKGFhX2Jsb2F0X2RpcmVjdGlvbi55ICE9IDAgJiYgdmVydGV4cG9zLnkgKiBjb3JuZXIueSA8IDApIAoJCXsKCQkJZmxvYXQgYmFja3NldCA9IGFicyh2ZXJ0ZXhwb3MueSk7CgkJCXZlcnRleHBvcy55ID0gMDsKCQkJdmVydGV4cG9zLnggKz0gYmFja3NldCAqIHNpZ24oY29ybmVyLngpICogcGl4ZWxsZW5ndGgueC9waXhlbGxlbmd0aC55OwoJCQljb3ZlcmFnZSA9IChjb3ZlcmFnZSAtIC41KSAqIGFicyhjb3JuZXIueSkgLyAoYWJzKGNvcm5lci55KSArIGJhY2tzZXQpICsgLjU7CgkJfQoJfQoJZmxvYXQyeDIgc2tld21hdHJpeCA9IGZsb2F0MngyKHNrZXcueHksIHNrZXcuencpOwoJZmxvYXQyIGRldmNvb3JkID0gdmVydGV4cG9zICogc2tld21hdHJpeCArIHRyYW5zbGF0ZV9hbmRfbG9jYWxyb3RhdGUueHk7CglpZiAoMCAhPSBpc19saW5lYXJfY292ZXJhZ2UpIAoJewoJCXZhcmNjb29yZF9TMC54eSA9IGZsb2F0MigwLCBjb3ZlcmFnZSAqIGNvdmVyYWdlX211bHRpcGxpZXIpOwoJfQoJZWxzZSAKCXsKCQlmbG9hdDIgYXJjY29vcmQgPSAxIC0gYWJzKHJhZGl1c19vdXRzZXQpICsgYWFfb3V0c2V0L3JhZGlpICogY29ybmVyOwoJCXZhcmNjb29yZF9TMC54eSA9IGZsb2F0MihhcmNjb29yZC54KzEsIGFyY2Nvb3JkLnkpOwoJfQoJc2tfUG9zaXRpb24gPSBkZXZjb29yZC54eTAxOwp9CgEAAAB3BQAAY29uc3QgaW50IGtGaWxsQldfUzEgPSAwOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQldfUzEgPSAyOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQUFfUzEgPSAzOwp1bmlmb3JtIGZsb2F0NCB1cmVjdFVuaWZvcm1fUzE7CmZsYXQgaW4gaGFsZjQgdmNvbG9yX1MwOwppbiBmbG9hdDIgdmFyY2Nvb3JkX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBSZWN0X1MxKGhhbGY0IF9pbnB1dCkgCnsKCWhhbGY0IF90bXBfMF9pbkNvbG9yID0gX2lucHV0OwoJaGFsZiBjb3ZlcmFnZTsKCWlmIChpbnQoMSkgPT0ga0ZpbGxCV19TMSB8fCBpbnQoMSkgPT0ga0ludmVyc2VGaWxsQldfUzEpIAoJewoJCWNvdmVyYWdlID0gaGFsZihhbGwoZ3JlYXRlclRoYW4oZmxvYXQ0KHNrX0ZyYWdDb29yZC54eSwgdXJlY3RVbmlmb3JtX1MxLnp3KSwgZmxvYXQ0KHVyZWN0VW5pZm9ybV9TMS54eSwgc2tfRnJhZ0Nvb3JkLnh5KSkpID8gMSA6IDApOwoJfQoJZWxzZSAKCXsKCQloYWxmNCBkaXN0czQgPSBjbGFtcChoYWxmNCgxLjAsIDEuMCwgLTEuMCwgLTEuMCkgKiBoYWxmNChza19GcmFnQ29vcmQueHl4eSAtIHVyZWN0VW5pZm9ybV9TMSksIDAuMCwgMS4wKTsKCQloYWxmMiBkaXN0czIgPSAoZGlzdHM0Lnh5ICsgZGlzdHM0Lnp3KSAtIDEuMDsKCQljb3ZlcmFnZSA9IGRpc3RzMi54ICogZGlzdHMyLnk7Cgl9CglpZiAoaW50KDEpID09IGtJbnZlcnNlRmlsbEJXX1MxIHx8IGludCgxKSA9PSBrSW52ZXJzZUZpbGxBQV9TMSkgCgl7CgkJY292ZXJhZ2UgPSAxLjAgLSBjb3ZlcmFnZTsKCX0KCXJldHVybiBoYWxmNChfaW5wdXQgKiBjb3ZlcmFnZSk7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIEZpbGxSUmVjdE9wOjpQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2Y29sb3JfUzA7CglmbG9hdCB4X3BsdXNfMT12YXJjY29vcmRfUzAueCwgeT12YXJjY29vcmRfUzAueTsKCWhhbGYgY292ZXJhZ2U7CglpZiAoMCA9PSB4X3BsdXNfMSkgCgl7CgkJY292ZXJhZ2UgPSBoYWxmKHkpOwoJfQoJZWxzZSAKCXsKCQlmbG9hdCBmbiA9IHhfcGx1c18xICogKHhfcGx1c18xIC0gMik7CgkJZm4gPSBmbWEoeSx5LCBmbik7CgkJZmxvYXQgZm53aWR0aCA9IGZ3aWR0aChmbik7CgkJY292ZXJhZ2UgPSAuNSAtIGhhbGYoZm4vZm53aWR0aCk7CgkJY292ZXJhZ2UgPSBjbGFtcChjb3ZlcmFnZSwgMCwgMSk7Cgl9CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KGNvdmVyYWdlKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IFJlY3RfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAIAAAADgAAAHJhZGlpX3NlbGVjdG9yAAAZAAAAY29ybmVyX2FuZF9yYWRpdXNfb3V0c2V0cwAAABUAAABhYV9ibG9hdF9hbmRfY292ZXJhZ2UAAAAHAAAAcmFkaWlfeAAHAAAAcmFkaWlfeQAEAAAAc2tldxkAAAB0cmFuc2xhdGVfYW5kX2xvY2Fscm90YXRlAAAABQAAAGNvbG9yAAAAAQAAAAAAAAA=","HUJAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAQAAAAAEQQGABZAA6IAAAAACAAAAAADUAAAAAAAEAAAAAIDEAAAAAAA":"CAAAAExTS1PlAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmxvY2FsQ29vcmRfUzAgPSBsb2NhbENvb3JkOwoJc2tfUG9zaXRpb24gPSBwb3NpdGlvbi54eTAxOwp9CgAAAAEAAABPAwAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CnVuaWZvcm0gc2FtcGxlckV4dGVybmFsT0VTIHVUZXh0dXJlU2FtcGxlcl8wX1MwOwppbiBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IENpcmN1bGFyUlJlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGR4eTAgPSB1aW5uZXJSZWN0X1MxLkxUIC0gc2tfRnJhZ0Nvb3JkLnh5OwoJZmxvYXQyIGR4eTEgPSBza19GcmFnQ29vcmQueHkgLSB1aW5uZXJSZWN0X1MxLlJCOwoJZmxvYXQyIGR4eSA9IG1heChtYXgoZHh5MCwgZHh5MSksIDAuMCk7CgloYWxmIGFscGhhID0gaGFsZihzYXR1cmF0ZSh1cmFkaXVzUGx1c0hhbGZfUzEueCAtIGxlbmd0aChkeHkpKSk7CglyZXR1cm4gX2lucHV0ICogYWxwaGE7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzAgPSBoYWxmNCgxKTsKCWZsb2F0MiB0ZXhDb29yZDsKCXRleENvb3JkID0gdmxvY2FsQ29vcmRfUzA7CglvdXRwdXRDb2xvcl9TMCA9ICgoc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MwLCB0ZXhDb29yZCkgKiBoYWxmNCgxKSkpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IENpcmN1bGFyUlJlY3RfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAACAAAACAAAAHBvc2l0aW9uCgAAAGxvY2FsQ29vcmQAAAEAAAAAAAAA","AYQQ5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAACAMQAHOMFARUBIAADAAAAAAAAAAAAAAHIAAAAAAAIAAAAAQGIAA":"CAAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgEAAACtBQAAY29uc3QgaW50IGtGaWxsQldfUzEgPSAwOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQldfUzEgPSAyOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQUFfUzEgPSAzOwp1bmlmb3JtIGZsb2F0NCB1cmVjdFVuaWZvcm1fUzE7CmluIGZsb2F0NCB2aW5DaXJjbGVFZGdlX1MwOwppbiBoYWxmNCB2aW5Db2xvcl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgUmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBfdG1wXzBfaW5Db2xvciA9IF9pbnB1dDsKCWhhbGYgY292ZXJhZ2U7CglpZiAoaW50KDEpID09IGtGaWxsQldfUzEgfHwgaW50KDEpID09IGtJbnZlcnNlRmlsbEJXX1MxKSAKCXsKCQljb3ZlcmFnZSA9IGhhbGYoYWxsKGdyZWF0ZXJUaGFuKGZsb2F0NChza19GcmFnQ29vcmQueHksIHVyZWN0VW5pZm9ybV9TMS56dyksIGZsb2F0NCh1cmVjdFVuaWZvcm1fUzEueHksIHNrX0ZyYWdDb29yZC54eSkpKSA/IDEgOiAwKTsKCX0KCWVsc2UgCgl7CgkJaGFsZjQgZGlzdHM0ID0gY2xhbXAoaGFsZjQoMS4wLCAxLjAsIC0xLjAsIC0xLjApICogaGFsZjQoc2tfRnJhZ0Nvb3JkLnh5eHkgLSB1cmVjdFVuaWZvcm1fUzEpLCAwLjAsIDEuMCk7CgkJaGFsZjIgZGlzdHMyID0gKGRpc3RzNC54eSArIGRpc3RzNC56dykgLSAxLjA7CgkJY292ZXJhZ2UgPSBkaXN0czIueCAqIGRpc3RzMi55OwoJfQoJaWYgKGludCgxKSA9PSBrSW52ZXJzZUZpbGxCV19TMSB8fCBpbnQoMSkgPT0ga0ludmVyc2VGaWxsQUFfUzEpIAoJewoJCWNvdmVyYWdlID0gMS4wIC0gY292ZXJhZ2U7Cgl9CglyZXR1cm4gaGFsZjQoX2lucHV0ICogY292ZXJhZ2UpOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQ0IGNpcmNsZUVkZ2U7CgljaXJjbGVFZGdlID0gdmluQ2lyY2xlRWRnZV9TMDsKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2aW5Db2xvcl9TMDsKCWZsb2F0IGQgPSBsZW5ndGgoY2lyY2xlRWRnZS54eSk7CgloYWxmIGRpc3RhbmNlVG9PdXRlckVkZ2UgPSBoYWxmKGNpcmNsZUVkZ2UueiAqICgxLjAgLSBkKSk7CgloYWxmIGVkZ2VBbHBoYSA9IHNhdHVyYXRlKGRpc3RhbmNlVG9PdXRlckVkZ2UpOwoJaGFsZiBkaXN0YW5jZVRvSW5uZXJFZGdlID0gaGFsZihjaXJjbGVFZGdlLnogKiAoZCAtIGNpcmNsZUVkZ2UudykpOwoJaGFsZiBpbm5lckFscGhhID0gc2F0dXJhdGUoZGlzdGFuY2VUb0lubmVyRWRnZSk7CgllZGdlQWxwaGEgKj0gaW5uZXJBbHBoYTsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoZWRnZUFscGhhKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IFJlY3RfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IADAAAAGluQ2lyY2xlRWRnZQEAAAAAAAAA","JABAAAAABAAACAABBYAAAKAAAMAAGEAAAABRAEAAAEHCAAAAAAAABCAAAAAABAEQAEAAAAA":"CAAAAExTS1MbFgAAY29uc3QgZmxvYXQgUFJFQ0lTSU9OID0gNC4wMDAwMDA7CmNvbnN0IGZsb2F0IE1BWF9GSVhFRF9SRVNPTFZFX0xFVkVMID0gNS4wMDAwMDA7CmNvbnN0IGZsb2F0IE1BWF9GSVhFRF9TRUdNRU5UUyA9IDMyLjAwMDAwMDsKdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVhZmZpbmVNYXRyaXhfUzA7CnVuaWZvcm0gZmxvYXQyIHV0cmFuc2xhdGVfUzA7CmluIGZsb2F0MiByZXNvbHZlTGV2ZWxfYW5kX2lkeDsKaW4gZmxvYXQ0IHAwMTsKaW4gZmxvYXQ0IHAyMzsKaW4gZmxvYXQyIGZhblBvaW50QXR0cmliOwovLyBSZXR1cm5zIHRoZSBsZW5ndGggc3F1YXJlZCBvZiB0aGUgbGFyZ2VzdCBmb3J3YXJkIGRpZmZlcmVuY2UgZnJvbSBXYW5nJ3MgY3ViaWMgZm9ybXVsYS4KZmxvYXQgd2FuZ3NfZm9ybXVsYV9tYXhfZmRpZmZfcG93MihmbG9hdDIgcDAsIGZsb2F0MiBwMSwgZmxvYXQyIHAyLCBmbG9hdDIgcDMsICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmxvYXQyeDIgbWF0cml4KSAKewoJZmxvYXQyIGQwID0gbWF0cml4ICogKGZtYShmbG9hdDIoLTIpLCBwMSwgcDIpICsgcDApOwoJZmxvYXQyIGQxID0gbWF0cml4ICogKGZtYShmbG9hdDIoLTIpLCBwMiwgcDMpICsgcDEpOwoJcmV0dXJuIG1heChkb3QoZDAsZDApLCBkb3QoZDEsZDEpKTsKfQpmbG9hdCB3YW5nc19mb3JtdWxhX2N1YmljKGZsb2F0IF9wcmVjaXNpb25fLCBmbG9hdDIgcDAsIGZsb2F0MiBwMSwgZmxvYXQyIHAyLCBmbG9hdDIgcDMsICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmxvYXQyeDIgbWF0cml4KSAKewoJZmxvYXQgbSA9IHdhbmdzX2Zvcm11bGFfbWF4X2ZkaWZmX3BvdzIocDAsIHAxLCBwMiwgcDMsIG1hdHJpeCk7CglyZXR1cm4gbWF4KGNlaWwoc3FydCgwLjc1MDAwMCAqIF9wcmVjaXNpb25fICogc3FydChtKSkpLCAxLjApOwp9CmZsb2F0IHdhbmdzX2Zvcm11bGFfY3ViaWNfbG9nMihmbG9hdCBfcHJlY2lzaW9uXywgZmxvYXQyIHAwLCBmbG9hdDIgcDEsIGZsb2F0MiBwMiwgZmxvYXQyIHAzLCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmxvYXQyeDIgbWF0cml4KSAKewoJZmxvYXQgbSA9IHdhbmdzX2Zvcm11bGFfbWF4X2ZkaWZmX3BvdzIocDAsIHAxLCBwMiwgcDMsIG1hdHJpeCk7CglyZXR1cm4gY2VpbChsb2cyKG1heCgwLjU2MjUwMCAqIF9wcmVjaXNpb25fICogX3ByZWNpc2lvbl8gKiBtLCAxLjApKSAqIC4yNSk7Cn0KZmxvYXQgd2FuZ3NfZm9ybXVsYV9jb25pY19wb3cyKGZsb2F0IF9wcmVjaXNpb25fLCBmbG9hdDIgcDAsIGZsb2F0MiBwMSwgZmxvYXQyIHAyLCBmbG9hdCB3KSAKewoJLy8gVHJhbnNsYXRlIHRoZSBib3VuZGluZyBib3ggY2VudGVyIHRvIHRoZSBvcmlnaW4uCglmbG9hdDIgQyA9IChtaW4obWluKHAwLCBwMSksIHAyKSArIG1heChtYXgocDAsIHAxKSwgcDIpKSAqIDAuNTsKCXAwIC09IEM7CglwMSAtPSBDOwoJcDIgLT0gQzsKCS8vIENvbXB1dGUgbWF4IGxlbmd0aC4KCWZsb2F0IG0gPSBzcXJ0KG1heChtYXgoZG90KHAwLHAwKSwgZG90KHAxLHAxKSksIGRvdChwMixwMikpKTsKCS8vIENvbXB1dGUgZm9yd2FyZCBkaWZmZXJlbmNlcy4KCWZsb2F0MiBkcCA9IGZtYShmbG9hdDIoLTIuMCAqIHcpLCBwMSwgcDApICsgcDI7CglmbG9hdCBkdyA9IGFicyhmbWEoLTIuMCwgdywgMi4wKSk7CgkvLyBDb21wdXRlIG51bWVyYXRvciBhbmQgZGVub21pbmF0b3IgZm9yIHBhcmFtZXRyaWMgc3RlcCBzaXplIG9mIGxpbmVhcml6YXRpb24uIEhlcmUsIHRoZQoJLy8gZXBzaWxvbiByZWZlcmVuY2VkIGZyb20gdGhlIGNpdGVkIHBhcGVyIGlzIDEvcHJlY2lzaW9uLgoJZmxvYXQgcnBfbWludXNfMSA9IG1heCgwLjAsIGZtYShtLCBfcHJlY2lzaW9uXywgLTEuMCkpOwoJZmxvYXQgbnVtZXIgPSBsZW5ndGgoZHApICogX3ByZWNpc2lvbl8gKyBycF9taW51c18xICogZHc7CglmbG9hdCBkZW5vbSA9IDQgKiBtaW4odywgMS4wKTsKCXJldHVybiBudW1lci9kZW5vbTsKfQpmbG9hdCB3YW5nc19mb3JtdWxhX2NvbmljKGZsb2F0IF9wcmVjaXNpb25fLCBmbG9hdDIgcDAsIGZsb2F0MiBwMSwgZmxvYXQyIHAyLCBmbG9hdCB3KSAKewoJZmxvYXQgbjIgPSB3YW5nc19mb3JtdWxhX2NvbmljX3BvdzIoX3ByZWNpc2lvbl8sIHAwLCBwMSwgcDIsIHcpOwoJcmV0dXJuIG1heChjZWlsKHNxcnQobjIpKSwgMS4wKTsKfQpmbG9hdCB3YW5nc19mb3JtdWxhX2NvbmljX2xvZzIoZmxvYXQgX3ByZWNpc2lvbl8sIGZsb2F0MiBwMCwgZmxvYXQyIHAxLCBmbG9hdDIgcDIsIGZsb2F0IHcpIAp7CglmbG9hdCBuMiA9IHdhbmdzX2Zvcm11bGFfY29uaWNfcG93MihfcHJlY2lzaW9uXywgcDAsIHAxLCBwMiwgdyk7CglyZXR1cm4gY2VpbChsb2cyKG1heChuMiwgMS4wKSkgKiAuNSk7Cn0KYm9vbCBpc19jb25pY19jdXJ2ZSgpIAp7CglyZXR1cm4gaXNpbmYocDIzLncpOwp9CmJvb2wgaXNfdHJpYW5ndWxhcl9jb25pY19jdXJ2ZSgpIAp7CglyZXR1cm4gaXNpbmYocDIzLnopOwp9CmZsb2F0IGxkZXhwX3BvcnRhYmxlKGZsb2F0IHgsIGZsb2F0IHApIAp7CglyZXR1cm4gbGRleHAoeCwgaW50KHApKTsKfQp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciB0ZXNzZWxsYXRlX01pZGRsZU91dFNoYWRlcgoJZmxvYXQyeDIgQUZGSU5FX01BVFJJWCA9IGZsb2F0MngyKHVhZmZpbmVNYXRyaXhfUzApOwoJZmxvYXQyIFRSQU5TTEFURSA9IHV0cmFuc2xhdGVfUzA7CglmbG9hdCByZXNvbHZlTGV2ZWwgPSByZXNvbHZlTGV2ZWxfYW5kX2lkeC54OwoJZmxvYXQgaWR4SW5SZXNvbHZlTGV2ZWwgPSByZXNvbHZlTGV2ZWxfYW5kX2lkeC55OwoJZmxvYXQyIGxvY2FsY29vcmQ7CgkvLyBBIG5lZ2F0aXZlIHJlc29sdmUgbGV2ZWwgbWVhbnMgdGhpcyBpcyB0aGUgZmFuIHBvaW50LgoJaWYgKHJlc29sdmVMZXZlbCA8IDApIAoJewoJCWxvY2FsY29vcmQgPSBmYW5Qb2ludEF0dHJpYjsKCX0KCWVsc2UgICAgICAgICAgICBpZiAoaXNfdHJpYW5ndWxhcl9jb25pY19jdXJ2ZSgpKSAKCXsKCQkvLyBUaGlzIHBhdGNoIGlzIGFuIGV4YWN0IHRyaWFuZ2xlLgoJCWxvY2FsY29vcmQgPSAocmVzb2x2ZUxldmVsICE9IDApICAgICAgPyBwMDEuencgICAgICAgICAgICAgICAgICAgICAgICAgICA6IChpZHhJblJlc29sdmVMZXZlbCAhPSAwKSA/IHAyMy54eSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDogcDAxLnh5OwoJfQoJZWxzZSAKCXsKCQlmbG9hdDIgcDA9cDAxLnh5LCBwMT1wMDEuencsIHAyPXAyMy54eSwgcDM9cDIzLnp3OwoJCWZsb2F0IHcgPSAtMTsKCQkvLyB3IDwgMCB0ZWxscyB1cyB0byB0cmVhdCB0aGUgaW5zdGFuY2UgYXMgYW4gaW50ZWdyYWwgY3ViaWMuCgkJZmxvYXQgbWF4UmVzb2x2ZUxldmVsOwoJCWlmIChpc19jb25pY19jdXJ2ZSgpKSAKCQl7CgkJCS8vIENvbmljcyBhcmUgMyBwb2ludHMsIHdpdGggdGhlIHdlaWdodCBpbiBwMy4KCQkJdyA9IHAzLng7CgkJCW1heFJlc29sdmVMZXZlbCA9IHdhbmdzX2Zvcm11bGFfY29uaWNfbG9nMihQUkVDSVNJT04sIEFGRklORV9NQVRSSVggKiBwMCwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEFGRklORV9NQVRSSVggKiBwMSwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEFGRklORV9NQVRSSVggKiBwMiwgdyk7CgkJCXAxICo9IHc7CgkJCS8vIFVucHJvamVjdCBwMS4KCQkJcDMgPSBwMjsKCQkJLy8gRHVwbGljYXRlIHRoZSBlbmRwb2ludCBmb3Igc2hhcmVkIGNvZGUgdGhhdCBhbHNvIHJ1bnMgb24gY3ViaWNzLgoJCX0KCQllbHNlIAoJCXsKCQkJLy8gVGhlIHBhdGNoIGlzIGFuIGludGVncmFsIGN1YmljLgoJCQltYXhSZXNvbHZlTGV2ZWwgPSB3YW5nc19mb3JtdWxhX2N1YmljX2xvZzIoUFJFQ0lTSU9OLCBwMCwgcDEsIHAyLCBwMywgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBBRkZJTkVfTUFUUklYKTsKCQl9CgkJaWYgKHJlc29sdmVMZXZlbCA+IG1heFJlc29sdmVMZXZlbCkgCgkJewoJCQkvLyBUaGlzIHZlcnRleCBpcyBhdCBhIGhpZ2hlciByZXNvbHZlIGxldmVsIHRoYW4gd2UgbmVlZC4gRGVtb3RlIHRvIGEgbG93ZXIKCQkJLy8gcmVzb2x2ZUxldmVsLCB3aGljaCB3aWxsIHByb2R1Y2UgYSBkZWdlbmVyYXRlIHRyaWFuZ2xlLgoJCQlpZHhJblJlc29sdmVMZXZlbCA9IGZsb29yKGxkZXhwX3BvcnRhYmxlKGlkeEluUmVzb2x2ZUxldmVsLCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXhSZXNvbHZlTGV2ZWwgLSByZXNvbHZlTGV2ZWwpKTsKCQkJcmVzb2x2ZUxldmVsID0gbWF4UmVzb2x2ZUxldmVsOwoJCX0KCQkvLyBQcm9tb3RlIG91ciBsb2NhdGlvbiB0byBhIGRpc2NyZXRlIHBvc2l0aW9uIGluIHRoZSBtYXhpbXVtIGZpeGVkIHJlc29sdmUgbGV2ZWwuCgkJLy8gVGhpcyBpcyBleHRyYSBwYXJhbm9pYSB0byBlbnN1cmUgd2UgZ2V0IHRoZSBleGFjdCBzYW1lIGZwMzIgY29vcmRpbmF0ZXMgZm9yCgkJLy8gY29sb2NhdGVkIHBvaW50cyBmcm9tIGRpZmZlcmVudCByZXNvbHZlIGxldmVscyAoZS5nLiwgdGhlIHZlcnRpY2VzIFQ9My80IGFuZAoJCS8vIFQ9Ni84IHNob3VsZCBiZSBleGFjdGx5IGNvbG9jYXRlZCkuCgkJZmxvYXQgZml4ZWRWZXJ0ZXhJRCA9IGZsb29yKC41ICsgbGRleHBfcG9ydGFibGUoICAgICAgICAgICAgICAgICAgICAgICAgaWR4SW5SZXNvbHZlTGV2ZWwsIE1BWF9GSVhFRF9SRVNPTFZFX0xFVkVMIC0gcmVzb2x2ZUxldmVsKSk7CgkJaWYgKDAgPCBmaXhlZFZlcnRleElEICYmIGZpeGVkVmVydGV4SUQgPCBNQVhfRklYRURfU0VHTUVOVFMpIAoJCXsKCQkJZmxvYXQgVCA9IGZpeGVkVmVydGV4SUQgKiAoMSAvIE1BWF9GSVhFRF9TRUdNRU5UUyk7CgkJCS8vIEV2YWx1YXRlIGF0IFQuIFVzZSBEZSBDYXN0ZWxqYXUncyBmb3IgaXRzIGFjY3VyYWN5IGFuZCBzdGFiaWxpdHkuCgkJCWZsb2F0MiBhYiA9IG1peChwMCwgcDEsIFQpOwoJCQlmbG9hdDIgYmMgPSBtaXgocDEsIHAyLCBUKTsKCQkJZmxvYXQyIGNkID0gbWl4KHAyLCBwMywgVCk7CgkJCWZsb2F0MiBhYmMgPSBtaXgoYWIsIGJjLCBUKTsKCQkJZmxvYXQyIGJjZCA9IG1peChiYywgY2QsIFQpOwoJCQlmbG9hdDIgYWJjZCA9IG1peChhYmMsIGJjZCwgVCk7CgkJCS8vIEV2YWx1YXRlIHRoZSBjb25pYyB3ZWlnaHQgYXQgVC4KCQkJZmxvYXQgdSA9IG1peCgxLjAsIHcsIFQpOwoJCQlmbG9hdCB2ID0gdyArIDEgLSB1OwoJCQkvLyA9PSBtaXgodywgMSwgVCkKCQkJZmxvYXQgdXYgPSBtaXgodSwgdiwgVCk7CgkJCWxvY2FsY29vcmQgPSAodyA8IDApID8gLypjdWJpYyovIGFiY2QgOiAvKmNvbmljKi8gYWJjL3V2OwoJCX0KCQllbHNlIAoJCXsKCQkJbG9jYWxjb29yZCA9IChmaXhlZFZlcnRleElEID09IDApID8gcDAueHkgOiBwMy54eTsKCQl9Cgl9CglmbG9hdDIgdmVydGV4cG9zID0gQUZGSU5FX01BVFJJWCAqIGxvY2FsY29vcmQgKyBUUkFOU0xBVEU7Cglza19Qb3NpdGlvbiA9IHZlcnRleHBvcy54eTAxOwp9CgAAAAAA4QAAAHVuaWZvcm0gaGFsZjQgdWNvbG9yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgdGVzc2VsbGF0ZV9NaWRkbGVPdXRTaGFkZXIKCWhhbGY0IG91dHB1dENvbG9yX1MwID0gdWNvbG9yX1MwOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogRGlzYWJsZSBDb2xvcgoJfQp9CgAAAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAQAAAAUAAAAcmVzb2x2ZUxldmVsX2FuZF9pZHgDAAAAcDAxAAMAAABwMjMADgAAAGZhblBvaW50QXR0cmliAAABAAAAAAAAAA==","HWQACAAAABAAADAAAIOAAAAADIIAAIRODAAP577774DSAIAA737777YBAAAAAAAAAAAHEADZAAAAAAIAAAAAAOQAAAAAAAQAAAABAMQAAAAAA":"CAAAAExTS1ONAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQgY292ZXJhZ2U7CmluIGhhbGY0IGNvbG9yOwppbiBmbG9hdDQgZ2VvbVN1YnNldDsKZmxhdCBvdXQgaGFsZjQgdmNvbG9yX1MwOwpvdXQgZmxvYXQgdmNvdmVyYWdlX1MwOwpmbGF0IG91dCBmbG9hdDQgdmdlb21TdWJzZXRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQyIHBvc2l0aW9uID0gcG9zaXRpb24ueHk7Cgl2Y29sb3JfUzAgPSBjb2xvcjsKCXZjb3ZlcmFnZV9TMCA9IGNvdmVyYWdlOwoJdmdlb21TdWJzZXRfUzAgPSBnZW9tU3Vic2V0OwoJc2tfUG9zaXRpb24gPSBwb3NpdGlvbi54eTAxOwp9CgAAAAEAAAAgBAAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CmZsYXQgaW4gaGFsZjQgdmNvbG9yX1MwOwppbiBmbG9hdCB2Y292ZXJhZ2VfUzA7CmZsYXQgaW4gZmxvYXQ0IHZnZW9tU3Vic2V0X1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBDaXJjdWxhclJSZWN0X1MxKGhhbGY0IF9pbnB1dCkgCnsKCWZsb2F0MiBkeHkwID0gdWlubmVyUmVjdF9TMS5MVCAtIHNrX0ZyYWdDb29yZC54eTsKCWZsb2F0MiBkeHkxID0gc2tfRnJhZ0Nvb3JkLnh5IC0gdWlubmVyUmVjdF9TMS5SQjsKCWZsb2F0MiBkeHkgPSBtYXgobWF4KGR4eTAsIGR4eTEpLCAwLjApOwoJaGFsZiBhbHBoYSA9IGhhbGYoc2F0dXJhdGUodXJhZGl1c1BsdXNIYWxmX1MxLnggLSBsZW5ndGgoZHh5KSkpOwoJcmV0dXJuIF9pbnB1dCAqIGFscGhhOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2Y29sb3JfUzA7CglmbG9hdCBjb3ZlcmFnZSA9IHZjb3ZlcmFnZV9TMDsKCWZsb2F0NCBnZW9TdWJzZXQ7CglnZW9TdWJzZXQgPSB2Z2VvbVN1YnNldF9TMDsKCWhhbGY0IGRpc3RzNCA9IGNsYW1wKGhhbGY0KDEsIDEsIC0xLCAtMSkgKiBoYWxmNChza19GcmFnQ29vcmQueHl4eSAtIGdlb1N1YnNldCksIDAsIDEpOwoJaGFsZjIgZGlzdHMyID0gZGlzdHM0Lnh5ICsgZGlzdHM0Lnp3IC0gMTsKCWhhbGYgc3Vic2V0Q292ZXJhZ2UgPSBkaXN0czIueCAqIGRpc3RzMi55OwoJY292ZXJhZ2UgPSBtaW4oY292ZXJhZ2UsIHN1YnNldENvdmVyYWdlKTsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoaGFsZihjb3ZlcmFnZSkpOwoJaGFsZjQgb3V0cHV0X1MxOwoJb3V0cHV0X1MxID0gQ2lyY3VsYXJSUmVjdF9TMShvdXRwdXRDb3ZlcmFnZV9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRfUzE7Cgl9Cn0KAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAABAAAAAgAAABwb3NpdGlvbggAAABjb3ZlcmFnZQUAAABjb2xvcgAAAAoAAABnZW9tU3Vic2V0AAABAAAAAAAAAA==","FAAQMYAAMAAAEADAAABAEYAAAICIAB5AABQAAAQAMAAAEATAAABAIIGAAEDCBYQCA4AAAAAAEADZABYAAAIAAAAAACQAGAAAAAQAAAAAAAQQG":"CAAAAExTS1PUCwAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0NCByYWRpaV9zZWxlY3RvcjsKaW4gZmxvYXQ0IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHM7CmluIGZsb2F0NCBhYV9ibG9hdF9hbmRfY292ZXJhZ2U7CmluIGZsb2F0NCByYWRpaV94OwppbiBmbG9hdDQgcmFkaWlfeTsKaW4gZmxvYXQ0IHNrZXc7CmluIGZsb2F0MiB0cmFuc2xhdGVfYW5kX2xvY2Fscm90YXRlOwppbiBoYWxmNCBjb2xvcjsKZmxhdCBvdXQgaGFsZjQgdmNvbG9yX1MwOwpvdXQgZmxvYXQyIHZhcmNjb29yZF9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgRmlsbFJSZWN0T3A6OlByb2Nlc3NvcgoJdmNvbG9yX1MwID0gY29sb3I7CglmbG9hdCBhYV9ibG9hdF9tdWx0aXBsaWVyID0gMTsKCWZsb2F0MiBjb3JuZXIgPSBjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzLnh5OwoJZmxvYXQyIHJhZGl1c19vdXRzZXQgPSBjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzLnp3OwoJZmxvYXQyIGFhX2Jsb2F0X2RpcmVjdGlvbiA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS54eTsKCWZsb2F0IGlzX2xpbmVhcl9jb3ZlcmFnZSA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS53OwoJZmxvYXQyIHBpeGVsbGVuZ3RoID0gaW52ZXJzZXNxcnQoZmxvYXQyKGRvdChza2V3Lnh6LCBza2V3Lnh6KSwgZG90KHNrZXcueXcsIHNrZXcueXcpKSk7CglmbG9hdDQgbm9ybWFsaXplZF9heGlzX2RpcnMgPSBza2V3ICogcGl4ZWxsZW5ndGgueHl4eTsKCWZsb2F0MiBheGlzd2lkdGhzID0gKGFicyhub3JtYWxpemVkX2F4aXNfZGlycy54eSkgKyBhYnMobm9ybWFsaXplZF9heGlzX2RpcnMuencpKTsKCWZsb2F0MiBhYV9ibG9hdHJhZGl1cyA9IGF4aXN3aWR0aHMgKiBwaXhlbGxlbmd0aCAqIC41OwoJZmxvYXQ0IHJhZGlpX2FuZF9uZWlnaGJvcnMgPSByYWRpaV9zZWxlY3RvciogZmxvYXQ0eDQocmFkaWlfeCwgcmFkaWlfeSwgcmFkaWlfeC55eHd6LCByYWRpaV95Lnd6eXgpOwoJZmxvYXQyIHJhZGlpID0gcmFkaWlfYW5kX25laWdoYm9ycy54eTsKCWZsb2F0MiBuZWlnaGJvcl9yYWRpaSA9IHJhZGlpX2FuZF9uZWlnaGJvcnMuenc7CglmbG9hdCBjb3ZlcmFnZV9tdWx0aXBsaWVyID0gMTsKCWlmIChhbnkoZ3JlYXRlclRoYW4oYWFfYmxvYXRyYWRpdXMsIGZsb2F0MigxKSkpKSAKCXsKCQljb3JuZXIgPSBtYXgoYWJzKGNvcm5lciksIGFhX2Jsb2F0cmFkaXVzKSAqIHNpZ24oY29ybmVyKTsKCQljb3ZlcmFnZV9tdWx0aXBsaWVyID0gMSAvIChtYXgoYWFfYmxvYXRyYWRpdXMueCwgMSkgKiBtYXgoYWFfYmxvYXRyYWRpdXMueSwgMSkpOwoJCXJhZGlpID0gZmxvYXQyKDApOwoJfQoJZmxvYXQgY292ZXJhZ2UgPSBhYV9ibG9hdF9hbmRfY292ZXJhZ2UuejsKCWlmIChhbnkobGVzc1RoYW4ocmFkaWksIGFhX2Jsb2F0cmFkaXVzICogMS41KSkpIAoJewoJCXJhZGlpID0gZmxvYXQyKDApOwoJCWFhX2Jsb2F0X2RpcmVjdGlvbiA9IHNpZ24oY29ybmVyKTsKCQlpZiAoY292ZXJhZ2UgPiAuNSkgCgkJewoJCQlhYV9ibG9hdF9kaXJlY3Rpb24gPSAtYWFfYmxvYXRfZGlyZWN0aW9uOwoJCX0KCQlpc19saW5lYXJfY292ZXJhZ2UgPSAxOwoJfQoJZWxzZSAKCXsKCQlyYWRpaSA9IGNsYW1wKHJhZGlpLCBwaXhlbGxlbmd0aCAqIDEuNSwgMiAtIHBpeGVsbGVuZ3RoICogMS41KTsKCQluZWlnaGJvcl9yYWRpaSA9IGNsYW1wKG5laWdoYm9yX3JhZGlpLCBwaXhlbGxlbmd0aCAqIDEuNSwgMiAtIHBpeGVsbGVuZ3RoICogMS41KTsKCQlmbG9hdDIgc3BhY2luZyA9IDIgLSByYWRpaSAtIG5laWdoYm9yX3JhZGlpOwoJCWZsb2F0MiBleHRyYV9wYWQgPSBtYXgocGl4ZWxsZW5ndGggKiAuMDYyNSAtIHNwYWNpbmcsIGZsb2F0MigwKSk7CgkJcmFkaWkgLT0gZXh0cmFfcGFkICogLjU7Cgl9CglmbG9hdDIgYWFfb3V0c2V0ID0gYWFfYmxvYXRfZGlyZWN0aW9uICogYWFfYmxvYXRyYWRpdXMgKiBhYV9ibG9hdF9tdWx0aXBsaWVyOwoJZmxvYXQyIHZlcnRleHBvcyA9IGNvcm5lciArIHJhZGl1c19vdXRzZXQgKiByYWRpaSArIGFhX291dHNldDsKCWlmIChjb3ZlcmFnZSA+IC41KSAKCXsKCQlpZiAoYWFfYmxvYXRfZGlyZWN0aW9uLnggIT0gMCAmJiB2ZXJ0ZXhwb3MueCAqIGNvcm5lci54IDwgMCkgCgkJewoJCQlmbG9hdCBiYWNrc2V0ID0gYWJzKHZlcnRleHBvcy54KTsKCQkJdmVydGV4cG9zLnggPSAwOwoJCQl2ZXJ0ZXhwb3MueSArPSBiYWNrc2V0ICogc2lnbihjb3JuZXIueSkgKiBwaXhlbGxlbmd0aC55L3BpeGVsbGVuZ3RoLng7CgkJCWNvdmVyYWdlID0gKGNvdmVyYWdlIC0gLjUpICogYWJzKGNvcm5lci54KSAvIChhYnMoY29ybmVyLngpICsgYmFja3NldCkgKyAuNTsKCQl9CgkJaWYgKGFhX2Jsb2F0X2RpcmVjdGlvbi55ICE9IDAgJiYgdmVydGV4cG9zLnkgKiBjb3JuZXIueSA8IDApIAoJCXsKCQkJZmxvYXQgYmFja3NldCA9IGFicyh2ZXJ0ZXhwb3MueSk7CgkJCXZlcnRleHBvcy55ID0gMDsKCQkJdmVydGV4cG9zLnggKz0gYmFja3NldCAqIHNpZ24oY29ybmVyLngpICogcGl4ZWxsZW5ndGgueC9waXhlbGxlbmd0aC55OwoJCQljb3ZlcmFnZSA9IChjb3ZlcmFnZSAtIC41KSAqIGFicyhjb3JuZXIueSkgLyAoYWJzKGNvcm5lci55KSArIGJhY2tzZXQpICsgLjU7CgkJfQoJfQoJZmxvYXQyeDIgc2tld21hdHJpeCA9IGZsb2F0MngyKHNrZXcueHksIHNrZXcuencpOwoJZmxvYXQyIGRldmNvb3JkID0gdmVydGV4cG9zICogc2tld21hdHJpeCArIHRyYW5zbGF0ZV9hbmRfbG9jYWxyb3RhdGUueHk7CglpZiAoMCAhPSBpc19saW5lYXJfY292ZXJhZ2UpIAoJewoJCXZhcmNjb29yZF9TMC54eSA9IGZsb2F0MigwLCBjb3ZlcmFnZSAqIGNvdmVyYWdlX211bHRpcGxpZXIpOwoJfQoJZWxzZSAKCXsKCQlmbG9hdDIgYXJjY29vcmQgPSAxIC0gYWJzKHJhZGl1c19vdXRzZXQpICsgYWFfb3V0c2V0L3JhZGlpICogY29ybmVyOwoJCXZhcmNjb29yZF9TMC54eSA9IGZsb2F0MihhcmNjb29yZC54KzEsIGFyY2Nvb3JkLnkpOwoJfQoJc2tfUG9zaXRpb24gPSBkZXZjb29yZC54eTAxOwp9CgEAAADsAwAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CmZsYXQgaW4gaGFsZjQgdmNvbG9yX1MwOwppbiBmbG9hdDIgdmFyY2Nvb3JkX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBDaXJjdWxhclJSZWN0X1MxKGhhbGY0IF9pbnB1dCkgCnsKCWZsb2F0MiBkeHkwID0gdWlubmVyUmVjdF9TMS5MVCAtIHNrX0ZyYWdDb29yZC54eTsKCWZsb2F0MiBkeHkxID0gc2tfRnJhZ0Nvb3JkLnh5IC0gdWlubmVyUmVjdF9TMS5SQjsKCWZsb2F0MiBkeHkgPSBtYXgobWF4KGR4eTAsIGR4eTEpLCAwLjApOwoJaGFsZiBhbHBoYSA9IGhhbGYoc2F0dXJhdGUodXJhZGl1c1BsdXNIYWxmX1MxLnggLSBsZW5ndGgoZHh5KSkpOwoJcmV0dXJuIF9pbnB1dCAqIGFscGhhOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBGaWxsUlJlY3RPcDo6UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJZmxvYXQgeF9wbHVzXzE9dmFyY2Nvb3JkX1MwLngsIHk9dmFyY2Nvb3JkX1MwLnk7CgloYWxmIGNvdmVyYWdlOwoJaWYgKDAgPT0geF9wbHVzXzEpIAoJewoJCWNvdmVyYWdlID0gaGFsZih5KTsKCX0KCWVsc2UgCgl7CgkJZmxvYXQgZm4gPSB4X3BsdXNfMSAqICh4X3BsdXNfMSAtIDIpOwoJCWZuID0gZm1hKHkseSwgZm4pOwoJCWZsb2F0IGZud2lkdGggPSBmd2lkdGgoZm4pOwoJCWNvdmVyYWdlID0gLjUgLSBoYWxmKGZuL2Zud2lkdGgpOwoJCWNvdmVyYWdlID0gY2xhbXAoY292ZXJhZ2UsIDAsIDEpOwoJfQoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNChjb3ZlcmFnZSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBDaXJjdWxhclJSZWN0X1MxKG91dHB1dENvdmVyYWdlX1MwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dF9TMTsKCX0KfQoBAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAIAAAADgAAAHJhZGlpX3NlbGVjdG9yAAAZAAAAY29ybmVyX2FuZF9yYWRpdXNfb3V0c2V0cwAAABUAAABhYV9ibG9hdF9hbmRfY292ZXJhZ2UAAAAHAAAAcmFkaWlfeAAHAAAAcmFkaWlfeQAEAAAAc2tldxkAAAB0cmFuc2xhdGVfYW5kX2xvY2Fscm90YXRlAAAABQAAAGNvbG9yAAAAAQAAAAAAAAA=","AYTRVAADQAAAOAEARAFQJAABBADAAAILBYAACCYUQD777777777767YAAAAAAAAAAAAOQAAAAAAAQAAAABAMQAAAAA":"CAAAAExTS1NyAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7CmluIGhhbGYzIGluQ2xpcFBsYW5lOwppbiBoYWxmMyBpbklzZWN0UGxhbmU7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGYzIHZpbkNsaXBQbGFuZV9TMDsKb3V0IGhhbGYzIHZpbklzZWN0UGxhbmVfUzA7Cm91dCBoYWxmNCB2aW5Db2xvcl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgQ2lyY2xlR2VvbWV0cnlQcm9jZXNzb3IKCXZpbkNpcmNsZUVkZ2VfUzAgPSBpbkNpcmNsZUVkZ2U7Cgl2aW5DbGlwUGxhbmVfUzAgPSBpbkNsaXBQbGFuZTsKCXZpbklzZWN0UGxhbmVfUzAgPSBpbklzZWN0UGxhbmU7Cgl2aW5Db2xvcl9TMCA9IGluQ29sb3I7CglmbG9hdDIgX3RtcF8wX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJZmxvYXQyIF90bXBfMV9pblBvc2l0aW9uID0gdWxvY2FsTWF0cml4X1MwLnh6ICogaW5Qb3NpdGlvbiArIHVsb2NhbE1hdHJpeF9TMC55dzsKCXNrX1Bvc2l0aW9uID0gX3RtcF8wX2luUG9zaXRpb24ueHkwMTsKfQoAAAAAAAD1AwAAaW4gZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfUzA7CmluIGhhbGYzIHZpbkNsaXBQbGFuZV9TMDsKaW4gaGFsZjMgdmluSXNlY3RQbGFuZV9TMDsKaW4gaGFsZjQgdmluQ29sb3JfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQ0IGNpcmNsZUVkZ2U7CgljaXJjbGVFZGdlID0gdmluQ2lyY2xlRWRnZV9TMDsKCWhhbGYzIGNsaXBQbGFuZTsKCWNsaXBQbGFuZSA9IHZpbkNsaXBQbGFuZV9TMDsKCWhhbGYzIGlzZWN0UGxhbmU7Cglpc2VjdFBsYW5lID0gdmluSXNlY3RQbGFuZV9TMDsKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2aW5Db2xvcl9TMDsKCWZsb2F0IGQgPSBsZW5ndGgoY2lyY2xlRWRnZS54eSk7CgloYWxmIGRpc3RhbmNlVG9PdXRlckVkZ2UgPSBoYWxmKGNpcmNsZUVkZ2UueiAqICgxLjAgLSBkKSk7CgloYWxmIGVkZ2VBbHBoYSA9IHNhdHVyYXRlKGRpc3RhbmNlVG9PdXRlckVkZ2UpOwoJaGFsZiBkaXN0YW5jZVRvSW5uZXJFZGdlID0gaGFsZihjaXJjbGVFZGdlLnogKiAoZCAtIGNpcmNsZUVkZ2UudykpOwoJaGFsZiBpbm5lckFscGhhID0gc2F0dXJhdGUoZGlzdGFuY2VUb0lubmVyRWRnZSk7CgllZGdlQWxwaGEgKj0gaW5uZXJBbHBoYTsKCWhhbGYgY2xpcCA9IGhhbGYoc2F0dXJhdGUoY2lyY2xlRWRnZS56ICogZG90KGNpcmNsZUVkZ2UueHksIGNsaXBQbGFuZS54eSkgKyBjbGlwUGxhbmUueikpOwoJY2xpcCAqPSBoYWxmKHNhdHVyYXRlKGNpcmNsZUVkZ2UueiAqIGRvdChjaXJjbGVFZGdlLnh5LCBpc2VjdFBsYW5lLnh5KSArIGlzZWN0UGxhbmUueikpOwoJZWRnZUFscGhhICo9IGNsaXA7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KGVkZ2VBbHBoYSk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAFAAAACgAAAGluUG9zaXRpb24AAAcAAABpbkNvbG9yAAwAAABpbkNpcmNsZUVkZ2ULAAAAaW5DbGlwUGxhbmUADAAAAGluSXNlY3RQbGFuZQEAAAAAAAAA","HUQAAAAAAAMAADAAAIOAAAH677776IZOCAAP577777777777777777YBAAAAAAAAAAAKAAYAAAACAAAAAAACCAYAAA":"CAAAAExTS1PUAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7CmZsYXQgb3V0IGhhbGY0IHZjb2xvcl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgl2Y29sb3JfUzAgPSBjb2xvcjsKCXNrX1Bvc2l0aW9uID0gcG9zaXRpb24ueHkwMTsKfQoAAAAAKQEAAGZsYXQgaW4gaGFsZjQgdmNvbG9yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24FAAAAY29sb3IAAAABAAAAAAAAAA==","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAALUAQMAAAAAAAAAEAAAABJYQAAAAAAAAIAAAAAWCBAAAABAAAAANAECAZAAAAAAAAAAAFAAMAAAABAAAAAAABBAM":"CAAAAExTS1M2AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAAAAAPIEAAB1bmlmb3JtIGZsb2F0M3gzIHVtYXRyaXhfUzFfYzBfYzA7CnVuaWZvcm0gaGFsZjIgdV8wX0luY3JlbWVudF9TMV9jMDsKdW5pZm9ybSBoYWxmNCB1XzFfS2VybmVsX1MxX2MwWzRdOwp1bmlmb3JtIGhhbGY0IHVfMl9PZmZzZXRzX1MxX2MwWzRdOwp1bmlmb3JtIGZsb2F0M3gzIHVtYXRyaXhfUzE7CnVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1MxOwppbiBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IFRleHR1cmVFZmZlY3RfUzFfYzBfYzBfYzAoaGFsZjQgX2lucHV0LCBmbG9hdDIgX2Nvb3JkcykgCnsKCXJldHVybiBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfUzEsIF9jb29yZHMpOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TMV9jMF9jMChoYWxmNCBfaW5wdXQsIGZsb2F0MiBfY29vcmRzKSAKewoJcmV0dXJuIFRleHR1cmVFZmZlY3RfUzFfYzBfYzBfYzAoX2lucHV0LCBmbG9hdDN4Mih1bWF0cml4X1MxX2MwX2MwKSAqIF9jb29yZHMueHkxKTsKfQpoYWxmNCBHYXVzc2lhbkNvbnZvbHV0aW9uX1MxX2MwKGhhbGY0IF9pbnB1dCkgCnsKCWhhbGY0IF8zX2NvbG9yID0gaGFsZjQoMC4wKTsKCWZsb2F0MiBfNV9jb29yZCA9IHZUcmFuc2Zvcm1lZENvb3Jkc18yX1MwOwoJZm9yIChpbnQgXzZfaSA9IDA7IChfNl9pIDwgMTMpOyBfNl9pKyspIChfM19jb2xvciArPSAoTWF0cml4RWZmZWN0X1MxX2MwX2MwKF9pbnB1dCwgKF81X2Nvb3JkICsgZmxvYXQyKCh1XzJfT2Zmc2V0c19TMV9jMFsoXzZfaSAvIDQpXVsoXzZfaSAmIDMpXSAqIHVfMF9JbmNyZW1lbnRfUzFfYzApKSkpICogdV8xX0tlcm5lbF9TMV9jMFsoXzZfaSAvIDQpXVsoXzZfaSAmIDMpXSkpOwoJcmV0dXJuIF8zX2NvbG9yOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CglyZXR1cm4gR2F1c3NpYW5Db252b2x1dGlvbl9TMV9jMChfaW5wdXQpOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwID0gaGFsZjQoMSk7Cgljb25zdCBoYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KDEpOwoJaGFsZjQgb3V0cHV0X1MxOwoJb3V0cHV0X1MxID0gTWF0cml4RWZmZWN0X1MxKG91dHB1dENvbG9yX1MwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRfUzEgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24KAAAAbG9jYWxDb29yZAAAAQAAAAAAAAA=","HTQAAGAABBYAAAEIXBAAAGEAMAAAAAAAAAAAAAAAQAHAAAAAQAAAAAAAQQGAAAAA":"CAAAAExTS1M/AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiBmbG9hdDQgaW5RdWFkRWRnZTsKb3V0IGZsb2F0NCB2UXVhZEVkZ2VfUzA7Cm91dCBoYWxmNCB2aW5Db2xvcl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZEVkZ2UKCXZRdWFkRWRnZV9TMCA9IGluUXVhZEVkZ2U7Cgl2aW5Db2xvcl9TMCA9IGluQ29sb3I7CglmbG9hdDIgX3RtcF8wX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgABAAAAHQMAAGluIGZsb2F0NCB2UXVhZEVkZ2VfUzA7CmluIGhhbGY0IHZpbkNvbG9yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZEVkZ2UKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2aW5Db2xvcl9TMDsKCWhhbGYgZWRnZUFscGhhOwoJaGFsZjIgZHV2ZHggPSBoYWxmMihkRmR4KHZRdWFkRWRnZV9TMC54eSkpOwoJaGFsZjIgZHV2ZHkgPSBoYWxmMihkRmR5KHZRdWFkRWRnZV9TMC54eSkpOwoJaWYgKHZRdWFkRWRnZV9TMC56ID4gMC4wICYmIHZRdWFkRWRnZV9TMC53ID4gMC4wKSAKCXsKCQllZGdlQWxwaGEgPSBoYWxmKG1pbihtaW4odlF1YWRFZGdlX1MwLnosIHZRdWFkRWRnZV9TMC53KSArIDAuNSwgMS4wKSk7Cgl9CgllbHNlIAoJewoJCWhhbGYyIGdGID0gaGFsZjIoaGFsZigyLjAqdlF1YWRFZGdlX1MwLngqZHV2ZHgueCAtIGR1dmR4LnkpLCAgICAgICAgICAgICAgICAgaGFsZigyLjAqdlF1YWRFZGdlX1MwLngqZHV2ZHkueCAtIGR1dmR5LnkpKTsKCQllZGdlQWxwaGEgPSBoYWxmKHZRdWFkRWRnZV9TMC54KnZRdWFkRWRnZV9TMC54IC0gdlF1YWRFZGdlX1MwLnkpOwoJCWVkZ2VBbHBoYSA9IHNhdHVyYXRlKDAuNSAtIGVkZ2VBbHBoYSAvIGxlbmd0aChnRikpOwoJfQoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNChlZGdlQWxwaGEpOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAwAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAKAAAAaW5RdWFkRWRnZQAAAQAAAAAAAAA=","HUJAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAQAAAAAAQQGACQAGAAAAAQAAAAAAAQQGAAA":"CAAAAExTS1PlAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmxvY2FsQ29vcmRfUzAgPSBsb2NhbENvb3JkOwoJc2tfUG9zaXRpb24gPSBwb3NpdGlvbi54eTAxOwp9CgAAAAAAAAC3AQAAdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzA7CmluIGZsb2F0MiB2bG9jYWxDb29yZF9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzAgPSBoYWxmNCgxKTsKCWZsb2F0MiB0ZXhDb29yZDsKCXRleENvb3JkID0gdmxvY2FsQ29vcmRfUzA7CglvdXRwdXRDb2xvcl9TMCA9ICgoc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MwLCB0ZXhDb29yZCkgKiBoYWxmNCgxKSkpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAACAAAACAAAAHBvc2l0aW9uCgAAAGxvY2FsQ29vcmQAAAEAAAAAAAAA","HUJAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAQAAAAAAQQGABZAA6IAAAAACAAAAAADUAAAAAAAEAAAAAIDEAAAAAAA":"CAAAAExTS1PlAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmxvY2FsQ29vcmRfUzAgPSBsb2NhbENvb3JkOwoJc2tfUG9zaXRpb24gPSBwb3NpdGlvbi54eTAxOwp9CgAAAAEAAABGAwAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CnVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1MwOwppbiBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IENpcmN1bGFyUlJlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGR4eTAgPSB1aW5uZXJSZWN0X1MxLkxUIC0gc2tfRnJhZ0Nvb3JkLnh5OwoJZmxvYXQyIGR4eTEgPSBza19GcmFnQ29vcmQueHkgLSB1aW5uZXJSZWN0X1MxLlJCOwoJZmxvYXQyIGR4eSA9IG1heChtYXgoZHh5MCwgZHh5MSksIDAuMCk7CgloYWxmIGFscGhhID0gaGFsZihzYXR1cmF0ZSh1cmFkaXVzUGx1c0hhbGZfUzEueCAtIGxlbmd0aChkeHkpKSk7CglyZXR1cm4gX2lucHV0ICogYWxwaGE7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzAgPSBoYWxmNCgxKTsKCWZsb2F0MiB0ZXhDb29yZDsKCXRleENvb3JkID0gdmxvY2FsQ29vcmRfUzA7CglvdXRwdXRDb2xvcl9TMCA9ICgoc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MwLCB0ZXhDb29yZCkgKiBoYWxmNCgxKSkpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IENpcmN1bGFyUlJlY3RfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgAAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","AYQA5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAACAFBQATAAAAAAFAAMAAAABAAAAAAABBAMAAAAA":"CAAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgEAAABYBAAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBmbG9hdDIgdWludlJhZGlpWFlfUzE7CmluIGZsb2F0NCB2aW5DaXJjbGVFZGdlX1MwOwppbiBoYWxmNCB2aW5Db2xvcl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgRWxsaXB0aWNhbFJSZWN0X1MxKGhhbGY0IF9pbnB1dCkgCnsKCWZsb2F0MiBkeHkwID0gdWlubmVyUmVjdF9TMS5MVCAtIHNrX0ZyYWdDb29yZC54eTsKCWZsb2F0MiBkeHkxID0gc2tfRnJhZ0Nvb3JkLnh5IC0gdWlubmVyUmVjdF9TMS5SQjsKCWZsb2F0MiBkeHkgPSBtYXgobWF4KGR4eTAsIGR4eTEpLCAwLjApOwoJZmxvYXQyIFogPSBkeHkgKiB1aW52UmFkaWlYWV9TMS54eTsKCWhhbGYgaW1wbGljaXQgPSBoYWxmKGRvdChaLCBkeHkpIC0gMS4wKTsKCWhhbGYgZ3JhZF9kb3QgPSBoYWxmKDQuMCAqIGRvdChaLCBaKSk7CglncmFkX2RvdCA9IG1heChncmFkX2RvdCwgMS4wZS00KTsKCWhhbGYgYXBwcm94X2Rpc3QgPSBpbXBsaWNpdCAqIGhhbGYoaW52ZXJzZXNxcnQoZ3JhZF9kb3QpKTsKCWhhbGYgYWxwaGEgPSBjbGFtcCgwLjUgKyBhcHByb3hfZGlzdCwgMC4wLCAxLjApOwoJcmV0dXJuIF9pbnB1dCAqIGFscGhhOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQ0IGNpcmNsZUVkZ2U7CgljaXJjbGVFZGdlID0gdmluQ2lyY2xlRWRnZV9TMDsKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2aW5Db2xvcl9TMDsKCWZsb2F0IGQgPSBsZW5ndGgoY2lyY2xlRWRnZS54eSk7CgloYWxmIGRpc3RhbmNlVG9PdXRlckVkZ2UgPSBoYWxmKGNpcmNsZUVkZ2UueiAqICgxLjAgLSBkKSk7CgloYWxmIGVkZ2VBbHBoYSA9IHNhdHVyYXRlKGRpc3RhbmNlVG9PdXRlckVkZ2UpOwoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNChlZGdlQWxwaGEpOwoJaGFsZjQgb3V0cHV0X1MxOwoJb3V0cHV0X1MxID0gRWxsaXB0aWNhbFJSZWN0X1MxKG91dHB1dENvdmVyYWdlX1MwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dF9TMTsKCX0KfQoBAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAADAAAACgAAAGluUG9zaXRpb24AAAcAAABpbkNvbG9yAAwAAABpbkNpcmNsZUVkZ2UBAAAAAAAAAA==","HUQACAAAAAMAADAAAIOAAAH677776IZOCAAP577777777777777777YBAAAAAAAAAAAKAAYAAAACAAAAAAACCAYAAA":"CAAAAExTS1PPAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7Cm91dCBoYWxmNCB2Y29sb3JfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmNvbG9yX1MwID0gY29sb3I7Cglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cn0KAAAAAAAkAQAAaW4gaGFsZjQgdmNvbG9yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24FAAAAY29sb3IAAAABAAAAAAAAAA==","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAALUAQBAAAQAAAAGQCBAMQACAAAAAAAACQAGAAAAAQAAAAAAAQQGAAAAA":"CAAAAExTS1M2AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAAAAAIgDAAB1bmlmb3JtIGZsb2F0NCB1Y2xhbXBfUzFfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzE7CmluIGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgVGV4dHVyZUVmZmVjdF9TMV9jMChoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgaW5Db29yZCA9IHZUcmFuc2Zvcm1lZENvb3Jkc18yX1MwOwoJZmxvYXQyIHN1YnNldENvb3JkOwoJc3Vic2V0Q29vcmQueCA9IGluQ29vcmQueDsKCXN1YnNldENvb3JkLnkgPSBpbkNvb3JkLnk7CglmbG9hdDIgY2xhbXBlZENvb3JkOwoJY2xhbXBlZENvb3JkLnggPSBjbGFtcChzdWJzZXRDb29yZC54LCB1Y2xhbXBfUzFfYzAueCwgdWNsYW1wX1MxX2MwLnopOwoJY2xhbXBlZENvb3JkLnkgPSBzdWJzZXRDb29yZC55OwoJaGFsZjQgdGV4dHVyZUNvbG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MxLCBjbGFtcGVkQ29vcmQpOwoJcmV0dXJuIHRleHR1cmVDb2xvcjsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIFRleHR1cmVFZmZlY3RfUzFfYzAoX2lucHV0KTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMCA9IGhhbGY0KDEpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IE1hdHJpeEVmZmVjdF9TMShvdXRwdXRDb2xvcl9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0X1MxICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","HUJAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAQAAAAAAQQGAARAGQWMHGBRIAAAAABQAAAAAAAAAAHIAAAAAAAIAAAAAQGIAA":"CAAAAExTS1PlAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmxvY2FsQ29vcmRfUzAgPSBsb2NhbENvb3JkOwoJc2tfUG9zaXRpb24gPSBwb3NpdGlvbi54eTAxOwp9CgAAAAEAAABhBAAAY29uc3QgaW50IGtGaWxsQUFfUzEgPSAxOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQldfUzEgPSAyOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQUFfUzEgPSAzOwp1bmlmb3JtIGZsb2F0NCB1Y2lyY2xlX1MxOwp1bmlmb3JtIHNhbXBsZXIyRCB1VGV4dHVyZVNhbXBsZXJfMF9TMDsKaW4gZmxvYXQyIHZsb2NhbENvb3JkX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBDaXJjbGVfUzEoaGFsZjQgX2lucHV0KSAKewoJaGFsZjQgX3RtcF8wX2luQ29sb3IgPSBfaW5wdXQ7CgloYWxmIGQ7CglpZiAoaW50KDEpID09IGtJbnZlcnNlRmlsbEJXX1MxIHx8IGludCgxKSA9PSBrSW52ZXJzZUZpbGxBQV9TMSkgCgl7CgkJZCA9IGhhbGYoKGxlbmd0aCgodWNpcmNsZV9TMS54eSAtIHNrX0ZyYWdDb29yZC54eSkgKiB1Y2lyY2xlX1MxLncpIC0gMS4wKSAqIHVjaXJjbGVfUzEueik7Cgl9CgllbHNlIAoJewoJCWQgPSBoYWxmKCgxLjAgLSBsZW5ndGgoKHVjaXJjbGVfUzEueHkgLSBza19GcmFnQ29vcmQueHkpICogdWNpcmNsZV9TMS53KSkgKiB1Y2lyY2xlX1MxLnopOwoJfQoJaWYgKGludCgxKSA9PSBrRmlsbEFBX1MxIHx8IGludCgxKSA9PSBrSW52ZXJzZUZpbGxBQV9TMSkgCgl7CgkJcmV0dXJuIGhhbGY0KF9pbnB1dCAqIHNhdHVyYXRlKGQpKTsKCX0KCWVsc2UgCgl7CgkJcmV0dXJuIGhhbGY0KGQgPiAwLjUgPyBfaW5wdXQgOiBoYWxmNCgwLjApKTsKCX0KfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMCA9IGhhbGY0KDEpOwoJZmxvYXQyIHRleENvb3JkOwoJdGV4Q29vcmQgPSB2bG9jYWxDb29yZF9TMDsKCW91dHB1dENvbG9yX1MwID0gKChzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfUzAsIHRleENvb3JkKSAqIGhhbGY0KDEpKSk7Cgljb25zdCBoYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KDEpOwoJaGFsZjQgb3V0cHV0X1MxOwoJb3V0cHV0X1MxID0gQ2lyY2xlX1MxKG91dHB1dENvdmVyYWdlX1MwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dF9TMTsKCX0KfQoAAAABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAACAAAACAAAAHBvc2l0aW9uCgAAAGxvY2FsQ29vcmQAAAEAAAAAAAAA","FAAQMYAAMAAAEADAAABAEYAAAICIAB5AABQAAAQAMAAAEATAAABAIIGAAEDCBYQCA4AAAAAAEAKPABAAAAAAB2AAAAAAACAAAAAEBSAAAAAAA":"CAAAAExTS1PUCwAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0NCByYWRpaV9zZWxlY3RvcjsKaW4gZmxvYXQ0IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHM7CmluIGZsb2F0NCBhYV9ibG9hdF9hbmRfY292ZXJhZ2U7CmluIGZsb2F0NCByYWRpaV94OwppbiBmbG9hdDQgcmFkaWlfeTsKaW4gZmxvYXQ0IHNrZXc7CmluIGZsb2F0MiB0cmFuc2xhdGVfYW5kX2xvY2Fscm90YXRlOwppbiBoYWxmNCBjb2xvcjsKZmxhdCBvdXQgaGFsZjQgdmNvbG9yX1MwOwpvdXQgZmxvYXQyIHZhcmNjb29yZF9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgRmlsbFJSZWN0T3A6OlByb2Nlc3NvcgoJdmNvbG9yX1MwID0gY29sb3I7CglmbG9hdCBhYV9ibG9hdF9tdWx0aXBsaWVyID0gMTsKCWZsb2F0MiBjb3JuZXIgPSBjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzLnh5OwoJZmxvYXQyIHJhZGl1c19vdXRzZXQgPSBjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzLnp3OwoJZmxvYXQyIGFhX2Jsb2F0X2RpcmVjdGlvbiA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS54eTsKCWZsb2F0IGlzX2xpbmVhcl9jb3ZlcmFnZSA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS53OwoJZmxvYXQyIHBpeGVsbGVuZ3RoID0gaW52ZXJzZXNxcnQoZmxvYXQyKGRvdChza2V3Lnh6LCBza2V3Lnh6KSwgZG90KHNrZXcueXcsIHNrZXcueXcpKSk7CglmbG9hdDQgbm9ybWFsaXplZF9heGlzX2RpcnMgPSBza2V3ICogcGl4ZWxsZW5ndGgueHl4eTsKCWZsb2F0MiBheGlzd2lkdGhzID0gKGFicyhub3JtYWxpemVkX2F4aXNfZGlycy54eSkgKyBhYnMobm9ybWFsaXplZF9heGlzX2RpcnMuencpKTsKCWZsb2F0MiBhYV9ibG9hdHJhZGl1cyA9IGF4aXN3aWR0aHMgKiBwaXhlbGxlbmd0aCAqIC41OwoJZmxvYXQ0IHJhZGlpX2FuZF9uZWlnaGJvcnMgPSByYWRpaV9zZWxlY3RvciogZmxvYXQ0eDQocmFkaWlfeCwgcmFkaWlfeSwgcmFkaWlfeC55eHd6LCByYWRpaV95Lnd6eXgpOwoJZmxvYXQyIHJhZGlpID0gcmFkaWlfYW5kX25laWdoYm9ycy54eTsKCWZsb2F0MiBuZWlnaGJvcl9yYWRpaSA9IHJhZGlpX2FuZF9uZWlnaGJvcnMuenc7CglmbG9hdCBjb3ZlcmFnZV9tdWx0aXBsaWVyID0gMTsKCWlmIChhbnkoZ3JlYXRlclRoYW4oYWFfYmxvYXRyYWRpdXMsIGZsb2F0MigxKSkpKSAKCXsKCQljb3JuZXIgPSBtYXgoYWJzKGNvcm5lciksIGFhX2Jsb2F0cmFkaXVzKSAqIHNpZ24oY29ybmVyKTsKCQljb3ZlcmFnZV9tdWx0aXBsaWVyID0gMSAvIChtYXgoYWFfYmxvYXRyYWRpdXMueCwgMSkgKiBtYXgoYWFfYmxvYXRyYWRpdXMueSwgMSkpOwoJCXJhZGlpID0gZmxvYXQyKDApOwoJfQoJZmxvYXQgY292ZXJhZ2UgPSBhYV9ibG9hdF9hbmRfY292ZXJhZ2UuejsKCWlmIChhbnkobGVzc1RoYW4ocmFkaWksIGFhX2Jsb2F0cmFkaXVzICogMS41KSkpIAoJewoJCXJhZGlpID0gZmxvYXQyKDApOwoJCWFhX2Jsb2F0X2RpcmVjdGlvbiA9IHNpZ24oY29ybmVyKTsKCQlpZiAoY292ZXJhZ2UgPiAuNSkgCgkJewoJCQlhYV9ibG9hdF9kaXJlY3Rpb24gPSAtYWFfYmxvYXRfZGlyZWN0aW9uOwoJCX0KCQlpc19saW5lYXJfY292ZXJhZ2UgPSAxOwoJfQoJZWxzZSAKCXsKCQlyYWRpaSA9IGNsYW1wKHJhZGlpLCBwaXhlbGxlbmd0aCAqIDEuNSwgMiAtIHBpeGVsbGVuZ3RoICogMS41KTsKCQluZWlnaGJvcl9yYWRpaSA9IGNsYW1wKG5laWdoYm9yX3JhZGlpLCBwaXhlbGxlbmd0aCAqIDEuNSwgMiAtIHBpeGVsbGVuZ3RoICogMS41KTsKCQlmbG9hdDIgc3BhY2luZyA9IDIgLSByYWRpaSAtIG5laWdoYm9yX3JhZGlpOwoJCWZsb2F0MiBleHRyYV9wYWQgPSBtYXgocGl4ZWxsZW5ndGggKiAuMDYyNSAtIHNwYWNpbmcsIGZsb2F0MigwKSk7CgkJcmFkaWkgLT0gZXh0cmFfcGFkICogLjU7Cgl9CglmbG9hdDIgYWFfb3V0c2V0ID0gYWFfYmxvYXRfZGlyZWN0aW9uICogYWFfYmxvYXRyYWRpdXMgKiBhYV9ibG9hdF9tdWx0aXBsaWVyOwoJZmxvYXQyIHZlcnRleHBvcyA9IGNvcm5lciArIHJhZGl1c19vdXRzZXQgKiByYWRpaSArIGFhX291dHNldDsKCWlmIChjb3ZlcmFnZSA+IC41KSAKCXsKCQlpZiAoYWFfYmxvYXRfZGlyZWN0aW9uLnggIT0gMCAmJiB2ZXJ0ZXhwb3MueCAqIGNvcm5lci54IDwgMCkgCgkJewoJCQlmbG9hdCBiYWNrc2V0ID0gYWJzKHZlcnRleHBvcy54KTsKCQkJdmVydGV4cG9zLnggPSAwOwoJCQl2ZXJ0ZXhwb3MueSArPSBiYWNrc2V0ICogc2lnbihjb3JuZXIueSkgKiBwaXhlbGxlbmd0aC55L3BpeGVsbGVuZ3RoLng7CgkJCWNvdmVyYWdlID0gKGNvdmVyYWdlIC0gLjUpICogYWJzKGNvcm5lci54KSAvIChhYnMoY29ybmVyLngpICsgYmFja3NldCkgKyAuNTsKCQl9CgkJaWYgKGFhX2Jsb2F0X2RpcmVjdGlvbi55ICE9IDAgJiYgdmVydGV4cG9zLnkgKiBjb3JuZXIueSA8IDApIAoJCXsKCQkJZmxvYXQgYmFja3NldCA9IGFicyh2ZXJ0ZXhwb3MueSk7CgkJCXZlcnRleHBvcy55ID0gMDsKCQkJdmVydGV4cG9zLnggKz0gYmFja3NldCAqIHNpZ24oY29ybmVyLngpICogcGl4ZWxsZW5ndGgueC9waXhlbGxlbmd0aC55OwoJCQljb3ZlcmFnZSA9IChjb3ZlcmFnZSAtIC41KSAqIGFicyhjb3JuZXIueSkgLyAoYWJzKGNvcm5lci55KSArIGJhY2tzZXQpICsgLjU7CgkJfQoJfQoJZmxvYXQyeDIgc2tld21hdHJpeCA9IGZsb2F0MngyKHNrZXcueHksIHNrZXcuencpOwoJZmxvYXQyIGRldmNvb3JkID0gdmVydGV4cG9zICogc2tld21hdHJpeCArIHRyYW5zbGF0ZV9hbmRfbG9jYWxyb3RhdGUueHk7CglpZiAoMCAhPSBpc19saW5lYXJfY292ZXJhZ2UpIAoJewoJCXZhcmNjb29yZF9TMC54eSA9IGZsb2F0MigwLCBjb3ZlcmFnZSAqIGNvdmVyYWdlX211bHRpcGxpZXIpOwoJfQoJZWxzZSAKCXsKCQlmbG9hdDIgYXJjY29vcmQgPSAxIC0gYWJzKHJhZGl1c19vdXRzZXQpICsgYWFfb3V0c2V0L3JhZGlpICogY29ybmVyOwoJCXZhcmNjb29yZF9TMC54eSA9IGZsb2F0MihhcmNjb29yZC54KzEsIGFyY2Nvb3JkLnkpOwoJfQoJc2tfUG9zaXRpb24gPSBkZXZjb29yZC54eTAxOwp9CgEAAACzBAAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBmbG9hdDIgdWludlJhZGlpWFlfUzE7CmZsYXQgaW4gaGFsZjQgdmNvbG9yX1MwOwppbiBmbG9hdDIgdmFyY2Nvb3JkX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBFbGxpcHRpY2FsUlJlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGR4eTAgPSB1aW5uZXJSZWN0X1MxLkxUIC0gc2tfRnJhZ0Nvb3JkLnh5OwoJZmxvYXQyIGR4eTEgPSBza19GcmFnQ29vcmQueHkgLSB1aW5uZXJSZWN0X1MxLlJCOwoJZmxvYXQyIGR4eSA9IG1heChtYXgoZHh5MCwgZHh5MSksIDAuMCk7CglmbG9hdDIgWiA9IGR4eSAqIHVpbnZSYWRpaVhZX1MxLnh5OwoJaGFsZiBpbXBsaWNpdCA9IGhhbGYoZG90KFosIGR4eSkgLSAxLjApOwoJaGFsZiBncmFkX2RvdCA9IGhhbGYoNC4wICogZG90KFosIFopKTsKCWdyYWRfZG90ID0gbWF4KGdyYWRfZG90LCAxLjBlLTQpOwoJaGFsZiBhcHByb3hfZGlzdCA9IGltcGxpY2l0ICogaGFsZihpbnZlcnNlc3FydChncmFkX2RvdCkpOwoJaGFsZiBhbHBoYSA9IGNsYW1wKDAuNSArIGFwcHJveF9kaXN0LCAwLjAsIDEuMCk7CglyZXR1cm4gX2lucHV0ICogYWxwaGE7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIEZpbGxSUmVjdE9wOjpQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2Y29sb3JfUzA7CglmbG9hdCB4X3BsdXNfMT12YXJjY29vcmRfUzAueCwgeT12YXJjY29vcmRfUzAueTsKCWhhbGYgY292ZXJhZ2U7CglpZiAoMCA9PSB4X3BsdXNfMSkgCgl7CgkJY292ZXJhZ2UgPSBoYWxmKHkpOwoJfQoJZWxzZSAKCXsKCQlmbG9hdCBmbiA9IHhfcGx1c18xICogKHhfcGx1c18xIC0gMik7CgkJZm4gPSBmbWEoeSx5LCBmbik7CgkJZmxvYXQgZm53aWR0aCA9IGZ3aWR0aChmbik7CgkJY292ZXJhZ2UgPSAuNSAtIGhhbGYoZm4vZm53aWR0aCk7CgkJY292ZXJhZ2UgPSBjbGFtcChjb3ZlcmFnZSwgMCwgMSk7Cgl9CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KGNvdmVyYWdlKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IEVsbGlwdGljYWxSUmVjdF9TMShvdXRwdXRDb3ZlcmFnZV9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRfUzE7Cgl9Cn0KAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAgAAAAOAAAAcmFkaWlfc2VsZWN0b3IAABkAAABjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzAAAAFQAAAGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZQAAAAcAAAByYWRpaV94AAcAAAByYWRpaV95AAQAAABza2V3GQAAAHRyYW5zbGF0ZV9hbmRfbG9jYWxyb3RhdGUAAAAFAAAAY29sb3IAAAABAAAAAAAAAA==","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAADUAANAAAAAAIBAIAAAABLCIIBAAAAABAEGABBAMAACAIAAAAAAAB2AAAAAAACAAAAAEBSAAAAA":"CAAAAExTS1M8AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMDsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfM19TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzNfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxX2MwKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAADhAwAAdW5pZm9ybSBmbG9hdDQgdWNsYW1wX1MxX2MwX2MwOwp1bmlmb3JtIGZsb2F0M3gzIHVtYXRyaXhfUzFfYzA7CnVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1MxOwppbiBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzNfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IFRleHR1cmVFZmZlY3RfUzFfYzBfYzAoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGluQ29vcmQgPSB2VHJhbnNmb3JtZWRDb29yZHNfM19TMDsKCWZsb2F0MiBzdWJzZXRDb29yZDsKCXN1YnNldENvb3JkLnggPSBpbkNvb3JkLng7CglzdWJzZXRDb29yZC55ID0gaW5Db29yZC55OwoJZmxvYXQyIGNsYW1wZWRDb29yZDsKCWNsYW1wZWRDb29yZCA9IGNsYW1wKHN1YnNldENvb3JkLCB1Y2xhbXBfUzFfYzBfYzAueHksIHVjbGFtcF9TMV9jMF9jMC56dyk7CgloYWxmNCB0ZXh0dXJlQ29sb3IgPSBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfUzEsIGNsYW1wZWRDb29yZCk7CglyZXR1cm4gdGV4dHVyZUNvbG9yOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TMV9jMChoYWxmNCBfaW5wdXQpIAp7CglyZXR1cm4gVGV4dHVyZUVmZmVjdF9TMV9jMF9jMChfaW5wdXQpOwp9CmhhbGY0IEJsZW5kX1MxKGhhbGY0IF9zcmMsIGhhbGY0IF9kc3QpIAp7CglyZXR1cm4gYmxlbmRfbW9kdWxhdGUoTWF0cml4RWZmZWN0X1MxX2MwKF9zcmMpLCBfc3JjKTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMCA9IGhhbGY0KDEpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IEJsZW5kX1MxKG91dHB1dENvbG9yX1MwLCBoYWxmNCgxKSk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0X1MxICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","DAQAAAAAAABGAABAYAAQAIHCAIAYAQUBAEAAAAAAEAAAAAAAAAAAAIAHSADQAAAQAAAAAAFAAMAAAABAAAAAAABBAM":"CAAAAExTS1MWAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQyIHVBdGxhc1NpemVJbnZfUzA7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiB1c2hvcnQyIGluVGV4dHVyZUNvb3JkczsKb3V0IGZsb2F0MiB2VGV4dHVyZUNvb3Jkc19TMDsKZmxhdCBvdXQgZmxvYXQgdlRleEluZGV4X1MwOwpvdXQgaGFsZjQgdmluQ29sb3JfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIEJpdG1hcFRleHQKCWludCB0ZXhJZHggPSAwOwoJZmxvYXQyIHVub3JtVGV4Q29vcmRzID0gZmxvYXQyKGluVGV4dHVyZUNvb3Jkcy54LCBpblRleHR1cmVDb29yZHMueSk7Cgl2VGV4dHVyZUNvb3Jkc19TMCA9IHVub3JtVGV4Q29vcmRzICogdUF0bGFzU2l6ZUludl9TMDsKCXZUZXhJbmRleF9TMCA9IGZsb2F0KHRleElkeCk7Cgl2aW5Db2xvcl9TMCA9IGluQ29sb3I7CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJc2tfUG9zaXRpb24gPSBpblBvc2l0aW9uLnh5MDE7Cn0KAAABAAAAWAMAAHVuaWZvcm0gZmxvYXQ0IHVpbm5lclJlY3RfUzE7CnVuaWZvcm0gaGFsZjIgdXJhZGl1c1BsdXNIYWxmX1MxOwp1bmlmb3JtIHNhbXBsZXIyRCB1VGV4dHVyZVNhbXBsZXJfMF9TMDsKaW4gZmxvYXQyIHZUZXh0dXJlQ29vcmRzX1MwOwpmbGF0IGluIGZsb2F0IHZUZXhJbmRleF9TMDsKaW4gaGFsZjQgdmluQ29sb3JfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IENpcmN1bGFyUlJlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGR4eTAgPSB1aW5uZXJSZWN0X1MxLkxUIC0gc2tfRnJhZ0Nvb3JkLnh5OwoJZmxvYXQyIGR4eTEgPSBza19GcmFnQ29vcmQueHkgLSB1aW5uZXJSZWN0X1MxLlJCOwoJZmxvYXQyIGR4eSA9IG1heChtYXgoZHh5MCwgZHh5MSksIDAuMCk7CgloYWxmIGFscGhhID0gaGFsZihzYXR1cmF0ZSh1cmFkaXVzUGx1c0hhbGZfUzEueCAtIGxlbmd0aChkeHkpKSk7CglyZXR1cm4gX2lucHV0ICogYWxwaGE7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIEJpdG1hcFRleHQKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2aW5Db2xvcl9TMDsKCWhhbGY0IHRleENvbG9yOwoJewoJCXRleENvbG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MwLCB2VGV4dHVyZUNvb3Jkc19TMCkucnJycjsKCX0KCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gdGV4Q29sb3I7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBDaXJjdWxhclJSZWN0X1MxKG91dHB1dENvdmVyYWdlX1MwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dF9TMTsKCX0KfQoBAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAADAAAACgAAAGluUG9zaXRpb24AAAcAAABpbkNvbG9yAA8AAABpblRleHR1cmVDb29yZHMAAQAAAAAAAAA=","AYQA5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAACABYQA6AAAEAAAAAAAIADQAAAAIAAAAAAAIIDA":"CAAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgEAAACRAwAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CmluIGZsb2F0NCB2aW5DaXJjbGVFZGdlX1MwOwppbiBoYWxmNCB2aW5Db2xvcl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgQ2lyY3VsYXJSUmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgZHh5MCA9IHVpbm5lclJlY3RfUzEuTFQgLSBza19GcmFnQ29vcmQueHk7CglmbG9hdDIgZHh5MSA9IHNrX0ZyYWdDb29yZC54eSAtIHVpbm5lclJlY3RfUzEuUkI7CglmbG9hdDIgZHh5ID0gbWF4KG1heChkeHkwLCBkeHkxKSwgMC4wKTsKCWhhbGYgYWxwaGEgPSBoYWxmKHNhdHVyYXRlKHVyYWRpdXNQbHVzSGFsZl9TMS54IC0gbGVuZ3RoKGR4eSkpKTsKCXJldHVybiBfaW5wdXQgKiBhbHBoYTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgQ2lyY2xlR2VvbWV0cnlQcm9jZXNzb3IKCWZsb2F0NCBjaXJjbGVFZGdlOwoJY2lyY2xlRWRnZSA9IHZpbkNpcmNsZUVkZ2VfUzA7CgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmluQ29sb3JfUzA7CglmbG9hdCBkID0gbGVuZ3RoKGNpcmNsZUVkZ2UueHkpOwoJaGFsZiBkaXN0YW5jZVRvT3V0ZXJFZGdlID0gaGFsZihjaXJjbGVFZGdlLnogKiAoMS4wIC0gZCkpOwoJaGFsZiBlZGdlQWxwaGEgPSBzYXR1cmF0ZShkaXN0YW5jZVRvT3V0ZXJFZGdlKTsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoZWRnZUFscGhhKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IENpcmN1bGFyUlJlY3RfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IADAAAAGluQ2lyY2xlRWRnZQEAAAAAAAAA","HUQACAAAAAMAADAAAIOAAAH677776IZOCAAP577777777777777777YBAAAAAAAAAAAHEADZAAAAAAIAAAAAAOQAAAAAAAQAAAABAMQAAAAAA":"CAAAAExTS1PPAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7Cm91dCBoYWxmNCB2Y29sb3JfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmNvbG9yX1MwID0gY29sb3I7Cglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cn0KAAEAAACzAgAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CmluIGhhbGY0IHZjb2xvcl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgQ2lyY3VsYXJSUmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgZHh5MCA9IHVpbm5lclJlY3RfUzEuTFQgLSBza19GcmFnQ29vcmQueHk7CglmbG9hdDIgZHh5MSA9IHNrX0ZyYWdDb29yZC54eSAtIHVpbm5lclJlY3RfUzEuUkI7CglmbG9hdDIgZHh5ID0gbWF4KG1heChkeHkwLCBkeHkxKSwgMC4wKTsKCWhhbGYgYWxwaGEgPSBoYWxmKHNhdHVyYXRlKHVyYWRpdXNQbHVzSGFsZl9TMS54IC0gbGVuZ3RoKGR4eSkpKTsKCXJldHVybiBfaW5wdXQgKiBhbHBoYTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IENpcmN1bGFyUlJlY3RfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAACAAAACAAAAHBvc2l0aW9uBQAAAGNvbG9yAAAAAQAAAAAAAAA=","CIAAAAAAQAARQAAYQAAAAGFYQAABRAAAAEEAAAAAAARAEAEABYAAAAEAAAAAAAEEBQAAAAA":"CAAAAExTS1NVAwAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQyIHVhdGxhc19hZGp1c3RfUzA7CmluIGZsb2F0NCBmaWxsQm91bmRzOwppbiBoYWxmNCBjb2xvcjsKaW4gZmxvYXQ0IGxvY2F0aW9uczsKb3V0IGZsb2F0MiB2YXRsYXNDb29yZF9TMDsKZmxhdCBvdXQgaGFsZjQgdmNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBEcmF3QXRsYXNQYXRoU2hhZGVyCglmbG9hdDIgdW5pdENvb3JkID0gZmxvYXQyKHNrX1ZlcnRleElEICYgMSwgc2tfVmVydGV4SUQgPj4gMSk7CglmbG9hdDIgZGV2Q29vcmQgPSBtaXgoZmlsbEJvdW5kcy54eSwgZmlsbEJvdW5kcy56dywgdW5pdENvb3JkKTsKCS8vIEEgbmVnYXRpdmUgeCBjb29yZGluYXRlIGluIHRoZSBhdGxhcyBpbmRpY2F0ZXMgdGhhdCB0aGUgcGF0aCBpcyB0cmFuc3Bvc2VkLgoJLy8gV2UgYWxzbyBhZGRlZCAxIHNpbmNlIHdlIGNhbid0IG5lZ2F0ZSB6ZXJvLgoJZmxvYXQyIGF0bGFzVG9wTGVmdCA9IGZsb2F0MihhYnMobG9jYXRpb25zLngpIC0gMSwgbG9jYXRpb25zLnkpOwoJZmxvYXQyIGRldlRvcExlZnQgPSBsb2NhdGlvbnMuenc7Cglib29sIHRyYW5zcG9zZWQgPSBsb2NhdGlvbnMueCA8IDA7CglmbG9hdDIgYXRsYXNDb29yZCA9IGRldkNvb3JkIC0gZGV2VG9wTGVmdDsKCWlmICh0cmFuc3Bvc2VkKSAKCXsKCQlhdGxhc0Nvb3JkID0gYXRsYXNDb29yZC55eDsKCX0KCWF0bGFzQ29vcmQgKz0gYXRsYXNUb3BMZWZ0OwoJdmF0bGFzQ29vcmRfUzAgPSBhdGxhc0Nvb3JkICogdWF0bGFzX2FkanVzdF9TMDsKCXZjb2xvcl9TMCA9IGNvbG9yOwoJc2tfUG9zaXRpb24gPSBkZXZDb29yZC54eTAxOwp9CgAAAAAAAADKAQAAdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzA7CmluIGZsb2F0MiB2YXRsYXNDb29yZF9TMDsKZmxhdCBpbiBoYWxmNCB2Y29sb3JfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBEcmF3QXRsYXNQYXRoU2hhZGVyCgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KDEpOwoJaGFsZiBhdGxhc0NvdmVyYWdlID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MwLCB2YXRsYXNDb29yZF9TMCkuMDAwci5hOwoJb3V0cHV0Q292ZXJhZ2VfUzAgKj0gYXRsYXNDb3ZlcmFnZTsKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2Y29sb3JfUzA7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAZmlsbEJvdW5kcwAABQAAAGNvbG9yAAAACQAAAGxvY2F0aW9ucwAAAAEAAAAAAAAA","AYQA5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAAAAB3QA6AAAEAAAAAAAMAAPEAEAAABAAAAAAB2AAAAAAACAAAAAEBSAAAA":"CAAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgEAAAA2BQAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CnVuaWZvcm0gZmxvYXQ0IHVpbm5lclJlY3RfUzI7CnVuaWZvcm0gaGFsZjIgdXJhZGl1c1BsdXNIYWxmX1MyOwppbiBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKaW4gaGFsZjQgdmluQ29sb3JfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IENpcmN1bGFyUlJlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGR4eTAgPSB1aW5uZXJSZWN0X1MxLkxUIC0gc2tfRnJhZ0Nvb3JkLnh5OwoJZmxvYXQyIGR4eTEgPSBza19GcmFnQ29vcmQueHkgLSB1aW5uZXJSZWN0X1MxLlJCOwoJZmxvYXQyIGR4eSA9IG1heChtYXgoZHh5MCwgZHh5MSksIDAuMCk7CgloYWxmIGFscGhhID0gaGFsZihzYXR1cmF0ZSh1cmFkaXVzUGx1c0hhbGZfUzEueCAtIGxlbmd0aChkeHkpKSk7CglhbHBoYSA9IDEuMCAtIGFscGhhOwoJcmV0dXJuIF9pbnB1dCAqIGFscGhhOwp9CmhhbGY0IENpcmN1bGFyUlJlY3RfUzIoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGR4eTAgPSB1aW5uZXJSZWN0X1MyLkxUIC0gc2tfRnJhZ0Nvb3JkLnh5OwoJZmxvYXQyIGR4eTEgPSBza19GcmFnQ29vcmQueHkgLSB1aW5uZXJSZWN0X1MyLlJCOwoJZmxvYXQyIGR4eSA9IG1heChtYXgoZHh5MCwgZHh5MSksIDAuMCk7CgloYWxmIGFscGhhID0gaGFsZihzYXR1cmF0ZSh1cmFkaXVzUGx1c0hhbGZfUzIueCAtIGxlbmd0aChkeHkpKSk7CglyZXR1cm4gX2lucHV0ICogYWxwaGE7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIENpcmNsZUdlb21ldHJ5UHJvY2Vzc29yCglmbG9hdDQgY2lyY2xlRWRnZTsKCWNpcmNsZUVkZ2UgPSB2aW5DaXJjbGVFZGdlX1MwOwoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZpbkNvbG9yX1MwOwoJZmxvYXQgZCA9IGxlbmd0aChjaXJjbGVFZGdlLnh5KTsKCWhhbGYgZGlzdGFuY2VUb091dGVyRWRnZSA9IGhhbGYoY2lyY2xlRWRnZS56ICogKDEuMCAtIGQpKTsKCWhhbGYgZWRnZUFscGhhID0gc2F0dXJhdGUoZGlzdGFuY2VUb091dGVyRWRnZSk7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KGVkZ2VBbHBoYSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBDaXJjdWxhclJSZWN0X1MxKG91dHB1dENvdmVyYWdlX1MwKTsKCWhhbGY0IG91dHB1dF9TMjsKCW91dHB1dF9TMiA9IENpcmN1bGFyUlJlY3RfUzIob3V0cHV0X1MxKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dF9TMjsKCX0KfQoAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IADAAAAGluQ2lyY2xlRWRnZQEAAAAAAAAA","FAAQMYAAMAAAEADAAABAEYAAAICIAB5AABQAAAQAMAAAEATAAABAIIGAAEDCBYQCA4AAAAAAAA5AAAAAAABAAAAACAZAAAAA":"CAAAAExTS1PUCwAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0NCByYWRpaV9zZWxlY3RvcjsKaW4gZmxvYXQ0IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHM7CmluIGZsb2F0NCBhYV9ibG9hdF9hbmRfY292ZXJhZ2U7CmluIGZsb2F0NCByYWRpaV94OwppbiBmbG9hdDQgcmFkaWlfeTsKaW4gZmxvYXQ0IHNrZXc7CmluIGZsb2F0MiB0cmFuc2xhdGVfYW5kX2xvY2Fscm90YXRlOwppbiBoYWxmNCBjb2xvcjsKZmxhdCBvdXQgaGFsZjQgdmNvbG9yX1MwOwpvdXQgZmxvYXQyIHZhcmNjb29yZF9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgRmlsbFJSZWN0T3A6OlByb2Nlc3NvcgoJdmNvbG9yX1MwID0gY29sb3I7CglmbG9hdCBhYV9ibG9hdF9tdWx0aXBsaWVyID0gMTsKCWZsb2F0MiBjb3JuZXIgPSBjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzLnh5OwoJZmxvYXQyIHJhZGl1c19vdXRzZXQgPSBjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzLnp3OwoJZmxvYXQyIGFhX2Jsb2F0X2RpcmVjdGlvbiA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS54eTsKCWZsb2F0IGlzX2xpbmVhcl9jb3ZlcmFnZSA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS53OwoJZmxvYXQyIHBpeGVsbGVuZ3RoID0gaW52ZXJzZXNxcnQoZmxvYXQyKGRvdChza2V3Lnh6LCBza2V3Lnh6KSwgZG90KHNrZXcueXcsIHNrZXcueXcpKSk7CglmbG9hdDQgbm9ybWFsaXplZF9heGlzX2RpcnMgPSBza2V3ICogcGl4ZWxsZW5ndGgueHl4eTsKCWZsb2F0MiBheGlzd2lkdGhzID0gKGFicyhub3JtYWxpemVkX2F4aXNfZGlycy54eSkgKyBhYnMobm9ybWFsaXplZF9heGlzX2RpcnMuencpKTsKCWZsb2F0MiBhYV9ibG9hdHJhZGl1cyA9IGF4aXN3aWR0aHMgKiBwaXhlbGxlbmd0aCAqIC41OwoJZmxvYXQ0IHJhZGlpX2FuZF9uZWlnaGJvcnMgPSByYWRpaV9zZWxlY3RvciogZmxvYXQ0eDQocmFkaWlfeCwgcmFkaWlfeSwgcmFkaWlfeC55eHd6LCByYWRpaV95Lnd6eXgpOwoJZmxvYXQyIHJhZGlpID0gcmFkaWlfYW5kX25laWdoYm9ycy54eTsKCWZsb2F0MiBuZWlnaGJvcl9yYWRpaSA9IHJhZGlpX2FuZF9uZWlnaGJvcnMuenc7CglmbG9hdCBjb3ZlcmFnZV9tdWx0aXBsaWVyID0gMTsKCWlmIChhbnkoZ3JlYXRlclRoYW4oYWFfYmxvYXRyYWRpdXMsIGZsb2F0MigxKSkpKSAKCXsKCQljb3JuZXIgPSBtYXgoYWJzKGNvcm5lciksIGFhX2Jsb2F0cmFkaXVzKSAqIHNpZ24oY29ybmVyKTsKCQljb3ZlcmFnZV9tdWx0aXBsaWVyID0gMSAvIChtYXgoYWFfYmxvYXRyYWRpdXMueCwgMSkgKiBtYXgoYWFfYmxvYXRyYWRpdXMueSwgMSkpOwoJCXJhZGlpID0gZmxvYXQyKDApOwoJfQoJZmxvYXQgY292ZXJhZ2UgPSBhYV9ibG9hdF9hbmRfY292ZXJhZ2UuejsKCWlmIChhbnkobGVzc1RoYW4ocmFkaWksIGFhX2Jsb2F0cmFkaXVzICogMS41KSkpIAoJewoJCXJhZGlpID0gZmxvYXQyKDApOwoJCWFhX2Jsb2F0X2RpcmVjdGlvbiA9IHNpZ24oY29ybmVyKTsKCQlpZiAoY292ZXJhZ2UgPiAuNSkgCgkJewoJCQlhYV9ibG9hdF9kaXJlY3Rpb24gPSAtYWFfYmxvYXRfZGlyZWN0aW9uOwoJCX0KCQlpc19saW5lYXJfY292ZXJhZ2UgPSAxOwoJfQoJZWxzZSAKCXsKCQlyYWRpaSA9IGNsYW1wKHJhZGlpLCBwaXhlbGxlbmd0aCAqIDEuNSwgMiAtIHBpeGVsbGVuZ3RoICogMS41KTsKCQluZWlnaGJvcl9yYWRpaSA9IGNsYW1wKG5laWdoYm9yX3JhZGlpLCBwaXhlbGxlbmd0aCAqIDEuNSwgMiAtIHBpeGVsbGVuZ3RoICogMS41KTsKCQlmbG9hdDIgc3BhY2luZyA9IDIgLSByYWRpaSAtIG5laWdoYm9yX3JhZGlpOwoJCWZsb2F0MiBleHRyYV9wYWQgPSBtYXgocGl4ZWxsZW5ndGggKiAuMDYyNSAtIHNwYWNpbmcsIGZsb2F0MigwKSk7CgkJcmFkaWkgLT0gZXh0cmFfcGFkICogLjU7Cgl9CglmbG9hdDIgYWFfb3V0c2V0ID0gYWFfYmxvYXRfZGlyZWN0aW9uICogYWFfYmxvYXRyYWRpdXMgKiBhYV9ibG9hdF9tdWx0aXBsaWVyOwoJZmxvYXQyIHZlcnRleHBvcyA9IGNvcm5lciArIHJhZGl1c19vdXRzZXQgKiByYWRpaSArIGFhX291dHNldDsKCWlmIChjb3ZlcmFnZSA+IC41KSAKCXsKCQlpZiAoYWFfYmxvYXRfZGlyZWN0aW9uLnggIT0gMCAmJiB2ZXJ0ZXhwb3MueCAqIGNvcm5lci54IDwgMCkgCgkJewoJCQlmbG9hdCBiYWNrc2V0ID0gYWJzKHZlcnRleHBvcy54KTsKCQkJdmVydGV4cG9zLnggPSAwOwoJCQl2ZXJ0ZXhwb3MueSArPSBiYWNrc2V0ICogc2lnbihjb3JuZXIueSkgKiBwaXhlbGxlbmd0aC55L3BpeGVsbGVuZ3RoLng7CgkJCWNvdmVyYWdlID0gKGNvdmVyYWdlIC0gLjUpICogYWJzKGNvcm5lci54KSAvIChhYnMoY29ybmVyLngpICsgYmFja3NldCkgKyAuNTsKCQl9CgkJaWYgKGFhX2Jsb2F0X2RpcmVjdGlvbi55ICE9IDAgJiYgdmVydGV4cG9zLnkgKiBjb3JuZXIueSA8IDApIAoJCXsKCQkJZmxvYXQgYmFja3NldCA9IGFicyh2ZXJ0ZXhwb3MueSk7CgkJCXZlcnRleHBvcy55ID0gMDsKCQkJdmVydGV4cG9zLnggKz0gYmFja3NldCAqIHNpZ24oY29ybmVyLngpICogcGl4ZWxsZW5ndGgueC9waXhlbGxlbmd0aC55OwoJCQljb3ZlcmFnZSA9IChjb3ZlcmFnZSAtIC41KSAqIGFicyhjb3JuZXIueSkgLyAoYWJzKGNvcm5lci55KSArIGJhY2tzZXQpICsgLjU7CgkJfQoJfQoJZmxvYXQyeDIgc2tld21hdHJpeCA9IGZsb2F0MngyKHNrZXcueHksIHNrZXcuencpOwoJZmxvYXQyIGRldmNvb3JkID0gdmVydGV4cG9zICogc2tld21hdHJpeCArIHRyYW5zbGF0ZV9hbmRfbG9jYWxyb3RhdGUueHk7CglpZiAoMCAhPSBpc19saW5lYXJfY292ZXJhZ2UpIAoJewoJCXZhcmNjb29yZF9TMC54eSA9IGZsb2F0MigwLCBjb3ZlcmFnZSAqIGNvdmVyYWdlX211bHRpcGxpZXIpOwoJfQoJZWxzZSAKCXsKCQlmbG9hdDIgYXJjY29vcmQgPSAxIC0gYWJzKHJhZGl1c19vdXRzZXQpICsgYWFfb3V0c2V0L3JhZGlpICogY29ybmVyOwoJCXZhcmNjb29yZF9TMC54eSA9IGZsb2F0MihhcmNjb29yZC54KzEsIGFyY2Nvb3JkLnkpOwoJfQoJc2tfUG9zaXRpb24gPSBkZXZjb29yZC54eTAxOwp9CgAAAABdAgAAZmxhdCBpbiBoYWxmNCB2Y29sb3JfUzA7CmluIGZsb2F0MiB2YXJjY29vcmRfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBGaWxsUlJlY3RPcDo6UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJZmxvYXQgeF9wbHVzXzE9dmFyY2Nvb3JkX1MwLngsIHk9dmFyY2Nvb3JkX1MwLnk7CgloYWxmIGNvdmVyYWdlOwoJaWYgKDAgPT0geF9wbHVzXzEpIAoJewoJCWNvdmVyYWdlID0gaGFsZih5KTsKCX0KCWVsc2UgCgl7CgkJZmxvYXQgZm4gPSB4X3BsdXNfMSAqICh4X3BsdXNfMSAtIDIpOwoJCWZuID0gZm1hKHkseSwgZm4pOwoJCWZsb2F0IGZud2lkdGggPSBmd2lkdGgoZm4pOwoJCWNvdmVyYWdlID0gLjUgLSBoYWxmKGZuL2Zud2lkdGgpOwoJCWNvdmVyYWdlID0gY2xhbXAoY292ZXJhZ2UsIDAsIDEpOwoJfQoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNChjb3ZlcmFnZSk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAIAAAADgAAAHJhZGlpX3NlbGVjdG9yAAAZAAAAY29ybmVyX2FuZF9yYWRpdXNfb3V0c2V0cwAAABUAAABhYV9ibG9hdF9hbmRfY292ZXJhZ2UAAAAHAAAAcmFkaWlfeAAHAAAAcmFkaWlfeQAEAAAAc2tldxkAAAB0cmFuc2xhdGVfYW5kX2xvY2Fscm90YXRlAAAABQAAAGNvbG9yAAAAAQAAAAAAAAA=","HVJAAAAAABIAAGAAAQ4AAAH477776R24EAAAIOBQAD6P7777777777YDAAAAABAAAAAABBAMABAAOAAAABAAAAAAABBAMAAA":"CAAAAExTS1MjAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7CmluIGZsb2F0MiBsb2NhbENvb3JkOwpmbGF0IG91dCBoYWxmNCB2Y29sb3JfUzA7Cm91dCBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmNvbG9yX1MwID0gY29sb3I7Cgl2bG9jYWxDb29yZF9TMCA9IGxvY2FsQ29vcmQ7Cglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cn0KAAAAAADoAQAAdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzA7CmZsYXQgaW4gaGFsZjQgdmNvbG9yX1MwOwppbiBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2Y29sb3JfUzA7CglmbG9hdDIgdGV4Q29vcmQ7Cgl0ZXhDb29yZCA9IHZsb2NhbENvb3JkX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSAoKHNhbXBsZSh1VGV4dHVyZVNhbXBsZXJfMF9TMCwgdGV4Q29vcmQpICogb3V0cHV0Q29sb3JfUzApKTsKCWNvbnN0IGhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoMSk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAADAAAACAAAAHBvc2l0aW9uBQAAAGNvbG9yAAAACgAAAGxvY2FsQ29vcmQAAAEAAAAAAAAA","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAADUAANAAAAAAAAAIAAAABLAIABAAAAABAEGABBAMAAAAAAAAAAAAB2AAAAAAACAAAAAEBSAAAAA":"CAAAAExTS1M8AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMDsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfM19TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzNfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxX2MwKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAADOAgAAdW5pZm9ybSBmbG9hdDN4MyB1bWF0cml4X1MxX2MwOwp1bmlmb3JtIHNhbXBsZXIyRCB1VGV4dHVyZVNhbXBsZXJfMF9TMTsKaW4gZmxvYXQyIHZUcmFuc2Zvcm1lZENvb3Jkc18zX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBUZXh0dXJlRWZmZWN0X1MxX2MwX2MwKGhhbGY0IF9pbnB1dCkgCnsKCXJldHVybiBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfUzEsIHZUcmFuc2Zvcm1lZENvb3Jkc18zX1MwKTsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfUzFfYzAoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIFRleHR1cmVFZmZlY3RfUzFfYzBfYzAoX2lucHV0KTsKfQpoYWxmNCBCbGVuZF9TMShoYWxmNCBfc3JjLCBoYWxmNCBfZHN0KSAKewoJcmV0dXJuIGJsZW5kX21vZHVsYXRlKE1hdHJpeEVmZmVjdF9TMV9jMChfc3JjKSwgX3NyYyk7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzAgPSBoYWxmNCgxKTsKCWNvbnN0IGhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoMSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBCbGVuZF9TMShvdXRwdXRDb2xvcl9TMCwgaGFsZjQoMSkpOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dF9TMSAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","AYQA5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAACABYQACAAAEAAAAAAAIADQAAAAIAAAAAAAIIDA":"CAAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgEAAADkAwAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CmluIGZsb2F0NCB2aW5DaXJjbGVFZGdlX1MwOwppbiBoYWxmNCB2aW5Db2xvcl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgQ2lyY3VsYXJSUmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgZHh5ID0gbWF4KHVpbm5lclJlY3RfUzEuTFQgLSBza19GcmFnQ29vcmQueHksIDAuMCk7CgloYWxmIHJpZ2h0QWxwaGEgPSBoYWxmKHNhdHVyYXRlKHVpbm5lclJlY3RfUzEuUiAtIHNrX0ZyYWdDb29yZC54KSk7CgloYWxmIGJvdHRvbUFscGhhID0gaGFsZihzYXR1cmF0ZSh1aW5uZXJSZWN0X1MxLkIgLSBza19GcmFnQ29vcmQueSkpOwoJaGFsZiBhbHBoYSA9IGJvdHRvbUFscGhhICogcmlnaHRBbHBoYSAqIGhhbGYoc2F0dXJhdGUodXJhZGl1c1BsdXNIYWxmX1MxLnggLSBsZW5ndGgoZHh5KSkpOwoJcmV0dXJuIF9pbnB1dCAqIGFscGhhOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQ0IGNpcmNsZUVkZ2U7CgljaXJjbGVFZGdlID0gdmluQ2lyY2xlRWRnZV9TMDsKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2aW5Db2xvcl9TMDsKCWZsb2F0IGQgPSBsZW5ndGgoY2lyY2xlRWRnZS54eSk7CgloYWxmIGRpc3RhbmNlVG9PdXRlckVkZ2UgPSBoYWxmKGNpcmNsZUVkZ2UueiAqICgxLjAgLSBkKSk7CgloYWxmIGVkZ2VBbHBoYSA9IHNhdHVyYXRlKGRpc3RhbmNlVG9PdXRlckVkZ2UpOwoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNChlZGdlQWxwaGEpOwoJaGFsZjQgb3V0cHV0X1MxOwoJb3V0cHV0X1MxID0gQ2lyY3VsYXJSUmVjdF9TMShvdXRwdXRDb3ZlcmFnZV9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRfUzE7Cgl9Cn0KAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAwAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAMAAAAaW5DaXJjbGVFZGdlAQAAAAAAAAA=","AYQA5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAACABZQA6AAAEAAAAAAAIADQAAAAIAAAAAAAIIDA":"CAAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgEAAACnAwAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CmluIGZsb2F0NCB2aW5DaXJjbGVFZGdlX1MwOwppbiBoYWxmNCB2aW5Db2xvcl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgQ2lyY3VsYXJSUmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgZHh5MCA9IHVpbm5lclJlY3RfUzEuTFQgLSBza19GcmFnQ29vcmQueHk7CglmbG9hdDIgZHh5MSA9IHNrX0ZyYWdDb29yZC54eSAtIHVpbm5lclJlY3RfUzEuUkI7CglmbG9hdDIgZHh5ID0gbWF4KG1heChkeHkwLCBkeHkxKSwgMC4wKTsKCWhhbGYgYWxwaGEgPSBoYWxmKHNhdHVyYXRlKHVyYWRpdXNQbHVzSGFsZl9TMS54IC0gbGVuZ3RoKGR4eSkpKTsKCWFscGhhID0gMS4wIC0gYWxwaGE7CglyZXR1cm4gX2lucHV0ICogYWxwaGE7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIENpcmNsZUdlb21ldHJ5UHJvY2Vzc29yCglmbG9hdDQgY2lyY2xlRWRnZTsKCWNpcmNsZUVkZ2UgPSB2aW5DaXJjbGVFZGdlX1MwOwoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZpbkNvbG9yX1MwOwoJZmxvYXQgZCA9IGxlbmd0aChjaXJjbGVFZGdlLnh5KTsKCWhhbGYgZGlzdGFuY2VUb091dGVyRWRnZSA9IGhhbGYoY2lyY2xlRWRnZS56ICogKDEuMCAtIGQpKTsKCWhhbGYgZWRnZUFscGhhID0gc2F0dXJhdGUoZGlzdGFuY2VUb091dGVyRWRnZSk7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KGVkZ2VBbHBoYSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBDaXJjdWxhclJSZWN0X1MxKG91dHB1dENvdmVyYWdlX1MwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dF9TMTsKCX0KfQoAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAwAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAMAAAAaW5DaXJjbGVFZGdlAQAAAAAAAAA=","DASAAAAAQAAWAABAYAAQBYH7777Z6QQBAEAAAAAAEAAAAAAAEBSAAAB2AAAAAAACAAAAAEBSAAAAA":"CAAAAExTS1PVAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQyIHVBdGxhc1NpemVJbnZfUzA7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiB1c2hvcnQyIGluVGV4dHVyZUNvb3JkczsKb3V0IGZsb2F0MiB2VGV4dHVyZUNvb3Jkc19TMDsKZmxhdCBvdXQgZmxvYXQgdlRleEluZGV4X1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBCaXRtYXBUZXh0CglpbnQgdGV4SWR4ID0gMDsKCWZsb2F0MiB1bm9ybVRleENvb3JkcyA9IGZsb2F0MihpblRleHR1cmVDb29yZHMueCwgaW5UZXh0dXJlQ29vcmRzLnkpOwoJdlRleHR1cmVDb29yZHNfUzAgPSB1bm9ybVRleENvb3JkcyAqIHVBdGxhc1NpemVJbnZfUzA7Cgl2VGV4SW5kZXhfUzAgPSBmbG9hdCh0ZXhJZHgpOwoJZmxvYXQyIF90bXBfMV9pblBvc2l0aW9uID0gaW5Qb3NpdGlvbjsKCXNrX1Bvc2l0aW9uID0gaW5Qb3NpdGlvbi54eTAxOwp9CgAAAAAAAAD4AQAAdW5pZm9ybSBoYWxmNCB1Q29sb3JfUzA7CnVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1MwOwppbiBmbG9hdDIgdlRleHR1cmVDb29yZHNfUzA7CmZsYXQgaW4gZmxvYXQgdlRleEluZGV4X1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgQml0bWFwVGV4dAoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHVDb2xvcl9TMDsKCWhhbGY0IHRleENvbG9yOwoJewoJCXRleENvbG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MwLCB2VGV4dHVyZUNvb3Jkc19TMCk7Cgl9CglvdXRwdXRDb2xvcl9TMCA9IG91dHB1dENvbG9yX1MwICogdGV4Q29sb3I7Cgljb25zdCBoYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KDEpOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAgAAAAoAAABpblBvc2l0aW9uAAAPAAAAaW5UZXh0dXJlQ29vcmRzAAEAAAAAAAAA","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAALUAQBAEAQAAAAGQCBAMQACAIAAAAAACQAGAAAAAQAAAAAAAQQGAAAAA":"CAAAAExTS1M2AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAAAAAGUDAAB1bmlmb3JtIGZsb2F0NCB1Y2xhbXBfUzFfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzE7CmluIGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgVGV4dHVyZUVmZmVjdF9TMV9jMChoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgaW5Db29yZCA9IHZUcmFuc2Zvcm1lZENvb3Jkc18yX1MwOwoJZmxvYXQyIHN1YnNldENvb3JkOwoJc3Vic2V0Q29vcmQueCA9IGluQ29vcmQueDsKCXN1YnNldENvb3JkLnkgPSBpbkNvb3JkLnk7CglmbG9hdDIgY2xhbXBlZENvb3JkOwoJY2xhbXBlZENvb3JkID0gY2xhbXAoc3Vic2V0Q29vcmQsIHVjbGFtcF9TMV9jMC54eSwgdWNsYW1wX1MxX2MwLnp3KTsKCWhhbGY0IHRleHR1cmVDb2xvciA9IHNhbXBsZSh1VGV4dHVyZVNhbXBsZXJfMF9TMSwgY2xhbXBlZENvb3JkKTsKCXJldHVybiB0ZXh0dXJlQ29sb3I7Cn0KaGFsZjQgTWF0cml4RWZmZWN0X1MxKGhhbGY0IF9pbnB1dCkgCnsKCXJldHVybiBUZXh0dXJlRWZmZWN0X1MxX2MwKF9pbnB1dCk7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzAgPSBoYWxmNCgxKTsKCWNvbnN0IGhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoMSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBNYXRyaXhFZmZlY3RfUzEob3V0cHV0Q29sb3JfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dF9TMSAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24KAAAAbG9jYWxDb29yZAAAAQAAAAAAAAA=","FAAQMYAAMAAAEADAAABAEYAAAICIAB5AABQAAAQAMAAAEATAAABAIIGAAEDCBYQCA4AAAAAAEADZAAAAAAIAAAAAACQAGAAAAAQAAAAAAAQQG":"CAAAAExTS1PUCwAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0NCByYWRpaV9zZWxlY3RvcjsKaW4gZmxvYXQ0IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHM7CmluIGZsb2F0NCBhYV9ibG9hdF9hbmRfY292ZXJhZ2U7CmluIGZsb2F0NCByYWRpaV94OwppbiBmbG9hdDQgcmFkaWlfeTsKaW4gZmxvYXQ0IHNrZXc7CmluIGZsb2F0MiB0cmFuc2xhdGVfYW5kX2xvY2Fscm90YXRlOwppbiBoYWxmNCBjb2xvcjsKZmxhdCBvdXQgaGFsZjQgdmNvbG9yX1MwOwpvdXQgZmxvYXQyIHZhcmNjb29yZF9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgRmlsbFJSZWN0T3A6OlByb2Nlc3NvcgoJdmNvbG9yX1MwID0gY29sb3I7CglmbG9hdCBhYV9ibG9hdF9tdWx0aXBsaWVyID0gMTsKCWZsb2F0MiBjb3JuZXIgPSBjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzLnh5OwoJZmxvYXQyIHJhZGl1c19vdXRzZXQgPSBjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzLnp3OwoJZmxvYXQyIGFhX2Jsb2F0X2RpcmVjdGlvbiA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS54eTsKCWZsb2F0IGlzX2xpbmVhcl9jb3ZlcmFnZSA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS53OwoJZmxvYXQyIHBpeGVsbGVuZ3RoID0gaW52ZXJzZXNxcnQoZmxvYXQyKGRvdChza2V3Lnh6LCBza2V3Lnh6KSwgZG90KHNrZXcueXcsIHNrZXcueXcpKSk7CglmbG9hdDQgbm9ybWFsaXplZF9heGlzX2RpcnMgPSBza2V3ICogcGl4ZWxsZW5ndGgueHl4eTsKCWZsb2F0MiBheGlzd2lkdGhzID0gKGFicyhub3JtYWxpemVkX2F4aXNfZGlycy54eSkgKyBhYnMobm9ybWFsaXplZF9heGlzX2RpcnMuencpKTsKCWZsb2F0MiBhYV9ibG9hdHJhZGl1cyA9IGF4aXN3aWR0aHMgKiBwaXhlbGxlbmd0aCAqIC41OwoJZmxvYXQ0IHJhZGlpX2FuZF9uZWlnaGJvcnMgPSByYWRpaV9zZWxlY3RvciogZmxvYXQ0eDQocmFkaWlfeCwgcmFkaWlfeSwgcmFkaWlfeC55eHd6LCByYWRpaV95Lnd6eXgpOwoJZmxvYXQyIHJhZGlpID0gcmFkaWlfYW5kX25laWdoYm9ycy54eTsKCWZsb2F0MiBuZWlnaGJvcl9yYWRpaSA9IHJhZGlpX2FuZF9uZWlnaGJvcnMuenc7CglmbG9hdCBjb3ZlcmFnZV9tdWx0aXBsaWVyID0gMTsKCWlmIChhbnkoZ3JlYXRlclRoYW4oYWFfYmxvYXRyYWRpdXMsIGZsb2F0MigxKSkpKSAKCXsKCQljb3JuZXIgPSBtYXgoYWJzKGNvcm5lciksIGFhX2Jsb2F0cmFkaXVzKSAqIHNpZ24oY29ybmVyKTsKCQljb3ZlcmFnZV9tdWx0aXBsaWVyID0gMSAvIChtYXgoYWFfYmxvYXRyYWRpdXMueCwgMSkgKiBtYXgoYWFfYmxvYXRyYWRpdXMueSwgMSkpOwoJCXJhZGlpID0gZmxvYXQyKDApOwoJfQoJZmxvYXQgY292ZXJhZ2UgPSBhYV9ibG9hdF9hbmRfY292ZXJhZ2UuejsKCWlmIChhbnkobGVzc1RoYW4ocmFkaWksIGFhX2Jsb2F0cmFkaXVzICogMS41KSkpIAoJewoJCXJhZGlpID0gZmxvYXQyKDApOwoJCWFhX2Jsb2F0X2RpcmVjdGlvbiA9IHNpZ24oY29ybmVyKTsKCQlpZiAoY292ZXJhZ2UgPiAuNSkgCgkJewoJCQlhYV9ibG9hdF9kaXJlY3Rpb24gPSAtYWFfYmxvYXRfZGlyZWN0aW9uOwoJCX0KCQlpc19saW5lYXJfY292ZXJhZ2UgPSAxOwoJfQoJZWxzZSAKCXsKCQlyYWRpaSA9IGNsYW1wKHJhZGlpLCBwaXhlbGxlbmd0aCAqIDEuNSwgMiAtIHBpeGVsbGVuZ3RoICogMS41KTsKCQluZWlnaGJvcl9yYWRpaSA9IGNsYW1wKG5laWdoYm9yX3JhZGlpLCBwaXhlbGxlbmd0aCAqIDEuNSwgMiAtIHBpeGVsbGVuZ3RoICogMS41KTsKCQlmbG9hdDIgc3BhY2luZyA9IDIgLSByYWRpaSAtIG5laWdoYm9yX3JhZGlpOwoJCWZsb2F0MiBleHRyYV9wYWQgPSBtYXgocGl4ZWxsZW5ndGggKiAuMDYyNSAtIHNwYWNpbmcsIGZsb2F0MigwKSk7CgkJcmFkaWkgLT0gZXh0cmFfcGFkICogLjU7Cgl9CglmbG9hdDIgYWFfb3V0c2V0ID0gYWFfYmxvYXRfZGlyZWN0aW9uICogYWFfYmxvYXRyYWRpdXMgKiBhYV9ibG9hdF9tdWx0aXBsaWVyOwoJZmxvYXQyIHZlcnRleHBvcyA9IGNvcm5lciArIHJhZGl1c19vdXRzZXQgKiByYWRpaSArIGFhX291dHNldDsKCWlmIChjb3ZlcmFnZSA+IC41KSAKCXsKCQlpZiAoYWFfYmxvYXRfZGlyZWN0aW9uLnggIT0gMCAmJiB2ZXJ0ZXhwb3MueCAqIGNvcm5lci54IDwgMCkgCgkJewoJCQlmbG9hdCBiYWNrc2V0ID0gYWJzKHZlcnRleHBvcy54KTsKCQkJdmVydGV4cG9zLnggPSAwOwoJCQl2ZXJ0ZXhwb3MueSArPSBiYWNrc2V0ICogc2lnbihjb3JuZXIueSkgKiBwaXhlbGxlbmd0aC55L3BpeGVsbGVuZ3RoLng7CgkJCWNvdmVyYWdlID0gKGNvdmVyYWdlIC0gLjUpICogYWJzKGNvcm5lci54KSAvIChhYnMoY29ybmVyLngpICsgYmFja3NldCkgKyAuNTsKCQl9CgkJaWYgKGFhX2Jsb2F0X2RpcmVjdGlvbi55ICE9IDAgJiYgdmVydGV4cG9zLnkgKiBjb3JuZXIueSA8IDApIAoJCXsKCQkJZmxvYXQgYmFja3NldCA9IGFicyh2ZXJ0ZXhwb3MueSk7CgkJCXZlcnRleHBvcy55ID0gMDsKCQkJdmVydGV4cG9zLnggKz0gYmFja3NldCAqIHNpZ24oY29ybmVyLngpICogcGl4ZWxsZW5ndGgueC9waXhlbGxlbmd0aC55OwoJCQljb3ZlcmFnZSA9IChjb3ZlcmFnZSAtIC41KSAqIGFicyhjb3JuZXIueSkgLyAoYWJzKGNvcm5lci55KSArIGJhY2tzZXQpICsgLjU7CgkJfQoJfQoJZmxvYXQyeDIgc2tld21hdHJpeCA9IGZsb2F0MngyKHNrZXcueHksIHNrZXcuencpOwoJZmxvYXQyIGRldmNvb3JkID0gdmVydGV4cG9zICogc2tld21hdHJpeCArIHRyYW5zbGF0ZV9hbmRfbG9jYWxyb3RhdGUueHk7CglpZiAoMCAhPSBpc19saW5lYXJfY292ZXJhZ2UpIAoJewoJCXZhcmNjb29yZF9TMC54eSA9IGZsb2F0MigwLCBjb3ZlcmFnZSAqIGNvdmVyYWdlX211bHRpcGxpZXIpOwoJfQoJZWxzZSAKCXsKCQlmbG9hdDIgYXJjY29vcmQgPSAxIC0gYWJzKHJhZGl1c19vdXRzZXQpICsgYWFfb3V0c2V0L3JhZGlpICogY29ybmVyOwoJCXZhcmNjb29yZF9TMC54eSA9IGZsb2F0MihhcmNjb29yZC54KzEsIGFyY2Nvb3JkLnkpOwoJfQoJc2tfUG9zaXRpb24gPSBkZXZjb29yZC54eTAxOwp9CgEAAAA/BAAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CmZsYXQgaW4gaGFsZjQgdmNvbG9yX1MwOwppbiBmbG9hdDIgdmFyY2Nvb3JkX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBDaXJjdWxhclJSZWN0X1MxKGhhbGY0IF9pbnB1dCkgCnsKCWZsb2F0MiBkeHkgPSBtYXgodWlubmVyUmVjdF9TMS5MVCAtIHNrX0ZyYWdDb29yZC54eSwgMC4wKTsKCWhhbGYgcmlnaHRBbHBoYSA9IGhhbGYoc2F0dXJhdGUodWlubmVyUmVjdF9TMS5SIC0gc2tfRnJhZ0Nvb3JkLngpKTsKCWhhbGYgYm90dG9tQWxwaGEgPSBoYWxmKHNhdHVyYXRlKHVpbm5lclJlY3RfUzEuQiAtIHNrX0ZyYWdDb29yZC55KSk7CgloYWxmIGFscGhhID0gYm90dG9tQWxwaGEgKiByaWdodEFscGhhICogaGFsZihzYXR1cmF0ZSh1cmFkaXVzUGx1c0hhbGZfUzEueCAtIGxlbmd0aChkeHkpKSk7CglyZXR1cm4gX2lucHV0ICogYWxwaGE7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIEZpbGxSUmVjdE9wOjpQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2Y29sb3JfUzA7CglmbG9hdCB4X3BsdXNfMT12YXJjY29vcmRfUzAueCwgeT12YXJjY29vcmRfUzAueTsKCWhhbGYgY292ZXJhZ2U7CglpZiAoMCA9PSB4X3BsdXNfMSkgCgl7CgkJY292ZXJhZ2UgPSBoYWxmKHkpOwoJfQoJZWxzZSAKCXsKCQlmbG9hdCBmbiA9IHhfcGx1c18xICogKHhfcGx1c18xIC0gMik7CgkJZm4gPSBmbWEoeSx5LCBmbik7CgkJZmxvYXQgZm53aWR0aCA9IGZ3aWR0aChmbik7CgkJY292ZXJhZ2UgPSAuNSAtIGhhbGYoZm4vZm53aWR0aCk7CgkJY292ZXJhZ2UgPSBjbGFtcChjb3ZlcmFnZSwgMCwgMSk7Cgl9CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KGNvdmVyYWdlKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IENpcmN1bGFyUlJlY3RfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAIAAAADgAAAHJhZGlpX3NlbGVjdG9yAAAZAAAAY29ybmVyX2FuZF9yYWRpdXNfb3V0c2V0cwAAABUAAABhYV9ibG9hdF9hbmRfY292ZXJhZ2UAAAAHAAAAcmFkaWlfeAAHAAAAcmFkaWlfeQAEAAAAc2tldxkAAAB0cmFuc2xhdGVfYW5kX2xvY2Fscm90YXRlAAAABQAAAGNvbG9yAAAAAQAAAAAAAAA=","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAALUAQMAAAAAAIBAEAAAABJYQAAAAAQCAIAAAAAWCBACAIBAAAAANAECAZAAEAQAAAAAAFAAMAAAABAAAAAAABBAM":"CAAAAExTS1M2AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAAAAAA4GAAB1bmlmb3JtIGZsb2F0NCB1Y2xhbXBfUzFfYzBfYzBfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMF9jMDsKdW5pZm9ybSBoYWxmMiB1XzBfSW5jcmVtZW50X1MxX2MwOwp1bmlmb3JtIGhhbGY0IHVfMV9LZXJuZWxfUzFfYzBbNF07CnVuaWZvcm0gaGFsZjQgdV8yX09mZnNldHNfUzFfYzBbNF07CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzE7CmluIGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgVGV4dHVyZUVmZmVjdF9TMV9jMF9jMF9jMChoYWxmNCBfaW5wdXQsIGZsb2F0MiBfY29vcmRzKSAKewoJZmxvYXQyIGluQ29vcmQgPSBfY29vcmRzOwoJZmxvYXQyIHN1YnNldENvb3JkOwoJc3Vic2V0Q29vcmQueCA9IGluQ29vcmQueDsKCXN1YnNldENvb3JkLnkgPSBpbkNvb3JkLnk7CglmbG9hdDIgY2xhbXBlZENvb3JkOwoJY2xhbXBlZENvb3JkID0gY2xhbXAoc3Vic2V0Q29vcmQsIHVjbGFtcF9TMV9jMF9jMF9jMC54eSwgdWNsYW1wX1MxX2MwX2MwX2MwLnp3KTsKCWhhbGY0IHRleHR1cmVDb2xvciA9IHNhbXBsZSh1VGV4dHVyZVNhbXBsZXJfMF9TMSwgY2xhbXBlZENvb3JkKTsKCXJldHVybiB0ZXh0dXJlQ29sb3I7Cn0KaGFsZjQgTWF0cml4RWZmZWN0X1MxX2MwX2MwKGhhbGY0IF9pbnB1dCwgZmxvYXQyIF9jb29yZHMpIAp7CglyZXR1cm4gVGV4dHVyZUVmZmVjdF9TMV9jMF9jMF9jMChfaW5wdXQsIGZsb2F0M3gyKHVtYXRyaXhfUzFfYzBfYzApICogX2Nvb3Jkcy54eTEpOwp9CmhhbGY0IEdhdXNzaWFuQ29udm9sdXRpb25fUzFfYzAoaGFsZjQgX2lucHV0KSAKewoJaGFsZjQgXzNfY29sb3IgPSBoYWxmNCgwLjApOwoJZmxvYXQyIF81X2Nvb3JkID0gdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzA7Cglmb3IgKGludCBfNl9pID0gMDsgKF82X2kgPCAxMyk7IF82X2krKykgKF8zX2NvbG9yICs9IChNYXRyaXhFZmZlY3RfUzFfYzBfYzAoX2lucHV0LCAoXzVfY29vcmQgKyBmbG9hdDIoKHVfMl9PZmZzZXRzX1MxX2MwWyhfNl9pIC8gNCldWyhfNl9pICYgMyldICogdV8wX0luY3JlbWVudF9TMV9jMCkpKSkgKiB1XzFfS2VybmVsX1MxX2MwWyhfNl9pIC8gNCldWyhfNl9pICYgMyldKSk7CglyZXR1cm4gXzNfY29sb3I7Cn0KaGFsZjQgTWF0cml4RWZmZWN0X1MxKGhhbGY0IF9pbnB1dCkgCnsKCXJldHVybiBHYXVzc2lhbkNvbnZvbHV0aW9uX1MxX2MwKF9pbnB1dCk7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzAgPSBoYWxmNCgxKTsKCWNvbnN0IGhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoMSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBNYXRyaXhFZmZlY3RfUzEob3V0cHV0Q29sb3JfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dF9TMSAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","FAAQMYAAMAAAEADAAABAEYAAAICIAB5AABQAAAQAMAAAEATAAABAIIGAAEDCBYQCA4AAAAAAEAZCBRE4GNEACAAAOAAAAAAAAAAABAAOAAAABAAAAAAABBAMAA":"CAAAAExTS1PUCwAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0NCByYWRpaV9zZWxlY3RvcjsKaW4gZmxvYXQ0IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHM7CmluIGZsb2F0NCBhYV9ibG9hdF9hbmRfY292ZXJhZ2U7CmluIGZsb2F0NCByYWRpaV94OwppbiBmbG9hdDQgcmFkaWlfeTsKaW4gZmxvYXQ0IHNrZXc7CmluIGZsb2F0MiB0cmFuc2xhdGVfYW5kX2xvY2Fscm90YXRlOwppbiBoYWxmNCBjb2xvcjsKZmxhdCBvdXQgaGFsZjQgdmNvbG9yX1MwOwpvdXQgZmxvYXQyIHZhcmNjb29yZF9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgRmlsbFJSZWN0T3A6OlByb2Nlc3NvcgoJdmNvbG9yX1MwID0gY29sb3I7CglmbG9hdCBhYV9ibG9hdF9tdWx0aXBsaWVyID0gMTsKCWZsb2F0MiBjb3JuZXIgPSBjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzLnh5OwoJZmxvYXQyIHJhZGl1c19vdXRzZXQgPSBjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzLnp3OwoJZmxvYXQyIGFhX2Jsb2F0X2RpcmVjdGlvbiA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS54eTsKCWZsb2F0IGlzX2xpbmVhcl9jb3ZlcmFnZSA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS53OwoJZmxvYXQyIHBpeGVsbGVuZ3RoID0gaW52ZXJzZXNxcnQoZmxvYXQyKGRvdChza2V3Lnh6LCBza2V3Lnh6KSwgZG90KHNrZXcueXcsIHNrZXcueXcpKSk7CglmbG9hdDQgbm9ybWFsaXplZF9heGlzX2RpcnMgPSBza2V3ICogcGl4ZWxsZW5ndGgueHl4eTsKCWZsb2F0MiBheGlzd2lkdGhzID0gKGFicyhub3JtYWxpemVkX2F4aXNfZGlycy54eSkgKyBhYnMobm9ybWFsaXplZF9heGlzX2RpcnMuencpKTsKCWZsb2F0MiBhYV9ibG9hdHJhZGl1cyA9IGF4aXN3aWR0aHMgKiBwaXhlbGxlbmd0aCAqIC41OwoJZmxvYXQ0IHJhZGlpX2FuZF9uZWlnaGJvcnMgPSByYWRpaV9zZWxlY3RvciogZmxvYXQ0eDQocmFkaWlfeCwgcmFkaWlfeSwgcmFkaWlfeC55eHd6LCByYWRpaV95Lnd6eXgpOwoJZmxvYXQyIHJhZGlpID0gcmFkaWlfYW5kX25laWdoYm9ycy54eTsKCWZsb2F0MiBuZWlnaGJvcl9yYWRpaSA9IHJhZGlpX2FuZF9uZWlnaGJvcnMuenc7CglmbG9hdCBjb3ZlcmFnZV9tdWx0aXBsaWVyID0gMTsKCWlmIChhbnkoZ3JlYXRlclRoYW4oYWFfYmxvYXRyYWRpdXMsIGZsb2F0MigxKSkpKSAKCXsKCQljb3JuZXIgPSBtYXgoYWJzKGNvcm5lciksIGFhX2Jsb2F0cmFkaXVzKSAqIHNpZ24oY29ybmVyKTsKCQljb3ZlcmFnZV9tdWx0aXBsaWVyID0gMSAvIChtYXgoYWFfYmxvYXRyYWRpdXMueCwgMSkgKiBtYXgoYWFfYmxvYXRyYWRpdXMueSwgMSkpOwoJCXJhZGlpID0gZmxvYXQyKDApOwoJfQoJZmxvYXQgY292ZXJhZ2UgPSBhYV9ibG9hdF9hbmRfY292ZXJhZ2UuejsKCWlmIChhbnkobGVzc1RoYW4ocmFkaWksIGFhX2Jsb2F0cmFkaXVzICogMS41KSkpIAoJewoJCXJhZGlpID0gZmxvYXQyKDApOwoJCWFhX2Jsb2F0X2RpcmVjdGlvbiA9IHNpZ24oY29ybmVyKTsKCQlpZiAoY292ZXJhZ2UgPiAuNSkgCgkJewoJCQlhYV9ibG9hdF9kaXJlY3Rpb24gPSAtYWFfYmxvYXRfZGlyZWN0aW9uOwoJCX0KCQlpc19saW5lYXJfY292ZXJhZ2UgPSAxOwoJfQoJZWxzZSAKCXsKCQlyYWRpaSA9IGNsYW1wKHJhZGlpLCBwaXhlbGxlbmd0aCAqIDEuNSwgMiAtIHBpeGVsbGVuZ3RoICogMS41KTsKCQluZWlnaGJvcl9yYWRpaSA9IGNsYW1wKG5laWdoYm9yX3JhZGlpLCBwaXhlbGxlbmd0aCAqIDEuNSwgMiAtIHBpeGVsbGVuZ3RoICogMS41KTsKCQlmbG9hdDIgc3BhY2luZyA9IDIgLSByYWRpaSAtIG5laWdoYm9yX3JhZGlpOwoJCWZsb2F0MiBleHRyYV9wYWQgPSBtYXgocGl4ZWxsZW5ndGggKiAuMDYyNSAtIHNwYWNpbmcsIGZsb2F0MigwKSk7CgkJcmFkaWkgLT0gZXh0cmFfcGFkICogLjU7Cgl9CglmbG9hdDIgYWFfb3V0c2V0ID0gYWFfYmxvYXRfZGlyZWN0aW9uICogYWFfYmxvYXRyYWRpdXMgKiBhYV9ibG9hdF9tdWx0aXBsaWVyOwoJZmxvYXQyIHZlcnRleHBvcyA9IGNvcm5lciArIHJhZGl1c19vdXRzZXQgKiByYWRpaSArIGFhX291dHNldDsKCWlmIChjb3ZlcmFnZSA+IC41KSAKCXsKCQlpZiAoYWFfYmxvYXRfZGlyZWN0aW9uLnggIT0gMCAmJiB2ZXJ0ZXhwb3MueCAqIGNvcm5lci54IDwgMCkgCgkJewoJCQlmbG9hdCBiYWNrc2V0ID0gYWJzKHZlcnRleHBvcy54KTsKCQkJdmVydGV4cG9zLnggPSAwOwoJCQl2ZXJ0ZXhwb3MueSArPSBiYWNrc2V0ICogc2lnbihjb3JuZXIueSkgKiBwaXhlbGxlbmd0aC55L3BpeGVsbGVuZ3RoLng7CgkJCWNvdmVyYWdlID0gKGNvdmVyYWdlIC0gLjUpICogYWJzKGNvcm5lci54KSAvIChhYnMoY29ybmVyLngpICsgYmFja3NldCkgKyAuNTsKCQl9CgkJaWYgKGFhX2Jsb2F0X2RpcmVjdGlvbi55ICE9IDAgJiYgdmVydGV4cG9zLnkgKiBjb3JuZXIueSA8IDApIAoJCXsKCQkJZmxvYXQgYmFja3NldCA9IGFicyh2ZXJ0ZXhwb3MueSk7CgkJCXZlcnRleHBvcy55ID0gMDsKCQkJdmVydGV4cG9zLnggKz0gYmFja3NldCAqIHNpZ24oY29ybmVyLngpICogcGl4ZWxsZW5ndGgueC9waXhlbGxlbmd0aC55OwoJCQljb3ZlcmFnZSA9IChjb3ZlcmFnZSAtIC41KSAqIGFicyhjb3JuZXIueSkgLyAoYWJzKGNvcm5lci55KSArIGJhY2tzZXQpICsgLjU7CgkJfQoJfQoJZmxvYXQyeDIgc2tld21hdHJpeCA9IGZsb2F0MngyKHNrZXcueHksIHNrZXcuencpOwoJZmxvYXQyIGRldmNvb3JkID0gdmVydGV4cG9zICogc2tld21hdHJpeCArIHRyYW5zbGF0ZV9hbmRfbG9jYWxyb3RhdGUueHk7CglpZiAoMCAhPSBpc19saW5lYXJfY292ZXJhZ2UpIAoJewoJCXZhcmNjb29yZF9TMC54eSA9IGZsb2F0MigwLCBjb3ZlcmFnZSAqIGNvdmVyYWdlX211bHRpcGxpZXIpOwoJfQoJZWxzZSAKCXsKCQlmbG9hdDIgYXJjY29vcmQgPSAxIC0gYWJzKHJhZGl1c19vdXRzZXQpICsgYWFfb3V0c2V0L3JhZGlpICogY29ybmVyOwoJCXZhcmNjb29yZF9TMC54eSA9IGZsb2F0MihhcmNjb29yZC54KzEsIGFyY2Nvb3JkLnkpOwoJfQoJc2tfUG9zaXRpb24gPSBkZXZjb29yZC54eTAxOwp9CgEAAAAHBQAAY29uc3QgaW50IGtGaWxsQUFfUzEgPSAxOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQldfUzEgPSAyOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQUFfUzEgPSAzOwp1bmlmb3JtIGZsb2F0NCB1Y2lyY2xlX1MxOwpmbGF0IGluIGhhbGY0IHZjb2xvcl9TMDsKaW4gZmxvYXQyIHZhcmNjb29yZF9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgQ2lyY2xlX1MxKGhhbGY0IF9pbnB1dCkgCnsKCWhhbGY0IF90bXBfMF9pbkNvbG9yID0gX2lucHV0OwoJaGFsZiBkOwoJaWYgKGludCgzKSA9PSBrSW52ZXJzZUZpbGxCV19TMSB8fCBpbnQoMykgPT0ga0ludmVyc2VGaWxsQUFfUzEpIAoJewoJCWQgPSBoYWxmKChsZW5ndGgoKHVjaXJjbGVfUzEueHkgLSBza19GcmFnQ29vcmQueHkpICogdWNpcmNsZV9TMS53KSAtIDEuMCkgKiB1Y2lyY2xlX1MxLnopOwoJfQoJZWxzZSAKCXsKCQlkID0gaGFsZigoMS4wIC0gbGVuZ3RoKCh1Y2lyY2xlX1MxLnh5IC0gc2tfRnJhZ0Nvb3JkLnh5KSAqIHVjaXJjbGVfUzEudykpICogdWNpcmNsZV9TMS56KTsKCX0KCWlmIChpbnQoMykgPT0ga0ZpbGxBQV9TMSB8fCBpbnQoMykgPT0ga0ludmVyc2VGaWxsQUFfUzEpIAoJewoJCXJldHVybiBoYWxmNChfaW5wdXQgKiBzYXR1cmF0ZShkKSk7Cgl9CgllbHNlIAoJewoJCXJldHVybiBoYWxmNChkID4gMC41ID8gX2lucHV0IDogaGFsZjQoMC4wKSk7Cgl9Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIEZpbGxSUmVjdE9wOjpQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2Y29sb3JfUzA7CglmbG9hdCB4X3BsdXNfMT12YXJjY29vcmRfUzAueCwgeT12YXJjY29vcmRfUzAueTsKCWhhbGYgY292ZXJhZ2U7CglpZiAoMCA9PSB4X3BsdXNfMSkgCgl7CgkJY292ZXJhZ2UgPSBoYWxmKHkpOwoJfQoJZWxzZSAKCXsKCQlmbG9hdCBmbiA9IHhfcGx1c18xICogKHhfcGx1c18xIC0gMik7CgkJZm4gPSBmbWEoeSx5LCBmbik7CgkJZmxvYXQgZm53aWR0aCA9IGZ3aWR0aChmbik7CgkJY292ZXJhZ2UgPSAuNSAtIGhhbGYoZm4vZm53aWR0aCk7CgkJY292ZXJhZ2UgPSBjbGFtcChjb3ZlcmFnZSwgMCwgMSk7Cgl9CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KGNvdmVyYWdlKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IENpcmNsZV9TMShvdXRwdXRDb3ZlcmFnZV9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRfUzE7Cgl9Cn0KAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAgAAAAOAAAAcmFkaWlfc2VsZWN0b3IAABkAAABjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzAAAAFQAAAGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZQAAAAcAAAByYWRpaV94AAcAAAByYWRpaV95AAQAAABza2V3GQAAAHRyYW5zbGF0ZV9hbmRfbG9jYWxyb3RhdGUAAAAFAAAAY29sb3IAAAABAAAAAAAAAA==","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAALUAQLAAAAAAABAEAAAABJWQAAAAAACAIAAAAAWCBAAAIBAAAAANAECAZAAAAQAAAAAAFAAMAAAABAAAAAAABBAM":"CAAAAExTS1M2AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAAAAADEGAAB1bmlmb3JtIGZsb2F0NCB1Y2xhbXBfUzFfYzBfYzBfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMF9jMDsKdW5pZm9ybSBoYWxmMiB1XzBfSW5jcmVtZW50X1MxX2MwOwp1bmlmb3JtIGhhbGY0IHVfMV9LZXJuZWxfUzFfYzBbM107CnVuaWZvcm0gaGFsZjQgdV8yX09mZnNldHNfUzFfYzBbM107CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzE7CmluIGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgVGV4dHVyZUVmZmVjdF9TMV9jMF9jMF9jMChoYWxmNCBfaW5wdXQsIGZsb2F0MiBfY29vcmRzKSAKewoJZmxvYXQyIGluQ29vcmQgPSBfY29vcmRzOwoJZmxvYXQyIHN1YnNldENvb3JkOwoJc3Vic2V0Q29vcmQueCA9IGluQ29vcmQueDsKCXN1YnNldENvb3JkLnkgPSBpbkNvb3JkLnk7CglmbG9hdDIgY2xhbXBlZENvb3JkOwoJY2xhbXBlZENvb3JkLnggPSBzdWJzZXRDb29yZC54OwoJY2xhbXBlZENvb3JkLnkgPSBjbGFtcChzdWJzZXRDb29yZC55LCB1Y2xhbXBfUzFfYzBfYzBfYzAueSwgdWNsYW1wX1MxX2MwX2MwX2MwLncpOwoJaGFsZjQgdGV4dHVyZUNvbG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MxLCBjbGFtcGVkQ29vcmQpOwoJcmV0dXJuIHRleHR1cmVDb2xvcjsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfUzFfYzBfYzAoaGFsZjQgX2lucHV0LCBmbG9hdDIgX2Nvb3JkcykgCnsKCXJldHVybiBUZXh0dXJlRWZmZWN0X1MxX2MwX2MwX2MwKF9pbnB1dCwgZmxvYXQzeDIodW1hdHJpeF9TMV9jMF9jMCkgKiBfY29vcmRzLnh5MSk7Cn0KaGFsZjQgR2F1c3NpYW5Db252b2x1dGlvbl9TMV9jMChoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBfM19jb2xvciA9IGhhbGY0KDAuMCk7CglmbG9hdDIgXzVfY29vcmQgPSB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKCWZvciAoaW50IF82X2kgPSAwOyAoXzZfaSA8IDEyKTsgXzZfaSsrKSAoXzNfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TMV9jMF9jMChfaW5wdXQsIChfNV9jb29yZCArIGZsb2F0MigodV8yX09mZnNldHNfUzFfYzBbKF82X2kgLyA0KV1bKF82X2kgJiAzKV0gKiB1XzBfSW5jcmVtZW50X1MxX2MwKSkpKSAqIHVfMV9LZXJuZWxfUzFfYzBbKF82X2kgLyA0KV1bKF82X2kgJiAzKV0pKTsKCXJldHVybiBfM19jb2xvcjsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIEdhdXNzaWFuQ29udm9sdXRpb25fUzFfYzAoX2lucHV0KTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMCA9IGhhbGY0KDEpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IE1hdHJpeEVmZmVjdF9TMShvdXRwdXRDb2xvcl9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0X1MxICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAALUAQAAEAQAAAAGQCBAMQAAAIAAAAAACQAGAAAAAQAAAAAAAQQGAAAAA":"CAAAAExTS1M2AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAAAAAIgDAAB1bmlmb3JtIGZsb2F0NCB1Y2xhbXBfUzFfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzE7CmluIGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgVGV4dHVyZUVmZmVjdF9TMV9jMChoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgaW5Db29yZCA9IHZUcmFuc2Zvcm1lZENvb3Jkc18yX1MwOwoJZmxvYXQyIHN1YnNldENvb3JkOwoJc3Vic2V0Q29vcmQueCA9IGluQ29vcmQueDsKCXN1YnNldENvb3JkLnkgPSBpbkNvb3JkLnk7CglmbG9hdDIgY2xhbXBlZENvb3JkOwoJY2xhbXBlZENvb3JkLnggPSBzdWJzZXRDb29yZC54OwoJY2xhbXBlZENvb3JkLnkgPSBjbGFtcChzdWJzZXRDb29yZC55LCB1Y2xhbXBfUzFfYzAueSwgdWNsYW1wX1MxX2MwLncpOwoJaGFsZjQgdGV4dHVyZUNvbG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MxLCBjbGFtcGVkQ29vcmQpOwoJcmV0dXJuIHRleHR1cmVDb2xvcjsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIFRleHR1cmVFZmZlY3RfUzFfYzAoX2lucHV0KTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMCA9IGhhbGY0KDEpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IE1hdHJpeEVmZmVjdF9TMShvdXRwdXRDb2xvcl9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0X1MxICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","DBAAAAAAAABGAABAYAAQAIHCAIAYAQUBAEAAAAAAIAAAAAAAAAAAAAAAAAAAAOQAAAAAAAQAAAABAMQAAAAA":"CAAAAExTS1NVAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQyIHVBdGxhc1NpemVJbnZfUzA7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiB1c2hvcnQyIGluVGV4dHVyZUNvb3JkczsKb3V0IGZsb2F0MiB2VGV4dHVyZUNvb3Jkc19TMDsKZmxhdCBvdXQgZmxvYXQgdlRleEluZGV4X1MwOwpvdXQgaGFsZjQgdmluQ29sb3JfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIEJpdG1hcFRleHQKCWludDIgY29vcmRzID0gaW50MihpblRleHR1cmVDb29yZHMueCwgaW5UZXh0dXJlQ29vcmRzLnkpOwoJaW50IHRleElkeCA9IGNvb3Jkcy54ID4+IDEzOwoJZmxvYXQyIHVub3JtVGV4Q29vcmRzID0gZmxvYXQyKGNvb3Jkcy54ICYgMHgxRkZGLCBjb29yZHMueSk7Cgl2VGV4dHVyZUNvb3Jkc19TMCA9IHVub3JtVGV4Q29vcmRzICogdUF0bGFzU2l6ZUludl9TMDsKCXZUZXhJbmRleF9TMCA9IGZsb2F0KHRleElkeCk7Cgl2aW5Db2xvcl9TMCA9IGluQ29sb3I7CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJc2tfUG9zaXRpb24gPSBpblBvc2l0aW9uLnh5MDE7Cn0KAAAAAAAAAFoCAAB1bmlmb3JtIHNhbXBsZXIyRCB1VGV4dHVyZVNhbXBsZXJfMF9TMDsKdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzFfUzA7CmluIGZsb2F0MiB2VGV4dHVyZUNvb3Jkc19TMDsKZmxhdCBpbiBmbG9hdCB2VGV4SW5kZXhfUzA7CmluIGhhbGY0IHZpbkNvbG9yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgQml0bWFwVGV4dAoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZpbkNvbG9yX1MwOwoJaGFsZjQgdGV4Q29sb3I7CglpZiAodlRleEluZGV4X1MwID09IDApIAoJewoJCXRleENvbG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MwLCB2VGV4dHVyZUNvb3Jkc19TMCkucnJycjsKCX0KCWVsc2UgCgl7CgkJdGV4Q29sb3IgPSBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzFfUzAsIHZUZXh0dXJlQ29vcmRzX1MwKS5ycnJyOwoJfQoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSB0ZXhDb2xvcjsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAwAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAPAAAAaW5UZXh0dXJlQ29vcmRzAAEAAAAAAAAA","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAALUAQMAAAAAAABAEAAAABJYQAAAAAACAIAAAAAWCBAAAIBAAAAANAECAZAAAAQAAAAAAFAAMAAAABAAAAAAABBAM":"CAAAAExTS1M2AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAAAAADEGAAB1bmlmb3JtIGZsb2F0NCB1Y2xhbXBfUzFfYzBfYzBfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMF9jMDsKdW5pZm9ybSBoYWxmMiB1XzBfSW5jcmVtZW50X1MxX2MwOwp1bmlmb3JtIGhhbGY0IHVfMV9LZXJuZWxfUzFfYzBbNF07CnVuaWZvcm0gaGFsZjQgdV8yX09mZnNldHNfUzFfYzBbNF07CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzE7CmluIGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgVGV4dHVyZUVmZmVjdF9TMV9jMF9jMF9jMChoYWxmNCBfaW5wdXQsIGZsb2F0MiBfY29vcmRzKSAKewoJZmxvYXQyIGluQ29vcmQgPSBfY29vcmRzOwoJZmxvYXQyIHN1YnNldENvb3JkOwoJc3Vic2V0Q29vcmQueCA9IGluQ29vcmQueDsKCXN1YnNldENvb3JkLnkgPSBpbkNvb3JkLnk7CglmbG9hdDIgY2xhbXBlZENvb3JkOwoJY2xhbXBlZENvb3JkLnggPSBzdWJzZXRDb29yZC54OwoJY2xhbXBlZENvb3JkLnkgPSBjbGFtcChzdWJzZXRDb29yZC55LCB1Y2xhbXBfUzFfYzBfYzBfYzAueSwgdWNsYW1wX1MxX2MwX2MwX2MwLncpOwoJaGFsZjQgdGV4dHVyZUNvbG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MxLCBjbGFtcGVkQ29vcmQpOwoJcmV0dXJuIHRleHR1cmVDb2xvcjsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfUzFfYzBfYzAoaGFsZjQgX2lucHV0LCBmbG9hdDIgX2Nvb3JkcykgCnsKCXJldHVybiBUZXh0dXJlRWZmZWN0X1MxX2MwX2MwX2MwKF9pbnB1dCwgZmxvYXQzeDIodW1hdHJpeF9TMV9jMF9jMCkgKiBfY29vcmRzLnh5MSk7Cn0KaGFsZjQgR2F1c3NpYW5Db252b2x1dGlvbl9TMV9jMChoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBfM19jb2xvciA9IGhhbGY0KDAuMCk7CglmbG9hdDIgXzVfY29vcmQgPSB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKCWZvciAoaW50IF82X2kgPSAwOyAoXzZfaSA8IDEzKTsgXzZfaSsrKSAoXzNfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TMV9jMF9jMChfaW5wdXQsIChfNV9jb29yZCArIGZsb2F0MigodV8yX09mZnNldHNfUzFfYzBbKF82X2kgLyA0KV1bKF82X2kgJiAzKV0gKiB1XzBfSW5jcmVtZW50X1MxX2MwKSkpKSAqIHVfMV9LZXJuZWxfUzFfYzBbKF82X2kgLyA0KV1bKF82X2kgJiAzKV0pKTsKCXJldHVybiBfM19jb2xvcjsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIEdhdXNzaWFuQ29udm9sdXRpb25fUzFfYzAoX2lucHV0KTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMCA9IGhhbGY0KDEpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IE1hdHJpeEVmZmVjdF9TMShvdXRwdXRDb2xvcl9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0X1MxICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAALUAQLAAAAAAIAAEAAAABJWQAAAAAQAAIAAAAAWCBACAABAAAAANAECAZAAEAAAAAAAAFAAMAAAABAAAAAAABBAM":"CAAAAExTS1M2AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAAAAADEGAAB1bmlmb3JtIGZsb2F0NCB1Y2xhbXBfUzFfYzBfYzBfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMF9jMDsKdW5pZm9ybSBoYWxmMiB1XzBfSW5jcmVtZW50X1MxX2MwOwp1bmlmb3JtIGhhbGY0IHVfMV9LZXJuZWxfUzFfYzBbM107CnVuaWZvcm0gaGFsZjQgdV8yX09mZnNldHNfUzFfYzBbM107CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzE7CmluIGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgVGV4dHVyZUVmZmVjdF9TMV9jMF9jMF9jMChoYWxmNCBfaW5wdXQsIGZsb2F0MiBfY29vcmRzKSAKewoJZmxvYXQyIGluQ29vcmQgPSBfY29vcmRzOwoJZmxvYXQyIHN1YnNldENvb3JkOwoJc3Vic2V0Q29vcmQueCA9IGluQ29vcmQueDsKCXN1YnNldENvb3JkLnkgPSBpbkNvb3JkLnk7CglmbG9hdDIgY2xhbXBlZENvb3JkOwoJY2xhbXBlZENvb3JkLnggPSBjbGFtcChzdWJzZXRDb29yZC54LCB1Y2xhbXBfUzFfYzBfYzBfYzAueCwgdWNsYW1wX1MxX2MwX2MwX2MwLnopOwoJY2xhbXBlZENvb3JkLnkgPSBzdWJzZXRDb29yZC55OwoJaGFsZjQgdGV4dHVyZUNvbG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MxLCBjbGFtcGVkQ29vcmQpOwoJcmV0dXJuIHRleHR1cmVDb2xvcjsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfUzFfYzBfYzAoaGFsZjQgX2lucHV0LCBmbG9hdDIgX2Nvb3JkcykgCnsKCXJldHVybiBUZXh0dXJlRWZmZWN0X1MxX2MwX2MwX2MwKF9pbnB1dCwgZmxvYXQzeDIodW1hdHJpeF9TMV9jMF9jMCkgKiBfY29vcmRzLnh5MSk7Cn0KaGFsZjQgR2F1c3NpYW5Db252b2x1dGlvbl9TMV9jMChoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBfM19jb2xvciA9IGhhbGY0KDAuMCk7CglmbG9hdDIgXzVfY29vcmQgPSB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKCWZvciAoaW50IF82X2kgPSAwOyAoXzZfaSA8IDEyKTsgXzZfaSsrKSAoXzNfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TMV9jMF9jMChfaW5wdXQsIChfNV9jb29yZCArIGZsb2F0MigodV8yX09mZnNldHNfUzFfYzBbKF82X2kgLyA0KV1bKF82X2kgJiAzKV0gKiB1XzBfSW5jcmVtZW50X1MxX2MwKSkpKSAqIHVfMV9LZXJuZWxfUzFfYzBbKF82X2kgLyA0KV1bKF82X2kgJiAzKV0pKTsKCXJldHVybiBfM19jb2xvcjsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIEdhdXNzaWFuQ29udm9sdXRpb25fUzFfYzAoX2lucHV0KTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMCA9IGhhbGY0KDEpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IE1hdHJpeEVmZmVjdF9TMShvdXRwdXRDb2xvcl9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0X1MxICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","HVIAAAAAABIAAGAAAQ4AAAH477776R24EAAAIOBQAD6P7777777777YDAAAAAAAAAAAFIBTWV34ISAIAAAAEYT7ZOFIQAAAADAAAAABQAAAAAIFGB7HB2BAAAAAFQRH6PYAAAAEAAAAAAAAZGE66LR2FAEAAAYAAAAAMAAAAACAJQPRYO4IAAAAAMAI7T6YBAAAAABAAAAAGIMFGB7HB2BAAAAAAAAAAQAKYCRPE54DQAAAABAAAAAEQEFMEJ7T6AAAAAAAAAAAAAHIAAAAAAAIAAAAAQGIA":"CAAAAExTS1OAAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMF9jMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBoYWxmNCBjb2xvcjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7CmZsYXQgb3V0IGhhbGY0IHZjb2xvcl9TMDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfNV9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgl2Y29sb3JfUzAgPSBjb2xvcjsKCXNrX1Bvc2l0aW9uID0gcG9zaXRpb24ueHkwMTsKCXsKCQl2VHJhbnNmb3JtZWRDb29yZHNfNV9TMCA9IGZsb2F0M3gyKHVtYXRyaXhfUzFfYzBfYzEpICogbG9jYWxDb29yZC54eTE7Cgl9Cn0KAAAAAOMGAAB1bmlmb3JtIGhhbGY0IHVzdGFydF9TMV9jMF9jMDsKdW5pZm9ybSBoYWxmNCB1ZW5kX1MxX2MwX2MwOwp1bmlmb3JtIGZsb2F0M3gzIHVtYXRyaXhfUzFfYzBfYzE7CnVuaWZvcm0gaGFsZjQgdWxlZnRCb3JkZXJDb2xvcl9TMV9jMDsKdW5pZm9ybSBoYWxmNCB1cmlnaHRCb3JkZXJDb2xvcl9TMV9jMDsKZmxhdCBpbiBoYWxmNCB2Y29sb3JfUzA7CmluIGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfNV9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgU2luZ2xlSW50ZXJ2YWxDb2xvcml6ZXJfUzFfYzBfYzAoaGFsZjQgX2lucHV0LCBmbG9hdDIgX2Nvb3JkcykgCnsKCWhhbGY0IF90bXBfMF9pbkNvbG9yID0gX2lucHV0OwoJZmxvYXQyIF90bXBfMV9jb29yZHMgPSBfY29vcmRzOwoJcmV0dXJuIGhhbGY0KG1peCh1c3RhcnRfUzFfYzBfYzAsIHVlbmRfUzFfYzBfYzAsIGhhbGYoX3RtcF8xX2Nvb3Jkcy54KSkpOwp9CmhhbGY0IExpbmVhckxheW91dF9TMV9jMF9jMV9jMChoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBfdG1wXzJfaW5Db2xvciA9IF9pbnB1dDsKCWZsb2F0MiBfdG1wXzNfY29vcmRzID0gdlRyYW5zZm9ybWVkQ29vcmRzXzVfUzA7CglyZXR1cm4gaGFsZjQoaGFsZjQoaGFsZihfdG1wXzNfY29vcmRzLngpICsgOS45OTk5OTk3NDczNzg3NTE2ZS0wNiwgMS4wLCAwLjAsIDAuMCkpOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TMV9jMF9jMShoYWxmNCBfaW5wdXQpIAp7CglyZXR1cm4gTGluZWFyTGF5b3V0X1MxX2MwX2MxX2MwKF9pbnB1dCk7Cn0KaGFsZjQgQ2xhbXBlZEdyYWRpZW50X1MxX2MwKGhhbGY0IF9pbnB1dCkgCnsKCWhhbGY0IF90bXBfNF9pbkNvbG9yID0gX2lucHV0OwoJaGFsZjQgdCA9IE1hdHJpeEVmZmVjdF9TMV9jMF9jMShfdG1wXzRfaW5Db2xvcik7CgloYWxmNCBvdXRDb2xvcjsKCWlmICghYm9vbChpbnQoMSkpICYmIHQueSA8IDAuMCkgCgl7CgkJb3V0Q29sb3IgPSBoYWxmNCgwLjApOwoJfQoJZWxzZSBpZiAodC54IDwgMC4wKSAKCXsKCQlvdXRDb2xvciA9IHVsZWZ0Qm9yZGVyQ29sb3JfUzFfYzA7Cgl9CgllbHNlIGlmICh0LnggPiAxLjApIAoJewoJCW91dENvbG9yID0gdXJpZ2h0Qm9yZGVyQ29sb3JfUzFfYzA7Cgl9CgllbHNlIAoJewoJCW91dENvbG9yID0gU2luZ2xlSW50ZXJ2YWxDb2xvcml6ZXJfUzFfYzBfYzAoX3RtcF80X2luQ29sb3IsIGZsb2F0MihoYWxmMih0LngsIDAuMCkpKTsKCX0KCWlmIChib29sKGludCgxKSkpIAoJewoJCW91dENvbG9yLnh5eiAqPSBvdXRDb2xvci53OwoJfQoJcmV0dXJuIGhhbGY0KG91dENvbG9yKTsKfQpoYWxmNCBEaXNhYmxlQ292ZXJhZ2VBc0FscGhhX1MxKGhhbGY0IF9pbnB1dCkgCnsKCV9pbnB1dCA9IENsYW1wZWRHcmFkaWVudF9TMV9jMChfaW5wdXQpOwoJaGFsZjQgX3RtcF81X2luQ29sb3IgPSBfaW5wdXQ7CglyZXR1cm4gaGFsZjQoX2lucHV0KTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IERpc2FibGVDb3ZlcmFnZUFzQWxwaGFfUzEob3V0cHV0Q29sb3JfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dF9TMSAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAADAAAACAAAAHBvc2l0aW9uBQAAAGNvbG9yAAAACgAAAGxvY2FsQ29vcmQAAAEAAAAAAAAA","DAQAAAAAAABGAABAYAAQAIHCAIAYAQUBAEAAAAAAEAAAAAAAAAAAAIBSQB5VRECGAEAAAMAAAAAAAAAAACAA4AAAACAAAAAAACCAYAA":"CAAAAExTS1MWAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQyIHVBdGxhc1NpemVJbnZfUzA7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiB1c2hvcnQyIGluVGV4dHVyZUNvb3JkczsKb3V0IGZsb2F0MiB2VGV4dHVyZUNvb3Jkc19TMDsKZmxhdCBvdXQgZmxvYXQgdlRleEluZGV4X1MwOwpvdXQgaGFsZjQgdmluQ29sb3JfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIEJpdG1hcFRleHQKCWludCB0ZXhJZHggPSAwOwoJZmxvYXQyIHVub3JtVGV4Q29vcmRzID0gZmxvYXQyKGluVGV4dHVyZUNvb3Jkcy54LCBpblRleHR1cmVDb29yZHMueSk7Cgl2VGV4dHVyZUNvb3Jkc19TMCA9IHVub3JtVGV4Q29vcmRzICogdUF0bGFzU2l6ZUludl9TMDsKCXZUZXhJbmRleF9TMCA9IGZsb2F0KHRleElkeCk7Cgl2aW5Db2xvcl9TMCA9IGluQ29sb3I7CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJc2tfUG9zaXRpb24gPSBpblBvc2l0aW9uLnh5MDE7Cn0KAAABAAAA4wQAAGNvbnN0IGludCBrRmlsbEJXX1MxID0gMDsKY29uc3QgaW50IGtJbnZlcnNlRmlsbEJXX1MxID0gMjsKY29uc3QgaW50IGtJbnZlcnNlRmlsbEFBX1MxID0gMzsKdW5pZm9ybSBmbG9hdDQgdXJlY3RVbmlmb3JtX1MxOwp1bmlmb3JtIHNhbXBsZXIyRCB1VGV4dHVyZVNhbXBsZXJfMF9TMDsKaW4gZmxvYXQyIHZUZXh0dXJlQ29vcmRzX1MwOwpmbGF0IGluIGZsb2F0IHZUZXhJbmRleF9TMDsKaW4gaGFsZjQgdmluQ29sb3JfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IFJlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJaGFsZjQgX3RtcF8wX2luQ29sb3IgPSBfaW5wdXQ7CgloYWxmIGNvdmVyYWdlOwoJaWYgKGludCgxKSA9PSBrRmlsbEJXX1MxIHx8IGludCgxKSA9PSBrSW52ZXJzZUZpbGxCV19TMSkgCgl7CgkJY292ZXJhZ2UgPSBoYWxmKGFsbChncmVhdGVyVGhhbihmbG9hdDQoc2tfRnJhZ0Nvb3JkLnh5LCB1cmVjdFVuaWZvcm1fUzEuencpLCBmbG9hdDQodXJlY3RVbmlmb3JtX1MxLnh5LCBza19GcmFnQ29vcmQueHkpKSkgPyAxIDogMCk7Cgl9CgllbHNlIAoJewoJCWhhbGY0IGRpc3RzNCA9IGNsYW1wKGhhbGY0KDEuMCwgMS4wLCAtMS4wLCAtMS4wKSAqIGhhbGY0KHNrX0ZyYWdDb29yZC54eXh5IC0gdXJlY3RVbmlmb3JtX1MxKSwgMC4wLCAxLjApOwoJCWhhbGYyIGRpc3RzMiA9IChkaXN0czQueHkgKyBkaXN0czQuencpIC0gMS4wOwoJCWNvdmVyYWdlID0gZGlzdHMyLnggKiBkaXN0czIueTsKCX0KCWlmIChpbnQoMSkgPT0ga0ludmVyc2VGaWxsQldfUzEgfHwgaW50KDEpID09IGtJbnZlcnNlRmlsbEFBX1MxKSAKCXsKCQljb3ZlcmFnZSA9IDEuMCAtIGNvdmVyYWdlOwoJfQoJcmV0dXJuIGhhbGY0KF9pbnB1dCAqIGNvdmVyYWdlKTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgQml0bWFwVGV4dAoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZpbkNvbG9yX1MwOwoJaGFsZjQgdGV4Q29sb3I7Cgl7CgkJdGV4Q29sb3IgPSBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfUzAsIHZUZXh0dXJlQ29vcmRzX1MwKS5ycnJyOwoJfQoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSB0ZXhDb2xvcjsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IFJlY3RfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAADAAAACgAAAGluUG9zaXRpb24AAAcAAABpbkNvbG9yAA8AAABpblRleHR1cmVDb29yZHMAAQAAAAAAAAA=","HVIAAAAAABIAAGAAAQ4AAAH477776R24EAAAIOBQAD6P7777777777YDAAAAAAAAAAAFIBTWV34ISAIAAAAEYT7ZOFIQAAAABAAAAABQAAAAAIFGB7HB2BAAAAAFQRH6PYAAAAEAAAAAAAAZGE66LR2FAEAAAIAAAAAMAAAAACAJQPRYO4IAAAAAMAI7T6YBAAAAABAAAAAGIMFGB7HB2BAAAAAAAAAAQAKYCRPE54DQAAAABAAAAAEQEFMEJ7T6AAAAAAAAAAAAAHIAAAAAAAIAAAAAQGIA":"CAAAAExTS1OAAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMF9jMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBoYWxmNCBjb2xvcjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7CmZsYXQgb3V0IGhhbGY0IHZjb2xvcl9TMDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfNV9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgl2Y29sb3JfUzAgPSBjb2xvcjsKCXNrX1Bvc2l0aW9uID0gcG9zaXRpb24ueHkwMTsKCXsKCQl2VHJhbnNmb3JtZWRDb29yZHNfNV9TMCA9IGZsb2F0M3gyKHVtYXRyaXhfUzFfYzBfYzEpICogbG9jYWxDb29yZC54eTE7Cgl9Cn0KAAAAAOMGAAB1bmlmb3JtIGhhbGY0IHVzdGFydF9TMV9jMF9jMDsKdW5pZm9ybSBoYWxmNCB1ZW5kX1MxX2MwX2MwOwp1bmlmb3JtIGZsb2F0M3gzIHVtYXRyaXhfUzFfYzBfYzE7CnVuaWZvcm0gaGFsZjQgdWxlZnRCb3JkZXJDb2xvcl9TMV9jMDsKdW5pZm9ybSBoYWxmNCB1cmlnaHRCb3JkZXJDb2xvcl9TMV9jMDsKZmxhdCBpbiBoYWxmNCB2Y29sb3JfUzA7CmluIGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfNV9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgU2luZ2xlSW50ZXJ2YWxDb2xvcml6ZXJfUzFfYzBfYzAoaGFsZjQgX2lucHV0LCBmbG9hdDIgX2Nvb3JkcykgCnsKCWhhbGY0IF90bXBfMF9pbkNvbG9yID0gX2lucHV0OwoJZmxvYXQyIF90bXBfMV9jb29yZHMgPSBfY29vcmRzOwoJcmV0dXJuIGhhbGY0KG1peCh1c3RhcnRfUzFfYzBfYzAsIHVlbmRfUzFfYzBfYzAsIGhhbGYoX3RtcF8xX2Nvb3Jkcy54KSkpOwp9CmhhbGY0IExpbmVhckxheW91dF9TMV9jMF9jMV9jMChoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBfdG1wXzJfaW5Db2xvciA9IF9pbnB1dDsKCWZsb2F0MiBfdG1wXzNfY29vcmRzID0gdlRyYW5zZm9ybWVkQ29vcmRzXzVfUzA7CglyZXR1cm4gaGFsZjQoaGFsZjQoaGFsZihfdG1wXzNfY29vcmRzLngpICsgOS45OTk5OTk3NDczNzg3NTE2ZS0wNiwgMS4wLCAwLjAsIDAuMCkpOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TMV9jMF9jMShoYWxmNCBfaW5wdXQpIAp7CglyZXR1cm4gTGluZWFyTGF5b3V0X1MxX2MwX2MxX2MwKF9pbnB1dCk7Cn0KaGFsZjQgQ2xhbXBlZEdyYWRpZW50X1MxX2MwKGhhbGY0IF9pbnB1dCkgCnsKCWhhbGY0IF90bXBfNF9pbkNvbG9yID0gX2lucHV0OwoJaGFsZjQgdCA9IE1hdHJpeEVmZmVjdF9TMV9jMF9jMShfdG1wXzRfaW5Db2xvcik7CgloYWxmNCBvdXRDb2xvcjsKCWlmICghYm9vbChpbnQoMSkpICYmIHQueSA8IDAuMCkgCgl7CgkJb3V0Q29sb3IgPSBoYWxmNCgwLjApOwoJfQoJZWxzZSBpZiAodC54IDwgMC4wKSAKCXsKCQlvdXRDb2xvciA9IHVsZWZ0Qm9yZGVyQ29sb3JfUzFfYzA7Cgl9CgllbHNlIGlmICh0LnggPiAxLjApIAoJewoJCW91dENvbG9yID0gdXJpZ2h0Qm9yZGVyQ29sb3JfUzFfYzA7Cgl9CgllbHNlIAoJewoJCW91dENvbG9yID0gU2luZ2xlSW50ZXJ2YWxDb2xvcml6ZXJfUzFfYzBfYzAoX3RtcF80X2luQ29sb3IsIGZsb2F0MihoYWxmMih0LngsIDAuMCkpKTsKCX0KCWlmIChib29sKGludCgwKSkpIAoJewoJCW91dENvbG9yLnh5eiAqPSBvdXRDb2xvci53OwoJfQoJcmV0dXJuIGhhbGY0KG91dENvbG9yKTsKfQpoYWxmNCBEaXNhYmxlQ292ZXJhZ2VBc0FscGhhX1MxKGhhbGY0IF9pbnB1dCkgCnsKCV9pbnB1dCA9IENsYW1wZWRHcmFkaWVudF9TMV9jMChfaW5wdXQpOwoJaGFsZjQgX3RtcF81X2luQ29sb3IgPSBfaW5wdXQ7CglyZXR1cm4gaGFsZjQoX2lucHV0KTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IERpc2FibGVDb3ZlcmFnZUFzQWxwaGFfUzEob3V0cHV0Q29sb3JfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dF9TMSAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAADAAAACAAAAHBvc2l0aW9uBQAAAGNvbG9yAAAACgAAAGxvY2FsQ29vcmQAAAEAAAAAAAAA","AYQA5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAACAMQAHOMFARUBIAADAAAAAAAAAAAAAAHIAAAAAAAIAAAAAQGIAA":"CAAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgEAAAAcBQAAY29uc3QgaW50IGtGaWxsQldfUzEgPSAwOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQldfUzEgPSAyOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQUFfUzEgPSAzOwp1bmlmb3JtIGZsb2F0NCB1cmVjdFVuaWZvcm1fUzE7CmluIGZsb2F0NCB2aW5DaXJjbGVFZGdlX1MwOwppbiBoYWxmNCB2aW5Db2xvcl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgUmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBfdG1wXzBfaW5Db2xvciA9IF9pbnB1dDsKCWhhbGYgY292ZXJhZ2U7CglpZiAoaW50KDEpID09IGtGaWxsQldfUzEgfHwgaW50KDEpID09IGtJbnZlcnNlRmlsbEJXX1MxKSAKCXsKCQljb3ZlcmFnZSA9IGhhbGYoYWxsKGdyZWF0ZXJUaGFuKGZsb2F0NChza19GcmFnQ29vcmQueHksIHVyZWN0VW5pZm9ybV9TMS56dyksIGZsb2F0NCh1cmVjdFVuaWZvcm1fUzEueHksIHNrX0ZyYWdDb29yZC54eSkpKSA/IDEgOiAwKTsKCX0KCWVsc2UgCgl7CgkJaGFsZjQgZGlzdHM0ID0gY2xhbXAoaGFsZjQoMS4wLCAxLjAsIC0xLjAsIC0xLjApICogaGFsZjQoc2tfRnJhZ0Nvb3JkLnh5eHkgLSB1cmVjdFVuaWZvcm1fUzEpLCAwLjAsIDEuMCk7CgkJaGFsZjIgZGlzdHMyID0gKGRpc3RzNC54eSArIGRpc3RzNC56dykgLSAxLjA7CgkJY292ZXJhZ2UgPSBkaXN0czIueCAqIGRpc3RzMi55OwoJfQoJaWYgKGludCgxKSA9PSBrSW52ZXJzZUZpbGxCV19TMSB8fCBpbnQoMSkgPT0ga0ludmVyc2VGaWxsQUFfUzEpIAoJewoJCWNvdmVyYWdlID0gMS4wIC0gY292ZXJhZ2U7Cgl9CglyZXR1cm4gaGFsZjQoX2lucHV0ICogY292ZXJhZ2UpOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQ0IGNpcmNsZUVkZ2U7CgljaXJjbGVFZGdlID0gdmluQ2lyY2xlRWRnZV9TMDsKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2aW5Db2xvcl9TMDsKCWZsb2F0IGQgPSBsZW5ndGgoY2lyY2xlRWRnZS54eSk7CgloYWxmIGRpc3RhbmNlVG9PdXRlckVkZ2UgPSBoYWxmKGNpcmNsZUVkZ2UueiAqICgxLjAgLSBkKSk7CgloYWxmIGVkZ2VBbHBoYSA9IHNhdHVyYXRlKGRpc3RhbmNlVG9PdXRlckVkZ2UpOwoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNChlZGdlQWxwaGEpOwoJaGFsZjQgb3V0cHV0X1MxOwoJb3V0cHV0X1MxID0gUmVjdF9TMShvdXRwdXRDb3ZlcmFnZV9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRfUzE7Cgl9Cn0KAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAwAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAMAAAAaW5DaXJjbGVFZGdlAQAAAAAAAAA="}} \ No newline at end of file diff --git a/shaders_3.0.2.sksl.json b/shaders_3.0.2.sksl.json new file mode 100644 index 000000000..a05d3fa00 --- /dev/null +++ b/shaders_3.0.2.sksl.json @@ -0,0 +1 @@ +{"platform":"android","name":"SM G970N","engineRevision":"f15f824b57476e369b5e656f53d4c431c5b04b9a","data":{"FAAQMYAAMAAAEADAAABAEYAAAICIAB5AABQAAAQAMAAAEATAAABAIIGAAEDCBYQCA4AAAAAAEADZABYAAAIAAAAAACQAGAAAAAQAAAAAAAQQG":"CAAAAExTS1PUCwAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0NCByYWRpaV9zZWxlY3RvcjsKaW4gZmxvYXQ0IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHM7CmluIGZsb2F0NCBhYV9ibG9hdF9hbmRfY292ZXJhZ2U7CmluIGZsb2F0NCByYWRpaV94OwppbiBmbG9hdDQgcmFkaWlfeTsKaW4gZmxvYXQ0IHNrZXc7CmluIGZsb2F0MiB0cmFuc2xhdGVfYW5kX2xvY2Fscm90YXRlOwppbiBoYWxmNCBjb2xvcjsKZmxhdCBvdXQgaGFsZjQgdmNvbG9yX1MwOwpvdXQgZmxvYXQyIHZhcmNjb29yZF9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgRmlsbFJSZWN0T3A6OlByb2Nlc3NvcgoJdmNvbG9yX1MwID0gY29sb3I7CglmbG9hdCBhYV9ibG9hdF9tdWx0aXBsaWVyID0gMTsKCWZsb2F0MiBjb3JuZXIgPSBjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzLnh5OwoJZmxvYXQyIHJhZGl1c19vdXRzZXQgPSBjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzLnp3OwoJZmxvYXQyIGFhX2Jsb2F0X2RpcmVjdGlvbiA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS54eTsKCWZsb2F0IGlzX2xpbmVhcl9jb3ZlcmFnZSA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS53OwoJZmxvYXQyIHBpeGVsbGVuZ3RoID0gaW52ZXJzZXNxcnQoZmxvYXQyKGRvdChza2V3Lnh6LCBza2V3Lnh6KSwgZG90KHNrZXcueXcsIHNrZXcueXcpKSk7CglmbG9hdDQgbm9ybWFsaXplZF9heGlzX2RpcnMgPSBza2V3ICogcGl4ZWxsZW5ndGgueHl4eTsKCWZsb2F0MiBheGlzd2lkdGhzID0gKGFicyhub3JtYWxpemVkX2F4aXNfZGlycy54eSkgKyBhYnMobm9ybWFsaXplZF9heGlzX2RpcnMuencpKTsKCWZsb2F0MiBhYV9ibG9hdHJhZGl1cyA9IGF4aXN3aWR0aHMgKiBwaXhlbGxlbmd0aCAqIC41OwoJZmxvYXQ0IHJhZGlpX2FuZF9uZWlnaGJvcnMgPSByYWRpaV9zZWxlY3RvciogZmxvYXQ0eDQocmFkaWlfeCwgcmFkaWlfeSwgcmFkaWlfeC55eHd6LCByYWRpaV95Lnd6eXgpOwoJZmxvYXQyIHJhZGlpID0gcmFkaWlfYW5kX25laWdoYm9ycy54eTsKCWZsb2F0MiBuZWlnaGJvcl9yYWRpaSA9IHJhZGlpX2FuZF9uZWlnaGJvcnMuenc7CglmbG9hdCBjb3ZlcmFnZV9tdWx0aXBsaWVyID0gMTsKCWlmIChhbnkoZ3JlYXRlclRoYW4oYWFfYmxvYXRyYWRpdXMsIGZsb2F0MigxKSkpKSAKCXsKCQljb3JuZXIgPSBtYXgoYWJzKGNvcm5lciksIGFhX2Jsb2F0cmFkaXVzKSAqIHNpZ24oY29ybmVyKTsKCQljb3ZlcmFnZV9tdWx0aXBsaWVyID0gMSAvIChtYXgoYWFfYmxvYXRyYWRpdXMueCwgMSkgKiBtYXgoYWFfYmxvYXRyYWRpdXMueSwgMSkpOwoJCXJhZGlpID0gZmxvYXQyKDApOwoJfQoJZmxvYXQgY292ZXJhZ2UgPSBhYV9ibG9hdF9hbmRfY292ZXJhZ2UuejsKCWlmIChhbnkobGVzc1RoYW4ocmFkaWksIGFhX2Jsb2F0cmFkaXVzICogMS41KSkpIAoJewoJCXJhZGlpID0gZmxvYXQyKDApOwoJCWFhX2Jsb2F0X2RpcmVjdGlvbiA9IHNpZ24oY29ybmVyKTsKCQlpZiAoY292ZXJhZ2UgPiAuNSkgCgkJewoJCQlhYV9ibG9hdF9kaXJlY3Rpb24gPSAtYWFfYmxvYXRfZGlyZWN0aW9uOwoJCX0KCQlpc19saW5lYXJfY292ZXJhZ2UgPSAxOwoJfQoJZWxzZSAKCXsKCQlyYWRpaSA9IGNsYW1wKHJhZGlpLCBwaXhlbGxlbmd0aCAqIDEuNSwgMiAtIHBpeGVsbGVuZ3RoICogMS41KTsKCQluZWlnaGJvcl9yYWRpaSA9IGNsYW1wKG5laWdoYm9yX3JhZGlpLCBwaXhlbGxlbmd0aCAqIDEuNSwgMiAtIHBpeGVsbGVuZ3RoICogMS41KTsKCQlmbG9hdDIgc3BhY2luZyA9IDIgLSByYWRpaSAtIG5laWdoYm9yX3JhZGlpOwoJCWZsb2F0MiBleHRyYV9wYWQgPSBtYXgocGl4ZWxsZW5ndGggKiAuMDYyNSAtIHNwYWNpbmcsIGZsb2F0MigwKSk7CgkJcmFkaWkgLT0gZXh0cmFfcGFkICogLjU7Cgl9CglmbG9hdDIgYWFfb3V0c2V0ID0gYWFfYmxvYXRfZGlyZWN0aW9uICogYWFfYmxvYXRyYWRpdXMgKiBhYV9ibG9hdF9tdWx0aXBsaWVyOwoJZmxvYXQyIHZlcnRleHBvcyA9IGNvcm5lciArIHJhZGl1c19vdXRzZXQgKiByYWRpaSArIGFhX291dHNldDsKCWlmIChjb3ZlcmFnZSA+IC41KSAKCXsKCQlpZiAoYWFfYmxvYXRfZGlyZWN0aW9uLnggIT0gMCAmJiB2ZXJ0ZXhwb3MueCAqIGNvcm5lci54IDwgMCkgCgkJewoJCQlmbG9hdCBiYWNrc2V0ID0gYWJzKHZlcnRleHBvcy54KTsKCQkJdmVydGV4cG9zLnggPSAwOwoJCQl2ZXJ0ZXhwb3MueSArPSBiYWNrc2V0ICogc2lnbihjb3JuZXIueSkgKiBwaXhlbGxlbmd0aC55L3BpeGVsbGVuZ3RoLng7CgkJCWNvdmVyYWdlID0gKGNvdmVyYWdlIC0gLjUpICogYWJzKGNvcm5lci54KSAvIChhYnMoY29ybmVyLngpICsgYmFja3NldCkgKyAuNTsKCQl9CgkJaWYgKGFhX2Jsb2F0X2RpcmVjdGlvbi55ICE9IDAgJiYgdmVydGV4cG9zLnkgKiBjb3JuZXIueSA8IDApIAoJCXsKCQkJZmxvYXQgYmFja3NldCA9IGFicyh2ZXJ0ZXhwb3MueSk7CgkJCXZlcnRleHBvcy55ID0gMDsKCQkJdmVydGV4cG9zLnggKz0gYmFja3NldCAqIHNpZ24oY29ybmVyLngpICogcGl4ZWxsZW5ndGgueC9waXhlbGxlbmd0aC55OwoJCQljb3ZlcmFnZSA9IChjb3ZlcmFnZSAtIC41KSAqIGFicyhjb3JuZXIueSkgLyAoYWJzKGNvcm5lci55KSArIGJhY2tzZXQpICsgLjU7CgkJfQoJfQoJZmxvYXQyeDIgc2tld21hdHJpeCA9IGZsb2F0MngyKHNrZXcueHksIHNrZXcuencpOwoJZmxvYXQyIGRldmNvb3JkID0gdmVydGV4cG9zICogc2tld21hdHJpeCArIHRyYW5zbGF0ZV9hbmRfbG9jYWxyb3RhdGUueHk7CglpZiAoMCAhPSBpc19saW5lYXJfY292ZXJhZ2UpIAoJewoJCXZhcmNjb29yZF9TMC54eSA9IGZsb2F0MigwLCBjb3ZlcmFnZSAqIGNvdmVyYWdlX211bHRpcGxpZXIpOwoJfQoJZWxzZSAKCXsKCQlmbG9hdDIgYXJjY29vcmQgPSAxIC0gYWJzKHJhZGl1c19vdXRzZXQpICsgYWFfb3V0c2V0L3JhZGlpICogY29ybmVyOwoJCXZhcmNjb29yZF9TMC54eSA9IGZsb2F0MihhcmNjb29yZC54KzEsIGFyY2Nvb3JkLnkpOwoJfQoJc2tfUG9zaXRpb24gPSBkZXZjb29yZC54eTAxOwp9CgEAAADsAwAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CmZsYXQgaW4gaGFsZjQgdmNvbG9yX1MwOwppbiBmbG9hdDIgdmFyY2Nvb3JkX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBDaXJjdWxhclJSZWN0X1MxKGhhbGY0IF9pbnB1dCkgCnsKCWZsb2F0MiBkeHkwID0gdWlubmVyUmVjdF9TMS5MVCAtIHNrX0ZyYWdDb29yZC54eTsKCWZsb2F0MiBkeHkxID0gc2tfRnJhZ0Nvb3JkLnh5IC0gdWlubmVyUmVjdF9TMS5SQjsKCWZsb2F0MiBkeHkgPSBtYXgobWF4KGR4eTAsIGR4eTEpLCAwLjApOwoJaGFsZiBhbHBoYSA9IGhhbGYoc2F0dXJhdGUodXJhZGl1c1BsdXNIYWxmX1MxLnggLSBsZW5ndGgoZHh5KSkpOwoJcmV0dXJuIF9pbnB1dCAqIGFscGhhOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBGaWxsUlJlY3RPcDo6UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJZmxvYXQgeF9wbHVzXzE9dmFyY2Nvb3JkX1MwLngsIHk9dmFyY2Nvb3JkX1MwLnk7CgloYWxmIGNvdmVyYWdlOwoJaWYgKDAgPT0geF9wbHVzXzEpIAoJewoJCWNvdmVyYWdlID0gaGFsZih5KTsKCX0KCWVsc2UgCgl7CgkJZmxvYXQgZm4gPSB4X3BsdXNfMSAqICh4X3BsdXNfMSAtIDIpOwoJCWZuID0gZm1hKHkseSwgZm4pOwoJCWZsb2F0IGZud2lkdGggPSBmd2lkdGgoZm4pOwoJCWNvdmVyYWdlID0gLjUgLSBoYWxmKGZuL2Zud2lkdGgpOwoJCWNvdmVyYWdlID0gY2xhbXAoY292ZXJhZ2UsIDAsIDEpOwoJfQoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNChjb3ZlcmFnZSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBDaXJjdWxhclJSZWN0X1MxKG91dHB1dENvdmVyYWdlX1MwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dF9TMTsKCX0KfQoBAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAIAAAADgAAAHJhZGlpX3NlbGVjdG9yAAAZAAAAY29ybmVyX2FuZF9yYWRpdXNfb3V0c2V0cwAAABUAAABhYV9ibG9hdF9hbmRfY292ZXJhZ2UAAAAHAAAAcmFkaWlfeAAHAAAAcmFkaWlfeQAEAAAAc2tldxkAAAB0cmFuc2xhdGVfYW5kX2xvY2Fscm90YXRlAAAABQAAAGNvbG9yAAAAAQAAAAAAAAA=","HWQACAAAABAAADAAAIOAAAAADIIAAIRODAAP577774DSAIAA737777YBAAAAAAAAAAAKAAYAAAACAAAAAAACCAYAAA":"CAAAAExTS1ONAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQgY292ZXJhZ2U7CmluIGhhbGY0IGNvbG9yOwppbiBmbG9hdDQgZ2VvbVN1YnNldDsKZmxhdCBvdXQgaGFsZjQgdmNvbG9yX1MwOwpvdXQgZmxvYXQgdmNvdmVyYWdlX1MwOwpmbGF0IG91dCBmbG9hdDQgdmdlb21TdWJzZXRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQyIHBvc2l0aW9uID0gcG9zaXRpb24ueHk7Cgl2Y29sb3JfUzAgPSBjb2xvcjsKCXZjb3ZlcmFnZV9TMCA9IGNvdmVyYWdlOwoJdmdlb21TdWJzZXRfUzAgPSBnZW9tU3Vic2V0OwoJc2tfUG9zaXRpb24gPSBwb3NpdGlvbi54eTAxOwp9CgAAAAEAAACRAgAAZmxhdCBpbiBoYWxmNCB2Y29sb3JfUzA7CmluIGZsb2F0IHZjb3ZlcmFnZV9TMDsKZmxhdCBpbiBmbG9hdDQgdmdlb21TdWJzZXRfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2Y29sb3JfUzA7CglmbG9hdCBjb3ZlcmFnZSA9IHZjb3ZlcmFnZV9TMDsKCWZsb2F0NCBnZW9TdWJzZXQ7CglnZW9TdWJzZXQgPSB2Z2VvbVN1YnNldF9TMDsKCWhhbGY0IGRpc3RzNCA9IGNsYW1wKGhhbGY0KDEsIDEsIC0xLCAtMSkgKiBoYWxmNChza19GcmFnQ29vcmQueHl4eSAtIGdlb1N1YnNldCksIDAsIDEpOwoJaGFsZjIgZGlzdHMyID0gZGlzdHM0Lnh5ICsgZGlzdHM0Lnp3IC0gMTsKCWhhbGYgc3Vic2V0Q292ZXJhZ2UgPSBkaXN0czIueCAqIGRpc3RzMi55OwoJY292ZXJhZ2UgPSBtaW4oY292ZXJhZ2UsIHN1YnNldENvdmVyYWdlKTsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoaGFsZihjb3ZlcmFnZSkpOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAABAAAAAgAAABwb3NpdGlvbggAAABjb3ZlcmFnZQUAAABjb2xvcgAAAAoAAABnZW9tU3Vic2V0AAABAAAAAAAAAA==","HUJAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAQAAAAAAQQGACQAGAAAAAQAAAAAAAQQGAAA":"CAAAAExTS1PlAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmxvY2FsQ29vcmRfUzAgPSBsb2NhbENvb3JkOwoJc2tfUG9zaXRpb24gPSBwb3NpdGlvbi54eTAxOwp9CgAAAAAAAAC3AQAAdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzA7CmluIGZsb2F0MiB2bG9jYWxDb29yZF9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzAgPSBoYWxmNCgxKTsKCWZsb2F0MiB0ZXhDb29yZDsKCXRleENvb3JkID0gdmxvY2FsQ29vcmRfUzA7CglvdXRwdXRDb2xvcl9TMCA9ICgoc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MwLCB0ZXhDb29yZCkgKiBoYWxmNCgxKSkpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAACAAAACAAAAHBvc2l0aW9uCgAAAGxvY2FsQ29vcmQAAAEAAAAAAAAA","DAQAAAAAAABGAABAYAAQAIHCAIAYAQUBAEAAAAAAEAAAAAAAAAAAAIAHSADQAAAQAAAAAAFAAMAAAABAAAAAAABBAM":"CAAAAExTS1MWAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQyIHVBdGxhc1NpemVJbnZfUzA7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiB1c2hvcnQyIGluVGV4dHVyZUNvb3JkczsKb3V0IGZsb2F0MiB2VGV4dHVyZUNvb3Jkc19TMDsKZmxhdCBvdXQgZmxvYXQgdlRleEluZGV4X1MwOwpvdXQgaGFsZjQgdmluQ29sb3JfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIEJpdG1hcFRleHQKCWludCB0ZXhJZHggPSAwOwoJZmxvYXQyIHVub3JtVGV4Q29vcmRzID0gZmxvYXQyKGluVGV4dHVyZUNvb3Jkcy54LCBpblRleHR1cmVDb29yZHMueSk7Cgl2VGV4dHVyZUNvb3Jkc19TMCA9IHVub3JtVGV4Q29vcmRzICogdUF0bGFzU2l6ZUludl9TMDsKCXZUZXhJbmRleF9TMCA9IGZsb2F0KHRleElkeCk7Cgl2aW5Db2xvcl9TMCA9IGluQ29sb3I7CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJc2tfUG9zaXRpb24gPSBpblBvc2l0aW9uLnh5MDE7Cn0KAAABAAAAWAMAAHVuaWZvcm0gZmxvYXQ0IHVpbm5lclJlY3RfUzE7CnVuaWZvcm0gaGFsZjIgdXJhZGl1c1BsdXNIYWxmX1MxOwp1bmlmb3JtIHNhbXBsZXIyRCB1VGV4dHVyZVNhbXBsZXJfMF9TMDsKaW4gZmxvYXQyIHZUZXh0dXJlQ29vcmRzX1MwOwpmbGF0IGluIGZsb2F0IHZUZXhJbmRleF9TMDsKaW4gaGFsZjQgdmluQ29sb3JfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IENpcmN1bGFyUlJlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGR4eTAgPSB1aW5uZXJSZWN0X1MxLkxUIC0gc2tfRnJhZ0Nvb3JkLnh5OwoJZmxvYXQyIGR4eTEgPSBza19GcmFnQ29vcmQueHkgLSB1aW5uZXJSZWN0X1MxLlJCOwoJZmxvYXQyIGR4eSA9IG1heChtYXgoZHh5MCwgZHh5MSksIDAuMCk7CgloYWxmIGFscGhhID0gaGFsZihzYXR1cmF0ZSh1cmFkaXVzUGx1c0hhbGZfUzEueCAtIGxlbmd0aChkeHkpKSk7CglyZXR1cm4gX2lucHV0ICogYWxwaGE7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIEJpdG1hcFRleHQKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2aW5Db2xvcl9TMDsKCWhhbGY0IHRleENvbG9yOwoJewoJCXRleENvbG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MwLCB2VGV4dHVyZUNvb3Jkc19TMCkucnJycjsKCX0KCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gdGV4Q29sb3I7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBDaXJjdWxhclJSZWN0X1MxKG91dHB1dENvdmVyYWdlX1MwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dF9TMTsKCX0KfQoBAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAADAAAACgAAAGluUG9zaXRpb24AAAcAAABpbkNvbG9yAA8AAABpblRleHR1cmVDb29yZHMAAQAAAAAAAAA=","AYQA5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAACABYQACAAAEAAAAAAAIADQAAAAIAAAAAAAIIDA":"CAAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgEAAADkAwAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CmluIGZsb2F0NCB2aW5DaXJjbGVFZGdlX1MwOwppbiBoYWxmNCB2aW5Db2xvcl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgQ2lyY3VsYXJSUmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgZHh5ID0gbWF4KHVpbm5lclJlY3RfUzEuTFQgLSBza19GcmFnQ29vcmQueHksIDAuMCk7CgloYWxmIHJpZ2h0QWxwaGEgPSBoYWxmKHNhdHVyYXRlKHVpbm5lclJlY3RfUzEuUiAtIHNrX0ZyYWdDb29yZC54KSk7CgloYWxmIGJvdHRvbUFscGhhID0gaGFsZihzYXR1cmF0ZSh1aW5uZXJSZWN0X1MxLkIgLSBza19GcmFnQ29vcmQueSkpOwoJaGFsZiBhbHBoYSA9IGJvdHRvbUFscGhhICogcmlnaHRBbHBoYSAqIGhhbGYoc2F0dXJhdGUodXJhZGl1c1BsdXNIYWxmX1MxLnggLSBsZW5ndGgoZHh5KSkpOwoJcmV0dXJuIF9pbnB1dCAqIGFscGhhOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQ0IGNpcmNsZUVkZ2U7CgljaXJjbGVFZGdlID0gdmluQ2lyY2xlRWRnZV9TMDsKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2aW5Db2xvcl9TMDsKCWZsb2F0IGQgPSBsZW5ndGgoY2lyY2xlRWRnZS54eSk7CgloYWxmIGRpc3RhbmNlVG9PdXRlckVkZ2UgPSBoYWxmKGNpcmNsZUVkZ2UueiAqICgxLjAgLSBkKSk7CgloYWxmIGVkZ2VBbHBoYSA9IHNhdHVyYXRlKGRpc3RhbmNlVG9PdXRlckVkZ2UpOwoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNChlZGdlQWxwaGEpOwoJaGFsZjQgb3V0cHV0X1MxOwoJb3V0cHV0X1MxID0gQ2lyY3VsYXJSUmVjdF9TMShvdXRwdXRDb3ZlcmFnZV9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRfUzE7Cgl9Cn0KAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAwAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAMAAAAaW5DaXJjbGVFZGdlAQAAAAAAAAA=","FAAQMYAAMAAAEADAAABAEYAAAICIAB5AABQAAAQAMAAAEATAAABAIIGAAEDCBYQCA4AAAAAAEAZIA62YSBDACAAAGAAAAAAAAAAABAAOAAAABAAAAAAABBAMAA":"CAAAAExTS1PUCwAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0NCByYWRpaV9zZWxlY3RvcjsKaW4gZmxvYXQ0IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHM7CmluIGZsb2F0NCBhYV9ibG9hdF9hbmRfY292ZXJhZ2U7CmluIGZsb2F0NCByYWRpaV94OwppbiBmbG9hdDQgcmFkaWlfeTsKaW4gZmxvYXQ0IHNrZXc7CmluIGZsb2F0MiB0cmFuc2xhdGVfYW5kX2xvY2Fscm90YXRlOwppbiBoYWxmNCBjb2xvcjsKZmxhdCBvdXQgaGFsZjQgdmNvbG9yX1MwOwpvdXQgZmxvYXQyIHZhcmNjb29yZF9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgRmlsbFJSZWN0T3A6OlByb2Nlc3NvcgoJdmNvbG9yX1MwID0gY29sb3I7CglmbG9hdCBhYV9ibG9hdF9tdWx0aXBsaWVyID0gMTsKCWZsb2F0MiBjb3JuZXIgPSBjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzLnh5OwoJZmxvYXQyIHJhZGl1c19vdXRzZXQgPSBjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzLnp3OwoJZmxvYXQyIGFhX2Jsb2F0X2RpcmVjdGlvbiA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS54eTsKCWZsb2F0IGlzX2xpbmVhcl9jb3ZlcmFnZSA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS53OwoJZmxvYXQyIHBpeGVsbGVuZ3RoID0gaW52ZXJzZXNxcnQoZmxvYXQyKGRvdChza2V3Lnh6LCBza2V3Lnh6KSwgZG90KHNrZXcueXcsIHNrZXcueXcpKSk7CglmbG9hdDQgbm9ybWFsaXplZF9heGlzX2RpcnMgPSBza2V3ICogcGl4ZWxsZW5ndGgueHl4eTsKCWZsb2F0MiBheGlzd2lkdGhzID0gKGFicyhub3JtYWxpemVkX2F4aXNfZGlycy54eSkgKyBhYnMobm9ybWFsaXplZF9heGlzX2RpcnMuencpKTsKCWZsb2F0MiBhYV9ibG9hdHJhZGl1cyA9IGF4aXN3aWR0aHMgKiBwaXhlbGxlbmd0aCAqIC41OwoJZmxvYXQ0IHJhZGlpX2FuZF9uZWlnaGJvcnMgPSByYWRpaV9zZWxlY3RvciogZmxvYXQ0eDQocmFkaWlfeCwgcmFkaWlfeSwgcmFkaWlfeC55eHd6LCByYWRpaV95Lnd6eXgpOwoJZmxvYXQyIHJhZGlpID0gcmFkaWlfYW5kX25laWdoYm9ycy54eTsKCWZsb2F0MiBuZWlnaGJvcl9yYWRpaSA9IHJhZGlpX2FuZF9uZWlnaGJvcnMuenc7CglmbG9hdCBjb3ZlcmFnZV9tdWx0aXBsaWVyID0gMTsKCWlmIChhbnkoZ3JlYXRlclRoYW4oYWFfYmxvYXRyYWRpdXMsIGZsb2F0MigxKSkpKSAKCXsKCQljb3JuZXIgPSBtYXgoYWJzKGNvcm5lciksIGFhX2Jsb2F0cmFkaXVzKSAqIHNpZ24oY29ybmVyKTsKCQljb3ZlcmFnZV9tdWx0aXBsaWVyID0gMSAvIChtYXgoYWFfYmxvYXRyYWRpdXMueCwgMSkgKiBtYXgoYWFfYmxvYXRyYWRpdXMueSwgMSkpOwoJCXJhZGlpID0gZmxvYXQyKDApOwoJfQoJZmxvYXQgY292ZXJhZ2UgPSBhYV9ibG9hdF9hbmRfY292ZXJhZ2UuejsKCWlmIChhbnkobGVzc1RoYW4ocmFkaWksIGFhX2Jsb2F0cmFkaXVzICogMS41KSkpIAoJewoJCXJhZGlpID0gZmxvYXQyKDApOwoJCWFhX2Jsb2F0X2RpcmVjdGlvbiA9IHNpZ24oY29ybmVyKTsKCQlpZiAoY292ZXJhZ2UgPiAuNSkgCgkJewoJCQlhYV9ibG9hdF9kaXJlY3Rpb24gPSAtYWFfYmxvYXRfZGlyZWN0aW9uOwoJCX0KCQlpc19saW5lYXJfY292ZXJhZ2UgPSAxOwoJfQoJZWxzZSAKCXsKCQlyYWRpaSA9IGNsYW1wKHJhZGlpLCBwaXhlbGxlbmd0aCAqIDEuNSwgMiAtIHBpeGVsbGVuZ3RoICogMS41KTsKCQluZWlnaGJvcl9yYWRpaSA9IGNsYW1wKG5laWdoYm9yX3JhZGlpLCBwaXhlbGxlbmd0aCAqIDEuNSwgMiAtIHBpeGVsbGVuZ3RoICogMS41KTsKCQlmbG9hdDIgc3BhY2luZyA9IDIgLSByYWRpaSAtIG5laWdoYm9yX3JhZGlpOwoJCWZsb2F0MiBleHRyYV9wYWQgPSBtYXgocGl4ZWxsZW5ndGggKiAuMDYyNSAtIHNwYWNpbmcsIGZsb2F0MigwKSk7CgkJcmFkaWkgLT0gZXh0cmFfcGFkICogLjU7Cgl9CglmbG9hdDIgYWFfb3V0c2V0ID0gYWFfYmxvYXRfZGlyZWN0aW9uICogYWFfYmxvYXRyYWRpdXMgKiBhYV9ibG9hdF9tdWx0aXBsaWVyOwoJZmxvYXQyIHZlcnRleHBvcyA9IGNvcm5lciArIHJhZGl1c19vdXRzZXQgKiByYWRpaSArIGFhX291dHNldDsKCWlmIChjb3ZlcmFnZSA+IC41KSAKCXsKCQlpZiAoYWFfYmxvYXRfZGlyZWN0aW9uLnggIT0gMCAmJiB2ZXJ0ZXhwb3MueCAqIGNvcm5lci54IDwgMCkgCgkJewoJCQlmbG9hdCBiYWNrc2V0ID0gYWJzKHZlcnRleHBvcy54KTsKCQkJdmVydGV4cG9zLnggPSAwOwoJCQl2ZXJ0ZXhwb3MueSArPSBiYWNrc2V0ICogc2lnbihjb3JuZXIueSkgKiBwaXhlbGxlbmd0aC55L3BpeGVsbGVuZ3RoLng7CgkJCWNvdmVyYWdlID0gKGNvdmVyYWdlIC0gLjUpICogYWJzKGNvcm5lci54KSAvIChhYnMoY29ybmVyLngpICsgYmFja3NldCkgKyAuNTsKCQl9CgkJaWYgKGFhX2Jsb2F0X2RpcmVjdGlvbi55ICE9IDAgJiYgdmVydGV4cG9zLnkgKiBjb3JuZXIueSA8IDApIAoJCXsKCQkJZmxvYXQgYmFja3NldCA9IGFicyh2ZXJ0ZXhwb3MueSk7CgkJCXZlcnRleHBvcy55ID0gMDsKCQkJdmVydGV4cG9zLnggKz0gYmFja3NldCAqIHNpZ24oY29ybmVyLngpICogcGl4ZWxsZW5ndGgueC9waXhlbGxlbmd0aC55OwoJCQljb3ZlcmFnZSA9IChjb3ZlcmFnZSAtIC41KSAqIGFicyhjb3JuZXIueSkgLyAoYWJzKGNvcm5lci55KSArIGJhY2tzZXQpICsgLjU7CgkJfQoJfQoJZmxvYXQyeDIgc2tld21hdHJpeCA9IGZsb2F0MngyKHNrZXcueHksIHNrZXcuencpOwoJZmxvYXQyIGRldmNvb3JkID0gdmVydGV4cG9zICogc2tld21hdHJpeCArIHRyYW5zbGF0ZV9hbmRfbG9jYWxyb3RhdGUueHk7CglpZiAoMCAhPSBpc19saW5lYXJfY292ZXJhZ2UpIAoJewoJCXZhcmNjb29yZF9TMC54eSA9IGZsb2F0MigwLCBjb3ZlcmFnZSAqIGNvdmVyYWdlX211bHRpcGxpZXIpOwoJfQoJZWxzZSAKCXsKCQlmbG9hdDIgYXJjY29vcmQgPSAxIC0gYWJzKHJhZGl1c19vdXRzZXQpICsgYWFfb3V0c2V0L3JhZGlpICogY29ybmVyOwoJCXZhcmNjb29yZF9TMC54eSA9IGZsb2F0MihhcmNjb29yZC54KzEsIGFyY2Nvb3JkLnkpOwoJfQoJc2tfUG9zaXRpb24gPSBkZXZjb29yZC54eTAxOwp9CgEAAAB3BQAAY29uc3QgaW50IGtGaWxsQldfUzEgPSAwOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQldfUzEgPSAyOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQUFfUzEgPSAzOwp1bmlmb3JtIGZsb2F0NCB1cmVjdFVuaWZvcm1fUzE7CmZsYXQgaW4gaGFsZjQgdmNvbG9yX1MwOwppbiBmbG9hdDIgdmFyY2Nvb3JkX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBSZWN0X1MxKGhhbGY0IF9pbnB1dCkgCnsKCWhhbGY0IF90bXBfMF9pbkNvbG9yID0gX2lucHV0OwoJaGFsZiBjb3ZlcmFnZTsKCWlmIChpbnQoMSkgPT0ga0ZpbGxCV19TMSB8fCBpbnQoMSkgPT0ga0ludmVyc2VGaWxsQldfUzEpIAoJewoJCWNvdmVyYWdlID0gaGFsZihhbGwoZ3JlYXRlclRoYW4oZmxvYXQ0KHNrX0ZyYWdDb29yZC54eSwgdXJlY3RVbmlmb3JtX1MxLnp3KSwgZmxvYXQ0KHVyZWN0VW5pZm9ybV9TMS54eSwgc2tfRnJhZ0Nvb3JkLnh5KSkpID8gMSA6IDApOwoJfQoJZWxzZSAKCXsKCQloYWxmNCBkaXN0czQgPSBjbGFtcChoYWxmNCgxLjAsIDEuMCwgLTEuMCwgLTEuMCkgKiBoYWxmNChza19GcmFnQ29vcmQueHl4eSAtIHVyZWN0VW5pZm9ybV9TMSksIDAuMCwgMS4wKTsKCQloYWxmMiBkaXN0czIgPSAoZGlzdHM0Lnh5ICsgZGlzdHM0Lnp3KSAtIDEuMDsKCQljb3ZlcmFnZSA9IGRpc3RzMi54ICogZGlzdHMyLnk7Cgl9CglpZiAoaW50KDEpID09IGtJbnZlcnNlRmlsbEJXX1MxIHx8IGludCgxKSA9PSBrSW52ZXJzZUZpbGxBQV9TMSkgCgl7CgkJY292ZXJhZ2UgPSAxLjAgLSBjb3ZlcmFnZTsKCX0KCXJldHVybiBoYWxmNChfaW5wdXQgKiBjb3ZlcmFnZSk7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIEZpbGxSUmVjdE9wOjpQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2Y29sb3JfUzA7CglmbG9hdCB4X3BsdXNfMT12YXJjY29vcmRfUzAueCwgeT12YXJjY29vcmRfUzAueTsKCWhhbGYgY292ZXJhZ2U7CglpZiAoMCA9PSB4X3BsdXNfMSkgCgl7CgkJY292ZXJhZ2UgPSBoYWxmKHkpOwoJfQoJZWxzZSAKCXsKCQlmbG9hdCBmbiA9IHhfcGx1c18xICogKHhfcGx1c18xIC0gMik7CgkJZm4gPSBmbWEoeSx5LCBmbik7CgkJZmxvYXQgZm53aWR0aCA9IGZ3aWR0aChmbik7CgkJY292ZXJhZ2UgPSAuNSAtIGhhbGYoZm4vZm53aWR0aCk7CgkJY292ZXJhZ2UgPSBjbGFtcChjb3ZlcmFnZSwgMCwgMSk7Cgl9CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KGNvdmVyYWdlKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IFJlY3RfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAIAAAADgAAAHJhZGlpX3NlbGVjdG9yAAAZAAAAY29ybmVyX2FuZF9yYWRpdXNfb3V0c2V0cwAAABUAAABhYV9ibG9hdF9hbmRfY292ZXJhZ2UAAAAHAAAAcmFkaWlfeAAHAAAAcmFkaWlfeQAEAAAAc2tldxkAAAB0cmFuc2xhdGVfYW5kX2xvY2Fscm90YXRlAAAABQAAAGNvbG9yAAAAAQAAAAAAAAA=","HUQACAAAAAMAADAAAIOAAAH677776IZOCAAP577777777777777777YBAAAAAAAAAAAKAAYAAAACAAAAAAACCAYAAA":"CAAAAExTS1PPAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7Cm91dCBoYWxmNCB2Y29sb3JfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmNvbG9yX1MwID0gY29sb3I7Cglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cn0KAAAAAAAkAQAAaW4gaGFsZjQgdmNvbG9yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24FAAAAY29sb3IAAAABAAAAAAAAAA==","HUQACAAAAAMAADAAAIOAAAH677776IZOCAAP577777777777777777YBAAAAAAAAAAAHEADZAAAAAAIAAAAAAOQAAAAAAAQAAAABAMQAAAAAA":"CAAAAExTS1PPAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7Cm91dCBoYWxmNCB2Y29sb3JfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmNvbG9yX1MwID0gY29sb3I7Cglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cn0KAAEAAACzAgAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CmluIGhhbGY0IHZjb2xvcl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgQ2lyY3VsYXJSUmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgZHh5MCA9IHVpbm5lclJlY3RfUzEuTFQgLSBza19GcmFnQ29vcmQueHk7CglmbG9hdDIgZHh5MSA9IHNrX0ZyYWdDb29yZC54eSAtIHVpbm5lclJlY3RfUzEuUkI7CglmbG9hdDIgZHh5ID0gbWF4KG1heChkeHkwLCBkeHkxKSwgMC4wKTsKCWhhbGYgYWxwaGEgPSBoYWxmKHNhdHVyYXRlKHVyYWRpdXNQbHVzSGFsZl9TMS54IC0gbGVuZ3RoKGR4eSkpKTsKCXJldHVybiBfaW5wdXQgKiBhbHBoYTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IENpcmN1bGFyUlJlY3RfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAACAAAACAAAAHBvc2l0aW9uBQAAAGNvbG9yAAAAAQAAAAAAAAA=","HTQAAGAABBYAAAEIXBAAAGEAMAAAAAAAAAAAAAAAQAHAAAAAQAAAAAAAQQGAAAAA":"CAAAAExTS1M/AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiBmbG9hdDQgaW5RdWFkRWRnZTsKb3V0IGZsb2F0NCB2UXVhZEVkZ2VfUzA7Cm91dCBoYWxmNCB2aW5Db2xvcl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZEVkZ2UKCXZRdWFkRWRnZV9TMCA9IGluUXVhZEVkZ2U7Cgl2aW5Db2xvcl9TMCA9IGluQ29sb3I7CglmbG9hdDIgX3RtcF8wX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgABAAAAHQMAAGluIGZsb2F0NCB2UXVhZEVkZ2VfUzA7CmluIGhhbGY0IHZpbkNvbG9yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZEVkZ2UKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2aW5Db2xvcl9TMDsKCWhhbGYgZWRnZUFscGhhOwoJaGFsZjIgZHV2ZHggPSBoYWxmMihkRmR4KHZRdWFkRWRnZV9TMC54eSkpOwoJaGFsZjIgZHV2ZHkgPSBoYWxmMihkRmR5KHZRdWFkRWRnZV9TMC54eSkpOwoJaWYgKHZRdWFkRWRnZV9TMC56ID4gMC4wICYmIHZRdWFkRWRnZV9TMC53ID4gMC4wKSAKCXsKCQllZGdlQWxwaGEgPSBoYWxmKG1pbihtaW4odlF1YWRFZGdlX1MwLnosIHZRdWFkRWRnZV9TMC53KSArIDAuNSwgMS4wKSk7Cgl9CgllbHNlIAoJewoJCWhhbGYyIGdGID0gaGFsZjIoaGFsZigyLjAqdlF1YWRFZGdlX1MwLngqZHV2ZHgueCAtIGR1dmR4LnkpLCAgICAgICAgICAgICAgICAgaGFsZigyLjAqdlF1YWRFZGdlX1MwLngqZHV2ZHkueCAtIGR1dmR5LnkpKTsKCQllZGdlQWxwaGEgPSBoYWxmKHZRdWFkRWRnZV9TMC54KnZRdWFkRWRnZV9TMC54IC0gdlF1YWRFZGdlX1MwLnkpOwoJCWVkZ2VBbHBoYSA9IHNhdHVyYXRlKDAuNSAtIGVkZ2VBbHBoYSAvIGxlbmd0aChnRikpOwoJfQoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNChlZGdlQWxwaGEpOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAwAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAKAAAAaW5RdWFkRWRnZQAAAQAAAAAAAAA=","FAAQMYAAMAAAEADAAABAEYAAAICIAB5AABQAAAQAMAAAEATAAABAIIGAAEDCBYQCA4AAAAAAAA5AAAAAAABAAAAACAZAAAAA":"CAAAAExTS1PUCwAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0NCByYWRpaV9zZWxlY3RvcjsKaW4gZmxvYXQ0IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHM7CmluIGZsb2F0NCBhYV9ibG9hdF9hbmRfY292ZXJhZ2U7CmluIGZsb2F0NCByYWRpaV94OwppbiBmbG9hdDQgcmFkaWlfeTsKaW4gZmxvYXQ0IHNrZXc7CmluIGZsb2F0MiB0cmFuc2xhdGVfYW5kX2xvY2Fscm90YXRlOwppbiBoYWxmNCBjb2xvcjsKZmxhdCBvdXQgaGFsZjQgdmNvbG9yX1MwOwpvdXQgZmxvYXQyIHZhcmNjb29yZF9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgRmlsbFJSZWN0T3A6OlByb2Nlc3NvcgoJdmNvbG9yX1MwID0gY29sb3I7CglmbG9hdCBhYV9ibG9hdF9tdWx0aXBsaWVyID0gMTsKCWZsb2F0MiBjb3JuZXIgPSBjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzLnh5OwoJZmxvYXQyIHJhZGl1c19vdXRzZXQgPSBjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzLnp3OwoJZmxvYXQyIGFhX2Jsb2F0X2RpcmVjdGlvbiA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS54eTsKCWZsb2F0IGlzX2xpbmVhcl9jb3ZlcmFnZSA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS53OwoJZmxvYXQyIHBpeGVsbGVuZ3RoID0gaW52ZXJzZXNxcnQoZmxvYXQyKGRvdChza2V3Lnh6LCBza2V3Lnh6KSwgZG90KHNrZXcueXcsIHNrZXcueXcpKSk7CglmbG9hdDQgbm9ybWFsaXplZF9heGlzX2RpcnMgPSBza2V3ICogcGl4ZWxsZW5ndGgueHl4eTsKCWZsb2F0MiBheGlzd2lkdGhzID0gKGFicyhub3JtYWxpemVkX2F4aXNfZGlycy54eSkgKyBhYnMobm9ybWFsaXplZF9heGlzX2RpcnMuencpKTsKCWZsb2F0MiBhYV9ibG9hdHJhZGl1cyA9IGF4aXN3aWR0aHMgKiBwaXhlbGxlbmd0aCAqIC41OwoJZmxvYXQ0IHJhZGlpX2FuZF9uZWlnaGJvcnMgPSByYWRpaV9zZWxlY3RvciogZmxvYXQ0eDQocmFkaWlfeCwgcmFkaWlfeSwgcmFkaWlfeC55eHd6LCByYWRpaV95Lnd6eXgpOwoJZmxvYXQyIHJhZGlpID0gcmFkaWlfYW5kX25laWdoYm9ycy54eTsKCWZsb2F0MiBuZWlnaGJvcl9yYWRpaSA9IHJhZGlpX2FuZF9uZWlnaGJvcnMuenc7CglmbG9hdCBjb3ZlcmFnZV9tdWx0aXBsaWVyID0gMTsKCWlmIChhbnkoZ3JlYXRlclRoYW4oYWFfYmxvYXRyYWRpdXMsIGZsb2F0MigxKSkpKSAKCXsKCQljb3JuZXIgPSBtYXgoYWJzKGNvcm5lciksIGFhX2Jsb2F0cmFkaXVzKSAqIHNpZ24oY29ybmVyKTsKCQljb3ZlcmFnZV9tdWx0aXBsaWVyID0gMSAvIChtYXgoYWFfYmxvYXRyYWRpdXMueCwgMSkgKiBtYXgoYWFfYmxvYXRyYWRpdXMueSwgMSkpOwoJCXJhZGlpID0gZmxvYXQyKDApOwoJfQoJZmxvYXQgY292ZXJhZ2UgPSBhYV9ibG9hdF9hbmRfY292ZXJhZ2UuejsKCWlmIChhbnkobGVzc1RoYW4ocmFkaWksIGFhX2Jsb2F0cmFkaXVzICogMS41KSkpIAoJewoJCXJhZGlpID0gZmxvYXQyKDApOwoJCWFhX2Jsb2F0X2RpcmVjdGlvbiA9IHNpZ24oY29ybmVyKTsKCQlpZiAoY292ZXJhZ2UgPiAuNSkgCgkJewoJCQlhYV9ibG9hdF9kaXJlY3Rpb24gPSAtYWFfYmxvYXRfZGlyZWN0aW9uOwoJCX0KCQlpc19saW5lYXJfY292ZXJhZ2UgPSAxOwoJfQoJZWxzZSAKCXsKCQlyYWRpaSA9IGNsYW1wKHJhZGlpLCBwaXhlbGxlbmd0aCAqIDEuNSwgMiAtIHBpeGVsbGVuZ3RoICogMS41KTsKCQluZWlnaGJvcl9yYWRpaSA9IGNsYW1wKG5laWdoYm9yX3JhZGlpLCBwaXhlbGxlbmd0aCAqIDEuNSwgMiAtIHBpeGVsbGVuZ3RoICogMS41KTsKCQlmbG9hdDIgc3BhY2luZyA9IDIgLSByYWRpaSAtIG5laWdoYm9yX3JhZGlpOwoJCWZsb2F0MiBleHRyYV9wYWQgPSBtYXgocGl4ZWxsZW5ndGggKiAuMDYyNSAtIHNwYWNpbmcsIGZsb2F0MigwKSk7CgkJcmFkaWkgLT0gZXh0cmFfcGFkICogLjU7Cgl9CglmbG9hdDIgYWFfb3V0c2V0ID0gYWFfYmxvYXRfZGlyZWN0aW9uICogYWFfYmxvYXRyYWRpdXMgKiBhYV9ibG9hdF9tdWx0aXBsaWVyOwoJZmxvYXQyIHZlcnRleHBvcyA9IGNvcm5lciArIHJhZGl1c19vdXRzZXQgKiByYWRpaSArIGFhX291dHNldDsKCWlmIChjb3ZlcmFnZSA+IC41KSAKCXsKCQlpZiAoYWFfYmxvYXRfZGlyZWN0aW9uLnggIT0gMCAmJiB2ZXJ0ZXhwb3MueCAqIGNvcm5lci54IDwgMCkgCgkJewoJCQlmbG9hdCBiYWNrc2V0ID0gYWJzKHZlcnRleHBvcy54KTsKCQkJdmVydGV4cG9zLnggPSAwOwoJCQl2ZXJ0ZXhwb3MueSArPSBiYWNrc2V0ICogc2lnbihjb3JuZXIueSkgKiBwaXhlbGxlbmd0aC55L3BpeGVsbGVuZ3RoLng7CgkJCWNvdmVyYWdlID0gKGNvdmVyYWdlIC0gLjUpICogYWJzKGNvcm5lci54KSAvIChhYnMoY29ybmVyLngpICsgYmFja3NldCkgKyAuNTsKCQl9CgkJaWYgKGFhX2Jsb2F0X2RpcmVjdGlvbi55ICE9IDAgJiYgdmVydGV4cG9zLnkgKiBjb3JuZXIueSA8IDApIAoJCXsKCQkJZmxvYXQgYmFja3NldCA9IGFicyh2ZXJ0ZXhwb3MueSk7CgkJCXZlcnRleHBvcy55ID0gMDsKCQkJdmVydGV4cG9zLnggKz0gYmFja3NldCAqIHNpZ24oY29ybmVyLngpICogcGl4ZWxsZW5ndGgueC9waXhlbGxlbmd0aC55OwoJCQljb3ZlcmFnZSA9IChjb3ZlcmFnZSAtIC41KSAqIGFicyhjb3JuZXIueSkgLyAoYWJzKGNvcm5lci55KSArIGJhY2tzZXQpICsgLjU7CgkJfQoJfQoJZmxvYXQyeDIgc2tld21hdHJpeCA9IGZsb2F0MngyKHNrZXcueHksIHNrZXcuencpOwoJZmxvYXQyIGRldmNvb3JkID0gdmVydGV4cG9zICogc2tld21hdHJpeCArIHRyYW5zbGF0ZV9hbmRfbG9jYWxyb3RhdGUueHk7CglpZiAoMCAhPSBpc19saW5lYXJfY292ZXJhZ2UpIAoJewoJCXZhcmNjb29yZF9TMC54eSA9IGZsb2F0MigwLCBjb3ZlcmFnZSAqIGNvdmVyYWdlX211bHRpcGxpZXIpOwoJfQoJZWxzZSAKCXsKCQlmbG9hdDIgYXJjY29vcmQgPSAxIC0gYWJzKHJhZGl1c19vdXRzZXQpICsgYWFfb3V0c2V0L3JhZGlpICogY29ybmVyOwoJCXZhcmNjb29yZF9TMC54eSA9IGZsb2F0MihhcmNjb29yZC54KzEsIGFyY2Nvb3JkLnkpOwoJfQoJc2tfUG9zaXRpb24gPSBkZXZjb29yZC54eTAxOwp9CgAAAABdAgAAZmxhdCBpbiBoYWxmNCB2Y29sb3JfUzA7CmluIGZsb2F0MiB2YXJjY29vcmRfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBGaWxsUlJlY3RPcDo6UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJZmxvYXQgeF9wbHVzXzE9dmFyY2Nvb3JkX1MwLngsIHk9dmFyY2Nvb3JkX1MwLnk7CgloYWxmIGNvdmVyYWdlOwoJaWYgKDAgPT0geF9wbHVzXzEpIAoJewoJCWNvdmVyYWdlID0gaGFsZih5KTsKCX0KCWVsc2UgCgl7CgkJZmxvYXQgZm4gPSB4X3BsdXNfMSAqICh4X3BsdXNfMSAtIDIpOwoJCWZuID0gZm1hKHkseSwgZm4pOwoJCWZsb2F0IGZud2lkdGggPSBmd2lkdGgoZm4pOwoJCWNvdmVyYWdlID0gLjUgLSBoYWxmKGZuL2Zud2lkdGgpOwoJCWNvdmVyYWdlID0gY2xhbXAoY292ZXJhZ2UsIDAsIDEpOwoJfQoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNChjb3ZlcmFnZSk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAIAAAADgAAAHJhZGlpX3NlbGVjdG9yAAAZAAAAY29ybmVyX2FuZF9yYWRpdXNfb3V0c2V0cwAAABUAAABhYV9ibG9hdF9hbmRfY292ZXJhZ2UAAAAHAAAAcmFkaWlfeAAHAAAAcmFkaWlfeQAEAAAAc2tldxkAAAB0cmFuc2xhdGVfYW5kX2xvY2Fscm90YXRlAAAABQAAAGNvbG9yAAAAAQAAAAAAAAA=","HWQACAAAABAAADAAAIOAAAAADIIAAIRODAAP577774DSAIAA737777YBAAAAAAAAAAAHEADZAAAAAAIAAAAAAOQAAAAAAAQAAAABAMQAAAAAA":"CAAAAExTS1ONAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQgY292ZXJhZ2U7CmluIGhhbGY0IGNvbG9yOwppbiBmbG9hdDQgZ2VvbVN1YnNldDsKZmxhdCBvdXQgaGFsZjQgdmNvbG9yX1MwOwpvdXQgZmxvYXQgdmNvdmVyYWdlX1MwOwpmbGF0IG91dCBmbG9hdDQgdmdlb21TdWJzZXRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQyIHBvc2l0aW9uID0gcG9zaXRpb24ueHk7Cgl2Y29sb3JfUzAgPSBjb2xvcjsKCXZjb3ZlcmFnZV9TMCA9IGNvdmVyYWdlOwoJdmdlb21TdWJzZXRfUzAgPSBnZW9tU3Vic2V0OwoJc2tfUG9zaXRpb24gPSBwb3NpdGlvbi54eTAxOwp9CgAAAAEAAAAgBAAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CmZsYXQgaW4gaGFsZjQgdmNvbG9yX1MwOwppbiBmbG9hdCB2Y292ZXJhZ2VfUzA7CmZsYXQgaW4gZmxvYXQ0IHZnZW9tU3Vic2V0X1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBDaXJjdWxhclJSZWN0X1MxKGhhbGY0IF9pbnB1dCkgCnsKCWZsb2F0MiBkeHkwID0gdWlubmVyUmVjdF9TMS5MVCAtIHNrX0ZyYWdDb29yZC54eTsKCWZsb2F0MiBkeHkxID0gc2tfRnJhZ0Nvb3JkLnh5IC0gdWlubmVyUmVjdF9TMS5SQjsKCWZsb2F0MiBkeHkgPSBtYXgobWF4KGR4eTAsIGR4eTEpLCAwLjApOwoJaGFsZiBhbHBoYSA9IGhhbGYoc2F0dXJhdGUodXJhZGl1c1BsdXNIYWxmX1MxLnggLSBsZW5ndGgoZHh5KSkpOwoJcmV0dXJuIF9pbnB1dCAqIGFscGhhOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2Y29sb3JfUzA7CglmbG9hdCBjb3ZlcmFnZSA9IHZjb3ZlcmFnZV9TMDsKCWZsb2F0NCBnZW9TdWJzZXQ7CglnZW9TdWJzZXQgPSB2Z2VvbVN1YnNldF9TMDsKCWhhbGY0IGRpc3RzNCA9IGNsYW1wKGhhbGY0KDEsIDEsIC0xLCAtMSkgKiBoYWxmNChza19GcmFnQ29vcmQueHl4eSAtIGdlb1N1YnNldCksIDAsIDEpOwoJaGFsZjIgZGlzdHMyID0gZGlzdHM0Lnh5ICsgZGlzdHM0Lnp3IC0gMTsKCWhhbGYgc3Vic2V0Q292ZXJhZ2UgPSBkaXN0czIueCAqIGRpc3RzMi55OwoJY292ZXJhZ2UgPSBtaW4oY292ZXJhZ2UsIHN1YnNldENvdmVyYWdlKTsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoaGFsZihjb3ZlcmFnZSkpOwoJaGFsZjQgb3V0cHV0X1MxOwoJb3V0cHV0X1MxID0gQ2lyY3VsYXJSUmVjdF9TMShvdXRwdXRDb3ZlcmFnZV9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRfUzE7Cgl9Cn0KAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAABAAAAAgAAABwb3NpdGlvbggAAABjb3ZlcmFnZQUAAABjb2xvcgAAAAoAAABnZW9tU3Vic2V0AAABAAAAAAAAAA==","GEMAAAYAAEHAAAARC4EAAAQWBQAAAAAAAAAQAAAAIBCAAABZQA6AAAEAAAAAAAAADUAAAAAAAEAAAAAIDEAA":"CAAAAExTS1NUAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiBoYWxmMyBpblNoYWRvd1BhcmFtczsKb3V0IGhhbGYzIHZpblNoYWRvd1BhcmFtc19TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBSUmVjdFNoYWRvdwoJdmluU2hhZG93UGFyYW1zX1MwID0gaW5TaGFkb3dQYXJhbXM7Cgl2aW5Db2xvcl9TMCA9IGluQ29sb3I7CglmbG9hdDIgX3RtcF8wX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgEAAACyAwAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CnVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1MwOwppbiBoYWxmMyB2aW5TaGFkb3dQYXJhbXNfUzA7CmluIGhhbGY0IHZpbkNvbG9yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBDaXJjdWxhclJSZWN0X1MxKGhhbGY0IF9pbnB1dCkgCnsKCWZsb2F0MiBkeHkwID0gdWlubmVyUmVjdF9TMS5MVCAtIHNrX0ZyYWdDb29yZC54eTsKCWZsb2F0MiBkeHkxID0gc2tfRnJhZ0Nvb3JkLnh5IC0gdWlubmVyUmVjdF9TMS5SQjsKCWZsb2F0MiBkeHkgPSBtYXgobWF4KGR4eTAsIGR4eTEpLCAwLjApOwoJaGFsZiBhbHBoYSA9IGhhbGYoc2F0dXJhdGUodXJhZGl1c1BsdXNIYWxmX1MxLnggLSBsZW5ndGgoZHh5KSkpOwoJcmV0dXJuIF9pbnB1dCAqIGFscGhhOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBSUmVjdFNoYWRvdwoJaGFsZjMgc2hhZG93UGFyYW1zOwoJc2hhZG93UGFyYW1zID0gdmluU2hhZG93UGFyYW1zX1MwOwoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZpbkNvbG9yX1MwOwoJaGFsZiBkID0gbGVuZ3RoKHNoYWRvd1BhcmFtcy54eSk7CglmbG9hdDIgdXYgPSBmbG9hdDIoc2hhZG93UGFyYW1zLnogKiAoMS4wIC0gZCksIDAuNSk7CgloYWxmIGZhY3RvciA9IHNhbXBsZSh1VGV4dHVyZVNhbXBsZXJfMF9TMCwgdXYpLjAwMHIuYTsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoZmFjdG9yKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IENpcmN1bGFyUlJlY3RfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgAAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAwAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAOAAAAaW5TaGFkb3dQYXJhbXMAAAEAAAAAAAAA","HVIACAAAABQAAGAAAQ4AAAAAGQQAARC4GAAAIOCAAD6P7777777777YDAAAAAAAAAAAFIBTWV34ISAIAAAAEYT7ZOFIQAAAADAAAAABQAAAAAIFGB7HB2BAAAAAFQRH6PYAAAAEAAAAAAAAZGE66LR2FAEAAAYAAAAAMAAAAACAJQPRYO4IAAAAAMAI7T6YBAAAAABAAAAAGIMFGB7HB2BAAAAAAAAAAQAKYCRPE54DQAAAABAAAAAEQEFMEJ7T6AAAAAAAAAAAAAHIAAAAIAAQAAAAAQGIA":"CAAAAExTS1PlAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMF9jMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdCBjb3ZlcmFnZTsKaW4gaGFsZjQgY29sb3I7CmluIGZsb2F0MiBsb2NhbENvb3JkOwpmbGF0IG91dCBoYWxmNCB2Y29sb3JfUzA7Cm91dCBmbG9hdCB2Y292ZXJhZ2VfUzA7Cm91dCBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzVfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQyIHBvc2l0aW9uID0gcG9zaXRpb24ueHk7Cgl2Y29sb3JfUzAgPSBjb2xvcjsKCXZjb3ZlcmFnZV9TMCA9IGNvdmVyYWdlOwoJc2tfUG9zaXRpb24gPSBwb3NpdGlvbi54eTAxOwoJewoJCXZUcmFuc2Zvcm1lZENvb3Jkc181X1MwID0gZmxvYXQzeDIodW1hdHJpeF9TMV9jMF9jMSkgKiBsb2NhbENvb3JkLnh5MTsKCX0KfQoAAAAAAAAAMAcAAHVuaWZvcm0gaGFsZjQgdXN0YXJ0X1MxX2MwX2MwOwp1bmlmb3JtIGhhbGY0IHVlbmRfUzFfYzBfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMF9jMTsKdW5pZm9ybSBoYWxmNCB1bGVmdEJvcmRlckNvbG9yX1MxX2MwOwp1bmlmb3JtIGhhbGY0IHVyaWdodEJvcmRlckNvbG9yX1MxX2MwOwpmbGF0IGluIGhhbGY0IHZjb2xvcl9TMDsKaW4gZmxvYXQgdmNvdmVyYWdlX1MwOwppbiBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzVfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IFNpbmdsZUludGVydmFsQ29sb3JpemVyX1MxX2MwX2MwKGhhbGY0IF9pbnB1dCwgZmxvYXQyIF9jb29yZHMpIAp7CgloYWxmNCBfdG1wXzBfaW5Db2xvciA9IF9pbnB1dDsKCWZsb2F0MiBfdG1wXzFfY29vcmRzID0gX2Nvb3JkczsKCXJldHVybiBoYWxmNChtaXgodXN0YXJ0X1MxX2MwX2MwLCB1ZW5kX1MxX2MwX2MwLCBoYWxmKF90bXBfMV9jb29yZHMueCkpKTsKfQpoYWxmNCBMaW5lYXJMYXlvdXRfUzFfYzBfYzFfYzAoaGFsZjQgX2lucHV0KSAKewoJaGFsZjQgX3RtcF8yX2luQ29sb3IgPSBfaW5wdXQ7CglmbG9hdDIgX3RtcF8zX2Nvb3JkcyA9IHZUcmFuc2Zvcm1lZENvb3Jkc181X1MwOwoJcmV0dXJuIGhhbGY0KGhhbGY0KGhhbGYoX3RtcF8zX2Nvb3Jkcy54KSArIDkuOTk5OTk5NzQ3Mzc4NzUxNmUtMDYsIDEuMCwgMC4wLCAwLjApKTsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfUzFfYzBfYzEoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIExpbmVhckxheW91dF9TMV9jMF9jMV9jMChfaW5wdXQpOwp9CmhhbGY0IENsYW1wZWRHcmFkaWVudF9TMV9jMChoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBfdG1wXzRfaW5Db2xvciA9IF9pbnB1dDsKCWhhbGY0IHQgPSBNYXRyaXhFZmZlY3RfUzFfYzBfYzEoX3RtcF80X2luQ29sb3IpOwoJaGFsZjQgb3V0Q29sb3I7CglpZiAoIWJvb2woaW50KDEpKSAmJiB0LnkgPCAwLjApIAoJewoJCW91dENvbG9yID0gaGFsZjQoMC4wKTsKCX0KCWVsc2UgaWYgKHQueCA8IDAuMCkgCgl7CgkJb3V0Q29sb3IgPSB1bGVmdEJvcmRlckNvbG9yX1MxX2MwOwoJfQoJZWxzZSBpZiAodC54ID4gMS4wKSAKCXsKCQlvdXRDb2xvciA9IHVyaWdodEJvcmRlckNvbG9yX1MxX2MwOwoJfQoJZWxzZSAKCXsKCQlvdXRDb2xvciA9IFNpbmdsZUludGVydmFsQ29sb3JpemVyX1MxX2MwX2MwKF90bXBfNF9pbkNvbG9yLCBmbG9hdDIoaGFsZjIodC54LCAwLjApKSk7Cgl9CglpZiAoYm9vbChpbnQoMSkpKSAKCXsKCQlvdXRDb2xvci54eXogKj0gb3V0Q29sb3IudzsKCX0KCXJldHVybiBoYWxmNChvdXRDb2xvcik7Cn0KaGFsZjQgRGlzYWJsZUNvdmVyYWdlQXNBbHBoYV9TMShoYWxmNCBfaW5wdXQpIAp7CglfaW5wdXQgPSBDbGFtcGVkR3JhZGllbnRfUzFfYzAoX2lucHV0KTsKCWhhbGY0IF90bXBfNV9pbkNvbG9yID0gX2lucHV0OwoJcmV0dXJuIGhhbGY0KF9pbnB1dCk7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZjb2xvcl9TMDsKCWZsb2F0IGNvdmVyYWdlID0gdmNvdmVyYWdlX1MwOwoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNChoYWxmKGNvdmVyYWdlKSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBEaXNhYmxlQ292ZXJhZ2VBc0FscGhhX1MxKG91dHB1dENvbG9yX1MwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSAoaGFsZjQoMS4wKSAtIG91dHB1dF9TMSkgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAEAAAACAAAAHBvc2l0aW9uCAAAAGNvdmVyYWdlBQAAAGNvbG9yAAAACgAAAGxvY2FsQ29vcmQAAAEAAAAAAAAA","AYQA5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAACAMQAHOMFARUBIAADAAAAAAAAAAAAAAHIAAAAAAAIAAAAAQGIAA":"CAAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgEAAAAcBQAAY29uc3QgaW50IGtGaWxsQldfUzEgPSAwOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQldfUzEgPSAyOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQUFfUzEgPSAzOwp1bmlmb3JtIGZsb2F0NCB1cmVjdFVuaWZvcm1fUzE7CmluIGZsb2F0NCB2aW5DaXJjbGVFZGdlX1MwOwppbiBoYWxmNCB2aW5Db2xvcl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgUmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBfdG1wXzBfaW5Db2xvciA9IF9pbnB1dDsKCWhhbGYgY292ZXJhZ2U7CglpZiAoaW50KDEpID09IGtGaWxsQldfUzEgfHwgaW50KDEpID09IGtJbnZlcnNlRmlsbEJXX1MxKSAKCXsKCQljb3ZlcmFnZSA9IGhhbGYoYWxsKGdyZWF0ZXJUaGFuKGZsb2F0NChza19GcmFnQ29vcmQueHksIHVyZWN0VW5pZm9ybV9TMS56dyksIGZsb2F0NCh1cmVjdFVuaWZvcm1fUzEueHksIHNrX0ZyYWdDb29yZC54eSkpKSA/IDEgOiAwKTsKCX0KCWVsc2UgCgl7CgkJaGFsZjQgZGlzdHM0ID0gY2xhbXAoaGFsZjQoMS4wLCAxLjAsIC0xLjAsIC0xLjApICogaGFsZjQoc2tfRnJhZ0Nvb3JkLnh5eHkgLSB1cmVjdFVuaWZvcm1fUzEpLCAwLjAsIDEuMCk7CgkJaGFsZjIgZGlzdHMyID0gKGRpc3RzNC54eSArIGRpc3RzNC56dykgLSAxLjA7CgkJY292ZXJhZ2UgPSBkaXN0czIueCAqIGRpc3RzMi55OwoJfQoJaWYgKGludCgxKSA9PSBrSW52ZXJzZUZpbGxCV19TMSB8fCBpbnQoMSkgPT0ga0ludmVyc2VGaWxsQUFfUzEpIAoJewoJCWNvdmVyYWdlID0gMS4wIC0gY292ZXJhZ2U7Cgl9CglyZXR1cm4gaGFsZjQoX2lucHV0ICogY292ZXJhZ2UpOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQ0IGNpcmNsZUVkZ2U7CgljaXJjbGVFZGdlID0gdmluQ2lyY2xlRWRnZV9TMDsKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2aW5Db2xvcl9TMDsKCWZsb2F0IGQgPSBsZW5ndGgoY2lyY2xlRWRnZS54eSk7CgloYWxmIGRpc3RhbmNlVG9PdXRlckVkZ2UgPSBoYWxmKGNpcmNsZUVkZ2UueiAqICgxLjAgLSBkKSk7CgloYWxmIGVkZ2VBbHBoYSA9IHNhdHVyYXRlKGRpc3RhbmNlVG9PdXRlckVkZ2UpOwoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNChlZGdlQWxwaGEpOwoJaGFsZjQgb3V0cHV0X1MxOwoJb3V0cHV0X1MxID0gUmVjdF9TMShvdXRwdXRDb3ZlcmFnZV9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRfUzE7Cgl9Cn0KAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAwAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAMAAAAaW5DaXJjbGVFZGdlAQAAAAAAAAA=","DAQAAAAAAABGAABAYAAQAIHCAIAYAQUBAEAAAAAAEAAAAAAAAAAAAAB2AAAAAAACAAAAAEBSAAAAA":"CAAAAExTS1MWAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQyIHVBdGxhc1NpemVJbnZfUzA7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiB1c2hvcnQyIGluVGV4dHVyZUNvb3JkczsKb3V0IGZsb2F0MiB2VGV4dHVyZUNvb3Jkc19TMDsKZmxhdCBvdXQgZmxvYXQgdlRleEluZGV4X1MwOwpvdXQgaGFsZjQgdmluQ29sb3JfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIEJpdG1hcFRleHQKCWludCB0ZXhJZHggPSAwOwoJZmxvYXQyIHVub3JtVGV4Q29vcmRzID0gZmxvYXQyKGluVGV4dHVyZUNvb3Jkcy54LCBpblRleHR1cmVDb29yZHMueSk7Cgl2VGV4dHVyZUNvb3Jkc19TMCA9IHVub3JtVGV4Q29vcmRzICogdUF0bGFzU2l6ZUludl9TMDsKCXZUZXhJbmRleF9TMCA9IGZsb2F0KHRleElkeCk7Cgl2aW5Db2xvcl9TMCA9IGluQ29sb3I7CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJc2tfUG9zaXRpb24gPSBpblBvc2l0aW9uLnh5MDE7Cn0KAAAAAAAAyQEAAHVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1MwOwppbiBmbG9hdDIgdlRleHR1cmVDb29yZHNfUzA7CmZsYXQgaW4gZmxvYXQgdlRleEluZGV4X1MwOwppbiBoYWxmNCB2aW5Db2xvcl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIEJpdG1hcFRleHQKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2aW5Db2xvcl9TMDsKCWhhbGY0IHRleENvbG9yOwoJewoJCXRleENvbG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MwLCB2VGV4dHVyZUNvb3Jkc19TMCkucnJycjsKCX0KCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gdGV4Q29sb3I7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAADAAAACgAAAGluUG9zaXRpb24AAAcAAABpbkNvbG9yAA8AAABpblRleHR1cmVDb29yZHMAAQAAAAAAAAA=","JABAAAAABAAACAABBYAAAKAAAMAAGEAAAABRAEAAAEHCAAAAAAAABCAAAAAABAEQAEAAAAA":"CAAAAExTS1MbFgAAY29uc3QgZmxvYXQgUFJFQ0lTSU9OID0gNC4wMDAwMDA7CmNvbnN0IGZsb2F0IE1BWF9GSVhFRF9SRVNPTFZFX0xFVkVMID0gNS4wMDAwMDA7CmNvbnN0IGZsb2F0IE1BWF9GSVhFRF9TRUdNRU5UUyA9IDMyLjAwMDAwMDsKdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVhZmZpbmVNYXRyaXhfUzA7CnVuaWZvcm0gZmxvYXQyIHV0cmFuc2xhdGVfUzA7CmluIGZsb2F0MiByZXNvbHZlTGV2ZWxfYW5kX2lkeDsKaW4gZmxvYXQ0IHAwMTsKaW4gZmxvYXQ0IHAyMzsKaW4gZmxvYXQyIGZhblBvaW50QXR0cmliOwovLyBSZXR1cm5zIHRoZSBsZW5ndGggc3F1YXJlZCBvZiB0aGUgbGFyZ2VzdCBmb3J3YXJkIGRpZmZlcmVuY2UgZnJvbSBXYW5nJ3MgY3ViaWMgZm9ybXVsYS4KZmxvYXQgd2FuZ3NfZm9ybXVsYV9tYXhfZmRpZmZfcG93MihmbG9hdDIgcDAsIGZsb2F0MiBwMSwgZmxvYXQyIHAyLCBmbG9hdDIgcDMsICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmxvYXQyeDIgbWF0cml4KSAKewoJZmxvYXQyIGQwID0gbWF0cml4ICogKGZtYShmbG9hdDIoLTIpLCBwMSwgcDIpICsgcDApOwoJZmxvYXQyIGQxID0gbWF0cml4ICogKGZtYShmbG9hdDIoLTIpLCBwMiwgcDMpICsgcDEpOwoJcmV0dXJuIG1heChkb3QoZDAsZDApLCBkb3QoZDEsZDEpKTsKfQpmbG9hdCB3YW5nc19mb3JtdWxhX2N1YmljKGZsb2F0IF9wcmVjaXNpb25fLCBmbG9hdDIgcDAsIGZsb2F0MiBwMSwgZmxvYXQyIHAyLCBmbG9hdDIgcDMsICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmxvYXQyeDIgbWF0cml4KSAKewoJZmxvYXQgbSA9IHdhbmdzX2Zvcm11bGFfbWF4X2ZkaWZmX3BvdzIocDAsIHAxLCBwMiwgcDMsIG1hdHJpeCk7CglyZXR1cm4gbWF4KGNlaWwoc3FydCgwLjc1MDAwMCAqIF9wcmVjaXNpb25fICogc3FydChtKSkpLCAxLjApOwp9CmZsb2F0IHdhbmdzX2Zvcm11bGFfY3ViaWNfbG9nMihmbG9hdCBfcHJlY2lzaW9uXywgZmxvYXQyIHAwLCBmbG9hdDIgcDEsIGZsb2F0MiBwMiwgZmxvYXQyIHAzLCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmxvYXQyeDIgbWF0cml4KSAKewoJZmxvYXQgbSA9IHdhbmdzX2Zvcm11bGFfbWF4X2ZkaWZmX3BvdzIocDAsIHAxLCBwMiwgcDMsIG1hdHJpeCk7CglyZXR1cm4gY2VpbChsb2cyKG1heCgwLjU2MjUwMCAqIF9wcmVjaXNpb25fICogX3ByZWNpc2lvbl8gKiBtLCAxLjApKSAqIC4yNSk7Cn0KZmxvYXQgd2FuZ3NfZm9ybXVsYV9jb25pY19wb3cyKGZsb2F0IF9wcmVjaXNpb25fLCBmbG9hdDIgcDAsIGZsb2F0MiBwMSwgZmxvYXQyIHAyLCBmbG9hdCB3KSAKewoJLy8gVHJhbnNsYXRlIHRoZSBib3VuZGluZyBib3ggY2VudGVyIHRvIHRoZSBvcmlnaW4uCglmbG9hdDIgQyA9IChtaW4obWluKHAwLCBwMSksIHAyKSArIG1heChtYXgocDAsIHAxKSwgcDIpKSAqIDAuNTsKCXAwIC09IEM7CglwMSAtPSBDOwoJcDIgLT0gQzsKCS8vIENvbXB1dGUgbWF4IGxlbmd0aC4KCWZsb2F0IG0gPSBzcXJ0KG1heChtYXgoZG90KHAwLHAwKSwgZG90KHAxLHAxKSksIGRvdChwMixwMikpKTsKCS8vIENvbXB1dGUgZm9yd2FyZCBkaWZmZXJlbmNlcy4KCWZsb2F0MiBkcCA9IGZtYShmbG9hdDIoLTIuMCAqIHcpLCBwMSwgcDApICsgcDI7CglmbG9hdCBkdyA9IGFicyhmbWEoLTIuMCwgdywgMi4wKSk7CgkvLyBDb21wdXRlIG51bWVyYXRvciBhbmQgZGVub21pbmF0b3IgZm9yIHBhcmFtZXRyaWMgc3RlcCBzaXplIG9mIGxpbmVhcml6YXRpb24uIEhlcmUsIHRoZQoJLy8gZXBzaWxvbiByZWZlcmVuY2VkIGZyb20gdGhlIGNpdGVkIHBhcGVyIGlzIDEvcHJlY2lzaW9uLgoJZmxvYXQgcnBfbWludXNfMSA9IG1heCgwLjAsIGZtYShtLCBfcHJlY2lzaW9uXywgLTEuMCkpOwoJZmxvYXQgbnVtZXIgPSBsZW5ndGgoZHApICogX3ByZWNpc2lvbl8gKyBycF9taW51c18xICogZHc7CglmbG9hdCBkZW5vbSA9IDQgKiBtaW4odywgMS4wKTsKCXJldHVybiBudW1lci9kZW5vbTsKfQpmbG9hdCB3YW5nc19mb3JtdWxhX2NvbmljKGZsb2F0IF9wcmVjaXNpb25fLCBmbG9hdDIgcDAsIGZsb2F0MiBwMSwgZmxvYXQyIHAyLCBmbG9hdCB3KSAKewoJZmxvYXQgbjIgPSB3YW5nc19mb3JtdWxhX2NvbmljX3BvdzIoX3ByZWNpc2lvbl8sIHAwLCBwMSwgcDIsIHcpOwoJcmV0dXJuIG1heChjZWlsKHNxcnQobjIpKSwgMS4wKTsKfQpmbG9hdCB3YW5nc19mb3JtdWxhX2NvbmljX2xvZzIoZmxvYXQgX3ByZWNpc2lvbl8sIGZsb2F0MiBwMCwgZmxvYXQyIHAxLCBmbG9hdDIgcDIsIGZsb2F0IHcpIAp7CglmbG9hdCBuMiA9IHdhbmdzX2Zvcm11bGFfY29uaWNfcG93MihfcHJlY2lzaW9uXywgcDAsIHAxLCBwMiwgdyk7CglyZXR1cm4gY2VpbChsb2cyKG1heChuMiwgMS4wKSkgKiAuNSk7Cn0KYm9vbCBpc19jb25pY19jdXJ2ZSgpIAp7CglyZXR1cm4gaXNpbmYocDIzLncpOwp9CmJvb2wgaXNfdHJpYW5ndWxhcl9jb25pY19jdXJ2ZSgpIAp7CglyZXR1cm4gaXNpbmYocDIzLnopOwp9CmZsb2F0IGxkZXhwX3BvcnRhYmxlKGZsb2F0IHgsIGZsb2F0IHApIAp7CglyZXR1cm4gbGRleHAoeCwgaW50KHApKTsKfQp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciB0ZXNzZWxsYXRlX01pZGRsZU91dFNoYWRlcgoJZmxvYXQyeDIgQUZGSU5FX01BVFJJWCA9IGZsb2F0MngyKHVhZmZpbmVNYXRyaXhfUzApOwoJZmxvYXQyIFRSQU5TTEFURSA9IHV0cmFuc2xhdGVfUzA7CglmbG9hdCByZXNvbHZlTGV2ZWwgPSByZXNvbHZlTGV2ZWxfYW5kX2lkeC54OwoJZmxvYXQgaWR4SW5SZXNvbHZlTGV2ZWwgPSByZXNvbHZlTGV2ZWxfYW5kX2lkeC55OwoJZmxvYXQyIGxvY2FsY29vcmQ7CgkvLyBBIG5lZ2F0aXZlIHJlc29sdmUgbGV2ZWwgbWVhbnMgdGhpcyBpcyB0aGUgZmFuIHBvaW50LgoJaWYgKHJlc29sdmVMZXZlbCA8IDApIAoJewoJCWxvY2FsY29vcmQgPSBmYW5Qb2ludEF0dHJpYjsKCX0KCWVsc2UgICAgICAgICAgICBpZiAoaXNfdHJpYW5ndWxhcl9jb25pY19jdXJ2ZSgpKSAKCXsKCQkvLyBUaGlzIHBhdGNoIGlzIGFuIGV4YWN0IHRyaWFuZ2xlLgoJCWxvY2FsY29vcmQgPSAocmVzb2x2ZUxldmVsICE9IDApICAgICAgPyBwMDEuencgICAgICAgICAgICAgICAgICAgICAgICAgICA6IChpZHhJblJlc29sdmVMZXZlbCAhPSAwKSA/IHAyMy54eSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDogcDAxLnh5OwoJfQoJZWxzZSAKCXsKCQlmbG9hdDIgcDA9cDAxLnh5LCBwMT1wMDEuencsIHAyPXAyMy54eSwgcDM9cDIzLnp3OwoJCWZsb2F0IHcgPSAtMTsKCQkvLyB3IDwgMCB0ZWxscyB1cyB0byB0cmVhdCB0aGUgaW5zdGFuY2UgYXMgYW4gaW50ZWdyYWwgY3ViaWMuCgkJZmxvYXQgbWF4UmVzb2x2ZUxldmVsOwoJCWlmIChpc19jb25pY19jdXJ2ZSgpKSAKCQl7CgkJCS8vIENvbmljcyBhcmUgMyBwb2ludHMsIHdpdGggdGhlIHdlaWdodCBpbiBwMy4KCQkJdyA9IHAzLng7CgkJCW1heFJlc29sdmVMZXZlbCA9IHdhbmdzX2Zvcm11bGFfY29uaWNfbG9nMihQUkVDSVNJT04sIEFGRklORV9NQVRSSVggKiBwMCwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEFGRklORV9NQVRSSVggKiBwMSwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEFGRklORV9NQVRSSVggKiBwMiwgdyk7CgkJCXAxICo9IHc7CgkJCS8vIFVucHJvamVjdCBwMS4KCQkJcDMgPSBwMjsKCQkJLy8gRHVwbGljYXRlIHRoZSBlbmRwb2ludCBmb3Igc2hhcmVkIGNvZGUgdGhhdCBhbHNvIHJ1bnMgb24gY3ViaWNzLgoJCX0KCQllbHNlIAoJCXsKCQkJLy8gVGhlIHBhdGNoIGlzIGFuIGludGVncmFsIGN1YmljLgoJCQltYXhSZXNvbHZlTGV2ZWwgPSB3YW5nc19mb3JtdWxhX2N1YmljX2xvZzIoUFJFQ0lTSU9OLCBwMCwgcDEsIHAyLCBwMywgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBBRkZJTkVfTUFUUklYKTsKCQl9CgkJaWYgKHJlc29sdmVMZXZlbCA+IG1heFJlc29sdmVMZXZlbCkgCgkJewoJCQkvLyBUaGlzIHZlcnRleCBpcyBhdCBhIGhpZ2hlciByZXNvbHZlIGxldmVsIHRoYW4gd2UgbmVlZC4gRGVtb3RlIHRvIGEgbG93ZXIKCQkJLy8gcmVzb2x2ZUxldmVsLCB3aGljaCB3aWxsIHByb2R1Y2UgYSBkZWdlbmVyYXRlIHRyaWFuZ2xlLgoJCQlpZHhJblJlc29sdmVMZXZlbCA9IGZsb29yKGxkZXhwX3BvcnRhYmxlKGlkeEluUmVzb2x2ZUxldmVsLCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXhSZXNvbHZlTGV2ZWwgLSByZXNvbHZlTGV2ZWwpKTsKCQkJcmVzb2x2ZUxldmVsID0gbWF4UmVzb2x2ZUxldmVsOwoJCX0KCQkvLyBQcm9tb3RlIG91ciBsb2NhdGlvbiB0byBhIGRpc2NyZXRlIHBvc2l0aW9uIGluIHRoZSBtYXhpbXVtIGZpeGVkIHJlc29sdmUgbGV2ZWwuCgkJLy8gVGhpcyBpcyBleHRyYSBwYXJhbm9pYSB0byBlbnN1cmUgd2UgZ2V0IHRoZSBleGFjdCBzYW1lIGZwMzIgY29vcmRpbmF0ZXMgZm9yCgkJLy8gY29sb2NhdGVkIHBvaW50cyBmcm9tIGRpZmZlcmVudCByZXNvbHZlIGxldmVscyAoZS5nLiwgdGhlIHZlcnRpY2VzIFQ9My80IGFuZAoJCS8vIFQ9Ni84IHNob3VsZCBiZSBleGFjdGx5IGNvbG9jYXRlZCkuCgkJZmxvYXQgZml4ZWRWZXJ0ZXhJRCA9IGZsb29yKC41ICsgbGRleHBfcG9ydGFibGUoICAgICAgICAgICAgICAgICAgICAgICAgaWR4SW5SZXNvbHZlTGV2ZWwsIE1BWF9GSVhFRF9SRVNPTFZFX0xFVkVMIC0gcmVzb2x2ZUxldmVsKSk7CgkJaWYgKDAgPCBmaXhlZFZlcnRleElEICYmIGZpeGVkVmVydGV4SUQgPCBNQVhfRklYRURfU0VHTUVOVFMpIAoJCXsKCQkJZmxvYXQgVCA9IGZpeGVkVmVydGV4SUQgKiAoMSAvIE1BWF9GSVhFRF9TRUdNRU5UUyk7CgkJCS8vIEV2YWx1YXRlIGF0IFQuIFVzZSBEZSBDYXN0ZWxqYXUncyBmb3IgaXRzIGFjY3VyYWN5IGFuZCBzdGFiaWxpdHkuCgkJCWZsb2F0MiBhYiA9IG1peChwMCwgcDEsIFQpOwoJCQlmbG9hdDIgYmMgPSBtaXgocDEsIHAyLCBUKTsKCQkJZmxvYXQyIGNkID0gbWl4KHAyLCBwMywgVCk7CgkJCWZsb2F0MiBhYmMgPSBtaXgoYWIsIGJjLCBUKTsKCQkJZmxvYXQyIGJjZCA9IG1peChiYywgY2QsIFQpOwoJCQlmbG9hdDIgYWJjZCA9IG1peChhYmMsIGJjZCwgVCk7CgkJCS8vIEV2YWx1YXRlIHRoZSBjb25pYyB3ZWlnaHQgYXQgVC4KCQkJZmxvYXQgdSA9IG1peCgxLjAsIHcsIFQpOwoJCQlmbG9hdCB2ID0gdyArIDEgLSB1OwoJCQkvLyA9PSBtaXgodywgMSwgVCkKCQkJZmxvYXQgdXYgPSBtaXgodSwgdiwgVCk7CgkJCWxvY2FsY29vcmQgPSAodyA8IDApID8gLypjdWJpYyovIGFiY2QgOiAvKmNvbmljKi8gYWJjL3V2OwoJCX0KCQllbHNlIAoJCXsKCQkJbG9jYWxjb29yZCA9IChmaXhlZFZlcnRleElEID09IDApID8gcDAueHkgOiBwMy54eTsKCQl9Cgl9CglmbG9hdDIgdmVydGV4cG9zID0gQUZGSU5FX01BVFJJWCAqIGxvY2FsY29vcmQgKyBUUkFOU0xBVEU7Cglza19Qb3NpdGlvbiA9IHZlcnRleHBvcy54eTAxOwp9CgAAAAAA4QAAAHVuaWZvcm0gaGFsZjQgdWNvbG9yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgdGVzc2VsbGF0ZV9NaWRkbGVPdXRTaGFkZXIKCWhhbGY0IG91dHB1dENvbG9yX1MwID0gdWNvbG9yX1MwOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogRGlzYWJsZSBDb2xvcgoJfQp9CgAAAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAQAAAAUAAAAcmVzb2x2ZUxldmVsX2FuZF9pZHgDAAAAcDAxAAMAAABwMjMADgAAAGZhblBvaW50QXR0cmliAAABAAAAAAAAAA==","DAQAAAAAAABGAABAYAAQAIHCAIAYAQUBAEAAAAAAEAAAAAAAAAAAAIBSQB5VRECGAEAAAMAAAAAAAAAAACAA4AAAACAAAAAAACCAYAA":"CAAAAExTS1MWAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQyIHVBdGxhc1NpemVJbnZfUzA7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiB1c2hvcnQyIGluVGV4dHVyZUNvb3JkczsKb3V0IGZsb2F0MiB2VGV4dHVyZUNvb3Jkc19TMDsKZmxhdCBvdXQgZmxvYXQgdlRleEluZGV4X1MwOwpvdXQgaGFsZjQgdmluQ29sb3JfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIEJpdG1hcFRleHQKCWludCB0ZXhJZHggPSAwOwoJZmxvYXQyIHVub3JtVGV4Q29vcmRzID0gZmxvYXQyKGluVGV4dHVyZUNvb3Jkcy54LCBpblRleHR1cmVDb29yZHMueSk7Cgl2VGV4dHVyZUNvb3Jkc19TMCA9IHVub3JtVGV4Q29vcmRzICogdUF0bGFzU2l6ZUludl9TMDsKCXZUZXhJbmRleF9TMCA9IGZsb2F0KHRleElkeCk7Cgl2aW5Db2xvcl9TMCA9IGluQ29sb3I7CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJc2tfUG9zaXRpb24gPSBpblBvc2l0aW9uLnh5MDE7Cn0KAAABAAAA4wQAAGNvbnN0IGludCBrRmlsbEJXX1MxID0gMDsKY29uc3QgaW50IGtJbnZlcnNlRmlsbEJXX1MxID0gMjsKY29uc3QgaW50IGtJbnZlcnNlRmlsbEFBX1MxID0gMzsKdW5pZm9ybSBmbG9hdDQgdXJlY3RVbmlmb3JtX1MxOwp1bmlmb3JtIHNhbXBsZXIyRCB1VGV4dHVyZVNhbXBsZXJfMF9TMDsKaW4gZmxvYXQyIHZUZXh0dXJlQ29vcmRzX1MwOwpmbGF0IGluIGZsb2F0IHZUZXhJbmRleF9TMDsKaW4gaGFsZjQgdmluQ29sb3JfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IFJlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJaGFsZjQgX3RtcF8wX2luQ29sb3IgPSBfaW5wdXQ7CgloYWxmIGNvdmVyYWdlOwoJaWYgKGludCgxKSA9PSBrRmlsbEJXX1MxIHx8IGludCgxKSA9PSBrSW52ZXJzZUZpbGxCV19TMSkgCgl7CgkJY292ZXJhZ2UgPSBoYWxmKGFsbChncmVhdGVyVGhhbihmbG9hdDQoc2tfRnJhZ0Nvb3JkLnh5LCB1cmVjdFVuaWZvcm1fUzEuencpLCBmbG9hdDQodXJlY3RVbmlmb3JtX1MxLnh5LCBza19GcmFnQ29vcmQueHkpKSkgPyAxIDogMCk7Cgl9CgllbHNlIAoJewoJCWhhbGY0IGRpc3RzNCA9IGNsYW1wKGhhbGY0KDEuMCwgMS4wLCAtMS4wLCAtMS4wKSAqIGhhbGY0KHNrX0ZyYWdDb29yZC54eXh5IC0gdXJlY3RVbmlmb3JtX1MxKSwgMC4wLCAxLjApOwoJCWhhbGYyIGRpc3RzMiA9IChkaXN0czQueHkgKyBkaXN0czQuencpIC0gMS4wOwoJCWNvdmVyYWdlID0gZGlzdHMyLnggKiBkaXN0czIueTsKCX0KCWlmIChpbnQoMSkgPT0ga0ludmVyc2VGaWxsQldfUzEgfHwgaW50KDEpID09IGtJbnZlcnNlRmlsbEFBX1MxKSAKCXsKCQljb3ZlcmFnZSA9IDEuMCAtIGNvdmVyYWdlOwoJfQoJcmV0dXJuIGhhbGY0KF9pbnB1dCAqIGNvdmVyYWdlKTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgQml0bWFwVGV4dAoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZpbkNvbG9yX1MwOwoJaGFsZjQgdGV4Q29sb3I7Cgl7CgkJdGV4Q29sb3IgPSBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfUzAsIHZUZXh0dXJlQ29vcmRzX1MwKS5ycnJyOwoJfQoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSB0ZXhDb2xvcjsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IFJlY3RfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAADAAAACgAAAGluUG9zaXRpb24AAAcAAABpbkNvbG9yAA8AAABpblRleHR1cmVDb29yZHMAAQAAAAAAAAA=","HUJAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAQAAAAAAQQGABZAA6IAAAAACAAAAAADUAAAAAAAEAAAAAIDEAAAAAAA":"CAAAAExTS1PlAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmxvY2FsQ29vcmRfUzAgPSBsb2NhbENvb3JkOwoJc2tfUG9zaXRpb24gPSBwb3NpdGlvbi54eTAxOwp9CgAAAAEAAABGAwAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CnVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1MwOwppbiBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IENpcmN1bGFyUlJlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGR4eTAgPSB1aW5uZXJSZWN0X1MxLkxUIC0gc2tfRnJhZ0Nvb3JkLnh5OwoJZmxvYXQyIGR4eTEgPSBza19GcmFnQ29vcmQueHkgLSB1aW5uZXJSZWN0X1MxLlJCOwoJZmxvYXQyIGR4eSA9IG1heChtYXgoZHh5MCwgZHh5MSksIDAuMCk7CgloYWxmIGFscGhhID0gaGFsZihzYXR1cmF0ZSh1cmFkaXVzUGx1c0hhbGZfUzEueCAtIGxlbmd0aChkeHkpKSk7CglyZXR1cm4gX2lucHV0ICogYWxwaGE7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzAgPSBoYWxmNCgxKTsKCWZsb2F0MiB0ZXhDb29yZDsKCXRleENvb3JkID0gdmxvY2FsQ29vcmRfUzA7CglvdXRwdXRDb2xvcl9TMCA9ICgoc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MwLCB0ZXhDb29yZCkgKiBoYWxmNCgxKSkpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IENpcmN1bGFyUlJlY3RfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgAAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","HUQAAAAAAAMAADAAAIOAAAH677776IZOCAAP577777777777777777YBAAAAAAAAAAAKAAYAAAACAAAAAAACCAYAAA":"CAAAAExTS1PUAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7CmZsYXQgb3V0IGhhbGY0IHZjb2xvcl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgl2Y29sb3JfUzAgPSBjb2xvcjsKCXNrX1Bvc2l0aW9uID0gcG9zaXRpb24ueHkwMTsKfQoAAAAAKQEAAGZsYXQgaW4gaGFsZjQgdmNvbG9yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24FAAAAY29sb3IAAAABAAAAAAAAAA==","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAALUAQBAEAQAAAAGQCBAMQACAIAAAAAACQAGAAAAAQAAAAAAAQQGAAAAA":"CAAAAExTS1M2AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAAAAAGUDAAB1bmlmb3JtIGZsb2F0NCB1Y2xhbXBfUzFfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzE7CmluIGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgVGV4dHVyZUVmZmVjdF9TMV9jMChoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgaW5Db29yZCA9IHZUcmFuc2Zvcm1lZENvb3Jkc18yX1MwOwoJZmxvYXQyIHN1YnNldENvb3JkOwoJc3Vic2V0Q29vcmQueCA9IGluQ29vcmQueDsKCXN1YnNldENvb3JkLnkgPSBpbkNvb3JkLnk7CglmbG9hdDIgY2xhbXBlZENvb3JkOwoJY2xhbXBlZENvb3JkID0gY2xhbXAoc3Vic2V0Q29vcmQsIHVjbGFtcF9TMV9jMC54eSwgdWNsYW1wX1MxX2MwLnp3KTsKCWhhbGY0IHRleHR1cmVDb2xvciA9IHNhbXBsZSh1VGV4dHVyZVNhbXBsZXJfMF9TMSwgY2xhbXBlZENvb3JkKTsKCXJldHVybiB0ZXh0dXJlQ29sb3I7Cn0KaGFsZjQgTWF0cml4RWZmZWN0X1MxKGhhbGY0IF9pbnB1dCkgCnsKCXJldHVybiBUZXh0dXJlRWZmZWN0X1MxX2MwKF9pbnB1dCk7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzAgPSBoYWxmNCgxKTsKCWNvbnN0IGhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoMSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBNYXRyaXhFZmZlY3RfUzEob3V0cHV0Q29sb3JfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dF9TMSAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24KAAAAbG9jYWxDb29yZAAAAQAAAAAAAAA=","B2AAQAAABQAAIAABBYAAB7777777777774ABICAAAAAAAAAAAAAABUABAAAAAEAAAAAIBEABAAAAA":"CAAAAExTS1MOAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmIGluQ292ZXJhZ2U7Cm91dCBoYWxmIHZpbkNvdmVyYWdlX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBEZWZhdWx0R2VvbWV0cnlQcm9jZXNzb3IKCWZsb2F0MiBfdG1wXzFfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247Cgl2aW5Db3ZlcmFnZV9TMCA9IGluQ292ZXJhZ2U7Cglza19Qb3NpdGlvbiA9IF90bXBfMV9pblBvc2l0aW9uLnh5MDE7Cn0KAAAAAAAAZQEAAHVuaWZvcm0gaGFsZjQgdUNvbG9yX1MwOwppbiBoYWxmIHZpbkNvdmVyYWdlX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgRGVmYXVsdEdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdUNvbG9yX1MwOwoJaGFsZiBhbHBoYSA9IDEuMDsKCWFscGhhID0gdmluQ292ZXJhZ2VfUzA7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KGFscGhhKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAIAAAAKAAAAaW5Qb3NpdGlvbgAACgAAAGluQ292ZXJhZ2UAAAEAAAAAAAAA","AYQQ5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAACAMQAHOMFARUBIAADAAAAAAAAAAAAAAHIAAAAAAAIAAAAAQGIAA":"CAAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgEAAACtBQAAY29uc3QgaW50IGtGaWxsQldfUzEgPSAwOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQldfUzEgPSAyOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQUFfUzEgPSAzOwp1bmlmb3JtIGZsb2F0NCB1cmVjdFVuaWZvcm1fUzE7CmluIGZsb2F0NCB2aW5DaXJjbGVFZGdlX1MwOwppbiBoYWxmNCB2aW5Db2xvcl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgUmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBfdG1wXzBfaW5Db2xvciA9IF9pbnB1dDsKCWhhbGYgY292ZXJhZ2U7CglpZiAoaW50KDEpID09IGtGaWxsQldfUzEgfHwgaW50KDEpID09IGtJbnZlcnNlRmlsbEJXX1MxKSAKCXsKCQljb3ZlcmFnZSA9IGhhbGYoYWxsKGdyZWF0ZXJUaGFuKGZsb2F0NChza19GcmFnQ29vcmQueHksIHVyZWN0VW5pZm9ybV9TMS56dyksIGZsb2F0NCh1cmVjdFVuaWZvcm1fUzEueHksIHNrX0ZyYWdDb29yZC54eSkpKSA/IDEgOiAwKTsKCX0KCWVsc2UgCgl7CgkJaGFsZjQgZGlzdHM0ID0gY2xhbXAoaGFsZjQoMS4wLCAxLjAsIC0xLjAsIC0xLjApICogaGFsZjQoc2tfRnJhZ0Nvb3JkLnh5eHkgLSB1cmVjdFVuaWZvcm1fUzEpLCAwLjAsIDEuMCk7CgkJaGFsZjIgZGlzdHMyID0gKGRpc3RzNC54eSArIGRpc3RzNC56dykgLSAxLjA7CgkJY292ZXJhZ2UgPSBkaXN0czIueCAqIGRpc3RzMi55OwoJfQoJaWYgKGludCgxKSA9PSBrSW52ZXJzZUZpbGxCV19TMSB8fCBpbnQoMSkgPT0ga0ludmVyc2VGaWxsQUFfUzEpIAoJewoJCWNvdmVyYWdlID0gMS4wIC0gY292ZXJhZ2U7Cgl9CglyZXR1cm4gaGFsZjQoX2lucHV0ICogY292ZXJhZ2UpOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQ0IGNpcmNsZUVkZ2U7CgljaXJjbGVFZGdlID0gdmluQ2lyY2xlRWRnZV9TMDsKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2aW5Db2xvcl9TMDsKCWZsb2F0IGQgPSBsZW5ndGgoY2lyY2xlRWRnZS54eSk7CgloYWxmIGRpc3RhbmNlVG9PdXRlckVkZ2UgPSBoYWxmKGNpcmNsZUVkZ2UueiAqICgxLjAgLSBkKSk7CgloYWxmIGVkZ2VBbHBoYSA9IHNhdHVyYXRlKGRpc3RhbmNlVG9PdXRlckVkZ2UpOwoJaGFsZiBkaXN0YW5jZVRvSW5uZXJFZGdlID0gaGFsZihjaXJjbGVFZGdlLnogKiAoZCAtIGNpcmNsZUVkZ2UudykpOwoJaGFsZiBpbm5lckFscGhhID0gc2F0dXJhdGUoZGlzdGFuY2VUb0lubmVyRWRnZSk7CgllZGdlQWxwaGEgKj0gaW5uZXJBbHBoYTsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoZWRnZUFscGhhKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IFJlY3RfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IADAAAAGluQ2lyY2xlRWRnZQEAAAAAAAAA","FAAQMYAAMAAAEADAAABAEYAAAICIAB5AABQAAAQAMAAAEATAAABAIIGAAEDCBYQCA4AAAAAAEADZAAAAAAIAAAAAACQAGAAAAAQAAAAAAAQQG":"CAAAAExTS1PUCwAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0NCByYWRpaV9zZWxlY3RvcjsKaW4gZmxvYXQ0IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHM7CmluIGZsb2F0NCBhYV9ibG9hdF9hbmRfY292ZXJhZ2U7CmluIGZsb2F0NCByYWRpaV94OwppbiBmbG9hdDQgcmFkaWlfeTsKaW4gZmxvYXQ0IHNrZXc7CmluIGZsb2F0MiB0cmFuc2xhdGVfYW5kX2xvY2Fscm90YXRlOwppbiBoYWxmNCBjb2xvcjsKZmxhdCBvdXQgaGFsZjQgdmNvbG9yX1MwOwpvdXQgZmxvYXQyIHZhcmNjb29yZF9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgRmlsbFJSZWN0T3A6OlByb2Nlc3NvcgoJdmNvbG9yX1MwID0gY29sb3I7CglmbG9hdCBhYV9ibG9hdF9tdWx0aXBsaWVyID0gMTsKCWZsb2F0MiBjb3JuZXIgPSBjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzLnh5OwoJZmxvYXQyIHJhZGl1c19vdXRzZXQgPSBjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzLnp3OwoJZmxvYXQyIGFhX2Jsb2F0X2RpcmVjdGlvbiA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS54eTsKCWZsb2F0IGlzX2xpbmVhcl9jb3ZlcmFnZSA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS53OwoJZmxvYXQyIHBpeGVsbGVuZ3RoID0gaW52ZXJzZXNxcnQoZmxvYXQyKGRvdChza2V3Lnh6LCBza2V3Lnh6KSwgZG90KHNrZXcueXcsIHNrZXcueXcpKSk7CglmbG9hdDQgbm9ybWFsaXplZF9heGlzX2RpcnMgPSBza2V3ICogcGl4ZWxsZW5ndGgueHl4eTsKCWZsb2F0MiBheGlzd2lkdGhzID0gKGFicyhub3JtYWxpemVkX2F4aXNfZGlycy54eSkgKyBhYnMobm9ybWFsaXplZF9heGlzX2RpcnMuencpKTsKCWZsb2F0MiBhYV9ibG9hdHJhZGl1cyA9IGF4aXN3aWR0aHMgKiBwaXhlbGxlbmd0aCAqIC41OwoJZmxvYXQ0IHJhZGlpX2FuZF9uZWlnaGJvcnMgPSByYWRpaV9zZWxlY3RvciogZmxvYXQ0eDQocmFkaWlfeCwgcmFkaWlfeSwgcmFkaWlfeC55eHd6LCByYWRpaV95Lnd6eXgpOwoJZmxvYXQyIHJhZGlpID0gcmFkaWlfYW5kX25laWdoYm9ycy54eTsKCWZsb2F0MiBuZWlnaGJvcl9yYWRpaSA9IHJhZGlpX2FuZF9uZWlnaGJvcnMuenc7CglmbG9hdCBjb3ZlcmFnZV9tdWx0aXBsaWVyID0gMTsKCWlmIChhbnkoZ3JlYXRlclRoYW4oYWFfYmxvYXRyYWRpdXMsIGZsb2F0MigxKSkpKSAKCXsKCQljb3JuZXIgPSBtYXgoYWJzKGNvcm5lciksIGFhX2Jsb2F0cmFkaXVzKSAqIHNpZ24oY29ybmVyKTsKCQljb3ZlcmFnZV9tdWx0aXBsaWVyID0gMSAvIChtYXgoYWFfYmxvYXRyYWRpdXMueCwgMSkgKiBtYXgoYWFfYmxvYXRyYWRpdXMueSwgMSkpOwoJCXJhZGlpID0gZmxvYXQyKDApOwoJfQoJZmxvYXQgY292ZXJhZ2UgPSBhYV9ibG9hdF9hbmRfY292ZXJhZ2UuejsKCWlmIChhbnkobGVzc1RoYW4ocmFkaWksIGFhX2Jsb2F0cmFkaXVzICogMS41KSkpIAoJewoJCXJhZGlpID0gZmxvYXQyKDApOwoJCWFhX2Jsb2F0X2RpcmVjdGlvbiA9IHNpZ24oY29ybmVyKTsKCQlpZiAoY292ZXJhZ2UgPiAuNSkgCgkJewoJCQlhYV9ibG9hdF9kaXJlY3Rpb24gPSAtYWFfYmxvYXRfZGlyZWN0aW9uOwoJCX0KCQlpc19saW5lYXJfY292ZXJhZ2UgPSAxOwoJfQoJZWxzZSAKCXsKCQlyYWRpaSA9IGNsYW1wKHJhZGlpLCBwaXhlbGxlbmd0aCAqIDEuNSwgMiAtIHBpeGVsbGVuZ3RoICogMS41KTsKCQluZWlnaGJvcl9yYWRpaSA9IGNsYW1wKG5laWdoYm9yX3JhZGlpLCBwaXhlbGxlbmd0aCAqIDEuNSwgMiAtIHBpeGVsbGVuZ3RoICogMS41KTsKCQlmbG9hdDIgc3BhY2luZyA9IDIgLSByYWRpaSAtIG5laWdoYm9yX3JhZGlpOwoJCWZsb2F0MiBleHRyYV9wYWQgPSBtYXgocGl4ZWxsZW5ndGggKiAuMDYyNSAtIHNwYWNpbmcsIGZsb2F0MigwKSk7CgkJcmFkaWkgLT0gZXh0cmFfcGFkICogLjU7Cgl9CglmbG9hdDIgYWFfb3V0c2V0ID0gYWFfYmxvYXRfZGlyZWN0aW9uICogYWFfYmxvYXRyYWRpdXMgKiBhYV9ibG9hdF9tdWx0aXBsaWVyOwoJZmxvYXQyIHZlcnRleHBvcyA9IGNvcm5lciArIHJhZGl1c19vdXRzZXQgKiByYWRpaSArIGFhX291dHNldDsKCWlmIChjb3ZlcmFnZSA+IC41KSAKCXsKCQlpZiAoYWFfYmxvYXRfZGlyZWN0aW9uLnggIT0gMCAmJiB2ZXJ0ZXhwb3MueCAqIGNvcm5lci54IDwgMCkgCgkJewoJCQlmbG9hdCBiYWNrc2V0ID0gYWJzKHZlcnRleHBvcy54KTsKCQkJdmVydGV4cG9zLnggPSAwOwoJCQl2ZXJ0ZXhwb3MueSArPSBiYWNrc2V0ICogc2lnbihjb3JuZXIueSkgKiBwaXhlbGxlbmd0aC55L3BpeGVsbGVuZ3RoLng7CgkJCWNvdmVyYWdlID0gKGNvdmVyYWdlIC0gLjUpICogYWJzKGNvcm5lci54KSAvIChhYnMoY29ybmVyLngpICsgYmFja3NldCkgKyAuNTsKCQl9CgkJaWYgKGFhX2Jsb2F0X2RpcmVjdGlvbi55ICE9IDAgJiYgdmVydGV4cG9zLnkgKiBjb3JuZXIueSA8IDApIAoJCXsKCQkJZmxvYXQgYmFja3NldCA9IGFicyh2ZXJ0ZXhwb3MueSk7CgkJCXZlcnRleHBvcy55ID0gMDsKCQkJdmVydGV4cG9zLnggKz0gYmFja3NldCAqIHNpZ24oY29ybmVyLngpICogcGl4ZWxsZW5ndGgueC9waXhlbGxlbmd0aC55OwoJCQljb3ZlcmFnZSA9IChjb3ZlcmFnZSAtIC41KSAqIGFicyhjb3JuZXIueSkgLyAoYWJzKGNvcm5lci55KSArIGJhY2tzZXQpICsgLjU7CgkJfQoJfQoJZmxvYXQyeDIgc2tld21hdHJpeCA9IGZsb2F0MngyKHNrZXcueHksIHNrZXcuencpOwoJZmxvYXQyIGRldmNvb3JkID0gdmVydGV4cG9zICogc2tld21hdHJpeCArIHRyYW5zbGF0ZV9hbmRfbG9jYWxyb3RhdGUueHk7CglpZiAoMCAhPSBpc19saW5lYXJfY292ZXJhZ2UpIAoJewoJCXZhcmNjb29yZF9TMC54eSA9IGZsb2F0MigwLCBjb3ZlcmFnZSAqIGNvdmVyYWdlX211bHRpcGxpZXIpOwoJfQoJZWxzZSAKCXsKCQlmbG9hdDIgYXJjY29vcmQgPSAxIC0gYWJzKHJhZGl1c19vdXRzZXQpICsgYWFfb3V0c2V0L3JhZGlpICogY29ybmVyOwoJCXZhcmNjb29yZF9TMC54eSA9IGZsb2F0MihhcmNjb29yZC54KzEsIGFyY2Nvb3JkLnkpOwoJfQoJc2tfUG9zaXRpb24gPSBkZXZjb29yZC54eTAxOwp9CgEAAAA/BAAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CmZsYXQgaW4gaGFsZjQgdmNvbG9yX1MwOwppbiBmbG9hdDIgdmFyY2Nvb3JkX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBDaXJjdWxhclJSZWN0X1MxKGhhbGY0IF9pbnB1dCkgCnsKCWZsb2F0MiBkeHkgPSBtYXgodWlubmVyUmVjdF9TMS5MVCAtIHNrX0ZyYWdDb29yZC54eSwgMC4wKTsKCWhhbGYgcmlnaHRBbHBoYSA9IGhhbGYoc2F0dXJhdGUodWlubmVyUmVjdF9TMS5SIC0gc2tfRnJhZ0Nvb3JkLngpKTsKCWhhbGYgYm90dG9tQWxwaGEgPSBoYWxmKHNhdHVyYXRlKHVpbm5lclJlY3RfUzEuQiAtIHNrX0ZyYWdDb29yZC55KSk7CgloYWxmIGFscGhhID0gYm90dG9tQWxwaGEgKiByaWdodEFscGhhICogaGFsZihzYXR1cmF0ZSh1cmFkaXVzUGx1c0hhbGZfUzEueCAtIGxlbmd0aChkeHkpKSk7CglyZXR1cm4gX2lucHV0ICogYWxwaGE7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIEZpbGxSUmVjdE9wOjpQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2Y29sb3JfUzA7CglmbG9hdCB4X3BsdXNfMT12YXJjY29vcmRfUzAueCwgeT12YXJjY29vcmRfUzAueTsKCWhhbGYgY292ZXJhZ2U7CglpZiAoMCA9PSB4X3BsdXNfMSkgCgl7CgkJY292ZXJhZ2UgPSBoYWxmKHkpOwoJfQoJZWxzZSAKCXsKCQlmbG9hdCBmbiA9IHhfcGx1c18xICogKHhfcGx1c18xIC0gMik7CgkJZm4gPSBmbWEoeSx5LCBmbik7CgkJZmxvYXQgZm53aWR0aCA9IGZ3aWR0aChmbik7CgkJY292ZXJhZ2UgPSAuNSAtIGhhbGYoZm4vZm53aWR0aCk7CgkJY292ZXJhZ2UgPSBjbGFtcChjb3ZlcmFnZSwgMCwgMSk7Cgl9CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KGNvdmVyYWdlKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IENpcmN1bGFyUlJlY3RfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAIAAAADgAAAHJhZGlpX3NlbGVjdG9yAAAZAAAAY29ybmVyX2FuZF9yYWRpdXNfb3V0c2V0cwAAABUAAABhYV9ibG9hdF9hbmRfY292ZXJhZ2UAAAAHAAAAcmFkaWlfeAAHAAAAcmFkaWlfeQAEAAAAc2tldxkAAAB0cmFuc2xhdGVfYW5kX2xvY2Fscm90YXRlAAAABQAAAGNvbG9yAAAAAQAAAAAAAAA=","DASAAAAAQAAWAABAYAAQBYH7777Z6QQBAEAAAAAAEAAAAAAAEBSAAAB2AAAAAAACAAAAAEBSAAAAA":"CAAAAExTS1PVAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQyIHVBdGxhc1NpemVJbnZfUzA7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiB1c2hvcnQyIGluVGV4dHVyZUNvb3JkczsKb3V0IGZsb2F0MiB2VGV4dHVyZUNvb3Jkc19TMDsKZmxhdCBvdXQgZmxvYXQgdlRleEluZGV4X1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBCaXRtYXBUZXh0CglpbnQgdGV4SWR4ID0gMDsKCWZsb2F0MiB1bm9ybVRleENvb3JkcyA9IGZsb2F0MihpblRleHR1cmVDb29yZHMueCwgaW5UZXh0dXJlQ29vcmRzLnkpOwoJdlRleHR1cmVDb29yZHNfUzAgPSB1bm9ybVRleENvb3JkcyAqIHVBdGxhc1NpemVJbnZfUzA7Cgl2VGV4SW5kZXhfUzAgPSBmbG9hdCh0ZXhJZHgpOwoJZmxvYXQyIF90bXBfMV9pblBvc2l0aW9uID0gaW5Qb3NpdGlvbjsKCXNrX1Bvc2l0aW9uID0gaW5Qb3NpdGlvbi54eTAxOwp9CgAAAAAAAAD4AQAAdW5pZm9ybSBoYWxmNCB1Q29sb3JfUzA7CnVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1MwOwppbiBmbG9hdDIgdlRleHR1cmVDb29yZHNfUzA7CmZsYXQgaW4gZmxvYXQgdlRleEluZGV4X1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgQml0bWFwVGV4dAoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHVDb2xvcl9TMDsKCWhhbGY0IHRleENvbG9yOwoJewoJCXRleENvbG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MwLCB2VGV4dHVyZUNvb3Jkc19TMCk7Cgl9CglvdXRwdXRDb2xvcl9TMCA9IG91dHB1dENvbG9yX1MwICogdGV4Q29sb3I7Cgljb25zdCBoYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KDEpOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAgAAAAoAAABpblBvc2l0aW9uAAAPAAAAaW5UZXh0dXJlQ29vcmRzAAEAAAAAAAAA","AYQA5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAACABYQA6AAAEAAAAAAAIADQAAAAIAAAAAAAIIDA":"CAAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgEAAACRAwAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CmluIGZsb2F0NCB2aW5DaXJjbGVFZGdlX1MwOwppbiBoYWxmNCB2aW5Db2xvcl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgQ2lyY3VsYXJSUmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgZHh5MCA9IHVpbm5lclJlY3RfUzEuTFQgLSBza19GcmFnQ29vcmQueHk7CglmbG9hdDIgZHh5MSA9IHNrX0ZyYWdDb29yZC54eSAtIHVpbm5lclJlY3RfUzEuUkI7CglmbG9hdDIgZHh5ID0gbWF4KG1heChkeHkwLCBkeHkxKSwgMC4wKTsKCWhhbGYgYWxwaGEgPSBoYWxmKHNhdHVyYXRlKHVyYWRpdXNQbHVzSGFsZl9TMS54IC0gbGVuZ3RoKGR4eSkpKTsKCXJldHVybiBfaW5wdXQgKiBhbHBoYTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgQ2lyY2xlR2VvbWV0cnlQcm9jZXNzb3IKCWZsb2F0NCBjaXJjbGVFZGdlOwoJY2lyY2xlRWRnZSA9IHZpbkNpcmNsZUVkZ2VfUzA7CgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmluQ29sb3JfUzA7CglmbG9hdCBkID0gbGVuZ3RoKGNpcmNsZUVkZ2UueHkpOwoJaGFsZiBkaXN0YW5jZVRvT3V0ZXJFZGdlID0gaGFsZihjaXJjbGVFZGdlLnogKiAoMS4wIC0gZCkpOwoJaGFsZiBlZGdlQWxwaGEgPSBzYXR1cmF0ZShkaXN0YW5jZVRvT3V0ZXJFZGdlKTsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoZWRnZUFscGhhKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IENpcmN1bGFyUlJlY3RfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IADAAAAGluQ2lyY2xlRWRnZQEAAAAAAAAA","CIAAAAAAQAARQAAYQAAAAGFYQAABRAAAAEEAAAAAAARAEAEABYAAAAEAAAAAAAEEBQAAAAA":"CAAAAExTS1NVAwAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQyIHVhdGxhc19hZGp1c3RfUzA7CmluIGZsb2F0NCBmaWxsQm91bmRzOwppbiBoYWxmNCBjb2xvcjsKaW4gZmxvYXQ0IGxvY2F0aW9uczsKb3V0IGZsb2F0MiB2YXRsYXNDb29yZF9TMDsKZmxhdCBvdXQgaGFsZjQgdmNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBEcmF3QXRsYXNQYXRoU2hhZGVyCglmbG9hdDIgdW5pdENvb3JkID0gZmxvYXQyKHNrX1ZlcnRleElEICYgMSwgc2tfVmVydGV4SUQgPj4gMSk7CglmbG9hdDIgZGV2Q29vcmQgPSBtaXgoZmlsbEJvdW5kcy54eSwgZmlsbEJvdW5kcy56dywgdW5pdENvb3JkKTsKCS8vIEEgbmVnYXRpdmUgeCBjb29yZGluYXRlIGluIHRoZSBhdGxhcyBpbmRpY2F0ZXMgdGhhdCB0aGUgcGF0aCBpcyB0cmFuc3Bvc2VkLgoJLy8gV2UgYWxzbyBhZGRlZCAxIHNpbmNlIHdlIGNhbid0IG5lZ2F0ZSB6ZXJvLgoJZmxvYXQyIGF0bGFzVG9wTGVmdCA9IGZsb2F0MihhYnMobG9jYXRpb25zLngpIC0gMSwgbG9jYXRpb25zLnkpOwoJZmxvYXQyIGRldlRvcExlZnQgPSBsb2NhdGlvbnMuenc7Cglib29sIHRyYW5zcG9zZWQgPSBsb2NhdGlvbnMueCA8IDA7CglmbG9hdDIgYXRsYXNDb29yZCA9IGRldkNvb3JkIC0gZGV2VG9wTGVmdDsKCWlmICh0cmFuc3Bvc2VkKSAKCXsKCQlhdGxhc0Nvb3JkID0gYXRsYXNDb29yZC55eDsKCX0KCWF0bGFzQ29vcmQgKz0gYXRsYXNUb3BMZWZ0OwoJdmF0bGFzQ29vcmRfUzAgPSBhdGxhc0Nvb3JkICogdWF0bGFzX2FkanVzdF9TMDsKCXZjb2xvcl9TMCA9IGNvbG9yOwoJc2tfUG9zaXRpb24gPSBkZXZDb29yZC54eTAxOwp9CgAAAAAAAADKAQAAdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzA7CmluIGZsb2F0MiB2YXRsYXNDb29yZF9TMDsKZmxhdCBpbiBoYWxmNCB2Y29sb3JfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBEcmF3QXRsYXNQYXRoU2hhZGVyCgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KDEpOwoJaGFsZiBhdGxhc0NvdmVyYWdlID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MwLCB2YXRsYXNDb29yZF9TMCkuMDAwci5hOwoJb3V0cHV0Q292ZXJhZ2VfUzAgKj0gYXRsYXNDb3ZlcmFnZTsKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2Y29sb3JfUzA7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAZmlsbEJvdW5kcwAABQAAAGNvbG9yAAAACQAAAGxvY2F0aW9ucwAAAAEAAAAAAAAA","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAADUAANAAAAAAIBAIAAAABLCIIBAAAAABAEGABBAMAACAIAAAAAAAB2AAAAAAACAAAAAEBSAAAAA":"CAAAAExTS1M8AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMDsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfM19TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzNfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxX2MwKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAADhAwAAdW5pZm9ybSBmbG9hdDQgdWNsYW1wX1MxX2MwX2MwOwp1bmlmb3JtIGZsb2F0M3gzIHVtYXRyaXhfUzFfYzA7CnVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1MxOwppbiBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzNfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IFRleHR1cmVFZmZlY3RfUzFfYzBfYzAoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGluQ29vcmQgPSB2VHJhbnNmb3JtZWRDb29yZHNfM19TMDsKCWZsb2F0MiBzdWJzZXRDb29yZDsKCXN1YnNldENvb3JkLnggPSBpbkNvb3JkLng7CglzdWJzZXRDb29yZC55ID0gaW5Db29yZC55OwoJZmxvYXQyIGNsYW1wZWRDb29yZDsKCWNsYW1wZWRDb29yZCA9IGNsYW1wKHN1YnNldENvb3JkLCB1Y2xhbXBfUzFfYzBfYzAueHksIHVjbGFtcF9TMV9jMF9jMC56dyk7CgloYWxmNCB0ZXh0dXJlQ29sb3IgPSBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfUzEsIGNsYW1wZWRDb29yZCk7CglyZXR1cm4gdGV4dHVyZUNvbG9yOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TMV9jMChoYWxmNCBfaW5wdXQpIAp7CglyZXR1cm4gVGV4dHVyZUVmZmVjdF9TMV9jMF9jMChfaW5wdXQpOwp9CmhhbGY0IEJsZW5kX1MxKGhhbGY0IF9zcmMsIGhhbGY0IF9kc3QpIAp7CglyZXR1cm4gYmxlbmRfbW9kdWxhdGUoTWF0cml4RWZmZWN0X1MxX2MwKF9zcmMpLCBfc3JjKTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMCA9IGhhbGY0KDEpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IEJsZW5kX1MxKG91dHB1dENvbG9yX1MwLCBoYWxmNCgxKSk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0X1MxICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","HVIAAAAAABIAAGAAAQ4AAAH477776R24EAAAIOBQAD6P7777777777YDAAAAAAAAAAAFIBTWV34ISAIAAAAEYT7ZOFIQAAAADAAAAABQAAAAAIFGB7HB2BAAAAAFQRH6PYAAAAEAAAAAAAAZGE66LR2FAEAAAYAAAAAMAAAAACAJQPRYO4IAAAAAMAI7T6YBAAAAABAAAAAGIMFGB7HB2BAAAAAAAAAAQAKYCRPE54DQAAAABAAAAAEQEFMEJ7T6AAAAAAAAAAAAAHIAAAAAAAIAAAAAQGIA":"CAAAAExTS1OAAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMF9jMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBoYWxmNCBjb2xvcjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7CmZsYXQgb3V0IGhhbGY0IHZjb2xvcl9TMDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfNV9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgl2Y29sb3JfUzAgPSBjb2xvcjsKCXNrX1Bvc2l0aW9uID0gcG9zaXRpb24ueHkwMTsKCXsKCQl2VHJhbnNmb3JtZWRDb29yZHNfNV9TMCA9IGZsb2F0M3gyKHVtYXRyaXhfUzFfYzBfYzEpICogbG9jYWxDb29yZC54eTE7Cgl9Cn0KAAAAAOMGAAB1bmlmb3JtIGhhbGY0IHVzdGFydF9TMV9jMF9jMDsKdW5pZm9ybSBoYWxmNCB1ZW5kX1MxX2MwX2MwOwp1bmlmb3JtIGZsb2F0M3gzIHVtYXRyaXhfUzFfYzBfYzE7CnVuaWZvcm0gaGFsZjQgdWxlZnRCb3JkZXJDb2xvcl9TMV9jMDsKdW5pZm9ybSBoYWxmNCB1cmlnaHRCb3JkZXJDb2xvcl9TMV9jMDsKZmxhdCBpbiBoYWxmNCB2Y29sb3JfUzA7CmluIGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfNV9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgU2luZ2xlSW50ZXJ2YWxDb2xvcml6ZXJfUzFfYzBfYzAoaGFsZjQgX2lucHV0LCBmbG9hdDIgX2Nvb3JkcykgCnsKCWhhbGY0IF90bXBfMF9pbkNvbG9yID0gX2lucHV0OwoJZmxvYXQyIF90bXBfMV9jb29yZHMgPSBfY29vcmRzOwoJcmV0dXJuIGhhbGY0KG1peCh1c3RhcnRfUzFfYzBfYzAsIHVlbmRfUzFfYzBfYzAsIGhhbGYoX3RtcF8xX2Nvb3Jkcy54KSkpOwp9CmhhbGY0IExpbmVhckxheW91dF9TMV9jMF9jMV9jMChoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBfdG1wXzJfaW5Db2xvciA9IF9pbnB1dDsKCWZsb2F0MiBfdG1wXzNfY29vcmRzID0gdlRyYW5zZm9ybWVkQ29vcmRzXzVfUzA7CglyZXR1cm4gaGFsZjQoaGFsZjQoaGFsZihfdG1wXzNfY29vcmRzLngpICsgOS45OTk5OTk3NDczNzg3NTE2ZS0wNiwgMS4wLCAwLjAsIDAuMCkpOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TMV9jMF9jMShoYWxmNCBfaW5wdXQpIAp7CglyZXR1cm4gTGluZWFyTGF5b3V0X1MxX2MwX2MxX2MwKF9pbnB1dCk7Cn0KaGFsZjQgQ2xhbXBlZEdyYWRpZW50X1MxX2MwKGhhbGY0IF9pbnB1dCkgCnsKCWhhbGY0IF90bXBfNF9pbkNvbG9yID0gX2lucHV0OwoJaGFsZjQgdCA9IE1hdHJpeEVmZmVjdF9TMV9jMF9jMShfdG1wXzRfaW5Db2xvcik7CgloYWxmNCBvdXRDb2xvcjsKCWlmICghYm9vbChpbnQoMSkpICYmIHQueSA8IDAuMCkgCgl7CgkJb3V0Q29sb3IgPSBoYWxmNCgwLjApOwoJfQoJZWxzZSBpZiAodC54IDwgMC4wKSAKCXsKCQlvdXRDb2xvciA9IHVsZWZ0Qm9yZGVyQ29sb3JfUzFfYzA7Cgl9CgllbHNlIGlmICh0LnggPiAxLjApIAoJewoJCW91dENvbG9yID0gdXJpZ2h0Qm9yZGVyQ29sb3JfUzFfYzA7Cgl9CgllbHNlIAoJewoJCW91dENvbG9yID0gU2luZ2xlSW50ZXJ2YWxDb2xvcml6ZXJfUzFfYzBfYzAoX3RtcF80X2luQ29sb3IsIGZsb2F0MihoYWxmMih0LngsIDAuMCkpKTsKCX0KCWlmIChib29sKGludCgxKSkpIAoJewoJCW91dENvbG9yLnh5eiAqPSBvdXRDb2xvci53OwoJfQoJcmV0dXJuIGhhbGY0KG91dENvbG9yKTsKfQpoYWxmNCBEaXNhYmxlQ292ZXJhZ2VBc0FscGhhX1MxKGhhbGY0IF9pbnB1dCkgCnsKCV9pbnB1dCA9IENsYW1wZWRHcmFkaWVudF9TMV9jMChfaW5wdXQpOwoJaGFsZjQgX3RtcF81X2luQ29sb3IgPSBfaW5wdXQ7CglyZXR1cm4gaGFsZjQoX2lucHV0KTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IERpc2FibGVDb3ZlcmFnZUFzQWxwaGFfUzEob3V0cHV0Q29sb3JfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dF9TMSAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAADAAAACAAAAHBvc2l0aW9uBQAAAGNvbG9yAAAACgAAAGxvY2FsQ29vcmQAAAEAAAAAAAAA","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAADUAANAAAAAAAAAIAAAABLAIABAAAAABAEGABBAMAAAAAAAAAAAAB2AAAAAAACAAAAAEBSAAAAA":"CAAAAExTS1M8AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMDsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfM19TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzNfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxX2MwKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAADOAgAAdW5pZm9ybSBmbG9hdDN4MyB1bWF0cml4X1MxX2MwOwp1bmlmb3JtIHNhbXBsZXIyRCB1VGV4dHVyZVNhbXBsZXJfMF9TMTsKaW4gZmxvYXQyIHZUcmFuc2Zvcm1lZENvb3Jkc18zX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBUZXh0dXJlRWZmZWN0X1MxX2MwX2MwKGhhbGY0IF9pbnB1dCkgCnsKCXJldHVybiBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfUzEsIHZUcmFuc2Zvcm1lZENvb3Jkc18zX1MwKTsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfUzFfYzAoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIFRleHR1cmVFZmZlY3RfUzFfYzBfYzAoX2lucHV0KTsKfQpoYWxmNCBCbGVuZF9TMShoYWxmNCBfc3JjLCBoYWxmNCBfZHN0KSAKewoJcmV0dXJuIGJsZW5kX21vZHVsYXRlKE1hdHJpeEVmZmVjdF9TMV9jMChfc3JjKSwgX3NyYyk7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzAgPSBoYWxmNCgxKTsKCWNvbnN0IGhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoMSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBCbGVuZF9TMShvdXRwdXRDb2xvcl9TMCwgaGFsZjQoMSkpOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dF9TMSAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAALUAQMAAAAAAIAAEAAAABJYQAAAAAQAAIAAAAAWCBACAABAAAAANAECAZAAEAAAAAAAAFAAMAAAABAAAAAAABBAM":"CAAAAExTS1M2AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAAAAADEGAAB1bmlmb3JtIGZsb2F0NCB1Y2xhbXBfUzFfYzBfYzBfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMF9jMDsKdW5pZm9ybSBoYWxmMiB1XzBfSW5jcmVtZW50X1MxX2MwOwp1bmlmb3JtIGhhbGY0IHVfMV9LZXJuZWxfUzFfYzBbNF07CnVuaWZvcm0gaGFsZjQgdV8yX09mZnNldHNfUzFfYzBbNF07CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzE7CmluIGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgVGV4dHVyZUVmZmVjdF9TMV9jMF9jMF9jMChoYWxmNCBfaW5wdXQsIGZsb2F0MiBfY29vcmRzKSAKewoJZmxvYXQyIGluQ29vcmQgPSBfY29vcmRzOwoJZmxvYXQyIHN1YnNldENvb3JkOwoJc3Vic2V0Q29vcmQueCA9IGluQ29vcmQueDsKCXN1YnNldENvb3JkLnkgPSBpbkNvb3JkLnk7CglmbG9hdDIgY2xhbXBlZENvb3JkOwoJY2xhbXBlZENvb3JkLnggPSBjbGFtcChzdWJzZXRDb29yZC54LCB1Y2xhbXBfUzFfYzBfYzBfYzAueCwgdWNsYW1wX1MxX2MwX2MwX2MwLnopOwoJY2xhbXBlZENvb3JkLnkgPSBzdWJzZXRDb29yZC55OwoJaGFsZjQgdGV4dHVyZUNvbG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MxLCBjbGFtcGVkQ29vcmQpOwoJcmV0dXJuIHRleHR1cmVDb2xvcjsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfUzFfYzBfYzAoaGFsZjQgX2lucHV0LCBmbG9hdDIgX2Nvb3JkcykgCnsKCXJldHVybiBUZXh0dXJlRWZmZWN0X1MxX2MwX2MwX2MwKF9pbnB1dCwgZmxvYXQzeDIodW1hdHJpeF9TMV9jMF9jMCkgKiBfY29vcmRzLnh5MSk7Cn0KaGFsZjQgR2F1c3NpYW5Db252b2x1dGlvbl9TMV9jMChoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBfM19jb2xvciA9IGhhbGY0KDAuMCk7CglmbG9hdDIgXzVfY29vcmQgPSB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKCWZvciAoaW50IF82X2kgPSAwOyAoXzZfaSA8IDEzKTsgXzZfaSsrKSAoXzNfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TMV9jMF9jMChfaW5wdXQsIChfNV9jb29yZCArIGZsb2F0MigodV8yX09mZnNldHNfUzFfYzBbKF82X2kgLyA0KV1bKF82X2kgJiAzKV0gKiB1XzBfSW5jcmVtZW50X1MxX2MwKSkpKSAqIHVfMV9LZXJuZWxfUzFfYzBbKF82X2kgLyA0KV1bKF82X2kgJiAzKV0pKTsKCXJldHVybiBfM19jb2xvcjsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIEdhdXNzaWFuQ29udm9sdXRpb25fUzFfYzAoX2lucHV0KTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMCA9IGhhbGY0KDEpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IE1hdHJpeEVmZmVjdF9TMShvdXRwdXRDb2xvcl9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0X1MxICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","HUQAAAAAAAMAADAAAIOAAAH677776IZOCAAP577777777777777777YBAAAAAAAAAAAKAAYAAAACAAAAAAYEIBAAAA":"CAAAAExTS1PUAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7CmZsYXQgb3V0IGhhbGY0IHZjb2xvcl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgl2Y29sb3JfUzAgPSBjb2xvcjsKCXNrX1Bvc2l0aW9uID0gcG9zaXRpb24ueHkwMTsKfQoAAAAATQEAAGZsYXQgaW4gaGFsZjQgdmNvbG9yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJCXNrX0ZyYWdDb2xvciA9IHNrX0ZyYWdDb2xvci5hMDAwOwoJfQp9CgAAAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24FAAAAY29sb3IAAAABAAAAAAAAAA==","FAAQMYAAMAAAEADAAABAEYAAAICIAB5AABQAAAQAMAAAEATAAABAIIGAAEDCBYQCA4AAAAAAEAKPABAAAAAAB2AAAAAAACAAAAAEBSAAAAAAA":"CAAAAExTS1PUCwAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0NCByYWRpaV9zZWxlY3RvcjsKaW4gZmxvYXQ0IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHM7CmluIGZsb2F0NCBhYV9ibG9hdF9hbmRfY292ZXJhZ2U7CmluIGZsb2F0NCByYWRpaV94OwppbiBmbG9hdDQgcmFkaWlfeTsKaW4gZmxvYXQ0IHNrZXc7CmluIGZsb2F0MiB0cmFuc2xhdGVfYW5kX2xvY2Fscm90YXRlOwppbiBoYWxmNCBjb2xvcjsKZmxhdCBvdXQgaGFsZjQgdmNvbG9yX1MwOwpvdXQgZmxvYXQyIHZhcmNjb29yZF9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgRmlsbFJSZWN0T3A6OlByb2Nlc3NvcgoJdmNvbG9yX1MwID0gY29sb3I7CglmbG9hdCBhYV9ibG9hdF9tdWx0aXBsaWVyID0gMTsKCWZsb2F0MiBjb3JuZXIgPSBjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzLnh5OwoJZmxvYXQyIHJhZGl1c19vdXRzZXQgPSBjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzLnp3OwoJZmxvYXQyIGFhX2Jsb2F0X2RpcmVjdGlvbiA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS54eTsKCWZsb2F0IGlzX2xpbmVhcl9jb3ZlcmFnZSA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS53OwoJZmxvYXQyIHBpeGVsbGVuZ3RoID0gaW52ZXJzZXNxcnQoZmxvYXQyKGRvdChza2V3Lnh6LCBza2V3Lnh6KSwgZG90KHNrZXcueXcsIHNrZXcueXcpKSk7CglmbG9hdDQgbm9ybWFsaXplZF9heGlzX2RpcnMgPSBza2V3ICogcGl4ZWxsZW5ndGgueHl4eTsKCWZsb2F0MiBheGlzd2lkdGhzID0gKGFicyhub3JtYWxpemVkX2F4aXNfZGlycy54eSkgKyBhYnMobm9ybWFsaXplZF9heGlzX2RpcnMuencpKTsKCWZsb2F0MiBhYV9ibG9hdHJhZGl1cyA9IGF4aXN3aWR0aHMgKiBwaXhlbGxlbmd0aCAqIC41OwoJZmxvYXQ0IHJhZGlpX2FuZF9uZWlnaGJvcnMgPSByYWRpaV9zZWxlY3RvciogZmxvYXQ0eDQocmFkaWlfeCwgcmFkaWlfeSwgcmFkaWlfeC55eHd6LCByYWRpaV95Lnd6eXgpOwoJZmxvYXQyIHJhZGlpID0gcmFkaWlfYW5kX25laWdoYm9ycy54eTsKCWZsb2F0MiBuZWlnaGJvcl9yYWRpaSA9IHJhZGlpX2FuZF9uZWlnaGJvcnMuenc7CglmbG9hdCBjb3ZlcmFnZV9tdWx0aXBsaWVyID0gMTsKCWlmIChhbnkoZ3JlYXRlclRoYW4oYWFfYmxvYXRyYWRpdXMsIGZsb2F0MigxKSkpKSAKCXsKCQljb3JuZXIgPSBtYXgoYWJzKGNvcm5lciksIGFhX2Jsb2F0cmFkaXVzKSAqIHNpZ24oY29ybmVyKTsKCQljb3ZlcmFnZV9tdWx0aXBsaWVyID0gMSAvIChtYXgoYWFfYmxvYXRyYWRpdXMueCwgMSkgKiBtYXgoYWFfYmxvYXRyYWRpdXMueSwgMSkpOwoJCXJhZGlpID0gZmxvYXQyKDApOwoJfQoJZmxvYXQgY292ZXJhZ2UgPSBhYV9ibG9hdF9hbmRfY292ZXJhZ2UuejsKCWlmIChhbnkobGVzc1RoYW4ocmFkaWksIGFhX2Jsb2F0cmFkaXVzICogMS41KSkpIAoJewoJCXJhZGlpID0gZmxvYXQyKDApOwoJCWFhX2Jsb2F0X2RpcmVjdGlvbiA9IHNpZ24oY29ybmVyKTsKCQlpZiAoY292ZXJhZ2UgPiAuNSkgCgkJewoJCQlhYV9ibG9hdF9kaXJlY3Rpb24gPSAtYWFfYmxvYXRfZGlyZWN0aW9uOwoJCX0KCQlpc19saW5lYXJfY292ZXJhZ2UgPSAxOwoJfQoJZWxzZSAKCXsKCQlyYWRpaSA9IGNsYW1wKHJhZGlpLCBwaXhlbGxlbmd0aCAqIDEuNSwgMiAtIHBpeGVsbGVuZ3RoICogMS41KTsKCQluZWlnaGJvcl9yYWRpaSA9IGNsYW1wKG5laWdoYm9yX3JhZGlpLCBwaXhlbGxlbmd0aCAqIDEuNSwgMiAtIHBpeGVsbGVuZ3RoICogMS41KTsKCQlmbG9hdDIgc3BhY2luZyA9IDIgLSByYWRpaSAtIG5laWdoYm9yX3JhZGlpOwoJCWZsb2F0MiBleHRyYV9wYWQgPSBtYXgocGl4ZWxsZW5ndGggKiAuMDYyNSAtIHNwYWNpbmcsIGZsb2F0MigwKSk7CgkJcmFkaWkgLT0gZXh0cmFfcGFkICogLjU7Cgl9CglmbG9hdDIgYWFfb3V0c2V0ID0gYWFfYmxvYXRfZGlyZWN0aW9uICogYWFfYmxvYXRyYWRpdXMgKiBhYV9ibG9hdF9tdWx0aXBsaWVyOwoJZmxvYXQyIHZlcnRleHBvcyA9IGNvcm5lciArIHJhZGl1c19vdXRzZXQgKiByYWRpaSArIGFhX291dHNldDsKCWlmIChjb3ZlcmFnZSA+IC41KSAKCXsKCQlpZiAoYWFfYmxvYXRfZGlyZWN0aW9uLnggIT0gMCAmJiB2ZXJ0ZXhwb3MueCAqIGNvcm5lci54IDwgMCkgCgkJewoJCQlmbG9hdCBiYWNrc2V0ID0gYWJzKHZlcnRleHBvcy54KTsKCQkJdmVydGV4cG9zLnggPSAwOwoJCQl2ZXJ0ZXhwb3MueSArPSBiYWNrc2V0ICogc2lnbihjb3JuZXIueSkgKiBwaXhlbGxlbmd0aC55L3BpeGVsbGVuZ3RoLng7CgkJCWNvdmVyYWdlID0gKGNvdmVyYWdlIC0gLjUpICogYWJzKGNvcm5lci54KSAvIChhYnMoY29ybmVyLngpICsgYmFja3NldCkgKyAuNTsKCQl9CgkJaWYgKGFhX2Jsb2F0X2RpcmVjdGlvbi55ICE9IDAgJiYgdmVydGV4cG9zLnkgKiBjb3JuZXIueSA8IDApIAoJCXsKCQkJZmxvYXQgYmFja3NldCA9IGFicyh2ZXJ0ZXhwb3MueSk7CgkJCXZlcnRleHBvcy55ID0gMDsKCQkJdmVydGV4cG9zLnggKz0gYmFja3NldCAqIHNpZ24oY29ybmVyLngpICogcGl4ZWxsZW5ndGgueC9waXhlbGxlbmd0aC55OwoJCQljb3ZlcmFnZSA9IChjb3ZlcmFnZSAtIC41KSAqIGFicyhjb3JuZXIueSkgLyAoYWJzKGNvcm5lci55KSArIGJhY2tzZXQpICsgLjU7CgkJfQoJfQoJZmxvYXQyeDIgc2tld21hdHJpeCA9IGZsb2F0MngyKHNrZXcueHksIHNrZXcuencpOwoJZmxvYXQyIGRldmNvb3JkID0gdmVydGV4cG9zICogc2tld21hdHJpeCArIHRyYW5zbGF0ZV9hbmRfbG9jYWxyb3RhdGUueHk7CglpZiAoMCAhPSBpc19saW5lYXJfY292ZXJhZ2UpIAoJewoJCXZhcmNjb29yZF9TMC54eSA9IGZsb2F0MigwLCBjb3ZlcmFnZSAqIGNvdmVyYWdlX211bHRpcGxpZXIpOwoJfQoJZWxzZSAKCXsKCQlmbG9hdDIgYXJjY29vcmQgPSAxIC0gYWJzKHJhZGl1c19vdXRzZXQpICsgYWFfb3V0c2V0L3JhZGlpICogY29ybmVyOwoJCXZhcmNjb29yZF9TMC54eSA9IGZsb2F0MihhcmNjb29yZC54KzEsIGFyY2Nvb3JkLnkpOwoJfQoJc2tfUG9zaXRpb24gPSBkZXZjb29yZC54eTAxOwp9CgEAAACzBAAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBmbG9hdDIgdWludlJhZGlpWFlfUzE7CmZsYXQgaW4gaGFsZjQgdmNvbG9yX1MwOwppbiBmbG9hdDIgdmFyY2Nvb3JkX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBFbGxpcHRpY2FsUlJlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGR4eTAgPSB1aW5uZXJSZWN0X1MxLkxUIC0gc2tfRnJhZ0Nvb3JkLnh5OwoJZmxvYXQyIGR4eTEgPSBza19GcmFnQ29vcmQueHkgLSB1aW5uZXJSZWN0X1MxLlJCOwoJZmxvYXQyIGR4eSA9IG1heChtYXgoZHh5MCwgZHh5MSksIDAuMCk7CglmbG9hdDIgWiA9IGR4eSAqIHVpbnZSYWRpaVhZX1MxLnh5OwoJaGFsZiBpbXBsaWNpdCA9IGhhbGYoZG90KFosIGR4eSkgLSAxLjApOwoJaGFsZiBncmFkX2RvdCA9IGhhbGYoNC4wICogZG90KFosIFopKTsKCWdyYWRfZG90ID0gbWF4KGdyYWRfZG90LCAxLjBlLTQpOwoJaGFsZiBhcHByb3hfZGlzdCA9IGltcGxpY2l0ICogaGFsZihpbnZlcnNlc3FydChncmFkX2RvdCkpOwoJaGFsZiBhbHBoYSA9IGNsYW1wKDAuNSArIGFwcHJveF9kaXN0LCAwLjAsIDEuMCk7CglyZXR1cm4gX2lucHV0ICogYWxwaGE7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIEZpbGxSUmVjdE9wOjpQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2Y29sb3JfUzA7CglmbG9hdCB4X3BsdXNfMT12YXJjY29vcmRfUzAueCwgeT12YXJjY29vcmRfUzAueTsKCWhhbGYgY292ZXJhZ2U7CglpZiAoMCA9PSB4X3BsdXNfMSkgCgl7CgkJY292ZXJhZ2UgPSBoYWxmKHkpOwoJfQoJZWxzZSAKCXsKCQlmbG9hdCBmbiA9IHhfcGx1c18xICogKHhfcGx1c18xIC0gMik7CgkJZm4gPSBmbWEoeSx5LCBmbik7CgkJZmxvYXQgZm53aWR0aCA9IGZ3aWR0aChmbik7CgkJY292ZXJhZ2UgPSAuNSAtIGhhbGYoZm4vZm53aWR0aCk7CgkJY292ZXJhZ2UgPSBjbGFtcChjb3ZlcmFnZSwgMCwgMSk7Cgl9CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KGNvdmVyYWdlKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IEVsbGlwdGljYWxSUmVjdF9TMShvdXRwdXRDb3ZlcmFnZV9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRfUzE7Cgl9Cn0KAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAgAAAAOAAAAcmFkaWlfc2VsZWN0b3IAABkAAABjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzAAAAFQAAAGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZQAAAAcAAAByYWRpaV94AAcAAAByYWRpaV95AAQAAABza2V3GQAAAHRyYW5zbGF0ZV9hbmRfbG9jYWxyb3RhdGUAAAAFAAAAY29sb3IAAAABAAAAAAAAAA==","HUJAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAQAAAAAAQQGAARAGQWMHGBRIAAAAABQAAAAAAAAAAHIAAAAAAAIAAAAAQGIAA":"CAAAAExTS1PlAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmxvY2FsQ29vcmRfUzAgPSBsb2NhbENvb3JkOwoJc2tfUG9zaXRpb24gPSBwb3NpdGlvbi54eTAxOwp9CgAAAAEAAABhBAAAY29uc3QgaW50IGtGaWxsQUFfUzEgPSAxOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQldfUzEgPSAyOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQUFfUzEgPSAzOwp1bmlmb3JtIGZsb2F0NCB1Y2lyY2xlX1MxOwp1bmlmb3JtIHNhbXBsZXIyRCB1VGV4dHVyZVNhbXBsZXJfMF9TMDsKaW4gZmxvYXQyIHZsb2NhbENvb3JkX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBDaXJjbGVfUzEoaGFsZjQgX2lucHV0KSAKewoJaGFsZjQgX3RtcF8wX2luQ29sb3IgPSBfaW5wdXQ7CgloYWxmIGQ7CglpZiAoaW50KDEpID09IGtJbnZlcnNlRmlsbEJXX1MxIHx8IGludCgxKSA9PSBrSW52ZXJzZUZpbGxBQV9TMSkgCgl7CgkJZCA9IGhhbGYoKGxlbmd0aCgodWNpcmNsZV9TMS54eSAtIHNrX0ZyYWdDb29yZC54eSkgKiB1Y2lyY2xlX1MxLncpIC0gMS4wKSAqIHVjaXJjbGVfUzEueik7Cgl9CgllbHNlIAoJewoJCWQgPSBoYWxmKCgxLjAgLSBsZW5ndGgoKHVjaXJjbGVfUzEueHkgLSBza19GcmFnQ29vcmQueHkpICogdWNpcmNsZV9TMS53KSkgKiB1Y2lyY2xlX1MxLnopOwoJfQoJaWYgKGludCgxKSA9PSBrRmlsbEFBX1MxIHx8IGludCgxKSA9PSBrSW52ZXJzZUZpbGxBQV9TMSkgCgl7CgkJcmV0dXJuIGhhbGY0KF9pbnB1dCAqIHNhdHVyYXRlKGQpKTsKCX0KCWVsc2UgCgl7CgkJcmV0dXJuIGhhbGY0KGQgPiAwLjUgPyBfaW5wdXQgOiBoYWxmNCgwLjApKTsKCX0KfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMCA9IGhhbGY0KDEpOwoJZmxvYXQyIHRleENvb3JkOwoJdGV4Q29vcmQgPSB2bG9jYWxDb29yZF9TMDsKCW91dHB1dENvbG9yX1MwID0gKChzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfUzAsIHRleENvb3JkKSAqIGhhbGY0KDEpKSk7Cgljb25zdCBoYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KDEpOwoJaGFsZjQgb3V0cHV0X1MxOwoJb3V0cHV0X1MxID0gQ2lyY2xlX1MxKG91dHB1dENvdmVyYWdlX1MwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dF9TMTsKCX0KfQoAAAABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAACAAAACAAAAHBvc2l0aW9uCgAAAGxvY2FsQ29vcmQAAAEAAAAAAAAA","AYQA5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAACAFBQATAAAAAAFAAMAAAABAAAAAAABBAMAAAAA":"CAAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgEAAABYBAAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBmbG9hdDIgdWludlJhZGlpWFlfUzE7CmluIGZsb2F0NCB2aW5DaXJjbGVFZGdlX1MwOwppbiBoYWxmNCB2aW5Db2xvcl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgRWxsaXB0aWNhbFJSZWN0X1MxKGhhbGY0IF9pbnB1dCkgCnsKCWZsb2F0MiBkeHkwID0gdWlubmVyUmVjdF9TMS5MVCAtIHNrX0ZyYWdDb29yZC54eTsKCWZsb2F0MiBkeHkxID0gc2tfRnJhZ0Nvb3JkLnh5IC0gdWlubmVyUmVjdF9TMS5SQjsKCWZsb2F0MiBkeHkgPSBtYXgobWF4KGR4eTAsIGR4eTEpLCAwLjApOwoJZmxvYXQyIFogPSBkeHkgKiB1aW52UmFkaWlYWV9TMS54eTsKCWhhbGYgaW1wbGljaXQgPSBoYWxmKGRvdChaLCBkeHkpIC0gMS4wKTsKCWhhbGYgZ3JhZF9kb3QgPSBoYWxmKDQuMCAqIGRvdChaLCBaKSk7CglncmFkX2RvdCA9IG1heChncmFkX2RvdCwgMS4wZS00KTsKCWhhbGYgYXBwcm94X2Rpc3QgPSBpbXBsaWNpdCAqIGhhbGYoaW52ZXJzZXNxcnQoZ3JhZF9kb3QpKTsKCWhhbGYgYWxwaGEgPSBjbGFtcCgwLjUgKyBhcHByb3hfZGlzdCwgMC4wLCAxLjApOwoJcmV0dXJuIF9pbnB1dCAqIGFscGhhOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQ0IGNpcmNsZUVkZ2U7CgljaXJjbGVFZGdlID0gdmluQ2lyY2xlRWRnZV9TMDsKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2aW5Db2xvcl9TMDsKCWZsb2F0IGQgPSBsZW5ndGgoY2lyY2xlRWRnZS54eSk7CgloYWxmIGRpc3RhbmNlVG9PdXRlckVkZ2UgPSBoYWxmKGNpcmNsZUVkZ2UueiAqICgxLjAgLSBkKSk7CgloYWxmIGVkZ2VBbHBoYSA9IHNhdHVyYXRlKGRpc3RhbmNlVG9PdXRlckVkZ2UpOwoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNChlZGdlQWxwaGEpOwoJaGFsZjQgb3V0cHV0X1MxOwoJb3V0cHV0X1MxID0gRWxsaXB0aWNhbFJSZWN0X1MxKG91dHB1dENvdmVyYWdlX1MwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dF9TMTsKCX0KfQoBAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAADAAAACgAAAGluUG9zaXRpb24AAAcAAABpbkNvbG9yAAwAAABpbkNpcmNsZUVkZ2UBAAAAAAAAAA==","AYQA5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAAAAB3QA6AAAEAAAAAAAMAAPEAEAAABAAAAAAB2AAAAAAACAAAAAEBSAAAA":"CAAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgEAAAA2BQAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CnVuaWZvcm0gZmxvYXQ0IHVpbm5lclJlY3RfUzI7CnVuaWZvcm0gaGFsZjIgdXJhZGl1c1BsdXNIYWxmX1MyOwppbiBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKaW4gaGFsZjQgdmluQ29sb3JfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IENpcmN1bGFyUlJlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGR4eTAgPSB1aW5uZXJSZWN0X1MxLkxUIC0gc2tfRnJhZ0Nvb3JkLnh5OwoJZmxvYXQyIGR4eTEgPSBza19GcmFnQ29vcmQueHkgLSB1aW5uZXJSZWN0X1MxLlJCOwoJZmxvYXQyIGR4eSA9IG1heChtYXgoZHh5MCwgZHh5MSksIDAuMCk7CgloYWxmIGFscGhhID0gaGFsZihzYXR1cmF0ZSh1cmFkaXVzUGx1c0hhbGZfUzEueCAtIGxlbmd0aChkeHkpKSk7CglhbHBoYSA9IDEuMCAtIGFscGhhOwoJcmV0dXJuIF9pbnB1dCAqIGFscGhhOwp9CmhhbGY0IENpcmN1bGFyUlJlY3RfUzIoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGR4eTAgPSB1aW5uZXJSZWN0X1MyLkxUIC0gc2tfRnJhZ0Nvb3JkLnh5OwoJZmxvYXQyIGR4eTEgPSBza19GcmFnQ29vcmQueHkgLSB1aW5uZXJSZWN0X1MyLlJCOwoJZmxvYXQyIGR4eSA9IG1heChtYXgoZHh5MCwgZHh5MSksIDAuMCk7CgloYWxmIGFscGhhID0gaGFsZihzYXR1cmF0ZSh1cmFkaXVzUGx1c0hhbGZfUzIueCAtIGxlbmd0aChkeHkpKSk7CglyZXR1cm4gX2lucHV0ICogYWxwaGE7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIENpcmNsZUdlb21ldHJ5UHJvY2Vzc29yCglmbG9hdDQgY2lyY2xlRWRnZTsKCWNpcmNsZUVkZ2UgPSB2aW5DaXJjbGVFZGdlX1MwOwoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZpbkNvbG9yX1MwOwoJZmxvYXQgZCA9IGxlbmd0aChjaXJjbGVFZGdlLnh5KTsKCWhhbGYgZGlzdGFuY2VUb091dGVyRWRnZSA9IGhhbGYoY2lyY2xlRWRnZS56ICogKDEuMCAtIGQpKTsKCWhhbGYgZWRnZUFscGhhID0gc2F0dXJhdGUoZGlzdGFuY2VUb091dGVyRWRnZSk7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KGVkZ2VBbHBoYSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBDaXJjdWxhclJSZWN0X1MxKG91dHB1dENvdmVyYWdlX1MwKTsKCWhhbGY0IG91dHB1dF9TMjsKCW91dHB1dF9TMiA9IENpcmN1bGFyUlJlY3RfUzIob3V0cHV0X1MxKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dF9TMjsKCX0KfQoAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IADAAAAGluQ2lyY2xlRWRnZQEAAAAAAAAA","GEMAAAYAAEHAAAARC4EAAAQWBQAAAAAAAAAQAAAAIBCAAAGQAEAAAAAQAAAABAEQAEAAAAA":"CAAAAExTS1NUAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiBoYWxmMyBpblNoYWRvd1BhcmFtczsKb3V0IGhhbGYzIHZpblNoYWRvd1BhcmFtc19TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBSUmVjdFNoYWRvdwoJdmluU2hhZG93UGFyYW1zX1MwID0gaW5TaGFkb3dQYXJhbXM7Cgl2aW5Db2xvcl9TMCA9IGluQ29sb3I7CglmbG9hdDIgX3RtcF8wX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgAAAAAjAgAAdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzA7CmluIGhhbGYzIHZpblNoYWRvd1BhcmFtc19TMDsKaW4gaGFsZjQgdmluQ29sb3JfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBSUmVjdFNoYWRvdwoJaGFsZjMgc2hhZG93UGFyYW1zOwoJc2hhZG93UGFyYW1zID0gdmluU2hhZG93UGFyYW1zX1MwOwoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZpbkNvbG9yX1MwOwoJaGFsZiBkID0gbGVuZ3RoKHNoYWRvd1BhcmFtcy54eSk7CglmbG9hdDIgdXYgPSBmbG9hdDIoc2hhZG93UGFyYW1zLnogKiAoMS4wIC0gZCksIDAuNSk7CgloYWxmIGZhY3RvciA9IHNhbXBsZSh1VGV4dHVyZVNhbXBsZXJfMF9TMCwgdXYpLjAwMHIuYTsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoZmFjdG9yKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAADAAAACgAAAGluUG9zaXRpb24AAAcAAABpbkNvbG9yAA4AAABpblNoYWRvd1BhcmFtcwAAAQAAAAAAAAA=","AYQA5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAAAAOQAAAAAAAQAAAABAMQAAAAA":"CAAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgAAAAACAgAAaW4gZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfUzA7CmluIGhhbGY0IHZpbkNvbG9yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgQ2lyY2xlR2VvbWV0cnlQcm9jZXNzb3IKCWZsb2F0NCBjaXJjbGVFZGdlOwoJY2lyY2xlRWRnZSA9IHZpbkNpcmNsZUVkZ2VfUzA7CgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmluQ29sb3JfUzA7CglmbG9hdCBkID0gbGVuZ3RoKGNpcmNsZUVkZ2UueHkpOwoJaGFsZiBkaXN0YW5jZVRvT3V0ZXJFZGdlID0gaGFsZihjaXJjbGVFZGdlLnogKiAoMS4wIC0gZCkpOwoJaGFsZiBlZGdlQWxwaGEgPSBzYXR1cmF0ZShkaXN0YW5jZVRvT3V0ZXJFZGdlKTsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoZWRnZUFscGhhKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAwAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAMAAAAaW5DaXJjbGVFZGdlAQAAAAAAAAA=","HUQAAAAAAAMAADAAAIOAAAH677776IZOCAAP577777777777777777YBAAAAAAAAAAAHEADZAAAAAAIAAAAAAOQAAAAAAAQAAAABAMQAAAAAA":"CAAAAExTS1PUAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7CmZsYXQgb3V0IGhhbGY0IHZjb2xvcl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgl2Y29sb3JfUzAgPSBjb2xvcjsKCXNrX1Bvc2l0aW9uID0gcG9zaXRpb24ueHkwMTsKfQoBAAAAuAIAAHVuaWZvcm0gZmxvYXQ0IHVpbm5lclJlY3RfUzE7CnVuaWZvcm0gaGFsZjIgdXJhZGl1c1BsdXNIYWxmX1MxOwpmbGF0IGluIGhhbGY0IHZjb2xvcl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgQ2lyY3VsYXJSUmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgZHh5MCA9IHVpbm5lclJlY3RfUzEuTFQgLSBza19GcmFnQ29vcmQueHk7CglmbG9hdDIgZHh5MSA9IHNrX0ZyYWdDb29yZC54eSAtIHVpbm5lclJlY3RfUzEuUkI7CglmbG9hdDIgZHh5ID0gbWF4KG1heChkeHkwLCBkeHkxKSwgMC4wKTsKCWhhbGYgYWxwaGEgPSBoYWxmKHNhdHVyYXRlKHVyYWRpdXNQbHVzSGFsZl9TMS54IC0gbGVuZ3RoKGR4eSkpKTsKCXJldHVybiBfaW5wdXQgKiBhbHBoYTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IENpcmN1bGFyUlJlY3RfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24FAAAAY29sb3IAAAABAAAAAAAAAA==","HVIAAAAAABIAAGAAAQ4AAAH477776R24EAAAIOBQAD6P7777777777YDAAAAAAAAAAAFIBTWV34ISAIAAAAEYT7ZOFIQAAAABAAAAABQAAAAAIFGB7HB2BAAAAAFQRH6PYAAAAEAAAAAAAAZGE66LR2FAEAAAIAAAAAMAAAAACAJQPRYO4IAAAAAMAI7T6YBAAAAABAAAAAGIMFGB7HB2BAAAAAAAAAAQAKYCRPE54DQAAAABAAAAAEQEFMEJ7T6AAAAAAAAAAAAAHIAAAAAAAIAAAAAQGIA":"CAAAAExTS1OAAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMF9jMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBoYWxmNCBjb2xvcjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7CmZsYXQgb3V0IGhhbGY0IHZjb2xvcl9TMDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfNV9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgl2Y29sb3JfUzAgPSBjb2xvcjsKCXNrX1Bvc2l0aW9uID0gcG9zaXRpb24ueHkwMTsKCXsKCQl2VHJhbnNmb3JtZWRDb29yZHNfNV9TMCA9IGZsb2F0M3gyKHVtYXRyaXhfUzFfYzBfYzEpICogbG9jYWxDb29yZC54eTE7Cgl9Cn0KAAAAAOMGAAB1bmlmb3JtIGhhbGY0IHVzdGFydF9TMV9jMF9jMDsKdW5pZm9ybSBoYWxmNCB1ZW5kX1MxX2MwX2MwOwp1bmlmb3JtIGZsb2F0M3gzIHVtYXRyaXhfUzFfYzBfYzE7CnVuaWZvcm0gaGFsZjQgdWxlZnRCb3JkZXJDb2xvcl9TMV9jMDsKdW5pZm9ybSBoYWxmNCB1cmlnaHRCb3JkZXJDb2xvcl9TMV9jMDsKZmxhdCBpbiBoYWxmNCB2Y29sb3JfUzA7CmluIGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfNV9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgU2luZ2xlSW50ZXJ2YWxDb2xvcml6ZXJfUzFfYzBfYzAoaGFsZjQgX2lucHV0LCBmbG9hdDIgX2Nvb3JkcykgCnsKCWhhbGY0IF90bXBfMF9pbkNvbG9yID0gX2lucHV0OwoJZmxvYXQyIF90bXBfMV9jb29yZHMgPSBfY29vcmRzOwoJcmV0dXJuIGhhbGY0KG1peCh1c3RhcnRfUzFfYzBfYzAsIHVlbmRfUzFfYzBfYzAsIGhhbGYoX3RtcF8xX2Nvb3Jkcy54KSkpOwp9CmhhbGY0IExpbmVhckxheW91dF9TMV9jMF9jMV9jMChoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBfdG1wXzJfaW5Db2xvciA9IF9pbnB1dDsKCWZsb2F0MiBfdG1wXzNfY29vcmRzID0gdlRyYW5zZm9ybWVkQ29vcmRzXzVfUzA7CglyZXR1cm4gaGFsZjQoaGFsZjQoaGFsZihfdG1wXzNfY29vcmRzLngpICsgOS45OTk5OTk3NDczNzg3NTE2ZS0wNiwgMS4wLCAwLjAsIDAuMCkpOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TMV9jMF9jMShoYWxmNCBfaW5wdXQpIAp7CglyZXR1cm4gTGluZWFyTGF5b3V0X1MxX2MwX2MxX2MwKF9pbnB1dCk7Cn0KaGFsZjQgQ2xhbXBlZEdyYWRpZW50X1MxX2MwKGhhbGY0IF9pbnB1dCkgCnsKCWhhbGY0IF90bXBfNF9pbkNvbG9yID0gX2lucHV0OwoJaGFsZjQgdCA9IE1hdHJpeEVmZmVjdF9TMV9jMF9jMShfdG1wXzRfaW5Db2xvcik7CgloYWxmNCBvdXRDb2xvcjsKCWlmICghYm9vbChpbnQoMSkpICYmIHQueSA8IDAuMCkgCgl7CgkJb3V0Q29sb3IgPSBoYWxmNCgwLjApOwoJfQoJZWxzZSBpZiAodC54IDwgMC4wKSAKCXsKCQlvdXRDb2xvciA9IHVsZWZ0Qm9yZGVyQ29sb3JfUzFfYzA7Cgl9CgllbHNlIGlmICh0LnggPiAxLjApIAoJewoJCW91dENvbG9yID0gdXJpZ2h0Qm9yZGVyQ29sb3JfUzFfYzA7Cgl9CgllbHNlIAoJewoJCW91dENvbG9yID0gU2luZ2xlSW50ZXJ2YWxDb2xvcml6ZXJfUzFfYzBfYzAoX3RtcF80X2luQ29sb3IsIGZsb2F0MihoYWxmMih0LngsIDAuMCkpKTsKCX0KCWlmIChib29sKGludCgwKSkpIAoJewoJCW91dENvbG9yLnh5eiAqPSBvdXRDb2xvci53OwoJfQoJcmV0dXJuIGhhbGY0KG91dENvbG9yKTsKfQpoYWxmNCBEaXNhYmxlQ292ZXJhZ2VBc0FscGhhX1MxKGhhbGY0IF9pbnB1dCkgCnsKCV9pbnB1dCA9IENsYW1wZWRHcmFkaWVudF9TMV9jMChfaW5wdXQpOwoJaGFsZjQgX3RtcF81X2luQ29sb3IgPSBfaW5wdXQ7CglyZXR1cm4gaGFsZjQoX2lucHV0KTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IERpc2FibGVDb3ZlcmFnZUFzQWxwaGFfUzEob3V0cHV0Q29sb3JfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dF9TMSAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAADAAAACAAAAHBvc2l0aW9uBQAAAGNvbG9yAAAACgAAAGxvY2FsQ29vcmQAAAEAAAAAAAAA","AYQA5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAACABZQA6AAAEAAAAAAAIADQAAAAIAAAAAAAIIDA":"CAAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgEAAACnAwAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CmluIGZsb2F0NCB2aW5DaXJjbGVFZGdlX1MwOwppbiBoYWxmNCB2aW5Db2xvcl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgQ2lyY3VsYXJSUmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgZHh5MCA9IHVpbm5lclJlY3RfUzEuTFQgLSBza19GcmFnQ29vcmQueHk7CglmbG9hdDIgZHh5MSA9IHNrX0ZyYWdDb29yZC54eSAtIHVpbm5lclJlY3RfUzEuUkI7CglmbG9hdDIgZHh5ID0gbWF4KG1heChkeHkwLCBkeHkxKSwgMC4wKTsKCWhhbGYgYWxwaGEgPSBoYWxmKHNhdHVyYXRlKHVyYWRpdXNQbHVzSGFsZl9TMS54IC0gbGVuZ3RoKGR4eSkpKTsKCWFscGhhID0gMS4wIC0gYWxwaGE7CglyZXR1cm4gX2lucHV0ICogYWxwaGE7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIENpcmNsZUdlb21ldHJ5UHJvY2Vzc29yCglmbG9hdDQgY2lyY2xlRWRnZTsKCWNpcmNsZUVkZ2UgPSB2aW5DaXJjbGVFZGdlX1MwOwoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZpbkNvbG9yX1MwOwoJZmxvYXQgZCA9IGxlbmd0aChjaXJjbGVFZGdlLnh5KTsKCWhhbGYgZGlzdGFuY2VUb091dGVyRWRnZSA9IGhhbGYoY2lyY2xlRWRnZS56ICogKDEuMCAtIGQpKTsKCWhhbGYgZWRnZUFscGhhID0gc2F0dXJhdGUoZGlzdGFuY2VUb091dGVyRWRnZSk7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KGVkZ2VBbHBoYSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBDaXJjdWxhclJSZWN0X1MxKG91dHB1dENvdmVyYWdlX1MwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dF9TMTsKCX0KfQoAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAwAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAMAAAAaW5DaXJjbGVFZGdlAQAAAAAAAAA=","AYAA5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAAAAOQAAAAAAAQAAAABAMQAAAAA":"CAAAAExTS1OCAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiBmbG9hdDQgaW5DaXJjbGVFZGdlOwpvdXQgZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfUzA7Cm91dCBoYWxmNCB2aW5Db2xvcl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgQ2lyY2xlR2VvbWV0cnlQcm9jZXNzb3IKCXZpbkNpcmNsZUVkZ2VfUzAgPSBpbkNpcmNsZUVkZ2U7Cgl2aW5Db2xvcl9TMCA9IGluQ29sb3I7CglmbG9hdDIgX3RtcF8wX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJZmxvYXQyIF90bXBfMl9pblBvc2l0aW9uID0gaW5Qb3NpdGlvbjsKCXNrX1Bvc2l0aW9uID0gX3RtcF8wX2luUG9zaXRpb24ueHkwMTsKfQoAAAAAAAACAgAAaW4gZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfUzA7CmluIGhhbGY0IHZpbkNvbG9yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgQ2lyY2xlR2VvbWV0cnlQcm9jZXNzb3IKCWZsb2F0NCBjaXJjbGVFZGdlOwoJY2lyY2xlRWRnZSA9IHZpbkNpcmNsZUVkZ2VfUzA7CgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmluQ29sb3JfUzA7CglmbG9hdCBkID0gbGVuZ3RoKGNpcmNsZUVkZ2UueHkpOwoJaGFsZiBkaXN0YW5jZVRvT3V0ZXJFZGdlID0gaGFsZihjaXJjbGVFZGdlLnogKiAoMS4wIC0gZCkpOwoJaGFsZiBlZGdlQWxwaGEgPSBzYXR1cmF0ZShkaXN0YW5jZVRvT3V0ZXJFZGdlKTsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoZWRnZUFscGhhKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAwAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAMAAAAaW5DaXJjbGVFZGdlAQAAAAAAAAA=","AYQQ5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAAAAOQAAAAAAAQAAAABAMQAAAAA":"CAAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgAAAACTAgAAaW4gZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfUzA7CmluIGhhbGY0IHZpbkNvbG9yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgQ2lyY2xlR2VvbWV0cnlQcm9jZXNzb3IKCWZsb2F0NCBjaXJjbGVFZGdlOwoJY2lyY2xlRWRnZSA9IHZpbkNpcmNsZUVkZ2VfUzA7CgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmluQ29sb3JfUzA7CglmbG9hdCBkID0gbGVuZ3RoKGNpcmNsZUVkZ2UueHkpOwoJaGFsZiBkaXN0YW5jZVRvT3V0ZXJFZGdlID0gaGFsZihjaXJjbGVFZGdlLnogKiAoMS4wIC0gZCkpOwoJaGFsZiBlZGdlQWxwaGEgPSBzYXR1cmF0ZShkaXN0YW5jZVRvT3V0ZXJFZGdlKTsKCWhhbGYgZGlzdGFuY2VUb0lubmVyRWRnZSA9IGhhbGYoY2lyY2xlRWRnZS56ICogKGQgLSBjaXJjbGVFZGdlLncpKTsKCWhhbGYgaW5uZXJBbHBoYSA9IHNhdHVyYXRlKGRpc3RhbmNlVG9Jbm5lckVkZ2UpOwoJZWRnZUFscGhhICo9IGlubmVyQWxwaGE7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KGVkZ2VBbHBoYSk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAwAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAMAAAAaW5DaXJjbGVFZGdlAQAAAAAAAAA=","HVJAAAAAABIAAGAAAQ4AAAH477776R24EAAAIOBQAD6P7777777777YDAAAAABAAAAAABBAMABAAOAAAABAAAAAAABBAMAAA":"CAAAAExTS1MjAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7CmluIGZsb2F0MiBsb2NhbENvb3JkOwpmbGF0IG91dCBoYWxmNCB2Y29sb3JfUzA7Cm91dCBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmNvbG9yX1MwID0gY29sb3I7Cgl2bG9jYWxDb29yZF9TMCA9IGxvY2FsQ29vcmQ7Cglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cn0KAAAAAADoAQAAdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzA7CmZsYXQgaW4gaGFsZjQgdmNvbG9yX1MwOwppbiBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2Y29sb3JfUzA7CglmbG9hdDIgdGV4Q29vcmQ7Cgl0ZXhDb29yZCA9IHZsb2NhbENvb3JkX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSAoKHNhbXBsZSh1VGV4dHVyZVNhbXBsZXJfMF9TMCwgdGV4Q29vcmQpICogb3V0cHV0Q29sb3JfUzApKTsKCWNvbnN0IGhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoMSk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAADAAAACAAAAHBvc2l0aW9uBQAAAGNvbG9yAAAACgAAAGxvY2FsQ29vcmQAAAEAAAAAAAAA","AYTRVAADQAAAOAEARAFQJAABBADAAAILBYAACCYUQD777777777767YAAAAAAAAAAAAOQAAAAAAAQAAAABAMQAAAAA":"CAAAAExTS1NyAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7CmluIGhhbGYzIGluQ2xpcFBsYW5lOwppbiBoYWxmMyBpbklzZWN0UGxhbmU7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGYzIHZpbkNsaXBQbGFuZV9TMDsKb3V0IGhhbGYzIHZpbklzZWN0UGxhbmVfUzA7Cm91dCBoYWxmNCB2aW5Db2xvcl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgQ2lyY2xlR2VvbWV0cnlQcm9jZXNzb3IKCXZpbkNpcmNsZUVkZ2VfUzAgPSBpbkNpcmNsZUVkZ2U7Cgl2aW5DbGlwUGxhbmVfUzAgPSBpbkNsaXBQbGFuZTsKCXZpbklzZWN0UGxhbmVfUzAgPSBpbklzZWN0UGxhbmU7Cgl2aW5Db2xvcl9TMCA9IGluQ29sb3I7CglmbG9hdDIgX3RtcF8wX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJZmxvYXQyIF90bXBfMV9pblBvc2l0aW9uID0gdWxvY2FsTWF0cml4X1MwLnh6ICogaW5Qb3NpdGlvbiArIHVsb2NhbE1hdHJpeF9TMC55dzsKCXNrX1Bvc2l0aW9uID0gX3RtcF8wX2luUG9zaXRpb24ueHkwMTsKfQoAAAAAAAD1AwAAaW4gZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfUzA7CmluIGhhbGYzIHZpbkNsaXBQbGFuZV9TMDsKaW4gaGFsZjMgdmluSXNlY3RQbGFuZV9TMDsKaW4gaGFsZjQgdmluQ29sb3JfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQ0IGNpcmNsZUVkZ2U7CgljaXJjbGVFZGdlID0gdmluQ2lyY2xlRWRnZV9TMDsKCWhhbGYzIGNsaXBQbGFuZTsKCWNsaXBQbGFuZSA9IHZpbkNsaXBQbGFuZV9TMDsKCWhhbGYzIGlzZWN0UGxhbmU7Cglpc2VjdFBsYW5lID0gdmluSXNlY3RQbGFuZV9TMDsKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2aW5Db2xvcl9TMDsKCWZsb2F0IGQgPSBsZW5ndGgoY2lyY2xlRWRnZS54eSk7CgloYWxmIGRpc3RhbmNlVG9PdXRlckVkZ2UgPSBoYWxmKGNpcmNsZUVkZ2UueiAqICgxLjAgLSBkKSk7CgloYWxmIGVkZ2VBbHBoYSA9IHNhdHVyYXRlKGRpc3RhbmNlVG9PdXRlckVkZ2UpOwoJaGFsZiBkaXN0YW5jZVRvSW5uZXJFZGdlID0gaGFsZihjaXJjbGVFZGdlLnogKiAoZCAtIGNpcmNsZUVkZ2UudykpOwoJaGFsZiBpbm5lckFscGhhID0gc2F0dXJhdGUoZGlzdGFuY2VUb0lubmVyRWRnZSk7CgllZGdlQWxwaGEgKj0gaW5uZXJBbHBoYTsKCWhhbGYgY2xpcCA9IGhhbGYoc2F0dXJhdGUoY2lyY2xlRWRnZS56ICogZG90KGNpcmNsZUVkZ2UueHksIGNsaXBQbGFuZS54eSkgKyBjbGlwUGxhbmUueikpOwoJY2xpcCAqPSBoYWxmKHNhdHVyYXRlKGNpcmNsZUVkZ2UueiAqIGRvdChjaXJjbGVFZGdlLnh5LCBpc2VjdFBsYW5lLnh5KSArIGlzZWN0UGxhbmUueikpOwoJZWRnZUFscGhhICo9IGNsaXA7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KGVkZ2VBbHBoYSk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAFAAAACgAAAGluUG9zaXRpb24AAAcAAABpbkNvbG9yAAwAAABpbkNpcmNsZUVkZ2ULAAAAaW5DbGlwUGxhbmUADAAAAGluSXNlY3RQbGFuZQEAAAAAAAAA","HUQAAAAAAAMAADAAAIOAAAH677776IZOCAAP577777777777777777YBAAAAAAAAAAACEA2X4PLOGEAAAAAAAAACAAAAAVQQAAQAAAAAQCDIBCAAAAAAAAAAAAAHIAAAAAAAIAAAAAQGIAAAAAAA":"CAAAAExTS1PUAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7CmZsYXQgb3V0IGhhbGY0IHZjb2xvcl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgl2Y29sb3JfUzAgPSBjb2xvcjsKCXNrX1Bvc2l0aW9uID0gcG9zaXRpb24ueHkwMTsKfQoBAAAACwQAAHVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMDsKdW5pZm9ybSBoYWxmNCB1Y2lyY2xlRGF0YV9TMTsKdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzE7CmZsYXQgaW4gaGFsZjQgdmNvbG9yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBUZXh0dXJlRWZmZWN0X1MxX2MwX2MwKGhhbGY0IF9pbnB1dCwgZmxvYXQyIF9jb29yZHMpIAp7CglyZXR1cm4gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MxLCBfY29vcmRzKS4wMDByOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TMV9jMChoYWxmNCBfaW5wdXQsIGZsb2F0MiBfY29vcmRzKSAKewoJcmV0dXJuIFRleHR1cmVFZmZlY3RfUzFfYzBfYzAoX2lucHV0LCBmbG9hdDN4Mih1bWF0cml4X1MxX2MwKSAqIF9jb29yZHMueHkxKTsKfQpoYWxmNCBDaXJjbGVCbHVyX1MxKGhhbGY0IF9pbnB1dCkgCnsKCWhhbGY0IF90bXBfMF9pbkNvbG9yID0gX2lucHV0OwoJaGFsZjIgdmVjID0gaGFsZjIoKHNrX0ZyYWdDb29yZC54eSAtIGZsb2F0Mih1Y2lyY2xlRGF0YV9TMS54eSkpICogZmxvYXQodWNpcmNsZURhdGFfUzEudykpOwoJaGFsZiBkaXN0ID0gbGVuZ3RoKHZlYykgKyAoMC41IC0gdWNpcmNsZURhdGFfUzEueikgKiB1Y2lyY2xlRGF0YV9TMS53OwoJcmV0dXJuIGhhbGY0KF9pbnB1dCAqIE1hdHJpeEVmZmVjdF9TMV9jMChfdG1wXzBfaW5Db2xvciwgZmxvYXQyKGhhbGYyKGRpc3QsIDAuNSkpKS53KTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IENpcmNsZUJsdXJfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAACAAAACAAAAHBvc2l0aW9uBQAAAGNvbG9yAAAAAQAAAAAAAAA=","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAALUAQMAAAAAAABAEAAAABJYQAAAAAACAIAAAAAWCBAAAIBAAAAANAECAZAAAAQAAAAAAFAAMAAAABAAAAAAABBAM":"CAAAAExTS1M2AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAAAAADEGAAB1bmlmb3JtIGZsb2F0NCB1Y2xhbXBfUzFfYzBfYzBfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMF9jMDsKdW5pZm9ybSBoYWxmMiB1XzBfSW5jcmVtZW50X1MxX2MwOwp1bmlmb3JtIGhhbGY0IHVfMV9LZXJuZWxfUzFfYzBbNF07CnVuaWZvcm0gaGFsZjQgdV8yX09mZnNldHNfUzFfYzBbNF07CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzE7CmluIGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgVGV4dHVyZUVmZmVjdF9TMV9jMF9jMF9jMChoYWxmNCBfaW5wdXQsIGZsb2F0MiBfY29vcmRzKSAKewoJZmxvYXQyIGluQ29vcmQgPSBfY29vcmRzOwoJZmxvYXQyIHN1YnNldENvb3JkOwoJc3Vic2V0Q29vcmQueCA9IGluQ29vcmQueDsKCXN1YnNldENvb3JkLnkgPSBpbkNvb3JkLnk7CglmbG9hdDIgY2xhbXBlZENvb3JkOwoJY2xhbXBlZENvb3JkLnggPSBzdWJzZXRDb29yZC54OwoJY2xhbXBlZENvb3JkLnkgPSBjbGFtcChzdWJzZXRDb29yZC55LCB1Y2xhbXBfUzFfYzBfYzBfYzAueSwgdWNsYW1wX1MxX2MwX2MwX2MwLncpOwoJaGFsZjQgdGV4dHVyZUNvbG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MxLCBjbGFtcGVkQ29vcmQpOwoJcmV0dXJuIHRleHR1cmVDb2xvcjsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfUzFfYzBfYzAoaGFsZjQgX2lucHV0LCBmbG9hdDIgX2Nvb3JkcykgCnsKCXJldHVybiBUZXh0dXJlRWZmZWN0X1MxX2MwX2MwX2MwKF9pbnB1dCwgZmxvYXQzeDIodW1hdHJpeF9TMV9jMF9jMCkgKiBfY29vcmRzLnh5MSk7Cn0KaGFsZjQgR2F1c3NpYW5Db252b2x1dGlvbl9TMV9jMChoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBfM19jb2xvciA9IGhhbGY0KDAuMCk7CglmbG9hdDIgXzVfY29vcmQgPSB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKCWZvciAoaW50IF82X2kgPSAwOyAoXzZfaSA8IDEzKTsgXzZfaSsrKSAoXzNfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TMV9jMF9jMChfaW5wdXQsIChfNV9jb29yZCArIGZsb2F0MigodV8yX09mZnNldHNfUzFfYzBbKF82X2kgLyA0KV1bKF82X2kgJiAzKV0gKiB1XzBfSW5jcmVtZW50X1MxX2MwKSkpKSAqIHVfMV9LZXJuZWxfUzFfYzBbKF82X2kgLyA0KV1bKF82X2kgJiAzKV0pKTsKCXJldHVybiBfM19jb2xvcjsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIEdhdXNzaWFuQ29udm9sdXRpb25fUzFfYzAoX2lucHV0KTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMCA9IGhhbGY0KDEpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IE1hdHJpeEVmZmVjdF9TMShvdXRwdXRDb2xvcl9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0X1MxICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAALUAQMAAAAAAAAAEAAAABJYQAAAAAAAAIAAAAAWCBAAAABAAAAANAECAZAAAAAAAAAAAFAAMAAAABAAAAAAABBAM":"CAAAAExTS1M2AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAAAAAPIEAAB1bmlmb3JtIGZsb2F0M3gzIHVtYXRyaXhfUzFfYzBfYzA7CnVuaWZvcm0gaGFsZjIgdV8wX0luY3JlbWVudF9TMV9jMDsKdW5pZm9ybSBoYWxmNCB1XzFfS2VybmVsX1MxX2MwWzRdOwp1bmlmb3JtIGhhbGY0IHVfMl9PZmZzZXRzX1MxX2MwWzRdOwp1bmlmb3JtIGZsb2F0M3gzIHVtYXRyaXhfUzE7CnVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1MxOwppbiBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IFRleHR1cmVFZmZlY3RfUzFfYzBfYzBfYzAoaGFsZjQgX2lucHV0LCBmbG9hdDIgX2Nvb3JkcykgCnsKCXJldHVybiBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfUzEsIF9jb29yZHMpOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TMV9jMF9jMChoYWxmNCBfaW5wdXQsIGZsb2F0MiBfY29vcmRzKSAKewoJcmV0dXJuIFRleHR1cmVFZmZlY3RfUzFfYzBfYzBfYzAoX2lucHV0LCBmbG9hdDN4Mih1bWF0cml4X1MxX2MwX2MwKSAqIF9jb29yZHMueHkxKTsKfQpoYWxmNCBHYXVzc2lhbkNvbnZvbHV0aW9uX1MxX2MwKGhhbGY0IF9pbnB1dCkgCnsKCWhhbGY0IF8zX2NvbG9yID0gaGFsZjQoMC4wKTsKCWZsb2F0MiBfNV9jb29yZCA9IHZUcmFuc2Zvcm1lZENvb3Jkc18yX1MwOwoJZm9yIChpbnQgXzZfaSA9IDA7IChfNl9pIDwgMTMpOyBfNl9pKyspIChfM19jb2xvciArPSAoTWF0cml4RWZmZWN0X1MxX2MwX2MwKF9pbnB1dCwgKF81X2Nvb3JkICsgZmxvYXQyKCh1XzJfT2Zmc2V0c19TMV9jMFsoXzZfaSAvIDQpXVsoXzZfaSAmIDMpXSAqIHVfMF9JbmNyZW1lbnRfUzFfYzApKSkpICogdV8xX0tlcm5lbF9TMV9jMFsoXzZfaSAvIDQpXVsoXzZfaSAmIDMpXSkpOwoJcmV0dXJuIF8zX2NvbG9yOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CglyZXR1cm4gR2F1c3NpYW5Db252b2x1dGlvbl9TMV9jMChfaW5wdXQpOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwID0gaGFsZjQoMSk7Cgljb25zdCBoYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KDEpOwoJaGFsZjQgb3V0cHV0X1MxOwoJb3V0cHV0X1MxID0gTWF0cml4RWZmZWN0X1MxKG91dHB1dENvbG9yX1MwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRfUzEgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24KAAAAbG9jYWxDb29yZAAAAQAAAAAAAAA=","HUJAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAQAAAAAEQQGABZAA6IAAAAACAAAAAADUAAAAAAAEAAAAAIDEAAAAAAA":"CAAAAExTS1PlAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmxvY2FsQ29vcmRfUzAgPSBsb2NhbENvb3JkOwoJc2tfUG9zaXRpb24gPSBwb3NpdGlvbi54eTAxOwp9CgAAAAEAAABPAwAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CnVuaWZvcm0gc2FtcGxlckV4dGVybmFsT0VTIHVUZXh0dXJlU2FtcGxlcl8wX1MwOwppbiBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IENpcmN1bGFyUlJlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGR4eTAgPSB1aW5uZXJSZWN0X1MxLkxUIC0gc2tfRnJhZ0Nvb3JkLnh5OwoJZmxvYXQyIGR4eTEgPSBza19GcmFnQ29vcmQueHkgLSB1aW5uZXJSZWN0X1MxLlJCOwoJZmxvYXQyIGR4eSA9IG1heChtYXgoZHh5MCwgZHh5MSksIDAuMCk7CgloYWxmIGFscGhhID0gaGFsZihzYXR1cmF0ZSh1cmFkaXVzUGx1c0hhbGZfUzEueCAtIGxlbmd0aChkeHkpKSk7CglyZXR1cm4gX2lucHV0ICogYWxwaGE7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzAgPSBoYWxmNCgxKTsKCWZsb2F0MiB0ZXhDb29yZDsKCXRleENvb3JkID0gdmxvY2FsQ29vcmRfUzA7CglvdXRwdXRDb2xvcl9TMCA9ICgoc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MwLCB0ZXhDb29yZCkgKiBoYWxmNCgxKSkpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IENpcmN1bGFyUlJlY3RfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAACAAAACAAAAHBvc2l0aW9uCgAAAGxvY2FsQ29vcmQAAAEAAAAAAAAA","FAAQMYAAMAAAEADAAABAEYAAAICIAB5AABQAAAQAMAAAEATAAABAIIGAAEDCBYQCA4AAAAAAEAZCBRE4GNEACAAAOAAAAAAAAAAABAAOAAAABAAAAAAABBAMAA":"CAAAAExTS1PUCwAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0NCByYWRpaV9zZWxlY3RvcjsKaW4gZmxvYXQ0IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHM7CmluIGZsb2F0NCBhYV9ibG9hdF9hbmRfY292ZXJhZ2U7CmluIGZsb2F0NCByYWRpaV94OwppbiBmbG9hdDQgcmFkaWlfeTsKaW4gZmxvYXQ0IHNrZXc7CmluIGZsb2F0MiB0cmFuc2xhdGVfYW5kX2xvY2Fscm90YXRlOwppbiBoYWxmNCBjb2xvcjsKZmxhdCBvdXQgaGFsZjQgdmNvbG9yX1MwOwpvdXQgZmxvYXQyIHZhcmNjb29yZF9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgRmlsbFJSZWN0T3A6OlByb2Nlc3NvcgoJdmNvbG9yX1MwID0gY29sb3I7CglmbG9hdCBhYV9ibG9hdF9tdWx0aXBsaWVyID0gMTsKCWZsb2F0MiBjb3JuZXIgPSBjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzLnh5OwoJZmxvYXQyIHJhZGl1c19vdXRzZXQgPSBjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzLnp3OwoJZmxvYXQyIGFhX2Jsb2F0X2RpcmVjdGlvbiA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS54eTsKCWZsb2F0IGlzX2xpbmVhcl9jb3ZlcmFnZSA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS53OwoJZmxvYXQyIHBpeGVsbGVuZ3RoID0gaW52ZXJzZXNxcnQoZmxvYXQyKGRvdChza2V3Lnh6LCBza2V3Lnh6KSwgZG90KHNrZXcueXcsIHNrZXcueXcpKSk7CglmbG9hdDQgbm9ybWFsaXplZF9heGlzX2RpcnMgPSBza2V3ICogcGl4ZWxsZW5ndGgueHl4eTsKCWZsb2F0MiBheGlzd2lkdGhzID0gKGFicyhub3JtYWxpemVkX2F4aXNfZGlycy54eSkgKyBhYnMobm9ybWFsaXplZF9heGlzX2RpcnMuencpKTsKCWZsb2F0MiBhYV9ibG9hdHJhZGl1cyA9IGF4aXN3aWR0aHMgKiBwaXhlbGxlbmd0aCAqIC41OwoJZmxvYXQ0IHJhZGlpX2FuZF9uZWlnaGJvcnMgPSByYWRpaV9zZWxlY3RvciogZmxvYXQ0eDQocmFkaWlfeCwgcmFkaWlfeSwgcmFkaWlfeC55eHd6LCByYWRpaV95Lnd6eXgpOwoJZmxvYXQyIHJhZGlpID0gcmFkaWlfYW5kX25laWdoYm9ycy54eTsKCWZsb2F0MiBuZWlnaGJvcl9yYWRpaSA9IHJhZGlpX2FuZF9uZWlnaGJvcnMuenc7CglmbG9hdCBjb3ZlcmFnZV9tdWx0aXBsaWVyID0gMTsKCWlmIChhbnkoZ3JlYXRlclRoYW4oYWFfYmxvYXRyYWRpdXMsIGZsb2F0MigxKSkpKSAKCXsKCQljb3JuZXIgPSBtYXgoYWJzKGNvcm5lciksIGFhX2Jsb2F0cmFkaXVzKSAqIHNpZ24oY29ybmVyKTsKCQljb3ZlcmFnZV9tdWx0aXBsaWVyID0gMSAvIChtYXgoYWFfYmxvYXRyYWRpdXMueCwgMSkgKiBtYXgoYWFfYmxvYXRyYWRpdXMueSwgMSkpOwoJCXJhZGlpID0gZmxvYXQyKDApOwoJfQoJZmxvYXQgY292ZXJhZ2UgPSBhYV9ibG9hdF9hbmRfY292ZXJhZ2UuejsKCWlmIChhbnkobGVzc1RoYW4ocmFkaWksIGFhX2Jsb2F0cmFkaXVzICogMS41KSkpIAoJewoJCXJhZGlpID0gZmxvYXQyKDApOwoJCWFhX2Jsb2F0X2RpcmVjdGlvbiA9IHNpZ24oY29ybmVyKTsKCQlpZiAoY292ZXJhZ2UgPiAuNSkgCgkJewoJCQlhYV9ibG9hdF9kaXJlY3Rpb24gPSAtYWFfYmxvYXRfZGlyZWN0aW9uOwoJCX0KCQlpc19saW5lYXJfY292ZXJhZ2UgPSAxOwoJfQoJZWxzZSAKCXsKCQlyYWRpaSA9IGNsYW1wKHJhZGlpLCBwaXhlbGxlbmd0aCAqIDEuNSwgMiAtIHBpeGVsbGVuZ3RoICogMS41KTsKCQluZWlnaGJvcl9yYWRpaSA9IGNsYW1wKG5laWdoYm9yX3JhZGlpLCBwaXhlbGxlbmd0aCAqIDEuNSwgMiAtIHBpeGVsbGVuZ3RoICogMS41KTsKCQlmbG9hdDIgc3BhY2luZyA9IDIgLSByYWRpaSAtIG5laWdoYm9yX3JhZGlpOwoJCWZsb2F0MiBleHRyYV9wYWQgPSBtYXgocGl4ZWxsZW5ndGggKiAuMDYyNSAtIHNwYWNpbmcsIGZsb2F0MigwKSk7CgkJcmFkaWkgLT0gZXh0cmFfcGFkICogLjU7Cgl9CglmbG9hdDIgYWFfb3V0c2V0ID0gYWFfYmxvYXRfZGlyZWN0aW9uICogYWFfYmxvYXRyYWRpdXMgKiBhYV9ibG9hdF9tdWx0aXBsaWVyOwoJZmxvYXQyIHZlcnRleHBvcyA9IGNvcm5lciArIHJhZGl1c19vdXRzZXQgKiByYWRpaSArIGFhX291dHNldDsKCWlmIChjb3ZlcmFnZSA+IC41KSAKCXsKCQlpZiAoYWFfYmxvYXRfZGlyZWN0aW9uLnggIT0gMCAmJiB2ZXJ0ZXhwb3MueCAqIGNvcm5lci54IDwgMCkgCgkJewoJCQlmbG9hdCBiYWNrc2V0ID0gYWJzKHZlcnRleHBvcy54KTsKCQkJdmVydGV4cG9zLnggPSAwOwoJCQl2ZXJ0ZXhwb3MueSArPSBiYWNrc2V0ICogc2lnbihjb3JuZXIueSkgKiBwaXhlbGxlbmd0aC55L3BpeGVsbGVuZ3RoLng7CgkJCWNvdmVyYWdlID0gKGNvdmVyYWdlIC0gLjUpICogYWJzKGNvcm5lci54KSAvIChhYnMoY29ybmVyLngpICsgYmFja3NldCkgKyAuNTsKCQl9CgkJaWYgKGFhX2Jsb2F0X2RpcmVjdGlvbi55ICE9IDAgJiYgdmVydGV4cG9zLnkgKiBjb3JuZXIueSA8IDApIAoJCXsKCQkJZmxvYXQgYmFja3NldCA9IGFicyh2ZXJ0ZXhwb3MueSk7CgkJCXZlcnRleHBvcy55ID0gMDsKCQkJdmVydGV4cG9zLnggKz0gYmFja3NldCAqIHNpZ24oY29ybmVyLngpICogcGl4ZWxsZW5ndGgueC9waXhlbGxlbmd0aC55OwoJCQljb3ZlcmFnZSA9IChjb3ZlcmFnZSAtIC41KSAqIGFicyhjb3JuZXIueSkgLyAoYWJzKGNvcm5lci55KSArIGJhY2tzZXQpICsgLjU7CgkJfQoJfQoJZmxvYXQyeDIgc2tld21hdHJpeCA9IGZsb2F0MngyKHNrZXcueHksIHNrZXcuencpOwoJZmxvYXQyIGRldmNvb3JkID0gdmVydGV4cG9zICogc2tld21hdHJpeCArIHRyYW5zbGF0ZV9hbmRfbG9jYWxyb3RhdGUueHk7CglpZiAoMCAhPSBpc19saW5lYXJfY292ZXJhZ2UpIAoJewoJCXZhcmNjb29yZF9TMC54eSA9IGZsb2F0MigwLCBjb3ZlcmFnZSAqIGNvdmVyYWdlX211bHRpcGxpZXIpOwoJfQoJZWxzZSAKCXsKCQlmbG9hdDIgYXJjY29vcmQgPSAxIC0gYWJzKHJhZGl1c19vdXRzZXQpICsgYWFfb3V0c2V0L3JhZGlpICogY29ybmVyOwoJCXZhcmNjb29yZF9TMC54eSA9IGZsb2F0MihhcmNjb29yZC54KzEsIGFyY2Nvb3JkLnkpOwoJfQoJc2tfUG9zaXRpb24gPSBkZXZjb29yZC54eTAxOwp9CgEAAAAHBQAAY29uc3QgaW50IGtGaWxsQUFfUzEgPSAxOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQldfUzEgPSAyOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQUFfUzEgPSAzOwp1bmlmb3JtIGZsb2F0NCB1Y2lyY2xlX1MxOwpmbGF0IGluIGhhbGY0IHZjb2xvcl9TMDsKaW4gZmxvYXQyIHZhcmNjb29yZF9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgQ2lyY2xlX1MxKGhhbGY0IF9pbnB1dCkgCnsKCWhhbGY0IF90bXBfMF9pbkNvbG9yID0gX2lucHV0OwoJaGFsZiBkOwoJaWYgKGludCgzKSA9PSBrSW52ZXJzZUZpbGxCV19TMSB8fCBpbnQoMykgPT0ga0ludmVyc2VGaWxsQUFfUzEpIAoJewoJCWQgPSBoYWxmKChsZW5ndGgoKHVjaXJjbGVfUzEueHkgLSBza19GcmFnQ29vcmQueHkpICogdWNpcmNsZV9TMS53KSAtIDEuMCkgKiB1Y2lyY2xlX1MxLnopOwoJfQoJZWxzZSAKCXsKCQlkID0gaGFsZigoMS4wIC0gbGVuZ3RoKCh1Y2lyY2xlX1MxLnh5IC0gc2tfRnJhZ0Nvb3JkLnh5KSAqIHVjaXJjbGVfUzEudykpICogdWNpcmNsZV9TMS56KTsKCX0KCWlmIChpbnQoMykgPT0ga0ZpbGxBQV9TMSB8fCBpbnQoMykgPT0ga0ludmVyc2VGaWxsQUFfUzEpIAoJewoJCXJldHVybiBoYWxmNChfaW5wdXQgKiBzYXR1cmF0ZShkKSk7Cgl9CgllbHNlIAoJewoJCXJldHVybiBoYWxmNChkID4gMC41ID8gX2lucHV0IDogaGFsZjQoMC4wKSk7Cgl9Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIEZpbGxSUmVjdE9wOjpQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2Y29sb3JfUzA7CglmbG9hdCB4X3BsdXNfMT12YXJjY29vcmRfUzAueCwgeT12YXJjY29vcmRfUzAueTsKCWhhbGYgY292ZXJhZ2U7CglpZiAoMCA9PSB4X3BsdXNfMSkgCgl7CgkJY292ZXJhZ2UgPSBoYWxmKHkpOwoJfQoJZWxzZSAKCXsKCQlmbG9hdCBmbiA9IHhfcGx1c18xICogKHhfcGx1c18xIC0gMik7CgkJZm4gPSBmbWEoeSx5LCBmbik7CgkJZmxvYXQgZm53aWR0aCA9IGZ3aWR0aChmbik7CgkJY292ZXJhZ2UgPSAuNSAtIGhhbGYoZm4vZm53aWR0aCk7CgkJY292ZXJhZ2UgPSBjbGFtcChjb3ZlcmFnZSwgMCwgMSk7Cgl9CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KGNvdmVyYWdlKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IENpcmNsZV9TMShvdXRwdXRDb3ZlcmFnZV9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRfUzE7Cgl9Cn0KAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAgAAAAOAAAAcmFkaWlfc2VsZWN0b3IAABkAAABjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzAAAAFQAAAGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZQAAAAcAAAByYWRpaV94AAcAAAByYWRpaV95AAQAAABza2V3GQAAAHRyYW5zbGF0ZV9hbmRfbG9jYWxyb3RhdGUAAAAFAAAAY29sb3IAAAABAAAAAAAAAA==","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAALUAQBAAAQAAAAGQCBAMQACAAAAAAAACQAGAAAAAQAAAAAAAQQGAAAAA":"CAAAAExTS1M2AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAAAAAIgDAAB1bmlmb3JtIGZsb2F0NCB1Y2xhbXBfUzFfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzE7CmluIGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgVGV4dHVyZUVmZmVjdF9TMV9jMChoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgaW5Db29yZCA9IHZUcmFuc2Zvcm1lZENvb3Jkc18yX1MwOwoJZmxvYXQyIHN1YnNldENvb3JkOwoJc3Vic2V0Q29vcmQueCA9IGluQ29vcmQueDsKCXN1YnNldENvb3JkLnkgPSBpbkNvb3JkLnk7CglmbG9hdDIgY2xhbXBlZENvb3JkOwoJY2xhbXBlZENvb3JkLnggPSBjbGFtcChzdWJzZXRDb29yZC54LCB1Y2xhbXBfUzFfYzAueCwgdWNsYW1wX1MxX2MwLnopOwoJY2xhbXBlZENvb3JkLnkgPSBzdWJzZXRDb29yZC55OwoJaGFsZjQgdGV4dHVyZUNvbG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MxLCBjbGFtcGVkQ29vcmQpOwoJcmV0dXJuIHRleHR1cmVDb2xvcjsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIFRleHR1cmVFZmZlY3RfUzFfYzAoX2lucHV0KTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMCA9IGhhbGY0KDEpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IE1hdHJpeEVmZmVjdF9TMShvdXRwdXRDb2xvcl9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0X1MxICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAALUAQAAEAQAAAAGQCBAMQAAAIAAAAAACQAGAAAAAQAAAAAAAQQGAAAAA":"CAAAAExTS1M2AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAAAAAIgDAAB1bmlmb3JtIGZsb2F0NCB1Y2xhbXBfUzFfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzE7CmluIGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgVGV4dHVyZUVmZmVjdF9TMV9jMChoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgaW5Db29yZCA9IHZUcmFuc2Zvcm1lZENvb3Jkc18yX1MwOwoJZmxvYXQyIHN1YnNldENvb3JkOwoJc3Vic2V0Q29vcmQueCA9IGluQ29vcmQueDsKCXN1YnNldENvb3JkLnkgPSBpbkNvb3JkLnk7CglmbG9hdDIgY2xhbXBlZENvb3JkOwoJY2xhbXBlZENvb3JkLnggPSBzdWJzZXRDb29yZC54OwoJY2xhbXBlZENvb3JkLnkgPSBjbGFtcChzdWJzZXRDb29yZC55LCB1Y2xhbXBfUzFfYzAueSwgdWNsYW1wX1MxX2MwLncpOwoJaGFsZjQgdGV4dHVyZUNvbG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MxLCBjbGFtcGVkQ29vcmQpOwoJcmV0dXJuIHRleHR1cmVDb2xvcjsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIFRleHR1cmVFZmZlY3RfUzFfYzAoX2lucHV0KTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMCA9IGhhbGY0KDEpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IE1hdHJpeEVmZmVjdF9TMShvdXRwdXRDb2xvcl9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0X1MxICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","BYIBQAAABQAAIAABBYAAAEIXBAAP777777777777AAAAAAAAAAAABUABAAAAAEAAAAAIBEABAAAAA":"CAAAAExTS1M+AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwpvdXQgaGFsZjQgdmNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBEZWZhdWx0R2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IGNvbG9yID0gaW5Db2xvcjsKCXZjb2xvcl9TMCA9IGNvbG9yOwoJZmxvYXQyIF90bXBfMV9pblBvc2l0aW9uID0gaW5Qb3NpdGlvbjsKCWZsb2F0MiBfdG1wXzNfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247Cglza19Qb3NpdGlvbiA9IF90bXBfMV9pblBvc2l0aW9uLnh5MDE7Cn0KAAAAAAAAHgEAAGluIGhhbGY0IHZjb2xvcl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIERlZmF1bHRHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZjb2xvcl9TMDsKCWNvbnN0IGhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoMSk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAIAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IAAQAAAAAAAAA="}} \ No newline at end of file diff --git a/test_driver/driver_screenshots.dart b/test_driver/driver_screenshots.dart index 58b35c7ab..e08ed8d56 100644 --- a/test_driver/driver_screenshots.dart +++ b/test_driver/driver_screenshots.dart @@ -18,10 +18,13 @@ Future configureAndLaunch() async { ..hasAcceptedTerms = true ..isInstalledAppAccessAllowed = true ..isErrorReportingAllowed = false - ..themeBrightness = AvesThemeBrightness.dark - ..themeColorMode = AvesThemeColorMode.polychrome ..setTileExtent(CountryListPage.routeName, 112) ..setTileLayout(CountryListPage.routeName, TileLayout.grid) + // display + ..themeBrightness = AvesThemeBrightness.dark + ..themeColorMode = AvesThemeColorMode.polychrome + ..enableDynamicColor = false + ..enableBlurEffect = true // navigation ..keepScreenOn = KeepScreenOn.always ..homePage = HomePageSetting.collection @@ -41,7 +44,6 @@ Future configureAndLaunch() async { ..showOverlayInfo = true ..showOverlayShootingDetails = false ..showOverlayThumbnailPreview = false - ..enableOverlayBlurEffect = true ..viewerUseCutout = true // info ..infoMapStyle = EntryMapStyle.stamenWatercolor diff --git a/test_driver/driver_screenshots_test.dart b/test_driver/driver_screenshots_test.dart index e9c2ce7a1..0f3158e19 100644 --- a/test_driver/driver_screenshots_test.dart +++ b/test_driver/driver_screenshots_test.dart @@ -83,6 +83,11 @@ void setLanguage(String languageCode) { await driver.tapKeyAndWait('drawer-settings-button'); await driver.tapKeyAndWait('section-language'); await driver.tapKeyAndWait('tile-language'); + + final name = SupportedLocales.languagesByLanguageCode[languageCode] ?? languageCode; + await driver.tap(find.byType('TextField')); + await driver.enterText(name); + await driver.tapKeyAndWait(languageCode); _languageCode = languageCode; diff --git a/test_driver/driver_shaders.dart b/test_driver/driver_shaders.dart index 8d973b7b3..168bb3cc9 100644 --- a/test_driver/driver_shaders.dart +++ b/test_driver/driver_shaders.dart @@ -19,8 +19,15 @@ Future configureAndLaunch() async { ..isInstalledAppAccessAllowed = true ..isErrorReportingAllowed = false ..locale = const Locale('en') + // display + ..themeBrightness = AvesThemeBrightness.dark + ..themeColorMode = AvesThemeColorMode.polychrome + ..enableDynamicColor = false + ..enableBlurEffect = true + // navigation ..keepScreenOn = KeepScreenOn.always ..homePage = HomePageSetting.collection + ..showBottomNavigationBar = true // collection ..collectionBrowsingQuickActions = SettingsDefaults.collectionBrowsingQuickActions // viewer @@ -29,7 +36,6 @@ Future configureAndLaunch() async { ..showOverlayInfo = true ..showOverlayShootingDetails = true ..showOverlayThumbnailPreview = true - ..enableOverlayBlurEffect = true ..imageBackground = EntryBackground.checkered // info ..infoMapStyle = EntryMapStyle.googleNormal; diff --git a/untranslated.json b/untranslated.json index 3da227021..a28413c14 100644 --- a/untranslated.json +++ b/untranslated.json @@ -1,6 +1,115 @@ { - "es": [ - "settingsShowBottomNavigationBar", - "settingsThumbnailShowTagIcon" + "id": [ + "slideshowActionResume", + "slideshowActionShowInCollection", + "slideshowVideoPlaybackSkip", + "slideshowVideoPlaybackMuted", + "slideshowVideoPlaybackWithSound", + "viewerTransitionSlide", + "viewerTransitionParallax", + "viewerTransitionFade", + "viewerTransitionZoomIn", + "wallpaperTargetHome", + "wallpaperTargetLock", + "wallpaperTargetHomeLock", + "menuActionSlideshow", + "collectionEmptyGrantAccessButtonLabel", + "settingsViewerSlideshowTile", + "settingsViewerSlideshowTitle", + "settingsSlideshowRepeat", + "settingsSlideshowShuffle", + "settingsSlideshowTransitionTile", + "settingsSlideshowTransitionTitle", + "settingsSlideshowIntervalTile", + "settingsSlideshowIntervalTitle", + "settingsSlideshowVideoPlaybackTile", + "settingsSlideshowVideoPlaybackTitle", + "settingsThemeEnableDynamicColor", + "viewerSetWallpaperButtonLabel" + ], + + "ja": [ + "slideshowActionResume", + "slideshowActionShowInCollection", + "slideshowVideoPlaybackSkip", + "slideshowVideoPlaybackMuted", + "slideshowVideoPlaybackWithSound", + "viewerTransitionSlide", + "viewerTransitionParallax", + "viewerTransitionFade", + "viewerTransitionZoomIn", + "wallpaperTargetHome", + "wallpaperTargetLock", + "wallpaperTargetHomeLock", + "menuActionSlideshow", + "collectionEmptyGrantAccessButtonLabel", + "settingsViewerSlideshowTile", + "settingsViewerSlideshowTitle", + "settingsSlideshowRepeat", + "settingsSlideshowShuffle", + "settingsSlideshowTransitionTile", + "settingsSlideshowTransitionTitle", + "settingsSlideshowIntervalTile", + "settingsSlideshowIntervalTitle", + "settingsSlideshowVideoPlaybackTile", + "settingsSlideshowVideoPlaybackTitle", + "settingsThemeEnableDynamicColor", + "viewerSetWallpaperButtonLabel" + ], + + "ru": [ + "slideshowActionResume", + "slideshowActionShowInCollection", + "slideshowVideoPlaybackSkip", + "slideshowVideoPlaybackMuted", + "slideshowVideoPlaybackWithSound", + "viewerTransitionSlide", + "viewerTransitionParallax", + "viewerTransitionFade", + "viewerTransitionZoomIn", + "wallpaperTargetHome", + "wallpaperTargetLock", + "wallpaperTargetHomeLock", + "menuActionSlideshow", + "collectionEmptyGrantAccessButtonLabel", + "settingsViewerSlideshowTile", + "settingsViewerSlideshowTitle", + "settingsSlideshowRepeat", + "settingsSlideshowShuffle", + "settingsSlideshowTransitionTile", + "settingsSlideshowTransitionTitle", + "settingsSlideshowIntervalTile", + "settingsSlideshowIntervalTitle", + "settingsSlideshowVideoPlaybackTile", + "settingsSlideshowVideoPlaybackTitle", + "settingsThemeEnableDynamicColor", + "viewerSetWallpaperButtonLabel" + ], + + "tr": [ + "slideshowActionResume", + "slideshowActionShowInCollection", + "slideshowVideoPlaybackSkip", + "slideshowVideoPlaybackMuted", + "slideshowVideoPlaybackWithSound", + "viewerTransitionSlide", + "viewerTransitionParallax", + "viewerTransitionFade", + "viewerTransitionZoomIn", + "wallpaperTargetHome", + "wallpaperTargetLock", + "wallpaperTargetHomeLock", + "menuActionSlideshow", + "settingsViewerSlideshowTile", + "settingsViewerSlideshowTitle", + "settingsSlideshowRepeat", + "settingsSlideshowShuffle", + "settingsSlideshowTransitionTile", + "settingsSlideshowTransitionTitle", + "settingsSlideshowIntervalTile", + "settingsSlideshowIntervalTitle", + "settingsSlideshowVideoPlaybackTile", + "settingsSlideshowVideoPlaybackTitle", + "viewerSetWallpaperButtonLabel" ] } diff --git a/whatsnew/whatsnew-en-US b/whatsnew/whatsnew-en-US index c34d25bfb..31d4d7931 100644 --- a/whatsnew/whatsnew-en-US +++ b/whatsnew/whatsnew-en-US @@ -1,5 +1,5 @@ -In v1.6.8: -- bottom navigation bar -- fast scroll with breadcrumbs -- settings search +In v1.6.9: +- start slideshows +- change your wallpaper +- enjoy the app in Turkish Full changelog available on GitHub \ No newline at end of file