3. Read the abundance table (taxa)
The abundance file contains the ASV numbers as rows and sample names
as columns. The abundance table contains 123 samples and 1866 ASVs.
taxa <- read.table(file = "feature_table_final_rarefied.tsv", sep = "\t", header = TRUE, check.names=F)
taxa
head(taxa[, 125], 20)
colnames(taxa)
head(rownames(taxa), 20)
length(rownames(taxa))
dim(taxa)
4. Read the taxonomy table (taxonomic classification)
The taxonomy file contains the ASV identifiers as rows and taxonomic
ranks as columns, from Kingdom to Genus. The ASV_table file was
extracted from the rarefied abundance table.
taxanames <- read.table(file = "ASV_table.csv", sep = ",", header = TRUE, check.names=F)
asv_table <- taxanames
asv_table
colnames(asv_table)
df <- separate(asv_table, taxonomy, into = c("Kingdom", "Phylum", "Class", "Order","Family","Genus"), sep = ";", convert = TRUE)
colnames(df)
df[1:50,1:7]
class(df)
fill_value <- "NA"
df$Family[df$Family == ""] <- fill_value
df$Kingdom[df$Kingdom == ""] <- fill_value
df$Phylum[df$Phylum == ""] <- fill_value
df$Class[df$Class == ""] <- fill_value
df$Order[df$Order == ""] <- fill_value
df$Genus[df$Genus == ""] <- fill_value
df[1:50,1:7]
df$Genus <- df$Genus %>% replace_na('NA')
df$Family <- df$Family %>% replace_na('NA')
df$Class <- df$Class %>% replace_na('NA')
df$Order <- df$Order %>% replace_na('NA')
df$Phylum <- df$Phylum %>% replace_na('NA')
df$Kingdom <- df$Kingdom %>% replace_na('NA')
df[1:50,1:7]
old_char <- "NA"
new_char <- "Unclassified"
df_replaced <- data.frame(lapply(df, function(x) gsub(old_char, new_char, x)))
df_replaced
df_replaced[1:50,1:7]
asv_table2 <- df_replaced
asv_table2
Create “ASV_#” and “ASV_#_Genus” identifiers
listnumber <- asv_table$FEATURE_ID
head(listnumber,20)
#Pay attention: This line defines a function called modify_vector_elements that takes two arguments: vec (the input vector) and ASV (the Additional String Vector).
#ASV_Additional String Vector
modify_vector_elements <- function(vec, ASV) {
modified_vec <- paste0(ASV, vec)
return(modified_vec)
}
FEATURE_ID2 <- modify_vector_elements(listnumber, "ASV_")
head(FEATURE_ID2,20)
length(FEATURE_ID2)
length(listnumber)
row.names(asv_table2) <- FEATURE_ID2
asv_table2
asv_table2 <- asv_table2[,-1]
colnames(asv_table2)
asv_table2$ASV <- rownames(asv_table2)
colnames(asv_table2)
asv_table2$ASV_Genus <- paste(asv_table2$ASV, asv_table2$Genus, sep = "_")
colnames(asv_table2)
head(asv_table2$ASV_Genus,20)
asv_table2$ASV_Genus <- gsub("g__", "", asv_table2$ASV_Genus)
colnames(asv_table2)
head(asv_table2$ASV_Genus,20)
listnumber2 <- asv_table2$ASV_Genus
head(listnumber2,20)
5. Object summary and characteristics
From the summary of abundance table, the value of 0.5359 corresponds
to the mean, i.e., 1000 counts / 1866 detected ASVs.
is.object(metadataarticle2)
is.object(taxa)
is.object(asv_table2)
class(metadataarticle2)
class(taxa)
class(asv_table2)
summary(metadataarticle2)
head(row.names(metadataarticle2),20)
head(colnames(metadataarticle2),20)
dim(taxa)
summary(taxa)
length(unique(taxa$FEATURE_ID))
max(taxa$FEATURE_ID)
min(taxa$FEATURE_ID)
head(row.names(taxa),20)
head(colnames(taxa),20)
summary(asv_table2)
head(row.names(asv_table2),20)
head(colnames(asv_table2),20)
Change column names in taxa to match row names of metadata (before
selecting samples) and change ASV identifiers using ASV_#_Genus.
taxa
colnames(taxa)
head(taxa$FEATURE_ID,25)
head(taxa$taxonomy,25)
taxa$ASV_Genus <- listnumber2
head(taxa$ASV_Genus,25)
head(row.names(taxa),20)
row.names(taxa) <- taxa$ASV_Genus
colnames(taxa)
taxa <- taxa[,-1]
colnames(taxa)
taxa <- taxa[,-124]
taxa <- taxa[,-124]
colnames(taxa)
taxa[1:10,1:10]
#Change column names of taxa based on row names of metadata. Distinctions between Hyphen or Dash, and Underscores!
colnames(taxa)
dim(taxa)
rownames(metadata)
dim(metadata)
colnames(taxa) <- rownames(metadata)
colnames(taxa)
#the identical() function in R does check both the values and their order.
identical(colnames(taxa), rownames(metadata))
6. Sample selection for heatmap construction
To construct the heatmap, we select metadataarticle3 including the
metabolic phases: Elong_H2CO2_Auto, Homoacetogenesis_Auto,
Homoacetogenesis_Mixo, Elong_H2CO2_Mixo, Elong_EtOH&Lactate, and
Acidogenesis.
If methanogenesis included, make reference to metadataarticle4.
columns_to_keep <- rownames(metadataarticle3)
columns_to_keep
columns_to_keep2 <- rownames(metadataarticle4)
columns_to_keep2
# Subset the dataframe to include only the specified columns
taxa_subset <- taxa[, columns_to_keep]
taxa_subset
dim(taxa_subset)
taxa_subset2 <- taxa[, columns_to_keep2]
taxa_subset2
dim(taxa_subset2)
taxa_subset2[1:20,1:20]
head(rowSums(taxa),20)
head(rowSums(taxa_subset),20)
head(rowSums(taxa_subset2),20)
length(rowSums(taxa))
length(rowSums(taxa_subset))
length(rowSums(taxa_subset2))
In taxa file, select ASVs with a relative abundance of 0 in all
samples.
asvs_zero_sum <- rownames(taxa)[rowSums(taxa) == 0]
length(asvs_zero_sum)
asvs_zero_sum2 <- rownames(taxa_subset)[rowSums(taxa_subset) == 0]
length(asvs_zero_sum2)
asvs_zero_sum2
asvs_zero_sum3 <- rownames(taxa_subset2)[rowSums(taxa_subset2) == 0]
length(asvs_zero_sum3)
In taxa file, select ASVs with a relative abundance greather than 0
in all samples. 635 ASVs detected in Elong_H2CO2_Auto,
Homoacetogenesis_Auto, Homoacetogenesis_Mixo, Elong_H2CO2_Mixo,
Elong_EtOH&Lactate, and Acidogenesis.
asvs_zero_sum4 <- rownames(taxa_subset)[rowSums(taxa_subset) != 0]
length(asvs_zero_sum4)
asvs_zero_sum4
Select ASVs greater than zero in taxa_subset
taxa_subset
asvs_zero_sum4
class(taxa_subset)
class(asvs_zero_sum4)
selected_rows <- taxa_subset[rownames(taxa_subset) %in% asvs_zero_sum4, ]
selected_rows
dim(selected_rows)
summary(selected_rows)
asv_counts_per_sample <- apply(selected_rows, 2, function(x) sum(x > 0))
asv_counts_per_sample
If you want to determine how many times an ASV is detected in
selected samples, you can use the R expression function(x) sum(x >
0). This defines an anonymous function that counts the number of
elements in x that are greater than 0.
asv_detection_counts <- apply(selected_rows, 1, function(x) sum(x > 0))
asv_detection_counts
asv_detection_counts_sorted <- sort(asv_detection_counts, decreasing = TRUE)
asv_detection_counts_sorted
7. Annotations and color palettes for heatmap
construction
#Define annotations
df <- metadataarticle3[,c("Main_Metabolic_Phase","Metabolism_Carbon","Reactor")]
colnames(df)
df
class(df)
df2 <- metadataarticle4[,c("Main_Metabolic_Phase","Metabolism_Carbon","Reactor")]
colnames(df2)
#Color palette
mypal5 <- colorRampPalette(rev(brewer.pal(n = 7, name = "RdYlBu")))(100)
mypal5
length(mypal5)
# Visualize the palette as a horizontal strip
image(1:100, 1, as.matrix(1:100), col = mypal5, axes = FALSE, xlab = "", ylab = "")
annotation_colors <- list(
Reactor = c(R1 = "#b1b1b1", R2 = "#7F1734"),
Metabolism_Carbon = c(Autotrophic = "#1e7dff", Mixotrophic = "#ff7b00"),
Main_Metabolic_Phase = c(
Acidogenesis = "#30d5c8",
`Elong_EtOH&Lactate` = "#ffd700",
`Elong_H2CO2_Auto` = "#ff6cda",
`Elong_H2CO2_Mixo` = "#9a009a",
Homoacetogenesis_Auto = "#008100",
Homoacetogenesis_Mixo = "#93c571" )
)
annotation_colors2 <- list(
Reactor = c(R1 = "#b1b1b1", R2 = "#7F1734"),
Metabolism_Carbon = c(Autotrophic = "#1e7dff", Mixotrophic = "#ff7b00"),
Main_Metabolic_Phase = c(
Acidogenesis = "#30d5c8",
`Elong_EtOH&Lactate` = "#ffd700",
`Elong_H2CO2_Auto` = "#ff6cda",
`Elong_H2CO2_Mixo` = "#9a009a",
Homoacetogenesis_Auto = "#008100",
Homoacetogenesis_Mixo = "#93c571",
Methanogenesis = "blue")
)
annotation_colors3 <- list(
Reactor = c(R1 = "#b1b1b1", R2 = "#7F1734"),
Metabolism_Carbon = c(Autotrophic = "#1e7dff", Mixotrophic = "#ff7b00"),
Main_Metabolic_Phase = c(
Acidogenesis = "#30d5c8",
`Elong_EtOH&Lactate` = "#ffd700",
`Elong_H2CO2_Auto` = "#ff6cda",
`Elong_H2CO2_Mixo` = "#9a009a",
Homoacetogenesis_Auto = "#008100",
Homoacetogenesis_Mixo = "#93c571" ),
Phylum = c(
"p__Actinobacteriota" = "#ff0000",
"p__Bacteroidota" = "#000000",
"p__Firmicutes" = "#ffff00")
)
To add row annotations in the heatmap.
asv_annotation <- asv_table2[, c("ASV_Genus", "Phylum")]
rownames(asv_annotation) <- asv_annotation$ASV_Genus
head(asv_annotation)
asv_annotation <- asv_annotation[, "Phylum", drop = FALSE]
head(asv_annotation)
unique(asv_annotation$Phylum)
identical(row.names(asv_annotation),row.names(taxa_subset))
class(asv_annotation)
taxatop25f
list <- row.names(taxatop25f)
list
asv_annotation
rownames(asv_annotation)
dim(asv_annotation)
asv_annotation_subset <- asv_annotation[rownames(asv_annotation) %in% rownames(taxatop25f), , drop = FALSE]
asv_annotation_subset
class(asv_annotation_subset)
8. Select the TOP25/30 taxa in all selected samples
taxa_subset
taxa_subsett <- t(taxa_subset)
taxa_subsett_decr <- taxa_subsett[,order(colSums(taxa_subsett),decreasing=TRUE)]
rownames(taxa_subsett_decr)
colnames(taxa_subsett_decr)
top25list <- colnames(taxa_subsett_decr)[1:25]
top25list
taxatop25 <- data.frame(taxa_subsett_decr[,colnames(taxa_subsett_decr) %in% top25list], check.names = FALSE)
taxatop25
taxatop25f <- t(taxatop25)
taxatop25f
colnames(taxatop25f)
colSums(taxatop25f)
rowSums(taxatop25f)
write.csv(taxatop25f, "taxatop25f.csv", row.names = TRUE)
w <- taxatop25f+1
write.csv(w, "taxatop25fwithone.csv", row.names = TRUE)
taxa_subset2
taxa_subset2t <- t(taxa_subset2)
taxa_subset2t_decr <- taxa_subset2t[,order(colSums(taxa_subset2t),decreasing=TRUE)]
rownames(taxa_subset2t_decr)
colnames(taxa_subset2t_decr)
top30listm <- colnames(taxa_subset2t_decr)[1:30]
top30listm
taxatop30m <- data.frame(taxa_subset2t_decr[,colnames(taxa_subset2t_decr) %in% top30listm], check.names = FALSE)
taxatop30m
taxatop30mf <- t(taxatop30m)
taxatop30mf
colSums(taxatop30mf)
9. Data transformation and heatmap construction
Add 1 to your rarefied counts before log transformation: log(x + 1)
is a standard preprocessing step.
taxatop25nozero <- taxatop25f+1
write.csv(taxatop25nozero, "taxatop25fnozero_2.csv", row.names = TRUE)
range(taxatop25nozero, na.rm = TRUE)
Euclidean distances are used by default in pheatmap, for both rows
and columns (clustering_distance_rows = “euclidean”;
clustering_distance_cols = “euclidean”)
9.1. Log2 transformation
LOG2(1) = 0 LOG2(0) undefined
datlog2 <- log2(taxatop25f+1)
range(datlog2, na.rm = TRUE)
summary(as.vector(datlog2))
str(datlog2)
is.matrix(datlog2)
is.numeric(datlog2)
This command uses the pheatmap package in R to generate a heatmap and
stores the result (including dendrogram information) in the object
plog2_average. pheatmap() uses here average linkage (UPGMA) for
hierarchical clustering of rows and columns. cutree_cols = 7 cuts the
column dendrogram into 7 clusters. This means: Columns (samples) will be
grouped into 7 clusters, and these clusters are shown with colored bars
based on defined annotations. cutree_rows = 10 cuts the row dendrogram
into 10 clusters. This groups the taxa (rows) into 10 clusters, and
helps visually identify groups of similar features.
tiff("plog2_average_2.tiff", width = 16, height = 8, units = "in", res = 300)
plog2_average <- pheatmap(datlog2, mypal5, clustering_method="average", breaks = NA, scale = "none", fontsize=10, fontsize_row=8, fontsize_col=8, annotation_col=df, annotation_colors = annotation_colors, cutree_cols = 7, cutree_rows = 10)
dev.off()
tiff("plog2_average_2_rows.tiff", width = 16, height = 8, units = "in", res = 300)
plog2_average <- pheatmap(datlog2, mypal5, clustering_method="average", breaks = NA, scale = "none", fontsize=10, fontsize_row=8, fontsize_col=8, annotation_col=df, annotation_row=asv_annotation_subset, annotation_colors = annotation_colors3, cutree_cols = 7, cutree_rows = 10)
dev.off()
Define scale of color palette.
my_breaks <- seq(0, 10, by = 0.5)
mypal6 <- colorRampPalette(RColorBrewer::brewer.pal(9, "YlGnBu"))(length(my_breaks) - 1)
mypal6 <- colorRampPalette(rev(brewer.pal(n = 7, name = "RdYlBu")))(20)
mypal6
length(mypal5)
length(mypal6)
str(my_breaks)
is.numeric(my_breaks)
all(is.finite(my_breaks))
length(mypal5) == length(my_breaks) - 1
print(length(mypal5))
tiff("plog2_average_2_scalecolor.tiff", width = 16, height = 8, units = "in", res = 300)
plog2_average <- pheatmap(mat = datlog2, color = mypal6, clustering_method="average", breaks = my_breaks, scale = "none", fontsize=10, fontsize_row=8, fontsize_col=8, annotation_col=df, annotation_colors = annotation_colors, cutree_cols = 7, cutree_rows = 10)
dev.off()
9.3. clr transformation
clr (Centered Log-Ratio) transformation from the package
compositions. clr uses the geometric mean of all components (in
microbiome data, each component in a compositional vector corresponds to
a specific ASV or OTU).
taxatop25f
row.names(taxatop25f)
colnames(taxatop25f)
taxatop25f2 <- taxatop25f+1
write.csv(taxatop25f2, "taxatop25fplusone.csv", row.names = TRUE)
datclr <- clr(acomp(taxatop25f+1))
range(datclr, na.rm = TRUE)
summary(as.vector(datclr))
class(datclr)
is.matrix(datclr)
is.numeric(datclr)
datclr_mat <- as.matrix(datclr)
datclr_mat
write.csv(datclr_mat, "datclr_mat.csv", row.names = TRUE)
tiff("pclr_average_2.tiff", width = 16, height = 8, units = "in", res = 300)
pclr_average <- pheatmap(datclr, mypal5, clustering_method="complete", breaks = NA, scale = "none", fontsize=10, fontsize_row=8, fontsize_col=8, annotation_col=df, annotation_colors = annotation_colors, cutree_cols = 7, cutree_rows = 10)
dev.off()
tiff("pclr_average_2_mat.tiff", width = 16, height = 8, units = "in", res = 300)
pclr_average <- pheatmap(datclr_mat, mypal5, clustering_method="complete", breaks = NA, scale = "none", fontsize=10, fontsize_row=8, fontsize_col=8, annotation_col=df, annotation_colors = annotation_colors, cutree_cols = 7, cutree_rows = 10)
dev.off()
LS0tDQp0aXRsZTogJ01pY3JvYmlvbWUgYW5hbHlzaXM6IGNsdXN0ZXJlZCBoZWF0bWFwcycNCmF1dGhvcjogIkIuIFN0ZW51aXQsIFVDTG91dmFpbiwgRWFydGggJiBMaWZlIEluc3RpdHV0ZSwgTG91dmFpbi1sYS1OZXV2ZSINCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCltWaWV3IHNvdXJjZSBvbiBHaXRIdWJdKGh0dHBzOi8vZ2l0aHViLmNvbS9CZW5TdGVudWl0L0hlYXRtYXBfQ2hhaW5fZWxvbmcpDQoNCg0KIyAxLiA8dT5JbnRyb2R1Y3Rpb248L3U+DQoNClRoaXMgaXMgYW4gW1IgTWFya2Rvd25dKGh0dHA6Ly9ybWFya2Rvd24ucnN0dWRpby5jb20pIE5vdGVib29rLiBUaGUgbWFpbiBwYWNrYWdlIHVzZWQgdG8gZ2VuZXJhdGUgdGhlIGhlYXRtYXAgaXMgJ3BoZWF0bWFwJyBpbiBSU3R1ZGlvLiBVc2UgdXBkYXRlUigpIGlmIG5lZWRlZCBmcm9tIHRoZSAnaW5zdGFsbHInIHBhY2thZ2UuDQoNCmBgYHtyfQ0KdmVyc2lvbg0KZ2V0UnZlcnNpb24oKQ0Kb3B0aW9ucyhyZXBvcyA9IGMoQ1JBTiA9ICJodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZyIpKQ0KaW5zdGFsbC5wYWNrYWdlcygiaW5zdGFsbHIiKQ0KbGlicmFyeShpbnN0YWxscikNCmluc3RhbGwucGFja2FnZXMoInJzdHVkaW9hcGkiKQ0KbGlicmFyeShyc3R1ZGlvYXBpKQ0KcnN0dWRpb192ZXJzaW9uIDwtIHJzdHVkaW9hcGk6OnZlcnNpb25JbmZvKCkkdmVyc2lvbg0KY2F0KCJSU3R1ZGlvIHZlcnNpb24gdXNlZDoiLCBhcy5jaGFyYWN0ZXIocnN0dWRpb192ZXJzaW9uKSwgIlxuIikNCmBgYA0KDQojIyAxLjEuIExvYWQgdGhlIG5lY2Vzc2FyeSBwYWNrYWdlcw0KDQpgYGB7cn0NCmluc3RhbGwucGFja2FnZXMoInBoZWF0bWFwIikNCmxpYnJhcnkocGhlYXRtYXApDQppbnN0YWxsLnBhY2thZ2VzKCJleHRyYWZvbnQiKQ0KbGlicmFyeShleHRyYWZvbnQpDQpmb250X2ltcG9ydChwYXR0ZXJuPSJbQS9hXXJpYWwiKQ0KaW5zdGFsbC5wYWNrYWdlcygidGlkeXZlcnNlIikgI2luY2x1ZGUgZ2dwbG90MiwgdGlkeXIgYW5kIGRwbHlyDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmluc3RhbGwucGFja2FnZXMoIlJDb2xvckJyZXdlciIpDQpsaWJyYXJ5KFJDb2xvckJyZXdlcikNCmluc3RhbGwucGFja2FnZXMoImNvbXBvc2l0aW9ucyIpDQpsaWJyYXJ5KGNvbXBvc2l0aW9ucykNCmBgYA0KDQojIyAxLjIuIFJlZmVyZW5jZXMNCg0KYGBge3J9DQpjaXRhdGlvbigpDQp2ZXJzaW9uJHZlcnNpb24uc3RyaW5nDQpjaXRhdGlvbigicGhlYXRtYXAiKQ0KcGFja2FnZVZlcnNpb24oInBoZWF0bWFwIikNCiNodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvcGhlYXRtYXAvDQpgYGANCg0KIyMgMS4zLiBTZXQgYW5kIGdldCB3b3JraW5nIGRpcmVjdG9yeQ0KDQpCeSBkZWZhdWx0LCB0aGUgd29ya2luZyBkaXJlY3RvcnkgZm9yIFIgY29kZSBjaHVua3MgaXMgdGhlIGRpcmVjdG9yeSB0aGF0IGNvbnRhaW5zIHRoZSBSbWQgZG9jdW1lbnQuDQoNCmBgYHtyfQ0KZ2V0d2QoKQ0KYGBgDQoNCg0KIyAyLiA8dT5NZXRhZGF0YTwvdT4NCg0KVGhlIG1ldGFkYXRhIGZpbGUgY29udGFpbnMgdGhlIHNhbXBsZSBuYW1lcyBhcyByb3dzIGFuZCBzYW1wbGUgZmVhdHVyZXMgYXMgY29sdW1ucy4NCg0KIyMgMi4xLiBMb2FkIHRoZSBtZXRhZGF0YSBmaWxlDQoNClJlbmFtZSBmdW5jdGlvbiBmcm9tIGRwbHlyIHBhY2thZ2UuDQoNCmBgYHtyfQ0KbWV0YWRhdGEgPC0gcmVhZC5jc3YoIkdIMjJfY29tYmluZWRfTWV0YWRhdGFfd2l0aHNpbXBsZV9GaW5ldHVuZWRfUHJvZHVjdGl2aXR5NC5jc3YiLCBjaGVjay5uYW1lcyA9IEZBTFNFKQ0KY29sbmFtZXMobWV0YWRhdGEpDQpyb3duYW1lcyhtZXRhZGF0YSkNCm1ldGFkYXRhDQptZXRhZGF0YSA8LSBtZXRhZGF0YSAlPiUgcmVuYW1lKFNhbXBsZV9OYW1lID0gTmFtZSkNCm1ldGFkYXRhIDwtIG1ldGFkYXRhICU+JSByZW5hbWUoTWV0YWJvbGlzbSA9IEZlcm1lbnRhdGlvbl9waGFzZV9maW5ldHVuZWQpDQpjb2xuYW1lcyhtZXRhZGF0YSkNCmxpc3RtZXQgPC0gbWV0YWRhdGEkTWV0YWJvbGlzbQ0KbGlzdG1ldA0KbGlzdG1ldHVuaXF1ZSA8LSB1bmlxdWUobGlzdG1ldCkNCmxpc3RtZXR1bmlxdWUNCmBgYA0KDQpTcGVjaWZ5IHJvdyBuYW1lcyBhbmQgc2VsZWN0IHNhbXBsZXMgaWYgcmVxdWlyZWQuDQpGb3IgdGhlIGFydGljbGUgIiIsIHdlIHNlbGVjdCBzYW1wbGVzIGZyb20gcmVhY3RvcnMgIzEgYW5kICMyLCBCU0cgc2FtcGxlcyBhbmQgaW5vY3VsdW0gc2FtcGxlcyAoMTAyIHNhbXBsZXMsIHdpdGhvdXQgUjMgYW5kIFI0IHJlYWN0b3JzKS4NCg0KYGBge3J9DQpyb3cubmFtZXMobWV0YWRhdGEpIDwtIG1ldGFkYXRhJFNhbXBsZV9OYW1lDQptZXRhZGF0YWFydGljbGUgPC0gbWV0YWRhdGEgJT4lIGZpbHRlcihncmVwbCgnUjJ8UjF8LycsIFJlYWN0b3IpKQ0KbWV0YWRhdGFhcnRpY2xlWzE6MTAsMToxMF0NCmxpc3RhcnRpY2xlIDwtIG1ldGFkYXRhYXJ0aWNsZSRTYW1wbGVfTmFtZQ0KbGlzdGFydGljbGUNCmxpc3RtZXRhYm9saXNtIDwtIG1ldGFkYXRhYXJ0aWNsZSRNZXRhYm9saXNtDQpsaXN0bWV0YWJvbGlzbQ0KdW5pcXVlKGxpc3RtZXRhYm9saXNtKQ0KY29sbmFtZXMobWV0YWRhdGFhcnRpY2xlKQ0KYGBgDQoNCkFkZCBtZXRhYm9saWMgc3RhZ2VzIGZvciBob21vYWNldG9nZW5lc2lzIGFuZCBFbG9uZ19IMkNPMiAoYXV0by4gdnMuIG1peG8uKSBpbiBhIG5ldyBjb2x1bW4gIk1haW5fTWV0YWJvbGljX1BoYXNlIg0KDQpgYGB7cn0NCm1ldGFkYXRhYXJ0aWNsZSRNYWluX01ldGFib2xpY19QaGFzZSA8LSBsaXN0bWV0YWJvbGlzbQ0KbWV0YWRhdGFhcnRpY2xlJE1haW5fTWV0YWJvbGljX1BoYXNlDQpjb2xuYW1lcyhtZXRhZGF0YWFydGljbGUpDQoNCm9sZF9uYW1lIDwtICJIb21vYWNldG9nZW5lc2lzIg0KIyBTcGVjaWZ5IHRoZSBuZXcgbmFtZQ0KbmV3X25hbWUgPC0gIkhvbW9hY2V0b2dlbmVzaXNfTWl4byINCg0KaWYgKGFueShtZXRhZGF0YWFydGljbGUkTWFpbl9NZXRhYm9saWNfUGhhc2UgPT0gb2xkX25hbWUgJiBtZXRhZGF0YWFydGljbGUkTWV0YWJvbGlzbV9DYXJib24gPT0gIk1peG90cm9waGljIikpIHsNCiAgbWV0YWRhdGFhcnRpY2xlJE1haW5fTWV0YWJvbGljX1BoYXNlW21ldGFkYXRhYXJ0aWNsZSRNYWluX01ldGFib2xpY19QaGFzZSA9PSBvbGRfbmFtZSAmIG1ldGFkYXRhYXJ0aWNsZSRNZXRhYm9saXNtX0NhcmJvbiA9PSAiTWl4b3Ryb3BoaWMiXSA8LSBuZXdfbmFtZQ0KfQ0KDQptZXRhZGF0YWFydGljbGUkTWFpbl9NZXRhYm9saWNfUGhhc2UNCg0Kb2xkX25hbWUgPC0gIkhvbW9hY2V0b2dlbmVzaXMiDQojIFNwZWNpZnkgdGhlIG5ldyBuYW1lDQpuZXdfbmFtZSA8LSAiSG9tb2FjZXRvZ2VuZXNpc19BdXRvIg0KDQppZiAoYW55KG1ldGFkYXRhYXJ0aWNsZSRNYWluX01ldGFib2xpY19QaGFzZSA9PSBvbGRfbmFtZSAmIG1ldGFkYXRhYXJ0aWNsZSRNZXRhYm9saXNtX0NhcmJvbiA9PSAiQXV0b3Ryb3BoaWMiKSkgew0KICBtZXRhZGF0YWFydGljbGUkTWFpbl9NZXRhYm9saWNfUGhhc2VbbWV0YWRhdGFhcnRpY2xlJE1haW5fTWV0YWJvbGljX1BoYXNlID09IG9sZF9uYW1lICYgbWV0YWRhdGFhcnRpY2xlJE1ldGFib2xpc21fQ2FyYm9uID09ICJBdXRvdHJvcGhpYyJdIDwtIG5ld19uYW1lDQp9DQoNCm1ldGFkYXRhYXJ0aWNsZSRNYWluX01ldGFib2xpY19QaGFzZQ0KDQpvbGRfbmFtZSA8LSAiRWxvbmdfSDJDTzIiDQojIFNwZWNpZnkgdGhlIG5ldyBuYW1lDQpuZXdfbmFtZSA8LSAiRWxvbmdfSDJDTzJfTWl4byINCg0KaWYgKGFueShtZXRhZGF0YWFydGljbGUkTWFpbl9NZXRhYm9saWNfUGhhc2UgPT0gb2xkX25hbWUgJiBtZXRhZGF0YWFydGljbGUkTWV0YWJvbGlzbV9DYXJib24gPT0gIk1peG90cm9waGljIikpIHsNCiAgbWV0YWRhdGFhcnRpY2xlJE1haW5fTWV0YWJvbGljX1BoYXNlW21ldGFkYXRhYXJ0aWNsZSRNYWluX01ldGFib2xpY19QaGFzZSA9PSBvbGRfbmFtZSAmIG1ldGFkYXRhYXJ0aWNsZSRNZXRhYm9saXNtX0NhcmJvbiA9PSAiTWl4b3Ryb3BoaWMiXSA8LSBuZXdfbmFtZQ0KfQ0KDQptZXRhZGF0YWFydGljbGUkTWFpbl9NZXRhYm9saWNfUGhhc2UNCg0Kb2xkX25hbWUgPC0gIkVsb25nX0gyQ08yIg0KIyBTcGVjaWZ5IHRoZSBuZXcgbmFtZQ0KbmV3X25hbWUgPC0gIkVsb25nX0gyQ08yX0F1dG8iDQoNCmlmIChhbnkobWV0YWRhdGFhcnRpY2xlJE1haW5fTWV0YWJvbGljX1BoYXNlID09IG9sZF9uYW1lICYgbWV0YWRhdGFhcnRpY2xlJE1ldGFib2xpc21fQ2FyYm9uID09ICJBdXRvdHJvcGhpYyIpKSB7DQogIG1ldGFkYXRhYXJ0aWNsZSRNYWluX01ldGFib2xpY19QaGFzZVttZXRhZGF0YWFydGljbGUkTWFpbl9NZXRhYm9saWNfUGhhc2UgPT0gb2xkX25hbWUgJiBtZXRhZGF0YWFydGljbGUkTWV0YWJvbGlzbV9DYXJib24gPT0gIkF1dG90cm9waGljIl0gPC0gbmV3X25hbWUNCn0NCg0KbWV0YWRhdGFhcnRpY2xlJE1haW5fTWV0YWJvbGljX1BoYXNlDQp1bmlxdWUobWV0YWRhdGFhcnRpY2xlJE1haW5fTWV0YWJvbGljX1BoYXNlKQ0KdW5pcXVlKGxpc3RtZXRhYm9saXNtKQ0KYGBgDQoNCiMjIDIuMi4gTWV0YWRhdGEgZmlsZSBjbGVhbmluZw0KDQpgYGB7cn0NCmNvbG5hbWVzKG1ldGFkYXRhYXJ0aWNsZSkNCm1ldGFkYXRhYXJ0aWNsZTIgPC0gbWV0YWRhdGFhcnRpY2xlWywgIWNvbG5hbWVzKG1ldGFkYXRhYXJ0aWNsZSkgJWluJSBjKCJTYW1wbGUiLCJSZXZlcnNlX3ByaW1lciIsIkJhcmNvZGVfaTciLCJJbmRleCBpNyBMZXV2ZW4iLCJFeHBlcmltZW50IiwiUmVwbGljYXRlIiwiTmFtZSIsIlNhbXBsZV9JRCIpXQ0KY29sbmFtZXMobWV0YWRhdGFhcnRpY2xlMikNCm1ldGFkYXRhYXJ0aWNsZTJbMToxMCwxOjEwXQ0KbWV0YWRhdGFhcnRpY2xlMiRGZXJtZW50YXRpb25fcGhhc2Vfc2ltcGxpZmllZA0KbWV0YWRhdGFhcnRpY2xlMiRNZXRhYm9saXNtDQp1bmlxdWUobWV0YWRhdGFhcnRpY2xlMiRGZXJtZW50YXRpb25fcGhhc2Vfc2ltcGxpZmllZCkNCnVuaXF1ZShtZXRhZGF0YWFydGljbGUyJE1ldGFib2xpc20pDQp1bmlxdWUobWV0YWRhdGFhcnRpY2xlMiRNZXRhYm9saXNtX0NhcmJvbikNCnVuaXF1ZShtZXRhZGF0YWFydGljbGUyJE1haW5fTWV0YWJvbGljX1BoYXNlKQ0KYGBgDQoNCiMjIDIuMy4gTWV0YWRhdGEgZmlsZSBzdWJzZXR0aW5nDQoNCldpdGhvdXQgbGFnIHBoYXNlcywgYW5kIHRoZSBpbm9jdWx1bS9CU0cgc2FtcGxlcw0KDQpgYGB7cn0NCm1ldGFkYXRhYXJ0aWNsZTMgPC0gbWV0YWRhdGFhcnRpY2xlMiAlPiUgZmlsdGVyKGdyZXBsKCdFbG9uZ19IMkNPMl9BdXRvfEhvbW9hY2V0b2dlbmVzaXNfQXV0b3xIb21vYWNldG9nZW5lc2lzX01peG98RWxvbmdfSDJDTzJfTWl4b3xFbG9uZ19FdE9IJkxhY3RhdGV8QWNpZG9nZW5lc2lzJywgTWFpbl9NZXRhYm9saWNfUGhhc2UpKQ0Kcm93bmFtZXMobWV0YWRhdGFhcnRpY2xlMykNCg0KI1dpdGggTWV0aGFub2dlbnMNCm1ldGFkYXRhYXJ0aWNsZTQgPC0gbWV0YWRhdGFhcnRpY2xlMiAlPiUgZmlsdGVyKGdyZXBsKCdFbG9uZ19IMkNPMl9BdXRvfEhvbW9hY2V0b2dlbmVzaXNfQXV0b3xIb21vYWNldG9nZW5lc2lzX01peG98RWxvbmdfSDJDTzJfTWl4b3xFbG9uZ19FdE9IJkxhY3RhdGV8QWNpZG9nZW5lc2lzfE1ldGhhbm9nZW5lc2lzJywgTWFpbl9NZXRhYm9saWNfUGhhc2UpKQ0Kcm93bmFtZXMobWV0YWRhdGFhcnRpY2xlNCkNCmBgYA0KDQoNCiMgMy4gPHU+UmVhZCB0aGUgYWJ1bmRhbmNlIHRhYmxlICh0YXhhKTwvdT4NCg0KVGhlIGFidW5kYW5jZSBmaWxlIGNvbnRhaW5zIHRoZSBBU1YgbnVtYmVycyBhcyByb3dzIGFuZCBzYW1wbGUgbmFtZXMgYXMgY29sdW1ucy4NClRoZSBhYnVuZGFuY2UgdGFibGUgY29udGFpbnMgMTIzIHNhbXBsZXMgYW5kIDE4NjYgQVNWcy4NCg0KYGBge3J9DQp0YXhhIDwtIHJlYWQudGFibGUoZmlsZSA9ICJmZWF0dXJlX3RhYmxlX2ZpbmFsX3JhcmVmaWVkLnRzdiIsIHNlcCA9ICJcdCIsIGhlYWRlciA9IFRSVUUsIGNoZWNrLm5hbWVzPUYpDQp0YXhhDQpoZWFkKHRheGFbLCAxMjVdLCAyMCkNCmNvbG5hbWVzKHRheGEpDQpoZWFkKHJvd25hbWVzKHRheGEpLCAyMCkNCmxlbmd0aChyb3duYW1lcyh0YXhhKSkNCmRpbSh0YXhhKQ0KYGBgDQoNCg0KIyMgNC4gPHU+UmVhZCB0aGUgdGF4b25vbXkgdGFibGUgKHRheG9ub21pYyBjbGFzc2lmaWNhdGlvbik8L3U+DQoNClRoZSB0YXhvbm9teSBmaWxlIGNvbnRhaW5zIHRoZSBBU1YgaWRlbnRpZmllcnMgYXMgcm93cyBhbmQgdGF4b25vbWljIHJhbmtzIGFzIGNvbHVtbnMsIGZyb20gS2luZ2RvbSB0byBHZW51cy4gVGhlIEFTVl90YWJsZSBmaWxlIHdhcyBleHRyYWN0ZWQgZnJvbSB0aGUgcmFyZWZpZWQgYWJ1bmRhbmNlIHRhYmxlLg0KDQpgYGB7cn0NCnRheGFuYW1lcyA8LSByZWFkLnRhYmxlKGZpbGUgPSAiQVNWX3RhYmxlLmNzdiIsIHNlcCA9ICIsIiwgaGVhZGVyID0gVFJVRSwgY2hlY2submFtZXM9RikNCmFzdl90YWJsZSA8LSB0YXhhbmFtZXMNCmFzdl90YWJsZQ0KY29sbmFtZXMoYXN2X3RhYmxlKQ0KZGYgPC0gc2VwYXJhdGUoYXN2X3RhYmxlLCB0YXhvbm9teSwgaW50byA9IGMoIktpbmdkb20iLCAiUGh5bHVtIiwgIkNsYXNzIiwgIk9yZGVyIiwiRmFtaWx5IiwiR2VudXMiKSwgc2VwID0gIjsiLCBjb252ZXJ0ID0gVFJVRSkNCmNvbG5hbWVzKGRmKQ0KZGZbMTo1MCwxOjddDQpjbGFzcyhkZikNCmZpbGxfdmFsdWUgPC0gIk5BIg0KZGYkRmFtaWx5W2RmJEZhbWlseSA9PSAiIl0gPC0gZmlsbF92YWx1ZQ0KZGYkS2luZ2RvbVtkZiRLaW5nZG9tID09ICIiXSA8LSBmaWxsX3ZhbHVlDQpkZiRQaHlsdW1bZGYkUGh5bHVtID09ICIiXSA8LSBmaWxsX3ZhbHVlDQpkZiRDbGFzc1tkZiRDbGFzcyA9PSAiIl0gPC0gZmlsbF92YWx1ZQ0KZGYkT3JkZXJbZGYkT3JkZXIgPT0gIiJdIDwtIGZpbGxfdmFsdWUNCmRmJEdlbnVzW2RmJEdlbnVzID09ICIiXSA8LSBmaWxsX3ZhbHVlDQpkZlsxOjUwLDE6N10NCmRmJEdlbnVzIDwtIGRmJEdlbnVzICU+JSByZXBsYWNlX25hKCdOQScpDQpkZiRGYW1pbHkgPC0gZGYkRmFtaWx5ICU+JSByZXBsYWNlX25hKCdOQScpDQpkZiRDbGFzcyA8LSBkZiRDbGFzcyAlPiUgcmVwbGFjZV9uYSgnTkEnKQ0KZGYkT3JkZXIgPC0gZGYkT3JkZXIgJT4lIHJlcGxhY2VfbmEoJ05BJykNCmRmJFBoeWx1bSA8LSBkZiRQaHlsdW0gJT4lIHJlcGxhY2VfbmEoJ05BJykNCmRmJEtpbmdkb20gPC0gZGYkS2luZ2RvbSAlPiUgcmVwbGFjZV9uYSgnTkEnKQ0KZGZbMTo1MCwxOjddDQpvbGRfY2hhciA8LSAiTkEiDQpuZXdfY2hhciA8LSAiVW5jbGFzc2lmaWVkIg0KZGZfcmVwbGFjZWQgPC0gZGF0YS5mcmFtZShsYXBwbHkoZGYsIGZ1bmN0aW9uKHgpIGdzdWIob2xkX2NoYXIsIG5ld19jaGFyLCB4KSkpDQpkZl9yZXBsYWNlZA0KZGZfcmVwbGFjZWRbMTo1MCwxOjddDQphc3ZfdGFibGUyIDwtIGRmX3JlcGxhY2VkDQphc3ZfdGFibGUyDQpgYGANCg0KQ3JlYXRlICJBU1ZfIyIgYW5kICJBU1ZfI19HZW51cyIgaWRlbnRpZmllcnMNCg0KYGBge3J9DQpsaXN0bnVtYmVyIDwtIGFzdl90YWJsZSRGRUFUVVJFX0lEDQpoZWFkKGxpc3RudW1iZXIsMjApDQojUGF5IGF0dGVudGlvbjogVGhpcyBsaW5lIGRlZmluZXMgYSBmdW5jdGlvbiBjYWxsZWQgbW9kaWZ5X3ZlY3Rvcl9lbGVtZW50cyB0aGF0IHRha2VzIHR3byBhcmd1bWVudHM6IHZlYyAodGhlIGlucHV0IHZlY3RvcikgYW5kIEFTViAodGhlIEFkZGl0aW9uYWwgU3RyaW5nIFZlY3RvcikuDQojQVNWX0FkZGl0aW9uYWwgU3RyaW5nIFZlY3Rvcg0KbW9kaWZ5X3ZlY3Rvcl9lbGVtZW50cyA8LSBmdW5jdGlvbih2ZWMsIEFTVikgew0KICBtb2RpZmllZF92ZWMgPC0gcGFzdGUwKEFTViwgdmVjKQ0KICByZXR1cm4obW9kaWZpZWRfdmVjKQ0KfQ0KRkVBVFVSRV9JRDIgPC0gbW9kaWZ5X3ZlY3Rvcl9lbGVtZW50cyhsaXN0bnVtYmVyLCAiQVNWXyIpDQpoZWFkKEZFQVRVUkVfSUQyLDIwKQ0KDQpsZW5ndGgoRkVBVFVSRV9JRDIpDQpsZW5ndGgobGlzdG51bWJlcikNCg0Kcm93Lm5hbWVzKGFzdl90YWJsZTIpIDwtIEZFQVRVUkVfSUQyDQphc3ZfdGFibGUyDQphc3ZfdGFibGUyIDwtIGFzdl90YWJsZTJbLC0xXQ0KY29sbmFtZXMoYXN2X3RhYmxlMikNCmFzdl90YWJsZTIkQVNWIDwtIHJvd25hbWVzKGFzdl90YWJsZTIpDQpjb2xuYW1lcyhhc3ZfdGFibGUyKQ0KYXN2X3RhYmxlMiRBU1ZfR2VudXMgPC0gcGFzdGUoYXN2X3RhYmxlMiRBU1YsIGFzdl90YWJsZTIkR2VudXMsIHNlcCA9ICJfIikNCmNvbG5hbWVzKGFzdl90YWJsZTIpDQpoZWFkKGFzdl90YWJsZTIkQVNWX0dlbnVzLDIwKQ0KYXN2X3RhYmxlMiRBU1ZfR2VudXMgPC0gZ3N1YigiZ19fIiwgIiIsIGFzdl90YWJsZTIkQVNWX0dlbnVzKQ0KY29sbmFtZXMoYXN2X3RhYmxlMikNCmhlYWQoYXN2X3RhYmxlMiRBU1ZfR2VudXMsMjApDQoNCmxpc3RudW1iZXIyIDwtIGFzdl90YWJsZTIkQVNWX0dlbnVzDQpoZWFkKGxpc3RudW1iZXIyLDIwKQ0KYGBgDQoNCg0KIyMgNS4gPHU+T2JqZWN0IHN1bW1hcnkgYW5kIGNoYXJhY3RlcmlzdGljczwvdT4NCg0KRnJvbSB0aGUgc3VtbWFyeSBvZiBhYnVuZGFuY2UgdGFibGUsIHRoZSB2YWx1ZSBvZiAwLjUzNTkgY29ycmVzcG9uZHMgdG8gdGhlIG1lYW4sIGkuZS4sIDEwMDAgY291bnRzIC8gMTg2NiBkZXRlY3RlZCBBU1ZzLg0KDQpgYGB7cn0NCmlzLm9iamVjdChtZXRhZGF0YWFydGljbGUyKQ0KaXMub2JqZWN0KHRheGEpDQppcy5vYmplY3QoYXN2X3RhYmxlMikNCg0KY2xhc3MobWV0YWRhdGFhcnRpY2xlMikNCmNsYXNzKHRheGEpDQpjbGFzcyhhc3ZfdGFibGUyKQ0KDQpzdW1tYXJ5KG1ldGFkYXRhYXJ0aWNsZTIpDQpoZWFkKHJvdy5uYW1lcyhtZXRhZGF0YWFydGljbGUyKSwyMCkNCmhlYWQoY29sbmFtZXMobWV0YWRhdGFhcnRpY2xlMiksMjApDQpkaW0odGF4YSkNCnN1bW1hcnkodGF4YSkNCmxlbmd0aCh1bmlxdWUodGF4YSRGRUFUVVJFX0lEKSkNCm1heCh0YXhhJEZFQVRVUkVfSUQpDQptaW4odGF4YSRGRUFUVVJFX0lEKQ0KaGVhZChyb3cubmFtZXModGF4YSksMjApDQpoZWFkKGNvbG5hbWVzKHRheGEpLDIwKQ0Kc3VtbWFyeShhc3ZfdGFibGUyKQ0KaGVhZChyb3cubmFtZXMoYXN2X3RhYmxlMiksMjApDQpoZWFkKGNvbG5hbWVzKGFzdl90YWJsZTIpLDIwKQ0KYGBgDQoNCkNoYW5nZSBjb2x1bW4gbmFtZXMgaW4gdGF4YSB0byBtYXRjaCByb3cgbmFtZXMgb2YgbWV0YWRhdGEgKGJlZm9yZSBzZWxlY3Rpbmcgc2FtcGxlcykgYW5kIGNoYW5nZSBBU1YgaWRlbnRpZmllcnMgdXNpbmcgQVNWXyNfR2VudXMuDQoNCmBgYHtyfQ0KdGF4YQ0KY29sbmFtZXModGF4YSkNCmhlYWQodGF4YSRGRUFUVVJFX0lELDI1KQ0KaGVhZCh0YXhhJHRheG9ub215LDI1KQ0KdGF4YSRBU1ZfR2VudXMgPC0gbGlzdG51bWJlcjINCmhlYWQodGF4YSRBU1ZfR2VudXMsMjUpDQpoZWFkKHJvdy5uYW1lcyh0YXhhKSwyMCkNCnJvdy5uYW1lcyh0YXhhKSA8LSB0YXhhJEFTVl9HZW51cw0KY29sbmFtZXModGF4YSkNCnRheGEgPC0gdGF4YVssLTFdDQpjb2xuYW1lcyh0YXhhKQ0KdGF4YSA8LSB0YXhhWywtMTI0XQ0KdGF4YSA8LSB0YXhhWywtMTI0XQ0KY29sbmFtZXModGF4YSkNCnRheGFbMToxMCwxOjEwXQ0KDQojQ2hhbmdlIGNvbHVtbiBuYW1lcyBvZiB0YXhhIGJhc2VkIG9uIHJvdyBuYW1lcyBvZiBtZXRhZGF0YS4gRGlzdGluY3Rpb25zIGJldHdlZW4gSHlwaGVuIG9yIERhc2gsIGFuZCBVbmRlcnNjb3JlcyEgDQpjb2xuYW1lcyh0YXhhKQ0KZGltKHRheGEpDQpyb3duYW1lcyhtZXRhZGF0YSkNCmRpbShtZXRhZGF0YSkNCg0KY29sbmFtZXModGF4YSkgPC0gcm93bmFtZXMobWV0YWRhdGEpDQpjb2xuYW1lcyh0YXhhKQ0KDQojdGhlIGlkZW50aWNhbCgpIGZ1bmN0aW9uIGluIFIgZG9lcyBjaGVjayBib3RoIHRoZSB2YWx1ZXMgYW5kIHRoZWlyIG9yZGVyLg0KaWRlbnRpY2FsKGNvbG5hbWVzKHRheGEpLCByb3duYW1lcyhtZXRhZGF0YSkpDQpgYGANCg0KDQojIyA2LiA8dT5TYW1wbGUgc2VsZWN0aW9uIGZvciBoZWF0bWFwIGNvbnN0cnVjdGlvbjwvdT4NCg0KVG8gY29uc3RydWN0IHRoZSBoZWF0bWFwLCB3ZSBzZWxlY3QgbWV0YWRhdGFhcnRpY2xlMyBpbmNsdWRpbmcgdGhlIG1ldGFib2xpYyBwaGFzZXM6IEVsb25nX0gyQ08yX0F1dG8sIEhvbW9hY2V0b2dlbmVzaXNfQXV0bywgSG9tb2FjZXRvZ2VuZXNpc19NaXhvLCBFbG9uZ19IMkNPMl9NaXhvLCBFbG9uZ19FdE9IJkxhY3RhdGUsIGFuZCBBY2lkb2dlbmVzaXMuDQoNCklmIG1ldGhhbm9nZW5lc2lzIGluY2x1ZGVkLCBtYWtlIHJlZmVyZW5jZSB0byBtZXRhZGF0YWFydGljbGU0Lg0KDQpgYGB7cn0NCmNvbHVtbnNfdG9fa2VlcCA8LSByb3duYW1lcyhtZXRhZGF0YWFydGljbGUzKQ0KY29sdW1uc190b19rZWVwDQoNCmNvbHVtbnNfdG9fa2VlcDIgPC0gcm93bmFtZXMobWV0YWRhdGFhcnRpY2xlNCkNCmNvbHVtbnNfdG9fa2VlcDINCg0KIyBTdWJzZXQgdGhlIGRhdGFmcmFtZSB0byBpbmNsdWRlIG9ubHkgdGhlIHNwZWNpZmllZCBjb2x1bW5zDQp0YXhhX3N1YnNldCA8LSB0YXhhWywgY29sdW1uc190b19rZWVwXQ0KdGF4YV9zdWJzZXQNCmRpbSh0YXhhX3N1YnNldCkNCg0KdGF4YV9zdWJzZXQyIDwtIHRheGFbLCBjb2x1bW5zX3RvX2tlZXAyXQ0KdGF4YV9zdWJzZXQyDQpkaW0odGF4YV9zdWJzZXQyKQ0KdGF4YV9zdWJzZXQyWzE6MjAsMToyMF0NCg0KaGVhZChyb3dTdW1zKHRheGEpLDIwKQ0KaGVhZChyb3dTdW1zKHRheGFfc3Vic2V0KSwyMCkNCmhlYWQocm93U3Vtcyh0YXhhX3N1YnNldDIpLDIwKQ0KDQpsZW5ndGgocm93U3Vtcyh0YXhhKSkNCmxlbmd0aChyb3dTdW1zKHRheGFfc3Vic2V0KSkNCmxlbmd0aChyb3dTdW1zKHRheGFfc3Vic2V0MikpDQpgYGANCg0KSW4gdGF4YSBmaWxlLCBzZWxlY3QgQVNWcyB3aXRoIGEgcmVsYXRpdmUgYWJ1bmRhbmNlIG9mIDAgaW4gYWxsIHNhbXBsZXMuDQoNCmBgYHtyfQ0KYXN2c196ZXJvX3N1bSA8LSByb3duYW1lcyh0YXhhKVtyb3dTdW1zKHRheGEpID09IDBdDQpsZW5ndGgoYXN2c196ZXJvX3N1bSkNCg0KYXN2c196ZXJvX3N1bTIgPC0gcm93bmFtZXModGF4YV9zdWJzZXQpW3Jvd1N1bXModGF4YV9zdWJzZXQpID09IDBdDQpsZW5ndGgoYXN2c196ZXJvX3N1bTIpDQphc3ZzX3plcm9fc3VtMg0KDQphc3ZzX3plcm9fc3VtMyA8LSByb3duYW1lcyh0YXhhX3N1YnNldDIpW3Jvd1N1bXModGF4YV9zdWJzZXQyKSA9PSAwXQ0KbGVuZ3RoKGFzdnNfemVyb19zdW0zKQ0KYGBgDQoNCkluIHRheGEgZmlsZSwgc2VsZWN0IEFTVnMgd2l0aCBhIHJlbGF0aXZlIGFidW5kYW5jZSBncmVhdGhlciB0aGFuIDAgaW4gYWxsIHNhbXBsZXMuDQo2MzUgQVNWcyBkZXRlY3RlZCBpbiBFbG9uZ19IMkNPMl9BdXRvLCBIb21vYWNldG9nZW5lc2lzX0F1dG8sIEhvbW9hY2V0b2dlbmVzaXNfTWl4bywgRWxvbmdfSDJDTzJfTWl4bywgRWxvbmdfRXRPSCZMYWN0YXRlLCBhbmQgQWNpZG9nZW5lc2lzLg0KDQpgYGB7cn0NCmFzdnNfemVyb19zdW00IDwtIHJvd25hbWVzKHRheGFfc3Vic2V0KVtyb3dTdW1zKHRheGFfc3Vic2V0KSAhPSAwXQ0KbGVuZ3RoKGFzdnNfemVyb19zdW00KQ0KYXN2c196ZXJvX3N1bTQNCmBgYA0KDQpTZWxlY3QgQVNWcyBncmVhdGVyIHRoYW4gemVybyBpbiB0YXhhX3N1YnNldA0KDQpgYGB7cn0NCnRheGFfc3Vic2V0DQphc3ZzX3plcm9fc3VtNA0KY2xhc3ModGF4YV9zdWJzZXQpDQpjbGFzcyhhc3ZzX3plcm9fc3VtNCkNCg0Kc2VsZWN0ZWRfcm93cyA8LSB0YXhhX3N1YnNldFtyb3duYW1lcyh0YXhhX3N1YnNldCkgJWluJSBhc3ZzX3plcm9fc3VtNCwgXQ0Kc2VsZWN0ZWRfcm93cw0KZGltKHNlbGVjdGVkX3Jvd3MpDQoNCnN1bW1hcnkoc2VsZWN0ZWRfcm93cykNCg0KYXN2X2NvdW50c19wZXJfc2FtcGxlIDwtIGFwcGx5KHNlbGVjdGVkX3Jvd3MsIDIsIGZ1bmN0aW9uKHgpIHN1bSh4ID4gMCkpDQphc3ZfY291bnRzX3Blcl9zYW1wbGUNCmBgYA0KDQpJZiB5b3Ugd2FudCB0byBkZXRlcm1pbmUgaG93IG1hbnkgdGltZXMgYW4gQVNWIGlzIGRldGVjdGVkIGluIHNlbGVjdGVkIHNhbXBsZXMsIHlvdSBjYW4gdXNlIHRoZSBSIGV4cHJlc3Npb24gZnVuY3Rpb24oeCkgc3VtKHggPiAwKS4gVGhpcyBkZWZpbmVzIGFuIGFub255bW91cyBmdW5jdGlvbiB0aGF0IGNvdW50cyB0aGUgbnVtYmVyIG9mIGVsZW1lbnRzIGluIHggdGhhdCBhcmUgZ3JlYXRlciB0aGFuIDAuDQoNCmBgYHtyfQ0KYXN2X2RldGVjdGlvbl9jb3VudHMgPC0gYXBwbHkoc2VsZWN0ZWRfcm93cywgMSwgZnVuY3Rpb24oeCkgc3VtKHggPiAwKSkNCmFzdl9kZXRlY3Rpb25fY291bnRzDQphc3ZfZGV0ZWN0aW9uX2NvdW50c19zb3J0ZWQgPC0gc29ydChhc3ZfZGV0ZWN0aW9uX2NvdW50cywgZGVjcmVhc2luZyA9IFRSVUUpDQphc3ZfZGV0ZWN0aW9uX2NvdW50c19zb3J0ZWQNCmBgYA0KDQojIyA3LiA8dT5Bbm5vdGF0aW9ucyBhbmQgY29sb3IgcGFsZXR0ZXMgZm9yIGhlYXRtYXAgY29uc3RydWN0aW9uPC91Pg0KDQpgYGB7cn0NCiNEZWZpbmUgYW5ub3RhdGlvbnMNCmRmIDwtIG1ldGFkYXRhYXJ0aWNsZTNbLGMoIk1haW5fTWV0YWJvbGljX1BoYXNlIiwiTWV0YWJvbGlzbV9DYXJib24iLCJSZWFjdG9yIildDQpjb2xuYW1lcyhkZikNCmRmDQpjbGFzcyhkZikNCg0KZGYyIDwtIG1ldGFkYXRhYXJ0aWNsZTRbLGMoIk1haW5fTWV0YWJvbGljX1BoYXNlIiwiTWV0YWJvbGlzbV9DYXJib24iLCJSZWFjdG9yIildDQpjb2xuYW1lcyhkZjIpDQoNCiNDb2xvciBwYWxldHRlDQpteXBhbDUgPC0gY29sb3JSYW1wUGFsZXR0ZShyZXYoYnJld2VyLnBhbChuID0gNywgbmFtZSA9ICJSZFlsQnUiKSkpKDEwMCkNCm15cGFsNQ0KDQpsZW5ndGgobXlwYWw1KQ0KDQojIFZpc3VhbGl6ZSB0aGUgcGFsZXR0ZSBhcyBhIGhvcml6b250YWwgc3RyaXANCmltYWdlKDE6MTAwLCAxLCBhcy5tYXRyaXgoMToxMDApLCBjb2wgPSBteXBhbDUsIGF4ZXMgPSBGQUxTRSwgeGxhYiA9ICIiLCB5bGFiID0gIiIpDQoNCmFubm90YXRpb25fY29sb3JzIDwtIGxpc3QoDQogIFJlYWN0b3IgPSBjKFIxID0gIiNiMWIxYjEiLCBSMiA9ICIjN0YxNzM0IiksDQogIE1ldGFib2xpc21fQ2FyYm9uID0gYyhBdXRvdHJvcGhpYyA9ICIjMWU3ZGZmIiwgTWl4b3Ryb3BoaWMgPSAiI2ZmN2IwMCIpLA0KICBNYWluX01ldGFib2xpY19QaGFzZSA9IGMoDQogICAgQWNpZG9nZW5lc2lzID0gIiMzMGQ1YzgiLCANCiAgICBgRWxvbmdfRXRPSCZMYWN0YXRlYCA9ICIjZmZkNzAwIiwgDQogICAgYEVsb25nX0gyQ08yX0F1dG9gID0gIiNmZjZjZGEiLCANCiAgICBgRWxvbmdfSDJDTzJfTWl4b2AgPSAiIzlhMDA5YSIsIA0KICAgIEhvbW9hY2V0b2dlbmVzaXNfQXV0byA9ICIjMDA4MTAwIiwgDQogICAgSG9tb2FjZXRvZ2VuZXNpc19NaXhvID0gIiM5M2M1NzEiICApDQopDQoNCmFubm90YXRpb25fY29sb3JzMiA8LSBsaXN0KA0KICBSZWFjdG9yID0gYyhSMSA9ICIjYjFiMWIxIiwgUjIgPSAiIzdGMTczNCIpLA0KICBNZXRhYm9saXNtX0NhcmJvbiA9IGMoQXV0b3Ryb3BoaWMgPSAiIzFlN2RmZiIsIE1peG90cm9waGljID0gIiNmZjdiMDAiKSwNCiAgTWFpbl9NZXRhYm9saWNfUGhhc2UgPSBjKA0KICAgIEFjaWRvZ2VuZXNpcyA9ICIjMzBkNWM4IiwgDQogICAgYEVsb25nX0V0T0gmTGFjdGF0ZWAgPSAiI2ZmZDcwMCIsIA0KICAgIGBFbG9uZ19IMkNPMl9BdXRvYCA9ICIjZmY2Y2RhIiwgDQogICAgYEVsb25nX0gyQ08yX01peG9gID0gIiM5YTAwOWEiLCANCiAgICBIb21vYWNldG9nZW5lc2lzX0F1dG8gPSAiIzAwODEwMCIsIA0KICAgIEhvbW9hY2V0b2dlbmVzaXNfTWl4byA9ICIjOTNjNTcxIiwNCiAgICBNZXRoYW5vZ2VuZXNpcyA9ICJibHVlIikNCikNCg0KYW5ub3RhdGlvbl9jb2xvcnMzIDwtIGxpc3QoDQogIFJlYWN0b3IgPSBjKFIxID0gIiNiMWIxYjEiLCBSMiA9ICIjN0YxNzM0IiksDQogIE1ldGFib2xpc21fQ2FyYm9uID0gYyhBdXRvdHJvcGhpYyA9ICIjMWU3ZGZmIiwgTWl4b3Ryb3BoaWMgPSAiI2ZmN2IwMCIpLA0KICBNYWluX01ldGFib2xpY19QaGFzZSA9IGMoDQogICAgQWNpZG9nZW5lc2lzID0gIiMzMGQ1YzgiLCANCiAgICBgRWxvbmdfRXRPSCZMYWN0YXRlYCA9ICIjZmZkNzAwIiwgDQogICAgYEVsb25nX0gyQ08yX0F1dG9gID0gIiNmZjZjZGEiLCANCiAgICBgRWxvbmdfSDJDTzJfTWl4b2AgPSAiIzlhMDA5YSIsIA0KICAgIEhvbW9hY2V0b2dlbmVzaXNfQXV0byA9ICIjMDA4MTAwIiwgDQogICAgSG9tb2FjZXRvZ2VuZXNpc19NaXhvID0gIiM5M2M1NzEiICApLA0KICBQaHlsdW0gPSBjKA0KICAicF9fQWN0aW5vYmFjdGVyaW90YSIgICAgICAgICAgICAgPSAiI2ZmMDAwMCIsDQogICJwX19CYWN0ZXJvaWRvdGEiICAgICAgICAgICAgICAgICA9ICIjMDAwMDAwIiwNCiAgInBfX0Zpcm1pY3V0ZXMiICAgICAgICAgICAgICAgICAgID0gIiNmZmZmMDAiKQ0KKQ0KDQpgYGANCg0KVG8gYWRkIHJvdyBhbm5vdGF0aW9ucyBpbiB0aGUgaGVhdG1hcC4NCg0KYGBge3J9DQphc3ZfYW5ub3RhdGlvbiA8LSBhc3ZfdGFibGUyWywgYygiQVNWX0dlbnVzIiwgIlBoeWx1bSIpXQ0Kcm93bmFtZXMoYXN2X2Fubm90YXRpb24pIDwtIGFzdl9hbm5vdGF0aW9uJEFTVl9HZW51cw0KaGVhZChhc3ZfYW5ub3RhdGlvbikNCmFzdl9hbm5vdGF0aW9uIDwtIGFzdl9hbm5vdGF0aW9uWywgIlBoeWx1bSIsIGRyb3AgPSBGQUxTRV0NCmhlYWQoYXN2X2Fubm90YXRpb24pDQp1bmlxdWUoYXN2X2Fubm90YXRpb24kUGh5bHVtKQ0KDQppZGVudGljYWwocm93Lm5hbWVzKGFzdl9hbm5vdGF0aW9uKSxyb3cubmFtZXModGF4YV9zdWJzZXQpKQ0KDQpjbGFzcyhhc3ZfYW5ub3RhdGlvbikNCg0KdGF4YXRvcDI1Zg0KbGlzdCA8LSByb3cubmFtZXModGF4YXRvcDI1ZikNCmxpc3QNCmFzdl9hbm5vdGF0aW9uDQpyb3duYW1lcyhhc3ZfYW5ub3RhdGlvbikNCmRpbShhc3ZfYW5ub3RhdGlvbikNCg0KYXN2X2Fubm90YXRpb25fc3Vic2V0IDwtIGFzdl9hbm5vdGF0aW9uW3Jvd25hbWVzKGFzdl9hbm5vdGF0aW9uKSAlaW4lIHJvd25hbWVzKHRheGF0b3AyNWYpLCAsIGRyb3AgPSBGQUxTRV0NCmFzdl9hbm5vdGF0aW9uX3N1YnNldA0KY2xhc3MoYXN2X2Fubm90YXRpb25fc3Vic2V0KQ0KYGBgDQoNCiMjIDx1PjguIFNlbGVjdCB0aGUgVE9QMjUvMzAgdGF4YSBpbiBhbGwgc2VsZWN0ZWQgc2FtcGxlczwvdT4NCg0KYGBge3J9DQp0YXhhX3N1YnNldA0KdGF4YV9zdWJzZXR0IDwtIHQodGF4YV9zdWJzZXQpDQp0YXhhX3N1YnNldHRfZGVjciA8LSB0YXhhX3N1YnNldHRbLG9yZGVyKGNvbFN1bXModGF4YV9zdWJzZXR0KSxkZWNyZWFzaW5nPVRSVUUpXQ0Kcm93bmFtZXModGF4YV9zdWJzZXR0X2RlY3IpDQpjb2xuYW1lcyh0YXhhX3N1YnNldHRfZGVjcikNCnRvcDI1bGlzdCA8LSBjb2xuYW1lcyh0YXhhX3N1YnNldHRfZGVjcilbMToyNV0NCnRvcDI1bGlzdA0KdGF4YXRvcDI1IDwtIGRhdGEuZnJhbWUodGF4YV9zdWJzZXR0X2RlY3JbLGNvbG5hbWVzKHRheGFfc3Vic2V0dF9kZWNyKSAlaW4lIHRvcDI1bGlzdF0sIGNoZWNrLm5hbWVzID0gRkFMU0UpDQp0YXhhdG9wMjUNCnRheGF0b3AyNWYgPC0gdCh0YXhhdG9wMjUpDQp0YXhhdG9wMjVmDQpjb2xuYW1lcyh0YXhhdG9wMjVmKQ0KY29sU3Vtcyh0YXhhdG9wMjVmKQ0Kcm93U3Vtcyh0YXhhdG9wMjVmKQ0Kd3JpdGUuY3N2KHRheGF0b3AyNWYsICJ0YXhhdG9wMjVmLmNzdiIsIHJvdy5uYW1lcyA9IFRSVUUpDQoNCncgPC0gdGF4YXRvcDI1ZisxDQp3cml0ZS5jc3YodywgInRheGF0b3AyNWZ3aXRob25lLmNzdiIsIHJvdy5uYW1lcyA9IFRSVUUpDQoNCnRheGFfc3Vic2V0Mg0KdGF4YV9zdWJzZXQydCA8LSB0KHRheGFfc3Vic2V0MikNCnRheGFfc3Vic2V0MnRfZGVjciA8LSB0YXhhX3N1YnNldDJ0WyxvcmRlcihjb2xTdW1zKHRheGFfc3Vic2V0MnQpLGRlY3JlYXNpbmc9VFJVRSldDQpyb3duYW1lcyh0YXhhX3N1YnNldDJ0X2RlY3IpDQpjb2xuYW1lcyh0YXhhX3N1YnNldDJ0X2RlY3IpDQp0b3AzMGxpc3RtIDwtIGNvbG5hbWVzKHRheGFfc3Vic2V0MnRfZGVjcilbMTozMF0NCnRvcDMwbGlzdG0NCnRheGF0b3AzMG0gPC0gZGF0YS5mcmFtZSh0YXhhX3N1YnNldDJ0X2RlY3JbLGNvbG5hbWVzKHRheGFfc3Vic2V0MnRfZGVjcikgJWluJSB0b3AzMGxpc3RtXSwgY2hlY2submFtZXMgPSBGQUxTRSkNCnRheGF0b3AzMG0NCnRheGF0b3AzMG1mIDwtIHQodGF4YXRvcDMwbSkNCnRheGF0b3AzMG1mDQpjb2xTdW1zKHRheGF0b3AzMG1mKQ0KYGBgDQoNCg0KIyA8dT45LiBEYXRhIHRyYW5zZm9ybWF0aW9uIGFuZCBoZWF0bWFwIGNvbnN0cnVjdGlvbjwvdT4NCg0KQWRkIDEgdG8geW91ciByYXJlZmllZCBjb3VudHMgYmVmb3JlIGxvZyB0cmFuc2Zvcm1hdGlvbjoNCmxvZyh4ICsgMSkgaXMgYSBzdGFuZGFyZCBwcmVwcm9jZXNzaW5nIHN0ZXAuDQoNCmBgYHtyfQ0KdGF4YXRvcDI1bm96ZXJvIDwtIHRheGF0b3AyNWYrMQ0Kd3JpdGUuY3N2KHRheGF0b3AyNW5vemVybywgInRheGF0b3AyNWZub3plcm9fMi5jc3YiLCByb3cubmFtZXMgPSBUUlVFKQ0KDQpyYW5nZSh0YXhhdG9wMjVub3plcm8sIG5hLnJtID0gVFJVRSkNCmBgYA0KDQpFdWNsaWRlYW4gZGlzdGFuY2VzIGFyZSB1c2VkIGJ5IGRlZmF1bHQgaW4gcGhlYXRtYXAsIGZvciBib3RoIHJvd3MgYW5kIGNvbHVtbnMgKGNsdXN0ZXJpbmdfZGlzdGFuY2Vfcm93cyA9ICJldWNsaWRlYW4iOyBjbHVzdGVyaW5nX2Rpc3RhbmNlX2NvbHMgPSAiZXVjbGlkZWFuIikNCg0KIyMgOS4xLiBMb2cyIHRyYW5zZm9ybWF0aW9uDQoNCkxPRzIoMSkgPSAwDQpMT0cyKDApIHVuZGVmaW5lZA0KDQpgYGB7cn0NCmRhdGxvZzIgPC0gbG9nMih0YXhhdG9wMjVmKzEpDQoNCnJhbmdlKGRhdGxvZzIsIG5hLnJtID0gVFJVRSkNCnN1bW1hcnkoYXMudmVjdG9yKGRhdGxvZzIpKQ0KDQpzdHIoZGF0bG9nMikNCmlzLm1hdHJpeChkYXRsb2cyKQ0KaXMubnVtZXJpYyhkYXRsb2cyKQ0KYGBgDQoNClRoaXMgY29tbWFuZCB1c2VzIHRoZSBwaGVhdG1hcCBwYWNrYWdlIGluIFIgdG8gZ2VuZXJhdGUgYSBoZWF0bWFwIGFuZCBzdG9yZXMgdGhlIHJlc3VsdCAoaW5jbHVkaW5nIGRlbmRyb2dyYW0gaW5mb3JtYXRpb24pIGluIHRoZSBvYmplY3QgcGxvZzJfYXZlcmFnZS4gcGhlYXRtYXAoKSB1c2VzIGhlcmUgYXZlcmFnZSBsaW5rYWdlIChVUEdNQSkgZm9yIGhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nIG9mIHJvd3MgYW5kIGNvbHVtbnMuIGN1dHJlZV9jb2xzID0gNyBjdXRzIHRoZSBjb2x1bW4gZGVuZHJvZ3JhbSBpbnRvIDcgY2x1c3RlcnMuIFRoaXMgbWVhbnM6IENvbHVtbnMgKHNhbXBsZXMpIHdpbGwgYmUgZ3JvdXBlZCBpbnRvIDcgY2x1c3RlcnMsIGFuZCB0aGVzZSBjbHVzdGVycyBhcmUgc2hvd24gd2l0aCBjb2xvcmVkIGJhcnMgYmFzZWQgb24gZGVmaW5lZCBhbm5vdGF0aW9ucy4gY3V0cmVlX3Jvd3MgPSAxMCBjdXRzIHRoZSByb3cgZGVuZHJvZ3JhbSBpbnRvIDEwIGNsdXN0ZXJzLiBUaGlzIGdyb3VwcyB0aGUgdGF4YSAocm93cykgaW50byAxMCBjbHVzdGVycywgYW5kIGhlbHBzIHZpc3VhbGx5IGlkZW50aWZ5IGdyb3VwcyBvZiBzaW1pbGFyIGZlYXR1cmVzLg0KDQpgYGB7cn0NCnRpZmYoInBsb2cyX2F2ZXJhZ2VfMi50aWZmIiwgd2lkdGggPSAxNiwgaGVpZ2h0ID0gOCwgdW5pdHMgPSAiaW4iLCByZXMgPSAzMDApDQpwbG9nMl9hdmVyYWdlIDwtIHBoZWF0bWFwKGRhdGxvZzIsIG15cGFsNSwgY2x1c3RlcmluZ19tZXRob2Q9ImF2ZXJhZ2UiLCBicmVha3MgPSBOQSwgc2NhbGUgPSAibm9uZSIsIGZvbnRzaXplPTEwLCBmb250c2l6ZV9yb3c9OCwgZm9udHNpemVfY29sPTgsIGFubm90YXRpb25fY29sPWRmLCBhbm5vdGF0aW9uX2NvbG9ycyA9IGFubm90YXRpb25fY29sb3JzLCBjdXRyZWVfY29scyA9IDcsIGN1dHJlZV9yb3dzID0gMTApDQpkZXYub2ZmKCkNCmBgYA0KDQpgYGB7cn0NCnRpZmYoInBsb2cyX2F2ZXJhZ2VfMl9yb3dzLnRpZmYiLCB3aWR0aCA9IDE2LCBoZWlnaHQgPSA4LCB1bml0cyA9ICJpbiIsIHJlcyA9IDMwMCkNCnBsb2cyX2F2ZXJhZ2UgPC0gcGhlYXRtYXAoZGF0bG9nMiwgbXlwYWw1LCBjbHVzdGVyaW5nX21ldGhvZD0iYXZlcmFnZSIsIGJyZWFrcyA9IE5BLCBzY2FsZSA9ICJub25lIiwgZm9udHNpemU9MTAsIGZvbnRzaXplX3Jvdz04LCBmb250c2l6ZV9jb2w9OCwgYW5ub3RhdGlvbl9jb2w9ZGYsIGFubm90YXRpb25fcm93PWFzdl9hbm5vdGF0aW9uX3N1YnNldCwgYW5ub3RhdGlvbl9jb2xvcnMgPSBhbm5vdGF0aW9uX2NvbG9yczMsIGN1dHJlZV9jb2xzID0gNywgY3V0cmVlX3Jvd3MgPSAxMCkNCmRldi5vZmYoKQ0KYGBgDQoNCg0KRGVmaW5lIHNjYWxlIG9mIGNvbG9yIHBhbGV0dGUuDQoNCmBgYHtyfQ0KbXlfYnJlYWtzIDwtIHNlcSgwLCAxMCwgYnkgPSAwLjUpDQpteXBhbDYgPC0gY29sb3JSYW1wUGFsZXR0ZShSQ29sb3JCcmV3ZXI6OmJyZXdlci5wYWwoOSwgIllsR25CdSIpKShsZW5ndGgobXlfYnJlYWtzKSAtIDEpDQpteXBhbDYgPC0gY29sb3JSYW1wUGFsZXR0ZShyZXYoYnJld2VyLnBhbChuID0gNywgbmFtZSA9ICJSZFlsQnUiKSkpKDIwKQ0KbXlwYWw2DQoNCmxlbmd0aChteXBhbDUpDQpsZW5ndGgobXlwYWw2KQ0KDQpzdHIobXlfYnJlYWtzKQ0KaXMubnVtZXJpYyhteV9icmVha3MpICAgICAgICAgICAgIA0KYWxsKGlzLmZpbml0ZShteV9icmVha3MpKSAgICAgICAgICANCmxlbmd0aChteXBhbDUpID09IGxlbmd0aChteV9icmVha3MpIC0gMQ0KcHJpbnQobGVuZ3RoKG15cGFsNSkpDQoNCnRpZmYoInBsb2cyX2F2ZXJhZ2VfMl9zY2FsZWNvbG9yLnRpZmYiLCB3aWR0aCA9IDE2LCBoZWlnaHQgPSA4LCB1bml0cyA9ICJpbiIsIHJlcyA9IDMwMCkNCnBsb2cyX2F2ZXJhZ2UgPC0gcGhlYXRtYXAobWF0ID0gZGF0bG9nMiwgY29sb3IgPSBteXBhbDYsIGNsdXN0ZXJpbmdfbWV0aG9kPSJhdmVyYWdlIiwgYnJlYWtzID0gbXlfYnJlYWtzLCBzY2FsZSA9ICJub25lIiwgZm9udHNpemU9MTAsIGZvbnRzaXplX3Jvdz04LCBmb250c2l6ZV9jb2w9OCwgYW5ub3RhdGlvbl9jb2w9ZGYsIGFubm90YXRpb25fY29sb3JzID0gYW5ub3RhdGlvbl9jb2xvcnMsIGN1dHJlZV9jb2xzID0gNywgY3V0cmVlX3Jvd3MgPSAxMCkNCmRldi5vZmYoKQ0KYGBgDQoNCiMjIDkuMi4gTG9nMTAgdHJhbnNmb3JtYXRpb24NCg0KYGBge3J9DQpkYXRsb2cxMCA8LSBsb2cxMCh0YXhhdG9wMjVmKzEpDQoNCnJhbmdlKGRhdGxvZzEwLCBuYS5ybSA9IFRSVUUpDQpzdW1tYXJ5KGFzLnZlY3RvcihkYXRsb2cxMCkpDQoNCnN0cihkYXRsb2cxMCkNCmlzLm1hdHJpeChkYXRsb2cxMCkNCmlzLm51bWVyaWMoZGF0bG9nMTApDQpgYGANCg0KYGBge3J9DQp0aWZmKCJwbG9nMTBfYXZlcmFnZV8yLnRpZmYiLCB3aWR0aCA9IDE2LCBoZWlnaHQgPSA4LCB1bml0cyA9ICJpbiIsIHJlcyA9IDMwMCkNCnBsb2cxMF9hdmVyYWdlIDwtIHBoZWF0bWFwKGRhdGxvZzEwLCBteXBhbDUsIGNsdXN0ZXJpbmdfbWV0aG9kPSJhdmVyYWdlIiwgYnJlYWtzID0gTkEsIHNjYWxlID0gIm5vbmUiLCBmb250c2l6ZT0xMCwgZm9udHNpemVfcm93PTgsIGZvbnRzaXplX2NvbD04LCBhbm5vdGF0aW9uX2NvbD1kZiwgYW5ub3RhdGlvbl9jb2xvcnMgPSBhbm5vdGF0aW9uX2NvbG9ycywgY3V0cmVlX2NvbHMgPSA3LCBjdXRyZWVfcm93cyA9IDEwKQ0KZGV2Lm9mZigpDQpgYGANCg0KIyMgOS4zLiBjbHIgdHJhbnNmb3JtYXRpb24NCg0KY2xyIChDZW50ZXJlZCBMb2ctUmF0aW8pIHRyYW5zZm9ybWF0aW9uIGZyb20gdGhlIHBhY2thZ2UgY29tcG9zaXRpb25zLiBjbHIgdXNlcyB0aGUgZ2VvbWV0cmljIG1lYW4gb2YgYWxsIGNvbXBvbmVudHMgKGluIG1pY3JvYmlvbWUgZGF0YSwgZWFjaCBjb21wb25lbnQgaW4gYSBjb21wb3NpdGlvbmFsIHZlY3RvciBjb3JyZXNwb25kcyB0byBhIHNwZWNpZmljIEFTViBvciBPVFUpLg0KDQpgYGB7cn0NCnRheGF0b3AyNWYNCnJvdy5uYW1lcyh0YXhhdG9wMjVmKQ0KY29sbmFtZXModGF4YXRvcDI1ZikNCg0KdGF4YXRvcDI1ZjIgPC0gdGF4YXRvcDI1ZisxDQp3cml0ZS5jc3YodGF4YXRvcDI1ZjIsICJ0YXhhdG9wMjVmcGx1c29uZS5jc3YiLCByb3cubmFtZXMgPSBUUlVFKQ0KDQpkYXRjbHIgPC0gY2xyKGFjb21wKHRheGF0b3AyNWYrMSkpDQoNCnJhbmdlKGRhdGNsciwgbmEucm0gPSBUUlVFKQ0Kc3VtbWFyeShhcy52ZWN0b3IoZGF0Y2xyKSkNCg0KY2xhc3MoZGF0Y2xyKQ0KaXMubWF0cml4KGRhdGNscikNCmlzLm51bWVyaWMoZGF0Y2xyKQ0KDQpkYXRjbHJfbWF0IDwtIGFzLm1hdHJpeChkYXRjbHIpDQpkYXRjbHJfbWF0DQp3cml0ZS5jc3YoZGF0Y2xyX21hdCwgImRhdGNscl9tYXQuY3N2Iiwgcm93Lm5hbWVzID0gVFJVRSkNCmBgYA0KDQpgYGB7cn0NCnRpZmYoInBjbHJfYXZlcmFnZV8yLnRpZmYiLCB3aWR0aCA9IDE2LCBoZWlnaHQgPSA4LCB1bml0cyA9ICJpbiIsIHJlcyA9IDMwMCkNCnBjbHJfYXZlcmFnZSA8LSBwaGVhdG1hcChkYXRjbHIsIG15cGFsNSwgY2x1c3RlcmluZ19tZXRob2Q9ImNvbXBsZXRlIiwgYnJlYWtzID0gTkEsIHNjYWxlID0gIm5vbmUiLCBmb250c2l6ZT0xMCwgZm9udHNpemVfcm93PTgsIGZvbnRzaXplX2NvbD04LCBhbm5vdGF0aW9uX2NvbD1kZiwgYW5ub3RhdGlvbl9jb2xvcnMgPSBhbm5vdGF0aW9uX2NvbG9ycywgY3V0cmVlX2NvbHMgPSA3LCBjdXRyZWVfcm93cyA9IDEwKQ0KZGV2Lm9mZigpDQpgYGANCg0KYGBge3J9DQp0aWZmKCJwY2xyX2F2ZXJhZ2VfMl9tYXQudGlmZiIsIHdpZHRoID0gMTYsIGhlaWdodCA9IDgsIHVuaXRzID0gImluIiwgcmVzID0gMzAwKQ0KcGNscl9hdmVyYWdlIDwtIHBoZWF0bWFwKGRhdGNscl9tYXQsIG15cGFsNSwgY2x1c3RlcmluZ19tZXRob2Q9ImNvbXBsZXRlIiwgYnJlYWtzID0gTkEsIHNjYWxlID0gIm5vbmUiLCBmb250c2l6ZT0xMCwgZm9udHNpemVfcm93PTgsIGZvbnRzaXplX2NvbD04LCBhbm5vdGF0aW9uX2NvbD1kZiwgYW5ub3RhdGlvbl9jb2xvcnMgPSBhbm5vdGF0aW9uX2NvbG9ycywgY3V0cmVlX2NvbHMgPSA3LCBjdXRyZWVfcm93cyA9IDEwKQ0KZGV2Lm9mZigpDQpgYGANCg==