ALL IMPORTED FILES MUST BE IN THE SAME DIRECTORY AS THIS SCRIPT

Load tidyverse package.

library("tidyverse")

Import individual tumour exome somatic variants (“tumour_sample_germline_exome_somatic_caller_file.tsv”) and tumour-only variants (“tumour_sample_germline_paired_exomes_HAP.tsv”- N/A for unpaired somatic tumour data) annotated vcf files as ‘f’ and ‘g’ data objects, alongside linked sample (“ViP Samples & Candidate Genes.txt”) and tumour purity (“Tumour Purity.txt”) files. All files available from the authors on reasonable request.

max.callers = 3
f <- read.table("tumour_sample_germline_exome_somatic_caller_file.tsv", header=TRUE, stringsAsFactors=FALSE, sep="\t", comment.char="", quote="") %>%
  dplyr::rename("Tumour_Sample"="X.Tumour_Sample") %>% 
  mutate(n.callers = setNames(sapply(strsplit(unique(Identified), "-"), 
                                     function(callers){
                                       ifelse("Intersection" %in% callers, max.callers, sum(!grepl("^filter", callers)))
                                       }), unique(Identified))[Identified])
g <- read.delim("tumour_sample_germline_paired_exomes_HAP.tsv", header=TRUE, stringsAsFactors=FALSE, sep="\t", comment.char="") %>% 
  dplyr::rename("Tumour_Sample"="X.Tumour_Sample")
ViP_list <- read.delim("ViP Samples & Candidate Genes.txt", header=TRUE, stringsAsFactors=FALSE, sep="\t", comment.char="") %>% 
  arrange(Sample)
tumour_type <- select(ViP_list,Sample,Tumour.Type) %>% distinct() %>% rename("Normal_Sample"="Sample")
f$Normal_Sample <- as.character(f$Normal_Sample)
g$Normal_Sample <- as.character(g$Normal_Sample)
f <- left_join(f,tumour_type,by="Normal_Sample",copy=FALSE)
g <- left_join(g,tumour_type,by="Normal_Sample",copy=FALSE) %>% filter(CANONICAL%in%"YES")
gene_list <- select(ViP_list,Sample,SYMBOL) %>% filter(Sample%in%(f$Normal_Sample))
genes <- gene_list$SYMBOL
tumour_purity <- read.delim("Tumour Purity.txt", header=TRUE, stringsAsFactors=FALSE, sep="\t", comment.char="") %>%
  select(Tumour_Sample,Purity,No_Mutations)
f <- left_join(f,tumour_purity,by="Tumour_Sample",copy=FALSE)
g <- left_join(g,tumour_purity,by="Tumour_Sample",copy=FALSE)

Import individual tumour genome somatic variants (“tumour_germline_genome_somatic_caller_file.tsv”) annotated vcf file as ‘ff’ data object. All files available from the authors on reasonable request.

ff <- read.table("tumour_germline_genome_somatic_caller_file.tsv", header=TRUE, stringsAsFactors=FALSE, sep="\t", comment.char="", quote="") %>%
  dplyr::rename("Tumour_Sample"="X.Tumour_Sample")
ff$Normal_Sample <- as.character(ff$Normal_Sample)
ff <- left_join(ff,tumour_purity,by="Tumour_Sample",copy=FALSE)

Convert all relevant strings to integers/doubles, and (for somatic exome data only) remove variants that fail QC in one or more somatic pipeline callers or are Consequence 7 (i.e. IMPACT==MODIFIER).

f$QUAL <- as.numeric(f$QUAL) %>% replace_na(0)
f$TUMOUR.PMCAD <- as.numeric(f$TUMOUR.PMCAD) %>% replace_na(0)
f$TUMOUR.PMCDP <- as.numeric(f$TUMOUR.PMCDP) %>% replace_na(0)
f$TUMOUR.PMCFREQ <- as.numeric(f$TUMOUR.PMCFREQ) %>% replace_na(0)
f$NORMAL.PMCAD <- as.numeric(f$NORMAL.PMCAD) %>% replace_na(0)
f$NORMAL.PMCDP <- as.numeric(f$NORMAL.PMCDP) %>% replace_na(0)
f$NORMAL.PMCFREQ <- as.numeric(f$NORMAL.PMCFREQ) %>% replace_na(0)
f$GnomAD_v2.1_non_cancer_AF <- as.numeric(f$GnomAD_v2.1_non_cancer_AF) %>% replace_na(0)
f$GnomAD_v3_AF <- as.numeric(f$GnomAD_v3_AF) %>% replace_na(0)

f0 <- filter(f,!Identified%in%c("FilteredInAll")) %>% 
    filter(n.callers >= 2) %>% # n.callers >= 3 for unpaired somatic tumour data (see Supplementary Figure 1a)
    filter(Consequence_Rank < 7)

g$QUAL <- as.numeric(g$QUAL) %>% replace_na(0)
g$TUMOUR.PMCAD <- as.numeric(g$TUMOUR.PMCAD) %>% replace_na(0)
g$TUMOUR.PMCDP <- as.numeric(g$TUMOUR.PMCDP) %>% replace_na(0)
g$TUMOUR.PMCFREQ <- as.numeric(g$TUMOUR.PMCFREQ) %>% replace_na(0)
g$NORMAL.PMCAD <- as.numeric(g$NORMAL.PMCAD) %>% replace_na(0)
g$NORMAL.PMCDP <- as.numeric(g$NORMAL.PMCDP) %>% replace_na(0)
g$NORMAL.PMCFREQ <- as.numeric(g$NORMAL.PMCFREQ) %>% replace_na(0)
g$GnomAD_v2.1_non_cancer_AF <- as.numeric(g$GnomAD_v2.1_non_cancer_AF) %>% replace_na(0)
g$GnomAD_v3_AF <- as.numeric(g$GnomAD_v3_AF) %>% replace_na(0)

ff$QUAL <- as.numeric(ff$QUAL) %>% replace_na(0)
ff$TUMOUR.PMCAD <- as.numeric(ff$TUMOUR.PMCAD) %>% replace_na(0)
ff$TUMOUR.PMCDP <- as.numeric(ff$TUMOUR.PMCDP) %>% replace_na(0)
ff$TUMOUR.PMCFREQ <- as.numeric(ff$TUMOUR.PMCFREQ) %>% replace_na(0)
ff$NORMAL.PMCAD <- as.numeric(ff$NORMAL.PMCAD) %>% replace_na(0)
ff$NORMAL.PMCDP <- as.numeric(ff$NORMAL.PMCDP) %>% replace_na(0)
ff$NORMAL.PMCFREQ <- as.numeric(ff$NORMAL.PMCFREQ) %>% replace_na(0)
ff$gnomAD_AF <- as.numeric(ff$gnomAD_AF) %>% replace_na(0)

For somatic caller files, list unique, sorted elements in Identified (Somatic Caller QC results, exomes only), Consequence Rank (exomes only), NORMAL.PMCAD (germline alt allele base depth, exomes only), NORMAL.PMCADP (germline total base depth, exomes and genomes) NORMAL.PMCFREQ (germline alt allele read freq, exomes and genomes), GnomAD v2.1/3.0 AF (exomes and genomes) and QUAL score (exomes only).

sort(unique(f0$Identified))
sort(unique(f0$Consequence_Rank))
sort(unique(f0$NORMAL.PMCAD))
sort(unique(f0$NORMAL.PMCDP))
sort(unique(f0$NORMAL.PMCFREQ))
sort(unique(f0$GnomAD_v2.1_non_cancer_AF))
sort(unique(f0$GnomAD_v3_AF))
sort(unique(f0$QUAL))
sort(unique(ff$NORMAL.PMCDP))
sort(unique(ff$NORMAL.PMCFREQ))
sort(unique(ff$gnomAD_AF))

Plot pre-filtering distribution of tumour alt allele read number(TUMOUR.PMCAD, exomes only), depth (TUMOUR.PMCDP, exomes and genomes) and frequency (TUMOUR.PMCFREQ, exomes and genomes).

sort(unique(f0$TUMOUR.PMCAD))
hist(f0$TUMOUR.PMCAD, breaks=2000, main="Distribution of TUMOUR.PMCAD Values", xlab="TUMOUR.PMCAD",xlim=c(0,20))
sort(unique(f0$TUMOUR.PMCDP))
hist(f0$TUMOUR.PMCDP, breaks=4000, main="Distribution of TUMOUR.PMCDP Values", xlab="TUMOUR.PMCDP",xlim=c(0,100))
sort(unique(f0$TUMOUR.PMCFREQ))
hist(f0$TUMOUR.PMCFREQ, breaks=200, main="Distribution of TUMOUR.PMCFREQ Values", xlab="TUMOUR.PMCFREQ",xlim=c(0,1))

sort(unique(ff$TUMOUR.PMCDP))
hist(ff$TUMOUR.PMCDP, breaks=4000, main="Distribution of TUMOUR.PMCDP Values", xlab="TUMOUR.PMCDP",xlim=c(0,100))
sort(unique(ff$TUMOUR.PMCFREQ))
hist(ff$TUMOUR.PMCFREQ, breaks=200, main="Distribution of TUMOUR.PMCFREQ Values", xlab="TUMOUR.PMCFREQ",xlim=c(0,1))

