La majorité des graphiques présentés sont interactifs, n’hésitez pas à passer votre souris dessus pour avoir les valeurs exactes !🙂

Introduction

Notre recherche se concentre sur l’identification des facteurs prédictifs de la démence. Notre variable dépendante, le “CDR” (Clinical Dementia Rate), est un indicateur neurologique établi en 1993 par Morris. Cette variable est ici binaire, avec 0 pour les individus non atteints de démence et 1 pour ceux qui le sont. Nous examinons également plusieurs autres variables potentiellement pertinentes, notamment le sexe, l’âge, le niveau d’éducation, le niveau socio-culturel, le volume cérébral total, le volume intercrânien, le niveau d’atrophie, ainsi que le Mini Mental State Examination (MMSE). Bien que le MMSE soit un test neurologique largement utilisé en raison de sa rapidité, il est toutefois souvent critiqué dans la littérature pour sa moindre précision et sa fiabilité variable. Nous avons l’opportunité de vérifier ces points en l’incluant dans notre étude parallèlement au CDR.

I. Analyse descriptive

Justification

L’analyse descriptive permet de comprendre la distribution et les caractéristiques générales des données, offrant ainsi un aperçu préliminaire des tendances et des associations potentielles entre les variables étudiées.

Il est important de s’assurer que les niveaux des variables sont assez équilibré, car sinon ils pourraient être sous-représentés dans les analyses à venir, si c’est le cas il sera ainsi important de le prendre en compte lors de l’interprétation.

Graphique des variables qualitatives

Pour simplifier la création des graphiques, nous pouvons écrire une fonction qui inclut tous les paramètres nécessaires. Cela nous permettra de générer des ggplot plus rapidement.


fastggplot <- function(data, x_var, title, x_label, y_label) {
  ggplot(data, aes(x = factor(data[[x_var]]), fill = factor(data[[x_var]]))) +
    geom_bar(color = "black", alpha = 0.8) +
    
    labs(title = title, x = x_label, y = y_label, fill = x_var) +
    theme_minimal() +
    theme(axis.text = element_text(size = 12),
          axis.title = element_text(size = 14, face = "bold"),
          plot.title = element_text(size = 16, face = "bold"))
}
CDR <- fastggplot(demence2, "CDR", "Répartition par score CDR", "Score CDR", "Nombre de cas")
CDR %>% plotly::ggplotly()

Notre variable dépendante a plus de non dément que de dément, ce qui est logique car c’est le cas dans la population générale.

MMSE <- fastggplot(demence2, "MMSE", "Répartition par score MMSE", "Score MMSE", "Nombre de cas")

MMSE <- MMSE + theme(axis.text.x = element_text(angle = 45, hjust = 1))

MMSE %>% plotly::ggplotly()

Ici, pour le Mini Mental State, on peut voir qu’on a moins de diagnostic de démence que pour le CDR, leur résultat ne semble donc pas homogène.

EDUC <- fastggplot(demence2, "EDUC", "Répartition par niveau d'éducation", "Niveau d'éducation", "Nombre de cas")

EDUC <- EDUC + theme(axis.text.x = element_text(angle = 45, hjust = 1))

EDUC %>% plotly::ggplotly()

Cette variable représente le niveau d’éducations de nos participants, on peut voir que la majorité est formé par deux groupes, avec 11-13 et 14-16, suivi par 17-18, et finalement 20-23, et 6-8, qui sont comparativement assez peu représenté.

genre = fastggplot(demence2, "sexe", "Répartition par sexe", "Genre", "Nombre de cas")
genre %>% plotly::ggplotly()

La répartition par sexe est assez homogène, avec un peu plus de femmes que d’hommes, notre population étant âgé (de 60 à 96 ans), c’est logique, car c’est aussi le cas dans la population générale pour cette tranche d’âge.

Graphique des variables quantitatives

hist_age <- ggplot(demence2, aes(x = Age)) +
  geom_histogram(binwidth = 0.01, fill = "green", color = "black", alpha = 0.8) +
  labs(title = "Distribution de l'âge", x = "", y = "Fréquence") +
  theme_minimal()
hist_age %>% plotly::ggplotly()

La répartition de l’âge de notre écahntillon va de 60 à 96 ans.


hist_nwbv <- ggplot(demence2, aes(x = Volume_cérébral_total)) +
  geom_histogram(binwidth = 0.01, fill = "green", color = "black", alpha = 0.8) +
  labs(title = "Distribution du volume cérébral total", x = "Volume cérébral total", y = "Fréquence") +
  theme_minimal()
hist_nwbv %>% plotly::ggplotly()

Le volume cérébral total de nos participants est compris entre 0.64 et 0.84.

hist_etiv <- ggplot(demence2, aes(x = Volume_intercranien)) +
  geom_histogram(binwidth = 100, fill = "orange", color = "black", alpha = 0.8) +
  labs(title = "Distribution du volume intercranien", x = "eTIV", y = "Fréquence") +
  theme_minimal()
hist_etiv %>% plotly::ggplotly()

Le volume intercrânien de nos participants est ocmpris entre 1100 et 2000.

hist_asf <- ggplot(demence2, aes(x = Indice_atrophie)) +
  geom_histogram(binwidth = 0.1, fill = "blue", color = "black", alpha = 0.8) +
  labs(title = "Distribution de l'indice d'atrophie", x = "Indice_atrophie", y = "Fréquence") +
  theme_minimal()
hist_asf %>% plotly::ggplotly()

L’indice d’atrophie de nos participants est compris entre 0.9 et 1.6.

Pour aller plus loin dans l’analyse descriptive, on peut également faire des graphiques qui associent notre variable dépendante, le CDR, aux autres.

Graphique associant la variable dépendante (CDR) et les variables indépendantes

CDRSEXE = ggplot(data = demence2, aes(x = CDR, fill = sexe)) +
  geom_bar(position = "dodge") +
  labs(title = "Comparaison de CDR par sexe",
       x = "CDR",
       y = "Nombre de participants",
       fill = "Sexe")
CDRSEXE %>% plotly::ggplotly()

Le niveau de démence semble être plus élevé chez les hommes que chez les femmes. Toutefois, des analyses plus poussés sont nécessaires pour comprendre ce phénomène.

CDREDUC = ggplot(data = demence2, aes(x = CDR, fill = EDUC)) +
  geom_bar(position = "dodge") +
  labs(title = "Comparaison de CDR par niveau d'éducation",
       x = "CDR",
       y = "Nombre de participants",
       fill = "EDUC")

CDREDUC %>% plotly::ggplotly()

Pour le niveau d’éducation, il semblerait que, à première vue, plus celui-ci est élevé, moins il risque d’avoir une démence pour une personne.

CDRSES = ggplot(data = demence2, aes(x = CDR, fill = SES)) +
  geom_bar(position = "dodge") +
  labs(title = "Comparaison de CDR par niveau socio-économique",
       x = "CDR",
       y = "Nombre de participants",
       fill = "SES")

CDRSES %>% plotly::ggplotly()

Pour le niveau socio-économique, il ne semble pas y avoir de tendance particulière qui se dégage.

CDRSES = ggplot(data = demence2, aes(x = CDR, fill = MMSE)) +
  geom_bar(position = "dodge") +
  labs(title = "Comparaison de CDR par MMSE",
       x = "CDR",
       y = "Nombre de participants",
       fill = "MMSE")

CDRSES %>% plotly::ggplotly()

Pour le CDR avec le MMSE, qui sont deux troubles neurologiques, les tests semblent être en désaccord, car, à 100 reprises, le CDR donne “Dément” comme diagnostic alors que MMSE donne “Aucun Trouble”.

CDReTIV = ggplot(demence2, aes(x = CDR, y = Volume_cérébral_total)) +
  geom_boxplot(fill = "lightgreen", color = "black") +
  labs(x = "CDR", y = "Volume cérébral total", title = "Distribution du volume cérébral total par CDR")

CDReTIV %>% plotly::ggplotly()

Ici, on peut voir que pour le volume cérébral total, plus il baisse, plus il semble y avoir de personne touché par les démences.

CDRASF = ggplot(demence2, aes(x = CDR, y = Indice_atrophie)) +
  geom_boxplot(fill = "darkred", color = "black") +
  labs(x = "CDR", y = "ASF", title = "Distribution de l'indice d'atrophie par CDR")
CDRASF %>% plotly::ggplotly()

Pour l’indice d’atrophie, il ne semble pas y avoir de différences significatives à première vue.

Pour continuer l’analyse, on peut calculer le niveau de corrélation entre notre variable dépendante et les autres.

Corrélation de point-bisériale, CDR + X

La corrélation point-bisériale mesure la relation entre une variable continue et une variable binaire. Un coefficient proche de +1 ou -1 indique une corrélation forte, tandis qu’une valeur proche de zéro suggère une faible corrélation. L’utilisation de cette méthode peut nous permettre d’avoir un premier aperçu des relations possibles entre notre variable dépendante et nos variables indépendantes.


    FORcorb <- demence2 
    # créer une variable binaire à partir de CDR
    FORcorb$CDR_binaire <- ifelse(FORcorb$CDR == "Dément", 1, 0)
    
    FORcorb$CDR_numeric <- FORcorb$CDR
    
    # sélection des variables pertinentes
    variables_quantitatives <- FORcorb[, c("Age", "Volume_intercranien", "Volume_cérébral_total", "Indice_atrophie")]
    
    variable_binaire <- as.numeric(FORcorb$CDR_numeric)
    
    # créer une liste pour stocker les résultats
    results <- list()
    
    # on calcule les coefficient
    for (variable in names(variables_quantitatives)) {
      correlation <- cor.test(variable_binaire, variables_quantitatives[[variable]], method = "pearson")
      results[[variable]] <- correlation$estimate
    }
    
# transformer en data frame
results_df <- data.frame(
  Variable = c("Age", "Volume_intercranien", "Volume_cérébral_total", "Indice_atrophie"),
  Correlation = unlist(results)
)

    corbis <- ggplot(results_df, aes(x = Variable, y = Correlation, fill = Correlation)) +
      geom_bar(stat = "identity", color = "black") +
      labs(title = "Coefficient de corrélation point-bisériale avec CDR",
           x = "Variable",
           y = "Coefficient de corrélation") +
      ylim(-1, 1) +  # Définir la limite de l'axe y de -1 à 1
      theme_minimal() +  # Utiliser un thème minimal
      theme(axis.text.x = element_text(angle = 45, hjust = 1)) +  # rotation des étiquettes de l'axe x
      theme(panel.grid.major.y = element_line(color = "gray", linetype = "NULL")) +  # Ajouter une grille en pointillés
      scale_fill_gradient2(low = "blue", mid = "white", high = "red", 
                           midpoint = 0, limits = c(-1, 1), name = "Corrélation")  # Échelle de couleur personnalisée
    
   suppressMessages({
  corbis_plotly <- ggplotly(corbis)
})
    # affichage du graphique Plotly
     suppressMessages(corbis_plotly)

Sur ce graphique, on peut voir que seul le Volume Cérébral total semble, à première vue, être en corrélation avec notre variable dépendante, obtenant 0.33, ce qui rélève une association modéré.

On peut maintenant faire la même choses pour nos variables qualitatives !

Coefficient de Cramér, CDR + X

Le coefficient de Cramér mesure l’association entre deux variables catégorielles. Calculé à partir des tables de contingence, il varie de 0 (aucune association) à 1 (association parfaite). Une valeur proche de 0 indique une faible relation, tandis que près de 1 indique une forte association. Il sert à évaluer la dépendance entre les variables.

# on crée d'abord des tables de contingence pour chaque paire de variables

t1 = table(demence2$sexe, demence2$CDR)
t2 = table(demence2$MMSE,demence2$CDR)
t3 = table(demence2$EDUC,demence2$CDR)
t4 = table(demence2$SES,demence2$CDR)

# calcul des les statistiques d'association pour chaque table
ma1 = assocstats(t1)
ma2 = assocstats(t2)
ma3 = assocstats(t3)
ma4 = assocstats(t4)

data <- data.frame(
  Variable = c("Sexe", "MMSE", "EDUC", "SES"),
  CoefficientdeCramer = c(ma1$cramer, ma2$cramer, ma3$cramer, ma4$cramer)
)
Cramer_V =  c(ma1$cramer, ma2$cramer, ma3$cramer, ma4$cramer)

# créer le graphique à barres
GGCram = ggplot(data, aes(x = Variable, y = CoefficientdeCramer, fill = CoefficientdeCramer)) +
  geom_bar(stat = "identity", position = "dodge", width = 0.7, color = "black") +
  scale_fill_gradient2(low = "darkblue", mid = "white", high = "red", 
                       midpoint = 0.5, limits = c(0, 1), 
                       name = "Indice de Cramér") +
  geom_hline(yintercept = 0.5, linetype = "dashed", color = "black") +
  labs(title = "Cramér en fonction de CDR",
       x = "Variables",
       y = "Cramér's V") +
  theme_bw()

GGCram = GGCram + theme(axis.text.x = element_text(angle = 45, hjust = 1))

GGCram %>% plotly::ggplotly()

On peut voir que le Mini Mental State Examination se dégage des autres, il a une association de 0.46 avec notre variable dépendante, tandis que EDUC, SES et sexe ont une association plus homogène, avec respectivement 0.23, 0.21 et 0.26.

Ces associations sont très intéressantes mais ne permettent que d’avoir un aperçu des résultats que l’on pourrais obtenir, pour avoir plus de précisions, on va faire des modèles prédictifs, à commencer par un CART.

II. CART

Justification

L’arbre de classification et de régression (CART) est un modèle d’apprentissage automatique robuste qui segmente de manière récursive les données en sous-groupes, facilitant ainsi la prise de décisions. Son avantage réside dans sa facilité de lecture et d’interprétation.

Pour chacun des modèles prédictifs créés dans cette étude, une validation croisée a été effectuée en divisant l’échantillon en un ensemble de test (regroupant 20% des données) et un ensemble d’apprentissage (regroupant 80% des données).

En plus des modèles traditionnels, des modèles de type dummy ont été mis en place pour servir de référence lors de l’évaluation des performances des modèles prédictifs, permettant ainsi de comparer leur efficacité.

Il a également été pris en compte des métriques telles que la précision, le rappel, la spécificité et l’AUC (Area Under the Curve) pour évaluer la performance des modèles prédictifs, fournissant ainsi une analyse complète de leur capacité à classifier correctement les données

Finalement, une boucle a été créée pour automatiser le processus d’évaluation des modèles sur différents ensembles de données, garantissant ainsi une analyse exhaustive et reproductible de leur performance. Cette boucle, répétée 100 fois, vise à évaluer les métriques d’évaluation telles que la précision, le rappel, la spécificité et l’AUC, permettant ainsi d’éliminer les variations aléatoires et d’obtenir des résultats robustes.

    nrun <- 100
    res <- c()
    performance_metrics <- list()
    
    # Stocker les noms des métriques
    metric_names <- c("Précision", "Rappel", "Spécificité", "AUC")
    
    for (irun in 1:nrun){
      df <- demence2[sample(nrow(demence2)), ]
      train <- df[1:280, ]
      test <- df[281:354, ]
      
      tree <- rpart(CDR ~ ., train)
      
      preds_dummy <- rep(names(table(train$CDR))[which.max(table(train$CDR))], nrow(test))
      predictions <- predict(tree, test, type="class")
      
      res <- rbind(res, c(
        mean(preds_dummy == test$CDR),
        mean(predictions == test$CDR)
      ))
      
      # calcul des métriques d'évaluation
      conf_matrix <- table(predictions = predictions, actual = test$CDR)
      
      precision <- confusionMatrix(predictions, test$CDR)$byClass["Pos Pred Value"]
      rappel <- confusionMatrix(predictions, test$CDR)$byClass["Sensitivity"]
      specificite <- confusionMatrix(predictions, test$CDR)$byClass["Specificity"]
      roc_curve <- roc(test$CDR, as.numeric(predictions))
      auc_value <- auc(roc_curve)

      performance_metrics[[irun]] <- c(precision, rappel, specificite, auc_value)
    }
    
    # on calcule la moyenne des métriques pour toutes les itérations
    average_metrics <- colMeans(do.call(rbind, performance_metrics), na.rm = TRUE)
    
    # créer un data frame avec les noms des métriques
    metric_summary <- data.frame(
      Métrique = metric_names,
      Moyenne = average_metrics
    )
    
    colnames(res) <- c("dummy", "tree")
    
    means = colMeans(res)
    
    df2 <- data.frame(Model = names(means), Accuracy = means)
    
    
    # créer le ggplot
    CompaCART =   ggplot(df2, aes(x = Model, y = Accuracy, fill = Model)) +
      geom_col() +
      labs(title = "Comparaison entre Dummy et Tree",
           x = "Modèle", y = "Précision") +
      theme_minimal()
    
    CompaCART %>% plotly::ggplotly()

Le dummy a obtenu 0.58, contre 0.73 pour notre modèle, nous pouvons donc continuer notre CART !

Robustesse du modèle

 # créer un ggplot
    recapCART = ggplot(metric_summary, aes(x = Métrique, y = Moyenne, fill = Métrique)) +
      geom_col() +
      labs(title = "CART (100 itérations)",
           x = "", y = "Moyenne") +
        theme_minimal()
    
recapCART <- recapCART + theme(axis.text.x = element_text(angle = 45, hjust = 1))

    recapCART %>% plotly::ggplotly()

Notre modèle a obtenu de très bonnes métriques d’évaluations, avec 0.80 pour l’AUC, 0.83 pour la précision, 0.71 pour le rappel et 0.90 pour la spécificité, cela montre quele modèle a une capacité élevée à discriminer entre les classes positives et négatives, tout en maintenant un bon équilibre entre la précision et le rappel, et en minimisant à la fois les faux positifs et les faux négatifs.

Importance des variables

On peut regarder l’importance des variables dans notre modèles

    tree_global <- rpart(CDR ~ ., df)
# Les importances des variables
importances <- c(Age = 14.44873,
                 EDUC = 33.85687,
                 Indice_atrophie = 16.66886,
                 MMSE = 37.60806,
                 SES = 34.07162,
                 sexe = 22.16193,
                 Volume_intercranien = 22.69176,
                 Volume_cérébral_total = 52.96523)

# Créer un data frame pour les importances
importances_df <- data.frame(Variables = names(importances), Importance = importances)

# Créer le ggplot
ggplot(importances_df, aes(x = Variables, y = Importance)) +
  geom_bar(stat = "identity", fill = "skyblue") +
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
  labs(title = "Importance des variables")

Visualisation de l’arbre de décision

rpart.plot(tree_global)

Interprétation de l’arbre :

  1. MMSE: Le premier nœud divise les individus en fonction de leur score au Mini-Mental State Examination (MMSE).

  2. Si le score MMSE est supérieur à la limite, l’arbre se divise en deux branches, une pour les hommes et une pour les femmes (dénoté par “sexe = M”).

  3. Pour les Femmes:

    • Le nœud suivant regarde le ‘Volume_intracrânien’. Si c’est moins de 1609, il se divise encore selon l’âge.
      • Si l’âge est >= 79, la probabilité de démence augmente (0,37 ou 37%).
      • Pour ceux avec un ‘Volume_intracrânien’ >= 1609, la division se fait selon le ‘Socioeconomic Status’ (SES).
        • Un SES <= 1,4 conduit à une division suivante basée sur le niveau d’éducation (EDUC).
          • Pour un EDUC entre 14 et 16, la probabilité de démence est plus basse (0,10 ou 10%).
  4. Pour les Hommes:

    • Si le ‘Volume_intracrânien’ est moins de 1486, cela amène à une autre division en fonction du ‘Volume_cérébral_total’.
      • Si le ‘Volume_cérébral_total’ est < 0,7, alors la probabilité de démence est de 0,46 (46%).
      • Si le ‘Volume_cérébral_total’ est >= 0,7, et l’âge < 77, la probabilité de démence est plus faible (0,19 ou 19%).
      • Pour un ‘Volume_cérébral_total’ >= 0,77 et un SES entre 3 et 4,5, les probabilités de démence varient de 0,16 (16%) à 0,39 (39%) selon l’âge et le SES.

Pour consolider les résultats du CART, on peut faire un Random Forest, qui utilise le même algorithme.

III. Random Forest

Justification

La forêt aléatoire (Random Forest) est une technique puissante en apprentissage automatique. Elle construit plusieurs arbres de décision lors de l’entraînement et les combine pour obtenir des prédictions plus robustes et précises. Cette méthode segmente les données de manière récursive comme l’arbre de classification et de régression (CART), mais au lieu de s’appuyer sur un seul arbre, elle assemble les résultats de multiples arbres pour produire une prédiction finale.

Nous avons suivi une démarche similaire au CART. Validation croisée avec division en ensembles de test (20%) et d’apprentissage (80%). Des modèles de type dummy ont été utilisés comme référence. Les performances ont été mesurées par précision, rappel, spécificité et AUC. Une boucle a été mise en place, répétée 100 fois pour des résultats robustes.


nrun <- 100
res <- c()
performance_metrics <- list()

# Stocker les noms des métriques
metric_names <- c("Précision", "Rappel", "Spécificité", "AUC")

for (irun in 1:nrun){
  dff <- demence2[sample(nrow(demence2)), ]
  train <- dff[1:280, ]
  test <- dff[281:354, ]
  
  forest <- randomForest(CDR ~ ., data = train)
  
  preds_dummy <- rep(names(table(train$CDR))[which.max(table(train$CDR))], nrow(test))
  predictions <- predict(forest, test)
  
  res <- rbind(res, c(
    mean(preds_dummy == test$CDR),
    mean(predictions == test$CDR)
  ))
  
  # prise en compte des metriques d'evaluation
  conf_matrix <- table(predictions = predictions, actual = test$CDR)
  
  precision <- confusionMatrix(predictions, test$CDR)$byClass["Pos Pred Value"]
  rappel <- confusionMatrix(predictions, test$CDR)$byClass["Sensitivity"]
  specificite <- confusionMatrix(predictions, test$CDR)$byClass["Specificity"]
  roc_curve <- roc(test$CDR, as.numeric(predictions))
  auc_value <- auc(roc_curve)
  
  # Stocker les métriques pour chaque itération
  performance_metrics[[irun]] <- c(precision, rappel, specificite, auc_value)}

Robustesse du modèle

colnames(res) <- c("dummy", "forest")


means = colMeans(res)

df2 <- data.frame(Model = names(means), Accuracy = means)

# Créer le ggplot
CompaRF <- ggplot(df2, aes(x = Model, y = Accuracy, fill = Model)) +
  geom_col() +
  labs(title = "Comparaison entre Dummy et Random Forest",
       x = "Modèle", y = "Précision") +
  theme_minimal()

CompaRF %>% plotly::ggplotly()

Le dummy a obtenu 0.58, contre 0.82 pour notre modèle, nous pouvons donc continuer notre analyse !

average_metrics <- colMeans(do.call(rbind, performance_metrics), na.rm = TRUE)

# créer un data frame avec les noms des métriques
metric_summary <- data.frame(
  Métrique = metric_names,
  Moyenne = average_metrics
)

# créer un ggplot
recapRF <- ggplot(metric_summary, aes(x = Métrique, y = Moyenne, fill = Métrique)) +
  geom_col() +
  labs(title = "Random Forest (100 itérations)",
       x = "", y = "Moyenne") +
     theme_minimal()

recapRF <- recapRF + theme(axis.text.x = element_text(angle = 45, hjust = 1))

recapRF %>% plotly::ggplotly()

Notre modèle a obtenu de très bonnes métriques d’évaluations, avec 0.80 pour l’AUC, 0.83 pour la précision, 0.71 pour le rappel et 0.90 pour la spécificité, cela montre quele modèle a une capacité élevée à discriminer entre les classes positives et négatives, tout en maintenant un bon équilibre entre la précision et le rappel, et en minimisant à la fois les faux positifs et les faux négatifs.

Optimisation du modèle

On peut essayer d’optimiser notre modèle pour qu’il ait d’encore meilleur résultat :

forest_global <- randomForest(CDR ~ ., data = dff, importance = TRUE, ntree = 5000)


plot(forest_global$err.rate[, 1], type = "l", xlab = "nombre d'arbres", ylab = "erreur OOB")

Ici, on voit le niveau d’erreur en ordonnée, pour le nombre d’arbres donnés en abscisse. On peut remarquer que le niveau d’erreur se stabilise autour de 1000 arbres. Nous allons donc choisir ce chiffre pour notre modèle.


ntree <- 1000
# selection du mtry optimal

tuned_rf <- tuneRF(dff[, -which(names(dff) == "CDR")], dff$CDR, ntree = ntree)
mtry = 2  OOB error = 16.1% 
Searching left ...
mtry = 1    OOB error = 22.6% 
-0.4035088 0.05 
Searching right ...
mtry = 4    OOB error = 14.97% 
0.07017544 0.05 
mtry = 8    OOB error = 17.8% 
-0.1886792 0.05 

plot(tuned_rf)

Le paramètre “mtry” contrôle le nombre de variables à considérer à chaque division d’un nœud de l’arbre de décision. Sur ce graphique, on a en ordonnée le niveau d’erreur et en abscisse le mtry. On voit que le mtry optimal est de 4, car c’est celui qui a le niveau d’erreur le plus bas.

On peut donc refaire notre modèle en prenant en compte ces deux paramètres.

Importance des variables

# on peut maintenant spécifier nos paramétres
ntree <- 1000
mtry <- 4

# on crée le modèle avec les paramètres spécifiés
forest_global <- randomForest(CDR ~ ., data = dff, importance = TRUE, ntree = ntree, mtry = mtry)

data <- data.frame(
  Variable = c("Sexe", "Âge", "Éducation", "SES", "MMSE", "Volume_intercranien", "Volume_cérébral_total", "Indice atrophie"),
  MeanDecreaseAccuracy = c(0.04451271, 0.03256549, 0.03458807, 0.03844448, 0.08953097, 0.04785418, 0.07876710, 0.05066611),
  MeanDecreaseGini = c(8.341651, 22.764785, 12.559660, 12.870613, 30.298598, 24.410822, 35.275160, 25.007553)
)

# Graphique pour MeanDecreaseAccuracy
plot_accuracy <- ggplot(data, aes(x = reorder(Variable, MeanDecreaseAccuracy), y = MeanDecreaseAccuracy)) +
  geom_bar(stat = "identity", fill = "skyblue") +
  labs(x = "Variable", y = "Mean Decrease Accuracy", title = "Mean Decrease Accuracy") +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

# Graphique pour MeanDecreaseGini
plot_gini <- ggplot(data, aes(x = reorder(Variable, MeanDecreaseGini), y = MeanDecreaseGini)) +
  geom_bar(stat = "identity", fill = "salmon") +
  labs(x = "Variable", y = "Gini", title = "Gini") +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))
grid.arrange(plot_accuracy, plot_gini, ncol = 2)

En Random Forest, “Mean Decrease Accuracy” (MDA) évalue comment l’exactitude du modèle diminue lorsque les valeurs d’une variable sont permutées, mesurant ainsi son importance pour la prédiction.

En revanche, “Gini” mesure comment une variable réduit l’impureté des nœuds lorsqu’elle est utilisée pour diviser les données, évaluant ainsi son efficacité à séparer les classes. Bien que les deux mesures évaluent l’importance des variables, elles le font à travers des perspectives différentes, elles sont complémentaires et permettent de solidifier les résultats.

