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()
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()
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()
genre = fastggplot(demence2, "sexe", "Répartition par sexe", "Genre", "Nombre de cas")
genre %>% plotly::ggplotly()
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()
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()
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()
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()
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()
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()
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()
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()
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()
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()
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)
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()
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’arble :
MMSE: Le premier nœud divise les individus en
fonction de leur score au Mini-Mental State Examination (MMSE). Un score
inférieur à la limite indiquée (non spécifiée ici) mène directement à un
diagnostic de démence (probabilité de 1,00 ou 100%).
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”).
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%).
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.
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, ]
# Utiliser un modèle Random Forest au lieu de CART
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)
))
# 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)
# 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 intercranien :
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’utilisation
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" "Volume_intercranien"
[4] "Volume_cérébral_total" "Indice_atrophie" "CDR_binaire"
[7] "Dim.1" "Dim.2" "Dim.3"
[10] "Dim.4" "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)
##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.
reslda2 <- lda(CDR ~ . , data=df)
predlda2 <- predict(reslda,df)
boxplot(predlda2$x~gp)

# 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))

LS0tDQp0aXRsZTogInN1cGVyIHByb2pldCINCmF1dGhvcjogInZhbG91Ig0Kb3V0cHV0Og0KICBodG1sX25vdGVib29rOg0KICAgIHRvYzogdHJ1ZQ0KICAgIGRmX3ByaW50OiBwYWdlZA0KICAgIHRoZW1lOiBqb3VybmFsDQogICAgdG9jX2Zsb2F0OiB0cnVlDQogICAgc291cmNlOiBlbWJlZA0KICBodG1sX2RvY3VtZW50Og0KICAgIHRvYzogdHJ1ZQ0KICAgIGRmX3ByaW50OiBwYWdlZA0KLS0tDQoNCmBgYHs9aHRtbH0NCjxzdHlsZT4NCmJvZHl7DQogIHRleHQtYWxpZ246IGp1c3RpZnl9DQo8L3N0eWxlPg0KYGBgDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkNCg0KbGlicmFyeShkYXRhLnRhYmxlKQ0KbGlicmFyeShjYXJldCkNCmxpYnJhcnkocmFuZG9tRm9yZXN0KQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeSh2Y2QpDQpsaWJyYXJ5KHJwYXJ0KQ0KbGlicmFyeShycGFydC5wbG90KQ0KbGlicmFyeShjYXIpDQpsaWJyYXJ5KE1BU1MpDQpsaWJyYXJ5KGdyaWRFeHRyYSkNCmxpYnJhcnkocGxvdGx5KQ0KbGlicmFyeShwUk9DKQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkoRmFjdG9NaW5lUikNCmluc3RhbGwucGFja2FnZXMoImFybSIpDQpsaWJyYXJ5KGFybSkNCg0KDQoNCnRlbXAgPC0gdGVtcGZpbGUoZmlsZWV4dCA9ICIuY3N2IikNCmRvd25sb2FkLmZpbGUoImh0dHBzOi8vZHJpdmUuZ29vZ2xlLmNvbS91Yz9leHBvcnQ9ZG93bmxvYWQmaWQ9MWVhYzVxQ0hkMV93NEttRm54WGJSckE1bVJSNGM4WFh0IiwgdGVtcCkNCmRlbWVuY2UyIDwtIHJlYWQuY3N2KHRlbXApDQoNCmRlbWVuY2UyIDwtIGRlbWVuY2UyWywgLWMoMSwgMiwgMywgNCwgNSwgNyldDQoNCiMgUsOpZHVjdGlvbiBkdSBub21icmUgZGUgbml2ZWF1eCBkZSBsYSB2YXJpYWJsZSBFRFVDDQpkZW1lbmNlMiA8LSBkZW1lbmNlMiAlPiUNCiAgbXV0YXRlKEVEVUMgPSBjYXNlX3doZW4oDQogICAgRURVQyAlaW4lIDY6OCB+ICI2LTgiLA0KICAgIEVEVUMgJWluJSAxMToxMyB+ICIxMS0xMyIsDQogICAgRURVQyAlaW4lIDE0OjE2IH4gIjE0LTE2IiwNCiAgICBFRFVDICVpbiUgMTc6MTggfiAiMTctMTgiLA0KICAgIEVEVUMgJWluJSAyMDoyMyB+ICIyMC0yMyIsDQogICAgVFJVRSB+IGFzLmNoYXJhY3RlcihFRFVDKSAgIyBWYWxldXJzIHF1aSBuZSByZW50cmVudCBkYW5zIGF1Y3VuIGdyb3VwZQ0KICApKQ0KDQoNCmRlbWVuY2UyJENEUiA8LSBpZmVsc2UoZGVtZW5jZTIkQ0RSID09IDAsICJOb25fZMOpbWVudCIsICJEw6ltZW50IikNCg0KaW50ZXJ2YWxzIDwtIGN1dChkZW1lbmNlMiRNTVNFLCBicmVha3MgPSBjKDAsIDE3LCAyMywgMzApLCBsYWJlbHMgPSBjKCJUcm91YmxlX3PDqXbDqHJlIiwgIlRyb3VibGVfbMOpZ2VyIiwgIkF1Y3VuX3Ryb3VibGUiKSwgaW5jbHVkZS5sb3dlc3QgPSBUUlVFKQ0KDQpkZW1lbmNlMiRNTVNFIDwtIGFzLmNoYXJhY3RlcihpbnRlcnZhbHMpDQp0YWJsZShkZW1lbmNlMiRNTVNFKQ0KdGFibGUoZGVtZW5jZTIkQ0RSKQ0KDQpkZW1lbmNlMiRDRFIgPC0gYXMuZmFjdG9yKGRlbWVuY2UyJENEUikNCmRlbWVuY2UyJHNleGUgPC0gZmFjdG9yKGRlbWVuY2UyJHNleGUpDQpkZW1lbmNlMiRTRVMgPC0gZmFjdG9yKGRlbWVuY2UyJFNFUykNCmRlbWVuY2UyJEVEVUMgPC0gZmFjdG9yKGRlbWVuY2UyJEVEVUMpDQpkZW1lbmNlMiRNTVNFIDwtIGZhY3RvcihkZW1lbmNlMiRNTVNFKQ0KdGFibGUoZGVtZW5jZTIkQ0RSKQ0KDQojIEJvdWNsZSBwb3VyIGNvbnZlcnRpciBkZXMgY29sb25uZXMgc3DDqWNpZmlxdWVzIGVuIG51bcOpcmlxdWVzDQpmb3IgKGkgaW4gYygiQWdlIiwgIm5XQlYiLCAiQVNGIiwgImVUSVYiKSkgew0KICBkZW1lbmNlMlssIGldIDwtIGFzLm51bWVyaWMoYXMuY2hhcmFjdGVyKGRlbWVuY2UyWywgaV0pKQ0KfQ0KDQojIFN1cHByaW1lciBsZXMgdmFsZXVycyBtYW5xdWFudGVzDQpkZW1lbmNlMiA8LSBuYS5vbWl0KGRlbWVuY2UyKQ0KDQpjb2xuYW1lcyhkZW1lbmNlMikgPC0gYygic2V4ZSIsICJBZ2UiLCAiRURVQyIsICJTRVMiLCAiTU1TRSIsICJDRFIiLCAiVm9sdW1lX2ludGVyY3JhbmllbiIsICJWb2x1bWVfY8OpcsOpYnJhbF90b3RhbCIsICJJbmRpY2VfYXRyb3BoaWUiKQ0KDQoNCg0KZmFzdGdncGxvdCA8LSBmdW5jdGlvbihkYXRhLCB4X3ZhciwgdGl0bGUsIHhfbGFiZWwsIHlfbGFiZWwpIHsNCiAgZ2dwbG90KGRhdGEsIGFlcyh4ID0gZmFjdG9yKGRhdGFbW3hfdmFyXV0pLCBmaWxsID0gZmFjdG9yKGRhdGFbW3hfdmFyXV0pKSkgKw0KICAgIGdlb21fYmFyKGNvbG9yID0gImJsYWNrIiwgYWxwaGEgPSAwLjgpICsNCiAgICANCiAgICBsYWJzKHRpdGxlID0gdGl0bGUsIHggPSB4X2xhYmVsLCB5ID0geV9sYWJlbCwgZmlsbCA9IHhfdmFyKSArDQogICAgdGhlbWVfbWluaW1hbCgpICsNCiAgICB0aGVtZShheGlzLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyKSwNCiAgICAgICAgICBheGlzLnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNCwgZmFjZSA9ICJib2xkIiksDQogICAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTYsIGZhY2UgPSAiYm9sZCIpKQ0KfQ0KDQoNCmBgYA0KDQoqTGEgbWFqb3JpdMOpIGRlcyBncmFwaGlxdWVzIHByw6lzZW50w6lzIHNvbnQgaW50ZXJhY3RpZnMsIG4naMOpc2l0ZXogcGFzIMOgIHBhc3NlciB2b3RyZSBzb3VyaXMgZGVzc3VzIHBvdXIgYXZvaXIgbGVzIHZhbGV1cnMgZXhhY3RlcyAh8J+ZgioNCg0KIyBJbnRyb2R1Y3Rpb24NCg0KTm90cmUgcmVjaGVyY2hlIHNlIGNvbmNlbnRyZSBzdXIgbCdpZGVudGlmaWNhdGlvbiBkZXMgZmFjdGV1cnMgcHLDqWRpY3RpZnMgZGUgbGEgZMOpbWVuY2UuIE5vdHJlICoqdmFyaWFibGUgZMOpcGVuZGFudGUsIGxlICJDRFIiKiogKENsaW5pY2FsIERlbWVudGlhIFJhdGUpLCBlc3QgdW4gaW5kaWNhdGV1ciBuZXVyb2xvZ2lxdWUgw6l0YWJsaSBlbiAxOTkzIHBhciBNb3JyaXMuIENldHRlIHZhcmlhYmxlIGVzdCBpY2kgYmluYWlyZSwgYXZlYyAwIHBvdXIgbGVzIGluZGl2aWR1cyBub24gYXR0ZWludHMgZGUgZMOpbWVuY2UgZXQgMSBwb3VyIGNldXggcXVpIGxlIHNvbnQuIE5vdXMgZXhhbWlub25zIMOpZ2FsZW1lbnQgcGx1c2lldXJzIGF1dHJlcyB2YXJpYWJsZXMgcG90ZW50aWVsbGVtZW50IHBlcnRpbmVudGVzLCBub3RhbW1lbnQgKipsZSBzZXhlLCBsJ8OiZ2UsIGxlIG5pdmVhdSBkJ8OpZHVjYXRpb24sIGxlIG5pdmVhdSBzb2Npby1jdWx0dXJlbCwgbGUgdm9sdW1lIGPDqXLDqWJyYWwgdG90YWwsIGxlIHZvbHVtZSBpbnRlcmNyw6JuaWVuLCBsZSBuaXZlYXUgZCdhdHJvcGhpZSwgYWluc2kgcXVlIGxlIE1pbmkgTWVudGFsIFN0YXRlIEV4YW1pbmF0aW9uIChNTVNFKSoqLiBCaWVuIHF1ZSBsZSBNTVNFIHNvaXQgdW4gdGVzdCBuZXVyb2xvZ2lxdWUgbGFyZ2VtZW50IHV0aWxpc8OpIGVuIHJhaXNvbiBkZSBzYSByYXBpZGl0w6ksIGlsIGVzdCB0b3V0ZWZvaXMgc291dmVudCBjcml0aXF1w6kgZGFucyBsYSBsaXR0w6lyYXR1cmUgcG91ciBzYSBtb2luZHJlIHByw6ljaXNpb24gZXQgc2EgZmlhYmlsaXTDqSB2YXJpYWJsZS4gTm91cyBhdm9ucyBsJ29wcG9ydHVuaXTDqSBkZSB2w6lyaWZpZXIgY2VzIHBvaW50cyBlbiBsJ2luY2x1YW50IGRhbnMgbm90cmUgw6l0dWRlIHBhcmFsbMOobGVtZW50IGF1IENEUi4NCg0KIyBJLiBBbmFseXNlIGRlc2NyaXB0aXZlDQoNCiMjIEp1c3RpZmljYXRpb24NCg0KTCdhbmFseXNlIGRlc2NyaXB0aXZlIHBlcm1ldCBkZSBjb21wcmVuZHJlIGxhIGRpc3RyaWJ1dGlvbiBldCBsZXMgY2FyYWN0w6lyaXN0aXF1ZXMgZ8OpbsOpcmFsZXMgZGVzIGRvbm7DqWVzLCBvZmZyYW50IGFpbnNpIHVuIGFwZXLDp3UgcHLDqWxpbWluYWlyZSBkZXMgdGVuZGFuY2VzIGV0IGRlcyBhc3NvY2lhdGlvbnMgcG90ZW50aWVsbGVzIGVudHJlIGxlcyB2YXJpYWJsZXMgw6l0dWRpw6llcy4NCg0KSWwgZXN0IGltcG9ydGFudCBkZSBzJ2Fzc3VyZXIgcXVlIGxlcyBuaXZlYXV4IGRlcyB2YXJpYWJsZXMgc29udCBhc3NleiDDqXF1aWxpYnLDqSwgY2FyIHNpbm9uIGlscyBwb3VycmFpZW50IMOqdHJlIHNvdXMtcmVwcsOpc2VudMOpcyBkYW5zIGxlcyBhbmFseXNlcyDDoCB2ZW5pciwgc2kgYydlc3QgbGUgY2FzIGlsIHNlcmEgYWluc2kgaW1wb3J0YW50IGRlIGxlIHByZW5kcmUgZW4gY29tcHRlIGxvcnMgZGUgbCdpbnRlcnByw6l0YXRpb24uDQoNCiMjIEdyYXBoaXF1ZSBkZXMgdmFyaWFibGVzIHF1YWxpdGF0aXZlcw0KDQpQb3VyIHNpbXBsaWZpZXIgbGEgY3LDqWF0aW9uIGRlcyBncmFwaGlxdWVzLCBub3VzIHBvdXZvbnMgw6ljcmlyZSB1bmUgZm9uY3Rpb24gcXVpIGluY2x1dCB0b3VzIGxlcyBwYXJhbcOodHJlcyBuw6ljZXNzYWlyZXMuIENlbGEgbm91cyBwZXJtZXR0cmEgZGUgZ8OpbsOpcmVyIGRlcyBnZ3Bsb3QgcGx1cyByYXBpZGVtZW50Lg0KDQpgYGB7ciBjYXJzfQ0KDQpmYXN0Z2dwbG90IDwtIGZ1bmN0aW9uKGRhdGEsIHhfdmFyLCB0aXRsZSwgeF9sYWJlbCwgeV9sYWJlbCkgew0KICBnZ3Bsb3QoZGF0YSwgYWVzKHggPSBmYWN0b3IoZGF0YVtbeF92YXJdXSksIGZpbGwgPSBmYWN0b3IoZGF0YVtbeF92YXJdXSkpKSArDQogICAgZ2VvbV9iYXIoY29sb3IgPSAiYmxhY2siLCBhbHBoYSA9IDAuOCkgKw0KICAgIA0KICAgIGxhYnModGl0bGUgPSB0aXRsZSwgeCA9IHhfbGFiZWwsIHkgPSB5X2xhYmVsLCBmaWxsID0geF92YXIpICsNCiAgICB0aGVtZV9taW5pbWFsKCkgKw0KICAgIHRoZW1lKGF4aXMudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIpLA0KICAgICAgICAgIGF4aXMudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE0LCBmYWNlID0gImJvbGQiKSwNCiAgICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNiwgZmFjZSA9ICJib2xkIikpDQp9DQoNCmBgYA0KDQpgYGB7cn0NCkNEUiA8LSBmYXN0Z2dwbG90KGRlbWVuY2UyLCAiQ0RSIiwgIlLDqXBhcnRpdGlvbiBwYXIgc2NvcmUgQ0RSIiwgIlNjb3JlIENEUiIsICJOb21icmUgZGUgY2FzIikNCkNEUiAlPiUgcGxvdGx5OjpnZ3Bsb3RseSgpDQpgYGANCg0KYGBge3J9DQpNTVNFIDwtIGZhc3RnZ3Bsb3QoZGVtZW5jZTIsICJNTVNFIiwgIlLDqXBhcnRpdGlvbiBwYXIgc2NvcmUgTU1TRSIsICJTY29yZSBNTVNFIiwgIk5vbWJyZSBkZSBjYXMiKQ0KDQpNTVNFIDwtIE1NU0UgKyB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKQ0KDQpNTVNFICU+JSBwbG90bHk6OmdncGxvdGx5KCkNCmBgYA0KDQpgYGB7cn0NCkVEVUMgPC0gZmFzdGdncGxvdChkZW1lbmNlMiwgIkVEVUMiLCAiUsOpcGFydGl0aW9uIHBhciBuaXZlYXUgZCfDqWR1Y2F0aW9uIiwgIk5pdmVhdSBkJ8OpZHVjYXRpb24iLCAiTm9tYnJlIGRlIGNhcyIpDQoNCkVEVUMgPC0gRURVQyArIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpDQoNCkVEVUMgJT4lIHBsb3RseTo6Z2dwbG90bHkoKQ0KYGBgDQoNCmBgYHtyfQ0KZ2VucmUgPSBmYXN0Z2dwbG90KGRlbWVuY2UyLCAic2V4ZSIsICJSw6lwYXJ0aXRpb24gcGFyIHNleGUiLCAiR2VucmUiLCAiTm9tYnJlIGRlIGNhcyIpDQpnZW5yZSAlPiUgcGxvdGx5OjpnZ3Bsb3RseSgpDQpgYGANCg0KIyMgR3JhcGhpcXVlIGRlcyB2YXJpYWJsZXMgcXVhbnRpdGF0aXZlcw0KDQpgYGB7cn0NCmhpc3RfYWdlIDwtIGdncGxvdChkZW1lbmNlMiwgYWVzKHggPSBBZ2UpKSArDQogIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMC4wMSwgZmlsbCA9ICJncmVlbiIsIGNvbG9yID0gImJsYWNrIiwgYWxwaGEgPSAwLjgpICsNCiAgbGFicyh0aXRsZSA9ICJEaXN0cmlidXRpb24gZGUgbCfDomdlIiwgeCA9ICIiLCB5ID0gIkZyw6lxdWVuY2UiKSArDQogIHRoZW1lX21pbmltYWwoKQ0KaGlzdF9hZ2UgJT4lIHBsb3RseTo6Z2dwbG90bHkoKQ0KYGBgDQoNCmBgYHtyfQ0KDQpoaXN0X253YnYgPC0gZ2dwbG90KGRlbWVuY2UyLCBhZXMoeCA9IFZvbHVtZV9jw6lyw6licmFsX3RvdGFsKSkgKw0KICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDAuMDEsIGZpbGwgPSAiZ3JlZW4iLCBjb2xvciA9ICJibGFjayIsIGFscGhhID0gMC44KSArDQogIGxhYnModGl0bGUgPSAiRGlzdHJpYnV0aW9uIGR1IHZvbHVtZSBjw6lyw6licmFsIHRvdGFsIiwgeCA9ICJWb2x1bWUgY8OpcsOpYnJhbCB0b3RhbCIsIHkgPSAiRnLDqXF1ZW5jZSIpICsNCiAgdGhlbWVfbWluaW1hbCgpDQpoaXN0X253YnYgJT4lIHBsb3RseTo6Z2dwbG90bHkoKQ0KYGBgDQoNCmBgYHtyfQ0KaGlzdF9ldGl2IDwtIGdncGxvdChkZW1lbmNlMiwgYWVzKHggPSBWb2x1bWVfaW50ZXJjcmFuaWVuKSkgKw0KICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDEwMCwgZmlsbCA9ICJvcmFuZ2UiLCBjb2xvciA9ICJibGFjayIsIGFscGhhID0gMC44KSArDQogIGxhYnModGl0bGUgPSAiRGlzdHJpYnV0aW9uIGR1IHZvbHVtZSBpbnRlcmNyYW5pZW4iLCB4ID0gImVUSVYiLCB5ID0gIkZyw6lxdWVuY2UiKSArDQogIHRoZW1lX21pbmltYWwoKQ0KaGlzdF9ldGl2ICU+JSBwbG90bHk6OmdncGxvdGx5KCkNCmBgYA0KDQpgYGB7cn0NCmhpc3RfYXNmIDwtIGdncGxvdChkZW1lbmNlMiwgYWVzKHggPSBJbmRpY2VfYXRyb3BoaWUpKSArDQogIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMC4xLCBmaWxsID0gImJsdWUiLCBjb2xvciA9ICJibGFjayIsIGFscGhhID0gMC44KSArDQogIGxhYnModGl0bGUgPSAiRGlzdHJpYnV0aW9uIGRlIGwnaW5kaWNlIGQnYXRyb3BoaWUiLCB4ID0gIkluZGljZV9hdHJvcGhpZSIsIHkgPSAiRnLDqXF1ZW5jZSIpICsNCiAgdGhlbWVfbWluaW1hbCgpDQpoaXN0X2FzZiAlPiUgcGxvdGx5OjpnZ3Bsb3RseSgpDQpgYGANCg0KUG91ciBhbGxlciBwbHVzIGxvaW4gZGFucyBsJ2FuYWx5c2UgZGVzY3JpcHRpdmUsIG9uIHBldXQgw6lnYWxlbWVudCBmYWlyZSBkZXMgZ3JhcGhpcXVlcyBxdWkgYXNzb2NpZW50IG5vdHJlIHZhcmlhYmxlIGTDqXBlbmRhbnRlLCBsZSBDRFIsIGF1eCBhdXRyZXMuDQoNCiMjIEdyYXBoaXF1ZSBhc3NvY2lhbnQgbGEgdmFyaWFibGUgZMOpcGVuZGFudGUgKENEUikgZXQgbGVzIHZhcmlhYmxlcyBpbmTDqXBlbmRhbnRlcw0KDQpgYGB7cn0NCkNEUlNFWEUgPSBnZ3Bsb3QoZGF0YSA9IGRlbWVuY2UyLCBhZXMoeCA9IENEUiwgZmlsbCA9IHNleGUpKSArDQogIGdlb21fYmFyKHBvc2l0aW9uID0gImRvZGdlIikgKw0KICBsYWJzKHRpdGxlID0gIkNvbXBhcmFpc29uIGRlIENEUiBwYXIgc2V4ZSIsDQogICAgICAgeCA9ICJDRFIiLA0KICAgICAgIHkgPSAiTm9tYnJlIGRlIHBhcnRpY2lwYW50cyIsDQogICAgICAgZmlsbCA9ICJTZXhlIikNCkNEUlNFWEUgJT4lIHBsb3RseTo6Z2dwbG90bHkoKQ0KYGBgDQoNCmBgYHtyfQ0KQ0RSRURVQyA9IGdncGxvdChkYXRhID0gZGVtZW5jZTIsIGFlcyh4ID0gQ0RSLCBmaWxsID0gRURVQykpICsNCiAgZ2VvbV9iYXIocG9zaXRpb24gPSAiZG9kZ2UiKSArDQogIGxhYnModGl0bGUgPSAiQ29tcGFyYWlzb24gZGUgQ0RSIHBhciBuaXZlYXUgZCfDqWR1Y2F0aW9uIiwNCiAgICAgICB4ID0gIkNEUiIsDQogICAgICAgeSA9ICJOb21icmUgZGUgcGFydGljaXBhbnRzIiwNCiAgICAgICBmaWxsID0gIkVEVUMiKQ0KDQpDRFJFRFVDICU+JSBwbG90bHk6OmdncGxvdGx5KCkNCmBgYA0KDQpgYGB7cn0NCkNEUlNFUyA9IGdncGxvdChkYXRhID0gZGVtZW5jZTIsIGFlcyh4ID0gQ0RSLCBmaWxsID0gU0VTKSkgKw0KICBnZW9tX2Jhcihwb3NpdGlvbiA9ICJkb2RnZSIpICsNCiAgbGFicyh0aXRsZSA9ICJDb21wYXJhaXNvbiBkZSBDRFIgcGFyIG5pdmVhdSBzb2Npby3DqWNvbm9taXF1ZSIsDQogICAgICAgeCA9ICJDRFIiLA0KICAgICAgIHkgPSAiTm9tYnJlIGRlIHBhcnRpY2lwYW50cyIsDQogICAgICAgZmlsbCA9ICJTRVMiKQ0KDQpDRFJTRVMgJT4lIHBsb3RseTo6Z2dwbG90bHkoKQ0KYGBgDQoNCmBgYHtyfQ0KQ0RSU0VTID0gZ2dwbG90KGRhdGEgPSBkZW1lbmNlMiwgYWVzKHggPSBDRFIsIGZpbGwgPSBNTVNFKSkgKw0KICBnZW9tX2Jhcihwb3NpdGlvbiA9ICJkb2RnZSIpICsNCiAgbGFicyh0aXRsZSA9ICJDb21wYXJhaXNvbiBkZSBDRFIgcGFyIE1NU0UiLA0KICAgICAgIHggPSAiQ0RSIiwNCiAgICAgICB5ID0gIk5vbWJyZSBkZSBwYXJ0aWNpcGFudHMiLA0KICAgICAgIGZpbGwgPSAiTU1TRSIpDQoNCkNEUlNFUyAlPiUgcGxvdGx5OjpnZ3Bsb3RseSgpDQpgYGANCg0KYGBge3J9DQpDRFJlVElWID0gZ2dwbG90KGRlbWVuY2UyLCBhZXMoeCA9IENEUiwgeSA9IFZvbHVtZV9jw6lyw6licmFsX3RvdGFsKSkgKw0KICBnZW9tX2JveHBsb3QoZmlsbCA9ICJsaWdodGdyZWVuIiwgY29sb3IgPSAiYmxhY2siKSArDQogIGxhYnMoeCA9ICJDRFIiLCB5ID0gIlZvbHVtZSBjw6lyw6licmFsIHRvdGFsIiwgdGl0bGUgPSAiRGlzdHJpYnV0aW9uIGR1IHZvbHVtZSBjw6lyw6licmFsIHRvdGFsIHBhciBDRFIiKQ0KDQpDRFJlVElWICU+JSBwbG90bHk6OmdncGxvdGx5KCkNCmBgYA0KDQpgYGB7cn0NCkNEUkFTRiA9IGdncGxvdChkZW1lbmNlMiwgYWVzKHggPSBDRFIsIHkgPSBJbmRpY2VfYXRyb3BoaWUpKSArDQogIGdlb21fYm94cGxvdChmaWxsID0gImRhcmtyZWQiLCBjb2xvciA9ICJibGFjayIpICsNCiAgbGFicyh4ID0gIkNEUiIsIHkgPSAiQVNGIiwgdGl0bGUgPSAiRGlzdHJpYnV0aW9uIGRlIGwnaW5kaWNlIGQnYXRyb3BoaWUgcGFyIENEUiIpDQpDRFJBU0YgJT4lIHBsb3RseTo6Z2dwbG90bHkoKQ0KYGBgDQoNCg0KIyMgQ29ycsOpbGF0aW9uIGRlIHBvaW50LWJpc8OpcmlhbGUsIENEUiArIFgNCg0KTGEgY29ycsOpbGF0aW9uIHBvaW50LWJpc8OpcmlhbGUgbWVzdXJlIGxhIHJlbGF0aW9uIGVudHJlIHVuZSB2YXJpYWJsZSBjb250aW51ZSBldCB1bmUgdmFyaWFibGUgYmluYWlyZS4gVW4gY29lZmZpY2llbnQgcHJvY2hlIGRlICsxIG91IC0xIGluZGlxdWUgdW5lIGNvcnLDqWxhdGlvbiBmb3J0ZSwgdGFuZGlzIHF1J3VuZSB2YWxldXIgcHJvY2hlIGRlIHrDqXJvIHN1Z2fDqHJlIHVuZSBmYWlibGUgY29ycsOpbGF0aW9uLiBMJ3V0aWxpc2F0aW9uIGRlIGNldHRlIG3DqXRob2RlIHBldXQgbm91cyBwZXJtZXR0cmUgZCdhdm9pciB1biBwcmVtaWVyIGFwZXLDp3UgZGVzIHJlbGF0aW9ucyBwb3NzaWJsZXMgZW50cmUgbm90cmUgdmFyaWFibGUgZMOpcGVuZGFudGUgZXQgbm9zIHZhcmlhYmxlcyBpbmTDqXBlbmRhbnRlcy4NCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0V9DQoNCiAgICBGT1Jjb3JiIDwtIGRlbWVuY2UyIA0KICAgICMgY3LDqWVyIHVuZSB2YXJpYWJsZSBiaW5haXJlIMOgIHBhcnRpciBkZSBDRFINCiAgICBGT1Jjb3JiJENEUl9iaW5haXJlIDwtIGlmZWxzZShGT1Jjb3JiJENEUiA9PSAiRMOpbWVudCIsIDEsIDApDQogICAgDQogICAgRk9SY29yYiRDRFJfbnVtZXJpYyA8LSBGT1Jjb3JiJENEUg0KICAgIA0KICAgICMgc8OpbGVjdGlvbiBkZXMgdmFyaWFibGVzIHBlcnRpbmVudGVzDQogICAgdmFyaWFibGVzX3F1YW50aXRhdGl2ZXMgPC0gRk9SY29yYlssIGMoIkFnZSIsICJWb2x1bWVfaW50ZXJjcmFuaWVuIiwgIlZvbHVtZV9jw6lyw6licmFsX3RvdGFsIiwgIkluZGljZV9hdHJvcGhpZSIpXQ0KICAgIA0KICAgIHZhcmlhYmxlX2JpbmFpcmUgPC0gYXMubnVtZXJpYyhGT1Jjb3JiJENEUl9udW1lcmljKQ0KICAgIA0KICAgICMgY3LDqWVyIHVuZSBsaXN0ZSBwb3VyIHN0b2NrZXIgbGVzIHLDqXN1bHRhdHMNCiAgICByZXN1bHRzIDwtIGxpc3QoKQ0KICAgIA0KICAgICMgb24gY2FsY3VsZSBsZXMgY29lZmZpY2llbnQNCiAgICBmb3IgKHZhcmlhYmxlIGluIG5hbWVzKHZhcmlhYmxlc19xdWFudGl0YXRpdmVzKSkgew0KICAgICAgY29ycmVsYXRpb24gPC0gY29yLnRlc3QodmFyaWFibGVfYmluYWlyZSwgdmFyaWFibGVzX3F1YW50aXRhdGl2ZXNbW3ZhcmlhYmxlXV0sIG1ldGhvZCA9ICJwZWFyc29uIikNCiAgICAgIHJlc3VsdHNbW3ZhcmlhYmxlXV0gPC0gY29ycmVsYXRpb24kZXN0aW1hdGUNCiAgICB9DQogICAgDQojIHRyYW5zZm9ybWVyIGVuIGRhdGEgZnJhbWUNCnJlc3VsdHNfZGYgPC0gZGF0YS5mcmFtZSgNCiAgVmFyaWFibGUgPSBjKCJBZ2UiLCAiVm9sdW1lX2ludGVyY3JhbmllbiIsICJWb2x1bWVfY8OpcsOpYnJhbF90b3RhbCIsICJJbmRpY2VfYXRyb3BoaWUiKSwNCiAgQ29ycmVsYXRpb24gPSB1bmxpc3QocmVzdWx0cykNCikNCg0KICAgIGNvcmJpcyA8LSBnZ3Bsb3QocmVzdWx0c19kZiwgYWVzKHggPSBWYXJpYWJsZSwgeSA9IENvcnJlbGF0aW9uLCBmaWxsID0gQ29ycmVsYXRpb24pKSArDQogICAgICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgY29sb3IgPSAiYmxhY2siKSArDQogICAgICBsYWJzKHRpdGxlID0gIkNvZWZmaWNpZW50IGRlIGNvcnLDqWxhdGlvbiBwb2ludC1iaXPDqXJpYWxlIGF2ZWMgQ0RSIiwNCiAgICAgICAgICAgeCA9ICJWYXJpYWJsZSIsDQogICAgICAgICAgIHkgPSAiQ29lZmZpY2llbnQgZGUgY29ycsOpbGF0aW9uIikgKw0KICAgICAgeWxpbSgtMSwgMSkgKyAgIyBEw6lmaW5pciBsYSBsaW1pdGUgZGUgbCdheGUgeSBkZSAtMSDDoCAxDQogICAgICB0aGVtZV9taW5pbWFsKCkgKyAgIyBVdGlsaXNlciB1biB0aMOobWUgbWluaW1hbA0KICAgICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkgKyAgIyByb3RhdGlvbiBkZXMgw6l0aXF1ZXR0ZXMgZGUgbCdheGUgeA0KICAgICAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9saW5lKGNvbG9yID0gImdyYXkiLCBsaW5ldHlwZSA9ICJOVUxMIikpICsgICMgQWpvdXRlciB1bmUgZ3JpbGxlIGVuIHBvaW50aWxsw6lzDQogICAgICBzY2FsZV9maWxsX2dyYWRpZW50Mihsb3cgPSAiYmx1ZSIsIG1pZCA9ICJ3aGl0ZSIsIGhpZ2ggPSAicmVkIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICBtaWRwb2ludCA9IDAsIGxpbWl0cyA9IGMoLTEsIDEpLCBuYW1lID0gIkNvcnLDqWxhdGlvbiIpICAjIMOJY2hlbGxlIGRlIGNvdWxldXIgcGVyc29ubmFsaXPDqWUNCiAgICANCiAgIHN1cHByZXNzTWVzc2FnZXMoew0KICBjb3JiaXNfcGxvdGx5IDwtIGdncGxvdGx5KGNvcmJpcykNCn0pDQogICAgIyBhZmZpY2hhZ2UgZHUgZ3JhcGhpcXVlIFBsb3RseQ0KICAgICBzdXBwcmVzc01lc3NhZ2VzKGNvcmJpc19wbG90bHkpDQpgYGANCg0KIyMgQ29lZmZpY2llbnQgZGUgQ3JhbcOpciwgQ0RSICsgWA0KDQpMZSBjb2VmZmljaWVudCBkZSBDcmFtw6lyIG1lc3VyZSBsJ2Fzc29jaWF0aW9uIGVudHJlIGRldXggdmFyaWFibGVzIGNhdMOpZ29yaWVsbGVzLiBDYWxjdWzDqSDDoCBwYXJ0aXIgZGVzIHRhYmxlcyBkZSBjb250aW5nZW5jZSwgaWwgdmFyaWUgZGUgMCAoYXVjdW5lIGFzc29jaWF0aW9uKSDDoCAxIChhc3NvY2lhdGlvbiBwYXJmYWl0ZSkuIFVuZSB2YWxldXIgcHJvY2hlIGRlIDAgaW5kaXF1ZSB1bmUgZmFpYmxlIHJlbGF0aW9uLCB0YW5kaXMgcXVlIHByw6hzIGRlIDEgaW5kaXF1ZSB1bmUgZm9ydGUgYXNzb2NpYXRpb24uIElsIHNlcnQgw6Agw6l2YWx1ZXIgbGEgZMOpcGVuZGFuY2UgZW50cmUgbGVzIHZhcmlhYmxlcy4NCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0V9DQojIG9uIGNyw6llIGQnYWJvcmQgZGVzIHRhYmxlcyBkZSBjb250aW5nZW5jZSBwb3VyIGNoYXF1ZSBwYWlyZSBkZSB2YXJpYWJsZXMNCg0KdDEgPSB0YWJsZShkZW1lbmNlMiRzZXhlLCBkZW1lbmNlMiRDRFIpDQp0MiA9IHRhYmxlKGRlbWVuY2UyJE1NU0UsZGVtZW5jZTIkQ0RSKQ0KdDMgPSB0YWJsZShkZW1lbmNlMiRFRFVDLGRlbWVuY2UyJENEUikNCnQ0ID0gdGFibGUoZGVtZW5jZTIkU0VTLGRlbWVuY2UyJENEUikNCg0KIyBjYWxjdWwgZGVzIGxlcyBzdGF0aXN0aXF1ZXMgZCdhc3NvY2lhdGlvbiBwb3VyIGNoYXF1ZSB0YWJsZQ0KbWExID0gYXNzb2NzdGF0cyh0MSkNCm1hMiA9IGFzc29jc3RhdHModDIpDQptYTMgPSBhc3NvY3N0YXRzKHQzKQ0KbWE0ID0gYXNzb2NzdGF0cyh0NCkNCg0KZGF0YSA8LSBkYXRhLmZyYW1lKA0KICBWYXJpYWJsZSA9IGMoIlNleGUiLCAiTU1TRSIsICJFRFVDIiwgIlNFUyIpLA0KICBDb2VmZmljaWVudGRlQ3JhbWVyID0gYyhtYTEkY3JhbWVyLCBtYTIkY3JhbWVyLCBtYTMkY3JhbWVyLCBtYTQkY3JhbWVyKQ0KKQ0KQ3JhbWVyX1YgPSAgYyhtYTEkY3JhbWVyLCBtYTIkY3JhbWVyLCBtYTMkY3JhbWVyLCBtYTQkY3JhbWVyKQ0KDQojIGNyw6llciBsZSBncmFwaGlxdWUgw6AgYmFycmVzDQpHR0NyYW0gPSBnZ3Bsb3QoZGF0YSwgYWVzKHggPSBWYXJpYWJsZSwgeSA9IENvZWZmaWNpZW50ZGVDcmFtZXIsIGZpbGwgPSBDb2VmZmljaWVudGRlQ3JhbWVyKSkgKw0KICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgcG9zaXRpb24gPSAiZG9kZ2UiLCB3aWR0aCA9IDAuNywgY29sb3IgPSAiYmxhY2siKSArDQogIHNjYWxlX2ZpbGxfZ3JhZGllbnQyKGxvdyA9ICJkYXJrYmx1ZSIsIG1pZCA9ICJ3aGl0ZSIsIGhpZ2ggPSAicmVkIiwgDQogICAgICAgICAgICAgICAgICAgICAgIG1pZHBvaW50ID0gMC41LCBsaW1pdHMgPSBjKDAsIDEpLCANCiAgICAgICAgICAgICAgICAgICAgICAgbmFtZSA9ICJJbmRpY2UgZGUgQ3JhbcOpciIpICsNCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMC41LCBsaW5ldHlwZSA9ICJkYXNoZWQiLCBjb2xvciA9ICJibGFjayIpICsNCiAgbGFicyh0aXRsZSA9ICJDcmFtw6lyIGVuIGZvbmN0aW9uIGRlIENEUiIsDQogICAgICAgeCA9ICJWYXJpYWJsZXMiLA0KICAgICAgIHkgPSAiQ3JhbcOpcidzIFYiKSArDQogIHRoZW1lX2J3KCkNCg0KR0dDcmFtID0gR0dDcmFtICsgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkNCg0KR0dDcmFtICU+JSBwbG90bHk6OmdncGxvdGx5KCkNCmBgYA0KDQojIElJLiBDQVJUDQoNCiMjIEp1c3RpZmljYXRpb24NCg0KTCdhcmJyZSBkZSBjbGFzc2lmaWNhdGlvbiBldCBkZSByw6lncmVzc2lvbiAoQ0FSVCkgZXN0IHVuIG1vZMOobGUgZCdhcHByZW50aXNzYWdlIGF1dG9tYXRpcXVlIHJvYnVzdGUgcXVpIHNlZ21lbnRlIGRlIG1hbmnDqHJlIHLDqWN1cnNpdmUgbGVzIGRvbm7DqWVzIGVuIHNvdXMtZ3JvdXBlcywgZmFjaWxpdGFudCBhaW5zaSBsYSBwcmlzZSBkZSBkw6ljaXNpb25zLiBTb24gYXZhbnRhZ2UgcsOpc2lkZSBkYW5zIHNhIGZhY2lsaXTDqSBkZSBsZWN0dXJlIGV0IGQnaW50ZXJwcsOpdGF0aW9uLg0KDQoqKlBvdXIgY2hhY3VuKiogZGVzIG1vZMOobGVzIHByw6lkaWN0aWZzIGNyw6nDqXMgZGFucyBjZXR0ZSDDqXR1ZGUsICoqdW5lIHZhbGlkYXRpb24gY3JvaXPDqWUqKiBhIMOpdMOpIGVmZmVjdHXDqWUgZW4gZGl2aXNhbnQgbCfDqWNoYW50aWxsb24gZW4gdW4gZW5zZW1ibGUgZGUgdGVzdCAocmVncm91cGFudCAyMCUgZGVzIGRvbm7DqWVzKSBldCB1biBlbnNlbWJsZSBkJ2FwcHJlbnRpc3NhZ2UgKHJlZ3JvdXBhbnQgODAlIGRlcyBkb25uw6llcykuDQoNCkVuIHBsdXMgZGVzIG1vZMOobGVzIHRyYWRpdGlvbm5lbHMsICoqZGVzIG1vZMOobGVzIGRlIHR5cGUgZHVtbXkqKiBvbnQgw6l0w6kgbWlzIGVuIHBsYWNlIHBvdXIgc2VydmlyIGRlIHLDqWbDqXJlbmNlIGxvcnMgZGUgbCfDqXZhbHVhdGlvbiBkZXMgcGVyZm9ybWFuY2VzIGRlcyBtb2TDqGxlcyBwcsOpZGljdGlmcywgcGVybWV0dGFudCBhaW5zaSBkZSBjb21wYXJlciBsZXVyIGVmZmljYWNpdMOpLg0KDQpJbCBhIMOpZ2FsZW1lbnQgw6l0w6kgcHJpcyBlbiBjb21wdGUgZGVzIG3DqXRyaXF1ZXMgdGVsbGVzIHF1ZSAqKmxhIHByw6ljaXNpb24sIGxlIHJhcHBlbCwgbGEgc3DDqWNpZmljaXTDqSBldCBsJ0FVQyoqIChBcmVhIFVuZGVyIHRoZSBDdXJ2ZSkgcG91ciDDqXZhbHVlciBsYSBwZXJmb3JtYW5jZSBkZXMgbW9kw6hsZXMgcHLDqWRpY3RpZnMsIGZvdXJuaXNzYW50IGFpbnNpIHVuZSBhbmFseXNlIGNvbXBsw6h0ZSBkZSBsZXVyIGNhcGFjaXTDqSDDoCBjbGFzc2lmaWVyIGNvcnJlY3RlbWVudCBsZXMgZG9ubsOpZXMNCg0KRmluYWxlbWVudCwgdW5lIGJvdWNsZSBhIMOpdMOpIGNyw6nDqWUgcG91ciBhdXRvbWF0aXNlciBsZSBwcm9jZXNzdXMgZCfDqXZhbHVhdGlvbiBkZXMgbW9kw6hsZXMgc3VyIGRpZmbDqXJlbnRzIGVuc2VtYmxlcyBkZSBkb25uw6llcywgZ2FyYW50aXNzYW50IGFpbnNpIHVuZSBhbmFseXNlIGV4aGF1c3RpdmUgZXQgcmVwcm9kdWN0aWJsZSBkZSBsZXVyIHBlcmZvcm1hbmNlLiBDZXR0ZSBib3VjbGUsIHLDqXDDqXTDqWUgMTAwIGZvaXMsIHZpc2Ugw6Agw6l2YWx1ZXIgbGVzIG3DqXRyaXF1ZXMgZCfDqXZhbHVhdGlvbiB0ZWxsZXMgcXVlIGxhIHByw6ljaXNpb24sIGxlIHJhcHBlbCwgbGEgc3DDqWNpZmljaXTDqSBldCBsJ0FVQywgcGVybWV0dGFudCBhaW5zaSBkJ8OpbGltaW5lciBsZXMgdmFyaWF0aW9ucyBhbMOpYXRvaXJlcyBldCBkJ29idGVuaXIgZGVzIHLDqXN1bHRhdHMgcm9idXN0ZXMuDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQ0KICAgIG5ydW4gPC0gMTAwDQogICAgcmVzIDwtIGMoKQ0KICAgIHBlcmZvcm1hbmNlX21ldHJpY3MgPC0gbGlzdCgpDQogICAgDQogICAgIyBTdG9ja2VyIGxlcyBub21zIGRlcyBtw6l0cmlxdWVzDQogICAgbWV0cmljX25hbWVzIDwtIGMoIlByw6ljaXNpb24iLCAiUmFwcGVsIiwgIlNww6ljaWZpY2l0w6kiLCAiQVVDIikNCiAgICANCiAgICBmb3IgKGlydW4gaW4gMTpucnVuKXsNCiAgICAgIGRmIDwtIGRlbWVuY2UyW3NhbXBsZShucm93KGRlbWVuY2UyKSksIF0NCiAgICAgIHRyYWluIDwtIGRmWzE6MjgwLCBdDQogICAgICB0ZXN0IDwtIGRmWzI4MTozNTQsIF0NCiAgICAgIA0KICAgICAgdHJlZSA8LSBycGFydChDRFIgfiAuLCB0cmFpbikNCiAgICAgIA0KICAgICAgcHJlZHNfZHVtbXkgPC0gcmVwKG5hbWVzKHRhYmxlKHRyYWluJENEUikpW3doaWNoLm1heCh0YWJsZSh0cmFpbiRDRFIpKV0sIG5yb3codGVzdCkpDQogICAgICBwcmVkaWN0aW9ucyA8LSBwcmVkaWN0KHRyZWUsIHRlc3QsIHR5cGU9ImNsYXNzIikNCiAgICAgIA0KICAgICAgcmVzIDwtIHJiaW5kKHJlcywgYygNCiAgICAgICAgbWVhbihwcmVkc19kdW1teSA9PSB0ZXN0JENEUiksDQogICAgICAgIG1lYW4ocHJlZGljdGlvbnMgPT0gdGVzdCRDRFIpDQogICAgICApKQ0KICAgICAgDQogICAgICAjIGNhbGN1bCBkZXMgbcOpdHJpcXVlcyBkJ8OpdmFsdWF0aW9uDQogICAgICBjb25mX21hdHJpeCA8LSB0YWJsZShwcmVkaWN0aW9ucyA9IHByZWRpY3Rpb25zLCBhY3R1YWwgPSB0ZXN0JENEUikNCiAgICAgIA0KICAgICAgcHJlY2lzaW9uIDwtIGNvbmZ1c2lvbk1hdHJpeChwcmVkaWN0aW9ucywgdGVzdCRDRFIpJGJ5Q2xhc3NbIlBvcyBQcmVkIFZhbHVlIl0NCiAgICAgIHJhcHBlbCA8LSBjb25mdXNpb25NYXRyaXgocHJlZGljdGlvbnMsIHRlc3QkQ0RSKSRieUNsYXNzWyJTZW5zaXRpdml0eSJdDQogICAgICBzcGVjaWZpY2l0ZSA8LSBjb25mdXNpb25NYXRyaXgocHJlZGljdGlvbnMsIHRlc3QkQ0RSKSRieUNsYXNzWyJTcGVjaWZpY2l0eSJdDQogICAgICByb2NfY3VydmUgPC0gcm9jKHRlc3QkQ0RSLCBhcy5udW1lcmljKHByZWRpY3Rpb25zKSkNCiAgICAgIGF1Y192YWx1ZSA8LSBhdWMocm9jX2N1cnZlKQ0KDQogICAgICBwZXJmb3JtYW5jZV9tZXRyaWNzW1tpcnVuXV0gPC0gYyhwcmVjaXNpb24sIHJhcHBlbCwgc3BlY2lmaWNpdGUsIGF1Y192YWx1ZSkNCiAgICB9DQogICAgDQogICAgIyBvbiBjYWxjdWxlIGxhIG1veWVubmUgZGVzIG3DqXRyaXF1ZXMgcG91ciB0b3V0ZXMgbGVzIGl0w6lyYXRpb25zDQogICAgYXZlcmFnZV9tZXRyaWNzIDwtIGNvbE1lYW5zKGRvLmNhbGwocmJpbmQsIHBlcmZvcm1hbmNlX21ldHJpY3MpLCBuYS5ybSA9IFRSVUUpDQogICAgDQogICAgIyBjcsOpZXIgdW4gZGF0YSBmcmFtZSBhdmVjIGxlcyBub21zIGRlcyBtw6l0cmlxdWVzDQogICAgbWV0cmljX3N1bW1hcnkgPC0gZGF0YS5mcmFtZSgNCiAgICAgIE3DqXRyaXF1ZSA9IG1ldHJpY19uYW1lcywNCiAgICAgIE1veWVubmUgPSBhdmVyYWdlX21ldHJpY3MNCiAgICApDQogICAgDQogICAgY29sbmFtZXMocmVzKSA8LSBjKCJkdW1teSIsICJ0cmVlIikNCiAgICANCiAgICBtZWFucyA9IGNvbE1lYW5zKHJlcykNCiAgICANCiAgICBkZjIgPC0gZGF0YS5mcmFtZShNb2RlbCA9IG5hbWVzKG1lYW5zKSwgQWNjdXJhY3kgPSBtZWFucykNCiAgICANCiAgICANCiAgICAjIGNyw6llciBsZSBnZ3Bsb3QNCiAgICBDb21wYUNBUlQgPSAgIGdncGxvdChkZjIsIGFlcyh4ID0gTW9kZWwsIHkgPSBBY2N1cmFjeSwgZmlsbCA9IE1vZGVsKSkgKw0KICAgICAgZ2VvbV9jb2woKSArDQogICAgICBsYWJzKHRpdGxlID0gIkNvbXBhcmFpc29uIGVudHJlIER1bW15IGV0IFRyZWUiLA0KICAgICAgICAgICB4ID0gIk1vZMOobGUiLCB5ID0gIlByw6ljaXNpb24iKSArDQogICAgICB0aGVtZV9taW5pbWFsKCkNCiAgICANCiAgICBDb21wYUNBUlQgJT4lIHBsb3RseTo6Z2dwbG90bHkoKQ0KYGBgDQoNCkxlIGR1bW15IGEgb2J0ZW51IDAuNTgsIGNvbnRyZSAwLjczIHBvdXIgbm90cmUgbW9kw6hsZSwgbm91cyBwb3V2b25zIGRvbmMgY29udGludWVyIG5vdHJlIENBUlQgIQ0KDQojIyBSb2J1c3Rlc3NlIGR1IG1vZMOobGUNCg0KYGBge3J9DQogIyBjcsOpZXIgdW4gZ2dwbG90DQogICAgcmVjYXBDQVJUID0gZ2dwbG90KG1ldHJpY19zdW1tYXJ5LCBhZXMoeCA9IE3DqXRyaXF1ZSwgeSA9IE1veWVubmUsIGZpbGwgPSBNw6l0cmlxdWUpKSArDQogICAgICBnZW9tX2NvbCgpICsNCiAgICAgIGxhYnModGl0bGUgPSAiQ0FSVCAoMTAwIGl0w6lyYXRpb25zKSIsDQogICAgICAgICAgIHggPSAiIiwgeSA9ICJNb3llbm5lIikgKw0KICAgICAgICB0aGVtZV9taW5pbWFsKCkNCiAgICANCnJlY2FwQ0FSVCA8LSByZWNhcENBUlQgKyB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKQ0KDQogICAgcmVjYXBDQVJUICU+JSBwbG90bHk6OmdncGxvdGx5KCkNCmBgYA0KDQpOb3RyZSBtb2TDqGxlIGEgb2J0ZW51IGRlIHRyw6hzIGJvbm5lcyBtw6l0cmlxdWVzIGQnw6l2YWx1YXRpb25zLCBhdmVjIDAuODAgcG91ciBsJ0FVQywgMC44MyBwb3VyIGxhIHByw6ljaXNpb24sIDAuNzEgcG91ciBsZSByYXBwZWwgZXQgMC45MCBwb3VyIGxhIHNww6ljaWZpY2l0w6ksIGNlbGEgbW9udHJlIHF1ZWxlIG1vZMOobGUgYSB1bmUgY2FwYWNpdMOpIMOpbGV2w6llIMOgIGRpc2NyaW1pbmVyIGVudHJlIGxlcyBjbGFzc2VzIHBvc2l0aXZlcyBldCBuw6lnYXRpdmVzLCB0b3V0IGVuIG1haW50ZW5hbnQgdW4gYm9uIMOpcXVpbGlicmUgZW50cmUgbGEgcHLDqWNpc2lvbiBldCBsZSByYXBwZWwsIGV0IGVuIG1pbmltaXNhbnQgw6AgbGEgZm9pcyBsZXMgZmF1eCBwb3NpdGlmcyBldCBsZXMgZmF1eCBuw6lnYXRpZnMuDQoNCiMjIEltcG9ydGFuY2UgZGVzIHZhcmlhYmxlcw0KDQpPbiBwZXV0IHJlZ2FyZGVyIGwnaW1wb3J0YW5jZSBkZXMgdmFyaWFibGVzIGRhbnMgbm90cmUgbW9kw6hsZXMgDQoNCmBgYHtyfQ0KICAgIHRyZWVfZ2xvYmFsIDwtIHJwYXJ0KENEUiB+IC4sIGRmKQ0KIyBMZXMgaW1wb3J0YW5jZXMgZGVzIHZhcmlhYmxlcw0KaW1wb3J0YW5jZXMgPC0gYyhBZ2UgPSAxNC40NDg3MywNCiAgICAgICAgICAgICAgICAgRURVQyA9IDMzLjg1Njg3LA0KICAgICAgICAgICAgICAgICBJbmRpY2VfYXRyb3BoaWUgPSAxNi42Njg4NiwNCiAgICAgICAgICAgICAgICAgTU1TRSA9IDM3LjYwODA2LA0KICAgICAgICAgICAgICAgICBTRVMgPSAzNC4wNzE2MiwNCiAgICAgICAgICAgICAgICAgc2V4ZSA9IDIyLjE2MTkzLA0KICAgICAgICAgICAgICAgICBWb2x1bWVfaW50ZXJjcmFuaWVuID0gMjIuNjkxNzYsDQogICAgICAgICAgICAgICAgIFZvbHVtZV9jw6lyw6licmFsX3RvdGFsID0gNTIuOTY1MjMpDQoNCiMgQ3LDqWVyIHVuIGRhdGEgZnJhbWUgcG91ciBsZXMgaW1wb3J0YW5jZXMNCmltcG9ydGFuY2VzX2RmIDwtIGRhdGEuZnJhbWUoVmFyaWFibGVzID0gbmFtZXMoaW1wb3J0YW5jZXMpLCBJbXBvcnRhbmNlID0gaW1wb3J0YW5jZXMpDQoNCiMgQ3LDqWVyIGxlIGdncGxvdA0KZ2dwbG90KGltcG9ydGFuY2VzX2RmLCBhZXMoeCA9IFZhcmlhYmxlcywgeSA9IEltcG9ydGFuY2UpKSArDQogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCBmaWxsID0gInNreWJsdWUiKSArDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpICsNCiAgbGFicyh0aXRsZSA9ICJJbXBvcnRhbmNlIGRlcyB2YXJpYWJsZXMiKQ0KYGBgDQoNCiMjIFZpc3VhbGlzYXRpb24gZGUgbCdhcmJyZSBkZSBkw6ljaXNpb24NCg0KYGBge3IgZmlnLmhlaWdodD0xMCwgZmlnLndpZHRoPTE0fQ0KcnBhcnQucGxvdCh0cmVlX2dsb2JhbCkNCmBgYA0KSW50ZXJwcsOpdGF0aW9uIGRlIGwnYXJibGUgOiANCg0KMS4gKipNTVNFKio6IExlIHByZW1pZXIgbsWTdWQgZGl2aXNlIGxlcyBpbmRpdmlkdXMgZW4gZm9uY3Rpb24gZGUgbGV1ciBzY29yZSBhdSBNaW5pLU1lbnRhbCBTdGF0ZSBFeGFtaW5hdGlvbiAoTU1TRSkuIFVuIHNjb3JlIGluZsOpcmlldXIgw6AgbGEgbGltaXRlIGluZGlxdcOpZSAobm9uIHNww6ljaWZpw6llIGljaSkgbcOobmUgZGlyZWN0ZW1lbnQgw6AgdW4gZGlhZ25vc3RpYyBkZSBkw6ltZW5jZSAocHJvYmFiaWxpdMOpIGRlIDEsMDAgb3UgMTAwJSkuDQoNCjIuIFNpIGxlIHNjb3JlIE1NU0UgZXN0IHN1cMOpcmlldXIgw6AgbGEgbGltaXRlLCBsJ2FyYnJlIHNlIGRpdmlzZSBlbiBkZXV4IGJyYW5jaGVzLCB1bmUgcG91ciBsZXMgaG9tbWVzIGV0IHVuZSBwb3VyIGxlcyBmZW1tZXMgKGTDqW5vdMOpIHBhciAic2V4ZSA9IE0iKS4NCg0KMy4gKipQb3VyIGxlcyBGZW1tZXMqKjoNCiAgICAtIExlIG7Fk3VkIHN1aXZhbnQgcmVnYXJkZSBsZSAnVm9sdW1lX2ludHJhY3LDom5pZW4nLiBTaSBjJ2VzdCBtb2lucyBkZSAxNjA5LCBpbCBzZSBkaXZpc2UgZW5jb3JlIHNlbG9uIGwnw6JnZS4NCiAgICAgICAgLSBTaSBsJ8OiZ2UgZXN0ID49IDc5LCBsYSBwcm9iYWJpbGl0w6kgZGUgZMOpbWVuY2UgYXVnbWVudGUgKDAsMzcgb3UgMzclKS4NCiAgICAgICAgLSBQb3VyIGNldXggYXZlYyB1biAnVm9sdW1lX2ludHJhY3LDom5pZW4nID49IDE2MDksIGxhIGRpdmlzaW9uIHNlIGZhaXQgc2Vsb24gbGUgJ1NvY2lvZWNvbm9taWMgU3RhdHVzJyAoU0VTKS4NCiAgICAgICAgICAgIC0gVW4gU0VTIDw9IDEsNCBjb25kdWl0IMOgIHVuZSBkaXZpc2lvbiBzdWl2YW50ZSBiYXPDqWUgc3VyIGxlIG5pdmVhdSBkJ8OpZHVjYXRpb24gKEVEVUMpLg0KICAgICAgICAgICAgICAgIC0gUG91ciB1biBFRFVDIGVudHJlIDE0IGV0IDE2LCBsYSBwcm9iYWJpbGl0w6kgZGUgZMOpbWVuY2UgZXN0IHBsdXMgYmFzc2UgKDAsMTAgb3UgMTAlKS4NCg0KNC4gKipQb3VyIGxlcyBIb21tZXMqKjoNCiAgICAtIFNpIGxlICdWb2x1bWVfaW50cmFjcsOibmllbicgZXN0IG1vaW5zIGRlIDE0ODYsIGNlbGEgYW3DqG5lIMOgIHVuZSBhdXRyZSBkaXZpc2lvbiBlbiBmb25jdGlvbiBkdSAnVm9sdW1lX2PDqXLDqWJyYWxfdG90YWwnLg0KICAgICAgICAtIFNpIGxlICdWb2x1bWVfY8OpcsOpYnJhbF90b3RhbCcgZXN0IDwgMCw3LCBhbG9ycyBsYSBwcm9iYWJpbGl0w6kgZGUgZMOpbWVuY2UgZXN0IGRlIDAsNDYgKDQ2JSkuDQogICAgICAgIC0gU2kgbGUgJ1ZvbHVtZV9jw6lyw6licmFsX3RvdGFsJyBlc3QgPj0gMCw3LCBldCBsJ8OiZ2UgPCA3NywgbGEgcHJvYmFiaWxpdMOpIGRlIGTDqW1lbmNlIGVzdCBwbHVzIGZhaWJsZSAoMCwxOSBvdSAxOSUpLg0KICAgICAgICAtIFBvdXIgdW4gJ1ZvbHVtZV9jw6lyw6licmFsX3RvdGFsJyA+PSAwLDc3IGV0IHVuIFNFUyBlbnRyZSAzIGV0IDQsNSwgbGVzIHByb2JhYmlsaXTDqXMgZGUgZMOpbWVuY2UgdmFyaWVudCBkZSAwLDE2ICgxNiUpIMOgIDAsMzkgKDM5JSkgc2Vsb24gbCfDomdlIGV0IGxlIFNFUy4NCg0KDQojIElJSS4gUmFuZG9tIEZvcmVzdA0KDQojIyBKdXN0aWZpY2F0aW9uDQoNCkxhIGZvcsOqdCBhbMOpYXRvaXJlIChSYW5kb20gRm9yZXN0KSBlc3QgdW5lIHRlY2huaXF1ZSBwdWlzc2FudGUgZW4gYXBwcmVudGlzc2FnZSBhdXRvbWF0aXF1ZS4gRWxsZSBjb25zdHJ1aXQgcGx1c2lldXJzIGFyYnJlcyBkZSBkw6ljaXNpb24gbG9ycyBkZSBsJ2VudHJhw65uZW1lbnQgZXQgbGVzIGNvbWJpbmUgcG91ciBvYnRlbmlyIGRlcyBwcsOpZGljdGlvbnMgcGx1cyByb2J1c3RlcyBldCBwcsOpY2lzZXMuIENldHRlIG3DqXRob2RlIHNlZ21lbnRlIGxlcyBkb25uw6llcyBkZSBtYW5pw6hyZSByw6ljdXJzaXZlIGNvbW1lIGwnYXJicmUgZGUgY2xhc3NpZmljYXRpb24gZXQgZGUgcsOpZ3Jlc3Npb24gKENBUlQpLCBtYWlzIGF1IGxpZXUgZGUgcydhcHB1eWVyIHN1ciB1biBzZXVsIGFyYnJlLCBlbGxlIGFzc2VtYmxlIGxlcyByw6lzdWx0YXRzIGRlIG11bHRpcGxlcyBhcmJyZXMgcG91ciBwcm9kdWlyZSB1bmUgcHLDqWRpY3Rpb24gZmluYWxlLg0KDQpOb3VzIGF2b25zIHN1aXZpIHVuZSBkw6ltYXJjaGUgc2ltaWxhaXJlIGF1IENBUlQuICoqVmFsaWRhdGlvbiBjcm9pc8OpZSoqIGF2ZWMgZGl2aXNpb24gZW4gZW5zZW1ibGVzIGRlIHRlc3QgKDIwJSkgZXQgZCdhcHByZW50aXNzYWdlICg4MCUpLiBEZXMgbW9kw6hsZXMgZGUgdHlwZSAqKmR1bW15Kiogb250IMOpdMOpIHV0aWxpc8OpcyBjb21tZSByw6lmw6lyZW5jZS4gTGVzIHBlcmZvcm1hbmNlcyBvbnQgw6l0w6kgbWVzdXLDqWVzIHBhciAqKnByw6ljaXNpb24sIHJhcHBlbCwgc3DDqWNpZmljaXTDqSBldCBBVUMqKi4gKipVbmUgYm91Y2xlKiogYSDDqXTDqSBtaXNlIGVuIHBsYWNlLCByw6lww6l0w6llIDEwMCBmb2lzIHBvdXIgZGVzIHLDqXN1bHRhdHMgcm9idXN0ZXMuDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQ0KDQpucnVuIDwtIDEwMA0KcmVzIDwtIGMoKQ0KcGVyZm9ybWFuY2VfbWV0cmljcyA8LSBsaXN0KCkNCg0KIyBTdG9ja2VyIGxlcyBub21zIGRlcyBtw6l0cmlxdWVzDQptZXRyaWNfbmFtZXMgPC0gYygiUHLDqWNpc2lvbiIsICJSYXBwZWwiLCAiU3DDqWNpZmljaXTDqSIsICJBVUMiKQ0KDQpmb3IgKGlydW4gaW4gMTpucnVuKXsNCiAgZGZmIDwtIGRlbWVuY2UyW3NhbXBsZShucm93KGRlbWVuY2UyKSksIF0NCiAgdHJhaW4gPC0gZGZmWzE6MjgwLCBdDQogIHRlc3QgPC0gZGZmWzI4MTozNTQsIF0NCiAgDQogICMgVXRpbGlzZXIgdW4gbW9kw6hsZSBSYW5kb20gRm9yZXN0IGF1IGxpZXUgZGUgQ0FSVA0KICBmb3Jlc3QgPC0gcmFuZG9tRm9yZXN0KENEUiB+IC4sIGRhdGEgPSB0cmFpbikNCiAgDQogIHByZWRzX2R1bW15IDwtIHJlcChuYW1lcyh0YWJsZSh0cmFpbiRDRFIpKVt3aGljaC5tYXgodGFibGUodHJhaW4kQ0RSKSldLCBucm93KHRlc3QpKQ0KICBwcmVkaWN0aW9ucyA8LSBwcmVkaWN0KGZvcmVzdCwgdGVzdCkNCiAgDQogIHJlcyA8LSByYmluZChyZXMsIGMoDQogICAgbWVhbihwcmVkc19kdW1teSA9PSB0ZXN0JENEUiksDQogICAgbWVhbihwcmVkaWN0aW9ucyA9PSB0ZXN0JENEUikNCiAgKSkNCiAgDQogICMgQ2FsY3VsIGRlcyBtw6l0cmlxdWVzIGQnw6l2YWx1YXRpb24NCiAgY29uZl9tYXRyaXggPC0gdGFibGUocHJlZGljdGlvbnMgPSBwcmVkaWN0aW9ucywgYWN0dWFsID0gdGVzdCRDRFIpDQogIA0KICBwcmVjaXNpb24gPC0gY29uZnVzaW9uTWF0cml4KHByZWRpY3Rpb25zLCB0ZXN0JENEUikkYnlDbGFzc1siUG9zIFByZWQgVmFsdWUiXQ0KICByYXBwZWwgPC0gY29uZnVzaW9uTWF0cml4KHByZWRpY3Rpb25zLCB0ZXN0JENEUikkYnlDbGFzc1siU2Vuc2l0aXZpdHkiXQ0KICBzcGVjaWZpY2l0ZSA8LSBjb25mdXNpb25NYXRyaXgocHJlZGljdGlvbnMsIHRlc3QkQ0RSKSRieUNsYXNzWyJTcGVjaWZpY2l0eSJdDQogIHJvY19jdXJ2ZSA8LSByb2ModGVzdCRDRFIsIGFzLm51bWVyaWMocHJlZGljdGlvbnMpKQ0KICBhdWNfdmFsdWUgPC0gYXVjKHJvY19jdXJ2ZSkNCiAgDQogICMgU3RvY2tlciBsZXMgbcOpdHJpcXVlcyBwb3VyIGNoYXF1ZSBpdMOpcmF0aW9uDQogIHBlcmZvcm1hbmNlX21ldHJpY3NbW2lydW5dXSA8LSBjKHByZWNpc2lvbiwgcmFwcGVsLCBzcGVjaWZpY2l0ZSwgYXVjX3ZhbHVlKX0NCmBgYA0KDQojIyBSb2J1c3Rlc3NlIGR1IG1vZMOobGUNCg0KYGBge3J9DQpjb2xuYW1lcyhyZXMpIDwtIGMoImR1bW15IiwgImZvcmVzdCIpDQoNCg0KbWVhbnMgPSBjb2xNZWFucyhyZXMpDQoNCmRmMiA8LSBkYXRhLmZyYW1lKE1vZGVsID0gbmFtZXMobWVhbnMpLCBBY2N1cmFjeSA9IG1lYW5zKQ0KDQojIENyw6llciBsZSBnZ3Bsb3QNCkNvbXBhUkYgPC0gZ2dwbG90KGRmMiwgYWVzKHggPSBNb2RlbCwgeSA9IEFjY3VyYWN5LCBmaWxsID0gTW9kZWwpKSArDQogIGdlb21fY29sKCkgKw0KICBsYWJzKHRpdGxlID0gIkNvbXBhcmFpc29uIGVudHJlIER1bW15IGV0IFJhbmRvbSBGb3Jlc3QiLA0KICAgICAgIHggPSAiTW9kw6hsZSIsIHkgPSAiUHLDqWNpc2lvbiIpICsNCiAgdGhlbWVfbWluaW1hbCgpDQoNCkNvbXBhUkYgJT4lIHBsb3RseTo6Z2dwbG90bHkoKQ0KYGBgDQoNCkxlIGR1bW15IGEgb2J0ZW51IDAuNTgsIGNvbnRyZSAwLjgyIHBvdXIgbm90cmUgbW9kw6hsZSwgbm91cyBwb3V2b25zIGRvbmMgY29udGludWVyIG5vdHJlIGFuYWx5c2UgIQ0KDQpgYGB7cn0NCmF2ZXJhZ2VfbWV0cmljcyA8LSBjb2xNZWFucyhkby5jYWxsKHJiaW5kLCBwZXJmb3JtYW5jZV9tZXRyaWNzKSwgbmEucm0gPSBUUlVFKQ0KDQojIENyw6llciB1biBkYXRhIGZyYW1lIGF2ZWMgbGVzIG5vbXMgZGVzIG3DqXRyaXF1ZXMNCm1ldHJpY19zdW1tYXJ5IDwtIGRhdGEuZnJhbWUoDQogIE3DqXRyaXF1ZSA9IG1ldHJpY19uYW1lcywNCiAgTW95ZW5uZSA9IGF2ZXJhZ2VfbWV0cmljcw0KKQ0KDQojIENyw6llciB1biBnZ3Bsb3QNCnJlY2FwUkYgPC0gZ2dwbG90KG1ldHJpY19zdW1tYXJ5LCBhZXMoeCA9IE3DqXRyaXF1ZSwgeSA9IE1veWVubmUsIGZpbGwgPSBNw6l0cmlxdWUpKSArDQogIGdlb21fY29sKCkgKw0KICBsYWJzKHRpdGxlID0gIlJhbmRvbSBGb3Jlc3QgKDEwMCBpdMOpcmF0aW9ucykiLA0KICAgICAgIHggPSAiIiwgeSA9ICJNb3llbm5lIikgKw0KICAgICB0aGVtZV9taW5pbWFsKCkNCg0KcmVjYXBSRiA8LSByZWNhcFJGICsgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkNCg0KcmVjYXBSRiAlPiUgcGxvdGx5OjpnZ3Bsb3RseSgpDQpgYGANCg0KTm90cmUgbW9kw6hsZSBhIG9idGVudSBkZSB0csOocyBib25uZXMgbcOpdHJpcXVlcyBkJ8OpdmFsdWF0aW9ucywgYXZlYyAwLjgwIHBvdXIgbCdBVUMsIDAuODMgcG91ciBsYSBwcsOpY2lzaW9uLCAwLjcxIHBvdXIgbGUgcmFwcGVsIGV0IDAuOTAgcG91ciBsYSBzcMOpY2lmaWNpdMOpLCBjZWxhIG1vbnRyZSBxdWVsZSBtb2TDqGxlIGEgdW5lIGNhcGFjaXTDqSDDqWxldsOpZSDDoCBkaXNjcmltaW5lciBlbnRyZSBsZXMgY2xhc3NlcyBwb3NpdGl2ZXMgZXQgbsOpZ2F0aXZlcywgdG91dCBlbiBtYWludGVuYW50IHVuIGJvbiDDqXF1aWxpYnJlIGVudHJlIGxhIHByw6ljaXNpb24gZXQgbGUgcmFwcGVsLCBldCBlbiBtaW5pbWlzYW50IMOgIGxhIGZvaXMgbGVzIGZhdXggcG9zaXRpZnMgZXQgbGVzIGZhdXggbsOpZ2F0aWZzLg0KDQojIyBPcHRpbWlzYXRpb24gZHUgbW9kw6hsZQ0KDQpPbiBwZXV0IGVzc2F5ZXIgZCdvcHRpbWlzZXIgbm90cmUgbW9kw6hsZSBwb3VyIHF1J2lsIGFpdCBkJ2VuY29yZSBtZWlsbGV1ciByw6lzdWx0YXQgOg0KDQpgYGB7cn0NCmZvcmVzdF9nbG9iYWwgPC0gcmFuZG9tRm9yZXN0KENEUiB+IC4sIGRhdGEgPSBkZmYsIGltcG9ydGFuY2UgPSBUUlVFLCBudHJlZSA9IDUwMDApDQoNCg0KcGxvdChmb3Jlc3RfZ2xvYmFsJGVyci5yYXRlWywgMV0sIHR5cGUgPSAibCIsIHhsYWIgPSAibm9tYnJlIGQnYXJicmVzIiwgeWxhYiA9ICJlcnJldXIgT09CIikNCg0KYGBgDQoNCkljaSwgb24gdm9pdCBsZSBuaXZlYXUgZCdlcnJldXIgZW4gb3Jkb25uw6llLCBwb3VyIGxlIG5vbWJyZSBkJ2FyYnJlcyBkb25uw6lzIGVuIGFic2Npc3NlLiBPbiBwZXV0IHJlbWFycXVlciBxdWUgbGUgbml2ZWF1IGQnZXJyZXVyIHNlIHN0YWJpbGlzZSBhdXRvdXIgZGUgMTAwMCBhcmJyZXMuIE5vdXMgYWxsb25zIGRvbmMgY2hvaXNpciBjZSBjaGlmZnJlIHBvdXIgbm90cmUgbW9kw6hsZS4NCg0KYGBge3J9DQoNCm50cmVlIDwtIDEwMDANCg0KYGBgDQoNCmBgYHtyfQ0KIyBzZWxlY3Rpb24gZHUgbXRyeSBvcHRpbWFsDQoNCnR1bmVkX3JmIDwtIHR1bmVSRihkZmZbLCAtd2hpY2gobmFtZXMoZGZmKSA9PSAiQ0RSIildLCBkZmYkQ0RSLCBudHJlZSA9IG50cmVlKQ0KDQpwbG90KHR1bmVkX3JmKQ0KYGBgDQoNCkxlIHBhcmFtw6h0cmUgIm10cnkiIGNvbnRyw7RsZSBsZSBub21icmUgZGUgdmFyaWFibGVzIMOgIGNvbnNpZMOpcmVyIMOgIGNoYXF1ZSBkaXZpc2lvbiBkJ3VuIG7Fk3VkIGRlIGwnYXJicmUgZGUgZMOpY2lzaW9uLiBTdXIgY2UgZ3JhcGhpcXVlLCBvbiBhIGVuIG9yZG9ubsOpZSBsZSBuaXZlYXUgZCdlcnJldXIgZXQgZW4gYWJzY2lzc2UgbGUgbXRyeS4gT24gdm9pdCBxdWUgbGUgbXRyeSBvcHRpbWFsIGVzdCBkZSA0LCBjYXIgYydlc3QgY2VsdWkgcXVpIGEgbGUgbml2ZWF1IGQnZXJyZXVyIGxlIHBsdXMgYmFzLiANCg0KT24gcGV1dCBkb25jIHJlZmFpcmUgbm90cmUgbW9kw6hsZSBlbiBwcmVuYW50IGVuIGNvbXB0ZSBjZXMgZGV1eCBwYXJhbcOodHJlcy4NCg0KIyMgSW1wb3J0YW5jZSBkZXMgdmFyaWFibGVzDQoNCmBgYHtyfQ0KIyBvbiBwZXV0IG1haW50ZW5hbnQgc3DDqWNpZmllciBub3MgcGFyYW3DqXRyZXMNCm50cmVlIDwtIDEwMDANCm10cnkgPC0gNA0KDQojIG9uIGNyw6llIGxlIG1vZMOobGUgYXZlYyBsZXMgcGFyYW3DqHRyZXMgc3DDqWNpZmnDqXMNCmZvcmVzdF9nbG9iYWwgPC0gcmFuZG9tRm9yZXN0KENEUiB+IC4sIGRhdGEgPSBkZmYsIGltcG9ydGFuY2UgPSBUUlVFLCBudHJlZSA9IG50cmVlLCBtdHJ5ID0gbXRyeSkNCg0KZGF0YSA8LSBkYXRhLmZyYW1lKA0KICBWYXJpYWJsZSA9IGMoIlNleGUiLCAiw4JnZSIsICLDiWR1Y2F0aW9uIiwgIlNFUyIsICJNTVNFIiwgIlZvbHVtZV9pbnRlcmNyYW5pZW4iLCAiVm9sdW1lX2PDqXLDqWJyYWxfdG90YWwiLCAiSW5kaWNlIGF0cm9waGllIiksDQogIE1lYW5EZWNyZWFzZUFjY3VyYWN5ID0gYygwLjA0NDUxMjcxLCAwLjAzMjU2NTQ5LCAwLjAzNDU4ODA3LCAwLjAzODQ0NDQ4LCAwLjA4OTUzMDk3LCAwLjA0Nzg1NDE4LCAwLjA3ODc2NzEwLCAwLjA1MDY2NjExKSwNCiAgTWVhbkRlY3JlYXNlR2luaSA9IGMoOC4zNDE2NTEsIDIyLjc2NDc4NSwgMTIuNTU5NjYwLCAxMi44NzA2MTMsIDMwLjI5ODU5OCwgMjQuNDEwODIyLCAzNS4yNzUxNjAsIDI1LjAwNzU1MykNCikNCg0KIyBHcmFwaGlxdWUgcG91ciBNZWFuRGVjcmVhc2VBY2N1cmFjeQ0KcGxvdF9hY2N1cmFjeSA8LSBnZ3Bsb3QoZGF0YSwgYWVzKHggPSByZW9yZGVyKFZhcmlhYmxlLCBNZWFuRGVjcmVhc2VBY2N1cmFjeSksIHkgPSBNZWFuRGVjcmVhc2VBY2N1cmFjeSkpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIGZpbGwgPSAic2t5Ymx1ZSIpICsNCiAgbGFicyh4ID0gIlZhcmlhYmxlIiwgeSA9ICJNZWFuIERlY3JlYXNlIEFjY3VyYWN5IiwgdGl0bGUgPSAiTWVhbiBEZWNyZWFzZSBBY2N1cmFjeSIpICsNCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkNCg0KIyBHcmFwaGlxdWUgcG91ciBNZWFuRGVjcmVhc2VHaW5pDQpwbG90X2dpbmkgPC0gZ2dwbG90KGRhdGEsIGFlcyh4ID0gcmVvcmRlcihWYXJpYWJsZSwgTWVhbkRlY3JlYXNlR2luaSksIHkgPSBNZWFuRGVjcmVhc2VHaW5pKSkgKw0KICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgZmlsbCA9ICJzYWxtb24iKSArDQogIGxhYnMoeCA9ICJWYXJpYWJsZSIsIHkgPSAiR2luaSIsIHRpdGxlID0gIkdpbmkiKSArDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpDQpgYGANCg0KYGBge3IsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD02fQ0KZ3JpZC5hcnJhbmdlKHBsb3RfYWNjdXJhY3ksIHBsb3RfZ2luaSwgbmNvbCA9IDIpDQpgYGANCg0KRW4gUmFuZG9tIEZvcmVzdCwgKioiTWVhbiBEZWNyZWFzZSBBY2N1cmFjeSIqKiAoTURBKSDDqXZhbHVlIGNvbW1lbnQgbCdleGFjdGl0dWRlIGR1IG1vZMOobGUgZGltaW51ZSBsb3JzcXVlIGxlcyB2YWxldXJzIGQndW5lIHZhcmlhYmxlIHNvbnQgcGVybXV0w6llcywgbWVzdXJhbnQgYWluc2kgc29uIGltcG9ydGFuY2UgcG91ciBsYSBwcsOpZGljdGlvbi4gDQoNCkVuIHJldmFuY2hlLCAqKiJHaW5pIioqIG1lc3VyZSBjb21tZW50IHVuZSB2YXJpYWJsZSByw6lkdWl0IGwnaW1wdXJldMOpIGRlcyBuxZN1ZHMgbG9yc3F1J2VsbGUgZXN0IHV0aWxpc8OpZSBwb3VyIGRpdmlzZXIgbGVzIGRvbm7DqWVzLCDDqXZhbHVhbnQgYWluc2kgc29uIGVmZmljYWNpdMOpIMOgIHPDqXBhcmVyIGxlcyBjbGFzc2VzLiBCaWVuIHF1ZSBsZXMgZGV1eCBtZXN1cmVzIMOpdmFsdWVudCBsJ2ltcG9ydGFuY2UgZGVzIHZhcmlhYmxlcywgZWxsZXMgbGUgZm9udCDDoCB0cmF2ZXJzIGRlcyBwZXJzcGVjdGl2ZXMgZGlmZsOpcmVudGVzLCBlbGxlcyBzb250IGNvbXBsw6ltZW50YWlyZXMgZXQgcGVybWV0dGVudCBkZSBzb2xpZGlmaWVyIGxlcyByw6lzdWx0YXRzLiANCg0KSWNpLCBvbiBwZXV0IHZvaXIgcXVlLCBwb3VyICoqbGEgTURBKiosIGwnw6JnZSBlc3QgbGEgdmFyaWFibGUgbGEgbW9pbnMgaW1wb3J0YW50ZSBkZSBub3RyZSBtb2TDqGxlLCBvbiBwb3VycmFpdCBmYWlyZSBkZXV4IGdyb3VwZXMsIGF2ZWMgOg0KDQotIEFnZSwgRWR1Y2F0aW9uLCBTRVMsIFNleGUsIFZvbHVtZSBpbnRlcmNyw6JuaWVuLCBJbmRpY2UgZCdhdHJvcGhpZSwgcXVpIG9udCBjaGFjdW4gZXUgZGVzIHLDqXN1bHRhdHMgYXNzZXogaG9tb2fDqG5lcy4NCi0gVm9sdW1lIGPDqXLDqWJyYWwgdG90YWwgZXQgTU1TRSwgcXVpIHN1cnZvbGUgcGx1cyBub3RyZSBtb2TDqGxlLg0KDQpQb3VyICoqR2luaSoqLCBvbiBhIHBsdXTDtHQgdHJvaXMgZ3JvdXBlcywgYXZlYyA6DQoNCi0gU2V4ZSwgRWR1Y2F0aW9uLCBTRVMNCi0gQWdlLCBWb2x1bWUgaW50ZXJjcsOibmllbiBldCBpbmRpY2UgZCdhdHJvcGhpZQ0KLSBNTVNFIGV0IFZvbHVtZSBjw6lyw6licmFsIHRvdGFsLCBxdWkgc29udCB1bmUgbm91dmVsbGUgZm9pcyBhdSBkZXNzdXMgZW4gdGVybWUgZCdpbXBvcnRhbmNlDQoNCk1haW50ZW5hbnQgcXVlIG5vdXMgYXZvbnMgdW5lIGlkw6llIGRlIGwnaW1wb3J0YW5jZSBkZSBub3MgdmFyaWFibGVzLCBub3VzIHBvdXZvbnMgZmFpcmUgdW5lIHLDqWdyZXNzaW9uIGxvZ2lzdGlxdWUsIHF1aSBwb3VycmFpdCBwZXJtZXR0cmUgdW5lIGFuYWx5c2UgcGx1cyBkw6l0YWlsbMOpZS4NCg0KIyBJVi4gUsOpZ3Jlc3Npb24gbG9naXN0aXF1ZSBiaW5haXJlDQoNCiMjIEp1c3RpZmljYXRpb24NCg0KTGEgcsOpZ3Jlc3Npb24gbG9naXN0aXF1ZSBiaW5haXJlIGVzdCB1bmUgZXh0ZW5zaW9uIGR1IG1vZMOobGUgbGluw6lhaXJlIGFkYXB0w6llIGF1eCBjYXMgb8O5IGxhIHZhcmlhYmxlIGTDqXBlbmRhbnRlIGVzdCBiaW5haXJlLiBFbGxlIG1vZMOpbGlzZSBsYSByZWxhdGlvbiBlbnRyZSBsZXMgdmFyaWFibGVzIGluZMOpcGVuZGFudGVzIGV0IGxhIHByb2JhYmlsaXTDqSBkJ29jY3VycmVuY2UgZGUgbCfDqXbDqW5lbWVudCBiaW5haXJlLCBwZXJtZXR0YW50IGQnZXN0aW1lciBsZXMgcHJvYmFiaWxpdMOpcyBkZSBzdWNjw6hzIHBvdXIgY2hhcXVlIG5pdmVhdSBkZXMgdmFyaWFibGVzIGluZMOpcGVuZGFudGVzLiBFbiB1dGlsaXNhbnQgY2V0dGUgbcOpdGhvZGUsIG9uIHBldXQgY2FsY3VsZXIgbGVzIHAtdmFsdWVzIHBvdXIgw6l2YWx1ZXIgbGEgc2lnbmlmaWNhdGl2aXTDqSBkZXMgZWZmZXRzIGRlcyB2YXJpYWJsZXMgaW5kw6lwZW5kYW50ZXMgc3VyIGxhIHZhcmlhYmxlIGTDqXBlbmRhbnRlLg0KDQpOb3VzIGF2b25zIGFkb3B0w6kgdW5lIGFwcHJvY2hlIHNlbWJsYWJsZSBhdXggbcOpdGhvZGVzIHByw6ljw6lkZW50ZXMsIGF2ZWMgdW5lICoqdmFsaWRhdGlvbiBjcm9pc8OpZSoqIGVmZmVjdHXDqWUgZW4gZGl2aXNhbnQgbGVzIGVuc2VtYmxlcyBlbiB0ZXN0ICgyMCUpIGV0IGVuIGFwcHJlbnRpc3NhZ2UgKDgwJSkuIERlcyBtb2TDqGxlcyBkZSB0eXBlICoqZHVtbXkqKiBvbnQgc2VydmkgZGUgcsOpZsOpcmVuY2UuIExlcyBwZXJmb3JtYW5jZXMgb250IMOpdMOpIMOpdmFsdcOpZXMgZW4gdGVybWVzIGRlICoqcHLDqWNpc2lvbiwgZGUgcmFwcGVsLCBkZSBzcMOpY2lmaWNpdMOpIGV0IGQnQVVDKiouIFVuZSAqKmJvdWNsZSByw6lww6l0aXRpdmUsIHLDqWl0w6lyw6llIDEwMCBmb2lzKiosIGEgw6l0w6kgaW5zdGF1csOpZSBwb3VyIGdhcmFudGlyIGRlcyByw6lzdWx0YXRzIHJvYnVzdGVzLg0KDQpgYGB7cn0NCmRlbWVuY2UyJENEUl9iaW5haXJlIDwtIGlmZWxzZShkZW1lbmNlMiRDRFIgPT0gIkTDqW1lbnQiLCAxLCAwKQ0KICANCiAgZGVtZW5jZTIgPC0gc3Vic2V0KGRlbWVuY2UyLCBzZWxlY3QgPSAtQ0RSKQ0KICBkZW1lbmNlMiRDRFJfYmluYWlyZSA8LSBhcy5mYWN0b3IoZGVtZW5jZTIkQ0RSX2JpbmFpcmUpDQpgYGANCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0V9DQoNCiAgbnJ1biA8LSAxMDAwDQogIHJlc19sciA8LSBjKCkNCiAgcmVzX3ByZWNpc2lvbiA8LSBjKCkNCiAgcmVzX3JlY2FsbCA8LSBjKCkNCiAgcmVzX3NwZWNpZmljaXR5IDwtIGMoKQ0KICByZXNfYXVjIDwtIGMoKQ0KICANCiAgZm9yIChpcnVuIGluIDE6bnJ1bikgew0KICAgIGRmZmxyIDwtIGRlbWVuY2UyW3NhbXBsZShucm93KGRlbWVuY2UyKSksIF0NCiAgICB0cmFpbmxvZyA8LSBkZmZsclsxOjI4MCwgXQ0KICAgIHRlc3Rsb2cgPC0gZGZmbHJbMjgxOjM1NCwgXQ0KICAgIGNvbG5hbWVzKGRlbWVuY2UyKQ0KICAgIA0KICAgICAgc3VwcHJlc3NXYXJuaW5ncyh7DQogICAgbHIgPC0gZ2xtKENEUl9iaW5haXJlIH4gc2V4ZSArIEFnZSArIE1NU0UgKyBJbmRpY2VfYXRyb3BoaWUgKyBWb2x1bWVfY8OpcsOpYnJhbF90b3RhbCArIA0KICAgICAgICAgICAgICAgIEluZGljZV9hdHJvcGhpZSArIEVEVUMgKyBTRVMsIGRhdGEgPSB0cmFpbmxvZywgZmFtaWx5ID0gYmlub21pYWwoKSkNCiAgfSkNCiAgICAgIA0KICAgIA0KICAgIGR1bW15bHIgPC0gcmVwKG5hbWVzKHRhYmxlKHRyYWlubG9nJENEUl9iaW5haXJlKSlbd2hpY2gubWF4KHRhYmxlKHRyYWlubG9nJENEUl9iaW5haXJlKSldLCBucm93KHRlc3Rsb2cpKQ0KICAgIHByZWRpY3Rpb25zbHIgPC0gcHJlZGljdChsciwgdGVzdGxvZywgdHlwZSA9ICJyZXNwb25zZSIpDQogICAgcHJlZGljdGlvbnNsciA8LSBpZmVsc2UocHJlZGljdGlvbnNsciA+IDAuNSwgMSwgMCkNCiAgICANCiAgICByZXNfbHIgPC0gcmJpbmQocmVzX2xyLCBjKA0KICAgICAgbWVhbihkdW1teWxyID09IHRlc3Rsb2ckQ0RSX2JpbmFpcmUpLA0KICAgICAgbWVhbihwcmVkaWN0aW9uc2xyID09IHRlc3Rsb2ckQ0RSX2JpbmFpcmUpDQogICAgKSkNCiAgICANCiAgICAjIGNhbGN1bCBkZXMgbcOpdHJpcXVlcw0KICAgIHByZWRpY3Rpb25zX2xyIDwtIHByZWRpY3QobHIsIHRlc3Rsb2csIHR5cGUgPSAicmVzcG9uc2UiKQ0KICAgIHByZWRpY3Rpb25zX2xyIDwtIGlmZWxzZShwcmVkaWN0aW9uc19sciA+IDAuNSwgMSwgMCkNCiAgICBwcmVkaWN0aW9uc19sciA8LSBmYWN0b3IocHJlZGljdGlvbnNfbHIpDQogICAgDQogICAgY29uZnVzaW9uX21hdHJpeCA8LSBjb25mdXNpb25NYXRyaXgocHJlZGljdGlvbnNfbHIsIHRlc3Rsb2ckQ0RSX2JpbmFpcmUpDQogICAgDQogICAgcHJlY2lzaW9uIDwtIGNvbmZ1c2lvbl9tYXRyaXgkYnlDbGFzc1siUG9zIFByZWQgVmFsdWUiXQ0KICAgIHJlY2FsbCA8LSBjb25mdXNpb25fbWF0cml4JGJ5Q2xhc3NbIlNlbnNpdGl2aXR5Il0NCiAgICBzcGVjaWZpY2l0eSA8LSBjb25mdXNpb25fbWF0cml4JGJ5Q2xhc3NbIlNwZWNpZmljaXR5Il0NCiAgICANCiAgICAjIGNhbGN1bCBkdSBST0MNCiAgICByb2NfY3VydmVfbHIgPC0gcm9jKHRlc3Rsb2ckQ0RSX2JpbmFpcmUsIGFzLm51bWVyaWMocHJlZGljdGlvbnNfbHIpKQ0KICAgIGF1Y192YWx1ZV9sciA8LSBhdWMocm9jX2N1cnZlX2xyKQ0KICAgIA0KICAgICMgb24gc3RvcmUgbGVzIHLDqXN1bHRhdA0KICAgIHJlc19wcmVjaXNpb24gPC0gYyhyZXNfcHJlY2lzaW9uLCBwcmVjaXNpb24pDQogICAgcmVzX3JlY2FsbCA8LSBjKHJlc19yZWNhbGwsIHJlY2FsbCkNCiAgICByZXNfc3BlY2lmaWNpdHkgPC0gYyhyZXNfc3BlY2lmaWNpdHksIHNwZWNpZmljaXR5KQ0KICAgIHJlc19hdWMgPC0gYyhyZXNfYXVjLCBhdWNfdmFsdWVfbHIpDQogIH0NCiAgDQogIA0KICBjb2xuYW1lcyhyZXNfbHIpIDwtIGMoImR1bW15IiwgImxyIikNCg0KICANCiAgbWVhbnNfbHIgPC0gY29sTWVhbnMocmVzX2xyKQ0KICANCiAgZGZfbHIgPC0gZGF0YS5mcmFtZShNb2RlbCA9IG5hbWVzKG1lYW5zX2xyKSwgQWNjdXJhY3kgPSBtZWFuc19scikNCg0KYGBgDQoNCiMjIFJvYnVzdGVzc2UgZHUgbW9kw6hsZQ0KDQpgYGB7cn0NCiAgICMgb24gY3LDqWUgbGUgZ2dwbG90IHBvdXIgbGEgY29tcGFyYWlzb24gZW50cmUgRHVtbXkgZXQgTG9naXN0aWMgUmVncmVzc2lvbg0KIGdnTFIgPSBnZ3Bsb3QoZGZfbHIsIGFlcyh4ID0gTW9kZWwsIHkgPSBBY2N1cmFjeSwgZmlsbCA9IE1vZGVsKSkgKw0KICAgIGdlb21fY29sKCkgKw0KICAgIGxhYnModGl0bGUgPSAiQ29tcGFyYWlzb24gZW50cmUgRHVtbXkgZXQgTFIiLA0KICAgICAgICAgeCA9ICJNb2TDqGxlIiwgeSA9ICJQcsOpY2lzaW9uIikgKw0KICAgIHRoZW1lX21pbmltYWwoKQ0KICBnZ0xSDQpgYGANCkxlIGR1bW15IGEgZGUgbW9pbnMgYm9ucyByw6lzdWx0YXQgcXVlIGxlIG1vZMOobGUsIG9uIHBldXQgZG9uYyBjb250aW51ZXIgbm90cmUgYW5hbHlzZSAhDQpgYGB7cn0NCiAjIG9uIHByZW5kIGxlcyBtb3llbm5lIGRlcyBtw6l0cmlxdWVzDQogIG1lYW5fcHJlY2lzaW9uIDwtIG1lYW4ocmVzX3ByZWNpc2lvbikNCiAgbWVhbl9yZWNhbGwgPC0gbWVhbihyZXNfcmVjYWxsKQ0KICBtZWFuX3NwZWNpZmljaXR5IDwtIG1lYW4ocmVzX3NwZWNpZmljaXR5KQ0KICBtZWFuX2F1YyA8LSBtZWFuKHJlc19hdWMpDQogIA0KICAjIGRhdGEgZnJhbWUgZGVzIG3DqXRyaXF1ZQ0KICBkZl9tZXRyaWNzIDwtIGRhdGEuZnJhbWUoTcOpdHJpcXVlID0gYygiUHJlY2lzaW9uIiwgIlJhcHBlbCIsICJTcGVjaWZpY2l0w6kiLCAiQVVDIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICBNZWFuX1ZhbHVlID0gYyhtZWFuX3ByZWNpc2lvbiwgbWVhbl9yZWNhbGwsIG1lYW5fc3BlY2lmaWNpdHksIG1lYW5fYXVjKSkNCiAgDQogICMgZ3JhcGggZGVzIG3DqXRyaXF1ZXMNCiAgUmVjYXBSTEIgPSBnZ3Bsb3QoZGZfbWV0cmljcywgYWVzKHggPSBNw6l0cmlxdWUsIHkgPSBNZWFuX1ZhbHVlLCBmaWxsID0gTcOpdHJpcXVlKSkgKw0KICAgIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArDQogICAgbGFicyh0aXRsZSA9ICJSw6lncmVzc2lvbiBMb2dpc3RpcXVlICgxMDAgaXTDqXJhdGlvbnMpIiwNCiAgICAgICAgIHggPSAiIiwgeSA9ICJNb3llbm5lIikgKw0KICAgICAgICB0aGVtZV9taW5pbWFsKCkNCiAgDQpSZWNhcFJMQiA8LSBSZWNhcFJMQiArIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpDQpSZWNhcFJMQiU+JSBwbG90bHk6OmdncGxvdGx5KCkNCmBgYA0KDQpMZSBtb2TDqGxlIGEgZGUgdHLDqHMgYm9ucyByw6lzdWx0YXRzIHBvdXIgbCdBVUMsIGxhIHByw6ljaXNpb24sIGV0IGxlIHJhcHBlbCwgYXZlYyByZXNwZWN0aXZlbWVudCAwLjcyLCAwLjc0IGV0IDAuODcuIFRvdXRlZm9pcywgbGEgc2Vuc2JpbGl0w6kgZXN0IHBsdXMgYmFzc2UsIGF2ZWMgMC41OCwgY2VsYSBpbmRpcXVlIHF1ZSBsZSBtb2TDqGxlIHBvdXJyYWl0IG1hbnF1ZXIgY2VydGFpbmVzIG9ic2VydmF0aW9ucyBwb3NpdGl2ZXMuDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQ0KDQp7DQogIGxyX2dsb2JhbCA8LSBnbG0oQ0RSX2JpbmFpcmUgfiBzZXhlICsgQWdlICsgTU1TRSArIEVEVUMgKyANCiAgICAgICAgICAgICAgICAgICBWb2x1bWVfaW50ZXJjcmFuaWVuICsgSW5kaWNlX2F0cm9waGllICsgU0VTICsgVm9sdW1lX2PDqXLDqWJyYWxfdG90YWwsIA0KICAgICAgICAgICAgICAgICAgIGRhdGEgPSBkZmZsciwgbWF4aXQgPSAxMDAwLCBmYW1pbHkgPSBiaW5vbWlhbCgpKQ0KfQ0KYGBgDQpDZSBtZXNzYWdlIGQnYXZpcyBhcHBhcmFpdCBjYXIgUiBub3VzIHByw6l2aWVudCBxdWUgZGVzIHZhcmlhYmxlcyBzb250IGNlcnRhaW5lbWVudCBtdWx0aWNvbGluw6lhaXJlcyAobGEgbXVsdGljb2xpbsOpYXJpdMOpIGRlcyB2YXJpYWJsZXMgcmVuZCBpbmludGVycHLDqXRhYmxlIGxlcyByw6lzdWx0YXRzIGVuIHLDqWdyZXNzaW9uIGxvZ2lzdGlxdWUpLCBub3VzIHByZW5kcm9ucyBjZWxhIGVuIGNvbXB0ZSBhcHLDqHMgYXZvaXIgYWZmaW5lciBub3RyZSBtb2TDqGxlLg0KDQpgYGB7cn0NCnN1cHByZXNzV2FybmluZ3Moew0KICBBbm92YShscl9nbG9iYWwpDQp9KQ0KYGBgDQoNCiMjIFPDqWxlY3Rpb24gZGUgdmFyaWFibGVzIHBhciDDqXRhcGVzDQoNClBvdXIgYXNzdXJlciBsJ2ludGVycHLDqXRhYmlsaXTDqSBkZXMgcsOpc3VsdGF0cyBkZSBsYSByw6lncmVzc2lvbiBsb2dpc3RpcXVlLCBpbCBlc3QgaW1ww6lyYXRpZiBxdWUgdG91dGVzIGxlcyB2YXJpYWJsZXMgZGUgbm90cmUgbW9kw6hsZSBzb2llbnQgc2lnbmlmaWNhdGl2ZXMgKGljaSwgbm91cyBwcmVuZHJvbnMgdW4gc2V1aWwgYWxwaGEgw6lnYWxlIMOgIDUlKS4gQWluc2ksIHBvdXIgYXR0ZWluZHJlIGNldCBvYmplY3RpZiwgb24gcmV0aXJlIHVuZSDDoCB1bmUgbGVzIHZhcmlhYmxlcyBub24gc2lnbmlmaWNhdGl2ZXMgKihjYXIgbGUgcmV0cmFpdCBkJ3VuZSB2YXJpYWJsZSBwZXV0IG1vZGlmaWVyIGxlIG5pdmVhdSBkZSBzaWduaWZpY2F0aXZpdMOpIGRlcyBhdXRyZXMsIGltcGFjdGFudCBhaW5zaSBsJ2FuYWx5c2UgZ2xvYmFsZSBkdSBtb2TDqGxlKSosIGVuIGNvbW1lbsOnYW50IHBhciDDqWxpbWluZXIgY2VsbGUgcXVpIHByw6lzZW50ZSBsYSBwLXZhbHVlIGxhIG1vaW5zIHNpZ25pZmljYXRpdmUuIENlIHByb2Nlc3N1cyBnYXJhbnRpdCBxdWUgc2V1bGVzIGxlcyB2YXJpYWJsZXMgbGVzIHBsdXMgcGVydGluZW50ZXMgZGVtZXVyZW50IGRhbnMgbGUgbW9kw6hsZSBmaW5hbC4NCg0KUG91ciBjb21tZW5jZXIsIG9uIHJldGlyZSBkb25jIGxlIHZvbHVtZSBpbnRlcmNyYW5pZW4gOg0KDQpgYGB7cn0NCg0KbHJfZ2xvYmFsIDwtIGdsbShDRFJfYmluYWlyZSB+IHNleGUgKyBBZ2UgKyBNTVNFICsgRURVQyArIA0KICAgICAgICAgICAgICAgICAgICBJbmRpY2VfYXRyb3BoaWUgKyBTRVMgKyBWb2x1bWVfY8OpcsOpYnJhbF90b3RhbCwgDQogICAgICAgICAgICAgICAgICAgZGF0YSA9IGRmZmxyLCBtYXhpdCA9IDEwMDAsIGZhbWlseSA9IGJpbm9taWFsKCkpDQpgYGANCg0KYGBge3J9DQp7DQogIGxyX2dsb2JhbCA8LSBnbG0oQ0RSX2JpbmFpcmUgfiBzZXhlICsgQWdlICsgTU1TRSArIEVEVUMgKyANCiAgICAgICAgICAgICAgICAgICAgSW5kaWNlX2F0cm9waGllICsgU0VTICsgVm9sdW1lX2PDqXLDqWJyYWxfdG90YWwsIA0KICAgICAgICAgICAgICAgICAgIGRhdGEgPSBkZmZsciwgbWF4aXQgPSAxMDAwLCBmYW1pbHkgPSBiaW5vbWlhbCgpKQ0KfQ0KDQpzdXBwcmVzc1dhcm5pbmdzKHsNCiAgQW5vdmEobHJfZ2xvYmFsKQ0KfSkNCmBgYA0KDQpPbiBwZXV0IHZvaXIgcXVlIGxlcyBwLXZhbHVlcyBkZSBub3MgdmFyaWFibGVzIG9udCDDqXZvbHXDqSwgdG91dGVzIHNvbnQgZGV2ZW51ZXMgc2lnbmlmaWNhdGl2ZXMsIGNlIHF1aSBlc3QgdW5lIGJvbm5lIG5vdXZlbGxlLiBUb3V0ZWZvaXMsIG9uIG5lIHBldXQgdG91am91cnMgcGFzIGludGVycHLDqXRlciBub3RyZSBtb2TDqGxlIGRlIHLDqWdyZXNzaW9uIGxvZ2lzdGlxdWUuIEF2YW50IGNlbGEsIGlsIGVzdCBuw6ljZXNzYWlyZSBkZSBwcmVuZHJlIGVuIGNvbXB0ZSBsYSBwb3NzaWJsZSBtdWx0aWNvbGxpbsOpYXJpdMOpIGRlcyB2YXJpYWJsZXMuDQoNCiMjIFByaXNlIGVuIGNvbXB0ZSBkdSBWSUYNCg0KTGUgVklGIChWYXJpYW5jZSBJbmZsYXRpb24gRmFjdG9yKSBlbiByw6lncmVzc2lvbiBlc3QgdW5lIG1lc3VyZSBkZSBsJ2FtcGxldXIgw6AgbGFxdWVsbGUgbGEgdmFyaWFuY2UgZCd1biBjb2VmZmljaWVudCBkZSByw6lncmVzc2lvbiBlc3QgYXVnbWVudMOpZSBlbiByYWlzb24gZGUgbGEgY29ycsOpbGF0aW9uIGVudHJlIGxlcyBwcsOpZGljdGV1cnMgZGFucyBsZSBtb2TDqGxlLiBVbiBWSUYgw6lsZXbDqSBzdWdnw6hyZSB1bmUgZm9ydGUgbXVsdGljb2xsaW7DqWFyaXTDqSBlbnRyZSBsZXMgdmFyaWFibGVzIGV4cGxpY2F0aXZlcywgY2UgcXVpIHBldXQgcmVuZHJlIGxlcyBlc3RpbWF0aW9ucyBkZSBjb2VmZmljaWVudHMgcGV1IGZpYWJsZXMuIE9uIHBldXQgYWluc2kgcmVnYXJkZXIgbGVzIFZJRnMgZGUgbm9zIHZhcmlhYmxlcyA6DQoNCmBgYHtyfQ0KdmlmKGxyX2dsb2JhbCkNCmBgYA0KDQpPbiBwZXV0IGNyw6llciB1biBHR3Bsb3QgcG91ciBxdWUgY2Ugc29pdCBwbHVzIHBhcmxhbnQgOg0KDQpgYGB7cn0NCiMgY3LDqWVyIHVuIGdncGxvdCBwb3VyIG1pZXV4IHZpc3VhbGlzZXINCmRhdGEgPC0gZGF0YS5mcmFtZSgNCiAgVmFyaWFibGUgPSBjKCJzZXhlIiwgIkFnZSIsICJNTVNFIiwgIkVEVUMiLCAiVm9sdW1lX2PDqXLDqWJyYWxfdCIsICJJbmRpY2VfYXRyb3BoaWUiLCAiU0VTIiksDQogIEdWSUYgPSBjKDEuNDIxMjIxLCAxLjM0NjQxMCwgMS4wMDAwMDAsIDcuNTYwNjU3LCAxLjQxOTY3NywgMS40NzEyMDgsIDcuNjU2OTA5KQ0KKQ0KDQojIEdyYXBoaXF1ZQ0KZ2d2aWYgPSBnZ3Bsb3QoZGF0YSwgYWVzKHggPSBWYXJpYWJsZSwgeSA9IEdWSUYsIGZpbGwgPSBWYXJpYWJsZSkpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIHBvc2l0aW9uID0gImRvZGdlIikgKw0KICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSA1LCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJzb2xpZCIpICsgIyBMaWduZSByb3VnZSDDoCB5ID0gNQ0KICBsYWJzKHRpdGxlID0gIkdWSUYgcG91ciBjaGFxdWUgdmFyaWFibGUiLA0KICAgICAgIHggPSAiVmFyaWFibGUiLA0KICAgICAgIHkgPSAiR1ZJRiBSYWlzZWQiKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpDQoNCmdndmlmICU+JSBwbG90bHk6OmdncGxvdGx5KCkNCmBgYA0KDQpEYW5zIGxhIGxpdHTDqXJhdHVyZSwgaWwgZXN0IGNvdXJhbW1lbnQgYWRtaXMgcXVlIGxlIFZJRiBwZXV0IHBvc2VyIHByb2Jsw6htZSBsb3JzcXVlIHNhIHZhbGV1ciBkw6lwYXNzZSA1LiBDZXR0ZSBsaW1pdGUgZXN0IHJlcHLDqXNlbnTDqWUgcGFyIGxhIGxpZ25lIHJvdWdlIHN1ciBsZSBncmFwaGlxdWUuIEljaSwgb24gcGV1dCB2b2lyIHF1ZSBsZSBuaXZlYXUgZCfDqWR1Y2F0aW9uIGV0IGxlIG5pdmVhdSBzb2Npby3DqWNvbm9taXF1ZSBwb3NlbnQgcHJvYmzDqG1lLCBub3VzIGFsbG9ucyByZXRpcmVyIGxhIHZhcmlhYmxlIHF1aSBhIGxlIFZJRiBsZSBwbHVzIMOpbGV2w6ksIMOgIHNhdm9pciBsZSBuaXZlYXUgc29jaW8tw6ljb25vbWlxdWUsIHF1aSBlc3QgZGUgNy42NS4NCg0KYGBge3J9DQoNCmxyX2dsb2JhbDIgPC0gZ2xtKENEUl9iaW5haXJlIH4gc2V4ZSArIEFnZSArIE1NU0UgKyBFRFVDICsgDQogICAgICAgICAgICAgICAgICAgVm9sdW1lX2PDqXLDqWJyYWxfdG90YWwgKyBJbmRpY2VfYXRyb3BoaWUsIA0KICAgICAgICAgICAgICAgICAgIGRhdGEgPSBkZmZsciwgbWF4aXQgPSAxMDAwLCBmYW1pbHkgPSBiaW5vbWlhbCgpKQ0KDQp2aWYobHJfZ2xvYmFsMikNCg0KIyBvbiB2YSBjcsOpZXIgdW4gZ2dwbG90IHBvdXIgbWlldXggdmlzdWFsaXNlcg0KZGF0YSA8LSBkYXRhLmZyYW1lKA0KICBWYXJpYWJsZSA9IGMoInNleGUiLCAiSW5kaWNlX2F0cm9waGllIiwgIk1NU0UiLCAiVm9sdW1lX3N1YnN0YW5jZV9ibGFuY2hlIiwgIkluZGljZV9hdHJvcGhpZSIsICJFRFVDIiksDQogIEdWSUYgPSBjKDEuMzI0NTI5LCAxLjQwMjU0MCwgMS4wMDAwMDAsIDEuMzc5MjA1LCAxLjQxOTY3NywgMS4wMjMyNTUpDQopDQoNCiMgR3JhcGhpcXVlDQpnZ3ZpZjIgPSBnZ3Bsb3QoZGF0YSwgYWVzKHggPSBWYXJpYWJsZSwgeSA9IEdWSUYsIGZpbGwgPSBWYXJpYWJsZSkpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIHBvc2l0aW9uID0gImRvZGdlIikgKw0KICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSA1LCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJzb2xpZCIpICsgIyBMaWduZSByb3VnZSDDoCB5ID0gNQ0KICBsYWJzKHRpdGxlID0gIkdWSUYgcG91ciBjaGFxdWUgdmFyaWFibGUiLA0KICAgICAgIHggPSAiVmFyaWFibGUiLA0KICAgICAgIHkgPSAiTml2ZWF1IikgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKQ0KDQpnZ3ZpZjIgJT4lIHBsb3RseTo6Z2dwbG90bHkoKQ0KYGBgDQoNClNhbnMgY2V0dGUgdmFyaWFibGUsIHRvdXMgbGVzIFZJRlMgc29udCBpbmbDqXJpZXVycyDDoCAxLDUgZXQgbmUgcG9zZW50IGRvbmMgcGx1cyBkZSBwcm9ibMOobWUuIE9uIHBldXQgZG9uYyBjb250aW51ZXIgbCdhbmFseXNlIGV0IHJlY2FsY3VsZXIgbm9zIHAtdmFsdWVzLg0KDQojIyBNb2TDqGxlIGZpbmFsDQoNCmBgYHtyfQ0KQW5vdmEobHJfZ2xvYmFsMikNCg0KYGBgDQoNCk1haW50ZW5hbnQgcXVlIG5vdXMgYXZvbnMgdsOpcmlmacOpIHRvdXRlcyBub3MgY29uZGl0aW9ucywgbm91cyBwb3V2b25zIGludGVycHLDqXRlciBub3RyZSBtb2TDqGxlIGZpbmFsLiBOb3VzIGF2b25zIGRvbmMgKipyZXRpcsOpIGxlIHZvbHVtZSBpbnRlcmNyYW5pZW4gYWluc2kgcXVlIGxlIG5pdmVhdSBzb2Npby3DqWNvbm9taXF1ZSwqKiBub3VzIGxhaXNzYW50IDoNCg0KInNleGUiLCAiTU1TRSIsICJWb2x1bWVfY8OpcsOpYnJhbF90b3RhbCIsIHF1aSBvbnQgZGVzIHAtdmFsdWVzIHRyw6hzIGZhaWJsZXMgKGluZsOpcmlldXJlcyDDoCAwLDAwMSksIGNlIHF1aSBlc3QgaW5kaXF1w6kgcGFyIHRyb2lzIMOpdG9pbGVzIChcKlwqXCopLCAqKnN1Z2fDqXJhbnQgdW5lIGZvcnRlIGFzc29jaWF0aW9uIGF2ZWMgbGEgdmFyaWFibGUgZMOpcGVuZGFudGUuKioNCg0KTGVzIHZhcmlhYmxlcyAiQWdlIiBldCAiSW5kaWNlX2F0cm9waGllIiBvbnQgZGVzIHAtdmFsZXVycyBkZSAwLjAwNDgyMCBldCAwLjAwMTI1NiByZXNwZWN0aXZlbWVudCwgY2UgcXVpIGVzdCBpbmRpcXXDqSBwYXIgZGV1eCDDqXRvaWxlcyoqLCBzdWdnw6lyYW50IHVuZSBhc3NvY2lhdGlvbiBzaWduaWZpY2F0aXZlLCBtYWlzIGF2ZWMgdW4gbml2ZWF1IGRlIGNvbmZpYW5jZSBsw6lnw6hyZW1lbnQgbW9pbnMgw6lsZXbDqSBxdWUgY2VsbGVzIGF2ZWMgdHJvaXMgw6l0b2lsZXMuKioNCg0KQ2UgcXVpIG5vdXMgbGFpc3NlIGxhIHZhcmlhYmxlICJFRFVDIiwgcXVpLCBiaWVuIHF1ZSBwcsOpc2VudGFudCB1bmUgcC12YWxldXIgbMOpZ8OocmVtZW50IHBsdXMgw6lsZXbDqWUgcXVlIGxlcyBhdXRyZXMgdmFyaWFibGVzIGRhbnMgbm90cmUgYW5hbHlzZSAoMCwwMTQwNTgpLCByZXN0ZSBzdGF0aXN0aXF1ZW1lbnQgc2lnbmlmaWNhdGl2ZSBhdSBuaXZlYXUgZGUgY29uZmlhbmNlIGRlIDk1JS4NCg0KIyMgVsOpcmlmaWNhdGlvbiBkZXMgY29uZGl0aW9ucyBkJ3V0aWxpc2F0aW9uDQoNCkRhbnMgbGEgcsOpZ3Jlc3Npb24gbG9naXN0aXF1ZSwgdW4gbW9kw6hsZSBsaW7DqWFpcmUgZ8OpbsOpcmFsaXPDqSwgbCdhZMOpcXVhdGlvbiBlc3Qgw6l2YWx1w6llIGVuIGV4YW1pbmFudCBsZXMgcsOpc2lkdXMgZGUgUGVhcnNvbiBldCBkZSBkw6l2aWFuY2UuIExlcyByw6lzaWR1cyBkZSBQZWFyc29uIG1lc3VyZW50IGxhIGRpZmbDqXJlbmNlIGVudHJlIGxlcyB2YWxldXJzIG9ic2VydsOpZXMgZXQgYXR0ZW5kdWVzLCB0YW5kaXMgcXVlIGxlcyByw6lzaWR1cyBkZSBkw6l2aWFuY2Ugw6l2YWx1ZW50IGwnYWp1c3RlbWVudCBkdSBtb2TDqGxlIGF1eCBkb25uw6llcy4gQ2VzIHLDqXNpZHVzIHNvbnQgc291dmVudCB0ZXN0w6lzIMOgIGwnYWlkZSBkZSBkaXN0cmlidXRpb25zIGR1IGNoaS1kZXV4IHBvdXIgZMOpdGVjdGVyIGxlcyBwb2ludHMgYXR5cGlxdWVzIG91IG1hbCBhanVzdMOpcyBkYW5zIGxlcyBkb25uw6llcy4NCmBgYHtyfQ0KYmlubmVkcGxvdChmaXR0ZWQobHJfZ2xvYmFsMiksIHJlc2lkdWFscyhscl9nbG9iYWwyLCB0eXBlID0gInJlc3BvbnNlIikpDQoNCmBgYA0KTGUgZ3JhcGhpcXVlIHByb2R1aXQgcGFyIGJpbm5lZHBsb3QgaW5kaXF1ZSB1biBpbnRlcnZhbGxlIGRlIHByw6lkaWN0aW9uIMOgIDk1JSAobGlnbmVzIGdyaXNlcykgcG91ciBsZXMgcsOpc2lkdXMgbW95ZW5zLiBBaW5zaSwgc2kgbGUgbW9kw6hsZSBiaW5vbWlhbCBlc3QgYm9uLCBlbnZpcm9uIDk1JSBkZXMgcsOpc2lkdXMgZGV2cmFpZW50IHNlIHRyb3V2ZXIgw6AgbOKAmWludMOpcmlldXIgZGUgY2V0IGludGVydmFsbGUuIEljaSwgY+KAmWVzdCBsZSBjYXMgZGUgMTMgcsOpc2lkdXMgc3VyIDE2LCBzb2l0IDgxLjI1JSwgY2UgcXVpIGVzdCBtb2lucyBwZXJmb3JtYW50IGRlIGNlIHF1J29uIGF0dGVuZHJhaXQgZGFucyBsJ2lkw6lhbCAhDQoNCg0KTCdlc3RpbWF0aW9uIGRlcyBwYXJhbcOodHJlcyBkZXMgbW9kw6hsZXMgbGluw6lhaXJlcyBnw6luw6lyYWxpc8OpcyBuJ3V0aWxpc2UgcGFzIGxhIG3DqXRob2RlIGRlcyBtb2luZHJlcyBjYXJyw6lzLCBjZSBxdWkgZmFpdCBxdWUgbGUgUsKyLCBjb3VyYW1tZW50IHV0aWxpc8OpIGVuIG1vZMOobGUgbGluYWlyZSBnw6luw6lyYWxlIHBvdXIganVnZXIgZHUgZGVncsOpIGQnZWZmaWNhY2l0w6kgZHUgbW9kw6hsZSwgbidhIHBhcyBkZSBzZW5zIGljaS4gDQoNClBvdXIgcGFsbGllciBjZWxhLCBvbiBwZXV0IHV0aWxpc2VyIHVuICoqcHNldWRvIFLCsioqLCBwbHVzaWV1cnMgZXhpc3RlLCBvbiBhIHBhciBleGVtcGxlIGxlICoqUsKyIGRlIE1jRmFkZGVuKiosIHF1aSBzZSBjYWxjdWxlIHRlbCBxdWUgIGxhIGZyYWN0aW9uIGRlIGxhIGTDqXZpYW5jZSBkdSBtb2TDqGxlIG51bCBleHBsaXF1w6llIHBhciBsZSBtb2TDqGxlIGluY2x1YW50IGxlcyBwcsOpZGljdGV1cnMuDQpgYGB7cn0NCnBzZXVkb19SMiA8LSAxIC0gbHJfZ2xvYmFsMiRkZXZpYW5jZS9scl9nbG9iYWwyJG51bGwuZGV2aWFuY2UNCnBzZXVkb19SMg0KYGBgDQpPbiBvYnRpZW50IHVuIFLCsiBhanVzdMOpIGRlIDAuMzIuIExlIFLCsiBhanVzdMOpIHZhIGRlIDAgw6AgMSwgMCB2ZXV0IGRpcmUgcXVlIGxlIG1vZMOobGUgbmUgcGFydmllbnQgcGFzIMOgIGV4cGxpcXVlciBxdW9pIHF1ZSBjZSBzb2l0IGVuIGZvbmN0aW9uIGRlIGxhIHZhcmlhYmxlIGTDqXBlbmRhbnRlLCBldCDDoCAxIHF1J2lsIHkgYXJyaXZlIHBhcmZhaXRlbWVudCwgY2UgcXVpIGluZGlxdWUgdW4gYWp1c3RlbWVudCBtb2TDqXLDqSBkdSBtb2TDqGxlIGF1eCBkb25uw6llcy4NCg0KDQpQb3VyIMOqdHJlIHPDu3IsIG9uIHBldXQgdXRpbGlzZXIgdW5lIGF1dHJlIG3DqXRob2RlIHF1aSBjYWxjdWxlIGxlIFLCsiBhanVzdMOpLCBxdWFudGlmaWUgbGEgZGlmZsOpcmVuY2UgZW50cmUgbGEgdmFsZXVyIG1veWVubmUgcHLDqWRpdGUgcG91ciBsZXMgb2JzZXJ2YXRpb25zIG/DuSBsYSB2YXJpYWJsZSBkw6lwZW5kYW50ZSBlc3Qgw6lnYWxlIMOgIDEgZXQgY2VsbGUgcG91ciBsZXMgb2JzZXJ2YXRpb25zIG/DuSBsYSB2YXJpYWJsZSBkw6lwZW5kYW50ZSBlc3Qgw6lnYWxlIMOgIDAuIENlbGEgcGVybWV0IGQnw6l2YWx1ZXIgbGEgY2FwYWNpdMOpIGR1IG1vZMOobGUgw6AgZGlmZsOpcmVuY2llciBsZXMgZGV1eCBjYXTDqWdvcmllcyBkZSByw6lwb25zZS4gYWluc2ksIGlsIG1lc3VyZSBsYSBmb3JjZSBkZSBwcsOpZGljdGlvbiBkdSBtb2TDqGxlIGVuIHRlcm1lcyBkZSBkaWZmw6lyZW5jaWF0aW9uIGVudHJlIGxlcyBkZXV4IG5pdmVhdXggZGUgbGEgdmFyaWFibGUgZMOpcGVuZGFudGUuDQoNCmBgYHtyfQ0KDQpyMl90anVyIDwtIG1lYW4obHJfZ2xvYmFsMiRmaXR0ZWQudmFsdWVzW2xyX2dsb2JhbDIkeSA9PSAxXSkgLSBtZWFuKGxyX2dsb2JhbDIkZml0dGVkLnZhbHVlc1tscl9nbG9iYWwyJHkgPT0gMF0pDQpyMl90anVyDQpgYGANCkF2ZWMgY2V0dGUgbcOpdGhvZGUsIG9uIG9idGllbnQgMC4zNiwgY2UgcXVpIGVzdCBhc3NleiBzZW1ibGFibGUgYXUgUsKyIGRlIE1jRmFkZGVuIGNhbGN1bMOpIHByw6ljZWRlbW1lbnQgZXQgY29uc29saWRlIGNlIHLDqXN1bHRhdC4NCg0KDQpFbiBjb25jbHVzaW9uLCBub3RyZSBhbmFseXNlIHLDqXbDqGxlIGRlcyBhc3NvY2lhdGlvbnMgc2lnbmlmaWNhdGl2ZXMgZW50cmUgbGUgc2V4ZSwgbCfDomdlLCBsZSBNTVNFLCBsZSBjcsOpYnJhbCB0b3RhbCwgbCdpbmRpY2UgZCdhdHJvcGhpZSBldCBsZSBuaXZlYXUgZCfDqWR1Y2F0aW9uIGF2ZWMgbGEgdmFyaWFibGUgZMOpcGVuZGFudGUsIHN1Z2fDqXJhbnQgbGV1ciBpbXBvcnRhbmNlIGRhbnMgbm90cmUgbW9kw6hsZS4NCg0KTGEgdsOpcmlmaWNhdGlvbiBkZXMgY29uZGl0aW9ucyBkJ2FwcGxpY2F0aW9ucyBtb250cmUgdW4gcsOpc3VsdGF0IGVuIGRlc3NvdXMgZGUgY2UgcXUnb24gYXVyYWl0IHB1IGF0dGVuZHJlIHBvdXIgTGVzIHLDqXNpZHVzIGRlIHBlYXJzb24sIGV0IHVuIFLCsiBhanVzdMOpIG1vbnRyYW50IHVuZSBlZmZpY2FjaXTDqSBtb2TDqXLDqSwgbGVzIHLDqXN1bHRhdHMgZGUgbm90cmUgcsOpZ3Jlc3Npb24gbG9naXN0aXF1ZSBiaW5vbWlhbGUsIGJpZW4gcXVlIHRyw6hzIGludMOpcmVzc2FudHMsIHNvbnQgZG9uYyDDoCBwcmVuZHJlIGF2ZWMgZGVzIHBpbmNldHRlcy4NCg0KIyBWLiBBbmFseXNlIGRpc2NyaW1pbmFudGUgbGluw6lhaXJlIChMREEpDQoNCiMjIEp1c3RpZmljYXRpb24NCg0KTCdhbmFseXNlIGRpc2NyaW1pbmFudGUgbGluw6lhaXJlIG9mZnJlIHVuZSBtw6l0aG9kZSBlZmZpY2FjZSBwb3VyIHNpbXBsaWZpZXIgbGVzIG1vZMOobGVzLCBhbcOpbGlvcmVyIGxhIHByw6ljaXNpb24gZGUgbGEgY2xhc3NpZmljYXRpb24gZXQgZm91cm5pciBkZXMgcsOpc3VsdGF0cyBpbnRlcnByw6l0YWJsZXMuIFBvdXIgZWZmZWN0dWVyIHVuZSBMREEsIGlsIGVzdCBuw6ljZXNzYWlyZSBxdWUgdG91dGVzIGxlcyB2YXJpYWJsZXMgc29pZW50IHF1YW50aXRhdGl2ZXMsIGV0IGlsIG5lIGRvaXQgcGFzIHkgYXZvaXIgZGUgY29saW7DqWFyaXTDqSBlbnRyZSBsZXMgdmFyaWFibGVzLg0KDQpDb21tZSBub3VzIGF2b25zIGRlcyB2YXJpYWJsZXMgcXVhbGl0YXRpdmVzLCBub3VzIGFsbG9ucyBkJ2Fib3JkIGZhaXJlIHVuZSBBbmFseXNlIEZhY3RvcmllbGxlIGRlcyBDb3JyZXNwb25kYW5jZXMgKEFGQykuDQoNCiMjIEFGQw0KDQpOb3VzIGF2b25zIHZ1IGxvcnMgZGUgbGEgcsOpZ3Jlc3Npb24gbG9naXN0aXF1ZSBxdSdpbCB5IGF2YWl0IHVuZSBtdWx0aWNvbGluw6lhcml0w6kgZW50cmUgbGEgdmFyaWFibGUgcmVwcsOpc2VudGFudCBsZSBuaXZlYXUgZCfDqWR1Y2F0aW9uIChFRFVDKSBldCBsYSB2YXJpYWJsZSByZXByw6lzZW50YW50IGxlIG5pdmVhdSBzb2Npby1jdWx0dXJlbCAoU0VTKS4gQ29tbWUgcHLDqWNlZGVtbWVudCwgbm91cyBhbGxvbnMgZG9uYyByZXRpcmVyIGxhIHZhcmlhYmxlIHF1aSBhdmFpdCBsZSBWSUYgbGUgcGx1cyBoYXV0LCBjJ2VzdCDDoCBkaXJlIGxlIG5pdmVhdSBzb2Npby1jdWx0dXJlbC4NCg0KQ2VsYSBkaXQsIG9uIHBldXQgZmFpcmUgbm90cmUgQUZDIDoNCg0KYGBge3J9DQogZGF0YV9hZmMgPC0gZGVtZW5jZTJbLCBjKCJzZXhlIiwgIk1NU0UiLCAiRURVQyIpXQ0KICANCiAgYWZjX3Jlc3VsdCA8LSBNQ0EoZGF0YV9hZmMsIGdyYXBoID0gRkFMU0UpDQogDQogIGhlYWQoYWZjX3Jlc3VsdCRpbmQkY29vcmQpDQpgYGANCg0KT24gcGV1dCB2b2lyIHF1J29uIGEgY2lucSBkaW1lbnNpb25zIGRhbnMgbm90cmUgQUZDLCBjZXV4LWNpIHJlcHLDqXNlbnRlbnQgbm9zIHZhcmlhYmxlcyBxdWFsaXRhdGl2ZXMsIG9uIHZhIGRvbmMgbGVzIGluY2x1cmUgZGFucyBub3RyZSBqZXUgZGUgZG9ubsOpZXMgZGUgYmFzZSwgcHVpcyByZXRpcmVyIGxlcyB2YXJpYWJsZXMgcXVhbGl0YXRpdmVzIGFmaW4gZGUgcG91dm9pciBmYWlyZSBub3RyZSBMREEuDQoNCmBgYHtyfQ0KIGxkYV9pbnB1dCA8LSBhcy5kYXRhLmZyYW1lKGFmY19yZXN1bHQkaW5kJGNvb3JkWywgcGFzdGUwKCJEaW0gIiwgMTo1KV0pIA0KICBkZW1lbmNlMiA8LSBjYmluZChkZW1lbmNlMiwgbGRhX2lucHV0KQ0KICBkZW1lbmNlMiA8LSBkZW1lbmNlMlssIC1jKDEsIDMsIDQsIDUpXQ0KYGBgDQoNCiMjIFByw6lwYXJhdGlvbiBkZXMgZG9ubsOpZXMgcG91ciBsYSBMREENCg0KQ2VsYSBmYWl0LCBvbiBwZXV0IHByw6lwYXJlciBub3MgZG9ubsOpZXMgcG91ciBmYWlyZSBsYSBMREEgOg0KDQpgYGB7cn0NCiAgZ3AgPSBhcy5mYWN0b3IoZGVtZW5jZTIkQ0RSKQ0KICBtYXRyaWNlID0gYXMubWF0cml4KGRlbWVuY2UyKQ0KICBkYXRhID0gZGF0YS5mcmFtZShncm91cD1kZW1lbmNlMiRDRFIsIG1hdHJpY2UpDQogIGNvbG5hbWVzKGRhdGEpDQogIGRhdGEkQWdlIDwtIGFzLm51bWVyaWMoZGF0YSRBZ2UpDQogIA0KICBkYXRhJFZvbHVtZV9pbnRlcmNyYW5pZW48LSBhcy5udW1lcmljKGRhdGEkVm9sdW1lX2ludGVyY3JhbmllbikNCiAgZGF0YSRWb2x1bWVfY8OpcsOpYnJhbF90b3RhbCA8LSBhcy5udW1lcmljKGRhdGEkVm9sdW1lX2PDqXLDqWJyYWxfdG90YWwpDQogIGRhdGEkSW5kaWNlX2F0cm9waGllIDwtIGFzLm51bWVyaWMoZGF0YSRJbmRpY2VfYXRyb3BoaWUpDQogIGRhdGEkRGltLjEgPC0gYXMubnVtZXJpYyhkYXRhJERpbS4xKQ0KICBkYXRhJERpbS4yIDwtIGFzLm51bWVyaWMoZGF0YSREaW0uMikNCiAgZGF0YSREaW0uMyA8LSBhcy5udW1lcmljKGRhdGEkRGltLjMpDQogIGRhdGEkRGltLjQgPC0gYXMubnVtZXJpYyhkYXRhJERpbS40KQ0KICBkYXRhJERpbS41IDwtIGFzLm51bWVyaWMoZGF0YSREaW0uNSkNCiAgDQogIGNvbG9ubmVzX251bWVyaXF1ZXMgPC0gc2FwcGx5KGRhdGEsIGlzLm51bWVyaWMpDQogIERhdGFDUjwtIHNjYWxlKGRhdGFbLGNvbG9ubmVzX251bWVyaXF1ZXNdKQ0KDQogIGRhdGEyIDwtIGFzLmRhdGEuZnJhbWUoRGF0YUNSKQ0KDQogIGRhdGEyJENEUiA8LSBkZW1lbmNlMiRDRFJfYmluYWlyZQ0KDQogIGRhdGEyJENEUiA8LSBhcy5mYWN0b3IoZGF0YTIkQ0RSKQ0KDQoNCiAgZ3AgPSBhcy5mYWN0b3IoZGF0YTIkQ0RSKQ0KICBEYXRhbGRhID0gZGF0YS5mcmFtZShncm91cD1ncCxkYXRhMikNCmBgYA0KIyMgTWlzZSBlbiBwbGFjZSBkZSBsYSBMREENCg0KQ29tbWUgcG91ciBsZXMgbcOpdGhvZGVzIHByw6ljw6lkZW50ZXMsIG9uIGZhaXQgZGUgbGEgKip2YWxpZGF0aW9uKiogKipjcm9pc8OpZSoqLCBhdmVjIDgwICUgcG91ciBsJ2VudHJhw65uZW1lbnQgZXQgMjAgJSBwb3VyIGxlIHRlc3QsIHB1aXMgb24gY29tcGFyZSBub3RyZSBtb2TDqGxlIMOgIHVuICoqbW9kw6hsZSBkdW1teSoqIGV0IG9uIGNhbGN1bGUgbGVzICoqbcOpdHJpcXVlcyBkJ8OpdmFsdWF0aW9uKiogc3VyIGwnw6ljaGFudGlsbG9uIHRlc3QsIGF2ZWMgdW5lICoqYm91Y2xlIGRlIDEwMCByw6lww6l0aXRpb25zLioqDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQ0KICAjIGNyw6llciB1bmUgbGlzdGUgdmlkZSBwb3VyIHN0b2NrZXIgbGVzIHBlcmZvcm1hbmNlcw0KICBwZXJmb3JtYW5jZXNfbWV0cmljcyA8LSBsaXN0KCkNCiAgDQogICAgIyBib3VjbGUgc3VyIGxlcyBpdMOpcmF0aW9ucw0KICBzZXQuc2VlZCgxMDApDQogIG5ydW4gPC0gMTAwDQogIGZvciAoaXJ1biBpbiAxOm5ydW4pew0KICAgIGRmPC0gZGF0YTJbc2FtcGxlKG5yb3coZGF0YTIpKSwgXQ0KICAgIA0KICAgIA0KICAgIHRyYWluIDwtIGRmWzE6MjgwLCBdDQogICAgdGVzdCA8LSBkZlsyODE6MzU0LCBdDQogICAgcmVzbGRhIDwtIGxkYShDRFIgfiAuICwgZGF0YT10cmFpbikNCiAgICBwcmVkbGRhIDwtIHByZWRpY3QocmVzbGRhLHRlc3QpDQogICAgIyBtYXRyaWNlIGRlIGNvbmZ1c2lvbg0KICAgIGNvbmZ1c2lvbl9tYXRyaXggPC0gdGFibGUodGVzdCRDRFIsIHByZWRsZGEkY2xhc3MpDQogICAgIyBtw6l0cmlxdWVzIHBvdXIgY2hhcXVlIGl0w6lyYXRpb24gZGUgdmFsaWRhdGlvbiBjcm9pc8OpZQ0KICAgIGNvbmZ1c2lvbl9tYXRyaWNlcyA8LSBjb25mdXNpb25NYXRyaXgodGVzdCRDRFIsIHByZWRsZGEkY2xhc3MpDQogICAgDQogICAgIyBtw6l0cmlxdWVzIGRlIHBlcmZvcm1hbmNlDQogICAgDQogICAgU2Vuc2liaWxpdGUgPC0gY29uZnVzaW9uX21hdHJpY2VzJGJ5Q2xhc3NbJ1NlbnNpdGl2aXR5J10NCiAgICBTcGVjaWZpY2l0ZSA8LSBjb25mdXNpb25fbWF0cmljZXMkYnlDbGFzc1snU3BlY2lmaWNpdHknXQ0KICAgIFByZWNpc2lvbiA8LSBjb25mdXNpb25fbWF0cmljZXMkYnlDbGFzc1snUG9zIFByZWQgVmFsdWUnXQ0KICAgICMgY2FsY3VsIGwnQVVDDQogICAgQVVDIDwtIHJvYyh0ZXN0JENEUiwgYXMubnVtZXJpYyhwcmVkbGRhJHgpKSRhdWMNCiAgICAjc3RvY2tlciBsZXMgbWV0cmlxdWVzIHBvdXIgY2hhcXVlIGl0ZXJhdGlvbg0KICAgIHBlcmZvcm1hbmNlc19tZXRyaWNzW1tpcnVuXV0gPSBjKFNlbnNpYmlsaXRlLCBTcGVjaWZpY2l0ZSwgUHJlY2lzaW9uLCBBVUMpDQogIH0NCiAgbWV0cmljX25vbXM9YygiU2Vuc2liaWxpdGUiLCAiU3BlY2lmaWNpdGUiLCAiUHJlY2lzaW9uIiwgIkFVQyIpDQogICNjYWxjdWxlciBsYSBtb3llbm5lIGRlcyBtZXRyaXF1ZXMgcG91ciB0b3V0ZXMgbGVzIGl0ZXJhdGlvbnMNCiAgYXZyX21ldHJpY3M9Y29sTWVhbnMoZG8uY2FsbChyYmluZCxwZXJmb3JtYW5jZXNfbWV0cmljcykpDQogIG1ldHJpY19zdW1tYXJ5IDwtIGRhdGEuZnJhbWUoTWV0cmlxdWUgPSBtZXRyaWNfbm9tcyxNb3llbm5lID0gYXZyX21ldHJpY3MpDQogDQogICMjY3LDqWVyIHVuIGdncGxvdA0KICByZWNhcExEQSA9IGdncGxvdChtZXRyaWNfc3VtbWFyeSwgYWVzKHggPSBNZXRyaXF1ZSwgeSA9IE1veWVubmUsIGZpbGwgPSBNZXRyaXF1ZSkpICsNCiAgICBnZW9tX2NvbCgpICsNCiAgICBsYWJzKHRpdGxlID0gIkxEQSAoMTAwIGl0w6lyYXRpb25zKSIsDQogICAgICAgICB4ID0gIiIsIHkgPSAiTW95ZW5uZSIpICsNCiAgICB0aGVtZV9taW5pbWFsKCkNCiAgcmVjYXBMREEgJT4lIHBsb3RseTo6Z2dwbG90bHkoKQ0KDQogIHJlY2FwTERBIDwtIHJlY2FwTERBICsgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkgKyB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKQ0KICANCiAgDQogICAgcmVjYXBMREEgJT4lIHBsb3RseTo6Z2dwbG90bHkoKQ0KYGBgDQoNCk5vdHJlIExEQSBkw6ltb250cmUgZGVzIHBlcmZvcm1hbmNlcyBzb2xpZGVzLCBhdmVjIHVuZSBBVUMgZGUgMCw4MCBpbmRpcXVhbnQgdW5lIGJvbm5lIGNhcGFjaXTDqSBkZSBkaXNjcmltaW5hdGlvbiBlbnRyZSBsZXMgY2xhc3NlcyBwb3NpdGl2ZXMgZXQgbsOpZ2F0aXZlcy4gRGUgcGx1cywgdW5lIHByw6ljaXNpb24gw6lsZXbDqWUgZGUgMCw5MCBzdWdnw6hyZSBxdWUgbGEgbWFqb3JpdMOpIGRlcyBwcsOpZGljdGlvbnMgcG9zaXRpdmVzIHNvbnQgY29ycmVjdGVzLiBCaWVuIHF1ZSBsYSBzZW5zaWJpbGl0w6kgc29pdCBsw6lnw6hyZW1lbnQgcGx1cyBiYXNzZSDDoCAwLDcwLCBlbGxlIGluZGlxdWUgdG91dCBkZSBtw6ptZSB1bmUgY2FwYWNpdMOpIMOgIGNhcHR1cmVyIHVuZSBwcm9wb3J0aW9uIHNpZ25pZmljYXRpdmUgZGVzIG9ic2VydmF0aW9ucyBwb3NpdGl2ZXMuIEVuZmluLCB1bmUgc3DDqWNpZmljaXTDqSBkZSAwLDc4IHNvdWxpZ25lIGxhIGNhcGFjaXTDqSBkdSBtb2TDqGxlIMOgIGlkZW50aWZpZXIgY29ycmVjdGVtZW50IGxlcyBvYnNlcnZhdGlvbnMgbsOpZ2F0aXZlcy4NCg0KDQoNCg0KYGBge3J9DQoNCnJlc2xkYTIgPC0gbGRhKENEUiB+IC4gLCBkYXRhPWRmKQ0KcHJlZGxkYTIgPC0gcHJlZGljdChyZXNsZGEsZGYpDQoNCmJveHBsb3QocHJlZGxkYTIkeH5ncCkNCmBgYA0KDQpgYGB7cn0NCiAjIGV4dHJhaXJlIGxlcyBjb2VmZmljaWVudHMgZHUgbW9kw6hsZSBMREENCiAgY29lZmZpY2llbnRzIDwtIGNvZWYocmVzbGRhMikNCiAgDQogICMgQ3LDqWVyIHVuIGRhdGFmcmFtZSDDoCBwYXJ0aXIgZGVzIGNvZWZmaWNpZW50cw0KICBjb2VmZmljaWVudHNfZGYgPC0gZGF0YS5mcmFtZSgNCiAgICBWYXJpYWJsZSA9IHJvd25hbWVzKGNvZWZmaWNpZW50cyksDQogICAgQ29lZmZpY2llbnQgPSBjb2VmZmljaWVudHNbLCAxXSAgIyBFeHRyYWlyZSB1bmlxdWVtZW50IGxhIHByZW1pw6hyZSBjb2xvbm5lIGRlIGNvZWZmaWNpZW50cw0KICApDQogIA0KICAjIENyw6llciBsZSBncmFwaGlxdWUgYXZlYyBsZXMgY29lZmZpY2llbnRzIGV4dHJhaXRzDQogIGdncGxvdChjb2VmZmljaWVudHNfZGYsIGFlcyh4ID0gVmFyaWFibGUsIHkgPSBDb2VmZmljaWVudCkpICsNCiAgICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgZmlsbCA9ICJza3libHVlIiwgd2lkdGggPSAwLjUpICsNCiAgICBsYWJzKHRpdGxlID0gIkNvZWZmaWNpZW50cyBkZSBsYSBkaW1lbnNpb24gZGlzY3JpbWluYW50ZSAoTEQxKSIsDQogICAgICAgICB4ID0gIiIsIHkgPSAiQ29lZmZpY2llbnQiKSArDQogICAgdGhlbWVfbWluaW1hbCgpICsNCiAgICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKQ0KYGBgDQoNCiMgVi4gQ29uY2x1c2lvbg0KDQpgYGB7cn0NCmdyb2JfcmVjYXBDQVJUIDwtIGdncGxvdEdyb2IocmVjYXBDQVJUKQ0KZ3JvYl9yZWNhcFJGIDwtIGdncGxvdEdyb2IocmVjYXBSRikNCmdyb2JfUmVjYXBSTEIgPC0gZ2dwbG90R3JvYihSZWNhcFJMQikNCmdyb2JfcmVjYXBMREEgPC0gZ2dwbG90R3JvYihyZWNhcExEQSkNCg0KYGBgDQoNCg0KYGBge3IgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9MTJ9DQpncmlkLmFycmFuZ2UoZ3JvYl9yZWNhcENBUlQsIGdyb2JfcmVjYXBSRiwgZ3JvYl9SZWNhcFJMQiwgZ3JvYl9yZWNhcExEQSkNCmBgYA0KDQoNCiAgIyBWSS4gQmlibGlvZ3JhcGhpZQ0K