R Notebook: Provides reproducible analysis for Broad Mutational Scanning in the following manuscript:

Citation: Romanowicz KJ, Resnick C, Hinton SR, Plesa C. Exploring antibiotic resistance in diverse homologs of the dihydrofolate reductase protein family through broad mutational scanning. bioRxiv, 2025.

GitHub Repository: https://github.com/PlesaLab/DHFR

NCBI BioProject: https://www.ncbi.nlm.nih.gov/bioproject/1189478

Experiment

This pipeline processes a library of 1,536 DHFR homologs and their associated mutants, with two-fold redundancy (two codon variants per sequence). Fitness scores are derived from a multiplexed in-vivo assay using a trimethoprim concentration gradient, assessing the ability of these homologs and their mutants to complement functionality in an E. coli knockout strain and their tolerance to trimethoprim treatment. This analysis provides insights into how antibiotic resistance evolves across a range of evolutionary starting points. Sequence data were generated using the Illumina NovaSeq platform with 100 bp paired-end sequencing of amplicons.

Methods overview to achieve a broad-mutational scan for DHFR homologs.
Methods overview to achieve a broad-mutational scan for DHFR homologs.

Packages

The following R packages must be installed prior to loading into the R session. See the Reproducibility tab for a complete list of packages and their versions used in this workflow.

# Load the latest version of python (3.10.14) for downstream use:
library(reticulate)
use_python("/Users/krom/miniforge3/bin/python3")

# Make a vector of required packages
required.packages <- c("ape", "bio3d", "Biostrings", "castor", "cowplot", "devtools", "dplyr", "ggExtra", "ggnewscale", "ggplot2", "ggridges", "ggtree", "ggtreeExtra", "glmnet", "gridExtra","igraph", "knitr", "matrixStats", "patchwork", "pheatmap", "purrr", "pscl", "RColorBrewer", "reshape","reshape2", "ROCR", "seqinr", "scales", "stringr", "stringi", "tidyr", "tidytree", "viridis")

# Load required packages with error handling
loaded.packages <- lapply(required.packages, function(package) {
  if (!require(package, character.only = TRUE)) {
    install.packages(package, dependencies = TRUE)
    if (!require(package, character.only = TRUE)) {
      message("Package ", package, " could not be installed and loaded.")
      return(NULL)
    }
  }
  return(package)
})

# Remove NULL entries from loaded packages
loaded.packages <- loaded.packages[!sapply(loaded.packages, is.null)]
Loaded packages: ape, bio3d, Biostrings, castor, cowplot, devtools, dplyr, ggExtra, ggnewscale, ggplot2, ggridges, ggtree, ggtreeExtra, glmnet, gridExtra, igraph, knitr, matrixStats, patchwork, pheatmap, purrr, pscl, RColorBrewer, reshape, reshape2, ROCR, seqinr, scales, stringr, stringi, tidyr, tidytree, viridis 

Import Data Files

Import MUTANTS files generated from DHFR.2.Counts.RMD relevant for downstream analysis.

# mut_collapse_15_good_filtered
mut_collapse_15_good_filtered <- read.csv("Mutants/mutants_files_formatted/mut_collapse_15_good_filtered.csv", 
                         header = TRUE, stringsAsFactors = FALSE)

BMS Analysis

This section is based on the R file: “R_BMS_all.Lib15.R”. It describes how to generate MSA mapping files for each homolog and map their fitness values by sequential amino acid positions aligned to E. coli as the reference sequence. The end result is a heatmap displaying median fitness values for each amino acid at each position along the protein sequence.

BMS - Complementation

Broad Mutational Scanning (BMS) Analysis Script: This set of scripts facilitates broad mutational scanning analysis where data from many related protein homologs and their mutants is combined and collapsed for further analysis and visualization. This script is based on the approach used in: Plesa C, Sidore AM, Lubock N, Zhang D, Kosuri S. Multiplexed Gene Synthesis in Emulsions for Exploring Protein Functional Landscapes. Science 359, 343–347 (2018), DOI: 10.1126/science.aao5167.

Procedure The analysis can be carried out by running the following scripts, which include python and bash:

  • clustalo Alignment python script based on fasta files generated for each library
  • map.aligned.residues.py Residue mapping from aligned fasta file generated for each library
  • parse_dssp.py Generate RSA and SS information files from the WT E. coli DHFR dssp file
  • score_conservation.py Generate Conservation score information file from the alignment file

Alignment: Use the clustalo executable to align the protein sequences associated with the filtered perfects (designed homologs) with the following criteria: numprunedBCs >= 5, fitD05D03 > -1, ID mutations = 0. This will align the following FASTA file: mutant.collapse.good.5AA.Lib15.fasta for use in BMS analysis.

SKIP THIS ALIGNMENT IF ALREADY RUN.

# May need to enable permissions to run the executable:
#chmod +x ./clustalo
./Scripts/clustalo -i Mutants/mutants_files_formatted/Lib15.mutant.collapse.good.5AA.fasta -o BMS/OUTPUT/Complementation/Lib15.mutant.collapse.good.5AA.tree.aligned.fasta.aln --outfmt=clustal --force

Mapping Residues

Mapping Residues: Use the following map.aligned.residues.py python script to generate csv files for each designed homolog and associated mutant that maps residue positions of each A.A. from alignment fasta:

  • map_aligned_residues.py This will parse the alignments and generate tables of which homolog’s residue corresponds to which position in the alignment table so that co-aligned residues can be determined.
reticulate::repl_python()
# Verify current python version
import sys
print(f"Python version: {sys.version}")
Python version: 3.10.14 | packaged by conda-forge | (main, Mar 20 2024, 12:57:05) [Clang 16.0.6 ]
import time
import csv

##################################
#INPUTS:

base_path = ""
trees_path_prefix = base_path+""

#clustal format alignment file
align_file_in = [trees_path_prefix+"BMS/OUTPUT/Lib15.mutant.collapse.good.5AA.tree.aligned.fasta.aln"] #New aligned FASTA

#number of seqs in each alignment file
num_samples_in_file = [418] #New FASTA (+1 from actual file count)

##################################
#OUTPUTS:

msa_map_out_path = [trees_path_prefix+"BMS/MSA_Comp/"]

This chunk can be SKIPPED if already ran once. Generates .csv files for each homolog in the “mut_collapse_15_good_filtered” collection:

MSA Mapping

Define the directory containing the parse MSA mapping files (.csv) for each perfect homolog (Lib15):

quit
BMS_MSA_directory = "BMS/MSA_Comp"

# AA map + X
BMS_aa_list<-data.frame(aa=c('G','P','A','V','L','I','M','C','F','Y','W','H','K','R','Q','N','E','D','S','T','X'),
                        aanum=c(1:21))

BMS_aa_dim <- length(BMS_aa_list$aa)

# Wildtype E. coli is:
#NP_414590

# AA length of homolog which collapsing on
BMS_ref_len <- 159

# Minimum number of BCs to use a mutant
BMS_min_BCs = 1

# Largest number of mutations to use
#currently limited to 5 due to mutation naming scheme
BMS_max_mutations = 5

# Minimum fitness to include the mutation
BMS_min_fitness <- -1.2 # This lower range includes NP_414590 (WT E. coli DHFR)
# Grab perfects (mut_collapse_15_good_filtered)
BMS_homolog_15_ID_list <- mut_collapse_15_good_filtered %>%
  filter(mutations == 0 & numprunedBCs >= BMS_min_BCs) %>%
  filter(fitD05D03 > BMS_min_fitness & !is.na(fitD05D03)) %>%
  dplyr::select(ID)

write.table(BMS_homolog_15_ID_list, file = paste("BMS/OUTPUT/Complementation/BMS_homolog_15_ID_list_min_fitness_",as.character(BMS_min_fitness),".csv",sep=""), sep = ",", row.names = F, quote=F, col.names = F)
# Count the number of perfects retained after filtering by BMS_min_fitness (fitness > -1.2, which includes NP_414590)
length(BMS_homolog_15_ID_list$ID)
[1] 417
#make sure all of the perfects are in the MSA
BMS_index_of_mut_to_drop = numeric()
missing_files = character()

for (i in 1:length(BMS_homolog_15_ID_list$ID)){
  mutant_current_temp <- BMS_homolog_15_ID_list$ID[i]
  file_path <- paste(BMS_MSA_directory,"/",mutant_current_temp,".csv",sep="")
  
  if(!file.exists(file_path)){
    print(paste("Missing file:", mutant_current_temp))
    BMS_index_of_mut_to_drop[length(BMS_index_of_mut_to_drop)+1] <-
      which(BMS_homolog_15_ID_list$ID==mutant_current_temp)
    missing_files <- c(missing_files, mutant_current_temp)
  }
}

if (length(BMS_index_of_mut_to_drop) > 0){
  BMS_homolog_15_ID_list <- BMS_homolog_15_ID_list[-BMS_index_of_mut_to_drop,]
  print(paste("Total missing files:", length(missing_files)))
  print("Missing files:")
  print(missing_files)
} else {
  print("All files are present in the directory.")
}
[1] "All files are present in the directory."
# Print the number of remaining files
print(paste("Number of files present:", nrow(BMS_homolog_15_ID_list)))
[1] "Number of files present: 417"

Convert BMS_homolog_15_ID_list to a data frame with a column named “ID”

# Convert BMS_homolog_15_ID_list to a data frame with a column named "ID"
BMS_homolog_15_ID_list_df <- data.frame(ID = BMS_homolog_15_ID_list)

BMS Perfects

Make a copy of the mut_collapse_15_good_filtered object filtered by the BMS_homolog_15_ID_list_df (min. fit = -1.2):

BMS_mutants15_temp <- mut_collapse_15_good_filtered %>%
  filter(numprunedBCs >= BMS_min_BCs & !is.na(fitD05D03) & mutations == 0) %>% 
  semi_join(BMS_homolog_15_ID_list_df,by="ID") %>% #filtering join
  ungroup() %>%
  dplyr::rename(IDalign=ID)

Make a new data frame which will keep the mapping info:

BMS_fitness15_map <- data.frame(position=numeric(),
                              aa=character(),
                              mutations=numeric(),
                              fitness=numeric(),
                              posortho=numeric(),
                              ingap=character(),
                              mutID=character(),
                              ID=character())

Load the E coli MSA mapping file:

#load ecoli MSA mapping
BMS_ecoli_map <- read.csv(file=paste(BMS_MSA_directory,"/NP_414590.csv",sep=""),
                          head=TRUE,
                          sep=",")

#E.coli DHFR seq: # Do we need to update this to include the starting "M"?
BMS_ecoli_seq <- "ISLIAALAVDRVIGMENAMPWNLPADLAWFKRNTLNKPVIMGRHTWESIGRPLPGRKNIILSSQPGTDDRVTWVKSVDEAIAACGDVPEIMVIGGGRVYEQFLPKAQKLYLTHIDAEVEGDTHFPDYEPDDWESVFSEFHDADAQNSHSYCFEILERR"

Loop over all perfects in MSA directory with fitness scores for each a.a. position and determine if it maps to an E. coli residue. SKIP THIS CHUNK IF LOOP HAS ALREADY BEEN RUN.

for (i in 1:nrow(BMS_mutants15_temp)) {
  #current homolog:
  mutant_current_temp <- BMS_mutants15_temp$IDalign[i]
  
  #get the MSA mapping:
  mutant_map_temp <- read.csv(file=paste(BMS_MSA_directory,"/",mutant_current_temp,".csv",sep=""), head=TRUE, sep=",")
  
  #load and define the full seq for each homolog:
  BMS_seq_temp <- as.character(BMS_mutants15_temp$seq[i])
  
  #get fitness for this homolog
  BMS_fit_temp <- BMS_mutants15_temp$fitD05D03[i]
  
  #loop over all residues
  for (j in 1:nchar(BMS_seq_temp)) {
    #find the corresponding residue in E.coli using MSA
    BMS_cons_aanum <- mutant_map_temp$msa_aanum[which(mutant_map_temp$orth_aanum == j)]
    
    #does this map to a non-gap residue in E.coli?
    if (BMS_cons_aanum %in% BMS_ecoli_map$msa_aanum) {
      #get the E.coli residue
      e_coli_residue <- BMS_ecoli_map$orth_aanum[which(BMS_ecoli_map$msa_aanum == BMS_cons_aanum)]
      
      #aa at this residue
      BMS_aa_temp <- substr(BMS_seq_temp, j, j)
      
      #add info for this residue to df
      BMS_fitness15_map <- rbind(BMS_fitness15_map,
                               data.frame(position=e_coli_residue,
                                          aa=BMS_aa_temp,
                                          mutations=0,
                                          fitness=BMS_fit_temp,
                                          posortho=j,
                                          ingap="No",
                                          mutID=mutant_current_temp,
                                          ID=mutant_current_temp))
    } else {
      #if it's here it maps to a gap
      BMS_aa_temp <- substr(BMS_seq_temp, j, j)
      BMS_fitness15_map <- rbind(BMS_fitness15_map,
                               data.frame(position=-1,
                                          aa=BMS_aa_temp,
                                          mutations=0,
                                          fitness=BMS_fit_temp,
                                          posortho=j,
                                          ingap="Yes",
                                          mutID=mutant_current_temp,
                                          ID=mutant_current_temp))
    }
  }
}

#Save the `BMS_fitness15_map` object to re-load in future sessions (saves substantial computational time):
write.csv(BMS_fitness15_map, "BMS/OUTPUT/Complementation/BMS_fitness15_complementation_map.csv", row.names = FALSE, quote = FALSE)

Load the BMS_fitness15_map dataset to continue the BMS analysis (if the loop step above was skipped).

SKIP THIS CHUNK IF LOOP ABOVE WAS RUN.

# Update which fitness map to read in Complementation (see loop above)
BMS_fitness15_map <- read.csv("BMS/OUTPUT/Complementation/BMS_fitness15_complementation_map.csv", 
                              header = TRUE, 
                              stringsAsFactors = FALSE, 
                              check.names = FALSE)

Confirm that correct number of unique mutIDs were integrated into the BMS_fitness15_map dataframe (should match count above):

# Count unique mutID in BMS_fitness15_map
length(unique(BMS_fitness15_map$mutID))
[1] 417

Collapse (median) fitness values for each position onto the BMS Fitness Map:

BMS_fitness15_collapsed <- BMS_fitness15_map %>%
  filter(position > 0) %>%
  group_by(position, aa) %>%
  summarise(fitval=median(fitness),
            numpoints=n(),
            stdfit=sd(fitness))
`summarise()` has grouped output by 'position'. You can override using the `.groups` argument.

These matrices have the fitness for each aa at each position:

BMS_matrix15_perfects = matrix(rep(NA, BMS_ref_len*BMS_aa_dim),nrow=BMS_aa_dim,ncol=BMS_ref_len)
BMS_matrix15_perfects_num = matrix(rep(NA, BMS_ref_len*BMS_aa_dim),nrow=BMS_aa_dim,ncol=BMS_ref_len)
BMS_matrix15_perfects_sd = matrix(rep(NA, BMS_ref_len*BMS_aa_dim),nrow=BMS_aa_dim,ncol=BMS_ref_len)

Populate matrix:

for (i in 1:nrow(BMS_fitness15_collapsed)){
  BMS_matrix15_perfects[which(BMS_aa_list$aa==as.character(BMS_fitness15_collapsed$aa[i])),
                      BMS_fitness15_collapsed$position[i]] <- as.numeric(BMS_fitness15_collapsed$fitval[i])
  BMS_matrix15_perfects_num[which(BMS_aa_list$aa==as.character(BMS_fitness15_collapsed$aa[i])),
                          BMS_fitness15_collapsed$position[i]] <- as.numeric(BMS_fitness15_collapsed$numpoints[i])
  BMS_matrix15_perfects_sd[which(BMS_aa_list$aa==as.character(BMS_fitness15_collapsed$aa[i])),
                         BMS_fitness15_collapsed$position[i]] <- as.numeric(BMS_fitness15_collapsed$stdfit[i])
}

Assign AA names and position numbers to matrices:

rownames(BMS_matrix15_perfects)<-BMS_aa_list$aa
colnames(BMS_matrix15_perfects)<-c(1:BMS_ref_len)
rownames(BMS_matrix15_perfects_num)<-BMS_aa_list$aa
colnames(BMS_matrix15_perfects_num)<-c(1:BMS_ref_len)
rownames(BMS_matrix15_perfects_sd)<-BMS_aa_list$aa
colnames(BMS_matrix15_perfects_sd)<-c(1:BMS_ref_len)

BMS_matrix15_perfects_melt <- melt(BMS_matrix15_perfects)
BMS_matrix15_perfects_num_melt <- melt(BMS_matrix15_perfects_num)
BMS_matrix15_perfects_sd_melt <- melt(BMS_matrix15_perfects_sd)

Perfects Plotting

Calculate the minimum and maximum values of BMS_matrix15_perfects_melt prior to plotting:

# Min
min(BMS_matrix15_perfects_melt$value, na.rm=T)
[1] -0.9986179
# Max
max(BMS_matrix15_perfects_melt$value, na.rm=T)
[1] 0.7070916

Plot the fitness data for the perfects:

# Rename columns to "X1" and "X2"
names(BMS_matrix15_perfects_melt)[names(BMS_matrix15_perfects_melt) %in% c("Var1", "Var2")] <- c("X1", "X2")

BMS_matrix15_perfects_melt_plot <- ggplot(data = BMS_matrix15_perfects_melt, aes(x=X2, y=X1, fill=value)) +
  geom_tile()+ labs(x = "Position (aa)", y ="Amino acid",color="") +
  scale_fill_gradient2(low = "blue", high = "red", mid="gold",name="Fitness",na.value="grey", limit = c(-1,1)) +
  theme_minimal() + 
  scale_x_continuous(breaks=seq(0,155,5))

print(BMS_matrix15_perfects_melt_plot)

Plot the coverage data:

# Rename columns to "X1" and "X2"
names(BMS_matrix15_perfects_num_melt)[names(BMS_matrix15_perfects_num_melt) %in% c("Var1", "Var2")] <- c("X1", "X2")

BMS_matrix15_perfects_num_melt_plot <- ggplot(data = BMS_matrix15_perfects_num_melt, aes(x=X2, y=X1, fill=log(value))) +
  geom_tile()+ labs(x = "Position (aa)", y ="Amino acid",color="") +
  scale_fill_gradient(low = "blue", high = "red",name="log(#points)",na.value="grey", limit = c(0,max(BMS_matrix15_perfects_num_melt$value))) +
  theme_minimal() + 
  scale_x_continuous(breaks=seq(0,155,5))

print(BMS_matrix15_perfects_num_melt_plot)

Plot the STD Data:

# Rename columns to "X1" and "X2"
names(BMS_matrix15_perfects_sd_melt)[names(BMS_matrix15_perfects_sd_melt) %in% c("Var1", "Var2")] <- c("X1", "X2")

BMS_matrix15_perfects_sd_melt_plot <- ggplot(data = BMS_matrix15_perfects_sd_melt, aes(x=X2, y=X1, fill=value)) +
  geom_tile()+ labs(x = "Position (aa)", y ="Amino acid",color="") +
  scale_fill_gradient2(low = "blue", high = "red", mid="gold",name="std(Fitness)",na.value="grey", limit = c(0,1.1*max(BMS_matrix15_perfects_sd_melt$value))) +
  theme_minimal() + 
  scale_x_continuous(breaks=seq(0,155,5))

print(BMS_matrix15_perfects_sd_melt_plot)

BMS Mutants

Count the total number of mutants in the mut_collapse_15_good_filtered dataset:

# Count mutants (up to 5 AA) associated with perfects after filtering by minimum fitness (-1)
mut_collapse_15_good_filtered %>%
  filter(mutations > 0 & mutations < 6 & numprunedBCs >= BMS_min_BCs & !is.na(fitD05D03)) %>%
  semi_join(BMS_homolog_15_ID_list,by="ID") %>%
  ungroup() %>%
  dplyr::rename(IDalign=ID) %>%
  nrow(.)
[1] 5419

Make a copy of the BMS_fitness15_map object for adding the mutants:

# Make a copy of the dataframe for mutants
BMS_fitness15_map_all <- BMS_fitness15_map

Loop over mutants at some distance (up to 5 aa distance):

for (BMS_cur_mut_num in 1:BMS_max_mutations){
  
  #grab the mutants (remove any mutants with "NA" fitness value for fitD05D03)
  BMS_mutants15_temp <- mut_collapse_15_good_filtered %>%
    filter(mutations > 0 & mutations < 6 & numprunedBCs >= BMS_min_BCs & !is.na(fitD05D03)) %>%
    filter(mutations == BMS_cur_mut_num) %>%
    semi_join(BMS_homolog_15_ID_list,by="ID") %>% #filtering join
    ungroup() %>%
    dplyr::rename(IDalign=ID)
  
  #initialize new df
  BMS_fitness15_map1 <- data.frame(position=numeric(),
                                 aa=character(),
                                 mutations=numeric(),
                                 fitness=numeric(),
                                 posortho=numeric(),
                                 ingap=character(),
                                 mutID=character(),
                                 ID=character())
  
  #loop over mutants
  for (i in 1:nrow(BMS_mutants15_temp)){
    
    #mutant base name
    mutant_current_temp <- BMS_mutants15_temp$IDalign[i]
    
    #length of the name
    name_size = nchar(paste(mutant_current_temp,"_",sep=""))
    
    #residue mappings
    mutant_map_temp <- read.csv(file=paste(BMS_MSA_directory,"/",mutant_current_temp,".csv",sep=""),head=TRUE,sep=",")
    
    #this mutants fitness
    BMS_fit_temp <- BMS_mutants15_temp$fitD05D03[i]
    
    #grab the mut name
    mutations15_names <- as.character(BMS_mutants15_temp$mutID[i])
    
    #grab only the relevant portion of the name
    mutations15_names <- substr(mutations15_names, name_size+1, nchar(mutations15_names))
    
    ## split mutation string at non-digits
    s <- strsplit(mutations15_names, "_")
    
    mut_total_length<- nchar(BMS_mutants15_temp$seq[i])
    
    for (mutnum in 1:BMS_cur_mut_num){
      
      #grab the corresponding mutation string
      mutcurr<-s[[1]][mutnum]
      
      #get the position
      mutpos <- as.numeric(str_extract(mutcurr, "[0-9]+"))
      
      if (mutpos<=mut_total_length){
        #get ending aa
        to_aa <- substr(mutcurr, nchar(mutpos)+2, nchar(mutcurr))
        
        #find the number in the consensus seq
        BMS_cons_aanum <- mutant_map_temp$msa_aanum[which(mutant_map_temp$orth_aanum == mutpos)]
        
        #does this map to a non-gap
        if (BMS_cons_aanum %in% BMS_ecoli_map$msa_aanum){
          
          #the corresponding e.coli residue
          e_coli_residue <- BMS_ecoli_map$orth_aanum[which(BMS_ecoli_map$msa_aanum == BMS_cons_aanum)]
          
          #add this point to the data
          BMS_fitness15_map1 <- rbind(BMS_fitness15_map1,
                                    data.frame(position=e_coli_residue,
                                               aa=to_aa,
                                               mutations=BMS_cur_mut_num,
                                               fitness=BMS_fit_temp,
                                               posortho=mutpos,
                                               ingap="No",
                                               mutID=as.character(BMS_mutants15_temp$mutID[i]),
                                               ID=mutant_current_temp))
          
        } else {
          #if it's here it maps to a gap
          
          #add this point to the data
          BMS_fitness15_map1 <- rbind(BMS_fitness15_map1,
                                    data.frame(position=-1,
                                               aa=to_aa,
                                               mutations=BMS_cur_mut_num,
                                               fitness=BMS_fit_temp,
                                               posortho=mutpos,
                                               ingap="Yes",
                                               mutID=as.character(BMS_mutants15_temp$mutID[i]),
                                               ID=mutant_current_temp))
          
        }
      }
      
    }
    
  }

  #Add these mutants onto the existing data:
  BMS_fitness15_map_all <- rbind(BMS_fitness15_map_all,BMS_fitness15_map1)

  write.table(BMS_fitness15_map1, file = paste("BMS/OUTPUT/Complementation/BMS_fitness15_map_",
                                               as.character(BMS_cur_mut_num),".csv",sep=""), 
            sep = ",", row.names = F,quote=F,col.names = T)
  
  #Calculate all data and stats
  BMS_fitness15_collapsed_all <- BMS_fitness15_map_all %>%
    filter(position > 0) %>%
    group_by(position, aa) %>%
    summarise(fitval=median(fitness),
              numpoints=n(),
              stdfit=sd(fitness))
  
  #these matrices have the fitness/num/sd for each aa at each position:
  BMS_matrix15_perfects_and_1 = matrix(rep(NA, BMS_ref_len*BMS_aa_dim),nrow=BMS_aa_dim,ncol=BMS_ref_len)
  BMS_matrix15_perfects_and_1_num = matrix(rep(NA, BMS_ref_len*BMS_aa_dim),nrow=BMS_aa_dim,ncol=BMS_ref_len)
  BMS_matrix15_perfects_and_1_sd = matrix(rep(NA, BMS_ref_len*BMS_aa_dim),nrow=BMS_aa_dim,ncol=BMS_ref_len)
  
  #populate matrix
  for (i in 1:nrow(BMS_fitness15_collapsed_all)){
    
    BMS_matrix15_perfects_and_1[which(BMS_aa_list$aa==as.character(BMS_fitness15_collapsed_all$aa[i])),BMS_fitness15_collapsed_all$position[i]] <- as.numeric(BMS_fitness15_collapsed_all$fitval[i])
    BMS_matrix15_perfects_and_1_num[which(BMS_aa_list$aa==as.character(BMS_fitness15_collapsed_all$aa[i])),BMS_fitness15_collapsed_all$position[i]] <- as.numeric(BMS_fitness15_collapsed_all$numpoints[i])
    BMS_matrix15_perfects_and_1_sd[which(BMS_aa_list$aa==as.character(BMS_fitness15_collapsed_all$aa[i])),BMS_fitness15_collapsed_all$position[i]] <- as.numeric(BMS_fitness15_collapsed_all$stdfit[i])
  }
  
  rownames(BMS_matrix15_perfects_and_1)<-BMS_aa_list$aa
  colnames(BMS_matrix15_perfects_and_1)<-c(1:BMS_ref_len)
  rownames(BMS_matrix15_perfects_and_1_num)<-BMS_aa_list$aa
  colnames(BMS_matrix15_perfects_and_1_num)<-c(1:BMS_ref_len)
  rownames(BMS_matrix15_perfects_and_1_sd)<-BMS_aa_list$aa
  colnames(BMS_matrix15_perfects_and_1_sd)<-c(1:BMS_ref_len)
  
  BMS_matrix15_perfects_and_1_melt <- melt(BMS_matrix15_perfects_and_1)
  BMS_matrix15_perfects_and_1_num_melt <- melt(BMS_matrix15_perfects_and_1_num)
  BMS_matrix15_perfects_and_1_sd_melt <- melt(BMS_matrix15_perfects_and_1_sd)
  
  # Rename columns to "X1" and "X2"
  names(BMS_matrix15_perfects_and_1_melt)[names(BMS_matrix15_perfects_and_1_melt) %in% c("Var1", "Var2")] <- c("X1", "X2")
  names(BMS_matrix15_perfects_and_1_num_melt)[names(BMS_matrix15_perfects_and_1_num_melt) %in% c("Var1", "Var2")] <- c("X1", "X2")
  names(BMS_matrix15_perfects_and_1_sd_melt)[names(BMS_matrix15_perfects_and_1_sd_melt) %in% c("Var1", "Var2")] <- c("X1", "X2")
  
  #Calculate minimum and maximum fitness values for each mutation level:
  min(BMS_matrix15_perfects_and_1_melt$value, na.rm=T)
  max(BMS_matrix15_perfects_and_1_melt$value, na.rm=T)
  
  #plot the data from these mutants:
  ggplot(data = BMS_matrix15_perfects_and_1_melt, aes(x=X2, y=X1, fill=value)) +
    geom_tile()+ labs(x = "Position (aa)", y ="Amino acid",color="") +
    scale_fill_gradient2(low = "blue", high = "red", mid="gold",name="Fitness",na.value="grey", limit = c(min(BMS_matrix15_perfects_and_1_sd_melt$value),max(BMS_matrix15_perfects_and_1_sd_melt$value))) +
    theme_minimal() + 
    scale_x_continuous(breaks=seq(0,155,5))
  ggsave(file=paste("BMS/PLOTS/Complementation/BMS_allpos15_min",toString(BMS_min_BCs),"_max",toString(BMS_cur_mut_num),"_fit.pdf",sep=""))
  
  write.table(BMS_matrix15_perfects_and_1_melt, 
              file = paste("BMS/OUTPUT/Complementation/BMS_matrix_perfects_and_", 
                           as.character(BMS_cur_mut_num),"_fit_melt.csv",sep=""), 
            sep = ",", row.names = F,quote=F,col.names = T)
  
  ggplot(data = BMS_matrix15_perfects_and_1_num_melt, aes(x=X2, y=X1, fill=log(value))) +
    geom_tile()+ labs(x = "Position (aa)", y ="Amino acid",color="") +
    scale_fill_gradient(low = "blue", high = "red",name="log(#points)",na.value="grey", 
                        limit = c(0,max(BMS_matrix15_perfects_and_1_num_melt$value))) +
    theme_minimal() + 
    scale_x_continuous(breaks=seq(0,155,5))
  ggsave(file=paste("BMS/PLOTS/Complementation/BMS_allpos15_min",toString(BMS_min_BCs),"_max",toString(BMS_cur_mut_num),"_num.pdf",sep=""))
  
  write.table(BMS_matrix15_perfects_and_1_num_melt, 
              file = paste("BMS/OUTPUT/Complementation/BMS_matrix15_perfects_and_", 
                           as.character(BMS_cur_mut_num),"_num_melt.csv",sep=""), 
            sep = ",", row.names = F,quote=F,col.names = T)
  
  ggplot(data = BMS_matrix15_perfects_and_1_sd_melt, aes(x=X2, y=X1, fill=value)) +
    geom_tile()+ labs(x = "Position (aa)", y ="Amino acid",color="") +
    scale_fill_gradient2(low = "blue", high = "red", mid="gold",name="std(Fitness)",na.value="grey", limit = c(0,1.1*max(BMS_matrix15_perfects_and_1_sd_melt$value))) +
    theme_minimal() + 
    scale_x_continuous(breaks=seq(0,155,5))
  ggsave(file=paste("BMS/PLOTS/Complementation/BMS_allpos15_min",toString(BMS_min_BCs),"_max",toString(BMS_cur_mut_num),"_sd.pdf",sep=""))
  
  write.table(BMS_matrix15_perfects_and_1_sd_melt, 
              file = paste("BMS/OUTPUT/Complementation/BMS_matrix15_perfects_and_", 
                           as.character(BMS_cur_mut_num),"_sd_melt.csv",sep=""), 
            sep = ",", row.names = F,quote=F,col.names = T)
}
`summarise()` has grouped output by 'position'. You can override using the `.groups` argument.Saving 7 x 7 in image

