Setup knitr and load utility functions

knitr::opts_chunk$set(echo = TRUE)
knitr::opts_knit$set(root.dir="E:/DISC/reproducibility")
utilities_path = "./source/utilities.r"
source(utilities_path)
Registered S3 method overwritten by 'dplyr':
  method           from
  print.rowwise_df     
Registered S3 methods overwritten by 'htmltools':
  method               from         
  print.html           tools:rstudio
  print.shiny.tag      tools:rstudio
  print.shiny.tag.list tools:rstudio
Registered S3 method overwritten by 'htmlwidgets':
  method           from         
  print.htmlwidget tools:rstudio
Registered S3 method overwritten by 'data.table':
  method           from
  print.data.table     
reldist: Relative Distribution Methods
Version 1.6-6 created on 2016-10-07.
copyright (c) 2003, Mark S. Handcock, University of California-Los Angeles
 For citation information, type citation("reldist").
 Type help(package="reldist") to get started.

Load Data

Here, gene expression matrixes of RNA-seq and FISH experiments will be loaded.
We removed cells as SAVER.
We removed genes as our methods.
Only the overlapped gene in both filtered RNA-seq and FISH matrixes will be used here.

dataset_list = list()
dataset_list[["FISH"]] = readh5_loom("./data/MELANOMA/fish.loom")
[1] "./data/MELANOMA/fish.loom"
[1]    26 88040
raw_data = readh5_loom("./data/MELANOMA/raw.loom")
[1] "./data/MELANOMA/raw.loom"
[1] 32287  8498
gene_filter = gene_selection(raw_data, 10)
raw_input_data = raw_data[gene_filter, ]
use_genes = intersect(rownames(dataset_list[["FISH"]]), rownames(raw_input_data))
use_cell = colnames(dataset_list[["Raw"]])

We use these 19 overlapped genes for analysis below.

print(length(use_genes))
[1] 19
print(use_genes)
 [1] "EGFR"   "SOX10"  "CCNA2"  "GAPDH"  "WNT5A"  "PDGFC"  "FOSL1"  "MITF"   "RUNX2"  "FGFR1"  "JUN"    "VGF"    "BABAM1" "KDM5A" 
[15] "LMNA"   "KDM5B"  "C1S"    "VCL"    "TXNRD1"
dataset_list[["Raw"]] = raw_input_data[use_genes, ]
print(dim(raw_input_data))
[1] 15204  8498
print(dim(dataset_list[["Raw"]]))
[1]   19 8498
use_cell = colnames(dataset_list[["Raw"]])

After Run imputation, we get imputation results.
Now we load them.
Note that SAVER needs to generate gene expression values following gamma distribution (FF, Gini CMD) or poisson–gamma mixture (density distribution). For this reason, we load its rds-formatted file here.

### DISC
dataset_list[["DISC"]] = readh5_loom("./data/MELANOMA/DISC.loom", use_genes)
[1] "./data/MELANOMA/DISC.loom"
[1]   19 8498
### Other methods
#dataset_list[["SAVER"]] = readRDS("./data/MELANOMA/SAVER.rds")
#set.seed(42)
#dataset_list[["SAVER_gamma"]] = gamma_result(dataset_list[["SAVER"]], num_of_obs=1)[use_genes, use_cell]
dataset_list[["scVI"]] = readh5_imputation("./data/MELANOMA/scVI.hdf5", use_genes, use_cell)
[1] "./data/MELANOMA/scVI.hdf5"
[1]   19 8498
dataset_list[["MAGIC"]] = readh5_imputation("./data/MELANOMA/MAGIC.hdf5", use_genes, use_cell)
[1] "./data/MELANOMA/MAGIC.hdf5"
[1]   19 8498
dataset_list[["DCA"]] = readh5_imputation("./data/MELANOMA/DCA.hdf5", use_genes, use_cell)
[1] "./data/MELANOMA/DCA.hdf5"
[1]   19 8498
dataset_list[["scScope"]] = readh5_imputation("./data/MELANOMA/scScope.hdf5", use_genes, use_cell)
[1] "./data/MELANOMA/scScope.hdf5"
[1]   19 8498
dataset_list[["DeepImpute"]] = readh5_imputation("./data/MELANOMA/DeepImpute.hdf5", use_genes)
[1] "./data/MELANOMA/DeepImpute.hdf5"
[1]   19 8498
dataset_list[["VIPER"]] = readh5_imputation("./data/MELANOMA/VIPER.hdf5", use_genes, use_cell)
[1] "./data/MELANOMA/VIPER.hdf5"
[1]   19 8498
dataset_list[["scImpute"]] = readh5_imputation("./data/MELANOMA/scImpute.hdf5", use_genes, use_cell)
[1] "./data/MELANOMA/scImpute.hdf5"
[1]   19 8498

Output settings

The output color for each method and the output directory will be setup here.

method_name = c("Raw", "DISC", "scVI", "MAGIC", "DCA", "scScope", "DeepImpute", "VIPER", "scImpute")
method_color = c("#A5A5A5", "#E83828", "#278BC4", "#EADE36", "#198B41", "#920783", "#F8B62D", "#8E5E32", "#1E2B68")
names(method_color) = method_name
bar_color = rep("gray50", length(method_name))
names(bar_color) = method_name
bar_color["Raw"] = "gray80"
bar_color["DISC"] = "red"
text_color = rep("black", length(method_name))
names(text_color) = method_name
text_color["DISC"] = "red"
### make output dir
outdir = "./results/MELANOMA/structure_recovery"
dir.create(outdir, showWarnings = F, recursive = T)

Evaluation

Calculate correlation with FISH

### calculate correlation of FISH
cor_mat = matrix(ncol = 3, nrow = 0, dimnames = list(c(), c("Gene x", "Gene y", "FISH Correlation")))
cor_all = list()
for(ii in c("FISH", method_name)){
  cor_all[[ii]] = matrix(nrow = length(use_genes), ncol = length(use_genes), dimnames = list(use_genes, use_genes))
}
for(ii in use_genes){
  for(jj in use_genes){
    #cat(ii, "\t", jj, "\n")
    for(kk in c("FISH", method_name)){
      if(kk == "SAVER"){
        cor_all[["SAVER"]][ii, jj] = cor(dataset_list[["SAVER_gamma"]][ii, ], dataset_list[["SAVER_gamma"]][jj, ], use = "pairwise.complete.obs")
      }else{
        cor_all[[kk]][ii, jj] = cor(dataset_list[[kk]][ii, ], dataset_list[[kk]][jj, ], use = "pairwise.complete.obs")
      }
    }
  }
}

correlation map

fish_mask_mat = !is.na(cor_all[["FISH"]])
for(ii in 1:length(use_genes)){
  fish_mask_mat[ii, seq(ii)] = FALSE
}
fish_mask = as.vector(fish_mask_mat)
fish_gene_mat = apply(fish_mask_mat, 1, names)
outfile = paste0(outdir, "/correlation_map.pdf")
mfrow = make_mfrow(2, length(method_name))
#pdf(outfile, height = mfrow[1] * 3.75, width = mfrow[2] * 3.25)
layout_scatter(cor_all, method_name, fish_mask, this_xlab = "FISH", this_ylab = "scRNA-seq", xlim = c(-0.2, 1), ylim = c(-0.2, 1), point_size = 2.75)
       Raw       DISC       scVI      MAGIC        DCA    scScope DeepImpute      VIPER   scImpute 
 0.2067813  0.1933942  0.3461712  0.7846847  0.7060096  0.2628344  0.2116670  0.2023288  0.2090803 

#dev.off()

Correlation heatmap

#  heatmap & CMD
outfile = paste0(outdir, "/correlation_heatmap.pdf")
use_order = order(colMeans(matrix(apply(cor_all[["FISH"]], 2, function(x){
  x[is.na(x)] = 0
  return(x)
})[t(t(fish_gene_mat) != colnames(fish_gene_mat))], nrow = nrow(cor_all[["FISH"]]) - 1, ncol = ncol(cor_all[["FISH"]]), dimnames = list(c(), colnames(cor_all[["FISH"]])))), decreasing = T)
#pdf(outfile, height = 7, width = 12.5)
cmd_vector = layout_correlogram_plot(cor_all, use_order=use_order)

#dev.off()
names(cmd_vector) = names(cor_all)
cmd_vector = cmd_vector[setdiff(names(cmd_vector), "FISH")]

CMD

barplot_usage(cmd_vector, main = "CMD", bar_color = method_color, text_color = text_color, use_data_order = T, use_border = F)

saveRDS(cor_all, paste0(outdir, "/cor_all.rds"))

Normalization

example_gene = c("WNT5A", "SOX10")
rescale_mean_list = list()
for(ii in method_name){
  if(ii != "SAVER"){
    rescale_mean_list[[ii]] = mean_norm_fun(dataset_list[[ii]], dataset_list[["FISH"]])
  }else{
    rescale_mean_list[[ii]] = mean_norm_fun(dataset_list[["SAVER_gamma"]], dataset_list[["FISH"]])
  }
}
max_points = ncol(dataset_list[["FISH"]])
for(ii in rescale_mean_list){
  max_points = max(c(max_points, ncol(ii)))
}
saver_style_filt_norm_list = rescale_mean_list
#saver_style_filt_norm_list[["SAVER"]] = binom_result_mean(dataset_list[["SAVER"]], dataset_list[["FISH"]])
saver_style_filt_norm_list[["FISH"]] = dataset_list[["FISH"]]
norm_for_density = saver_style_filt_norm_list
for(ii in names(norm_for_density)){
  norm_for_density[[ii]] = norm_for_density[[ii]][example_gene,]
}
saveRDS(norm_for_density, paste0(outdir, "/norm_for_density.rds"))