Filter out and remove elements/rows with high number alt allele reads in germline (NORMAL.PMCAD, exomes only), high germline alt allele read freq (NORMAL.PMCFREQ, exomes and genomes), low germline and tumour read depths (NORMAL.PMCDP and TUMOUR.PMCDP, exomes and genomes), low number alt allele reads in tumour (TUMOUR.PMCAD, exomes only), low tumour alt allele read freq (TUMOUR.PMCFREQ, exomes and genomes) and common variants (using GnomAD v2.1/3.0 AFs, exomes and genomes).

f1<-filter(f0, (NORMAL.PMCDP >= 10) & (NORMAL.PMCAD <= 2) & (NORMAL.PMCFREQ < 0.01)) # N/A for unpaired somatic tumour data (see Supplementary Figure 1a)
f2<-filter(f1,TUMOUR.PMCDP >= 20)
f3<-filter(f2,TUMOUR.PMCAD >= 5)
f4<-filter(f3,TUMOUR.PMCFREQ >= (f3$Purity*0.5*(2/3)))
f5<-filter(f4,GnomAD_v2.1_non_cancer_AF <= 0.0001) # GnomAD_v2.1_non_cancer_AF == 0 for unpaired somatic tumour data (see Supplementary Figure 1a)
f6<-filter(f5,GnomAD_v3_AF <= 0.0001) # GnomAD_v3_AF == 0 for unpaired somatic tumour data (see Supplementary Figure 1a)

ff1<-filter(ff,(NORMAL.PMCDP >= 10) & (NORMAL.PMCAD <= 2) & (NORMAL.PMCFREQ < 0.01))
ff2<-filter(ff1,TUMOUR.PMCDP >= 10)
ff3<-filter(ff2,TUMOUR.PMCAD >= 4)
ff4<-filter(ff3,TUMOUR.PMCFREQ >= (ff3$Purity*0.5*(3/4)))
ff5<-filter(ff4,gnomAD_AF == 0) # NB Tumour genome data annotated to GnomAD v3.0 only (see Supplementary Figure 1b)

List remaining unique elements from above fields, and save output (exomes- for ENSEMBL CANONICAL transcripts only, genomes- for MANE SELECT transcripts only).

sort(unique(f2$TUMOUR.PMCAD))
sort(unique(f3$TUMOUR.PMCDP))
sort(unique(f4$TUMOUR.PMCFREQ))
sort(unique(f5$GnomAD_v2.1_non_cancer_AF))
sort(unique(f6$GnomAD_v3_AF))
samples_WES <- unique(f$Tumour_Sample)
for (sample_WES in samples_WES){
  dir.create(sample_WES)
  setwd(sample_WES)
f6 %>% 
  filter(CANONICAL%in%"YES") %>% 
  write_excel_csv(path=paste(sample_WES,"tumour_germline_exome_somatic_caller_file_filtered.csv",sep="_"), na=".",append=FALSE,col_names=TRUE)
}
sort(unique(ff2$TUMOUR.PMCDP))
sort(unique(ff3$TUMOUR.PMCAD))
sort(unique(ff4$TUMOUR.PMCFREQ))
sort(unique(ff5$gnomAD_AF))
samples_WGS <- unique(ff$Tumour_Sample)
for (sample_WGS in samples_WGS){
  dir.create(sample_WGS)
  setwd(sample_WGS)
ff5 %>% 
  filter(MANE_Status!=".") %>% 
  write_excel_csv(path=paste(sample_WGS,"tumour_germline_genome_somatic_caller_file_filtered.csv",sep="_"), na=".",append=FALSE,col_names=TRUE)
}

Plot post-filtering distribution of tumour alt allele read number (TUMOUR.PMCAD, exomes only), depth (TUMOUR.PMCDP/DP, exomes and genomes) and frequency (TUMOUR.PMCFREQ/AF, exomes and genomes).

sort(unique(f6$TUMOUR.PMCAD))
hist(f6$TUMOUR.PMCAD, breaks=2000, main="Distribution of TUMOUR.PMCAD Values", xlab="TUMOUR.PMCAD",xlim=c(0,20))
sort(unique(f6$TUMOUR.PMCDP))
hist(f6$TUMOUR.PMCDP, breaks=4000, main="Distribution of TUMOUR.PMCDP Values", xlab="TUMOUR.PMCDP",xlim=c(0,100))
sort(unique(f6$TUMOUR.PMCFREQ))
hist(f6$TUMOUR.PMCFREQ, breaks=200, main="Distribution of TUMOUR.PMCFREQ Values", xlab="TUMOUR.PMCFREQ",xlim=c(0,1))

sort(unique(ff5$TUMOUR.PMCDP))
hist(ff5$TUMOUR.PMCDP, breaks=4000, main="Distribution of TUMOUR.PMCDP Values", xlab="TUMOUR.PMCDP",xlim=c(0,100))
sort(unique(ff4$TUMOUR.PMCFREQ))
hist(ff5$TUMOUR.PMCFREQ, breaks=200, main="Distribution of TUMOUR.PMCFREQ Values", xlab="TUMOUR.PMCFREQ",xlim=c(0,1))

Extract remaining variants and columns used for mutation signature analysis, and rename sample column.

f7 <- select(f6,c(CHROM,POS,REF,ALT,Variant_Type))
unique(f7$Variant_Type)
f7$Sample <- f6$Tumour_Sample

ff6<-select(ff5,c(CHROM,POS,REF,ALT)) %>% 
    filter(!CHROM%in%"chrY")
ff6$Sample<-sample_WGS

Output mutations for SIGNAL mutation signature analysis, with count of number of variants used and filtering metrics.

mut.ref_f <- f7
mut.ref_f$CHROM<-unlist(lapply(mut.ref_f$CHROM,function(x) paste("chr",x,sep=""))) 
mut.ref_edit_f <- mut.ref_f[,c("Sample","CHROM","POS","REF","ALT")]

for (sample_WES in samples_WES){
  setwd(sample_WES)
  mut.ref2_f<-mut.ref_edit_f[mut.ref_edit_f$Sample==sample_WES,]
  mut.ref2_f<-unique(mut.ref2_f)
  mut.Num_f <- nrow(mut.ref2_f)
  s <- paste("The number of variants in sample", sample_WES, "used for mutation signature analysis is", mut.Num_f)
  t <- paste("The minimum TUMOUR.PMCAD in sample", sample_WES, "used for filtering is", min(unique(f2$TUMOUR.PMCAD)), "and the minimum figure for the variants used for mutation signature analysis is", min(unique(f7$TUMOUR.PMCAD)))
  u <- paste("The minimum TUMOUR.PMCDP in sample", sample_WES, "used for filtering is", min(unique(f3$TUMOUR.PMCDP)), "and the minimum figure for the variants used for mutation signature analysis is", min(unique(f7$TUMOUR.PMCDP)))
  v <- paste("The minimum TUMOUR.PMCFREQ in sample", sample_WES, "used for filtering is", min(unique(f4$TUMOUR.PMCFREQ)), "and the minimum figure for the variants used for mutation signature analysis is", min(unique(f7$TUMOUR.PMCFREQ)))
  w <- paste("The maximuum GnomAD_v2.1_non_cancer_AF in sample", sample_WES, "used for filtering is", max(unique(f5$GnomAD_v2.1_non_cancer_AF)), "and the maximum figure for the variants used for mutation signature analysis is", max(unique(f7$GnomAD_v2.1_non_cancer_AF)))
  x <- paste("The maximum GnomAD_v3_AF in sample", sample_WES, "used for filtering and for the variants used for mutation signature analysis is", max(unique(f7$GnomAD_v3_AF)))
  parameters <- print(c(s,t,u,v,w,x))
  mut.ref3_f = mut.ref2_f[,c(3:5)]
  mut.ref_f.chr = tibble(gsub("[chr]","", mut.ref2_f$CHROM))
  bind_cols(mut.ref_f.chr,mut.ref3_f) %>% write_tsv(path=paste(sample_WES,"unique_mutations_forSignal.tsv",sep="_"),col_names=FALSE)
  write.table(parameters,file=paste(sample_WGS,"_parameters.txt",sep=""),quote=F,row.names = F,sep="\t")
}
mut.ref_ff <- ff6
mut.ref_edit_ff<-mut.ref_ff[,c("Sample","CHROM","POS","REF","ALT")]
for (sample_WGS in samples_WGS){
  setwd(sample_WGS)
  mut.ref2_ff<-mut.ref_edit_ff[mut.ref_edit_ff$Sample==sample_WGS,]
  mut.ref2_ff<-unique(mut.ref2_ff)
  mut.Num_ff <- nrow(mut.ref2_ff)
  s <- paste("The number of variants in sample", sample_WGS, "used for mutation signature analysis is", mut.Num_ff)
  t <- paste("The minimum TUMOUR.PMCAD in sample", sample_WGS, "used for filtering is", min(unique(f2$TUMOUR.PMCAD)), "and the minimum figure for the variants used for mutation signature analysis is", min(unique(f4$TUMOUR.PMCAD)))
  u <- paste("The minimum TUMOUR.PMCDP in sample", sample_WGS, "used for filtering is", min(unique(ff2$TUMOUR.PMCDP)), "and the minimum figure for the variants used for mutation signature analysis is", min(unique(ff2$TUMOUR.PMCDP)))
  v <- paste("The minimum TUMOUR.PMCFREQ in sample", sample_WGS, "used for filtering is", min(unique(ff3$TUMOUR.PMCFREQ)), "and the minimum figure for the variants used for mutation signature analysis is", min(unique(ff3$TUMOUR.PMCFREQ)))
  w <- paste("The maximum GnomAD_v3_AF in sample", sample_WGS, "used for filtering and for the variants used for mutation signature analysis is", max(unique(ff4$gnomAD_AF)))
  parameters <- print(c(s,t,u,v,w))
  mut.ref3_ff = mut.ref2_ff[,c(3:5)]
  mut.ref_ff.chr = tibble(gsub("[chr]","", mut.ref2_ff$CHROM))
  bind_cols(mut.ref2_ff$Sample,mut.ref_ff.chr,mut.ref3_ff) %>% write_tsv(path=paste(sample_WGS,"unique_mutations_forSignal.tsv",sep="_"),col_names=FALSE)
  write.table(parameters,file=paste(sample_WGS,"_parameters.txt",sep=""),quote=F,row.names = F,sep="\t")
}

