1 load libraries

# Data Processing
library(dplyr)
library(Seurat)
library(tibble)
library(tidyr)
library(stringr)

# Visualization
library(ggplot2)
library(ComplexHeatmap)
library(patchwork)
library(SCpubr)

# Regulatory Network Inference
library(decoupleR)
library(dorothea)
data(dorothea_hs, package = "dorothea")
library(tictoc)

2 Load Seurat Object


# Load your Seurat Object
seurat_obj <- readRDS("Output_Objects/Seurat_Object_With_TF_Activity.rds")

Idents(seurat_obj) <- "seurat_clusters"
print("Object Loaded.")
[1] "Object Loaded."

2.1 Run this code block to restore activities instantly:


# If 'activities' is missing but 'dorothea' assay exists, reconstruct it:
if (!exists("activities") && "dorothea" %in% names(seurat_obj@assays)) {
  
  print("Reconstructing 'activities' dataframe from Seurat object...")
  
  # Extract the matrix (Seurat v5 uses 'layer' instead of 'slot')
  # Since you ran ScaleData, we use 'scale.data'
  mat <- GetAssayData(seurat_obj, assay = "dorothea", layer = "scale.data")
  
  # Convert to long format (what SCpubr needs)
  activities <- as.data.frame(mat) %>%
    rownames_to_column("source") %>%
    pivot_longer(cols = -source, names_to = "condition", values_to = "score") %>%
    mutate(statistic = "norm_wmean") # SCpubr requires this column
    
  print("Activities dataframe restored!")
}
[1] "Reconstructing 'activities' dataframe from Seurat object..."
[1] "Activities dataframe restored!"

2.2 SCpubr Heatmap Visualization-Heatmap of averaged scores

library(SCpubr)
# General heatmap (Top Variable TFs)
out <- SCpubr::do_TFActivityPlot(sample = seurat_obj,
                                 activities = activities)
p1 <- out$heatmaps$average_scores
print(p1)

# 1. Save as PDF
pdf("Output_Figures/SCpubr_Heatmap_Default.pdf", width = 10, height = 8)
print(p1) # ComplexHeatmap requires explicit print() inside pdf()
dev.off()
png 
  2 
# 2. Save as PNG
png("Output_Figures/SCpubr_Heatmap_Default.png", width = 10 * 300, height = 8 * 300, res = 300)
print(p1)
dev.off()
png 
  2 

2.3 Set the scale limits


out <- SCpubr::do_TFActivityPlot(sample = seurat_obj,
                                 activities = activities,
                                 min.cutoff = -1.5,
                                 max.cutoff = 1.5)
p2 <- out$heatmaps$average_scores
print(p2)

# Save ComplexHeatmap properly
pdf("Output_Figures/SCpubr_Heatmap_Scaled.pdf", width = 10, height = 8)
print(p2)
dev.off()
png 
  2 
png("Output_Figures/SCpubr_Heatmap_Scaled.png", width = 10 * 300, height = 8 * 300, res = 300)
print(p2)
dev.off()
png 
  2 

2.4 Enforce Symmetry (Best for Manuscript)


out <- SCpubr::do_TFActivityPlot(sample = seurat_obj,
                                 activities = activities,
                                 min.cutoff = -1.5,
                                 max.cutoff = 1.5,
                                 enforce_symmetry = TRUE)
p3 <- out$heatmaps$average_scores
print(p3)

pdf("Output_Figures/SCpubr_Heatmap_Symmetric.pdf", width = 10, height = 8)
print(p3)
dev.off()
png 
  2 
png("Output_Figures/SCpubr_Heatmap_Symmetric.png", width = 10 * 300, height = 8 * 300, res = 300)
print(p3)
dev.off()
png 
  2 

2.5 Top 40 TFs

out <- SCpubr::do_TFActivityPlot(sample = seurat_obj,
                                 activities = activities,
                                 n_tfs = 40)
p4 <- out$heatmaps$average_scores
print(p4)

pdf("Output_Figures/SCpubr_Heatmap_Top40.pdf", width = 14, height = 6)
print(p4)
dev.off()
png 
  2 
png("Output_Figures/SCpubr_Heatmap_Top40.png", width = 14 * 300, height = 6 * 300, res = 300)
print(p4)
dev.off()
png 
  2 

2.6 Top 100 TFs (Figure A for Manuscript)


out <- SCpubr::do_TFActivityPlot(sample = seurat_obj,
                                 activities = activities,
                                 n_tfs = 100)
p5 <- out$heatmaps$average_scores
print(p5)

pdf("Output_Figures/Figure_3.16A_Global_TF_Heatmap_Top100.pdf", width = 32, height = 12)
print(p5)
dev.off()
png 
  2 
png("Output_Figures/Figure_3.16A_Global_TF_Heatmap_Top100.png", width = 32 * 300, height = 12 * 300, res = 300)
print(p5)
dev.off()
png 
  2 

3 Differential TF Activity (Malignant vs. Normal)


# Define Comparison: Clusters 3 & 10 (Normal) vs Rest (Malignant)
non_malignant_clusters <- c(3, 10)
seurat_obj$Condition <- ifelse(seurat_obj$seurat_clusters %in% non_malignant_clusters, "Non-Malignant", "Malignant")

# Perform Differential Analysis on TF Activity
DefaultAssay(seurat_obj) <- "dorothea"
Idents(seurat_obj) <- "Condition"

print("Running FindMarkers on TF Activity...")
[1] "Running FindMarkers on TF Activity..."
diff_tfs <- FindMarkers(seurat_obj, 
                        ident.1 = "Malignant", 
                        ident.2 = "Non-Malignant", 
                        logfc.threshold = 0, # Get all for volcano
                        min.pct = 0)

