1 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"
)

2 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 

3 UMAP Validation

p1 <- DimPlot(cd4_ref, group.by = "predicted.celltype.l2",
              reduction = "umap", label = TRUE, repel = TRUE,
              label.size = 3) + NoLegend() +
      ggtitle("Azimuth l2 states")

p2 <- DimPlot(cd4_ref, group.by = "milestone",
              reduction = "umap", label = TRUE, repel = TRUE,
              label.size = 3) + NoLegend() +
      ggtitle("Milestones M00–M07")

p3 <- DimPlot(cd4_ref, group.by = "seurat_clusters",
              reduction = "umap", label = TRUE, repel = TRUE,
              label.size = 3) + NoLegend() +
      ggtitle("Seurat clusters")

print(p1 | p2 | p3)


4 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

5 PAGA Analysis

5.1 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

5.2 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

5.3 Save PAGA Figures (Python)- state-level PAGA

# ✅ EXACT SCT script pattern
sc$tl$paga(adata, groups = "celltype_l2")
sc$pl$paga(adata, threshold = 0.15, show = FALSE, save = "_states.png")

file.rename("figures/paga_states.png", "Output_Figures/PAGA_states.png")
cat("✅ State PAGA saved\n")

5.4 Save PAGA Figures (Python)- Milestones PAGA


sc$tl$paga(adata, groups = "milestone")
sc$pl$paga(adata, threshold = 0.10, show = FALSE, save = "_milestones.png")
file.rename("figures/paga_milestones.png", "Output_Figures/PAGA_milestones.png")

knitr::include_graphics("Output_Figures/PAGA_states.png")

NA
NA
NA

5.5 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

6 MST Topology Validation

6.1 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

6.2 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)

6.3 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)

6.4 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==