Overview

This R Markdown performs FGSEA-based pathway characterization of malignant CD4+ T-cell clusters, using the entire ranked differential expression output per cluster (not a top-N marker subset). This is a methodological correction relative to an earlier top100-based FGSEA attempt: FGSEA’s permutation-based null distribution requires a full ranked gene list to be statistically valid. Restricting FGSEA to a short marker list is the reason that earlier run was underpowered; over-representation analysis (ORA) remains the correct tool for short, fixed marker-gene lists, while FGSEA is the correct tool here because the entire transcriptome per cluster is being ranked.

The workflow:

  1. Identify cluster-specific differential expression using Seurat FindAllMarkers, retaining all tested genes (no logfc.threshold/top-N filtering).
  2. Rank genes per cluster using a composite statistic combining effect size and statistical confidence, with an explicit floor on p-values to avoid infinite ranks.
  3. Run FGSEA against Hallmark, Reactome, KEGG, and GO:BP gene sets.
  4. Export enrichment tables including NES, p-value, adjusted p-value, and leading-edge genes – the genes actually driving each enrichment signal.

1. Load packages


library(Seurat)
library(dplyr)
library(fgsea)
library(msigdbr)
library(ggplot2)
library(tibble)
library(stringr)

set.seed(123)

2. Load Seurat object

Load your integrated malignant CD4+ T-cell Seurat object.


# Example:
All_samples_Merged <- readRDS("../../1-Seurat_RDS_OBJECT_FINAL/All_samples_Merged_with_Renamed_Clusters_Cell_state-03-12-2025.rds.rds")

All_samples_Merged

DefaultAssay(All_samples_Merged)

Idents(All_samples_Merged) <- "seurat_clusters"

Idents(All_samples_Merged)

3. Differential expression analysis

All genes are retained for FGSEA.

No top-100 filtering is applied.

All tested genes are retained for FGSEA – no top-N filtering, since FGSEA requires a full ranked list rather than a short marker set.


markers_all <- FindAllMarkers(
  object = All_samples_Merged,
  only.pos = FALSE,
  min.pct = 0.1,
  logfc.threshold = 0
)


head(markers_all)
NA

4. Generate ranking statistic

The ranking combines expression effect size and statistical confidence:

\[ Score = sign(avg\_log2FC) \times |avg\_log2FC| \times \sqrt{-log10(p_{adj})} \]

Fix applied: in large single-cell datasets, p_val_adj is frequently exactly 0 due to very large cell numbers, which makes -log10(p_val_adj) = Inf for many genes simultaneously. Replacing all of those with the single maximum finite rank (as in the original script) collapses many genes to an identical tied value, destroying the relative ordering FGSEA depends on. Instead, a numerical floor is applied to p_val_adj before the log transform, preserving relative differences between highly significant genes.

p_floor <- min(markers_all$p_val_adj[markers_all$p_val_adj > 0], na.rm = TRUE) / 10

markers_all <- markers_all %>%
  mutate(
    p_val_adj_floored = ifelse(p_val_adj == 0, p_floor, p_val_adj),
    fgsea_rank = sign(avg_log2FC) * abs(avg_log2FC) * sqrt(-log10(p_val_adj_floored))
  )

sum(is.infinite(markers_all$fgsea_rank))
[1] 0
sum(markers_all$p_val_adj == 0)
[1] 8266

5. Prepare pathway databases


library(msigdbr)

msigdbr_species <- "Homo sapiens"


# -------------------------
# Hallmark pathways
# -------------------------

hallmark_sets <- msigdbr(
  species = msigdbr_species,
  collection = "H"
)

hallmark_sets <- split(
  hallmark_sets$gene_symbol,
  hallmark_sets$gs_name
)



# -------------------------
# Reactome pathways
# -------------------------

reactome_sets <- msigdbr(
  species = msigdbr_species,
  collection = "C2",
  subcollection = "CP:REACTOME"
)

reactome_sets <- split(
  reactome_sets$gene_symbol,
  reactome_sets$gs_name
)



# -------------------------
# KEGG pathways
# -------------------------

kegg_sets <- msigdbr(
  species = msigdbr_species,
  collection = "C2",
  subcollection = "CP:KEGG_LEGACY"
)

kegg_sets <- split(
  kegg_sets$gene_symbol,
  kegg_sets$gs_name
)



# -------------------------
# GO Biological Process
# -------------------------

go_sets <- msigdbr(
  species = msigdbr_species,
  collection = "C5",
  subcollection = "GO:BP"
)

go_sets <- split(
  go_sets$gene_symbol,
  go_sets$gs_name
)


# Check pathway numbers

length(hallmark_sets)
[1] 50
length(reactome_sets)
[1] 1839
length(kegg_sets)
[1] 186
length(go_sets)
[1] 7538

Note: if your installed msigdbr version is >= 10.0, the category/subcategory arguments have been renamed to collection/subcollection. If the chunk above errors with “unused argument”, replace category = with collection = and subcategory = with subcollection = throughout this section.

6. FGSEA function

run_fgsea_cluster <- function(cluster_id, markers, pathways) {

  message("Running cluster: ", cluster_id)

  cluster_markers <- markers %>% dplyr::filter(cluster == cluster_id)

  ranks <- cluster_markers$fgsea_rank
  names(ranks) <- cluster_markers$gene

  ranks <- ranks[!duplicated(names(ranks))]
  ranks <- ranks[!is.na(ranks) & !is.infinite(ranks)]
  ranks <- sort(ranks, decreasing = TRUE)

  fgsea_result <- fgsea(
    pathways = pathways,
    stats = ranks,
    minSize = 15,
    maxSize = 500,
    eps = 0
  )

  fgsea_result <- as.data.frame(fgsea_result)

  fgsea_result$leadingEdge_genes <- sapply(
    fgsea_result$leadingEdge,
    function(x) paste(x, collapse = ", ")
  )

  fgsea_result$cluster <- cluster_id
  fgsea_result$n_genes_ranked <- length(ranks)

  return(fgsea_result)
}

