1. Análisis de palabras y documentos



Una de las principales preguntas que nos hacemos en tareas de mineo de texto y procesamiento del lenguaje natural es ¿cómo cuantificar de qué se trata un documento?. Para llevar a cabo esta tarea, cuando ya tenemos los datos pre-procesados, podemos calcular medidas sobre cada uno de los tokens, es decir, podemos empezar a extraer características. Tres medidas muy usadas son:

  • Frecuencias (term frequency): Esta mide qué tan frecuentemente una palabra aparece en un documento.

  • Frecuencia inversa en documentos (inverse document frequency): A veces existen palabras que aparecen muchas veces pero que no son importantes. Para separar este tipo de palabras podemos utilizar el enfoque de la frecuencia inversa en documentos. Esta medida decrece conforme la palabra aparezca en más documentos. \[idf(palabra) = ln\left(\frac{n_{\text{documentos}}}{n_{\text{documentos con el término}}}\right)\]

  • Tf-Idf (Term frequency - inverse document frequency): Esta medida combina las dos anteriores. Es decir, mide la importancia de una palabra por su frecuencia general, pero la penaliza según más frecuente se haga entre varios documentos.

1.1. Análisis por palabras

Como primera aproximación, veremos cuáles son las palabras más frecuentes en cada uno de los datasets de noticias que hemos venido trabajando. Para ello, primero cargaremos los datos:

library(tidyverse)
library(tidytext)

tidy_Velasco_annotated = readRDS("Caso1_Noticias/tidy_Velasco_annotated.RDS")
tidy_Velasco = tidy_Velasco_annotated %>% 
  filter(!is.na(lemma)) 

tidy_Larrea_annotated = readRDS("Caso1_Noticias/tidy_Larrea_annotated.RDS")
tidy_Larrea = tidy_Larrea_annotated %>% 
  filter(!is.na(lemma))

tidy_Freile_annotated = readRDS("Caso1_Noticias/tidy_Freile_annotated.RDS")
tidy_Freile = tidy_Freile_annotated %>% 
  filter(!is.na(lemma))

ejemplo_Velasco = unique(tidy_Velasco$doc_id)[1]
ejemplo_Larrea = unique(tidy_Larrea$doc_id)[1]
ejemplo_Freile = unique(tidy_Freile$doc_id)[2]

Con los datos cargados, exploraremos la frecuencia de palabras como se muestra a continuación:

token1_Velasco = tidy_Velasco %>% 
  count(doc_id, upos, lemma, sort = TRUE) %>% 
  group_by(doc_id) %>%
  mutate(total = sum(n))

token1_Velasco %>% 
  filter(doc_id==ejemplo_Velasco) %>% 
  select(doc_id, lemma, upos, n, total) %>% 
  head()
token1_Larrea = tidy_Larrea %>% 
  count(doc_id, upos, lemma, sort = TRUE) %>% 
  group_by(doc_id) %>%
  mutate(total = sum(n))

token1_Larrea %>% 
  filter(doc_id==ejemplo_Larrea) %>% 
  select(doc_id, lemma, upos, n, total) %>% 
  head()
token1_Freile = tidy_Freile %>% 
  count(doc_id, upos, lemma, sort = TRUE) %>% 
  group_by(doc_id) %>%
  mutate(total = sum(n))

token1_Freile %>% 
  filter(doc_id==ejemplo_Freile) %>% 
  select(doc_id, lemma, upos, n, total) %>% 
  head()

Estos datos los podemos graficar para obtener mejores primeras conclusiones:

token1_Velasco %>% 
  filter(doc_id %in% unique(token1_Velasco$doc_id)[1:4]) %>% 
  filter(upos %in% c('NOUN','PROPN','VERB','ADJ')) %>% 
  group_by(doc_id, upos) %>% 
  slice_max(order_by = n/total, n=3) %>% 
  ggplot(aes(x=reorder_within(lemma, n/total, list(doc_id,upos)), y=n/total, fill = upos)) +
  geom_col() +
  scale_x_reordered()+
  coord_flip()+
  facet_wrap(vars(doc_id), scales = "free") + 
  labs(x="Frecuencia relativa de cada palabra en cada documento", y="Frecuencia",
       title = "Frecuencias relativas: Cuatro noticias de Juan Fernando Velasco")+
  theme(legend.position = "bottom")

Aquí podemos observar que la frecuencia relativa de las palabras va en general, máximo hasta un poco más arriba del 2% con respecto al total de palabras.

1.2. Tf-idf

La medida Tf-idf lo que hace es buscar las palabras importantes en el contenido de un documento midiendo su frecuencia general y penalizándola según más frecuente se haga entre varios documentos. Es decir, buscamos las palabras frecuentes pero no comunes.

En R esto se logra como:

library(tidytext)
token1_Velasco = token1_Velasco %>%
  filter(upos %in% c('NOUN','PROPN','VERB','ADJ')) %>% 
  bind_tf_idf(lemma, doc_id, n)
A value for tf_idf is negative:
 Input should have exactly one row per document-term combination.
token1_Velasco %>%
  select(-total) %>%
  arrange(desc(tf_idf))

La medida de tf_idf nos muestra qué palabras son más importantes dentro de cada documento, con respecto a su colección (conjunto de noticias).

Esto lo podemos observar gráficamente:

token1_Velasco %>%
  filter(doc_id %in% unique(token1_Velasco$doc_id)[1:4]) %>% 
  arrange(desc(tf_idf)) %>%
  mutate(lemma = factor(lemma, levels = rev(unique(lemma)))) %>% 
  group_by(doc_id) %>% 
  top_n(15) %>% 
  ungroup() %>%
  ggplot(aes(reorder_within(lemma, tf_idf, doc_id), tf_idf, fill = doc_id)) +
  geom_col(show.legend = FALSE) +
  scale_x_reordered()+
  labs(x = NULL, y = "tf-idf",
       title = "tf-idf: Cuatro noticias de Juan Fernando Velasco") +
  facet_wrap(~doc_id, ncol = 2, scales = "free") +
  coord_flip()
Selecting by tf_idf

2. Análisis exploratorio de n-gramas

Usualmente, para ver qué tan seguido una palabra x es seguida de una palabra y podemos construir un modelo relacional entre ellas, y ese será nuestro objetivo al final de este análisis.

2.1. Construcción de bigramas

Para construir los bigramas (o n-gramas de dimensión dos) recordemos el código que utilizamos en la última clase, sobre los datasets de noticias originales. Trabajaremos sobre dichos datasets en pos de ver las relaciones de las palabras incluso con artículos y preposiciones.

noticiasVelascoDF = readRDS("Caso1_Noticias/noticiasVelascoDF.RDS")
noticiasLarreaDF = readRDS("Caso1_Noticias/noticiasLarreaDF.RDS")
noticiasFreileDF = readRDS("Caso1_Noticias/noticiasFreileDF.RDS")
bigramas_Velasco = noticiasVelascoDF %>%
  unnest_tokens(bigrama, Noticia, token = "ngrams", n = 2)

bigramas_Velasco %>% 
  filter(Titular==ejemplo_Velasco) %>% 
  select(Titular, bigrama)
bigramas_Larrea = noticiasLarreaDF %>%
  unnest_tokens(bigrama, Noticia, token = "ngrams", n = 2)

bigramas_Larrea %>% 
  filter(Titular==ejemplo_Larrea) %>% 
  select(Titular, bigrama)
bigramas_Freile = noticiasFreileDF %>%
  unnest_tokens(bigrama, Noticia, token = "ngrams", n = 2)

bigramas_Freile %>% 
  filter(Titular==ejemplo_Freile) %>% 
  select(Titular, bigrama)

2.2. Conteo y tf-idf de bigramas

Sobre los bigramas construidos en el literal anterior realicemos el conteo de tokens, quitando primero las palabras vacías, como aprendimos en la clase anterior.

library(readxl)
stopwords_es_1 = read_excel("Diccionarios/Stopwords/CustomStopWords.xlsx")
names(stopwords_es_1) = c("Token","Fuente")
stopwords_es_2 = tibble(Token=tm::stopwords(kind = "es"), Fuente="tm")
stopwords_es = rbind(stopwords_es_1, stopwords_es_2)
stopwords_es = stopwords_es[!duplicated(stopwords_es$Token),]
remove(stopwords_es_1, stopwords_es_2)
bigramas_Velasco = bigramas_Velasco %>%
  separate(bigrama, c("palabra1", "palabra2"), sep = " ") %>%
  filter(!palabra1 %in% c(stopwords_es$Token)) %>%
  filter(!palabra2 %in% c(stopwords_es$Token))

bigramas_frec_Velasco = bigramas_Velasco %>% 
  count(Titular, palabra1, palabra2, sort = TRUE) %>% 
  unite(bigrama, palabra1, palabra2, sep = " ")

bigramas_frec_Velasco %>% select(bigrama, n) %>% head()
bigramas_Larrea = bigramas_Larrea %>%
  separate(bigrama, c("palabra1", "palabra2"), sep = " ") %>%
  filter(!palabra1 %in% c(stopwords_es$Token)) %>%
  filter(!palabra2 %in% c(stopwords_es$Token))

bigramas_frec_Larrea = bigramas_Larrea %>% 
  count(Titular, palabra1, palabra2, sort = TRUE) %>% 
  unite(bigrama, palabra1, palabra2, sep = " ")

bigramas_frec_Larrea %>% select(bigrama, n) %>% head()
bigramas_Freile = bigramas_Freile %>%
  separate(bigrama, c("palabra1", "palabra2"), sep = " ") %>%
  filter(!palabra1 %in% c(stopwords_es$Token)) %>%
  filter(!palabra2 %in% c(stopwords_es$Token))

bigramas_frec_Freile = bigramas_Freile %>% 
  count(Titular, palabra1, palabra2, sort = TRUE) %>% 
  unite(bigrama, palabra1, palabra2, sep = " ")

bigramas_frec_Freile %>% select(bigrama, n) %>% head()

Ahora, realicemos el tf-idf en estos datasets:

bigramas_tfidf_Velasco = bigramas_frec_Velasco %>%
  bind_tf_idf(bigrama, Titular, n)

bigramas_tfidf_Velasco %>% arrange(desc(tf_idf))
bigramas_tfidf_Larrea = bigramas_frec_Larrea %>%
  bind_tf_idf(bigrama, Titular, n)

bigramas_tfidf_Larrea %>% arrange(desc(tf_idf))
bigramas_tfidf_Freile = bigramas_frec_Freile %>%
  bind_tf_idf(bigrama, Titular, n)

bigramas_tfidf_Freile %>% arrange(desc(tf_idf))

La utilidad adicional de utilizar bigramas es que podemos darle contexto al análisis de sentimientos.

2.3. Red de bigramas

Cuando tenemos bigramas o n-gramas de dimensión mayor a 2, es interesante visualizar las relaciones de manera simultánea a través de un grafo. Un grafo es sencillamente la combinación de nodos interconectados. Tales grafos se pueden construir a partir de un objeto tidy ya que solo requiere de 3 variables:

  • Origen
  • Destino
  • Peso

El paquete igraph se vale de la librería ggraph para realizar grafos a partir de datos en formato tidy. Esto lo haremos como se muestra a continuación.

library(igraph)

bigrama_grafo_Velasco = bigramas_Velasco %>%
  count(palabra1, palabra2, sort = TRUE) %>% 
  filter(n >= 6) %>%
  graph_from_data_frame()

bigrama_grafo_Velasco
IGRAPH 49e5247 DN-- 108 68 -- 
+ attr: name (v/c), n (e/n)
+ edges from 49e5247 (vertex names):
 [1] juan       ->fernando    fernando   ->velasco     correo     ->electrónico electrónico->requerido  
 [5] requerido  ->asunto      maría      ->paula       paula      ->romo        sector     ->cultural   
 [9] lenín      ->moreno      rafael     ->correa      ana        ->maría       andrés     ->arauz      
[13] covid      ->19          redes      ->sociales    biblioteca ->nacional    cultura    ->juan       
[17] gestores   ->culturales  lucio      ->gutiérrez   consejo    ->nacional    electoral  ->cne        
[21] nacional   ->electoral   presidente ->lenín       artes      ->vivas       gustavo    ->larrea     
[25] movimiento ->construye   césar      ->montúfar    fuerza     ->ecuador     guillermo  ->celi       
[29] guillermo  ->lasso       asamblea   ->política    carlos     ->sagnay      cultura    ->ecuatoriana
+ ... omitted several edges
bigrama_grafo_Larrea = bigramas_Larrea %>%
  count(palabra1, palabra2, sort = TRUE) %>% 
  filter(n >= 6) %>%
  graph_from_data_frame()

bigrama_grafo_Larrea
IGRAPH 4a68ae3 DN-- 210 150 -- 
+ attr: name (v/c), n (e/n)
+ edges from 4a68ae3 (vertex names):
 [1] gustavo       ->larrea       rafael        ->correa       consejo       ->nacional    
 [4] nacional      ->electoral    maría         ->paula        paula         ->romo        
 [7] electoral     ->cne          candidato     ->presidencial guillermo     ->lasso       
[10] lucio         ->gutiérrez    organizaciones->políticas    andrés        ->arauz       
[13] isidro        ->romero       correo        ->electrónico  electrónico   ->requerido   
[16] requerido     ->asunto       ecuatoriano   ->unido        gerson        ->almeida     
[19] asamblea      ->política     alianza       ->país         sociedad      ->patriótica  
[22] alexandra     ->peralta      08            ->2020         centro        ->democrático 
+ ... omitted several edges
bigrama_grafo_Freile = bigramas_Freile %>%
  count(palabra1, palabra2, sort = TRUE) %>% 
  filter(n >= 6) %>%
  graph_from_data_frame()

bigrama_grafo_Freile
IGRAPH 4af695d DN-- 190 131 -- 
+ attr: name (v/c), n (e/n)
+ edges from 4af695d (vertex names):
 [1] consejo       ->nacional     nacional      ->electoral    electoral     ->cne         
 [4] rafael        ->correa       candidato     ->presidencial organizaciones->políticas   
 [7] josé          ->freile       maría         ->paula        paula         ->romo        
[10] pedro         ->josé         lucio         ->gutiérrez    yaku          ->pérez       
[13] 08            ->2020         gustavo       ->larrea       isidro        ->romero      
[16] guillermo     ->lasso        ximena        ->peña         andrés        ->arauz       
[19] correo        ->electrónico  electrónico   ->requerido    fernando      ->velasco     
[22] gerson        ->almeida      juan          ->fernando     requerido     ->asunto      
+ ... omitted several edges
library(ggraph)
set.seed(123)

ggraph(bigrama_grafo_Velasco, layout = "fr") +
  geom_edge_link() +
  geom_node_point() +
  geom_node_label(aes(label = name), vjust = 1, hjust = 1)

ggraph(bigrama_grafo_Larrea, layout = "fr") +
  geom_edge_link() +
  geom_node_point() +
  geom_node_label(aes(label = name), vjust = 1, hjust = 1)

ggraph(bigrama_grafo_Freile, layout = "fr") +
  geom_edge_link() +
  geom_node_point() +
  geom_node_label(aes(label = name), vjust = 1, hjust = 1)

3. Nubes de palabras

Para realizar nubes de palabras, que coloquen el tamaño de las palabras acorde a su frecuencia, utilizaremos la librería wordcloud2.

library(echarts4r)
# library(stringi)
wc_Velasco = tidy_Velasco_annotated %>% 
  filter(upos %in% c("NOUN","PROPN", "ADJ", "VERB")) %>%
  count(lemma, sort=T) %>% 
  filter(n > 20) %>% 
  e_color_range(n, color) %>%
  e_charts() %>%
  e_cloud(lemma, n, color) %>% 
  e_tooltip()
wc_Velasco

4. Extracción de palabras clave

El generar conclusiones e insight de un texto con miles de líneas y palabras puede ser abordado desde varias perspectivas. Una de ellas es la extracción de palabras claves, que puede ser conseguida a través de algoritmos de NLP. Esta será entonces nuestra última misión en este capítulo.

Acorde a Thushara et al. (2019), algunos de los algoritmos de extracción de palabras clave son:

  • RAKE (Rapid Automatic Keyword Extraction): Es un algoritmo no supervisado independiente del idioma que obtiene palabras clave a través del análisis de la frecuencia y grado (co-ocurrencia) de las palabras.
  • TextRank: Es una técnica no supervisada basada en grafos que obtiene el resumen de un texto. Está basada sobretodo en etiquetado POS.
  • PositionRank: Es un algoritmo basado en grafos que busca frases al inicio de los documentos y los candidatiza a ser clave.

4.1. RAKE

La librería udpipe incluye el algoritmo RAKE para extracción de palabras clave. Este es un algoritmo no supervisado independiente del idioma que obtiene palabras clave a través del análisis de la frecuencia y grado (co-ocurrencia) de las palabras. Específicamiente, sigue estos pasos:

  • Se extraen las palabras candidatas del conjunto de palabras, después de haber quitado palabras vacías e irrelevantes.
  • Se calcula un score para cada palabra a través de:
    • Se busca cuántas veces ocurre una palabra y cuántas veces co-ocurre con otra.
    • Se construye el score como el ratio de co-ocurrencias versus frecuencia.
  • Se ordena el score de mayor a menor y las primeras serán las palabras clave.

A continuación veremos ejemplos de la aplicación de esta técnica para palabras y n-gramas.

library(udpipe)
Registered S3 method overwritten by 'data.table':
  method           from
  print.data.table     
Velasco_keywords = keywords_rake(x = tidy_Velasco, term = "lemma", group = "doc_id", relevant = tidy_Velasco$upos %in% c("NOUN", "ADJ","PROPN","VERB"), ngram_max = 3)
head(Velasco_keywords)
Larrea_keywords = keywords_rake(x = tidy_Larrea, term = "lemma", group = "doc_id", relevant = tidy_Larrea$upos %in% c("NOUN", "ADJ","PROPN","VERB"), ngram_max = 3)
head(Larrea_keywords)
Freile_keywords = keywords_rake(x = tidy_Freile, term = "lemma", group = "doc_id", relevant = tidy_Freile$upos %in% c("NOUN", "ADJ","PROPN","VERB"), ngram_max = 3)
head(Freile_keywords)

5. Bibliografía

Silge, J. & Robinson, D. (2017), Text Mining with R, a tidy approach.

Thushara, M. G., Mownika, T. & Mangamuru, R. (2019), «A comparative study on different keyword extraction algorithms», Proceedings of the 3rd International Conference on Computing Methodologies and Communication, ICCMC 2019, No. March.

