In this R Notebook we preprocess spatial and corresponding reference scRNA-seq data of human HER2+ Breast Cancer for cell type deconvolution.

  1. Spatial data preprocessing:

    1.1 Input original data files

    • Raw nUMI of spatial spots: H1.tsv.gz is from zipped file count-matrices.zip downloaded from zenodo.4751624, and this first section from patient H is selected for analysis.

    1.2 Output data files for cell type deconvolution

  2. Reference scRNA-seq data preprocessing:

    2.1 Input original data files

    scRNA-seq data GSE176078_Wu_etal_2021_BRCA_scRNASeq.tar.gz are downloaded from GSE176078. This compressed file contains 4 files:

    • count_matrix_sparse.mtx, count_matrix_barcodes.tsv and count_matrix_genes.tsv: Raw nUMI of all 100,064 cells and 29,733 genes

    • metadata.csv: meta data of all 100,064 cells

    2.2 Output data files for cell type deconvolution

    We select 19,311 HER2+ cells. NO filtering on genes, i.e. all genes are included for analysis.

1 Version

version[['version.string']]
[1] "R version 4.2.2 Patched (2022-11-10 r83330)"

2 Proprocess Breast Cancer spatial dataset

2.1 Read original data file H1.tsv.gz

file_name = file.path(home.dir, 'H1.tsv.gz')
org_data = data.table::fread(file_name, sep = "\t", check.names = FALSE)
Registered S3 method overwritten by 'data.table':
  method           from
  print.data.table     
print(sprintf('load data from %s', file_name))
[1] "load data from /home/hill103/Documents/SharedFolder/ToHost/CVAE-GLRM_Analysis/RealData/Breast_Cancer/H1.tsv.gz"
org_data = as.data.frame(org_data)
# extract first column as row name
row.names(org_data) = org_data$V1
org_data = org_data[, 2:ncol(org_data)]

print(sprintf('spots: %d; genes: %d', nrow(org_data), ncol(org_data)))
[1] "spots: 613; genes: 15029"
org_data[1:5, 1:5]

2.2 Save files for deconvolution

2.2.1 Spatial spot nUMI

No filtering on spots or genes, directly save all spots and genes into file Breast_Cancer_spatial_spot_nUMI.csv. Rows as spatial spots and columns as genes.

write.csv(org_data, 'Breast_Cancer_spatial_spot_nUMI.csv')
print(sprintf('save %d gene nUMIs of %d spatial spots into file %s', ncol(org_data), nrow(org_data), 'Breast_Cancer_spatial_spot_nUMI.csv'))
[1] "save 15029 gene nUMIs of 613 spatial spots into file Breast_Cancer_spatial_spot_nUMI.csv"

2.2.2 Physical Locations of spatial spots

Directly extract the spatial x and y coordinates from spot names, then save it into file Breast_Cancer_spatial_spot_loc.csv.

local_df = data.frame(names = row.names(org_data), row.names = row.names(org_data))
local_df = local_df %>%
  tidyr::separate_wider_delim(names, 'x', names = c('x', 'y'))
local_df = as.data.frame(local_df)
row.names(local_df) = row.names(org_data)

local_df['x'] = as.numeric(local_df$x)
local_df['y'] = as.numeric(local_df$y)

local_df[1:5, ]

write.csv(local_df, 'Breast_Cancer_spatial_spot_loc.csv')
print(sprintf('save Physical Locations of spatial spots into file %s', 'Breast_Cancer_spatial_spot_loc.csv'))
[1] "save Physical Locations of spatial spots into file Breast_Cancer_spatial_spot_loc.csv"

2.2.3 Adjacency Matrix of spatial spots

We define the neighborhood of a spatial spot contains the adjacent left, right, top and bottom spot, that is, one spot has at most 4 neighbors.

The generated Adjacency Matrix A only contains 1 and 0, where 1 represents corresponding two spots are adjacent spots according to the definition of neighborhood, while value 0 for non-adjacent spots. Note all diagonal entries are 0s.

Adjacency Matrix are saved into file Breast_Cancer_spatial_spot_adjacency_matrix.csv.

getNeighbour = function(array_row, array_col) {
  # based on the (row, col) of one spot, return the (row, col) of all 4 neighbours
  return(list(c(array_row-1, array_col),
              c(array_row+1, array_col),
              c(array_row+0, array_col-1),
              c(array_row+0, array_col+1)))
}

# adjacency matrix
A = matrix(0, nrow = nrow(local_df), ncol = nrow(local_df))
row.names(A) = rownames(local_df)
colnames(A) = rownames(local_df)
for (i in 1:nrow(local_df)) {
  barcode = rownames(local_df)[i]
  array_row = local_df[i, 'y']
  array_col = local_df[i, 'x']
  
  # get neighbors
  neighbours = getNeighbour(array_row, array_col)
  
  # fill the adjacency matrix
  for (this.vec in neighbours) {
    tmp.p = rownames(local_df[local_df$y==this.vec[1] & local_df$x==this.vec[2], ])
    
    if (length(tmp.p) >= 1) {
      # target spots have neighbors in selected spots
      for (neigh.barcode in tmp.p) {
        A[barcode, neigh.barcode] = 1
      }
    }
  }
}

A[1:5, 1:5]
      10x10 10x11 10x12 10x13 10x14
10x10     0     1     0     0     0
10x11     1     0     1     0     0
10x12     0     1     0     1     0
10x13     0     0     1     0     1
10x14     0     0     0     1     0
write.csv(A, 'Breast_Cancer_spatial_spot_adjacency_matrix.csv')
print(sprintf('save Adjacency Matrix of spatial spots into file %s', 'Breast_Cancer_spatial_spot_adjacency_matrix.csv'))
[1] "save Adjacency Matrix of spatial spots into file Breast_Cancer_spatial_spot_adjacency_matrix.csv"

Plot Adjacency Matrix. Each node is spot, spots within neighborhood are connected with edges.

Note multiply the y coordinated with -1 to match the figure with H&E staining image

g = graph_from_adjacency_matrix(A, 'undirected', add.colnames = NA, add.rownames = NA)
# manually set nodes x and y coordinates
vertex_attr(g, name = 'x') = local_df$x
vertex_attr(g, name = 'y') = -(local_df$y)
plot(g, vertex.size=5, edge.width=4, margin=-0.05)

3 Preprocess reference scRNA-seq data

3.1 Read and preprocess scRNA-seq meta data

Original meta data file metadata.csv is in the compressed file GSE176078_Wu_etal_2021_BRCA_scRNASeq.tar.gz downloaded from GSE176078.

It contains meta data of 100,064 cells from human breast cancer tissue. We select cells from HER2+ subjects (subtype=HER2+), and 19,311 cells from 5 subjects are selected.

The cell type annotation is stored in column celltype_major, which includes total 9 distinct annotations.

file_name = file.path(home.dir, 'Wu_etal_2021_BRCA_scRNASeq', 'metadata.csv')
ref_meta = read.csv(file_name, sep=',', check.names = F, header = T, row.names = 1)
print(sprintf('load data from %s', file_name))
[1] "load data from /home/hill103/Documents/SharedFolder/ToHost/CVAE-GLRM_Analysis/RealData/Breast_Cancer/Wu_etal_2021_BRCA_scRNASeq/metadata.csv"
print(sprintf('total %d cells with distinct %d cell type annotations', nrow(ref_meta), length(unique(ref_meta$celltype_major))))
[1] "total 100064 cells with distinct 9 cell type annotations"
# select HER2+ cells
ref_meta = ref_meta %>%
  filter(subtype == 'HER2+')
print(sprintf('remain %d HER2+ cells', nrow(ref_meta)))
[1] "remain 19311 HER2+ cells"
table(ref_meta$orig.ident)

 CID3586  CID3838  CID3921  CID4066 CID45171 
    6178     2353     3024     5309     2447 
count = as.matrix(table(ref_meta$celltype_major))
colnames(count) = 'num'
Matrix::t(count)
    B-cells CAFs Cancer Epithelial Endothelial Myeloid Normal Epithelial Plasmablasts PVL T-cells
num     624 1449              1775        1016    1422               968          226 894   10937
ref_meta[1:5, 'celltype_major', drop=F]

Save cell type annotation of selected cells to file Breast_Cancer_ref_scRNA_cell_celltype.csv.

colnames(ref_meta)[colnames(ref_meta)=='celltype_major'] = 'celltype'
write.csv(ref_meta[, 'celltype', drop=F], 'Breast_Cancer_ref_scRNA_cell_celltype.csv')
print(sprintf('save cell type annotation of reference scRNA-seq cells into file %s', 'Breast_Cancer_ref_scRNA_cell_celltype.csv'))
[1] "save cell type annotation of reference scRNA-seq cells into file Breast_Cancer_ref_scRNA_cell_celltype.csv"

3.2 Read and preprocess scRNA-seq nUMI data

Original gene nUMI count data file count_matrix_sparse.mtx, count_matrix_barcodes.tsv and count_matrix_genes.tsv are in the compressed file GSE176078_Wu_etal_2021_BRCA_scRNASeq.tar.gz downloaded from GSE176078. It contains total 100,064 cells and 29,733 genes.

We just selected 19,311 HER2+ cells by barcodes, and discard other cells. NO filtering on genes, i.e. all 29,733 genes will be used for cell type deconvolution.

# https://support.10xgenomics.com/single-cell-gene-expression/software/pipelines/latest/output/matrices

barcode.path = file.path(home.dir, 'Wu_etal_2021_BRCA_scRNASeq', "count_matrix_barcodes.tsv")
features.path = file.path(home.dir, 'Wu_etal_2021_BRCA_scRNASeq', "count_matrix_genes.tsv")
matrix.path = file.path(home.dir, 'Wu_etal_2021_BRCA_scRNASeq', "count_matrix_sparse.mtx")

mat = Matrix::readMM(file = matrix.path)
feature.names = read.delim(features.path,
                           header = FALSE,
                           stringsAsFactors = FALSE)
barcode.names = read.delim(barcode.path,
                           header = FALSE,
                           stringsAsFactors = FALSE)
colnames(mat) = barcode.names$V1
row.names(mat) = feature.names$V1

print(sprintf('load data from %s', matrix.path))
[1] "load data from /home/hill103/Documents/SharedFolder/ToHost/CVAE-GLRM_Analysis/RealData/Breast_Cancer/Wu_etal_2021_BRCA_scRNASeq/count_matrix_sparse.mtx"
print(sprintf('total cells: %d; genes: %d', ncol(mat), nrow(mat)))
[1] "total cells: 100064; genes: 29733"
# select HER2+ cells
mat = mat[, row.names(ref_meta)]
print(sprintf('select cells: %d; genes: %d', ncol(mat), nrow(mat)))
[1] "select cells: 19311; genes: 29733"
# transpose it
mat = Matrix::t(mat)

Save scRNA-seq nUMI matrix to file Breast_Cancer_ref_scRNA_cell_nUMI.csv.gz

ref_data = as.data.frame(as.matrix(mat), check.names=F)
ref_data[1:5, 1:5]

data.table::fwrite(ref_data, 'Breast_Cancer_ref_scRNA_cell_nUMI.csv.gz', row.names = T)

Written 18.2% of 19311 rows in 2 secs using 4 threads. maxBuffUsed=2%. ETA 9 secs.      
Written 26.9% of 19311 rows in 3 secs using 4 threads. maxBuffUsed=2%. ETA 8 secs.      
Written 33.5% of 19311 rows in 4 secs using 4 threads. maxBuffUsed=2%. ETA 7 secs.      
Written 40.0% of 19311 rows in 5 secs using 4 threads. maxBuffUsed=2%. ETA 7 secs.      
Written 46.3% of 19311 rows in 6 secs using 4 threads. maxBuffUsed=2%. ETA 7 secs.      
Written 54.2% of 19311 rows in 7 secs using 4 threads. maxBuffUsed=2%. ETA 5 secs.      
Written 60.8% of 19311 rows in 8 secs using 4 threads. maxBuffUsed=2%. ETA 5 secs.      
Written 66.2% of 19311 rows in 9 secs using 4 threads. maxBuffUsed=2%. ETA 4 secs.      
Written 71.9% of 19311 rows in 10 secs using 4 threads. maxBuffUsed=2%. ETA 3 secs.      
Written 78.8% of 19311 rows in 11 secs using 4 threads. maxBuffUsed=2%. ETA 2 secs.      
Written 86.5% of 19311 rows in 12 secs using 4 threads. maxBuffUsed=2%. ETA 1 secs.      
Written 95.4% of 19311 rows in 13 secs using 4 threads. maxBuffUsed=2%. ETA 0 secs.      
                                                                                                                                     
