1 Overview

What this script does — nothing is re-computed from scratch.

  • Loads the already-saved reference trajectory objects from Step 1
  • Loads Sézary cell lines L1–L7
  • Runs per-cell-line SCT normalisation before FindTransferAnchors → each line gets its own SCT model → more biologically meaningful anchors
  • Projects each line individually onto the frozen reference UMAP
  • Transfers both MST pseudotime and Monocle3 pseudotime from reference
  • Transfers milestone labels (M00–M06) so positions can be compared with the 7-milestone structure confirmed in Step 1
  • Produces visualisations identical in style to previous results so you can directly compare this run (7 milestones) with the previous run

Reference milestone structure (already confirmed in Step 1):

M00 = CD4 Naive      (2037)
M01 = CD4 TCM early  (4717)
M02 = CD4 TCM late   (4350)
M03 = CD4 TEM        (145)
M04 = CD4 Temra/CTL  (10)
M05 = Treg resting   (146)
M06 = Treg effector  (61)

2 Setup

suppressPackageStartupMessages({
  library(Seurat)
  library(SeuratWrappers)
  library(monocle3)
  library(igraph)
  library(ggplot2)
  library(ggrepel)
  library(dplyr)
  library(tidyr)
  library(patchwork)
  library(viridis)
  library(scales)
  library(RColorBrewer)
  library(knitr)
  library(kableExtra)
})

# ── Must match Step 1 exactly ──────────────────────────────────────────────
STATE_COLORS <- c(
  "CD4 Naive"     = "#4472C4",
  "CD4 TCM"       = "#70AD47",
  "CD4 TEM"       = "#ED7D31",
  "CD4 Temra/CTL" = "#C00000",
  "Treg"          = "#7030A0"
)

CELL_LINE_COLORS <- setNames(
  colorRampPalette(brewer.pal(8,"Dark2"))(7),
  paste0("L", 1:7)
)

MILESTONE_COLORS <- setNames(
  c("#4472C4",            # M00 Naive
    "#70AD47","#A9D18E",  # M01/M02 TCM early/late
    "#ED7D31",            # M03 TEM
    "#C00000",            # M04 Temra
    "#7030A0","#B4A0D4"), # M05/M06 Treg
  sprintf("M%02d", 0:6)
)

MILESTONE_LABELS <- c(
  M00 = "M00 Naive",
  M01 = "M01 TCM(early)",
  M02 = "M02 TCM(late)",
  M03 = "M03 TEM",
  M04 = "M04 Temra",
  M05 = "M05 Treg(rest)",
  M06 = "M06 Treg(eff)"
)

STATE_COL  <- "predicted.celltype.l2"
UMAP_NAME  <- "umap"
CELL_LINES <- paste0("L", 1:7)

MILESTONE_ORDER <- sprintf("M%02d", 0:6)

cat("✓ Setup complete\n")
✓ Setup complete

3 Load Reference Objects (Step 1 outputs — not re-run)

# ── Load the reference Seurat object with frozen UMAP model ───────────────
cd4_ref <- readRDS("../../1-Custom_MST/Objects/cd4_ref_dual_trajectory.rds")

# ── Load MST graph and centroids ──────────────────────────────────────────
mst_graph           <- readRDS("../../1-Custom_MST/Objects/mst_graph.rds")
milestone_centroids <- readRDS("../../1-Custom_MST/Objects/milestone_centroids.rds")

# ── Validate milestone structure ──────────────────────────────────────────
expected_ms <- sprintf("M%02d", 0:6)
actual_ms   <- sort(unique(na.omit(cd4_ref@meta.data$milestone)))
stopifnot(
  "Milestone mismatch — re-run Step 1 first" = identical(actual_ms, expected_ms),
  "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),
  "UMAP model missing from cd4_ref"          = !is.null(cd4_ref@reductions[[UMAP_NAME]]@misc$model)
)

cat("✅ Reference objects loaded and validated\n")
✅ Reference objects loaded and validated
cat(sprintf("   %d reference cells | %d milestones\n", ncol(cd4_ref), length(actual_ms)))
   11466 reference cells | 7 milestones
cat(sprintf("   MST: %d nodes, %d edges\n", vcount(mst_graph), ecount(mst_graph)))
   MST: 7 nodes, 6 edges
cat("\nMilestone structure from Step 1:\n")

Milestone structure from Step 1:
print(milestone_centroids[, c("milestone","state","n_cells")])

# ── Build reference UMAP data frame for background plotting ───────────────
ref_umap_df           <- as.data.frame(Embeddings(cd4_ref, UMAP_NAME))
colnames(ref_umap_df) <- c("UMAP_1","UMAP_2")
ref_umap_df$state     <- cd4_ref@meta.data[[STATE_COL]]
ref_umap_df$milestone <- cd4_ref@meta.data$milestone
ref_umap_df$mst_pt    <- cd4_ref@meta.data$mst_pseudotime_norm
ref_umap_df$m3_pt     <- cd4_ref@meta.data$monocle3_pseudotime_norm

# ── MST edge data frame for overlay ───────────────────────────────────────
mst_edges   <- as_edgelist(mst_graph)
mst_edge_df <- do.call(rbind, lapply(seq_len(nrow(mst_edges)), function(i) {
  m1 <- mst_edges[i,1]; m2 <- mst_edges[i,2]
  r1 <- milestone_centroids[milestone_centroids$milestone == m1, ]
  r2 <- milestone_centroids[milestone_centroids$milestone == m2, ]
  data.frame(x1=r1$UMAP_1, y1=r1$UMAP_2, x2=r2$UMAP_1, y2=r2$UMAP_2)
}))

4 Load Sézary Cell Lines

all_obj <- readRDS("/home/nabbasi/apollo_home/1-Seurat_RDS_OBJECT_FINAL/All_samples_Merged_with_Renamed_Clusters_Cell_state-03-12-2025.rds.rds")

# ── cell_line column confirmed present (40,695 cells, L1–L7) ─────────────
all_obj$cell_line <- as.character(all_obj$cell_line)
sezary_obj <- subset(all_obj, subset = cell_line %in% CELL_LINES)
rm(all_obj); gc()
             used    (Mb) gc trigger    (Mb)   max used    (Mb)
Ncells    9046606   483.2   16164308   863.3   16164308   863.3
Vcells 1394581656 10639.9 3064212682 23378.1 2635168938 20104.8
cat(sprintf("Sézary cells loaded: %d total\n", ncol(sezary_obj)))
Sézary cells loaded: 40695 total
cat("Per cell line:\n")
Per cell line:
print(table(sezary_obj$cell_line))

  L1   L2   L3   L4   L5   L6   L7 
5825 5935 6428 6006 6022 5148 5331 
DefaultAssay(sezary_obj) <- "RNA"

# Shared genes with reference (confirmed ~36,601)
shared_genes <- intersect(rownames(sezary_obj), rownames(cd4_ref))
cat(sprintf("\nShared genes (query ∩ reference): %d\n", length(shared_genes)))

Shared genes (query ∩ reference): 36601
stopifnot("Fewer than 2000 shared genes — check objects" = length(shared_genes) >= 2000)

# ── Check percent.mt exists — needed for SCTransform vars.to.regress ──────
# If percent.mt is absent, SCT will run without regressing it (see per-line chunk)
HAS_PCT_MT <- "percent.mt" %in% colnames(sezary_obj@meta.data)
cat(sprintf("\npercent.mt in metadata: %s\n", HAS_PCT_MT))

percent.mt in metadata: TRUE

5 Per-Cell-Line SCT + Projection

Rationale for per-cell-line SCT:
Each Sézary cell line has its own library size distribution and technical variation. Running SCTransform on each line independently fits a line-specific regression model, which produces better-calibrated residuals for anchor finding. This gives FindTransferAnchors more reliable anchors compared to normalising all lines together, at the cost of one SCT run per line (~30–60 s each).

# ── Step 7: Summary ───────────────────────────────────────────────────────
cat("\nPredicted state distribution:\n")

Predicted state distribution:
print(table(sezary_obj$predicted.state))

CD4 TCM CD4 TEM    Treg 
  40628      66       1 
cat("\nPredicted milestone distribution:\n")

Predicted milestone distribution:
print(table(sezary_obj$predicted.milestone))

  M01   M02   M03   M05 
 1713 38893    86     3 
cat("\nMST pseudotime range:", round(range(sezary_obj$mst_pseudotime_norm, na.rm=TRUE), 1), "\n")

MST pseudotime range: 8.3 99.6 
cat("\n✅ Projection complete\n")

✅ Projection complete
cat(sprintf("✅ Anchors found: %d anchor pairs\n", nrow(anchors@anchors)))
✅ Anchors found: 8661 anchor pairs
cat(sprintf("Cells projected: %d\n", ncol(sezary_obj)))
Cells projected: 40695
cat(sprintf("Mean prediction score: %.3f (>0.5 = reliable, >0.7 = confident)\n",
            mean(sezary_obj$predicted.state.score, na.rm=TRUE)))
Mean prediction score: 0.984 (>0.5 = reliable, >0.7 = confident)
cat(sprintf("Cells with score >0.5: %d (%.1f%%)\n",
            sum(sezary_obj$predicted.state.score > 0.5, na.rm=TRUE),
            100*mean(sezary_obj$predicted.state.score > 0.5, na.rm=TRUE)))
Cells with score >0.5: 40643 (99.9%)
cat(sprintf("Cells with score >0.7: %d (%.1f%%)\n",
            sum(sezary_obj$predicted.state.score > 0.7, na.rm=TRUE),
            100*mean(sezary_obj$predicted.state.score > 0.7, na.rm=TRUE)))
Cells with score >0.7: 40207 (98.8%)
cat(sprintf("Cells with score >0.5: %d (%.1f%%)\n",
            sum(sezary_obj$predicted.milestone.score > 0.5, na.rm=TRUE),
            100*mean(sezary_obj$predicted.milestone.score > 0.5, na.rm=TRUE)))
Cells with score >0.5: 40388 (99.2%)
cat(sprintf("Cells with score >0.7: %d (%.1f%%)\n",
            sum(sezary_obj$predicted.milestone.score > 0.7, na.rm=TRUE),
            100*mean(sezary_obj$predicted.milestone.score > 0.7, na.rm=TRUE)))
Cells with score >0.7: 34183 (84.0%)
cat(sprintf("Total cells: %d\n", nrow(query_umap_df)))
Total cells: 40695
cat("Per cell line:\n")
Per cell line:
print(table(query_umap_df$cell_line))

  L1   L2   L3   L4   L5   L6   L7 
5825 5935 6428 6006 6022 5148 5331 
cat("\nMilestone distribution:\n")

Milestone distribution:
print(table(query_umap_df$predicted_milestone, useNA="ifany"))

  M01   M02   M03   M05 
 1713 38893    86     3 

transfer_summary <- query_umap_df %>%
  group_by(cell_line) %>%
  summarise(
    n_cells       = n(),
    mst_pt_mean   = round(mean(mst_pt, na.rm=TRUE), 1),
    mst_pt_med    = round(median(mst_pt, na.rm=TRUE), 1),
    m3_pt_mean    = round(mean(m3_pt, na.rm=TRUE), 1),
    m3_pt_med     = round(median(m3_pt, na.rm=TRUE), 1),
    top_milestone = names(sort(table(predicted_milestone), decreasing=TRUE))[1],
    .groups       = "drop"
  )

ms_wide <- query_umap_df %>%
  filter(!is.na(predicted_milestone)) %>%
  count(cell_line, predicted_milestone) %>%
  group_by(cell_line) %>%
  mutate(pct = round(100 * n / sum(n), 1)) %>%
  select(cell_line, predicted_milestone, pct) %>%
  pivot_wider(names_from=predicted_milestone, values_from=pct, values_fill=0)

for (m in MILESTONE_ORDER) {
  if (!m %in% colnames(ms_wide)) ms_wide[[m]] <- 0
}
ms_wide      <- ms_wide[, c("cell_line", MILESTONE_ORDER)]
full_summary <- left_join(transfer_summary, ms_wide, by="cell_line")

kable(full_summary,
      caption = "Sézary projection summary — pseudotime and milestone composition",
      digits  = 1) %>%
  kable_styling(bootstrap_options=c("striped","hover","condensed"),
                full_width=FALSE) %>%
  add_header_above(c(" "=ncol(transfer_summary),
                     "% cells per milestone"=length(MILESTONE_ORDER)))
Sézary projection summary — pseudotime and milestone composition
% cells per milestone
cell_line n_cells mst_pt_mean mst_pt_med m3_pt_mean m3_pt_med top_milestone M00 M01 M02 M03 M04 M05 M06
L1 5825 57.5 55.7 68.6 66.1 M02 0 6.4 92.1 1.5 0 0.1 0
L2 5935 52.7 54.5 57.2 58.9 M02 0 4.7 95.3 0.0 0 0.0 0
L3 6428 52.1 53.5 61.0 60.7 M02 0 6.9 93.1 0.0 0 0.0 0
L4 6006 56.7 56.7 63.7 60.6 M02 0 1.9 98.1 0.0 0 0.0 0
L5 6022 51.8 52.8 57.4 58.4 M02 0 5.5 94.5 0.0 0 0.0 0
L6 5148 58.3 57.1 62.8 60.7 M02 0 2.1 97.9 0.0 0 0.0 0
L7 5331 55.5 55.8 62.8 60.0 M02 0 1.3 98.7 0.0 0 0.0 0

6 Visualisations

6.1 Reference UMAP — milestones

p_ref_ms <- ggplot() +
  geom_point(data = ref_umap_df[sample(nrow(ref_umap_df)),],
             aes(x=UMAP_1, y=UMAP_2, colour=state), size=.4, alpha=.5) +
  geom_segment(data=mst_edge_df,
               aes(x=x1,y=y1,xend=x2,yend=y2),
               colour="black", linewidth=1, alpha=.85, lineend="round") +
  geom_point(data=milestone_centroids,
             aes(x=UMAP_1,y=UMAP_2,fill=state),
             shape=21, size=8, colour="white", stroke=2) +
  geom_text_repel(data=milestone_centroids,
                  aes(x=UMAP_1,y=UMAP_2,
                      label=paste0(milestone,"\n(",MILESTONE_LABELS[milestone],")")),
                  size=2.8, fontface="bold",
                  bg.color="grey80", bg.r=.15, max.overlaps=20) +
  scale_colour_manual(values=STATE_COLORS, name="State") +
  scale_fill_manual(values=STATE_COLORS, guide="none") +
  theme_classic() +
  labs(x="UMAP-1", y="UMAP-2",
       title="Reference CD4 T cells — 7-milestone structure",
       subtitle="M00=Naive | M01/M02=TCM | M03=TEM | M04=Temra | M05/M06=Treg") +
  theme(plot.title=element_text(size=13,face="bold"))

print(p_ref_ms)

p_ref_ms <- ggplot() +
  geom_point(data = ref_umap_df[sample(nrow(ref_umap_df)),],
             aes(x=UMAP_1, y=UMAP_2, colour=state), size=.4, alpha=.5) +
  geom_segment(data=mst_edge_df,
               aes(x=x1,y=y1,xend=x2,yend=y2),
               colour="black", linewidth=1, alpha=.85, lineend="round") +
  geom_point(data=milestone_centroids,
             aes(x=UMAP_1,y=UMAP_2,fill=state),
             shape=21, size=8, colour="white", stroke=2) +
  geom_text_repel(data=milestone_centroids,
                  aes(x=UMAP_1,y=UMAP_2,
                      label=paste0(milestone,"\n(",MILESTONE_LABELS[milestone],")")),
                  size=2.8, fontface="bold",
                  bg.color="grey80", bg.r=.15, max.overlaps=20) +
  scale_colour_manual(values=STATE_COLORS, name="State") +
  scale_fill_manual(values=STATE_COLORS, guide="none") +
  theme_classic() +
  labs(x="UMAP-1", y="UMAP-2",
       title="Reference CD4 T cells — 7-milestone structure",
       subtitle="M00=Naive | M01/M02=TCM | M03=TEM | M04=Temra | M05/M06=Treg") +
  theme(plot.title=element_text(size=13,face="bold"))

print(p_ref_ms)

6.2 All Lines — Position on Reference UMAP

