1 Import data

The scRNAseq package provides convenient access to several datasets. See the package Bioconductor page for more information.

# install BiocManager package if not installed yet.
# BiocManager is the package installer for Bioconductor software.
if (!requireNamespace("BiocManager", quietly = TRUE))
    install.packages("BiocManager")

# install packages if not yet installed.
pkgs <- c("SingleCellExperiment", "DropletUtils", "scRNAseq", "scater", "scuttle", "scran", "BiocSingular", "scDblFinder", "glmpca", "uwot")
notInstalled <- pkgs[!pkgs %in% installed.packages()[,1]]
if(length(notInstalled) > 0){
  BiocManager::install(notInstalled)
}

# Code below might ask you to create an ExperimentHub directory. 
# Type 'yes' and hit Enter, to allow this.
suppressPackageStartupMessages(library(scRNAseq))
## Warning: package 'SingleCellExperiment' was built under R version 4.4.2
## Warning: package 'GenomeInfoDb' was built under R version 4.4.2
sce <- MacoskoRetinaData()

2 A SingleCellExperiment object

sce
## class: SingleCellExperiment 
## dim: 24658 49300 
## metadata(0):
## assays(1): counts
## rownames(24658): KITL TMTC3 ... 1110059M19RIK GM20861
## rowData names(0):
## colnames(49300): r1_GGCCGCAGTCCG r1_CTTGTGCGGGAA ... p1_TAACGCGCTCCT
##   p1_ATTCTTGTTCTT
## colData names(1): cluster
## reducedDimNames(0):
## mainExpName: NULL
## altExpNames(0):

2.1 Accessing data from a SingleCellExperiment object

Please see Figure 4.1 in OSCA for an overview of a SingleCellExperiment object.

# Data: assays
assays(sce)
## List of length 1
## names(1): counts
assays(sce)$counts[1:5, 1:5]
## 5 x 5 sparse Matrix of class "dgCMatrix"
##               r1_GGCCGCAGTCCG r1_CTTGTGCGGGAA r1_GCGCAACTGCTC r1_GATTGGGAGGCA
## KITL                        .               .               1               .
## TMTC3                       3               .               .               .
## CEP290                      1               3               .               2
## 4930430F08RIK               2               1               2               .
## 1700017N19RIK               .               .               .               .
##               r1_CCTCCTAGTTGG
## KITL                        .
## TMTC3                       2
## CEP290                      1
## 4930430F08RIK               1
## 1700017N19RIK               .
# Feature metadata: rowData
rowData(sce) # empty for now
## DataFrame with 24658 rows and 0 columns
# Cell metadata: colData
colData(sce)
## DataFrame with 49300 rows and 1 column
##                   cluster
##                 <integer>
## r1_GGCCGCAGTCCG         2
## r1_CTTGTGCGGGAA         2
## r1_GCGCAACTGCTC         2
## r1_GATTGGGAGGCA         2
## r1_CCTCCTAGTTGG        NA
## ...                   ...
## p1_TCAAAAGCCGGG        24
## p1_ATTAAGTTCCAA        34
## p1_CTGTCTGAGACC         2
## p1_TAACGCGCTCCT        24
## p1_ATTCTTGTTCTT        24
# Reduced dimensions: reducedDims
reducedDims(sce) # empty for now
## List of length 0
## names(0):

2.2 Creating a new SingleCellExperiment object

sceNew <- SingleCellExperiment(assays = list(counts = assays(sce)$counts))
sceNew
## class: SingleCellExperiment 
## dim: 24658 49300 
## metadata(0):
## assays(1): counts
## rownames(24658): KITL TMTC3 ... 1110059M19RIK GM20861
## rowData names(0):
## colnames(49300): r1_GGCCGCAGTCCG r1_CTTGTGCGGGAA ... p1_TAACGCGCTCCT
##   p1_ATTCTTGTTCTT
## colData names(0):
## reducedDimNames(0):
## mainExpName: NULL
## altExpNames(0):
rm(sceNew)

2.3 Storing (meta)data in a SingleCellExperiment object

fakeGeneNames <- paste0("gene", 1:nrow(sce))
rowData(sce)$fakeName <- fakeGeneNames
head(rowData(sce))
## DataFrame with 6 rows and 1 column
##                  fakeName
##               <character>
## KITL                gene1
## TMTC3               gene2
## CEP290              gene3
## 4930430F08RIK       gene4
## 1700017N19RIK       gene5
## MGAT4C              gene6
# Remove again by setting to NULL
rowData(sce)$fakeName <- NULL

assays(sce)$logCounts <- log1p(assays(sce)$counts)
assays(sce)
## List of length 2
## names(2): counts logCounts
assays(sce)$logCounts[1:5, 1:5]
## 5 x 5 sparse Matrix of class "dgCMatrix"
##               r1_GGCCGCAGTCCG r1_CTTGTGCGGGAA r1_GCGCAACTGCTC r1_GATTGGGAGGCA
## KITL                .               .               0.6931472        .       
## TMTC3               1.3862944       .               .                .       
## CEP290              0.6931472       1.3862944       .                1.098612
## 4930430F08RIK       1.0986123       0.6931472       1.0986123        .       
## 1700017N19RIK       .               .               .                .       
##               r1_CCTCCTAGTTGG
## KITL                .        
## TMTC3               1.0986123
## CEP290              0.6931472
## 4930430F08RIK       0.6931472
## 1700017N19RIK       .
assays(sce)$logCounts <- NULL

3 Filtering non-informative genes

keep <- rowSums(assays(sce)$counts > 0) > 10
table(keep)
## keep
## FALSE  TRUE 
##  6771 17887
sce <- sce[keep,]

4 Quality control

4.1 Calculate QC variables

