This document details a differential expression analysis of total proteome data acquired using a TMT-MS approach with SPS-MS3 on an Orbitrap Fusion Lumos. This document begins with the processing of peptide identification search results that were obtained using the procedure outlined in the document found here.

Setting up the environment

These are packages you will need for this notebook. For exact versions used, please refer to the session info at the bottom of this notebook.

library('tidyverse')
library('ggplot2')
library('RColorBrewer')
library('vroom')
library('DEqMS')

I like to set a base directory that we can use as a link to the directory where we will do most of the work. I use two directories here because the Workspace is what is pushed to my GitHub for version tracking and contains things like scripts, but the Repository is where more of the big data is stored that does not get pushed (raw search results).

baseWorkspace = 'C:/Users/chris/OneDrive/Documents/bccrc/protocolsDryLab/relatedToProteomics/'
baseRepository = 'C:/Users/chris/OneDrive/Documents/bccrc/protocolsRepository/totalProteomeDifferentialExpressionAnalysis'

In order to compile the quantification data later on, I use a function for simplicity. I have defined this function in a separate file that I call into our session using the command below. The file referenced below with the function can be found here.

source(paste(baseWorkspace, '/totalProteomeDifferentialExpressionAnalysisUserDefinedFunctions.R', sep = ''))

The last thing we want to get is our annotated fasta index. If you followed the raw data processing outlined in the file mentioned above, this will have been created in the directory with all of the other search results. If you did not follow that processing, you can use the R script provided in that file to easily generate an index file from a fasta database.

##########################################################################################
proteinAnnotation = readRDS(paste(baseRepository, '/uniprotHumanJun2020.fasta.annotated.rds', sep = ''))

Data processing

I want to process the data using DEqMS for differential expression. I am following their guide as detailed here as it is very nice and quite extensive.

The first thing we need to do is read in the peptide search results. Below there are a few commands, but we are essentially reading our PSM result file (as generated in the raw data processing script above) and doing some basic parsing to make it more manageable. It is all based on the tidyverse package, so if you are unfamiliar with text processing using this package, please look around online as they have extensive documentation. If you have not followed the same raw data processing pipeline as I have above, your search results may look different.

##########################################################################################
psm = vroom(paste(baseRepository, '/n_10Oct2016_ADROIT_PrepCompare_TMT10_hph_1_Default_PSM_Report.txt', sep = '')) %>%
  dplyr::select(`Protein(s)`, Sequence, `Modified Sequence`, `Spectrum File`, `Spectrum Scan Number`) %>%
  mutate(fraction = sub('.*hph_(.*)\\.raw\\.mgf$', '\\1', `Spectrum File`)) %>%
  mutate(accession = `Protein(s)`, scan = `Spectrum Scan Number`, sequence = Sequence, modSequence = `Modified Sequence`) %>%
  left_join(proteinAnnotation) %>%
  dplyr::select(fraction, scan, accession, gene, detectablePeptides, sequence, modSequence)
New names:
* `` -> ...1
Rows: 157,560
Columns: 24
Delimiter: "\t"
chr [16]: Protein(s), Sequence, AAs Before, AAs After, Position, Modified Sequence, Variable Modifications, Fixed Modifications, Spectrum File, Spectrum Title, ...
dbl [ 8]: , Spectrum Scan Number, RT, m/z, Theoretical Mass, Isotope Number, Precursor m/z Error [ppm], Confidence [%]

Use `spec()` to retrieve the guessed column specification
Pass a specification to the `col_types` argument to quiet this message
Joining, by = "accession"

Now we need the quantification data. These are hosted in individual files in the same directory as the above data and were generated as part of the raw data processing described above. Again, if you have not followed the same initial data processing pipeline as me, your files may look different.

##########################################################################################
##get the list of files to be processed
quantFiles = as.list(list.files(paste(baseRepository, '/quantFiles/', sep = ''),
           pattern = '_Matrix.txt', full.names = TRUE))
##process using our defined function, combineQuantFiles
quantDataSet = lapply(quantFiles, combineQuantFiles)
[1] "Processing file for fraction 1."
[1] "Processing file for fraction 10."
[1] "Processing file for fraction 11."
[1] "Processing file for fraction 12."
[1] "Processing file for fraction 2."
[1] "Processing file for fraction 3."
[1] "Processing file for fraction 4."
[1] "Processing file for fraction 5."
[1] "Processing file for fraction 6."
[1] "Processing file for fraction 7."
[1] "Processing file for fraction 8."
[1] "Processing file for fraction 9."

Now combine the quant data into a single data frame and combine with the previously processed PSM data.

##########################################################################################
##collapse the data into a single data frame
allQuantData = do.call('rbind', quantDataSet)
##combine with the PSM data from above
psmQuant = psm %>%
  left_join(allQuantData)
Joining, by = c("fraction", "scan")

Now we will filter the expression data to discard any entries where the signal is just too low to be confident in the data. I generally will use a cutoff of a minimum signal of 10 per TMT channel. In this case we have 9 channels, so we will use a sum signal of 90 as a cutoff. Here, we also filter out genes that have no assigned gene name as this will cause problems with DEqMS later on. If you don’t want to do this, I suggest using ‘accession’ instead of ‘gene’ in the DEqMS analysis below.

##########################################################################################
psmQuant$sampleSignal = rowSums(psmQuant[,c(8:16)])
psmQuantFiltered = subset(psmQuant, psmQuant$sampleSignal >= 90 & !is.na(psmQuant$gene) & !grepl('-', psmQuant$gene))
psmQuantFiltered

We will also normalize the data to deal with any large loading differences. We save both the non-normalized data here as well and check the normalization with a plot. In the code below, we only include columns where we have sample data. In this case we didn’t use all 11 TMT reporter ion channels, so we get rid of those where we didn’t have a sample.

##########################################################################################
quantInput = psmQuantFiltered[,c(1:16)] ##make a new data frame object
saveRDS(quantInput, paste(baseRepository, '/dataset_peptideQuantDataPreDEqMS.rds', sep = '')) ##save the data
quantInputLog = quantInput[,c(7,4,8:16)] ##choose only columns where we have samples
quantInputLog[,3:11][quantInputLog[,3:11] == 0] = NA ##set zero values to NA
quantInputLog[,3:11] = log2(quantInputLog[,3:11])
quantInputNormalized = medianSweeping(quantInputLog, group_col = 2)
boxplot(quantInputNormalized, las = 2, ylab = 'log2 ratio', main = 'normalized TMT data')

