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)
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."
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!"
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

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

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

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

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."
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)
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==