load libraries
Load RDS with all
annotations
# Ensure clusters are ordered 0-18
All_samples_Merged$seurat_clusters <- factor(All_samples_Merged$seurat_clusters,
levels = as.character(0:18))
# Order samples logically
sample_order <- c("Healthy_Blood", "Healthy_Skin",
"SS1_Blood", "SS1_Skin",
"SS2_Blood", "SS2_Skin",
"SS3_Blood", "SS3_Skin",
"SS4_Blood", "SS4_Skin",
"SS5_Blood", "SS6_Blood")
All_samples_Merged$sample_id <- factor(All_samples_Merged$sample_id,
levels = sample_order)
cat("Total cells:", ncol(All_samples_Merged), "\n")
Total cells: 10785
cat("Total clusters:", length(unique(All_samples_Merged$seurat_clusters)), "\n\n")
Total clusters: 19
cat("Clusters (ordered 0-18):\n")
Clusters (ordered 0-18):
print(table(All_samples_Merged$seurat_clusters))
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
185 92 3241 1006 2278 223 404 230 923 84 829 727 124 150 183 48 4 44 10
cat("\n\nSamples (ordered):\n")
Samples (ordered):
print(table(All_samples_Merged$sample_id))
Healthy_Blood Healthy_Skin SS1_Blood SS1_Skin SS2_Blood SS2_Skin SS3_Blood
4386 33 1635 58 935 38 997
SS3_Skin SS4_Blood SS4_Skin SS5_Blood SS6_Blood
23 373 184 959 1164
Create Annotation
Summary Table by Clusters
library(dplyr)
library(tidyr)
library(purrr)
# Define annotation methods
methods <- c(
"predicted.id", # scPred
"predicted.celltype.l2", # Azimuth (l2 prediction)
"singler.immune", # SingleR
"scATOMIC_annotation" # scATOMIC
)
# Create summary - most common label per cluster for each method
annotation_summary <- map_dfr(methods, function(m) {
df <- All_samples_Merged@meta.data
df %>%
filter(!is.na(.data[[m]])) %>%
group_by(seurat_clusters, label = .data[[m]]) %>%
summarise(n = n(), .groups = "drop") %>%
group_by(seurat_clusters) %>%
slice_max(n, n = 1, with_ties = FALSE) %>%
mutate(method = m)
})
# Rename methods for display
annotation_summary <- annotation_summary %>%
mutate(method = recode(method,
"predicted.id" = "scPred",
"predicted.celltype.l2" = "Azimuth.l2",
"singler.immune" = "SingleR(Immune)",
"scATOMIC_annotation" = "scATOMIC"))
# Set method order
annotation_summary$method <- factor(annotation_summary$method,
levels = c("scPred", "Azimuth.l2",
"SingleR(Immune)", "scATOMIC"))
# Ensure cluster order 0-18
annotation_summary$seurat_clusters <- factor(annotation_summary$seurat_clusters,
levels = as.character(0:18))
# Save annotation summary by clusters (long format)
write.csv(annotation_summary,
"annotation_summary_by_clusters_04-02-2026.csv",
row.names = FALSE)
# Create and save wide format
annotation_table_clusters_wide <- annotation_summary %>%
select(seurat_clusters, method, label) %>%
pivot_wider(names_from = method, values_from = label)
write.csv(annotation_table_clusters_wide,
"annotation_table_clusters_wide_04-02-2026.csv",
row.names = FALSE)
# Display
cat("\n=== Annotation Summary by Clusters ===\n\n")
=== Annotation Summary by Clusters ===
print(annotation_summary)
# A tibble: 76 × 4
# Groups: seurat_clusters [19]
seurat_clusters label n method
<fct> <chr> <int> <fct>
1 0 CD4 T cell 123 scPred
2 1 cMono 85 scPred
3 2 CD4 T cell 3137 scPred
4 3 CD8 T cell 981 scPred
5 4 CD8 T cell 1393 scPred
6 5 NK cell 210 scPred
7 6 B cell 374 scPred
8 7 CD4 T cell 230 scPred
9 8 CD4 T cell 922 scPred
10 9 ncMono 84 scPred
# ℹ 66 more rows
# ℹ Use `print(n = ...)` to see more rows
cat("\n✓ Saved: annotation_summary_by_clusters_04-02-2026.csv (long format)\n")
✓ Saved: annotation_summary_by_clusters_04-02-2026.csv (long format)
cat("✓ Saved: annotation_table_clusters_wide_04-02-2026.csv (wide format)\n")
✓ Saved: annotation_table_clusters_wide_04-02-2026.csv (wide format)
Summary
Statistics
# Cell counts per method
cat("\n=== Cells Annotated Per Method ===\n\n")
=== Cells Annotated Per Method ===
method_cols <- c("predicted.id", "predicted.celltype.l2", "singler.immune", "scATOMIC_annotation")
for(m in method_cols){
n_annotated <- sum(!is.na(All_samples_Merged@meta.data[[m]]))
cat(sprintf("%-25s: %5d cells (%.1f%%)\n",
m, n_annotated, n_annotated/ncol(All_samples_Merged)*100))
}
predicted.id : 10785 cells (100.0%)
predicted.celltype.l2 : 10785 cells (100.0%)
singler.immune : 10710 cells (99.3%)
scATOMIC_annotation : 10752 cells (99.7%)
# Number of unique labels per method
cat("\n=== Unique Labels Per Method ===\n\n")
=== Unique Labels Per Method ===
annotation_summary %>%
group_by(method) %>%
summarise(n_unique_labels = n_distinct(label)) %>%
print()
# A tibble: 4 × 2
method n_unique_labels
<fct> <int>
1 scPred 8
2 Azimuth.l2 10
3 SingleR(Immune) 9
4 scATOMIC 10
Basic Heatmap
Visualization
library(ggplot2)
ggplot(annotation_summary, aes(x = seurat_clusters, y = method, fill = label)) +
geom_tile(color = "white", linewidth = 0.5) +
scale_fill_discrete() +
labs(
x = "Seurat Clusters",
y = "Annotation Method",
fill = "Assigned Cell Type",
title = "Cross-Method Comparison of Cell Type Annotations"
) +
theme_minimal(base_size = 14) +
theme(
axis.text.x = element_text(angle = 45, hjust = 1),
axis.title = element_text(face = "bold"),
plot.title = element_text(hjust = 0.5, face = "bold", size = 16)
)

NA
NA
Enhanced Heatmap with
Custom Colors
library(RColorBrewer)
# Generate color palette
num_colors <- length(unique(annotation_summary$label))
my_colors <- colorRampPalette(brewer.pal(8, "Set2"))(num_colors)
p_heatmap <- ggplot(annotation_summary, aes(x = seurat_clusters, y = method, fill = label)) +
geom_tile(color = "white", linewidth = 0.5) +
scale_fill_manual(values = my_colors) +
labs(
x = "Seurat Clusters",
y = "Annotation Method",
fill = "Assigned Cell Type",
title = "Cross-Method Comparison of Cell Type Annotations"
) +
theme_minimal(base_size = 14) +
theme(
axis.text.x = element_text(angle = 45, hjust = 1),
axis.title = element_text(face = "bold"),
plot.title = element_text(hjust = 0.5, face = "bold", size = 16),
legend.position = "right"
)
print(p_heatmap)

# Save high-quality versions
ggsave("annotation_heatmap_04-02-2026.png", plot = p_heatmap,
width = 14, height = 5, dpi = 300, bg = "white")
ggsave("annotation_heatmap_04-02-2026.pdf", plot = p_heatmap,
width = 14, height = 5)
cat("\n✓ Heatmap saved as PNG and PDF\n")
✓ Heatmap saved as PNG and PDF
Heatmap: Methods ×
Clusters
library(RColorBrewer)
# Generate color palette
num_colors <- length(unique(annotation_summary$label))
my_colors <- colorRampPalette(brewer.pal(8, "Set2"))(num_colors)
p_heatmap_clusters <- ggplot(annotation_summary,
aes(x = seurat_clusters, y = method, fill = label)) +
geom_tile(color = "white", linewidth = 0.5) +
scale_fill_manual(values = my_colors) +
scale_x_discrete(drop = FALSE) + # Keep all cluster levels even if missing
labs(
x = "Seurat Clusters (0-18)",
y = "Annotation Method",
fill = "Assigned Cell Type",
title = "Cross-Method Comparison of Cell Type Annotations"
) +
theme_minimal(base_size = 14) +
theme(
axis.text.x = element_text(angle = 45, hjust = 1),
axis.title = element_text(face = "bold"),
plot.title = element_text(hjust = 0.5, face = "bold", size = 16),
legend.position = "right"
)
print(p_heatmap_clusters)

# Save
ggsave("annotation_heatmap_methods_clusters_04-02-2026.png",
plot = p_heatmap_clusters, width = 14, height = 5, dpi = 300, bg = "white")
ggsave("annotation_heatmap_methods_clusters_04-02-2026.pdf",
plot = p_heatmap_clusters, width = 14, height = 5)
cat("\n✓ Methods × Clusters heatmap saved\n")
✓ Methods × Clusters heatmap saved
Heatmap: Clusters ×
Samples (scATOMIC annotations)
# Create summary by cluster and sample for scATOMIC
cluster_sample_summary <- All_samples_Merged@meta.data %>%
filter(!is.na(scATOMIC_annotation)) %>%
group_by(seurat_clusters, sample_id, scATOMIC_annotation) %>%
summarise(n = n(), .groups = "drop") %>%
group_by(seurat_clusters, sample_id) %>%
slice_max(n, n = 1, with_ties = FALSE) %>%
ungroup()
# Ensure cluster and sample order
cluster_sample_summary$seurat_clusters <- factor(cluster_sample_summary$seurat_clusters,
levels = as.character(0:18))
cluster_sample_summary$sample_id <- factor(cluster_sample_summary$sample_id,
levels = sample_order)
# Plot
p_cluster_sample <- ggplot(cluster_sample_summary,
aes(x = sample_id, y = seurat_clusters, fill = scATOMIC_annotation)) +
geom_tile(color = "white", linewidth = 0.5) +
scale_fill_manual(values = my_colors) +
scale_y_discrete(drop = FALSE) + # Keep all cluster levels
scale_x_discrete(drop = FALSE) + # Keep all sample levels
labs(
x = "Sample ID",
y = "Seurat Clusters (0-18)",
fill = "scATOMIC Annotation",
title = "Cluster Distribution Across Samples (scATOMIC Annotation)"
) +
theme_minimal(base_size = 14) +
theme(
axis.text.x = element_text(angle = 45, hjust = 1),
axis.title = element_text(face = "bold"),
plot.title = element_text(hjust = 0.5, face = "bold", size = 16),
legend.position = "right"
)
print(p_cluster_sample)

# Save
ggsave("annotation_heatmap_clusters_samples_04-02-2026.png",
plot = p_cluster_sample, width = 16, height = 10, dpi = 300, bg = "white")
ggsave("annotation_heatmap_clusters_samples_04-02-2026.pdf",
plot = p_cluster_sample, width = 16, height = 10)
cat("\n✓ Clusters × Samples heatmap saved\n")
✓ Clusters × Samples heatmap saved
Alluvial Diagram -
Annotation Flow Across Methods
library(dplyr)
library(tidyr)
library(ggplot2)
library(ggalluvial)
# Extract columns safely using base R (avoids select() conflicts)
cols_needed <- c("seurat_clusters", "predicted.id", "predicted.celltype.l2",
"singler.immune", "scATOMIC_annotation")
alluvial_df_raw <- All_samples_Merged@meta.data[, cols_needed]
colnames(alluvial_df_raw) <- c("Cluster", "scPred", "Azimuth", "SingleR", "scATOMIC")
# Summarize and filter
alluvial_data <- alluvial_df_raw %>%
mutate(Cluster = factor(as.character(Cluster), levels = as.character(0:18))) %>%
filter(!is.na(scPred) & !is.na(Azimuth) & !is.na(SingleR) & !is.na(scATOMIC)) %>%
group_by(Cluster, scPred, Azimuth, SingleR, scATOMIC) %>%
summarise(Freq = n(), .groups = "drop") %>%
filter(Freq > 5)
# Check
print(paste("Rows in plot data:", nrow(alluvial_data)))
[1] "Rows in plot data: 151"
# Plot
p_alluvial <- ggplot(
alluvial_data,
aes(axis1 = scPred, axis2 = Azimuth, axis3 = SingleR, axis4 = scATOMIC, y = Freq)
) +
geom_alluvium(aes(fill = scATOMIC), alpha = 0.7, curve_type = "sigmoid") +
geom_stratum(width = 1/5, fill = "white", color = "grey50") +
geom_text(stat = "stratum", aes(label = after_stat(stratum)),
size = 3, fontface = "bold") +
scale_x_discrete(
limits = c("scPred", "Azimuth", "SingleR", "scATOMIC"),
expand = c(0.15, 0.05)
) +
labs(
title = "Annotation Flow Across Methods",
subtitle = "Flow shows how cell annotations change across different methods",
y = "Number of Cells",
fill = "scATOMIC Label"
) +
theme_minimal(base_size = 14) +
theme(
legend.position = "bottom",
axis.text.x = element_text(face = "bold", size = 12),
plot.title = element_text(hjust = 0.5, face = "bold", size = 16),
plot.subtitle = element_text(hjust = 0.5, size = 12),
legend.text = element_text(size = 8)
) +
guides(fill = guide_legend(nrow = 3))
print(p_alluvial)