This plot looks good, Our data are generally mean centered with the middle three samples have some more variation (these are actually treated samples, so this is not surprising). Lets continue with the DEqMS analysis. The next thing we need to do for DeQMS analysis is make our sample table. Our sample layout is ‘A1’,‘A2’,‘A3’,‘B1’,‘B2’,‘B3’,‘C1’,‘C2’,‘C3’, and a ‘pool’ of all samples in the last channel in a TMT10-plex format (TMT131C is not used).

##########################################################################################
cond = as.factor(c('A','A','A','B','B','B','C','C','C'))
design = model.matrix(~0+cond) 
colnames(design) = gsub("cond","",colnames(design))

Now we can build the Limma model and get the different contrasts. Again, as noted above, we are just following the DEqMS pipeline as described on their own page. The limma package also has some great documentation that explains many of the processes that DEqMS is using below.

##########################################################################################
geneMatrix = as.matrix(quantInputNormalized) ##extract just the quantification values to a matrix
limmaFit1 = lmFit(geneMatrix, design) ##generate the initial model fit using limma
Partial NA coefficients for 2 probe(s)
limmaContrasts = c('A-B','A-C','B-C') ##the different conditions we want to compare
limmaContrastDesign =  makeContrasts(contrasts = limmaContrasts, levels = design) ##make a new design matrix with our contrasts
limmaFit2 = eBayes(contrasts.fit(limmaFit1, contrasts = limmaContrastDesign)) ##perform eBayes smoothing using limma

Lastly, we will use DEqMS to summarize the data and extract out the different contrasts to save them to a file for each.

##########################################################################################
psmCountTable = as.data.frame(table(quantInput$gene)) ##count the number of times each gene is appearing
rownames(psmCountTable) = psmCountTable$Var1 ##assign genes as the row names
limmaFit2$count = psmCountTable[rownames(limmaFit2$coefficients),2] ##add the counts to the limma model
limmaFit3 = spectraCounteBayes(limmaFit2) ##do the final processing with deqms
head(limmaFit3$coefficients) ##take a look at the final data
             A-B         A-C        B-C
A2M    1.7989850  0.16655554 -1.6324295
A2ML1  1.5225197  0.44212828 -1.0803914
AAAS   1.5744897  0.25890516 -1.3155845
AACS   0.4925278 -0.04314785 -0.5356756
AAGAB  0.6976049 -0.78425106 -1.4818560
AAK1  -0.6994632  0.17290417  0.8723674
##save the individual contrasts
deqmsResults = outputResult(limmaFit3, coef_col = 1) 
write.table(deqmsResults, 
            paste(baseRepository, '/dataset_deqmsA-B.csv', sep = ''),
            sep = ',', row.names = FALSE, quote = FALSE)
##
deqmsResults = outputResult(limmaFit3, coef_col = 2) 
write.table(deqmsResults, 
            paste(baseRepository, '/dataset_deqmsA-C.csv', sep = ''),
            sep = ',', row.names = FALSE, quote = FALSE)
##
deqmsResults = outputResult(limmaFit3, coef_col = 3) 
write.table(deqmsResults, 
            paste(baseRepository, '/dataset_deqmsB-C.csv', sep = ''),
            sep = ',', row.names = FALSE, quote = FALSE)

Generate the variance plot to see the DEqMS model for the data.

##########################################################################################
VarianceBoxplot(limmaFit3, n=20, xlab = 'PSM count', main = 'DEqMS analysis of TMT data')

So we can see variance increasing as the PSM count gets lower, which is what we expect. Now let us make a volcano plot to show the data, as an example.

##########################################################################################
deqmsResults = outputResult(limmaFit3, coef_col = 1)
deqmsResults$plotPValue = -log10(deqmsResults$sca.P.Value)
deqmsResults$pColors = ifelse(deqmsResults$logFC <= -1 & deqmsResults$plotPValue >= 3, brewer.pal(8,'RdBu')[8],
                              ifelse(deqmsResults$logFC >= 1 & deqmsResults$plotPValue >= 3, brewer.pal(8,'RdBu')[1],
                                     brewer.pal(8,'Greys')[6]))
##
ggplot(deqmsResults, aes(logFC, plotPValue)) +
  geom_point(size = 1, color = deqmsResults$pColors, alpha = 0.5) +
  labs(x = 'log2(A fold change to B)', y = '-log10(P-value)', title = 'Sample A versus B') +
  scale_y_continuous(limits = c(0,12), breaks = seq(0,18,3)) +
  geom_vline(xintercept = c(-1,1), linetype = 'dashed') +
  geom_hline(yintercept = 3, linetype = 'dashed') +
  theme_classic()
ggsave(paste(baseRepository, '/scatter_deqmsA-B.pdf', sep = ''),
       height = 4, width = 4, useDingbats = FALSE)

So quite a bit has changed here. If you want to plot other contrasts, simply change the ‘coef_col’ value in the first command in the code above to a different value. You can now investigate these data further to determine biological variance seen in your own data!

Session info

sessionInfo()
R version 3.6.3 (2020-02-29)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows 10 x64 (build 18363)

Matrix products: default

locale:
[1] LC_COLLATE=English_Canada.1252  LC_CTYPE=English_Canada.1252    LC_MONETARY=English_Canada.1252 LC_NUMERIC=C                   
[5] LC_TIME=English_Canada.1252    

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] DEqMS_1.4.0        limma_3.42.2       vroom_1.2.1        RColorBrewer_1.1-2 forcats_0.5.0      stringr_1.4.0      dplyr_0.8.5       
 [8] purrr_0.3.4        readr_1.3.1        tidyr_1.0.3        tibble_3.0.1       ggplot2_3.3.1      tidyverse_1.3.0   

loaded via a namespace (and not attached):
 [1] Rcpp_1.0.4.6       lubridate_1.7.8    lattice_0.20-38    assertthat_0.2.1   digest_0.6.25      R6_2.4.1           cellranger_1.1.0  
 [8] plyr_1.8.6         backports_1.1.7    reprex_0.3.0       evaluate_0.14      httr_1.4.1         pillar_1.4.4       rlang_0.4.6       