Ici, on peut voir que, pour la MDA, l’âge est la variable la moins importante de notre modèle, on pourrait faire deux groupes, avec :

  • Age, Education, SES, Sexe, Volume intercrânien, Indice d’atrophie, qui ont chacun eu des résultats assez homogènes.
  • Volume cérébral total et MMSE, qui survole plus notre modèle.

Pour Gini, on a plutôt trois groupes, avec :

  • Sexe, Education, SES
  • Age, Volume intercrânien et indice d’atrophie
  • MMSE et Volume cérébral total, qui sont une nouvelle fois au dessus en terme d’importance

Maintenant que nous avons une idée de l’importance de nos variables, nous pouvons faire une régression logistique, qui pourrait permettre une analyse plus détaillée.

IV. Régression logistique binaire

Justification

La régression logistique binaire est une extension du modèle linéaire adaptée aux cas où la variable dépendante est binaire. Elle modélise la relation entre les variables indépendantes et la probabilité d’occurrence de l’événement binaire, permettant d’estimer les probabilités de succès pour chaque niveau des variables indépendantes. En utilisant cette méthode, on peut calculer les p-values pour évaluer la significativité des effets des variables indépendantes sur la variable dépendante.

Nous avons adopté une approche semblable aux méthodes précédentes, avec une validation croisée effectuée en divisant les ensembles en test (20%) et en apprentissage (80%). Des modèles de type dummy ont servi de référence. Les performances ont été évaluées en termes de précision, de rappel, de spécificité et d’AUC. Une boucle répétitive, réitérée 100 fois, a été instaurée pour garantir des résultats robustes.

demence2$CDR_binaire <- ifelse(demence2$CDR == "Dément", 1, 0)
  
  demence2 <- subset(demence2, select = -CDR)
  demence2$CDR_binaire <- as.factor(demence2$CDR_binaire)

  nrun <- 1000
  res_lr <- c()
  res_precision <- c()
  res_recall <- c()
  res_specificity <- c()
  res_auc <- c()
  
  for (irun in 1:nrun) {
    dfflr <- demence2[sample(nrow(demence2)), ]
    trainlog <- dfflr[1:280, ]
    testlog <- dfflr[281:354, ]
    colnames(demence2)
    
      suppressWarnings({
    lr <- glm(CDR_binaire ~ sexe + Age + MMSE + Indice_atrophie + Volume_cérébral_total + 
                Indice_atrophie + EDUC + SES, data = trainlog, family = binomial())
  })
      
    
    dummylr <- rep(names(table(trainlog$CDR_binaire))[which.max(table(trainlog$CDR_binaire))], nrow(testlog))
    predictionslr <- predict(lr, testlog, type = "response")
    predictionslr <- ifelse(predictionslr > 0.5, 1, 0)
    
    res_lr <- rbind(res_lr, c(
      mean(dummylr == testlog$CDR_binaire),
      mean(predictionslr == testlog$CDR_binaire)
    ))
    
    # calcul des métriques
    predictions_lr <- predict(lr, testlog, type = "response")
    predictions_lr <- ifelse(predictions_lr > 0.5, 1, 0)
    predictions_lr <- factor(predictions_lr)
    
    confusion_matrix <- confusionMatrix(predictions_lr, testlog$CDR_binaire)
    
    precision <- confusion_matrix$byClass["Pos Pred Value"]
    recall <- confusion_matrix$byClass["Sensitivity"]
    specificity <- confusion_matrix$byClass["Specificity"]
    
    # calcul du ROC
    roc_curve_lr <- roc(testlog$CDR_binaire, as.numeric(predictions_lr))
    auc_value_lr <- auc(roc_curve_lr)
    
    # on store les résultat
    res_precision <- c(res_precision, precision)
    res_recall <- c(res_recall, recall)
    res_specificity <- c(res_specificity, specificity)
    res_auc <- c(res_auc, auc_value_lr)
  }
  
  
  colnames(res_lr) <- c("dummy", "lr")

  
  means_lr <- colMeans(res_lr)
  
  df_lr <- data.frame(Model = names(means_lr), Accuracy = means_lr)

Robustesse du modèle

   # on crée le ggplot pour la comparaison entre Dummy et Logistic Regression
 ggLR = ggplot(df_lr, aes(x = Model, y = Accuracy, fill = Model)) +
    geom_col() +
    labs(title = "Comparaison entre Dummy et LR",
         x = "Modèle", y = "Précision") +
    theme_minimal()
  ggLR

Le dummy a de moins bons résultat que le modèle, on peut donc continuer notre analyse !

 # on prend les moyenne des métriques
  mean_precision <- mean(res_precision)
  mean_recall <- mean(res_recall)
  mean_specificity <- mean(res_specificity)
  mean_auc <- mean(res_auc)
  
  # data frame des métrique
  df_metrics <- data.frame(Métrique = c("Precision", "Rappel", "Specificité", "AUC"),
                           Mean_Value = c(mean_precision, mean_recall, mean_specificity, mean_auc))
  
  # graph des métriques
  RecapRLB = ggplot(df_metrics, aes(x = Métrique, y = Mean_Value, fill = Métrique)) +
    geom_bar(stat = "identity") +
    labs(title = "Régression Logistique (100 itérations)",
         x = "", y = "Moyenne") +
        theme_minimal()
  
RecapRLB <- RecapRLB + theme(axis.text.x = element_text(angle = 45, hjust = 1))
RecapRLB%>% plotly::ggplotly()

Le modèle a de très bons résultats pour l’AUC, la précision, et le rappel, avec respectivement 0.72, 0.74 et 0.87. Toutefois, la sensbilité est plus basse, avec 0.58, cela indique que le modèle pourrait manquer certaines observations positives.


{
  lr_global <- glm(CDR_binaire ~ sexe + Age + MMSE + EDUC + 
                   Volume_intercranien + Indice_atrophie + SES + Volume_cérébral_total, 
                   data = dfflr, maxit = 1000, family = binomial())
}
Avis : glm.fit: des probabilités ont été ajustées numériquement à 0 ou 1

Ce message d’avis apparait car R nous prévient que des variables sont certainement multicolinéaires (la multicolinéarité des variables rend ininterprétable les résultats en régression logistique), nous prendrons cela en compte après avoir affiner notre modèle.

suppressWarnings({
  Anova(lr_global)
})
Analysis of Deviance Table (Type II tests)

Response: CDR_binaire
                      LR Chisq Df Pr(>Chisq)    
sexe                    21.662  1  3.252e-06 ***
Age                     10.340  1   0.001301 ** 
MMSE                    54.956  2  1.166e-12 ***
EDUC                    11.614  4   0.020466 *  
Volume_intercranien      0.018  1   0.892697    
Indice_atrophie          0.272  1   0.602225    
SES                     12.709  4   0.012787 *  
Volume_cérébral_total   21.446  1  3.639e-06 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Sélection de variables par étapes

Pour assurer l’interprétabilité des résultats de la régression logistique, il est impératif que toutes les variables de notre modèle soient significatives (ici, nous prendrons un seuil alpha égale à 5%). Ainsi, pour atteindre cet objectif, on retire une à une les variables non significatives (car le retrait d’une variable peut modifier le niveau de significativité des autres, impactant ainsi l’analyse globale du modèle), en commençant par éliminer celle qui présente la p-value la moins significative. Ce processus garantit que seules les variables les plus pertinentes demeurent dans le modèle final.

Pour commencer, on retire donc le volume intercrânien :


lr_global <- glm(CDR_binaire ~ sexe + Age + MMSE + EDUC + 
                    Indice_atrophie + SES + Volume_cérébral_total, 
                   data = dfflr, maxit = 1000, family = binomial())
Avis : glm.fit: des probabilités ont été ajustées numériquement à 0 ou 1
{
  lr_global <- glm(CDR_binaire ~ sexe + Age + MMSE + EDUC + 
                    Indice_atrophie + SES + Volume_cérébral_total, 
                   data = dfflr, maxit = 1000, family = binomial())
}
Avis : glm.fit: des probabilités ont été ajustées numériquement à 0 ou 1
suppressWarnings({
  Anova(lr_global)
})
Analysis of Deviance Table (Type II tests)

Response: CDR_binaire
                      LR Chisq Df Pr(>Chisq)    
sexe                    21.794  1  3.036e-06 ***
Age                     10.570  1   0.001149 ** 
MMSE                    55.413  2  9.273e-13 ***
EDUC                    11.769  4   0.019153 *  
Indice_atrophie         10.519  1   0.001182 ** 
SES                     12.838  4   0.012097 *  
Volume_cérébral_total   21.791  1  3.040e-06 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

On peut voir que les p-values de nos variables ont évolué, toutes sont devenues significatives, ce qui est une bonne nouvelle. Toutefois, on ne peut toujours pas interpréter notre modèle de régression logistique. Avant cela, il est nécessaire de prendre en compte la possible multicollinéarité des variables.

Prise en compte du VIF

Le VIF (Variance Inflation Factor) en régression est une mesure de l’ampleur à laquelle la variance d’un coefficient de régression est augmentée en raison de la corrélation entre les prédicteurs dans le modèle. Un VIF élevé suggère une forte multicollinéarité entre les variables explicatives, ce qui peut rendre les estimations de coefficients peu fiables. On peut ainsi regarder les VIFs de nos variables :

vif(lr_global)
                              GVIF Df GVIF^(1/(2*Df))
sexe                  2.019870e+00  1        1.421221
Age                   1.812819e+00  1        1.346410
MMSE                  1.000000e+00  2        1.000000
EDUC                  1.067767e+07  4        7.560657
Indice_atrophie       2.164453e+00  1        1.471208
SES                   1.181484e+07  4        7.656909
Volume_cérébral_total 2.015482e+00  1        1.419677

On peut créer un GGplot pour que ce soit plus parlant :

# créer un ggplot pour mieux visualiser
data <- data.frame(
  Variable = c("sexe", "Age", "MMSE", "EDUC", "Volume_cérébral_t", "Indice_atrophie", "SES"),
  GVIF = c(1.421221, 1.346410, 1.000000, 7.560657, 1.419677, 1.471208, 7.656909)
)

# Graphique
ggvif = ggplot(data, aes(x = Variable, y = GVIF, fill = Variable)) +
  geom_bar(stat = "identity", position = "dodge") +
  geom_hline(yintercept = 5, color = "red", linetype = "solid") + # Ligne rouge à y = 5
  labs(title = "GVIF pour chaque variable",
       x = "Variable",
       y = "GVIF Raised") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

ggvif %>% plotly::ggplotly()

Dans la littérature, il est couramment admis que le VIF peut poser problème lorsque sa valeur dépasse 5. Cette limite est représentée par la ligne rouge sur le graphique. Ici, on peut voir que le niveau d’éducation et le niveau socio-économique posent problème, nous allons retirer la variable qui a le VIF le plus élevé, à savoir le niveau socio-économique, qui est de 7.65.


lr_global2 <- glm(CDR_binaire ~ sexe + Age + MMSE + EDUC + 
                   Volume_cérébral_total + Indice_atrophie, 
                   data = dfflr, maxit = 1000, family = binomial())

vif(lr_global2)
                          GVIF Df GVIF^(1/(2*Df))
sexe                  1.754377  1        1.324529
Age                   1.759665  1        1.326524
MMSE                  1.000000  2        1.000000
EDUC                  1.201903  4        1.023255
Volume_cérébral_total 1.902207  1        1.379205
Indice_atrophie       1.967118  1        1.402540
# on va créer un ggplot pour mieux visualiser
data <- data.frame(
  Variable = c("sexe", "Indice_atrophie", "MMSE", "Volume_substance_blanche", "Indice_atrophie", "EDUC"),
  GVIF = c(1.324529, 1.402540, 1.000000, 1.379205, 1.419677, 1.023255)
)

# Graphique
ggvif2 = ggplot(data, aes(x = Variable, y = GVIF, fill = Variable)) +
  geom_bar(stat = "identity", position = "dodge") +
  geom_hline(yintercept = 5, color = "red", linetype = "solid") + # Ligne rouge à y = 5
  labs(title = "GVIF pour chaque variable",
       x = "Variable",
       y = "Niveau") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

ggvif2 %>% plotly::ggplotly()

Sans cette variable, tous les VIFS sont inférieurs à 1,5 et ne posent donc plus de problème. On peut donc continuer l’analyse et recalculer nos p-values.

Modèle final

Anova(lr_global2)
Analysis of Deviance Table (Type II tests)

Response: CDR_binaire
                      LR Chisq Df Pr(>Chisq)    
sexe                    22.386  1  2.230e-06 ***
Age                      7.946  1   0.004820 ** 
MMSE                    50.322  2  1.182e-11 ***
EDUC                    12.490  4   0.014058 *  
Volume_cérébral_total   20.794  1  5.115e-06 ***
Indice_atrophie         10.406  1   0.001256 ** 
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Maintenant que nous avons vérifié toutes nos conditions, nous pouvons interpréter notre modèle final. Nous avons donc retiré le volume intercranien ainsi que le niveau socio-économique, nous laissant :

“sexe”, “MMSE”, “Volume_cérébral_total”, qui ont des p-values très faibles (inférieures à 0,001), ce qui est indiqué par trois étoiles (***), suggérant une forte association avec la variable dépendante.

Les variables “Age” et “Indice_atrophie” ont des p-valeurs de 0.004820 et 0.001256 respectivement, ce qui est indiqué par deux étoiles, suggérant une association significative, mais avec un niveau de confiance légèrement moins élevé que celles avec trois étoiles.

Ce qui nous laisse la variable “EDUC”, qui, bien que présentant une p-valeur légèrement plus élevée que les autres variables dans notre analyse (0,014058), reste statistiquement significative au niveau de confiance de 95%.

Vérification des conditions d’utilisations

Dans la régression logistique, un modèle linéaire généralisé, l’adéquation est évaluée en examinant les résidus de Pearson et de déviance. Les résidus de Pearson mesurent la différence entre les valeurs observées et attendues, tandis que les résidus de déviance évaluent l’ajustement du modèle aux données. Ces résidus sont souvent testés à l’aide de distributions du chi-deux pour détecter les points atypiques ou mal ajustés dans les données.

binnedplot(fitted(lr_global2), residuals(lr_global2, type = "response"))

Le graphique produit par binnedplot indique un intervalle de prédiction à 95% (lignes grises) pour les résidus moyens. Ainsi, si le modèle binomial est bon, environ 95% des résidus devraient se trouver à l’intérieur de cet intervalle. Ici, c’est le cas de 13 résidus sur 16, soit 81.25%, ce qui est moins performant de ce qu’on attendrait dans l’idéal !

L’estimation des paramètres des modèles linéaires généralisés n’utilise pas la méthode des moindres carrés, ce qui fait que le R², couramment utilisé en modèle linaire générale pour juger du degré d’efficacité du modèle, n’a pas de sens ici.

Pour pallier cela, on peut utiliser un pseudo R², plusieurs existe, on a par exemple le R² de McFadden, qui se calcule tel que la fraction de la déviance du modèle nul expliquée par le modèle incluant les prédicteurs.

pseudo_R2 <- 1 - lr_global2$deviance/lr_global2$null.deviance
pseudo_R2
[1] 0.3249663

On obtient un R² ajusté de 0.32. Le R² ajusté va de 0 à 1, 0 veut dire que le modèle ne parvient pas à expliquer quoi que ce soit en fonction de la variable dépendante, et à 1 qu’il y arrive parfaitement, ce qui indique un ajustement modéré du modèle aux données.

Pour être sûr, on peut utiliser une autre méthode qui calcule le R² ajusté, quantifie la différence entre la valeur moyenne prédite pour les observations où la variable dépendante est égale à 1 et celle pour les observations où la variable dépendante est égale à 0. Cela permet d’évaluer la capacité du modèle à différencier les deux catégories de réponse. ainsi, il mesure la force de prédiction du modèle en termes de différenciation entre les deux niveaux de la variable dépendante.


r2_tjur <- mean(lr_global2$fitted.values[lr_global2$y == 1]) - mean(lr_global2$fitted.values[lr_global2$y == 0])
r2_tjur
[1] 0.36894

Avec cette méthode, on obtient 0.36, ce qui est assez semblable au R² de McFadden calculé précedemment et consolide ce résultat.

En conclusion, notre analyse révèle des associations significatives entre le sexe, l’âge, le MMSE, le crébral total, l’indice d’atrophie et le niveau d’éducation avec la variable dépendante, suggérant leur importance dans notre modèle.

La vérification des conditions d’applications montre un résultat en dessous de ce qu’on aurait pu attendre pour Les résidus de pearson, et un R² ajusté montrant une efficacité modéré, les résultats de notre régression logistique binomiale, bien que très intéressants, sont donc à prendre avec des pincettes.

V. Analyse discriminante linéaire (LDA)

Justification

L’analyse discriminante linéaire offre une méthode efficace pour simplifier les modèles, améliorer la précision de la classification et fournir des résultats interprétables. Pour effectuer une LDA, il est nécessaire que toutes les variables soient quantitatives, et il ne doit pas y avoir de colinéarité entre les variables.

Comme nous avons des variables qualitatives, nous allons d’abord faire une Analyse Factorielle des Correspondances (AFC).

AFC

Nous avons vu lors de la régression logistique qu’il y avait une multicolinéarité entre la variable représentant le niveau d’éducation (EDUC) et la variable représentant le niveau socio-culturel (SES). Comme précedemment, nous allons donc retirer la variable qui avait le VIF le plus haut, c’est à dire le niveau socio-culturel.

Cela dit, on peut faire notre AFC :

 data_afc <- demence2[, c("sexe", "MMSE", "EDUC")]
  
  afc_result <- MCA(data_afc, graph = FALSE)
 
  head(afc_result$ind$coord)
       Dim 1      Dim 2       Dim 3      Dim 4       Dim 5
1  0.2145599  0.5027782 -0.07376176 -0.6477649 -0.19594701
2  0.2145599  0.5027782 -0.07376176 -0.6477649 -0.19594701
6 -0.8155078 -0.1621775  0.50210218 -0.1128201  0.62699299
7 -0.8155078 -0.1621775  0.50210218 -0.1128201  0.62699299
8  0.1340800  0.2572015 -0.16953925  0.6477649  0.08310553
9  0.1340800  0.2572015 -0.16953925  0.6477649  0.08310553

On peut voir qu’on a cinq dimensions dans notre AFC, ceux-ci représentent nos variables qualitatives, on va donc les inclure dans notre jeu de données de base, puis retirer les variables qualitatives afin de pouvoir faire notre LDA.

 lda_input <- as.data.frame(afc_result$ind$coord[, paste0("Dim ", 1:5)]) 
  demence2 <- cbind(demence2, lda_input)
  demence2 <- demence2[, -c(1, 3, 4, 5)]

Préparation des données pour la LDA

Cela fait, on peut préparer nos données pour faire la LDA :

  gp = as.factor(demence2$CDR)
  matrice = as.matrix(demence2)
  data = data.frame(group=demence2$CDR, matrice)
  colnames(data)
 [1] "group"                 "Age"                  
 [3] "Volume_intercranien"   "Volume_cérébral_total"
 [5] "Indice_atrophie"       "CDR_binaire"          
 [7] "Dim.1"                 "Dim.2"                
 [9] "Dim.3"                 "Dim.4"                
[11] "Dim.5"                
  data$Age <- as.numeric(data$Age)
  
  data$Volume_intercranien<- as.numeric(data$Volume_intercranien)
  data$Volume_cérébral_total <- as.numeric(data$Volume_cérébral_total)
  data$Indice_atrophie <- as.numeric(data$Indice_atrophie)
  data$Dim.1 <- as.numeric(data$Dim.1)
  data$Dim.2 <- as.numeric(data$Dim.2)
  data$Dim.3 <- as.numeric(data$Dim.3)
  data$Dim.4 <- as.numeric(data$Dim.4)
  data$Dim.5 <- as.numeric(data$Dim.5)
  
  colonnes_numeriques <- sapply(data, is.numeric)
  DataCR<- scale(data[,colonnes_numeriques])

  data2 <- as.data.frame(DataCR)

  data2$CDR <- demence2$CDR_binaire

  data2$CDR <- as.factor(data2$CDR)


  gp = as.factor(data2$CDR)
  Datalda = data.frame(group=gp,data2)

Mise en place de la LDA

Comme pour les méthodes précédentes, on fait de la validation croisée, avec 80 % pour l’entraînement et 20 % pour le test, puis on compare notre modèle à un modèle dummy et on calcule les métriques d’évaluation sur l’échantillon test, avec une boucle de 100 répétitions.

  # créer une liste vide pour stocker les performances
  performances_metrics <- list()
  
    # boucle sur les itérations
  set.seed(100)
  nrun <- 100
  for (irun in 1:nrun){
    df<- data2[sample(nrow(data2)), ]
    
    
    train <- df[1:280, ]
    test <- df[281:354, ]
    reslda <- lda(CDR ~ . , data=train)
    predlda <- predict(reslda,test)
    # matrice de confusion
    confusion_matrix <- table(test$CDR, predlda$class)
    # métriques pour chaque itération de validation croisée
    confusion_matrices <- confusionMatrix(test$CDR, predlda$class)
    
    # métriques de performance
    
    Sensibilite <- confusion_matrices$byClass['Sensitivity']
    Specificite <- confusion_matrices$byClass['Specificity']
    Precision <- confusion_matrices$byClass['Pos Pred Value']
    # calcul l'AUC
    AUC <- roc(test$CDR, as.numeric(predlda$x))$auc
    #stocker les metriques pour chaque iteration
    performances_metrics[[irun]] = c(Sensibilite, Specificite, Precision, AUC)
  }
  metric_noms=c("Sensibilite", "Specificite", "Precision", "AUC")
  #calculer la moyenne des metriques pour toutes les iterations
  avr_metrics=colMeans(do.call(rbind,performances_metrics))
  metric_summary <- data.frame(Metrique = metric_noms,Moyenne = avr_metrics)
 

Robustesse du modèle

 ##créer un ggplot
  recapLDA = ggplot(metric_summary, aes(x = Metrique, y = Moyenne, fill = Metrique)) +
    geom_col() +
    labs(title = "LDA (100 itérations)",
         x = "", y = "Moyenne") +
    theme_minimal()
  recapLDA %>% plotly::ggplotly()

  recapLDA <- recapLDA + theme(axis.text.x = element_text(angle = 45, hjust = 1)) + theme(axis.text.x = element_text(angle = 45, hjust = 1))
  
  
    recapLDA %>% plotly::ggplotly()

Notre LDA démontre des performances solides, avec une AUC de 0,80 indiquant une bonne capacité de discrimination entre les classes positives et négatives. De plus, une précision élevée de 0,90 suggère que la majorité des prédictions positives sont correctes. Bien que la sensibilité soit légèrement plus basse à 0,70, elle indique tout de même une capacité à capturer une proportion significative des observations positives. Enfin, une spécificité de 0,78 souligne la capacité du modèle à identifier correctement les observations négatives.

Résultats


reslda2 <- lda(CDR ~ . , data=df)
predlda2 <- predict(reslda,df)

boxplot(predlda2$x~gp)

Le graphique présente des boîtes à moustaches pour les deux niveaux de notre variable dépendante (0 étant non dément et 1 dément). La médiane du groupe 1 semble légèrement plus élevée que celle du groupe 0, avec davantage de valeurs aberrantes, suggérant une différence entre les deux groupes pour la variable prédite par la LDA. Il y a aussi comme un chevauchement entre les distributions, indiquant une superposition malgré la différence de tendance centrale.

 # extraire les coefficients du modèle LDA
  coefficients <- coef(reslda2)
  
  # créer un dataframe à partir des coefficients
  coefficients_df <- data.frame(
    Variable = rownames(coefficients),
    Coefficient = coefficients[, 1]  # Extraire uniquement la première colonne de coefficients
  )
  
  # créer le graphique avec les coefficients extraits
  ggplot(coefficients_df, aes(x = Variable, y = Coefficient)) +
    geom_bar(stat = "identity", fill = "skyblue", width = 0.5) +
    labs(title = "Coefficients de la dimension discriminante (LD1)",
         x = "", y = "Coefficient") +
    theme_minimal() +
    theme(axis.text.x = element_text(angle = 45, hjust = 1))

Pour interpréter ce graphique, on peut d’abord afficher ce que représente les dimensions de notre AFCm :

             Dim.1 Dim.2 Dim.3  

sexe | 0.240 0.383 0.021 | MMSE | 0.540 0.261 0.435 | EDUC | 0.451 0.548 0.656 |

Celle qui nous intéresse le plus pour l’interprétation, c’est la dimension 1, on peut voir que la variable la plus importante est le MMSE, avec 0.540, suivi par EDUC, avec 0.451, et finalement le sexe, avec 0.240.

Ainsi, le graphique montre les coefficients calculés par la LDA pour chaque variable, on peut voir que le coefficient le plus élevé est la Dimension 1, suivi par le Volume cérébral total, le Volume intercrânien.

VI. Conclusion

grob_recapCART <- ggplotGrob(recapCART)
grob_recapRF <- ggplotGrob(recapRF)
grob_RecapRLB <- ggplotGrob(RecapRLB)
grob_recapLDA <- ggplotGrob(recapLDA)

Chacune des méthodes présentées dans ce travail a globalement bien fonctionné et a apporté des éléments très enrichissants dans les études sur la démence (avec de petites lacunes en spécificité pour la régression logistique).

Toutes ont montré que le MMSE a une place prépondérante dans le modèle proposé, ce qui montre que le test fonctionne bien, ce qui est intéressant car il y a des débats dans la littérature scientifique qui parlent de son bon fonctionnement ou non.

Également, le volume cérébral total est ressorti comme très important pour le CART, le Random Forest et la LDA, ainsi que très significatif pour la régression logistique.

Bien que moins représentés dans les modèles, l’âge (ici de 60 à 96 ans), le niveau socio-économique et le niveau d’éducation (qui vont de pair), ainsi que l’indice d’atrophie, ont montré leur importance et semblent être des facteurs importants dans la compréhension du fonctionnement des démences.

Par contre, le volume intercrânien n’était vraiment pas significatif pour la régression logistique (P.value de 0.89), alors qu’il semble avoir une importance pour la LDA, pour le CART et pour le Random Forest, cela suggère que la régression logisique pourrait ne pas être la méthode la plus fiable pour modéliser la relation entre nos variables indépendantes et la variable dépendante (ce qui ce confirme par les conditions d’applications).

VII. Limite

Dans le cadre de cette étude, il est pertinent de souligner une limitation importante : tous les participants étaient des droitiers. Cette absence de gauchers dans l’échantillon est significative, car la littérature scientifique suggère qu’il existe des différences neurologiques entre les gauchers et les droitiers, notamment en ce qui concerne la latéralisation cérébrale et la structure du cerveau. Prochainement, il pourrait être intéressant d’en inclure afin de voirs si une variable incluant la latéralité pourrait être ou non significative.

VIII. Ouverture

Dans la continuité de cette étude, une exploration plus approfondie des démences pourrait être faites en intégrant de nouvelles variables pertinentes. Parmi celles-ci, on pourrait envisager l’inclusion de marqueurs biologiques tels que les biomarqueurs de la protéine bêta-amyloïde et de la protéine tau, qui sont largement étudiés dans le domaine de la neurologie et pourraient fournir des informations précieuses sur les mécanismes sous-jacents aux démences.

