Chargement des packages nécessaires et des données
R est un langage de programmation flexible et en constante évolution grâce à un immense échantillon de “packages”/paquets qui permettent d’importer des fonctions.
Un ensemble de paquets quasi-indispensable sur R sont regroupés sous une sorte de méta-paquet, tidyverse. Plus particulièrement nous allons surtout utiliser les packages stringr et dplyr de tidyverse. On “charge” ensuite tidyverse pour pouvoir en utiliser les fonctions.
install.packages("tidyverse", dependencies = TRUE)
library(tidyverse)
Une autre manière d’utiliser les fonctions d’un package sans le charger est d’écrire tidyverse::la_fonction_utilisée
. Dans ce tutoriel, tous les packages utilisés seront chargés, mais j’essaierai tout de même d’utiliser cette formulation pour que vous puissiez voir à quel package appartie chaque fonction.
Quand on a plusieurs packages à installer et charger, il peut être plus clair de mettre ces packages dans une liste, d’installer ces packages uniquement s’ils ne sont pas déjà installés, puis de les charger.
package_list <- c(
"tidyverse",
"here", # use for paths creation
"janitor", # useful functions for cleaning imported data
"biblionetwork" # creating edges
)
for (p in package_list) {
if (p %in% installed.packages() == FALSE) {
install.packages(p, dependencies = TRUE)
}
library(p, character.only = TRUE)
}
Le package here permet de manipuler plus aisément les sentiers de vos dossiers sur votre ordinateur. Cela est important quand vous importez et exportez des fichiers. Par exemple, la commande suivante vous permet de savoir où est votre “dossier de travail” actuel:
here::here() # on enregistre le sentier dans un "objet"
[1] "C:/Users/goutsmedt/Documents/MEGAsync/Research/R"
C:/Users/goutsmedt/Documents/MEGAsync/Research/R
Pour plus de simplicité, vous pouvez mettre les données transmises par mail, dsge_articles_sample.csv
dans ce dossier. Vous n’avez alors plus qu’à les importer avec readr::read_csv2(here("dsge_articles_sample.csv"))
. Ou bien vous pouvez mettre les données dans le même dossier que le présent fichier et l’importer comme suite:
data <- readr::read_csv2("dsge_articles_sample.csv")
i Using "','" as decimal and "'.'" as grouping mark. Use `read_delim()` for more control.
Rows: 500 Columns: 22
-- Column specification ---------------------------------------------------------------------------------------------------------------
Delimiter: ";"
chr (19): authors, title, source_title, volume, issue, art_no, page_start, page_end, doi, affiliations, authors_with_affiliations, ...
dbl (3): year, page_count, cited_by
i Use `spec()` to retrieve the full column specification for this data.
i Specify the column types or set `show_col_types = FALSE` to quiet this message.
On peut désormais regarder à quoi ressemble nos données:
data
Nettoyage des données
Il y a plusieurs choses à nettoyer :
- Nous avons plusieurs
auteurs
par document. Pour certaines analyses, par exemple les réseaux de co-auteurs, il est préférable d’avoir une “table des auteurs” qui associe chaque auteur à une liste d’articles ;
- Nous avons plusieurs
affiliations
par article ainsi que plusieurs authors_with_affiliations
. Cela nous permet de relier les auteurs à leurs affiliations, mais là encore nous devons le séparer en autant de lignes que d’auteurs (si je ne me trompe pas, il semble qu’il n’y ait qu’une seule affiliation par auteur dans ce jeu de données) ;
références
;
- Eventuellement pour séparer
author_keywords
et index_keywords
si vous voulez l’utiliser.
Dans ce qui suit, nous nettoyons data
afin de produire 2 data.frames
supplémentaires :
- un
data.frame
qui associe chaque article à la liste des références qu’il cite (un article a autant de lignes que le nombre de références citées) : c’est un tableau de “citations directes” ;
- une liste de toutes les références citées, qui permet de retrouver les références identiques dans le tableau de citations directes.
(Très) brève introduction aux regex
Pour nettoyer les données nous allons utiliser un langage particulier, très rebutant au départ: les expressions régulières (regular expressions ou regex en anglais). Les regex permettent de repérer dans un ensemble de caractères certaines particularités. Vous pouvez par exemple ensuite supprimer, extraire, modifier ces particularités.
phrase_stupide <- "Nous somme en cours le vendredi 4 février et l'intervenant commence à nous parler de quelque chose d'étrange: les regex."
Donnons nous quelques tâches:
- extraire le premier mot, ainsi que le dernier
premier_mot <- stringr::str_extract(phrase_stupide, "^[A-z]{4}")
premier_mot <- stringr::str_extract(phrase_stupide, "^[A-z]+") # peut être simplifié
dernier_mot <- stringr::str_extract(phrase_stupide, "[A-z]+$") # ne marche pas
dernier_mot <- stringr::str_extract(phrase_stupide, "[A-z]+\\.$") # peut être amélioré
dernier_mot <- stringr::str_extract(phrase_stupide, "[A-z]+(?=\\.$)")
date <- str_extract(phrase_stupide, "[A-z]+ \\d [A-z]+") # ne marche pas pour le mois
date <- str_extract(phrase_stupide, "[A-z]+ \\d [:letter:]+")
- remplacer la dernière partie du texte après les deux points
nouvelle_phrase <- str_replace(phrase_stupide, ":.*", ": mais je m'en souviens plus.")
- supprimer les mots de moins de 2 lettres ou moins
phrase_deconstruite <- str_remove_all(phrase_stupide, " [:letter:]{1,2} ")
phrase_deconstruite <- str_remove_all(phrase_stupide, "(?<= )[:letter:]{1,2}(?= )")
Maintenant, vous allez pouvoir tout comprendre au nettoyage des données qui suit. Ou peut-être pas…
Nettoyage des références
La première chose à faire est de créer un identifiant pour les articles citant, ce qui nous permettra de faire le pont entre les articles citant et les références citées:
data <- data %>%
mutate(citing_id = paste0("A", 1:n()))
Extrayons d’abord la colonne references
en gardant un identifiant pour l’article citant. Nous mettons chaque référence citée par un article sur une ligne séparée, en utilisant le fait que les références sont séparées par un point-virgule. Nous créons un identifiant pour chaque référence.
#' ## Extracting and cleaning references
references_extract <- data %>%
filter(! is.na(references)) %>% # enlever les articles qui n'ont pas de références
select(citing_id, references) %>%
separate_rows(references, sep = "; ") %>% # on met une référence par ligne
mutate(id_ref = 1:n()) %>% # on donne un identifiant aux références
as_tibble
references_extract
Notre but désormais est de pouvoir extraire les métadonnées de chaque référence: les auteurs, la date de publication, le titre, le journal, etc… L’objectif est d’avoir des informations relativement standardisées, pour pouvoir ensuite identifier quelles sont les références qui sont les mêmes. Une première étape est d’extraire les auteurs ainsi que l’année de publication. On enlève les auteurs de la référence principale et on stock l’info restante, qu’on va nettoyer, dans une colonne séparée.
extract_authors <- ".*[:upper:][:alpha:]+( Jr(.)?)?, ([A-Z]\\.[ -]?)?([A-Z]\\.[ -]?)?([A-Z]\\.)?[A-Z]\\."
extract_year_brackets <- "(?<=\\()\\d{4}(?=\\))"
extract_pages <- "(?<= (p)?p\\. )([A-Z])?\\d+(-([A-Z])?\\d+)?"
extract_volume_and_number <- "(?<=( |^)?)\\d+ \\(\\d+(-\\d+)?\\)"
cleaning_references <- references_extract %>%
mutate(authors = str_extract(references, paste0(extract_authors, "(?=, )")),
remaining_ref = str_remove(references, paste0(extract_authors, ", ")), # cleaning from authors
is_article = ! str_detect(remaining_ref, "^\\([:digit:]{4}"),
year = str_extract(references, extract_year_brackets) %>% as.integer)
cleaning_references
A partir de maintenant, il y a beaucoup trop d’étapes pour pouvoir tout détailler, mais la stratégie générale est de séparer les références suivant la position de l’année de publication: le plus simple à nettoyer (cleaning_articles
) est quand la titre est avant la date de publication car cela permet d’extraire plus simplement le titre, et donc aussi, en général, le nom du journal qui va suivre la date de publication. On extrait également des informations utiles comme le volume, le numéro, les pages, et le DOI.
cleaning_articles <- cleaning_references %>%
filter(is_article == TRUE) %>%
mutate(title = str_extract(remaining_ref, ".*(?=\\(\\d{4})"), # pre date extraction
journal_to_clean = str_extract(remaining_ref, "(?<=\\d{4}\\)).*"), # post date extraction
journal_to_clean = str_remove(journal_to_clean, "^,") %>% str_trim("both"), # cleaning a bit the journal info column
pages = str_extract(journal_to_clean, extract_pages), # extracting pages
volume_and_number = str_extract(journal_to_clean, extract_volume_and_number), # extracting standard volument and number: X (X)
journal_to_clean = str_remove(journal_to_clean, " (p)?p\\. ([A-Z])?\\d+(-([A-Z])?\\d+)?"), # clean from extracted pages
journal_to_clean = str_remove(journal_to_clean, "( |^)?\\d+ \\(\\d+(-\\d+)?\\)"), # clean from extracted volume and number
volume_and_number = ifelse(is.na(volume_and_number), str_extract(journal_to_clean, "(?<= )([A-Z])?\\d+(-\\d+)?"), volume_and_number), # extract remaining numbers
journal_to_clean = str_remove(journal_to_clean, " ([A-Z])?\\d+(-\\d+)?"), # clean from remaining numbers
journal = str_remove_all(journal_to_clean, "^[:punct:]+( )?[:punct:]+( )?|(?<=,( )?)[:punct:]+( )?([:punct:])?|[:punct:]( )?[:punct:]+( )?$"), # extract journal info by removing inappropriate punctuations
first_page = str_extract(pages, "\\d+"),
volume = str_extract(volume_and_number, "\\d+"),
issue = str_extract(volume_and_number, "(?<=\\()\\d+(?=\\))"),
publisher = ifelse(is.na(first_page) & is.na(volume) & is.na(issue) & ! str_detect(journal, "(W|w)orking (P|p)?aper"), journal, NA),
book_title = ifelse(str_detect(journal, " (E|e)d(s)?\\.| (E|e)dite(d|urs)? "), journal, NA), # Incollection article: Title of the book here
book_title = str_extract(book_title, "[A-z ]+(?=,)"), # keeping only the title of the book
publisher = ifelse(!is.na(book_title), NA, publisher), # if we have an incollection article, that's not a book, so no publisher
journal = ifelse(!is.na(book_title) | ! is.na(publisher), NA, journal), # removing journal as what we have is a book
publisher = ifelse(is.na(publisher) & str_detect(journal, "(W|w)orking (P|p)?aper"), journal, publisher), # adding working paper publisher information in publisher column
journal = ifelse(str_detect(journal, "(W|w)orking (P|p)?aper"), "Working Paper", journal))
cleaned_articles <- cleaning_articles %>%
select(citing_id, id_ref, authors, year, title, journal, volume, issue, pages, first_page, book_title, publisher, references)
cleaning_non_articles <- cleaning_references %>%
filter(is_article == FALSE) %>%
mutate(remaining_ref = str_remove(remaining_ref, "\\(\\d{4}\\)(,)? "),
title = str_extract(remaining_ref, ".*(?=, ,)"),
pages = str_extract(remaining_ref, "(?<= (p)?p\\. )([A-Z])?\\d+(-([A-Z])?\\d+)?"), # extracting pages
volume_and_number = str_extract(remaining_ref, "(?<=( |^)?)\\d+ \\(\\d+(-\\d+)?\\)"), # extracting standard volument and number: X (X)
remaining_ref = str_remove(remaining_ref, " (p)?p\\. ([A-Z])?\\d+(-([A-Z])?\\d+)?"), # clean from extracted pages
remaining_ref = str_remove_all(remaining_ref, ".*, ,"), # clean dates and already extracted titles
remaining_ref = str_remove(remaining_ref, "( |^)?\\d+ \\(\\d+(-\\d+)?\\)"), # clean from extracted volume and number
volume_and_number = ifelse(is.na(volume_and_number), str_extract(remaining_ref, "(?<= )([A-Z])?\\d+(-\\d+)?"), volume_and_number), # extract remaining numbers
remaining_ref = str_remove(remaining_ref, " ([A-Z])?\\d+(-\\d+)?"), # clean from remaining numbers
journal = ifelse(str_detect(remaining_ref, "(W|w)orking (P|p)aper"), "Working Paper", NA),
journal = ifelse(str_detect(remaining_ref, "(M|m)anuscript"), "Manuscript", journal),
journal = ifelse(str_detect(remaining_ref, "(M|m)imeo"), "Mimeo", journal),
publisher = ifelse(is.na(journal), remaining_ref, NA) %>% str_trim("both"),
first_page = str_extract(pages, "\\d+"),
volume = str_extract(volume_and_number, "\\d+"),
issue = str_extract(volume_and_number, "(?<=\\()\\d+(?=\\))"),
book_title = NA) # to be symetric with "cleaned_articles"
cleaned_non_articles <- cleaning_non_articles %>%
select(citing_id, id_ref, authors, year, title, journal, volume, issue, pages, first_page, book_title, publisher, references)
# merging the two files.
cleaned_ref <- rbind(cleaned_articles, cleaned_non_articles)
#' Now we have all the references, we can do a bit of cleaning on the authors name,
#' and extract useful information, like DOI, for matching later.
cleaned_ref <- cleaned_ref %>%
mutate(authors = str_remove(authors, " Jr\\."), # standardising authors name to favour matching later
authors = str_remove(authors, "^\\(\\d{4}\\)(\\.)?( )?"),
authors = str_remove(authors, "^, "),
authors = ifelse(is.na(authors), str_extract(references, ".*[:upper:]\\.(?= \\d{4})"), authors), # specific case
journal = str_remove(journal, "[:punct:]$"), # remove unnecessary punctuations at the end
doi = str_extract(references, "(?<=DOI(:)? ).*|(?<=\\/doi\\.org\\/).*"),
pii = str_extract(doi, "(?<=PII ).*"),
doi = str_remove(doi, ",.*"), # cleaning doi
pii = str_remove(pii, ",.*"), # cleaning pii
)
cleaned_ref
Matcher les références ensemble
Regardons de plus prêt la première ligne de notre tableau des références pour mieux comprendre les problèmes potentiels:
cleaned_ref[1,]
Notre but est de trouver toutes les citations qui renvoient au même article. En extrayant les informations de manière systématique, on exclut le problème des “styles” de citation différent. Au-delà de l’imperfection du nettoyage ci-dessus, il reste plusieurs problèmes qui peuvent nous empêcher de matcher les références correctement:
- Informations non-référencées;
- Erreurs;
- Différentes manières d’écrire le journal;
- Variations dans l’écriture des titres (souvent lié à la ponctuation ou aux articles);
- Différences dans l’écriture des noms (souvent alternance entre une ou deux initiales).
Le compromis consiste à faire correspondre autant de vrais positifs que possible (références identiques) tout en évitant de faire correspondre des faux positifs, c’est-à-dire des références qui ont des informations en commun, mais qui ne sont en fait pas les mêmes. Par exemple, une correspondance basée uniquement sur le nom des auteurs et l’année de publication est trop large, car ces auteurs peuvent avoir publié plusieurs articles au cours de la même année. Voici plusieurs façons d’identifier une référence commune qui comportent très peu de risques de faire correspondre des références différentes :
- même nom de famille du premier auteur ou des auteurs, année, volume et page (ce sont les plus sûres) : appelons-les
fayvp
& ayvp
;
- même journal, volume, numéro et première page :
jvip
;
- même auteur, année et titre :
ayt
;
- même titre, même année et même première page :
typ
;
- même Doi ou PII.
Nous extrayons le nom de famille du premier auteur pour favoriser la correspondance car il y a plus de possibilités de petites différences pour plusieurs auteurs qui nous empêcheraient de faire correspondre des références similaires.
cleaned_ref <- cleaned_ref %>%
mutate(first_author = str_extract(authors, "^[[:alpha:]+[']?[ -]?]+, ([A-Z]\\.[ -]?)?([A-Z]\\.[ -]?)?([A-Z]\\.)?[A-Z]\\.(?=(,|$))"),
first_author_surname = str_extract(first_author, ".*(?=,)"),
across(.cols = c("authors", "first_author", "journal", "title"), ~toupper(.)))
matching_ref <- function(data, id_ref, ..., col_name){
match <- data %>%
group_by(...) %>%
mutate(new_id = min({{id_ref}})) %>%
drop_na(...) %>%
ungroup() %>%
select({{id_ref}}, new_id) %>%
rename_with(~ paste0(col_name, "_new_id"), .cols = new_id)
data <- data %>%
left_join(match)
}
identifying_ref <- cleaned_ref %>%
matching_ref(id_ref, first_author_surname, year, title, col_name = "fayt") %>%
matching_ref(id_ref, journal, volume, issue, first_page, col_name = "jvip") %>%
matching_ref(id_ref, authors, year, volume, first_page, col_name = "ayvp") %>%
matching_ref(id_ref, first_author_surname, year, volume, first_page, col_name = "fayvp") %>%
matching_ref(id_ref, title, year, first_page, col_name = "typ") %>%
matching_ref(id_ref, pii, col_name = "pii") %>%
matching_ref(id_ref, doi, col_name = "doi")
Joining, by = "id_ref"
Joining, by = "id_ref"
Joining, by = "id_ref"
Joining, by = "id_ref"
Joining, by = "id_ref"
Joining, by = "id_ref"
Joining, by = "id_ref"
View(identifying_ref)
Nous avons maintenant notre tableau de citations directes reliant les articles citant aux références. Nous avons autant de lignes que le nombre de citations par les articles citant. Les deux premières colonnes, sont les seules colonnes nécessaires pour construire les liens de notre réseau tout à l’heure.
direct_citation <- identifying_ref %>%
mutate(new_id_ref = select(., ends_with("new_id")) %>% reduce(pmin, na.rm = TRUE),
new_id_ref = ifelse(is.na(new_id_ref), id_ref, new_id_ref)) %>%
relocate(new_id_ref, .after = citing_id) %>%
select(-id_ref & ! ends_with("new_id"))
direct_citation
Nous pouvons extraire la liste de toutes les références citées. Nous avons autant de lignes que de références citées par les articles citant (c’est-à-dire qu’une référence citée plusieurs fois n’est présente qu’une seule fois dans le tableau). Comme pour les références appariées ensemble, on peut avoir des informations différentes (dues au fait que les références ont été citées différemment selon les articles citant), nous prenons une ligne où l’information semble être la plus complète, c’est-à-dire là où il y a le moins d’information manquantes dans les métadonnées.
important_info <- c("authors",
"year",
"title",
"journal",
"volume",
"issue",
"pages",
"book_title",
"publisher")
references <- direct_citation %>%
mutate(nb_na = rowSums(!is.na(select(., all_of(important_info))))) %>%
group_by(new_id_ref) %>%
slice_max(order_by = nb_na, n = 1, with_ties = FALSE) %>%
select(-citing_id) %>%
unique
Les données que vous avez désormais nettoyées vous permettent de faire des premières explorations statistiques. Par exemple, on peut regarder quels sont les travaux les plus cités:
direct_citation %>%
add_count(new_id_ref) %>%
select(new_id_ref, n) %>%
unique() %>%
slice_max(n, n = 10) %>%
left_join(select(references, new_id_ref, references)) %>%
select(references, n)
Joining, by = "new_id_ref"
Préparer noeuds et liens pour le réseau de co-citation
Il s’agit désormais de préparer les données que nous allons utiliser dans Gephi pour construire un réseau. On veut faire un réseau de co-citation, donc on s’intéresse aux références citées (le couplage bibliographique s’intéresse aux articles citant). Pour limiter le nombre de nœuds, on peut choisir de ne prendre que les nœuds les plus cités:
citations <- direct_citation %>%
add_count(new_id_ref) %>%
select(new_id_ref, n) %>%
unique
nodes <- references %>%
left_join(citations) %>%
filter(n >= 10)
Joining, by = "new_id_ref"
nodes
On va ensuite créer les liens à partir des citations directes: pour chaque référence, on regarde quels articles la citent. Ensuite, on chercher quelles autres références sont citées par les mêmes articles. En d’autres mots, toutes les références citées dans une même bibliographie (d’un article citant) vont être reliées entre elles. On utilise une méthode qui permet de pondérer les liens en fonction du nombre de fois où chaque référence est citée: en effet, on considère que si deux références beaucoup citées sont citées k fois ensemble, ce lien doit être moins important que des références peu citées, qui sont citées le même nombre k de fois ensemble.
direct_citation <- direct_citation %>%
filter(new_id_ref %in% nodes$new_id_ref)
edges <- biblionetwork::biblio_cocitation(direct_citation,
"citing_id",
"new_id_ref",
weight_threshold = 5)
edges
LS0tDQp0aXRsZTogIk5ldHRveWFnZSBkZXMgZG9ubsOpZXMgU2NvcHVzIHN1ciBsZXMgRFNHRSINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KZWRpdG9yX29wdGlvbnM6DQogIGNodW5rX291dHB1dF90eXBlOiBpbmxpbmUNCi0tLQ0KDQojIENoYXJnZW1lbnQgZGVzIHBhY2thZ2VzIG7DqWNlc3NhaXJlcyBldCBkZXMgZG9ubsOpZXMNCg0KUiBlc3QgdW4gbGFuZ2FnZSBkZSBwcm9ncmFtbWF0aW9uIGZsZXhpYmxlIGV0IGVuIGNvbnN0YW50ZSDDqXZvbHV0aW9uIGdyw6JjZSDDoCB1biBpbW1lbnNlIMOpY2hhbnRpbGxvbiBkZSAicGFja2FnZXMiL3BhcXVldHMgcXVpIHBlcm1ldHRlbnQgZCdpbXBvcnRlciBkZXMgZm9uY3Rpb25zLg0KDQpVbiBlbnNlbWJsZSBkZSBwYXF1ZXRzIHF1YXNpLWluZGlzcGVuc2FibGUgc3VyIFIgc29udCByZWdyb3Vww6lzIHNvdXMgdW5lIHNvcnRlIGRlIG3DqXRhLXBhcXVldCwgW3RpZHl2ZXJzZV0oaHR0cHM6Ly93d3cudGlkeXZlcnNlLm9yZy8pLiBQbHVzIHBhcnRpY3VsacOocmVtZW50IG5vdXMgYWxsb25zIHN1cnRvdXQgdXRpbGlzZXIgbGVzIHBhY2thZ2VzIFtzdHJpbmdyXShodHRwczovL3N0cmluZ3IudGlkeXZlcnNlLm9yZy8pIGV0IFtkcGx5cl0oaHR0cHM6Ly9kcGx5ci50aWR5dmVyc2Uub3JnLykgZGUgdGlkeXZlcnNlLiBPbiAiY2hhcmdlIiBlbnN1aXRlIHRpZHl2ZXJzZSBwb3VyIHBvdXZvaXIgZW4gdXRpbGlzZXIgbGVzIGZvbmN0aW9ucy4gDQoNCmBgYHtyLCBldmFsPUZBTFNFfQ0KaW5zdGFsbC5wYWNrYWdlcygidGlkeXZlcnNlIiwgZGVwZW5kZW5jaWVzID0gVFJVRSkNCmxpYnJhcnkodGlkeXZlcnNlKQ0KYGBgDQoNClVuZSBhdXRyZSBtYW5pw6hyZSBkJ3V0aWxpc2VyIGxlcyBmb25jdGlvbnMgZCd1biBwYWNrYWdlIHNhbnMgbGUgY2hhcmdlciBlc3QgZCfDqWNyaXJlIGB0aWR5dmVyc2U6OmxhX2ZvbmN0aW9uX3V0aWxpc8OpZWAuIERhbnMgY2UgdHV0b3JpZWwsIHRvdXMgbGVzIHBhY2thZ2VzIHV0aWxpc8OpcyBzZXJvbnQgY2hhcmfDqXMsIG1haXMgaidlc3NhaWVyYWkgdG91dCBkZSBtw6ptZSBkJ3V0aWxpc2VyIGNldHRlIGZvcm11bGF0aW9uIHBvdXIgcXVlIHZvdXMgcHVpc3NpZXogdm9pciDDoCBxdWVsIHBhY2thZ2UgYXBwYXJ0aWUgY2hhcXVlIGZvbmN0aW9uLiANCg0KUXVhbmQgb24gYSBwbHVzaWV1cnMgcGFja2FnZXMgw6AgaW5zdGFsbGVyIGV0IGNoYXJnZXIsIGlsIHBldXQgw6p0cmUgcGx1cyBjbGFpciBkZSBtZXR0cmUgY2VzIHBhY2thZ2VzIGRhbnMgdW5lIGxpc3RlLCBkJ2luc3RhbGxlciBjZXMgcGFja2FnZXMgdW5pcXVlbWVudCBzJ2lscyBuZSBzb250IHBhcyBkw6lqw6AgaW5zdGFsbMOpcywgcHVpcyBkZSBsZXMgY2hhcmdlci4NCg0KYGBge3J9DQpwYWNrYWdlX2xpc3QgPC0gYygNCiAgInRpZHl2ZXJzZSIsDQogICJoZXJlIiwgIyB1c2UgZm9yIHBhdGhzIGNyZWF0aW9uDQogICJqYW5pdG9yIiwgIyB1c2VmdWwgZnVuY3Rpb25zIGZvciBjbGVhbmluZyBpbXBvcnRlZCBkYXRhDQogICJiaWJsaW9uZXR3b3JrIiAjIGNyZWF0aW5nIGVkZ2VzDQopDQoNCmZvciAocCBpbiBwYWNrYWdlX2xpc3QpIHsNCiAgaWYgKHAgJWluJSBpbnN0YWxsZWQucGFja2FnZXMoKSA9PSBGQUxTRSkgew0KICAgIGluc3RhbGwucGFja2FnZXMocCwgZGVwZW5kZW5jaWVzID0gVFJVRSkNCiAgfQ0KICBsaWJyYXJ5KHAsIGNoYXJhY3Rlci5vbmx5ID0gVFJVRSkNCn0NCmBgYA0KDQpMZSBwYWNrYWdlICpoZXJlKiBwZXJtZXQgZGUgbWFuaXB1bGVyIHBsdXMgYWlzw6ltZW50IGxlcyBzZW50aWVycyBkZSB2b3MgZG9zc2llcnMgc3VyIHZvdHJlIG9yZGluYXRldXIuIENlbGEgZXN0IGltcG9ydGFudCBxdWFuZCB2b3VzIGltcG9ydGV6IGV0IGV4cG9ydGV6IGRlcyBmaWNoaWVycy4gUGFyIGV4ZW1wbGUsIGxhIGNvbW1hbmRlIHN1aXZhbnRlIHZvdXMgcGVybWV0IGRlIHNhdm9pciBvw7kgZXN0IHZvdHJlICJkb3NzaWVyIGRlIHRyYXZhaWwiIGFjdHVlbDoNCg0KYGBge3J9DQpoZXJlOjpoZXJlKCkgIyBvbiBlbnJlZ2lzdHJlIGxlIHNlbnRpZXIgZGFucyB1biAib2JqZXQiDQpgYGANCg0KUG91ciBwbHVzIGRlIHNpbXBsaWNpdMOpLCB2b3VzIHBvdXZleiBtZXR0cmUgbGVzIGRvbm7DqWVzIHRyYW5zbWlzZXMgcGFyIG1haWwsIGBkc2dlX2FydGljbGVzX3NhbXBsZS5jc3ZgIGRhbnMgY2UgZG9zc2llci4gVm91cyBuJ2F2ZXogYWxvcnMgcGx1cyBxdSfDoCBsZXMgaW1wb3J0ZXIgYXZlYyAgYHJlYWRyOjpyZWFkX2NzdjIoaGVyZSgiZHNnZV9hcnRpY2xlc19zYW1wbGUuY3N2IikpYC4gT3UgYmllbiB2b3VzIHBvdXZleiBtZXR0cmUgbGVzIGRvbm7DqWVzIGRhbnMgbGUgbcOqbWUgZG9zc2llciBxdWUgbGUgcHLDqXNlbnQgZmljaGllciBldCBsJ2ltcG9ydGVyIGNvbW1lIHN1aXRlOg0KDQpgYGB7cn0NCmRhdGEgPC0gcmVhZHI6OnJlYWRfY3N2MigiZHNnZV9hcnRpY2xlc19zYW1wbGUuY3N2IikNCmBgYA0KDQpPbiBwZXV0IGTDqXNvcm1haXMgcmVnYXJkZXIgw6AgcXVvaSByZXNzZW1ibGUgbm9zIGRvbm7DqWVzOg0KDQpgYGB7cn0NCmRhdGENCmBgYA0KDQojIE5ldHRveWFnZSBkZXMgZG9ubsOpZXMNCg0KSWwgeSBhIHBsdXNpZXVycyBjaG9zZXMgw6AgbmV0dG95ZXIgOg0KDQotIE5vdXMgYXZvbnMgcGx1c2lldXJzIGBhdXRldXJzYCBwYXIgZG9jdW1lbnQuIFBvdXIgY2VydGFpbmVzIGFuYWx5c2VzLCBwYXIgZXhlbXBsZSBsZXMgcsOpc2VhdXggZGUgY28tYXV0ZXVycywgaWwgZXN0IHByw6lmw6lyYWJsZSBkJ2F2b2lyIHVuZSAidGFibGUgZGVzIGF1dGV1cnMiIHF1aSBhc3NvY2llIGNoYXF1ZSBhdXRldXIgw6AgdW5lIGxpc3RlIGQnYXJ0aWNsZXMgOw0KLSBOb3VzIGF2b25zIHBsdXNpZXVycyBgYWZmaWxpYXRpb25zYCBwYXIgYXJ0aWNsZSBhaW5zaSBxdWUgcGx1c2lldXJzIGBhdXRob3JzX3dpdGhfYWZmaWxpYXRpb25zYC4gQ2VsYSBub3VzIHBlcm1ldCBkZSByZWxpZXIgbGVzIGF1dGV1cnMgw6AgbGV1cnMgYWZmaWxpYXRpb25zLCBtYWlzIGzDoCBlbmNvcmUgbm91cyBkZXZvbnMgbGUgc8OpcGFyZXIgZW4gYXV0YW50IGRlIGxpZ25lcyBxdWUgZCdhdXRldXJzIChzaSBqZSBuZSBtZSB0cm9tcGUgcGFzLCBpbCBzZW1ibGUgcXUnaWwgbid5IGFpdCBxdSd1bmUgc2V1bGUgYWZmaWxpYXRpb24gcGFyIGF1dGV1ciBkYW5zIGNlIGpldSBkZSBkb25uw6llcykgOw0KLSBgcsOpZsOpcmVuY2VzYCA7DQotIEV2ZW50dWVsbGVtZW50IHBvdXIgc8OpcGFyZXIgYGF1dGhvcl9rZXl3b3Jkc2AgZXQgYGluZGV4X2tleXdvcmRzYCBzaSB2b3VzIHZvdWxleiBsJ3V0aWxpc2VyLg0KDQpEYW5zIGNlIHF1aSBzdWl0LCBub3VzIG5ldHRveW9ucyBgZGF0YWAgYWZpbiBkZSBwcm9kdWlyZSAyIGBkYXRhLmZyYW1lc2Agc3VwcGzDqW1lbnRhaXJlcyA6DQoNCi0gdW4gYGRhdGEuZnJhbWVgIHF1aSBhc3NvY2llIGNoYXF1ZSBhcnRpY2xlIMOgIGxhIGxpc3RlIGRlcyByw6lmw6lyZW5jZXMgcXUnaWwgY2l0ZSAodW4gYXJ0aWNsZSBhIGF1dGFudCBkZSBsaWduZXMgcXVlIGxlIG5vbWJyZSBkZSByw6lmw6lyZW5jZXMgY2l0w6llcykgOiBjJ2VzdCB1biB0YWJsZWF1IGRlICJjaXRhdGlvbnMgZGlyZWN0ZXMiIDsNCi0gdW5lIGxpc3RlIGRlIHRvdXRlcyBsZXMgcsOpZsOpcmVuY2VzIGNpdMOpZXMsIHF1aSBwZXJtZXQgZGUgcmV0cm91dmVyIGxlcyByw6lmw6lyZW5jZXMgaWRlbnRpcXVlcyBkYW5zIGxlIHRhYmxlYXUgZGUgY2l0YXRpb25zIGRpcmVjdGVzLg0KDQoNCiMjIChUcsOocykgYnLDqHZlIGludHJvZHVjdGlvbiBhdXggcmVnZXggDQoNClBvdXIgbmV0dG95ZXIgbGVzIGRvbm7DqWVzIG5vdXMgYWxsb25zIHV0aWxpc2VyIHVuIGxhbmdhZ2UgcGFydGljdWxpZXIsIHRyw6hzIHJlYnV0YW50IGF1IGTDqXBhcnQ6IGxlcyBleHByZXNzaW9ucyByw6lndWxpw6hyZXMgKHJlZ3VsYXIgZXhwcmVzc2lvbnMgb3UgcmVnZXggZW4gYW5nbGFpcykuIExlcyByZWdleCBwZXJtZXR0ZW50IGRlIHJlcMOpcmVyIGRhbnMgdW4gZW5zZW1ibGUgZGUgY2FyYWN0w6hyZXMgY2VydGFpbmVzIHBhcnRpY3VsYXJpdMOpcy4gVm91cyBwb3V2ZXogcGFyIGV4ZW1wbGUgZW5zdWl0ZSBzdXBwcmltZXIsIGV4dHJhaXJlLCBtb2RpZmllciBjZXMgcGFydGljdWxhcml0w6lzLiANCg0KYGBge3J9DQpwaHJhc2Vfc3R1cGlkZSA8LSAiTm91cyBzb21tZSBlbiBjb3VycyBsZSB2ZW5kcmVkaSA0IGbDqXZyaWVyIGV0IGwnaW50ZXJ2ZW5hbnQgY29tbWVuY2Ugw6Agbm91cyBwYXJsZXIgZGUgcXVlbHF1ZSBjaG9zZSBkJ8OpdHJhbmdlOiBsZXMgcmVnZXguIg0KYGBgDQoNCkRvbm5vbnMgbm91cyBxdWVscXVlcyB0w6JjaGVzOg0KDQotIGV4dHJhaXJlIGxlIHByZW1pZXIgbW90LCBhaW5zaSBxdWUgbGUgZGVybmllcg0KDQpgYGB7cn0NCnByZW1pZXJfbW90IDwtIHN0cmluZ3I6OnN0cl9leHRyYWN0KHBocmFzZV9zdHVwaWRlLCAiXltBLXpdezR9IikNCg0KcHJlbWllcl9tb3QgPC0gc3RyaW5ncjo6c3RyX2V4dHJhY3QocGhyYXNlX3N0dXBpZGUsICJeW0Etel0rIikgIyBwZXV0IMOqdHJlIHNpbXBsaWZpw6kNCg0KZGVybmllcl9tb3QgPC0gc3RyaW5ncjo6c3RyX2V4dHJhY3QocGhyYXNlX3N0dXBpZGUsICJbQS16XSskIikgIyBuZSBtYXJjaGUgcGFzDQoNCmRlcm5pZXJfbW90IDwtIHN0cmluZ3I6OnN0cl9leHRyYWN0KHBocmFzZV9zdHVwaWRlLCAiW0Etel0rXFwuJCIpICMgcGV1dCDDqnRyZSBhbcOpbGlvcsOpDQoNCmRlcm5pZXJfbW90IDwtIHN0cmluZ3I6OnN0cl9leHRyYWN0KHBocmFzZV9zdHVwaWRlLCAiW0Etel0rKD89XFwuJCkiKQ0KYGBgDQoNCi0gZXh0cmFpcmUgbGEgZGF0ZSANCg0KYGBge3J9DQpkYXRlIDwtIHN0cl9leHRyYWN0KHBocmFzZV9zdHVwaWRlLCAiW0Etel0rIFxcZCBbQS16XSsiKSAjIG5lIG1hcmNoZSBwYXMgcG91ciBsZSBtb2lzDQoNCmRhdGUgPC0gc3RyX2V4dHJhY3QocGhyYXNlX3N0dXBpZGUsICJbQS16XSsgXFxkIFs6bGV0dGVyOl0rIikNCg0KYGBgDQoNCi0gcmVtcGxhY2VyIGxhIGRlcm5pw6hyZSBwYXJ0aWUgZHUgdGV4dGUgYXByw6hzIGxlcyBkZXV4IHBvaW50cw0KDQpgYGB7cn0NCm5vdXZlbGxlX3BocmFzZSA8LSBzdHJfcmVwbGFjZShwaHJhc2Vfc3R1cGlkZSwgIjouKiIsICI6IG1haXMgamUgbSdlbiBzb3V2aWVucyBwbHVzLiIpDQpgYGANCg0KLSBzdXBwcmltZXIgbGVzIG1vdHMgZGUgbW9pbnMgZGUgMiBsZXR0cmVzIG91IG1vaW5zDQoNCmBgYHtyfQ0KcGhyYXNlX2RlY29uc3RydWl0ZSA8LSBzdHJfcmVtb3ZlX2FsbChwaHJhc2Vfc3R1cGlkZSwgIiBbOmxldHRlcjpdezEsMn0gIikNCg0KcGhyYXNlX2RlY29uc3RydWl0ZSA8LSBzdHJfcmVtb3ZlX2FsbChwaHJhc2Vfc3R1cGlkZSwgIig/PD0gKVs6bGV0dGVyOl17MSwyfSg/PSApIikNCmBgYA0KDQpNYWludGVuYW50LCB2b3VzIGFsbGV6IHBvdXZvaXIgdG91dCBjb21wcmVuZHJlIGF1IG5ldHRveWFnZSBkZXMgZG9ubsOpZXMgcXVpIHN1aXQuIE91IHBldXQtw6p0cmUgcGFzLi4uDQoNCiMjIE5ldHRveWFnZSBkZXMgcsOpZsOpcmVuY2VzDQoNCkxhIHByZW1pw6hyZSBjaG9zZSDDoCBmYWlyZSBlc3QgZGUgY3LDqWVyIHVuIGlkZW50aWZpYW50IHBvdXIgbGVzIGFydGljbGVzIGNpdGFudCwgY2UgcXVpIG5vdXMgcGVybWV0dHJhIGRlIGZhaXJlIGxlIHBvbnQgZW50cmUgbGVzIGFydGljbGVzIGNpdGFudCBldCBsZXMgcsOpZsOpcmVuY2VzIGNpdMOpZXM6DQoNCmBgYHtyfQ0KZGF0YSA8LSBkYXRhICU+JSANCiAgbXV0YXRlKGNpdGluZ19pZCA9IHBhc3RlMCgiQSIsIDE6bigpKSkNCmBgYA0KDQpFeHRyYXlvbnMgZCdhYm9yZCBsYSBjb2xvbm5lIGByZWZlcmVuY2VzYCBlbiBnYXJkYW50IHVuIGlkZW50aWZpYW50IHBvdXIgbCdhcnRpY2xlIGNpdGFudC4gTm91cyBtZXR0b25zIGNoYXF1ZSByw6lmw6lyZW5jZSBjaXTDqWUgcGFyIHVuIGFydGljbGUgc3VyIHVuZSBsaWduZSBzw6lwYXLDqWUsIGVuIHV0aWxpc2FudCBsZSBmYWl0IHF1ZSBsZXMgcsOpZsOpcmVuY2VzIHNvbnQgc8OpcGFyw6llcyBwYXIgdW4gcG9pbnQtdmlyZ3VsZS4gTm91cyBjcsOpb25zIHVuIGlkZW50aWZpYW50IHBvdXIgY2hhcXVlIHLDqWbDqXJlbmNlLg0KDQpgYGB7ciBleHRyYWN0LXJlZmVyZW5jZXN9DQojJyAjIyBFeHRyYWN0aW5nIGFuZCBjbGVhbmluZyByZWZlcmVuY2VzDQpyZWZlcmVuY2VzX2V4dHJhY3QgPC0gZGF0YSAlPiUgDQogIGZpbHRlcighIGlzLm5hKHJlZmVyZW5jZXMpKSAlPiUgIyBlbmxldmVyIGxlcyBhcnRpY2xlcyBxdWkgbidvbnQgcGFzIGRlIHLDqWbDqXJlbmNlcw0KICBzZWxlY3QoY2l0aW5nX2lkLCByZWZlcmVuY2VzKSAlPiUgDQogIHNlcGFyYXRlX3Jvd3MocmVmZXJlbmNlcywgc2VwID0gIjsgIikgJT4lICMgb24gbWV0IHVuZSByw6lmw6lyZW5jZSBwYXIgbGlnbmUNCiAgbXV0YXRlKGlkX3JlZiA9IDE6bigpKSAlPiUgIyBvbiBkb25uZSB1biBpZGVudGlmaWFudCBhdXggcsOpZsOpcmVuY2VzDQogIGFzX3RpYmJsZQ0KDQpyZWZlcmVuY2VzX2V4dHJhY3QNCmBgYA0KDQpOb3RyZSBidXQgZMOpc29ybWFpcyBlc3QgZGUgcG91dm9pciBleHRyYWlyZSBsZXMgbcOpdGFkb25uw6llcyBkZSBjaGFxdWUgcsOpZsOpcmVuY2U6IGxlcyBhdXRldXJzLCBsYSBkYXRlIGRlIHB1YmxpY2F0aW9uLCBsZSB0aXRyZSwgbGUgam91cm5hbCwgZXRjLi4uIEwnb2JqZWN0aWYgZXN0IGQnYXZvaXIgZGVzIGluZm9ybWF0aW9ucyByZWxhdGl2ZW1lbnQgc3RhbmRhcmRpc8OpZXMsIHBvdXIgcG91dm9pciBlbnN1aXRlIGlkZW50aWZpZXIgcXVlbGxlcyBzb250IGxlcyByw6lmw6lyZW5jZXMgcXVpIHNvbnQgbGVzIG3Dqm1lcy4gVW5lIHByZW1pw6hyZSDDqXRhcGUgZXN0IGQnZXh0cmFpcmUgbGVzIGF1dGV1cnMgYWluc2kgcXVlIGwnYW5uw6llIGRlIHB1YmxpY2F0aW9uLiBPbiBlbmzDqHZlIGxlcyBhdXRldXJzIGRlIGxhIHLDqWbDqXJlbmNlIHByaW5jaXBhbGUgZXQgb24gc3RvY2sgbCdpbmZvIHJlc3RhbnRlLCBxdSdvbiB2YSBuZXR0b3llciwgZGFucyB1bmUgY29sb25uZSBzw6lwYXLDqWUuDQoNCmBgYHtyIHVzZWZ1bC1yZWdleH0NCmV4dHJhY3RfYXV0aG9ycyA8LSAiLipbOnVwcGVyOl1bOmFscGhhOl0rKCBKciguKT8pPywgKFtBLVpdXFwuWyAtXT8pPyhbQS1aXVxcLlsgLV0/KT8oW0EtWl1cXC4pP1tBLVpdXFwuIg0KZXh0cmFjdF95ZWFyX2JyYWNrZXRzIDwtICIoPzw9XFwoKVxcZHs0fSg/PVxcKSkiDQpleHRyYWN0X3BhZ2VzIDwtICIoPzw9IChwKT9wXFwuICkoW0EtWl0pP1xcZCsoLShbQS1aXSk/XFxkKyk/Ig0KZXh0cmFjdF92b2x1bWVfYW5kX251bWJlciA8LSAiKD88PSggfF4pPylcXGQrIFxcKFxcZCsoLVxcZCspP1xcKSINCg0KY2xlYW5pbmdfcmVmZXJlbmNlcyA8LSByZWZlcmVuY2VzX2V4dHJhY3QgJT4lIA0KICBtdXRhdGUoYXV0aG9ycyA9IHN0cl9leHRyYWN0KHJlZmVyZW5jZXMsIHBhc3RlMChleHRyYWN0X2F1dGhvcnMsICIoPz0sICkiKSksDQogICAgICAgICByZW1haW5pbmdfcmVmID0gc3RyX3JlbW92ZShyZWZlcmVuY2VzLCBwYXN0ZTAoZXh0cmFjdF9hdXRob3JzLCAiLCAiKSksICMgY2xlYW5pbmcgZnJvbSBhdXRob3JzDQogICAgICAgICBpc19hcnRpY2xlID0gISBzdHJfZGV0ZWN0KHJlbWFpbmluZ19yZWYsICJeXFwoWzpkaWdpdDpdezR9IiksIA0KICAgICAgICAgeWVhciA9IHN0cl9leHRyYWN0KHJlZmVyZW5jZXMsIGV4dHJhY3RfeWVhcl9icmFja2V0cykgJT4lIGFzLmludGVnZXIpDQoNCmNsZWFuaW5nX3JlZmVyZW5jZXMNCmBgYA0KDQpBIHBhcnRpciBkZSBtYWludGVuYW50LCBpbCB5IGEgYmVhdWNvdXAgdHJvcCBkJ8OpdGFwZXMgcG91ciBwb3V2b2lyIHRvdXQgZMOpdGFpbGxlciwgbWFpcyBsYSBzdHJhdMOpZ2llIGfDqW7DqXJhbGUgZXN0IGRlIHPDqXBhcmVyIGxlcyByw6lmw6lyZW5jZXMgc3VpdmFudCBsYSBwb3NpdGlvbiBkZSBsJ2FubsOpZSBkZSBwdWJsaWNhdGlvbjogbGUgcGx1cyBzaW1wbGUgw6AgbmV0dG95ZXIgKGBjbGVhbmluZ19hcnRpY2xlc2ApIGVzdCBxdWFuZCBsYSB0aXRyZSBlc3QgYXZhbnQgbGEgZGF0ZSBkZSBwdWJsaWNhdGlvbiBjYXIgY2VsYSBwZXJtZXQgZCdleHRyYWlyZSBwbHVzIHNpbXBsZW1lbnQgbGUgdGl0cmUsIGV0IGRvbmMgYXVzc2ksIGVuIGfDqW7DqXJhbCwgbGUgbm9tIGR1IGpvdXJuYWwgcXVpIHZhIHN1aXZyZSBsYSBkYXRlIGRlIHB1YmxpY2F0aW9uLiBPbiBleHRyYWl0IMOpZ2FsZW1lbnQgZGVzIGluZm9ybWF0aW9ucyB1dGlsZXMgY29tbWUgbGUgdm9sdW1lLCBsZSBudW3DqXJvLCBsZXMgcGFnZXMsIGV0IGxlIERPSS4NCg0KYGBge3IgY2xlYW4tYXJ0aWNsZXN9DQpjbGVhbmluZ19hcnRpY2xlcyA8LSBjbGVhbmluZ19yZWZlcmVuY2VzICU+JSANCiAgZmlsdGVyKGlzX2FydGljbGUgPT0gVFJVRSkgJT4lIA0KICBtdXRhdGUodGl0bGUgPSBzdHJfZXh0cmFjdChyZW1haW5pbmdfcmVmLCAiLiooPz1cXChcXGR7NH0pIiksICMgcHJlIGRhdGUgZXh0cmFjdGlvbg0KICAgICAgICAgam91cm5hbF90b19jbGVhbiA9IHN0cl9leHRyYWN0KHJlbWFpbmluZ19yZWYsICIoPzw9XFxkezR9XFwpKS4qIiksICMgcG9zdCBkYXRlIGV4dHJhY3Rpb24NCiAgICAgICAgIGpvdXJuYWxfdG9fY2xlYW4gPSBzdHJfcmVtb3ZlKGpvdXJuYWxfdG9fY2xlYW4sICJeLCIpICU+JSBzdHJfdHJpbSgiYm90aCIpLCAjIGNsZWFuaW5nIGEgYml0IHRoZSBqb3VybmFsIGluZm8gY29sdW1uDQogICAgICAgICBwYWdlcyA9IHN0cl9leHRyYWN0KGpvdXJuYWxfdG9fY2xlYW4sIGV4dHJhY3RfcGFnZXMpLCAjIGV4dHJhY3RpbmcgcGFnZXMNCiAgICAgICAgIHZvbHVtZV9hbmRfbnVtYmVyID0gc3RyX2V4dHJhY3Qoam91cm5hbF90b19jbGVhbiwgZXh0cmFjdF92b2x1bWVfYW5kX251bWJlciksICMgZXh0cmFjdGluZyBzdGFuZGFyZCB2b2x1bWVudCBhbmQgbnVtYmVyOiBYIChYKQ0KICAgICAgICAgam91cm5hbF90b19jbGVhbiA9IHN0cl9yZW1vdmUoam91cm5hbF90b19jbGVhbiwgIiAocCk/cFxcLiAoW0EtWl0pP1xcZCsoLShbQS1aXSk/XFxkKyk/IiksICMgY2xlYW4gZnJvbSBleHRyYWN0ZWQgcGFnZXMNCiAgICAgICAgIGpvdXJuYWxfdG9fY2xlYW4gPSBzdHJfcmVtb3ZlKGpvdXJuYWxfdG9fY2xlYW4sICIoIHxeKT9cXGQrIFxcKFxcZCsoLVxcZCspP1xcKSIpLCAjIGNsZWFuIGZyb20gZXh0cmFjdGVkIHZvbHVtZSBhbmQgbnVtYmVyDQogICAgICAgICB2b2x1bWVfYW5kX251bWJlciA9IGlmZWxzZShpcy5uYSh2b2x1bWVfYW5kX251bWJlciksIHN0cl9leHRyYWN0KGpvdXJuYWxfdG9fY2xlYW4sICIoPzw9ICkoW0EtWl0pP1xcZCsoLVxcZCspPyIpLCB2b2x1bWVfYW5kX251bWJlciksICMgZXh0cmFjdCByZW1haW5pbmcgbnVtYmVycw0KICAgICAgICAgam91cm5hbF90b19jbGVhbiA9IHN0cl9yZW1vdmUoam91cm5hbF90b19jbGVhbiwgIiAoW0EtWl0pP1xcZCsoLVxcZCspPyIpLCAjIGNsZWFuIGZyb20gcmVtYWluaW5nIG51bWJlcnMNCiAgICAgICAgIGpvdXJuYWwgPSBzdHJfcmVtb3ZlX2FsbChqb3VybmFsX3RvX2NsZWFuLCAiXls6cHVuY3Q6XSsoICk/WzpwdW5jdDpdKyggKT98KD88PSwoICk/KVs6cHVuY3Q6XSsoICk/KFs6cHVuY3Q6XSk/fFs6cHVuY3Q6XSggKT9bOnB1bmN0Ol0rKCApPyQiKSwgIyBleHRyYWN0IGpvdXJuYWwgaW5mbyBieSByZW1vdmluZyBpbmFwcHJvcHJpYXRlIHB1bmN0dWF0aW9ucw0KICAgICAgICAgZmlyc3RfcGFnZSA9IHN0cl9leHRyYWN0KHBhZ2VzLCAiXFxkKyIpLA0KICAgICAgICAgdm9sdW1lID0gc3RyX2V4dHJhY3Qodm9sdW1lX2FuZF9udW1iZXIsICJcXGQrIiksDQogICAgICAgICBpc3N1ZSA9IHN0cl9leHRyYWN0KHZvbHVtZV9hbmRfbnVtYmVyLCAiKD88PVxcKClcXGQrKD89XFwpKSIpLA0KICAgICAgICAgcHVibGlzaGVyID0gaWZlbHNlKGlzLm5hKGZpcnN0X3BhZ2UpICYgaXMubmEodm9sdW1lKSAmIGlzLm5hKGlzc3VlKSAmICEgc3RyX2RldGVjdChqb3VybmFsLCAiKFd8dylvcmtpbmcgKFB8cCk/YXBlciIpLCBqb3VybmFsLCBOQSksDQogICAgICAgICBib29rX3RpdGxlID0gaWZlbHNlKHN0cl9kZXRlY3Qoam91cm5hbCwgIiAoRXxlKWQocyk/XFwufCAoRXxlKWRpdGUoZHx1cnMpPyAiKSwgam91cm5hbCwgTkEpLCAjIEluY29sbGVjdGlvbiBhcnRpY2xlOiBUaXRsZSBvZiB0aGUgYm9vayBoZXJlDQogICAgICAgICBib29rX3RpdGxlID0gc3RyX2V4dHJhY3QoYm9va190aXRsZSwgIltBLXogXSsoPz0sKSIpLCAjIGtlZXBpbmcgb25seSB0aGUgdGl0bGUgb2YgdGhlIGJvb2sNCiAgICAgICAgIHB1Ymxpc2hlciA9IGlmZWxzZSghaXMubmEoYm9va190aXRsZSksIE5BLCBwdWJsaXNoZXIpLCAjIGlmIHdlIGhhdmUgYW4gaW5jb2xsZWN0aW9uIGFydGljbGUsIHRoYXQncyBub3QgYSBib29rLCBzbyBubyBwdWJsaXNoZXINCiAgICAgICAgIGpvdXJuYWwgPSBpZmVsc2UoIWlzLm5hKGJvb2tfdGl0bGUpIHwgISBpcy5uYShwdWJsaXNoZXIpLCBOQSwgam91cm5hbCksICMgcmVtb3Zpbmcgam91cm5hbCBhcyB3aGF0IHdlIGhhdmUgaXMgYSBib29rDQogICAgICAgICBwdWJsaXNoZXIgPSBpZmVsc2UoaXMubmEocHVibGlzaGVyKSAmIHN0cl9kZXRlY3Qoam91cm5hbCwgIihXfHcpb3JraW5nIChQfHApP2FwZXIiKSwgam91cm5hbCwgcHVibGlzaGVyKSwgIyBhZGRpbmcgd29ya2luZyBwYXBlciBwdWJsaXNoZXIgaW5mb3JtYXRpb24gaW4gcHVibGlzaGVyIGNvbHVtbg0KICAgICAgICAgam91cm5hbCA9IGlmZWxzZShzdHJfZGV0ZWN0KGpvdXJuYWwsICIoV3x3KW9ya2luZyAoUHxwKT9hcGVyIiksICJXb3JraW5nIFBhcGVyIiwgam91cm5hbCkpDQoNCmNsZWFuZWRfYXJ0aWNsZXMgPC0gY2xlYW5pbmdfYXJ0aWNsZXMgJT4lIA0KICBzZWxlY3QoY2l0aW5nX2lkLCBpZF9yZWYsIGF1dGhvcnMsIHllYXIsIHRpdGxlLCBqb3VybmFsLCB2b2x1bWUsIGlzc3VlLCBwYWdlcywgZmlyc3RfcGFnZSwgYm9va190aXRsZSwgcHVibGlzaGVyLCByZWZlcmVuY2VzKQ0KDQpjbGVhbmluZ19ub25fYXJ0aWNsZXMgPC0gY2xlYW5pbmdfcmVmZXJlbmNlcyAlPiUgDQogIGZpbHRlcihpc19hcnRpY2xlID09IEZBTFNFKSAlPiUgDQogIG11dGF0ZShyZW1haW5pbmdfcmVmID0gc3RyX3JlbW92ZShyZW1haW5pbmdfcmVmLCAiXFwoXFxkezR9XFwpKCwpPyAiKSwNCiAgICAgICAgIHRpdGxlID0gc3RyX2V4dHJhY3QocmVtYWluaW5nX3JlZiwgIi4qKD89LCAsKSIpLA0KICAgICAgICAgcGFnZXMgPSBzdHJfZXh0cmFjdChyZW1haW5pbmdfcmVmLCAiKD88PSAocCk/cFxcLiApKFtBLVpdKT9cXGQrKC0oW0EtWl0pP1xcZCspPyIpLCAjIGV4dHJhY3RpbmcgcGFnZXMNCiAgICAgICAgIHZvbHVtZV9hbmRfbnVtYmVyID0gc3RyX2V4dHJhY3QocmVtYWluaW5nX3JlZiwgIig/PD0oIHxeKT8pXFxkKyBcXChcXGQrKC1cXGQrKT9cXCkiKSwgIyBleHRyYWN0aW5nIHN0YW5kYXJkIHZvbHVtZW50IGFuZCBudW1iZXI6IFggKFgpDQogICAgICAgICByZW1haW5pbmdfcmVmID0gc3RyX3JlbW92ZShyZW1haW5pbmdfcmVmLCAiIChwKT9wXFwuIChbQS1aXSk/XFxkKygtKFtBLVpdKT9cXGQrKT8iKSwgIyBjbGVhbiBmcm9tIGV4dHJhY3RlZCBwYWdlcw0KICAgICAgICAgcmVtYWluaW5nX3JlZiA9IHN0cl9yZW1vdmVfYWxsKHJlbWFpbmluZ19yZWYsICIuKiwgLCIpLCAjIGNsZWFuIGRhdGVzIGFuZCBhbHJlYWR5IGV4dHJhY3RlZCB0aXRsZXMNCiAgICAgICAgIHJlbWFpbmluZ19yZWYgPSBzdHJfcmVtb3ZlKHJlbWFpbmluZ19yZWYsICIoIHxeKT9cXGQrIFxcKFxcZCsoLVxcZCspP1xcKSIpLCAjIGNsZWFuIGZyb20gZXh0cmFjdGVkIHZvbHVtZSBhbmQgbnVtYmVyDQogICAgICAgICB2b2x1bWVfYW5kX251bWJlciA9IGlmZWxzZShpcy5uYSh2b2x1bWVfYW5kX251bWJlciksIHN0cl9leHRyYWN0KHJlbWFpbmluZ19yZWYsICIoPzw9ICkoW0EtWl0pP1xcZCsoLVxcZCspPyIpLCB2b2x1bWVfYW5kX251bWJlciksICMgZXh0cmFjdCByZW1haW5pbmcgbnVtYmVycw0KICAgICAgICAgcmVtYWluaW5nX3JlZiA9IHN0cl9yZW1vdmUocmVtYWluaW5nX3JlZiwgIiAoW0EtWl0pP1xcZCsoLVxcZCspPyIpLCAjIGNsZWFuIGZyb20gcmVtYWluaW5nIG51bWJlcnMNCiAgICAgICAgIGpvdXJuYWwgPSBpZmVsc2Uoc3RyX2RldGVjdChyZW1haW5pbmdfcmVmLCAiKFd8dylvcmtpbmcgKFB8cClhcGVyIiksICJXb3JraW5nIFBhcGVyIiwgTkEpLA0KICAgICAgICAgam91cm5hbCA9IGlmZWxzZShzdHJfZGV0ZWN0KHJlbWFpbmluZ19yZWYsICIoTXxtKWFudXNjcmlwdCIpLCAiTWFudXNjcmlwdCIsIGpvdXJuYWwpLA0KICAgICAgICAgam91cm5hbCA9IGlmZWxzZShzdHJfZGV0ZWN0KHJlbWFpbmluZ19yZWYsICIoTXxtKWltZW8iKSwgIk1pbWVvIiwgam91cm5hbCksDQogICAgICAgICBwdWJsaXNoZXIgPSBpZmVsc2UoaXMubmEoam91cm5hbCksIHJlbWFpbmluZ19yZWYsIE5BKSAlPiUgc3RyX3RyaW0oImJvdGgiKSwNCiAgICAgICAgIGZpcnN0X3BhZ2UgPSBzdHJfZXh0cmFjdChwYWdlcywgIlxcZCsiKSwNCiAgICAgICAgIHZvbHVtZSA9IHN0cl9leHRyYWN0KHZvbHVtZV9hbmRfbnVtYmVyLCAiXFxkKyIpLA0KICAgICAgICAgaXNzdWUgPSBzdHJfZXh0cmFjdCh2b2x1bWVfYW5kX251bWJlciwgIig/PD1cXCgpXFxkKyg/PVxcKSkiKSwNCiAgICAgICAgIGJvb2tfdGl0bGUgPSBOQSkgIyB0byBiZSBzeW1ldHJpYyB3aXRoICJjbGVhbmVkX2FydGljbGVzIg0KDQpjbGVhbmVkX25vbl9hcnRpY2xlcyA8LSBjbGVhbmluZ19ub25fYXJ0aWNsZXMgJT4lIA0KICBzZWxlY3QoY2l0aW5nX2lkLCBpZF9yZWYsIGF1dGhvcnMsIHllYXIsIHRpdGxlLCBqb3VybmFsLCB2b2x1bWUsIGlzc3VlLCBwYWdlcywgZmlyc3RfcGFnZSwgYm9va190aXRsZSwgcHVibGlzaGVyLCByZWZlcmVuY2VzKQ0KDQojIG1lcmdpbmcgdGhlIHR3byBmaWxlcy4NCmNsZWFuZWRfcmVmIDwtIHJiaW5kKGNsZWFuZWRfYXJ0aWNsZXMsIGNsZWFuZWRfbm9uX2FydGljbGVzKQ0KDQojJyBOb3cgd2UgaGF2ZSBhbGwgdGhlIHJlZmVyZW5jZXMsIHdlIGNhbiBkbyBhIGJpdCBvZiBjbGVhbmluZyBvbiB0aGUgYXV0aG9ycyBuYW1lLA0KIycgYW5kIGV4dHJhY3QgdXNlZnVsIGluZm9ybWF0aW9uLCBsaWtlIERPSSwgZm9yIG1hdGNoaW5nIGxhdGVyLg0KDQpjbGVhbmVkX3JlZiA8LSBjbGVhbmVkX3JlZiAlPiUgDQogIG11dGF0ZShhdXRob3JzID0gc3RyX3JlbW92ZShhdXRob3JzLCAiIEpyXFwuIiksICMgc3RhbmRhcmRpc2luZyBhdXRob3JzIG5hbWUgdG8gZmF2b3VyIG1hdGNoaW5nIGxhdGVyDQogICAgICAgICBhdXRob3JzID0gc3RyX3JlbW92ZShhdXRob3JzLCAiXlxcKFxcZHs0fVxcKShcXC4pPyggKT8iKSwNCiAgICAgICAgIGF1dGhvcnMgPSBzdHJfcmVtb3ZlKGF1dGhvcnMsICJeLCAiKSwNCiAgICAgICAgIGF1dGhvcnMgPSBpZmVsc2UoaXMubmEoYXV0aG9ycyksIHN0cl9leHRyYWN0KHJlZmVyZW5jZXMsICIuKls6dXBwZXI6XVxcLig/PSBcXGR7NH0pIiksIGF1dGhvcnMpLCAjIHNwZWNpZmljIGNhc2UNCiAgICAgICAgIGpvdXJuYWwgPSBzdHJfcmVtb3ZlKGpvdXJuYWwsICJbOnB1bmN0Ol0kIiksICMgcmVtb3ZlIHVubmVjZXNzYXJ5IHB1bmN0dWF0aW9ucyBhdCB0aGUgZW5kDQogICAgICAgICBkb2kgPSBzdHJfZXh0cmFjdChyZWZlcmVuY2VzLCAiKD88PURPSSg6KT8gKS4qfCg/PD1cXC9kb2lcXC5vcmdcXC8pLioiKSwNCiAgICAgICAgIHBpaSA9IHN0cl9leHRyYWN0KGRvaSwgIig/PD1QSUkgKS4qIiksDQogICAgICAgICBkb2kgPSBzdHJfcmVtb3ZlKGRvaSwgIiwuKiIpLCAjIGNsZWFuaW5nIGRvaQ0KICAgICAgICAgcGlpID0gc3RyX3JlbW92ZShwaWksICIsLioiKSwgIyBjbGVhbmluZyBwaWkNCiAgKQ0KDQpjbGVhbmVkX3JlZg0KYGBgDQoNCiMjIE1hdGNoZXIgbGVzIHLDqWbDqXJlbmNlcyBlbnNlbWJsZQ0KDQpSZWdhcmRvbnMgZGUgcGx1cyBwcsOqdCBsYSBwcmVtacOocmUgbGlnbmUgZGUgbm90cmUgdGFibGVhdSBkZXMgcsOpZsOpcmVuY2VzIHBvdXIgbWlldXggY29tcHJlbmRyZSBsZXMgcHJvYmzDqG1lcyBwb3RlbnRpZWxzOg0KDQpgYGB7cn0NCmNsZWFuZWRfcmVmWzEsXQ0KYGBgDQoNCk5vdHJlIGJ1dCBlc3QgZGUgdHJvdXZlciB0b3V0ZXMgbGVzIGNpdGF0aW9ucyBxdWkgcmVudm9pZW50IGF1IG3Dqm1lIGFydGljbGUuIEVuIGV4dHJheWFudCBsZXMgaW5mb3JtYXRpb25zIGRlIG1hbmnDqHJlIHN5c3TDqW1hdGlxdWUsIG9uIGV4Y2x1dCBsZSBwcm9ibMOobWUgZGVzICJzdHlsZXMiIGRlIGNpdGF0aW9uIGRpZmbDqXJlbnQuIEF1LWRlbMOgIGRlIGwnaW1wZXJmZWN0aW9uIGR1IG5ldHRveWFnZSBjaS1kZXNzdXMsIGlsIHJlc3RlIHBsdXNpZXVycyBwcm9ibMOobWVzIHF1aSBwZXV2ZW50IG5vdXMgZW1ww6pjaGVyIGRlIG1hdGNoZXIgbGVzIHLDqWbDqXJlbmNlcyBjb3JyZWN0ZW1lbnQ6DQoNCi0gSW5mb3JtYXRpb25zIG5vbi1yw6lmw6lyZW5jw6llczsNCi0gRXJyZXVyczsNCi0gRGlmZsOpcmVudGVzIG1hbmnDqHJlcyBkJ8OpY3JpcmUgbGUgam91cm5hbDsNCi0gVmFyaWF0aW9ucyBkYW5zIGwnw6ljcml0dXJlIGRlcyB0aXRyZXMgKHNvdXZlbnQgbGnDqSDDoCBsYSBwb25jdHVhdGlvbiBvdSBhdXggYXJ0aWNsZXMpOw0KLSBEaWZmw6lyZW5jZXMgZGFucyBsJ8OpY3JpdHVyZSBkZXMgbm9tcyAoc291dmVudCBhbHRlcm5hbmNlIGVudHJlIHVuZSBvdSBkZXV4IGluaXRpYWxlcykuDQoNCkxlIGNvbXByb21pcyBjb25zaXN0ZSDDoCBmYWlyZSBjb3JyZXNwb25kcmUgYXV0YW50IGRlIHZyYWlzIHBvc2l0aWZzIHF1ZSBwb3NzaWJsZSAocsOpZsOpcmVuY2VzIGlkZW50aXF1ZXMpIHRvdXQgZW4gw6l2aXRhbnQgZGUgZmFpcmUgY29ycmVzcG9uZHJlIGRlcyBmYXV4IHBvc2l0aWZzLCBjJ2VzdC3DoC1kaXJlIGRlcyByw6lmw6lyZW5jZXMgcXVpIG9udCBkZXMgaW5mb3JtYXRpb25zIGVuIGNvbW11biwgbWFpcyBxdWkgbmUgc29udCBlbiBmYWl0IHBhcyBsZXMgbcOqbWVzLiBQYXIgZXhlbXBsZSwgdW5lIGNvcnJlc3BvbmRhbmNlIGJhc8OpZSB1bmlxdWVtZW50IHN1ciBsZSBub20gZGVzIGF1dGV1cnMgZXQgbCdhbm7DqWUgZGUgcHVibGljYXRpb24gZXN0IHRyb3AgbGFyZ2UsIGNhciBjZXMgYXV0ZXVycyBwZXV2ZW50IGF2b2lyIHB1Ymxpw6kgcGx1c2lldXJzIGFydGljbGVzIGF1IGNvdXJzIGRlIGxhIG3Dqm1lIGFubsOpZS4gVm9pY2kgcGx1c2lldXJzIGZhw6dvbnMgZCdpZGVudGlmaWVyIHVuZSByw6lmw6lyZW5jZSBjb21tdW5lIHF1aSBjb21wb3J0ZW50IHRyw6hzIHBldSBkZSByaXNxdWVzIGRlIGZhaXJlIGNvcnJlc3BvbmRyZSBkZXMgcsOpZsOpcmVuY2VzIGRpZmbDqXJlbnRlcyA6DQoNCi0gbcOqbWUgbm9tIGRlIGZhbWlsbGUgZHUgcHJlbWllciBhdXRldXIgb3UgZGVzIGF1dGV1cnMsIGFubsOpZSwgdm9sdW1lIGV0IHBhZ2UgKGNlIHNvbnQgbGVzIHBsdXMgc8O7cmVzKSA6IGFwcGVsb25zLWxlcyBgZmF5dnBgICYgYGF5dnBgIDsNCi0gbcOqbWUgam91cm5hbCwgdm9sdW1lLCBudW3DqXJvIGV0IHByZW1pw6hyZSBwYWdlIDogYGp2aXBgIDsNCi0gbcOqbWUgYXV0ZXVyLCBhbm7DqWUgZXQgdGl0cmUgOiBgYXl0YCA7DQotIG3Dqm1lIHRpdHJlLCBtw6ptZSBhbm7DqWUgZXQgbcOqbWUgcHJlbWnDqHJlIHBhZ2UgOiBgdHlwYCA7DQotIG3Dqm1lIERvaSBvdSBQSUkuDQoNCk5vdXMgZXh0cmF5b25zIGxlIG5vbSBkZSBmYW1pbGxlIGR1IHByZW1pZXIgYXV0ZXVyIHBvdXIgZmF2b3Jpc2VyIGxhIGNvcnJlc3BvbmRhbmNlIGNhciBpbCB5IGEgcGx1cyBkZSBwb3NzaWJpbGl0w6lzIGRlIHBldGl0ZXMgZGlmZsOpcmVuY2VzIHBvdXIgcGx1c2lldXJzIGF1dGV1cnMgcXVpIG5vdXMgZW1ww6pjaGVyYWllbnQgZGUgZmFpcmUgY29ycmVzcG9uZHJlIGRlcyByw6lmw6lyZW5jZXMgc2ltaWxhaXJlcy4NCg0KYGBge3IgZXh0cmFjdGluZy1maXJzdC1hdXRob3J9DQpjbGVhbmVkX3JlZiA8LSBjbGVhbmVkX3JlZiAlPiUNCiAgbXV0YXRlKGZpcnN0X2F1dGhvciA9IHN0cl9leHRyYWN0KGF1dGhvcnMsICJeW1s6YWxwaGE6XStbJ10/WyAtXT9dKywgKFtBLVpdXFwuWyAtXT8pPyhbQS1aXVxcLlsgLV0/KT8oW0EtWl1cXC4pP1tBLVpdXFwuKD89KCx8JCkpIiksDQogICAgICAgICBmaXJzdF9hdXRob3Jfc3VybmFtZSA9IHN0cl9leHRyYWN0KGZpcnN0X2F1dGhvciwgIi4qKD89LCkiKSwNCiAgICAgICAgIGFjcm9zcyguY29scyA9IGMoImF1dGhvcnMiLCAiZmlyc3RfYXV0aG9yIiwgImpvdXJuYWwiLCAidGl0bGUiKSwgfnRvdXBwZXIoLikpKSANCg0KbWF0Y2hpbmdfcmVmIDwtIGZ1bmN0aW9uKGRhdGEsIGlkX3JlZiwgLi4uLCBjb2xfbmFtZSl7DQogIG1hdGNoIDwtIGRhdGEgJT4lIA0KICAgIGdyb3VwX2J5KC4uLikgJT4lIA0KICAgIG11dGF0ZShuZXdfaWQgPSBtaW4oe3tpZF9yZWZ9fSkpICU+JSANCiAgICBkcm9wX25hKC4uLikgJT4lIA0KICAgIHVuZ3JvdXAoKSAlPiUgDQogICAgc2VsZWN0KHt7aWRfcmVmfX0sIG5ld19pZCkgJT4lIA0KICAgIHJlbmFtZV93aXRoKH4gcGFzdGUwKGNvbF9uYW1lLCAiX25ld19pZCIpLCAuY29scyA9IG5ld19pZCkNCiAgDQogIGRhdGEgPC0gZGF0YSAlPiUgDQogICAgbGVmdF9qb2luKG1hdGNoKQ0KfQ0KDQppZGVudGlmeWluZ19yZWYgPC0gY2xlYW5lZF9yZWYgJT4lDQogIG1hdGNoaW5nX3JlZihpZF9yZWYsIGZpcnN0X2F1dGhvcl9zdXJuYW1lLCB5ZWFyLCB0aXRsZSwgY29sX25hbWUgPSAiZmF5dCIpICU+JSANCiAgbWF0Y2hpbmdfcmVmKGlkX3JlZiwgam91cm5hbCwgdm9sdW1lLCBpc3N1ZSwgZmlyc3RfcGFnZSwgY29sX25hbWUgPSAianZpcCIpICU+JSANCiAgbWF0Y2hpbmdfcmVmKGlkX3JlZiwgYXV0aG9ycywgeWVhciwgdm9sdW1lLCBmaXJzdF9wYWdlLCBjb2xfbmFtZSA9ICJheXZwIikgJT4lIA0KICBtYXRjaGluZ19yZWYoaWRfcmVmLCBmaXJzdF9hdXRob3Jfc3VybmFtZSwgeWVhciwgdm9sdW1lLCBmaXJzdF9wYWdlLCBjb2xfbmFtZSA9ICJmYXl2cCIpICU+JQ0KICBtYXRjaGluZ19yZWYoaWRfcmVmLCB0aXRsZSwgeWVhciwgZmlyc3RfcGFnZSwgY29sX25hbWUgPSAidHlwIikgJT4lIA0KICBtYXRjaGluZ19yZWYoaWRfcmVmLCBwaWksIGNvbF9uYW1lID0gInBpaSIpICU+JSANCiAgbWF0Y2hpbmdfcmVmKGlkX3JlZiwgZG9pLCBjb2xfbmFtZSA9ICJkb2kiKSANCg0KVmlldyhpZGVudGlmeWluZ19yZWYpDQpgYGANCg0KTm91cyBhdm9ucyBtYWludGVuYW50IG5vdHJlIHRhYmxlYXUgZGUgY2l0YXRpb25zIGRpcmVjdGVzIHJlbGlhbnQgbGVzIGFydGljbGVzIGNpdGFudCBhdXggcsOpZsOpcmVuY2VzLiBOb3VzIGF2b25zIGF1dGFudCBkZSBsaWduZXMgcXVlIGxlIG5vbWJyZSBkZSBjaXRhdGlvbnMgcGFyIGxlcyBhcnRpY2xlcyBjaXRhbnQuIExlcyBkZXV4IHByZW1pw6hyZXMgY29sb25uZXMsIHNvbnQgbGVzIHNldWxlcyBjb2xvbm5lcyBuw6ljZXNzYWlyZXMgcG91ciBjb25zdHJ1aXJlIGxlcyBsaWVucyBkZSBub3RyZSByw6lzZWF1IHRvdXQgw6AgbCdoZXVyZS4NCg0KYGBge3IgY3JlYXRpbmctZGlyZWN0LWNpdGF0aW9ufQ0KZGlyZWN0X2NpdGF0aW9uIDwtIGlkZW50aWZ5aW5nX3JlZiAlPiUgIA0KICBtdXRhdGUobmV3X2lkX3JlZiA9IHNlbGVjdCguLCBlbmRzX3dpdGgoIm5ld19pZCIpKSAlPiUgIHJlZHVjZShwbWluLCBuYS5ybSA9IFRSVUUpLA0KICAgICAgICAgbmV3X2lkX3JlZiA9IGlmZWxzZShpcy5uYShuZXdfaWRfcmVmKSwgaWRfcmVmLCBuZXdfaWRfcmVmKSkgICU+JSANCiAgcmVsb2NhdGUobmV3X2lkX3JlZiwgLmFmdGVyID0gY2l0aW5nX2lkKSAlPiUgDQogIHNlbGVjdCgtaWRfcmVmICYgISBlbmRzX3dpdGgoIm5ld19pZCIpKQ0KDQpkaXJlY3RfY2l0YXRpb24NCmBgYA0KDQpOb3VzIHBvdXZvbnMgZXh0cmFpcmUgbGEgbGlzdGUgZGUgdG91dGVzIGxlcyByw6lmw6lyZW5jZXMgY2l0w6llcy4gTm91cyBhdm9ucyBhdXRhbnQgZGUgbGlnbmVzIHF1ZSBkZSByw6lmw6lyZW5jZXMgY2l0w6llcyBwYXIgbGVzIGFydGljbGVzIGNpdGFudCAoYydlc3Qtw6AtZGlyZSBxdSd1bmUgcsOpZsOpcmVuY2UgY2l0w6llIHBsdXNpZXVycyBmb2lzIG4nZXN0IHByw6lzZW50ZSBxdSd1bmUgc2V1bGUgZm9pcyBkYW5zIGxlIHRhYmxlYXUpLiBDb21tZSBwb3VyIGxlcyByw6lmw6lyZW5jZXMgYXBwYXJpw6llcyBlbnNlbWJsZSwgb24gcGV1dCBhdm9pciBkZXMgaW5mb3JtYXRpb25zIGRpZmbDqXJlbnRlcyAoZHVlcyBhdSBmYWl0IHF1ZSBsZXMgcsOpZsOpcmVuY2VzIG9udCDDqXTDqSBjaXTDqWVzIGRpZmbDqXJlbW1lbnQgc2Vsb24gbGVzIGFydGljbGVzIGNpdGFudCksIG5vdXMgcHJlbm9ucyB1bmUgbGlnbmUgb8O5IGwnaW5mb3JtYXRpb24gc2VtYmxlIMOqdHJlIGxhIHBsdXMgY29tcGzDqHRlLCBjJ2VzdC3DoC1kaXJlIGzDoCBvw7kgaWwgeSBhIGxlIG1vaW5zIGQnaW5mb3JtYXRpb24gbWFucXVhbnRlcyBkYW5zIGxlcyBtw6l0YWRvbm7DqWVzLg0KDQpgYGB7ciByZWZlcmVuY2VzLXRhYmxlfQ0KaW1wb3J0YW50X2luZm8gPC0gYygiYXV0aG9ycyIsDQogICAgICAgICAgICAgICAgICAgICJ5ZWFyIiwNCiAgICAgICAgICAgICAgICAgICAgInRpdGxlIiwNCiAgICAgICAgICAgICAgICAgICAgImpvdXJuYWwiLA0KICAgICAgICAgICAgICAgICAgICAidm9sdW1lIiwNCiAgICAgICAgICAgICAgICAgICAgImlzc3VlIiwNCiAgICAgICAgICAgICAgICAgICAgInBhZ2VzIiwNCiAgICAgICAgICAgICAgICAgICAgImJvb2tfdGl0bGUiLA0KICAgICAgICAgICAgICAgICAgICAicHVibGlzaGVyIikNCnJlZmVyZW5jZXMgPC0gZGlyZWN0X2NpdGF0aW9uICU+JSANCiAgbXV0YXRlKG5iX25hID0gcm93U3VtcyghaXMubmEoc2VsZWN0KC4sIGFsbF9vZihpbXBvcnRhbnRfaW5mbykpKSkpICU+JSANCiAgZ3JvdXBfYnkobmV3X2lkX3JlZikgJT4lIA0KICBzbGljZV9tYXgob3JkZXJfYnkgPSBuYl9uYSwgbiA9IDEsIHdpdGhfdGllcyA9IEZBTFNFKSAlPiUgDQogIHNlbGVjdCgtY2l0aW5nX2lkKSAlPiUgDQogIHVuaXF1ZQ0KYGBgDQoNCkxlcyBkb25uw6llcyBxdWUgdm91cyBhdmV6IGTDqXNvcm1haXMgbmV0dG95w6llcyB2b3VzIHBlcm1ldHRlbnQgZGUgZmFpcmUgZGVzIHByZW1pw6hyZXMgZXhwbG9yYXRpb25zIHN0YXRpc3RpcXVlcy4gUGFyIGV4ZW1wbGUsIG9uIHBldXQgcmVnYXJkZXIgcXVlbHMgc29udCBsZXMgdHJhdmF1eCBsZXMgcGx1cyBjaXTDqXM6DQoNCmBgYHtyfQ0KZGlyZWN0X2NpdGF0aW9uICU+JSANCiAgYWRkX2NvdW50KG5ld19pZF9yZWYpICU+JSANCiAgc2VsZWN0KG5ld19pZF9yZWYsIG4pICU+JSANCiAgdW5pcXVlKCkgJT4lIA0KICBzbGljZV9tYXgobiwgbiA9IDEwKSAlPiUNCiAgbGVmdF9qb2luKHNlbGVjdChyZWZlcmVuY2VzLCBuZXdfaWRfcmVmLCByZWZlcmVuY2VzKSkgJT4lIA0KICBzZWxlY3QocmVmZXJlbmNlcywgbikNCmBgYA0KDQoNCiMjIFByw6lwYXJlciBub2V1ZHMgZXQgbGllbnMgcG91ciBsZSByw6lzZWF1IGRlIGNvLWNpdGF0aW9uDQoNCklsIHMnYWdpdCBkw6lzb3JtYWlzIGRlIHByw6lwYXJlciBsZXMgZG9ubsOpZXMgcXVlIG5vdXMgYWxsb25zIHV0aWxpc2VyIGRhbnMgR2VwaGkgcG91ciBjb25zdHJ1aXJlIHVuIHLDqXNlYXUuIE9uIHZldXQgZmFpcmUgdW4gcsOpc2VhdSBkZSBjby1jaXRhdGlvbiwgZG9uYyBvbiBzJ2ludMOpcmVzc2UgYXV4IHLDqWbDqXJlbmNlcyBjaXTDqWVzIChsZSBjb3VwbGFnZSBiaWJsaW9ncmFwaGlxdWUgcydpbnTDqXJlc3NlIGF1eCBhcnRpY2xlcyBjaXRhbnQpLiBQb3VyIGxpbWl0ZXIgbGUgbm9tYnJlIGRlIG7Fk3Vkcywgb24gcGV1dCBjaG9pc2lyIGRlIG5lIHByZW5kcmUgcXVlIGxlcyBuxZN1ZHMgbGVzIHBsdXMgY2l0w6lzOg0KDQpgYGB7cn0NCmNpdGF0aW9ucyA8LSBkaXJlY3RfY2l0YXRpb24gJT4lIA0KICBhZGRfY291bnQobmV3X2lkX3JlZikgJT4lIA0KICBzZWxlY3QobmV3X2lkX3JlZiwgbikgJT4lIA0KICB1bmlxdWUNCg0Kbm9kZXMgPC0gcmVmZXJlbmNlcyAlPiUgDQogIGxlZnRfam9pbihjaXRhdGlvbnMpICU+JSANCiAgZmlsdGVyKG4gPj0gMTApDQoNCm5vZGVzDQpgYGANCg0KT24gdmEgZW5zdWl0ZSBjcsOpZXIgbGVzIGxpZW5zIMOgIHBhcnRpciBkZXMgY2l0YXRpb25zIGRpcmVjdGVzOiBwb3VyIGNoYXF1ZSByw6lmw6lyZW5jZSwgb24gcmVnYXJkZSBxdWVscyBhcnRpY2xlcyBsYSBjaXRlbnQuIEVuc3VpdGUsIG9uIGNoZXJjaGVyIHF1ZWxsZXMgYXV0cmVzIHLDqWbDqXJlbmNlcyBzb250IGNpdMOpZXMgcGFyIGxlcyBtw6ptZXMgYXJ0aWNsZXMuIEVuIGQnYXV0cmVzIG1vdHMsIHRvdXRlcyBsZXMgcsOpZsOpcmVuY2VzIGNpdMOpZXMgZGFucyB1bmUgbcOqbWUgYmlibGlvZ3JhcGhpZSAoZCd1biBhcnRpY2xlIGNpdGFudCkgdm9udCDDqnRyZSByZWxpw6llcyBlbnRyZSBlbGxlcy4gT24gdXRpbGlzZSB1bmUgbcOpdGhvZGUgcXVpIHBlcm1ldCBkZSBwb25kw6lyZXIgbGVzIGxpZW5zIGVuIGZvbmN0aW9uIGR1IG5vbWJyZSBkZSBmb2lzIG/DuSBjaGFxdWUgcsOpZsOpcmVuY2UgZXN0IGNpdMOpZTogZW4gZWZmZXQsIG9uIGNvbnNpZMOocmUgcXVlIHNpIGRldXggcsOpZsOpcmVuY2VzIGJlYXVjb3VwIGNpdMOpZXMgc29udCBjaXTDqWVzICprKiBmb2lzIGVuc2VtYmxlLCBjZSBsaWVuIGRvaXQgw6p0cmUgbW9pbnMgaW1wb3J0YW50IHF1ZSBkZXMgcsOpZsOpcmVuY2VzIHBldSBjaXTDqWVzLCBxdWkgc29udCBjaXTDqWVzIGxlIG3Dqm1lIG5vbWJyZSAqayogZGUgZm9pcyBlbnNlbWJsZS4NCg0KYGBge3J9DQpkaXJlY3RfY2l0YXRpb24gPC0gZGlyZWN0X2NpdGF0aW9uICU+JSANCiAgZmlsdGVyKG5ld19pZF9yZWYgJWluJSBub2RlcyRuZXdfaWRfcmVmKQ0KDQplZGdlcyA8LSBiaWJsaW9uZXR3b3JrOjpiaWJsaW9fY29jaXRhdGlvbihkaXJlY3RfY2l0YXRpb24sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImNpdGluZ19pZCIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIm5ld19pZF9yZWYiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd2VpZ2h0X3RocmVzaG9sZCA9IDUpDQoNCmVkZ2VzDQpgYGANCg0K