BMS Custom Build

Import the BMS_matrix15_perfects_and_5_melt dataset based on 5 a.a. mutations:

# Import the BMS_matrix15 dataset based on 1 a.a. mutation:
BMS_matrix15_perfects_and_1_melt <- read.csv("BMS/OUTPUT/Complementation/BMS_matrix_perfects_and_5_fit_melt.csv", 
                                             header = TRUE)

Begin by adding a new column for the WT residues:

BMS_matrix15_perfects_and_1_melt$WTcolor <- NA
#X1 is aa, X2 is position

BMS_matrix15_perfects_and_1_melt$aanum <- 0

for (i in 1:nrow(BMS_matrix15_perfects_and_1_melt)){
  
  #find the corresponding residue in E.coli using MSA
  BMS_cons_aanum <- BMS_matrix15_perfects_and_1_melt$X2[i]
  
  #is this the WT E.coli residue?
  if (BMS_matrix15_perfects_and_1_melt$X1[i] == substr(BMS_ecoli_seq, BMS_cons_aanum, BMS_cons_aanum)){
    
    #assign WT color
    BMS_matrix15_perfects_and_1_melt$WTcolor[i] <- "red"
    
  }
  
  BMS_matrix15_perfects_and_1_melt$aanum[i] <- which(BMS_aa_list$aa == BMS_matrix15_perfects_and_1_melt$X1[i])
  
}

Read in the RSA, SS, and Conservation files:

RSA_1H1T <- read.table("BMS/DSSP/4KJK.RSAs.txt", skip = 2, header = FALSE)
SS_1H1T <- read.table("BMS/DSSP/4KJK.SSs.txt", skip = 2, header = FALSE)
cons_1H1T <- read.table("BMS/DSSP/Lib15.JSD.out.txt", skip = 5, header = FALSE)

In the conservation score file, replace values of -1000 with NA to ignore downstream:

# Replace values of -1000 with NA:
cons_1H1T$V2[cons_1H1T$V2 == -1000] <- NA

Add in the RSA, SS, and Conservation scores for plotting:

colnames(cons_1H1T) <- c("pos","cons","col")
cons_1H1T$pos <- cons_1H1T$pos+1

protein_info_1H1T <- RSA_1H1T %>%
  right_join(SS_1H1T, by="V1")

colnames(protein_info_1H1T) <- c("pos","RSA","SS")

protein_info_1H1T$cons <- 0

for (i in 1:nrow(cons_1H1T)){
  #get MSA position: cons_1H1T$pos[i]
  #find if this position exists in the e.coli MSA
  if (cons_1H1T$pos[i] %in% BMS_ecoli_map$msa_aanum){
    #get corresponding residue
    e_coli_residue <- BMS_ecoli_map$orth_aanum[which(BMS_ecoli_map$msa_aanum==cons_1H1T$pos[i])]
    protein_info_1H1T$cons[which(protein_info_1H1T$pos==e_coli_residue)] <- cons_1H1T$cons[i]
  }
}

Merge protein_info_1H1T with the BMS_matrix15_perfects_and_1_melt fitness scores for each a.a. at each position:

protein_info_1H1T <- BMS_matrix15_perfects_and_1_melt %>%
  filter(X1 != "X") %>%
  group_by(X2) %>%
  dplyr::rename(pos=X2) %>%
  summarise(avgfit=mean(value,na.rm=TRUE),
            numcov=20-sum(is.na(value))) %>%
  right_join(protein_info_1H1T, by="pos")

protein_info_1H1T_melt <- melt(as.data.frame(protein_info_1H1T),
                               id=c("pos"))

protein_info_1H1T_melt$yval <- 0
protein_info_1H1T_melt$yval[which(protein_info_1H1T_melt$variable=="avgfit")] <- 23
protein_info_1H1T_melt$yval[which(protein_info_1H1T_melt$variable=="RSA")] <- 24
protein_info_1H1T_melt$yval[which(protein_info_1H1T_melt$variable=="SS")] <- 25

BMS_matrix15_perfects_and_1_melt$aanum[which(BMS_matrix15_perfects_and_1_melt$X1=="X")] <- 21.3

Calculate the min. and max. avgfit values in protein_info_1H1T:

# Minimum avgfit:
min(protein_info_1H1T$avgfit, na.rm = T)
[1] -1.040356
# Maximium avgfit:
max(protein_info_1H1T$avgfit, na.rm = T)
[1] 0.2375597

Generate a fitness values file from the protein_info_1H1T object:

sink("BMS/structures/avg_fit_4KJK_complementation_attribute.defattr")
cat("# BMS data for 4KJK\n")
cat("attribute: percentExposed\n")
cat("match mode: 1-to-1\n")
cat("recipient: residues\n")
for (i in 1:nrow(protein_info_1H1T)){
  cat(paste("\t:",
            as.character(protein_info_1H1T$pos[i]),
            "\t",
            as.character(protein_info_1H1T$avgfit[i]),
            "\n",
            sep=""))
}

sink()

Determine coverage:

(nrow(BMS_matrix15_perfects_and_1_melt %>%
            filter(X1 != "X") %>%
            dplyr::select(value)) -
  sum(is.na(BMS_matrix15_perfects_and_1_melt %>%
              filter(X1 != "X") %>%
              dplyr::select(value))))/nrow(BMS_matrix15_perfects_and_1_melt %>%
                                             filter(X1 != "X") %>%
                                             dplyr::select(value))
[1] 0.7927673
BMS_Fig_yminlim <- -2
BMS_Fig_ymaxlim <- 30
BMS_Fig_xminlim <- -1
BMS_Fig_xmaxlim <- 160

# Read in BMS_fitness15_map_1 with 1 a.a. mutation
BMS_1mut15_del <- read.table("BMS/OUTPUT/Complementation/BMS_fitness15_map_1.csv", skip = 0 , sep=",",header = TRUE )

# Calculate all data and stats
BMS_1mut15_del_collapsed_all <- BMS_1mut15_del %>%
  filter(position > 0) %>%
  group_by(position, aa) %>%
  summarise(fitval=median(fitness),
            numpoints=n(),
            stdfit=sd(fitness))
`summarise()` has grouped output by 'position'. You can override using the `.groups` argument.
# These matrices have the fitness/num/sd for each aa at each position:
BMS_1mut15_del_matrix = matrix(rep(NA, BMS_ref_len*BMS_aa_dim),nrow=BMS_aa_dim,ncol=BMS_ref_len)

# Populate matrix
for (i in 1:nrow(BMS_1mut15_del_collapsed_all)){
  
  BMS_1mut15_del_matrix[which(BMS_aa_list$aa==as.character(BMS_1mut15_del_collapsed_all$aa[i])),BMS_1mut15_del_collapsed_all$position[i]] <- as.numeric(BMS_1mut15_del_collapsed_all$fitval[i])
}

rownames(BMS_1mut15_del_matrix)<-BMS_aa_list$aa
colnames(BMS_1mut15_del_matrix)<-c(1:BMS_ref_len)

BMS_1mut15_del_matrix_melt <- melt(BMS_1mut15_del_matrix)

# Rename columns to "X1" and "X2"
names(BMS_1mut15_del_matrix_melt)[names(BMS_1mut15_del_matrix_melt) %in% c("Var1", "Var2")] <- c("X1", "X2")

BMS_1mut15_del_matrix_melt <- BMS_1mut15_del_matrix_melt %>%
  filter(X1=="X")
BMS_1mut15_del_matrix_melt$WTcolor <- NA
BMS_1mut15_del_matrix_melt$aanum <- 21.3
  
BMS_matrix15_perfects_and_1_melt <- BMS_matrix15_perfects_and_1_melt %>%
  filter(X1!="X")

BMS_matrix15_perfects_and_1_melt <- rbind(BMS_matrix15_perfects_and_1_melt,BMS_1mut15_del_matrix_melt)

BMS_matrix15_perfects_and_1_melt$aanum[which(BMS_matrix15_perfects_and_1_melt$X1=="A")] <- 12
BMS_matrix15_perfects_and_1_melt$aanum[which(BMS_matrix15_perfects_and_1_melt$X1=="C")] <- 10
BMS_matrix15_perfects_and_1_melt$aanum[which(BMS_matrix15_perfects_and_1_melt$X1=="D")] <- 5
BMS_matrix15_perfects_and_1_melt$aanum[which(BMS_matrix15_perfects_and_1_melt$X1=="E")] <- 4
BMS_matrix15_perfects_and_1_melt$aanum[which(BMS_matrix15_perfects_and_1_melt$X1=="F")] <- 19
BMS_matrix15_perfects_and_1_melt$aanum[which(BMS_matrix15_perfects_and_1_melt$X1=="G")] <- 11
BMS_matrix15_perfects_and_1_melt$aanum[which(BMS_matrix15_perfects_and_1_melt$X1=="H")] <- 3
BMS_matrix15_perfects_and_1_melt$aanum[which(BMS_matrix15_perfects_and_1_melt$X1=="I")] <- 15
BMS_matrix15_perfects_and_1_melt$aanum[which(BMS_matrix15_perfects_and_1_melt$X1=="K")] <- 1
BMS_matrix15_perfects_and_1_melt$aanum[which(BMS_matrix15_perfects_and_1_melt$X1=="L")] <- 14
BMS_matrix15_perfects_and_1_melt$aanum[which(BMS_matrix15_perfects_and_1_melt$X1=="M")] <- 16
BMS_matrix15_perfects_and_1_melt$aanum[which(BMS_matrix15_perfects_and_1_melt$X1=="N")] <- 6
BMS_matrix15_perfects_and_1_melt$aanum[which(BMS_matrix15_perfects_and_1_melt$X1=="P")] <- 17
BMS_matrix15_perfects_and_1_melt$aanum[which(BMS_matrix15_perfects_and_1_melt$X1=="Q")] <- 7
BMS_matrix15_perfects_and_1_melt$aanum[which(BMS_matrix15_perfects_and_1_melt$X1=="R")] <- 2
BMS_matrix15_perfects_and_1_melt$aanum[which(BMS_matrix15_perfects_and_1_melt$X1=="S")] <- 9
BMS_matrix15_perfects_and_1_melt$aanum[which(BMS_matrix15_perfects_and_1_melt$X1=="T")] <- 8
BMS_matrix15_perfects_and_1_melt$aanum[which(BMS_matrix15_perfects_and_1_melt$X1=="V")] <- 13
BMS_matrix15_perfects_and_1_melt$aanum[which(BMS_matrix15_perfects_and_1_melt$X1=="W")] <- 20
BMS_matrix15_perfects_and_1_melt$aanum[which(BMS_matrix15_perfects_and_1_melt$X1=="Y")] <- 18
BMS_matrix15_perfects_and_1_melt$aanum[which(BMS_matrix15_perfects_and_1_melt$X1=="X")] <- 22.6

BMS_matrix15_perfects_and_1_melt_WT <- BMS_matrix15_perfects_and_1_melt %>%
  filter(WTcolor=="red")

Calculate the min. and max. value in BMS_matrix15_perfects_and_1_melt for plotting below:

# Minimum value:
min(BMS_matrix15_perfects_and_1_melt$value, na.rm = T)
[1] -5.990958
# Maximium value:
max(BMS_matrix15_perfects_and_1_melt$value, na.rm = T)
[1] 1.705215

BMS Custom Plot

Adjust the index position to shift to the right:

BMS_matrix15_perfects_and_1_melt_withM <- BMS_matrix15_perfects_and_1_melt %>%
  filter(X2==1) %>%
  mutate(X2=0, value=NA)

BMS_matrix15_perfects_and_1_melt_fix <- rbind(BMS_matrix15_perfects_and_1_melt_withM, BMS_matrix15_perfects_and_1_melt)

Bring it all together to build a custom BMS plot for COMPLEMENTATION (fitD05D03) for LIBRARY 15:

BMS_plot15 <- ggplot() +
  #BMS matrix
  geom_rect(data=BMS_matrix15_perfects_and_1_melt_fix,aes(xmin=X2+1, xmax=X2+2, ymin=aanum, ymax=aanum+1, fill=value))+
  #WT seq
  geom_rect(data=BMS_matrix15_perfects_and_1_melt_WT,aes(xmin=X2+1, xmax=X2+2, ymin=aanum, ymax=aanum+1), color="black",alpha=0)+
  #avg fit
  geom_rect(data=protein_info_1H1T,aes(xmin=pos+1, xmax=pos+2, ymin=21.3, ymax=22.3,fill=avgfit)) +
  labs(x = "Position (aa)", y ="Amino acid",color="") +
  scale_fill_gradient2(low = "blue", high = "red", mid="gold",name="Fitness",na.value="grey", limit = c(-6,3)) + 
  geom_text(data=BMS_matrix15_perfects_and_1_melt[1:21,], aes(x=-1, y=aanum+0.5, label=X1), size=3.5)+
  geom_text(data=data.frame(pos=seq(0,150,30)),aes(x=pos+0.5,y=0,label=pos))+
  geom_segment(aes(x = 14.5, y = 1, xend = 14.5, yend = 0), colour = "blue")+
  geom_segment(aes(x = 15.5, y = 1, xend = 15.5, yend = 0), colour = "blue")+
  geom_segment(aes(x = 16.5, y = 1, xend = 16.5, yend = 0), colour = "blue")+
  geom_segment(aes(x = 17.5, y = 1, xend = 17.5, yend = 0), colour = "blue")+
  geom_segment(aes(x = 20.5, y = 1, xend = 20.5, yend = 0), colour = "blue")+
  geom_segment(aes(x = 27.5, y = 1, xend = 27.5, yend = 0), colour = "blue")+
  geom_segment(aes(x = 31.5, y = 1, xend = 31.5, yend = 0), colour = "blue")+
  geom_segment(aes(x = 32.5, y = 1, xend = 32.5, yend = 0), colour = "blue")+
  geom_segment(aes(x = 46.5, y = 1, xend = 46.5, yend = 0), colour = "blue")+
  geom_segment(aes(x = 52.5, y = 1, xend = 52.5, yend = 0), colour = "blue")+
  geom_segment(aes(x = 57.5, y = 1, xend = 57.5, yend = 0), colour = "blue")+
  geom_segment(aes(x = 94.5, y = 1, xend = 94.5, yend = 0), colour = "blue")+
  geom_segment(aes(x = 100.5, y = 1, xend = 100.5, yend = 0), colour = "blue")+
  theme(panel.grid.major = element_blank(), 
        panel.grid.minor = element_blank(),
        axis.ticks.x=element_blank(),
        axis.ticks.y=element_blank(),
        axis.text.x=element_blank(),
        axis.text.y=element_blank(),
        axis.line=element_blank())+
  xlim(BMS_Fig_xminlim,BMS_Fig_xmaxlim)+
  ylim(BMS_Fig_yminlim,BMS_Fig_ymaxlim)

BMS_plot15

Plot the SS string to go above the custom BMS plot:

protein_info_1H1T_no_loop <- protein_info_1H1T %>%
  filter(SS != "loop")

# Plot SS

SS_plot15 <- ggplot()+
  geom_segment(aes(x = 1, y = 24.5, xend = 160, yend = 24.5), colour = "black")+
  geom_rect(data=protein_info_1H1T_no_loop,aes(xmin=pos, xmax=pos+1, ymin=24, ymax=25, fill=SS))+
  xlim(BMS_Fig_xminlim,BMS_Fig_xmaxlim)+
  ylim(BMS_Fig_yminlim,BMS_Fig_ymaxlim)+
  labs(x = "", y ="",color="") +
  theme(panel.grid.major = element_blank(), 
        panel.grid.minor = element_blank(),
        axis.ticks.x=element_blank(),
        axis.ticks.y=element_blank(),
        axis.text.x=element_blank(),
        axis.text.y=element_blank(),
        axis.line=element_blank())

SS_plot15

Plot the RSA (protein) string:

RSA_plot15 <- ggplot(protein_info_1H1T)+
  geom_rect(aes(xmin=pos, xmax=pos+1, ymin=25.3, ymax=26.3, fill=RSA))+
  xlim(BMS_Fig_xminlim,BMS_Fig_xmaxlim)+
  ylim(BMS_Fig_yminlim,BMS_Fig_ymaxlim)+
  labs(x = "", y ="",color="") +
  scale_fill_gradient(low = "white", high = "red",name="RSA",na.value="grey") +
  theme(panel.grid.major = element_blank(), 
        panel.grid.minor = element_blank(),
        axis.ticks.x=element_blank(),
        axis.ticks.y=element_blank(),
        axis.text.x=element_blank(),
        axis.text.y=element_blank(),
        axis.line=element_blank())

RSA_plot15

Plot the conservation score:

cons_plot15 <- ggplot(protein_info_1H1T)+
  geom_rect(aes(xmin=pos, xmax=pos+1, ymin=26.6, ymax=27.6, fill=cons))+
  xlim(BMS_Fig_xminlim,BMS_Fig_xmaxlim)+
  ylim(BMS_Fig_yminlim,BMS_Fig_ymaxlim)+
  labs(x = "", y ="",color="") +
  scale_fill_gradient(low = "white", high = "red",name="Cons",na.value="grey") +
  theme(panel.grid.major = element_blank(), 
        panel.grid.minor = element_blank(),
        axis.ticks.x=element_blank(),
        axis.ticks.y=element_blank(),
        axis.text.x=element_blank(),
        axis.text.y=element_blank(),
        axis.line=element_blank())

cons_plot15

Summary Plots

Plot average fitness versus position coverage:

coverage_fit_plot15 <- ggplot(protein_info_1H1T,aes(x=avgfit,y=numcov/20*100))+
  geom_smooth(fill="#0072B2")+
  geom_point()+
  labs(x = "Average fitness at position", y ="Position mutational coverage (%)",color="") +
  theme(title = element_text(size = 18),
        axis.text.x = element_text(size = 14),
        axis.text.y = element_text(size = 14),
        axis.title.x = element_text(size = 16),
        axis.title.y = element_text(size = 16),
        panel.background = element_blank()) +
  panel_border(color = "black")

coverage_fit_plot15

Correlation Test for coverage versus average fit:

cor.test(protein_info_1H1T$avgfit,protein_info_1H1T$numcov/20*100)

    Pearson's product-moment correlation

data:  protein_info_1H1T$avgfit and protein_info_1H1T$numcov/20 * 100
t = 6.9751, df = 153, p-value = 8.63e-11
alternative hypothesis: true correlation is not equal to 0
95 percent confidence interval:
 0.3615376 0.6022063
sample estimates:
      cor 
0.4911901 
cor(protein_info_1H1T$avgfit,protein_info_1H1T$numcov, use = "complete.obs")
[1] 0.4911901

Plot Average Fit versus SS:

SS_fit_plot15 <- ggplot(protein_info_1H1T,aes(x=SS,y=avgfit))+
  geom_boxplot(color="black", fill="#0072B2", alpha = 0.8)+
  geom_jitter()+
  labs(x = "Secondary structure", y ="Average fitness at position",color="")+
  theme(title = element_text(size = 18),
        axis.text.x = element_text(size = 14),
        axis.text.y = element_text(size = 14),
        axis.title.x = element_text(size = 16),
        axis.title.y = element_text(size = 16),
        panel.background = element_blank()) +
  panel_border(color = "black")

SS_fit_plot15

Plot Average Fit versus Conservation:

Cons_fit_plot15 <- ggplot(protein_info_1H1T,aes(x=cons,y=avgfit))+
  geom_smooth()+
  geom_point(alpha=0.7)+
  labs(x = "Position Conservation", y ="Average fitness at position",color="")+
  xlim(0.1,0.8)+
  theme(title = element_text(size = 18),
        axis.text.x = element_text(size = 14),
        axis.text.y = element_text(size = 14),
        axis.title.x = element_text(size = 16),
        axis.title.y = element_text(size = 16),
        panel.background = element_blank()) +
  panel_border(color = "black")

Cons_fit_plot15 <- ggExtra::ggMarginal(Cons_fit_plot15,type = "histogram",
                         xparams = list(bins=30),
                         yparams = list(bins=20),
                         col = 'black',
                         fill = '#0072B2', alpha = 0.8) 
`geom_smooth()` using method = 'loess' and formula = 'y ~ x'Warning: Removed 43 rows containing non-finite outside the scale range (`stat_smooth()`).`geom_smooth()` using method = 'loess' and formula = 'y ~ x'Warning: Removed 43 rows containing non-finite outside the scale range (`stat_smooth()`).Warning: Removed 43 rows containing missing values or values outside the scale range (`geom_point()`).
Cons_fit_plot15

Correlation Tests for Cons versus average fit:

cor.test(protein_info_1H1T$cons,protein_info_1H1T$avgfit)

    Pearson's product-moment correlation

data:  protein_info_1H1T$cons and protein_info_1H1T$avgfit
t = -6.2874, df = 111, p-value = 6.539e-09
alternative hypothesis: true correlation is not equal to 0
95 percent confidence interval:
 -0.6368952 -0.3619969
sample estimates:
       cor 
-0.5124578 
cor(protein_info_1H1T$cons,protein_info_1H1T$avgfit, use = "complete.obs")
[1] -0.5124578

Plot Average Fit versus RSA:

RSA_fit_plot15 <- ggplot(protein_info_1H1T,aes(x=RSA,y=avgfit))+
  geom_smooth()+
  geom_point(alpha=0.7)+
  labs(x = "Relative solvent accesibility", y ="Average fitness at position",color="")+
  #ylim(-2.7,2.7)+
  theme(title = element_text(size = 18),
        axis.text.x = element_text(size = 14),
        axis.text.y = element_text(size = 14),
        axis.title.x = element_text(size = 16),
        axis.title.y = element_text(size = 16),
        panel.background = element_blank()) +
  panel_border(color = "black")

RSA_fit_plot15 <- ggExtra::ggMarginal(RSA_fit_plot15,type = "histogram",
                         xparams = list(bins=30),
                         yparams = list(bins=20),
                         col = 'black',
                         fill = '#0072B2', alpha = 0.8) 
`geom_smooth()` using method = 'loess' and formula = 'y ~ x'Warning: Removed 1 row containing non-finite outside the scale range (`stat_smooth()`).`geom_smooth()` using method = 'loess' and formula = 'y ~ x'Warning: Removed 1 row containing non-finite outside the scale range (`stat_smooth()`).Warning: Removed 1 row containing missing values or values outside the scale range (`geom_point()`).
RSA_fit_plot15

Correlation Tests for RSA versus average fit:

cor.test(protein_info_1H1T$RSA,protein_info_1H1T$avgfit)

    Pearson's product-moment correlation

data:  protein_info_1H1T$RSA and protein_info_1H1T$avgfit
t = 3.0691, df = 153, p-value = 0.002541
alternative hypothesis: true correlation is not equal to 0
95 percent confidence interval:
 0.08645328 0.38389317
sample estimates:
      cor 
0.2408193 
cor(protein_info_1H1T$RSA,protein_info_1H1T$avgfit, use = "complete.obs")
[1] 0.2408193

Plot the average fitness for WT E. coli DHFR vs. All remaining DHFR homologs:

WT_fit_plot15 <- ggplot(BMS_matrix15_perfects_and_1_melt,aes(x=WTcolor,y=value))+
  geom_boxplot(color="black", fill="#0072B2", alpha = 0.8)+
  labs(x = "Residue", y ="Average Fitness (LogFC)",color="")+ 
  scale_x_discrete(name ="Residue type",
                   labels=c("Wildtype E. coli \nDHFR Homolog","All Remaining \nDHFR Homologs"))+
  theme(title = element_text(size = 18),
        axis.text.x = element_text(size = 14),
        axis.text.y = element_text(size = 14),
        axis.title.x = element_text(size = 16),
        axis.title.y = element_text(size = 16),
        panel.background = element_blank()) +
  panel_border(color = "black")

WT_fit_plot15

Summary Stats for WT E. coli DHFR homolog and All remaining DHFR homologs:

### WT Summary Stats

# WT Mean Fitness
mean(as.numeric(unlist(BMS_matrix15_perfects_and_1_melt %>% filter(WTcolor=="red") %>% dplyr::select(value))))
[1] -0.01281394
# WT Median Fitness
median(as.numeric(unlist(BMS_matrix15_perfects_and_1_melt %>% filter(WTcolor=="red") %>% dplyr::select(value))))
[1] 0.02925269
# WT SD Fitness
sd(as.numeric(unlist(BMS_matrix15_perfects_and_1_melt %>% filter(WTcolor=="red") %>% dplyr::select(value))))
[1] 0.1978762
### Non-WT Summary Stats

# Non-WT Mean Fitness
mean(as.numeric(unlist(BMS_matrix15_perfects_and_1_melt %>% filter(is.na(WTcolor) & !is.na(value)) %>% dplyr::select(value))))
[1] -0.1707993
# Non-WT Median Fitness
median(as.numeric(unlist(BMS_matrix15_perfects_and_1_melt %>% filter(is.na(WTcolor) & !is.na(value)) %>% dplyr::select(value))))
[1] -0.05846074
# Non-WT SD Fitness
sd(as.numeric(unlist(BMS_matrix15_perfects_and_1_melt %>% filter(is.na(WTcolor) & !is.na(value)) %>% dplyr::select(value))))
[1] 0.5990878

BMS STD Plots

Establish plotting parameters:

# Import the BMS_matrix15 dataset based on 1 a.a. mutation:
BMS_matrix15_perfects_and_1_sd_melt <- read.csv("BMS/OUTPUT/Complementation/BMS_matrix15_perfects_and_1_sd_melt.csv",
                                                header = TRUE)


BMS_matrix15_perfects_and_1_sd_melt <- BMS_matrix15_perfects_and_1_sd_melt %>%
  filter(X1!="X")

BMS_matrix15_perfects_and_1_sd_melt$aanum <- 0

BMS_matrix15_perfects_and_1_sd_melt$aanum[which(BMS_matrix15_perfects_and_1_sd_melt$X1=="A")] <- 12
BMS_matrix15_perfects_and_1_sd_melt$aanum[which(BMS_matrix15_perfects_and_1_sd_melt$X1=="C")] <- 10
BMS_matrix15_perfects_and_1_sd_melt$aanum[which(BMS_matrix15_perfects_and_1_sd_melt$X1=="D")] <- 5
BMS_matrix15_perfects_and_1_sd_melt$aanum[which(BMS_matrix15_perfects_and_1_sd_melt$X1=="E")] <- 4
BMS_matrix15_perfects_and_1_sd_melt$aanum[which(BMS_matrix15_perfects_and_1_sd_melt$X1=="F")] <- 19
BMS_matrix15_perfects_and_1_sd_melt$aanum[which(BMS_matrix15_perfects_and_1_sd_melt$X1=="G")] <- 11
BMS_matrix15_perfects_and_1_sd_melt$aanum[which(BMS_matrix15_perfects_and_1_sd_melt$X1=="H")] <- 3
BMS_matrix15_perfects_and_1_sd_melt$aanum[which(BMS_matrix15_perfects_and_1_sd_melt$X1=="I")] <- 15
BMS_matrix15_perfects_and_1_sd_melt$aanum[which(BMS_matrix15_perfects_and_1_sd_melt$X1=="K")] <- 1
BMS_matrix15_perfects_and_1_sd_melt$aanum[which(BMS_matrix15_perfects_and_1_sd_melt$X1=="L")] <- 14
BMS_matrix15_perfects_and_1_sd_melt$aanum[which(BMS_matrix15_perfects_and_1_sd_melt$X1=="M")] <- 16
BMS_matrix15_perfects_and_1_sd_melt$aanum[which(BMS_matrix15_perfects_and_1_sd_melt$X1=="N")] <- 6
BMS_matrix15_perfects_and_1_sd_melt$aanum[which(BMS_matrix15_perfects_and_1_sd_melt$X1=="P")] <- 17
BMS_matrix15_perfects_and_1_sd_melt$aanum[which(BMS_matrix15_perfects_and_1_sd_melt$X1=="Q")] <- 7
BMS_matrix15_perfects_and_1_sd_melt$aanum[which(BMS_matrix15_perfects_and_1_sd_melt$X1=="R")] <- 2
BMS_matrix15_perfects_and_1_sd_melt$aanum[which(BMS_matrix15_perfects_and_1_sd_melt$X1=="S")] <- 9
BMS_matrix15_perfects_and_1_sd_melt$aanum[which(BMS_matrix15_perfects_and_1_sd_melt$X1=="T")] <- 8
BMS_matrix15_perfects_and_1_sd_melt$aanum[which(BMS_matrix15_perfects_and_1_sd_melt$X1=="V")] <- 13
BMS_matrix15_perfects_and_1_sd_melt$aanum[which(BMS_matrix15_perfects_and_1_sd_melt$X1=="W")] <- 20
BMS_matrix15_perfects_and_1_sd_melt$aanum[which(BMS_matrix15_perfects_and_1_sd_melt$X1=="Y")] <- 18
BMS_matrix15_perfects_and_1_sd_melt$aanum[which(BMS_matrix15_perfects_and_1_sd_melt$X1=="X")] <- 22.6