De plus, l’incorporation de variables liées au mode de vie et à l’environnement, telles que l’activité physique, le régime alimentaire, l’exposition aux toxines environnementales, et le niveau de stress, pourrait offrir une perspective plus holistique sur les facteurs de risque et les déterminants des démences.

LS0tDQp0aXRsZTogIsOJdHVkZSBkZXMgZmFjdGV1cnMgcHLDqWRpY3RpZnMgZGUgbGEgZMOpbWVuY2UiDQphdXRob3I6ICJCRUNLIFZhbGVudGluIg0Kb3V0cHV0Og0KICBodG1sX25vdGVib29rOg0KICAgIHRvYzogdHJ1ZQ0KICAgIGRmX3ByaW50OiBwYWdlZA0KICAgIHRoZW1lOiBqb3VybmFsDQogICAgdG9jX2Zsb2F0OiB0cnVlDQogICAgc291cmNlOiBlbWJlZA0KICBodG1sX2RvY3VtZW50Og0KICAgIHRvYzogdHJ1ZQ0KICAgIGRmX3ByaW50OiBwYWdlZA0KLS0tDQoNCmBgYHs9aHRtbH0NCjxzdHlsZT4NCmJvZHl7DQogIHRleHQtYWxpZ246IGp1c3RpZnl9DQo8L3N0eWxlPg0KYGBgDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkNCg0KbGlicmFyeShkYXRhLnRhYmxlKQ0KbGlicmFyeShjYXJldCkNCmxpYnJhcnkocmFuZG9tRm9yZXN0KQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeSh2Y2QpDQpsaWJyYXJ5KHJwYXJ0KQ0KbGlicmFyeShycGFydC5wbG90KQ0KbGlicmFyeShjYXIpDQpsaWJyYXJ5KE1BU1MpDQpsaWJyYXJ5KGdyaWRFeHRyYSkNCmxpYnJhcnkocGxvdGx5KQ0KbGlicmFyeShwUk9DKQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkoRmFjdG9NaW5lUikNCmluc3RhbGwucGFja2FnZXMoImFybSIpDQpsaWJyYXJ5KGFybSkNCg0KDQoNCnRlbXAgPC0gdGVtcGZpbGUoZmlsZWV4dCA9ICIuY3N2IikNCmRvd25sb2FkLmZpbGUoImh0dHBzOi8vZHJpdmUuZ29vZ2xlLmNvbS91Yz9leHBvcnQ9ZG93bmxvYWQmaWQ9MWVhYzVxQ0hkMV93NEttRm54WGJSckE1bVJSNGM4WFh0IiwgdGVtcCkNCmRlbWVuY2UyIDwtIHJlYWQuY3N2KHRlbXApDQoNCmRlbWVuY2UyIDwtIGRlbWVuY2UyWywgLWMoMSwgMiwgMywgNCwgNSwgNyldDQoNCiMgUsOpZHVjdGlvbiBkdSBub21icmUgZGUgbml2ZWF1eCBkZSBsYSB2YXJpYWJsZSBFRFVDDQpkZW1lbmNlMiA8LSBkZW1lbmNlMiAlPiUNCiAgbXV0YXRlKEVEVUMgPSBjYXNlX3doZW4oDQogICAgRURVQyAlaW4lIDY6OCB+ICI2LTgiLA0KICAgIEVEVUMgJWluJSAxMToxMyB+ICIxMS0xMyIsDQogICAgRURVQyAlaW4lIDE0OjE2IH4gIjE0LTE2IiwNCiAgICBFRFVDICVpbiUgMTc6MTggfiAiMTctMTgiLA0KICAgIEVEVUMgJWluJSAyMDoyMyB+ICIyMC0yMyIsDQogICAgVFJVRSB+IGFzLmNoYXJhY3RlcihFRFVDKSAgIyBWYWxldXJzIHF1aSBuZSByZW50cmVudCBkYW5zIGF1Y3VuIGdyb3VwZQ0KICApKQ0KDQoNCmRlbWVuY2UyJENEUiA8LSBpZmVsc2UoZGVtZW5jZTIkQ0RSID09IDAsICJOb25fZMOpbWVudCIsICJEw6ltZW50IikNCg0KaW50ZXJ2YWxzIDwtIGN1dChkZW1lbmNlMiRNTVNFLCBicmVha3MgPSBjKDAsIDE3LCAyMywgMzApLCBsYWJlbHMgPSBjKCJUcm91YmxlX3PDqXbDqHJlIiwgIlRyb3VibGVfbMOpZ2VyIiwgIkF1Y3VuX3Ryb3VibGUiKSwgaW5jbHVkZS5sb3dlc3QgPSBUUlVFKQ0KDQpkZW1lbmNlMiRNTVNFIDwtIGFzLmNoYXJhY3RlcihpbnRlcnZhbHMpDQp0YWJsZShkZW1lbmNlMiRNTVNFKQ0KdGFibGUoZGVtZW5jZTIkQ0RSKQ0KDQpkZW1lbmNlMiRDRFIgPC0gYXMuZmFjdG9yKGRlbWVuY2UyJENEUikNCmRlbWVuY2UyJHNleGUgPC0gZmFjdG9yKGRlbWVuY2UyJHNleGUpDQpkZW1lbmNlMiRTRVMgPC0gZmFjdG9yKGRlbWVuY2UyJFNFUykNCmRlbWVuY2UyJEVEVUMgPC0gZmFjdG9yKGRlbWVuY2UyJEVEVUMpDQpkZW1lbmNlMiRNTVNFIDwtIGZhY3RvcihkZW1lbmNlMiRNTVNFKQ0KdGFibGUoZGVtZW5jZTIkQ0RSKQ0KDQojIEJvdWNsZSBwb3VyIGNvbnZlcnRpciBkZXMgY29sb25uZXMgc3DDqWNpZmlxdWVzIGVuIG51bcOpcmlxdWVzDQpmb3IgKGkgaW4gYygiQWdlIiwgIm5XQlYiLCAiQVNGIiwgImVUSVYiKSkgew0KICBkZW1lbmNlMlssIGldIDwtIGFzLm51bWVyaWMoYXMuY2hhcmFjdGVyKGRlbWVuY2UyWywgaV0pKQ0KfQ0KDQojIFN1cHByaW1lciBsZXMgdmFsZXVycyBtYW5xdWFudGVzDQpkZW1lbmNlMiA8LSBuYS5vbWl0KGRlbWVuY2UyKQ0KDQpjb2xuYW1lcyhkZW1lbmNlMikgPC0gYygic2V4ZSIsICJBZ2UiLCAiRURVQyIsICJTRVMiLCAiTU1TRSIsICJDRFIiLCAiVm9sdW1lX2ludGVyY3JhbmllbiIsICJWb2x1bWVfY8OpcsOpYnJhbF90b3RhbCIsICJJbmRpY2VfYXRyb3BoaWUiKQ0KDQoNCg0KZmFzdGdncGxvdCA8LSBmdW5jdGlvbihkYXRhLCB4X3ZhciwgdGl0bGUsIHhfbGFiZWwsIHlfbGFiZWwpIHsNCiAgZ2dwbG90KGRhdGEsIGFlcyh4ID0gZmFjdG9yKGRhdGFbW3hfdmFyXV0pLCBmaWxsID0gZmFjdG9yKGRhdGFbW3hfdmFyXV0pKSkgKw0KICAgIGdlb21fYmFyKGNvbG9yID0gImJsYWNrIiwgYWxwaGEgPSAwLjgpICsNCiAgICANCiAgICBsYWJzKHRpdGxlID0gdGl0bGUsIHggPSB4X2xhYmVsLCB5ID0geV9sYWJlbCwgZmlsbCA9IHhfdmFyKSArDQogICAgdGhlbWVfbWluaW1hbCgpICsNCiAgICB0aGVtZShheGlzLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyKSwNCiAgICAgICAgICBheGlzLnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNCwgZmFjZSA9ICJib2xkIiksDQogICAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTYsIGZhY2UgPSAiYm9sZCIpKQ0KfQ0KDQoNCmBgYA0KDQoqTGEgbWFqb3JpdMOpIGRlcyBncmFwaGlxdWVzIHByw6lzZW50w6lzIHNvbnQgaW50ZXJhY3RpZnMsIG4naMOpc2l0ZXogcGFzIMOgIHBhc3NlciB2b3RyZSBzb3VyaXMgZGVzc3VzIHBvdXIgYXZvaXIgbGVzIHZhbGV1cnMgZXhhY3RlcyAh8J+ZgioNCg0KIyBJbnRyb2R1Y3Rpb24NCg0KTm90cmUgcmVjaGVyY2hlIHNlIGNvbmNlbnRyZSBzdXIgbCdpZGVudGlmaWNhdGlvbiBkZXMgZmFjdGV1cnMgcHLDqWRpY3RpZnMgZGUgbGEgZMOpbWVuY2UuIE5vdHJlICoqdmFyaWFibGUgZMOpcGVuZGFudGUsIGxlICJDRFIiKiogKENsaW5pY2FsIERlbWVudGlhIFJhdGUpLCBlc3QgdW4gaW5kaWNhdGV1ciBuZXVyb2xvZ2lxdWUgw6l0YWJsaSBlbiAxOTkzIHBhciBNb3JyaXMuIENldHRlIHZhcmlhYmxlIGVzdCBpY2kgYmluYWlyZSwgYXZlYyAwIHBvdXIgbGVzIGluZGl2aWR1cyBub24gYXR0ZWludHMgZGUgZMOpbWVuY2UgZXQgMSBwb3VyIGNldXggcXVpIGxlIHNvbnQuIE5vdXMgZXhhbWlub25zIMOpZ2FsZW1lbnQgcGx1c2lldXJzIGF1dHJlcyB2YXJpYWJsZXMgcG90ZW50aWVsbGVtZW50IHBlcnRpbmVudGVzLCBub3RhbW1lbnQgKipsZSBzZXhlLCBsJ8OiZ2UsIGxlIG5pdmVhdSBkJ8OpZHVjYXRpb24sIGxlIG5pdmVhdSBzb2Npby1jdWx0dXJlbCwgbGUgdm9sdW1lIGPDqXLDqWJyYWwgdG90YWwsIGxlIHZvbHVtZSBpbnRlcmNyw6JuaWVuLCBsZSBuaXZlYXUgZCdhdHJvcGhpZSwgYWluc2kgcXVlIGxlIE1pbmkgTWVudGFsIFN0YXRlIEV4YW1pbmF0aW9uIChNTVNFKSoqLiBCaWVuIHF1ZSBsZSBNTVNFIHNvaXQgdW4gdGVzdCBuZXVyb2xvZ2lxdWUgbGFyZ2VtZW50IHV0aWxpc8OpIGVuIHJhaXNvbiBkZSBzYSByYXBpZGl0w6ksIGlsIGVzdCB0b3V0ZWZvaXMgc291dmVudCBjcml0aXF1w6kgZGFucyBsYSBsaXR0w6lyYXR1cmUgcG91ciBzYSBtb2luZHJlIHByw6ljaXNpb24gZXQgc2EgZmlhYmlsaXTDqSB2YXJpYWJsZS4gTm91cyBhdm9ucyBsJ29wcG9ydHVuaXTDqSBkZSB2w6lyaWZpZXIgY2VzIHBvaW50cyBlbiBsJ2luY2x1YW50IGRhbnMgbm90cmUgw6l0dWRlIHBhcmFsbMOobGVtZW50IGF1IENEUi4NCg0KIyBJLiBBbmFseXNlIGRlc2NyaXB0aXZlDQoNCiMjIEp1c3RpZmljYXRpb24NCg0KTCdhbmFseXNlIGRlc2NyaXB0aXZlIHBlcm1ldCBkZSBjb21wcmVuZHJlIGxhIGRpc3RyaWJ1dGlvbiBldCBsZXMgY2FyYWN0w6lyaXN0aXF1ZXMgZ8OpbsOpcmFsZXMgZGVzIGRvbm7DqWVzLCBvZmZyYW50IGFpbnNpIHVuIGFwZXLDp3UgcHLDqWxpbWluYWlyZSBkZXMgdGVuZGFuY2VzIGV0IGRlcyBhc3NvY2lhdGlvbnMgcG90ZW50aWVsbGVzIGVudHJlIGxlcyB2YXJpYWJsZXMgw6l0dWRpw6llcy4NCg0KSWwgZXN0IGltcG9ydGFudCBkZSBzJ2Fzc3VyZXIgcXVlIGxlcyBuaXZlYXV4IGRlcyB2YXJpYWJsZXMgc29udCBhc3NleiDDqXF1aWxpYnLDqSwgY2FyIHNpbm9uIGlscyBwb3VycmFpZW50IMOqdHJlIHNvdXMtcmVwcsOpc2VudMOpcyBkYW5zIGxlcyBhbmFseXNlcyDDoCB2ZW5pciwgc2kgYydlc3QgbGUgY2FzIGlsIHNlcmEgYWluc2kgaW1wb3J0YW50IGRlIGxlIHByZW5kcmUgZW4gY29tcHRlIGxvcnMgZGUgbCdpbnRlcnByw6l0YXRpb24uDQoNCiMjIEdyYXBoaXF1ZSBkZXMgdmFyaWFibGVzIHF1YWxpdGF0aXZlcw0KDQpQb3VyIHNpbXBsaWZpZXIgbGEgY3LDqWF0aW9uIGRlcyBncmFwaGlxdWVzLCBub3VzIHBvdXZvbnMgw6ljcmlyZSB1bmUgZm9uY3Rpb24gcXVpIGluY2x1dCB0b3VzIGxlcyBwYXJhbcOodHJlcyBuw6ljZXNzYWlyZXMuIENlbGEgbm91cyBwZXJtZXR0cmEgZGUgZ8OpbsOpcmVyIGRlcyBnZ3Bsb3QgcGx1cyByYXBpZGVtZW50Lg0KDQpgYGB7ciBjYXJzfQ0KDQpmYXN0Z2dwbG90IDwtIGZ1bmN0aW9uKGRhdGEsIHhfdmFyLCB0aXRsZSwgeF9sYWJlbCwgeV9sYWJlbCkgew0KICBnZ3Bsb3QoZGF0YSwgYWVzKHggPSBmYWN0b3IoZGF0YVtbeF92YXJdXSksIGZpbGwgPSBmYWN0b3IoZGF0YVtbeF92YXJdXSkpKSArDQogICAgZ2VvbV9iYXIoY29sb3IgPSAiYmxhY2siLCBhbHBoYSA9IDAuOCkgKw0KICAgIA0KICAgIGxhYnModGl0bGUgPSB0aXRsZSwgeCA9IHhfbGFiZWwsIHkgPSB5X2xhYmVsLCBmaWxsID0geF92YXIpICsNCiAgICB0aGVtZV9taW5pbWFsKCkgKw0KICAgIHRoZW1lKGF4aXMudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIpLA0KICAgICAgICAgIGF4aXMudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE0LCBmYWNlID0gImJvbGQiKSwNCiAgICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNiwgZmFjZSA9ICJib2xkIikpDQp9DQoNCmBgYA0KDQpgYGB7cn0NCkNEUiA8LSBmYXN0Z2dwbG90KGRlbWVuY2UyLCAiQ0RSIiwgIlLDqXBhcnRpdGlvbiBwYXIgc2NvcmUgQ0RSIiwgIlNjb3JlIENEUiIsICJOb21icmUgZGUgY2FzIikNCkNEUiAlPiUgcGxvdGx5OjpnZ3Bsb3RseSgpDQpgYGANCg0KTm90cmUgdmFyaWFibGUgZMOpcGVuZGFudGUgYSBwbHVzIGRlIG5vbiBkw6ltZW50IHF1ZSBkZSBkw6ltZW50LCBjZSBxdWkgZXN0IGxvZ2lxdWUgY2FyIGMnZXN0IGxlIGNhcyBkYW5zIGxhIHBvcHVsYXRpb24gZ8OpbsOpcmFsZS4NCg0KYGBge3J9DQpNTVNFIDwtIGZhc3RnZ3Bsb3QoZGVtZW5jZTIsICJNTVNFIiwgIlLDqXBhcnRpdGlvbiBwYXIgc2NvcmUgTU1TRSIsICJTY29yZSBNTVNFIiwgIk5vbWJyZSBkZSBjYXMiKQ0KDQpNTVNFIDwtIE1NU0UgKyB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKQ0KDQpNTVNFICU+JSBwbG90bHk6OmdncGxvdGx5KCkNCmBgYA0KDQpJY2ksIHBvdXIgbGUgTWluaSBNZW50YWwgU3RhdGUsIG9uIHBldXQgdm9pciBxdSdvbiBhIG1vaW5zIGRlIGRpYWdub3N0aWMgZGUgZMOpbWVuY2UgcXVlIHBvdXIgbGUgQ0RSLCBsZXVyIHLDqXN1bHRhdCBuZSBzZW1ibGUgZG9uYyBwYXMgaG9tb2fDqG5lLiANCg0KYGBge3J9DQpFRFVDIDwtIGZhc3RnZ3Bsb3QoZGVtZW5jZTIsICJFRFVDIiwgIlLDqXBhcnRpdGlvbiBwYXIgbml2ZWF1IGQnw6lkdWNhdGlvbiIsICJOaXZlYXUgZCfDqWR1Y2F0aW9uIiwgIk5vbWJyZSBkZSBjYXMiKQ0KDQpFRFVDIDwtIEVEVUMgKyB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKQ0KDQpFRFVDICU+JSBwbG90bHk6OmdncGxvdGx5KCkNCmBgYA0KDQpDZXR0ZSB2YXJpYWJsZSByZXByw6lzZW50ZSBsZSBuaXZlYXUgZCfDqWR1Y2F0aW9ucyBkZSBub3MgcGFydGljaXBhbnRzLCBvbiBwZXV0IHZvaXIgcXVlIGxhIG1ham9yaXTDqSBlc3QgZm9ybcOpIHBhciBkZXV4IGdyb3VwZXMsIGF2ZWMgMTEtMTMgZXQgMTQtMTYsIHN1aXZpIHBhciAxNy0xOCwgZXQgZmluYWxlbWVudCAyMC0yMywgZXQgNi04LCBxdWkgc29udCBjb21wYXJhdGl2ZW1lbnQgYXNzZXogcGV1IHJlcHLDqXNlbnTDqS4NCg0KYGBge3J9DQpnZW5yZSA9IGZhc3RnZ3Bsb3QoZGVtZW5jZTIsICJzZXhlIiwgIlLDqXBhcnRpdGlvbiBwYXIgc2V4ZSIsICJHZW5yZSIsICJOb21icmUgZGUgY2FzIikNCmdlbnJlICU+JSBwbG90bHk6OmdncGxvdGx5KCkNCmBgYA0KDQpMYSByw6lwYXJ0aXRpb24gcGFyIHNleGUgZXN0IGFzc2V6IGhvbW9nw6huZSwgYXZlYyB1biBwZXUgcGx1cyBkZSBmZW1tZXMgcXVlIGQnaG9tbWVzLCBub3RyZSBwb3B1bGF0aW9uIMOpdGFudCDDomfDqSAoZGUgNjAgw6AgOTYgYW5zKSwgYydlc3QgbG9naXF1ZSwgY2FyIGMnZXN0IGF1c3NpIGxlIGNhcyBkYW5zIGxhIHBvcHVsYXRpb24gZ8OpbsOpcmFsZSBwb3VyIGNldHRlIHRyYW5jaGUgZCfDomdlLg0KDQojIyBHcmFwaGlxdWUgZGVzIHZhcmlhYmxlcyBxdWFudGl0YXRpdmVzDQoNCmBgYHtyfQ0KaGlzdF9hZ2UgPC0gZ2dwbG90KGRlbWVuY2UyLCBhZXMoeCA9IEFnZSkpICsNCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAwLjAxLCBmaWxsID0gImdyZWVuIiwgY29sb3IgPSAiYmxhY2siLCBhbHBoYSA9IDAuOCkgKw0KICBsYWJzKHRpdGxlID0gIkRpc3RyaWJ1dGlvbiBkZSBsJ8OiZ2UiLCB4ID0gIiIsIHkgPSAiRnLDqXF1ZW5jZSIpICsNCiAgdGhlbWVfbWluaW1hbCgpDQpoaXN0X2FnZSAlPiUgcGxvdGx5OjpnZ3Bsb3RseSgpDQpgYGANCg0KTGEgcsOpcGFydGl0aW9uIGRlIGwnw6JnZSBkZSBub3RyZSDDqWNhaG50aWxsb24gdmEgZGUgNjAgw6AgOTYgYW5zLg0KDQpgYGB7cn0NCg0KaGlzdF9ud2J2IDwtIGdncGxvdChkZW1lbmNlMiwgYWVzKHggPSBWb2x1bWVfY8OpcsOpYnJhbF90b3RhbCkpICsNCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAwLjAxLCBmaWxsID0gImdyZWVuIiwgY29sb3IgPSAiYmxhY2siLCBhbHBoYSA9IDAuOCkgKw0KICBsYWJzKHRpdGxlID0gIkRpc3RyaWJ1dGlvbiBkdSB2b2x1bWUgY8OpcsOpYnJhbCB0b3RhbCIsIHggPSAiVm9sdW1lIGPDqXLDqWJyYWwgdG90YWwiLCB5ID0gIkZyw6lxdWVuY2UiKSArDQogIHRoZW1lX21pbmltYWwoKQ0KaGlzdF9ud2J2ICU+JSBwbG90bHk6OmdncGxvdGx5KCkNCmBgYA0KTGUgdm9sdW1lIGPDqXLDqWJyYWwgdG90YWwgZGUgbm9zIHBhcnRpY2lwYW50cyBlc3QgY29tcHJpcyBlbnRyZSAwLjY0IGV0IDAuODQuDQoNCg0KYGBge3J9DQpoaXN0X2V0aXYgPC0gZ2dwbG90KGRlbWVuY2UyLCBhZXMoeCA9IFZvbHVtZV9pbnRlcmNyYW5pZW4pKSArDQogIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMTAwLCBmaWxsID0gIm9yYW5nZSIsIGNvbG9yID0gImJsYWNrIiwgYWxwaGEgPSAwLjgpICsNCiAgbGFicyh0aXRsZSA9ICJEaXN0cmlidXRpb24gZHUgdm9sdW1lIGludGVyY3JhbmllbiIsIHggPSAiZVRJViIsIHkgPSAiRnLDqXF1ZW5jZSIpICsNCiAgdGhlbWVfbWluaW1hbCgpDQpoaXN0X2V0aXYgJT4lIHBsb3RseTo6Z2dwbG90bHkoKQ0KYGBgDQpMZSB2b2x1bWUgaW50ZXJjcsOibmllbiBkZSBub3MgcGFydGljaXBhbnRzIGVzdCBvY21wcmlzIGVudHJlIDExMDAgZXQgMjAwMC4NCg0KYGBge3J9DQpoaXN0X2FzZiA8LSBnZ3Bsb3QoZGVtZW5jZTIsIGFlcyh4ID0gSW5kaWNlX2F0cm9waGllKSkgKw0KICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDAuMSwgZmlsbCA9ICJibHVlIiwgY29sb3IgPSAiYmxhY2siLCBhbHBoYSA9IDAuOCkgKw0KICBsYWJzKHRpdGxlID0gIkRpc3RyaWJ1dGlvbiBkZSBsJ2luZGljZSBkJ2F0cm9waGllIiwgeCA9ICJJbmRpY2VfYXRyb3BoaWUiLCB5ID0gIkZyw6lxdWVuY2UiKSArDQogIHRoZW1lX21pbmltYWwoKQ0KaGlzdF9hc2YgJT4lIHBsb3RseTo6Z2dwbG90bHkoKQ0KYGBgDQoNCkwnaW5kaWNlIGQnYXRyb3BoaWUgZGUgbm9zIHBhcnRpY2lwYW50cyBlc3QgY29tcHJpcyBlbnRyZSAwLjkgZXQgMS42Lg0KDQpQb3VyIGFsbGVyIHBsdXMgbG9pbiBkYW5zIGwnYW5hbHlzZSBkZXNjcmlwdGl2ZSwgb24gcGV1dCDDqWdhbGVtZW50IGZhaXJlIGRlcyBncmFwaGlxdWVzIHF1aSBhc3NvY2llbnQgbm90cmUgdmFyaWFibGUgZMOpcGVuZGFudGUsIGxlIENEUiwgYXV4IGF1dHJlcy4NCg0KIyMgR3JhcGhpcXVlIGFzc29jaWFudCBsYSB2YXJpYWJsZSBkw6lwZW5kYW50ZSAoQ0RSKSBldCBsZXMgdmFyaWFibGVzIGluZMOpcGVuZGFudGVzDQoNCmBgYHtyfQ0KQ0RSU0VYRSA9IGdncGxvdChkYXRhID0gZGVtZW5jZTIsIGFlcyh4ID0gQ0RSLCBmaWxsID0gc2V4ZSkpICsNCiAgZ2VvbV9iYXIocG9zaXRpb24gPSAiZG9kZ2UiKSArDQogIGxhYnModGl0bGUgPSAiQ29tcGFyYWlzb24gZGUgQ0RSIHBhciBzZXhlIiwNCiAgICAgICB4ID0gIkNEUiIsDQogICAgICAgeSA9ICJOb21icmUgZGUgcGFydGljaXBhbnRzIiwNCiAgICAgICBmaWxsID0gIlNleGUiKQ0KQ0RSU0VYRSAlPiUgcGxvdGx5OjpnZ3Bsb3RseSgpDQpgYGANCg0KTGUgbml2ZWF1IGRlIGTDqW1lbmNlIHNlbWJsZSDDqnRyZSBwbHVzIMOpbGV2w6kgY2hleiBsZXMgaG9tbWVzIHF1ZSBjaGV6IGxlcyBmZW1tZXMuIFRvdXRlZm9pcywgZGVzIGFuYWx5c2VzIHBsdXMgcG91c3PDqXMgc29udCBuw6ljZXNzYWlyZXMgcG91ciBjb21wcmVuZHJlIGNlIHBow6lub23DqG5lLg0KYGBge3J9DQpDRFJFRFVDID0gZ2dwbG90KGRhdGEgPSBkZW1lbmNlMiwgYWVzKHggPSBDRFIsIGZpbGwgPSBFRFVDKSkgKw0KICBnZW9tX2Jhcihwb3NpdGlvbiA9ICJkb2RnZSIpICsNCiAgbGFicyh0aXRsZSA9ICJDb21wYXJhaXNvbiBkZSBDRFIgcGFyIG5pdmVhdSBkJ8OpZHVjYXRpb24iLA0KICAgICAgIHggPSAiQ0RSIiwNCiAgICAgICB5ID0gIk5vbWJyZSBkZSBwYXJ0aWNpcGFudHMiLA0KICAgICAgIGZpbGwgPSAiRURVQyIpDQoNCkNEUkVEVUMgJT4lIHBsb3RseTo6Z2dwbG90bHkoKQ0KYGBgDQoNClBvdXIgbGUgbml2ZWF1IGQnw6lkdWNhdGlvbiwgaWwgc2VtYmxlcmFpdCBxdWUsIMOgIHByZW1pw6hyZSB2dWUsIHBsdXMgY2VsdWktY2kgZXN0IMOpbGV2w6ksIG1vaW5zIGlsIHJpc3F1ZSBkJ2F2b2lyIHVuZSBkw6ltZW5jZSBwb3VyIHVuZSBwZXJzb25uZS4NCg0KYGBge3J9DQpDRFJTRVMgPSBnZ3Bsb3QoZGF0YSA9IGRlbWVuY2UyLCBhZXMoeCA9IENEUiwgZmlsbCA9IFNFUykpICsNCiAgZ2VvbV9iYXIocG9zaXRpb24gPSAiZG9kZ2UiKSArDQogIGxhYnModGl0bGUgPSAiQ29tcGFyYWlzb24gZGUgQ0RSIHBhciBuaXZlYXUgc29jaW8tw6ljb25vbWlxdWUiLA0KICAgICAgIHggPSAiQ0RSIiwNCiAgICAgICB5ID0gIk5vbWJyZSBkZSBwYXJ0aWNpcGFudHMiLA0KICAgICAgIGZpbGwgPSAiU0VTIikNCg0KQ0RSU0VTICU+JSBwbG90bHk6OmdncGxvdGx5KCkNCmBgYA0KDQpQb3VyIGxlIG5pdmVhdSBzb2Npby3DqWNvbm9taXF1ZSwgaWwgbmUgc2VtYmxlIHBhcyB5IGF2b2lyIGRlIHRlbmRhbmNlIHBhcnRpY3VsacOocmUgcXVpIHNlIGTDqWdhZ2UuDQoNCmBgYHtyfQ0KQ0RSU0VTID0gZ2dwbG90KGRhdGEgPSBkZW1lbmNlMiwgYWVzKHggPSBDRFIsIGZpbGwgPSBNTVNFKSkgKw0KICBnZW9tX2Jhcihwb3NpdGlvbiA9ICJkb2RnZSIpICsNCiAgbGFicyh0aXRsZSA9ICJDb21wYXJhaXNvbiBkZSBDRFIgcGFyIE1NU0UiLA0KICAgICAgIHggPSAiQ0RSIiwNCiAgICAgICB5ID0gIk5vbWJyZSBkZSBwYXJ0aWNpcGFudHMiLA0KICAgICAgIGZpbGwgPSAiTU1TRSIpDQoNCkNEUlNFUyAlPiUgcGxvdGx5OjpnZ3Bsb3RseSgpDQpgYGANCg0KUG91ciBsZSBDRFIgYXZlYyBsZSBNTVNFLCBxdWkgc29udCBkZXV4IHRyb3VibGVzIG5ldXJvbG9naXF1ZXMsIGxlcyB0ZXN0cyBzZW1ibGVudCDDqnRyZSBlbiBkw6lzYWNjb3JkLCBjYXIsIMOgIDEwMCByZXByaXNlcywgbGUgQ0RSIGRvbm5lICJEw6ltZW50IiBjb21tZSBkaWFnbm9zdGljIGFsb3JzIHF1ZSBNTVNFIGRvbm5lICJBdWN1biBUcm91YmxlIi4NCg0KYGBge3J9DQpDRFJlVElWID0gZ2dwbG90KGRlbWVuY2UyLCBhZXMoeCA9IENEUiwgeSA9IFZvbHVtZV9jw6lyw6licmFsX3RvdGFsKSkgKw0KICBnZW9tX2JveHBsb3QoZmlsbCA9ICJsaWdodGdyZWVuIiwgY29sb3IgPSAiYmxhY2siKSArDQogIGxhYnMoeCA9ICJDRFIiLCB5ID0gIlZvbHVtZSBjw6lyw6licmFsIHRvdGFsIiwgdGl0bGUgPSAiRGlzdHJpYnV0aW9uIGR1IHZvbHVtZSBjw6lyw6licmFsIHRvdGFsIHBhciBDRFIiKQ0KDQpDRFJlVElWICU+JSBwbG90bHk6OmdncGxvdGx5KCkNCmBgYA0KDQpJY2ksIG9uIHBldXQgdm9pciBxdWUgcG91ciBsZSB2b2x1bWUgY8OpcsOpYnJhbCB0b3RhbCwgcGx1cyBpbCBiYWlzc2UsIHBsdXMgaWwgc2VtYmxlIHkgYXZvaXIgZGUgcGVyc29ubmUgdG91Y2jDqSBwYXIgbGVzIGTDqW1lbmNlcy4NCg0KYGBge3J9DQpDRFJBU0YgPSBnZ3Bsb3QoZGVtZW5jZTIsIGFlcyh4ID0gQ0RSLCB5ID0gSW5kaWNlX2F0cm9waGllKSkgKw0KICBnZW9tX2JveHBsb3QoZmlsbCA9ICJkYXJrcmVkIiwgY29sb3IgPSAiYmxhY2siKSArDQogIGxhYnMoeCA9ICJDRFIiLCB5ID0gIkFTRiIsIHRpdGxlID0gIkRpc3RyaWJ1dGlvbiBkZSBsJ2luZGljZSBkJ2F0cm9waGllIHBhciBDRFIiKQ0KQ0RSQVNGICU+JSBwbG90bHk6OmdncGxvdGx5KCkNCmBgYA0KDQpQb3VyIGwnaW5kaWNlIGQnYXRyb3BoaWUsIGlsIG5lIHNlbWJsZSBwYXMgeSBhdm9pciBkZSBkaWZmw6lyZW5jZXMgc2lnbmlmaWNhdGl2ZXMgw6AgcHJlbWnDqHJlIHZ1ZS4NCg0KUG91ciBjb250aW51ZXIgbCdhbmFseXNlLCBvbiBwZXV0IGNhbGN1bGVyIGxlIG5pdmVhdSBkZSBjb3Jyw6lsYXRpb24gZW50cmUgbm90cmUgdmFyaWFibGUgZMOpcGVuZGFudGUgZXQgbGVzIGF1dHJlcy4NCg0KIyMgQ29ycsOpbGF0aW9uIGRlIHBvaW50LWJpc8OpcmlhbGUsIENEUiArIFgNCg0KTGEgY29ycsOpbGF0aW9uIHBvaW50LWJpc8OpcmlhbGUgbWVzdXJlIGxhIHJlbGF0aW9uIGVudHJlIHVuZSB2YXJpYWJsZSBjb250aW51ZSBldCB1bmUgdmFyaWFibGUgYmluYWlyZS4gVW4gY29lZmZpY2llbnQgcHJvY2hlIGRlICsxIG91IC0xIGluZGlxdWUgdW5lIGNvcnLDqWxhdGlvbiBmb3J0ZSwgdGFuZGlzIHF1J3VuZSB2YWxldXIgcHJvY2hlIGRlIHrDqXJvIHN1Z2fDqHJlIHVuZSBmYWlibGUgY29ycsOpbGF0aW9uLiBMJ3V0aWxpc2F0aW9uIGRlIGNldHRlIG3DqXRob2RlIHBldXQgbm91cyBwZXJtZXR0cmUgZCdhdm9pciB1biBwcmVtaWVyIGFwZXLDp3UgZGVzIHJlbGF0aW9ucyBwb3NzaWJsZXMgZW50cmUgbm90cmUgdmFyaWFibGUgZMOpcGVuZGFudGUgZXQgbm9zIHZhcmlhYmxlcyBpbmTDqXBlbmRhbnRlcy4NCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0V9DQoNCiAgICBGT1Jjb3JiIDwtIGRlbWVuY2UyIA0KICAgICMgY3LDqWVyIHVuZSB2YXJpYWJsZSBiaW5haXJlIMOgIHBhcnRpciBkZSBDRFINCiAgICBGT1Jjb3JiJENEUl9iaW5haXJlIDwtIGlmZWxzZShGT1Jjb3JiJENEUiA9PSAiRMOpbWVudCIsIDEsIDApDQogICAgDQogICAgRk9SY29yYiRDRFJfbnVtZXJpYyA8LSBGT1Jjb3JiJENEUg0KICAgIA0KICAgICMgc8OpbGVjdGlvbiBkZXMgdmFyaWFibGVzIHBlcnRpbmVudGVzDQogICAgdmFyaWFibGVzX3F1YW50aXRhdGl2ZXMgPC0gRk9SY29yYlssIGMoIkFnZSIsICJWb2x1bWVfaW50ZXJjcmFuaWVuIiwgIlZvbHVtZV9jw6lyw6licmFsX3RvdGFsIiwgIkluZGljZV9hdHJvcGhpZSIpXQ0KICAgIA0KICAgIHZhcmlhYmxlX2JpbmFpcmUgPC0gYXMubnVtZXJpYyhGT1Jjb3JiJENEUl9udW1lcmljKQ0KICAgIA0KICAgICMgY3LDqWVyIHVuZSBsaXN0ZSBwb3VyIHN0b2NrZXIgbGVzIHLDqXN1bHRhdHMNCiAgICByZXN1bHRzIDwtIGxpc3QoKQ0KICAgIA0KICAgICMgb24gY2FsY3VsZSBsZXMgY29lZmZpY2llbnQNCiAgICBmb3IgKHZhcmlhYmxlIGluIG5hbWVzKHZhcmlhYmxlc19xdWFudGl0YXRpdmVzKSkgew0KICAgICAgY29ycmVsYXRpb24gPC0gY29yLnRlc3QodmFyaWFibGVfYmluYWlyZSwgdmFyaWFibGVzX3F1YW50aXRhdGl2ZXNbW3ZhcmlhYmxlXV0sIG1ldGhvZCA9ICJwZWFyc29uIikNCiAgICAgIHJlc3VsdHNbW3ZhcmlhYmxlXV0gPC0gY29ycmVsYXRpb24kZXN0aW1hdGUNCiAgICB9DQogICAgDQojIHRyYW5zZm9ybWVyIGVuIGRhdGEgZnJhbWUNCnJlc3VsdHNfZGYgPC0gZGF0YS5mcmFtZSgNCiAgVmFyaWFibGUgPSBjKCJBZ2UiLCAiVm9sdW1lX2ludGVyY3JhbmllbiIsICJWb2x1bWVfY8OpcsOpYnJhbF90b3RhbCIsICJJbmRpY2VfYXRyb3BoaWUiKSwNCiAgQ29ycmVsYXRpb24gPSB1bmxpc3QocmVzdWx0cykNCikNCg0KICAgIGNvcmJpcyA8LSBnZ3Bsb3QocmVzdWx0c19kZiwgYWVzKHggPSBWYXJpYWJsZSwgeSA9IENvcnJlbGF0aW9uLCBmaWxsID0gQ29ycmVsYXRpb24pKSArDQogICAgICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgY29sb3IgPSAiYmxhY2siKSArDQogICAgICBsYWJzKHRpdGxlID0gIkNvZWZmaWNpZW50IGRlIGNvcnLDqWxhdGlvbiBwb2ludC1iaXPDqXJpYWxlIGF2ZWMgQ0RSIiwNCiAgICAgICAgICAgeCA9ICJWYXJpYWJsZSIsDQogICAgICAgICAgIHkgPSAiQ29lZmZpY2llbnQgZGUgY29ycsOpbGF0aW9uIikgKw0KICAgICAgeWxpbSgtMSwgMSkgKyAgIyBEw6lmaW5pciBsYSBsaW1pdGUgZGUgbCdheGUgeSBkZSAtMSDDoCAxDQogICAgICB0aGVtZV9taW5pbWFsKCkgKyAgIyBVdGlsaXNlciB1biB0aMOobWUgbWluaW1hbA0KICAgICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkgKyAgIyByb3RhdGlvbiBkZXMgw6l0aXF1ZXR0ZXMgZGUgbCdheGUgeA0KICAgICAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9saW5lKGNvbG9yID0gImdyYXkiLCBsaW5ldHlwZSA9ICJOVUxMIikpICsgICMgQWpvdXRlciB1bmUgZ3JpbGxlIGVuIHBvaW50aWxsw6lzDQogICAgICBzY2FsZV9maWxsX2dyYWRpZW50Mihsb3cgPSAiYmx1ZSIsIG1pZCA9ICJ3aGl0ZSIsIGhpZ2ggPSAicmVkIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICBtaWRwb2ludCA9IDAsIGxpbWl0cyA9IGMoLTEsIDEpLCBuYW1lID0gIkNvcnLDqWxhdGlvbiIpICAjIMOJY2hlbGxlIGRlIGNvdWxldXIgcGVyc29ubmFsaXPDqWUNCiAgICANCiAgIHN1cHByZXNzTWVzc2FnZXMoew0KICBjb3JiaXNfcGxvdGx5IDwtIGdncGxvdGx5KGNvcmJpcykNCn0pDQogICAgIyBhZmZpY2hhZ2UgZHUgZ3JhcGhpcXVlIFBsb3RseQ0KICAgICBzdXBwcmVzc01lc3NhZ2VzKGNvcmJpc19wbG90bHkpDQpgYGANCg0KU3VyIGNlIGdyYXBoaXF1ZSwgb24gcGV1dCB2b2lyIHF1ZSBzZXVsIGxlIFZvbHVtZSBDw6lyw6licmFsIHRvdGFsIHNlbWJsZSwgw6AgcHJlbWnDqHJlIHZ1ZSwgw6p0cmUgZW4gY29ycsOpbGF0aW9uIGF2ZWMgbm90cmUgdmFyaWFibGUgZMOpcGVuZGFudGUsIG9idGVuYW50IDAuMzMsIGNlIHF1aSByw6lsw6h2ZSB1bmUgYXNzb2NpYXRpb24gbW9kw6lyw6kuDQoNCk9uIHBldXQgbWFpbnRlbmFudCBmYWlyZSBsYSBtw6ptZSBjaG9zZXMgcG91ciBub3MgdmFyaWFibGVzIHF1YWxpdGF0aXZlcyAhDQoNCiMjIENvZWZmaWNpZW50IGRlIENyYW3DqXIsIENEUiArIFgNCg0KTGUgY29lZmZpY2llbnQgZGUgQ3JhbcOpciBtZXN1cmUgbCdhc3NvY2lhdGlvbiBlbnRyZSBkZXV4IHZhcmlhYmxlcyBjYXTDqWdvcmllbGxlcy4gQ2FsY3Vsw6kgw6AgcGFydGlyIGRlcyB0YWJsZXMgZGUgY29udGluZ2VuY2UsIGlsIHZhcmllIGRlIDAgKGF1Y3VuZSBhc3NvY2lhdGlvbikgw6AgMSAoYXNzb2NpYXRpb24gcGFyZmFpdGUpLiBVbmUgdmFsZXVyIHByb2NoZSBkZSAwIGluZGlxdWUgdW5lIGZhaWJsZSByZWxhdGlvbiwgdGFuZGlzIHF1ZSBwcsOocyBkZSAxIGluZGlxdWUgdW5lIGZvcnRlIGFzc29jaWF0aW9uLiBJbCBzZXJ0IMOgIMOpdmFsdWVyIGxhIGTDqXBlbmRhbmNlIGVudHJlIGxlcyB2YXJpYWJsZXMuDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQ0KIyBvbiBjcsOpZSBkJ2Fib3JkIGRlcyB0YWJsZXMgZGUgY29udGluZ2VuY2UgcG91ciBjaGFxdWUgcGFpcmUgZGUgdmFyaWFibGVzDQoNCnQxID0gdGFibGUoZGVtZW5jZTIkc2V4ZSwgZGVtZW5jZTIkQ0RSKQ0KdDIgPSB0YWJsZShkZW1lbmNlMiRNTVNFLGRlbWVuY2UyJENEUikNCnQzID0gdGFibGUoZGVtZW5jZTIkRURVQyxkZW1lbmNlMiRDRFIpDQp0NCA9IHRhYmxlKGRlbWVuY2UyJFNFUyxkZW1lbmNlMiRDRFIpDQoNCiMgY2FsY3VsIGRlcyBsZXMgc3RhdGlzdGlxdWVzIGQnYXNzb2NpYXRpb24gcG91ciBjaGFxdWUgdGFibGUNCm1hMSA9IGFzc29jc3RhdHModDEpDQptYTIgPSBhc3NvY3N0YXRzKHQyKQ0KbWEzID0gYXNzb2NzdGF0cyh0MykNCm1hNCA9IGFzc29jc3RhdHModDQpDQoNCmRhdGEgPC0gZGF0YS5mcmFtZSgNCiAgVmFyaWFibGUgPSBjKCJTZXhlIiwgIk1NU0UiLCAiRURVQyIsICJTRVMiKSwNCiAgQ29lZmZpY2llbnRkZUNyYW1lciA9IGMobWExJGNyYW1lciwgbWEyJGNyYW1lciwgbWEzJGNyYW1lciwgbWE0JGNyYW1lcikNCikNCkNyYW1lcl9WID0gIGMobWExJGNyYW1lciwgbWEyJGNyYW1lciwgbWEzJGNyYW1lciwgbWE0JGNyYW1lcikNCg0KIyBjcsOpZXIgbGUgZ3JhcGhpcXVlIMOgIGJhcnJlcw0KR0dDcmFtID0gZ2dwbG90KGRhdGEsIGFlcyh4ID0gVmFyaWFibGUsIHkgPSBDb2VmZmljaWVudGRlQ3JhbWVyLCBmaWxsID0gQ29lZmZpY2llbnRkZUNyYW1lcikpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIHBvc2l0aW9uID0gImRvZGdlIiwgd2lkdGggPSAwLjcsIGNvbG9yID0gImJsYWNrIikgKw0KICBzY2FsZV9maWxsX2dyYWRpZW50Mihsb3cgPSAiZGFya2JsdWUiLCBtaWQgPSAid2hpdGUiLCBoaWdoID0gInJlZCIsIA0KICAgICAgICAgICAgICAgICAgICAgICBtaWRwb2ludCA9IDAuNSwgbGltaXRzID0gYygwLCAxKSwgDQogICAgICAgICAgICAgICAgICAgICAgIG5hbWUgPSAiSW5kaWNlIGRlIENyYW3DqXIiKSArDQogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAuNSwgbGluZXR5cGUgPSAiZGFzaGVkIiwgY29sb3IgPSAiYmxhY2siKSArDQogIGxhYnModGl0bGUgPSAiQ3JhbcOpciBlbiBmb25jdGlvbiBkZSBDRFIiLA0KICAgICAgIHggPSAiVmFyaWFibGVzIiwNCiAgICAgICB5ID0gIkNyYW3DqXIncyBWIikgKw0KICB0aGVtZV9idygpDQoNCkdHQ3JhbSA9IEdHQ3JhbSArIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpDQoNCkdHQ3JhbSAlPiUgcGxvdGx5OjpnZ3Bsb3RseSgpDQpgYGANCg0KT24gcGV1dCB2b2lyIHF1ZSBsZSBNaW5pIE1lbnRhbCBTdGF0ZSBFeGFtaW5hdGlvbiBzZSBkw6lnYWdlIGRlcyBhdXRyZXMsIGlsIGEgdW5lIGFzc29jaWF0aW9uIGRlIDAuNDYgYXZlYyBub3RyZSB2YXJpYWJsZSBkw6lwZW5kYW50ZSwgdGFuZGlzIHF1ZSBFRFVDLCBTRVMgZXQgc2V4ZSBvbnQgdW5lIGFzc29jaWF0aW9uIHBsdXMgaG9tb2fDqG5lLCBhdmVjIHJlc3BlY3RpdmVtZW50ICAwLjIzLCAwLjIxIGV0IDAuMjYuDQoNCkNlcyBhc3NvY2lhdGlvbnMgc29udCB0csOocyBpbnTDqXJlc3NhbnRlcyBtYWlzIG5lIHBlcm1ldHRlbnQgcXVlIGQnYXZvaXIgdW4gYXBlcsOndSBkZXMgcsOpc3VsdGF0cyBxdWUgbCdvbiBwb3VycmFpcyBvYnRlbmlyLCBwb3VyIGF2b2lyIHBsdXMgZGUgcHLDqWNpc2lvbnMsIG9uIHZhIGZhaXJlIGRlcyBtb2TDqGxlcyBwcsOpZGljdGlmcywgw6AgY29tbWVuY2VyIHBhciB1biBDQVJULg0KDQojIElJLiBDQVJUDQoNCiMjIEp1c3RpZmljYXRpb24NCg0KTCdhcmJyZSBkZSBjbGFzc2lmaWNhdGlvbiBldCBkZSByw6lncmVzc2lvbiAoQ0FSVCkgZXN0IHVuIG1vZMOobGUgZCdhcHByZW50aXNzYWdlIGF1dG9tYXRpcXVlIHJvYnVzdGUgcXVpIHNlZ21lbnRlIGRlIG1hbmnDqHJlIHLDqWN1cnNpdmUgbGVzIGRvbm7DqWVzIGVuIHNvdXMtZ3JvdXBlcywgZmFjaWxpdGFudCBhaW5zaSBsYSBwcmlzZSBkZSBkw6ljaXNpb25zLiBTb24gYXZhbnRhZ2UgcsOpc2lkZSBkYW5zIHNhIGZhY2lsaXTDqSBkZSBsZWN0dXJlIGV0IGQnaW50ZXJwcsOpdGF0aW9uLg0KDQoqKlBvdXIgY2hhY3VuKiogZGVzIG1vZMOobGVzIHByw6lkaWN0aWZzIGNyw6nDqXMgZGFucyBjZXR0ZSDDqXR1ZGUsICoqdW5lIHZhbGlkYXRpb24gY3JvaXPDqWUqKiBhIMOpdMOpIGVmZmVjdHXDqWUgZW4gZGl2aXNhbnQgbCfDqWNoYW50aWxsb24gZW4gdW4gZW5zZW1ibGUgZGUgdGVzdCAocmVncm91cGFudCAyMCUgZGVzIGRvbm7DqWVzKSBldCB1biBlbnNlbWJsZSBkJ2FwcHJlbnRpc3NhZ2UgKHJlZ3JvdXBhbnQgODAlIGRlcyBkb25uw6llcykuDQoNCkVuIHBsdXMgZGVzIG1vZMOobGVzIHRyYWRpdGlvbm5lbHMsICoqZGVzIG1vZMOobGVzIGRlIHR5cGUgZHVtbXkqKiBvbnQgw6l0w6kgbWlzIGVuIHBsYWNlIHBvdXIgc2VydmlyIGRlIHLDqWbDqXJlbmNlIGxvcnMgZGUgbCfDqXZhbHVhdGlvbiBkZXMgcGVyZm9ybWFuY2VzIGRlcyBtb2TDqGxlcyBwcsOpZGljdGlmcywgcGVybWV0dGFudCBhaW5zaSBkZSBjb21wYXJlciBsZXVyIGVmZmljYWNpdMOpLg0KDQpJbCBhIMOpZ2FsZW1lbnQgw6l0w6kgcHJpcyBlbiBjb21wdGUgZGVzIG3DqXRyaXF1ZXMgdGVsbGVzIHF1ZSAqKmxhIHByw6ljaXNpb24sIGxlIHJhcHBlbCwgbGEgc3DDqWNpZmljaXTDqSBldCBsJ0FVQyoqIChBcmVhIFVuZGVyIHRoZSBDdXJ2ZSkgcG91ciDDqXZhbHVlciBsYSBwZXJmb3JtYW5jZSBkZXMgbW9kw6hsZXMgcHLDqWRpY3RpZnMsIGZvdXJuaXNzYW50IGFpbnNpIHVuZSBhbmFseXNlIGNvbXBsw6h0ZSBkZSBsZXVyIGNhcGFjaXTDqSDDoCBjbGFzc2lmaWVyIGNvcnJlY3RlbWVudCBsZXMgZG9ubsOpZXMNCg0KRmluYWxlbWVudCwgdW5lIGJvdWNsZSBhIMOpdMOpIGNyw6nDqWUgcG91ciBhdXRvbWF0aXNlciBsZSBwcm9jZXNzdXMgZCfDqXZhbHVhdGlvbiBkZXMgbW9kw6hsZXMgc3VyIGRpZmbDqXJlbnRzIGVuc2VtYmxlcyBkZSBkb25uw6llcywgZ2FyYW50aXNzYW50IGFpbnNpIHVuZSBhbmFseXNlIGV4aGF1c3RpdmUgZXQgcmVwcm9kdWN0aWJsZSBkZSBsZXVyIHBlcmZvcm1hbmNlLiBDZXR0ZSBib3VjbGUsIHLDqXDDqXTDqWUgMTAwIGZvaXMsIHZpc2Ugw6Agw6l2YWx1ZXIgbGVzIG3DqXRyaXF1ZXMgZCfDqXZhbHVhdGlvbiB0ZWxsZXMgcXVlIGxhIHByw6ljaXNpb24sIGxlIHJhcHBlbCwgbGEgc3DDqWNpZmljaXTDqSBldCBsJ0FVQywgcGVybWV0dGFudCBhaW5zaSBkJ8OpbGltaW5lciBsZXMgdmFyaWF0aW9ucyBhbMOpYXRvaXJlcyBldCBkJ29idGVuaXIgZGVzIHLDqXN1bHRhdHMgcm9idXN0ZXMuDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQ0KICAgIG5ydW4gPC0gMTAwDQogICAgcmVzIDwtIGMoKQ0KICAgIHBlcmZvcm1hbmNlX21ldHJpY3MgPC0gbGlzdCgpDQogICAgDQogICAgIyBTdG9ja2VyIGxlcyBub21zIGRlcyBtw6l0cmlxdWVzDQogICAgbWV0cmljX25hbWVzIDwtIGMoIlByw6ljaXNpb24iLCAiUmFwcGVsIiwgIlNww6ljaWZpY2l0w6kiLCAiQVVDIikNCiAgICANCiAgICBmb3IgKGlydW4gaW4gMTpucnVuKXsNCiAgICAgIGRmIDwtIGRlbWVuY2UyW3NhbXBsZShucm93KGRlbWVuY2UyKSksIF0NCiAgICAgIHRyYWluIDwtIGRmWzE6MjgwLCBdDQogICAgICB0ZXN0IDwtIGRmWzI4MTozNTQsIF0NCiAgICAgIA0KICAgICAgdHJlZSA8LSBycGFydChDRFIgfiAuLCB0cmFpbikNCiAgICAgIA0KICAgICAgcHJlZHNfZHVtbXkgPC0gcmVwKG5hbWVzKHRhYmxlKHRyYWluJENEUikpW3doaWNoLm1heCh0YWJsZSh0cmFpbiRDRFIpKV0sIG5yb3codGVzdCkpDQogICAgICBwcmVkaWN0aW9ucyA8LSBwcmVkaWN0KHRyZWUsIHRlc3QsIHR5cGU9ImNsYXNzIikNCiAgICAgIA0KICAgICAgcmVzIDwtIHJiaW5kKHJlcywgYygNCiAgICAgICAgbWVhbihwcmVkc19kdW1teSA9PSB0ZXN0JENEUiksDQogICAgICAgIG1lYW4ocHJlZGljdGlvbnMgPT0gdGVzdCRDRFIpDQogICAgICApKQ0KICAgICAgDQogICAgICAjIGNhbGN1bCBkZXMgbcOpdHJpcXVlcyBkJ8OpdmFsdWF0aW9uDQogICAgICBjb25mX21hdHJpeCA8LSB0YWJsZShwcmVkaWN0aW9ucyA9IHByZWRpY3Rpb25zLCBhY3R1YWwgPSB0ZXN0JENEUikNCiAgICAgIA0KICAgICAgcHJlY2lzaW9uIDwtIGNvbmZ1c2lvbk1hdHJpeChwcmVkaWN0aW9ucywgdGVzdCRDRFIpJGJ5Q2xhc3NbIlBvcyBQcmVkIFZhbHVlIl0NCiAgICAgIHJhcHBlbCA8LSBjb25mdXNpb25NYXRyaXgocHJlZGljdGlvbnMsIHRlc3QkQ0RSKSRieUNsYXNzWyJTZW5zaXRpdml0eSJdDQogICAgICBzcGVjaWZpY2l0ZSA8LSBjb25mdXNpb25NYXRyaXgocHJlZGljdGlvbnMsIHRlc3QkQ0RSKSRieUNsYXNzWyJTcGVjaWZpY2l0eSJdDQogICAgICByb2NfY3VydmUgPC0gcm9jKHRlc3QkQ0RSLCBhcy5udW1lcmljKHByZWRpY3Rpb25zKSkNCiAgICAgIGF1Y192YWx1ZSA8LSBhdWMocm9jX2N1cnZlKQ0KDQogICAgICBwZXJmb3JtYW5jZV9tZXRyaWNzW1tpcnVuXV0gPC0gYyhwcmVjaXNpb24sIHJhcHBlbCwgc3BlY2lmaWNpdGUsIGF1Y192YWx1ZSkNCiAgICB9DQogICAgDQogICAgIyBvbiBjYWxjdWxlIGxhIG1veWVubmUgZGVzIG3DqXRyaXF1ZXMgcG91ciB0b3V0ZXMgbGVzIGl0w6lyYXRpb25zDQogICAgYXZlcmFnZV9tZXRyaWNzIDwtIGNvbE1lYW5zKGRvLmNhbGwocmJpbmQsIHBlcmZvcm1hbmNlX21ldHJpY3MpLCBuYS5ybSA9IFRSVUUpDQogICAgDQogICAgIyBjcsOpZXIgdW4gZGF0YSBmcmFtZSBhdmVjIGxlcyBub21zIGRlcyBtw6l0cmlxdWVzDQogICAgbWV0cmljX3N1bW1hcnkgPC0gZGF0YS5mcmFtZSgNCiAgICAgIE3DqXRyaXF1ZSA9IG1ldHJpY19uYW1lcywNCiAgICAgIE1veWVubmUgPSBhdmVyYWdlX21ldHJpY3MNCiAgICApDQogICAgDQogICAgY29sbmFtZXMocmVzKSA8LSBjKCJkdW1teSIsICJ0cmVlIikNCiAgICANCiAgICBtZWFucyA9IGNvbE1lYW5zKHJlcykNCiAgICANCiAgICBkZjIgPC0gZGF0YS5mcmFtZShNb2RlbCA9IG5hbWVzKG1lYW5zKSwgQWNjdXJhY3kgPSBtZWFucykNCiAgICANCiAgICANCiAgICAjIGNyw6llciBsZSBnZ3Bsb3QNCiAgICBDb21wYUNBUlQgPSAgIGdncGxvdChkZjIsIGFlcyh4ID0gTW9kZWwsIHkgPSBBY2N1cmFjeSwgZmlsbCA9IE1vZGVsKSkgKw0KICAgICAgZ2VvbV9jb2woKSArDQogICAgICBsYWJzKHRpdGxlID0gIkNvbXBhcmFpc29uIGVudHJlIER1bW15IGV0IFRyZWUiLA0KICAgICAgICAgICB4ID0gIk1vZMOobGUiLCB5ID0gIlByw6ljaXNpb24iKSArDQogICAgICB0aGVtZV9taW5pbWFsKCkNCiAgICANCiAgICBDb21wYUNBUlQgJT4lIHBsb3RseTo6Z2dwbG90bHkoKQ0KYGBgDQoNCkxlIGR1bW15IGEgb2J0ZW51IDAuNTgsIGNvbnRyZSAwLjczIHBvdXIgbm90cmUgbW9kw6hsZSwgbm91cyBwb3V2b25zIGRvbmMgY29udGludWVyIG5vdHJlIENBUlQgIQ0KDQojIyBSb2J1c3Rlc3NlIGR1IG1vZMOobGUNCg0KYGBge3J9DQogIyBjcsOpZXIgdW4gZ2dwbG90DQogICAgcmVjYXBDQVJUID0gZ2dwbG90KG1ldHJpY19zdW1tYXJ5LCBhZXMoeCA9IE3DqXRyaXF1ZSwgeSA9IE1veWVubmUsIGZpbGwgPSBNw6l0cmlxdWUpKSArDQogICAgICBnZW9tX2NvbCgpICsNCiAgICAgIGxhYnModGl0bGUgPSAiQ0FSVCAoMTAwIGl0w6lyYXRpb25zKSIsDQogICAgICAgICAgIHggPSAiIiwgeSA9ICJNb3llbm5lIikgKw0KICAgICAgICB0aGVtZV9taW5pbWFsKCkNCiAgICANCnJlY2FwQ0FSVCA8LSByZWNhcENBUlQgKyB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKQ0KDQogICAgcmVjYXBDQVJUICU+JSBwbG90bHk6OmdncGxvdGx5KCkNCmBgYA0KDQpOb3RyZSBtb2TDqGxlIGEgb2J0ZW51IGRlIHRyw6hzIGJvbm5lcyBtw6l0cmlxdWVzIGQnw6l2YWx1YXRpb25zLCBhdmVjIDAuODAgcG91ciBsJ0FVQywgMC44MyBwb3VyIGxhIHByw6ljaXNpb24sIDAuNzEgcG91ciBsZSByYXBwZWwgZXQgMC45MCBwb3VyIGxhIHNww6ljaWZpY2l0w6ksIGNlbGEgbW9udHJlIHF1ZWxlIG1vZMOobGUgYSB1bmUgY2FwYWNpdMOpIMOpbGV2w6llIMOgIGRpc2NyaW1pbmVyIGVudHJlIGxlcyBjbGFzc2VzIHBvc2l0aXZlcyBldCBuw6lnYXRpdmVzLCB0b3V0IGVuIG1haW50ZW5hbnQgdW4gYm9uIMOpcXVpbGlicmUgZW50cmUgbGEgcHLDqWNpc2lvbiBldCBsZSByYXBwZWwsIGV0IGVuIG1pbmltaXNhbnQgw6AgbGEgZm9pcyBsZXMgZmF1eCBwb3NpdGlmcyBldCBsZXMgZmF1eCBuw6lnYXRpZnMuDQoNCiMjIEltcG9ydGFuY2UgZGVzIHZhcmlhYmxlcw0KDQpPbiBwZXV0IHJlZ2FyZGVyIGwnaW1wb3J0YW5jZSBkZXMgdmFyaWFibGVzIGRhbnMgbm90cmUgbW9kw6hsZXMgDQoNCmBgYHtyfQ0KICAgIHRyZWVfZ2xvYmFsIDwtIHJwYXJ0KENEUiB+IC4sIGRmKQ0KIyBMZXMgaW1wb3J0YW5jZXMgZGVzIHZhcmlhYmxlcw0KaW1wb3J0YW5jZXMgPC0gYyhBZ2UgPSAxNC40NDg3MywNCiAgICAgICAgICAgICAgICAgRURVQyA9IDMzLjg1Njg3LA0KICAgICAgICAgICAgICAgICBJbmRpY2VfYXRyb3BoaWUgPSAxNi42Njg4NiwNCiAgICAgICAgICAgICAgICAgTU1TRSA9IDM3LjYwODA2LA0KICAgICAgICAgICAgICAgICBTRVMgPSAzNC4wNzE2MiwNCiAgICAgICAgICAgICAgICAgc2V4ZSA9IDIyLjE2MTkzLA0KICAgICAgICAgICAgICAgICBWb2x1bWVfaW50ZXJjcmFuaWVuID0gMjIuNjkxNzYsDQogICAgICAgICAgICAgICAgIFZvbHVtZV9jw6lyw6licmFsX3RvdGFsID0gNTIuOTY1MjMpDQoNCiMgQ3LDqWVyIHVuIGRhdGEgZnJhbWUgcG91ciBsZXMgaW1wb3J0YW5jZXMNCmltcG9ydGFuY2VzX2RmIDwtIGRhdGEuZnJhbWUoVmFyaWFibGVzID0gbmFtZXMoaW1wb3J0YW5jZXMpLCBJbXBvcnRhbmNlID0gaW1wb3J0YW5jZXMpDQoNCiMgQ3LDqWVyIGxlIGdncGxvdA0KZ2dwbG90KGltcG9ydGFuY2VzX2RmLCBhZXMoeCA9IFZhcmlhYmxlcywgeSA9IEltcG9ydGFuY2UpKSArDQogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCBmaWxsID0gInNreWJsdWUiKSArDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpICsNCiAgbGFicyh0aXRsZSA9ICJJbXBvcnRhbmNlIGRlcyB2YXJpYWJsZXMiKQ0KYGBgDQoNCiMjIFZpc3VhbGlzYXRpb24gZGUgbCdhcmJyZSBkZSBkw6ljaXNpb24NCg0KYGBge3IgZmlnLmhlaWdodD0xMCwgZmlnLndpZHRoPTE0fQ0KcnBhcnQucGxvdCh0cmVlX2dsb2JhbCkNCmBgYA0KSW50ZXJwcsOpdGF0aW9uIGRlIGwnYXJicmUgOiANCg0KMS4gKipNTVNFKio6IExlIHByZW1pZXIgbsWTdWQgZGl2aXNlIGxlcyBpbmRpdmlkdXMgZW4gZm9uY3Rpb24gZGUgbGV1ciBzY29yZSBhdSBNaW5pLU1lbnRhbCBTdGF0ZSBFeGFtaW5hdGlvbiAoTU1TRSkuIA0KDQoyLiBTaSBsZSBzY29yZSBNTVNFIGVzdCBzdXDDqXJpZXVyIMOgIGxhIGxpbWl0ZSwgbCdhcmJyZSBzZSBkaXZpc2UgZW4gZGV1eCBicmFuY2hlcywgdW5lIHBvdXIgbGVzIGhvbW1lcyBldCB1bmUgcG91ciBsZXMgZmVtbWVzIChkw6lub3TDqSBwYXIgInNleGUgPSBNIikuDQoNCjMuICoqUG91ciBsZXMgRmVtbWVzKio6DQogICAgLSBMZSBuxZN1ZCBzdWl2YW50IHJlZ2FyZGUgbGUgJ1ZvbHVtZV9pbnRyYWNyw6JuaWVuJy4gU2kgYydlc3QgbW9pbnMgZGUgMTYwOSwgaWwgc2UgZGl2aXNlIGVuY29yZSBzZWxvbiBsJ8OiZ2UuDQogICAgICAgIC0gU2kgbCfDomdlIGVzdCA+PSA3OSwgbGEgcHJvYmFiaWxpdMOpIGRlIGTDqW1lbmNlIGF1Z21lbnRlICgwLDM3IG91IDM3JSkuDQogICAgICAgIC0gUG91ciBjZXV4IGF2ZWMgdW4gJ1ZvbHVtZV9pbnRyYWNyw6JuaWVuJyA+PSAxNjA5LCBsYSBkaXZpc2lvbiBzZSBmYWl0IHNlbG9uIGxlICdTb2Npb2Vjb25vbWljIFN0YXR1cycgKFNFUykuDQogICAgICAgICAgICAtIFVuIFNFUyA8PSAxLDQgY29uZHVpdCDDoCB1bmUgZGl2aXNpb24gc3VpdmFudGUgYmFzw6llIHN1ciBsZSBuaXZlYXUgZCfDqWR1Y2F0aW9uIChFRFVDKS4NCiAgICAgICAgICAgICAgICAtIFBvdXIgdW4gRURVQyBlbnRyZSAxNCBldCAxNiwgbGEgcHJvYmFiaWxpdMOpIGRlIGTDqW1lbmNlIGVzdCBwbHVzIGJhc3NlICgwLDEwIG91IDEwJSkuDQoNCjQuICoqUG91ciBsZXMgSG9tbWVzKio6DQogICAgLSBTaSBsZSAnVm9sdW1lX2ludHJhY3LDom5pZW4nIGVzdCBtb2lucyBkZSAxNDg2LCBjZWxhIGFtw6huZSDDoCB1bmUgYXV0cmUgZGl2aXNpb24gZW4gZm9uY3Rpb24gZHUgJ1ZvbHVtZV9jw6lyw6licmFsX3RvdGFsJy4NCiAgICAgICAgLSBTaSBsZSAnVm9sdW1lX2PDqXLDqWJyYWxfdG90YWwnIGVzdCA8IDAsNywgYWxvcnMgbGEgcHJvYmFiaWxpdMOpIGRlIGTDqW1lbmNlIGVzdCBkZSAwLDQ2ICg0NiUpLg0KICAgICAgICAtIFNpIGxlICdWb2x1bWVfY8OpcsOpYnJhbF90b3RhbCcgZXN0ID49IDAsNywgZXQgbCfDomdlIDwgNzcsIGxhIHByb2JhYmlsaXTDqSBkZSBkw6ltZW5jZSBlc3QgcGx1cyBmYWlibGUgKDAsMTkgb3UgMTklKS4NCiAgICAgICAgLSBQb3VyIHVuICdWb2x1bWVfY8OpcsOpYnJhbF90b3RhbCcgPj0gMCw3NyBldCB1biBTRVMgZW50cmUgMyBldCA0LDUsIGxlcyBwcm9iYWJpbGl0w6lzIGRlIGTDqW1lbmNlIHZhcmllbnQgZGUgMCwxNiAoMTYlKSDDoCAwLDM5ICgzOSUpIHNlbG9uIGwnw6JnZSBldCBsZSBTRVMuDQoNClBvdXIgY29uc29saWRlciBsZXMgcsOpc3VsdGF0cyBkdSBDQVJULCBvbiBwZXV0IGZhaXJlIHVuIFJhbmRvbSBGb3Jlc3QsIHF1aSB1dGlsaXNlIGxlIG3Dqm1lIGFsZ29yaXRobWUuDQoNCiMgSUlJLiBSYW5kb20gRm9yZXN0DQoNCiMjIEp1c3RpZmljYXRpb24NCg0KTGEgZm9yw6p0IGFsw6lhdG9pcmUgKFJhbmRvbSBGb3Jlc3QpIGVzdCB1bmUgdGVjaG5pcXVlIHB1aXNzYW50ZSBlbiBhcHByZW50aXNzYWdlIGF1dG9tYXRpcXVlLiBFbGxlIGNvbnN0cnVpdCBwbHVzaWV1cnMgYXJicmVzIGRlIGTDqWNpc2lvbiBsb3JzIGRlIGwnZW50cmHDrm5lbWVudCBldCBsZXMgY29tYmluZSBwb3VyIG9idGVuaXIgZGVzIHByw6lkaWN0aW9ucyBwbHVzIHJvYnVzdGVzIGV0IHByw6ljaXNlcy4gQ2V0dGUgbcOpdGhvZGUgc2VnbWVudGUgbGVzIGRvbm7DqWVzIGRlIG1hbmnDqHJlIHLDqWN1cnNpdmUgY29tbWUgbCdhcmJyZSBkZSBjbGFzc2lmaWNhdGlvbiBldCBkZSByw6lncmVzc2lvbiAoQ0FSVCksIG1haXMgYXUgbGlldSBkZSBzJ2FwcHV5ZXIgc3VyIHVuIHNldWwgYXJicmUsIGVsbGUgYXNzZW1ibGUgbGVzIHLDqXN1bHRhdHMgZGUgbXVsdGlwbGVzIGFyYnJlcyBwb3VyIHByb2R1aXJlIHVuZSBwcsOpZGljdGlvbiBmaW5hbGUuDQoNCk5vdXMgYXZvbnMgc3VpdmkgdW5lIGTDqW1hcmNoZSBzaW1pbGFpcmUgYXUgQ0FSVC4gKipWYWxpZGF0aW9uIGNyb2lzw6llKiogYXZlYyBkaXZpc2lvbiBlbiBlbnNlbWJsZXMgZGUgdGVzdCAoMjAlKSBldCBkJ2FwcHJlbnRpc3NhZ2UgKDgwJSkuIERlcyBtb2TDqGxlcyBkZSB0eXBlICoqZHVtbXkqKiBvbnQgw6l0w6kgdXRpbGlzw6lzIGNvbW1lIHLDqWbDqXJlbmNlLiBMZXMgcGVyZm9ybWFuY2VzIG9udCDDqXTDqSBtZXN1csOpZXMgcGFyICoqcHLDqWNpc2lvbiwgcmFwcGVsLCBzcMOpY2lmaWNpdMOpIGV0IEFVQyoqLiAqKlVuZSBib3VjbGUqKiBhIMOpdMOpIG1pc2UgZW4gcGxhY2UsIHLDqXDDqXTDqWUgMTAwIGZvaXMgcG91ciBkZXMgcsOpc3VsdGF0cyByb2J1c3Rlcy4NCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0V9DQoNCm5ydW4gPC0gMTAwDQpyZXMgPC0gYygpDQpwZXJmb3JtYW5jZV9tZXRyaWNzIDwtIGxpc3QoKQ0KDQojIFN0b2NrZXIgbGVzIG5vbXMgZGVzIG3DqXRyaXF1ZXMNCm1ldHJpY19uYW1lcyA8LSBjKCJQcsOpY2lzaW9uIiwgIlJhcHBlbCIsICJTcMOpY2lmaWNpdMOpIiwgIkFVQyIpDQoNCmZvciAoaXJ1biBpbiAxOm5ydW4pew0KICBkZmYgPC0gZGVtZW5jZTJbc2FtcGxlKG5yb3coZGVtZW5jZTIpKSwgXQ0KICB0cmFpbiA8LSBkZmZbMToyODAsIF0NCiAgdGVzdCA8LSBkZmZbMjgxOjM1NCwgXQ0KICANCiAgZm9yZXN0IDwtIHJhbmRvbUZvcmVzdChDRFIgfiAuLCBkYXRhID0gdHJhaW4pDQogIA0KICBwcmVkc19kdW1teSA8LSByZXAobmFtZXModGFibGUodHJhaW4kQ0RSKSlbd2hpY2gubWF4KHRhYmxlKHRyYWluJENEUikpXSwgbnJvdyh0ZXN0KSkNCiAgcHJlZGljdGlvbnMgPC0gcHJlZGljdChmb3Jlc3QsIHRlc3QpDQogIA0KICByZXMgPC0gcmJpbmQocmVzLCBjKA0KICAgIG1lYW4ocHJlZHNfZHVtbXkgPT0gdGVzdCRDRFIpLA0KICAgIG1lYW4ocHJlZGljdGlvbnMgPT0gdGVzdCRDRFIpDQogICkpDQogIA0KICAjIHByaXNlIGVuIGNvbXB0ZSBkZXMgbWV0cmlxdWVzIGQnZXZhbHVhdGlvbg0KICBjb25mX21hdHJpeCA8LSB0YWJsZShwcmVkaWN0aW9ucyA9IHByZWRpY3Rpb25zLCBhY3R1YWwgPSB0ZXN0JENEUikNCiAgDQogIHByZWNpc2lvbiA8LSBjb25mdXNpb25NYXRyaXgocHJlZGljdGlvbnMsIHRlc3QkQ0RSKSRieUNsYXNzWyJQb3MgUHJlZCBWYWx1ZSJdDQogIHJhcHBlbCA8LSBjb25mdXNpb25NYXRyaXgocHJlZGljdGlvbnMsIHRlc3QkQ0RSKSRieUNsYXNzWyJTZW5zaXRpdml0eSJdDQogIHNwZWNpZmljaXRlIDwtIGNvbmZ1c2lvbk1hdHJpeChwcmVkaWN0aW9ucywgdGVzdCRDRFIpJGJ5Q2xhc3NbIlNwZWNpZmljaXR5Il0NCiAgcm9jX2N1cnZlIDwtIHJvYyh0ZXN0JENEUiwgYXMubnVtZXJpYyhwcmVkaWN0aW9ucykpDQogIGF1Y192YWx1ZSA8LSBhdWMocm9jX2N1cnZlKQ0KICANCiAgIyBTdG9ja2VyIGxlcyBtw6l0cmlxdWVzIHBvdXIgY2hhcXVlIGl0w6lyYXRpb24NCiAgcGVyZm9ybWFuY2VfbWV0cmljc1tbaXJ1bl1dIDwtIGMocHJlY2lzaW9uLCByYXBwZWwsIHNwZWNpZmljaXRlLCBhdWNfdmFsdWUpfQ0KYGBgDQoNCiMjIFJvYnVzdGVzc2UgZHUgbW9kw6hsZQ0KDQpgYGB7cn0NCmNvbG5hbWVzKHJlcykgPC0gYygiZHVtbXkiLCAiZm9yZXN0IikNCg0KDQptZWFucyA9IGNvbE1lYW5zKHJlcykNCg0KZGYyIDwtIGRhdGEuZnJhbWUoTW9kZWwgPSBuYW1lcyhtZWFucyksIEFjY3VyYWN5ID0gbWVhbnMpDQoNCiMgQ3LDqWVyIGxlIGdncGxvdA0KQ29tcGFSRiA8LSBnZ3Bsb3QoZGYyLCBhZXMoeCA9IE1vZGVsLCB5ID0gQWNjdXJhY3ksIGZpbGwgPSBNb2RlbCkpICsNCiAgZ2VvbV9jb2woKSArDQogIGxhYnModGl0bGUgPSAiQ29tcGFyYWlzb24gZW50cmUgRHVtbXkgZXQgUmFuZG9tIEZvcmVzdCIsDQogICAgICAgeCA9ICJNb2TDqGxlIiwgeSA9ICJQcsOpY2lzaW9uIikgKw0KICB0aGVtZV9taW5pbWFsKCkNCg0KQ29tcGFSRiAlPiUgcGxvdGx5OjpnZ3Bsb3RseSgpDQpgYGANCg0KTGUgZHVtbXkgYSBvYnRlbnUgMC41OCwgY29udHJlIDAuODIgcG91ciBub3RyZSBtb2TDqGxlLCBub3VzIHBvdXZvbnMgZG9uYyBjb250aW51ZXIgbm90cmUgYW5hbHlzZSAhDQoNCmBgYHtyfQ0KYXZlcmFnZV9tZXRyaWNzIDwtIGNvbE1lYW5zKGRvLmNhbGwocmJpbmQsIHBlcmZvcm1hbmNlX21ldHJpY3MpLCBuYS5ybSA9IFRSVUUpDQoNCiMgY3LDqWVyIHVuIGRhdGEgZnJhbWUgYXZlYyBsZXMgbm9tcyBkZXMgbcOpdHJpcXVlcw0KbWV0cmljX3N1bW1hcnkgPC0gZGF0YS5mcmFtZSgNCiAgTcOpdHJpcXVlID0gbWV0cmljX25hbWVzLA0KICBNb3llbm5lID0gYXZlcmFnZV9tZXRyaWNzDQopDQoNCiMgY3LDqWVyIHVuIGdncGxvdA0KcmVjYXBSRiA8LSBnZ3Bsb3QobWV0cmljX3N1bW1hcnksIGFlcyh4ID0gTcOpdHJpcXVlLCB5ID0gTW95ZW5uZSwgZmlsbCA9IE3DqXRyaXF1ZSkpICsNCiAgZ2VvbV9jb2woKSArDQogIGxhYnModGl0bGUgPSAiUmFuZG9tIEZvcmVzdCAoMTAwIGl0w6lyYXRpb25zKSIsDQogICAgICAgeCA9ICIiLCB5ID0gIk1veWVubmUiKSArDQogICAgIHRoZW1lX21pbmltYWwoKQ0KDQpyZWNhcFJGIDwtIHJlY2FwUkYgKyB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKQ0KDQpyZWNhcFJGICU+JSBwbG90bHk6OmdncGxvdGx5KCkNCmBgYA0KDQpOb3RyZSBtb2TDqGxlIGEgb2J0ZW51IGRlIHRyw6hzIGJvbm5lcyBtw6l0cmlxdWVzIGQnw6l2YWx1YXRpb25zLCBhdmVjIDAuODAgcG91ciBsJ0FVQywgMC44MyBwb3VyIGxhIHByw6ljaXNpb24sIDAuNzEgcG91ciBsZSByYXBwZWwgZXQgMC45MCBwb3VyIGxhIHNww6ljaWZpY2l0w6ksIGNlbGEgbW9udHJlIHF1ZWxlIG1vZMOobGUgYSB1bmUgY2FwYWNpdMOpIMOpbGV2w6llIMOgIGRpc2NyaW1pbmVyIGVudHJlIGxlcyBjbGFzc2VzIHBvc2l0aXZlcyBldCBuw6lnYXRpdmVzLCB0b3V0IGVuIG1haW50ZW5hbnQgdW4gYm9uIMOpcXVpbGlicmUgZW50cmUgbGEgcHLDqWNpc2lvbiBldCBsZSByYXBwZWwsIGV0IGVuIG1pbmltaXNhbnQgw6AgbGEgZm9pcyBsZXMgZmF1eCBwb3NpdGlmcyBldCBsZXMgZmF1eCBuw6lnYXRpZnMuDQoNCiMjIE9wdGltaXNhdGlvbiBkdSBtb2TDqGxlDQoNCk9uIHBldXQgZXNzYXllciBkJ29wdGltaXNlciBub3RyZSBtb2TDqGxlIHBvdXIgcXUnaWwgYWl0IGQnZW5jb3JlIG1laWxsZXVyIHLDqXN1bHRhdCA6DQoNCmBgYHtyfQ0KZm9yZXN0X2dsb2JhbCA8LSByYW5kb21Gb3Jlc3QoQ0RSIH4gLiwgZGF0YSA9IGRmZiwgaW1wb3J0YW5jZSA9IFRSVUUsIG50cmVlID0gNTAwMCkNCg0KDQpwbG90KGZvcmVzdF9nbG9iYWwkZXJyLnJhdGVbLCAxXSwgdHlwZSA9ICJsIiwgeGxhYiA9ICJub21icmUgZCdhcmJyZXMiLCB5bGFiID0gImVycmV1ciBPT0IiKQ0KDQpgYGANCg0KSWNpLCBvbiB2b2l0IGxlIG5pdmVhdSBkJ2VycmV1ciBlbiBvcmRvbm7DqWUsIHBvdXIgbGUgbm9tYnJlIGQnYXJicmVzIGRvbm7DqXMgZW4gYWJzY2lzc2UuIE9uIHBldXQgcmVtYXJxdWVyIHF1ZSBsZSBuaXZlYXUgZCdlcnJldXIgc2Ugc3RhYmlsaXNlIGF1dG91ciBkZSAxMDAwIGFyYnJlcy4gTm91cyBhbGxvbnMgZG9uYyBjaG9pc2lyIGNlIGNoaWZmcmUgcG91ciBub3RyZSBtb2TDqGxlLg0KDQpgYGB7cn0NCg0KbnRyZWUgPC0gMTAwMA0KDQpgYGANCg0KYGBge3J9DQojIHNlbGVjdGlvbiBkdSBtdHJ5IG9wdGltYWwNCg0KdHVuZWRfcmYgPC0gdHVuZVJGKGRmZlssIC13aGljaChuYW1lcyhkZmYpID09ICJDRFIiKV0sIGRmZiRDRFIsIG50cmVlID0gbnRyZWUpDQoNCnBsb3QodHVuZWRfcmYpDQpgYGANCg0KTGUgcGFyYW3DqHRyZSAibXRyeSIgY29udHLDtGxlIGxlIG5vbWJyZSBkZSB2YXJpYWJsZXMgw6AgY29uc2lkw6lyZXIgw6AgY2hhcXVlIGRpdmlzaW9uIGQndW4gbsWTdWQgZGUgbCdhcmJyZSBkZSBkw6ljaXNpb24uIFN1ciBjZSBncmFwaGlxdWUsIG9uIGEgZW4gb3Jkb25uw6llIGxlIG5pdmVhdSBkJ2VycmV1ciBldCBlbiBhYnNjaXNzZSBsZSBtdHJ5LiBPbiB2b2l0IHF1ZSBsZSBtdHJ5IG9wdGltYWwgZXN0IGRlIDQsIGNhciBjJ2VzdCBjZWx1aSBxdWkgYSBsZSBuaXZlYXUgZCdlcnJldXIgbGUgcGx1cyBiYXMuIA0KDQpPbiBwZXV0IGRvbmMgcmVmYWlyZSBub3RyZSBtb2TDqGxlIGVuIHByZW5hbnQgZW4gY29tcHRlIGNlcyBkZXV4IHBhcmFtw6h0cmVzLg0KDQojIyBJbXBvcnRhbmNlIGRlcyB2YXJpYWJsZXMNCg0KYGBge3J9DQojIG9uIHBldXQgbWFpbnRlbmFudCBzcMOpY2lmaWVyIG5vcyBwYXJhbcOpdHJlcw0KbnRyZWUgPC0gMTAwMA0KbXRyeSA8LSA0DQoNCiMgb24gY3LDqWUgbGUgbW9kw6hsZSBhdmVjIGxlcyBwYXJhbcOodHJlcyBzcMOpY2lmacOpcw0KZm9yZXN0X2dsb2JhbCA8LSByYW5kb21Gb3Jlc3QoQ0RSIH4gLiwgZGF0YSA9IGRmZiwgaW1wb3J0YW5jZSA9IFRSVUUsIG50cmVlID0gbnRyZWUsIG10cnkgPSBtdHJ5KQ0KDQpkYXRhIDwtIGRhdGEuZnJhbWUoDQogIFZhcmlhYmxlID0gYygiU2V4ZSIsICLDgmdlIiwgIsOJZHVjYXRpb24iLCAiU0VTIiwgIk1NU0UiLCAiVm9sdW1lX2ludGVyY3JhbmllbiIsICJWb2x1bWVfY8OpcsOpYnJhbF90b3RhbCIsICJJbmRpY2UgYXRyb3BoaWUiKSwNCiAgTWVhbkRlY3JlYXNlQWNjdXJhY3kgPSBjKDAuMDQ0NTEyNzEsIDAuMDMyNTY1NDksIDAuMDM0NTg4MDcsIDAuMDM4NDQ0NDgsIDAuMDg5NTMwOTcsIDAuMDQ3ODU0MTgsIDAuMDc4NzY3MTAsIDAuMDUwNjY2MTEpLA0KICBNZWFuRGVjcmVhc2VHaW5pID0gYyg4LjM0MTY1MSwgMjIuNzY0Nzg1LCAxMi41NTk2NjAsIDEyLjg3MDYxMywgMzAuMjk4NTk4LCAyNC40MTA4MjIsIDM1LjI3NTE2MCwgMjUuMDA3NTUzKQ0KKQ0KDQojIEdyYXBoaXF1ZSBwb3VyIE1lYW5EZWNyZWFzZUFjY3VyYWN5DQpwbG90X2FjY3VyYWN5IDwtIGdncGxvdChkYXRhLCBhZXMoeCA9IHJlb3JkZXIoVmFyaWFibGUsIE1lYW5EZWNyZWFzZUFjY3VyYWN5KSwgeSA9IE1lYW5EZWNyZWFzZUFjY3VyYWN5KSkgKw0KICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgZmlsbCA9ICJza3libHVlIikgKw0KICBsYWJzKHggPSAiVmFyaWFibGUiLCB5ID0gIk1lYW4gRGVjcmVhc2UgQWNjdXJhY3kiLCB0aXRsZSA9ICJNZWFuIERlY3JlYXNlIEFjY3VyYWN5IikgKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKQ0KDQojIEdyYXBoaXF1ZSBwb3VyIE1lYW5EZWNyZWFzZUdpbmkNCnBsb3RfZ2luaSA8LSBnZ3Bsb3QoZGF0YSwgYWVzKHggPSByZW9yZGVyKFZhcmlhYmxlLCBNZWFuRGVjcmVhc2VHaW5pKSwgeSA9IE1lYW5EZWNyZWFzZUdpbmkpKSArDQogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCBmaWxsID0gInNhbG1vbiIpICsNCiAgbGFicyh4ID0gIlZhcmlhYmxlIiwgeSA9ICJHaW5pIiwgdGl0bGUgPSAiR2luaSIpICsNCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkNCmBgYA0KDQpgYGB7ciwgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTZ9DQpncmlkLmFycmFuZ2UocGxvdF9hY2N1cmFjeSwgcGxvdF9naW5pLCBuY29sID0gMikNCmBgYA0KDQpFbiBSYW5kb20gRm9yZXN0LCAqKiJNZWFuIERlY3JlYXNlIEFjY3VyYWN5IioqIChNREEpIMOpdmFsdWUgY29tbWVudCBsJ2V4YWN0aXR1ZGUgZHUgbW9kw6hsZSBkaW1pbnVlIGxvcnNxdWUgbGVzIHZhbGV1cnMgZCd1bmUgdmFyaWFibGUgc29udCBwZXJtdXTDqWVzLCBtZXN1cmFudCBhaW5zaSBzb24gaW1wb3J0YW5jZSBwb3VyIGxhIHByw6lkaWN0aW9uLiANCg0KRW4gcmV2YW5jaGUsICoqIkdpbmkiKiogbWVzdXJlIGNvbW1lbnQgdW5lIHZhcmlhYmxlIHLDqWR1aXQgbCdpbXB1cmV0w6kgZGVzIG7Fk3VkcyBsb3JzcXUnZWxsZSBlc3QgdXRpbGlzw6llIHBvdXIgZGl2aXNlciBsZXMgZG9ubsOpZXMsIMOpdmFsdWFudCBhaW5zaSBzb24gZWZmaWNhY2l0w6kgw6Agc8OpcGFyZXIgbGVzIGNsYXNzZXMuIEJpZW4gcXVlIGxlcyBkZXV4IG1lc3VyZXMgw6l2YWx1ZW50IGwnaW1wb3J0YW5jZSBkZXMgdmFyaWFibGVzLCBlbGxlcyBsZSBmb250IMOgIHRyYXZlcnMgZGVzIHBlcnNwZWN0aXZlcyBkaWZmw6lyZW50ZXMsIGVsbGVzIHNvbnQgY29tcGzDqW1lbnRhaXJlcyBldCBwZXJtZXR0ZW50IGRlIHNvbGlkaWZpZXIgbGVzIHLDqXN1bHRhdHMuIA0KDQpJY2ksIG9uIHBldXQgdm9pciBxdWUsIHBvdXIgKipsYSBNREEqKiwgbCfDomdlIGVzdCBsYSB2YXJpYWJsZSBsYSBtb2lucyBpbXBvcnRhbnRlIGRlIG5vdHJlIG1vZMOobGUsIG9uIHBvdXJyYWl0IGZhaXJlIGRldXggZ3JvdXBlcywgYXZlYyA6DQoNCi0gQWdlLCBFZHVjYXRpb24sIFNFUywgU2V4ZSwgVm9sdW1lIGludGVyY3LDom5pZW4sIEluZGljZSBkJ2F0cm9waGllLCBxdWkgb250IGNoYWN1biBldSBkZXMgcsOpc3VsdGF0cyBhc3NleiBob21vZ8OobmVzLg0KLSBWb2x1bWUgY8OpcsOpYnJhbCB0b3RhbCBldCBNTVNFLCBxdWkgc3Vydm9sZSBwbHVzIG5vdHJlIG1vZMOobGUuDQoNClBvdXIgKipHaW5pKiosIG9uIGEgcGx1dMO0dCB0cm9pcyBncm91cGVzLCBhdmVjIDoNCg0KLSBTZXhlLCBFZHVjYXRpb24sIFNFUw0KLSBBZ2UsIFZvbHVtZSBpbnRlcmNyw6JuaWVuIGV0IGluZGljZSBkJ2F0cm9waGllDQotIE1NU0UgZXQgVm9sdW1lIGPDqXLDqWJyYWwgdG90YWwsIHF1aSBzb250IHVuZSBub3V2ZWxsZSBmb2lzIGF1IGRlc3N1cyBlbiB0ZXJtZSBkJ2ltcG9ydGFuY2UNCg0KTWFpbnRlbmFudCBxdWUgbm91cyBhdm9ucyB1bmUgaWTDqWUgZGUgbCdpbXBvcnRhbmNlIGRlIG5vcyB2YXJpYWJsZXMsIG5vdXMgcG91dm9ucyBmYWlyZSB1bmUgcsOpZ3Jlc3Npb24gbG9naXN0aXF1ZSwgcXVpIHBvdXJyYWl0IHBlcm1ldHRyZSB1bmUgYW5hbHlzZSBwbHVzIGTDqXRhaWxsw6llLg0KDQojIElWLiBSw6lncmVzc2lvbiBsb2dpc3RpcXVlIGJpbmFpcmUNCg0KIyMgSnVzdGlmaWNhdGlvbg0KDQpMYSByw6lncmVzc2lvbiBsb2dpc3RpcXVlIGJpbmFpcmUgZXN0IHVuZSBleHRlbnNpb24gZHUgbW9kw6hsZSBsaW7DqWFpcmUgYWRhcHTDqWUgYXV4IGNhcyBvw7kgbGEgdmFyaWFibGUgZMOpcGVuZGFudGUgZXN0IGJpbmFpcmUuIEVsbGUgbW9kw6lsaXNlIGxhIHJlbGF0aW9uIGVudHJlIGxlcyB2YXJpYWJsZXMgaW5kw6lwZW5kYW50ZXMgZXQgbGEgcHJvYmFiaWxpdMOpIGQnb2NjdXJyZW5jZSBkZSBsJ8OpdsOpbmVtZW50IGJpbmFpcmUsIHBlcm1ldHRhbnQgZCdlc3RpbWVyIGxlcyBwcm9iYWJpbGl0w6lzIGRlIHN1Y2PDqHMgcG91ciBjaGFxdWUgbml2ZWF1IGRlcyB2YXJpYWJsZXMgaW5kw6lwZW5kYW50ZXMuIEVuIHV0aWxpc2FudCBjZXR0ZSBtw6l0aG9kZSwgb24gcGV1dCBjYWxjdWxlciBsZXMgcC12YWx1ZXMgcG91ciDDqXZhbHVlciBsYSBzaWduaWZpY2F0aXZpdMOpIGRlcyBlZmZldHMgZGVzIHZhcmlhYmxlcyBpbmTDqXBlbmRhbnRlcyBzdXIgbGEgdmFyaWFibGUgZMOpcGVuZGFudGUuDQoNCk5vdXMgYXZvbnMgYWRvcHTDqSB1bmUgYXBwcm9jaGUgc2VtYmxhYmxlIGF1eCBtw6l0aG9kZXMgcHLDqWPDqWRlbnRlcywgYXZlYyB1bmUgKip2YWxpZGF0aW9uIGNyb2lzw6llKiogZWZmZWN0dcOpZSBlbiBkaXZpc2FudCBsZXMgZW5zZW1ibGVzIGVuIHRlc3QgKDIwJSkgZXQgZW4gYXBwcmVudGlzc2FnZSAoODAlKS4gRGVzIG1vZMOobGVzIGRlIHR5cGUgKipkdW1teSoqIG9udCBzZXJ2aSBkZSByw6lmw6lyZW5jZS4gTGVzIHBlcmZvcm1hbmNlcyBvbnQgw6l0w6kgw6l2YWx1w6llcyBlbiB0ZXJtZXMgZGUgKipwcsOpY2lzaW9uLCBkZSByYXBwZWwsIGRlIHNww6ljaWZpY2l0w6kgZXQgZCdBVUMqKi4gVW5lICoqYm91Y2xlIHLDqXDDqXRpdGl2ZSwgcsOpaXTDqXLDqWUgMTAwIGZvaXMqKiwgYSDDqXTDqSBpbnN0YXVyw6llIHBvdXIgZ2FyYW50aXIgZGVzIHLDqXN1bHRhdHMgcm9idXN0ZXMuDQoNCmBgYHtyfQ0KZGVtZW5jZTIkQ0RSX2JpbmFpcmUgPC0gaWZlbHNlKGRlbWVuY2UyJENEUiA9PSAiRMOpbWVudCIsIDEsIDApDQogIA0KICBkZW1lbmNlMiA8LSBzdWJzZXQoZGVtZW5jZTIsIHNlbGVjdCA9IC1DRFIpDQogIGRlbWVuY2UyJENEUl9iaW5haXJlIDwtIGFzLmZhY3RvcihkZW1lbmNlMiRDRFJfYmluYWlyZSkNCmBgYA0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRX0NCg0KICBucnVuIDwtIDEwMDANCiAgcmVzX2xyIDwtIGMoKQ0KICByZXNfcHJlY2lzaW9uIDwtIGMoKQ0KICByZXNfcmVjYWxsIDwtIGMoKQ0KICByZXNfc3BlY2lmaWNpdHkgPC0gYygpDQogIHJlc19hdWMgPC0gYygpDQogIA0KICBmb3IgKGlydW4gaW4gMTpucnVuKSB7DQogICAgZGZmbHIgPC0gZGVtZW5jZTJbc2FtcGxlKG5yb3coZGVtZW5jZTIpKSwgXQ0KICAgIHRyYWlubG9nIDwtIGRmZmxyWzE6MjgwLCBdDQogICAgdGVzdGxvZyA8LSBkZmZsclsyODE6MzU0LCBdDQogICAgY29sbmFtZXMoZGVtZW5jZTIpDQogICAgDQogICAgICBzdXBwcmVzc1dhcm5pbmdzKHsNCiAgICBsciA8LSBnbG0oQ0RSX2JpbmFpcmUgfiBzZXhlICsgQWdlICsgTU1TRSArIEluZGljZV9hdHJvcGhpZSArIFZvbHVtZV9jw6lyw6licmFsX3RvdGFsICsgDQogICAgICAgICAgICAgICAgSW5kaWNlX2F0cm9waGllICsgRURVQyArIFNFUywgZGF0YSA9IHRyYWlubG9nLCBmYW1pbHkgPSBiaW5vbWlhbCgpKQ0KICB9KQ0KICAgICAgDQogICAgDQogICAgZHVtbXlsciA8LSByZXAobmFtZXModGFibGUodHJhaW5sb2ckQ0RSX2JpbmFpcmUpKVt3aGljaC5tYXgodGFibGUodHJhaW5sb2ckQ0RSX2JpbmFpcmUpKV0sIG5yb3codGVzdGxvZykpDQogICAgcHJlZGljdGlvbnNsciA8LSBwcmVkaWN0KGxyLCB0ZXN0bG9nLCB0eXBlID0gInJlc3BvbnNlIikNCiAgICBwcmVkaWN0aW9uc2xyIDwtIGlmZWxzZShwcmVkaWN0aW9uc2xyID4gMC41LCAxLCAwKQ0KICAgIA0KICAgIHJlc19sciA8LSByYmluZChyZXNfbHIsIGMoDQogICAgICBtZWFuKGR1bW15bHIgPT0gdGVzdGxvZyRDRFJfYmluYWlyZSksDQogICAgICBtZWFuKHByZWRpY3Rpb25zbHIgPT0gdGVzdGxvZyRDRFJfYmluYWlyZSkNCiAgICApKQ0KICAgIA0KICAgICMgY2FsY3VsIGRlcyBtw6l0cmlxdWVzDQogICAgcHJlZGljdGlvbnNfbHIgPC0gcHJlZGljdChsciwgdGVzdGxvZywgdHlwZSA9ICJyZXNwb25zZSIpDQogICAgcHJlZGljdGlvbnNfbHIgPC0gaWZlbHNlKHByZWRpY3Rpb25zX2xyID4gMC41LCAxLCAwKQ0KICAgIHByZWRpY3Rpb25zX2xyIDwtIGZhY3RvcihwcmVkaWN0aW9uc19scikNCiAgICANCiAgICBjb25mdXNpb25fbWF0cml4IDwtIGNvbmZ1c2lvbk1hdHJpeChwcmVkaWN0aW9uc19sciwgdGVzdGxvZyRDRFJfYmluYWlyZSkNCiAgICANCiAgICBwcmVjaXNpb24gPC0gY29uZnVzaW9uX21hdHJpeCRieUNsYXNzWyJQb3MgUHJlZCBWYWx1ZSJdDQogICAgcmVjYWxsIDwtIGNvbmZ1c2lvbl9tYXRyaXgkYnlDbGFzc1siU2Vuc2l0aXZpdHkiXQ0KICAgIHNwZWNpZmljaXR5IDwtIGNvbmZ1c2lvbl9tYXRyaXgkYnlDbGFzc1siU3BlY2lmaWNpdHkiXQ0KICAgIA0KICAgICMgY2FsY3VsIGR1IFJPQw0KICAgIHJvY19jdXJ2ZV9sciA8LSByb2ModGVzdGxvZyRDRFJfYmluYWlyZSwgYXMubnVtZXJpYyhwcmVkaWN0aW9uc19scikpDQogICAgYXVjX3ZhbHVlX2xyIDwtIGF1Yyhyb2NfY3VydmVfbHIpDQogICAgDQogICAgIyBvbiBzdG9yZSBsZXMgcsOpc3VsdGF0DQogICAgcmVzX3ByZWNpc2lvbiA8LSBjKHJlc19wcmVjaXNpb24sIHByZWNpc2lvbikNCiAgICByZXNfcmVjYWxsIDwtIGMocmVzX3JlY2FsbCwgcmVjYWxsKQ0KICAgIHJlc19zcGVjaWZpY2l0eSA8LSBjKHJlc19zcGVjaWZpY2l0eSwgc3BlY2lmaWNpdHkpDQogICAgcmVzX2F1YyA8LSBjKHJlc19hdWMsIGF1Y192YWx1ZV9scikNCiAgfQ0KICANCiAgDQogIGNvbG5hbWVzKHJlc19scikgPC0gYygiZHVtbXkiLCAibHIiKQ0KDQogIA0KICBtZWFuc19sciA8LSBjb2xNZWFucyhyZXNfbHIpDQogIA0KICBkZl9sciA8LSBkYXRhLmZyYW1lKE1vZGVsID0gbmFtZXMobWVhbnNfbHIpLCBBY2N1cmFjeSA9IG1lYW5zX2xyKQ0KDQpgYGANCg0KIyMgUm9idXN0ZXNzZSBkdSBtb2TDqGxlDQoNCmBgYHtyfQ0KICAgIyBvbiBjcsOpZSBsZSBnZ3Bsb3QgcG91ciBsYSBjb21wYXJhaXNvbiBlbnRyZSBEdW1teSBldCBMb2dpc3RpYyBSZWdyZXNzaW9uDQogZ2dMUiA9IGdncGxvdChkZl9sciwgYWVzKHggPSBNb2RlbCwgeSA9IEFjY3VyYWN5LCBmaWxsID0gTW9kZWwpKSArDQogICAgZ2VvbV9jb2woKSArDQogICAgbGFicyh0aXRsZSA9ICJDb21wYXJhaXNvbiBlbnRyZSBEdW1teSBldCBMUiIsDQogICAgICAgICB4ID0gIk1vZMOobGUiLCB5ID0gIlByw6ljaXNpb24iKSArDQogICAgdGhlbWVfbWluaW1hbCgpDQogIGdnTFINCmBgYA0KTGUgZHVtbXkgYSBkZSBtb2lucyBib25zIHLDqXN1bHRhdCBxdWUgbGUgbW9kw6hsZSwgb24gcGV1dCBkb25jIGNvbnRpbnVlciBub3RyZSBhbmFseXNlICENCmBgYHtyfQ0KICMgb24gcHJlbmQgbGVzIG1veWVubmUgZGVzIG3DqXRyaXF1ZXMNCiAgbWVhbl9wcmVjaXNpb24gPC0gbWVhbihyZXNfcHJlY2lzaW9uKQ0KICBtZWFuX3JlY2FsbCA8LSBtZWFuKHJlc19yZWNhbGwpDQogIG1lYW5fc3BlY2lmaWNpdHkgPC0gbWVhbihyZXNfc3BlY2lmaWNpdHkpDQogIG1lYW5fYXVjIDwtIG1lYW4ocmVzX2F1YykNCiAgDQogICMgZGF0YSBmcmFtZSBkZXMgbcOpdHJpcXVlDQogIGRmX21ldHJpY3MgPC0gZGF0YS5mcmFtZShNw6l0cmlxdWUgPSBjKCJQcmVjaXNpb24iLCAiUmFwcGVsIiwgIlNwZWNpZmljaXTDqSIsICJBVUMiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIE1lYW5fVmFsdWUgPSBjKG1lYW5fcHJlY2lzaW9uLCBtZWFuX3JlY2FsbCwgbWVhbl9zcGVjaWZpY2l0eSwgbWVhbl9hdWMpKQ0KICANCiAgIyBncmFwaCBkZXMgbcOpdHJpcXVlcw0KICBSZWNhcFJMQiA9IGdncGxvdChkZl9tZXRyaWNzLCBhZXMoeCA9IE3DqXRyaXF1ZSwgeSA9IE1lYW5fVmFsdWUsIGZpbGwgPSBNw6l0cmlxdWUpKSArDQogICAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpICsNCiAgICBsYWJzKHRpdGxlID0gIlLDqWdyZXNzaW9uIExvZ2lzdGlxdWUgKDEwMCBpdMOpcmF0aW9ucykiLA0KICAgICAgICAgeCA9ICIiLCB5ID0gIk1veWVubmUiKSArDQogICAgICAgIHRoZW1lX21pbmltYWwoKQ0KICANClJlY2FwUkxCIDwtIFJlY2FwUkxCICsgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkNClJlY2FwUkxCJT4lIHBsb3RseTo6Z2dwbG90bHkoKQ0KYGBgDQoNCkxlIG1vZMOobGUgYSBkZSB0csOocyBib25zIHLDqXN1bHRhdHMgcG91ciBsJ0FVQywgbGEgcHLDqWNpc2lvbiwgZXQgbGUgcmFwcGVsLCBhdmVjIHJlc3BlY3RpdmVtZW50IDAuNzIsIDAuNzQgZXQgMC44Ny4gVG91dGVmb2lzLCBsYSBzZW5zYmlsaXTDqSBlc3QgcGx1cyBiYXNzZSwgYXZlYyAwLjU4LCBjZWxhIGluZGlxdWUgcXVlIGxlIG1vZMOobGUgcG91cnJhaXQgbWFucXVlciBjZXJ0YWluZXMgb2JzZXJ2YXRpb25zIHBvc2l0aXZlcy4NCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0V9DQoNCnsNCiAgbHJfZ2xvYmFsIDwtIGdsbShDRFJfYmluYWlyZSB+IHNleGUgKyBBZ2UgKyBNTVNFICsgRURVQyArIA0KICAgICAgICAgICAgICAgICAgIFZvbHVtZV9pbnRlcmNyYW5pZW4gKyBJbmRpY2VfYXRyb3BoaWUgKyBTRVMgKyBWb2x1bWVfY8OpcsOpYnJhbF90b3RhbCwgDQogICAgICAgICAgICAgICAgICAgZGF0YSA9IGRmZmxyLCBtYXhpdCA9IDEwMDAsIGZhbWlseSA9IGJpbm9taWFsKCkpDQp9DQpgYGANCkNlIG1lc3NhZ2UgZCdhdmlzIGFwcGFyYWl0IGNhciBSIG5vdXMgcHLDqXZpZW50IHF1ZSBkZXMgdmFyaWFibGVzIHNvbnQgY2VydGFpbmVtZW50IG11bHRpY29saW7DqWFpcmVzIChsYSBtdWx0aWNvbGluw6lhcml0w6kgZGVzIHZhcmlhYmxlcyByZW5kIGluaW50ZXJwcsOpdGFibGUgbGVzIHLDqXN1bHRhdHMgZW4gcsOpZ3Jlc3Npb24gbG9naXN0aXF1ZSksIG5vdXMgcHJlbmRyb25zIGNlbGEgZW4gY29tcHRlIGFwcsOocyBhdm9pciBhZmZpbmVyIG5vdHJlIG1vZMOobGUuDQoNCmBgYHtyfQ0Kc3VwcHJlc3NXYXJuaW5ncyh7DQogIEFub3ZhKGxyX2dsb2JhbCkNCn0pDQpgYGANCg0KIyMgU8OpbGVjdGlvbiBkZSB2YXJpYWJsZXMgcGFyIMOpdGFwZXMNCg0KUG91ciBhc3N1cmVyIGwnaW50ZXJwcsOpdGFiaWxpdMOpIGRlcyByw6lzdWx0YXRzIGRlIGxhIHLDqWdyZXNzaW9uIGxvZ2lzdGlxdWUsIGlsIGVzdCBpbXDDqXJhdGlmIHF1ZSB0b3V0ZXMgbGVzIHZhcmlhYmxlcyBkZSBub3RyZSBtb2TDqGxlIHNvaWVudCBzaWduaWZpY2F0aXZlcyAoaWNpLCBub3VzIHByZW5kcm9ucyB1biBzZXVpbCBhbHBoYSDDqWdhbGUgw6AgNSUpLiBBaW5zaSwgcG91ciBhdHRlaW5kcmUgY2V0IG9iamVjdGlmLCBvbiByZXRpcmUgdW5lIMOgIHVuZSBsZXMgdmFyaWFibGVzIG5vbiBzaWduaWZpY2F0aXZlcyAqKGNhciBsZSByZXRyYWl0IGQndW5lIHZhcmlhYmxlIHBldXQgbW9kaWZpZXIgbGUgbml2ZWF1IGRlIHNpZ25pZmljYXRpdml0w6kgZGVzIGF1dHJlcywgaW1wYWN0YW50IGFpbnNpIGwnYW5hbHlzZSBnbG9iYWxlIGR1IG1vZMOobGUpKiwgZW4gY29tbWVuw6dhbnQgcGFyIMOpbGltaW5lciBjZWxsZSBxdWkgcHLDqXNlbnRlIGxhIHAtdmFsdWUgbGEgbW9pbnMgc2lnbmlmaWNhdGl2ZS4gQ2UgcHJvY2Vzc3VzIGdhcmFudGl0IHF1ZSBzZXVsZXMgbGVzIHZhcmlhYmxlcyBsZXMgcGx1cyBwZXJ0aW5lbnRlcyBkZW1ldXJlbnQgZGFucyBsZSBtb2TDqGxlIGZpbmFsLg0KDQpQb3VyIGNvbW1lbmNlciwgb24gcmV0aXJlIGRvbmMgbGUgdm9sdW1lIGludGVyY3LDom5pZW4gOg0KDQpgYGB7cn0NCg0KbHJfZ2xvYmFsIDwtIGdsbShDRFJfYmluYWlyZSB+IHNleGUgKyBBZ2UgKyBNTVNFICsgRURVQyArIA0KICAgICAgICAgICAgICAgICAgICBJbmRpY2VfYXRyb3BoaWUgKyBTRVMgKyBWb2x1bWVfY8OpcsOpYnJhbF90b3RhbCwgDQogICAgICAgICAgICAgICAgICAgZGF0YSA9IGRmZmxyLCBtYXhpdCA9IDEwMDAsIGZhbWlseSA9IGJpbm9taWFsKCkpDQpgYGANCg0KYGBge3J9DQp7DQogIGxyX2dsb2JhbCA8LSBnbG0oQ0RSX2JpbmFpcmUgfiBzZXhlICsgQWdlICsgTU1TRSArIEVEVUMgKyANCiAgICAgICAgICAgICAgICAgICAgSW5kaWNlX2F0cm9waGllICsgU0VTICsgVm9sdW1lX2PDqXLDqWJyYWxfdG90YWwsIA0KICAgICAgICAgICAgICAgICAgIGRhdGEgPSBkZmZsciwgbWF4aXQgPSAxMDAwLCBmYW1pbHkgPSBiaW5vbWlhbCgpKQ0KfQ0KDQpzdXBwcmVzc1dhcm5pbmdzKHsNCiAgQW5vdmEobHJfZ2xvYmFsKQ0KfSkNCmBgYA0KDQpPbiBwZXV0IHZvaXIgcXVlIGxlcyBwLXZhbHVlcyBkZSBub3MgdmFyaWFibGVzIG9udCDDqXZvbHXDqSwgdG91dGVzIHNvbnQgZGV2ZW51ZXMgc2lnbmlmaWNhdGl2ZXMsIGNlIHF1aSBlc3QgdW5lIGJvbm5lIG5vdXZlbGxlLiBUb3V0ZWZvaXMsIG9uIG5lIHBldXQgdG91am91cnMgcGFzIGludGVycHLDqXRlciBub3RyZSBtb2TDqGxlIGRlIHLDqWdyZXNzaW9uIGxvZ2lzdGlxdWUuIEF2YW50IGNlbGEsIGlsIGVzdCBuw6ljZXNzYWlyZSBkZSBwcmVuZHJlIGVuIGNvbXB0ZSBsYSBwb3NzaWJsZSBtdWx0aWNvbGxpbsOpYXJpdMOpIGRlcyB2YXJpYWJsZXMuDQoNCiMjIFByaXNlIGVuIGNvbXB0ZSBkdSBWSUYNCg0KTGUgVklGIChWYXJpYW5jZSBJbmZsYXRpb24gRmFjdG9yKSBlbiByw6lncmVzc2lvbiBlc3QgdW5lIG1lc3VyZSBkZSBsJ2FtcGxldXIgw6AgbGFxdWVsbGUgbGEgdmFyaWFuY2UgZCd1biBjb2VmZmljaWVudCBkZSByw6lncmVzc2lvbiBlc3QgYXVnbWVudMOpZSBlbiByYWlzb24gZGUgbGEgY29ycsOpbGF0aW9uIGVudHJlIGxlcyBwcsOpZGljdGV1cnMgZGFucyBsZSBtb2TDqGxlLiBVbiBWSUYgw6lsZXbDqSBzdWdnw6hyZSB1bmUgZm9ydGUgbXVsdGljb2xsaW7DqWFyaXTDqSBlbnRyZSBsZXMgdmFyaWFibGVzIGV4cGxpY2F0aXZlcywgY2UgcXVpIHBldXQgcmVuZHJlIGxlcyBlc3RpbWF0aW9ucyBkZSBjb2VmZmljaWVudHMgcGV1IGZpYWJsZXMuIE9uIHBldXQgYWluc2kgcmVnYXJkZXIgbGVzIFZJRnMgZGUgbm9zIHZhcmlhYmxlcyA6DQoNCmBgYHtyfQ0KdmlmKGxyX2dsb2JhbCkNCmBgYA0KDQpPbiBwZXV0IGNyw6llciB1biBHR3Bsb3QgcG91ciBxdWUgY2Ugc29pdCBwbHVzIHBhcmxhbnQgOg0KDQpgYGB7cn0NCiMgY3LDqWVyIHVuIGdncGxvdCBwb3VyIG1pZXV4IHZpc3VhbGlzZXINCmRhdGEgPC0gZGF0YS5mcmFtZSgNCiAgVmFyaWFibGUgPSBjKCJzZXhlIiwgIkFnZSIsICJNTVNFIiwgIkVEVUMiLCAiVm9sdW1lX2PDqXLDqWJyYWxfdCIsICJJbmRpY2VfYXRyb3BoaWUiLCAiU0VTIiksDQogIEdWSUYgPSBjKDEuNDIxMjIxLCAxLjM0NjQxMCwgMS4wMDAwMDAsIDcuNTYwNjU3LCAxLjQxOTY3NywgMS40NzEyMDgsIDcuNjU2OTA5KQ0KKQ0KDQojIEdyYXBoaXF1ZQ0KZ2d2aWYgPSBnZ3Bsb3QoZGF0YSwgYWVzKHggPSBWYXJpYWJsZSwgeSA9IEdWSUYsIGZpbGwgPSBWYXJpYWJsZSkpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIHBvc2l0aW9uID0gImRvZGdlIikgKw0KICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSA1LCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJzb2xpZCIpICsgIyBMaWduZSByb3VnZSDDoCB5ID0gNQ0KICBsYWJzKHRpdGxlID0gIkdWSUYgcG91ciBjaGFxdWUgdmFyaWFibGUiLA0KICAgICAgIHggPSAiVmFyaWFibGUiLA0KICAgICAgIHkgPSAiR1ZJRiBSYWlzZWQiKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpDQoNCmdndmlmICU+JSBwbG90bHk6OmdncGxvdGx5KCkNCmBgYA0KDQpEYW5zIGxhIGxpdHTDqXJhdHVyZSwgaWwgZXN0IGNvdXJhbW1lbnQgYWRtaXMgcXVlIGxlIFZJRiBwZXV0IHBvc2VyIHByb2Jsw6htZSBsb3JzcXVlIHNhIHZhbGV1ciBkw6lwYXNzZSA1LiBDZXR0ZSBsaW1pdGUgZXN0IHJlcHLDqXNlbnTDqWUgcGFyIGxhIGxpZ25lIHJvdWdlIHN1ciBsZSBncmFwaGlxdWUuIEljaSwgb24gcGV1dCB2b2lyIHF1ZSBsZSBuaXZlYXUgZCfDqWR1Y2F0aW9uIGV0IGxlIG5pdmVhdSBzb2Npby3DqWNvbm9taXF1ZSBwb3NlbnQgcHJvYmzDqG1lLCBub3VzIGFsbG9ucyByZXRpcmVyIGxhIHZhcmlhYmxlIHF1aSBhIGxlIFZJRiBsZSBwbHVzIMOpbGV2w6ksIMOgIHNhdm9pciBsZSBuaXZlYXUgc29jaW8tw6ljb25vbWlxdWUsIHF1aSBlc3QgZGUgNy42NS4NCg0KYGBge3J9DQoNCmxyX2dsb2JhbDIgPC0gZ2xtKENEUl9iaW5haXJlIH4gc2V4ZSArIEFnZSArIE1NU0UgKyBFRFVDICsgDQogICAgICAgICAgICAgICAgICAgVm9sdW1lX2PDqXLDqWJyYWxfdG90YWwgKyBJbmRpY2VfYXRyb3BoaWUsIA0KICAgICAgICAgICAgICAgICAgIGRhdGEgPSBkZmZsciwgbWF4aXQgPSAxMDAwLCBmYW1pbHkgPSBiaW5vbWlhbCgpKQ0KDQp2aWYobHJfZ2xvYmFsMikNCg0KIyBvbiB2YSBjcsOpZXIgdW4gZ2dwbG90IHBvdXIgbWlldXggdmlzdWFsaXNlcg0KZGF0YSA8LSBkYXRhLmZyYW1lKA0KICBWYXJpYWJsZSA9IGMoInNleGUiLCAiSW5kaWNlX2F0cm9waGllIiwgIk1NU0UiLCAiVm9sdW1lX3N1YnN0YW5jZV9ibGFuY2hlIiwgIkluZGljZV9hdHJvcGhpZSIsICJFRFVDIiksDQogIEdWSUYgPSBjKDEuMzI0NTI5LCAxLjQwMjU0MCwgMS4wMDAwMDAsIDEuMzc5MjA1LCAxLjQxOTY3NywgMS4wMjMyNTUpDQopDQoNCiMgR3JhcGhpcXVlDQpnZ3ZpZjIgPSBnZ3Bsb3QoZGF0YSwgYWVzKHggPSBWYXJpYWJsZSwgeSA9IEdWSUYsIGZpbGwgPSBWYXJpYWJsZSkpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIHBvc2l0aW9uID0gImRvZGdlIikgKw0KICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSA1LCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJzb2xpZCIpICsgIyBMaWduZSByb3VnZSDDoCB5ID0gNQ0KICBsYWJzKHRpdGxlID0gIkdWSUYgcG91ciBjaGFxdWUgdmFyaWFibGUiLA0KICAgICAgIHggPSAiVmFyaWFibGUiLA0KICAgICAgIHkgPSAiTml2ZWF1IikgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKQ0KDQpnZ3ZpZjIgJT4lIHBsb3RseTo6Z2dwbG90bHkoKQ0KYGBgDQoNClNhbnMgY2V0dGUgdmFyaWFibGUsIHRvdXMgbGVzIFZJRlMgc29udCBpbmbDqXJpZXVycyDDoCAxLDUgZXQgbmUgcG9zZW50IGRvbmMgcGx1cyBkZSBwcm9ibMOobWUuIE9uIHBldXQgZG9uYyBjb250aW51ZXIgbCdhbmFseXNlIGV0IHJlY2FsY3VsZXIgbm9zIHAtdmFsdWVzLg0KDQojIyBNb2TDqGxlIGZpbmFsDQoNCmBgYHtyfQ0KQW5vdmEobHJfZ2xvYmFsMikNCg0KYGBgDQoNCk1haW50ZW5hbnQgcXVlIG5vdXMgYXZvbnMgdsOpcmlmacOpIHRvdXRlcyBub3MgY29uZGl0aW9ucywgbm91cyBwb3V2b25zIGludGVycHLDqXRlciBub3RyZSBtb2TDqGxlIGZpbmFsLiBOb3VzIGF2b25zIGRvbmMgKipyZXRpcsOpIGxlIHZvbHVtZSBpbnRlcmNyYW5pZW4gYWluc2kgcXVlIGxlIG5pdmVhdSBzb2Npby3DqWNvbm9taXF1ZSwqKiBub3VzIGxhaXNzYW50IDoNCg0KInNleGUiLCAiTU1TRSIsICJWb2x1bWVfY8OpcsOpYnJhbF90b3RhbCIsIHF1aSBvbnQgZGVzIHAtdmFsdWVzIHRyw6hzIGZhaWJsZXMgKGluZsOpcmlldXJlcyDDoCAwLDAwMSksIGNlIHF1aSBlc3QgaW5kaXF1w6kgcGFyIHRyb2lzIMOpdG9pbGVzIChcKlwqXCopLCAqKnN1Z2fDqXJhbnQgdW5lIGZvcnRlIGFzc29jaWF0aW9uIGF2ZWMgbGEgdmFyaWFibGUgZMOpcGVuZGFudGUuKioNCg0KTGVzIHZhcmlhYmxlcyAiQWdlIiBldCAiSW5kaWNlX2F0cm9waGllIiBvbnQgZGVzIHAtdmFsZXVycyBkZSAwLjAwNDgyMCBldCAwLjAwMTI1NiByZXNwZWN0aXZlbWVudCwgY2UgcXVpIGVzdCBpbmRpcXXDqSBwYXIgZGV1eCDDqXRvaWxlcyoqLCBzdWdnw6lyYW50IHVuZSBhc3NvY2lhdGlvbiBzaWduaWZpY2F0aXZlLCBtYWlzIGF2ZWMgdW4gbml2ZWF1IGRlIGNvbmZpYW5jZSBsw6lnw6hyZW1lbnQgbW9pbnMgw6lsZXbDqSBxdWUgY2VsbGVzIGF2ZWMgdHJvaXMgw6l0b2lsZXMuKioNCg0KQ2UgcXVpIG5vdXMgbGFpc3NlIGxhIHZhcmlhYmxlICJFRFVDIiwgcXVpLCBiaWVuIHF1ZSBwcsOpc2VudGFudCB1bmUgcC12YWxldXIgbMOpZ8OocmVtZW50IHBsdXMgw6lsZXbDqWUgcXVlIGxlcyBhdXRyZXMgdmFyaWFibGVzIGRhbnMgbm90cmUgYW5hbHlzZSAoMCwwMTQwNTgpLCByZXN0ZSBzdGF0aXN0aXF1ZW1lbnQgc2lnbmlmaWNhdGl2ZSBhdSBuaXZlYXUgZGUgY29uZmlhbmNlIGRlIDk1JS4NCg0KIyMgVsOpcmlmaWNhdGlvbiBkZXMgY29uZGl0aW9ucyBkJ3V0aWxpc2F0aW9ucw0KDQpEYW5zIGxhIHLDqWdyZXNzaW9uIGxvZ2lzdGlxdWUsIHVuIG1vZMOobGUgbGluw6lhaXJlIGfDqW7DqXJhbGlzw6ksIGwnYWTDqXF1YXRpb24gZXN0IMOpdmFsdcOpZSBlbiBleGFtaW5hbnQgbGVzIHLDqXNpZHVzIGRlIFBlYXJzb24gZXQgZGUgZMOpdmlhbmNlLiBMZXMgcsOpc2lkdXMgZGUgUGVhcnNvbiBtZXN1cmVudCBsYSBkaWZmw6lyZW5jZSBlbnRyZSBsZXMgdmFsZXVycyBvYnNlcnbDqWVzIGV0IGF0dGVuZHVlcywgdGFuZGlzIHF1ZSBsZXMgcsOpc2lkdXMgZGUgZMOpdmlhbmNlIMOpdmFsdWVudCBsJ2FqdXN0ZW1lbnQgZHUgbW9kw6hsZSBhdXggZG9ubsOpZXMuIENlcyByw6lzaWR1cyBzb250IHNvdXZlbnQgdGVzdMOpcyDDoCBsJ2FpZGUgZGUgZGlzdHJpYnV0aW9ucyBkdSBjaGktZGV1eCBwb3VyIGTDqXRlY3RlciBsZXMgcG9pbnRzIGF0eXBpcXVlcyBvdSBtYWwgYWp1c3TDqXMgZGFucyBsZXMgZG9ubsOpZXMuDQpgYGB7cn0NCmJpbm5lZHBsb3QoZml0dGVkKGxyX2dsb2JhbDIpLCByZXNpZHVhbHMobHJfZ2xvYmFsMiwgdHlwZSA9ICJyZXNwb25zZSIpKQ0KDQpgYGANCkxlIGdyYXBoaXF1ZSBwcm9kdWl0IHBhciBiaW5uZWRwbG90IGluZGlxdWUgdW4gaW50ZXJ2YWxsZSBkZSBwcsOpZGljdGlvbiDDoCA5NSUgKGxpZ25lcyBncmlzZXMpIHBvdXIgbGVzIHLDqXNpZHVzIG1veWVucy4gQWluc2ksIHNpIGxlIG1vZMOobGUgYmlub21pYWwgZXN0IGJvbiwgZW52aXJvbiA5NSUgZGVzIHLDqXNpZHVzIGRldnJhaWVudCBzZSB0cm91dmVyIMOgIGzigJlpbnTDqXJpZXVyIGRlIGNldCBpbnRlcnZhbGxlLiBJY2ksIGPigJllc3QgbGUgY2FzIGRlIDEzIHLDqXNpZHVzIHN1ciAxNiwgc29pdCA4MS4yNSUsIGNlIHF1aSBlc3QgbW9pbnMgcGVyZm9ybWFudCBkZSBjZSBxdSdvbiBhdHRlbmRyYWl0IGRhbnMgbCdpZMOpYWwgIQ0KDQoNCkwnZXN0aW1hdGlvbiBkZXMgcGFyYW3DqHRyZXMgZGVzIG1vZMOobGVzIGxpbsOpYWlyZXMgZ8OpbsOpcmFsaXPDqXMgbid1dGlsaXNlIHBhcyBsYSBtw6l0aG9kZSBkZXMgbW9pbmRyZXMgY2FycsOpcywgY2UgcXVpIGZhaXQgcXVlIGxlIFLCsiwgY291cmFtbWVudCB1dGlsaXPDqSBlbiBtb2TDqGxlIGxpbmFpcmUgZ8OpbsOpcmFsZSBwb3VyIGp1Z2VyIGR1IGRlZ3LDqSBkJ2VmZmljYWNpdMOpIGR1IG1vZMOobGUsIG4nYSBwYXMgZGUgc2VucyBpY2kuIA0KDQpQb3VyIHBhbGxpZXIgY2VsYSwgb24gcGV1dCB1dGlsaXNlciB1biAqKnBzZXVkbyBSwrIqKiwgcGx1c2lldXJzIGV4aXN0ZSwgb24gYSBwYXIgZXhlbXBsZSBsZSAqKlLCsiBkZSBNY0ZhZGRlbioqLCBxdWkgc2UgY2FsY3VsZSB0ZWwgcXVlICBsYSBmcmFjdGlvbiBkZSBsYSBkw6l2aWFuY2UgZHUgbW9kw6hsZSBudWwgZXhwbGlxdcOpZSBwYXIgbGUgbW9kw6hsZSBpbmNsdWFudCBsZXMgcHLDqWRpY3RldXJzLg0KYGBge3J9DQpwc2V1ZG9fUjIgPC0gMSAtIGxyX2dsb2JhbDIkZGV2aWFuY2UvbHJfZ2xvYmFsMiRudWxsLmRldmlhbmNlDQpwc2V1ZG9fUjINCmBgYA0KT24gb2J0aWVudCB1biBSwrIgYWp1c3TDqSBkZSAwLjMyLiBMZSBSwrIgYWp1c3TDqSB2YSBkZSAwIMOgIDEsIDAgdmV1dCBkaXJlIHF1ZSBsZSBtb2TDqGxlIG5lIHBhcnZpZW50IHBhcyDDoCBleHBsaXF1ZXIgcXVvaSBxdWUgY2Ugc29pdCBlbiBmb25jdGlvbiBkZSBsYSB2YXJpYWJsZSBkw6lwZW5kYW50ZSwgZXQgw6AgMSBxdSdpbCB5IGFycml2ZSBwYXJmYWl0ZW1lbnQsIGNlIHF1aSBpbmRpcXVlIHVuIGFqdXN0ZW1lbnQgbW9kw6lyw6kgZHUgbW9kw6hsZSBhdXggZG9ubsOpZXMuDQoNCg0KUG91ciDDqnRyZSBzw7tyLCBvbiBwZXV0IHV0aWxpc2VyIHVuZSBhdXRyZSBtw6l0aG9kZSBxdWkgY2FsY3VsZSBsZSBSwrIgYWp1c3TDqSwgcXVhbnRpZmllIGxhIGRpZmbDqXJlbmNlIGVudHJlIGxhIHZhbGV1ciBtb3llbm5lIHByw6lkaXRlIHBvdXIgbGVzIG9ic2VydmF0aW9ucyBvw7kgbGEgdmFyaWFibGUgZMOpcGVuZGFudGUgZXN0IMOpZ2FsZSDDoCAxIGV0IGNlbGxlIHBvdXIgbGVzIG9ic2VydmF0aW9ucyBvw7kgbGEgdmFyaWFibGUgZMOpcGVuZGFudGUgZXN0IMOpZ2FsZSDDoCAwLiBDZWxhIHBlcm1ldCBkJ8OpdmFsdWVyIGxhIGNhcGFjaXTDqSBkdSBtb2TDqGxlIMOgIGRpZmbDqXJlbmNpZXIgbGVzIGRldXggY2F0w6lnb3JpZXMgZGUgcsOpcG9uc2UuIGFpbnNpLCBpbCBtZXN1cmUgbGEgZm9yY2UgZGUgcHLDqWRpY3Rpb24gZHUgbW9kw6hsZSBlbiB0ZXJtZXMgZGUgZGlmZsOpcmVuY2lhdGlvbiBlbnRyZSBsZXMgZGV1eCBuaXZlYXV4IGRlIGxhIHZhcmlhYmxlIGTDqXBlbmRhbnRlLg0KDQpgYGB7cn0NCg0KcjJfdGp1ciA8LSBtZWFuKGxyX2dsb2JhbDIkZml0dGVkLnZhbHVlc1tscl9nbG9iYWwyJHkgPT0gMV0pIC0gbWVhbihscl9nbG9iYWwyJGZpdHRlZC52YWx1ZXNbbHJfZ2xvYmFsMiR5ID09IDBdKQ0KcjJfdGp1cg0KYGBgDQpBdmVjIGNldHRlIG3DqXRob2RlLCBvbiBvYnRpZW50IDAuMzYsIGNlIHF1aSBlc3QgYXNzZXogc2VtYmxhYmxlIGF1IFLCsiBkZSBNY0ZhZGRlbiBjYWxjdWzDqSBwcsOpY2VkZW1tZW50IGV0IGNvbnNvbGlkZSBjZSByw6lzdWx0YXQuDQoNCg0KRW4gY29uY2x1c2lvbiwgbm90cmUgYW5hbHlzZSByw6l2w6hsZSBkZXMgYXNzb2NpYXRpb25zIHNpZ25pZmljYXRpdmVzIGVudHJlIGxlIHNleGUsIGwnw6JnZSwgbGUgTU1TRSwgbGUgY3LDqWJyYWwgdG90YWwsIGwnaW5kaWNlIGQnYXRyb3BoaWUgZXQgbGUgbml2ZWF1IGQnw6lkdWNhdGlvbiBhdmVjIGxhIHZhcmlhYmxlIGTDqXBlbmRhbnRlLCBzdWdnw6lyYW50IGxldXIgaW1wb3J0YW5jZSBkYW5zIG5vdHJlIG1vZMOobGUuDQoNCkxhIHbDqXJpZmljYXRpb24gZGVzIGNvbmRpdGlvbnMgZCdhcHBsaWNhdGlvbnMgbW9udHJlIHVuIHLDqXN1bHRhdCBlbiBkZXNzb3VzIGRlIGNlIHF1J29uIGF1cmFpdCBwdSBhdHRlbmRyZSBwb3VyIExlcyByw6lzaWR1cyBkZSBwZWFyc29uLCBldCB1biBSwrIgYWp1c3TDqSBtb250cmFudCB1bmUgZWZmaWNhY2l0w6kgbW9kw6lyw6ksIGxlcyByw6lzdWx0YXRzIGRlIG5vdHJlIHLDqWdyZXNzaW9uIGxvZ2lzdGlxdWUgYmlub21pYWxlLCBiaWVuIHF1ZSB0csOocyBpbnTDqXJlc3NhbnRzLCBzb250IGRvbmMgw6AgcHJlbmRyZSBhdmVjIGRlcyBwaW5jZXR0ZXMuDQoNCiMgVi4gQW5hbHlzZSBkaXNjcmltaW5hbnRlIGxpbsOpYWlyZSAoTERBKQ0KDQojIyBKdXN0aWZpY2F0aW9uDQoNCkwnYW5hbHlzZSBkaXNjcmltaW5hbnRlIGxpbsOpYWlyZSBvZmZyZSB1bmUgbcOpdGhvZGUgZWZmaWNhY2UgcG91ciBzaW1wbGlmaWVyIGxlcyBtb2TDqGxlcywgYW3DqWxpb3JlciBsYSBwcsOpY2lzaW9uIGRlIGxhIGNsYXNzaWZpY2F0aW9uIGV0IGZvdXJuaXIgZGVzIHLDqXN1bHRhdHMgaW50ZXJwcsOpdGFibGVzLiBQb3VyIGVmZmVjdHVlciB1bmUgTERBLCBpbCBlc3QgbsOpY2Vzc2FpcmUgcXVlIHRvdXRlcyBsZXMgdmFyaWFibGVzIHNvaWVudCBxdWFudGl0YXRpdmVzLCBldCBpbCBuZSBkb2l0IHBhcyB5IGF2b2lyIGRlIGNvbGluw6lhcml0w6kgZW50cmUgbGVzIHZhcmlhYmxlcy4NCg0KQ29tbWUgbm91cyBhdm9ucyBkZXMgdmFyaWFibGVzIHF1YWxpdGF0aXZlcywgbm91cyBhbGxvbnMgZCdhYm9yZCBmYWlyZSB1bmUgQW5hbHlzZSBGYWN0b3JpZWxsZSBkZXMgQ29ycmVzcG9uZGFuY2VzIChBRkMpLg0KDQojIyBBRkMNCg0KTm91cyBhdm9ucyB2dSBsb3JzIGRlIGxhIHLDqWdyZXNzaW9uIGxvZ2lzdGlxdWUgcXUnaWwgeSBhdmFpdCB1bmUgbXVsdGljb2xpbsOpYXJpdMOpIGVudHJlIGxhIHZhcmlhYmxlIHJlcHLDqXNlbnRhbnQgbGUgbml2ZWF1IGQnw6lkdWNhdGlvbiAoRURVQykgZXQgbGEgdmFyaWFibGUgcmVwcsOpc2VudGFudCBsZSBuaXZlYXUgc29jaW8tY3VsdHVyZWwgKFNFUykuIENvbW1lIHByw6ljZWRlbW1lbnQsIG5vdXMgYWxsb25zIGRvbmMgcmV0aXJlciBsYSB2YXJpYWJsZSBxdWkgYXZhaXQgbGUgVklGIGxlIHBsdXMgaGF1dCwgYydlc3Qgw6AgZGlyZSBsZSBuaXZlYXUgc29jaW8tY3VsdHVyZWwuDQoNCkNlbGEgZGl0LCBvbiBwZXV0IGZhaXJlIG5vdHJlIEFGQyA6DQoNCmBgYHtyfQ0KIGRhdGFfYWZjIDwtIGRlbWVuY2UyWywgYygic2V4ZSIsICJNTVNFIiwgIkVEVUMiKV0NCiAgDQogIGFmY19yZXN1bHQgPC0gTUNBKGRhdGFfYWZjLCBncmFwaCA9IEZBTFNFKQ0KIA0KICBoZWFkKGFmY19yZXN1bHQkaW5kJGNvb3JkKQ0KYGBgDQoNCk9uIHBldXQgdm9pciBxdSdvbiBhIGNpbnEgZGltZW5zaW9ucyBkYW5zIG5vdHJlIEFGQywgY2V1eC1jaSByZXByw6lzZW50ZW50IG5vcyB2YXJpYWJsZXMgcXVhbGl0YXRpdmVzLCBvbiB2YSBkb25jIGxlcyBpbmNsdXJlIGRhbnMgbm90cmUgamV1IGRlIGRvbm7DqWVzIGRlIGJhc2UsIHB1aXMgcmV0aXJlciBsZXMgdmFyaWFibGVzIHF1YWxpdGF0aXZlcyBhZmluIGRlIHBvdXZvaXIgZmFpcmUgbm90cmUgTERBLg0KDQpgYGB7cn0NCiBsZGFfaW5wdXQgPC0gYXMuZGF0YS5mcmFtZShhZmNfcmVzdWx0JGluZCRjb29yZFssIHBhc3RlMCgiRGltICIsIDE6NSldKSANCiAgZGVtZW5jZTIgPC0gY2JpbmQoZGVtZW5jZTIsIGxkYV9pbnB1dCkNCiAgZGVtZW5jZTIgPC0gZGVtZW5jZTJbLCAtYygxLCAzLCA0LCA1KV0NCmBgYA0KDQojIyBQcsOpcGFyYXRpb24gZGVzIGRvbm7DqWVzIHBvdXIgbGEgTERBDQoNCkNlbGEgZmFpdCwgb24gcGV1dCBwcsOpcGFyZXIgbm9zIGRvbm7DqWVzIHBvdXIgZmFpcmUgbGEgTERBIDoNCg0KYGBge3J9DQogIGdwID0gYXMuZmFjdG9yKGRlbWVuY2UyJENEUikNCiAgbWF0cmljZSA9IGFzLm1hdHJpeChkZW1lbmNlMikNCiAgZGF0YSA9IGRhdGEuZnJhbWUoZ3JvdXA9ZGVtZW5jZTIkQ0RSLCBtYXRyaWNlKQ0KICBjb2xuYW1lcyhkYXRhKQ0KICBkYXRhJEFnZSA8LSBhcy5udW1lcmljKGRhdGEkQWdlKQ0KICANCiAgZGF0YSRWb2x1bWVfaW50ZXJjcmFuaWVuPC0gYXMubnVtZXJpYyhkYXRhJFZvbHVtZV9pbnRlcmNyYW5pZW4pDQogIGRhdGEkVm9sdW1lX2PDqXLDqWJyYWxfdG90YWwgPC0gYXMubnVtZXJpYyhkYXRhJFZvbHVtZV9jw6lyw6licmFsX3RvdGFsKQ0KICBkYXRhJEluZGljZV9hdHJvcGhpZSA8LSBhcy5udW1lcmljKGRhdGEkSW5kaWNlX2F0cm9waGllKQ0KICBkYXRhJERpbS4xIDwtIGFzLm51bWVyaWMoZGF0YSREaW0uMSkNCiAgZGF0YSREaW0uMiA8LSBhcy5udW1lcmljKGRhdGEkRGltLjIpDQogIGRhdGEkRGltLjMgPC0gYXMubnVtZXJpYyhkYXRhJERpbS4zKQ0KICBkYXRhJERpbS40IDwtIGFzLm51bWVyaWMoZGF0YSREaW0uNCkNCiAgZGF0YSREaW0uNSA8LSBhcy5udW1lcmljKGRhdGEkRGltLjUpDQogIA0KICBjb2xvbm5lc19udW1lcmlxdWVzIDwtIHNhcHBseShkYXRhLCBpcy5udW1lcmljKQ0KICBEYXRhQ1I8LSBzY2FsZShkYXRhWyxjb2xvbm5lc19udW1lcmlxdWVzXSkNCg0KICBkYXRhMiA8LSBhcy5kYXRhLmZyYW1lKERhdGFDUikNCg0KICBkYXRhMiRDRFIgPC0gZGVtZW5jZTIkQ0RSX2JpbmFpcmUNCg0KICBkYXRhMiRDRFIgPC0gYXMuZmFjdG9yKGRhdGEyJENEUikNCg0KDQogIGdwID0gYXMuZmFjdG9yKGRhdGEyJENEUikNCiAgRGF0YWxkYSA9IGRhdGEuZnJhbWUoZ3JvdXA9Z3AsZGF0YTIpDQpgYGANCiMjIE1pc2UgZW4gcGxhY2UgZGUgbGEgTERBDQoNCkNvbW1lIHBvdXIgbGVzIG3DqXRob2RlcyBwcsOpY8OpZGVudGVzLCBvbiBmYWl0IGRlIGxhICoqdmFsaWRhdGlvbioqICoqY3JvaXPDqWUqKiwgYXZlYyA4MCAlIHBvdXIgbCdlbnRyYcOubmVtZW50IGV0IDIwICUgcG91ciBsZSB0ZXN0LCBwdWlzIG9uIGNvbXBhcmUgbm90cmUgbW9kw6hsZSDDoCB1biAqKm1vZMOobGUgZHVtbXkqKiBldCBvbiBjYWxjdWxlIGxlcyAqKm3DqXRyaXF1ZXMgZCfDqXZhbHVhdGlvbioqIHN1ciBsJ8OpY2hhbnRpbGxvbiB0ZXN0LCBhdmVjIHVuZSAqKmJvdWNsZSBkZSAxMDAgcsOpcMOpdGl0aW9ucy4qKg0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRX0NCiAgIyBjcsOpZXIgdW5lIGxpc3RlIHZpZGUgcG91ciBzdG9ja2VyIGxlcyBwZXJmb3JtYW5jZXMNCiAgcGVyZm9ybWFuY2VzX21ldHJpY3MgPC0gbGlzdCgpDQogIA0KICAgICMgYm91Y2xlIHN1ciBsZXMgaXTDqXJhdGlvbnMNCiAgc2V0LnNlZWQoMTAwKQ0KICBucnVuIDwtIDEwMA0KICBmb3IgKGlydW4gaW4gMTpucnVuKXsNCiAgICBkZjwtIGRhdGEyW3NhbXBsZShucm93KGRhdGEyKSksIF0NCiAgICANCiAgICANCiAgICB0cmFpbiA8LSBkZlsxOjI4MCwgXQ0KICAgIHRlc3QgPC0gZGZbMjgxOjM1NCwgXQ0KICAgIHJlc2xkYSA8LSBsZGEoQ0RSIH4gLiAsIGRhdGE9dHJhaW4pDQogICAgcHJlZGxkYSA8LSBwcmVkaWN0KHJlc2xkYSx0ZXN0KQ0KICAgICMgbWF0cmljZSBkZSBjb25mdXNpb24NCiAgICBjb25mdXNpb25fbWF0cml4IDwtIHRhYmxlKHRlc3QkQ0RSLCBwcmVkbGRhJGNsYXNzKQ0KICAgICMgbcOpdHJpcXVlcyBwb3VyIGNoYXF1ZSBpdMOpcmF0aW9uIGRlIHZhbGlkYXRpb24gY3JvaXPDqWUNCiAgICBjb25mdXNpb25fbWF0cmljZXMgPC0gY29uZnVzaW9uTWF0cml4KHRlc3QkQ0RSLCBwcmVkbGRhJGNsYXNzKQ0KICAgIA0KICAgICMgbcOpdHJpcXVlcyBkZSBwZXJmb3JtYW5jZQ0KICAgIA0KICAgIFNlbnNpYmlsaXRlIDwtIGNvbmZ1c2lvbl9tYXRyaWNlcyRieUNsYXNzWydTZW5zaXRpdml0eSddDQogICAgU3BlY2lmaWNpdGUgPC0gY29uZnVzaW9uX21hdHJpY2VzJGJ5Q2xhc3NbJ1NwZWNpZmljaXR5J10NCiAgICBQcmVjaXNpb24gPC0gY29uZnVzaW9uX21hdHJpY2VzJGJ5Q2xhc3NbJ1BvcyBQcmVkIFZhbHVlJ10NCiAgICAjIGNhbGN1bCBsJ0FVQw0KICAgIEFVQyA8LSByb2ModGVzdCRDRFIsIGFzLm51bWVyaWMocHJlZGxkYSR4KSkkYXVjDQogICAgI3N0b2NrZXIgbGVzIG1ldHJpcXVlcyBwb3VyIGNoYXF1ZSBpdGVyYXRpb24NCiAgICBwZXJmb3JtYW5jZXNfbWV0cmljc1tbaXJ1bl1dID0gYyhTZW5zaWJpbGl0ZSwgU3BlY2lmaWNpdGUsIFByZWNpc2lvbiwgQVVDKQ0KICB9DQogIG1ldHJpY19ub21zPWMoIlNlbnNpYmlsaXRlIiwgIlNwZWNpZmljaXRlIiwgIlByZWNpc2lvbiIsICJBVUMiKQ0KICAjY2FsY3VsZXIgbGEgbW95ZW5uZSBkZXMgbWV0cmlxdWVzIHBvdXIgdG91dGVzIGxlcyBpdGVyYXRpb25zDQogIGF2cl9tZXRyaWNzPWNvbE1lYW5zKGRvLmNhbGwocmJpbmQscGVyZm9ybWFuY2VzX21ldHJpY3MpKQ0KICBtZXRyaWNfc3VtbWFyeSA8LSBkYXRhLmZyYW1lKE1ldHJpcXVlID0gbWV0cmljX25vbXMsTW95ZW5uZSA9IGF2cl9tZXRyaWNzKQ0KIA0KYGBgDQojIyBSb2J1c3Rlc3NlIGR1IG1vZMOobGUNCmBgYHtyfQ0KICMjY3LDqWVyIHVuIGdncGxvdA0KICByZWNhcExEQSA9IGdncGxvdChtZXRyaWNfc3VtbWFyeSwgYWVzKHggPSBNZXRyaXF1ZSwgeSA9IE1veWVubmUsIGZpbGwgPSBNZXRyaXF1ZSkpICsNCiAgICBnZW9tX2NvbCgpICsNCiAgICBsYWJzKHRpdGxlID0gIkxEQSAoMTAwIGl0w6lyYXRpb25zKSIsDQogICAgICAgICB4ID0gIiIsIHkgPSAiTW95ZW5uZSIpICsNCiAgICB0aGVtZV9taW5pbWFsKCkNCiAgcmVjYXBMREEgJT4lIHBsb3RseTo6Z2dwbG90bHkoKQ0KDQogIHJlY2FwTERBIDwtIHJlY2FwTERBICsgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkgKyB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKQ0KICANCiAgDQogICAgcmVjYXBMREEgJT4lIHBsb3RseTo6Z2dwbG90bHkoKQ0KYGBgDQoNCk5vdHJlIExEQSBkw6ltb250cmUgZGVzIHBlcmZvcm1hbmNlcyBzb2xpZGVzLCBhdmVjIHVuZSBBVUMgZGUgMCw4MCBpbmRpcXVhbnQgdW5lIGJvbm5lIGNhcGFjaXTDqSBkZSBkaXNjcmltaW5hdGlvbiBlbnRyZSBsZXMgY2xhc3NlcyBwb3NpdGl2ZXMgZXQgbsOpZ2F0aXZlcy4gRGUgcGx1cywgdW5lIHByw6ljaXNpb24gw6lsZXbDqWUgZGUgMCw5MCBzdWdnw6hyZSBxdWUgbGEgbWFqb3JpdMOpIGRlcyBwcsOpZGljdGlvbnMgcG9zaXRpdmVzIHNvbnQgY29ycmVjdGVzLiBCaWVuIHF1ZSBsYSBzZW5zaWJpbGl0w6kgc29pdCBsw6lnw6hyZW1lbnQgcGx1cyBiYXNzZSDDoCAwLDcwLCBlbGxlIGluZGlxdWUgdG91dCBkZSBtw6ptZSB1bmUgY2FwYWNpdMOpIMOgIGNhcHR1cmVyIHVuZSBwcm9wb3J0aW9uIHNpZ25pZmljYXRpdmUgZGVzIG9ic2VydmF0aW9ucyBwb3NpdGl2ZXMuIEVuZmluLCB1bmUgc3DDqWNpZmljaXTDqSBkZSAwLDc4IHNvdWxpZ25lIGxhIGNhcGFjaXTDqSBkdSBtb2TDqGxlIMOgIGlkZW50aWZpZXIgY29ycmVjdGVtZW50IGxlcyBvYnNlcnZhdGlvbnMgbsOpZ2F0aXZlcy4NCg0KIyMgUsOpc3VsdGF0cw0KDQpgYGB7cn0NCg0KcmVzbGRhMiA8LSBsZGEoQ0RSIH4gLiAsIGRhdGE9ZGYpDQpwcmVkbGRhMiA8LSBwcmVkaWN0KHJlc2xkYSxkZikNCg0KYm94cGxvdChwcmVkbGRhMiR4fmdwKQ0KDQpgYGANCg0KTGUgZ3JhcGhpcXVlIHByw6lzZW50ZSBkZXMgYm/DrnRlcyDDoCBtb3VzdGFjaGVzIHBvdXIgbGVzIGRldXggbml2ZWF1eCBkZSBub3RyZSB2YXJpYWJsZSBkw6lwZW5kYW50ZSAoMCDDqXRhbnQgbm9uIGTDqW1lbnQgZXQgMSBkw6ltZW50KS4gTGEgbcOpZGlhbmUgZHUgZ3JvdXBlIDEgc2VtYmxlIGzDqWfDqHJlbWVudCBwbHVzIMOpbGV2w6llIHF1ZSBjZWxsZSBkdSBncm91cGUgMCwgYXZlYyBkYXZhbnRhZ2UgZGUgdmFsZXVycyBhYmVycmFudGVzLCBzdWdnw6lyYW50IHVuZSBkaWZmw6lyZW5jZSBlbnRyZSBsZXMgZGV1eCBncm91cGVzIHBvdXIgbGEgdmFyaWFibGUgcHLDqWRpdGUgcGFyIGxhIExEQS4gSWwgeSBhIGF1c3NpIGNvbW1lIHVuIGNoZXZhdWNoZW1lbnQgZW50cmUgbGVzIGRpc3RyaWJ1dGlvbnMsIGluZGlxdWFudCB1bmUgc3VwZXJwb3NpdGlvbiBtYWxncsOpIGxhIGRpZmbDqXJlbmNlIGRlIHRlbmRhbmNlIGNlbnRyYWxlLg0KDQpgYGB7cn0NCiAjIGV4dHJhaXJlIGxlcyBjb2VmZmljaWVudHMgZHUgbW9kw6hsZSBMREENCiAgY29lZmZpY2llbnRzIDwtIGNvZWYocmVzbGRhMikNCiAgDQogICMgY3LDqWVyIHVuIGRhdGFmcmFtZSDDoCBwYXJ0aXIgZGVzIGNvZWZmaWNpZW50cw0KICBjb2VmZmljaWVudHNfZGYgPC0gZGF0YS5mcmFtZSgNCiAgICBWYXJpYWJsZSA9IHJvd25hbWVzKGNvZWZmaWNpZW50cyksDQogICAgQ29lZmZpY2llbnQgPSBjb2VmZmljaWVudHNbLCAxXSAgIyBFeHRyYWlyZSB1bmlxdWVtZW50IGxhIHByZW1pw6hyZSBjb2xvbm5lIGRlIGNvZWZmaWNpZW50cw0KICApDQogIA0KICAjIGNyw6llciBsZSBncmFwaGlxdWUgYXZlYyBsZXMgY29lZmZpY2llbnRzIGV4dHJhaXRzDQogIGdncGxvdChjb2VmZmljaWVudHNfZGYsIGFlcyh4ID0gVmFyaWFibGUsIHkgPSBDb2VmZmljaWVudCkpICsNCiAgICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgZmlsbCA9ICJza3libHVlIiwgd2lkdGggPSAwLjUpICsNCiAgICBsYWJzKHRpdGxlID0gIkNvZWZmaWNpZW50cyBkZSBsYSBkaW1lbnNpb24gZGlzY3JpbWluYW50ZSAoTEQxKSIsDQogICAgICAgICB4ID0gIiIsIHkgPSAiQ29lZmZpY2llbnQiKSArDQogICAgdGhlbWVfbWluaW1hbCgpICsNCiAgICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKQ0KYGBgDQpQb3VyIGludGVycHLDqXRlciBjZSBncmFwaGlxdWUsIG9uIHBldXQgZCdhYm9yZCBhZmZpY2hlciBjZSBxdWUgcmVwcsOpc2VudGUgbGVzIGRpbWVuc2lvbnMgZGUgbm90cmUgQUZDbSA6DQoNCiAgICAgICAgICAgICAgICAgRGltLjEgRGltLjIgRGltLjMgIA0Kc2V4ZSAgICAgICAgICAgfCAwLjI0MCAwLjM4MyAwLjAyMSB8DQpNTVNFICAgICAgICAgICB8IDAuNTQwIDAuMjYxIDAuNDM1IHwNCkVEVUMgICAgICAgICAgIHwgMC40NTEgMC41NDggMC42NTYgfA0KDQpDZWxsZSBxdWkgbm91cyBpbnTDqXJlc3NlIGxlIHBsdXMgcG91ciBsJ2ludGVycHLDqXRhdGlvbiwgYydlc3QgbGEgZGltZW5zaW9uIDEsIG9uIHBldXQgdm9pciBxdWUgbGEgdmFyaWFibGUgbGEgcGx1cyBpbXBvcnRhbnRlIGVzdCBsZSBNTVNFLCBhdmVjIDAuNTQwLCBzdWl2aSBwYXIgRURVQywgYXZlYyAwLjQ1MSwgZXQgZmluYWxlbWVudCBsZSBzZXhlLCBhdmVjIDAuMjQwLg0KDQpBaW5zaSwgbGUgZ3JhcGhpcXVlIG1vbnRyZSBsZXMgY29lZmZpY2llbnRzIGNhbGN1bMOpcyBwYXIgbGEgTERBIHBvdXIgY2hhcXVlIHZhcmlhYmxlLCBvbiBwZXV0IHZvaXIgcXVlIGxlIGNvZWZmaWNpZW50IGxlIHBsdXMgw6lsZXbDqSBlc3QgbGEgRGltZW5zaW9uIDEsIHN1aXZpIHBhciBsZSBWb2x1bWUgY8OpcsOpYnJhbCB0b3RhbCwgbGUgVm9sdW1lIGludGVyY3LDom5pZW4uDQoNCiMgVkkuIENvbmNsdXNpb24NCg0KYGBge3J9DQpncm9iX3JlY2FwQ0FSVCA8LSBnZ3Bsb3RHcm9iKHJlY2FwQ0FSVCkNCmdyb2JfcmVjYXBSRiA8LSBnZ3Bsb3RHcm9iKHJlY2FwUkYpDQpncm9iX1JlY2FwUkxCIDwtIGdncGxvdEdyb2IoUmVjYXBSTEIpDQpncm9iX3JlY2FwTERBIDwtIGdncGxvdEdyb2IocmVjYXBMREEpDQoNCmBgYA0KDQoNCmBgYHtyIGZpZy5oZWlnaHQ9OCwgZmlnLndpZHRoPTEyfQ0KZ3JpZC5hcnJhbmdlKGdyb2JfcmVjYXBDQVJULCBncm9iX3JlY2FwUkYsIGdyb2JfUmVjYXBSTEIsIGdyb2JfcmVjYXBMREEpDQpgYGANCiANCkNoYWN1bmUgZGVzIG3DqXRob2RlcyBwcsOpc2VudMOpZXMgZGFucyBjZSB0cmF2YWlsIGEgZ2xvYmFsZW1lbnQgYmllbiBmb25jdGlvbm7DqSBldCBhIGFwcG9ydMOpIGRlcyDDqWzDqW1lbnRzIHRyw6hzIGVucmljaGlzc2FudHMgZGFucyBsZXMgw6l0dWRlcyBzdXIgbGEgZMOpbWVuY2UgKGF2ZWMgZGUgcGV0aXRlcyBsYWN1bmVzIGVuIHNww6ljaWZpY2l0w6kgcG91ciBsYSByw6lncmVzc2lvbiBsb2dpc3RpcXVlKS4NCg0KVG91dGVzIG9udCBtb250csOpIHF1ZSBsZSBNTVNFIGEgdW5lIHBsYWNlIHByw6lwb25kw6lyYW50ZSBkYW5zIGxlIG1vZMOobGUgcHJvcG9zw6ksIGNlIHF1aSBtb250cmUgcXVlIGxlIHRlc3QgZm9uY3Rpb25uZSBiaWVuLCBjZSBxdWkgZXN0IGludMOpcmVzc2FudCBjYXIgaWwgeSBhIGRlcyBkw6liYXRzIGRhbnMgbGEgbGl0dMOpcmF0dXJlIHNjaWVudGlmaXF1ZSBxdWkgcGFybGVudCBkZSBzb24gYm9uIGZvbmN0aW9ubmVtZW50IG91IG5vbi4NCg0Kw4lnYWxlbWVudCwgbGUgdm9sdW1lIGPDqXLDqWJyYWwgdG90YWwgZXN0IHJlc3NvcnRpIGNvbW1lIHRyw6hzIGltcG9ydGFudCBwb3VyIGxlIENBUlQsIGxlIFJhbmRvbSBGb3Jlc3QgZXQgbGEgTERBLCBhaW5zaSBxdWUgdHLDqHMgc2lnbmlmaWNhdGlmIHBvdXIgbGEgcsOpZ3Jlc3Npb24gbG9naXN0aXF1ZS4NCg0KQmllbiBxdWUgbW9pbnMgcmVwcsOpc2VudMOpcyBkYW5zIGxlcyBtb2TDqGxlcywgbCfDomdlIChpY2kgZGUgNjAgw6AgOTYgYW5zKSwgbGUgbml2ZWF1IHNvY2lvLcOpY29ub21pcXVlIGV0IGxlIG5pdmVhdSBkJ8OpZHVjYXRpb24gKHF1aSB2b250IGRlIHBhaXIpLCBhaW5zaSBxdWUgbCdpbmRpY2UgZCdhdHJvcGhpZSwgb250IG1vbnRyw6kgbGV1ciBpbXBvcnRhbmNlIGV0IHNlbWJsZW50IMOqdHJlIGRlcyBmYWN0ZXVycyBpbXBvcnRhbnRzIGRhbnMgbGEgY29tcHLDqWhlbnNpb24gZHUgZm9uY3Rpb25uZW1lbnQgZGVzIGTDqW1lbmNlcy4NCg0KUGFyIGNvbnRyZSwgbGUgdm9sdW1lIGludGVyY3LDom5pZW4gbifDqXRhaXQgdnJhaW1lbnQgcGFzIHNpZ25pZmljYXRpZiBwb3VyIGxhIHLDqWdyZXNzaW9uIGxvZ2lzdGlxdWUgKFAudmFsdWUgZGUgMC44OSksIGFsb3JzIHF1J2lsIHNlbWJsZSBhdm9pciB1bmUgaW1wb3J0YW5jZSBwb3VyIGxhIExEQSwgcG91ciBsZSBDQVJUIGV0IHBvdXIgbGUgUmFuZG9tIEZvcmVzdCwgY2VsYSBzdWdnw6hyZSBxdWUgbGEgcsOpZ3Jlc3Npb24gbG9naXNpcXVlIHBvdXJyYWl0IG5lIHBhcyDDqnRyZSBsYSBtw6l0aG9kZSBsYSBwbHVzIGZpYWJsZSBwb3VyIG1vZMOpbGlzZXIgbGEgcmVsYXRpb24gZW50cmUgbm9zIHZhcmlhYmxlcyBpbmTDqXBlbmRhbnRlcyBldCBsYSB2YXJpYWJsZSBkw6lwZW5kYW50ZSAoY2UgcXVpIGNlIGNvbmZpcm1lIHBhciBsZXMgY29uZGl0aW9ucyBkJ2FwcGxpY2F0aW9ucykuDQoNCiMgVklJLiBMaW1pdGUNCg0KRGFucyBsZSBjYWRyZSBkZSBjZXR0ZSDDqXR1ZGUsIGlsIGVzdCBwZXJ0aW5lbnQgZGUgc291bGlnbmVyIHVuZSBsaW1pdGF0aW9uIGltcG9ydGFudGUgOiB0b3VzIGxlcyBwYXJ0aWNpcGFudHMgw6l0YWllbnQgZGVzIGRyb2l0aWVycy4gQ2V0dGUgYWJzZW5jZSBkZSBnYXVjaGVycyBkYW5zIGwnw6ljaGFudGlsbG9uIGVzdCBzaWduaWZpY2F0aXZlLCBjYXIgbGEgbGl0dMOpcmF0dXJlIHNjaWVudGlmaXF1ZSBzdWdnw6hyZSBxdSdpbCBleGlzdGUgZGVzIGRpZmbDqXJlbmNlcyBuZXVyb2xvZ2lxdWVzIGVudHJlIGxlcyBnYXVjaGVycyBldCBsZXMgZHJvaXRpZXJzLCBub3RhbW1lbnQgZW4gY2UgcXVpIGNvbmNlcm5lIGxhIGxhdMOpcmFsaXNhdGlvbiBjw6lyw6licmFsZSBldCBsYSBzdHJ1Y3R1cmUgZHUgY2VydmVhdS4gUHJvY2hhaW5lbWVudCwgaWwgcG91cnJhaXQgw6p0cmUgaW50w6lyZXNzYW50IGQnZW4gaW5jbHVyZSBhZmluIGRlIHZvaXJzIHNpIHVuZSB2YXJpYWJsZSBpbmNsdWFudCBsYSBsYXTDqXJhbGl0w6kgcG91cnJhaXQgw6p0cmUgb3Ugbm9uIHNpZ25pZmljYXRpdmUuDQoNCiMgVklJSS4gT3V2ZXJ0dXJlDQoNCkRhbnMgbGEgY29udGludWl0w6kgZGUgY2V0dGUgw6l0dWRlLCB1bmUgZXhwbG9yYXRpb24gcGx1cyBhcHByb2ZvbmRpZSBkZXMgZMOpbWVuY2VzIHBvdXJyYWl0IMOqdHJlIGZhaXRlcyBlbiBpbnTDqWdyYW50IGRlIG5vdXZlbGxlcyB2YXJpYWJsZXMgcGVydGluZW50ZXMuIFBhcm1pIGNlbGxlcy1jaSwgb24gcG91cnJhaXQgZW52aXNhZ2VyIGwnaW5jbHVzaW9uIGRlIG1hcnF1ZXVycyBiaW9sb2dpcXVlcyB0ZWxzIHF1ZSBsZXMgYmlvbWFycXVldXJzIGRlIGxhIHByb3TDqWluZSBiw6p0YS1hbXlsb8OvZGUgZXQgZGUgbGEgcHJvdMOpaW5lIHRhdSwgcXVpIHNvbnQgbGFyZ2VtZW50IMOpdHVkacOpcyBkYW5zIGxlIGRvbWFpbmUgZGUgbGEgbmV1cm9sb2dpZSBldCBwb3VycmFpZW50IGZvdXJuaXIgZGVzIGluZm9ybWF0aW9ucyBwcsOpY2lldXNlcyBzdXIgbGVzIG3DqWNhbmlzbWVzIHNvdXMtamFjZW50cyBhdXggZMOpbWVuY2VzLg0KDQpEZSBwbHVzLCBsJ2luY29ycG9yYXRpb24gZGUgdmFyaWFibGVzIGxpw6llcyBhdSBtb2RlIGRlIHZpZSBldCDDoCBsJ2Vudmlyb25uZW1lbnQsIHRlbGxlcyBxdWUgbCdhY3Rpdml0w6kgcGh5c2lxdWUsIGxlIHLDqWdpbWUgYWxpbWVudGFpcmUsIGwnZXhwb3NpdGlvbiBhdXggdG94aW5lcyBlbnZpcm9ubmVtZW50YWxlcywgZXQgbGUgbml2ZWF1IGRlIHN0cmVzcywgcG91cnJhaXQgb2ZmcmlyIHVuZSBwZXJzcGVjdGl2ZSBwbHVzIGhvbGlzdGlxdWUgc3VyIGxlcyBmYWN0ZXVycyBkZSByaXNxdWUgZXQgbGVzIGTDqXRlcm1pbmFudHMgZGVzIGTDqW1lbmNlcy4NCg==