p_all_lines <- ggplot() +
  # Reference background (grey)
  geom_point(data=ref_umap_df,
             aes(x=UMAP_1,y=UMAP_2), colour="grey68", size=.25, alpha=.6) +
  # MST overlay
  geom_segment(data=mst_edge_df,
               aes(x=x1,y=y1,xend=x2,yend=y2),
               colour="grey30", linewidth=.7, alpha=.7, lineend="round") +
  # Query cells coloured by cell line
  geom_point(data=query_umap_df[sample(nrow(query_umap_df)),],
             aes(x=UMAP_1,y=UMAP_2,colour=cell_line), size=1.2, alpha=.75) +
  scale_colour_manual(values=CELL_LINE_COLORS, name="Cell line") +
  # Milestone labels
  geom_text_repel(data=milestone_centroids,
                  aes(x=UMAP_1,y=UMAP_2,label=milestone),
                  size=3, fontface="bold", colour="grey20",
                  bg.color="white", bg.r=.12, max.overlaps=15) +
  theme_classic() +
  labs(x="UMAP-1",y="UMAP-2",
       title="Sézary L1–L7 projected onto reference UMAP",
       subtitle="Grey = reference | Coloured = query cell lines") +
  theme(plot.title=element_text(size=13,face="bold")) +
  facet_wrap(~cell_line, ncol=4)

print(p_all_lines)

6.3 All Lines — MST Pseudotime on Reference UMAP

6.4 Per-Line MST Pseudotime Violin

# Milestone median lines for reference
ms_medians <- cd4_ref@meta.data %>%
  group_by(milestone) %>%
  summarise(med = median(mst_pseudotime_norm, na.rm=TRUE), .groups="drop") %>%
  filter(milestone %in% c("M00","M01","M02","M03","M04","M05","M06"))

violin_df <- data.frame(
  cell_line = sezary_obj$cell_line,
  mst_pt    = sezary_obj$mst_pseudotime_norm,
  m3_pt     = sezary_obj$monocle3_pseudotime_norm,
  milestone = sezary_obj$predicted.milestone
) %>% filter(!is.na(mst_pt))

p_violin_mst <- ggplot(violin_df, aes(x=cell_line, y=mst_pt, fill=cell_line)) +
  geom_violin(scale="width", trim=FALSE, alpha=.85) +
  geom_boxplot(width=.08, fill="white", outlier.size=.3, outlier.alpha=.4) +
  # Reference milestone median lines
  geom_hline(data=ms_medians,
             aes(yintercept=med, linetype=milestone), colour="grey40", linewidth=.5) +
  geom_text(data=ms_medians,
          aes(x=7.6, y=med, label=milestone),
          size=2.8, hjust=0, colour="grey25",
          inherit.aes=FALSE) +
  scale_fill_manual(values=CELL_LINE_COLORS, guide="none") +
  scale_linetype_manual(values=rep("dashed",7), guide="none") +
  coord_cartesian(xlim=c(0.5,8.5)) +
  theme_classic() +
  labs(x="Cell line", y="MST pseudotime (0–100)",
       title="MST pseudotime per Sézary cell line",
       subtitle="Dashed lines = reference milestone medians (M00–M06)") +
  theme(plot.title=element_text(size=13,face="bold"))

print(p_violin_mst)

6.5 Per-Line Monocle3 Pseudotime Violin

# Reference milestone medians — Monocle3
ms_medians_m3 <- cd4_ref@meta.data %>%
  group_by(milestone) %>%
  summarise(med = median(monocle3_pseudotime_norm, na.rm=TRUE), .groups="drop")

p_violin_m3 <- ggplot(violin_df, aes(x=cell_line, y=m3_pt, fill=cell_line)) +
  geom_violin(scale="width", trim=FALSE, alpha=.85) +
  geom_boxplot(width=.08, fill="white", outlier.size=.3, outlier.alpha=.4) +
  geom_hline(data=ms_medians_m3,
             aes(yintercept=med, linetype=milestone), colour="grey40", linewidth=.5) +
  geom_text(data=ms_medians,
          aes(x=7.6, y=med, label=milestone),
          size=2.8, hjust=0, colour="grey25",
          inherit.aes=FALSE) +
  scale_fill_manual(values=CELL_LINE_COLORS, guide="none") +
  scale_linetype_manual(values=rep("dashed",7), guide="none") +
  coord_cartesian(xlim=c(0.5,8.5)) +
  theme_classic() +
  labs(x="Cell line", y="Monocle3 pseudotime (0–100)",
       title="Monocle3 pseudotime per Sézary cell line",
       subtitle="Dashed lines = reference milestone medians (M00–M06)") +
  theme(plot.title=element_text(size=13,face="bold"))

print(p_violin_m3)

6.6 Milestone Composition per Cell Line

ms_comp <- violin_df %>%
  count(cell_line, milestone) %>%
  group_by(cell_line) %>%
  mutate(pct = 100 * n / sum(n)) %>%
  ungroup() %>%
  filter(!is.na(milestone)) %>%
  mutate(milestone = factor(milestone, levels=sprintf("M%02d",0:6)))

p_ms_bar <- ggplot(ms_comp, aes(x=cell_line, y=pct, fill=milestone)) +
  geom_col(width=.75) +
  scale_fill_manual(values=MILESTONE_COLORS,
                    labels=MILESTONE_LABELS, name="Milestone") +
  theme_classic() +
  labs(x="Cell line", y="% cells",
       title="Milestone composition per Sézary cell line",
       subtitle="Based on transferred milestone labels from reference") +
  theme(plot.title=element_text(size=13,face="bold"),
        legend.text=element_text(size=9))

print(p_ms_bar)


# Milestone labels with biological annotation
MILESTONE_LABELS_FULL <- c(
  "M00" = "M00\nNaive",
  "M01" = "M01\nTCM early",
  "M02" = "M02\nTCM late\n(branch)",
  "M03" = "M03\nTEM",
  "M04" = "M04\nTemra",
  "M05" = "M05\nTreg(rest)",
  "M06" = "M06\nTreg(eff)"
)

# Compute milestone distribution per cell line
ms_per_line <- query_umap_df %>%
  filter(!is.na(predicted_milestone)) %>%
  count(cell_line, predicted_milestone) %>%
  group_by(cell_line) %>%
  mutate(pct = round(100 * n / sum(n), 1)) %>%
  ungroup() %>%
  mutate(predicted_milestone = factor(predicted_milestone,
                                      levels = MILESTONE_ORDER))

# ── Heatmap tile plot: cell line × milestone ──────────────────────────────
p_ms_heatmap <- ggplot(ms_per_line,
                        aes(x=predicted_milestone, y=cell_line, fill=pct)) +
  geom_tile(colour="white", linewidth=.5) +
  geom_text(aes(label=ifelse(pct >= 2, paste0(pct, "%"), "")),
            size=3.2, fontface="bold",
            colour=ifelse(ms_per_line$pct > 40, "white", "grey20")) +
  scale_fill_gradientn(
    colours = c("#F7FBFF","#DEEBF7","#9ECAE1","#4292C6","#08519C","#08306B"),
    name    = "% of cell\nline cells",
    limits  = c(0, 100)
  ) +
  scale_x_discrete(labels=MILESTONE_LABELS_FULL) +
  theme_classic() +
  labs(x="Reference milestone", y="Sézary cell line",
       title="Milestone distribution per Sézary cell line",
       subtitle="Darker = more cells | M02 = branch point between effector and regulatory arms") +
  theme(plot.title   = element_text(size=13, face="bold"),
        plot.subtitle= element_text(size=9,  colour="grey40"),
        axis.text.x  = element_text(size=9),
        axis.text.y  = element_text(size=11, face="bold"),
        legend.title = element_text(size=9))

print(p_ms_heatmap)


ggsave("Figures/fig_milestone_heatmap.pdf", p_ms_heatmap, width=10, height=5)

# Colours per milestone — derived from state colours
ms_state_colors <- setNames(
  STATE_COLORS[milestone_centroids$state[
    match(MILESTONE_ORDER, milestone_centroids$milestone)]],
  MILESTONE_ORDER
)

p_ms_stack <- ggplot(ms_per_line,
                      aes(x=cell_line, y=pct, fill=predicted_milestone)) +
  geom_col(width=.75, colour="white", linewidth=.3) +
  geom_text(aes(label=ifelse(pct >= 5,
                             paste0(predicted_milestone, "\n", pct, "%"), "")),
            position=position_stack(vjust=0.5),
            size=2.8, colour="white", fontface="bold") +
  scale_fill_manual(values=ms_state_colors,
                    labels=MILESTONE_LABELS_FULL,
                    name="Milestone") +
  scale_y_continuous(expand=c(0,0), limits=c(0,105)) +
  theme_classic() +
  labs(x="Cell line", y="% of cells",
       title="Milestone composition per Sézary cell line",
       subtitle="Colour = CD4 state | Label shown if ≥ 5%") +
  theme(plot.title  = element_text(size=13, face="bold"),
        plot.subtitle = element_text(size=9, colour="grey40"),
        axis.text.x = element_text(size=12, face="bold"))

print(p_ms_stack)


ggsave("Figures/fig_milestone_stack.pdf", p_ms_stack, width=8, height=5)

Note: this chunk must come after the milestone-heatmap chunk since it uses ms_per_line and MILESTONE_LABELS_FULL defined there.

6.7 MST vs Monocle3 Pseudotime per Line

# Scatter: MST vs Monocle3 per cell line — method agreement check
p_pt_cor <- ggplot(violin_df[sample(nrow(violin_df)),],
                    aes(x=mst_pt, y=m3_pt, colour=milestone)) +
  geom_point(size=.8, alpha=.6) +
  geom_abline(slope=1, intercept=0, linetype="dashed", colour="grey40") +
  scale_colour_manual(values=MILESTONE_COLORS,
                      labels=MILESTONE_LABELS, name="Milestone") +
  facet_wrap(~cell_line, ncol=4) +
  theme_classic() +
  labs(x="MST pseudotime (0–100)", y="Monocle3 pseudotime (0–100)",
       title="MST vs Monocle3 pseudotime per Sézary cell line",
       subtitle="Dashed = perfect agreement") +
  theme(plot.title=element_text(size=13,face="bold"),
        strip.text=element_text(face="bold"))

# Compute per-line Spearman rho
pt_cors <- violin_df %>%
  group_by(cell_line) %>%
  summarise(
    spearman = round(cor(mst_pt, m3_pt, method="spearman", use="complete.obs"), 3),
    n        = n(),
    .groups  = "drop"
  )

print(p_pt_cor)

cat("\nPer-line MST vs Monocle3 Spearman ρ:\n")

Per-line MST vs Monocle3 Spearman ρ:
print(pt_cors)
NA

6.8 Per-Cell-Line Individual UMAPs

# Individual UMAP + pseudotime for each line — same style as previous results
pt_gradient <- scale_colour_gradientn(
  colours = c("#0D0887","#6A00A8","#B12A90","#E16462","#FCA636","#F0F921"),
  name    = "MST pseudotime\n(0–100)", limits = c(0,100), na.value = "grey80"
)

for (ln in CELL_LINES) {
  q_df <- query_umap_df %>% filter(cell_line == ln)

  p <- ggplot() +
    geom_point(data=ref_umap_df, aes(x=UMAP_1,y=UMAP_2),
               colour="grey78", size=.25, alpha=.5) +
    geom_segment(data=mst_edge_df,
                 aes(x=x1,y=y1,xend=x2,yend=y2),
                 colour="grey30", linewidth=.7, alpha=.7, lineend="round") +
    geom_point(data=q_df[sample(nrow(q_df)),],
               aes(x=UMAP_1,y=UMAP_2,colour=mst_pt), size=1.5, alpha=.85) +
    pt_gradient +
    # Milestone labels
    geom_text_repel(data=milestone_centroids,
                    aes(x=UMAP_1,y=UMAP_2,label=milestone),
                    size=2.8, fontface="bold", colour="grey20",
                    bg.color="white", bg.r=.1, max.overlaps=15) +
    theme_classic() +
    labs(x="UMAP-1",y="UMAP-2",
         title=sprintf("%s — MST pseudotime on reference UMAP", ln),
         subtitle=sprintf("n=%d cells | MST median=%.1f",
                 nrow(q_df),
                 median(q_df$mst_pt, na.rm=TRUE)))  +
    theme(plot.title=element_text(size=12,face="bold"))

  print(p)
}


7 Pseudotime Summary Table

# Full per-line summary including milestone breakdown
ms_wide <- ms_comp %>%
  select(cell_line, milestone, pct) %>%
  pivot_wider(names_from=milestone, values_from=pct, values_fill=0) %>%
  mutate(across(where(is.numeric), ~round(., 1)))

full_summary <- transfer_summary %>%
  left_join(ms_wide, by="cell_line")

kable(full_summary,
      caption = "Sézary cell line projection summary — pseudotime and milestone composition (%)",
      digits  = 1) %>%
  kable_styling(bootstrap_options=c("striped","hover","condensed"), full_width=FALSE) %>%
  add_header_above(c(" "=ncol(transfer_summary),
                     "% cells per milestone"=ncol(ms_wide)-1))
Sézary cell line projection summary — pseudotime and milestone composition (%)
% cells per milestone
cell_line n_cells mst_pt_mean mst_pt_med m3_pt_mean m3_pt_med top_milestone M01 M02 M03 M05
L1 5825 57.5 55.7 68.6 66.1 M02 6.4 92.1 1.5 0.1
L2 5935 52.7 54.5 57.2 58.9 M02 4.7 95.3 0.0 0.0
L3 6428 52.1 53.5 61.0 60.7 M02 6.9 93.1 0.0 0.0
L4 6006 56.7 56.7 63.7 60.6 M02 1.9 98.1 0.0 0.0
L5 6022 51.8 52.8 57.4 58.4 M02 5.5 94.5 0.0 0.0
L6 5148 58.3 57.1 62.8 60.7 M02 2.1 97.9 0.0 0.0
L7 5331 55.5 55.8 62.8 60.0 M02 1.3 98.7 0.0 0.0

8 Save Outputs

dir.create("Objects", showWarnings=FALSE)
dir.create("Figures", showWarnings=FALSE)
dir.create("Tables",  showWarnings=FALSE)

SaveSeuratRds(sezary_obj, "Objects/sezary_projected.rds")

write.csv(full_summary, "Tables/projection_summary_per_line.csv",   row.names=FALSE)
write.csv(ms_comp,      "Tables/milestone_composition_per_line.csv", row.names=FALSE)

ggsave("Figures/fig_all_lines_umap.pdf",        p_all_lines,  width=14, height=10)
ggsave("Figures/fig_violin_mst.pdf",            p_violin_mst, width=10, height=5)
ggsave("Figures/fig_violin_m3.pdf",             p_violin_m3,  width=10, height=5)
ggsave("Figures/fig_milestone_composition.pdf", p_ms_bar,     width=11, height=5)
ggsave("Figures/fig_pt_correlation.pdf",        p_pt_cor,     width=12, height=8)

cat("✅ All outputs saved\n")

sessionInfo()
R version 4.5.2 (2025-10-31)
Platform: x86_64-redhat-linux-gnu
Running under: Rocky Linux 9.7 (Blue Onyx)

Matrix products: default
BLAS/LAPACK: FlexiBLAS OPENBLAS-OPENMP;  LAPACK version 3.9.0

locale:
 [1] LC_CTYPE=C.UTF-8       LC_NUMERIC=C           LC_TIME=C.UTF-8        LC_COLLATE=C.UTF-8     LC_MONETARY=C.UTF-8    LC_MESSAGES=C.UTF-8   
 [7] LC_PAPER=C.UTF-8       LC_NAME=C              LC_ADDRESS=C           LC_TELEPHONE=C         LC_MEASUREMENT=C.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] future_1.69.0               kableExtra_1.4.0            knitr_1.51                  RColorBrewer_1.1-3          scales_1.4.0               
 [6] viridis_0.6.5               viridisLite_0.4.3           patchwork_1.3.2             tidyr_1.3.2                 dplyr_1.2.0                
[11] ggrepel_0.9.6               ggplot2_4.0.2               igraph_2.2.2                monocle3_1.4.26             SingleCellExperiment_1.32.0
[16] SummarizedExperiment_1.40.0 GenomicRanges_1.62.1        Seqinfo_1.0.0               IRanges_2.44.0              S4Vectors_0.48.0           
[21] MatrixGenerics_1.22.0       matrixStats_1.5.0           Biobase_2.70.0              BiocGenerics_0.56.0         generics_0.1.4             
[26] SeuratWrappers_0.4.0        Seurat_5.4.0                SeuratObject_5.3.0          sp_2.2-1                   

