1. Introducción

Vamos a utilizar R como una aplicación GIS (Sistemas de información geográfica); es decir, lo vamos a utilizar para almacenar, manipular, analizar y presentar datos geográficos o geoespaciales. GIS es un área enorme, por ejemplo, aquí tienes un listado de herramientas y paquetes para trabajar con datos espaciales en R. Hay muchísimas!!!, es un área de conocimiento en sí misma, así que sólo voy a intentar hacer una pequeña introducción a GIS con R.

Durante este tutorial usaremos distintos data.frames con geometrías de:

  • países del mundo (world)
  • CC.AA españolas (CCAA)
  • provincias españolas (Provincias)
  • municipios españoles en 2020 (municipios_2020)
  • ríos españoles (rios_espanya)
  • 4 ríos relacionados con el río Ebro (rio_ebro_4)
  • localización (longitud, latitud) de los municipios españoles (IGN_nomencla_muni)

Venga, vamos a hacer mapas con ellos!!! Sí, enseguida!! A todos nos gustan los mapas y hacer mapas con R es easy … bueno, no tan fácil, pero vamos a ello!!

Más que mapas lo que vamos a hacer es visualizar datos geoespaciales. Unos datos no se pueden considerar geoespaciales si no vienen acompañados de un CRS (Coordinate Reference System) de forma que las aplicaciones GIS puedan manipular y mostrar correctamente esos datos. Los CRS utilizan un modelo matemático para conectar los datos, las coordenadas de un objeto, con su posición física en la superficie terrestre. Un sistema de referencia de coordenadas geográficas permite que todos los puntos de la tierra puedan ser descritos como un conjuntos de coordenadas (como latitud, longitud y altitud). Diversos sistemas se utilizan para representar la tierra tridimensional en un plano o mapa bidimensional.

Los datos geoespaciales representan lugares geográficos de la Tierra y para poder representar la posición de un objeto en la Tierra necesitamos dos cosas:

  • las coordenadas del objeto

  • un sistema de referencia (CRS) para conectar esos datos con la superficie terrestre.

Un CRS consiste en modelo geométrico elipsoide de la superficie terrestre y un datum que identifica el origen y orientación de los ejes y también las unidades de medida.

Como sabéis la Tierra no es esférica y esto complica la utilización de un único CRS, de forma que se han creado unos cuantos para tratar de representar la superficie terrestre de forma adecuada.

Además, hay dos tipos de CRS: CRS geográficos y proyectados. En los sistema geográficos, el marco de referencia es el globo terráqueo y cuantos grados norte o este, está el objeto; mientras que en los sistemas proyectados, el marco de referencia es una representación plana de la Tierra (o de parte de ella). Si te interesa el tema, aquí y aquí tienes dos posts divulgativos.

Proyecciones

Como la Tierra es esférica, para hacer un mapa bidimensional (o plano) de ella, se tiene que usar algún tipo de proyección, por lo que las áreas se mostrarán más o menos distorsionadas. Por ejemplo:

p1 <- ggplot(CCAA,  aes(fill = ine_ccaa) ) + 
          geom_sf() +
          scale_fill_viridis_d(guide = "none") +
          labs(title = "Coord. geográficas")

p2 <- ggplot(CCAA,  aes(fill = ine_ccaa) ) + 
           geom_sf() + coord_sf(crs = 2154, datum = sf::st_crs(2154)) +
           scale_fill_viridis_d(guide = "none") +
           labs(title = "Lambert 93")

p1 + p2

Datos espaciales

Para poder representar adecuadamente datos espaciales, estos deben contener la información de su CRS y algunas veces hay que convertir entre CRS’s; de forma que la estructura tradicional de datos en R (los data.frames) no es adecuada para almacenar datos espaciales1. Piensa además que los datos espaciales más simples (los puntos) serían fáciles de almacenar en dos columnas de un data.frame, pero piensa en objetos espaciales más complejos como lineas, polígonos, polígonos con huecos etc… hace que para almacenar datos espaciales en R se utilicen estructuras de datos más complejas que un data.frame.

Además, para complicar un poco más las cosas, hay dos tipos fundamentales de datos geográficos: raster y vectoriales. En el curso solo vamos a ver (un poquito) los datos espaciales vectoriales.

  • Los datos ráster se almacenan como una cuadrícula de valores que se representan en un mapa como píxeles. Cada valor de píxel representa un área en la superficie de la Tierra. Los datos ráster son datos pixelados (o cuadriculados) en los que cada píxel está asociado a una ubicación geográfica específica. El valor de un píxel puede ser continuo (por ejemplo, elevación) o categórico (por ejemplo, uso del suelo). Un ráster geoespacial solo es diferente de una foto digital en que está acompañado de información espacial que conecta los datos a una ubicación en particular. Esto incluye la extensión y el tamaño de celda del ráster, el número de filas y columnas y su sistema de referencia de coordenadas (o CRS)

  • Las estructuras de datos vectoriales representan posiciones específicas en la superficie de la Tierra.

sp versus sf

En R hay 2 formas alternativas de almacenar (y de manipular) datos geográficos vectoriales: el paquete sp y el paquete sf. Las principales diferencias entre los paquetes sp y sf se derivan de la manera en que las clases sp y sf almacenan información sobre CRS, distinguen entre puntos, líneas y polígonos, y cómo conectan estos datos espaciales con datos no espaciales.

Cuando se usan estos paquetes, tanto sp como sf, proporcionan enlaces a potentes bibliotecas SIG de código abierto, como GDAL (Geospatial Data Abstraction Library: es una biblioteca de software para la lectura y escritura de formatos de datos geoespaciales, publicada bajo la MIT License por la fundación geoespacial de código abierto (Open Source Geospatial Foundation), GEOS (Geometry Engine Open Source: para operaciones de topología en geometrías) y PROJ (PROJ es una biblioteca que proporciona métodos para transformar entre sistemas de referencia de coordenadas diferentes y operaciones de proyección). Por eso cuando cargamos el paquete sf nos aparece el siguiente mensaje en la consola: Linking to GEOS 3.9.1, GDAL 3.3.2, PROJ 7.2.1

Muchos paquetes R para GIS utilizan la metodología/arquitectura proporcionada por sp, pero por varias razones la metodología implementada por el paquete el paquete sf ya es prácticamente el estándar.

Las principales razones son 4:

  1. El paquete sf implementa el estándar abierto “simple feautures” para la representación de datos geográficos vectoriales en R. Simple features es un estándar, un conjunto común de reglas, para representar las geometrías espaciales de objetos del mundo real en un ordenador, en las que se define desde cómo se almacenan los datos en el ordenador hasta qué operaciones geométricas se pueden aplicar.

  2. sf utiliza para contener/almacenar los datos espaciales una estructura muy similar a un dataframe: los spatial dataframes o sf2 , que son muy parecidos a un data frame salvo que incluyen una columna adicional, generalmente llamada geometry que almacena la información geográfica, los datos vectoriales espaciales, ya sean estos puntos, lineas, polígonos etc…

  3. Muchas de las funciones del paquete sf comienzan con st_; por ejemplo st_nearest_points(), lo que facilita su uso.

  4. las funciones de sf se integran en el tidyverse workflow. De hecho la estructura de datos que utiliza el paquete sf para almacenar datos geográficos son los objetos de tipo `sf una extensión de los data.frames, de forma que pueden ser manipulados con dplyr y graficados con ggplot2. Los objetos sf pueden almacenar datos normales (cuantitativos, cualitativos, texto) pero almacenan datos espaciales en una columna especial llamada habitualmente geometry con clase especial (sfc), y allí se almacena la información geográfica: el CRS, coordenadas y el tipo de objeto geométrico. La clase sfc tiene siete subclases para denotar los distintos tipos de objetos geométricos que se derivan del estándar sf.

  • Por ejemplo, para seleccionar geometrías usamos la función filter():
#- elegimos 4 CC.AA
zz_prov_chosen <- c("Andalucía", "Galicia", "Aragón", "Canarias", "Illes Balears")

zz_prov_1 <- Provincias %>% filter(ine_ccaa.n %in% zz_prov_chosen)
zz_prov_2 <- Provincias %>% filter(!(ine_ccaa.n %in% zz_prov_chosen))

p1 <- ggplot(zz_prov_1, aes(fill = ine_prov) ) + 
         geom_sf()  +
         scale_fill_viridis_d(guide = "none") +
         labs(title = "Coord. geográficas")

p2 <- ggplot(zz_prov_2, aes(fill = ine_prov) ) + 
         geom_sf()  +
         scale_fill_viridis_d(guide = "none") +
         labs(title = "Coord. geográficas")

p1 + p2 

  • Por ejemplo, para agrupar geometrías, en este caso las provincias españolas, usamos la función summarize()
#- creamos las geometrías de las CC.AA por agrupación de las geometrías provinciales
CCAA_2 <- Provincias %>% group_by(ine_ccaa, ine_ccaa.n) %>% summarize()

p1 <- ggplot(Provincias, aes(fill = ine_prov) ) + 
         geom_sf()  +
         scale_fill_viridis_d(guide = "none") +
         labs(title = "Coord. geográficas")


p2 <- ggplot(CCAA_2, aes(fill = ine_ccaa) ) + 
         geom_sf()  +
         scale_fill_discrete(guide = "none") +
         labs(title = "Coord. geográficas")

p1 + p2 

Bueno, pues este es toda la teoría del tutorial. Vamos a hacer gráficos!!




2. Haciendo gráficos

Primero vamos cargar un conjunto de datos espaciales del paquete rnaturalearth. Bueno, en realidad ya lo hemos cargado antes.

#- antes usaba las worlg geometies del paquete rnaturalearth, pero ...
# world <- rnaturalearth::ne_countries(scale = "medium", returnclass = "sf")
#- pero ahora prefiero utilizar las geometrias del paquete tmap,
#- las guarde en noviembre de 2021. Se cargaban así:
library(tmap) 
data(World) #- hacemos accesibles 1 df
world <- World ; rm(World)

Veamos que hay en world

class(world) #- es un sf, pero tb es un data.frame
#> [1] "sf"         "data.frame"
names(world) #- fijate que la ultima columna se llama geometry y almacena los datos espaciales
#>  [1] "iso_a3"       "name"         "sovereignt"   "continent"    "area"        
#>  [6] "pop_est"      "pop_est_dens" "economy"      "income_grp"   "gdp_cap_est" 
#> [11] "life_exp"     "well_being"   "footprint"    "inequality"   "HPI"         
#> [16] "geometry"

Los gráficos los vamos a hacer, de momento, con ggplot2. Es tan fácil como usar geom_sf(). Las siguientes 3 expresiones producen el mismo gráfico:

ggplot(data = world) + geom_sf()

ggplot(data = world, aes(geometry = geometry)) + geom_sf()

ggplot() + geom_sf(data = world, aes(geometry = geometry))

Como el mapa que hemos hecho, en realidad es un gráfico ggplot, pues ya sabemos cambiar/tunear algunas de las características del gráfico. Por ejemplo:

ggplot(data = world) + geom_sf(color = "black", fill = "lightgreen", lwd = 0.2)

Podemos poner títulos y leyendas:

p <- ggplot(data = world) + geom_sf() +
       labs(title = "Gráfico 1: Mapa del mundo",
            caption = "Datos provenientes de tmap")
p

Podemos hacer mapas de COROPLETAS: las distintas regiones se colorean de forma que muestren los valores de una variable estadística. Por ejemplo, una coropleta con la variable población(pob_est)

p + geom_sf(aes(fill = pop_est))
p + geom_sf(aes(fill = pop_est)) + scale_fill_distiller()
p + geom_sf(aes(fill = pop_est)) + scale_fill_viridis_c(option = "plasma", trans = "sqrt")


Proyecciones

Para cambiar la proyección de un objeto “sf” a otro CRS usamos la función st_transform(), o alternativamente podemos especificar directamente el CRS en el gráfico con coord_sf():

#- world2 <- st_transform(world, "+proj=laea +y_0=0 +lon_0=155 +lat_0=-90 +ellps=WGS84 +no_defs")
p + coord_sf(crs = "+proj=laea +y_0=0 +lon_0=155 +lat_0=-90 +ellps=WGS84 +no_defs") + 
     labs(subtitle = "Lambert Azimuthal Equal Area projection")


Vamos a jugar un poco con el CRS con la función coord_sf(). Hay que saber que ggplot usará el CRS de la primera capa que contenga uno. Si no hubiese ningún CRS se usa WGS84 (latitude/longitude, the reference system used in GPS).

Se puede cambiar el CRS de varias maneras:

  1. usando un **string PROJ** válido. Por ejemplo este string“+proj=laea +lat_0=52 +lon_0=10 +x_0=4321000 +y_0=3210000 +ellps=GRS80 +units=m +no_defs”` es el European-centric ETRS89 Lambert Azimuthal Equal-Area projection)

  2. usando un código SRID (Spatial Reference System Identifier)

  3. Usando un código EPGS (European Petroleum Survey Group)3

Los siguientes tres gráficos hacen lo mismo. Representan los datos del spatial.data.frame world usando the ETRS89 Lambert Azimuthal Equal-Area projection, cuyo código EPSG es 3035. Aquí tienes algunos códigos de proyecciones:

p + coord_sf(crs = "+proj=laea +lat_0=52 +lon_0=10 +x_0=4321000 +y_0=3210000 +ellps=GRS80 +units=m +no_defs ")  #- usamos PROJ string

p + coord_sf(crs = st_crs(3035))   #- usamos SRID identifier

p + coord_sf(crs = "+init=epsg:3035")  #- usamos EPSG code (mejor no usarlo xq GDAL ha deprecated esta sintaxis)
p + coord_sf(crs = st_crs(3035)) + #- usamos SRID identifier
    labs(subtitle = "(European-centric ETRS89 \nLambert Azimuthal Equal-Area projection)")


Zoom

Volvemos a la proyección nativa del conjunto de datos world y hacemos un zoom en nuestro mapa con coord_sf(). El zoom lo hacemos pare centrarnos en España y alrededores. Para hacer el zoom en realidad ajustamos los límites de los ejes del gráfico

p_esp <- p + coord_sf(xlim = c(-15.00, 15.00), ylim = c(21, 47.44), expand = TRUE)
p_esp

Con el paquete ggspatial podemos poner elementos de mapas como una Scale bar con annotation_scale() y una North arrow con annotation_north_arrow():

p_esp + 
 ggspatial::annotation_scale(location = "bl", width_hint = 0.5) +
 ggspatial::annotation_north_arrow(location = "bl", which_north = "true", 
                                   pad_x = unit(0.75, "in"), pad_y = unit(0.5, "in"),
                                   style = north_arrow_fancy_orienteering)


texto en el mapa

El mapa se ha hecho con ggplot2, así que podemos añadir, por ejemplo, texto con geom_text(); pero antes de ello, vamos a crear/calcular centroides de cada país para ver donde situamos el texto en el mapa.

world_points <- st_centroid(world, of_largest_polygon = TRUE)    #- sustituye la geometry por el centroide

De esta forma, con st_centroid() podemos calcular los centroides de cada geometría, de cada país, pero Los datos del centroide (longitud, latitud) se almacenan en la columna geometry; es decir sustituyen o eliminan las geometrías anteriores que eran los límites o fronteras de los países.

Si queremos que las geometrías originales y los centroides estén en un mismo data.frame, hemos de hacer:

world_points <- cbind(world, st_coordinates(st_centroid(world$geometry, of_largest_polygon = TRUE))) #- ahora el centroide pasa a estar en 2 columnas, llamadas X e Y, donde figuran la longitud y latitud del centroide

Lo que hacemos es fusionar los los dos data.frame’s, y el centroide pasará a estar en dos nuevas columnas llamadas X e Y, sin sustituir las geometrías de los países.

Para ver esas dos nuevas columnas (X e Y), vamos a hacer un “pequeño truco”, vamos a quitar la columna de geometrías. Esto facilita el visionado de los datos, pero lógicamente cuando vayamos a hacer el gráfico, realmente no podremos eliminar la geometry porque si no, los datos dejarían de ser datos espaciales.

#- truco para ver mejor los datos espaciales: quitar la geometría
names(world_points)
#>  [1] "iso_a3"       "name"         "sovereignt"   "continent"    "area"        
#>  [6] "pop_est"      "pop_est_dens" "economy"      "income_grp"   "gdp_cap_est" 
#> [11] "life_exp"     "well_being"   "footprint"    "inequality"   "HPI"         
#> [16] "X"            "Y"            "geometry"
zz <- world_points %>% select(name, iso_a3,  X, Y) %>%  
      st_set_geometry(NULL)  #- por si quiero ver mejor los datos del df

Hacemos el mapa con los nombres de los países con geom_text() y el nombre del Mar Mediterráneo con annotate()

p_esp + 
  geom_text(data = world_points, 
            aes(x = X, y = Y, label = name), 
            color = "darkblue", fontface = "bold", check_overlap = TRUE, size = 2.5) +
  annotate(geom = "text", x = 5.5, y = 38, 
           label = "Mar Mediterráneo", fontface = "italic", color = "grey22", size = 4)


Tuneando del mapa

Vamos a tunear el mapa. Por ejemplo, vamos a poner el mar (en realidad al background del theme) del color aliceblue, y a los continentes les pondremos un color terroso con fill = "antiquewhite". Arreglamos un poco las grid-lines dentro del theme, y le ponemos la estrellita. No nos va a salir bonito-bonito porque los nombres de los países se van a superponer. Podríamos quitar nombres, pero otro día:

p <- p + 
  geom_sf(fill = "antiquewhite") +
  geom_text(data = world_points, 
            aes(x = X, y = Y, label = name), 
                color = "darkblue", fontface = "bold", 
                check_overlap = TRUE, size = 2) +
  annotation_north_arrow(location = "bl", which_north = "true",
                         pad_x = unit(0.75, "in"), 
                         pad_y = unit(0.5, "in"),
                         style = north_arrow_fancy_orienteering) +
  theme(panel.grid.major = element_line(color = gray(.5), linetype = "dashed", size = 0.5), 
        panel.background = element_rect(fill = "aliceblue"))
p

Hacemos otra vez un zoom con sf::coord_sf()

p + coord_sf(xlim = c(-15.00, 15.00), ylim = c(21, 47.44), expand = TRUE) +
    ggspatial::annotation_scale(location = "bl", width_hint = 0.5)

Como nos ha quedado medio chulo, si quisiéramos guardar el mapa haríamos:

ggsave("map.pdf")
ggsave("map_web.png", width = 6, height = 6, dpi = "screen")




3. Mas tuneado

Vamos a ir modificando el mapa, no necesariamente para hacerlo más bonito, sino para ver como se pueden hacer mapas con ggplot2:

añadiendo puntos

Primero quiero añadir al mapa donde está mi pueblo y donde está Valencia, vamos que voy a añadir puntos al mapa.

Para ello, primero necesito tener la localización de estos puntos en el mapa, así que me fui a OSM, OpenStreetMap y geolocalicé los dos municipios y con esta información, voy a crear un data.frame con la long/lat de mi pueblo y de Valencia:

pueblos <- data.frame(
               nombre = c("Pancrudo", "Valencia"), 
               longitude = c(-1.028781, -0.3756572),
               latitude = c(40.76175, 39.47534) )     
pueblos
nombre longitude latitude
Pancrudo -1.0287810 40.76175
Valencia -0.3756572 39.47534
  • lo mas fácil para añadir puntos es usar geom_point()
p_esp + 
  geom_point(data = pueblos, 
             aes(x = longitude, y = latitude), 
             size = 2, color = "darkred") +
  geom_text(data = pueblos, 
            aes(x = longitude, y = latitude, label = nombre), 
            color = "darkblue", fontface = "bold", check_overlap = TRUE, size = 2.5)