library(scater)
## Loading required package: scuttle
## Loading required package: ggplot2
is.mito <- grepl("^MT-", rownames(sce))
sum(is.mito) # 28 mitochondrial genes
## [1] 28
df <- perCellQCMetrics(sce, subsets=list(Mito=is.mito))
## add the QC variables to sce object
colData(sce) <- cbind(colData(sce), df)
# the QC variables have now been added to the colData of our SCE object.
colData(sce)
## DataFrame with 49300 rows and 7 columns
##                   cluster       sum  detected subsets_Mito_sum
##                 <integer> <numeric> <integer>        <numeric>
## r1_GGCCGCAGTCCG         2     37478      7235              427
## r1_CTTGTGCGGGAA         2     32034      6921              503
## r1_GCGCAACTGCTC         2     28140      6390              460
## r1_GATTGGGAGGCA         2     20352      5727              326
## r1_CCTCCTAGTTGG        NA     19550      5769              264
## ...                   ...       ...       ...              ...
## p1_TCAAAAGCCGGG        24       817       537               13
## p1_ATTAAGTTCCAA        34       817       574               10
## p1_CTGTCTGAGACC         2       816       636               24
## p1_TAACGCGCTCCT        24       816       488               27
## p1_ATTCTTGTTCTT        24       816       484               16
##                 subsets_Mito_detected subsets_Mito_percent     total
##                             <integer>            <numeric> <numeric>
## r1_GGCCGCAGTCCG                    14              1.13934     37478
## r1_CTTGTGCGGGAA                    15              1.57021     32034
## r1_GCGCAACTGCTC                    13              1.63468     28140
## r1_GATTGGGAGGCA                    11              1.60181     20352
## r1_CCTCCTAGTTGG                     9              1.35038     19550
## ...                               ...                  ...       ...
## p1_TCAAAAGCCGGG                     4              1.59119       817
## p1_ATTAAGTTCCAA                     5              1.22399       817
## p1_CTGTCTGAGACC                     7              2.94118       816
## p1_TAACGCGCTCCT                     5              3.30882       816
## p1_ATTCTTGTTCTT                     4              1.96078       816

4.2 EDA

High-quality cells should have many features expressed, and a low contribution of mitochondrial genes. Here, we see that several cells have a very low number of expressed genes, and where most of the molecules are derived from mitochondrial genes. This indicates likely damaged cells, presumably because of loss of cytoplasmic RNA from perforated cells, so we’d want to remove these for the downstream analysis.

# Number of genes vs library size
plotColData(sce, x = "sum", y="detected", colour_by="cluster") 

# Mitochondrial genes
plotColData(sce, x = "detected", y="subsets_Mito_percent")

4.3 QC using adaptive thresholds

Below, we remove cells that are outlying with respect to

  1. A low sequencing depth (number of UMIs);
  2. A low number of genes detected;
  3. A high percentage of reads from mitochondrial genes.

We remove a total of \(3423\) cells, most of which because of an outlyingly high percentage of reads from mitochondrial genes.

lowLib <- isOutlier(df$sum, type="lower", log=TRUE)
lowFeatures <- isOutlier(df$detected, type="lower", log=TRUE)
highMito <- isOutlier(df$subsets_Mito_percent, type="higher")

table(lowLib)
## lowLib
## FALSE 
## 49300
table(lowFeatures)
## lowFeatures
## FALSE  TRUE 
## 49287    13
table(highMito)
## highMito
## FALSE  TRUE 
## 45890  3410
discardCells <- (lowLib | lowFeatures | highMito)
table(discardCells)
## discardCells
## FALSE  TRUE 
## 45877  3423
colData(sce)$discardCells <- discardCells

# visualize cells to be removed
plotColData(sce, x = "detected", y="subsets_Mito_percent", colour_by = "discardCells")

4.4 Identifying and removing empty droplets

Note that the removal of cells with low sequencing depth using the adaptive threshold procedure above is a way of removing empty droplets. Other approaches are possible, e.g., removing cells by statistical testing using emtpyDrops. This does require us to specify a lower bound on the total number of UMIs, below which all cells are considered to correspond to empty droplets. This lower bound may not be trivial to derive, but the barcodeRanks function can be useful to identify an elbow/knee point.

library(DropletUtils)
bcrank <- barcodeRanks(counts(sce))

# Only showing unique points for plotting speed.
uniq <- !duplicated(bcrank$rank)
plot(bcrank$rank[uniq], bcrank$total[uniq], log="xy",
    xlab="Rank", ylab="Total UMI count", cex.lab=1.2)

abline(h=metadata(bcrank)$inflection, col="darkgreen", lty=2)
abline(h=metadata(bcrank)$knee, col="dodgerblue", lty=2)
abline(h=350, col="orange", lty=2) # picked visually myself

legend("topright", legend=c("Inflection", "Knee", "Empirical knee point"), 
        col=c("darkgreen", "dodgerblue", "orange"), lty=2, cex=1.2)

set.seed(100)
limit <- 350   
all.out <- emptyDrops(counts(sce), lower=limit, test.ambient=TRUE)
## Warning in smooth.spline(x[new.keep], y[new.keep], df = df, ...): not using
## invalid df; must have 1 < df <= n := #{unique x} = 9
# p-values for cells with total UMI count under the lower bound.
hist(all.out$PValue[all.out$Total <= limit & all.out$Total > 0],
    xlab="P-value", main="", col="grey80")

# but note that it would remove a very high number of cells
length(which(all.out$FDR <= 0.001))
## [1] 25088
# so we stick to the more lenient adaptive filtering strategy
# remove cells identified using adaptive thresholds
sce <- sce[, !colData(sce)$discardCells]

4.5 Identifying and removing doublets

We will use scDblFinder to detect doublet cells.

## perform doublet detection
library(scDblFinder)
set.seed(211103)
colData(sce)$cell.id <- rownames(colData(sce))
sampleID <- unlist(lapply(strsplit(colData(sce)$cell.id, split="_"), "[[", 1))
table(sampleID)
## sampleID
##   p1   r1   r2   r3   r4   r5   r6 
## 3942 5953 8414 5319 7015 7487 7747
sce <- scDblFinder(sce,
                 samples = factor(sampleID))
##   |                                                                              |                                                                      |   0%  |                                                                              |==========                                                            |  14%  |                                                                              |====================                                                  |  29%  |                                                                              |==============================                                        |  43%  |                                                                              |========================================                              |  57%  |                                                                              |==================================================                    |  71%  |                                                                              |============================================================          |  86%  |                                                                              |======================================================================| 100%
table(sce$scDblFinder.class)
## 
## singlet doublet 
##   37092    8785
## visualize these scores
## explore doublet score wrt original cluster labels
boxplot(log1p(sce$scDblFinder.score) ~ factor(colData(sce)$cluster, exclude=NULL))

tab <- table(sce$scDblFinder.class, sce$cluster, 
       exclude=NULL)
