This notebook looks at cell type annotations and gene set expression across all samples in SCPCP00015 and assigns “final” annotations. To do this we are using the merged, but not batch-corrected, object containing the gene expression data for all samples.

The following input is used:

Setup

suppressPackageStartupMessages({
  # load required packages
  library(SingleCellExperiment)
  library(ggplot2)
})

# Set default ggplot theme
theme_set(
  theme_classic()
)

# set seed
set.seed(2024)

# quiet messages
options(readr.show_col_types = FALSE)
ComplexHeatmap::ht_opt(message = FALSE)
# The base path for the OpenScPCA repository, found by its (hidden) .git directory
repository_base <- rprojroot::find_root(rprojroot::is_git_root)

# The current data directory, found within the repository base directory
data_dir <- file.path(repository_base, "data", "current", "results", "merge-sce", "SCPCP000015")

# The path to this module
module_base <- file.path(repository_base, "analyses", "cell-type-ewings") 
# path to sce 
sce_file <- file.path(data_dir, "SCPCP000015_merged.rds")

# path to workflow results
workflow_results_dir <- file.path(module_base, "results")

singler_results_dir <- file.path(workflow_results_dir, "aucell_singler_annotation")
singler_results_files <- list.files(singler_results_dir, pattern = "*singler-classifications.tsv", full.names = TRUE, recursive = TRUE)
library_ids <- stringr::str_remove(basename(singler_results_files), "_singler-classifications.tsv")


aucell_results_dir <- file.path(workflow_results_dir, "aucell-ews-signatures")
aucell_results_file <- file.path(aucell_results_dir, "SCPCP000015_auc-ews-gene-signatures.tsv")

consensus_results_dir <- file.path(repository_base, "analyses", "cell-type-consensus", "results", "cell-type-consensus", "SCPCP000015")
consensus_results_files <- list.files(consensus_results_dir, pattern = "*_consensus-cell-type-assignments.tsv.gz", full.names = TRUE, recursive = TRUE)

# small gene sets
visser_marker_genes_file <- file.path(module_base, "references", "visser-all-marker-genes.tsv")
cell_state_genes_file <- file.path(module_base, "references", "tumor-cell-state-markers.tsv")

# marker genes to be used for validating assignments 
validation_markers_file <- file.path(module_base, "references", "combined-validation-markers.tsv")
# output file to save final annotations 
results_dir <- file.path(module_base, "results", "final-annotations")
fs::dir_create(results_dir)
output_file <- file.path(results_dir, "SCPCP000015_celltype-annotations.tsv.gz")
# source in setup functions prep_results()
setup_functions <- file.path(module_base, "template_notebooks", "utils", "setup-functions.R")
source(setup_functions)

# source in validation functions 
# calculate_mean_markers(), plot_faceted_umap()
validation_functions <- file.path(module_base, "scripts", "utils", "tumor-validation-helpers.R")
source(validation_functions)

# source in plotting functions 
# expression_umap(), cluster_density_plot(), and annotated_exp_heatmap()
plotting_functions <- file.path(module_base, "template_notebooks", "utils", "plotting-functions.R")
source(plotting_functions)

# source jaccard functions plot_jaccard()
jaccard_functions <- file.path(module_base, "scripts", "utils", "jaccard-functions.R")
source(jaccard_functions)
stopifnot(
  "sce file does not exist" = file.exists(sce_file),
  "aucell results file does not exist" = file.exists(aucell_results_file), 
  "at least one singler file is missing" = all(file.exists(singler_results_files)),
  "at least one consensus file is missing" = all(file.exists(consensus_results_files))
)
# read in sce
sce <- readr::read_rds(sce_file)

# read in workflow results
singler_df <- singler_results_files |> 
  purrr::set_names(library_ids) |>
  purrr::map(readr::read_tsv) |> 
  dplyr::bind_rows(.id = "library_id")

aucell_df <- readr::read_tsv(aucell_results_file) |> 
  tidyr::separate(barcodes, "-", into = c("library_id", "barcodes"))

# consensus cell types 
consensus_df <- consensus_results_files |> 
  purrr::map(readr::read_tsv) |> 
  dplyr::bind_rows()

# read in marker genes and combine into one list 
visser_markers_df <- readr::read_tsv(visser_marker_genes_file) |> 
  dplyr::select(cell_type, ensembl_gene_id, gene_symbol) |> 
  unique()
  
cell_state_markers_df <- readr::read_tsv(cell_state_genes_file) |> 
  dplyr::select(cell_type = cell_state, ensembl_gene_id, gene_symbol)

all_markers_df <- dplyr::bind_rows(list(visser_markers_df, cell_state_markers_df))

Prepare data for plotting

all_results_df <- prep_results(
  sce, 
  singler_df = singler_df, 
  cluster_df = NULL, 
  aucell_df = aucell_df,
  consensus_df = consensus_df,
  cluster_nn = params$cluster_nn,
  cluster_res = params$cluster_res,
  join_columns = c("barcodes", "library_id")
  ) |>
  dplyr::mutate(barcodes = glue::glue("{library_id}-{barcodes}"))
  
cell_types <- unique(all_markers_df$cell_type)

# get the mean expression of all genes for each cell state
gene_exp_df <- cell_types |>
  purrr::map(\(type){
    calculate_mean_markers(all_markers_df, sce, type, cell_type)
  }) |>
  purrr::reduce(dplyr::inner_join, by = "barcodes")

all_info_df <- all_results_df |> 
  dplyr::left_join(gene_exp_df, by = "barcodes") |> 
  dplyr::mutate(
    singler_annotation = dplyr::if_else(is.na(singler_annotation), "unknown", singler_annotation)
  )

Combine classifications from SingleR and consensus cell types