# Save
ggsave("annotation_alluvial_flow_04-02-2026.png", plot = p_alluvial,
width = 12, height = 8, dpi = 300, bg = "white")
ggsave("annotation_alluvial_flow_04-02-2026.pdf", plot = p_alluvial,
width = 12, height = 8)
cat("\n✓ Alluvial diagram saved\n")
✓ Alluvial diagram saved
Method Agreement
Analysis
Calculate Pairwise
Agreement
library(dplyr)
library(tidyr)
# Create matrix: clusters × methods
# Step 1: Extract columns safely
cols_needed <- c("seurat_clusters", "predicted.id", "predicted.celltype.l2",
"singler.immune", "scATOMIC_annotation")
cluster_annotation_df <- All_samples_Merged@meta.data[, cols_needed]
# Step 2: Pivot and find dominant label per cluster per method
cluster_annotation_matrix <- cluster_annotation_df %>%
pivot_longer(cols = -seurat_clusters, names_to = "method", values_to = "label") %>%
filter(!is.na(label)) %>%
group_by(seurat_clusters, method, label) %>%
summarise(n = n(), .groups = "drop") %>%
group_by(seurat_clusters, method) %>%
slice_max(n, n = 1, with_ties = FALSE) %>%
ungroup() %>%
dplyr::select(seurat_clusters, method, label) %>%
pivot_wider(names_from = method, values_from = label)
# Convert to matrix
mat <- as.matrix(cluster_annotation_matrix[, -1])
rownames(mat) <- cluster_annotation_matrix$seurat_clusters
# Calculate method similarity (Agreement proportion)
method_similarity <- function(m1, m2) {
sum(m1 == m2, na.rm = TRUE) / sum(!is.na(m1) & !is.na(m2))
}
# Create similarity matrix
n_methods <- ncol(mat)
sim_matrix <- matrix(0, n_methods, n_methods)
colnames(sim_matrix) <- c("scPred", "Azimuth.l2", "SingleR", "scATOMIC")
rownames(sim_matrix) <- c("scPred", "Azimuth.l2", "SingleR", "scATOMIC")
for(i in 1:n_methods) {
for(j in 1:n_methods) {
sim_matrix[i, j] <- method_similarity(mat[, i], mat[, j])
}
}
# Display agreement matrix
cat("\n=== Pairwise Agreement Between Methods ===\n\n")
=== Pairwise Agreement Between Methods ===
print(round(sim_matrix, 3))
scPred Azimuth.l2 SingleR scATOMIC
scPred 1.000 0 0.105 0
Azimuth.l2 0.000 1 0.000 0
SingleR 0.105 0 1.000 0
scATOMIC 0.000 0 0.000 1
# Save as CSV
write.csv(sim_matrix, "method_agreement_matrix_04-02-2026.csv", row.names = TRUE)
cat("\n✓ Saved: method_agreement_matrix_04-02-2026.csv\n")
✓ Saved: method_agreement_matrix_04-02-2026.csv
Agreement
Heatmap
library(pheatmap)
# Plot agreement heatmap
pheatmap(
sim_matrix,
display_numbers = TRUE,
number_format = "%.2f",
cluster_rows = TRUE,
cluster_cols = TRUE,
color = colorRampPalette(c("white", "orange", "red"))(100),
main = "Pairwise Agreement Between Annotation Methods",
fontsize = 14,
fontsize_number = 12,
angle_col = 45,
border_color = "grey60"
)
# Save PNG
png("annotation_method_agreement_heatmap_04-02-2026.png",
width = 10, height = 10, units = "in", res = 300)

pheatmap(
sim_matrix,
display_numbers = TRUE,
number_format = "%.2f",
cluster_rows = TRUE,
cluster_cols = TRUE,
color = colorRampPalette(c("white", "orange", "red"))(100),
main = "Pairwise Agreement Between Annotation Methods",
fontsize = 14,
fontsize_number = 12,
angle_col = 45,
border_color = "grey60"
)
dev.off()
png
3
# Save PDF
pdf("annotation_method_agreement_heatmap_04-02-2026.pdf",
width = 10, height = 10)

pheatmap(
sim_matrix,
display_numbers = TRUE,
number_format = "%.2f",
cluster_rows = TRUE,
cluster_cols = TRUE,
color = colorRampPalette(c("white", "orange", "red"))(100),
main = "Pairwise Agreement Between Annotation Methods",
fontsize = 14,
fontsize_number = 12,
angle_col = 45,
border_color = "grey60"
)
dev.off()
png
3
cat("\n✓ Agreement heatmap saved\n")
✓ Agreement heatmap saved
Hierarchical
Clustering Dendrogram
# Convert similarity to distance
dist_matrix <- as.dist(1 - sim_matrix)
Found more than one class "dist" in cache; using the first, from namespace 'spam'
Also defined by 'BiocGenerics'
# Hierarchical clustering
hc <- hclust(dist_matrix, method = "ward.D2")
# Plot dendrogram
par(cex = 1.2)
plot(hc,
main = "Hierarchical Clustering of Annotation Methods",
xlab = "Annotation Method",
ylab = "Distance (1 - Agreement)",
hang = -1,
cex.main = 1.5,
cex.lab = 1.3,
cex.axis = 1.2)
# Save PNG
png("annotation_method_dendrogram_04-02-2026.png",
width = 12, height = 8, units = "in", res = 300)
par(cex = 1.2)
plot(hc,
main = "Hierarchical Clustering of Annotation Methods",
xlab = "Annotation Method",
ylab = "Distance (1 - Agreement)",
hang = -1,
cex.main = 1.5,
cex.lab = 1.3,
cex.axis = 1.2)
dev.off()
png
2
# Save PDF
pdf("annotation_method_dendrogram_04-02-2026.pdf",
width = 12, height = 8)
par(cex = 1.2)
plot(hc,
main = "Hierarchical Clustering of Annotation Methods",
xlab = "Annotation Method",
ylab = "Distance (1 - Agreement)",
hang = -1,
cex.main = 1.5,
cex.lab = 1.3,
cex.axis = 1.2)
dev.off()
png
2

cat("\n✓ Dendrogram saved\n")
✓ Dendrogram saved
Dominant Label Table
(Simplest)
# A clean table showing the dominant label from each method per cluster:
library(dplyr)
library(knitr)
library(kableExtra)
# Extract dominant label per cluster per method
cols_needed <- c("seurat_clusters", "predicted.id", "predicted.celltype.l2",
"singler.immune", "scATOMIC_annotation")
annotation_table <- All_samples_Merged@meta.data[, cols_needed] %>%
group_by(seurat_clusters) %>%
summarise(
scPred = names(sort(table(predicted.id), decreasing = TRUE))[1],
Azimuth = names(sort(table(predicted.celltype.l2), decreasing = TRUE))[1],
SingleR = names(sort(table(singler.immune), decreasing = TRUE))[1],
scATOMIC = names(sort(table(scATOMIC_annotation), decreasing = TRUE))[1],
n_cells = n(),
.groups = "drop"
) %>%
arrange(seurat_clusters)
# Add consensus column (TRUE if all 4 agree)
annotation_table <- annotation_table %>%
mutate(
Consensus = (scPred == Azimuth & Azimuth == SingleR & SingleR == scATOMIC)
)
# Display
kable(annotation_table, caption = "Dominant Cell Type Annotation per Cluster") %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
full_width = FALSE, font_size = 10) %>%
column_spec(7, background = ifelse(annotation_table$Consensus, "#d4edda", "#f8d7da"))
Dominant Cell Type Annotation per Cluster
| seurat_clusters |
scPred |
Azimuth |
SingleR |
scATOMIC |
n_cells |
Consensus |
| 0 |
CD4 T cell |
CD4 TCM |
T cells, CD4+, Th1 |
Effector/Memory CD4+ T cells |
185 |
FALSE |
| 1 |
cMono |
CD14 Mono |
Monocytes, CD14+ |
CD14 Monocyte |
92 |
FALSE |
| 2 |
CD4 T cell |
CD4 TCM |
T cells, CD4+, memory TREG |
Naive CD4+ T cells |
3241 |
FALSE |
| 3 |
CD8 T cell |
CD8 TEM |
NK cells |
Effector/Memory CD8+ T cells |
1006 |
FALSE |
| 4 |
CD8 T cell |
CD4 TCM |
T cells, CD8+, naive |
Naive CD8+ T cells |
2278 |
FALSE |
| 5 |
NK cell |
NK |
NK cells |
Natural killer cell |
223 |
FALSE |
| 6 |
B cell |
B naive |
B cells, naive |
B Cell |
404 |
FALSE |
| 7 |
CD4 T cell |
CD4 TCM |
T cells, CD4+, Th2 |
Naive CD4+ T cells |
230 |
FALSE |
| 8 |
CD4 T cell |
CD4 TCM |
T cells, CD4+, Th17 |
Naive CD4+ T cells |
923 |
FALSE |
| 9 |
ncMono |
CD16 Mono |
Monocytes, CD16+ |
CD16 Monocyte |
84 |
FALSE |
| 10 |
cMono |
CD14 Mono |
Monocytes, CD14+ |
CD14 Monocyte |
829 |
FALSE |
| 11 |
CD4 T cell |
CD4 TCM |
T cells, CD4+, memory TREG |
Naive CD4+ T cells |
727 |
FALSE |
| 12 |
CD4 T cell |
CD4 TCM |
T cells, CD4+, memory TREG |
Effector/Memory CD4+ T cells |
124 |
FALSE |
| 13 |
cDC |
cDC2 |
Monocytes, CD14+ |
cDC2 |
150 |
FALSE |
| 14 |
CD4 T cell |
CD4 Proliferating |
T cells, CD4+, memory TREG |
Effector/Memory CD8+ T cells |
183 |
FALSE |
| 15 |
CD4 T cell |
HSPC |
T cells, CD4+, Th2 |
HSPC |
48 |
FALSE |
| 16 |
CD4 T cell |
CD4 TCM |
T cells, CD4+, memory TREG |
Effector/Memory CD4+ T cells |
4 |
FALSE |
| 17 |
Plasma cell |
Plasmablast |
Monocytes, CD14+ |
CD14 Monocyte |
44 |
FALSE |
| 18 |
CD8 T cell |
CD8 TEM |
NK cells |
Effector/Memory CD8+ T cells |
10 |
FALSE |
# Save as CSV
write.csv(annotation_table, "annotation_comparison_clean_table_04-02-2026.csv",
row.names = FALSE)
cat("\n✓ Clean annotation table saved\n")
✓ Clean annotation table saved
Simplified Text-Based
Heatmap
library(ggplot2)
library(dplyr)
library(tidyr)
# Prepare data
cols_needed <- c("seurat_clusters", "predicted.id", "predicted.celltype.l2",
"singler.immune", "scATOMIC_annotation")
heatmap_data <- All_samples_Merged@meta.data[, cols_needed] %>%
group_by(seurat_clusters) %>%
summarise(
scPred = names(sort(table(predicted.id), decreasing = TRUE))[1],
Azimuth = names(sort(table(predicted.celltype.l2), decreasing = TRUE))[1],
SingleR = names(sort(table(singler.immune), decreasing = TRUE))[1],
scATOMIC = names(sort(table(scATOMIC_annotation), decreasing = TRUE))[1],
.groups = "drop"
) %>%
pivot_longer(cols = c(scPred, Azimuth, SingleR, scATOMIC),
names_to = "Method", values_to = "CellType") %>%
mutate(
Method = factor(Method, levels = c("scPred", "Azimuth", "SingleR", "scATOMIC")),
seurat_clusters = factor(seurat_clusters, levels = as.character(0:18))
)
# Create text-based heatmap
p_text_heatmap <- ggplot(heatmap_data, aes(x = seurat_clusters, y = Method, label = CellType)) +
geom_tile(color = "grey30", fill = "white", linewidth = 0.5) +
geom_text(size = 2.5, hjust = 0.5, vjust = 0.5) +
labs(
title = "Cell Type Annotation Comparison Across Methods",
x = "Seurat Cluster",
y = "Annotation Method"
) +
theme_minimal(base_size = 14) +
theme(
axis.text.x = element_text(face = "bold", size = 12),
axis.text.y = element_text(face = "bold", size = 12),
plot.title = element_text(hjust = 0.5, face = "bold", size = 16),
panel.grid = element_blank()
)
print(p_text_heatmap)

# Save
ggsave("annotation_text_heatmap_04-02-2026.png",
plot = p_text_heatmap, width = 28, height = 4, dpi = 300, bg = "white")
ggsave("annotation_text_heatmap_04-02-2026.pdf",
plot = p_text_heatmap, width = 28, height = 4)
cat("\n✓ Text-based heatmap saved\n")
✓ Text-based heatmap saved
Dot Plot with
Consensus Highlighting
library(ggplot2)
# Add consensus column
heatmap_data_consensus <- heatmap_data %>%
group_by(seurat_clusters) %>%
mutate(
all_same = n_distinct(CellType) == 1,
CellType_short = ifelse(nchar(CellType) > 15,
paste0(substr(CellType, 1, 12), "..."),
CellType)
) %>%
ungroup()
# Create dot plot
p_dotplot <- ggplot(heatmap_data_consensus,
aes(x = seurat_clusters, y = Method, fill = all_same)) +
geom_tile(color = "grey40", linewidth = 0.3) +
geom_text(aes(label = CellType_short), size = 2.2, fontface = "bold") +
scale_fill_manual(
values = c("TRUE" = "#d4edda", "FALSE" = "white"),
labels = c("TRUE" = "Consensus", "FALSE" = "No consensus"),
name = ""
) +
labs(
title = "Cell Type Annotation Comparison with Consensus Highlighting",
x = "Seurat Cluster (0-18)",
y = "Annotation Method"
) +
theme_minimal(base_size = 13) +
theme(
axis.text.x = element_text(face = "bold", size = 11),
axis.text.y = element_text(face = "bold", size = 12),
plot.title = element_text(hjust = 0.5, face = "bold", size = 15),
panel.grid = element_blank(),
legend.position = "top"
)
print(p_dotplot)

# Save
ggsave("annotation_dotplot_consensus_04-02-2026.png",
plot = p_dotplot, width = 16, height = 6, dpi = 300, bg = "white")
ggsave("annotation_dotplot_consensus_04-02-2026.pdf",
plot = p_dotplot, width = 16, height = 6)
cat("\n✓ Dot plot with consensus highlighting saved\n")
✓ Dot plot with consensus highlighting saved
Per-Cluster
Annotation Distribution
library(ggplot2)
library(dplyr)
library(tidyr)
library(patchwork)
# Function to plot annotation distribution for one cluster
plot_cluster_agreement <- function(cluster_id) {
cols_needed <- c("seurat_clusters", "predicted.id", "predicted.celltype.l2",
"singler.immune", "scATOMIC_annotation")
# Extract and filter using base R
temp_df <- All_samples_Merged@meta.data[, cols_needed]
temp_df <- temp_df[temp_df$seurat_clusters == cluster_id, ]
# Rename columns
colnames(temp_df) <- c("Cluster", "scPred", "Azimuth", "SingleR", "scATOMIC")
# Now use dplyr safely with explicit namespace
df <- temp_df %>%
dplyr::select(-Cluster) %>%
pivot_longer(everything(), names_to = "method", values_to = "label") %>%
filter(!is.na(label)) %>%
dplyr::count(method, label) %>%
group_by(method) %>%
mutate(prop = n / sum(n)) %>%
ungroup()
ggplot(df, aes(x = method, y = prop, fill = label)) +
geom_bar(stat = "identity") +
labs(title = paste("Cluster", cluster_id),
y = "Proportion", x = "") +
theme_minimal(base_size = 10) +
theme(
axis.text.x = element_text(angle = 45, hjust = 1, size = 8),
legend.position = "none",
plot.title = element_text(hjust = 0.5, face = "bold")
)
}
# Create plots for clusters 0-18
clusters <- as.character(0:18)
cluster_plots <- lapply(clusters, plot_cluster_agreement)
# Combine plots
p_combined <- wrap_plots(cluster_plots, ncol = 5) +
plot_annotation(
title = "Annotation Distribution Across Methods per Cluster (0-18)",
theme = theme(plot.title = element_text(hjust = 0.5, face = "bold", size = 16))
)
print(p_combined)

