load libraries
knitr::opts_chunk$set(
echo = TRUE,
message = FALSE,
warning = FALSE,
fig.align = "center",
dpi = 150
)
library(Seurat)
library(reticulate)
library(ggplot2)
library(pheatmap)
library(dplyr)
library(RColorBrewer)
# Python environment
Sys.setenv(RETICULATE_PYTHON = "/home/bioinfo/.virtualenvs/r-reticulate/bin/python")
use_python("/home/bioinfo/.virtualenvs/r-reticulate/bin/python", required = TRUE)
sc <- import("scanpy")
ad <- import("anndata")
cat("✓ Python libraries imported\n")
✓ Python libraries imported
# Output directory
dir.create("Output_Figures", showWarnings = FALSE)
# State constants
STATE_ORDER <- c("CD4 Naive","CD4 TCM","CD4 TEM","CD4 Temra/CTL","Treg")
STATE_COLORS <- c(
"CD4 Naive" = "#4472C4",
"CD4 TCM" = "#70AD47",
"CD4 TEM" = "#ED7D31",
"CD4 Temra/CTL" = "#C00000",
"Treg" = "#7030A0"
)
Load Reference
Object
cd4_ref <- readRDS("cd4_ref_dual_trajectory.rds")
cat("Cells loaded:", ncol(cd4_ref), "\n")
Cells loaded: 11466
stopifnot(
"predicted.celltype.l2 missing" = "predicted.celltype.l2" %in% colnames(cd4_ref@meta.data),
"milestone missing" = "milestone" %in% colnames(cd4_ref@meta.data),
"mst_pseudotime_norm missing" = "mst_pseudotime_norm" %in% colnames(cd4_ref@meta.data),
"monocle3_pseudotime_norm missing" = "monocle3_pseudotime_norm" %in% colnames(cd4_ref@meta.data),
"pca reduction missing" = "pca" %in% names(cd4_ref@reductions),
"umap reduction missing" = "umap" %in% names(cd4_ref@reductions)
)
cat("✅ All slots verified\n")
✅ All slots verified
cat("\nCell state distribution:\n")
Cell state distribution:
print(table(cd4_ref$predicted.celltype.l2))
CD4 Naive CD4 TCM CD4 TEM CD4 Temra/CTL Treg
2037 9067 145 10 207
cat("\nMilestone distribution:\n")
Milestone distribution:
print(table(cd4_ref$milestone))
M00 M01 M02 M03 M04 M05 M06 M07
2037 388 8679 64 81 10 146 61
Build AnnData
Object
# ── Build AnnData — PCA as X, no obsm needed ──────────────────────────────
DefaultAssay(cd4_ref) <- "integrated"
# Pass PCA embeddings directly as X (exactly like TF matrix worked before)
pca_embed <- Embeddings(cd4_ref, "pca")[, 1:20]
umap_embed <- Embeddings(cd4_ref, "umap")
obs <- data.frame(
row.names = colnames(cd4_ref),
celltype_l2 = as.character(cd4_ref$predicted.celltype.l2),
milestone = as.character(cd4_ref$milestone),
mst_pt = cd4_ref$mst_pseudotime_norm,
monocle3_pt = cd4_ref$monocle3_pseudotime_norm
)
# ✅ PCA goes into X directly — same pattern that worked for TF matrix
adata <- ad$AnnData(X = pca_embed, obs = obs)
cat(sprintf("✅ AnnData: %d cells × %d PCs\n",
py_to_r(adata$n_obs), py_to_r(adata$n_vars)))
✅ AnnData: 11466 cells × 20 PCs
# ── Neighbors using X directly (no obsm needed) ───────────────────────────
sc$pp$neighbors(
adata,
use_rep = "X", # ← matches working script pattern
n_neighbors = 30L,
metric = "cosine"
)
cat("✅ KNN graph computed\n")
✅ KNN graph computed
# ── PAGA ──────────────────────────────────────────────────────────────────
sc$tl$paga(adata, groups = "celltype_l2")
cat("✅ PAGA state-level complete\n")
✅ PAGA state-level complete
PAGA Analysis
PAGA at State Level
(l2)
cat("Running PAGA — Azimuth l2 states...\n")
Running PAGA — Azimuth l2 states...
sc$tl$paga(adata, groups = "celltype_l2")
py_run_string("
import numpy as np
paga_conn = r.adata.uns['paga']['connectivities'].todense()
state_names = r.adata.obs['celltype_l2'].cat.categories.values
np.savetxt('paga_state_conn.csv', paga_conn, delimiter=',', header=','.join(state_names), comments='')
print('State connectivity saved')
", local = TRUE)
# Read CSV - skip row names column, assign manually
paga_state_df <- read.csv("paga_state_conn.csv", header=TRUE, check.names=FALSE)
state_names <- colnames(paga_state_df)
paga_state_conn <- as.matrix(paga_state_df)
rownames(paga_state_conn) <- state_names
colnames(paga_state_conn) <- state_names
cat("✅ State-level PAGA complete\n\nConnectivity matrix:\n")
✅ State-level PAGA complete
Connectivity matrix:
print(round(paga_state_conn, 3))
CD4 Naive CD4 TCM CD4 TEM CD4 Temra/CTL Treg
CD4 Naive 0.000 0.630 0.006 0.000 0.143
CD4 TCM 0.630 0.000 0.844 0.889 0.490
CD4 TEM 0.006 0.844 0.000 1.000 0.037
CD4 Temra/CTL 0.000 0.889 1.000 0.000 0.000
Treg 0.143 0.490 0.037 0.000 0.000
# Clean up temp file
file.remove("paga_state_conn.csv")
[1] TRUE
PAGA at Milestone
Level (M00–M07)
cat("Running PAGA — milestones M00-M07...\n")
Running PAGA — milestones M00-M07...
sc$tl$paga(adata, groups = "milestone")
py_run_string("
import numpy as np
paga_conn = r.adata.uns['paga']['connectivities'].todense()
ms_names = r.adata.obs['milestone'].cat.categories.values
np.savetxt('paga_ms_conn.csv', paga_conn, delimiter=',', header=','.join(ms_names), comments='')
print('Milestone connectivity saved')
", local = TRUE)
# Read CSV - skip row names column, assign manually
paga_ms_df <- read.csv("paga_ms_conn.csv", header=TRUE, check.names=FALSE)
ms_names <- colnames(paga_ms_df)
paga_ms_conn <- as.matrix(paga_ms_df)
rownames(paga_ms_conn) <- ms_names
colnames(paga_ms_conn) <- ms_names
cat("✅ Milestone-level PAGA complete\n\nConnectivity matrix:\n")
✅ Milestone-level PAGA complete
Connectivity matrix:
print(round(paga_ms_conn, 3))
M00 M01 M02 M03 M04 M05 M06 M07
M00 0.000 0.183 0.650 0.013 0.000 0.000 0.203 0.000
M01 0.183 0.000 0.124 0.007 0.006 0.000 0.010 0.000
M02 0.650 0.124 0.000 0.741 0.992 0.929 0.440 0.681
M03 0.013 0.007 0.741 0.000 1.000 1.000 0.099 0.047
M04 0.000 0.006 0.992 1.000 0.000 1.000 0.000 0.000
M05 0.000 0.000 0.929 1.000 1.000 0.000 0.000 0.000
M06 0.203 0.010 0.440 0.099 0.000 0.000 0.000 1.000
M07 0.000 0.000 0.681 0.047 0.000 0.000 1.000 0.000
# Clean up temp file
file.remove("paga_ms_conn.csv")
[1] TRUE
PAGA Connectivity
Heatmaps (R)
# ── State-level heatmap ───────────────────────────────────────────────────
conn_sub <- paga_state_conn[
intersect(STATE_ORDER, rownames(paga_state_conn)),
intersect(STATE_ORDER, colnames(paga_state_conn))
]
# Save to file
pheatmap(
conn_sub,
color = colorRampPalette(c("white","#2980b9","#c0392b"))(50),
display_numbers = round(conn_sub, 2),
number_color = "black",
fontsize_number = 11,
main = "PAGA connectivity — CD4 T cell states",
cluster_rows = FALSE,
cluster_cols = FALSE,
filename = "Output_Figures/PAGA_state_heatmap.png",
width = 7, height = 6
)
# Display in notebook ← ADD THIS
pheatmap(
conn_sub,
color = colorRampPalette(c("white","#2980b9","#c0392b"))(50),
display_numbers = round(conn_sub, 2),
number_color = "black",
fontsize_number = 11,
main = "PAGA connectivity — CD4 T cell states",
cluster_rows = FALSE,
cluster_cols = FALSE
# NO filename = displays in notebook
)

# ── Milestone-level heatmap ───────────────────────────────────────────────
ms_order <- paste0("M", sprintf("%02d", 0:7))
ms_sub <- paga_ms_conn[
intersect(ms_order, rownames(paga_ms_conn)),
intersect(ms_order, colnames(paga_ms_conn))
]
# Save to file
pheatmap(
ms_sub,
color = colorRampPalette(c("white","#2980b9","#c0392b"))(50),
display_numbers = round(ms_sub, 2),
number_color = "black",
fontsize_number = 10,
main = "PAGA connectivity — Milestones M00–M07",
cluster_rows = FALSE,
cluster_cols = FALSE,
filename = "Output_Figures/PAGA_milestone_heatmap.png",
width = 8, height = 7
)
# Display in notebook ← ADD THIS
pheatmap(
ms_sub,
color = colorRampPalette(c("white","#2980b9","#c0392b"))(50),
display_numbers = round(ms_sub, 2),
number_color = "black",
fontsize_number = 10,
main = "PAGA connectivity — Milestones M00–M07",
cluster_rows = FALSE,
cluster_cols = FALSE
# NO filename = displays in notebook
)

cat("✅ Heatmaps saved\n")
✅ Heatmaps saved
MST Topology
Validation
Define MST Edges
# MST topology:
# M00(Naive) → M01(TCM-early) → M02(TCM-late) → M03(TEM-early) → M04(TEM-late) → M05(Temra)
# ↓
# M06(Treg-naive) → M07(Treg-terminal)
# ── FIX 3: Correct state edges (removed duplicate CD4 TCM → CD4 TCM) ─────
mst_state_edges <- data.frame(
from = c("CD4 Naive","CD4 TCM","CD4 TEM","CD4 TCM"),
to = c("CD4 TCM", "CD4 TEM","CD4 Temra/CTL","Treg"),
edge_label = c("Main axis","Main axis","Terminal","Treg branch")
)
mst_milestone_edges <- data.frame(
from = c("M00","M01","M02","M02","M03","M04","M06"),
to = c("M01","M02","M03","M06","M04","M05","M07"),
biology = c("Naive→TCM-early","TCM-early→late","TCM-late→TEM-early",
"TCM-late→Treg-naive","TEM-early→late","TEM-late→Temra",
"Treg-naive→terminal")
)
cat("MST state edges:\n"); print(mst_state_edges)
MST state edges:
from to edge_label
1 CD4 Naive CD4 TCM Main axis
2 CD4 TCM CD4 TEM Main axis
3 CD4 TEM CD4 Temra/CTL Terminal
4 CD4 TCM Treg Treg branch
cat("\nMST milestone edges:\n"); print(mst_milestone_edges)
MST milestone edges:
from to biology
1 M00 M01 Naive→TCM-early
2 M01 M02 TCM-early→late
3 M02 M03 TCM-late→TEM-early
4 M02 M06 TCM-late→Treg-naive
5 M03 M04 TEM-early→late
6 M04 M05 TEM-late→Temra
7 M06 M07 Treg-naive→terminal
State-Level
Validation
mst_state_edges$paga_score <- mapply(
function(f, t) {
if (f %in% rownames(paga_state_conn) && t %in% colnames(paga_state_conn))
round(paga_state_conn[f, t], 3) else NA_real_
},
mst_state_edges$from, mst_state_edges$to
)
mst_state_edges$status <- case_when(
mst_state_edges$paga_score > 0.50 ~ "Strong",
mst_state_edges$paga_score > 0.30 ~ "Moderate",
mst_state_edges$paga_score > 0.15 ~ "Weak",
TRUE ~ "Not confirmed"
)
cat("\nMST state-level validation:\n")
MST state-level validation:
print(mst_state_edges)
from to edge_label paga_score status
1 CD4 Naive CD4 TCM Main axis 0.630 Strong
2 CD4 TCM CD4 TEM Main axis 0.844 Strong
3 CD4 TEM CD4 Temra/CTL Terminal 1.000 Strong
4 CD4 TCM Treg Treg branch 0.490 Moderate
cat(sprintf("\n%d/%d MST state edges confirmed (PAGA > 0.3)\n",
sum(mst_state_edges$paga_score > 0.3, na.rm=TRUE),
nrow(mst_state_edges)))
4/4 MST state edges confirmed (PAGA > 0.3)
ggplot(mst_state_edges,
aes(x = reorder(paste(from, "→", to), paga_score),
y = paga_score, fill = status)) +
geom_col(width = 0.7) +
geom_hline(yintercept = 0.3, linetype="dashed",
colour="grey40", linewidth=0.8) +
geom_hline(yintercept = 0.5, linetype="dotted",
colour="grey40", linewidth=0.8) +
annotate("text", x=0.6, y=0.32, label="Moderate (0.3)",
size=3, hjust=0, colour="grey40") +
annotate("text", x=0.6, y=0.52, label="Strong (0.5)",
size=3, hjust=0, colour="grey40") +
scale_fill_manual(
values = c("Strong"="#27ae60","Moderate"="#2980b9",
"Weak"="#f39c12","Not confirmed"="#c0392b"),
name = "Validation"
) +
coord_flip() +
theme_classic() +
labs(x=NULL, y="PAGA connectivity score",
title="Custom MST state edges — PAGA validation",
subtitle="Dashed = 0.3 | Dotted = 0.5") +
theme(plot.title=element_text(size=13, face="bold"))

ggsave("Output_Figures/MST_PAGA_state_validation.png",
width=9, height=5, dpi=150)
Milestone-Level
Validation
mst_milestone_edges$paga_score <- mapply(
function(f, t) {
if (f %in% rownames(paga_ms_conn) && t %in% colnames(paga_ms_conn))
round(paga_ms_conn[f, t], 3) else NA_real_
},
mst_milestone_edges$from, mst_milestone_edges$to
)
mst_milestone_edges$status <- case_when(
mst_milestone_edges$paga_score > 0.50 ~ "Strong",
mst_milestone_edges$paga_score > 0.30 ~ "Moderate",
mst_milestone_edges$paga_score > 0.15 ~ "Weak",
TRUE ~ "Not confirmed"
)
cat("MST milestone-level validation:\n")
MST milestone-level validation:
print(mst_milestone_edges)
from to biology paga_score status
1 M00 M01 Naive→TCM-early 0.183 Weak
2 M01 M02 TCM-early→late 0.124 Not confirmed
3 M02 M03 TCM-late→TEM-early 0.741 Strong
4 M02 M06 TCM-late→Treg-naive 0.440 Moderate
5 M03 M04 TEM-early→late 1.000 Strong
6 M04 M05 TEM-late→Temra 1.000 Strong
7 M06 M07 Treg-naive→terminal 1.000 Strong
cat(sprintf("\n%d/%d MST milestone edges confirmed (PAGA > 0.3)\n",
sum(mst_milestone_edges$paga_score > 0.3, na.rm=TRUE),
nrow(mst_milestone_edges)))
5/7 MST milestone edges confirmed (PAGA > 0.3)
ggplot(mst_milestone_edges,
aes(x = reorder(paste(from, "→", to), paga_score),
y = paga_score, fill = status)) +
geom_col(width = 0.7) +
geom_hline(yintercept = 0.3, linetype="dashed",
colour="grey40", linewidth=0.8) +
geom_text(aes(label=biology), hjust=-0.1, size=3) +
scale_fill_manual(
values = c("Strong"="#27ae60","Moderate"="#2980b9",
"Weak"="#f39c12","Not confirmed"="#c0392b"),
name = "Validation"
) +
coord_flip() +
expand_limits(y=1.2) +
theme_classic() +
labs(x=NULL, y="PAGA connectivity score",
title="MST milestone edges (M00–M07) — PAGA validation") +
theme(plot.title=element_text(size=13, face="bold"))

ggsave("Output_Figures/MST_PAGA_milestone_validation.png",
width=10, height=6, dpi=150)
Final Summary
cat("══════════════════════════════════════════\n")
══════════════════════════════════════════
cat("PAGA VALIDATION SUMMARY\n")
PAGA VALIDATION SUMMARY
cat("══════════════════════════════════════════\n")
══════════════════════════════════════════
cat(sprintf("State-level: %d/%d edges confirmed (> 0.3)\n",
sum(mst_state_edges$paga_score > 0.3, na.rm=TRUE),
nrow(mst_state_edges)))
State-level: 4/4 edges confirmed (> 0.3)
cat(sprintf("Milestone-level: %d/%d edges confirmed (> 0.3)\n",
sum(mst_milestone_edges$paga_score > 0.3, na.rm=TRUE),
nrow(mst_milestone_edges)))
Milestone-level: 5/7 edges confirmed (> 0.3)
cat("\nFigures saved:\n")
Figures saved:
for (f in list.files("Output_Figures", pattern="PAGA|MST"))
cat(sprintf(" ✅ %s\n", f))
✅ MST_PAGA_milestone_validation.png
✅ MST_PAGA_state_validation.png
✅ PAGA_milestone_heatmap.png
✅ PAGA_milestones.png
✅ PAGA_state_heatmap.png
✅ PAGA_states.png
# ── Save PAGA results ──────────────────────────────────────────────────────
# Add PAGA connectivity scores back to cd4_ref metadata (optional)
# So every cell knows its state's PAGA connectivity
saveRDS(cd4_ref, "cd4_ref_dual_trajectory_with_PAGA.rds") # Re-save with any updates
cat("✅ cd4_ref updated → cd4_ref_dual_trajectory.rds\n")
✅ cd4_ref updated → cd4_ref_dual_trajectory.rds
# ── Quick reload check ─────────────────────────────────────────────────────
cat("\nTo reload PAGA results later:\n")
To reload PAGA results later:
cat(" paga_results <- readRDS('paga_validation_results.rds')\n")
paga_results <- readRDS('paga_validation_results.rds')
cat(" paga_state_conn <- paga_results$state_connectivity\n")
paga_state_conn <- paga_results$state_connectivity
cat(" paga_ms_conn <- paga_results$milestone_connectivity\n")
paga_ms_conn <- paga_results$milestone_connectivity
cat(" adata <- anndata$read_h5ad('cd4_ref_PAGA_validated.h5ad')\n")
adata <- anndata$read_h5ad('cd4_ref_PAGA_validated.h5ad')
# ── Final inventory ────────────────────────────────────────────────────────
cat("\n══════════════════════════════════════════\n")
══════════════════════════════════════════
cat("SAVED OBJECTS\n")
SAVED OBJECTS
cat("══════════════════════════════════════════\n")
══════════════════════════════════════════
cat(" cd4_ref_PAGA_validated.h5ad ← AnnData with PAGA\n")
cd4_ref_PAGA_validated.h5ad ← AnnData with PAGA
cat(" paga_validation_results.rds ← R matrices + validation\n")
paga_validation_results.rds ← R matrices + validation
cat(" cd4_ref_dual_trajectory.rds ← Seurat object (unchanged)\n")
cd4_ref_dual_trajectory.rds ← Seurat object (unchanged)
cat("\nOutput_Figures/:\n")
Output_Figures/:
for (f in list.files("Output_Figures", pattern="PAGA|MST"))
cat(sprintf(" ✅ %s\n", f))
✅ MST_PAGA_milestone_validation.png
✅ MST_PAGA_state_validation.png
✅ PAGA_milestone_heatmap.png
✅ PAGA_milestones.png
✅ PAGA_state_heatmap.png
✅ PAGA_states.png
LS0tCnRpdGxlOiAiUEFHQSBUb3BvbG9neSBWYWxpZGF0aW9uIOKAlCBDRDQgVCBDZWxsIFJlZmVyZW5jZSIKc3VidGl0bGU6ICJQQUdBIGNvbm5lY3Rpdml0eSB8IEN1c3RvbSBNU1QgZWRnZSB2YWxpZGF0aW9uIgphdXRob3I6ICJOYXNpciBNYWhtb29kIEFiYmFzaSIKZGF0ZTogImByIFN5cy5EYXRlKClgIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIG51bWJlcl9zZWN0aW9uczogdHJ1ZQogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6CiAgICAgIGNvbGxhcHNlZDogdHJ1ZQogICAgdGhlbWU6IGpvdXJuYWwKLS0tCgoKIyBsb2FkIGxpYnJhcmllcwpgYGB7ciBzZXR1cCwgaW5jbHVkZT1UUlVFfQprbml0cjo6b3B0c19jaHVuayRzZXQoCiAgZWNobyAgICA9IFRSVUUsCiAgbWVzc2FnZSA9IEZBTFNFLAogIHdhcm5pbmcgPSBGQUxTRSwKICBmaWcuYWxpZ24gPSAiY2VudGVyIiwKICBkcGkgICAgID0gMTUwCikKCmxpYnJhcnkoU2V1cmF0KQpsaWJyYXJ5KHJldGljdWxhdGUpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShwaGVhdG1hcCkKbGlicmFyeShkcGx5cikKbGlicmFyeShSQ29sb3JCcmV3ZXIpCgojIFB5dGhvbiBlbnZpcm9ubWVudApTeXMuc2V0ZW52KFJFVElDVUxBVEVfUFlUSE9OID0gIi9ob21lL2Jpb2luZm8vLnZpcnR1YWxlbnZzL3ItcmV0aWN1bGF0ZS9iaW4vcHl0aG9uIikKdXNlX3B5dGhvbigiL2hvbWUvYmlvaW5mby8udmlydHVhbGVudnMvci1yZXRpY3VsYXRlL2Jpbi9weXRob24iLCByZXF1aXJlZCA9IFRSVUUpCgpzYyA8LSBpbXBvcnQoInNjYW5weSIpCmFkIDwtIGltcG9ydCgiYW5uZGF0YSIpCgpjYXQoIuKckyBQeXRob24gbGlicmFyaWVzIGltcG9ydGVkXG4iKQoKIyBPdXRwdXQgZGlyZWN0b3J5CmRpci5jcmVhdGUoIk91dHB1dF9GaWd1cmVzIiwgc2hvd1dhcm5pbmdzID0gRkFMU0UpCgojIFN0YXRlIGNvbnN0YW50cwpTVEFURV9PUkRFUiA8LSBjKCJDRDQgTmFpdmUiLCJDRDQgVENNIiwiQ0Q0IFRFTSIsIkNENCBUZW1yYS9DVEwiLCJUcmVnIikKU1RBVEVfQ09MT1JTIDwtIGMoCiAgIkNENCBOYWl2ZSIgICAgID0gIiM0NDcyQzQiLAogICJDRDQgVENNIiAgICAgICA9ICIjNzBBRDQ3IiwKICAiQ0Q0IFRFTSIgICAgICAgPSAiI0VEN0QzMSIsCiAgIkNENCBUZW1yYS9DVEwiID0gIiNDMDAwMDAiLAogICJUcmVnIiAgICAgICAgICA9ICIjNzAzMEEwIgopCgpgYGAKCiMgTG9hZCBSZWZlcmVuY2UgT2JqZWN0CgpgYGB7ciBsb2FkLW9iamVjdH0KY2Q0X3JlZiA8LSByZWFkUkRTKCJjZDRfcmVmX2R1YWxfdHJhamVjdG9yeS5yZHMiKQoKY2F0KCJDZWxscyBsb2FkZWQ6IiwgbmNvbChjZDRfcmVmKSwgIlxuIikKCnN0b3BpZm5vdCgKICAicHJlZGljdGVkLmNlbGx0eXBlLmwyIG1pc3NpbmciICAgID0gInByZWRpY3RlZC5jZWxsdHlwZS5sMiIgJWluJSBjb2xuYW1lcyhjZDRfcmVmQG1ldGEuZGF0YSksCiAgIm1pbGVzdG9uZSBtaXNzaW5nIiAgICAgICAgICAgICAgICA9ICJtaWxlc3RvbmUiICVpbiUgY29sbmFtZXMoY2Q0X3JlZkBtZXRhLmRhdGEpLAogICJtc3RfcHNldWRvdGltZV9ub3JtIG1pc3NpbmciICAgICAgPSAibXN0X3BzZXVkb3RpbWVfbm9ybSIgJWluJSBjb2xuYW1lcyhjZDRfcmVmQG1ldGEuZGF0YSksCiAgIm1vbm9jbGUzX3BzZXVkb3RpbWVfbm9ybSBtaXNzaW5nIiA9ICJtb25vY2xlM19wc2V1ZG90aW1lX25vcm0iICVpbiUgY29sbmFtZXMoY2Q0X3JlZkBtZXRhLmRhdGEpLAogICJwY2EgcmVkdWN0aW9uIG1pc3NpbmciICAgICAgICAgICAgPSAicGNhIiAlaW4lIG5hbWVzKGNkNF9yZWZAcmVkdWN0aW9ucyksCiAgInVtYXAgcmVkdWN0aW9uIG1pc3NpbmciICAgICAgICAgICA9ICJ1bWFwIiAlaW4lIG5hbWVzKGNkNF9yZWZAcmVkdWN0aW9ucykKKQpjYXQoIuKchSBBbGwgc2xvdHMgdmVyaWZpZWRcbiIpCgpjYXQoIlxuQ2VsbCBzdGF0ZSBkaXN0cmlidXRpb246XG4iKQpwcmludCh0YWJsZShjZDRfcmVmJHByZWRpY3RlZC5jZWxsdHlwZS5sMikpCmNhdCgiXG5NaWxlc3RvbmUgZGlzdHJpYnV0aW9uOlxuIikKcHJpbnQodGFibGUoY2Q0X3JlZiRtaWxlc3RvbmUpKQpgYGAKCiMgVU1BUCBWYWxpZGF0aW9uCgpgYGB7ciB1bWFwLWNoZWNrLCBmaWcud2lkdGg9MTQsIGZpZy5oZWlnaHQ9NX0KcDEgPC0gRGltUGxvdChjZDRfcmVmLCBncm91cC5ieSA9ICJwcmVkaWN0ZWQuY2VsbHR5cGUubDIiLAogICAgICAgICAgICAgIHJlZHVjdGlvbiA9ICJ1bWFwIiwgbGFiZWwgPSBUUlVFLCByZXBlbCA9IFRSVUUsCiAgICAgICAgICAgICAgbGFiZWwuc2l6ZSA9IDMpICsgTm9MZWdlbmQoKSArCiAgICAgIGdndGl0bGUoIkF6aW11dGggbDIgc3RhdGVzIikKCnAyIDwtIERpbVBsb3QoY2Q0X3JlZiwgZ3JvdXAuYnkgPSAibWlsZXN0b25lIiwKICAgICAgICAgICAgICByZWR1Y3Rpb24gPSAidW1hcCIsIGxhYmVsID0gVFJVRSwgcmVwZWwgPSBUUlVFLAogICAgICAgICAgICAgIGxhYmVsLnNpemUgPSAzKSArIE5vTGVnZW5kKCkgKwogICAgICBnZ3RpdGxlKCJNaWxlc3RvbmVzIE0wMOKAk00wNyIpCgpwMyA8LSBEaW1QbG90KGNkNF9yZWYsIGdyb3VwLmJ5ID0gInNldXJhdF9jbHVzdGVycyIsCiAgICAgICAgICAgICAgcmVkdWN0aW9uID0gInVtYXAiLCBsYWJlbCA9IFRSVUUsIHJlcGVsID0gVFJVRSwKICAgICAgICAgICAgICBsYWJlbC5zaXplID0gMykgKyBOb0xlZ2VuZCgpICsKICAgICAgZ2d0aXRsZSgiU2V1cmF0IGNsdXN0ZXJzIikKCnByaW50KHAxIHwgcDIgfCBwMykKYGBgCgotLS0KCiMgQnVpbGQgQW5uRGF0YSBPYmplY3QKCmBgYHtyIGJ1aWxkLWFubmRhdGF9CiMg4pSA4pSAIEJ1aWxkIEFubkRhdGEg4oCUIFBDQSBhcyBYLCBubyBvYnNtIG5lZWRlZCDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIAKRGVmYXVsdEFzc2F5KGNkNF9yZWYpIDwtICJpbnRlZ3JhdGVkIgoKIyBQYXNzIFBDQSBlbWJlZGRpbmdzIGRpcmVjdGx5IGFzIFggKGV4YWN0bHkgbGlrZSBURiBtYXRyaXggd29ya2VkIGJlZm9yZSkKcGNhX2VtYmVkICA8LSBFbWJlZGRpbmdzKGNkNF9yZWYsICJwY2EiKVssIDE6MjBdCnVtYXBfZW1iZWQgPC0gRW1iZWRkaW5ncyhjZDRfcmVmLCAidW1hcCIpCgpvYnMgPC0gZGF0YS5mcmFtZSgKICByb3cubmFtZXMgICA9IGNvbG5hbWVzKGNkNF9yZWYpLAogIGNlbGx0eXBlX2wyID0gYXMuY2hhcmFjdGVyKGNkNF9yZWYkcHJlZGljdGVkLmNlbGx0eXBlLmwyKSwKICBtaWxlc3RvbmUgICA9IGFzLmNoYXJhY3RlcihjZDRfcmVmJG1pbGVzdG9uZSksCiAgbXN0X3B0ICAgICAgPSBjZDRfcmVmJG1zdF9wc2V1ZG90aW1lX25vcm0sCiAgbW9ub2NsZTNfcHQgPSBjZDRfcmVmJG1vbm9jbGUzX3BzZXVkb3RpbWVfbm9ybQopCgojIOKchSBQQ0EgZ29lcyBpbnRvIFggZGlyZWN0bHkg4oCUIHNhbWUgcGF0dGVybiB0aGF0IHdvcmtlZCBmb3IgVEYgbWF0cml4CmFkYXRhIDwtIGFkJEFubkRhdGEoWCA9IHBjYV9lbWJlZCwgb2JzID0gb2JzKQoKY2F0KHNwcmludGYoIuKchSBBbm5EYXRhOiAlZCBjZWxscyDDlyAlZCBQQ3NcbiIsCiAgICAgICAgICAgIHB5X3RvX3IoYWRhdGEkbl9vYnMpLCBweV90b19yKGFkYXRhJG5fdmFycykpKQoKIyDilIDilIAgTmVpZ2hib3JzIHVzaW5nIFggZGlyZWN0bHkgKG5vIG9ic20gbmVlZGVkKSDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIAKc2MkcHAkbmVpZ2hib3JzKAogIGFkYXRhLAogIHVzZV9yZXAgICAgID0gIlgiLCAgICAgIyDihpAgbWF0Y2hlcyB3b3JraW5nIHNjcmlwdCBwYXR0ZXJuCiAgbl9uZWlnaGJvcnMgPSAzMEwsCiAgbWV0cmljICAgICAgPSAiY29zaW5lIgopCmNhdCgi4pyFIEtOTiBncmFwaCBjb21wdXRlZFxuIikKCiMg4pSA4pSAIFBBR0Eg4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSACnNjJHRsJHBhZ2EoYWRhdGEsIGdyb3VwcyA9ICJjZWxsdHlwZV9sMiIpCmNhdCgi4pyFIFBBR0Egc3RhdGUtbGV2ZWwgY29tcGxldGVcbiIpCgpgYGAKCi0tLQoKIyBQQUdBIEFuYWx5c2lzCgojIyBQQUdBIGF0IFN0YXRlIExldmVsIChsMikKCmBgYHtyIHBhZ2Etc3RhdGV9CgpjYXQoIlJ1bm5pbmcgUEFHQSDigJQgQXppbXV0aCBsMiBzdGF0ZXMuLi5cbiIpCnNjJHRsJHBhZ2EoYWRhdGEsIGdyb3VwcyA9ICJjZWxsdHlwZV9sMiIpCgpweV9ydW5fc3RyaW5nKCIKaW1wb3J0IG51bXB5IGFzIG5wCnBhZ2FfY29ubiA9IHIuYWRhdGEudW5zWydwYWdhJ11bJ2Nvbm5lY3Rpdml0aWVzJ10udG9kZW5zZSgpCnN0YXRlX25hbWVzID0gci5hZGF0YS5vYnNbJ2NlbGx0eXBlX2wyJ10uY2F0LmNhdGVnb3JpZXMudmFsdWVzCm5wLnNhdmV0eHQoJ3BhZ2Ffc3RhdGVfY29ubi5jc3YnLCBwYWdhX2Nvbm4sIGRlbGltaXRlcj0nLCcsIGhlYWRlcj0nLCcuam9pbihzdGF0ZV9uYW1lcyksIGNvbW1lbnRzPScnKQpwcmludCgnU3RhdGUgY29ubmVjdGl2aXR5IHNhdmVkJykKIiwgbG9jYWwgPSBUUlVFKQoKIyBSZWFkIENTViAtIHNraXAgcm93IG5hbWVzIGNvbHVtbiwgYXNzaWduIG1hbnVhbGx5CnBhZ2Ffc3RhdGVfZGYgPC0gcmVhZC5jc3YoInBhZ2Ffc3RhdGVfY29ubi5jc3YiLCBoZWFkZXI9VFJVRSwgY2hlY2submFtZXM9RkFMU0UpCnN0YXRlX25hbWVzIDwtIGNvbG5hbWVzKHBhZ2Ffc3RhdGVfZGYpCnBhZ2Ffc3RhdGVfY29ubiA8LSBhcy5tYXRyaXgocGFnYV9zdGF0ZV9kZikKcm93bmFtZXMocGFnYV9zdGF0ZV9jb25uKSA8LSBzdGF0ZV9uYW1lcwpjb2xuYW1lcyhwYWdhX3N0YXRlX2Nvbm4pIDwtIHN0YXRlX25hbWVzCgpjYXQoIuKchSBTdGF0ZS1sZXZlbCBQQUdBIGNvbXBsZXRlXG5cbkNvbm5lY3Rpdml0eSBtYXRyaXg6XG4iKQpwcmludChyb3VuZChwYWdhX3N0YXRlX2Nvbm4sIDMpKQoKIyBDbGVhbiB1cCB0ZW1wIGZpbGUKZmlsZS5yZW1vdmUoInBhZ2Ffc3RhdGVfY29ubi5jc3YiKQoKYGBgCgojIyBQQUdBIGF0IE1pbGVzdG9uZSBMZXZlbCAoTTAw4oCTTTA3KQoKYGBge3IgcGFnYS1taWxlc3RvbmV9CmNhdCgiUnVubmluZyBQQUdBIOKAlCBtaWxlc3RvbmVzIE0wMC1NMDcuLi5cbiIpCnNjJHRsJHBhZ2EoYWRhdGEsIGdyb3VwcyA9ICJtaWxlc3RvbmUiKQoKcHlfcnVuX3N0cmluZygiCmltcG9ydCBudW1weSBhcyBucApwYWdhX2Nvbm4gPSByLmFkYXRhLnVuc1sncGFnYSddWydjb25uZWN0aXZpdGllcyddLnRvZGVuc2UoKQptc19uYW1lcyA9IHIuYWRhdGEub2JzWydtaWxlc3RvbmUnXS5jYXQuY2F0ZWdvcmllcy52YWx1ZXMKbnAuc2F2ZXR4dCgncGFnYV9tc19jb25uLmNzdicsIHBhZ2FfY29ubiwgZGVsaW1pdGVyPScsJywgaGVhZGVyPScsJy5qb2luKG1zX25hbWVzKSwgY29tbWVudHM9JycpCnByaW50KCdNaWxlc3RvbmUgY29ubmVjdGl2aXR5IHNhdmVkJykKIiwgbG9jYWwgPSBUUlVFKQoKIyBSZWFkIENTViAtIHNraXAgcm93IG5hbWVzIGNvbHVtbiwgYXNzaWduIG1hbnVhbGx5CnBhZ2FfbXNfZGYgPC0gcmVhZC5jc3YoInBhZ2FfbXNfY29ubi5jc3YiLCBoZWFkZXI9VFJVRSwgY2hlY2submFtZXM9RkFMU0UpCm1zX25hbWVzIDwtIGNvbG5hbWVzKHBhZ2FfbXNfZGYpCnBhZ2FfbXNfY29ubiA8LSBhcy5tYXRyaXgocGFnYV9tc19kZikKcm93bmFtZXMocGFnYV9tc19jb25uKSA8LSBtc19uYW1lcwpjb2xuYW1lcyhwYWdhX21zX2Nvbm4pIDwtIG1zX25hbWVzCgpjYXQoIuKchSBNaWxlc3RvbmUtbGV2ZWwgUEFHQSBjb21wbGV0ZVxuXG5Db25uZWN0aXZpdHkgbWF0cml4OlxuIikKcHJpbnQocm91bmQocGFnYV9tc19jb25uLCAzKSkKCiMgQ2xlYW4gdXAgdGVtcCBmaWxlCmZpbGUucmVtb3ZlKCJwYWdhX21zX2Nvbm4uY3N2IikKCgpgYGAKCiMjIFNhdmUgUEFHQSBGaWd1cmVzIChQeXRob24pLSBzdGF0ZS1sZXZlbCBQQUdBCmBgYHtyfQojIOKchSBFWEFDVCBTQ1Qgc2NyaXB0IHBhdHRlcm4Kc2MkdGwkcGFnYShhZGF0YSwgZ3JvdXBzID0gImNlbGx0eXBlX2wyIikKc2MkcGwkcGFnYShhZGF0YSwgdGhyZXNob2xkID0gMC4xNSwgc2hvdyA9IEZBTFNFLCBzYXZlID0gIl9zdGF0ZXMucG5nIikKCmZpbGUucmVuYW1lKCJmaWd1cmVzL3BhZ2Ffc3RhdGVzLnBuZyIsICJPdXRwdXRfRmlndXJlcy9QQUdBX3N0YXRlcy5wbmciKQpjYXQoIuKchSBTdGF0ZSBQQUdBIHNhdmVkXG4iKQoKCgpgYGAKIyMgU2F2ZSBQQUdBIEZpZ3VyZXMgKFB5dGhvbiktIE1pbGVzdG9uZXMgUEFHQQpgYGB7cn0KCnNjJHRsJHBhZ2EoYWRhdGEsIGdyb3VwcyA9ICJtaWxlc3RvbmUiKQpzYyRwbCRwYWdhKGFkYXRhLCB0aHJlc2hvbGQgPSAwLjEwLCBzaG93ID0gRkFMU0UsIHNhdmUgPSAiX21pbGVzdG9uZXMucG5nIikKZmlsZS5yZW5hbWUoImZpZ3VyZXMvcGFnYV9taWxlc3RvbmVzLnBuZyIsICJPdXRwdXRfRmlndXJlcy9QQUdBX21pbGVzdG9uZXMucG5nIikKYGBgCgpgYGB7ciBkaXNwbGF5LXBhZ2EtZ3JhcGhzfQoKa25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoIk91dHB1dF9GaWd1cmVzL1BBR0Ffc3RhdGVzLnBuZyIpCgoKCmBgYAoKYGBge3IgcGFnYS1taWxlc3RvbmVzLW5iLCBlY2hvPUZBTFNFfQoKa25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoIk91dHB1dF9GaWd1cmVzL1BBR0FfbWlsZXN0b25lcy5wbmciKQoKCmBgYAoKCiMjIFBBR0EgQ29ubmVjdGl2aXR5IEhlYXRtYXBzIChSKQoKYGBge3IgcGFnYS1oZWF0bWFwcy1DZWxsX3N0YXRlcywgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9Nn0KCiMg4pSA4pSAIFN0YXRlLWxldmVsIGhlYXRtYXAg4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSACmNvbm5fc3ViIDwtIHBhZ2Ffc3RhdGVfY29ublsKICBpbnRlcnNlY3QoU1RBVEVfT1JERVIsIHJvd25hbWVzKHBhZ2Ffc3RhdGVfY29ubikpLAogIGludGVyc2VjdChTVEFURV9PUkRFUiwgY29sbmFtZXMocGFnYV9zdGF0ZV9jb25uKSkKXQoKIyBTYXZlIHRvIGZpbGUKcGhlYXRtYXAoCiAgY29ubl9zdWIsCiAgY29sb3IgICAgICAgICAgID0gY29sb3JSYW1wUGFsZXR0ZShjKCJ3aGl0ZSIsIiMyOTgwYjkiLCIjYzAzOTJiIikpKDUwKSwKICBkaXNwbGF5X251bWJlcnMgPSByb3VuZChjb25uX3N1YiwgMiksCiAgbnVtYmVyX2NvbG9yICAgID0gImJsYWNrIiwKICBmb250c2l6ZV9udW1iZXIgPSAxMSwKICBtYWluICAgICAgICAgICAgPSAiUEFHQSBjb25uZWN0aXZpdHkg4oCUIENENCBUIGNlbGwgc3RhdGVzIiwKICBjbHVzdGVyX3Jvd3MgICAgPSBGQUxTRSwKICBjbHVzdGVyX2NvbHMgICAgPSBGQUxTRSwKICBmaWxlbmFtZSAgICAgICAgPSAiT3V0cHV0X0ZpZ3VyZXMvUEFHQV9zdGF0ZV9oZWF0bWFwLnBuZyIsCiAgd2lkdGggPSA3LCBoZWlnaHQgPSA2CikKCiMgRGlzcGxheSBpbiBub3RlYm9vayAg4oaQIEFERCBUSElTCnBoZWF0bWFwKAogIGNvbm5fc3ViLAogIGNvbG9yICAgICAgICAgICA9IGNvbG9yUmFtcFBhbGV0dGUoYygid2hpdGUiLCIjMjk4MGI5IiwiI2MwMzkyYiIpKSg1MCksCiAgZGlzcGxheV9udW1iZXJzID0gcm91bmQoY29ubl9zdWIsIDIpLAogIG51bWJlcl9jb2xvciAgICA9ICJibGFjayIsCiAgZm9udHNpemVfbnVtYmVyID0gMTEsCiAgbWFpbiAgICAgICAgICAgID0gIlBBR0EgY29ubmVjdGl2aXR5IOKAlCBDRDQgVCBjZWxsIHN0YXRlcyIsCiAgY2x1c3Rlcl9yb3dzICAgID0gRkFMU0UsCiAgY2x1c3Rlcl9jb2xzICAgID0gRkFMU0UKICAjIE5PIGZpbGVuYW1lID0gZGlzcGxheXMgaW4gbm90ZWJvb2sKKQpgYGAKCmBgYHtyIHBhZ2EtaGVhdG1hcHMtbWlsZXN0b25lcywgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9Nn0KIyDilIDilIAgTWlsZXN0b25lLWxldmVsIGhlYXRtYXAg4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSACm1zX29yZGVyIDwtIHBhc3RlMCgiTSIsIHNwcmludGYoIiUwMmQiLCAwOjcpKQptc19zdWIgICA8LSBwYWdhX21zX2Nvbm5bCiAgaW50ZXJzZWN0KG1zX29yZGVyLCByb3duYW1lcyhwYWdhX21zX2Nvbm4pKSwKICBpbnRlcnNlY3QobXNfb3JkZXIsIGNvbG5hbWVzKHBhZ2FfbXNfY29ubikpCl0KCiMgU2F2ZSB0byBmaWxlCnBoZWF0bWFwKAogIG1zX3N1YiwKICBjb2xvciAgICAgICAgICAgPSBjb2xvclJhbXBQYWxldHRlKGMoIndoaXRlIiwiIzI5ODBiOSIsIiNjMDM5MmIiKSkoNTApLAogIGRpc3BsYXlfbnVtYmVycyA9IHJvdW5kKG1zX3N1YiwgMiksCiAgbnVtYmVyX2NvbG9yICAgID0gImJsYWNrIiwKICBmb250c2l6ZV9udW1iZXIgPSAxMCwKICBtYWluICAgICAgICAgICAgPSAiUEFHQSBjb25uZWN0aXZpdHkg4oCUIE1pbGVzdG9uZXMgTTAw4oCTTTA3IiwKICBjbHVzdGVyX3Jvd3MgICAgPSBGQUxTRSwKICBjbHVzdGVyX2NvbHMgICAgPSBGQUxTRSwKICBmaWxlbmFtZSAgICAgICAgPSAiT3V0cHV0X0ZpZ3VyZXMvUEFHQV9taWxlc3RvbmVfaGVhdG1hcC5wbmciLAogIHdpZHRoID0gOCwgaGVpZ2h0ID0gNwopCgojIERpc3BsYXkgaW4gbm90ZWJvb2sgIOKGkCBBREQgVEhJUwpwaGVhdG1hcCgKICBtc19zdWIsCiAgY29sb3IgICAgICAgICAgID0gY29sb3JSYW1wUGFsZXR0ZShjKCJ3aGl0ZSIsIiMyOTgwYjkiLCIjYzAzOTJiIikpKDUwKSwKICBkaXNwbGF5X251bWJlcnMgPSByb3VuZChtc19zdWIsIDIpLAogIG51bWJlcl9jb2xvciAgICA9ICJibGFjayIsCiAgZm9udHNpemVfbnVtYmVyID0gMTAsCiAgbWFpbiAgICAgICAgICAgID0gIlBBR0EgY29ubmVjdGl2aXR5IOKAlCBNaWxlc3RvbmVzIE0wMOKAk00wNyIsCiAgY2x1c3Rlcl9yb3dzICAgID0gRkFMU0UsCiAgY2x1c3Rlcl9jb2xzICAgID0gRkFMU0UKICAjIE5PIGZpbGVuYW1lID0gZGlzcGxheXMgaW4gbm90ZWJvb2sKKQoKY2F0KCLinIUgSGVhdG1hcHMgc2F2ZWRcbiIpCgpgYGAKCi0tLQoKIyBNU1QgVG9wb2xvZ3kgVmFsaWRhdGlvbgoKIyMgRGVmaW5lIE1TVCBFZGdlcwoKYGBge3IgbXN0LWVkZ2VzfQojIE1TVCB0b3BvbG9neToKIyBNMDAoTmFpdmUpIOKGkiBNMDEoVENNLWVhcmx5KSDihpIgTTAyKFRDTS1sYXRlKSDihpIgTTAzKFRFTS1lYXJseSkg4oaSIE0wNChURU0tbGF0ZSkg4oaSIE0wNShUZW1yYSkKIyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICDihpMKIyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTTA2KFRyZWctbmFpdmUpIOKGkiBNMDcoVHJlZy10ZXJtaW5hbCkKCiMg4pSA4pSAIEZJWCAzOiBDb3JyZWN0IHN0YXRlIGVkZ2VzIChyZW1vdmVkIGR1cGxpY2F0ZSBDRDQgVENNIOKGkiBDRDQgVENNKSDilIDilIDilIDilIDilIAKbXN0X3N0YXRlX2VkZ2VzIDwtIGRhdGEuZnJhbWUoCiAgZnJvbSA9IGMoIkNENCBOYWl2ZSIsIkNENCBUQ00iLCJDRDQgVEVNIiwiQ0Q0IFRDTSIpLAogIHRvICAgPSBjKCJDRDQgVENNIiwgICJDRDQgVEVNIiwiQ0Q0IFRlbXJhL0NUTCIsIlRyZWciKSwKICBlZGdlX2xhYmVsID0gYygiTWFpbiBheGlzIiwiTWFpbiBheGlzIiwiVGVybWluYWwiLCJUcmVnIGJyYW5jaCIpCikKCm1zdF9taWxlc3RvbmVfZWRnZXMgPC0gZGF0YS5mcmFtZSgKICBmcm9tICAgID0gYygiTTAwIiwiTTAxIiwiTTAyIiwiTTAyIiwiTTAzIiwiTTA0IiwiTTA2IiksCiAgdG8gICAgICA9IGMoIk0wMSIsIk0wMiIsIk0wMyIsIk0wNiIsIk0wNCIsIk0wNSIsIk0wNyIpLAogIGJpb2xvZ3kgPSBjKCJOYWl2ZeKGklRDTS1lYXJseSIsIlRDTS1lYXJseeKGkmxhdGUiLCJUQ00tbGF0ZeKGklRFTS1lYXJseSIsCiAgICAgICAgICAgICAgIlRDTS1sYXRl4oaSVHJlZy1uYWl2ZSIsIlRFTS1lYXJseeKGkmxhdGUiLCJURU0tbGF0ZeKGklRlbXJhIiwKICAgICAgICAgICAgICAiVHJlZy1uYWl2ZeKGknRlcm1pbmFsIikKKQoKY2F0KCJNU1Qgc3RhdGUgZWRnZXM6XG4iKTsgcHJpbnQobXN0X3N0YXRlX2VkZ2VzKQpjYXQoIlxuTVNUIG1pbGVzdG9uZSBlZGdlczpcbiIpOyBwcmludChtc3RfbWlsZXN0b25lX2VkZ2VzKQpgYGAKCiMjIFN0YXRlLUxldmVsIFZhbGlkYXRpb24KCmBgYHtyIG1zdC12YWxpZGF0ZS1zdGF0ZSwgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9NX0KbXN0X3N0YXRlX2VkZ2VzJHBhZ2Ffc2NvcmUgPC0gbWFwcGx5KAogIGZ1bmN0aW9uKGYsIHQpIHsKICAgIGlmIChmICVpbiUgcm93bmFtZXMocGFnYV9zdGF0ZV9jb25uKSAmJiB0ICVpbiUgY29sbmFtZXMocGFnYV9zdGF0ZV9jb25uKSkKICAgICAgcm91bmQocGFnYV9zdGF0ZV9jb25uW2YsIHRdLCAzKSBlbHNlIE5BX3JlYWxfCiAgfSwKICBtc3Rfc3RhdGVfZWRnZXMkZnJvbSwgbXN0X3N0YXRlX2VkZ2VzJHRvCikKCm1zdF9zdGF0ZV9lZGdlcyRzdGF0dXMgPC0gY2FzZV93aGVuKAogIG1zdF9zdGF0ZV9lZGdlcyRwYWdhX3Njb3JlID4gMC41MCB+ICJTdHJvbmciLAogIG1zdF9zdGF0ZV9lZGdlcyRwYWdhX3Njb3JlID4gMC4zMCB+ICJNb2RlcmF0ZSIsCiAgbXN0X3N0YXRlX2VkZ2VzJHBhZ2Ffc2NvcmUgPiAwLjE1IH4gIldlYWsiLAogIFRSVUUgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfiAiTm90IGNvbmZpcm1lZCIKKQoKY2F0KCJcbk1TVCBzdGF0ZS1sZXZlbCB2YWxpZGF0aW9uOlxuIikKcHJpbnQobXN0X3N0YXRlX2VkZ2VzKQpjYXQoc3ByaW50ZigiXG4lZC8lZCBNU1Qgc3RhdGUgZWRnZXMgY29uZmlybWVkIChQQUdBID4gMC4zKVxuIiwKICAgICAgICAgICAgc3VtKG1zdF9zdGF0ZV9lZGdlcyRwYWdhX3Njb3JlID4gMC4zLCBuYS5ybT1UUlVFKSwKICAgICAgICAgICAgbnJvdyhtc3Rfc3RhdGVfZWRnZXMpKSkKCmdncGxvdChtc3Rfc3RhdGVfZWRnZXMsCiAgICAgICBhZXMoeCA9IHJlb3JkZXIocGFzdGUoZnJvbSwgIuKGkiIsIHRvKSwgcGFnYV9zY29yZSksCiAgICAgICAgICAgeSA9IHBhZ2Ffc2NvcmUsIGZpbGwgPSBzdGF0dXMpKSArCiAgZ2VvbV9jb2wod2lkdGggPSAwLjcpICsKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLjMsIGxpbmV0eXBlPSJkYXNoZWQiLAogICAgICAgICAgICAgY29sb3VyPSJncmV5NDAiLCBsaW5ld2lkdGg9MC44KSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMC41LCBsaW5ldHlwZT0iZG90dGVkIiwKICAgICAgICAgICAgIGNvbG91cj0iZ3JleTQwIiwgbGluZXdpZHRoPTAuOCkgKwogIGFubm90YXRlKCJ0ZXh0IiwgeD0wLjYsIHk9MC4zMiwgbGFiZWw9Ik1vZGVyYXRlICgwLjMpIiwKICAgICAgICAgICBzaXplPTMsIGhqdXN0PTAsIGNvbG91cj0iZ3JleTQwIikgKwogIGFubm90YXRlKCJ0ZXh0IiwgeD0wLjYsIHk9MC41MiwgbGFiZWw9IlN0cm9uZyAoMC41KSIsCiAgICAgICAgICAgc2l6ZT0zLCBoanVzdD0wLCBjb2xvdXI9ImdyZXk0MCIpICsKICBzY2FsZV9maWxsX21hbnVhbCgKICAgIHZhbHVlcyA9IGMoIlN0cm9uZyI9IiMyN2FlNjAiLCJNb2RlcmF0ZSI9IiMyOTgwYjkiLAogICAgICAgICAgICAgICAiV2VhayI9IiNmMzljMTIiLCJOb3QgY29uZmlybWVkIj0iI2MwMzkyYiIpLAogICAgbmFtZSAgID0gIlZhbGlkYXRpb24iCiAgKSArCiAgY29vcmRfZmxpcCgpICsKICB0aGVtZV9jbGFzc2ljKCkgKwogIGxhYnMoeD1OVUxMLCB5PSJQQUdBIGNvbm5lY3Rpdml0eSBzY29yZSIsCiAgICAgICB0aXRsZT0iQ3VzdG9tIE1TVCBzdGF0ZSBlZGdlcyDigJQgUEFHQSB2YWxpZGF0aW9uIiwKICAgICAgIHN1YnRpdGxlPSJEYXNoZWQgPSAwLjMgfCBEb3R0ZWQgPSAwLjUiKSArCiAgdGhlbWUocGxvdC50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xMywgZmFjZT0iYm9sZCIpKQoKZ2dzYXZlKCJPdXRwdXRfRmlndXJlcy9NU1RfUEFHQV9zdGF0ZV92YWxpZGF0aW9uLnBuZyIsCiAgICAgICB3aWR0aD05LCBoZWlnaHQ9NSwgZHBpPTE1MCkKYGBgCgojIyBNaWxlc3RvbmUtTGV2ZWwgVmFsaWRhdGlvbgoKYGBge3IgbXN0LXZhbGlkYXRlLW1pbGVzdG9uZSwgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTZ9Cm1zdF9taWxlc3RvbmVfZWRnZXMkcGFnYV9zY29yZSA8LSBtYXBwbHkoCiAgZnVuY3Rpb24oZiwgdCkgewogICAgaWYgKGYgJWluJSByb3duYW1lcyhwYWdhX21zX2Nvbm4pICYmIHQgJWluJSBjb2xuYW1lcyhwYWdhX21zX2Nvbm4pKQogICAgICByb3VuZChwYWdhX21zX2Nvbm5bZiwgdF0sIDMpIGVsc2UgTkFfcmVhbF8KICB9LAogIG1zdF9taWxlc3RvbmVfZWRnZXMkZnJvbSwgbXN0X21pbGVzdG9uZV9lZGdlcyR0bwopCgptc3RfbWlsZXN0b25lX2VkZ2VzJHN0YXR1cyA8LSBjYXNlX3doZW4oCiAgbXN0X21pbGVzdG9uZV9lZGdlcyRwYWdhX3Njb3JlID4gMC41MCB+ICJTdHJvbmciLAogIG1zdF9taWxlc3RvbmVfZWRnZXMkcGFnYV9zY29yZSA+IDAuMzAgfiAiTW9kZXJhdGUiLAogIG1zdF9taWxlc3RvbmVfZWRnZXMkcGFnYV9zY29yZSA+IDAuMTUgfiAiV2VhayIsCiAgVFJVRSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfiAiTm90IGNvbmZpcm1lZCIKKQoKY2F0KCJNU1QgbWlsZXN0b25lLWxldmVsIHZhbGlkYXRpb246XG4iKQpwcmludChtc3RfbWlsZXN0b25lX2VkZ2VzKQpjYXQoc3ByaW50ZigiXG4lZC8lZCBNU1QgbWlsZXN0b25lIGVkZ2VzIGNvbmZpcm1lZCAoUEFHQSA+IDAuMylcbiIsCiAgICAgICAgICAgIHN1bShtc3RfbWlsZXN0b25lX2VkZ2VzJHBhZ2Ffc2NvcmUgPiAwLjMsIG5hLnJtPVRSVUUpLAogICAgICAgICAgICBucm93KG1zdF9taWxlc3RvbmVfZWRnZXMpKSkKCmdncGxvdChtc3RfbWlsZXN0b25lX2VkZ2VzLAogICAgICAgYWVzKHggPSByZW9yZGVyKHBhc3RlKGZyb20sICLihpIiLCB0byksIHBhZ2Ffc2NvcmUpLAogICAgICAgICAgIHkgPSBwYWdhX3Njb3JlLCBmaWxsID0gc3RhdHVzKSkgKwogIGdlb21fY29sKHdpZHRoID0gMC43KSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMC4zLCBsaW5ldHlwZT0iZGFzaGVkIiwKICAgICAgICAgICAgIGNvbG91cj0iZ3JleTQwIiwgbGluZXdpZHRoPTAuOCkgKwogIGdlb21fdGV4dChhZXMobGFiZWw9YmlvbG9neSksIGhqdXN0PS0wLjEsIHNpemU9MykgKwogIHNjYWxlX2ZpbGxfbWFudWFsKAogICAgdmFsdWVzID0gYygiU3Ryb25nIj0iIzI3YWU2MCIsIk1vZGVyYXRlIj0iIzI5ODBiOSIsCiAgICAgICAgICAgICAgICJXZWFrIj0iI2YzOWMxMiIsIk5vdCBjb25maXJtZWQiPSIjYzAzOTJiIiksCiAgICBuYW1lICAgPSAiVmFsaWRhdGlvbiIKICApICsKICBjb29yZF9mbGlwKCkgKwogIGV4cGFuZF9saW1pdHMoeT0xLjIpICsKICB0aGVtZV9jbGFzc2ljKCkgKwogIGxhYnMoeD1OVUxMLCB5PSJQQUdBIGNvbm5lY3Rpdml0eSBzY29yZSIsCiAgICAgICB0aXRsZT0iTVNUIG1pbGVzdG9uZSBlZGdlcyAoTTAw4oCTTTA3KSDigJQgUEFHQSB2YWxpZGF0aW9uIikgKwogIHRoZW1lKHBsb3QudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTMsIGZhY2U9ImJvbGQiKSkKCmdnc2F2ZSgiT3V0cHV0X0ZpZ3VyZXMvTVNUX1BBR0FfbWlsZXN0b25lX3ZhbGlkYXRpb24ucG5nIiwKICAgICAgIHdpZHRoPTEwLCBoZWlnaHQ9NiwgZHBpPTE1MCkKYGBgCgojIyBGaW5hbCBTdW1tYXJ5CgpgYGB7ciBzdW1tYXJ5fQpjYXQoIuKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkFxuIikKY2F0KCJQQUdBIFZBTElEQVRJT04gU1VNTUFSWVxuIikKY2F0KCLilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZDilZBcbiIpCmNhdChzcHJpbnRmKCJTdGF0ZS1sZXZlbDogICAgICVkLyVkIGVkZ2VzIGNvbmZpcm1lZCAoPiAwLjMpXG4iLAogICAgICAgICAgICBzdW0obXN0X3N0YXRlX2VkZ2VzJHBhZ2Ffc2NvcmUgPiAwLjMsIG5hLnJtPVRSVUUpLAogICAgICAgICAgICBucm93KG1zdF9zdGF0ZV9lZGdlcykpKQpjYXQoc3ByaW50ZigiTWlsZXN0b25lLWxldmVsOiAlZC8lZCBlZGdlcyBjb25maXJtZWQgKD4gMC4zKVxuIiwKICAgICAgICAgICAgc3VtKG1zdF9taWxlc3RvbmVfZWRnZXMkcGFnYV9zY29yZSA+IDAuMywgbmEucm09VFJVRSksCiAgICAgICAgICAgIG5yb3cobXN0X21pbGVzdG9uZV9lZGdlcykpKQpjYXQoIlxuRmlndXJlcyBzYXZlZDpcbiIpCmZvciAoZiBpbiBsaXN0LmZpbGVzKCJPdXRwdXRfRmlndXJlcyIsIHBhdHRlcm49IlBBR0F8TVNUIikpCiAgY2F0KHNwcmludGYoIiAg4pyFICVzXG4iLCBmKSkKYGBgCgoKCmBgYHtyIH0KIyDilIDilIAgU2F2ZSBQQUdBIHJlc3VsdHMg4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSACgoKIyBBZGQgUEFHQSBjb25uZWN0aXZpdHkgc2NvcmVzIGJhY2sgdG8gY2Q0X3JlZiBtZXRhZGF0YSAob3B0aW9uYWwpCiMgU28gZXZlcnkgY2VsbCBrbm93cyBpdHMgc3RhdGUncyBQQUdBIGNvbm5lY3Rpdml0eQpzYXZlUkRTKGNkNF9yZWYsICJjZDRfcmVmX2R1YWxfdHJhamVjdG9yeV93aXRoX1BBR0EucmRzIikgICMgUmUtc2F2ZSB3aXRoIGFueSB1cGRhdGVzCmNhdCgi4pyFIGNkNF9yZWYgdXBkYXRlZCDihpIgY2Q0X3JlZl9kdWFsX3RyYWplY3RvcnkucmRzXG4iKQoKIyDilIDilIAgUXVpY2sgcmVsb2FkIGNoZWNrIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgApjYXQoIlxuVG8gcmVsb2FkIFBBR0EgcmVzdWx0cyBsYXRlcjpcbiIpCmNhdCgiICBwYWdhX3Jlc3VsdHMgPC0gcmVhZFJEUygncGFnYV92YWxpZGF0aW9uX3Jlc3VsdHMucmRzJylcbiIpCmNhdCgiICBwYWdhX3N0YXRlX2Nvbm4gPC0gcGFnYV9yZXN1bHRzJHN0YXRlX2Nvbm5lY3Rpdml0eVxuIikKY2F0KCIgIHBhZ2FfbXNfY29ubiAgICA8LSBwYWdhX3Jlc3VsdHMkbWlsZXN0b25lX2Nvbm5lY3Rpdml0eVxuIikKY2F0KCIgIGFkYXRhIDwtIGFubmRhdGEkcmVhZF9oNWFkKCdjZDRfcmVmX1BBR0FfdmFsaWRhdGVkLmg1YWQnKVxuIikKCiMg4pSA4pSAIEZpbmFsIGludmVudG9yeSDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIAKY2F0KCJcbuKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkFxuIikKY2F0KCJTQVZFRCBPQkpFQ1RTXG4iKQpjYXQoIuKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkFxuIikKY2F0KCIgIGNkNF9yZWZfUEFHQV92YWxpZGF0ZWQuaDVhZCAgICAg4oaQIEFubkRhdGEgd2l0aCBQQUdBXG4iKQpjYXQoIiAgcGFnYV92YWxpZGF0aW9uX3Jlc3VsdHMucmRzICAgICDihpAgUiBtYXRyaWNlcyArIHZhbGlkYXRpb25cbiIpCmNhdCgiICBjZDRfcmVmX2R1YWxfdHJhamVjdG9yeS5yZHMgICAgIOKGkCBTZXVyYXQgb2JqZWN0ICh1bmNoYW5nZWQpXG4iKQpjYXQoIlxuT3V0cHV0X0ZpZ3VyZXMvOlxuIikKZm9yIChmIGluIGxpc3QuZmlsZXMoIk91dHB1dF9GaWd1cmVzIiwgcGF0dGVybj0iUEFHQXxNU1QiKSkKICBjYXQoc3ByaW50ZigiICDinIUgJXNcbiIsIGYpKQoKYGBgCg==