STEP 0: Import all data and fix formats.

library(dplyr)
library(data.table)

setwd() # fix this according to your system #

allmutations <- fread("allmutations.txt", header=T, sep="\t") # import mutation data #
allmutations <- as.data.frame(allmutations)
allmutations$effect <- gsub("3UTR.*", "3UTR", allmutations$effect) 
allmutations$SIFT <- gsub("\\(.*", "", allmutations$SIFT)
allmutations$PolyPhen <- gsub("\\(.*", "", allmutations$PolyPhen)
allmutations$Amino_Acid_Change <- gsub("[^0-9]", "", allmutations$Amino_Acid_Change) 
allmutations$Amino_Acid_Change <- as.numeric(allmutations$Amino_Acid_Change)

allmutations$effect <- gsub("Frame_Shift_Del", "Frameshift", allmutations$effect) # simple recode effect #
allmutations$effect <- gsub("Frame_Shift_Ins", "Frameshift", allmutations$effect)
allmutations$effect <- gsub("In_Frame_Del", "Deletion", allmutations$effect)
allmutations$effect <- gsub("In_Frame_Ins", "Insertion", allmutations$effect)
allmutations$effect <- gsub("Flank", "Others", allmutations$effect)
allmutations$effect <- gsub("Intron","Others", allmutations$effect)
allmutations$effect <- gsub("larged","D", allmutations$effect)
allmutations$effect <- gsub("_Mutation","", allmutations$effect)
allmutations$effect <- gsub("RNA", "Others", allmutations$effect)
allmutations$effect <- gsub("_S", " s", allmutations$effect)
allmutations$effect <- gsub("Translation start site", "Others", allmutations$effect)
allmutations$effect <- gsub("UTR", "Others", allmutations$effect)
allmutations$effect <- gsub("[^A-z]", "", allmutations$effect)

allmutations$PolyPhen <- gsub("^$","unknown",allmutations$PolyPhen) # recoding polyphen & SIFT #
allmutations$SIFT <- gsub("^$","unknown",allmutations$SIFT)

conv <- fread("uniprot_matrisome.txt", header=F, sep="\t") # load conversion from genes to protein names #
conv <- conv[,c(1:2)]
colnames(conv) <- c("gene","Uniprot_ID")

mat <- fread("matrisome_hs.txt", header = T, sep="\t") 
conv <- merge(conv, mat, by.x="gene", by.y="Gene Symbol")
conv <- as.data.frame(conv)

#Adding a column that states whether a gene is matrisome or not #
allmutations$is.matrisome <- ifelse(allmutations$gene %in% mat$`Gene Symbol`, "matrisome", "rest.of.genome")
cl <- fread("TCGAclindata.txt", header=T,sep="\t")
cl <- cl[,c(1,3)]
cl <- unique(merge(cl, allmutations, by="sample"))
cl <- subset(cl, cl$effect != "Silent")

STEP 1: A general overview of the data included in the study so far. We have a total of 2277979 non-silent entries from 9075 patients and 32 tumor types. Of these, only ~ 6.6% occur in the matrisome, but considering that the matrisome is 1027 genes in total (vs. 20228 other genes) the frequency of mutation “per gene” is ~ 147 in the matrisome vs. ~105 in the rest of the genome, in line with our previous report [https://pubmed.ncbi.nlm.nih.gov/32722287/]. Regardless, there is no difference between the % of the different types of mutation in matrisome vs. rest of the genome, again as reported.

length(unique(cl$sample))
length(unique(cl$`cancer type abbreviation`))
allmutations <- cl

tab1 <- table(allmutations$is.matrisome)
tab1 <- (tab1/2277979)*100
length(unique(mat$`Gene Symbol`))
sb <- subset(allmutations, allmutations$is.matrisome != "matrisome")
length(unique(sb$gene))
tab1 <- table(allmutations$is.matrisome)
pie(tab1, col = c("#fdae61","#4575b4"))
tab1[1] <- tab1[1]/1027
tab1[2] <- tab1[2]/20259
barplot(tab1, col = c("#fdae61","#4575b4"), ylim = c(0,200))
tab1 <- table(allmutations$is.matrisome, allmutations$effect) 
tab2 <- (tab1/rowSums(tab1))*100
chisq.test(tab2) #not significant#

library(ggplot2)
vec <- c("#fdae61","#4575b4", "#313695", "#92c5de", "#d73027","#fee090", "#e0f3f8","#f46d43", "#abd9e9","#a50026") # vector for colours #
df <- as.data.frame(tab2)
colnames(df) <- c("type", "mutation", "frequency (%)")
df$type <- gsub("rest.of.genome", "nonmatrisome", df$type)
ggplot(df, aes(x=mutation, y=`frequency (%)`, fill=type)) + # side by side barplot #
     geom_bar(position=position_dodge() , stat="identity") + 
     theme_classic() + 
     scale_fill_manual(values = vec) + 
     coord_flip()

tab1 <- table(allmutations$is.matrisome, allmutations$SIFT)
tab2 <- (tab1/rowSums(tab1))*100
chisq.test(tab2) #not significant#
df <- as.data.frame(tab2)
colnames(df) <- c("type", "mutation", "frequency (%)")
df$type <- gsub("rest.of.genome", "nonmatrisome", df$type)
ggplot(df, aes(x=mutation, y=`frequency (%)`, fill=type)) + # side by side barplot #
     geom_bar(position=position_dodge() , stat="identity") + 
     theme_classic() + 
     scale_fill_manual(values = vec) + 
     coord_flip()

tab1 <- table(allmutations$is.matrisome, allmutations$PolyPhen)
tab2 <- (tab1/rowSums(tab1))*100
chisq.test(tab2) #not significant#
df <- as.data.frame(tab2)
colnames(df) <- c("type", "mutation", "frequency (%)")
df$type <- gsub("rest.of.genome", "nonmatrisome", df$type)
ggplot(df, aes(x=mutation, y=`frequency (%)`, fill=type)) + # side by side barplot #
     geom_bar(position=position_dodge() , stat="identity") + 
     theme_classic() + 
     scale_fill_manual(values = vec) + 
     coord_flip()

STEP 2: Intersect mutation data with PTM sites data and identify mutations which potentially disrupt PTMs in tumors. In the first chunk, all PTM sites are loaded and integrated with mutation sites.

# PTMs - phosphorylation #

library(R.utils)
# gunzip("Phosphorylation_site_dataset.gz") # After opening the file with editor as a txt file, the first three rows were deleted #

phosdata <- fread("Phosphorylation_site_dataset.txt", header=T, sep="\t") # load PTM #
phosdata$PTM <- c("phosphorylation")
phosdata$MOD_RSD <- gsub("[^0-9]", "", phosdata$MOD_RSD)
phosdata$MOD_RSD <- as.numeric(phosdata$MOD_RSD)

# PTMs - acetylation #

# gunzip("Acetylation_site_dataset.gz") # After opening the file with editor as a txt file, the first three rows were deleted #

acdata <- fread("Acetylation_site_dataset.txt", header=T, sep="\t") # load PTM #
acdata$PTM <- c("acetylation")
acdata$MOD_RSD <- gsub("[^0-9]", "", acdata$MOD_RSD)
acdata$MOD_RSD <- as.numeric(acdata$MOD_RSD)

# PTMs - methylation #

# gunzip("Methylation_site_dataset.gz") # After opening the file with editor as a txt file, the first three rows were deleted #

metdata <- fread("Methylation_site_dataset.txt", header=T, sep="\t") # load PTM #
metdata$PTM <- c("methylation")
metdata$MOD_RSD <- gsub("[^0-9]", "", metdata$MOD_RSD)
metdata$MOD_RSD <- as.numeric(metdata$MOD_RSD)

# PTMs - O-glycosylation #

# gunzip("O-GalNAc_site_dataset.gz") # After opening the file with editor as a txt file, the first three rows were deleted #
# gunzip("O-GlcNAc_site_dataset.gz") # Same as on top #

Ogal <- fread("O-GalNAc_site_dataset.txt", header=T, sep="\t") # load PTM #
Ogal$MOD_RSD <- gsub("[^0-9]", "", Ogal$MOD_RSD)
Ogal$MOD_RSD <- as.numeric(Ogal$MOD_RSD)

Oglc <- fread("O-GlcNAc_site_dataset.txt", header=T, sep="\t")
Oglc$MOD_RSD <- gsub("[^0-9]", "", Oglc$MOD_RSD)
Oglc$MOD_RSD <- as.numeric(Oglc$MOD_RSD)

OG <- rbind(Ogal, Oglc)
OG$PTM <- c("O-glycosylation")

# PTMs - sumoylation #

# gunzip("Sumoylation_site_dataset.gz") # After opening the file with editor as a txt file, the first three rows were deleted #

sumodata <- fread("Sumoylation_site_dataset.txt", header=T, sep="\t") # load PTM #
sumodata$PTM <- c("sumoylation")
sumodata$MOD_RSD <- gsub("[^0-9]", "", sumodata$MOD_RSD)
sumodata$MOD_RSD <- as.numeric(sumodata$MOD_RSD)

# PTMs - ubiquitylation #

# gunzip("Ubiquitination_site_dataset.gz") # After opening the file with editor as a txt file, the first three rows were deleted #

ubidata <- fread("Ubiquitination_site_dataset.txt", header=T, sep="\t") # load PTM #
ubidata$PTM <- c("ubiquitylation")
ubidata$MOD_RSD <- gsub("[^0-9]", "", ubidata$MOD_RSD)
ubidata$MOD_RSD <- as.numeric(ubidata$MOD_RSD)

# PTMs - N-glycosylation #

Nglyc <- fread("N-GlycositeAtlas_HumanAll.txt", sep="\t", header=T) # data from N-GlycositeAtlas, converted into txt file and deletion of first row #
Nglyc <- Nglyc[,2:5]
colnames(Nglyc) <- c("Uniprot_ID", "gene", "protein", "Amino_Acid_Change")
Nglyc$PTM <- c("N-glycosylation")
Nglyc$Amino_Acid_Change <- as.numeric(Nglyc$Amino_Acid_Change)

# PTMs - hydroxylation #

h <- fread("Hydroxylation sites_Uniprot.txt", sep="\t", header=T) # data from uniprot #
colnames(h) <- c("Uniprot_ID", "gene", "specification", "Amino_Acid_Change", "validation", "PTM")
h$specification <- NULL

## Combine all PTMs ##

allPTMs <- rbind(acdata, metdata) # combining all mutated PTM sites #
allPTMs <- rbind(allPTMs, OG)
allPTMs <- rbind(allPTMs, phosdata)
allPTMs <- rbind(allPTMs, sumodata)
allPTMs <- rbind(allPTMs, ubidata)
allPTMs <- allPTMs[, c(1:3, 5, 15)]
colnames(allPTMs) <- c("gene", "protein", "Uniprot_ID", "Amino_Acid_Change", "PTM")

allPTMs <- rbind(allPTMs, h, fill=T) 
allPTMs <- rbind(allPTMs, Nglyc, fill=T)
allPTMs$validation <- NULL

allPTMs <- as.data.frame(allPTMs)
allPTMs <- unique(allPTMs)
allPTMs$gene <- toupper(allPTMs$gene) # the file contains upper and lower case gene names, some wouldn't match with mutations otherwise #

hum <- fread("uniprot-proteome_UP000005640.tab",header=T,sep="\t") #need to cut to human proteins only, some of the IDs included are form other species!#
allPTMs <- subset(allPTMs, allPTMs$Uniprot_ID %in% hum$Entry)
allmutations_PTMs <- merge(allmutations, allPTMs, by.x=c("gene", "Amino_Acid_Change"), by.y=c("gene", "Amino_Acid_Change"))

fwrite(allmutations_PTMs, "All PTM mutations.txt", sep="\t", row.names = F, quote=F)
fwrite(allmutations, "All mutations.txt", sep="\t", row.names = F, quote=F)

In the second chunk, we start to analyze the breakdowns and genomic patterns of disruptive PTM mutations (PTMmut). In total, there are 42733 PTMmut (~1.87% of all mutations) from 6303 patients and 32 cancers, of which 1811 from the matrisome. The ratio of PTMmut/total mut is thus 1.2% for the matrisome and 1.9% for the rest of the genome. Note that the matrisome has, on average, lower PTMmut frequency per unit length as compared to the rest of the genome, oppositely to what observed for other mutations.

#PTMmut vs non-PTMmut#
dim(allmutations_PTMs)
(nrow(allmutations_PTMs)/nrow(allmutations))*100
length(unique(allmutations_PTMs$sample))
length(unique(allmutations_PTMs$`cancer type abbreviation`))
nrow(subset(allmutations_PTMs, allmutations_PTMs$is.matrisome != "rest.of.genome"))

r1 <- (nrow(subset(allmutations_PTMs, allmutations_PTMs$is.matrisome != "rest.of.genome"))/nrow(subset(allmutations, allmutations$is.matrisome != "rest.of.genome")))*100
r2 <- (nrow(subset(allmutations_PTMs, allmutations_PTMs$is.matrisome == "rest.of.genome"))/nrow(subset(allmutations, allmutations$is.matrisome == "rest.of.genome")))*100
r1
r2

tab1 <- c((nrow(allmutations_PTMs)/nrow(allmutations))*100,100-((nrow(allmutations_PTMs)/nrow(allmutations))*100))
names(tab1) <- c("PTMmut", "other mutations")
pie(tab1, col = c("red","#4575b4"))

#N of PTMmut by gene length#
library(goseq)
g <- unique(allmutations_PTMs$gene)
l<- getlength(g,'hg19','geneSymbol')
df <- data.frame(g=g,l=l)
tab1 <- as.data.frame(table(allmutations_PTMs$gene))
df <- merge(df,tab1,by.x="g",by.y="Var1")
df$r <- df$Freq/df$l
df$source <- ifelse(df$g %in% mat$`Gene Symbol`, "matrisome", "rest.of.genome")
df <- na.omit(df)
tab1 <- as.data.frame(table(allmutations$gene))
df <- merge(df,tab1,by.x="g",by.y="Var1")
df$Freq.y <- df$Freq.y-df$Freq.x
df$r2 <- df$Freq.y/df$l
df <- na.omit(df)
v <- aggregate(df$r,list(df$source),mean)
v2 <- aggregate(df$r2,list(df$source),mean) #all non-PTM-muts#
v <- merge(v,v2,by="Group.1")

df <- data.frame(g=g,l=l)
tab1 <- as.data.frame(table(allmutations$gene, allmutations$`cancer type abbreviation`))
df <- merge(df,tab1,by.x="g",by.y="Var1")
df$r <- df$Freq/df$l
df <- na.omit(df)
v <- aggregate(df$r,list(df$Var2),mean)
v$ord <- c(1:nrow(v))
v <- v[order(-v$ord),]
v$ord <- c(1:nrow(v))

ggplot(v, aes(x=ord, y=x)) + # side by side barplot #
     geom_bar(position=position_dodge() , stat="identity", fill="#4575b4") + 
     theme_classic() +  
     coord_flip() +
     scale_x_continuous(breaks = c(1:32),labels = as.character(v$Group.1)) +
     xlab("") + ylab("mean frequency of mutations per gene length")

df <- data.frame(g=g,l=l)
tab1 <- as.data.frame(table(allmutations_PTMs$gene, allmutations_PTMs$`cancer type abbreviation`))
df <- merge(df,tab1,by.x="g",by.y="Var1")
df$r <- df$Freq/df$l
df <- na.omit(df)
v <- aggregate(df$r,list(df$Var2),mean)
v$ord <- c(1:nrow(v))
v <- v[order(-v$ord),]
v$ord <- c(1:nrow(v))

ggplot(v, aes(x=ord, y=x)) + # side by side barplot #
     geom_bar(position=position_dodge() , stat="identity", fill="#fdae61") + 
     theme_classic() +  
     coord_flip() +
     scale_fill_manual(values="#fdae61") +
     scale_x_continuous(breaks = c(1:32),labels = as.character(v$Group.1)) +
     xlab("") + ylab("mean frequency of PTMmut per gene length")

Transitions and transversions are also equal in absolute, in line with previous reports. In comparison, also, transitions and transversions seem to have similar mutational effect, the only noticeable difference being the reduced amount of transversions among splice-site mutations.

m <- subset(allmutations_PTMs, !nchar(allmutations_PTMs$reference) > 1)
m <- subset(m, !nchar(m$alt) > 1)
m <- subset(m, !m$reference == "-")
m <- subset(m, !m$alt == "-")
m$mut.type <- paste0(m$reference,m$alt)
m$mut.type <- ifelse(m$mut.type == "AC", "transversions", ifelse(m$mut.type == "CA", "transversions", ifelse(m$mut.type == "GT", "transversions", ifelse(m$mut.type == "TG", "transversions", "transitions"))))
chisq.test(table(m$is.matrisome,m$mut.type)) #P-value = 0.5431
tab1 <- as.data.frame(table(m$is.matrisome,m$mut.type))

library(ggsci)
ggplot(tab1, aes(x=Var1, y=Freq, fill=Var2)) +
     geom_bar(position="fill" , stat="identity") + 
     scale_fill_aaas() + 
     theme_classic() + xlab("") 

m <- subset(allmutations, !nchar(allmutations$reference) > 1)
m <- subset(m, !nchar(m$alt) > 1)
m <- subset(m, !m$reference == "-")
m <- subset(m, !m$alt == "-")
m$mut.type <- paste0(m$reference,m$alt)
m$mut.type <- ifelse(m$mut.type == "AC", "transversions", ifelse(m$mut.type == "CA", "transversions", ifelse(m$mut.type == "GT", "transversions", ifelse(m$mut.type == "TG", "transversions", "transitions"))))
chisq.test(table(m$is.matrisome,m$mut.type)) #P-value = 0.01251, but if using %'s [tb <- table(m$is.matrisome,m$mut.type) ; tb <- (tb/rowSums(tb))*100] then there clearly are no differences!
tab1 <- as.data.frame(table(m$is.matrisome,m$mut.type))

ggplot(tab1, aes(x=Var1, y=Freq, fill=Var2)) +
     geom_bar(position="fill" , stat="identity") + 
     scale_fill_aaas() + 
     theme_classic() + xlab("")

m <- subset(allmutations_PTMs, !nchar(allmutations_PTMs$reference) > 1)
m <- subset(m, !nchar(m$alt) > 1)
m <- subset(m, !m$reference == "-")
m <- subset(m, !m$alt == "-")
m$mut.type <- paste0(m$reference,m$alt)
m$mut.type <- ifelse(m$mut.type == "AC", "transversions", ifelse(m$mut.type == "CA", "transversions", ifelse(m$mut.type == "GT", "transversions", ifelse(m$mut.type == "TG", "transversions", "transitions"))))

m.1 <- subset(m, m$is.matrisome == "matrisome")
chisq.test(table(m.1$mut.type,m.1$effect)) #P-value = 4.708e-06
tab1 <- as.data.frame(table(m.1$mut.type,m.1$effect))
m.2 <- subset(m, m$is.matrisome != "matrisome")
chisq.test(table(m.2$mut.type,m.2$effect)) #P-value = 2.2e-16
tab2 <- as.data.frame(table(m.2$mut.type,m.2$effect))
tab1$source <- "matrisome"
tab2$source <- "rest.of.genome"
tab <- bind_rows(tab1,tab2)
tab$source <- paste0(tab$source,"_",tab$Var1)
tab <- subset(tab, tab$Var2 != "Others") #there is jut one "Others" mutation here, to be removed to make columns compatible later#
res <- list()
for(i in unique(tab$Var2)){
  z <- subset(tab, tab$Var2 == i)
  df <- as.data.frame(z$Freq)
  names(df) <- unique(z$Var2)
  res[[i]] <- df
}
res <- bind_cols(res)
rownames(res) <- unique(tab$source)

ggplot(tab, aes(x=source, y=Freq, fill=Var2)) +
     geom_bar(position="fill" , stat="identity") + 
     scale_fill_aaas() + 
     theme_classic() + theme(axis.text.x = element_text(angle = 90, hjust = 0.5, vjust = 0.5, size = 10)) +   xlab("")

m.1 <- merge(mat,m.1,by.x="Gene Symbol",by.y="gene")
m.1a <- subset(m.1, m.1$mut.type == "transitions")
m.1b <- subset(m.1, m.1$mut.type == "transversions")
t1 <- as.data.frame(table(m.1a$Category))
t1$type="transitions"
t2 <- as.data.frame(table(m.1b$Category))
t2$type="transversions"

tab1 <- rbind(t1,t2) #P-value : 0.4122#

ggplot(tab1, aes(x=type, y=Freq, fill=Var1)) +
     geom_bar(position="fill" , stat="identity") + 
     scale_fill_d3() + 
     theme_classic() + theme(axis.text.x = element_text(angle = 90, hjust = 0.5, vjust = 0.5, size = 10)) + 
  xlab("")

At a glance, comparison of disruptive PTMmut in matrisome vs rest of the genome shows accumulation of events potentially affecting hydroxylation, N- and O-glycosylation in the matrisome, which is conversely devoid of methylation, sumoylation and ubiquitylation event. Acetylation and phoshporylation events seem comparable. However, the baseline frequency of PTM events in the genome is different, so we need to account for such differences before comparing. When we do, we get a measure we call “burden” (actually, the global burden) and we notice that the matrisome accumulates more PTMmut than the rest of the genome in almost all categories, with the most evident differences in hydroxylation, O-glycosylation and acetylation.

#overall PTMs and PTMmut in matrisome vs. non-matrisome#
allPTMs$source <- ifelse(allPTMs$gene %in% mat$`Gene Symbol`, "matrisome","rest.of.genome")
tab1 <- table(allPTMs$source,allPTMs$PTM)
tab2 <- (tab1/rowSums(tab1))*100 
chisq.test(tab2) # p-value = 0.000149 -> significant! #
df <- as.data.frame(tab2)
colnames(df) <- c("type", "PTM", "frequency (%)")

ggplot(df, aes(x=type, y=`frequency (%)`, fill=PTM)) +
  geom_bar(position="fill" , stat="identity") + 
  theme_classic() + 
  scale_fill_d3() + 
  theme(axis.text.x = element_text(angle = 90, hjust = 0.5, vjust = 0.5, size = 10)) + 
  xlab("")

tab1 <- table(allmutations_PTMs$is.matrisome,allmutations_PTMs$PTM)
tab2 <- (tab1/rowSums(tab1))*100 
chisq.test(tab2) # p-value = 0.0002194 -> significant! #
df <- as.data.frame(tab2)
colnames(df) <- c("type", "PTM", "frequency (%)")

ggplot(df, aes(x=type, y=`frequency (%)`, fill=PTM)) +
  geom_bar(position="fill" , stat="identity") + 
  theme_classic() + 
  scale_fill_d3() + 
  theme(axis.text.x = element_text(angle = 90, hjust = 0.5, vjust = 0.5, size = 10)) + 
  xlab("")

#normalization of N of PTMmut by N Ptm sites (burden), matrisome vs. non-matrisome#
res <- list()
for(i in unique(allmutations_PTMs$`cancer type abbreviation`)){
  print(paste0("analyzing cancer: ",i))
  z <- subset(allmutations_PTMs, allmutations_PTMs$`cancer type abbreviation` == i)
  z <- unique(z[,c(1,2,17)])
  z <- as.data.frame(table(z$gene,z$PTM))
  z2 <- subset(allPTMs, allPTMs$gene %in% z$Var1)
  z2 <- as.data.frame(table(z2$gene,z2$PTM))
  m <- merge(z,z2,by=c("Var1","Var2"),all.y=T)
  m$tot <- rowSums(m[,c(3,4)])
  #m <- subset(m,m$tot > 0)
  m$burden <- round((m$Freq.x/m$tot)*100,2)
  m$burden[is.na(m$burden)] <- 0
  m$cancer <- i
  res[[i]] <- m
}
res <- bind_rows(res)
res$source <- ifelse(res$Var1 %in% mat$`Gene Symbol`, "matrisome", "rest of genome")
v <- aggregate(res$burden,list(res$Var2, res$source),mean)
v2 <- data.frame(Group.1="hydroxylation",Group.2="rest of genome",x=0)
v <- bind_rows(v,v2)

ggplot(v, aes(x=Group.1, y=x, fill=Group.2)) + # side by side barplot #
     geom_bar(position=position_dodge() , stat="identity") + 
     theme_classic() + 
     scale_fill_manual(values = vec) + 
     theme(axis.text.x = element_text(angle = 90, hjust = 0.5, vjust = 0.5, size = 10)) +      xlab("") + ylab("")

res2 <- list()
for(i in unique(res$Var2)){
  z <- subset(res, res$Var2==i)
  x <- subset(z,z$source == "matrisome")
  y <- subset(z, z$source != "matrisome")
  tt <- t.test(x$burden,y$burden)
  p <- paste0("p value for ",i," : ",format.pval(tt$p.value))
  res2[[i]] <- p
} #hydroxylation cannot be counted, we proxy at p-value < 2.22e-16#

v2 <- list()
for(i in unique(v$Group.2)){
  z <- subset(v, v$Group.2 == i)
  df <- data.frame(source=i, t(z$x))
  names(df)[2:ncol(df)] <- z$Group.1
  v2[[i]] <- df
}
v2 <- bind_rows(v2)
v2 <- v2[,c(1:9)]
rownames(v2) <- v2[,1]
v2[,1] <- NULL
v2[is.na(v2)] <- 0.00000000001
v2 <- v2[1,]/v2[2,]
v2

Finally, we recalculate the burdens (exact by mutation), merge with the PTMmut table and export it.

res <- list()
for(i in unique(allmutations_PTMs$`cancer type abbreviation`)){
  print(paste0("analyzing cancer: ",i))
  z <- subset(allmutations_PTMs, allmutations_PTMs$`cancer type abbreviation` == i)
  z <- unique(z[,c(1,2,17)])
  g <- z$gene
  z$gene <- paste0(z$gene,"_",z$Amino_Acid_Change)
  z <- as.data.frame(table(z$gene,z$PTM))
  z2 <- subset(allPTMs, allPTMs$gene %in% g)
  z2 <- as.data.frame(table(z2$gene,z2$PTM))
  z$nVar1 <- gsub("\\_.*","",z$Var1)
  names(z)[1] <- "id"
  names(z)[4] <- "Var1"
  m <- merge(z,z2,by=c("Var1","Var2"),all.y=T)
  m[is.na(m)]<- 0
  m$tot <- rowSums(m[,c(4,5)])
  m <- subset(m,m$tot > 0)
  m$burden <- round((m$Freq.x/m$tot)*100,2)
  m$cancer <- i
  m <- subset(m,m$burden > 0)
  res[[i]] <- m
}
res <- bind_rows(res)
allmutations_PTMs$id <- paste0(allmutations_PTMs$gene,"_",allmutations_PTMs$Amino_Acid_Change)

fin <- merge(allmutations_PTMs,res,by.x=c("cancer type abbreviation","id"),by.y=c("cancer","id"))
fin <- as.data.frame(fin)
fin <- fin[,c(1,4,3,4:18,24)]
fin[,4] <- NULL
fin <- fin[,c(4,1,3,2,5:ncol(fin))]
fwrite(fin,"TABLE 1.txt",sep="\t",row.names = F,quote = F)

Before moving on, we calculate the correlation between the burden and the N of mutations. Expectedly, these two measures are generally opposite and breaking down by tumor type and matrisome category confirms that all significant correlations found are negative.

fin2 <- merge(fin,mat,by.x="gene",by.y="Gene Symbol")
fin2 <- unique(fin2)
res <- list()
res2 <- list()
for(i in unique(fin2$`cancer type abbreviation`)){
  print(paste0("analyzing tumor: ",i))
  z <- subset(fin2, fin2$`cancer type abbreviation` == i)
  z <- na.omit(z)
  for(w in unique(z$Category)){
    print(paste0("*** now analyzing matrisome category: ",w))
    df <- subset(z, z$Category == w)
    x <- df[, c(1,18)]
    y <- as.data.frame(table(df$gene))
    m <- na.omit(m)
    m <- merge(x,y,by.x="gene",by.y="Var1")
    if(nrow(m)>=4){
        ct <- cor.test(m$burden,m$Freq)
        m$tumor <- i
        m$category <- w
        m$corr <- ct$estimate
        m$pval <- ct$p.value}else{
          m$tumor <- i
          m$category <- w
          m$corr <- 0
          m$pval <- 1
        }
    ct <- NULL
    res[[w]] <- m
  }
  res2[[i]] <- bind_rows(res)
}
res2 <- bind_rows(res2)
res2$eval <- ifelse(res2$pval < 0.05, "significant", "NS")
res2$dir <- ifelse(res2$corr > 0 , "positive", "negative")

for(i in unique(res2$category)){
    z <- subset(res2, res2$category == i)
    lm <- lm(z$burden~z$Freq)
    s <- summary(lm)
    df <- data.frame(category=i, lr=s$coefficients[2,1], pval=format.pval(s$coefficients[2,4]))
    print(df)
}

res3 <- unique(res2[,c(4:9)])
table(res3$category,res3$eval,res3$dir)

ggplot(res2, aes(x=burden, y=Freq, color=factor(category))) +
  geom_point() + 
  scale_color_d3() + 
  theme_minimal() + 
  geom_smooth(method = "lm")

tab <- as.data.frame(table(res3$category,res3$eval,res3$dir))
df <- aggregate(tab$Freq,by=list(tab$Var3, tab$Var2),sum)
pie(c(df[1,3],df[3,3]),col = c("blue","red"))
pie(c(df[2,3],df[4,3]),col = c("blue","red"))

STEP 3: PTMmut in the tumor matrisome at the gene level. Here we focus on the matrisome, comparing frequency of mutations between different matrisome categories across cancers etc. Aggregating data by matrisome category shows a clear redistribution of PTMmut types, so that, e.g., hydroxylation is 52% of all PTMmut in collagens (and hydroxy-PTMmut in collagens are 74% of all PTMmut for that PTM) while phoshorylation is 72% of all PTMmut in secreted factors and 21.4% of all phoshpo-PTMmut.

fin2 <- unique(merge(fin,mat,by.x="gene",by.y="Gene Symbol"))
m <- subset(fin2, fin2$is.matrisome == "matrisome")
chisq.test(table(m$Division, m$PTM)) #p-value < 2.2e-16 significant!#
df <- as.data.frame(table(m$Division, m$PTM))

vec <- c("darkkhaki","cadetblue3","skyblue4","peachpuff3","navajowhite4","sandybrown","cadetblue4","lightblue3")

ggplot(df, aes(x=Var1, y=Freq, fill=Var2)) +
  geom_bar(position="fill" , stat="identity") + 
  theme_classic() + 
  scale_fill_manual(values = vec) + 
  theme(axis.text.x = element_text(angle = 0, hjust = 0.5, vjust = 0.5, size = 10)) + 
  xlab("")

chisq.test(table(m$Category, m$PTM)) #p-value < 2.2e-16 significant!#
df <- as.data.frame(table(m$Category, m$PTM))

ggplot(df, aes(x=Var1, y=Freq, fill=Var2)) +
  geom_bar(position="fill" , stat="identity") + 
  theme_classic() + 
  scale_fill_manual(values = vec) + 
  theme(axis.text.x = element_text(angle = 90, hjust = 0.5, vjust = 0.5, size = 10)) + 
  xlab("")

#PTMmut, % of tot by matrisome category
tab <- table(fin2$Category,fin2$PTM)
tab1 <- round((tab/rowSums(tab))*100,1)
tab1

#PTMmut, % of tot by total in that type of PTM
v <- apply(tab,2,sum)
tab2 <- apply(tab,2,function(x){round((x/sum(x)*100),1)})

The trends seem very similar pan-cancer, with SKCM, STAD and UCEC clearly leading the way. There seem to be no tissue-of-origin effects, though various local differences exist.

tab <- as.data.frame(table(fin2$`cancer type abbreviation`,fin2$Category,fin2$PTM))

ggplot(tab, aes(x=Var1, y=Var3, size=Freq, fill=Var3)) + 
geom_point(alpha=1, shape=21, color="black") + 
theme_classic() + 
scale_size_area(max_size = 8, name="freq") +
scale_fill_manual(values=vec) +
theme(axis.text.x = element_text(angle=90, size=10, vjust=0.4), axis.text.y = element_text(size=8)) + 
xlab("") + 
ylab("") +
facet_wrap(tab$Var2)

res <- list()
res2 <- list()
for(i in unique(tab$Var2)){
  z <- subset(tab, tab$Var2 == i)
  z$Var2 <- NULL
  for(w in unique(z$Var3)){
    k <- subset(z, z$Var3==w)
    df <- data.frame(matrisome=i,PTM=w,t(k$Freq))
    names(df)[3:ncol(df)] <- as.character(k$Var1)
    res[[w]]<- df
  }
  res2[[i]] <- bind_rows(res)
}
res2 <- bind_rows(res2)
fwrite(res2,"TABLE 2.txt", sep="\t", row.names = F, quote = F)

ndf <- list()
for(i in unique(res2$matrisome)){
  z <- subset(res2, res2$matrisome == i)
  print(paste0("now graphing data for: ",i))
  rownames(z) <- z$PTM
  z <- z[,c(3:ncol(z))]
  for(w in 1:nrow(z)){
    k <- z[w,]
    k <- as.data.frame(t(k))
    k$tumor <- rownames(k)
    ndf[[w]] <- k
  }
  f <- Reduce(merge,ndf)
  f <- reshape2::melt(f)
  
  p <- ggplot(data=f, aes(x=tumor, y=value, fill=variable)) +
  geom_bar(stat="identity") + 
  scale_fill_manual(values=vec) + 
  theme_bw() +
  facet_wrap(~f$variable) + ggtitle(i) + ggtitle(i) + theme(strip.background = element_rect(colour="black", fill="white"), axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1, size = 4))
  plot(p)
}

Examples of hydroxylation and phosphorylation PTMmut in collagens.

#zoom in: collagens#
prova <- subset(fin2, fin2$Category == "Collagens")
prova <- subset(prova, prova$PTM == "phosphorylation" | prova$PTM == "hydroxylation")
prova <- as.data.frame(table(prova$gene,prova$PTM,prova$`cancer type abbreviation`))
prova <- subset(prova, prova$Freq > 0)

ndf <- list()
ndf2 <- list()
for(i in unique(prova$Var3)){
  z <- subset(prova, prova$Var3 == i)
  for(j in unique(z$Var2)){
    k <- subset(z, z$Var2 == j)
    k <- k[order(-k$Freq),]
    if(nrow(k) >= 10){k <- k[1:10,]}else{k <- k}
    ndf[[j]] <- k
  }
  ndf2[[i]] <- bind_rows(ndf)
}
ndf2 <- bind_rows(ndf2)
ndf2 <- merge(ndf2,mat,by.x="Var1",by.y="Gene Symbol")

ggplot(ndf2, aes(x=Var3, y=Var1, size=Freq, fill=Var3)) + 
geom_point(alpha=1, shape=21, color="black") + 
theme_classic() + 
scale_size_area(max_size = 8, name="freq") +
theme(axis.text.x = element_text(angle=90, size=10, vjust=0.4), axis.text.y = element_text(size=8)) + 
xlab("") + 
ylab("") +
facet_wrap(ndf2$Var2)

Finally, the absolute Top10 per tumor type and a look at eventual “hotspots” across and within the cohorts.

prova <- fin2

# to see the table, for describing it in text:
#prova <- table(prova$gene,prova$`cancer type abbreviation`)
#prova <- ifelse(prova == 0, 0 ,1)
#prova <- as.data.frame(prova)
#prova$tot <- rowSums(prova)
#prova <- prova[order(-prova$tot),]

prova <- as.data.frame(table(prova$gene,prova$`cancer type abbreviation`))
prova <- subset(prova, prova$Freq > 0)

res <- list()
for(i in unique(prova$Var2)){
  print(paste0("now analyzing: ",i))
  z <- subset(prova, prova$Var2 == i)
  z <- z[order(-z$Freq),]
  if(nrow(z) <= 10){z <- z}else{z <- z[1:10,]}
  res[[i]] <- z
}
res <- bind_rows(res)

ggplot(res, aes(x=Var2, y=Var1, size=Freq, fill=Var2)) + 
geom_point(alpha=1, shape=21, color="black") + 
theme_classic() + 
scale_size_area(max_size = 8, name="freq") +
theme(axis.text.x = element_text(angle=90, size=10, vjust=0.4), axis.text.y = element_text(size=8)) + 
xlab("") + 
ylab("") 

prova <- fin2
prova$mut <- paste(prova$gene,prova$Amino_Acid_Change,prova$PTM,sep="_")
prova <- table(prova$mut, prova$`cancer type abbreviation`)
prova <- as.data.frame.matrix(prova)
prova[prova > 0] <- 1 #this allows calculation of absolute occurrences#
prova$tot <- rowSums(prova)
prova <- prova[order(-prova$tot),]
df1 <- data.frame(mut=rownames(prova),n.of.cohorts=prova$tot)
prova <- fin2
prova$mut <- paste(prova$gene,prova$Amino_Acid_Change,prova$PTM,sep="_")
prova <- table(prova$mut, prova$`cancer type abbreviation`)
prova <- as.data.frame.matrix(prova)
prova$tot <- rowSums(prova)
prova <- prova[order(-prova$tot),]
df2 <- data.frame(mut=rownames(prova),n.total=prova$tot)
df <- merge(df1,df2,by="mut",all=T)
df$col <- ifelse(df$n.of.cohorts >= 3 & df$n.total >= 3, 1, 0)
df <- df[order(-df$col, -df$n.of.cohorts),]
df$col <- ifelse(df$col==1, "1", "0")
df$labs <- gsub("_.*","",df$mut)

ggplot(df, aes(n.total, n.of.cohorts, color=col, label=labs)) + geom_point() +
  theme_classic() + 
  scale_color_aaas() +
  geom_vline(xintercept = 3.0, linetype="dotted", color = "grey80") +
  geom_hline(yintercept = 3.0, linetype="dotted", color = "grey80")

df <- subset(df, df$col == 1)
df$col <- NULL
colnames(df) <- c("mutation","number of cohorts","number of total occurrences")
fwrite(df, "TABLE 3.txt", sep="\t", quote = F, row.names = F)

STEP 4: Non-conservation of hydroxylation and glycosylation PTMmut. Why is none of these PTMmut in the hotspot list? Is there a negative selection against them? We check here the distribution of PTMmut type per SIFT/polyphen category and evaluate the ratio of non-silent PTMmut to silent PTMmut (dN/dS), as reported [https://doi.org/10.1186/s13059-018-1434-0].

prova <- fin2
tab <- table(prova$PTM,prova$SIFT)
tab <- round((tab/rowSums(tab))*100,0)
#for SIFT, we move the low-confidence findings to the "unknown" group#
tab <- tab[,c(1,3,5)]
tab[,3] <- 100-(tab[,1]+tab[,2])
cc <- chisq.test(tab)
tab <- reshape2::melt(tab)

ggplot(tab, aes(x=Var1, y=value, fill=Var2)) +
  geom_bar(position="fill" , stat="identity") + 
  theme_classic() + 
  scale_fill_npg() + 
  theme(axis.text.x = element_text(angle = 90, hjust = 0.5, vjust = 0.5, size = 10)) + 
  xlab("") + ggtitle(paste0("matrisome_SIFT","\n",cc$p.value))

allp <- subset(allmutations_PTMs, allmutations_PTMs$is.matrisome == "rest.of.genome")
tab <- table(allp$PTM,allp$SIFT)
tab <- round((tab/rowSums(tab))*100,0)
#for SIFT, we move the low-confidence findings to the "unknown" group#
tab <- tab[,c(1,3,5)]
tab[,3] <- 100-(tab[,1]+tab[,2])
cc <- chisq.test(tab)
tab <- reshape2::melt(tab)

ggplot(tab, aes(x=Var1, y=value, fill=Var2)) +
  geom_bar(position="fill" , stat="identity") + 
  theme_classic() + 
  scale_fill_npg() + 
  theme(axis.text.x = element_text(angle = 90, hjust = 0.5, vjust = 0.5, size = 10)) + 
  xlab("") + ggtitle(paste0("rest of genome_SIFT","\n",cc$p.value))

tab <- table(prova$PTM,prova$PolyPhen)
tab <- round((tab/rowSums(tab))*100,0)
#for Polyphen, we merge the "damaging" groups together#
tab[,2] <- tab[,1]+tab[,2]
tab <- tab[,c(1,2,4)]
colnames(tab)[2] <- "damaging"
cc <- chisq.test(tab)
tab <- reshape2::melt(tab)
levels(tab$Var2) <- c("damaging","benign","unknown")

ggplot(tab, aes(x=Var1, y=value, fill=Var2)) +
  geom_bar(position="fill" , stat="identity") + 
  theme_classic() + 
  scale_fill_npg() + 
  theme(axis.text.x = element_text(angle = 90, hjust = 0.5, vjust = 0.5, size = 10)) + 
  xlab("") + ggtitle(paste0("matrisome_PolyPhen","\n",cc$p.value))

tab <- table(allp$PTM,allp$PolyPhen)
tab <- round((tab/rowSums(tab))*100,0)
#for Polyphen, we merge the "damaging" groups together#
tab[,2] <- tab[,1]+tab[,2]
tab <- tab[,c(1,2,4)]
colnames(tab)[2] <- "damaging"
cc <- chisq.test(tab)
tab <- reshape2::melt(tab)
levels(tab$Var2) <- c("damaging","benign","unknown")

ggplot(tab, aes(x=Var1, y=value, fill=Var2)) +
  geom_bar(position="fill" , stat="identity") + 
  theme_classic() + 
  scale_fill_npg() + 
  theme(axis.text.x = element_text(angle = 90, hjust = 0.5, vjust = 0.5, size = 10)) + 
  xlab("") + ggtitle(paste0("rest of genome_PolyPhen","\n",cc$p.value))

#to calculate the dN/dS ratio, the initial allmutations file must be recreated since we had previously removed all silent mutations!#
allmutations <- fread("allmutations.txt", header=T, sep="\t") # import mutation data #
allmutations <- as.data.frame(allmutations)
allmutations$effect <- gsub("3UTR.*", "3UTR", allmutations$effect) 
allmutations$SIFT <- gsub("\\(.*", "", allmutations$SIFT)
allmutations$PolyPhen <- gsub("\\(.*", "", allmutations$PolyPhen)
allmutations$Amino_Acid_Change <- gsub("[^0-9]", "", allmutations$Amino_Acid_Change) 
allmutations$Amino_Acid_Change <- as.numeric(allmutations$Amino_Acid_Change)
allmutations$effect <- gsub("Frame_Shift_Del", "Frameshift", allmutations$effect) # simple recode effect #
allmutations$effect <- gsub("Frame_Shift_Ins", "Frameshift", allmutations$effect)
allmutations$effect <- gsub("In_Frame_Del", "Deletion", allmutations$effect)
allmutations$effect <- gsub("In_Frame_Ins", "Insertion", allmutations$effect)
allmutations$effect <- gsub("Flank", "Others", allmutations$effect)
allmutations$effect <- gsub("Intron","Others", allmutations$effect)
allmutations$effect <- gsub("larged","D", allmutations$effect)
allmutations$effect <- gsub("_Mutation","", allmutations$effect)
allmutations$effect <- gsub("RNA", "Others", allmutations$effect)
allmutations$effect <- gsub("_S", " s", allmutations$effect)
allmutations$effect <- gsub("Translation start site", "Others", allmutations$effect)
allmutations$effect <- gsub("UTR", "Others", allmutations$effect)
allmutations$effect <- gsub("[^A-z]", "", allmutations$effect)
allmutations$PolyPhen <- gsub("^$","unknown",allmutations$PolyPhen) # recoding polyphen & SIFT #
allmutations$SIFT <- gsub("^$","unknown",allmutations$SIFT)
conv <- fread("uniprot_matrisome.txt", header=F, sep="\t") # load conversion from genes to protein names #
conv <- conv[,c(1:2)]
colnames(conv) <- c("gene","Uniprot_ID")
mat <- fread("matrisome_hs.txt", header = T, sep="\t") 
conv <- merge(conv, mat, by.x="gene", by.y="Gene Symbol")
conv <- as.data.frame(conv)
allmutations$is.matrisome <- ifelse(allmutations$gene %in% mat$`Gene Symbol`, "matrisome", "rest.of.genome")
cl <- fread("TCGAclindata.txt", header=T,sep="\t")
cl <- cl[,c(1,3)]
cl <- unique(merge(cl, allmutations, by="sample"))
allmutations <- cl

res <- list()
res2 <- list()
stp <- 0
pb <- txtProgressBar(min = 0, max = length(unique(allmutations_PTMs$gene)), style = 3)
for(i in unique(allmutations_PTMs$gene)){
  stp <- stp+1
  setTxtProgressBar(pb, stp)
  z1 <- subset(allmutations_PTMs, allmutations_PTMs$gene == i)
  z2 <- subset(allmutations, allmutations$gene == i)
  z2 <- subset(z2, z2$effect =="Silent")
  r1 <- nrow(z1)/nrow(z2)
  z3 <- subset(allmutations, allmutations$gene == i)
  z3 <- subset(z3, z3$effect !="Silent")
  r2 <- (nrow(z3)-nrow(z1))/nrow(z2)
  
  df <- data.frame(gene=i,dN_dS_tot=r2,dN_dS_PTM=r1,PTM=z1$PTM)
  
  res[[i]] <- df
}
res <- bind_rows(res)
res$is.matrisome <- ifelse(res$gene %in% mat$`Gene Symbol`, "matrisome", "rest.of.genome")
res$r <- res$dN_dS_PTM/res$dN_dS_tot

res2 <- list()
for(i in unique(res$PTM)[1:7]){
  x <- subset(res,res$PTM == i & res$is.matrisome == "matrisome")
  y <- subset(res,res$PTM == i & res$is.matrisome != "matrisome")
  tt <- t.test(x$r,y$r)
  df <- data.frame(PTM=i,mean.of.r.matrisome=tt$estimate[1],mean.of.r.rest=tt$estimate[2],pval=tt$p.value,direction=ifelse(tt$estimate[1]>tt$estimate[2],"enriched in matrisome","depleted in matrisome"))
  res2[[i]] <- df
}
res2 <- bind_rows(res2)
m <- reshape2::melt(res2, id.var="PTM")
m <- m[1:14,]

ggplot(m,aes(PTM, as.numeric(value), colour=variable), show.legend=FALSE) +
  geom_boxplot() +
  theme_bw() + theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1))

res2$diff <- res2$mean.of.r.matrisome/mean.of.r.rest
m <- reshape2::melt(res2, id.var="PTM")
m <- m[29:nrow(m),]
ggplot(m,aes(PTM,value,fill=PTM), show.legend=FALSE) +
  geom_bar(stat="identity") +
  theme_bw() + theme() + theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1)) + scale_fill_d3()

fwrite(res2,"table pvals dN-dS.txt",sep="\t",row.names = F, quote = F)

PTMmut in the tumor matrisome at the protein domain level. Comparing PTMmut and non-PTMmut we expect to find enrichments/depletions at some domains that might explain why some frequent types of PTMs are not in the top-10 list. To this aim, we proceed as follows: 1) map PTMmut and non-PTMmut into protein domains 2) subset to PFAM domains only 3) compare the ratio of PTMmut to non-PTMmut, by domain, in matrisome and non matrisome genes 4) evaluate the 53 domains in common between matrisome and non matrisome for their ratios 5) evaluate the ratios by PTM types in these domains. O-glycosylation and actylation are, in fact, less “tolerated”!

library(ensembldb)
library(EnsDb.Hsapiens.v86)
edb <- EnsDb.Hsapiens.v86
library(magrittr)
pnames <- edb %>% filter(~ symbol == unique(allmutations_PTMs$gene) & tx_biotype =="protein_coding") %>% proteins
z <- select(edb, keys = pnames$protein_id, keytype = "PROTEINID",
            columns = c("PROTEINID", "GENENAME", "PROTDOMSTART", "PROTDOMEND", "PROTEINDOMAINID", "PROTEINDOMAINSOURCE"))
pos <- allmutations_PTMs[,c(1,2,4,14,17)]
pos <- pos[pos$gene %in% z$GENENAME,]
pos <- pos[order(pos$`cancer type abbreviation`),]
library(sqldf)
library(bit64)
res <- list()
res2 <- list()
for(i in unique(pos$'cancer type abbreviation')){
  print(paste0("PTM scanning domains for: ",i))
  k <- unique(subset(pos, pos$'cancer type abbreviation' == i))
  pb <- txtProgressBar(min = 0, max = length(unique(k$gene)), style = 3)
  stp <- 0
  for(q in unique(k$gene)){
    stp <- stp+1
    setTxtProgressBar(pb, stp)
    k2 <- subset(k, k$gene == q)
    nz <- subset(z, z$GENENAME == q)
    db <- sqldf("select * from k2
                left join nz
                on k2.Amino_Acid_Change between nz.PROTDOMSTART and nz.PROTDOMEND")
    db <- na.omit(db)
    res[[q]] <- db
  } 
  res2[[i]] <- bind_rows(res)
}
res2 <- bind_rows(res2)
res2 <- distinct(res2)
dom_ptm <- res2

allmutations_PTMs$mut <- paste0(allmutations_PTMs$gene,"_",allmutations_PTMs$Amino_Acid_Change,"_",allmutations_PTMs$sample)
allmutations$mut <- paste0(allmutations$gene,"_",allmutations$Amino_Acid_Change,"_",allmutations$sample)
pos <- subset(allmutations, !(allmutations$mut %in% allmutations_PTMs$mut))
pos <- pos[,c(8,1,2,10,14)]
pos <- pos[pos$gene %in% z$GENENAME,]
pos <- na.omit(pos)
pos <- pos[order(pos$`cancer type abbreviation`),]
res <- list()
res2 <- list()
for(i in unique(pos$'cancer type abbreviation')){
  print(paste0("NON-PTM scanning domains for: ",i))
  k <- unique(subset(pos, pos$'cancer type abbreviation' == i))
  pb <- txtProgressBar(min = 0, max = length(unique(k$gene)), style = 3)
  stp <- 0
  for(q in unique(k$gene)){
    stp <- stp+1
    setTxtProgressBar(pb, stp)
    k2 <- subset(k, k$gene == q)
    nz <- subset(z, z$GENENAME == q)
    db <- sqldf("select * from k2
                left join nz
                on k2.Amino_Acid_Change between nz.PROTDOMSTART and nz.PROTDOMEND")
    db <- na.omit(db)
    res[[q]] <- db
  } 
  res2[[i]] <- bind_rows(res)
}
res2 <- bind_rows(res2)
res2 <- distinct(res2)
dom_nonptm <- res2

#for a comparison, let's focus on PFAM domains only#
d1 <- subset(dom_ptm, dom_ptm$PROTEINDOMAINSOURCE == "pfam" & dom_ptm$is.matrisome == "matrisome")
d2 <- subset(dom_ptm, dom_ptm$PROTEINDOMAINSOURCE == "pfam" & dom_ptm$is.matrisome != "matrisome")
d3 <- subset(dom_nonptm, dom_nonptm$PROTEINDOMAINSOURCE == "pfam")

res <- list()
stp <- 0
print("checking mutation frequencies in matrisome domains")
pb <- txtProgressBar(min = 0, max = length(unique(d1$PROTEINDOMAINID)), style = 3)
for(i in unique(d1$PROTEINDOMAINID)){
  stp <- stp+1
  setTxtProgressBar(pb, stp)
  z <- subset(d3, d3$PROTEINDOMAINID == i)
  z <- as.data.table(z)[,.N, by = gene]
  z <- mean(z$N)
  z2 <- subset(d1, d1$PROTEINDOMAINID == i)
  z2 <- as.data.table(z2)[,.N, by = gene]
  z2 <- mean(z2$N)
  res[[i]] <- data.frame(domain=i, freq=(z2/z))
}
res <- bind_rows(res)

res2 <- list()
stp <- 0
print("checking mutation frequencies in non-matrisome domains")
pb <- txtProgressBar(min = 0, max = length(unique(d2$PROTEINDOMAINID)), style = 3)
for(i in unique(d2$PROTEINDOMAINID)){
  stp <- stp+1
  setTxtProgressBar(pb, stp)
  z <- subset(d3, d3$PROTEINDOMAINID == i)
  z <- as.data.table(z)[,.N, by = gene]
  z <- mean(z$N)
  z2 <- subset(d2, d2$PROTEINDOMAINID == i)
  z2 <- as.data.table(z2)[,.N, by = gene]
  z2 <- mean(z2$N)
  res2[[i]] <- data.frame(domain=i, freq=(z2/z))
}
res2 <- bind_rows(res2)
toex <- merge(res,res2,by="domain",all=T)
toex[is.na(toex)] <- 0
colnames(toex) <- c("PFAM domain","PTMmut/allmut frequency in matrisome", "PTMmut/allmut frequency in rest of genome")
toex$enrich <- ifelse(toex$`PTMmut/allmut frequency in matrisome`> toex$`PTMmut/allmut frequency in rest of genome`,"matrisome","rest of genome")
fwrite(toex,"relative freq of mutations at the domain level.txt",sep="\t",row.names = F, quote = F)

mt <- subset(toex,toex$`PTMmut/allmut frequency in matrisome`> toex$`PTMmut/allmut frequency in rest of genome`)
length(unique(mt$`PFAM domain`)) #128 enriched domains#

m1 <- subset(d1, d1$PROTEINDOMAINID %in% mt$`PFAM domain`)
m2 <- subset(d2, d2$PROTEINDOMAINID %in% mt$`PFAM domain`)
m1 <- distinct(m1[,c("gene","PTM","PROTEINDOMAINID")])
m2 <- distinct(m2[,c("gene","PTM","PROTEINDOMAINID")])
m3 <- distinct(d3[,c("gene","PROTEINDOMAINID")])

res1 <- list()
for(i in unique(m1$PROTEINDOMAINID)){
  z <- subset(m1, m1$PROTEINDOMAINID == i)
  z <- as.data.frame(table(z$PTM))
  z2 <- subset(m3, m3$PROTEINDOMAINID == i)
  z2 <- nrow(z2)
  z$tot <- z2
  z$rel.freq <- z$Freq/z$tot
  res1[[i]] <- z
}
res1 <- bind_rows(res1)
res1 <- aggregate(res1$rel.freq,by=list(res1$Var1),mean)

res2 <- list()
for(i in unique(m2$PROTEINDOMAINID)){
  z <- subset(m2, m2$PROTEINDOMAINID == i)
  z <- as.data.frame(table(z$PTM))
  z2 <- subset(m3, m3$PROTEINDOMAINID == i)
  z2 <- nrow(z2)
  z$tot <- z2
  z$rel.freq <- z$Freq/z$tot
  res2[[i]] <- z
}
res2 <- bind_rows(res2)
res2 <- aggregate(res2$rel.freq,by=list(res2$Var1),mean)

res <- merge(res1,res2,by="Group.1")
res[5,3] <- 1 #proxy
m <- reshape2::melt(res)

ggplot(data=m, aes(x=Group.1, y=value, fill=variable)) +
  geom_bar(stat="identity", position = "dodge") +
  theme_bw() +
  scale_fill_aaas() + 
  theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1))

Are there correlations between PTMmut location and gene function?

tab <- as.data.frame(fread("relative freq of mutations at the domain level.txt",sep="\t",header = T, dec=","))
library(clusterProfiler)
library(org.Hs.eg.db)
library(cowplot)

tab.mat <- subset(tab, tab[,2]>=2*tab[,3])
sel <- subset(dom_ptm, dom_ptm$PROTEINDOMAINID %in% tab.mat$`PFAM domain`)
gene <- table(sel$gene)
gene <- gene[order(-gene)]

ego <- enrichKEGG(gene)
p1 <- dotplot(ego, showCategory=10, title="matrisome enriched")

tab.mat <- subset(tab, tab[,3]>=2*tab[,2])
sel <- subset(dom_ptm, dom_ptm$PROTEINDOMAINID %in% tab.mat$`PFAM domain`)
gene2 <- table(sel$gene)
gene2 <- gene2[order(-gene2)]

ego <- enrichKEGG(gene2)
p2 <- dotplot(ego, showCategory=10, title="rest of genome enriched")

plot_grid(p1, p2, ncol=2)

ego <- enrichGO(gene          = gene,
                OrgDb         = org.Hs.eg.db,
                ont           = "BP",
                pAdjustMethod = "BH",
                pvalueCutoff  = 0.01,
                qvalueCutoff  = 0.05,
        readable      = TRUE)
p1 <- dotplot(ego)

ego <- enrichGO(gene          = gene2,
                OrgDb         = org.Hs.eg.db,
                ont           = "BP",
                pAdjustMethod = "BH",
                pvalueCutoff  = 0.01,
                qvalueCutoff  = 0.05,
        readable      = TRUE)
p2 <- dotplot(ego)
plot_grid(p1, p2, ncol=2)

l <- list(mat=gene,rest=gene2)
ck <- compareCluster(geneCluster = l, 
                fun = "enrichGO",
                OrgDb         = org.Hs.eg.db,
                ont           = "BP",
                pAdjustMethod = "BH",
                pvalueCutoff  = 0.01,
                qvalueCutoff  = 0.05)
dotplot(ck)

And are there matrisome PTMmut associated with protein functions?

reg <- as.data.frame(fread("regions.txt",header=T,sep="\t"))
reg <- reg[,c(1:16)]
reg <- subset(reg, reg$is.matrisome=="matrisome")
reg$`region I` <- ifelse(reg$`region I` == "iside","inside",reg$`region I`)

#of 921 mutations annotated with region information, 286 (286/921, 31%) fall in a functional region#

fmut <- subset(reg, reg$`region I` == "inside")
tfmut <- table(fmut$gene)
tfmut <- tfmut[order(-tfmut)]

Finally, do the proteins targeted by PTMmut interact? To answer this question, we will use PPI data from BioGrid v. 4.2.192.

ppi <- as.data.frame(fread("BIOGRID-ORGANISM-Homo_sapiens-4.2.192.tab3.txt",header=T,sep="\t"))
ppi <- ppi[,c(8,9)]

tab.mat <- subset(tab, tab[,2]>=tab[,3])
tab.mat <- subset(tab, tab$enrich == "matrisome")
sel <- subset(dom_ptm, dom_ptm$PROTEINDOMAINID %in% tab.mat$`PFAM domain`)
sel <- unique(sel$gene)
exp <- data.frame(gene=sel)
fwrite(exp,"gene with PTMmut in enriched domains.txt",sep="\t",row.names = F, quote = F)
ppi.mat <- subset(ppi, ppi$`Official Symbol Interactor A` %in% sel & ppi$`Official Symbol Interactor B` %in% sel)
ppi.mat$type <- ifelse(ppi.mat[,1]==ppi.mat[,2],"homo","hetero")
fwrite(ppi.mat,"PPI interactions in enriched domains.txt",sep="\t",row.names = F, quote = F)

#total would be:
#ppi.mat <- subset(ppi, ppi$`Official Symbol Interactor A` %in% sel | ppi$`Official Symbol Interactor B` %in% sel)

library(igraph)
net <- simplify(graph_from_data_frame(ppi.mat), remove.multiple = F, remove.loops = T)

V(net)$size <- 8
V(net)$frame.color <- "white"
#V(net)$color <- "orange"
E(net)$arrow.mode <- 0
deg <- degree(net, mode = "all")

g <- degree(net)
g <- g[order(-g)]
g <- names(g[1:10])

V(net)$label <- ifelse(V(net)$name %in% g, V(net)$name,"") 
V(net)$label.color <- "black"
V(net)$color <- ifelse(V(net)$label %in% names(tfmut), "red","orange")
l <- layout_with_fr(net)
plot(net, layout=l, vertex.size=deg*0.5) #this is for reference, labels will be added in post-production#
sel <- V(net)$label[V(net)$label %in% names(tfmut)]

V(net)$label <- ""
l <- layout_with_fr(net)
plot(net, layout=l, vertex.size=deg*0.5) 

Backtracking the TPMmut into their regions and domains

z <- fread("regions.txt",sep="\t",header=T)
z <- subset(z, z$is.matrisome != "rest.of.genome")
z <- subset(z, z$`region I` %in% c("inside","iside"))
z <- z[,c(1:18)]
sel <- unique(c(ppi.mat$`Official Symbol Interactor A`,ppi.mat$`Official Symbol Interactor B`))
z2 <- subset(z, z$gene %in% sel)
z2 <- as.data.frame(z2)

res <- list()
for(i in unique(z2$gene)){
  k <- subset(ppi.mat, ppi.mat$`Official Symbol Interactor A` == i | ppi.mat$`Official Symbol Interactor B` == i)
  k <- unique(c(k[,1],k[,2]))
  k <- paste(k, collapse = ",")
  df <- data.frame(gene=i, all.interacting.genes=k)
  res[[i]] <- df
}
res <- bind_rows(res)

m <- merge(z2,res,by="gene")
m$V11 <- NULL
m$V12 <- NULL
m$V17 <- NULL
m$V18 <- NULL

length(unique(m$gene))
fwrite(m, "FINAL MAPPING TAB.txt", sep="\t", row.names = F, quote = F)

net <- simplify(graph_from_data_frame(ppi.mat), remove.multiple = F, remove.loops = T)

V(net)$size <- 8
V(net)$frame.color <- "white"
E(net)$arrow.mode <- 0
V(net)$color <- "orange"
V(net)$color <- ifelse(names(V(net)) %in% m$gene, "red","orange")
V(net)$label <- ""
#n <- c("FN1","COL1A1","MMP2","ADAM10")
#V(net)$label <- ifelse(names(V(net)) %in% n,n,"") #to see labels of most-interesting hubs

deg <- degree(net, mode = "all")

l <- layout_with_fr(net)
plot(net, layout=l, vertex.size=deg*0.5) 

EXTRA IDEAS (from further thinking of, and discussing, the findings):

What is the % of different amino acids impacted by PTMmut?

tab <- fread("TABLE 1.txt",header=T,sep="\t")
allmutations <- fread("allmutations.txt", header=T, sep="\t") # import mutation data #
allmutations <- as.data.frame(allmutations)
allmutations$Amino_Acid_Change <- gsub("p.","",allmutations$Amino_Acid_Change)
allmutations$Amino_Acid_Change <- substring(allmutations$Amino_Acid_Change, 1, 1)
m <- merge(tab, allmutations, by.x=c("sample","gene","chr","start","end"), by.y=c("sample","gene","chr","start","end"))
tab <- table(m$PTM, m$Amino_Acid_Change.y)
tab <- tab[,-1]
tab <- round((tab/rowSums(tab))*100,0)
tab <- as.data.frame.matrix(tab)
tab$PTM <- rownames(tab)
tab <- tab[,c(22,1:21)]
fwrite(tab,"percentage of mutated aminoacids_PTM.txt", sep="\t",row.names = F, quote = F)

Are there patients with concurring/coexisting mutations in potentially interacting proteins?

ppi <- as.data.frame(fread("BIOGRID-ORGANISM-Homo_sapiens-4.2.192.tab3.txt",header=T,sep="\t"))
ppi <- ppi[,c(8,9)]
tab <- as.data.frame(fread("TABLE 1.txt",header=T,sep="\t"))
tab <- subset(tab, tab$is.matrisome == "matrisome")

res <- list()
pb <- txtProgressBar(min = 0, max = length(unique(tab$sample)), style = 3)
stp <- 0
for(i in unique(tab$sample)){
  stp <- stp+1
  setTxtProgressBar(pb, stp)
  z <- subset(tab, tab$sample %in% i)
  g <- unique(z$gene)
  s1 <- subset(ppi, ppi$`Official Symbol Interactor A` %in% g & ppi$`Official Symbol Interactor B` %in%  g)
  if(nrow(s1)==0){next}else{
    s1 <- subset(s1, s1$`Official Symbol Interactor A` != s1$`Official Symbol Interactor B`)
    if(nrow(s1)==0){next}else{
      s1$patient <- i
      res[[i]] <- s1
    }
  }
}
res <- bind_rows(res)
res <- unique(res)
length(unique(res$patient))
fwrite(res, "TABLE 2A.txt", sep="\t", row.names = F, quote = F)
res1 <- res

tab <- as.data.frame(fread("TABLE 1.txt",header=T,sep="\t"))
tab <- subset(tab, tab$is.matrisome != "matrisome")
res <- list()
pb <- txtProgressBar(min = 0, max = length(unique(tab$sample)), style = 3)
stp <- 0
for(i in unique(tab$sample)){
  stp <- stp+1
  setTxtProgressBar(pb, stp)
  z <- subset(tab, tab$sample %in% i)
  g <- unique(z$gene)
  s1 <- subset(ppi, ppi$`Official Symbol Interactor A` %in% g & ppi$`Official Symbol Interactor B` %in%  g)
  if(nrow(s1)==0){next}else{
    s1 <- subset(s1, s1$`Official Symbol Interactor A` != s1$`Official Symbol Interactor B`)
    if(nrow(s1)==0){next}else{
      s1$patient <- i
      res[[i]] <- s1
    }
  }
}
res <- bind_rows(res)
res <- unique(res)
length(unique(res$patient))

tab <- as.data.frame(fread("TABLE 1.txt",header=T,sep="\t"))
tab <- subset(tab, tab$is.matrisome == "matrisome")
nrow(tab)
tab <- as.data.frame(fread("TABLE 1.txt",header=T,sep="\t"))
tab <- subset(tab, tab$is.matrisome != "matrisome")
nrow(tab)

m <- matrix(c(nrow(res1),nrow(res),1907,43962),ncol=2,nrow=2) 
chisq.test(m) #p-value < 2.2e-16

Are there mutations in motifs mediating cell/ECM adhesions (GPP, RGD, LDV, collagen motifs - GFPGER, GLPGER, the latter two taken from doi: 10.1074/jbc.M806865200)? First, let’s check PTMmut. Results show that this kind of event is extremely infrequent, so much so as to ask whether other kind of mutations are similarly excluded.

tab <- fread("TABLE 1.txt",header=T,sep="\t")
allmutations <- fread("allmutations.txt", header=T, sep="\t") # import mutation data #
allmutations <- as.data.frame(allmutations)
allmutations$Amino_Acid_Change <- gsub("p.","",allmutations$Amino_Acid_Change)
allmutations$Amino_Acid_Change <- substring(allmutations$Amino_Acid_Change, 1, 1)
m <- merge(tab, allmutations, by.x=c("sample","gene","chr","start","end"), by.y=c("sample","gene","chr","start","end"))
m <- subset(m, m$is.matrisome == "matrisome")
sm <- m

library(EnsDb.Hsapiens.v86)  
library(ensembldb)
library(Biostrings)
library(bit64)

### GPP filter ###

#first, filter all proteins that have no GPP at all#
m <- sm
l <- list()
pb <- txtProgressBar(min = 0, max = length(unique(m$gene)), style = 3)
stp <- 0
for(i in unique(m$gene)){
  stp <- stp+1
  setTxtProgressBar(pb, stp)
  z <- subset(m, m$gene == i)
  g <- proteins(EnsDb.Hsapiens.v86, filter= GeneNameFilter(i), columns=c("uniprot_id","protein_sequence"),return.type = "data.frame")
  g <- subset(g, g$uniprot_id %in% unique(z$Uniprot_ID))
  g$r <- grepl("GPP",g$protein_sequence, fixed = TRUE)
  g <- as.data.frame(g)
  g$protein_sequence <- NULL
  l[[i]] <- g
}
l <- bind_rows(l)
l <- subset(l, l$r != "FALSE")

#next, backfilter m to include only the genes with a valid entry in l#
m <- subset(m, m$gene %in% l$gene_name & m$Uniprot_ID %in% l$uniprot_id)

#and then, the actual filter#
pb <- txtProgressBar(min = 0, max = length(unique(m$gene)), style = 3)
stp <- 0
res <- list()
res2 <- list()
for(i in unique(m$gene)){
  stp <- stp+1
  setTxtProgressBar(pb, stp)
  z <- subset(m, m$gene == i)
  g <- proteins(EnsDb.Hsapiens.v86, filter= GeneNameFilter(i), columns=c("uniprot_id","protein_sequence"),return.type = "data.frame")
  g <- subset(g, g$uniprot_id %in% unique(z$Uniprot_ID))
  if(nrow(g)==0){df <- data.frame(gene=i,position=0,available.prot.seq=0,of.which.seq.found=0,is.GVD="FALSE")
  res[[i]] <- df
  df <- NULL}else{
    g$n.aa <- nchar(g$protein_sequence)
    aa <- unique(z$Amino_Acid_Change.x)

    for(k in aa){
     min.aa <- k-3
     max.aa <- k+3
     s <- subset(g, g$n.aa >= min.aa & g$n.aa >= max.aa)
     if(nrow(s)==0){df <- data.frame(gene=i,position=0,available.prot.seq=0,of.which.seq.found=0,is.GVD="FALSE")
  res[[i]] <- df
  df <- NULL}else{
      
      for(w in 1:nrow(s)){
        v <- s[w,2]
        v <- subseq(v, start=min.aa, end=max.aa)
        r <- grepl("GPP",v, fixed = TRUE)
        res2[[stp]] <- data.frame(gene=i,position=k,available.prot.seq=nrow(g),of.which.seq.found=w,is.GVD=as.character(r))
      }}
    }
  }
}
res <- unique(bind_rows(res2))
res <- subset(res, res$is.GVD == "TRUE")
gpp <- res
gpp$motif <- "GPP"

### GVD filter ###

#first, filter all proteins that have no GVD at all#
m <- sm
l <- list()
pb <- txtProgressBar(min = 0, max = length(unique(m$gene)), style = 3)
stp <- 0
for(i in unique(m$gene)){
  stp <- stp+1
  setTxtProgressBar(pb, stp)
  z <- subset(m, m$gene == i)
  g <- proteins(EnsDb.Hsapiens.v86, filter= GeneNameFilter(i), columns=c("uniprot_id","protein_sequence"),return.type = "data.frame")
  g <- subset(g, g$uniprot_id %in% unique(z$Uniprot_ID))
  g$r <- grepl("GVD",g$protein_sequence, fixed = TRUE)
  g <- as.data.frame(g)
  g$protein_sequence <- NULL
  l[[i]] <- g
}
l <- bind_rows(l)
l <- subset(l, l$r != "FALSE")

#next, backfilter m to include only the genes with a valid entry in l#
m <- subset(m, m$gene %in% l$gene_name & m$Uniprot_ID %in% l$uniprot_id)

#and then, the actual filter#
pb <- txtProgressBar(min = 0, max = length(unique(m$gene)), style = 3)
stp <- 0
res <- list()
res2 <- list()
for(i in unique(m$gene)){
  stp <- stp+1
  setTxtProgressBar(pb, stp)
  z <- subset(m, m$gene == i)
  g <- proteins(EnsDb.Hsapiens.v86, filter= GeneNameFilter(i), columns=c("uniprot_id","protein_sequence"),return.type = "data.frame")
  g <- subset(g, g$uniprot_id %in% unique(z$Uniprot_ID))
  if(nrow(g)==0){df <- data.frame(gene=i,position=0,available.prot.seq=0,of.which.seq.found=0,is.GVD="FALSE")
  res[[i]] <- df
  df <- NULL}else{
    g$n.aa <- nchar(g$protein_sequence)
    aa <- unique(z$Amino_Acid_Change.x)

    for(k in aa){
     min.aa <- k-3
     max.aa <- k+3
     s <- subset(g, g$n.aa >= min.aa & g$n.aa >= max.aa)
     if(nrow(s)==0){df <- data.frame(gene=i,position=0,available.prot.seq=0,of.which.seq.found=0,is.GVD="FALSE")
  res[[i]] <- df
  df <- NULL}else{
      
      for(w in 1:nrow(s)){
        v <- s[w,2]
        v <- subseq(v, start=min.aa, end=max.aa)
        r <- grepl("GVD",v, fixed = TRUE)
        res2[[stp]] <- data.frame(gene=i,position=k,available.prot.seq=nrow(g),of.which.seq.found=w,is.GVD=as.character(r))
      }}
    }
  }
}
res <- unique(bind_rows(res2))
res <- subset(res, res$is.GVD == "TRUE")
gvd <- res
gvd$motif <- "GVD"
     
### RGD filter ###

#first, filter all proteins that have no RGD at all#
m <- sm
l <- list()
pb <- txtProgressBar(min = 0, max = length(unique(m$gene)), style = 3)
stp <- 0
for(i in unique(m$gene)){
  stp <- stp+1
  setTxtProgressBar(pb, stp)
  z <- subset(m, m$gene == i)
  g <- proteins(EnsDb.Hsapiens.v86, filter= GeneNameFilter(i), columns=c("uniprot_id","protein_sequence"),return.type = "data.frame")
  g <- subset(g, g$uniprot_id %in% unique(z$Uniprot_ID))
  g$r <- grepl("RGD",g$protein_sequence, fixed = TRUE)
  g <- as.data.frame(g)
  g$protein_sequence <- NULL
  l[[i]] <- g
}
l <- bind_rows(l)
l <- subset(l, l$r != "FALSE")

#next, backfilter m to include only the genes with a valid entry in l#
m <- subset(m, m$gene %in% l$gene_name & m$Uniprot_ID %in% l$uniprot_id)

#and then, the actual filter#
pb <- txtProgressBar(min = 0, max = length(unique(m$gene)), style = 3)
stp <- 0
res <- list()
res2 <- list()
for(i in unique(m$gene)){
  stp <- stp+1
  setTxtProgressBar(pb, stp)
  z <- subset(m, m$gene == i)
  g <- proteins(EnsDb.Hsapiens.v86, filter= GeneNameFilter(i), columns=c("uniprot_id","protein_sequence"),return.type = "data.frame")
  g <- subset(g, g$uniprot_id %in% unique(z$Uniprot_ID))
  if(nrow(g)==0){df <- data.frame(gene=i,position=0,available.prot.seq=0,of.which.seq.found=0,is.GVD="FALSE")
  res[[i]] <- df
  df <- NULL}else{
    g$n.aa <- nchar(g$protein_sequence)
    aa <- unique(z$Amino_Acid_Change.x)

    for(k in aa){
     min.aa <- k-3
     max.aa <- k+3
     s <- subset(g, g$n.aa >= min.aa & g$n.aa >= max.aa)
     if(nrow(s)==0){df <- data.frame(gene=i,position=0,available.prot.seq=0,of.which.seq.found=0,is.GVD="FALSE")
  res[[i]] <- df
  df <- NULL}else{
      
      for(w in 1:nrow(s)){
        v <- s[w,2]
        v <- subseq(v, start=min.aa, end=max.aa)
        r <- grepl("RGD",v, fixed = TRUE)
        res2[[stp]] <- data.frame(gene=i,position=k,available.prot.seq=nrow(g),of.which.seq.found=w,is.GVD=as.character(r))
      }}
    }
  }
}
res <- unique(bind_rows(res2))
res <- subset(res, res$is.GVD == "TRUE")
rgd <- res
#rgd$motif <- "RGD" #no RGD!

### LDV filter ###

#first, filter all proteins that have no LDV at all#
m <- sm
l <- list()
pb <- txtProgressBar(min = 0, max = length(unique(m$gene)), style = 3)
stp <- 0
for(i in unique(m$gene)){
  stp <- stp+1
  setTxtProgressBar(pb, stp)
  z <- subset(m, m$gene == i)
  g <- proteins(EnsDb.Hsapiens.v86, filter= GeneNameFilter(i), columns=c("uniprot_id","protein_sequence"),return.type = "data.frame")
  g <- subset(g, g$uniprot_id %in% unique(z$Uniprot_ID))
  g$r <- grepl("LDV",g$protein_sequence, fixed = TRUE)
  g <- as.data.frame(g)
  g$protein_sequence <- NULL
  l[[i]] <- g
}
l <- bind_rows(l)
l <- subset(l, l$r != "FALSE")

#next, backfilter m to include only the genes with a valid entry in l#
m <- subset(m, m$gene %in% l$gene_name & m$Uniprot_ID %in% l$uniprot_id)

#and then, the actual filter#
pb <- txtProgressBar(min = 0, max = length(unique(m$gene)), style = 3)
stp <- 0
res <- list()
res2 <- list()
for(i in unique(m$gene)){
  stp <- stp+1
  setTxtProgressBar(pb, stp)
  z <- subset(m, m$gene == i)
  g <- proteins(EnsDb.Hsapiens.v86, filter= GeneNameFilter(i), columns=c("uniprot_id","protein_sequence"),return.type = "data.frame")
  g <- subset(g, g$uniprot_id %in% unique(z$Uniprot_ID))
  if(nrow(g)==0){df <- data.frame(gene=i,position=0,available.prot.seq=0,of.which.seq.found=0,is.GVD="FALSE")
  res[[i]] <- df
  df <- NULL}else{
    g$n.aa <- nchar(g$protein_sequence)
    aa <- unique(z$Amino_Acid_Change.x)

    for(k in aa){
     min.aa <- k-3
     max.aa <- k+3
     s <- subset(g, g$n.aa >= min.aa & g$n.aa >= max.aa)
     if(nrow(s)==0){df <- data.frame(gene=i,position=0,available.prot.seq=0,of.which.seq.found=0,is.GVD="FALSE")
  res[[i]] <- df
  df <- NULL}else{
      
      for(w in 1:nrow(s)){
        v <- s[w,2]
        v <- subseq(v, start=min.aa, end=max.aa)
        r <- grepl("LDV",v, fixed = TRUE)
        res2[[stp]] <- data.frame(gene=i,position=k,available.prot.seq=nrow(g),of.which.seq.found=w,is.GVD=as.character(r))
      }}
    }
  }
}
res <- unique(bind_rows(res2))
res <- subset(res, res$is.GVD == "TRUE")
ldv <- res
#ldv$motif <- "LDV" #no LDV

### GFPGER filter ###

#first, filter all proteins that have no LDV at all#
m <- sm
l <- list()
pb <- txtProgressBar(min = 0, max = length(unique(m$gene)), style = 3)
stp <- 0
for(i in unique(m$gene)){
  stp <- stp+1
  setTxtProgressBar(pb, stp)
  z <- subset(m, m$gene == i)
  g <- proteins(EnsDb.Hsapiens.v86, filter= GeneNameFilter(i), columns=c("uniprot_id","protein_sequence"),return.type = "data.frame")
  g <- subset(g, g$uniprot_id %in% unique(z$Uniprot_ID))
  g$r <- grepl("GFPGER",g$protein_sequence, fixed = TRUE)
  g <- as.data.frame(g)
  g$protein_sequence <- NULL
  l[[i]] <- g
}
l <- bind_rows(l)
l <- subset(l, l$r != "FALSE")

#next, backfilter m to include only the genes with a valid entry in l#
m <- subset(m, m$gene %in% l$gene_name & m$Uniprot_ID %in% l$uniprot_id)

#and then, the actual filter#
pb <- txtProgressBar(min = 0, max = length(unique(m$gene)), style = 3)
stp <- 0
res <- list()
res2 <- list()
for(i in unique(m$gene)){
  stp <- stp+1
  setTxtProgressBar(pb, stp)
  z <- subset(m, m$gene == i)
  g <- proteins(EnsDb.Hsapiens.v86, filter= GeneNameFilter(i), columns=c("uniprot_id","protein_sequence"),return.type = "data.frame")
  g <- subset(g, g$uniprot_id %in% unique(z$Uniprot_ID))
  if(nrow(g)==0){df <- data.frame(gene=i,position=0,available.prot.seq=0,of.which.seq.found=0,is.GVD="FALSE")
  res[[i]] <- df
  df <- NULL}else{
    g$n.aa <- nchar(g$protein_sequence)
    aa <- unique(z$Amino_Acid_Change.x)

    for(k in aa){
     min.aa <- k-3
     max.aa <- k+3
     s <- subset(g, g$n.aa >= min.aa & g$n.aa >= max.aa)
     if(nrow(s)==0){df <- data.frame(gene=i,position=0,available.prot.seq=0,of.which.seq.found=0,is.GVD="FALSE")
  res[[i]] <- df
  df <- NULL}else{
      
      for(w in 1:nrow(s)){
        v <- s[w,2]
        v <- subseq(v, start=min.aa, end=max.aa)
        r <- grepl("GFPGER",v, fixed = TRUE)
        res2[[stp]] <- data.frame(gene=i,position=k,available.prot.seq=nrow(g),of.which.seq.found=w,is.GVD=as.character(r))
      }}
    }
  }
}
res <- unique(bind_rows(res2))
res <- subset(res, res$is.GVD == "TRUE")
gfpger <- res
#gfpger$motif <- "GFPGER" #no GFPGER

### GLPGER filter ###

#first, filter all proteins that have no LDV at all#
m <- sm
l <- list()
pb <- txtProgressBar(min = 0, max = length(unique(m$gene)), style = 3)
stp <- 0
for(i in unique(m$gene)){
  stp <- stp+1
  setTxtProgressBar(pb, stp)
  z <- subset(m, m$gene == i)
  g <- proteins(EnsDb.Hsapiens.v86, filter= GeneNameFilter(i), columns=c("uniprot_id","protein_sequence"),return.type = "data.frame")
  g <- subset(g, g$uniprot_id %in% unique(z$Uniprot_ID))
  g$r <- grepl("GLPGER",g$protein_sequence, fixed = TRUE)
  g <- as.data.frame(g)
  g$protein_sequence <- NULL
  l[[i]] <- g
}
l <- bind_rows(l)
l <- subset(l, l$r != "FALSE")

#next, backfilter m to include only the genes with a valid entry in l#
m <- subset(m, m$gene %in% l$gene_name & m$Uniprot_ID %in% l$uniprot_id)

#and then, the actual filter#
pb <- txtProgressBar(min = 0, max = length(unique(m$gene)), style = 3)
stp <- 0
res <- list()
res2 <- list()
for(i in unique(m$gene)){
  stp <- stp+1
  setTxtProgressBar(pb, stp)
  z <- subset(m, m$gene == i)
  g <- proteins(EnsDb.Hsapiens.v86, filter= GeneNameFilter(i), columns=c("uniprot_id","protein_sequence"),return.type = "data.frame")
  g <- subset(g, g$uniprot_id %in% unique(z$Uniprot_ID))
  if(nrow(g)==0){df <- data.frame(gene=i,position=0,available.prot.seq=0,of.which.seq.found=0,is.GVD="FALSE")
  res[[i]] <- df
  df <- NULL}else{
    g$n.aa <- nchar(g$protein_sequence)
    aa <- unique(z$Amino_Acid_Change.x)

    for(k in aa){
     min.aa <- k-3
     max.aa <- k+3
     s <- subset(g, g$n.aa >= min.aa & g$n.aa >= max.aa)
     if(nrow(s)==0){df <- data.frame(gene=i,position=0,available.prot.seq=0,of.which.seq.found=0,is.GVD="FALSE")
  res[[i]] <- df
  df <- NULL}else{
      
      for(w in 1:nrow(s)){
        v <- s[w,2]
        v <- subseq(v, start=min.aa, end=max.aa)
        r <- grepl("GLPGER",v, fixed = TRUE)
        res2[[stp]] <- data.frame(gene=i,position=k,available.prot.seq=nrow(g),of.which.seq.found=w,is.GVD=as.character(r))
      }}
    }
  }
}
res <- unique(bind_rows(res2))
res <- subset(res, res$is.GVD == "TRUE")
glpger <- res
#glpger$motif <- "GFPGER" #no GLPGER

### Bind results together ###

res <- bind_rows(gpp, gvd)
res$id <- paste0(res$gene,"_",res$position)
sm$id <- paste0(sm$gene,"_",sm$Amino_Acid_Change.x)
sm <- subset(sm, select = c("gene","PTM","id"))
res <- merge(sm,res,by="id")
res$id <- NULL
res$gene.y <- NULL
names(res)[1] <- "gene"
names(res)[6] <- "is.GVD.plus.minus.3"
fwrite(res, "MOTIF MAPPING OF PTMmut.txt",sep="\t",row.names = F, quote = F)

Check for non-PTMmut.

tab <- as.data.frame(fread("all mutations.txt",header=T,sep="\t"))
tab2 <- as.data.frame(fread("all PTM mutations.txt",header=T,sep="\t"))
tab$id <- paste0(tab$sample,"_",tab$gene,"_",tab$Amino_Acid_Change,"_",tab$`cancer type abbreviation`)
tab2$id <- paste0(tab2$sample,"_",tab2$gene,"_",tab2$Amino_Acid_Change,"_",tab2$`cancer type abbreviation`)
tab <- subset(tab, !(tab$id %in% tab2$id))
tab <- subset(tab, tab$is.matrisome == "matrisome")
tab <- na.omit(tab)
ntab <- fread("TABLE 1.txt",header=T,sep="\t")
ntab <- subset(ntab, ntab$is.matrisome == "matrisome")
ntab <- subset(ntab, select=c("gene","Uniprot_ID"))
m <- distinct(merge(tab,ntab,by="gene"))
m <- m %>% mutate_if(bit64::is.integer64, as.integer)

sm <- m

### GPP filter ###

#first, filter all proteins that have no GPP at all#
m <- sm
l <- list()
pb <- txtProgressBar(min = 0, max = length(unique(m$gene)), style = 3)
stp <- 0
for(i in unique(m$gene)){
  stp <- stp+1
  setTxtProgressBar(pb, stp)
  z <- subset(m, m$gene == i)
  g <- proteins(EnsDb.Hsapiens.v86, filter= GeneNameFilter(i), columns=c("uniprot_id","protein_sequence"),return.type = "data.frame")
  g <- subset(g, g$uniprot_id %in% unique(z$Uniprot_ID))
  g$r <- grepl("GPP",g$protein_sequence, fixed = TRUE)
  g <- as.data.frame(g)
  g$protein_sequence <- NULL
  l[[i]] <- g
}
l <- bind_rows(l)
l <- subset(l, l$r != "FALSE")

#next, backfilter m to include only the genes with a valid entry in l#
m <- subset(m, m$gene %in% l$gene_name & m$Uniprot_ID %in% l$uniprot_id)

#and then, the actual filter#
pb <- txtProgressBar(min = 0, max = length(unique(m$gene)), style = 3)
stp <- 0
res <- list()
res2 <- list()
for(i in unique(m$gene)){
  stp <- stp+1
  setTxtProgressBar(pb, stp)
  z <- subset(m, m$gene == i)
  g <- proteins(EnsDb.Hsapiens.v86, filter= GeneNameFilter(i), columns=c("uniprot_id","protein_sequence"),return.type = "data.frame")
  g <- subset(g, g$uniprot_id %in% unique(z$Uniprot_ID))
  if(nrow(g)==0){df <- data.frame(gene=i,position=0,available.prot.seq=0,of.which.seq.found=0,is.GVD="FALSE")
  res[[i]] <- df
  df <- NULL}else{
    g$n.aa <- nchar(g$protein_sequence)
    aa <- unique(z$Amino_Acid_Change)
    aa <- aa[aa > 4]

    for(k in aa){
     min.aa <- k-3
     max.aa <- k+3
     s <- subset(g, g$n.aa >= min.aa & g$n.aa >= max.aa)
     if(nrow(s)==0){df <- data.frame(gene=i,position=0,available.prot.seq=0,of.which.seq.found=0,is.GVD="FALSE")
  res[[i]] <- df
  df <- NULL}else{
      
      for(w in 1:nrow(s)){
        v <- s[w,2]
        v <- subseq(v, start=min.aa, end=max.aa)
        r <- grepl("GPP",v, fixed = TRUE)
        res2[[stp]] <- data.frame(gene=i,position=k,available.prot.seq=nrow(g),of.which.seq.found=w,is.GVD=as.character(r))
      }}
    }
  }
}
res <- unique(bind_rows(res2))
res <- subset(res, res$is.GVD == "TRUE")
gpp <- res
gpp$motif <- "GPP"

### GVD filter ###

#first, filter all proteins that have no GVD at all#
m <- sm
l <- list()
pb <- txtProgressBar(min = 0, max = length(unique(m$gene)), style = 3)
stp <- 0
for(i in unique(m$gene)){
  stp <- stp+1
  setTxtProgressBar(pb, stp)
  z <- subset(m, m$gene == i)
  g <- proteins(EnsDb.Hsapiens.v86, filter= GeneNameFilter(i), columns=c("uniprot_id","protein_sequence"),return.type = "data.frame")
  g <- subset(g, g$uniprot_id %in% unique(z$Uniprot_ID))
  g$r <- grepl("GVD",g$protein_sequence, fixed = TRUE)
  g <- as.data.frame(g)
  g$protein_sequence <- NULL
  l[[i]] <- g
}
l <- bind_rows(l)
l <- subset(l, l$r != "FALSE")

#next, backfilter m to include only the genes with a valid entry in l#
m <- subset(m, m$gene %in% l$gene_name & m$Uniprot_ID %in% l$uniprot_id)

#and then, the actual filter#
pb <- txtProgressBar(min = 0, max = length(unique(m$gene)), style = 3)
stp <- 0
res <- list()
res2 <- list()
for(i in unique(m$gene)){
  stp <- stp+1
  setTxtProgressBar(pb, stp)
  z <- subset(m, m$gene == i)
  g <- proteins(EnsDb.Hsapiens.v86, filter= GeneNameFilter(i), columns=c("uniprot_id","protein_sequence"),return.type = "data.frame")
  g <- subset(g, g$uniprot_id %in% unique(z$Uniprot_ID))
  if(nrow(g)==0){df <- data.frame(gene=i,position=0,available.prot.seq=0,of.which.seq.found=0,is.GVD="FALSE")
  res[[i]] <- df
  df <- NULL}else{
    g$n.aa <- nchar(g$protein_sequence)
    aa <- unique(z$Amino_Acid_Change)
    aa <- aa[aa > 4]

    for(k in aa){
     min.aa <- k-3
     max.aa <- k+3
     s <- subset(g, g$n.aa >= min.aa & g$n.aa >= max.aa)
     if(nrow(s)==0){df <- data.frame(gene=i,position=0,available.prot.seq=0,of.which.seq.found=0,is.GVD="FALSE")
  res[[i]] <- df
  df <- NULL}else{
      
      for(w in 1:nrow(s)){
        v <- s[w,2]
        v <- subseq(v, start=min.aa, end=max.aa)
        r <- grepl("GVD",v, fixed = TRUE)
        res2[[stp]] <- data.frame(gene=i,position=k,available.prot.seq=nrow(g),of.which.seq.found=w,is.GVD=as.character(r))
      }}
    }
  }
}
res <- unique(bind_rows(res2))
res <- subset(res, res$is.GVD == "TRUE")
gvd <- res
#gvd$motif <- "GVD" #no GVD!

### RGD filter ###

#first, filter all proteins that have no RGD at all#
m <- sm
l <- list()
pb <- txtProgressBar(min = 0, max = length(unique(m$gene)), style = 3)
stp <- 0
for(i in unique(m$gene)){
  stp <- stp+1
  setTxtProgressBar(pb, stp)
  z <- subset(m, m$gene == i)
  g <- proteins(EnsDb.Hsapiens.v86, filter= GeneNameFilter(i), columns=c("uniprot_id","protein_sequence"),return.type = "data.frame")
  g <- subset(g, g$uniprot_id %in% unique(z$Uniprot_ID))
  g$r <- grepl("RGD",g$protein_sequence, fixed = TRUE)
  g <- as.data.frame(g)
  g$protein_sequence <- NULL
  l[[i]] <- g
}
l <- bind_rows(l)
l <- subset(l, l$r != "FALSE")

#next, backfilter m to include only the genes with a valid entry in l#
m <- subset(m, m$gene %in% l$gene_name & m$Uniprot_ID %in% l$uniprot_id)

#and then, the actual filter#
pb <- txtProgressBar(min = 0, max = length(unique(m$gene)), style = 3)
stp <- 0
res <- list()
res2 <- list()
for(i in unique(m$gene)){
  stp <- stp+1
  setTxtProgressBar(pb, stp)
  z <- subset(m, m$gene == i)
  g <- proteins(EnsDb.Hsapiens.v86, filter= GeneNameFilter(i), columns=c("uniprot_id","protein_sequence"),return.type = "data.frame")
  g <- subset(g, g$uniprot_id %in% unique(z$Uniprot_ID))
  if(nrow(g)==0){df <- data.frame(gene=i,position=0,available.prot.seq=0,of.which.seq.found=0,is.GVD="FALSE")
  res[[i]] <- df
  df <- NULL}else{
    g$n.aa <- nchar(g$protein_sequence)
    aa <- unique(z$Amino_Acid_Change)
    aa <- aa[aa > 4]

    for(k in aa){
     min.aa <- k-3
     max.aa <- k+3
     s <- subset(g, g$n.aa >= min.aa & g$n.aa >= max.aa)
     if(nrow(s)==0){df <- data.frame(gene=i,position=0,available.prot.seq=0,of.which.seq.found=0,is.GVD="FALSE")
  res[[i]] <- df
  df <- NULL}else{
      
      for(w in 1:nrow(s)){
        v <- s[w,2]
        v <- subseq(v, start=min.aa, end=max.aa)
        r <- grepl("RGD",v, fixed = TRUE)
        res2[[stp]] <- data.frame(gene=i,position=k,available.prot.seq=nrow(g),of.which.seq.found=w,is.GVD=as.character(r))
      }}
    }
  }
}
res <- unique(bind_rows(res2))
res <- subset(res, res$is.GVD == "TRUE")
rgd <- res
#rgd$motif <- "RGD" #no RGD

### LDV filter ###

#first, filter all proteins that have no LDV at all#
m <- sm
l <- list()
pb <- txtProgressBar(min = 0, max = length(unique(m$gene)), style = 3)
stp <- 0
for(i in unique(m$gene)){
  stp <- stp+1
  setTxtProgressBar(pb, stp)
  z <- subset(m, m$gene == i)
  g <- proteins(EnsDb.Hsapiens.v86, filter= GeneNameFilter(i), columns=c("uniprot_id","protein_sequence"),return.type = "data.frame")
  g <- subset(g, g$uniprot_id %in% unique(z$Uniprot_ID))
  g$r <- grepl("LDV",g$protein_sequence, fixed = TRUE)
  g <- as.data.frame(g)
  g$protein_sequence <- NULL
  l[[i]] <- g
}
l <- bind_rows(l)
l <- subset(l, l$r != "FALSE")

#next, backfilter m to include only the genes with a valid entry in l#
m <- subset(m, m$gene %in% l$gene_name & m$Uniprot_ID %in% l$uniprot_id)

#and then, the actual filter#
pb <- txtProgressBar(min = 0, max = length(unique(m$gene)), style = 3)
stp <- 0
res <- list()
res2 <- list()
for(i in unique(m$gene)){
  stp <- stp+1
  setTxtProgressBar(pb, stp)
  z <- subset(m, m$gene == i)
  g <- proteins(EnsDb.Hsapiens.v86, filter= GeneNameFilter(i), columns=c("uniprot_id","protein_sequence"),return.type = "data.frame")
  g <- subset(g, g$uniprot_id %in% unique(z$Uniprot_ID))
  if(nrow(g)==0){df <- data.frame(gene=i,position=0,available.prot.seq=0,of.which.seq.found=0,is.GVD="FALSE")
  res[[i]] <- df
  df <- NULL}else{
    g$n.aa <- nchar(g$protein_sequence)
    aa <- unique(z$Amino_Acid_Change)
    aa <- aa[aa > 4]

    for(k in aa){
     min.aa <- k-3
     max.aa <- k+3
     s <- subset(g, g$n.aa >= min.aa & g$n.aa >= max.aa)
     if(nrow(s)==0){df <- data.frame(gene=i,position=0,available.prot.seq=0,of.which.seq.found=0,is.GVD="FALSE")
  res[[i]] <- df
  df <- NULL}else{
      
      for(w in 1:nrow(s)){
        v <- s[w,2]
        v <- subseq(v, start=min.aa, end=max.aa)
        r <- grepl("LDV",v, fixed = TRUE)
        res2[[stp]] <- data.frame(gene=i,position=k,available.prot.seq=nrow(g),of.which.seq.found=w,is.GVD=as.character(r))
      }}
    }
  }
}
res <- unique(bind_rows(res2))
res <- subset(res, res$is.GVD == "TRUE")
ldv <- res
ldv$motif <- "LDV"

### GFPGER filter ###

#first, filter all proteins that have no LDV at all#
m <- sm
l <- list()
pb <- txtProgressBar(min = 0, max = length(unique(m$gene)), style = 3)
stp <- 0
for(i in unique(m$gene)){
  stp <- stp+1
  setTxtProgressBar(pb, stp)
  z <- subset(m, m$gene == i)
  g <- proteins(EnsDb.Hsapiens.v86, filter= GeneNameFilter(i), columns=c("uniprot_id","protein_sequence"),return.type = "data.frame")
  g <- subset(g, g$uniprot_id %in% unique(z$Uniprot_ID))
  g$r <- grepl("GFPGER",g$protein_sequence, fixed = TRUE)
  g <- as.data.frame(g)
  g$protein_sequence <- NULL
  l[[i]] <- g
}
l <- bind_rows(l)
l <- subset(l, l$r != "FALSE")

#next, backfilter m to include only the genes with a valid entry in l#
m <- subset(m, m$gene %in% l$gene_name & m$Uniprot_ID %in% l$uniprot_id)

#and then, the actual filter#
pb <- txtProgressBar(min = 0, max = length(unique(m$gene)), style = 3)
stp <- 0
res <- list()
res2 <- list()
for(i in unique(m$gene)){
  stp <- stp+1
  setTxtProgressBar(pb, stp)
  z <- subset(m, m$gene == i)
  g <- proteins(EnsDb.Hsapiens.v86, filter= GeneNameFilter(i), columns=c("uniprot_id","protein_sequence"),return.type = "data.frame")
  g <- subset(g, g$uniprot_id %in% unique(z$Uniprot_ID))
  if(nrow(g)==0){df <- data.frame(gene=i,position=0,available.prot.seq=0,of.which.seq.found=0,is.GVD="FALSE")
  res[[i]] <- df
  df <- NULL}else{
    g$n.aa <- nchar(g$protein_sequence)
    aa <- unique(z$Amino_Acid_Change)
    aa <- aa[aa > 4]

    for(k in aa){
     min.aa <- k-3
     max.aa <- k+3
     s <- subset(g, g$n.aa >= min.aa & g$n.aa >= max.aa)
     if(nrow(s)==0){df <- data.frame(gene=i,position=0,available.prot.seq=0,of.which.seq.found=0,is.GVD="FALSE")
  res[[i]] <- df
  df <- NULL}else{
      
      for(w in 1:nrow(s)){
        v <- s[w,2]
        v <- subseq(v, start=min.aa, end=max.aa)
        r <- grepl("GFPGER",v, fixed = TRUE)
        res2[[stp]] <- data.frame(gene=i,position=k,available.prot.seq=nrow(g),of.which.seq.found=w,is.GVD=as.character(r))
      }}
    }
  }
}
res <- unique(bind_rows(res2))
res <- subset(res, res$is.GVD == "TRUE")
gfpger <- res
#gfpger$motif <- "GFPGER" #no GFPGER!

### GLPGER filter ###

#first, filter all proteins that have no LDV at all#
m <- sm
l <- list()
pb <- txtProgressBar(min = 0, max = length(unique(m$gene)), style = 3)
stp <- 0
for(i in unique(m$gene)){
  stp <- stp+1
  setTxtProgressBar(pb, stp)
  z <- subset(m, m$gene == i)
  g <- proteins(EnsDb.Hsapiens.v86, filter= GeneNameFilter(i), columns=c("uniprot_id","protein_sequence"),return.type = "data.frame")
  g <- subset(g, g$uniprot_id %in% unique(z$Uniprot_ID))
  g$r <- grepl("GLPGER",g$protein_sequence, fixed = TRUE)
  g <- as.data.frame(g)
  g$protein_sequence <- NULL
  l[[i]] <- g
}
l <- bind_rows(l)
l <- subset(l, l$r != "FALSE")

#next, backfilter m to include only the genes with a valid entry in l#
m <- subset(m, m$gene %in% l$gene_name & m$Uniprot_ID %in% l$uniprot_id)

#and then, the actual filter#
pb <- txtProgressBar(min = 0, max = length(unique(m$gene)), style = 3)
stp <- 0
res <- list()
res2 <- list()
for(i in unique(m$gene)){
  stp <- stp+1
  setTxtProgressBar(pb, stp)
  z <- subset(m, m$gene == i)
  g <- proteins(EnsDb.Hsapiens.v86, filter= GeneNameFilter(i), columns=c("uniprot_id","protein_sequence"),return.type = "data.frame")
  g <- subset(g, g$uniprot_id %in% unique(z$Uniprot_ID))
  if(nrow(g)==0){df <- data.frame(gene=i,position=0,available.prot.seq=0,of.which.seq.found=0,is.GVD="FALSE")
  res[[i]] <- df
  df <- NULL}else{
    g$n.aa <- nchar(g$protein_sequence)
    aa <- unique(z$Amino_Acid_Change)
    aa <- aa[aa > 4]

    for(k in aa){
     min.aa <- k-3
     max.aa <- k+3
     s <- subset(g, g$n.aa >= min.aa & g$n.aa >= max.aa)
     if(nrow(s)==0){df <- data.frame(gene=i,position=0,available.prot.seq=0,of.which.seq.found=0,is.GVD="FALSE")
  res[[i]] <- df
  df <- NULL}else{
      
      for(w in 1:nrow(s)){
        v <- s[w,2]
        v <- subseq(v, start=min.aa, end=max.aa)
        r <- grepl("GLPGER",v, fixed = TRUE)
        res2[[stp]] <- data.frame(gene=i,position=k,available.prot.seq=nrow(g),of.which.seq.found=w,is.GVD=as.character(r))
      }}
    }
  }
}
res <- unique(bind_rows(res2))
res <- subset(res, res$is.GVD == "TRUE")
glpger <- res
#glpger$motif <- "GLPGER" #no GLPGER!



tab <- fread("TABLE 1.txt",header=T,sep="\t")
allmutations <- fread("allmutations.txt", header=T, sep="\t") # import mutation data #
allmutations <- as.data.frame(allmutations)
allmutations$Amino_Acid_Change <- gsub("p.","",allmutations$Amino_Acid_Change)
allmutations$Amino_Acid_Change <- substring(allmutations$Amino_Acid_Change, 1, 1)
m <- merge(tab, allmutations, by.x=c("sample","gene","chr","start","end"), by.y=c("sample","gene","chr","start","end"))
m <- subset(m, m$is.matrisome == "matrisome")
sm <- m

library(EnsDb.Hsapiens.v86)  
library(ensembldb)
library(Biostrings)
library(bit64)

### GPP filter ###

#first, filter all proteins that have no GPP at all#
m <- sm
l <- list()
pb <- txtProgressBar(min = 0, max = length(unique(m$gene)), style = 3)
stp <- 0
for(i in unique(m$gene)){
  stp <- stp+1
  setTxtProgressBar(pb, stp)
  z <- subset(m, m$gene == i)
  g <- proteins(EnsDb.Hsapiens.v86, filter= GeneNameFilter(i), columns=c("uniprot_id","protein_sequence"),return.type = "data.frame")
  g <- subset(g, g$uniprot_id %in% unique(z$Uniprot_ID))
  g$r <- grepl("GPP",g$protein_sequence, fixed = TRUE)
  g <- as.data.frame(g)
  g$protein_sequence <- NULL
  l[[i]] <- g
}
l <- bind_rows(l)
l <- subset(l, l$r != "FALSE")

#next, backfilter m to include only the genes with a valid entry in l#
m <- subset(m, m$gene %in% l$gene_name & m$Uniprot_ID %in% l$uniprot_id)

#and then, the actual filter#
pb <- txtProgressBar(min = 0, max = length(unique(m$gene)), style = 3)
stp <- 0
res <- list()
res2 <- list()
for(i in unique(m$gene)){
  stp <- stp+1
  setTxtProgressBar(pb, stp)
  z <- subset(m, m$gene == i)
  g <- proteins(EnsDb.Hsapiens.v86, filter= GeneNameFilter(i), columns=c("uniprot_id","protein_sequence"),return.type = "data.frame")
  g <- subset(g, g$uniprot_id %in% unique(z$Uniprot_ID))
  if(nrow(g)==0){df <- data.frame(gene=i,position=0,available.prot.seq=0,of.which.seq.found=0,is.GVD="FALSE")
  res[[i]] <- df
  df <- NULL}else{
    g$n.aa <- nchar(g$protein_sequence)
    aa <- unique(z$Amino_Acid_Change.x)

    for(k in aa){
     min.aa <- k-3
     max.aa <- k+3
     s <- subset(g, g$n.aa >= min.aa & g$n.aa >= max.aa)
     if(nrow(s)==0){df <- data.frame(gene=i,position=0,available.prot.seq=0,of.which.seq.found=0,is.GVD="FALSE")
  res[[i]] <- df
  df <- NULL}else{
      
      for(w in 1:nrow(s)){
        v <- s[w,2]
        v <- subseq(v, start=min.aa, end=max.aa)
        r <- grepl("GPP",v, fixed = TRUE)
        res2[[stp]] <- data.frame(gene=i,position=k,available.prot.seq=nrow(g),of.which.seq.found=w,is.GVD=as.character(r))
      }}
    }
  }
}
res <- unique(bind_rows(res2))
res <- subset(res, res$is.GVD == "TRUE")
gpp <- res
gpp$motif <- "GPP"

### GVD filter ###

#first, filter all proteins that have no GVD at all#
m <- sm
l <- list()
pb <- txtProgressBar(min = 0, max = length(unique(m$gene)), style = 3)
stp <- 0
for(i in unique(m$gene)){
  stp <- stp+1
  setTxtProgressBar(pb, stp)
  z <- subset(m, m$gene == i)
  g <- proteins(EnsDb.Hsapiens.v86, filter= GeneNameFilter(i), columns=c("uniprot_id","protein_sequence"),return.type = "data.frame")
  g <- subset(g, g$uniprot_id %in% unique(z$Uniprot_ID))
  g$r <- grepl("GVD",g$protein_sequence, fixed = TRUE)
  g <- as.data.frame(g)
  g$protein_sequence <- NULL
  l[[i]] <- g
}
l <- bind_rows(l)
l <- subset(l, l$r != "FALSE")

#next, backfilter m to include only the genes with a valid entry in l#
m <- subset(m, m$gene %in% l$gene_name & m$Uniprot_ID %in% l$uniprot_id)

#and then, the actual filter#
pb <- txtProgressBar(min = 0, max = length(unique(m$gene)), style = 3)
stp <- 0
res <- list()
res2 <- list()
for(i in unique(m$gene)){
  stp <- stp+1
  setTxtProgressBar(pb, stp)
  z <- subset(m, m$gene == i)
  g <- proteins(EnsDb.Hsapiens.v86, filter= GeneNameFilter(i), columns=c("uniprot_id","protein_sequence"),return.type = "data.frame")
  g <- subset(g, g$uniprot_id %in% unique(z$Uniprot_ID))
  if(nrow(g)==0){df <- data.frame(gene=i,position=0,available.prot.seq=0,of.which.seq.found=0,is.GVD="FALSE")
  res[[i]] <- df
  df <- NULL}else{
    g$n.aa <- nchar(g$protein_sequence)
    aa <- unique(z$Amino_Acid_Change.x)

    for(k in aa){
     min.aa <- k-3
     max.aa <- k+3
     s <- subset(g, g$n.aa >= min.aa & g$n.aa >= max.aa)
     if(nrow(s)==0){df <- data.frame(gene=i,position=0,available.prot.seq=0,of.which.seq.found=0,is.GVD="FALSE")
  res[[i]] <- df
  df <- NULL}else{
      
      for(w in 1:nrow(s)){
        v <- s[w,2]
        v <- subseq(v, start=min.aa, end=max.aa)
        r <- grepl("GVD",v, fixed = TRUE)
        res2[[stp]] <- data.frame(gene=i,position=k,available.prot.seq=nrow(g),of.which.seq.found=w,is.GVD=as.character(r))
      }}
    }
  }
}
res <- unique(bind_rows(res2))
res <- subset(res, res$is.GVD == "TRUE")
gvd <- res
gvd$motif <- "GVD"
     
### RGD filter ###

#first, filter all proteins that have no RGD at all#
m <- sm
l <- list()
pb <- txtProgressBar(min = 0, max = length(unique(m$gene)), style = 3)
stp <- 0
for(i in unique(m$gene)){
  stp <- stp+1
  setTxtProgressBar(pb, stp)
  z <- subset(m, m$gene == i)
  g <- proteins(EnsDb.Hsapiens.v86, filter= GeneNameFilter(i), columns=c("uniprot_id","protein_sequence"),return.type = "data.frame")
  g <- subset(g, g$uniprot_id %in% unique(z$Uniprot_ID))
  g$r <- grepl("RGD",g$protein_sequence, fixed = TRUE)
  g <- as.data.frame(g)
  g$protein_sequence <- NULL
  l[[i]] <- g
}
l <- bind_rows(l)
l <- subset(l, l$r != "FALSE")

#next, backfilter m to include only the genes with a valid entry in l#
m <- subset(m, m$gene %in% l$gene_name & m$Uniprot_ID %in% l$uniprot_id)

#and then, the actual filter#
pb <- txtProgressBar(min = 0, max = length(unique(m$gene)), style = 3)
stp <- 0
res <- list()
res2 <- list()
for(i in unique(m$gene)){
  stp <- stp+1
  setTxtProgressBar(pb, stp)
  z <- subset(m, m$gene == i)
  g <- proteins(EnsDb.Hsapiens.v86, filter= GeneNameFilter(i), columns=c("uniprot_id","protein_sequence"),return.type = "data.frame")
  g <- subset(g, g$uniprot_id %in% unique(z$Uniprot_ID))
  if(nrow(g)==0){df <- data.frame(gene=i,position=0,available.prot.seq=0,of.which.seq.found=0,is.GVD="FALSE")
  res[[i]] <- df
  df <- NULL}else{
    g$n.aa <- nchar(g$protein_sequence)
    aa <- unique(z$Amino_Acid_Change.x)

    for(k in aa){
     min.aa <- k-3
     max.aa <- k+3
     s <- subset(g, g$n.aa >= min.aa & g$n.aa >= max.aa)
     if(nrow(s)==0){df <- data.frame(gene=i,position=0,available.prot.seq=0,of.which.seq.found=0,is.GVD="FALSE")
  res[[i]] <- df
  df <- NULL}else{
      
      for(w in 1:nrow(s)){
        v <- s[w,2]
        v <- subseq(v, start=min.aa, end=max.aa)
        r <- grepl("RGD",v, fixed = TRUE)
        res2[[stp]] <- data.frame(gene=i,position=k,available.prot.seq=nrow(g),of.which.seq.found=w,is.GVD=as.character(r))
      }}
    }
  }
}
res <- unique(bind_rows(res2))
res <- subset(res, res$is.GVD == "TRUE")
rgd <- res
#rgd$motif <- "RGD" #no RGD!

### LDV filter ###

#first, filter all proteins that have no LDV at all#
m <- sm
l <- list()
pb <- txtProgressBar(min = 0, max = length(unique(m$gene)), style = 3)
stp <- 0
for(i in unique(m$gene)){
  stp <- stp+1
  setTxtProgressBar(pb, stp)
  z <- subset(m, m$gene == i)
  g <- proteins(EnsDb.Hsapiens.v86, filter= GeneNameFilter(i), columns=c("uniprot_id","protein_sequence"),return.type = "data.frame")
  g <- subset(g, g$uniprot_id %in% unique(z$Uniprot_ID))
  g$r <- grepl("LDV",g$protein_sequence, fixed = TRUE)
  g <- as.data.frame(g)
  g$protein_sequence <- NULL
  l[[i]] <- g
}
l <- bind_rows(l)
l <- subset(l, l$r != "FALSE")

#next, backfilter m to include only the genes with a valid entry in l#
m <- subset(m, m$gene %in% l$gene_name & m$Uniprot_ID %in% l$uniprot_id)

#and then, the actual filter#
pb <- txtProgressBar(min = 0, max = length(unique(m$gene)), style = 3)
stp <- 0
res <- list()
res2 <- list()
for(i in unique(m$gene)){
  stp <- stp+1
  setTxtProgressBar(pb, stp)
  z <- subset(m, m$gene == i)
  g <- proteins(EnsDb.Hsapiens.v86, filter= GeneNameFilter(i), columns=c("uniprot_id","protein_sequence"),return.type = "data.frame")
  g <- subset(g, g$uniprot_id %in% unique(z$Uniprot_ID))
  if(nrow(g)==0){df <- data.frame(gene=i,position=0,available.prot.seq=0,of.which.seq.found=0,is.GVD="FALSE")
  res[[i]] <- df
  df <- NULL}else{
    g$n.aa <- nchar(g$protein_sequence)
    aa <- unique(z$Amino_Acid_Change.x)

    for(k in aa){
     min.aa <- k-3
     max.aa <- k+3
     s <- subset(g, g$n.aa >= min.aa & g$n.aa >= max.aa)
     if(nrow(s)==0){df <- data.frame(gene=i,position=0,available.prot.seq=0,of.which.seq.found=0,is.GVD="FALSE")
  res[[i]] <- df
  df <- NULL}else{
      
      for(w in 1:nrow(s)){
        v <- s[w,2]
        v <- subseq(v, start=min.aa, end=max.aa)
        r <- grepl("LDV",v, fixed = TRUE)
        res2[[stp]] <- data.frame(gene=i,position=k,available.prot.seq=nrow(g),of.which.seq.found=w,is.GVD=as.character(r))
      }}
    }
  }
}
res <- unique(bind_rows(res2))
res <- subset(res, res$is.GVD == "TRUE")
ldv <- res
#ldv$motif <- "LDV" #no LDV

### GFPGER filter ###

#first, filter all proteins that have no LDV at all#
m <- sm
l <- list()
pb <- txtProgressBar(min = 0, max = length(unique(m$gene)), style = 3)
stp <- 0
for(i in unique(m$gene)){
  stp <- stp+1
  setTxtProgressBar(pb, stp)
  z <- subset(m, m$gene == i)
  g <- proteins(EnsDb.Hsapiens.v86, filter= GeneNameFilter(i), columns=c("uniprot_id","protein_sequence"),return.type = "data.frame")
  g <- subset(g, g$uniprot_id %in% unique(z$Uniprot_ID))
  g$r <- grepl("GFPGER",g$protein_sequence, fixed = TRUE)
  g <- as.data.frame(g)
  g$protein_sequence <- NULL
  l[[i]] <- g
}
l <- bind_rows(l)
l <- subset(l, l$r != "FALSE")

#next, backfilter m to include only the genes with a valid entry in l#
m <- subset(m, m$gene %in% l$gene_name & m$Uniprot_ID %in% l$uniprot_id)

#and then, the actual filter#
pb <- txtProgressBar(min = 0, max = length(unique(m$gene)), style = 3)
stp <- 0
res <- list()
res2 <- list()
for(i in unique(m$gene)){
  stp <- stp+1
  setTxtProgressBar(pb, stp)
  z <- subset(m, m$gene == i)
  g <- proteins(EnsDb.Hsapiens.v86, filter= GeneNameFilter(i), columns=c("uniprot_id","protein_sequence"),return.type = "data.frame")
  g <- subset(g, g$uniprot_id %in% unique(z$Uniprot_ID))
  if(nrow(g)==0){df <- data.frame(gene=i,position=0,available.prot.seq=0,of.which.seq.found=0,is.GVD="FALSE")
  res[[i]] <- df
  df <- NULL}else{
    g$n.aa <- nchar(g$protein_sequence)
    aa <- unique(z$Amino_Acid_Change.x)

    for(k in aa){
     min.aa <- k-3
     max.aa <- k+3
     s <- subset(g, g$n.aa >= min.aa & g$n.aa >= max.aa)
     if(nrow(s)==0){df <- data.frame(gene=i,position=0,available.prot.seq=0,of.which.seq.found=0,is.GVD="FALSE")
  res[[i]] <- df
  df <- NULL}else{
      
      for(w in 1:nrow(s)){
        v <- s[w,2]
        v <- subseq(v, start=min.aa, end=max.aa)
        r <- grepl("GFPGER",v, fixed = TRUE)
        res2[[stp]] <- data.frame(gene=i,position=k,available.prot.seq=nrow(g),of.which.seq.found=w,is.GVD=as.character(r))
      }}
    }
  }
}
res <- unique(bind_rows(res2))
res <- subset(res, res$is.GVD == "TRUE")
gfpger <- res
#gfpger$motif <- "GFPGER" #no LDV

### GLPGER filter ###

#first, filter all proteins that have no LDV at all#
m <- sm
l <- list()
pb <- txtProgressBar(min = 0, max = length(unique(m$gene)), style = 3)
stp <- 0
for(i in unique(m$gene)){
  stp <- stp+1
  setTxtProgressBar(pb, stp)
  z <- subset(m, m$gene == i)
  g <- proteins(EnsDb.Hsapiens.v86, filter= GeneNameFilter(i), columns=c("uniprot_id","protein_sequence"),return.type = "data.frame")
  g <- subset(g, g$uniprot_id %in% unique(z$Uniprot_ID))
  g$r <- grepl("GLPGER",g$protein_sequence, fixed = TRUE)
  g <- as.data.frame(g)
  g$protein_sequence <- NULL
  l[[i]] <- g
}
l <- bind_rows(l)
l <- subset(l, l$r != "FALSE")

#next, backfilter m to include only the genes with a valid entry in l#
m <- subset(m, m$gene %in% l$gene_name & m$Uniprot_ID %in% l$uniprot_id)

#and then, the actual filter#
pb <- txtProgressBar(min = 0, max = length(unique(m$gene)), style = 3)
stp <- 0
res <- list()
res2 <- list()
for(i in unique(m$gene)){
  stp <- stp+1
  setTxtProgressBar(pb, stp)
  z <- subset(m, m$gene == i)
  g <- proteins(EnsDb.Hsapiens.v86, filter= GeneNameFilter(i), columns=c("uniprot_id","protein_sequence"),return.type = "data.frame")
  g <- subset(g, g$uniprot_id %in% unique(z$Uniprot_ID))
  if(nrow(g)==0){df <- data.frame(gene=i,position=0,available.prot.seq=0,of.which.seq.found=0,is.GVD="FALSE")
  res[[i]] <- df
  df <- NULL}else{
    g$n.aa <- nchar(g$protein_sequence)
    aa <- unique(z$Amino_Acid_Change.x)

    for(k in aa){
     min.aa <- k-3
     max.aa <- k+3
     s <- subset(g, g$n.aa >= min.aa & g$n.aa >= max.aa)
     if(nrow(s)==0){df <- data.frame(gene=i,position=0,available.prot.seq=0,of.which.seq.found=0,is.GVD="FALSE")
  res[[i]] <- df
  df <- NULL}else{
      
      for(w in 1:nrow(s)){
        v <- s[w,2]
        v <- subseq(v, start=min.aa, end=max.aa)
        r <- grepl("GLPGER",v, fixed = TRUE)
        res2[[stp]] <- data.frame(gene=i,position=k,available.prot.seq=nrow(g),of.which.seq.found=w,is.GVD=as.character(r))
      }}
    }
  }
}
res <- unique(bind_rows(res2))
res <- subset(res, res$is.GVD == "TRUE")
glpger <- res
#glpger$motif <- "GFPGER" #no LDV

### Bind results together ###

res <- bind_rows(gpp, ldv)
res$id <- paste0(res$gene,"_",res$position)
sm$id <- paste0(sm$gene,"_",sm$Amino_Acid_Change)
sm <- subset(sm, select = c("gene","id"))
res <- merge(sm,res,by="id")
res$id <- NULL
res$gene.y <- NULL
names(res)[1] <- "gene"
names(res)[5] <- "is.GVD.plus.minus.3"
fwrite(res, "MOTIF MAPPING OF non-PTMmut.txt",sep="\t",row.names = F, quote = F)

Actually, they are both unlikely but PTMmut seem better tolerated in toto! Then, are there sequence features that can tell us something about the mapping mutations?

tab <- fread("TABLE 1.txt",header=T,sep="\t")
allmutations <- fread("allmutations.txt", header=T, sep="\t") # import mutation data #
allmutations <- as.data.frame(allmutations)
allmutations$Amino_Acid_Change <- gsub("p.","",allmutations$Amino_Acid_Change)
allmutations$Amino_Acid_Change <- substring(allmutations$Amino_Acid_Change, 1, 1)
m <- merge(tab, allmutations, by.x=c("sample","gene","chr","start","end"), by.y=c("sample","gene","chr","start","end"))
m <- subset(m, m$is.matrisome == "matrisome")

pb <- txtProgressBar(min = 0, max = length(unique(m$gene)), style = 3)
stp <- 0
res <- list()
res2 <- list()
for(i in unique(m$gene)){
  stp <- stp+1
  setTxtProgressBar(pb, stp)
  z <- subset(m, m$gene == i)
  g <- proteins(EnsDb.Hsapiens.v86, filter= GeneNameFilter(i), columns=c("uniprot_id","protein_sequence"),return.type = "data.frame")
  g <- subset(g, g$uniprot_id %in% unique(z$Uniprot_ID))
  if(nrow(g)==0){df <- data.frame(gene=i,position=0,available.prot.seq=0,of.which.seq.found=0,is.GVD="FALSE")
  res[[i]] <- df
  df <- NULL}else{
    g$n.aa <- nchar(g$protein_sequence)
    aa <- unique(z$Amino_Acid_Change.x)
    aa <- aa[aa>11]

    for(k in aa){
     min.aa <- k-10
     max.aa <- k+10
     s <- subset(g, g$n.aa >= min.aa & g$n.aa >= max.aa)
     if(nrow(s)==0){df <- data.frame(gene=i,position=0,available.prot.seq=0,of.which.seq.found=0,is.GVD="FALSE")
  res[[i]] <- df
  df <- NULL}else{
      
      for(w in 1:nrow(s)){
        v <- s[w,2]
        v <- subseq(v, start=min.aa, end=max.aa)
        res2[[stp]] <- data.frame(gene=i,position=k,available.prot.seq=nrow(g),of.which.seq.found=w,is.GVD=as.character(v))
      }}
    }
  }
}
res <- unique(bind_rows(res2))
res$id <- paste0(res$gene,"_",res$position)
m$id <- paste0(sm$gene,"_",sm$Amino_Acid_Change.x)
m <- subset(m, select = c("gene","PTM","id"))
res <- merge(m,res,by="id")
res$id <- NULL
res$gene.y <- NULL
names(res)[1] <- "gene"
names(res)[6] <- "sequence.context.10aa"
ptm <- res

tab <- as.data.frame(fread("all mutations.txt",header=T,sep="\t"))
tab2 <- as.data.frame(fread("all PTM mutations.txt",header=T,sep="\t"))
tab$id <- paste0(tab$sample,"_",tab$gene,"_",tab$Amino_Acid_Change,"_",tab$`cancer type abbreviation`)
tab2$id <- paste0(tab2$sample,"_",tab2$gene,"_",tab2$Amino_Acid_Change,"_",tab2$`cancer type abbreviation`)
tab <- subset(tab, !(tab$id %in% tab2$id))
tab <- subset(tab, tab$is.matrisome == "matrisome")
tab <- na.omit(tab)
ntab <- fread("TABLE 1.txt",header=T,sep="\t")
ntab <- subset(ntab, ntab$is.matrisome == "matrisome")
ntab <- subset(ntab, select=c("gene","Uniprot_ID"))
m <- distinct(merge(tab,ntab,by="gene"))
m <- m %>% mutate_if(bit64::is.integer64, as.integer)
m <- subset(m, m$is.matrisome == "matrisome")

pb <- txtProgressBar(min = 0, max = length(unique(m$gene)), style = 3)
stp <- 0
res <- list()
res2 <- list()
for(i in unique(m$gene)){
  stp <- stp+1
  setTxtProgressBar(pb, stp)
  z <- subset(m, m$gene == i)
  g <- proteins(EnsDb.Hsapiens.v86, filter= GeneNameFilter(i), columns=c("uniprot_id","protein_sequence"),return.type = "data.frame")
  g <- subset(g, g$uniprot_id %in% unique(z$Uniprot_ID))
  if(nrow(g)==0){df <- data.frame(gene=i,position=0,available.prot.seq=0,of.which.seq.found=0,is.GVD="FALSE")
  res[[i]] <- df
  df <- NULL}else{
    g$n.aa <- nchar(g$protein_sequence)
    aa <- unique(z$Amino_Acid_Change)
    aa <- aa[aa>11]

    for(k in aa){
     min.aa <- k-10
     max.aa <- k+10
     s <- subset(g, g$n.aa >= min.aa & g$n.aa >= max.aa)
     if(nrow(s)==0){df <- data.frame(gene=i,position=0,available.prot.seq=0,of.which.seq.found=0,is.GVD="FALSE")
  res[[i]] <- df
  df <- NULL}else{
      
      for(w in 1:nrow(s)){
        v <- s[w,2]
        v <- subseq(v, start=min.aa, end=max.aa)
        res2[[stp]] <- data.frame(gene=i,position=k,available.prot.seq=nrow(g),of.which.seq.found=w,is.GVD=as.character(v))
      }}
    }
  }
}
res <- unique(bind_rows(res2))
res$id <- paste0(res$gene,"_",res$position)
m$id <- paste0(m$gene,"_",m$Amino_Acid_Change)
m <- subset(m, select = c("gene","id"))
res <- merge(m,res,by="id")
res$id <- NULL
res$gene.y <- NULL
names(res)[1] <- "gene"
names(res)[5] <- "sequence.context.10aa"
noptm <- res

l <- list()
l2 <- list()
for(i in unique(ptm$PTM)){
  z <- subset(ptm, ptm$PTM == i)
  g <- z$gene
  l[[i]] <- z$sequence.context.10aa
  z <- subset(noptm, noptm$gene %in% g)
  l2[[i]] <- z$sequence.context.10aa
}
names(l) <- unique(ptm$PTM)
names(l2) <- unique(ptm$PTM) #these are, of course, fictional!

ggseqlogo(l, ncol = 4)
ggseqlogo(l2, ncol = 4)
LS0tDQp0aXRsZTogIkRpc3J1cHRpdmUgUFRNIG11dGF0aW9ucyBpbiB0aGUgdHVtb3IgbWF0cmlzb21lIg0Kc2VsZl9jb250YWluZWQ6IHllcw0Kb3V0cHV0Og0KICBodG1sX25vdGVib29rOiBkZWZhdWx0DQogIHBkZl9kb2N1bWVudDogZGVmYXVsdA0KICBodG1sX2RvY3VtZW50Og0KICAgIGRmX3ByaW50OiBwYWdlZA0KLS0tDQoNClNURVAgMDogSW1wb3J0IGFsbCBkYXRhIGFuZCBmaXggZm9ybWF0cy4NCg0KYGBge3IsIGV2YWw9Rn0NCg0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkoZGF0YS50YWJsZSkNCg0Kc2V0d2QoKSAjIGZpeCB0aGlzIGFjY29yZGluZyB0byB5b3VyIHN5c3RlbSAjDQoNCmFsbG11dGF0aW9ucyA8LSBmcmVhZCgiYWxsbXV0YXRpb25zLnR4dCIsIGhlYWRlcj1ULCBzZXA9Ilx0IikgIyBpbXBvcnQgbXV0YXRpb24gZGF0YSAjDQphbGxtdXRhdGlvbnMgPC0gYXMuZGF0YS5mcmFtZShhbGxtdXRhdGlvbnMpDQphbGxtdXRhdGlvbnMkZWZmZWN0IDwtIGdzdWIoIjNVVFIuKiIsICIzVVRSIiwgYWxsbXV0YXRpb25zJGVmZmVjdCkgDQphbGxtdXRhdGlvbnMkU0lGVCA8LSBnc3ViKCJcXCguKiIsICIiLCBhbGxtdXRhdGlvbnMkU0lGVCkNCmFsbG11dGF0aW9ucyRQb2x5UGhlbiA8LSBnc3ViKCJcXCguKiIsICIiLCBhbGxtdXRhdGlvbnMkUG9seVBoZW4pDQphbGxtdXRhdGlvbnMkQW1pbm9fQWNpZF9DaGFuZ2UgPC0gZ3N1YigiW14wLTldIiwgIiIsIGFsbG11dGF0aW9ucyRBbWlub19BY2lkX0NoYW5nZSkgDQphbGxtdXRhdGlvbnMkQW1pbm9fQWNpZF9DaGFuZ2UgPC0gYXMubnVtZXJpYyhhbGxtdXRhdGlvbnMkQW1pbm9fQWNpZF9DaGFuZ2UpDQoNCmFsbG11dGF0aW9ucyRlZmZlY3QgPC0gZ3N1YigiRnJhbWVfU2hpZnRfRGVsIiwgIkZyYW1lc2hpZnQiLCBhbGxtdXRhdGlvbnMkZWZmZWN0KSAjIHNpbXBsZSByZWNvZGUgZWZmZWN0ICMNCmFsbG11dGF0aW9ucyRlZmZlY3QgPC0gZ3N1YigiRnJhbWVfU2hpZnRfSW5zIiwgIkZyYW1lc2hpZnQiLCBhbGxtdXRhdGlvbnMkZWZmZWN0KQ0KYWxsbXV0YXRpb25zJGVmZmVjdCA8LSBnc3ViKCJJbl9GcmFtZV9EZWwiLCAiRGVsZXRpb24iLCBhbGxtdXRhdGlvbnMkZWZmZWN0KQ0KYWxsbXV0YXRpb25zJGVmZmVjdCA8LSBnc3ViKCJJbl9GcmFtZV9JbnMiLCAiSW5zZXJ0aW9uIiwgYWxsbXV0YXRpb25zJGVmZmVjdCkNCmFsbG11dGF0aW9ucyRlZmZlY3QgPC0gZ3N1YigiRmxhbmsiLCAiT3RoZXJzIiwgYWxsbXV0YXRpb25zJGVmZmVjdCkNCmFsbG11dGF0aW9ucyRlZmZlY3QgPC0gZ3N1YigiSW50cm9uIiwiT3RoZXJzIiwgYWxsbXV0YXRpb25zJGVmZmVjdCkNCmFsbG11dGF0aW9ucyRlZmZlY3QgPC0gZ3N1YigibGFyZ2VkIiwiRCIsIGFsbG11dGF0aW9ucyRlZmZlY3QpDQphbGxtdXRhdGlvbnMkZWZmZWN0IDwtIGdzdWIoIl9NdXRhdGlvbiIsIiIsIGFsbG11dGF0aW9ucyRlZmZlY3QpDQphbGxtdXRhdGlvbnMkZWZmZWN0IDwtIGdzdWIoIlJOQSIsICJPdGhlcnMiLCBhbGxtdXRhdGlvbnMkZWZmZWN0KQ0KYWxsbXV0YXRpb25zJGVmZmVjdCA8LSBnc3ViKCJfUyIsICIgcyIsIGFsbG11dGF0aW9ucyRlZmZlY3QpDQphbGxtdXRhdGlvbnMkZWZmZWN0IDwtIGdzdWIoIlRyYW5zbGF0aW9uIHN0YXJ0IHNpdGUiLCAiT3RoZXJzIiwgYWxsbXV0YXRpb25zJGVmZmVjdCkNCmFsbG11dGF0aW9ucyRlZmZlY3QgPC0gZ3N1YigiVVRSIiwgIk90aGVycyIsIGFsbG11dGF0aW9ucyRlZmZlY3QpDQphbGxtdXRhdGlvbnMkZWZmZWN0IDwtIGdzdWIoIlteQS16XSIsICIiLCBhbGxtdXRhdGlvbnMkZWZmZWN0KQ0KDQphbGxtdXRhdGlvbnMkUG9seVBoZW4gPC0gZ3N1YigiXiQiLCJ1bmtub3duIixhbGxtdXRhdGlvbnMkUG9seVBoZW4pICMgcmVjb2RpbmcgcG9seXBoZW4gJiBTSUZUICMNCmFsbG11dGF0aW9ucyRTSUZUIDwtIGdzdWIoIl4kIiwidW5rbm93biIsYWxsbXV0YXRpb25zJFNJRlQpDQoNCmNvbnYgPC0gZnJlYWQoInVuaXByb3RfbWF0cmlzb21lLnR4dCIsIGhlYWRlcj1GLCBzZXA9Ilx0IikgIyBsb2FkIGNvbnZlcnNpb24gZnJvbSBnZW5lcyB0byBwcm90ZWluIG5hbWVzICMNCmNvbnYgPC0gY29udlssYygxOjIpXQ0KY29sbmFtZXMoY29udikgPC0gYygiZ2VuZSIsIlVuaXByb3RfSUQiKQ0KDQptYXQgPC0gZnJlYWQoIm1hdHJpc29tZV9ocy50eHQiLCBoZWFkZXIgPSBULCBzZXA9Ilx0IikgDQpjb252IDwtIG1lcmdlKGNvbnYsIG1hdCwgYnkueD0iZ2VuZSIsIGJ5Lnk9IkdlbmUgU3ltYm9sIikNCmNvbnYgPC0gYXMuZGF0YS5mcmFtZShjb252KQ0KDQojQWRkaW5nIGEgY29sdW1uIHRoYXQgc3RhdGVzIHdoZXRoZXIgYSBnZW5lIGlzIG1hdHJpc29tZSBvciBub3QgIw0KYWxsbXV0YXRpb25zJGlzLm1hdHJpc29tZSA8LSBpZmVsc2UoYWxsbXV0YXRpb25zJGdlbmUgJWluJSBtYXQkYEdlbmUgU3ltYm9sYCwgIm1hdHJpc29tZSIsICJyZXN0Lm9mLmdlbm9tZSIpDQpjbCA8LSBmcmVhZCgiVENHQWNsaW5kYXRhLnR4dCIsIGhlYWRlcj1ULHNlcD0iXHQiKQ0KY2wgPC0gY2xbLGMoMSwzKV0NCmNsIDwtIHVuaXF1ZShtZXJnZShjbCwgYWxsbXV0YXRpb25zLCBieT0ic2FtcGxlIikpDQpjbCA8LSBzdWJzZXQoY2wsIGNsJGVmZmVjdCAhPSAiU2lsZW50IikNCg0KYGBgDQoNClNURVAgMTogQSBnZW5lcmFsIG92ZXJ2aWV3IG9mIHRoZSBkYXRhIGluY2x1ZGVkIGluIHRoZSBzdHVkeSBzbyBmYXIuIFdlIGhhdmUgYSB0b3RhbCBvZiAyMjc3OTc5IG5vbi1zaWxlbnQgZW50cmllcyBmcm9tIDkwNzUgcGF0aWVudHMgYW5kIDMyIHR1bW9yIHR5cGVzLiBPZiB0aGVzZSwgb25seSB+IDYuNiUgb2NjdXIgaW4gdGhlIG1hdHJpc29tZSwgYnV0IGNvbnNpZGVyaW5nIHRoYXQgdGhlIG1hdHJpc29tZSBpcyAxMDI3IGdlbmVzIGluIHRvdGFsICh2cy4gMjAyMjggb3RoZXIgZ2VuZXMpIHRoZSBmcmVxdWVuY3kgb2YgbXV0YXRpb24gInBlciBnZW5lIiBpcyB+IDE0NyBpbiB0aGUgbWF0cmlzb21lIHZzLiB+MTA1IGluIHRoZSByZXN0IG9mIHRoZSBnZW5vbWUsIGluIGxpbmUgd2l0aCBvdXIgcHJldmlvdXMgcmVwb3J0IFtodHRwczovL3B1Ym1lZC5uY2JpLm5sbS5uaWguZ292LzMyNzIyMjg3L10uIFJlZ2FyZGxlc3MsIHRoZXJlIGlzIG5vIGRpZmZlcmVuY2UgYmV0d2VlbiB0aGUgJSBvZiB0aGUgZGlmZmVyZW50IHR5cGVzIG9mIG11dGF0aW9uIGluIG1hdHJpc29tZSB2cy4gcmVzdCBvZiB0aGUgZ2Vub21lLCBhZ2FpbiBhcyByZXBvcnRlZC4gDQoNCmBgYHtyLCBldmFsPUZ9DQoNCmxlbmd0aCh1bmlxdWUoY2wkc2FtcGxlKSkNCmxlbmd0aCh1bmlxdWUoY2wkYGNhbmNlciB0eXBlIGFiYnJldmlhdGlvbmApKQ0KYWxsbXV0YXRpb25zIDwtIGNsDQoNCnRhYjEgPC0gdGFibGUoYWxsbXV0YXRpb25zJGlzLm1hdHJpc29tZSkNCnRhYjEgPC0gKHRhYjEvMjI3Nzk3OSkqMTAwDQpsZW5ndGgodW5pcXVlKG1hdCRgR2VuZSBTeW1ib2xgKSkNCnNiIDwtIHN1YnNldChhbGxtdXRhdGlvbnMsIGFsbG11dGF0aW9ucyRpcy5tYXRyaXNvbWUgIT0gIm1hdHJpc29tZSIpDQpsZW5ndGgodW5pcXVlKHNiJGdlbmUpKQ0KdGFiMSA8LSB0YWJsZShhbGxtdXRhdGlvbnMkaXMubWF0cmlzb21lKQ0KcGllKHRhYjEsIGNvbCA9IGMoIiNmZGFlNjEiLCIjNDU3NWI0IikpDQp0YWIxWzFdIDwtIHRhYjFbMV0vMTAyNw0KdGFiMVsyXSA8LSB0YWIxWzJdLzIwMjU5DQpiYXJwbG90KHRhYjEsIGNvbCA9IGMoIiNmZGFlNjEiLCIjNDU3NWI0IiksIHlsaW0gPSBjKDAsMjAwKSkNCnRhYjEgPC0gdGFibGUoYWxsbXV0YXRpb25zJGlzLm1hdHJpc29tZSwgYWxsbXV0YXRpb25zJGVmZmVjdCkgDQp0YWIyIDwtICh0YWIxL3Jvd1N1bXModGFiMSkpKjEwMA0KY2hpc3EudGVzdCh0YWIyKSAjbm90IHNpZ25pZmljYW50Iw0KDQpsaWJyYXJ5KGdncGxvdDIpDQp2ZWMgPC0gYygiI2ZkYWU2MSIsIiM0NTc1YjQiLCAiIzMxMzY5NSIsICIjOTJjNWRlIiwgIiNkNzMwMjciLCIjZmVlMDkwIiwgIiNlMGYzZjgiLCIjZjQ2ZDQzIiwgIiNhYmQ5ZTkiLCIjYTUwMDI2IikgIyB2ZWN0b3IgZm9yIGNvbG91cnMgIw0KZGYgPC0gYXMuZGF0YS5mcmFtZSh0YWIyKQ0KY29sbmFtZXMoZGYpIDwtIGMoInR5cGUiLCAibXV0YXRpb24iLCAiZnJlcXVlbmN5ICglKSIpDQpkZiR0eXBlIDwtIGdzdWIoInJlc3Qub2YuZ2Vub21lIiwgIm5vbm1hdHJpc29tZSIsIGRmJHR5cGUpDQpnZ3Bsb3QoZGYsIGFlcyh4PW11dGF0aW9uLCB5PWBmcmVxdWVuY3kgKCUpYCwgZmlsbD10eXBlKSkgKyAjIHNpZGUgYnkgc2lkZSBiYXJwbG90ICMNCiAgICAgZ2VvbV9iYXIocG9zaXRpb249cG9zaXRpb25fZG9kZ2UoKSAsIHN0YXQ9ImlkZW50aXR5IikgKyANCiAgICAgdGhlbWVfY2xhc3NpYygpICsgDQogICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IHZlYykgKyANCiAgICAgY29vcmRfZmxpcCgpDQoNCnRhYjEgPC0gdGFibGUoYWxsbXV0YXRpb25zJGlzLm1hdHJpc29tZSwgYWxsbXV0YXRpb25zJFNJRlQpDQp0YWIyIDwtICh0YWIxL3Jvd1N1bXModGFiMSkpKjEwMA0KY2hpc3EudGVzdCh0YWIyKSAjbm90IHNpZ25pZmljYW50Iw0KZGYgPC0gYXMuZGF0YS5mcmFtZSh0YWIyKQ0KY29sbmFtZXMoZGYpIDwtIGMoInR5cGUiLCAibXV0YXRpb24iLCAiZnJlcXVlbmN5ICglKSIpDQpkZiR0eXBlIDwtIGdzdWIoInJlc3Qub2YuZ2Vub21lIiwgIm5vbm1hdHJpc29tZSIsIGRmJHR5cGUpDQpnZ3Bsb3QoZGYsIGFlcyh4PW11dGF0aW9uLCB5PWBmcmVxdWVuY3kgKCUpYCwgZmlsbD10eXBlKSkgKyAjIHNpZGUgYnkgc2lkZSBiYXJwbG90ICMNCiAgICAgZ2VvbV9iYXIocG9zaXRpb249cG9zaXRpb25fZG9kZ2UoKSAsIHN0YXQ9ImlkZW50aXR5IikgKyANCiAgICAgdGhlbWVfY2xhc3NpYygpICsgDQogICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IHZlYykgKyANCiAgICAgY29vcmRfZmxpcCgpDQoNCnRhYjEgPC0gdGFibGUoYWxsbXV0YXRpb25zJGlzLm1hdHJpc29tZSwgYWxsbXV0YXRpb25zJFBvbHlQaGVuKQ0KdGFiMiA8LSAodGFiMS9yb3dTdW1zKHRhYjEpKSoxMDANCmNoaXNxLnRlc3QodGFiMikgI25vdCBzaWduaWZpY2FudCMNCmRmIDwtIGFzLmRhdGEuZnJhbWUodGFiMikNCmNvbG5hbWVzKGRmKSA8LSBjKCJ0eXBlIiwgIm11dGF0aW9uIiwgImZyZXF1ZW5jeSAoJSkiKQ0KZGYkdHlwZSA8LSBnc3ViKCJyZXN0Lm9mLmdlbm9tZSIsICJub25tYXRyaXNvbWUiLCBkZiR0eXBlKQ0KZ2dwbG90KGRmLCBhZXMoeD1tdXRhdGlvbiwgeT1gZnJlcXVlbmN5ICglKWAsIGZpbGw9dHlwZSkpICsgIyBzaWRlIGJ5IHNpZGUgYmFycGxvdCAjDQogICAgIGdlb21fYmFyKHBvc2l0aW9uPXBvc2l0aW9uX2RvZGdlKCkgLCBzdGF0PSJpZGVudGl0eSIpICsgDQogICAgIHRoZW1lX2NsYXNzaWMoKSArIA0KICAgICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSB2ZWMpICsgDQogICAgIGNvb3JkX2ZsaXAoKQ0KDQpgYGANCg0KU1RFUCAyOiBJbnRlcnNlY3QgbXV0YXRpb24gZGF0YSB3aXRoIFBUTSBzaXRlcyBkYXRhIGFuZCBpZGVudGlmeSBtdXRhdGlvbnMgd2hpY2ggcG90ZW50aWFsbHkgZGlzcnVwdCBQVE1zIGluIHR1bW9ycy4gSW4gdGhlIGZpcnN0IGNodW5rLCBhbGwgUFRNIHNpdGVzIGFyZSBsb2FkZWQgYW5kIGludGVncmF0ZWQgd2l0aCBtdXRhdGlvbiBzaXRlcy4NCg0KYGBge3IsIGV2YWw9Rn0NCg0KIyBQVE1zIC0gcGhvc3Bob3J5bGF0aW9uICMNCg0KbGlicmFyeShSLnV0aWxzKQ0KIyBndW56aXAoIlBob3NwaG9yeWxhdGlvbl9zaXRlX2RhdGFzZXQuZ3oiKSAjIEFmdGVyIG9wZW5pbmcgdGhlIGZpbGUgd2l0aCBlZGl0b3IgYXMgYSB0eHQgZmlsZSwgdGhlIGZpcnN0IHRocmVlIHJvd3Mgd2VyZSBkZWxldGVkICMNCg0KcGhvc2RhdGEgPC0gZnJlYWQoIlBob3NwaG9yeWxhdGlvbl9zaXRlX2RhdGFzZXQudHh0IiwgaGVhZGVyPVQsIHNlcD0iXHQiKSAjIGxvYWQgUFRNICMNCnBob3NkYXRhJFBUTSA8LSBjKCJwaG9zcGhvcnlsYXRpb24iKQ0KcGhvc2RhdGEkTU9EX1JTRCA8LSBnc3ViKCJbXjAtOV0iLCAiIiwgcGhvc2RhdGEkTU9EX1JTRCkNCnBob3NkYXRhJE1PRF9SU0QgPC0gYXMubnVtZXJpYyhwaG9zZGF0YSRNT0RfUlNEKQ0KDQojIFBUTXMgLSBhY2V0eWxhdGlvbiAjDQoNCiMgZ3VuemlwKCJBY2V0eWxhdGlvbl9zaXRlX2RhdGFzZXQuZ3oiKSAjIEFmdGVyIG9wZW5pbmcgdGhlIGZpbGUgd2l0aCBlZGl0b3IgYXMgYSB0eHQgZmlsZSwgdGhlIGZpcnN0IHRocmVlIHJvd3Mgd2VyZSBkZWxldGVkICMNCg0KYWNkYXRhIDwtIGZyZWFkKCJBY2V0eWxhdGlvbl9zaXRlX2RhdGFzZXQudHh0IiwgaGVhZGVyPVQsIHNlcD0iXHQiKSAjIGxvYWQgUFRNICMNCmFjZGF0YSRQVE0gPC0gYygiYWNldHlsYXRpb24iKQ0KYWNkYXRhJE1PRF9SU0QgPC0gZ3N1YigiW14wLTldIiwgIiIsIGFjZGF0YSRNT0RfUlNEKQ0KYWNkYXRhJE1PRF9SU0QgPC0gYXMubnVtZXJpYyhhY2RhdGEkTU9EX1JTRCkNCg0KIyBQVE1zIC0gbWV0aHlsYXRpb24gIw0KDQojIGd1bnppcCgiTWV0aHlsYXRpb25fc2l0ZV9kYXRhc2V0Lmd6IikgIyBBZnRlciBvcGVuaW5nIHRoZSBmaWxlIHdpdGggZWRpdG9yIGFzIGEgdHh0IGZpbGUsIHRoZSBmaXJzdCB0aHJlZSByb3dzIHdlcmUgZGVsZXRlZCAjDQoNCm1ldGRhdGEgPC0gZnJlYWQoIk1ldGh5bGF0aW9uX3NpdGVfZGF0YXNldC50eHQiLCBoZWFkZXI9VCwgc2VwPSJcdCIpICMgbG9hZCBQVE0gIw0KbWV0ZGF0YSRQVE0gPC0gYygibWV0aHlsYXRpb24iKQ0KbWV0ZGF0YSRNT0RfUlNEIDwtIGdzdWIoIlteMC05XSIsICIiLCBtZXRkYXRhJE1PRF9SU0QpDQptZXRkYXRhJE1PRF9SU0QgPC0gYXMubnVtZXJpYyhtZXRkYXRhJE1PRF9SU0QpDQoNCiMgUFRNcyAtIE8tZ2x5Y29zeWxhdGlvbiAjDQoNCiMgZ3VuemlwKCJPLUdhbE5BY19zaXRlX2RhdGFzZXQuZ3oiKSAjIEFmdGVyIG9wZW5pbmcgdGhlIGZpbGUgd2l0aCBlZGl0b3IgYXMgYSB0eHQgZmlsZSwgdGhlIGZpcnN0IHRocmVlIHJvd3Mgd2VyZSBkZWxldGVkICMNCiMgZ3VuemlwKCJPLUdsY05BY19zaXRlX2RhdGFzZXQuZ3oiKSAjIFNhbWUgYXMgb24gdG9wICMNCg0KT2dhbCA8LSBmcmVhZCgiTy1HYWxOQWNfc2l0ZV9kYXRhc2V0LnR4dCIsIGhlYWRlcj1ULCBzZXA9Ilx0IikgIyBsb2FkIFBUTSAjDQpPZ2FsJE1PRF9SU0QgPC0gZ3N1YigiW14wLTldIiwgIiIsIE9nYWwkTU9EX1JTRCkNCk9nYWwkTU9EX1JTRCA8LSBhcy5udW1lcmljKE9nYWwkTU9EX1JTRCkNCg0KT2dsYyA8LSBmcmVhZCgiTy1HbGNOQWNfc2l0ZV9kYXRhc2V0LnR4dCIsIGhlYWRlcj1ULCBzZXA9Ilx0IikNCk9nbGMkTU9EX1JTRCA8LSBnc3ViKCJbXjAtOV0iLCAiIiwgT2dsYyRNT0RfUlNEKQ0KT2dsYyRNT0RfUlNEIDwtIGFzLm51bWVyaWMoT2dsYyRNT0RfUlNEKQ0KDQpPRyA8LSByYmluZChPZ2FsLCBPZ2xjKQ0KT0ckUFRNIDwtIGMoIk8tZ2x5Y29zeWxhdGlvbiIpDQoNCiMgUFRNcyAtIHN1bW95bGF0aW9uICMNCg0KIyBndW56aXAoIlN1bW95bGF0aW9uX3NpdGVfZGF0YXNldC5neiIpICMgQWZ0ZXIgb3BlbmluZyB0aGUgZmlsZSB3aXRoIGVkaXRvciBhcyBhIHR4dCBmaWxlLCB0aGUgZmlyc3QgdGhyZWUgcm93cyB3ZXJlIGRlbGV0ZWQgIw0KDQpzdW1vZGF0YSA8LSBmcmVhZCgiU3Vtb3lsYXRpb25fc2l0ZV9kYXRhc2V0LnR4dCIsIGhlYWRlcj1ULCBzZXA9Ilx0IikgIyBsb2FkIFBUTSAjDQpzdW1vZGF0YSRQVE0gPC0gYygic3Vtb3lsYXRpb24iKQ0Kc3Vtb2RhdGEkTU9EX1JTRCA8LSBnc3ViKCJbXjAtOV0iLCAiIiwgc3Vtb2RhdGEkTU9EX1JTRCkNCnN1bW9kYXRhJE1PRF9SU0QgPC0gYXMubnVtZXJpYyhzdW1vZGF0YSRNT0RfUlNEKQ0KDQojIFBUTXMgLSB1YmlxdWl0eWxhdGlvbiAjDQoNCiMgZ3VuemlwKCJVYmlxdWl0aW5hdGlvbl9zaXRlX2RhdGFzZXQuZ3oiKSAjIEFmdGVyIG9wZW5pbmcgdGhlIGZpbGUgd2l0aCBlZGl0b3IgYXMgYSB0eHQgZmlsZSwgdGhlIGZpcnN0IHRocmVlIHJvd3Mgd2VyZSBkZWxldGVkICMNCg0KdWJpZGF0YSA8LSBmcmVhZCgiVWJpcXVpdGluYXRpb25fc2l0ZV9kYXRhc2V0LnR4dCIsIGhlYWRlcj1ULCBzZXA9Ilx0IikgIyBsb2FkIFBUTSAjDQp1YmlkYXRhJFBUTSA8LSBjKCJ1YmlxdWl0eWxhdGlvbiIpDQp1YmlkYXRhJE1PRF9SU0QgPC0gZ3N1YigiW14wLTldIiwgIiIsIHViaWRhdGEkTU9EX1JTRCkNCnViaWRhdGEkTU9EX1JTRCA8LSBhcy5udW1lcmljKHViaWRhdGEkTU9EX1JTRCkNCg0KIyBQVE1zIC0gTi1nbHljb3N5bGF0aW9uICMNCg0KTmdseWMgPC0gZnJlYWQoIk4tR2x5Y29zaXRlQXRsYXNfSHVtYW5BbGwudHh0Iiwgc2VwPSJcdCIsIGhlYWRlcj1UKSAjIGRhdGEgZnJvbSBOLUdseWNvc2l0ZUF0bGFzLCBjb252ZXJ0ZWQgaW50byB0eHQgZmlsZSBhbmQgZGVsZXRpb24gb2YgZmlyc3Qgcm93ICMNCk5nbHljIDwtIE5nbHljWywyOjVdDQpjb2xuYW1lcyhOZ2x5YykgPC0gYygiVW5pcHJvdF9JRCIsICJnZW5lIiwgInByb3RlaW4iLCAiQW1pbm9fQWNpZF9DaGFuZ2UiKQ0KTmdseWMkUFRNIDwtIGMoIk4tZ2x5Y29zeWxhdGlvbiIpDQpOZ2x5YyRBbWlub19BY2lkX0NoYW5nZSA8LSBhcy5udW1lcmljKE5nbHljJEFtaW5vX0FjaWRfQ2hhbmdlKQ0KDQojIFBUTXMgLSBoeWRyb3h5bGF0aW9uICMNCg0KaCA8LSBmcmVhZCgiSHlkcm94eWxhdGlvbiBzaXRlc19Vbmlwcm90LnR4dCIsIHNlcD0iXHQiLCBoZWFkZXI9VCkgIyBkYXRhIGZyb20gdW5pcHJvdCAjDQpjb2xuYW1lcyhoKSA8LSBjKCJVbmlwcm90X0lEIiwgImdlbmUiLCAic3BlY2lmaWNhdGlvbiIsICJBbWlub19BY2lkX0NoYW5nZSIsICJ2YWxpZGF0aW9uIiwgIlBUTSIpDQpoJHNwZWNpZmljYXRpb24gPC0gTlVMTA0KDQojIyBDb21iaW5lIGFsbCBQVE1zICMjDQoNCmFsbFBUTXMgPC0gcmJpbmQoYWNkYXRhLCBtZXRkYXRhKSAjIGNvbWJpbmluZyBhbGwgbXV0YXRlZCBQVE0gc2l0ZXMgIw0KYWxsUFRNcyA8LSByYmluZChhbGxQVE1zLCBPRykNCmFsbFBUTXMgPC0gcmJpbmQoYWxsUFRNcywgcGhvc2RhdGEpDQphbGxQVE1zIDwtIHJiaW5kKGFsbFBUTXMsIHN1bW9kYXRhKQ0KYWxsUFRNcyA8LSByYmluZChhbGxQVE1zLCB1YmlkYXRhKQ0KYWxsUFRNcyA8LSBhbGxQVE1zWywgYygxOjMsIDUsIDE1KV0NCmNvbG5hbWVzKGFsbFBUTXMpIDwtIGMoImdlbmUiLCAicHJvdGVpbiIsICJVbmlwcm90X0lEIiwgIkFtaW5vX0FjaWRfQ2hhbmdlIiwgIlBUTSIpDQoNCmFsbFBUTXMgPC0gcmJpbmQoYWxsUFRNcywgaCwgZmlsbD1UKSANCmFsbFBUTXMgPC0gcmJpbmQoYWxsUFRNcywgTmdseWMsIGZpbGw9VCkNCmFsbFBUTXMkdmFsaWRhdGlvbiA8LSBOVUxMDQoNCmFsbFBUTXMgPC0gYXMuZGF0YS5mcmFtZShhbGxQVE1zKQ0KYWxsUFRNcyA8LSB1bmlxdWUoYWxsUFRNcykNCmFsbFBUTXMkZ2VuZSA8LSB0b3VwcGVyKGFsbFBUTXMkZ2VuZSkgIyB0aGUgZmlsZSBjb250YWlucyB1cHBlciBhbmQgbG93ZXIgY2FzZSBnZW5lIG5hbWVzLCBzb21lIHdvdWxkbid0IG1hdGNoIHdpdGggbXV0YXRpb25zIG90aGVyd2lzZSAjDQoNCmh1bSA8LSBmcmVhZCgidW5pcHJvdC1wcm90ZW9tZV9VUDAwMDAwNTY0MC50YWIiLGhlYWRlcj1ULHNlcD0iXHQiKSAjbmVlZCB0byBjdXQgdG8gaHVtYW4gcHJvdGVpbnMgb25seSwgc29tZSBvZiB0aGUgSURzIGluY2x1ZGVkIGFyZSBmb3JtIG90aGVyIHNwZWNpZXMhIw0KYWxsUFRNcyA8LSBzdWJzZXQoYWxsUFRNcywgYWxsUFRNcyRVbmlwcm90X0lEICVpbiUgaHVtJEVudHJ5KQ0KYWxsbXV0YXRpb25zX1BUTXMgPC0gbWVyZ2UoYWxsbXV0YXRpb25zLCBhbGxQVE1zLCBieS54PWMoImdlbmUiLCAiQW1pbm9fQWNpZF9DaGFuZ2UiKSwgYnkueT1jKCJnZW5lIiwgIkFtaW5vX0FjaWRfQ2hhbmdlIikpDQoNCmZ3cml0ZShhbGxtdXRhdGlvbnNfUFRNcywgIkFsbCBQVE0gbXV0YXRpb25zLnR4dCIsIHNlcD0iXHQiLCByb3cubmFtZXMgPSBGLCBxdW90ZT1GKQ0KZndyaXRlKGFsbG11dGF0aW9ucywgIkFsbCBtdXRhdGlvbnMudHh0Iiwgc2VwPSJcdCIsIHJvdy5uYW1lcyA9IEYsIHF1b3RlPUYpDQoNCmBgYA0KDQpJbiB0aGUgc2Vjb25kIGNodW5rLCB3ZSBzdGFydCB0byBhbmFseXplIHRoZSBicmVha2Rvd25zIGFuZCBnZW5vbWljIHBhdHRlcm5zIG9mIGRpc3J1cHRpdmUgUFRNIG11dGF0aW9ucyAoUFRNbXV0KS4gSW4gdG90YWwsIHRoZXJlIGFyZSA0MjczMyBQVE1tdXQgKH4xLjg3JSBvZiBhbGwgbXV0YXRpb25zKSBmcm9tIDYzMDMgcGF0aWVudHMgYW5kIDMyIGNhbmNlcnMsIG9mIHdoaWNoIDE4MTEgZnJvbSB0aGUgbWF0cmlzb21lLiBUaGUgcmF0aW8gb2YgUFRNbXV0L3RvdGFsIG11dCBpcyB0aHVzIDEuMiUgZm9yIHRoZSBtYXRyaXNvbWUgYW5kIDEuOSUgZm9yIHRoZSByZXN0IG9mIHRoZSBnZW5vbWUuIE5vdGUgdGhhdCB0aGUgbWF0cmlzb21lIGhhcywgb24gYXZlcmFnZSwgbG93ZXIgUFRNbXV0IGZyZXF1ZW5jeSBwZXIgdW5pdCBsZW5ndGggYXMgY29tcGFyZWQgdG8gdGhlIHJlc3Qgb2YgdGhlIGdlbm9tZSwgb3Bwb3NpdGVseSB0byB3aGF0IG9ic2VydmVkIGZvciBvdGhlciBtdXRhdGlvbnMuIA0KDQpgYGB7ciwgZXZhbD1GfQ0KDQojUFRNbXV0IHZzIG5vbi1QVE1tdXQjDQpkaW0oYWxsbXV0YXRpb25zX1BUTXMpDQoobnJvdyhhbGxtdXRhdGlvbnNfUFRNcykvbnJvdyhhbGxtdXRhdGlvbnMpKSoxMDANCmxlbmd0aCh1bmlxdWUoYWxsbXV0YXRpb25zX1BUTXMkc2FtcGxlKSkNCmxlbmd0aCh1bmlxdWUoYWxsbXV0YXRpb25zX1BUTXMkYGNhbmNlciB0eXBlIGFiYnJldmlhdGlvbmApKQ0KbnJvdyhzdWJzZXQoYWxsbXV0YXRpb25zX1BUTXMsIGFsbG11dGF0aW9uc19QVE1zJGlzLm1hdHJpc29tZSAhPSAicmVzdC5vZi5nZW5vbWUiKSkNCg0KcjEgPC0gKG5yb3coc3Vic2V0KGFsbG11dGF0aW9uc19QVE1zLCBhbGxtdXRhdGlvbnNfUFRNcyRpcy5tYXRyaXNvbWUgIT0gInJlc3Qub2YuZ2Vub21lIikpL25yb3coc3Vic2V0KGFsbG11dGF0aW9ucywgYWxsbXV0YXRpb25zJGlzLm1hdHJpc29tZSAhPSAicmVzdC5vZi5nZW5vbWUiKSkpKjEwMA0KcjIgPC0gKG5yb3coc3Vic2V0KGFsbG11dGF0aW9uc19QVE1zLCBhbGxtdXRhdGlvbnNfUFRNcyRpcy5tYXRyaXNvbWUgPT0gInJlc3Qub2YuZ2Vub21lIikpL25yb3coc3Vic2V0KGFsbG11dGF0aW9ucywgYWxsbXV0YXRpb25zJGlzLm1hdHJpc29tZSA9PSAicmVzdC5vZi5nZW5vbWUiKSkpKjEwMA0KcjENCnIyDQoNCnRhYjEgPC0gYygobnJvdyhhbGxtdXRhdGlvbnNfUFRNcykvbnJvdyhhbGxtdXRhdGlvbnMpKSoxMDAsMTAwLSgobnJvdyhhbGxtdXRhdGlvbnNfUFRNcykvbnJvdyhhbGxtdXRhdGlvbnMpKSoxMDApKQ0KbmFtZXModGFiMSkgPC0gYygiUFRNbXV0IiwgIm90aGVyIG11dGF0aW9ucyIpDQpwaWUodGFiMSwgY29sID0gYygicmVkIiwiIzQ1NzViNCIpKQ0KDQojTiBvZiBQVE1tdXQgYnkgZ2VuZSBsZW5ndGgjDQpsaWJyYXJ5KGdvc2VxKQ0KZyA8LSB1bmlxdWUoYWxsbXV0YXRpb25zX1BUTXMkZ2VuZSkNCmw8LSBnZXRsZW5ndGgoZywnaGcxOScsJ2dlbmVTeW1ib2wnKQ0KZGYgPC0gZGF0YS5mcmFtZShnPWcsbD1sKQ0KdGFiMSA8LSBhcy5kYXRhLmZyYW1lKHRhYmxlKGFsbG11dGF0aW9uc19QVE1zJGdlbmUpKQ0KZGYgPC0gbWVyZ2UoZGYsdGFiMSxieS54PSJnIixieS55PSJWYXIxIikNCmRmJHIgPC0gZGYkRnJlcS9kZiRsDQpkZiRzb3VyY2UgPC0gaWZlbHNlKGRmJGcgJWluJSBtYXQkYEdlbmUgU3ltYm9sYCwgIm1hdHJpc29tZSIsICJyZXN0Lm9mLmdlbm9tZSIpDQpkZiA8LSBuYS5vbWl0KGRmKQ0KdGFiMSA8LSBhcy5kYXRhLmZyYW1lKHRhYmxlKGFsbG11dGF0aW9ucyRnZW5lKSkNCmRmIDwtIG1lcmdlKGRmLHRhYjEsYnkueD0iZyIsYnkueT0iVmFyMSIpDQpkZiRGcmVxLnkgPC0gZGYkRnJlcS55LWRmJEZyZXEueA0KZGYkcjIgPC0gZGYkRnJlcS55L2RmJGwNCmRmIDwtIG5hLm9taXQoZGYpDQp2IDwtIGFnZ3JlZ2F0ZShkZiRyLGxpc3QoZGYkc291cmNlKSxtZWFuKQ0KdjIgPC0gYWdncmVnYXRlKGRmJHIyLGxpc3QoZGYkc291cmNlKSxtZWFuKSAjYWxsIG5vbi1QVE0tbXV0cyMNCnYgPC0gbWVyZ2Uodix2MixieT0iR3JvdXAuMSIpDQoNCmRmIDwtIGRhdGEuZnJhbWUoZz1nLGw9bCkNCnRhYjEgPC0gYXMuZGF0YS5mcmFtZSh0YWJsZShhbGxtdXRhdGlvbnMkZ2VuZSwgYWxsbXV0YXRpb25zJGBjYW5jZXIgdHlwZSBhYmJyZXZpYXRpb25gKSkNCmRmIDwtIG1lcmdlKGRmLHRhYjEsYnkueD0iZyIsYnkueT0iVmFyMSIpDQpkZiRyIDwtIGRmJEZyZXEvZGYkbA0KZGYgPC0gbmEub21pdChkZikNCnYgPC0gYWdncmVnYXRlKGRmJHIsbGlzdChkZiRWYXIyKSxtZWFuKQ0KdiRvcmQgPC0gYygxOm5yb3codikpDQp2IDwtIHZbb3JkZXIoLXYkb3JkKSxdDQp2JG9yZCA8LSBjKDE6bnJvdyh2KSkNCg0KZ2dwbG90KHYsIGFlcyh4PW9yZCwgeT14KSkgKyAjIHNpZGUgYnkgc2lkZSBiYXJwbG90ICMNCiAgICAgZ2VvbV9iYXIocG9zaXRpb249cG9zaXRpb25fZG9kZ2UoKSAsIHN0YXQ9ImlkZW50aXR5IiwgZmlsbD0iIzQ1NzViNCIpICsgDQogICAgIHRoZW1lX2NsYXNzaWMoKSArICANCiAgICAgY29vcmRfZmxpcCgpICsNCiAgICAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IGMoMTozMiksbGFiZWxzID0gYXMuY2hhcmFjdGVyKHYkR3JvdXAuMSkpICsNCiAgICAgeGxhYigiIikgKyB5bGFiKCJtZWFuIGZyZXF1ZW5jeSBvZiBtdXRhdGlvbnMgcGVyIGdlbmUgbGVuZ3RoIikNCg0KZGYgPC0gZGF0YS5mcmFtZShnPWcsbD1sKQ0KdGFiMSA8LSBhcy5kYXRhLmZyYW1lKHRhYmxlKGFsbG11dGF0aW9uc19QVE1zJGdlbmUsIGFsbG11dGF0aW9uc19QVE1zJGBjYW5jZXIgdHlwZSBhYmJyZXZpYXRpb25gKSkNCmRmIDwtIG1lcmdlKGRmLHRhYjEsYnkueD0iZyIsYnkueT0iVmFyMSIpDQpkZiRyIDwtIGRmJEZyZXEvZGYkbA0KZGYgPC0gbmEub21pdChkZikNCnYgPC0gYWdncmVnYXRlKGRmJHIsbGlzdChkZiRWYXIyKSxtZWFuKQ0KdiRvcmQgPC0gYygxOm5yb3codikpDQp2IDwtIHZbb3JkZXIoLXYkb3JkKSxdDQp2JG9yZCA8LSBjKDE6bnJvdyh2KSkNCg0KZ2dwbG90KHYsIGFlcyh4PW9yZCwgeT14KSkgKyAjIHNpZGUgYnkgc2lkZSBiYXJwbG90ICMNCiAgICAgZ2VvbV9iYXIocG9zaXRpb249cG9zaXRpb25fZG9kZ2UoKSAsIHN0YXQ9ImlkZW50aXR5IiwgZmlsbD0iI2ZkYWU2MSIpICsgDQogICAgIHRoZW1lX2NsYXNzaWMoKSArICANCiAgICAgY29vcmRfZmxpcCgpICsNCiAgICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPSIjZmRhZTYxIikgKw0KICAgICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gYygxOjMyKSxsYWJlbHMgPSBhcy5jaGFyYWN0ZXIodiRHcm91cC4xKSkgKw0KICAgICB4bGFiKCIiKSArIHlsYWIoIm1lYW4gZnJlcXVlbmN5IG9mIFBUTW11dCBwZXIgZ2VuZSBsZW5ndGgiKQ0KDQpgYGANCg0KVHJhbnNpdGlvbnMgYW5kIHRyYW5zdmVyc2lvbnMgYXJlIGFsc28gZXF1YWwgaW4gYWJzb2x1dGUsIGluIGxpbmUgd2l0aCBwcmV2aW91cyByZXBvcnRzLiBJbiBjb21wYXJpc29uLCBhbHNvLCB0cmFuc2l0aW9ucyBhbmQgdHJhbnN2ZXJzaW9ucyBzZWVtIHRvIGhhdmUgc2ltaWxhciBtdXRhdGlvbmFsIGVmZmVjdCwgdGhlIG9ubHkgbm90aWNlYWJsZSBkaWZmZXJlbmNlIGJlaW5nIHRoZSByZWR1Y2VkIGFtb3VudCBvZiB0cmFuc3ZlcnNpb25zIGFtb25nIHNwbGljZS1zaXRlIG11dGF0aW9ucy4gDQoNCmBgYHtyLCBldmFsPUZ9DQoNCm0gPC0gc3Vic2V0KGFsbG11dGF0aW9uc19QVE1zLCAhbmNoYXIoYWxsbXV0YXRpb25zX1BUTXMkcmVmZXJlbmNlKSA+IDEpDQptIDwtIHN1YnNldChtLCAhbmNoYXIobSRhbHQpID4gMSkNCm0gPC0gc3Vic2V0KG0sICFtJHJlZmVyZW5jZSA9PSAiLSIpDQptIDwtIHN1YnNldChtLCAhbSRhbHQgPT0gIi0iKQ0KbSRtdXQudHlwZSA8LSBwYXN0ZTAobSRyZWZlcmVuY2UsbSRhbHQpDQptJG11dC50eXBlIDwtIGlmZWxzZShtJG11dC50eXBlID09ICJBQyIsICJ0cmFuc3ZlcnNpb25zIiwgaWZlbHNlKG0kbXV0LnR5cGUgPT0gIkNBIiwgInRyYW5zdmVyc2lvbnMiLCBpZmVsc2UobSRtdXQudHlwZSA9PSAiR1QiLCAidHJhbnN2ZXJzaW9ucyIsIGlmZWxzZShtJG11dC50eXBlID09ICJURyIsICJ0cmFuc3ZlcnNpb25zIiwgInRyYW5zaXRpb25zIikpKSkNCmNoaXNxLnRlc3QodGFibGUobSRpcy5tYXRyaXNvbWUsbSRtdXQudHlwZSkpICNQLXZhbHVlID0gMC41NDMxDQp0YWIxIDwtIGFzLmRhdGEuZnJhbWUodGFibGUobSRpcy5tYXRyaXNvbWUsbSRtdXQudHlwZSkpDQoNCmxpYnJhcnkoZ2dzY2kpDQpnZ3Bsb3QodGFiMSwgYWVzKHg9VmFyMSwgeT1GcmVxLCBmaWxsPVZhcjIpKSArDQogICAgIGdlb21fYmFyKHBvc2l0aW9uPSJmaWxsIiAsIHN0YXQ9ImlkZW50aXR5IikgKyANCiAgICAgc2NhbGVfZmlsbF9hYWFzKCkgKyANCiAgICAgdGhlbWVfY2xhc3NpYygpICsgeGxhYigiIikgDQoNCm0gPC0gc3Vic2V0KGFsbG11dGF0aW9ucywgIW5jaGFyKGFsbG11dGF0aW9ucyRyZWZlcmVuY2UpID4gMSkNCm0gPC0gc3Vic2V0KG0sICFuY2hhcihtJGFsdCkgPiAxKQ0KbSA8LSBzdWJzZXQobSwgIW0kcmVmZXJlbmNlID09ICItIikNCm0gPC0gc3Vic2V0KG0sICFtJGFsdCA9PSAiLSIpDQptJG11dC50eXBlIDwtIHBhc3RlMChtJHJlZmVyZW5jZSxtJGFsdCkNCm0kbXV0LnR5cGUgPC0gaWZlbHNlKG0kbXV0LnR5cGUgPT0gIkFDIiwgInRyYW5zdmVyc2lvbnMiLCBpZmVsc2UobSRtdXQudHlwZSA9PSAiQ0EiLCAidHJhbnN2ZXJzaW9ucyIsIGlmZWxzZShtJG11dC50eXBlID09ICJHVCIsICJ0cmFuc3ZlcnNpb25zIiwgaWZlbHNlKG0kbXV0LnR5cGUgPT0gIlRHIiwgInRyYW5zdmVyc2lvbnMiLCAidHJhbnNpdGlvbnMiKSkpKQ0KY2hpc3EudGVzdCh0YWJsZShtJGlzLm1hdHJpc29tZSxtJG11dC50eXBlKSkgI1AtdmFsdWUgPSAwLjAxMjUxLCBidXQgaWYgdXNpbmcgJSdzIFt0YiA8LSB0YWJsZShtJGlzLm1hdHJpc29tZSxtJG11dC50eXBlKSA7IHRiIDwtICh0Yi9yb3dTdW1zKHRiKSkqMTAwXSB0aGVuIHRoZXJlIGNsZWFybHkgYXJlIG5vIGRpZmZlcmVuY2VzIQ0KdGFiMSA8LSBhcy5kYXRhLmZyYW1lKHRhYmxlKG0kaXMubWF0cmlzb21lLG0kbXV0LnR5cGUpKQ0KDQpnZ3Bsb3QodGFiMSwgYWVzKHg9VmFyMSwgeT1GcmVxLCBmaWxsPVZhcjIpKSArDQogICAgIGdlb21fYmFyKHBvc2l0aW9uPSJmaWxsIiAsIHN0YXQ9ImlkZW50aXR5IikgKyANCiAgICAgc2NhbGVfZmlsbF9hYWFzKCkgKyANCiAgICAgdGhlbWVfY2xhc3NpYygpICsgeGxhYigiIikNCg0KbSA8LSBzdWJzZXQoYWxsbXV0YXRpb25zX1BUTXMsICFuY2hhcihhbGxtdXRhdGlvbnNfUFRNcyRyZWZlcmVuY2UpID4gMSkNCm0gPC0gc3Vic2V0KG0sICFuY2hhcihtJGFsdCkgPiAxKQ0KbSA8LSBzdWJzZXQobSwgIW0kcmVmZXJlbmNlID09ICItIikNCm0gPC0gc3Vic2V0KG0sICFtJGFsdCA9PSAiLSIpDQptJG11dC50eXBlIDwtIHBhc3RlMChtJHJlZmVyZW5jZSxtJGFsdCkNCm0kbXV0LnR5cGUgPC0gaWZlbHNlKG0kbXV0LnR5cGUgPT0gIkFDIiwgInRyYW5zdmVyc2lvbnMiLCBpZmVsc2UobSRtdXQudHlwZSA9PSAiQ0EiLCAidHJhbnN2ZXJzaW9ucyIsIGlmZWxzZShtJG11dC50eXBlID09ICJHVCIsICJ0cmFuc3ZlcnNpb25zIiwgaWZlbHNlKG0kbXV0LnR5cGUgPT0gIlRHIiwgInRyYW5zdmVyc2lvbnMiLCAidHJhbnNpdGlvbnMiKSkpKQ0KDQptLjEgPC0gc3Vic2V0KG0sIG0kaXMubWF0cmlzb21lID09ICJtYXRyaXNvbWUiKQ0KY2hpc3EudGVzdCh0YWJsZShtLjEkbXV0LnR5cGUsbS4xJGVmZmVjdCkpICNQLXZhbHVlID0gNC43MDhlLTA2DQp0YWIxIDwtIGFzLmRhdGEuZnJhbWUodGFibGUobS4xJG11dC50eXBlLG0uMSRlZmZlY3QpKQ0KbS4yIDwtIHN1YnNldChtLCBtJGlzLm1hdHJpc29tZSAhPSAibWF0cmlzb21lIikNCmNoaXNxLnRlc3QodGFibGUobS4yJG11dC50eXBlLG0uMiRlZmZlY3QpKSAjUC12YWx1ZSA9IDIuMmUtMTYNCnRhYjIgPC0gYXMuZGF0YS5mcmFtZSh0YWJsZShtLjIkbXV0LnR5cGUsbS4yJGVmZmVjdCkpDQp0YWIxJHNvdXJjZSA8LSAibWF0cmlzb21lIg0KdGFiMiRzb3VyY2UgPC0gInJlc3Qub2YuZ2Vub21lIg0KdGFiIDwtIGJpbmRfcm93cyh0YWIxLHRhYjIpDQp0YWIkc291cmNlIDwtIHBhc3RlMCh0YWIkc291cmNlLCJfIix0YWIkVmFyMSkNCnRhYiA8LSBzdWJzZXQodGFiLCB0YWIkVmFyMiAhPSAiT3RoZXJzIikgI3RoZXJlIGlzIGp1dCBvbmUgIk90aGVycyIgbXV0YXRpb24gaGVyZSwgdG8gYmUgcmVtb3ZlZCB0byBtYWtlIGNvbHVtbnMgY29tcGF0aWJsZSBsYXRlciMNCnJlcyA8LSBsaXN0KCkNCmZvcihpIGluIHVuaXF1ZSh0YWIkVmFyMikpew0KICB6IDwtIHN1YnNldCh0YWIsIHRhYiRWYXIyID09IGkpDQogIGRmIDwtIGFzLmRhdGEuZnJhbWUoeiRGcmVxKQ0KICBuYW1lcyhkZikgPC0gdW5pcXVlKHokVmFyMikNCiAgcmVzW1tpXV0gPC0gZGYNCn0NCnJlcyA8LSBiaW5kX2NvbHMocmVzKQ0Kcm93bmFtZXMocmVzKSA8LSB1bmlxdWUodGFiJHNvdXJjZSkNCg0KZ2dwbG90KHRhYiwgYWVzKHg9c291cmNlLCB5PUZyZXEsIGZpbGw9VmFyMikpICsNCiAgICAgZ2VvbV9iYXIocG9zaXRpb249ImZpbGwiICwgc3RhdD0iaWRlbnRpdHkiKSArIA0KICAgICBzY2FsZV9maWxsX2FhYXMoKSArIA0KICAgICB0aGVtZV9jbGFzc2ljKCkgKyB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDAuNSwgdmp1c3QgPSAwLjUsIHNpemUgPSAxMCkpICsgICB4bGFiKCIiKQ0KDQptLjEgPC0gbWVyZ2UobWF0LG0uMSxieS54PSJHZW5lIFN5bWJvbCIsYnkueT0iZ2VuZSIpDQptLjFhIDwtIHN1YnNldChtLjEsIG0uMSRtdXQudHlwZSA9PSAidHJhbnNpdGlvbnMiKQ0KbS4xYiA8LSBzdWJzZXQobS4xLCBtLjEkbXV0LnR5cGUgPT0gInRyYW5zdmVyc2lvbnMiKQ0KdDEgPC0gYXMuZGF0YS5mcmFtZSh0YWJsZShtLjFhJENhdGVnb3J5KSkNCnQxJHR5cGU9InRyYW5zaXRpb25zIg0KdDIgPC0gYXMuZGF0YS5mcmFtZSh0YWJsZShtLjFiJENhdGVnb3J5KSkNCnQyJHR5cGU9InRyYW5zdmVyc2lvbnMiDQoNCnRhYjEgPC0gcmJpbmQodDEsdDIpICNQLXZhbHVlIDogMC40MTIyIw0KDQpnZ3Bsb3QodGFiMSwgYWVzKHg9dHlwZSwgeT1GcmVxLCBmaWxsPVZhcjEpKSArDQogICAgIGdlb21fYmFyKHBvc2l0aW9uPSJmaWxsIiAsIHN0YXQ9ImlkZW50aXR5IikgKyANCiAgICAgc2NhbGVfZmlsbF9kMygpICsgDQogICAgIHRoZW1lX2NsYXNzaWMoKSArIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0ID0gMC41LCB2anVzdCA9IDAuNSwgc2l6ZSA9IDEwKSkgKyANCiAgeGxhYigiIikNCg0KYGBgDQoNCkF0IGEgZ2xhbmNlLCBjb21wYXJpc29uIG9mIGRpc3J1cHRpdmUgUFRNbXV0IGluIG1hdHJpc29tZSB2cyByZXN0IG9mIHRoZSBnZW5vbWUgc2hvd3MgYWNjdW11bGF0aW9uIG9mIGV2ZW50cyBwb3RlbnRpYWxseSBhZmZlY3RpbmcgaHlkcm94eWxhdGlvbiwgTi0gYW5kIE8tZ2x5Y29zeWxhdGlvbiBpbiB0aGUgbWF0cmlzb21lLCB3aGljaCBpcyBjb252ZXJzZWx5IGRldm9pZCBvZiBtZXRoeWxhdGlvbiwgc3Vtb3lsYXRpb24gYW5kIHViaXF1aXR5bGF0aW9uIGV2ZW50LiBBY2V0eWxhdGlvbiBhbmQgcGhvc2hwb3J5bGF0aW9uIGV2ZW50cyBzZWVtIGNvbXBhcmFibGUuIEhvd2V2ZXIsIHRoZSBiYXNlbGluZSBmcmVxdWVuY3kgb2YgUFRNIGV2ZW50cyBpbiB0aGUgZ2Vub21lIGlzIGRpZmZlcmVudCwgc28gd2UgbmVlZCB0byBhY2NvdW50IGZvciBzdWNoIGRpZmZlcmVuY2VzIGJlZm9yZSBjb21wYXJpbmcuIFdoZW4gd2UgZG8sIHdlIGdldCBhIG1lYXN1cmUgd2UgY2FsbCAiYnVyZGVuIiAoYWN0dWFsbHksIHRoZSBnbG9iYWwgYnVyZGVuKSBhbmQgd2Ugbm90aWNlIHRoYXQgdGhlIG1hdHJpc29tZSBhY2N1bXVsYXRlcyBtb3JlIFBUTW11dCB0aGFuIHRoZSByZXN0IG9mIHRoZSBnZW5vbWUgaW4gYWxtb3N0IGFsbCBjYXRlZ29yaWVzLCB3aXRoIHRoZSBtb3N0IGV2aWRlbnQgZGlmZmVyZW5jZXMgaW4gaHlkcm94eWxhdGlvbiwgTy1nbHljb3N5bGF0aW9uIGFuZCBhY2V0eWxhdGlvbi4gDQoNCmBgYHtyLCBldmFsPUZ9DQoNCiNvdmVyYWxsIFBUTXMgYW5kIFBUTW11dCBpbiBtYXRyaXNvbWUgdnMuIG5vbi1tYXRyaXNvbWUjDQphbGxQVE1zJHNvdXJjZSA8LSBpZmVsc2UoYWxsUFRNcyRnZW5lICVpbiUgbWF0JGBHZW5lIFN5bWJvbGAsICJtYXRyaXNvbWUiLCJyZXN0Lm9mLmdlbm9tZSIpDQp0YWIxIDwtIHRhYmxlKGFsbFBUTXMkc291cmNlLGFsbFBUTXMkUFRNKQ0KdGFiMiA8LSAodGFiMS9yb3dTdW1zKHRhYjEpKSoxMDAgDQpjaGlzcS50ZXN0KHRhYjIpICMgcC12YWx1ZSA9IDAuMDAwMTQ5IC0+IHNpZ25pZmljYW50ISAjDQpkZiA8LSBhcy5kYXRhLmZyYW1lKHRhYjIpDQpjb2xuYW1lcyhkZikgPC0gYygidHlwZSIsICJQVE0iLCAiZnJlcXVlbmN5ICglKSIpDQoNCmdncGxvdChkZiwgYWVzKHg9dHlwZSwgeT1gZnJlcXVlbmN5ICglKWAsIGZpbGw9UFRNKSkgKw0KICBnZW9tX2Jhcihwb3NpdGlvbj0iZmlsbCIgLCBzdGF0PSJpZGVudGl0eSIpICsgDQogIHRoZW1lX2NsYXNzaWMoKSArIA0KICBzY2FsZV9maWxsX2QzKCkgKyANCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgaGp1c3QgPSAwLjUsIHZqdXN0ID0gMC41LCBzaXplID0gMTApKSArIA0KICB4bGFiKCIiKQ0KDQp0YWIxIDwtIHRhYmxlKGFsbG11dGF0aW9uc19QVE1zJGlzLm1hdHJpc29tZSxhbGxtdXRhdGlvbnNfUFRNcyRQVE0pDQp0YWIyIDwtICh0YWIxL3Jvd1N1bXModGFiMSkpKjEwMCANCmNoaXNxLnRlc3QodGFiMikgIyBwLXZhbHVlID0gMC4wMDAyMTk0IC0+IHNpZ25pZmljYW50ISAjDQpkZiA8LSBhcy5kYXRhLmZyYW1lKHRhYjIpDQpjb2xuYW1lcyhkZikgPC0gYygidHlwZSIsICJQVE0iLCAiZnJlcXVlbmN5ICglKSIpDQoNCmdncGxvdChkZiwgYWVzKHg9dHlwZSwgeT1gZnJlcXVlbmN5ICglKWAsIGZpbGw9UFRNKSkgKw0KICBnZW9tX2Jhcihwb3NpdGlvbj0iZmlsbCIgLCBzdGF0PSJpZGVudGl0eSIpICsgDQogIHRoZW1lX2NsYXNzaWMoKSArIA0KICBzY2FsZV9maWxsX2QzKCkgKyANCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgaGp1c3QgPSAwLjUsIHZqdXN0ID0gMC41LCBzaXplID0gMTApKSArIA0KICB4bGFiKCIiKQ0KDQojbm9ybWFsaXphdGlvbiBvZiBOIG9mIFBUTW11dCBieSBOIFB0bSBzaXRlcyAoYnVyZGVuKSwgbWF0cmlzb21lIHZzLiBub24tbWF0cmlzb21lIw0KcmVzIDwtIGxpc3QoKQ0KZm9yKGkgaW4gdW5pcXVlKGFsbG11dGF0aW9uc19QVE1zJGBjYW5jZXIgdHlwZSBhYmJyZXZpYXRpb25gKSl7DQogIHByaW50KHBhc3RlMCgiYW5hbHl6aW5nIGNhbmNlcjogIixpKSkNCiAgeiA8LSBzdWJzZXQoYWxsbXV0YXRpb25zX1BUTXMsIGFsbG11dGF0aW9uc19QVE1zJGBjYW5jZXIgdHlwZSBhYmJyZXZpYXRpb25gID09IGkpDQogIHogPC0gdW5pcXVlKHpbLGMoMSwyLDE3KV0pDQogIHogPC0gYXMuZGF0YS5mcmFtZSh0YWJsZSh6JGdlbmUseiRQVE0pKQ0KICB6MiA8LSBzdWJzZXQoYWxsUFRNcywgYWxsUFRNcyRnZW5lICVpbiUgeiRWYXIxKQ0KICB6MiA8LSBhcy5kYXRhLmZyYW1lKHRhYmxlKHoyJGdlbmUsejIkUFRNKSkNCiAgbSA8LSBtZXJnZSh6LHoyLGJ5PWMoIlZhcjEiLCJWYXIyIiksYWxsLnk9VCkNCiAgbSR0b3QgPC0gcm93U3VtcyhtWyxjKDMsNCldKQ0KICAjbSA8LSBzdWJzZXQobSxtJHRvdCA+IDApDQogIG0kYnVyZGVuIDwtIHJvdW5kKChtJEZyZXEueC9tJHRvdCkqMTAwLDIpDQogIG0kYnVyZGVuW2lzLm5hKG0kYnVyZGVuKV0gPC0gMA0KICBtJGNhbmNlciA8LSBpDQogIHJlc1tbaV1dIDwtIG0NCn0NCnJlcyA8LSBiaW5kX3Jvd3MocmVzKQ0KcmVzJHNvdXJjZSA8LSBpZmVsc2UocmVzJFZhcjEgJWluJSBtYXQkYEdlbmUgU3ltYm9sYCwgIm1hdHJpc29tZSIsICJyZXN0IG9mIGdlbm9tZSIpDQp2IDwtIGFnZ3JlZ2F0ZShyZXMkYnVyZGVuLGxpc3QocmVzJFZhcjIsIHJlcyRzb3VyY2UpLG1lYW4pDQp2MiA8LSBkYXRhLmZyYW1lKEdyb3VwLjE9Imh5ZHJveHlsYXRpb24iLEdyb3VwLjI9InJlc3Qgb2YgZ2Vub21lIix4PTApDQp2IDwtIGJpbmRfcm93cyh2LHYyKQ0KDQpnZ3Bsb3QodiwgYWVzKHg9R3JvdXAuMSwgeT14LCBmaWxsPUdyb3VwLjIpKSArICMgc2lkZSBieSBzaWRlIGJhcnBsb3QgIw0KICAgICBnZW9tX2Jhcihwb3NpdGlvbj1wb3NpdGlvbl9kb2RnZSgpICwgc3RhdD0iaWRlbnRpdHkiKSArIA0KICAgICB0aGVtZV9jbGFzc2ljKCkgKyANCiAgICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gdmVjKSArIA0KICAgICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDAuNSwgdmp1c3QgPSAwLjUsIHNpemUgPSAxMCkpICsgICAgICB4bGFiKCIiKSArIHlsYWIoIiIpDQoNCnJlczIgPC0gbGlzdCgpDQpmb3IoaSBpbiB1bmlxdWUocmVzJFZhcjIpKXsNCiAgeiA8LSBzdWJzZXQocmVzLCByZXMkVmFyMj09aSkNCiAgeCA8LSBzdWJzZXQoeix6JHNvdXJjZSA9PSAibWF0cmlzb21lIikNCiAgeSA8LSBzdWJzZXQoeiwgeiRzb3VyY2UgIT0gIm1hdHJpc29tZSIpDQogIHR0IDwtIHQudGVzdCh4JGJ1cmRlbix5JGJ1cmRlbikNCiAgcCA8LSBwYXN0ZTAoInAgdmFsdWUgZm9yICIsaSwiIDogIixmb3JtYXQucHZhbCh0dCRwLnZhbHVlKSkNCiAgcmVzMltbaV1dIDwtIHANCn0gI2h5ZHJveHlsYXRpb24gY2Fubm90IGJlIGNvdW50ZWQsIHdlIHByb3h5IGF0IHAtdmFsdWUgPCAyLjIyZS0xNiMNCg0KdjIgPC0gbGlzdCgpDQpmb3IoaSBpbiB1bmlxdWUodiRHcm91cC4yKSl7DQogIHogPC0gc3Vic2V0KHYsIHYkR3JvdXAuMiA9PSBpKQ0KICBkZiA8LSBkYXRhLmZyYW1lKHNvdXJjZT1pLCB0KHokeCkpDQogIG5hbWVzKGRmKVsyOm5jb2woZGYpXSA8LSB6JEdyb3VwLjENCiAgdjJbW2ldXSA8LSBkZg0KfQ0KdjIgPC0gYmluZF9yb3dzKHYyKQ0KdjIgPC0gdjJbLGMoMTo5KV0NCnJvd25hbWVzKHYyKSA8LSB2MlssMV0NCnYyWywxXSA8LSBOVUxMDQp2Mltpcy5uYSh2MildIDwtIDAuMDAwMDAwMDAwMDENCnYyIDwtIHYyWzEsXS92MlsyLF0NCnYyDQoNCmBgYA0KDQpGaW5hbGx5LCB3ZSByZWNhbGN1bGF0ZSB0aGUgYnVyZGVucyAoZXhhY3QgYnkgbXV0YXRpb24pLCBtZXJnZSB3aXRoIHRoZSBQVE1tdXQgdGFibGUgYW5kIGV4cG9ydCBpdC4NCg0KYGBge3IsIGV2YWw9Rn0NCg0KcmVzIDwtIGxpc3QoKQ0KZm9yKGkgaW4gdW5pcXVlKGFsbG11dGF0aW9uc19QVE1zJGBjYW5jZXIgdHlwZSBhYmJyZXZpYXRpb25gKSl7DQogIHByaW50KHBhc3RlMCgiYW5hbHl6aW5nIGNhbmNlcjogIixpKSkNCiAgeiA8LSBzdWJzZXQoYWxsbXV0YXRpb25zX1BUTXMsIGFsbG11dGF0aW9uc19QVE1zJGBjYW5jZXIgdHlwZSBhYmJyZXZpYXRpb25gID09IGkpDQogIHogPC0gdW5pcXVlKHpbLGMoMSwyLDE3KV0pDQogIGcgPC0geiRnZW5lDQogIHokZ2VuZSA8LSBwYXN0ZTAoeiRnZW5lLCJfIix6JEFtaW5vX0FjaWRfQ2hhbmdlKQ0KICB6IDwtIGFzLmRhdGEuZnJhbWUodGFibGUoeiRnZW5lLHokUFRNKSkNCiAgejIgPC0gc3Vic2V0KGFsbFBUTXMsIGFsbFBUTXMkZ2VuZSAlaW4lIGcpDQogIHoyIDwtIGFzLmRhdGEuZnJhbWUodGFibGUoejIkZ2VuZSx6MiRQVE0pKQ0KICB6JG5WYXIxIDwtIGdzdWIoIlxcXy4qIiwiIix6JFZhcjEpDQogIG5hbWVzKHopWzFdIDwtICJpZCINCiAgbmFtZXMoeilbNF0gPC0gIlZhcjEiDQogIG0gPC0gbWVyZ2Uoeix6MixieT1jKCJWYXIxIiwiVmFyMiIpLGFsbC55PVQpDQogIG1baXMubmEobSldPC0gMA0KICBtJHRvdCA8LSByb3dTdW1zKG1bLGMoNCw1KV0pDQogIG0gPC0gc3Vic2V0KG0sbSR0b3QgPiAwKQ0KICBtJGJ1cmRlbiA8LSByb3VuZCgobSRGcmVxLngvbSR0b3QpKjEwMCwyKQ0KICBtJGNhbmNlciA8LSBpDQogIG0gPC0gc3Vic2V0KG0sbSRidXJkZW4gPiAwKQ0KICByZXNbW2ldXSA8LSBtDQp9DQpyZXMgPC0gYmluZF9yb3dzKHJlcykNCmFsbG11dGF0aW9uc19QVE1zJGlkIDwtIHBhc3RlMChhbGxtdXRhdGlvbnNfUFRNcyRnZW5lLCJfIixhbGxtdXRhdGlvbnNfUFRNcyRBbWlub19BY2lkX0NoYW5nZSkNCg0KZmluIDwtIG1lcmdlKGFsbG11dGF0aW9uc19QVE1zLHJlcyxieS54PWMoImNhbmNlciB0eXBlIGFiYnJldmlhdGlvbiIsImlkIiksYnkueT1jKCJjYW5jZXIiLCJpZCIpKQ0KZmluIDwtIGFzLmRhdGEuZnJhbWUoZmluKQ0KZmluIDwtIGZpblssYygxLDQsMyw0OjE4LDI0KV0NCmZpblssNF0gPC0gTlVMTA0KZmluIDwtIGZpblssYyg0LDEsMywyLDU6bmNvbChmaW4pKV0NCmZ3cml0ZShmaW4sIlRBQkxFIDEudHh0IixzZXA9Ilx0Iixyb3cubmFtZXMgPSBGLHF1b3RlID0gRikNCg0KYGBgDQoNCkJlZm9yZSBtb3Zpbmcgb24sIHdlIGNhbGN1bGF0ZSB0aGUgY29ycmVsYXRpb24gYmV0d2VlbiB0aGUgYnVyZGVuIGFuZCB0aGUgTiBvZiBtdXRhdGlvbnMuIEV4cGVjdGVkbHksIHRoZXNlIHR3byBtZWFzdXJlcyBhcmUgZ2VuZXJhbGx5IG9wcG9zaXRlIGFuZCBicmVha2luZyBkb3duIGJ5IHR1bW9yIHR5cGUgYW5kIG1hdHJpc29tZSBjYXRlZ29yeSBjb25maXJtcyB0aGF0IGFsbCBzaWduaWZpY2FudCBjb3JyZWxhdGlvbnMgZm91bmQgYXJlIG5lZ2F0aXZlLg0KDQpgYGB7ciwgZXZhbD1GfQ0KDQpmaW4yIDwtIG1lcmdlKGZpbixtYXQsYnkueD0iZ2VuZSIsYnkueT0iR2VuZSBTeW1ib2wiKQ0KZmluMiA8LSB1bmlxdWUoZmluMikNCnJlcyA8LSBsaXN0KCkNCnJlczIgPC0gbGlzdCgpDQpmb3IoaSBpbiB1bmlxdWUoZmluMiRgY2FuY2VyIHR5cGUgYWJicmV2aWF0aW9uYCkpew0KICBwcmludChwYXN0ZTAoImFuYWx5emluZyB0dW1vcjogIixpKSkNCiAgeiA8LSBzdWJzZXQoZmluMiwgZmluMiRgY2FuY2VyIHR5cGUgYWJicmV2aWF0aW9uYCA9PSBpKQ0KICB6IDwtIG5hLm9taXQoeikNCiAgZm9yKHcgaW4gdW5pcXVlKHokQ2F0ZWdvcnkpKXsNCiAgICBwcmludChwYXN0ZTAoIioqKiBub3cgYW5hbHl6aW5nIG1hdHJpc29tZSBjYXRlZ29yeTogIix3KSkNCiAgICBkZiA8LSBzdWJzZXQoeiwgeiRDYXRlZ29yeSA9PSB3KQ0KICAgIHggPC0gZGZbLCBjKDEsMTgpXQ0KICAgIHkgPC0gYXMuZGF0YS5mcmFtZSh0YWJsZShkZiRnZW5lKSkNCiAgICBtIDwtIG5hLm9taXQobSkNCiAgICBtIDwtIG1lcmdlKHgseSxieS54PSJnZW5lIixieS55PSJWYXIxIikNCiAgICBpZihucm93KG0pPj00KXsNCiAgICAgICAgY3QgPC0gY29yLnRlc3QobSRidXJkZW4sbSRGcmVxKQ0KICAgICAgICBtJHR1bW9yIDwtIGkNCiAgICAgICAgbSRjYXRlZ29yeSA8LSB3DQogICAgICAgIG0kY29yciA8LSBjdCRlc3RpbWF0ZQ0KICAgICAgICBtJHB2YWwgPC0gY3QkcC52YWx1ZX1lbHNlew0KICAgICAgICAgIG0kdHVtb3IgPC0gaQ0KICAgICAgICAgIG0kY2F0ZWdvcnkgPC0gdw0KICAgICAgICAgIG0kY29yciA8LSAwDQogICAgICAgICAgbSRwdmFsIDwtIDENCiAgICAgICAgfQ0KICAgIGN0IDwtIE5VTEwNCiAgICByZXNbW3ddXSA8LSBtDQogIH0NCiAgcmVzMltbaV1dIDwtIGJpbmRfcm93cyhyZXMpDQp9DQpyZXMyIDwtIGJpbmRfcm93cyhyZXMyKQ0KcmVzMiRldmFsIDwtIGlmZWxzZShyZXMyJHB2YWwgPCAwLjA1LCAic2lnbmlmaWNhbnQiLCAiTlMiKQ0KcmVzMiRkaXIgPC0gaWZlbHNlKHJlczIkY29yciA+IDAgLCAicG9zaXRpdmUiLCAibmVnYXRpdmUiKQ0KDQpmb3IoaSBpbiB1bmlxdWUocmVzMiRjYXRlZ29yeSkpew0KICAgIHogPC0gc3Vic2V0KHJlczIsIHJlczIkY2F0ZWdvcnkgPT0gaSkNCiAgICBsbSA8LSBsbSh6JGJ1cmRlbn56JEZyZXEpDQogICAgcyA8LSBzdW1tYXJ5KGxtKQ0KICAgIGRmIDwtIGRhdGEuZnJhbWUoY2F0ZWdvcnk9aSwgbHI9cyRjb2VmZmljaWVudHNbMiwxXSwgcHZhbD1mb3JtYXQucHZhbChzJGNvZWZmaWNpZW50c1syLDRdKSkNCiAgICBwcmludChkZikNCn0NCg0KcmVzMyA8LSB1bmlxdWUocmVzMlssYyg0OjkpXSkNCnRhYmxlKHJlczMkY2F0ZWdvcnkscmVzMyRldmFsLHJlczMkZGlyKQ0KDQpnZ3Bsb3QocmVzMiwgYWVzKHg9YnVyZGVuLCB5PUZyZXEsIGNvbG9yPWZhY3RvcihjYXRlZ29yeSkpKSArDQogIGdlb21fcG9pbnQoKSArIA0KICBzY2FsZV9jb2xvcl9kMygpICsgDQogIHRoZW1lX21pbmltYWwoKSArIA0KICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iKQ0KDQp0YWIgPC0gYXMuZGF0YS5mcmFtZSh0YWJsZShyZXMzJGNhdGVnb3J5LHJlczMkZXZhbCxyZXMzJGRpcikpDQpkZiA8LSBhZ2dyZWdhdGUodGFiJEZyZXEsYnk9bGlzdCh0YWIkVmFyMywgdGFiJFZhcjIpLHN1bSkNCnBpZShjKGRmWzEsM10sZGZbMywzXSksY29sID0gYygiYmx1ZSIsInJlZCIpKQ0KcGllKGMoZGZbMiwzXSxkZls0LDNdKSxjb2wgPSBjKCJibHVlIiwicmVkIikpDQoNCmBgYA0KDQpTVEVQIDM6IFBUTW11dCBpbiB0aGUgdHVtb3IgbWF0cmlzb21lIGF0IHRoZSBnZW5lIGxldmVsLiBIZXJlIHdlIGZvY3VzIG9uIHRoZSBtYXRyaXNvbWUsIGNvbXBhcmluZyBmcmVxdWVuY3kgb2YgbXV0YXRpb25zIGJldHdlZW4gZGlmZmVyZW50IG1hdHJpc29tZSBjYXRlZ29yaWVzIGFjcm9zcyBjYW5jZXJzIGV0Yy4gQWdncmVnYXRpbmcgZGF0YSBieSBtYXRyaXNvbWUgY2F0ZWdvcnkgc2hvd3MgYSBjbGVhciByZWRpc3RyaWJ1dGlvbiBvZiBQVE1tdXQgdHlwZXMsIHNvIHRoYXQsIGUuZy4sIGh5ZHJveHlsYXRpb24gaXMgNTIlIG9mIGFsbCBQVE1tdXQgaW4gY29sbGFnZW5zIChhbmQgaHlkcm94eS1QVE1tdXQgaW4gY29sbGFnZW5zIGFyZSA3NCUgb2YgYWxsIFBUTW11dCBmb3IgdGhhdCBQVE0pIHdoaWxlIHBob3Nob3J5bGF0aW9uIGlzIDcyJSBvZiBhbGwgUFRNbXV0IGluIHNlY3JldGVkIGZhY3RvcnMgYW5kIDIxLjQlIG9mIGFsbCBwaG9zaHBvLVBUTW11dC4NCg0KYGBge3IsIGV2YWw9Rn0NCg0KZmluMiA8LSB1bmlxdWUobWVyZ2UoZmluLG1hdCxieS54PSJnZW5lIixieS55PSJHZW5lIFN5bWJvbCIpKQ0KbSA8LSBzdWJzZXQoZmluMiwgZmluMiRpcy5tYXRyaXNvbWUgPT0gIm1hdHJpc29tZSIpDQpjaGlzcS50ZXN0KHRhYmxlKG0kRGl2aXNpb24sIG0kUFRNKSkgI3AtdmFsdWUgPCAyLjJlLTE2IHNpZ25pZmljYW50ISMNCmRmIDwtIGFzLmRhdGEuZnJhbWUodGFibGUobSREaXZpc2lvbiwgbSRQVE0pKQ0KDQp2ZWMgPC0gYygiZGFya2toYWtpIiwiY2FkZXRibHVlMyIsInNreWJsdWU0IiwicGVhY2hwdWZmMyIsIm5hdmFqb3doaXRlNCIsInNhbmR5YnJvd24iLCJjYWRldGJsdWU0IiwibGlnaHRibHVlMyIpDQoNCmdncGxvdChkZiwgYWVzKHg9VmFyMSwgeT1GcmVxLCBmaWxsPVZhcjIpKSArDQogIGdlb21fYmFyKHBvc2l0aW9uPSJmaWxsIiAsIHN0YXQ9ImlkZW50aXR5IikgKyANCiAgdGhlbWVfY2xhc3NpYygpICsgDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IHZlYykgKyANCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSAwLCBoanVzdCA9IDAuNSwgdmp1c3QgPSAwLjUsIHNpemUgPSAxMCkpICsgDQogIHhsYWIoIiIpDQoNCmNoaXNxLnRlc3QodGFibGUobSRDYXRlZ29yeSwgbSRQVE0pKSAjcC12YWx1ZSA8IDIuMmUtMTYgc2lnbmlmaWNhbnQhIw0KZGYgPC0gYXMuZGF0YS5mcmFtZSh0YWJsZShtJENhdGVnb3J5LCBtJFBUTSkpDQoNCmdncGxvdChkZiwgYWVzKHg9VmFyMSwgeT1GcmVxLCBmaWxsPVZhcjIpKSArDQogIGdlb21fYmFyKHBvc2l0aW9uPSJmaWxsIiAsIHN0YXQ9ImlkZW50aXR5IikgKyANCiAgdGhlbWVfY2xhc3NpYygpICsgDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IHZlYykgKyANCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgaGp1c3QgPSAwLjUsIHZqdXN0ID0gMC41LCBzaXplID0gMTApKSArIA0KICB4bGFiKCIiKQ0KDQojUFRNbXV0LCAlIG9mIHRvdCBieSBtYXRyaXNvbWUgY2F0ZWdvcnkNCnRhYiA8LSB0YWJsZShmaW4yJENhdGVnb3J5LGZpbjIkUFRNKQ0KdGFiMSA8LSByb3VuZCgodGFiL3Jvd1N1bXModGFiKSkqMTAwLDEpDQp0YWIxDQoNCiNQVE1tdXQsICUgb2YgdG90IGJ5IHRvdGFsIGluIHRoYXQgdHlwZSBvZiBQVE0NCnYgPC0gYXBwbHkodGFiLDIsc3VtKQ0KdGFiMiA8LSBhcHBseSh0YWIsMixmdW5jdGlvbih4KXtyb3VuZCgoeC9zdW0oeCkqMTAwKSwxKX0pDQoNCmBgYA0KDQpUaGUgdHJlbmRzIHNlZW0gdmVyeSBzaW1pbGFyIHBhbi1jYW5jZXIsIHdpdGggU0tDTSwgU1RBRCBhbmQgVUNFQyBjbGVhcmx5IGxlYWRpbmcgdGhlIHdheS4gVGhlcmUgc2VlbSB0byBiZSBubyB0aXNzdWUtb2Ytb3JpZ2luIGVmZmVjdHMsIHRob3VnaCB2YXJpb3VzIGxvY2FsIGRpZmZlcmVuY2VzIGV4aXN0Lg0KDQpgYGB7ciwgZXZhbD1GfQ0KDQp0YWIgPC0gYXMuZGF0YS5mcmFtZSh0YWJsZShmaW4yJGBjYW5jZXIgdHlwZSBhYmJyZXZpYXRpb25gLGZpbjIkQ2F0ZWdvcnksZmluMiRQVE0pKQ0KDQpnZ3Bsb3QodGFiLCBhZXMoeD1WYXIxLCB5PVZhcjMsIHNpemU9RnJlcSwgZmlsbD1WYXIzKSkgKyANCmdlb21fcG9pbnQoYWxwaGE9MSwgc2hhcGU9MjEsIGNvbG9yPSJibGFjayIpICsgDQp0aGVtZV9jbGFzc2ljKCkgKyANCnNjYWxlX3NpemVfYXJlYShtYXhfc2l6ZSA9IDgsIG5hbWU9ImZyZXEiKSArDQpzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9dmVjKSArDQp0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZT05MCwgc2l6ZT0xMCwgdmp1c3Q9MC40KSwgYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoc2l6ZT04KSkgKyANCnhsYWIoIiIpICsgDQp5bGFiKCIiKSArDQpmYWNldF93cmFwKHRhYiRWYXIyKQ0KDQpyZXMgPC0gbGlzdCgpDQpyZXMyIDwtIGxpc3QoKQ0KZm9yKGkgaW4gdW5pcXVlKHRhYiRWYXIyKSl7DQogIHogPC0gc3Vic2V0KHRhYiwgdGFiJFZhcjIgPT0gaSkNCiAgeiRWYXIyIDwtIE5VTEwNCiAgZm9yKHcgaW4gdW5pcXVlKHokVmFyMykpew0KICAgIGsgPC0gc3Vic2V0KHosIHokVmFyMz09dykNCiAgICBkZiA8LSBkYXRhLmZyYW1lKG1hdHJpc29tZT1pLFBUTT13LHQoayRGcmVxKSkNCiAgICBuYW1lcyhkZilbMzpuY29sKGRmKV0gPC0gYXMuY2hhcmFjdGVyKGskVmFyMSkNCiAgICByZXNbW3ddXTwtIGRmDQogIH0NCiAgcmVzMltbaV1dIDwtIGJpbmRfcm93cyhyZXMpDQp9DQpyZXMyIDwtIGJpbmRfcm93cyhyZXMyKQ0KZndyaXRlKHJlczIsIlRBQkxFIDIudHh0Iiwgc2VwPSJcdCIsIHJvdy5uYW1lcyA9IEYsIHF1b3RlID0gRikNCg0KbmRmIDwtIGxpc3QoKQ0KZm9yKGkgaW4gdW5pcXVlKHJlczIkbWF0cmlzb21lKSl7DQogIHogPC0gc3Vic2V0KHJlczIsIHJlczIkbWF0cmlzb21lID09IGkpDQogIHByaW50KHBhc3RlMCgibm93IGdyYXBoaW5nIGRhdGEgZm9yOiAiLGkpKQ0KICByb3duYW1lcyh6KSA8LSB6JFBUTQ0KICB6IDwtIHpbLGMoMzpuY29sKHopKV0NCiAgZm9yKHcgaW4gMTpucm93KHopKXsNCiAgICBrIDwtIHpbdyxdDQogICAgayA8LSBhcy5kYXRhLmZyYW1lKHQoaykpDQogICAgayR0dW1vciA8LSByb3duYW1lcyhrKQ0KICAgIG5kZltbd11dIDwtIGsNCiAgfQ0KICBmIDwtIFJlZHVjZShtZXJnZSxuZGYpDQogIGYgPC0gcmVzaGFwZTI6Om1lbHQoZikNCiAgDQogIHAgPC0gZ2dwbG90KGRhdGE9ZiwgYWVzKHg9dHVtb3IsIHk9dmFsdWUsIGZpbGw9dmFyaWFibGUpKSArDQogIGdlb21fYmFyKHN0YXQ9ImlkZW50aXR5IikgKyANCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPXZlYykgKyANCiAgdGhlbWVfYncoKSArDQogIGZhY2V0X3dyYXAofmYkdmFyaWFibGUpICsgZ2d0aXRsZShpKSArIGdndGl0bGUoaSkgKyB0aGVtZShzdHJpcC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGNvbG91cj0iYmxhY2siLCBmaWxsPSJ3aGl0ZSIpLCBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCB2anVzdCA9IDAuNSwgaGp1c3Q9MSwgc2l6ZSA9IDQpKQ0KICBwbG90KHApDQp9DQoNCmBgYA0KDQpFeGFtcGxlcyBvZiBoeWRyb3h5bGF0aW9uIGFuZCBwaG9zcGhvcnlsYXRpb24gUFRNbXV0IGluIGNvbGxhZ2Vucy4NCg0KYGBge3IsIGV2YWw9Rn0NCg0KI3pvb20gaW46IGNvbGxhZ2VucyMNCnByb3ZhIDwtIHN1YnNldChmaW4yLCBmaW4yJENhdGVnb3J5ID09ICJDb2xsYWdlbnMiKQ0KcHJvdmEgPC0gc3Vic2V0KHByb3ZhLCBwcm92YSRQVE0gPT0gInBob3NwaG9yeWxhdGlvbiIgfCBwcm92YSRQVE0gPT0gImh5ZHJveHlsYXRpb24iKQ0KcHJvdmEgPC0gYXMuZGF0YS5mcmFtZSh0YWJsZShwcm92YSRnZW5lLHByb3ZhJFBUTSxwcm92YSRgY2FuY2VyIHR5cGUgYWJicmV2aWF0aW9uYCkpDQpwcm92YSA8LSBzdWJzZXQocHJvdmEsIHByb3ZhJEZyZXEgPiAwKQ0KDQpuZGYgPC0gbGlzdCgpDQpuZGYyIDwtIGxpc3QoKQ0KZm9yKGkgaW4gdW5pcXVlKHByb3ZhJFZhcjMpKXsNCiAgeiA8LSBzdWJzZXQocHJvdmEsIHByb3ZhJFZhcjMgPT0gaSkNCiAgZm9yKGogaW4gdW5pcXVlKHokVmFyMikpew0KICAgIGsgPC0gc3Vic2V0KHosIHokVmFyMiA9PSBqKQ0KICAgIGsgPC0ga1tvcmRlcigtayRGcmVxKSxdDQogICAgaWYobnJvdyhrKSA+PSAxMCl7ayA8LSBrWzE6MTAsXX1lbHNle2sgPC0ga30NCiAgICBuZGZbW2pdXSA8LSBrDQogIH0NCiAgbmRmMltbaV1dIDwtIGJpbmRfcm93cyhuZGYpDQp9DQpuZGYyIDwtIGJpbmRfcm93cyhuZGYyKQ0KbmRmMiA8LSBtZXJnZShuZGYyLG1hdCxieS54PSJWYXIxIixieS55PSJHZW5lIFN5bWJvbCIpDQoNCmdncGxvdChuZGYyLCBhZXMoeD1WYXIzLCB5PVZhcjEsIHNpemU9RnJlcSwgZmlsbD1WYXIzKSkgKyANCmdlb21fcG9pbnQoYWxwaGE9MSwgc2hhcGU9MjEsIGNvbG9yPSJibGFjayIpICsgDQp0aGVtZV9jbGFzc2ljKCkgKyANCnNjYWxlX3NpemVfYXJlYShtYXhfc2l6ZSA9IDgsIG5hbWU9ImZyZXEiKSArDQp0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZT05MCwgc2l6ZT0xMCwgdmp1c3Q9MC40KSwgYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoc2l6ZT04KSkgKyANCnhsYWIoIiIpICsgDQp5bGFiKCIiKSArDQpmYWNldF93cmFwKG5kZjIkVmFyMikNCg0KYGBgDQoNCkZpbmFsbHksIHRoZSBhYnNvbHV0ZSBUb3AxMCBwZXIgdHVtb3IgdHlwZSBhbmQgYSBsb29rIGF0IGV2ZW50dWFsICJob3RzcG90cyIgYWNyb3NzIGFuZCB3aXRoaW4gdGhlIGNvaG9ydHMuDQoNCmBgYHtyLCBldmFsPUZ9DQoNCnByb3ZhIDwtIGZpbjINCg0KIyB0byBzZWUgdGhlIHRhYmxlLCBmb3IgZGVzY3JpYmluZyBpdCBpbiB0ZXh0Og0KI3Byb3ZhIDwtIHRhYmxlKHByb3ZhJGdlbmUscHJvdmEkYGNhbmNlciB0eXBlIGFiYnJldmlhdGlvbmApDQojcHJvdmEgPC0gaWZlbHNlKHByb3ZhID09IDAsIDAgLDEpDQojcHJvdmEgPC0gYXMuZGF0YS5mcmFtZShwcm92YSkNCiNwcm92YSR0b3QgPC0gcm93U3Vtcyhwcm92YSkNCiNwcm92YSA8LSBwcm92YVtvcmRlcigtcHJvdmEkdG90KSxdDQoNCnByb3ZhIDwtIGFzLmRhdGEuZnJhbWUodGFibGUocHJvdmEkZ2VuZSxwcm92YSRgY2FuY2VyIHR5cGUgYWJicmV2aWF0aW9uYCkpDQpwcm92YSA8LSBzdWJzZXQocHJvdmEsIHByb3ZhJEZyZXEgPiAwKQ0KDQpyZXMgPC0gbGlzdCgpDQpmb3IoaSBpbiB1bmlxdWUocHJvdmEkVmFyMikpew0KICBwcmludChwYXN0ZTAoIm5vdyBhbmFseXppbmc6ICIsaSkpDQogIHogPC0gc3Vic2V0KHByb3ZhLCBwcm92YSRWYXIyID09IGkpDQogIHogPC0geltvcmRlcigteiRGcmVxKSxdDQogIGlmKG5yb3coeikgPD0gMTApe3ogPC0gen1lbHNle3ogPC0gelsxOjEwLF19DQogIHJlc1tbaV1dIDwtIHoNCn0NCnJlcyA8LSBiaW5kX3Jvd3MocmVzKQ0KDQpnZ3Bsb3QocmVzLCBhZXMoeD1WYXIyLCB5PVZhcjEsIHNpemU9RnJlcSwgZmlsbD1WYXIyKSkgKyANCmdlb21fcG9pbnQoYWxwaGE9MSwgc2hhcGU9MjEsIGNvbG9yPSJibGFjayIpICsgDQp0aGVtZV9jbGFzc2ljKCkgKyANCnNjYWxlX3NpemVfYXJlYShtYXhfc2l6ZSA9IDgsIG5hbWU9ImZyZXEiKSArDQp0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZT05MCwgc2l6ZT0xMCwgdmp1c3Q9MC40KSwgYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoc2l6ZT04KSkgKyANCnhsYWIoIiIpICsgDQp5bGFiKCIiKSANCg0KcHJvdmEgPC0gZmluMg0KcHJvdmEkbXV0IDwtIHBhc3RlKHByb3ZhJGdlbmUscHJvdmEkQW1pbm9fQWNpZF9DaGFuZ2UscHJvdmEkUFRNLHNlcD0iXyIpDQpwcm92YSA8LSB0YWJsZShwcm92YSRtdXQsIHByb3ZhJGBjYW5jZXIgdHlwZSBhYmJyZXZpYXRpb25gKQ0KcHJvdmEgPC0gYXMuZGF0YS5mcmFtZS5tYXRyaXgocHJvdmEpDQpwcm92YVtwcm92YSA+IDBdIDwtIDEgI3RoaXMgYWxsb3dzIGNhbGN1bGF0aW9uIG9mIGFic29sdXRlIG9jY3VycmVuY2VzIw0KcHJvdmEkdG90IDwtIHJvd1N1bXMocHJvdmEpDQpwcm92YSA8LSBwcm92YVtvcmRlcigtcHJvdmEkdG90KSxdDQpkZjEgPC0gZGF0YS5mcmFtZShtdXQ9cm93bmFtZXMocHJvdmEpLG4ub2YuY29ob3J0cz1wcm92YSR0b3QpDQpwcm92YSA8LSBmaW4yDQpwcm92YSRtdXQgPC0gcGFzdGUocHJvdmEkZ2VuZSxwcm92YSRBbWlub19BY2lkX0NoYW5nZSxwcm92YSRQVE0sc2VwPSJfIikNCnByb3ZhIDwtIHRhYmxlKHByb3ZhJG11dCwgcHJvdmEkYGNhbmNlciB0eXBlIGFiYnJldmlhdGlvbmApDQpwcm92YSA8LSBhcy5kYXRhLmZyYW1lLm1hdHJpeChwcm92YSkNCnByb3ZhJHRvdCA8LSByb3dTdW1zKHByb3ZhKQ0KcHJvdmEgPC0gcHJvdmFbb3JkZXIoLXByb3ZhJHRvdCksXQ0KZGYyIDwtIGRhdGEuZnJhbWUobXV0PXJvd25hbWVzKHByb3ZhKSxuLnRvdGFsPXByb3ZhJHRvdCkNCmRmIDwtIG1lcmdlKGRmMSxkZjIsYnk9Im11dCIsYWxsPVQpDQpkZiRjb2wgPC0gaWZlbHNlKGRmJG4ub2YuY29ob3J0cyA+PSAzICYgZGYkbi50b3RhbCA+PSAzLCAxLCAwKQ0KZGYgPC0gZGZbb3JkZXIoLWRmJGNvbCwgLWRmJG4ub2YuY29ob3J0cyksXQ0KZGYkY29sIDwtIGlmZWxzZShkZiRjb2w9PTEsICIxIiwgIjAiKQ0KZGYkbGFicyA8LSBnc3ViKCJfLioiLCIiLGRmJG11dCkNCg0KZ2dwbG90KGRmLCBhZXMobi50b3RhbCwgbi5vZi5jb2hvcnRzLCBjb2xvcj1jb2wsIGxhYmVsPWxhYnMpKSArIGdlb21fcG9pbnQoKSArDQogIHRoZW1lX2NsYXNzaWMoKSArIA0KICBzY2FsZV9jb2xvcl9hYWFzKCkgKw0KICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSAzLjAsIGxpbmV0eXBlPSJkb3R0ZWQiLCBjb2xvciA9ICJncmV5ODAiKSArDQogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDMuMCwgbGluZXR5cGU9ImRvdHRlZCIsIGNvbG9yID0gImdyZXk4MCIpDQoNCmRmIDwtIHN1YnNldChkZiwgZGYkY29sID09IDEpDQpkZiRjb2wgPC0gTlVMTA0KY29sbmFtZXMoZGYpIDwtIGMoIm11dGF0aW9uIiwibnVtYmVyIG9mIGNvaG9ydHMiLCJudW1iZXIgb2YgdG90YWwgb2NjdXJyZW5jZXMiKQ0KZndyaXRlKGRmLCAiVEFCTEUgMy50eHQiLCBzZXA9Ilx0IiwgcXVvdGUgPSBGLCByb3cubmFtZXMgPSBGKQ0KDQpgYGANCg0KU1RFUCA0OiBOb24tY29uc2VydmF0aW9uIG9mIGh5ZHJveHlsYXRpb24gYW5kIGdseWNvc3lsYXRpb24gUFRNbXV0LiBXaHkgaXMgbm9uZSBvZiB0aGVzZSBQVE1tdXQgaW4gdGhlIGhvdHNwb3QgbGlzdD8gSXMgdGhlcmUgYSBuZWdhdGl2ZSBzZWxlY3Rpb24gYWdhaW5zdCB0aGVtPyBXZSBjaGVjayBoZXJlIHRoZSBkaXN0cmlidXRpb24gb2YgUFRNbXV0IHR5cGUgcGVyIFNJRlQvcG9seXBoZW4gY2F0ZWdvcnkgYW5kIGV2YWx1YXRlIHRoZSByYXRpbyBvZiBub24tc2lsZW50IFBUTW11dCB0byBzaWxlbnQgUFRNbXV0IChkTi9kUyksIGFzIHJlcG9ydGVkIFtodHRwczovL2RvaS5vcmcvMTAuMTE4Ni9zMTMwNTktMDE4LTE0MzQtMF0uDQoNCmBgYHtyLCBldmFsPUZ9DQoNCnByb3ZhIDwtIGZpbjINCnRhYiA8LSB0YWJsZShwcm92YSRQVE0scHJvdmEkU0lGVCkNCnRhYiA8LSByb3VuZCgodGFiL3Jvd1N1bXModGFiKSkqMTAwLDApDQojZm9yIFNJRlQsIHdlIG1vdmUgdGhlIGxvdy1jb25maWRlbmNlIGZpbmRpbmdzIHRvIHRoZSAidW5rbm93biIgZ3JvdXAjDQp0YWIgPC0gdGFiWyxjKDEsMyw1KV0NCnRhYlssM10gPC0gMTAwLSh0YWJbLDFdK3RhYlssMl0pDQpjYyA8LSBjaGlzcS50ZXN0KHRhYikNCnRhYiA8LSByZXNoYXBlMjo6bWVsdCh0YWIpDQoNCmdncGxvdCh0YWIsIGFlcyh4PVZhcjEsIHk9dmFsdWUsIGZpbGw9VmFyMikpICsNCiAgZ2VvbV9iYXIocG9zaXRpb249ImZpbGwiICwgc3RhdD0iaWRlbnRpdHkiKSArIA0KICB0aGVtZV9jbGFzc2ljKCkgKyANCiAgc2NhbGVfZmlsbF9ucGcoKSArIA0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDAuNSwgdmp1c3QgPSAwLjUsIHNpemUgPSAxMCkpICsgDQogIHhsYWIoIiIpICsgZ2d0aXRsZShwYXN0ZTAoIm1hdHJpc29tZV9TSUZUIiwiXG4iLGNjJHAudmFsdWUpKQ0KDQphbGxwIDwtIHN1YnNldChhbGxtdXRhdGlvbnNfUFRNcywgYWxsbXV0YXRpb25zX1BUTXMkaXMubWF0cmlzb21lID09ICJyZXN0Lm9mLmdlbm9tZSIpDQp0YWIgPC0gdGFibGUoYWxscCRQVE0sYWxscCRTSUZUKQ0KdGFiIDwtIHJvdW5kKCh0YWIvcm93U3Vtcyh0YWIpKSoxMDAsMCkNCiNmb3IgU0lGVCwgd2UgbW92ZSB0aGUgbG93LWNvbmZpZGVuY2UgZmluZGluZ3MgdG8gdGhlICJ1bmtub3duIiBncm91cCMNCnRhYiA8LSB0YWJbLGMoMSwzLDUpXQ0KdGFiWywzXSA8LSAxMDAtKHRhYlssMV0rdGFiWywyXSkNCmNjIDwtIGNoaXNxLnRlc3QodGFiKQ0KdGFiIDwtIHJlc2hhcGUyOjptZWx0KHRhYikNCg0KZ2dwbG90KHRhYiwgYWVzKHg9VmFyMSwgeT12YWx1ZSwgZmlsbD1WYXIyKSkgKw0KICBnZW9tX2Jhcihwb3NpdGlvbj0iZmlsbCIgLCBzdGF0PSJpZGVudGl0eSIpICsgDQogIHRoZW1lX2NsYXNzaWMoKSArIA0KICBzY2FsZV9maWxsX25wZygpICsgDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0ID0gMC41LCB2anVzdCA9IDAuNSwgc2l6ZSA9IDEwKSkgKyANCiAgeGxhYigiIikgKyBnZ3RpdGxlKHBhc3RlMCgicmVzdCBvZiBnZW5vbWVfU0lGVCIsIlxuIixjYyRwLnZhbHVlKSkNCg0KdGFiIDwtIHRhYmxlKHByb3ZhJFBUTSxwcm92YSRQb2x5UGhlbikNCnRhYiA8LSByb3VuZCgodGFiL3Jvd1N1bXModGFiKSkqMTAwLDApDQojZm9yIFBvbHlwaGVuLCB3ZSBtZXJnZSB0aGUgImRhbWFnaW5nIiBncm91cHMgdG9nZXRoZXIjDQp0YWJbLDJdIDwtIHRhYlssMV0rdGFiWywyXQ0KdGFiIDwtIHRhYlssYygxLDIsNCldDQpjb2xuYW1lcyh0YWIpWzJdIDwtICJkYW1hZ2luZyINCmNjIDwtIGNoaXNxLnRlc3QodGFiKQ0KdGFiIDwtIHJlc2hhcGUyOjptZWx0KHRhYikNCmxldmVscyh0YWIkVmFyMikgPC0gYygiZGFtYWdpbmciLCJiZW5pZ24iLCJ1bmtub3duIikNCg0KZ2dwbG90KHRhYiwgYWVzKHg9VmFyMSwgeT12YWx1ZSwgZmlsbD1WYXIyKSkgKw0KICBnZW9tX2Jhcihwb3NpdGlvbj0iZmlsbCIgLCBzdGF0PSJpZGVudGl0eSIpICsgDQogIHRoZW1lX2NsYXNzaWMoKSArIA0KICBzY2FsZV9maWxsX25wZygpICsgDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0ID0gMC41LCB2anVzdCA9IDAuNSwgc2l6ZSA9IDEwKSkgKyANCiAgeGxhYigiIikgKyBnZ3RpdGxlKHBhc3RlMCgibWF0cmlzb21lX1BvbHlQaGVuIiwiXG4iLGNjJHAudmFsdWUpKQ0KDQp0YWIgPC0gdGFibGUoYWxscCRQVE0sYWxscCRQb2x5UGhlbikNCnRhYiA8LSByb3VuZCgodGFiL3Jvd1N1bXModGFiKSkqMTAwLDApDQojZm9yIFBvbHlwaGVuLCB3ZSBtZXJnZSB0aGUgImRhbWFnaW5nIiBncm91cHMgdG9nZXRoZXIjDQp0YWJbLDJdIDwtIHRhYlssMV0rdGFiWywyXQ0KdGFiIDwtIHRhYlssYygxLDIsNCldDQpjb2xuYW1lcyh0YWIpWzJdIDwtICJkYW1hZ2luZyINCmNjIDwtIGNoaXNxLnRlc3QodGFiKQ0KdGFiIDwtIHJlc2hhcGUyOjptZWx0KHRhYikNCmxldmVscyh0YWIkVmFyMikgPC0gYygiZGFtYWdpbmciLCJiZW5pZ24iLCJ1bmtub3duIikNCg0KZ2dwbG90KHRhYiwgYWVzKHg9VmFyMSwgeT12YWx1ZSwgZmlsbD1WYXIyKSkgKw0KICBnZW9tX2Jhcihwb3NpdGlvbj0iZmlsbCIgLCBzdGF0PSJpZGVudGl0eSIpICsgDQogIHRoZW1lX2NsYXNzaWMoKSArIA0KICBzY2FsZV9maWxsX25wZygpICsgDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0ID0gMC41LCB2anVzdCA9IDAuNSwgc2l6ZSA9IDEwKSkgKyANCiAgeGxhYigiIikgKyBnZ3RpdGxlKHBhc3RlMCgicmVzdCBvZiBnZW5vbWVfUG9seVBoZW4iLCJcbiIsY2MkcC52YWx1ZSkpDQoNCiN0byBjYWxjdWxhdGUgdGhlIGROL2RTIHJhdGlvLCB0aGUgaW5pdGlhbCBhbGxtdXRhdGlvbnMgZmlsZSBtdXN0IGJlIHJlY3JlYXRlZCBzaW5jZSB3ZSBoYWQgcHJldmlvdXNseSByZW1vdmVkIGFsbCBzaWxlbnQgbXV0YXRpb25zISMNCmFsbG11dGF0aW9ucyA8LSBmcmVhZCgiYWxsbXV0YXRpb25zLnR4dCIsIGhlYWRlcj1ULCBzZXA9Ilx0IikgIyBpbXBvcnQgbXV0YXRpb24gZGF0YSAjDQphbGxtdXRhdGlvbnMgPC0gYXMuZGF0YS5mcmFtZShhbGxtdXRhdGlvbnMpDQphbGxtdXRhdGlvbnMkZWZmZWN0IDwtIGdzdWIoIjNVVFIuKiIsICIzVVRSIiwgYWxsbXV0YXRpb25zJGVmZmVjdCkgDQphbGxtdXRhdGlvbnMkU0lGVCA8LSBnc3ViKCJcXCguKiIsICIiLCBhbGxtdXRhdGlvbnMkU0lGVCkNCmFsbG11dGF0aW9ucyRQb2x5UGhlbiA8LSBnc3ViKCJcXCguKiIsICIiLCBhbGxtdXRhdGlvbnMkUG9seVBoZW4pDQphbGxtdXRhdGlvbnMkQW1pbm9fQWNpZF9DaGFuZ2UgPC0gZ3N1YigiW14wLTldIiwgIiIsIGFsbG11dGF0aW9ucyRBbWlub19BY2lkX0NoYW5nZSkgDQphbGxtdXRhdGlvbnMkQW1pbm9fQWNpZF9DaGFuZ2UgPC0gYXMubnVtZXJpYyhhbGxtdXRhdGlvbnMkQW1pbm9fQWNpZF9DaGFuZ2UpDQphbGxtdXRhdGlvbnMkZWZmZWN0IDwtIGdzdWIoIkZyYW1lX1NoaWZ0X0RlbCIsICJGcmFtZXNoaWZ0IiwgYWxsbXV0YXRpb25zJGVmZmVjdCkgIyBzaW1wbGUgcmVjb2RlIGVmZmVjdCAjDQphbGxtdXRhdGlvbnMkZWZmZWN0IDwtIGdzdWIoIkZyYW1lX1NoaWZ0X0lucyIsICJGcmFtZXNoaWZ0IiwgYWxsbXV0YXRpb25zJGVmZmVjdCkNCmFsbG11dGF0aW9ucyRlZmZlY3QgPC0gZ3N1YigiSW5fRnJhbWVfRGVsIiwgIkRlbGV0aW9uIiwgYWxsbXV0YXRpb25zJGVmZmVjdCkNCmFsbG11dGF0aW9ucyRlZmZlY3QgPC0gZ3N1YigiSW5fRnJhbWVfSW5zIiwgIkluc2VydGlvbiIsIGFsbG11dGF0aW9ucyRlZmZlY3QpDQphbGxtdXRhdGlvbnMkZWZmZWN0IDwtIGdzdWIoIkZsYW5rIiwgIk90aGVycyIsIGFsbG11dGF0aW9ucyRlZmZlY3QpDQphbGxtdXRhdGlvbnMkZWZmZWN0IDwtIGdzdWIoIkludHJvbiIsIk90aGVycyIsIGFsbG11dGF0aW9ucyRlZmZlY3QpDQphbGxtdXRhdGlvbnMkZWZmZWN0IDwtIGdzdWIoImxhcmdlZCIsIkQiLCBhbGxtdXRhdGlvbnMkZWZmZWN0KQ0KYWxsbXV0YXRpb25zJGVmZmVjdCA8LSBnc3ViKCJfTXV0YXRpb24iLCIiLCBhbGxtdXRhdGlvbnMkZWZmZWN0KQ0KYWxsbXV0YXRpb25zJGVmZmVjdCA8LSBnc3ViKCJSTkEiLCAiT3RoZXJzIiwgYWxsbXV0YXRpb25zJGVmZmVjdCkNCmFsbG11dGF0aW9ucyRlZmZlY3QgPC0gZ3N1YigiX1MiLCAiIHMiLCBhbGxtdXRhdGlvbnMkZWZmZWN0KQ0KYWxsbXV0YXRpb25zJGVmZmVjdCA8LSBnc3ViKCJUcmFuc2xhdGlvbiBzdGFydCBzaXRlIiwgIk90aGVycyIsIGFsbG11dGF0aW9ucyRlZmZlY3QpDQphbGxtdXRhdGlvbnMkZWZmZWN0IDwtIGdzdWIoIlVUUiIsICJPdGhlcnMiLCBhbGxtdXRhdGlvbnMkZWZmZWN0KQ0KYWxsbXV0YXRpb25zJGVmZmVjdCA8LSBnc3ViKCJbXkEtel0iLCAiIiwgYWxsbXV0YXRpb25zJGVmZmVjdCkNCmFsbG11dGF0aW9ucyRQb2x5UGhlbiA8LSBnc3ViKCJeJCIsInVua25vd24iLGFsbG11dGF0aW9ucyRQb2x5UGhlbikgIyByZWNvZGluZyBwb2x5cGhlbiAmIFNJRlQgIw0KYWxsbXV0YXRpb25zJFNJRlQgPC0gZ3N1YigiXiQiLCJ1bmtub3duIixhbGxtdXRhdGlvbnMkU0lGVCkNCmNvbnYgPC0gZnJlYWQoInVuaXByb3RfbWF0cmlzb21lLnR4dCIsIGhlYWRlcj1GLCBzZXA9Ilx0IikgIyBsb2FkIGNvbnZlcnNpb24gZnJvbSBnZW5lcyB0byBwcm90ZWluIG5hbWVzICMNCmNvbnYgPC0gY29udlssYygxOjIpXQ0KY29sbmFtZXMoY29udikgPC0gYygiZ2VuZSIsIlVuaXByb3RfSUQiKQ0KbWF0IDwtIGZyZWFkKCJtYXRyaXNvbWVfaHMudHh0IiwgaGVhZGVyID0gVCwgc2VwPSJcdCIpIA0KY29udiA8LSBtZXJnZShjb252LCBtYXQsIGJ5Lng9ImdlbmUiLCBieS55PSJHZW5lIFN5bWJvbCIpDQpjb252IDwtIGFzLmRhdGEuZnJhbWUoY29udikNCmFsbG11dGF0aW9ucyRpcy5tYXRyaXNvbWUgPC0gaWZlbHNlKGFsbG11dGF0aW9ucyRnZW5lICVpbiUgbWF0JGBHZW5lIFN5bWJvbGAsICJtYXRyaXNvbWUiLCAicmVzdC5vZi5nZW5vbWUiKQ0KY2wgPC0gZnJlYWQoIlRDR0FjbGluZGF0YS50eHQiLCBoZWFkZXI9VCxzZXA9Ilx0IikNCmNsIDwtIGNsWyxjKDEsMyldDQpjbCA8LSB1bmlxdWUobWVyZ2UoY2wsIGFsbG11dGF0aW9ucywgYnk9InNhbXBsZSIpKQ0KYWxsbXV0YXRpb25zIDwtIGNsDQoNCnJlcyA8LSBsaXN0KCkNCnJlczIgPC0gbGlzdCgpDQpzdHAgPC0gMA0KcGIgPC0gdHh0UHJvZ3Jlc3NCYXIobWluID0gMCwgbWF4ID0gbGVuZ3RoKHVuaXF1ZShhbGxtdXRhdGlvbnNfUFRNcyRnZW5lKSksIHN0eWxlID0gMykNCmZvcihpIGluIHVuaXF1ZShhbGxtdXRhdGlvbnNfUFRNcyRnZW5lKSl7DQogIHN0cCA8LSBzdHArMQ0KICBzZXRUeHRQcm9ncmVzc0JhcihwYiwgc3RwKQ0KICB6MSA8LSBzdWJzZXQoYWxsbXV0YXRpb25zX1BUTXMsIGFsbG11dGF0aW9uc19QVE1zJGdlbmUgPT0gaSkNCiAgejIgPC0gc3Vic2V0KGFsbG11dGF0aW9ucywgYWxsbXV0YXRpb25zJGdlbmUgPT0gaSkNCiAgejIgPC0gc3Vic2V0KHoyLCB6MiRlZmZlY3QgPT0iU2lsZW50IikNCiAgcjEgPC0gbnJvdyh6MSkvbnJvdyh6MikNCiAgejMgPC0gc3Vic2V0KGFsbG11dGF0aW9ucywgYWxsbXV0YXRpb25zJGdlbmUgPT0gaSkNCiAgejMgPC0gc3Vic2V0KHozLCB6MyRlZmZlY3QgIT0iU2lsZW50IikNCiAgcjIgPC0gKG5yb3coejMpLW5yb3coejEpKS9ucm93KHoyKQ0KICANCiAgZGYgPC0gZGF0YS5mcmFtZShnZW5lPWksZE5fZFNfdG90PXIyLGROX2RTX1BUTT1yMSxQVE09ejEkUFRNKQ0KICANCiAgcmVzW1tpXV0gPC0gZGYNCn0NCnJlcyA8LSBiaW5kX3Jvd3MocmVzKQ0KcmVzJGlzLm1hdHJpc29tZSA8LSBpZmVsc2UocmVzJGdlbmUgJWluJSBtYXQkYEdlbmUgU3ltYm9sYCwgIm1hdHJpc29tZSIsICJyZXN0Lm9mLmdlbm9tZSIpDQpyZXMkciA8LSByZXMkZE5fZFNfUFRNL3JlcyRkTl9kU190b3QNCg0KcmVzMiA8LSBsaXN0KCkNCmZvcihpIGluIHVuaXF1ZShyZXMkUFRNKVsxOjddKXsNCiAgeCA8LSBzdWJzZXQocmVzLHJlcyRQVE0gPT0gaSAmIHJlcyRpcy5tYXRyaXNvbWUgPT0gIm1hdHJpc29tZSIpDQogIHkgPC0gc3Vic2V0KHJlcyxyZXMkUFRNID09IGkgJiByZXMkaXMubWF0cmlzb21lICE9ICJtYXRyaXNvbWUiKQ0KICB0dCA8LSB0LnRlc3QoeCRyLHkkcikNCiAgZGYgPC0gZGF0YS5mcmFtZShQVE09aSxtZWFuLm9mLnIubWF0cmlzb21lPXR0JGVzdGltYXRlWzFdLG1lYW4ub2Yuci5yZXN0PXR0JGVzdGltYXRlWzJdLHB2YWw9dHQkcC52YWx1ZSxkaXJlY3Rpb249aWZlbHNlKHR0JGVzdGltYXRlWzFdPnR0JGVzdGltYXRlWzJdLCJlbnJpY2hlZCBpbiBtYXRyaXNvbWUiLCJkZXBsZXRlZCBpbiBtYXRyaXNvbWUiKSkNCiAgcmVzMltbaV1dIDwtIGRmDQp9DQpyZXMyIDwtIGJpbmRfcm93cyhyZXMyKQ0KbSA8LSByZXNoYXBlMjo6bWVsdChyZXMyLCBpZC52YXI9IlBUTSIpDQptIDwtIG1bMToxNCxdDQoNCmdncGxvdChtLGFlcyhQVE0sIGFzLm51bWVyaWModmFsdWUpLCBjb2xvdXI9dmFyaWFibGUpLCBzaG93LmxlZ2VuZD1GQUxTRSkgKw0KICBnZW9tX2JveHBsb3QoKSArDQogIHRoZW1lX2J3KCkgKyB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCB2anVzdCA9IDAuNSwgaGp1c3Q9MSkpDQoNCnJlczIkZGlmZiA8LSByZXMyJG1lYW4ub2Yuci5tYXRyaXNvbWUvbWVhbi5vZi5yLnJlc3QNCm0gPC0gcmVzaGFwZTI6Om1lbHQocmVzMiwgaWQudmFyPSJQVE0iKQ0KbSA8LSBtWzI5Om5yb3cobSksXQ0KZ2dwbG90KG0sYWVzKFBUTSx2YWx1ZSxmaWxsPVBUTSksIHNob3cubGVnZW5kPUZBTFNFKSArDQogIGdlb21fYmFyKHN0YXQ9ImlkZW50aXR5IikgKw0KICB0aGVtZV9idygpICsgdGhlbWUoKSArIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIHZqdXN0ID0gMC41LCBoanVzdD0xKSkgKyBzY2FsZV9maWxsX2QzKCkNCg0KZndyaXRlKHJlczIsInRhYmxlIHB2YWxzIGROLWRTLnR4dCIsc2VwPSJcdCIscm93Lm5hbWVzID0gRiwgcXVvdGUgPSBGKQ0KDQpgYGANCg0KUFRNbXV0IGluIHRoZSB0dW1vciBtYXRyaXNvbWUgYXQgdGhlIHByb3RlaW4gZG9tYWluIGxldmVsLiBDb21wYXJpbmcgUFRNbXV0IGFuZCBub24tUFRNbXV0IHdlIGV4cGVjdCB0byBmaW5kIGVucmljaG1lbnRzL2RlcGxldGlvbnMgYXQgc29tZSBkb21haW5zIHRoYXQgbWlnaHQgZXhwbGFpbiB3aHkgc29tZSBmcmVxdWVudCB0eXBlcyBvZiBQVE1zIGFyZSBub3QgaW4gdGhlIHRvcC0xMCBsaXN0LiBUbyB0aGlzIGFpbSwgd2UgcHJvY2VlZCBhcyBmb2xsb3dzOg0KMSkgbWFwIFBUTW11dCBhbmQgbm9uLVBUTW11dCBpbnRvIHByb3RlaW4gZG9tYWlucw0KMikgc3Vic2V0IHRvIFBGQU0gZG9tYWlucyBvbmx5DQozKSBjb21wYXJlIHRoZSByYXRpbyBvZiBQVE1tdXQgdG8gbm9uLVBUTW11dCwgYnkgZG9tYWluLCBpbiBtYXRyaXNvbWUgYW5kIG5vbiBtYXRyaXNvbWUgZ2VuZXMNCjQpIGV2YWx1YXRlIHRoZSA1MyBkb21haW5zIGluIGNvbW1vbiBiZXR3ZWVuIG1hdHJpc29tZSBhbmQgbm9uIG1hdHJpc29tZSBmb3IgdGhlaXIgcmF0aW9zDQo1KSBldmFsdWF0ZSB0aGUgcmF0aW9zIGJ5IFBUTSB0eXBlcyBpbiB0aGVzZSBkb21haW5zLg0KTy1nbHljb3N5bGF0aW9uIGFuZCBhY3R5bGF0aW9uIGFyZSwgaW4gZmFjdCwgbGVzcyAidG9sZXJhdGVkIiENCg0KYGBge3IsIGV2YWw9Rn0NCg0KbGlicmFyeShlbnNlbWJsZGIpDQpsaWJyYXJ5KEVuc0RiLkhzYXBpZW5zLnY4NikNCmVkYiA8LSBFbnNEYi5Ic2FwaWVucy52ODYNCmxpYnJhcnkobWFncml0dHIpDQpwbmFtZXMgPC0gZWRiICU+JSBmaWx0ZXIofiBzeW1ib2wgPT0gdW5pcXVlKGFsbG11dGF0aW9uc19QVE1zJGdlbmUpICYgdHhfYmlvdHlwZSA9PSJwcm90ZWluX2NvZGluZyIpICU+JSBwcm90ZWlucw0KeiA8LSBzZWxlY3QoZWRiLCBrZXlzID0gcG5hbWVzJHByb3RlaW5faWQsIGtleXR5cGUgPSAiUFJPVEVJTklEIiwNCiAgICAgICAgICAgIGNvbHVtbnMgPSBjKCJQUk9URUlOSUQiLCAiR0VORU5BTUUiLCAiUFJPVERPTVNUQVJUIiwgIlBST1RET01FTkQiLCAiUFJPVEVJTkRPTUFJTklEIiwgIlBST1RFSU5ET01BSU5TT1VSQ0UiKSkNCnBvcyA8LSBhbGxtdXRhdGlvbnNfUFRNc1ssYygxLDIsNCwxNCwxNyldDQpwb3MgPC0gcG9zW3BvcyRnZW5lICVpbiUgeiRHRU5FTkFNRSxdDQpwb3MgPC0gcG9zW29yZGVyKHBvcyRgY2FuY2VyIHR5cGUgYWJicmV2aWF0aW9uYCksXQ0KbGlicmFyeShzcWxkZikNCmxpYnJhcnkoYml0NjQpDQpyZXMgPC0gbGlzdCgpDQpyZXMyIDwtIGxpc3QoKQ0KZm9yKGkgaW4gdW5pcXVlKHBvcyQnY2FuY2VyIHR5cGUgYWJicmV2aWF0aW9uJykpew0KICBwcmludChwYXN0ZTAoIlBUTSBzY2FubmluZyBkb21haW5zIGZvcjogIixpKSkNCiAgayA8LSB1bmlxdWUoc3Vic2V0KHBvcywgcG9zJCdjYW5jZXIgdHlwZSBhYmJyZXZpYXRpb24nID09IGkpKQ0KICBwYiA8LSB0eHRQcm9ncmVzc0JhcihtaW4gPSAwLCBtYXggPSBsZW5ndGgodW5pcXVlKGskZ2VuZSkpLCBzdHlsZSA9IDMpDQogIHN0cCA8LSAwDQogIGZvcihxIGluIHVuaXF1ZShrJGdlbmUpKXsNCiAgICBzdHAgPC0gc3RwKzENCiAgICBzZXRUeHRQcm9ncmVzc0JhcihwYiwgc3RwKQ0KICAgIGsyIDwtIHN1YnNldChrLCBrJGdlbmUgPT0gcSkNCiAgICBueiA8LSBzdWJzZXQoeiwgeiRHRU5FTkFNRSA9PSBxKQ0KICAgIGRiIDwtIHNxbGRmKCJzZWxlY3QgKiBmcm9tIGsyDQogICAgICAgICAgICAgICAgbGVmdCBqb2luIG56DQogICAgICAgICAgICAgICAgb24gazIuQW1pbm9fQWNpZF9DaGFuZ2UgYmV0d2VlbiBuei5QUk9URE9NU1RBUlQgYW5kIG56LlBST1RET01FTkQiKQ0KICAgIGRiIDwtIG5hLm9taXQoZGIpDQogICAgcmVzW1txXV0gPC0gZGINCiAgfSANCiAgcmVzMltbaV1dIDwtIGJpbmRfcm93cyhyZXMpDQp9DQpyZXMyIDwtIGJpbmRfcm93cyhyZXMyKQ0KcmVzMiA8LSBkaXN0aW5jdChyZXMyKQ0KZG9tX3B0bSA8LSByZXMyDQoNCmFsbG11dGF0aW9uc19QVE1zJG11dCA8LSBwYXN0ZTAoYWxsbXV0YXRpb25zX1BUTXMkZ2VuZSwiXyIsYWxsbXV0YXRpb25zX1BUTXMkQW1pbm9fQWNpZF9DaGFuZ2UsIl8iLGFsbG11dGF0aW9uc19QVE1zJHNhbXBsZSkNCmFsbG11dGF0aW9ucyRtdXQgPC0gcGFzdGUwKGFsbG11dGF0aW9ucyRnZW5lLCJfIixhbGxtdXRhdGlvbnMkQW1pbm9fQWNpZF9DaGFuZ2UsIl8iLGFsbG11dGF0aW9ucyRzYW1wbGUpDQpwb3MgPC0gc3Vic2V0KGFsbG11dGF0aW9ucywgIShhbGxtdXRhdGlvbnMkbXV0ICVpbiUgYWxsbXV0YXRpb25zX1BUTXMkbXV0KSkNCnBvcyA8LSBwb3NbLGMoOCwxLDIsMTAsMTQpXQ0KcG9zIDwtIHBvc1twb3MkZ2VuZSAlaW4lIHokR0VORU5BTUUsXQ0KcG9zIDwtIG5hLm9taXQocG9zKQ0KcG9zIDwtIHBvc1tvcmRlcihwb3MkYGNhbmNlciB0eXBlIGFiYnJldmlhdGlvbmApLF0NCnJlcyA8LSBsaXN0KCkNCnJlczIgPC0gbGlzdCgpDQpmb3IoaSBpbiB1bmlxdWUocG9zJCdjYW5jZXIgdHlwZSBhYmJyZXZpYXRpb24nKSl7DQogIHByaW50KHBhc3RlMCgiTk9OLVBUTSBzY2FubmluZyBkb21haW5zIGZvcjogIixpKSkNCiAgayA8LSB1bmlxdWUoc3Vic2V0KHBvcywgcG9zJCdjYW5jZXIgdHlwZSBhYmJyZXZpYXRpb24nID09IGkpKQ0KICBwYiA8LSB0eHRQcm9ncmVzc0JhcihtaW4gPSAwLCBtYXggPSBsZW5ndGgodW5pcXVlKGskZ2VuZSkpLCBzdHlsZSA9IDMpDQogIHN0cCA8LSAwDQogIGZvcihxIGluIHVuaXF1ZShrJGdlbmUpKXsNCiAgICBzdHAgPC0gc3RwKzENCiAgICBzZXRUeHRQcm9ncmVzc0JhcihwYiwgc3RwKQ0KICAgIGsyIDwtIHN1YnNldChrLCBrJGdlbmUgPT0gcSkNCiAgICBueiA8LSBzdWJzZXQoeiwgeiRHRU5FTkFNRSA9PSBxKQ0KICAgIGRiIDwtIHNxbGRmKCJzZWxlY3QgKiBmcm9tIGsyDQogICAgICAgICAgICAgICAgbGVmdCBqb2luIG56DQogICAgICAgICAgICAgICAgb24gazIuQW1pbm9fQWNpZF9DaGFuZ2UgYmV0d2VlbiBuei5QUk9URE9NU1RBUlQgYW5kIG56LlBST1RET01FTkQiKQ0KICAgIGRiIDwtIG5hLm9taXQoZGIpDQogICAgcmVzW1txXV0gPC0gZGINCiAgfSANCiAgcmVzMltbaV1dIDwtIGJpbmRfcm93cyhyZXMpDQp9DQpyZXMyIDwtIGJpbmRfcm93cyhyZXMyKQ0KcmVzMiA8LSBkaXN0aW5jdChyZXMyKQ0KZG9tX25vbnB0bSA8LSByZXMyDQoNCiNmb3IgYSBjb21wYXJpc29uLCBsZXQncyBmb2N1cyBvbiBQRkFNIGRvbWFpbnMgb25seSMNCmQxIDwtIHN1YnNldChkb21fcHRtLCBkb21fcHRtJFBST1RFSU5ET01BSU5TT1VSQ0UgPT0gInBmYW0iICYgZG9tX3B0bSRpcy5tYXRyaXNvbWUgPT0gIm1hdHJpc29tZSIpDQpkMiA8LSBzdWJzZXQoZG9tX3B0bSwgZG9tX3B0bSRQUk9URUlORE9NQUlOU09VUkNFID09ICJwZmFtIiAmIGRvbV9wdG0kaXMubWF0cmlzb21lICE9ICJtYXRyaXNvbWUiKQ0KZDMgPC0gc3Vic2V0KGRvbV9ub25wdG0sIGRvbV9ub25wdG0kUFJPVEVJTkRPTUFJTlNPVVJDRSA9PSAicGZhbSIpDQoNCnJlcyA8LSBsaXN0KCkNCnN0cCA8LSAwDQpwcmludCgiY2hlY2tpbmcgbXV0YXRpb24gZnJlcXVlbmNpZXMgaW4gbWF0cmlzb21lIGRvbWFpbnMiKQ0KcGIgPC0gdHh0UHJvZ3Jlc3NCYXIobWluID0gMCwgbWF4ID0gbGVuZ3RoKHVuaXF1ZShkMSRQUk9URUlORE9NQUlOSUQpKSwgc3R5bGUgPSAzKQ0KZm9yKGkgaW4gdW5pcXVlKGQxJFBST1RFSU5ET01BSU5JRCkpew0KICBzdHAgPC0gc3RwKzENCiAgc2V0VHh0UHJvZ3Jlc3NCYXIocGIsIHN0cCkNCiAgeiA8LSBzdWJzZXQoZDMsIGQzJFBST1RFSU5ET01BSU5JRCA9PSBpKQ0KICB6IDwtIGFzLmRhdGEudGFibGUoeilbLC5OLCBieSA9IGdlbmVdDQogIHogPC0gbWVhbih6JE4pDQogIHoyIDwtIHN1YnNldChkMSwgZDEkUFJPVEVJTkRPTUFJTklEID09IGkpDQogIHoyIDwtIGFzLmRhdGEudGFibGUoejIpWywuTiwgYnkgPSBnZW5lXQ0KICB6MiA8LSBtZWFuKHoyJE4pDQogIHJlc1tbaV1dIDwtIGRhdGEuZnJhbWUoZG9tYWluPWksIGZyZXE9KHoyL3opKQ0KfQ0KcmVzIDwtIGJpbmRfcm93cyhyZXMpDQoNCnJlczIgPC0gbGlzdCgpDQpzdHAgPC0gMA0KcHJpbnQoImNoZWNraW5nIG11dGF0aW9uIGZyZXF1ZW5jaWVzIGluIG5vbi1tYXRyaXNvbWUgZG9tYWlucyIpDQpwYiA8LSB0eHRQcm9ncmVzc0JhcihtaW4gPSAwLCBtYXggPSBsZW5ndGgodW5pcXVlKGQyJFBST1RFSU5ET01BSU5JRCkpLCBzdHlsZSA9IDMpDQpmb3IoaSBpbiB1bmlxdWUoZDIkUFJPVEVJTkRPTUFJTklEKSl7DQogIHN0cCA8LSBzdHArMQ0KICBzZXRUeHRQcm9ncmVzc0JhcihwYiwgc3RwKQ0KICB6IDwtIHN1YnNldChkMywgZDMkUFJPVEVJTkRPTUFJTklEID09IGkpDQogIHogPC0gYXMuZGF0YS50YWJsZSh6KVssLk4sIGJ5ID0gZ2VuZV0NCiAgeiA8LSBtZWFuKHokTikNCiAgejIgPC0gc3Vic2V0KGQyLCBkMiRQUk9URUlORE9NQUlOSUQgPT0gaSkNCiAgejIgPC0gYXMuZGF0YS50YWJsZSh6MilbLC5OLCBieSA9IGdlbmVdDQogIHoyIDwtIG1lYW4oejIkTikNCiAgcmVzMltbaV1dIDwtIGRhdGEuZnJhbWUoZG9tYWluPWksIGZyZXE9KHoyL3opKQ0KfQ0KcmVzMiA8LSBiaW5kX3Jvd3MocmVzMikNCnRvZXggPC0gbWVyZ2UocmVzLHJlczIsYnk9ImRvbWFpbiIsYWxsPVQpDQp0b2V4W2lzLm5hKHRvZXgpXSA8LSAwDQpjb2xuYW1lcyh0b2V4KSA8LSBjKCJQRkFNIGRvbWFpbiIsIlBUTW11dC9hbGxtdXQgZnJlcXVlbmN5IGluIG1hdHJpc29tZSIsICJQVE1tdXQvYWxsbXV0IGZyZXF1ZW5jeSBpbiByZXN0IG9mIGdlbm9tZSIpDQp0b2V4JGVucmljaCA8LSBpZmVsc2UodG9leCRgUFRNbXV0L2FsbG11dCBmcmVxdWVuY3kgaW4gbWF0cmlzb21lYD4gdG9leCRgUFRNbXV0L2FsbG11dCBmcmVxdWVuY3kgaW4gcmVzdCBvZiBnZW5vbWVgLCJtYXRyaXNvbWUiLCJyZXN0IG9mIGdlbm9tZSIpDQpmd3JpdGUodG9leCwicmVsYXRpdmUgZnJlcSBvZiBtdXRhdGlvbnMgYXQgdGhlIGRvbWFpbiBsZXZlbC50eHQiLHNlcD0iXHQiLHJvdy5uYW1lcyA9IEYsIHF1b3RlID0gRikNCg0KbXQgPC0gc3Vic2V0KHRvZXgsdG9leCRgUFRNbXV0L2FsbG11dCBmcmVxdWVuY3kgaW4gbWF0cmlzb21lYD4gdG9leCRgUFRNbXV0L2FsbG11dCBmcmVxdWVuY3kgaW4gcmVzdCBvZiBnZW5vbWVgKQ0KbGVuZ3RoKHVuaXF1ZShtdCRgUEZBTSBkb21haW5gKSkgIzEyOCBlbnJpY2hlZCBkb21haW5zIw0KDQptMSA8LSBzdWJzZXQoZDEsIGQxJFBST1RFSU5ET01BSU5JRCAlaW4lIG10JGBQRkFNIGRvbWFpbmApDQptMiA8LSBzdWJzZXQoZDIsIGQyJFBST1RFSU5ET01BSU5JRCAlaW4lIG10JGBQRkFNIGRvbWFpbmApDQptMSA8LSBkaXN0aW5jdChtMVssYygiZ2VuZSIsIlBUTSIsIlBST1RFSU5ET01BSU5JRCIpXSkNCm0yIDwtIGRpc3RpbmN0KG0yWyxjKCJnZW5lIiwiUFRNIiwiUFJPVEVJTkRPTUFJTklEIildKQ0KbTMgPC0gZGlzdGluY3QoZDNbLGMoImdlbmUiLCJQUk9URUlORE9NQUlOSUQiKV0pDQoNCnJlczEgPC0gbGlzdCgpDQpmb3IoaSBpbiB1bmlxdWUobTEkUFJPVEVJTkRPTUFJTklEKSl7DQogIHogPC0gc3Vic2V0KG0xLCBtMSRQUk9URUlORE9NQUlOSUQgPT0gaSkNCiAgeiA8LSBhcy5kYXRhLmZyYW1lKHRhYmxlKHokUFRNKSkNCiAgejIgPC0gc3Vic2V0KG0zLCBtMyRQUk9URUlORE9NQUlOSUQgPT0gaSkNCiAgejIgPC0gbnJvdyh6MikNCiAgeiR0b3QgPC0gejINCiAgeiRyZWwuZnJlcSA8LSB6JEZyZXEveiR0b3QNCiAgcmVzMVtbaV1dIDwtIHoNCn0NCnJlczEgPC0gYmluZF9yb3dzKHJlczEpDQpyZXMxIDwtIGFnZ3JlZ2F0ZShyZXMxJHJlbC5mcmVxLGJ5PWxpc3QocmVzMSRWYXIxKSxtZWFuKQ0KDQpyZXMyIDwtIGxpc3QoKQ0KZm9yKGkgaW4gdW5pcXVlKG0yJFBST1RFSU5ET01BSU5JRCkpew0KICB6IDwtIHN1YnNldChtMiwgbTIkUFJPVEVJTkRPTUFJTklEID09IGkpDQogIHogPC0gYXMuZGF0YS5mcmFtZSh0YWJsZSh6JFBUTSkpDQogIHoyIDwtIHN1YnNldChtMywgbTMkUFJPVEVJTkRPTUFJTklEID09IGkpDQogIHoyIDwtIG5yb3coejIpDQogIHokdG90IDwtIHoyDQogIHokcmVsLmZyZXEgPC0geiRGcmVxL3okdG90DQogIHJlczJbW2ldXSA8LSB6DQp9DQpyZXMyIDwtIGJpbmRfcm93cyhyZXMyKQ0KcmVzMiA8LSBhZ2dyZWdhdGUocmVzMiRyZWwuZnJlcSxieT1saXN0KHJlczIkVmFyMSksbWVhbikNCg0KcmVzIDwtIG1lcmdlKHJlczEscmVzMixieT0iR3JvdXAuMSIpDQpyZXNbNSwzXSA8LSAxICNwcm94eQ0KbSA8LSByZXNoYXBlMjo6bWVsdChyZXMpDQoNCmdncGxvdChkYXRhPW0sIGFlcyh4PUdyb3VwLjEsIHk9dmFsdWUsIGZpbGw9dmFyaWFibGUpKSArDQogIGdlb21fYmFyKHN0YXQ9ImlkZW50aXR5IiwgcG9zaXRpb24gPSAiZG9kZ2UiKSArDQogIHRoZW1lX2J3KCkgKw0KICBzY2FsZV9maWxsX2FhYXMoKSArIA0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCB2anVzdCA9IDAuNSwgaGp1c3Q9MSkpDQoNCmBgYA0KDQpBcmUgdGhlcmUgY29ycmVsYXRpb25zIGJldHdlZW4gUFRNbXV0IGxvY2F0aW9uIGFuZCBnZW5lIGZ1bmN0aW9uPw0KDQpgYGB7ciwgZXZhbD1GfQ0KDQp0YWIgPC0gYXMuZGF0YS5mcmFtZShmcmVhZCgicmVsYXRpdmUgZnJlcSBvZiBtdXRhdGlvbnMgYXQgdGhlIGRvbWFpbiBsZXZlbC50eHQiLHNlcD0iXHQiLGhlYWRlciA9IFQsIGRlYz0iLCIpKQ0KbGlicmFyeShjbHVzdGVyUHJvZmlsZXIpDQpsaWJyYXJ5KG9yZy5Icy5lZy5kYikNCmxpYnJhcnkoY293cGxvdCkNCg0KdGFiLm1hdCA8LSBzdWJzZXQodGFiLCB0YWJbLDJdPj0yKnRhYlssM10pDQpzZWwgPC0gc3Vic2V0KGRvbV9wdG0sIGRvbV9wdG0kUFJPVEVJTkRPTUFJTklEICVpbiUgdGFiLm1hdCRgUEZBTSBkb21haW5gKQ0KZ2VuZSA8LSB0YWJsZShzZWwkZ2VuZSkNCmdlbmUgPC0gZ2VuZVtvcmRlcigtZ2VuZSldDQoNCmVnbyA8LSBlbnJpY2hLRUdHKGdlbmUpDQpwMSA8LSBkb3RwbG90KGVnbywgc2hvd0NhdGVnb3J5PTEwLCB0aXRsZT0ibWF0cmlzb21lIGVucmljaGVkIikNCg0KdGFiLm1hdCA8LSBzdWJzZXQodGFiLCB0YWJbLDNdPj0yKnRhYlssMl0pDQpzZWwgPC0gc3Vic2V0KGRvbV9wdG0sIGRvbV9wdG0kUFJPVEVJTkRPTUFJTklEICVpbiUgdGFiLm1hdCRgUEZBTSBkb21haW5gKQ0KZ2VuZTIgPC0gdGFibGUoc2VsJGdlbmUpDQpnZW5lMiA8LSBnZW5lMltvcmRlcigtZ2VuZTIpXQ0KDQplZ28gPC0gZW5yaWNoS0VHRyhnZW5lMikNCnAyIDwtIGRvdHBsb3QoZWdvLCBzaG93Q2F0ZWdvcnk9MTAsIHRpdGxlPSJyZXN0IG9mIGdlbm9tZSBlbnJpY2hlZCIpDQoNCnBsb3RfZ3JpZChwMSwgcDIsIG5jb2w9MikNCg0KZWdvIDwtIGVucmljaEdPKGdlbmUgICAgICAgICAgPSBnZW5lLA0KICAgICAgICAgICAgICAgIE9yZ0RiICAgICAgICAgPSBvcmcuSHMuZWcuZGIsDQogICAgICAgICAgICAgICAgb250ICAgICAgICAgICA9ICJCUCIsDQogICAgICAgICAgICAgICAgcEFkanVzdE1ldGhvZCA9ICJCSCIsDQogICAgICAgICAgICAgICAgcHZhbHVlQ3V0b2ZmICA9IDAuMDEsDQogICAgICAgICAgICAgICAgcXZhbHVlQ3V0b2ZmICA9IDAuMDUsDQogICAgICAgIHJlYWRhYmxlICAgICAgPSBUUlVFKQ0KcDEgPC0gZG90cGxvdChlZ28pDQoNCmVnbyA8LSBlbnJpY2hHTyhnZW5lICAgICAgICAgID0gZ2VuZTIsDQogICAgICAgICAgICAgICAgT3JnRGIgICAgICAgICA9IG9yZy5Icy5lZy5kYiwNCiAgICAgICAgICAgICAgICBvbnQgICAgICAgICAgID0gIkJQIiwNCiAgICAgICAgICAgICAgICBwQWRqdXN0TWV0aG9kID0gIkJIIiwNCiAgICAgICAgICAgICAgICBwdmFsdWVDdXRvZmYgID0gMC4wMSwNCiAgICAgICAgICAgICAgICBxdmFsdWVDdXRvZmYgID0gMC4wNSwNCiAgICAgICAgcmVhZGFibGUgICAgICA9IFRSVUUpDQpwMiA8LSBkb3RwbG90KGVnbykNCnBsb3RfZ3JpZChwMSwgcDIsIG5jb2w9MikNCg0KbCA8LSBsaXN0KG1hdD1nZW5lLHJlc3Q9Z2VuZTIpDQpjayA8LSBjb21wYXJlQ2x1c3RlcihnZW5lQ2x1c3RlciA9IGwsIA0KICAgICAgICAgICAgICAgIGZ1biA9ICJlbnJpY2hHTyIsDQogICAgICAgICAgICAgICAgT3JnRGIgICAgICAgICA9IG9yZy5Icy5lZy5kYiwNCiAgICAgICAgICAgICAgICBvbnQgICAgICAgICAgID0gIkJQIiwNCiAgICAgICAgICAgICAgICBwQWRqdXN0TWV0aG9kID0gIkJIIiwNCiAgICAgICAgICAgICAgICBwdmFsdWVDdXRvZmYgID0gMC4wMSwNCiAgICAgICAgICAgICAgICBxdmFsdWVDdXRvZmYgID0gMC4wNSkNCmRvdHBsb3QoY2spDQoNCg0KYGBgDQoNCkFuZCBhcmUgdGhlcmUgbWF0cmlzb21lIFBUTW11dCBhc3NvY2lhdGVkIHdpdGggcHJvdGVpbiBmdW5jdGlvbnM/DQoNCmBgYHtyLCBldmFsPUZ9DQoNCnJlZyA8LSBhcy5kYXRhLmZyYW1lKGZyZWFkKCJyZWdpb25zLnR4dCIsaGVhZGVyPVQsc2VwPSJcdCIpKQ0KcmVnIDwtIHJlZ1ssYygxOjE2KV0NCnJlZyA8LSBzdWJzZXQocmVnLCByZWckaXMubWF0cmlzb21lPT0ibWF0cmlzb21lIikNCnJlZyRgcmVnaW9uIElgIDwtIGlmZWxzZShyZWckYHJlZ2lvbiBJYCA9PSAiaXNpZGUiLCJpbnNpZGUiLHJlZyRgcmVnaW9uIElgKQ0KDQojb2YgOTIxIG11dGF0aW9ucyBhbm5vdGF0ZWQgd2l0aCByZWdpb24gaW5mb3JtYXRpb24sIDI4NiAoMjg2LzkyMSwgMzElKSBmYWxsIGluIGEgZnVuY3Rpb25hbCByZWdpb24jDQoNCmZtdXQgPC0gc3Vic2V0KHJlZywgcmVnJGByZWdpb24gSWAgPT0gImluc2lkZSIpDQp0Zm11dCA8LSB0YWJsZShmbXV0JGdlbmUpDQp0Zm11dCA8LSB0Zm11dFtvcmRlcigtdGZtdXQpXQ0KDQpgYGANCg0KRmluYWxseSwgZG8gdGhlIHByb3RlaW5zIHRhcmdldGVkIGJ5IFBUTW11dCBpbnRlcmFjdD8gVG8gYW5zd2VyIHRoaXMgcXVlc3Rpb24sIHdlIHdpbGwgdXNlIFBQSSBkYXRhIGZyb20gQmlvR3JpZCB2LiA0LjIuMTkyLg0KDQpgYGB7ciwgZXZhbD1GfQ0KDQpwcGkgPC0gYXMuZGF0YS5mcmFtZShmcmVhZCgiQklPR1JJRC1PUkdBTklTTS1Ib21vX3NhcGllbnMtNC4yLjE5Mi50YWIzLnR4dCIsaGVhZGVyPVQsc2VwPSJcdCIpKQ0KcHBpIDwtIHBwaVssYyg4LDkpXQ0KDQp0YWIubWF0IDwtIHN1YnNldCh0YWIsIHRhYlssMl0+PXRhYlssM10pDQp0YWIubWF0IDwtIHN1YnNldCh0YWIsIHRhYiRlbnJpY2ggPT0gIm1hdHJpc29tZSIpDQpzZWwgPC0gc3Vic2V0KGRvbV9wdG0sIGRvbV9wdG0kUFJPVEVJTkRPTUFJTklEICVpbiUgdGFiLm1hdCRgUEZBTSBkb21haW5gKQ0Kc2VsIDwtIHVuaXF1ZShzZWwkZ2VuZSkNCmV4cCA8LSBkYXRhLmZyYW1lKGdlbmU9c2VsKQ0KZndyaXRlKGV4cCwiZ2VuZSB3aXRoIFBUTW11dCBpbiBlbnJpY2hlZCBkb21haW5zLnR4dCIsc2VwPSJcdCIscm93Lm5hbWVzID0gRiwgcXVvdGUgPSBGKQ0KcHBpLm1hdCA8LSBzdWJzZXQocHBpLCBwcGkkYE9mZmljaWFsIFN5bWJvbCBJbnRlcmFjdG9yIEFgICVpbiUgc2VsICYgcHBpJGBPZmZpY2lhbCBTeW1ib2wgSW50ZXJhY3RvciBCYCAlaW4lIHNlbCkNCnBwaS5tYXQkdHlwZSA8LSBpZmVsc2UocHBpLm1hdFssMV09PXBwaS5tYXRbLDJdLCJob21vIiwiaGV0ZXJvIikNCmZ3cml0ZShwcGkubWF0LCJQUEkgaW50ZXJhY3Rpb25zIGluIGVucmljaGVkIGRvbWFpbnMudHh0IixzZXA9Ilx0Iixyb3cubmFtZXMgPSBGLCBxdW90ZSA9IEYpDQoNCiN0b3RhbCB3b3VsZCBiZToNCiNwcGkubWF0IDwtIHN1YnNldChwcGksIHBwaSRgT2ZmaWNpYWwgU3ltYm9sIEludGVyYWN0b3IgQWAgJWluJSBzZWwgfCBwcGkkYE9mZmljaWFsIFN5bWJvbCBJbnRlcmFjdG9yIEJgICVpbiUgc2VsKQ0KDQpsaWJyYXJ5KGlncmFwaCkNCm5ldCA8LSBzaW1wbGlmeShncmFwaF9mcm9tX2RhdGFfZnJhbWUocHBpLm1hdCksIHJlbW92ZS5tdWx0aXBsZSA9IEYsIHJlbW92ZS5sb29wcyA9IFQpDQoNClYobmV0KSRzaXplIDwtIDgNClYobmV0KSRmcmFtZS5jb2xvciA8LSAid2hpdGUiDQojVihuZXQpJGNvbG9yIDwtICJvcmFuZ2UiDQpFKG5ldCkkYXJyb3cubW9kZSA8LSAwDQpkZWcgPC0gZGVncmVlKG5ldCwgbW9kZSA9ICJhbGwiKQ0KDQpnIDwtIGRlZ3JlZShuZXQpDQpnIDwtIGdbb3JkZXIoLWcpXQ0KZyA8LSBuYW1lcyhnWzE6MTBdKQ0KDQpWKG5ldCkkbGFiZWwgPC0gaWZlbHNlKFYobmV0KSRuYW1lICVpbiUgZywgVihuZXQpJG5hbWUsIiIpIA0KVihuZXQpJGxhYmVsLmNvbG9yIDwtICJibGFjayINClYobmV0KSRjb2xvciA8LSBpZmVsc2UoVihuZXQpJGxhYmVsICVpbiUgbmFtZXModGZtdXQpLCAicmVkIiwib3JhbmdlIikNCmwgPC0gbGF5b3V0X3dpdGhfZnIobmV0KQ0KcGxvdChuZXQsIGxheW91dD1sLCB2ZXJ0ZXguc2l6ZT1kZWcqMC41KSAjdGhpcyBpcyBmb3IgcmVmZXJlbmNlLCBsYWJlbHMgd2lsbCBiZSBhZGRlZCBpbiBwb3N0LXByb2R1Y3Rpb24jDQpzZWwgPC0gVihuZXQpJGxhYmVsW1YobmV0KSRsYWJlbCAlaW4lIG5hbWVzKHRmbXV0KV0NCg0KVihuZXQpJGxhYmVsIDwtICIiDQpsIDwtIGxheW91dF93aXRoX2ZyKG5ldCkNCnBsb3QobmV0LCBsYXlvdXQ9bCwgdmVydGV4LnNpemU9ZGVnKjAuNSkgDQoNCmBgYA0KDQpCYWNrdHJhY2tpbmcgdGhlIFRQTW11dCBpbnRvIHRoZWlyIHJlZ2lvbnMgYW5kIGRvbWFpbnMNCg0KYGBge3IsIGV2YWw9Rn0NCg0KeiA8LSBmcmVhZCgicmVnaW9ucy50eHQiLHNlcD0iXHQiLGhlYWRlcj1UKQ0KeiA8LSBzdWJzZXQoeiwgeiRpcy5tYXRyaXNvbWUgIT0gInJlc3Qub2YuZ2Vub21lIikNCnogPC0gc3Vic2V0KHosIHokYHJlZ2lvbiBJYCAlaW4lIGMoImluc2lkZSIsImlzaWRlIikpDQp6IDwtIHpbLGMoMToxOCldDQpzZWwgPC0gdW5pcXVlKGMocHBpLm1hdCRgT2ZmaWNpYWwgU3ltYm9sIEludGVyYWN0b3IgQWAscHBpLm1hdCRgT2ZmaWNpYWwgU3ltYm9sIEludGVyYWN0b3IgQmApKQ0KejIgPC0gc3Vic2V0KHosIHokZ2VuZSAlaW4lIHNlbCkNCnoyIDwtIGFzLmRhdGEuZnJhbWUoejIpDQoNCnJlcyA8LSBsaXN0KCkNCmZvcihpIGluIHVuaXF1ZSh6MiRnZW5lKSl7DQogIGsgPC0gc3Vic2V0KHBwaS5tYXQsIHBwaS5tYXQkYE9mZmljaWFsIFN5bWJvbCBJbnRlcmFjdG9yIEFgID09IGkgfCBwcGkubWF0JGBPZmZpY2lhbCBTeW1ib2wgSW50ZXJhY3RvciBCYCA9PSBpKQ0KICBrIDwtIHVuaXF1ZShjKGtbLDFdLGtbLDJdKSkNCiAgayA8LSBwYXN0ZShrLCBjb2xsYXBzZSA9ICIsIikNCiAgZGYgPC0gZGF0YS5mcmFtZShnZW5lPWksIGFsbC5pbnRlcmFjdGluZy5nZW5lcz1rKQ0KICByZXNbW2ldXSA8LSBkZg0KfQ0KcmVzIDwtIGJpbmRfcm93cyhyZXMpDQoNCm0gPC0gbWVyZ2UoejIscmVzLGJ5PSJnZW5lIikNCm0kVjExIDwtIE5VTEwNCm0kVjEyIDwtIE5VTEwNCm0kVjE3IDwtIE5VTEwNCm0kVjE4IDwtIE5VTEwNCg0KbGVuZ3RoKHVuaXF1ZShtJGdlbmUpKQ0KZndyaXRlKG0sICJGSU5BTCBNQVBQSU5HIFRBQi50eHQiLCBzZXA9Ilx0Iiwgcm93Lm5hbWVzID0gRiwgcXVvdGUgPSBGKQ0KDQpuZXQgPC0gc2ltcGxpZnkoZ3JhcGhfZnJvbV9kYXRhX2ZyYW1lKHBwaS5tYXQpLCByZW1vdmUubXVsdGlwbGUgPSBGLCByZW1vdmUubG9vcHMgPSBUKQ0KDQpWKG5ldCkkc2l6ZSA8LSA4DQpWKG5ldCkkZnJhbWUuY29sb3IgPC0gIndoaXRlIg0KRShuZXQpJGFycm93Lm1vZGUgPC0gMA0KVihuZXQpJGNvbG9yIDwtICJvcmFuZ2UiDQpWKG5ldCkkY29sb3IgPC0gaWZlbHNlKG5hbWVzKFYobmV0KSkgJWluJSBtJGdlbmUsICJyZWQiLCJvcmFuZ2UiKQ0KVihuZXQpJGxhYmVsIDwtICIiDQojbiA8LSBjKCJGTjEiLCJDT0wxQTEiLCJNTVAyIiwiQURBTTEwIikNCiNWKG5ldCkkbGFiZWwgPC0gaWZlbHNlKG5hbWVzKFYobmV0KSkgJWluJSBuLG4sIiIpICN0byBzZWUgbGFiZWxzIG9mIG1vc3QtaW50ZXJlc3RpbmcgaHVicw0KDQpkZWcgPC0gZGVncmVlKG5ldCwgbW9kZSA9ICJhbGwiKQ0KDQpsIDwtIGxheW91dF93aXRoX2ZyKG5ldCkNCnBsb3QobmV0LCBsYXlvdXQ9bCwgdmVydGV4LnNpemU9ZGVnKjAuNSkgDQoNCmBgYA0KDQpFWFRSQSBJREVBUyAoZnJvbSBmdXJ0aGVyIHRoaW5raW5nIG9mLCBhbmQgZGlzY3Vzc2luZywgdGhlIGZpbmRpbmdzKTogDQoNCldoYXQgaXMgdGhlICUgb2YgZGlmZmVyZW50IGFtaW5vIGFjaWRzIGltcGFjdGVkIGJ5IFBUTW11dD8NCg0KYGBge3IsIGV2YWw9Rn0NCg0KdGFiIDwtIGZyZWFkKCJUQUJMRSAxLnR4dCIsaGVhZGVyPVQsc2VwPSJcdCIpDQphbGxtdXRhdGlvbnMgPC0gZnJlYWQoImFsbG11dGF0aW9ucy50eHQiLCBoZWFkZXI9VCwgc2VwPSJcdCIpICMgaW1wb3J0IG11dGF0aW9uIGRhdGEgIw0KYWxsbXV0YXRpb25zIDwtIGFzLmRhdGEuZnJhbWUoYWxsbXV0YXRpb25zKQ0KYWxsbXV0YXRpb25zJEFtaW5vX0FjaWRfQ2hhbmdlIDwtIGdzdWIoInAuIiwiIixhbGxtdXRhdGlvbnMkQW1pbm9fQWNpZF9DaGFuZ2UpDQphbGxtdXRhdGlvbnMkQW1pbm9fQWNpZF9DaGFuZ2UgPC0gc3Vic3RyaW5nKGFsbG11dGF0aW9ucyRBbWlub19BY2lkX0NoYW5nZSwgMSwgMSkNCm0gPC0gbWVyZ2UodGFiLCBhbGxtdXRhdGlvbnMsIGJ5Lng9Yygic2FtcGxlIiwiZ2VuZSIsImNociIsInN0YXJ0IiwiZW5kIiksIGJ5Lnk9Yygic2FtcGxlIiwiZ2VuZSIsImNociIsInN0YXJ0IiwiZW5kIikpDQp0YWIgPC0gdGFibGUobSRQVE0sIG0kQW1pbm9fQWNpZF9DaGFuZ2UueSkNCnRhYiA8LSB0YWJbLC0xXQ0KdGFiIDwtIHJvdW5kKCh0YWIvcm93U3Vtcyh0YWIpKSoxMDAsMCkNCnRhYiA8LSBhcy5kYXRhLmZyYW1lLm1hdHJpeCh0YWIpDQp0YWIkUFRNIDwtIHJvd25hbWVzKHRhYikNCnRhYiA8LSB0YWJbLGMoMjIsMToyMSldDQpmd3JpdGUodGFiLCJwZXJjZW50YWdlIG9mIG11dGF0ZWQgYW1pbm9hY2lkc19QVE0udHh0Iiwgc2VwPSJcdCIscm93Lm5hbWVzID0gRiwgcXVvdGUgPSBGKQ0KDQpgYGANCg0KQXJlIHRoZXJlIHBhdGllbnRzIHdpdGggY29uY3VycmluZy9jb2V4aXN0aW5nIG11dGF0aW9ucyBpbiBwb3RlbnRpYWxseSBpbnRlcmFjdGluZyBwcm90ZWlucz8NCg0KYGBge3IsIGV2YWw9Rn0NCg0KcHBpIDwtIGFzLmRhdGEuZnJhbWUoZnJlYWQoIkJJT0dSSUQtT1JHQU5JU00tSG9tb19zYXBpZW5zLTQuMi4xOTIudGFiMy50eHQiLGhlYWRlcj1ULHNlcD0iXHQiKSkNCnBwaSA8LSBwcGlbLGMoOCw5KV0NCnRhYiA8LSBhcy5kYXRhLmZyYW1lKGZyZWFkKCJUQUJMRSAxLnR4dCIsaGVhZGVyPVQsc2VwPSJcdCIpKQ0KdGFiIDwtIHN1YnNldCh0YWIsIHRhYiRpcy5tYXRyaXNvbWUgPT0gIm1hdHJpc29tZSIpDQoNCnJlcyA8LSBsaXN0KCkNCnBiIDwtIHR4dFByb2dyZXNzQmFyKG1pbiA9IDAsIG1heCA9IGxlbmd0aCh1bmlxdWUodGFiJHNhbXBsZSkpLCBzdHlsZSA9IDMpDQpzdHAgPC0gMA0KZm9yKGkgaW4gdW5pcXVlKHRhYiRzYW1wbGUpKXsNCiAgc3RwIDwtIHN0cCsxDQogIHNldFR4dFByb2dyZXNzQmFyKHBiLCBzdHApDQogIHogPC0gc3Vic2V0KHRhYiwgdGFiJHNhbXBsZSAlaW4lIGkpDQogIGcgPC0gdW5pcXVlKHokZ2VuZSkNCiAgczEgPC0gc3Vic2V0KHBwaSwgcHBpJGBPZmZpY2lhbCBTeW1ib2wgSW50ZXJhY3RvciBBYCAlaW4lIGcgJiBwcGkkYE9mZmljaWFsIFN5bWJvbCBJbnRlcmFjdG9yIEJgICVpbiUgIGcpDQogIGlmKG5yb3coczEpPT0wKXtuZXh0fWVsc2V7DQogICAgczEgPC0gc3Vic2V0KHMxLCBzMSRgT2ZmaWNpYWwgU3ltYm9sIEludGVyYWN0b3IgQWAgIT0gczEkYE9mZmljaWFsIFN5bWJvbCBJbnRlcmFjdG9yIEJgKQ0KICAgIGlmKG5yb3coczEpPT0wKXtuZXh0fWVsc2V7DQogICAgICBzMSRwYXRpZW50IDwtIGkNCiAgICAgIHJlc1tbaV1dIDwtIHMxDQogICAgfQ0KICB9DQp9DQpyZXMgPC0gYmluZF9yb3dzKHJlcykNCnJlcyA8LSB1bmlxdWUocmVzKQ0KbGVuZ3RoKHVuaXF1ZShyZXMkcGF0aWVudCkpDQpmd3JpdGUocmVzLCAiVEFCTEUgMkEudHh0Iiwgc2VwPSJcdCIsIHJvdy5uYW1lcyA9IEYsIHF1b3RlID0gRikNCnJlczEgPC0gcmVzDQoNCnRhYiA8LSBhcy5kYXRhLmZyYW1lKGZyZWFkKCJUQUJMRSAxLnR4dCIsaGVhZGVyPVQsc2VwPSJcdCIpKQ0KdGFiIDwtIHN1YnNldCh0YWIsIHRhYiRpcy5tYXRyaXNvbWUgIT0gIm1hdHJpc29tZSIpDQpyZXMgPC0gbGlzdCgpDQpwYiA8LSB0eHRQcm9ncmVzc0JhcihtaW4gPSAwLCBtYXggPSBsZW5ndGgodW5pcXVlKHRhYiRzYW1wbGUpKSwgc3R5bGUgPSAzKQ0Kc3RwIDwtIDANCmZvcihpIGluIHVuaXF1ZSh0YWIkc2FtcGxlKSl7DQogIHN0cCA8LSBzdHArMQ0KICBzZXRUeHRQcm9ncmVzc0JhcihwYiwgc3RwKQ0KICB6IDwtIHN1YnNldCh0YWIsIHRhYiRzYW1wbGUgJWluJSBpKQ0KICBnIDwtIHVuaXF1ZSh6JGdlbmUpDQogIHMxIDwtIHN1YnNldChwcGksIHBwaSRgT2ZmaWNpYWwgU3ltYm9sIEludGVyYWN0b3IgQWAgJWluJSBnICYgcHBpJGBPZmZpY2lhbCBTeW1ib2wgSW50ZXJhY3RvciBCYCAlaW4lICBnKQ0KICBpZihucm93KHMxKT09MCl7bmV4dH1lbHNlew0KICAgIHMxIDwtIHN1YnNldChzMSwgczEkYE9mZmljaWFsIFN5bWJvbCBJbnRlcmFjdG9yIEFgICE9IHMxJGBPZmZpY2lhbCBTeW1ib2wgSW50ZXJhY3RvciBCYCkNCiAgICBpZihucm93KHMxKT09MCl7bmV4dH1lbHNlew0KICAgICAgczEkcGF0aWVudCA8LSBpDQogICAgICByZXNbW2ldXSA8LSBzMQ0KICAgIH0NCiAgfQ0KfQ0KcmVzIDwtIGJpbmRfcm93cyhyZXMpDQpyZXMgPC0gdW5pcXVlKHJlcykNCmxlbmd0aCh1bmlxdWUocmVzJHBhdGllbnQpKQ0KDQp0YWIgPC0gYXMuZGF0YS5mcmFtZShmcmVhZCgiVEFCTEUgMS50eHQiLGhlYWRlcj1ULHNlcD0iXHQiKSkNCnRhYiA8LSBzdWJzZXQodGFiLCB0YWIkaXMubWF0cmlzb21lID09ICJtYXRyaXNvbWUiKQ0KbnJvdyh0YWIpDQp0YWIgPC0gYXMuZGF0YS5mcmFtZShmcmVhZCgiVEFCTEUgMS50eHQiLGhlYWRlcj1ULHNlcD0iXHQiKSkNCnRhYiA8LSBzdWJzZXQodGFiLCB0YWIkaXMubWF0cmlzb21lICE9ICJtYXRyaXNvbWUiKQ0KbnJvdyh0YWIpDQoNCm0gPC0gbWF0cml4KGMobnJvdyhyZXMxKSxucm93KHJlcyksMTkwNyw0Mzk2MiksbmNvbD0yLG5yb3c9MikgDQpjaGlzcS50ZXN0KG0pICNwLXZhbHVlIDwgMi4yZS0xNg0KDQpgYGANCg0KQXJlIHRoZXJlIG11dGF0aW9ucyBpbiBtb3RpZnMgbWVkaWF0aW5nIGNlbGwvRUNNIGFkaGVzaW9ucyAoR1BQLCBSR0QsIExEViwgY29sbGFnZW4gbW90aWZzIC0gR0ZQR0VSLCBHTFBHRVIsIHRoZSBsYXR0ZXIgdHdvIHRha2VuIGZyb20gZG9pOiAxMC4xMDc0L2piYy5NODA2ODY1MjAwKT8gRmlyc3QsIGxldCdzIGNoZWNrIFBUTW11dC4gUmVzdWx0cyBzaG93IHRoYXQgdGhpcyBraW5kIG9mIGV2ZW50IGlzIGV4dHJlbWVseSBpbmZyZXF1ZW50LCBzbyBtdWNoIHNvIGFzIHRvIGFzayB3aGV0aGVyIG90aGVyIGtpbmQgb2YgbXV0YXRpb25zIGFyZSBzaW1pbGFybHkgZXhjbHVkZWQuDQoNCmBgYHtyLCBldmFsPUZ9DQoNCnRhYiA8LSBmcmVhZCgiVEFCTEUgMS50eHQiLGhlYWRlcj1ULHNlcD0iXHQiKQ0KYWxsbXV0YXRpb25zIDwtIGZyZWFkKCJhbGxtdXRhdGlvbnMudHh0IiwgaGVhZGVyPVQsIHNlcD0iXHQiKSAjIGltcG9ydCBtdXRhdGlvbiBkYXRhICMNCmFsbG11dGF0aW9ucyA8LSBhcy5kYXRhLmZyYW1lKGFsbG11dGF0aW9ucykNCmFsbG11dGF0aW9ucyRBbWlub19BY2lkX0NoYW5nZSA8LSBnc3ViKCJwLiIsIiIsYWxsbXV0YXRpb25zJEFtaW5vX0FjaWRfQ2hhbmdlKQ0KYWxsbXV0YXRpb25zJEFtaW5vX0FjaWRfQ2hhbmdlIDwtIHN1YnN0cmluZyhhbGxtdXRhdGlvbnMkQW1pbm9fQWNpZF9DaGFuZ2UsIDEsIDEpDQptIDwtIG1lcmdlKHRhYiwgYWxsbXV0YXRpb25zLCBieS54PWMoInNhbXBsZSIsImdlbmUiLCJjaHIiLCJzdGFydCIsImVuZCIpLCBieS55PWMoInNhbXBsZSIsImdlbmUiLCJjaHIiLCJzdGFydCIsImVuZCIpKQ0KbSA8LSBzdWJzZXQobSwgbSRpcy5tYXRyaXNvbWUgPT0gIm1hdHJpc29tZSIpDQpzbSA8LSBtDQoNCmxpYnJhcnkoRW5zRGIuSHNhcGllbnMudjg2KSAgDQpsaWJyYXJ5KGVuc2VtYmxkYikNCmxpYnJhcnkoQmlvc3RyaW5ncykNCmxpYnJhcnkoYml0NjQpDQoNCiMjIyBHUFAgZmlsdGVyICMjIw0KDQojZmlyc3QsIGZpbHRlciBhbGwgcHJvdGVpbnMgdGhhdCBoYXZlIG5vIEdQUCBhdCBhbGwjDQptIDwtIHNtDQpsIDwtIGxpc3QoKQ0KcGIgPC0gdHh0UHJvZ3Jlc3NCYXIobWluID0gMCwgbWF4ID0gbGVuZ3RoKHVuaXF1ZShtJGdlbmUpKSwgc3R5bGUgPSAzKQ0Kc3RwIDwtIDANCmZvcihpIGluIHVuaXF1ZShtJGdlbmUpKXsNCiAgc3RwIDwtIHN0cCsxDQogIHNldFR4dFByb2dyZXNzQmFyKHBiLCBzdHApDQogIHogPC0gc3Vic2V0KG0sIG0kZ2VuZSA9PSBpKQ0KICBnIDwtIHByb3RlaW5zKEVuc0RiLkhzYXBpZW5zLnY4NiwgZmlsdGVyPSBHZW5lTmFtZUZpbHRlcihpKSwgY29sdW1ucz1jKCJ1bmlwcm90X2lkIiwicHJvdGVpbl9zZXF1ZW5jZSIpLHJldHVybi50eXBlID0gImRhdGEuZnJhbWUiKQ0KICBnIDwtIHN1YnNldChnLCBnJHVuaXByb3RfaWQgJWluJSB1bmlxdWUoeiRVbmlwcm90X0lEKSkNCiAgZyRyIDwtIGdyZXBsKCJHUFAiLGckcHJvdGVpbl9zZXF1ZW5jZSwgZml4ZWQgPSBUUlVFKQ0KICBnIDwtIGFzLmRhdGEuZnJhbWUoZykNCiAgZyRwcm90ZWluX3NlcXVlbmNlIDwtIE5VTEwNCiAgbFtbaV1dIDwtIGcNCn0NCmwgPC0gYmluZF9yb3dzKGwpDQpsIDwtIHN1YnNldChsLCBsJHIgIT0gIkZBTFNFIikNCg0KI25leHQsIGJhY2tmaWx0ZXIgbSB0byBpbmNsdWRlIG9ubHkgdGhlIGdlbmVzIHdpdGggYSB2YWxpZCBlbnRyeSBpbiBsIw0KbSA8LSBzdWJzZXQobSwgbSRnZW5lICVpbiUgbCRnZW5lX25hbWUgJiBtJFVuaXByb3RfSUQgJWluJSBsJHVuaXByb3RfaWQpDQoNCiNhbmQgdGhlbiwgdGhlIGFjdHVhbCBmaWx0ZXIjDQpwYiA8LSB0eHRQcm9ncmVzc0JhcihtaW4gPSAwLCBtYXggPSBsZW5ndGgodW5pcXVlKG0kZ2VuZSkpLCBzdHlsZSA9IDMpDQpzdHAgPC0gMA0KcmVzIDwtIGxpc3QoKQ0KcmVzMiA8LSBsaXN0KCkNCmZvcihpIGluIHVuaXF1ZShtJGdlbmUpKXsNCiAgc3RwIDwtIHN0cCsxDQogIHNldFR4dFByb2dyZXNzQmFyKHBiLCBzdHApDQogIHogPC0gc3Vic2V0KG0sIG0kZ2VuZSA9PSBpKQ0KICBnIDwtIHByb3RlaW5zKEVuc0RiLkhzYXBpZW5zLnY4NiwgZmlsdGVyPSBHZW5lTmFtZUZpbHRlcihpKSwgY29sdW1ucz1jKCJ1bmlwcm90X2lkIiwicHJvdGVpbl9zZXF1ZW5jZSIpLHJldHVybi50eXBlID0gImRhdGEuZnJhbWUiKQ0KICBnIDwtIHN1YnNldChnLCBnJHVuaXByb3RfaWQgJWluJSB1bmlxdWUoeiRVbmlwcm90X0lEKSkNCiAgaWYobnJvdyhnKT09MCl7ZGYgPC0gZGF0YS5mcmFtZShnZW5lPWkscG9zaXRpb249MCxhdmFpbGFibGUucHJvdC5zZXE9MCxvZi53aGljaC5zZXEuZm91bmQ9MCxpcy5HVkQ9IkZBTFNFIikNCiAgcmVzW1tpXV0gPC0gZGYNCiAgZGYgPC0gTlVMTH1lbHNlew0KICAgIGckbi5hYSA8LSBuY2hhcihnJHByb3RlaW5fc2VxdWVuY2UpDQogICAgYWEgPC0gdW5pcXVlKHokQW1pbm9fQWNpZF9DaGFuZ2UueCkNCg0KICAgIGZvcihrIGluIGFhKXsNCiAgICAgbWluLmFhIDwtIGstMw0KICAgICBtYXguYWEgPC0gayszDQogICAgIHMgPC0gc3Vic2V0KGcsIGckbi5hYSA+PSBtaW4uYWEgJiBnJG4uYWEgPj0gbWF4LmFhKQ0KICAgICBpZihucm93KHMpPT0wKXtkZiA8LSBkYXRhLmZyYW1lKGdlbmU9aSxwb3NpdGlvbj0wLGF2YWlsYWJsZS5wcm90LnNlcT0wLG9mLndoaWNoLnNlcS5mb3VuZD0wLGlzLkdWRD0iRkFMU0UiKQ0KICByZXNbW2ldXSA8LSBkZg0KICBkZiA8LSBOVUxMfWVsc2V7DQogICAgICANCiAgICAgIGZvcih3IGluIDE6bnJvdyhzKSl7DQogICAgICAgIHYgPC0gc1t3LDJdDQogICAgICAgIHYgPC0gc3Vic2VxKHYsIHN0YXJ0PW1pbi5hYSwgZW5kPW1heC5hYSkNCiAgICAgICAgciA8LSBncmVwbCgiR1BQIix2LCBmaXhlZCA9IFRSVUUpDQogICAgICAgIHJlczJbW3N0cF1dIDwtIGRhdGEuZnJhbWUoZ2VuZT1pLHBvc2l0aW9uPWssYXZhaWxhYmxlLnByb3Quc2VxPW5yb3coZyksb2Yud2hpY2guc2VxLmZvdW5kPXcsaXMuR1ZEPWFzLmNoYXJhY3RlcihyKSkNCiAgICAgIH19DQogICAgfQ0KICB9DQp9DQpyZXMgPC0gdW5pcXVlKGJpbmRfcm93cyhyZXMyKSkNCnJlcyA8LSBzdWJzZXQocmVzLCByZXMkaXMuR1ZEID09ICJUUlVFIikNCmdwcCA8LSByZXMNCmdwcCRtb3RpZiA8LSAiR1BQIg0KDQojIyMgR1ZEIGZpbHRlciAjIyMNCg0KI2ZpcnN0LCBmaWx0ZXIgYWxsIHByb3RlaW5zIHRoYXQgaGF2ZSBubyBHVkQgYXQgYWxsIw0KbSA8LSBzbQ0KbCA8LSBsaXN0KCkNCnBiIDwtIHR4dFByb2dyZXNzQmFyKG1pbiA9IDAsIG1heCA9IGxlbmd0aCh1bmlxdWUobSRnZW5lKSksIHN0eWxlID0gMykNCnN0cCA8LSAwDQpmb3IoaSBpbiB1bmlxdWUobSRnZW5lKSl7DQogIHN0cCA8LSBzdHArMQ0KICBzZXRUeHRQcm9ncmVzc0JhcihwYiwgc3RwKQ0KICB6IDwtIHN1YnNldChtLCBtJGdlbmUgPT0gaSkNCiAgZyA8LSBwcm90ZWlucyhFbnNEYi5Ic2FwaWVucy52ODYsIGZpbHRlcj0gR2VuZU5hbWVGaWx0ZXIoaSksIGNvbHVtbnM9YygidW5pcHJvdF9pZCIsInByb3RlaW5fc2VxdWVuY2UiKSxyZXR1cm4udHlwZSA9ICJkYXRhLmZyYW1lIikNCiAgZyA8LSBzdWJzZXQoZywgZyR1bmlwcm90X2lkICVpbiUgdW5pcXVlKHokVW5pcHJvdF9JRCkpDQogIGckciA8LSBncmVwbCgiR1ZEIixnJHByb3RlaW5fc2VxdWVuY2UsIGZpeGVkID0gVFJVRSkNCiAgZyA8LSBhcy5kYXRhLmZyYW1lKGcpDQogIGckcHJvdGVpbl9zZXF1ZW5jZSA8LSBOVUxMDQogIGxbW2ldXSA8LSBnDQp9DQpsIDwtIGJpbmRfcm93cyhsKQ0KbCA8LSBzdWJzZXQobCwgbCRyICE9ICJGQUxTRSIpDQoNCiNuZXh0LCBiYWNrZmlsdGVyIG0gdG8gaW5jbHVkZSBvbmx5IHRoZSBnZW5lcyB3aXRoIGEgdmFsaWQgZW50cnkgaW4gbCMNCm0gPC0gc3Vic2V0KG0sIG0kZ2VuZSAlaW4lIGwkZ2VuZV9uYW1lICYgbSRVbmlwcm90X0lEICVpbiUgbCR1bmlwcm90X2lkKQ0KDQojYW5kIHRoZW4sIHRoZSBhY3R1YWwgZmlsdGVyIw0KcGIgPC0gdHh0UHJvZ3Jlc3NCYXIobWluID0gMCwgbWF4ID0gbGVuZ3RoKHVuaXF1ZShtJGdlbmUpKSwgc3R5bGUgPSAzKQ0Kc3RwIDwtIDANCnJlcyA8LSBsaXN0KCkNCnJlczIgPC0gbGlzdCgpDQpmb3IoaSBpbiB1bmlxdWUobSRnZW5lKSl7DQogIHN0cCA8LSBzdHArMQ0KICBzZXRUeHRQcm9ncmVzc0JhcihwYiwgc3RwKQ0KICB6IDwtIHN1YnNldChtLCBtJGdlbmUgPT0gaSkNCiAgZyA8LSBwcm90ZWlucyhFbnNEYi5Ic2FwaWVucy52ODYsIGZpbHRlcj0gR2VuZU5hbWVGaWx0ZXIoaSksIGNvbHVtbnM9YygidW5pcHJvdF9pZCIsInByb3RlaW5fc2VxdWVuY2UiKSxyZXR1cm4udHlwZSA9ICJkYXRhLmZyYW1lIikNCiAgZyA8LSBzdWJzZXQoZywgZyR1bmlwcm90X2lkICVpbiUgdW5pcXVlKHokVW5pcHJvdF9JRCkpDQogIGlmKG5yb3coZyk9PTApe2RmIDwtIGRhdGEuZnJhbWUoZ2VuZT1pLHBvc2l0aW9uPTAsYXZhaWxhYmxlLnByb3Quc2VxPTAsb2Yud2hpY2guc2VxLmZvdW5kPTAsaXMuR1ZEPSJGQUxTRSIpDQogIHJlc1tbaV1dIDwtIGRmDQogIGRmIDwtIE5VTEx9ZWxzZXsNCiAgICBnJG4uYWEgPC0gbmNoYXIoZyRwcm90ZWluX3NlcXVlbmNlKQ0KICAgIGFhIDwtIHVuaXF1ZSh6JEFtaW5vX0FjaWRfQ2hhbmdlLngpDQoNCiAgICBmb3IoayBpbiBhYSl7DQogICAgIG1pbi5hYSA8LSBrLTMNCiAgICAgbWF4LmFhIDwtIGsrMw0KICAgICBzIDwtIHN1YnNldChnLCBnJG4uYWEgPj0gbWluLmFhICYgZyRuLmFhID49IG1heC5hYSkNCiAgICAgaWYobnJvdyhzKT09MCl7ZGYgPC0gZGF0YS5mcmFtZShnZW5lPWkscG9zaXRpb249MCxhdmFpbGFibGUucHJvdC5zZXE9MCxvZi53aGljaC5zZXEuZm91bmQ9MCxpcy5HVkQ9IkZBTFNFIikNCiAgcmVzW1tpXV0gPC0gZGYNCiAgZGYgPC0gTlVMTH1lbHNlew0KICAgICAgDQogICAgICBmb3IodyBpbiAxOm5yb3cocykpew0KICAgICAgICB2IDwtIHNbdywyXQ0KICAgICAgICB2IDwtIHN1YnNlcSh2LCBzdGFydD1taW4uYWEsIGVuZD1tYXguYWEpDQogICAgICAgIHIgPC0gZ3JlcGwoIkdWRCIsdiwgZml4ZWQgPSBUUlVFKQ0KICAgICAgICByZXMyW1tzdHBdXSA8LSBkYXRhLmZyYW1lKGdlbmU9aSxwb3NpdGlvbj1rLGF2YWlsYWJsZS5wcm90LnNlcT1ucm93KGcpLG9mLndoaWNoLnNlcS5mb3VuZD13LGlzLkdWRD1hcy5jaGFyYWN0ZXIocikpDQogICAgICB9fQ0KICAgIH0NCiAgfQ0KfQ0KcmVzIDwtIHVuaXF1ZShiaW5kX3Jvd3MocmVzMikpDQpyZXMgPC0gc3Vic2V0KHJlcywgcmVzJGlzLkdWRCA9PSAiVFJVRSIpDQpndmQgPC0gcmVzDQpndmQkbW90aWYgPC0gIkdWRCINCiAgICAgDQojIyMgUkdEIGZpbHRlciAjIyMNCg0KI2ZpcnN0LCBmaWx0ZXIgYWxsIHByb3RlaW5zIHRoYXQgaGF2ZSBubyBSR0QgYXQgYWxsIw0KbSA8LSBzbQ0KbCA8LSBsaXN0KCkNCnBiIDwtIHR4dFByb2dyZXNzQmFyKG1pbiA9IDAsIG1heCA9IGxlbmd0aCh1bmlxdWUobSRnZW5lKSksIHN0eWxlID0gMykNCnN0cCA8LSAwDQpmb3IoaSBpbiB1bmlxdWUobSRnZW5lKSl7DQogIHN0cCA8LSBzdHArMQ0KICBzZXRUeHRQcm9ncmVzc0JhcihwYiwgc3RwKQ0KICB6IDwtIHN1YnNldChtLCBtJGdlbmUgPT0gaSkNCiAgZyA8LSBwcm90ZWlucyhFbnNEYi5Ic2FwaWVucy52ODYsIGZpbHRlcj0gR2VuZU5hbWVGaWx0ZXIoaSksIGNvbHVtbnM9YygidW5pcHJvdF9pZCIsInByb3RlaW5fc2VxdWVuY2UiKSxyZXR1cm4udHlwZSA9ICJkYXRhLmZyYW1lIikNCiAgZyA8LSBzdWJzZXQoZywgZyR1bmlwcm90X2lkICVpbiUgdW5pcXVlKHokVW5pcHJvdF9JRCkpDQogIGckciA8LSBncmVwbCgiUkdEIixnJHByb3RlaW5fc2VxdWVuY2UsIGZpeGVkID0gVFJVRSkNCiAgZyA8LSBhcy5kYXRhLmZyYW1lKGcpDQogIGckcHJvdGVpbl9zZXF1ZW5jZSA8LSBOVUxMDQogIGxbW2ldXSA8LSBnDQp9DQpsIDwtIGJpbmRfcm93cyhsKQ0KbCA8LSBzdWJzZXQobCwgbCRyICE9ICJGQUxTRSIpDQoNCiNuZXh0LCBiYWNrZmlsdGVyIG0gdG8gaW5jbHVkZSBvbmx5IHRoZSBnZW5lcyB3aXRoIGEgdmFsaWQgZW50cnkgaW4gbCMNCm0gPC0gc3Vic2V0KG0sIG0kZ2VuZSAlaW4lIGwkZ2VuZV9uYW1lICYgbSRVbmlwcm90X0lEICVpbiUgbCR1bmlwcm90X2lkKQ0KDQojYW5kIHRoZW4sIHRoZSBhY3R1YWwgZmlsdGVyIw0KcGIgPC0gdHh0UHJvZ3Jlc3NCYXIobWluID0gMCwgbWF4ID0gbGVuZ3RoKHVuaXF1ZShtJGdlbmUpKSwgc3R5bGUgPSAzKQ0Kc3RwIDwtIDANCnJlcyA8LSBsaXN0KCkNCnJlczIgPC0gbGlzdCgpDQpmb3IoaSBpbiB1bmlxdWUobSRnZW5lKSl7DQogIHN0cCA8LSBzdHArMQ0KICBzZXRUeHRQcm9ncmVzc0JhcihwYiwgc3RwKQ0KICB6IDwtIHN1YnNldChtLCBtJGdlbmUgPT0gaSkNCiAgZyA8LSBwcm90ZWlucyhFbnNEYi5Ic2FwaWVucy52ODYsIGZpbHRlcj0gR2VuZU5hbWVGaWx0ZXIoaSksIGNvbHVtbnM9YygidW5pcHJvdF9pZCIsInByb3RlaW5fc2VxdWVuY2UiKSxyZXR1cm4udHlwZSA9ICJkYXRhLmZyYW1lIikNCiAgZyA8LSBzdWJzZXQoZywgZyR1bmlwcm90X2lkICVpbiUgdW5pcXVlKHokVW5pcHJvdF9JRCkpDQogIGlmKG5yb3coZyk9PTApe2RmIDwtIGRhdGEuZnJhbWUoZ2VuZT1pLHBvc2l0aW9uPTAsYXZhaWxhYmxlLnByb3Quc2VxPTAsb2Yud2hpY2guc2VxLmZvdW5kPTAsaXMuR1ZEPSJGQUxTRSIpDQogIHJlc1tbaV1dIDwtIGRmDQogIGRmIDwtIE5VTEx9ZWxzZXsNCiAgICBnJG4uYWEgPC0gbmNoYXIoZyRwcm90ZWluX3NlcXVlbmNlKQ0KICAgIGFhIDwtIHVuaXF1ZSh6JEFtaW5vX0FjaWRfQ2hhbmdlLngpDQoNCiAgICBmb3IoayBpbiBhYSl7DQogICAgIG1pbi5hYSA8LSBrLTMNCiAgICAgbWF4LmFhIDwtIGsrMw0KICAgICBzIDwtIHN1YnNldChnLCBnJG4uYWEgPj0gbWluLmFhICYgZyRuLmFhID49IG1heC5hYSkNCiAgICAgaWYobnJvdyhzKT09MCl7ZGYgPC0gZGF0YS5mcmFtZShnZW5lPWkscG9zaXRpb249MCxhdmFpbGFibGUucHJvdC5zZXE9MCxvZi53aGljaC5zZXEuZm91bmQ9MCxpcy5HVkQ9IkZBTFNFIikNCiAgcmVzW1tpXV0gPC0gZGYNCiAgZGYgPC0gTlVMTH1lbHNlew0KICAgICAgDQogICAgICBmb3IodyBpbiAxOm5yb3cocykpew0KICAgICAgICB2IDwtIHNbdywyXQ0KICAgICAgICB2IDwtIHN1YnNlcSh2LCBzdGFydD1taW4uYWEsIGVuZD1tYXguYWEpDQogICAgICAgIHIgPC0gZ3JlcGwoIlJHRCIsdiwgZml4ZWQgPSBUUlVFKQ0KICAgICAgICByZXMyW1tzdHBdXSA8LSBkYXRhLmZyYW1lKGdlbmU9aSxwb3NpdGlvbj1rLGF2YWlsYWJsZS5wcm90LnNlcT1ucm93KGcpLG9mLndoaWNoLnNlcS5mb3VuZD13LGlzLkdWRD1hcy5jaGFyYWN0ZXIocikpDQogICAgICB9fQ0KICAgIH0NCiAgfQ0KfQ0KcmVzIDwtIHVuaXF1ZShiaW5kX3Jvd3MocmVzMikpDQpyZXMgPC0gc3Vic2V0KHJlcywgcmVzJGlzLkdWRCA9PSAiVFJVRSIpDQpyZ2QgPC0gcmVzDQojcmdkJG1vdGlmIDwtICJSR0QiICNubyBSR0QhDQoNCiMjIyBMRFYgZmlsdGVyICMjIw0KDQojZmlyc3QsIGZpbHRlciBhbGwgcHJvdGVpbnMgdGhhdCBoYXZlIG5vIExEViBhdCBhbGwjDQptIDwtIHNtDQpsIDwtIGxpc3QoKQ0KcGIgPC0gdHh0UHJvZ3Jlc3NCYXIobWluID0gMCwgbWF4ID0gbGVuZ3RoKHVuaXF1ZShtJGdlbmUpKSwgc3R5bGUgPSAzKQ0Kc3RwIDwtIDANCmZvcihpIGluIHVuaXF1ZShtJGdlbmUpKXsNCiAgc3RwIDwtIHN0cCsxDQogIHNldFR4dFByb2dyZXNzQmFyKHBiLCBzdHApDQogIHogPC0gc3Vic2V0KG0sIG0kZ2VuZSA9PSBpKQ0KICBnIDwtIHByb3RlaW5zKEVuc0RiLkhzYXBpZW5zLnY4NiwgZmlsdGVyPSBHZW5lTmFtZUZpbHRlcihpKSwgY29sdW1ucz1jKCJ1bmlwcm90X2lkIiwicHJvdGVpbl9zZXF1ZW5jZSIpLHJldHVybi50eXBlID0gImRhdGEuZnJhbWUiKQ0KICBnIDwtIHN1YnNldChnLCBnJHVuaXByb3RfaWQgJWluJSB1bmlxdWUoeiRVbmlwcm90X0lEKSkNCiAgZyRyIDwtIGdyZXBsKCJMRFYiLGckcHJvdGVpbl9zZXF1ZW5jZSwgZml4ZWQgPSBUUlVFKQ0KICBnIDwtIGFzLmRhdGEuZnJhbWUoZykNCiAgZyRwcm90ZWluX3NlcXVlbmNlIDwtIE5VTEwNCiAgbFtbaV1dIDwtIGcNCn0NCmwgPC0gYmluZF9yb3dzKGwpDQpsIDwtIHN1YnNldChsLCBsJHIgIT0gIkZBTFNFIikNCg0KI25leHQsIGJhY2tmaWx0ZXIgbSB0byBpbmNsdWRlIG9ubHkgdGhlIGdlbmVzIHdpdGggYSB2YWxpZCBlbnRyeSBpbiBsIw0KbSA8LSBzdWJzZXQobSwgbSRnZW5lICVpbiUgbCRnZW5lX25hbWUgJiBtJFVuaXByb3RfSUQgJWluJSBsJHVuaXByb3RfaWQpDQoNCiNhbmQgdGhlbiwgdGhlIGFjdHVhbCBmaWx0ZXIjDQpwYiA8LSB0eHRQcm9ncmVzc0JhcihtaW4gPSAwLCBtYXggPSBsZW5ndGgodW5pcXVlKG0kZ2VuZSkpLCBzdHlsZSA9IDMpDQpzdHAgPC0gMA0KcmVzIDwtIGxpc3QoKQ0KcmVzMiA8LSBsaXN0KCkNCmZvcihpIGluIHVuaXF1ZShtJGdlbmUpKXsNCiAgc3RwIDwtIHN0cCsxDQogIHNldFR4dFByb2dyZXNzQmFyKHBiLCBzdHApDQogIHogPC0gc3Vic2V0KG0sIG0kZ2VuZSA9PSBpKQ0KICBnIDwtIHByb3RlaW5zKEVuc0RiLkhzYXBpZW5zLnY4NiwgZmlsdGVyPSBHZW5lTmFtZUZpbHRlcihpKSwgY29sdW1ucz1jKCJ1bmlwcm90X2lkIiwicHJvdGVpbl9zZXF1ZW5jZSIpLHJldHVybi50eXBlID0gImRhdGEuZnJhbWUiKQ0KICBnIDwtIHN1YnNldChnLCBnJHVuaXByb3RfaWQgJWluJSB1bmlxdWUoeiRVbmlwcm90X0lEKSkNCiAgaWYobnJvdyhnKT09MCl7ZGYgPC0gZGF0YS5mcmFtZShnZW5lPWkscG9zaXRpb249MCxhdmFpbGFibGUucHJvdC5zZXE9MCxvZi53aGljaC5zZXEuZm91bmQ9MCxpcy5HVkQ9IkZBTFNFIikNCiAgcmVzW1tpXV0gPC0gZGYNCiAgZGYgPC0gTlVMTH1lbHNlew0KICAgIGckbi5hYSA8LSBuY2hhcihnJHByb3RlaW5fc2VxdWVuY2UpDQogICAgYWEgPC0gdW5pcXVlKHokQW1pbm9fQWNpZF9DaGFuZ2UueCkNCg0KICAgIGZvcihrIGluIGFhKXsNCiAgICAgbWluLmFhIDwtIGstMw0KICAgICBtYXguYWEgPC0gayszDQogICAgIHMgPC0gc3Vic2V0KGcsIGckbi5hYSA+PSBtaW4uYWEgJiBnJG4uYWEgPj0gbWF4LmFhKQ0KICAgICBpZihucm93KHMpPT0wKXtkZiA8LSBkYXRhLmZyYW1lKGdlbmU9aSxwb3NpdGlvbj0wLGF2YWlsYWJsZS5wcm90LnNlcT0wLG9mLndoaWNoLnNlcS5mb3VuZD0wLGlzLkdWRD0iRkFMU0UiKQ0KICByZXNbW2ldXSA8LSBkZg0KICBkZiA8LSBOVUxMfWVsc2V7DQogICAgICANCiAgICAgIGZvcih3IGluIDE6bnJvdyhzKSl7DQogICAgICAgIHYgPC0gc1t3LDJdDQogICAgICAgIHYgPC0gc3Vic2VxKHYsIHN0YXJ0PW1pbi5hYSwgZW5kPW1heC5hYSkNCiAgICAgICAgciA8LSBncmVwbCgiTERWIix2LCBmaXhlZCA9IFRSVUUpDQogICAgICAgIHJlczJbW3N0cF1dIDwtIGRhdGEuZnJhbWUoZ2VuZT1pLHBvc2l0aW9uPWssYXZhaWxhYmxlLnByb3Quc2VxPW5yb3coZyksb2Yud2hpY2guc2VxLmZvdW5kPXcsaXMuR1ZEPWFzLmNoYXJhY3RlcihyKSkNCiAgICAgIH19DQogICAgfQ0KICB9DQp9DQpyZXMgPC0gdW5pcXVlKGJpbmRfcm93cyhyZXMyKSkNCnJlcyA8LSBzdWJzZXQocmVzLCByZXMkaXMuR1ZEID09ICJUUlVFIikNCmxkdiA8LSByZXMNCiNsZHYkbW90aWYgPC0gIkxEViIgI25vIExEVg0KDQojIyMgR0ZQR0VSIGZpbHRlciAjIyMNCg0KI2ZpcnN0LCBmaWx0ZXIgYWxsIHByb3RlaW5zIHRoYXQgaGF2ZSBubyBMRFYgYXQgYWxsIw0KbSA8LSBzbQ0KbCA8LSBsaXN0KCkNCnBiIDwtIHR4dFByb2dyZXNzQmFyKG1pbiA9IDAsIG1heCA9IGxlbmd0aCh1bmlxdWUobSRnZW5lKSksIHN0eWxlID0gMykNCnN0cCA8LSAwDQpmb3IoaSBpbiB1bmlxdWUobSRnZW5lKSl7DQogIHN0cCA8LSBzdHArMQ0KICBzZXRUeHRQcm9ncmVzc0JhcihwYiwgc3RwKQ0KICB6IDwtIHN1YnNldChtLCBtJGdlbmUgPT0gaSkNCiAgZyA8LSBwcm90ZWlucyhFbnNEYi5Ic2FwaWVucy52ODYsIGZpbHRlcj0gR2VuZU5hbWVGaWx0ZXIoaSksIGNvbHVtbnM9YygidW5pcHJvdF9pZCIsInByb3RlaW5fc2VxdWVuY2UiKSxyZXR1cm4udHlwZSA9ICJkYXRhLmZyYW1lIikNCiAgZyA8LSBzdWJzZXQoZywgZyR1bmlwcm90X2lkICVpbiUgdW5pcXVlKHokVW5pcHJvdF9JRCkpDQogIGckciA8LSBncmVwbCgiR0ZQR0VSIixnJHByb3RlaW5fc2VxdWVuY2UsIGZpeGVkID0gVFJVRSkNCiAgZyA8LSBhcy5kYXRhLmZyYW1lKGcpDQogIGckcHJvdGVpbl9zZXF1ZW5jZSA8LSBOVUxMDQogIGxbW2ldXSA8LSBnDQp9DQpsIDwtIGJpbmRfcm93cyhsKQ0KbCA8LSBzdWJzZXQobCwgbCRyICE9ICJGQUxTRSIpDQoNCiNuZXh0LCBiYWNrZmlsdGVyIG0gdG8gaW5jbHVkZSBvbmx5IHRoZSBnZW5lcyB3aXRoIGEgdmFsaWQgZW50cnkgaW4gbCMNCm0gPC0gc3Vic2V0KG0sIG0kZ2VuZSAlaW4lIGwkZ2VuZV9uYW1lICYgbSRVbmlwcm90X0lEICVpbiUgbCR1bmlwcm90X2lkKQ0KDQojYW5kIHRoZW4sIHRoZSBhY3R1YWwgZmlsdGVyIw0KcGIgPC0gdHh0UHJvZ3Jlc3NCYXIobWluID0gMCwgbWF4ID0gbGVuZ3RoKHVuaXF1ZShtJGdlbmUpKSwgc3R5bGUgPSAzKQ0Kc3RwIDwtIDANCnJlcyA8LSBsaXN0KCkNCnJlczIgPC0gbGlzdCgpDQpmb3IoaSBpbiB1bmlxdWUobSRnZW5lKSl7DQogIHN0cCA8LSBzdHArMQ0KICBzZXRUeHRQcm9ncmVzc0JhcihwYiwgc3RwKQ0KICB6IDwtIHN1YnNldChtLCBtJGdlbmUgPT0gaSkNCiAgZyA8LSBwcm90ZWlucyhFbnNEYi5Ic2FwaWVucy52ODYsIGZpbHRlcj0gR2VuZU5hbWVGaWx0ZXIoaSksIGNvbHVtbnM9YygidW5pcHJvdF9pZCIsInByb3RlaW5fc2VxdWVuY2UiKSxyZXR1cm4udHlwZSA9ICJkYXRhLmZyYW1lIikNCiAgZyA8LSBzdWJzZXQoZywgZyR1bmlwcm90X2lkICVpbiUgdW5pcXVlKHokVW5pcHJvdF9JRCkpDQogIGlmKG5yb3coZyk9PTApe2RmIDwtIGRhdGEuZnJhbWUoZ2VuZT1pLHBvc2l0aW9uPTAsYXZhaWxhYmxlLnByb3Quc2VxPTAsb2Yud2hpY2guc2VxLmZvdW5kPTAsaXMuR1ZEPSJGQUxTRSIpDQogIHJlc1tbaV1dIDwtIGRmDQogIGRmIDwtIE5VTEx9ZWxzZXsNCiAgICBnJG4uYWEgPC0gbmNoYXIoZyRwcm90ZWluX3NlcXVlbmNlKQ0KICAgIGFhIDwtIHVuaXF1ZSh6JEFtaW5vX0FjaWRfQ2hhbmdlLngpDQoNCiAgICBmb3IoayBpbiBhYSl7DQogICAgIG1pbi5hYSA8LSBrLTMNCiAgICAgbWF4LmFhIDwtIGsrMw0KICAgICBzIDwtIHN1YnNldChnLCBnJG4uYWEgPj0gbWluLmFhICYgZyRuLmFhID49IG1heC5hYSkNCiAgICAgaWYobnJvdyhzKT09MCl7ZGYgPC0gZGF0YS5mcmFtZShnZW5lPWkscG9zaXRpb249MCxhdmFpbGFibGUucHJvdC5zZXE9MCxvZi53aGljaC5zZXEuZm91bmQ9MCxpcy5HVkQ9IkZBTFNFIikNCiAgcmVzW1tpXV0gPC0gZGYNCiAgZGYgPC0gTlVMTH1lbHNlew0KICAgICAgDQogICAgICBmb3IodyBpbiAxOm5yb3cocykpew0KICAgICAgICB2IDwtIHNbdywyXQ0KICAgICAgICB2IDwtIHN1YnNlcSh2LCBzdGFydD1taW4uYWEsIGVuZD1tYXguYWEpDQogICAgICAgIHIgPC0gZ3JlcGwoIkdGUEdFUiIsdiwgZml4ZWQgPSBUUlVFKQ0KICAgICAgICByZXMyW1tzdHBdXSA8LSBkYXRhLmZyYW1lKGdlbmU9aSxwb3NpdGlvbj1rLGF2YWlsYWJsZS5wcm90LnNlcT1ucm93KGcpLG9mLndoaWNoLnNlcS5mb3VuZD13LGlzLkdWRD1hcy5jaGFyYWN0ZXIocikpDQogICAgICB9fQ0KICAgIH0NCiAgfQ0KfQ0KcmVzIDwtIHVuaXF1ZShiaW5kX3Jvd3MocmVzMikpDQpyZXMgPC0gc3Vic2V0KHJlcywgcmVzJGlzLkdWRCA9PSAiVFJVRSIpDQpnZnBnZXIgPC0gcmVzDQojZ2ZwZ2VyJG1vdGlmIDwtICJHRlBHRVIiICNubyBHRlBHRVINCg0KIyMjIEdMUEdFUiBmaWx0ZXIgIyMjDQoNCiNmaXJzdCwgZmlsdGVyIGFsbCBwcm90ZWlucyB0aGF0IGhhdmUgbm8gTERWIGF0IGFsbCMNCm0gPC0gc20NCmwgPC0gbGlzdCgpDQpwYiA8LSB0eHRQcm9ncmVzc0JhcihtaW4gPSAwLCBtYXggPSBsZW5ndGgodW5pcXVlKG0kZ2VuZSkpLCBzdHlsZSA9IDMpDQpzdHAgPC0gMA0KZm9yKGkgaW4gdW5pcXVlKG0kZ2VuZSkpew0KICBzdHAgPC0gc3RwKzENCiAgc2V0VHh0UHJvZ3Jlc3NCYXIocGIsIHN0cCkNCiAgeiA8LSBzdWJzZXQobSwgbSRnZW5lID09IGkpDQogIGcgPC0gcHJvdGVpbnMoRW5zRGIuSHNhcGllbnMudjg2LCBmaWx0ZXI9IEdlbmVOYW1lRmlsdGVyKGkpLCBjb2x1bW5zPWMoInVuaXByb3RfaWQiLCJwcm90ZWluX3NlcXVlbmNlIikscmV0dXJuLnR5cGUgPSAiZGF0YS5mcmFtZSIpDQogIGcgPC0gc3Vic2V0KGcsIGckdW5pcHJvdF9pZCAlaW4lIHVuaXF1ZSh6JFVuaXByb3RfSUQpKQ0KICBnJHIgPC0gZ3JlcGwoIkdMUEdFUiIsZyRwcm90ZWluX3NlcXVlbmNlLCBmaXhlZCA9IFRSVUUpDQogIGcgPC0gYXMuZGF0YS5mcmFtZShnKQ0KICBnJHByb3RlaW5fc2VxdWVuY2UgPC0gTlVMTA0KICBsW1tpXV0gPC0gZw0KfQ0KbCA8LSBiaW5kX3Jvd3MobCkNCmwgPC0gc3Vic2V0KGwsIGwkciAhPSAiRkFMU0UiKQ0KDQojbmV4dCwgYmFja2ZpbHRlciBtIHRvIGluY2x1ZGUgb25seSB0aGUgZ2VuZXMgd2l0aCBhIHZhbGlkIGVudHJ5IGluIGwjDQptIDwtIHN1YnNldChtLCBtJGdlbmUgJWluJSBsJGdlbmVfbmFtZSAmIG0kVW5pcHJvdF9JRCAlaW4lIGwkdW5pcHJvdF9pZCkNCg0KI2FuZCB0aGVuLCB0aGUgYWN0dWFsIGZpbHRlciMNCnBiIDwtIHR4dFByb2dyZXNzQmFyKG1pbiA9IDAsIG1heCA9IGxlbmd0aCh1bmlxdWUobSRnZW5lKSksIHN0eWxlID0gMykNCnN0cCA8LSAwDQpyZXMgPC0gbGlzdCgpDQpyZXMyIDwtIGxpc3QoKQ0KZm9yKGkgaW4gdW5pcXVlKG0kZ2VuZSkpew0KICBzdHAgPC0gc3RwKzENCiAgc2V0VHh0UHJvZ3Jlc3NCYXIocGIsIHN0cCkNCiAgeiA8LSBzdWJzZXQobSwgbSRnZW5lID09IGkpDQogIGcgPC0gcHJvdGVpbnMoRW5zRGIuSHNhcGllbnMudjg2LCBmaWx0ZXI9IEdlbmVOYW1lRmlsdGVyKGkpLCBjb2x1bW5zPWMoInVuaXByb3RfaWQiLCJwcm90ZWluX3NlcXVlbmNlIikscmV0dXJuLnR5cGUgPSAiZGF0YS5mcmFtZSIpDQogIGcgPC0gc3Vic2V0KGcsIGckdW5pcHJvdF9pZCAlaW4lIHVuaXF1ZSh6JFVuaXByb3RfSUQpKQ0KICBpZihucm93KGcpPT0wKXtkZiA8LSBkYXRhLmZyYW1lKGdlbmU9aSxwb3NpdGlvbj0wLGF2YWlsYWJsZS5wcm90LnNlcT0wLG9mLndoaWNoLnNlcS5mb3VuZD0wLGlzLkdWRD0iRkFMU0UiKQ0KICByZXNbW2ldXSA8LSBkZg0KICBkZiA8LSBOVUxMfWVsc2V7DQogICAgZyRuLmFhIDwtIG5jaGFyKGckcHJvdGVpbl9zZXF1ZW5jZSkNCiAgICBhYSA8LSB1bmlxdWUoeiRBbWlub19BY2lkX0NoYW5nZS54KQ0KDQogICAgZm9yKGsgaW4gYWEpew0KICAgICBtaW4uYWEgPC0gay0zDQogICAgIG1heC5hYSA8LSBrKzMNCiAgICAgcyA8LSBzdWJzZXQoZywgZyRuLmFhID49IG1pbi5hYSAmIGckbi5hYSA+PSBtYXguYWEpDQogICAgIGlmKG5yb3cocyk9PTApe2RmIDwtIGRhdGEuZnJhbWUoZ2VuZT1pLHBvc2l0aW9uPTAsYXZhaWxhYmxlLnByb3Quc2VxPTAsb2Yud2hpY2guc2VxLmZvdW5kPTAsaXMuR1ZEPSJGQUxTRSIpDQogIHJlc1tbaV1dIDwtIGRmDQogIGRmIDwtIE5VTEx9ZWxzZXsNCiAgICAgIA0KICAgICAgZm9yKHcgaW4gMTpucm93KHMpKXsNCiAgICAgICAgdiA8LSBzW3csMl0NCiAgICAgICAgdiA8LSBzdWJzZXEodiwgc3RhcnQ9bWluLmFhLCBlbmQ9bWF4LmFhKQ0KICAgICAgICByIDwtIGdyZXBsKCJHTFBHRVIiLHYsIGZpeGVkID0gVFJVRSkNCiAgICAgICAgcmVzMltbc3RwXV0gPC0gZGF0YS5mcmFtZShnZW5lPWkscG9zaXRpb249ayxhdmFpbGFibGUucHJvdC5zZXE9bnJvdyhnKSxvZi53aGljaC5zZXEuZm91bmQ9dyxpcy5HVkQ9YXMuY2hhcmFjdGVyKHIpKQ0KICAgICAgfX0NCiAgICB9DQogIH0NCn0NCnJlcyA8LSB1bmlxdWUoYmluZF9yb3dzKHJlczIpKQ0KcmVzIDwtIHN1YnNldChyZXMsIHJlcyRpcy5HVkQgPT0gIlRSVUUiKQ0KZ2xwZ2VyIDwtIHJlcw0KI2dscGdlciRtb3RpZiA8LSAiR0ZQR0VSIiAjbm8gR0xQR0VSDQoNCiMjIyBCaW5kIHJlc3VsdHMgdG9nZXRoZXIgIyMjDQoNCnJlcyA8LSBiaW5kX3Jvd3MoZ3BwLCBndmQpDQpyZXMkaWQgPC0gcGFzdGUwKHJlcyRnZW5lLCJfIixyZXMkcG9zaXRpb24pDQpzbSRpZCA8LSBwYXN0ZTAoc20kZ2VuZSwiXyIsc20kQW1pbm9fQWNpZF9DaGFuZ2UueCkNCnNtIDwtIHN1YnNldChzbSwgc2VsZWN0ID0gYygiZ2VuZSIsIlBUTSIsImlkIikpDQpyZXMgPC0gbWVyZ2Uoc20scmVzLGJ5PSJpZCIpDQpyZXMkaWQgPC0gTlVMTA0KcmVzJGdlbmUueSA8LSBOVUxMDQpuYW1lcyhyZXMpWzFdIDwtICJnZW5lIg0KbmFtZXMocmVzKVs2XSA8LSAiaXMuR1ZELnBsdXMubWludXMuMyINCmZ3cml0ZShyZXMsICJNT1RJRiBNQVBQSU5HIE9GIFBUTW11dC50eHQiLHNlcD0iXHQiLHJvdy5uYW1lcyA9IEYsIHF1b3RlID0gRikNCg0KYGBgDQoNCkNoZWNrIGZvciBub24tUFRNbXV0Lg0KDQpgYGB7ciwgZXZhbD1GfQ0KdGFiIDwtIGFzLmRhdGEuZnJhbWUoZnJlYWQoImFsbCBtdXRhdGlvbnMudHh0IixoZWFkZXI9VCxzZXA9Ilx0IikpDQp0YWIyIDwtIGFzLmRhdGEuZnJhbWUoZnJlYWQoImFsbCBQVE0gbXV0YXRpb25zLnR4dCIsaGVhZGVyPVQsc2VwPSJcdCIpKQ0KdGFiJGlkIDwtIHBhc3RlMCh0YWIkc2FtcGxlLCJfIix0YWIkZ2VuZSwiXyIsdGFiJEFtaW5vX0FjaWRfQ2hhbmdlLCJfIix0YWIkYGNhbmNlciB0eXBlIGFiYnJldmlhdGlvbmApDQp0YWIyJGlkIDwtIHBhc3RlMCh0YWIyJHNhbXBsZSwiXyIsdGFiMiRnZW5lLCJfIix0YWIyJEFtaW5vX0FjaWRfQ2hhbmdlLCJfIix0YWIyJGBjYW5jZXIgdHlwZSBhYmJyZXZpYXRpb25gKQ0KdGFiIDwtIHN1YnNldCh0YWIsICEodGFiJGlkICVpbiUgdGFiMiRpZCkpDQp0YWIgPC0gc3Vic2V0KHRhYiwgdGFiJGlzLm1hdHJpc29tZSA9PSAibWF0cmlzb21lIikNCnRhYiA8LSBuYS5vbWl0KHRhYikNCm50YWIgPC0gZnJlYWQoIlRBQkxFIDEudHh0IixoZWFkZXI9VCxzZXA9Ilx0IikNCm50YWIgPC0gc3Vic2V0KG50YWIsIG50YWIkaXMubWF0cmlzb21lID09ICJtYXRyaXNvbWUiKQ0KbnRhYiA8LSBzdWJzZXQobnRhYiwgc2VsZWN0PWMoImdlbmUiLCJVbmlwcm90X0lEIikpDQptIDwtIGRpc3RpbmN0KG1lcmdlKHRhYixudGFiLGJ5PSJnZW5lIikpDQptIDwtIG0gJT4lIG11dGF0ZV9pZihiaXQ2NDo6aXMuaW50ZWdlcjY0LCBhcy5pbnRlZ2VyKQ0KDQpzbSA8LSBtDQoNCiMjIyBHUFAgZmlsdGVyICMjIw0KDQojZmlyc3QsIGZpbHRlciBhbGwgcHJvdGVpbnMgdGhhdCBoYXZlIG5vIEdQUCBhdCBhbGwjDQptIDwtIHNtDQpsIDwtIGxpc3QoKQ0KcGIgPC0gdHh0UHJvZ3Jlc3NCYXIobWluID0gMCwgbWF4ID0gbGVuZ3RoKHVuaXF1ZShtJGdlbmUpKSwgc3R5bGUgPSAzKQ0Kc3RwIDwtIDANCmZvcihpIGluIHVuaXF1ZShtJGdlbmUpKXsNCiAgc3RwIDwtIHN0cCsxDQogIHNldFR4dFByb2dyZXNzQmFyKHBiLCBzdHApDQogIHogPC0gc3Vic2V0KG0sIG0kZ2VuZSA9PSBpKQ0KICBnIDwtIHByb3RlaW5zKEVuc0RiLkhzYXBpZW5zLnY4NiwgZmlsdGVyPSBHZW5lTmFtZUZpbHRlcihpKSwgY29sdW1ucz1jKCJ1bmlwcm90X2lkIiwicHJvdGVpbl9zZXF1ZW5jZSIpLHJldHVybi50eXBlID0gImRhdGEuZnJhbWUiKQ0KICBnIDwtIHN1YnNldChnLCBnJHVuaXByb3RfaWQgJWluJSB1bmlxdWUoeiRVbmlwcm90X0lEKSkNCiAgZyRyIDwtIGdyZXBsKCJHUFAiLGckcHJvdGVpbl9zZXF1ZW5jZSwgZml4ZWQgPSBUUlVFKQ0KICBnIDwtIGFzLmRhdGEuZnJhbWUoZykNCiAgZyRwcm90ZWluX3NlcXVlbmNlIDwtIE5VTEwNCiAgbFtbaV1dIDwtIGcNCn0NCmwgPC0gYmluZF9yb3dzKGwpDQpsIDwtIHN1YnNldChsLCBsJHIgIT0gIkZBTFNFIikNCg0KI25leHQsIGJhY2tmaWx0ZXIgbSB0byBpbmNsdWRlIG9ubHkgdGhlIGdlbmVzIHdpdGggYSB2YWxpZCBlbnRyeSBpbiBsIw0KbSA8LSBzdWJzZXQobSwgbSRnZW5lICVpbiUgbCRnZW5lX25hbWUgJiBtJFVuaXByb3RfSUQgJWluJSBsJHVuaXByb3RfaWQpDQoNCiNhbmQgdGhlbiwgdGhlIGFjdHVhbCBmaWx0ZXIjDQpwYiA8LSB0eHRQcm9ncmVzc0JhcihtaW4gPSAwLCBtYXggPSBsZW5ndGgodW5pcXVlKG0kZ2VuZSkpLCBzdHlsZSA9IDMpDQpzdHAgPC0gMA0KcmVzIDwtIGxpc3QoKQ0KcmVzMiA8LSBsaXN0KCkNCmZvcihpIGluIHVuaXF1ZShtJGdlbmUpKXsNCiAgc3RwIDwtIHN0cCsxDQogIHNldFR4dFByb2dyZXNzQmFyKHBiLCBzdHApDQogIHogPC0gc3Vic2V0KG0sIG0kZ2VuZSA9PSBpKQ0KICBnIDwtIHByb3RlaW5zKEVuc0RiLkhzYXBpZW5zLnY4NiwgZmlsdGVyPSBHZW5lTmFtZUZpbHRlcihpKSwgY29sdW1ucz1jKCJ1bmlwcm90X2lkIiwicHJvdGVpbl9zZXF1ZW5jZSIpLHJldHVybi50eXBlID0gImRhdGEuZnJhbWUiKQ0KICBnIDwtIHN1YnNldChnLCBnJHVuaXByb3RfaWQgJWluJSB1bmlxdWUoeiRVbmlwcm90X0lEKSkNCiAgaWYobnJvdyhnKT09MCl7ZGYgPC0gZGF0YS5mcmFtZShnZW5lPWkscG9zaXRpb249MCxhdmFpbGFibGUucHJvdC5zZXE9MCxvZi53aGljaC5zZXEuZm91bmQ9MCxpcy5HVkQ9IkZBTFNFIikNCiAgcmVzW1tpXV0gPC0gZGYNCiAgZGYgPC0gTlVMTH1lbHNlew0KICAgIGckbi5hYSA8LSBuY2hhcihnJHByb3RlaW5fc2VxdWVuY2UpDQogICAgYWEgPC0gdW5pcXVlKHokQW1pbm9fQWNpZF9DaGFuZ2UpDQogICAgYWEgPC0gYWFbYWEgPiA0XQ0KDQogICAgZm9yKGsgaW4gYWEpew0KICAgICBtaW4uYWEgPC0gay0zDQogICAgIG1heC5hYSA8LSBrKzMNCiAgICAgcyA8LSBzdWJzZXQoZywgZyRuLmFhID49IG1pbi5hYSAmIGckbi5hYSA+PSBtYXguYWEpDQogICAgIGlmKG5yb3cocyk9PTApe2RmIDwtIGRhdGEuZnJhbWUoZ2VuZT1pLHBvc2l0aW9uPTAsYXZhaWxhYmxlLnByb3Quc2VxPTAsb2Yud2hpY2guc2VxLmZvdW5kPTAsaXMuR1ZEPSJGQUxTRSIpDQogIHJlc1tbaV1dIDwtIGRmDQogIGRmIDwtIE5VTEx9ZWxzZXsNCiAgICAgIA0KICAgICAgZm9yKHcgaW4gMTpucm93KHMpKXsNCiAgICAgICAgdiA8LSBzW3csMl0NCiAgICAgICAgdiA8LSBzdWJzZXEodiwgc3RhcnQ9bWluLmFhLCBlbmQ9bWF4LmFhKQ0KICAgICAgICByIDwtIGdyZXBsKCJHUFAiLHYsIGZpeGVkID0gVFJVRSkNCiAgICAgICAgcmVzMltbc3RwXV0gPC0gZGF0YS5mcmFtZShnZW5lPWkscG9zaXRpb249ayxhdmFpbGFibGUucHJvdC5zZXE9bnJvdyhnKSxvZi53aGljaC5zZXEuZm91bmQ9dyxpcy5HVkQ9YXMuY2hhcmFjdGVyKHIpKQ0KICAgICAgfX0NCiAgICB9DQogIH0NCn0NCnJlcyA8LSB1bmlxdWUoYmluZF9yb3dzKHJlczIpKQ0KcmVzIDwtIHN1YnNldChyZXMsIHJlcyRpcy5HVkQgPT0gIlRSVUUiKQ0KZ3BwIDwtIHJlcw0KZ3BwJG1vdGlmIDwtICJHUFAiDQoNCiMjIyBHVkQgZmlsdGVyICMjIw0KDQojZmlyc3QsIGZpbHRlciBhbGwgcHJvdGVpbnMgdGhhdCBoYXZlIG5vIEdWRCBhdCBhbGwjDQptIDwtIHNtDQpsIDwtIGxpc3QoKQ0KcGIgPC0gdHh0UHJvZ3Jlc3NCYXIobWluID0gMCwgbWF4ID0gbGVuZ3RoKHVuaXF1ZShtJGdlbmUpKSwgc3R5bGUgPSAzKQ0Kc3RwIDwtIDANCmZvcihpIGluIHVuaXF1ZShtJGdlbmUpKXsNCiAgc3RwIDwtIHN0cCsxDQogIHNldFR4dFByb2dyZXNzQmFyKHBiLCBzdHApDQogIHogPC0gc3Vic2V0KG0sIG0kZ2VuZSA9PSBpKQ0KICBnIDwtIHByb3RlaW5zKEVuc0RiLkhzYXBpZW5zLnY4NiwgZmlsdGVyPSBHZW5lTmFtZUZpbHRlcihpKSwgY29sdW1ucz1jKCJ1bmlwcm90X2lkIiwicHJvdGVpbl9zZXF1ZW5jZSIpLHJldHVybi50eXBlID0gImRhdGEuZnJhbWUiKQ0KICBnIDwtIHN1YnNldChnLCBnJHVuaXByb3RfaWQgJWluJSB1bmlxdWUoeiRVbmlwcm90X0lEKSkNCiAgZyRyIDwtIGdyZXBsKCJHVkQiLGckcHJvdGVpbl9zZXF1ZW5jZSwgZml4ZWQgPSBUUlVFKQ0KICBnIDwtIGFzLmRhdGEuZnJhbWUoZykNCiAgZyRwcm90ZWluX3NlcXVlbmNlIDwtIE5VTEwNCiAgbFtbaV1dIDwtIGcNCn0NCmwgPC0gYmluZF9yb3dzKGwpDQpsIDwtIHN1YnNldChsLCBsJHIgIT0gIkZBTFNFIikNCg0KI25leHQsIGJhY2tmaWx0ZXIgbSB0byBpbmNsdWRlIG9ubHkgdGhlIGdlbmVzIHdpdGggYSB2YWxpZCBlbnRyeSBpbiBsIw0KbSA8LSBzdWJzZXQobSwgbSRnZW5lICVpbiUgbCRnZW5lX25hbWUgJiBtJFVuaXByb3RfSUQgJWluJSBsJHVuaXByb3RfaWQpDQoNCiNhbmQgdGhlbiwgdGhlIGFjdHVhbCBmaWx0ZXIjDQpwYiA8LSB0eHRQcm9ncmVzc0JhcihtaW4gPSAwLCBtYXggPSBsZW5ndGgodW5pcXVlKG0kZ2VuZSkpLCBzdHlsZSA9IDMpDQpzdHAgPC0gMA0KcmVzIDwtIGxpc3QoKQ0KcmVzMiA8LSBsaXN0KCkNCmZvcihpIGluIHVuaXF1ZShtJGdlbmUpKXsNCiAgc3RwIDwtIHN0cCsxDQogIHNldFR4dFByb2dyZXNzQmFyKHBiLCBzdHApDQogIHogPC0gc3Vic2V0KG0sIG0kZ2VuZSA9PSBpKQ0KICBnIDwtIHByb3RlaW5zKEVuc0RiLkhzYXBpZW5zLnY4NiwgZmlsdGVyPSBHZW5lTmFtZUZpbHRlcihpKSwgY29sdW1ucz1jKCJ1bmlwcm90X2lkIiwicHJvdGVpbl9zZXF1ZW5jZSIpLHJldHVybi50eXBlID0gImRhdGEuZnJhbWUiKQ0KICBnIDwtIHN1YnNldChnLCBnJHVuaXByb3RfaWQgJWluJSB1bmlxdWUoeiRVbmlwcm90X0lEKSkNCiAgaWYobnJvdyhnKT09MCl7ZGYgPC0gZGF0YS5mcmFtZShnZW5lPWkscG9zaXRpb249MCxhdmFpbGFibGUucHJvdC5zZXE9MCxvZi53aGljaC5zZXEuZm91bmQ9MCxpcy5HVkQ9IkZBTFNFIikNCiAgcmVzW1tpXV0gPC0gZGYNCiAgZGYgPC0gTlVMTH1lbHNlew0KICAgIGckbi5hYSA8LSBuY2hhcihnJHByb3RlaW5fc2VxdWVuY2UpDQogICAgYWEgPC0gdW5pcXVlKHokQW1pbm9fQWNpZF9DaGFuZ2UpDQogICAgYWEgPC0gYWFbYWEgPiA0XQ0KDQogICAgZm9yKGsgaW4gYWEpew0KICAgICBtaW4uYWEgPC0gay0zDQogICAgIG1heC5hYSA8LSBrKzMNCiAgICAgcyA8LSBzdWJzZXQoZywgZyRuLmFhID49IG1pbi5hYSAmIGckbi5hYSA+PSBtYXguYWEpDQogICAgIGlmKG5yb3cocyk9PTApe2RmIDwtIGRhdGEuZnJhbWUoZ2VuZT1pLHBvc2l0aW9uPTAsYXZhaWxhYmxlLnByb3Quc2VxPTAsb2Yud2hpY2guc2VxLmZvdW5kPTAsaXMuR1ZEPSJGQUxTRSIpDQogIHJlc1tbaV1dIDwtIGRmDQogIGRmIDwtIE5VTEx9ZWxzZXsNCiAgICAgIA0KICAgICAgZm9yKHcgaW4gMTpucm93KHMpKXsNCiAgICAgICAgdiA8LSBzW3csMl0NCiAgICAgICAgdiA8LSBzdWJzZXEodiwgc3RhcnQ9bWluLmFhLCBlbmQ9bWF4LmFhKQ0KICAgICAgICByIDwtIGdyZXBsKCJHVkQiLHYsIGZpeGVkID0gVFJVRSkNCiAgICAgICAgcmVzMltbc3RwXV0gPC0gZGF0YS5mcmFtZShnZW5lPWkscG9zaXRpb249ayxhdmFpbGFibGUucHJvdC5zZXE9bnJvdyhnKSxvZi53aGljaC5zZXEuZm91bmQ9dyxpcy5HVkQ9YXMuY2hhcmFjdGVyKHIpKQ0KICAgICAgfX0NCiAgICB9DQogIH0NCn0NCnJlcyA8LSB1bmlxdWUoYmluZF9yb3dzKHJlczIpKQ0KcmVzIDwtIHN1YnNldChyZXMsIHJlcyRpcy5HVkQgPT0gIlRSVUUiKQ0KZ3ZkIDwtIHJlcw0KI2d2ZCRtb3RpZiA8LSAiR1ZEIiAjbm8gR1ZEIQ0KDQojIyMgUkdEIGZpbHRlciAjIyMNCg0KI2ZpcnN0LCBmaWx0ZXIgYWxsIHByb3RlaW5zIHRoYXQgaGF2ZSBubyBSR0QgYXQgYWxsIw0KbSA8LSBzbQ0KbCA8LSBsaXN0KCkNCnBiIDwtIHR4dFByb2dyZXNzQmFyKG1pbiA9IDAsIG1heCA9IGxlbmd0aCh1bmlxdWUobSRnZW5lKSksIHN0eWxlID0gMykNCnN0cCA8LSAwDQpmb3IoaSBpbiB1bmlxdWUobSRnZW5lKSl7DQogIHN0cCA8LSBzdHArMQ0KICBzZXRUeHRQcm9ncmVzc0JhcihwYiwgc3RwKQ0KICB6IDwtIHN1YnNldChtLCBtJGdlbmUgPT0gaSkNCiAgZyA8LSBwcm90ZWlucyhFbnNEYi5Ic2FwaWVucy52ODYsIGZpbHRlcj0gR2VuZU5hbWVGaWx0ZXIoaSksIGNvbHVtbnM9YygidW5pcHJvdF9pZCIsInByb3RlaW5fc2VxdWVuY2UiKSxyZXR1cm4udHlwZSA9ICJkYXRhLmZyYW1lIikNCiAgZyA8LSBzdWJzZXQoZywgZyR1bmlwcm90X2lkICVpbiUgdW5pcXVlKHokVW5pcHJvdF9JRCkpDQogIGckciA8LSBncmVwbCgiUkdEIixnJHByb3RlaW5fc2VxdWVuY2UsIGZpeGVkID0gVFJVRSkNCiAgZyA8LSBhcy5kYXRhLmZyYW1lKGcpDQogIGckcHJvdGVpbl9zZXF1ZW5jZSA8LSBOVUxMDQogIGxbW2ldXSA8LSBnDQp9DQpsIDwtIGJpbmRfcm93cyhsKQ0KbCA8LSBzdWJzZXQobCwgbCRyICE9ICJGQUxTRSIpDQoNCiNuZXh0LCBiYWNrZmlsdGVyIG0gdG8gaW5jbHVkZSBvbmx5IHRoZSBnZW5lcyB3aXRoIGEgdmFsaWQgZW50cnkgaW4gbCMNCm0gPC0gc3Vic2V0KG0sIG0kZ2VuZSAlaW4lIGwkZ2VuZV9uYW1lICYgbSRVbmlwcm90X0lEICVpbiUgbCR1bmlwcm90X2lkKQ0KDQojYW5kIHRoZW4sIHRoZSBhY3R1YWwgZmlsdGVyIw0KcGIgPC0gdHh0UHJvZ3Jlc3NCYXIobWluID0gMCwgbWF4ID0gbGVuZ3RoKHVuaXF1ZShtJGdlbmUpKSwgc3R5bGUgPSAzKQ0Kc3RwIDwtIDANCnJlcyA8LSBsaXN0KCkNCnJlczIgPC0gbGlzdCgpDQpmb3IoaSBpbiB1bmlxdWUobSRnZW5lKSl7DQogIHN0cCA8LSBzdHArMQ0KICBzZXRUeHRQcm9ncmVzc0JhcihwYiwgc3RwKQ0KICB6IDwtIHN1YnNldChtLCBtJGdlbmUgPT0gaSkNCiAgZyA8LSBwcm90ZWlucyhFbnNEYi5Ic2FwaWVucy52ODYsIGZpbHRlcj0gR2VuZU5hbWVGaWx0ZXIoaSksIGNvbHVtbnM9YygidW5pcHJvdF9pZCIsInByb3RlaW5fc2VxdWVuY2UiKSxyZXR1cm4udHlwZSA9ICJkYXRhLmZyYW1lIikNCiAgZyA8LSBzdWJzZXQoZywgZyR1bmlwcm90X2lkICVpbiUgdW5pcXVlKHokVW5pcHJvdF9JRCkpDQogIGlmKG5yb3coZyk9PTApe2RmIDwtIGRhdGEuZnJhbWUoZ2VuZT1pLHBvc2l0aW9uPTAsYXZhaWxhYmxlLnByb3Quc2VxPTAsb2Yud2hpY2guc2VxLmZvdW5kPTAsaXMuR1ZEPSJGQUxTRSIpDQogIHJlc1tbaV1dIDwtIGRmDQogIGRmIDwtIE5VTEx9ZWxzZXsNCiAgICBnJG4uYWEgPC0gbmNoYXIoZyRwcm90ZWluX3NlcXVlbmNlKQ0KICAgIGFhIDwtIHVuaXF1ZSh6JEFtaW5vX0FjaWRfQ2hhbmdlKQ0KICAgIGFhIDwtIGFhW2FhID4gNF0NCg0KICAgIGZvcihrIGluIGFhKXsNCiAgICAgbWluLmFhIDwtIGstMw0KICAgICBtYXguYWEgPC0gayszDQogICAgIHMgPC0gc3Vic2V0KGcsIGckbi5hYSA+PSBtaW4uYWEgJiBnJG4uYWEgPj0gbWF4LmFhKQ0KICAgICBpZihucm93KHMpPT0wKXtkZiA8LSBkYXRhLmZyYW1lKGdlbmU9aSxwb3NpdGlvbj0wLGF2YWlsYWJsZS5wcm90LnNlcT0wLG9mLndoaWNoLnNlcS5mb3VuZD0wLGlzLkdWRD0iRkFMU0UiKQ0KICByZXNbW2ldXSA8LSBkZg0KICBkZiA8LSBOVUxMfWVsc2V7DQogICAgICANCiAgICAgIGZvcih3IGluIDE6bnJvdyhzKSl7DQogICAgICAgIHYgPC0gc1t3LDJdDQogICAgICAgIHYgPC0gc3Vic2VxKHYsIHN0YXJ0PW1pbi5hYSwgZW5kPW1heC5hYSkNCiAgICAgICAgciA8LSBncmVwbCgiUkdEIix2LCBmaXhlZCA9IFRSVUUpDQogICAgICAgIHJlczJbW3N0cF1dIDwtIGRhdGEuZnJhbWUoZ2VuZT1pLHBvc2l0aW9uPWssYXZhaWxhYmxlLnByb3Quc2VxPW5yb3coZyksb2Yud2hpY2guc2VxLmZvdW5kPXcsaXMuR1ZEPWFzLmNoYXJhY3RlcihyKSkNCiAgICAgIH19DQogICAgfQ0KICB9DQp9DQpyZXMgPC0gdW5pcXVlKGJpbmRfcm93cyhyZXMyKSkNCnJlcyA8LSBzdWJzZXQocmVzLCByZXMkaXMuR1ZEID09ICJUUlVFIikNCnJnZCA8LSByZXMNCiNyZ2QkbW90aWYgPC0gIlJHRCIgI25vIFJHRA0KDQojIyMgTERWIGZpbHRlciAjIyMNCg0KI2ZpcnN0LCBmaWx0ZXIgYWxsIHByb3RlaW5zIHRoYXQgaGF2ZSBubyBMRFYgYXQgYWxsIw0KbSA8LSBzbQ0KbCA8LSBsaXN0KCkNCnBiIDwtIHR4dFByb2dyZXNzQmFyKG1pbiA9IDAsIG1heCA9IGxlbmd0aCh1bmlxdWUobSRnZW5lKSksIHN0eWxlID0gMykNCnN0cCA8LSAwDQpmb3IoaSBpbiB1bmlxdWUobSRnZW5lKSl7DQogIHN0cCA8LSBzdHArMQ0KICBzZXRUeHRQcm9ncmVzc0JhcihwYiwgc3RwKQ0KICB6IDwtIHN1YnNldChtLCBtJGdlbmUgPT0gaSkNCiAgZyA8LSBwcm90ZWlucyhFbnNEYi5Ic2FwaWVucy52ODYsIGZpbHRlcj0gR2VuZU5hbWVGaWx0ZXIoaSksIGNvbHVtbnM9YygidW5pcHJvdF9pZCIsInByb3RlaW5fc2VxdWVuY2UiKSxyZXR1cm4udHlwZSA9ICJkYXRhLmZyYW1lIikNCiAgZyA8LSBzdWJzZXQoZywgZyR1bmlwcm90X2lkICVpbiUgdW5pcXVlKHokVW5pcHJvdF9JRCkpDQogIGckciA8LSBncmVwbCgiTERWIixnJHByb3RlaW5fc2VxdWVuY2UsIGZpeGVkID0gVFJVRSkNCiAgZyA8LSBhcy5kYXRhLmZyYW1lKGcpDQogIGckcHJvdGVpbl9zZXF1ZW5jZSA8LSBOVUxMDQogIGxbW2ldXSA8LSBnDQp9DQpsIDwtIGJpbmRfcm93cyhsKQ0KbCA8LSBzdWJzZXQobCwgbCRyICE9ICJGQUxTRSIpDQoNCiNuZXh0LCBiYWNrZmlsdGVyIG0gdG8gaW5jbHVkZSBvbmx5IHRoZSBnZW5lcyB3aXRoIGEgdmFsaWQgZW50cnkgaW4gbCMNCm0gPC0gc3Vic2V0KG0sIG0kZ2VuZSAlaW4lIGwkZ2VuZV9uYW1lICYgbSRVbmlwcm90X0lEICVpbiUgbCR1bmlwcm90X2lkKQ0KDQojYW5kIHRoZW4sIHRoZSBhY3R1YWwgZmlsdGVyIw0KcGIgPC0gdHh0UHJvZ3Jlc3NCYXIobWluID0gMCwgbWF4ID0gbGVuZ3RoKHVuaXF1ZShtJGdlbmUpKSwgc3R5bGUgPSAzKQ0Kc3RwIDwtIDANCnJlcyA8LSBsaXN0KCkNCnJlczIgPC0gbGlzdCgpDQpmb3IoaSBpbiB1bmlxdWUobSRnZW5lKSl7DQogIHN0cCA8LSBzdHArMQ0KICBzZXRUeHRQcm9ncmVzc0JhcihwYiwgc3RwKQ0KICB6IDwtIHN1YnNldChtLCBtJGdlbmUgPT0gaSkNCiAgZyA8LSBwcm90ZWlucyhFbnNEYi5Ic2FwaWVucy52ODYsIGZpbHRlcj0gR2VuZU5hbWVGaWx0ZXIoaSksIGNvbHVtbnM9YygidW5pcHJvdF9pZCIsInByb3RlaW5fc2VxdWVuY2UiKSxyZXR1cm4udHlwZSA9ICJkYXRhLmZyYW1lIikNCiAgZyA8LSBzdWJzZXQoZywgZyR1bmlwcm90X2lkICVpbiUgdW5pcXVlKHokVW5pcHJvdF9JRCkpDQogIGlmKG5yb3coZyk9PTApe2RmIDwtIGRhdGEuZnJhbWUoZ2VuZT1pLHBvc2l0aW9uPTAsYXZhaWxhYmxlLnByb3Quc2VxPTAsb2Yud2hpY2guc2VxLmZvdW5kPTAsaXMuR1ZEPSJGQUxTRSIpDQogIHJlc1tbaV1dIDwtIGRmDQogIGRmIDwtIE5VTEx9ZWxzZXsNCiAgICBnJG4uYWEgPC0gbmNoYXIoZyRwcm90ZWluX3NlcXVlbmNlKQ0KICAgIGFhIDwtIHVuaXF1ZSh6JEFtaW5vX0FjaWRfQ2hhbmdlKQ0KICAgIGFhIDwtIGFhW2FhID4gNF0NCg0KICAgIGZvcihrIGluIGFhKXsNCiAgICAgbWluLmFhIDwtIGstMw0KICAgICBtYXguYWEgPC0gayszDQogICAgIHMgPC0gc3Vic2V0KGcsIGckbi5hYSA+PSBtaW4uYWEgJiBnJG4uYWEgPj0gbWF4LmFhKQ0KICAgICBpZihucm93KHMpPT0wKXtkZiA8LSBkYXRhLmZyYW1lKGdlbmU9aSxwb3NpdGlvbj0wLGF2YWlsYWJsZS5wcm90LnNlcT0wLG9mLndoaWNoLnNlcS5mb3VuZD0wLGlzLkdWRD0iRkFMU0UiKQ0KICByZXNbW2ldXSA8LSBkZg0KICBkZiA8LSBOVUxMfWVsc2V7DQogICAgICANCiAgICAgIGZvcih3IGluIDE6bnJvdyhzKSl7DQogICAgICAgIHYgPC0gc1t3LDJdDQogICAgICAgIHYgPC0gc3Vic2VxKHYsIHN0YXJ0PW1pbi5hYSwgZW5kPW1heC5hYSkNCiAgICAgICAgciA8LSBncmVwbCgiTERWIix2LCBmaXhlZCA9IFRSVUUpDQogICAgICAgIHJlczJbW3N0cF1dIDwtIGRhdGEuZnJhbWUoZ2VuZT1pLHBvc2l0aW9uPWssYXZhaWxhYmxlLnByb3Quc2VxPW5yb3coZyksb2Yud2hpY2guc2VxLmZvdW5kPXcsaXMuR1ZEPWFzLmNoYXJhY3RlcihyKSkNCiAgICAgIH19DQogICAgfQ0KICB9DQp9DQpyZXMgPC0gdW5pcXVlKGJpbmRfcm93cyhyZXMyKSkNCnJlcyA8LSBzdWJzZXQocmVzLCByZXMkaXMuR1ZEID09ICJUUlVFIikNCmxkdiA8LSByZXMNCmxkdiRtb3RpZiA8LSAiTERWIg0KDQojIyMgR0ZQR0VSIGZpbHRlciAjIyMNCg0KI2ZpcnN0LCBmaWx0ZXIgYWxsIHByb3RlaW5zIHRoYXQgaGF2ZSBubyBMRFYgYXQgYWxsIw0KbSA8LSBzbQ0KbCA8LSBsaXN0KCkNCnBiIDwtIHR4dFByb2dyZXNzQmFyKG1pbiA9IDAsIG1heCA9IGxlbmd0aCh1bmlxdWUobSRnZW5lKSksIHN0eWxlID0gMykNCnN0cCA8LSAwDQpmb3IoaSBpbiB1bmlxdWUobSRnZW5lKSl7DQogIHN0cCA8LSBzdHArMQ0KICBzZXRUeHRQcm9ncmVzc0JhcihwYiwgc3RwKQ0KICB6IDwtIHN1YnNldChtLCBtJGdlbmUgPT0gaSkNCiAgZyA8LSBwcm90ZWlucyhFbnNEYi5Ic2FwaWVucy52ODYsIGZpbHRlcj0gR2VuZU5hbWVGaWx0ZXIoaSksIGNvbHVtbnM9YygidW5pcHJvdF9pZCIsInByb3RlaW5fc2VxdWVuY2UiKSxyZXR1cm4udHlwZSA9ICJkYXRhLmZyYW1lIikNCiAgZyA8LSBzdWJzZXQoZywgZyR1bmlwcm90X2lkICVpbiUgdW5pcXVlKHokVW5pcHJvdF9JRCkpDQogIGckciA8LSBncmVwbCgiR0ZQR0VSIixnJHByb3RlaW5fc2VxdWVuY2UsIGZpeGVkID0gVFJVRSkNCiAgZyA8LSBhcy5kYXRhLmZyYW1lKGcpDQogIGckcHJvdGVpbl9zZXF1ZW5jZSA8LSBOVUxMDQogIGxbW2ldXSA8LSBnDQp9DQpsIDwtIGJpbmRfcm93cyhsKQ0KbCA8LSBzdWJzZXQobCwgbCRyICE9ICJGQUxTRSIpDQoNCiNuZXh0LCBiYWNrZmlsdGVyIG0gdG8gaW5jbHVkZSBvbmx5IHRoZSBnZW5lcyB3aXRoIGEgdmFsaWQgZW50cnkgaW4gbCMNCm0gPC0gc3Vic2V0KG0sIG0kZ2VuZSAlaW4lIGwkZ2VuZV9uYW1lICYgbSRVbmlwcm90X0lEICVpbiUgbCR1bmlwcm90X2lkKQ0KDQojYW5kIHRoZW4sIHRoZSBhY3R1YWwgZmlsdGVyIw0KcGIgPC0gdHh0UHJvZ3Jlc3NCYXIobWluID0gMCwgbWF4ID0gbGVuZ3RoKHVuaXF1ZShtJGdlbmUpKSwgc3R5bGUgPSAzKQ0Kc3RwIDwtIDANCnJlcyA8LSBsaXN0KCkNCnJlczIgPC0gbGlzdCgpDQpmb3IoaSBpbiB1bmlxdWUobSRnZW5lKSl7DQogIHN0cCA8LSBzdHArMQ0KICBzZXRUeHRQcm9ncmVzc0JhcihwYiwgc3RwKQ0KICB6IDwtIHN1YnNldChtLCBtJGdlbmUgPT0gaSkNCiAgZyA8LSBwcm90ZWlucyhFbnNEYi5Ic2FwaWVucy52ODYsIGZpbHRlcj0gR2VuZU5hbWVGaWx0ZXIoaSksIGNvbHVtbnM9YygidW5pcHJvdF9pZCIsInByb3RlaW5fc2VxdWVuY2UiKSxyZXR1cm4udHlwZSA9ICJkYXRhLmZyYW1lIikNCiAgZyA8LSBzdWJzZXQoZywgZyR1bmlwcm90X2lkICVpbiUgdW5pcXVlKHokVW5pcHJvdF9JRCkpDQogIGlmKG5yb3coZyk9PTApe2RmIDwtIGRhdGEuZnJhbWUoZ2VuZT1pLHBvc2l0aW9uPTAsYXZhaWxhYmxlLnByb3Quc2VxPTAsb2Yud2hpY2guc2VxLmZvdW5kPTAsaXMuR1ZEPSJGQUxTRSIpDQogIHJlc1tbaV1dIDwtIGRmDQogIGRmIDwtIE5VTEx9ZWxzZXsNCiAgICBnJG4uYWEgPC0gbmNoYXIoZyRwcm90ZWluX3NlcXVlbmNlKQ0KICAgIGFhIDwtIHVuaXF1ZSh6JEFtaW5vX0FjaWRfQ2hhbmdlKQ0KICAgIGFhIDwtIGFhW2FhID4gNF0NCg0KICAgIGZvcihrIGluIGFhKXsNCiAgICAgbWluLmFhIDwtIGstMw0KICAgICBtYXguYWEgPC0gayszDQogICAgIHMgPC0gc3Vic2V0KGcsIGckbi5hYSA+PSBtaW4uYWEgJiBnJG4uYWEgPj0gbWF4LmFhKQ0KICAgICBpZihucm93KHMpPT0wKXtkZiA8LSBkYXRhLmZyYW1lKGdlbmU9aSxwb3NpdGlvbj0wLGF2YWlsYWJsZS5wcm90LnNlcT0wLG9mLndoaWNoLnNlcS5mb3VuZD0wLGlzLkdWRD0iRkFMU0UiKQ0KICByZXNbW2ldXSA8LSBkZg0KICBkZiA8LSBOVUxMfWVsc2V7DQogICAgICANCiAgICAgIGZvcih3IGluIDE6bnJvdyhzKSl7DQogICAgICAgIHYgPC0gc1t3LDJdDQogICAgICAgIHYgPC0gc3Vic2VxKHYsIHN0YXJ0PW1pbi5hYSwgZW5kPW1heC5hYSkNCiAgICAgICAgciA8LSBncmVwbCgiR0ZQR0VSIix2LCBmaXhlZCA9IFRSVUUpDQogICAgICAgIHJlczJbW3N0cF1dIDwtIGRhdGEuZnJhbWUoZ2VuZT1pLHBvc2l0aW9uPWssYXZhaWxhYmxlLnByb3Quc2VxPW5yb3coZyksb2Yud2hpY2guc2VxLmZvdW5kPXcsaXMuR1ZEPWFzLmNoYXJhY3RlcihyKSkNCiAgICAgIH19DQogICAgfQ0KICB9DQp9DQpyZXMgPC0gdW5pcXVlKGJpbmRfcm93cyhyZXMyKSkNCnJlcyA8LSBzdWJzZXQocmVzLCByZXMkaXMuR1ZEID09ICJUUlVFIikNCmdmcGdlciA8LSByZXMNCiNnZnBnZXIkbW90aWYgPC0gIkdGUEdFUiIgI25vIEdGUEdFUiENCg0KIyMjIEdMUEdFUiBmaWx0ZXIgIyMjDQoNCiNmaXJzdCwgZmlsdGVyIGFsbCBwcm90ZWlucyB0aGF0IGhhdmUgbm8gTERWIGF0IGFsbCMNCm0gPC0gc20NCmwgPC0gbGlzdCgpDQpwYiA8LSB0eHRQcm9ncmVzc0JhcihtaW4gPSAwLCBtYXggPSBsZW5ndGgodW5pcXVlKG0kZ2VuZSkpLCBzdHlsZSA9IDMpDQpzdHAgPC0gMA0KZm9yKGkgaW4gdW5pcXVlKG0kZ2VuZSkpew0KICBzdHAgPC0gc3RwKzENCiAgc2V0VHh0UHJvZ3Jlc3NCYXIocGIsIHN0cCkNCiAgeiA8LSBzdWJzZXQobSwgbSRnZW5lID09IGkpDQogIGcgPC0gcHJvdGVpbnMoRW5zRGIuSHNhcGllbnMudjg2LCBmaWx0ZXI9IEdlbmVOYW1lRmlsdGVyKGkpLCBjb2x1bW5zPWMoInVuaXByb3RfaWQiLCJwcm90ZWluX3NlcXVlbmNlIikscmV0dXJuLnR5cGUgPSAiZGF0YS5mcmFtZSIpDQogIGcgPC0gc3Vic2V0KGcsIGckdW5pcHJvdF9pZCAlaW4lIHVuaXF1ZSh6JFVuaXByb3RfSUQpKQ0KICBnJHIgPC0gZ3JlcGwoIkdMUEdFUiIsZyRwcm90ZWluX3NlcXVlbmNlLCBmaXhlZCA9IFRSVUUpDQogIGcgPC0gYXMuZGF0YS5mcmFtZShnKQ0KICBnJHByb3RlaW5fc2VxdWVuY2UgPC0gTlVMTA0KICBsW1tpXV0gPC0gZw0KfQ0KbCA8LSBiaW5kX3Jvd3MobCkNCmwgPC0gc3Vic2V0KGwsIGwkciAhPSAiRkFMU0UiKQ0KDQojbmV4dCwgYmFja2ZpbHRlciBtIHRvIGluY2x1ZGUgb25seSB0aGUgZ2VuZXMgd2l0aCBhIHZhbGlkIGVudHJ5IGluIGwjDQptIDwtIHN1YnNldChtLCBtJGdlbmUgJWluJSBsJGdlbmVfbmFtZSAmIG0kVW5pcHJvdF9JRCAlaW4lIGwkdW5pcHJvdF9pZCkNCg0KI2FuZCB0aGVuLCB0aGUgYWN0dWFsIGZpbHRlciMNCnBiIDwtIHR4dFByb2dyZXNzQmFyKG1pbiA9IDAsIG1heCA9IGxlbmd0aCh1bmlxdWUobSRnZW5lKSksIHN0eWxlID0gMykNCnN0cCA8LSAwDQpyZXMgPC0gbGlzdCgpDQpyZXMyIDwtIGxpc3QoKQ0KZm9yKGkgaW4gdW5pcXVlKG0kZ2VuZSkpew0KICBzdHAgPC0gc3RwKzENCiAgc2V0VHh0UHJvZ3Jlc3NCYXIocGIsIHN0cCkNCiAgeiA8LSBzdWJzZXQobSwgbSRnZW5lID09IGkpDQogIGcgPC0gcHJvdGVpbnMoRW5zRGIuSHNhcGllbnMudjg2LCBmaWx0ZXI9IEdlbmVOYW1lRmlsdGVyKGkpLCBjb2x1bW5zPWMoInVuaXByb3RfaWQiLCJwcm90ZWluX3NlcXVlbmNlIikscmV0dXJuLnR5cGUgPSAiZGF0YS5mcmFtZSIpDQogIGcgPC0gc3Vic2V0KGcsIGckdW5pcHJvdF9pZCAlaW4lIHVuaXF1ZSh6JFVuaXByb3RfSUQpKQ0KICBpZihucm93KGcpPT0wKXtkZiA8LSBkYXRhLmZyYW1lKGdlbmU9aSxwb3NpdGlvbj0wLGF2YWlsYWJsZS5wcm90LnNlcT0wLG9mLndoaWNoLnNlcS5mb3VuZD0wLGlzLkdWRD0iRkFMU0UiKQ0KICByZXNbW2ldXSA8LSBkZg0KICBkZiA8LSBOVUxMfWVsc2V7DQogICAgZyRuLmFhIDwtIG5jaGFyKGckcHJvdGVpbl9zZXF1ZW5jZSkNCiAgICBhYSA8LSB1bmlxdWUoeiRBbWlub19BY2lkX0NoYW5nZSkNCiAgICBhYSA8LSBhYVthYSA+IDRdDQoNCiAgICBmb3IoayBpbiBhYSl7DQogICAgIG1pbi5hYSA8LSBrLTMNCiAgICAgbWF4LmFhIDwtIGsrMw0KICAgICBzIDwtIHN1YnNldChnLCBnJG4uYWEgPj0gbWluLmFhICYgZyRuLmFhID49IG1heC5hYSkNCiAgICAgaWYobnJvdyhzKT09MCl7ZGYgPC0gZGF0YS5mcmFtZShnZW5lPWkscG9zaXRpb249MCxhdmFpbGFibGUucHJvdC5zZXE9MCxvZi53aGljaC5zZXEuZm91bmQ9MCxpcy5HVkQ9IkZBTFNFIikNCiAgcmVzW1tpXV0gPC0gZGYNCiAgZGYgPC0gTlVMTH1lbHNlew0KICAgICAgDQogICAgICBmb3IodyBpbiAxOm5yb3cocykpew0KICAgICAgICB2IDwtIHNbdywyXQ0KICAgICAgICB2IDwtIHN1YnNlcSh2LCBzdGFydD1taW4uYWEsIGVuZD1tYXguYWEpDQogICAgICAgIHIgPC0gZ3JlcGwoIkdMUEdFUiIsdiwgZml4ZWQgPSBUUlVFKQ0KICAgICAgICByZXMyW1tzdHBdXSA8LSBkYXRhLmZyYW1lKGdlbmU9aSxwb3NpdGlvbj1rLGF2YWlsYWJsZS5wcm90LnNlcT1ucm93KGcpLG9mLndoaWNoLnNlcS5mb3VuZD13LGlzLkdWRD1hcy5jaGFyYWN0ZXIocikpDQogICAgICB9fQ0KICAgIH0NCiAgfQ0KfQ0KcmVzIDwtIHVuaXF1ZShiaW5kX3Jvd3MocmVzMikpDQpyZXMgPC0gc3Vic2V0KHJlcywgcmVzJGlzLkdWRCA9PSAiVFJVRSIpDQpnbHBnZXIgPC0gcmVzDQojZ2xwZ2VyJG1vdGlmIDwtICJHTFBHRVIiICNubyBHTFBHRVIhDQoNCg0KDQp0YWIgPC0gZnJlYWQoIlRBQkxFIDEudHh0IixoZWFkZXI9VCxzZXA9Ilx0IikNCmFsbG11dGF0aW9ucyA8LSBmcmVhZCgiYWxsbXV0YXRpb25zLnR4dCIsIGhlYWRlcj1ULCBzZXA9Ilx0IikgIyBpbXBvcnQgbXV0YXRpb24gZGF0YSAjDQphbGxtdXRhdGlvbnMgPC0gYXMuZGF0YS5mcmFtZShhbGxtdXRhdGlvbnMpDQphbGxtdXRhdGlvbnMkQW1pbm9fQWNpZF9DaGFuZ2UgPC0gZ3N1YigicC4iLCIiLGFsbG11dGF0aW9ucyRBbWlub19BY2lkX0NoYW5nZSkNCmFsbG11dGF0aW9ucyRBbWlub19BY2lkX0NoYW5nZSA8LSBzdWJzdHJpbmcoYWxsbXV0YXRpb25zJEFtaW5vX0FjaWRfQ2hhbmdlLCAxLCAxKQ0KbSA8LSBtZXJnZSh0YWIsIGFsbG11dGF0aW9ucywgYnkueD1jKCJzYW1wbGUiLCJnZW5lIiwiY2hyIiwic3RhcnQiLCJlbmQiKSwgYnkueT1jKCJzYW1wbGUiLCJnZW5lIiwiY2hyIiwic3RhcnQiLCJlbmQiKSkNCm0gPC0gc3Vic2V0KG0sIG0kaXMubWF0cmlzb21lID09ICJtYXRyaXNvbWUiKQ0Kc20gPC0gbQ0KDQpsaWJyYXJ5KEVuc0RiLkhzYXBpZW5zLnY4NikgIA0KbGlicmFyeShlbnNlbWJsZGIpDQpsaWJyYXJ5KEJpb3N0cmluZ3MpDQpsaWJyYXJ5KGJpdDY0KQ0KDQojIyMgR1BQIGZpbHRlciAjIyMNCg0KI2ZpcnN0LCBmaWx0ZXIgYWxsIHByb3RlaW5zIHRoYXQgaGF2ZSBubyBHUFAgYXQgYWxsIw0KbSA8LSBzbQ0KbCA8LSBsaXN0KCkNCnBiIDwtIHR4dFByb2dyZXNzQmFyKG1pbiA9IDAsIG1heCA9IGxlbmd0aCh1bmlxdWUobSRnZW5lKSksIHN0eWxlID0gMykNCnN0cCA8LSAwDQpmb3IoaSBpbiB1bmlxdWUobSRnZW5lKSl7DQogIHN0cCA8LSBzdHArMQ0KICBzZXRUeHRQcm9ncmVzc0JhcihwYiwgc3RwKQ0KICB6IDwtIHN1YnNldChtLCBtJGdlbmUgPT0gaSkNCiAgZyA8LSBwcm90ZWlucyhFbnNEYi5Ic2FwaWVucy52ODYsIGZpbHRlcj0gR2VuZU5hbWVGaWx0ZXIoaSksIGNvbHVtbnM9YygidW5pcHJvdF9pZCIsInByb3RlaW5fc2VxdWVuY2UiKSxyZXR1cm4udHlwZSA9ICJkYXRhLmZyYW1lIikNCiAgZyA8LSBzdWJzZXQoZywgZyR1bmlwcm90X2lkICVpbiUgdW5pcXVlKHokVW5pcHJvdF9JRCkpDQogIGckciA8LSBncmVwbCgiR1BQIixnJHByb3RlaW5fc2VxdWVuY2UsIGZpeGVkID0gVFJVRSkNCiAgZyA8LSBhcy5kYXRhLmZyYW1lKGcpDQogIGckcHJvdGVpbl9zZXF1ZW5jZSA8LSBOVUxMDQogIGxbW2ldXSA8LSBnDQp9DQpsIDwtIGJpbmRfcm93cyhsKQ0KbCA8LSBzdWJzZXQobCwgbCRyICE9ICJGQUxTRSIpDQoNCiNuZXh0LCBiYWNrZmlsdGVyIG0gdG8gaW5jbHVkZSBvbmx5IHRoZSBnZW5lcyB3aXRoIGEgdmFsaWQgZW50cnkgaW4gbCMNCm0gPC0gc3Vic2V0KG0sIG0kZ2VuZSAlaW4lIGwkZ2VuZV9uYW1lICYgbSRVbmlwcm90X0lEICVpbiUgbCR1bmlwcm90X2lkKQ0KDQojYW5kIHRoZW4sIHRoZSBhY3R1YWwgZmlsdGVyIw0KcGIgPC0gdHh0UHJvZ3Jlc3NCYXIobWluID0gMCwgbWF4ID0gbGVuZ3RoKHVuaXF1ZShtJGdlbmUpKSwgc3R5bGUgPSAzKQ0Kc3RwIDwtIDANCnJlcyA8LSBsaXN0KCkNCnJlczIgPC0gbGlzdCgpDQpmb3IoaSBpbiB1bmlxdWUobSRnZW5lKSl7DQogIHN0cCA8LSBzdHArMQ0KICBzZXRUeHRQcm9ncmVzc0JhcihwYiwgc3RwKQ0KICB6IDwtIHN1YnNldChtLCBtJGdlbmUgPT0gaSkNCiAgZyA8LSBwcm90ZWlucyhFbnNEYi5Ic2FwaWVucy52ODYsIGZpbHRlcj0gR2VuZU5hbWVGaWx0ZXIoaSksIGNvbHVtbnM9YygidW5pcHJvdF9pZCIsInByb3RlaW5fc2VxdWVuY2UiKSxyZXR1cm4udHlwZSA9ICJkYXRhLmZyYW1lIikNCiAgZyA8LSBzdWJzZXQoZywgZyR1bmlwcm90X2lkICVpbiUgdW5pcXVlKHokVW5pcHJvdF9JRCkpDQogIGlmKG5yb3coZyk9PTApe2RmIDwtIGRhdGEuZnJhbWUoZ2VuZT1pLHBvc2l0aW9uPTAsYXZhaWxhYmxlLnByb3Quc2VxPTAsb2Yud2hpY2guc2VxLmZvdW5kPTAsaXMuR1ZEPSJGQUxTRSIpDQogIHJlc1tbaV1dIDwtIGRmDQogIGRmIDwtIE5VTEx9ZWxzZXsNCiAgICBnJG4uYWEgPC0gbmNoYXIoZyRwcm90ZWluX3NlcXVlbmNlKQ0KICAgIGFhIDwtIHVuaXF1ZSh6JEFtaW5vX0FjaWRfQ2hhbmdlLngpDQoNCiAgICBmb3IoayBpbiBhYSl7DQogICAgIG1pbi5hYSA8LSBrLTMNCiAgICAgbWF4LmFhIDwtIGsrMw0KICAgICBzIDwtIHN1YnNldChnLCBnJG4uYWEgPj0gbWluLmFhICYgZyRuLmFhID49IG1heC5hYSkNCiAgICAgaWYobnJvdyhzKT09MCl7ZGYgPC0gZGF0YS5mcmFtZShnZW5lPWkscG9zaXRpb249MCxhdmFpbGFibGUucHJvdC5zZXE9MCxvZi53aGljaC5zZXEuZm91bmQ9MCxpcy5HVkQ9IkZBTFNFIikNCiAgcmVzW1tpXV0gPC0gZGYNCiAgZGYgPC0gTlVMTH1lbHNlew0KICAgICAgDQogICAgICBmb3IodyBpbiAxOm5yb3cocykpew0KICAgICAgICB2IDwtIHNbdywyXQ0KICAgICAgICB2IDwtIHN1YnNlcSh2LCBzdGFydD1taW4uYWEsIGVuZD1tYXguYWEpDQogICAgICAgIHIgPC0gZ3JlcGwoIkdQUCIsdiwgZml4ZWQgPSBUUlVFKQ0KICAgICAgICByZXMyW1tzdHBdXSA8LSBkYXRhLmZyYW1lKGdlbmU9aSxwb3NpdGlvbj1rLGF2YWlsYWJsZS5wcm90LnNlcT1ucm93KGcpLG9mLndoaWNoLnNlcS5mb3VuZD13LGlzLkdWRD1hcy5jaGFyYWN0ZXIocikpDQogICAgICB9fQ0KICAgIH0NCiAgfQ0KfQ0KcmVzIDwtIHVuaXF1ZShiaW5kX3Jvd3MocmVzMikpDQpyZXMgPC0gc3Vic2V0KHJlcywgcmVzJGlzLkdWRCA9PSAiVFJVRSIpDQpncHAgPC0gcmVzDQpncHAkbW90aWYgPC0gIkdQUCINCg0KIyMjIEdWRCBmaWx0ZXIgIyMjDQoNCiNmaXJzdCwgZmlsdGVyIGFsbCBwcm90ZWlucyB0aGF0IGhhdmUgbm8gR1ZEIGF0IGFsbCMNCm0gPC0gc20NCmwgPC0gbGlzdCgpDQpwYiA8LSB0eHRQcm9ncmVzc0JhcihtaW4gPSAwLCBtYXggPSBsZW5ndGgodW5pcXVlKG0kZ2VuZSkpLCBzdHlsZSA9IDMpDQpzdHAgPC0gMA0KZm9yKGkgaW4gdW5pcXVlKG0kZ2VuZSkpew0KICBzdHAgPC0gc3RwKzENCiAgc2V0VHh0UHJvZ3Jlc3NCYXIocGIsIHN0cCkNCiAgeiA8LSBzdWJzZXQobSwgbSRnZW5lID09IGkpDQogIGcgPC0gcHJvdGVpbnMoRW5zRGIuSHNhcGllbnMudjg2LCBmaWx0ZXI9IEdlbmVOYW1lRmlsdGVyKGkpLCBjb2x1bW5zPWMoInVuaXByb3RfaWQiLCJwcm90ZWluX3NlcXVlbmNlIikscmV0dXJuLnR5cGUgPSAiZGF0YS5mcmFtZSIpDQogIGcgPC0gc3Vic2V0KGcsIGckdW5pcHJvdF9pZCAlaW4lIHVuaXF1ZSh6JFVuaXByb3RfSUQpKQ0KICBnJHIgPC0gZ3JlcGwoIkdWRCIsZyRwcm90ZWluX3NlcXVlbmNlLCBmaXhlZCA9IFRSVUUpDQogIGcgPC0gYXMuZGF0YS5mcmFtZShnKQ0KICBnJHByb3RlaW5fc2VxdWVuY2UgPC0gTlVMTA0KICBsW1tpXV0gPC0gZw0KfQ0KbCA8LSBiaW5kX3Jvd3MobCkNCmwgPC0gc3Vic2V0KGwsIGwkciAhPSAiRkFMU0UiKQ0KDQojbmV4dCwgYmFja2ZpbHRlciBtIHRvIGluY2x1ZGUgb25seSB0aGUgZ2VuZXMgd2l0aCBhIHZhbGlkIGVudHJ5IGluIGwjDQptIDwtIHN1YnNldChtLCBtJGdlbmUgJWluJSBsJGdlbmVfbmFtZSAmIG0kVW5pcHJvdF9JRCAlaW4lIGwkdW5pcHJvdF9pZCkNCg0KI2FuZCB0aGVuLCB0aGUgYWN0dWFsIGZpbHRlciMNCnBiIDwtIHR4dFByb2dyZXNzQmFyKG1pbiA9IDAsIG1heCA9IGxlbmd0aCh1bmlxdWUobSRnZW5lKSksIHN0eWxlID0gMykNCnN0cCA8LSAwDQpyZXMgPC0gbGlzdCgpDQpyZXMyIDwtIGxpc3QoKQ0KZm9yKGkgaW4gdW5pcXVlKG0kZ2VuZSkpew0KICBzdHAgPC0gc3RwKzENCiAgc2V0VHh0UHJvZ3Jlc3NCYXIocGIsIHN0cCkNCiAgeiA8LSBzdWJzZXQobSwgbSRnZW5lID09IGkpDQogIGcgPC0gcHJvdGVpbnMoRW5zRGIuSHNhcGllbnMudjg2LCBmaWx0ZXI9IEdlbmVOYW1lRmlsdGVyKGkpLCBjb2x1bW5zPWMoInVuaXByb3RfaWQiLCJwcm90ZWluX3NlcXVlbmNlIikscmV0dXJuLnR5cGUgPSAiZGF0YS5mcmFtZSIpDQogIGcgPC0gc3Vic2V0KGcsIGckdW5pcHJvdF9pZCAlaW4lIHVuaXF1ZSh6JFVuaXByb3RfSUQpKQ0KICBpZihucm93KGcpPT0wKXtkZiA8LSBkYXRhLmZyYW1lKGdlbmU9aSxwb3NpdGlvbj0wLGF2YWlsYWJsZS5wcm90LnNlcT0wLG9mLndoaWNoLnNlcS5mb3VuZD0wLGlzLkdWRD0iRkFMU0UiKQ0KICByZXNbW2ldXSA8LSBkZg0KICBkZiA8LSBOVUxMfWVsc2V7DQogICAgZyRuLmFhIDwtIG5jaGFyKGckcHJvdGVpbl9zZXF1ZW5jZSkNCiAgICBhYSA8LSB1bmlxdWUoeiRBbWlub19BY2lkX0NoYW5nZS54KQ0KDQogICAgZm9yKGsgaW4gYWEpew0KICAgICBtaW4uYWEgPC0gay0zDQogICAgIG1heC5hYSA8LSBrKzMNCiAgICAgcyA8LSBzdWJzZXQoZywgZyRuLmFhID49IG1pbi5hYSAmIGckbi5hYSA+PSBtYXguYWEpDQogICAgIGlmKG5yb3cocyk9PTApe2RmIDwtIGRhdGEuZnJhbWUoZ2VuZT1pLHBvc2l0aW9uPTAsYXZhaWxhYmxlLnByb3Quc2VxPTAsb2Yud2hpY2guc2VxLmZvdW5kPTAsaXMuR1ZEPSJGQUxTRSIpDQogIHJlc1tbaV1dIDwtIGRmDQogIGRmIDwtIE5VTEx9ZWxzZXsNCiAgICAgIA0KICAgICAgZm9yKHcgaW4gMTpucm93KHMpKXsNCiAgICAgICAgdiA8LSBzW3csMl0NCiAgICAgICAgdiA8LSBzdWJzZXEodiwgc3RhcnQ9bWluLmFhLCBlbmQ9bWF4LmFhKQ0KICAgICAgICByIDwtIGdyZXBsKCJHVkQiLHYsIGZpeGVkID0gVFJVRSkNCiAgICAgICAgcmVzMltbc3RwXV0gPC0gZGF0YS5mcmFtZShnZW5lPWkscG9zaXRpb249ayxhdmFpbGFibGUucHJvdC5zZXE9bnJvdyhnKSxvZi53aGljaC5zZXEuZm91bmQ9dyxpcy5HVkQ9YXMuY2hhcmFjdGVyKHIpKQ0KICAgICAgfX0NCiAgICB9DQogIH0NCn0NCnJlcyA8LSB1bmlxdWUoYmluZF9yb3dzKHJlczIpKQ0KcmVzIDwtIHN1YnNldChyZXMsIHJlcyRpcy5HVkQgPT0gIlRSVUUiKQ0KZ3ZkIDwtIHJlcw0KZ3ZkJG1vdGlmIDwtICJHVkQiDQogICAgIA0KIyMjIFJHRCBmaWx0ZXIgIyMjDQoNCiNmaXJzdCwgZmlsdGVyIGFsbCBwcm90ZWlucyB0aGF0IGhhdmUgbm8gUkdEIGF0IGFsbCMNCm0gPC0gc20NCmwgPC0gbGlzdCgpDQpwYiA8LSB0eHRQcm9ncmVzc0JhcihtaW4gPSAwLCBtYXggPSBsZW5ndGgodW5pcXVlKG0kZ2VuZSkpLCBzdHlsZSA9IDMpDQpzdHAgPC0gMA0KZm9yKGkgaW4gdW5pcXVlKG0kZ2VuZSkpew0KICBzdHAgPC0gc3RwKzENCiAgc2V0VHh0UHJvZ3Jlc3NCYXIocGIsIHN0cCkNCiAgeiA8LSBzdWJzZXQobSwgbSRnZW5lID09IGkpDQogIGcgPC0gcHJvdGVpbnMoRW5zRGIuSHNhcGllbnMudjg2LCBmaWx0ZXI9IEdlbmVOYW1lRmlsdGVyKGkpLCBjb2x1bW5zPWMoInVuaXByb3RfaWQiLCJwcm90ZWluX3NlcXVlbmNlIikscmV0dXJuLnR5cGUgPSAiZGF0YS5mcmFtZSIpDQogIGcgPC0gc3Vic2V0KGcsIGckdW5pcHJvdF9pZCAlaW4lIHVuaXF1ZSh6JFVuaXByb3RfSUQpKQ0KICBnJHIgPC0gZ3JlcGwoIlJHRCIsZyRwcm90ZWluX3NlcXVlbmNlLCBmaXhlZCA9IFRSVUUpDQogIGcgPC0gYXMuZGF0YS5mcmFtZShnKQ0KICBnJHByb3RlaW5fc2VxdWVuY2UgPC0gTlVMTA0KICBsW1tpXV0gPC0gZw0KfQ0KbCA8LSBiaW5kX3Jvd3MobCkNCmwgPC0gc3Vic2V0KGwsIGwkciAhPSAiRkFMU0UiKQ0KDQojbmV4dCwgYmFja2ZpbHRlciBtIHRvIGluY2x1ZGUgb25seSB0aGUgZ2VuZXMgd2l0aCBhIHZhbGlkIGVudHJ5IGluIGwjDQptIDwtIHN1YnNldChtLCBtJGdlbmUgJWluJSBsJGdlbmVfbmFtZSAmIG0kVW5pcHJvdF9JRCAlaW4lIGwkdW5pcHJvdF9pZCkNCg0KI2FuZCB0aGVuLCB0aGUgYWN0dWFsIGZpbHRlciMNCnBiIDwtIHR4dFByb2dyZXNzQmFyKG1pbiA9IDAsIG1heCA9IGxlbmd0aCh1bmlxdWUobSRnZW5lKSksIHN0eWxlID0gMykNCnN0cCA8LSAwDQpyZXMgPC0gbGlzdCgpDQpyZXMyIDwtIGxpc3QoKQ0KZm9yKGkgaW4gdW5pcXVlKG0kZ2VuZSkpew0KICBzdHAgPC0gc3RwKzENCiAgc2V0VHh0UHJvZ3Jlc3NCYXIocGIsIHN0cCkNCiAgeiA8LSBzdWJzZXQobSwgbSRnZW5lID09IGkpDQogIGcgPC0gcHJvdGVpbnMoRW5zRGIuSHNhcGllbnMudjg2LCBmaWx0ZXI9IEdlbmVOYW1lRmlsdGVyKGkpLCBjb2x1bW5zPWMoInVuaXByb3RfaWQiLCJwcm90ZWluX3NlcXVlbmNlIikscmV0dXJuLnR5cGUgPSAiZGF0YS5mcmFtZSIpDQogIGcgPC0gc3Vic2V0KGcsIGckdW5pcHJvdF9pZCAlaW4lIHVuaXF1ZSh6JFVuaXByb3RfSUQpKQ0KICBpZihucm93KGcpPT0wKXtkZiA8LSBkYXRhLmZyYW1lKGdlbmU9aSxwb3NpdGlvbj0wLGF2YWlsYWJsZS5wcm90LnNlcT0wLG9mLndoaWNoLnNlcS5mb3VuZD0wLGlzLkdWRD0iRkFMU0UiKQ0KICByZXNbW2ldXSA8LSBkZg0KICBkZiA8LSBOVUxMfWVsc2V7DQogICAgZyRuLmFhIDwtIG5jaGFyKGckcHJvdGVpbl9zZXF1ZW5jZSkNCiAgICBhYSA8LSB1bmlxdWUoeiRBbWlub19BY2lkX0NoYW5nZS54KQ0KDQogICAgZm9yKGsgaW4gYWEpew0KICAgICBtaW4uYWEgPC0gay0zDQogICAgIG1heC5hYSA8LSBrKzMNCiAgICAgcyA8LSBzdWJzZXQoZywgZyRuLmFhID49IG1pbi5hYSAmIGckbi5hYSA+PSBtYXguYWEpDQogICAgIGlmKG5yb3cocyk9PTApe2RmIDwtIGRhdGEuZnJhbWUoZ2VuZT1pLHBvc2l0aW9uPTAsYXZhaWxhYmxlLnByb3Quc2VxPTAsb2Yud2hpY2guc2VxLmZvdW5kPTAsaXMuR1ZEPSJGQUxTRSIpDQogIHJlc1tbaV1dIDwtIGRmDQogIGRmIDwtIE5VTEx9ZWxzZXsNCiAgICAgIA0KICAgICAgZm9yKHcgaW4gMTpucm93KHMpKXsNCiAgICAgICAgdiA8LSBzW3csMl0NCiAgICAgICAgdiA8LSBzdWJzZXEodiwgc3RhcnQ9bWluLmFhLCBlbmQ9bWF4LmFhKQ0KICAgICAgICByIDwtIGdyZXBsKCJSR0QiLHYsIGZpeGVkID0gVFJVRSkNCiAgICAgICAgcmVzMltbc3RwXV0gPC0gZGF0YS5mcmFtZShnZW5lPWkscG9zaXRpb249ayxhdmFpbGFibGUucHJvdC5zZXE9bnJvdyhnKSxvZi53aGljaC5zZXEuZm91bmQ9dyxpcy5HVkQ9YXMuY2hhcmFjdGVyKHIpKQ0KICAgICAgfX0NCiAgICB9DQogIH0NCn0NCnJlcyA8LSB1bmlxdWUoYmluZF9yb3dzKHJlczIpKQ0KcmVzIDwtIHN1YnNldChyZXMsIHJlcyRpcy5HVkQgPT0gIlRSVUUiKQ0KcmdkIDwtIHJlcw0KI3JnZCRtb3RpZiA8LSAiUkdEIiAjbm8gUkdEIQ0KDQojIyMgTERWIGZpbHRlciAjIyMNCg0KI2ZpcnN0LCBmaWx0ZXIgYWxsIHByb3RlaW5zIHRoYXQgaGF2ZSBubyBMRFYgYXQgYWxsIw0KbSA8LSBzbQ0KbCA8LSBsaXN0KCkNCnBiIDwtIHR4dFByb2dyZXNzQmFyKG1pbiA9IDAsIG1heCA9IGxlbmd0aCh1bmlxdWUobSRnZW5lKSksIHN0eWxlID0gMykNCnN0cCA8LSAwDQpmb3IoaSBpbiB1bmlxdWUobSRnZW5lKSl7DQogIHN0cCA8LSBzdHArMQ0KICBzZXRUeHRQcm9ncmVzc0JhcihwYiwgc3RwKQ0KICB6IDwtIHN1YnNldChtLCBtJGdlbmUgPT0gaSkNCiAgZyA8LSBwcm90ZWlucyhFbnNEYi5Ic2FwaWVucy52ODYsIGZpbHRlcj0gR2VuZU5hbWVGaWx0ZXIoaSksIGNvbHVtbnM9YygidW5pcHJvdF9pZCIsInByb3RlaW5fc2VxdWVuY2UiKSxyZXR1cm4udHlwZSA9ICJkYXRhLmZyYW1lIikNCiAgZyA8LSBzdWJzZXQoZywgZyR1bmlwcm90X2lkICVpbiUgdW5pcXVlKHokVW5pcHJvdF9JRCkpDQogIGckciA8LSBncmVwbCgiTERWIixnJHByb3RlaW5fc2VxdWVuY2UsIGZpeGVkID0gVFJVRSkNCiAgZyA8LSBhcy5kYXRhLmZyYW1lKGcpDQogIGckcHJvdGVpbl9zZXF1ZW5jZSA8LSBOVUxMDQogIGxbW2ldXSA8LSBnDQp9DQpsIDwtIGJpbmRfcm93cyhsKQ0KbCA8LSBzdWJzZXQobCwgbCRyICE9ICJGQUxTRSIpDQoNCiNuZXh0LCBiYWNrZmlsdGVyIG0gdG8gaW5jbHVkZSBvbmx5IHRoZSBnZW5lcyB3aXRoIGEgdmFsaWQgZW50cnkgaW4gbCMNCm0gPC0gc3Vic2V0KG0sIG0kZ2VuZSAlaW4lIGwkZ2VuZV9uYW1lICYgbSRVbmlwcm90X0lEICVpbiUgbCR1bmlwcm90X2lkKQ0KDQojYW5kIHRoZW4sIHRoZSBhY3R1YWwgZmlsdGVyIw0KcGIgPC0gdHh0UHJvZ3Jlc3NCYXIobWluID0gMCwgbWF4ID0gbGVuZ3RoKHVuaXF1ZShtJGdlbmUpKSwgc3R5bGUgPSAzKQ0Kc3RwIDwtIDANCnJlcyA8LSBsaXN0KCkNCnJlczIgPC0gbGlzdCgpDQpmb3IoaSBpbiB1bmlxdWUobSRnZW5lKSl7DQogIHN0cCA8LSBzdHArMQ0KICBzZXRUeHRQcm9ncmVzc0JhcihwYiwgc3RwKQ0KICB6IDwtIHN1YnNldChtLCBtJGdlbmUgPT0gaSkNCiAgZyA8LSBwcm90ZWlucyhFbnNEYi5Ic2FwaWVucy52ODYsIGZpbHRlcj0gR2VuZU5hbWVGaWx0ZXIoaSksIGNvbHVtbnM9YygidW5pcHJvdF9pZCIsInByb3RlaW5fc2VxdWVuY2UiKSxyZXR1cm4udHlwZSA9ICJkYXRhLmZyYW1lIikNCiAgZyA8LSBzdWJzZXQoZywgZyR1bmlwcm90X2lkICVpbiUgdW5pcXVlKHokVW5pcHJvdF9JRCkpDQogIGlmKG5yb3coZyk9PTApe2RmIDwtIGRhdGEuZnJhbWUoZ2VuZT1pLHBvc2l0aW9uPTAsYXZhaWxhYmxlLnByb3Quc2VxPTAsb2Yud2hpY2guc2VxLmZvdW5kPTAsaXMuR1ZEPSJGQUxTRSIpDQogIHJlc1tbaV1dIDwtIGRmDQogIGRmIDwtIE5VTEx9ZWxzZXsNCiAgICBnJG4uYWEgPC0gbmNoYXIoZyRwcm90ZWluX3NlcXVlbmNlKQ0KICAgIGFhIDwtIHVuaXF1ZSh6JEFtaW5vX0FjaWRfQ2hhbmdlLngpDQoNCiAgICBmb3IoayBpbiBhYSl7DQogICAgIG1pbi5hYSA8LSBrLTMNCiAgICAgbWF4LmFhIDwtIGsrMw0KICAgICBzIDwtIHN1YnNldChnLCBnJG4uYWEgPj0gbWluLmFhICYgZyRuLmFhID49IG1heC5hYSkNCiAgICAgaWYobnJvdyhzKT09MCl7ZGYgPC0gZGF0YS5mcmFtZShnZW5lPWkscG9zaXRpb249MCxhdmFpbGFibGUucHJvdC5zZXE9MCxvZi53aGljaC5zZXEuZm91bmQ9MCxpcy5HVkQ9IkZBTFNFIikNCiAgcmVzW1tpXV0gPC0gZGYNCiAgZGYgPC0gTlVMTH1lbHNlew0KICAgICAgDQogICAgICBmb3IodyBpbiAxOm5yb3cocykpew0KICAgICAgICB2IDwtIHNbdywyXQ0KICAgICAgICB2IDwtIHN1YnNlcSh2LCBzdGFydD1taW4uYWEsIGVuZD1tYXguYWEpDQogICAgICAgIHIgPC0gZ3JlcGwoIkxEViIsdiwgZml4ZWQgPSBUUlVFKQ0KICAgICAgICByZXMyW1tzdHBdXSA8LSBkYXRhLmZyYW1lKGdlbmU9aSxwb3NpdGlvbj1rLGF2YWlsYWJsZS5wcm90LnNlcT1ucm93KGcpLG9mLndoaWNoLnNlcS5mb3VuZD13LGlzLkdWRD1hcy5jaGFyYWN0ZXIocikpDQogICAgICB9fQ0KICAgIH0NCiAgfQ0KfQ0KcmVzIDwtIHVuaXF1ZShiaW5kX3Jvd3MocmVzMikpDQpyZXMgPC0gc3Vic2V0KHJlcywgcmVzJGlzLkdWRCA9PSAiVFJVRSIpDQpsZHYgPC0gcmVzDQojbGR2JG1vdGlmIDwtICJMRFYiICNubyBMRFYNCg0KIyMjIEdGUEdFUiBmaWx0ZXIgIyMjDQoNCiNmaXJzdCwgZmlsdGVyIGFsbCBwcm90ZWlucyB0aGF0IGhhdmUgbm8gTERWIGF0IGFsbCMNCm0gPC0gc20NCmwgPC0gbGlzdCgpDQpwYiA8LSB0eHRQcm9ncmVzc0JhcihtaW4gPSAwLCBtYXggPSBsZW5ndGgodW5pcXVlKG0kZ2VuZSkpLCBzdHlsZSA9IDMpDQpzdHAgPC0gMA0KZm9yKGkgaW4gdW5pcXVlKG0kZ2VuZSkpew0KICBzdHAgPC0gc3RwKzENCiAgc2V0VHh0UHJvZ3Jlc3NCYXIocGIsIHN0cCkNCiAgeiA8LSBzdWJzZXQobSwgbSRnZW5lID09IGkpDQogIGcgPC0gcHJvdGVpbnMoRW5zRGIuSHNhcGllbnMudjg2LCBmaWx0ZXI9IEdlbmVOYW1lRmlsdGVyKGkpLCBjb2x1bW5zPWMoInVuaXByb3RfaWQiLCJwcm90ZWluX3NlcXVlbmNlIikscmV0dXJuLnR5cGUgPSAiZGF0YS5mcmFtZSIpDQogIGcgPC0gc3Vic2V0KGcsIGckdW5pcHJvdF9pZCAlaW4lIHVuaXF1ZSh6JFVuaXByb3RfSUQpKQ0KICBnJHIgPC0gZ3JlcGwoIkdGUEdFUiIsZyRwcm90ZWluX3NlcXVlbmNlLCBmaXhlZCA9IFRSVUUpDQogIGcgPC0gYXMuZGF0YS5mcmFtZShnKQ0KICBnJHByb3RlaW5fc2VxdWVuY2UgPC0gTlVMTA0KICBsW1tpXV0gPC0gZw0KfQ0KbCA8LSBiaW5kX3Jvd3MobCkNCmwgPC0gc3Vic2V0KGwsIGwkciAhPSAiRkFMU0UiKQ0KDQojbmV4dCwgYmFja2ZpbHRlciBtIHRvIGluY2x1ZGUgb25seSB0aGUgZ2VuZXMgd2l0aCBhIHZhbGlkIGVudHJ5IGluIGwjDQptIDwtIHN1YnNldChtLCBtJGdlbmUgJWluJSBsJGdlbmVfbmFtZSAmIG0kVW5pcHJvdF9JRCAlaW4lIGwkdW5pcHJvdF9pZCkNCg0KI2FuZCB0aGVuLCB0aGUgYWN0dWFsIGZpbHRlciMNCnBiIDwtIHR4dFByb2dyZXNzQmFyKG1pbiA9IDAsIG1heCA9IGxlbmd0aCh1bmlxdWUobSRnZW5lKSksIHN0eWxlID0gMykNCnN0cCA8LSAwDQpyZXMgPC0gbGlzdCgpDQpyZXMyIDwtIGxpc3QoKQ0KZm9yKGkgaW4gdW5pcXVlKG0kZ2VuZSkpew0KICBzdHAgPC0gc3RwKzENCiAgc2V0VHh0UHJvZ3Jlc3NCYXIocGIsIHN0cCkNCiAgeiA8LSBzdWJzZXQobSwgbSRnZW5lID09IGkpDQogIGcgPC0gcHJvdGVpbnMoRW5zRGIuSHNhcGllbnMudjg2LCBmaWx0ZXI9IEdlbmVOYW1lRmlsdGVyKGkpLCBjb2x1bW5zPWMoInVuaXByb3RfaWQiLCJwcm90ZWluX3NlcXVlbmNlIikscmV0dXJuLnR5cGUgPSAiZGF0YS5mcmFtZSIpDQogIGcgPC0gc3Vic2V0KGcsIGckdW5pcHJvdF9pZCAlaW4lIHVuaXF1ZSh6JFVuaXByb3RfSUQpKQ0KICBpZihucm93KGcpPT0wKXtkZiA8LSBkYXRhLmZyYW1lKGdlbmU9aSxwb3NpdGlvbj0wLGF2YWlsYWJsZS5wcm90LnNlcT0wLG9mLndoaWNoLnNlcS5mb3VuZD0wLGlzLkdWRD0iRkFMU0UiKQ0KICByZXNbW2ldXSA8LSBkZg0KICBkZiA8LSBOVUxMfWVsc2V7DQogICAgZyRuLmFhIDwtIG5jaGFyKGckcHJvdGVpbl9zZXF1ZW5jZSkNCiAgICBhYSA8LSB1bmlxdWUoeiRBbWlub19BY2lkX0NoYW5nZS54KQ0KDQogICAgZm9yKGsgaW4gYWEpew0KICAgICBtaW4uYWEgPC0gay0zDQogICAgIG1heC5hYSA8LSBrKzMNCiAgICAgcyA8LSBzdWJzZXQoZywgZyRuLmFhID49IG1pbi5hYSAmIGckbi5hYSA+PSBtYXguYWEpDQogICAgIGlmKG5yb3cocyk9PTApe2RmIDwtIGRhdGEuZnJhbWUoZ2VuZT1pLHBvc2l0aW9uPTAsYXZhaWxhYmxlLnByb3Quc2VxPTAsb2Yud2hpY2guc2VxLmZvdW5kPTAsaXMuR1ZEPSJGQUxTRSIpDQogIHJlc1tbaV1dIDwtIGRmDQogIGRmIDwtIE5VTEx9ZWxzZXsNCiAgICAgIA0KICAgICAgZm9yKHcgaW4gMTpucm93KHMpKXsNCiAgICAgICAgdiA8LSBzW3csMl0NCiAgICAgICAgdiA8LSBzdWJzZXEodiwgc3RhcnQ9bWluLmFhLCBlbmQ9bWF4LmFhKQ0KICAgICAgICByIDwtIGdyZXBsKCJHRlBHRVIiLHYsIGZpeGVkID0gVFJVRSkNCiAgICAgICAgcmVzMltbc3RwXV0gPC0gZGF0YS5mcmFtZShnZW5lPWkscG9zaXRpb249ayxhdmFpbGFibGUucHJvdC5zZXE9bnJvdyhnKSxvZi53aGljaC5zZXEuZm91bmQ9dyxpcy5HVkQ9YXMuY2hhcmFjdGVyKHIpKQ0KICAgICAgfX0NCiAgICB9DQogIH0NCn0NCnJlcyA8LSB1bmlxdWUoYmluZF9yb3dzKHJlczIpKQ0KcmVzIDwtIHN1YnNldChyZXMsIHJlcyRpcy5HVkQgPT0gIlRSVUUiKQ0KZ2ZwZ2VyIDwtIHJlcw0KI2dmcGdlciRtb3RpZiA8LSAiR0ZQR0VSIiAjbm8gTERWDQoNCiMjIyBHTFBHRVIgZmlsdGVyICMjIw0KDQojZmlyc3QsIGZpbHRlciBhbGwgcHJvdGVpbnMgdGhhdCBoYXZlIG5vIExEViBhdCBhbGwjDQptIDwtIHNtDQpsIDwtIGxpc3QoKQ0KcGIgPC0gdHh0UHJvZ3Jlc3NCYXIobWluID0gMCwgbWF4ID0gbGVuZ3RoKHVuaXF1ZShtJGdlbmUpKSwgc3R5bGUgPSAzKQ0Kc3RwIDwtIDANCmZvcihpIGluIHVuaXF1ZShtJGdlbmUpKXsNCiAgc3RwIDwtIHN0cCsxDQogIHNldFR4dFByb2dyZXNzQmFyKHBiLCBzdHApDQogIHogPC0gc3Vic2V0KG0sIG0kZ2VuZSA9PSBpKQ0KICBnIDwtIHByb3RlaW5zKEVuc0RiLkhzYXBpZW5zLnY4NiwgZmlsdGVyPSBHZW5lTmFtZUZpbHRlcihpKSwgY29sdW1ucz1jKCJ1bmlwcm90X2lkIiwicHJvdGVpbl9zZXF1ZW5jZSIpLHJldHVybi50eXBlID0gImRhdGEuZnJhbWUiKQ0KICBnIDwtIHN1YnNldChnLCBnJHVuaXByb3RfaWQgJWluJSB1bmlxdWUoeiRVbmlwcm90X0lEKSkNCiAgZyRyIDwtIGdyZXBsKCJHTFBHRVIiLGckcHJvdGVpbl9zZXF1ZW5jZSwgZml4ZWQgPSBUUlVFKQ0KICBnIDwtIGFzLmRhdGEuZnJhbWUoZykNCiAgZyRwcm90ZWluX3NlcXVlbmNlIDwtIE5VTEwNCiAgbFtbaV1dIDwtIGcNCn0NCmwgPC0gYmluZF9yb3dzKGwpDQpsIDwtIHN1YnNldChsLCBsJHIgIT0gIkZBTFNFIikNCg0KI25leHQsIGJhY2tmaWx0ZXIgbSB0byBpbmNsdWRlIG9ubHkgdGhlIGdlbmVzIHdpdGggYSB2YWxpZCBlbnRyeSBpbiBsIw0KbSA8LSBzdWJzZXQobSwgbSRnZW5lICVpbiUgbCRnZW5lX25hbWUgJiBtJFVuaXByb3RfSUQgJWluJSBsJHVuaXByb3RfaWQpDQoNCiNhbmQgdGhlbiwgdGhlIGFjdHVhbCBmaWx0ZXIjDQpwYiA8LSB0eHRQcm9ncmVzc0JhcihtaW4gPSAwLCBtYXggPSBsZW5ndGgodW5pcXVlKG0kZ2VuZSkpLCBzdHlsZSA9IDMpDQpzdHAgPC0gMA0KcmVzIDwtIGxpc3QoKQ0KcmVzMiA8LSBsaXN0KCkNCmZvcihpIGluIHVuaXF1ZShtJGdlbmUpKXsNCiAgc3RwIDwtIHN0cCsxDQogIHNldFR4dFByb2dyZXNzQmFyKHBiLCBzdHApDQogIHogPC0gc3Vic2V0KG0sIG0kZ2VuZSA9PSBpKQ0KICBnIDwtIHByb3RlaW5zKEVuc0RiLkhzYXBpZW5zLnY4NiwgZmlsdGVyPSBHZW5lTmFtZUZpbHRlcihpKSwgY29sdW1ucz1jKCJ1bmlwcm90X2lkIiwicHJvdGVpbl9zZXF1ZW5jZSIpLHJldHVybi50eXBlID0gImRhdGEuZnJhbWUiKQ0KICBnIDwtIHN1YnNldChnLCBnJHVuaXByb3RfaWQgJWluJSB1bmlxdWUoeiRVbmlwcm90X0lEKSkNCiAgaWYobnJvdyhnKT09MCl7ZGYgPC0gZGF0YS5mcmFtZShnZW5lPWkscG9zaXRpb249MCxhdmFpbGFibGUucHJvdC5zZXE9MCxvZi53aGljaC5zZXEuZm91bmQ9MCxpcy5HVkQ9IkZBTFNFIikNCiAgcmVzW1tpXV0gPC0gZGYNCiAgZGYgPC0gTlVMTH1lbHNlew0KICAgIGckbi5hYSA8LSBuY2hhcihnJHByb3RlaW5fc2VxdWVuY2UpDQogICAgYWEgPC0gdW5pcXVlKHokQW1pbm9fQWNpZF9DaGFuZ2UueCkNCg0KICAgIGZvcihrIGluIGFhKXsNCiAgICAgbWluLmFhIDwtIGstMw0KICAgICBtYXguYWEgPC0gayszDQogICAgIHMgPC0gc3Vic2V0KGcsIGckbi5hYSA+PSBtaW4uYWEgJiBnJG4uYWEgPj0gbWF4LmFhKQ0KICAgICBpZihucm93KHMpPT0wKXtkZiA8LSBkYXRhLmZyYW1lKGdlbmU9aSxwb3NpdGlvbj0wLGF2YWlsYWJsZS5wcm90LnNlcT0wLG9mLndoaWNoLnNlcS5mb3VuZD0wLGlzLkdWRD0iRkFMU0UiKQ0KICByZXNbW2ldXSA8LSBkZg0KICBkZiA8LSBOVUxMfWVsc2V7DQogICAgICANCiAgICAgIGZvcih3IGluIDE6bnJvdyhzKSl7DQogICAgICAgIHYgPC0gc1t3LDJdDQogICAgICAgIHYgPC0gc3Vic2VxKHYsIHN0YXJ0PW1pbi5hYSwgZW5kPW1heC5hYSkNCiAgICAgICAgciA8LSBncmVwbCgiR0xQR0VSIix2LCBmaXhlZCA9IFRSVUUpDQogICAgICAgIHJlczJbW3N0cF1dIDwtIGRhdGEuZnJhbWUoZ2VuZT1pLHBvc2l0aW9uPWssYXZhaWxhYmxlLnByb3Quc2VxPW5yb3coZyksb2Yud2hpY2guc2VxLmZvdW5kPXcsaXMuR1ZEPWFzLmNoYXJhY3RlcihyKSkNCiAgICAgIH19DQogICAgfQ0KICB9DQp9DQpyZXMgPC0gdW5pcXVlKGJpbmRfcm93cyhyZXMyKSkNCnJlcyA8LSBzdWJzZXQocmVzLCByZXMkaXMuR1ZEID09ICJUUlVFIikNCmdscGdlciA8LSByZXMNCiNnbHBnZXIkbW90aWYgPC0gIkdGUEdFUiIgI25vIExEVg0KDQojIyMgQmluZCByZXN1bHRzIHRvZ2V0aGVyICMjIw0KDQpyZXMgPC0gYmluZF9yb3dzKGdwcCwgbGR2KQ0KcmVzJGlkIDwtIHBhc3RlMChyZXMkZ2VuZSwiXyIscmVzJHBvc2l0aW9uKQ0Kc20kaWQgPC0gcGFzdGUwKHNtJGdlbmUsIl8iLHNtJEFtaW5vX0FjaWRfQ2hhbmdlKQ0Kc20gPC0gc3Vic2V0KHNtLCBzZWxlY3QgPSBjKCJnZW5lIiwiaWQiKSkNCnJlcyA8LSBtZXJnZShzbSxyZXMsYnk9ImlkIikNCnJlcyRpZCA8LSBOVUxMDQpyZXMkZ2VuZS55IDwtIE5VTEwNCm5hbWVzKHJlcylbMV0gPC0gImdlbmUiDQpuYW1lcyhyZXMpWzVdIDwtICJpcy5HVkQucGx1cy5taW51cy4zIg0KZndyaXRlKHJlcywgIk1PVElGIE1BUFBJTkcgT0Ygbm9uLVBUTW11dC50eHQiLHNlcD0iXHQiLHJvdy5uYW1lcyA9IEYsIHF1b3RlID0gRikNCmBgYA0KDQpBY3R1YWxseSwgdGhleSBhcmUgYm90aCB1bmxpa2VseSBidXQgUFRNbXV0IHNlZW0gYmV0dGVyIHRvbGVyYXRlZCBpbiB0b3RvISBUaGVuLCBhcmUgdGhlcmUgc2VxdWVuY2UgZmVhdHVyZXMgdGhhdCBjYW4gdGVsbCB1cyBzb21ldGhpbmcgYWJvdXQgdGhlIG1hcHBpbmcgbXV0YXRpb25zPw0KDQpgYGB7ciwgZXZhbD1GfQ0KDQp0YWIgPC0gZnJlYWQoIlRBQkxFIDEudHh0IixoZWFkZXI9VCxzZXA9Ilx0IikNCmFsbG11dGF0aW9ucyA8LSBmcmVhZCgiYWxsbXV0YXRpb25zLnR4dCIsIGhlYWRlcj1ULCBzZXA9Ilx0IikgIyBpbXBvcnQgbXV0YXRpb24gZGF0YSAjDQphbGxtdXRhdGlvbnMgPC0gYXMuZGF0YS5mcmFtZShhbGxtdXRhdGlvbnMpDQphbGxtdXRhdGlvbnMkQW1pbm9fQWNpZF9DaGFuZ2UgPC0gZ3N1YigicC4iLCIiLGFsbG11dGF0aW9ucyRBbWlub19BY2lkX0NoYW5nZSkNCmFsbG11dGF0aW9ucyRBbWlub19BY2lkX0NoYW5nZSA8LSBzdWJzdHJpbmcoYWxsbXV0YXRpb25zJEFtaW5vX0FjaWRfQ2hhbmdlLCAxLCAxKQ0KbSA8LSBtZXJnZSh0YWIsIGFsbG11dGF0aW9ucywgYnkueD1jKCJzYW1wbGUiLCJnZW5lIiwiY2hyIiwic3RhcnQiLCJlbmQiKSwgYnkueT1jKCJzYW1wbGUiLCJnZW5lIiwiY2hyIiwic3RhcnQiLCJlbmQiKSkNCm0gPC0gc3Vic2V0KG0sIG0kaXMubWF0cmlzb21lID09ICJtYXRyaXNvbWUiKQ0KDQpwYiA8LSB0eHRQcm9ncmVzc0JhcihtaW4gPSAwLCBtYXggPSBsZW5ndGgodW5pcXVlKG0kZ2VuZSkpLCBzdHlsZSA9IDMpDQpzdHAgPC0gMA0KcmVzIDwtIGxpc3QoKQ0KcmVzMiA8LSBsaXN0KCkNCmZvcihpIGluIHVuaXF1ZShtJGdlbmUpKXsNCiAgc3RwIDwtIHN0cCsxDQogIHNldFR4dFByb2dyZXNzQmFyKHBiLCBzdHApDQogIHogPC0gc3Vic2V0KG0sIG0kZ2VuZSA9PSBpKQ0KICBnIDwtIHByb3RlaW5zKEVuc0RiLkhzYXBpZW5zLnY4NiwgZmlsdGVyPSBHZW5lTmFtZUZpbHRlcihpKSwgY29sdW1ucz1jKCJ1bmlwcm90X2lkIiwicHJvdGVpbl9zZXF1ZW5jZSIpLHJldHVybi50eXBlID0gImRhdGEuZnJhbWUiKQ0KICBnIDwtIHN1YnNldChnLCBnJHVuaXByb3RfaWQgJWluJSB1bmlxdWUoeiRVbmlwcm90X0lEKSkNCiAgaWYobnJvdyhnKT09MCl7ZGYgPC0gZGF0YS5mcmFtZShnZW5lPWkscG9zaXRpb249MCxhdmFpbGFibGUucHJvdC5zZXE9MCxvZi53aGljaC5zZXEuZm91bmQ9MCxpcy5HVkQ9IkZBTFNFIikNCiAgcmVzW1tpXV0gPC0gZGYNCiAgZGYgPC0gTlVMTH1lbHNlew0KICAgIGckbi5hYSA8LSBuY2hhcihnJHByb3RlaW5fc2VxdWVuY2UpDQogICAgYWEgPC0gdW5pcXVlKHokQW1pbm9fQWNpZF9DaGFuZ2UueCkNCiAgICBhYSA8LSBhYVthYT4xMV0NCg0KICAgIGZvcihrIGluIGFhKXsNCiAgICAgbWluLmFhIDwtIGstMTANCiAgICAgbWF4LmFhIDwtIGsrMTANCiAgICAgcyA8LSBzdWJzZXQoZywgZyRuLmFhID49IG1pbi5hYSAmIGckbi5hYSA+PSBtYXguYWEpDQogICAgIGlmKG5yb3cocyk9PTApe2RmIDwtIGRhdGEuZnJhbWUoZ2VuZT1pLHBvc2l0aW9uPTAsYXZhaWxhYmxlLnByb3Quc2VxPTAsb2Yud2hpY2guc2VxLmZvdW5kPTAsaXMuR1ZEPSJGQUxTRSIpDQogIHJlc1tbaV1dIDwtIGRmDQogIGRmIDwtIE5VTEx9ZWxzZXsNCiAgICAgIA0KICAgICAgZm9yKHcgaW4gMTpucm93KHMpKXsNCiAgICAgICAgdiA8LSBzW3csMl0NCiAgICAgICAgdiA8LSBzdWJzZXEodiwgc3RhcnQ9bWluLmFhLCBlbmQ9bWF4LmFhKQ0KICAgICAgICByZXMyW1tzdHBdXSA8LSBkYXRhLmZyYW1lKGdlbmU9aSxwb3NpdGlvbj1rLGF2YWlsYWJsZS5wcm90LnNlcT1ucm93KGcpLG9mLndoaWNoLnNlcS5mb3VuZD13LGlzLkdWRD1hcy5jaGFyYWN0ZXIodikpDQogICAgICB9fQ0KICAgIH0NCiAgfQ0KfQ0KcmVzIDwtIHVuaXF1ZShiaW5kX3Jvd3MocmVzMikpDQpyZXMkaWQgPC0gcGFzdGUwKHJlcyRnZW5lLCJfIixyZXMkcG9zaXRpb24pDQptJGlkIDwtIHBhc3RlMChzbSRnZW5lLCJfIixzbSRBbWlub19BY2lkX0NoYW5nZS54KQ0KbSA8LSBzdWJzZXQobSwgc2VsZWN0ID0gYygiZ2VuZSIsIlBUTSIsImlkIikpDQpyZXMgPC0gbWVyZ2UobSxyZXMsYnk9ImlkIikNCnJlcyRpZCA8LSBOVUxMDQpyZXMkZ2VuZS55IDwtIE5VTEwNCm5hbWVzKHJlcylbMV0gPC0gImdlbmUiDQpuYW1lcyhyZXMpWzZdIDwtICJzZXF1ZW5jZS5jb250ZXh0LjEwYWEiDQpwdG0gPC0gcmVzDQoNCnRhYiA8LSBhcy5kYXRhLmZyYW1lKGZyZWFkKCJhbGwgbXV0YXRpb25zLnR4dCIsaGVhZGVyPVQsc2VwPSJcdCIpKQ0KdGFiMiA8LSBhcy5kYXRhLmZyYW1lKGZyZWFkKCJhbGwgUFRNIG11dGF0aW9ucy50eHQiLGhlYWRlcj1ULHNlcD0iXHQiKSkNCnRhYiRpZCA8LSBwYXN0ZTAodGFiJHNhbXBsZSwiXyIsdGFiJGdlbmUsIl8iLHRhYiRBbWlub19BY2lkX0NoYW5nZSwiXyIsdGFiJGBjYW5jZXIgdHlwZSBhYmJyZXZpYXRpb25gKQ0KdGFiMiRpZCA8LSBwYXN0ZTAodGFiMiRzYW1wbGUsIl8iLHRhYjIkZ2VuZSwiXyIsdGFiMiRBbWlub19BY2lkX0NoYW5nZSwiXyIsdGFiMiRgY2FuY2VyIHR5cGUgYWJicmV2aWF0aW9uYCkNCnRhYiA8LSBzdWJzZXQodGFiLCAhKHRhYiRpZCAlaW4lIHRhYjIkaWQpKQ0KdGFiIDwtIHN1YnNldCh0YWIsIHRhYiRpcy5tYXRyaXNvbWUgPT0gIm1hdHJpc29tZSIpDQp0YWIgPC0gbmEub21pdCh0YWIpDQpudGFiIDwtIGZyZWFkKCJUQUJMRSAxLnR4dCIsaGVhZGVyPVQsc2VwPSJcdCIpDQpudGFiIDwtIHN1YnNldChudGFiLCBudGFiJGlzLm1hdHJpc29tZSA9PSAibWF0cmlzb21lIikNCm50YWIgPC0gc3Vic2V0KG50YWIsIHNlbGVjdD1jKCJnZW5lIiwiVW5pcHJvdF9JRCIpKQ0KbSA8LSBkaXN0aW5jdChtZXJnZSh0YWIsbnRhYixieT0iZ2VuZSIpKQ0KbSA8LSBtICU+JSBtdXRhdGVfaWYoYml0NjQ6OmlzLmludGVnZXI2NCwgYXMuaW50ZWdlcikNCm0gPC0gc3Vic2V0KG0sIG0kaXMubWF0cmlzb21lID09ICJtYXRyaXNvbWUiKQ0KDQpwYiA8LSB0eHRQcm9ncmVzc0JhcihtaW4gPSAwLCBtYXggPSBsZW5ndGgodW5pcXVlKG0kZ2VuZSkpLCBzdHlsZSA9IDMpDQpzdHAgPC0gMA0KcmVzIDwtIGxpc3QoKQ0KcmVzMiA8LSBsaXN0KCkNCmZvcihpIGluIHVuaXF1ZShtJGdlbmUpKXsNCiAgc3RwIDwtIHN0cCsxDQogIHNldFR4dFByb2dyZXNzQmFyKHBiLCBzdHApDQogIHogPC0gc3Vic2V0KG0sIG0kZ2VuZSA9PSBpKQ0KICBnIDwtIHByb3RlaW5zKEVuc0RiLkhzYXBpZW5zLnY4NiwgZmlsdGVyPSBHZW5lTmFtZUZpbHRlcihpKSwgY29sdW1ucz1jKCJ1bmlwcm90X2lkIiwicHJvdGVpbl9zZXF1ZW5jZSIpLHJldHVybi50eXBlID0gImRhdGEuZnJhbWUiKQ0KICBnIDwtIHN1YnNldChnLCBnJHVuaXByb3RfaWQgJWluJSB1bmlxdWUoeiRVbmlwcm90X0lEKSkNCiAgaWYobnJvdyhnKT09MCl7ZGYgPC0gZGF0YS5mcmFtZShnZW5lPWkscG9zaXRpb249MCxhdmFpbGFibGUucHJvdC5zZXE9MCxvZi53aGljaC5zZXEuZm91bmQ9MCxpcy5HVkQ9IkZBTFNFIikNCiAgcmVzW1tpXV0gPC0gZGYNCiAgZGYgPC0gTlVMTH1lbHNlew0KICAgIGckbi5hYSA8LSBuY2hhcihnJHByb3RlaW5fc2VxdWVuY2UpDQogICAgYWEgPC0gdW5pcXVlKHokQW1pbm9fQWNpZF9DaGFuZ2UpDQogICAgYWEgPC0gYWFbYWE+MTFdDQoNCiAgICBmb3IoayBpbiBhYSl7DQogICAgIG1pbi5hYSA8LSBrLTEwDQogICAgIG1heC5hYSA8LSBrKzEwDQogICAgIHMgPC0gc3Vic2V0KGcsIGckbi5hYSA+PSBtaW4uYWEgJiBnJG4uYWEgPj0gbWF4LmFhKQ0KICAgICBpZihucm93KHMpPT0wKXtkZiA8LSBkYXRhLmZyYW1lKGdlbmU9aSxwb3NpdGlvbj0wLGF2YWlsYWJsZS5wcm90LnNlcT0wLG9mLndoaWNoLnNlcS5mb3VuZD0wLGlzLkdWRD0iRkFMU0UiKQ0KICByZXNbW2ldXSA8LSBkZg0KICBkZiA8LSBOVUxMfWVsc2V7DQogICAgICANCiAgICAgIGZvcih3IGluIDE6bnJvdyhzKSl7DQogICAgICAgIHYgPC0gc1t3LDJdDQogICAgICAgIHYgPC0gc3Vic2VxKHYsIHN0YXJ0PW1pbi5hYSwgZW5kPW1heC5hYSkNCiAgICAgICAgcmVzMltbc3RwXV0gPC0gZGF0YS5mcmFtZShnZW5lPWkscG9zaXRpb249ayxhdmFpbGFibGUucHJvdC5zZXE9bnJvdyhnKSxvZi53aGljaC5zZXEuZm91bmQ9dyxpcy5HVkQ9YXMuY2hhcmFjdGVyKHYpKQ0KICAgICAgfX0NCiAgICB9DQogIH0NCn0NCnJlcyA8LSB1bmlxdWUoYmluZF9yb3dzKHJlczIpKQ0KcmVzJGlkIDwtIHBhc3RlMChyZXMkZ2VuZSwiXyIscmVzJHBvc2l0aW9uKQ0KbSRpZCA8LSBwYXN0ZTAobSRnZW5lLCJfIixtJEFtaW5vX0FjaWRfQ2hhbmdlKQ0KbSA8LSBzdWJzZXQobSwgc2VsZWN0ID0gYygiZ2VuZSIsImlkIikpDQpyZXMgPC0gbWVyZ2UobSxyZXMsYnk9ImlkIikNCnJlcyRpZCA8LSBOVUxMDQpyZXMkZ2VuZS55IDwtIE5VTEwNCm5hbWVzKHJlcylbMV0gPC0gImdlbmUiDQpuYW1lcyhyZXMpWzVdIDwtICJzZXF1ZW5jZS5jb250ZXh0LjEwYWEiDQpub3B0bSA8LSByZXMNCg0KbCA8LSBsaXN0KCkNCmwyIDwtIGxpc3QoKQ0KZm9yKGkgaW4gdW5pcXVlKHB0bSRQVE0pKXsNCiAgeiA8LSBzdWJzZXQocHRtLCBwdG0kUFRNID09IGkpDQogIGcgPC0geiRnZW5lDQogIGxbW2ldXSA8LSB6JHNlcXVlbmNlLmNvbnRleHQuMTBhYQ0KICB6IDwtIHN1YnNldChub3B0bSwgbm9wdG0kZ2VuZSAlaW4lIGcpDQogIGwyW1tpXV0gPC0geiRzZXF1ZW5jZS5jb250ZXh0LjEwYWENCn0NCm5hbWVzKGwpIDwtIHVuaXF1ZShwdG0kUFRNKQ0KbmFtZXMobDIpIDwtIHVuaXF1ZShwdG0kUFRNKSAjdGhlc2UgYXJlLCBvZiBjb3Vyc2UsIGZpY3Rpb25hbCENCg0KZ2dzZXFsb2dvKGwsIG5jb2wgPSA0KQ0KZ2dzZXFsb2dvKGwyLCBuY29sID0gNCkNCg0KYGBgDQo=