tab
##          
##               1     2     3     4     5     6     7     8     9    10    11
##   singlet   212   363   245    63    60   150   231   137   278   153   161
##   doublet     3    14    30     9    14    55    86    12    59    36    46
##          
##              12    13    14    15    16    17    18    19    20    21    22
##   singlet   192    42    75    42   187   286    61   100   306   166   184
##   doublet    62     8    33    30    64    85    19    25    76    81    77
##          
##              23    24    25    26    27    28    29    30    31    32    33
##   singlet   195 22607  1138  1864   424   301   431   469   381   259   612
##   doublet    64  4336   635   297   205   180   150   161   121    59   201
##          
##              34    35    36    37    38    39  <NA>
##   singlet  1386    36    37   230    56    61  2911
##   doublet   178     7     8    10     3     5  1241
t(t(tab) / colSums(tab))
##          
##                    1          2          3          4          5          6
##   singlet 0.98604651 0.96286472 0.89090909 0.87500000 0.81081081 0.73170732
##   doublet 0.01395349 0.03713528 0.10909091 0.12500000 0.18918919 0.26829268
##          
##                    7          8          9         10         11         12
##   singlet 0.72870662 0.91946309 0.82492582 0.80952381 0.77777778 0.75590551
##   doublet 0.27129338 0.08053691 0.17507418 0.19047619 0.22222222 0.24409449
##          
##                   13         14         15         16         17         18
##   singlet 0.84000000 0.69444444 0.58333333 0.74501992 0.77088949 0.76250000
##   doublet 0.16000000 0.30555556 0.41666667 0.25498008 0.22911051 0.23750000
##          
##                   19         20         21         22         23         24
##   singlet 0.80000000 0.80104712 0.67206478 0.70498084 0.75289575 0.83906766
##   doublet 0.20000000 0.19895288 0.32793522 0.29501916 0.24710425 0.16093234
##          
##                   25         26         27         28         29         30
##   singlet 0.64184997 0.86256363 0.67408585 0.62577963 0.74182444 0.74444444
##   doublet 0.35815003 0.13743637 0.32591415 0.37422037 0.25817556 0.25555556
##          
##                   31         32         33         34         35         36
##   singlet 0.75896414 0.81446541 0.75276753 0.88618926 0.83720930 0.82222222
##   doublet 0.24103586 0.18553459 0.24723247 0.11381074 0.16279070 0.17777778
##          
##                   37         38         39       <NA>
##   singlet 0.95833333 0.94915254 0.92424242 0.70110790
##   doublet 0.04166667 0.05084746 0.07575758 0.29889210
barplot(t(t(tab) / colSums(tab))[2,],
        xlab = "Cluster", ylab = "Fraction of doublets")

# remove doublets
sce <- sce[,!sce$scDblFinder.class == "doublet"]

5 Normalization

For normalization, the size factors \(s_i\) computed here are simply scaled library sizes: \[ N_i = \sum_g Y_{gi} \] \[ s_i = N_i / \bar{N}_i \]

sce <- logNormCounts(sce)

# note we also returned log counts: see the additional logcounts assay.
sce
## class: SingleCellExperiment 
## dim: 17887 37092 
## metadata(1): scDblFinder.threshold
## assays(2): counts logcounts
## rownames(17887): KITL TMTC3 ... GM16012 GM21464
## rowData names(0):
## colnames(37092): r1_GGCCGCAGTCCG r1_CTTGTGCGGGAA ... p1_TAACGCGCTCCT
##   p1_ATTCTTGTTCTT
## colData names(15): cluster sum ... scDblFinder.cxds_score sizeFactor
## reducedDimNames(0):
## mainExpName: NULL
## altExpNames(0):
# you can extract size factors using
sf <- librarySizeFactors(sce)
mean(sf) # equal to 1 due to scaling.
## [1] 1
plot(x= log(colSums(assays(sce)$counts)), 
     y=sf)

6 Feature selection

library(scran)
dec <- modelGeneVar(sce)
fitRetina <- metadata(dec)
plot(fitRetina$mean, fitRetina$var, 
     xlab="Mean of log-expression",
    ylab="Variance of log-expression")
curve(fitRetina$trend(x), col="dodgerblue", add=TRUE, lwd=2)

# get 10% highly variable genes
hvg <- getTopHVGs(dec, prop=0.1)
head(hvg)
## [1] "RHO"     "CALM1"   "MEG3"    "RPGRIP1" "GNGT1"   "TRPM1"
# plot these 
plot(fitRetina$mean, fitRetina$var, 
     col = c("orange", "darkseagreen3")[(names(fitRetina$mean) %in% hvg)+1],
     xlab="Mean of log-expression",
    ylab="Variance of log-expression")
curve(fitRetina$trend(x), col="dodgerblue", add=TRUE, lwd=2)
legend("topleft", 
       legend = c("Selected", "Not selected"), 
       col = c("darkseagreen3", "orange"),
       pch = 16,
       bty='n')

7 Dimensionality reduction

Note that, below, we color the cells using the known, true cell type label as defined in the metadata, to empirically evaluate the dimensionality reduction. In reality, we don’t know this yet at this stage.

7.1 The most basic DR

Just by looking at the top two genes based on our feature selection criterion, we can already see some separation according to the cell type!

colData(sce)$cluster <- as.factor(colData(sce)$cluster)
cl <- colData(sce)$cluster

par(bty='l')
plot(x = assays(sce)$counts[hvg[1],],
     y = assays(sce)$counts[hvg[2],],
     col = as.numeric(cl),
     pch = 16, cex = 1/3,
     xlab = "Most informative gene",
     ylab = "Second most informative gene",
     main = "Cells colored acc to cell type")

7.2 Linear dimensionality reduction: PCA

We are able to recover quite some structure. However, many cell populations remain obscure, and the plot is overcrowded.

set.seed(1234)
sce <- runPCA(sce, ncomponents=30, subset_row=hvg)
plotPCA(sce, colour_by = "cluster")
## Warning: Removed 2911 rows containing missing values or values outside the scale range
## (`geom_point()`).

7.2.1 PCA without feature selection

set.seed(1234)
sceNoFS <- runPCA(sce, ncomponents=30, subset_row=1:nrow(sce))
plotPCA(sceNoFS, colour_by = "cluster")
## Warning: Removed 2911 rows containing missing values or values outside the scale range
## (`geom_point()`).

rm(sceNoFS)

7.3 A generalization of PCA for exponential family distributions.

library(glmpca)
set.seed(211103)
poipca <- glmpca(assays(sce)$counts[hvg,],
                 L=2, fam="poi",
                 minibatch="stochastic")
reducedDim(sce, "PoiPCA") <- poipca$factors
plotReducedDim(sce, 
               dimred="PoiPCA",
               colour_by = "cluster")
## Warning: Removed 2911 rows containing missing values or values outside the scale range
## (`geom_point()`).

7.4 Non-linear dimensionality reduction: UMAP

sce <- runUMAP(sce, dimred = 'PCA', external_neighbors=TRUE)
plotUMAP(sce,
         colour_by = "cluster")
## Warning: Removed 2911 rows containing missing values or values outside the scale range
## (`geom_point()`).

8 Clustering

# Build a shared nearest-neighbor graph from PCA space
g <- buildSNNGraph(sce, use.dimred = 'PCA')
# Louvain clustering on the SNN graph, and add to sce
colData(sce)$label <- factor(igraph::cluster_louvain(g)$membership)

# Visualization.
plotUMAP(sce, colour_by="label")

LS0tCnRpdGxlOiAnc2NSTkEtc2VxIHdvcmtmbG93OiBNYWNvc2tvIGV0IGFsLicKYXV0aG9yOiAiS29lbiBWYW4gZGVuIEJlcmdlIgpkYXRlOiAiMTEvMTYvMjAyMCIKb3V0cHV0OiAKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IHRydWUKLS0tCgojIEltcG9ydCBkYXRhCgpUaGUgYHNjUk5Bc2VxYCBwYWNrYWdlIHByb3ZpZGVzIGNvbnZlbmllbnQgYWNjZXNzIHRvIHNldmVyYWwgZGF0YXNldHMuIFNlZSB0aGUgW3BhY2thZ2UgQmlvY29uZHVjdG9yIHBhZ2VdKGh0dHA6Ly9iaW9jb25kdWN0b3Iub3JnL3BhY2thZ2VzL3JlbGVhc2UvZGF0YS9leHBlcmltZW50L2h0bWwvc2NSTkFzZXEuaHRtbCkgZm9yIG1vcmUgaW5mb3JtYXRpb24uCgpgYGB7cn0KIyBpbnN0YWxsIEJpb2NNYW5hZ2VyIHBhY2thZ2UgaWYgbm90IGluc3RhbGxlZCB5ZXQuCiMgQmlvY01hbmFnZXIgaXMgdGhlIHBhY2thZ2UgaW5zdGFsbGVyIGZvciBCaW9jb25kdWN0b3Igc29mdHdhcmUuCmlmICghcmVxdWlyZU5hbWVzcGFjZSgiQmlvY01hbmFnZXIiLCBxdWlldGx5ID0gVFJVRSkpCiAgICBpbnN0YWxsLnBhY2thZ2VzKCJCaW9jTWFuYWdlciIpCgojIGluc3RhbGwgcGFja2FnZXMgaWYgbm90IHlldCBpbnN0YWxsZWQuCnBrZ3MgPC0gYygiU2luZ2xlQ2VsbEV4cGVyaW1lbnQiLCAiRHJvcGxldFV0aWxzIiwgInNjUk5Bc2VxIiwgInNjYXRlciIsICJzY3V0dGxlIiwgInNjcmFuIiwgIkJpb2NTaW5ndWxhciIsICJzY0RibEZpbmRlciIsICJnbG1wY2EiLCAidXdvdCIpCm5vdEluc3RhbGxlZCA8LSBwa2dzWyFwa2dzICVpbiUgaW5zdGFsbGVkLnBhY2thZ2VzKClbLDFdXQppZihsZW5ndGgobm90SW5zdGFsbGVkKSA+IDApewogIEJpb2NNYW5hZ2VyOjppbnN0YWxsKG5vdEluc3RhbGxlZCkKfQoKIyBDb2RlIGJlbG93IG1pZ2h0IGFzayB5b3UgdG8gY3JlYXRlIGFuIEV4cGVyaW1lbnRIdWIgZGlyZWN0b3J5LiAKIyBUeXBlICd5ZXMnIGFuZCBoaXQgRW50ZXIsIHRvIGFsbG93IHRoaXMuCnN1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcyhsaWJyYXJ5KHNjUk5Bc2VxKSkKc2NlIDwtIE1hY29za29SZXRpbmFEYXRhKCkKYGBgCgojIEEgYFNpbmdsZUNlbGxFeHBlcmltZW50YCBvYmplY3QKCgpgYGB7cn0Kc2NlCmBgYAoKIyMgQWNjZXNzaW5nIGRhdGEgZnJvbSBhIGBTaW5nbGVDZWxsRXhwZXJpbWVudGAgb2JqZWN0CgpQbGVhc2Ugc2VlIFtGaWd1cmUgNC4xIGluIE9TQ0FdKGh0dHA6Ly9iaW9jb25kdWN0b3Iub3JnL2Jvb2tzL3JlbGVhc2UvT1NDQS9kYXRhLWluZnJhc3RydWN0dXJlLmh0bWwpIGZvciBhbiBvdmVydmlldyBvZiBhIGBTaW5nbGVDZWxsRXhwZXJpbWVudGAgb2JqZWN0LgoKYGBge3J9CiMgRGF0YTogYXNzYXlzCmFzc2F5cyhzY2UpCmFzc2F5cyhzY2UpJGNvdW50c1sxOjUsIDE6NV0KCiMgRmVhdHVyZSBtZXRhZGF0YTogcm93RGF0YQpyb3dEYXRhKHNjZSkgIyBlbXB0eSBmb3Igbm93CgojIENlbGwgbWV0YWRhdGE6IGNvbERhdGEKY29sRGF0YShzY2UpCgojIFJlZHVjZWQgZGltZW5zaW9uczogcmVkdWNlZERpbXMKcmVkdWNlZERpbXMoc2NlKSAjIGVtcHR5IGZvciBub3cKYGBgCgojIyBDcmVhdGluZyBhIG5ldyBgU2luZ2xlQ2VsbEV4cGVyaW1lbnRgIG9iamVjdAoKYGBge3J9CnNjZU5ldyA8LSBTaW5nbGVDZWxsRXhwZXJpbWVudChhc3NheXMgPSBsaXN0KGNvdW50cyA9IGFzc2F5cyhzY2UpJGNvdW50cykpCnNjZU5ldwoKcm0oc2NlTmV3KQpgYGAKCiMjIFN0b3JpbmcgKG1ldGEpZGF0YSBpbiBhIGBTaW5nbGVDZWxsRXhwZXJpbWVudGAgb2JqZWN0CgpgYGB7cn0KZmFrZUdlbmVOYW1lcyA8LSBwYXN0ZTAoImdlbmUiLCAxOm5yb3coc2NlKSkKcm93RGF0YShzY2UpJGZha2VOYW1lIDwtIGZha2VHZW5lTmFtZXMKaGVhZChyb3dEYXRhKHNjZSkpCiMgUmVtb3ZlIGFnYWluIGJ5IHNldHRpbmcgdG8gTlVMTApyb3dEYXRhKHNjZSkkZmFrZU5hbWUgPC0gTlVMTAoKYXNzYXlzKHNjZSkkbG9nQ291bnRzIDwtIGxvZzFwKGFzc2F5cyhzY2UpJGNvdW50cykKYXNzYXlzKHNjZSkKYXNzYXlzKHNjZSkkbG9nQ291bnRzWzE6NSwgMTo1XQphc3NheXMoc2NlKSRsb2dDb3VudHMgPC0gTlVMTApgYGAKCiMgRmlsdGVyaW5nIG5vbi1pbmZvcm1hdGl2ZSBnZW5lcwoKYGBge3J9CmtlZXAgPC0gcm93U3Vtcyhhc3NheXMoc2NlKSRjb3VudHMgPiAwKSA+IDEwCnRhYmxlKGtlZXApCgpzY2UgPC0gc2NlW2tlZXAsXQpgYGAKCgojIFF1YWxpdHkgY29udHJvbAoKIyMgQ2FsY3VsYXRlIFFDIHZhcmlhYmxlcwoKYGBge3J9CmxpYnJhcnkoc2NhdGVyKQppcy5taXRvIDwtIGdyZXBsKCJeTVQtIiwgcm93bmFtZXMoc2NlKSkKc3VtKGlzLm1pdG8pICMgMjggbWl0b2Nob25kcmlhbCBnZW5lcwpkZiA8LSBwZXJDZWxsUUNNZXRyaWNzKHNjZSwgc3Vic2V0cz1saXN0KE1pdG89aXMubWl0bykpCiMjIGFkZCB0aGUgUUMgdmFyaWFibGVzIHRvIHNjZSBvYmplY3QKY29sRGF0YShzY2UpIDwtIGNiaW5kKGNvbERhdGEoc2NlKSwgZGYpCiMgdGhlIFFDIHZhcmlhYmxlcyBoYXZlIG5vdyBiZWVuIGFkZGVkIHRvIHRoZSBjb2xEYXRhIG9mIG91ciBTQ0Ugb2JqZWN0Lgpjb2xEYXRhKHNjZSkKYGBgCgojIyBFREEKCkhpZ2gtcXVhbGl0eSBjZWxscyBzaG91bGQgaGF2ZSBtYW55IGZlYXR1cmVzIGV4cHJlc3NlZCwgYW5kIGEgbG93IGNvbnRyaWJ1dGlvbiBvZiBtaXRvY2hvbmRyaWFsIGdlbmVzLiBIZXJlLCB3ZSBzZWUgdGhhdCBzZXZlcmFsIGNlbGxzIGhhdmUgYSB2ZXJ5IGxvdyBudW1iZXIgb2YgZXhwcmVzc2VkIGdlbmVzLCBhbmQgd2hlcmUgbW9zdCBvZiB0aGUgbW9sZWN1bGVzIGFyZSBkZXJpdmVkIGZyb20gbWl0b2Nob25kcmlhbCBnZW5lcy4gVGhpcyBpbmRpY2F0ZXMgbGlrZWx5IGRhbWFnZWQgY2VsbHMsIHByZXN1bWFibHkgYmVjYXVzZSBvZiBsb3NzIG9mIGN5dG9wbGFzbWljIFJOQSBmcm9tIHBlcmZvcmF0ZWQgY2VsbHMsIHNvIHdlJ2Qgd2FudCB0byByZW1vdmUgdGhlc2UgZm9yIHRoZSBkb3duc3RyZWFtIGFuYWx5c2lzLgoKYGBge3J9CiMgTnVtYmVyIG9mIGdlbmVzIHZzIGxpYnJhcnkgc2l6ZQpwbG90Q29sRGF0YShzY2UsIHggPSAic3VtIiwgeT0iZGV0ZWN0ZWQiLCBjb2xvdXJfYnk9ImNsdXN0ZXIiKSAKCiMgTWl0b2Nob25kcmlhbCBnZW5lcwpwbG90Q29sRGF0YShzY2UsIHggPSAiZGV0ZWN0ZWQiLCB5PSJzdWJzZXRzX01pdG9fcGVyY2VudCIpCmBgYAoKIyMgUUMgdXNpbmcgYWRhcHRpdmUgdGhyZXNob2xkcwoKQmVsb3csIHdlIHJlbW92ZSBjZWxscyB0aGF0IGFyZSBvdXRseWluZyB3aXRoIHJlc3BlY3QgdG8KCiAxLiBBIGxvdyBzZXF1ZW5jaW5nIGRlcHRoIChudW1iZXIgb2YgVU1Jcyk7CiAyLiBBIGxvdyBudW1iZXIgb2YgZ2VuZXMgZGV0ZWN0ZWQ7CiAzLiBBIGhpZ2ggcGVyY2VudGFnZSBvZiByZWFkcyBmcm9tIG1pdG9jaG9uZHJpYWwgZ2VuZXMuCiAKV2UgcmVtb3ZlIGEgdG90YWwgb2YgJDM0MjMkIGNlbGxzLCBtb3N0IG9mIHdoaWNoIGJlY2F1c2Ugb2YgYW4gb3V0bHlpbmdseSBoaWdoIHBlcmNlbnRhZ2Ugb2YgcmVhZHMgZnJvbSBtaXRvY2hvbmRyaWFsIGdlbmVzLgoKYGBge3J9Cmxvd0xpYiA8LSBpc091dGxpZXIoZGYkc3VtLCB0eXBlPSJsb3dlciIsIGxvZz1UUlVFKQpsb3dGZWF0dXJlcyA8LSBpc091dGxpZXIoZGYkZGV0ZWN0ZWQsIHR5cGU9Imxvd2VyIiwgbG9nPVRSVUUpCmhpZ2hNaXRvIDwtIGlzT3V0bGllcihkZiRzdWJzZXRzX01pdG9fcGVyY2VudCwgdHlwZT0iaGlnaGVyIikKCnRhYmxlKGxvd0xpYikKdGFibGUobG93RmVhdHVyZXMpCnRhYmxlKGhpZ2hNaXRvKQoKZGlzY2FyZENlbGxzIDwtIChsb3dMaWIgfCBsb3dGZWF0dXJlcyB8IGhpZ2hNaXRvKQp0YWJsZShkaXNjYXJkQ2VsbHMpCmNvbERhdGEoc2NlKSRkaXNjYXJkQ2VsbHMgPC0gZGlzY2FyZENlbGxzCgojIHZpc3VhbGl6ZSBjZWxscyB0byBiZSByZW1vdmVkCnBsb3RDb2xEYXRhKHNjZSwgeCA9ICJkZXRlY3RlZCIsIHk9InN1YnNldHNfTWl0b19wZXJjZW50IiwgY29sb3VyX2J5ID0gImRpc2NhcmRDZWxscyIpCgpgYGAKCgojIyBJZGVudGlmeWluZyBhbmQgcmVtb3ZpbmcgZW1wdHkgZHJvcGxldHMKCk5vdGUgdGhhdCB0aGUgcmVtb3ZhbCBvZiBjZWxscyB3aXRoIGxvdyBzZXF1ZW5jaW5nIGRlcHRoIHVzaW5nIHRoZSBhZGFwdGl2ZSB0aHJlc2hvbGQgcHJvY2VkdXJlIGFib3ZlIGlzIGEgd2F5IG9mIHJlbW92aW5nIGVtcHR5IGRyb3BsZXRzLiAKT3RoZXIgYXBwcm9hY2hlcyBhcmUgcG9zc2libGUsIGUuZy4sIHJlbW92aW5nIGNlbGxzIGJ5IHN0YXRpc3RpY2FsIHRlc3RpbmcgdXNpbmcgYGVtdHB5RHJvcHNgLgpUaGlzIGRvZXMgcmVxdWlyZSB1cyB0byBzcGVjaWZ5IGEgbG93ZXIgYm91bmQgb24gdGhlIHRvdGFsIG51bWJlciBvZiBVTUlzLCBiZWxvdyB3aGljaCBhbGwgY2VsbHMgYXJlIGNvbnNpZGVyZWQgdG8gY29ycmVzcG9uZCB0byBlbXB0eSBkcm9wbGV0cy4KVGhpcyBsb3dlciBib3VuZCBtYXkgbm90IGJlIHRyaXZpYWwgdG8gZGVyaXZlLCBidXQgdGhlIGBiYXJjb2RlUmFua3NgIGZ1bmN0aW9uIGNhbiBiZSB1c2VmdWwgdG8gaWRlbnRpZnkgYW4gZWxib3cva25lZSBwb2ludC4KCmBgYHtyfQpsaWJyYXJ5KERyb3BsZXRVdGlscykKYmNyYW5rIDwtIGJhcmNvZGVSYW5rcyhjb3VudHMoc2NlKSkKCiMgT25seSBzaG93aW5nIHVuaXF1ZSBwb2ludHMgZm9yIHBsb3R0aW5nIHNwZWVkLgp1bmlxIDwtICFkdXBsaWNhdGVkKGJjcmFuayRyYW5rKQpwbG90KGJjcmFuayRyYW5rW3VuaXFdLCBiY3JhbmskdG90YWxbdW5pcV0sIGxvZz0ieHkiLAogICAgeGxhYj0iUmFuayIsIHlsYWI9IlRvdGFsIFVNSSBjb3VudCIsIGNleC5sYWI9MS4yKQoKYWJsaW5lKGg9bWV0YWRhdGEoYmNyYW5rKSRpbmZsZWN0aW9uLCBjb2w9ImRhcmtncmVlbiIsIGx0eT0yKQphYmxpbmUoaD1tZXRhZGF0YShiY3JhbmspJGtuZWUsIGNvbD0iZG9kZ2VyYmx1ZSIsIGx0eT0yKQphYmxpbmUoaD0zNTAsIGNvbD0ib3JhbmdlIiwgbHR5PTIpICMgcGlja2VkIHZpc3VhbGx5IG15c2VsZgoKbGVnZW5kKCJ0b3ByaWdodCIsIGxlZ2VuZD1jKCJJbmZsZWN0aW9uIiwgIktuZWUiLCAiRW1waXJpY2FsIGtuZWUgcG9pbnQiKSwgCiAgICAgICAgY29sPWMoImRhcmtncmVlbiIsICJkb2RnZXJibHVlIiwgIm9yYW5nZSIpLCBsdHk9MiwgY2V4PTEuMikKCnNldC5zZWVkKDEwMCkKbGltaXQgPC0gMzUwICAgCmFsbC5vdXQgPC0gZW1wdHlEcm9wcyhjb3VudHMoc2NlKSwgbG93ZXI9bGltaXQsIHRlc3QuYW1iaWVudD1UUlVFKQojIHAtdmFsdWVzIGZvciBjZWxscyB3aXRoIHRvdGFsIFVNSSBjb3VudCB1bmRlciB0aGUgbG93ZXIgYm91bmQuCmhpc3QoYWxsLm91dCRQVmFsdWVbYWxsLm91dCRUb3RhbCA8PSBsaW1pdCAmIGFsbC5vdXQkVG90YWwgPiAwXSwKICAgIHhsYWI9IlAtdmFsdWUiLCBtYWluPSIiLCBjb2w9ImdyZXk4MCIpCgojIGJ1dCBub3RlIHRoYXQgaXQgd291bGQgcmVtb3ZlIGEgdmVyeSBoaWdoIG51bWJlciBvZiBjZWxscwpsZW5ndGgod2hpY2goYWxsLm91dCRGRFIgPD0gMC4wMDEpKQoKIyBzbyB3ZSBzdGljayB0byB0aGUgbW9yZSBsZW5pZW50IGFkYXB0aXZlIGZpbHRlcmluZyBzdHJhdGVneQojIHJlbW92ZSBjZWxscyBpZGVudGlmaWVkIHVzaW5nIGFkYXB0aXZlIHRocmVzaG9sZHMKc2NlIDwtIHNjZVssICFjb2xEYXRhKHNjZSkkZGlzY2FyZENlbGxzXQpgYGAKCiMjIElkZW50aWZ5aW5nIGFuZCByZW1vdmluZyBkb3VibGV0cwoKV2Ugd2lsbCB1c2UgW3NjRGJsRmluZGVyXShodHRwczovL2Jpb2NvbmR1Y3Rvci5vcmcvcGFja2FnZXMvMy4xNC9iaW9jL2h0bWwvc2NEYmxGaW5kZXIuaHRtbCkgdG8gZGV0ZWN0IGRvdWJsZXQgY2VsbHMuCgoKCmBgYHtyfQojIyBwZXJmb3JtIGRvdWJsZXQgZGV0ZWN0aW9uCmxpYnJhcnkoc2NEYmxGaW5kZXIpCnNldC5zZWVkKDIxMTEwMykKY29sRGF0YShzY2UpJGNlbGwuaWQgPC0gcm93bmFtZXMoY29sRGF0YShzY2UpKQpzYW1wbGVJRCA8LSB1bmxpc3QobGFwcGx5KHN0cnNwbGl0KGNvbERhdGEoc2NlKSRjZWxsLmlkLCBzcGxpdD0iXyIpLCAiW1siLCAxKSkKdGFibGUoc2FtcGxlSUQpCnNjZSA8LSBzY0RibEZpbmRlcihzY2UsCiAgICAgICAgICAgICAgICAgc2FtcGxlcyA9IGZhY3RvcihzYW1wbGVJRCkpCnRhYmxlKHNjZSRzY0RibEZpbmRlci5jbGFzcykKCgojIyB2aXN1YWxpemUgdGhlc2Ugc2NvcmVzCiMjIGV4cGxvcmUgZG91YmxldCBzY29yZSB3cnQgb3JpZ2luYWwgY2x1c3RlciBsYWJlbHMKYm94cGxvdChsb2cxcChzY2Ukc2NEYmxGaW5kZXIuc2NvcmUpIH4gZmFjdG9yKGNvbERhdGEoc2NlKSRjbHVzdGVyLCBleGNsdWRlPU5VTEwpKQoKdGFiIDwtIHRhYmxlKHNjZSRzY0RibEZpbmRlci5jbGFzcywgc2NlJGNsdXN0ZXIsIAogICAgICAgZXhjbHVkZT1OVUxMKQp0YWIKdCh0KHRhYikgLyBjb2xTdW1zKHRhYikpCgpiYXJwbG90KHQodCh0YWIpIC8gY29sU3Vtcyh0YWIpKVsyLF0sCiAgICAgICAgeGxhYiA9ICJDbHVzdGVyIiwgeWxhYiA9ICJGcmFjdGlvbiBvZiBkb3VibGV0cyIpCgojIHJlbW92ZSBkb3VibGV0cwpzY2UgPC0gc2NlWywhc2NlJHNjRGJsRmluZGVyLmNsYXNzID09ICJkb3VibGV0Il0KYGBgCgoKIyBOb3JtYWxpemF0aW9uCgpGb3Igbm9ybWFsaXphdGlvbiwgdGhlIHNpemUgZmFjdG9ycyAkc19pJCBjb21wdXRlZCBoZXJlIGFyZSBzaW1wbHkgc2NhbGVkIGxpYnJhcnkgc2l6ZXM6ClxbIE5faSA9IFxzdW1fZyBZX3tnaX0gXF0KXFsgc19pID0gTl9pIC8gXGJhcntOfV9pIFxdCgpgYGB7cn0Kc2NlIDwtIGxvZ05vcm1Db3VudHMoc2NlKQoKIyBub3RlIHdlIGFsc28gcmV0dXJuZWQgbG9nIGNvdW50czogc2VlIHRoZSBhZGRpdGlvbmFsIGxvZ2NvdW50cyBhc3NheS4Kc2NlCgojIHlvdSBjYW4gZXh0cmFjdCBzaXplIGZhY3RvcnMgdXNpbmcKc2YgPC0gbGlicmFyeVNpemVGYWN0b3JzKHNjZSkKbWVhbihzZikgIyBlcXVhbCB0byAxIGR1ZSB0byBzY2FsaW5nLgpwbG90KHg9IGxvZyhjb2xTdW1zKGFzc2F5cyhzY2UpJGNvdW50cykpLCAKICAgICB5PXNmKQpgYGAKCiMgRmVhdHVyZSBzZWxlY3Rpb24KCmBgYHtyfQpsaWJyYXJ5KHNjcmFuKQpkZWMgPC0gbW9kZWxHZW5lVmFyKHNjZSkKZml0UmV0aW5hIDwtIG1ldGFkYXRhKGRlYykKcGxvdChmaXRSZXRpbmEkbWVhbiwgZml0UmV0aW5hJHZhciwgCiAgICAgeGxhYj0iTWVhbiBvZiBsb2ctZXhwcmVzc2lvbiIsCiAgICB5bGFiPSJWYXJpYW5jZSBvZiBsb2ctZXhwcmVzc2lvbiIpCmN1cnZlKGZpdFJldGluYSR0cmVuZCh4KSwgY29sPSJkb2RnZXJibHVlIiwgYWRkPVRSVUUsIGx3ZD0yKQoKIyBnZXQgMTAlIGhpZ2hseSB2YXJpYWJsZSBnZW5lcwpodmcgPC0gZ2V0VG9wSFZHcyhkZWMsIHByb3A9MC4xKQpoZWFkKGh2ZykKCiMgcGxvdCB0aGVzZSAKcGxvdChmaXRSZXRpbmEkbWVhbiwgZml0UmV0aW5hJHZhciwgCiAgICAgY29sID0gYygib3JhbmdlIiwgImRhcmtzZWFncmVlbjMiKVsobmFtZXMoZml0UmV0aW5hJG1lYW4pICVpbiUgaHZnKSsxXSwKICAgICB4bGFiPSJNZWFuIG9mIGxvZy1leHByZXNzaW9uIiwKICAgIHlsYWI9IlZhcmlhbmNlIG9mIGxvZy1leHByZXNzaW9uIikKY3VydmUoZml0UmV0aW5hJHRyZW5kKHgpLCBjb2w9ImRvZGdlcmJsdWUiLCBhZGQ9VFJVRSwgbHdkPTIpCmxlZ2VuZCgidG9wbGVmdCIsIAogICAgICAgbGVnZW5kID0gYygiU2VsZWN0ZWQiLCAiTm90IHNlbGVjdGVkIiksIAogICAgICAgY29sID0gYygiZGFya3NlYWdyZWVuMyIsICJvcmFuZ2UiKSwKICAgICAgIHBjaCA9IDE2LAogICAgICAgYnR5PSduJykKYGBgCgojIERpbWVuc2lvbmFsaXR5IHJlZHVjdGlvbgoKTm90ZSB0aGF0LCBiZWxvdywgd2UgY29sb3IgdGhlIGNlbGxzIHVzaW5nIHRoZSBrbm93biwgdHJ1ZSBjZWxsIHR5cGUgbGFiZWwgYXMgZGVmaW5lZCBpbiB0aGUgbWV0YWRhdGEsIHRvIGVtcGlyaWNhbGx5IGV2YWx1YXRlIHRoZSBkaW1lbnNpb25hbGl0eSByZWR1Y3Rpb24uIEluIHJlYWxpdHksIHdlIGRvbid0IGtub3cgdGhpcyB5ZXQgYXQgdGhpcyBzdGFnZS4KCiMjIFRoZSBtb3N0IGJhc2ljIERSCgpKdXN0IGJ5IGxvb2tpbmcgYXQgdGhlIHRvcCB0d28gZ2VuZXMgYmFzZWQgb24gb3VyIGZlYXR1cmUgc2VsZWN0aW9uIGNyaXRlcmlvbiwgd2UgY2FuIGFscmVhZHkgc2VlIHNvbWUgc2VwYXJhdGlvbiBhY2NvcmRpbmcgdG8gdGhlIGNlbGwgdHlwZSEKCmBgYHtyfQpjb2xEYXRhKHNjZSkkY2x1c3RlciA8LSBhcy5mYWN0b3IoY29sRGF0YShzY2UpJGNsdXN0ZXIpCmNsIDwtIGNvbERhdGEoc2NlKSRjbHVzdGVyCgpwYXIoYnR5PSdsJykKcGxvdCh4ID0gYXNzYXlzKHNjZSkkY291bnRzW2h2Z1sxXSxdLAogICAgIHkgPSBhc3NheXMoc2NlKSRjb3VudHNbaHZnWzJdLF0sCiAgICAgY29sID0gYXMubnVtZXJpYyhjbCksCiAgICAgcGNoID0gMTYsIGNleCA9IDEvMywKICAgICB4bGFiID0gIk1vc3QgaW5mb3JtYXRpdmUgZ2VuZSIsCiAgICAgeWxhYiA9ICJTZWNvbmQgbW9zdCBpbmZvcm1hdGl2ZSBnZW5lIiwKICAgICBtYWluID0gIkNlbGxzIGNvbG9yZWQgYWNjIHRvIGNlbGwgdHlwZSIpCmBgYAoKIyMgTGluZWFyIGRpbWVuc2lvbmFsaXR5IHJlZHVjdGlvbjogUENBCgpXZSBhcmUgYWJsZSB0byByZWNvdmVyIHF1aXRlIHNvbWUgc3RydWN0dXJlLiAKSG93ZXZlciwgbWFueSBjZWxsIHBvcHVsYXRpb25zIHJlbWFpbiBvYnNjdXJlLCBhbmQgdGhlIHBsb3QgaXMgb3ZlcmNyb3dkZWQuCgpgYGB7cn0Kc2V0LnNlZWQoMTIzNCkKc2NlIDwtIHJ1blBDQShzY2UsIG5jb21wb25lbnRzPTMwLCBzdWJzZXRfcm93PWh2ZykKcGxvdFBDQShzY2UsIGNvbG91cl9ieSA9ICJjbHVzdGVyIikKYGBgCgojIyMgUENBIHdpdGhvdXQgZmVhdHVyZSBzZWxlY3Rpb24KCmBgYHtyfQpzZXQuc2VlZCgxMjM0KQpzY2VOb0ZTIDwtIHJ1blBDQShzY2UsIG5jb21wb25lbnRzPTMwLCBzdWJzZXRfcm93PTE6bnJvdyhzY2UpKQpwbG90UENBKHNjZU5vRlMsIGNvbG91cl9ieSA9ICJjbHVzdGVyIikKcm0oc2NlTm9GUykKYGBgCgoKIyMgQSBnZW5lcmFsaXphdGlvbiBvZiBQQ0EgZm9yIGV4cG9uZW50aWFsIGZhbWlseSBkaXN0cmlidXRpb25zLgoKYGBge3IsIGV2YWw9VFJVRX0KbGlicmFyeShnbG1wY2EpCnNldC5zZWVkKDIxMTEwMykKcG9pcGNhIDwtIGdsbXBjYShhc3NheXMoc2NlKSRjb3VudHNbaHZnLF0sCiAgICAgICAgICAgICAgICAgTD0yLCBmYW09InBvaSIsCiAgICAgICAgICAgICAgICAgbWluaWJhdGNoPSJzdG9jaGFzdGljIikKcmVkdWNlZERpbShzY2UsICJQb2lQQ0EiKSA8LSBwb2lwY2EkZmFjdG9ycwpwbG90UmVkdWNlZERpbShzY2UsIAogICAgICAgICAgICAgICBkaW1yZWQ9IlBvaVBDQSIsCiAgICAgICAgICAgICAgIGNvbG91cl9ieSA9ICJjbHVzdGVyIikKYGBgCgoKCiMjIE5vbi1saW5lYXIgZGltZW5zaW9uYWxpdHkgcmVkdWN0aW9uOiBVTUFQCgpgYGB7cn0Kc2NlIDwtIHJ1blVNQVAoc2NlLCBkaW1yZWQgPSAnUENBJywgZXh0ZXJuYWxfbmVpZ2hib3JzPVRSVUUpCnBsb3RVTUFQKHNjZSwKICAgICAgICAgY29sb3VyX2J5ID0gImNsdXN0ZXIiKQpgYGAKCiMgQ2x1c3RlcmluZwoKYGBge3J9CiMgQnVpbGQgYSBzaGFyZWQgbmVhcmVzdC1uZWlnaGJvciBncmFwaCBmcm9tIFBDQSBzcGFjZQpnIDwtIGJ1aWxkU05OR3JhcGgoc2NlLCB1c2UuZGltcmVkID0gJ1BDQScpCiMgTG91dmFpbiBjbHVzdGVyaW5nIG9uIHRoZSBTTk4gZ3JhcGgsIGFuZCBhZGQgdG8gc2NlCmNvbERhdGEoc2NlKSRsYWJlbCA8LSBmYWN0b3IoaWdyYXBoOjpjbHVzdGVyX2xvdXZhaW4oZykkbWVtYmVyc2hpcCkKCiMgVmlzdWFsaXphdGlvbi4KcGxvdFVNQVAoc2NlLCBjb2xvdXJfYnk9ImxhYmVsIikKYGBgCg==