LS0tDQp0aXRsZTogIkFuw6FsaXNpcyBkZSBjb21wb3J0YW1pZW50byBlbiByZWRlcyBzb2NpYWxlcyB1c2FuZG8gUHJvY2VzYW1pZW50byBkZWwgTGVuZ3VhamUgTmF0dXJhbCINCnN1YnRpdGxlOiAnQ2Fww610dWxvIDQ6IFTDqWNuaWNhcyBkZSBhbsOhbGlzaXMgZXhwbG9yYXRvcmlvIGRlIGRhdG9zIGRlIHRleHRvJw0KYXV0aG9yOiBIdWdvIFBvcnJhcw0Kb3V0cHV0OiANCiAgaHRtbF9ub3RlYm9vazoNCiAgICBjc3M6IEVzdGlsb3MuY3NzDQogICAgdG9jOiB0cnVlDQogICAgdG9jX2RlcHRoOiAyDQogICAgdG9jX2Zsb2F0Og0KICAgICAgY29sbGFwc2VkOiB0cnVlDQogICAgICBzbW9vdGhfc2Nyb2xsOiBmYWxzZQ0KYmlibGlvZ3JhcGh5OiBCaWJsaW9ncmFmaWEuYmliDQpjc2w6IGNlcGFsLnhtbA0Kbm9jaXRlOiB8IA0KICBAU2lsZ2UyMDE3LCBAVGh1c2hhcmEyMDE5DQotLS0NCg0KIyAqKjEuIEFuw6FsaXNpcyBkZSBwYWxhYnJhcyB5IGRvY3VtZW50b3MqKg0KDQo8YnI+PC9icj4NCjxjZW50ZXI+PGE+PGltZyB3aWR0aD0iNTAlIiBzcmM9ImZpZ3MvMDNfRURBLnBuZyI+PC9hPjwvY2VudGVyPg0KPGJyPjwvYnI+DQoNClVuYSBkZSBsYXMgcHJpbmNpcGFsZXMgcHJlZ3VudGFzIHF1ZSBub3MgaGFjZW1vcyBlbiB0YXJlYXMgZGUgbWluZW8gZGUgdGV4dG8geSBwcm9jZXNhbWllbnRvIGRlbCBsZW5ndWFqZSBuYXR1cmFsIGVzICoqwr9jw7NtbyBjdWFudGlmaWNhciBkZSBxdcOpIHNlIHRyYXRhIHVuIGRvY3VtZW50bz8qKi4gUGFyYSBsbGV2YXIgYSBjYWJvIGVzdGEgdGFyZWEsIGN1YW5kbyB5YSB0ZW5lbW9zIGxvcyBkYXRvcyBwcmUtcHJvY2VzYWRvcywgcG9kZW1vcyBjYWxjdWxhciBtZWRpZGFzIHNvYnJlIGNhZGEgdW5vIGRlIGxvcyB0b2tlbnMsIGVzIGRlY2lyLCBwb2RlbW9zIGVtcGV6YXIgYSBleHRyYWVyIGNhcmFjdGVyw61zdGljYXMuIFRyZXMgbWVkaWRhcyBtdXkgdXNhZGFzIHNvbjoNCg0KKyAqKkZyZWN1ZW5jaWFzICh0ZXJtIGZyZXF1ZW5jeSkqKjogRXN0YSBtaWRlIHF1w6kgdGFuIGZyZWN1ZW50ZW1lbnRlIHVuYSBwYWxhYnJhIGFwYXJlY2UgZW4gdW4gZG9jdW1lbnRvLg0KKyAqKkZyZWN1ZW5jaWEgaW52ZXJzYSBlbiBkb2N1bWVudG9zIChpbnZlcnNlIGRvY3VtZW50IGZyZXF1ZW5jeSkqKjogQSB2ZWNlcyBleGlzdGVuIHBhbGFicmFzIHF1ZSBhcGFyZWNlbiBtdWNoYXMgdmVjZXMgcGVybyBxdWUgbm8gc29uIGltcG9ydGFudGVzLiBQYXJhIHNlcGFyYXIgZXN0ZSB0aXBvIGRlIHBhbGFicmFzIHBvZGVtb3MgdXRpbGl6YXIgZWwgZW5mb3F1ZSBkZSBsYSBmcmVjdWVuY2lhIGludmVyc2EgZW4gZG9jdW1lbnRvcy4gRXN0YSBtZWRpZGEgZGVjcmVjZSBjb25mb3JtZSBsYSBwYWxhYnJhIGFwYXJlemNhIGVuIG3DoXMgZG9jdW1lbnRvcy4NClxbaWRmKHBhbGFicmEpID0gbG5cbGVmdChcZnJhY3tuX3tcdGV4dHtkb2N1bWVudG9zfX19e25fe1x0ZXh0e2RvY3VtZW50b3MgY29uIGVsIHTDqXJtaW5vfX19XHJpZ2h0KVxdDQoNCisgKipUZi1JZGYgKFRlcm0gZnJlcXVlbmN5IC0gaW52ZXJzZSBkb2N1bWVudCBmcmVxdWVuY3kpKio6IEVzdGEgbWVkaWRhIGNvbWJpbmEgbGFzIGRvcyBhbnRlcmlvcmVzLiBFcyBkZWNpciwgbWlkZSBsYSBpbXBvcnRhbmNpYSBkZSB1bmEgcGFsYWJyYSBwb3Igc3UgZnJlY3VlbmNpYSBnZW5lcmFsLCBwZXJvIGxhIHBlbmFsaXphIHNlZ8O6biBtw6FzIGZyZWN1ZW50ZSBzZSBoYWdhIGVudHJlIHZhcmlvcyBkb2N1bWVudG9zLg0KDQojIyAqKjEuMS4gQW7DoWxpc2lzIHBvciBwYWxhYnJhcyoqDQoNCkNvbW8gcHJpbWVyYSBhcHJveGltYWNpw7NuLCB2ZXJlbW9zIGN1w6FsZXMgc29uIGxhcyBwYWxhYnJhcyBtw6FzIGZyZWN1ZW50ZXMgZW4gY2FkYSB1bm8gZGUgbG9zIGRhdGFzZXRzIGRlIG5vdGljaWFzIHF1ZSBoZW1vcyB2ZW5pZG8gdHJhYmFqYW5kby4gUGFyYSBlbGxvLCBwcmltZXJvIGNhcmdhcmVtb3MgbG9zIGRhdG9zOg0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeSh0aWR5dGV4dCkNCg0KdGlkeV9WZWxhc2NvX2Fubm90YXRlZCA9IHJlYWRSRFMoIkNhc28xX05vdGljaWFzL3RpZHlfVmVsYXNjb19hbm5vdGF0ZWQuUkRTIikNCnRpZHlfVmVsYXNjbyA9IHRpZHlfVmVsYXNjb19hbm5vdGF0ZWQgJT4lIA0KICBmaWx0ZXIoIWlzLm5hKGxlbW1hKSkgDQoNCnRpZHlfTGFycmVhX2Fubm90YXRlZCA9IHJlYWRSRFMoIkNhc28xX05vdGljaWFzL3RpZHlfTGFycmVhX2Fubm90YXRlZC5SRFMiKQ0KdGlkeV9MYXJyZWEgPSB0aWR5X0xhcnJlYV9hbm5vdGF0ZWQgJT4lIA0KICBmaWx0ZXIoIWlzLm5hKGxlbW1hKSkNCg0KdGlkeV9GcmVpbGVfYW5ub3RhdGVkID0gcmVhZFJEUygiQ2FzbzFfTm90aWNpYXMvdGlkeV9GcmVpbGVfYW5ub3RhdGVkLlJEUyIpDQp0aWR5X0ZyZWlsZSA9IHRpZHlfRnJlaWxlX2Fubm90YXRlZCAlPiUgDQogIGZpbHRlcighaXMubmEobGVtbWEpKQ0KDQplamVtcGxvX1ZlbGFzY28gPSB1bmlxdWUodGlkeV9WZWxhc2NvJGRvY19pZClbMV0NCmVqZW1wbG9fTGFycmVhID0gdW5pcXVlKHRpZHlfTGFycmVhJGRvY19pZClbMV0NCmVqZW1wbG9fRnJlaWxlID0gdW5pcXVlKHRpZHlfRnJlaWxlJGRvY19pZClbMl0NCmBgYA0KDQpDb24gbG9zIGRhdG9zIGNhcmdhZG9zLCBleHBsb3JhcmVtb3MgbGEgZnJlY3VlbmNpYSBkZSBwYWxhYnJhcyBjb21vIHNlIG11ZXN0cmEgYSBjb250aW51YWNpw7NuOg0KDQpgYGB7cn0NCnRva2VuMV9WZWxhc2NvID0gdGlkeV9WZWxhc2NvICU+JSANCiAgY291bnQoZG9jX2lkLCB1cG9zLCBsZW1tYSwgc29ydCA9IFRSVUUpICU+JSANCiAgZ3JvdXBfYnkoZG9jX2lkKSAlPiUNCiAgbXV0YXRlKHRvdGFsID0gc3VtKG4pKQ0KDQp0b2tlbjFfVmVsYXNjbyAlPiUgDQogIGZpbHRlcihkb2NfaWQ9PWVqZW1wbG9fVmVsYXNjbykgJT4lIA0KICBzZWxlY3QoZG9jX2lkLCBsZW1tYSwgdXBvcywgbiwgdG90YWwpICU+JSANCiAgaGVhZCgpDQpgYGANCg0KYGBge3J9DQp0b2tlbjFfTGFycmVhID0gdGlkeV9MYXJyZWEgJT4lIA0KICBjb3VudChkb2NfaWQsIHVwb3MsIGxlbW1hLCBzb3J0ID0gVFJVRSkgJT4lIA0KICBncm91cF9ieShkb2NfaWQpICU+JQ0KICBtdXRhdGUodG90YWwgPSBzdW0obikpDQoNCnRva2VuMV9MYXJyZWEgJT4lIA0KICBmaWx0ZXIoZG9jX2lkPT1lamVtcGxvX0xhcnJlYSkgJT4lIA0KICBzZWxlY3QoZG9jX2lkLCBsZW1tYSwgdXBvcywgbiwgdG90YWwpICU+JSANCiAgaGVhZCgpDQpgYGANCg0KYGBge3J9DQp0b2tlbjFfRnJlaWxlID0gdGlkeV9GcmVpbGUgJT4lIA0KICBjb3VudChkb2NfaWQsIHVwb3MsIGxlbW1hLCBzb3J0ID0gVFJVRSkgJT4lIA0KICBncm91cF9ieShkb2NfaWQpICU+JQ0KICBtdXRhdGUodG90YWwgPSBzdW0obikpDQoNCnRva2VuMV9GcmVpbGUgJT4lIA0KICBmaWx0ZXIoZG9jX2lkPT1lamVtcGxvX0ZyZWlsZSkgJT4lIA0KICBzZWxlY3QoZG9jX2lkLCBsZW1tYSwgdXBvcywgbiwgdG90YWwpICU+JSANCiAgaGVhZCgpDQpgYGANCg0KDQpFc3RvcyBkYXRvcyBsb3MgcG9kZW1vcyBncmFmaWNhciBwYXJhIG9idGVuZXIgbWVqb3JlcyBwcmltZXJhcyBjb25jbHVzaW9uZXM6DQoNCmBgYHtyIGZpZy5oZWlnaHQgPSA4fQ0KdG9rZW4xX1ZlbGFzY28gJT4lIA0KICBmaWx0ZXIoZG9jX2lkICVpbiUgdW5pcXVlKHRva2VuMV9WZWxhc2NvJGRvY19pZClbMTo0XSkgJT4lIA0KICBmaWx0ZXIodXBvcyAlaW4lIGMoJ05PVU4nLCdQUk9QTicsJ1ZFUkInLCdBREonKSkgJT4lIA0KICBncm91cF9ieShkb2NfaWQsIHVwb3MpICU+JSANCiAgc2xpY2VfbWF4KG9yZGVyX2J5ID0gbi90b3RhbCwgbj0zKSAlPiUgDQogIGdncGxvdChhZXMoeD1yZW9yZGVyX3dpdGhpbihsZW1tYSwgbi90b3RhbCwgbGlzdChkb2NfaWQsdXBvcykpLCB5PW4vdG90YWwsIGZpbGwgPSB1cG9zKSkgKw0KICBnZW9tX2NvbCgpICsNCiAgc2NhbGVfeF9yZW9yZGVyZWQoKSsNCiAgY29vcmRfZmxpcCgpKw0KICBmYWNldF93cmFwKHZhcnMoZG9jX2lkKSwgc2NhbGVzID0gImZyZWUiKSArIA0KICBsYWJzKHg9IkZyZWN1ZW5jaWEgcmVsYXRpdmEgZGUgY2FkYSBwYWxhYnJhIGVuIGNhZGEgZG9jdW1lbnRvIiwgeT0iRnJlY3VlbmNpYSIsDQogICAgICAgdGl0bGUgPSAiRnJlY3VlbmNpYXMgcmVsYXRpdmFzOiBDdWF0cm8gbm90aWNpYXMgZGUgSnVhbiBGZXJuYW5kbyBWZWxhc2NvIikrDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKQ0KYGBgDQoNCkFxdcOtIHBvZGVtb3Mgb2JzZXJ2YXIgcXVlIGxhIGZyZWN1ZW5jaWEgcmVsYXRpdmEgZGUgbGFzIHBhbGFicmFzIHZhIGVuIGdlbmVyYWwsIG3DoXhpbW8gaGFzdGEgdW4gcG9jbyBtw6FzIGFycmliYSBkZWwgMiUgY29uIHJlc3BlY3RvIGFsIHRvdGFsIGRlIHBhbGFicmFzLg0KDQo8IS0tICMjICoqMS4yLiBMZXkgZGUgWmlwZioqIC0tPg0KDQo8IS0tIEVuIGRhdG9zIGRlIHRleHRvLCBsYXMgZGlzdHJpYnVjaW9uZXMgY29uIGNvbGFzIGxhcmdhcyBzb24gdXN1YWxlcy4gRW4gdW5hIG1hdHJpeiB0w6lybWlubyBkb2N1bWVudG8gdmVyZW1vcyBlc3RvIHJlZmxlamFkbyBlbiBxdWUgbGEgbWF0cml6IGVzIGRpc3BlcnNhICh0aWVuZSBtdWNoYXMgZW50cmFkYXMgaWd1YWwgYSBjZXJvKS4gR2VvcmdlIFppcGYsIGxpbmfDvGlzdGEgZXN0YWRvdW5pZGVuc2UgZXN0YWJsZWNlIHJlc3BlY3RvIGEgZXN0YXMgZGlzdHJpYnVjaW9uZXMgcXVlICoqJydsYSBmcmVjdWVuY2lhIHJlbGF0aXZhIGRlIGFwYXJlY2ltaWVudG8gZGUgdW5hIHBhbGFicmEgZXN0w6EgaW52ZXJzYW1lbnRlIHJlbGFjaW9uYWRhIGNvbiBzdSByYW5nbycnKiouIEVuIGVmZWN0bywgYWwgb3JkZW5hciBsYXMgcGFsYWJyYXMgcG9yIGZyZWN1ZW5jaWEgZGUgbWF5b3IgYSBtZW5vciwgY29sb2NhcmVtb3Mgc3UgcmFuZ28gY29tbyB1biBuw7ptZXJvIGVudGVybywgZW1wZXphbmRvIGVuIHVuby4gLS0+DQoNCjwhLS0gRW4gUiwgcG9kZW1vcyB2ZXIgbGEgbGV5IGRlIFppcGYgY29tbzogLS0+DQoNCjwhLS0gYGBge3J9IC0tPg0KPCEtLSByYW5nb19mcmVjX1l1bmRhID0gdG9rZW4xX1l1bmRhICU+JSAgLS0+DQo8IS0tICAgZ3JvdXBfYnkoZG9jX2lkKSAlPiUgIC0tPg0KPCEtLSAgIG11dGF0ZShyYW5nbyA9IHJvd19udW1iZXIoKSwgIC0tPg0KPCEtLSAgICAgICAgICBgdGVybSBmcmVxdWVuY3lgID0gbi90b3RhbCkgLS0+DQoNCjwhLS0gcmFuZ29fZnJlY19ZdW5kYSAlPiUgIC0tPg0KPCEtLSAgIGZpbHRlcihkb2NfaWQ9PWVqZW1wbG9fWXVuZGEpICU+JSAgLS0+DQo8IS0tICAgaGVhZCgpIC0tPg0KPCEtLSBgYGAgLS0+DQoNCjwhLS0gYGBge3J9IC0tPg0KPCEtLSByYW5nb19mcmVjX1ZpdGVyaSA9IHRva2VuMV9WaXRlcmkgJT4lICAtLT4NCjwhLS0gICBncm91cF9ieShkb2NfaWQpICU+JSAgLS0+DQo8IS0tICAgbXV0YXRlKHJhbmdvID0gcm93X251bWJlcigpLCAgLS0+DQo8IS0tICAgICAgICAgIGB0ZXJtIGZyZXF1ZW5jeWAgPSBuL3RvdGFsKSAtLT4NCg0KPCEtLSByYW5nb19mcmVjX1ZpdGVyaSAlPiUgIC0tPg0KPCEtLSAgIGZpbHRlcihkb2NfaWQ9PWVqZW1wbG9fVml0ZXJpKSAlPiUgIC0tPg0KPCEtLSAgIGhlYWQoKSAtLT4NCjwhLS0gYGBgIC0tPg0KDQo8IS0tIEdyw6FmaWNhbWVudGU6IC0tPg0KDQo8IS0tIGBgYHtyfSAtLT4NCjwhLS0gcmFuZ29fZnJlY19ZdW5kYSAlPiUgIC0tPg0KPCEtLSAgIGdncGxvdChhZXMocmFuZ28sIGB0ZXJtIGZyZXF1ZW5jeWAsIGNvbG9yID0gZG9jX2lkKSkgKyAgLS0+DQo8IS0tICAgZ2VvbV9saW5lKHNpemUgPSAxLjEsIGFscGhhID0gMC44LCBzaG93LmxlZ2VuZCA9IEZBTFNFKSArICAtLT4NCjwhLS0gICBzY2FsZV94X2xvZzEwKCkgKyAtLT4NCjwhLS0gICBzY2FsZV95X2xvZzEwKCkgKyAtLT4NCjwhLS0gICBsYWJzKHRpdGxlID0gIkxleSBkZSBaaXBmOiBKb3JnZSBZdW5kYSwgUXVpdG8iKSAtLT4NCjwhLS0gYGBgIC0tPg0KDQo8IS0tIGBgYHtyfSAtLT4NCjwhLS0gcmFuZ29fZnJlY19WaXRlcmkgJT4lICAtLT4NCjwhLS0gICBnZ3Bsb3QoYWVzKHJhbmdvLCBgdGVybSBmcmVxdWVuY3lgLCBjb2xvciA9IGRvY19pZCkpICsgIC0tPg0KPCEtLSAgIGdlb21fbGluZShzaXplID0gMS4xLCBhbHBoYSA9IDAuOCwgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKyAgLS0+DQo8IS0tICAgc2NhbGVfeF9sb2cxMCgpICsgLS0+DQo8IS0tICAgc2NhbGVfeV9sb2cxMCgpICsgLS0+DQo8IS0tICAgbGFicyh0aXRsZSA9ICJMZXkgZGUgWmlwZjogQ3ludGhpYSBWaXRlcmksIFF1aXRvIikgLS0+DQo8IS0tIGBgYCAtLT4NCg0KPCEtLSBDb21vIHNlIHB1ZWRlIG9ic2VydmFyLCBlbiBlZmVjdG8gbGEgcmVsYWNpw7NuIGVuIGVsIHJhbmdvIHkgbGEgZnJlY3VlbmNpYSByZWxhdGl2YSBkZSBsYXMgcGFsYWJyYXMgcGFyYSBsb3Mgbm90aWNpYXMgYW5hbGl6YWRhcyBlcyBpbnZlcnNhLiDCv1F1w6kgdGFsIHNpIGVzdGltYW1vcyBlc3RhIHJlbGFjacOzbiBhIHRyYXbDqXMgZGUgdW5hIHJlZ3Jlc2nDs24gbGluZWFsPyAtLT4NCg0KPCEtLSBgYGB7cn0gLS0+DQo8IS0tIHN1bW1hcnkobG0obG9nMTAoYHRlcm0gZnJlcXVlbmN5YCkgfiBsb2cxMChyYW5nbyksIGRhdGEgPSByYW5nb19mcmVjX1l1bmRhKSkgLS0+DQo8IS0tIGBgYCAtLT4NCg0KPCEtLSBgYGB7cn0gLS0+DQo8IS0tIHN1bW1hcnkobG0obG9nMTAoYHRlcm0gZnJlcXVlbmN5YCkgfiBsb2cxMChyYW5nbyksIGRhdGEgPSByYW5nb19mcmVjX1ZpdGVyaSkpIC0tPg0KPCEtLSBgYGAgLS0+DQoNCjwhLS0gTGEgcmVncmVzacOzbiBjb25maXJtYSBsYSBkaXJlY2Npw7NuIGRlIGxhIGxleSBkZSBaaXBmLCBxdWUgZ3LDoWZpY2FtZW50ZSBzZSB2ZSBjb21vOiAtLT4NCg0KPCEtLSBgYGB7cn0gLS0+DQo8IS0tIHJhbmdvX2ZyZWNfWXVuZGEgJT4lICAtLT4NCjwhLS0gICBnZ3Bsb3QoYWVzKHJhbmdvLCBgdGVybSBmcmVxdWVuY3lgLCBjb2xvciA9IGRvY19pZCkpICsgIC0tPg0KPCEtLSAgIGdlb21fYWJsaW5lKGludGVyY2VwdCA9IC0xLjQsIHNsb3BlID0gLTAuNSwgY29sb3IgPSAiZ3JheTUwIiwgbGluZXR5cGUgPSAyKSArIC0tPg0KPCEtLSAgIGdlb21fbGluZShzaXplID0gMS4xLCBhbHBoYSA9IDAuOCwgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKyAgLS0+DQo8IS0tICAgc2NhbGVfeF9sb2cxMCgpICsgLS0+DQo8IS0tICAgc2NhbGVfeV9sb2cxMCgpICsgLS0+DQo8IS0tICAgbGFicyhUaXRsZT0iTGV5IGRlIFppcGYgYWp1c3RhZGEgcG9yIHJlZ3Jlc2nDs246IEpvcmdlIFl1bmRhLCBRdWl0byIpIC0tPg0KPCEtLSBgYGAgLS0+DQoNCjwhLS0gYGBge3J9IC0tPg0KPCEtLSByYW5nb19mcmVjX1ZpdGVyaSAlPiUgIC0tPg0KPCEtLSAgIGdncGxvdChhZXMocmFuZ28sIGB0ZXJtIGZyZXF1ZW5jeWAsIGNvbG9yID0gZG9jX2lkKSkgKyAgLS0+DQo8IS0tICAgZ2VvbV9hYmxpbmUoaW50ZXJjZXB0ID0gLTEuNSwgc2xvcGUgPSAtMC40LCBjb2xvciA9ICJncmF5NTAiLCBsaW5ldHlwZSA9IDIpICsgLS0+DQo8IS0tICAgZ2VvbV9saW5lKHNpemUgPSAxLjEsIGFscGhhID0gMC44LCBzaG93LmxlZ2VuZCA9IEZBTFNFKSArICAtLT4NCjwhLS0gICBzY2FsZV94X2xvZzEwKCkgKyAtLT4NCjwhLS0gICBzY2FsZV95X2xvZzEwKCkgKyAtLT4NCjwhLS0gICBsYWJzKFRpdGxlPSJMZXkgZGUgWmlwZiBhanVzdGFkYSBwb3IgcmVncmVzacOzbjogQ3ludGhpYSBWaXRlcmksIEd1YXlhcXVpbCIpIC0tPg0KPCEtLSBgYGAgLS0+DQoNCiMjICoqMS4yLiBUZi1pZGYqKg0KDQpMYSBtZWRpZGEgVGYtaWRmIGxvIHF1ZSBoYWNlIGVzIGJ1c2NhciBsYXMgcGFsYWJyYXMgaW1wb3J0YW50ZXMgZW4gZWwgY29udGVuaWRvIGRlIHVuIGRvY3VtZW50byBtaWRpZW5kbyBzdSBmcmVjdWVuY2lhIGdlbmVyYWwgeSBwZW5hbGl6w6FuZG9sYSBzZWfDum4gbcOhcyBmcmVjdWVudGUgc2UgaGFnYSBlbnRyZSB2YXJpb3MgZG9jdW1lbnRvcy4gRXMgZGVjaXIsIGJ1c2NhbW9zIGxhcyBwYWxhYnJhcyBmcmVjdWVudGVzIHBlcm8gbm8gY29tdW5lcy4gDQoNCkVuIFIgZXN0byBzZSBsb2dyYSBjb21vOg0KDQpgYGB7cn0NCmxpYnJhcnkodGlkeXRleHQpDQp0b2tlbjFfVmVsYXNjbyA9IHRva2VuMV9WZWxhc2NvICU+JQ0KICBmaWx0ZXIodXBvcyAlaW4lIGMoJ05PVU4nLCdQUk9QTicsJ1ZFUkInLCdBREonKSkgJT4lIA0KICBiaW5kX3RmX2lkZihsZW1tYSwgZG9jX2lkLCBuKQ0KDQp0b2tlbjFfVmVsYXNjbyAlPiUNCiAgc2VsZWN0KC10b3RhbCkgJT4lDQogIGFycmFuZ2UoZGVzYyh0Zl9pZGYpKQ0KYGBgDQoNCkxhIG1lZGlkYSBkZSB0Zl9pZGYgbm9zIG11ZXN0cmEgcXXDqSBwYWxhYnJhcyBzb24gbcOhcyBpbXBvcnRhbnRlcyBkZW50cm8gZGUgY2FkYSBkb2N1bWVudG8sIGNvbiByZXNwZWN0byBhIHN1IGNvbGVjY2nDs24gKGNvbmp1bnRvIGRlIG5vdGljaWFzKS4NCg0KRXN0byBsbyBwb2RlbW9zIG9ic2VydmFyIGdyw6FmaWNhbWVudGU6DQoNCmBgYHtyIGZpZy5oZWlnaHQgPSA3fQ0KdG9rZW4xX1ZlbGFzY28gJT4lDQogIGZpbHRlcihkb2NfaWQgJWluJSB1bmlxdWUodG9rZW4xX1ZlbGFzY28kZG9jX2lkKVsxOjRdKSAlPiUgDQogIGFycmFuZ2UoZGVzYyh0Zl9pZGYpKSAlPiUNCiAgbXV0YXRlKGxlbW1hID0gZmFjdG9yKGxlbW1hLCBsZXZlbHMgPSByZXYodW5pcXVlKGxlbW1hKSkpKSAlPiUgDQogIGdyb3VwX2J5KGRvY19pZCkgJT4lIA0KICB0b3BfbigxNSkgJT4lIA0KICB1bmdyb3VwKCkgJT4lDQogIGdncGxvdChhZXMocmVvcmRlcl93aXRoaW4obGVtbWEsIHRmX2lkZiwgZG9jX2lkKSwgdGZfaWRmLCBmaWxsID0gZG9jX2lkKSkgKw0KICBnZW9tX2NvbChzaG93LmxlZ2VuZCA9IEZBTFNFKSArDQogIHNjYWxlX3hfcmVvcmRlcmVkKCkrDQogIGxhYnMoeCA9IE5VTEwsIHkgPSAidGYtaWRmIiwNCiAgICAgICB0aXRsZSA9ICJ0Zi1pZGY6IEN1YXRybyBub3RpY2lhcyBkZSBKdWFuIEZlcm5hbmRvIFZlbGFzY28iKSArDQogIGZhY2V0X3dyYXAofmRvY19pZCwgbmNvbCA9IDIsIHNjYWxlcyA9ICJmcmVlIikgKw0KICBjb29yZF9mbGlwKCkNCmBgYA0KDQojICoqMi4gQW7DoWxpc2lzIGV4cGxvcmF0b3JpbyBkZSBuLWdyYW1hcyoqDQoNClVzdWFsbWVudGUsIHBhcmEgdmVyIHF1w6kgdGFuIHNlZ3VpZG8gdW5hIHBhbGFicmEgKngqIGVzIHNlZ3VpZGEgZGUgdW5hIHBhbGFicmEgKnkqIHBvZGVtb3MgY29uc3RydWlyIHVuIG1vZGVsbyByZWxhY2lvbmFsIGVudHJlIGVsbGFzLCB5IGVzZSBzZXLDoSBudWVzdHJvIG9iamV0aXZvIGFsIGZpbmFsIGRlIGVzdGUgYW7DoWxpc2lzLg0KDQojIyAqKjIuMS4gQ29uc3RydWNjacOzbiBkZSBiaWdyYW1hcyoqDQoNClBhcmEgY29uc3RydWlyIGxvcyBiaWdyYW1hcyAobyBuLWdyYW1hcyBkZSBkaW1lbnNpw7NuIGRvcykgcmVjb3JkZW1vcyBlbCBjw7NkaWdvIHF1ZSB1dGlsaXphbW9zIGVuIGxhIMO6bHRpbWEgY2xhc2UsIHNvYnJlIGxvcyBkYXRhc2V0cyBkZSBub3RpY2lhcyBvcmlnaW5hbGVzLiBUcmFiYWphcmVtb3Mgc29icmUgZGljaG9zIGRhdGFzZXRzIGVuIHBvcyBkZSB2ZXIgbGFzIHJlbGFjaW9uZXMgZGUgbGFzIHBhbGFicmFzIGluY2x1c28gY29uIGFydMOtY3Vsb3MgeSBwcmVwb3NpY2lvbmVzLg0KDQpgYGB7cn0NCm5vdGljaWFzVmVsYXNjb0RGID0gcmVhZFJEUygiQ2FzbzFfTm90aWNpYXMvbm90aWNpYXNWZWxhc2NvREYuUkRTIikNCm5vdGljaWFzTGFycmVhREYgPSByZWFkUkRTKCJDYXNvMV9Ob3RpY2lhcy9ub3RpY2lhc0xhcnJlYURGLlJEUyIpDQpub3RpY2lhc0ZyZWlsZURGID0gcmVhZFJEUygiQ2FzbzFfTm90aWNpYXMvbm90aWNpYXNGcmVpbGVERi5SRFMiKQ0KYGBgDQoNCmBgYHtyfQ0KYmlncmFtYXNfVmVsYXNjbyA9IG5vdGljaWFzVmVsYXNjb0RGICU+JQ0KICB1bm5lc3RfdG9rZW5zKGJpZ3JhbWEsIE5vdGljaWEsIHRva2VuID0gIm5ncmFtcyIsIG4gPSAyKQ0KDQpiaWdyYW1hc19WZWxhc2NvICU+JSANCiAgZmlsdGVyKFRpdHVsYXI9PWVqZW1wbG9fVmVsYXNjbykgJT4lIA0KICBzZWxlY3QoVGl0dWxhciwgYmlncmFtYSkNCmBgYA0KDQpgYGB7cn0NCmJpZ3JhbWFzX0xhcnJlYSA9IG5vdGljaWFzTGFycmVhREYgJT4lDQogIHVubmVzdF90b2tlbnMoYmlncmFtYSwgTm90aWNpYSwgdG9rZW4gPSAibmdyYW1zIiwgbiA9IDIpDQoNCmJpZ3JhbWFzX0xhcnJlYSAlPiUgDQogIGZpbHRlcihUaXR1bGFyPT1lamVtcGxvX0xhcnJlYSkgJT4lIA0KICBzZWxlY3QoVGl0dWxhciwgYmlncmFtYSkNCmBgYA0KDQpgYGB7cn0NCmJpZ3JhbWFzX0ZyZWlsZSA9IG5vdGljaWFzRnJlaWxlREYgJT4lDQogIHVubmVzdF90b2tlbnMoYmlncmFtYSwgTm90aWNpYSwgdG9rZW4gPSAibmdyYW1zIiwgbiA9IDIpDQoNCmJpZ3JhbWFzX0ZyZWlsZSAlPiUgDQogIGZpbHRlcihUaXR1bGFyPT1lamVtcGxvX0ZyZWlsZSkgJT4lIA0KICBzZWxlY3QoVGl0dWxhciwgYmlncmFtYSkNCmBgYA0KDQojIyAqKjIuMi4gQ29udGVvIHkgdGYtaWRmIGRlIGJpZ3JhbWFzKioNCg0KU29icmUgbG9zIGJpZ3JhbWFzIGNvbnN0cnVpZG9zIGVuIGVsIGxpdGVyYWwgYW50ZXJpb3IgcmVhbGljZW1vcyBlbCBjb250ZW8gZGUgdG9rZW5zLCBxdWl0YW5kbyBwcmltZXJvIGxhcyBwYWxhYnJhcyB2YWPDrWFzLCBjb21vIGFwcmVuZGltb3MgZW4gbGEgY2xhc2UgYW50ZXJpb3IuDQoNCmBgYHtyfQ0KbGlicmFyeShyZWFkeGwpDQpzdG9wd29yZHNfZXNfMSA9IHJlYWRfZXhjZWwoIkRpY2Npb25hcmlvcy9TdG9wd29yZHMvQ3VzdG9tU3RvcFdvcmRzLnhsc3giKQ0KbmFtZXMoc3RvcHdvcmRzX2VzXzEpID0gYygiVG9rZW4iLCJGdWVudGUiKQ0Kc3RvcHdvcmRzX2VzXzIgPSB0aWJibGUoVG9rZW49dG06OnN0b3B3b3JkcyhraW5kID0gImVzIiksIEZ1ZW50ZT0idG0iKQ0Kc3RvcHdvcmRzX2VzID0gcmJpbmQoc3RvcHdvcmRzX2VzXzEsIHN0b3B3b3Jkc19lc18yKQ0Kc3RvcHdvcmRzX2VzID0gc3RvcHdvcmRzX2VzWyFkdXBsaWNhdGVkKHN0b3B3b3Jkc19lcyRUb2tlbiksXQ0KcmVtb3ZlKHN0b3B3b3Jkc19lc18xLCBzdG9wd29yZHNfZXNfMikNCmBgYA0KDQoNCmBgYHtyfQ0KYmlncmFtYXNfVmVsYXNjbyA9IGJpZ3JhbWFzX1ZlbGFzY28gJT4lDQogIHNlcGFyYXRlKGJpZ3JhbWEsIGMoInBhbGFicmExIiwgInBhbGFicmEyIiksIHNlcCA9ICIgIikgJT4lDQogIGZpbHRlcighcGFsYWJyYTEgJWluJSBjKHN0b3B3b3Jkc19lcyRUb2tlbikpICU+JQ0KICBmaWx0ZXIoIXBhbGFicmEyICVpbiUgYyhzdG9wd29yZHNfZXMkVG9rZW4pKQ0KDQpiaWdyYW1hc19mcmVjX1ZlbGFzY28gPSBiaWdyYW1hc19WZWxhc2NvICU+JSANCiAgY291bnQoVGl0dWxhciwgcGFsYWJyYTEsIHBhbGFicmEyLCBzb3J0ID0gVFJVRSkgJT4lIA0KICB1bml0ZShiaWdyYW1hLCBwYWxhYnJhMSwgcGFsYWJyYTIsIHNlcCA9ICIgIikNCg0KYmlncmFtYXNfZnJlY19WZWxhc2NvICU+JSBzZWxlY3QoYmlncmFtYSwgbikgJT4lIGhlYWQoKQ0KYGBgDQoNCmBgYHtyfQ0KYmlncmFtYXNfTGFycmVhID0gYmlncmFtYXNfTGFycmVhICU+JQ0KICBzZXBhcmF0ZShiaWdyYW1hLCBjKCJwYWxhYnJhMSIsICJwYWxhYnJhMiIpLCBzZXAgPSAiICIpICU+JQ0KICBmaWx0ZXIoIXBhbGFicmExICVpbiUgYyhzdG9wd29yZHNfZXMkVG9rZW4pKSAlPiUNCiAgZmlsdGVyKCFwYWxhYnJhMiAlaW4lIGMoc3RvcHdvcmRzX2VzJFRva2VuKSkNCg0KYmlncmFtYXNfZnJlY19MYXJyZWEgPSBiaWdyYW1hc19MYXJyZWEgJT4lIA0KICBjb3VudChUaXR1bGFyLCBwYWxhYnJhMSwgcGFsYWJyYTIsIHNvcnQgPSBUUlVFKSAlPiUgDQogIHVuaXRlKGJpZ3JhbWEsIHBhbGFicmExLCBwYWxhYnJhMiwgc2VwID0gIiAiKQ0KDQpiaWdyYW1hc19mcmVjX0xhcnJlYSAlPiUgc2VsZWN0KGJpZ3JhbWEsIG4pICU+JSBoZWFkKCkNCmBgYA0KDQpgYGB7cn0NCmJpZ3JhbWFzX0ZyZWlsZSA9IGJpZ3JhbWFzX0ZyZWlsZSAlPiUNCiAgc2VwYXJhdGUoYmlncmFtYSwgYygicGFsYWJyYTEiLCAicGFsYWJyYTIiKSwgc2VwID0gIiAiKSAlPiUNCiAgZmlsdGVyKCFwYWxhYnJhMSAlaW4lIGMoc3RvcHdvcmRzX2VzJFRva2VuKSkgJT4lDQogIGZpbHRlcighcGFsYWJyYTIgJWluJSBjKHN0b3B3b3Jkc19lcyRUb2tlbikpDQoNCmJpZ3JhbWFzX2ZyZWNfRnJlaWxlID0gYmlncmFtYXNfRnJlaWxlICU+JSANCiAgY291bnQoVGl0dWxhciwgcGFsYWJyYTEsIHBhbGFicmEyLCBzb3J0ID0gVFJVRSkgJT4lIA0KICB1bml0ZShiaWdyYW1hLCBwYWxhYnJhMSwgcGFsYWJyYTIsIHNlcCA9ICIgIikNCg0KYmlncmFtYXNfZnJlY19GcmVpbGUgJT4lIHNlbGVjdChiaWdyYW1hLCBuKSAlPiUgaGVhZCgpDQpgYGANCg0KDQpBaG9yYSwgcmVhbGljZW1vcyBlbCB0Zi1pZGYgZW4gZXN0b3MgZGF0YXNldHM6DQoNCmBgYHtyfQ0KYmlncmFtYXNfdGZpZGZfVmVsYXNjbyA9IGJpZ3JhbWFzX2ZyZWNfVmVsYXNjbyAlPiUNCiAgYmluZF90Zl9pZGYoYmlncmFtYSwgVGl0dWxhciwgbikNCg0KYmlncmFtYXNfdGZpZGZfVmVsYXNjbyAlPiUgYXJyYW5nZShkZXNjKHRmX2lkZikpDQpgYGANCg0KYGBge3J9DQpiaWdyYW1hc190ZmlkZl9MYXJyZWEgPSBiaWdyYW1hc19mcmVjX0xhcnJlYSAlPiUNCiAgYmluZF90Zl9pZGYoYmlncmFtYSwgVGl0dWxhciwgbikNCg0KYmlncmFtYXNfdGZpZGZfTGFycmVhICU+JSBhcnJhbmdlKGRlc2ModGZfaWRmKSkNCmBgYA0KDQpgYGB7cn0NCmJpZ3JhbWFzX3RmaWRmX0ZyZWlsZSA9IGJpZ3JhbWFzX2ZyZWNfRnJlaWxlICU+JQ0KICBiaW5kX3RmX2lkZihiaWdyYW1hLCBUaXR1bGFyLCBuKQ0KDQpiaWdyYW1hc190ZmlkZl9GcmVpbGUgJT4lIGFycmFuZ2UoZGVzYyh0Zl9pZGYpKQ0KYGBgDQoNCg0KTGEgdXRpbGlkYWQgYWRpY2lvbmFsIGRlIHV0aWxpemFyIGJpZ3JhbWFzIGVzIHF1ZSBwb2RlbW9zIGRhcmxlIGNvbnRleHRvIGFsIGFuw6FsaXNpcyBkZSBzZW50aW1pZW50b3MuDQoNCiMjICoqMi4zLiBSZWQgZGUgYmlncmFtYXMqKg0KDQpDdWFuZG8gdGVuZW1vcyBiaWdyYW1hcyBvIG4tZ3JhbWFzIGRlIGRpbWVuc2nDs24gbWF5b3IgYSAyLCBlcyBpbnRlcmVzYW50ZSB2aXN1YWxpemFyIGxhcyByZWxhY2lvbmVzIGRlIG1hbmVyYSBzaW11bHTDoW5lYSBhIHRyYXbDqXMgZGUgdW4gZ3JhZm8uIFVuIGdyYWZvIGVzIHNlbmNpbGxhbWVudGUgbGEgY29tYmluYWNpw7NuIGRlIG5vZG9zIGludGVyY29uZWN0YWRvcy4gVGFsZXMgZ3JhZm9zIHNlIHB1ZWRlbiBjb25zdHJ1aXIgYSBwYXJ0aXIgZGUgdW4gb2JqZXRvIHRpZHkgeWEgcXVlIHNvbG8gcmVxdWllcmUgZGUgMyB2YXJpYWJsZXM6DQoNCisgT3JpZ2VuDQorIERlc3Rpbm8NCisgUGVzbw0KDQpFbCBwYXF1ZXRlICppZ3JhcGgqIHNlIHZhbGUgZGUgbGEgbGlicmVyw61hICpnZ3JhcGgqIHBhcmEgcmVhbGl6YXIgZ3JhZm9zIGEgcGFydGlyIGRlIGRhdG9zIGVuIGZvcm1hdG8gdGlkeS4gRXN0byBsbyBoYXJlbW9zIGNvbW8gc2UgbXVlc3RyYSBhIGNvbnRpbnVhY2nDs24uDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZ9DQpsaWJyYXJ5KGlncmFwaCkNCg0KYmlncmFtYV9ncmFmb19WZWxhc2NvID0gYmlncmFtYXNfVmVsYXNjbyAlPiUNCiAgY291bnQocGFsYWJyYTEsIHBhbGFicmEyLCBzb3J0ID0gVFJVRSkgJT4lIA0KICBmaWx0ZXIobiA+PSA2KSAlPiUNCiAgZ3JhcGhfZnJvbV9kYXRhX2ZyYW1lKCkNCg0KYmlncmFtYV9ncmFmb19WZWxhc2NvDQpgYGANCmBgYHtyfQ0KYmlncmFtYV9ncmFmb19MYXJyZWEgPSBiaWdyYW1hc19MYXJyZWEgJT4lDQogIGNvdW50KHBhbGFicmExLCBwYWxhYnJhMiwgc29ydCA9IFRSVUUpICU+JSANCiAgZmlsdGVyKG4gPj0gNikgJT4lDQogIGdyYXBoX2Zyb21fZGF0YV9mcmFtZSgpDQoNCmJpZ3JhbWFfZ3JhZm9fTGFycmVhDQpgYGANCmBgYHtyfQ0KYmlncmFtYV9ncmFmb19GcmVpbGUgPSBiaWdyYW1hc19GcmVpbGUgJT4lDQogIGNvdW50KHBhbGFicmExLCBwYWxhYnJhMiwgc29ydCA9IFRSVUUpICU+JSANCiAgZmlsdGVyKG4gPj0gNikgJT4lDQogIGdyYXBoX2Zyb21fZGF0YV9mcmFtZSgpDQoNCmJpZ3JhbWFfZ3JhZm9fRnJlaWxlDQpgYGANCg0KDQpgYGB7ciBmaWcuaGVpZ2h0PTEwLCBmaWcud2lkdGg9MTAsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9DQpsaWJyYXJ5KGdncmFwaCkNCnNldC5zZWVkKDEyMykNCg0KZ2dyYXBoKGJpZ3JhbWFfZ3JhZm9fVmVsYXNjbywgbGF5b3V0ID0gImZyIikgKw0KICBnZW9tX2VkZ2VfbGluaygpICsNCiAgZ2VvbV9ub2RlX3BvaW50KCkgKw0KICBnZW9tX25vZGVfbGFiZWwoYWVzKGxhYmVsID0gbmFtZSksIHZqdXN0ID0gMSwgaGp1c3QgPSAxKQ0KYGBgDQoNCmBgYHtyIGZpZy5oZWlnaHQ9MTAsIGZpZy53aWR0aD0xMH0NCmdncmFwaChiaWdyYW1hX2dyYWZvX0xhcnJlYSwgbGF5b3V0ID0gImZyIikgKw0KICBnZW9tX2VkZ2VfbGluaygpICsNCiAgZ2VvbV9ub2RlX3BvaW50KCkgKw0KICBnZW9tX25vZGVfbGFiZWwoYWVzKGxhYmVsID0gbmFtZSksIHZqdXN0ID0gMSwgaGp1c3QgPSAxKQ0KYGBgDQoNCmBgYHtyIGZpZy5oZWlnaHQ9MTAsIGZpZy53aWR0aD0xMH0NCmdncmFwaChiaWdyYW1hX2dyYWZvX0ZyZWlsZSwgbGF5b3V0ID0gImZyIikgKw0KICBnZW9tX2VkZ2VfbGluaygpICsNCiAgZ2VvbV9ub2RlX3BvaW50KCkgKw0KICBnZW9tX25vZGVfbGFiZWwoYWVzKGxhYmVsID0gbmFtZSksIHZqdXN0ID0gMSwgaGp1c3QgPSAxKQ0KYGBgDQoNCg0KIyAqKjMuIE51YmVzIGRlIHBhbGFicmFzKiogDQoNClBhcmEgcmVhbGl6YXIgbnViZXMgZGUgcGFsYWJyYXMsIHF1ZSBjb2xvcXVlbiBlbCB0YW1hw7FvIGRlIGxhcyBwYWxhYnJhcyBhY29yZGUgYSBzdSBmcmVjdWVuY2lhLCB1dGlsaXphcmVtb3MgbGEgbGlicmVyw61hICp3b3JkY2xvdWQyKi4NCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpsaWJyYXJ5KGVjaGFydHM0cikNCiMgbGlicmFyeShzdHJpbmdpKQ0Kd2NfVmVsYXNjbyA9IHRpZHlfVmVsYXNjb19hbm5vdGF0ZWQgJT4lIA0KICBmaWx0ZXIodXBvcyAlaW4lIGMoIk5PVU4iLCJQUk9QTiIsICJBREoiLCAiVkVSQiIpKSAlPiUNCiAgY291bnQobGVtbWEsIHNvcnQ9VCkgJT4lIA0KICBmaWx0ZXIobiA+IDIwKSAlPiUgDQogIGVfY29sb3JfcmFuZ2UobiwgY29sb3IpICU+JQ0KICBlX2NoYXJ0cygpICU+JQ0KICBlX2Nsb3VkKGxlbW1hLCBuLCBjb2xvcikgJT4lIA0KICBlX3Rvb2x0aXAoKQ0Kd2NfVmVsYXNjbw0KYGBgDQoNCiMgKio0LiBFeHRyYWNjacOzbiBkZSBwYWxhYnJhcyBjbGF2ZSoqDQoNCkVsIGdlbmVyYXIgY29uY2x1c2lvbmVzIGUgaW5zaWdodCBkZSB1biB0ZXh0byBjb24gbWlsZXMgZGUgbMOtbmVhcyB5IHBhbGFicmFzIHB1ZWRlIHNlciBhYm9yZGFkbyBkZXNkZSB2YXJpYXMgcGVyc3BlY3RpdmFzLiBVbmEgZGUgZWxsYXMgZXMgbGEgZXh0cmFjY2nDs24gZGUgcGFsYWJyYXMgY2xhdmVzLCBxdWUgcHVlZGUgc2VyIGNvbnNlZ3VpZGEgYSB0cmF2w6lzIGRlIGFsZ29yaXRtb3MgZGUgTkxQLiBFc3RhIHNlcsOhIGVudG9uY2VzIG51ZXN0cmEgw7psdGltYSBtaXNpw7NuIGVuIGVzdGUgY2Fww610dWxvLg0KDQpBY29yZGUgYSBUaHVzaGFyYSAqZXQgYWwuKiAoMjAxOSksIGFsZ3Vub3MgZGUgbG9zIGFsZ29yaXRtb3MgZGUgZXh0cmFjY2nDs24gZGUgcGFsYWJyYXMgY2xhdmUgc29uOg0KDQorIFJBS0UgKFJhcGlkIEF1dG9tYXRpYyBLZXl3b3JkIEV4dHJhY3Rpb24pOiAgRXMgdW4gYWxnb3JpdG1vIG5vIHN1cGVydmlzYWRvIGluZGVwZW5kaWVudGUgZGVsIGlkaW9tYSBxdWUgb2J0aWVuZSBwYWxhYnJhcyBjbGF2ZSBhIHRyYXbDqXMgZGVsIGFuw6FsaXNpcyBkZSBsYSBmcmVjdWVuY2lhIHkgZ3JhZG8gKGNvLW9jdXJyZW5jaWEpIGRlIGxhcyBwYWxhYnJhcy4NCisgVGV4dFJhbms6IEVzIHVuYSB0w6ljbmljYSBubyBzdXBlcnZpc2FkYSBiYXNhZGEgZW4gZ3JhZm9zIHF1ZSBvYnRpZW5lIGVsIHJlc3VtZW4gZGUgdW4gdGV4dG8uIEVzdMOhIGJhc2FkYSBzb2JyZXRvZG8gZW4gZXRpcXVldGFkbyBQT1MuDQorIFBvc2l0aW9uUmFuazogRXMgdW4gYWxnb3JpdG1vIGJhc2FkbyBlbiBncmFmb3MgcXVlIGJ1c2NhIGZyYXNlcyBhbCBpbmljaW8gZGUgbG9zIGRvY3VtZW50b3MgeSBsb3MgY2FuZGlkYXRpemEgYSBzZXIgY2xhdmUuDQoNCiMjICoqNC4xLiBSQUtFKioNCg0KTGEgbGlicmVyw61hICp1ZHBpcGUqIGluY2x1eWUgZWwgYWxnb3JpdG1vIFJBS0UgcGFyYSBleHRyYWNjacOzbiBkZSBwYWxhYnJhcyBjbGF2ZS4gRXN0ZSBlcyB1biBhbGdvcml0bW8gbm8gc3VwZXJ2aXNhZG8gaW5kZXBlbmRpZW50ZSBkZWwgaWRpb21hIHF1ZSBvYnRpZW5lIHBhbGFicmFzIGNsYXZlIGEgdHJhdsOpcyBkZWwgYW7DoWxpc2lzIGRlIGxhIGZyZWN1ZW5jaWEgeSBncmFkbyAoY28tb2N1cnJlbmNpYSkgZGUgbGFzIHBhbGFicmFzLiBFc3BlY8OtZmljYW1pZW50ZSwgc2lndWUgZXN0b3MgcGFzb3M6DQoNCisgU2UgZXh0cmFlbiBsYXMgcGFsYWJyYXMgY2FuZGlkYXRhcyBkZWwgY29uanVudG8gZGUgcGFsYWJyYXMsIGRlc3B1w6lzIGRlIGhhYmVyIHF1aXRhZG8gcGFsYWJyYXMgdmFjw61hcyBlIGlycmVsZXZhbnRlcy4NCisgU2UgY2FsY3VsYSB1biBzY29yZSBwYXJhIGNhZGEgcGFsYWJyYSBhIHRyYXbDqXMgZGU6DQogICsgU2UgYnVzY2EgY3XDoW50YXMgdmVjZXMgb2N1cnJlIHVuYSBwYWxhYnJhIHkgY3XDoW50YXMgdmVjZXMgY28tb2N1cnJlIGNvbiBvdHJhLg0KICArIFNlIGNvbnN0cnV5ZSBlbCBzY29yZSBjb21vIGVsIHJhdGlvIGRlIGNvLW9jdXJyZW5jaWFzIHZlcnN1cyBmcmVjdWVuY2lhLg0KKyBTZSBvcmRlbmEgZWwgc2NvcmUgZGUgbWF5b3IgYSBtZW5vciB5IGxhcyBwcmltZXJhcyBzZXLDoW4gbGFzIHBhbGFicmFzIGNsYXZlLg0KDQpBIGNvbnRpbnVhY2nDs24gdmVyZW1vcyBlamVtcGxvcyBkZSBsYSBhcGxpY2FjacOzbiBkZSBlc3RhIHTDqWNuaWNhIHBhcmEgcGFsYWJyYXMgeSBuLWdyYW1hcy4NCg0KYGBge3J9DQpsaWJyYXJ5KHVkcGlwZSkNClZlbGFzY29fa2V5d29yZHMgPSBrZXl3b3Jkc19yYWtlKHggPSB0aWR5X1ZlbGFzY28sIHRlcm0gPSAibGVtbWEiLCBncm91cCA9ICJkb2NfaWQiLCByZWxldmFudCA9IHRpZHlfVmVsYXNjbyR1cG9zICVpbiUgYygiTk9VTiIsICJBREoiLCJQUk9QTiIsIlZFUkIiKSwgbmdyYW1fbWF4ID0gMykNCmhlYWQoVmVsYXNjb19rZXl3b3JkcykNCmBgYA0KDQpgYGB7cn0NCkxhcnJlYV9rZXl3b3JkcyA9IGtleXdvcmRzX3Jha2UoeCA9IHRpZHlfTGFycmVhLCB0ZXJtID0gImxlbW1hIiwgZ3JvdXAgPSAiZG9jX2lkIiwgcmVsZXZhbnQgPSB0aWR5X0xhcnJlYSR1cG9zICVpbiUgYygiTk9VTiIsICJBREoiLCJQUk9QTiIsIlZFUkIiKSwgbmdyYW1fbWF4ID0gMykNCmhlYWQoTGFycmVhX2tleXdvcmRzKQ0KYGBgDQoNCmBgYHtyfQ0KRnJlaWxlX2tleXdvcmRzID0ga2V5d29yZHNfcmFrZSh4ID0gdGlkeV9GcmVpbGUsIHRlcm0gPSAibGVtbWEiLCBncm91cCA9ICJkb2NfaWQiLCByZWxldmFudCA9IHRpZHlfRnJlaWxlJHVwb3MgJWluJSBjKCJOT1VOIiwgIkFESiIsIlBST1BOIiwiVkVSQiIpLCBuZ3JhbV9tYXggPSAzKQ0KaGVhZChGcmVpbGVfa2V5d29yZHMpDQpgYGANCg0KPCEtLSAjICoqNS4gQ2FzbyBwcsOhY3RpY28qKiAtLT4NCg0KPCEtLSBQYXJhIGFwcm9iYXIgZXN0ZSBjdXJzbyBlbCBlc3R1ZGlhbnRlIGRlYmVyw6EgaGFjZXIgdW4gdHJhYmFqbyBwcsOhY3RpY28gY29uIHRvZG8gbG8gcXVlIGFwcmVuZGltb3MuIC0tPg0KDQo8IS0tIEVuIGJhc2UgYSBsYXMgdMOpY25pY2FzIGRlIG9idGVuY2nDs24sIGVzdHJ1Y3R1cmFjacOzbiwgcHJlLXByb2Nlc2FtaWVudG8geSBhbsOhbGlzaXMgZXhwbG9yYXRvcmlvIGRlIGRhdG9zIGRlIHRleHRvIHJldmlzYWRhcyBlbiBjbGFzZSwgZGVmaW5pciB1biBvYmpldGl2byBkZSBhbsOhbGlzaXMgcGFyYSB1biBjYXNvIGRlIGVzdHVkaW8gZGUgc2VsZWNjacOzbiBwZXJzb25hbC4gU29icmUgZXN0ZSBvYmpldGl2byByZWFsaXphciBjYWRhIHBhc28gZGVmaW5pZG8gZW4gbGEgc2VjY2nDs24g4oCcRmx1am8gZGUgdHJhYmFqbyBzb2JyZSBkYXRvcyBkZSB0ZXh0b+KAnSBkZWwgbnVtZXJhbCAxLjEuIGRlbCBjYXDDrXR1bG8gMy4gRXMgZGVjaXIsIGVsIGVzdHVkaWFudGUgZGViZXLDoTogLS0+DQoNCjwhLS0gKyBEZWZpbmlyIHVuIG9iamV0aXZvIGRlIGFuw6FsaXNpcy4gLS0+DQo8IS0tICsgT2J0ZW5lciBsb3MgZGF0b3MgYSB0cmF2w6lzIGRlIHdlYiBzY3JhcHBpbmcsIGRvY3VtZW50b3MgcGRmIG8gdXNhciB1biBkYXRhc2V0IGFwcm9waWFkby4gLS0+DQo8IS0tICsgUmVhbGl6YXIgbGEgZXN0cnVjdHVyYWNpw7NuIHkgcHJlLXByb2Nlc2VhbWllbnRvIGRlIGxvcyBkYXRvcywgaW5jbHV5ZW5kbyB5IG5vIGxpbWl0w6FuZG9zZSBhOiBkYXIgZm9ybWF0byB0aWR5IGEgbG9zIGRhdG9zLCB0b2tlbml6YXIsIGxlbWF0aXphciwgcmVhbGl6YXIgc3RlbW1pbmcsIHJlbW92ZXIgc3RvcHdvcmRzLCBldGMuIENhYmUgbm90YXIgcXVlLCBzZWfDum4gc3Ugb2JqZXRpdm8gZGUgZXN0dWRpbywgbGEgbGVtYXRpemFjacOzbiB5IHN0ZW1taW5nIHBvZHLDrWEgbm8gc2VyIG5lY2VzYXJpYXMuIC0tPg0KPCEtLSArIFJlYWxpemFyIGVsIGFuw6FsaXNpcyBleHBsb3JhdG9yaW8gc29icmUgcGFsYWJyYXMsIG4tZ3JhbWFzIG8gYWxnw7puIHRva2VuIGRlc2VhZG8uIFNlIHB1ZWRlbiB1c2FyIG51YmVzIGRlIHBhbGFicmFzLCBhbsOhbGlzaXMgZGUgZnJlY3VlbmNpYXMsIGdyYWZvcywgeSBvdHJhcyB0w6ljbmljYXMgcXVlIGVsIGVzdHVkaWFudGUgY29uc2lkZXJlIGFwcm9waWFkYS4gLS0+DQo8IS0tIENvbW8gw7puaWNhIHJlc3RyaWNjacOzbiBhIGVzdGEgdGFyZWEgc2UgZXN0YWJsZWNlIHF1ZSBubyBzZSBwb2Ryw6FuIHV0aWxpemFyIGxvcyBkYXRhc2V0IGRlIG5vdGljaWFzIG5pIGxpdGVyYXR1cmEgaW5jbHVpZGFzIGVuIGxvcyBub3RlYm9va3MgZGUgbGFzIGNsYXNlcy4gLS0+DQoNCjwhLS0gRWwgdGllbXBvIGzDrW1pdGUgcGFyYSBsYSBlbnRyZWdhIGRlIGxhIGFjdGl2aWRhZCBzZSBtZW5jaW9uYXLDoSBlbiBlbCDDumx0aW1vIGTDrWEgZGUgY2xhc2UsIHBlcm8gbm8gc2Vyw6EgaW5mZXJpb3IgYSBsb3MgMTUgZMOtYXMuIEVsIGFuw6FsaXNpcyBzZSBlbnRyZWdhcsOhIGNvbW8gdW4gc2NyaXB0IGNvbWVudGFkbyBvIGNvbiB1biBjdWFkZXJubyBkZSB0cmFiYWpvIHJlYWxpemFkbyBlbiBsZW5ndWFqZSBSIHNvYnJlIHVuIElERSBhcHJvcGlhZG8gKFJTdHVkaW8sIEp1cHl0ZXJMYWIsIEp1cHl0ZXJOb3RlYm9vaywgZXRjKS4gLS0+DQoNCjwhLS0gQXF1ZWxsb3MgdHJhYmFqb3Mgc2luIGNvbmNsdXNpb25lcyBhY2VyY2EgZGUgbG9zIHJlc3VsdGFkb3Mgc2Vyw6FuIGNhbGlmaWNhZG9zIGNvbiB1bmEgbm90YSBkZSBjZXJvLiAtLT4NCg0KDQojICoqNS4gQmlibGlvZ3JhZsOtYSoqDQo=