Plot the STD values of the BMS analysis:

BMS_std_plot15 <- ggplot() +
  #BMS matrix
  geom_rect(data=BMS_matrix15_perfects_and_1_sd_melt,aes(xmin=X2, xmax=X2+1, ymin=aanum, ymax=aanum+1, fill=value))+
  #WT seq
  geom_rect(data=BMS_matrix15_perfects_and_1_melt_WT,aes(xmin=X2, xmax=X2+1, ymin=aanum, ymax=aanum+1), color="green",alpha=0)+
  labs(x = "Position (aa)", y ="Amino acid",color="") +
  scale_fill_gradient2(low = "blue", high = "red", mid="gold",name="Std",na.value="grey", midpoint = 3, limit = c(0,1.1*max(BMS_matrix15_perfects_and_1_sd_melt$value))) +#
  theme(panel.grid.major = element_blank(), 
        panel.grid.minor = element_blank(),
        axis.ticks.x=element_blank(),
        axis.ticks.y=element_blank(),
        axis.text.x=element_blank(),
        axis.text.y=element_blank(),
        axis.line=element_blank())

BMS_std_plot15

Plot epistatis:

BMS_epistasis15 <- BMS_matrix15_perfects_and_1_sd_melt %>%
  dplyr::rename(sd=value) %>%
  inner_join(BMS_matrix15_perfects_and_1_num_melt, by=c("X1","X2")) %>%
  dplyr::rename(num=value) %>%
  filter(num>4)

BMS_epistasis_std_plot15 <- ggplot() +
  #BMS matrix
  geom_rect(data=BMS_epistasis15,aes(xmin=X2, xmax=X2+1, ymin=aanum, ymax=aanum+1, fill=sd))+
  #WT seq
  geom_rect(data=BMS_matrix15_perfects_and_1_melt_WT,aes(xmin=X2, xmax=X2+1, ymin=aanum, ymax=aanum+1), color="green",alpha=0)+
  geom_text(data=BMS_matrix15_perfects_and_1_melt[1:21,], aes(x=-1, y=aanum+0.5, label=X1), size=3.5)+
  geom_text(data=data.frame(pos=seq(0,150,30)),aes(x=pos+0.5,y=0,label=pos))+
  labs(x = "Position (aa)", y ="Amino acid",color="") +
  scale_fill_gradient(low = "gold", high = "red", name="Std",na.value="grey", limit = c(3,1.1*max(BMS_epistasis15$sd))) +#
  theme(panel.grid.major = element_blank(), 
        panel.grid.minor = element_blank(),
        axis.ticks.x=element_blank(),
        axis.ticks.y=element_blank(),
        axis.text.x=element_blank(),
        axis.text.y=element_blank(),
        axis.line=element_blank())

BMS_epistasis_std_plot15

Plot potential epistatic interactions:

BMS_epistasis15_pos <- BMS_epistasis15 %>%
  group_by(X2) %>%
  summarise(numaa=n(),meansd=mean(sd),stdsd=sd(sd))

# ___
ggplot(BMS_epistasis15_pos,aes(x=X2,y=numaa)) +
  geom_point()+
  theme(title = element_text(size = 18),
        axis.text.x = element_text(size = 14),
        axis.text.y = element_text(size = 14),
        axis.title.x = element_text(size = 16),
        axis.title.y = element_text(size = 16),
        panel.background = element_blank()) +
  panel_border(color = "black")


# ___
ggplot(BMS_epistasis15_pos,aes(x=X2,y=meansd)) +
  geom_point()+
  theme(title = element_text(size = 18),
        axis.text.x = element_text(size = 14),
        axis.text.y = element_text(size = 14),
        axis.title.x = element_text(size = 16),
        axis.title.y = element_text(size = 16),
        panel.background = element_blank()) +
  panel_border(color = "black")


# ___
ggplot(BMS_epistasis15_pos,aes(x=X2,y=stdsd)) +
  geom_point()+
  theme(title = element_text(size = 18),
        axis.text.x = element_text(size = 14),
        axis.text.y = element_text(size = 14),
        axis.title.x = element_text(size = 16),
        axis.title.y = element_text(size = 16),
        panel.background = element_blank()) +
  panel_border(color = "black")

Save BMS Info as dataframe (and write to csv file):

BMS_info15 <- right_join(BMS_matrix15_perfects_and_1_melt %>%
                         select(X1,X2,value) %>%
                         dplyr::rename(AA=X1,Pos=X2,avgfitness=value),
                       BMS_matrix15_perfects_and_1_num_melt %>%
                         select(X1,X2,value) %>%
                         dplyr::rename(AA=X1,Pos=X2,numpoints=value),
                       by=c("AA", "Pos"))

BMS_info15 <- right_join(BMS_info15,
                       BMS_matrix15_perfects_and_1_sd_melt%>%
                         select(X1,X2,value) %>%
                         dplyr::rename(AA=X1,Pos=X2,sd=value),
                       by=c("AA", "Pos"))

Save BMS Files

Save the formatted BMS files to import for downstream analyses

# protein_info_1H1T
write.csv(protein_info_1H1T, 
          "BMS/bms_files_formatted/protein_info_1H1T.csv", row.names = FALSE)

# BMS_matrix15_perfects_and_1_melt
write.csv(BMS_matrix15_perfects_and_1_melt, 
          "BMS/bms_files_formatted/BMS_matrix15_perfects_and_1_melt.csv", row.names = FALSE)

# BMS_matrix15_perfects_and_1_num_melt
write.csv(BMS_matrix15_perfects_and_1_num_melt, 
          "BMS/bms_files_formatted/BMS_matrix15_perfects_and_1_num_melt.csv", row.names = FALSE)

Reproducibility

The session information is provided for full reproducibility.

devtools::session_info()
─ Session info ─────────────────────────────────────────────────────────────────────────────────────────────────────────────
 setting  value
 version  R version 4.3.2 (2023-10-31)
 os       macOS 15.2
 system   aarch64, darwin20
 ui       RStudio
 language (EN)
 collate  en_US.UTF-8
 ctype    en_US.UTF-8
 tz       America/Los_Angeles
 date     2025-01-23
 rstudio  2024.09.0+375 Cranberry Hibiscus (desktop)
 pandoc   3.2 @ /Applications/RStudio.app/Contents/Resources/app/quarto/bin/tools/aarch64/ (via rmarkdown)

─ Packages ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────
 package          * version    date (UTC) lib source
 ade4               1.7-22     2023-02-06 [1] CRAN (R 4.3.0)
 ape              * 5.8        2024-04-11 [1] CRAN (R 4.3.1)
 aplot              0.2.2      2023-10-06 [1] CRAN (R 4.3.1)
 bio3d            * 2.4-5      2024-10-29 [1] CRAN (R 4.3.3)
 BiocGenerics     * 0.46.0     2023-06-04 [1] Bioconductor
 Biostrings       * 2.68.1     2023-05-21 [1] Bioconductor
 bitops             1.0-7      2021-04-24 [1] CRAN (R 4.3.0)
 cachem             1.0.8      2023-05-01 [1] CRAN (R 4.3.0)
 castor           * 1.8.0      2024-01-09 [1] CRAN (R 4.3.1)
 cli                3.6.2      2023-12-11 [1] CRAN (R 4.3.1)
 codetools          0.2-20     2024-03-31 [1] CRAN (R 4.3.1)
 colorspace         2.1-0      2023-01-23 [1] CRAN (R 4.3.0)
 cowplot          * 1.1.3      2024-01-22 [1] CRAN (R 4.3.1)
 crayon             1.5.2      2022-09-29 [1] CRAN (R 4.3.0)
 devtools         * 2.4.5      2022-10-11 [1] CRAN (R 4.3.0)
 digest             0.6.35     2024-03-11 [1] CRAN (R 4.3.1)
 dplyr            * 1.1.4      2023-11-17 [1] CRAN (R 4.3.1)
 ellipsis           0.3.2      2021-04-29 [1] CRAN (R 4.3.0)
 evaluate           0.23       2023-11-01 [1] CRAN (R 4.3.1)
 fansi              1.0.6      2023-12-08 [1] CRAN (R 4.3.1)
 farver             2.1.1      2022-07-06 [1] CRAN (R 4.3.0)
 fastmap            1.1.1      2023-02-24 [1] CRAN (R 4.3.0)
 foreach            1.5.2      2022-02-02 [1] CRAN (R 4.3.0)
 fs                 1.6.3      2023-07-20 [1] CRAN (R 4.3.0)
 generics           0.1.3      2022-07-05 [1] CRAN (R 4.3.0)
 GenomeInfoDb     * 1.36.4     2023-10-08 [1] Bioconductor
 GenomeInfoDbData   1.2.10     2023-09-13 [1] Bioconductor
 ggExtra          * 0.10.1     2023-08-21 [1] CRAN (R 4.3.0)
 ggfun              0.1.4      2024-01-19 [1] CRAN (R 4.3.1)
 ggnewscale       * 0.4.10     2024-02-08 [1] CRAN (R 4.3.1)
 ggplot2          * 3.5.1      2024-04-23 [1] CRAN (R 4.3.1)
 ggplotify          0.1.2      2023-08-09 [1] CRAN (R 4.3.0)
 ggridges         * 0.5.6      2024-01-23 [1] CRAN (R 4.3.1)
 ggtree           * 3.8.2      2023-07-30 [1] Bioconductor
 ggtreeExtra      * 1.10.0     2023-04-25 [1] Bioconductor
 glmnet           * 4.1-8      2023-08-22 [1] CRAN (R 4.3.0)
 glue               1.7.0      2024-01-09 [1] CRAN (R 4.3.1)
 gridExtra        * 2.3        2017-09-09 [1] CRAN (R 4.3.0)
 gridGraphics       0.5-1      2020-12-13 [1] CRAN (R 4.3.0)
 gtable             0.3.5      2024-04-22 [1] CRAN (R 4.3.1)
 htmltools          0.5.8.1    2024-04-04 [1] CRAN (R 4.3.1)
 htmlwidgets        1.6.4      2023-12-06 [1] CRAN (R 4.3.1)
 httpuv             1.6.15     2024-03-26 [1] CRAN (R 4.3.1)
 igraph           * 2.0.3      2024-03-13 [1] CRAN (R 4.3.1)
 IRanges          * 2.34.1     2023-07-02 [1] Bioconductor
 iterators          1.0.14     2022-02-05 [1] CRAN (R 4.3.0)
 jsonlite           1.8.8      2023-12-04 [1] CRAN (R 4.3.1)
 knitr            * 1.45       2023-10-30 [1] CRAN (R 4.3.1)
 labeling           0.4.3      2023-08-29 [1] CRAN (R 4.3.0)
 later              1.3.2      2023-12-06 [1] CRAN (R 4.3.1)
 lattice            0.22-6     2024-03-20 [1] CRAN (R 4.3.1)
 lazyeval           0.2.2      2019-03-15 [1] CRAN (R 4.3.0)
 lifecycle          1.0.4      2023-11-07 [1] CRAN (R 4.3.1)
 magrittr           2.0.3      2022-03-30 [1] CRAN (R 4.3.0)
 MASS               7.3-60.0.1 2024-01-13 [1] CRAN (R 4.3.1)
 Matrix           * 1.6-5      2024-01-11 [1] CRAN (R 4.3.1)
 matrixStats      * 1.3.0      2024-04-11 [1] CRAN (R 4.3.1)
 memoise            2.0.1      2021-11-26 [1] CRAN (R 4.3.0)
 mgcv               1.9-1      2023-12-21 [1] CRAN (R 4.3.1)
 mime               0.12       2021-09-28 [1] CRAN (R 4.3.0)
 miniUI             0.1.1.1    2018-05-18 [1] CRAN (R 4.3.0)
 munsell            0.5.1      2024-04-01 [1] CRAN (R 4.3.1)
 naturalsort        0.1.3      2016-08-30 [1] CRAN (R 4.3.0)
 nlme               3.1-164    2023-11-27 [1] CRAN (R 4.3.1)
 patchwork        * 1.2.0      2024-01-08 [1] CRAN (R 4.3.1)
 pheatmap         * 1.0.12     2019-01-04 [1] CRAN (R 4.3.0)
 pillar             1.9.0      2023-03-22 [1] CRAN (R 4.3.0)
 pkgbuild           1.4.4      2024-03-17 [1] CRAN (R 4.3.1)
 pkgconfig          2.0.3      2019-09-22 [1] CRAN (R 4.3.0)
 pkgload            1.3.4      2024-01-16 [1] CRAN (R 4.3.1)
 plyr               1.8.9      2023-10-02 [1] CRAN (R 4.3.1)
 png                0.1-8      2022-11-29 [1] CRAN (R 4.3.0)
 profvis            0.3.8      2023-05-02 [1] CRAN (R 4.3.0)
 promises           1.3.0      2024-04-05 [1] CRAN (R 4.3.1)
 pscl             * 1.5.9      2024-01-31 [1] CRAN (R 4.3.1)
 purrr            * 1.0.2      2023-08-10 [1] CRAN (R 4.3.0)
 R6                 2.5.1      2021-08-19 [1] CRAN (R 4.3.0)
 ragg               1.3.0      2024-03-13 [1] CRAN (R 4.3.1)
 RColorBrewer     * 1.1-3      2022-04-03 [1] CRAN (R 4.3.0)
 Rcpp             * 1.0.13     2024-07-17 [1] CRAN (R 4.3.3)
 RCurl              1.98-1.14  2024-01-09 [1] CRAN (R 4.3.1)
 remotes            2.5.0      2024-03-17 [1] CRAN (R 4.3.1)
 reshape          * 0.8.9      2022-04-12 [1] CRAN (R 4.3.0)
 reshape2         * 1.4.4      2020-04-09 [1] CRAN (R 4.3.0)
 reticulate       * 1.36.1     2024-04-22 [1] CRAN (R 4.3.1)
 rlang              1.1.3      2024-01-10 [1] CRAN (R 4.3.1)
 rmarkdown          2.26       2024-03-05 [1] CRAN (R 4.3.1)
 ROCR             * 1.0-11     2020-05-02 [1] CRAN (R 4.3.0)
 RSpectra           0.16-1     2022-04-24 [1] CRAN (R 4.3.0)
 rstudioapi         0.16.0     2024-03-24 [1] CRAN (R 4.3.1)
 S4Vectors        * 0.38.2     2023-09-24 [1] Bioconductor
 scales           * 1.3.0      2023-11-28 [1] CRAN (R 4.3.1)
 seqinr           * 4.2-36     2023-12-08 [1] CRAN (R 4.3.1)
 sessioninfo        1.2.2      2021-12-06 [1] CRAN (R 4.3.0)
 shape              1.4.6.1    2024-02-23 [1] CRAN (R 4.3.1)
 shiny              1.8.1.1    2024-04-02 [1] CRAN (R 4.3.1)
 stringi          * 1.8.3      2023-12-11 [1] CRAN (R 4.3.1)
 stringr          * 1.5.1      2023-11-14 [1] CRAN (R 4.3.1)
 survival           3.5-8      2024-02-14 [1] CRAN (R 4.3.1)
 systemfonts        1.0.6      2024-03-07 [1] CRAN (R 4.3.1)
 textshaping        0.3.7      2023-10-09 [1] CRAN (R 4.3.1)
 tibble             3.2.1      2023-03-20 [1] CRAN (R 4.3.0)
 tidyr            * 1.3.1      2024-01-24 [1] CRAN (R 4.3.1)
 tidyselect         1.2.1      2024-03-11 [1] CRAN (R 4.3.1)
 tidytree         * 0.4.6      2023-12-12 [1] CRAN (R 4.3.1)
 treeio             1.24.3     2023-07-30 [1] Bioconductor
 urlchecker         1.0.1      2021-11-30 [1] CRAN (R 4.3.0)
 usethis          * 2.2.3      2024-02-19 [1] CRAN (R 4.3.1)
 utf8               1.2.4      2023-10-22 [1] CRAN (R 4.3.1)
 vctrs              0.6.5      2023-12-01 [1] CRAN (R 4.3.1)
 viridis          * 0.6.5      2024-01-29 [1] CRAN (R 4.3.1)
 viridisLite      * 0.4.2      2023-05-02 [1] CRAN (R 4.3.0)
 withr              3.0.0      2024-01-16 [1] CRAN (R 4.3.1)
 xfun               0.43       2024-03-25 [1] CRAN (R 4.3.1)
 xtable             1.8-4      2019-04-21 [1] CRAN (R 4.3.0)
 XVector          * 0.40.0     2023-05-08 [1] Bioconductor
 yaml               2.3.8      2023-12-11 [1] CRAN (R 4.3.1)
 yulab.utils        0.1.4      2024-01-28 [1] CRAN (R 4.3.1)
 zlibbioc           1.46.0     2023-05-08 [1] Bioconductor

 [1] /Library/Frameworks/R.framework/Versions/4.3-arm64/Resources/library

─ Python configuration ─────────────────────────────────────────────────────────────────────────────────────────────────────
 python:         /Users/krom/miniforge3/bin/python3
 libpython:      /Users/krom/miniforge3/lib/libpython3.10.dylib
 pythonhome:     /Users/krom/miniforge3:/Users/krom/miniforge3
 version:        3.10.14 | packaged by conda-forge | (main, Mar 20 2024, 12:51:49) [Clang 16.0.6 ]
 numpy:           [NOT FOUND]
 
 NOTE: Python version was forced by use_python() function