7. Define clusters

clusters <- sort(unique(markers_all$cluster))
clusters
 [1] 0  1  2  3  4  5  6  7  8  9  10 11 12 13
Levels: 0 1 2 3 4 5 6 7 8 9 10 11 12 13

8. Run FGSEA across all four databases

hallmark_results <- bind_rows(lapply(clusters, run_fgsea_cluster,
                                      markers = markers_all, pathways = hallmark_sets))
hallmark_results$database <- "Hallmark"

reactome_results <- bind_rows(lapply(clusters, run_fgsea_cluster,
                                      markers = markers_all, pathways = reactome_sets))
reactome_results$database <- "Reactome"

kegg_results <- bind_rows(lapply(clusters, run_fgsea_cluster,
                                  markers = markers_all, pathways = kegg_sets))
kegg_results$database <- "KEGG"

go_results <- bind_rows(lapply(clusters, run_fgsea_cluster,
                                markers = markers_all, pathways = go_sets))
go_results$database <- "GO_BP"

9. Combine all databases

all_fgsea_results <- bind_rows(hallmark_results, reactome_results, kegg_results, go_results)

all_fgsea_results <- all_fgsea_results %>%
  dplyr::select(cluster, database, pathway, NES, pval, padj, size,
                n_genes_ranked, leadingEdge_genes) %>%
  dplyr::arrange(cluster, padj)

head(all_fgsea_results)

10. Export complete FGSEA table

write.csv(all_fgsea_results, "FGSEA_All_Pathways_with_LeadingEdge_Genes.csv", row.names = FALSE)

11. Significant pathways

significant_fgsea <- all_fgsea_results %>% dplyr::filter(padj < 0.05)
write.csv(significant_fgsea, "FGSEA_Significant_Pathways.csv", row.names = FALSE)

12. Top pathways per cluster

top_pathways <- all_fgsea_results %>%
  dplyr::group_by(cluster) %>%
  dplyr::arrange(padj) %>%
  dplyr::slice_head(n = 10)

write.csv(top_pathways, "FGSEA_Top10_Pathways_Per_Cluster.csv", row.names = FALSE)
top_pathways

13. Plot Hallmark enrichment per cluster

plot_data <- hallmark_results %>%
  dplyr::filter(padj < 0.05) %>%
  dplyr::group_by(cluster) %>%
  dplyr::slice_max(order_by = abs(NES), n = 5)

if (nrow(plot_data) > 0) {
  ggplot(plot_data, aes(x = reorder(pathway, NES), y = NES, fill = NES > 0)) +
    geom_col() +
    facet_wrap(~cluster, scales = "free") +
    coord_flip() +
    scale_fill_manual(values = c("TRUE" = "#B2182B", "FALSE" = "#4393C3"), guide = "none") +
    labs(x = NULL, y = "Normalized Enrichment Score (NES)") +
    theme_classic(base_size = 10)
} else {
  cat("No Hallmark pathways passed padj < 0.05 in any cluster.\n")
}

14. Save R objects

save(markers_all, all_fgsea_results, hallmark_results, reactome_results,
     kegg_results, go_results, file = "FGSEA_SS_Malignant_CD4_results.RData")

15. Notes on using this output for cluster naming justification

  • FGSEA_All_Pathways_with_LeadingEdge_Genes.csv is the primary file to cite from, since it links each pathway’s NES/FDR directly to the specific leading-edge genes driving the signal (e.g., “driven by IFIT2, IFIT3, OASL”).
  • Report NES sign and magnitude, not just significance: a large positive NES means the pathway’s genes are concentrated toward the top (up-regulated) end of that cluster’s ranking relative to all other clusters.
  • This FGSEA approach and your existing ORA (enrichGO/enrichKEGG/enrichPathway) approach answer different questions and should be reported as complementary, not interchangeable: ORA asks “are my top marker genes overrepresented in a pathway?”; FGSEA asks “is a pathway’s gene set skewed toward the top of my full ranked list for this cluster?” Reporting both, when they agree, is stronger evidence than either alone.

16. Session Info

sessionInfo()
R version 4.5.3 (2026-03-11)
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    LC_PAPER=C.UTF-8      
 [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    parallel  stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] msigdbr_26.1.0              fgsea_1.36.2                future_1.70.0               igraph_2.3.2                pheatmap_1.0.13             scales_1.4.0               
 [7] ggrepel_0.9.8               ggridges_0.5.7              viridis_0.6.5               viridisLite_0.4.3           RColorBrewer_1.1-3          SeuratWrappers_0.4.0       
[13] monocle3_1.4.26             SingleCellExperiment_1.32.0 SummarizedExperiment_1.40.0 GenomicRanges_1.62.1        Seqinfo_1.0.0               IRanges_2.44.0             
[19] S4Vectors_0.48.1            MatrixGenerics_1.22.0       matrixStats_1.5.0           Biobase_2.70.0              BiocGenerics_0.56.0         generics_0.1.4             
[25] CytoTRACE2_1.1.0            RSpectra_0.16-2             Rfast_2.1.5.2               RcppParallel_5.1.11-2       zigg_0.0.2                  Rcpp_1.1.1-1.1             
[31] plyr_1.8.9                  Matrix_1.7-5                magrittr_2.0.5              HiClimR_2.2.1               doParallel_1.0.17           iterators_1.0.14           
[37] foreach_1.5.2               data.table_1.18.4           patchwork_1.3.2             SCpubr_3.0.1                cowplot_1.2.0               lubridate_1.9.5            
[43] forcats_1.0.1               stringr_1.6.0               dplyr_1.2.1                 purrr_1.2.2                 readr_2.2.0                 tidyr_1.3.2                
[49] tibble_3.3.1                tidyverse_2.0.0             plotly_4.12.0               ggplot2_4.0.3               Seurat_5.5.0                SeuratObject_5.4.0         
[55] sp_2.2-1                   

