Setup.
Si l’un de ces packages n’a pas encore été installé sur votre machine, ou que vous en possédez une version ancienne, il faut entrer la commande correspondante après l’avoir dé-commentée.
# Obligatoire
# install.packages("ggplot2")
# Facultatif
# install.packages("RColorBrewer")
# install.packages("hexbin")
# install.packages("maps")
Dans tous les cas, il est nécessaire au lancement de charger le ou les packages.
library(ggplot2)
library(RColorBrewer)
library(hexbin)
library(maps)
Un jeu de données interne à ggplot2
Nous utilisons un jeu de données interne à ggplot2 contenant le prix ainsi que d’autres attributs de 53 940 diamants.
Les commandes suivantes permettent de prendre connaissance du jeu de données.
# help(diamonds)
# View(diamonds)
head(diamonds)
str(diamonds)
Classes ‘tbl_df’, ‘tbl’ and 'data.frame': 53940 obs. of 10 variables:
$ carat : num 0.23 0.21 0.23 0.29 0.31 0.24 0.24 0.26 0.22 0.23 ...
$ cut : Ord.factor w/ 5 levels "Fair"<"Good"<..: 5 4 2 4 2 3 3 3 1 3 ...
$ color : Ord.factor w/ 7 levels "D"<"E"<"F"<"G"<..: 2 2 2 6 7 7 6 5 2 5 ...
$ clarity: Ord.factor w/ 8 levels "I1"<"SI2"<"SI1"<..: 2 3 5 4 2 6 7 3 4 5 ...
$ depth : num 61.5 59.8 56.9 62.4 63.3 62.8 62.3 61.9 65.1 59.4 ...
$ table : num 55 61 65 58 58 57 57 55 61 61 ...
$ price : int 326 326 327 334 335 336 336 337 337 338 ...
$ x : num 3.95 3.89 4.05 4.2 4.34 3.94 3.95 4.07 3.87 4 ...
$ y : num 3.98 3.84 4.07 4.23 4.35 3.96 3.98 4.11 3.78 4.05 ...
$ z : num 2.43 2.31 2.31 2.63 2.75 2.48 2.47 2.53 2.49 2.39 ...
On ne va garder qu’une entrée sur dix de ce jeu de données afin de rendre les calculs plus rapides et les graphiques moins chargés.
diam <- diamonds[seq(10, nrow(diamonds), 10),]
Charger les données dans ggplot
Remarque : à partir d’ici, nous parlons principalement de ggplot sans le “2” car il va s’agir de la fonction “ggplot”.
Le “2” n’est utilisé que dans le nom du package.
Et oui, ggplot1 existe.
On charge les données
Attention ! L’input doit toujours être un data frame !
g <- ggplot(diam)
g
Contrairement à la fonction plot
, qui bricole un visuel lorsqu’on entre la commande plot(diamonds)
, avec ggplot
il ne se passe rien.
Juste un grand carré gris.
Le résultat de plot(diamonds)
.
La commande de base plot(diamonds)
considère toutes les variables du data frame et les croise entre elles - numériques comme ordinales - pour un résultat illisible et inutile.
Qu’obtient-on avec la commande ggplot
?
str(g)
List of 9
$ data :Classes ‘tbl_df’, ‘tbl’ and 'data.frame': 5394 obs. of 10 variables:
..$ carat : num [1:5394] 0.23 0.3 0.23 0.33 0.29 0.3 0.24 0.26 0.32 0.8 ...
..$ cut : Ord.factor w/ 5 levels "Fair"<"Good"<..: 3 3 3 5 3 2 4 3 4 4 ...
..$ color : Ord.factor w/ 7 levels "D"<"E"<"F"<"G"<..: 5 7 3 6 5 6 2 2 6 5 ...
..$ clarity: Ord.factor w/ 8 levels "I1"<"SI2"<"SI1"<..: 5 3 5 2 2 3 7 7 3 3 ...
..$ depth : num [1:5394] 59.4 62.7 60.9 61.8 60.7 63.2 60.7 62.6 62.9 61.5 ...
..$ table : num [1:5394] 61 59 57 55 60 55 58 59 58 58 ...
..$ price : int [1:5394] 338 351 357 403 404 405 553 554 554 2760 ...
..$ x : num [1:5394] 4 4.21 3.96 4.49 4.33 4.25 4.01 4.06 4.35 5.97 ...
..$ y : num [1:5394] 4.05 4.27 3.99 4.51 4.37 4.29 4.03 4.09 4.33 5.93 ...
..$ z : num [1:5394] 2.39 2.66 2.42 2.78 2.64 2.7 2.44 2.55 2.73 3.66 ...
$ layers : list()
$ scales :Classes 'ScalesList', 'ggproto' <ggproto object: Class ScalesList>
add: function
clone: function
find: function
get_scales: function
has_scale: function
input: function
n: function
non_position_scales: function
scales: NULL
super: <ggproto object: Class ScalesList>
$ mapping : list()
$ theme : list()
$ coordinates:Classes 'CoordCartesian', 'Coord', 'ggproto' <ggproto object: Class CoordCartesian, Coord>
aspect: function
distance: function
expand: TRUE
is_linear: function
labels: function
limits: list
range: function
render_axis_h: function
render_axis_v: function
render_bg: function
render_fg: function
train: function
transform: function
super: <ggproto object: Class CoordCartesian, Coord>
$ facet :List of 1
..$ shrink: logi TRUE
..- attr(*, "class")= chr [1:2] "null" "facet"
$ plot_env :<environment: R_GlobalEnv>
$ labels : list()
- attr(*, "class")= chr [1:2] "gg" "ggplot"
On retrouve le data frame contenant nos données, ainsi que les différents éléments propres à l’approche grammaticale des graphiques proposée par ggplot2
, pour l’instant vides :
layers
scales
mapping
theme
coordinates
facet
À ce stade, le graphe est vide car nous n’avons défini ni mapping ni géométries (voir figure suivante).
g <- ggplot(diam)
g
Mapping
Avec un mapping mais sans géométrie
cf. figure suivante.
Pour rappel : ‘carat’ et ‘price’ sont des variables continues.
ggplot(diam, aes(x = carat, y = price))
On remarque que les échelles et les labels des axes sont déjà posés.
ggplot
attend maintenant de savoir quoi dessiner.
Avec une géométrie mais sans mapping
Pas de figure…
ggplot(diam) + geom_point()
Error: geom_point requires the following missing aesthetics: x, y
Cette fois, ggplot n’a pas trouvé de mapping lui indiquant où poser son dessin, d’où l’erreur.
Avec un mapping et une géométrie
ggplot(diam, aes(x = carat, y = price)) + geom_point()
Pour comparaison, la commande la plus simple permettant d’obtenir (à peu près) le même résultat avec le package de base.
plot(diam$carat, diam$price)
Les échelles sont justes et les points sont bien situés, mais c’est tout et c’est moche.
C’est là le principe de ggplot : à partir de maintenant nous pouvons faire varier les éléments graphiques sans avoir à toucher aux données.
Par exemple, une interpolation.
ggplot(diam, aes(x = carat, y = price)) + geom_smooth()
Superposer des layers
Il suffit de les additionner pour les superposer.
Attention au ‘+’ à mettre à la fin de la ligne et pas au début.
ggplot(diam, aes(x = carat, y = price)) +
geom_point() +
geom_smooth()
Attention, l’ordre des géométries a une influence sur le graphique !
ggplot(diam, aes(x = carat, y = price)) +
geom_smooth() +
geom_point()
Dans le graphique précédent, la courbe d’interpolation a été dessinée avant les points.
Ainsi, elle apparaît dans la figure cachée sous ces derniers.
Différents mappings
Le mapping peut se faire à plusieurs endroits :
Dans ggplot()
, ce qui a pour effet d’appliquer le mapping à tous les autres éléments.
Dans les éléments graphiques eux-mêmes.
Tout dans ggplot
ggplot(diam, aes(x = carat, y = price)) + geom_point()
Dans les deux
ggplot(diam, aes(x = carat)) + geom_point(aes(y = price))
Tout dans la géométrie
ggplot(diam) + geom_point(aes(x = carat, y = price))
Bref, on peut mapper de nombreuses variables, en une seule fois, directement dans ggplot
et elles seront reprises par les autres éléments.
Par exemple, l’attribut color
de geom_point
.
Remarque : la variable ‘clarity’ est ordinale.
ggplot(diam, aes(x = carat, y = price, color = clarity)) +
geom_point()
Grâce à ce seul mapping, ggplot
attribue à chaque modalité une couleur, attribue aux points la couleur correspondante, et génère dans la foulée une légende (une des angoisses lorsqu’on travaille avec les commandes de base).
Pour l’anecdote, nous aurions obtenu le même résultat en faisant le mapping dans geom_point
.
ggplot(diam, aes(x = carat, y = price)) +
geom_point(aes(color = clarity))
Attention à ne pas oublier de faire le mapping, c’est-à-dire d’utiliser la fonction aes()
, sinon ça ne fonctionnera pas !
ggplot(diam, aes(x = carat, y = price)) +
geom_point(color = clarity)
Error in layer(data = data, mapping = mapping, stat = stat, geom = GeomPoint, :
object 'clarity' not found
On trouve toutes les géométries disponibles ainsi que de nombreuses autres ressources indispensables dans l’indispensable cheat sheet de ggplot2 !
Variables et transformations, discrètes et continues
C’est hasardeux, mais on peut également appliquer une transformation continue (size
) à une variable ordinale (cut
).
ggplot(diam, aes(x = carat, y = price, color = clarity, size = cut)) +
geom_point()
Toutefois, il est recommandé d’appliquer une transformation continue (size
) à une variable continue (par exemple depth
) et une transformation discrète comme la couleur ou la forme (shape
) à une variable discrète (par exemple cut
).
ggplot(diam, aes(x = carat, y = price, color = clarity, shape = cut)) +
geom_point()
Détail qui a son importance dans l’exemple suivant : la fonction geom_smooth
va hériter du mapping sur la couleur.
Note : “se = FALSE” empêche l’affichage de l’incertitude.
ggplot(diam, aes(x = carat, y = price, color = clarity)) +
geom_point() +
geom_smooth(se = FALSE)
Pour le mapping sur la forme (au lieu de la couleur), on repassera.
ggplot(diam, aes(x = carat, y = price, shape = cut)) +
geom_point() +
geom_smooth(se = FALSE)
Et si l’on prend en considération les deux en même temps, ça peut mener à la catastrophe.
ggplot(diam, aes(x = carat, y = price, color = clarity, shape = cut)) +
geom_point() +
geom_smooth(se = FALSE)
Le même code que précédemment, avec cette fois le graphique à la place du message d’erreur.
ggplot(diam, aes(x = carat, y = price, color = clarity, shape = cut)) +
geom_point() + geom_smooth(se = FALSE)
Il est nécessaire de redistribuer le mapping plus subtilement.
ggplot(diam, aes(x = carat, y = price, color = clarity)) +
geom_point(aes(shape = cut)) +
geom_smooth(se = FALSE)
D’autres exemples concernant les attributs des éléments géométriques.
Transformation continue, variable continue
ggplot(diam, aes(x = carat, y = price, color = clarity, size = depth)) +
geom_point()
Il est possible évidemment de modifier plus subtilement la taille des sommets lorsqu’on fait un mapping dessus, si les valeurs par défaut ne nous plaisent pas.
En général, cela passe par les fonctions scale
.
Elles commencent par scale_
(voir la cheat sheet).
Nous reviendrons plus en détail là-dessus.
Dans la figure suivante, nous donnons des valeurs minimales et maximales pour la taille des sommets.
ggplot(diam, aes(x = carat, y = price, color = clarity, size = depth)) +
geom_point() +
scale_size(range = c(1,3))
Remarque : l’effet est difficile à observer car la variance est très petite.
summary(diam$depth)
Min. 1st Qu. Median Mean 3rd Qu. Max.
55.00 61.00 61.90 61.74 62.50 73.60
sd(diam$depth)
[1] 1.436404
Au passage, remarquons que l’on peut aussi utiliser l’attribut size
de geom_point
sans mapping.
ggplot(diam, aes(x = carat, y = price, color = clarity)) +
geom_point(size = 1)
Dans ce cas, la taille des points est considérée hors mapping et donc indépendamment d’une quelconque variable.
Remarquons également l’organisation hiérarchique des mappings et des transformations : dans l’exemple qui suit, la variable depth
est tout d’abord mappée sur la taille des sommets.
Puis, dans geom_point
, on lui attribue une valeur fixe.
Arrivée ensuite, c’est cette dernière qui l’emporte sur le mapping initial.
ggplot(diam, aes(x = carat, y = price, color = clarity, size = depth)) +
geom_point(size = 1)
Au passage, la modification du titre en légende dépend de la fonction scale
correspondante (ici scale_size
).
C’est logique, mais contre-intuitif pour qui aura passé beaucoup (trop ?) de temps avec les commandes graphiques de base dans R.
ggplot(diam, aes(x = carat, y = price, color = clarity, size = depth)) +
geom_point() +
scale_size("DEPTH", range = c(1,3))
Le facettage
Attention ! Ceci s’applique à des variables discrètes.
Le facettage divise le jeu de données en fonction des catégories d’une variable.
Dans un sens…
ggplot(diam, aes(x = carat, y = price)) +
geom_point() +
facet_grid(. ~ cut)
… et dans l’autre.
(Remarquez la position inversée de la variable cut
dans facet_grid()
.)
ggplot(diam, aes(x = carat, y = price)) +
geom_point() +
facet_grid(cut ~ .)
En croisant deux variables discrètes.
ggplot(diam, aes(x = carat, y = price)) +
geom_point() +
facet_grid(color ~ clarity)
Finalement, en croisant deux variables discrètes, avec un mapping sur la couleur.
ggplot(diam, aes(x = carat, y = price, color = cut)) +
geom_point() +
facet_grid(color ~ clarity)
Il y a une autre option de facettage lorsqu’on n’utilise qu’une seule variable discrète : facet_wrap
.
Dans ce cas, remarquez que nous n’utilisons plus le point (.) avant le tilde (~).
ggplot(diam, aes(x = carat, y = price)) +
geom_point() +
facet_wrap(~ clarity)
Les échelles
Elles commencent toutes par scale_
Ensuite, on complète avec le nom de la variable concernée.
scale_alpha scale_alpha_continuous scale_alpha_discrete scale_alpha_identity scale_alpha_manual scale_color_brewer scale_color_continuous scale_color_discrete scale_color_distiller scale_color_gradient scale_color_gradient2 scale_color_gradientn scale_color_grey scale_color_hue scale_color_identity scale_color_manual scale_colour_brewer scale_colour_continuous scale_colour_date scale_colour_datetime scale_colour_discrete scale_colour_distiller scale_colour_gradient scale_colour_gradient2 scale_colour_gradientn scale_colour_grey scale_colour_hue scale_colour_identity scale_colour_manual scale_continuous scale_date scale_fill_brewer scale_fill_continuous scale_fill_date scale_fill_datetime scale_fill_discrete scale_fill_distiller scale_fill_gradient scale_fill_gradient2 scale_fill_gradientn scale_fill_grey scale_fill_hue scale_fill_identity scale_fill_manual scale_identity scale_linetype scale_linetype_continuous scale_linetype_discrete scale_linetype_identity scale_linetype_manual scale_manual scale_radius scale_shape scale_shape_continuous scale_shape_discrete scale_shape_identity scale_shape_manual scale_size scale_size_area scale_size_continuous scale_size_date scale_size_datetime scale_size_discrete scale_size_identity scale_size_manual scale_x_continuous scale_x_date scale_x_datetime scale_x_discrete scale_x_log10 scale_x_reverse scale_x_sqrt scale_y_continuous scale_y_date scale_y_datetime scale_y_discrete scale_y_log10 scale_y_reverse scale_y_sqrt
Par exemple, si l’on travaille sur la couleur, on pourra faire varier la palette des couleurs en modifiant le nom de l’échelle. Faites le test en écrivant scale_color_
dans la console puis en pressant sur la touche tab pour voir les suggestions…
En gris
ggplot(diam, aes(x = carat, y = price, color = clarity)) +
geom_point() +
scale_color_grey()
La version par défaut.
ggplot(diam, aes(x = carat, y = price, color = clarity)) +
geom_point() +
scale_color_discrete()
ColorBrewer, nommée d’après une de ses auteurs, Cnythia Brewer, est une librairie de couleurs précalculées qui s’accordent bien.
On peut les utiliser ici avec la fonction scale_color_brewer
.
ggplot(diam, aes(x = carat, y = price, color = clarity)) +
geom_point() +
scale_color_brewer()
En changeant de palette.
ggplot(diam, aes(x = carat, y = price, color = clarity)) +
geom_point() +
scale_color_brewer(palette = 2)
Attention si vous utilisez la mauvaise échelle : soit il ne se passera rien (comme dans la figure suivante), soit il y aura un message d’erreur.
ggplot(diam, aes(x = carat, y = price, color = clarity)) +
geom_point() +
scale_fill_brewer(palette = 2)
Dans la figure suivante, nous changeons la forme utilisée pour dessiner les points afin qu’il y ait un pourtour (color
) et un contenu (fill
).
Cette transformation a été effectuée en donnant comme instruction que les points doivent changer de forme (indépendamment de toute variable).
Nous en profitons pour dessiner l’intérieur. Quelle fonction faut-il utiliser ?
ggplot(diam, aes(x = carat, y = price, fill = clarity)) +
geom_point(shape = 21) +
scale_fill_brewer(palette = 2)
Mais les échelles, ça ne concerne pas seulement l’intérieur du graphique.
Nous utilisons également des échelles sur les axes.
ggplot(diam, aes(x = carat, y = price, color = clarity)) +
geom_point() +
scale_x_continuous(breaks = c(1,3), minor_breaks = c(sqrt(2), pi)) +
scale_y_continuous(breaks = sample(20000, 10))
Échelle logarithmique FTW.
ggplot(diam, aes(x = carat, y = price, color = clarity)) +
geom_point() +
scale_y_log10()
Les annotations
Via un mapping, par exemple pour un MDS.
ggplot(diam, aes(x = carat, y = price, label = clarity)) +
geom_text()
L’annotation manuelle est possible, à l’ancienne, mais pas forcément recommandée.
ggplot(diam, aes(x = carat, y = price, color = clarity)) +
geom_point() +
annotate("text", x = 3.5, y = 10000, label = "HELLO")
D’autres types de graphiques
C’est la seule fois que nous voyons une transformation statistique dans cette présentation (malheureusement).
Ce sont les fonctions commençant par stat_
.
Pour plus d’infos, voir la cheat sheet.
ggplot(diam, aes(price)) +
geom_area(stat = "bin")
ggplot(diam, aes(price)) +
geom_density(kernel = "gaussian")
ggplot(diam, aes(price)) +
geom_histogram(binwidth = 30)
Avec une variable discrète, cette fois.
ggplot(diam, aes(color)) +
geom_bar()
Une variable discrète et une variable continue.
ggplot(diam, aes(x = color, y = price)) +
geom_boxplot()
Deux variables discrètes.
ggplot(diam, aes(x = cut, y = color)) +
geom_count()
ggplot(diamonds, aes(x=price, fill=cut)) +
geom_histogram()
Distributions bi-variées.
ggplot(diamonds, aes(carat, price)) +
geom_bin2d(binwidth = c(0.25, 500))
ggplot(diamonds, aes(carat, price)) +
geom_hex()
Les thèmes
La fonction ggtitle
est utilisée pour choisir un titre.
ggplot(diam, aes(x = carat, y = price, color = clarity)) +
geom_point() +
ggtitle("Mon joli graphique")
Et, classique pour une fois, xlab
et ylab
pour changer les noms des axes.
ggplot(diam, aes(x = carat, y = price, color = clarity)) +
geom_point() +
xlab("Ma jolie abscisse") +
ylab("Ma jolie ordonnée")
Pour varier les thèmes : theme_
.
ggplot(diam, aes(x = carat, y = price, color = clarity)) +
geom_point() +
theme_bw()
ggplot(diam, aes(x = carat, y = price, color = clarity)) +
geom_point() +
theme_dark()
ggplot(diam, aes(x = carat, y = price, color = clarity)) +
geom_point() +
theme_minimal()
Sauver un graphique
ggsave
function (filename, plot = last_plot(), device = NULL, path = NULL,
scale = 1, width = NA, height = NA, units = c("in", "cm",
"mm"), dpi = 300, limitsize = TRUE, ...)
{
dev <- plot_dev(device, filename, dpi = dpi)
dim <- plot_dim(c(width, height), scale = scale, units = units,
limitsize = limitsize)
if (!is.null(path)) {
filename <- file.path(path, filename)
}
dev(file = filename, width = dim[1], height = dim[2], ...)
on.exit(utils::capture.output(grDevices::dev.off()))
grid.draw(plot)
invisible()
}
<environment: namespace:ggplot2>
Par exemple
ggsave("plot.pdf", width = 7, height = 7)
Bien préparer ses données
«Tidy data is a standard way of mapping the meaning of a dataset to its structure.»
«A dataset is messy or tidy depending on how rows, columns and tables are matched up with observations, variables and types.»
«In tidy data:
- Each variable forms a column.
- Each observation forms a row.
- Each type of observational unit forms a table.»
Source :
# library(tidyr)
# vignette("tidy-data")
Marie-Louise Timcke a proposé sur journocode.com
une très bonne ressource à ce sujet.
En particulier, elle présente l’exemple suivant, extrait d’explications d’Hadley Wickham.
Le format tidy, ou long form, est fortement recommandé dans ggplot2
.
En un schéma, Frederik Aust a très bien résumé les opérations nécessaires pour la mise en forme des données.
Réaliser des cartes
data <- data.frame(murder = USArrests$Murder, state = tolower(rownames(USArrests)))
map <- map_data("state")
k <- ggplot(data, aes(fill = murder))
k <- k +
geom_map(aes(map_id = state), map = map) +
expand_limits(x = map$long, y = map$lat)
k
gganimate
# devtools::install_github("dgrtwo/gganimate")
# install.packages("gapminder")
library(gganimate)
library(gapminder)
theme_set(theme_bw())
p <- ggplot(gapminder, aes(gdpPercap, lifeExp, size = pop, color = continent, frame = year)) +
geom_point() +
scale_x_log10()
(Si ça ne fonctionne, lancer ce code dans R/RStudio.)
gg_animate(p)
Interactivité
Un exemple permettant la consultation de données de films tirées de Rotten Tomatoes.
Un autre exemple, cette fois sur la base de données des diamants utilisée dans ce notebook/ces slides.
Le mot-clé à retenir lorsqu’on veut rendre un graphique de R interactif : shiny
. Mais ce sera pour un autre workshop…
LS0tCnRpdGxlOiAiSW50cm9kdWN0aW9uIMOgIGdncGxvdDIiCmF1dGhvcjogIllhbm5pY2sgUm9jaGF0IgpkYXRlOiAiMiBub3ZlbWJyZSAyMDE2IgpvdXRwdXQ6CiAgaW9zbGlkZXNfcHJlc2VudGF0aW9uOiBkZWZhdWx0CiAgaHRtbF9ub3RlYm9vazogZGVmYXVsdAogIGJlYW1lcl9wcmVzZW50YXRpb246IGRlZmF1bHQKICBzbGlkeV9wcmVzZW50YXRpb246IGRlZmF1bHQKLS0tCgojIyBTZXR1cC4gCgpTaSBsJ3VuIGRlIGNlcyBwYWNrYWdlcyBuJ2EgcGFzIGVuY29yZSDDqXTDqSBpbnN0YWxsw6kgc3VyIHZvdHJlIG1hY2hpbmUsIG91IHF1ZSB2b3VzIGVuIHBvc3PDqWRleiB1bmUgdmVyc2lvbiBhbmNpZW5uZSwgaWwgZmF1dCBlbnRyZXIgbGEgY29tbWFuZGUgY29ycmVzcG9uZGFudGUgYXByw6hzIGwnYXZvaXIgZMOpLWNvbW1lbnTDqWUuCgpgYGB7cn0KIyBPYmxpZ2F0b2lyZQojIGluc3RhbGwucGFja2FnZXMoImdncGxvdDIiKQoKIyBGYWN1bHRhdGlmCiMgaW5zdGFsbC5wYWNrYWdlcygiUkNvbG9yQnJld2VyIikKIyBpbnN0YWxsLnBhY2thZ2VzKCJoZXhiaW4iKQojIGluc3RhbGwucGFja2FnZXMoIm1hcHMiKQpgYGAKCi0tLS0KCkRhbnMgdG91cyBsZXMgY2FzLCBpbCBlc3QgbsOpY2Vzc2FpcmUgYXUgbGFuY2VtZW50IGRlIGNoYXJnZXIgbGUgb3UgbGVzIHBhY2thZ2VzLgoKYGBge3J9CmxpYnJhcnkoZ2dwbG90MikKCmxpYnJhcnkoUkNvbG9yQnJld2VyKQpsaWJyYXJ5KGhleGJpbikKbGlicmFyeShtYXBzKQpgYGAKCmBgYHtyLCBlY2hvID0gRkFMU0V9CiMgSW5kaXNwZW5zYWJsZSBwb3VyIGV4cG9ydGVyIGxlIG5vdGVib29rL2xlcyBzbGlkZXMgc2FucyBtZXNzYWdlIGQnZXJyZXVyICE/CmxpYnJhcnkoa25pdHIpCm9wdHNfa25pdCRzZXQocm9vdC5kaXIgPSAnLycpCmBgYAoKCiMjIFVuIGpldSBkZSBkb25uw6llcyBpbnRlcm5lIMOgIGdncGxvdDIKCk5vdXMgdXRpbGlzb25zIHVuIGpldSBkZSBkb25uw6llcyBpbnRlcm5lIMOgIGdncGxvdDIgY29udGVuYW50IGxlIHByaXggYWluc2kgcXVlIGQnYXV0cmVzIGF0dHJpYnV0cyBkZSA1MyA5NDAgZGlhbWFudHMuCgpMZXMgY29tbWFuZGVzIHN1aXZhbnRlcyBwZXJtZXR0ZW50IGRlIHByZW5kcmUgY29ubmFpc3NhbmNlIGR1IGpldSBkZSBkb25uw6llcy4KCmBgYHtyfQojIGhlbHAoZGlhbW9uZHMpCgojIFZpZXcoZGlhbW9uZHMpCmBgYAoKLS0tLQoKYGBge3J9CmhlYWQoZGlhbW9uZHMpCmBgYAoKLS0tLQoKYGBge3J9CnN0cihkaWFtb25kcykKYGBgCgotLS0tCgpPbiBuZSB2YSBnYXJkZXIgcXUndW5lIGVudHLDqWUgc3VyIGRpeCBkZSBjZSBqZXUgZGUgZG9ubsOpZXMgYWZpbiBkZSByZW5kcmUgbGVzIGNhbGN1bHMgcGx1cyByYXBpZGVzIGV0IGxlcyBncmFwaGlxdWVzIG1vaW5zIGNoYXJnw6lzLgoKYGBge3J9CmRpYW0gPC0gZGlhbW9uZHNbc2VxKDEwLCBucm93KGRpYW1vbmRzKSwgMTApLF0KYGBgCgojIyBDaGFyZ2VyIGxlcyBkb25uw6llcyBkYW5zIGdncGxvdAoKKipSZW1hcnF1ZSoqIDogw6AgcGFydGlyIGQnaWNpLCBub3VzIHBhcmxvbnMgcHJpbmNpcGFsZW1lbnQgZGUgZ2dwbG90IHNhbnMgbGUgIjIiIGNhciBpbCB2YSBzJ2FnaXIgZGUgbGEgZm9uY3Rpb24gImdncGxvdCIuIAoKTGUgIjIiIG4nZXN0IHV0aWxpc8OpIHF1ZSBkYW5zIGxlIG5vbSBkdSBwYWNrYWdlLiAKCkV0IG91aSwgZ2dwbG90MSBbZXhpc3RlXShodHRwczovL2dpdGh1Yi5jb20vaGFkbGV5L2dncGxvdDEpLgoKLS0tLQoKIyMjIE9uIGNoYXJnZSBsZXMgZG9ubsOpZXMKCipBdHRlbnRpb24gISBMJ2lucHV0IGRvaXQgdG91am91cnMgw6p0cmUgdW4gZGF0YSBmcmFtZSAhKgoKYGBge3J9CmcgPC0gZ2dwbG90KGRpYW0pCmBgYAoKLS0tLQoKYGBge3J9CmcKYGBgCgotLS0tCgpDb250cmFpcmVtZW50IMOgIGxhIGZvbmN0aW9uIGBwbG90YCwgcXVpIGJyaWNvbGUgdW4gdmlzdWVsIGxvcnNxdSdvbiBlbnRyZSBsYSBjb21tYW5kZSBgcGxvdChkaWFtb25kcylgLCBhdmVjIGBnZ3Bsb3RgIGlsIG5lIHNlIHBhc3NlIHJpZW4uCgpKdXN0ZSB1biBncmFuZCBjYXJyw6kgZ3Jpcy4KCi0tLS0KCkxlIHLDqXN1bHRhdCBkZSBgcGxvdChkaWFtb25kcylgLgoKIVtdKFJwbG90MDAxLnBuZykKCi0tLS0KCkxhIGNvbW1hbmRlIGRlIGJhc2UgYHBsb3QoZGlhbW9uZHMpYCBjb25zaWTDqHJlIHRvdXRlcyBsZXMgdmFyaWFibGVzIGR1IGRhdGEgZnJhbWUgZXQgbGVzIGNyb2lzZSBlbnRyZSBlbGxlcyAtIG51bcOpcmlxdWVzIGNvbW1lIG9yZGluYWxlcyAtIHBvdXIgdW4gcsOpc3VsdGF0IGlsbGlzaWJsZSBldCBpbnV0aWxlLgoKUXUnb2J0aWVudC1vbiBhdmVjIGxhIGNvbW1hbmRlIGBnZ3Bsb3RgID8KCi0tLS0KCmBgYHtyfQpzdHIoZykKYGBgCgotLS0tCgpPbiByZXRyb3V2ZSBsZSBkYXRhIGZyYW1lIGNvbnRlbmFudCBub3MgZG9ubsOpZXMsIGFpbnNpIHF1ZSBsZXMgZGlmZsOpcmVudHMgw6lsw6ltZW50cyBwcm9wcmVzIMOgIGwnYXBwcm9jaGUgKmdyYW1tYXRpY2FsZSogZGVzIGdyYXBoaXF1ZXMgcHJvcG9zw6llIHBhciBgZ2dwbG90MmAsIHBvdXIgbCdpbnN0YW50IHZpZGVzIDoKCiogbGF5ZXJzCgoqIHNjYWxlcwoKKiBtYXBwaW5nCgoqIHRoZW1lCgoqIGNvb3JkaW5hdGVzCgoqIGZhY2V0CgotLS0tCgrDgCBjZSBzdGFkZSwgbGUgZ3JhcGhlIGVzdCB2aWRlIGNhciBub3VzIG4nYXZvbnMgZMOpZmluaSBuaSBtYXBwaW5nIG5pIGfDqW9tw6l0cmllcyAodm9pciBmaWd1cmUgc3VpdmFudGUpLgoKLS0tLQoKYGBge3J9CmcgPC0gZ2dwbG90KGRpYW0pCmcKYGBgCgojIyBNYXBwaW5nCgojIyMgQXZlYyB1biBtYXBwaW5nIG1haXMgc2FucyBnw6lvbcOpdHJpZQoKY2YuIGZpZ3VyZSBzdWl2YW50ZS4KClBvdXIgcmFwcGVsIDogJ2NhcmF0JyBldCAncHJpY2UnIHNvbnQgZGVzIHZhcmlhYmxlcyBjb250aW51ZXMuCgotLS0tCgpgYGB7cn0KZ2dwbG90KGRpYW0sIGFlcyh4ID0gY2FyYXQsIHkgPSBwcmljZSkpIApgYGAKCi0tLS0KCk9uIHJlbWFycXVlIHF1ZSBsZXMgw6ljaGVsbGVzIGV0IGxlcyBsYWJlbHMgZGVzIGF4ZXMgc29udCBkw6lqw6AgcG9zw6lzLiAKCmBnZ3Bsb3RgIGF0dGVuZCBtYWludGVuYW50IGRlIHNhdm9pciBxdW9pIGRlc3NpbmVyLgoKLS0tLQoKIyMjIEF2ZWMgdW5lIGfDqW9tw6l0cmllIG1haXMgc2FucyBtYXBwaW5nCgpQYXMgZGUgZmlndXJlLi4uCgpgYGB7ciwgZXJyb3IgPSBUUlVFfQpnZ3Bsb3QoZGlhbSkgKyBnZW9tX3BvaW50KCkKYGBgCgotLS0tCgpDZXR0ZSBmb2lzLCBnZ3Bsb3QgbidhIHBhcyB0cm91dsOpIGRlIG1hcHBpbmcgbHVpIGluZGlxdWFudCBvw7kgcG9zZXIgc29uIGRlc3NpbiwgZCdvw7kgbCdlcnJldXIuCgotLS0tCgojIyMgQXZlYyB1biBtYXBwaW5nIGV0IHVuZSBnw6lvbcOpdHJpZQoKYGBge3J9CmdncGxvdChkaWFtLCBhZXMoeCA9IGNhcmF0LCB5ID0gcHJpY2UpKSArIGdlb21fcG9pbnQoKQpgYGAKCi0tLS0KClBvdXIgY29tcGFyYWlzb24sIGxhIGNvbW1hbmRlIGxhIHBsdXMgc2ltcGxlIHBlcm1ldHRhbnQgZCdvYnRlbmlyICjDoCBwZXUgcHLDqHMpIGxlIG3Dqm1lIHLDqXN1bHRhdCBhdmVjIGxlIHBhY2thZ2UgZGUgYmFzZS4KCmBgYHtyfQpwbG90KGRpYW0kY2FyYXQsIGRpYW0kcHJpY2UpCmBgYAoKLS0tLQoKTGVzIMOpY2hlbGxlcyBzb250IGp1c3RlcyBldCBsZXMgcG9pbnRzIHNvbnQgYmllbiBzaXR1w6lzLCBtYWlzIGMnZXN0IHRvdXQgZXQgYydlc3QgbW9jaGUuCgotLS0tCgpDJ2VzdCBsw6AgbGUgcHJpbmNpcGUgZGUgZ2dwbG90IDogw6AgcGFydGlyIGRlIG1haW50ZW5hbnQgbm91cyBwb3V2b25zIGZhaXJlIHZhcmllciBsZXMgw6lsw6ltZW50cyBncmFwaGlxdWVzIHNhbnMgYXZvaXIgw6AgdG91Y2hlciBhdXggZG9ubsOpZXMuCgpQYXIgZXhlbXBsZSwgdW5lIGludGVycG9sYXRpb24uCgotLS0tCgpgYGB7cn0KZ2dwbG90KGRpYW0sIGFlcyh4ID0gY2FyYXQsIHkgPSBwcmljZSkpICsgZ2VvbV9zbW9vdGgoKQpgYGAKCiMjIFN1cGVycG9zZXIgZGVzIGxheWVycwoKSWwgc3VmZml0IGRlIGxlcyBhZGRpdGlvbm5lciBwb3VyIGxlcyBzdXBlcnBvc2VyLgoKKipBdHRlbnRpb24qKiBhdSAnKycgw6AgbWV0dHJlIMOgIGxhIGZpbiBkZSBsYSBsaWduZSBldCBwYXMgYXUgZMOpYnV0LgoKLS0tLQoKYGBge3J9CmdncGxvdChkaWFtLCBhZXMoeCA9IGNhcmF0LCB5ID0gcHJpY2UpKSArIAogIGdlb21fcG9pbnQoKSArIAogIGdlb21fc21vb3RoKCkKYGBgCgotLS0tCgpBdHRlbnRpb24sIGwnb3JkcmUgZGVzIGfDqW9tw6l0cmllcyBhIHVuZSBpbmZsdWVuY2Ugc3VyIGxlIGdyYXBoaXF1ZSAhCgotLS0tCgpgYGB7cn0KZ2dwbG90KGRpYW0sIGFlcyh4ID0gY2FyYXQsIHkgPSBwcmljZSkpICsgCiAgZ2VvbV9zbW9vdGgoKSArIAogIGdlb21fcG9pbnQoKQpgYGAKCi0tLS0KCkRhbnMgbGUgZ3JhcGhpcXVlIHByw6ljw6lkZW50LCBsYSBjb3VyYmUgZCdpbnRlcnBvbGF0aW9uIGEgw6l0w6kgZGVzc2luw6llIGF2YW50IGxlcyBwb2ludHMuCgpBaW5zaSwgZWxsZSBhcHBhcmHDrnQgZGFucyBsYSBmaWd1cmUgY2FjaMOpZSBzb3VzIGNlcyBkZXJuaWVycy4KCiMjIERpZmbDqXJlbnRzIG1hcHBpbmdzCgpMZSBtYXBwaW5nIHBldXQgc2UgZmFpcmUgw6AgcGx1c2lldXJzIGVuZHJvaXRzIDogCgoqIERhbnMgYGdncGxvdCgpYCwgY2UgcXVpIGEgcG91ciBlZmZldCBkJ2FwcGxpcXVlciBsZSBtYXBwaW5nIMOgIHRvdXMgbGVzIGF1dHJlcyDDqWzDqW1lbnRzLgoKKiBEYW5zIGxlcyDDqWzDqW1lbnRzIGdyYXBoaXF1ZXMgZXV4LW3Dqm1lcy4KCi0tLS0KCiMjIyBUb3V0IGRhbnMgZ2dwbG90CgpgYGB7cn0KZ2dwbG90KGRpYW0sIGFlcyh4ID0gY2FyYXQsIHkgPSBwcmljZSkpICsgZ2VvbV9wb2ludCgpCmBgYAoKLS0tLQoKIyMjIERhbnMgbGVzIGRldXgKCmBgYHtyfQpnZ3Bsb3QoZGlhbSwgYWVzKHggPSBjYXJhdCkpICsgZ2VvbV9wb2ludChhZXMoeSA9IHByaWNlKSkKYGBgCgotLS0tCgojIyMgVG91dCBkYW5zIGxhIGfDqW9tw6l0cmllCgpgYGB7cn0KZ2dwbG90KGRpYW0pICsgZ2VvbV9wb2ludChhZXMoeCA9IGNhcmF0LCB5ID0gcHJpY2UpKQpgYGAKCi0tLS0KCkJyZWYsIG9uIHBldXQgbWFwcGVyIGRlIG5vbWJyZXVzZXMgdmFyaWFibGVzLCBlbiB1bmUgc2V1bGUgZm9pcywgZGlyZWN0ZW1lbnQgZGFucyBgZ2dwbG90YCBldCBlbGxlcyBzZXJvbnQgcmVwcmlzZXMgcGFyIGxlcyBhdXRyZXMgw6lsw6ltZW50cy4gCgpQYXIgZXhlbXBsZSwgbCdhdHRyaWJ1dCBgY29sb3JgIGRlIGBnZW9tX3BvaW50YC4gCgoqUmVtYXJxdWUqIDogbGEgdmFyaWFibGUgJ2NsYXJpdHknIGVzdCBvcmRpbmFsZS4KCi0tLS0KCmBgYHtyfQpnZ3Bsb3QoZGlhbSwgYWVzKHggPSBjYXJhdCwgeSA9IHByaWNlLCBjb2xvciA9IGNsYXJpdHkpKSArIAogIGdlb21fcG9pbnQoKQpgYGAKCi0tLS0KCkdyw6JjZSDDoCBjZSBzZXVsIG1hcHBpbmcsIGBnZ3Bsb3RgIGF0dHJpYnVlIMOgIGNoYXF1ZSBtb2RhbGl0w6kgdW5lIGNvdWxldXIsIGF0dHJpYnVlIGF1eCBwb2ludHMgbGEgY291bGV1ciBjb3JyZXNwb25kYW50ZSwgZXQgZ8OpbsOocmUgZGFucyBsYSBmb3Vsw6llIHVuZSBsw6lnZW5kZSAodW5lIGRlcyBhbmdvaXNzZXMgbG9yc3F1J29uIHRyYXZhaWxsZSBhdmVjIGxlcyBjb21tYW5kZXMgZGUgYmFzZSkuCgpQb3VyIGwnYW5lY2RvdGUsIG5vdXMgYXVyaW9ucyBvYnRlbnUgbGUgbcOqbWUgcsOpc3VsdGF0IGVuIGZhaXNhbnQgbGUgbWFwcGluZyBkYW5zIGBnZW9tX3BvaW50YC4KCi0tLS0KCmBgYHtyfQpnZ3Bsb3QoZGlhbSwgYWVzKHggPSBjYXJhdCwgeSA9IHByaWNlKSkgKyAKICBnZW9tX3BvaW50KGFlcyhjb2xvciA9IGNsYXJpdHkpKQpgYGAKCi0tLS0KCkF0dGVudGlvbiDDoCBuZSBwYXMgb3VibGllciBkZSBmYWlyZSBsZSBtYXBwaW5nLCBjJ2VzdC3DoC1kaXJlIGQndXRpbGlzZXIgbGEgZm9uY3Rpb24gYGFlcygpYCwgc2lub24gw6dhIG5lIGZvbmN0aW9ubmVyYSBwYXMgIQoKYGBge3IsIGVycm9yID0gVFJVRX0KZ2dwbG90KGRpYW0sIGFlcyh4ID0gY2FyYXQsIHkgPSBwcmljZSkpICsgCiAgZ2VvbV9wb2ludChjb2xvciA9IGNsYXJpdHkpCmBgYAoKLS0tLQoKT24gdHJvdXZlIHRvdXRlcyBsZXMgZ8Opb23DqXRyaWVzIGRpc3BvbmlibGVzIGFpbnNpIHF1ZSBkZSBub21icmV1c2VzIGF1dHJlcyByZXNzb3VyY2VzIGluZGlzcGVuc2FibGVzIGRhbnMgbCcqKmluZGlzcGVuc2FibGUqKiBbY2hlYXQgc2hlZXQgZGUgZ2dwbG90Ml0oaHR0cHM6Ly93d3cucnN0dWRpby5jb20vd3AtY29udGVudC91cGxvYWRzLzIwMTUvMDMvZ2dwbG90Mi1jaGVhdHNoZWV0LnBkZikgIQoKLS0tLQoKIyMjIFZhcmlhYmxlcyBldCB0cmFuc2Zvcm1hdGlvbnMsIGRpc2Nyw6h0ZXMgZXQgY29udGludWVzCgpDJ2VzdCBoYXNhcmRldXgsIG1haXMgb24gcGV1dCDDqWdhbGVtZW50IGFwcGxpcXVlciB1bmUgdHJhbnNmb3JtYXRpb24gY29udGludWUgKGBzaXplYCkgw6AgdW5lIHZhcmlhYmxlIG9yZGluYWxlIChgY3V0YCkuCgotLS0tCgpgYGB7cn0KZ2dwbG90KGRpYW0sIGFlcyh4ID0gY2FyYXQsIHkgPSBwcmljZSwgY29sb3IgPSBjbGFyaXR5LCBzaXplID0gY3V0KSkgKyAKICBnZW9tX3BvaW50KCkKYGBgCgotLS0tCgpUb3V0ZWZvaXMsIGlsIGVzdCByZWNvbW1hbmTDqSBkJ2FwcGxpcXVlciB1bmUgdHJhbnNmb3JtYXRpb24gY29udGludWUgKGBzaXplYCkgw6AgdW5lIHZhcmlhYmxlIGNvbnRpbnVlIChwYXIgZXhlbXBsZSBgZGVwdGhgKSBldCB1bmUgdHJhbnNmb3JtYXRpb24gZGlzY3LDqHRlIGNvbW1lIGxhIGNvdWxldXIgb3UgbGEgZm9ybWUgKGBzaGFwZWApIMOgIHVuZSB2YXJpYWJsZSBkaXNjcsOodGUgKHBhciBleGVtcGxlIGBjdXRgKS4KCi0tLS0KCmBgYHtyfQpnZ3Bsb3QoZGlhbSwgYWVzKHggPSBjYXJhdCwgeSA9IHByaWNlLCBjb2xvciA9IGNsYXJpdHksIHNoYXBlID0gY3V0KSkgKyAKICBnZW9tX3BvaW50KCkKYGBgCgotLS0tCgpEw6l0YWlsIHF1aSBhIHNvbiBpbXBvcnRhbmNlIGRhbnMgbCdleGVtcGxlIHN1aXZhbnQgOiBsYSBmb25jdGlvbiBgZ2VvbV9zbW9vdGhgIHZhIGjDqXJpdGVyIGR1IG1hcHBpbmcgc3VyIGxhIGNvdWxldXIuCgoqTm90ZSogOiAic2UgPSBGQUxTRSIgZW1ww6pjaGUgbCdhZmZpY2hhZ2UgZGUgbCdpbmNlcnRpdHVkZS4KCi0tLS0KCmBgYHtyfQpnZ3Bsb3QoZGlhbSwgYWVzKHggPSBjYXJhdCwgeSA9IHByaWNlLCBjb2xvciA9IGNsYXJpdHkpKSArIAogIGdlb21fcG9pbnQoKSArIAogIGdlb21fc21vb3RoKHNlID0gRkFMU0UpCmBgYAoKLS0tLQoKUG91ciBsZSBtYXBwaW5nIHN1ciBsYSBmb3JtZSAoYXUgbGlldSBkZSBsYSBjb3VsZXVyKSwgb24gcmVwYXNzZXJhLgoKLS0tLQoKYGBge3J9CmdncGxvdChkaWFtLCBhZXMoeCA9IGNhcmF0LCB5ID0gcHJpY2UsIHNoYXBlID0gY3V0KSkgKyAKICBnZW9tX3BvaW50KCkgKyAKICBnZW9tX3Ntb290aChzZSA9IEZBTFNFKQpgYGAKCi0tLS0KCkV0IHNpIGwnb24gcHJlbmQgZW4gY29uc2lkw6lyYXRpb24gbGVzIGRldXggZW4gbcOqbWUgdGVtcHMsIMOnYSBwZXV0IG1lbmVyIMOgIGxhIGNhdGFzdHJvcGhlLgoKLS0tLQoKYGBge3J9CmdncGxvdChkaWFtLCBhZXMoeCA9IGNhcmF0LCB5ID0gcHJpY2UsIGNvbG9yID0gY2xhcml0eSwgc2hhcGUgPSBjdXQpKSArIAogIGdlb21fcG9pbnQoKSArIAogIGdlb21fc21vb3RoKHNlID0gRkFMU0UpCmBgYAoKLS0tLQoKTGUgbcOqbWUgY29kZSBxdWUgcHLDqWPDqWRlbW1lbnQsIGF2ZWMgY2V0dGUgZm9pcyBsZSBncmFwaGlxdWUgw6AgbGEgcGxhY2UgZHUgbWVzc2FnZSBkJ2VycmV1ci4KCmBgYHtyLCB3YXJuaW5nID0gRkFMU0V9CmdncGxvdChkaWFtLCBhZXMoeCA9IGNhcmF0LCB5ID0gcHJpY2UsIGNvbG9yID0gY2xhcml0eSwgc2hhcGUgPSBjdXQpKSArIAogIGdlb21fcG9pbnQoKSArIGdlb21fc21vb3RoKHNlID0gRkFMU0UpCmBgYAoKLS0tLQoKSWwgZXN0IG7DqWNlc3NhaXJlIGRlIHJlZGlzdHJpYnVlciBsZSBtYXBwaW5nIHBsdXMgc3VidGlsZW1lbnQuCgotLS0tCgpgYGB7cn0KZ2dwbG90KGRpYW0sIGFlcyh4ID0gY2FyYXQsIHkgPSBwcmljZSwgY29sb3IgPSBjbGFyaXR5KSkgKyAKICBnZW9tX3BvaW50KGFlcyhzaGFwZSA9IGN1dCkpICsgCiAgZ2VvbV9zbW9vdGgoc2UgPSBGQUxTRSkKYGBgCgotLS0tCgpEJ2F1dHJlcyBleGVtcGxlcyBjb25jZXJuYW50IGxlcyBhdHRyaWJ1dHMgZGVzIMOpbMOpbWVudHMgZ8Opb23DqXRyaXF1ZXMuCgotLS0tCgojIyMgVHJhbnNmb3JtYXRpb24gY29udGludWUsIHZhcmlhYmxlIGNvbnRpbnVlCgpgYGB7cn0KZ2dwbG90KGRpYW0sIGFlcyh4ID0gY2FyYXQsIHkgPSBwcmljZSwgY29sb3IgPSBjbGFyaXR5LCBzaXplID0gZGVwdGgpKSArIAogIGdlb21fcG9pbnQoKQpgYGAKCi0tLS0KCklsIGVzdCBwb3NzaWJsZSDDqXZpZGVtbWVudCBkZSBtb2RpZmllciBwbHVzIHN1YnRpbGVtZW50IGxhIHRhaWxsZSBkZXMgc29tbWV0cyBsb3JzcXUnb24gZmFpdCB1biBtYXBwaW5nIGRlc3N1cywgc2kgbGVzIHZhbGV1cnMgcGFyIGTDqWZhdXQgbmUgbm91cyBwbGFpc2VudCBwYXMuIAoKRW4gZ8OpbsOpcmFsLCBjZWxhIHBhc3NlIHBhciBsZXMgZm9uY3Rpb25zIGBzY2FsZWAuIAoKRWxsZXMgY29tbWVuY2VudCBwYXIgYHNjYWxlX2AgKHZvaXIgbGEgY2hlYXQgc2hlZXQpLiAKCk5vdXMgcmV2aWVuZHJvbnMgcGx1cyBlbiBkw6l0YWlsIGzDoC1kZXNzdXMuCgpEYW5zIGxhIGZpZ3VyZSBzdWl2YW50ZSwgbm91cyBkb25ub25zIGRlcyB2YWxldXJzIG1pbmltYWxlcyBldCBtYXhpbWFsZXMgcG91ciBsYSB0YWlsbGUgZGVzIHNvbW1ldHMuCgotLS0tCgpgYGB7cn0KZ2dwbG90KGRpYW0sIGFlcyh4ID0gY2FyYXQsIHkgPSBwcmljZSwgY29sb3IgPSBjbGFyaXR5LCBzaXplID0gZGVwdGgpKSArIAogIGdlb21fcG9pbnQoKSArIAogIHNjYWxlX3NpemUocmFuZ2UgPSBjKDEsMykpCmBgYAoKLS0tLQoKKlJlbWFycXVlKiA6IGwnZWZmZXQgZXN0IGRpZmZpY2lsZSDDoCBvYnNlcnZlciBjYXIgbGEgdmFyaWFuY2UgZXN0IHRyw6hzIHBldGl0ZS4KCmBgYHtyfQpzdW1tYXJ5KGRpYW0kZGVwdGgpCnNkKGRpYW0kZGVwdGgpCmBgYAoKLS0tLQoKQXUgcGFzc2FnZSwgcmVtYXJxdW9ucyBxdWUgbCdvbiBwZXV0IGF1c3NpIHV0aWxpc2VyIGwnYXR0cmlidXQgYHNpemVgIGRlIGBnZW9tX3BvaW50YCBzYW5zIG1hcHBpbmcuCgotLS0tCgpgYGB7cn0KZ2dwbG90KGRpYW0sIGFlcyh4ID0gY2FyYXQsIHkgPSBwcmljZSwgY29sb3IgPSBjbGFyaXR5KSkgKyAKICBnZW9tX3BvaW50KHNpemUgPSAxKQpgYGAKCi0tLS0KCkRhbnMgY2UgY2FzLCBsYSB0YWlsbGUgZGVzIHBvaW50cyBlc3QgY29uc2lkw6lyw6llIGhvcnMgbWFwcGluZyBldCBkb25jIGluZMOpcGVuZGFtbWVudCBkJ3VuZSBxdWVsY29ucXVlIHZhcmlhYmxlLgoKLS0tLQoKUmVtYXJxdW9ucyDDqWdhbGVtZW50IGwnb3JnYW5pc2F0aW9uIGhpw6lyYXJjaGlxdWUgZGVzIG1hcHBpbmdzIGV0IGRlcyB0cmFuc2Zvcm1hdGlvbnMgOiBkYW5zIGwnZXhlbXBsZSBxdWkgc3VpdCwgbGEgdmFyaWFibGUgIGBkZXB0aGAgZXN0IHRvdXQgZCdhYm9yZCBtYXBww6llIHN1ciBsYSB0YWlsbGUgZGVzIHNvbW1ldHMuIAoKUHVpcywgZGFucyBgZ2VvbV9wb2ludGAsIG9uIGx1aSBhdHRyaWJ1ZSB1bmUgdmFsZXVyIGZpeGUuIAoKQXJyaXbDqWUgZW5zdWl0ZSwgYydlc3QgY2V0dGUgZGVybmnDqHJlIHF1aSBsJ2VtcG9ydGUgc3VyIGxlIG1hcHBpbmcgaW5pdGlhbC4KCi0tLS0KCmBgYHtyfQpnZ3Bsb3QoZGlhbSwgYWVzKHggPSBjYXJhdCwgeSA9IHByaWNlLCBjb2xvciA9IGNsYXJpdHksIHNpemUgPSBkZXB0aCkpICsgCiAgZ2VvbV9wb2ludChzaXplID0gMSkKYGBgCgotLS0tCgpBdSBwYXNzYWdlLCBsYSBtb2RpZmljYXRpb24gZHUgdGl0cmUgZW4gbMOpZ2VuZGUgZMOpcGVuZCBkZSBsYSBmb25jdGlvbiBgc2NhbGVgIGNvcnJlc3BvbmRhbnRlIChpY2kgYHNjYWxlX3NpemVgKS4KCkMnZXN0IGxvZ2lxdWUsIG1haXMgY29udHJlLWludHVpdGlmIHBvdXIgcXVpIGF1cmEgcGFzc8OpIGJlYXVjb3VwICh0cm9wID8pIGRlIHRlbXBzIGF2ZWMgbGVzIGNvbW1hbmRlcyBncmFwaGlxdWVzIGRlIGJhc2UgZGFucyBSLgoKLS0tLQoKYGBge3J9CmdncGxvdChkaWFtLCBhZXMoeCA9IGNhcmF0LCB5ID0gcHJpY2UsIGNvbG9yID0gY2xhcml0eSwgc2l6ZSA9IGRlcHRoKSkgKyAKICBnZW9tX3BvaW50KCkgKyAKICBzY2FsZV9zaXplKCJERVBUSCIsIHJhbmdlID0gYygxLDMpKQpgYGAKCiMjIEJpbGFuIGludGVybcOpZGlhaXJlCgpOb3VzIGF2b25zIHZ1IGNvbW1lbnQgOgoKKiBDaGFyZ2VyIGxlcyBkb25uw6llcy4KCiogRmFpcmUgdW4gbWFwcGluZy4KCiogU3VwZXJwb3NlciB1bmUgb3UgcGx1c2lldXJzIGfDqW9tw6l0cmllcy4KCi0tLS0KCklsIG5vdXMgcmVzdGUgw6AgZMOpY291dnJpciA6CgoqIExlIGZhY2V0dGFnZS4KCiogTGVzIMOpY2hlbGxlcy4KCiogTGVzIGFubm90YXRpb25zLgoKKiBMZXMgdHlwZXMgZGUgZ3JhcGhpcXVlcyBhdXRyZXMgcXVlIGxlcyAqc2NhdHRlcnBsb3RzKi4KCiogQ29tbWVudCBzYXV2ZXIgdW4gZ3JhcGhpcXVlLgoKKiBDb21tZW50IGJpZW4gcHLDqXBhcmVyIHNlcyBkb25uw6llcy4KCiMjIExlIGZhY2V0dGFnZQoKQXR0ZW50aW9uICEgQ2VjaSBzJ2FwcGxpcXVlIMOgIGRlcyB2YXJpYWJsZXMgZGlzY3LDqHRlcy4KCkxlIGZhY2V0dGFnZSBkaXZpc2UgbGUgamV1IGRlIGRvbm7DqWVzIGVuIGZvbmN0aW9uIGRlcyBjYXTDqWdvcmllcyBkJ3VuZSB2YXJpYWJsZS4KCkRhbnMgdW4gc2Vuc+KApiAKCi0tLS0KCmBgYHtyfQpnZ3Bsb3QoZGlhbSwgYWVzKHggPSBjYXJhdCwgeSA9IHByaWNlKSkgKyAKICBnZW9tX3BvaW50KCkgKwogIGZhY2V0X2dyaWQoLiB+IGN1dCkgCmBgYAoKLS0tLQoK4oCmIGV0IGRhbnMgbCdhdXRyZS4KCihSZW1hcnF1ZXogbGEgcG9zaXRpb24gaW52ZXJzw6llIGRlIGxhIHZhcmlhYmxlIGBjdXRgIGRhbnMgYGZhY2V0X2dyaWQoKWAuKQoKLS0tLQoKYGBge3J9CmdncGxvdChkaWFtLCBhZXMoeCA9IGNhcmF0LCB5ID0gcHJpY2UpKSArIAogIGdlb21fcG9pbnQoKSArCiAgZmFjZXRfZ3JpZChjdXQgfiAuKSAKYGBgCgotLS0tCgpFbiBjcm9pc2FudCBkZXV4IHZhcmlhYmxlcyBkaXNjcsOodGVzLgoKLS0tLQoKYGBge3J9CmdncGxvdChkaWFtLCBhZXMoeCA9IGNhcmF0LCB5ID0gcHJpY2UpKSArIAogIGdlb21fcG9pbnQoKSArCiAgZmFjZXRfZ3JpZChjb2xvciB+IGNsYXJpdHkpIApgYGAKCi0tLS0KCkZpbmFsZW1lbnQsIGVuIGNyb2lzYW50IGRldXggdmFyaWFibGVzIGRpc2Nyw6h0ZXMsIGF2ZWMgdW4gbWFwcGluZyBzdXIgbGEgY291bGV1ci4KCi0tLS0KCmBgYHtyfQpnZ3Bsb3QoZGlhbSwgYWVzKHggPSBjYXJhdCwgeSA9IHByaWNlLCBjb2xvciA9IGN1dCkpICsgCiAgZ2VvbV9wb2ludCgpICsKICBmYWNldF9ncmlkKGNvbG9yIH4gY2xhcml0eSkgCmBgYAoKLS0tLQoKSWwgeSBhIHVuZSBhdXRyZSBvcHRpb24gZGUgZmFjZXR0YWdlIGxvcnNxdSdvbiBuJ3V0aWxpc2UgcXUndW5lIHNldWxlIHZhcmlhYmxlIGRpc2Nyw6h0ZSA6IGBmYWNldF93cmFwYC4KCkRhbnMgY2UgY2FzLCByZW1hcnF1ZXogcXVlIG5vdXMgbid1dGlsaXNvbnMgcGx1cyBsZSBwb2ludCAoLikgYXZhbnQgbGUgdGlsZGUgKH4pLgoKLS0tLQoKYGBge3J9CmdncGxvdChkaWFtLCBhZXMoeCA9IGNhcmF0LCB5ID0gcHJpY2UpKSArIAogIGdlb21fcG9pbnQoKSArCiAgZmFjZXRfd3JhcCh+IGNsYXJpdHkpIApgYGAKCgojIyBMZXMgw6ljaGVsbGVzCgpFbGxlcyBjb21tZW5jZW50IHRvdXRlcyBwYXIgYHNjYWxlX2AKCkVuc3VpdGUsIG9uIGNvbXBsw6h0ZSBhdmVjIGxlIG5vbSBkZSBsYSB2YXJpYWJsZSBjb25jZXJuw6llLgoKLS0tLQoKCnNjYWxlX2FscGhhIHNjYWxlX2FscGhhX2NvbnRpbnVvdXMgc2NhbGVfYWxwaGFfZGlzY3JldGUgc2NhbGVfYWxwaGFfaWRlbnRpdHkgc2NhbGVfYWxwaGFfbWFudWFsIHNjYWxlX2NvbG9yX2JyZXdlciBzY2FsZV9jb2xvcl9jb250aW51b3VzIHNjYWxlX2NvbG9yX2Rpc2NyZXRlIHNjYWxlX2NvbG9yX2Rpc3RpbGxlciBzY2FsZV9jb2xvcl9ncmFkaWVudCBzY2FsZV9jb2xvcl9ncmFkaWVudDIgc2NhbGVfY29sb3JfZ3JhZGllbnRuIHNjYWxlX2NvbG9yX2dyZXkgc2NhbGVfY29sb3JfaHVlIHNjYWxlX2NvbG9yX2lkZW50aXR5IHNjYWxlX2NvbG9yX21hbnVhbCBzY2FsZV9jb2xvdXJfYnJld2VyIHNjYWxlX2NvbG91cl9jb250aW51b3VzIHNjYWxlX2NvbG91cl9kYXRlIHNjYWxlX2NvbG91cl9kYXRldGltZSBzY2FsZV9jb2xvdXJfZGlzY3JldGUgc2NhbGVfY29sb3VyX2Rpc3RpbGxlciBzY2FsZV9jb2xvdXJfZ3JhZGllbnQgc2NhbGVfY29sb3VyX2dyYWRpZW50MiBzY2FsZV9jb2xvdXJfZ3JhZGllbnRuIHNjYWxlX2NvbG91cl9ncmV5IHNjYWxlX2NvbG91cl9odWUgc2NhbGVfY29sb3VyX2lkZW50aXR5IHNjYWxlX2NvbG91cl9tYW51YWwgc2NhbGVfY29udGludW91cyBzY2FsZV9kYXRlIHNjYWxlX2ZpbGxfYnJld2VyIHNjYWxlX2ZpbGxfY29udGludW91cyBzY2FsZV9maWxsX2RhdGUgc2NhbGVfZmlsbF9kYXRldGltZSBzY2FsZV9maWxsX2Rpc2NyZXRlIHNjYWxlX2ZpbGxfZGlzdGlsbGVyIHNjYWxlX2ZpbGxfZ3JhZGllbnQgc2NhbGVfZmlsbF9ncmFkaWVudDIgc2NhbGVfZmlsbF9ncmFkaWVudG4gc2NhbGVfZmlsbF9ncmV5IHNjYWxlX2ZpbGxfaHVlIHNjYWxlX2ZpbGxfaWRlbnRpdHkgc2NhbGVfZmlsbF9tYW51YWwgc2NhbGVfaWRlbnRpdHkgc2NhbGVfbGluZXR5cGUgc2NhbGVfbGluZXR5cGVfY29udGludW91cyBzY2FsZV9saW5ldHlwZV9kaXNjcmV0ZSBzY2FsZV9saW5ldHlwZV9pZGVudGl0eSBzY2FsZV9saW5ldHlwZV9tYW51YWwgc2NhbGVfbWFudWFsIHNjYWxlX3JhZGl1cyBzY2FsZV9zaGFwZSBzY2FsZV9zaGFwZV9jb250aW51b3VzIHNjYWxlX3NoYXBlX2Rpc2NyZXRlIHNjYWxlX3NoYXBlX2lkZW50aXR5IHNjYWxlX3NoYXBlX21hbnVhbCBzY2FsZV9zaXplIHNjYWxlX3NpemVfYXJlYSBzY2FsZV9zaXplX2NvbnRpbnVvdXMgc2NhbGVfc2l6ZV9kYXRlIHNjYWxlX3NpemVfZGF0ZXRpbWUgc2NhbGVfc2l6ZV9kaXNjcmV0ZSBzY2FsZV9zaXplX2lkZW50aXR5IHNjYWxlX3NpemVfbWFudWFsIHNjYWxlX3hfY29udGludW91cyBzY2FsZV94X2RhdGUgc2NhbGVfeF9kYXRldGltZSBzY2FsZV94X2Rpc2NyZXRlIHNjYWxlX3hfbG9nMTAgc2NhbGVfeF9yZXZlcnNlIHNjYWxlX3hfc3FydCBzY2FsZV95X2NvbnRpbnVvdXMgc2NhbGVfeV9kYXRlIHNjYWxlX3lfZGF0ZXRpbWUgc2NhbGVfeV9kaXNjcmV0ZSBzY2FsZV95X2xvZzEwCXNjYWxlX3lfcmV2ZXJzZSBzY2FsZV95X3NxcnQKCi0tLS0KClBhciBleGVtcGxlLCBzaSBsJ29uIHRyYXZhaWxsZSBzdXIgbGEgY291bGV1ciwgb24gcG91cnJhIGZhaXJlIHZhcmllciBsYSBwYWxldHRlIGRlcyBjb3VsZXVycyBlbiBtb2RpZmlhbnQgbGUgbm9tIGRlIGwnw6ljaGVsbGUuIEZhaXRlcyBsZSB0ZXN0IGVuIMOpY3JpdmFudCBgc2NhbGVfY29sb3JfYCBkYW5zIGxhIGNvbnNvbGUgcHVpcyBlbiBwcmVzc2FudCBzdXIgbGEgdG91Y2hlIHRhYiBwb3VyIHZvaXIgbGVzIHN1Z2dlc3Rpb25z4oCmCgotLS0tCgpFbiBncmlzIAoKYGBge3J9CmdncGxvdChkaWFtLCBhZXMoeCA9IGNhcmF0LCB5ID0gcHJpY2UsIGNvbG9yID0gY2xhcml0eSkpICsgCiAgZ2VvbV9wb2ludCgpICsKICBzY2FsZV9jb2xvcl9ncmV5KCkKYGBgCgotLS0tCgpMYSB2ZXJzaW9uIHBhciBkw6lmYXV0LgoKYGBge3J9CmdncGxvdChkaWFtLCBhZXMoeCA9IGNhcmF0LCB5ID0gcHJpY2UsIGNvbG9yID0gY2xhcml0eSkpICsgCiAgZ2VvbV9wb2ludCgpICsKICBzY2FsZV9jb2xvcl9kaXNjcmV0ZSgpCmBgYAoKLS0tLQoKQ29sb3JCcmV3ZXIsIG5vbW3DqWUgZCdhcHLDqHMgdW5lIGRlIHNlcyBhdXRldXJzLCBDbnl0aGlhIEJyZXdlciwgZXN0IHVuZSBsaWJyYWlyaWUgZGUgY291bGV1cnMgcHLDqWNhbGN1bMOpZXMgcXVpIHMnYWNjb3JkZW50IGJpZW4uCgpPbiBwZXV0IGxlcyB1dGlsaXNlciBpY2kgYXZlYyBsYSBmb25jdGlvbiBgc2NhbGVfY29sb3JfYnJld2VyYC4KCi0tLS0KCmBgYHtyfQpnZ3Bsb3QoZGlhbSwgYWVzKHggPSBjYXJhdCwgeSA9IHByaWNlLCBjb2xvciA9IGNsYXJpdHkpKSArIAogIGdlb21fcG9pbnQoKSArCiAgc2NhbGVfY29sb3JfYnJld2VyKCkKYGBgCgotLS0tCgpFbiBjaGFuZ2VhbnQgZGUgcGFsZXR0ZS4KCi0tLS0KCmBgYHtyfQpnZ3Bsb3QoZGlhbSwgYWVzKHggPSBjYXJhdCwgeSA9IHByaWNlLCBjb2xvciA9IGNsYXJpdHkpKSArIAogIGdlb21fcG9pbnQoKSArCiAgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGUgPSAyKQpgYGAKCi0tLS0KCkF0dGVudGlvbiBzaSB2b3VzIHV0aWxpc2V6IGxhIG1hdXZhaXNlIMOpY2hlbGxlIDogc29pdCBpbCBuZSBzZSBwYXNzZXJhIHJpZW4gKGNvbW1lIGRhbnMgbGEgZmlndXJlIHN1aXZhbnRlKSwgc29pdCBpbCB5IGF1cmEgdW4gbWVzc2FnZSBkJ2VycmV1ci4KCi0tLS0KCmBgYHtyfQpnZ3Bsb3QoZGlhbSwgYWVzKHggPSBjYXJhdCwgeSA9IHByaWNlLCBjb2xvciA9IGNsYXJpdHkpKSArIAogIGdlb21fcG9pbnQoKSArCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9IDIpCmBgYAoKLS0tLQoKRGFucyBsYSBmaWd1cmUgc3VpdmFudGUsIG5vdXMgY2hhbmdlb25zIGxhIGZvcm1lIHV0aWxpc8OpZSBwb3VyIGRlc3NpbmVyIGxlcyBwb2ludHMgYWZpbiBxdSdpbCB5IGFpdCB1biBwb3VydG91ciAoYGNvbG9yYCkgZXQgdW4gY29udGVudSAoYGZpbGxgKS4KCkNldHRlIHRyYW5zZm9ybWF0aW9uIGEgw6l0w6kgZWZmZWN0dcOpZSBlbiBkb25uYW50IGNvbW1lIGluc3RydWN0aW9uIHF1ZSBsZXMgcG9pbnRzIGRvaXZlbnQgY2hhbmdlciBkZSBmb3JtZSAoaW5kw6lwZW5kYW1tZW50IGRlIHRvdXRlIHZhcmlhYmxlKS4KCk5vdXMgZW4gcHJvZml0b25zIHBvdXIgZGVzc2luZXIgbCdpbnTDqXJpZXVyLiBRdWVsbGUgZm9uY3Rpb24gZmF1dC1pbCB1dGlsaXNlciA/CgotLS0tCgpgYGB7cn0KZ2dwbG90KGRpYW0sIGFlcyh4ID0gY2FyYXQsIHkgPSBwcmljZSwgZmlsbCA9IGNsYXJpdHkpKSArIAogIGdlb21fcG9pbnQoc2hhcGUgPSAyMSkgKwogIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGUgPSAyKQpgYGAKCi0tLS0KCk1haXMgbGVzIMOpY2hlbGxlcywgw6dhIG5lIGNvbmNlcm5lIHBhcyBzZXVsZW1lbnQgbCdpbnTDqXJpZXVyIGR1IGdyYXBoaXF1ZS4KCk5vdXMgdXRpbGlzb25zIMOpZ2FsZW1lbnQgZGVzIMOpY2hlbGxlcyBzdXIgbGVzIGF4ZXMuCgotLS0tCgpgYGB7cn0KZ2dwbG90KGRpYW0sIGFlcyh4ID0gY2FyYXQsIHkgPSBwcmljZSwgY29sb3IgPSBjbGFyaXR5KSkgKyAKICBnZW9tX3BvaW50KCkgKyAKICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gYygxLDMpLCBtaW5vcl9icmVha3MgPSBjKHNxcnQoMiksIHBpKSkgKwogIHNjYWxlX3lfY29udGludW91cyhicmVha3MgPSBzYW1wbGUoMjAwMDAsIDEwKSkKYGBgCgotLS0tCgrDiWNoZWxsZSBsb2dhcml0aG1pcXVlIEZUVy4KCi0tLS0KCmBgYHtyfQpnZ3Bsb3QoZGlhbSwgYWVzKHggPSBjYXJhdCwgeSA9IHByaWNlLCBjb2xvciA9IGNsYXJpdHkpKSArIAogIGdlb21fcG9pbnQoKSArIAogIHNjYWxlX3lfbG9nMTAoKQpgYGAKCgojIyBMZXMgYW5ub3RhdGlvbnMKClZpYSB1biBtYXBwaW5nLCBwYXIgZXhlbXBsZSBwb3VyIHVuIE1EUy4KCi0tLS0KCmBgYHtyfQpnZ3Bsb3QoZGlhbSwgYWVzKHggPSBjYXJhdCwgeSA9IHByaWNlLCBsYWJlbCA9IGNsYXJpdHkpKSArIAogIGdlb21fdGV4dCgpCmBgYAoKLS0tLQoKTCdhbm5vdGF0aW9uIG1hbnVlbGxlIGVzdCBwb3NzaWJsZSwgw6AgbCdhbmNpZW5uZSwgbWFpcyBwYXMgZm9yY8OpbWVudCByZWNvbW1hbmTDqWUuCgotLS0tCgpgYGB7cn0KZ2dwbG90KGRpYW0sIGFlcyh4ID0gY2FyYXQsIHkgPSBwcmljZSwgY29sb3IgPSBjbGFyaXR5KSkgKyAKICBnZW9tX3BvaW50KCkgKwogIGFubm90YXRlKCJ0ZXh0IiwgeCA9IDMuNSwgeSA9IDEwMDAwLCBsYWJlbCA9ICJIRUxMTyIpCmBgYAoKCiMjIEQnYXV0cmVzIHR5cGVzIGRlIGdyYXBoaXF1ZXMKCkMnZXN0IGxhIHNldWxlIGZvaXMgcXVlIG5vdXMgdm95b25zIHVuZSB0cmFuc2Zvcm1hdGlvbiBzdGF0aXN0aXF1ZSBkYW5zIGNldHRlIHByw6lzZW50YXRpb24gKG1hbGhldXJldXNlbWVudCkuCgpDZSBzb250IGxlcyBmb25jdGlvbnMgY29tbWVuw6dhbnQgcGFyIGBzdGF0X2AuCgpQb3VyIHBsdXMgZCdpbmZvcywgdm9pciBsYSBjaGVhdCBzaGVldC4KCi0tLS0KCmBgYHtyfQpnZ3Bsb3QoZGlhbSwgYWVzKHByaWNlKSkgKwogIGdlb21fYXJlYShzdGF0ID0gImJpbiIpCmBgYAoKLS0tLQoKYGBge3J9CmdncGxvdChkaWFtLCBhZXMocHJpY2UpKSArCiAgZ2VvbV9kZW5zaXR5KGtlcm5lbCA9ICJnYXVzc2lhbiIpCmBgYAoKLS0tLQoKYGBge3J9CmdncGxvdChkaWFtLCBhZXMocHJpY2UpKSArCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAzMCkKYGBgCgotLS0tCgpBdmVjIHVuZSB2YXJpYWJsZSBkaXNjcsOodGUsIGNldHRlIGZvaXMuCgpgYGB7cn0KZ2dwbG90KGRpYW0sIGFlcyhjb2xvcikpICsKICBnZW9tX2JhcigpCmBgYAoKLS0tLQoKVW5lIHZhcmlhYmxlIGRpc2Nyw6h0ZSBldCB1bmUgdmFyaWFibGUgY29udGludWUuCgpgYGB7cn0KZ2dwbG90KGRpYW0sIGFlcyh4ID0gY29sb3IsIHkgPSBwcmljZSkpICsKICBnZW9tX2JveHBsb3QoKQpgYGAKCi0tLS0KCkRldXggdmFyaWFibGVzIGRpc2Nyw6h0ZXMuCgpgYGB7cn0KZ2dwbG90KGRpYW0sIGFlcyh4ID0gY3V0LCB5ID0gY29sb3IpKSArCiAgZ2VvbV9jb3VudCgpCmBgYAoKLS0tLQoKYGBge3J9CmdncGxvdChkaWFtb25kcywgYWVzKHg9cHJpY2UsIGZpbGw9Y3V0KSkgKyAKICBnZW9tX2hpc3RvZ3JhbSgpCmBgYAoKLS0tLQoKRGlzdHJpYnV0aW9ucyBiaS12YXJpw6llcy4KCmBgYHtyfQpnZ3Bsb3QoZGlhbW9uZHMsIGFlcyhjYXJhdCwgcHJpY2UpKSArCiAgZ2VvbV9iaW4yZChiaW53aWR0aCA9IGMoMC4yNSwgNTAwKSkKYGBgCgotLS0tCgpgYGB7cn0KZ2dwbG90KGRpYW1vbmRzLCBhZXMoY2FyYXQsIHByaWNlKSkgKwogIGdlb21faGV4KCkKYGBgCgoKCgoKIyMgTGVzIHRow6htZXMKCkxhIGZvbmN0aW9uIGBnZ3RpdGxlYCBlc3QgdXRpbGlzw6llIHBvdXIgY2hvaXNpciB1biB0aXRyZS4KCi0tLS0KCmBgYHtyfQpnZ3Bsb3QoZGlhbSwgYWVzKHggPSBjYXJhdCwgeSA9IHByaWNlLCBjb2xvciA9IGNsYXJpdHkpKSArIAogIGdlb21fcG9pbnQoKSArIAogIGdndGl0bGUoIk1vbiBqb2xpIGdyYXBoaXF1ZSIpCmBgYAoKLS0tLQoKRXQsIGNsYXNzaXF1ZSBwb3VyIHVuZSBmb2lzLCBgeGxhYmAgZXQgYHlsYWJgIHBvdXIgY2hhbmdlciBsZXMgbm9tcyBkZXMgYXhlcy4KCi0tLS0KCmBgYHtyfQpnZ3Bsb3QoZGlhbSwgYWVzKHggPSBjYXJhdCwgeSA9IHByaWNlLCBjb2xvciA9IGNsYXJpdHkpKSArIAogIGdlb21fcG9pbnQoKSArIAogIHhsYWIoIk1hIGpvbGllIGFic2Npc3NlIikgKyAKICB5bGFiKCJNYSBqb2xpZSBvcmRvbm7DqWUiKQpgYGAKCi0tLS0KClBvdXIgdmFyaWVyIGxlcyB0aMOobWVzIDogYHRoZW1lX2AuCgotLS0tCgpgYGB7cn0KZ2dwbG90KGRpYW0sIGFlcyh4ID0gY2FyYXQsIHkgPSBwcmljZSwgY29sb3IgPSBjbGFyaXR5KSkgKyAKICBnZW9tX3BvaW50KCkgKwogIHRoZW1lX2J3KCkKYGBgCgotLS0tCgpgYGB7cn0KZ2dwbG90KGRpYW0sIGFlcyh4ID0gY2FyYXQsIHkgPSBwcmljZSwgY29sb3IgPSBjbGFyaXR5KSkgKyAKICBnZW9tX3BvaW50KCkgKwogIHRoZW1lX2RhcmsoKQpgYGAKCi0tLS0KCmBgYHtyfQpnZ3Bsb3QoZGlhbSwgYWVzKHggPSBjYXJhdCwgeSA9IHByaWNlLCBjb2xvciA9IGNsYXJpdHkpKSArIAogIGdlb21fcG9pbnQoKSArCiAgdGhlbWVfbWluaW1hbCgpCmBgYAoKIyMgU2F1dmVyIHVuIGdyYXBoaXF1ZQoKYGBge3J9Cmdnc2F2ZQpgYGAKCi0tLS0KClBhciBleGVtcGxlIAoKYGdnc2F2ZSgicGxvdC5wZGYiLCB3aWR0aCA9IDcsIGhlaWdodCA9IDcpYAoKIyMgQmllbiBwcsOpcGFyZXIgc2VzIGRvbm7DqWVzCgrCq1RpZHkgZGF0YSBpcyBhIHN0YW5kYXJkIHdheSBvZiBtYXBwaW5nIHRoZSBtZWFuaW5nIG9mIGEgZGF0YXNldCB0byBpdHMgc3RydWN0dXJlLsK7IAoKwqtBIGRhdGFzZXQgaXMgbWVzc3kgb3IgdGlkeSBkZXBlbmRpbmcgb24gaG93IHJvd3MsIGNvbHVtbnMgYW5kIHRhYmxlcyBhcmUgbWF0Y2hlZCB1cCB3aXRoIG9ic2VydmF0aW9ucywgdmFyaWFibGVzIGFuZCB0eXBlcy7CuyAKCi0tLS0KCsKrSW4gdGlkeSBkYXRhOgoKMS4gRWFjaCB2YXJpYWJsZSBmb3JtcyBhIGNvbHVtbi4KMi4gRWFjaCBvYnNlcnZhdGlvbiBmb3JtcyBhIHJvdy4KMy4gRWFjaCB0eXBlIG9mIG9ic2VydmF0aW9uYWwgdW5pdCBmb3JtcyBhIHRhYmxlLsK7CgpTb3VyY2UgOgoKYGBge3J9CiMgbGlicmFyeSh0aWR5cikKIyB2aWduZXR0ZSgidGlkeS1kYXRhIikKYGBgCgotLS0tCgpNYXJpZS1Mb3Vpc2UgVGltY2tlIGEgcHJvcG9zw6kgc3VyIGBqb3Vybm9jb2RlLmNvbWAgW3VuZSB0csOocyBib25uZSByZXNzb3VyY2Ugw6AgY2Ugc3VqZXRdKGh0dHA6Ly9qb3Vybm9jb2RlLmNvbS8yMDE2LzAzLzA1L3ItdGlkeS1kYXRhLykuCgpFbiBwYXJ0aWN1bGllciwgZWxsZSBwcsOpc2VudGUgbCdleGVtcGxlIHN1aXZhbnQsIGV4dHJhaXQgZCdleHBsaWNhdGlvbnMgZCdIYWRsZXkgV2lja2hhbS4KCi0tLS0KCiFbXSh0aWR5X3dpY2toYW0ucG5nKQoKLS0tLQoKTGUgZm9ybWF0ICp0aWR5Kiwgb3UgKmxvbmcgZm9ybSosIGVzdCBmb3J0ZW1lbnQgcmVjb21tYW5kw6kgZGFucyBgZ2dwbG90MmAuCgpFbiB1biBzY2jDqW1hLCBbRnJlZGVyaWsgQXVzdF0oaHR0cHM6Ly90d2l0dGVyLmNvbS9GcmVkZXJpa0F1c3Qvc3RhdHVzLzc4OTEwMTM0NjU5NTE1MTg3MikgYSB0csOocyBiaWVuIHLDqXN1bcOpIGxlcyBvcMOpcmF0aW9ucyBuw6ljZXNzYWlyZXMgcG91ciBsYSBtaXNlIGVuIGZvcm1lIGRlcyBkb25uw6llcy4KCiMjIEJvbnVzICEKCiogUsOpYWxpc2VyIGRlcyBjYXJ0ZXMgYXZlYyBnZ3Bsb3QyLgoKKiBEZXV4IFtleHRlbnNpb25zXShodHRwczovL3d3dy5nZ3Bsb3QyLWV4dHMub3JnLykuIAogIAojIyBSw6lhbGlzZXIgZGVzIGNhcnRlcwoKYGBge3J9CmRhdGEgPC0gZGF0YS5mcmFtZShtdXJkZXIgPSBVU0FycmVzdHMkTXVyZGVyLCBzdGF0ZSA9IHRvbG93ZXIocm93bmFtZXMoVVNBcnJlc3RzKSkpCm1hcCA8LSBtYXBfZGF0YSgic3RhdGUiKQprIDwtIGdncGxvdChkYXRhLCBhZXMoZmlsbCA9IG11cmRlcikpCmsgPC0gayArIAogIGdlb21fbWFwKGFlcyhtYXBfaWQgPSBzdGF0ZSksIG1hcCA9IG1hcCkgKyAKICBleHBhbmRfbGltaXRzKHggPSBtYXAkbG9uZywgeSA9IG1hcCRsYXQpCmBgYAoKLS0tLQoKYGBge3J9CmsKYGBgCgoKIyMgZ2dhbmltYXRlCgpgYGB7cn0KIyBkZXZ0b29sczo6aW5zdGFsbF9naXRodWIoImRncnR3by9nZ2FuaW1hdGUiKQojIGluc3RhbGwucGFja2FnZXMoImdhcG1pbmRlciIpCmxpYnJhcnkoZ2dhbmltYXRlKQpsaWJyYXJ5KGdhcG1pbmRlcikKdGhlbWVfc2V0KHRoZW1lX2J3KCkpCnAgPC0gZ2dwbG90KGdhcG1pbmRlciwgYWVzKGdkcFBlcmNhcCwgbGlmZUV4cCwgc2l6ZSA9IHBvcCwgY29sb3IgPSBjb250aW5lbnQsIGZyYW1lID0geWVhcikpICsKICBnZW9tX3BvaW50KCkgKwogIHNjYWxlX3hfbG9nMTAoKQpgYGAKCi0tLS0KCihTaSDDp2EgbmUgZm9uY3Rpb25uZSwgbGFuY2VyIGNlIGNvZGUgZGFucyBSL1JTdHVkaW8uKQoKYGBge3J9CmdnX2FuaW1hdGUocCkKYGBgCgojIyBJbnRlcmFjdGl2aXTDqQoKVW4gW2V4ZW1wbGVdKGh0dHA6Ly9zaGlueS5yc3R1ZGlvLmNvbS9nYWxsZXJ5L21vdmllLWV4cGxvcmVyLmh0bWwpIHBlcm1ldHRhbnQgbGEgY29uc3VsdGF0aW9uIGRlIGRvbm7DqWVzIGRlIGZpbG1zIHRpcsOpZXMgZGUgKlJvdHRlbiBUb21hdG9lcyouCgpVbiBhdXRyZSBbZXhlbXBsZV0oaHR0cHM6Ly9naXN0LmdpdGh1Yi5jb20vamNoZW5nNS8zMjM5NjY3KSwgY2V0dGUgZm9pcyBzdXIgbGEgYmFzZSBkZSBkb25uw6llcyBkZXMgZGlhbWFudHMgdXRpbGlzw6llIGRhbnMgY2Ugbm90ZWJvb2svY2VzIHNsaWRlcy4KCkxlIG1vdC1jbMOpIMOgIHJldGVuaXIgbG9yc3F1J29uIHZldXQgcmVuZHJlIHVuIGdyYXBoaXF1ZSBkZSBSIGludGVyYWN0aWYgOiBgc2hpbnlgLiBNYWlzIGNlIHNlcmEgcG91ciB1biBhdXRyZSB3b3Jrc2hvcOKApgoKCgoKCgoKCgoK