# Save
ggsave("annotation_per_cluster_comparison_04-02-2026.png",
plot = p_combined, width = 16, height = 12, dpi = 300, bg = "white")
ggsave("annotation_per_cluster_comparison_04-02-2026.pdf",
plot = p_combined, width = 16, height = 12)
cat("\n✓ Per-cluster comparison saved\n")
✓ Per-cluster comparison saved
Improved Version for
Manuscript Comparison
library(ggplot2)
library(dplyr)
library(tidyr)
# Prepare data
cols_needed <- c("seurat_clusters", "predicted.id", "predicted.celltype.l2",
"singler.immune", "scATOMIC_annotation")
heatmap_data <- All_samples_Merged@meta.data[, cols_needed] %>%
group_by(seurat_clusters) %>%
summarise(
scPred = names(sort(table(predicted.id), decreasing = TRUE))[1],
Azimuth = names(sort(table(predicted.celltype.l2), decreasing = TRUE))[1],
SingleR = names(sort(table(singler.immune), decreasing = TRUE))[1],
scATOMIC = names(sort(table(scATOMIC_annotation), decreasing = TRUE))[1],
.groups = "drop"
) %>%
pivot_longer(cols = c(scPred, Azimuth, SingleR, scATOMIC),
names_to = "Method", values_to = "CellType") %>%
mutate(
Method = factor(Method, levels = c("scATOMIC", "SingleR", "Azimuth", "scPred")),
seurat_clusters = factor(seurat_clusters, levels = as.character(0:18)),
# Shorten long labels for readability
CellType_display = ifelse(nchar(CellType) > 20,
paste0(substr(CellType, 1, 17), "..."),
CellType)
)
# Create publication-quality heatmap
p_manuscript <- ggplot(heatmap_data,
aes(x = seurat_clusters, y = Method, label = CellType_display)) +
geom_tile(color = "grey20", fill = "white", linewidth = 0.8) +
geom_text(size = 3, hjust = 0.5, vjust = 0.5, fontface = "bold") +
labs(
title = "Discordance in Automated Cell Type Annotation - Herrera Dataset",
x = "Seurat Cluster",
y = "Annotation Method"
) +
theme_minimal(base_size = 16) +
theme(
axis.text.x = element_text(face = "bold", size = 14),
axis.text.y = element_text(face = "bold", size = 14),
axis.title = element_text(size = 16, face = "bold"),
plot.title = element_text(hjust = 0.5, face = "bold", size = 18),
panel.grid = element_blank(),
plot.margin = margin(10, 10, 10, 10)
)
print(p_manuscript)