loaded via a namespace (and not attached):
  [1] RcppAnnoy_0.0.23       splines_4.5.3          later_1.4.8            R.oo_1.27.1            polyclip_1.10-7        fastDummies_1.7.6      lifecycle_1.0.5       
  [8] Rdpack_2.6.6           globals_0.19.1         lattice_0.22-9         MASS_7.3-65            sass_0.4.10            limma_3.66.0           rmarkdown_2.31        
 [15] jquerylib_0.1.4        yaml_2.3.12            remotes_2.5.0          httpuv_1.6.17          otel_0.2.0             sctransform_0.4.3      spam_2.11-4           
 [22] spatstat.sparse_3.2-0  reticulate_1.46.0      pbapply_1.7-4          minqa_1.2.8            abind_1.4-8            Rtsne_0.17             presto_1.0.0          
 [29] R.utils_2.13.0         irlba_2.3.7            listenv_0.10.1         spatstat.utils_3.2-3   goftest_1.2-3          spatstat.random_3.5-0  fitdistrplus_1.2-6    
 [36] parallelly_1.47.0      ncdf4_1.24             codetools_0.2-20       DelayedArray_0.36.1    tidyselect_1.2.1       farver_2.1.2           lme4_2.0-1            
 [43] spatstat.explore_3.8-1 jsonlite_2.0.0         progressr_0.19.0       survival_3.8-6         systemfonts_1.3.2      tools_4.5.3            ragg_1.5.2            
 [50] ica_1.0-3              glue_1.8.1             gridExtra_2.3          SparseArray_1.10.10    xfun_0.58              withr_3.0.2            BiocManager_1.30.27   
 [57] fastmap_1.2.0          boot_1.3-32            digest_0.6.39          rsvd_1.0.5             timechange_0.4.0       R6_2.6.1               mime_0.13             
 [64] textshaping_1.0.5      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            
 [71] httr_1.4.8             htmlwidgets_1.6.4      S4Arrays_1.10.1        uwot_0.2.4             pkgconfig_2.0.3        gtable_0.3.6           rsconnect_1.10.0      
 [78] lmtest_0.9-40          S7_0.2.2               XVector_0.50.0         htmltools_0.5.9        dotCall64_1.2          png_0.1-9              spatstat.univar_3.2-0 
 [85] reformulas_0.4.4       knitr_1.51             rstudioapi_0.19.0      tzdb_0.5.0             reshape2_1.4.5         curl_7.1.0             nlme_3.1-169          
 [92] nloptr_2.2.1           cachem_1.1.0           zoo_1.8-15             KernSmooth_2.23-26     miniUI_0.1.2           pillar_1.11.1          grid_4.5.3            
 [99] vctrs_0.7.3            RANN_2.6.2             promises_1.5.0         xtable_1.8-8           cluster_2.1.8.2        evaluate_1.0.5         cli_3.6.6             