Gini

gini_result_list = list()
for(ii in names(saver_style_filt_norm_list)){
  if(ii != "SAVER"){
    gini_result_list[[ii]] = apply(dataset_list[[ii]], 1, function(x){gini(x[complete.cases(x)])})
  }else{
    gini_result_list[[ii]] = apply(dataset_list[["SAVER_gamma"]], 1, function(x){gini(x[complete.cases(x)])})
  }
}
color_point = c("#31a354", "#a63603")
names(color_point) = example_gene
outfile = paste0(outdir, "/Gini.pdf")
mfrow = make_mfrow(2, length(method_name))
#pdf(outfile, height = mfrow[1] * 3.75, width = mfrow[2] * 3.25)
gini_rmse = layout_scatter(gini_result_list, method_name, use_genes, color_point = color_point, this_xlab = "FISH Gini", this_ylab = "scRNA-seq Gini", xlim = c(0, 1), ylim = c(0, 1))

#dev.off()
saveRDS(gini_result_list, paste0(outdir, "/gini_result_list.rds"))
par(mfrow = c(1, 1))
barplot_usage(gini_rmse, main = "Gini RMSE", bar_color = method_color, text_color = text_color, use_data_order = T, use_border = F)

Density

plot_genes = use_genes
ks_matrix = matrix(nrow = length(plot_genes), ncol = length(method_name), dimnames = list(plot_genes, method_name))
mfrow = c(3, 6)
outfile = paste0(outdir, "/density.pdf")
#pdf(outfile, height = mfrow[1] * 3, width = mfrow[2] * 2.75)
par(mfrow = mfrow)
for(ii in plot_genes){
  this_fish = saver_style_filt_norm_list[["FISH"]][ii, ]
  this_fish = this_fish[!is.na(this_fish)]
  fish_density = density(this_fish)
  zero_proportion = round(100 * (1 - sum(dataset_list[["Raw"]][ii, ] > 0) / length(use_cell)), digits = 4)
  xlim_max = as.numeric(quantile(this_fish, 0.90)) * 2
  dens.bw = fish_density$bw
  ylim_max = max(fish_density$y)
  use_density = list()
  for(method_index in 1:length(method_name)){
    this_method_expression = saver_style_filt_norm_list[[method_name[method_index]]][ii, ]
    if(length(unique(this_method_expression)) == 1){
      this_method_expression[1] = this_method_expression[1] * 1.0001
    }
    #if(method_index <= 2){
    if(T){
      this_density = density(this_method_expression, bw = dens.bw)
      use_density[[method_name[method_index]]] = this_density
      ylim_max = max(c(ylim_max, this_density$y))
    }
    ks_matrix[ii, method_index] = ks.test(delete_lt0.5(this_method_expression), delete_lt0.5(this_fish))$statistic
  }
  plot(fish_density, lwd = 2, col = "black", lty = 1,
       xlim = c(min(fish_density$x, 0), xlim_max + 5),
       ylim = c(0, ylim_max), yaxt = "s", bty="n",
       main = paste0(toupper(ii), " (", zero_proportion, "%)"),
       sub = "", ylab = "Density", xlab = "mRNA Counts")
  par(las = 0)
  for(this_density_name in names(use_density)){
    lines(use_density[[this_density_name]], lwd = 3, col=method_color[this_density_name])
  }
  if(ii %in% plot_genes[mfrow[2] + (mfrow[1] * mfrow[2] * seq(0, floor(length(plot_genes) / mfrow[1] * mfrow[2])))]){
    legend("topright", c("FISH", names(use_density)), lty = rep(1, 1 + length(names(use_density))),
           lwd = rep(3, 1 + length(names(use_density))), col = c("black", method_color[names(use_density)]), box.lty = 0, xjust = 1, yjust = 1)
  }
}

#dev.off()
outfile = paste0(outdir, "/density_summary.pdf")
#pdf(outfile, height = 6, width = ncol(ks_matrix) * 0.6)
ks_mean = Matrix::colMeans(ks_matrix)
standard_error_ks = apply(ks_matrix, 2, function(x) sqrt(var(x)/length(x)))
par(mfrow = c(1, 1))
barplot_usage(ks_mean, main = "K-S Statistic", bar_color = method_color, text_color = text_color, use_data_order = T, standard_error = standard_error_ks, use_border = F)

#dev.off()

2D distribution

# Make the plots
dist_outdir = paste0(outdir, "/2d_distribution")
dir.create(dist_outdir, showWarnings = F)
mfrow = make_mfrow(2, length(c("FISH", method_name)))
pairs_2d_distribution = cor_mat[order(abs(cor_mat[, 3]), decreasing = TRUE), ]
library(parallel)
no_cores <- max(c(detectCores() - 1, 1))
cl <- makeCluster(no_cores)
clusterExport(cl, varlist = c("dist_outdir", "rescale_mean_list", "method_name", "use_cell", "mfrow", "fish_gene_mat", "fish_mask_mat", "dataset_list", "utilities_path"))
return_list = parLapply(cl, 1:sum(fish_mask), function(ii){
  source(utilities_path)
  gene_x = fish_gene_mat[t(fish_mask_mat)][ii]
  gene_y = t(fish_gene_mat)[t(fish_mask_mat)][ii]
  x_dropout_rate = round(100 * (1 - sum(dataset_list[["Raw"]][gene_x, ] > 0) / length(use_cell)), digits = 4)
  y_dropout_rate = round(100 * (1 - sum(dataset_list[["Raw"]][gene_y, ] > 0) / length(use_cell)), digits = 4)
  x_fish_raw = dataset_list[["FISH"]][gene_x, ]
  y_fish_raw = dataset_list[["FISH"]][gene_y, ]
  select_cell = !is.na(x_fish_raw) & !is.na(y_fish_raw)
  x_fish = x_fish_raw[select_cell]
  y_fish = y_fish_raw[select_cell]
  fish_pair_mat = matrix(c(x_fish, y_fish), ncol = 2)
  ks_stat = c()
  corr_score = cor(x_fish, y_fish)
  x_i = list(FISH = x_fish)
  y_i = list(FISH = y_fish)
  for(jj in 1:length(method_name)){
    this_method_name = method_name[jj]
    x_i[[this_method_name]] = rescale_mean_list[[this_method_name]][gene_x, ]
    y_i[[this_method_name]] = rescale_mean_list[[this_method_name]][gene_y, ]
    ks_stat = c(ks_stat, ks2d2s(round(x_fish), round(y_fish), round(x_i[[this_method_name]]), round(y_i[[this_method_name]])))
    corr_score = c(corr_score, cor(x_i[[this_method_name]], y_i[[this_method_name]]))
  }
  names(corr_score) = c("FISH", method_name)
  names(ks_stat) = method_name
  this_name = c(paste(gene_x, gene_y, sep = "_"))
  pdf(paste0(dist_outdir, "/", this_name, ".pdf"),
      height = mfrow[1] * 4,
      width = mfrow[2] * 3.75)
  par(mfrow = mfrow)
  nbin = 128
  x_fish_95 = quantile(x_fish, 0.95) + 1### R is from 1 to max + 1
  y_fish_95 = quantile(y_fish, 0.95) + 1
  for(jj in c("FISH", method_name)){
    if(jj == "DISC"){
      col.main = "red"
    }else{
      col.main = "black"
    }
    if(jj == "Raw"){
      x_use = dataset_list[[jj]][gene_x, ]
      y_use = dataset_list[[jj]][gene_y, ]
      xlim = c(0, max(x_use))
      ylim = c(0, max(y_use))
      bandwidth = c(xlim[2] / nbin, ylim[2] / nbin)
    }else{
      x_use = x_i[[jj]]
      y_use = y_i[[jj]]
      xlim = c(0, x_fish_95)
      ylim = c(0, y_fish_95)
      bandwidth = c(max(x_fish) / nbin, max(y_fish) / nbin)
    }
    smoothScatter1(x = x_use, y = y_use,
                   xlab = paste0(gene_x, " (", x_dropout_rate, "%)"),
                   ylab = paste0(gene_y, " (", y_dropout_rate, "%)"),
                   cex = 1.5, xlim = xlim, ylim = ylim,
                   lwd = 2, main = paste0(jj, " - FF = ", round(ks_stat[jj], 4)),
                   nrpoints = 0, col.main = col.main, nbin = nbin, bandwidth = bandwidth)
  }
  dev.off()
  return(list("ks_stat" = matrix(ks_stat, nrow = 1, dimnames = list(paste(gene_x, gene_y, sep = " - "), c())),
              "corr_score" = matrix(corr_score, nrow = 1, dimnames = list(paste(gene_x, gene_y, sep = " - "), c()))))
})
stopCluster(cl)
ks_stat_mat = matrix(nrow = 0, ncol = length(method_name), dimnames = list(c(), method_name))
corr_mat = matrix(nrow = 0, ncol = length(method_name) + 1, dimnames = list(c(), c("FISH", method_name)))