# Add gene column for labeling
diff_tfs$gene <- rownames(diff_tfs)

# Save Results
write.csv(diff_tfs, "Output_Tables/Differential_TF_Activity_Malignant_vs_Normal.csv")
print("Differential analysis complete.")
[1] "Differential analysis complete."

4 Figure C: Volcano Plot (Loss of Homeostasis)

# Highlight key drivers mentioned in text
highlight_tfs <- c("FOXO1", "MYC", "E2F1", "E2F4", "FOXM1", "RELA", "IRF1", "STAT1")

p_volcano <- SCpubr::do_VolcanoPlot(sample = seurat_obj,
                                    de_genes = diff_tfs
                                   )

ggsave("Output_Figures/Figure_3.16C_Volcano_TF_Activity.pdf", plot = p_volcano, width = 8, height = 6)
ggsave("Output_Figures/Figure_3.16C_Volcano_TF_Activity.png", plot = p_volcano, width = 8, height = 6, dpi = 300)
print(p_volcano)

5 Updated Figure C: EnhancedVolcano

library(EnhancedVolcano)

# Highlight key drivers mentioned in text
highlight_tfs <- c("FOXO1", "MYC", "E2F1", "E2F4", "FOXM1", "RELA", "IRF1", "STAT1", "TOX", "GATA3")

# Create the EnhancedVolcano Plot
p_volcano <- EnhancedVolcano(diff_tfs,
    lab = rownames(diff_tfs),
    x = 'avg_log2FC',
    y = 'p_val_adj',
    
    title = 'Differential TF Activity: Malignant vs. Non-Malignant',
    subtitle = 'DecoupleR Inferred Activity',
    pCutoff = 1e-5,
    FCcutoff = 0.5,
    pointSize = 3.0,
    labSize = 5.0,
    colAlpha = 0.8,
    legendPosition = 'right',
    legendLabSize = 12,
    legendIconSize = 4.0,
    drawConnectors = TRUE, # Draw lines to labels to avoid overlap
    widthConnectors = 0.5,
    colConnectors = 'grey30',
    # Custom Colors: Down (Blue), Up (Red), NS (Grey)
    col = c("grey30", "forestgreen", "royalblue", "firebrick2")
)

# Print
print(p_volcano)


# Save
ggsave("Output_Figures/Figure_3.16C_EnhancedVolcano_TF_Activity.pdf", plot = p_volcano, width = 10, height = 8)
ggsave("Output_Figures/Figure_3.16C_EnhancedVolcano_TF_Activity.png", plot = p_volcano, width = 10, height = 8, dpi = 300)

6 Figure D: Mixed Feature Plots (Activity vs Expression)


# We manually construct this to mix Assays

# Part 1: TF Activity Plots (Assay: dorothea)
DefaultAssay(seurat_obj) <- "dorothea"

p1 <- FeaturePlot(seurat_obj, features = "FOXO1", order = T, reduction = "umap") + 
      scale_color_gradientn(colors = c("grey90", "firebrick")) + ggtitle("FOXO1 Activity (Homeostasis)")
p2 <- FeaturePlot(seurat_obj, features = "RELA", order = T, reduction = "umap") + 
      scale_color_gradientn(colors = c("grey90", "firebrick")) + ggtitle("RELA Activity (Inflammatory)")
p3 <- FeaturePlot(seurat_obj, features = "IRF1", order = T, reduction = "umap") + 
      scale_color_gradientn(colors = c("grey90", "firebrick")) + ggtitle("IRF1 Activity (IFN-Response)")
p4 <- FeaturePlot(seurat_obj, features = "FOXM1", order = T, reduction = "umap") + 
      scale_color_gradientn(colors = c("grey90", "firebrick")) + ggtitle("FOXM1 Activity (Proliferation)")

# Part 2: Gene Expression Plots (Assay: SCT/RNA)
DefaultAssay(seurat_obj) <- "SCT"

p5 <- FeaturePlot(seurat_obj, features = "HMGA2", order = T, reduction = "umap") + 
      scale_color_gradientn(colors = c("grey90", "darkblue")) + ggtitle("HMGA2 Expression (Stem-like)")
p6 <- FeaturePlot(seurat_obj, features = "SOX4", order = T, reduction = "umap") + 
      scale_color_gradientn(colors = c("grey90", "darkblue")) + ggtitle("SOX4 Expression (Stem-like)")

# Combine
final_figure_D <- (p1 | p2 | p3) / (p4 | p5 | p6) + 
                  plot_annotation(title = "Figure 3.16D: Key Drivers (Red=Activity, Blue=Expression)")

ggsave("Output_Figures/Figure_3.16D_Mixed_Features.pdf", plot = final_figure_D, width = 14, height = 10)
ggsave("Output_Figures/Figure_3.16D_Mixed_Features.png", plot = final_figure_D, width = 14, height = 10, dpi = 300)
print(final_figure_D)

7 Figure E (ComplexHeatmap) chunk

8 Final Save