[15] readxl_1.3.1       rstudioapi_0.11    blob_1.2.1         rmarkdown_2.1      labeling_0.3       bit_1.1-15.2       munsell_0.5.0     
[22] broom_0.5.6        compiler_3.6.3     modelr_0.1.8       xfun_0.14          pkgconfig_2.0.3    base64enc_0.1-3    htmltools_0.4.0   
[29] tidyselect_1.1.0   matrixStats_0.56.0 fansi_0.4.1        crayon_1.3.4       dbplyr_1.4.4       withr_2.2.0        grid_3.6.3        
[36] nlme_3.1-144       jsonlite_1.6.1     gtable_0.3.0       lifecycle_0.2.0    DBI_1.1.0          magrittr_1.5       scales_1.1.1      
[43] cli_2.0.2          stringi_1.4.6      farver_2.0.3       fs_1.4.1           xml2_1.3.2         ellipsis_0.3.1     generics_0.0.2    
[50] vctrs_0.3.0        tools_3.6.3        bit64_0.9-7        glue_1.4.1         hms_0.5.3          parallel_3.6.3     yaml_2.2.1        
[57] colorspace_1.4-1   rvest_0.3.5        knitr_1.28         haven_2.2.0       
LS0tDQp0aXRsZTogIkRpZmZlcmVudGlhbCBleHByZXNzaW9uIGFuYWx5c2lzIG9mIHByb3Rlb21pY3MgZGF0YSINCm91dHB1dDoNCiAgaHRtbF9ub3RlYm9vazoNCiAgICAgIGNvZGVfZm9sZGluZzogbm9uZQ0KLS0tDQoNClRoaXMgZG9jdW1lbnQgZGV0YWlscyBhIGRpZmZlcmVudGlhbCBleHByZXNzaW9uIGFuYWx5c2lzIG9mIHRvdGFsIHByb3Rlb21lIGRhdGEgYWNxdWlyZWQgdXNpbmcgYSBUTVQtTVMgYXBwcm9hY2ggd2l0aCBTUFMtTVMzIG9uIGFuIE9yYml0cmFwIEZ1c2lvbiBMdW1vcy4gVGhpcyBkb2N1bWVudCBiZWdpbnMgd2l0aCB0aGUgcHJvY2Vzc2luZyBvZiBwZXB0aWRlIGlkZW50aWZpY2F0aW9uIHNlYXJjaCByZXN1bHRzIHRoYXQgd2VyZSBvYnRhaW5lZCB1c2luZyB0aGUgcHJvY2VkdXJlIG91dGxpbmVkIGluIHRoZSBkb2N1bWVudCBmb3VuZCBbaGVyZV0oaHR0cHM6Ly9naXRodWIuY29tL2NocmlzaHVnZXMvcHJvdG9jb2xzRHJ5TGFiL2Jsb2IvbWFzdGVyL3JlbGF0ZWRUb1Byb3Rlb21pY3MvdG90YWxQcm90ZW9tZURpZmZlcmVudGlhbEV4cHJlc3Npb25BbmFseXNpc1Jhd01zRGF0YVByb2Nlc3NpbmcubWQpLg0KDQo8ZGl2IHN0eWxlPSJtYXJnaW4tYm90dG9tOjUwcHg7Ij48L2Rpdj4NCg0KIyMgU2V0dGluZyB1cCB0aGUgZW52aXJvbm1lbnQNCg0KVGhlc2UgYXJlIHBhY2thZ2VzIHlvdSB3aWxsIG5lZWQgZm9yIHRoaXMgbm90ZWJvb2suIEZvciBleGFjdCB2ZXJzaW9ucyB1c2VkLCBwbGVhc2UgcmVmZXIgdG8gdGhlIHNlc3Npb24gaW5mbyBhdCB0aGUgYm90dG9tIG9mIHRoaXMgbm90ZWJvb2suDQoNCmBgYHtyLCBtZXNzYWdlID0gRkFMU0V9DQpsaWJyYXJ5KCd0aWR5dmVyc2UnKQ0KbGlicmFyeSgnZ2dwbG90MicpDQpsaWJyYXJ5KCdSQ29sb3JCcmV3ZXInKQ0KbGlicmFyeSgndnJvb20nKQ0KbGlicmFyeSgnREVxTVMnKQ0KYGBgDQoNCjxkaXYgc3R5bGU9Im1hcmdpbi1ib3R0b206NTBweDsiPjwvZGl2Pg0KDQpJIGxpa2UgdG8gc2V0IGEgYmFzZSBkaXJlY3RvcnkgdGhhdCB3ZSBjYW4gdXNlIGFzIGEgbGluayB0byB0aGUgZGlyZWN0b3J5IHdoZXJlIHdlIHdpbGwgZG8gbW9zdCBvZiB0aGUgd29yay4gSSB1c2UgdHdvIGRpcmVjdG9yaWVzIGhlcmUgYmVjYXVzZSB0aGUgV29ya3NwYWNlIGlzIHdoYXQgaXMgcHVzaGVkIHRvIG15IEdpdEh1YiBmb3IgdmVyc2lvbiB0cmFja2luZyBhbmQgY29udGFpbnMgdGhpbmdzIGxpa2Ugc2NyaXB0cywgYnV0IHRoZSBSZXBvc2l0b3J5IGlzIHdoZXJlIG1vcmUgb2YgdGhlIGJpZyBkYXRhIGlzIHN0b3JlZCB0aGF0IGRvZXMgbm90IGdldCBwdXNoZWQgKHJhdyBzZWFyY2ggcmVzdWx0cykuDQoNCmBgYHtyfQ0KYmFzZVdvcmtzcGFjZSA9ICdDOi9Vc2Vycy9jaHJpcy9PbmVEcml2ZS9Eb2N1bWVudHMvYmNjcmMvcHJvdG9jb2xzRHJ5TGFiL3JlbGF0ZWRUb1Byb3Rlb21pY3MvJw0KYmFzZVJlcG9zaXRvcnkgPSAnQzovVXNlcnMvY2hyaXMvT25lRHJpdmUvRG9jdW1lbnRzL2JjY3JjL3Byb3RvY29sc1JlcG9zaXRvcnkvdG90YWxQcm90ZW9tZURpZmZlcmVudGlhbEV4cHJlc3Npb25BbmFseXNpcycNCmBgYA0KDQo8ZGl2IHN0eWxlPSJtYXJnaW4tYm90dG9tOjUwcHg7Ij48L2Rpdj4NCg0KSW4gb3JkZXIgdG8gY29tcGlsZSB0aGUgcXVhbnRpZmljYXRpb24gZGF0YSBsYXRlciBvbiwgSSB1c2UgYSBmdW5jdGlvbiBmb3Igc2ltcGxpY2l0eS4gSSBoYXZlIGRlZmluZWQgdGhpcyBmdW5jdGlvbiBpbiBhIHNlcGFyYXRlIGZpbGUgdGhhdCBJIGNhbGwgaW50byBvdXIgc2Vzc2lvbiB1c2luZyB0aGUgY29tbWFuZCBiZWxvdy4gVGhlIGZpbGUgcmVmZXJlbmNlZCBiZWxvdyB3aXRoIHRoZSBmdW5jdGlvbiBjYW4gYmUgZm91bmQgW2hlcmVdKGh0dHBzOi8vZ2l0aHViLmNvbS9jaHJpc2h1Z2VzL3Byb3RvY29sc0RyeUxhYi9ibG9iL21hc3Rlci9yZWxhdGVkVG9Qcm90ZW9taWNzL3RvdGFsUHJvdGVvbWVEaWZmZXJlbnRpYWxFeHByZXNzaW9uQW5hbHlzaXNVc2VyRGVmaW5lZEZ1bmN0aW9ucy5SKS4NCg0KYGBge3J9DQpzb3VyY2UocGFzdGUoYmFzZVdvcmtzcGFjZSwgJy90b3RhbFByb3Rlb21lRGlmZmVyZW50aWFsRXhwcmVzc2lvbkFuYWx5c2lzVXNlckRlZmluZWRGdW5jdGlvbnMuUicsIHNlcCA9ICcnKSkNCmBgYA0KDQo8ZGl2IHN0eWxlPSJtYXJnaW4tYm90dG9tOjUwcHg7Ij48L2Rpdj4NCg0KVGhlIGxhc3QgdGhpbmcgd2Ugd2FudCB0byBnZXQgaXMgb3VyIGFubm90YXRlZCBmYXN0YSBpbmRleC4gSWYgeW91IGZvbGxvd2VkIHRoZSByYXcgZGF0YSBwcm9jZXNzaW5nIG91dGxpbmVkIGluIHRoZSBmaWxlIG1lbnRpb25lZCBhYm92ZSwgdGhpcyB3aWxsIGhhdmUgYmVlbiBjcmVhdGVkIGluIHRoZSBkaXJlY3Rvcnkgd2l0aCBhbGwgb2YgdGhlIG90aGVyIHNlYXJjaCByZXN1bHRzLiBJZiB5b3UgZGlkIG5vdCBmb2xsb3cgdGhhdCBwcm9jZXNzaW5nLCB5b3UgY2FuIHVzZSB0aGUgUiBzY3JpcHQgcHJvdmlkZWQgaW4gdGhhdCBmaWxlIHRvIGVhc2lseSBnZW5lcmF0ZSBhbiBpbmRleCBmaWxlIGZyb20gYSBmYXN0YSBkYXRhYmFzZS4NCg0KYGBge3J9DQojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMNCnByb3RlaW5Bbm5vdGF0aW9uID0gcmVhZFJEUyhwYXN0ZShiYXNlUmVwb3NpdG9yeSwgJy91bmlwcm90SHVtYW5KdW4yMDIwLmZhc3RhLmFubm90YXRlZC5yZHMnLCBzZXAgPSAnJykpDQpgYGANCg0KPGRpdiBzdHlsZT0ibWFyZ2luLWJvdHRvbTo1MHB4OyI+PC9kaXY+DQoNCiMjIERhdGEgcHJvY2Vzc2luZw0KDQpJIHdhbnQgdG8gcHJvY2VzcyB0aGUgZGF0YSB1c2luZyBERXFNUyBmb3IgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24uIEkgYW0gZm9sbG93aW5nIHRoZWlyIGd1aWRlIGFzIGRldGFpbGVkIFtoZXJlXShodHRwczovL3d3dy5iaW9jb25kdWN0b3Iub3JnL3BhY2thZ2VzL3JlbGVhc2UvYmlvYy92aWduZXR0ZXMvREVxTVMvaW5zdC9kb2MvREVxTVMtcGFja2FnZS12aWduZXR0ZS5odG1sI3F1aWNrLXN0YXJ0KSBhcyBpdCBpcyB2ZXJ5IG5pY2UgYW5kIHF1aXRlIGV4dGVuc2l2ZS4gDQoNClRoZSBmaXJzdCB0aGluZyB3ZSBuZWVkIHRvIGRvIGlzIHJlYWQgaW4gdGhlIHBlcHRpZGUgc2VhcmNoIHJlc3VsdHMuIEJlbG93IHRoZXJlIGFyZSBhIGZldyBjb21tYW5kcywgYnV0IHdlIGFyZSBlc3NlbnRpYWxseSByZWFkaW5nIG91ciBQU00gcmVzdWx0IGZpbGUgKGFzIGdlbmVyYXRlZCBpbiB0aGUgcmF3IGRhdGEgcHJvY2Vzc2luZyBzY3JpcHQgYWJvdmUpIGFuZCBkb2luZyBzb21lIGJhc2ljIHBhcnNpbmcgdG8gbWFrZSBpdCBtb3JlIG1hbmFnZWFibGUuIEl0IGlzIGFsbCBiYXNlZCBvbiB0aGUgdGlkeXZlcnNlIHBhY2thZ2UsIHNvIGlmIHlvdSBhcmUgdW5mYW1pbGlhciB3aXRoIHRleHQgcHJvY2Vzc2luZyB1c2luZyB0aGlzIHBhY2thZ2UsIHBsZWFzZSBsb29rIGFyb3VuZCBvbmxpbmUgYXMgdGhleSBoYXZlIGV4dGVuc2l2ZSBkb2N1bWVudGF0aW9uLiBJZiB5b3UgaGF2ZSBub3QgZm9sbG93ZWQgdGhlIHNhbWUgcmF3IGRhdGEgcHJvY2Vzc2luZyBwaXBlbGluZSBhcyBJIGhhdmUgYWJvdmUsIHlvdXIgc2VhcmNoIHJlc3VsdHMgbWF5IGxvb2sgZGlmZmVyZW50Lg0KDQpgYGB7cn0NCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIw0KcHNtID0gdnJvb20ocGFzdGUoYmFzZVJlcG9zaXRvcnksICcvbl8xME9jdDIwMTZfQURST0lUX1ByZXBDb21wYXJlX1RNVDEwX2hwaF8xX0RlZmF1bHRfUFNNX1JlcG9ydC50eHQnLCBzZXAgPSAnJykpICU+JQ0KICBkcGx5cjo6c2VsZWN0KGBQcm90ZWluKHMpYCwgU2VxdWVuY2UsIGBNb2RpZmllZCBTZXF1ZW5jZWAsIGBTcGVjdHJ1bSBGaWxlYCwgYFNwZWN0cnVtIFNjYW4gTnVtYmVyYCkgJT4lDQogIG11dGF0ZShmcmFjdGlvbiA9IHN1YignLipocGhfKC4qKVxcLnJhd1xcLm1nZiQnLCAnXFwxJywgYFNwZWN0cnVtIEZpbGVgKSkgJT4lDQogIG11dGF0ZShhY2Nlc3Npb24gPSBgUHJvdGVpbihzKWAsIHNjYW4gPSBgU3BlY3RydW0gU2NhbiBOdW1iZXJgLCBzZXF1ZW5jZSA9IFNlcXVlbmNlLCBtb2RTZXF1ZW5jZSA9IGBNb2RpZmllZCBTZXF1ZW5jZWApICU+JQ0KICBsZWZ0X2pvaW4ocHJvdGVpbkFubm90YXRpb24pICU+JQ0KICBkcGx5cjo6c2VsZWN0KGZyYWN0aW9uLCBzY2FuLCBhY2Nlc3Npb24sIGdlbmUsIGRldGVjdGFibGVQZXB0aWRlcywgc2VxdWVuY2UsIG1vZFNlcXVlbmNlKQ0KYGBgDQoNCjxkaXYgc3R5bGU9Im1hcmdpbi1ib3R0b206NTBweDsiPjwvZGl2Pg0KDQpOb3cgd2UgbmVlZCB0aGUgcXVhbnRpZmljYXRpb24gZGF0YS4gVGhlc2UgYXJlIGhvc3RlZCBpbiBpbmRpdmlkdWFsIGZpbGVzIGluIHRoZSBzYW1lIGRpcmVjdG9yeSBhcyB0aGUgYWJvdmUgZGF0YSBhbmQgd2VyZSBnZW5lcmF0ZWQgYXMgcGFydCBvZiB0aGUgcmF3IGRhdGEgcHJvY2Vzc2luZyBkZXNjcmliZWQgYWJvdmUuIEFnYWluLCBpZiB5b3UgaGF2ZSBub3QgZm9sbG93ZWQgdGhlIHNhbWUgaW5pdGlhbCBkYXRhIHByb2Nlc3NpbmcgcGlwZWxpbmUgYXMgbWUsIHlvdXIgZmlsZXMgbWF5IGxvb2sgZGlmZmVyZW50Lg0KDQpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0V9DQojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMNCiMjZ2V0IHRoZSBsaXN0IG9mIGZpbGVzIHRvIGJlIHByb2Nlc3NlZA0KcXVhbnRGaWxlcyA9IGFzLmxpc3QobGlzdC5maWxlcyhwYXN0ZShiYXNlUmVwb3NpdG9yeSwgJy9xdWFudEZpbGVzLycsIHNlcCA9ICcnKSwNCiAgICAgICAgICAgcGF0dGVybiA9ICdfTWF0cml4LnR4dCcsIGZ1bGwubmFtZXMgPSBUUlVFKSkNCiMjcHJvY2VzcyB1c2luZyBvdXIgZGVmaW5lZCBmdW5jdGlvbiwgY29tYmluZVF1YW50RmlsZXMNCnF1YW50RGF0YVNldCA9IGxhcHBseShxdWFudEZpbGVzLCBjb21iaW5lUXVhbnRGaWxlcykNCmBgYA0KDQo8ZGl2IHN0eWxlPSJtYXJnaW4tYm90dG9tOjUwcHg7Ij48L2Rpdj4NCg0KTm93IGNvbWJpbmUgdGhlIHF1YW50IGRhdGEgaW50byBhIHNpbmdsZSBkYXRhIGZyYW1lIGFuZCBjb21iaW5lIHdpdGggdGhlIHByZXZpb3VzbHkgcHJvY2Vzc2VkIFBTTSBkYXRhLg0KDQpgYGB7cn0NCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIw0KIyNjb2xsYXBzZSB0aGUgZGF0YSBpbnRvIGEgc2luZ2xlIGRhdGEgZnJhbWUNCmFsbFF1YW50RGF0YSA9IGRvLmNhbGwoJ3JiaW5kJywgcXVhbnREYXRhU2V0KQ0KIyNjb21iaW5lIHdpdGggdGhlIFBTTSBkYXRhIGZyb20gYWJvdmUNCnBzbVF1YW50ID0gcHNtICU+JQ0KICBsZWZ0X2pvaW4oYWxsUXVhbnREYXRhKQ0KYGBgDQoNCjxkaXYgc3R5bGU9Im1hcmdpbi1ib3R0b206NTBweDsiPjwvZGl2Pg0KDQpOb3cgd2Ugd2lsbCBmaWx0ZXIgdGhlIGV4cHJlc3Npb24gZGF0YSB0byBkaXNjYXJkIGFueSBlbnRyaWVzIHdoZXJlIHRoZSBzaWduYWwgaXMganVzdCB0b28gbG93IHRvIGJlIGNvbmZpZGVudCBpbiB0aGUgZGF0YS4gSSBnZW5lcmFsbHkgd2lsbCB1c2UgYSBjdXRvZmYgb2YgYSBtaW5pbXVtIHNpZ25hbCBvZiAxMCBwZXIgVE1UIGNoYW5uZWwuIEluIHRoaXMgY2FzZSB3ZSBoYXZlIDkgY2hhbm5lbHMsIHNvIHdlIHdpbGwgdXNlIGEgc3VtIHNpZ25hbCBvZiA5MCBhcyBhIGN1dG9mZi4gSGVyZSwgd2UgYWxzbyBmaWx0ZXIgb3V0IGdlbmVzIHRoYXQgaGF2ZSBubyBhc3NpZ25lZCBnZW5lIG5hbWUgYXMgdGhpcyB3aWxsIGNhdXNlIHByb2JsZW1zIHdpdGggREVxTVMgbGF0ZXIgb24uIElmIHlvdSBkb24ndCB3YW50IHRvIGRvIHRoaXMsIEkgc3VnZ2VzdCB1c2luZyAnYWNjZXNzaW9uJyBpbnN0ZWFkIG9mICdnZW5lJyBpbiB0aGUgREVxTVMgYW5hbHlzaXMgYmVsb3cuDQoNCmBgYHtyfQ0KIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjDQpwc21RdWFudCRzYW1wbGVTaWduYWwgPSByb3dTdW1zKHBzbVF1YW50WyxjKDg6MTYpXSkNCnBzbVF1YW50RmlsdGVyZWQgPSBzdWJzZXQocHNtUXVhbnQsIHBzbVF1YW50JHNhbXBsZVNpZ25hbCA+PSA5MCAmICFpcy5uYShwc21RdWFudCRnZW5lKSAmICFncmVwbCgnLScsIHBzbVF1YW50JGdlbmUpKQ0KcHNtUXVhbnRGaWx0ZXJlZA0KYGBgDQoNCjxkaXYgc3R5bGU9Im1hcmdpbi1ib3R0b206NTBweDsiPjwvZGl2Pg0KDQpXZSB3aWxsIGFsc28gbm9ybWFsaXplIHRoZSBkYXRhIHRvIGRlYWwgd2l0aCBhbnkgbGFyZ2UgbG9hZGluZyBkaWZmZXJlbmNlcy4gV2Ugc2F2ZSBib3RoIHRoZSBub24tbm9ybWFsaXplZCBkYXRhIGhlcmUgYXMgd2VsbCBhbmQgY2hlY2sgdGhlIG5vcm1hbGl6YXRpb24gd2l0aCBhIHBsb3QuIEluIHRoZSBjb2RlIGJlbG93LCB3ZSBvbmx5IGluY2x1ZGUgY29sdW1ucyB3aGVyZSB3ZSBoYXZlIHNhbXBsZSBkYXRhLiBJbiB0aGlzIGNhc2Ugd2UgZGlkbid0IHVzZSBhbGwgMTEgVE1UIHJlcG9ydGVyIGlvbiBjaGFubmVscywgc28gd2UgZ2V0IHJpZCBvZiB0aG9zZSB3aGVyZSB3ZSBkaWRuJ3QgaGF2ZSBhIHNhbXBsZS4NCg0KYGBge3J9DQojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMNCnF1YW50SW5wdXQgPSBwc21RdWFudEZpbHRlcmVkWyxjKDE6MTYpXSAjI21ha2UgYSBuZXcgZGF0YSBmcmFtZSBvYmplY3QNCnNhdmVSRFMocXVhbnRJbnB1dCwgcGFzdGUoYmFzZVJlcG9zaXRvcnksICcvZGF0YXNldF9wZXB0aWRlUXVhbnREYXRhUHJlREVxTVMucmRzJywgc2VwID0gJycpKSAjI3NhdmUgdGhlIGRhdGENCnF1YW50SW5wdXRMb2cgPSBxdWFudElucHV0WyxjKDcsNCw4OjE2KV0gIyNjaG9vc2Ugb25seSBjb2x1bW5zIHdoZXJlIHdlIGhhdmUgc2FtcGxlcw0KcXVhbnRJbnB1dExvZ1ssMzoxMV1bcXVhbnRJbnB1dExvZ1ssMzoxMV0gPT0gMF0gPSBOQSAjI3NldCB6ZXJvIHZhbHVlcyB0byBOQQ0KcXVhbnRJbnB1dExvZ1ssMzoxMV0gPSBsb2cyKHF1YW50SW5wdXRMb2dbLDM6MTFdKQ0KcXVhbnRJbnB1dE5vcm1hbGl6ZWQgPSBtZWRpYW5Td2VlcGluZyhxdWFudElucHV0TG9nLCBncm91cF9jb2wgPSAyKQ0KYm94cGxvdChxdWFudElucHV0Tm9ybWFsaXplZCwgbGFzID0gMiwgeWxhYiA9ICdsb2cyIHJhdGlvJywgbWFpbiA9ICdub3JtYWxpemVkIFRNVCBkYXRhJykNCmBgYA0KDQo8ZGl2IHN0eWxlPSJtYXJnaW4tYm90dG9tOjUwcHg7Ij48L2Rpdj4NCg0KVGhpcyBwbG90IGxvb2tzIGdvb2QsIE91ciBkYXRhIGFyZSBnZW5lcmFsbHkgbWVhbiBjZW50ZXJlZCB3aXRoIHRoZSBtaWRkbGUgdGhyZWUgc2FtcGxlcyBoYXZlIHNvbWUgbW9yZSB2YXJpYXRpb24gKHRoZXNlIGFyZSBhY3R1YWxseSB0cmVhdGVkIHNhbXBsZXMsIHNvIHRoaXMgaXMgbm90IHN1cnByaXNpbmcpLiBMZXRzIGNvbnRpbnVlIHdpdGggdGhlIERFcU1TIGFuYWx5c2lzLiBUaGUgbmV4dCB0aGluZyB3ZSBuZWVkIHRvIGRvIGZvciBEZVFNUyBhbmFseXNpcyBpcyBtYWtlIG91ciBzYW1wbGUgdGFibGUuIE91ciBzYW1wbGUgbGF5b3V0IGlzICdBMScsJ0EyJywnQTMnLCdCMScsJ0IyJywnQjMnLCdDMScsJ0MyJywnQzMnLCBhbmQgYSAncG9vbCcgb2YgYWxsIHNhbXBsZXMgaW4gdGhlIGxhc3QgY2hhbm5lbCBpbiBhIFRNVDEwLXBsZXggZm9ybWF0IChUTVQxMzFDIGlzIG5vdCB1c2VkKS4NCg0KYGBge3J9DQojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMNCmNvbmQgPSBhcy5mYWN0b3IoYygnQScsJ0EnLCdBJywnQicsJ0InLCdCJywnQycsJ0MnLCdDJykpDQpkZXNpZ24gPSBtb2RlbC5tYXRyaXgofjArY29uZCkgDQpjb2xuYW1lcyhkZXNpZ24pID0gZ3N1YigiY29uZCIsIiIsY29sbmFtZXMoZGVzaWduKSkNCmBgYA0KDQo8ZGl2IHN0eWxlPSJtYXJnaW4tYm90dG9tOjUwcHg7Ij48L2Rpdj4NCg0KTm93IHdlIGNhbiBidWlsZCB0aGUgTGltbWEgbW9kZWwgYW5kIGdldCB0aGUgZGlmZmVyZW50IGNvbnRyYXN0cy4gQWdhaW4sIGFzIG5vdGVkIGFib3ZlLCB3ZSBhcmUganVzdCBmb2xsb3dpbmcgdGhlIERFcU1TIHBpcGVsaW5lIGFzIGRlc2NyaWJlZCBvbiB0aGVpciBvd24gcGFnZS4gVGhlIGxpbW1hIHBhY2thZ2UgYWxzbyBoYXMgc29tZSBncmVhdCBkb2N1bWVudGF0aW9uIHRoYXQgZXhwbGFpbnMgbWFueSBvZiB0aGUgcHJvY2Vzc2VzIHRoYXQgREVxTVMgaXMgdXNpbmcgYmVsb3cuDQoNCmBgYHtyfQ0KIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjDQpnZW5lTWF0cml4ID0gYXMubWF0cml4KHF1YW50SW5wdXROb3JtYWxpemVkKSAjI2V4dHJhY3QganVzdCB0aGUgcXVhbnRpZmljYXRpb24gdmFsdWVzIHRvIGEgbWF0cml4DQpsaW1tYUZpdDEgPSBsbUZpdChnZW5lTWF0cml4LCBkZXNpZ24pICMjZ2VuZXJhdGUgdGhlIGluaXRpYWwgbW9kZWwgZml0IHVzaW5nIGxpbW1hDQpsaW1tYUNvbnRyYXN0cyA9IGMoJ0EtQicsJ0EtQycsJ0ItQycpICMjdGhlIGRpZmZlcmVudCBjb25kaXRpb25zIHdlIHdhbnQgdG8gY29tcGFyZQ0KbGltbWFDb250cmFzdERlc2lnbiA9ICBtYWtlQ29udHJhc3RzKGNvbnRyYXN0cyA9IGxpbW1hQ29udHJhc3RzLCBsZXZlbHMgPSBkZXNpZ24pICMjbWFrZSBhIG5ldyBkZXNpZ24gbWF0cml4IHdpdGggb3VyIGNvbnRyYXN0cw0KbGltbWFGaXQyID0gZUJheWVzKGNvbnRyYXN0cy5maXQobGltbWFGaXQxLCBjb250cmFzdHMgPSBsaW1tYUNvbnRyYXN0RGVzaWduKSkgIyNwZXJmb3JtIGVCYXllcyBzbW9vdGhpbmcgdXNpbmcgbGltbWENCmBgYA0KDQo8ZGl2IHN0eWxlPSJtYXJnaW4tYm90dG9tOjUwcHg7Ij48L2Rpdj4NCg0KTGFzdGx5LCB3ZSB3aWxsIHVzZSBERXFNUyB0byBzdW1tYXJpemUgdGhlIGRhdGEgYW5kIGV4dHJhY3Qgb3V0IHRoZSBkaWZmZXJlbnQgY29udHJhc3RzIHRvIHNhdmUgdGhlbSB0byBhIGZpbGUgZm9yIGVhY2guDQoNCmBgYHtyfQ0KIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjDQpwc21Db3VudFRhYmxlID0gYXMuZGF0YS5mcmFtZSh0YWJsZShxdWFudElucHV0JGdlbmUpKSAjI2NvdW50IHRoZSBudW1iZXIgb2YgdGltZXMgZWFjaCBnZW5lIGlzIGFwcGVhcmluZw0Kcm93bmFtZXMocHNtQ291bnRUYWJsZSkgPSBwc21Db3VudFRhYmxlJFZhcjEgIyNhc3NpZ24gZ2VuZXMgYXMgdGhlIHJvdyBuYW1lcw0KbGltbWFGaXQyJGNvdW50ID0gcHNtQ291bnRUYWJsZVtyb3duYW1lcyhsaW1tYUZpdDIkY29lZmZpY2llbnRzKSwyXSAjI2FkZCB0aGUgY291bnRzIHRvIHRoZSBsaW1tYSBtb2RlbA0KbGltbWFGaXQzID0gc3BlY3RyYUNvdW50ZUJheWVzKGxpbW1hRml0MikgIyNkbyB0aGUgZmluYWwgcHJvY2Vzc2luZyB3aXRoIGRlcW1zDQpoZWFkKGxpbW1hRml0MyRjb2VmZmljaWVudHMpICMjdGFrZSBhIGxvb2sgYXQgdGhlIGZpbmFsIGRhdGENCiMjc2F2ZSB0aGUgaW5kaXZpZHVhbCBjb250cmFzdHMNCmRlcW1zUmVzdWx0cyA9IG91dHB1dFJlc3VsdChsaW1tYUZpdDMsIGNvZWZfY29sID0gMSkgDQp3cml0ZS50YWJsZShkZXFtc1Jlc3VsdHMsIA0KICAgICAgICAgICAgcGFzdGUoYmFzZVJlcG9zaXRvcnksICcvZGF0YXNldF9kZXFtc0EtQi5jc3YnLCBzZXAgPSAnJyksDQogICAgICAgICAgICBzZXAgPSAnLCcsIHJvdy5uYW1lcyA9IEZBTFNFLCBxdW90ZSA9IEZBTFNFKQ0KIyMNCmRlcW1zUmVzdWx0cyA9IG91dHB1dFJlc3VsdChsaW1tYUZpdDMsIGNvZWZfY29sID0gMikgDQp3cml0ZS50YWJsZShkZXFtc1Jlc3VsdHMsIA0KICAgICAgICAgICAgcGFzdGUoYmFzZVJlcG9zaXRvcnksICcvZGF0YXNldF9kZXFtc0EtQy5jc3YnLCBzZXAgPSAnJyksDQogICAgICAgICAgICBzZXAgPSAnLCcsIHJvdy5uYW1lcyA9IEZBTFNFLCBxdW90ZSA9IEZBTFNFKQ0KIyMNCmRlcW1zUmVzdWx0cyA9IG91dHB1dFJlc3VsdChsaW1tYUZpdDMsIGNvZWZfY29sID0gMykgDQp3cml0ZS50YWJsZShkZXFtc1Jlc3VsdHMsIA0KICAgICAgICAgICAgcGFzdGUoYmFzZVJlcG9zaXRvcnksICcvZGF0YXNldF9kZXFtc0ItQy5jc3YnLCBzZXAgPSAnJyksDQogICAgICAgICAgICBzZXAgPSAnLCcsIHJvdy5uYW1lcyA9IEZBTFNFLCBxdW90ZSA9IEZBTFNFKQ0KYGBgDQoNCjxkaXYgc3R5bGU9Im1hcmdpbi1ib3R0b206NTBweDsiPjwvZGl2Pg0KDQpHZW5lcmF0ZSB0aGUgdmFyaWFuY2UgcGxvdCB0byBzZWUgdGhlIERFcU1TIG1vZGVsIGZvciB0aGUgZGF0YS4NCg0KYGBge3J9DQojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMNClZhcmlhbmNlQm94cGxvdChsaW1tYUZpdDMsIG49MjAsIHhsYWIgPSAnUFNNIGNvdW50JywgbWFpbiA9ICdERXFNUyBhbmFseXNpcyBvZiBUTVQgZGF0YScpDQpgYGANCg0KPGRpdiBzdHlsZT0ibWFyZ2luLWJvdHRvbTo1MHB4OyI+PC9kaXY+DQoNClNvIHdlIGNhbiBzZWUgdmFyaWFuY2UgaW5jcmVhc2luZyBhcyB0aGUgUFNNIGNvdW50IGdldHMgbG93ZXIsIHdoaWNoIGlzIHdoYXQgd2UgZXhwZWN0LiBOb3cgbGV0IHVzIG1ha2UgYSB2b2xjYW5vIHBsb3QgdG8gc2hvdyB0aGUgZGF0YSwgYXMgYW4gZXhhbXBsZS4NCg0KYGBge3J9DQojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMNCmRlcW1zUmVzdWx0cyA9IG91dHB1dFJlc3VsdChsaW1tYUZpdDMsIGNvZWZfY29sID0gMSkNCmRlcW1zUmVzdWx0cyRwbG90UFZhbHVlID0gLWxvZzEwKGRlcW1zUmVzdWx0cyRzY2EuUC5WYWx1ZSkNCmRlcW1zUmVzdWx0cyRwQ29sb3JzID0gaWZlbHNlKGRlcW1zUmVzdWx0cyRsb2dGQyA8PSAtMSAmIGRlcW1zUmVzdWx0cyRwbG90UFZhbHVlID49IDMsIGJyZXdlci5wYWwoOCwnUmRCdScpWzhdLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKGRlcW1zUmVzdWx0cyRsb2dGQyA+PSAxICYgZGVxbXNSZXN1bHRzJHBsb3RQVmFsdWUgPj0gMywgYnJld2VyLnBhbCg4LCdSZEJ1JylbMV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnJld2VyLnBhbCg4LCdHcmV5cycpWzZdKSkNCiMjDQpnZ3Bsb3QoZGVxbXNSZXN1bHRzLCBhZXMobG9nRkMsIHBsb3RQVmFsdWUpKSArDQogIGdlb21fcG9pbnQoc2l6ZSA9IDEsIGNvbG9yID0gZGVxbXNSZXN1bHRzJHBDb2xvcnMsIGFscGhhID0gMC41KSArDQogIGxhYnMoeCA9ICdsb2cyKEEgZm9sZCBjaGFuZ2UgdG8gQiknLCB5ID0gJy1sb2cxMChQLXZhbHVlKScsIHRpdGxlID0gJ1NhbXBsZSBBIHZlcnN1cyBCJykgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMobGltaXRzID0gYygwLDEyKSwgYnJlYWtzID0gc2VxKDAsMTgsMykpICsNCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gYygtMSwxKSwgbGluZXR5cGUgPSAnZGFzaGVkJykgKw0KICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAzLCBsaW5ldHlwZSA9ICdkYXNoZWQnKSArDQogIHRoZW1lX2NsYXNzaWMoKQ0KZ2dzYXZlKHBhc3RlKGJhc2VSZXBvc2l0b3J5LCAnL3NjYXR0ZXJfZGVxbXNBLUIucGRmJywgc2VwID0gJycpLA0KICAgICAgIGhlaWdodCA9IDQsIHdpZHRoID0gNCwgdXNlRGluZ2JhdHMgPSBGQUxTRSkNCmBgYA0KDQpTbyBxdWl0ZSBhIGJpdCBoYXMgY2hhbmdlZCBoZXJlLiBJZiB5b3Ugd2FudCB0byBwbG90IG90aGVyIGNvbnRyYXN0cywgc2ltcGx5IGNoYW5nZSB0aGUgJ2NvZWZfY29sJyB2YWx1ZSBpbiB0aGUgZmlyc3QgY29tbWFuZCBpbiB0aGUgY29kZSBhYm92ZSB0byBhIGRpZmZlcmVudCB2YWx1ZS4gWW91IGNhbiBub3cgaW52ZXN0aWdhdGUgdGhlc2UgZGF0YSBmdXJ0aGVyIHRvIGRldGVybWluZSBiaW9sb2dpY2FsIHZhcmlhbmNlIHNlZW4gaW4geW91ciBvd24gZGF0YSENCg0KDQojIyBTZXNzaW9uIGluZm8NCg0KYGBge3J9DQpzZXNzaW9uSW5mbygpDQpgYGA=