Extract rows with variants in gene(s) of interest pre- and post-filtering (e.g. TP53) from somatic variants file (exomes and genomes), and save output.

HGS_tumour_genes <- c("TP53","BRCA1","BRCA2","PTEN","NF1","RB1","CDK12","CDKN2A")

setwd(sample_WES)
f0_genes <- filter(f0,(SYMBOL%in%HGS_tumour_genes)|
                     (SYMBOL%in%c(genes))) %>% 
  write_excel_csv(path=paste(samples_WES,"tumour_germline_exome_somatic_caller_genes_unfiltered.csv",sep="_"), na=".",append=FALSE,col_names=TRUE)
f6_genes <- filter(f6,(SYMBOL%in%HGS_tumour_genes)|
                     (SYMBOL%in%c(genes))) %>% 
  write_excel_csv(sample,path=paste(samples_WES,"tumour_germline_exome_somatic_caller_genes_filtered.csv",sep="_"), na=".",append=FALSE,col_names=TRUE)
setwd(sample_WGS)
ff_genes <- filter(ff,(SYMBOL%in%HGS_tumour_genes)|
                     (SYMBOL%in%c(genes))) %>% 
  write_excel_csv(path=paste(samples_WGS,"tumour_germline_genome_somatic_caller_genes_unfiltered.csv",sep="_"), na=".",append=FALSE,col_names=TRUE)
ff5_genes <- filter(ff5,(SYMBOL%in%HGS_tumour_genes)|
                     (SYMBOL%in%c(genes))) %>% 
  write_excel_csv(sample_WGS,path=paste(samples_WGS,"tumour_germline_genome_somatic_caller_genes_filtered.csv",sep="_"), na=".",append=FALSE,col_names=TRUE)

Extract equivalent rows with variants in gene(s) of interest (one gene transcript per variant) from tumour-only variants file (exomes only- N/A for unpaired somatic tumour data), and save output.

setwd(sample_WES)
g_genes <- filter(g,(SYMBOL%in%HGS_tumour_genes)|
                     (SYMBOL%in%c(genes))) %>% 
  arrange(Transcript_Index) %>% 
  distinct(CHROM,POS,REF,ALT,.keep_all=TRUE)