# Save high-quality versions for manuscript
ggsave("FigureX_Herrera_Annotation_Comparison.png",
plot = p_manuscript, width = 28, height = 5, dpi = 600, bg = "white")
ggsave("FigureX_Herrera_Annotation_Comparison.pdf",
plot = p_manuscript, width = 28, height = 5)
ggsave("FigureX_Herrera_Annotation_Comparison.tiff",
plot = p_manuscript, width = 28, height = 5, dpi = 600, bg = "white")
cat("\n✓ Herrera manuscript figure saved (PNG, PDF, TIFF at 600 DPI)\n")
✓ Herrera manuscript figure saved (PNG, PDF, TIFF at 600 DPI)
#
# **Figure X. Automated annotation methods show poor concordance across two independent Sézary syndrome datasets.**
# **(A)** Cell line-derived Sézary samples. **(B)** Patient-derived Sézary samples (Herrera dataset). Each cell displays the dominant annotation assigned by four reference-based methods (scATOMIC, SingleR, Azimuth, and scPred) to cells within each cluster. Despite using identical methodology, methods assign divergent labels in both datasets, demonstrating that malignant T-cell populations from Sézary syndrome consistently fail to match healthy reference atlases. This cross-dataset validation highlights the necessity for manual curation or disease-specific annotation strategies.
sessionInfo()
sessionInfo()
R version 4.5.2 (2025-10-31)
Platform: x86_64-pc-linux-gnu
Running under: Ubuntu 24.04.3 LTS
Matrix products: default
BLAS: /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.12.0
LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.12.0 LAPACK version 3.12.0
locale:
[1] LC_CTYPE=en_GB.UTF-8 LC_NUMERIC=C LC_TIME=fr_FR.UTF-8
[4] LC_COLLATE=en_GB.UTF-8 LC_MONETARY=fr_FR.UTF-8 LC_MESSAGES=en_GB.UTF-8
[7] LC_PAPER=fr_FR.UTF-8 LC_NAME=C LC_ADDRESS=C
[10] LC_TELEPHONE=C LC_MEASUREMENT=fr_FR.UTF-8 LC_IDENTIFICATION=C
time zone: Europe/Paris
tzcode source: system (glibc)
attached base packages:
[1] stats4 stats graphics grDevices utils datasets methods base
other attached packages:
[1] kableExtra_1.4.0 knitr_1.51 ggalluvial_0.12.5
[4] RColorBrewer_1.1-3 purrr_1.2.1 tidyr_1.3.2
[7] Azimuth_0.5.0 shinyBS_0.63.0 pbmcsca.SeuratData_3.0.0
[10] pbmcref.SeuratData_1.0.0 SeuratData_0.2.2.9002 SeuratDisk_0.0.0.9021
[13] presto_1.0.0 data.table_1.18.2.1 Rcpp_1.1.1
[16] remotes_2.5.0 SingleR_2.12.0 celldex_1.20.0
[19] SummarizedExperiment_1.40.0 Biobase_2.70.0 GenomicRanges_1.62.1
[22] Seqinfo_1.0.0 IRanges_2.44.0 S4Vectors_0.48.0
[25] BiocGenerics_0.56.0 generics_0.1.4 MatrixGenerics_1.22.0
[28] matrixStats_1.5.0 scPred_1.9.2 pheatmap_1.0.13
[31] ggplot2_4.0.1 patchwork_1.3.2 dplyr_1.1.4
[34] Seurat_5.4.0 SeuratObject_5.3.0 sp_2.2-0
loaded via a namespace (and not attached):
[1] dichromat_2.0-0.1 nnet_7.3-20
[3] goftest_1.2-3 DT_0.34.0
[5] Biostrings_2.78.0 HDF5Array_1.38.0
[7] vctrs_0.7.1 spatstat.random_3.4-4
[9] digest_0.6.39 png_0.1-8
[11] gypsum_1.6.0 ggrepel_0.9.6
[13] deldir_2.0-4 parallelly_1.46.1
[15] MASS_7.3-65 Signac_1.16.0
[17] reshape2_1.4.5 httpuv_1.6.16
[19] foreach_1.5.2 withr_3.0.2
[21] xfun_0.56 survival_3.8-3
[23] EnsDb.Hsapiens.v86_2.99.0 memoise_2.0.1
[25] ggbeeswarm_0.7.3 systemfonts_1.3.1
[27] ragg_1.5.0 zoo_1.8-15
[29] gtools_3.9.5 pbapply_1.7-4
[31] KEGGREST_1.50.0 promises_1.5.0
[33] otel_0.2.0 httr_1.4.7
[35] restfulr_0.0.16 globals_0.18.0
[37] fitdistrplus_1.2-6 rhdf5filters_1.22.0
[39] rhdf5_2.54.1 rstudioapi_0.18.0
[41] UCSC.utils_1.6.1 miniUI_0.1.2
[43] curl_7.0.0 h5mread_1.2.1
[45] polyclip_1.10-7 ExperimentHub_3.0.0
[47] SparseArray_1.10.8 xtable_1.8-4
[49] stringr_1.6.0 evaluate_1.0.5
[51] S4Arrays_1.10.1 BiocFileCache_3.0.0
[53] irlba_2.3.5.1 filelock_1.0.3
[55] hdf5r_1.3.12 ROCR_1.0-12
[57] harmony_1.2.4 reticulate_1.44.1
[59] spatstat.data_3.1-9 magrittr_2.0.4
[61] lmtest_0.9-40 later_1.4.5
[63] lattice_0.22-7 spatstat.geom_3.7-0
[65] future.apply_1.20.1 scattermore_1.2
[67] XML_3.99-0.20 cowplot_1.2.0
[69] RcppAnnoy_0.0.23 class_7.3-23
[71] pillar_1.11.1 nlme_3.1-168
[73] iterators_1.0.14 pwalign_1.6.0
[75] caTools_1.18.3 compiler_4.5.2
[77] beachmat_2.26.0 RSpectra_0.16-2
[79] stringi_1.8.7 gower_1.0.2
[81] tensor_1.5.1 lubridate_1.9.4
[83] GenomicAlignments_1.46.0 plyr_1.8.9
[85] crayon_1.5.3 abind_1.4-8
[87] BiocIO_1.20.0 googledrive_2.1.2
[89] bit_4.6.0 fastmatch_1.1-8
[91] textshaping_1.0.4 codetools_0.2-20
[93] recipes_1.3.1 bslib_0.10.0
[95] alabaster.ranges_1.10.0 plotly_4.12.0
[97] mime_0.13 splines_4.5.2
[99] fastDummies_1.7.5 dbplyr_2.5.1
[101] sparseMatrixStats_1.22.0 cellranger_1.1.0
[103] utf8_1.2.6 blob_1.3.0
[105] BiocVersion_3.22.0 seqLogo_1.76.0
[107] AnnotationFilter_1.34.0 fs_1.6.6
[109] listenv_0.10.0 DelayedMatrixStats_1.32.0
[111] tibble_3.3.1 Matrix_1.7-4
[113] svglite_2.2.2 pkgconfig_2.0.3
[115] tools_4.5.2 cachem_1.1.0
[117] cigarillo_1.0.0 RSQLite_2.4.5
[119] viridisLite_0.4.2 DBI_1.2.3
[121] rmarkdown_2.30 fastmap_1.2.0
[123] scales_1.4.0 grid_4.5.2
[125] ica_1.0-3 shinydashboard_0.7.3
[127] Rsamtools_2.26.0 sass_0.4.10
[129] AnnotationHub_4.0.0 BiocManager_1.30.27
[131] dotCall64_1.2 RANN_2.6.2
[133] alabaster.schemas_1.10.0 rpart_4.1.24
[135] farver_2.1.2 yaml_2.3.12
[137] rtracklayer_1.70.1 cli_3.6.5
[139] lifecycle_1.0.5 caret_7.0-1
[141] uwot_0.2.4 lava_1.8.2
[143] BSgenome.Hsapiens.UCSC.hg38_1.4.5 BiocParallel_1.44.0
[145] timechange_0.3.0 gtable_0.3.6
[147] rjson_0.2.23 ggridges_0.5.7
[149] progressr_0.18.0 parallel_4.5.2
[151] pROC_1.19.0.1 jsonlite_2.0.0
[153] RcppHNSW_0.6.0 TFBSTools_1.48.0
[155] bitops_1.0-9 bit64_4.6.0-1
[157] Rtsne_0.17 alabaster.matrix_1.10.0
[159] spatstat.utils_3.2-1 BiocNeighbors_2.4.0
[161] jquerylib_0.1.4 alabaster.se_1.10.0
[163] shinyjs_2.1.1 spatstat.univar_3.1-6
[165] timeDate_4052.112 lazyeval_0.2.2
[167] alabaster.base_1.10.0 shiny_1.12.1
[169] htmltools_0.5.9 sctransform_0.4.3
[171] rappdirs_0.3.4 ensembldb_2.34.0
[173] glue_1.8.0 TFMPvalue_1.0.0
[175] spam_2.11-3 googlesheets4_1.1.2
[177] httr2_1.2.2 XVector_0.50.0
[179] RCurl_1.98-1.17 BSgenome_1.78.0
[181] gridExtra_2.3 JASPAR2020_0.99.10
[183] igraph_2.2.1 R6_2.6.1
[185] labeling_0.4.3 RcppRoll_0.3.1
[187] GenomicFeatures_1.62.0 cluster_2.1.8.1
[189] gargle_1.6.0 Rhdf5lib_1.32.0
[191] GenomeInfoDb_1.46.2 ipred_0.9-15
[193] DirichletMultinomial_1.52.0 DelayedArray_0.36.0
[195] tidyselect_1.2.1 vipor_0.4.7
[197] ProtGenerics_1.42.0 xml2_1.5.2
[199] AnnotationDbi_1.72.0 future_1.69.0
[201] ModelMetrics_1.2.2.2 KernSmooth_2.23-26
[203] S7_0.2.1 htmlwidgets_1.6.4
[205] rlang_1.1.7 spatstat.sparse_3.1-0
[207] spatstat.explore_3.7-0 hardhat_1.4.2
[209] beeswarm_0.4.0 prodlim_2025.04.28
LS0tCnRpdGxlOiAiQ3Jvc3MtTWV0aG9kIEFubm90YXRpb24gQ29tcGFyaXNvbiAtIEhlcnJlcmEgU8OpemFyeSBTeW5kcm9tZSBEYXRhc2V0IgphdXRob3I6IE5hc2lyIE1haG1vb2QgQWJiYXNpCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OgogICAgICBjb2xsYXBzZWQ6IHRydWUKICAgIHRoZW1lOiBqb3VybmFsCi0tLQoKCgojIGxvYWQgbGlicmFyaWVzCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoCiAgZWNobyA9IFRSVUUsCiAgd2FybmluZyA9IEZBTFNFLAogIG1lc3NhZ2UgPSBGQUxTRSwKICBmaWcud2lkdGggPSAxNCwKICBmaWcuaGVpZ2h0ID0gOCwKICBkcGkgPSAzMDAKKQojIGxvYWQgbGlicmFyaWVzCiAgICBsaWJyYXJ5KFNldXJhdCkKICAgIGxpYnJhcnkoZHBseXIpCiAgICBsaWJyYXJ5KHBhdGNod29yaykKICAgIGxpYnJhcnkoZ2dwbG90MikKICAgIGxpYnJhcnkocGhlYXRtYXApCiAgICBsaWJyYXJ5KHNjUHJlZCkKICAgIGxpYnJhcnkoY2VsbGRleCkKICAgIGxpYnJhcnkoU2luZ2xlUikKICAgIGxpYnJhcnkocmVtb3RlcykKICAgIGxpYnJhcnkocHJlc3RvKQogICAgbGlicmFyeShTZXVyYXREaXNrKQogICAgbGlicmFyeShTZXVyYXREYXRhKQogICAgbGlicmFyeShBemltdXRoKQoKYGBgCgoKCgojIExvYWQgUkRTIHdpdGggYWxsIGFubm90YXRpb25zCmBgYHtyfQojIExvYWQgbWFpbiBvYmplY3Qgd2l0aCBhbGwgYW5ub3RhdGlvbnMKQWxsX3NhbXBsZXNfTWVyZ2VkIDwtIHJlYWRSRFMoIi9ob21lL2Jpb2luZm8vMS1UaGVzaXNfRmluYWxfWWVhcl8yMDI1LzIwMjUtWWVhcjNfQW5hbHlzaXMvMS1zY1JOQV9SRVNVTFRTLTE5LTExLTIwMjUvMTctSGVycmVyYV9EQVRBX0NFTExfQW5ub3RhdGlvbi9BbGxfc2FtcGxlc19IZXJyZXJhX3dpdGhfc2NBVE9NSUNfMDQtMDItMjAyNi5yZHMiKQoKCklkZW50cyhBbGxfc2FtcGxlc19NZXJnZWQpIDwtICJzZXVyYXRfY2x1c3RlcnMiCgojIEVuc3VyZSBjbHVzdGVycyBhcmUgb3JkZXJlZCAwLTE4CkFsbF9zYW1wbGVzX01lcmdlZCRzZXVyYXRfY2x1c3RlcnMgPC0gZmFjdG9yKEFsbF9zYW1wbGVzX01lcmdlZCRzZXVyYXRfY2x1c3RlcnMsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGV2ZWxzID0gYXMuY2hhcmFjdGVyKDA6MTgpKQoKIyBPcmRlciBzYW1wbGVzIGxvZ2ljYWxseQpzYW1wbGVfb3JkZXIgPC0gYygiSGVhbHRoeV9CbG9vZCIsICJIZWFsdGh5X1NraW4iLCAKICAgICAgICAgICAgICAgICAgIlNTMV9CbG9vZCIsICJTUzFfU2tpbiIsIAogICAgICAgICAgICAgICAgICAiU1MyX0Jsb29kIiwgIlNTMl9Ta2luIiwgCiAgICAgICAgICAgICAgICAgICJTUzNfQmxvb2QiLCAiU1MzX1NraW4iLCAKICAgICAgICAgICAgICAgICAgIlNTNF9CbG9vZCIsICJTUzRfU2tpbiIsIAogICAgICAgICAgICAgICAgICAiU1M1X0Jsb29kIiwgIlNTNl9CbG9vZCIpCgpBbGxfc2FtcGxlc19NZXJnZWQkc2FtcGxlX2lkIDwtIGZhY3RvcihBbGxfc2FtcGxlc19NZXJnZWQkc2FtcGxlX2lkLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGV2ZWxzID0gc2FtcGxlX29yZGVyKQoKY2F0KCJUb3RhbCBjZWxsczoiLCBuY29sKEFsbF9zYW1wbGVzX01lcmdlZCksICJcbiIpCmNhdCgiVG90YWwgY2x1c3RlcnM6IiwgbGVuZ3RoKHVuaXF1ZShBbGxfc2FtcGxlc19NZXJnZWQkc2V1cmF0X2NsdXN0ZXJzKSksICJcblxuIikKY2F0KCJDbHVzdGVycyAob3JkZXJlZCAwLTE4KTpcbiIpCnByaW50KHRhYmxlKEFsbF9zYW1wbGVzX01lcmdlZCRzZXVyYXRfY2x1c3RlcnMpKQoKY2F0KCJcblxuU2FtcGxlcyAob3JkZXJlZCk6XG4iKQpwcmludCh0YWJsZShBbGxfc2FtcGxlc19NZXJnZWQkc2FtcGxlX2lkKSkKCmBgYAoKIyBDcmVhdGUgQW5ub3RhdGlvbiBTdW1tYXJ5IFRhYmxlIGJ5IENsdXN0ZXJzCmBgYHtyIH0KbGlicmFyeShkcGx5cikKbGlicmFyeSh0aWR5cikKbGlicmFyeShwdXJycikKCiMgRGVmaW5lIGFubm90YXRpb24gbWV0aG9kcwptZXRob2RzIDwtIGMoCiAgInByZWRpY3RlZC5pZCIsICAgICAgICAgICAgICMgc2NQcmVkCiAgInByZWRpY3RlZC5jZWxsdHlwZS5sMiIsICAgICMgQXppbXV0aCAobDIgcHJlZGljdGlvbikKICAic2luZ2xlci5pbW11bmUiLCAgICAgICAgICAgIyBTaW5nbGVSCiAgInNjQVRPTUlDX2Fubm90YXRpb24iICAgICAgICMgc2NBVE9NSUMKKQoKIyBDcmVhdGUgc3VtbWFyeSAtIG1vc3QgY29tbW9uIGxhYmVsIHBlciBjbHVzdGVyIGZvciBlYWNoIG1ldGhvZAphbm5vdGF0aW9uX3N1bW1hcnkgPC0gbWFwX2RmcihtZXRob2RzLCBmdW5jdGlvbihtKSB7CiAgZGYgPC0gQWxsX3NhbXBsZXNfTWVyZ2VkQG1ldGEuZGF0YQogIGRmICU+JQogICAgZmlsdGVyKCFpcy5uYSguZGF0YVtbbV1dKSkgJT4lCiAgICBncm91cF9ieShzZXVyYXRfY2x1c3RlcnMsIGxhYmVsID0gLmRhdGFbW21dXSkgJT4lCiAgICBzdW1tYXJpc2UobiA9IG4oKSwgLmdyb3VwcyA9ICJkcm9wIikgJT4lCiAgICBncm91cF9ieShzZXVyYXRfY2x1c3RlcnMpICU+JQogICAgc2xpY2VfbWF4KG4sIG4gPSAxLCB3aXRoX3RpZXMgPSBGQUxTRSkgJT4lCiAgICBtdXRhdGUobWV0aG9kID0gbSkKfSkKCiMgUmVuYW1lIG1ldGhvZHMgZm9yIGRpc3BsYXkKYW5ub3RhdGlvbl9zdW1tYXJ5IDwtIGFubm90YXRpb25fc3VtbWFyeSAlPiUKICBtdXRhdGUobWV0aG9kID0gcmVjb2RlKG1ldGhvZCwKICAgICAgICAgICAgICAgICAgICAgICAgICJwcmVkaWN0ZWQuaWQiID0gInNjUHJlZCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAicHJlZGljdGVkLmNlbGx0eXBlLmwyIiA9ICJBemltdXRoLmwyIiwKICAgICAgICAgICAgICAgICAgICAgICAgICJzaW5nbGVyLmltbXVuZSIgPSAiU2luZ2xlUihJbW11bmUpIiwKICAgICAgICAgICAgICAgICAgICAgICAgICJzY0FUT01JQ19hbm5vdGF0aW9uIiA9ICJzY0FUT01JQyIpKQoKIyBTZXQgbWV0aG9kIG9yZGVyCmFubm90YXRpb25fc3VtbWFyeSRtZXRob2QgPC0gZmFjdG9yKGFubm90YXRpb25fc3VtbWFyeSRtZXRob2QsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZXZlbHMgPSBjKCJzY1ByZWQiLCAiQXppbXV0aC5sMiIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlNpbmdsZVIoSW1tdW5lKSIsICJzY0FUT01JQyIpKQoKIyBFbnN1cmUgY2x1c3RlciBvcmRlciAwLTE4CmFubm90YXRpb25fc3VtbWFyeSRzZXVyYXRfY2x1c3RlcnMgPC0gZmFjdG9yKGFubm90YXRpb25fc3VtbWFyeSRzZXVyYXRfY2x1c3RlcnMsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZXZlbHMgPSBhcy5jaGFyYWN0ZXIoMDoxOCkpCgojIFNhdmUgYW5ub3RhdGlvbiBzdW1tYXJ5IGJ5IGNsdXN0ZXJzIChsb25nIGZvcm1hdCkKd3JpdGUuY3N2KGFubm90YXRpb25fc3VtbWFyeSwgCiAgICAgICAgICAiYW5ub3RhdGlvbl9zdW1tYXJ5X2J5X2NsdXN0ZXJzXzA0LTAyLTIwMjYuY3N2IiwgCiAgICAgICAgICByb3cubmFtZXMgPSBGQUxTRSkKCiMgQ3JlYXRlIGFuZCBzYXZlIHdpZGUgZm9ybWF0CmFubm90YXRpb25fdGFibGVfY2x1c3RlcnNfd2lkZSA8LSBhbm5vdGF0aW9uX3N1bW1hcnkgJT4lCiAgc2VsZWN0KHNldXJhdF9jbHVzdGVycywgbWV0aG9kLCBsYWJlbCkgJT4lCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IG1ldGhvZCwgdmFsdWVzX2Zyb20gPSBsYWJlbCkKCndyaXRlLmNzdihhbm5vdGF0aW9uX3RhYmxlX2NsdXN0ZXJzX3dpZGUsIAogICAgICAgICAgImFubm90YXRpb25fdGFibGVfY2x1c3RlcnNfd2lkZV8wNC0wMi0yMDI2LmNzdiIsIAogICAgICAgICAgcm93Lm5hbWVzID0gRkFMU0UpCgojIERpc3BsYXkKY2F0KCJcbj09PSBBbm5vdGF0aW9uIFN1bW1hcnkgYnkgQ2x1c3RlcnMgPT09XG5cbiIpCnByaW50KGFubm90YXRpb25fc3VtbWFyeSkKY2F0KCJcbuKckyBTYXZlZDogYW5ub3RhdGlvbl9zdW1tYXJ5X2J5X2NsdXN0ZXJzXzA0LTAyLTIwMjYuY3N2IChsb25nIGZvcm1hdClcbiIpCmNhdCgi4pyTIFNhdmVkOiBhbm5vdGF0aW9uX3RhYmxlX2NsdXN0ZXJzX3dpZGVfMDQtMDItMjAyNi5jc3YgKHdpZGUgZm9ybWF0KVxuIikKCmBgYAoKIyMgU3VtbWFyeSBTdGF0aXN0aWNzCmBgYHtyIH0KIyBDZWxsIGNvdW50cyBwZXIgbWV0aG9kCmNhdCgiXG49PT0gQ2VsbHMgQW5ub3RhdGVkIFBlciBNZXRob2QgPT09XG5cbiIpCm1ldGhvZF9jb2xzIDwtIGMoInByZWRpY3RlZC5pZCIsICJwcmVkaWN0ZWQuY2VsbHR5cGUubDIiLCAic2luZ2xlci5pbW11bmUiLCAic2NBVE9NSUNfYW5ub3RhdGlvbiIpCmZvcihtIGluIG1ldGhvZF9jb2xzKXsKICBuX2Fubm90YXRlZCA8LSBzdW0oIWlzLm5hKEFsbF9zYW1wbGVzX01lcmdlZEBtZXRhLmRhdGFbW21dXSkpCiAgY2F0KHNwcmludGYoIiUtMjVzOiAlNWQgY2VsbHMgKCUuMWYlJSlcbiIsIAogICAgICAgICAgICAgIG0sIG5fYW5ub3RhdGVkLCBuX2Fubm90YXRlZC9uY29sKEFsbF9zYW1wbGVzX01lcmdlZCkqMTAwKSkKfQoKIyBOdW1iZXIgb2YgdW5pcXVlIGxhYmVscyBwZXIgbWV0aG9kCmNhdCgiXG49PT0gVW5pcXVlIExhYmVscyBQZXIgTWV0aG9kID09PVxuXG4iKQphbm5vdGF0aW9uX3N1bW1hcnkgJT4lCiAgZ3JvdXBfYnkobWV0aG9kKSAlPiUKICBzdW1tYXJpc2Uobl91bmlxdWVfbGFiZWxzID0gbl9kaXN0aW5jdChsYWJlbCkpICU+JQogIHByaW50KCkKCmBgYAojIEJhc2ljIEhlYXRtYXAgVmlzdWFsaXphdGlvbgpgYGB7ciwgZmlnLmhlaWdodD0gNSwgZmlnLndpZHRoPSAxOH0KbGlicmFyeShnZ3Bsb3QyKQoKZ2dwbG90KGFubm90YXRpb25fc3VtbWFyeSwgYWVzKHggPSBzZXVyYXRfY2x1c3RlcnMsIHkgPSBtZXRob2QsIGZpbGwgPSBsYWJlbCkpICsKICBnZW9tX3RpbGUoY29sb3IgPSAid2hpdGUiLCBsaW5ld2lkdGggPSAwLjUpICsKICBzY2FsZV9maWxsX2Rpc2NyZXRlKCkgKwogIGxhYnMoCiAgICB4ID0gIlNldXJhdCBDbHVzdGVycyIsCiAgICB5ID0gIkFubm90YXRpb24gTWV0aG9kIiwKICAgIGZpbGwgPSAiQXNzaWduZWQgQ2VsbCBUeXBlIiwKICAgIHRpdGxlID0gIkNyb3NzLU1ldGhvZCBDb21wYXJpc29uIG9mIENlbGwgVHlwZSBBbm5vdGF0aW9ucyIKICApICsKICB0aGVtZV9taW5pbWFsKGJhc2Vfc2l6ZSA9IDE0KSArCiAgdGhlbWUoCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpLAogICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwKICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsIGZhY2UgPSAiYm9sZCIsIHNpemUgPSAxNikKICApCgoKYGBgCgojIyBFbmhhbmNlZCBIZWF0bWFwIHdpdGggQ3VzdG9tIENvbG9ycwpgYGB7ciwgZmlnLmhlaWdodD0gNSwgZmlnLndpZHRoPSAxOCB9CmxpYnJhcnkoUkNvbG9yQnJld2VyKQoKIyBHZW5lcmF0ZSBjb2xvciBwYWxldHRlCm51bV9jb2xvcnMgPC0gbGVuZ3RoKHVuaXF1ZShhbm5vdGF0aW9uX3N1bW1hcnkkbGFiZWwpKQpteV9jb2xvcnMgPC0gY29sb3JSYW1wUGFsZXR0ZShicmV3ZXIucGFsKDgsICJTZXQyIikpKG51bV9jb2xvcnMpCgpwX2hlYXRtYXAgPC0gZ2dwbG90KGFubm90YXRpb25fc3VtbWFyeSwgYWVzKHggPSBzZXVyYXRfY2x1c3RlcnMsIHkgPSBtZXRob2QsIGZpbGwgPSBsYWJlbCkpICsKICBnZW9tX3RpbGUoY29sb3IgPSAid2hpdGUiLCBsaW5ld2lkdGggPSAwLjUpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBteV9jb2xvcnMpICsKICBsYWJzKAogICAgeCA9ICJTZXVyYXQgQ2x1c3RlcnMiLAogICAgeSA9ICJBbm5vdGF0aW9uIE1ldGhvZCIsCiAgICBmaWxsID0gIkFzc2lnbmVkIENlbGwgVHlwZSIsCiAgICB0aXRsZSA9ICJDcm9zcy1NZXRob2QgQ29tcGFyaXNvbiBvZiBDZWxsIFR5cGUgQW5ub3RhdGlvbnMiCiAgKSArCiAgdGhlbWVfbWluaW1hbChiYXNlX3NpemUgPSAxNCkgKwogIHRoZW1lKAogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSwKICAgIGF4aXMudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksCiAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41LCBmYWNlID0gImJvbGQiLCBzaXplID0gMTYpLAogICAgbGVnZW5kLnBvc2l0aW9uID0gInJpZ2h0IgogICkKCnByaW50KHBfaGVhdG1hcCkKCiMgU2F2ZSBoaWdoLXF1YWxpdHkgdmVyc2lvbnMKZ2dzYXZlKCJhbm5vdGF0aW9uX2hlYXRtYXBfMDQtMDItMjAyNi5wbmciLCBwbG90ID0gcF9oZWF0bWFwLCAKICAgICAgIHdpZHRoID0gMTQsIGhlaWdodCA9IDUsIGRwaSA9IDMwMCwgYmcgPSAid2hpdGUiKQpnZ3NhdmUoImFubm90YXRpb25faGVhdG1hcF8wNC0wMi0yMDI2LnBkZiIsIHBsb3QgPSBwX2hlYXRtYXAsIAogICAgICAgd2lkdGggPSAxNCwgaGVpZ2h0ID0gNSkKCmNhdCgiXG7inJMgSGVhdG1hcCBzYXZlZCBhcyBQTkcgYW5kIFBERlxuIikKCmBgYAoKIyAgSGVhdG1hcDogTWV0aG9kcyDDlyBDbHVzdGVycwpgYGB7ciwgZmlnLmhlaWdodD0gNSwgZmlnLndpZHRoPSAxOCB9CmxpYnJhcnkoUkNvbG9yQnJld2VyKQoKIyBHZW5lcmF0ZSBjb2xvciBwYWxldHRlCm51bV9jb2xvcnMgPC0gbGVuZ3RoKHVuaXF1ZShhbm5vdGF0aW9uX3N1bW1hcnkkbGFiZWwpKQpteV9jb2xvcnMgPC0gY29sb3JSYW1wUGFsZXR0ZShicmV3ZXIucGFsKDgsICJTZXQyIikpKG51bV9jb2xvcnMpCgpwX2hlYXRtYXBfY2x1c3RlcnMgPC0gZ2dwbG90KGFubm90YXRpb25fc3VtbWFyeSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFlcyh4ID0gc2V1cmF0X2NsdXN0ZXJzLCB5ID0gbWV0aG9kLCBmaWxsID0gbGFiZWwpKSArCiAgZ2VvbV90aWxlKGNvbG9yID0gIndoaXRlIiwgbGluZXdpZHRoID0gMC41KSArCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gbXlfY29sb3JzKSArCiAgc2NhbGVfeF9kaXNjcmV0ZShkcm9wID0gRkFMU0UpICsgICMgS2VlcCBhbGwgY2x1c3RlciBsZXZlbHMgZXZlbiBpZiBtaXNzaW5nCiAgbGFicygKICAgIHggPSAiU2V1cmF0IENsdXN0ZXJzICgwLTE4KSIsCiAgICB5ID0gIkFubm90YXRpb24gTWV0aG9kIiwKICAgIGZpbGwgPSAiQXNzaWduZWQgQ2VsbCBUeXBlIiwKICAgIHRpdGxlID0gIkNyb3NzLU1ldGhvZCBDb21wYXJpc29uIG9mIENlbGwgVHlwZSBBbm5vdGF0aW9ucyIKICApICsKICB0aGVtZV9taW5pbWFsKGJhc2Vfc2l6ZSA9IDE0KSArCiAgdGhlbWUoCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpLAogICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwKICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsIGZhY2UgPSAiYm9sZCIsIHNpemUgPSAxNiksCiAgICBsZWdlbmQucG9zaXRpb24gPSAicmlnaHQiCiAgKQoKcHJpbnQocF9oZWF0bWFwX2NsdXN0ZXJzKQoKIyBTYXZlCmdnc2F2ZSgiYW5ub3RhdGlvbl9oZWF0bWFwX21ldGhvZHNfY2x1c3RlcnNfMDQtMDItMjAyNi5wbmciLCAKICAgICAgIHBsb3QgPSBwX2hlYXRtYXBfY2x1c3RlcnMsIHdpZHRoID0gMTQsIGhlaWdodCA9IDUsIGRwaSA9IDMwMCwgYmcgPSAid2hpdGUiKQpnZ3NhdmUoImFubm90YXRpb25faGVhdG1hcF9tZXRob2RzX2NsdXN0ZXJzXzA0LTAyLTIwMjYucGRmIiwgCiAgICAgICBwbG90ID0gcF9oZWF0bWFwX2NsdXN0ZXJzLCB3aWR0aCA9IDE0LCBoZWlnaHQgPSA1KQoKY2F0KCJcbuKckyBNZXRob2RzIMOXIENsdXN0ZXJzIGhlYXRtYXAgc2F2ZWRcbiIpCgoKYGBgCgoKCiMgIEhlYXRtYXA6IENsdXN0ZXJzIMOXIFNhbXBsZXMgKHNjQVRPTUlDIGFubm90YXRpb25zKQpgYGB7ciwgZmlnLmhlaWdodD0gNiwgZmlnLndpZHRoPSAxMiB9CiMgQ3JlYXRlIHN1bW1hcnkgYnkgY2x1c3RlciBhbmQgc2FtcGxlIGZvciBzY0FUT01JQwpjbHVzdGVyX3NhbXBsZV9zdW1tYXJ5IDwtIEFsbF9zYW1wbGVzX01lcmdlZEBtZXRhLmRhdGEgJT4lCiAgZmlsdGVyKCFpcy5uYShzY0FUT01JQ19hbm5vdGF0aW9uKSkgJT4lCiAgZ3JvdXBfYnkoc2V1cmF0X2NsdXN0ZXJzLCBzYW1wbGVfaWQsIHNjQVRPTUlDX2Fubm90YXRpb24pICU+JQogIHN1bW1hcmlzZShuID0gbigpLCAuZ3JvdXBzID0gImRyb3AiKSAlPiUKICBncm91cF9ieShzZXVyYXRfY2x1c3RlcnMsIHNhbXBsZV9pZCkgJT4lCiAgc2xpY2VfbWF4KG4sIG4gPSAxLCB3aXRoX3RpZXMgPSBGQUxTRSkgJT4lCiAgdW5ncm91cCgpCgojIEVuc3VyZSBjbHVzdGVyIGFuZCBzYW1wbGUgb3JkZXIKY2x1c3Rlcl9zYW1wbGVfc3VtbWFyeSRzZXVyYXRfY2x1c3RlcnMgPC0gZmFjdG9yKGNsdXN0ZXJfc2FtcGxlX3N1bW1hcnkkc2V1cmF0X2NsdXN0ZXJzLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxldmVscyA9IGFzLmNoYXJhY3RlcigwOjE4KSkKY2x1c3Rlcl9zYW1wbGVfc3VtbWFyeSRzYW1wbGVfaWQgPC0gZmFjdG9yKGNsdXN0ZXJfc2FtcGxlX3N1bW1hcnkkc2FtcGxlX2lkLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxldmVscyA9IHNhbXBsZV9vcmRlcikKCiMgUGxvdApwX2NsdXN0ZXJfc2FtcGxlIDwtIGdncGxvdChjbHVzdGVyX3NhbXBsZV9zdW1tYXJ5LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgYWVzKHggPSBzYW1wbGVfaWQsIHkgPSBzZXVyYXRfY2x1c3RlcnMsIGZpbGwgPSBzY0FUT01JQ19hbm5vdGF0aW9uKSkgKwogIGdlb21fdGlsZShjb2xvciA9ICJ3aGl0ZSIsIGxpbmV3aWR0aCA9IDAuNSkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IG15X2NvbG9ycykgKwogIHNjYWxlX3lfZGlzY3JldGUoZHJvcCA9IEZBTFNFKSArICAjIEtlZXAgYWxsIGNsdXN0ZXIgbGV2ZWxzCiAgc2NhbGVfeF9kaXNjcmV0ZShkcm9wID0gRkFMU0UpICsgICMgS2VlcCBhbGwgc2FtcGxlIGxldmVscwogIGxhYnMoCiAgICB4ID0gIlNhbXBsZSBJRCIsCiAgICB5ID0gIlNldXJhdCBDbHVzdGVycyAoMC0xOCkiLAogICAgZmlsbCA9ICJzY0FUT01JQyBBbm5vdGF0aW9uIiwKICAgIHRpdGxlID0gIkNsdXN0ZXIgRGlzdHJpYnV0aW9uIEFjcm9zcyBTYW1wbGVzIChzY0FUT01JQyBBbm5vdGF0aW9uKSIKICApICsKICB0aGVtZV9taW5pbWFsKGJhc2Vfc2l6ZSA9IDE0KSArCiAgdGhlbWUoCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpLAogICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwKICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsIGZhY2UgPSAiYm9sZCIsIHNpemUgPSAxNiksCiAgICBsZWdlbmQucG9zaXRpb24gPSAicmlnaHQiCiAgKQoKcHJpbnQocF9jbHVzdGVyX3NhbXBsZSkKCiMgU2F2ZQpnZ3NhdmUoImFubm90YXRpb25faGVhdG1hcF9jbHVzdGVyc19zYW1wbGVzXzA0LTAyLTIwMjYucG5nIiwgCiAgICAgICBwbG90ID0gcF9jbHVzdGVyX3NhbXBsZSwgd2lkdGggPSAxNiwgaGVpZ2h0ID0gMTAsIGRwaSA9IDMwMCwgYmcgPSAid2hpdGUiKQpnZ3NhdmUoImFubm90YXRpb25faGVhdG1hcF9jbHVzdGVyc19zYW1wbGVzXzA0LTAyLTIwMjYucGRmIiwgCiAgICAgICBwbG90ID0gcF9jbHVzdGVyX3NhbXBsZSwgd2lkdGggPSAxNiwgaGVpZ2h0ID0gMTApCgpjYXQoIlxu4pyTIENsdXN0ZXJzIMOXIFNhbXBsZXMgaGVhdG1hcCBzYXZlZFxuIikKCmBgYAoKIyAgQWxsdXZpYWwgRGlhZ3JhbSAtIEFubm90YXRpb24gRmxvdyBBY3Jvc3MgTWV0aG9kcwpgYGB7ciwgZmlnLmhlaWdodD0gMTQsIGZpZy53aWR0aD0gMjYgfQoKbGlicmFyeShkcGx5cikKbGlicmFyeSh0aWR5cikKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KGdnYWxsdXZpYWwpCgojIEV4dHJhY3QgY29sdW1ucyBzYWZlbHkgdXNpbmcgYmFzZSBSIChhdm9pZHMgc2VsZWN0KCkgY29uZmxpY3RzKQpjb2xzX25lZWRlZCA8LSBjKCJzZXVyYXRfY2x1c3RlcnMiLCAicHJlZGljdGVkLmlkIiwgInByZWRpY3RlZC5jZWxsdHlwZS5sMiIsIAogICAgICAgICAgICAgICAgICJzaW5nbGVyLmltbXVuZSIsICJzY0FUT01JQ19hbm5vdGF0aW9uIikKCmFsbHV2aWFsX2RmX3JhdyA8LSBBbGxfc2FtcGxlc19NZXJnZWRAbWV0YS5kYXRhWywgY29sc19uZWVkZWRdCmNvbG5hbWVzKGFsbHV2aWFsX2RmX3JhdykgPC0gYygiQ2x1c3RlciIsICJzY1ByZWQiLCAiQXppbXV0aCIsICJTaW5nbGVSIiwgInNjQVRPTUlDIikKCiMgU3VtbWFyaXplIGFuZCBmaWx0ZXIKYWxsdXZpYWxfZGF0YSA8LSBhbGx1dmlhbF9kZl9yYXcgJT4lCiAgbXV0YXRlKENsdXN0ZXIgPSBmYWN0b3IoYXMuY2hhcmFjdGVyKENsdXN0ZXIpLCBsZXZlbHMgPSBhcy5jaGFyYWN0ZXIoMDoxOCkpKSAlPiUKICBmaWx0ZXIoIWlzLm5hKHNjUHJlZCkgJiAhaXMubmEoQXppbXV0aCkgJiAhaXMubmEoU2luZ2xlUikgJiAhaXMubmEoc2NBVE9NSUMpKSAlPiUKICBncm91cF9ieShDbHVzdGVyLCBzY1ByZWQsIEF6aW11dGgsIFNpbmdsZVIsIHNjQVRPTUlDKSAlPiUKICBzdW1tYXJpc2UoRnJlcSA9IG4oKSwgLmdyb3VwcyA9ICJkcm9wIikgJT4lCiAgZmlsdGVyKEZyZXEgPiA1KQoKIyBDaGVjawpwcmludChwYXN0ZSgiUm93cyBpbiBwbG90IGRhdGE6IiwgbnJvdyhhbGx1dmlhbF9kYXRhKSkpCgojIFBsb3QKcF9hbGx1dmlhbCA8LSBnZ3Bsb3QoCiAgYWxsdXZpYWxfZGF0YSwKICBhZXMoYXhpczEgPSBzY1ByZWQsIGF4aXMyID0gQXppbXV0aCwgYXhpczMgPSBTaW5nbGVSLCBheGlzNCA9IHNjQVRPTUlDLCB5ID0gRnJlcSkKKSArCiAgZ2VvbV9hbGx1dml1bShhZXMoZmlsbCA9IHNjQVRPTUlDKSwgYWxwaGEgPSAwLjcsIGN1cnZlX3R5cGUgPSAic2lnbW9pZCIpICsKICBnZW9tX3N0cmF0dW0od2lkdGggPSAxLzUsIGZpbGwgPSAid2hpdGUiLCBjb2xvciA9ICJncmV5NTAiKSArCiAgZ2VvbV90ZXh0KHN0YXQgPSAic3RyYXR1bSIsIGFlcyhsYWJlbCA9IGFmdGVyX3N0YXQoc3RyYXR1bSkpLCAKICAgICAgICAgICAgc2l6ZSA9IDMsIGZvbnRmYWNlID0gImJvbGQiKSArCiAgc2NhbGVfeF9kaXNjcmV0ZSgKICAgIGxpbWl0cyA9IGMoInNjUHJlZCIsICJBemltdXRoIiwgIlNpbmdsZVIiLCAic2NBVE9NSUMiKSwKICAgIGV4cGFuZCA9IGMoMC4xNSwgMC4wNSkKICApICsKICBsYWJzKAogICAgdGl0bGUgPSAiQW5ub3RhdGlvbiBGbG93IEFjcm9zcyBNZXRob2RzIiwKICAgIHN1YnRpdGxlID0gIkZsb3cgc2hvd3MgaG93IGNlbGwgYW5ub3RhdGlvbnMgY2hhbmdlIGFjcm9zcyBkaWZmZXJlbnQgbWV0aG9kcyIsCiAgICB5ID0gIk51bWJlciBvZiBDZWxscyIsCiAgICBmaWxsID0gInNjQVRPTUlDIExhYmVsIgogICkgKwogIHRoZW1lX21pbmltYWwoYmFzZV9zaXplID0gMTQpICsKICB0aGVtZSgKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLAogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiwgc2l6ZSA9IDEyKSwKICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsIGZhY2UgPSAiYm9sZCIsIHNpemUgPSAxNiksCiAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41LCBzaXplID0gMTIpLAogICAgbGVnZW5kLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDgpCiAgKSArCiAgZ3VpZGVzKGZpbGwgPSBndWlkZV9sZWdlbmQobnJvdyA9IDMpKQoKcHJpbnQocF9hbGx1dmlhbCkKCiMgU2F2ZQpnZ3NhdmUoImFubm90YXRpb25fYWxsdXZpYWxfZmxvd18wNC0wMi0yMDI2LnBuZyIsIHBsb3QgPSBwX2FsbHV2aWFsLAogICAgICAgd2lkdGggPSAxMiwgaGVpZ2h0ID0gOCwgZHBpID0gMzAwLCBiZyA9ICJ3aGl0ZSIpCmdnc2F2ZSgiYW5ub3RhdGlvbl9hbGx1dmlhbF9mbG93XzA0LTAyLTIwMjYucGRmIiwgcGxvdCA9IHBfYWxsdXZpYWwsCiAgICAgICB3aWR0aCA9IDEyLCBoZWlnaHQgPSA4KQoKY2F0KCJcbuKckyBBbGx1dmlhbCBkaWFncmFtIHNhdmVkXG4iKQoKCmBgYAoKCgoKIyBNZXRob2QgQWdyZWVtZW50IEFuYWx5c2lzCgojIyAgQ2FsY3VsYXRlIFBhaXJ3aXNlIEFncmVlbWVudApgYGB7ciwgZmlnLmhlaWdodD0gNSwgZmlnLndpZHRoPSAxOCB9CmxpYnJhcnkoZHBseXIpCmxpYnJhcnkodGlkeXIpCgojIENyZWF0ZSBtYXRyaXg6IGNsdXN0ZXJzIMOXIG1ldGhvZHMKIyBTdGVwIDE6IEV4dHJhY3QgY29sdW1ucyBzYWZlbHkKY29sc19uZWVkZWQgPC0gYygic2V1cmF0X2NsdXN0ZXJzIiwgInByZWRpY3RlZC5pZCIsICJwcmVkaWN0ZWQuY2VsbHR5cGUubDIiLCAKICAgICAgICAgICAgICAgICAic2luZ2xlci5pbW11bmUiLCAic2NBVE9NSUNfYW5ub3RhdGlvbiIpCgpjbHVzdGVyX2Fubm90YXRpb25fZGYgPC0gQWxsX3NhbXBsZXNfTWVyZ2VkQG1ldGEuZGF0YVssIGNvbHNfbmVlZGVkXQoKIyBTdGVwIDI6IFBpdm90IGFuZCBmaW5kIGRvbWluYW50IGxhYmVsIHBlciBjbHVzdGVyIHBlciBtZXRob2QKY2x1c3Rlcl9hbm5vdGF0aW9uX21hdHJpeCA8LSBjbHVzdGVyX2Fubm90YXRpb25fZGYgJT4lCiAgcGl2b3RfbG9uZ2VyKGNvbHMgPSAtc2V1cmF0X2NsdXN0ZXJzLCBuYW1lc190byA9ICJtZXRob2QiLCB2YWx1ZXNfdG8gPSAibGFiZWwiKSAlPiUKICBmaWx0ZXIoIWlzLm5hKGxhYmVsKSkgJT4lCiAgZ3JvdXBfYnkoc2V1cmF0X2NsdXN0ZXJzLCBtZXRob2QsIGxhYmVsKSAlPiUKICBzdW1tYXJpc2UobiA9IG4oKSwgLmdyb3VwcyA9ICJkcm9wIikgJT4lCiAgZ3JvdXBfYnkoc2V1cmF0X2NsdXN0ZXJzLCBtZXRob2QpICU+JQogIHNsaWNlX21heChuLCBuID0gMSwgd2l0aF90aWVzID0gRkFMU0UpICU+JQogIHVuZ3JvdXAoKSAlPiUKICBkcGx5cjo6c2VsZWN0KHNldXJhdF9jbHVzdGVycywgbWV0aG9kLCBsYWJlbCkgJT4lCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IG1ldGhvZCwgdmFsdWVzX2Zyb20gPSBsYWJlbCkKCiMgQ29udmVydCB0byBtYXRyaXgKbWF0IDwtIGFzLm1hdHJpeChjbHVzdGVyX2Fubm90YXRpb25fbWF0cml4WywgLTFdKQpyb3duYW1lcyhtYXQpIDwtIGNsdXN0ZXJfYW5ub3RhdGlvbl9tYXRyaXgkc2V1cmF0X2NsdXN0ZXJzCgojIENhbGN1bGF0ZSBtZXRob2Qgc2ltaWxhcml0eSAoQWdyZWVtZW50IHByb3BvcnRpb24pCm1ldGhvZF9zaW1pbGFyaXR5IDwtIGZ1bmN0aW9uKG0xLCBtMikgewogIHN1bShtMSA9PSBtMiwgbmEucm0gPSBUUlVFKSAvIHN1bSghaXMubmEobTEpICYgIWlzLm5hKG0yKSkKfQoKIyBDcmVhdGUgc2ltaWxhcml0eSBtYXRyaXgKbl9tZXRob2RzIDwtIG5jb2wobWF0KQpzaW1fbWF0cml4IDwtIG1hdHJpeCgwLCBuX21ldGhvZHMsIG5fbWV0aG9kcykKY29sbmFtZXMoc2ltX21hdHJpeCkgPC0gYygic2NQcmVkIiwgIkF6aW11dGgubDIiLCAiU2luZ2xlUiIsICJzY0FUT01JQyIpCnJvd25hbWVzKHNpbV9tYXRyaXgpIDwtIGMoInNjUHJlZCIsICJBemltdXRoLmwyIiwgIlNpbmdsZVIiLCAic2NBVE9NSUMiKQoKZm9yKGkgaW4gMTpuX21ldGhvZHMpIHsKICBmb3IoaiBpbiAxOm5fbWV0aG9kcykgewogICAgc2ltX21hdHJpeFtpLCBqXSA8LSBtZXRob2Rfc2ltaWxhcml0eShtYXRbLCBpXSwgbWF0Wywgal0pCiAgfQp9CgojIERpc3BsYXkgYWdyZWVtZW50IG1hdHJpeApjYXQoIlxuPT09IFBhaXJ3aXNlIEFncmVlbWVudCBCZXR3ZWVuIE1ldGhvZHMgPT09XG5cbiIpCnByaW50KHJvdW5kKHNpbV9tYXRyaXgsIDMpKQoKIyBTYXZlIGFzIENTVgp3cml0ZS5jc3Yoc2ltX21hdHJpeCwgIm1ldGhvZF9hZ3JlZW1lbnRfbWF0cml4XzA0LTAyLTIwMjYuY3N2Iiwgcm93Lm5hbWVzID0gVFJVRSkKCmNhdCgiXG7inJMgU2F2ZWQ6IG1ldGhvZF9hZ3JlZW1lbnRfbWF0cml4XzA0LTAyLTIwMjYuY3N2XG4iKQpgYGAKCiMjICBBZ3JlZW1lbnQgSGVhdG1hcApgYGB7ciwgZmlnLmhlaWdodD02LCBmaWcud2lkdGg9OH0KbGlicmFyeShwaGVhdG1hcCkKCiMgUGxvdCBhZ3JlZW1lbnQgaGVhdG1hcApwaGVhdG1hcCgKICBzaW1fbWF0cml4LAogIGRpc3BsYXlfbnVtYmVycyA9IFRSVUUsCiAgbnVtYmVyX2Zvcm1hdCA9ICIlLjJmIiwKICBjbHVzdGVyX3Jvd3MgPSBUUlVFLAogIGNsdXN0ZXJfY29scyA9IFRSVUUsCiAgY29sb3IgPSBjb2xvclJhbXBQYWxldHRlKGMoIndoaXRlIiwgIm9yYW5nZSIsICJyZWQiKSkoMTAwKSwKICBtYWluID0gIlBhaXJ3aXNlIEFncmVlbWVudCBCZXR3ZWVuIEFubm90YXRpb24gTWV0aG9kcyIsCiAgZm9udHNpemUgPSAxNCwKICBmb250c2l6ZV9udW1iZXIgPSAxMiwKICBhbmdsZV9jb2wgPSA0NSwKICBib3JkZXJfY29sb3IgPSAiZ3JleTYwIgopCgojIFNhdmUgUE5HCnBuZygiYW5ub3RhdGlvbl9tZXRob2RfYWdyZWVtZW50X2hlYXRtYXBfMDQtMDItMjAyNi5wbmciLCAKICAgIHdpZHRoID0gMTAsIGhlaWdodCA9IDEwLCB1bml0cyA9ICJpbiIsIHJlcyA9IDMwMCkKcGhlYXRtYXAoCiAgc2ltX21hdHJpeCwKICBkaXNwbGF5X251bWJlcnMgPSBUUlVFLAogIG51bWJlcl9mb3JtYXQgPSAiJS4yZiIsCiAgY2x1c3Rlcl9yb3dzID0gVFJVRSwKICBjbHVzdGVyX2NvbHMgPSBUUlVFLAogIGNvbG9yID0gY29sb3JSYW1wUGFsZXR0ZShjKCJ3aGl0ZSIsICJvcmFuZ2UiLCAicmVkIikpKDEwMCksCiAgbWFpbiA9ICJQYWlyd2lzZSBBZ3JlZW1lbnQgQmV0d2VlbiBBbm5vdGF0aW9uIE1ldGhvZHMiLAogIGZvbnRzaXplID0gMTQsCiAgZm9udHNpemVfbnVtYmVyID0gMTIsCiAgYW5nbGVfY29sID0gNDUsCiAgYm9yZGVyX2NvbG9yID0gImdyZXk2MCIKKQpkZXYub2ZmKCkKCiMgU2F2ZSBQREYKcGRmKCJhbm5vdGF0aW9uX21ldGhvZF9hZ3JlZW1lbnRfaGVhdG1hcF8wNC0wMi0yMDI2LnBkZiIsIAogICAgd2lkdGggPSAxMCwgaGVpZ2h0ID0gMTApCnBoZWF0bWFwKAogIHNpbV9tYXRyaXgsCiAgZGlzcGxheV9udW1iZXJzID0gVFJVRSwKICBudW1iZXJfZm9ybWF0ID0gIiUuMmYiLAogIGNsdXN0ZXJfcm93cyA9IFRSVUUsCiAgY2x1c3Rlcl9jb2xzID0gVFJVRSwKICBjb2xvciA9IGNvbG9yUmFtcFBhbGV0dGUoYygid2hpdGUiLCAib3JhbmdlIiwgInJlZCIpKSgxMDApLAogIG1haW4gPSAiUGFpcndpc2UgQWdyZWVtZW50IEJldHdlZW4gQW5ub3RhdGlvbiBNZXRob2RzIiwKICBmb250c2l6ZSA9IDE0LAogIGZvbnRzaXplX251bWJlciA9IDEyLAogIGFuZ2xlX2NvbCA9IDQ1LAogIGJvcmRlcl9jb2xvciA9ICJncmV5NjAiCikKZGV2Lm9mZigpCgpjYXQoIlxu4pyTIEFncmVlbWVudCBoZWF0bWFwIHNhdmVkXG4iKQpgYGAKCgojIyBIaWVyYXJjaGljYWwgQ2x1c3RlcmluZyBEZW5kcm9ncmFtCmBgYHtyLCBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD04fQojIENvbnZlcnQgc2ltaWxhcml0eSB0byBkaXN0YW5jZQpkaXN0X21hdHJpeCA8LSBhcy5kaXN0KDEgLSBzaW1fbWF0cml4KQoKIyBIaWVyYXJjaGljYWwgY2x1c3RlcmluZwpoYyA8LSBoY2x1c3QoZGlzdF9tYXRyaXgsIG1ldGhvZCA9ICJ3YXJkLkQyIikKCiMgUGxvdCBkZW5kcm9ncmFtCnBhcihjZXggPSAxLjIpCnBsb3QoaGMsIAogICAgIG1haW4gPSAiSGllcmFyY2hpY2FsIENsdXN0ZXJpbmcgb2YgQW5ub3RhdGlvbiBNZXRob2RzIiwKICAgICB4bGFiID0gIkFubm90YXRpb24gTWV0aG9kIiwgCiAgICAgeWxhYiA9ICJEaXN0YW5jZSAoMSAtIEFncmVlbWVudCkiLAogICAgIGhhbmcgPSAtMSwKICAgICBjZXgubWFpbiA9IDEuNSwKICAgICBjZXgubGFiID0gMS4zLAogICAgIGNleC5heGlzID0gMS4yKQoKIyBTYXZlIFBORwpwbmcoImFubm90YXRpb25fbWV0aG9kX2RlbmRyb2dyYW1fMDQtMDItMjAyNi5wbmciLCAKICAgIHdpZHRoID0gMTIsIGhlaWdodCA9IDgsIHVuaXRzID0gImluIiwgcmVzID0gMzAwKQpwYXIoY2V4ID0gMS4yKQpwbG90KGhjLCAKICAgICBtYWluID0gIkhpZXJhcmNoaWNhbCBDbHVzdGVyaW5nIG9mIEFubm90YXRpb24gTWV0aG9kcyIsCiAgICAgeGxhYiA9ICJBbm5vdGF0aW9uIE1ldGhvZCIsIAogICAgIHlsYWIgPSAiRGlzdGFuY2UgKDEgLSBBZ3JlZW1lbnQpIiwKICAgICBoYW5nID0gLTEsCiAgICAgY2V4Lm1haW4gPSAxLjUsCiAgICAgY2V4LmxhYiA9IDEuMywKICAgICBjZXguYXhpcyA9IDEuMikKZGV2Lm9mZigpCgojIFNhdmUgUERGCnBkZigiYW5ub3RhdGlvbl9tZXRob2RfZGVuZHJvZ3JhbV8wNC0wMi0yMDI2LnBkZiIsIAogICAgd2lkdGggPSAxMiwgaGVpZ2h0ID0gOCkKcGFyKGNleCA9IDEuMikKcGxvdChoYywgCiAgICAgbWFpbiA9ICJIaWVyYXJjaGljYWwgQ2x1c3RlcmluZyBvZiBBbm5vdGF0aW9uIE1ldGhvZHMiLAogICAgIHhsYWIgPSAiQW5ub3RhdGlvbiBNZXRob2QiLCAKICAgICB5bGFiID0gIkRpc3RhbmNlICgxIC0gQWdyZWVtZW50KSIsCiAgICAgaGFuZyA9IC0xLAogICAgIGNleC5tYWluID0gMS41LAogICAgIGNleC5sYWIgPSAxLjMsCiAgICAgY2V4LmF4aXMgPSAxLjIpCmRldi5vZmYoKQoKY2F0KCJcbuKckyBEZW5kcm9ncmFtIHNhdmVkXG4iKQpgYGAKCgojICBEb21pbmFudCBMYWJlbCBUYWJsZSAoU2ltcGxlc3QpCmBgYHtyLCBmaWcuaGVpZ2h0PTgsIGZpZy53aWR0aD0xMH0KIyBBIGNsZWFuIHRhYmxlIHNob3dpbmcgdGhlIGRvbWluYW50IGxhYmVsIGZyb20gZWFjaCBtZXRob2QgcGVyIGNsdXN0ZXI6CgpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KGtuaXRyKQpsaWJyYXJ5KGthYmxlRXh0cmEpCgojIEV4dHJhY3QgZG9taW5hbnQgbGFiZWwgcGVyIGNsdXN0ZXIgcGVyIG1ldGhvZApjb2xzX25lZWRlZCA8LSBjKCJzZXVyYXRfY2x1c3RlcnMiLCAicHJlZGljdGVkLmlkIiwgInByZWRpY3RlZC5jZWxsdHlwZS5sMiIsIAogICAgICAgICAgICAgICAgICJzaW5nbGVyLmltbXVuZSIsICJzY0FUT01JQ19hbm5vdGF0aW9uIikKCmFubm90YXRpb25fdGFibGUgPC0gQWxsX3NhbXBsZXNfTWVyZ2VkQG1ldGEuZGF0YVssIGNvbHNfbmVlZGVkXSAlPiUKICBncm91cF9ieShzZXVyYXRfY2x1c3RlcnMpICU+JQogIHN1bW1hcmlzZSgKICAgIHNjUHJlZCA9IG5hbWVzKHNvcnQodGFibGUocHJlZGljdGVkLmlkKSwgZGVjcmVhc2luZyA9IFRSVUUpKVsxXSwKICAgIEF6aW11dGggPSBuYW1lcyhzb3J0KHRhYmxlKHByZWRpY3RlZC5jZWxsdHlwZS5sMiksIGRlY3JlYXNpbmcgPSBUUlVFKSlbMV0sCiAgICBTaW5nbGVSID0gbmFtZXMoc29ydCh0YWJsZShzaW5nbGVyLmltbXVuZSksIGRlY3JlYXNpbmcgPSBUUlVFKSlbMV0sCiAgICBzY0FUT01JQyA9IG5hbWVzKHNvcnQodGFibGUoc2NBVE9NSUNfYW5ub3RhdGlvbiksIGRlY3JlYXNpbmcgPSBUUlVFKSlbMV0sCiAgICBuX2NlbGxzID0gbigpLAogICAgLmdyb3VwcyA9ICJkcm9wIgogICkgJT4lCiAgYXJyYW5nZShzZXVyYXRfY2x1c3RlcnMpCgojIEFkZCBjb25zZW5zdXMgY29sdW1uIChUUlVFIGlmIGFsbCA0IGFncmVlKQphbm5vdGF0aW9uX3RhYmxlIDwtIGFubm90YXRpb25fdGFibGUgJT4lCiAgbXV0YXRlKAogICAgQ29uc2Vuc3VzID0gKHNjUHJlZCA9PSBBemltdXRoICYgQXppbXV0aCA9PSBTaW5nbGVSICYgU2luZ2xlUiA9PSBzY0FUT01JQykKICApCgojIERpc3BsYXkKa2FibGUoYW5ub3RhdGlvbl90YWJsZSwgY2FwdGlvbiA9ICJEb21pbmFudCBDZWxsIFR5cGUgQW5ub3RhdGlvbiBwZXIgQ2x1c3RlciIpICU+JQogIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwgImhvdmVyIiwgImNvbmRlbnNlZCIpLCAKICAgICAgICAgICAgICAgIGZ1bGxfd2lkdGggPSBGQUxTRSwgZm9udF9zaXplID0gMTApICU+JQogIGNvbHVtbl9zcGVjKDcsIGJhY2tncm91bmQgPSBpZmVsc2UoYW5ub3RhdGlvbl90YWJsZSRDb25zZW5zdXMsICIjZDRlZGRhIiwgIiNmOGQ3ZGEiKSkKCiMgU2F2ZSBhcyBDU1YKd3JpdGUuY3N2KGFubm90YXRpb25fdGFibGUsICJhbm5vdGF0aW9uX2NvbXBhcmlzb25fY2xlYW5fdGFibGVfMDQtMDItMjAyNi5jc3YiLCAKICAgICAgICAgIHJvdy5uYW1lcyA9IEZBTFNFKQoKY2F0KCJcbuKckyBDbGVhbiBhbm5vdGF0aW9uIHRhYmxlIHNhdmVkXG4iKQoKCmBgYAoKCiMjICBTaW1wbGlmaWVkIFRleHQtQmFzZWQgSGVhdG1hcApgYGB7ciwgZmlnLmhlaWdodD00LCBmaWcud2lkdGg9Mjh9CmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShkcGx5cikKbGlicmFyeSh0aWR5cikKCiMgUHJlcGFyZSBkYXRhCmNvbHNfbmVlZGVkIDwtIGMoInNldXJhdF9jbHVzdGVycyIsICJwcmVkaWN0ZWQuaWQiLCAicHJlZGljdGVkLmNlbGx0eXBlLmwyIiwgCiAgICAgICAgICAgICAgICAgInNpbmdsZXIuaW1tdW5lIiwgInNjQVRPTUlDX2Fubm90YXRpb24iKQoKaGVhdG1hcF9kYXRhIDwtIEFsbF9zYW1wbGVzX01lcmdlZEBtZXRhLmRhdGFbLCBjb2xzX25lZWRlZF0gJT4lCiAgZ3JvdXBfYnkoc2V1cmF0X2NsdXN0ZXJzKSAlPiUKICBzdW1tYXJpc2UoCiAgICBzY1ByZWQgPSBuYW1lcyhzb3J0KHRhYmxlKHByZWRpY3RlZC5pZCksIGRlY3JlYXNpbmcgPSBUUlVFKSlbMV0sCiAgICBBemltdXRoID0gbmFtZXMoc29ydCh0YWJsZShwcmVkaWN0ZWQuY2VsbHR5cGUubDIpLCBkZWNyZWFzaW5nID0gVFJVRSkpWzFdLAogICAgU2luZ2xlUiA9IG5hbWVzKHNvcnQodGFibGUoc2luZ2xlci5pbW11bmUpLCBkZWNyZWFzaW5nID0gVFJVRSkpWzFdLAogICAgc2NBVE9NSUMgPSBuYW1lcyhzb3J0KHRhYmxlKHNjQVRPTUlDX2Fubm90YXRpb24pLCBkZWNyZWFzaW5nID0gVFJVRSkpWzFdLAogICAgLmdyb3VwcyA9ICJkcm9wIgogICkgJT4lCiAgcGl2b3RfbG9uZ2VyKGNvbHMgPSBjKHNjUHJlZCwgQXppbXV0aCwgU2luZ2xlUiwgc2NBVE9NSUMpLAogICAgICAgICAgICAgICBuYW1lc190byA9ICJNZXRob2QiLCB2YWx1ZXNfdG8gPSAiQ2VsbFR5cGUiKSAlPiUKICBtdXRhdGUoCiAgICBNZXRob2QgPSBmYWN0b3IoTWV0aG9kLCBsZXZlbHMgPSBjKCJzY1ByZWQiLCAiQXppbXV0aCIsICJTaW5nbGVSIiwgInNjQVRPTUlDIikpLAogICAgc2V1cmF0X2NsdXN0ZXJzID0gZmFjdG9yKHNldXJhdF9jbHVzdGVycywgbGV2ZWxzID0gYXMuY2hhcmFjdGVyKDA6MTgpKQogICkKCiMgQ3JlYXRlIHRleHQtYmFzZWQgaGVhdG1hcApwX3RleHRfaGVhdG1hcCA8LSBnZ3Bsb3QoaGVhdG1hcF9kYXRhLCBhZXMoeCA9IHNldXJhdF9jbHVzdGVycywgeSA9IE1ldGhvZCwgbGFiZWwgPSBDZWxsVHlwZSkpICsKICBnZW9tX3RpbGUoY29sb3IgPSAiZ3JleTMwIiwgZmlsbCA9ICJ3aGl0ZSIsIGxpbmV3aWR0aCA9IDAuNSkgKwogIGdlb21fdGV4dChzaXplID0gMi41LCBoanVzdCA9IDAuNSwgdmp1c3QgPSAwLjUpICsKICBsYWJzKAogICAgdGl0bGUgPSAiQ2VsbCBUeXBlIEFubm90YXRpb24gQ29tcGFyaXNvbiBBY3Jvc3MgTWV0aG9kcyIsCiAgICB4ID0gIlNldXJhdCBDbHVzdGVyIiwKICAgIHkgPSAiQW5ub3RhdGlvbiBNZXRob2QiCiAgKSArCiAgdGhlbWVfbWluaW1hbChiYXNlX3NpemUgPSAxNCkgKwogIHRoZW1lKAogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiwgc2l6ZSA9IDEyKSwKICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIsIHNpemUgPSAxMiksCiAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41LCBmYWNlID0gImJvbGQiLCBzaXplID0gMTYpLAogICAgcGFuZWwuZ3JpZCA9IGVsZW1lbnRfYmxhbmsoKQogICkKCnByaW50KHBfdGV4dF9oZWF0bWFwKQoKIyBTYXZlCmdnc2F2ZSgiYW5ub3RhdGlvbl90ZXh0X2hlYXRtYXBfMDQtMDItMjAyNi5wbmciLCAKICAgICAgIHBsb3QgPSBwX3RleHRfaGVhdG1hcCwgd2lkdGggPSAyOCwgaGVpZ2h0ID0gNCwgZHBpID0gMzAwLCBiZyA9ICJ3aGl0ZSIpCmdnc2F2ZSgiYW5ub3RhdGlvbl90ZXh0X2hlYXRtYXBfMDQtMDItMjAyNi5wZGYiLCAKICAgICAgIHBsb3QgPSBwX3RleHRfaGVhdG1hcCwgd2lkdGggPSAyOCwgaGVpZ2h0ID0gNCkKCmNhdCgiXG7inJMgVGV4dC1iYXNlZCBoZWF0bWFwIHNhdmVkXG4iKQpgYGAKCiMjICBEb3QgUGxvdCB3aXRoIENvbnNlbnN1cyBIaWdobGlnaHRpbmcKYGBge3IsIGZpZy5oZWlnaHQ9NCwgZmlnLndpZHRoPTI4fQpsaWJyYXJ5KGdncGxvdDIpCgojIEFkZCBjb25zZW5zdXMgY29sdW1uCmhlYXRtYXBfZGF0YV9jb25zZW5zdXMgPC0gaGVhdG1hcF9kYXRhICU+JQogIGdyb3VwX2J5KHNldXJhdF9jbHVzdGVycykgJT4lCiAgbXV0YXRlKAogICAgYWxsX3NhbWUgPSBuX2Rpc3RpbmN0KENlbGxUeXBlKSA9PSAxLAogICAgQ2VsbFR5cGVfc2hvcnQgPSBpZmVsc2UobmNoYXIoQ2VsbFR5cGUpID4gMTUsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFzdGUwKHN1YnN0cihDZWxsVHlwZSwgMSwgMTIpLCAiLi4uIiksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgQ2VsbFR5cGUpCiAgKSAlPiUKICB1bmdyb3VwKCkKCiMgQ3JlYXRlIGRvdCBwbG90CnBfZG90cGxvdCA8LSBnZ3Bsb3QoaGVhdG1hcF9kYXRhX2NvbnNlbnN1cywgCiAgICAgICAgICAgICAgICAgICAgYWVzKHggPSBzZXVyYXRfY2x1c3RlcnMsIHkgPSBNZXRob2QsIGZpbGwgPSBhbGxfc2FtZSkpICsKICBnZW9tX3RpbGUoY29sb3IgPSAiZ3JleTQwIiwgbGluZXdpZHRoID0gMC4zKSArCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IENlbGxUeXBlX3Nob3J0KSwgc2l6ZSA9IDIuMiwgZm9udGZhY2UgPSAiYm9sZCIpICsKICBzY2FsZV9maWxsX21hbnVhbCgKICAgIHZhbHVlcyA9IGMoIlRSVUUiID0gIiNkNGVkZGEiLCAiRkFMU0UiID0gIndoaXRlIiksCiAgICBsYWJlbHMgPSBjKCJUUlVFIiA9ICJDb25zZW5zdXMiLCAiRkFMU0UiID0gIk5vIGNvbnNlbnN1cyIpLAogICAgbmFtZSA9ICIiCiAgKSArCiAgbGFicygKICAgIHRpdGxlID0gIkNlbGwgVHlwZSBBbm5vdGF0aW9uIENvbXBhcmlzb24gd2l0aCBDb25zZW5zdXMgSGlnaGxpZ2h0aW5nIiwKICAgIHggPSAiU2V1cmF0IENsdXN0ZXIgKDAtMTgpIiwKICAgIHkgPSAiQW5ub3RhdGlvbiBNZXRob2QiCiAgKSArCiAgdGhlbWVfbWluaW1hbChiYXNlX3NpemUgPSAxMykgKwogIHRoZW1lKAogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiwgc2l6ZSA9IDExKSwKICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIsIHNpemUgPSAxMiksCiAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41LCBmYWNlID0gImJvbGQiLCBzaXplID0gMTUpLAogICAgcGFuZWwuZ3JpZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiCiAgKQoKcHJpbnQocF9kb3RwbG90KQoKIyBTYXZlCmdnc2F2ZSgiYW5ub3RhdGlvbl9kb3RwbG90X2NvbnNlbnN1c18wNC0wMi0yMDI2LnBuZyIsIAogICAgICAgcGxvdCA9IHBfZG90cGxvdCwgd2lkdGggPSAxNiwgaGVpZ2h0ID0gNiwgZHBpID0gMzAwLCBiZyA9ICJ3aGl0ZSIpCmdnc2F2ZSgiYW5ub3RhdGlvbl9kb3RwbG90X2NvbnNlbnN1c18wNC0wMi0yMDI2LnBkZiIsIAogICAgICAgcGxvdCA9IHBfZG90cGxvdCwgd2lkdGggPSAxNiwgaGVpZ2h0ID0gNikKCmNhdCgiXG7inJMgRG90IHBsb3Qgd2l0aCBjb25zZW5zdXMgaGlnaGxpZ2h0aW5nIHNhdmVkXG4iKQpgYGAKCgojIyAgUGVyLUNsdXN0ZXIgQW5ub3RhdGlvbiBEaXN0cmlidXRpb24KYGBge3IsIGZpZy5oZWlnaHQ9OCwgZmlnLndpZHRoPTEwfQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkodGlkeXIpCmxpYnJhcnkocGF0Y2h3b3JrKQoKIyBGdW5jdGlvbiB0byBwbG90IGFubm90YXRpb24gZGlzdHJpYnV0aW9uIGZvciBvbmUgY2x1c3RlcgpwbG90X2NsdXN0ZXJfYWdyZWVtZW50IDwtIGZ1bmN0aW9uKGNsdXN0ZXJfaWQpIHsKICBjb2xzX25lZWRlZCA8LSBjKCJzZXVyYXRfY2x1c3RlcnMiLCAicHJlZGljdGVkLmlkIiwgInByZWRpY3RlZC5jZWxsdHlwZS5sMiIsIAogICAgICAgICAgICAgICAgICAgInNpbmdsZXIuaW1tdW5lIiwgInNjQVRPTUlDX2Fubm90YXRpb24iKQogIAogICMgRXh0cmFjdCBhbmQgZmlsdGVyIHVzaW5nIGJhc2UgUgogIHRlbXBfZGYgPC0gQWxsX3NhbXBsZXNfTWVyZ2VkQG1ldGEuZGF0YVssIGNvbHNfbmVlZGVkXQogIHRlbXBfZGYgPC0gdGVtcF9kZlt0ZW1wX2RmJHNldXJhdF9jbHVzdGVycyA9PSBjbHVzdGVyX2lkLCBdCiAgCiAgIyBSZW5hbWUgY29sdW1ucwogIGNvbG5hbWVzKHRlbXBfZGYpIDwtIGMoIkNsdXN0ZXIiLCAic2NQcmVkIiwgIkF6aW11dGgiLCAiU2luZ2xlUiIsICJzY0FUT01JQyIpCiAgCiAgIyBOb3cgdXNlIGRwbHlyIHNhZmVseSB3aXRoIGV4cGxpY2l0IG5hbWVzcGFjZQogIGRmIDwtIHRlbXBfZGYgJT4lCiAgICBkcGx5cjo6c2VsZWN0KC1DbHVzdGVyKSAlPiUKICAgIHBpdm90X2xvbmdlcihldmVyeXRoaW5nKCksIG5hbWVzX3RvID0gIm1ldGhvZCIsIHZhbHVlc190byA9ICJsYWJlbCIpICU+JQogICAgZmlsdGVyKCFpcy5uYShsYWJlbCkpICU+JQogICAgZHBseXI6OmNvdW50KG1ldGhvZCwgbGFiZWwpICU+JQogICAgZ3JvdXBfYnkobWV0aG9kKSAlPiUKICAgIG11dGF0ZShwcm9wID0gbiAvIHN1bShuKSkgJT4lCiAgICB1bmdyb3VwKCkKICAKICBnZ3Bsb3QoZGYsIGFlcyh4ID0gbWV0aG9kLCB5ID0gcHJvcCwgZmlsbCA9IGxhYmVsKSkgKwogICAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpICsKICAgIGxhYnModGl0bGUgPSBwYXN0ZSgiQ2x1c3RlciIsIGNsdXN0ZXJfaWQpLAogICAgICAgICB5ID0gIlByb3BvcnRpb24iLCB4ID0gIiIpICsKICAgIHRoZW1lX21pbmltYWwoYmFzZV9zaXplID0gMTApICsKICAgIHRoZW1lKAogICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEsIHNpemUgPSA4KSwKICAgICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLAogICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41LCBmYWNlID0gImJvbGQiKQogICAgKQp9CgojIENyZWF0ZSBwbG90cyBmb3IgY2x1c3RlcnMgMC0xOApjbHVzdGVycyA8LSBhcy5jaGFyYWN0ZXIoMDoxOCkKY2x1c3Rlcl9wbG90cyA8LSBsYXBwbHkoY2x1c3RlcnMsIHBsb3RfY2x1c3Rlcl9hZ3JlZW1lbnQpCgojIENvbWJpbmUgcGxvdHMKcF9jb21iaW5lZCA8LSB3cmFwX3Bsb3RzKGNsdXN0ZXJfcGxvdHMsIG5jb2wgPSA1KSArCiAgcGxvdF9hbm5vdGF0aW9uKAogICAgdGl0bGUgPSAiQW5ub3RhdGlvbiBEaXN0cmlidXRpb24gQWNyb3NzIE1ldGhvZHMgcGVyIENsdXN0ZXIgKDAtMTgpIiwKICAgIHRoZW1lID0gdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSwgZmFjZSA9ICJib2xkIiwgc2l6ZSA9IDE2KSkKICApCgpwcmludChwX2NvbWJpbmVkKQoKIyBTYXZlCmdnc2F2ZSgiYW5ub3RhdGlvbl9wZXJfY2x1c3Rlcl9jb21wYXJpc29uXzA0LTAyLTIwMjYucG5nIiwgCiAgICAgICBwbG90ID0gcF9jb21iaW5lZCwgd2lkdGggPSAxNiwgaGVpZ2h0ID0gMTIsIGRwaSA9IDMwMCwgYmcgPSAid2hpdGUiKQpnZ3NhdmUoImFubm90YXRpb25fcGVyX2NsdXN0ZXJfY29tcGFyaXNvbl8wNC0wMi0yMDI2LnBkZiIsIAogICAgICAgcGxvdCA9IHBfY29tYmluZWQsIHdpZHRoID0gMTYsIGhlaWdodCA9IDEyKQoKY2F0KCJcbuKckyBQZXItY2x1c3RlciBjb21wYXJpc29uIHNhdmVkXG4iKQpgYGAKCgoKIyBJbXByb3ZlZCBWZXJzaW9uIGZvciBNYW51c2NyaXB0IENvbXBhcmlzb24KYGBge3IsIGZpZy5oZWlnaHQ9NCwgZmlnLndpZHRoPTI4fQoKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KHRpZHlyKQoKIyBQcmVwYXJlIGRhdGEKY29sc19uZWVkZWQgPC0gYygic2V1cmF0X2NsdXN0ZXJzIiwgInByZWRpY3RlZC5pZCIsICJwcmVkaWN0ZWQuY2VsbHR5cGUubDIiLCAKICAgICAgICAgICAgICAgICAic2luZ2xlci5pbW11bmUiLCAic2NBVE9NSUNfYW5ub3RhdGlvbiIpCgpoZWF0bWFwX2RhdGEgPC0gQWxsX3NhbXBsZXNfTWVyZ2VkQG1ldGEuZGF0YVssIGNvbHNfbmVlZGVkXSAlPiUKICBncm91cF9ieShzZXVyYXRfY2x1c3RlcnMpICU+JQogIHN1bW1hcmlzZSgKICAgIHNjUHJlZCA9IG5hbWVzKHNvcnQodGFibGUocHJlZGljdGVkLmlkKSwgZGVjcmVhc2luZyA9IFRSVUUpKVsxXSwKICAgIEF6aW11dGggPSBuYW1lcyhzb3J0KHRhYmxlKHByZWRpY3RlZC5jZWxsdHlwZS5sMiksIGRlY3JlYXNpbmcgPSBUUlVFKSlbMV0sCiAgICBTaW5nbGVSID0gbmFtZXMoc29ydCh0YWJsZShzaW5nbGVyLmltbXVuZSksIGRlY3JlYXNpbmcgPSBUUlVFKSlbMV0sCiAgICBzY0FUT01JQyA9IG5hbWVzKHNvcnQodGFibGUoc2NBVE9NSUNfYW5ub3RhdGlvbiksIGRlY3JlYXNpbmcgPSBUUlVFKSlbMV0sCiAgICAuZ3JvdXBzID0gImRyb3AiCiAgKSAlPiUKICBwaXZvdF9sb25nZXIoY29scyA9IGMoc2NQcmVkLCBBemltdXRoLCBTaW5nbGVSLCBzY0FUT01JQyksCiAgICAgICAgICAgICAgIG5hbWVzX3RvID0gIk1ldGhvZCIsIHZhbHVlc190byA9ICJDZWxsVHlwZSIpICU+JQogIG11dGF0ZSgKICAgIE1ldGhvZCA9IGZhY3RvcihNZXRob2QsIGxldmVscyA9IGMoInNjQVRPTUlDIiwgIlNpbmdsZVIiLCAiQXppbXV0aCIsICJzY1ByZWQiKSksCiAgICBzZXVyYXRfY2x1c3RlcnMgPSBmYWN0b3Ioc2V1cmF0X2NsdXN0ZXJzLCBsZXZlbHMgPSBhcy5jaGFyYWN0ZXIoMDoxOCkpLAogICAgIyBTaG9ydGVuIGxvbmcgbGFiZWxzIGZvciByZWFkYWJpbGl0eQogICAgQ2VsbFR5cGVfZGlzcGxheSA9IGlmZWxzZShuY2hhcihDZWxsVHlwZSkgPiAyMCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhc3RlMChzdWJzdHIoQ2VsbFR5cGUsIDEsIDE3KSwgIi4uLiIpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQ2VsbFR5cGUpCiAgKQoKIyBDcmVhdGUgcHVibGljYXRpb24tcXVhbGl0eSBoZWF0bWFwCnBfbWFudXNjcmlwdCA8LSBnZ3Bsb3QoaGVhdG1hcF9kYXRhLCAKICAgICAgICAgICAgICAgICAgICAgICBhZXMoeCA9IHNldXJhdF9jbHVzdGVycywgeSA9IE1ldGhvZCwgbGFiZWwgPSBDZWxsVHlwZV9kaXNwbGF5KSkgKwogIGdlb21fdGlsZShjb2xvciA9ICJncmV5MjAiLCBmaWxsID0gIndoaXRlIiwgbGluZXdpZHRoID0gMC44KSArCiAgZ2VvbV90ZXh0KHNpemUgPSAzLCBoanVzdCA9IDAuNSwgdmp1c3QgPSAwLjUsIGZvbnRmYWNlID0gImJvbGQiKSArCiAgbGFicygKICAgIHRpdGxlID0gIkRpc2NvcmRhbmNlIGluIEF1dG9tYXRlZCBDZWxsIFR5cGUgQW5ub3RhdGlvbiAtIEhlcnJlcmEgRGF0YXNldCIsCiAgICB4ID0gIlNldXJhdCBDbHVzdGVyIiwKICAgIHkgPSAiQW5ub3RhdGlvbiBNZXRob2QiCiAgKSArCiAgdGhlbWVfbWluaW1hbChiYXNlX3NpemUgPSAxNikgKwogIHRoZW1lKAogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiwgc2l6ZSA9IDE0KSwKICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIsIHNpemUgPSAxNCksCiAgICBheGlzLnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNiwgZmFjZSA9ICJib2xkIiksCiAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41LCBmYWNlID0gImJvbGQiLCBzaXplID0gMTgpLAogICAgcGFuZWwuZ3JpZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBsb3QubWFyZ2luID0gbWFyZ2luKDEwLCAxMCwgMTAsIDEwKQogICkKCnByaW50KHBfbWFudXNjcmlwdCkKCiMgU2F2ZSBoaWdoLXF1YWxpdHkgdmVyc2lvbnMgZm9yIG1hbnVzY3JpcHQKZ2dzYXZlKCJGaWd1cmVYX0hlcnJlcmFfQW5ub3RhdGlvbl9Db21wYXJpc29uLnBuZyIsIAogICAgICAgcGxvdCA9IHBfbWFudXNjcmlwdCwgd2lkdGggPSAyOCwgaGVpZ2h0ID0gNSwgZHBpID0gNjAwLCBiZyA9ICJ3aGl0ZSIpCmdnc2F2ZSgiRmlndXJlWF9IZXJyZXJhX0Fubm90YXRpb25fQ29tcGFyaXNvbi5wZGYiLCAKICAgICAgIHBsb3QgPSBwX21hbnVzY3JpcHQsIHdpZHRoID0gMjgsIGhlaWdodCA9IDUpCmdnc2F2ZSgiRmlndXJlWF9IZXJyZXJhX0Fubm90YXRpb25fQ29tcGFyaXNvbi50aWZmIiwgCiAgICAgICBwbG90ID0gcF9tYW51c2NyaXB0LCB3aWR0aCA9IDI4LCBoZWlnaHQgPSA1LCBkcGkgPSA2MDAsIGJnID0gIndoaXRlIikKCmNhdCgiXG7inJMgSGVycmVyYSBtYW51c2NyaXB0IGZpZ3VyZSBzYXZlZCAoUE5HLCBQREYsIFRJRkYgYXQgNjAwIERQSSlcbiIpCgojIAojICoqRmlndXJlIFguIEF1dG9tYXRlZCBhbm5vdGF0aW9uIG1ldGhvZHMgc2hvdyBwb29yIGNvbmNvcmRhbmNlIGFjcm9zcyB0d28gaW5kZXBlbmRlbnQgU8OpemFyeSBzeW5kcm9tZSBkYXRhc2V0cy4qKiAgCiMgKiooQSkqKiBDZWxsIGxpbmUtZGVyaXZlZCBTw6l6YXJ5IHNhbXBsZXMuICoqKEIpKiogUGF0aWVudC1kZXJpdmVkIFPDqXphcnkgc2FtcGxlcyAoSGVycmVyYSBkYXRhc2V0KS4gRWFjaCBjZWxsIGRpc3BsYXlzIHRoZSBkb21pbmFudCBhbm5vdGF0aW9uIGFzc2lnbmVkIGJ5IGZvdXIgcmVmZXJlbmNlLWJhc2VkIG1ldGhvZHMgKHNjQVRPTUlDLCBTaW5nbGVSLCBBemltdXRoLCBhbmQgc2NQcmVkKSB0byBjZWxscyB3aXRoaW4gZWFjaCBjbHVzdGVyLiBEZXNwaXRlIHVzaW5nIGlkZW50aWNhbCBtZXRob2RvbG9neSwgbWV0aG9kcyBhc3NpZ24gZGl2ZXJnZW50IGxhYmVscyBpbiBib3RoIGRhdGFzZXRzLCBkZW1vbnN0cmF0aW5nIHRoYXQgbWFsaWduYW50IFQtY2VsbCBwb3B1bGF0aW9ucyBmcm9tIFPDqXphcnkgc3luZHJvbWUgY29uc2lzdGVudGx5IGZhaWwgdG8gbWF0Y2ggaGVhbHRoeSByZWZlcmVuY2UgYXRsYXNlcy4gVGhpcyBjcm9zcy1kYXRhc2V0IHZhbGlkYXRpb24gaGlnaGxpZ2h0cyB0aGUgbmVjZXNzaXR5IGZvciBtYW51YWwgY3VyYXRpb24gb3IgZGlzZWFzZS1zcGVjaWZpYyBhbm5vdGF0aW9uIHN0cmF0ZWdpZXMuCgoKYGBgCgoKCgojIHNlc3Npb25JbmZvKCkKYGBge3J9CgpzZXNzaW9uSW5mbygpCgoKYGBgCgoK