────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
LS0tCnRpdGxlOiAiQnJvYWQgTXV0YXRpb25hbCBTY2FubmluZyBBbmFseXNpcyIKYXV0aG9yOiAnQXV0aG9yczogW0thcmwgSi4gUm9tYW5vd2ljel0oaHR0cHM6Ly9rcm9tYW5vd2ljei5naXRodWIuaW8vKSwgQ2FybWVuIFJlc25pY2ssIFNhbXVlbCBSLiBIaW50b24sIENhbGluIFBsZXNhJwpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIHRoZW1lOiBzcGFjZWxhYgogICAgdG9jOiB5ZXMKICAgIHRvY19kZXB0aDogNQogICAgdG9jX2Zsb2F0OgogICAgICBjb2xsYXBzZWQ6IHllcwogICAgICBzbW9vdGhfc2Nyb2xsOiB5ZXMKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB5ZXMKICAgIHRvY19kZXB0aDogJzUnCiAgICBkZl9wcmludDogcGFnZWQKICBwZGZfZG9jdW1lbnQ6CiAgICB0b2M6IHllcwogICAgdG9jX2RlcHRoOiAnNScKLS0tCgoqKlIgTm90ZWJvb2s6KiogPGZvbnQgY29sb3I9ImdyZWVuIj5Qcm92aWRlcyByZXByb2R1Y2libGUgYW5hbHlzaXMgZm9yICoqQnJvYWQgTXV0YXRpb25hbCBTY2FubmluZyoqIGluIHRoZSBmb2xsb3dpbmcgbWFudXNjcmlwdDo8L2ZvbnQ+CgoqKkNpdGF0aW9uOioqIFJvbWFub3dpY3ogS0osIFJlc25pY2sgQywgSGludG9uIFNSLCBQbGVzYSBDLiBFeHBsb3JpbmcgYW50aWJpb3RpYyByZXNpc3RhbmNlIGluIGRpdmVyc2UgaG9tb2xvZ3Mgb2YgdGhlIGRpaHlkcm9mb2xhdGUgcmVkdWN0YXNlIHByb3RlaW4gZmFtaWx5IHRocm91Z2ggYnJvYWQgbXV0YXRpb25hbCBzY2FubmluZy4gKioqYmlvUnhpdioqKiwgMjAyNS4gW10oKQoKKipHaXRIdWIgUmVwb3NpdG9yeToqKiBbaHR0cHM6Ly9naXRodWIuY29tL1BsZXNhTGFiL0RIRlJdKGh0dHBzOi8vZ2l0aHViLmNvbS9QbGVzYUxhYi9ESEZSKQoKKipOQ0JJIEJpb1Byb2plY3Q6KiogW2h0dHBzOi8vd3d3Lm5jYmkubmxtLm5paC5nb3YvYmlvcHJvamVjdC8xMTg5NDc4XShodHRwczovL3d3dy5uY2JpLm5sbS5uaWguZ292L2Jpb3Byb2plY3QvMTE4OTQ3OCkKCiMgRXhwZXJpbWVudAoKVGhpcyBwaXBlbGluZSBwcm9jZXNzZXMgYSBsaWJyYXJ5IG9mIDEsNTM2IERIRlIgaG9tb2xvZ3MgYW5kIHRoZWlyIGFzc29jaWF0ZWQgbXV0YW50cywgd2l0aCB0d28tZm9sZCByZWR1bmRhbmN5ICh0d28gY29kb24gdmFyaWFudHMgcGVyIHNlcXVlbmNlKS4gRml0bmVzcyBzY29yZXMgYXJlIGRlcml2ZWQgZnJvbSBhIG11bHRpcGxleGVkIGluLXZpdm8gYXNzYXkgdXNpbmcgYSB0cmltZXRob3ByaW0gY29uY2VudHJhdGlvbiBncmFkaWVudCwgYXNzZXNzaW5nIHRoZSBhYmlsaXR5IG9mIHRoZXNlIGhvbW9sb2dzIGFuZCB0aGVpciBtdXRhbnRzIHRvIGNvbXBsZW1lbnQgZnVuY3Rpb25hbGl0eSBpbiBhbiAqRS4gY29saSoga25vY2tvdXQgc3RyYWluIGFuZCB0aGVpciB0b2xlcmFuY2UgdG8gdHJpbWV0aG9wcmltIHRyZWF0bWVudC4gVGhpcyBhbmFseXNpcyBwcm92aWRlcyBpbnNpZ2h0cyBpbnRvIGhvdyBhbnRpYmlvdGljIHJlc2lzdGFuY2UgZXZvbHZlcyBhY3Jvc3MgYSByYW5nZSBvZiBldm9sdXRpb25hcnkgc3RhcnRpbmcgcG9pbnRzLiBTZXF1ZW5jZSBkYXRhIHdlcmUgZ2VuZXJhdGVkIHVzaW5nIHRoZSBJbGx1bWluYSBOb3ZhU2VxIHBsYXRmb3JtIHdpdGggMTAwIGJwIHBhaXJlZC1lbmQgc2VxdWVuY2luZyBvZiBhbXBsaWNvbnMuCgohW01ldGhvZHMgb3ZlcnZpZXcgdG8gYWNoaWV2ZSBhIGJyb2FkLW11dGF0aW9uYWwgc2NhbiBmb3IgREhGUiBob21vbG9ncy5dKEltYWdlcy9ESEZSLkRpYWdyYW0ucG5nKQoKYGBge2Nzc30KLmJhZENvZGUgewpiYWNrZ3JvdW5kLWNvbG9yOiBsaWdodHBpbms7CmZvbnQtd2VpZ2h0OiBib2xkOwp9CgouZ29vZENvZGUgewpiYWNrZ3JvdW5kLWNvbG9yOiBsaWdodGdyZWVuOwpmb250LXdlaWdodDogYm9sZDsKfQoKLnNoYXJlZENvZGUgewpiYWNrZ3JvdW5kLWNvbG9yOiBsaWdodGJsdWU7CmZvbnQtd2VpZ2h0OiBib2xkOwp9Cgp0YWJsZSB7CiAgbWFyZ2luOiBhdXRvOwogIGJvcmRlci10b3A6IDFweCBzb2xpZCAjNjY2OwogIGJvcmRlci1ib3R0b206IDFweCBzb2xpZCAjNjY2Owp9CnRhYmxlIHRoZWFkIHRoIHsgYm9yZGVyLWJvdHRvbTogMXB4IHNvbGlkICNkZGQ7IH0KdGgsIHRkIHsgcGFkZGluZzogNXB4OyB9CnRoZWFkLCB0Zm9vdCwgdHI6bnRoLWNoaWxkKGV2ZW4pIHsgYmFja2dyb3VuZDogI2VlZTsgfQpgYGAKCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQojIFNldCBnbG9iYWwgb3B0aW9ucyBmb3Igbm90ZWJvb2sKa25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQprbml0cjo6b3B0c19jaHVuayRzZXQod2FybmluZyA9IFRSVUUsIG1lc3NhZ2UgPSBUUlVFKQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIGNsYXNzLnNvdXJjZSA9ICJiZy1zdWNjZXNzIikKCiMgR2V0dGluZyB0aGUgcGF0aCBvZiB5b3VyIGN1cnJlbnQgb3BlbiBmaWxlIGFuZCBzZXQgYXMgd2QKY3VycmVudF9wYXRoID0gcnN0dWRpb2FwaTo6Z2V0QWN0aXZlRG9jdW1lbnRDb250ZXh0KCkkcGF0aCAKc2V0d2QoZGlybmFtZShjdXJyZW50X3BhdGgpKQpwcmludChnZXR3ZCgpKQpgYGAKCiMgUGFja2FnZXMKVGhlIGZvbGxvd2luZyBSIHBhY2thZ2VzIG11c3QgYmUgaW5zdGFsbGVkIHByaW9yIHRvIGxvYWRpbmcgaW50byB0aGUgUiBzZXNzaW9uLiBTZWUgdGhlICoqUmVwcm9kdWNpYmlsaXR5KiogdGFiIGZvciBhIGNvbXBsZXRlIGxpc3Qgb2YgcGFja2FnZXMgYW5kIHRoZWlyIHZlcnNpb25zIHVzZWQgaW4gdGhpcyB3b3JrZmxvdy4KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgcmVzdWx0cz0naGlkZSd9CiMgTG9hZCB0aGUgbGF0ZXN0IHZlcnNpb24gb2YgcHl0aG9uICgzLjEwLjE0KSBmb3IgZG93bnN0cmVhbSB1c2U6CmxpYnJhcnkocmV0aWN1bGF0ZSkKdXNlX3B5dGhvbigiL1VzZXJzL2tyb20vbWluaWZvcmdlMy9iaW4vcHl0aG9uMyIpCgojIE1ha2UgYSB2ZWN0b3Igb2YgcmVxdWlyZWQgcGFja2FnZXMKcmVxdWlyZWQucGFja2FnZXMgPC0gYygiYXBlIiwgImJpbzNkIiwgIkJpb3N0cmluZ3MiLCAiY2FzdG9yIiwgImNvd3Bsb3QiLCAiZGV2dG9vbHMiLCAiZHBseXIiLCAiZ2dFeHRyYSIsICJnZ25ld3NjYWxlIiwgImdncGxvdDIiLCAiZ2dyaWRnZXMiLCAiZ2d0cmVlIiwgImdndHJlZUV4dHJhIiwgImdsbW5ldCIsICJncmlkRXh0cmEiLCJpZ3JhcGgiLCAia25pdHIiLCAibWF0cml4U3RhdHMiLCAicGF0Y2h3b3JrIiwgInBoZWF0bWFwIiwgInB1cnJyIiwgInBzY2wiLCAiUkNvbG9yQnJld2VyIiwgInJlc2hhcGUiLCJyZXNoYXBlMiIsICJST0NSIiwgInNlcWluciIsICJzY2FsZXMiLCAic3RyaW5nciIsICJzdHJpbmdpIiwgInRpZHlyIiwgInRpZHl0cmVlIiwgInZpcmlkaXMiKQoKIyBMb2FkIHJlcXVpcmVkIHBhY2thZ2VzIHdpdGggZXJyb3IgaGFuZGxpbmcKbG9hZGVkLnBhY2thZ2VzIDwtIGxhcHBseShyZXF1aXJlZC5wYWNrYWdlcywgZnVuY3Rpb24ocGFja2FnZSkgewogIGlmICghcmVxdWlyZShwYWNrYWdlLCBjaGFyYWN0ZXIub25seSA9IFRSVUUpKSB7CiAgICBpbnN0YWxsLnBhY2thZ2VzKHBhY2thZ2UsIGRlcGVuZGVuY2llcyA9IFRSVUUpCiAgICBpZiAoIXJlcXVpcmUocGFja2FnZSwgY2hhcmFjdGVyLm9ubHkgPSBUUlVFKSkgewogICAgICBtZXNzYWdlKCJQYWNrYWdlICIsIHBhY2thZ2UsICIgY291bGQgbm90IGJlIGluc3RhbGxlZCBhbmQgbG9hZGVkLiIpCiAgICAgIHJldHVybihOVUxMKQogICAgfQogIH0KICByZXR1cm4ocGFja2FnZSkKfSkKCiMgUmVtb3ZlIE5VTEwgZW50cmllcyBmcm9tIGxvYWRlZCBwYWNrYWdlcwpsb2FkZWQucGFja2FnZXMgPC0gbG9hZGVkLnBhY2thZ2VzWyFzYXBwbHkobG9hZGVkLnBhY2thZ2VzLCBpcy5udWxsKV0KYGBgCgpgYGB7ciBjbGFzcy5vdXRwdXQ9InNoYXJlZENvZGUiLCBlY2hvPUZBTFNFfQojIFByaW50IGxvYWRlZCBwYWNrYWdlcwpjYXQoIkxvYWRlZCBwYWNrYWdlczoiLCBwYXN0ZShsb2FkZWQucGFja2FnZXMsIGNvbGxhcHNlID0gIiwgIiksICJcbiIpCmBgYAoKYGBge3IgaW5jbHVkZT1GQUxTRX0KIyBzZXQuc2VlZCBpcyB1c2VkIHRvIGZpeCB0aGUgcmFuZG9tIG51bWJlciBnZW5lcmF0aW9uIHRvIG1ha2UgdGhlIHJlc3VsdHMgcmVwZWF0YWJsZQpzZXQuc2VlZCgxMjMpCmBgYAoKIyBJbXBvcnQgRGF0YSBGaWxlcwoKSW1wb3J0ICoqTVVUQU5UUyoqIGZpbGVzIGdlbmVyYXRlZCBmcm9tIFtESEZSLjIuQ291bnRzLlJNRF0oaHR0cHM6Ly9naXRodWIuY29tL1BsZXNhTGFiL0RIRlIpIHJlbGV2YW50IGZvciBkb3duc3RyZWFtIGFuYWx5c2lzLgpgYGB7cn0KIyBtdXRfY29sbGFwc2VfMTVfZ29vZF9maWx0ZXJlZAptdXRfY29sbGFwc2VfMTVfZ29vZF9maWx0ZXJlZCA8LSByZWFkLmNzdigiTXV0YW50cy9tdXRhbnRzX2ZpbGVzX2Zvcm1hdHRlZC9tdXRfY29sbGFwc2VfMTVfZ29vZF9maWx0ZXJlZC5jc3YiLCAKICAgICAgICAgICAgICAgICAgICAgICAgIGhlYWRlciA9IFRSVUUsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkKYGBgCgojIEJNUyBBbmFseXNpcwoKPGZvbnQgY29sb3I9ImJsdWUiPioqVGhpcyBzZWN0aW9uIGlzIGJhc2VkIG9uIHRoZSBSIGZpbGU6ICJSX0JNU19hbGwuTGliMTUuUiIuKio8L2ZvbnQ+IEl0IGRlc2NyaWJlcyBob3cgdG8gZ2VuZXJhdGUgTVNBIG1hcHBpbmcgZmlsZXMgZm9yIGVhY2ggaG9tb2xvZyBhbmQgbWFwIHRoZWlyIGZpdG5lc3MgdmFsdWVzIGJ5IHNlcXVlbnRpYWwgYW1pbm8gYWNpZCBwb3NpdGlvbnMgYWxpZ25lZCB0byBFLiBjb2xpIGFzIHRoZSByZWZlcmVuY2Ugc2VxdWVuY2UuIFRoZSBlbmQgcmVzdWx0IGlzIGEgaGVhdG1hcCBkaXNwbGF5aW5nIG1lZGlhbiBmaXRuZXNzIHZhbHVlcyBmb3IgZWFjaCBhbWlubyBhY2lkIGF0IGVhY2ggcG9zaXRpb24gYWxvbmcgdGhlIHByb3RlaW4gc2VxdWVuY2UuCgojIyBCTVMgLSBDb21wbGVtZW50YXRpb24KCioqQnJvYWQgTXV0YXRpb25hbCBTY2FubmluZyAoQk1TKSBBbmFseXNpcyBTY3JpcHQ6KioKVGhpcyBzZXQgb2Ygc2NyaXB0cyBmYWNpbGl0YXRlcyBicm9hZCBtdXRhdGlvbmFsIHNjYW5uaW5nIGFuYWx5c2lzIHdoZXJlIGRhdGEgZnJvbSBtYW55IHJlbGF0ZWQgcHJvdGVpbiBob21vbG9ncyBhbmQgdGhlaXIgbXV0YW50cyBpcyBjb21iaW5lZCBhbmQgY29sbGFwc2VkIGZvciBmdXJ0aGVyIGFuYWx5c2lzIGFuZCB2aXN1YWxpemF0aW9uLiBUaGlzIHNjcmlwdCBpcyBiYXNlZCBvbiB0aGUgYXBwcm9hY2ggdXNlZCBpbjogUGxlc2EgQywgU2lkb3JlIEFNLCBMdWJvY2sgTiwgWmhhbmcgRCwgS29zdXJpIFMuIE11bHRpcGxleGVkIEdlbmUgU3ludGhlc2lzIGluIEVtdWxzaW9ucyBmb3IgRXhwbG9yaW5nIFByb3RlaW4gRnVuY3Rpb25hbCBMYW5kc2NhcGVzLiBTY2llbmNlIDM1OSwgMzQz4oCTMzQ3ICgyMDE4KSwgW0RPSTogMTAuMTEyNi9zY2llbmNlLmFhbzUxNjddKGh0dHBzOi8vd3d3LnNjaWVuY2Uub3JnL2RvaS8xMC4xMTI2L3NjaWVuY2UuYWFvNTE2NykuCgoqKlByb2NlZHVyZSoqClRoZSBhbmFseXNpcyBjYW4gYmUgY2FycmllZCBvdXQgYnkgcnVubmluZyB0aGUgZm9sbG93aW5nIHNjcmlwdHMsIHdoaWNoIGluY2x1ZGUgPGlucz5weXRob248L2lucz4gYW5kIDxpbnM+YmFzaDwvaW5zPjoKCi0gYGNsdXN0YWxvYCBBbGlnbm1lbnQgcHl0aG9uIHNjcmlwdCBiYXNlZCBvbiBmYXN0YSBmaWxlcyBnZW5lcmF0ZWQgZm9yIGVhY2ggbGlicmFyeQotIGBtYXAuYWxpZ25lZC5yZXNpZHVlcy5weWAgUmVzaWR1ZSBtYXBwaW5nIGZyb20gYWxpZ25lZCBmYXN0YSBmaWxlIGdlbmVyYXRlZCBmb3IgZWFjaCBsaWJyYXJ5Ci0gYHBhcnNlX2Rzc3AucHlgIEdlbmVyYXRlIFJTQSBhbmQgU1MgaW5mb3JtYXRpb24gZmlsZXMgZnJvbSB0aGUgV1QgRS4gY29saSBESEZSIGRzc3AgZmlsZQotIGBzY29yZV9jb25zZXJ2YXRpb24ucHlgIEdlbmVyYXRlIENvbnNlcnZhdGlvbiBzY29yZSBpbmZvcm1hdGlvbiBmaWxlIGZyb20gdGhlIGFsaWdubWVudCBmaWxlCgoqKkFsaWdubWVudDoqKiBVc2UgdGhlIGBjbHVzdGFsb2AgZXhlY3V0YWJsZSB0byBhbGlnbiB0aGUgcHJvdGVpbiBzZXF1ZW5jZXMgYXNzb2NpYXRlZCB3aXRoIHRoZSBmaWx0ZXJlZCBwZXJmZWN0cyAoZGVzaWduZWQgaG9tb2xvZ3MpIHdpdGggdGhlIGZvbGxvd2luZyBjcml0ZXJpYTogbnVtcHJ1bmVkQkNzID49IDUsIGZpdEQwNUQwMyA+IC0xLCBJRCBtdXRhdGlvbnMgPSAwLiBUaGlzIHdpbGwgYWxpZ24gdGhlIGZvbGxvd2luZyBGQVNUQSBmaWxlOiAqKm11dGFudC5jb2xsYXBzZS5nb29kLjVBQS5MaWIxNS5mYXN0YSoqIGZvciB1c2UgaW4gQk1TIGFuYWx5c2lzLgoKPGZvbnQgY29sb3I9InJlZCI+KipTS0lQIFRISVMgQUxJR05NRU5UIElGIEFMUkVBRFkgUlVOLioqPC9mb250PgpgYGB7YmFzaCByZXN1bHRzID0gJ2hpZGUnfQojIE1heSBuZWVkIHRvIGVuYWJsZSBwZXJtaXNzaW9ucyB0byBydW4gdGhlIGV4ZWN1dGFibGU6CiNjaG1vZCAreCAuL2NsdXN0YWxvCi4vU2NyaXB0cy9jbHVzdGFsbyAtaSBNdXRhbnRzL211dGFudHNfZmlsZXNfZm9ybWF0dGVkL0xpYjE1Lm11dGFudC5jb2xsYXBzZS5nb29kLjVBQS5mYXN0YSAtbyBCTVMvT1VUUFVUL0NvbXBsZW1lbnRhdGlvbi9MaWIxNS5tdXRhbnQuY29sbGFwc2UuZ29vZC41QUEudHJlZS5hbGlnbmVkLmZhc3RhLmFsbiAtLW91dGZtdD1jbHVzdGFsIC0tZm9yY2UKYGBgCgojIyMgTWFwcGluZyBSZXNpZHVlcwoKKipNYXBwaW5nIFJlc2lkdWVzOioqIFVzZSB0aGUgZm9sbG93aW5nIGBtYXAuYWxpZ25lZC5yZXNpZHVlcy5weWAgcHl0aG9uIHNjcmlwdCB0byBnZW5lcmF0ZSBjc3YgZmlsZXMgZm9yIGVhY2ggZGVzaWduZWQgaG9tb2xvZyBhbmQgYXNzb2NpYXRlZCBtdXRhbnQgdGhhdCBtYXBzIHJlc2lkdWUgcG9zaXRpb25zIG9mIGVhY2ggQS5BLiBmcm9tIGFsaWdubWVudCBmYXN0YToKCi0gYG1hcF9hbGlnbmVkX3Jlc2lkdWVzLnB5YApUaGlzIHdpbGwgcGFyc2UgdGhlIGFsaWdubWVudHMgYW5kIGdlbmVyYXRlIHRhYmxlcyBvZiB3aGljaCBob21vbG9nJ3MgcmVzaWR1ZSBjb3JyZXNwb25kcyB0byB3aGljaCBwb3NpdGlvbiBpbiB0aGUgYWxpZ25tZW50IHRhYmxlIHNvIHRoYXQgY28tYWxpZ25lZCByZXNpZHVlcyBjYW4gYmUgZGV0ZXJtaW5lZC4KYGBge3B5dGhvbiBjbGFzcy5vdXRwdXQ9InNoYXJlZENvZGUifQojIFZlcmlmeSBjdXJyZW50IHB5dGhvbiB2ZXJzaW9uCmltcG9ydCBzeXMKcHJpbnQoZiJQeXRob24gdmVyc2lvbjoge3N5cy52ZXJzaW9ufSIpCgppbXBvcnQgdGltZQppbXBvcnQgY3N2CgojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCiNJTlBVVFM6CgpiYXNlX3BhdGggPSAiIgp0cmVlc19wYXRoX3ByZWZpeCA9IGJhc2VfcGF0aCsiIgoKI2NsdXN0YWwgZm9ybWF0IGFsaWdubWVudCBmaWxlCmFsaWduX2ZpbGVfaW4gPSBbdHJlZXNfcGF0aF9wcmVmaXgrIkJNUy9PVVRQVVQvTGliMTUubXV0YW50LmNvbGxhcHNlLmdvb2QuNUFBLnRyZWUuYWxpZ25lZC5mYXN0YS5hbG4iXSAjTmV3IGFsaWduZWQgRkFTVEEKCiNudW1iZXIgb2Ygc2VxcyBpbiBlYWNoIGFsaWdubWVudCBmaWxlCm51bV9zYW1wbGVzX2luX2ZpbGUgPSBbNDE4XSAjTmV3IEZBU1RBICgrMSBmcm9tIGFjdHVhbCBmaWxlIGNvdW50KQoKIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwojT1VUUFVUUzoKCm1zYV9tYXBfb3V0X3BhdGggPSBbdHJlZXNfcGF0aF9wcmVmaXgrIkJNUy9NU0FfQ29tcC8iXQpgYGAKCjxmb250IGNvbG9yPSJyZWQiPioqVGhpcyBjaHVuayBjYW4gYmUgU0tJUFBFRCBpZiBhbHJlYWR5IHJhbiBvbmNlLiBHZW5lcmF0ZXMgLmNzdiBmaWxlcyBmb3IgZWFjaCBob21vbG9nIGluIHRoZSAibXV0X2NvbGxhcHNlXzE1X2dvb2RfZmlsdGVyZWQiIGNvbGxlY3Rpb246Kio8L2ZvbnQ+CmBgYHtweXRob24gcmVzdWx0cz0naGlkZSd9CiMgVmVyaWZ5IGN1cnJlbnQgcHl0aG9uIHZlcnNpb24KaW1wb3J0IHN5cwpwcmludChmIlB5dGhvbiB2ZXJzaW9uOiB7c3lzLnZlcnNpb259IikKCiMgTG9vcCB0byBnZW5lcmF0ZSAuY3N2IGZpbGVzIGZvciBlYWNoIElECmZvciBhbG5pIGluIHJhbmdlKDEpOgogICAgI3ByaW50KGFsbmkpCiAgICAKICAgICMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMKICAgICNWQVJJQUJMRVM6CiAgICAKICAgICNJRCBhcyBrZXksIGFsaWduIGFzIHZhbHVlCiAgICBhbGlnbl9kaWN0ID0gZGljdCgpCiAgICAKICAgICNudW1fc2FtcGxlcyA9IDQxOAogICAgbnVtX3NhbXBsZXMgPSBudW1fc2FtcGxlc19pbl9maWxlW2FsbmldCiAgICAKICAgICNwb3Mga2V5LCBjb25zZW5zdXMgcG9zIHZhbAogICAgSURhYWRpY3RsaXN0ID0gW2RpY3QoKSBmb3IgeCBpbiByYW5nZShudW1fc2FtcGxlcyldCiAgICAKICAgIElEdG9pbmRleGRpY3QgPSBkaWN0KCkKICAgIGluZGV4ZHRvSURpY3QgPSBkaWN0KCkKICAgIAogICAgIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwogICAgI0NPREU6CiAgICAKICAgIGxpbmVfY291bnQgPSAwCiAgICAjbG9vcCBvdmVyIGFsbCBhbGlnbm1lbnRzOgogICAgI3ByaW50KGFsaWduX2ZpbGVfaW5bYWxuaV0pCiAgICBmb3IgbGluZSBpbiBvcGVuKGFsaWduX2ZpbGVfaW5bYWxuaV0pOgogICAgICAgICNza2lwIGhlYWRlcgogICAgICAgIGlmIGxpbmVfY291bnQgPiAxOgogICAgICAgICAgICBsaXN0V29yZHMgPSBsaW5lLnNwbGl0KCcgICAgJykKICAgICAgICAgICAgSUQgPSBsaXN0V29yZHNbMF0KICAgICAgICAgICAgYWxpZ24gPSBsaW5lWzE2Ol0ucnN0cmlwKCkKICAgICAgICAgICAgaWYgSUQuc3RyaXAoKSAhPSAiIjoKICAgICAgICAgICAgICAgIGFsaWduX2RpY3RbSURdID0gYWxpZ25fZGljdC5nZXQoSUQsICIiKSArIGFsaWduLnJlcGxhY2UoIiAiLCAiIikKICAgICAgICBsaW5lX2NvdW50ICs9IDEKICAgIAogICAgI3ByaW50KCJOUF80MTQ1OTAiKQogICAgI3ByaW50KGFsaWduX2RpY3RbIk5QXzQxNDU5MCJdKQoKICAgIGNvdW50ZXIgPSAwCiAgICBmb3IgSUQgaW4gYWxpZ25fZGljdDoKICAgICAgICAjcHJpbnQoSUQpCiAgICAgICAgI3ByaW50KGFsaWduX2RpY3RbSURdKQogICAgICAgIElEdG9pbmRleGRpY3RbSURdID0gY291bnRlcgogICAgICAgIGluZGV4ZHRvSURpY3RbY291bnRlcl09SUQKICAgICAgICBhbGlnbiA9IGFsaWduX2RpY3RbSURdCiAgICAgICAgCiAgICAgICAgYWFjb3VudGVyID0gMQogICAgICAgIAogICAgICAgIAogICAgICAgIGZvciBpIGluIHJhbmdlKGxlbihhbGlnbikpOgogICAgICAgICAgICBpZiBhbGlnbltpXSAhPSAiLSI6CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICNwcmludChzdHIoY291bnRlcikrIiAiK3N0cihhYWNvdW50ZXIpKQogICAgICAgICAgICAgICAgSURhYWRpY3RsaXN0W2NvdW50ZXJdW2FhY291bnRlcl09aSsxCiAgICAgICAgICAgICAgICBhYWNvdW50ZXIgKz0gMQogICAgICAgIGNvdW50ZXIgKz0gMQogICAgICAgIAogICAgI3ByaW50KGxlbihJRGFhZGljdGxpc3QpKQogICAgZm9yIGkgaW4gcmFuZ2UobGVuKElEYWFkaWN0bGlzdCktMSk6CiAgICAgICAgI3ByaW50KGluZGV4ZHRvSURpY3RbaV0pCiAgICAgICAgI3ByaW50KGkpCiAgICAgICAgI3ByaW50KGFsbmkpCiAgICAgICAgI3ByaW50KGluZGV4ZHRvSURpY3RbaV0pCiAgICAgICAgY3N2ZmlsZSA9IG9wZW4oc3RyKG1zYV9tYXBfb3V0X3BhdGhbYWxuaV0raW5kZXhkdG9JRGljdFtpXSsiLmNzdiIpLCAndycpCiAgICAgICAgZmllbGRuYW1lcyA9IFsnb3J0aF9hYW51bScsJ21zYV9hYW51bSddCiAgICAgICAgd3JpdGVyID0gY3N2LkRpY3RXcml0ZXIoY3N2ZmlsZSwgZmllbGRuYW1lcz1maWVsZG5hbWVzKQogICAgICAgIHdyaXRlci53cml0ZWhlYWRlcigpCiAgICAgICAgZm9yIGogaW4gSURhYWRpY3RsaXN0W2ldOgogICAgICAgICAgICAjcHJpbnQoc3RyKGopKyIgIitzdHIoSURhYWRpY3RsaXN0W2ldW2pdKSkKICAgICAgICAgICAgI3NhdmUgYWxsIGRhdGE6CiAgICAgICAgICAgIHdyaXRlci53cml0ZXJvdyh7J29ydGhfYWFudW0nOnN0cihqKSwnbXNhX2FhbnVtJzpzdHIoSURhYWRpY3RsaXN0W2ldW2pdKX0pCiAgICAgICAgY3N2ZmlsZS5jbG9zZSgpCmBgYAoKIyMjIE1TQSBNYXBwaW5nCkRlZmluZSB0aGUgZGlyZWN0b3J5IGNvbnRhaW5pbmcgdGhlIHBhcnNlIE1TQSBtYXBwaW5nIGZpbGVzICguY3N2KSBmb3IgZWFjaCBwZXJmZWN0IGhvbW9sb2cgKExpYjE1KToKYGBge3J9CkJNU19NU0FfZGlyZWN0b3J5ID0gIkJNUy9NU0FfQ29tcCIKCiMgQUEgbWFwICsgWApCTVNfYWFfbGlzdDwtZGF0YS5mcmFtZShhYT1jKCdHJywnUCcsJ0EnLCdWJywnTCcsJ0knLCdNJywnQycsJ0YnLCdZJywnVycsJ0gnLCdLJywnUicsJ1EnLCdOJywnRScsJ0QnLCdTJywnVCcsJ1gnKSwKICAgICAgICAgICAgICAgICAgICAgICAgYWFudW09YygxOjIxKSkKCkJNU19hYV9kaW0gPC0gbGVuZ3RoKEJNU19hYV9saXN0JGFhKQoKIyBXaWxkdHlwZSBFLiBjb2xpIGlzOgojTlBfNDE0NTkwCgojIEFBIGxlbmd0aCBvZiBob21vbG9nIHdoaWNoIGNvbGxhcHNpbmcgb24KQk1TX3JlZl9sZW4gPC0gMTU5CgojIE1pbmltdW0gbnVtYmVyIG9mIEJDcyB0byB1c2UgYSBtdXRhbnQKQk1TX21pbl9CQ3MgPSAxCgojIExhcmdlc3QgbnVtYmVyIG9mIG11dGF0aW9ucyB0byB1c2UKI2N1cnJlbnRseSBsaW1pdGVkIHRvIDUgZHVlIHRvIG11dGF0aW9uIG5hbWluZyBzY2hlbWUKQk1TX21heF9tdXRhdGlvbnMgPSA1CgojIE1pbmltdW0gZml0bmVzcyB0byBpbmNsdWRlIHRoZSBtdXRhdGlvbgpCTVNfbWluX2ZpdG5lc3MgPC0gLTEuMiAjIFRoaXMgbG93ZXIgcmFuZ2UgaW5jbHVkZXMgTlBfNDE0NTkwIChXVCBFLiBjb2xpIERIRlIpCmBgYAoKYGBge3J9CiMgR3JhYiBwZXJmZWN0cyAobXV0X2NvbGxhcHNlXzE1X2dvb2RfZmlsdGVyZWQpCkJNU19ob21vbG9nXzE1X0lEX2xpc3QgPC0gbXV0X2NvbGxhcHNlXzE1X2dvb2RfZmlsdGVyZWQgJT4lCiAgZmlsdGVyKG11dGF0aW9ucyA9PSAwICYgbnVtcHJ1bmVkQkNzID49IEJNU19taW5fQkNzKSAlPiUKICBmaWx0ZXIoZml0RDA1RDAzID4gQk1TX21pbl9maXRuZXNzICYgIWlzLm5hKGZpdEQwNUQwMykpICU+JQogIGRwbHlyOjpzZWxlY3QoSUQpCgp3cml0ZS50YWJsZShCTVNfaG9tb2xvZ18xNV9JRF9saXN0LCBmaWxlID0gcGFzdGUoIkJNUy9PVVRQVVQvQ29tcGxlbWVudGF0aW9uL0JNU19ob21vbG9nXzE1X0lEX2xpc3RfbWluX2ZpdG5lc3NfIixhcy5jaGFyYWN0ZXIoQk1TX21pbl9maXRuZXNzKSwiLmNzdiIsc2VwPSIiKSwgc2VwID0gIiwiLCByb3cubmFtZXMgPSBGLCBxdW90ZT1GLCBjb2wubmFtZXMgPSBGKQpgYGAKCmBgYHtyIGNsYXNzLm91dHB1dD0iZ29vZENvZGUifQojIENvdW50IHRoZSBudW1iZXIgb2YgcGVyZmVjdHMgcmV0YWluZWQgYWZ0ZXIgZmlsdGVyaW5nIGJ5IEJNU19taW5fZml0bmVzcyAoZml0bmVzcyA+IC0xLjIsIHdoaWNoIGluY2x1ZGVzIE5QXzQxNDU5MCkKbGVuZ3RoKEJNU19ob21vbG9nXzE1X0lEX2xpc3QkSUQpCmBgYAoKYGBge3IgY2xhc3Mub3V0cHV0PSJnb29kQ29kZSJ9CiNtYWtlIHN1cmUgYWxsIG9mIHRoZSBwZXJmZWN0cyBhcmUgaW4gdGhlIE1TQQpCTVNfaW5kZXhfb2ZfbXV0X3RvX2Ryb3AgPSBudW1lcmljKCkKbWlzc2luZ19maWxlcyA9IGNoYXJhY3RlcigpCgpmb3IgKGkgaW4gMTpsZW5ndGgoQk1TX2hvbW9sb2dfMTVfSURfbGlzdCRJRCkpewogIG11dGFudF9jdXJyZW50X3RlbXAgPC0gQk1TX2hvbW9sb2dfMTVfSURfbGlzdCRJRFtpXQogIGZpbGVfcGF0aCA8LSBwYXN0ZShCTVNfTVNBX2RpcmVjdG9yeSwiLyIsbXV0YW50X2N1cnJlbnRfdGVtcCwiLmNzdiIsc2VwPSIiKQogIAogIGlmKCFmaWxlLmV4aXN0cyhmaWxlX3BhdGgpKXsKICAgIHByaW50KHBhc3RlKCJNaXNzaW5nIGZpbGU6IiwgbXV0YW50X2N1cnJlbnRfdGVtcCkpCiAgICBCTVNfaW5kZXhfb2ZfbXV0X3RvX2Ryb3BbbGVuZ3RoKEJNU19pbmRleF9vZl9tdXRfdG9fZHJvcCkrMV0gPC0KICAgICAgd2hpY2goQk1TX2hvbW9sb2dfMTVfSURfbGlzdCRJRD09bXV0YW50X2N1cnJlbnRfdGVtcCkKICAgIG1pc3NpbmdfZmlsZXMgPC0gYyhtaXNzaW5nX2ZpbGVzLCBtdXRhbnRfY3VycmVudF90ZW1wKQogIH0KfQoKaWYgKGxlbmd0aChCTVNfaW5kZXhfb2ZfbXV0X3RvX2Ryb3ApID4gMCl7CiAgQk1TX2hvbW9sb2dfMTVfSURfbGlzdCA8LSBCTVNfaG9tb2xvZ18xNV9JRF9saXN0Wy1CTVNfaW5kZXhfb2ZfbXV0X3RvX2Ryb3AsXQogIHByaW50KHBhc3RlKCJUb3RhbCBtaXNzaW5nIGZpbGVzOiIsIGxlbmd0aChtaXNzaW5nX2ZpbGVzKSkpCiAgcHJpbnQoIk1pc3NpbmcgZmlsZXM6IikKICBwcmludChtaXNzaW5nX2ZpbGVzKQp9IGVsc2UgewogIHByaW50KCJBbGwgZmlsZXMgYXJlIHByZXNlbnQgaW4gdGhlIGRpcmVjdG9yeS4iKQp9CgojIFByaW50IHRoZSBudW1iZXIgb2YgcmVtYWluaW5nIGZpbGVzCnByaW50KHBhc3RlKCJOdW1iZXIgb2YgZmlsZXMgcHJlc2VudDoiLCBucm93KEJNU19ob21vbG9nXzE1X0lEX2xpc3QpKSkKYGBgCgpDb252ZXJ0IEJNU19ob21vbG9nXzE1X0lEX2xpc3QgdG8gYSBkYXRhIGZyYW1lIHdpdGggYSBjb2x1bW4gbmFtZWQgIklEIgpgYGB7cn0KIyBDb252ZXJ0IEJNU19ob21vbG9nXzE1X0lEX2xpc3QgdG8gYSBkYXRhIGZyYW1lIHdpdGggYSBjb2x1bW4gbmFtZWQgIklEIgpCTVNfaG9tb2xvZ18xNV9JRF9saXN0X2RmIDwtIGRhdGEuZnJhbWUoSUQgPSBCTVNfaG9tb2xvZ18xNV9JRF9saXN0KQpgYGAKCiMjIyBCTVMgUGVyZmVjdHMKCk1ha2UgYSBjb3B5IG9mIHRoZSBgbXV0X2NvbGxhcHNlXzE1X2dvb2RfZmlsdGVyZWRgIG9iamVjdCBmaWx0ZXJlZCBieSB0aGUgYEJNU19ob21vbG9nXzE1X0lEX2xpc3RfZGZgIChtaW4uIGZpdCA9IC0xLjIpOgpgYGB7cn0KQk1TX211dGFudHMxNV90ZW1wIDwtIG11dF9jb2xsYXBzZV8xNV9nb29kX2ZpbHRlcmVkICU+JQogIGZpbHRlcihudW1wcnVuZWRCQ3MgPj0gQk1TX21pbl9CQ3MgJiAhaXMubmEoZml0RDA1RDAzKSAmIG11dGF0aW9ucyA9PSAwKSAlPiUgCiAgc2VtaV9qb2luKEJNU19ob21vbG9nXzE1X0lEX2xpc3RfZGYsYnk9IklEIikgJT4lICNmaWx0ZXJpbmcgam9pbgogIHVuZ3JvdXAoKSAlPiUKICBkcGx5cjo6cmVuYW1lKElEYWxpZ249SUQpCmBgYAoKTWFrZSBhIG5ldyBkYXRhIGZyYW1lIHdoaWNoIHdpbGwga2VlcCB0aGUgbWFwcGluZyBpbmZvOgpgYGB7cn0KQk1TX2ZpdG5lc3MxNV9tYXAgPC0gZGF0YS5mcmFtZShwb3NpdGlvbj1udW1lcmljKCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFhPWNoYXJhY3RlcigpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtdXRhdGlvbnM9bnVtZXJpYygpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaXRuZXNzPW51bWVyaWMoKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcG9zb3J0aG89bnVtZXJpYygpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmdhcD1jaGFyYWN0ZXIoKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbXV0SUQ9Y2hhcmFjdGVyKCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIElEPWNoYXJhY3RlcigpKQpgYGAKCkxvYWQgdGhlIEUgY29saSBNU0EgbWFwcGluZyBmaWxlOgpgYGB7cn0KI2xvYWQgZWNvbGkgTVNBIG1hcHBpbmcKQk1TX2Vjb2xpX21hcCA8LSByZWFkLmNzdihmaWxlPXBhc3RlKEJNU19NU0FfZGlyZWN0b3J5LCIvTlBfNDE0NTkwLmNzdiIsc2VwPSIiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICBoZWFkPVRSVUUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgc2VwPSIsIikKCiNFLmNvbGkgREhGUiBzZXE6ICMgRG8gd2UgbmVlZCB0byB1cGRhdGUgdGhpcyB0byBpbmNsdWRlIHRoZSBzdGFydGluZyAiTSI/CkJNU19lY29saV9zZXEgPC0gIklTTElBQUxBVkRSVklHTUVOQU1QV05MUEFETEFXRktSTlRMTktQVklNR1JIVFdFU0lHUlBMUEdSS05JSUxTU1FQR1RERFJWVFdWS1NWREVBSUFBQ0dEVlBFSU1WSUdHR1JWWUVRRkxQS0FRS0xZTFRISURBRVZFR0RUSEZQRFlFUEREV0VTVkZTRUZIREFEQVFOU0hTWUNGRUlMRVJSIgpgYGAKCkxvb3Agb3ZlciBhbGwgcGVyZmVjdHMgaW4gTVNBIGRpcmVjdG9yeSB3aXRoIGZpdG5lc3Mgc2NvcmVzIGZvciBlYWNoIGEuYS4gcG9zaXRpb24gYW5kIGRldGVybWluZSBpZiBpdCBtYXBzIHRvIGFuIEUuIGNvbGkgcmVzaWR1ZS4gPGZvbnQgY29sb3I9InJlZCI+KipTS0lQIFRISVMgQ0hVTksgSUYgTE9PUCBIQVMgQUxSRUFEWSBCRUVOIFJVTi4qKjwvZm9udD4KYGBge3IgZXZhbD1GQUxTRSwgcmVzdWx0cz0naGlkZSd9CmZvciAoaSBpbiAxOm5yb3coQk1TX211dGFudHMxNV90ZW1wKSkgewogICNjdXJyZW50IGhvbW9sb2c6CiAgbXV0YW50X2N1cnJlbnRfdGVtcCA8LSBCTVNfbXV0YW50czE1X3RlbXAkSURhbGlnbltpXQogIAogICNnZXQgdGhlIE1TQSBtYXBwaW5nOgogIG11dGFudF9tYXBfdGVtcCA8LSByZWFkLmNzdihmaWxlPXBhc3RlKEJNU19NU0FfZGlyZWN0b3J5LCIvIixtdXRhbnRfY3VycmVudF90ZW1wLCIuY3N2IixzZXA9IiIpLCBoZWFkPVRSVUUsIHNlcD0iLCIpCiAgCiAgI2xvYWQgYW5kIGRlZmluZSB0aGUgZnVsbCBzZXEgZm9yIGVhY2ggaG9tb2xvZzoKICBCTVNfc2VxX3RlbXAgPC0gYXMuY2hhcmFjdGVyKEJNU19tdXRhbnRzMTVfdGVtcCRzZXFbaV0pCiAgCiAgI2dldCBmaXRuZXNzIGZvciB0aGlzIGhvbW9sb2cKICBCTVNfZml0X3RlbXAgPC0gQk1TX211dGFudHMxNV90ZW1wJGZpdEQwNUQwM1tpXQogIAogICNsb29wIG92ZXIgYWxsIHJlc2lkdWVzCiAgZm9yIChqIGluIDE6bmNoYXIoQk1TX3NlcV90ZW1wKSkgewogICAgI2ZpbmQgdGhlIGNvcnJlc3BvbmRpbmcgcmVzaWR1ZSBpbiBFLmNvbGkgdXNpbmcgTVNBCiAgICBCTVNfY29uc19hYW51bSA8LSBtdXRhbnRfbWFwX3RlbXAkbXNhX2FhbnVtW3doaWNoKG11dGFudF9tYXBfdGVtcCRvcnRoX2FhbnVtID09IGopXQogICAgCiAgICAjZG9lcyB0aGlzIG1hcCB0byBhIG5vbi1nYXAgcmVzaWR1ZSBpbiBFLmNvbGk/CiAgICBpZiAoQk1TX2NvbnNfYWFudW0gJWluJSBCTVNfZWNvbGlfbWFwJG1zYV9hYW51bSkgewogICAgICAjZ2V0IHRoZSBFLmNvbGkgcmVzaWR1ZQogICAgICBlX2NvbGlfcmVzaWR1ZSA8LSBCTVNfZWNvbGlfbWFwJG9ydGhfYWFudW1bd2hpY2goQk1TX2Vjb2xpX21hcCRtc2FfYWFudW0gPT0gQk1TX2NvbnNfYWFudW0pXQogICAgICAKICAgICAgI2FhIGF0IHRoaXMgcmVzaWR1ZQogICAgICBCTVNfYWFfdGVtcCA8LSBzdWJzdHIoQk1TX3NlcV90ZW1wLCBqLCBqKQogICAgICAKICAgICAgI2FkZCBpbmZvIGZvciB0aGlzIHJlc2lkdWUgdG8gZGYKICAgICAgQk1TX2ZpdG5lc3MxNV9tYXAgPC0gcmJpbmQoQk1TX2ZpdG5lc3MxNV9tYXAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhLmZyYW1lKHBvc2l0aW9uPWVfY29saV9yZXNpZHVlLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhYT1CTVNfYWFfdGVtcCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbXV0YXRpb25zPTAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpdG5lc3M9Qk1TX2ZpdF90ZW1wLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwb3NvcnRobz1qLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmdhcD0iTm8iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtdXRJRD1tdXRhbnRfY3VycmVudF90ZW1wLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJRD1tdXRhbnRfY3VycmVudF90ZW1wKSkKICAgIH0gZWxzZSB7CiAgICAgICNpZiBpdCdzIGhlcmUgaXQgbWFwcyB0byBhIGdhcAogICAgICBCTVNfYWFfdGVtcCA8LSBzdWJzdHIoQk1TX3NlcV90ZW1wLCBqLCBqKQogICAgICBCTVNfZml0bmVzczE1X21hcCA8LSByYmluZChCTVNfZml0bmVzczE1X21hcCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEuZnJhbWUocG9zaXRpb249LTEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFhPUJNU19hYV90ZW1wLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtdXRhdGlvbnM9MCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZml0bmVzcz1CTVNfZml0X3RlbXAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBvc29ydGhvPWosCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluZ2FwPSJZZXMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtdXRJRD1tdXRhbnRfY3VycmVudF90ZW1wLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJRD1tdXRhbnRfY3VycmVudF90ZW1wKSkKICAgIH0KICB9Cn0KCiNTYXZlIHRoZSBgQk1TX2ZpdG5lc3MxNV9tYXBgIG9iamVjdCB0byByZS1sb2FkIGluIGZ1dHVyZSBzZXNzaW9ucyAoc2F2ZXMgc3Vic3RhbnRpYWwgY29tcHV0YXRpb25hbCB0aW1lKToKd3JpdGUuY3N2KEJNU19maXRuZXNzMTVfbWFwLCAiQk1TL09VVFBVVC9Db21wbGVtZW50YXRpb24vQk1TX2ZpdG5lc3MxNV9jb21wbGVtZW50YXRpb25fbWFwLmNzdiIsIHJvdy5uYW1lcyA9IEZBTFNFLCBxdW90ZSA9IEZBTFNFKQpgYGAKCkxvYWQgdGhlIGBCTVNfZml0bmVzczE1X21hcGAgZGF0YXNldCB0byBjb250aW51ZSB0aGUgQk1TIGFuYWx5c2lzIChpZiB0aGUgbG9vcCBzdGVwIGFib3ZlIHdhcyBza2lwcGVkKS4KCjxmb250IGNvbG9yPSJyZWQiPioqU0tJUCBUSElTIENIVU5LIElGIExPT1AgQUJPVkUgV0FTIFJVTi4qKjwvZm9udD4KYGBge3IgZXZhbD1UUlVFfQojIFVwZGF0ZSB3aGljaCBmaXRuZXNzIG1hcCB0byByZWFkIGluIENvbXBsZW1lbnRhdGlvbiAoc2VlIGxvb3AgYWJvdmUpCkJNU19maXRuZXNzMTVfbWFwIDwtIHJlYWQuY3N2KCJCTVMvT1VUUFVUL0NvbXBsZW1lbnRhdGlvbi9CTVNfZml0bmVzczE1X2NvbXBsZW1lbnRhdGlvbl9tYXAuY3N2IiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhlYWRlciA9IFRSVUUsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjaGVjay5uYW1lcyA9IEZBTFNFKQpgYGAKCkNvbmZpcm0gdGhhdCBjb3JyZWN0IG51bWJlciBvZiB1bmlxdWUgbXV0SURzIHdlcmUgaW50ZWdyYXRlZCBpbnRvIHRoZSBCTVNfZml0bmVzczE1X21hcCBkYXRhZnJhbWUgKHNob3VsZCBtYXRjaCBjb3VudCBhYm92ZSk6CmBgYHtyIGNsYXNzLm91dHB1dD0iZ29vZENvZGUifQojIENvdW50IHVuaXF1ZSBtdXRJRCBpbiBCTVNfZml0bmVzczE1X21hcApsZW5ndGgodW5pcXVlKEJNU19maXRuZXNzMTVfbWFwJG11dElEKSkKYGBgCgpDb2xsYXBzZSAobWVkaWFuKSBmaXRuZXNzIHZhbHVlcyBmb3IgZWFjaCBwb3NpdGlvbiBvbnRvIHRoZSBCTVMgRml0bmVzcyBNYXA6CmBgYHtyfQpCTVNfZml0bmVzczE1X2NvbGxhcHNlZCA8LSBCTVNfZml0bmVzczE1X21hcCAlPiUKICBmaWx0ZXIocG9zaXRpb24gPiAwKSAlPiUKICBncm91cF9ieShwb3NpdGlvbiwgYWEpICU+JQogIHN1bW1hcmlzZShmaXR2YWw9bWVkaWFuKGZpdG5lc3MpLAogICAgICAgICAgICBudW1wb2ludHM9bigpLAogICAgICAgICAgICBzdGRmaXQ9c2QoZml0bmVzcykpCmBgYAoKVGhlc2UgbWF0cmljZXMgaGF2ZSB0aGUgZml0bmVzcyBmb3IgZWFjaCBhYSBhdCBlYWNoIHBvc2l0aW9uOgpgYGB7cn0KQk1TX21hdHJpeDE1X3BlcmZlY3RzID0gbWF0cml4KHJlcChOQSwgQk1TX3JlZl9sZW4qQk1TX2FhX2RpbSksbnJvdz1CTVNfYWFfZGltLG5jb2w9Qk1TX3JlZl9sZW4pCkJNU19tYXRyaXgxNV9wZXJmZWN0c19udW0gPSBtYXRyaXgocmVwKE5BLCBCTVNfcmVmX2xlbipCTVNfYWFfZGltKSxucm93PUJNU19hYV9kaW0sbmNvbD1CTVNfcmVmX2xlbikKQk1TX21hdHJpeDE1X3BlcmZlY3RzX3NkID0gbWF0cml4KHJlcChOQSwgQk1TX3JlZl9sZW4qQk1TX2FhX2RpbSksbnJvdz1CTVNfYWFfZGltLG5jb2w9Qk1TX3JlZl9sZW4pCmBgYAoKUG9wdWxhdGUgbWF0cml4OgpgYGB7cn0KZm9yIChpIGluIDE6bnJvdyhCTVNfZml0bmVzczE1X2NvbGxhcHNlZCkpewogIEJNU19tYXRyaXgxNV9wZXJmZWN0c1t3aGljaChCTVNfYWFfbGlzdCRhYT09YXMuY2hhcmFjdGVyKEJNU19maXRuZXNzMTVfY29sbGFwc2VkJGFhW2ldKSksCiAgICAgICAgICAgICAgICAgICAgICBCTVNfZml0bmVzczE1X2NvbGxhcHNlZCRwb3NpdGlvbltpXV0gPC0gYXMubnVtZXJpYyhCTVNfZml0bmVzczE1X2NvbGxhcHNlZCRmaXR2YWxbaV0pCiAgQk1TX21hdHJpeDE1X3BlcmZlY3RzX251bVt3aGljaChCTVNfYWFfbGlzdCRhYT09YXMuY2hhcmFjdGVyKEJNU19maXRuZXNzMTVfY29sbGFwc2VkJGFhW2ldKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgQk1TX2ZpdG5lc3MxNV9jb2xsYXBzZWQkcG9zaXRpb25baV1dIDwtIGFzLm51bWVyaWMoQk1TX2ZpdG5lc3MxNV9jb2xsYXBzZWQkbnVtcG9pbnRzW2ldKQogIEJNU19tYXRyaXgxNV9wZXJmZWN0c19zZFt3aGljaChCTVNfYWFfbGlzdCRhYT09YXMuY2hhcmFjdGVyKEJNU19maXRuZXNzMTVfY29sbGFwc2VkJGFhW2ldKSksCiAgICAgICAgICAgICAgICAgICAgICAgICBCTVNfZml0bmVzczE1X2NvbGxhcHNlZCRwb3NpdGlvbltpXV0gPC0gYXMubnVtZXJpYyhCTVNfZml0bmVzczE1X2NvbGxhcHNlZCRzdGRmaXRbaV0pCn0KYGBgCgpBc3NpZ24gQUEgbmFtZXMgYW5kIHBvc2l0aW9uIG51bWJlcnMgdG8gbWF0cmljZXM6CmBgYHtyIHdhcm5pbmc9RkFMU0V9CnJvd25hbWVzKEJNU19tYXRyaXgxNV9wZXJmZWN0cyk8LUJNU19hYV9saXN0JGFhCmNvbG5hbWVzKEJNU19tYXRyaXgxNV9wZXJmZWN0cyk8LWMoMTpCTVNfcmVmX2xlbikKcm93bmFtZXMoQk1TX21hdHJpeDE1X3BlcmZlY3RzX251bSk8LUJNU19hYV9saXN0JGFhCmNvbG5hbWVzKEJNU19tYXRyaXgxNV9wZXJmZWN0c19udW0pPC1jKDE6Qk1TX3JlZl9sZW4pCnJvd25hbWVzKEJNU19tYXRyaXgxNV9wZXJmZWN0c19zZCk8LUJNU19hYV9saXN0JGFhCmNvbG5hbWVzKEJNU19tYXRyaXgxNV9wZXJmZWN0c19zZCk8LWMoMTpCTVNfcmVmX2xlbikKCkJNU19tYXRyaXgxNV9wZXJmZWN0c19tZWx0IDwtIG1lbHQoQk1TX21hdHJpeDE1X3BlcmZlY3RzKQpCTVNfbWF0cml4MTVfcGVyZmVjdHNfbnVtX21lbHQgPC0gbWVsdChCTVNfbWF0cml4MTVfcGVyZmVjdHNfbnVtKQpCTVNfbWF0cml4MTVfcGVyZmVjdHNfc2RfbWVsdCA8LSBtZWx0KEJNU19tYXRyaXgxNV9wZXJmZWN0c19zZCkKYGBgCgojIyMgUGVyZmVjdHMgUGxvdHRpbmcKCkNhbGN1bGF0ZSB0aGUgbWluaW11bSBhbmQgbWF4aW11bSB2YWx1ZXMgb2YgYEJNU19tYXRyaXgxNV9wZXJmZWN0c19tZWx0YCBwcmlvciB0byBwbG90dGluZzoKYGBge3IgY2xhc3Mub3V0cHV0PSJnb29kQ29kZSJ9CiMgTWluCm1pbihCTVNfbWF0cml4MTVfcGVyZmVjdHNfbWVsdCR2YWx1ZSwgbmEucm09VCkKCiMgTWF4Cm1heChCTVNfbWF0cml4MTVfcGVyZmVjdHNfbWVsdCR2YWx1ZSwgbmEucm09VCkKYGBgCgpQbG90IHRoZSBmaXRuZXNzIGRhdGEgZm9yIHRoZSBwZXJmZWN0czoKYGBge3J9CiMgUmVuYW1lIGNvbHVtbnMgdG8gIlgxIiBhbmQgIlgyIgpuYW1lcyhCTVNfbWF0cml4MTVfcGVyZmVjdHNfbWVsdClbbmFtZXMoQk1TX21hdHJpeDE1X3BlcmZlY3RzX21lbHQpICVpbiUgYygiVmFyMSIsICJWYXIyIildIDwtIGMoIlgxIiwgIlgyIikKCkJNU19tYXRyaXgxNV9wZXJmZWN0c19tZWx0X3Bsb3QgPC0gZ2dwbG90KGRhdGEgPSBCTVNfbWF0cml4MTVfcGVyZmVjdHNfbWVsdCwgYWVzKHg9WDIsIHk9WDEsIGZpbGw9dmFsdWUpKSArCiAgZ2VvbV90aWxlKCkrIGxhYnMoeCA9ICJQb3NpdGlvbiAoYWEpIiwgeSA9IkFtaW5vIGFjaWQiLGNvbG9yPSIiKSArCiAgc2NhbGVfZmlsbF9ncmFkaWVudDIobG93ID0gImJsdWUiLCBoaWdoID0gInJlZCIsIG1pZD0iZ29sZCIsbmFtZT0iRml0bmVzcyIsbmEudmFsdWU9ImdyZXkiLCBsaW1pdCA9IGMoLTEsMSkpICsKICB0aGVtZV9taW5pbWFsKCkgKyAKICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzPXNlcSgwLDE1NSw1KSkKCnByaW50KEJNU19tYXRyaXgxNV9wZXJmZWN0c19tZWx0X3Bsb3QpCmBgYAoKYGBge3IgZWNobz1GQUxTRX0KI1NhdmluZyAxMy4zIHggNi41MSBpbiBpbWFnZXMKZ2dzYXZlKGZpbGU9IkJNUy9QTE9UUy9Db21wbGVtZW50YXRpb24vQk1TX2FsbHBvczE1X21pbkJDXzFfbXV0YXRpb25zXzBfZml0LnBkZiIsIAogICAgICAgcGxvdD1CTVNfbWF0cml4MTVfcGVyZmVjdHNfbWVsdF9wbG90LAogICAgICAgd2lkdGg9MTMuMywgaGVpZ2h0PTYuNSwgdW5pdHM9ImluIikKYGBgCgpQbG90IHRoZSBjb3ZlcmFnZSBkYXRhOgpgYGB7cn0KIyBSZW5hbWUgY29sdW1ucyB0byAiWDEiIGFuZCAiWDIiCm5hbWVzKEJNU19tYXRyaXgxNV9wZXJmZWN0c19udW1fbWVsdClbbmFtZXMoQk1TX21hdHJpeDE1X3BlcmZlY3RzX251bV9tZWx0KSAlaW4lIGMoIlZhcjEiLCAiVmFyMiIpXSA8LSBjKCJYMSIsICJYMiIpCgpCTVNfbWF0cml4MTVfcGVyZmVjdHNfbnVtX21lbHRfcGxvdCA8LSBnZ3Bsb3QoZGF0YSA9IEJNU19tYXRyaXgxNV9wZXJmZWN0c19udW1fbWVsdCwgYWVzKHg9WDIsIHk9WDEsIGZpbGw9bG9nKHZhbHVlKSkpICsKICBnZW9tX3RpbGUoKSsgbGFicyh4ID0gIlBvc2l0aW9uIChhYSkiLCB5ID0iQW1pbm8gYWNpZCIsY29sb3I9IiIpICsKICBzY2FsZV9maWxsX2dyYWRpZW50KGxvdyA9ICJibHVlIiwgaGlnaCA9ICJyZWQiLG5hbWU9ImxvZygjcG9pbnRzKSIsbmEudmFsdWU9ImdyZXkiLCBsaW1pdCA9IGMoMCxtYXgoQk1TX21hdHJpeDE1X3BlcmZlY3RzX251bV9tZWx0JHZhbHVlKSkpICsKICB0aGVtZV9taW5pbWFsKCkgKyAKICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzPXNlcSgwLDE1NSw1KSkKCnByaW50KEJNU19tYXRyaXgxNV9wZXJmZWN0c19udW1fbWVsdF9wbG90KQpgYGAKCmBgYHtyIGVjaG89RkFMU0V9CiNTYXZpbmcgMTMuMyB4IDYuNTEgaW4gaW1hZ2VzCmdnc2F2ZShmaWxlPSJCTVMvUExPVFMvQ29tcGxlbWVudGF0aW9uL0JNU19hbGxwb3MxNV9taW5CQ18xX211dGF0aW9uc18wX251bS5wZGYiLCBwbG90PUJNU19tYXRyaXgxNV9wZXJmZWN0c19udW1fbWVsdF9wbG90LAogICAgICAgd2lkdGg9MTMuMywgaGVpZ2h0PTYuNSwgdW5pdHM9ImluIikKYGBgCgpQbG90IHRoZSBTVEQgRGF0YToKYGBge3J9CiMgUmVuYW1lIGNvbHVtbnMgdG8gIlgxIiBhbmQgIlgyIgpuYW1lcyhCTVNfbWF0cml4MTVfcGVyZmVjdHNfc2RfbWVsdClbbmFtZXMoQk1TX21hdHJpeDE1X3BlcmZlY3RzX3NkX21lbHQpICVpbiUgYygiVmFyMSIsICJWYXIyIildIDwtIGMoIlgxIiwgIlgyIikKCkJNU19tYXRyaXgxNV9wZXJmZWN0c19zZF9tZWx0X3Bsb3QgPC0gZ2dwbG90KGRhdGEgPSBCTVNfbWF0cml4MTVfcGVyZmVjdHNfc2RfbWVsdCwgYWVzKHg9WDIsIHk9WDEsIGZpbGw9dmFsdWUpKSArCiAgZ2VvbV90aWxlKCkrIGxhYnMoeCA9ICJQb3NpdGlvbiAoYWEpIiwgeSA9IkFtaW5vIGFjaWQiLGNvbG9yPSIiKSArCiAgc2NhbGVfZmlsbF9ncmFkaWVudDIobG93ID0gImJsdWUiLCBoaWdoID0gInJlZCIsIG1pZD0iZ29sZCIsbmFtZT0ic3RkKEZpdG5lc3MpIixuYS52YWx1ZT0iZ3JleSIsIGxpbWl0ID0gYygwLDEuMSptYXgoQk1TX21hdHJpeDE1X3BlcmZlY3RzX3NkX21lbHQkdmFsdWUpKSkgKwogIHRoZW1lX21pbmltYWwoKSArIAogIHNjYWxlX3hfY29udGludW91cyhicmVha3M9c2VxKDAsMTU1LDUpKQoKcHJpbnQoQk1TX21hdHJpeDE1X3BlcmZlY3RzX3NkX21lbHRfcGxvdCkKYGBgCgpgYGB7ciBlY2hvPUZBTFNFfQojU2F2aW5nIDEzLjMgeCA2LjUxIGluIGltYWdlcwpnZ3NhdmUoZmlsZT0iQk1TL1BMT1RTL0NvbXBsZW1lbnRhdGlvbi9CTVNfYWxscG9zMTVfbWluQkNfMV9tdXRhdGlvbnNfMF9zZC5wZGYiLCAKICAgICAgIHBsb3Q9Qk1TX21hdHJpeDE1X3BlcmZlY3RzX3NkX21lbHRfcGxvdCwKICAgICAgIHdpZHRoPTEzLjMsIGhlaWdodD02LjUsIHVuaXRzPSJpbiIpCmBgYAoKIyMjIEJNUyBNdXRhbnRzCgpDb3VudCB0aGUgdG90YWwgbnVtYmVyIG9mIG11dGFudHMgaW4gdGhlIGBtdXRfY29sbGFwc2VfMTVfZ29vZF9maWx0ZXJlZGAgZGF0YXNldDoKYGBge3IgY2xhc3Mub3V0cHV0PSJnb29kQ29kZSJ9CiMgQ291bnQgbXV0YW50cyAodXAgdG8gNSBBQSkgYXNzb2NpYXRlZCB3aXRoIHBlcmZlY3RzIGFmdGVyIGZpbHRlcmluZyBieSBtaW5pbXVtIGZpdG5lc3MgKC0xKQptdXRfY29sbGFwc2VfMTVfZ29vZF9maWx0ZXJlZCAlPiUKICBmaWx0ZXIobXV0YXRpb25zID4gMCAmIG11dGF0aW9ucyA8IDYgJiBudW1wcnVuZWRCQ3MgPj0gQk1TX21pbl9CQ3MgJiAhaXMubmEoZml0RDA1RDAzKSkgJT4lCiAgc2VtaV9qb2luKEJNU19ob21vbG9nXzE1X0lEX2xpc3QsYnk9IklEIikgJT4lCiAgdW5ncm91cCgpICU+JQogIGRwbHlyOjpyZW5hbWUoSURhbGlnbj1JRCkgJT4lCiAgbnJvdyguKQpgYGAKCk1ha2UgYSBjb3B5IG9mIHRoZSBCTVNfZml0bmVzczE1X21hcCBvYmplY3QgZm9yIGFkZGluZyB0aGUgbXV0YW50czoKYGBge3J9CiMgTWFrZSBhIGNvcHkgb2YgdGhlIGRhdGFmcmFtZSBmb3IgbXV0YW50cwpCTVNfZml0bmVzczE1X21hcF9hbGwgPC0gQk1TX2ZpdG5lc3MxNV9tYXAKYGBgCgpMb29wIG92ZXIgbXV0YW50cyBhdCBzb21lIGRpc3RhbmNlICh1cCB0byA1IGFhIGRpc3RhbmNlKToKYGBge3J9CmZvciAoQk1TX2N1cl9tdXRfbnVtIGluIDE6Qk1TX21heF9tdXRhdGlvbnMpewogIAogICNncmFiIHRoZSBtdXRhbnRzIChyZW1vdmUgYW55IG11dGFudHMgd2l0aCAiTkEiIGZpdG5lc3MgdmFsdWUgZm9yIGZpdEQwNUQwMykKICBCTVNfbXV0YW50czE1X3RlbXAgPC0gbXV0X2NvbGxhcHNlXzE1X2dvb2RfZmlsdGVyZWQgJT4lCiAgICBmaWx0ZXIobXV0YXRpb25zID4gMCAmIG11dGF0aW9ucyA8IDYgJiBudW1wcnVuZWRCQ3MgPj0gQk1TX21pbl9CQ3MgJiAhaXMubmEoZml0RDA1RDAzKSkgJT4lCiAgICBmaWx0ZXIobXV0YXRpb25zID09IEJNU19jdXJfbXV0X251bSkgJT4lCiAgICBzZW1pX2pvaW4oQk1TX2hvbW9sb2dfMTVfSURfbGlzdCxieT0iSUQiKSAlPiUgI2ZpbHRlcmluZyBqb2luCiAgICB1bmdyb3VwKCkgJT4lCiAgICBkcGx5cjo6cmVuYW1lKElEYWxpZ249SUQpCiAgCiAgI2luaXRpYWxpemUgbmV3IGRmCiAgQk1TX2ZpdG5lc3MxNV9tYXAxIDwtIGRhdGEuZnJhbWUocG9zaXRpb249bnVtZXJpYygpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhYT1jaGFyYWN0ZXIoKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbXV0YXRpb25zPW51bWVyaWMoKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZml0bmVzcz1udW1lcmljKCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBvc29ydGhvPW51bWVyaWMoKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5nYXA9Y2hhcmFjdGVyKCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG11dElEPWNoYXJhY3RlcigpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJRD1jaGFyYWN0ZXIoKSkKICAKICAjbG9vcCBvdmVyIG11dGFudHMKICBmb3IgKGkgaW4gMTpucm93KEJNU19tdXRhbnRzMTVfdGVtcCkpewogICAgCiAgICAjbXV0YW50IGJhc2UgbmFtZQogICAgbXV0YW50X2N1cnJlbnRfdGVtcCA8LSBCTVNfbXV0YW50czE1X3RlbXAkSURhbGlnbltpXQogICAgCiAgICAjbGVuZ3RoIG9mIHRoZSBuYW1lCiAgICBuYW1lX3NpemUgPSBuY2hhcihwYXN0ZShtdXRhbnRfY3VycmVudF90ZW1wLCJfIixzZXA9IiIpKQogICAgCiAgICAjcmVzaWR1ZSBtYXBwaW5ncwogICAgbXV0YW50X21hcF90ZW1wIDwtIHJlYWQuY3N2KGZpbGU9cGFzdGUoQk1TX01TQV9kaXJlY3RvcnksIi8iLG11dGFudF9jdXJyZW50X3RlbXAsIi5jc3YiLHNlcD0iIiksaGVhZD1UUlVFLHNlcD0iLCIpCiAgICAKICAgICN0aGlzIG11dGFudHMgZml0bmVzcwogICAgQk1TX2ZpdF90ZW1wIDwtIEJNU19tdXRhbnRzMTVfdGVtcCRmaXREMDVEMDNbaV0KICAgIAogICAgI2dyYWIgdGhlIG11dCBuYW1lCiAgICBtdXRhdGlvbnMxNV9uYW1lcyA8LSBhcy5jaGFyYWN0ZXIoQk1TX211dGFudHMxNV90ZW1wJG11dElEW2ldKQogICAgCiAgICAjZ3JhYiBvbmx5IHRoZSByZWxldmFudCBwb3J0aW9uIG9mIHRoZSBuYW1lCiAgICBtdXRhdGlvbnMxNV9uYW1lcyA8LSBzdWJzdHIobXV0YXRpb25zMTVfbmFtZXMsIG5hbWVfc2l6ZSsxLCBuY2hhcihtdXRhdGlvbnMxNV9uYW1lcykpCiAgICAKICAgICMjIHNwbGl0IG11dGF0aW9uIHN0cmluZyBhdCBub24tZGlnaXRzCiAgICBzIDwtIHN0cnNwbGl0KG11dGF0aW9uczE1X25hbWVzLCAiXyIpCiAgICAKICAgIG11dF90b3RhbF9sZW5ndGg8LSBuY2hhcihCTVNfbXV0YW50czE1X3RlbXAkc2VxW2ldKQogICAgCiAgICBmb3IgKG11dG51bSBpbiAxOkJNU19jdXJfbXV0X251bSl7CiAgICAgIAogICAgICAjZ3JhYiB0aGUgY29ycmVzcG9uZGluZyBtdXRhdGlvbiBzdHJpbmcKICAgICAgbXV0Y3Vycjwtc1tbMV1dW211dG51bV0KICAgICAgCiAgICAgICNnZXQgdGhlIHBvc2l0aW9uCiAgICAgIG11dHBvcyA8LSBhcy5udW1lcmljKHN0cl9leHRyYWN0KG11dGN1cnIsICJbMC05XSsiKSkKICAgICAgCiAgICAgIGlmIChtdXRwb3M8PW11dF90b3RhbF9sZW5ndGgpewogICAgICAgICNnZXQgZW5kaW5nIGFhCiAgICAgICAgdG9fYWEgPC0gc3Vic3RyKG11dGN1cnIsIG5jaGFyKG11dHBvcykrMiwgbmNoYXIobXV0Y3VycikpCiAgICAgICAgCiAgICAgICAgI2ZpbmQgdGhlIG51bWJlciBpbiB0aGUgY29uc2Vuc3VzIHNlcQogICAgICAgIEJNU19jb25zX2FhbnVtIDwtIG11dGFudF9tYXBfdGVtcCRtc2FfYWFudW1bd2hpY2gobXV0YW50X21hcF90ZW1wJG9ydGhfYWFudW0gPT0gbXV0cG9zKV0KICAgICAgICAKICAgICAgICAjZG9lcyB0aGlzIG1hcCB0byBhIG5vbi1nYXAKICAgICAgICBpZiAoQk1TX2NvbnNfYWFudW0gJWluJSBCTVNfZWNvbGlfbWFwJG1zYV9hYW51bSl7CiAgICAgICAgICAKICAgICAgICAgICN0aGUgY29ycmVzcG9uZGluZyBlLmNvbGkgcmVzaWR1ZQogICAgICAgICAgZV9jb2xpX3Jlc2lkdWUgPC0gQk1TX2Vjb2xpX21hcCRvcnRoX2FhbnVtW3doaWNoKEJNU19lY29saV9tYXAkbXNhX2FhbnVtID09IEJNU19jb25zX2FhbnVtKV0KICAgICAgICAgIAogICAgICAgICAgI2FkZCB0aGlzIHBvaW50IHRvIHRoZSBkYXRhCiAgICAgICAgICBCTVNfZml0bmVzczE1X21hcDEgPC0gcmJpbmQoQk1TX2ZpdG5lc3MxNV9tYXAxLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhLmZyYW1lKHBvc2l0aW9uPWVfY29saV9yZXNpZHVlLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFhPXRvX2FhLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG11dGF0aW9ucz1CTVNfY3VyX211dF9udW0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZml0bmVzcz1CTVNfZml0X3RlbXAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcG9zb3J0aG89bXV0cG9zLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluZ2FwPSJObyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbXV0SUQ9YXMuY2hhcmFjdGVyKEJNU19tdXRhbnRzMTVfdGVtcCRtdXRJRFtpXSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSUQ9bXV0YW50X2N1cnJlbnRfdGVtcCkpCiAgICAgICAgICAKICAgICAgICB9IGVsc2UgewogICAgICAgICAgI2lmIGl0J3MgaGVyZSBpdCBtYXBzIHRvIGEgZ2FwCiAgICAgICAgICAKICAgICAgICAgICNhZGQgdGhpcyBwb2ludCB0byB0aGUgZGF0YQogICAgICAgICAgQk1TX2ZpdG5lc3MxNV9tYXAxIDwtIHJiaW5kKEJNU19maXRuZXNzMTVfbWFwMSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YS5mcmFtZShwb3NpdGlvbj0tMSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhYT10b19hYSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtdXRhdGlvbnM9Qk1TX2N1cl9tdXRfbnVtLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpdG5lc3M9Qk1TX2ZpdF90ZW1wLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBvc29ydGhvPW11dHBvcywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmdhcD0iWWVzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtdXRJRD1hcy5jaGFyYWN0ZXIoQk1TX211dGFudHMxNV90ZW1wJG11dElEW2ldKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJRD1tdXRhbnRfY3VycmVudF90ZW1wKSkKICAgICAgICAgIAogICAgICAgIH0KICAgICAgfQogICAgICAKICAgIH0KICAgIAogIH0KCiAgI0FkZCB0aGVzZSBtdXRhbnRzIG9udG8gdGhlIGV4aXN0aW5nIGRhdGE6CiAgQk1TX2ZpdG5lc3MxNV9tYXBfYWxsIDwtIHJiaW5kKEJNU19maXRuZXNzMTVfbWFwX2FsbCxCTVNfZml0bmVzczE1X21hcDEpCgogIHdyaXRlLnRhYmxlKEJNU19maXRuZXNzMTVfbWFwMSwgZmlsZSA9IHBhc3RlKCJCTVMvT1VUUFVUL0NvbXBsZW1lbnRhdGlvbi9CTVNfZml0bmVzczE1X21hcF8iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFzLmNoYXJhY3RlcihCTVNfY3VyX211dF9udW0pLCIuY3N2IixzZXA9IiIpLCAKICAgICAgICAgICAgc2VwID0gIiwiLCByb3cubmFtZXMgPSBGLHF1b3RlPUYsY29sLm5hbWVzID0gVCkKICAKICAjQ2FsY3VsYXRlIGFsbCBkYXRhIGFuZCBzdGF0cwogIEJNU19maXRuZXNzMTVfY29sbGFwc2VkX2FsbCA8LSBCTVNfZml0bmVzczE1X21hcF9hbGwgJT4lCiAgICBmaWx0ZXIocG9zaXRpb24gPiAwKSAlPiUKICAgIGdyb3VwX2J5KHBvc2l0aW9uLCBhYSkgJT4lCiAgICBzdW1tYXJpc2UoZml0dmFsPW1lZGlhbihmaXRuZXNzKSwKICAgICAgICAgICAgICBudW1wb2ludHM9bigpLAogICAgICAgICAgICAgIHN0ZGZpdD1zZChmaXRuZXNzKSkKICAKICAjdGhlc2UgbWF0cmljZXMgaGF2ZSB0aGUgZml0bmVzcy9udW0vc2QgZm9yIGVhY2ggYWEgYXQgZWFjaCBwb3NpdGlvbjoKICBCTVNfbWF0cml4MTVfcGVyZmVjdHNfYW5kXzEgPSBtYXRyaXgocmVwKE5BLCBCTVNfcmVmX2xlbipCTVNfYWFfZGltKSxucm93PUJNU19hYV9kaW0sbmNvbD1CTVNfcmVmX2xlbikKICBCTVNfbWF0cml4MTVfcGVyZmVjdHNfYW5kXzFfbnVtID0gbWF0cml4KHJlcChOQSwgQk1TX3JlZl9sZW4qQk1TX2FhX2RpbSksbnJvdz1CTVNfYWFfZGltLG5jb2w9Qk1TX3JlZl9sZW4pCiAgQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX3NkID0gbWF0cml4KHJlcChOQSwgQk1TX3JlZl9sZW4qQk1TX2FhX2RpbSksbnJvdz1CTVNfYWFfZGltLG5jb2w9Qk1TX3JlZl9sZW4pCiAgCiAgI3BvcHVsYXRlIG1hdHJpeAogIGZvciAoaSBpbiAxOm5yb3coQk1TX2ZpdG5lc3MxNV9jb2xsYXBzZWRfYWxsKSl7CiAgICAKICAgIEJNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMVt3aGljaChCTVNfYWFfbGlzdCRhYT09YXMuY2hhcmFjdGVyKEJNU19maXRuZXNzMTVfY29sbGFwc2VkX2FsbCRhYVtpXSkpLEJNU19maXRuZXNzMTVfY29sbGFwc2VkX2FsbCRwb3NpdGlvbltpXV0gPC0gYXMubnVtZXJpYyhCTVNfZml0bmVzczE1X2NvbGxhcHNlZF9hbGwkZml0dmFsW2ldKQogICAgQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX251bVt3aGljaChCTVNfYWFfbGlzdCRhYT09YXMuY2hhcmFjdGVyKEJNU19maXRuZXNzMTVfY29sbGFwc2VkX2FsbCRhYVtpXSkpLEJNU19maXRuZXNzMTVfY29sbGFwc2VkX2FsbCRwb3NpdGlvbltpXV0gPC0gYXMubnVtZXJpYyhCTVNfZml0bmVzczE1X2NvbGxhcHNlZF9hbGwkbnVtcG9pbnRzW2ldKQogICAgQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX3NkW3doaWNoKEJNU19hYV9saXN0JGFhPT1hcy5jaGFyYWN0ZXIoQk1TX2ZpdG5lc3MxNV9jb2xsYXBzZWRfYWxsJGFhW2ldKSksQk1TX2ZpdG5lc3MxNV9jb2xsYXBzZWRfYWxsJHBvc2l0aW9uW2ldXSA8LSBhcy5udW1lcmljKEJNU19maXRuZXNzMTVfY29sbGFwc2VkX2FsbCRzdGRmaXRbaV0pCiAgfQogIAogIHJvd25hbWVzKEJNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMSk8LUJNU19hYV9saXN0JGFhCiAgY29sbmFtZXMoQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xKTwtYygxOkJNU19yZWZfbGVuKQogIHJvd25hbWVzKEJNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMV9udW0pPC1CTVNfYWFfbGlzdCRhYQogIGNvbG5hbWVzKEJNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMV9udW0pPC1jKDE6Qk1TX3JlZl9sZW4pCiAgcm93bmFtZXMoQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX3NkKTwtQk1TX2FhX2xpc3QkYWEKICBjb2xuYW1lcyhCTVNfbWF0cml4MTVfcGVyZmVjdHNfYW5kXzFfc2QpPC1jKDE6Qk1TX3JlZl9sZW4pCiAgCiAgQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX21lbHQgPC0gbWVsdChCTVNfbWF0cml4MTVfcGVyZmVjdHNfYW5kXzEpCiAgQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX251bV9tZWx0IDwtIG1lbHQoQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX251bSkKICBCTVNfbWF0cml4MTVfcGVyZmVjdHNfYW5kXzFfc2RfbWVsdCA8LSBtZWx0KEJNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMV9zZCkKICAKICAjIFJlbmFtZSBjb2x1bW5zIHRvICJYMSIgYW5kICJYMiIKICBuYW1lcyhCTVNfbWF0cml4MTVfcGVyZmVjdHNfYW5kXzFfbWVsdClbbmFtZXMoQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX21lbHQpICVpbiUgYygiVmFyMSIsICJWYXIyIildIDwtIGMoIlgxIiwgIlgyIikKICBuYW1lcyhCTVNfbWF0cml4MTVfcGVyZmVjdHNfYW5kXzFfbnVtX21lbHQpW25hbWVzKEJNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMV9udW1fbWVsdCkgJWluJSBjKCJWYXIxIiwgIlZhcjIiKV0gPC0gYygiWDEiLCAiWDIiKQogIG5hbWVzKEJNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMV9zZF9tZWx0KVtuYW1lcyhCTVNfbWF0cml4MTVfcGVyZmVjdHNfYW5kXzFfc2RfbWVsdCkgJWluJSBjKCJWYXIxIiwgIlZhcjIiKV0gPC0gYygiWDEiLCAiWDIiKQogIAogICNDYWxjdWxhdGUgbWluaW11bSBhbmQgbWF4aW11bSBmaXRuZXNzIHZhbHVlcyBmb3IgZWFjaCBtdXRhdGlvbiBsZXZlbDoKICBtaW4oQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX21lbHQkdmFsdWUsIG5hLnJtPVQpCiAgbWF4KEJNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMV9tZWx0JHZhbHVlLCBuYS5ybT1UKQogIAogICNwbG90IHRoZSBkYXRhIGZyb20gdGhlc2UgbXV0YW50czoKICBnZ3Bsb3QoZGF0YSA9IEJNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMV9tZWx0LCBhZXMoeD1YMiwgeT1YMSwgZmlsbD12YWx1ZSkpICsKICAgIGdlb21fdGlsZSgpKyBsYWJzKHggPSAiUG9zaXRpb24gKGFhKSIsIHkgPSJBbWlubyBhY2lkIixjb2xvcj0iIikgKwogICAgc2NhbGVfZmlsbF9ncmFkaWVudDIobG93ID0gImJsdWUiLCBoaWdoID0gInJlZCIsIG1pZD0iZ29sZCIsbmFtZT0iRml0bmVzcyIsbmEudmFsdWU9ImdyZXkiLCBsaW1pdCA9IGMobWluKEJNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMV9zZF9tZWx0JHZhbHVlKSxtYXgoQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX3NkX21lbHQkdmFsdWUpKSkgKwogICAgdGhlbWVfbWluaW1hbCgpICsgCiAgICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzPXNlcSgwLDE1NSw1KSkKICBnZ3NhdmUoZmlsZT1wYXN0ZSgiQk1TL1BMT1RTL0NvbXBsZW1lbnRhdGlvbi9CTVNfYWxscG9zMTVfbWluIix0b1N0cmluZyhCTVNfbWluX0JDcyksIl9tYXgiLHRvU3RyaW5nKEJNU19jdXJfbXV0X251bSksIl9maXQucGRmIixzZXA9IiIpKQogIAogIHdyaXRlLnRhYmxlKEJNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMV9tZWx0LCAKICAgICAgICAgICAgICBmaWxlID0gcGFzdGUoIkJNUy9PVVRQVVQvQ29tcGxlbWVudGF0aW9uL0JNU19tYXRyaXhfcGVyZmVjdHNfYW5kXyIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICBhcy5jaGFyYWN0ZXIoQk1TX2N1cl9tdXRfbnVtKSwiX2ZpdF9tZWx0LmNzdiIsc2VwPSIiKSwgCiAgICAgICAgICAgIHNlcCA9ICIsIiwgcm93Lm5hbWVzID0gRixxdW90ZT1GLGNvbC5uYW1lcyA9IFQpCiAgCiAgZ2dwbG90KGRhdGEgPSBCTVNfbWF0cml4MTVfcGVyZmVjdHNfYW5kXzFfbnVtX21lbHQsIGFlcyh4PVgyLCB5PVgxLCBmaWxsPWxvZyh2YWx1ZSkpKSArCiAgICBnZW9tX3RpbGUoKSsgbGFicyh4ID0gIlBvc2l0aW9uIChhYSkiLCB5ID0iQW1pbm8gYWNpZCIsY29sb3I9IiIpICsKICAgIHNjYWxlX2ZpbGxfZ3JhZGllbnQobG93ID0gImJsdWUiLCBoaWdoID0gInJlZCIsbmFtZT0ibG9nKCNwb2ludHMpIixuYS52YWx1ZT0iZ3JleSIsIAogICAgICAgICAgICAgICAgICAgICAgICBsaW1pdCA9IGMoMCxtYXgoQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX251bV9tZWx0JHZhbHVlKSkpICsKICAgIHRoZW1lX21pbmltYWwoKSArIAogICAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcz1zZXEoMCwxNTUsNSkpCiAgZ2dzYXZlKGZpbGU9cGFzdGUoIkJNUy9QTE9UUy9Db21wbGVtZW50YXRpb24vQk1TX2FsbHBvczE1X21pbiIsdG9TdHJpbmcoQk1TX21pbl9CQ3MpLCJfbWF4Iix0b1N0cmluZyhCTVNfY3VyX211dF9udW0pLCJfbnVtLnBkZiIsc2VwPSIiKSkKICAKICB3cml0ZS50YWJsZShCTVNfbWF0cml4MTVfcGVyZmVjdHNfYW5kXzFfbnVtX21lbHQsIAogICAgICAgICAgICAgIGZpbGUgPSBwYXN0ZSgiQk1TL09VVFBVVC9Db21wbGVtZW50YXRpb24vQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8iLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgYXMuY2hhcmFjdGVyKEJNU19jdXJfbXV0X251bSksIl9udW1fbWVsdC5jc3YiLHNlcD0iIiksIAogICAgICAgICAgICBzZXAgPSAiLCIsIHJvdy5uYW1lcyA9IEYscXVvdGU9Rixjb2wubmFtZXMgPSBUKQogIAogIGdncGxvdChkYXRhID0gQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX3NkX21lbHQsIGFlcyh4PVgyLCB5PVgxLCBmaWxsPXZhbHVlKSkgKwogICAgZ2VvbV90aWxlKCkrIGxhYnMoeCA9ICJQb3NpdGlvbiAoYWEpIiwgeSA9IkFtaW5vIGFjaWQiLGNvbG9yPSIiKSArCiAgICBzY2FsZV9maWxsX2dyYWRpZW50Mihsb3cgPSAiYmx1ZSIsIGhpZ2ggPSAicmVkIiwgbWlkPSJnb2xkIixuYW1lPSJzdGQoRml0bmVzcykiLG5hLnZhbHVlPSJncmV5IiwgbGltaXQgPSBjKDAsMS4xKm1heChCTVNfbWF0cml4MTVfcGVyZmVjdHNfYW5kXzFfc2RfbWVsdCR2YWx1ZSkpKSArCiAgICB0aGVtZV9taW5pbWFsKCkgKyAKICAgIHNjYWxlX3hfY29udGludW91cyhicmVha3M9c2VxKDAsMTU1LDUpKQogIGdnc2F2ZShmaWxlPXBhc3RlKCJCTVMvUExPVFMvQ29tcGxlbWVudGF0aW9uL0JNU19hbGxwb3MxNV9taW4iLHRvU3RyaW5nKEJNU19taW5fQkNzKSwiX21heCIsdG9TdHJpbmcoQk1TX2N1cl9tdXRfbnVtKSwiX3NkLnBkZiIsc2VwPSIiKSkKICAKICB3cml0ZS50YWJsZShCTVNfbWF0cml4MTVfcGVyZmVjdHNfYW5kXzFfc2RfbWVsdCwgCiAgICAgICAgICAgICAgZmlsZSA9IHBhc3RlKCJCTVMvT1VUUFVUL0NvbXBsZW1lbnRhdGlvbi9CTVNfbWF0cml4MTVfcGVyZmVjdHNfYW5kXyIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICBhcy5jaGFyYWN0ZXIoQk1TX2N1cl9tdXRfbnVtKSwiX3NkX21lbHQuY3N2IixzZXA9IiIpLCAKICAgICAgICAgICAgc2VwID0gIiwiLCByb3cubmFtZXMgPSBGLHF1b3RlPUYsY29sLm5hbWVzID0gVCkKfQpgYGAKCiMjIyBCTVMgQ3VzdG9tIEJ1aWxkCgpJbXBvcnQgdGhlIGBCTVNfbWF0cml4MTVfcGVyZmVjdHNfYW5kXzVfbWVsdGAgZGF0YXNldCBiYXNlZCBvbiA1IGEuYS4gbXV0YXRpb25zOgpgYGB7cn0KIyBJbXBvcnQgdGhlIEJNU19tYXRyaXgxNSBkYXRhc2V0IGJhc2VkIG9uIDEgYS5hLiBtdXRhdGlvbjoKQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX21lbHQgPC0gcmVhZC5jc3YoIkJNUy9PVVRQVVQvQ29tcGxlbWVudGF0aW9uL0JNU19tYXRyaXhfcGVyZmVjdHNfYW5kXzVfZml0X21lbHQuY3N2IiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhlYWRlciA9IFRSVUUpCmBgYAoKQmVnaW4gYnkgYWRkaW5nIGEgbmV3IGNvbHVtbiBmb3IgdGhlIFdUIHJlc2lkdWVzOgpgYGB7cn0KQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX21lbHQkV1Rjb2xvciA8LSBOQQojWDEgaXMgYWEsIFgyIGlzIHBvc2l0aW9uCgpCTVNfbWF0cml4MTVfcGVyZmVjdHNfYW5kXzFfbWVsdCRhYW51bSA8LSAwCgpmb3IgKGkgaW4gMTpucm93KEJNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMV9tZWx0KSl7CiAgCiAgI2ZpbmQgdGhlIGNvcnJlc3BvbmRpbmcgcmVzaWR1ZSBpbiBFLmNvbGkgdXNpbmcgTVNBCiAgQk1TX2NvbnNfYWFudW0gPC0gQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX21lbHQkWDJbaV0KICAKICAjaXMgdGhpcyB0aGUgV1QgRS5jb2xpIHJlc2lkdWU/CiAgaWYgKEJNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMV9tZWx0JFgxW2ldID09IHN1YnN0cihCTVNfZWNvbGlfc2VxLCBCTVNfY29uc19hYW51bSwgQk1TX2NvbnNfYWFudW0pKXsKICAgIAogICAgI2Fzc2lnbiBXVCBjb2xvcgogICAgQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX21lbHQkV1Rjb2xvcltpXSA8LSAicmVkIgogICAgCiAgfQogIAogIEJNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMV9tZWx0JGFhbnVtW2ldIDwtIHdoaWNoKEJNU19hYV9saXN0JGFhID09IEJNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMV9tZWx0JFgxW2ldKQogIAp9CmBgYAoKUmVhZCBpbiB0aGUgUlNBLCBTUywgYW5kIENvbnNlcnZhdGlvbiBmaWxlczoKYGBge3J9ClJTQV8xSDFUIDwtIHJlYWQudGFibGUoIkJNUy9EU1NQLzRLSksuUlNBcy50eHQiLCBza2lwID0gMiwgaGVhZGVyID0gRkFMU0UpClNTXzFIMVQgPC0gcmVhZC50YWJsZSgiQk1TL0RTU1AvNEtKSy5TU3MudHh0Iiwgc2tpcCA9IDIsIGhlYWRlciA9IEZBTFNFKQpjb25zXzFIMVQgPC0gcmVhZC50YWJsZSgiQk1TL0RTU1AvTGliMTUuSlNELm91dC50eHQiLCBza2lwID0gNSwgaGVhZGVyID0gRkFMU0UpCmBgYAoKSW4gdGhlIGNvbnNlcnZhdGlvbiBzY29yZSBmaWxlLCByZXBsYWNlIHZhbHVlcyBvZiAtMTAwMCB3aXRoIE5BIHRvIGlnbm9yZSBkb3duc3RyZWFtOgpgYGB7cn0KIyBSZXBsYWNlIHZhbHVlcyBvZiAtMTAwMCB3aXRoIE5BOgpjb25zXzFIMVQkVjJbY29uc18xSDFUJFYyID09IC0xMDAwXSA8LSBOQQpgYGAKCkFkZCBpbiB0aGUgUlNBLCBTUywgYW5kIENvbnNlcnZhdGlvbiBzY29yZXMgZm9yIHBsb3R0aW5nOgpgYGB7cn0KY29sbmFtZXMoY29uc18xSDFUKSA8LSBjKCJwb3MiLCJjb25zIiwiY29sIikKY29uc18xSDFUJHBvcyA8LSBjb25zXzFIMVQkcG9zKzEKCnByb3RlaW5faW5mb18xSDFUIDwtIFJTQV8xSDFUICU+JQogIHJpZ2h0X2pvaW4oU1NfMUgxVCwgYnk9IlYxIikKCmNvbG5hbWVzKHByb3RlaW5faW5mb18xSDFUKSA8LSBjKCJwb3MiLCJSU0EiLCJTUyIpCgpwcm90ZWluX2luZm9fMUgxVCRjb25zIDwtIDAKCmZvciAoaSBpbiAxOm5yb3coY29uc18xSDFUKSl7CiAgI2dldCBNU0EgcG9zaXRpb246IGNvbnNfMUgxVCRwb3NbaV0KICAjZmluZCBpZiB0aGlzIHBvc2l0aW9uIGV4aXN0cyBpbiB0aGUgZS5jb2xpIE1TQQogIGlmIChjb25zXzFIMVQkcG9zW2ldICVpbiUgQk1TX2Vjb2xpX21hcCRtc2FfYWFudW0pewogICAgI2dldCBjb3JyZXNwb25kaW5nIHJlc2lkdWUKICAgIGVfY29saV9yZXNpZHVlIDwtIEJNU19lY29saV9tYXAkb3J0aF9hYW51bVt3aGljaChCTVNfZWNvbGlfbWFwJG1zYV9hYW51bT09Y29uc18xSDFUJHBvc1tpXSldCiAgICBwcm90ZWluX2luZm9fMUgxVCRjb25zW3doaWNoKHByb3RlaW5faW5mb18xSDFUJHBvcz09ZV9jb2xpX3Jlc2lkdWUpXSA8LSBjb25zXzFIMVQkY29uc1tpXQogIH0KfQpgYGAKCk1lcmdlIGBwcm90ZWluX2luZm9fMUgxVGAgd2l0aCB0aGUgYEJNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMV9tZWx0YCBmaXRuZXNzIHNjb3JlcyBmb3IgZWFjaCBhLmEuIGF0IGVhY2ggcG9zaXRpb246CmBgYHtyfQpwcm90ZWluX2luZm9fMUgxVCA8LSBCTVNfbWF0cml4MTVfcGVyZmVjdHNfYW5kXzFfbWVsdCAlPiUKICBmaWx0ZXIoWDEgIT0gIlgiKSAlPiUKICBncm91cF9ieShYMikgJT4lCiAgZHBseXI6OnJlbmFtZShwb3M9WDIpICU+JQogIHN1bW1hcmlzZShhdmdmaXQ9bWVhbih2YWx1ZSxuYS5ybT1UUlVFKSwKICAgICAgICAgICAgbnVtY292PTIwLXN1bShpcy5uYSh2YWx1ZSkpKSAlPiUKICByaWdodF9qb2luKHByb3RlaW5faW5mb18xSDFULCBieT0icG9zIikKCnByb3RlaW5faW5mb18xSDFUX21lbHQgPC0gbWVsdChhcy5kYXRhLmZyYW1lKHByb3RlaW5faW5mb18xSDFUKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlkPWMoInBvcyIpKQoKcHJvdGVpbl9pbmZvXzFIMVRfbWVsdCR5dmFsIDwtIDAKcHJvdGVpbl9pbmZvXzFIMVRfbWVsdCR5dmFsW3doaWNoKHByb3RlaW5faW5mb18xSDFUX21lbHQkdmFyaWFibGU9PSJhdmdmaXQiKV0gPC0gMjMKcHJvdGVpbl9pbmZvXzFIMVRfbWVsdCR5dmFsW3doaWNoKHByb3RlaW5faW5mb18xSDFUX21lbHQkdmFyaWFibGU9PSJSU0EiKV0gPC0gMjQKcHJvdGVpbl9pbmZvXzFIMVRfbWVsdCR5dmFsW3doaWNoKHByb3RlaW5faW5mb18xSDFUX21lbHQkdmFyaWFibGU9PSJTUyIpXSA8LSAyNQoKQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX21lbHQkYWFudW1bd2hpY2goQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX21lbHQkWDE9PSJYIildIDwtIDIxLjMKYGBgCgpDYWxjdWxhdGUgdGhlIG1pbi4gYW5kIG1heC4gYXZnZml0IHZhbHVlcyBpbiBgcHJvdGVpbl9pbmZvXzFIMVRgOgpgYGB7ciBjbGFzcy5vdXRwdXQ9Imdvb2RDb2RlIn0KIyBNaW5pbXVtIGF2Z2ZpdDoKbWluKHByb3RlaW5faW5mb18xSDFUJGF2Z2ZpdCwgbmEucm0gPSBUKQoKIyBNYXhpbWl1bSBhdmdmaXQ6Cm1heChwcm90ZWluX2luZm9fMUgxVCRhdmdmaXQsIG5hLnJtID0gVCkKYGBgCgpHZW5lcmF0ZSBhIGZpdG5lc3MgdmFsdWVzIGZpbGUgZnJvbSB0aGUgcHJvdGVpbl9pbmZvXzFIMVQgb2JqZWN0OgpgYGB7cn0Kc2luaygiQk1TL3N0cnVjdHVyZXMvYXZnX2ZpdF80S0pLX2NvbXBsZW1lbnRhdGlvbl9hdHRyaWJ1dGUuZGVmYXR0ciIpCmNhdCgiIyBCTVMgZGF0YSBmb3IgNEtKS1xuIikKY2F0KCJhdHRyaWJ1dGU6IHBlcmNlbnRFeHBvc2VkXG4iKQpjYXQoIm1hdGNoIG1vZGU6IDEtdG8tMVxuIikKY2F0KCJyZWNpcGllbnQ6IHJlc2lkdWVzXG4iKQpmb3IgKGkgaW4gMTpucm93KHByb3RlaW5faW5mb18xSDFUKSl7CiAgY2F0KHBhc3RlKCJcdDoiLAogICAgICAgICAgICBhcy5jaGFyYWN0ZXIocHJvdGVpbl9pbmZvXzFIMVQkcG9zW2ldKSwKICAgICAgICAgICAgIlx0IiwKICAgICAgICAgICAgYXMuY2hhcmFjdGVyKHByb3RlaW5faW5mb18xSDFUJGF2Z2ZpdFtpXSksCiAgICAgICAgICAgICJcbiIsCiAgICAgICAgICAgIHNlcD0iIikpCn0KCnNpbmsoKQpgYGAKCkRldGVybWluZSBjb3ZlcmFnZToKYGBge3Igd2FybmluZz1GQUxTRSwgY2xhc3Mub3V0cHV0PSJnb29kQ29kZSJ9Cihucm93KEJNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMV9tZWx0ICU+JQogICAgICAgICAgICBmaWx0ZXIoWDEgIT0gIlgiKSAlPiUKICAgICAgICAgICAgZHBseXI6OnNlbGVjdCh2YWx1ZSkpIC0KICBzdW0oaXMubmEoQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX21lbHQgJT4lCiAgICAgICAgICAgICAgZmlsdGVyKFgxICE9ICJYIikgJT4lCiAgICAgICAgICAgICAgZHBseXI6OnNlbGVjdCh2YWx1ZSkpKSkvbnJvdyhCTVNfbWF0cml4MTVfcGVyZmVjdHNfYW5kXzFfbWVsdCAlPiUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsdGVyKFgxICE9ICJYIikgJT4lCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRwbHlyOjpzZWxlY3QodmFsdWUpKQoKCgpCTVNfRmlnX3ltaW5saW0gPC0gLTIKQk1TX0ZpZ195bWF4bGltIDwtIDMwCkJNU19GaWdfeG1pbmxpbSA8LSAtMQpCTVNfRmlnX3htYXhsaW0gPC0gMTYwCgojIFJlYWQgaW4gQk1TX2ZpdG5lc3MxNV9tYXBfMSB3aXRoIDEgYS5hLiBtdXRhdGlvbgpCTVNfMW11dDE1X2RlbCA8LSByZWFkLnRhYmxlKCJCTVMvT1VUUFVUL0NvbXBsZW1lbnRhdGlvbi9CTVNfZml0bmVzczE1X21hcF8xLmNzdiIsIHNraXAgPSAwICwgc2VwPSIsIixoZWFkZXIgPSBUUlVFICkKCiMgQ2FsY3VsYXRlIGFsbCBkYXRhIGFuZCBzdGF0cwpCTVNfMW11dDE1X2RlbF9jb2xsYXBzZWRfYWxsIDwtIEJNU18xbXV0MTVfZGVsICU+JQogIGZpbHRlcihwb3NpdGlvbiA+IDApICU+JQogIGdyb3VwX2J5KHBvc2l0aW9uLCBhYSkgJT4lCiAgc3VtbWFyaXNlKGZpdHZhbD1tZWRpYW4oZml0bmVzcyksCiAgICAgICAgICAgIG51bXBvaW50cz1uKCksCiAgICAgICAgICAgIHN0ZGZpdD1zZChmaXRuZXNzKSkKCiMgVGhlc2UgbWF0cmljZXMgaGF2ZSB0aGUgZml0bmVzcy9udW0vc2QgZm9yIGVhY2ggYWEgYXQgZWFjaCBwb3NpdGlvbjoKQk1TXzFtdXQxNV9kZWxfbWF0cml4ID0gbWF0cml4KHJlcChOQSwgQk1TX3JlZl9sZW4qQk1TX2FhX2RpbSksbnJvdz1CTVNfYWFfZGltLG5jb2w9Qk1TX3JlZl9sZW4pCgojIFBvcHVsYXRlIG1hdHJpeApmb3IgKGkgaW4gMTpucm93KEJNU18xbXV0MTVfZGVsX2NvbGxhcHNlZF9hbGwpKXsKICAKICBCTVNfMW11dDE1X2RlbF9tYXRyaXhbd2hpY2goQk1TX2FhX2xpc3QkYWE9PWFzLmNoYXJhY3RlcihCTVNfMW11dDE1X2RlbF9jb2xsYXBzZWRfYWxsJGFhW2ldKSksQk1TXzFtdXQxNV9kZWxfY29sbGFwc2VkX2FsbCRwb3NpdGlvbltpXV0gPC0gYXMubnVtZXJpYyhCTVNfMW11dDE1X2RlbF9jb2xsYXBzZWRfYWxsJGZpdHZhbFtpXSkKfQoKcm93bmFtZXMoQk1TXzFtdXQxNV9kZWxfbWF0cml4KTwtQk1TX2FhX2xpc3QkYWEKY29sbmFtZXMoQk1TXzFtdXQxNV9kZWxfbWF0cml4KTwtYygxOkJNU19yZWZfbGVuKQoKQk1TXzFtdXQxNV9kZWxfbWF0cml4X21lbHQgPC0gbWVsdChCTVNfMW11dDE1X2RlbF9tYXRyaXgpCgojIFJlbmFtZSBjb2x1bW5zIHRvICJYMSIgYW5kICJYMiIKbmFtZXMoQk1TXzFtdXQxNV9kZWxfbWF0cml4X21lbHQpW25hbWVzKEJNU18xbXV0MTVfZGVsX21hdHJpeF9tZWx0KSAlaW4lIGMoIlZhcjEiLCAiVmFyMiIpXSA8LSBjKCJYMSIsICJYMiIpCgpCTVNfMW11dDE1X2RlbF9tYXRyaXhfbWVsdCA8LSBCTVNfMW11dDE1X2RlbF9tYXRyaXhfbWVsdCAlPiUKICBmaWx0ZXIoWDE9PSJYIikKQk1TXzFtdXQxNV9kZWxfbWF0cml4X21lbHQkV1Rjb2xvciA8LSBOQQpCTVNfMW11dDE1X2RlbF9tYXRyaXhfbWVsdCRhYW51bSA8LSAyMS4zCiAgCkJNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMV9tZWx0IDwtIEJNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMV9tZWx0ICU+JQogIGZpbHRlcihYMSE9IlgiKQoKQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX21lbHQgPC0gcmJpbmQoQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX21lbHQsQk1TXzFtdXQxNV9kZWxfbWF0cml4X21lbHQpCgpCTVNfbWF0cml4MTVfcGVyZmVjdHNfYW5kXzFfbWVsdCRhYW51bVt3aGljaChCTVNfbWF0cml4MTVfcGVyZmVjdHNfYW5kXzFfbWVsdCRYMT09IkEiKV0gPC0gMTIKQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX21lbHQkYWFudW1bd2hpY2goQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX21lbHQkWDE9PSJDIildIDwtIDEwCkJNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMV9tZWx0JGFhbnVtW3doaWNoKEJNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMV9tZWx0JFgxPT0iRCIpXSA8LSA1CkJNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMV9tZWx0JGFhbnVtW3doaWNoKEJNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMV9tZWx0JFgxPT0iRSIpXSA8LSA0CkJNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMV9tZWx0JGFhbnVtW3doaWNoKEJNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMV9tZWx0JFgxPT0iRiIpXSA8LSAxOQpCTVNfbWF0cml4MTVfcGVyZmVjdHNfYW5kXzFfbWVsdCRhYW51bVt3aGljaChCTVNfbWF0cml4MTVfcGVyZmVjdHNfYW5kXzFfbWVsdCRYMT09IkciKV0gPC0gMTEKQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX21lbHQkYWFudW1bd2hpY2goQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX21lbHQkWDE9PSJIIildIDwtIDMKQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX21lbHQkYWFudW1bd2hpY2goQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX21lbHQkWDE9PSJJIildIDwtIDE1CkJNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMV9tZWx0JGFhbnVtW3doaWNoKEJNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMV9tZWx0JFgxPT0iSyIpXSA8LSAxCkJNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMV9tZWx0JGFhbnVtW3doaWNoKEJNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMV9tZWx0JFgxPT0iTCIpXSA8LSAxNApCTVNfbWF0cml4MTVfcGVyZmVjdHNfYW5kXzFfbWVsdCRhYW51bVt3aGljaChCTVNfbWF0cml4MTVfcGVyZmVjdHNfYW5kXzFfbWVsdCRYMT09Ik0iKV0gPC0gMTYKQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX21lbHQkYWFudW1bd2hpY2goQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX21lbHQkWDE9PSJOIildIDwtIDYKQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX21lbHQkYWFudW1bd2hpY2goQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX21lbHQkWDE9PSJQIildIDwtIDE3CkJNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMV9tZWx0JGFhbnVtW3doaWNoKEJNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMV9tZWx0JFgxPT0iUSIpXSA8LSA3CkJNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMV9tZWx0JGFhbnVtW3doaWNoKEJNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMV9tZWx0JFgxPT0iUiIpXSA8LSAyCkJNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMV9tZWx0JGFhbnVtW3doaWNoKEJNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMV9tZWx0JFgxPT0iUyIpXSA8LSA5CkJNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMV9tZWx0JGFhbnVtW3doaWNoKEJNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMV9tZWx0JFgxPT0iVCIpXSA8LSA4CkJNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMV9tZWx0JGFhbnVtW3doaWNoKEJNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMV9tZWx0JFgxPT0iViIpXSA8LSAxMwpCTVNfbWF0cml4MTVfcGVyZmVjdHNfYW5kXzFfbWVsdCRhYW51bVt3aGljaChCTVNfbWF0cml4MTVfcGVyZmVjdHNfYW5kXzFfbWVsdCRYMT09IlciKV0gPC0gMjAKQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX21lbHQkYWFudW1bd2hpY2goQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX21lbHQkWDE9PSJZIildIDwtIDE4CkJNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMV9tZWx0JGFhbnVtW3doaWNoKEJNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMV9tZWx0JFgxPT0iWCIpXSA8LSAyMi42CgpCTVNfbWF0cml4MTVfcGVyZmVjdHNfYW5kXzFfbWVsdF9XVCA8LSBCTVNfbWF0cml4MTVfcGVyZmVjdHNfYW5kXzFfbWVsdCAlPiUKICBmaWx0ZXIoV1Rjb2xvcj09InJlZCIpCmBgYAoKQ2FsY3VsYXRlIHRoZSBtaW4uIGFuZCBtYXguIHZhbHVlIGluIGBCTVNfbWF0cml4MTVfcGVyZmVjdHNfYW5kXzFfbWVsdGAgZm9yIHBsb3R0aW5nIGJlbG93OgpgYGB7ciBjbGFzcy5vdXRwdXQ9Imdvb2RDb2RlIn0KIyBNaW5pbXVtIHZhbHVlOgptaW4oQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX21lbHQkdmFsdWUsIG5hLnJtID0gVCkKCiMgTWF4aW1pdW0gdmFsdWU6Cm1heChCTVNfbWF0cml4MTVfcGVyZmVjdHNfYW5kXzFfbWVsdCR2YWx1ZSwgbmEucm0gPSBUKQpgYGAKCiMjIyBCTVMgQ3VzdG9tIFBsb3QKCkFkanVzdCB0aGUgaW5kZXggcG9zaXRpb24gdG8gc2hpZnQgdG8gdGhlIHJpZ2h0OgpgYGB7cn0KQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX21lbHRfd2l0aE0gPC0gQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX21lbHQgJT4lCiAgZmlsdGVyKFgyPT0xKSAlPiUKICBtdXRhdGUoWDI9MCwgdmFsdWU9TkEpCgpCTVNfbWF0cml4MTVfcGVyZmVjdHNfYW5kXzFfbWVsdF9maXggPC0gcmJpbmQoQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX21lbHRfd2l0aE0sIEJNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMV9tZWx0KQpgYGAKCkJyaW5nIGl0IGFsbCB0b2dldGhlciB0byBidWlsZCBhIGN1c3RvbSBCTVMgcGxvdCBmb3IgQ09NUExFTUVOVEFUSU9OIChmaXREMDVEMDMpIGZvciBMSUJSQVJZIDE1OgpgYGB7cn0KQk1TX3Bsb3QxNSA8LSBnZ3Bsb3QoKSArCiAgI0JNUyBtYXRyaXgKICBnZW9tX3JlY3QoZGF0YT1CTVNfbWF0cml4MTVfcGVyZmVjdHNfYW5kXzFfbWVsdF9maXgsYWVzKHhtaW49WDIrMSwgeG1heD1YMisyLCB5bWluPWFhbnVtLCB5bWF4PWFhbnVtKzEsIGZpbGw9dmFsdWUpKSsKICAjV1Qgc2VxCiAgZ2VvbV9yZWN0KGRhdGE9Qk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX21lbHRfV1QsYWVzKHhtaW49WDIrMSwgeG1heD1YMisyLCB5bWluPWFhbnVtLCB5bWF4PWFhbnVtKzEpLCBjb2xvcj0iYmxhY2siLGFscGhhPTApKwogICNhdmcgZml0CiAgZ2VvbV9yZWN0KGRhdGE9cHJvdGVpbl9pbmZvXzFIMVQsYWVzKHhtaW49cG9zKzEsIHhtYXg9cG9zKzIsIHltaW49MjEuMywgeW1heD0yMi4zLGZpbGw9YXZnZml0KSkgKwogIGxhYnMoeCA9ICJQb3NpdGlvbiAoYWEpIiwgeSA9IkFtaW5vIGFjaWQiLGNvbG9yPSIiKSArCiAgc2NhbGVfZmlsbF9ncmFkaWVudDIobG93ID0gImJsdWUiLCBoaWdoID0gInJlZCIsIG1pZD0iZ29sZCIsbmFtZT0iRml0bmVzcyIsbmEudmFsdWU9ImdyZXkiLCBsaW1pdCA9IGMoLTYsMykpICsgCiAgZ2VvbV90ZXh0KGRhdGE9Qk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX21lbHRbMToyMSxdLCBhZXMoeD0tMSwgeT1hYW51bSswLjUsIGxhYmVsPVgxKSwgc2l6ZT0zLjUpKwogIGdlb21fdGV4dChkYXRhPWRhdGEuZnJhbWUocG9zPXNlcSgwLDE1MCwzMCkpLGFlcyh4PXBvcyswLjUseT0wLGxhYmVsPXBvcykpKwogIGdlb21fc2VnbWVudChhZXMoeCA9IDE0LjUsIHkgPSAxLCB4ZW5kID0gMTQuNSwgeWVuZCA9IDApLCBjb2xvdXIgPSAiYmx1ZSIpKwogIGdlb21fc2VnbWVudChhZXMoeCA9IDE1LjUsIHkgPSAxLCB4ZW5kID0gMTUuNSwgeWVuZCA9IDApLCBjb2xvdXIgPSAiYmx1ZSIpKwogIGdlb21fc2VnbWVudChhZXMoeCA9IDE2LjUsIHkgPSAxLCB4ZW5kID0gMTYuNSwgeWVuZCA9IDApLCBjb2xvdXIgPSAiYmx1ZSIpKwogIGdlb21fc2VnbWVudChhZXMoeCA9IDE3LjUsIHkgPSAxLCB4ZW5kID0gMTcuNSwgeWVuZCA9IDApLCBjb2xvdXIgPSAiYmx1ZSIpKwogIGdlb21fc2VnbWVudChhZXMoeCA9IDIwLjUsIHkgPSAxLCB4ZW5kID0gMjAuNSwgeWVuZCA9IDApLCBjb2xvdXIgPSAiYmx1ZSIpKwogIGdlb21fc2VnbWVudChhZXMoeCA9IDI3LjUsIHkgPSAxLCB4ZW5kID0gMjcuNSwgeWVuZCA9IDApLCBjb2xvdXIgPSAiYmx1ZSIpKwogIGdlb21fc2VnbWVudChhZXMoeCA9IDMxLjUsIHkgPSAxLCB4ZW5kID0gMzEuNSwgeWVuZCA9IDApLCBjb2xvdXIgPSAiYmx1ZSIpKwogIGdlb21fc2VnbWVudChhZXMoeCA9IDMyLjUsIHkgPSAxLCB4ZW5kID0gMzIuNSwgeWVuZCA9IDApLCBjb2xvdXIgPSAiYmx1ZSIpKwogIGdlb21fc2VnbWVudChhZXMoeCA9IDQ2LjUsIHkgPSAxLCB4ZW5kID0gNDYuNSwgeWVuZCA9IDApLCBjb2xvdXIgPSAiYmx1ZSIpKwogIGdlb21fc2VnbWVudChhZXMoeCA9IDUyLjUsIHkgPSAxLCB4ZW5kID0gNTIuNSwgeWVuZCA9IDApLCBjb2xvdXIgPSAiYmx1ZSIpKwogIGdlb21fc2VnbWVudChhZXMoeCA9IDU3LjUsIHkgPSAxLCB4ZW5kID0gNTcuNSwgeWVuZCA9IDApLCBjb2xvdXIgPSAiYmx1ZSIpKwogIGdlb21fc2VnbWVudChhZXMoeCA9IDk0LjUsIHkgPSAxLCB4ZW5kID0gOTQuNSwgeWVuZCA9IDApLCBjb2xvdXIgPSAiYmx1ZSIpKwogIGdlb21fc2VnbWVudChhZXMoeCA9IDEwMC41LCB5ID0gMSwgeGVuZCA9IDEwMC41LCB5ZW5kID0gMCksIGNvbG91ciA9ICJibHVlIikrCiAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfYmxhbmsoKSwgCiAgICAgICAgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzLng9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MueT1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0Lng9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLmxpbmU9ZWxlbWVudF9ibGFuaygpKSsKICB4bGltKEJNU19GaWdfeG1pbmxpbSxCTVNfRmlnX3htYXhsaW0pKwogIHlsaW0oQk1TX0ZpZ195bWlubGltLEJNU19GaWdfeW1heGxpbSkKCkJNU19wbG90MTUKYGBgCgpgYGB7ciBlY2hvPUZBTFNFfQojU2F2aW5nIDEzLjMgeCA2LjUxIGluIGltYWdlcwpnZ3NhdmUoZmlsZT0iQk1TL1BMT1RTL0NvbXBsZW1lbnRhdGlvbi9DdXN0b20vQk1TX0xpYjE1X0NvbXBsZW1lbnRhdGlvbl9MZWdlbmQubWVhbi5maXhlZC5wZGYiLCAKICAgICAgIHBsb3Q9Qk1TX3Bsb3QxNSwKICAgICAgIHdpZHRoPTEzLjMsIGhlaWdodD02LjUsIHVuaXRzPSJpbiIpCgojUGxvdCB3aXRob3V0IGxlZ2VuZCBhbmQgc2F2ZSBjb3B5CkJNU19wbG90MTVfMiA8LSBCTVNfcGxvdDE1ICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIikKCiNTYXZpbmcgMTMuMyB4IDYuNTEgaW4gaW1hZ2VzCmdnc2F2ZShmaWxlPSJCTVMvUExPVFMvQ29tcGxlbWVudGF0aW9uL0N1c3RvbS9CTVNfTGliMTVfQ29tcGxlbWVudGF0aW9uX25vX0xlZ2VuZC5tZWFuLmZpeGVkLnBkZiIsIAogICAgICAgcGxvdD1CTVNfcGxvdDE1XzIsCiAgICAgICB3aWR0aD0xMy4zLCBoZWlnaHQ9Ni41LCB1bml0cz0iaW4iKQpgYGAKClBsb3QgdGhlIFNTIHN0cmluZyB0byBnbyBhYm92ZSB0aGUgY3VzdG9tIEJNUyBwbG90OgpgYGB7cn0KcHJvdGVpbl9pbmZvXzFIMVRfbm9fbG9vcCA8LSBwcm90ZWluX2luZm9fMUgxVCAlPiUKICBmaWx0ZXIoU1MgIT0gImxvb3AiKQoKIyBQbG90IFNTCgpTU19wbG90MTUgPC0gZ2dwbG90KCkrCiAgZ2VvbV9zZWdtZW50KGFlcyh4ID0gMSwgeSA9IDI0LjUsIHhlbmQgPSAxNjAsIHllbmQgPSAyNC41KSwgY29sb3VyID0gImJsYWNrIikrCiAgZ2VvbV9yZWN0KGRhdGE9cHJvdGVpbl9pbmZvXzFIMVRfbm9fbG9vcCxhZXMoeG1pbj1wb3MsIHhtYXg9cG9zKzEsIHltaW49MjQsIHltYXg9MjUsIGZpbGw9U1MpKSsKICB4bGltKEJNU19GaWdfeG1pbmxpbSxCTVNfRmlnX3htYXhsaW0pKwogIHlsaW0oQk1TX0ZpZ195bWlubGltLEJNU19GaWdfeW1heGxpbSkrCiAgbGFicyh4ID0gIiIsIHkgPSIiLGNvbG9yPSIiKSArCiAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfYmxhbmsoKSwgCiAgICAgICAgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzLng9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MueT1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0Lng9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLmxpbmU9ZWxlbWVudF9ibGFuaygpKQoKU1NfcGxvdDE1CmBgYAoKYGBge3IgZWNobz1GQUxTRX0KI1NhdmluZyAxMy4zIHggNi41MSBpbiBpbWFnZXMKZ2dzYXZlKGZpbGU9IkJNUy9QTE9UUy9Db21wbGVtZW50YXRpb24vQ3VzdG9tL1NTX0xpYjE1X0NvbXBsZW1lbnRhdGlvbl9MZWdlbmQuZml4ZWQucGRmIiwgCiAgICAgICBwbG90PVNTX3Bsb3QxNSwKICAgICAgIHdpZHRoPTEzLjMsIGhlaWdodD02LjUsIHVuaXRzPSJpbiIpCgojUGxvdCB3aXRob3V0IGxlZ2VuZCBhbmQgc2F2ZSBjb3B5ClNTX3Bsb3QxNV8yIDwtIFNTX3Bsb3QxNSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIpCgojU2F2aW5nIDEzLjMgeCA2LjUxIGluIGltYWdlcwpnZ3NhdmUoZmlsZT0iQk1TL1BMT1RTL0NvbXBsZW1lbnRhdGlvbi9DdXN0b20vU1NfTGliMTVfQ29tcGxlbWVudGF0aW9uX25vX0xlZ2VuZC5maXhlZC5wZGYiLCAKICAgICAgIHBsb3Q9U1NfcGxvdDE1XzIsCiAgICAgICB3aWR0aD0xMy4zLCBoZWlnaHQ9Ni41LCB1bml0cz0iaW4iKQpgYGAKClBsb3QgdGhlIFJTQSAocHJvdGVpbikgc3RyaW5nOgpgYGB7cn0KUlNBX3Bsb3QxNSA8LSBnZ3Bsb3QocHJvdGVpbl9pbmZvXzFIMVQpKwogIGdlb21fcmVjdChhZXMoeG1pbj1wb3MsIHhtYXg9cG9zKzEsIHltaW49MjUuMywgeW1heD0yNi4zLCBmaWxsPVJTQSkpKwogIHhsaW0oQk1TX0ZpZ194bWlubGltLEJNU19GaWdfeG1heGxpbSkrCiAgeWxpbShCTVNfRmlnX3ltaW5saW0sQk1TX0ZpZ195bWF4bGltKSsKICBsYWJzKHggPSAiIiwgeSA9IiIsY29sb3I9IiIpICsKICBzY2FsZV9maWxsX2dyYWRpZW50KGxvdyA9ICJ3aGl0ZSIsIGhpZ2ggPSAicmVkIixuYW1lPSJSU0EiLG5hLnZhbHVlPSJncmV5IikgKwogIHRoZW1lKHBhbmVsLmdyaWQubWFqb3IgPSBlbGVtZW50X2JsYW5rKCksIAogICAgICAgIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcy54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzLnk9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy5saW5lPWVsZW1lbnRfYmxhbmsoKSkKClJTQV9wbG90MTUKYGBgCgpgYGB7ciBlY2hvPUZBTFNFfQojU2F2aW5nIDEzLjMgeCA2LjUxIGluIGltYWdlcwpnZ3NhdmUoZmlsZT0iQk1TL1BMT1RTL0NvbXBsZW1lbnRhdGlvbi9DdXN0b20vUlNBX0xpYjE1X0NvbXBsZW1lbnRhdGlvbl9MZWdlbmQuZml4ZWQucGRmIiwgCiAgICAgICBwbG90PVJTQV9wbG90MTUsCiAgICAgICB3aWR0aD0xMy4zLCBoZWlnaHQ9Ni41LCB1bml0cz0iaW4iKQoKI1Bsb3Qgd2l0aG91dCBsZWdlbmQgYW5kIHNhdmUgY29weQpSU0FfcGxvdDE1XzIgPC0gUlNBX3Bsb3QxNSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIpCgojU2F2aW5nIDEzLjMgeCA2LjUxIGluIGltYWdlcwpnZ3NhdmUoZmlsZT0iQk1TL1BMT1RTL0NvbXBsZW1lbnRhdGlvbi9DdXN0b20vUlNBX0xpYjE1X0NvbXBsZW1lbnRhdGlvbl9ub19MZWdlbmQuZml4ZWQucGRmIiwgCiAgICAgICBwbG90PVJTQV9wbG90MTVfMiwKICAgICAgIHdpZHRoPTEzLjMsIGhlaWdodD02LjUsIHVuaXRzPSJpbiIpCmBgYAoKUGxvdCB0aGUgY29uc2VydmF0aW9uIHNjb3JlOgpgYGB7cn0KY29uc19wbG90MTUgPC0gZ2dwbG90KHByb3RlaW5faW5mb18xSDFUKSsKICBnZW9tX3JlY3QoYWVzKHhtaW49cG9zLCB4bWF4PXBvcysxLCB5bWluPTI2LjYsIHltYXg9MjcuNiwgZmlsbD1jb25zKSkrCiAgeGxpbShCTVNfRmlnX3htaW5saW0sQk1TX0ZpZ194bWF4bGltKSsKICB5bGltKEJNU19GaWdfeW1pbmxpbSxCTVNfRmlnX3ltYXhsaW0pKwogIGxhYnMoeCA9ICIiLCB5ID0iIixjb2xvcj0iIikgKwogIHNjYWxlX2ZpbGxfZ3JhZGllbnQobG93ID0gIndoaXRlIiwgaGlnaCA9ICJyZWQiLG5hbWU9IkNvbnMiLG5hLnZhbHVlPSJncmV5IikgKwogIHRoZW1lKHBhbmVsLmdyaWQubWFqb3IgPSBlbGVtZW50X2JsYW5rKCksIAogICAgICAgIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcy54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzLnk9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy5saW5lPWVsZW1lbnRfYmxhbmsoKSkKCmNvbnNfcGxvdDE1CmBgYAoKYGBge3IgZWNobz1GQUxTRX0KI1NhdmluZyAxMy4zIHggNi41MSBpbiBpbWFnZXMKZ2dzYXZlKGZpbGU9IkJNUy9QTE9UUy9Db21wbGVtZW50YXRpb24vQ3VzdG9tL0NvbnNfTGliMTVfQ29tcGxlbWVudGF0aW9uX0xlZ2VuZC5maXhlZC5wZGYiLCAKICAgICAgIHBsb3Q9Y29uc19wbG90MTUsCiAgICAgICB3aWR0aD0xMy4zLCBoZWlnaHQ9Ni41LCB1bml0cz0iaW4iKQoKI1Bsb3Qgd2l0aG91dCBsZWdlbmQgYW5kIHNhdmUgY29weQpjb25zX3Bsb3QxNV8yIDwtIGNvbnNfcGxvdDE1ICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIikKCiNTYXZpbmcgMTMuMyB4IDYuNTEgaW4gaW1hZ2VzCmdnc2F2ZShmaWxlPSJCTVMvUExPVFMvQ29tcGxlbWVudGF0aW9uL0N1c3RvbS9Db25zX0xpYjE1X0NvbXBsZW1lbnRhdGlvbl9ub19MZWdlbmQuZml4ZWQucGRmIiwgCiAgICAgICBwbG90PWNvbnNfcGxvdDE1XzIsCiAgICAgICB3aWR0aD0xMy4zLCBoZWlnaHQ9Ni41LCB1bml0cz0iaW4iKQpgYGAKCiMjIyBTdW1tYXJ5IFBsb3RzCgpQbG90IGF2ZXJhZ2UgZml0bmVzcyB2ZXJzdXMgcG9zaXRpb24gY292ZXJhZ2U6CmBgYHtyfQpjb3ZlcmFnZV9maXRfcGxvdDE1IDwtIGdncGxvdChwcm90ZWluX2luZm9fMUgxVCxhZXMoeD1hdmdmaXQseT1udW1jb3YvMjAqMTAwKSkrCiAgZ2VvbV9zbW9vdGgoZmlsbD0iIzAwNzJCMiIpKwogIGdlb21fcG9pbnQoKSsKICBsYWJzKHggPSAiQXZlcmFnZSBmaXRuZXNzIGF0IHBvc2l0aW9uIiwgeSA9IlBvc2l0aW9uIG11dGF0aW9uYWwgY292ZXJhZ2UgKCUpIixjb2xvcj0iIikgKwogIHRoZW1lKHRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxOCksCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE0KSwKICAgICAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTQpLAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTYpLAogICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTYpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCkpICsKICBwYW5lbF9ib3JkZXIoY29sb3IgPSAiYmxhY2siKQoKY292ZXJhZ2VfZml0X3Bsb3QxNQpgYGAKCmBgYHtyIGVjaG89RkFMU0V9CiNTYXZpbmcgNiB4IDUgaW4gaW1hZ2VzCmdnc2F2ZShmaWxlPSJCTVMvUExPVFMvQ29tcGxlbWVudGF0aW9uL1N1bW1hcnkvTGliMTVfQ29tcGxlbWVudGF0aW9uX0NvdmVyYWdlX3ZzX0F2Z19GaXQucGRmIiwKICAgICAgIHBsb3Q9Y292ZXJhZ2VfZml0X3Bsb3QxNSwKICAgICAgIHdpZHRoPTYsIGhlaWdodD01LCB1bml0cz0iaW4iKQpgYGAKCkNvcnJlbGF0aW9uIFRlc3QgZm9yIGNvdmVyYWdlIHZlcnN1cyBhdmVyYWdlIGZpdDoKYGBge3IgY2xhc3Mub3V0cHV0PSJnb29kQ29kZSJ9CmNvci50ZXN0KHByb3RlaW5faW5mb18xSDFUJGF2Z2ZpdCxwcm90ZWluX2luZm9fMUgxVCRudW1jb3YvMjAqMTAwKQpjb3IocHJvdGVpbl9pbmZvXzFIMVQkYXZnZml0LHByb3RlaW5faW5mb18xSDFUJG51bWNvdiwgdXNlID0gImNvbXBsZXRlLm9icyIpCmBgYAoKUGxvdCBBdmVyYWdlIEZpdCB2ZXJzdXMgU1M6CmBgYHtyfQpTU19maXRfcGxvdDE1IDwtIGdncGxvdChwcm90ZWluX2luZm9fMUgxVCxhZXMoeD1TUyx5PWF2Z2ZpdCkpKwogIGdlb21fYm94cGxvdChjb2xvcj0iYmxhY2siLCBmaWxsPSIjMDA3MkIyIiwgYWxwaGEgPSAwLjgpKwogIGdlb21faml0dGVyKCkrCiAgbGFicyh4ID0gIlNlY29uZGFyeSBzdHJ1Y3R1cmUiLCB5ID0iQXZlcmFnZSBmaXRuZXNzIGF0IHBvc2l0aW9uIixjb2xvcj0iIikrCiAgdGhlbWUodGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE4KSwKICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTQpLAogICAgICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNCksCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNiksCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNiksCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSkgKwogIHBhbmVsX2JvcmRlcihjb2xvciA9ICJibGFjayIpCgpTU19maXRfcGxvdDE1CmBgYAoKYGBge3IgZWNobz1GQUxTRX0KI1NhdmluZyA2IHggNSBpbiBpbWFnZXMKZ2dzYXZlKGZpbGU9IkJNUy9QTE9UUy9Db21wbGVtZW50YXRpb24vU3VtbWFyeS9MaWIxNV9Db21wbGVtZW50YXRpb25fQXZnX0ZpdF92c19TUy5wZGYiLCAKICAgICAgIHBsb3Q9U1NfZml0X3Bsb3QxNSwKICAgICAgIHdpZHRoPTYsIGhlaWdodD01LCB1bml0cz0iaW4iKQpgYGAKClBsb3QgQXZlcmFnZSBGaXQgdmVyc3VzIENvbnNlcnZhdGlvbjoKYGBge3J9CkNvbnNfZml0X3Bsb3QxNSA8LSBnZ3Bsb3QocHJvdGVpbl9pbmZvXzFIMVQsYWVzKHg9Y29ucyx5PWF2Z2ZpdCkpKwogIGdlb21fc21vb3RoKCkrCiAgZ2VvbV9wb2ludChhbHBoYT0wLjcpKwogIGxhYnMoeCA9ICJQb3NpdGlvbiBDb25zZXJ2YXRpb24iLCB5ID0iQXZlcmFnZSBmaXRuZXNzIGF0IHBvc2l0aW9uIixjb2xvcj0iIikrCiAgeGxpbSgwLjEsMC44KSsKICB0aGVtZSh0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTgpLAogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNCksCiAgICAgICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE0KSwKICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE2KSwKICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE2KSwKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpKSArCiAgcGFuZWxfYm9yZGVyKGNvbG9yID0gImJsYWNrIikKCkNvbnNfZml0X3Bsb3QxNSA8LSBnZ0V4dHJhOjpnZ01hcmdpbmFsKENvbnNfZml0X3Bsb3QxNSx0eXBlID0gImhpc3RvZ3JhbSIsCiAgICAgICAgICAgICAgICAgICAgICAgICB4cGFyYW1zID0gbGlzdChiaW5zPTMwKSwKICAgICAgICAgICAgICAgICAgICAgICAgIHlwYXJhbXMgPSBsaXN0KGJpbnM9MjApLAogICAgICAgICAgICAgICAgICAgICAgICAgY29sID0gJ2JsYWNrJywKICAgICAgICAgICAgICAgICAgICAgICAgIGZpbGwgPSAnIzAwNzJCMicsIGFscGhhID0gMC44KSAKCkNvbnNfZml0X3Bsb3QxNQpgYGAKCmBgYHtyIGVjaG89RkFMU0V9CiNTYXZpbmcgNiB4IDUgaW4gaW1hZ2VzCmdnc2F2ZShmaWxlPSJCTVMvUExPVFMvQ29tcGxlbWVudGF0aW9uL1N1bW1hcnkvTGliMTVfQ29tcGxlbWVudGF0aW9uX0F2Z19GaXRfdnNfQ29ucy5wZGYiLCAKICAgICAgIHBsb3Q9Q29uc19maXRfcGxvdDE1LAogICAgICAgd2lkdGg9NiwgaGVpZ2h0PTUsIHVuaXRzPSJpbiIpCmBgYAoKQ29ycmVsYXRpb24gVGVzdHMgZm9yIENvbnMgdmVyc3VzIGF2ZXJhZ2UgZml0OgpgYGB7ciBjbGFzcy5vdXRwdXQ9Imdvb2RDb2RlIn0KY29yLnRlc3QocHJvdGVpbl9pbmZvXzFIMVQkY29ucyxwcm90ZWluX2luZm9fMUgxVCRhdmdmaXQpCmNvcihwcm90ZWluX2luZm9fMUgxVCRjb25zLHByb3RlaW5faW5mb18xSDFUJGF2Z2ZpdCwgdXNlID0gImNvbXBsZXRlLm9icyIpCmBgYAoKUGxvdCBBdmVyYWdlIEZpdCB2ZXJzdXMgUlNBOgpgYGB7cn0KUlNBX2ZpdF9wbG90MTUgPC0gZ2dwbG90KHByb3RlaW5faW5mb18xSDFULGFlcyh4PVJTQSx5PWF2Z2ZpdCkpKwogIGdlb21fc21vb3RoKCkrCiAgZ2VvbV9wb2ludChhbHBoYT0wLjcpKwogIGxhYnMoeCA9ICJSZWxhdGl2ZSBzb2x2ZW50IGFjY2VzaWJpbGl0eSIsIHkgPSJBdmVyYWdlIGZpdG5lc3MgYXQgcG9zaXRpb24iLGNvbG9yPSIiKSsKICAjeWxpbSgtMi43LDIuNykrCiAgdGhlbWUodGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE4KSwKICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTQpLAogICAgICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNCksCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNiksCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNiksCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSkgKwogIHBhbmVsX2JvcmRlcihjb2xvciA9ICJibGFjayIpCgpSU0FfZml0X3Bsb3QxNSA8LSBnZ0V4dHJhOjpnZ01hcmdpbmFsKFJTQV9maXRfcGxvdDE1LHR5cGUgPSAiaGlzdG9ncmFtIiwKICAgICAgICAgICAgICAgICAgICAgICAgIHhwYXJhbXMgPSBsaXN0KGJpbnM9MzApLAogICAgICAgICAgICAgICAgICAgICAgICAgeXBhcmFtcyA9IGxpc3QoYmlucz0yMCksCiAgICAgICAgICAgICAgICAgICAgICAgICBjb2wgPSAnYmxhY2snLAogICAgICAgICAgICAgICAgICAgICAgICAgZmlsbCA9ICcjMDA3MkIyJywgYWxwaGEgPSAwLjgpIAoKUlNBX2ZpdF9wbG90MTUKYGBgCgpgYGB7ciBlY2hvPUZBTFNFfQojU2F2aW5nIDYgeCA1IGluIGltYWdlcwpnZ3NhdmUoZmlsZT0iQk1TL1BMT1RTL0NvbXBsZW1lbnRhdGlvbi9TdW1tYXJ5L0xpYjE1X0NvbXBsZW1lbnRhdGlvbl9BdmdfRml0X3ZzX1JTQS5wZGYiLCAKICAgICAgIHBsb3Q9UlNBX2ZpdF9wbG90MTUsCiAgICAgICB3aWR0aD02LCBoZWlnaHQ9NSwgdW5pdHM9ImluIikKYGBgCgpDb3JyZWxhdGlvbiBUZXN0cyBmb3IgUlNBIHZlcnN1cyBhdmVyYWdlIGZpdDoKYGBge3IgY2xhc3Mub3V0cHV0PSJnb29kQ29kZSJ9CmNvci50ZXN0KHByb3RlaW5faW5mb18xSDFUJFJTQSxwcm90ZWluX2luZm9fMUgxVCRhdmdmaXQpCmNvcihwcm90ZWluX2luZm9fMUgxVCRSU0EscHJvdGVpbl9pbmZvXzFIMVQkYXZnZml0LCB1c2UgPSAiY29tcGxldGUub2JzIikKYGBgCgpQbG90IHRoZSBhdmVyYWdlIGZpdG5lc3MgZm9yIFdUIEUuIGNvbGkgREhGUiB2cy4gQWxsIHJlbWFpbmluZyBESEZSIGhvbW9sb2dzOgpgYGB7cn0KV1RfZml0X3Bsb3QxNSA8LSBnZ3Bsb3QoQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX21lbHQsYWVzKHg9V1Rjb2xvcix5PXZhbHVlKSkrCiAgZ2VvbV9ib3hwbG90KGNvbG9yPSJibGFjayIsIGZpbGw9IiMwMDcyQjIiLCBhbHBoYSA9IDAuOCkrCiAgbGFicyh4ID0gIlJlc2lkdWUiLCB5ID0iQXZlcmFnZSBGaXRuZXNzIChMb2dGQykiLGNvbG9yPSIiKSsgCiAgc2NhbGVfeF9kaXNjcmV0ZShuYW1lID0iUmVzaWR1ZSB0eXBlIiwKICAgICAgICAgICAgICAgICAgIGxhYmVscz1jKCJXaWxkdHlwZSBFLiBjb2xpIFxuREhGUiBIb21vbG9nIiwiQWxsIFJlbWFpbmluZyBcbkRIRlIgSG9tb2xvZ3MiKSkrCiAgdGhlbWUodGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE4KSwKICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTQpLAogICAgICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNCksCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNiksCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNiksCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSkgKwogIHBhbmVsX2JvcmRlcihjb2xvciA9ICJibGFjayIpCgpXVF9maXRfcGxvdDE1CmBgYAoKYGBge3IgZWNobz1GQUxTRX0KI1NhdmluZyA2IHggNSBpbiBpbWFnZXMKZ2dzYXZlKGZpbGU9IkJNUy9QTE9UUy9Db21wbGVtZW50YXRpb24vU3VtbWFyeS9MaWIxNV9Db21wbGVtZW50YXRpb25fV1RfdnNfb3RoZXJfZml0bmVzcy5wZGYiLCAKICAgICAgIHBsb3Q9V1RfZml0X3Bsb3QxNSwKICAgICAgIHdpZHRoPTYsIGhlaWdodD01LCB1bml0cz0iaW4iKQpgYGAKClN1bW1hcnkgU3RhdHMgZm9yIFdUIEUuIGNvbGkgREhGUiBob21vbG9nIGFuZCBBbGwgcmVtYWluaW5nIERIRlIgaG9tb2xvZ3M6CmBgYHtyIGNsYXNzLm91dHB1dD0iZ29vZENvZGUifQojIyMgV1QgU3VtbWFyeSBTdGF0cwoKIyBXVCBNZWFuIEZpdG5lc3MKbWVhbihhcy5udW1lcmljKHVubGlzdChCTVNfbWF0cml4MTVfcGVyZmVjdHNfYW5kXzFfbWVsdCAlPiUgZmlsdGVyKFdUY29sb3I9PSJyZWQiKSAlPiUgZHBseXI6OnNlbGVjdCh2YWx1ZSkpKSkKIyBXVCBNZWRpYW4gRml0bmVzcwptZWRpYW4oYXMubnVtZXJpYyh1bmxpc3QoQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX21lbHQgJT4lIGZpbHRlcihXVGNvbG9yPT0icmVkIikgJT4lIGRwbHlyOjpzZWxlY3QodmFsdWUpKSkpCiMgV1QgU0QgRml0bmVzcwpzZChhcy5udW1lcmljKHVubGlzdChCTVNfbWF0cml4MTVfcGVyZmVjdHNfYW5kXzFfbWVsdCAlPiUgZmlsdGVyKFdUY29sb3I9PSJyZWQiKSAlPiUgZHBseXI6OnNlbGVjdCh2YWx1ZSkpKSkKCiMjIyBOb24tV1QgU3VtbWFyeSBTdGF0cwoKIyBOb24tV1QgTWVhbiBGaXRuZXNzCm1lYW4oYXMubnVtZXJpYyh1bmxpc3QoQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX21lbHQgJT4lIGZpbHRlcihpcy5uYShXVGNvbG9yKSAmICFpcy5uYSh2YWx1ZSkpICU+JSBkcGx5cjo6c2VsZWN0KHZhbHVlKSkpKQojIE5vbi1XVCBNZWRpYW4gRml0bmVzcwptZWRpYW4oYXMubnVtZXJpYyh1bmxpc3QoQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX21lbHQgJT4lIGZpbHRlcihpcy5uYShXVGNvbG9yKSAmICFpcy5uYSh2YWx1ZSkpICU+JSBkcGx5cjo6c2VsZWN0KHZhbHVlKSkpKQojIE5vbi1XVCBTRCBGaXRuZXNzCnNkKGFzLm51bWVyaWModW5saXN0KEJNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMV9tZWx0ICU+JSBmaWx0ZXIoaXMubmEoV1Rjb2xvcikgJiAhaXMubmEodmFsdWUpKSAlPiUgZHBseXI6OnNlbGVjdCh2YWx1ZSkpKSkKYGBgCgojIyMgQk1TIFNURCBQbG90cwoKRXN0YWJsaXNoIHBsb3R0aW5nIHBhcmFtZXRlcnM6CmBgYHtyfQojIEltcG9ydCB0aGUgQk1TX21hdHJpeDE1IGRhdGFzZXQgYmFzZWQgb24gMSBhLmEuIG11dGF0aW9uOgpCTVNfbWF0cml4MTVfcGVyZmVjdHNfYW5kXzFfc2RfbWVsdCA8LSByZWFkLmNzdigiQk1TL09VVFBVVC9Db21wbGVtZW50YXRpb24vQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX3NkX21lbHQuY3N2IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaGVhZGVyID0gVFJVRSkKCgpCTVNfbWF0cml4MTVfcGVyZmVjdHNfYW5kXzFfc2RfbWVsdCA8LSBCTVNfbWF0cml4MTVfcGVyZmVjdHNfYW5kXzFfc2RfbWVsdCAlPiUKICBmaWx0ZXIoWDEhPSJYIikKCkJNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMV9zZF9tZWx0JGFhbnVtIDwtIDAKCkJNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMV9zZF9tZWx0JGFhbnVtW3doaWNoKEJNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMV9zZF9tZWx0JFgxPT0iQSIpXSA8LSAxMgpCTVNfbWF0cml4MTVfcGVyZmVjdHNfYW5kXzFfc2RfbWVsdCRhYW51bVt3aGljaChCTVNfbWF0cml4MTVfcGVyZmVjdHNfYW5kXzFfc2RfbWVsdCRYMT09IkMiKV0gPC0gMTAKQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX3NkX21lbHQkYWFudW1bd2hpY2goQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX3NkX21lbHQkWDE9PSJEIildIDwtIDUKQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX3NkX21lbHQkYWFudW1bd2hpY2goQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX3NkX21lbHQkWDE9PSJFIildIDwtIDQKQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX3NkX21lbHQkYWFudW1bd2hpY2goQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX3NkX21lbHQkWDE9PSJGIildIDwtIDE5CkJNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMV9zZF9tZWx0JGFhbnVtW3doaWNoKEJNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMV9zZF9tZWx0JFgxPT0iRyIpXSA8LSAxMQpCTVNfbWF0cml4MTVfcGVyZmVjdHNfYW5kXzFfc2RfbWVsdCRhYW51bVt3aGljaChCTVNfbWF0cml4MTVfcGVyZmVjdHNfYW5kXzFfc2RfbWVsdCRYMT09IkgiKV0gPC0gMwpCTVNfbWF0cml4MTVfcGVyZmVjdHNfYW5kXzFfc2RfbWVsdCRhYW51bVt3aGljaChCTVNfbWF0cml4MTVfcGVyZmVjdHNfYW5kXzFfc2RfbWVsdCRYMT09IkkiKV0gPC0gMTUKQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX3NkX21lbHQkYWFudW1bd2hpY2goQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX3NkX21lbHQkWDE9PSJLIildIDwtIDEKQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX3NkX21lbHQkYWFudW1bd2hpY2goQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX3NkX21lbHQkWDE9PSJMIildIDwtIDE0CkJNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMV9zZF9tZWx0JGFhbnVtW3doaWNoKEJNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMV9zZF9tZWx0JFgxPT0iTSIpXSA8LSAxNgpCTVNfbWF0cml4MTVfcGVyZmVjdHNfYW5kXzFfc2RfbWVsdCRhYW51bVt3aGljaChCTVNfbWF0cml4MTVfcGVyZmVjdHNfYW5kXzFfc2RfbWVsdCRYMT09Ik4iKV0gPC0gNgpCTVNfbWF0cml4MTVfcGVyZmVjdHNfYW5kXzFfc2RfbWVsdCRhYW51bVt3aGljaChCTVNfbWF0cml4MTVfcGVyZmVjdHNfYW5kXzFfc2RfbWVsdCRYMT09IlAiKV0gPC0gMTcKQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX3NkX21lbHQkYWFudW1bd2hpY2goQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX3NkX21lbHQkWDE9PSJRIildIDwtIDcKQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX3NkX21lbHQkYWFudW1bd2hpY2goQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX3NkX21lbHQkWDE9PSJSIildIDwtIDIKQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX3NkX21lbHQkYWFudW1bd2hpY2goQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX3NkX21lbHQkWDE9PSJTIildIDwtIDkKQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX3NkX21lbHQkYWFudW1bd2hpY2goQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX3NkX21lbHQkWDE9PSJUIildIDwtIDgKQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX3NkX21lbHQkYWFudW1bd2hpY2goQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX3NkX21lbHQkWDE9PSJWIildIDwtIDEzCkJNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMV9zZF9tZWx0JGFhbnVtW3doaWNoKEJNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMV9zZF9tZWx0JFgxPT0iVyIpXSA8LSAyMApCTVNfbWF0cml4MTVfcGVyZmVjdHNfYW5kXzFfc2RfbWVsdCRhYW51bVt3aGljaChCTVNfbWF0cml4MTVfcGVyZmVjdHNfYW5kXzFfc2RfbWVsdCRYMT09IlkiKV0gPC0gMTgKQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX3NkX21lbHQkYWFudW1bd2hpY2goQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX3NkX21lbHQkWDE9PSJYIildIDwtIDIyLjYKYGBgCgpQbG90IHRoZSBTVEQgdmFsdWVzIG9mIHRoZSBCTVMgYW5hbHlzaXM6CmBgYHtyfQpCTVNfc3RkX3Bsb3QxNSA8LSBnZ3Bsb3QoKSArCiAgI0JNUyBtYXRyaXgKICBnZW9tX3JlY3QoZGF0YT1CTVNfbWF0cml4MTVfcGVyZmVjdHNfYW5kXzFfc2RfbWVsdCxhZXMoeG1pbj1YMiwgeG1heD1YMisxLCB5bWluPWFhbnVtLCB5bWF4PWFhbnVtKzEsIGZpbGw9dmFsdWUpKSsKICAjV1Qgc2VxCiAgZ2VvbV9yZWN0KGRhdGE9Qk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX21lbHRfV1QsYWVzKHhtaW49WDIsIHhtYXg9WDIrMSwgeW1pbj1hYW51bSwgeW1heD1hYW51bSsxKSwgY29sb3I9ImdyZWVuIixhbHBoYT0wKSsKICBsYWJzKHggPSAiUG9zaXRpb24gKGFhKSIsIHkgPSJBbWlubyBhY2lkIixjb2xvcj0iIikgKwogIHNjYWxlX2ZpbGxfZ3JhZGllbnQyKGxvdyA9ICJibHVlIiwgaGlnaCA9ICJyZWQiLCBtaWQ9ImdvbGQiLG5hbWU9IlN0ZCIsbmEudmFsdWU9ImdyZXkiLCBtaWRwb2ludCA9IDMsIGxpbWl0ID0gYygwLDEuMSptYXgoQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX3NkX21lbHQkdmFsdWUpKSkgKyMKICB0aGVtZShwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9ibGFuaygpLCAKICAgICAgICBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MueD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcy55PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMubGluZT1lbGVtZW50X2JsYW5rKCkpCgpCTVNfc3RkX3Bsb3QxNQpgYGAKCmBgYHtyIGVjaG89RkFMU0V9Cmdnc2F2ZShmaWxlPSJCTVMvUExPVFMvQ29tcGxlbWVudGF0aW9uL0JNU19zdGRfcGxvdDE1LnBkZiIsIAogICAgICAgcGxvdD1CTVNfc3RkX3Bsb3QxNSwKICAgICAgIHdpZHRoPTEzLjMsIGhlaWdodD02LjUsIHVuaXRzPSJpbiIpCmBgYAoKUGxvdCBlcGlzdGF0aXM6CmBgYHtyfQpCTVNfZXBpc3Rhc2lzMTUgPC0gQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX3NkX21lbHQgJT4lCiAgZHBseXI6OnJlbmFtZShzZD12YWx1ZSkgJT4lCiAgaW5uZXJfam9pbihCTVNfbWF0cml4MTVfcGVyZmVjdHNfYW5kXzFfbnVtX21lbHQsIGJ5PWMoIlgxIiwiWDIiKSkgJT4lCiAgZHBseXI6OnJlbmFtZShudW09dmFsdWUpICU+JQogIGZpbHRlcihudW0+NCkKCkJNU19lcGlzdGFzaXNfc3RkX3Bsb3QxNSA8LSBnZ3Bsb3QoKSArCiAgI0JNUyBtYXRyaXgKICBnZW9tX3JlY3QoZGF0YT1CTVNfZXBpc3Rhc2lzMTUsYWVzKHhtaW49WDIsIHhtYXg9WDIrMSwgeW1pbj1hYW51bSwgeW1heD1hYW51bSsxLCBmaWxsPXNkKSkrCiAgI1dUIHNlcQogIGdlb21fcmVjdChkYXRhPUJNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMV9tZWx0X1dULGFlcyh4bWluPVgyLCB4bWF4PVgyKzEsIHltaW49YWFudW0sIHltYXg9YWFudW0rMSksIGNvbG9yPSJncmVlbiIsYWxwaGE9MCkrCiAgZ2VvbV90ZXh0KGRhdGE9Qk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX21lbHRbMToyMSxdLCBhZXMoeD0tMSwgeT1hYW51bSswLjUsIGxhYmVsPVgxKSwgc2l6ZT0zLjUpKwogIGdlb21fdGV4dChkYXRhPWRhdGEuZnJhbWUocG9zPXNlcSgwLDE1MCwzMCkpLGFlcyh4PXBvcyswLjUseT0wLGxhYmVsPXBvcykpKwogIGxhYnMoeCA9ICJQb3NpdGlvbiAoYWEpIiwgeSA9IkFtaW5vIGFjaWQiLGNvbG9yPSIiKSArCiAgc2NhbGVfZmlsbF9ncmFkaWVudChsb3cgPSAiZ29sZCIsIGhpZ2ggPSAicmVkIiwgbmFtZT0iU3RkIixuYS52YWx1ZT0iZ3JleSIsIGxpbWl0ID0gYygzLDEuMSptYXgoQk1TX2VwaXN0YXNpczE1JHNkKSkpICsjCiAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfYmxhbmsoKSwgCiAgICAgICAgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzLng9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MueT1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0Lng9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLmxpbmU9ZWxlbWVudF9ibGFuaygpKQoKQk1TX2VwaXN0YXNpc19zdGRfcGxvdDE1CmBgYAoKYGBge3IgZWNobz1GQUxTRX0KZ2dzYXZlKGZpbGU9IkJNUy9QTE9UUy9Db21wbGVtZW50YXRpb24vQk1TX2VwaXN0YXNpc19zdGRfcGxvdDE1LnBkZiIsIAogICAgICAgcGxvdD1CTVNfZXBpc3Rhc2lzX3N0ZF9wbG90MTUsCiAgICAgICB3aWR0aD0xMy4zLCBoZWlnaHQ9Ni41LCB1bml0cz0iaW4iKQpgYGAKClBsb3QgcG90ZW50aWFsIGVwaXN0YXRpYyBpbnRlcmFjdGlvbnM6CmBgYHtyfQpCTVNfZXBpc3Rhc2lzMTVfcG9zIDwtIEJNU19lcGlzdGFzaXMxNSAlPiUKICBncm91cF9ieShYMikgJT4lCiAgc3VtbWFyaXNlKG51bWFhPW4oKSxtZWFuc2Q9bWVhbihzZCksc3Rkc2Q9c2Qoc2QpKQoKIyBfX18KZ2dwbG90KEJNU19lcGlzdGFzaXMxNV9wb3MsYWVzKHg9WDIseT1udW1hYSkpICsKICBnZW9tX3BvaW50KCkrCiAgdGhlbWUodGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE4KSwKICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTQpLAogICAgICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNCksCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNiksCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNiksCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSkgKwogIHBhbmVsX2JvcmRlcihjb2xvciA9ICJibGFjayIpCgojIF9fXwpnZ3Bsb3QoQk1TX2VwaXN0YXNpczE1X3BvcyxhZXMoeD1YMix5PW1lYW5zZCkpICsKICBnZW9tX3BvaW50KCkrCiAgdGhlbWUodGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE4KSwKICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTQpLAogICAgICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNCksCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNiksCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNiksCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSkgKwogIHBhbmVsX2JvcmRlcihjb2xvciA9ICJibGFjayIpCgojIF9fXwpnZ3Bsb3QoQk1TX2VwaXN0YXNpczE1X3BvcyxhZXMoeD1YMix5PXN0ZHNkKSkgKwogIGdlb21fcG9pbnQoKSsKICB0aGVtZSh0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTgpLAogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNCksCiAgICAgICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE0KSwKICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE2KSwKICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE2KSwKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpKSArCiAgcGFuZWxfYm9yZGVyKGNvbG9yID0gImJsYWNrIikKYGBgCgpTYXZlIEJNUyBJbmZvIGFzIGRhdGFmcmFtZSAoYW5kIHdyaXRlIHRvIGNzdiBmaWxlKToKYGBge3J9CkJNU19pbmZvMTUgPC0gcmlnaHRfam9pbihCTVNfbWF0cml4MTVfcGVyZmVjdHNfYW5kXzFfbWVsdCAlPiUKICAgICAgICAgICAgICAgICAgICAgICAgIHNlbGVjdChYMSxYMix2YWx1ZSkgJT4lCiAgICAgICAgICAgICAgICAgICAgICAgICBkcGx5cjo6cmVuYW1lKEFBPVgxLFBvcz1YMixhdmdmaXRuZXNzPXZhbHVlKSwKICAgICAgICAgICAgICAgICAgICAgICBCTVNfbWF0cml4MTVfcGVyZmVjdHNfYW5kXzFfbnVtX21lbHQgJT4lCiAgICAgICAgICAgICAgICAgICAgICAgICBzZWxlY3QoWDEsWDIsdmFsdWUpICU+JQogICAgICAgICAgICAgICAgICAgICAgICAgZHBseXI6OnJlbmFtZShBQT1YMSxQb3M9WDIsbnVtcG9pbnRzPXZhbHVlKSwKICAgICAgICAgICAgICAgICAgICAgICBieT1jKCJBQSIsICJQb3MiKSkKCkJNU19pbmZvMTUgPC0gcmlnaHRfam9pbihCTVNfaW5mbzE1LAogICAgICAgICAgICAgICAgICAgICAgIEJNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMV9zZF9tZWx0JT4lCiAgICAgICAgICAgICAgICAgICAgICAgICBzZWxlY3QoWDEsWDIsdmFsdWUpICU+JQogICAgICAgICAgICAgICAgICAgICAgICAgZHBseXI6OnJlbmFtZShBQT1YMSxQb3M9WDIsc2Q9dmFsdWUpLAogICAgICAgICAgICAgICAgICAgICAgIGJ5PWMoIkFBIiwgIlBvcyIpKQpgYGAKCmBgYHtyIGVjaG89RkFMU0V9CiNzYXZlIENTVgp3cml0ZS50YWJsZShCTVNfaW5mbzE1LCBmaWxlID0gcGFzdGUoIkJNUy9PVVRQVVQvQ29tcGxlbWVudGF0aW9uL0JNU19pbmZvX0wxNV8xYWFfbXV0YXRpb25zLmNzdiIsc2VwPSIiKSwgCiAgICAgICAgICAgIHNlcCA9ICIsIiwgcm93Lm5hbWVzID0gRixxdW90ZT1GLGNvbC5uYW1lcyA9IFQpCmBgYAoKIyBTYXZlIEJNUyBGaWxlcwoKU2F2ZSB0aGUgZm9ybWF0dGVkIEJNUyBmaWxlcyB0byBpbXBvcnQgZm9yIGRvd25zdHJlYW0gYW5hbHlzZXMKYGBge3J9CiMgcHJvdGVpbl9pbmZvXzFIMVQKd3JpdGUuY3N2KHByb3RlaW5faW5mb18xSDFULCAKICAgICAgICAgICJCTVMvYm1zX2ZpbGVzX2Zvcm1hdHRlZC9wcm90ZWluX2luZm9fMUgxVC5jc3YiLCByb3cubmFtZXMgPSBGQUxTRSkKCiMgQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX21lbHQKd3JpdGUuY3N2KEJNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMV9tZWx0LCAKICAgICAgICAgICJCTVMvYm1zX2ZpbGVzX2Zvcm1hdHRlZC9CTVNfbWF0cml4MTVfcGVyZmVjdHNfYW5kXzFfbWVsdC5jc3YiLCByb3cubmFtZXMgPSBGQUxTRSkKCiMgQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX251bV9tZWx0CndyaXRlLmNzdihCTVNfbWF0cml4MTVfcGVyZmVjdHNfYW5kXzFfbnVtX21lbHQsIAogICAgICAgICAgIkJNUy9ibXNfZmlsZXNfZm9ybWF0dGVkL0JNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMV9udW1fbWVsdC5jc3YiLCByb3cubmFtZXMgPSBGQUxTRSkKYGBgCgojIFJlcHJvZHVjaWJpbGl0eQoKVGhlIHNlc3Npb24gaW5mb3JtYXRpb24gaXMgcHJvdmlkZWQgZm9yIGZ1bGwgcmVwcm9kdWNpYmlsaXR5LgpgYGB7cn0KZGV2dG9vbHM6OnNlc3Npb25faW5mbygpCmBgYA==