write_excel_csv(g_genes,path=paste(sample_WES,"tumour_germline_paired_exomes_HAP_genes.csv",sep="_"), na=".",append=FALSE,col_names=TRUE)
LS0tCnRpdGxlOiAiVHVtb3VyIHNlcXVlbmNpbmcgZGF0YSBmaWx0ZXJpbmcgUiBzY3JpcHQgZm9yICdBc3Nlc3NtZW50IG9mIGNhbmRpZGF0ZSBoaWdoLWdyYWRlIHNlcm91cyBvdmFyaWFuIGNhcmNpbm9tYSBwcmVkaXNwb3NpdGlvbiBnZW5lcyB0aHJvdWdoIGludGVncmF0ZWQgZ2VybWxpbmUgYW5kIHR1bW91ciBzZXF1ZW5jaW5nJyAoU3VicmFtYW5pYW4gZXQgYWwuLCBucGogR2Vub21pYyBNZWRpY2luZSwgMjAyNCkiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCkFMTCBJTVBPUlRFRCBGSUxFUyBNVVNUIEJFIElOIFRIRSBTQU1FIERJUkVDVE9SWSBBUyBUSElTIFNDUklQVAoKTG9hZCB0aWR5dmVyc2UgcGFja2FnZS4KYGBge3J9CmxpYnJhcnkoInRpZHl2ZXJzZSIpCmBgYAoKSW1wb3J0IGluZGl2aWR1YWwgdHVtb3VyIGV4b21lIHNvbWF0aWMgdmFyaWFudHMgKCJ0dW1vdXJfc2FtcGxlX2dlcm1saW5lX2V4b21lX3NvbWF0aWNfY2FsbGVyX2ZpbGUudHN2IikgYW5kIHR1bW91ci1vbmx5IHZhcmlhbnRzICgidHVtb3VyX3NhbXBsZV9nZXJtbGluZV9wYWlyZWRfZXhvbWVzX0hBUC50c3YiLSBOL0EgZm9yIHVucGFpcmVkIHNvbWF0aWMgdHVtb3VyIGRhdGEpIGFubm90YXRlZCB2Y2YgZmlsZXMgYXMgJ2YnIGFuZCAnZycgZGF0YSBvYmplY3RzLCBhbG9uZ3NpZGUgbGlua2VkIHNhbXBsZSAoIlZpUCBTYW1wbGVzICYgQ2FuZGlkYXRlIEdlbmVzLnR4dCIpIGFuZCB0dW1vdXIgcHVyaXR5ICgiVHVtb3VyIFB1cml0eS50eHQiKSBmaWxlcy4gQWxsIGZpbGVzIGF2YWlsYWJsZSBmcm9tIHRoZSBhdXRob3JzIG9uIHJlYXNvbmFibGUgcmVxdWVzdC4KYGBge3J9Cm1heC5jYWxsZXJzID0gMwpmIDwtIHJlYWQudGFibGUoInR1bW91cl9zYW1wbGVfZ2VybWxpbmVfZXhvbWVfc29tYXRpY19jYWxsZXJfZmlsZS50c3YiLCBoZWFkZXI9VFJVRSwgc3RyaW5nc0FzRmFjdG9ycz1GQUxTRSwgc2VwPSJcdCIsIGNvbW1lbnQuY2hhcj0iIiwgcXVvdGU9IiIpICU+JQogIGRwbHlyOjpyZW5hbWUoIlR1bW91cl9TYW1wbGUiPSJYLlR1bW91cl9TYW1wbGUiKSAlPiUgCiAgbXV0YXRlKG4uY2FsbGVycyA9IHNldE5hbWVzKHNhcHBseShzdHJzcGxpdCh1bmlxdWUoSWRlbnRpZmllZCksICItIiksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZnVuY3Rpb24oY2FsbGVycyl7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZSgiSW50ZXJzZWN0aW9uIiAlaW4lIGNhbGxlcnMsIG1heC5jYWxsZXJzLCBzdW0oIWdyZXBsKCJeZmlsdGVyIiwgY2FsbGVycykpKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB9KSwgdW5pcXVlKElkZW50aWZpZWQpKVtJZGVudGlmaWVkXSkKZyA8LSByZWFkLmRlbGltKCJ0dW1vdXJfc2FtcGxlX2dlcm1saW5lX3BhaXJlZF9leG9tZXNfSEFQLnRzdiIsIGhlYWRlcj1UUlVFLCBzdHJpbmdzQXNGYWN0b3JzPUZBTFNFLCBzZXA9Ilx0IiwgY29tbWVudC5jaGFyPSIiKSAlPiUgCiAgZHBseXI6OnJlbmFtZSgiVHVtb3VyX1NhbXBsZSI9IlguVHVtb3VyX1NhbXBsZSIpClZpUF9saXN0IDwtIHJlYWQuZGVsaW0oIlZpUCBTYW1wbGVzICYgQ2FuZGlkYXRlIEdlbmVzLnR4dCIsIGhlYWRlcj1UUlVFLCBzdHJpbmdzQXNGYWN0b3JzPUZBTFNFLCBzZXA9Ilx0IiwgY29tbWVudC5jaGFyPSIiKSAlPiUgCiAgYXJyYW5nZShTYW1wbGUpCnR1bW91cl90eXBlIDwtIHNlbGVjdChWaVBfbGlzdCxTYW1wbGUsVHVtb3VyLlR5cGUpICU+JSBkaXN0aW5jdCgpICU+JSByZW5hbWUoIk5vcm1hbF9TYW1wbGUiPSJTYW1wbGUiKQpmJE5vcm1hbF9TYW1wbGUgPC0gYXMuY2hhcmFjdGVyKGYkTm9ybWFsX1NhbXBsZSkKZyROb3JtYWxfU2FtcGxlIDwtIGFzLmNoYXJhY3RlcihnJE5vcm1hbF9TYW1wbGUpCmYgPC0gbGVmdF9qb2luKGYsdHVtb3VyX3R5cGUsYnk9Ik5vcm1hbF9TYW1wbGUiLGNvcHk9RkFMU0UpCmcgPC0gbGVmdF9qb2luKGcsdHVtb3VyX3R5cGUsYnk9Ik5vcm1hbF9TYW1wbGUiLGNvcHk9RkFMU0UpICU+JSBmaWx0ZXIoQ0FOT05JQ0FMJWluJSJZRVMiKQpnZW5lX2xpc3QgPC0gc2VsZWN0KFZpUF9saXN0LFNhbXBsZSxTWU1CT0wpICU+JSBmaWx0ZXIoU2FtcGxlJWluJShmJE5vcm1hbF9TYW1wbGUpKQpnZW5lcyA8LSBnZW5lX2xpc3QkU1lNQk9MCnR1bW91cl9wdXJpdHkgPC0gcmVhZC5kZWxpbSgiVHVtb3VyIFB1cml0eS50eHQiLCBoZWFkZXI9VFJVRSwgc3RyaW5nc0FzRmFjdG9ycz1GQUxTRSwgc2VwPSJcdCIsIGNvbW1lbnQuY2hhcj0iIikgJT4lCiAgc2VsZWN0KFR1bW91cl9TYW1wbGUsUHVyaXR5LE5vX011dGF0aW9ucykKZiA8LSBsZWZ0X2pvaW4oZix0dW1vdXJfcHVyaXR5LGJ5PSJUdW1vdXJfU2FtcGxlIixjb3B5PUZBTFNFKQpnIDwtIGxlZnRfam9pbihnLHR1bW91cl9wdXJpdHksYnk9IlR1bW91cl9TYW1wbGUiLGNvcHk9RkFMU0UpCmBgYAoKSW1wb3J0IGluZGl2aWR1YWwgdHVtb3VyIGdlbm9tZSBzb21hdGljIHZhcmlhbnRzICgidHVtb3VyX2dlcm1saW5lX2dlbm9tZV9zb21hdGljX2NhbGxlcl9maWxlLnRzdiIpIGFubm90YXRlZCB2Y2YgZmlsZSBhcyAnZmYnIGRhdGEgb2JqZWN0LiBBbGwgZmlsZXMgYXZhaWxhYmxlIGZyb20gdGhlIGF1dGhvcnMgb24gcmVhc29uYWJsZSByZXF1ZXN0LgpgYGB7cn0KZmYgPC0gcmVhZC50YWJsZSgidHVtb3VyX2dlcm1saW5lX2dlbm9tZV9zb21hdGljX2NhbGxlcl9maWxlLnRzdiIsIGhlYWRlcj1UUlVFLCBzdHJpbmdzQXNGYWN0b3JzPUZBTFNFLCBzZXA9Ilx0IiwgY29tbWVudC5jaGFyPSIiLCBxdW90ZT0iIikgJT4lCiAgZHBseXI6OnJlbmFtZSgiVHVtb3VyX1NhbXBsZSI9IlguVHVtb3VyX1NhbXBsZSIpCmZmJE5vcm1hbF9TYW1wbGUgPC0gYXMuY2hhcmFjdGVyKGZmJE5vcm1hbF9TYW1wbGUpCmZmIDwtIGxlZnRfam9pbihmZix0dW1vdXJfcHVyaXR5LGJ5PSJUdW1vdXJfU2FtcGxlIixjb3B5PUZBTFNFKQpgYGAKCkNvbnZlcnQgYWxsIHJlbGV2YW50IHN0cmluZ3MgdG8gaW50ZWdlcnMvZG91YmxlcywgYW5kIChmb3Igc29tYXRpYyBleG9tZSBkYXRhIG9ubHkpIHJlbW92ZSB2YXJpYW50cyB0aGF0IGZhaWwgUUMgaW4gb25lIG9yIG1vcmUgc29tYXRpYyBwaXBlbGluZSBjYWxsZXJzIG9yIGFyZSBDb25zZXF1ZW5jZSA3IChpLmUuIElNUEFDVD09TU9ESUZJRVIpLgpgYGB7cn0KZiRRVUFMIDwtIGFzLm51bWVyaWMoZiRRVUFMKSAlPiUgcmVwbGFjZV9uYSgwKQpmJFRVTU9VUi5QTUNBRCA8LSBhcy5udW1lcmljKGYkVFVNT1VSLlBNQ0FEKSAlPiUgcmVwbGFjZV9uYSgwKQpmJFRVTU9VUi5QTUNEUCA8LSBhcy5udW1lcmljKGYkVFVNT1VSLlBNQ0RQKSAlPiUgcmVwbGFjZV9uYSgwKQpmJFRVTU9VUi5QTUNGUkVRIDwtIGFzLm51bWVyaWMoZiRUVU1PVVIuUE1DRlJFUSkgJT4lIHJlcGxhY2VfbmEoMCkKZiROT1JNQUwuUE1DQUQgPC0gYXMubnVtZXJpYyhmJE5PUk1BTC5QTUNBRCkgJT4lIHJlcGxhY2VfbmEoMCkKZiROT1JNQUwuUE1DRFAgPC0gYXMubnVtZXJpYyhmJE5PUk1BTC5QTUNEUCkgJT4lIHJlcGxhY2VfbmEoMCkKZiROT1JNQUwuUE1DRlJFUSA8LSBhcy5udW1lcmljKGYkTk9STUFMLlBNQ0ZSRVEpICU+JSByZXBsYWNlX25hKDApCmYkR25vbUFEX3YyLjFfbm9uX2NhbmNlcl9BRiA8LSBhcy5udW1lcmljKGYkR25vbUFEX3YyLjFfbm9uX2NhbmNlcl9BRikgJT4lIHJlcGxhY2VfbmEoMCkKZiRHbm9tQURfdjNfQUYgPC0gYXMubnVtZXJpYyhmJEdub21BRF92M19BRikgJT4lIHJlcGxhY2VfbmEoMCkKCmYwIDwtIGZpbHRlcihmLCFJZGVudGlmaWVkJWluJWMoIkZpbHRlcmVkSW5BbGwiKSkgJT4lIAogICAgZmlsdGVyKG4uY2FsbGVycyA+PSAyKSAlPiUgIyBuLmNhbGxlcnMgPj0gMyBmb3IgdW5wYWlyZWQgc29tYXRpYyB0dW1vdXIgZGF0YSAoc2VlIFN1cHBsZW1lbnRhcnkgRmlndXJlIDFhKQogICAgZmlsdGVyKENvbnNlcXVlbmNlX1JhbmsgPCA3KQoKZyRRVUFMIDwtIGFzLm51bWVyaWMoZyRRVUFMKSAlPiUgcmVwbGFjZV9uYSgwKQpnJFRVTU9VUi5QTUNBRCA8LSBhcy5udW1lcmljKGckVFVNT1VSLlBNQ0FEKSAlPiUgcmVwbGFjZV9uYSgwKQpnJFRVTU9VUi5QTUNEUCA8LSBhcy5udW1lcmljKGckVFVNT1VSLlBNQ0RQKSAlPiUgcmVwbGFjZV9uYSgwKQpnJFRVTU9VUi5QTUNGUkVRIDwtIGFzLm51bWVyaWMoZyRUVU1PVVIuUE1DRlJFUSkgJT4lIHJlcGxhY2VfbmEoMCkKZyROT1JNQUwuUE1DQUQgPC0gYXMubnVtZXJpYyhnJE5PUk1BTC5QTUNBRCkgJT4lIHJlcGxhY2VfbmEoMCkKZyROT1JNQUwuUE1DRFAgPC0gYXMubnVtZXJpYyhnJE5PUk1BTC5QTUNEUCkgJT4lIHJlcGxhY2VfbmEoMCkKZyROT1JNQUwuUE1DRlJFUSA8LSBhcy5udW1lcmljKGckTk9STUFMLlBNQ0ZSRVEpICU+JSByZXBsYWNlX25hKDApCmckR25vbUFEX3YyLjFfbm9uX2NhbmNlcl9BRiA8LSBhcy5udW1lcmljKGckR25vbUFEX3YyLjFfbm9uX2NhbmNlcl9BRikgJT4lIHJlcGxhY2VfbmEoMCkKZyRHbm9tQURfdjNfQUYgPC0gYXMubnVtZXJpYyhnJEdub21BRF92M19BRikgJT4lIHJlcGxhY2VfbmEoMCkKCmZmJFFVQUwgPC0gYXMubnVtZXJpYyhmZiRRVUFMKSAlPiUgcmVwbGFjZV9uYSgwKQpmZiRUVU1PVVIuUE1DQUQgPC0gYXMubnVtZXJpYyhmZiRUVU1PVVIuUE1DQUQpICU+JSByZXBsYWNlX25hKDApCmZmJFRVTU9VUi5QTUNEUCA8LSBhcy5udW1lcmljKGZmJFRVTU9VUi5QTUNEUCkgJT4lIHJlcGxhY2VfbmEoMCkKZmYkVFVNT1VSLlBNQ0ZSRVEgPC0gYXMubnVtZXJpYyhmZiRUVU1PVVIuUE1DRlJFUSkgJT4lIHJlcGxhY2VfbmEoMCkKZmYkTk9STUFMLlBNQ0FEIDwtIGFzLm51bWVyaWMoZmYkTk9STUFMLlBNQ0FEKSAlPiUgcmVwbGFjZV9uYSgwKQpmZiROT1JNQUwuUE1DRFAgPC0gYXMubnVtZXJpYyhmZiROT1JNQUwuUE1DRFApICU+JSByZXBsYWNlX25hKDApCmZmJE5PUk1BTC5QTUNGUkVRIDwtIGFzLm51bWVyaWMoZmYkTk9STUFMLlBNQ0ZSRVEpICU+JSByZXBsYWNlX25hKDApCmZmJGdub21BRF9BRiA8LSBhcy5udW1lcmljKGZmJGdub21BRF9BRikgJT4lIHJlcGxhY2VfbmEoMCkKYGBgCgpGb3Igc29tYXRpYyBjYWxsZXIgZmlsZXMsIGxpc3QgdW5pcXVlLCBzb3J0ZWQgZWxlbWVudHMgaW4gSWRlbnRpZmllZCAoU29tYXRpYyBDYWxsZXIgUUMgcmVzdWx0cywgZXhvbWVzIG9ubHkpLCBDb25zZXF1ZW5jZSBSYW5rIChleG9tZXMgb25seSksIE5PUk1BTC5QTUNBRCAoZ2VybWxpbmUgYWx0IGFsbGVsZSBiYXNlIGRlcHRoLCBleG9tZXMgb25seSksIE5PUk1BTC5QTUNBRFAgKGdlcm1saW5lIHRvdGFsIGJhc2UgZGVwdGgsIGV4b21lcyBhbmQgZ2Vub21lcykgTk9STUFMLlBNQ0ZSRVEgKGdlcm1saW5lIGFsdCBhbGxlbGUgcmVhZCBmcmVxLCBleG9tZXMgYW5kIGdlbm9tZXMpLCBHbm9tQUQgdjIuMS8zLjAgQUYgKGV4b21lcyBhbmQgZ2Vub21lcykgYW5kIFFVQUwgc2NvcmUgKGV4b21lcyBvbmx5KS4KYGBge3J9CnNvcnQodW5pcXVlKGYwJElkZW50aWZpZWQpKQpgYGAKYGBge3J9CnNvcnQodW5pcXVlKGYwJENvbnNlcXVlbmNlX1JhbmspKQpgYGAKYGBge3J9CnNvcnQodW5pcXVlKGYwJE5PUk1BTC5QTUNBRCkpCmBgYApgYGB7cn0Kc29ydCh1bmlxdWUoZjAkTk9STUFMLlBNQ0RQKSkKYGBgCmBgYHtyfQpzb3J0KHVuaXF1ZShmMCROT1JNQUwuUE1DRlJFUSkpCmBgYApgYGB7cn0Kc29ydCh1bmlxdWUoZjAkR25vbUFEX3YyLjFfbm9uX2NhbmNlcl9BRikpCmBgYApgYGB7cn0Kc29ydCh1bmlxdWUoZjAkR25vbUFEX3YzX0FGKSkKYGBgCmBgYHtyfQpzb3J0KHVuaXF1ZShmMCRRVUFMKSkKYGBgCmBgYHtyfQpzb3J0KHVuaXF1ZShmZiROT1JNQUwuUE1DRFApKQpgYGAKYGBge3J9CnNvcnQodW5pcXVlKGZmJE5PUk1BTC5QTUNGUkVRKSkKYGBgCmBgYHtyfQpzb3J0KHVuaXF1ZShmZiRnbm9tQURfQUYpKQpgYGAKClBsb3QgcHJlLWZpbHRlcmluZyBkaXN0cmlidXRpb24gb2YgdHVtb3VyIGFsdCBhbGxlbGUgcmVhZCBudW1iZXIoVFVNT1VSLlBNQ0FELCBleG9tZXMgb25seSksIGRlcHRoIChUVU1PVVIuUE1DRFAsIGV4b21lcyBhbmQgZ2Vub21lcykgYW5kIGZyZXF1ZW5jeSAoVFVNT1VSLlBNQ0ZSRVEsIGV4b21lcyBhbmQgZ2Vub21lcykuCmBgYHtyfQpzb3J0KHVuaXF1ZShmMCRUVU1PVVIuUE1DQUQpKQpoaXN0KGYwJFRVTU9VUi5QTUNBRCwgYnJlYWtzPTIwMDAsIG1haW49IkRpc3RyaWJ1dGlvbiBvZiBUVU1PVVIuUE1DQUQgVmFsdWVzIiwgeGxhYj0iVFVNT1VSLlBNQ0FEIix4bGltPWMoMCwyMCkpCnNvcnQodW5pcXVlKGYwJFRVTU9VUi5QTUNEUCkpCmhpc3QoZjAkVFVNT1VSLlBNQ0RQLCBicmVha3M9NDAwMCwgbWFpbj0iRGlzdHJpYnV0aW9uIG9mIFRVTU9VUi5QTUNEUCBWYWx1ZXMiLCB4bGFiPSJUVU1PVVIuUE1DRFAiLHhsaW09YygwLDEwMCkpCnNvcnQodW5pcXVlKGYwJFRVTU9VUi5QTUNGUkVRKSkKaGlzdChmMCRUVU1PVVIuUE1DRlJFUSwgYnJlYWtzPTIwMCwgbWFpbj0iRGlzdHJpYnV0aW9uIG9mIFRVTU9VUi5QTUNGUkVRIFZhbHVlcyIsIHhsYWI9IlRVTU9VUi5QTUNGUkVRIix4bGltPWMoMCwxKSkKCnNvcnQodW5pcXVlKGZmJFRVTU9VUi5QTUNEUCkpCmhpc3QoZmYkVFVNT1VSLlBNQ0RQLCBicmVha3M9NDAwMCwgbWFpbj0iRGlzdHJpYnV0aW9uIG9mIFRVTU9VUi5QTUNEUCBWYWx1ZXMiLCB4bGFiPSJUVU1PVVIuUE1DRFAiLHhsaW09YygwLDEwMCkpCnNvcnQodW5pcXVlKGZmJFRVTU9VUi5QTUNGUkVRKSkKaGlzdChmZiRUVU1PVVIuUE1DRlJFUSwgYnJlYWtzPTIwMCwgbWFpbj0iRGlzdHJpYnV0aW9uIG9mIFRVTU9VUi5QTUNGUkVRIFZhbHVlcyIsIHhsYWI9IlRVTU9VUi5QTUNGUkVRIix4bGltPWMoMCwxKSkKYGBgCgpGaWx0ZXIgb3V0IGFuZCByZW1vdmUgZWxlbWVudHMvcm93cyB3aXRoIGhpZ2ggbnVtYmVyIGFsdCBhbGxlbGUgcmVhZHMgaW4gZ2VybWxpbmUgKE5PUk1BTC5QTUNBRCwgZXhvbWVzIG9ubHkpLCBoaWdoIGdlcm1saW5lIGFsdCBhbGxlbGUgcmVhZCBmcmVxIChOT1JNQUwuUE1DRlJFUSwgZXhvbWVzIGFuZCBnZW5vbWVzKSwgbG93IGdlcm1saW5lIGFuZCB0dW1vdXIgcmVhZCBkZXB0aHMgKE5PUk1BTC5QTUNEUCBhbmQgVFVNT1VSLlBNQ0RQLCBleG9tZXMgYW5kIGdlbm9tZXMpLCBsb3cgbnVtYmVyIGFsdCBhbGxlbGUgcmVhZHMgaW4gdHVtb3VyIChUVU1PVVIuUE1DQUQsIGV4b21lcyBvbmx5KSwgbG93IHR1bW91ciBhbHQgYWxsZWxlIHJlYWQgZnJlcSAoVFVNT1VSLlBNQ0ZSRVEsIGV4b21lcyBhbmQgZ2Vub21lcykgYW5kIGNvbW1vbiB2YXJpYW50cyAodXNpbmcgR25vbUFEIHYyLjEvMy4wIEFGcywgZXhvbWVzIGFuZCBnZW5vbWVzKS4KYGBge3J9CmYxPC1maWx0ZXIoZjAsIChOT1JNQUwuUE1DRFAgPj0gMTApICYgKE5PUk1BTC5QTUNBRCA8PSAyKSAmIChOT1JNQUwuUE1DRlJFUSA8IDAuMDEpKSAjIE4vQSBmb3IgdW5wYWlyZWQgc29tYXRpYyB0dW1vdXIgZGF0YSAoc2VlIFN1cHBsZW1lbnRhcnkgRmlndXJlIDFhKQpmMjwtZmlsdGVyKGYxLFRVTU9VUi5QTUNEUCA+PSAyMCkKZjM8LWZpbHRlcihmMixUVU1PVVIuUE1DQUQgPj0gNSkKZjQ8LWZpbHRlcihmMyxUVU1PVVIuUE1DRlJFUSA+PSAoZjMkUHVyaXR5KjAuNSooMi8zKSkpCmY1PC1maWx0ZXIoZjQsR25vbUFEX3YyLjFfbm9uX2NhbmNlcl9BRiA8PSAwLjAwMDEpICMgR25vbUFEX3YyLjFfbm9uX2NhbmNlcl9BRiA9PSAwIGZvciB1bnBhaXJlZCBzb21hdGljIHR1bW91ciBkYXRhIChzZWUgU3VwcGxlbWVudGFyeSBGaWd1cmUgMWEpCmY2PC1maWx0ZXIoZjUsR25vbUFEX3YzX0FGIDw9IDAuMDAwMSkgIyBHbm9tQURfdjNfQUYgPT0gMCBmb3IgdW5wYWlyZWQgc29tYXRpYyB0dW1vdXIgZGF0YSAoc2VlIFN1cHBsZW1lbnRhcnkgRmlndXJlIDFhKQoKZmYxPC1maWx0ZXIoZmYsKE5PUk1BTC5QTUNEUCA+PSAxMCkgJiAoTk9STUFMLlBNQ0FEIDw9IDIpICYgKE5PUk1BTC5QTUNGUkVRIDwgMC4wMSkpCmZmMjwtZmlsdGVyKGZmMSxUVU1PVVIuUE1DRFAgPj0gMTApCmZmMzwtZmlsdGVyKGZmMixUVU1PVVIuUE1DQUQgPj0gNCkKZmY0PC1maWx0ZXIoZmYzLFRVTU9VUi5QTUNGUkVRID49IChmZjMkUHVyaXR5KjAuNSooMy80KSkpCmZmNTwtZmlsdGVyKGZmNCxnbm9tQURfQUYgPT0gMCkgIyBOQiBUdW1vdXIgZ2Vub21lIGRhdGEgYW5ub3RhdGVkIHRvIEdub21BRCB2My4wIG9ubHkgKHNlZSBTdXBwbGVtZW50YXJ5IEZpZ3VyZSAxYikKYGBgCgpMaXN0IHJlbWFpbmluZyB1bmlxdWUgZWxlbWVudHMgZnJvbSBhYm92ZSBmaWVsZHMsIGFuZCBzYXZlIG91dHB1dCAoZXhvbWVzLSBmb3IgRU5TRU1CTCBDQU5PTklDQUwgdHJhbnNjcmlwdHMgb25seSwgZ2Vub21lcy0gZm9yIE1BTkUgU0VMRUNUIHRyYW5zY3JpcHRzIG9ubHkpLgpgYGB7cn0Kc29ydCh1bmlxdWUoZjIkVFVNT1VSLlBNQ0FEKSkKYGBgCmBgYHtyfQpzb3J0KHVuaXF1ZShmMyRUVU1PVVIuUE1DRFApKQpgYGAKYGBge3J9CnNvcnQodW5pcXVlKGY0JFRVTU9VUi5QTUNGUkVRKSkKYGBgCmBgYHtyfQpzb3J0KHVuaXF1ZShmNSRHbm9tQURfdjIuMV9ub25fY2FuY2VyX0FGKSkKYGBgCmBgYHtyfQpzb3J0KHVuaXF1ZShmNiRHbm9tQURfdjNfQUYpKQpgYGAKYGBge3J9CnNhbXBsZXNfV0VTIDwtIHVuaXF1ZShmJFR1bW91cl9TYW1wbGUpCmZvciAoc2FtcGxlX1dFUyBpbiBzYW1wbGVzX1dFUyl7CiAgZGlyLmNyZWF0ZShzYW1wbGVfV0VTKQogIHNldHdkKHNhbXBsZV9XRVMpCmY2ICU+JSAKICBmaWx0ZXIoQ0FOT05JQ0FMJWluJSJZRVMiKSAlPiUgCiAgd3JpdGVfZXhjZWxfY3N2KHBhdGg9cGFzdGUoc2FtcGxlX1dFUywidHVtb3VyX2dlcm1saW5lX2V4b21lX3NvbWF0aWNfY2FsbGVyX2ZpbGVfZmlsdGVyZWQuY3N2IixzZXA9Il8iKSwgbmE9Ii4iLGFwcGVuZD1GQUxTRSxjb2xfbmFtZXM9VFJVRSkKfQpgYGAKYGBge3J9CnNvcnQodW5pcXVlKGZmMiRUVU1PVVIuUE1DRFApKQpgYGAKYGBge3J9CnNvcnQodW5pcXVlKGZmMyRUVU1PVVIuUE1DQUQpKQpgYGAKYGBge3J9CnNvcnQodW5pcXVlKGZmNCRUVU1PVVIuUE1DRlJFUSkpCmBgYApgYGB7cn0Kc29ydCh1bmlxdWUoZmY1JGdub21BRF9BRikpCmBgYApgYGB7cn0Kc2FtcGxlc19XR1MgPC0gdW5pcXVlKGZmJFR1bW91cl9TYW1wbGUpCmZvciAoc2FtcGxlX1dHUyBpbiBzYW1wbGVzX1dHUyl7CiAgZGlyLmNyZWF0ZShzYW1wbGVfV0dTKQogIHNldHdkKHNhbXBsZV9XR1MpCmZmNSAlPiUgCiAgZmlsdGVyKE1BTkVfU3RhdHVzIT0iLiIpICU+JSAKICB3cml0ZV9leGNlbF9jc3YocGF0aD1wYXN0ZShzYW1wbGVfV0dTLCJ0dW1vdXJfZ2VybWxpbmVfZ2Vub21lX3NvbWF0aWNfY2FsbGVyX2ZpbGVfZmlsdGVyZWQuY3N2IixzZXA9Il8iKSwgbmE9Ii4iLGFwcGVuZD1GQUxTRSxjb2xfbmFtZXM9VFJVRSkKfQpgYGAKClBsb3QgcG9zdC1maWx0ZXJpbmcgZGlzdHJpYnV0aW9uIG9mIHR1bW91ciBhbHQgYWxsZWxlIHJlYWQgbnVtYmVyIChUVU1PVVIuUE1DQUQsIGV4b21lcyBvbmx5KSwgZGVwdGggKFRVTU9VUi5QTUNEUC9EUCwgZXhvbWVzIGFuZCBnZW5vbWVzKSBhbmQgZnJlcXVlbmN5IChUVU1PVVIuUE1DRlJFUS9BRiwgZXhvbWVzIGFuZCBnZW5vbWVzKS4KYGBge3J9CnNvcnQodW5pcXVlKGY2JFRVTU9VUi5QTUNBRCkpCmhpc3QoZjYkVFVNT1VSLlBNQ0FELCBicmVha3M9MjAwMCwgbWFpbj0iRGlzdHJpYnV0aW9uIG9mIFRVTU9VUi5QTUNBRCBWYWx1ZXMiLCB4bGFiPSJUVU1PVVIuUE1DQUQiLHhsaW09YygwLDIwKSkKc29ydCh1bmlxdWUoZjYkVFVNT1VSLlBNQ0RQKSkKaGlzdChmNiRUVU1PVVIuUE1DRFAsIGJyZWFrcz00MDAwLCBtYWluPSJEaXN0cmlidXRpb24gb2YgVFVNT1VSLlBNQ0RQIFZhbHVlcyIsIHhsYWI9IlRVTU9VUi5QTUNEUCIseGxpbT1jKDAsMTAwKSkKc29ydCh1bmlxdWUoZjYkVFVNT1VSLlBNQ0ZSRVEpKQpoaXN0KGY2JFRVTU9VUi5QTUNGUkVRLCBicmVha3M9MjAwLCBtYWluPSJEaXN0cmlidXRpb24gb2YgVFVNT1VSLlBNQ0ZSRVEgVmFsdWVzIiwgeGxhYj0iVFVNT1VSLlBNQ0ZSRVEiLHhsaW09YygwLDEpKQoKc29ydCh1bmlxdWUoZmY1JFRVTU9VUi5QTUNEUCkpCmhpc3QoZmY1JFRVTU9VUi5QTUNEUCwgYnJlYWtzPTQwMDAsIG1haW49IkRpc3RyaWJ1dGlvbiBvZiBUVU1PVVIuUE1DRFAgVmFsdWVzIiwgeGxhYj0iVFVNT1VSLlBNQ0RQIix4bGltPWMoMCwxMDApKQpzb3J0KHVuaXF1ZShmZjQkVFVNT1VSLlBNQ0ZSRVEpKQpoaXN0KGZmNSRUVU1PVVIuUE1DRlJFUSwgYnJlYWtzPTIwMCwgbWFpbj0iRGlzdHJpYnV0aW9uIG9mIFRVTU9VUi5QTUNGUkVRIFZhbHVlcyIsIHhsYWI9IlRVTU9VUi5QTUNGUkVRIix4bGltPWMoMCwxKSkKYGBgCgpFeHRyYWN0IHJlbWFpbmluZyB2YXJpYW50cyBhbmQgY29sdW1ucyB1c2VkIGZvciBtdXRhdGlvbiBzaWduYXR1cmUgYW5hbHlzaXMsIGFuZCByZW5hbWUgc2FtcGxlIGNvbHVtbi4KYGBge3J9CmY3IDwtIHNlbGVjdChmNixjKENIUk9NLFBPUyxSRUYsQUxULFZhcmlhbnRfVHlwZSkpCnVuaXF1ZShmNyRWYXJpYW50X1R5cGUpCmY3JFNhbXBsZSA8LSBmNiRUdW1vdXJfU2FtcGxlCgpmZjY8LXNlbGVjdChmZjUsYyhDSFJPTSxQT1MsUkVGLEFMVCkpICU+JSAKICAgIGZpbHRlcighQ0hST00laW4lImNoclkiKQpmZjYkU2FtcGxlPC1zYW1wbGVfV0dTCmBgYAoKT3V0cHV0IG11dGF0aW9ucyBmb3IgU0lHTkFMIG11dGF0aW9uIHNpZ25hdHVyZSBhbmFseXNpcywgd2l0aCBjb3VudCBvZiBudW1iZXIgb2YgdmFyaWFudHMgdXNlZCBhbmQgZmlsdGVyaW5nIG1ldHJpY3MuCmBgYHtyfQptdXQucmVmX2YgPC0gZjcKbXV0LnJlZl9mJENIUk9NPC11bmxpc3QobGFwcGx5KG11dC5yZWZfZiRDSFJPTSxmdW5jdGlvbih4KSBwYXN0ZSgiY2hyIix4LHNlcD0iIikpKSAKbXV0LnJlZl9lZGl0X2YgPC0gbXV0LnJlZl9mWyxjKCJTYW1wbGUiLCJDSFJPTSIsIlBPUyIsIlJFRiIsIkFMVCIpXQoKZm9yIChzYW1wbGVfV0VTIGluIHNhbXBsZXNfV0VTKXsKICBzZXR3ZChzYW1wbGVfV0VTKQogIG11dC5yZWYyX2Y8LW11dC5yZWZfZWRpdF9mW211dC5yZWZfZWRpdF9mJFNhbXBsZT09c2FtcGxlX1dFUyxdCiAgbXV0LnJlZjJfZjwtdW5pcXVlKG11dC5yZWYyX2YpCiAgbXV0Lk51bV9mIDwtIG5yb3cobXV0LnJlZjJfZikKICBzIDwtIHBhc3RlKCJUaGUgbnVtYmVyIG9mIHZhcmlhbnRzIGluIHNhbXBsZSIsIHNhbXBsZV9XRVMsICJ1c2VkIGZvciBtdXRhdGlvbiBzaWduYXR1cmUgYW5hbHlzaXMgaXMiLCBtdXQuTnVtX2YpCiAgdCA8LSBwYXN0ZSgiVGhlIG1pbmltdW0gVFVNT1VSLlBNQ0FEIGluIHNhbXBsZSIsIHNhbXBsZV9XRVMsICJ1c2VkIGZvciBmaWx0ZXJpbmcgaXMiLCBtaW4odW5pcXVlKGYyJFRVTU9VUi5QTUNBRCkpLCAiYW5kIHRoZSBtaW5pbXVtIGZpZ3VyZSBmb3IgdGhlIHZhcmlhbnRzIHVzZWQgZm9yIG11dGF0aW9uIHNpZ25hdHVyZSBhbmFseXNpcyBpcyIsIG1pbih1bmlxdWUoZjckVFVNT1VSLlBNQ0FEKSkpCiAgdSA8LSBwYXN0ZSgiVGhlIG1pbmltdW0gVFVNT1VSLlBNQ0RQIGluIHNhbXBsZSIsIHNhbXBsZV9XRVMsICJ1c2VkIGZvciBmaWx0ZXJpbmcgaXMiLCBtaW4odW5pcXVlKGYzJFRVTU9VUi5QTUNEUCkpLCAiYW5kIHRoZSBtaW5pbXVtIGZpZ3VyZSBmb3IgdGhlIHZhcmlhbnRzIHVzZWQgZm9yIG11dGF0aW9uIHNpZ25hdHVyZSBhbmFseXNpcyBpcyIsIG1pbih1bmlxdWUoZjckVFVNT1VSLlBNQ0RQKSkpCiAgdiA8LSBwYXN0ZSgiVGhlIG1pbmltdW0gVFVNT1VSLlBNQ0ZSRVEgaW4gc2FtcGxlIiwgc2FtcGxlX1dFUywgInVzZWQgZm9yIGZpbHRlcmluZyBpcyIsIG1pbih1bmlxdWUoZjQkVFVNT1VSLlBNQ0ZSRVEpKSwgImFuZCB0aGUgbWluaW11bSBmaWd1cmUgZm9yIHRoZSB2YXJpYW50cyB1c2VkIGZvciBtdXRhdGlvbiBzaWduYXR1cmUgYW5hbHlzaXMgaXMiLCBtaW4odW5pcXVlKGY3JFRVTU9VUi5QTUNGUkVRKSkpCiAgdyA8LSBwYXN0ZSgiVGhlIG1heGltdXVtIEdub21BRF92Mi4xX25vbl9jYW5jZXJfQUYgaW4gc2FtcGxlIiwgc2FtcGxlX1dFUywgInVzZWQgZm9yIGZpbHRlcmluZyBpcyIsIG1heCh1bmlxdWUoZjUkR25vbUFEX3YyLjFfbm9uX2NhbmNlcl9BRikpLCAiYW5kIHRoZSBtYXhpbXVtIGZpZ3VyZSBmb3IgdGhlIHZhcmlhbnRzIHVzZWQgZm9yIG11dGF0aW9uIHNpZ25hdHVyZSBhbmFseXNpcyBpcyIsIG1heCh1bmlxdWUoZjckR25vbUFEX3YyLjFfbm9uX2NhbmNlcl9BRikpKQogIHggPC0gcGFzdGUoIlRoZSBtYXhpbXVtIEdub21BRF92M19BRiBpbiBzYW1wbGUiLCBzYW1wbGVfV0VTLCAidXNlZCBmb3IgZmlsdGVyaW5nIGFuZCBmb3IgdGhlIHZhcmlhbnRzIHVzZWQgZm9yIG11dGF0aW9uIHNpZ25hdHVyZSBhbmFseXNpcyBpcyIsIG1heCh1bmlxdWUoZjckR25vbUFEX3YzX0FGKSkpCiAgcGFyYW1ldGVycyA8LSBwcmludChjKHMsdCx1LHYsdyx4KSkKICBtdXQucmVmM19mID0gbXV0LnJlZjJfZlssYygzOjUpXQogIG11dC5yZWZfZi5jaHIgPSB0aWJibGUoZ3N1YigiW2Nocl0iLCIiLCBtdXQucmVmMl9mJENIUk9NKSkKICBiaW5kX2NvbHMobXV0LnJlZl9mLmNocixtdXQucmVmM19mKSAlPiUgd3JpdGVfdHN2KHBhdGg9cGFzdGUoc2FtcGxlX1dFUywidW5pcXVlX211dGF0aW9uc19mb3JTaWduYWwudHN2IixzZXA9Il8iKSxjb2xfbmFtZXM9RkFMU0UpCiAgd3JpdGUudGFibGUocGFyYW1ldGVycyxmaWxlPXBhc3RlKHNhbXBsZV9XR1MsIl9wYXJhbWV0ZXJzLnR4dCIsc2VwPSIiKSxxdW90ZT1GLHJvdy5uYW1lcyA9IEYsc2VwPSJcdCIpCn0KYGBgCmBgYHtyfQptdXQucmVmX2ZmIDwtIGZmNgptdXQucmVmX2VkaXRfZmY8LW11dC5yZWZfZmZbLGMoIlNhbXBsZSIsIkNIUk9NIiwiUE9TIiwiUkVGIiwiQUxUIildCmZvciAoc2FtcGxlX1dHUyBpbiBzYW1wbGVzX1dHUyl7CiAgc2V0d2Qoc2FtcGxlX1dHUykKICBtdXQucmVmMl9mZjwtbXV0LnJlZl9lZGl0X2ZmW211dC5yZWZfZWRpdF9mZiRTYW1wbGU9PXNhbXBsZV9XR1MsXQogIG11dC5yZWYyX2ZmPC11bmlxdWUobXV0LnJlZjJfZmYpCiAgbXV0Lk51bV9mZiA8LSBucm93KG11dC5yZWYyX2ZmKQogIHMgPC0gcGFzdGUoIlRoZSBudW1iZXIgb2YgdmFyaWFudHMgaW4gc2FtcGxlIiwgc2FtcGxlX1dHUywgInVzZWQgZm9yIG11dGF0aW9uIHNpZ25hdHVyZSBhbmFseXNpcyBpcyIsIG11dC5OdW1fZmYpCiAgdCA8LSBwYXN0ZSgiVGhlIG1pbmltdW0gVFVNT1VSLlBNQ0FEIGluIHNhbXBsZSIsIHNhbXBsZV9XR1MsICJ1c2VkIGZvciBmaWx0ZXJpbmcgaXMiLCBtaW4odW5pcXVlKGYyJFRVTU9VUi5QTUNBRCkpLCAiYW5kIHRoZSBtaW5pbXVtIGZpZ3VyZSBmb3IgdGhlIHZhcmlhbnRzIHVzZWQgZm9yIG11dGF0aW9uIHNpZ25hdHVyZSBhbmFseXNpcyBpcyIsIG1pbih1bmlxdWUoZjQkVFVNT1VSLlBNQ0FEKSkpCiAgdSA8LSBwYXN0ZSgiVGhlIG1pbmltdW0gVFVNT1VSLlBNQ0RQIGluIHNhbXBsZSIsIHNhbXBsZV9XR1MsICJ1c2VkIGZvciBmaWx0ZXJpbmcgaXMiLCBtaW4odW5pcXVlKGZmMiRUVU1PVVIuUE1DRFApKSwgImFuZCB0aGUgbWluaW11bSBmaWd1cmUgZm9yIHRoZSB2YXJpYW50cyB1c2VkIGZvciBtdXRhdGlvbiBzaWduYXR1cmUgYW5hbHlzaXMgaXMiLCBtaW4odW5pcXVlKGZmMiRUVU1PVVIuUE1DRFApKSkKICB2IDwtIHBhc3RlKCJUaGUgbWluaW11bSBUVU1PVVIuUE1DRlJFUSBpbiBzYW1wbGUiLCBzYW1wbGVfV0dTLCAidXNlZCBmb3IgZmlsdGVyaW5nIGlzIiwgbWluKHVuaXF1ZShmZjMkVFVNT1VSLlBNQ0ZSRVEpKSwgImFuZCB0aGUgbWluaW11bSBmaWd1cmUgZm9yIHRoZSB2YXJpYW50cyB1c2VkIGZvciBtdXRhdGlvbiBzaWduYXR1cmUgYW5hbHlzaXMgaXMiLCBtaW4odW5pcXVlKGZmMyRUVU1PVVIuUE1DRlJFUSkpKQogIHcgPC0gcGFzdGUoIlRoZSBtYXhpbXVtIEdub21BRF92M19BRiBpbiBzYW1wbGUiLCBzYW1wbGVfV0dTLCAidXNlZCBmb3IgZmlsdGVyaW5nIGFuZCBmb3IgdGhlIHZhcmlhbnRzIHVzZWQgZm9yIG11dGF0aW9uIHNpZ25hdHVyZSBhbmFseXNpcyBpcyIsIG1heCh1bmlxdWUoZmY0JGdub21BRF9BRikpKQogIHBhcmFtZXRlcnMgPC0gcHJpbnQoYyhzLHQsdSx2LHcpKQogIG11dC5yZWYzX2ZmID0gbXV0LnJlZjJfZmZbLGMoMzo1KV0KICBtdXQucmVmX2ZmLmNociA9IHRpYmJsZShnc3ViKCJbY2hyXSIsIiIsIG11dC5yZWYyX2ZmJENIUk9NKSkKICBiaW5kX2NvbHMobXV0LnJlZjJfZmYkU2FtcGxlLG11dC5yZWZfZmYuY2hyLG11dC5yZWYzX2ZmKSAlPiUgd3JpdGVfdHN2KHBhdGg9cGFzdGUoc2FtcGxlX1dHUywidW5pcXVlX211dGF0aW9uc19mb3JTaWduYWwudHN2IixzZXA9Il8iKSxjb2xfbmFtZXM9RkFMU0UpCiAgd3JpdGUudGFibGUocGFyYW1ldGVycyxmaWxlPXBhc3RlKHNhbXBsZV9XR1MsIl9wYXJhbWV0ZXJzLnR4dCIsc2VwPSIiKSxxdW90ZT1GLHJvdy5uYW1lcyA9IEYsc2VwPSJcdCIpCn0KYGBgCgpFeHRyYWN0IHJvd3Mgd2l0aCB2YXJpYW50cyBpbiBnZW5lKHMpIG9mIGludGVyZXN0IHByZS0gYW5kIHBvc3QtZmlsdGVyaW5nIChlLmcuIFRQNTMpIGZyb20gc29tYXRpYyB2YXJpYW50cyBmaWxlIChleG9tZXMgYW5kIGdlbm9tZXMpLCBhbmQgc2F2ZSBvdXRwdXQuCmBgYHtyfQpIR1NfdHVtb3VyX2dlbmVzIDwtIGMoIlRQNTMiLCJCUkNBMSIsIkJSQ0EyIiwiUFRFTiIsIk5GMSIsIlJCMSIsIkNESzEyIiwiQ0RLTjJBIikKCnNldHdkKHNhbXBsZV9XRVMpCmYwX2dlbmVzIDwtIGZpbHRlcihmMCwoU1lNQk9MJWluJUhHU190dW1vdXJfZ2VuZXMpfAogICAgICAgICAgICAgICAgICAgICAoU1lNQk9MJWluJWMoZ2VuZXMpKSkgJT4lIAogIHdyaXRlX2V4Y2VsX2NzdihwYXRoPXBhc3RlKHNhbXBsZXNfV0VTLCJ0dW1vdXJfZ2VybWxpbmVfZXhvbWVfc29tYXRpY19jYWxsZXJfZ2VuZXNfdW5maWx0ZXJlZC5jc3YiLHNlcD0iXyIpLCBuYT0iLiIsYXBwZW5kPUZBTFNFLGNvbF9uYW1lcz1UUlVFKQpmNl9nZW5lcyA8LSBmaWx0ZXIoZjYsKFNZTUJPTCVpbiVIR1NfdHVtb3VyX2dlbmVzKXwKICAgICAgICAgICAgICAgICAgICAgKFNZTUJPTCVpbiVjKGdlbmVzKSkpICU+JSAKICB3cml0ZV9leGNlbF9jc3Yoc2FtcGxlLHBhdGg9cGFzdGUoc2FtcGxlc19XRVMsInR1bW91cl9nZXJtbGluZV9leG9tZV9zb21hdGljX2NhbGxlcl9nZW5lc19maWx0ZXJlZC5jc3YiLHNlcD0iXyIpLCBuYT0iLiIsYXBwZW5kPUZBTFNFLGNvbF9uYW1lcz1UUlVFKQpgYGAKYGBge3J9CnNldHdkKHNhbXBsZV9XR1MpCmZmX2dlbmVzIDwtIGZpbHRlcihmZiwoU1lNQk9MJWluJUhHU190dW1vdXJfZ2VuZXMpfAogICAgICAgICAgICAgICAgICAgICAoU1lNQk9MJWluJWMoZ2VuZXMpKSkgJT4lIAogIHdyaXRlX2V4Y2VsX2NzdihwYXRoPXBhc3RlKHNhbXBsZXNfV0dTLCJ0dW1vdXJfZ2VybWxpbmVfZ2Vub21lX3NvbWF0aWNfY2FsbGVyX2dlbmVzX3VuZmlsdGVyZWQuY3N2IixzZXA9Il8iKSwgbmE9Ii4iLGFwcGVuZD1GQUxTRSxjb2xfbmFtZXM9VFJVRSkKZmY1X2dlbmVzIDwtIGZpbHRlcihmZjUsKFNZTUJPTCVpbiVIR1NfdHVtb3VyX2dlbmVzKXwKICAgICAgICAgICAgICAgICAgICAgKFNZTUJPTCVpbiVjKGdlbmVzKSkpICU+JSAKICB3cml0ZV9leGNlbF9jc3Yoc2FtcGxlX1dHUyxwYXRoPXBhc3RlKHNhbXBsZXNfV0dTLCJ0dW1vdXJfZ2VybWxpbmVfZ2Vub21lX3NvbWF0aWNfY2FsbGVyX2dlbmVzX2ZpbHRlcmVkLmNzdiIsc2VwPSJfIiksIG5hPSIuIixhcHBlbmQ9RkFMU0UsY29sX25hbWVzPVRSVUUpCmBgYAoKRXh0cmFjdCBlcXVpdmFsZW50IHJvd3Mgd2l0aCB2YXJpYW50cyBpbiBnZW5lKHMpIG9mIGludGVyZXN0IChvbmUgZ2VuZSB0cmFuc2NyaXB0IHBlciB2YXJpYW50KSBmcm9tIHR1bW91ci1vbmx5IHZhcmlhbnRzIGZpbGUgKGV4b21lcyBvbmx5LSBOL0EgZm9yIHVucGFpcmVkIHNvbWF0aWMgdHVtb3VyIGRhdGEpLCBhbmQgc2F2ZSBvdXRwdXQuCmBgYHtyfQpzZXR3ZChzYW1wbGVfV0VTKQpnX2dlbmVzIDwtIGZpbHRlcihnLChTWU1CT0wlaW4lSEdTX3R1bW91cl9nZW5lcyl8CiAgICAgICAgICAgICAgICAgICAgIChTWU1CT0wlaW4lYyhnZW5lcykpKSAlPiUgCiAgYXJyYW5nZShUcmFuc2NyaXB0X0luZGV4KSAlPiUgCiAgZGlzdGluY3QoQ0hST00sUE9TLFJFRixBTFQsLmtlZXBfYWxsPVRSVUUpCndyaXRlX2V4Y2VsX2NzdihnX2dlbmVzLHBhdGg9cGFzdGUoc2FtcGxlX1dFUywidHVtb3VyX2dlcm1saW5lX3BhaXJlZF9leG9tZXNfSEFQX2dlbmVzLmNzdiIsc2VwPSJfIiksIG5hPSIuIixhcHBlbmQ9RkFMU0UsY29sX25hbWVzPVRSVUUpCmBgYA==