The first thing we will do is compare cell type annotations obtained by each method, SingleR with tumor cells as a reference (output by aucell-singler-annotation.sh) and consensus annotations (output from assign-consensus-celltypes.sh in cell-type-consensus.

Let’s see how similar these annotations are and see if we can create a combined annotation. Note that SingleR will label tumor cells but the consensus cell types only label normal cells. Consensus cell types are observed when cell types from SingleR and CellAssign (using only normal tissue references) share a common ancestor. If no consensus is found, the cells are labeled with “Unknown”.

Any cells that are unable to be labeled via consensus cell types are labeled as “Unknown” and I expect these will line up with cells labeled as tumor cells by SingleR.

# get cell types that have at least 50 cells
singler_to_keep <- all_info_df |> 
  dplyr::count(singler_annotation) |>
  dplyr::filter(n >= 50) |> 
  dplyr::pull(singler_annotation)

# first get jaccard index between consensus and singler
jaccard_mtx <- all_info_df |> 
  dplyr::filter(singler_annotation %in% singler_to_keep) |> 
  make_jaccard_matrix(
    "consensus_annotation",
    "singler_annotation"
  )

ComplexHeatmap::Heatmap(
  t(jaccard_mtx),
  col = circlize::colorRamp2(c(0, 1), colors = c("white", "darkslateblue")),
  border = TRUE,
  ## Row parameters
  cluster_rows = TRUE,
  row_title = "SingleR with tumor reference",
  row_title_gp = grid::gpar(fontsize = 12),
  row_title_side = "left",
  row_names_side = "left",
  row_dend_side = "right",
  row_names_gp = grid::gpar(fontsize = 12),
  ## Column parameters
  cluster_columns = TRUE,
  column_title = "Consensus",
  column_title_gp = grid::gpar(fontsize = 12),
  column_names_side = "bottom",
  column_names_gp = grid::gpar(fontsize = 12),
  column_names_rot = 90,
  ## Legend parameters
  heatmap_legend_param = list(
    title = "Jaccard index",
    direction = "vertical",
    legend_width = unit(1.5, "in")
  ))

It looks like generally these two cell type annotations are in agreement. We also see that most of the “Unknown” cells as denoted by consensus annotations are in fact lining up with tumor cells. It looks like some tumor cells labeled by SingleR are labeled as “smooth muscle cells” in the consensus cell types, so that’s something to keep in mind.

Let’s create a combined annotation that labels cells as tumor if the cell type label is tumor in SingleR and Unknown in consensus labels. All other labels will be taken from the consensus cell types.

# classify based on combined consensus and singler annotation 
all_info_df <- all_info_df |> 
  dplyr::mutate(combined_annotation = dplyr::if_else(
    consensus_annotation == "Unknown" & singler_annotation == "tumor",
    "tumor",
    consensus_annotation
  ),
  # lump for plotting later 
  combined_lumped = combined_annotation |>
    forcats::fct_lump_n(9, other_level = "All remaining cell types", ties.method = "first") |>
    forcats::fct_infreq() |> 
    forcats::fct_relevel("All remaining cell types", after = Inf)
  )

all_info_df |> 
  dplyr::count(combined_annotation) |> 
  dplyr::arrange(desc(n))

Use AUCell and mean gene set scores to refine tumor cells and identify tumor cell states

We used AUCell with gene sets that we expect to be up and down regulated in tumor cells. We can look at the AUC values for these gene sets across all the cell types and see if there are any cells that should be classified as tumor that are not. Additionally, we have gene sets that can help us distinguish EWS high and EWS low tumor cells.

First we’ll look at the mean AUC value for each gene set across all cell types.

Below is some information for the gene sets:

# reformat auc data for plots
auc_df <- all_info_df |> 
  tidyr::pivot_longer(starts_with("auc_"), names_to = "geneset", values_to = "auc") |> 
  dplyr::mutate(
    geneset = stringr::str_remove(geneset, "auc_")
  ) 

# create mtx with mean auc for heatmap 
auc_mtx <- auc_df |> 
  dplyr::group_by(combined_annotation, geneset) |> 
  dplyr::summarize(
    mean_auc = mean(auc)
  ) |> 
  tidyr::pivot_wider(names_from = geneset, 
                     values_from = mean_auc) |> 
  tidyr::drop_na() |> 
  tibble::column_to_rownames("combined_annotation") |>
  as.matrix()
`summarise()` has grouped output by 'combined_annotation'. You can override using the `.groups` argument.
ComplexHeatmap::Heatmap(
    auc_mtx,
    # set the color scale based on min and max values
    col = circlize::colorRamp2(seq(min(auc_mtx), max(auc_mtx), length = 2), colors = c("white", "#00274C")),
    border = TRUE,
    ## Row parameters
    cluster_rows = TRUE,
    row_title = "Combined cell types",
    row_title_side = "left",
    row_names_side = "left",
    row_dend_side = "right",
    row_names_gp = grid::gpar(fontsize = 10),
    ## Column parameters
    column_title = "MSigDB gene set",
    cluster_columns = TRUE,
    show_column_names = TRUE,
    column_names_gp = grid::gpar(fontsize = 8),
    heatmap_legend_param = list(
      title = "Mean expression"
    )
  )

Because we have some normal cell types that are showing gene signatures expected to be specific to tumor cells, we should use the AUC values to help pull out any remaining cells that should be tumor cells. We can also use the up and down gene sets to distinguish EWS-high and EWS-low tumor cells.

Let’s pick some gene sets where we see variation across cell types that we can use to define cutoffs:

genesets_of_interest <- c(
  "auc_aynaud-ews-targets",
  "auc_staege",
  "auc_wrenn-nt5e-genes",
  "auc_hallmark_EMT"
)

cluster_density_plot(all_info_df, genesets_of_interest, "combined_lumped", "AUC")

We can use these plots to help define cutoffs of AUC values to use for re-labeling any normal cells that should be tumor cells. We expect that the endothelial cells and macrophages have low AUC values so we can use these cell types as the background. Any AUC values above that background should be classified as tumor.

With this in mind, let’s use these cutoffs to define tumor cells:

Let’s re-plot the density plots adding in a line for the AUC cutoffs we have chosen.

cutoffs = c(
  "auc_aynaud-ews-targets" = 0.04,
  "auc_staege" = 0.01,
  "auc_wrenn-nt5e-genes" = 0.1,
  "auc_hallmark_EMT" = 0.05
)


cutoffs |>
    purrr::imap(\(cutoff, column){
      plot_density(
        all_info_df,
        column,
        "combined_lumped"
      ) +
        theme(text = element_text(size = 8)) +
        geom_vline(xintercept = cutoff)
    }) |>
    patchwork::wrap_plots(ncol = 2)

I don’t think these cutoffs are perfect, but I think they are reasonable. Any cells that are to the right of these cutoffs will be classified as tumor.

We can also use these to further stratify tumor cells into EWS high and EWS low classes.

# top cell type order 
density_plot_order <- c(
  "tumor EWS-high",
  "tumor EWS-low", 
  "Unknown",
  "smooth muscle cell",
  "endothelial cell",
  "macrophage",
  "fibroblast",
  "muscle cell",
  "mature T cell",
  "All remaining cell types"
)

# define "final.final" cell types 
all_info_df <- all_info_df |> 
  dplyr::mutate(
    final_annotation = dplyr::case_when(
      `auc_wrenn-nt5e-genes` > 0.1 & auc_hallmark_EMT > 0.05 ~ "tumor EWS-low",
      `auc_aynaud-ews-targets` > 0.04 & auc_staege > 0.01 ~ "tumor EWS-high",
      .default = consensus_annotation
    ),
    # lump together for easier plotting
    final_lumped = final_annotation |>
      forcats::fct_lump_n(9, other_level = "All remaining cell types", ties.method = "first") |>
      forcats::fct_infreq() |>
      forcats::fct_relevel(density_plot_order)
  )

all_info_df |> 
  dplyr::count(final_annotation) |> 
  dplyr::arrange(desc(n))

Let’s remake the density plot to see if we have pulled out the tumor cells from the normal cell types like muscle cells.

cluster_density_plot(all_info_df, genesets_of_interest, "final_lumped", "AUC")

It looks like generally the tumor EWS-high cells show high expression of both upregulated gene sets and tumor EWS-low cells show high expression of both downregulated gene sets, so I feel good about this!

I will also note that there are some muscle cells that show slight expression of aynaud-ews-targets, but show little to no expression of staege targets. I’m still a little worried these are actually tumor cells, but because they only show expression of one of the genesets, we are probably okay to keep them as is noting that the expression profile of muscle cells is very similar to those of tumor cells, this being sarcoma.

Identifying proliferative cells

The last tumor cell state that we want to identify is if any cells can be considered proliferative using the set of proliferative markers in tumor-cell-state-markers.tsv. Let’s look at the mean expression of these markers across all cells. Since we are plotting the proliferative markers, let’s also look at our other custom marker gene sets.

# get columns with mean expression of custom gene sets 
mean_exp_columns <- colnames(all_info_df)[which(endsWith(colnames(all_info_df), "_mean"))]
# make sure order is defined by cell type order
all_info_df <- all_info_df |> 
  dplyr::arrange(final_lumped)

single_annotation_heatmap(
  all_info_df, 
  exp_columns = mean_exp_columns, 
  cell_type_column = "final_lumped", 
  legend_title = "AUC"
)

It looks like we do have a set of cells that are tumor EWS-high and also have high expression of proliferative markers. Because of this, let’s label any cells that are tumor EWS-high and have mean expression of proliferative markers > 0 as tumor EWS-high proliferative.

# set desired order for plotting final annotations later
cell_type_order <- c(
  "tumor EWS-high",
  "tumor EWS-high proliferative",
  "tumor EWS-low",
  "muscle cell", 
  "smooth muscle cell",
  "fibroblast",
  "endothelial cell",
  "macrophage",
  "mature T cell",
  "Unknown",
  "All remaining cell types"
)

all_info_df <- all_info_df |> 
  dplyr::mutate(
    final_annotation = dplyr::if_else(
      final_annotation == "tumor EWS-high" & proliferative_mean > 0,
      "tumor EWS-high proliferative",
      final_annotation
    ), 
    final_lumped = final_annotation |>
      forcats::fct_lump_n(10, other_level = "All remaining cell types", ties.method = "first") |>
      forcats::fct_infreq() |>
      forcats::fct_relevel(cell_type_order)
  )

# first get tally for how many libraries each cell type is present in 
total_libraries <- all_info_df |> 
  dplyr::select(final_annotation, library_id) |> 
  unique() |> # get rid of duplicate cells
  dplyr::group_by(final_annotation) |> 
  dplyr::summarize(
    total_libraries = length(library_id)) |> 
  dplyr::arrange(desc(total_libraries))

# print out both total cells and total libraries 
all_info_df |> 
  dplyr::count(final_annotation, name = "total_cells") |>
  dplyr::left_join(total_libraries, by = "final_annotation") |> 
  dplyr::arrange(desc(total_cells))

For plotting purposes, we will only show the top 10 cell types with muscle cell being the last cell type. It looks like all other cell types are only present in 1-3 libraries. For any cells where there’s only 1 cell we may want to confirm that those cells express the expected targets, but that is outside the scope of this notebook.

Validation of cell type assignments

In the following section we’ll show some plots to help validate that we are content with our cell type assignments. These plots will look specifically at expression of a set of key marker genes that we expect to up in the assigned cell types. These markers are found in references/combined-markers.tsv and include a smaller set of markers for each of the cell types we have identified.

# read in file with validation markers 
validation_markers_df <- readr::read_tsv(validation_markers_file)

# pull out list of genes 
genes <- validation_markers_df |>
  dplyr::pull(ensembl_gene_id)

# get individual gene counts for all marker genes 
gene_cts <- logcounts(sce[genes, ]) |>
  as.matrix() |>
  t() |> 
  as.data.frame()
gene_cts$barcodes <- rownames(gene_cts)

# get all unique cell types from the final lumped group 
celltypes_df <- all_info_df |> 
  dplyr::select(barcodes, final_lumped)

# create a df that has gene expression column and column indicating whether or not the gene is detected in that cell 
genes_df <- gene_cts |> 
  tidyr::pivot_longer(!barcodes, names_to = "ensembl_gene_id", values_to = "gene_exp") |> 
  dplyr::left_join(celltypes_df, by = c("barcodes")) |> 
  dplyr::left_join(validation_markers_df, by = c("ensembl_gene_id")) |> 
  dplyr::rowwise() |> 
  dplyr::mutate(
    detected = gene_exp > 0 # column indicating if gene is present or not
  )

# get total number of cells per final annotation group 
total_cells_df <- genes_df |> 
  dplyr::select(barcodes, final_lumped) |> 
  unique() |> 
  dplyr::count(final_lumped, name = "total_cells")

# get total number of cells each gene is detected in per group 
# and mean gene expression per group 
group_stats_df <- genes_df |> 
  dplyr::group_by(final_lumped, ensembl_gene_id) |>
  dplyr::summarize(
    detected_count = sum(detected),
    mean_exp = mean(gene_exp)
  )
`summarise()` has grouped output by 'final_lumped'. You can override using the `.groups` argument.
gene_summary_df <- genes_df |> 
  # add total cells
  dplyr::left_join(total_cells_df, by = c("final_lumped")) |> 
  # add per gene stats
  dplyr::left_join(group_stats_df, by = c("ensembl_gene_id", "final_lumped")) |> 
  dplyr::select(gene_symbol, final_lumped, cell_type, total_cells, detected_count, mean_exp) |> 
  unique() |>
  dplyr::rowwise() |> 
  dplyr::mutate(
    # get total percent
    percent_exp = (detected_count/total_cells) * 100,
    # order genes based on cell type they indicate
    gene_symbol = factor(gene_symbol, levels = validation_markers_df$gene_symbol),
    final_lumped = forcats::fct_relevel(final_lumped, cell_type_order)
    
  ) 
# filter out low expressed genes
dotplot_df <- gene_summary_df |> 
  dplyr::filter(mean_exp > 0, percent_exp > 10) |>
  dplyr::arrange(final_lumped) |> 
  dplyr::mutate(y_label = as.factor(glue::glue("{final_lumped} ({total_cells})")))


dotplot <- ggplot(dotplot_df, aes(y = forcats::fct_rev(y_label), x = gene_symbol, color = mean_exp, size = percent_exp)) +
  geom_point() +
  scale_color_viridis_c(option = "magma", limits = c(0,2.5), oob = scales::squish) +
  theme(
    axis.text.x = element_text(angle = 90, hjust = 0.5)
  ) +
  labs(
    x = "",
    y = "Final cell type annotation"
  )

# specify legend order 
color_order <- dotplot_df |> 
  dplyr::pull(cell_type) |> 
  unique()

color_bar <- ggplot(dotplot_df, aes(x = gene_symbol, y = 1, fill = cell_type)) + 
  geom_tile() + 
  scale_fill_brewer(palette = 'Set1', breaks = color_order) +
  ggmap::theme_nothing() +
  theme(legend.position = "bottom") +
  labs(fill = "")

dotplot + color_bar +
  patchwork::plot_layout(ncol = 1, heights = c(4, 0.1)) 

A few notes from this plot:

Let’s make a heatmap version of the same plot.

# note that we can't use the same heatmap function as before since we are looking at cell types as rows rather than gene sets
# we also want to specify the annotation name

# first make a mtx to use for the heatmap with rows as cell types and genes as columns 
heatmap_mtx <- gene_summary_df |> 
  dplyr::select(gene_symbol, final_lumped, mean_exp) |> 
  tidyr::pivot_wider(
    names_from = gene_symbol,
    values_from = mean_exp
  ) |> 
  tibble::column_to_rownames("final_lumped") |>
  as.matrix()
# make sure cell types are present in the right order
heatmap_mtx <- heatmap_mtx[cell_type_order,]

# get annotation colors for each of the marker gene types 
marker_gene_categories <- unique(validation_markers_df$cell_type)
num_categories <- length(marker_gene_categories)
category_colors <- palette.colors(palette = "Dark2") |>
  head(n = num_categories) |>
  purrr::set_names(marker_gene_categories)


# create annotation for heatmap
annotation <- ComplexHeatmap::columnAnnotation(
  category = validation_markers_df$cell_type,
  col = list(
    category = category_colors
  ),
  annotation_legend_param = list(
      title = "Marker gene category",
      labels = unique(validation_markers_df$cell_type)
    )
)

ComplexHeatmap::Heatmap(
    heatmap_mtx,
    # set the color scale based on min and max values
    col = circlize::colorRamp2(seq(min(heatmap_mtx), max(heatmap_mtx), length = 2), colors = c("white", "#00274C")),
    border = TRUE,
    ## Row parameters
    cluster_rows = FALSE,
    row_title = "",
    row_title_side = "left",
    row_names_side = "left",
    row_dend_side = "right",
    row_names_gp = grid::gpar(fontsize = 10),
    ## Column parameters
    cluster_columns = FALSE,
    show_column_names = TRUE,
    column_names_gp = grid::gpar(fontsize = 8),
    top_annotation = annotation,
    heatmap_legend_param = list(
      title = "Mean expression"
    )
  )

And finally we’ll look at our annotations on a UMAP! Because, why not.

ggplot(all_info_df, aes(x = UMAP1, y = UMAP2, color = final_lumped)) +
  geom_point(alpha = 0.5, size = 0.01) +
  # set color for all remaining cell types since there are more colors than in the palette
  scale_color_manual(values = c(palette.colors(palette = "Dark2"), "black", "grey65", "grey90")) +
  labs(color = "") +
  guides(color = guide_legend(override.aes = list(alpha = 1, size = 1.5)))

plot_faceted_umap(all_info_df, final_lumped, legend_title = "Final cell type annotations") +
  theme(strip.text = element_text(size = 6))

Export cell types

Now that we have our cell types let’s export them to a TSV to save for future use!

annotation_df <- all_info_df |> 
  dplyr::mutate(
    # remove library id from barcodes since we have a library id column already 
    barcodes = stringr::word(barcodes, -1, sep = "-"),
    # assign ontology ID using consensus ontology ID
    # anything without an ID is either unknown or tumor 
    final_ontology = dplyr::if_else(
      final_annotation == consensus_annotation,
      consensus_ontology,
      final_annotation
    )
  ) |> 
  dplyr::select(
    barcodes,
    library_id,
    sample_id,
    sample_type,
    singler_ontology,
    singler_annotation,
    consensus_annotation,
    consensus_ontology,
    final_annotation,
    final_ontology
  )

readr::write_tsv(annotation_df, output_file)

Session info

# record the versions of the packages used in this analysis and other environment information
sessionInfo()
R version 4.4.2 (2024-10-31)
Platform: aarch64-apple-darwin20
Running under: macOS Sequoia 15.3

Matrix products: default
BLAS:   /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib 
LAPACK: /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.0

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

time zone: America/Chicago
tzcode source: internal

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

other attached packages:
 [1] ggplot2_3.5.1               SingleCellExperiment_1.26.0 SummarizedExperiment_1.34.0 Biobase_2.64.0             
 [5] GenomicRanges_1.56.1        GenomeInfoDb_1.40.1         IRanges_2.38.1              S4Vectors_0.42.1           
 [9] BiocGenerics_0.50.0         MatrixGenerics_1.16.0       matrixStats_1.3.0          

loaded via a namespace (and not attached):
 [1] bitops_1.0-8              rlang_1.1.4               magrittr_2.0.3            clue_0.3-65              
 [5] GetoptLong_1.0.5          compiler_4.4.2            DelayedMatrixStats_1.26.0 png_0.1-8                
 [9] vctrs_0.6.5               stringr_1.5.1             pkgconfig_2.0.3           shape_1.4.6.1            
[13] crayon_1.5.3              fastmap_1.2.0             XVector_0.44.0            scuttle_1.14.0           
[17] labeling_0.4.3            utf8_1.2.4                rmarkdown_2.27            tzdb_0.4.0               
[21] UCSC.utils_1.0.0          purrr_1.0.2               bit_4.0.5                 xfun_0.46                
[25] zlibbioc_1.50.0           cachem_1.1.0              beachmat_2.20.0           jsonlite_1.8.8           
[29] highr_0.11                DelayedArray_0.30.1       BiocParallel_1.38.0       jpeg_0.1-10              
[33] parallel_4.4.2            cluster_2.1.6             R6_2.5.1                  bslib_0.8.0              
[37] stringi_1.8.4             RColorBrewer_1.1-3        jquerylib_0.1.4           Rcpp_1.0.14              
[41] iterators_1.0.14          knitr_1.48                readr_2.1.5               Matrix_1.7-0             
[45] tidyselect_1.2.1          abind_1.4-5               yaml_2.3.10               doParallel_1.0.17        
[49] codetools_0.2-20          plyr_1.8.9                lattice_0.22-6            tibble_3.2.1             
[53] withr_3.0.0               evaluate_0.24.0           circlize_0.4.16           pillar_1.9.0             
[57] BiocManager_1.30.23       renv_1.0.7                foreach_1.5.2             generics_0.1.3           
[61] vroom_1.6.5               rprojroot_2.0.4           hms_1.1.3                 sparseMatrixStats_1.16.0 
[65] munsell_0.5.1             scales_1.3.0              glue_1.7.0                tools_4.4.2              
[69] forcats_1.0.0             fs_1.6.4                  grid_4.4.2                tidyr_1.3.1              
[73] colorspace_2.1-1          GenomeInfoDbData_1.2.12   patchwork_1.2.0           cli_3.6.3                
[77] ggmap_4.0.0               fansi_1.0.6               S4Arrays_1.4.1            viridisLite_0.4.2        
[81] ComplexHeatmap_2.20.0     dplyr_1.1.4               gtable_0.3.5              sass_0.4.9               
[85] digest_0.6.36             SparseArray_1.4.8         rjson_0.2.21              farver_2.1.2             
[89] htmltools_0.5.8.1         lifecycle_1.0.4           httr_1.4.7                GlobalOptions_0.1.2      
[93] bit64_4.0.5              
LS0tCnRpdGxlOiAiQ2VsbCB0eXBlIGFzc2lnbm1lbnRzIGZvciBTQ1BDUDAwMDAxNSIKYXV0aG9yOiBBbGx5IEhhd2tpbnMKZGF0ZTogImByIFN5cy5EYXRlKClgIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIHRvYzogdHJ1ZQogICAgdG9jX2RlcHRoOiAzCiAgICBjb2RlX2ZvbGRpbmc6ICJoaWRlIgotLS0KClRoaXMgbm90ZWJvb2sgbG9va3MgYXQgY2VsbCB0eXBlIGFubm90YXRpb25zIGFuZCBnZW5lIHNldCBleHByZXNzaW9uIGFjcm9zcyBhbGwgc2FtcGxlcyBpbiBgU0NQQ1AwMDAxNWAgYW5kIGFzc2lnbnMgImZpbmFsIiBhbm5vdGF0aW9ucy4gClRvIGRvIHRoaXMgd2UgYXJlIHVzaW5nIHRoZSBtZXJnZWQsIGJ1dCBub3QgYmF0Y2gtY29ycmVjdGVkLCBvYmplY3QgY29udGFpbmluZyB0aGUgZ2VuZSBleHByZXNzaW9uIGRhdGEgZm9yIGFsbCBzYW1wbGVzLiAKClRoZSBmb2xsb3dpbmcgaW5wdXQgaXMgdXNlZDogCgotIEFubm90YXRpb25zIG9idGFpbmVkIGJ5IHJ1bm5pbmcgYFNpbmdsZVJgIHdpdGggdHVtb3IgY2VsbHMgYXMgYSByZWZlcmVuY2Ugb3V0cHV0IGJ5IFtgYXVjZWxsLXNpbmdsZXItYW5ub3RhdGlvbi5zaGBdKGh0dHBzOi8vZ2l0aHViLmNvbS9BbGV4c0xlbW9uYWRlL09wZW5TY1BDQS1hbmFseXNpcy9ibG9iL21haW4vYW5hbHlzZXMvY2VsbC10eXBlLWV3aW5ncy9hdWNlbGwtc2luZ2xlci1hbm5vdGF0aW9uLnNoKS4gCi0gQ29uc2Vuc3VzIGNlbGwgdHlwZSBhbm5vdGF0aW9ucyBvdXRwdXQgYnkgcnVubmluZyBbYGFzc2lnbi1jb25zZW5zdXMtY2VsbHR5cGVzLnNoYCBpbiBgY2VsbC10eXBlLWNvbnNlbnN1c2BdKGh0dHBzOi8vZ2l0aHViLmNvbS9BbGV4c0xlbW9uYWRlL09wZW5TY1BDQS1hbmFseXNpcy9ibG9iL21haW4vYW5hbHlzZXMvY2VsbC10eXBlLWNvbnNlbnN1cy9hc3NpZ24tY29uc2Vuc3VzLWNlbGx0eXBlcy5zaCkuIAotIEFVQyB2YWx1ZXMgYXMgY2FsY3VsYXRlZCBieSBgQVVDZWxsYCBmb3IgYSBzZXQgb2YgRXdpbmcgc2FyY29tYSBzcGVjaWZpYyBnZW5lIHNldHMgaW4gTVNpZ0RCIG91dHB1dCBieSBgcnVuLWF1Y2VsbC1ld3Mtc2lnbmF0dXJlcy5zaGAuIAoKIyMgU2V0dXAKCmBgYHtyIHBhY2thZ2VzfQpzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMoewogICMgbG9hZCByZXF1aXJlZCBwYWNrYWdlcwogIGxpYnJhcnkoU2luZ2xlQ2VsbEV4cGVyaW1lbnQpCiAgbGlicmFyeShnZ3Bsb3QyKQp9KQoKIyBTZXQgZGVmYXVsdCBnZ3Bsb3QgdGhlbWUKdGhlbWVfc2V0KAogIHRoZW1lX2NsYXNzaWMoKQopCgojIHNldCBzZWVkCnNldC5zZWVkKDIwMjQpCgojIHF1aWV0IG1lc3NhZ2VzCm9wdGlvbnMocmVhZHIuc2hvd19jb2xfdHlwZXMgPSBGQUxTRSkKQ29tcGxleEhlYXRtYXA6Omh0X29wdChtZXNzYWdlID0gRkFMU0UpCmBgYAoKCmBgYHtyIGJhc2UgcGF0aHN9CiMgVGhlIGJhc2UgcGF0aCBmb3IgdGhlIE9wZW5TY1BDQSByZXBvc2l0b3J5LCBmb3VuZCBieSBpdHMgKGhpZGRlbikgLmdpdCBkaXJlY3RvcnkKcmVwb3NpdG9yeV9iYXNlIDwtIHJwcm9qcm9vdDo6ZmluZF9yb290KHJwcm9qcm9vdDo6aXNfZ2l0X3Jvb3QpCgojIFRoZSBjdXJyZW50IGRhdGEgZGlyZWN0b3J5LCBmb3VuZCB3aXRoaW4gdGhlIHJlcG9zaXRvcnkgYmFzZSBkaXJlY3RvcnkKZGF0YV9kaXIgPC0gZmlsZS5wYXRoKHJlcG9zaXRvcnlfYmFzZSwgImRhdGEiLCAiY3VycmVudCIsICJyZXN1bHRzIiwgIm1lcmdlLXNjZSIsICJTQ1BDUDAwMDAxNSIpCgojIFRoZSBwYXRoIHRvIHRoaXMgbW9kdWxlCm1vZHVsZV9iYXNlIDwtIGZpbGUucGF0aChyZXBvc2l0b3J5X2Jhc2UsICJhbmFseXNlcyIsICJjZWxsLXR5cGUtZXdpbmdzIikgCmBgYAoKYGBge3J9CiMgcGF0aCB0byBzY2UgCnNjZV9maWxlIDwtIGZpbGUucGF0aChkYXRhX2RpciwgIlNDUENQMDAwMDE1X21lcmdlZC5yZHMiKQoKIyBwYXRoIHRvIHdvcmtmbG93IHJlc3VsdHMKd29ya2Zsb3dfcmVzdWx0c19kaXIgPC0gZmlsZS5wYXRoKG1vZHVsZV9iYXNlLCAicmVzdWx0cyIpCgpzaW5nbGVyX3Jlc3VsdHNfZGlyIDwtIGZpbGUucGF0aCh3b3JrZmxvd19yZXN1bHRzX2RpciwgImF1Y2VsbF9zaW5nbGVyX2Fubm90YXRpb24iKQpzaW5nbGVyX3Jlc3VsdHNfZmlsZXMgPC0gbGlzdC5maWxlcyhzaW5nbGVyX3Jlc3VsdHNfZGlyLCBwYXR0ZXJuID0gIipzaW5nbGVyLWNsYXNzaWZpY2F0aW9ucy50c3YiLCBmdWxsLm5hbWVzID0gVFJVRSwgcmVjdXJzaXZlID0gVFJVRSkKbGlicmFyeV9pZHMgPC0gc3RyaW5ncjo6c3RyX3JlbW92ZShiYXNlbmFtZShzaW5nbGVyX3Jlc3VsdHNfZmlsZXMpLCAiX3NpbmdsZXItY2xhc3NpZmljYXRpb25zLnRzdiIpCgoKYXVjZWxsX3Jlc3VsdHNfZGlyIDwtIGZpbGUucGF0aCh3b3JrZmxvd19yZXN1bHRzX2RpciwgImF1Y2VsbC1ld3Mtc2lnbmF0dXJlcyIpCmF1Y2VsbF9yZXN1bHRzX2ZpbGUgPC0gZmlsZS5wYXRoKGF1Y2VsbF9yZXN1bHRzX2RpciwgIlNDUENQMDAwMDE1X2F1Yy1ld3MtZ2VuZS1zaWduYXR1cmVzLnRzdiIpCgpjb25zZW5zdXNfcmVzdWx0c19kaXIgPC0gZmlsZS5wYXRoKHJlcG9zaXRvcnlfYmFzZSwgImFuYWx5c2VzIiwgImNlbGwtdHlwZS1jb25zZW5zdXMiLCAicmVzdWx0cyIsICJjZWxsLXR5cGUtY29uc2Vuc3VzIiwgIlNDUENQMDAwMDE1IikKY29uc2Vuc3VzX3Jlc3VsdHNfZmlsZXMgPC0gbGlzdC5maWxlcyhjb25zZW5zdXNfcmVzdWx0c19kaXIsIHBhdHRlcm4gPSAiKl9jb25zZW5zdXMtY2VsbC10eXBlLWFzc2lnbm1lbnRzLnRzdi5neiIsIGZ1bGwubmFtZXMgPSBUUlVFLCByZWN1cnNpdmUgPSBUUlVFKQoKIyBzbWFsbCBnZW5lIHNldHMKdmlzc2VyX21hcmtlcl9nZW5lc19maWxlIDwtIGZpbGUucGF0aChtb2R1bGVfYmFzZSwgInJlZmVyZW5jZXMiLCAidmlzc2VyLWFsbC1tYXJrZXItZ2VuZXMudHN2IikKY2VsbF9zdGF0ZV9nZW5lc19maWxlIDwtIGZpbGUucGF0aChtb2R1bGVfYmFzZSwgInJlZmVyZW5jZXMiLCAidHVtb3ItY2VsbC1zdGF0ZS1tYXJrZXJzLnRzdiIpCgojIG1hcmtlciBnZW5lcyB0byBiZSB1c2VkIGZvciB2YWxpZGF0aW5nIGFzc2lnbm1lbnRzIAp2YWxpZGF0aW9uX21hcmtlcnNfZmlsZSA8LSBmaWxlLnBhdGgobW9kdWxlX2Jhc2UsICJyZWZlcmVuY2VzIiwgImNvbWJpbmVkLXZhbGlkYXRpb24tbWFya2Vycy50c3YiKQpgYGAKCmBgYHtyfQojIG91dHB1dCBmaWxlIHRvIHNhdmUgZmluYWwgYW5ub3RhdGlvbnMgCnJlc3VsdHNfZGlyIDwtIGZpbGUucGF0aChtb2R1bGVfYmFzZSwgInJlc3VsdHMiLCAiZmluYWwtYW5ub3RhdGlvbnMiKQpmczo6ZGlyX2NyZWF0ZShyZXN1bHRzX2RpcikKb3V0cHV0X2ZpbGUgPC0gZmlsZS5wYXRoKHJlc3VsdHNfZGlyLCAiU0NQQ1AwMDAwMTVfY2VsbHR5cGUtYW5ub3RhdGlvbnMudHN2Lmd6IikKYGBgCgoKYGBge3J9CiMgc291cmNlIGluIHNldHVwIGZ1bmN0aW9ucyBwcmVwX3Jlc3VsdHMoKQpzZXR1cF9mdW5jdGlvbnMgPC0gZmlsZS5wYXRoKG1vZHVsZV9iYXNlLCAidGVtcGxhdGVfbm90ZWJvb2tzIiwgInV0aWxzIiwgInNldHVwLWZ1bmN0aW9ucy5SIikKc291cmNlKHNldHVwX2Z1bmN0aW9ucykKCiMgc291cmNlIGluIHZhbGlkYXRpb24gZnVuY3Rpb25zIAojIGNhbGN1bGF0ZV9tZWFuX21hcmtlcnMoKSwgcGxvdF9mYWNldGVkX3VtYXAoKQp2YWxpZGF0aW9uX2Z1bmN0aW9ucyA8LSBmaWxlLnBhdGgobW9kdWxlX2Jhc2UsICJzY3JpcHRzIiwgInV0aWxzIiwgInR1bW9yLXZhbGlkYXRpb24taGVscGVycy5SIikKc291cmNlKHZhbGlkYXRpb25fZnVuY3Rpb25zKQoKIyBzb3VyY2UgaW4gcGxvdHRpbmcgZnVuY3Rpb25zIAojIGV4cHJlc3Npb25fdW1hcCgpLCBjbHVzdGVyX2RlbnNpdHlfcGxvdCgpLCBhbmQgYW5ub3RhdGVkX2V4cF9oZWF0bWFwKCkKcGxvdHRpbmdfZnVuY3Rpb25zIDwtIGZpbGUucGF0aChtb2R1bGVfYmFzZSwgInRlbXBsYXRlX25vdGVib29rcyIsICJ1dGlscyIsICJwbG90dGluZy1mdW5jdGlvbnMuUiIpCnNvdXJjZShwbG90dGluZ19mdW5jdGlvbnMpCgojIHNvdXJjZSBqYWNjYXJkIGZ1bmN0aW9ucyBwbG90X2phY2NhcmQoKQpqYWNjYXJkX2Z1bmN0aW9ucyA8LSBmaWxlLnBhdGgobW9kdWxlX2Jhc2UsICJzY3JpcHRzIiwgInV0aWxzIiwgImphY2NhcmQtZnVuY3Rpb25zLlIiKQpzb3VyY2UoamFjY2FyZF9mdW5jdGlvbnMpCmBgYAoKYGBge3J9CnN0b3BpZm5vdCgKICAic2NlIGZpbGUgZG9lcyBub3QgZXhpc3QiID0gZmlsZS5leGlzdHMoc2NlX2ZpbGUpLAogICJhdWNlbGwgcmVzdWx0cyBmaWxlIGRvZXMgbm90IGV4aXN0IiA9IGZpbGUuZXhpc3RzKGF1Y2VsbF9yZXN1bHRzX2ZpbGUpLCAKICAiYXQgbGVhc3Qgb25lIHNpbmdsZXIgZmlsZSBpcyBtaXNzaW5nIiA9IGFsbChmaWxlLmV4aXN0cyhzaW5nbGVyX3Jlc3VsdHNfZmlsZXMpKSwKICAiYXQgbGVhc3Qgb25lIGNvbnNlbnN1cyBmaWxlIGlzIG1pc3NpbmciID0gYWxsKGZpbGUuZXhpc3RzKGNvbnNlbnN1c19yZXN1bHRzX2ZpbGVzKSkKKQpgYGAKCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KIyByZWFkIGluIHNjZQpzY2UgPC0gcmVhZHI6OnJlYWRfcmRzKHNjZV9maWxlKQoKIyByZWFkIGluIHdvcmtmbG93IHJlc3VsdHMKc2luZ2xlcl9kZiA8LSBzaW5nbGVyX3Jlc3VsdHNfZmlsZXMgfD4gCiAgcHVycnI6OnNldF9uYW1lcyhsaWJyYXJ5X2lkcykgfD4KICBwdXJycjo6bWFwKHJlYWRyOjpyZWFkX3RzdikgfD4gCiAgZHBseXI6OmJpbmRfcm93cyguaWQgPSAibGlicmFyeV9pZCIpCgphdWNlbGxfZGYgPC0gcmVhZHI6OnJlYWRfdHN2KGF1Y2VsbF9yZXN1bHRzX2ZpbGUpIHw+IAogIHRpZHlyOjpzZXBhcmF0ZShiYXJjb2RlcywgIi0iLCBpbnRvID0gYygibGlicmFyeV9pZCIsICJiYXJjb2RlcyIpKQoKIyBjb25zZW5zdXMgY2VsbCB0eXBlcyAKY29uc2Vuc3VzX2RmIDwtIGNvbnNlbnN1c19yZXN1bHRzX2ZpbGVzIHw+IAogIHB1cnJyOjptYXAocmVhZHI6OnJlYWRfdHN2KSB8PiAKICBkcGx5cjo6YmluZF9yb3dzKCkKCiMgcmVhZCBpbiBtYXJrZXIgZ2VuZXMgYW5kIGNvbWJpbmUgaW50byBvbmUgbGlzdCAKdmlzc2VyX21hcmtlcnNfZGYgPC0gcmVhZHI6OnJlYWRfdHN2KHZpc3Nlcl9tYXJrZXJfZ2VuZXNfZmlsZSkgfD4gCiAgZHBseXI6OnNlbGVjdChjZWxsX3R5cGUsIGVuc2VtYmxfZ2VuZV9pZCwgZ2VuZV9zeW1ib2wpIHw+IAogIHVuaXF1ZSgpCiAgCmNlbGxfc3RhdGVfbWFya2Vyc19kZiA8LSByZWFkcjo6cmVhZF90c3YoY2VsbF9zdGF0ZV9nZW5lc19maWxlKSB8PiAKICBkcGx5cjo6c2VsZWN0KGNlbGxfdHlwZSA9IGNlbGxfc3RhdGUsIGVuc2VtYmxfZ2VuZV9pZCwgZ2VuZV9zeW1ib2wpCgphbGxfbWFya2Vyc19kZiA8LSBkcGx5cjo6YmluZF9yb3dzKGxpc3Qodmlzc2VyX21hcmtlcnNfZGYsIGNlbGxfc3RhdGVfbWFya2Vyc19kZikpCmBgYAoKIyMgUHJlcGFyZSBkYXRhIGZvciBwbG90dGluZwoKYGBge3J9CmFsbF9yZXN1bHRzX2RmIDwtIHByZXBfcmVzdWx0cygKICBzY2UsIAogIHNpbmdsZXJfZGYgPSBzaW5nbGVyX2RmLCAKICBjbHVzdGVyX2RmID0gTlVMTCwgCiAgYXVjZWxsX2RmID0gYXVjZWxsX2RmLAogIGNvbnNlbnN1c19kZiA9IGNvbnNlbnN1c19kZiwKICBjbHVzdGVyX25uID0gcGFyYW1zJGNsdXN0ZXJfbm4sCiAgY2x1c3Rlcl9yZXMgPSBwYXJhbXMkY2x1c3Rlcl9yZXMsCiAgam9pbl9jb2x1bW5zID0gYygiYmFyY29kZXMiLCAibGlicmFyeV9pZCIpCiAgKSB8PgogIGRwbHlyOjptdXRhdGUoYmFyY29kZXMgPSBnbHVlOjpnbHVlKCJ7bGlicmFyeV9pZH0te2JhcmNvZGVzfSIpKQogIApjZWxsX3R5cGVzIDwtIHVuaXF1ZShhbGxfbWFya2Vyc19kZiRjZWxsX3R5cGUpCgojIGdldCB0aGUgbWVhbiBleHByZXNzaW9uIG9mIGFsbCBnZW5lcyBmb3IgZWFjaCBjZWxsIHN0YXRlCmdlbmVfZXhwX2RmIDwtIGNlbGxfdHlwZXMgfD4KICBwdXJycjo6bWFwKFwodHlwZSl7CiAgICBjYWxjdWxhdGVfbWVhbl9tYXJrZXJzKGFsbF9tYXJrZXJzX2RmLCBzY2UsIHR5cGUsIGNlbGxfdHlwZSkKICB9KSB8PgogIHB1cnJyOjpyZWR1Y2UoZHBseXI6OmlubmVyX2pvaW4sIGJ5ID0gImJhcmNvZGVzIikKCmFsbF9pbmZvX2RmIDwtIGFsbF9yZXN1bHRzX2RmIHw+IAogIGRwbHlyOjpsZWZ0X2pvaW4oZ2VuZV9leHBfZGYsIGJ5ID0gImJhcmNvZGVzIikgfD4gCiAgZHBseXI6Om11dGF0ZSgKICAgIHNpbmdsZXJfYW5ub3RhdGlvbiA9IGRwbHlyOjppZl9lbHNlKGlzLm5hKHNpbmdsZXJfYW5ub3RhdGlvbiksICJ1bmtub3duIiwgc2luZ2xlcl9hbm5vdGF0aW9uKQogICkKYGBgCgojIyBDb21iaW5lIGNsYXNzaWZpY2F0aW9ucyBmcm9tIGBTaW5nbGVSYCBhbmQgY29uc2Vuc3VzIGNlbGwgdHlwZXMgCgpUaGUgZmlyc3QgdGhpbmcgd2Ugd2lsbCBkbyBpcyBjb21wYXJlIGNlbGwgdHlwZSBhbm5vdGF0aW9ucyBvYnRhaW5lZCBieSBlYWNoIG1ldGhvZCwgYFNpbmdsZVJgIHdpdGggdHVtb3IgY2VsbHMgYXMgYSByZWZlcmVuY2UgKG91dHB1dCBieSBbYGF1Y2VsbC1zaW5nbGVyLWFubm90YXRpb24uc2hgXShodHRwczovL2dpdGh1Yi5jb20vQWxleHNMZW1vbmFkZS9PcGVuU2NQQ0EtYW5hbHlzaXMvYmxvYi9tYWluL2FuYWx5c2VzL2NlbGwtdHlwZS1ld2luZ3MvYXVjZWxsLXNpbmdsZXItYW5ub3RhdGlvbi5zaCkpIGFuZCBjb25zZW5zdXMgYW5ub3RhdGlvbnMgKG91dHB1dCBmcm9tIFtgYXNzaWduLWNvbnNlbnN1cy1jZWxsdHlwZXMuc2hgIGluIGBjZWxsLXR5cGUtY29uc2Vuc3VzYF0oaHR0cHM6Ly9naXRodWIuY29tL0FsZXhzTGVtb25hZGUvT3BlblNjUENBLWFuYWx5c2lzL2Jsb2IvbWFpbi9hbmFseXNlcy9jZWxsLXR5cGUtY29uc2Vuc3VzL2Fzc2lnbi1jb25zZW5zdXMtY2VsbHR5cGVzLnNoKS4gIAoKTGV0J3Mgc2VlIGhvdyBzaW1pbGFyIHRoZXNlIGFubm90YXRpb25zIGFyZSBhbmQgc2VlIGlmIHdlIGNhbiBjcmVhdGUgYSBjb21iaW5lZCBhbm5vdGF0aW9uLiAKTm90ZSB0aGF0IGBTaW5nbGVSYCB3aWxsIGxhYmVsIHR1bW9yIGNlbGxzIGJ1dCB0aGUgY29uc2Vuc3VzIGNlbGwgdHlwZXMgb25seSBsYWJlbCBub3JtYWwgY2VsbHMuIApDb25zZW5zdXMgY2VsbCB0eXBlcyBhcmUgb2JzZXJ2ZWQgd2hlbiBjZWxsIHR5cGVzIGZyb20gYFNpbmdsZVJgIGFuZCBgQ2VsbEFzc2lnbmAgKHVzaW5nIG9ubHkgbm9ybWFsIHRpc3N1ZSByZWZlcmVuY2VzKSBzaGFyZSBhIGNvbW1vbiBhbmNlc3Rvci4gCklmIG5vIGNvbnNlbnN1cyBpcyBmb3VuZCwgdGhlIGNlbGxzIGFyZSBsYWJlbGVkIHdpdGggIlVua25vd24iLiAKCkFueSBjZWxscyB0aGF0IGFyZSB1bmFibGUgdG8gYmUgbGFiZWxlZCB2aWEgY29uc2Vuc3VzIGNlbGwgdHlwZXMgYXJlIGxhYmVsZWQgYXMgIlVua25vd24iIGFuZCBJIGV4cGVjdCB0aGVzZSB3aWxsIGxpbmUgdXAgd2l0aCBjZWxscyBsYWJlbGVkIGFzIHR1bW9yIGNlbGxzIGJ5IGBTaW5nbGVSYC4gIAoKYGBge3IsIGZpZy5oZWlnaHQ9NX0KIyBnZXQgY2VsbCB0eXBlcyB0aGF0IGhhdmUgYXQgbGVhc3QgNTAgY2VsbHMKc2luZ2xlcl90b19rZWVwIDwtIGFsbF9pbmZvX2RmIHw+IAogIGRwbHlyOjpjb3VudChzaW5nbGVyX2Fubm90YXRpb24pIHw+CiAgZHBseXI6OmZpbHRlcihuID49IDUwKSB8PiAKICBkcGx5cjo6cHVsbChzaW5nbGVyX2Fubm90YXRpb24pCgojIGZpcnN0IGdldCBqYWNjYXJkIGluZGV4IGJldHdlZW4gY29uc2Vuc3VzIGFuZCBzaW5nbGVyCmphY2NhcmRfbXR4IDwtIGFsbF9pbmZvX2RmIHw+IAogIGRwbHlyOjpmaWx0ZXIoc2luZ2xlcl9hbm5vdGF0aW9uICVpbiUgc2luZ2xlcl90b19rZWVwKSB8PiAKICBtYWtlX2phY2NhcmRfbWF0cml4KAogICAgImNvbnNlbnN1c19hbm5vdGF0aW9uIiwKICAgICJzaW5nbGVyX2Fubm90YXRpb24iCiAgKQoKQ29tcGxleEhlYXRtYXA6OkhlYXRtYXAoCiAgdChqYWNjYXJkX210eCksCiAgY29sID0gY2lyY2xpemU6OmNvbG9yUmFtcDIoYygwLCAxKSwgY29sb3JzID0gYygid2hpdGUiLCAiZGFya3NsYXRlYmx1ZSIpKSwKICBib3JkZXIgPSBUUlVFLAogICMjIFJvdyBwYXJhbWV0ZXJzCiAgY2x1c3Rlcl9yb3dzID0gVFJVRSwKICByb3dfdGl0bGUgPSAiU2luZ2xlUiB3aXRoIHR1bW9yIHJlZmVyZW5jZSIsCiAgcm93X3RpdGxlX2dwID0gZ3JpZDo6Z3Bhcihmb250c2l6ZSA9IDEyKSwKICByb3dfdGl0bGVfc2lkZSA9ICJsZWZ0IiwKICByb3dfbmFtZXNfc2lkZSA9ICJsZWZ0IiwKICByb3dfZGVuZF9zaWRlID0gInJpZ2h0IiwKICByb3dfbmFtZXNfZ3AgPSBncmlkOjpncGFyKGZvbnRzaXplID0gMTIpLAogICMjIENvbHVtbiBwYXJhbWV0ZXJzCiAgY2x1c3Rlcl9jb2x1bW5zID0gVFJVRSwKICBjb2x1bW5fdGl0bGUgPSAiQ29uc2Vuc3VzIiwKICBjb2x1bW5fdGl0bGVfZ3AgPSBncmlkOjpncGFyKGZvbnRzaXplID0gMTIpLAogIGNvbHVtbl9uYW1lc19zaWRlID0gImJvdHRvbSIsCiAgY29sdW1uX25hbWVzX2dwID0gZ3JpZDo6Z3Bhcihmb250c2l6ZSA9IDEyKSwKICBjb2x1bW5fbmFtZXNfcm90ID0gOTAsCiAgIyMgTGVnZW5kIHBhcmFtZXRlcnMKICBoZWF0bWFwX2xlZ2VuZF9wYXJhbSA9IGxpc3QoCiAgICB0aXRsZSA9ICJKYWNjYXJkIGluZGV4IiwKICAgIGRpcmVjdGlvbiA9ICJ2ZXJ0aWNhbCIsCiAgICBsZWdlbmRfd2lkdGggPSB1bml0KDEuNSwgImluIikKICApKQpgYGAKCkl0IGxvb2tzIGxpa2UgZ2VuZXJhbGx5IHRoZXNlIHR3byBjZWxsIHR5cGUgYW5ub3RhdGlvbnMgYXJlIGluIGFncmVlbWVudC4gCldlIGFsc28gc2VlIHRoYXQgbW9zdCBvZiB0aGUgIlVua25vd24iIGNlbGxzIGFzIGRlbm90ZWQgYnkgY29uc2Vuc3VzIGFubm90YXRpb25zIGFyZSBpbiBmYWN0IGxpbmluZyB1cCB3aXRoIHR1bW9yIGNlbGxzLiBJdCBsb29rcyBsaWtlIHNvbWUgdHVtb3IgY2VsbHMgbGFiZWxlZCBieSBgU2luZ2xlUmAgYXJlIGxhYmVsZWQgYXMgInNtb290aCBtdXNjbGUgY2VsbHMiIGluIHRoZSBjb25zZW5zdXMgY2VsbCB0eXBlcywgc28gdGhhdCdzIHNvbWV0aGluZyB0byBrZWVwIGluIG1pbmQuIAoKTGV0J3MgY3JlYXRlIGEgY29tYmluZWQgYW5ub3RhdGlvbiB0aGF0IGxhYmVscyBjZWxscyBhcyB0dW1vciBpZiB0aGUgY2VsbCB0eXBlIGxhYmVsIGlzIHR1bW9yIGluIGBTaW5nbGVSYCBhbmQgVW5rbm93biBpbiBjb25zZW5zdXMgbGFiZWxzLiAKQWxsIG90aGVyIGxhYmVscyB3aWxsIGJlIHRha2VuIGZyb20gdGhlIGNvbnNlbnN1cyBjZWxsIHR5cGVzLiAKCmBgYHtyfQojIGNsYXNzaWZ5IGJhc2VkIG9uIGNvbWJpbmVkIGNvbnNlbnN1cyBhbmQgc2luZ2xlciBhbm5vdGF0aW9uIAphbGxfaW5mb19kZiA8LSBhbGxfaW5mb19kZiB8PiAKICBkcGx5cjo6bXV0YXRlKGNvbWJpbmVkX2Fubm90YXRpb24gPSBkcGx5cjo6aWZfZWxzZSgKICAgIGNvbnNlbnN1c19hbm5vdGF0aW9uID09ICJVbmtub3duIiAmIHNpbmdsZXJfYW5ub3RhdGlvbiA9PSAidHVtb3IiLAogICAgInR1bW9yIiwKICAgIGNvbnNlbnN1c19hbm5vdGF0aW9uCiAgKSwKICAjIGx1bXAgZm9yIHBsb3R0aW5nIGxhdGVyIAogIGNvbWJpbmVkX2x1bXBlZCA9IGNvbWJpbmVkX2Fubm90YXRpb24gfD4KICAgIGZvcmNhdHM6OmZjdF9sdW1wX24oOSwgb3RoZXJfbGV2ZWwgPSAiQWxsIHJlbWFpbmluZyBjZWxsIHR5cGVzIiwgdGllcy5tZXRob2QgPSAiZmlyc3QiKSB8PgogICAgZm9yY2F0czo6ZmN0X2luZnJlcSgpIHw+IAogICAgZm9yY2F0czo6ZmN0X3JlbGV2ZWwoIkFsbCByZW1haW5pbmcgY2VsbCB0eXBlcyIsIGFmdGVyID0gSW5mKQogICkKCmFsbF9pbmZvX2RmIHw+IAogIGRwbHlyOjpjb3VudChjb21iaW5lZF9hbm5vdGF0aW9uKSB8PiAKICBkcGx5cjo6YXJyYW5nZShkZXNjKG4pKQpgYGAKCgojIyBVc2UgYEFVQ2VsbGAgYW5kIG1lYW4gZ2VuZSBzZXQgc2NvcmVzIHRvIHJlZmluZSB0dW1vciBjZWxscyBhbmQgaWRlbnRpZnkgdHVtb3IgY2VsbCBzdGF0ZXMgCgpXZSB1c2VkIGBBVUNlbGxgIHdpdGggZ2VuZSBzZXRzIHRoYXQgd2UgZXhwZWN0IHRvIGJlIHVwIGFuZCBkb3duIHJlZ3VsYXRlZCBpbiB0dW1vciBjZWxscy4gCldlIGNhbiBsb29rIGF0IHRoZSBBVUMgdmFsdWVzIGZvciB0aGVzZSBnZW5lIHNldHMgYWNyb3NzIGFsbCB0aGUgY2VsbCB0eXBlcyBhbmQgc2VlIGlmIHRoZXJlIGFyZSBhbnkgY2VsbHMgdGhhdCBzaG91bGQgYmUgY2xhc3NpZmllZCBhcyB0dW1vciB0aGF0IGFyZSBub3QuIApBZGRpdGlvbmFsbHksIHdlIGhhdmUgZ2VuZSBzZXRzIHRoYXQgY2FuIGhlbHAgdXMgZGlzdGluZ3Vpc2ggRVdTIGhpZ2ggYW5kIEVXUyBsb3cgdHVtb3IgY2VsbHMuIAoKRmlyc3Qgd2UnbGwgbG9vayBhdCB0aGUgbWVhbiBBVUMgdmFsdWUgZm9yIGVhY2ggZ2VuZSBzZXQgYWNyb3NzIGFsbCBjZWxsIHR5cGVzLiAKCkJlbG93IGlzIHNvbWUgaW5mb3JtYXRpb24gZm9yIHRoZSBnZW5lIHNldHM6ICAKCi0gQW55IGdlbmUgc2V0cyBsYWJlbGVkIHdpdGggYHVwYCByZXByZXNlbnQgYEVXUy1GTEkxYCB0YXJnZXQgZ2VuZXMgdGhhdCB3ZSBleHBlY3QgdG8gYmUgdXByZWd1bGF0ZWQgaW4gYEVXUy1GTEkxYCBoaWdoIHR1bW9yIGNlbGxzLiAKLSBBbnkgZ2VuZSBzZXRzIGxhYmVsZWQgd2l0aCBgZG93bmAgcmVwcmVzZW50IGBFV1MtRkxJMWAgcmVwcmVzc2VkIGdlbmVzIHRoYXQgd2UgZXhwZWN0IHRvIGJlIGRvd25yZWd1bGF0ZWQgaW4gYEVXUy1GTEkxYCBoaWdoIHR1bW9yIGNlbGxzIGFuZCB1cHJlZ3VsYXRlZCBpbiBgRVdTLUZMSTFgIGxvdyB0dW1vciBjZWxscy4gCi0gVGhlIGBheW5hdWQtZXdzLXRhcmdldHNgIGlzIGEgbGlzdCBvZiBnZW5lcyBmb3VuZCB0byBiZSB1cHJlZ3VsYXRlZCBpbiBgRVdTLUZMSTFgIGhpZ2ggdHVtb3IgY2VsbHMuIAotIFRoZSBgd3Jlbm4tbnQ1ZS1nZW5lc2AgaXMgYSBsaXN0IG9mIGdlbmVzIGZvdW5kIHRvIGJlIHVwcmVndWxhdGVkIGluIGBFV1MtRkxJMWAgbG93IHR1bW9yIGNlbGxzLyBjYW5jZXIgYXNzb2NpYXRlZCBmaWJyb2JsYXN0cy4KLSBUaGUgYGdvYnBfRUNNYCBhbmQgYGhhbGxtYXJrX0VNVGAgYXJlIGdlbmUgc2V0cyB0aGF0IGFyZSBoeXBvdGhlc2l6ZWQgdG8gYmUgdXByZWd1bGF0ZWQgaW4gYEVXUy1GTEkxYCBsb3cgdHVtb3IgY2VsbHMvIGNhbmNlciBhc3NvY2lhdGVkIGZpYnJvYmxhc3RzLgoKYGBge3J9CiMgcmVmb3JtYXQgYXVjIGRhdGEgZm9yIHBsb3RzCmF1Y19kZiA8LSBhbGxfaW5mb19kZiB8PiAKICB0aWR5cjo6cGl2b3RfbG9uZ2VyKHN0YXJ0c193aXRoKCJhdWNfIiksIG5hbWVzX3RvID0gImdlbmVzZXQiLCB2YWx1ZXNfdG8gPSAiYXVjIikgfD4gCiAgZHBseXI6Om11dGF0ZSgKICAgIGdlbmVzZXQgPSBzdHJpbmdyOjpzdHJfcmVtb3ZlKGdlbmVzZXQsICJhdWNfIikKICApIAoKIyBjcmVhdGUgbXR4IHdpdGggbWVhbiBhdWMgZm9yIGhlYXRtYXAgCmF1Y19tdHggPC0gYXVjX2RmIHw+IAogIGRwbHlyOjpncm91cF9ieShjb21iaW5lZF9hbm5vdGF0aW9uLCBnZW5lc2V0KSB8PiAKICBkcGx5cjo6c3VtbWFyaXplKAogICAgbWVhbl9hdWMgPSBtZWFuKGF1YykKICApIHw+IAogIHRpZHlyOjpwaXZvdF93aWRlcihuYW1lc19mcm9tID0gZ2VuZXNldCwgCiAgICAgICAgICAgICAgICAgICAgIHZhbHVlc19mcm9tID0gbWVhbl9hdWMpIHw+IAogIHRpZHlyOjpkcm9wX25hKCkgfD4gCiAgdGliYmxlOjpjb2x1bW5fdG9fcm93bmFtZXMoImNvbWJpbmVkX2Fubm90YXRpb24iKSB8PgogIGFzLm1hdHJpeCgpCgpDb21wbGV4SGVhdG1hcDo6SGVhdG1hcCgKICAgIGF1Y19tdHgsCiAgICAjIHNldCB0aGUgY29sb3Igc2NhbGUgYmFzZWQgb24gbWluIGFuZCBtYXggdmFsdWVzCiAgICBjb2wgPSBjaXJjbGl6ZTo6Y29sb3JSYW1wMihzZXEobWluKGF1Y19tdHgpLCBtYXgoYXVjX210eCksIGxlbmd0aCA9IDIpLCBjb2xvcnMgPSBjKCJ3aGl0ZSIsICIjMDAyNzRDIikpLAogICAgYm9yZGVyID0gVFJVRSwKICAgICMjIFJvdyBwYXJhbWV0ZXJzCiAgICBjbHVzdGVyX3Jvd3MgPSBUUlVFLAogICAgcm93X3RpdGxlID0gIkNvbWJpbmVkIGNlbGwgdHlwZXMiLAogICAgcm93X3RpdGxlX3NpZGUgPSAibGVmdCIsCiAgICByb3dfbmFtZXNfc2lkZSA9ICJsZWZ0IiwKICAgIHJvd19kZW5kX3NpZGUgPSAicmlnaHQiLAogICAgcm93X25hbWVzX2dwID0gZ3JpZDo6Z3Bhcihmb250c2l6ZSA9IDEwKSwKICAgICMjIENvbHVtbiBwYXJhbWV0ZXJzCiAgICBjb2x1bW5fdGl0bGUgPSAiTVNpZ0RCIGdlbmUgc2V0IiwKICAgIGNsdXN0ZXJfY29sdW1ucyA9IFRSVUUsCiAgICBzaG93X2NvbHVtbl9uYW1lcyA9IFRSVUUsCiAgICBjb2x1bW5fbmFtZXNfZ3AgPSBncmlkOjpncGFyKGZvbnRzaXplID0gOCksCiAgICBoZWF0bWFwX2xlZ2VuZF9wYXJhbSA9IGxpc3QoCiAgICAgIHRpdGxlID0gIk1lYW4gZXhwcmVzc2lvbiIKICAgICkKICApCgpgYGAKCi0gSGVyZSBpdCBsb29rcyBsaWtlIHRoZSB0dW1vciBjZWxscyBoYXZlIGhpZ2ggZXhwcmVzc2lvbiBvZiBgRVdTLUZMSTFgIHVwcmVndWxhdGVkIGdlbmUgc2V0cyBhbmQgbG93IGV4cHJlc3Npb24gb2YgYEVXUy1GTEkxYCByZXByZXNzZWQgdGFyZ2V0cy4gClRoaXMgc3VnZ2VzdHMgdGhlc2UgY2VsbHMgYXJlIGBFV1MtaGlnaGAuIAotIFRoaXMgc2FtZSBwYXR0ZXJuIGlzIHNlZW4gaW4gY2VsbHMgbGFiZWxlZCBhcyAic21vb3RoIG11c2NsZSBjZWxsIiBhbmQgInN0cm9tYWwgY2VsbCIuIApUaGVyZSBtYXkgZXZlbiBiZSBzb21lIHNsaWdodCBleHByZXNzaW9uIG9mIGBFV1MtRkxJMWAgdXByZWd1bGF0ZWQgZ2VuZSBzZXRzIGluICJtdXNjbGUgY2VsbCIsICJVbmtub3duIiwgYW5kIHNvbWUgb2YgdGhlIFQgY2VsbCBwb3B1bGF0aW9ucy4gClNvbWUgb2YgdGhlc2UgY2VsbHMgYXJlIHByb2JhYmx5IG1pc3Rha2VubHkgbGFiZWxlZCBhbmQgc2hvdWxkIGJlIHR1bW9yIGNlbGxzIHRoYXQgYXJlICJFV1MtaGlnaCIuCi0gRmlicm9ibGFzdHMgYW5kIGNob25kcm9jdHllcyBoYXZlIGxvdyBleHByZXNzaW9uIG9mIGBFV1MtRkxJMWAgdXByZWd1bGF0ZWQgZ2VuZSBzZXRzIGFuZCBoaWdoIGV4cHJlc3Npb24gb2YgYEVXUy1GTEkxYCByZXByZXNzZWQgdGFyZ2V0cy4gCk1hbnkgb2YgdGhlIGdlbmVzIGluIHRoZSBgRVdTLUZMSTFgIHJlcHJlc3NlZCB0YXJnZXRzIGFyZSBhbHNvIGdlbmVzIHVwcmVndWxhdGVkIGluIGZpYnJvYmxhc3RzIHNvIEknbSBzdXJlIHdlIGhhdmUgYSBtaXggb2YgdHVtb3IgYW5kIGZpYnJvYmxhc3RzIGluIHRoZXJlLgoKQmVjYXVzZSB3ZSBoYXZlIHNvbWUgbm9ybWFsIGNlbGwgdHlwZXMgdGhhdCBhcmUgc2hvd2luZyBnZW5lIHNpZ25hdHVyZXMgZXhwZWN0ZWQgdG8gYmUgc3BlY2lmaWMgdG8gdHVtb3IgY2VsbHMsIHdlIHNob3VsZCB1c2UgdGhlIEFVQyB2YWx1ZXMgdG8gaGVscCBwdWxsIG91dCBhbnkgcmVtYWluaW5nIGNlbGxzIHRoYXQgc2hvdWxkIGJlIHR1bW9yIGNlbGxzLiAKV2UgY2FuIGFsc28gdXNlIHRoZSB1cCBhbmQgZG93biBnZW5lIHNldHMgdG8gZGlzdGluZ3Vpc2ggRVdTLWhpZ2ggYW5kIEVXUy1sb3cgdHVtb3IgY2VsbHMuIAoKTGV0J3MgcGljayBzb21lIGdlbmUgc2V0cyB3aGVyZSB3ZSBzZWUgdmFyaWF0aW9uIGFjcm9zcyBjZWxsIHR5cGVzIHRoYXQgd2UgY2FuIHVzZSB0byBkZWZpbmUgY3V0b2ZmczogCgotIEVXUy1oaWdoIGdlbmUgc2V0czogYGF5bmF1ZC1ld3MtdGFyZ2V0c2AgYW5kIGBzdGFlZ2VgCi0gRVdTLWxvdyBnZW5lIHNldHM6IGB3cmVubi1udDVlLWdlbmVzYCBhbmQgYGhhbGxtYXJrX0VNVGAKCmBgYHtyfQpnZW5lc2V0c19vZl9pbnRlcmVzdCA8LSBjKAogICJhdWNfYXluYXVkLWV3cy10YXJnZXRzIiwKICAiYXVjX3N0YWVnZSIsCiAgImF1Y193cmVubi1udDVlLWdlbmVzIiwKICAiYXVjX2hhbGxtYXJrX0VNVCIKKQoKY2x1c3Rlcl9kZW5zaXR5X3Bsb3QoYWxsX2luZm9fZGYsIGdlbmVzZXRzX29mX2ludGVyZXN0LCAiY29tYmluZWRfbHVtcGVkIiwgIkFVQyIpCmBgYAoKV2UgY2FuIHVzZSB0aGVzZSBwbG90cyB0byBoZWxwIGRlZmluZSBjdXRvZmZzIG9mIEFVQyB2YWx1ZXMgdG8gdXNlIGZvciByZS1sYWJlbGluZyBhbnkgbm9ybWFsIGNlbGxzIHRoYXQgc2hvdWxkIGJlIHR1bW9yIGNlbGxzLiAKV2UgZXhwZWN0IHRoYXQgdGhlIGVuZG90aGVsaWFsIGNlbGxzIGFuZCBtYWNyb3BoYWdlcyBoYXZlIGxvdyBBVUMgdmFsdWVzIHNvIHdlIGNhbiB1c2UgdGhlc2UgY2VsbCB0eXBlcyBhcyB0aGUgYmFja2dyb3VuZC4gCkFueSBBVUMgdmFsdWVzIGFib3ZlIHRoYXQgYmFja2dyb3VuZCBzaG91bGQgYmUgY2xhc3NpZmllZCBhcyB0dW1vci4gCgpXaXRoIHRoaXMgaW4gbWluZCwgbGV0J3MgdXNlIHRoZXNlIGN1dG9mZnMgdG8gZGVmaW5lIHR1bW9yIGNlbGxzOgoKLSBgYXluYXVkLWV3cy10YXJnZXRzYCA+IDAuMDQgQVVDCi0gYHN0YWVnZWAgPiAwLjAxIEFVQwotIGB3cmVubi1udDVlLWdlbmVzYCA+IDAuMSBBVUMKLSBgaGFsbG1hcmtfRU1UYCA+IDAuMDUgQVVDIAoKTGV0J3MgcmUtcGxvdCB0aGUgZGVuc2l0eSBwbG90cyBhZGRpbmcgaW4gYSBsaW5lIGZvciB0aGUgQVVDIGN1dG9mZnMgd2UgaGF2ZSBjaG9zZW4uIAoKYGBge3J9CmN1dG9mZnMgPSBjKAogICJhdWNfYXluYXVkLWV3cy10YXJnZXRzIiA9IDAuMDQsCiAgImF1Y19zdGFlZ2UiID0gMC4wMSwKICAiYXVjX3dyZW5uLW50NWUtZ2VuZXMiID0gMC4xLAogICJhdWNfaGFsbG1hcmtfRU1UIiA9IDAuMDUKKQoKCmN1dG9mZnMgfD4KICAgIHB1cnJyOjppbWFwKFwoY3V0b2ZmLCBjb2x1bW4pewogICAgICBwbG90X2RlbnNpdHkoCiAgICAgICAgYWxsX2luZm9fZGYsCiAgICAgICAgY29sdW1uLAogICAgICAgICJjb21iaW5lZF9sdW1wZWQiCiAgICAgICkgKwogICAgICAgIHRoZW1lKHRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDgpKSArCiAgICAgICAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gY3V0b2ZmKQogICAgfSkgfD4KICAgIHBhdGNod29yazo6d3JhcF9wbG90cyhuY29sID0gMikKYGBgCgpJIGRvbid0IHRoaW5rIHRoZXNlIGN1dG9mZnMgYXJlIHBlcmZlY3QsIGJ1dCBJIHRoaW5rIHRoZXkgYXJlIHJlYXNvbmFibGUuIApBbnkgY2VsbHMgdGhhdCBhcmUgdG8gdGhlIHJpZ2h0IG9mIHRoZXNlIGN1dG9mZnMgd2lsbCBiZSBjbGFzc2lmaWVkIGFzIHR1bW9yLiAKCldlIGNhbiBhbHNvIHVzZSB0aGVzZSB0byBmdXJ0aGVyIHN0cmF0aWZ5IHR1bW9yIGNlbGxzIGludG8gRVdTIGhpZ2ggYW5kIEVXUyBsb3cgY2xhc3Nlcy4gCgotIGB0dW1vciBFV1MtbG93YDogSGlnaCBleHByZXNzaW9uIG9mIGB3cmVubi1udDVlLWdlbmVzYCAoPiAwLjEgQVVDKSBhbmQgRU1UIG1hcmtlcnMgYGhhbGxtYXJrX0VNVGAgKD4gMC4wNSBBVUMpCi0gYHR1bW9yIEVXUy1oaWdoYDogSGlnaCBleHByZXNzaW9uIG9mIGBheW5hdWQtZXdzLXRhcmdldHNgICg+MC4wNCBBVUMpIGFuZCBgc3RhZWdlYCAoPjAuMDEgQVVDKQoKYGBge3J9CiMgdG9wIGNlbGwgdHlwZSBvcmRlciAKZGVuc2l0eV9wbG90X29yZGVyIDwtIGMoCiAgInR1bW9yIEVXUy1oaWdoIiwKICAidHVtb3IgRVdTLWxvdyIsIAogICJVbmtub3duIiwKICAic21vb3RoIG11c2NsZSBjZWxsIiwKICAiZW5kb3RoZWxpYWwgY2VsbCIsCiAgIm1hY3JvcGhhZ2UiLAogICJmaWJyb2JsYXN0IiwKICAibXVzY2xlIGNlbGwiLAogICJtYXR1cmUgVCBjZWxsIiwKICAiQWxsIHJlbWFpbmluZyBjZWxsIHR5cGVzIgopCgojIGRlZmluZSAiZmluYWwuZmluYWwiIGNlbGwgdHlwZXMgCmFsbF9pbmZvX2RmIDwtIGFsbF9pbmZvX2RmIHw+IAogIGRwbHlyOjptdXRhdGUoCiAgICBmaW5hbF9hbm5vdGF0aW9uID0gZHBseXI6OmNhc2Vfd2hlbigKICAgICAgYGF1Y193cmVubi1udDVlLWdlbmVzYCA+IDAuMSAmIGF1Y19oYWxsbWFya19FTVQgPiAwLjA1IH4gInR1bW9yIEVXUy1sb3ciLAogICAgICBgYXVjX2F5bmF1ZC1ld3MtdGFyZ2V0c2AgPiAwLjA0ICYgYXVjX3N0YWVnZSA+IDAuMDEgfiAidHVtb3IgRVdTLWhpZ2giLAogICAgICAuZGVmYXVsdCA9IGNvbnNlbnN1c19hbm5vdGF0aW9uCiAgICApLAogICAgIyBsdW1wIHRvZ2V0aGVyIGZvciBlYXNpZXIgcGxvdHRpbmcKICAgIGZpbmFsX2x1bXBlZCA9IGZpbmFsX2Fubm90YXRpb24gfD4KICAgICAgZm9yY2F0czo6ZmN0X2x1bXBfbig5LCBvdGhlcl9sZXZlbCA9ICJBbGwgcmVtYWluaW5nIGNlbGwgdHlwZXMiLCB0aWVzLm1ldGhvZCA9ICJmaXJzdCIpIHw+CiAgICAgIGZvcmNhdHM6OmZjdF9pbmZyZXEoKSB8PgogICAgICBmb3JjYXRzOjpmY3RfcmVsZXZlbChkZW5zaXR5X3Bsb3Rfb3JkZXIpCiAgKQoKYWxsX2luZm9fZGYgfD4gCiAgZHBseXI6OmNvdW50KGZpbmFsX2Fubm90YXRpb24pIHw+IAogIGRwbHlyOjphcnJhbmdlKGRlc2MobikpCmBgYAoKTGV0J3MgcmVtYWtlIHRoZSBkZW5zaXR5IHBsb3QgdG8gc2VlIGlmIHdlIGhhdmUgcHVsbGVkIG91dCB0aGUgdHVtb3IgY2VsbHMgZnJvbSB0aGUgbm9ybWFsIGNlbGwgdHlwZXMgbGlrZSBtdXNjbGUgY2VsbHMuIAoKYGBge3J9CmNsdXN0ZXJfZGVuc2l0eV9wbG90KGFsbF9pbmZvX2RmLCBnZW5lc2V0c19vZl9pbnRlcmVzdCwgImZpbmFsX2x1bXBlZCIsICJBVUMiKQpgYGAKCkl0IGxvb2tzIGxpa2UgZ2VuZXJhbGx5IHRoZSBgdHVtb3IgRVdTLWhpZ2hgIGNlbGxzIHNob3cgaGlnaCBleHByZXNzaW9uIG9mIGJvdGggdXByZWd1bGF0ZWQgZ2VuZSBzZXRzIGFuZCBgdHVtb3IgRVdTLWxvd2AgY2VsbHMgc2hvdyBoaWdoIGV4cHJlc3Npb24gb2YgYm90aCBkb3ducmVndWxhdGVkIGdlbmUgc2V0cywgc28gSSBmZWVsIGdvb2QgYWJvdXQgdGhpcyEgCgpJIHdpbGwgYWxzbyBub3RlIHRoYXQgdGhlcmUgYXJlIHNvbWUgbXVzY2xlIGNlbGxzIHRoYXQgc2hvdyBzbGlnaHQgZXhwcmVzc2lvbiBvZiBgYXluYXVkLWV3cy10YXJnZXRzYCwgYnV0IHNob3cgbGl0dGxlIHRvIG5vIGV4cHJlc3Npb24gb2YgYHN0YWVnZWAgdGFyZ2V0cy4gCkknbSBzdGlsbCBhIGxpdHRsZSB3b3JyaWVkIHRoZXNlIGFyZSBhY3R1YWxseSB0dW1vciBjZWxscywgYnV0IGJlY2F1c2UgdGhleSBvbmx5IHNob3cgZXhwcmVzc2lvbiBvZiBvbmUgb2YgdGhlIGdlbmVzZXRzLCB3ZSBhcmUgcHJvYmFibHkgb2theSB0byBrZWVwIHRoZW0gYXMgaXMgbm90aW5nIHRoYXQgdGhlIGV4cHJlc3Npb24gcHJvZmlsZSBvZiBtdXNjbGUgY2VsbHMgaXMgdmVyeSBzaW1pbGFyIHRvIHRob3NlIG9mIHR1bW9yIGNlbGxzLCB0aGlzIGJlaW5nIHNhcmNvbWEuIAoKIyMgSWRlbnRpZnlpbmcgcHJvbGlmZXJhdGl2ZSBjZWxscwoKVGhlIGxhc3QgdHVtb3IgY2VsbCBzdGF0ZSB0aGF0IHdlIHdhbnQgdG8gaWRlbnRpZnkgaXMgaWYgYW55IGNlbGxzIGNhbiBiZSBjb25zaWRlcmVkIHByb2xpZmVyYXRpdmUgdXNpbmcgdGhlIHNldCBvZiBwcm9saWZlcmF0aXZlIG1hcmtlcnMgaW4gYHR1bW9yLWNlbGwtc3RhdGUtbWFya2Vycy50c3ZgLiAKTGV0J3MgbG9vayBhdCB0aGUgbWVhbiBleHByZXNzaW9uIG9mIHRoZXNlIG1hcmtlcnMgYWNyb3NzIGFsbCBjZWxscy4gClNpbmNlIHdlIGFyZSBwbG90dGluZyB0aGUgcHJvbGlmZXJhdGl2ZSBtYXJrZXJzLCBsZXQncyBhbHNvIGxvb2sgYXQgb3VyIG90aGVyIGN1c3RvbSBtYXJrZXIgZ2VuZSBzZXRzLiAKCmBgYHtyfQojIGdldCBjb2x1bW5zIHdpdGggbWVhbiBleHByZXNzaW9uIG9mIGN1c3RvbSBnZW5lIHNldHMgCm1lYW5fZXhwX2NvbHVtbnMgPC0gY29sbmFtZXMoYWxsX2luZm9fZGYpW3doaWNoKGVuZHNXaXRoKGNvbG5hbWVzKGFsbF9pbmZvX2RmKSwgIl9tZWFuIikpXQojIG1ha2Ugc3VyZSBvcmRlciBpcyBkZWZpbmVkIGJ5IGNlbGwgdHlwZSBvcmRlcgphbGxfaW5mb19kZiA8LSBhbGxfaW5mb19kZiB8PiAKICBkcGx5cjo6YXJyYW5nZShmaW5hbF9sdW1wZWQpCgpzaW5nbGVfYW5ub3RhdGlvbl9oZWF0bWFwKAogIGFsbF9pbmZvX2RmLCAKICBleHBfY29sdW1ucyA9IG1lYW5fZXhwX2NvbHVtbnMsIAogIGNlbGxfdHlwZV9jb2x1bW4gPSAiZmluYWxfbHVtcGVkIiwgCiAgbGVnZW5kX3RpdGxlID0gIkFVQyIKKQpgYGAKCkl0IGxvb2tzIGxpa2Ugd2UgZG8gaGF2ZSBhIHNldCBvZiBjZWxscyB0aGF0IGFyZSBgdHVtb3IgRVdTLWhpZ2hgIGFuZCBhbHNvIGhhdmUgaGlnaCBleHByZXNzaW9uIG9mIHByb2xpZmVyYXRpdmUgbWFya2Vycy4gCkJlY2F1c2Ugb2YgdGhpcywgbGV0J3MgbGFiZWwgYW55IGNlbGxzIHRoYXQgYXJlIGB0dW1vciBFV1MtaGlnaGAgYW5kIGhhdmUgbWVhbiBleHByZXNzaW9uIG9mIHByb2xpZmVyYXRpdmUgbWFya2VycyA+IDAgYXMgYHR1bW9yIEVXUy1oaWdoIHByb2xpZmVyYXRpdmVgLiAKCmBgYHtyfQojIHNldCBkZXNpcmVkIG9yZGVyIGZvciBwbG90dGluZyBmaW5hbCBhbm5vdGF0aW9ucyBsYXRlcgpjZWxsX3R5cGVfb3JkZXIgPC0gYygKICAidHVtb3IgRVdTLWhpZ2giLAogICJ0dW1vciBFV1MtaGlnaCBwcm9saWZlcmF0aXZlIiwKICAidHVtb3IgRVdTLWxvdyIsCiAgIm11c2NsZSBjZWxsIiwgCiAgInNtb290aCBtdXNjbGUgY2VsbCIsCiAgImZpYnJvYmxhc3QiLAogICJlbmRvdGhlbGlhbCBjZWxsIiwKICAibWFjcm9waGFnZSIsCiAgIm1hdHVyZSBUIGNlbGwiLAogICJVbmtub3duIiwKICAiQWxsIHJlbWFpbmluZyBjZWxsIHR5cGVzIgopCgphbGxfaW5mb19kZiA8LSBhbGxfaW5mb19kZiB8PiAKICBkcGx5cjo6bXV0YXRlKAogICAgZmluYWxfYW5ub3RhdGlvbiA9IGRwbHlyOjppZl9lbHNlKAogICAgICBmaW5hbF9hbm5vdGF0aW9uID09ICJ0dW1vciBFV1MtaGlnaCIgJiBwcm9saWZlcmF0aXZlX21lYW4gPiAwLAogICAgICAidHVtb3IgRVdTLWhpZ2ggcHJvbGlmZXJhdGl2ZSIsCiAgICAgIGZpbmFsX2Fubm90YXRpb24KICAgICksIAogICAgZmluYWxfbHVtcGVkID0gZmluYWxfYW5ub3RhdGlvbiB8PgogICAgICBmb3JjYXRzOjpmY3RfbHVtcF9uKDEwLCBvdGhlcl9sZXZlbCA9ICJBbGwgcmVtYWluaW5nIGNlbGwgdHlwZXMiLCB0aWVzLm1ldGhvZCA9ICJmaXJzdCIpIHw+CiAgICAgIGZvcmNhdHM6OmZjdF9pbmZyZXEoKSB8PgogICAgICBmb3JjYXRzOjpmY3RfcmVsZXZlbChjZWxsX3R5cGVfb3JkZXIpCiAgKQoKIyBmaXJzdCBnZXQgdGFsbHkgZm9yIGhvdyBtYW55IGxpYnJhcmllcyBlYWNoIGNlbGwgdHlwZSBpcyBwcmVzZW50IGluIAp0b3RhbF9saWJyYXJpZXMgPC0gYWxsX2luZm9fZGYgfD4gCiAgZHBseXI6OnNlbGVjdChmaW5hbF9hbm5vdGF0aW9uLCBsaWJyYXJ5X2lkKSB8PiAKICB1bmlxdWUoKSB8PiAjIGdldCByaWQgb2YgZHVwbGljYXRlIGNlbGxzCiAgZHBseXI6Omdyb3VwX2J5KGZpbmFsX2Fubm90YXRpb24pIHw+IAogIGRwbHlyOjpzdW1tYXJpemUoCiAgICB0b3RhbF9saWJyYXJpZXMgPSBsZW5ndGgobGlicmFyeV9pZCkpIHw+IAogIGRwbHlyOjphcnJhbmdlKGRlc2ModG90YWxfbGlicmFyaWVzKSkKCiMgcHJpbnQgb3V0IGJvdGggdG90YWwgY2VsbHMgYW5kIHRvdGFsIGxpYnJhcmllcyAKYWxsX2luZm9fZGYgfD4gCiAgZHBseXI6OmNvdW50KGZpbmFsX2Fubm90YXRpb24sIG5hbWUgPSAidG90YWxfY2VsbHMiKSB8PgogIGRwbHlyOjpsZWZ0X2pvaW4odG90YWxfbGlicmFyaWVzLCBieSA9ICJmaW5hbF9hbm5vdGF0aW9uIikgfD4gCiAgZHBseXI6OmFycmFuZ2UoZGVzYyh0b3RhbF9jZWxscykpCmBgYAoKCkZvciBwbG90dGluZyBwdXJwb3Nlcywgd2Ugd2lsbCBvbmx5IHNob3cgdGhlIHRvcCAxMCBjZWxsIHR5cGVzIHdpdGggbXVzY2xlIGNlbGwgYmVpbmcgdGhlIGxhc3QgY2VsbCB0eXBlLiAKSXQgbG9va3MgbGlrZSBhbGwgb3RoZXIgY2VsbCB0eXBlcyBhcmUgb25seSBwcmVzZW50IGluIDEtMyBsaWJyYXJpZXMuIApGb3IgYW55IGNlbGxzIHdoZXJlIHRoZXJlJ3Mgb25seSAxIGNlbGwgd2UgbWF5IHdhbnQgdG8gY29uZmlybSB0aGF0IHRob3NlIGNlbGxzIGV4cHJlc3MgdGhlIGV4cGVjdGVkIHRhcmdldHMsIGJ1dCB0aGF0IGlzIG91dHNpZGUgdGhlIHNjb3BlIG9mIHRoaXMgbm90ZWJvb2suIAoKIyMgVmFsaWRhdGlvbiBvZiBjZWxsIHR5cGUgYXNzaWdubWVudHMKCkluIHRoZSBmb2xsb3dpbmcgc2VjdGlvbiB3ZSdsbCBzaG93IHNvbWUgcGxvdHMgdG8gaGVscCB2YWxpZGF0ZSB0aGF0IHdlIGFyZSBjb250ZW50IHdpdGggb3VyIGNlbGwgdHlwZSBhc3NpZ25tZW50cy4gClRoZXNlIHBsb3RzIHdpbGwgbG9vayBzcGVjaWZpY2FsbHkgYXQgZXhwcmVzc2lvbiBvZiBhIHNldCBvZiBrZXkgbWFya2VyIGdlbmVzIHRoYXQgd2UgZXhwZWN0IHRvIHVwIGluIHRoZSBhc3NpZ25lZCBjZWxsIHR5cGVzLiAKVGhlc2UgbWFya2VycyBhcmUgZm91bmQgaW4gYHJlZmVyZW5jZXMvY29tYmluZWQtbWFya2Vycy50c3ZgIGFuZCBpbmNsdWRlIGEgc21hbGxlciBzZXQgb2YgbWFya2VycyBmb3IgZWFjaCBvZiB0aGUgY2VsbCB0eXBlcyB3ZSBoYXZlIGlkZW50aWZpZWQuIAoKYGBge3J9CiMgcmVhZCBpbiBmaWxlIHdpdGggdmFsaWRhdGlvbiBtYXJrZXJzIAp2YWxpZGF0aW9uX21hcmtlcnNfZGYgPC0gcmVhZHI6OnJlYWRfdHN2KHZhbGlkYXRpb25fbWFya2Vyc19maWxlKQoKIyBwdWxsIG91dCBsaXN0IG9mIGdlbmVzIApnZW5lcyA8LSB2YWxpZGF0aW9uX21hcmtlcnNfZGYgfD4KICBkcGx5cjo6cHVsbChlbnNlbWJsX2dlbmVfaWQpCgojIGdldCBpbmRpdmlkdWFsIGdlbmUgY291bnRzIGZvciBhbGwgbWFya2VyIGdlbmVzIApnZW5lX2N0cyA8LSBsb2djb3VudHMoc2NlW2dlbmVzLCBdKSB8PgogIGFzLm1hdHJpeCgpIHw+CiAgdCgpIHw+IAogIGFzLmRhdGEuZnJhbWUoKQpnZW5lX2N0cyRiYXJjb2RlcyA8LSByb3duYW1lcyhnZW5lX2N0cykKCiMgZ2V0IGFsbCB1bmlxdWUgY2VsbCB0eXBlcyBmcm9tIHRoZSBmaW5hbCBsdW1wZWQgZ3JvdXAgCmNlbGx0eXBlc19kZiA8LSBhbGxfaW5mb19kZiB8PiAKICBkcGx5cjo6c2VsZWN0KGJhcmNvZGVzLCBmaW5hbF9sdW1wZWQpCgojIGNyZWF0ZSBhIGRmIHRoYXQgaGFzIGdlbmUgZXhwcmVzc2lvbiBjb2x1bW4gYW5kIGNvbHVtbiBpbmRpY2F0aW5nIHdoZXRoZXIgb3Igbm90IHRoZSBnZW5lIGlzIGRldGVjdGVkIGluIHRoYXQgY2VsbCAKZ2VuZXNfZGYgPC0gZ2VuZV9jdHMgfD4gCiAgdGlkeXI6OnBpdm90X2xvbmdlcighYmFyY29kZXMsIG5hbWVzX3RvID0gImVuc2VtYmxfZ2VuZV9pZCIsIHZhbHVlc190byA9ICJnZW5lX2V4cCIpIHw+IAogIGRwbHlyOjpsZWZ0X2pvaW4oY2VsbHR5cGVzX2RmLCBieSA9IGMoImJhcmNvZGVzIikpIHw+IAogIGRwbHlyOjpsZWZ0X2pvaW4odmFsaWRhdGlvbl9tYXJrZXJzX2RmLCBieSA9IGMoImVuc2VtYmxfZ2VuZV9pZCIpKSB8PiAKICBkcGx5cjo6cm93d2lzZSgpIHw+IAogIGRwbHlyOjptdXRhdGUoCiAgICBkZXRlY3RlZCA9IGdlbmVfZXhwID4gMCAjIGNvbHVtbiBpbmRpY2F0aW5nIGlmIGdlbmUgaXMgcHJlc2VudCBvciBub3QKICApCgojIGdldCB0b3RhbCBudW1iZXIgb2YgY2VsbHMgcGVyIGZpbmFsIGFubm90YXRpb24gZ3JvdXAgCnRvdGFsX2NlbGxzX2RmIDwtIGdlbmVzX2RmIHw+IAogIGRwbHlyOjpzZWxlY3QoYmFyY29kZXMsIGZpbmFsX2x1bXBlZCkgfD4gCiAgdW5pcXVlKCkgfD4gCiAgZHBseXI6OmNvdW50KGZpbmFsX2x1bXBlZCwgbmFtZSA9ICJ0b3RhbF9jZWxscyIpCgojIGdldCB0b3RhbCBudW1iZXIgb2YgY2VsbHMgZWFjaCBnZW5lIGlzIGRldGVjdGVkIGluIHBlciBncm91cCAKIyBhbmQgbWVhbiBnZW5lIGV4cHJlc3Npb24gcGVyIGdyb3VwIApncm91cF9zdGF0c19kZiA8LSBnZW5lc19kZiB8PiAKICBkcGx5cjo6Z3JvdXBfYnkoZmluYWxfbHVtcGVkLCBlbnNlbWJsX2dlbmVfaWQpIHw+CiAgZHBseXI6OnN1bW1hcml6ZSgKICAgIGRldGVjdGVkX2NvdW50ID0gc3VtKGRldGVjdGVkKSwKICAgIG1lYW5fZXhwID0gbWVhbihnZW5lX2V4cCkKICApCgpnZW5lX3N1bW1hcnlfZGYgPC0gZ2VuZXNfZGYgfD4gCiAgIyBhZGQgdG90YWwgY2VsbHMKICBkcGx5cjo6bGVmdF9qb2luKHRvdGFsX2NlbGxzX2RmLCBieSA9IGMoImZpbmFsX2x1bXBlZCIpKSB8PiAKICAjIGFkZCBwZXIgZ2VuZSBzdGF0cwogIGRwbHlyOjpsZWZ0X2pvaW4oZ3JvdXBfc3RhdHNfZGYsIGJ5ID0gYygiZW5zZW1ibF9nZW5lX2lkIiwgImZpbmFsX2x1bXBlZCIpKSB8PiAKICBkcGx5cjo6c2VsZWN0KGdlbmVfc3ltYm9sLCBmaW5hbF9sdW1wZWQsIGNlbGxfdHlwZSwgdG90YWxfY2VsbHMsIGRldGVjdGVkX2NvdW50LCBtZWFuX2V4cCkgfD4gCiAgdW5pcXVlKCkgfD4KICBkcGx5cjo6cm93d2lzZSgpIHw+IAogIGRwbHlyOjptdXRhdGUoCiAgICAjIGdldCB0b3RhbCBwZXJjZW50CiAgICBwZXJjZW50X2V4cCA9IChkZXRlY3RlZF9jb3VudC90b3RhbF9jZWxscykgKiAxMDAsCiAgICAjIG9yZGVyIGdlbmVzIGJhc2VkIG9uIGNlbGwgdHlwZSB0aGV5IGluZGljYXRlCiAgICBnZW5lX3N5bWJvbCA9IGZhY3RvcihnZW5lX3N5bWJvbCwgbGV2ZWxzID0gdmFsaWRhdGlvbl9tYXJrZXJzX2RmJGdlbmVfc3ltYm9sKSwKICAgIGZpbmFsX2x1bXBlZCA9IGZvcmNhdHM6OmZjdF9yZWxldmVsKGZpbmFsX2x1bXBlZCwgY2VsbF90eXBlX29yZGVyKQogICAgCiAgKSAKYGBgCgpgYGB7ciwgZmlnLmhlaWdodD03LCBmaWcud2lkdGg9MTB9CiMgZmlsdGVyIG91dCBsb3cgZXhwcmVzc2VkIGdlbmVzCmRvdHBsb3RfZGYgPC0gZ2VuZV9zdW1tYXJ5X2RmIHw+IAogIGRwbHlyOjpmaWx0ZXIobWVhbl9leHAgPiAwLCBwZXJjZW50X2V4cCA+IDEwKSB8PgogIGRwbHlyOjphcnJhbmdlKGZpbmFsX2x1bXBlZCkgfD4gCiAgZHBseXI6Om11dGF0ZSh5X2xhYmVsID0gYXMuZmFjdG9yKGdsdWU6OmdsdWUoIntmaW5hbF9sdW1wZWR9ICh7dG90YWxfY2VsbHN9KSIpKSkKCgpkb3RwbG90IDwtIGdncGxvdChkb3RwbG90X2RmLCBhZXMoeSA9IGZvcmNhdHM6OmZjdF9yZXYoeV9sYWJlbCksIHggPSBnZW5lX3N5bWJvbCwgY29sb3IgPSBtZWFuX2V4cCwgc2l6ZSA9IHBlcmNlbnRfZXhwKSkgKwogIGdlb21fcG9pbnQoKSArCiAgc2NhbGVfY29sb3JfdmlyaWRpc19jKG9wdGlvbiA9ICJtYWdtYSIsIGxpbWl0cyA9IGMoMCwyLjUpLCBvb2IgPSBzY2FsZXM6OnNxdWlzaCkgKwogIHRoZW1lKAogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgaGp1c3QgPSAwLjUpCiAgKSArCiAgbGFicygKICAgIHggPSAiIiwKICAgIHkgPSAiRmluYWwgY2VsbCB0eXBlIGFubm90YXRpb24iCiAgKQoKIyBzcGVjaWZ5IGxlZ2VuZCBvcmRlciAKY29sb3Jfb3JkZXIgPC0gZG90cGxvdF9kZiB8PiAKICBkcGx5cjo6cHVsbChjZWxsX3R5cGUpIHw+IAogIHVuaXF1ZSgpCgpjb2xvcl9iYXIgPC0gZ2dwbG90KGRvdHBsb3RfZGYsIGFlcyh4ID0gZ2VuZV9zeW1ib2wsIHkgPSAxLCBmaWxsID0gY2VsbF90eXBlKSkgKyAKICBnZW9tX3RpbGUoKSArIAogIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGUgPSAnU2V0MScsIGJyZWFrcyA9IGNvbG9yX29yZGVyKSArCiAgZ2dtYXA6OnRoZW1lX25vdGhpbmcoKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpICsKICBsYWJzKGZpbGwgPSAiIikKCmRvdHBsb3QgKyBjb2xvcl9iYXIgKwogIHBhdGNod29yazo6cGxvdF9sYXlvdXQobmNvbCA9IDEsIGhlaWdodHMgPSBjKDQsIDAuMSkpIApgYGAKCkEgZmV3IG5vdGVzIGZyb20gdGhpcyBwbG90OiAKCi0gR2VuZXJhbGx5IHR1bW9yIG1hcmtlcnMgYXJlIHByZXNlbnQgdGhyb3VnaG91dCwgYWx0aG91Z2ggdGhleSBhcmUgaGlnaGVzdCBpbiB0dW1vciBjZWxscy4gCi0gRVdTLWhpZ2ggcHJvbGlmZXJhdGl2ZSBjZWxscyBzaG93IHN0cm9uZyBleHByZXNzaW9uIG9mIHByb2xpZmVyYXRpb24gbWFya2Vycy4gCi0gVHVtb3IgRVdTLWxvdyBjZWxscyBzaG93IHByZXR0eSBzdHJvbmcgZXhwcmVzc2lvbiBvZiB0aGUgYEVXUy1sb3dgIG1hcmtlcnMuIApUaGVzZSBtYXJrZXJzIGFyZSBhbHNvIHByZXNlbnQgaW4gZmlicm9ibGFzdHMsIGJ1dCB0byBhIGxlc3NlciBkZWdyZWUuIApBZGRpdGlvbmFsbHksIGBUTkNgLCB3aGljaCBoYXMgc2hvd24gdG8gYmUgc2VjcmV0ZWQgYnkgRXdpbmcgY2VsbHMgYW5kIGltcG9ydGFudCBpbiB0aGUgbWV0YXN0YXRpYyBwaGVub3R5cGUgb2YgRXdpbmcgc2FyY29tYSAoaHR0cHM6Ly9kb2kub3JnLzEwLjEwMTYvai5uZW8uMjAxOS4wOC4wMDcpLCBpcyBzcGVjaWZpYyB0byB0aGUgbG93IGNlbGxzIGFuZCBub3QgZm91bmQgaW4gdGhlIGZpYnJvYmxhc3RzLCB3aGljaCBtYWtlcyBtZSBtb3JlIGNvbmZpZGVudCB0aGF0IHRob3NlIGFyZSBpbmRlZWQgdHVtb3IgY2VsbHMuIAotIEVuZG90aGVsaWFsIGNlbGxzIHNob3cgc3Ryb25nIGV4cHJlc3Npb24gb2YgYFBFQ0FNMWAgYW5kIGBWV0ZgLCB3aGlsZSB0aGF0IGlzIG5vdCBzZWVuIGluIG1vc3Qgb2YgdGhlIG90aGVyIGNlbGxzLiAKLSBBbGwgaW1tdW5lIGNlbGwgdHlwZXMgc2hvdyBgUFRQUkNgIGFzIGV4cGVjdGVkIHdpdGggbWFjcm9waGFnZXMgYWxzbyBzaG93aW5nIGBNUkMxYCBhbmQgVCBjZWxscyBzaG93aW5nIGV4cHJlc3Npb24gb2YgYENEM0dgCgpMZXQncyBtYWtlIGEgaGVhdG1hcCB2ZXJzaW9uIG9mIHRoZSBzYW1lIHBsb3QuIAoKYGBge3IsIGZpZy53aWR0aD0xMH0KIyBub3RlIHRoYXQgd2UgY2FuJ3QgdXNlIHRoZSBzYW1lIGhlYXRtYXAgZnVuY3Rpb24gYXMgYmVmb3JlIHNpbmNlIHdlIGFyZSBsb29raW5nIGF0IGNlbGwgdHlwZXMgYXMgcm93cyByYXRoZXIgdGhhbiBnZW5lIHNldHMKIyB3ZSBhbHNvIHdhbnQgdG8gc3BlY2lmeSB0aGUgYW5ub3RhdGlvbiBuYW1lCgojIGZpcnN0IG1ha2UgYSBtdHggdG8gdXNlIGZvciB0aGUgaGVhdG1hcCB3aXRoIHJvd3MgYXMgY2VsbCB0eXBlcyBhbmQgZ2VuZXMgYXMgY29sdW1ucyAKaGVhdG1hcF9tdHggPC0gZ2VuZV9zdW1tYXJ5X2RmIHw+IAogIGRwbHlyOjpzZWxlY3QoZ2VuZV9zeW1ib2wsIGZpbmFsX2x1bXBlZCwgbWVhbl9leHApIHw+IAogIHRpZHlyOjpwaXZvdF93aWRlcigKICAgIG5hbWVzX2Zyb20gPSBnZW5lX3N5bWJvbCwKICAgIHZhbHVlc19mcm9tID0gbWVhbl9leHAKICApIHw+IAogIHRpYmJsZTo6Y29sdW1uX3RvX3Jvd25hbWVzKCJmaW5hbF9sdW1wZWQiKSB8PgogIGFzLm1hdHJpeCgpCiMgbWFrZSBzdXJlIGNlbGwgdHlwZXMgYXJlIHByZXNlbnQgaW4gdGhlIHJpZ2h0IG9yZGVyCmhlYXRtYXBfbXR4IDwtIGhlYXRtYXBfbXR4W2NlbGxfdHlwZV9vcmRlcixdCgojIGdldCBhbm5vdGF0aW9uIGNvbG9ycyBmb3IgZWFjaCBvZiB0aGUgbWFya2VyIGdlbmUgdHlwZXMgCm1hcmtlcl9nZW5lX2NhdGVnb3JpZXMgPC0gdW5pcXVlKHZhbGlkYXRpb25fbWFya2Vyc19kZiRjZWxsX3R5cGUpCm51bV9jYXRlZ29yaWVzIDwtIGxlbmd0aChtYXJrZXJfZ2VuZV9jYXRlZ29yaWVzKQpjYXRlZ29yeV9jb2xvcnMgPC0gcGFsZXR0ZS5jb2xvcnMocGFsZXR0ZSA9ICJEYXJrMiIpIHw+CiAgaGVhZChuID0gbnVtX2NhdGVnb3JpZXMpIHw+CiAgcHVycnI6OnNldF9uYW1lcyhtYXJrZXJfZ2VuZV9jYXRlZ29yaWVzKQoKCiMgY3JlYXRlIGFubm90YXRpb24gZm9yIGhlYXRtYXAKYW5ub3RhdGlvbiA8LSBDb21wbGV4SGVhdG1hcDo6Y29sdW1uQW5ub3RhdGlvbigKICBjYXRlZ29yeSA9IHZhbGlkYXRpb25fbWFya2Vyc19kZiRjZWxsX3R5cGUsCiAgY29sID0gbGlzdCgKICAgIGNhdGVnb3J5ID0gY2F0ZWdvcnlfY29sb3JzCiAgKSwKICBhbm5vdGF0aW9uX2xlZ2VuZF9wYXJhbSA9IGxpc3QoCiAgICAgIHRpdGxlID0gIk1hcmtlciBnZW5lIGNhdGVnb3J5IiwKICAgICAgbGFiZWxzID0gdW5pcXVlKHZhbGlkYXRpb25fbWFya2Vyc19kZiRjZWxsX3R5cGUpCiAgICApCikKCkNvbXBsZXhIZWF0bWFwOjpIZWF0bWFwKAogICAgaGVhdG1hcF9tdHgsCiAgICAjIHNldCB0aGUgY29sb3Igc2NhbGUgYmFzZWQgb24gbWluIGFuZCBtYXggdmFsdWVzCiAgICBjb2wgPSBjaXJjbGl6ZTo6Y29sb3JSYW1wMihzZXEobWluKGhlYXRtYXBfbXR4KSwgbWF4KGhlYXRtYXBfbXR4KSwgbGVuZ3RoID0gMiksIGNvbG9ycyA9IGMoIndoaXRlIiwgIiMwMDI3NEMiKSksCiAgICBib3JkZXIgPSBUUlVFLAogICAgIyMgUm93IHBhcmFtZXRlcnMKICAgIGNsdXN0ZXJfcm93cyA9IEZBTFNFLAogICAgcm93X3RpdGxlID0gIiIsCiAgICByb3dfdGl0bGVfc2lkZSA9ICJsZWZ0IiwKICAgIHJvd19uYW1lc19zaWRlID0gImxlZnQiLAogICAgcm93X2RlbmRfc2lkZSA9ICJyaWdodCIsCiAgICByb3dfbmFtZXNfZ3AgPSBncmlkOjpncGFyKGZvbnRzaXplID0gMTApLAogICAgIyMgQ29sdW1uIHBhcmFtZXRlcnMKICAgIGNsdXN0ZXJfY29sdW1ucyA9IEZBTFNFLAogICAgc2hvd19jb2x1bW5fbmFtZXMgPSBUUlVFLAogICAgY29sdW1uX25hbWVzX2dwID0gZ3JpZDo6Z3Bhcihmb250c2l6ZSA9IDgpLAogICAgdG9wX2Fubm90YXRpb24gPSBhbm5vdGF0aW9uLAogICAgaGVhdG1hcF9sZWdlbmRfcGFyYW0gPSBsaXN0KAogICAgICB0aXRsZSA9ICJNZWFuIGV4cHJlc3Npb24iCiAgICApCiAgKQpgYGAKCgpBbmQgZmluYWxseSB3ZSdsbCBsb29rIGF0IG91ciBhbm5vdGF0aW9ucyBvbiBhIFVNQVAhIApCZWNhdXNlLCB3aHkgbm90LiAKCmBgYHtyfQpnZ3Bsb3QoYWxsX2luZm9fZGYsIGFlcyh4ID0gVU1BUDEsIHkgPSBVTUFQMiwgY29sb3IgPSBmaW5hbF9sdW1wZWQpKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNSwgc2l6ZSA9IDAuMDEpICsKICAjIHNldCBjb2xvciBmb3IgYWxsIHJlbWFpbmluZyBjZWxsIHR5cGVzIHNpbmNlIHRoZXJlIGFyZSBtb3JlIGNvbG9ycyB0aGFuIGluIHRoZSBwYWxldHRlCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMocGFsZXR0ZS5jb2xvcnMocGFsZXR0ZSA9ICJEYXJrMiIpLCAiYmxhY2siLCAiZ3JleTY1IiwgImdyZXk5MCIpKSArCiAgbGFicyhjb2xvciA9ICIiKSArCiAgZ3VpZGVzKGNvbG9yID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3QoYWxwaGEgPSAxLCBzaXplID0gMS41KSkpCmBgYAoKYGBge3IsIGZpZy5oZWlnaHQ9N30KcGxvdF9mYWNldGVkX3VtYXAoYWxsX2luZm9fZGYsIGZpbmFsX2x1bXBlZCwgbGVnZW5kX3RpdGxlID0gIkZpbmFsIGNlbGwgdHlwZSBhbm5vdGF0aW9ucyIpICsKICB0aGVtZShzdHJpcC50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSA2KSkKYGBgCgoKIyMgRXhwb3J0IGNlbGwgdHlwZXMgCgpOb3cgdGhhdCB3ZSBoYXZlIG91ciBjZWxsIHR5cGVzIGxldCdzIGV4cG9ydCB0aGVtIHRvIGEgVFNWIHRvIHNhdmUgZm9yIGZ1dHVyZSB1c2UhIAoKYGBge3J9CmFubm90YXRpb25fZGYgPC0gYWxsX2luZm9fZGYgfD4gCiAgZHBseXI6Om11dGF0ZSgKICAgICMgcmVtb3ZlIGxpYnJhcnkgaWQgZnJvbSBiYXJjb2RlcyBzaW5jZSB3ZSBoYXZlIGEgbGlicmFyeSBpZCBjb2x1bW4gYWxyZWFkeSAKICAgIGJhcmNvZGVzID0gc3RyaW5ncjo6d29yZChiYXJjb2RlcywgLTEsIHNlcCA9ICItIiksCiAgICAjIGFzc2lnbiBvbnRvbG9neSBJRCB1c2luZyBjb25zZW5zdXMgb250b2xvZ3kgSUQKICAgICMgYW55dGhpbmcgd2l0aG91dCBhbiBJRCBpcyBlaXRoZXIgdW5rbm93biBvciB0dW1vciAKICAgIGZpbmFsX29udG9sb2d5ID0gZHBseXI6OmlmX2Vsc2UoCiAgICAgIGZpbmFsX2Fubm90YXRpb24gPT0gY29uc2Vuc3VzX2Fubm90YXRpb24sCiAgICAgIGNvbnNlbnN1c19vbnRvbG9neSwKICAgICAgZmluYWxfYW5ub3RhdGlvbgogICAgKQogICkgfD4gCiAgZHBseXI6OnNlbGVjdCgKICAgIGJhcmNvZGVzLAogICAgbGlicmFyeV9pZCwKICAgIHNhbXBsZV9pZCwKICAgIHNhbXBsZV90eXBlLAogICAgc2luZ2xlcl9vbnRvbG9neSwKICAgIHNpbmdsZXJfYW5ub3RhdGlvbiwKICAgIGNvbnNlbnN1c19hbm5vdGF0aW9uLAogICAgY29uc2Vuc3VzX29udG9sb2d5LAogICAgZmluYWxfYW5ub3RhdGlvbiwKICAgIGZpbmFsX29udG9sb2d5CiAgKQoKcmVhZHI6OndyaXRlX3Rzdihhbm5vdGF0aW9uX2RmLCBvdXRwdXRfZmlsZSkKYGBgCgoKIyMgU2Vzc2lvbiBpbmZvIAoKYGBge3Igc2Vzc2lvbiBpbmZvfQojIHJlY29yZCB0aGUgdmVyc2lvbnMgb2YgdGhlIHBhY2thZ2VzIHVzZWQgaW4gdGhpcyBhbmFseXNpcyBhbmQgb3RoZXIgZW52aXJvbm1lbnQgaW5mb3JtYXRpb24Kc2Vzc2lvbkluZm8oKQpgYGAKCg==