Utilizar geom_point() es fácil y funciona, pero es más flexible convertir el data.frame a un objeto sf. Esto lo podemos hacer con la función st_as_sf(), definiendo la proyección (aquí WGS84, que es el código CRS # 4326):

pueblos_sf <- st_as_sf(pueblos, 
                       coords = c("longitude", "latitude"), 
                       crs = 4326,  #- EPSG:4326 Coordenadas Geográficas WGS84
                       agr = "constant")

Para después añadirlo como una capa más con geom_sf() a nuestro gráfico:

p_esp + geom_sf(data = pueblos_sf, size = 2, color = "darkred")

Hagamos zoom con coord_sf

p_esp + geom_sf(data = pueblos_sf, size = 2, color = "darkred") + 
        coord_sf(xlim = c(-15.00, 15.00), ylim = c(21, 47.44), expand = TRUE)

Añadiendo polígonos

La verdad es que se hace igual que con los puntos, hay que usar la función geom_sf() y a correr.

Vamos a añadir los contornos/polígonos de las CC.AA:

zz_CCAA <- CCAA %>% st_set_geometry(NULL)
p + geom_sf(data = CCAA, fill = NA) + 
      coord_sf(xlim = c(-15.00, 15.00), ylim = c(21, 47.44), expand = TRUE)

Ahora añadimos las provincias de Andalucía, PERO coloreadas según su población media por municipio. Para ello:

    1. primero cargamos datos de población
INE_padron_muni_96_20 <- pjpv.datos.01::pob_muni_1996_2020
#INE_padron_muni_96_20 <- pjpv2020.01::pjp_data_pob_mun_1996_2019
pob_andalucia <- INE_padron_muni_96_20 %>% 
                 filter(year == 2020) %>% 
                 filter(ine_ccaa.n == "Andalucía") %>%
                 group_by(ine_prov.n) %>% mutate(n_pueblos = n_distinct(ine_muni.n)) %>%
                 mutate(pob_prov = sum(pob_total, na.rm = TRUE)) %>% ungroup() %>%
                 mutate(pob_media_pueblos = pob_prov/n_pueblos) %>% 
                 select(ine_prov.n, ine_prov, pob_media_pueblos, pob_prov, n_pueblos) %>%
                 distinct() %>% ungroup()
    1. después cargamos lindes provinciales (en realidad ya los tenemos)
# Provincias <- Provincias
zz_Provincias <- Provincias %>% st_set_geometry(NULL)
    1. fusionamos los datos de población y los lines provinciales:
geo_pob_andalucia <- left_join(pob_andalucia, Provincias, by = c("ine_prov" = "ine_prov"))
geo_pob_andalucia <- left_join(pob_andalucia, Provincias)
    1. finalmente añadimos las provincias coloreadas por población al mapa:
p + geom_sf(data = CCAA, fill = NA) +
    geom_sf(data = geo_pob_andalucia, aes(geometry = geometry, fill = n_pueblos)) +
    scale_fill_viridis_c(alpha = .4) +
    geom_sf(data = pueblos_sf, size = 2, color = "darkred") +
    coord_sf(xlim = c(-15.00, 25.00), ylim = c(21, 57.44), expand = TRUE)

  • vamos añadir también 3 municipios españoles seleccionados al azar:
pueblos_3 <- IGN_nomencla_muni %>% 
             sample_n(3) %>% 
             select(NOMBRE_ACTUAL, PROVINCIA, LONGITUD_ETRS89, LATITUD_ETRS89) %>%
             st_as_sf(coords = c("LONGITUD_ETRS89", "LATITUD_ETRS89"), crs = 4326, agr = "constant", remove = FALSE)
  • Bueno, en realidad no los voy a elegir tan tan al azar:
pueblos_3 <- IGN_nomencla_muni %>% 
             filter(NOMBRE_ACTUAL %in% c("Pancrudo", "Alburquerque", "Polentinos")) %>% 
             select(NOMBRE_ACTUAL, PROVINCIA, LONGITUD_ETRS89, LATITUD_ETRS89) %>%
             st_as_sf(coords = c("LONGITUD_ETRS89", "LATITUD_ETRS89"), crs = 4326, agr = "constant", remove = FALSE)
p_esp <- p + geom_sf(data = CCAA, fill = NA) +
         geom_sf(data = geo_pob_andalucia, aes(geometry = geometry, fill = n_pueblos)) +
         scale_fill_viridis_c(alpha = .4) +
         geom_sf(data = pueblos_3, size = 2, color = "darkred") +
         ggrepel::geom_label_repel(data = pueblos_3, aes(x = LONGITUD_ETRS89, y = LATITUD_ETRS89, label = NOMBRE_ACTUAL)) +
         coord_sf(xlim = c(-10.00, 7.00), ylim = c(35, 45), expand = TRUE)
p_esp

Con los mapas y gráficos nunca se acaba!!!

Canarias no se ve. ¿La situamos en el mapa? Usaremos sf::st_bbox()

canarias <- Provincias %>% filter(ine_prov %in% c(35,38))
peninsula <- Provincias %>% filter( !(ine_prov %in% c(35, 38)) )
my_shift <- st_bbox(peninsula)[c(1,2)]- (st_bbox(canarias)[c(1,2)]) + c(-2.4, -1.1)
canarias$geometry <- canarias$geometry + my_shift
st_crs(canarias)  <- st_crs(peninsula)
peninsula_y_canarias <- rbind(peninsula, canarias)

p1 <- ggplot() + geom_sf(data = peninsula_y_canarias)
p2 <- ggplot() + geom_sf(data = peninsula) + geom_sf(data = canarias, fill = "purple") 
#coord_sf(xlim = c(-12.00, 4.00), ylim = c(33, 44), expand = TRUE)

p1 + p2 

  • hagamos zoom
ggplot() + geom_sf(data = peninsula) +
           geom_sf(data = canarias) +
           coord_sf(xlim = c(-12.00, 4.00), ylim = c(33, 44), expand = TRUE)

  • pongamos Canarias, pintada en rojo, en nuestro mapa
p_esp + geom_sf(data = canarias, fill = "red") +
         coord_sf(xlim = c(-12.00, 5.00), ylim = c(30, 44), expand = TRUE)

el río Ebro

Pues que quiero poner el Río Ebro en el mapa. Logicamente necesio los datos geográficos del rio Ebro.

#rios <- rio::import("https://github.com/perezp44/archivos_download/blob/master/rio_ebro.rds?raw=true")
zz_rios <- rios_espanya %>% st_set_geometry(NULL)
names(rios_espanya)
#> [1] "Cod_Mar"    "PfafCuen"   "PfafRio"    "Cod_Uni"    "Nom_Rio"   
#> [6] "LngTramo_m" "Long_Rio_m" "color"      "geometry"
  • Voy a situar en el mapa 4 ríos:
#- no hace falta pero
cod_rios_guenos <- c(913757, 913687, 913685, 913619)
nombre_rios_guenos <- c("Pancrudo", "Jiloca", "Jalón", "Ebro")

rios_guenos <- rios_espanya %>% filter(Cod_Uni %in% cod_rios_guenos)
ggplot(data = peninsula) + geom_sf() +
    geom_sf(data = rios_guenos, color = "lightblue", lwd = 1.3) +
    labs(subtitle = "Mapa de España con 4 ríos Güenos", color = "")
# col = sf::sf.colors(2, alpha = 0.5)

  • ¿Cómo se llamará el río pequeñín?
ggplot(data = peninsula) + geom_sf() +
    geom_sf(data = rios_guenos, color = "lightblue", lwd = 1.3) +
    geom_point(data = pueblos, aes(x = longitude, y = latitude), size = 2, color = "darkred") +
    geom_text(data = pueblos, aes(x = longitude, y = latitude, label = nombre), color = "darkblue", fontface = "bold", check_overlap = TRUE, size = 2.5) + 
    labs(subtitle = "Mapa de España con 4 ríos Güenos", color = "")




4. Layouts

Con los mapas se pueden hacer composiciones para destacar el mensaje que se quiere mostrar. Se pueden hacer estas composiciones de varias maneras:

  • Using “grobs”, i.e. graphic objects from ggplot2, which can be inserted in the plot region using plot coordinates;

  • Usando el paquete patchwork o el paquete cowplot

names(world)
#>  [1] "iso_a3"       "name"         "sovereignt"   "continent"    "area"        
#>  [6] "pop_est"      "pop_est_dens" "economy"      "income_grp"   "gdp_cap_est" 
#> [11] "life_exp"     "well_being"   "footprint"    "inequality"   "HPI"         
#> [16] "geometry"
gworld <- ggplot(data = world) +
  geom_sf(aes(fill = income_grp)) +
  # geom_sf(aes(fill = region_wb)) +
  geom_rect(xmin = -15, xmax = 25.00, ymin = 21, 
            ymax = 57.44, fill = NA, colour = "black", size = 1.5) +
  theme(panel.background = element_rect(fill = "azure")) +
  labs(subtitle = "Mapa del mundo") +
  scale_fill_discrete(guide = "none")

gworld

g_europe <- ggplot(data = world) +
    geom_sf() +
    geom_sf(data = CCAA, fill = NA) +
    geom_sf(data = geo_pob_andalucia, aes(geometry = geometry, fill = n_pueblos)) +
    geom_sf(data = pueblos_3, size = 2, color = "darkred") +
    geom_label_repel(data = pueblos_3, aes(x = LONGITUD_ETRS89, y = LATITUD_ETRS89, label = NOMBRE_ACTUAL)) +
    coord_sf(xlim = c(-15.00, 25.00), ylim = c(21, 57.44), expand = TRUE) +
    theme(panel.background = element_rect(fill = "azure")) +
    labs(subtitle = "Mapa de Europa Occidental") +
    scale_fill_continuous(guide = "none")
g_europe

library(patchwork)
gworld + g_europe + plot_layout(widths = c(1, 2))




5. Paquete Mapview

Vamos a ver lo fácil que es hacer un mapa con el paquete mapview.

Por ejemplo, teníamos geolocalizados a Pancrudo y Valencia. Veámoslos en un gráfico con mapview()

pueblos <- data.frame(nombre = c("Pancrudo", "Valencia"), longitude = c(-1.028781, -0.3756572), latitude = c(40.76175, 39.47534)) #- ETRS89
pueblos_sf <- st_as_sf(pueblos, coords = c("longitude", "latitude"), crs = 4326, agr = "constant") #- EPSG:4326 Coordenadas Geográficas WGS84
mapview::mapview(pueblos_sf)

Otro ejemplo con las geometrías de las CC.AA, aunque es una afrenta para Pancrudo

mapview::mapview(rios)

Para ver que se puede hacer con mapview vamos a añadir alguna información a CCAA, por ejemplo el número de provincias, el número de municipios y la población total en el año 2017:

INE_padron_muni_96_20 <- pjpv.datos.01::pob_muni_1996_2020
#INE_padron_muni_96_20 <- pjpv2020.01::pjp_data_pob_mun_1996_2019
info_CCAAs <- INE_padron_muni_96_20 %>% 
              filter(year == 2017) %>% 
              #filter(poblacion == "Total") %>% 
              group_by(ine_ccaa.n) %>% 
              mutate(n_prov = n_distinct(ine_prov)) %>% 
              mutate(n_pueblos = n_distinct(ine_muni)) %>% 
              mutate(pob_CCAA = sum(pob_total, na.rm = TRUE)) %>% ungroup() %>%
              select(ine_ccaa, ine_ccaa.n, n_prov, n_pueblos, pob_CCAA) %>%
              distinct() %>% ungroup()

Juntamos el data.frame info_CCAAs con el dataframe con las geometrías `CCAA:

CCAA_con_info <- left_join(CCAA, info_CCAAs)

Y vamos a graficarlo ya con mapview:

mapview::mapview(CCAA_con_info, 
                 zcol = c("ine_ccaa.n", "n_prov", "n_pueblos", "pob_CCAA"),
                 color = "tomato", col.regions = "purple", lwd = 3,
                 cex = "n_pueblos")
mapview::mapview(CCAA_con_info, 
                 zcol = c("ine_ccaa.n", "n_prov", "n_pueblos", "pob_CCAA"),
                cex = "n_pueblos")

Se pueden poner varias capas:

mapview(list(CCAA_con_info, pueblos_sf), layer.name = c("CC.AA", "2 municipios chachis"))

O mejor así:

mapview::mapview(CCAA_con_info, zcol = c("ine_ccaa.n", "n_prov", "n_pueblos", "pob_CCAA")) +
mapview::mapview(pueblos_sf) 

Y muchas mas cosas, por ejemplo Map pop-ups o Image pop-ups

  • se puede usar el operador %>% con las funciones de sf
CCAA_con_info %>% st_union %>% mapview


Integración con el paquete mapedit

Integracion con mapedit

#install.packages("mapedit")
library(mapedit)
my_map <- mapview(pueblos_sf)
drawFeatures(my_map, editor = "leafpm")   #- good

Para recuperar el mapa es mejor así:

library(mapedit)
mys_dibujitos <- editMap(mapview(pueblos_sf))
mapview(mys_dibujitos$finished)




6. Paquete leaflet

El paquete leaflet permite utilizar en R una de la librerías de JavaScript más conocidas: Leaflet.

Para abrir un mapa leaflet:

library(leaflet)
leaflet() %>% addTiles() %>% leafem::addMouseCoordinates()

Podemos centrar el mapa, decidir el nivel de zoom y ponerle marcadores y popups:

m <- leaflet() %>%
  addTiles() %>% 
  setView(lng = -1.0287810, lat = 40.76175, zoom = 6) %>% 
  addMarkers(lng = -1.0287810, lat = 40.76175, popup = "Pancrudo") %>% 
  addPopups(lng = -0.3756572, lat = 39.47534, popup = "Valencia")
m

Vamos a añadirle elementos, por ejemplo puntos, por ejemplo mis 2 municipios:

leaflet() %>% 
  addTiles() %>% # Add default OpenStreetMap map tiles
  addCircleMarkers(data = pueblos_sf)
  #leafem::addHomeButton(ext = extent(breweries91), layer.name = "pueblos")

O, por ejemplo polígonos como las CC.AA:

leaflet(data = CCAA) %>% addTiles() %>%
  addPolygons(fillColor = topo.colors(18, alpha = NULL), stroke = FALSE)

Si te ha gustado tu mapa leaflet y quieres guardarlo, puedes guardarlo como un archivo html así:

htmltools::save_html(lplot, "leaflet.html")

Se pueden hacer bastantes más cosas, por ejemplo, para hacer coropletas con leaflet puedes ir aquí. Otro ejemplo de análisis con leaflet aquí.




7. Operaciones geométricas

Las operaciones geométricas que podemos hacer con sf con datos espaciales vectoriales se pueden dividir en tres grupos:

    1. funciones que devuelven un valor que resume alguna propiedad de la geometría:

      • st_area() : calcula el area
      • st_lenght() : calcula el perímetro
      • st_distance(): calcula la distancia (entre dos geometrías)
    1. funciones que chequean si se cumple una condición:

      • En 1 geometría: st_is_valid() : chequea si la geometria es válida

      • En 2 geometrías: por ejemplo, st_intersects(A, B) chequea si dos geometrías se intersectan. Otras funciones: st_disjoint(), st_touches(), st_crosses(), st_within(), st_contains(), st_overlaps(), st_covers(), st_equals(), st_is_within_distance(), y alguna más

    1. funciones que crean una nueva geometría:

      • En 1 geométria: por ejemplo, st_centroid(): calcula el centroide. Otras funciones: st_buffer(), st_boundary() , st_convex_hull(), st_voronoi()

      • En 2 geométrias: por ejemplo, st_intersection() calcula la interseccion. Otras funciones: st_combine(), st_union(), st_difference()


7.1 Operaciones geométricas (Ejemplos)

Las 5 provincias más grandes

Vamos a colorear las 5 provincias españolas con más territorio, para ello tenemos que calcular el área con sf::st_area():

Provicias_5 <- Provincias %>% 
               mutate(area = st_area(.)) %>% 
               mutate(areakm2 = units::set_units(area, km^2)) %>% 
               arrange(desc(areakm2)) %>% 
               slice(1:5)
ggplot() + geom_sf(data = Provincias) + geom_sf(data = Provicias_5, fill = "purple")

Bien, pero mejor si ponemos el nombre de las provincias

Prov_5_centroides <- st_centroid(Provicias_5) 
Prov_5_centroides <-  cbind(Prov_5_centroides, st_coordinates(st_centroid(Prov_5_centroides$geometry)))

ggplot() + geom_sf(data = Provincias) + 
           geom_sf(data = Provicias_5, fill = "purple") +
           geom_text(data = Prov_5_centroides, 
                     aes(x = X, y = Y, label = ine_prov.n),
                     color = "darkblue", fontface = "bold", check_overlap = TRUE, size = 2.5) +
          labs(title = "Las 5 provincias españolas más extensas", x = "", y = "") 

Distancias (a mi pueblo)

Calculemos las distancias entre puntos, por ejemplo entre los centroides de las 52 provincias españolas:

Prov_centroides <- st_centroid(Provincias) 
Prov_centroides_XY <- cbind(Prov_centroides, st_coordinates(st_centroid(Prov_centroides$geometry)))
distancias <- st_distance(Prov_centroides_XY, by_element = FALSE) #- devuelve una matriz

Quiero ver que municipio está mas lejos de mi pueblo. Cargo datos de los municipios con latitud-longitud en ETRS89:

municipios <- IGN_nomencla_muni
municipios <- municipios %>% select(COD_INE, PROVINCIA, NOMBRE_ACTUAL, LONGITUD_ETRS89, LATITUD_ETRS89)

Tengo que convertir el df a un spatial-df, y tengo que tener cuidado con el CRS que le pongo. Aquí vi que ETRS89 es equivalente a EPSG Projection 4258, así que, espero haberlo hecho bien, ya vorem:

municipios_sf <- st_as_sf(municipios, coords = c("LONGITUD_ETRS89", "LATITUD_ETRS89"), crs = st_crs(4258))

Vamos a ver que fila ocupa mi pueblo. Ocupa la fila nº 6694, así que voy a crear un df sólo con mi pueblo y luego calcular las distancias con fs::st_distance():

pancrudo_sf <- municipios_sf %>% slice(6694)
g1 <- st_geometry(municipios_sf)
g1_pancrudo <- st_geometry(pancrudo_sf) #- dejo solo la geometria

#- distancias_a_Pancrudo <- mapply(st_distance, g1, g1_pancrudo) %>% as.vector) #- con R-base
distancias_a_Pancrudo <- map2(g1, g1_pancrudo, st_distance) %>% as_vector()         #- con purrr
distancias_a_Pancrudo <- as.data.frame(distancias_a_Pancrudo)
df_con_distancias_a_Pancrudo <- bind_cols(municipios_sf, distancias_a_Pancrudo) %>% 
                                mutate(Kms = distancias_a_Pancrudo * 100) %>% 
                                arrange(Kms)

El pueblo más cercano a Pancrudo es Pozuel del Campo que está a 3.3507456, eso si, en linea recta. Por la carretera hay un poco más, según Google Maps 7,1 kilómetros. A ver si podemos calcularlo luego con R usando OSM.

El pueblo más lejano a Pancrudo, si consideramos sólo la península es Muxía en la provincia de A Coruña, que está a 851,65 kilómetros (en linea recta). En coche, según Google Maps, 924 Kms.


Municipios por los que pasa el río Ebro

Pues eso, que me apetece saber por donde pasa el Ebro y el Pancrudo.

  1. Primero cargo geometrías de los municipios españoles. Las lindes municipales provienen del paquete LAU2boundaries4spain
muni_xx <- municipios_2020 %>% st_set_geometry(NULL)
muni    <- municipios_2020

Vamos a quedarnos solo con la España peninsular:

#zz <- muni_xx %>% group_by(ine_ccaa, ine_ccaa.n) %>% count()
muni_borrar <- c("Illes Balears", "Canarias")
muni <- muni %>% filter(!(ine_ccaa.n %in% muni_borrar))
  1. Ahora los datos de ríos Pancrudo, Jiloca, Jalón y Ebro
rio_ebro_4 <- read_rds(here::here("datos",  "mapas", "Rios", "rio_ebro.rds"))

Vamos a poner todos los tramos de los 4 ríos juntos. Con summarise()

rio_ebro_4_xx <- rio_ebro_4 %>% st_set_geometry(NULL)
rio_ebro_4_x <- rio_ebro_4 %>% select(Nom_Rio, Long_Rio_m, geometry) %>% group_by(Nom_Rio) %>% summarize()

Hagamos ya un mapa de los lindes municipales, los ríos y voy a situar Pancrudo y Valencia:

theme_set(cowplot::theme_map())
p <- ggplot(muni) + 
     geom_sf(color = "grey", fill = "white", lwd = 0.01) +
     geom_sf(data = rio_ebro_4_x, color = "lightblue", lwd = 1.6) +
     geom_point(data = pueblos, aes(x = longitude, y = latitude), size = 3, color = "darkred") +
     geom_text(data  = pueblos, aes(x = longitude, y = latitude, label = nombre), color = "darkred",
                    fontface = "bold", check_overlap = TRUE, size = 2.5) +
     labs(title = "R-Mapa con 4 ríos güenos",
          caption = "Datos provenientes del paquete LAU2boundaries4spain") +  
          theme(panel.background = element_rect(fill = "aliceblue"))
p

Venga, calculamos las intersecciones con sf::st_intersects() pero antes tenemos que poner los 2 dfs en el mismo CRS:

muni_a <- muni %>% st_transform(crs = st_crs(rio_ebro_4_x))
zz_a <- st_intersects(muni_a, rio_ebro_4_x, sparse = FALSE, prepared = TRUE)

Ahora ya si seleccionamos aquellos municipios por los que pasa el Ebro. Lo hago con R-base:

muni_ebro <- muni_a[zz_a,]

Parece que hay 190 municipios por loq eu pasa el Ebro. A ver si nos sale el mapa

p + geom_sf(data = muni_ebro) +
    geom_sf(data = rio_ebro_4_x, color = "lightblue", lwd = 0.25)

No, no voy a obtener los municipios por los que pasa el rio Pancrudo. Eso está MAL!!!!




Biblio




  1. Most geocoded data created through Google Maps or other geocoding sites provides coordinates in longitude and latitude values using the WGS84 ellipsoid and thus contains a CRS equivalent to the PROJ argument +proj=longlat +datum=WGS84 or EPSG 4326. However, this information about the CRS is implicit and not contained anywhere within the data frame itself↩︎

  2. Sí, todo se llama sf!!! Tanto el paquete como el estándar que incorpora como la estructura de datos se llaman todos sf.↩︎

  3. Parece que con PROJ7 es mejor no usarlo↩︎

LS0tCnRpdGxlOiAiSW50cm9kdWNjacOzbiBhIEdJUyBjb24gUiIKYXV0aG9yOiAiUGVkcm8gSi4gUMOpcmV6IChwZWRyby5qLnBlcmV6QHV2LmVzKS4gVW5pdmVyc2l0YXQgZGUgVmFsw6huY2lhIDxicj4gPGJyPiBXZWIgZGVsIGN1cnNvOiA8aHR0cHM6Ly9wZXJlenA0NC5naXRodWIuaW8vaW50cm8tZHMtMjItMjMtd2ViLz4iCmRhdGU6ICJPY3R1YnJlIGRlIDIwMTkgKGFjdHVhbGl6YWRvIGVsIGByIGZvcm1hdChTeXMudGltZSgpLCAnJWQtJW0tJVknKWApIgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIGNzczogIWV4cHIgaGVyZTo6aGVyZSgiYXNzZXRzIiwgInN0eWxlc19wanAuY3NzIikKICAgIHRoZW1lOiBwYXBlcgogICAgaGlnaGxpZ2h0OiB0ZXh0bWF0ZQogICAgdG9jOiB0cnVlCiAgICB0b2NfZGVwdGg6IDMKICAgIHRvY19mbG9hdDoKICAgICAgY29sbGFwc2VkOiB0cnVlCiAgICAgIHNtb290aF9zY3JvbGw6IHRydWUKICAgIHNlbGZfY29udGFpbmVkOiB0cnVlCiAgICBudW1iZXJfc2VjdGlvbnM6IGZhbHNlCiAgICBpbmNsdWRlczoKICAgICAgYWZ0ZXJfYm9keTogIWV4cHIgaGVyZTo6aGVyZSgiYXNzZXRzIiwgImZvb3Rlci5odG1sIikgCiAgICAgIGluX2hlYWRlcjogCiAgICAgICAgLSAhZXhwciBoZXJlOjpoZXJlKCJhc3NldHMiLCAiZ29vZ2xlLWFuYWx5dGljcy5odG1sIikgCiAgICAgICAgLSAhZXhwciBoZXJlOjpoZXJlKCJhc3NldHMiLCAiZmF2aWNvbi1zb2wuaHRtbCIpCiAgICBkZl9wcmludDoga2FibGUKICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKZWRpdG9yX29wdGlvbnM6CiAgY2h1bmtfb3V0cHV0X3R5cGU6IGNvbnNvbGUKLS0tCgpgYGB7ciwgaW5jbHVkZSA9IEZBTFNFfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKYGBgCgpgYGB7ciBjaHVuay1zZXR1cCwgaW5jbHVkZSA9IEZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIGV2YWwgPSBUUlVFLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRSwKICAgICAgICAgICAgICAgICAgICAgICNyZXN1bHRzID0gImhvbGQiLAogICAgICAgICAgICAgICAgICAgICAgY2FjaGUgPSBGQUxTRSwgY2FjaGUucGF0aCA9ICIvY2FjaGVzLyIsIGNvbW1lbnQgPSAiIz4iLAogICAgICAgICAgICAgICAgICAgICAgI2ZpZy53aWR0aCA9IDcsICNmaWcuaGVpZ2h0PSA3LAogICAgICAgICAgICAgICAgICAgICAgI291dC53aWR0aCA9IDcsIG91dC5oZWlnaHQgPSA3LAogICAgICAgICAgICAgICAgICAgICAgY29sbGFwc2UgPSBUUlVFLCAgZmlnLnNob3cgPSAiaG9sZCIsCiAgICAgICAgICAgICAgICAgICAgICBmaWcuYXNwID0gMC42MjgsIG91dC53aWR0aCA9ICI3NSUiLCBmaWcuYWxpZ24gPSAiY2VudGVyIikKCiMtIHBhcmEgbWVqb3JhciBsb3MgZ3LDoWZpY29zLCBidWVubyBlbiByZWFsaWRhZCBwYXJhIHF1ZSBzZSB2ZWFuIGlndWFsIGVuIGRpc3RpbnRvcyBTTwojLSBodHRwczovL3d3dy5qdW1waW5ncml2ZXJzLmNvbS9ibG9nL3Ita25pdHItbWFya2Rvd24tcG5nLXBkZi1ncmFwaGljcy8Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGRldiA9ICJwbmciLCBkZXYuYXJncyA9IGxpc3QodHlwZSA9ICJjYWlyby1wbmciKSkKYGBgCgpgYGB7ciBvcHRpb25zLXNldHVwLCBpbmNsdWRlID0gRkFMU0V9Cm9wdGlvbnMoc2NpcGVuID0gOTk5KSAjLSBwYXJhIHF1aXRhciBsYSBub3RhY2nDs24gY2llbnTDrWZpY2EKb3B0aW9ucygieWFtbC5ldmFsLmV4cHIiID0gVFJVRSkgIy0gaHR0cHM6Ly9naXRodWIuY29tL3Zpa2luZy9yLXlhbWwvaXNzdWVzLzQ3ICAobG8gcHVzZSB4IGVsIHBiIGNvbiBlbCB3YXJuaW5nKSBFbiByZWFsaWRhZCBjcmVvIHF1ZSBtZWpvciBzZXLDrWEgcG9uZXJsbyBlbiBSUHJvZmlsZQpgYGAKCgpgYGB7ciBrbGlwcHksIGVjaG8gPSBGQUxTRX0Ka2xpcHB5OjprbGlwcHkocG9zaXRpb24gPSBjKCJ0b3AiLCAicmlnaHQiKSkgIy0gcmVtb3Rlczo6aW5zdGFsbF9naXRodWIoInJsZXN1ci9rbGlwcHkiKQpgYGAKCmBgYHtyIHNldHVwLCBlY2hvID0gRkFMU0V9CmxpYnJhcnkoa25pdHIpCmxpYnJhcnkoaGVyZSkKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkocGF0Y2h3b3JrKQpsaWJyYXJ5KGdncmVwZWwpCmxpYnJhcnkoc2YpCmxpYnJhcnkocm5hdHVyYWxlYXJ0aCkKbGlicmFyeShybmF0dXJhbGVhcnRoZGF0YSkKbGlicmFyeShnZ3NwYXRpYWwpCiNsaWJyYXJ5KHNwYW5pc2hScG9ibGFjaW9uKSAgICAgIy0gcmVtb3Rlczo6aW5zdGFsbF9naXRodWIoInBlcmV6cDQ0L3NwYW5pc2hScG9ibGFjaW9uIikKI2xpYnJhcnkoc3BhbmlzaFJlbnRpZGFkZXNJR04pICAgIy0gZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKCJwZXJlenA0NC9zcGFuaXNoUmVudGlkYWRlc0lHTiIpCmxpYnJhcnkobWFwdmlldykKbGlicmFyeShsZWFmZW0pCmxpYnJhcnkobGVhZmxldCkKYGBgCgotLS0tLS0tLS0tLS0tCgo8YnI+CgoKYGBge3IgcGFyYV9jYXNhXzIwMjEsIGV2YWwgPSBGQUxTRSwgZWNobyA9IEZBTFNFfQojLSBBUVVJIGNyZW8gZWwgZmljaGVybyBjb24gUkRhdGEgY29uIGxvcyBkYXRvcyBxdWUgbHVlZ28gdXNvCiMgQ0NBQSA8LSByaW86OmltcG9ydCgiL2hvbWUvcGpwdi9Fc2NyaXRvcmlvL215X1JSXzIwMjEvbXlfZGF0b3NfMjAyMS9kYXRvcy9nZW9fZGF0b3NfbWlvcy9nZW9fY2NhYV8yMDIwX0xBVTIucmRzIikKIyBQcm92aW5jaWFzIDwtIHJpbzo6aW1wb3J0KCIvaG9tZS9wanB2L0VzY3JpdG9yaW8vbXlfUlJfMjAyMS9teV9kYXRvc18yMDIxL2RhdG9zL2dlb19kYXRvc19taW9zL2dlb19wcm92XzIwMjBfTEFVMi5yZHMiKQojIG11bmljaXBpb3NfMjAyMCA8LSByaW86OmltcG9ydCgiL2hvbWUvcGpwdi9Fc2NyaXRvcmlvL215X1JSXzIwMjEvbXlfZGF0b3NfMjAyMS9kYXRvcy9nZW9fZGF0b3NfbWlvcy9nZW9fbXVuaV8yMDIwX0xBVTIucmRzIikKIyBJR05fbm9tZW5jbGFfbXVuaSA8LSByaW86OmltcG9ydCgiL2hvbWUvcGpwdi9Fc2NyaXRvcmlvL215X1JSXzIwMjEvbXlfZGF0b3NfMjAyMS9kYXRvcy9jb2RpZ29zL0lHTl9tdW5pY2lwaW9zLnhsc3giKQojIHJpb3NfZXNwYW55YSA8LSByaW86OmltcG9ydCgiL2hvbWUvcGpwdi9Fc2NyaXRvcmlvL215X1JSXzIwMjEvbXlfZGF0b3NfMjAyMS9kYXRvcy9nZW9fZGF0b3NfbWlvcy9yaW9zX2VzcGFueWEucmRzIikKIyByaW9fZWJyb180IDwtIHJpbzo6aW1wb3J0KCIvaG9tZS9wanB2L0VzY3JpdG9yaW8vbXlfUlJfMjAyMS9teV9kYXRvc18yMDIxL2RhdG9zL2dlb19kYXRvc19taW9zL3Jpb19lYnJvXzQucmRzIikKCiMtIHVzYWJhIGVsIGRlIE5hdHVyYWwgSGVhcnQgcGVybyBkYSBwYnMsIGFzaSBxIHVzbyBlbCBkZSB0bWFwCiN3b3JsZCA8LSBybmF0dXJhbGVhcnRoOjpuZV9jb3VudHJpZXMoc2NhbGUgPSAibWVkaXVtIiwgcmV0dXJuY2xhc3MgPSAic2YiKQpsaWJyYXJ5KHRtYXApIApkYXRhKFdvcmxkKSAjLSBoYWNlbW9zIGFjY2VzaWJsZXMgMSBkZgp3b3JsZCA8LSBXb3JsZApybShXb3JsZCkKIy0gYXF1w60gbG9zIGd1YXJkbyBwYXJhIGx1ZWdvIGltcG9ydGFybG9zIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KI3NhdmUoQ0NBQSwgUHJvdmluY2lhcywgbXVuaWNpcGlvc18yMDIwLCB3b3JsZCwgSUdOX25vbWVuY2xhX211bmksIHJpb3NfZXNwYW55YSwgcmlvX2Vicm9fNCwgZmlsZSA9ICIuL2RhdG9zL21hcGFzL2dlb21ldHJpYXNfQmlnRGF0YV8yMDIxLlJEYXRhIikKCgojLSBPTEQgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KI0NDQUEgPC0gcmlvOjppbXBvcnQoImh0dHBzOi8vZ2l0aHViLmNvbS9wZXJlenA0NC9MQVUyYm91bmRhcmllczRzcGFpbi9ibG9iL21hc3Rlci9kYXRhL0NDQUEucmRhP3Jhdz10cnVlIikKI1Byb3ZpbmNpYXMgPC0gcmlvOjppbXBvcnQoImh0dHBzOi8vZ2l0aHViLmNvbS9wZXJlenA0NC9MQVUyYm91bmRhcmllczRzcGFpbi9ibG9iL21hc3Rlci9kYXRhL1Byb3ZpbmNpYXMucmRhP3Jhdz10cnVlIikKI211bmljaXBpb3NfMjAxNyA8LSByaW86OmltcG9ydCgiaHR0cHM6Ly9naXRodWIuY29tL3BlcmV6cDQ0L0xBVTJib3VuZGFyaWVzNHNwYWluL2Jsb2IvbWFzdGVyL2RhdGEvbXVuaWNpcGlvc18yMDE3LnJkYT9yYXc9dHJ1ZSIpCiMgd29ybGQgPC0gcm5hdHVyYWxlYXJ0aDo6bmVfY291bnRyaWVzKHNjYWxlID0gIm1lZGl1bSIsIHJldHVybmNsYXNzID0gInNmIikKIyByaW9zIDwtIHJpbzo6aW1wb3J0KCJodHRwczovL2dpdGh1Yi5jb20vcGVyZXpwNDQvYXJjaGl2b3NfZG93bmxvYWQvYmxvYi9tYXN0ZXIvcmlvX2Vicm8ucmRzP3Jhdz10cnVlIikKIyBJR05fbm9tZW5jbGFfbXVuaSA8LSByaW86OmltcG9ydCgiaHR0cHM6Ly9naXRodWIuY29tL3BlcmV6cDQ0L2FyY2hpdm9zX2Rvd25sb2FkL2Jsb2IvbWFzdGVyL0lHTl9ub21lbmNsYV9tdW5pLnJkcz9yYXc9dHJ1ZSIpCmBgYAoKCgoKYGBge3IgcGFyYV9jYXJnYXJfZGF0b3NfMjAyMSwgZXZhbCA9IFRSVUUsIGVjaG8gPSBGQUxTRX0KI3NhdmUoQ0NBQSwgUHJvdmluY2lhcywgbXVuaWNpcGlvc18yMDIwLCB3b3JsZCwgSUdOX25vbWVuY2xhX211bmksIHJpb3NfZXNwYW55YSwgcmlvX2Vicm9fNCwgZmlsZSA9ICIuL2RhdG9zL21hcGFzL2dlb21ldHJpYXNfQmlnRGF0YV8yMDIxLlJEYXRhIikKbG9hZChoZXJlOjpoZXJlKCJkYXRvcyIsICJtYXBhcyIsICJnZW9tZXRyaWFzX0JpZ0RhdGFfMjAyMS5SRGF0YSIpKQojLSBPTEQgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojLSBsb3MgZ3VhcmTDqSBlbiAiLi9kYXRvcy9nZW9tZXRyaWFzX2NsYXNlXzEwLlJEYXRhIgojc2F2ZShDQ0FBLCBQcm92aW5jaWFzLCBtdW5pY2lwaW9zXzIwMTcsIHdvcmxkLCByaW9zLCBJR05fbm9tZW5jbGFfbXVuaSwgZmlsZSA9ICIuL2RhdG9zL21hcGFzL2dlb21ldHJpYXNfY2xhc2VfMTAuUkRhdGEiKQojLSBjYXJnbyBsb3MgZGF0b3MgcHJldmlhbWVudGUgZ3VhcmRhZG9zCiMtIGxvYWQoaGVyZTo6aGVyZSgiZGF0b3MiLCAibWFwYXMiLCAiZ2VvbWV0cmlhc19jbGFzZV8xMC5SRGF0YSIpKQpgYGAKCgojIDEuIEludHJvZHVjY2nDs24KClZhbW9zIGEgdXRpbGl6YXIgUiBjb21vIHVuYSBhcGxpY2FjacOzbiBHSVMgKFNpc3RlbWFzIGRlIGluZm9ybWFjacOzbiBnZW9ncsOhZmljYSk7IGVzIGRlY2lyLCBsbyB2YW1vcyBhIHV0aWxpemFyIHBhcmEgYWxtYWNlbmFyLCBtYW5pcHVsYXIsIGFuYWxpemFyIHkgcHJlc2VudGFyIGRhdG9zIGdlb2dyw6FmaWNvcyBvIGdlb2VzcGFjaWFsZXMuIEdJUyBlcyB1biDDoXJlYSBlbm9ybWUsIHBvciBlamVtcGxvLCBbYXF1w61dKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi92aWV3cy9TcGF0aWFsLmh0bWwpIHRpZW5lcyB1biBsaXN0YWRvIGRlIGhlcnJhbWllbnRhcyB5IHBhcXVldGVzIHBhcmEgdHJhYmFqYXIgY29uIGRhdG9zIGVzcGFjaWFsZXMgZW4gUi4gSGF5IG11Y2jDrXNpbWFzISEhLCBlcyB1biDDoXJlYSBkZSBjb25vY2ltaWVudG8gZW4gc8OtIG1pc21hLCBhc8OtIHF1ZSBzw7NsbyB2b3kgYSBpbnRlbnRhciBoYWNlciB1bmEgcGVxdWXDsWEgaW50cm9kdWNjacOzbiBhIEdJUyBjb24gUi4KCkR1cmFudGUgZXN0ZSB0dXRvcmlhbCB1c2FyZW1vcyBkaXN0aW50b3MgZGF0YS5mcmFtZXMgY29uIGdlb21ldHLDrWFzIGRlOgoKICAtIHBhw61zZXMgZGVsIG11bmRvICh3b3JsZCkgIAogIC0gQ0MuQUEgZXNwYcOxb2xhcyAoQ0NBQSkgIAogIC0gcHJvdmluY2lhcyBlc3Bhw7FvbGFzIChQcm92aW5jaWFzKSAgCiAgLSBtdW5pY2lwaW9zIGVzcGHDsW9sZXMgZW4gMjAyMCAobXVuaWNpcGlvc18yMDIwKSAgIAogIC0gcsOtb3MgZXNwYcOxb2xlcyAocmlvc19lc3BhbnlhKSAgIAogIC0gNCByw61vcyByZWxhY2lvbmFkb3MgY29uIGVsIHLDrW8gRWJybyAocmlvX2Vicm9fNCkgIAogIC0gbG9jYWxpemFjacOzbiAobG9uZ2l0dWQsIGxhdGl0dWQpIGRlIGxvcyBtdW5pY2lwaW9zIGVzcGHDsW9sZXMgKElHTl9ub21lbmNsYV9tdW5pKSAgCgoKClZlbmdhLCB2YW1vcyBhIGhhY2VyIG1hcGFzIGNvbiBlbGxvcyEhISBTw60sIGVuc2VndWlkYSEhIEEgdG9kb3Mgbm9zIGd1c3RhbiBsb3MgbWFwYXMgeSBoYWNlciBtYXBhcyBjb24gUiBlcyAqZWFzeSogLi4uIGJ1ZW5vLCBubyB0YW4gZsOhY2lsLCBwZXJvIHZhbW9zIGEgZWxsbyEhCgoKTcOhcyBxdWUgbWFwYXMgbG8gcXVlIHZhbW9zIGEgaGFjZXIgZXMgdmlzdWFsaXphciAqKmRhdG9zIGdlb2VzcGFjaWFsZXMqKi4gVW5vcyBkYXRvcyBubyBzZSBwdWVkZW4gY29uc2lkZXJhciBnZW9lc3BhY2lhbGVzIHNpIG5vIHZpZW5lbiBhY29tcGHDsWFkb3MgZGUgdW4gKipDUlMqKiAoQ29vcmRpbmF0ZSBSZWZlcmVuY2UgU3lzdGVtKSBkZSBmb3JtYSBxdWUgbGFzIGFwbGljYWNpb25lcyBHSVMgcHVlZGFuIG1hbmlwdWxhciB5IG1vc3RyYXIgY29ycmVjdGFtZW50ZSBlc29zIGRhdG9zLiBMb3MgQ1JTIHV0aWxpemFuIHVuIG1vZGVsbyBtYXRlbcOhdGljbyBwYXJhIGNvbmVjdGFyIGxvcyBkYXRvcywgbGFzIGNvb3JkZW5hZGFzIGRlIHVuIG9iamV0bywgY29uIHN1IHBvc2ljacOzbiBmw61zaWNhIGVuIGxhIHN1cGVyZmljaWUgdGVycmVzdHJlLiBVbiBzaXN0ZW1hIGRlIHJlZmVyZW5jaWEgZGUgY29vcmRlbmFkYXMgZ2VvZ3LDoWZpY2FzIHBlcm1pdGUgcXVlIHRvZG9zIGxvcyBwdW50b3MgZGUgbGEgdGllcnJhIHB1ZWRhbiBzZXIgZGVzY3JpdG9zIGNvbW8gdW4gY29uanVudG9zIGRlIGNvb3JkZW5hZGFzIChjb21vIGxhdGl0dWQsIGxvbmdpdHVkIHkgYWx0aXR1ZCkuIERpdmVyc29zIHNpc3RlbWFzIHNlIHV0aWxpemFuIHBhcmEgcmVwcmVzZW50YXIgbGEgdGllcnJhIHRyaWRpbWVuc2lvbmFsIGVuIHVuIHBsYW5vIG8gbWFwYSBiaWRpbWVuc2lvbmFsLgoKCkxvcyBkYXRvcyBnZW9lc3BhY2lhbGVzIHJlcHJlc2VudGFuIGx1Z2FyZXMgZ2VvZ3LDoWZpY29zIGRlIGxhIFRpZXJyYSB5IHBhcmEgcG9kZXIgcmVwcmVzZW50YXIgbGEgcG9zaWNpw7NuIGRlIHVuIG9iamV0byBlbiBsYSBUaWVycmEgbmVjZXNpdGFtb3MgZG9zIGNvc2FzOgoKLSBsYXMgY29vcmRlbmFkYXMgZGVsIG9iamV0byAKCi0gdW4gc2lzdGVtYSBkZSByZWZlcmVuY2lhIChDUlMpIHBhcmEgY29uZWN0YXIgZXNvcyBkYXRvcyBjb24gbGEgc3VwZXJmaWNpZSB0ZXJyZXN0cmUuCgpVbiBDUlMgY29uc2lzdGUgZW4gbW9kZWxvIGdlb23DqXRyaWNvIGVsaXBzb2lkZSBkZSBsYSBzdXBlcmZpY2llIHRlcnJlc3RyZSB5IHVuICpkYXR1bSogcXVlIGlkZW50aWZpY2EgZWwgb3JpZ2VuIHkgb3JpZW50YWNpw7NuIGRlIGxvcyBlamVzIHkgdGFtYmnDqW4gbGFzIHVuaWRhZGVzIGRlIG1lZGlkYS4KCkNvbW8gc2Fiw6lpcyBsYSBUaWVycmEgbm8gZXMgZXNmw6lyaWNhIHkgZXN0byBjb21wbGljYSBsYSB1dGlsaXphY2nDs24gZGUgdW4gw7puaWNvIENSUywgZGUgZm9ybWEgcXVlIHNlIGhhbiBjcmVhZG8gdW5vcyBjdWFudG9zIHBhcmEgdHJhdGFyIGRlIHJlcHJlc2VudGFyIGxhIHN1cGVyZmljaWUgdGVycmVzdHJlIGRlIGZvcm1hIGFkZWN1YWRhLgoKQWRlbcOhcywgaGF5IGRvcyB0aXBvcyBkZSBDUlM6ICoqQ1JTIGdlb2dyw6FmaWNvcyB5IHByb3llY3RhZG9zKiouIEVuIGxvcyBzaXN0ZW1hIGdlb2dyw6FmaWNvcywgZWwgbWFyY28gZGUgcmVmZXJlbmNpYSBlcyBlbCBnbG9ibyB0ZXJyw6FxdWVvIHkgY3VhbnRvcyBncmFkb3Mgbm9ydGUgbyBlc3RlLCBlc3TDoSBlbCBvYmpldG87IG1pZW50cmFzIHF1ZSBlbiBsb3Mgc2lzdGVtYXMgcHJveWVjdGFkb3MsIGVsIG1hcmNvIGRlIHJlZmVyZW5jaWEgZXMgdW5hIHJlcHJlc2VudGFjacOzbiBwbGFuYSBkZSBsYSBUaWVycmEgKG8gZGUgcGFydGUgZGUgZWxsYSkuIFNpIHRlIGludGVyZXNhIGVsIHRlbWEsIFthcXXDrV0oaHR0cHM6Ly9nZW9jb21wci5naXRodWIuaW8vcG9zdC8yMDE5L2Nycy1wcm9qZWN0aW9ucy10cmFuc2Zvcm1hdGlvbnMvKSB5ICBbYXF1w61dKGh0dHBzOi8vcGF1bGJsZ2lzLndvcmRwcmVzcy5jb20vMjAxNi8xMi8wOS9zaXN0ZW1hcy1kZS1jb29yZGVuYWRhcy1wcm95ZWN0YWRhcy15LWdlb2dyYWZpY2FzLWN1YWwtZXMtbGEtZGlmZXJlbmNpYS8pIHRpZW5lcyBkb3MgcG9zdHMgZGl2dWxnYXRpdm9zLgoKCgojIyBQcm95ZWNjaW9uZXMKCkNvbW8gbGEgVGllcnJhIGVzIGVzZsOpcmljYSwgcGFyYSBoYWNlciB1biBtYXBhIGJpZGltZW5zaW9uYWwgKG8gcGxhbm8pIGRlIGVsbGEsIHNlIHRpZW5lIHF1ZSB1c2FyIGFsZ8O6biB0aXBvIGRlIHByb3llY2Npw7NuLCBwb3IgbG8gcXVlIGxhcyDDoXJlYXMgc2UgbW9zdHJhcsOhbiBtw6FzIG8gbWVub3MgZGlzdG9yc2lvbmFkYXMuIFBvciBlamVtcGxvOgoKCgpgYGB7cn0KcDEgPC0gZ2dwbG90KENDQUEsICBhZXMoZmlsbCA9IGluZV9jY2FhKSApICsgCiAgICAgICAgICBnZW9tX3NmKCkgKwogICAgICAgICAgc2NhbGVfZmlsbF92aXJpZGlzX2QoZ3VpZGUgPSAibm9uZSIpICsKICAgICAgICAgIGxhYnModGl0bGUgPSAiQ29vcmQuIGdlb2dyw6FmaWNhcyIpCgpwMiA8LSBnZ3Bsb3QoQ0NBQSwgIGFlcyhmaWxsID0gaW5lX2NjYWEpICkgKyAKICAgICAgICAgICBnZW9tX3NmKCkgKyBjb29yZF9zZihjcnMgPSAyMTU0LCBkYXR1bSA9IHNmOjpzdF9jcnMoMjE1NCkpICsKICAgICAgICAgICBzY2FsZV9maWxsX3ZpcmlkaXNfZChndWlkZSA9ICJub25lIikgKwogICAgICAgICAgIGxhYnModGl0bGUgPSAiTGFtYmVydCA5MyIpCgpwMSArIHAyCmBgYAoKCgoKIyMgRGF0b3MgZXNwYWNpYWxlcwoKUGFyYSBwb2RlciByZXByZXNlbnRhciBhZGVjdWFkYW1lbnRlIGRhdG9zIGVzcGFjaWFsZXMsIGVzdG9zIGRlYmVuIGNvbnRlbmVyIGxhIGluZm9ybWFjacOzbiBkZSBzdSBDUlMgeSBhbGd1bmFzIHZlY2VzIGhheSBxdWUgY29udmVydGlyIGVudHJlIENSUydzOyBkZSBmb3JtYSBxdWUgbGEgZXN0cnVjdHVyYSB0cmFkaWNpb25hbCBkZSBkYXRvcyBlbiBSIChsb3MgZGF0YS5mcmFtZXMpIG5vIGVzIGFkZWN1YWRhIHBhcmEgYWxtYWNlbmFyIGRhdG9zIGVzcGFjaWFsZXNeW01vc3QgZ2VvY29kZWQgZGF0YSBjcmVhdGVkIHRocm91Z2ggR29vZ2xlIE1hcHMgb3Igb3RoZXIgZ2VvY29kaW5nIHNpdGVzICBwcm92aWRlcyBjb29yZGluYXRlcyBpbiBsb25naXR1ZGUgYW5kIGxhdGl0dWRlIHZhbHVlcyB1c2luZyB0aGUgV0dTODQgZWxsaXBzb2lkIGFuZCB0aHVzIGNvbnRhaW5zIGEgQ1JTIGVxdWl2YWxlbnQgdG8gdGhlIFBST0ogYXJndW1lbnQgK3Byb2o9bG9uZ2xhdCArZGF0dW09V0dTODQgb3IgRVBTRyA0MzI2LiBIb3dldmVyLCB0aGlzIGluZm9ybWF0aW9uIGFib3V0IHRoZSBDUlMgaXMgaW1wbGljaXQgYW5kIG5vdCBjb250YWluZWQgYW55d2hlcmUgd2l0aGluIHRoZSBkYXRhIGZyYW1lIGl0c2VsZl0uIFBpZW5zYSBhZGVtw6FzIHF1ZSBsb3MgZGF0b3MgZXNwYWNpYWxlcyBtw6FzIHNpbXBsZXMgKGxvcyBwdW50b3MpIHNlcsOtYW4gZsOhY2lsZXMgZGUgYWxtYWNlbmFyIGVuIGRvcyBjb2x1bW5hcyBkZSB1biBkYXRhLmZyYW1lLCBwZXJvIHBpZW5zYSBlbiBvYmpldG9zIGVzcGFjaWFsZXMgbcOhcyBjb21wbGVqb3MgY29tbyBsaW5lYXMsIHBvbMOtZ29ub3MsIHBvbMOtZ29ub3MgY29uIGh1ZWNvcyBldGMuLi4gaGFjZSBxdWUgcGFyYSBhbG1hY2VuYXIgZGF0b3MgZXNwYWNpYWxlcyBlbiBSIHNlIHV0aWxpY2VuIGVzdHJ1Y3R1cmFzIGRlIGRhdG9zIG3DoXMgY29tcGxlamFzIHF1ZSB1biBkYXRhLmZyYW1lLgoKCkFkZW3DoXMsIHBhcmEgY29tcGxpY2FyIHVuIHBvY28gbcOhcyBsYXMgY29zYXMsIGhheSBkb3MgdGlwb3MgZnVuZGFtZW50YWxlcyBkZSBkYXRvcyBnZW9ncsOhZmljb3M6ICoqcmFzdGVyIHkgdmVjdG9yaWFsZXMqKi4gRW4gZWwgY3Vyc28gc29sbyB2YW1vcyBhIHZlciAodW4gcG9xdWl0bykgbG9zIGRhdG9zIGVzcGFjaWFsZXMgdmVjdG9yaWFsZXMuCgotIExvcyBkYXRvcyByw6FzdGVyIHNlIGFsbWFjZW5hbiBjb21vIHVuYSBjdWFkcsOtY3VsYSBkZSB2YWxvcmVzIHF1ZSBzZSByZXByZXNlbnRhbiBlbiB1biBtYXBhIGNvbW8gcMOteGVsZXMuIENhZGEgdmFsb3IgZGUgcMOteGVsIHJlcHJlc2VudGEgdW4gw6FyZWEgZW4gbGEgc3VwZXJmaWNpZSBkZSBsYSBUaWVycmEuIExvcyBkYXRvcyByw6FzdGVyIHNvbiBkYXRvcyBwaXhlbGFkb3MgKG8gY3VhZHJpY3VsYWRvcykgZW4gbG9zIHF1ZSBjYWRhIHDDrXhlbCBlc3TDoSBhc29jaWFkbyBhIHVuYSB1YmljYWNpw7NuIGdlb2dyw6FmaWNhIGVzcGVjw61maWNhLiBFbCB2YWxvciBkZSB1biBww614ZWwgcHVlZGUgc2VyIGNvbnRpbnVvIChwb3IgZWplbXBsbywgZWxldmFjacOzbikgbyBjYXRlZ8OzcmljbyAocG9yIGVqZW1wbG8sIHVzbyBkZWwgc3VlbG8pLiBVbiByw6FzdGVyIGdlb2VzcGFjaWFsIHNvbG8gZXMgZGlmZXJlbnRlIGRlIHVuYSBmb3RvIGRpZ2l0YWwgZW4gcXVlIGVzdMOhIGFjb21wYcOxYWRvIGRlIGluZm9ybWFjacOzbiBlc3BhY2lhbCBxdWUgY29uZWN0YSBsb3MgZGF0b3MgYSB1bmEgdWJpY2FjacOzbiBlbiBwYXJ0aWN1bGFyLiBFc3RvIGluY2x1eWUgbGEgZXh0ZW5zacOzbiB5IGVsIHRhbWHDsW8gZGUgY2VsZGEgZGVsIHLDoXN0ZXIsIGVsIG7Dum1lcm8gZGUgZmlsYXMgeSBjb2x1bW5hcyB5IHN1IHNpc3RlbWEgZGUgcmVmZXJlbmNpYSBkZSBjb29yZGVuYWRhcyAobyBDUlMpCgoKLSBMYXMgZXN0cnVjdHVyYXMgZGUgZGF0b3MgdmVjdG9yaWFsZXMgcmVwcmVzZW50YW4gcG9zaWNpb25lcyBlc3BlY8OtZmljYXMgZW4gbGEgc3VwZXJmaWNpZSBkZSBsYSBUaWVycmEuCgoKCiMjIGBzcGAgdmVyc3VzIGBzZmAKCkVuIFIgaGF5IDIgZm9ybWFzIGFsdGVybmF0aXZhcyBkZSBhbG1hY2VuYXIgKHkgZGUgbWFuaXB1bGFyKSBkYXRvcyBnZW9ncsOhZmljb3MgdmVjdG9yaWFsZXM6IGVsIHBhcXVldGUgYHNwYCB5IGVsIHBhcXVldGUgYHNmYC4gTGFzIHByaW5jaXBhbGVzIGRpZmVyZW5jaWFzIGVudHJlIGxvcyBwYXF1ZXRlcyBzcCB5IHNmIHNlIGRlcml2YW4gZGUgbGEgbWFuZXJhIGVuIHF1ZSBsYXMgY2xhc2VzIHNwIHkgc2YgYWxtYWNlbmFuIGluZm9ybWFjacOzbiBzb2JyZSBDUlMsIGRpc3Rpbmd1ZW4gZW50cmUgcHVudG9zLCBsw61uZWFzIHkgcG9sw61nb25vcywgeSBjw7NtbyBjb25lY3RhbiBlc3RvcyBkYXRvcyBlc3BhY2lhbGVzIGNvbiBkYXRvcyBubyBlc3BhY2lhbGVzLgoKQ3VhbmRvIHNlIHVzYW4gZXN0b3MgcGFxdWV0ZXMsIHRhbnRvIGBzcGAgY29tbyBgc2ZgLCBwcm9wb3JjaW9uYW4gZW5sYWNlcyBhIHBvdGVudGVzIGJpYmxpb3RlY2FzIFNJRyBkZSBjw7NkaWdvIGFiaWVydG8sIGNvbW8gWyoqR0RBTCoqXShodHRwczovL2dkYWwub3JnLykgKEdlb3NwYXRpYWwgRGF0YSBBYnN0cmFjdGlvbiBMaWJyYXJ5OiBlcyB1bmEgYmlibGlvdGVjYSBkZSBzb2Z0d2FyZSBwYXJhIGxhIGxlY3R1cmEgeSBlc2NyaXR1cmEgZGUgZm9ybWF0b3MgZGUgZGF0b3MgZ2VvZXNwYWNpYWxlcywgcHVibGljYWRhIGJham8gbGEgTUlUIExpY2Vuc2UgcG9yIGxhIGZ1bmRhY2nDs24gZ2VvZXNwYWNpYWwgZGUgY8OzZGlnbyBhYmllcnRvIChPcGVuIFNvdXJjZSBHZW9zcGF0aWFsIEZvdW5kYXRpb24pLCBbKipHRU9TKipdKGh0dHBzOi8vZ2Vvcy5vc2dlby5vcmcvZG94eWdlbi9pbmRleC5odG1sKSAoR2VvbWV0cnkgRW5naW5lIE9wZW4gU291cmNlOiBwYXJhIG9wZXJhY2lvbmVzIGRlIHRvcG9sb2fDrWEgZW4gZ2VvbWV0csOtYXMpIHkgWyoqUFJPSioqXShodHRwczovL3Byb2oub3JnL25ld3MuaHRtbCkgKFBST0ogZXMgdW5hIGJpYmxpb3RlY2EgcXVlIHByb3BvcmNpb25hIG3DqXRvZG9zIHBhcmEgdHJhbnNmb3JtYXIgZW50cmUgc2lzdGVtYXMgZGUgcmVmZXJlbmNpYSBkZSBjb29yZGVuYWRhcyBkaWZlcmVudGVzIHkgb3BlcmFjaW9uZXMgZGUgcHJveWVjY2nDs24pLiBQb3IgZXNvIGN1YW5kbyBjYXJnYW1vcyBlbCBwYXF1ZXRlIGBzZmAgbm9zIGFwYXJlY2UgZWwgc2lndWllbnRlIG1lbnNhamUgZW4gbGEgY29uc29sYTogYExpbmtpbmcgdG8gR0VPUyAzLjkuMSwgR0RBTCAzLjMuMiwgUFJPSiA3LjIuMWAKCgpNdWNob3MgcGFxdWV0ZXMgUiBwYXJhIEdJUyB1dGlsaXphbiBsYSBtZXRvZG9sb2fDrWEvYXJxdWl0ZWN0dXJhIHByb3BvcmNpb25hZGEgcG9yIGBzcGAsIHBlcm8gcG9yIHZhcmlhcyByYXpvbmVzIGxhIG1ldG9kb2xvZ8OtYSBpbXBsZW1lbnRhZGEgcG9yIGVsIHBhcXVldGUgIGVsIHBhcXVldGUgYHNmYCB5YSBlcyBwcsOhY3RpY2FtZW50ZSBlbCBlc3TDoW5kYXIuCgpMYXMgcHJpbmNpcGFsZXMgcmF6b25lcyBzb24gNDogCgoxKSBFbCBwYXF1ZXRlIGBzZmAgaW1wbGVtZW50YSBlbCBlc3TDoW5kYXIgYWJpZXJ0byAic2ltcGxlIGZlYXV0dXJlcyIgcGFyYSBsYSByZXByZXNlbnRhY2nDs24gZGUgZGF0b3MgZ2VvZ3LDoWZpY29zIHZlY3RvcmlhbGVzIGVuIFIuIFNpbXBsZSBmZWF0dXJlcyBlcyB1biBlc3TDoW5kYXIsIHVuIGNvbmp1bnRvIGNvbcO6biBkZSByZWdsYXMsIHBhcmEgcmVwcmVzZW50YXIgbGFzIGdlb21ldHLDrWFzIGVzcGFjaWFsZXMgZGUgb2JqZXRvcyBkZWwgbXVuZG8gcmVhbCBlbiB1biBvcmRlbmFkb3IsIGVuIGxhcyBxdWUgc2UgZGVmaW5lIGRlc2RlIGPDs21vIHNlIGFsbWFjZW5hbiBsb3MgZGF0b3MgZW4gZWwgb3JkZW5hZG9yIGhhc3RhIHF1w6kgb3BlcmFjaW9uZXMgZ2VvbcOpdHJpY2FzIHNlIHB1ZWRlbiBhcGxpY2FyLiAKCjIpIGBzZmAgdXRpbGl6YSBwYXJhIGNvbnRlbmVyL2FsbWFjZW5hciBsb3MgZGF0b3MgZXNwYWNpYWxlcyB1bmEgZXN0cnVjdHVyYSBtdXkgc2ltaWxhciBhIHVuIGRhdGFmcmFtZTogbG9zIHNwYXRpYWwgZGF0YWZyYW1lcyBvIGBzZmBeW1PDrSwgdG9kbyBzZSBsbGFtYSBgc2ZgISEhIFRhbnRvIGVsIHBhcXVldGUgY29tbyBlbCBlc3TDoW5kYXIgcXVlIGluY29ycG9yYSBjb21vIGxhIGVzdHJ1Y3R1cmEgZGUgZGF0b3Mgc2UgbGxhbWFuIHRvZG9zIGBzZmAuXSAsIHF1ZSBzb24gbXV5IHBhcmVjaWRvcyBhIHVuIGRhdGEgZnJhbWUgc2Fsdm8gcXVlIGluY2x1eWVuIHVuYSBjb2x1bW5hIGFkaWNpb25hbCwgZ2VuZXJhbG1lbnRlIGxsYW1hZGEgYGdlb21ldHJ5YCBxdWUgYWxtYWNlbmEgbGEgaW5mb3JtYWNpw7NuIGdlb2dyw6FmaWNhLCBsb3MgZGF0b3MgdmVjdG9yaWFsZXMgZXNwYWNpYWxlcywgeWEgc2VhbiBlc3RvcyBwdW50b3MsIGxpbmVhcywgcG9sw61nb25vcyBldGMuLi4KCjMpIE11Y2hhcyBkZSBsYXMgZnVuY2lvbmVzIGRlbCBwYXF1ZXRlIGBzZmAgY29taWVuemFuIGNvbiBgc3RfYDsgcG9yIGVqZW1wbG8gIGBzdF9uZWFyZXN0X3BvaW50cygpYCwgbG8gcXVlIGZhY2lsaXRhIHN1IHVzby4KCgo0KSBsYXMgZnVuY2lvbmVzIGRlIGBzZmAgc2UgaW50ZWdyYW4gZW4gZWwgKip0aWR5dmVyc2Ugd29ya2Zsb3cqKi4gRGUgaGVjaG8gbGEgZXN0cnVjdHVyYSBkZSBkYXRvcyBxdWUgdXRpbGl6YSBlbCBwYXF1ZXRlIGBzZmAgcGFyYSBhbG1hY2VuYXIgZGF0b3MgZ2VvZ3LDoWZpY29zIHNvbiBsb3Mgb2JqZXRvcyBkZSB0aXBvIGBgc2ZgIHVuYSBleHRlbnNpw7NuIGRlIGxvcyBkYXRhLmZyYW1lcywgZGUgZm9ybWEgcXVlIHB1ZWRlbiBzZXIgbWFuaXB1bGFkb3MgY29uIGBkcGx5cmAgeSBncmFmaWNhZG9zIGNvbiBgZ2dwbG90MmAuIExvcyBvYmpldG9zIGBzZmAgcHVlZGVuIGFsbWFjZW5hciBkYXRvcyBub3JtYWxlcyAoY3VhbnRpdGF0aXZvcywgY3VhbGl0YXRpdm9zLCB0ZXh0bykgcGVybyBhbG1hY2VuYW4gZGF0b3MgZXNwYWNpYWxlcyBlbiB1bmEgY29sdW1uYSBlc3BlY2lhbCBsbGFtYWRhIGhhYml0dWFsbWVudGUgYGdlb21ldHJ5YCBjb24gY2xhc2UgZXNwZWNpYWwgKGBzZmNgKSwgeSBhbGzDrSBzZSBhbG1hY2VuYSBsYSBpbmZvcm1hY2nDs24gZ2VvZ3LDoWZpY2E6IGVsIENSUywgY29vcmRlbmFkYXMgeSBlbCB0aXBvIGRlIG9iamV0byBnZW9tw6l0cmljby4gTGEgY2xhc2Ugc2ZjIHRpZW5lIHNpZXRlIHN1YmNsYXNlcyBwYXJhIGRlbm90YXIgbG9zIGRpc3RpbnRvcyB0aXBvcyBkZSBvYmpldG9zIGdlb23DqXRyaWNvcyBxdWUgc2UgZGVyaXZhbiBkZWwgZXN0w6FuZGFyIGBzZmAuIAoKCi0gUG9yIGVqZW1wbG8sIHBhcmEgc2VsZWNjaW9uYXIgZ2VvbWV0csOtYXMgdXNhbW9zIGxhIGZ1bmNpw7NuIGBmaWx0ZXIoKWA6CgoKYGBge3J9CiMtIGVsZWdpbW9zIDQgQ0MuQUEKenpfcHJvdl9jaG9zZW4gPC0gYygiQW5kYWx1Y8OtYSIsICJHYWxpY2lhIiwgIkFyYWfDs24iLCAiQ2FuYXJpYXMiLCAiSWxsZXMgQmFsZWFycyIpCgp6el9wcm92XzEgPC0gUHJvdmluY2lhcyAlPiUgZmlsdGVyKGluZV9jY2FhLm4gJWluJSB6el9wcm92X2Nob3NlbikKenpfcHJvdl8yIDwtIFByb3ZpbmNpYXMgJT4lIGZpbHRlcighKGluZV9jY2FhLm4gJWluJSB6el9wcm92X2Nob3NlbikpCgpwMSA8LSBnZ3Bsb3QoenpfcHJvdl8xLCBhZXMoZmlsbCA9IGluZV9wcm92KSApICsgCiAgICAgICAgIGdlb21fc2YoKSAgKwogICAgICAgICBzY2FsZV9maWxsX3ZpcmlkaXNfZChndWlkZSA9ICJub25lIikgKwogICAgICAgICBsYWJzKHRpdGxlID0gIkNvb3JkLiBnZW9ncsOhZmljYXMiKQoKcDIgPC0gZ2dwbG90KHp6X3Byb3ZfMiwgYWVzKGZpbGwgPSBpbmVfcHJvdikgKSArIAogICAgICAgICBnZW9tX3NmKCkgICsKICAgICAgICAgc2NhbGVfZmlsbF92aXJpZGlzX2QoZ3VpZGUgPSAibm9uZSIpICsKICAgICAgICAgbGFicyh0aXRsZSA9ICJDb29yZC4gZ2VvZ3LDoWZpY2FzIikKCnAxICsgcDIgCmBgYAoKLSBQb3IgZWplbXBsbywgcGFyYSBhZ3J1cGFyIGdlb21ldHLDrWFzLCBlbiBlc3RlIGNhc28gbGFzIHByb3ZpbmNpYXMgZXNwYcOxb2xhcywgdXNhbW9zIGxhIGZ1bmNpw7NuIGBzdW1tYXJpemUoKWAKCgpgYGB7cn0KIy0gY3JlYW1vcyBsYXMgZ2VvbWV0csOtYXMgZGUgbGFzIENDLkFBIHBvciBhZ3J1cGFjacOzbiBkZSBsYXMgZ2VvbWV0csOtYXMgcHJvdmluY2lhbGVzCkNDQUFfMiA8LSBQcm92aW5jaWFzICU+JSBncm91cF9ieShpbmVfY2NhYSwgaW5lX2NjYWEubikgJT4lIHN1bW1hcml6ZSgpCgpwMSA8LSBnZ3Bsb3QoUHJvdmluY2lhcywgYWVzKGZpbGwgPSBpbmVfcHJvdikgKSArIAogICAgICAgICBnZW9tX3NmKCkgICsKICAgICAgICAgc2NhbGVfZmlsbF92aXJpZGlzX2QoZ3VpZGUgPSAibm9uZSIpICsKICAgICAgICAgbGFicyh0aXRsZSA9ICJDb29yZC4gZ2VvZ3LDoWZpY2FzIikKCgpwMiA8LSBnZ3Bsb3QoQ0NBQV8yLCBhZXMoZmlsbCA9IGluZV9jY2FhKSApICsgCiAgICAgICAgIGdlb21fc2YoKSAgKwogICAgICAgICBzY2FsZV9maWxsX2Rpc2NyZXRlKGd1aWRlID0gIm5vbmUiKSArCiAgICAgICAgIGxhYnModGl0bGUgPSAiQ29vcmQuIGdlb2dyw6FmaWNhcyIpCgpwMSArIHAyIApgYGAKCgpgYGB7ciwgZWNobyA9IEZBTFNFfQojLSBib3JyYW1vcyBsbyBxIG5vIG5vcyBoYWNlIGZhbHRhCnJtKHAxLCBwMiwgenpfcHJvdl9jaG9zZW4sIHp6X3Byb3ZfMSwgenpfcHJvdl8yLCBDQ0FBXzIpCmBgYAoKQnVlbm8sIHB1ZXMgZXN0ZSBlcyB0b2RhIGxhIHRlb3LDrWEgZGVsIHR1dG9yaWFsLiBWYW1vcyBhIGhhY2VyIGdyw6FmaWNvcyEhCgo8YnI+CgotLS0tLS0tLS0tLS0tLS0KCjxicj4KCgojIDIuIEhhY2llbmRvIGdyw6FmaWNvcwoKYGBge3IsIGV2YWwgPSBGQUxTRSwgZWNobyA9IEZBTFNFfQojLSBwb25lbW9zIHVuIHRoZW1lIHVuIHBvY28gbcOhcyBhZGVjdWFkbyBwYXJhIG1hcGFzCnRoZW1lX3NldCh0aGVtZV9idygpKSAgIy0gVGhlIGNsYXNzaWMgZGFyay1vbi1saWdodCBnZ3Bsb3QyIHRoZW1lIHdvcmsgYmV0dGVyIGZvciBwcmVzZW50YXRpb25zIAp0aGVtZV9zZXQoY293cGxvdDo6dGhlbWVfbWFwKCkpCmBgYAoKClByaW1lcm8gdmFtb3MgY2FyZ2FyIHVuIGNvbmp1bnRvIGRlIGRhdG9zIGVzcGFjaWFsZXMgZGVsIHBhcXVldGUgW2BybmF0dXJhbGVhcnRoYF0oaHR0cHM6Ly9kb2NzLnJvcGVuc2NpLm9yZy9ybmF0dXJhbGVhcnRoLykuIEJ1ZW5vLCBlbiByZWFsaWRhZCB5YSBsbyBoZW1vcyBjYXJnYWRvIGFudGVzLgoKCmBgYHtyLCBldmFsID0gRkFMU0V9CiMtIGFudGVzIHVzYWJhIGxhcyB3b3JsZyBnZW9tZXRpZXMgZGVsIHBhcXVldGUgcm5hdHVyYWxlYXJ0aCwgcGVybyAuLi4KIyB3b3JsZCA8LSBybmF0dXJhbGVhcnRoOjpuZV9jb3VudHJpZXMoc2NhbGUgPSAibWVkaXVtIiwgcmV0dXJuY2xhc3MgPSAic2YiKQojLSBwZXJvIGFob3JhIHByZWZpZXJvIHV0aWxpemFyIGxhcyBnZW9tZXRyaWFzIGRlbCBwYXF1ZXRlIHRtYXAsCiMtIGxhcyBndWFyZGUgZW4gbm92aWVtYnJlIGRlIDIwMjEuIFNlIGNhcmdhYmFuIGFzw606CmxpYnJhcnkodG1hcCkgCmRhdGEoV29ybGQpICMtIGhhY2Vtb3MgYWNjZXNpYmxlcyAxIGRmCndvcmxkIDwtIFdvcmxkIDsgcm0oV29ybGQpCmBgYAoKVmVhbW9zIHF1ZSBoYXkgZW4gYHdvcmxkYAoKYGBge3J9CmNsYXNzKHdvcmxkKSAjLSBlcyB1biBzZiwgcGVybyB0YiBlcyB1biBkYXRhLmZyYW1lCm5hbWVzKHdvcmxkKSAjLSBmaWphdGUgcXVlIGxhIHVsdGltYSBjb2x1bW5hIHNlIGxsYW1hIGdlb21ldHJ5IHkgYWxtYWNlbmEgbG9zIGRhdG9zIGVzcGFjaWFsZXMKYGBgCgoKTG9zIGdyw6FmaWNvcyBsb3MgdmFtb3MgYSBoYWNlciwgZGUgbW9tZW50bywgY29uIGBnZ3Bsb3QyYC4gRXMgdGFuIGbDoWNpbCBjb21vIHVzYXIgYGdlb21fc2YoKWAuIExhcyBzaWd1aWVudGVzIDMgZXhwcmVzaW9uZXMgcHJvZHVjZW4gZWwgbWlzbW8gZ3LDoWZpY286IAoKYGBge3IsIGV2YWwgPSBGQUxTRX0KZ2dwbG90KGRhdGEgPSB3b3JsZCkgKyBnZW9tX3NmKCkKCmdncGxvdChkYXRhID0gd29ybGQsIGFlcyhnZW9tZXRyeSA9IGdlb21ldHJ5KSkgKyBnZW9tX3NmKCkKCmdncGxvdCgpICsgZ2VvbV9zZihkYXRhID0gd29ybGQsIGFlcyhnZW9tZXRyeSA9IGdlb21ldHJ5KSkKYGBgCgoKYGBge3IsIGVjaG8gPSBGQUxTRX0KZ2dwbG90KGRhdGEgPSB3b3JsZCkgKyBnZW9tX3NmKCkKYGBgCgpDb21vIGVsIG1hcGEgcXVlIGhlbW9zIGhlY2hvLCBlbiByZWFsaWRhZCBlcyB1biBncsOhZmljbyBnZ3Bsb3QsIHB1ZXMgeWEgc2FiZW1vcyBjYW1iaWFyL3R1bmVhciBhbGd1bmFzIGRlIGxhcyBjYXJhY3RlcsOtc3RpY2FzIGRlbCBncsOhZmljby4gUG9yIGVqZW1wbG86IAoKYGBge3J9CmdncGxvdChkYXRhID0gd29ybGQpICsgZ2VvbV9zZihjb2xvciA9ICJibGFjayIsIGZpbGwgPSAibGlnaHRncmVlbiIsIGx3ZCA9IDAuMikKYGBgCgoKUG9kZW1vcyBwb25lciB0w610dWxvcyB5IGxleWVuZGFzOgoKYGBge3J9CnAgPC0gZ2dwbG90KGRhdGEgPSB3b3JsZCkgKyBnZW9tX3NmKCkgKwogICAgICAgbGFicyh0aXRsZSA9ICJHcsOhZmljbyAxOiBNYXBhIGRlbCBtdW5kbyIsCiAgICAgICAgICAgIGNhcHRpb24gPSAiRGF0b3MgcHJvdmVuaWVudGVzIGRlIHRtYXAiKQpwCmBgYAoKClBvZGVtb3MgaGFjZXIgbWFwYXMgZGUgKipDT1JPUExFVEFTKio6IGxhcyBkaXN0aW50YXMgcmVnaW9uZXMgc2UgY29sb3JlYW4gZGUgZm9ybWEgcXVlIG11ZXN0cmVuIGxvcyB2YWxvcmVzIGRlIHVuYSB2YXJpYWJsZSBlc3RhZMOtc3RpY2EuIFBvciBlamVtcGxvLCB1bmEgY29yb3BsZXRhIGNvbiBsYSB2YXJpYWJsZSBwb2JsYWNpw7NuKHBvYl9lc3QpCgoKYGBge3IsIGV2YWwgPSBGQUxTRX0KcCArIGdlb21fc2YoYWVzKGZpbGwgPSBwb3BfZXN0KSkKcCArIGdlb21fc2YoYWVzKGZpbGwgPSBwb3BfZXN0KSkgKyBzY2FsZV9maWxsX2Rpc3RpbGxlcigpCnAgKyBnZW9tX3NmKGFlcyhmaWxsID0gcG9wX2VzdCkpICsgc2NhbGVfZmlsbF92aXJpZGlzX2Mob3B0aW9uID0gInBsYXNtYSIsIHRyYW5zID0gInNxcnQiKQpgYGAKCgpgYGB7ciwgZWNobyA9IEZBTFNFfQpwICsgZ2VvbV9zZihhZXMoZmlsbCA9IHBvcF9lc3QpKSArIHNjYWxlX2ZpbGxfdmlyaWRpc19jKG9wdGlvbiA9ICJwbGFzbWEiLCB0cmFucyA9ICJzcXJ0IikKYGBgCjxicj4KCiMjIyBQcm95ZWNjaW9uZXMKClBhcmEgY2FtYmlhciBsYSBwcm95ZWNjacOzbiBkZSB1biBvYmpldG8gInNmIiBhIG90cm8gQ1JTIHVzYW1vcyBsYSBmdW5jacOzbiAgYHN0X3RyYW5zZm9ybSgpYCwgbyBhbHRlcm5hdGl2YW1lbnRlIHBvZGVtb3MgZXNwZWNpZmljYXIgZGlyZWN0YW1lbnRlIGVsIENSUyBlbiBlbCBncsOhZmljbyBjb24gYGNvb3JkX3NmKClgOgoKYGBge3J9CiMtIHdvcmxkMiA8LSBzdF90cmFuc2Zvcm0od29ybGQsICIrcHJvaj1sYWVhICt5XzA9MCArbG9uXzA9MTU1ICtsYXRfMD0tOTAgK2VsbHBzPVdHUzg0ICtub19kZWZzIikKcCArIGNvb3JkX3NmKGNycyA9ICIrcHJvaj1sYWVhICt5XzA9MCArbG9uXzA9MTU1ICtsYXRfMD0tOTAgK2VsbHBzPVdHUzg0ICtub19kZWZzIikgKyAKICAgICBsYWJzKHN1YnRpdGxlID0gIkxhbWJlcnQgQXppbXV0aGFsIEVxdWFsIEFyZWEgcHJvamVjdGlvbiIpCmBgYAoKPGJyPgoKVmFtb3MgYSBqdWdhciB1biBwb2NvIGNvbiBlbCBDUlMgY29uIGxhIGZ1bmNpw7NuIGBjb29yZF9zZigpYC4gSGF5IHF1ZSBzYWJlciBxdWUgZ2dwbG90IHVzYXLDoSBlbCBDUlMgZGUgbGEgcHJpbWVyYSBjYXBhIHF1ZSBjb250ZW5nYSB1bm8uIFNpIG5vIGh1Ymllc2UgbmluZ8O6biBDUlMgc2UgdXNhICoqV0dTODQqKiAobGF0aXR1ZGUvbG9uZ2l0dWRlLCB0aGUgcmVmZXJlbmNlIHN5c3RlbSB1c2VkIGluIEdQUykuCgpTZSBwdWVkZSBjYW1iaWFyIGVsIENSUyBkZSB2YXJpYXMgbWFuZXJhczoKCjEuIHVzYW5kbyB1biBgKipzdHJpbmcgUFJPSioqIHbDoWxpZG8uIFBvciBlamVtcGxvIGVzdGUgc3RyaW5nIGAiK3Byb2o9bGFlYSArbGF0XzA9NTIgK2xvbl8wPTEwICt4XzA9NDMyMTAwMCAreV8wPTMyMTAwMDAgK2VsbHBzPUdSUzgwICt1bml0cz1tICtub19kZWZzICJgIGVzIGVsIEV1cm9wZWFuLWNlbnRyaWMgRVRSUzg5IExhbWJlcnQgQXppbXV0aGFsIEVxdWFsLUFyZWEgcHJvamVjdGlvbikKCjIuIHVzYW5kbyB1biAqKmPDs2RpZ28gU1JJRCoqIChTcGF0aWFsIFJlZmVyZW5jZSBTeXN0ZW0gSWRlbnRpZmllcikgIAoKMy4gVXNhbmRvIHVuICoqY8OzZGlnbyBFUEdTKiogKEV1cm9wZWFuIFBldHJvbGV1bSBTdXJ2ZXkgR3JvdXApXltQYXJlY2UgcXVlIGNvbiBQUk9KNyBlcyBtZWpvciBubyB1c2FybG9dCgpMb3Mgc2lndWllbnRlcyB0cmVzIGdyw6FmaWNvcyBoYWNlbiBsbyBtaXNtby4gUmVwcmVzZW50YW4gbG9zIGRhdG9zIGRlbCBzcGF0aWFsLmRhdGEuZnJhbWUgYHdvcmxkYCB1c2FuZG8gdGhlIEVUUlM4OSBMYW1iZXJ0IEF6aW11dGhhbCBFcXVhbC1BcmVhIHByb2plY3Rpb24sIGN1eW8gY8OzZGlnbyBFUFNHIGVzIDMwMzUuIFtBcXXDrV0oaHR0cDovL3d3dy5qdW50YWRlYW5kYWx1Y2lhLmVzL21lZGlvYW1iaWVudGUvc2l0ZS9yZWRpYW0vbWVudWl0ZW0uMDRkYzQ0MjgxZTVkNTNjZjhjYTc4Y2E3MzE1MjVlYTAvP3ZnbmV4dG9pZD0yYTQxMmFiY2I4NmEyMjEwVmduVkNNMTAwMDAwMTMyNWU1MGFSQ1JEJmxyPWxhbmdfZXMpIHRpZW5lcyBhbGd1bm9zIGPDs2RpZ29zIGRlIHByb3llY2Npb25lczoKIAoKYGBge3IsIGV2YWwgPSBGQUxTRX0KcCArIGNvb3JkX3NmKGNycyA9ICIrcHJvaj1sYWVhICtsYXRfMD01MiArbG9uXzA9MTAgK3hfMD00MzIxMDAwICt5XzA9MzIxMDAwMCArZWxscHM9R1JTODAgK3VuaXRzPW0gK25vX2RlZnMgIikgICMtIHVzYW1vcyBQUk9KIHN0cmluZwoKcCArIGNvb3JkX3NmKGNycyA9IHN0X2NycygzMDM1KSkgICAjLSB1c2Ftb3MgU1JJRCBpZGVudGlmaWVyCgpwICsgY29vcmRfc2YoY3JzID0gIitpbml0PWVwc2c6MzAzNSIpICAjLSB1c2Ftb3MgRVBTRyBjb2RlIChtZWpvciBubyB1c2FybG8geHEgR0RBTCBoYSBkZXByZWNhdGVkIGVzdGEgc2ludGF4aXMpCmBgYAoKCmBgYHtyLCBlY2hvID0gVFJVRX0KcCArIGNvb3JkX3NmKGNycyA9IHN0X2NycygzMDM1KSkgKyAjLSB1c2Ftb3MgU1JJRCBpZGVudGlmaWVyCiAgICBsYWJzKHN1YnRpdGxlID0gIihFdXJvcGVhbi1jZW50cmljIEVUUlM4OSBcbkxhbWJlcnQgQXppbXV0aGFsIEVxdWFsLUFyZWEgcHJvamVjdGlvbikiKQpgYGAKCgo8YnI+CgojIyMgWm9vbQoKVm9sdmVtb3MgYSBsYSBwcm95ZWNjacOzbiBuYXRpdmEgZGVsIGNvbmp1bnRvIGRlIGRhdG9zIGB3b3JsZGAgeSBoYWNlbW9zIHVuIHpvb20gZW4gbnVlc3RybyBtYXBhIGNvbiBgY29vcmRfc2YoKWAuIEVsIHpvb20gbG8gaGFjZW1vcyBwYXJlIGNlbnRyYXJub3MgZW4gRXNwYcOxYSB5IGFscmVkZWRvcmVzLiBQYXJhIGhhY2VyIGVsIHpvb20gZW4gcmVhbGlkYWQgYWp1c3RhbW9zIGxvcyBsw61taXRlcyBkZSBsb3MgZWplcyBkZWwgZ3LDoWZpY28KCgpgYGB7cn0KcF9lc3AgPC0gcCArIGNvb3JkX3NmKHhsaW0gPSBjKC0xNS4wMCwgMTUuMDApLCB5bGltID0gYygyMSwgNDcuNDQpLCBleHBhbmQgPSBUUlVFKQpwX2VzcApgYGAKCkNvbiBlbCBwYXF1ZXRlIGBnZ3NwYXRpYWxgIHBvZGVtb3MgcG9uZXIgZWxlbWVudG9zIGRlIG1hcGFzIGNvbW8gdW5hICpTY2FsZSBiYXIqIGNvbiBgYW5ub3RhdGlvbl9zY2FsZSgpYCB5IHVuYSAqTm9ydGggYXJyb3cqIGNvbiBgYW5ub3RhdGlvbl9ub3J0aF9hcnJvdygpYDoKCgpgYGB7cn0KcF9lc3AgKyAKIGdnc3BhdGlhbDo6YW5ub3RhdGlvbl9zY2FsZShsb2NhdGlvbiA9ICJibCIsIHdpZHRoX2hpbnQgPSAwLjUpICsKIGdnc3BhdGlhbDo6YW5ub3RhdGlvbl9ub3J0aF9hcnJvdyhsb2NhdGlvbiA9ICJibCIsIHdoaWNoX25vcnRoID0gInRydWUiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYWRfeCA9IHVuaXQoMC43NSwgImluIiksIHBhZF95ID0gdW5pdCgwLjUsICJpbiIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0eWxlID0gbm9ydGhfYXJyb3dfZmFuY3lfb3JpZW50ZWVyaW5nKQpgYGAKCjxicj4KCiMjIyB0ZXh0byBlbiBlbCBtYXBhCgpFbCBtYXBhIHNlIGhhIGhlY2hvIGNvbiBgZ2dwbG90MmAsIGFzw60gcXVlIHBvZGVtb3MgYcOxYWRpciwgcG9yIGVqZW1wbG8sIHRleHRvIGNvbiBgZ2VvbV90ZXh0KClgOyBwZXJvIGFudGVzIGRlIGVsbG8sIHZhbW9zIGEgY3JlYXIvY2FsY3VsYXIgY2VudHJvaWRlcyBkZSBjYWRhIHBhw61zIHBhcmEgdmVyIGRvbmRlIHNpdHVhbW9zIGVsIHRleHRvIGVuIGVsIG1hcGEuIAoKCmBgYHtyfQp3b3JsZF9wb2ludHMgPC0gc3RfY2VudHJvaWQod29ybGQsIG9mX2xhcmdlc3RfcG9seWdvbiA9IFRSVUUpICAgICMtIHN1c3RpdHV5ZSBsYSBnZW9tZXRyeSBwb3IgZWwgY2VudHJvaWRlCmBgYAoKRGUgZXN0YSBmb3JtYSwgY29uIGBzdF9jZW50cm9pZCgpYCBwb2RlbW9zIGNhbGN1bGFyIGxvcyBjZW50cm9pZGVzIGRlIGNhZGEgZ2VvbWV0csOtYSwgZGUgY2FkYSBwYcOtcywgcGVybyBMb3MgZGF0b3MgZGVsIGNlbnRyb2lkZSAobG9uZ2l0dWQsIGxhdGl0dWQpIHNlIGFsbWFjZW5hbiBlbiBsYSBjb2x1bW5hIGBnZW9tZXRyeWA7IGVzIGRlY2lyIHN1c3RpdHV5ZW4gbyBlbGltaW5hbiBsYXMgZ2VvbWV0csOtYXMgYW50ZXJpb3JlcyBxdWUgZXJhbiBsb3MgbMOtbWl0ZXMgbyBmcm9udGVyYXMgZGUgbG9zIHBhw61zZXMuCgpTaSBxdWVyZW1vcyBxdWUgbGFzIGdlb21ldHLDrWFzIG9yaWdpbmFsZXMgeSBsb3MgY2VudHJvaWRlcyBlc3TDqW4gZW4gdW4gbWlzbW8gZGF0YS5mcmFtZSwgaGVtb3MgZGUgaGFjZXI6CgpgYGB7cn0Kd29ybGRfcG9pbnRzIDwtIGNiaW5kKHdvcmxkLCBzdF9jb29yZGluYXRlcyhzdF9jZW50cm9pZCh3b3JsZCRnZW9tZXRyeSwgb2ZfbGFyZ2VzdF9wb2x5Z29uID0gVFJVRSkpKSAjLSBhaG9yYSBlbCBjZW50cm9pZGUgcGFzYSBhIGVzdGFyIGVuIDIgY29sdW1uYXMsIGxsYW1hZGFzIFggZSBZLCBkb25kZSBmaWd1cmFuIGxhIGxvbmdpdHVkIHkgbGF0aXR1ZCBkZWwgY2VudHJvaWRlCmBgYAoKTG8gcXVlIGhhY2Vtb3MgZXMgZnVzaW9uYXIgbG9zIGxvcyBkb3MgKmRhdGEuZnJhbWUncyosIHkgZWwgY2VudHJvaWRlIHBhc2Fyw6EgYSBlc3RhciBlbiBkb3MgbnVldmFzIGNvbHVtbmFzIGxsYW1hZGFzIFggZSBZLCBzaW4gc3VzdGl0dWlyIGxhcyBnZW9tZXRyw61hcyBkZSBsb3MgcGHDrXNlcy4KClBhcmEgdmVyIGVzYXMgZG9zIG51ZXZhcyBjb2x1bW5hcyAoWCBlIFkpLCB2YW1vcyBhIGhhY2VyIHVuICJwZXF1ZcOxbyB0cnVjbyIsIHZhbW9zIGEgcXVpdGFyIGxhIGNvbHVtbmEgZGUgZ2VvbWV0csOtYXMuIEVzdG8gZmFjaWxpdGEgZWwgdmlzaW9uYWRvIGRlIGxvcyBkYXRvcywgcGVybyBsw7NnaWNhbWVudGUgY3VhbmRvIHZheWFtb3MgYSBoYWNlciBlbCBncsOhZmljbywgcmVhbG1lbnRlIG5vIHBvZHJlbW9zIGVsaW1pbmFyIGxhIGdlb21ldHJ5IHBvcnF1ZSBzaSBubywgbG9zIGRhdG9zIGRlamFyw61hbiBkZSBzZXIgZGF0b3MgZXNwYWNpYWxlcy4KCmBgYHtyfQojLSB0cnVjbyBwYXJhIHZlciBtZWpvciBsb3MgZGF0b3MgZXNwYWNpYWxlczogcXVpdGFyIGxhIGdlb21ldHLDrWEKbmFtZXMod29ybGRfcG9pbnRzKQp6eiA8LSB3b3JsZF9wb2ludHMgJT4lIHNlbGVjdChuYW1lLCBpc29fYTMsICBYLCBZKSAlPiUgIAogICAgICBzdF9zZXRfZ2VvbWV0cnkoTlVMTCkgICMtIHBvciBzaSBxdWllcm8gdmVyIG1lam9yIGxvcyBkYXRvcyBkZWwgZGYKYGBgCgoKSGFjZW1vcyBlbCBtYXBhIGNvbiBsb3Mgbm9tYnJlcyBkZSBsb3MgcGHDrXNlcyBjb24gYGdlb21fdGV4dCgpYCB5IGVsIG5vbWJyZSBkZWwgTWFyIE1lZGl0ZXJyw6FuZW8gY29uIGBhbm5vdGF0ZSgpYAoKYGBge3J9CnBfZXNwICsgCiAgZ2VvbV90ZXh0KGRhdGEgPSB3b3JsZF9wb2ludHMsIAogICAgICAgICAgICBhZXMoeCA9IFgsIHkgPSBZLCBsYWJlbCA9IG5hbWUpLCAKICAgICAgICAgICAgY29sb3IgPSAiZGFya2JsdWUiLCBmb250ZmFjZSA9ICJib2xkIiwgY2hlY2tfb3ZlcmxhcCA9IFRSVUUsIHNpemUgPSAyLjUpICsKICBhbm5vdGF0ZShnZW9tID0gInRleHQiLCB4ID0gNS41LCB5ID0gMzgsIAogICAgICAgICAgIGxhYmVsID0gIk1hciBNZWRpdGVycsOhbmVvIiwgZm9udGZhY2UgPSAiaXRhbGljIiwgY29sb3IgPSAiZ3JleTIyIiwgc2l6ZSA9IDQpCmBgYAoKCjxicj4KCiMjIyBUdW5lYW5kbyBkZWwgbWFwYQoKClZhbW9zIGEgdHVuZWFyIGVsIG1hcGEuIFBvciBlamVtcGxvLCB2YW1vcyBhIHBvbmVyIGVsIG1hciAoZW4gcmVhbGlkYWQgYWwgYGJhY2tncm91bmRgIGRlbCBgdGhlbWVgKSBkZWwgY29sb3IgYGFsaWNlYmx1ZWAsIHkgYSBsb3MgY29udGluZW50ZXMgbGVzIHBvbmRyZW1vcyB1biBjb2xvciB0ZXJyb3NvIGNvbiBgZmlsbCA9ICJhbnRpcXVld2hpdGUiYC4gQXJyZWdsYW1vcyB1biBwb2NvIGxhcyBncmlkLWxpbmVzIGRlbnRybyBkZWwgYHRoZW1lYCwgeSBsZSBwb25lbW9zIGxhIGVzdHJlbGxpdGEuIE5vIG5vcyB2YSBhIHNhbGlyIGJvbml0by1ib25pdG8gcG9ycXVlIGxvcyBub21icmVzIGRlIGxvcyBwYcOtc2VzIHNlIHZhbiBhIHN1cGVycG9uZXIuIFBvZHLDrWFtb3MgcXVpdGFyIG5vbWJyZXMsIHBlcm8gb3RybyBkw61hOgoKCmBgYHtyLCBvdXQud2lkdGggPSAiOTAlIn0KcCA8LSBwICsgCiAgZ2VvbV9zZihmaWxsID0gImFudGlxdWV3aGl0ZSIpICsKICBnZW9tX3RleHQoZGF0YSA9IHdvcmxkX3BvaW50cywgCiAgICAgICAgICAgIGFlcyh4ID0gWCwgeSA9IFksIGxhYmVsID0gbmFtZSksIAogICAgICAgICAgICAgICAgY29sb3IgPSAiZGFya2JsdWUiLCBmb250ZmFjZSA9ICJib2xkIiwgCiAgICAgICAgICAgICAgICBjaGVja19vdmVybGFwID0gVFJVRSwgc2l6ZSA9IDIpICsKICBhbm5vdGF0aW9uX25vcnRoX2Fycm93KGxvY2F0aW9uID0gImJsIiwgd2hpY2hfbm9ydGggPSAidHJ1ZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICBwYWRfeCA9IHVuaXQoMC43NSwgImluIiksIAogICAgICAgICAgICAgICAgICAgICAgICAgcGFkX3kgPSB1bml0KDAuNSwgImluIiksCiAgICAgICAgICAgICAgICAgICAgICAgICBzdHlsZSA9IG5vcnRoX2Fycm93X2ZhbmN5X29yaWVudGVlcmluZykgKwogIHRoZW1lKHBhbmVsLmdyaWQubWFqb3IgPSBlbGVtZW50X2xpbmUoY29sb3IgPSBncmF5KC41KSwgbGluZXR5cGUgPSAiZGFzaGVkIiwgc2l6ZSA9IDAuNSksIAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJhbGljZWJsdWUiKSkKcApgYGAKCkhhY2Vtb3Mgb3RyYSB2ZXogdW4gem9vbSBjb24gYHNmOjpjb29yZF9zZigpYAoKCmBgYHtyLCBvdXQud2lkdGggPSAiNzUlIn0KcCArIGNvb3JkX3NmKHhsaW0gPSBjKC0xNS4wMCwgMTUuMDApLCB5bGltID0gYygyMSwgNDcuNDQpLCBleHBhbmQgPSBUUlVFKSArCiAgICBnZ3NwYXRpYWw6OmFubm90YXRpb25fc2NhbGUobG9jYXRpb24gPSAiYmwiLCB3aWR0aF9oaW50ID0gMC41KQpgYGAKCgpDb21vIG5vcyBoYSBxdWVkYWRvIG1lZGlvIGNodWxvLCBzaSBxdWlzacOpcmFtb3MgZ3VhcmRhciBlbCBtYXBhIGhhcsOtYW1vczoKCmBgYHtyLCBldmFsID0gRkFMU0V9Cmdnc2F2ZSgibWFwLnBkZiIpCmdnc2F2ZSgibWFwX3dlYi5wbmciLCB3aWR0aCA9IDYsIGhlaWdodCA9IDYsIGRwaSA9ICJzY3JlZW4iKQpgYGAKCjxicj4KCi0tLS0tLS0tLS0tLS0tLQoKPGJyPgoKCiMgMy4gTWFzIHR1bmVhZG8KClZhbW9zIGEgaXIgbW9kaWZpY2FuZG8gZWwgbWFwYSwgbm8gbmVjZXNhcmlhbWVudGUgcGFyYSBoYWNlcmxvIG3DoXMgYm9uaXRvLCBzaW5vIHBhcmEgdmVyIGNvbW8gc2UgcHVlZGVuIGhhY2VyIG1hcGFzIGNvbiBgZ2dwbG90MmA6CgojIyMgYcOxYWRpZW5kbyBwdW50b3MKClByaW1lcm8gcXVpZXJvIGHDsWFkaXIgYWwgbWFwYSBkb25kZSBlc3TDoSBtaSBwdWVibG8geSBkb25kZSBlc3TDoSBWYWxlbmNpYSwgdmFtb3MgcXVlIHZveSBhIGHDsWFkaXIgcHVudG9zIGFsIG1hcGEuIAoKUGFyYSBlbGxvLCBwcmltZXJvIG5lY2VzaXRvIHRlbmVyIGxhIGxvY2FsaXphY2nDs24gZGUgZXN0b3MgcHVudG9zIGVuIGVsIG1hcGEsIGFzw60gcXVlIG1lIGZ1aSBhIFtPU00sIE9wZW5TdHJlZXRNYXBdKGh0dHBzOi8vd3d3Lm9wZW5zdHJlZXRtYXAub3JnLyNtYXA9MTQvLTEuOTQyMi8zMS41ODY2JmxheWVycz1DKSB5IGdlb2xvY2FsaWPDqSBsb3MgZG9zIG11bmljaXBpb3MgeSBjb24gZXN0YSBpbmZvcm1hY2nDs24sIHZveSAgYSBjcmVhciB1biBkYXRhLmZyYW1lIGNvbiBsYSBsb25nL2xhdCBkZSBtaSBwdWVibG8geSBkZSBWYWxlbmNpYTogCgoKYGBge3J9CnB1ZWJsb3MgPC0gZGF0YS5mcmFtZSgKICAgICAgICAgICAgICAgbm9tYnJlID0gYygiUGFuY3J1ZG8iLCAiVmFsZW5jaWEiKSwgCiAgICAgICAgICAgICAgIGxvbmdpdHVkZSA9IGMoLTEuMDI4NzgxLCAtMC4zNzU2NTcyKSwKICAgICAgICAgICAgICAgbGF0aXR1ZGUgPSBjKDQwLjc2MTc1LCAzOS40NzUzNCkgKSAgICAgCnB1ZWJsb3MKYGBgCgoKLSBsbyBtYXMgZsOhY2lsIHBhcmEgYcOxYWRpciBwdW50b3MgZXMgdXNhciBgZ2VvbV9wb2ludCgpYAoKYGBge3J9CnBfZXNwICsgCiAgZ2VvbV9wb2ludChkYXRhID0gcHVlYmxvcywgCiAgICAgICAgICAgICBhZXMoeCA9IGxvbmdpdHVkZSwgeSA9IGxhdGl0dWRlKSwgCiAgICAgICAgICAgICBzaXplID0gMiwgY29sb3IgPSAiZGFya3JlZCIpICsKICBnZW9tX3RleHQoZGF0YSA9IHB1ZWJsb3MsIAogICAgICAgICAgICBhZXMoeCA9IGxvbmdpdHVkZSwgeSA9IGxhdGl0dWRlLCBsYWJlbCA9IG5vbWJyZSksIAogICAgICAgICAgICBjb2xvciA9ICJkYXJrYmx1ZSIsIGZvbnRmYWNlID0gImJvbGQiLCBjaGVja19vdmVybGFwID0gVFJVRSwgc2l6ZSA9IDIuNSkKYGBgCgpVdGlsaXphciBgZ2VvbV9wb2ludCgpYCBlcyBmw6FjaWwgeSBmdW5jaW9uYSwgcGVybyBlcyBtw6FzIGZsZXhpYmxlIGNvbnZlcnRpciBlbCBkYXRhLmZyYW1lIGEgdW4gb2JqZXRvIGBzZmAuIEVzdG8gbG8gcG9kZW1vcyBoYWNlciBjb24gbGEgZnVuY2nDs24gYHN0X2FzX3NmKClgLCBkZWZpbmllbmRvIGxhIHByb3llY2Npw7NuIChhcXXDrSBXR1M4NCwgcXVlIGVzIGVsIGPDs2RpZ28gQ1JTICMgNDMyNik6CgoKYGBge3J9CnB1ZWJsb3Nfc2YgPC0gc3RfYXNfc2YocHVlYmxvcywgCiAgICAgICAgICAgICAgICAgICAgICAgY29vcmRzID0gYygibG9uZ2l0dWRlIiwgImxhdGl0dWRlIiksIAogICAgICAgICAgICAgICAgICAgICAgIGNycyA9IDQzMjYsICAjLSBFUFNHOjQzMjYgQ29vcmRlbmFkYXMgR2VvZ3LDoWZpY2FzIFdHUzg0CiAgICAgICAgICAgICAgICAgICAgICAgYWdyID0gImNvbnN0YW50IikKYGBgCgpQYXJhIGRlc3B1w6lzIGHDsWFkaXJsbyBjb21vIHVuYSBjYXBhIG3DoXMgY29uIGBnZW9tX3NmKClgIGEgbnVlc3RybyBncsOhZmljbzogCgpgYGB7cn0KcF9lc3AgKyBnZW9tX3NmKGRhdGEgPSBwdWVibG9zX3NmLCBzaXplID0gMiwgY29sb3IgPSAiZGFya3JlZCIpCmBgYAoKSGFnYW1vcyB6b29tIGNvbiBgY29vcmRfc2ZgCgpgYGB7cn0KcF9lc3AgKyBnZW9tX3NmKGRhdGEgPSBwdWVibG9zX3NmLCBzaXplID0gMiwgY29sb3IgPSAiZGFya3JlZCIpICsgCiAgICAgICAgY29vcmRfc2YoeGxpbSA9IGMoLTE1LjAwLCAxNS4wMCksIHlsaW0gPSBjKDIxLCA0Ny40NCksIGV4cGFuZCA9IFRSVUUpCmBgYAoKCiMjIyBBw7FhZGllbmRvIHBvbMOtZ29ub3MKCkxhIHZlcmRhZCBlcyBxdWUgc2UgaGFjZSBpZ3VhbCBxdWUgY29uIGxvcyBwdW50b3MsIGhheSBxdWUgdXNhciBsYSBmdW5jacOzbiBgZ2VvbV9zZigpYCB5IGEgY29ycmVyLgoKVmFtb3MgYSBhw7FhZGlyIGxvcyBjb250b3Jub3MvcG9sw61nb25vcyBkZSBsYXMgQ0MuQUE6CgoKYGBge3J9Cnp6X0NDQUEgPC0gQ0NBQSAlPiUgc3Rfc2V0X2dlb21ldHJ5KE5VTEwpCnAgKyBnZW9tX3NmKGRhdGEgPSBDQ0FBLCBmaWxsID0gTkEpICsgCiAgICAgIGNvb3JkX3NmKHhsaW0gPSBjKC0xNS4wMCwgMTUuMDApLCB5bGltID0gYygyMSwgNDcuNDQpLCBleHBhbmQgPSBUUlVFKQpgYGAKCkFob3JhIGHDsWFkaW1vcyBsYXMgcHJvdmluY2lhcyBkZSBBbmRhbHVjw61hLCBQRVJPIGNvbG9yZWFkYXMgc2Vnw7puIHN1IHBvYmxhY2nDs24gbWVkaWEgcG9yIG11bmljaXBpby4gUGFyYSBlbGxvOiAKCi0gMS4gcHJpbWVybyBjYXJnYW1vcyBkYXRvcyBkZSBwb2JsYWNpw7NuCgpgYGB7cn0KSU5FX3BhZHJvbl9tdW5pXzk2XzIwIDwtIHBqcHYuZGF0b3MuMDE6OnBvYl9tdW5pXzE5OTZfMjAyMAojSU5FX3BhZHJvbl9tdW5pXzk2XzIwIDwtIHBqcHYyMDIwLjAxOjpwanBfZGF0YV9wb2JfbXVuXzE5OTZfMjAxOQpwb2JfYW5kYWx1Y2lhIDwtIElORV9wYWRyb25fbXVuaV85Nl8yMCAlPiUgCiAgICAgICAgICAgICAgICAgZmlsdGVyKHllYXIgPT0gMjAyMCkgJT4lIAogICAgICAgICAgICAgICAgIGZpbHRlcihpbmVfY2NhYS5uID09ICJBbmRhbHVjw61hIikgJT4lCiAgICAgICAgICAgICAgICAgZ3JvdXBfYnkoaW5lX3Byb3YubikgJT4lIG11dGF0ZShuX3B1ZWJsb3MgPSBuX2Rpc3RpbmN0KGluZV9tdW5pLm4pKSAlPiUKICAgICAgICAgICAgICAgICBtdXRhdGUocG9iX3Byb3YgPSBzdW0ocG9iX3RvdGFsLCBuYS5ybSA9IFRSVUUpKSAlPiUgdW5ncm91cCgpICU+JQogICAgICAgICAgICAgICAgIG11dGF0ZShwb2JfbWVkaWFfcHVlYmxvcyA9IHBvYl9wcm92L25fcHVlYmxvcykgJT4lIAogICAgICAgICAgICAgICAgIHNlbGVjdChpbmVfcHJvdi5uLCBpbmVfcHJvdiwgcG9iX21lZGlhX3B1ZWJsb3MsIHBvYl9wcm92LCBuX3B1ZWJsb3MpICU+JQogICAgICAgICAgICAgICAgIGRpc3RpbmN0KCkgJT4lIHVuZ3JvdXAoKQpgYGAKCgoKLSAyLiBkZXNwdcOpcyBjYXJnYW1vcyBsaW5kZXMgcHJvdmluY2lhbGVzIChlbiByZWFsaWRhZCB5YSBsb3MgdGVuZW1vcykKCmBgYHtyfQojIFByb3ZpbmNpYXMgPC0gUHJvdmluY2lhcwp6el9Qcm92aW5jaWFzIDwtIFByb3ZpbmNpYXMgJT4lIHN0X3NldF9nZW9tZXRyeShOVUxMKQpgYGAKCgotIDMuIGZ1c2lvbmFtb3MgbG9zIGRhdG9zIGRlIHBvYmxhY2nDs24geSBsb3MgbGluZXMgcHJvdmluY2lhbGVzOgoKYGBge3J9Cmdlb19wb2JfYW5kYWx1Y2lhIDwtIGxlZnRfam9pbihwb2JfYW5kYWx1Y2lhLCBQcm92aW5jaWFzLCBieSA9IGMoImluZV9wcm92IiA9ICJpbmVfcHJvdiIpKQpnZW9fcG9iX2FuZGFsdWNpYSA8LSBsZWZ0X2pvaW4ocG9iX2FuZGFsdWNpYSwgUHJvdmluY2lhcykKYGBgCgoKLSA0LiBmaW5hbG1lbnRlIGHDsWFkaW1vcyBsYXMgcHJvdmluY2lhcyBjb2xvcmVhZGFzIHBvciBwb2JsYWNpw7NuIGFsIG1hcGE6CgpgYGB7cn0KcCArIGdlb21fc2YoZGF0YSA9IENDQUEsIGZpbGwgPSBOQSkgKwogICAgZ2VvbV9zZihkYXRhID0gZ2VvX3BvYl9hbmRhbHVjaWEsIGFlcyhnZW9tZXRyeSA9IGdlb21ldHJ5LCBmaWxsID0gbl9wdWVibG9zKSkgKwogICAgc2NhbGVfZmlsbF92aXJpZGlzX2MoYWxwaGEgPSAuNCkgKwogICAgZ2VvbV9zZihkYXRhID0gcHVlYmxvc19zZiwgc2l6ZSA9IDIsIGNvbG9yID0gImRhcmtyZWQiKSArCiAgICBjb29yZF9zZih4bGltID0gYygtMTUuMDAsIDI1LjAwKSwgeWxpbSA9IGMoMjEsIDU3LjQ0KSwgZXhwYW5kID0gVFJVRSkKYGBgCgotIHZhbW9zIGHDsWFkaXIgdGFtYmnDqW4gMyBtdW5pY2lwaW9zIGVzcGHDsW9sZXMgc2VsZWNjaW9uYWRvcyBhbCBhemFyOgoKYGBge3IsIGV2YWwgPSBGQUxTRX0KcHVlYmxvc18zIDwtIElHTl9ub21lbmNsYV9tdW5pICU+JSAKICAgICAgICAgICAgIHNhbXBsZV9uKDMpICU+JSAKICAgICAgICAgICAgIHNlbGVjdChOT01CUkVfQUNUVUFMLCBQUk9WSU5DSUEsIExPTkdJVFVEX0VUUlM4OSwgTEFUSVRVRF9FVFJTODkpICU+JQogICAgICAgICAgICAgc3RfYXNfc2YoY29vcmRzID0gYygiTE9OR0lUVURfRVRSUzg5IiwgIkxBVElUVURfRVRSUzg5IiksIGNycyA9IDQzMjYsIGFnciA9ICJjb25zdGFudCIsIHJlbW92ZSA9IEZBTFNFKQpgYGAKCi0gQnVlbm8sIGVuIHJlYWxpZGFkIG5vIGxvcyB2b3kgYSBlbGVnaXIgdGFuIHRhbiBhbCBhemFyOgoKYGBge3IsIGV2YWwgPSBUUlVFfQpwdWVibG9zXzMgPC0gSUdOX25vbWVuY2xhX211bmkgJT4lIAogICAgICAgICAgICAgZmlsdGVyKE5PTUJSRV9BQ1RVQUwgJWluJSBjKCJQYW5jcnVkbyIsICJBbGJ1cnF1ZXJxdWUiLCAiUG9sZW50aW5vcyIpKSAlPiUgCiAgICAgICAgICAgICBzZWxlY3QoTk9NQlJFX0FDVFVBTCwgUFJPVklOQ0lBLCBMT05HSVRVRF9FVFJTODksIExBVElUVURfRVRSUzg5KSAlPiUKICAgICAgICAgICAgIHN0X2FzX3NmKGNvb3JkcyA9IGMoIkxPTkdJVFVEX0VUUlM4OSIsICJMQVRJVFVEX0VUUlM4OSIpLCBjcnMgPSA0MzI2LCBhZ3IgPSAiY29uc3RhbnQiLCByZW1vdmUgPSBGQUxTRSkKYGBgCgoKYGBge3J9CnBfZXNwIDwtIHAgKyBnZW9tX3NmKGRhdGEgPSBDQ0FBLCBmaWxsID0gTkEpICsKICAgICAgICAgZ2VvbV9zZihkYXRhID0gZ2VvX3BvYl9hbmRhbHVjaWEsIGFlcyhnZW9tZXRyeSA9IGdlb21ldHJ5LCBmaWxsID0gbl9wdWVibG9zKSkgKwogICAgICAgICBzY2FsZV9maWxsX3ZpcmlkaXNfYyhhbHBoYSA9IC40KSArCiAgICAgICAgIGdlb21fc2YoZGF0YSA9IHB1ZWJsb3NfMywgc2l6ZSA9IDIsIGNvbG9yID0gImRhcmtyZWQiKSArCiAgICAgICAgIGdncmVwZWw6Omdlb21fbGFiZWxfcmVwZWwoZGF0YSA9IHB1ZWJsb3NfMywgYWVzKHggPSBMT05HSVRVRF9FVFJTODksIHkgPSBMQVRJVFVEX0VUUlM4OSwgbGFiZWwgPSBOT01CUkVfQUNUVUFMKSkgKwogICAgICAgICBjb29yZF9zZih4bGltID0gYygtMTAuMDAsIDcuMDApLCB5bGltID0gYygzNSwgNDUpLCBleHBhbmQgPSBUUlVFKQpwX2VzcApgYGAKCgojIyMgQ29uIGxvcyBtYXBhcyB5IGdyw6FmaWNvcyBudW5jYSBzZSBhY2FiYSEhIQoKQ2FuYXJpYXMgbm8gc2UgdmUuIMK/TGEgc2l0dWFtb3MgZW4gZWwgbWFwYT8gVXNhcmVtb3MgYHNmOjpzdF9iYm94KClgCgoKYGBge3J9CmNhbmFyaWFzIDwtIFByb3ZpbmNpYXMgJT4lIGZpbHRlcihpbmVfcHJvdiAlaW4lIGMoMzUsMzgpKQpwZW5pbnN1bGEgPC0gUHJvdmluY2lhcyAlPiUgZmlsdGVyKCAhKGluZV9wcm92ICVpbiUgYygzNSwgMzgpKSApCm15X3NoaWZ0IDwtIHN0X2Jib3gocGVuaW5zdWxhKVtjKDEsMildLSAoc3RfYmJveChjYW5hcmlhcylbYygxLDIpXSkgKyBjKC0yLjQsIC0xLjEpCmNhbmFyaWFzJGdlb21ldHJ5IDwtIGNhbmFyaWFzJGdlb21ldHJ5ICsgbXlfc2hpZnQKc3RfY3JzKGNhbmFyaWFzKSAgPC0gc3RfY3JzKHBlbmluc3VsYSkKcGVuaW5zdWxhX3lfY2FuYXJpYXMgPC0gcmJpbmQocGVuaW5zdWxhLCBjYW5hcmlhcykKCnAxIDwtIGdncGxvdCgpICsgZ2VvbV9zZihkYXRhID0gcGVuaW5zdWxhX3lfY2FuYXJpYXMpCnAyIDwtIGdncGxvdCgpICsgZ2VvbV9zZihkYXRhID0gcGVuaW5zdWxhKSArIGdlb21fc2YoZGF0YSA9IGNhbmFyaWFzLCBmaWxsID0gInB1cnBsZSIpIAojY29vcmRfc2YoeGxpbSA9IGMoLTEyLjAwLCA0LjAwKSwgeWxpbSA9IGMoMzMsIDQ0KSwgZXhwYW5kID0gVFJVRSkKCnAxICsgcDIgCmBgYAoKCi0gaGFnYW1vcyB6b29tCgpgYGB7cn0KZ2dwbG90KCkgKyBnZW9tX3NmKGRhdGEgPSBwZW5pbnN1bGEpICsKICAgICAgICAgICBnZW9tX3NmKGRhdGEgPSBjYW5hcmlhcykgKwogICAgICAgICAgIGNvb3JkX3NmKHhsaW0gPSBjKC0xMi4wMCwgNC4wMCksIHlsaW0gPSBjKDMzLCA0NCksIGV4cGFuZCA9IFRSVUUpCmBgYAoKCi0gcG9uZ2Ftb3MgQ2FuYXJpYXMsIHBpbnRhZGEgZW4gcm9qbywgZW4gbnVlc3RybyBtYXBhIAoKCmBgYHtyfQpwX2VzcCArIGdlb21fc2YoZGF0YSA9IGNhbmFyaWFzLCBmaWxsID0gInJlZCIpICsKICAgICAgICAgY29vcmRfc2YoeGxpbSA9IGMoLTEyLjAwLCA1LjAwKSwgeWxpbSA9IGMoMzAsIDQ0KSwgZXhwYW5kID0gVFJVRSkKYGBgCgoKIyMjIGVsIHLDrW8gRWJybwoKUHVlcyBxdWUgcXVpZXJvIHBvbmVyIGVsIFLDrW8gRWJybyBlbiBlbCBtYXBhLiBMb2dpY2FtZW50ZSBuZWNlc2lvIGxvcyBkYXRvcyBnZW9ncsOhZmljb3MgZGVsIHJpbyBFYnJvLgoKYGBge3J9CiNyaW9zIDwtIHJpbzo6aW1wb3J0KCJodHRwczovL2dpdGh1Yi5jb20vcGVyZXpwNDQvYXJjaGl2b3NfZG93bmxvYWQvYmxvYi9tYXN0ZXIvcmlvX2Vicm8ucmRzP3Jhdz10cnVlIikKenpfcmlvcyA8LSByaW9zX2VzcGFueWEgJT4lIHN0X3NldF9nZW9tZXRyeShOVUxMKQpuYW1lcyhyaW9zX2VzcGFueWEpCmBgYAoKCi0gVm95IGEgc2l0dWFyIGVuIGVsIG1hcGEgNCByw61vczoKCmBgYHtyLCByZXN1bHRzID0gImhpZGUifQojLSBubyBoYWNlIGZhbHRhIHBlcm8KY29kX3Jpb3NfZ3Vlbm9zIDwtIGMoOTEzNzU3LCA5MTM2ODcsIDkxMzY4NSwgOTEzNjE5KQpub21icmVfcmlvc19ndWVub3MgPC0gYygiUGFuY3J1ZG8iLCAiSmlsb2NhIiwgIkphbMOzbiIsICJFYnJvIikKCnJpb3NfZ3Vlbm9zIDwtIHJpb3NfZXNwYW55YSAlPiUgZmlsdGVyKENvZF9VbmkgJWluJSBjb2Rfcmlvc19ndWVub3MpCmBgYAoKCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IHBlbmluc3VsYSkgKyBnZW9tX3NmKCkgKwogICAgZ2VvbV9zZihkYXRhID0gcmlvc19ndWVub3MsIGNvbG9yID0gImxpZ2h0Ymx1ZSIsIGx3ZCA9IDEuMykgKwogICAgbGFicyhzdWJ0aXRsZSA9ICJNYXBhIGRlIEVzcGHDsWEgY29uIDQgcsOtb3MgR8O8ZW5vcyIsIGNvbG9yID0gIiIpCiMgY29sID0gc2Y6OnNmLmNvbG9ycygyLCBhbHBoYSA9IDAuNSkKYGBgCgotIMK/Q8OzbW8gc2UgbGxhbWFyw6EgZWwgcsOtbyBwZXF1ZcOxw61uPwoKCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IHBlbmluc3VsYSkgKyBnZW9tX3NmKCkgKwogICAgZ2VvbV9zZihkYXRhID0gcmlvc19ndWVub3MsIGNvbG9yID0gImxpZ2h0Ymx1ZSIsIGx3ZCA9IDEuMykgKwogICAgZ2VvbV9wb2ludChkYXRhID0gcHVlYmxvcywgYWVzKHggPSBsb25naXR1ZGUsIHkgPSBsYXRpdHVkZSksIHNpemUgPSAyLCBjb2xvciA9ICJkYXJrcmVkIikgKwogICAgZ2VvbV90ZXh0KGRhdGEgPSBwdWVibG9zLCBhZXMoeCA9IGxvbmdpdHVkZSwgeSA9IGxhdGl0dWRlLCBsYWJlbCA9IG5vbWJyZSksIGNvbG9yID0gImRhcmtibHVlIiwgZm9udGZhY2UgPSAiYm9sZCIsIGNoZWNrX292ZXJsYXAgPSBUUlVFLCBzaXplID0gMi41KSArIAogICAgbGFicyhzdWJ0aXRsZSA9ICJNYXBhIGRlIEVzcGHDsWEgY29uIDQgcsOtb3MgR8O8ZW5vcyIsIGNvbG9yID0gIiIpCmBgYAoKCjxicj4KCi0tLS0tLS0tLS0tLS0tLQoKPGJyPgoKCgojIDQuICBMYXlvdXRzCgpDb24gbG9zIG1hcGFzIHNlIHB1ZWRlbiBoYWNlciBjb21wb3NpY2lvbmVzIHBhcmEgZGVzdGFjYXIgZWwgbWVuc2FqZSBxdWUgc2UgcXVpZXJlIG1vc3RyYXIuIFNlIHB1ZWRlbiBoYWNlciBlc3RhcyBjb21wb3NpY2lvbmVzIGRlIHZhcmlhcyBtYW5lcmFzOgoKCi0gVXNpbmcg4oCcZ3JvYnPigJ0sIGkuZS4gZ3JhcGhpYyBvYmplY3RzIGZyb20gZ2dwbG90Miwgd2hpY2ggY2FuIGJlIGluc2VydGVkIGluIHRoZSBwbG90IHJlZ2lvbiB1c2luZyBwbG90IGNvb3JkaW5hdGVzOwoKLSBVc2FuZG8gZWwgcGFxdWV0ZSBgcGF0Y2h3b3JrYCBvIGVsIHBhcXVldGUgYGNvd3Bsb3RgCgpgYGB7ciwgZXZhbCA9IFRSVUV9Cm5hbWVzKHdvcmxkKQpnd29ybGQgPC0gZ2dwbG90KGRhdGEgPSB3b3JsZCkgKwogIGdlb21fc2YoYWVzKGZpbGwgPSBpbmNvbWVfZ3JwKSkgKwogICMgZ2VvbV9zZihhZXMoZmlsbCA9IHJlZ2lvbl93YikpICsKICBnZW9tX3JlY3QoeG1pbiA9IC0xNSwgeG1heCA9IDI1LjAwLCB5bWluID0gMjEsIAogICAgICAgICAgICB5bWF4ID0gNTcuNDQsIGZpbGwgPSBOQSwgY29sb3VyID0gImJsYWNrIiwgc2l6ZSA9IDEuNSkgKwogIHRoZW1lKHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJhenVyZSIpKSArCiAgbGFicyhzdWJ0aXRsZSA9ICJNYXBhIGRlbCBtdW5kbyIpICsKICBzY2FsZV9maWxsX2Rpc2NyZXRlKGd1aWRlID0gIm5vbmUiKQoKZ3dvcmxkCmBgYAoKCmBgYHtyLCBldmFsID0gVFJVRX0KZ19ldXJvcGUgPC0gZ2dwbG90KGRhdGEgPSB3b3JsZCkgKwogICAgZ2VvbV9zZigpICsKICAgIGdlb21fc2YoZGF0YSA9IENDQUEsIGZpbGwgPSBOQSkgKwogICAgZ2VvbV9zZihkYXRhID0gZ2VvX3BvYl9hbmRhbHVjaWEsIGFlcyhnZW9tZXRyeSA9IGdlb21ldHJ5LCBmaWxsID0gbl9wdWVibG9zKSkgKwogICAgZ2VvbV9zZihkYXRhID0gcHVlYmxvc18zLCBzaXplID0gMiwgY29sb3IgPSAiZGFya3JlZCIpICsKICAgIGdlb21fbGFiZWxfcmVwZWwoZGF0YSA9IHB1ZWJsb3NfMywgYWVzKHggPSBMT05HSVRVRF9FVFJTODksIHkgPSBMQVRJVFVEX0VUUlM4OSwgbGFiZWwgPSBOT01CUkVfQUNUVUFMKSkgKwogICAgY29vcmRfc2YoeGxpbSA9IGMoLTE1LjAwLCAyNS4wMCksIHlsaW0gPSBjKDIxLCA1Ny40NCksIGV4cGFuZCA9IFRSVUUpICsKICAgIHRoZW1lKHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJhenVyZSIpKSArCiAgICBsYWJzKHN1YnRpdGxlID0gIk1hcGEgZGUgRXVyb3BhIE9jY2lkZW50YWwiKSArCiAgICBzY2FsZV9maWxsX2NvbnRpbnVvdXMoZ3VpZGUgPSAibm9uZSIpCmdfZXVyb3BlCmBgYAoKCgpgYGB7ciwgZXZhbCA9IFRSVUV9CmxpYnJhcnkocGF0Y2h3b3JrKQpnd29ybGQgKyBnX2V1cm9wZSArIHBsb3RfbGF5b3V0KHdpZHRocyA9IGMoMSwgMikpCmBgYAoKCgo8YnI+CgotLS0tLS0tLS0tLS0tLS0KCjxicj4KCiMgNS4gUGFxdWV0ZSBgTWFwdmlld2AKClZhbW9zIGEgdmVyIGxvIGbDoWNpbCBxdWUgZXMgaGFjZXIgdW4gbWFwYSBjb24gZWwgcGFxdWV0ZSBbYG1hcHZpZXdgXShodHRwczovL3Itc3BhdGlhbC5naXRodWIuaW8vbWFwdmlldy8pLgoKUG9yIGVqZW1wbG8sIHRlbsOtYW1vcyBnZW9sb2NhbGl6YWRvcyBhIFBhbmNydWRvIHkgVmFsZW5jaWEuIFZlw6Ftb3Nsb3MgZW4gdW4gZ3LDoWZpY28gY29uIGBtYXB2aWV3KClgCgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpwdWVibG9zIDwtIGRhdGEuZnJhbWUobm9tYnJlID0gYygiUGFuY3J1ZG8iLCAiVmFsZW5jaWEiKSwgbG9uZ2l0dWRlID0gYygtMS4wMjg3ODEsIC0wLjM3NTY1NzIpLCBsYXRpdHVkZSA9IGMoNDAuNzYxNzUsIDM5LjQ3NTM0KSkgIy0gRVRSUzg5CnB1ZWJsb3Nfc2YgPC0gc3RfYXNfc2YocHVlYmxvcywgY29vcmRzID0gYygibG9uZ2l0dWRlIiwgImxhdGl0dWRlIiksIGNycyA9IDQzMjYsIGFnciA9ICJjb25zdGFudCIpICMtIEVQU0c6NDMyNiBDb29yZGVuYWRhcyBHZW9ncsOhZmljYXMgV0dTODQKbWFwdmlldzo6bWFwdmlldyhwdWVibG9zX3NmKQpgYGAKCgpPdHJvIGVqZW1wbG8gY29uIGxhcyBnZW9tZXRyw61hcyBkZSBsYXMgQ0MuQUEsIGF1bnF1ZSBlcyB1bmEgYWZyZW50YSBwYXJhIFBhbmNydWRvCgpgYGB7ciwgZXZhbCA9IEZBTFNFfQptYXB2aWV3OjptYXB2aWV3KHJpb3MpCmBgYAoKUGFyYSB2ZXIgcXVlIHNlIHB1ZWRlIGhhY2VyIGNvbiBgbWFwdmlld2AgdmFtb3MgYSBhw7FhZGlyIGFsZ3VuYSBpbmZvcm1hY2nDs24gYSBDQ0FBLCBwb3IgZWplbXBsbyBlbCBuw7ptZXJvIGRlIHByb3ZpbmNpYXMsIGVsIG7Dum1lcm8gZGUgbXVuaWNpcGlvcyB5IGxhIHBvYmxhY2nDs24gdG90YWwgZW4gZWwgYcOxbyAyMDE3OgoKCmBgYHtyLCBldmFsID0gRkFMU0V9CklORV9wYWRyb25fbXVuaV85Nl8yMCA8LSBwanB2LmRhdG9zLjAxOjpwb2JfbXVuaV8xOTk2XzIwMjAKI0lORV9wYWRyb25fbXVuaV85Nl8yMCA8LSBwanB2MjAyMC4wMTo6cGpwX2RhdGFfcG9iX211bl8xOTk2XzIwMTkKaW5mb19DQ0FBcyA8LSBJTkVfcGFkcm9uX211bmlfOTZfMjAgJT4lIAogICAgICAgICAgICAgIGZpbHRlcih5ZWFyID09IDIwMTcpICU+JSAKICAgICAgICAgICAgICAjZmlsdGVyKHBvYmxhY2lvbiA9PSAiVG90YWwiKSAlPiUgCiAgICAgICAgICAgICAgZ3JvdXBfYnkoaW5lX2NjYWEubikgJT4lIAogICAgICAgICAgICAgIG11dGF0ZShuX3Byb3YgPSBuX2Rpc3RpbmN0KGluZV9wcm92KSkgJT4lIAogICAgICAgICAgICAgIG11dGF0ZShuX3B1ZWJsb3MgPSBuX2Rpc3RpbmN0KGluZV9tdW5pKSkgJT4lIAogICAgICAgICAgICAgIG11dGF0ZShwb2JfQ0NBQSA9IHN1bShwb2JfdG90YWwsIG5hLnJtID0gVFJVRSkpICU+JSB1bmdyb3VwKCkgJT4lCiAgICAgICAgICAgICAgc2VsZWN0KGluZV9jY2FhLCBpbmVfY2NhYS5uLCBuX3Byb3YsIG5fcHVlYmxvcywgcG9iX0NDQUEpICU+JQogICAgICAgICAgICAgIGRpc3RpbmN0KCkgJT4lIHVuZ3JvdXAoKQpgYGAKCgpKdW50YW1vcyBlbCBkYXRhLmZyYW1lIGBpbmZvX0NDQUFzYCBjb24gZWwgZGF0YWZyYW1lIGNvbiBsYXMgZ2VvbWV0csOtYXMgYGBDQ0FBYDoKCgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpDQ0FBX2Nvbl9pbmZvIDwtIGxlZnRfam9pbihDQ0FBLCBpbmZvX0NDQUFzKQpgYGAKCgpZIHZhbW9zIGEgZ3JhZmljYXJsbyB5YSBjb24gYG1hcHZpZXdgOgoKCmBgYHtyLCBldmFsID0gRkFMU0V9Cm1hcHZpZXc6Om1hcHZpZXcoQ0NBQV9jb25faW5mbywgCiAgICAgICAgICAgICAgICAgemNvbCA9IGMoImluZV9jY2FhLm4iLCAibl9wcm92IiwgIm5fcHVlYmxvcyIsICJwb2JfQ0NBQSIpLAogICAgICAgICAgICAgICAgIGNvbG9yID0gInRvbWF0byIsIGNvbC5yZWdpb25zID0gInB1cnBsZSIsIGx3ZCA9IDMsCiAgICAgICAgICAgICAgICAgY2V4ID0gIm5fcHVlYmxvcyIpCmBgYAoKCmBgYHtyLCBldmFsID0gRkFMU0V9Cm1hcHZpZXc6Om1hcHZpZXcoQ0NBQV9jb25faW5mbywgCiAgICAgICAgICAgICAgICAgemNvbCA9IGMoImluZV9jY2FhLm4iLCAibl9wcm92IiwgIm5fcHVlYmxvcyIsICJwb2JfQ0NBQSIpLAogICAgICAgICAgICAgICAgY2V4ID0gIm5fcHVlYmxvcyIpCmBgYAoKU2UgcHVlZGVuIHBvbmVyIHZhcmlhcyBjYXBhczoKCmBgYHtyLCBldmFsID0gRkFMU0V9Cm1hcHZpZXcobGlzdChDQ0FBX2Nvbl9pbmZvLCBwdWVibG9zX3NmKSwgbGF5ZXIubmFtZSA9IGMoIkNDLkFBIiwgIjIgbXVuaWNpcGlvcyBjaGFjaGlzIikpCmBgYAoKTyBtZWpvciBhc8OtOgoKYGBge3IsIGV2YWwgPSBGQUxTRX0KbWFwdmlldzo6bWFwdmlldyhDQ0FBX2Nvbl9pbmZvLCB6Y29sID0gYygiaW5lX2NjYWEubiIsICJuX3Byb3YiLCAibl9wdWVibG9zIiwgInBvYl9DQ0FBIikpICsKbWFwdmlldzo6bWFwdmlldyhwdWVibG9zX3NmKSAKYGBgCgoKWSBtdWNoYXMgbWFzIGNvc2FzLCBwb3IgZWplbXBsbyBbTWFwIHBvcC11cHNdKGh0dHBzOi8vci1zcGF0aWFsLmdpdGh1Yi5pby9tYXB2aWV3L2FydGljbGVzL2FydGljbGVzL21hcHZpZXdfMDQtcG9wdXBzLmh0bWwpICBvIFtJbWFnZSBwb3AtdXBzXShodHRwczovL3Itc3BhdGlhbC5naXRodWIuaW8vbWFwdmlldy9hcnRpY2xlcy9hcnRpY2xlcy9tYXB2aWV3XzA0LXBvcHVwcy5odG1sKQoKCi0gc2UgcHVlZGUgdXNhciBlbCBvcGVyYWRvciBgICU+JWAgY29uIGxhcyBmdW5jaW9uZXMgZGUgYHNmYAoKYGBge3IsIGV2YWwgPSBGQUxTRX0KQ0NBQV9jb25faW5mbyAlPiUgc3RfdW5pb24gJT4lIG1hcHZpZXcKYGBgCgoKCjxicj4KCgojIyMgSW50ZWdyYWNpw7NuIGNvbiBlbCBwYXF1ZXRlIGBtYXBlZGl0YAoKSW50ZWdyYWNpb24gY29uIFtgbWFwZWRpdGBdKGh0dHBzOi8vd3d3LnItc3BhdGlhbC5vcmcvci8yMDE3LzA2LzA5L21hcGVkaXRfMC0yLTAuaHRtbCkKCgoKYGBge3IsIGV2YWwgPSBGQUxTRX0KI2luc3RhbGwucGFja2FnZXMoIm1hcGVkaXQiKQpsaWJyYXJ5KG1hcGVkaXQpCm15X21hcCA8LSBtYXB2aWV3KHB1ZWJsb3Nfc2YpCmRyYXdGZWF0dXJlcyhteV9tYXAsIGVkaXRvciA9ICJsZWFmcG0iKSAgICMtIGdvb2QKYGBgCgpQYXJhIHJlY3VwZXJhciBlbCBtYXBhIGVzIG1lam9yIGFzw606CgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpsaWJyYXJ5KG1hcGVkaXQpCm15c19kaWJ1aml0b3MgPC0gZWRpdE1hcChtYXB2aWV3KHB1ZWJsb3Nfc2YpKQptYXB2aWV3KG15c19kaWJ1aml0b3MkZmluaXNoZWQpCmBgYAoKCgoKPGJyPgoKLS0tLS0tLS0tLS0tLS0tCgo8YnI+CgoKIyA2LiBQYXF1ZXRlIGBsZWFmbGV0YAoKCkVsIHBhcXVldGUgW2BsZWFmbGV0YF0oaHR0cHM6Ly9yc3R1ZGlvLmdpdGh1Yi5pby9sZWFmbGV0LykgcGVybWl0ZSB1dGlsaXphciBlbiBSIHVuYSBkZSBsYSBsaWJyZXLDrWFzIGRlIEphdmFTY3JpcHQgbcOhcyBjb25vY2lkYXM6IExlYWZsZXQuCgoKUGFyYSBhYnJpciB1biBtYXBhIGxlYWZsZXQ6CgpgYGB7ciwgZXZhbCA9IEZBTFNFIH0KbGlicmFyeShsZWFmbGV0KQpsZWFmbGV0KCkgJT4lIGFkZFRpbGVzKCkgJT4lIGxlYWZlbTo6YWRkTW91c2VDb29yZGluYXRlcygpCmBgYAoKCgpQb2RlbW9zIGNlbnRyYXIgZWwgbWFwYSwgZGVjaWRpciBlbCBuaXZlbCBkZSB6b29tIHkgcG9uZXJsZSBtYXJjYWRvcmVzIHkgcG9wdXBzOgoKCmBgYHtyfQptIDwtIGxlYWZsZXQoKSAlPiUKICBhZGRUaWxlcygpICU+JSAKICBzZXRWaWV3KGxuZyA9IC0xLjAyODc4MTAsIGxhdCA9IDQwLjc2MTc1LCB6b29tID0gNikgJT4lIAogIGFkZE1hcmtlcnMobG5nID0gLTEuMDI4NzgxMCwgbGF0ID0gNDAuNzYxNzUsIHBvcHVwID0gIlBhbmNydWRvIikgJT4lIAogIGFkZFBvcHVwcyhsbmcgPSAtMC4zNzU2NTcyLCBsYXQgPSAzOS40NzUzNCwgcG9wdXAgPSAiVmFsZW5jaWEiKQptCmBgYAoKClZhbW9zIGEgYcOxYWRpcmxlIGVsZW1lbnRvcywgcG9yIGVqZW1wbG8gcHVudG9zLCBwb3IgZWplbXBsbyBtaXMgMiBtdW5pY2lwaW9zOgoKCmBgYHtyLCBldmFsID0gRkFMU0V9CmxlYWZsZXQoKSAlPiUgCiAgYWRkVGlsZXMoKSAlPiUgIyBBZGQgZGVmYXVsdCBPcGVuU3RyZWV0TWFwIG1hcCB0aWxlcwogIGFkZENpcmNsZU1hcmtlcnMoZGF0YSA9IHB1ZWJsb3Nfc2YpCiAgI2xlYWZlbTo6YWRkSG9tZUJ1dHRvbihleHQgPSBleHRlbnQoYnJld2VyaWVzOTEpLCBsYXllci5uYW1lID0gInB1ZWJsb3MiKQpgYGAKCk8sIHBvciBlamVtcGxvIHBvbMOtZ29ub3MgY29tbyBsYXMgQ0MuQUE6CgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpsZWFmbGV0KGRhdGEgPSBDQ0FBKSAlPiUgYWRkVGlsZXMoKSAlPiUKICBhZGRQb2x5Z29ucyhmaWxsQ29sb3IgPSB0b3BvLmNvbG9ycygxOCwgYWxwaGEgPSBOVUxMKSwgc3Ryb2tlID0gRkFMU0UpCmBgYAoKCgpTaSB0ZSBoYSBndXN0YWRvIHR1IG1hcGEgbGVhZmxldCB5IHF1aWVyZXMgZ3VhcmRhcmxvLCBwdWVkZXMgZ3VhcmRhcmxvIGNvbW8gdW4gYXJjaGl2byBodG1sIGFzw606CgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpodG1sdG9vbHM6OnNhdmVfaHRtbChscGxvdCwgImxlYWZsZXQuaHRtbCIpCmBgYAoKCgpTZSBwdWVkZW4gaGFjZXIgYmFzdGFudGVzIG3DoXMgY29zYXMsIHBvciBlamVtcGxvLCBwYXJhIGhhY2VyIGNvcm9wbGV0YXMgY29uIGxlYWZsZXQgcHVlZGVzIGlyIFthcXXDrV0oaHR0cHM6Ly9yc3R1ZGlvLmdpdGh1Yi5pby9sZWFmbGV0L2Nob3JvcGxldGhzLmh0bWwpLiBPdHJvIGVqZW1wbG8gZGUgYW7DoWxpc2lzIGNvbiBsZWFmbGV0IFthcXXDrV0oaHR0cHM6Ly93d3cuamxhLWRhdGEubmV0L2VuZy9sZWFmbGV0LWluLXItdGlwcy1hbmQtdHJpY2tzL2luZGV4Lmh0bWwpLgoKCgo8YnI+CgotLS0tLS0tLS0tLS0tLS0KCjxicj4KCgojIDcuIE9wZXJhY2lvbmVzIGdlb23DqXRyaWNhcwoKCkxhcyBvcGVyYWNpb25lcyBnZW9tw6l0cmljYXMgcXVlIHBvZGVtb3MgaGFjZXIgY29uIGBzZmAgY29uIGRhdG9zIGVzcGFjaWFsZXMgdmVjdG9yaWFsZXMgc2UgcHVlZGVuIGRpdmlkaXIgZW4gdHJlcyBncnVwb3M6CgotIDEuIGZ1bmNpb25lcyBxdWUgZGV2dWVsdmVuIHVuIHZhbG9yIHF1ZSByZXN1bWUgYWxndW5hIHByb3BpZWRhZCBkZSBsYSBnZW9tZXRyw61hOiAgCgogICAgICAtICBgc3RfYXJlYSgpYCAgICA6IGNhbGN1bGEgZWwgYXJlYSAKICAgICAgLSAgYHN0X2xlbmdodCgpYCAgOiBjYWxjdWxhIGVsIHBlcsOtbWV0cm8gICAKICAgICAgLSAgYHN0X2Rpc3RhbmNlKClgOiBjYWxjdWxhIGxhIGRpc3RhbmNpYSAoZW50cmUgZG9zIGdlb21ldHLDrWFzKSAgIAogICAgCi0gMi4gZnVuY2lvbmVzIHF1ZSBjaGVxdWVhbiBzaSBzZSBjdW1wbGUgdW5hIGNvbmRpY2nDs246ICAKCiAgICAgIC0gIEVuIDEgZ2VvbWV0csOtYTogYHN0X2lzX3ZhbGlkKClgICAgIDogY2hlcXVlYSBzaSBsYSBnZW9tZXRyaWEgZXMgdsOhbGlkYSAgIAogICAgICAKICAgICAKICAgICAgLSBFbiAyIGdlb21ldHLDrWFzOiBwb3IgZWplbXBsbywgYHN0X2ludGVyc2VjdHMoQSwgQilgIGNoZXF1ZWEgc2kgZG9zIGdlb21ldHLDrWFzIHNlIGludGVyc2VjdGFuLiBPdHJhcyBmdW5jaW9uZXM6IGBzdF9kaXNqb2ludCgpYCwgYHN0X3RvdWNoZXMoKWAsIGBzdF9jcm9zc2VzKClgLCBgc3Rfd2l0aGluKClgLCBgc3RfY29udGFpbnMoKWAsIGBzdF9vdmVybGFwcygpYCwgYHN0X2NvdmVycygpYCwgYHN0X2VxdWFscygpYCwgYHN0X2lzX3dpdGhpbl9kaXN0YW5jZSgpYCwgeSBhbGd1bmEgbcOhcwogICAgCiAgICAKLSAzLiBmdW5jaW9uZXMgcXVlIGNyZWFuIHVuYSAgbnVldmEgZ2VvbWV0csOtYTogIAoKICAgICAgLSBFbiAxIGdlb23DqXRyaWE6IHBvciBlamVtcGxvLCBgc3RfY2VudHJvaWQoKWA6IGNhbGN1bGEgZWwgY2VudHJvaWRlLiBPdHJhcyBmdW5jaW9uZXM6IGBzdF9idWZmZXIoKWAsIGBzdF9ib3VuZGFyeSgpYCAsIGBzdF9jb252ZXhfaHVsbCgpYCwgYHN0X3Zvcm9ub2koKWAKICAgICAgCiAgICAgIAogICAgICAtIEVuIDIgZ2VvbcOpdHJpYXM6IHBvciBlamVtcGxvLCBgc3RfaW50ZXJzZWN0aW9uKClgIGNhbGN1bGEgbGEgaW50ZXJzZWNjaW9uLiBPdHJhcyBmdW5jaW9uZXM6IGBzdF9jb21iaW5lKClgLCBgc3RfdW5pb24oKWAsIGBzdF9kaWZmZXJlbmNlKClgCgo8YnI+CgojIyAgNy4xICBPcGVyYWNpb25lcyBnZW9tw6l0cmljYXMgKEVqZW1wbG9zKQoKCiMjIyBMYXMgNSBwcm92aW5jaWFzIG3DoXMgZ3JhbmRlcwoKVmFtb3MgYSBjb2xvcmVhciBsYXMgNSBwcm92aW5jaWFzIGVzcGHDsW9sYXMgY29uIG3DoXMgdGVycml0b3JpbywgcGFyYSBlbGxvIHRlbmVtb3MgcXVlIGNhbGN1bGFyIGVsIMOhcmVhIGNvbiBgc2Y6OnN0X2FyZWEoKWA6CgpgYGB7cn0KUHJvdmljaWFzXzUgPC0gUHJvdmluY2lhcyAlPiUgCiAgICAgICAgICAgICAgIG11dGF0ZShhcmVhID0gc3RfYXJlYSguKSkgJT4lIAogICAgICAgICAgICAgICBtdXRhdGUoYXJlYWttMiA9IHVuaXRzOjpzZXRfdW5pdHMoYXJlYSwga21eMikpICU+JSAKICAgICAgICAgICAgICAgYXJyYW5nZShkZXNjKGFyZWFrbTIpKSAlPiUgCiAgICAgICAgICAgICAgIHNsaWNlKDE6NSkKZ2dwbG90KCkgKyBnZW9tX3NmKGRhdGEgPSBQcm92aW5jaWFzKSArIGdlb21fc2YoZGF0YSA9IFByb3ZpY2lhc181LCBmaWxsID0gInB1cnBsZSIpCmBgYAoKQmllbiwgcGVybyBtZWpvciBzaSBwb25lbW9zIGVsIG5vbWJyZSBkZSBsYXMgcHJvdmluY2lhcwoKYGBge3J9ClByb3ZfNV9jZW50cm9pZGVzIDwtIHN0X2NlbnRyb2lkKFByb3ZpY2lhc181KSAKUHJvdl81X2NlbnRyb2lkZXMgPC0gIGNiaW5kKFByb3ZfNV9jZW50cm9pZGVzLCBzdF9jb29yZGluYXRlcyhzdF9jZW50cm9pZChQcm92XzVfY2VudHJvaWRlcyRnZW9tZXRyeSkpKQoKZ2dwbG90KCkgKyBnZW9tX3NmKGRhdGEgPSBQcm92aW5jaWFzKSArIAogICAgICAgICAgIGdlb21fc2YoZGF0YSA9IFByb3ZpY2lhc181LCBmaWxsID0gInB1cnBsZSIpICsKICAgICAgICAgICBnZW9tX3RleHQoZGF0YSA9IFByb3ZfNV9jZW50cm9pZGVzLCAKICAgICAgICAgICAgICAgICAgICAgYWVzKHggPSBYLCB5ID0gWSwgbGFiZWwgPSBpbmVfcHJvdi5uKSwKICAgICAgICAgICAgICAgICAgICAgY29sb3IgPSAiZGFya2JsdWUiLCBmb250ZmFjZSA9ICJib2xkIiwgY2hlY2tfb3ZlcmxhcCA9IFRSVUUsIHNpemUgPSAyLjUpICsKICAgICAgICAgIGxhYnModGl0bGUgPSAiTGFzIDUgcHJvdmluY2lhcyBlc3Bhw7FvbGFzIG3DoXMgZXh0ZW5zYXMiLCB4ID0gIiIsIHkgPSAiIikgCmBgYAoKCgojIyMgRGlzdGFuY2lhcyAoYSBtaSBwdWVibG8pCgpDYWxjdWxlbW9zIGxhcyBkaXN0YW5jaWFzIGVudHJlIHB1bnRvcywgcG9yIGVqZW1wbG8gZW50cmUgbG9zIGNlbnRyb2lkZXMgZGUgbGFzIDUyIHByb3ZpbmNpYXMgZXNwYcOxb2xhczoKCgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpQcm92X2NlbnRyb2lkZXMgPC0gc3RfY2VudHJvaWQoUHJvdmluY2lhcykgClByb3ZfY2VudHJvaWRlc19YWSA8LSBjYmluZChQcm92X2NlbnRyb2lkZXMsIHN0X2Nvb3JkaW5hdGVzKHN0X2NlbnRyb2lkKFByb3ZfY2VudHJvaWRlcyRnZW9tZXRyeSkpKQpkaXN0YW5jaWFzIDwtIHN0X2Rpc3RhbmNlKFByb3ZfY2VudHJvaWRlc19YWSwgYnlfZWxlbWVudCA9IEZBTFNFKSAjLSBkZXZ1ZWx2ZSB1bmEgbWF0cml6CmBgYAoKClF1aWVybyB2ZXIgcXVlIG11bmljaXBpbyBlc3TDoSBtYXMgbGVqb3MgZGUgbWkgcHVlYmxvLiBDYXJnbyBkYXRvcyBkZSBsb3MgbXVuaWNpcGlvcyBjb24gbGF0aXR1ZC1sb25naXR1ZCBlbiBgRVRSUzg5YDoKCgpgYGB7cn0KbXVuaWNpcGlvcyA8LSBJR05fbm9tZW5jbGFfbXVuaQptdW5pY2lwaW9zIDwtIG11bmljaXBpb3MgJT4lIHNlbGVjdChDT0RfSU5FLCBQUk9WSU5DSUEsIE5PTUJSRV9BQ1RVQUwsIExPTkdJVFVEX0VUUlM4OSwgTEFUSVRVRF9FVFJTODkpCmBgYAoKVGVuZ28gcXVlIGNvbnZlcnRpciBlbCBkZiBhIHVuIHNwYXRpYWwtZGYsIHkgdGVuZ28gcXVlIHRlbmVyIGN1aWRhZG8gY29uIGVsIENSUyBxdWUgbGUgcG9uZ28uIFtBcXXDrV0oaHR0cHM6Ly9zcGF0aWFscmVmZXJlbmNlLm9yZy9yZWYvZXBzZy80MjU4LykgdmkgcXVlIEVUUlM4OSBlcyBlcXVpdmFsZW50ZSBhIGBFUFNHIFByb2plY3Rpb24gNDI1OGAsIGFzw60gcXVlLCBlc3Blcm8gaGFiZXJsbyBoZWNobyBiaWVuLCB5YSB2b3JlbToKCgpgYGB7cn0KbXVuaWNpcGlvc19zZiA8LSBzdF9hc19zZihtdW5pY2lwaW9zLCBjb29yZHMgPSBjKCJMT05HSVRVRF9FVFJTODkiLCAiTEFUSVRVRF9FVFJTODkiKSwgY3JzID0gc3RfY3JzKDQyNTgpKQpgYGAKClZhbW9zIGEgdmVyIHF1ZSBmaWxhIG9jdXBhIG1pIHB1ZWJsby4gT2N1cGEgbGEgZmlsYSBuwrogNjY5NCwgYXPDrSBxdWUgdm95IGEgY3JlYXIgdW4gZGYgc8OzbG8gY29uIG1pIHB1ZWJsbyB5IGx1ZWdvIGNhbGN1bGFyIGxhcyBkaXN0YW5jaWFzIGNvbiBgZnM6OnN0X2Rpc3RhbmNlKClgOgoKCmBgYHtyfQpwYW5jcnVkb19zZiA8LSBtdW5pY2lwaW9zX3NmICU+JSBzbGljZSg2Njk0KQpnMSA8LSBzdF9nZW9tZXRyeShtdW5pY2lwaW9zX3NmKQpnMV9wYW5jcnVkbyA8LSBzdF9nZW9tZXRyeShwYW5jcnVkb19zZikgIy0gZGVqbyBzb2xvIGxhIGdlb21ldHJpYQoKIy0gZGlzdGFuY2lhc19hX1BhbmNydWRvIDwtIG1hcHBseShzdF9kaXN0YW5jZSwgZzEsIGcxX3BhbmNydWRvKSAlPiUgYXMudmVjdG9yKSAjLSBjb24gUi1iYXNlCmRpc3RhbmNpYXNfYV9QYW5jcnVkbyA8LSBtYXAyKGcxLCBnMV9wYW5jcnVkbywgc3RfZGlzdGFuY2UpICU+JSBhc192ZWN0b3IoKSAgICAgICAgICMtIGNvbiBwdXJycgpkaXN0YW5jaWFzX2FfUGFuY3J1ZG8gPC0gYXMuZGF0YS5mcmFtZShkaXN0YW5jaWFzX2FfUGFuY3J1ZG8pCmRmX2Nvbl9kaXN0YW5jaWFzX2FfUGFuY3J1ZG8gPC0gYmluZF9jb2xzKG11bmljaXBpb3Nfc2YsIGRpc3RhbmNpYXNfYV9QYW5jcnVkbykgJT4lIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG11dGF0ZShLbXMgPSBkaXN0YW5jaWFzX2FfUGFuY3J1ZG8gKiAxMDApICU+JSAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhcnJhbmdlKEttcykKYGBgIAoKRWwgcHVlYmxvIG3DoXMgY2VyY2FubyBhIFBhbmNydWRvIGVzIGByIGRmX2Nvbl9kaXN0YW5jaWFzX2FfUGFuY3J1ZG8kTk9NQlJFX0FDVFVBTFsyXWAgcXVlIGVzdMOhIGEgYHIgZGZfY29uX2Rpc3RhbmNpYXNfYV9QYW5jcnVkbyRLbXNbMl1gLCBlc28gc2ksIGVuIGxpbmVhIHJlY3RhLiBQb3IgbGEgY2FycmV0ZXJhIGhheSB1biBwb2NvIG3DoXMsIHNlZ8O6biBHb29nbGUgTWFwcyA3LDEga2lsw7NtZXRyb3MuIEEgdmVyIHNpIHBvZGVtb3MgY2FsY3VsYXJsbyBsdWVnbyBjb24gUiB1c2FuZG8gT1NNLgoKRWwgcHVlYmxvIG3DoXMgbGVqYW5vIGEgUGFuY3J1ZG8sIHNpIGNvbnNpZGVyYW1vcyBzw7NsbyBsYSBwZW7DrW5zdWxhIGVzIE11eMOtYSBlbiBsYSBwcm92aW5jaWEgZGUgQSBDb3J1w7FhLCBxdWUgZXN0w6EgYSA4NTEsNjUga2lsw7NtZXRyb3MgKGVuIGxpbmVhIHJlY3RhKS4gRW4gY29jaGUsIHNlZ8O6biBHb29nbGUgTWFwcywgOTI0IEttcy4KCjxicj4KCgojIyMgTXVuaWNpcGlvcyBwb3IgbG9zIHF1ZSBwYXNhIGVsIHLDrW8gRWJybwoKClB1ZXMgZXNvLCBxdWUgbWUgYXBldGVjZSBzYWJlciBwb3IgZG9uZGUgcGFzYSBlbCBFYnJvIHkgZWwgUGFuY3J1ZG8uCgoxLiBQcmltZXJvIGNhcmdvIGdlb21ldHLDrWFzIGRlIGxvcyBtdW5pY2lwaW9zIGVzcGHDsW9sZXMuIExhcyBsaW5kZXMgbXVuaWNpcGFsZXMgcHJvdmllbmVuIGRlbCBwYXF1ZXRlIFtgTEFVMmJvdW5kYXJpZXM0c3BhaW5gXShodHRwczovL2dpdGh1Yi5jb20vcGVyZXpwNDQvTEFVMmJvdW5kYXJpZXM0c3BhaW4pCgpgYGB7cn0KbXVuaV94eCA8LSBtdW5pY2lwaW9zXzIwMjAgJT4lIHN0X3NldF9nZW9tZXRyeShOVUxMKQptdW5pICAgIDwtIG11bmljaXBpb3NfMjAyMApgYGAKCgpWYW1vcyBhIHF1ZWRhcm5vcyBzb2xvIGNvbiBsYSBFc3Bhw7FhIHBlbmluc3VsYXI6CgpgYGB7cn0KI3p6IDwtIG11bmlfeHggJT4lIGdyb3VwX2J5KGluZV9jY2FhLCBpbmVfY2NhYS5uKSAlPiUgY291bnQoKQptdW5pX2JvcnJhciA8LSBjKCJJbGxlcyBCYWxlYXJzIiwgIkNhbmFyaWFzIikKbXVuaSA8LSBtdW5pICU+JSBmaWx0ZXIoIShpbmVfY2NhYS5uICVpbiUgbXVuaV9ib3JyYXIpKQpgYGAKCgoyLiBBaG9yYSBsb3MgZGF0b3MgZGUgcsOtb3MgUGFuY3J1ZG8sIEppbG9jYSwgSmFsw7NuIHkgRWJybwoKCmBgYHtyLCBldmFsID0gRkFMU0UsIGVjaG8gPSBGQUxTRX0KIy0gYXNpIGNyZWUgcmlvc19lc3BhbnlhIHkgcmlvX2Vicm9fNAojcmlvcyA8LSBzdF9yZWFkKGhlcmU6OmhlcmUoImRhdG9zIiwgIm1hcGFzIiwgIlJpb3MiLCAiTV9SaW9zX3YyLnNocCIpKQojc2F2ZVJEUyhyaW9zLCAicmlvc19lc3BhbnlhLnJkcyIpICMtIGxvIGd1YXJkZQpjb2Rfcmlvc19ndWVub3MgPC0gYyg5MTM3NTcsIDkxMzY4NywgOTEzNjg1LCA5MTM2MTkpCm5vbWJyZV9yaW9zX2d1ZW5vcyA8LSBjKCJQYW5jcnVkbyIsICJKaWxvY2EiLCAiSmFsw7NuIiwgIkVicm8iKQpyaW9fZWJyb180IDwtIHJpb3MgJT4lIGZpbHRlcihDb2RfVW5pICVpbiUgY29kX3Jpb3NfZ3Vlbm9zKQpjbGFzcyhyaW9fZWJyb180KQojc2F2ZVJEUyhyaW9fZWJyb180LCAicmlvX2Vicm9fNC5yZHMiKQpgYGAKCgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpyaW9fZWJyb180IDwtIHJlYWRfcmRzKGhlcmU6OmhlcmUoImRhdG9zIiwgICJtYXBhcyIsICJSaW9zIiwgInJpb19lYnJvLnJkcyIpKQpgYGAKClZhbW9zIGEgcG9uZXIgdG9kb3MgbG9zIHRyYW1vcyBkZSBsb3MgNCByw61vcyBqdW50b3MuIENvbiBgc3VtbWFyaXNlKClgCgpgYGB7cn0KcmlvX2Vicm9fNF94eCA8LSByaW9fZWJyb180ICU+JSBzdF9zZXRfZ2VvbWV0cnkoTlVMTCkKcmlvX2Vicm9fNF94IDwtIHJpb19lYnJvXzQgJT4lIHNlbGVjdChOb21fUmlvLCBMb25nX1Jpb19tLCBnZW9tZXRyeSkgJT4lIGdyb3VwX2J5KE5vbV9SaW8pICU+JSBzdW1tYXJpemUoKQpgYGAKCgpIYWdhbW9zIHlhIHVuIG1hcGEgZGUgbG9zIGxpbmRlcyBtdW5pY2lwYWxlcywgbG9zIHLDrW9zIHkgdm95IGEgc2l0dWFyIFBhbmNydWRvIHkgVmFsZW5jaWE6CgpgYGB7ciwgZWNobyA9IEZBTFNFfQpwdWVibG9zICA8LSBkYXRhLmZyYW1lKG5vbWJyZSA9IGMoIlBhbmNydWRvIiwgIlZhbGVuY2lhIiksIGxvbmdpdHVkZSA9IGMoLTEuMDI4NzgxLCAtMC4zNzU2NTcyKSwgbGF0aXR1ZGUgPSBjKDQwLjc2MTc1LCAzOS40NzUzNCkgKSAjLSBFVFJTODkKYGBgCgoKCmBgYHtyfQp0aGVtZV9zZXQoY293cGxvdDo6dGhlbWVfbWFwKCkpCnAgPC0gZ2dwbG90KG11bmkpICsgCiAgICAgZ2VvbV9zZihjb2xvciA9ICJncmV5IiwgZmlsbCA9ICJ3aGl0ZSIsIGx3ZCA9IDAuMDEpICsKICAgICBnZW9tX3NmKGRhdGEgPSByaW9fZWJyb180X3gsIGNvbG9yID0gImxpZ2h0Ymx1ZSIsIGx3ZCA9IDEuNikgKwogICAgIGdlb21fcG9pbnQoZGF0YSA9IHB1ZWJsb3MsIGFlcyh4ID0gbG9uZ2l0dWRlLCB5ID0gbGF0aXR1ZGUpLCBzaXplID0gMywgY29sb3IgPSAiZGFya3JlZCIpICsKICAgICBnZW9tX3RleHQoZGF0YSAgPSBwdWVibG9zLCBhZXMoeCA9IGxvbmdpdHVkZSwgeSA9IGxhdGl0dWRlLCBsYWJlbCA9IG5vbWJyZSksIGNvbG9yID0gImRhcmtyZWQiLAogICAgICAgICAgICAgICAgICAgIGZvbnRmYWNlID0gImJvbGQiLCBjaGVja19vdmVybGFwID0gVFJVRSwgc2l6ZSA9IDIuNSkgKwogICAgIGxhYnModGl0bGUgPSAiUi1NYXBhIGNvbiA0IHLDrW9zIGfDvGVub3MiLAogICAgICAgICAgY2FwdGlvbiA9ICJEYXRvcyBwcm92ZW5pZW50ZXMgZGVsIHBhcXVldGUgTEFVMmJvdW5kYXJpZXM0c3BhaW4iKSArICAKICAgICAgICAgIHRoZW1lKHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJhbGljZWJsdWUiKSkKcApgYGAKClZlbmdhLCBjYWxjdWxhbW9zIGxhcyBpbnRlcnNlY2Npb25lcyBjb24gYHNmOjpzdF9pbnRlcnNlY3RzKClgIHBlcm8gYW50ZXMgdGVuZW1vcyBxdWUgcG9uZXIgbG9zIDIgZGZzIGVuIGVsIG1pc21vIENSUzoKCmBgYHtyLCBldmFsID0gVFJVRX0KbXVuaV9hIDwtIG11bmkgJT4lIHN0X3RyYW5zZm9ybShjcnMgPSBzdF9jcnMocmlvX2Vicm9fNF94KSkKenpfYSA8LSBzdF9pbnRlcnNlY3RzKG11bmlfYSwgcmlvX2Vicm9fNF94LCBzcGFyc2UgPSBGQUxTRSwgcHJlcGFyZWQgPSBUUlVFKQpgYGAKCkFob3JhIHlhIHNpIHNlbGVjY2lvbmFtb3MgYXF1ZWxsb3MgbXVuaWNpcGlvcyBwb3IgbG9zIHF1ZSBwYXNhIGVsIEVicm8uIExvIGhhZ28gY29uIFItYmFzZToKCmBgYHtyLCBldmFsID0gVFJVRX0KbXVuaV9lYnJvIDwtIG11bmlfYVt6el9hLF0KYGBgCgoKUGFyZWNlIHF1ZSBoYXkgYHIgbnJvdyhtdW5pX2Vicm8pYCBtdW5pY2lwaW9zIHBvciBsb3EgZXUgcGFzYSBlbCBFYnJvLiBBIHZlciBzaSBub3Mgc2FsZSBlbCBtYXBhCgpgYGB7cn0KcCArIGdlb21fc2YoZGF0YSA9IG11bmlfZWJybykgKwogICAgZ2VvbV9zZihkYXRhID0gcmlvX2Vicm9fNF94LCBjb2xvciA9ICJsaWdodGJsdWUiLCBsd2QgPSAwLjI1KQpgYGAKCk5vLCBubyB2b3kgYSBvYnRlbmVyIGxvcyBtdW5pY2lwaW9zIHBvciBsb3MgcXVlIHBhc2EgZWwgcmlvIFBhbmNydWRvLiBFc28gZXN0w6EgTUFMISEhIQoKPGJyPgogIAotLS0tLS0tLS0tLS0tCiAgCjxicj4KCgojIEJpYmxpbwoKLSBbR2VvY29tcHV0YXRpb24gd2l0aCBSXShodHRwczovL2dlb2NvbXByLnJvYmlubG92ZWxhY2UubmV0L2luZGV4Lmh0bWwpLiBkZSBSLiBMb3ZlbGFjZSB5IG90cm9zLiBBIGJvb2tkb3duIG9uIGdlb2dyYXBoaWMgZGF0YSBhbmFseXNpcywgdmlzdWFsaXphdGlvbiBhbmQgbW9kZWxpbmcgd2l0aCBSLiAKCi0gW0ludHJvIHRvIEdJUyBhbmQgU3BhdGlhbCBBbmFseXNpc10oaHR0cHM6Ly9tZ2ltb25kLmdpdGh1Yi5pby9TcGF0aWFsL2luZGV4Lmh0bWwpIGRlIE0uIEdpbW9uZC4gQnVlbiBsaWJybwoKLSBbSW50cm9kdWN0aW9uIHRvIEdJUyB3aXRoIFJdKGh0dHBzOi8vd3d3Lmplc3Nlc2FkbGVyLmNvbS9wb3N0L2dpcy13aXRoLXItaW50cm8vP3V0bV9jb250ZW50PWJ1ZmZlcjA3MmFlJnV0bV9tZWRpdW09c29jaWFsJnV0bV9zb3VyY2U9dHdpdHRlci5jb20mdXRtX2NhbXBhaWduPWJ1ZmZlcikgZGUgSmVzc2UgU2FkbGVyLCBzZWfDum4gW01pbGVzIE1jQmFpbl0oaHR0cHM6Ly90d2l0dGVyLmNvbS9NaWxlc01jQmFpbikgdW5hIGRlIGxhcyBtZWpvcmVzIGludHJvZHVjY2lvbmVzIGEgR0lTIGVuIFIuCgoKLSBbVmnDsWV0YXMgZGVsIHBhcXVldGUgYHNmYF0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3NmLykKCgotIFtDb29yZGluYXRlIFJlZmVyZW5jZSBTeXN0ZW1zXShodHRwczovL2RhdGFjYXJwZW50cnkub3JnL29yZ2FuaXphdGlvbi1nZW9zcGF0aWFsLzAzLWNycy9pbmRleC5odG1sKS4gVHV0b3JpYWwgZGUgRGF0YSBDYXJwZW50cnkgc29icmUgQ1JTLgoKCi0gW0ludHJvZHVjdGlvbiB0byBHZW9zcGF0aWFsIENvbmNlcHRzXShodHRwczovL2RhdGFjYXJwZW50cnkub3JnL29yZ2FuaXphdGlvbi1nZW9zcGF0aWFsLykgZGUgRGF0YSBDYXJwZW50cnkuCgoKLSBVbmEgc2VyaWUgZGUgcG9zdHMgc29icmU6RHJhd2luZyBiZWF1dGlmdWwgbWFwcyBwcm9ncmFtbWF0aWNhbGx5IHdpdGggUiwgc2YgYW5kIGdncGxvdDIuIFtCYXNpY3NdKGh0dHBzOi8vd3d3LnItc3BhdGlhbC5vcmcvL3IvMjAxOC8xMC8yNS9nZ3Bsb3QyLXNmLmh0bWwpLFtMYXllcnNdKGh0dHBzOi8vd3d3LnItc3BhdGlhbC5vcmcvL3IvMjAxOC8xMC8yNS9nZ3Bsb3QyLXNmLTIuaHRtbCkgeSBbTGF5b3V0c10oaHR0cHM6Ly93d3cuci1zcGF0aWFsLm9yZy8vci8yMDE4LzEwLzI1L2dncGxvdDItc2YtMy5odG1sKQoKLSBVbiBlamVtcGxvIGVuIGVsIHF1ZSBzZSB2YSBjb25zdHJ1eWVuZG8gbGEgW1dpbmtlbCB0cmlwZWwgcHJvamVjdGlvbl0oaHR0cHM6Ly93aWxrZWxhYi5vcmcvcHJhY3RpY2FsZ2cvYXJ0aWNsZXMvV2lua2VsX3RyaXBlbC5odG1sKQoKLSBVbiBbcG9zdF0oaHR0cHM6Ly93d3cuamxhLWRhdGEubmV0L2VuZy9sZWFmbGV0LWluLXItdGlwcy1hbmQtdHJpY2tzL2luZGV4Lmh0bWwpIHNvYnJlIGxhIHV0aWxpemFjacOzbiBkZSBsZWFmbGV0CgotIFBhcXVldGUgW2BvcGVucm91dGVzZXJ2aWNlYF0oaHR0cHM6Ly9naXNjaWVuY2UuZ2l0aHViLmlvL29wZW5yb3V0ZXNlcnZpY2Utci9hcnRpY2xlcy9vcGVucm91dGVzZXJ2aWNlLmh0bWwpIHBhcmEgY2FsY3VsYXIgcnV0YXMgY29uIFtvcGVucm91dGVzZXJ2aWNlXShodHRwczovL29wZW5yb3V0ZXNlcnZpY2Uub3JnLykuCgotIFtTaXN0ZW1hcyBkZSBJbmZvcm1hY2nDs24gR2VvZ3LDoWZpY2FdKGh0dHBzOi8vdm9sYXlhLmdpdGh1Yi5pby9saWJyby1zaWcvaW5kZXguaHRtbCkuIFVuIGxpYnJvIGxpYnJlIGRlIFbDrWN0b3IgT2xheWEKCjxicj48YnI+Cgo=