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:
- Any gene sets labeled with
up
represent
EWS-FLI1
target genes that we expect to be upregulated in
EWS-FLI1
high tumor cells.
- Any gene sets labeled with
down
represent
EWS-FLI1
repressed genes that we expect to be downregulated
in EWS-FLI1
high tumor cells and upregulated in
EWS-FLI1
low tumor cells.
- The
aynaud-ews-targets
is a list of genes found to be
upregulated in EWS-FLI1
high tumor cells.
- The
wrenn-nt5e-genes
is a list of genes found to be
upregulated in EWS-FLI1
low tumor cells/ cancer associated
fibroblasts.
- The
gobp_ECM
and hallmark_EMT
are gene
sets that are hypothesized to be upregulated in EWS-FLI1
low tumor cells/ cancer associated fibroblasts.
# 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"
)
)

- Here it looks like the tumor cells have high expression of
EWS-FLI1
upregulated gene sets and low expression of
EWS-FLI1
repressed targets. This suggests these cells are
EWS-high
.
- This same pattern is seen in cells labeled as “smooth muscle cell”
and “stromal cell”. There may even be some slight expression of
EWS-FLI1
upregulated gene sets in “muscle cell”, “Unknown”,
and some of the T cell populations. Some of these cells are probably
mistakenly labeled and should be tumor cells that are “EWS-high”.
- Fibroblasts and chondroctyes have low expression of
EWS-FLI1
upregulated gene sets and high expression of
EWS-FLI1
repressed targets. Many of the genes in the
EWS-FLI1
repressed targets are also genes upregulated in
fibroblasts so I’m sure we have a mix of tumor and fibroblasts in
there.
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:
- EWS-high gene sets:
aynaud-ews-targets
and
staege
- EWS-low gene sets:
wrenn-nt5e-genes
and
hallmark_EMT
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:
aynaud-ews-targets
> 0.04 AUC
staege
> 0.01 AUC
wrenn-nt5e-genes
> 0.1 AUC
hallmark_EMT
> 0.05 AUC
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.
tumor EWS-low
: High expression of
wrenn-nt5e-genes
(> 0.1 AUC) and EMT markers
hallmark_EMT
(> 0.05 AUC)
tumor EWS-high
: High expression of
aynaud-ews-targets
(>0.04 AUC) and staege
(>0.01 AUC)
# 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:
- Generally tumor markers are present throughout, although they are
highest in tumor cells.
- EWS-high proliferative cells show strong expression of proliferation
markers.
- Tumor EWS-low cells show pretty strong expression of the
EWS-low
markers. These markers are also present in
fibroblasts, but to a lesser degree. Additionally, TNC
,
which has shown to be secreted by Ewing cells and important in the
metastatic phenotype of Ewing sarcoma (https://doi.org/10.1016/j.neo.2019.08.007), is specific
to the low cells and not found in the fibroblasts, which makes me more
confident that those are indeed tumor cells.
- Endothelial cells show strong expression of
PECAM1
and
VWF
, while that is not seen in most of the other
cells.
- All immune cell types show
PTPRC
as expected with
macrophages also showing MRC1
and T cells showing
expression of CD3G
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==