loaded via a namespace (and not attached):
  [1] RcppAnnoy_0.0.23          splines_4.5.2             later_1.4.6               tibble_3.3.1              R.oo_1.27.1               polyclip_1.10-7          
  [7] fastDummies_1.7.5         lifecycle_1.0.5           Rdpack_2.6.6              globals_0.19.0            lattice_0.22-9            MASS_7.3-65              
 [13] magrittr_2.0.4            plotly_4.12.0             sass_0.4.10               rmarkdown_2.30            jquerylib_0.1.4           yaml_2.3.12              
 [19] remotes_2.5.0             httpuv_1.6.16             otel_0.2.0                glmGamPoi_1.22.0          sctransform_0.4.3         spam_2.11-3              
 [25] spatstat.sparse_3.1-0     reticulate_1.45.0         cowplot_1.2.0             pbapply_1.7-4             minqa_1.2.8               abind_1.4-8              
 [31] Rtsne_0.17                purrr_1.2.1               R.utils_2.13.0            irlba_2.3.7               listenv_0.10.0            spatstat.utils_3.2-1     
 [37] goftest_1.2-3             RSpectra_0.16-2           spatstat.random_3.4-4     fitdistrplus_1.2-6        parallelly_1.46.1         DelayedMatrixStats_1.32.0
 [43] svglite_2.2.2             codetools_0.2-20          DelayedArray_0.36.0       xml2_1.5.2                tidyselect_1.2.1          farver_2.1.2             
 [49] lme4_1.1-38               spatstat.explore_3.7-0    jsonlite_2.0.0            progressr_0.18.0          ggridges_0.5.7            survival_3.8-6           
 [55] systemfonts_1.3.1         tools_4.5.2               ragg_1.5.0                ica_1.0-3                 Rcpp_1.1.1                glue_1.8.0               
 [61] gridExtra_2.3             SparseArray_1.10.8        xfun_0.56                 withr_3.0.2               BiocManager_1.30.27       fastmap_1.2.0            
 [67] boot_1.3-32               digest_0.6.39             rsvd_1.0.5                R6_2.6.1                  mime_0.13                 textshaping_1.0.4        
 [73] scattermore_1.2           tensor_1.5.1              dichromat_2.0-0.1         spatstat.data_3.1-9       R.methodsS3_1.8.2         utf8_1.2.6               
 [79] data.table_1.18.2.1       httr_1.4.8                htmlwidgets_1.6.4         S4Arrays_1.10.1           uwot_0.2.4                pkgconfig_2.0.3          
 [85] gtable_0.3.6              rsconnect_1.7.0           lmtest_0.9-40             S7_0.2.1                  XVector_0.50.0            htmltools_0.5.9          
 [91] dotCall64_1.2             png_0.1-8                 spatstat.univar_3.1-6     reformulas_0.4.4          rstudioapi_0.18.0         tzdb_0.5.0               
 [97] reshape2_1.4.5            nlme_3.1-168              nloptr_2.2.1              zoo_1.8-15                cachem_1.1.0              stringr_1.6.0            