[106] compiler_4.5.3         rlang_1.2.0            future.apply_1.20.2    labeling_0.4.3         stringi_1.8.7          BiocParallel_1.44.0    deldir_2.0-4          
[113] babelgene_22.9         assertthat_0.2.1       lazyeval_0.2.3         spatstat.geom_3.8-1    RcppHNSW_0.7.0         hms_1.1.4              statmod_1.5.2         
[120] shiny_1.13.0           rbibutils_2.4.1        ROCR_1.0-12            bslib_0.11.0           fastmatch_1.1-8       
LS0tCnRpdGxlOiAiRkdTRUEgQW5hbHlzaXMgb2YgTWFsaWduYW50IENENCBULWNlbGwgU3RhdGVzIChhbGwpIGluIFNlemFyeSBTeW5kcm9tZSIKYXV0aG9yOiAiTmFzaXIgTWFobW9vZCBBYmJhc2kiCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OiB5ZXMKICAgIHRvY19jb2xsYXBzZWQ6IHllcwogICAgdGhlbWU6IGZsYXRseQogICAgaGlnaGxpZ2h0OiB0YW5nbwogIGh0bWxfZG9jdW1lbnQ6CiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OiB5ZXMKICAgIGRmX3ByaW50OiBwYWdlZAogIHdvcmRfZG9jdW1lbnQ6CiAgICB0b2M6IHllcwogIHBkZl9kb2N1bWVudDoKICAgIHRvYzogeWVzCi0tLQoKIyBPdmVydmlldwoKVGhpcyBSIE1hcmtkb3duIHBlcmZvcm1zIEZHU0VBLWJhc2VkIHBhdGh3YXkgY2hhcmFjdGVyaXphdGlvbiBvZiBtYWxpZ25hbnQgQ0Q0KyBULWNlbGwKY2x1c3RlcnMsIHVzaW5nIHRoZSAqZW50aXJlKiByYW5rZWQgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gb3V0cHV0IHBlciBjbHVzdGVyIChub3QgYQp0b3AtTiBtYXJrZXIgc3Vic2V0KS4gVGhpcyBpcyBhIG1ldGhvZG9sb2dpY2FsIGNvcnJlY3Rpb24gcmVsYXRpdmUgdG8gYW4gZWFybGllcgp0b3AxMDAtYmFzZWQgRkdTRUEgYXR0ZW1wdDogRkdTRUEncyBwZXJtdXRhdGlvbi1iYXNlZCBudWxsIGRpc3RyaWJ1dGlvbiByZXF1aXJlcyBhCmZ1bGwgcmFua2VkIGdlbmUgbGlzdCB0byBiZSBzdGF0aXN0aWNhbGx5IHZhbGlkLiBSZXN0cmljdGluZyBGR1NFQSB0byBhIHNob3J0IG1hcmtlcgpsaXN0IGlzIHRoZSByZWFzb24gdGhhdCBlYXJsaWVyIHJ1biB3YXMgdW5kZXJwb3dlcmVkOyBvdmVyLXJlcHJlc2VudGF0aW9uIGFuYWx5c2lzCihPUkEpIHJlbWFpbnMgdGhlIGNvcnJlY3QgdG9vbCBmb3Igc2hvcnQsIGZpeGVkIG1hcmtlci1nZW5lIGxpc3RzLCB3aGlsZSBGR1NFQSBpcyB0aGUKY29ycmVjdCB0b29sIGhlcmUgYmVjYXVzZSB0aGUgKmVudGlyZSogdHJhbnNjcmlwdG9tZSBwZXIgY2x1c3RlciBpcyBiZWluZyByYW5rZWQuCgpUaGUgd29ya2Zsb3c6CgoxLiBJZGVudGlmeSBjbHVzdGVyLXNwZWNpZmljIGRpZmZlcmVudGlhbCBleHByZXNzaW9uIHVzaW5nIFNldXJhdCBgRmluZEFsbE1hcmtlcnNgLAogICByZXRhaW5pbmcgYWxsIHRlc3RlZCBnZW5lcyAobm8gYGxvZ2ZjLnRocmVzaG9sZGAvdG9wLU4gZmlsdGVyaW5nKS4KMi4gUmFuayBnZW5lcyBwZXIgY2x1c3RlciB1c2luZyBhIGNvbXBvc2l0ZSBzdGF0aXN0aWMgY29tYmluaW5nIGVmZmVjdCBzaXplIGFuZAogICBzdGF0aXN0aWNhbCBjb25maWRlbmNlLCB3aXRoIGFuIGV4cGxpY2l0IGZsb29yIG9uIHAtdmFsdWVzIHRvIGF2b2lkIGluZmluaXRlIHJhbmtzLgozLiBSdW4gRkdTRUEgYWdhaW5zdCBIYWxsbWFyaywgUmVhY3RvbWUsIEtFR0csIGFuZCBHTzpCUCBnZW5lIHNldHMuCjQuIEV4cG9ydCBlbnJpY2htZW50IHRhYmxlcyBpbmNsdWRpbmcgTkVTLCBwLXZhbHVlLCBhZGp1c3RlZCBwLXZhbHVlLCBhbmQgbGVhZGluZy1lZGdlCiAgIGdlbmVzIC0tIHRoZSBnZW5lcyBhY3R1YWxseSBkcml2aW5nIGVhY2ggZW5yaWNobWVudCBzaWduYWwuCgoKIyAxLiBMb2FkIHBhY2thZ2VzCgpgYGB7ciBzZXR1cCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KCmxpYnJhcnkoU2V1cmF0KQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KGZnc2VhKQpsaWJyYXJ5KG1zaWdkYnIpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeSh0aWJibGUpCmxpYnJhcnkoc3RyaW5ncikKCnNldC5zZWVkKDEyMykKYGBgCgoKCgoKIyAyLiBMb2FkIFNldXJhdCBvYmplY3QKCkxvYWQgeW91ciBpbnRlZ3JhdGVkIG1hbGlnbmFudCBDRDQrIFQtY2VsbCBTZXVyYXQgb2JqZWN0LgoKYGBge3J9CgojIEV4YW1wbGU6CkFsbF9zYW1wbGVzX01lcmdlZCA8LSByZWFkUkRTKCIuLi8uLi8xLVNldXJhdF9SRFNfT0JKRUNUX0ZJTkFML0FsbF9zYW1wbGVzX01lcmdlZF93aXRoX1JlbmFtZWRfQ2x1c3RlcnNfQ2VsbF9zdGF0ZS0wMy0xMi0yMDI1LnJkcy5yZHMiKQoKQWxsX3NhbXBsZXNfTWVyZ2VkCgpEZWZhdWx0QXNzYXkoQWxsX3NhbXBsZXNfTWVyZ2VkKQoKSWRlbnRzKEFsbF9zYW1wbGVzX01lcmdlZCkgPC0gInNldXJhdF9jbHVzdGVycyIKCklkZW50cyhBbGxfc2FtcGxlc19NZXJnZWQpCgpgYGAKCiMgMy4gRGlmZmVyZW50aWFsIGV4cHJlc3Npb24gYW5hbHlzaXMKCkFsbCBnZW5lcyBhcmUgcmV0YWluZWQgZm9yIEZHU0VBLgoKTm8gdG9wLTEwMCBmaWx0ZXJpbmcgaXMgYXBwbGllZC4KCkFsbCB0ZXN0ZWQgZ2VuZXMgYXJlIHJldGFpbmVkIGZvciBGR1NFQSAtLSBubyB0b3AtTiBmaWx0ZXJpbmcsIHNpbmNlIEZHU0VBIHJlcXVpcmVzCmEgZnVsbCByYW5rZWQgbGlzdCByYXRoZXIgdGhhbiBhIHNob3J0IG1hcmtlciBzZXQuCgoKYGBge3J9CgptYXJrZXJzX2FsbCA8LSBGaW5kQWxsTWFya2VycygKICBvYmplY3QgPSBBbGxfc2FtcGxlc19NZXJnZWQsCiAgb25seS5wb3MgPSBGQUxTRSwKICBtaW4ucGN0ID0gMC4xLAogIGxvZ2ZjLnRocmVzaG9sZCA9IDAKKQoKCmhlYWQobWFya2Vyc19hbGwpCgpgYGAKCiMgNC4gR2VuZXJhdGUgcmFua2luZyBzdGF0aXN0aWMKClRoZSByYW5raW5nIGNvbWJpbmVzIGV4cHJlc3Npb24gZWZmZWN0IHNpemUgYW5kIHN0YXRpc3RpY2FsIGNvbmZpZGVuY2U6CgpcWwpTY29yZSA9IHNpZ24oYXZnXF9sb2cyRkMpIFx0aW1lcyB8YXZnXF9sb2cyRkN8IFx0aW1lcyBcc3FydHstbG9nMTAocF97YWRqfSl9ClxdCgoqKkZpeCBhcHBsaWVkOioqIGluIGxhcmdlIHNpbmdsZS1jZWxsIGRhdGFzZXRzLCBgcF92YWxfYWRqYCBpcyBmcmVxdWVudGx5IGV4YWN0bHkgMApkdWUgdG8gdmVyeSBsYXJnZSBjZWxsIG51bWJlcnMsIHdoaWNoIG1ha2VzIGAtbG9nMTAocF92YWxfYWRqKSA9IEluZmAgZm9yIG1hbnkgZ2VuZXMKc2ltdWx0YW5lb3VzbHkuIFJlcGxhY2luZyBhbGwgb2YgdGhvc2Ugd2l0aCB0aGUgc2luZ2xlIG1heGltdW0gZmluaXRlIHJhbmsgKGFzIGluIHRoZQpvcmlnaW5hbCBzY3JpcHQpIGNvbGxhcHNlcyBtYW55IGdlbmVzIHRvIGFuIGlkZW50aWNhbCB0aWVkIHZhbHVlLCBkZXN0cm95aW5nIHRoZQpyZWxhdGl2ZSBvcmRlcmluZyBGR1NFQSBkZXBlbmRzIG9uLiBJbnN0ZWFkLCBhIG51bWVyaWNhbCBmbG9vciBpcyBhcHBsaWVkIHRvCmBwX3ZhbF9hZGpgICpiZWZvcmUqIHRoZSBsb2cgdHJhbnNmb3JtLCBwcmVzZXJ2aW5nIHJlbGF0aXZlIGRpZmZlcmVuY2VzIGJldHdlZW4KaGlnaGx5IHNpZ25pZmljYW50IGdlbmVzLgoKYGBge3IgcmFua2luZy1zdGF0aXN0aWN9CnBfZmxvb3IgPC0gbWluKG1hcmtlcnNfYWxsJHBfdmFsX2FkalttYXJrZXJzX2FsbCRwX3ZhbF9hZGogPiAwXSwgbmEucm0gPSBUUlVFKSAvIDEwCgptYXJrZXJzX2FsbCA8LSBtYXJrZXJzX2FsbCAlPiUKICBtdXRhdGUoCiAgICBwX3ZhbF9hZGpfZmxvb3JlZCA9IGlmZWxzZShwX3ZhbF9hZGogPT0gMCwgcF9mbG9vciwgcF92YWxfYWRqKSwKICAgIGZnc2VhX3JhbmsgPSBzaWduKGF2Z19sb2cyRkMpICogYWJzKGF2Z19sb2cyRkMpICogc3FydCgtbG9nMTAocF92YWxfYWRqX2Zsb29yZWQpKQogICkKCnN1bShpcy5pbmZpbml0ZShtYXJrZXJzX2FsbCRmZ3NlYV9yYW5rKSkKc3VtKG1hcmtlcnNfYWxsJHBfdmFsX2FkaiA9PSAwKQpgYGAKCiMgNS4gUHJlcGFyZSBwYXRod2F5IGRhdGFiYXNlcwoKYGBge3IgcGF0aHdheS1kYXRhYmFzZXN9CgpsaWJyYXJ5KG1zaWdkYnIpCgptc2lnZGJyX3NwZWNpZXMgPC0gIkhvbW8gc2FwaWVucyIKCgojIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyBIYWxsbWFyayBwYXRod2F5cwojIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCmhhbGxtYXJrX3NldHMgPC0gbXNpZ2RicigKICBzcGVjaWVzID0gbXNpZ2Ricl9zcGVjaWVzLAogIGNvbGxlY3Rpb24gPSAiSCIKKQoKaGFsbG1hcmtfc2V0cyA8LSBzcGxpdCgKICBoYWxsbWFya19zZXRzJGdlbmVfc3ltYm9sLAogIGhhbGxtYXJrX3NldHMkZ3NfbmFtZQopCgoKCiMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIFJlYWN0b21lIHBhdGh3YXlzCiMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKcmVhY3RvbWVfc2V0cyA8LSBtc2lnZGJyKAogIHNwZWNpZXMgPSBtc2lnZGJyX3NwZWNpZXMsCiAgY29sbGVjdGlvbiA9ICJDMiIsCiAgc3ViY29sbGVjdGlvbiA9ICJDUDpSRUFDVE9NRSIKKQoKcmVhY3RvbWVfc2V0cyA8LSBzcGxpdCgKICByZWFjdG9tZV9zZXRzJGdlbmVfc3ltYm9sLAogIHJlYWN0b21lX3NldHMkZ3NfbmFtZQopCgoKCiMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIEtFR0cgcGF0aHdheXMKIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgprZWdnX3NldHMgPC0gbXNpZ2RicigKICBzcGVjaWVzID0gbXNpZ2Ricl9zcGVjaWVzLAogIGNvbGxlY3Rpb24gPSAiQzIiLAogIHN1YmNvbGxlY3Rpb24gPSAiQ1A6S0VHR19MRUdBQ1kiCikKCmtlZ2dfc2V0cyA8LSBzcGxpdCgKICBrZWdnX3NldHMkZ2VuZV9zeW1ib2wsCiAga2VnZ19zZXRzJGdzX25hbWUKKQoKCgojIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyBHTyBCaW9sb2dpY2FsIFByb2Nlc3MKIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgpnb19zZXRzIDwtIG1zaWdkYnIoCiAgc3BlY2llcyA9IG1zaWdkYnJfc3BlY2llcywKICBjb2xsZWN0aW9uID0gIkM1IiwKICBzdWJjb2xsZWN0aW9uID0gIkdPOkJQIgopCgpnb19zZXRzIDwtIHNwbGl0KAogIGdvX3NldHMkZ2VuZV9zeW1ib2wsCiAgZ29fc2V0cyRnc19uYW1lCikKCgojIENoZWNrIHBhdGh3YXkgbnVtYmVycwoKbGVuZ3RoKGhhbGxtYXJrX3NldHMpCmxlbmd0aChyZWFjdG9tZV9zZXRzKQpsZW5ndGgoa2VnZ19zZXRzKQpsZW5ndGgoZ29fc2V0cykKCmBgYAoKTm90ZTogaWYgeW91ciBpbnN0YWxsZWQgYG1zaWdkYnJgIHZlcnNpb24gaXMgPj0gMTAuMCwgdGhlIGBjYXRlZ29yeWAvYHN1YmNhdGVnb3J5YAphcmd1bWVudHMgaGF2ZSBiZWVuIHJlbmFtZWQgdG8gYGNvbGxlY3Rpb25gL2BzdWJjb2xsZWN0aW9uYC4gSWYgdGhlIGNodW5rIGFib3ZlIGVycm9ycwp3aXRoICJ1bnVzZWQgYXJndW1lbnQiLCByZXBsYWNlIGBjYXRlZ29yeSA9YCB3aXRoIGBjb2xsZWN0aW9uID1gIGFuZApgc3ViY2F0ZWdvcnkgPWAgd2l0aCBgc3ViY29sbGVjdGlvbiA9YCB0aHJvdWdob3V0IHRoaXMgc2VjdGlvbi4KCiMgNi4gRkdTRUEgZnVuY3Rpb24KCmBgYHtyIGZnc2VhLWZ1bmN0aW9ufQpydW5fZmdzZWFfY2x1c3RlciA8LSBmdW5jdGlvbihjbHVzdGVyX2lkLCBtYXJrZXJzLCBwYXRod2F5cykgewoKICBtZXNzYWdlKCJSdW5uaW5nIGNsdXN0ZXI6ICIsIGNsdXN0ZXJfaWQpCgogIGNsdXN0ZXJfbWFya2VycyA8LSBtYXJrZXJzICU+JSBkcGx5cjo6ZmlsdGVyKGNsdXN0ZXIgPT0gY2x1c3Rlcl9pZCkKCiAgcmFua3MgPC0gY2x1c3Rlcl9tYXJrZXJzJGZnc2VhX3JhbmsKICBuYW1lcyhyYW5rcykgPC0gY2x1c3Rlcl9tYXJrZXJzJGdlbmUKCiAgcmFua3MgPC0gcmFua3NbIWR1cGxpY2F0ZWQobmFtZXMocmFua3MpKV0KICByYW5rcyA8LSByYW5rc1shaXMubmEocmFua3MpICYgIWlzLmluZmluaXRlKHJhbmtzKV0KICByYW5rcyA8LSBzb3J0KHJhbmtzLCBkZWNyZWFzaW5nID0gVFJVRSkKCiAgZmdzZWFfcmVzdWx0IDwtIGZnc2VhKAogICAgcGF0aHdheXMgPSBwYXRod2F5cywKICAgIHN0YXRzID0gcmFua3MsCiAgICBtaW5TaXplID0gMTUsCiAgICBtYXhTaXplID0gNTAwLAogICAgZXBzID0gMAogICkKCiAgZmdzZWFfcmVzdWx0IDwtIGFzLmRhdGEuZnJhbWUoZmdzZWFfcmVzdWx0KQoKICBmZ3NlYV9yZXN1bHQkbGVhZGluZ0VkZ2VfZ2VuZXMgPC0gc2FwcGx5KAogICAgZmdzZWFfcmVzdWx0JGxlYWRpbmdFZGdlLAogICAgZnVuY3Rpb24oeCkgcGFzdGUoeCwgY29sbGFwc2UgPSAiLCAiKQogICkKCiAgZmdzZWFfcmVzdWx0JGNsdXN0ZXIgPC0gY2x1c3Rlcl9pZAogIGZnc2VhX3Jlc3VsdCRuX2dlbmVzX3JhbmtlZCA8LSBsZW5ndGgocmFua3MpCgogIHJldHVybihmZ3NlYV9yZXN1bHQpCn0KYGBgCgojIDcuIERlZmluZSBjbHVzdGVycwoKYGBge3IgZGVmaW5lLWNsdXN0ZXJzfQpjbHVzdGVycyA8LSBzb3J0KHVuaXF1ZShtYXJrZXJzX2FsbCRjbHVzdGVyKSkKY2x1c3RlcnMKYGBgCgojIDguIFJ1biBGR1NFQSBhY3Jvc3MgYWxsIGZvdXIgZGF0YWJhc2VzCgpgYGB7ciBydW4tZmdzZWEtYWxsfQpoYWxsbWFya19yZXN1bHRzIDwtIGJpbmRfcm93cyhsYXBwbHkoY2x1c3RlcnMsIHJ1bl9mZ3NlYV9jbHVzdGVyLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1hcmtlcnMgPSBtYXJrZXJzX2FsbCwgcGF0aHdheXMgPSBoYWxsbWFya19zZXRzKSkKaGFsbG1hcmtfcmVzdWx0cyRkYXRhYmFzZSA8LSAiSGFsbG1hcmsiCgpyZWFjdG9tZV9yZXN1bHRzIDwtIGJpbmRfcm93cyhsYXBwbHkoY2x1c3RlcnMsIHJ1bl9mZ3NlYV9jbHVzdGVyLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1hcmtlcnMgPSBtYXJrZXJzX2FsbCwgcGF0aHdheXMgPSByZWFjdG9tZV9zZXRzKSkKcmVhY3RvbWVfcmVzdWx0cyRkYXRhYmFzZSA8LSAiUmVhY3RvbWUiCgprZWdnX3Jlc3VsdHMgPC0gYmluZF9yb3dzKGxhcHBseShjbHVzdGVycywgcnVuX2Znc2VhX2NsdXN0ZXIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXJrZXJzID0gbWFya2Vyc19hbGwsIHBhdGh3YXlzID0ga2VnZ19zZXRzKSkKa2VnZ19yZXN1bHRzJGRhdGFiYXNlIDwtICJLRUdHIgoKZ29fcmVzdWx0cyA8LSBiaW5kX3Jvd3MobGFwcGx5KGNsdXN0ZXJzLCBydW5fZmdzZWFfY2x1c3RlciwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXJrZXJzID0gbWFya2Vyc19hbGwsIHBhdGh3YXlzID0gZ29fc2V0cykpCmdvX3Jlc3VsdHMkZGF0YWJhc2UgPC0gIkdPX0JQIgpgYGAKCiMgOS4gQ29tYmluZSBhbGwgZGF0YWJhc2VzCgpgYGB7ciBjb21iaW5lLXJlc3VsdHN9CmFsbF9mZ3NlYV9yZXN1bHRzIDwtIGJpbmRfcm93cyhoYWxsbWFya19yZXN1bHRzLCByZWFjdG9tZV9yZXN1bHRzLCBrZWdnX3Jlc3VsdHMsIGdvX3Jlc3VsdHMpCgphbGxfZmdzZWFfcmVzdWx0cyA8LSBhbGxfZmdzZWFfcmVzdWx0cyAlPiUKICBkcGx5cjo6c2VsZWN0KGNsdXN0ZXIsIGRhdGFiYXNlLCBwYXRod2F5LCBORVMsIHB2YWwsIHBhZGosIHNpemUsCiAgICAgICAgICAgICAgICBuX2dlbmVzX3JhbmtlZCwgbGVhZGluZ0VkZ2VfZ2VuZXMpICU+JQogIGRwbHlyOjphcnJhbmdlKGNsdXN0ZXIsIHBhZGopCgpoZWFkKGFsbF9mZ3NlYV9yZXN1bHRzKQpgYGAKCiMgMTAuIEV4cG9ydCBjb21wbGV0ZSBGR1NFQSB0YWJsZQoKYGBge3IgZXhwb3J0LWFsbH0Kd3JpdGUuY3N2KGFsbF9mZ3NlYV9yZXN1bHRzLCAiRkdTRUFfQWxsX1BhdGh3YXlzX3dpdGhfTGVhZGluZ0VkZ2VfR2VuZXMuY3N2Iiwgcm93Lm5hbWVzID0gRkFMU0UpCmBgYAoKIyAxMS4gU2lnbmlmaWNhbnQgcGF0aHdheXMKCmBgYHtyIGV4cG9ydC1zaWduaWZpY2FudH0Kc2lnbmlmaWNhbnRfZmdzZWEgPC0gYWxsX2Znc2VhX3Jlc3VsdHMgJT4lIGRwbHlyOjpmaWx0ZXIocGFkaiA8IDAuMDUpCndyaXRlLmNzdihzaWduaWZpY2FudF9mZ3NlYSwgIkZHU0VBX1NpZ25pZmljYW50X1BhdGh3YXlzLmNzdiIsIHJvdy5uYW1lcyA9IEZBTFNFKQpgYGAKCiMgMTIuIFRvcCBwYXRod2F5cyBwZXIgY2x1c3RlcgoKYGBge3IgdG9wLXBhdGh3YXlzfQp0b3BfcGF0aHdheXMgPC0gYWxsX2Znc2VhX3Jlc3VsdHMgJT4lCiAgZHBseXI6Omdyb3VwX2J5KGNsdXN0ZXIpICU+JQogIGRwbHlyOjphcnJhbmdlKHBhZGopICU+JQogIGRwbHlyOjpzbGljZV9oZWFkKG4gPSAxMCkKCndyaXRlLmNzdih0b3BfcGF0aHdheXMsICJGR1NFQV9Ub3AxMF9QYXRod2F5c19QZXJfQ2x1c3Rlci5jc3YiLCByb3cubmFtZXMgPSBGQUxTRSkKdG9wX3BhdGh3YXlzCmBgYAoKIyAxMy4gUGxvdCBIYWxsbWFyayBlbnJpY2htZW50IHBlciBjbHVzdGVyCgpgYGB7ciBwbG90LWhhbGxtYXJrLCBmaWcud2lkdGg9MTgsIGZpZy5oZWlnaHQ9OH0KcGxvdF9kYXRhIDwtIGhhbGxtYXJrX3Jlc3VsdHMgJT4lCiAgZHBseXI6OmZpbHRlcihwYWRqIDwgMC4wNSkgJT4lCiAgZHBseXI6Omdyb3VwX2J5KGNsdXN0ZXIpICU+JQogIGRwbHlyOjpzbGljZV9tYXgob3JkZXJfYnkgPSBhYnMoTkVTKSwgbiA9IDUpCgppZiAobnJvdyhwbG90X2RhdGEpID4gMCkgewogIGdncGxvdChwbG90X2RhdGEsIGFlcyh4ID0gcmVvcmRlcihwYXRod2F5LCBORVMpLCB5ID0gTkVTLCBmaWxsID0gTkVTID4gMCkpICsKICAgIGdlb21fY29sKCkgKwogICAgZmFjZXRfd3JhcCh+Y2x1c3Rlciwgc2NhbGVzID0gImZyZWUiKSArCiAgICBjb29yZF9mbGlwKCkgKwogICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYygiVFJVRSIgPSAiI0IyMTgyQiIsICJGQUxTRSIgPSAiIzQzOTNDMyIpLCBndWlkZSA9ICJub25lIikgKwogICAgbGFicyh4ID0gTlVMTCwgeSA9ICJOb3JtYWxpemVkIEVucmljaG1lbnQgU2NvcmUgKE5FUykiKSArCiAgICB0aGVtZV9jbGFzc2ljKGJhc2Vfc2l6ZSA9IDEwKQp9IGVsc2UgewogIGNhdCgiTm8gSGFsbG1hcmsgcGF0aHdheXMgcGFzc2VkIHBhZGogPCAwLjA1IGluIGFueSBjbHVzdGVyLlxuIikKfQpgYGAKCiMgMTQuIFNhdmUgUiBvYmplY3RzCgpgYGB7ciBzYXZlLW9iamVjdHN9CnNhdmUobWFya2Vyc19hbGwsIGFsbF9mZ3NlYV9yZXN1bHRzLCBoYWxsbWFya19yZXN1bHRzLCByZWFjdG9tZV9yZXN1bHRzLAogICAgIGtlZ2dfcmVzdWx0cywgZ29fcmVzdWx0cywgZmlsZSA9ICJGR1NFQV9TU19NYWxpZ25hbnRfQ0Q0X3Jlc3VsdHMuUkRhdGEiKQpgYGAKCiMgMTUuIE5vdGVzIG9uIHVzaW5nIHRoaXMgb3V0cHV0IGZvciBjbHVzdGVyIG5hbWluZyBqdXN0aWZpY2F0aW9uCgotIGBGR1NFQV9BbGxfUGF0aHdheXNfd2l0aF9MZWFkaW5nRWRnZV9HZW5lcy5jc3ZgIGlzIHRoZSBwcmltYXJ5IGZpbGUgdG8gY2l0ZSBmcm9tLAogIHNpbmNlIGl0IGxpbmtzIGVhY2ggcGF0aHdheSdzIE5FUy9GRFIgZGlyZWN0bHkgdG8gdGhlIHNwZWNpZmljIGxlYWRpbmctZWRnZSBnZW5lcwogIGRyaXZpbmcgdGhlIHNpZ25hbCAoZS5nLiwgImRyaXZlbiBieSBJRklUMiwgSUZJVDMsIE9BU0wiKS4KLSBSZXBvcnQgTkVTIHNpZ24gYW5kIG1hZ25pdHVkZSwgbm90IGp1c3Qgc2lnbmlmaWNhbmNlOiBhIGxhcmdlIHBvc2l0aXZlIE5FUyBtZWFucwogIHRoZSBwYXRod2F5J3MgZ2VuZXMgYXJlIGNvbmNlbnRyYXRlZCB0b3dhcmQgdGhlIHRvcCAodXAtcmVndWxhdGVkKSBlbmQgb2YgdGhhdAogIGNsdXN0ZXIncyByYW5raW5nIHJlbGF0aXZlIHRvIGFsbCBvdGhlciBjbHVzdGVycy4KLSBUaGlzIEZHU0VBIGFwcHJvYWNoIGFuZCB5b3VyIGV4aXN0aW5nIE9SQSAoYGVucmljaEdPYC9gZW5yaWNoS0VHR2AvYGVucmljaFBhdGh3YXlgKQogIGFwcHJvYWNoIGFuc3dlciBkaWZmZXJlbnQgcXVlc3Rpb25zIGFuZCBzaG91bGQgYmUgcmVwb3J0ZWQgYXMgY29tcGxlbWVudGFyeSwgbm90CiAgaW50ZXJjaGFuZ2VhYmxlOiBPUkEgYXNrcyAiYXJlIG15IHRvcCBtYXJrZXIgZ2VuZXMgb3ZlcnJlcHJlc2VudGVkIGluIGEgcGF0aHdheT8iOwogIEZHU0VBIGFza3MgImlzIGEgcGF0aHdheSdzIGdlbmUgc2V0IHNrZXdlZCB0b3dhcmQgdGhlIHRvcCBvZiBteSBmdWxsIHJhbmtlZCBsaXN0IGZvcgogIHRoaXMgY2x1c3Rlcj8iIFJlcG9ydGluZyBib3RoLCB3aGVuIHRoZXkgYWdyZWUsIGlzIHN0cm9uZ2VyIGV2aWRlbmNlIHRoYW4gZWl0aGVyCiAgYWxvbmUuCiAgCiMgMTYuIFNlc3Npb24gSW5mbwoKYGBge3Igc2Vzc2lvbi1pbmZvfQpzZXNzaW9uSW5mbygpCmBgYA==