print(sprintf('save nUMI matrix of reference scRNA-seq cells into gzip compressed file %s', 'Breast_Cancer_ref_scRNA_cell_nUMI.csv.gz'))
[1] "save nUMI matrix of reference scRNA-seq cells into gzip compressed file Breast_Cancer_ref_scRNA_cell_nUMI.csv.gz"
LS0tCnRpdGxlOiAiUHJlcHJvY2VzcyBIRVIyKyBCcmVhc2UgQ2FuY2VyIGRhdGEgZm9yIGNlbGwgdHlwZSBkZWNvbnZvbHV0aW9uIgphdXRob3I6ICJOaW5nc2hhbiBMaSAmIE5hdGluZyBXYW5nICYgWXVucWluZyBMaXUiCmRhdGU6ICIyMDIzLzA0LzE5IgpvdXRwdXQ6IAogIGh0bWxfbm90ZWJvb2s6CiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUKICAgIGhpZ2hsaWdodDogdGFuZ28KICAgIG51bWJlcl9zZWN0aW9uczogeWVzCiAgICB0aGVtZTogdW5pdGVkCiAgICB0b2M6IHllcwogICAgdG9jX2RlcHRoOiA2CiAgICB0b2NfZmxvYXQ6IHllcwotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIGV2YWwgPSBUUlVFLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSwgcmVzdWx0cz0naG9sZCcsIGZpZy53aWR0aCA9IDcsIGZpZy5oZWlnaHQgPSA1LCBkcGkgPSAzMDApCgoKbGlicmFyeShkcGx5cikKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KGlncmFwaCkKCmAlbm90aW4lYCA9IE5lZ2F0ZShgJWluJWApCgpzZXQuc2VlZCgxKQoKaG9tZS5kaXIgPSAnL2hvbWUvaGlsbDEwMy9Eb2N1bWVudHMvU2hhcmVkRm9sZGVyL1RvSG9zdC9DVkFFLUdMUk1fQW5hbHlzaXMvUmVhbERhdGEvQnJlYXN0X0NhbmNlcicKCgpteS5kaXN0aW5jdC5jb2xvcnMyMCA9IGMoIiNlNjE5NGIiLCAiIzNjYjQ0YiIsICIjZmZlMTE5IiwgIiM0MzYzZDgiLCAiI2Y1ODIzMSIsICIjOTExZWI0IiwgIiM0NmYwZjAiLCAiI2YwMzJlNiIsICIjYmNmNjBjIiwgIiNmYWJlYmUiLCAiIzAwODA4MCIsICIjOWE2MzI0IiwgIiM4MDAwMDAiLCAiI2FhZmZjMyIsICIjODA4MDAwIiwgIiMwMDAwNzUiLCAiIzgwODA4MCIsICIjZTZiZWZmIiwgIiNmZmQ4YjEiLCAiIzAwMDAwMCIpCgpteS5kaXN0aW5jdC5jb2xvcnM0MCA9IGMoIiMwMGZmMDAiLCIjZmY0NTAwIiwiIzAwY2VkMSIsIiM1NTZiMmYiLCIjYTA1MjJkIiwiIzhiMDAwMCIsIiM4MDgwMDAiLCIjNDgzZDhiIiwiIzAwODAwMCIsIiMwMDgwODAiLCIjNDY4MmI0IiwiIzAwMDA4MCIsIiM5YWNkMzIiLCIjZGFhNTIwIiwiIzdmMDA3ZiIsIiM4ZmJjOGYiLCIjYjAzMDYwIiwiI2QyYjQ4YyIsIiM2OTY5NjkiLCIjZmY4YzAwIiwiIzAwZmY3ZiIsIiNkYzE0M2MiLCIjZjRhNDYwIiwiIzAwMDBmZiIsIiNhMDIwZjAiLCIjYWRmZjJmIiwiI2ZmMDBmZiIsIiMxZTkwZmYiLCIjZjBlNjhjIiwiI2ZhODA3MiIsIiNmZmZmNTQiLCIjZGRhMGRkIiwiIzg3Y2VlYiIsIiM3YjY4ZWUiLCIjZWU4MmVlIiwiIzk4ZmI5OCIsIiM3ZmZmZDQiLCIjZmZiNmMxIiwiI2RjZGNkYyIsIiMwMDAwMDAiKQpgYGAKCgpJbiB0aGlzIFIgTm90ZWJvb2sgd2UgcHJlcHJvY2VzcyBzcGF0aWFsIGFuZCBjb3JyZXNwb25kaW5nIHJlZmVyZW5jZSBzY1JOQS1zZXEgZGF0YSBvZiBodW1hbiAqKkhFUjIrIEJyZWFzdCBDYW5jZXIqKiBmb3IgY2VsbCB0eXBlIGRlY29udm9sdXRpb24uCgoxLiAqKlNwYXRpYWwgZGF0YSBwcmVwcm9jZXNzaW5nKio6CgogICAgMS4xIElucHV0IG9yaWdpbmFsIGRhdGEgZmlsZXMKICAgIAogICAgKiBSYXcgblVNSSBvZiBzcGF0aWFsIHNwb3RzOiBgSDEudHN2Lmd6YCBpcyBmcm9tIHppcHBlZCBmaWxlIFtjb3VudC1tYXRyaWNlcy56aXBdKGh0dHBzOi8vemVub2RvLm9yZy9yZWNvcmQvNDc1MTYyNC9maWxlcy9jb3VudC1tYXRyaWNlcy56aXA/ZG93bmxvYWQ9MSkgZG93bmxvYWRlZCBmcm9tIFt6ZW5vZG8uNDc1MTYyNF0oaHR0cHM6Ly96ZW5vZG8ub3JnL3JlY29yZC80NzUxNjI0KSwgYW5kIHRoaXMgKipmaXJzdCBzZWN0aW9uIGZyb20gcGF0aWVudCBIKiogaXMgc2VsZWN0ZWQgZm9yIGFuYWx5c2lzLgogICAgCiAgICAxLjIgT3V0cHV0IGRhdGEgZmlsZXMgZm9yIGNlbGwgdHlwZSBkZWNvbnZvbHV0aW9uCiAgICAKICAgICogUmF3IG5VTUkgb2Ygc3BhdGlhbCBzcG90czogW0JyZWFzdF9DYW5jZXJfc3BhdGlhbF9zcG90X25VTUkuY3N2XShodHRwczovL2dpdGh1Yi5jb20vYXo3amgyL1NEZVBFUl9BbmFseXNpcy9ibG9iL21haW4vUmVhbERhdGEvQnJlYXN0X0NhbmNlci9CcmVhc3RfQ2FuY2VyX3NwYXRpYWxfc3BvdF9uVU1JLmNzdikuICoqTm8gZmlsdGVyaW5nIG9uIHNwb3RzIG9yIGdlbmVzKiosIGkuZS4gYWxsIHNwb3RzIGFuZCBnZW5lcyBhcmUgcHJlc2VydmVkLgogICAgKiBQaHlzaWNhbCBsb2NhdGlvbiBvZiBzcGF0aWFsIHNwb3RzOiBbQnJlYXN0X0NhbmNlcl9zcGF0aWFsX3Nwb3RfbG9jLmNzdl0oaHR0cHM6Ly9naXRodWIuY29tL2F6N2poMi9TRGVQRVJfQW5hbHlzaXMvYmxvYi9tYWluL1JlYWxEYXRhL0JyZWFzdF9DYW5jZXIvQnJlYXN0X0NhbmNlcl9zcGF0aWFsX3Nwb3RfbG9jLmNzdikuIFRoZSBzcGF0aWFsIGB4YCBhbmQgYHlgIGNvb3JkaW5hdGVzIGFyZSBkaXJlY3RseSBleHRyYWN0ZWQgZnJvbSBzcG90IG5hbWVzLgogICAgKiBBZGphY2VuY3kgTWF0cml4OiBbQnJlYXN0X0NhbmNlcl9zcGF0aWFsX3Nwb3RfYWRqYWNlbmN5X21hdHJpeC5jc3ZdKGh0dHBzOi8vZ2l0aHViLmNvbS9hejdqaDIvU0RlUEVSX0FuYWx5c2lzL2Jsb2IvbWFpbi9SZWFsRGF0YS9CcmVhc3RfQ2FuY2VyL0JyZWFzdF9DYW5jZXJfc3BhdGlhbF9zcG90X2FkamFjZW5jeV9tYXRyaXguY3N2KS4gU3BvdHMgd2l0aGluIG5laWdoYm9yaG9vZCBhcmUgYWRqYWNlbnQgKipsZWZ0KiosICoqcmlnaHQqKiwgKip0b3AqKiBhbmQgKipib3R0b20qKiBzcG90cy4KCgoyLiAqKlJlZmVyZW5jZSBzY1JOQS1zZXEgZGF0YSBwcmVwcm9jZXNzaW5nKio6CgogICAgMi4xIElucHV0IG9yaWdpbmFsIGRhdGEgZmlsZXMKICAgIAogICAgc2NSTkEtc2VxIGRhdGEgW0dTRTE3NjA3OF9XdV9ldGFsXzIwMjFfQlJDQV9zY1JOQVNlcS50YXIuZ3pdKGh0dHBzOi8vd3d3Lm5jYmkubmxtLm5paC5nb3YvZ2VvL2Rvd25sb2FkLz9hY2M9R1NFMTc2MDc4JmZvcm1hdD1maWxlJmZpbGU9R1NFMTc2MDc4JTVGV3UlNUZldGFsJTVGMjAyMSU1RkJSQ0ElNUZzY1JOQVNlcSUyRXRhciUyRWd6KSBhcmUgZG93bmxvYWRlZCBmcm9tIFtHU0UxNzYwNzhdKGh0dHBzOi8vd3d3Lm5jYmkubmxtLm5paC5nb3YvZ2VvL3F1ZXJ5L2FjYy5jZ2k/YWNjPUdTRTE3NjA3OCkuIFRoaXMgY29tcHJlc3NlZCBmaWxlIGNvbnRhaW5zIDQgZmlsZXM6CiAgICAKICAgICogYGNvdW50X21hdHJpeF9zcGFyc2UubXR4YCwgYGNvdW50X21hdHJpeF9iYXJjb2Rlcy50c3ZgIGFuZCBgY291bnRfbWF0cml4X2dlbmVzLnRzdmA6IFJhdyBuVU1JIG9mIGFsbCAxMDAsMDY0IGNlbGxzIGFuZCAyOSw3MzMgZ2VuZXMKICAgIAogICAgKiBgbWV0YWRhdGEuY3N2YDogbWV0YSBkYXRhIG9mIGFsbCAxMDAsMDY0IGNlbGxzCiAgICAKICAgIDIuMiBPdXRwdXQgZGF0YSBmaWxlcyBmb3IgY2VsbCB0eXBlIGRlY29udm9sdXRpb24KICAgIAogICAgV2Ugc2VsZWN0ICoqMTksMzExIEhFUjIrIGNlbGxzKiouICoqTk8gZmlsdGVyaW5nIG9uIGdlbmVzKiosIGkuZS4gYWxsIGdlbmVzIGFyZSBpbmNsdWRlZCBmb3IgYW5hbHlzaXMuCiAgICAKICAgICogUmF3IG5VTUkgb2YgMTksMzExIGNlbGxzIHdpdGggOSBjZWxsIHR5cGVzIGFuZCAyOSw3MzMgZ2VuZXM6IFtCcmVhc3RfQ2FuY2VyX3JlZl9zY1JOQV9jZWxsX25VTUkuY3N2Lmd6XShodHRwczovL2dpdGh1Yi5jb20vYXo3amgyL1NEZVBFUl9BbmFseXNpcy9ibG9iL21haW4vUmVhbERhdGEvQnJlYXN0X0NhbmNlci9CcmVhc3RfQ2FuY2VyX3JlZl9zY1JOQV9jZWxsX25VTUkuY3N2Lmd6KS4KICAgIAogICAgKiBDZWxsIHR5cGUgYW5ub3RhdGlvbiBmb3IgdGhvc2UgMTksMzExIGNlbGxzOiBbQnJlYXN0X0NhbmNlcl9yZWZfc2NSTkFfY2VsbF9jZWxsdHlwZS5jc3ZdKGh0dHBzOi8vZ2l0aHViLmNvbS9hejdqaDIvU0RlUEVSX0FuYWx5c2lzL2Jsb2IvbWFpbi9SZWFsRGF0YS9CcmVhc3RfQ2FuY2VyL0JyZWFzdF9DYW5jZXJfcmVmX3NjUk5BX2NlbGxfY2VsbHR5cGUuY3N2KS4uCgoKCiMgVmVyc2lvbgoKYGBge3J9CnZlcnNpb25bWyd2ZXJzaW9uLnN0cmluZyddXQpgYGAKCgojIFByb3Byb2Nlc3MgQnJlYXN0IENhbmNlciBzcGF0aWFsIGRhdGFzZXQKCiMjIFJlYWQgb3JpZ2luYWwgZGF0YSBmaWxlIGBIMS50c3YuZ3pgCgpgYGB7cn0KZmlsZV9uYW1lID0gZmlsZS5wYXRoKGhvbWUuZGlyLCAnSDEudHN2Lmd6JykKb3JnX2RhdGEgPSBkYXRhLnRhYmxlOjpmcmVhZChmaWxlX25hbWUsIHNlcCA9ICJcdCIsIGNoZWNrLm5hbWVzID0gRkFMU0UpCnByaW50KHNwcmludGYoJ2xvYWQgZGF0YSBmcm9tICVzJywgZmlsZV9uYW1lKSkKCm9yZ19kYXRhID0gYXMuZGF0YS5mcmFtZShvcmdfZGF0YSkKIyBleHRyYWN0IGZpcnN0IGNvbHVtbiBhcyByb3cgbmFtZQpyb3cubmFtZXMob3JnX2RhdGEpID0gb3JnX2RhdGEkVjEKb3JnX2RhdGEgPSBvcmdfZGF0YVssIDI6bmNvbChvcmdfZGF0YSldCgpwcmludChzcHJpbnRmKCdzcG90czogJWQ7IGdlbmVzOiAlZCcsIG5yb3cob3JnX2RhdGEpLCBuY29sKG9yZ19kYXRhKSkpCm9yZ19kYXRhWzE6NSwgMTo1XQpgYGAKCgoKIyMgU2F2ZSBmaWxlcyBmb3IgZGVjb252b2x1dGlvbgoKIyMjIFNwYXRpYWwgc3BvdCBuVU1JCgoqKk5vIGZpbHRlcmluZyBvbiBzcG90cyBvciBnZW5lcyoqLCBkaXJlY3RseSBzYXZlIGFsbCBzcG90cyBhbmQgZ2VuZXMgaW50byBmaWxlIFtCcmVhc3RfQ2FuY2VyX3NwYXRpYWxfc3BvdF9uVU1JLmNzdl0oaHR0cHM6Ly9naXRodWIuY29tL2F6N2poMi9TRGVQRVJfQW5hbHlzaXMvYmxvYi9tYWluL1JlYWxEYXRhL0JyZWFzdF9DYW5jZXIvQnJlYXN0X0NhbmNlcl9zcGF0aWFsX3Nwb3RfblVNSS5jc3YpLiAqKlJvd3MgYXMgc3BhdGlhbCBzcG90cyBhbmQgY29sdW1ucyBhcyBnZW5lcyoqLgoKYGBge3J9CndyaXRlLmNzdihvcmdfZGF0YSwgJ0JyZWFzdF9DYW5jZXJfc3BhdGlhbF9zcG90X25VTUkuY3N2JykKcHJpbnQoc3ByaW50Zignc2F2ZSAlZCBnZW5lIG5VTUlzIG9mICVkIHNwYXRpYWwgc3BvdHMgaW50byBmaWxlICVzJywgbmNvbChvcmdfZGF0YSksIG5yb3cob3JnX2RhdGEpLCAnQnJlYXN0X0NhbmNlcl9zcGF0aWFsX3Nwb3RfblVNSS5jc3YnKSkKYGBgCgoKIyMjIFBoeXNpY2FsIExvY2F0aW9ucyBvZiBzcGF0aWFsIHNwb3RzCgpEaXJlY3RseSBleHRyYWN0IHRoZSBzcGF0aWFsIGB4YCBhbmQgYHlgIGNvb3JkaW5hdGVzIGZyb20gc3BvdCBuYW1lcywgdGhlbiBzYXZlIGl0IGludG8gZmlsZSBbQnJlYXN0X0NhbmNlcl9zcGF0aWFsX3Nwb3RfbG9jLmNzdl0oaHR0cHM6Ly9naXRodWIuY29tL2F6N2poMi9TRGVQRVJfQW5hbHlzaXMvYmxvYi9tYWluL1JlYWxEYXRhL0JyZWFzdF9DYW5jZXIvQnJlYXN0X0NhbmNlcl9zcGF0aWFsX3Nwb3RfbG9jLmNzdikuCgoKYGBge3J9CmxvY2FsX2RmID0gZGF0YS5mcmFtZShuYW1lcyA9IHJvdy5uYW1lcyhvcmdfZGF0YSksIHJvdy5uYW1lcyA9IHJvdy5uYW1lcyhvcmdfZGF0YSkpCmxvY2FsX2RmID0gbG9jYWxfZGYgJT4lCiAgdGlkeXI6OnNlcGFyYXRlX3dpZGVyX2RlbGltKG5hbWVzLCAneCcsIG5hbWVzID0gYygneCcsICd5JykpCmxvY2FsX2RmID0gYXMuZGF0YS5mcmFtZShsb2NhbF9kZikKcm93Lm5hbWVzKGxvY2FsX2RmKSA9IHJvdy5uYW1lcyhvcmdfZGF0YSkKCmxvY2FsX2RmWyd4J10gPSBhcy5udW1lcmljKGxvY2FsX2RmJHgpCmxvY2FsX2RmWyd5J10gPSBhcy5udW1lcmljKGxvY2FsX2RmJHkpCgpsb2NhbF9kZlsxOjUsIF0KCndyaXRlLmNzdihsb2NhbF9kZiwgJ0JyZWFzdF9DYW5jZXJfc3BhdGlhbF9zcG90X2xvYy5jc3YnKQpwcmludChzcHJpbnRmKCdzYXZlIFBoeXNpY2FsIExvY2F0aW9ucyBvZiBzcGF0aWFsIHNwb3RzIGludG8gZmlsZSAlcycsICdCcmVhc3RfQ2FuY2VyX3NwYXRpYWxfc3BvdF9sb2MuY3N2JykpCmBgYAoKCiMjIyBBZGphY2VuY3kgTWF0cml4IG9mIHNwYXRpYWwgc3BvdHMKCldlIGRlZmluZSB0aGUgbmVpZ2hib3Job29kIG9mIGEgc3BhdGlhbCBzcG90IGNvbnRhaW5zIHRoZSBhZGphY2VudCAqKmxlZnQqKiwgKipyaWdodCoqLCAqKnRvcCoqIGFuZCAqKmJvdHRvbSoqIHNwb3QsIHRoYXQgaXMsIG9uZSBzcG90IGhhcyBhdCBtb3N0IDQgbmVpZ2hib3JzLgoKVGhlIGdlbmVyYXRlZCBBZGphY2VuY3kgTWF0cml4IGBBYCBvbmx5IGNvbnRhaW5zICoqMSoqIGFuZCAqKjAqKiwgd2hlcmUgMSByZXByZXNlbnRzIGNvcnJlc3BvbmRpbmcgdHdvIHNwb3RzIGFyZSBhZGphY2VudCBzcG90cyBhY2NvcmRpbmcgdG8gdGhlIGRlZmluaXRpb24gb2YgbmVpZ2hib3Job29kLCB3aGlsZSB2YWx1ZSAwIGZvciBub24tYWRqYWNlbnQgc3BvdHMuIE5vdGUgKiphbGwgZGlhZ29uYWwgZW50cmllcyBhcmUgMHMqKi4KCkFkamFjZW5jeSBNYXRyaXggYXJlIHNhdmVkIGludG8gZmlsZSBbQnJlYXN0X0NhbmNlcl9zcGF0aWFsX3Nwb3RfYWRqYWNlbmN5X21hdHJpeC5jc3ZdKGh0dHBzOi8vZ2l0aHViLmNvbS9hejdqaDIvU0RlUEVSX0FuYWx5c2lzL2Jsb2IvbWFpbi9SZWFsRGF0YS9CcmVhc3RfQ2FuY2VyL0JyZWFzdF9DYW5jZXJfc3BhdGlhbF9zcG90X2FkamFjZW5jeV9tYXRyaXguY3N2KS4KCmBgYHtyfQpnZXROZWlnaGJvdXIgPSBmdW5jdGlvbihhcnJheV9yb3csIGFycmF5X2NvbCkgewogICMgYmFzZWQgb24gdGhlIChyb3csIGNvbCkgb2Ygb25lIHNwb3QsIHJldHVybiB0aGUgKHJvdywgY29sKSBvZiBhbGwgNCBuZWlnaGJvdXJzCiAgcmV0dXJuKGxpc3QoYyhhcnJheV9yb3ctMSwgYXJyYXlfY29sKSwKICAgICAgICAgICAgICBjKGFycmF5X3JvdysxLCBhcnJheV9jb2wpLAogICAgICAgICAgICAgIGMoYXJyYXlfcm93KzAsIGFycmF5X2NvbC0xKSwKICAgICAgICAgICAgICBjKGFycmF5X3JvdyswLCBhcnJheV9jb2wrMSkpKQp9CgojIGFkamFjZW5jeSBtYXRyaXgKQSA9IG1hdHJpeCgwLCBucm93ID0gbnJvdyhsb2NhbF9kZiksIG5jb2wgPSBucm93KGxvY2FsX2RmKSkKcm93Lm5hbWVzKEEpID0gcm93bmFtZXMobG9jYWxfZGYpCmNvbG5hbWVzKEEpID0gcm93bmFtZXMobG9jYWxfZGYpCmZvciAoaSBpbiAxOm5yb3cobG9jYWxfZGYpKSB7CiAgYmFyY29kZSA9IHJvd25hbWVzKGxvY2FsX2RmKVtpXQogIGFycmF5X3JvdyA9IGxvY2FsX2RmW2ksICd5J10KICBhcnJheV9jb2wgPSBsb2NhbF9kZltpLCAneCddCiAgCiAgIyBnZXQgbmVpZ2hib3JzCiAgbmVpZ2hib3VycyA9IGdldE5laWdoYm91cihhcnJheV9yb3csIGFycmF5X2NvbCkKICAKICAjIGZpbGwgdGhlIGFkamFjZW5jeSBtYXRyaXgKICBmb3IgKHRoaXMudmVjIGluIG5laWdoYm91cnMpIHsKICAgIHRtcC5wID0gcm93bmFtZXMobG9jYWxfZGZbbG9jYWxfZGYkeT09dGhpcy52ZWNbMV0gJiBsb2NhbF9kZiR4PT10aGlzLnZlY1syXSwgXSkKICAgIAogICAgaWYgKGxlbmd0aCh0bXAucCkgPj0gMSkgewogICAgICAjIHRhcmdldCBzcG90cyBoYXZlIG5laWdoYm9ycyBpbiBzZWxlY3RlZCBzcG90cwogICAgICBmb3IgKG5laWdoLmJhcmNvZGUgaW4gdG1wLnApIHsKICAgICAgICBBW2JhcmNvZGUsIG5laWdoLmJhcmNvZGVdID0gMQogICAgICB9CiAgICB9CiAgfQp9CgpBWzE6NSwgMTo1XQp3cml0ZS5jc3YoQSwgJ0JyZWFzdF9DYW5jZXJfc3BhdGlhbF9zcG90X2FkamFjZW5jeV9tYXRyaXguY3N2JykKcHJpbnQoc3ByaW50Zignc2F2ZSBBZGphY2VuY3kgTWF0cml4IG9mIHNwYXRpYWwgc3BvdHMgaW50byBmaWxlICVzJywgJ0JyZWFzdF9DYW5jZXJfc3BhdGlhbF9zcG90X2FkamFjZW5jeV9tYXRyaXguY3N2JykpCmBgYAoKUGxvdCBBZGphY2VuY3kgTWF0cml4LiBFYWNoIG5vZGUgaXMgc3BvdCwgc3BvdHMgd2l0aGluIG5laWdoYm9yaG9vZCBhcmUgY29ubmVjdGVkIHdpdGggZWRnZXMuCgpOb3RlICoqbXVsdGlwbHkgdGhlIHkgY29vcmRpbmF0ZWQgd2l0aCAtMSoqIHRvIG1hdGNoIHRoZSBmaWd1cmUgd2l0aCBIJkUgc3RhaW5pbmcgaW1hZ2UKCmBgYHtyLCBmaWcud2lkdGg9MTIsIGZpZy5oZWlnaHQ9MTJ9CmcgPSBncmFwaF9mcm9tX2FkamFjZW5jeV9tYXRyaXgoQSwgJ3VuZGlyZWN0ZWQnLCBhZGQuY29sbmFtZXMgPSBOQSwgYWRkLnJvd25hbWVzID0gTkEpCiMgbWFudWFsbHkgc2V0IG5vZGVzIHggYW5kIHkgY29vcmRpbmF0ZXMKdmVydGV4X2F0dHIoZywgbmFtZSA9ICd4JykgPSBsb2NhbF9kZiR4CnZlcnRleF9hdHRyKGcsIG5hbWUgPSAneScpID0gLShsb2NhbF9kZiR5KQpwbG90KGcsIHZlcnRleC5zaXplPTUsIGVkZ2Uud2lkdGg9NCwgbWFyZ2luPS0wLjA1KQpgYGAKCgojIFByZXByb2Nlc3MgcmVmZXJlbmNlIHNjUk5BLXNlcSBkYXRhCgojIyBSZWFkIGFuZCBwcmVwcm9jZXNzIHNjUk5BLXNlcSBtZXRhIGRhdGEKCk9yaWdpbmFsIG1ldGEgZGF0YSBmaWxlIGBtZXRhZGF0YS5jc3ZgIGlzIGluIHRoZSBjb21wcmVzc2VkIGZpbGUgW0dTRTE3NjA3OF9XdV9ldGFsXzIwMjFfQlJDQV9zY1JOQVNlcS50YXIuZ3pdKGh0dHBzOi8vd3d3Lm5jYmkubmxtLm5paC5nb3YvZ2VvL2Rvd25sb2FkLz9hY2M9R1NFMTc2MDc4JmZvcm1hdD1maWxlJmZpbGU9R1NFMTc2MDc4JTVGV3UlNUZldGFsJTVGMjAyMSU1RkJSQ0ElNUZzY1JOQVNlcSUyRXRhciUyRWd6KSBkb3dubG9hZGVkIGZyb20gW0dTRTE3NjA3OF0oaHR0cHM6Ly93d3cubmNiaS5ubG0ubmloLmdvdi9nZW8vcXVlcnkvYWNjLmNnaT9hY2M9R1NFMTc2MDc4KS4KCkl0IGNvbnRhaW5zIG1ldGEgZGF0YSBvZiAxMDAsMDY0IGNlbGxzIGZyb20gaHVtYW4gYnJlYXN0IGNhbmNlciB0aXNzdWUuIFdlIHNlbGVjdCBjZWxscyBmcm9tICoqSEVSMisqKiBzdWJqZWN0cyAoYHN1YnR5cGU9SEVSMitgKSwgYW5kICoqMTksMzExIGNlbGxzKiogZnJvbSA1IHN1YmplY3RzIGFyZSBzZWxlY3RlZC4KClRoZSBjZWxsIHR5cGUgYW5ub3RhdGlvbiBpcyBzdG9yZWQgaW4gY29sdW1uIGBjZWxsdHlwZV9tYWpvcmAsIHdoaWNoIGluY2x1ZGVzIHRvdGFsIDkgZGlzdGluY3QgYW5ub3RhdGlvbnMuCgoKYGBge3J9CmZpbGVfbmFtZSA9IGZpbGUucGF0aChob21lLmRpciwgJ1d1X2V0YWxfMjAyMV9CUkNBX3NjUk5BU2VxJywgJ21ldGFkYXRhLmNzdicpCnJlZl9tZXRhID0gcmVhZC5jc3YoZmlsZV9uYW1lLCBzZXA9JywnLCBjaGVjay5uYW1lcyA9IEYsIGhlYWRlciA9IFQsIHJvdy5uYW1lcyA9IDEpCnByaW50KHNwcmludGYoJ2xvYWQgZGF0YSBmcm9tICVzJywgZmlsZV9uYW1lKSkKcHJpbnQoc3ByaW50ZigndG90YWwgJWQgY2VsbHMgd2l0aCBkaXN0aW5jdCAlZCBjZWxsIHR5cGUgYW5ub3RhdGlvbnMnLCBucm93KHJlZl9tZXRhKSwgbGVuZ3RoKHVuaXF1ZShyZWZfbWV0YSRjZWxsdHlwZV9tYWpvcikpKSkKCiMgc2VsZWN0IEhFUjIrIGNlbGxzCnJlZl9tZXRhID0gcmVmX21ldGEgJT4lCiAgZmlsdGVyKHN1YnR5cGUgPT0gJ0hFUjIrJykKcHJpbnQoc3ByaW50ZigncmVtYWluICVkIEhFUjIrIGNlbGxzJywgbnJvdyhyZWZfbWV0YSkpKQoKdGFibGUocmVmX21ldGEkb3JpZy5pZGVudCkKCmNvdW50ID0gYXMubWF0cml4KHRhYmxlKHJlZl9tZXRhJGNlbGx0eXBlX21ham9yKSkKY29sbmFtZXMoY291bnQpID0gJ251bScKTWF0cml4Ojp0KGNvdW50KQoKcmVmX21ldGFbMTo1LCAnY2VsbHR5cGVfbWFqb3InLCBkcm9wPUZdCmBgYAoKClNhdmUgY2VsbCB0eXBlIGFubm90YXRpb24gb2Ygc2VsZWN0ZWQgY2VsbHMgdG8gZmlsZSBbQnJlYXN0X0NhbmNlcl9yZWZfc2NSTkFfY2VsbF9jZWxsdHlwZS5jc3ZdKGh0dHBzOi8vZ2l0aHViLmNvbS9hejdqaDIvU0RlUEVSX0FuYWx5c2lzL2Jsb2IvbWFpbi9SZWFsRGF0YS9CcmVhc3RfQ2FuY2VyL0JyZWFzdF9DYW5jZXJfcmVmX3NjUk5BX2NlbGxfY2VsbHR5cGUuY3N2KS4KCmBgYHtyfQpjb2xuYW1lcyhyZWZfbWV0YSlbY29sbmFtZXMocmVmX21ldGEpPT0nY2VsbHR5cGVfbWFqb3InXSA9ICdjZWxsdHlwZScKd3JpdGUuY3N2KHJlZl9tZXRhWywgJ2NlbGx0eXBlJywgZHJvcD1GXSwgJ0JyZWFzdF9DYW5jZXJfcmVmX3NjUk5BX2NlbGxfY2VsbHR5cGUuY3N2JykKcHJpbnQoc3ByaW50Zignc2F2ZSBjZWxsIHR5cGUgYW5ub3RhdGlvbiBvZiByZWZlcmVuY2Ugc2NSTkEtc2VxIGNlbGxzIGludG8gZmlsZSAlcycsICdCcmVhc3RfQ2FuY2VyX3JlZl9zY1JOQV9jZWxsX2NlbGx0eXBlLmNzdicpKQpgYGAKCgojIyBSZWFkIGFuZCBwcmVwcm9jZXNzIHNjUk5BLXNlcSBuVU1JIGRhdGEKCk9yaWdpbmFsIGdlbmUgblVNSSBjb3VudCBkYXRhIGZpbGUgYGNvdW50X21hdHJpeF9zcGFyc2UubXR4YCwgYGNvdW50X21hdHJpeF9iYXJjb2Rlcy50c3ZgIGFuZCBgY291bnRfbWF0cml4X2dlbmVzLnRzdmAgYXJlIGluIHRoZSBjb21wcmVzc2VkIGZpbGUgW0dTRTE3NjA3OF9XdV9ldGFsXzIwMjFfQlJDQV9zY1JOQVNlcS50YXIuZ3pdKGh0dHBzOi8vd3d3Lm5jYmkubmxtLm5paC5nb3YvZ2VvL2Rvd25sb2FkLz9hY2M9R1NFMTc2MDc4JmZvcm1hdD1maWxlJmZpbGU9R1NFMTc2MDc4JTVGV3UlNUZldGFsJTVGMjAyMSU1RkJSQ0ElNUZzY1JOQVNlcSUyRXRhciUyRWd6KSBkb3dubG9hZGVkIGZyb20gW0dTRTE3NjA3OF0oaHR0cHM6Ly93d3cubmNiaS5ubG0ubmloLmdvdi9nZW8vcXVlcnkvYWNjLmNnaT9hY2M9R1NFMTc2MDc4KS4gSXQgY29udGFpbnMgdG90YWwgMTAwLDA2NCBjZWxscyBhbmQgMjksNzMzIGdlbmVzLgoKV2UganVzdCBzZWxlY3RlZCAxOSwzMTEgSEVSMisgY2VsbHMgYnkgYmFyY29kZXMsIGFuZCBkaXNjYXJkIG90aGVyIGNlbGxzLiAqKk5PIGZpbHRlcmluZyBvbiBnZW5lcyoqLCBpLmUuIGFsbCAyOSw3MzMgZ2VuZXMgd2lsbCBiZSB1c2VkIGZvciBjZWxsIHR5cGUgZGVjb252b2x1dGlvbi4KCmBgYHtyfQojIGh0dHBzOi8vc3VwcG9ydC4xMHhnZW5vbWljcy5jb20vc2luZ2xlLWNlbGwtZ2VuZS1leHByZXNzaW9uL3NvZnR3YXJlL3BpcGVsaW5lcy9sYXRlc3Qvb3V0cHV0L21hdHJpY2VzCgpiYXJjb2RlLnBhdGggPSBmaWxlLnBhdGgoaG9tZS5kaXIsICdXdV9ldGFsXzIwMjFfQlJDQV9zY1JOQVNlcScsICJjb3VudF9tYXRyaXhfYmFyY29kZXMudHN2IikKZmVhdHVyZXMucGF0aCA9IGZpbGUucGF0aChob21lLmRpciwgJ1d1X2V0YWxfMjAyMV9CUkNBX3NjUk5BU2VxJywgImNvdW50X21hdHJpeF9nZW5lcy50c3YiKQptYXRyaXgucGF0aCA9IGZpbGUucGF0aChob21lLmRpciwgJ1d1X2V0YWxfMjAyMV9CUkNBX3NjUk5BU2VxJywgImNvdW50X21hdHJpeF9zcGFyc2UubXR4IikKCm1hdCA9IE1hdHJpeDo6cmVhZE1NKGZpbGUgPSBtYXRyaXgucGF0aCkKZmVhdHVyZS5uYW1lcyA9IHJlYWQuZGVsaW0oZmVhdHVyZXMucGF0aCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgaGVhZGVyID0gRkFMU0UsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkKYmFyY29kZS5uYW1lcyA9IHJlYWQuZGVsaW0oYmFyY29kZS5wYXRoLAogICAgICAgICAgICAgICAgICAgICAgICAgICBoZWFkZXIgPSBGQUxTRSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQpjb2xuYW1lcyhtYXQpID0gYmFyY29kZS5uYW1lcyRWMQpyb3cubmFtZXMobWF0KSA9IGZlYXR1cmUubmFtZXMkVjEKCnByaW50KHNwcmludGYoJ2xvYWQgZGF0YSBmcm9tICVzJywgbWF0cml4LnBhdGgpKQpwcmludChzcHJpbnRmKCd0b3RhbCBjZWxsczogJWQ7IGdlbmVzOiAlZCcsIG5jb2wobWF0KSwgbnJvdyhtYXQpKSkKCgojIHNlbGVjdCBIRVIyKyBjZWxscwptYXQgPSBtYXRbLCByb3cubmFtZXMocmVmX21ldGEpXQpwcmludChzcHJpbnRmKCdzZWxlY3QgY2VsbHM6ICVkOyBnZW5lczogJWQnLCBuY29sKG1hdCksIG5yb3cobWF0KSkpCgojIHRyYW5zcG9zZSBpdAptYXQgPSBNYXRyaXg6OnQobWF0KQpgYGAKCgpTYXZlIHNjUk5BLXNlcSBuVU1JIG1hdHJpeCB0byBmaWxlIFtCcmVhc3RfQ2FuY2VyX3JlZl9zY1JOQV9jZWxsX25VTUkuY3N2Lmd6XShodHRwczovL2dpdGh1Yi5jb20vYXo3amgyL1NEZVBFUl9BbmFseXNpcy9ibG9iL21haW4vUmVhbERhdGEvQnJlYXN0X0NhbmNlci9CcmVhc3RfQ2FuY2VyX3JlZl9zY1JOQV9jZWxsX25VTUkuY3N2Lmd6KQoKYGBge3J9CnJlZl9kYXRhID0gYXMuZGF0YS5mcmFtZShhcy5tYXRyaXgobWF0KSwgY2hlY2submFtZXM9RikKcmVmX2RhdGFbMTo1LCAxOjVdCgpkYXRhLnRhYmxlOjpmd3JpdGUocmVmX2RhdGEsICdCcmVhc3RfQ2FuY2VyX3JlZl9zY1JOQV9jZWxsX25VTUkuY3N2Lmd6Jywgcm93Lm5hbWVzID0gVCkKcHJpbnQoc3ByaW50Zignc2F2ZSBuVU1JIG1hdHJpeCBvZiByZWZlcmVuY2Ugc2NSTkEtc2VxIGNlbGxzIGludG8gZ3ppcCBjb21wcmVzc2VkIGZpbGUgJXMnLCAnQnJlYXN0X0NhbmNlcl9yZWZfc2NSTkFfY2VsbF9uVU1JLmNzdi5neicpKQpgYGAKCgo=