[103] KernSmooth_2.23-26        parallel_4.5.2            miniUI_0.1.2              pillar_1.11.1             grid_4.5.2                vctrs_0.7.1              
[109] RANN_2.6.2                promises_1.5.0            beachmat_2.26.0           xtable_1.8-4              cluster_2.1.8.2           evaluate_1.0.5           
[115] readr_2.1.6               cli_3.6.5                 compiler_4.5.2            rlang_1.1.7               future.apply_1.20.1       labeling_0.4.3           
[121] fs_1.6.6                  plyr_1.8.9                stringi_1.8.7             deldir_2.0-4              lazyeval_0.2.2            spatstat.geom_3.7-0      
[127] Matrix_1.7-4              RcppHNSW_0.6.0            hms_1.1.4                 sparseMatrixStats_1.22.0  shiny_1.12.1              rbibutils_2.4.1          
[133] ROCR_1.0-12               bslib_0.10.0             
LS0tCnRpdGxlOiAiU3RlcCAyOiBTw6l6YXJ5IENlbGwgTGluZSBQcm9qZWN0aW9uIG9udG8gQ0Q0IFJlZmVyZW5jZSBUcmFqZWN0b3J5IgpzdWJ0aXRsZTogIlBlci1jZWxsLWxpbmUgU0NUIHwgTVNUICsgTW9ub2NsZTMgcHNldWRvdGltZSB0cmFuc2ZlciB8IDctbWlsZXN0b25lIGFsaWdubWVudCIKYXV0aG9yOiAiTkFTSVIgTUFITU9PRCBBQkJBU0kiCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OgogICAgICBjb2xsYXBzZWQ6IGZhbHNlCiAgICB0aGVtZTogam91cm5hbAogICAgaGlnaGxpZ2h0OiB0YW5nbwogIGh0bWxfZG9jdW1lbnQ6CiAgICB0b2M6IHRydWUKICAgIGRmX3ByaW50OiBwYWdlZAogICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlCi0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsCiAgICAgICAgICAgICAgICAgICAgICBmaWcuYWxpZ249ImNlbnRlciIsIGRwaT0xNTApCmBgYAoKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgojIE92ZXJ2aWV3CgoqKldoYXQgdGhpcyBzY3JpcHQgZG9lcyDigJQgbm90aGluZyBpcyByZS1jb21wdXRlZCBmcm9tIHNjcmF0Y2guKioKCi0gTG9hZHMgdGhlIGFscmVhZHktc2F2ZWQgcmVmZXJlbmNlIHRyYWplY3Rvcnkgb2JqZWN0cyBmcm9tIFN0ZXAgMQotIExvYWRzIFPDqXphcnkgY2VsbCBsaW5lcyBMMeKAk0w3Ci0gUnVucyAqKnBlci1jZWxsLWxpbmUgU0NUIG5vcm1hbGlzYXRpb24qKiBiZWZvcmUgYEZpbmRUcmFuc2ZlckFuY2hvcnNgCiAg4oaSIGVhY2ggbGluZSBnZXRzIGl0cyBvd24gU0NUIG1vZGVsIOKGkiBtb3JlIGJpb2xvZ2ljYWxseSBtZWFuaW5nZnVsIGFuY2hvcnMKLSBQcm9qZWN0cyBlYWNoIGxpbmUgaW5kaXZpZHVhbGx5IG9udG8gdGhlIGZyb3plbiByZWZlcmVuY2UgVU1BUAotIFRyYW5zZmVycyBib3RoICoqTVNUIHBzZXVkb3RpbWUqKiBhbmQgKipNb25vY2xlMyBwc2V1ZG90aW1lKiogZnJvbSByZWZlcmVuY2UKLSBUcmFuc2ZlcnMgKiptaWxlc3RvbmUgbGFiZWxzKiogKE0wMOKAk00wNikgc28gcG9zaXRpb25zIGNhbiBiZSBjb21wYXJlZCB3aXRoCiAgdGhlIDctbWlsZXN0b25lIHN0cnVjdHVyZSBjb25maXJtZWQgaW4gU3RlcCAxCi0gUHJvZHVjZXMgdmlzdWFsaXNhdGlvbnMgaWRlbnRpY2FsIGluIHN0eWxlIHRvIHByZXZpb3VzIHJlc3VsdHMgc28geW91IGNhbgogIGRpcmVjdGx5IGNvbXBhcmUgdGhpcyBydW4gKDcgbWlsZXN0b25lcykgd2l0aCB0aGUgcHJldmlvdXMgcnVuCgoqKlJlZmVyZW5jZSBtaWxlc3RvbmUgc3RydWN0dXJlIChhbHJlYWR5IGNvbmZpcm1lZCBpbiBTdGVwIDEpOioqCmBgYApNMDAgPSBDRDQgTmFpdmUgICAgICAoMjAzNykKTTAxID0gQ0Q0IFRDTSBlYXJseSAgKDQ3MTcpCk0wMiA9IENENCBUQ00gbGF0ZSAgICg0MzUwKQpNMDMgPSBDRDQgVEVNICAgICAgICAoMTQ1KQpNMDQgPSBDRDQgVGVtcmEvQ1RMICAoMTApCk0wNSA9IFRyZWcgcmVzdGluZyAgICgxNDYpCk0wNiA9IFRyZWcgZWZmZWN0b3IgICg2MSkKYGBgCgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCiMgU2V0dXAKCmBgYHtyIGxpYnJhcmllc30Kc3VwcHJlc3NQYWNrYWdlU3RhcnR1cE1lc3NhZ2VzKHsKICBsaWJyYXJ5KFNldXJhdCkKICBsaWJyYXJ5KFNldXJhdFdyYXBwZXJzKQogIGxpYnJhcnkobW9ub2NsZTMpCiAgbGlicmFyeShpZ3JhcGgpCiAgbGlicmFyeShnZ3Bsb3QyKQogIGxpYnJhcnkoZ2dyZXBlbCkKICBsaWJyYXJ5KGRwbHlyKQogIGxpYnJhcnkodGlkeXIpCiAgbGlicmFyeShwYXRjaHdvcmspCiAgbGlicmFyeSh2aXJpZGlzKQogIGxpYnJhcnkoc2NhbGVzKQogIGxpYnJhcnkoUkNvbG9yQnJld2VyKQogIGxpYnJhcnkoa25pdHIpCiAgbGlicmFyeShrYWJsZUV4dHJhKQp9KQoKIyDilIDilIAgTXVzdCBtYXRjaCBTdGVwIDEgZXhhY3RseSDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIAKU1RBVEVfQ09MT1JTIDwtIGMoCiAgIkNENCBOYWl2ZSIgICAgID0gIiM0NDcyQzQiLAogICJDRDQgVENNIiAgICAgICA9ICIjNzBBRDQ3IiwKICAiQ0Q0IFRFTSIgICAgICAgPSAiI0VEN0QzMSIsCiAgIkNENCBUZW1yYS9DVEwiID0gIiNDMDAwMDAiLAogICJUcmVnIiAgICAgICAgICA9ICIjNzAzMEEwIgopCgpDRUxMX0xJTkVfQ09MT1JTIDwtIHNldE5hbWVzKAogIGNvbG9yUmFtcFBhbGV0dGUoYnJld2VyLnBhbCg4LCJEYXJrMiIpKSg3KSwKICBwYXN0ZTAoIkwiLCAxOjcpCikKCk1JTEVTVE9ORV9DT0xPUlMgPC0gc2V0TmFtZXMoCiAgYygiIzQ0NzJDNCIsICAgICAgICAgICAgIyBNMDAgTmFpdmUKICAgICIjNzBBRDQ3IiwiI0E5RDE4RSIsICAjIE0wMS9NMDIgVENNIGVhcmx5L2xhdGUKICAgICIjRUQ3RDMxIiwgICAgICAgICAgICAjIE0wMyBURU0KICAgICIjQzAwMDAwIiwgICAgICAgICAgICAjIE0wNCBUZW1yYQogICAgIiM3MDMwQTAiLCIjQjRBMEQ0IiksICMgTTA1L00wNiBUcmVnCiAgc3ByaW50ZigiTSUwMmQiLCAwOjYpCikKCk1JTEVTVE9ORV9MQUJFTFMgPC0gYygKICBNMDAgPSAiTTAwIE5haXZlIiwKICBNMDEgPSAiTTAxIFRDTShlYXJseSkiLAogIE0wMiA9ICJNMDIgVENNKGxhdGUpIiwKICBNMDMgPSAiTTAzIFRFTSIsCiAgTTA0ID0gIk0wNCBUZW1yYSIsCiAgTTA1ID0gIk0wNSBUcmVnKHJlc3QpIiwKICBNMDYgPSAiTTA2IFRyZWcoZWZmKSIKKQoKU1RBVEVfQ09MICA8LSAicHJlZGljdGVkLmNlbGx0eXBlLmwyIgpVTUFQX05BTUUgIDwtICJ1bWFwIgpDRUxMX0xJTkVTIDwtIHBhc3RlMCgiTCIsIDE6NykKCk1JTEVTVE9ORV9PUkRFUiA8LSBzcHJpbnRmKCJNJTAyZCIsIDA6NikKCmNhdCgi4pyTIFNldHVwIGNvbXBsZXRlXG4iKQpgYGAKCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKIyBMb2FkIFJlZmVyZW5jZSBPYmplY3RzIChTdGVwIDEgb3V0cHV0cyDigJQgbm90IHJlLXJ1bikKCmBgYHtyIGxvYWQtcmVmZXJlbmNlfQojIOKUgOKUgCBMb2FkIHRoZSByZWZlcmVuY2UgU2V1cmF0IG9iamVjdCB3aXRoIGZyb3plbiBVTUFQIG1vZGVsIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgApjZDRfcmVmIDwtIHJlYWRSRFMoIi4uLy4uLzEtQ3VzdG9tX01TVC9PYmplY3RzL2NkNF9yZWZfZHVhbF90cmFqZWN0b3J5LnJkcyIpCgojIOKUgOKUgCBMb2FkIE1TVCBncmFwaCBhbmQgY2VudHJvaWRzIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgAptc3RfZ3JhcGggICAgICAgICAgIDwtIHJlYWRSRFMoIi4uLy4uLzEtQ3VzdG9tX01TVC9PYmplY3RzL21zdF9ncmFwaC5yZHMiKQptaWxlc3RvbmVfY2VudHJvaWRzIDwtIHJlYWRSRFMoIi4uLy4uLzEtQ3VzdG9tX01TVC9PYmplY3RzL21pbGVzdG9uZV9jZW50cm9pZHMucmRzIikKCiMg4pSA4pSAIFZhbGlkYXRlIG1pbGVzdG9uZSBzdHJ1Y3R1cmUg4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSACmV4cGVjdGVkX21zIDwtIHNwcmludGYoIk0lMDJkIiwgMDo2KQphY3R1YWxfbXMgICA8LSBzb3J0KHVuaXF1ZShuYS5vbWl0KGNkNF9yZWZAbWV0YS5kYXRhJG1pbGVzdG9uZSkpKQpzdG9waWZub3QoCiAgIk1pbGVzdG9uZSBtaXNtYXRjaCDigJQgcmUtcnVuIFN0ZXAgMSBmaXJzdCIgPSBpZGVudGljYWwoYWN0dWFsX21zLCBleHBlY3RlZF9tcyksCiAgIm1zdF9wc2V1ZG90aW1lX25vcm0gbWlzc2luZyIgICAgICAgICAgICAgID0gIm1zdF9wc2V1ZG90aW1lX25vcm0iICVpbiUgY29sbmFtZXMoY2Q0X3JlZkBtZXRhLmRhdGEpLAogICJtb25vY2xlM19wc2V1ZG90aW1lX25vcm0gbWlzc2luZyIgICAgICAgICA9ICJtb25vY2xlM19wc2V1ZG90aW1lX25vcm0iICVpbiUgY29sbmFtZXMoY2Q0X3JlZkBtZXRhLmRhdGEpLAogICJVTUFQIG1vZGVsIG1pc3NpbmcgZnJvbSBjZDRfcmVmIiAgICAgICAgICA9ICFpcy5udWxsKGNkNF9yZWZAcmVkdWN0aW9uc1tbVU1BUF9OQU1FXV1AbWlzYyRtb2RlbCkKKQoKY2F0KCLinIUgUmVmZXJlbmNlIG9iamVjdHMgbG9hZGVkIGFuZCB2YWxpZGF0ZWRcbiIpCmNhdChzcHJpbnRmKCIgICAlZCByZWZlcmVuY2UgY2VsbHMgfCAlZCBtaWxlc3RvbmVzXG4iLCBuY29sKGNkNF9yZWYpLCBsZW5ndGgoYWN0dWFsX21zKSkpCmNhdChzcHJpbnRmKCIgICBNU1Q6ICVkIG5vZGVzLCAlZCBlZGdlc1xuIiwgdmNvdW50KG1zdF9ncmFwaCksIGVjb3VudChtc3RfZ3JhcGgpKSkKY2F0KCJcbk1pbGVzdG9uZSBzdHJ1Y3R1cmUgZnJvbSBTdGVwIDE6XG4iKQpwcmludChtaWxlc3RvbmVfY2VudHJvaWRzWywgYygibWlsZXN0b25lIiwic3RhdGUiLCJuX2NlbGxzIildKQoKIyDilIDilIAgQnVpbGQgcmVmZXJlbmNlIFVNQVAgZGF0YSBmcmFtZSBmb3IgYmFja2dyb3VuZCBwbG90dGluZyDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIAKcmVmX3VtYXBfZGYgICAgICAgICAgIDwtIGFzLmRhdGEuZnJhbWUoRW1iZWRkaW5ncyhjZDRfcmVmLCBVTUFQX05BTUUpKQpjb2xuYW1lcyhyZWZfdW1hcF9kZikgPC0gYygiVU1BUF8xIiwiVU1BUF8yIikKcmVmX3VtYXBfZGYkc3RhdGUgICAgIDwtIGNkNF9yZWZAbWV0YS5kYXRhW1tTVEFURV9DT0xdXQpyZWZfdW1hcF9kZiRtaWxlc3RvbmUgPC0gY2Q0X3JlZkBtZXRhLmRhdGEkbWlsZXN0b25lCnJlZl91bWFwX2RmJG1zdF9wdCAgICA8LSBjZDRfcmVmQG1ldGEuZGF0YSRtc3RfcHNldWRvdGltZV9ub3JtCnJlZl91bWFwX2RmJG0zX3B0ICAgICA8LSBjZDRfcmVmQG1ldGEuZGF0YSRtb25vY2xlM19wc2V1ZG90aW1lX25vcm0KCiMg4pSA4pSAIE1TVCBlZGdlIGRhdGEgZnJhbWUgZm9yIG92ZXJsYXkg4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSACm1zdF9lZGdlcyAgIDwtIGFzX2VkZ2VsaXN0KG1zdF9ncmFwaCkKbXN0X2VkZ2VfZGYgPC0gZG8uY2FsbChyYmluZCwgbGFwcGx5KHNlcV9sZW4obnJvdyhtc3RfZWRnZXMpKSwgZnVuY3Rpb24oaSkgewogIG0xIDwtIG1zdF9lZGdlc1tpLDFdOyBtMiA8LSBtc3RfZWRnZXNbaSwyXQogIHIxIDwtIG1pbGVzdG9uZV9jZW50cm9pZHNbbWlsZXN0b25lX2NlbnRyb2lkcyRtaWxlc3RvbmUgPT0gbTEsIF0KICByMiA8LSBtaWxlc3RvbmVfY2VudHJvaWRzW21pbGVzdG9uZV9jZW50cm9pZHMkbWlsZXN0b25lID09IG0yLCBdCiAgZGF0YS5mcmFtZSh4MT1yMSRVTUFQXzEsIHkxPXIxJFVNQVBfMiwgeDI9cjIkVU1BUF8xLCB5Mj1yMiRVTUFQXzIpCn0pKQpgYGAKCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKIyBMb2FkIFPDqXphcnkgQ2VsbCBMaW5lcwoKYGBge3IgbG9hZC1zZXphcnl9CmFsbF9vYmogPC0gcmVhZFJEUygiL2hvbWUvbmFiYmFzaS9hcG9sbG9faG9tZS8xLVNldXJhdF9SRFNfT0JKRUNUX0ZJTkFML0FsbF9zYW1wbGVzX01lcmdlZF93aXRoX1JlbmFtZWRfQ2x1c3RlcnNfQ2VsbF9zdGF0ZS0wMy0xMi0yMDI1LnJkcy5yZHMiKQoKIyDilIDilIAgY2VsbF9saW5lIGNvbHVtbiBjb25maXJtZWQgcHJlc2VudCAoNDAsNjk1IGNlbGxzLCBMMeKAk0w3KSDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIAKYWxsX29iaiRjZWxsX2xpbmUgPC0gYXMuY2hhcmFjdGVyKGFsbF9vYmokY2VsbF9saW5lKQpzZXphcnlfb2JqIDwtIHN1YnNldChhbGxfb2JqLCBzdWJzZXQgPSBjZWxsX2xpbmUgJWluJSBDRUxMX0xJTkVTKQpybShhbGxfb2JqKTsgZ2MoKQoKY2F0KHNwcmludGYoIlPDqXphcnkgY2VsbHMgbG9hZGVkOiAlZCB0b3RhbFxuIiwgbmNvbChzZXphcnlfb2JqKSkpCmNhdCgiUGVyIGNlbGwgbGluZTpcbiIpCnByaW50KHRhYmxlKHNlemFyeV9vYmokY2VsbF9saW5lKSkKCkRlZmF1bHRBc3NheShzZXphcnlfb2JqKSA8LSAiUk5BIgoKIyBTaGFyZWQgZ2VuZXMgd2l0aCByZWZlcmVuY2UgKGNvbmZpcm1lZCB+MzYsNjAxKQpzaGFyZWRfZ2VuZXMgPC0gaW50ZXJzZWN0KHJvd25hbWVzKHNlemFyeV9vYmopLCByb3duYW1lcyhjZDRfcmVmKSkKY2F0KHNwcmludGYoIlxuU2hhcmVkIGdlbmVzIChxdWVyeSDiiKkgcmVmZXJlbmNlKTogJWRcbiIsIGxlbmd0aChzaGFyZWRfZ2VuZXMpKSkKc3RvcGlmbm90KCJGZXdlciB0aGFuIDIwMDAgc2hhcmVkIGdlbmVzIOKAlCBjaGVjayBvYmplY3RzIiA9IGxlbmd0aChzaGFyZWRfZ2VuZXMpID49IDIwMDApCgojIOKUgOKUgCBDaGVjayBwZXJjZW50Lm10IGV4aXN0cyDigJQgbmVlZGVkIGZvciBTQ1RyYW5zZm9ybSB2YXJzLnRvLnJlZ3Jlc3Mg4pSA4pSA4pSA4pSA4pSA4pSACiMgSWYgcGVyY2VudC5tdCBpcyBhYnNlbnQsIFNDVCB3aWxsIHJ1biB3aXRob3V0IHJlZ3Jlc3NpbmcgaXQgKHNlZSBwZXItbGluZSBjaHVuaykKSEFTX1BDVF9NVCA8LSAicGVyY2VudC5tdCIgJWluJSBjb2xuYW1lcyhzZXphcnlfb2JqQG1ldGEuZGF0YSkKY2F0KHNwcmludGYoIlxucGVyY2VudC5tdCBpbiBtZXRhZGF0YTogJXNcbiIsIEhBU19QQ1RfTVQpKQpgYGAKCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKIyBQZXItQ2VsbC1MaW5lIFNDVCArIFByb2plY3Rpb24KCioqUmF0aW9uYWxlIGZvciBwZXItY2VsbC1saW5lIFNDVDoqKiAgCkVhY2ggU8OpemFyeSBjZWxsIGxpbmUgaGFzIGl0cyBvd24gbGlicmFyeSBzaXplIGRpc3RyaWJ1dGlvbiBhbmQgdGVjaG5pY2FsCnZhcmlhdGlvbi4gUnVubmluZyBTQ1RyYW5zZm9ybSBvbiBlYWNoIGxpbmUgaW5kZXBlbmRlbnRseSBmaXRzIGEgbGluZS1zcGVjaWZpYwpyZWdyZXNzaW9uIG1vZGVsLCB3aGljaCBwcm9kdWNlcyBiZXR0ZXItY2FsaWJyYXRlZCByZXNpZHVhbHMgZm9yIGFuY2hvciBmaW5kaW5nLgpUaGlzIGdpdmVzIGBGaW5kVHJhbnNmZXJBbmNob3JzYCBtb3JlIHJlbGlhYmxlIGFuY2hvcnMgY29tcGFyZWQgdG8gbm9ybWFsaXNpbmcKYWxsIGxpbmVzIHRvZ2V0aGVyLCBhdCB0aGUgY29zdCBvZiBvbmUgU0NUIHJ1biBwZXIgbGluZSAofjMw4oCTNjAgcyBlYWNoKS4KCmBgYHtyIHBlci1saW5lLXByb2plY3Rpb24sIHJlc3VsdHM9J2hvbGQnfQoKcGxhbigic2VxdWVudGlhbCIpCm9wdGlvbnMoZnV0dXJlLmdsb2JhbHMubWF4U2l6ZSA9IDIwMDAwMCAqIDEwMjReMikgICMgMjAwIEdCIOKAlCBlZmZlY3RpdmVseSB1bmxpbWl0ZWQKCiMg4pSA4pSAIFN0ZXAgMTogQ2VsbCBjeWNsZSBzY29yaW5nIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgApEZWZhdWx0QXNzYXkoc2V6YXJ5X29iaikgPC0gIlJOQSIKc2V6YXJ5X29iaiA8LSBOb3JtYWxpemVEYXRhKHNlemFyeV9vYmosIHZlcmJvc2U9RkFMU0UpCnNlemFyeV9vYmogPC0gQ2VsbEN5Y2xlU2NvcmluZygKICBzZXphcnlfb2JqLAogIHMuZmVhdHVyZXMgICA9IGNjLmdlbmVzLnVwZGF0ZWQuMjAxOSRzLmdlbmVzLAogIGcybS5mZWF0dXJlcyA9IGNjLmdlbmVzLnVwZGF0ZWQuMjAxOSRnMm0uZ2VuZXMsCiAgc2V0LmlkZW50ICAgID0gRkFMU0UKKQpjYXQoIkNlbGwgY3ljbGUgcGhhc2VzOlxuIik7IHByaW50KHRhYmxlKHNlemFyeV9vYmokUGhhc2UpKQoKIyDilIDilIAgU3RlcCAyOiBQZXItbGluZSBTQ1Qg4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSACkhBU19QQ1RfTVQgIDwtICJwZXJjZW50Lm10IiAlaW4lIGNvbG5hbWVzKHNlemFyeV9vYmpAbWV0YS5kYXRhKQpzZXphcnlfbGlzdCA8LSBTcGxpdE9iamVjdChzZXphcnlfb2JqLCBzcGxpdC5ieSA9ICJjZWxsX2xpbmUiKQoKc2V6YXJ5X2xpc3QgPC0gbGFwcGx5KHNlcV9hbG9uZyhzZXphcnlfbGlzdCksIGZ1bmN0aW9uKGkpIHsKICBjYXQoc3ByaW50ZigiICBTQ1Q6ICVzICglZCBjZWxscylcbiIsCiAgICAgICAgICAgICAgbmFtZXMoc2V6YXJ5X2xpc3QpW2ldLCBuY29sKHNlemFyeV9saXN0W1tpXV0pKSkKICBTQ1RyYW5zZm9ybSgKICAgIHNlemFyeV9saXN0W1tpXV0sCiAgICB2c3QuZmxhdm9yICAgICAgPSAidjIiLAogICAgdmFycy50by5yZWdyZXNzID0gYygiUy5TY29yZSIsICJHMk0uU2NvcmUiLAogICAgICAgICAgICAgICAgICAgICAgICBpZiAoSEFTX1BDVF9NVCkgInBlcmNlbnQubXQiIGVsc2UgTlVMTCksCiAgICB2ZXJib3NlICAgICAgICAgPSBGQUxTRQogICkKfSkKCnNlemFyeV9vYmogPC0gbWVyZ2Uoc2V6YXJ5X2xpc3RbWzFdXSwgc2V6YXJ5X2xpc3RbLTFdLCBtZXJnZS5kYXRhPVRSVUUpCmNhdChzcHJpbnRmKCJNZXJnZWQgcXVlcnk6ICVkIGNlbGxzXG4iLCBuY29sKHNlemFyeV9vYmopKSkKCiMg4pSA4pSAIFN0ZXAgMzogU2hhcmVkIGZlYXR1cmVzIGZyb20gcmVmZXJlbmNlIGludGVncmF0ZWQgYXNzYXkg4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSACkRlZmF1bHRBc3NheShjZDRfcmVmKSA8LSAiaW50ZWdyYXRlZCIKcmVmX2ZlYXR1cmVzICAgICAgICAgIDwtIHJvd25hbWVzKGNkNF9yZWZbWyJpbnRlZ3JhdGVkIl1dQHNjYWxlLmRhdGEpCnF1ZXJ5X2dlbmVzICAgICAgICAgICA8LSByb3duYW1lcyhzZXphcnlfb2JqW1siU0NUIl1dKQpzaGFyZWRfZmVhdHVyZXMgICAgICAgPC0gaW50ZXJzZWN0KHJlZl9mZWF0dXJlcywgcXVlcnlfZ2VuZXMpCgpqdW5rX3BhdHRlcm4gICAgICAgICAgPC0gIl5SUEx8XlJQU3xeTVQtfF5IU1B8XlNOSEd8Xk1BTEFUMXxeTkVBVDF8XlhJU1R8XlRSQlZ8XlRSQVZ8XlRSR1Z8XlRSRFZ8XkhMQS0iCnNoYXJlZF9mZWF0dXJlc19jbGVhbiA8LSBzaGFyZWRfZmVhdHVyZXNbIWdyZXBsKGp1bmtfcGF0dGVybiwgc2hhcmVkX2ZlYXR1cmVzKV0KY2F0KHNwcmludGYoIlNoYXJlZCBmZWF0dXJlcyBhZnRlciBjbGVhbmluZzogJWRcbiIsIGxlbmd0aChzaGFyZWRfZmVhdHVyZXNfY2xlYW4pKSkKClZhcmlhYmxlRmVhdHVyZXMoc2V6YXJ5X29iaikgPC0gc2hhcmVkX2ZlYXR1cmVzX2NsZWFuCkRlZmF1bHRBc3NheShzZXphcnlfb2JqKSAgICAgPC0gIlNDVCIKCiMg4pSA4pSAIFN0ZXAgNDogRmluZFRyYW5zZmVyQW5jaG9ycyDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIAKYW5jaG9ycyA8LSBGaW5kVHJhbnNmZXJBbmNob3JzKAogIHJlZmVyZW5jZSAgICAgICAgICAgID0gY2Q0X3JlZiwKICBxdWVyeSAgICAgICAgICAgICAgICA9IHNlemFyeV9vYmosCiAgZmVhdHVyZXMgICAgICAgICAgICAgPSBzaGFyZWRfZmVhdHVyZXNfY2xlYW4sCiAgbm9ybWFsaXphdGlvbi5tZXRob2QgPSAiU0NUIiwKICByZWZlcmVuY2UucmVkdWN0aW9uICA9ICJwY2EiLAogIHJlZHVjdGlvbiAgICAgICAgICAgID0gInBjYXByb2plY3QiLAogIGRpbXMgICAgICAgICAgICAgICAgID0gMTozMCwKICBrLmFuY2hvciAgICAgICAgICAgICA9IDEwLAogIGsuZmlsdGVyICAgICAgICAgICAgID0gNTAwLAogIGsuc2NvcmUgICAgICAgICAgICAgID0gMzAsCiAgdmVyYm9zZSAgICAgICAgICAgICAgPSBUUlVFCikKY2F0KHNwcmludGYoIkFuY2hvcnMgZm91bmQ6ICVkXG4iLCBucm93KGFuY2hvcnNAYW5jaG9ycykpKQoKIyDilIDilIAgU3RlcCA1OiBNYXBRdWVyeSDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIAKc2V6YXJ5X29iaiA8LSBNYXBRdWVyeSgKICBhbmNob3JzZXQgICAgICAgICAgID0gYW5jaG9ycywKICBxdWVyeSAgICAgICAgICAgICAgID0gc2V6YXJ5X29iaiwKICByZWZlcmVuY2UgICAgICAgICAgID0gY2Q0X3JlZiwKICByZWZkYXRhICAgICAgICAgICAgID0gbGlzdCgKICAgIG1pbGVzdG9uZSA9IGNkNF9yZWZAbWV0YS5kYXRhJG1pbGVzdG9uZSwKICAgIHN0YXRlICAgICA9IGNkNF9yZWZAbWV0YS5kYXRhW1tTVEFURV9DT0xdXQogICksCiAgcmVmZXJlbmNlLnJlZHVjdGlvbiA9ICJwY2EiLAogIHJlZHVjdGlvbi5tb2RlbCAgICAgPSBVTUFQX05BTUUsCiAgdmVyYm9zZSAgICAgICAgICAgICA9IEZBTFNFCikKY2F0KHNwcmludGYoIk1hcFF1ZXJ5IGNvbXBsZXRlOiAlZCBjZWxsc1xuIiwgbmNvbChzZXphcnlfb2JqKSkpCgojIOKUgOKUgCBTdGVwIDY6IFRyYW5zZmVyIHBzZXVkb3RpbWUg4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSACnB0X21hdCAgICAgICAgICAgPC0gcmJpbmQoCiAgbXN0X3B0ID0gY2Q0X3JlZkBtZXRhLmRhdGEkbXN0X3BzZXVkb3RpbWVfbm9ybSwKICBtM19wdCAgPSBjZDRfcmVmQG1ldGEuZGF0YSRtb25vY2xlM19wc2V1ZG90aW1lX25vcm0KKQpjb2xuYW1lcyhwdF9tYXQpIDwtIGNvbG5hbWVzKGNkNF9yZWYpCgp3dF9yZWQgPC0gaWYgKCJyZWYucGNhIiAlaW4lIG5hbWVzKHNlemFyeV9vYmpAcmVkdWN0aW9ucykpICJyZWYucGNhIiBlbHNlICJwY2Fwcm9qZWN0IgpwdF90cmFuc2ZlciA8LSBUcmFuc2ZlckRhdGEoCiAgYW5jaG9yc2V0ICAgICAgICA9IGFuY2hvcnMsCiAgcmVmZGF0YSAgICAgICAgICA9IHB0X21hdCwKICB3ZWlnaHQucmVkdWN0aW9uID0gc2V6YXJ5X29ialtbd3RfcmVkXV0sCiAgZGltcyAgICAgICAgICAgICA9IDE6MzAKKQpwdF9kYXRhIDwtIEdldEFzc2F5RGF0YShwdF90cmFuc2ZlciwgbGF5ZXI9ImRhdGEiKQpjYXQoInB0IHJvd25hbWVzOiIsIHJvd25hbWVzKHB0X2RhdGEpLCAiXG4iKQpzZXphcnlfb2JqJG1zdF9wc2V1ZG90aW1lX25vcm0gICAgICA8LSBhcy5udW1lcmljKHB0X2RhdGFbIm1zdC1wdCIsIF0pCnNlemFyeV9vYmokbW9ub2NsZTNfcHNldWRvdGltZV9ub3JtIDwtIGFzLm51bWVyaWMocHRfZGF0YVsibTMtcHQiLCAgXSkKCiMg4pSA4pSAIFN0ZXAgNzogU3VtbWFyeSDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIAKY2F0KCJcblByZWRpY3RlZCBzdGF0ZSBkaXN0cmlidXRpb246XG4iKQpwcmludCh0YWJsZShzZXphcnlfb2JqJHByZWRpY3RlZC5zdGF0ZSkpCmNhdCgiXG5QcmVkaWN0ZWQgbWlsZXN0b25lIGRpc3RyaWJ1dGlvbjpcbiIpCnByaW50KHRhYmxlKHNlemFyeV9vYmokcHJlZGljdGVkLm1pbGVzdG9uZSkpCmNhdCgiXG5NU1QgcHNldWRvdGltZSByYW5nZToiLCByb3VuZChyYW5nZShzZXphcnlfb2JqJG1zdF9wc2V1ZG90aW1lX25vcm0sIG5hLnJtPVRSVUUpLCAxKSwgIlxuIikKY2F0KCJcbuKchSBQcm9qZWN0aW9uIGNvbXBsZXRlXG4iKQoKCmNhdChzcHJpbnRmKCLinIUgQW5jaG9ycyBmb3VuZDogJWQgYW5jaG9yIHBhaXJzXG4iLCBucm93KGFuY2hvcnNAYW5jaG9ycykpKQpjYXQoc3ByaW50ZigiQ2VsbHMgcHJvamVjdGVkOiAlZFxuIiwgbmNvbChzZXphcnlfb2JqKSkpCmNhdChzcHJpbnRmKCJNZWFuIHByZWRpY3Rpb24gc2NvcmU6ICUuM2YgKD4wLjUgPSByZWxpYWJsZSwgPjAuNyA9IGNvbmZpZGVudClcbiIsCiAgICAgICAgICAgIG1lYW4oc2V6YXJ5X29iaiRwcmVkaWN0ZWQuc3RhdGUuc2NvcmUsIG5hLnJtPVRSVUUpKSkKY2F0KHNwcmludGYoIkNlbGxzIHdpdGggc2NvcmUgPjAuNTogJWQgKCUuMWYlJSlcbiIsCiAgICAgICAgICAgIHN1bShzZXphcnlfb2JqJHByZWRpY3RlZC5zdGF0ZS5zY29yZSA+IDAuNSwgbmEucm09VFJVRSksCiAgICAgICAgICAgIDEwMCptZWFuKHNlemFyeV9vYmokcHJlZGljdGVkLnN0YXRlLnNjb3JlID4gMC41LCBuYS5ybT1UUlVFKSkpCgpjYXQoc3ByaW50ZigiQ2VsbHMgd2l0aCBzY29yZSA+MC43OiAlZCAoJS4xZiUlKVxuIiwKICAgICAgICAgICAgc3VtKHNlemFyeV9vYmokcHJlZGljdGVkLnN0YXRlLnNjb3JlID4gMC43LCBuYS5ybT1UUlVFKSwKICAgICAgICAgICAgMTAwKm1lYW4oc2V6YXJ5X29iaiRwcmVkaWN0ZWQuc3RhdGUuc2NvcmUgPiAwLjcsIG5hLnJtPVRSVUUpKSkKCgpjYXQoc3ByaW50ZigiQ2VsbHMgd2l0aCBzY29yZSA+MC41OiAlZCAoJS4xZiUlKVxuIiwKICAgICAgICAgICAgc3VtKHNlemFyeV9vYmokcHJlZGljdGVkLm1pbGVzdG9uZS5zY29yZSA+IDAuNSwgbmEucm09VFJVRSksCiAgICAgICAgICAgIDEwMCptZWFuKHNlemFyeV9vYmokcHJlZGljdGVkLm1pbGVzdG9uZS5zY29yZSA+IDAuNSwgbmEucm09VFJVRSkpKQoKY2F0KHNwcmludGYoIkNlbGxzIHdpdGggc2NvcmUgPjAuNzogJWQgKCUuMWYlJSlcbiIsCiAgICAgICAgICAgIHN1bShzZXphcnlfb2JqJHByZWRpY3RlZC5taWxlc3RvbmUuc2NvcmUgPiAwLjcsIG5hLnJtPVRSVUUpLAogICAgICAgICAgICAxMDAqbWVhbihzZXphcnlfb2JqJHByZWRpY3RlZC5taWxlc3RvbmUuc2NvcmUgPiAwLjcsIG5hLnJtPVRSVUUpKSkKCmBgYAoKCgpgYGB7ciBtZXJnZS1wcm9qZWN0ZWR9CnF1ZXJ5X3VtYXBfZGYgPC0gYXMuZGF0YS5mcmFtZShFbWJlZGRpbmdzKHNlemFyeV9vYmosICJyZWYudW1hcCIpKQpjb2xuYW1lcyhxdWVyeV91bWFwX2RmKSA8LSBjKCJVTUFQXzEiLCJVTUFQXzIiKQpxdWVyeV91bWFwX2RmJGNlbGxfbGluZSAgICAgICAgICAgPC0gc2V6YXJ5X29iaiRjZWxsX2xpbmUKcXVlcnlfdW1hcF9kZiRwcmVkaWN0ZWRfc3RhdGUgICAgIDwtIHNlemFyeV9vYmokcHJlZGljdGVkLnN0YXRlCnF1ZXJ5X3VtYXBfZGYkcHJlZGljdGVkX21pbGVzdG9uZSA8LSBzZXphcnlfb2JqJHByZWRpY3RlZC5taWxlc3RvbmUKcXVlcnlfdW1hcF9kZiRtc3RfcHQgICAgICAgICAgICAgIDwtIHNlemFyeV9vYmokbXN0X3BzZXVkb3RpbWVfbm9ybQpxdWVyeV91bWFwX2RmJG0zX3B0ICAgICAgICAgICAgICAgPC0gc2V6YXJ5X29iaiRtb25vY2xlM19wc2V1ZG90aW1lX25vcm0KCmNhdChzcHJpbnRmKCJUb3RhbCBjZWxsczogJWRcbiIsIG5yb3cocXVlcnlfdW1hcF9kZikpKQpjYXQoIlBlciBjZWxsIGxpbmU6XG4iKQpwcmludCh0YWJsZShxdWVyeV91bWFwX2RmJGNlbGxfbGluZSkpCmNhdCgiXG5NaWxlc3RvbmUgZGlzdHJpYnV0aW9uOlxuIikKcHJpbnQodGFibGUocXVlcnlfdW1hcF9kZiRwcmVkaWN0ZWRfbWlsZXN0b25lLCB1c2VOQT0iaWZhbnkiKSkKCmBgYAoKLS0tCgpgYGB7ciBzdW1tYXJ5LXRhYmxlLCBmaWcud2lkdGg9MjAsIGZpZy5oZWlnaHQ9OH0KdHJhbnNmZXJfc3VtbWFyeSA8LSBxdWVyeV91bWFwX2RmICU+JQogIGdyb3VwX2J5KGNlbGxfbGluZSkgJT4lCiAgc3VtbWFyaXNlKAogICAgbl9jZWxscyAgICAgICA9IG4oKSwKICAgIG1zdF9wdF9tZWFuICAgPSByb3VuZChtZWFuKG1zdF9wdCwgbmEucm09VFJVRSksIDEpLAogICAgbXN0X3B0X21lZCAgICA9IHJvdW5kKG1lZGlhbihtc3RfcHQsIG5hLnJtPVRSVUUpLCAxKSwKICAgIG0zX3B0X21lYW4gICAgPSByb3VuZChtZWFuKG0zX3B0LCBuYS5ybT1UUlVFKSwgMSksCiAgICBtM19wdF9tZWQgICAgID0gcm91bmQobWVkaWFuKG0zX3B0LCBuYS5ybT1UUlVFKSwgMSksCiAgICB0b3BfbWlsZXN0b25lID0gbmFtZXMoc29ydCh0YWJsZShwcmVkaWN0ZWRfbWlsZXN0b25lKSwgZGVjcmVhc2luZz1UUlVFKSlbMV0sCiAgICAuZ3JvdXBzICAgICAgID0gImRyb3AiCiAgKQoKbXNfd2lkZSA8LSBxdWVyeV91bWFwX2RmICU+JQogIGZpbHRlcighaXMubmEocHJlZGljdGVkX21pbGVzdG9uZSkpICU+JQogIGNvdW50KGNlbGxfbGluZSwgcHJlZGljdGVkX21pbGVzdG9uZSkgJT4lCiAgZ3JvdXBfYnkoY2VsbF9saW5lKSAlPiUKICBtdXRhdGUocGN0ID0gcm91bmQoMTAwICogbiAvIHN1bShuKSwgMSkpICU+JQogIHNlbGVjdChjZWxsX2xpbmUsIHByZWRpY3RlZF9taWxlc3RvbmUsIHBjdCkgJT4lCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbT1wcmVkaWN0ZWRfbWlsZXN0b25lLCB2YWx1ZXNfZnJvbT1wY3QsIHZhbHVlc19maWxsPTApCgpmb3IgKG0gaW4gTUlMRVNUT05FX09SREVSKSB7CiAgaWYgKCFtICVpbiUgY29sbmFtZXMobXNfd2lkZSkpIG1zX3dpZGVbW21dXSA8LSAwCn0KbXNfd2lkZSAgICAgIDwtIG1zX3dpZGVbLCBjKCJjZWxsX2xpbmUiLCBNSUxFU1RPTkVfT1JERVIpXQpmdWxsX3N1bW1hcnkgPC0gbGVmdF9qb2luKHRyYW5zZmVyX3N1bW1hcnksIG1zX3dpZGUsIGJ5PSJjZWxsX2xpbmUiKQoKa2FibGUoZnVsbF9zdW1tYXJ5LAogICAgICBjYXB0aW9uID0gIlPDqXphcnkgcHJvamVjdGlvbiBzdW1tYXJ5IOKAlCBwc2V1ZG90aW1lIGFuZCBtaWxlc3RvbmUgY29tcG9zaXRpb24iLAogICAgICBkaWdpdHMgID0gMSkgJT4lCiAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucz1jKCJzdHJpcGVkIiwiaG92ZXIiLCJjb25kZW5zZWQiKSwKICAgICAgICAgICAgICAgIGZ1bGxfd2lkdGg9RkFMU0UpICU+JQogIGFkZF9oZWFkZXJfYWJvdmUoYygiICI9bmNvbCh0cmFuc2Zlcl9zdW1tYXJ5KSwKICAgICAgICAgICAgICAgICAgICAgIiUgY2VsbHMgcGVyIG1pbGVzdG9uZSI9bGVuZ3RoKE1JTEVTVE9ORV9PUkRFUikpKQpgYGAKCgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCiMgVmlzdWFsaXNhdGlvbnMgey50YWJzZXR9CgojIyBSZWZlcmVuY2UgVU1BUCDigJQgbWlsZXN0b25lcwoKYGBge3IgdmlzLXJlZi1taWxlc3RvbmVzLCBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9OH0KcF9yZWZfbXMgPC0gZ2dwbG90KCkgKwogIGdlb21fcG9pbnQoZGF0YSA9IHJlZl91bWFwX2RmW3NhbXBsZShucm93KHJlZl91bWFwX2RmKSksXSwKICAgICAgICAgICAgIGFlcyh4PVVNQVBfMSwgeT1VTUFQXzIsIGNvbG91cj1zdGF0ZSksIHNpemU9LjQsIGFscGhhPS41KSArCiAgZ2VvbV9zZWdtZW50KGRhdGE9bXN0X2VkZ2VfZGYsCiAgICAgICAgICAgICAgIGFlcyh4PXgxLHk9eTEseGVuZD14Mix5ZW5kPXkyKSwKICAgICAgICAgICAgICAgY29sb3VyPSJibGFjayIsIGxpbmV3aWR0aD0xLCBhbHBoYT0uODUsIGxpbmVlbmQ9InJvdW5kIikgKwogIGdlb21fcG9pbnQoZGF0YT1taWxlc3RvbmVfY2VudHJvaWRzLAogICAgICAgICAgICAgYWVzKHg9VU1BUF8xLHk9VU1BUF8yLGZpbGw9c3RhdGUpLAogICAgICAgICAgICAgc2hhcGU9MjEsIHNpemU9OCwgY29sb3VyPSJ3aGl0ZSIsIHN0cm9rZT0yKSArCiAgZ2VvbV90ZXh0X3JlcGVsKGRhdGE9bWlsZXN0b25lX2NlbnRyb2lkcywKICAgICAgICAgICAgICAgICAgYWVzKHg9VU1BUF8xLHk9VU1BUF8yLAogICAgICAgICAgICAgICAgICAgICAgbGFiZWw9cGFzdGUwKG1pbGVzdG9uZSwiXG4oIixNSUxFU1RPTkVfTEFCRUxTW21pbGVzdG9uZV0sIikiKSksCiAgICAgICAgICAgICAgICAgIHNpemU9Mi44LCBmb250ZmFjZT0iYm9sZCIsCiAgICAgICAgICAgICAgICAgIGJnLmNvbG9yPSJncmV5ODAiLCBiZy5yPS4xNSwgbWF4Lm92ZXJsYXBzPTIwKSArCiAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXM9U1RBVEVfQ09MT1JTLCBuYW1lPSJTdGF0ZSIpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9U1RBVEVfQ09MT1JTLCBndWlkZT0ibm9uZSIpICsKICB0aGVtZV9jbGFzc2ljKCkgKwogIGxhYnMoeD0iVU1BUC0xIiwgeT0iVU1BUC0yIiwKICAgICAgIHRpdGxlPSJSZWZlcmVuY2UgQ0Q0IFQgY2VsbHMg4oCUIDctbWlsZXN0b25lIHN0cnVjdHVyZSIsCiAgICAgICBzdWJ0aXRsZT0iTTAwPU5haXZlIHwgTTAxL00wMj1UQ00gfCBNMDM9VEVNIHwgTTA0PVRlbXJhIHwgTTA1L00wNj1UcmVnIikgKwogIHRoZW1lKHBsb3QudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTMsZmFjZT0iYm9sZCIpKQoKcHJpbnQocF9yZWZfbXMpCgpgYGAKCmBgYHtyICwgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9Nn0KcF9yZWZfbXMgPC0gZ2dwbG90KCkgKwogIGdlb21fcG9pbnQoZGF0YSA9IHJlZl91bWFwX2RmW3NhbXBsZShucm93KHJlZl91bWFwX2RmKSksXSwKICAgICAgICAgICAgIGFlcyh4PVVNQVBfMSwgeT1VTUFQXzIsIGNvbG91cj1zdGF0ZSksIHNpemU9LjQsIGFscGhhPS41KSArCiAgZ2VvbV9zZWdtZW50KGRhdGE9bXN0X2VkZ2VfZGYsCiAgICAgICAgICAgICAgIGFlcyh4PXgxLHk9eTEseGVuZD14Mix5ZW5kPXkyKSwKICAgICAgICAgICAgICAgY29sb3VyPSJibGFjayIsIGxpbmV3aWR0aD0xLCBhbHBoYT0uODUsIGxpbmVlbmQ9InJvdW5kIikgKwogIGdlb21fcG9pbnQoZGF0YT1taWxlc3RvbmVfY2VudHJvaWRzLAogICAgICAgICAgICAgYWVzKHg9VU1BUF8xLHk9VU1BUF8yLGZpbGw9c3RhdGUpLAogICAgICAgICAgICAgc2hhcGU9MjEsIHNpemU9OCwgY29sb3VyPSJ3aGl0ZSIsIHN0cm9rZT0yKSArCiAgZ2VvbV90ZXh0X3JlcGVsKGRhdGE9bWlsZXN0b25lX2NlbnRyb2lkcywKICAgICAgICAgICAgICAgICAgYWVzKHg9VU1BUF8xLHk9VU1BUF8yLAogICAgICAgICAgICAgICAgICAgICAgbGFiZWw9cGFzdGUwKG1pbGVzdG9uZSwiXG4oIixNSUxFU1RPTkVfTEFCRUxTW21pbGVzdG9uZV0sIikiKSksCiAgICAgICAgICAgICAgICAgIHNpemU9Mi44LCBmb250ZmFjZT0iYm9sZCIsCiAgICAgICAgICAgICAgICAgIGJnLmNvbG9yPSJncmV5ODAiLCBiZy5yPS4xNSwgbWF4Lm92ZXJsYXBzPTIwKSArCiAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXM9U1RBVEVfQ09MT1JTLCBuYW1lPSJTdGF0ZSIpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9U1RBVEVfQ09MT1JTLCBndWlkZT0ibm9uZSIpICsKICB0aGVtZV9jbGFzc2ljKCkgKwogIGxhYnMoeD0iVU1BUC0xIiwgeT0iVU1BUC0yIiwKICAgICAgIHRpdGxlPSJSZWZlcmVuY2UgQ0Q0IFQgY2VsbHMg4oCUIDctbWlsZXN0b25lIHN0cnVjdHVyZSIsCiAgICAgICBzdWJ0aXRsZT0iTTAwPU5haXZlIHwgTTAxL00wMj1UQ00gfCBNMDM9VEVNIHwgTTA0PVRlbXJhIHwgTTA1L00wNj1UcmVnIikgKwogIHRoZW1lKHBsb3QudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTMsZmFjZT0iYm9sZCIpKQoKcHJpbnQocF9yZWZfbXMpCgpgYGAKCgoKCmBgYHtyLCBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD02fQpwX2FsbF9saW5lcyA8LSBnZ3Bsb3QoKSArCiAgIyBSZWZlcmVuY2UgYmFja2dyb3VuZAogIGdlb21fcG9pbnQoZGF0YSA9IHJlZl91bWFwX2RmW3NhbXBsZShucm93KHJlZl91bWFwX2RmKSksXSwKICAgICAgICAgICAgIGFlcyh4PVVNQVBfMSwgeT1VTUFQXzIpLAogICAgICAgICAgICAgY29sb3VyPSJncmV5NjgiLCBzaXplPS4yNSwgYWxwaGE9LjQpICsKICAjIE1TVCB0cmFqZWN0b3J5IGxpbmVzCiAgZ2VvbV9zZWdtZW50KGRhdGEgPSBtc3RfZWRnZV9kZiwKICAgICAgICAgICAgICAgYWVzKHg9eDEsIHk9eTEsIHhlbmQ9eDIsIHllbmQ9eTIpLAogICAgICAgICAgICAgICBjb2xvdXI9ImdyZXkzMCIsIGxpbmV3aWR0aD0xLCBhbHBoYT0uOCwgbGluZWVuZD0icm91bmQiKSArCiAgIyBTw6l6YXJ5IGNlbGxzIGNvbG91cmVkIGJ5IGNlbGwgbGluZQogIGdlb21fcG9pbnQoZGF0YSA9IHF1ZXJ5X3VtYXBfZGZbc2FtcGxlKG5yb3cocXVlcnlfdW1hcF9kZikpLF0sCiAgICAgICAgICAgICBhZXMoeD1VTUFQXzEsIHk9VU1BUF8yLCBjb2xvdXI9Y2VsbF9saW5lKSwKICAgICAgICAgICAgIHNpemU9MS41LCBhbHBoYT0uNzUpICsKICAjIE1pbGVzdG9uZSBsYWJlbHMgdXNpbmcgY29uZmlybWVkIDctbWlsZXN0b25lIG5hbWVzCiAgZ2VvbV9sYWJlbF9yZXBlbChkYXRhID0gbWlsZXN0b25lX2NlbnRyb2lkcywKICAgICAgICAgICAgICAgICAgIGFlcyh4PVVNQVBfMSwgeT1VTUFQXzIsCiAgICAgICAgICAgICAgICAgICAgICAgbGFiZWw9cGFzdGUwKG1pbGVzdG9uZSwgIlxuIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTUlMRVNUT05FX0xBQkVMU1ttaWxlc3RvbmVdKSksCiAgICAgICAgICAgICAgICAgICBzaXplPTIuNSwgZm9udGZhY2U9ImJvbGQiLAogICAgICAgICAgICAgICAgICAgZmlsbD0id2hpdGUiLCBjb2xvdXI9ImdyZXkxNSIsCiAgICAgICAgICAgICAgICAgICBsYWJlbC5zaXplPTAuMiwgbWF4Lm92ZXJsYXBzPTIwLAogICAgICAgICAgICAgICAgICAgYm94LnBhZGRpbmc9MC41KSArCiAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXM9Q0VMTF9MSU5FX0NPTE9SUywgbmFtZT0iQ2VsbCBsaW5lIikgKwogIGd1aWRlcyhjb2xvdXI9Z3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcz1saXN0KHNpemU9NCwgYWxwaGE9MSkpKSArCiAgdGhlbWVfY2xhc3NpYygpICsKICBsYWJzKHg9IlVNQVAtMSIsIHk9IlVNQVAtMiIsCiAgICAgICB0aXRsZT0iU8OpemFyeSBjZWxsIGxpbmVzIOKAlCBwcm9qZWN0aW9uIG9udG8gaGVhbHRoeSBDRDQgVCBjZWxsIHJlZmVyZW5jZSIsCiAgICAgICBzdWJ0aXRsZT1zcHJpbnRmKCJHcmV5ID0gaGVhbHRoeSByZWZlcmVuY2UgKCVkIGNlbGxzKSB8IENvbG91cmVkID0gU8OpemFyeSBMMeKAk0w3ICglZCBjZWxscykgfCBMaW5lcyA9IE1TVCB0cmFqZWN0b3J5IiwKICAgICAgICAgICAgICAgICAgICAgICAgbnJvdyhyZWZfdW1hcF9kZiksIG5yb3cocXVlcnlfdW1hcF9kZikpKSArCiAgdGhlbWUocGxvdC50aXRsZSAgICA9IGVsZW1lbnRfdGV4dChzaXplPTEzLCBmYWNlPSJib2xkIiksCiAgICAgICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplPTksICBjb2xvdXI9ImdyZXk0MCIpLAogICAgICAgIGxlZ2VuZC50aXRsZSAgPSBlbGVtZW50X3RleHQoc2l6ZT0xMSwgZmFjZT0iYm9sZCIpLAogICAgICAgIGxlZ2VuZC50ZXh0ICAgPSBlbGVtZW50X3RleHQoc2l6ZT0xMCkpCgpwcmludChwX2FsbF9saW5lcykKCmBgYAoKIyMgQWxsIExpbmVzIOKAlCBQb3NpdGlvbiBvbiBSZWZlcmVuY2UgVU1BUAoKYGBge3IgdmlzLWFsbC1saW5lcy11bWFwLCBmaWcud2lkdGg9MTQsIGZpZy5oZWlnaHQ9OX0KcF9hbGxfbGluZXMgPC0gZ2dwbG90KCkgKwogICMgUmVmZXJlbmNlIGJhY2tncm91bmQgKGdyZXkpCiAgZ2VvbV9wb2ludChkYXRhPXJlZl91bWFwX2RmLAogICAgICAgICAgICAgYWVzKHg9VU1BUF8xLHk9VU1BUF8yKSwgY29sb3VyPSJncmV5NjgiLCBzaXplPS4yNSwgYWxwaGE9LjYpICsKICAjIE1TVCBvdmVybGF5CiAgZ2VvbV9zZWdtZW50KGRhdGE9bXN0X2VkZ2VfZGYsCiAgICAgICAgICAgICAgIGFlcyh4PXgxLHk9eTEseGVuZD14Mix5ZW5kPXkyKSwKICAgICAgICAgICAgICAgY29sb3VyPSJncmV5MzAiLCBsaW5ld2lkdGg9LjcsIGFscGhhPS43LCBsaW5lZW5kPSJyb3VuZCIpICsKICAjIFF1ZXJ5IGNlbGxzIGNvbG91cmVkIGJ5IGNlbGwgbGluZQogIGdlb21fcG9pbnQoZGF0YT1xdWVyeV91bWFwX2RmW3NhbXBsZShucm93KHF1ZXJ5X3VtYXBfZGYpKSxdLAogICAgICAgICAgICAgYWVzKHg9VU1BUF8xLHk9VU1BUF8yLGNvbG91cj1jZWxsX2xpbmUpLCBzaXplPTEuMiwgYWxwaGE9Ljc1KSArCiAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXM9Q0VMTF9MSU5FX0NPTE9SUywgbmFtZT0iQ2VsbCBsaW5lIikgKwogICMgTWlsZXN0b25lIGxhYmVscwogIGdlb21fdGV4dF9yZXBlbChkYXRhPW1pbGVzdG9uZV9jZW50cm9pZHMsCiAgICAgICAgICAgICAgICAgIGFlcyh4PVVNQVBfMSx5PVVNQVBfMixsYWJlbD1taWxlc3RvbmUpLAogICAgICAgICAgICAgICAgICBzaXplPTMsIGZvbnRmYWNlPSJib2xkIiwgY29sb3VyPSJncmV5MjAiLAogICAgICAgICAgICAgICAgICBiZy5jb2xvcj0id2hpdGUiLCBiZy5yPS4xMiwgbWF4Lm92ZXJsYXBzPTE1KSArCiAgdGhlbWVfY2xhc3NpYygpICsKICBsYWJzKHg9IlVNQVAtMSIseT0iVU1BUC0yIiwKICAgICAgIHRpdGxlPSJTw6l6YXJ5IEwx4oCTTDcgcHJvamVjdGVkIG9udG8gcmVmZXJlbmNlIFVNQVAiLAogICAgICAgc3VidGl0bGU9IkdyZXkgPSByZWZlcmVuY2UgfCBDb2xvdXJlZCA9IHF1ZXJ5IGNlbGwgbGluZXMiKSArCiAgdGhlbWUocGxvdC50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xMyxmYWNlPSJib2xkIikpICsKICBmYWNldF93cmFwKH5jZWxsX2xpbmUsIG5jb2w9NCkKCnByaW50KHBfYWxsX2xpbmVzKQoKYGBgCgojIyBBbGwgTGluZXMg4oCUIE1TVCBQc2V1ZG90aW1lIG9uIFJlZmVyZW5jZSBVTUFQCgpgYGB7ciB2aXMtcHNldWRvdGltZS1tc3QsIGZpZy53aWR0aD0xNCwgZmlnLmhlaWdodD02fQojIFBhbmVsIDE6IHJlZmVyZW5jZSBwc2V1ZG90aW1lCnBfcmVmX3B0IDwtIGdncGxvdChyZWZfdW1hcF9kZltzYW1wbGUobnJvdyhyZWZfdW1hcF9kZikpLF0sCiAgICAgICAgICAgICAgICAgICAgYWVzKHg9VU1BUF8xLHk9VU1BUF8yLGNvbG91cj1tc3RfcHQpKSArCiAgZ2VvbV9wb2ludChzaXplPS4zLCBhbHBoYT0uNykgKwogIHNjYWxlX2NvbG91cl9ncmFkaWVudG4oCiAgICBjb2xvdXJzPWMoIiMwRDA4ODciLCIjNkEwMEE4IiwiI0IxMkE5MCIsIiNFMTY0NjIiLCIjRkNBNjM2IiwiI0YwRjkyMSIpLAogICAgbmFtZT0iTVNUIHBzZXVkb3RpbWVcbigw4oCTMTAwKSIsIGxpbWl0cz1jKDAsMTAwKSwgbmEudmFsdWU9ImdyZXk4MCIpICsKICBnZW9tX3NlZ21lbnQoZGF0YT1tc3RfZWRnZV9kZixhZXMoeD14MSx5PXkxLHhlbmQ9eDIseWVuZD15MiksCiAgICAgICAgICAgICAgIGNvbG91cj0iYmxhY2siLGxpbmV3aWR0aD0uNixhbHBoYT0uNixpbmhlcml0LmFlcz1GQUxTRSkgKwogIHRoZW1lX2NsYXNzaWMoKSArCiAgbGFicyh4PSJVTUFQLTEiLHk9IlVNQVAtMiIsdGl0bGU9IlJlZmVyZW5jZSDigJQgTVNUIHBzZXVkb3RpbWUiKSArCiAgdGhlbWUocGxvdC50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xMixmYWNlPSJib2xkIikpCgojIFBhbmVsIDI6IHF1ZXJ5IHBzZXVkb3RpbWUgKGFsbCBsaW5lcyBjb21iaW5lZCkKcF9xdWVyeV9wdCA8LSBnZ3Bsb3QoKSArCiAgZ2VvbV9wb2ludChkYXRhPXJlZl91bWFwX2RmLCBhZXMoeD1VTUFQXzEseT1VTUFQXzIpLAogICAgICAgICAgICAgY29sb3VyPSJncmV5NzgiLCBzaXplPS4yLCBhbHBoYT0uNSkgKwogIGdlb21fcG9pbnQoZGF0YT1xdWVyeV91bWFwX2RmW3NhbXBsZShucm93KHF1ZXJ5X3VtYXBfZGYpKSxdLAogICAgICAgICAgICAgYWVzKHg9VU1BUF8xLHk9VU1BUF8yLGNvbG91cj1tc3RfcHQpLCBzaXplPTEsIGFscGhhPS44KSArCiAgc2NhbGVfY29sb3VyX2dyYWRpZW50bigKICAgIGNvbG91cnM9YygiIzBEMDg4NyIsIiM2QTAwQTgiLCIjQjEyQTkwIiwiI0UxNjQ2MiIsIiNGQ0E2MzYiLCIjRjBGOTIxIiksCiAgICBuYW1lPSJNU1QgcHNldWRvdGltZVxuKDDigJMxMDApIiwgbGltaXRzPWMoMCwxMDApLCBuYS52YWx1ZT0iZ3JleTgwIikgKwogIGdlb21fc2VnbWVudChkYXRhPW1zdF9lZGdlX2RmLGFlcyh4PXgxLHk9eTEseGVuZD14Mix5ZW5kPXkyKSwKICAgICAgICAgICAgICAgY29sb3VyPSJncmV5MzAiLGxpbmV3aWR0aD0uNixhbHBoYT0uNixpbmhlcml0LmFlcz1GQUxTRSkgKwogIHRoZW1lX2NsYXNzaWMoKSArCiAgbGFicyh4PSJVTUFQLTEiLHk9IlVNQVAtMiIsdGl0bGU9IlPDqXphcnkgTDHigJNMNyDigJQgdHJhbnNmZXJyZWQgTVNUIHBzZXVkb3RpbWUiKSArCiAgdGhlbWUocGxvdC50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xMixmYWNlPSJib2xkIikpCgpwcmludChwX3JlZl9wdCB8IHBfcXVlcnlfcHQpCgpgYGAKCiMjIFBlci1MaW5lIE1TVCBQc2V1ZG90aW1lIFZpb2xpbgoKYGBge3IgdmlzLXZpb2xpbi1tc3QsIGZpZy53aWR0aD03LCBmaWcuaGVpZ2h0PTZ9CiMgTWlsZXN0b25lIG1lZGlhbiBsaW5lcyBmb3IgcmVmZXJlbmNlCm1zX21lZGlhbnMgPC0gY2Q0X3JlZkBtZXRhLmRhdGEgJT4lCiAgZ3JvdXBfYnkobWlsZXN0b25lKSAlPiUKICBzdW1tYXJpc2UobWVkID0gbWVkaWFuKG1zdF9wc2V1ZG90aW1lX25vcm0sIG5hLnJtPVRSVUUpLCAuZ3JvdXBzPSJkcm9wIikgJT4lCiAgZmlsdGVyKG1pbGVzdG9uZSAlaW4lIGMoIk0wMCIsIk0wMSIsIk0wMiIsIk0wMyIsIk0wNCIsIk0wNSIsIk0wNiIpKQoKdmlvbGluX2RmIDwtIGRhdGEuZnJhbWUoCiAgY2VsbF9saW5lID0gc2V6YXJ5X29iaiRjZWxsX2xpbmUsCiAgbXN0X3B0ICAgID0gc2V6YXJ5X29iaiRtc3RfcHNldWRvdGltZV9ub3JtLAogIG0zX3B0ICAgICA9IHNlemFyeV9vYmokbW9ub2NsZTNfcHNldWRvdGltZV9ub3JtLAogIG1pbGVzdG9uZSA9IHNlemFyeV9vYmokcHJlZGljdGVkLm1pbGVzdG9uZQopICU+JSBmaWx0ZXIoIWlzLm5hKG1zdF9wdCkpCgpwX3Zpb2xpbl9tc3QgPC0gZ2dwbG90KHZpb2xpbl9kZiwgYWVzKHg9Y2VsbF9saW5lLCB5PW1zdF9wdCwgZmlsbD1jZWxsX2xpbmUpKSArCiAgZ2VvbV92aW9saW4oc2NhbGU9IndpZHRoIiwgdHJpbT1GQUxTRSwgYWxwaGE9Ljg1KSArCiAgZ2VvbV9ib3hwbG90KHdpZHRoPS4wOCwgZmlsbD0id2hpdGUiLCBvdXRsaWVyLnNpemU9LjMsIG91dGxpZXIuYWxwaGE9LjQpICsKICAjIFJlZmVyZW5jZSBtaWxlc3RvbmUgbWVkaWFuIGxpbmVzCiAgZ2VvbV9obGluZShkYXRhPW1zX21lZGlhbnMsCiAgICAgICAgICAgICBhZXMoeWludGVyY2VwdD1tZWQsIGxpbmV0eXBlPW1pbGVzdG9uZSksIGNvbG91cj0iZ3JleTQwIiwgbGluZXdpZHRoPS41KSArCiAgZ2VvbV90ZXh0KGRhdGE9bXNfbWVkaWFucywKICAgICAgICAgIGFlcyh4PTcuNiwgeT1tZWQsIGxhYmVsPW1pbGVzdG9uZSksCiAgICAgICAgICBzaXplPTIuOCwgaGp1c3Q9MCwgY29sb3VyPSJncmV5MjUiLAogICAgICAgICAgaW5oZXJpdC5hZXM9RkFMU0UpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9Q0VMTF9MSU5FX0NPTE9SUywgZ3VpZGU9Im5vbmUiKSArCiAgc2NhbGVfbGluZXR5cGVfbWFudWFsKHZhbHVlcz1yZXAoImRhc2hlZCIsNyksIGd1aWRlPSJub25lIikgKwogIGNvb3JkX2NhcnRlc2lhbih4bGltPWMoMC41LDguNSkpICsKICB0aGVtZV9jbGFzc2ljKCkgKwogIGxhYnMoeD0iQ2VsbCBsaW5lIiwgeT0iTVNUIHBzZXVkb3RpbWUgKDDigJMxMDApIiwKICAgICAgIHRpdGxlPSJNU1QgcHNldWRvdGltZSBwZXIgU8OpemFyeSBjZWxsIGxpbmUiLAogICAgICAgc3VidGl0bGU9IkRhc2hlZCBsaW5lcyA9IHJlZmVyZW5jZSBtaWxlc3RvbmUgbWVkaWFucyAoTTAw4oCTTTA2KSIpICsKICB0aGVtZShwbG90LnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTEzLGZhY2U9ImJvbGQiKSkKCnByaW50KHBfdmlvbGluX21zdCkKYGBgCgojIyBQZXItTGluZSBNb25vY2xlMyBQc2V1ZG90aW1lIFZpb2xpbgoKYGBge3IgdmlzLXZpb2xpbi1tMywgZmlnLndpZHRoPTcsIGZpZy5oZWlnaHQ9Nn0KIyBSZWZlcmVuY2UgbWlsZXN0b25lIG1lZGlhbnMg4oCUIE1vbm9jbGUzCm1zX21lZGlhbnNfbTMgPC0gY2Q0X3JlZkBtZXRhLmRhdGEgJT4lCiAgZ3JvdXBfYnkobWlsZXN0b25lKSAlPiUKICBzdW1tYXJpc2UobWVkID0gbWVkaWFuKG1vbm9jbGUzX3BzZXVkb3RpbWVfbm9ybSwgbmEucm09VFJVRSksIC5ncm91cHM9ImRyb3AiKQoKcF92aW9saW5fbTMgPC0gZ2dwbG90KHZpb2xpbl9kZiwgYWVzKHg9Y2VsbF9saW5lLCB5PW0zX3B0LCBmaWxsPWNlbGxfbGluZSkpICsKICBnZW9tX3Zpb2xpbihzY2FsZT0id2lkdGgiLCB0cmltPUZBTFNFLCBhbHBoYT0uODUpICsKICBnZW9tX2JveHBsb3Qod2lkdGg9LjA4LCBmaWxsPSJ3aGl0ZSIsIG91dGxpZXIuc2l6ZT0uMywgb3V0bGllci5hbHBoYT0uNCkgKwogIGdlb21faGxpbmUoZGF0YT1tc19tZWRpYW5zX20zLAogICAgICAgICAgICAgYWVzKHlpbnRlcmNlcHQ9bWVkLCBsaW5ldHlwZT1taWxlc3RvbmUpLCBjb2xvdXI9ImdyZXk0MCIsIGxpbmV3aWR0aD0uNSkgKwogIGdlb21fdGV4dChkYXRhPW1zX21lZGlhbnMsCiAgICAgICAgICBhZXMoeD03LjYsIHk9bWVkLCBsYWJlbD1taWxlc3RvbmUpLAogICAgICAgICAgc2l6ZT0yLjgsIGhqdXN0PTAsIGNvbG91cj0iZ3JleTI1IiwKICAgICAgICAgIGluaGVyaXQuYWVzPUZBTFNFKSArCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPUNFTExfTElORV9DT0xPUlMsIGd1aWRlPSJub25lIikgKwogIHNjYWxlX2xpbmV0eXBlX21hbnVhbCh2YWx1ZXM9cmVwKCJkYXNoZWQiLDcpLCBndWlkZT0ibm9uZSIpICsKICBjb29yZF9jYXJ0ZXNpYW4oeGxpbT1jKDAuNSw4LjUpKSArCiAgdGhlbWVfY2xhc3NpYygpICsKICBsYWJzKHg9IkNlbGwgbGluZSIsIHk9Ik1vbm9jbGUzIHBzZXVkb3RpbWUgKDDigJMxMDApIiwKICAgICAgIHRpdGxlPSJNb25vY2xlMyBwc2V1ZG90aW1lIHBlciBTw6l6YXJ5IGNlbGwgbGluZSIsCiAgICAgICBzdWJ0aXRsZT0iRGFzaGVkIGxpbmVzID0gcmVmZXJlbmNlIG1pbGVzdG9uZSBtZWRpYW5zIChNMDDigJNNMDYpIikgKwogIHRoZW1lKHBsb3QudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTMsZmFjZT0iYm9sZCIpKQoKcHJpbnQocF92aW9saW5fbTMpCmBgYAoKIyMgTWlsZXN0b25lIENvbXBvc2l0aW9uIHBlciBDZWxsIExpbmUKCmBgYHtyIHZpcy1taWxlc3RvbmUtYmFyLCBmaWcud2lkdGg9NywgZmlnLmhlaWdodD00fQptc19jb21wIDwtIHZpb2xpbl9kZiAlPiUKICBjb3VudChjZWxsX2xpbmUsIG1pbGVzdG9uZSkgJT4lCiAgZ3JvdXBfYnkoY2VsbF9saW5lKSAlPiUKICBtdXRhdGUocGN0ID0gMTAwICogbiAvIHN1bShuKSkgJT4lCiAgdW5ncm91cCgpICU+JQogIGZpbHRlcighaXMubmEobWlsZXN0b25lKSkgJT4lCiAgbXV0YXRlKG1pbGVzdG9uZSA9IGZhY3RvcihtaWxlc3RvbmUsIGxldmVscz1zcHJpbnRmKCJNJTAyZCIsMDo2KSkpCgpwX21zX2JhciA8LSBnZ3Bsb3QobXNfY29tcCwgYWVzKHg9Y2VsbF9saW5lLCB5PXBjdCwgZmlsbD1taWxlc3RvbmUpKSArCiAgZ2VvbV9jb2wod2lkdGg9Ljc1KSArCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPU1JTEVTVE9ORV9DT0xPUlMsCiAgICAgICAgICAgICAgICAgICAgbGFiZWxzPU1JTEVTVE9ORV9MQUJFTFMsIG5hbWU9Ik1pbGVzdG9uZSIpICsKICB0aGVtZV9jbGFzc2ljKCkgKwogIGxhYnMoeD0iQ2VsbCBsaW5lIiwgeT0iJSBjZWxscyIsCiAgICAgICB0aXRsZT0iTWlsZXN0b25lIGNvbXBvc2l0aW9uIHBlciBTw6l6YXJ5IGNlbGwgbGluZSIsCiAgICAgICBzdWJ0aXRsZT0iQmFzZWQgb24gdHJhbnNmZXJyZWQgbWlsZXN0b25lIGxhYmVscyBmcm9tIHJlZmVyZW5jZSIpICsKICB0aGVtZShwbG90LnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTEzLGZhY2U9ImJvbGQiKSwKICAgICAgICBsZWdlbmQudGV4dD1lbGVtZW50X3RleHQoc2l6ZT05KSkKCnByaW50KHBfbXNfYmFyKQpgYGAKCmBgYHtyIG1pbGVzdG9uZS1oZWF0bWFwLCBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9NX0KCiMgTWlsZXN0b25lIGxhYmVscyB3aXRoIGJpb2xvZ2ljYWwgYW5ub3RhdGlvbgpNSUxFU1RPTkVfTEFCRUxTX0ZVTEwgPC0gYygKICAiTTAwIiA9ICJNMDBcbk5haXZlIiwKICAiTTAxIiA9ICJNMDFcblRDTSBlYXJseSIsCiAgIk0wMiIgPSAiTTAyXG5UQ00gbGF0ZVxuKGJyYW5jaCkiLAogICJNMDMiID0gIk0wM1xuVEVNIiwKICAiTTA0IiA9ICJNMDRcblRlbXJhIiwKICAiTTA1IiA9ICJNMDVcblRyZWcocmVzdCkiLAogICJNMDYiID0gIk0wNlxuVHJlZyhlZmYpIgopCgojIENvbXB1dGUgbWlsZXN0b25lIGRpc3RyaWJ1dGlvbiBwZXIgY2VsbCBsaW5lCm1zX3Blcl9saW5lIDwtIHF1ZXJ5X3VtYXBfZGYgJT4lCiAgZmlsdGVyKCFpcy5uYShwcmVkaWN0ZWRfbWlsZXN0b25lKSkgJT4lCiAgY291bnQoY2VsbF9saW5lLCBwcmVkaWN0ZWRfbWlsZXN0b25lKSAlPiUKICBncm91cF9ieShjZWxsX2xpbmUpICU+JQogIG11dGF0ZShwY3QgPSByb3VuZCgxMDAgKiBuIC8gc3VtKG4pLCAxKSkgJT4lCiAgdW5ncm91cCgpICU+JQogIG11dGF0ZShwcmVkaWN0ZWRfbWlsZXN0b25lID0gZmFjdG9yKHByZWRpY3RlZF9taWxlc3RvbmUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGV2ZWxzID0gTUlMRVNUT05FX09SREVSKSkKCiMg4pSA4pSAIEhlYXRtYXAgdGlsZSBwbG90OiBjZWxsIGxpbmUgw5cgbWlsZXN0b25lIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgApwX21zX2hlYXRtYXAgPC0gZ2dwbG90KG1zX3Blcl9saW5lLAogICAgICAgICAgICAgICAgICAgICAgICBhZXMoeD1wcmVkaWN0ZWRfbWlsZXN0b25lLCB5PWNlbGxfbGluZSwgZmlsbD1wY3QpKSArCiAgZ2VvbV90aWxlKGNvbG91cj0id2hpdGUiLCBsaW5ld2lkdGg9LjUpICsKICBnZW9tX3RleHQoYWVzKGxhYmVsPWlmZWxzZShwY3QgPj0gMiwgcGFzdGUwKHBjdCwgIiUiKSwgIiIpKSwKICAgICAgICAgICAgc2l6ZT0zLjIsIGZvbnRmYWNlPSJib2xkIiwKICAgICAgICAgICAgY29sb3VyPWlmZWxzZShtc19wZXJfbGluZSRwY3QgPiA0MCwgIndoaXRlIiwgImdyZXkyMCIpKSArCiAgc2NhbGVfZmlsbF9ncmFkaWVudG4oCiAgICBjb2xvdXJzID0gYygiI0Y3RkJGRiIsIiNERUVCRjciLCIjOUVDQUUxIiwiIzQyOTJDNiIsIiMwODUxOUMiLCIjMDgzMDZCIiksCiAgICBuYW1lICAgID0gIiUgb2YgY2VsbFxubGluZSBjZWxscyIsCiAgICBsaW1pdHMgID0gYygwLCAxMDApCiAgKSArCiAgc2NhbGVfeF9kaXNjcmV0ZShsYWJlbHM9TUlMRVNUT05FX0xBQkVMU19GVUxMKSArCiAgdGhlbWVfY2xhc3NpYygpICsKICBsYWJzKHg9IlJlZmVyZW5jZSBtaWxlc3RvbmUiLCB5PSJTw6l6YXJ5IGNlbGwgbGluZSIsCiAgICAgICB0aXRsZT0iTWlsZXN0b25lIGRpc3RyaWJ1dGlvbiBwZXIgU8OpemFyeSBjZWxsIGxpbmUiLAogICAgICAgc3VidGl0bGU9IkRhcmtlciA9IG1vcmUgY2VsbHMgfCBNMDIgPSBicmFuY2ggcG9pbnQgYmV0d2VlbiBlZmZlY3RvciBhbmQgcmVndWxhdG9yeSBhcm1zIikgKwogIHRoZW1lKHBsb3QudGl0bGUgICA9IGVsZW1lbnRfdGV4dChzaXplPTEzLCBmYWNlPSJib2xkIiksCiAgICAgICAgcGxvdC5zdWJ0aXRsZT0gZWxlbWVudF90ZXh0KHNpemU9OSwgIGNvbG91cj0iZ3JleTQwIiksCiAgICAgICAgYXhpcy50ZXh0LnggID0gZWxlbWVudF90ZXh0KHNpemU9OSksCiAgICAgICAgYXhpcy50ZXh0LnkgID0gZWxlbWVudF90ZXh0KHNpemU9MTEsIGZhY2U9ImJvbGQiKSwKICAgICAgICBsZWdlbmQudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZT05KSkKCnByaW50KHBfbXNfaGVhdG1hcCkKCmdnc2F2ZSgiRmlndXJlcy9maWdfbWlsZXN0b25lX2hlYXRtYXAucGRmIiwgcF9tc19oZWF0bWFwLCB3aWR0aD0xMCwgaGVpZ2h0PTUpCmBgYAoKCgoKYGBge3IgbWlsZXN0b25lLXN0YWNrLCBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD01fQoKIyBDb2xvdXJzIHBlciBtaWxlc3RvbmUg4oCUIGRlcml2ZWQgZnJvbSBzdGF0ZSBjb2xvdXJzCm1zX3N0YXRlX2NvbG9ycyA8LSBzZXROYW1lcygKICBTVEFURV9DT0xPUlNbbWlsZXN0b25lX2NlbnRyb2lkcyRzdGF0ZVsKICAgIG1hdGNoKE1JTEVTVE9ORV9PUkRFUiwgbWlsZXN0b25lX2NlbnRyb2lkcyRtaWxlc3RvbmUpXV0sCiAgTUlMRVNUT05FX09SREVSCikKCnBfbXNfc3RhY2sgPC0gZ2dwbG90KG1zX3Blcl9saW5lLAogICAgICAgICAgICAgICAgICAgICAgYWVzKHg9Y2VsbF9saW5lLCB5PXBjdCwgZmlsbD1wcmVkaWN0ZWRfbWlsZXN0b25lKSkgKwogIGdlb21fY29sKHdpZHRoPS43NSwgY29sb3VyPSJ3aGl0ZSIsIGxpbmV3aWR0aD0uMykgKwogIGdlb21fdGV4dChhZXMobGFiZWw9aWZlbHNlKHBjdCA+PSA1LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhc3RlMChwcmVkaWN0ZWRfbWlsZXN0b25lLCAiXG4iLCBwY3QsICIlIiksICIiKSksCiAgICAgICAgICAgIHBvc2l0aW9uPXBvc2l0aW9uX3N0YWNrKHZqdXN0PTAuNSksCiAgICAgICAgICAgIHNpemU9Mi44LCBjb2xvdXI9IndoaXRlIiwgZm9udGZhY2U9ImJvbGQiKSArCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPW1zX3N0YXRlX2NvbG9ycywKICAgICAgICAgICAgICAgICAgICBsYWJlbHM9TUlMRVNUT05FX0xBQkVMU19GVUxMLAogICAgICAgICAgICAgICAgICAgIG5hbWU9Ik1pbGVzdG9uZSIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kPWMoMCwwKSwgbGltaXRzPWMoMCwxMDUpKSArCiAgdGhlbWVfY2xhc3NpYygpICsKICBsYWJzKHg9IkNlbGwgbGluZSIsIHk9IiUgb2YgY2VsbHMiLAogICAgICAgdGl0bGU9Ik1pbGVzdG9uZSBjb21wb3NpdGlvbiBwZXIgU8OpemFyeSBjZWxsIGxpbmUiLAogICAgICAgc3VidGl0bGU9IkNvbG91ciA9IENENCBzdGF0ZSB8IExhYmVsIHNob3duIGlmIOKJpSA1JSIpICsKICB0aGVtZShwbG90LnRpdGxlICA9IGVsZW1lbnRfdGV4dChzaXplPTEzLCBmYWNlPSJib2xkIiksCiAgICAgICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplPTksIGNvbG91cj0iZ3JleTQwIiksCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZT0xMiwgZmFjZT0iYm9sZCIpKQoKcHJpbnQocF9tc19zdGFjaykKCmdnc2F2ZSgiRmlndXJlcy9maWdfbWlsZXN0b25lX3N0YWNrLnBkZiIsIHBfbXNfc3RhY2ssIHdpZHRoPTgsIGhlaWdodD01KQoKYGBgCgoKCk5vdGU6IHRoaXMgY2h1bmsgbXVzdCBjb21lICoqYWZ0ZXIqKiB0aGUgYG1pbGVzdG9uZS1oZWF0bWFwYCBjaHVuayBzaW5jZSBpdCB1c2VzIGBtc19wZXJfbGluZWAgYW5kIGBNSUxFU1RPTkVfTEFCRUxTX0ZVTExgIGRlZmluZWQgdGhlcmUuCgoKCiMjIE1TVCB2cyBNb25vY2xlMyBQc2V1ZG90aW1lIHBlciBMaW5lCgpgYGB7ciB2aXMtcHQtY29ycmVsYXRpb24sIGZpZy53aWR0aD0xMiwgZmlnLmhlaWdodD04fQojIFNjYXR0ZXI6IE1TVCB2cyBNb25vY2xlMyBwZXIgY2VsbCBsaW5lIOKAlCBtZXRob2QgYWdyZWVtZW50IGNoZWNrCnBfcHRfY29yIDwtIGdncGxvdCh2aW9saW5fZGZbc2FtcGxlKG5yb3codmlvbGluX2RmKSksXSwKICAgICAgICAgICAgICAgICAgICBhZXMoeD1tc3RfcHQsIHk9bTNfcHQsIGNvbG91cj1taWxlc3RvbmUpKSArCiAgZ2VvbV9wb2ludChzaXplPS44LCBhbHBoYT0uNikgKwogIGdlb21fYWJsaW5lKHNsb3BlPTEsIGludGVyY2VwdD0wLCBsaW5ldHlwZT0iZGFzaGVkIiwgY29sb3VyPSJncmV5NDAiKSArCiAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXM9TUlMRVNUT05FX0NPTE9SUywKICAgICAgICAgICAgICAgICAgICAgIGxhYmVscz1NSUxFU1RPTkVfTEFCRUxTLCBuYW1lPSJNaWxlc3RvbmUiKSArCiAgZmFjZXRfd3JhcCh+Y2VsbF9saW5lLCBuY29sPTQpICsKICB0aGVtZV9jbGFzc2ljKCkgKwogIGxhYnMoeD0iTVNUIHBzZXVkb3RpbWUgKDDigJMxMDApIiwgeT0iTW9ub2NsZTMgcHNldWRvdGltZSAoMOKAkzEwMCkiLAogICAgICAgdGl0bGU9Ik1TVCB2cyBNb25vY2xlMyBwc2V1ZG90aW1lIHBlciBTw6l6YXJ5IGNlbGwgbGluZSIsCiAgICAgICBzdWJ0aXRsZT0iRGFzaGVkID0gcGVyZmVjdCBhZ3JlZW1lbnQiKSArCiAgdGhlbWUocGxvdC50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xMyxmYWNlPSJib2xkIiksCiAgICAgICAgc3RyaXAudGV4dD1lbGVtZW50X3RleHQoZmFjZT0iYm9sZCIpKQoKIyBDb21wdXRlIHBlci1saW5lIFNwZWFybWFuIHJobwpwdF9jb3JzIDwtIHZpb2xpbl9kZiAlPiUKICBncm91cF9ieShjZWxsX2xpbmUpICU+JQogIHN1bW1hcmlzZSgKICAgIHNwZWFybWFuID0gcm91bmQoY29yKG1zdF9wdCwgbTNfcHQsIG1ldGhvZD0ic3BlYXJtYW4iLCB1c2U9ImNvbXBsZXRlLm9icyIpLCAzKSwKICAgIG4gICAgICAgID0gbigpLAogICAgLmdyb3VwcyAgPSAiZHJvcCIKICApCgpwcmludChwX3B0X2NvcikKY2F0KCJcblBlci1saW5lIE1TVCB2cyBNb25vY2xlMyBTcGVhcm1hbiDPgTpcbiIpCnByaW50KHB0X2NvcnMpCgpgYGAKCiMjIFBlci1DZWxsLUxpbmUgSW5kaXZpZHVhbCBVTUFQcwoKYGBge3IgdmlzLXBlci1saW5lLWluZGl2aWR1YWwsIGZpZy53aWR0aD02LCBmaWcuaGVpZ2h0PTR9CiMgSW5kaXZpZHVhbCBVTUFQICsgcHNldWRvdGltZSBmb3IgZWFjaCBsaW5lIOKAlCBzYW1lIHN0eWxlIGFzIHByZXZpb3VzIHJlc3VsdHMKcHRfZ3JhZGllbnQgPC0gc2NhbGVfY29sb3VyX2dyYWRpZW50bigKICBjb2xvdXJzID0gYygiIzBEMDg4NyIsIiM2QTAwQTgiLCIjQjEyQTkwIiwiI0UxNjQ2MiIsIiNGQ0E2MzYiLCIjRjBGOTIxIiksCiAgbmFtZSAgICA9ICJNU1QgcHNldWRvdGltZVxuKDDigJMxMDApIiwgbGltaXRzID0gYygwLDEwMCksIG5hLnZhbHVlID0gImdyZXk4MCIKKQoKZm9yIChsbiBpbiBDRUxMX0xJTkVTKSB7CiAgcV9kZiA8LSBxdWVyeV91bWFwX2RmICU+JSBmaWx0ZXIoY2VsbF9saW5lID09IGxuKQoKICBwIDwtIGdncGxvdCgpICsKICAgIGdlb21fcG9pbnQoZGF0YT1yZWZfdW1hcF9kZiwgYWVzKHg9VU1BUF8xLHk9VU1BUF8yKSwKICAgICAgICAgICAgICAgY29sb3VyPSJncmV5NzgiLCBzaXplPS4yNSwgYWxwaGE9LjUpICsKICAgIGdlb21fc2VnbWVudChkYXRhPW1zdF9lZGdlX2RmLAogICAgICAgICAgICAgICAgIGFlcyh4PXgxLHk9eTEseGVuZD14Mix5ZW5kPXkyKSwKICAgICAgICAgICAgICAgICBjb2xvdXI9ImdyZXkzMCIsIGxpbmV3aWR0aD0uNywgYWxwaGE9LjcsIGxpbmVlbmQ9InJvdW5kIikgKwogICAgZ2VvbV9wb2ludChkYXRhPXFfZGZbc2FtcGxlKG5yb3cocV9kZikpLF0sCiAgICAgICAgICAgICAgIGFlcyh4PVVNQVBfMSx5PVVNQVBfMixjb2xvdXI9bXN0X3B0KSwgc2l6ZT0xLjUsIGFscGhhPS44NSkgKwogICAgcHRfZ3JhZGllbnQgKwogICAgIyBNaWxlc3RvbmUgbGFiZWxzCiAgICBnZW9tX3RleHRfcmVwZWwoZGF0YT1taWxlc3RvbmVfY2VudHJvaWRzLAogICAgICAgICAgICAgICAgICAgIGFlcyh4PVVNQVBfMSx5PVVNQVBfMixsYWJlbD1taWxlc3RvbmUpLAogICAgICAgICAgICAgICAgICAgIHNpemU9Mi44LCBmb250ZmFjZT0iYm9sZCIsIGNvbG91cj0iZ3JleTIwIiwKICAgICAgICAgICAgICAgICAgICBiZy5jb2xvcj0id2hpdGUiLCBiZy5yPS4xLCBtYXgub3ZlcmxhcHM9MTUpICsKICAgIHRoZW1lX2NsYXNzaWMoKSArCiAgICBsYWJzKHg9IlVNQVAtMSIseT0iVU1BUC0yIiwKICAgICAgICAgdGl0bGU9c3ByaW50ZigiJXMg4oCUIE1TVCBwc2V1ZG90aW1lIG9uIHJlZmVyZW5jZSBVTUFQIiwgbG4pLAogICAgICAgICBzdWJ0aXRsZT1zcHJpbnRmKCJuPSVkIGNlbGxzIHwgTVNUIG1lZGlhbj0lLjFmIiwKICAgICAgICAgICAgICAgICBucm93KHFfZGYpLAogICAgICAgICAgICAgICAgIG1lZGlhbihxX2RmJG1zdF9wdCwgbmEucm09VFJVRSkpKSAgKwogICAgdGhlbWUocGxvdC50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xMixmYWNlPSJib2xkIikpCgogIHByaW50KHApCn0KCmBgYAoKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgojIFBzZXVkb3RpbWUgU3VtbWFyeSBUYWJsZQoKYGBge3J9CiMgRnVsbCBwZXItbGluZSBzdW1tYXJ5IGluY2x1ZGluZyBtaWxlc3RvbmUgYnJlYWtkb3duCm1zX3dpZGUgPC0gbXNfY29tcCAlPiUKICBzZWxlY3QoY2VsbF9saW5lLCBtaWxlc3RvbmUsIHBjdCkgJT4lCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbT1taWxlc3RvbmUsIHZhbHVlc19mcm9tPXBjdCwgdmFsdWVzX2ZpbGw9MCkgJT4lCiAgbXV0YXRlKGFjcm9zcyh3aGVyZShpcy5udW1lcmljKSwgfnJvdW5kKC4sIDEpKSkKCmZ1bGxfc3VtbWFyeSA8LSB0cmFuc2Zlcl9zdW1tYXJ5ICU+JQogIGxlZnRfam9pbihtc193aWRlLCBieT0iY2VsbF9saW5lIikKCmthYmxlKGZ1bGxfc3VtbWFyeSwKICAgICAgY2FwdGlvbiA9ICJTw6l6YXJ5IGNlbGwgbGluZSBwcm9qZWN0aW9uIHN1bW1hcnkg4oCUIHBzZXVkb3RpbWUgYW5kIG1pbGVzdG9uZSBjb21wb3NpdGlvbiAoJSkiLAogICAgICBkaWdpdHMgID0gMSkgJT4lCiAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucz1jKCJzdHJpcGVkIiwiaG92ZXIiLCJjb25kZW5zZWQiKSwgZnVsbF93aWR0aD1GQUxTRSkgJT4lCiAgYWRkX2hlYWRlcl9hYm92ZShjKCIgIj1uY29sKHRyYW5zZmVyX3N1bW1hcnkpLAogICAgICAgICAgICAgICAgICAgICAiJSBjZWxscyBwZXIgbWlsZXN0b25lIj1uY29sKG1zX3dpZGUpLTEpKQpgYGAKCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKIyBTYXZlIE91dHB1dHMKCmBgYHtyIHNhdmUtb3V0cHV0c30KZGlyLmNyZWF0ZSgiT2JqZWN0cyIsIHNob3dXYXJuaW5ncz1GQUxTRSkKZGlyLmNyZWF0ZSgiRmlndXJlcyIsIHNob3dXYXJuaW5ncz1GQUxTRSkKZGlyLmNyZWF0ZSgiVGFibGVzIiwgIHNob3dXYXJuaW5ncz1GQUxTRSkKClNhdmVTZXVyYXRSZHMoc2V6YXJ5X29iaiwgIk9iamVjdHMvc2V6YXJ5X3Byb2plY3RlZC5yZHMiKQoKd3JpdGUuY3N2KGZ1bGxfc3VtbWFyeSwgIlRhYmxlcy9wcm9qZWN0aW9uX3N1bW1hcnlfcGVyX2xpbmUuY3N2IiwgICByb3cubmFtZXM9RkFMU0UpCndyaXRlLmNzdihtc19jb21wLCAgICAgICJUYWJsZXMvbWlsZXN0b25lX2NvbXBvc2l0aW9uX3Blcl9saW5lLmNzdiIsIHJvdy5uYW1lcz1GQUxTRSkKCmdnc2F2ZSgiRmlndXJlcy9maWdfYWxsX2xpbmVzX3VtYXAucGRmIiwgICAgICAgIHBfYWxsX2xpbmVzLCAgd2lkdGg9MTQsIGhlaWdodD0xMCkKZ2dzYXZlKCJGaWd1cmVzL2ZpZ192aW9saW5fbXN0LnBkZiIsICAgICAgICAgICAgcF92aW9saW5fbXN0LCB3aWR0aD0xMCwgaGVpZ2h0PTUpCmdnc2F2ZSgiRmlndXJlcy9maWdfdmlvbGluX20zLnBkZiIsICAgICAgICAgICAgIHBfdmlvbGluX20zLCAgd2lkdGg9MTAsIGhlaWdodD01KQpnZ3NhdmUoIkZpZ3VyZXMvZmlnX21pbGVzdG9uZV9jb21wb3NpdGlvbi5wZGYiLCBwX21zX2JhciwgICAgIHdpZHRoPTExLCBoZWlnaHQ9NSkKZ2dzYXZlKCJGaWd1cmVzL2ZpZ19wdF9jb3JyZWxhdGlvbi5wZGYiLCAgICAgICAgcF9wdF9jb3IsICAgICB3aWR0aD0xMiwgaGVpZ2h0PTgpCgpjYXQoIuKchSBBbGwgb3V0cHV0cyBzYXZlZFxuIikKYGBgCgpgYGB7ciBzZXNzaW9ufQoKc2Vzc2lvbkluZm8oKQoKYGBgCg==