for(ii in return_list){
  ks_stat_mat = rbind(ks_stat_mat, ii$ks_stat)
  corr_mat = rbind(corr_mat, ii$corr_score)
}
saveRDS(ks_stat_mat, paste(outdir, "/ks_stat_mat.rds", sep = ""))
print(paste0("Please see ", dist_outdir, " for all results."))
[1] "Please see ./results/MELANOMA/structure_recovery/2d_distribution for all results."
###all_compare
mean_ks_stat = Matrix::colMeans(ks_stat_mat)
standard_error_ks_stat = apply(ks_stat_mat, 2, function(x) sqrt(var(x)/length(x)))
outfile = paste(outdir, "/score_compare.pdf", sep = "")
#pdf(outfile, height = 5, width = 11)
par(mfrow = c(1, 2))
barplot_usage(mean_ks_stat, main = "Fasano and Franceschini's Test", cex.main = 1.5,bar_color = method_color, text_color = text_color, use_data_order = T, standard_error = standard_error_ks_stat, use_border = F)
corr_rmse = sapply(method_name, function(x) rmse(corr_mat[, "FISH"], corr_mat[, x]))
barplot_usage(corr_rmse, main = "FISH - Impute Correlation RMSE", cex.main = 1.5, bar_color = method_color, text_color = text_color, use_data_order = T, use_border = F)

#dev.off()

Summary

outfile = paste(outdir, "/Bar_plot.pdf", sep = "")
#pdf(outfile, height = 3, width = 9.5)
plot_height = 4
plot_width = 3.5
plot_region = matrix(seq(3), nrow = 1)
this_height = rep(plot_height, nrow(plot_region))
this_width = rep(plot_width, ncol(plot_region))
this_index = max(plot_region) + 1
layout_mat = plot_region
xlab_region = matrix(rep(this_index, ncol(plot_region)), nrow = 1)
layout_mat = rbind(layout_mat, xlab_region)
this_height = c(this_height, 0.5)
layout(mat = layout_mat, heights = this_height, widths = this_width)
par(mar = c(1, 4.1, 4.1, 2.1))
barplot_usage(cmd_vector, main = "CMD", bar_color = method_color, use_data_order = T, use_border = F)
barplot_usage(gini_rmse, main = "Gini RMSE", bar_color = method_color, use_data_order = T, use_border = F)
barplot_usage(mean_ks_stat, main = "Fasano and\nFranceschini's Test", cex.main = 1.5, bar_color = method_color, use_data_order = T, standard_error = standard_error_ks_stat, use_border = F)
par(mar = rep(0, 4))
plot(1, type = "n", axes = FALSE, xlab="", ylab="")
legend(x = "top",inset = 0, legend = names(method_color), fill = method_color, horiz = TRUE, border = NA, bty = "n")

#dev.off()
output_list = list()
output_list[["cmd"]] = cmd_vector
output_list[["gini_rmse"]] = gini_rmse
output_list[["mean_ks_stat"]] = mean_ks_stat
output_list[["standard_error_ks_stat"]] = standard_error_ks_stat
saveRDS(output_list, paste(outdir, "/Bar_stat.rds", sep = ""))

Notes:

Single plots of FF are saved in here.
ALL data we used in this script can be found here.

LS0tDQp0aXRsZTogIkdlbmUgZXhwcmVzc2lvbiBzdHJ1Y3R1cmVzIHJlY292ZXJ5IHZhbGlkYXRlZCBieSBGSVNIIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCiMjIyBTZXR1cCBrbml0ciBhbmQgbG9hZCB1dGlsaXR5IGZ1bmN0aW9ucw0KYGBge3Igc2V0dXB9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpDQprbml0cjo6b3B0c19rbml0JHNldChyb290LmRpcj0iRTovRElTQy9yZXByb2R1Y2liaWxpdHkiKQ0KYGBgDQpgYGB7cn0NCnV0aWxpdGllc19wYXRoID0gIi4vc291cmNlL3V0aWxpdGllcy5yIg0Kc291cmNlKHV0aWxpdGllc19wYXRoKQ0KYGBgDQojIyMgTG9hZCBEYXRhDQpIZXJlLCBnZW5lIGV4cHJlc3Npb24gbWF0cml4ZXMgb2YgUk5BLXNlcSBhbmQgRklTSCBleHBlcmltZW50cyB3aWxsIGJlIGxvYWRlZC48L2JyPg0KV2UgcmVtb3ZlZCBjZWxscyBhcyA8YSBocmVmPSJodHRwczovL3d3dy5uYXR1cmUuY29tL2FydGljbGVzL3M0MTU5Mi0wMTgtMDAzMy16Ij5TQVZFUjwvYT4uPC9icj4NCldlIHJlbW92ZWQgZ2VuZXMgYXMgb3VyIG1ldGhvZHMuPC9icj4NCk9ubHkgdGhlIG92ZXJsYXBwZWQgZ2VuZSBpbiBib3RoIGZpbHRlcmVkIFJOQS1zZXEgYW5kIEZJU0ggbWF0cml4ZXMgd2lsbCBiZSB1c2VkIGhlcmUuDQpgYGB7cn0NCmRhdGFzZXRfbGlzdCA9IGxpc3QoKQ0KZGF0YXNldF9saXN0W1siRklTSCJdXSA9IHJlYWRoNV9sb29tKCIuL2RhdGEvTUVMQU5PTUEvZmlzaC5sb29tIikNCnJhd19kYXRhID0gcmVhZGg1X2xvb20oIi4vZGF0YS9NRUxBTk9NQS9yYXcubG9vbSIpDQpnZW5lX2ZpbHRlciA9IGdlbmVfc2VsZWN0aW9uKHJhd19kYXRhLCAxMCkNCnJhd19pbnB1dF9kYXRhID0gcmF3X2RhdGFbZ2VuZV9maWx0ZXIsIF0NCnVzZV9nZW5lcyA9IGludGVyc2VjdChyb3duYW1lcyhkYXRhc2V0X2xpc3RbWyJGSVNIIl1dKSwgcm93bmFtZXMocmF3X2lucHV0X2RhdGEpKQ0KdXNlX2NlbGwgPSBjb2xuYW1lcyhkYXRhc2V0X2xpc3RbWyJSYXciXV0pDQpgYGANCldlIHVzZSB0aGVzZSAxOSBvdmVybGFwcGVkIGdlbmVzIGZvciBhbmFseXNpcyBiZWxvdy4NCmBgYHtyfQ0KcHJpbnQobGVuZ3RoKHVzZV9nZW5lcykpDQpwcmludCh1c2VfZ2VuZXMpDQpkYXRhc2V0X2xpc3RbWyJSYXciXV0gPSByYXdfaW5wdXRfZGF0YVt1c2VfZ2VuZXMsIF0NCnByaW50KGRpbShyYXdfaW5wdXRfZGF0YSkpDQpwcmludChkaW0oZGF0YXNldF9saXN0W1siUmF3Il1dKSkNCnVzZV9jZWxsID0gY29sbmFtZXMoZGF0YXNldF9saXN0W1siUmF3Il1dKQ0KYGBgDQpBZnRlciA8YSBocmVmPSJodHRwczovL2dpdGh1Yi5jb20vaXloYW9vL0RJU0MvYmxvYi9tYXN0ZXIvcmVwcm9kdWNpYmlsaXR5L3R1dG9yaWFscy9ydW5faW1wdXRhdGlvbi5tZCI+UnVuIGltcHV0YXRpb248L2E+LCB3ZSBnZXQgaW1wdXRhdGlvbiByZXN1bHRzLjwvYnI+DQpOb3cgd2UgbG9hZCB0aGVtLjwvYnI+DQpOb3RlIHRoYXQgU0FWRVIgbmVlZHMgdG8gZ2VuZXJhdGUgZ2VuZSBleHByZXNzaW9uIHZhbHVlcyBmb2xsb3dpbmcgZ2FtbWEgZGlzdHJpYnV0aW9uIChGRiwgR2luaSBDTUQpIG9yIHBvaXNzb27igJNnYW1tYSBtaXh0dXJlIChkZW5zaXR5IGRpc3RyaWJ1dGlvbikuIEZvciB0aGlzIHJlYXNvbiwgd2UgbG9hZCBpdHMgcmRzLWZvcm1hdHRlZCBmaWxlIGhlcmUuDQpgYGB7cn0NCiMjIyBESVNDDQpkYXRhc2V0X2xpc3RbWyJESVNDIl1dID0gcmVhZGg1X2xvb20oIi4vZGF0YS9NRUxBTk9NQS9ESVNDLmxvb20iLCB1c2VfZ2VuZXMpDQojIyMgT3RoZXIgbWV0aG9kcw0KI2RhdGFzZXRfbGlzdFtbIlNBVkVSIl1dID0gcmVhZFJEUygiLi9kYXRhL01FTEFOT01BL1NBVkVSLnJkcyIpDQojc2V0LnNlZWQoNDIpDQojZGF0YXNldF9saXN0W1siU0FWRVJfZ2FtbWEiXV0gPSBnYW1tYV9yZXN1bHQoZGF0YXNldF9saXN0W1siU0FWRVIiXV0sIG51bV9vZl9vYnM9MSlbdXNlX2dlbmVzLCB1c2VfY2VsbF0NCmRhdGFzZXRfbGlzdFtbInNjVkkiXV0gPSByZWFkaDVfaW1wdXRhdGlvbigiLi9kYXRhL01FTEFOT01BL3NjVkkuaGRmNSIsIHVzZV9nZW5lcywgdXNlX2NlbGwpDQpkYXRhc2V0X2xpc3RbWyJNQUdJQyJdXSA9IHJlYWRoNV9pbXB1dGF0aW9uKCIuL2RhdGEvTUVMQU5PTUEvTUFHSUMuaGRmNSIsIHVzZV9nZW5lcywgdXNlX2NlbGwpDQpkYXRhc2V0X2xpc3RbWyJEQ0EiXV0gPSByZWFkaDVfaW1wdXRhdGlvbigiLi9kYXRhL01FTEFOT01BL0RDQS5oZGY1IiwgdXNlX2dlbmVzLCB1c2VfY2VsbCkNCmRhdGFzZXRfbGlzdFtbInNjU2NvcGUiXV0gPSByZWFkaDVfaW1wdXRhdGlvbigiLi9kYXRhL01FTEFOT01BL3NjU2NvcGUuaGRmNSIsIHVzZV9nZW5lcywgdXNlX2NlbGwpDQpkYXRhc2V0X2xpc3RbWyJEZWVwSW1wdXRlIl1dID0gcmVhZGg1X2ltcHV0YXRpb24oIi4vZGF0YS9NRUxBTk9NQS9EZWVwSW1wdXRlLmhkZjUiLCB1c2VfZ2VuZXMpDQpkYXRhc2V0X2xpc3RbWyJWSVBFUiJdXSA9IHJlYWRoNV9pbXB1dGF0aW9uKCIuL2RhdGEvTUVMQU5PTUEvVklQRVIuaGRmNSIsIHVzZV9nZW5lcywgdXNlX2NlbGwpDQpkYXRhc2V0X2xpc3RbWyJzY0ltcHV0ZSJdXSA9IHJlYWRoNV9pbXB1dGF0aW9uKCIuL2RhdGEvTUVMQU5PTUEvc2NJbXB1dGUuaGRmNSIsIHVzZV9nZW5lcywgdXNlX2NlbGwpDQpgYGANCiMjIyBPdXRwdXQgc2V0dGluZ3MNClRoZSBvdXRwdXQgY29sb3IgZm9yIGVhY2ggbWV0aG9kIGFuZCB0aGUgb3V0cHV0IGRpcmVjdG9yeSB3aWxsIGJlIHNldHVwIGhlcmUuDQpgYGB7cn0NCm1ldGhvZF9uYW1lID0gYygiUmF3IiwgIkRJU0MiLCAic2NWSSIsICJNQUdJQyIsICJEQ0EiLCAic2NTY29wZSIsICJEZWVwSW1wdXRlIiwgIlZJUEVSIiwgInNjSW1wdXRlIikNCm1ldGhvZF9jb2xvciA9IGMoIiNBNUE1QTUiLCAiI0U4MzgyOCIsICIjMjc4QkM0IiwgIiNFQURFMzYiLCAiIzE5OEI0MSIsICIjOTIwNzgzIiwgIiNGOEI2MkQiLCAiIzhFNUUzMiIsICIjMUUyQjY4IikNCm5hbWVzKG1ldGhvZF9jb2xvcikgPSBtZXRob2RfbmFtZQ0KYmFyX2NvbG9yID0gcmVwKCJncmF5NTAiLCBsZW5ndGgobWV0aG9kX25hbWUpKQ0KbmFtZXMoYmFyX2NvbG9yKSA9IG1ldGhvZF9uYW1lDQpiYXJfY29sb3JbIlJhdyJdID0gImdyYXk4MCINCmJhcl9jb2xvclsiRElTQyJdID0gInJlZCINCnRleHRfY29sb3IgPSByZXAoImJsYWNrIiwgbGVuZ3RoKG1ldGhvZF9uYW1lKSkNCm5hbWVzKHRleHRfY29sb3IpID0gbWV0aG9kX25hbWUNCnRleHRfY29sb3JbIkRJU0MiXSA9ICJyZWQiDQojIyMgbWFrZSBvdXRwdXQgZGlyDQpvdXRkaXIgPSAiLi9yZXN1bHRzL01FTEFOT01BL3N0cnVjdHVyZV9yZWNvdmVyeSINCmRpci5jcmVhdGUob3V0ZGlyLCBzaG93V2FybmluZ3MgPSBGLCByZWN1cnNpdmUgPSBUKQ0KYGBgDQojIyMgRXZhbHVhdGlvbg0KIyMjIyBDYWxjdWxhdGUgY29ycmVsYXRpb24gd2l0aCBGSVNIDQpgYGB7cn0NCiMjIyBjYWxjdWxhdGUgY29ycmVsYXRpb24gb2YgRklTSA0KY29yX21hdCA9IG1hdHJpeChuY29sID0gMywgbnJvdyA9IDAsIGRpbW5hbWVzID0gbGlzdChjKCksIGMoIkdlbmUgeCIsICJHZW5lIHkiLCAiRklTSCBDb3JyZWxhdGlvbiIpKSkNCmNvcl9hbGwgPSBsaXN0KCkNCmZvcihpaSBpbiBjKCJGSVNIIiwgbWV0aG9kX25hbWUpKXsNCiAgY29yX2FsbFtbaWldXSA9IG1hdHJpeChucm93ID0gbGVuZ3RoKHVzZV9nZW5lcyksIG5jb2wgPSBsZW5ndGgodXNlX2dlbmVzKSwgZGltbmFtZXMgPSBsaXN0KHVzZV9nZW5lcywgdXNlX2dlbmVzKSkNCn0NCmZvcihpaSBpbiB1c2VfZ2VuZXMpew0KICBmb3IoamogaW4gdXNlX2dlbmVzKXsNCiAgICAjY2F0KGlpLCAiXHQiLCBqaiwgIlxuIikNCiAgICBmb3Ioa2sgaW4gYygiRklTSCIsIG1ldGhvZF9uYW1lKSl7DQogICAgICBpZihrayA9PSAiU0FWRVIiKXsNCiAgICAgICAgY29yX2FsbFtbIlNBVkVSIl1dW2lpLCBqal0gPSBjb3IoZGF0YXNldF9saXN0W1siU0FWRVJfZ2FtbWEiXV1baWksIF0sIGRhdGFzZXRfbGlzdFtbIlNBVkVSX2dhbW1hIl1dW2pqLCBdLCB1c2UgPSAicGFpcndpc2UuY29tcGxldGUub2JzIikNCiAgICAgIH1lbHNlew0KICAgICAgICBjb3JfYWxsW1tra11dW2lpLCBqal0gPSBjb3IoZGF0YXNldF9saXN0W1tra11dW2lpLCBdLCBkYXRhc2V0X2xpc3RbW2trXV1bamosIF0sIHVzZSA9ICJwYWlyd2lzZS5jb21wbGV0ZS5vYnMiKQ0KICAgICAgfQ0KICAgIH0NCiAgfQ0KfQ0KYGBgDQojIyMjIGNvcnJlbGF0aW9uIG1hcA0KYGBge3IgZmlnLmhlaWdodD03LjUsIGZpZy53aWR0aD0xNn0NCmZpc2hfbWFza19tYXQgPSAhaXMubmEoY29yX2FsbFtbIkZJU0giXV0pDQpmb3IoaWkgaW4gMTpsZW5ndGgodXNlX2dlbmVzKSl7DQogIGZpc2hfbWFza19tYXRbaWksIHNlcShpaSldID0gRkFMU0UNCn0NCmZpc2hfbWFzayA9IGFzLnZlY3RvcihmaXNoX21hc2tfbWF0KQ0KZmlzaF9nZW5lX21hdCA9IGFwcGx5KGZpc2hfbWFza19tYXQsIDEsIG5hbWVzKQ0Kb3V0ZmlsZSA9IHBhc3RlMChvdXRkaXIsICIvY29ycmVsYXRpb25fbWFwLnBkZiIpDQptZnJvdyA9IG1ha2VfbWZyb3coMiwgbGVuZ3RoKG1ldGhvZF9uYW1lKSkNCiNwZGYob3V0ZmlsZSwgaGVpZ2h0ID0gbWZyb3dbMV0gKiAzLjc1LCB3aWR0aCA9IG1mcm93WzJdICogMy4yNSkNCmxheW91dF9zY2F0dGVyKGNvcl9hbGwsIG1ldGhvZF9uYW1lLCBmaXNoX21hc2ssIHRoaXNfeGxhYiA9ICJGSVNIIiwgdGhpc195bGFiID0gInNjUk5BLXNlcSIsIHhsaW0gPSBjKC0wLjIsIDEpLCB5bGltID0gYygtMC4yLCAxKSwgcG9pbnRfc2l6ZSA9IDIuNzUpDQojZGV2Lm9mZigpDQpgYGANCiMjIyMgQ29ycmVsYXRpb24gaGVhdG1hcA0KYGBge3IgZmlnLmhlaWdodD03LCBmaWcud2lkdGg9MTZ9DQojICBoZWF0bWFwICYgQ01EDQpvdXRmaWxlID0gcGFzdGUwKG91dGRpciwgIi9jb3JyZWxhdGlvbl9oZWF0bWFwLnBkZiIpDQp1c2Vfb3JkZXIgPSBvcmRlcihjb2xNZWFucyhtYXRyaXgoYXBwbHkoY29yX2FsbFtbIkZJU0giXV0sIDIsIGZ1bmN0aW9uKHgpew0KICB4W2lzLm5hKHgpXSA9IDANCiAgcmV0dXJuKHgpDQp9KVt0KHQoZmlzaF9nZW5lX21hdCkgIT0gY29sbmFtZXMoZmlzaF9nZW5lX21hdCkpXSwgbnJvdyA9IG5yb3coY29yX2FsbFtbIkZJU0giXV0pIC0gMSwgbmNvbCA9IG5jb2woY29yX2FsbFtbIkZJU0giXV0pLCBkaW1uYW1lcyA9IGxpc3QoYygpLCBjb2xuYW1lcyhjb3JfYWxsW1siRklTSCJdXSkpKSksIGRlY3JlYXNpbmcgPSBUKQ0KI3BkZihvdXRmaWxlLCBoZWlnaHQgPSA3LCB3aWR0aCA9IDEyLjUpDQpjbWRfdmVjdG9yID0gbGF5b3V0X2NvcnJlbG9ncmFtX3Bsb3QoY29yX2FsbCwgdXNlX29yZGVyPXVzZV9vcmRlcikNCiNkZXYub2ZmKCkNCm5hbWVzKGNtZF92ZWN0b3IpID0gbmFtZXMoY29yX2FsbCkNCmNtZF92ZWN0b3IgPSBjbWRfdmVjdG9yW3NldGRpZmYobmFtZXMoY21kX3ZlY3RvciksICJGSVNIIildDQpgYGANCiMjIyMgQ01EDQpgYGB7ciBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD00LjV9DQpiYXJwbG90X3VzYWdlKGNtZF92ZWN0b3IsIG1haW4gPSAiQ01EIiwgYmFyX2NvbG9yID0gbWV0aG9kX2NvbG9yLCB0ZXh0X2NvbG9yID0gdGV4dF9jb2xvciwgdXNlX2RhdGFfb3JkZXIgPSBULCB1c2VfYm9yZGVyID0gRikNCnNhdmVSRFMoY29yX2FsbCwgcGFzdGUwKG91dGRpciwgIi9jb3JfYWxsLnJkcyIpKQ0KYGBgDQojIyMjIE5vcm1hbGl6YXRpb24NCmBgYHtyfQ0KZXhhbXBsZV9nZW5lID0gYygiV05UNUEiLCAiU09YMTAiKQ0KcmVzY2FsZV9tZWFuX2xpc3QgPSBsaXN0KCkNCmZvcihpaSBpbiBtZXRob2RfbmFtZSl7DQogIGlmKGlpICE9ICJTQVZFUiIpew0KICAgIHJlc2NhbGVfbWVhbl9saXN0W1tpaV1dID0gbWVhbl9ub3JtX2Z1bihkYXRhc2V0X2xpc3RbW2lpXV0sIGRhdGFzZXRfbGlzdFtbIkZJU0giXV0pDQogIH1lbHNlew0KICAgIHJlc2NhbGVfbWVhbl9saXN0W1tpaV1dID0gbWVhbl9ub3JtX2Z1bihkYXRhc2V0X2xpc3RbWyJTQVZFUl9nYW1tYSJdXSwgZGF0YXNldF9saXN0W1siRklTSCJdXSkNCiAgfQ0KfQ0KbWF4X3BvaW50cyA9IG5jb2woZGF0YXNldF9saXN0W1siRklTSCJdXSkNCmZvcihpaSBpbiByZXNjYWxlX21lYW5fbGlzdCl7DQogIG1heF9wb2ludHMgPSBtYXgoYyhtYXhfcG9pbnRzLCBuY29sKGlpKSkpDQp9DQpzYXZlcl9zdHlsZV9maWx0X25vcm1fbGlzdCA9IHJlc2NhbGVfbWVhbl9saXN0DQojc2F2ZXJfc3R5bGVfZmlsdF9ub3JtX2xpc3RbWyJTQVZFUiJdXSA9IGJpbm9tX3Jlc3VsdF9tZWFuKGRhdGFzZXRfbGlzdFtbIlNBVkVSIl1dLCBkYXRhc2V0X2xpc3RbWyJGSVNIIl1dKQ0Kc2F2ZXJfc3R5bGVfZmlsdF9ub3JtX2xpc3RbWyJGSVNIIl1dID0gZGF0YXNldF9saXN0W1siRklTSCJdXQ0Kbm9ybV9mb3JfZGVuc2l0eSA9IHNhdmVyX3N0eWxlX2ZpbHRfbm9ybV9saXN0DQpmb3IoaWkgaW4gbmFtZXMobm9ybV9mb3JfZGVuc2l0eSkpew0KICBub3JtX2Zvcl9kZW5zaXR5W1tpaV1dID0gbm9ybV9mb3JfZGVuc2l0eVtbaWldXVtleGFtcGxlX2dlbmUsXQ0KfQ0Kc2F2ZVJEUyhub3JtX2Zvcl9kZW5zaXR5LCBwYXN0ZTAob3V0ZGlyLCAiL25vcm1fZm9yX2RlbnNpdHkucmRzIikpDQpgYGANCiMjIyMgR2luaQ0KYGBge3IgZmlnLmhlaWdodD03LjUsIGZpZy53aWR0aD0xNi41fQ0KZ2luaV9yZXN1bHRfbGlzdCA9IGxpc3QoKQ0KZm9yKGlpIGluIG5hbWVzKHNhdmVyX3N0eWxlX2ZpbHRfbm9ybV9saXN0KSl7DQogIGlmKGlpICE9ICJTQVZFUiIpew0KICAgIGdpbmlfcmVzdWx0X2xpc3RbW2lpXV0gPSBhcHBseShkYXRhc2V0X2xpc3RbW2lpXV0sIDEsIGZ1bmN0aW9uKHgpe2dpbmkoeFtjb21wbGV0ZS5jYXNlcyh4KV0pfSkNCiAgfWVsc2V7DQogICAgZ2luaV9yZXN1bHRfbGlzdFtbaWldXSA9IGFwcGx5KGRhdGFzZXRfbGlzdFtbIlNBVkVSX2dhbW1hIl1dLCAxLCBmdW5jdGlvbih4KXtnaW5pKHhbY29tcGxldGUuY2FzZXMoeCldKX0pDQogIH0NCn0NCmNvbG9yX3BvaW50ID0gYygiIzMxYTM1NCIsICIjYTYzNjAzIikNCm5hbWVzKGNvbG9yX3BvaW50KSA9IGV4YW1wbGVfZ2VuZQ0Kb3V0ZmlsZSA9IHBhc3RlMChvdXRkaXIsICIvR2luaS5wZGYiKQ0KbWZyb3cgPSBtYWtlX21mcm93KDIsIGxlbmd0aChtZXRob2RfbmFtZSkpDQojcGRmKG91dGZpbGUsIGhlaWdodCA9IG1mcm93WzFdICogMy43NSwgd2lkdGggPSBtZnJvd1syXSAqIDMuMjUpDQpnaW5pX3Jtc2UgPSBsYXlvdXRfc2NhdHRlcihnaW5pX3Jlc3VsdF9saXN0LCBtZXRob2RfbmFtZSwgdXNlX2dlbmVzLCBjb2xvcl9wb2ludCA9IGNvbG9yX3BvaW50LCB0aGlzX3hsYWIgPSAiRklTSCBHaW5pIiwgdGhpc195bGFiID0gInNjUk5BLXNlcSBHaW5pIiwgeGxpbSA9IGMoMCwgMSksIHlsaW0gPSBjKDAsIDEpKQ0KI2Rldi5vZmYoKQ0Kc2F2ZVJEUyhnaW5pX3Jlc3VsdF9saXN0LCBwYXN0ZTAob3V0ZGlyLCAiL2dpbmlfcmVzdWx0X2xpc3QucmRzIikpDQpgYGANCmBgYHtyIGZpZy5oZWlnaHQ9NiwgZmlnLndpZHRoPTQuNX0NCnBhcihtZnJvdyA9IGMoMSwgMSkpDQpiYXJwbG90X3VzYWdlKGdpbmlfcm1zZSwgbWFpbiA9ICJHaW5pIFJNU0UiLCBiYXJfY29sb3IgPSBtZXRob2RfY29sb3IsIHRleHRfY29sb3IgPSB0ZXh0X2NvbG9yLCB1c2VfZGF0YV9vcmRlciA9IFQsIHVzZV9ib3JkZXIgPSBGKQ0KYGBgDQojIyMjIERlbnNpdHkNCmBgYHtyIGZpZy5oZWlnaHQ9OSwgZmlnLndpZHRoPTE2LjUsIHdhcm5pbmc9RkFMU0V9DQpwbG90X2dlbmVzID0gdXNlX2dlbmVzDQprc19tYXRyaXggPSBtYXRyaXgobnJvdyA9IGxlbmd0aChwbG90X2dlbmVzKSwgbmNvbCA9IGxlbmd0aChtZXRob2RfbmFtZSksIGRpbW5hbWVzID0gbGlzdChwbG90X2dlbmVzLCBtZXRob2RfbmFtZSkpDQptZnJvdyA9IGMoMywgNikNCm91dGZpbGUgPSBwYXN0ZTAob3V0ZGlyLCAiL2RlbnNpdHkucGRmIikNCiNwZGYob3V0ZmlsZSwgaGVpZ2h0ID0gbWZyb3dbMV0gKiAzLCB3aWR0aCA9IG1mcm93WzJdICogMi43NSkNCnBhcihtZnJvdyA9IG1mcm93KQ0KZm9yKGlpIGluIHBsb3RfZ2VuZXMpew0KICB0aGlzX2Zpc2ggPSBzYXZlcl9zdHlsZV9maWx0X25vcm1fbGlzdFtbIkZJU0giXV1baWksIF0NCiAgdGhpc19maXNoID0gdGhpc19maXNoWyFpcy5uYSh0aGlzX2Zpc2gpXQ0KICBmaXNoX2RlbnNpdHkgPSBkZW5zaXR5KHRoaXNfZmlzaCkNCiAgemVyb19wcm9wb3J0aW9uID0gcm91bmQoMTAwICogKDEgLSBzdW0oZGF0YXNldF9saXN0W1siUmF3Il1dW2lpLCBdID4gMCkgLyBsZW5ndGgodXNlX2NlbGwpKSwgZGlnaXRzID0gNCkNCiAgeGxpbV9tYXggPSBhcy5udW1lcmljKHF1YW50aWxlKHRoaXNfZmlzaCwgMC45MCkpICogMg0KICBkZW5zLmJ3ID0gZmlzaF9kZW5zaXR5JGJ3DQogIHlsaW1fbWF4ID0gbWF4KGZpc2hfZGVuc2l0eSR5KQ0KICB1c2VfZGVuc2l0eSA9IGxpc3QoKQ0KICBmb3IobWV0aG9kX2luZGV4IGluIDE6bGVuZ3RoKG1ldGhvZF9uYW1lKSl7DQogICAgdGhpc19tZXRob2RfZXhwcmVzc2lvbiA9IHNhdmVyX3N0eWxlX2ZpbHRfbm9ybV9saXN0W1ttZXRob2RfbmFtZVttZXRob2RfaW5kZXhdXV1baWksIF0NCiAgICBpZihsZW5ndGgodW5pcXVlKHRoaXNfbWV0aG9kX2V4cHJlc3Npb24pKSA9PSAxKXsNCiAgICAgIHRoaXNfbWV0aG9kX2V4cHJlc3Npb25bMV0gPSB0aGlzX21ldGhvZF9leHByZXNzaW9uWzFdICogMS4wMDAxDQogICAgfQ0KICAgICNpZihtZXRob2RfaW5kZXggPD0gMil7DQogICAgaWYoVCl7DQogICAgICB0aGlzX2RlbnNpdHkgPSBkZW5zaXR5KHRoaXNfbWV0aG9kX2V4cHJlc3Npb24sIGJ3ID0gZGVucy5idykNCiAgICAgIHVzZV9kZW5zaXR5W1ttZXRob2RfbmFtZVttZXRob2RfaW5kZXhdXV0gPSB0aGlzX2RlbnNpdHkNCiAgICAgIHlsaW1fbWF4ID0gbWF4KGMoeWxpbV9tYXgsIHRoaXNfZGVuc2l0eSR5KSkNCiAgICB9DQogICAga3NfbWF0cml4W2lpLCBtZXRob2RfaW5kZXhdID0ga3MudGVzdChkZWxldGVfbHQwLjUodGhpc19tZXRob2RfZXhwcmVzc2lvbiksIGRlbGV0ZV9sdDAuNSh0aGlzX2Zpc2gpKSRzdGF0aXN0aWMNCiAgfQ0KICBwbG90KGZpc2hfZGVuc2l0eSwgbHdkID0gMiwgY29sID0gImJsYWNrIiwgbHR5ID0gMSwNCiAgICAgICB4bGltID0gYyhtaW4oZmlzaF9kZW5zaXR5JHgsIDApLCB4bGltX21heCArIDUpLA0KICAgICAgIHlsaW0gPSBjKDAsIHlsaW1fbWF4KSwgeWF4dCA9ICJzIiwgYnR5PSJuIiwNCiAgICAgICBtYWluID0gcGFzdGUwKHRvdXBwZXIoaWkpLCAiICgiLCB6ZXJvX3Byb3BvcnRpb24sICIlKSIpLA0KICAgICAgIHN1YiA9ICIiLCB5bGFiID0gIkRlbnNpdHkiLCB4bGFiID0gIm1STkEgQ291bnRzIikNCiAgcGFyKGxhcyA9IDApDQogIGZvcih0aGlzX2RlbnNpdHlfbmFtZSBpbiBuYW1lcyh1c2VfZGVuc2l0eSkpew0KICAgIGxpbmVzKHVzZV9kZW5zaXR5W1t0aGlzX2RlbnNpdHlfbmFtZV1dLCBsd2QgPSAzLCBjb2w9bWV0aG9kX2NvbG9yW3RoaXNfZGVuc2l0eV9uYW1lXSkNCiAgfQ0KICBpZihpaSAlaW4lIHBsb3RfZ2VuZXNbbWZyb3dbMl0gKyAobWZyb3dbMV0gKiBtZnJvd1syXSAqIHNlcSgwLCBmbG9vcihsZW5ndGgocGxvdF9nZW5lcykgLyBtZnJvd1sxXSAqIG1mcm93WzJdKSkpXSl7DQogICAgbGVnZW5kKCJ0b3ByaWdodCIsIGMoIkZJU0giLCBuYW1lcyh1c2VfZGVuc2l0eSkpLCBsdHkgPSByZXAoMSwgMSArIGxlbmd0aChuYW1lcyh1c2VfZGVuc2l0eSkpKSwNCiAgICAgICAgICAgbHdkID0gcmVwKDMsIDEgKyBsZW5ndGgobmFtZXModXNlX2RlbnNpdHkpKSksIGNvbCA9IGMoImJsYWNrIiwgbWV0aG9kX2NvbG9yW25hbWVzKHVzZV9kZW5zaXR5KV0pLCBib3gubHR5ID0gMCwgeGp1c3QgPSAxLCB5anVzdCA9IDEpDQogIH0NCn0NCiNkZXYub2ZmKCkNCmBgYA0KYGBge3IgZmlnLmhlaWdodD02LCBmaWcud2lkdGg9NC41fQ0Kb3V0ZmlsZSA9IHBhc3RlMChvdXRkaXIsICIvZGVuc2l0eV9zdW1tYXJ5LnBkZiIpDQojcGRmKG91dGZpbGUsIGhlaWdodCA9IDYsIHdpZHRoID0gbmNvbChrc19tYXRyaXgpICogMC42KQ0Ka3NfbWVhbiA9IE1hdHJpeDo6Y29sTWVhbnMoa3NfbWF0cml4KQ0Kc3RhbmRhcmRfZXJyb3Jfa3MgPSBhcHBseShrc19tYXRyaXgsIDIsIGZ1bmN0aW9uKHgpIHNxcnQodmFyKHgpL2xlbmd0aCh4KSkpDQpwYXIobWZyb3cgPSBjKDEsIDEpKQ0KYmFycGxvdF91c2FnZShrc19tZWFuLCBtYWluID0gIkstUyBTdGF0aXN0aWMiLCBiYXJfY29sb3IgPSBtZXRob2RfY29sb3IsIHRleHRfY29sb3IgPSB0ZXh0X2NvbG9yLCB1c2VfZGF0YV9vcmRlciA9IFQsIHN0YW5kYXJkX2Vycm9yID0gc3RhbmRhcmRfZXJyb3Jfa3MsIHVzZV9ib3JkZXIgPSBGKQ0KI2Rldi5vZmYoKQ0KYGBgDQojIyMjIDJEIGRpc3RyaWJ1dGlvbg0KYGBge3J9DQojIE1ha2UgdGhlIHBsb3RzDQpkaXN0X291dGRpciA9IHBhc3RlMChvdXRkaXIsICIvMmRfZGlzdHJpYnV0aW9uIikNCmRpci5jcmVhdGUoZGlzdF9vdXRkaXIsIHNob3dXYXJuaW5ncyA9IEYpDQptZnJvdyA9IG1ha2VfbWZyb3coMiwgbGVuZ3RoKGMoIkZJU0giLCBtZXRob2RfbmFtZSkpKQ0KcGFpcnNfMmRfZGlzdHJpYnV0aW9uID0gY29yX21hdFtvcmRlcihhYnMoY29yX21hdFssIDNdKSwgZGVjcmVhc2luZyA9IFRSVUUpLCBdDQpsaWJyYXJ5KHBhcmFsbGVsKQ0Kbm9fY29yZXMgPC0gbWF4KGMoZGV0ZWN0Q29yZXMoKSAtIDEsIDEpKQ0KY2wgPC0gbWFrZUNsdXN0ZXIobm9fY29yZXMpDQpjbHVzdGVyRXhwb3J0KGNsLCB2YXJsaXN0ID0gYygiZGlzdF9vdXRkaXIiLCAicmVzY2FsZV9tZWFuX2xpc3QiLCAibWV0aG9kX25hbWUiLCAidXNlX2NlbGwiLCAibWZyb3ciLCAiZmlzaF9nZW5lX21hdCIsICJmaXNoX21hc2tfbWF0IiwgImRhdGFzZXRfbGlzdCIsICJ1dGlsaXRpZXNfcGF0aCIpKQ0KcmV0dXJuX2xpc3QgPSBwYXJMYXBwbHkoY2wsIDE6c3VtKGZpc2hfbWFzayksIGZ1bmN0aW9uKGlpKXsNCiAgc291cmNlKHV0aWxpdGllc19wYXRoKQ0KICBnZW5lX3ggPSBmaXNoX2dlbmVfbWF0W3QoZmlzaF9tYXNrX21hdCldW2lpXQ0KICBnZW5lX3kgPSB0KGZpc2hfZ2VuZV9tYXQpW3QoZmlzaF9tYXNrX21hdCldW2lpXQ0KICB4X2Ryb3BvdXRfcmF0ZSA9IHJvdW5kKDEwMCAqICgxIC0gc3VtKGRhdGFzZXRfbGlzdFtbIlJhdyJdXVtnZW5lX3gsIF0gPiAwKSAvIGxlbmd0aCh1c2VfY2VsbCkpLCBkaWdpdHMgPSA0KQ0KICB5X2Ryb3BvdXRfcmF0ZSA9IHJvdW5kKDEwMCAqICgxIC0gc3VtKGRhdGFzZXRfbGlzdFtbIlJhdyJdXVtnZW5lX3ksIF0gPiAwKSAvIGxlbmd0aCh1c2VfY2VsbCkpLCBkaWdpdHMgPSA0KQ0KICB4X2Zpc2hfcmF3ID0gZGF0YXNldF9saXN0W1siRklTSCJdXVtnZW5lX3gsIF0NCiAgeV9maXNoX3JhdyA9IGRhdGFzZXRfbGlzdFtbIkZJU0giXV1bZ2VuZV95LCBdDQogIHNlbGVjdF9jZWxsID0gIWlzLm5hKHhfZmlzaF9yYXcpICYgIWlzLm5hKHlfZmlzaF9yYXcpDQogIHhfZmlzaCA9IHhfZmlzaF9yYXdbc2VsZWN0X2NlbGxdDQogIHlfZmlzaCA9IHlfZmlzaF9yYXdbc2VsZWN0X2NlbGxdDQogIGZpc2hfcGFpcl9tYXQgPSBtYXRyaXgoYyh4X2Zpc2gsIHlfZmlzaCksIG5jb2wgPSAyKQ0KICBrc19zdGF0ID0gYygpDQogIGNvcnJfc2NvcmUgPSBjb3IoeF9maXNoLCB5X2Zpc2gpDQogIHhfaSA9IGxpc3QoRklTSCA9IHhfZmlzaCkNCiAgeV9pID0gbGlzdChGSVNIID0geV9maXNoKQ0KICBmb3IoamogaW4gMTpsZW5ndGgobWV0aG9kX25hbWUpKXsNCiAgICB0aGlzX21ldGhvZF9uYW1lID0gbWV0aG9kX25hbWVbampdDQogICAgeF9pW1t0aGlzX21ldGhvZF9uYW1lXV0gPSByZXNjYWxlX21lYW5fbGlzdFtbdGhpc19tZXRob2RfbmFtZV1dW2dlbmVfeCwgXQ0KICAgIHlfaVtbdGhpc19tZXRob2RfbmFtZV1dID0gcmVzY2FsZV9tZWFuX2xpc3RbW3RoaXNfbWV0aG9kX25hbWVdXVtnZW5lX3ksIF0NCiAgICBrc19zdGF0ID0gYyhrc19zdGF0LCBrczJkMnMocm91bmQoeF9maXNoKSwgcm91bmQoeV9maXNoKSwgcm91bmQoeF9pW1t0aGlzX21ldGhvZF9uYW1lXV0pLCByb3VuZCh5X2lbW3RoaXNfbWV0aG9kX25hbWVdXSkpKQ0KICAgIGNvcnJfc2NvcmUgPSBjKGNvcnJfc2NvcmUsIGNvcih4X2lbW3RoaXNfbWV0aG9kX25hbWVdXSwgeV9pW1t0aGlzX21ldGhvZF9uYW1lXV0pKQ0KICB9DQogIG5hbWVzKGNvcnJfc2NvcmUpID0gYygiRklTSCIsIG1ldGhvZF9uYW1lKQ0KICBuYW1lcyhrc19zdGF0KSA9IG1ldGhvZF9uYW1lDQogIHRoaXNfbmFtZSA9IGMocGFzdGUoZ2VuZV94LCBnZW5lX3ksIHNlcCA9ICJfIikpDQogIHBkZihwYXN0ZTAoZGlzdF9vdXRkaXIsICIvIiwgdGhpc19uYW1lLCAiLnBkZiIpLA0KICAgICAgaGVpZ2h0ID0gbWZyb3dbMV0gKiA0LA0KICAgICAgd2lkdGggPSBtZnJvd1syXSAqIDMuNzUpDQogIHBhcihtZnJvdyA9IG1mcm93KQ0KICBuYmluID0gMTI4DQogIHhfZmlzaF85NSA9IHF1YW50aWxlKHhfZmlzaCwgMC45NSkgKyAxIyMjIFIgaXMgZnJvbSAxIHRvIG1heCArIDENCiAgeV9maXNoXzk1ID0gcXVhbnRpbGUoeV9maXNoLCAwLjk1KSArIDENCiAgZm9yKGpqIGluIGMoIkZJU0giLCBtZXRob2RfbmFtZSkpew0KICAgIGlmKGpqID09ICJESVNDIil7DQogICAgICBjb2wubWFpbiA9ICJyZWQiDQogICAgfWVsc2V7DQogICAgICBjb2wubWFpbiA9ICJibGFjayINCiAgICB9DQogICAgaWYoamogPT0gIlJhdyIpew0KICAgICAgeF91c2UgPSBkYXRhc2V0X2xpc3RbW2pqXV1bZ2VuZV94LCBdDQogICAgICB5X3VzZSA9IGRhdGFzZXRfbGlzdFtbampdXVtnZW5lX3ksIF0NCiAgICAgIHhsaW0gPSBjKDAsIG1heCh4X3VzZSkpDQogICAgICB5bGltID0gYygwLCBtYXgoeV91c2UpKQ0KICAgICAgYmFuZHdpZHRoID0gYyh4bGltWzJdIC8gbmJpbiwgeWxpbVsyXSAvIG5iaW4pDQogICAgfWVsc2V7DQogICAgICB4X3VzZSA9IHhfaVtbampdXQ0KICAgICAgeV91c2UgPSB5X2lbW2pqXV0NCiAgICAgIHhsaW0gPSBjKDAsIHhfZmlzaF85NSkNCiAgICAgIHlsaW0gPSBjKDAsIHlfZmlzaF85NSkNCiAgICAgIGJhbmR3aWR0aCA9IGMobWF4KHhfZmlzaCkgLyBuYmluLCBtYXgoeV9maXNoKSAvIG5iaW4pDQogICAgfQ0KICAgIHNtb290aFNjYXR0ZXIxKHggPSB4X3VzZSwgeSA9IHlfdXNlLA0KICAgICAgICAgICAgICAgICAgIHhsYWIgPSBwYXN0ZTAoZ2VuZV94LCAiICgiLCB4X2Ryb3BvdXRfcmF0ZSwgIiUpIiksDQogICAgICAgICAgICAgICAgICAgeWxhYiA9IHBhc3RlMChnZW5lX3ksICIgKCIsIHlfZHJvcG91dF9yYXRlLCAiJSkiKSwNCiAgICAgICAgICAgICAgICAgICBjZXggPSAxLjUsIHhsaW0gPSB4bGltLCB5bGltID0geWxpbSwNCiAgICAgICAgICAgICAgICAgICBsd2QgPSAyLCBtYWluID0gcGFzdGUwKGpqLCAiIC0gRkYgPSAiLCByb3VuZChrc19zdGF0W2pqXSwgNCkpLA0KICAgICAgICAgICAgICAgICAgIG5ycG9pbnRzID0gMCwgY29sLm1haW4gPSBjb2wubWFpbiwgbmJpbiA9IG5iaW4sIGJhbmR3aWR0aCA9IGJhbmR3aWR0aCkNCiAgfQ0KICBkZXYub2ZmKCkNCiAgcmV0dXJuKGxpc3QoImtzX3N0YXQiID0gbWF0cml4KGtzX3N0YXQsIG5yb3cgPSAxLCBkaW1uYW1lcyA9IGxpc3QocGFzdGUoZ2VuZV94LCBnZW5lX3ksIHNlcCA9ICIgLSAiKSwgYygpKSksDQogICAgICAgICAgICAgICJjb3JyX3Njb3JlIiA9IG1hdHJpeChjb3JyX3Njb3JlLCBucm93ID0gMSwgZGltbmFtZXMgPSBsaXN0KHBhc3RlKGdlbmVfeCwgZ2VuZV95LCBzZXAgPSAiIC0gIiksIGMoKSkpKSkNCn0pDQpzdG9wQ2x1c3RlcihjbCkNCmtzX3N0YXRfbWF0ID0gbWF0cml4KG5yb3cgPSAwLCBuY29sID0gbGVuZ3RoKG1ldGhvZF9uYW1lKSwgZGltbmFtZXMgPSBsaXN0KGMoKSwgbWV0aG9kX25hbWUpKQ0KY29ycl9tYXQgPSBtYXRyaXgobnJvdyA9IDAsIG5jb2wgPSBsZW5ndGgobWV0aG9kX25hbWUpICsgMSwgZGltbmFtZXMgPSBsaXN0KGMoKSwgYygiRklTSCIsIG1ldGhvZF9uYW1lKSkpDQoNCmZvcihpaSBpbiByZXR1cm5fbGlzdCl7DQogIGtzX3N0YXRfbWF0ID0gcmJpbmQoa3Nfc3RhdF9tYXQsIGlpJGtzX3N0YXQpDQogIGNvcnJfbWF0ID0gcmJpbmQoY29ycl9tYXQsIGlpJGNvcnJfc2NvcmUpDQp9DQpzYXZlUkRTKGtzX3N0YXRfbWF0LCBwYXN0ZShvdXRkaXIsICIva3Nfc3RhdF9tYXQucmRzIiwgc2VwID0gIiIpKQ0KcHJpbnQocGFzdGUwKCJQbGVhc2Ugc2VlICIsIGRpc3Rfb3V0ZGlyLCAiIGZvciBhbGwgcmVzdWx0cy4iKSkNCiMjI2FsbF9jb21wYXJlDQptZWFuX2tzX3N0YXQgPSBNYXRyaXg6OmNvbE1lYW5zKGtzX3N0YXRfbWF0KQ0Kc3RhbmRhcmRfZXJyb3Jfa3Nfc3RhdCA9IGFwcGx5KGtzX3N0YXRfbWF0LCAyLCBmdW5jdGlvbih4KSBzcXJ0KHZhcih4KS9sZW5ndGgoeCkpKQ0KYGBgDQpgYGB7ciBmaWcuaGVpZ2h0PTgsIGZpZy53aWR0aD0xMn0NCm91dGZpbGUgPSBwYXN0ZShvdXRkaXIsICIvc2NvcmVfY29tcGFyZS5wZGYiLCBzZXAgPSAiIikNCiNwZGYob3V0ZmlsZSwgaGVpZ2h0ID0gNSwgd2lkdGggPSAxMSkNCnBhcihtZnJvdyA9IGMoMSwgMikpDQpiYXJwbG90X3VzYWdlKG1lYW5fa3Nfc3RhdCwgbWFpbiA9ICJGYXNhbm8gYW5kIEZyYW5jZXNjaGluaSdzIFRlc3QiLCBjZXgubWFpbiA9IDEuNSxiYXJfY29sb3IgPSBtZXRob2RfY29sb3IsIHRleHRfY29sb3IgPSB0ZXh0X2NvbG9yLCB1c2VfZGF0YV9vcmRlciA9IFQsIHN0YW5kYXJkX2Vycm9yID0gc3RhbmRhcmRfZXJyb3Jfa3Nfc3RhdCwgdXNlX2JvcmRlciA9IEYpDQpjb3JyX3Jtc2UgPSBzYXBwbHkobWV0aG9kX25hbWUsIGZ1bmN0aW9uKHgpIHJtc2UoY29ycl9tYXRbLCAiRklTSCJdLCBjb3JyX21hdFssIHhdKSkNCmJhcnBsb3RfdXNhZ2UoY29ycl9ybXNlLCBtYWluID0gIkZJU0ggLSBJbXB1dGUgQ29ycmVsYXRpb24gUk1TRSIsIGNleC5tYWluID0gMS41LCBiYXJfY29sb3IgPSBtZXRob2RfY29sb3IsIHRleHRfY29sb3IgPSB0ZXh0X2NvbG9yLCB1c2VfZGF0YV9vcmRlciA9IFQsIHVzZV9ib3JkZXIgPSBGKQ0KI2Rldi5vZmYoKQ0KYGBgDQojIyMgU3VtbWFyeQ0KYGBge3IgZmlnLmhlaWdodD0zLCBmaWcud2lkdGg9OS41fQ0Kb3V0ZmlsZSA9IHBhc3RlKG91dGRpciwgIi9CYXJfcGxvdC5wZGYiLCBzZXAgPSAiIikNCiNwZGYob3V0ZmlsZSwgaGVpZ2h0ID0gMywgd2lkdGggPSA5LjUpDQpwbG90X2hlaWdodCA9IDQNCnBsb3Rfd2lkdGggPSAzLjUNCnBsb3RfcmVnaW9uID0gbWF0cml4KHNlcSgzKSwgbnJvdyA9IDEpDQp0aGlzX2hlaWdodCA9IHJlcChwbG90X2hlaWdodCwgbnJvdyhwbG90X3JlZ2lvbikpDQp0aGlzX3dpZHRoID0gcmVwKHBsb3Rfd2lkdGgsIG5jb2wocGxvdF9yZWdpb24pKQ0KdGhpc19pbmRleCA9IG1heChwbG90X3JlZ2lvbikgKyAxDQpsYXlvdXRfbWF0ID0gcGxvdF9yZWdpb24NCnhsYWJfcmVnaW9uID0gbWF0cml4KHJlcCh0aGlzX2luZGV4LCBuY29sKHBsb3RfcmVnaW9uKSksIG5yb3cgPSAxKQ0KbGF5b3V0X21hdCA9IHJiaW5kKGxheW91dF9tYXQsIHhsYWJfcmVnaW9uKQ0KdGhpc19oZWlnaHQgPSBjKHRoaXNfaGVpZ2h0LCAwLjUpDQpsYXlvdXQobWF0ID0gbGF5b3V0X21hdCwgaGVpZ2h0cyA9IHRoaXNfaGVpZ2h0LCB3aWR0aHMgPSB0aGlzX3dpZHRoKQ0KcGFyKG1hciA9IGMoMSwgNC4xLCA0LjEsIDIuMSkpDQpiYXJwbG90X3VzYWdlKGNtZF92ZWN0b3IsIG1haW4gPSAiQ01EIiwgYmFyX2NvbG9yID0gbWV0aG9kX2NvbG9yLCB1c2VfZGF0YV9vcmRlciA9IFQsIHVzZV9ib3JkZXIgPSBGKQ0KYmFycGxvdF91c2FnZShnaW5pX3Jtc2UsIG1haW4gPSAiR2luaSBSTVNFIiwgYmFyX2NvbG9yID0gbWV0aG9kX2NvbG9yLCB1c2VfZGF0YV9vcmRlciA9IFQsIHVzZV9ib3JkZXIgPSBGKQ0KYmFycGxvdF91c2FnZShtZWFuX2tzX3N0YXQsIG1haW4gPSAiRmFzYW5vIGFuZFxuRnJhbmNlc2NoaW5pJ3MgVGVzdCIsIGNleC5tYWluID0gMS41LCBiYXJfY29sb3IgPSBtZXRob2RfY29sb3IsIHVzZV9kYXRhX29yZGVyID0gVCwgc3RhbmRhcmRfZXJyb3IgPSBzdGFuZGFyZF9lcnJvcl9rc19zdGF0LCB1c2VfYm9yZGVyID0gRikNCnBhcihtYXIgPSByZXAoMCwgNCkpDQpwbG90KDEsIHR5cGUgPSAibiIsIGF4ZXMgPSBGQUxTRSwgeGxhYj0iIiwgeWxhYj0iIikNCmxlZ2VuZCh4ID0gInRvcCIsaW5zZXQgPSAwLCBsZWdlbmQgPSBuYW1lcyhtZXRob2RfY29sb3IpLCBmaWxsID0gbWV0aG9kX2NvbG9yLCBob3JpeiA9IFRSVUUsIGJvcmRlciA9IE5BLCBidHkgPSAibiIpDQojZGV2Lm9mZigpDQpvdXRwdXRfbGlzdCA9IGxpc3QoKQ0Kb3V0cHV0X2xpc3RbWyJjbWQiXV0gPSBjbWRfdmVjdG9yDQpvdXRwdXRfbGlzdFtbImdpbmlfcm1zZSJdXSA9IGdpbmlfcm1zZQ0Kb3V0cHV0X2xpc3RbWyJtZWFuX2tzX3N0YXQiXV0gPSBtZWFuX2tzX3N0YXQNCm91dHB1dF9saXN0W1sic3RhbmRhcmRfZXJyb3Jfa3Nfc3RhdCJdXSA9IHN0YW5kYXJkX2Vycm9yX2tzX3N0YXQNCnNhdmVSRFMob3V0cHV0X2xpc3QsIHBhc3RlKG91dGRpciwgIi9CYXJfc3RhdC5yZHMiLCBzZXAgPSAiIikpDQpgYGANCiMjIyBOb3RlczoNClNpbmdsZSBwbG90cyBvZiBGRiBhcmUgc2F2ZWQgaW4gPGEgaHJlZj0iaHR0cHM6Ly9naXRodWIuY29tL2l5aGFvby9ESVNDL3RyZWUvbWFzdGVyL3JlcHJvZHVjaWJpbGl0eS9yZXN1bHRzL01FTEFOT01BL3N0cnVjdHVyZV9yZWNvdmVyeS8yZF9kaXN0cmlidXRpb24iPmhlcmU8L2E+LjwvYnI+DQpBTEwgZGF0YSB3ZSB1c2VkIGluIHRoaXMgc2NyaXB0IGNhbiBiZSBmb3VuZCA8YSBocmVmPSJodHRwczovL2dpdGh1Yi5jb20vaXloYW9vL0RJU0NfZGF0YV9hdmFpbGFiaWxpdHkvdHJlZS9tYXN0ZXIvTUVMQU5PTUEiPmhlcmU8L2E+Lg0KDQoNCg==