print("Analysis pipeline complete. All figures and objects saved in Output_Figures folder.")
[1] "Analysis pipeline complete. All figures and objects saved in Output_Figures folder."
LS0tCnRpdGxlOiAiVEYgQWN0aXZpdHkgSW5mZXJlbmNlIEFuYWx5c2lzIChEZWNvdXBsZVIgKyBEb1JvdGhFQSkgVmlzdWFsaXphdGlvbiIKYXV0aG9yOiAiTmFzaXIgTWFobW9vZCBBYmJhc2kiCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OgogICAgICBjb2xsYXBzZWQ6IHRydWUKICAgIHRoZW1lOiBqb3VybmFsCi0tLQoKCiMgbG9hZCBsaWJyYXJpZXMKYGBge3Igc2V0dXAsIGluY2x1ZGU9VFJVRX0KIyBEYXRhIFByb2Nlc3NpbmcKbGlicmFyeShkcGx5cikKbGlicmFyeShTZXVyYXQpCmxpYnJhcnkodGliYmxlKQpsaWJyYXJ5KHRpZHlyKQpsaWJyYXJ5KHN0cmluZ3IpCgojIFZpc3VhbGl6YXRpb24KbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KENvbXBsZXhIZWF0bWFwKQpsaWJyYXJ5KHBhdGNod29yaykKbGlicmFyeShTQ3B1YnIpCgojIFJlZ3VsYXRvcnkgTmV0d29yayBJbmZlcmVuY2UKbGlicmFyeShkZWNvdXBsZVIpCmxpYnJhcnkoZG9yb3RoZWEpCmRhdGEoZG9yb3RoZWFfaHMsIHBhY2thZ2UgPSAiZG9yb3RoZWEiKQpsaWJyYXJ5KHRpY3RvYykKCgpgYGAKCiMgTG9hZCBTZXVyYXQgT2JqZWN0IApgYGB7cn0KCiMgTG9hZCB5b3VyIFNldXJhdCBPYmplY3QKc2V1cmF0X29iaiA8LSByZWFkUkRTKCJPdXRwdXRfT2JqZWN0cy9TZXVyYXRfT2JqZWN0X1dpdGhfVEZfQWN0aXZpdHkucmRzIikKCklkZW50cyhzZXVyYXRfb2JqKSA8LSAic2V1cmF0X2NsdXN0ZXJzIgpwcmludCgiT2JqZWN0IExvYWRlZC4iKQpgYGAKCiMjIFJ1biB0aGlzIGNvZGUgYmxvY2sgdG8gcmVzdG9yZSBhY3Rpdml0aWVzIGluc3RhbnRseToKYGBge3J9CgojIElmICdhY3Rpdml0aWVzJyBpcyBtaXNzaW5nIGJ1dCAnZG9yb3RoZWEnIGFzc2F5IGV4aXN0cywgcmVjb25zdHJ1Y3QgaXQ6CmlmICghZXhpc3RzKCJhY3Rpdml0aWVzIikgJiYgImRvcm90aGVhIiAlaW4lIG5hbWVzKHNldXJhdF9vYmpAYXNzYXlzKSkgewogIAogIHByaW50KCJSZWNvbnN0cnVjdGluZyAnYWN0aXZpdGllcycgZGF0YWZyYW1lIGZyb20gU2V1cmF0IG9iamVjdC4uLiIpCiAgCiAgIyBFeHRyYWN0IHRoZSBtYXRyaXggKFNldXJhdCB2NSB1c2VzICdsYXllcicgaW5zdGVhZCBvZiAnc2xvdCcpCiAgIyBTaW5jZSB5b3UgcmFuIFNjYWxlRGF0YSwgd2UgdXNlICdzY2FsZS5kYXRhJwogIG1hdCA8LSBHZXRBc3NheURhdGEoc2V1cmF0X29iaiwgYXNzYXkgPSAiZG9yb3RoZWEiLCBsYXllciA9ICJzY2FsZS5kYXRhIikKICAKICAjIENvbnZlcnQgdG8gbG9uZyBmb3JtYXQgKHdoYXQgU0NwdWJyIG5lZWRzKQogIGFjdGl2aXRpZXMgPC0gYXMuZGF0YS5mcmFtZShtYXQpICU+JQogICAgcm93bmFtZXNfdG9fY29sdW1uKCJzb3VyY2UiKSAlPiUKICAgIHBpdm90X2xvbmdlcihjb2xzID0gLXNvdXJjZSwgbmFtZXNfdG8gPSAiY29uZGl0aW9uIiwgdmFsdWVzX3RvID0gInNjb3JlIikgJT4lCiAgICBtdXRhdGUoc3RhdGlzdGljID0gIm5vcm1fd21lYW4iKSAjIFNDcHViciByZXF1aXJlcyB0aGlzIGNvbHVtbgogICAgCiAgcHJpbnQoIkFjdGl2aXRpZXMgZGF0YWZyYW1lIHJlc3RvcmVkISIpCn0KYGBgCiMjIFNDcHViciBIZWF0bWFwIFZpc3VhbGl6YXRpb24tSGVhdG1hcCBvZiBhdmVyYWdlZCBzY29yZXMKYGBge3IsIGZpZy5oZWlnaHQ9OCwgZmlnLndpZHRoPTEwfQpsaWJyYXJ5KFNDcHVicikKIyBHZW5lcmFsIGhlYXRtYXAgKFRvcCBWYXJpYWJsZSBURnMpCm91dCA8LSBTQ3B1YnI6OmRvX1RGQWN0aXZpdHlQbG90KHNhbXBsZSA9IHNldXJhdF9vYmosCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFjdGl2aXRpZXMgPSBhY3Rpdml0aWVzKQpwMSA8LSBvdXQkaGVhdG1hcHMkYXZlcmFnZV9zY29yZXMKcHJpbnQocDEpCgojIDEuIFNhdmUgYXMgUERGCnBkZigiT3V0cHV0X0ZpZ3VyZXMvU0NwdWJyX0hlYXRtYXBfRGVmYXVsdC5wZGYiLCB3aWR0aCA9IDEwLCBoZWlnaHQgPSA4KQpwcmludChwMSkgIyBDb21wbGV4SGVhdG1hcCByZXF1aXJlcyBleHBsaWNpdCBwcmludCgpIGluc2lkZSBwZGYoKQpkZXYub2ZmKCkKCiMgMi4gU2F2ZSBhcyBQTkcKcG5nKCJPdXRwdXRfRmlndXJlcy9TQ3B1YnJfSGVhdG1hcF9EZWZhdWx0LnBuZyIsIHdpZHRoID0gMTAgKiAzMDAsIGhlaWdodCA9IDggKiAzMDAsIHJlcyA9IDMwMCkKcHJpbnQocDEpCmRldi5vZmYoKQpgYGAKCgojIyBTZXQgdGhlIHNjYWxlIGxpbWl0cwpgYGB7ciwgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9MTB9CgpvdXQgPC0gU0NwdWJyOjpkb19URkFjdGl2aXR5UGxvdChzYW1wbGUgPSBzZXVyYXRfb2JqLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhY3Rpdml0aWVzID0gYWN0aXZpdGllcywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWluLmN1dG9mZiA9IC0xLjUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1heC5jdXRvZmYgPSAxLjUpCnAyIDwtIG91dCRoZWF0bWFwcyRhdmVyYWdlX3Njb3JlcwpwcmludChwMikKCiMgU2F2ZSBDb21wbGV4SGVhdG1hcCBwcm9wZXJseQpwZGYoIk91dHB1dF9GaWd1cmVzL1NDcHVicl9IZWF0bWFwX1NjYWxlZC5wZGYiLCB3aWR0aCA9IDEwLCBoZWlnaHQgPSA4KQpwcmludChwMikKZGV2Lm9mZigpCgpwbmcoIk91dHB1dF9GaWd1cmVzL1NDcHVicl9IZWF0bWFwX1NjYWxlZC5wbmciLCB3aWR0aCA9IDEwICogMzAwLCBoZWlnaHQgPSA4ICogMzAwLCByZXMgPSAzMDApCnByaW50KHAyKQpkZXYub2ZmKCkKCgpgYGAKCiMjIEVuZm9yY2UgU3ltbWV0cnkgKEJlc3QgZm9yIE1hbnVzY3JpcHQpCmBgYHtyLCBmaWcuaGVpZ2h0PTgsIGZpZy53aWR0aD0xMH0KCm91dCA8LSBTQ3B1YnI6OmRvX1RGQWN0aXZpdHlQbG90KHNhbXBsZSA9IHNldXJhdF9vYmosCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFjdGl2aXRpZXMgPSBhY3Rpdml0aWVzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtaW4uY3V0b2ZmID0gLTEuNSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4LmN1dG9mZiA9IDEuNSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZW5mb3JjZV9zeW1tZXRyeSA9IFRSVUUpCnAzIDwtIG91dCRoZWF0bWFwcyRhdmVyYWdlX3Njb3JlcwpwcmludChwMykKCnBkZigiT3V0cHV0X0ZpZ3VyZXMvU0NwdWJyX0hlYXRtYXBfU3ltbWV0cmljLnBkZiIsIHdpZHRoID0gMTAsIGhlaWdodCA9IDgpCnByaW50KHAzKQpkZXYub2ZmKCkKCnBuZygiT3V0cHV0X0ZpZ3VyZXMvU0NwdWJyX0hlYXRtYXBfU3ltbWV0cmljLnBuZyIsIHdpZHRoID0gMTAgKiAzMDAsIGhlaWdodCA9IDggKiAzMDAsIHJlcyA9IDMwMCkKcHJpbnQocDMpCmRldi5vZmYoKQoKYGBgCgojIyBUb3AgNDAgVEZzCmBgYHtyLCBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD0xNH0Kb3V0IDwtIFNDcHVicjo6ZG9fVEZBY3Rpdml0eVBsb3Qoc2FtcGxlID0gc2V1cmF0X29iaiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWN0aXZpdGllcyA9IGFjdGl2aXRpZXMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5fdGZzID0gNDApCnA0IDwtIG91dCRoZWF0bWFwcyRhdmVyYWdlX3Njb3JlcwpwcmludChwNCkKCnBkZigiT3V0cHV0X0ZpZ3VyZXMvU0NwdWJyX0hlYXRtYXBfVG9wNDAucGRmIiwgd2lkdGggPSAxNCwgaGVpZ2h0ID0gNikKcHJpbnQocDQpCmRldi5vZmYoKQoKcG5nKCJPdXRwdXRfRmlndXJlcy9TQ3B1YnJfSGVhdG1hcF9Ub3A0MC5wbmciLCB3aWR0aCA9IDE0ICogMzAwLCBoZWlnaHQgPSA2ICogMzAwLCByZXMgPSAzMDApCnByaW50KHA0KQpkZXYub2ZmKCkKYGBgCgoKIyMgVG9wIDEwMCBURnMgKEZpZ3VyZSBBIGZvciBNYW51c2NyaXB0KQpgYGB7ciwgZmlnLmhlaWdodD0xMiwgZmlnLndpZHRoPTMyfQoKb3V0IDwtIFNDcHVicjo6ZG9fVEZBY3Rpdml0eVBsb3Qoc2FtcGxlID0gc2V1cmF0X29iaiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWN0aXZpdGllcyA9IGFjdGl2aXRpZXMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5fdGZzID0gMTAwKQpwNSA8LSBvdXQkaGVhdG1hcHMkYXZlcmFnZV9zY29yZXMKcHJpbnQocDUpCgpwZGYoIk91dHB1dF9GaWd1cmVzL0ZpZ3VyZV8zLjE2QV9HbG9iYWxfVEZfSGVhdG1hcF9Ub3AxMDAucGRmIiwgd2lkdGggPSAzMiwgaGVpZ2h0ID0gMTIpCnByaW50KHA1KQpkZXYub2ZmKCkKCnBuZygiT3V0cHV0X0ZpZ3VyZXMvRmlndXJlXzMuMTZBX0dsb2JhbF9URl9IZWF0bWFwX1RvcDEwMC5wbmciLCB3aWR0aCA9IDMyICogMzAwLCBoZWlnaHQgPSAxMiAqIDMwMCwgcmVzID0gMzAwKQpwcmludChwNSkKZGV2Lm9mZigpCmBgYAoKCiMgRGlmZmVyZW50aWFsIFRGIEFjdGl2aXR5IChNYWxpZ25hbnQgdnMuIE5vcm1hbCkKYGBge3IsIGZpZy5oZWlnaHQ9MTIsIGZpZy53aWR0aD0xNn0KCiMgRGVmaW5lIENvbXBhcmlzb246IENsdXN0ZXJzIDMgJiAxMCAoTm9ybWFsKSB2cyBSZXN0IChNYWxpZ25hbnQpCm5vbl9tYWxpZ25hbnRfY2x1c3RlcnMgPC0gYygzLCAxMCkKc2V1cmF0X29iaiRDb25kaXRpb24gPC0gaWZlbHNlKHNldXJhdF9vYmokc2V1cmF0X2NsdXN0ZXJzICVpbiUgbm9uX21hbGlnbmFudF9jbHVzdGVycywgIk5vbi1NYWxpZ25hbnQiLCAiTWFsaWduYW50IikKCiMgUGVyZm9ybSBEaWZmZXJlbnRpYWwgQW5hbHlzaXMgb24gVEYgQWN0aXZpdHkKRGVmYXVsdEFzc2F5KHNldXJhdF9vYmopIDwtICJkb3JvdGhlYSIKSWRlbnRzKHNldXJhdF9vYmopIDwtICJDb25kaXRpb24iCgpwcmludCgiUnVubmluZyBGaW5kTWFya2VycyBvbiBURiBBY3Rpdml0eS4uLiIpCmRpZmZfdGZzIDwtIEZpbmRNYXJrZXJzKHNldXJhdF9vYmosIAogICAgICAgICAgICAgICAgICAgICAgICBpZGVudC4xID0gIk1hbGlnbmFudCIsIAogICAgICAgICAgICAgICAgICAgICAgICBpZGVudC4yID0gIk5vbi1NYWxpZ25hbnQiLCAKICAgICAgICAgICAgICAgICAgICAgICAgbG9nZmMudGhyZXNob2xkID0gMCwgIyBHZXQgYWxsIGZvciB2b2xjYW5vCiAgICAgICAgICAgICAgICAgICAgICAgIG1pbi5wY3QgPSAwKQoKIyBBZGQgZ2VuZSBjb2x1bW4gZm9yIGxhYmVsaW5nCmRpZmZfdGZzJGdlbmUgPC0gcm93bmFtZXMoZGlmZl90ZnMpCgojIFNhdmUgUmVzdWx0cwp3cml0ZS5jc3YoZGlmZl90ZnMsICJPdXRwdXRfVGFibGVzL0RpZmZlcmVudGlhbF9URl9BY3Rpdml0eV9NYWxpZ25hbnRfdnNfTm9ybWFsLmNzdiIpCnByaW50KCJEaWZmZXJlbnRpYWwgYW5hbHlzaXMgY29tcGxldGUuIikKCmBgYAoKCiMgRmlndXJlIEM6IFZvbGNhbm8gUGxvdCAoTG9zcyBvZiBIb21lb3N0YXNpcykKYGBge3IsIGZpZy5oZWlnaHQ9NiwgZmlnLndpZHRoPTh9CiMgSGlnaGxpZ2h0IGtleSBkcml2ZXJzIG1lbnRpb25lZCBpbiB0ZXh0CmhpZ2hsaWdodF90ZnMgPC0gYygiRk9YTzEiLCAiTVlDIiwgIkUyRjEiLCAiRTJGNCIsICJGT1hNMSIsICJSRUxBIiwgIklSRjEiLCAiU1RBVDEiKQoKcF92b2xjYW5vIDwtIFNDcHVicjo6ZG9fVm9sY2Fub1Bsb3Qoc2FtcGxlID0gc2V1cmF0X29iaiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVfZ2VuZXMgPSBkaWZmX3RmcwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkKCmdnc2F2ZSgiT3V0cHV0X0ZpZ3VyZXMvRmlndXJlXzMuMTZDX1ZvbGNhbm9fVEZfQWN0aXZpdHkucGRmIiwgcGxvdCA9IHBfdm9sY2Fubywgd2lkdGggPSA4LCBoZWlnaHQgPSA2KQpnZ3NhdmUoIk91dHB1dF9GaWd1cmVzL0ZpZ3VyZV8zLjE2Q19Wb2xjYW5vX1RGX0FjdGl2aXR5LnBuZyIsIHBsb3QgPSBwX3ZvbGNhbm8sIHdpZHRoID0gOCwgaGVpZ2h0ID0gNiwgZHBpID0gMzAwKQpwcmludChwX3ZvbGNhbm8pCmBgYAoKCiMgVXBkYXRlZCBGaWd1cmUgQzogRW5oYW5jZWRWb2xjYW5vCmBgYHtyLCBmaWcuaGVpZ2h0PTEyLCBmaWcud2lkdGg9MTZ9CmxpYnJhcnkoRW5oYW5jZWRWb2xjYW5vKQoKIyBIaWdobGlnaHQga2V5IGRyaXZlcnMgbWVudGlvbmVkIGluIHRleHQKaGlnaGxpZ2h0X3RmcyA8LSBjKCJGT1hPMSIsICJNWUMiLCAiRTJGMSIsICJFMkY0IiwgIkZPWE0xIiwgIlJFTEEiLCAiSVJGMSIsICJTVEFUMSIsICJUT1giLCAiR0FUQTMiKQoKIyBDcmVhdGUgdGhlIEVuaGFuY2VkVm9sY2FubyBQbG90CnBfdm9sY2FubyA8LSBFbmhhbmNlZFZvbGNhbm8oZGlmZl90ZnMsCiAgICBsYWIgPSByb3duYW1lcyhkaWZmX3RmcyksCiAgICB4ID0gJ2F2Z19sb2cyRkMnLAogICAgeSA9ICdwX3ZhbF9hZGonLAogICAgCiAgICB0aXRsZSA9ICdEaWZmZXJlbnRpYWwgVEYgQWN0aXZpdHk6IE1hbGlnbmFudCB2cy4gTm9uLU1hbGlnbmFudCcsCiAgICBzdWJ0aXRsZSA9ICdEZWNvdXBsZVIgSW5mZXJyZWQgQWN0aXZpdHknLAogICAgcEN1dG9mZiA9IDFlLTUsCiAgICBGQ2N1dG9mZiA9IDAuNSwKICAgIHBvaW50U2l6ZSA9IDMuMCwKICAgIGxhYlNpemUgPSA1LjAsCiAgICBjb2xBbHBoYSA9IDAuOCwKICAgIGxlZ2VuZFBvc2l0aW9uID0gJ3JpZ2h0JywKICAgIGxlZ2VuZExhYlNpemUgPSAxMiwKICAgIGxlZ2VuZEljb25TaXplID0gNC4wLAogICAgZHJhd0Nvbm5lY3RvcnMgPSBUUlVFLCAjIERyYXcgbGluZXMgdG8gbGFiZWxzIHRvIGF2b2lkIG92ZXJsYXAKICAgIHdpZHRoQ29ubmVjdG9ycyA9IDAuNSwKICAgIGNvbENvbm5lY3RvcnMgPSAnZ3JleTMwJywKICAgICMgQ3VzdG9tIENvbG9yczogRG93biAoQmx1ZSksIFVwIChSZWQpLCBOUyAoR3JleSkKICAgIGNvbCA9IGMoImdyZXkzMCIsICJmb3Jlc3RncmVlbiIsICJyb3lhbGJsdWUiLCAiZmlyZWJyaWNrMiIpCikKCiMgUHJpbnQKcHJpbnQocF92b2xjYW5vKQoKIyBTYXZlCmdnc2F2ZSgiT3V0cHV0X0ZpZ3VyZXMvRmlndXJlXzMuMTZDX0VuaGFuY2VkVm9sY2Fub19URl9BY3Rpdml0eS5wZGYiLCBwbG90ID0gcF92b2xjYW5vLCB3aWR0aCA9IDEwLCBoZWlnaHQgPSA4KQpnZ3NhdmUoIk91dHB1dF9GaWd1cmVzL0ZpZ3VyZV8zLjE2Q19FbmhhbmNlZFZvbGNhbm9fVEZfQWN0aXZpdHkucG5nIiwgcGxvdCA9IHBfdm9sY2Fubywgd2lkdGggPSAxMCwgaGVpZ2h0ID0gOCwgZHBpID0gMzAwKQpgYGAKCgoKCgoKCgojIEZpZ3VyZSBEOiBNaXhlZCBGZWF0dXJlIFBsb3RzIChBY3Rpdml0eSB2cyBFeHByZXNzaW9uKQpgYGB7ciwgZmlnLmhlaWdodD0xMiwgZmlnLndpZHRoPTE2fQoKIyBXZSBtYW51YWxseSBjb25zdHJ1Y3QgdGhpcyB0byBtaXggQXNzYXlzCgojIFBhcnQgMTogVEYgQWN0aXZpdHkgUGxvdHMgKEFzc2F5OiBkb3JvdGhlYSkKRGVmYXVsdEFzc2F5KHNldXJhdF9vYmopIDwtICJkb3JvdGhlYSIKCnAxIDwtIEZlYXR1cmVQbG90KHNldXJhdF9vYmosIGZlYXR1cmVzID0gIkZPWE8xIiwgb3JkZXIgPSBULCByZWR1Y3Rpb24gPSAidW1hcCIpICsgCiAgICAgIHNjYWxlX2NvbG9yX2dyYWRpZW50bihjb2xvcnMgPSBjKCJncmV5OTAiLCAiZmlyZWJyaWNrIikpICsgZ2d0aXRsZSgiRk9YTzEgQWN0aXZpdHkgKEhvbWVvc3Rhc2lzKSIpCnAyIDwtIEZlYXR1cmVQbG90KHNldXJhdF9vYmosIGZlYXR1cmVzID0gIlJFTEEiLCBvcmRlciA9IFQsIHJlZHVjdGlvbiA9ICJ1bWFwIikgKyAKICAgICAgc2NhbGVfY29sb3JfZ3JhZGllbnRuKGNvbG9ycyA9IGMoImdyZXk5MCIsICJmaXJlYnJpY2siKSkgKyBnZ3RpdGxlKCJSRUxBIEFjdGl2aXR5IChJbmZsYW1tYXRvcnkpIikKcDMgPC0gRmVhdHVyZVBsb3Qoc2V1cmF0X29iaiwgZmVhdHVyZXMgPSAiSVJGMSIsIG9yZGVyID0gVCwgcmVkdWN0aW9uID0gInVtYXAiKSArIAogICAgICBzY2FsZV9jb2xvcl9ncmFkaWVudG4oY29sb3JzID0gYygiZ3JleTkwIiwgImZpcmVicmljayIpKSArIGdndGl0bGUoIklSRjEgQWN0aXZpdHkgKElGTi1SZXNwb25zZSkiKQpwNCA8LSBGZWF0dXJlUGxvdChzZXVyYXRfb2JqLCBmZWF0dXJlcyA9ICJGT1hNMSIsIG9yZGVyID0gVCwgcmVkdWN0aW9uID0gInVtYXAiKSArIAogICAgICBzY2FsZV9jb2xvcl9ncmFkaWVudG4oY29sb3JzID0gYygiZ3JleTkwIiwgImZpcmVicmljayIpKSArIGdndGl0bGUoIkZPWE0xIEFjdGl2aXR5IChQcm9saWZlcmF0aW9uKSIpCgojIFBhcnQgMjogR2VuZSBFeHByZXNzaW9uIFBsb3RzIChBc3NheTogU0NUL1JOQSkKRGVmYXVsdEFzc2F5KHNldXJhdF9vYmopIDwtICJTQ1QiCgpwNSA8LSBGZWF0dXJlUGxvdChzZXVyYXRfb2JqLCBmZWF0dXJlcyA9ICJITUdBMiIsIG9yZGVyID0gVCwgcmVkdWN0aW9uID0gInVtYXAiKSArIAogICAgICBzY2FsZV9jb2xvcl9ncmFkaWVudG4oY29sb3JzID0gYygiZ3JleTkwIiwgImRhcmtibHVlIikpICsgZ2d0aXRsZSgiSE1HQTIgRXhwcmVzc2lvbiAoU3RlbS1saWtlKSIpCnA2IDwtIEZlYXR1cmVQbG90KHNldXJhdF9vYmosIGZlYXR1cmVzID0gIlNPWDQiLCBvcmRlciA9IFQsIHJlZHVjdGlvbiA9ICJ1bWFwIikgKyAKICAgICAgc2NhbGVfY29sb3JfZ3JhZGllbnRuKGNvbG9ycyA9IGMoImdyZXk5MCIsICJkYXJrYmx1ZSIpKSArIGdndGl0bGUoIlNPWDQgRXhwcmVzc2lvbiAoU3RlbS1saWtlKSIpCgojIENvbWJpbmUKZmluYWxfZmlndXJlX0QgPC0gKHAxIHwgcDIgfCBwMykgLyAocDQgfCBwNSB8IHA2KSArIAogICAgICAgICAgICAgICAgICBwbG90X2Fubm90YXRpb24odGl0bGUgPSAiRmlndXJlIDMuMTZEOiBLZXkgRHJpdmVycyAoUmVkPUFjdGl2aXR5LCBCbHVlPUV4cHJlc3Npb24pIikKCmdnc2F2ZSgiT3V0cHV0X0ZpZ3VyZXMvRmlndXJlXzMuMTZEX01peGVkX0ZlYXR1cmVzLnBkZiIsIHBsb3QgPSBmaW5hbF9maWd1cmVfRCwgd2lkdGggPSAxNCwgaGVpZ2h0ID0gMTApCmdnc2F2ZSgiT3V0cHV0X0ZpZ3VyZXMvRmlndXJlXzMuMTZEX01peGVkX0ZlYXR1cmVzLnBuZyIsIHBsb3QgPSBmaW5hbF9maWd1cmVfRCwgd2lkdGggPSAxNCwgaGVpZ2h0ID0gMTAsIGRwaSA9IDMwMCkKcHJpbnQoZmluYWxfZmlndXJlX0QpCmBgYAoKCgoKIyBGaWd1cmUgRSAoQ29tcGxleEhlYXRtYXApIGNodW5rCmBgYHtyLCBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD0xMH0KCmxpYnJhcnkoQ29tcGxleEhlYXRtYXApCmxpYnJhcnkoY2lyY2xpemUpCmxpYnJhcnkoTWF0cml4KQoKIyBDdXJhdGVkIGxpc3Qgb2YgU2V6YXJ5IGRyaXZlcnMKbGl0ZXJhdHVyZV90ZnMgPC0gYygKICAiR0FUQTMiLCAiVEJYMjEiLCAiUk9SQyIsICJGT1hQMyIsCiAgIlRPWCIsICJTQVRCMSIsICJJS1pGMiIsCiAgIlNUQVQzIiwgIlNUQVQ1QiIsICJTVEFUNiIsCiAgIlJFTEEiLCAiTkZLQjEiLCAiTVlDIiwgIkUyRjEiCikKCiMgS2VlcCBvbmx5IFRGcyBwcmVzZW50IGluIHRoZSBkb3JvdGhlYSBhc3NheQphdmFpbGFibGVfdGZzIDwtIGludGVyc2VjdChsaXRlcmF0dXJlX3Rmcywgcm93bmFtZXMoc2V1cmF0X29ialtbImRvcm90aGVhIl1dKSkKaWYgKGxlbmd0aChhdmFpbGFibGVfdGZzKSA8IDUpIHN0b3AoIlRvbyBmZXcgVEZzIGZvdW5kIGluIGRvcm90aGVhIGFzc2F5LiBDaGVjayBURiBuYW1pbmcgLyBhc3NheSBjb250ZW50LiIpCgojIEV4dHJhY3QgVEYgYWN0aXZpdHkgbWF0cml4IChURnMgeCBjZWxscykKIyBVc2Ugc2NhbGUuZGF0YSBpZiBhdmFpbGFibGU7IG90aGVyd2lzZSBmYWxsIGJhY2sgdG8gZGF0YSBsYXllci4KbWF0X3NjYWxlZCA8LSB0cnlDYXRjaCgKICBTZXVyYXRPYmplY3Q6OkdldEFzc2F5RGF0YShzZXVyYXRfb2JqLCBhc3NheSA9ICJkb3JvdGhlYSIsIGxheWVyID0gInNjYWxlLmRhdGEiKSwKICBlcnJvciA9IGZ1bmN0aW9uKGUpIE5VTEwKKQptYXRfZGF0YSA8LSBTZXVyYXRPYmplY3Q6OkdldEFzc2F5RGF0YShzZXVyYXRfb2JqLCBhc3NheSA9ICJkb3JvdGhlYSIsIGxheWVyID0gImRhdGEiKQoKbWF0X3VzZSA8LSBpZiAoIWlzLm51bGwobWF0X3NjYWxlZCkgJiYgbnJvdyhtYXRfc2NhbGVkKSA+IDApIG1hdF9zY2FsZWQgZWxzZSBtYXRfZGF0YQptYXRfdXNlIDwtIG1hdF91c2VbYXZhaWxhYmxlX3RmcywgLCBkcm9wID0gRkFMU0VdCgojIEF2ZXJhZ2UgcGVyIGNsdXN0ZXIgKFRGIHggY2x1c3RlcikKY2x1c3RlcnMgPC0gYXMuZmFjdG9yKHNldXJhdF9vYmokc2V1cmF0X2NsdXN0ZXJzKQphdmdfbWF0IDwtIHNhcHBseShsZXZlbHMoY2x1c3RlcnMpLCBmdW5jdGlvbihjbCkgewogIE1hdHJpeDo6cm93TWVhbnMobWF0X3VzZVssIGNsdXN0ZXJzID09IGNsLCBkcm9wID0gRkFMU0VdKQp9KQpjb2xuYW1lcyhhdmdfbWF0KSA8LSBsZXZlbHMoY2x1c3RlcnMpCgojIE9wdGlvbmFsOiB6LXNjb3JlIGFjcm9zcyBjbHVzdGVycyAoaGVscHMgcmVhZGFiaWxpdHkgaWYgeW91IHVzZWQgcmF3ICdkYXRhJyBpbnN0ZWFkIG9mICdzY2FsZS5kYXRhJykKYXZnX21hdF96IDwtIHQoc2NhbGUodChhdmdfbWF0KSkpCmF2Z19tYXRfeltpcy5uYShhdmdfbWF0X3opXSA8LSAwCgojIENvbG9ycwpjb2xfZnVuIDwtIGNpcmNsaXplOjpjb2xvclJhbXAyKGMoLTIsIDAsIDIpLCBjKCIjMzEzNjk1IiwgIndoaXRlIiwgIiNBNTAwMjYiKSkKCmh0IDwtIEhlYXRtYXAoCiAgYXZnX21hdF96LAogIG5hbWUgPSAiVEYgYWN0aXZpdHkgKHopIiwKICBjb2wgPSBjb2xfZnVuLAogIGNsdXN0ZXJfcm93cyA9IFRSVUUsCiAgY2x1c3Rlcl9jb2x1bW5zID0gVFJVRSwKICBzaG93X3Jvd19kZW5kID0gVFJVRSwKICBzaG93X2NvbHVtbl9kZW5kID0gVFJVRSwKICByb3dfbmFtZXNfZ3AgPSBncmlkOjpncGFyKGZvbnRzaXplID0gMTApLAogIGNvbHVtbl9uYW1lc19ncCA9IGdyaWQ6OmdwYXIoZm9udHNpemUgPSAxMCksCiAgY29sdW1uX3RpdGxlID0gIkxpdGVyYXR1cmUtdmFsaWRhdGVkIFPDqXphcnkgVEYgbW9kdWxlcyAoRG9Sb3RoRUEvZGVjb3VwbGVSKSIsCiAgaGVhdG1hcF9sZWdlbmRfcGFyYW0gPSBsaXN0KGRpcmVjdGlvbiA9ICJ2ZXJ0aWNhbCIpCikKCiMgRHJhdyB0byBub3RlYm9vawpkcmF3KGh0KQoKIyBTYXZlIFBERiAodmVjdG9yKQpwZGYoIk91dHB1dF9GaWd1cmVzL0ZpZ3VyZV8zLjE2RV9MaXRlcmF0dXJlX1RGX0hlYXRtYXBfQ29tcGxleEhlYXRtYXAucGRmIiwgd2lkdGggPSAxMCwgaGVpZ2h0ID0gOCkKZHJhdyhodCkKZGV2Lm9mZigpCgojIFNhdmUgUE5HIChyYXN0ZXIsIHB1YmxpY2F0aW9uLXJlYWR5KQpwbmcoIk91dHB1dF9GaWd1cmVzL0ZpZ3VyZV8zLjE2RV9MaXRlcmF0dXJlX1RGX0hlYXRtYXBfQ29tcGxleEhlYXRtYXAucG5nIiwKICAgIHdpZHRoID0gMTAgKiAzMDAsIGhlaWdodCA9IDggKiAzMDAsIHJlcyA9IDMwMCkKZHJhdyhodCkKZGV2Lm9mZigpCmBgYAoKCiMgRmluYWwgU2F2ZQpgYGB7ciwgZmlnLmhlaWdodD0xMiwgZmlnLndpZHRoPTE2fQpwcmludCgiQW5hbHlzaXMgcGlwZWxpbmUgY29tcGxldGUuIEFsbCBmaWd1cmVzIGFuZCBvYmplY3RzIHNhdmVkIGluIE91dHB1dF9GaWd1cmVzIGZvbGRlci4iKQpgYGAKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCg==