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, de la que, con los escasos conocimientos que tengo, voy a hacer una pequeña introducción a GIS con R.
Durante este tutorial usaremos estos conjuntos de datos
CCAA <- rio::import("https://github.com/perezp44/LAU2boundaries4spain/blob/master/data/CCAA.rda?raw=true")
Provincias <- rio::import("https://github.com/perezp44/LAU2boundaries4spain/blob/master/data/Provincias.rda?raw=true")
municipios_2017 <- rio::import("https://github.com/perezp44/LAU2boundaries4spain/blob/master/data/municipios_2017.rda?raw=true")
world <- rnaturalearth::ne_countries(scale = "medium", returnclass = "sf")
rios <- rio::import("https://github.com/perezp44/archivos_download/blob/master/rio_ebro.rds?raw=true")
IGN_nomencla_muni <- rio::import("https://github.com/perezp44/archivos_download/blob/master/IGN_nomencla_muni.rds?raw=true")
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:
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 = INECodCCAA) ) +
geom_sf() +
scale_fill_viridis_d(guide = FALSE) +
labs(title = "Coord. geográficas")
p2 <- ggplot(CCAA, aes(fill = INECodCCAA) ) +
geom_sf() + coord_sf(crs = 2154, datum = sf::st_crs(2154)) +
scale_fill_viridis_d(guide = FALSE) +
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 espaciales. 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.8.1, GDAL 3.1.3, PROJ 7.1.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
se está convirtiendo en el estándar.
Las principales razones son 4:
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.
sf
utiliza para contener/almacenar los datos espaciales una estructura muy similar a un dataframe: los spatial dataframes o sf
, 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…
Muchas de las funciones del paquete sf
comienzan con st_
; por ejemplo st_nearest_points()
, lo que facilita su uso.
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(NombreCCAA %in% zz_prov_chosen)
zz_prov_2 <- Provincias %>% filter(!(NombreCCAA %in% zz_prov_chosen))
p1 <- ggplot(zz_prov_1, aes(fill = INECodProv) ) +
geom_sf() +
scale_fill_viridis_d(guide = FALSE) +
labs(title = "Coord. geográficas")
p2 <- ggplot(zz_prov_2, aes(fill = INECodProv) ) +
geom_sf() +
scale_fill_viridis_d(guide = FALSE) +
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(INECodCCAA, NombreCCAA) %>% summarize()
p1 <- ggplot(Provincias, aes(fill = INECodProv) ) +
geom_sf() +
scale_fill_viridis_d(guide = FALSE) +
labs(title = "Coord. geográficas")
p2 <- ggplot(CCAA_2, aes(fill = INECodCCAA) ) +
geom_sf() +
scale_fill_discrete(guide = FALSE) +
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.
#world <- rnaturalearth::ne_countries(scale = "medium", returnclass = "sf")
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] "scalerank" "featurecla" "labelrank" "sovereignt" "sov_a3"
#> [6] "adm0_dif" "level" "type" "admin" "adm0_a3"
#> [11] "geou_dif" "geounit" "gu_a3" "su_dif" "subunit"
#> [16] "su_a3" "brk_diff" "name" "name_long" "brk_a3"
#> [21] "brk_name" "brk_group" "abbrev" "postal" "formal_en"
#> [26] "formal_fr" "note_adm0" "note_brk" "name_sort" "name_alt"
#> [31] "mapcolor7" "mapcolor8" "mapcolor9" "mapcolor13" "pop_est"
#> [36] "gdp_md_est" "pop_year" "lastcensus" "gdp_year" "economy"
#> [41] "income_grp" "wikipedia" "fips_10" "iso_a2" "iso_a3"
#> [46] "iso_n3" "un_a3" "wb_a2" "wb_a3" "woe_id"
#> [51] "adm0_a3_is" "adm0_a3_us" "adm0_a3_un" "adm0_a3_wb" "continent"
#> [56] "region_un" "subregion" "region_wb" "name_len" "long_len"
#> [61] "abbrev_len" "tiny" "homepart" "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 rnaturalearth")
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:
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)
usando un código SRID (Spatial Reference System Identifier)
Usando un código EPGS (European Petroleum Survey Group)
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 = FALSE)
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)
Fíjate en el warning: Scale on map varies by more than 10%, scale bar may be inaccurate
. Como el mapa usa un CRS no proyectado, sino geográfico, los datos están en longitud/latitud (WGS84) en una proyección cilíndrica equidistante (todos los meridianos son paralelos), entonces, la distancia en kilómetros en el mapa depende de la latitud. Los mapas de regiones pequeñas generalmente proporcionan barras de escala más precisas que las regiones extensas como la que vemos en el gráfico anterior.
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) #- sustituye la geometry por el centroide
world_points <- cbind(world, st_coordinates(st_centroid(world$geometry))) #- ahora el centroide pasa a estar en 2 columnas, llamadas X e Y, donde figuran la longitud y latitud del centroide
Para calcular los centroides hemos usado la función st_centroid()
. Los datos del centroide (longitud, latitud) se almacenan una nueva columna llamada geometry
; pero al fusionar los dos data.frame’s, el centroide pasa a estar en dos nuevas columnas llamadas X e Y.
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] "scalerank" "featurecla" "labelrank" "sovereignt" "sov_a3"
#> [6] "adm0_dif" "level" "type" "admin" "adm0_a3"
#> [11] "geou_dif" "geounit" "gu_a3" "su_dif" "subunit"
#> [16] "su_a3" "brk_diff" "name" "name_long" "brk_a3"
#> [21] "brk_name" "brk_group" "abbrev" "postal" "formal_en"
#> [26] "formal_fr" "note_adm0" "note_brk" "name_sort" "name_alt"
#> [31] "mapcolor7" "mapcolor8" "mapcolor9" "mapcolor13" "pop_est"
#> [36] "gdp_md_est" "pop_year" "lastcensus" "gdp_year" "economy"
#> [41] "income_grp" "wikipedia" "fips_10" "iso_a2" "iso_a3"
#> [46] "iso_n3" "un_a3" "wb_a2" "wb_a3" "woe_id"
#> [51] "adm0_a3_is" "adm0_a3_us" "adm0_a3_un" "adm0_a3_wb" "continent"
#> [56] "region_un" "subregion" "region_wb" "name_len" "long_len"
#> [61] "abbrev_len" "tiny" "homepart" "X" "Y"
#> [66] "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)
El nombre de Francia no ha salido bien. Lo hemos situado en España, es por sus territorios en América: Guadalupe, Martinica, … Para arreglarlo podríamos usar st_point_on_surface()
pero será otro día. Hoy vamos a arreglarlo a lo bruto, vamos a quitar a Frnacia del data.frame world_points y a correr.
world_points <- world_points %>% filter(admin != "France")
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 = FALSE) +
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
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 = FALSE)
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 = FALSE)
Ahora añadimos las provincias de Andalucía, PERO coloreadas según su población media por municipio. Para ello:
- primero cargamos datos de población
INE_padron_muni_96_19 <- pjpv2020.01::pjp_data_pob_mun_1996_2019
pob_andalucia <- INE_padron_muni_96_19 %>%
filter(year == 2017) %>%
filter(poblacion == "Total") %>%
filter(INECodCCAA.n == "Andalucía") %>%
group_by(INECodProv.n) %>% mutate(n_pueblos = n_distinct(INECodMuni.n)) %>%
mutate(pob_prov = sum(pob_values, na.rm = TRUE)) %>% ungroup() %>%
mutate(pob_media_pueblos = pob_prov/n_pueblos) %>%
select(INECodProv.n, INECodProv, pob_media_pueblos, pob_prov, n_pueblos) %>%
distinct() %>% ungroup()
- después cargamos lindes provinciales (en realidad ya los tenemos)
# Provincias <- Provincias
zz_Provincias <- Provincias %>% st_set_geometry(NULL)
- fusionamos los datos de población y los lines provinciales:
geo_pob_andalucia <- left_join(pob_andalucia, Provincias, by = c("INECodProv" = "INECodProv"))
geo_pob_andalucia <- left_join(pob_andalucia, Provincias)
- 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 = FALSE)
- 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 = FALSE)
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(INECodProv %in% c(35,38))
peninsula <- Provincias %>% filter( !(INECodProv %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 = FALSE)
p1 + p2
ggplot() + geom_sf(data = peninsula) +
geom_sf(data = canarias) +
coord_sf(xlim = c(-12.00, 4.00), ylim = c(33, 44), expand = FALSE)
- 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 = FALSE)
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 %>% st_set_geometry(NULL)
names(rios)
#> [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 %>% 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
gworld <- ggplot(data = world) +
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 = FALSE)
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 = FALSE) +
theme(panel.background = element_rect(fill = "azure")) +
labs(subtitle = "Mapa de Europa Occidental") +
scale_fill_continuous(guide = FALSE)
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_19 <- pjpv2020.01::pjp_data_pob_mun_1996_2019
info_CCAAs <- INE_padron_muni_96_19 %>%
filter(year == 2017) %>%
filter(poblacion == "Total") %>%
group_by(INECodCCAA.n) %>%
mutate(n_prov = n_distinct(INECodProv)) %>%
mutate(n_pueblos = n_distinct(INECodMuni)) %>%
mutate(pob_CCAA = sum(pob_values, na.rm = TRUE)) %>% ungroup() %>%
select(INECodCCAA, INECodCCAA.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, by = c("INECodCCAA" = "INECodCCAA"))
Y vamos a graficarlo ya con mapview
:
mapview::mapview(CCAA_con_info,
zcol = c("NombreCCAA", "n_prov", "n_pueblos", "pob_CCAA"),
color = "tomato", col.regions = "purple", lwd = 3,
cex = "n_pueblos")
mapview::mapview(CCAA_con_info,
zcol = c("NombreCCAA", "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("NombreCCAA", "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:
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)
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
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 = NombreProv),
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 Rillo que está a 5.1960574, 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.
- Primero cargo geometrías de los municipios españoles. Las lindes municipales provienen del paquete
LAU2boundaries4spain
muni_xx <- municipios_2017 %>% st_set_geometry(NULL)
muni <- municipios_2017
Vamos a quedarnos solo con la España peninsular:
#zz <- muni_xx %>% group_by(INECodCCAA, NombreCCAA) %>% count()
muni_borrar <- c("Illes Balears", "Canarias")
muni <- muni %>% filter(!(NombreCCAA %in% muni_borrar))
- Ahora los datos de ríos Pancrudo, Jiloca, Jalón y Ebro
rios <- read_rds(here::here("datos", "mapas", "Rios", "rio_ebro.rds"))
Vamos a poner todos los tramos de los 4 ríos juntos. Con summarise()
rios_xx <- rios %>% st_set_geometry(NULL)
rios_x <- rios %>% 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 = rios_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(rios_x))
zz_a <- st_intersects(muni_a, rios_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 189 municipios por loq eu pasa el Ebro. A ver si nos sale el mapa
p + geom_sf(data = muni_ebro) +
geom_sf(data = rios_x, color = "lightblue", lwd = 0.25)
No, no voy a obtener los municipios por los que pasa el rio Pancrudo. Eso está MAL!!!!
LS0tCnRpdGxlOiAiSW50cm9kdWNjacOzbiBhIEdJUyBjb24gUiIKYXV0aG9yOiAiUGVkcm8gSi4gUMOpcmV6IChwZWRyby5qLnBlcmV6QHV2LmVzKS4gVW5pdmVyc2l0YXQgZGUgVmFsw6huY2lhIDxicj4gPGJyPiBXZWIgZGVsIGN1cnNvOiA8aHR0cHM6Ly9wZXJlenA0NC5naXRodWIuaW8vaW50cm8tZHMtMjAtMjEtd2ViLz4iCmRhdGU6ICJBYnJpbCBkZSAyMDE3IChhY3R1YWxpemFkbyBlbCBgciBmb3JtYXQoU3lzLnRpbWUoKSwgJyVkLSVtLSVZJylgKSIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICBjc3M6ICFleHByIGhlcmU6OmhlcmUoImFzc2V0cyIsICJzdHlsZXNfcGpwLmNzcyIpCiAgICB0aGVtZTogcGFwZXIKICAgIGhpZ2hsaWdodDogdGV4dG1hdGUgCiAgICB0b2M6IHRydWUKICAgIHRvY19kZXB0aDogMyAKICAgIHRvY19mbG9hdDogCiAgICAgIGNvbGxhcHNlZDogdHJ1ZQogICAgICBzbW9vdGhfc2Nyb2xsOiB0cnVlCiAgICBzZWxmX2NvbnRhaW5lZDogdHJ1ZQogICAgbnVtYmVyX3NlY3Rpb25zOiBmYWxzZQogICAgaW5jbHVkZXM6CiAgICAgIGFmdGVyX2JvZHk6ICFleHByIGhlcmU6OmhlcmUoImFzc2V0cyIsICJmb290ZXIuaHRtbCIpIAogICAgICBpbl9oZWFkZXI6IAogICAgICAgIC0gIWV4cHIgaGVyZTo6aGVyZSgiYXNzZXRzIiwgImdvb2dsZS1hbmFseXRpY3MuaHRtbCIpIAogICAgICAgIC0gIWV4cHIgaGVyZTo6aGVyZSgiYXNzZXRzIiwgImZhdmljb24tc29sLmh0bWwiKQogICAgZGZfcHJpbnQ6IGthYmxlCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCmVkaXRvcl9vcHRpb25zOiAKICBjaHVua19vdXRwdXRfdHlwZTogY29uc29sZQpiaWJsaW9ncmFwaHk6ICJgciBoZXJlOjpoZXJlKCdhc3NldHMnLCAnYmlibGlvLmJpYicpYCIgICMtIGpvb29kZXIuIHNpbmdsZSBxdW90ZXMgaHR0cHM6Ly9jb21tdW5pdHkucnN0dWRpby5jb20vdC91c2UtaGVyZS1oZXJlLWZ1bmN0aW9uLWluLXlhbWwtb3B0aW9uLzE4NjY3LzkKLS0tCgpgYGB7ciBjaHVua19zZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCBldmFsID0gVFJVRSwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0UsIAogICAgICAgICAgICAgICAgICAgICAgY2FjaGUgPSBGQUxTRSwgY2FjaGUucGF0aCA9ICIvY2FjaGVzLyIsIGNvbW1lbnQgPSAiIz4iLAogICAgICAgICAgICAgICAgICAgICAgI2ZpZy53aWR0aCA9IDcsIGZpZy5oZWlnaHQ9IDcsICAgCiAgICAgICAgICAgICAgICAgICAgICAjb3V0LndpZHRoID0gNywgb3V0LmhlaWdodCA9IDcsCiAgICAgICAgICAgICAgICAgICAgICBjb2xsYXBzZSA9IFRSVUUsICBmaWcuc2hvdyA9ICJob2xkIiwKICAgICAgICAgICAgICAgICAgICAgIGZpZy5hc3AgPSA3LzksIG91dC53aWR0aCA9ICI5NSUiLCBmaWcuYWxpZ24gPSAiY2VudGVyIikKYGBgCgpgYGB7ciBvcHRpb25zX3NldHVwLCBlY2hvID0gRkFMU0V9Cm9wdGlvbnMoc2NpcGVuID0gOTk5KSAjLSBwYXJhIHF1aXRhciBsYSBub3RhY2lvbiBjaWVudGlmaWNhCmBgYAoKYGBge3Igc2V0dXAsIGVjaG8gPSBGQUxTRX0KbGlicmFyeShrbml0cikKbGlicmFyeShoZXJlKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShwYXRjaHdvcmspCmxpYnJhcnkoZ2dyZXBlbCkKbGlicmFyeShzZikKbGlicmFyeShybmF0dXJhbGVhcnRoKQpsaWJyYXJ5KHJuYXR1cmFsZWFydGhkYXRhKQpsaWJyYXJ5KGdnc3BhdGlhbCkKI2xpYnJhcnkoc3BhbmlzaFJwb2JsYWNpb24pICAgICAjLSByZW1vdGVzOjppbnN0YWxsX2dpdGh1YigicGVyZXpwNDQvc3BhbmlzaFJwb2JsYWNpb24iKQpsaWJyYXJ5KHNwYW5pc2hSZW50aWRhZGVzSUdOKSAgICMtIGRldnRvb2xzOjppbnN0YWxsX2dpdGh1YigicGVyZXpwNDQvc3BhbmlzaFJlbnRpZGFkZXNJR04iKQpsaWJyYXJ5KG1hcHZpZXcpCmxpYnJhcnkobGVhZmVtKQpsaWJyYXJ5KGxlYWZsZXQpCmBgYAoKYGBge3Iga2xpcHB5LCBlY2hvID0gRkFMU0V9CmtsaXBweTo6a2xpcHB5KHBvc2l0aW9uID0gYygidG9wIiwgInJpZ2h0IikpICMtIHJlbW90ZXM6Omluc3RhbGxfZ2l0aHViKCJybGVzdXIva2xpcHB5IikKYGBgCgoKYGBge3IsIGluY2x1ZGUgPSBGQUxTRX0Kb3B0aW9ucyhodG1sdG9vbHMuZGlyLnZlcnNpb24gPSBGQUxTRSkKI2tuaXRyOjpvcHRzX2NodW5rJHNldChmaWcucmV0aW5hID0gMywgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0UsIGVjaG89RkFMU0UsIG91dC53aWR0aCA9ICI4NSUiKQpsaWJyYXJ5KG1ldGF0aGlzKQptZXRhKCkgJT4lIG1ldGFfbmFtZSgiZ2l0aHViLXJlcG8iID0gInBlcmV6cDQ0L2ludHJvLWRzLTIwLTIxLXdlYiIpICU+JSAKICBtZXRhX3NvY2lhbCgKICAgIHRpdGxlID0gIkludHJvZHVjY2nDs24gYSBHSVMgY29uIFIiLAogICAgZGVzY3JpcHRpb24gPSBwYXN0ZSgKICAgICAgIkludHJvZHVjY2nDs24gYSBsb3Mgc2lzdGVtYXMgZGUgaW5mb3JtYWNpw7NuIGdlb2dyw6FmaWNhIChTSUcpIGNvbiBSIiksCiAgICB1cmwgPSAiaHR0cHM6Ly9wZXJlenA0NC5naXRodWIuaW8vaW50cm8tZHMtMjAtMjEtd2ViL3R1dG9yaWFsZXMvdHRfMDlfR0lTLWNvbi1SLmh0bWwiLAogICAgb2dfdHlwZSA9ICJ3ZWJzaXRlIiwKICAgIG9nX2F1dGhvciA9ICJQZWRybyBKLiBQw6lyZXoiCiAgKQpgYGAKCi0tLS0tLS0tLS0tLS0KCjxicj4KCgpgYGB7ciBwYXJhX2Nhcmdhcl9kYXRvcywgZXZhbCA9IFRSVUUsIGVjaG8gPSBGQUxTRX0KIy0gbG9zIGd1YXJkw6kgZW4gIi4vZGF0b3MvZ2VvbWV0cmlhc19jbGFzZV8xMC5SRGF0YSIKI3NhdmUoQ0NBQSwgUHJvdmluY2lhcywgbXVuaWNpcGlvc18yMDE3LCB3b3JsZCwgcmlvcywgSUdOX25vbWVuY2xhX211bmksIGZpbGUgPSAiLi9kYXRvcy9tYXBhcy9nZW9tZXRyaWFzX2NsYXNlXzEwLlJEYXRhIikKIy0gY2FyZ28gbG9zIGRhdG9zIHByZXZpYW1lbnRlIGd1YXJkYWRvcwpsb2FkKGhlcmU6OmhlcmUoImRhdG9zIiwgIm1hcGFzIiwgImdlb21ldHJpYXNfY2xhc2VfMTAuUkRhdGEiKSkKYGBgCgoKIyAxLiBJbnRyb2R1Y2Npw7NuCgpWYW1vcyBhIHV0aWxpemFyIFIgY29tbyB1bmEgYXBsaWNhY2nDs24gR0lTIChTaXN0ZW1hcyBkZSBpbmZvcm1hY2nDs24gZ2VvZ3LDoWZpY2EpOyBlcyBkZWNpciwgbG8gdmFtb3MgYSB1dGlsaXphciBwYXJhIGFsbWFjZW5hciwgbWFuaXB1bGFyLCBhbmFsaXphciB5IHByZXNlbnRhciBkYXRvcyBnZW9ncsOhZmljb3MgbyBnZW9lc3BhY2lhbGVzLiBHSVMgZXMgdW4gw6FyZWEgZW5vcm1lLCBwb3IgZWplbXBsbywgW2FxdcOtXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvdmlld3MvU3BhdGlhbC5odG1sKSB0aWVuZXMgdW4gbGlzdGFkbyBkZSBoZXJyYW1pZW50YXMgeSBwYXF1ZXRlcyBwYXJhIHRyYWJhamFyIGNvbiBkYXRvcyBlc3BhY2lhbGVzIGVuIFIuIEhheSBtdWNow61zaW1hcyEhISwgZXMgdW4gw6FyZWEgZGUgY29ub2NpbWllbnRvIGVuIHPDrSBtaXNtYSwgZGUgbGEgcXVlLCBjb24gbG9zIGVzY2Fzb3MgY29ub2NpbWllbnRvcyBxdWUgdGVuZ28sIHZveSBhIGhhY2VyIHVuYSBwZXF1ZcOxYSBpbnRyb2R1Y2Npw7NuIGEgR0lTIGNvbiBSLgoKRHVyYW50ZSBlc3RlIHR1dG9yaWFsIHVzYXJlbW9zIGVzdG9zIGNvbmp1bnRvcyBkZSBkYXRvcwoKYGBge3IgcGFyYV9jYXNhLCBldmFsID0gRkFMU0V9CkNDQUEgPC0gcmlvOjppbXBvcnQoImh0dHBzOi8vZ2l0aHViLmNvbS9wZXJlenA0NC9MQVUyYm91bmRhcmllczRzcGFpbi9ibG9iL21hc3Rlci9kYXRhL0NDQUEucmRhP3Jhdz10cnVlIikKUHJvdmluY2lhcyA8LSByaW86OmltcG9ydCgiaHR0cHM6Ly9naXRodWIuY29tL3BlcmV6cDQ0L0xBVTJib3VuZGFyaWVzNHNwYWluL2Jsb2IvbWFzdGVyL2RhdGEvUHJvdmluY2lhcy5yZGE/cmF3PXRydWUiKQptdW5pY2lwaW9zXzIwMTcgPC0gcmlvOjppbXBvcnQoImh0dHBzOi8vZ2l0aHViLmNvbS9wZXJlenA0NC9MQVUyYm91bmRhcmllczRzcGFpbi9ibG9iL21hc3Rlci9kYXRhL211bmljaXBpb3NfMjAxNy5yZGE/cmF3PXRydWUiKQp3b3JsZCA8LSBybmF0dXJhbGVhcnRoOjpuZV9jb3VudHJpZXMoc2NhbGUgPSAibWVkaXVtIiwgcmV0dXJuY2xhc3MgPSAic2YiKQpyaW9zIDwtIHJpbzo6aW1wb3J0KCJodHRwczovL2dpdGh1Yi5jb20vcGVyZXpwNDQvYXJjaGl2b3NfZG93bmxvYWQvYmxvYi9tYXN0ZXIvcmlvX2Vicm8ucmRzP3Jhdz10cnVlIikKSUdOX25vbWVuY2xhX211bmkgPC0gcmlvOjppbXBvcnQoImh0dHBzOi8vZ2l0aHViLmNvbS9wZXJlenA0NC9hcmNoaXZvc19kb3dubG9hZC9ibG9iL21hc3Rlci9JR05fbm9tZW5jbGFfbXVuaS5yZHM/cmF3PXRydWUiKQpgYGAKCgoKVmVuZ2EsIHZhbW9zIGEgaGFjZXIgbWFwYXMgY29uIGVsbG9zISEhIFPDrSwgZW5zZWd1aWRhISEgQSB0b2RvcyBub3MgZ3VzdGFuIGxvcyBtYXBhcyB5IGhhY2VyIG1hcGFzIGNvbiBSIGVzICplYXN5KiAuLi4gYnVlbm8sIG5vIHRhbiBmw6FjaWwsIHBlcm8gdmFtb3MgYSBlbGxvISEKCgpNw6FzIHF1ZSBtYXBhcyBsbyBxdWUgdmFtb3MgYSBoYWNlciBlcyB2aXN1YWxpemFyICoqZGF0b3MgZ2VvZXNwYWNpYWxlcyoqLiBVbm9zIGRhdG9zIG5vIHNlIHB1ZWRlbiBjb25zaWRlcmFyIGdlb2VzcGFjaWFsZXMgc2kgbm8gdmllbmVuIGFjb21wYcOxYWRvcyBkZSB1biAqKkNSUyoqIChDb29yZGluYXRlIFJlZmVyZW5jZSBTeXN0ZW0pIGRlIGZvcm1hIHF1ZSBsYXMgYXBsaWNhY2lvbmVzIEdJUyBwdWVkYW4gbWFuaXB1bGFyIHkgbW9zdHJhciBjb3JyZWN0YW1lbnRlIGVzb3MgZGF0b3MuIExvcyBDUlMgdXRpbGl6YW4gdW4gbW9kZWxvIG1hdGVtw6F0aWNvIHBhcmEgY29uZWN0YXIgbG9zIGRhdG9zLCBsYXMgY29vcmRlbmFkYXMgZGUgdW4gb2JqZXRvLCBjb24gc3UgcG9zaWNpw7NuIGbDrXNpY2EgZW4gbGEgc3VwZXJmaWNpZSB0ZXJyZXN0cmUuIFVuIHNpc3RlbWEgZGUgcmVmZXJlbmNpYSBkZSBjb29yZGVuYWRhcyBnZW9ncsOhZmljYXMgcGVybWl0ZSBxdWUgdG9kb3MgbG9zIHB1bnRvcyBkZSBsYSB0aWVycmEgcHVlZGFuIHNlciBkZXNjcml0b3MgY29tbyB1biBjb25qdW50b3MgZGUgY29vcmRlbmFkYXMgKGNvbW8gbGF0aXR1ZCwgbG9uZ2l0dWQgeSBhbHRpdHVkKS4gRGl2ZXJzb3Mgc2lzdGVtYXMgc2UgdXRpbGl6YW4gcGFyYSByZXByZXNlbnRhciBsYSB0aWVycmEgdHJpZGltZW5zaW9uYWwgZW4gdW4gcGxhbm8gbyBtYXBhIGJpZGltZW5zaW9uYWwuCgoKTG9zIGRhdG9zIGdlb2VzcGFjaWFsZXMgcmVwcmVzZW50YW4gbHVnYXJlcyBnZW9ncsOhZmljb3MgZGUgbGEgVGllcnJhIHkgcGFyYSBwb2RlciByZXByZXNlbnRhciBsYSBwb3NpY2nDs24gZGUgdW4gb2JqZXRvIGVuIGxhIFRpZXJyYSBuZWNlc2l0YW1vcyBkb3MgY29zYXM6CgotIGxhcyBjb29yZGVuYWRhcyBkZWwgb2JqZXRvIAoKLSB1biBzaXN0ZW1hIGRlIHJlZmVyZW5jaWEgKENSUykgcGFyYSBjb25lY3RhciBlc29zIGRhdG9zIGNvbiBsYSBzdXBlcmZpY2llIHRlcnJlc3RyZS4KClVuIENSUyBjb25zaXN0ZSBlbiBtb2RlbG8gZ2VvbcOpdHJpY28gZWxpcHNvaWRlIGRlIGxhIHN1cGVyZmljaWUgdGVycmVzdHJlIHkgdW4gKmRhdHVtKiBxdWUgaWRlbnRpZmljYSBlbCBvcmlnZW4geSBvcmllbnRhY2nDs24gZGUgbG9zIGVqZXMgeSB0YW1iacOpbiBsYXMgdW5pZGFkZXMgZGUgbWVkaWRhLgoKQ29tbyBzYWLDqWlzIGxhIFRpZXJyYSBubyBlcyBlc2bDqXJpY2EgeSBlc3RvIGNvbXBsaWNhIGxhIHV0aWxpemFjacOzbiBkZSB1biDDum5pY28gQ1JTLCBkZSBmb3JtYSBxdWUgc2UgaGFuIGNyZWFkbyB1bm9zIGN1YW50b3MgcGFyYSB0cmF0YXIgZGUgcmVwcmVzZW50YXIgbGEgc3VwZXJmaWNpZSB0ZXJyZXN0cmUgZGUgZm9ybWEgYWRlY3VhZGEuCgpBZGVtw6FzLCBoYXkgZG9zIHRpcG9zIGRlIENSUzogKipDUlMgZ2VvZ3LDoWZpY29zIHkgcHJveWVjdGFkb3MqKi4gRW4gbG9zIHNpc3RlbWEgZ2VvZ3LDoWZpY29zLCBlbCBtYXJjbyBkZSByZWZlcmVuY2lhIGVzIGVsIGdsb2JvIHRlcnLDoXF1ZW8geSBjdWFudG9zIGdyYWRvcyBub3J0ZSBvIGVzdGUsIGVzdMOhIGVsIG9iamV0bzsgbWllbnRyYXMgcXVlIGVuIGxvcyBzaXN0ZW1hcyBwcm95ZWN0YWRvcywgZWwgbWFyY28gZGUgcmVmZXJlbmNpYSBlcyB1bmEgcmVwcmVzZW50YWNpw7NuIHBsYW5hIGRlIGxhIFRpZXJyYSAobyBkZSBwYXJ0ZSBkZSBlbGxhKS4gU2kgdGUgaW50ZXJlc2EgZWwgdGVtYSwgW2FxdcOtXShodHRwczovL2dlb2NvbXByLmdpdGh1Yi5pby9wb3N0LzIwMTkvY3JzLXByb2plY3Rpb25zLXRyYW5zZm9ybWF0aW9ucy8pIHkgIFthcXXDrV0oaHR0cHM6Ly9wYXVsYmxnaXMud29yZHByZXNzLmNvbS8yMDE2LzEyLzA5L3Npc3RlbWFzLWRlLWNvb3JkZW5hZGFzLXByb3llY3RhZGFzLXktZ2VvZ3JhZmljYXMtY3VhbC1lcy1sYS1kaWZlcmVuY2lhLykgdGllbmVzIGRvcyBwb3N0cyBkaXZ1bGdhdGl2b3MuCgoKCiMjIFByb3llY2Npb25lcwoKQ29tbyBsYSBUaWVycmEgZXMgZXNmw6lyaWNhLCBwYXJhIGhhY2VyIHVuIG1hcGEgYmlkaW1lbnNpb25hbCAobyBwbGFubykgZGUgZWxsYSwgc2UgdGllbmUgcXVlIHVzYXIgYWxnw7puIHRpcG8gZGUgcHJveWVjY2nDs24sIHBvciBsbyBxdWUgbGFzIMOhcmVhcyBzZSBtb3N0cmFyw6FuIG3DoXMgbyBtZW5vcyBkaXN0b3JzaW9uYWRhcy4gUG9yIGVqZW1wbG86CgoKCmBgYHtyfQpwMSA8LSBnZ3Bsb3QoQ0NBQSwgIGFlcyhmaWxsID0gSU5FQ29kQ0NBQSkgKSArIAogICAgICAgICAgZ2VvbV9zZigpICsKICAgICAgICAgIHNjYWxlX2ZpbGxfdmlyaWRpc19kKGd1aWRlID0gRkFMU0UpICsKICAgICAgICAgIGxhYnModGl0bGUgPSAiQ29vcmQuIGdlb2dyw6FmaWNhcyIpCgpwMiA8LSBnZ3Bsb3QoQ0NBQSwgIGFlcyhmaWxsID0gSU5FQ29kQ0NBQSkgKSArIAogICAgICAgICAgIGdlb21fc2YoKSArIGNvb3JkX3NmKGNycyA9IDIxNTQsIGRhdHVtID0gc2Y6OnN0X2NycygyMTU0KSkgKwogICAgICAgICAgIHNjYWxlX2ZpbGxfdmlyaWRpc19kKGd1aWRlID0gRkFMU0UpICsKICAgICAgICAgICBsYWJzKHRpdGxlID0gIkxhbWJlcnQgOTMiKQoKcDEgKyBwMgpgYGAKCgoKCiMjIERhdG9zIGVzcGFjaWFsZXMKClBhcmEgcG9kZXIgcmVwcmVzZW50YXIgYWRlY3VhZGFtZW50ZSBkYXRvcyBlc3BhY2lhbGVzLCBlc3RvcyBkZWJlbiBjb250ZW5lciBsYSBpbmZvcm1hY2nDs24gZGUgc3UgQ1JTIHkgYWxndW5hcyB2ZWNlcyBoYXkgcXVlIGNvbnZlcnRpciBlbnRyZSBDUlMnczsgZGUgZm9ybWEgcXVlIGxhIGVzdHJ1Y3R1cmEgdHJhZGljaW9uYWwgZGUgZGF0b3MgZW4gUiAobG9zIGRhdGEuZnJhbWVzKSBubyBlcyBhZGVjdWFkYSBwYXJhIGFsbWFjZW5hciBkYXRvcyBlc3BhY2lhbGVzXltNb3N0IGdlb2NvZGVkIGRhdGEgY3JlYXRlZCB0aHJvdWdoIEdvb2dsZSBNYXBzIG9yIG90aGVyIGdlb2NvZGluZyBzaXRlcyAgcHJvdmlkZXMgY29vcmRpbmF0ZXMgaW4gbG9uZ2l0dWRlIGFuZCBsYXRpdHVkZSB2YWx1ZXMgdXNpbmcgdGhlIFdHUzg0IGVsbGlwc29pZCBhbmQgdGh1cyBjb250YWlucyBhIENSUyBlcXVpdmFsZW50IHRvIHRoZSBQUk9KIGFyZ3VtZW50ICtwcm9qPWxvbmdsYXQgK2RhdHVtPVdHUzg0IG9yIEVQU0cgNDMyNi4gSG93ZXZlciwgdGhpcyBpbmZvcm1hdGlvbiBhYm91dCB0aGUgQ1JTIGlzIGltcGxpY2l0IGFuZCBub3QgY29udGFpbmVkIGFueXdoZXJlIHdpdGhpbiB0aGUgZGF0YSBmcmFtZSBpdHNlbGZdLiBQaWVuc2EgYWRlbcOhcyBxdWUgbG9zIGRhdG9zIGVzcGFjaWFsZXMgbcOhcyBzaW1wbGVzIChsb3MgcHVudG9zKSBzZXLDrWFuIGbDoWNpbGVzIGRlIGFsbWFjZW5hciBlbiBkb3MgY29sdW1uYXMgZGUgdW4gZGF0YS5mcmFtZSwgcGVybyBwaWVuc2EgZW4gb2JqZXRvcyBlc3BhY2lhbGVzIG3DoXMgY29tcGxlam9zIGNvbW8gbGluZWFzLCBwb2zDrWdvbm9zLCBwb2zDrWdvbm9zIGNvbiBodWVjb3MgZXRjLi4uIGhhY2UgcXVlIHBhcmEgYWxtYWNlbmFyIGRhdG9zIGVzcGFjaWFsZXMgZW4gUiBzZSB1dGlsaWNlbiBlc3RydWN0dXJhcyBkZSBkYXRvcyBtw6FzIGNvbXBsZWphcyBxdWUgdW4gZGF0YS5mcmFtZS4KCgpBZGVtw6FzLCBwYXJhIGNvbXBsaWNhciB1biBwb2NvIG3DoXMgbGFzIGNvc2FzLCBoYXkgZG9zIHRpcG9zIGZ1bmRhbWVudGFsZXMgZGUgZGF0b3MgZ2VvZ3LDoWZpY29zOiAqKnJhc3RlciB5IHZlY3RvcmlhbGVzKiouIEVuIGVsIGN1cnNvIHNvbG8gdmFtb3MgYSB2ZXIgKHVuIHBvcXVpdG8pIGxvcyBkYXRvcyBlc3BhY2lhbGVzIHZlY3RvcmlhbGVzLgoKLSBMb3MgZGF0b3MgcsOhc3RlciBzZSBhbG1hY2VuYW4gY29tbyB1bmEgY3VhZHLDrWN1bGEgZGUgdmFsb3JlcyBxdWUgc2UgcmVwcmVzZW50YW4gZW4gdW4gbWFwYSBjb21vIHDDrXhlbGVzLiBDYWRhIHZhbG9yIGRlIHDDrXhlbCByZXByZXNlbnRhIHVuIMOhcmVhIGVuIGxhIHN1cGVyZmljaWUgZGUgbGEgVGllcnJhLiBMb3MgZGF0b3MgcsOhc3RlciBzb24gZGF0b3MgcGl4ZWxhZG9zIChvIGN1YWRyaWN1bGFkb3MpIGVuIGxvcyBxdWUgY2FkYSBww614ZWwgZXN0w6EgYXNvY2lhZG8gYSB1bmEgdWJpY2FjacOzbiBnZW9ncsOhZmljYSBlc3BlY8OtZmljYS4gRWwgdmFsb3IgZGUgdW4gcMOteGVsIHB1ZWRlIHNlciBjb250aW51byAocG9yIGVqZW1wbG8sIGVsZXZhY2nDs24pIG8gY2F0ZWfDs3JpY28gKHBvciBlamVtcGxvLCB1c28gZGVsIHN1ZWxvKS4gVW4gcsOhc3RlciBnZW9lc3BhY2lhbCBzb2xvIGVzIGRpZmVyZW50ZSBkZSB1bmEgZm90byBkaWdpdGFsIGVuIHF1ZSBlc3TDoSBhY29tcGHDsWFkbyBkZSBpbmZvcm1hY2nDs24gZXNwYWNpYWwgcXVlIGNvbmVjdGEgbG9zIGRhdG9zIGEgdW5hIHViaWNhY2nDs24gZW4gcGFydGljdWxhci4gRXN0byBpbmNsdXllIGxhIGV4dGVuc2nDs24geSBlbCB0YW1hw7FvIGRlIGNlbGRhIGRlbCByw6FzdGVyLCBlbCBuw7ptZXJvIGRlIGZpbGFzIHkgY29sdW1uYXMgeSBzdSBzaXN0ZW1hIGRlIHJlZmVyZW5jaWEgZGUgY29vcmRlbmFkYXMgKG8gQ1JTKQoKCi0gTGFzIGVzdHJ1Y3R1cmFzIGRlIGRhdG9zIHZlY3RvcmlhbGVzIHJlcHJlc2VudGFuIHBvc2ljaW9uZXMgZXNwZWPDrWZpY2FzIGVuIGxhIHN1cGVyZmljaWUgZGUgbGEgVGllcnJhLgoKCgojIyBgc3BgIHZlcnN1cyBgc2ZgCgpFbiBSIGhheSAyIGZvcm1hcyBhbHRlcm5hdGl2YXMgZGUgYWxtYWNlbmFyICh5IGRlIG1hbmlwdWxhcikgZGF0b3MgZ2VvZ3LDoWZpY29zIHZlY3RvcmlhbGVzOiBlbCBwYXF1ZXRlIGBzcGAgeSBlbCBwYXF1ZXRlIGBzZmAuIExhcyBwcmluY2lwYWxlcyBkaWZlcmVuY2lhcyBlbnRyZSBsb3MgcGFxdWV0ZXMgc3AgeSBzZiBzZSBkZXJpdmFuIGRlIGxhIG1hbmVyYSBlbiBxdWUgbGFzIGNsYXNlcyBzcCB5IHNmIGFsbWFjZW5hbiBpbmZvcm1hY2nDs24gc29icmUgQ1JTLCBkaXN0aW5ndWVuIGVudHJlIHB1bnRvcywgbMOtbmVhcyB5IHBvbMOtZ29ub3MsIHkgY8OzbW8gY29uZWN0YW4gZXN0b3MgZGF0b3MgZXNwYWNpYWxlcyBjb24gZGF0b3Mgbm8gZXNwYWNpYWxlcy4KCkN1YW5kbyBzZSB1c2FuIGVzdG9zIHBhcXVldGVzLCB0YW50byBgc3BgIGNvbW8gYHNmYCwgcHJvcG9yY2lvbmFuIGVubGFjZXMgYSBwb3RlbnRlcyBiaWJsaW90ZWNhcyBTSUcgZGUgY8OzZGlnbyBhYmllcnRvLCBjb21vIFsqKkdEQUwqKl0oaHR0cHM6Ly9nZGFsLm9yZy8pIChHZW9zcGF0aWFsIERhdGEgQWJzdHJhY3Rpb24gTGlicmFyeTogZXMgdW5hIGJpYmxpb3RlY2EgZGUgc29mdHdhcmUgcGFyYSBsYSBsZWN0dXJhIHkgZXNjcml0dXJhIGRlIGZvcm1hdG9zIGRlIGRhdG9zIGdlb2VzcGFjaWFsZXMsIHB1YmxpY2FkYSBiYWpvIGxhIE1JVCBMaWNlbnNlIHBvciBsYSBmdW5kYWNpw7NuIGdlb2VzcGFjaWFsIGRlIGPDs2RpZ28gYWJpZXJ0byAoT3BlbiBTb3VyY2UgR2Vvc3BhdGlhbCBGb3VuZGF0aW9uKSwgWyoqR0VPUyoqXShodHRwczovL2dlb3Mub3NnZW8ub3JnL2RveHlnZW4vaW5kZXguaHRtbCkgKEdlb21ldHJ5IEVuZ2luZSBPcGVuIFNvdXJjZTogcGFyYSBvcGVyYWNpb25lcyBkZSB0b3BvbG9nw61hIGVuIGdlb21ldHLDrWFzKSB5IFsqKlBST0oqKl0oaHR0cHM6Ly9wcm9qLm9yZy9uZXdzLmh0bWwpIChQUk9KIGVzIHVuYSBiaWJsaW90ZWNhIHF1ZSBwcm9wb3JjaW9uYSBtw6l0b2RvcyBwYXJhIHRyYW5zZm9ybWFyIGVudHJlIHNpc3RlbWFzIGRlIHJlZmVyZW5jaWEgZGUgY29vcmRlbmFkYXMgZGlmZXJlbnRlcyB5IG9wZXJhY2lvbmVzIGRlIHByb3llY2Npw7NuKS4gUG9yIGVzbyBjdWFuZG8gY2FyZ2Ftb3MgZWwgcGFxdWV0ZSBgc2ZgIG5vcyBhcGFyZWNlIGVsIHNpZ3VpZW50ZSBtZW5zYWplIGVuIGxhIGNvbnNvbGE6IGBMaW5raW5nIHRvIEdFT1MgMy44LjEsIEdEQUwgMy4xLjMsIFBST0ogNy4xLjFgCgoKTXVjaG9zIHBhcXVldGVzIFIgcGFyYSBHSVMgdXRpbGl6YW4gbGEgbWV0b2RvbG9nw61hL2FycXVpdGVjdHVyYSBwcm9wb3JjaW9uYWRhIHBvciBgc3BgLCBwZXJvIHBvciB2YXJpYXMgcmF6b25lcyBsYSBtZXRvZG9sb2fDrWEgaW1wbGVtZW50YWRhIHBvciBlbCBwYXF1ZXRlICBlbCBwYXF1ZXRlIGBzZmAgc2UgZXN0w6EgY29udmlydGllbmRvIGVuIGVsIGVzdMOhbmRhci4KCkxhcyBwcmluY2lwYWxlcyByYXpvbmVzIHNvbiA0OiAKCjEpIEVsIHBhcXVldGUgYHNmYCBpbXBsZW1lbnRhIGVsIGVzdMOhbmRhciBhYmllcnRvICJzaW1wbGUgZmVhdXR1cmVzIiBwYXJhIGxhIHJlcHJlc2VudGFjacOzbiBkZSBkYXRvcyBnZW9ncsOhZmljb3MgdmVjdG9yaWFsZXMgZW4gUi4gU2ltcGxlIGZlYXR1cmVzIGVzIHVuIGVzdMOhbmRhciwgdW4gY29uanVudG8gY29tw7puIGRlIHJlZ2xhcywgcGFyYSByZXByZXNlbnRhciBsYXMgZ2VvbWV0csOtYXMgZXNwYWNpYWxlcyBkZSBvYmpldG9zIGRlbCBtdW5kbyByZWFsIGVuIHVuIG9yZGVuYWRvciwgZW4gbGFzIHF1ZSBzZSBkZWZpbmUgZGVzZGUgY8OzbW8gc2UgYWxtYWNlbmFuIGxvcyBkYXRvcyBlbiBlbCBvcmRlbmFkb3IgaGFzdGEgcXXDqSBvcGVyYWNpb25lcyBnZW9tw6l0cmljYXMgc2UgcHVlZGVuIGFwbGljYXIuIAoKMikgYHNmYCB1dGlsaXphIHBhcmEgY29udGVuZXIvYWxtYWNlbmFyIGxvcyBkYXRvcyBlc3BhY2lhbGVzIHVuYSBlc3RydWN0dXJhIG11eSBzaW1pbGFyIGEgdW4gZGF0YWZyYW1lOiBsb3Mgc3BhdGlhbCBkYXRhZnJhbWVzIG8gYHNmYF5bU8OtLCB0b2RvIHNlIGxsYW1hIGBzZmAhISEgVGFudG8gZWwgcGFxdWV0ZSBjb21vIGVsIGVzdMOhbmRhciBxdWUgaW5jb3Jwb3JhIGNvbW8gbGEgZXN0cnVjdHVyYSBkZSBkYXRvcyBzZSBsbGFtYW4gdG9kb3MgYHNmYC5dICwgcXVlIHNvbiBtdXkgcGFyZWNpZG9zIGEgdW4gZGF0YSBmcmFtZSBzYWx2byBxdWUgaW5jbHV5ZW4gdW5hIGNvbHVtbmEgYWRpY2lvbmFsLCBnZW5lcmFsbWVudGUgbGxhbWFkYSBgZ2VvbWV0cnlgIHF1ZSBhbG1hY2VuYSBsYSBpbmZvcm1hY2nDs24gZ2VvZ3LDoWZpY2EsIGxvcyBkYXRvcyB2ZWN0b3JpYWxlcyBlc3BhY2lhbGVzLCB5YSBzZWFuIGVzdG9zIHB1bnRvcywgbGluZWFzLCBwb2zDrWdvbm9zIGV0Yy4uLgoKMykgTXVjaGFzIGRlIGxhcyBmdW5jaW9uZXMgZGVsIHBhcXVldGUgYHNmYCBjb21pZW56YW4gY29uIGBzdF9gOyBwb3IgZWplbXBsbyAgYHN0X25lYXJlc3RfcG9pbnRzKClgLCBsbyBxdWUgZmFjaWxpdGEgc3UgdXNvLgoKCjQpIGxhcyBmdW5jaW9uZXMgZGUgYHNmYCBzZSBpbnRlZ3JhbiBlbiBlbCAqKnRpZHl2ZXJzZSB3b3JrZmxvdyoqLiBEZSBoZWNobyBsYSBlc3RydWN0dXJhIGRlIGRhdG9zIHF1ZSB1dGlsaXphIGVsIHBhcXVldGUgYHNmYCBwYXJhIGFsbWFjZW5hciBkYXRvcyBnZW9ncsOhZmljb3Mgc29uIGxvcyBvYmpldG9zIGRlIHRpcG8gYGBzZmAgdW5hIGV4dGVuc2nDs24gZGUgbG9zIGRhdGEuZnJhbWVzLCBkZSBmb3JtYSBxdWUgcHVlZGVuIHNlciBtYW5pcHVsYWRvcyBjb24gYGRwbHlyYCB5IGdyYWZpY2Fkb3MgY29uIGBnZ3Bsb3QyYC4gTG9zIG9iamV0b3MgYHNmYCBwdWVkZW4gYWxtYWNlbmFyIGRhdG9zIG5vcm1hbGVzIChjdWFudGl0YXRpdm9zLCBjdWFsaXRhdGl2b3MsIHRleHRvKSBwZXJvIGFsbWFjZW5hbiBkYXRvcyBlc3BhY2lhbGVzIGVuIHVuYSBjb2x1bW5hIGVzcGVjaWFsIGxsYW1hZGEgaGFiaXR1YWxtZW50ZSBgZ2VvbWV0cnlgIGNvbiBjbGFzZSBlc3BlY2lhbCAoYHNmY2ApLCB5IGFsbMOtIHNlIGFsbWFjZW5hIGxhIGluZm9ybWFjacOzbiBnZW9ncsOhZmljYTogZWwgQ1JTLCBjb29yZGVuYWRhcyB5IGVsIHRpcG8gZGUgb2JqZXRvIGdlb23DqXRyaWNvLiBMYSBjbGFzZSBzZmMgdGllbmUgc2lldGUgc3ViY2xhc2VzIHBhcmEgZGVub3RhciBsb3MgZGlzdGludG9zIHRpcG9zIGRlIG9iamV0b3MgZ2VvbcOpdHJpY29zIHF1ZSBzZSBkZXJpdmFuIGRlbCBlc3TDoW5kYXIgYHNmYC4gCgoKLSBQb3IgZWplbXBsbywgcGFyYSBzZWxlY2Npb25hciBnZW9tZXRyw61hcyB1c2Ftb3MgbGEgZnVuY2nDs24gYGZpbHRlcigpYDoKCgpgYGB7cn0KIy0gZWxlZ2ltb3MgNCBDQy5BQQp6el9wcm92X2Nob3NlbiA8LSBjKCJBbmRhbHVjw61hIiwgIkdhbGljaWEiLCAiQXJhZ8OzbiIsICJDYW5hcmlhcyIsICJJbGxlcyBCYWxlYXJzIikKCnp6X3Byb3ZfMSA8LSBQcm92aW5jaWFzICU+JSBmaWx0ZXIoTm9tYnJlQ0NBQSAlaW4lIHp6X3Byb3ZfY2hvc2VuKQp6el9wcm92XzIgPC0gUHJvdmluY2lhcyAlPiUgZmlsdGVyKCEoTm9tYnJlQ0NBQSAlaW4lIHp6X3Byb3ZfY2hvc2VuKSkKCnAxIDwtIGdncGxvdCh6el9wcm92XzEsIGFlcyhmaWxsID0gSU5FQ29kUHJvdikgKSArIAogICAgICAgICBnZW9tX3NmKCkgICsKICAgICAgICAgc2NhbGVfZmlsbF92aXJpZGlzX2QoZ3VpZGUgPSBGQUxTRSkgKwogICAgICAgICBsYWJzKHRpdGxlID0gIkNvb3JkLiBnZW9ncsOhZmljYXMiKQoKcDIgPC0gZ2dwbG90KHp6X3Byb3ZfMiwgYWVzKGZpbGwgPSBJTkVDb2RQcm92KSApICsgCiAgICAgICAgIGdlb21fc2YoKSAgKwogICAgICAgICBzY2FsZV9maWxsX3ZpcmlkaXNfZChndWlkZSA9IEZBTFNFKSArCiAgICAgICAgIGxhYnModGl0bGUgPSAiQ29vcmQuIGdlb2dyw6FmaWNhcyIpCgpwMSArIHAyIApgYGAKCi0gUG9yIGVqZW1wbG8sIHBhcmEgYWdydXBhciBnZW9tZXRyw61hcywgZW4gZXN0ZSBjYXNvIGxhcyBwcm92aW5jaWFzIGVzcGHDsW9sYXMsIHVzYW1vcyBsYSBmdW5jacOzbiBgc3VtbWFyaXplKClgCgoKYGBge3J9CiMtIGNyZWFtb3MgbGFzIGdlb21ldHLDrWFzIGRlIGxhcyBDQy5BQSBwb3IgYWdydXBhY2nDs24gZGUgbGFzIGdlb21ldHLDrWFzIHByb3ZpbmNpYWxlcwpDQ0FBXzIgPC0gUHJvdmluY2lhcyAlPiUgZ3JvdXBfYnkoSU5FQ29kQ0NBQSwgTm9tYnJlQ0NBQSkgJT4lIHN1bW1hcml6ZSgpCgpwMSA8LSBnZ3Bsb3QoUHJvdmluY2lhcywgYWVzKGZpbGwgPSBJTkVDb2RQcm92KSApICsgCiAgICAgICAgIGdlb21fc2YoKSAgKwogICAgICAgICBzY2FsZV9maWxsX3ZpcmlkaXNfZChndWlkZSA9IEZBTFNFKSArCiAgICAgICAgIGxhYnModGl0bGUgPSAiQ29vcmQuIGdlb2dyw6FmaWNhcyIpCgoKcDIgPC0gZ2dwbG90KENDQUFfMiwgYWVzKGZpbGwgPSBJTkVDb2RDQ0FBKSApICsgCiAgICAgICAgIGdlb21fc2YoKSAgKwogICAgICAgICBzY2FsZV9maWxsX2Rpc2NyZXRlKGd1aWRlID0gRkFMU0UpICsKICAgICAgICAgbGFicyh0aXRsZSA9ICJDb29yZC4gZ2VvZ3LDoWZpY2FzIikKCnAxICsgcDIgCmBgYAoKCmBgYHtyLCBlY2hvID0gRkFMU0V9CiMtIGJvcnJhbW9zIGxvIHEgbm8gbm9zIGhhY2UgZmFsdGEKcm0ocDEsIHAyLCB6el9wcm92X2Nob3NlbiwgenpfcHJvdl8xLCB6el9wcm92XzIsIENDQUFfMikKYGBgCgpCdWVubywgcHVlcyBlc3RlIGVzIHRvZGEgbGEgdGVvcsOtYSBkZWwgdHV0b3JpYWwuIFZhbW9zIGEgaGFjZXIgZ3LDoWZpY29zISEKCjxicj4KCi0tLS0tLS0tLS0tLS0tLQoKPGJyPgoKCiMgMi4gSGFjaWVuZG8gZ3LDoWZpY29zCgpgYGB7ciwgZXZhbCA9IEZBTFNFLCBlY2hvID0gRkFMU0V9CiMtIHBvbmVtb3MgdW4gdGhlbWUgdW4gcG9jbyBtw6FzIGFkZWN1YWRvIHBhcmEgbWFwYXMKdGhlbWVfc2V0KHRoZW1lX2J3KCkpICAjLSBUaGUgY2xhc3NpYyBkYXJrLW9uLWxpZ2h0IGdncGxvdDIgdGhlbWUgd29yayBiZXR0ZXIgZm9yIHByZXNlbnRhdGlvbnMgCnRoZW1lX3NldChjb3dwbG90Ojp0aGVtZV9tYXAoKSkKYGBgCgoKUHJpbWVybyB2YW1vcyBjYXJnYXIgdW4gY29uanVudG8gZGUgZGF0b3MgZXNwYWNpYWxlcyBkZWwgcGFxdWV0ZSBbYHJuYXR1cmFsZWFydGhgXShodHRwczovL2RvY3Mucm9wZW5zY2kub3JnL3JuYXR1cmFsZWFydGgvKS4gQnVlbm8sIGVuIHJlYWxpZGFkIHlhIGxvIGhlbW9zIGNhcmdhZG8gYW50ZXMuCgoKYGBge3J9CiN3b3JsZCA8LSBybmF0dXJhbGVhcnRoOjpuZV9jb3VudHJpZXMoc2NhbGUgPSAibWVkaXVtIiwgcmV0dXJuY2xhc3MgPSAic2YiKQpjbGFzcyh3b3JsZCkgIy0gZXMgdW4gc2YsIHBlcm8gdGIgZXMgdW4gZGF0YS5mcmFtZQpuYW1lcyh3b3JsZCkgIy0gZmlqYXRlIHF1ZSBsYSB1bHRpbWEgY29sdW1uYSBzZSBsbGFtYSBnZW9tZXRyeSB5IGFsbWFjZW5hIGxvcyBkYXRvcyBlc3BhY2lhbGVzCmBgYAoKCkxvcyBncsOhZmljb3MgbG9zIHZhbW9zIGEgaGFjZXIsIGRlIG1vbWVudG8sIGNvbiBgZ2dwbG90MmAuIEVzIHRhbiBmw6FjaWwgY29tbyB1c2FyIGBnZW9tX3NmKClgLiBMYXMgc2lndWllbnRlcyAzIGV4cHJlc2lvbmVzIHByb2R1Y2VuIGVsIG1pc21vIGdyw6FmaWNvOiAKCmBgYHtyLCBldmFsID0gRkFMU0V9CmdncGxvdChkYXRhID0gd29ybGQpICsgZ2VvbV9zZigpCgpnZ3Bsb3QoZGF0YSA9IHdvcmxkLCBhZXMoZ2VvbWV0cnkgPSBnZW9tZXRyeSkpICsgZ2VvbV9zZigpCgpnZ3Bsb3QoKSArIGdlb21fc2YoZGF0YSA9IHdvcmxkLCBhZXMoZ2VvbWV0cnkgPSBnZW9tZXRyeSkpCmBgYAoKCmBgYHtyLCBlY2hvID0gRkFMU0V9CmdncGxvdChkYXRhID0gd29ybGQpICsgZ2VvbV9zZigpCmBgYAoKQ29tbyBlbCBtYXBhIHF1ZSBoZW1vcyBoZWNobywgZW4gcmVhbGlkYWQgZXMgdW4gZ3LDoWZpY28gZ2dwbG90LCBwdWVzIHlhIHNhYmVtb3MgY2FtYmlhci90dW5lYXIgYWxndW5hcyBkZSBsYXMgY2FyYWN0ZXLDrXN0aWNhcyBkZWwgZ3LDoWZpY28uIFBvciBlamVtcGxvOiAKCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IHdvcmxkKSArIGdlb21fc2YoY29sb3IgPSAiYmxhY2siLCBmaWxsID0gImxpZ2h0Z3JlZW4iLCBsd2QgPSAwLjIpCmBgYAoKClBvZGVtb3MgcG9uZXIgdMOtdHVsb3MgeSBsZXllbmRhczoKCmBgYHtyfQpwIDwtIGdncGxvdChkYXRhID0gd29ybGQpICsgZ2VvbV9zZigpICsKICAgICAgIGxhYnModGl0bGUgPSAiR3LDoWZpY28gMTogTWFwYSBkZWwgbXVuZG8iLAogICAgICAgICAgICBjYXB0aW9uID0gIkRhdG9zIHByb3ZlbmllbnRlcyBkZSBybmF0dXJhbGVhcnRoIikKcApgYGAKCgpQb2RlbW9zIGhhY2VyIG1hcGFzIGRlICoqQ09ST1BMRVRBUyoqOiBsYXMgZGlzdGludGFzIHJlZ2lvbmVzIHNlIGNvbG9yZWFuIGRlIGZvcm1hIHF1ZSBtdWVzdHJlbiBsb3MgdmFsb3JlcyBkZSB1bmEgdmFyaWFibGUgZXN0YWTDrXN0aWNhLiBQb3IgZWplbXBsbywgdW5hIGNvcm9wbGV0YSBjb24gbGEgdmFyaWFibGUgcG9ibGFjacOzbihwb2JfZXN0KQoKCmBgYHtyLCBldmFsID0gRkFMU0V9CnAgKyBnZW9tX3NmKGFlcyhmaWxsID0gcG9wX2VzdCkpCnAgKyBnZW9tX3NmKGFlcyhmaWxsID0gcG9wX2VzdCkpICsgc2NhbGVfZmlsbF9kaXN0aWxsZXIoKQpwICsgZ2VvbV9zZihhZXMoZmlsbCA9IHBvcF9lc3QpKSArIHNjYWxlX2ZpbGxfdmlyaWRpc19jKG9wdGlvbiA9ICJwbGFzbWEiLCB0cmFucyA9ICJzcXJ0IikKYGBgCgoKYGBge3IsIGVjaG8gPSBGQUxTRX0KcCArIGdlb21fc2YoYWVzKGZpbGwgPSBwb3BfZXN0KSkgKyBzY2FsZV9maWxsX3ZpcmlkaXNfYyhvcHRpb24gPSAicGxhc21hIiwgdHJhbnMgPSAic3FydCIpCmBgYAo8YnI+CgojIyMgUHJveWVjY2lvbmVzCgpQYXJhIGNhbWJpYXIgbGEgcHJveWVjY2nDs24gZGUgdW4gb2JqZXRvICJzZiIgYSBvdHJvIENSUyB1c2Ftb3MgbGEgZnVuY2nDs24gIGBzdF90cmFuc2Zvcm0oKWAsIG8gYWx0ZXJuYXRpdmFtZW50ZSBwb2RlbW9zIGVzcGVjaWZpY2FyIGRpcmVjdGFtZW50ZSBlbCBDUlMgZW4gZWwgZ3LDoWZpY28gY29uIGBjb29yZF9zZigpYDoKCmBgYHtyfQojLSB3b3JsZDIgPC0gc3RfdHJhbnNmb3JtKHdvcmxkLCAiK3Byb2o9bGFlYSAreV8wPTAgK2xvbl8wPTE1NSArbGF0XzA9LTkwICtlbGxwcz1XR1M4NCArbm9fZGVmcyIpCnAgKyBjb29yZF9zZihjcnMgPSAiK3Byb2o9bGFlYSAreV8wPTAgK2xvbl8wPTE1NSArbGF0XzA9LTkwICtlbGxwcz1XR1M4NCArbm9fZGVmcyIpICsgCiAgICAgbGFicyhzdWJ0aXRsZSA9ICJMYW1iZXJ0IEF6aW11dGhhbCBFcXVhbCBBcmVhIHByb2plY3Rpb24iKQpgYGAKCjxicj4KClZhbW9zIGEganVnYXIgdW4gcG9jbyBjb24gZWwgQ1JTIGNvbiBsYSBmdW5jacOzbiBgY29vcmRfc2YoKWAuIEhheSBxdWUgc2FiZXIgcXVlIGdncGxvdCB1c2Fyw6EgZWwgQ1JTIGRlIGxhIHByaW1lcmEgY2FwYSBxdWUgY29udGVuZ2EgdW5vLiBTaSBubyBodWJpZXNlIG5pbmfDum4gQ1JTIHNlIHVzYSAqKldHUzg0KiogKGxhdGl0dWRlL2xvbmdpdHVkZSwgdGhlIHJlZmVyZW5jZSBzeXN0ZW0gdXNlZCBpbiBHUFMpLgoKU2UgcHVlZGUgY2FtYmlhciBlbCBDUlMgZGUgdmFyaWFzIG1hbmVyYXM6CgoxLiB1c2FuZG8gdW4gYCoqc3RyaW5nIFBST0oqKiB2w6FsaWRvLiBQb3IgZWplbXBsbyBlc3RlIHN0cmluZyBgIitwcm9qPWxhZWEgK2xhdF8wPTUyICtsb25fMD0xMCAreF8wPTQzMjEwMDAgK3lfMD0zMjEwMDAwICtlbGxwcz1HUlM4MCArdW5pdHM9bSArbm9fZGVmcyAiYCBlcyBlbCBFdXJvcGVhbi1jZW50cmljIEVUUlM4OSBMYW1iZXJ0IEF6aW11dGhhbCBFcXVhbC1BcmVhIHByb2plY3Rpb24pCgoyLiB1c2FuZG8gdW4gKipjw7NkaWdvIFNSSUQqKiAoU3BhdGlhbCBSZWZlcmVuY2UgU3lzdGVtIElkZW50aWZpZXIpICAKCjMuIFVzYW5kbyB1biAqKmPDs2RpZ28gRVBHUyoqIChFdXJvcGVhbiBQZXRyb2xldW0gU3VydmV5IEdyb3VwKV5bUGFyZWNlIHF1ZSBjb24gUFJPSjcgZXMgbWVqb3Igbm8gdXNhcmxvXQoKTG9zIHNpZ3VpZW50ZXMgdHJlcyBncsOhZmljb3MgaGFjZW4gbG8gbWlzbW8uIFJlcHJlc2VudGFuIGxvcyBkYXRvcyBkZWwgc3BhdGlhbC5kYXRhLmZyYW1lIGB3b3JsZGAgdXNhbmRvIHRoZSBFVFJTODkgTGFtYmVydCBBemltdXRoYWwgRXF1YWwtQXJlYSBwcm9qZWN0aW9uLCBjdXlvIGPDs2RpZ28gRVBTRyBlcyAzMDM1LiBbQXF1w61dKGh0dHA6Ly93d3cuanVudGFkZWFuZGFsdWNpYS5lcy9tZWRpb2FtYmllbnRlL3NpdGUvcmVkaWFtL21lbnVpdGVtLjA0ZGM0NDI4MWU1ZDUzY2Y4Y2E3OGNhNzMxNTI1ZWEwLz92Z25leHRvaWQ9MmE0MTJhYmNiODZhMjIxMFZnblZDTTEwMDAwMDEzMjVlNTBhUkNSRCZscj1sYW5nX2VzKSB0aWVuZXMgYWxndW5vcyBjw7NkaWdvcyBkZSBwcm95ZWNjaW9uZXM6CiAKCmBgYHtyLCBldmFsID0gRkFMU0V9CnAgKyBjb29yZF9zZihjcnMgPSAiK3Byb2o9bGFlYSArbGF0XzA9NTIgK2xvbl8wPTEwICt4XzA9NDMyMTAwMCAreV8wPTMyMTAwMDAgK2VsbHBzPUdSUzgwICt1bml0cz1tICtub19kZWZzICIpICAjLSB1c2Ftb3MgUFJPSiBzdHJpbmcKCnAgKyBjb29yZF9zZihjcnMgPSBzdF9jcnMoMzAzNSkpICAgIy0gdXNhbW9zIFNSSUQgaWRlbnRpZmllcgoKcCArIGNvb3JkX3NmKGNycyA9ICIraW5pdD1lcHNnOjMwMzUiKSAgIy0gdXNhbW9zIEVQU0cgY29kZSAobWVqb3Igbm8gdXNhcmxvIHhxIEdEQUwgaGEgZGVwcmVjYXRlZCBlc3RhIHNpbnRheGlzKQpgYGAKCgpgYGB7ciwgZWNobyA9IFRSVUV9CnAgKyBjb29yZF9zZihjcnMgPSBzdF9jcnMoMzAzNSkpICsgIy0gdXNhbW9zIFNSSUQgaWRlbnRpZmllcgogICAgbGFicyhzdWJ0aXRsZSA9ICIoRXVyb3BlYW4tY2VudHJpYyBFVFJTODkgXG5MYW1iZXJ0IEF6aW11dGhhbCBFcXVhbC1BcmVhIHByb2plY3Rpb24pIikKYGBgCgoKPGJyPgoKIyMjIFpvb20KClZvbHZlbW9zIGEgbGEgcHJveWVjY2nDs24gbmF0aXZhIGRlbCBjb25qdW50byBkZSBkYXRvcyBgd29ybGRgIHkgaGFjZW1vcyB1biB6b29tIGVuIG51ZXN0cm8gbWFwYSBjb24gYGNvb3JkX3NmKClgLiBFbCB6b29tIGxvIGhhY2Vtb3MgcGFyZSBjZW50cmFybm9zIGVuIEVzcGHDsWEgeSBhbHJlZGVkb3Jlcy4gUGFyYSBoYWNlciBlbCB6b29tIGVuIHJlYWxpZGFkIGFqdXN0YW1vcyBsb3MgbMOtbWl0ZXMgZGUgbG9zIGVqZXMgZGVsIGdyw6FmaWNvCgoKYGBge3J9CnBfZXNwIDwtIHAgKyBjb29yZF9zZih4bGltID0gYygtMTUuMDAsIDE1LjAwKSwgeWxpbSA9IGMoMjEsIDQ3LjQ0KSwgZXhwYW5kID0gRkFMU0UpCnBfZXNwCmBgYAoKQ29uIGVsIHBhcXVldGUgYGdnc3BhdGlhbGAgcG9kZW1vcyBwb25lciBlbGVtZW50b3MgZGUgbWFwYXMgY29tbyB1bmEgKlNjYWxlIGJhciogY29uIGBhbm5vdGF0aW9uX3NjYWxlKClgIHkgdW5hICpOb3J0aCBhcnJvdyogY29uIGBhbm5vdGF0aW9uX25vcnRoX2Fycm93KClgOgoKCmBgYHtyfQpwX2VzcCArIAogZ2dzcGF0aWFsOjphbm5vdGF0aW9uX3NjYWxlKGxvY2F0aW9uID0gImJsIiwgd2lkdGhfaGludCA9IDAuNSkgKwogZ2dzcGF0aWFsOjphbm5vdGF0aW9uX25vcnRoX2Fycm93KGxvY2F0aW9uID0gImJsIiwgd2hpY2hfbm9ydGggPSAidHJ1ZSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhZF94ID0gdW5pdCgwLjc1LCAiaW4iKSwgcGFkX3kgPSB1bml0KDAuNSwgImluIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3R5bGUgPSBub3J0aF9hcnJvd19mYW5jeV9vcmllbnRlZXJpbmcpCmBgYAoKRsOtamF0ZSBlbiBlbCAqKndhcm5pbmcqKjogYFNjYWxlIG9uIG1hcCB2YXJpZXMgYnkgbW9yZSB0aGFuIDEwJSwgc2NhbGUgYmFyIG1heSBiZSBpbmFjY3VyYXRlYC4gQ29tbyBlbCBtYXBhIHVzYSB1biBDUlMgKipubyBwcm95ZWN0YWRvKiosIHNpbm8gZ2VvZ3LDoWZpY28sIGxvcyBkYXRvcyBlc3TDoW4gZW4gbG9uZ2l0dWQvbGF0aXR1ZCAoV0dTODQpIGVuIHVuYSBwcm95ZWNjacOzbiBjaWzDrW5kcmljYSBlcXVpZGlzdGFudGUgKHRvZG9zIGxvcyBtZXJpZGlhbm9zIHNvbiBwYXJhbGVsb3MpLCBlbnRvbmNlcywgbGEgZGlzdGFuY2lhIGVuIGtpbMOzbWV0cm9zIGVuIGVsIG1hcGEgZGVwZW5kZSBkZSBsYSBsYXRpdHVkLiBMb3MgbWFwYXMgZGUgcmVnaW9uZXMgcGVxdWXDsWFzIGdlbmVyYWxtZW50ZSBwcm9wb3JjaW9uYW4gYmFycmFzIGRlIGVzY2FsYSBtw6FzIHByZWNpc2FzIHF1ZSBsYXMgcmVnaW9uZXMgZXh0ZW5zYXMgY29tbyBsYSBxdWUgdmVtb3MgZW4gZWwgZ3LDoWZpY28gYW50ZXJpb3IuCgo8YnI+CgojIyMgdGV4dG8gZW4gZWwgbWFwYQoKRWwgbWFwYSBzZSBoYSBoZWNobyBjb24gYGdncGxvdDJgLCBhc8OtIHF1ZSBwb2RlbW9zIGHDsWFkaXIsIHBvciBlamVtcGxvLCB0ZXh0byBjb24gYGdlb21fdGV4dCgpYDsgcGVybyBhbnRlcyBkZSBlbGxvLCB2YW1vcyBhIGNyZWFyL2NhbGN1bGFyIGNlbnRyb2lkZXMgZGUgY2FkYSBwYcOtcyBwYXJhIHZlciBkb25kZSBzaXR1YW1vcyBlbCB0ZXh0byBlbiBlbCBtYXBhLiAKCmBgYHtyfQp3b3JsZF9wb2ludHMgPC0gc3RfY2VudHJvaWQod29ybGQpICAgICMtIHN1c3RpdHV5ZSBsYSBnZW9tZXRyeSBwb3IgZWwgY2VudHJvaWRlCndvcmxkX3BvaW50cyA8LSBjYmluZCh3b3JsZCwgc3RfY29vcmRpbmF0ZXMoc3RfY2VudHJvaWQod29ybGQkZ2VvbWV0cnkpKSkgIy0gYWhvcmEgZWwgY2VudHJvaWRlIHBhc2EgYSBlc3RhciBlbiAyIGNvbHVtbmFzLCBsbGFtYWRhcyBYIGUgWSwgZG9uZGUgZmlndXJhbiBsYSBsb25naXR1ZCB5IGxhdGl0dWQgZGVsIGNlbnRyb2lkZQpgYGAKClBhcmEgY2FsY3VsYXIgbG9zIGNlbnRyb2lkZXMgaGVtb3MgdXNhZG8gbGEgZnVuY2nDs24gYHN0X2NlbnRyb2lkKClgLiBMb3MgZGF0b3MgZGVsIGNlbnRyb2lkZSAobG9uZ2l0dWQsIGxhdGl0dWQpIHNlIGFsbWFjZW5hbiB1bmEgbnVldmEgY29sdW1uYSBsbGFtYWRhIGBnZW9tZXRyeWA7IHBlcm8gYWwgZnVzaW9uYXIgbG9zIGRvcyAqZGF0YS5mcmFtZSdzKiwgZWwgY2VudHJvaWRlIHBhc2EgYSBlc3RhciBlbiBkb3MgbnVldmFzIGNvbHVtbmFzIGxsYW1hZGFzIFggZSBZLiAKClBhcmEgdmVyIGVzYXMgZG9zIG51ZXZhcyBjb2x1bW5hcyAoWCBlIFkpLCB2YW1vcyBhIGhhY2VyIHVuICJwZXF1ZcOxbyB0cnVjbyIsIHZhbW9zIGEgcXVpdGFyIGxhIGNvbHVtbmEgZGUgZ2VvbWV0csOtYXMuIEVzdG8gZmFjaWxpdGEgZWwgdmlzaW9uYWRvIGRlIGxvcyBkYXRvcywgcGVybyBsw7NnaWNhbWVudGUgY3VhbmRvIHZheWFtb3MgYSBoYWNlciBlbCBncsOhZmljbywgcmVhbG1lbnRlIG5vIHBvZHJlbW9zIGVsaW1pbmFyIGxhIGdlb21ldHJ5IHBvcnF1ZSBzaSBubywgbG9zIGRhdG9zIGRlamFyw61hbiBkZSBzZXIgZGF0b3MgZXNwYWNpYWxlcy4KCmBgYHtyfQojLSB0cnVjbyBwYXJhIHZlciBtZWpvciBsb3MgZGF0b3MgZXNwYWNpYWxlczogcXVpdGFyIGxhIGdlb21ldHLDrWEKbmFtZXMod29ybGRfcG9pbnRzKQp6eiA8LSB3b3JsZF9wb2ludHMgJT4lIHNlbGVjdChuYW1lLCBpc29fYTMsICBYLCBZKSAlPiUgIAogICAgICBzdF9zZXRfZ2VvbWV0cnkoTlVMTCkgICMtIHBvciBzaSBxdWllcm8gdmVyIG1lam9yIGxvcyBkYXRvcyBkZWwgZGYKYGBgCgoKSGFjZW1vcyBlbCBtYXBhIGNvbiBsb3Mgbm9tYnJlcyBkZSBsb3MgcGHDrXNlcyBjb24gYGdlb21fdGV4dCgpYCB5IGVsIG5vbWJyZSBkZWwgTWFyIE1lZGl0ZXJyw6FuZW8gY29uIGBhbm5vdGF0ZSgpYAoKYGBge3J9CnBfZXNwICsgCiAgZ2VvbV90ZXh0KGRhdGEgPSB3b3JsZF9wb2ludHMsIAogICAgICAgICAgICBhZXMoeCA9IFgsIHkgPSBZLCBsYWJlbCA9IG5hbWUpLCAKICAgICAgICAgICAgY29sb3IgPSAiZGFya2JsdWUiLCBmb250ZmFjZSA9ICJib2xkIiwgY2hlY2tfb3ZlcmxhcCA9IFRSVUUsIHNpemUgPSAyLjUpICsKICBhbm5vdGF0ZShnZW9tID0gInRleHQiLCB4ID0gNS41LCB5ID0gMzgsIAogICAgICAgICAgIGxhYmVsID0gIk1hciBNZWRpdGVycsOhbmVvIiwgZm9udGZhY2UgPSAiaXRhbGljIiwgY29sb3IgPSAiZ3JleTIyIiwgc2l6ZSA9IDQpCmBgYAoKCkVsIG5vbWJyZSBkZSBGcmFuY2lhIG5vIGhhIHNhbGlkbyBiaWVuLiBMbyBoZW1vcyBzaXR1YWRvIGVuIEVzcGHDsWEsIGVzIHBvciBzdXMgdGVycml0b3Jpb3MgZW4gQW3DqXJpY2E6IEd1YWRhbHVwZSwgTWFydGluaWNhLCAuLi4gUGFyYSBhcnJlZ2xhcmxvIHBvZHLDrWFtb3MgdXNhciBgc3RfcG9pbnRfb25fc3VyZmFjZSgpYCBwZXJvIHNlcsOhIG90cm8gZMOtYS4gSG95IHZhbW9zIGEgYXJyZWdsYXJsbyBhIGxvIGJydXRvLCB2YW1vcyBhIHF1aXRhciBhIEZybmFjaWEgZGVsIGRhdGEuZnJhbWUgd29ybGRfcG9pbnRzIHkgYSBjb3JyZXIuCgpgYGB7cn0Kd29ybGRfcG9pbnRzIDwtIHdvcmxkX3BvaW50cyAlPiUgZmlsdGVyKGFkbWluICE9ICJGcmFuY2UiKQpgYGAKCgo8YnI+CgojIyMgVHVuZWFuZG8gZGVsIG1hcGEKCgpWYW1vcyBhIHR1bmVhciBlbCBtYXBhLiBQb3IgZWplbXBsbywgdmFtb3MgYSBwb25lciBlbCBtYXIgKGVuIHJlYWxpZGFkIGFsIGBiYWNrZ3JvdW5kYCBkZWwgYHRoZW1lYCkgZGVsIGNvbG9yIGBhbGljZWJsdWVgLCB5IGEgbG9zIGNvbnRpbmVudGVzIGxlcyBwb25kcmVtb3MgdW4gY29sb3IgdGVycm9zbyBjb24gYGZpbGwgPSAiYW50aXF1ZXdoaXRlImAuIEFycmVnbGFtb3MgdW4gcG9jbyBsYXMgZ3JpZC1saW5lcyBkZW50cm8gZGVsIGB0aGVtZWAsIHkgbGUgcG9uZW1vcyBsYSBlc3RyZWxsaXRhLiBObyBub3MgdmEgYSBzYWxpciBib25pdG8tYm9uaXRvIHBvcnF1ZSBsb3Mgbm9tYnJlcyBkZSBsb3MgcGHDrXNlcyBzZSB2YW4gYSBzdXBlcnBvbmVyLiBQb2Ryw61hbW9zIHF1aXRhciBub21icmVzLCBwZXJvIG90cm8gZMOtYToKCgpgYGB7cn0KcCA8LSBwICsgCiAgZ2VvbV9zZihmaWxsID0gImFudGlxdWV3aGl0ZSIpICsKICBnZW9tX3RleHQoZGF0YSA9IHdvcmxkX3BvaW50cywgCiAgICAgICAgICAgIGFlcyh4ID0gWCwgeSA9IFksIGxhYmVsID0gbmFtZSksIAogICAgICAgICAgICAgICAgY29sb3IgPSAiZGFya2JsdWUiLCBmb250ZmFjZSA9ICJib2xkIiwgCiAgICAgICAgICAgICAgICBjaGVja19vdmVybGFwID0gVFJVRSwgc2l6ZSA9IDIpICsKICBhbm5vdGF0aW9uX25vcnRoX2Fycm93KGxvY2F0aW9uID0gImJsIiwgd2hpY2hfbm9ydGggPSAidHJ1ZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICBwYWRfeCA9IHVuaXQoMC43NSwgImluIiksIAogICAgICAgICAgICAgICAgICAgICAgICAgcGFkX3kgPSB1bml0KDAuNSwgImluIiksCiAgICAgICAgICAgICAgICAgICAgICAgICBzdHlsZSA9IG5vcnRoX2Fycm93X2ZhbmN5X29yaWVudGVlcmluZykgKwogIHRoZW1lKHBhbmVsLmdyaWQubWFqb3IgPSBlbGVtZW50X2xpbmUoY29sb3IgPSBncmF5KC41KSwgbGluZXR5cGUgPSAiZGFzaGVkIiwgc2l6ZSA9IDAuNSksIAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJhbGljZWJsdWUiKSkKcApgYGAKCkhhY2Vtb3Mgb3RyYSB2ZXogdW4gem9vbSBjb24gYHNmOjpjb29yZF9zZigpYAoKCmBgYHtyfQpwICsgY29vcmRfc2YoeGxpbSA9IGMoLTE1LjAwLCAxNS4wMCksIHlsaW0gPSBjKDIxLCA0Ny40NCksIGV4cGFuZCA9IEZBTFNFKSArCiAgICBnZ3NwYXRpYWw6OmFubm90YXRpb25fc2NhbGUobG9jYXRpb24gPSAiYmwiLCB3aWR0aF9oaW50ID0gMC41KQpgYGAKCgpDb21vIG5vcyBoYSBxdWVkYWRvIG1lZGlvIGNodWxvLCBzaSBxdWlzacOpcmFtb3MgZ3VhcmRhciBlbCBtYXBhIGhhcsOtYW1vczoKCmBgYHtyLCBldmFsID0gRkFMU0V9Cmdnc2F2ZSgibWFwLnBkZiIpCmdnc2F2ZSgibWFwX3dlYi5wbmciLCB3aWR0aCA9IDYsIGhlaWdodCA9IDYsIGRwaSA9ICJzY3JlZW4iKQpgYGAKCjxicj4KCi0tLS0tLS0tLS0tLS0tLQoKPGJyPgoKCiMgMy4gTWFzIHR1bmVhZG8KClZhbW9zIGEgaXIgbW9kaWZpY2FuZG8gZWwgbWFwYSwgbm8gbmVjZXNhcmlhbWVudGUgcGFyYSBoYWNlcmxvIG3DoXMgYm9uaXRvLCBzaW5vIHBhcmEgdmVyIGNvbW8gc2UgcHVlZGVuIGhhY2VyIG1hcGFzIGNvbiBgZ2dwbG90MmA6CgojIyMgYcOxYWRpZW5kbyBwdW50b3MKClByaW1lcm8gcXVpZXJvIGHDsWFkaXIgYWwgbWFwYSBkb25kZSBlc3TDoSBtaSBwdWVibG8geSBkb25kZSBlc3TDoSBWYWxlbmNpYSwgdmFtb3MgcXVlIHZveSBhIGHDsWFkaXIgcHVudG9zIGFsIG1hcGEuIAoKUGFyYSBlbGxvLCBwcmltZXJvIG5lY2VzaXRvIHRlbmVyIGxhIGxvY2FsaXphY2nDs24gZGUgZXN0b3MgcHVudG9zIGVuIGVsIG1hcGEsIGFzw60gcXVlIG1lIGZ1aSBhIFtPU00sIE9wZW5TdHJlZXRNYXBdKGh0dHBzOi8vd3d3Lm9wZW5zdHJlZXRtYXAub3JnLyNtYXA9MTQvLTEuOTQyMi8zMS41ODY2JmxheWVycz1DKSB5IGdlb2xvY2FsaWPDqSBsb3MgZG9zIG11bmljaXBpb3MgeSBjb24gZXN0YSBpbmZvcm1hY2nDs24sIHZveSAgYSBjcmVhciB1biBkYXRhLmZyYW1lIGNvbiBsYSBsb25nL2xhdCBkZSBtaSBwdWVibG8geSBkZSBWYWxlbmNpYTogCgoKYGBge3J9CnB1ZWJsb3MgPC0gZGF0YS5mcmFtZSgKICAgICAgICAgICAgICAgbm9tYnJlID0gYygiUGFuY3J1ZG8iLCAiVmFsZW5jaWEiKSwgCiAgICAgICAgICAgICAgIGxvbmdpdHVkZSA9IGMoLTEuMDI4NzgxLCAtMC4zNzU2NTcyKSwKICAgICAgICAgICAgICAgbGF0aXR1ZGUgPSBjKDQwLjc2MTc1LCAzOS40NzUzNCkgKSAgICAgCnB1ZWJsb3MKYGBgCgoKLSBsbyBtYXMgZsOhY2lsIHBhcmEgYcOxYWRpciBwdW50b3MgZXMgdXNhciBgZ2VvbV9wb2ludCgpYAoKYGBge3J9CnBfZXNwICsgCiAgZ2VvbV9wb2ludChkYXRhID0gcHVlYmxvcywgCiAgICAgICAgICAgICBhZXMoeCA9IGxvbmdpdHVkZSwgeSA9IGxhdGl0dWRlKSwgCiAgICAgICAgICAgICBzaXplID0gMiwgY29sb3IgPSAiZGFya3JlZCIpICsKICBnZW9tX3RleHQoZGF0YSA9IHB1ZWJsb3MsIAogICAgICAgICAgICBhZXMoeCA9IGxvbmdpdHVkZSwgeSA9IGxhdGl0dWRlLCBsYWJlbCA9IG5vbWJyZSksIAogICAgICAgICAgICBjb2xvciA9ICJkYXJrYmx1ZSIsIGZvbnRmYWNlID0gImJvbGQiLCBjaGVja19vdmVybGFwID0gVFJVRSwgc2l6ZSA9IDIuNSkKYGBgCgpVdGlsaXphciBgZ2VvbV9wb2ludCgpYCBlcyBmw6FjaWwgeSBmdW5jaW9uYSwgcGVybyBlcyBtw6FzIGZsZXhpYmxlIGNvbnZlcnRpciBlbCBkYXRhLmZyYW1lIGEgdW4gb2JqZXRvIGBzZmAuIEVzdG8gbG8gcG9kZW1vcyBoYWNlciBjb24gbGEgZnVuY2nDs24gYHN0X2FzX3NmKClgLCBkZWZpbmllbmRvIGxhIHByb3llY2Npw7NuIChhcXXDrSBXR1M4NCwgcXVlIGVzIGVsIGPDs2RpZ28gQ1JTICMgNDMyNik6CgoKYGBge3J9CnB1ZWJsb3Nfc2YgPC0gc3RfYXNfc2YocHVlYmxvcywgCiAgICAgICAgICAgICAgICAgICAgICAgY29vcmRzID0gYygibG9uZ2l0dWRlIiwgImxhdGl0dWRlIiksIAogICAgICAgICAgICAgICAgICAgICAgIGNycyA9IDQzMjYsICAjLSBFUFNHOjQzMjYgQ29vcmRlbmFkYXMgR2VvZ3LDoWZpY2FzIFdHUzg0CiAgICAgICAgICAgICAgICAgICAgICAgYWdyID0gImNvbnN0YW50IikKYGBgCgpQYXJhIGRlc3B1w6lzIGHDsWFkaXJsbyBjb21vIHVuYSBjYXBhIG3DoXMgY29uIGBnZW9tX3NmKClgIGEgbnVlc3RybyBncsOhZmljbzogCgpgYGB7cn0KcF9lc3AgKyBnZW9tX3NmKGRhdGEgPSBwdWVibG9zX3NmLCBzaXplID0gMiwgY29sb3IgPSAiZGFya3JlZCIpCmBgYAoKSGFnYW1vcyB6b29tIGNvbiBgY29vcmRfc2ZgCgpgYGB7cn0KcF9lc3AgKyBnZW9tX3NmKGRhdGEgPSBwdWVibG9zX3NmLCBzaXplID0gMiwgY29sb3IgPSAiZGFya3JlZCIpICsgCiAgICAgICAgY29vcmRfc2YoeGxpbSA9IGMoLTE1LjAwLCAxNS4wMCksIHlsaW0gPSBjKDIxLCA0Ny40NCksIGV4cGFuZCA9IEZBTFNFKQpgYGAKCgojIyMgQcOxYWRpZW5kbyBwb2zDrWdvbm9zCgpMYSB2ZXJkYWQgZXMgcXVlIHNlIGhhY2UgaWd1YWwgcXVlIGNvbiBsb3MgcHVudG9zLCBoYXkgcXVlIHVzYXIgbGEgZnVuY2nDs24gYGdlb21fc2YoKWAgeSBhIGNvcnJlci4KClZhbW9zIGEgYcOxYWRpciBsb3MgY29udG9ybm9zL3BvbMOtZ29ub3MgZGUgbGFzIENDLkFBOgoKCmBgYHtyfQp6el9DQ0FBIDwtIENDQUEgJT4lIHN0X3NldF9nZW9tZXRyeShOVUxMKQpwICsgZ2VvbV9zZihkYXRhID0gQ0NBQSwgZmlsbCA9IE5BKSArIAogICAgICBjb29yZF9zZih4bGltID0gYygtMTUuMDAsIDE1LjAwKSwgeWxpbSA9IGMoMjEsIDQ3LjQ0KSwgZXhwYW5kID0gRkFMU0UpCmBgYAoKQWhvcmEgYcOxYWRpbW9zIGxhcyBwcm92aW5jaWFzIGRlIEFuZGFsdWPDrWEsIFBFUk8gY29sb3JlYWRhcyBzZWfDum4gc3UgcG9ibGFjacOzbiBtZWRpYSBwb3IgbXVuaWNpcGlvLiBQYXJhIGVsbG86IAoKLSAxLiBwcmltZXJvIGNhcmdhbW9zIGRhdG9zIGRlIHBvYmxhY2nDs24KCmBgYHtyfQpJTkVfcGFkcm9uX211bmlfOTZfMTkgPC0gcGpwdjIwMjAuMDE6OnBqcF9kYXRhX3BvYl9tdW5fMTk5Nl8yMDE5CnBvYl9hbmRhbHVjaWEgPC0gSU5FX3BhZHJvbl9tdW5pXzk2XzE5ICU+JSAKICAgICAgICAgICAgICAgICBmaWx0ZXIoeWVhciA9PSAyMDE3KSAlPiUgCiAgICAgICAgICAgICAgICAgZmlsdGVyKHBvYmxhY2lvbiA9PSAiVG90YWwiKSAlPiUgCiAgICAgICAgICAgICAgICAgZmlsdGVyKElORUNvZENDQUEubiA9PSAiQW5kYWx1Y8OtYSIpICU+JQogICAgICAgICAgICAgICAgIGdyb3VwX2J5KElORUNvZFByb3YubikgJT4lIG11dGF0ZShuX3B1ZWJsb3MgPSBuX2Rpc3RpbmN0KElORUNvZE11bmkubikpICU+JQogICAgICAgICAgICAgICAgIG11dGF0ZShwb2JfcHJvdiA9IHN1bShwb2JfdmFsdWVzLCBuYS5ybSA9IFRSVUUpKSAlPiUgdW5ncm91cCgpICU+JQogICAgICAgICAgICAgICAgIG11dGF0ZShwb2JfbWVkaWFfcHVlYmxvcyA9IHBvYl9wcm92L25fcHVlYmxvcykgJT4lIAogICAgICAgICAgICAgICAgIHNlbGVjdChJTkVDb2RQcm92Lm4sIElORUNvZFByb3YsIHBvYl9tZWRpYV9wdWVibG9zLCBwb2JfcHJvdiwgbl9wdWVibG9zKSAlPiUKICAgICAgICAgICAgICAgICBkaXN0aW5jdCgpICU+JSB1bmdyb3VwKCkKYGBgCgoKCi0gMi4gZGVzcHXDqXMgY2FyZ2Ftb3MgbGluZGVzIHByb3ZpbmNpYWxlcyAoZW4gcmVhbGlkYWQgeWEgbG9zIHRlbmVtb3MpCgpgYGB7cn0KIyBQcm92aW5jaWFzIDwtIFByb3ZpbmNpYXMKenpfUHJvdmluY2lhcyA8LSBQcm92aW5jaWFzICU+JSBzdF9zZXRfZ2VvbWV0cnkoTlVMTCkKYGBgCgoKLSAzLiBmdXNpb25hbW9zIGxvcyBkYXRvcyBkZSBwb2JsYWNpw7NuIHkgbG9zIGxpbmVzIHByb3ZpbmNpYWxlczoKCmBgYHtyfQpnZW9fcG9iX2FuZGFsdWNpYSA8LSBsZWZ0X2pvaW4ocG9iX2FuZGFsdWNpYSwgUHJvdmluY2lhcywgYnkgPSBjKCJJTkVDb2RQcm92IiA9ICJJTkVDb2RQcm92IikpCmdlb19wb2JfYW5kYWx1Y2lhIDwtIGxlZnRfam9pbihwb2JfYW5kYWx1Y2lhLCBQcm92aW5jaWFzKQpgYGAKCgotIDQuIGZpbmFsbWVudGUgYcOxYWRpbW9zIGxhcyBwcm92aW5jaWFzIGNvbG9yZWFkYXMgcG9yIHBvYmxhY2nDs24gYWwgbWFwYToKCmBgYHtyfQpwICsgZ2VvbV9zZihkYXRhID0gQ0NBQSwgZmlsbCA9IE5BKSArCiAgICBnZW9tX3NmKGRhdGEgPSBnZW9fcG9iX2FuZGFsdWNpYSwgYWVzKGdlb21ldHJ5ID0gZ2VvbWV0cnksIGZpbGwgPSBuX3B1ZWJsb3MpKSArCiAgICBzY2FsZV9maWxsX3ZpcmlkaXNfYyhhbHBoYSA9IC40KSArCiAgICBnZW9tX3NmKGRhdGEgPSBwdWVibG9zX3NmLCBzaXplID0gMiwgY29sb3IgPSAiZGFya3JlZCIpICsKICAgIGNvb3JkX3NmKHhsaW0gPSBjKC0xNS4wMCwgMjUuMDApLCB5bGltID0gYygyMSwgNTcuNDQpLCBleHBhbmQgPSBGQUxTRSkKYGBgCgotIHZhbW9zIGHDsWFkaXIgdGFtYmnDqW4gMyBtdW5pY2lwaW9zIGVzcGHDsW9sZXMgc2VsZWNjaW9uYWRvcyBhbCBhemFyOgoKYGBge3IsIGV2YWwgPSBGQUxTRX0KcHVlYmxvc18zIDwtIElHTl9ub21lbmNsYV9tdW5pICU+JSAKICAgICAgICAgICAgIHNhbXBsZV9uKDMpICU+JSAKICAgICAgICAgICAgIHNlbGVjdChOT01CUkVfQUNUVUFMLCBQUk9WSU5DSUEsIExPTkdJVFVEX0VUUlM4OSwgTEFUSVRVRF9FVFJTODkpICU+JQogICAgICAgICAgICAgc3RfYXNfc2YoY29vcmRzID0gYygiTE9OR0lUVURfRVRSUzg5IiwgIkxBVElUVURfRVRSUzg5IiksIGNycyA9IDQzMjYsIGFnciA9ICJjb25zdGFudCIsIHJlbW92ZSA9IEZBTFNFKQpgYGAKCi0gQnVlbm8sIGVuIHJlYWxpZGFkIG5vIGxvcyB2b3kgYSBlbGVnaXIgdGFuIHRhbiBhbCBhemFyOgoKYGBge3IsIGV2YWwgPSBUUlVFfQpwdWVibG9zXzMgPC0gSUdOX25vbWVuY2xhX211bmkgJT4lIAogICAgICAgICAgICAgZmlsdGVyKE5PTUJSRV9BQ1RVQUwgJWluJSBjKCJQYW5jcnVkbyIsICJBbGJ1cnF1ZXJxdWUiLCAiUG9sZW50aW5vcyIpKSAlPiUgCiAgICAgICAgICAgICBzZWxlY3QoTk9NQlJFX0FDVFVBTCwgUFJPVklOQ0lBLCBMT05HSVRVRF9FVFJTODksIExBVElUVURfRVRSUzg5KSAlPiUKICAgICAgICAgICAgIHN0X2FzX3NmKGNvb3JkcyA9IGMoIkxPTkdJVFVEX0VUUlM4OSIsICJMQVRJVFVEX0VUUlM4OSIpLCBjcnMgPSA0MzI2LCBhZ3IgPSAiY29uc3RhbnQiLCByZW1vdmUgPSBGQUxTRSkKYGBgCgoKYGBge3J9CnBfZXNwIDwtIHAgKyBnZW9tX3NmKGRhdGEgPSBDQ0FBLCBmaWxsID0gTkEpICsKICAgICAgICAgZ2VvbV9zZihkYXRhID0gZ2VvX3BvYl9hbmRhbHVjaWEsIGFlcyhnZW9tZXRyeSA9IGdlb21ldHJ5LCBmaWxsID0gbl9wdWVibG9zKSkgKwogICAgICAgICBzY2FsZV9maWxsX3ZpcmlkaXNfYyhhbHBoYSA9IC40KSArCiAgICAgICAgIGdlb21fc2YoZGF0YSA9IHB1ZWJsb3NfMywgc2l6ZSA9IDIsIGNvbG9yID0gImRhcmtyZWQiKSArCiAgICAgICAgIGdncmVwZWw6Omdlb21fbGFiZWxfcmVwZWwoZGF0YSA9IHB1ZWJsb3NfMywgYWVzKHggPSBMT05HSVRVRF9FVFJTODksIHkgPSBMQVRJVFVEX0VUUlM4OSwgbGFiZWwgPSBOT01CUkVfQUNUVUFMKSkgKwogICAgICAgICBjb29yZF9zZih4bGltID0gYygtMTAuMDAsIDcuMDApLCB5bGltID0gYygzNSwgNDUpLCBleHBhbmQgPSBGQUxTRSkKcF9lc3AKYGBgCgoKIyMjIENvbiBsb3MgbWFwYXMgeSBncsOhZmljb3MgbnVuY2Egc2UgYWNhYmEhISEKCkNhbmFyaWFzIG5vIHNlIHZlLiDCv0xhIHNpdHVhbW9zIGVuIGVsIG1hcGE/IFVzYXJlbW9zIGBzZjo6c3RfYmJveCgpYAoKCmBgYHtyfQpjYW5hcmlhcyA8LSBQcm92aW5jaWFzICU+JSBmaWx0ZXIoSU5FQ29kUHJvdiAlaW4lIGMoMzUsMzgpKQpwZW5pbnN1bGEgPC0gUHJvdmluY2lhcyAlPiUgZmlsdGVyKCAhKElORUNvZFByb3YgJWluJSBjKDM1LCAzOCkpICkKbXlfc2hpZnQgPC0gc3RfYmJveChwZW5pbnN1bGEpW2MoMSwyKV0tIChzdF9iYm94KGNhbmFyaWFzKVtjKDEsMildKSArIGMoLTIuNCwgLTEuMSkKY2FuYXJpYXMkZ2VvbWV0cnkgPC0gY2FuYXJpYXMkZ2VvbWV0cnkgKyBteV9zaGlmdApzdF9jcnMoY2FuYXJpYXMpICA8LSBzdF9jcnMocGVuaW5zdWxhKQpwZW5pbnN1bGFfeV9jYW5hcmlhcyA8LSByYmluZChwZW5pbnN1bGEsIGNhbmFyaWFzKQoKcDEgPC0gZ2dwbG90KCkgKyBnZW9tX3NmKGRhdGEgPSBwZW5pbnN1bGFfeV9jYW5hcmlhcykKcDIgPC0gZ2dwbG90KCkgKyBnZW9tX3NmKGRhdGEgPSBwZW5pbnN1bGEpICsgZ2VvbV9zZihkYXRhID0gY2FuYXJpYXMsIGZpbGwgPSAicHVycGxlIikgCiNjb29yZF9zZih4bGltID0gYygtMTIuMDAsIDQuMDApLCB5bGltID0gYygzMywgNDQpLCBleHBhbmQgPSBGQUxTRSkKCnAxICsgcDIgCmBgYAoKCi0gaGFnYW1vcyB6b29tCgpgYGB7cn0KZ2dwbG90KCkgKyBnZW9tX3NmKGRhdGEgPSBwZW5pbnN1bGEpICsKICAgICAgICAgICBnZW9tX3NmKGRhdGEgPSBjYW5hcmlhcykgKwogICAgICAgICAgIGNvb3JkX3NmKHhsaW0gPSBjKC0xMi4wMCwgNC4wMCksIHlsaW0gPSBjKDMzLCA0NCksIGV4cGFuZCA9IEZBTFNFKQpgYGAKCgotIHBvbmdhbW9zIENhbmFyaWFzLCBwaW50YWRhIGVuIHJvam8sIGVuIG51ZXN0cm8gbWFwYSAKCgpgYGB7cn0KcF9lc3AgKyBnZW9tX3NmKGRhdGEgPSBjYW5hcmlhcywgZmlsbCA9ICJyZWQiKSArCiAgICAgICAgIGNvb3JkX3NmKHhsaW0gPSBjKC0xMi4wMCwgNS4wMCksIHlsaW0gPSBjKDMwLCA0NCksIGV4cGFuZCA9IEZBTFNFKQpgYGAKCgojIyMgZWwgcsOtbyBFYnJvCgpQdWVzIHF1ZSBxdWllcm8gcG9uZXIgZWwgUsOtbyBFYnJvIGVuIGVsIG1hcGEuIExvZ2ljYW1lbnRlIG5lY2VzaW8gbG9zIGRhdG9zIGdlb2dyw6FmaWNvcyBkZWwgcmlvIEVicm8uCgpgYGB7cn0KI3Jpb3MgPC0gcmlvOjppbXBvcnQoImh0dHBzOi8vZ2l0aHViLmNvbS9wZXJlenA0NC9hcmNoaXZvc19kb3dubG9hZC9ibG9iL21hc3Rlci9yaW9fZWJyby5yZHM/cmF3PXRydWUiKQp6el9yaW9zIDwtIHJpb3MgJT4lIHN0X3NldF9nZW9tZXRyeShOVUxMKQpuYW1lcyhyaW9zKQpgYGAKCgotIFZveSBhIHNpdHVhciBlbiBlbCBtYXBhIDQgcsOtb3M6CgpgYGB7ciwgcmVzdWx0cyA9ICJoaWRlIn0KIy0gbm8gaGFjZSBmYWx0YSBwZXJvCmNvZF9yaW9zX2d1ZW5vcyA8LSBjKDkxMzc1NywgOTEzNjg3LCA5MTM2ODUsIDkxMzYxOSkKbm9tYnJlX3Jpb3NfZ3Vlbm9zIDwtIGMoIlBhbmNydWRvIiwgIkppbG9jYSIsICJKYWzDs24iLCAiRWJybyIpCgpyaW9zX2d1ZW5vcyA8LSByaW9zICU+JSBmaWx0ZXIoQ29kX1VuaSAlaW4lIGNvZF9yaW9zX2d1ZW5vcykKYGBgCgoKYGBge3J9CmdncGxvdChkYXRhID0gcGVuaW5zdWxhKSArIGdlb21fc2YoKSArCiAgICBnZW9tX3NmKGRhdGEgPSByaW9zX2d1ZW5vcywgY29sb3IgPSAibGlnaHRibHVlIiwgbHdkID0gMS4zKSArCiAgICBsYWJzKHN1YnRpdGxlID0gIk1hcGEgZGUgRXNwYcOxYSBjb24gNCByw61vcyBHw7xlbm9zIiwgY29sb3IgPSAiIikKIyBjb2wgPSBzZjo6c2YuY29sb3JzKDIsIGFscGhhID0gMC41KQpgYGAKCi0gwr9Dw7NtbyBzZSBsbGFtYXLDoSBlbCByw61vIHBlcXVlw7HDrW4/CgoKYGBge3J9CmdncGxvdChkYXRhID0gcGVuaW5zdWxhKSArIGdlb21fc2YoKSArCiAgICBnZW9tX3NmKGRhdGEgPSByaW9zX2d1ZW5vcywgY29sb3IgPSAibGlnaHRibHVlIiwgbHdkID0gMS4zKSArCiAgICBnZW9tX3BvaW50KGRhdGEgPSBwdWVibG9zLCBhZXMoeCA9IGxvbmdpdHVkZSwgeSA9IGxhdGl0dWRlKSwgc2l6ZSA9IDIsIGNvbG9yID0gImRhcmtyZWQiKSArCiAgICBnZW9tX3RleHQoZGF0YSA9IHB1ZWJsb3MsIGFlcyh4ID0gbG9uZ2l0dWRlLCB5ID0gbGF0aXR1ZGUsIGxhYmVsID0gbm9tYnJlKSwgY29sb3IgPSAiZGFya2JsdWUiLCBmb250ZmFjZSA9ICJib2xkIiwgY2hlY2tfb3ZlcmxhcCA9IFRSVUUsIHNpemUgPSAyLjUpICsgCiAgICBsYWJzKHN1YnRpdGxlID0gIk1hcGEgZGUgRXNwYcOxYSBjb24gNCByw61vcyBHw7xlbm9zIiwgY29sb3IgPSAiIikKYGBgCgoKPGJyPgoKLS0tLS0tLS0tLS0tLS0tCgo8YnI+CgoKCiMgNC4gIExheW91dHMKCkNvbiBsb3MgbWFwYXMgc2UgcHVlZGVuIGhhY2VyIGNvbXBvc2ljaW9uZXMgcGFyYSBkZXN0YWNhciBlbCBtZW5zYWplIHF1ZSBzZSBxdWllcmUgbW9zdHJhci4gU2UgcHVlZGVuIGhhY2VyIGVzdGFzIGNvbXBvc2ljaW9uZXMgZGUgdmFyaWFzIG1hbmVyYXM6CgoKLSBVc2luZyDigJxncm9ic+KAnSwgaS5lLiBncmFwaGljIG9iamVjdHMgZnJvbSBnZ3Bsb3QyLCB3aGljaCBjYW4gYmUgaW5zZXJ0ZWQgaW4gdGhlIHBsb3QgcmVnaW9uIHVzaW5nIHBsb3QgY29vcmRpbmF0ZXM7CgotIFVzYW5kbyBlbCBwYXF1ZXRlIGBwYXRjaHdvcmtgIG8gZWwgcGFxdWV0ZSBgY293cGxvdGAKCmBgYHtyLCBldmFsID0gVFJVRX0KZ3dvcmxkIDwtIGdncGxvdChkYXRhID0gd29ybGQpICsKICBnZW9tX3NmKGFlcyhmaWxsID0gcmVnaW9uX3diKSkgKwogIGdlb21fcmVjdCh4bWluID0gLTE1LCB4bWF4ID0gMjUuMDAsIHltaW4gPSAyMSwgCiAgICAgICAgICAgIHltYXggPSA1Ny40NCwgZmlsbCA9IE5BLCBjb2xvdXIgPSAiYmxhY2siLCBzaXplID0gMS41KSArCiAgdGhlbWUocGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gImF6dXJlIikpICsKICBsYWJzKHN1YnRpdGxlID0gIk1hcGEgZGVsIG11bmRvIikgKwogIHNjYWxlX2ZpbGxfZGlzY3JldGUoZ3VpZGUgPSBGQUxTRSkKCmd3b3JsZApgYGAKCgpgYGB7ciwgZXZhbCA9IFRSVUV9CmdfZXVyb3BlIDwtIGdncGxvdChkYXRhID0gd29ybGQpICsKICAgIGdlb21fc2YoKSArCiAgICBnZW9tX3NmKGRhdGEgPSBDQ0FBLCBmaWxsID0gTkEpICsKICAgIGdlb21fc2YoZGF0YSA9IGdlb19wb2JfYW5kYWx1Y2lhLCBhZXMoZ2VvbWV0cnkgPSBnZW9tZXRyeSwgZmlsbCA9IG5fcHVlYmxvcykpICsKICAgIGdlb21fc2YoZGF0YSA9IHB1ZWJsb3NfMywgc2l6ZSA9IDIsIGNvbG9yID0gImRhcmtyZWQiKSArCiAgICBnZW9tX2xhYmVsX3JlcGVsKGRhdGEgPSBwdWVibG9zXzMsIGFlcyh4ID0gTE9OR0lUVURfRVRSUzg5LCB5ID0gTEFUSVRVRF9FVFJTODksIGxhYmVsID0gTk9NQlJFX0FDVFVBTCkpICsKICAgIGNvb3JkX3NmKHhsaW0gPSBjKC0xNS4wMCwgMjUuMDApLCB5bGltID0gYygyMSwgNTcuNDQpLCBleHBhbmQgPSBGQUxTRSkgKwogICAgdGhlbWUocGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gImF6dXJlIikpICsKICAgIGxhYnMoc3VidGl0bGUgPSAiTWFwYSBkZSBFdXJvcGEgT2NjaWRlbnRhbCIpICsKICAgIHNjYWxlX2ZpbGxfY29udGludW91cyhndWlkZSA9IEZBTFNFKQpnX2V1cm9wZQpgYGAKCgoKYGBge3IsIGV2YWwgPSBUUlVFfQpsaWJyYXJ5KHBhdGNod29yaykKZ3dvcmxkICsgZ19ldXJvcGUgKyBwbG90X2xheW91dCh3aWR0aHMgPSBjKDEsIDIpKQpgYGAKCgoKPGJyPgoKLS0tLS0tLS0tLS0tLS0tCgo8YnI+CgojIDUuIFBhcXVldGUgYE1hcHZpZXdgCgpWYW1vcyBhIHZlciBsbyBmw6FjaWwgcXVlIGVzIGhhY2VyIHVuIG1hcGEgY29uIGVsIHBhcXVldGUgW2BtYXB2aWV3YF0oaHR0cHM6Ly9yLXNwYXRpYWwuZ2l0aHViLmlvL21hcHZpZXcvKS4KClBvciBlamVtcGxvLCB0ZW7DrWFtb3MgZ2VvbG9jYWxpemFkb3MgYSBQYW5jcnVkbyB5IFZhbGVuY2lhLiBWZcOhbW9zbG9zIGVuIHVuIGdyw6FmaWNvIGNvbiBgbWFwdmlldygpYAoKYGBge3IsIGV2YWwgPSBGQUxTRX0KcHVlYmxvcyA8LSBkYXRhLmZyYW1lKG5vbWJyZSA9IGMoIlBhbmNydWRvIiwgIlZhbGVuY2lhIiksIGxvbmdpdHVkZSA9IGMoLTEuMDI4NzgxLCAtMC4zNzU2NTcyKSwgbGF0aXR1ZGUgPSBjKDQwLjc2MTc1LCAzOS40NzUzNCkpICMtIEVUUlM4OQpwdWVibG9zX3NmIDwtIHN0X2FzX3NmKHB1ZWJsb3MsIGNvb3JkcyA9IGMoImxvbmdpdHVkZSIsICJsYXRpdHVkZSIpLCBjcnMgPSA0MzI2LCBhZ3IgPSAiY29uc3RhbnQiKSAjLSBFUFNHOjQzMjYgQ29vcmRlbmFkYXMgR2VvZ3LDoWZpY2FzIFdHUzg0Cm1hcHZpZXc6Om1hcHZpZXcocHVlYmxvc19zZikKYGBgCgoKT3RybyBlamVtcGxvIGNvbiBsYXMgZ2VvbWV0csOtYXMgZGUgbGFzIENDLkFBLCBhdW5xdWUgZXMgdW5hIGFmcmVudGEgcGFyYSBQYW5jcnVkbwoKYGBge3IsIGV2YWwgPSBGQUxTRX0KbWFwdmlldzo6bWFwdmlldyhyaW9zKQpgYGAKClBhcmEgdmVyIHF1ZSBzZSBwdWVkZSBoYWNlciBjb24gYG1hcHZpZXdgIHZhbW9zIGEgYcOxYWRpciBhbGd1bmEgaW5mb3JtYWNpw7NuIGEgQ0NBQSwgcG9yIGVqZW1wbG8gZWwgbsO6bWVybyBkZSBwcm92aW5jaWFzLCBlbCBuw7ptZXJvIGRlIG11bmljaXBpb3MgeSBsYSBwb2JsYWNpw7NuIHRvdGFsIGVuIGVsIGHDsW8gMjAxNzoKCgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpJTkVfcGFkcm9uX211bmlfOTZfMTkgPC0gcGpwdjIwMjAuMDE6OnBqcF9kYXRhX3BvYl9tdW5fMTk5Nl8yMDE5CmluZm9fQ0NBQXMgPC0gSU5FX3BhZHJvbl9tdW5pXzk2XzE5ICU+JSAKICAgICAgICAgICAgICBmaWx0ZXIoeWVhciA9PSAyMDE3KSAlPiUgCiAgICAgICAgICAgICAgZmlsdGVyKHBvYmxhY2lvbiA9PSAiVG90YWwiKSAlPiUgCiAgICAgICAgICAgICAgZ3JvdXBfYnkoSU5FQ29kQ0NBQS5uKSAlPiUgCiAgICAgICAgICAgICAgbXV0YXRlKG5fcHJvdiA9IG5fZGlzdGluY3QoSU5FQ29kUHJvdikpICU+JSAKICAgICAgICAgICAgICBtdXRhdGUobl9wdWVibG9zID0gbl9kaXN0aW5jdChJTkVDb2RNdW5pKSkgJT4lIAogICAgICAgICAgICAgIG11dGF0ZShwb2JfQ0NBQSA9IHN1bShwb2JfdmFsdWVzLCBuYS5ybSA9IFRSVUUpKSAlPiUgdW5ncm91cCgpICU+JQogICAgICAgICAgICAgIHNlbGVjdChJTkVDb2RDQ0FBLCBJTkVDb2RDQ0FBLm4sIG5fcHJvdiwgbl9wdWVibG9zLCBwb2JfQ0NBQSkgJT4lCiAgICAgICAgICAgICAgZGlzdGluY3QoKSAlPiUgdW5ncm91cCgpCmBgYAoKCkp1bnRhbW9zIGVsIGRhdGEuZnJhbWUgYGluZm9fQ0NBQXNgIGNvbiBlbCBkYXRhZnJhbWUgY29uIGxhcyBnZW9tZXRyw61hcyBgYENDQUFgOgoKCmBgYHtyLCBldmFsID0gRkFMU0V9CkNDQUFfY29uX2luZm8gPC0gbGVmdF9qb2luKENDQUEsIGluZm9fQ0NBQXMsICBieSA9IGMoIklORUNvZENDQUEiID0gIklORUNvZENDQUEiKSkKYGBgCgoKWSB2YW1vcyBhIGdyYWZpY2FybG8geWEgY29uIGBtYXB2aWV3YDoKCgpgYGB7ciwgZXZhbCA9IEZBTFNFfQptYXB2aWV3OjptYXB2aWV3KENDQUFfY29uX2luZm8sIAogICAgICAgICAgICAgICAgIHpjb2wgPSBjKCJOb21icmVDQ0FBIiwgIm5fcHJvdiIsICJuX3B1ZWJsb3MiLCAicG9iX0NDQUEiKSwKICAgICAgICAgICAgICAgICBjb2xvciA9ICJ0b21hdG8iLCBjb2wucmVnaW9ucyA9ICJwdXJwbGUiLCBsd2QgPSAzLAogICAgICAgICAgICAgICAgIGNleCA9ICJuX3B1ZWJsb3MiKQpgYGAKCgpgYGB7ciwgZXZhbCA9IEZBTFNFfQptYXB2aWV3OjptYXB2aWV3KENDQUFfY29uX2luZm8sIAogICAgICAgICAgICAgICAgIHpjb2wgPSBjKCJOb21icmVDQ0FBIiwgIm5fcHJvdiIsICJuX3B1ZWJsb3MiLCAicG9iX0NDQUEiKSwKICAgICAgICAgICAgICAgIGNleCA9ICJuX3B1ZWJsb3MiKQpgYGAKClNlIHB1ZWRlbiBwb25lciB2YXJpYXMgY2FwYXM6CgpgYGB7ciwgZXZhbCA9IEZBTFNFfQptYXB2aWV3KGxpc3QoQ0NBQV9jb25faW5mbywgcHVlYmxvc19zZiksIGxheWVyLm5hbWUgPSBjKCJDQy5BQSIsICIyIG11bmljaXBpb3MgY2hhY2hpcyIpKQpgYGAKCk8gbWVqb3IgYXPDrToKCmBgYHtyLCBldmFsID0gRkFMU0V9Cm1hcHZpZXc6Om1hcHZpZXcoQ0NBQV9jb25faW5mbywgemNvbCA9IGMoIk5vbWJyZUNDQUEiLCAibl9wcm92IiwgIm5fcHVlYmxvcyIsICJwb2JfQ0NBQSIpKSArCm1hcHZpZXc6Om1hcHZpZXcocHVlYmxvc19zZikgCmBgYAoKClkgbXVjaGFzIG1hcyBjb3NhcywgcG9yIGVqZW1wbG8gW01hcCBwb3AtdXBzXShodHRwczovL3Itc3BhdGlhbC5naXRodWIuaW8vbWFwdmlldy9hcnRpY2xlcy9hcnRpY2xlcy9tYXB2aWV3XzA0LXBvcHVwcy5odG1sKSAgbyBbSW1hZ2UgcG9wLXVwc10oaHR0cHM6Ly9yLXNwYXRpYWwuZ2l0aHViLmlvL21hcHZpZXcvYXJ0aWNsZXMvYXJ0aWNsZXMvbWFwdmlld18wNC1wb3B1cHMuaHRtbCkKCgotIHNlIHB1ZWRlIHVzYXIgZWwgb3BlcmFkb3IgYCAlPiVgIGNvbiBsYXMgZnVuY2lvbmVzIGRlIGBzZmAKCmBgYHtyLCBldmFsID0gRkFMU0V9CkNDQUFfY29uX2luZm8gJT4lIHN0X3VuaW9uICU+JSBtYXB2aWV3CmBgYAoKCgo8YnI+CgoKIyMjIEludGVncmFjacOzbiBjb24gZWwgcGFxdWV0ZSBgbWFwZWRpdGAKCkludGVncmFjaW9uIGNvbiBbYG1hcGVkaXRgXShodHRwczovL3d3dy5yLXNwYXRpYWwub3JnL3IvMjAxNy8wNi8wOS9tYXBlZGl0XzAtMi0wLmh0bWwpCgoKCmBgYHtyLCBldmFsID0gRkFMU0V9CiNpbnN0YWxsLnBhY2thZ2VzKCJtYXBlZGl0IikKbGlicmFyeShtYXBlZGl0KQpteV9tYXAgPC0gbWFwdmlldyhwdWVibG9zX3NmKQpkcmF3RmVhdHVyZXMobXlfbWFwLCBlZGl0b3IgPSAibGVhZnBtIikgICAjLSBnb29kCmBgYAoKUGFyYSByZWN1cGVyYXIgZWwgbWFwYSBlcyBtZWpvciBhc8OtOgoKYGBge3IsIGV2YWwgPSBGQUxTRX0KbGlicmFyeShtYXBlZGl0KQpteXNfZGlidWppdG9zIDwtIGVkaXRNYXAobWFwdmlldyhwdWVibG9zX3NmKSkKbWFwdmlldyhteXNfZGlidWppdG9zJGZpbmlzaGVkKQpgYGAKCgoKCjxicj4KCi0tLS0tLS0tLS0tLS0tLQoKPGJyPgoKCiMgNi4gUGFxdWV0ZSBgbGVhZmxldGAKCgpFbCBwYXF1ZXRlIFtgbGVhZmxldGBdKGh0dHBzOi8vcnN0dWRpby5naXRodWIuaW8vbGVhZmxldC8pIHBlcm1pdGUgdXRpbGl6YXIgZW4gUiB1bmEgZGUgbGEgbGlicmVyw61hcyBkZSBKYXZhU2NyaXB0IG3DoXMgY29ub2NpZGFzOiBMZWFmbGV0LgoKClBhcmEgYWJyaXIgdW4gbWFwYSBsZWFmbGV0OgoKYGBge3IsIGV2YWwgPSBGQUxTRSB9CmxpYnJhcnkobGVhZmxldCkKbGVhZmxldCgpICU+JSBhZGRUaWxlcygpICU+JSBsZWFmZW06OmFkZE1vdXNlQ29vcmRpbmF0ZXMoKQpgYGAKCgoKUG9kZW1vcyBjZW50cmFyIGVsIG1hcGEsIGRlY2lkaXIgZWwgbml2ZWwgZGUgem9vbSB5IHBvbmVybGUgbWFyY2Fkb3JlcyB5IHBvcHVwczoKCgpgYGB7cn0KbSA8LSBsZWFmbGV0KCkgJT4lCiAgYWRkVGlsZXMoKSAlPiUgCiAgc2V0VmlldyhsbmcgPSAtMS4wMjg3ODEwLCBsYXQgPSA0MC43NjE3NSwgem9vbSA9IDYpICU+JSAKICBhZGRNYXJrZXJzKGxuZyA9IC0xLjAyODc4MTAsIGxhdCA9IDQwLjc2MTc1LCBwb3B1cCA9ICJQYW5jcnVkbyIpICU+JSAKICBhZGRQb3B1cHMobG5nID0gLTAuMzc1NjU3MiwgbGF0ID0gMzkuNDc1MzQsIHBvcHVwID0gIlZhbGVuY2lhIikKbQpgYGAKCgpWYW1vcyBhIGHDsWFkaXJsZSBlbGVtZW50b3MsIHBvciBlamVtcGxvIHB1bnRvcywgcG9yIGVqZW1wbG8gbWlzIDIgbXVuaWNpcGlvczoKCgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpsZWFmbGV0KCkgJT4lIAogIGFkZFRpbGVzKCkgJT4lICMgQWRkIGRlZmF1bHQgT3BlblN0cmVldE1hcCBtYXAgdGlsZXMKICBhZGRDaXJjbGVNYXJrZXJzKGRhdGEgPSBwdWVibG9zX3NmKQogICNsZWFmZW06OmFkZEhvbWVCdXR0b24oZXh0ID0gZXh0ZW50KGJyZXdlcmllczkxKSwgbGF5ZXIubmFtZSA9ICJwdWVibG9zIikKYGBgCgpPLCBwb3IgZWplbXBsbyBwb2zDrWdvbm9zIGNvbW8gbGFzIENDLkFBOgoKYGBge3IsIGV2YWwgPSBGQUxTRX0KbGVhZmxldChkYXRhID0gQ0NBQSkgJT4lIGFkZFRpbGVzKCkgJT4lCiAgYWRkUG9seWdvbnMoZmlsbENvbG9yID0gdG9wby5jb2xvcnMoMTgsIGFscGhhID0gTlVMTCksIHN0cm9rZSA9IEZBTFNFKQpgYGAKCgoKU2kgdGUgaGEgZ3VzdGFkbyB0dSBtYXBhIGxlYWZsZXQgeSBxdWllcmVzIGd1YXJkYXJsbywgcHVlZGVzIGd1YXJkYXJsbyBjb21vIHVuIGFyY2hpdm8gaHRtbCBhc8OtOgoKYGBge3IsIGV2YWwgPSBGQUxTRX0KaHRtbHRvb2xzOjpzYXZlX2h0bWwobHBsb3QsICJsZWFmbGV0Lmh0bWwiKQpgYGAKCgoKU2UgcHVlZGVuIGhhY2VyIGJhc3RhbnRlcyBtw6FzIGNvc2FzLCBwb3IgZWplbXBsbywgcGFyYSBoYWNlciBjb3JvcGxldGFzIGNvbiBsZWFmbGV0IHB1ZWRlcyBpciBbYXF1w61dKGh0dHBzOi8vcnN0dWRpby5naXRodWIuaW8vbGVhZmxldC9jaG9yb3BsZXRocy5odG1sKS4gT3RybyBlamVtcGxvIGRlIGFuw6FsaXNpcyBjb24gbGVhZmxldCBbYXF1w61dKGh0dHBzOi8vd3d3LmpsYS1kYXRhLm5ldC9lbmcvbGVhZmxldC1pbi1yLXRpcHMtYW5kLXRyaWNrcy9pbmRleC5odG1sKS4KCgoKPGJyPgoKLS0tLS0tLS0tLS0tLS0tCgo8YnI+CgoKIyA3LiBPcGVyYWNpb25lcyBnZW9tw6l0cmljYXMKCgpMYXMgb3BlcmFjaW9uZXMgZ2VvbcOpdHJpY2FzIHF1ZSBwb2RlbW9zIGhhY2VyIGNvbiBgc2ZgIGNvbiBkYXRvcyBlc3BhY2lhbGVzIHZlY3RvcmlhbGVzIHNlIHB1ZWRlbiBkaXZpZGlyIGVuIHRyZXMgZ3J1cG9zOgoKLSAxLiBmdW5jaW9uZXMgcXVlIGRldnVlbHZlbiB1biB2YWxvciBxdWUgcmVzdW1lIGFsZ3VuYSBwcm9waWVkYWQgZGUgbGEgZ2VvbWV0csOtYTogIAoKICAgICAgLSAgYHN0X2FyZWEoKWAgICAgOiBjYWxjdWxhIGVsIGFyZWEgCiAgICAgIC0gIGBzdF9sZW5naHQoKWAgIDogY2FsY3VsYSBlbCBwZXLDrW1ldHJvICAgCiAgICAgIC0gIGBzdF9kaXN0YW5jZSgpYDogY2FsY3VsYSBsYSBkaXN0YW5jaWEgKGVudHJlIGRvcyBnZW9tZXRyw61hcykgICAKICAgIAotIDIuIGZ1bmNpb25lcyBxdWUgY2hlcXVlYW4gc2kgc2UgY3VtcGxlIHVuYSBjb25kaWNpw7NuOiAgCgogICAgICAtICBFbiAxIGdlb21ldHLDrWE6IGBzdF9pc192YWxpZCgpYCAgICA6IGNoZXF1ZWEgc2kgbGEgZ2VvbWV0cmlhIGVzIHbDoWxpZGEgICAKICAgICAgCiAgICAgCiAgICAgIC0gRW4gMiBnZW9tZXRyw61hczogcG9yIGVqZW1wbG8sIGBzdF9pbnRlcnNlY3RzKEEsIEIpYCBjaGVxdWVhIHNpIGRvcyBnZW9tZXRyw61hcyBzZSBpbnRlcnNlY3Rhbi4gT3RyYXMgZnVuY2lvbmVzOiBgc3RfZGlzam9pbnQoKWAsIGBzdF90b3VjaGVzKClgLCBgc3RfY3Jvc3NlcygpYCwgYHN0X3dpdGhpbigpYCwgYHN0X2NvbnRhaW5zKClgLCBgc3Rfb3ZlcmxhcHMoKWAsIGBzdF9jb3ZlcnMoKWAsIGBzdF9lcXVhbHMoKWAsIGBzdF9pc193aXRoaW5fZGlzdGFuY2UoKWAsIHkgYWxndW5hIG3DoXMKICAgIAogICAgCi0gMy4gZnVuY2lvbmVzIHF1ZSBjcmVhbiB1bmEgIG51ZXZhIGdlb21ldHLDrWE6ICAKCiAgICAgIC0gRW4gMSBnZW9tw6l0cmlhOiBwb3IgZWplbXBsbywgYHN0X2NlbnRyb2lkKClgOiBjYWxjdWxhIGVsIGNlbnRyb2lkZS4gT3RyYXMgZnVuY2lvbmVzOiBgc3RfYnVmZmVyKClgLCBgc3RfYm91bmRhcnkoKWAgLCBgc3RfY29udmV4X2h1bGwoKWAsIGBzdF92b3Jvbm9pKClgCiAgICAgIAogICAgICAKICAgICAgLSBFbiAyIGdlb23DqXRyaWFzOiBwb3IgZWplbXBsbywgYHN0X2ludGVyc2VjdGlvbigpYCBjYWxjdWxhIGxhIGludGVyc2VjY2lvbi4gT3RyYXMgZnVuY2lvbmVzOiBgc3RfY29tYmluZSgpYCwgYHN0X3VuaW9uKClgLCBgc3RfZGlmZmVyZW5jZSgpYAoKPGJyPgoKIyMgIDcuMSAgT3BlcmFjaW9uZXMgZ2VvbcOpdHJpY2FzIChFamVtcGxvcykKCgojIyMgTGFzIDUgcHJvdmluY2lhcyBtw6FzIGdyYW5kZXMKClZhbW9zIGEgY29sb3JlYXIgbGFzIDUgcHJvdmluY2lhcyBlc3Bhw7FvbGFzIGNvbiBtw6FzIHRlcnJpdG9yaW8sIHBhcmEgZWxsbyB0ZW5lbW9zIHF1ZSBjYWxjdWxhciBlbCDDoXJlYSBjb24gYHNmOjpzdF9hcmVhKClgOgoKYGBge3J9ClByb3ZpY2lhc181IDwtIFByb3ZpbmNpYXMgJT4lIAogICAgICAgICAgICAgICBtdXRhdGUoYXJlYSA9IHN0X2FyZWEoLikpICU+JSAKICAgICAgICAgICAgICAgbXV0YXRlKGFyZWFrbTIgPSB1bml0czo6c2V0X3VuaXRzKGFyZWEsIGttXjIpKSAlPiUgCiAgICAgICAgICAgICAgIGFycmFuZ2UoZGVzYyhhcmVha20yKSkgJT4lIAogICAgICAgICAgICAgICBzbGljZSgxOjUpCmdncGxvdCgpICsgZ2VvbV9zZihkYXRhID0gUHJvdmluY2lhcykgKyBnZW9tX3NmKGRhdGEgPSBQcm92aWNpYXNfNSwgZmlsbCA9ICJwdXJwbGUiKQpgYGAKCkJpZW4sIHBlcm8gbWVqb3Igc2kgcG9uZW1vcyBlbCBub21icmUgZGUgbGFzIHByb3ZpbmNpYXMKCmBgYHtyfQpQcm92XzVfY2VudHJvaWRlcyA8LSBzdF9jZW50cm9pZChQcm92aWNpYXNfNSkgClByb3ZfNV9jZW50cm9pZGVzIDwtICBjYmluZChQcm92XzVfY2VudHJvaWRlcywgc3RfY29vcmRpbmF0ZXMoc3RfY2VudHJvaWQoUHJvdl81X2NlbnRyb2lkZXMkZ2VvbWV0cnkpKSkKCmdncGxvdCgpICsgZ2VvbV9zZihkYXRhID0gUHJvdmluY2lhcykgKyAKICAgICAgICAgICBnZW9tX3NmKGRhdGEgPSBQcm92aWNpYXNfNSwgZmlsbCA9ICJwdXJwbGUiKSArCiAgICAgICAgICAgZ2VvbV90ZXh0KGRhdGEgPSBQcm92XzVfY2VudHJvaWRlcywgCiAgICAgICAgICAgICAgICAgICAgIGFlcyh4ID0gWCwgeSA9IFksIGxhYmVsID0gTm9tYnJlUHJvdiksCiAgICAgICAgICAgICAgICAgICAgIGNvbG9yID0gImRhcmtibHVlIiwgZm9udGZhY2UgPSAiYm9sZCIsIGNoZWNrX292ZXJsYXAgPSBUUlVFLCBzaXplID0gMi41KSArCiAgICAgICAgICBsYWJzKHRpdGxlID0gIkxhcyA1IHByb3ZpbmNpYXMgZXNwYcOxb2xhcyBtw6FzIGV4dGVuc2FzIiwgeCA9ICIiLCB5ID0gIiIpIApgYGAKCgoKIyMjIERpc3RhbmNpYXMgKGEgbWkgcHVlYmxvKQoKQ2FsY3VsZW1vcyBsYXMgZGlzdGFuY2lhcyBlbnRyZSBwdW50b3MsIHBvciBlamVtcGxvIGVudHJlIGxvcyBjZW50cm9pZGVzIGRlIGxhcyA1MiBwcm92aW5jaWFzIGVzcGHDsW9sYXM6CgoKYGBge3IsIGV2YWwgPSBGQUxTRX0KUHJvdl9jZW50cm9pZGVzIDwtIHN0X2NlbnRyb2lkKFByb3ZpbmNpYXMpIApQcm92X2NlbnRyb2lkZXNfWFkgPC0gY2JpbmQoUHJvdl9jZW50cm9pZGVzLCBzdF9jb29yZGluYXRlcyhzdF9jZW50cm9pZChQcm92X2NlbnRyb2lkZXMkZ2VvbWV0cnkpKSkKZGlzdGFuY2lhcyA8LSBzdF9kaXN0YW5jZShQcm92X2NlbnRyb2lkZXNfWFksIGJ5X2VsZW1lbnQgPSBGQUxTRSkgIy0gZGV2dWVsdmUgdW5hIG1hdHJpegpgYGAKCgpRdWllcm8gdmVyIHF1ZSBtdW5pY2lwaW8gZXN0w6EgbWFzIGxlam9zIGRlIG1pIHB1ZWJsby4gQ2FyZ28gZGF0b3MgZGUgbG9zIG11bmljaXBpb3MgY29uIGxhdGl0dWQtbG9uZ2l0dWQgZW4gYEVUUlM4OWA6CgoKYGBge3J9Cm11bmljaXBpb3MgPC0gSUdOX25vbWVuY2xhX211bmkKbXVuaWNpcGlvcyA8LSBtdW5pY2lwaW9zICU+JSBzZWxlY3QoQ09EX0lORSwgUFJPVklOQ0lBLCBOT01CUkVfQUNUVUFMLCBMT05HSVRVRF9FVFJTODksIExBVElUVURfRVRSUzg5KQpgYGAKClRlbmdvIHF1ZSBjb252ZXJ0aXIgZWwgZGYgYSB1biBzcGF0aWFsLWRmLCB5IHRlbmdvIHF1ZSB0ZW5lciBjdWlkYWRvIGNvbiBlbCBDUlMgcXVlIGxlIHBvbmdvLiBbQXF1w61dKGh0dHBzOi8vc3BhdGlhbHJlZmVyZW5jZS5vcmcvcmVmL2Vwc2cvNDI1OC8pIHZpIHF1ZSBFVFJTODkgZXMgZXF1aXZhbGVudGUgYSBgRVBTRyBQcm9qZWN0aW9uIDQyNThgLCBhc8OtIHF1ZSwgZXNwZXJvIGhhYmVybG8gaGVjaG8gYmllbiwgeWEgdm9yZW06CgoKYGBge3J9Cm11bmljaXBpb3Nfc2YgPC0gc3RfYXNfc2YobXVuaWNpcGlvcywgY29vcmRzID0gYygiTE9OR0lUVURfRVRSUzg5IiwgIkxBVElUVURfRVRSUzg5IiksIGNycyA9IHN0X2Nycyg0MjU4KSkKYGBgCgpWYW1vcyBhIHZlciBxdWUgZmlsYSBvY3VwYSBtaSBwdWVibG8uIE9jdXBhIGxhIGZpbGEgbsK6IDY2OTQsIGFzw60gcXVlIHZveSBhIGNyZWFyIHVuIGRmIHPDs2xvIGNvbiBtaSBwdWVibG8geSBsdWVnbyBjYWxjdWxhciBsYXMgZGlzdGFuY2lhcyBjb24gYGZzOjpzdF9kaXN0YW5jZSgpYDoKCgpgYGB7cn0KcGFuY3J1ZG9fc2YgPC0gbXVuaWNpcGlvc19zZiAlPiUgc2xpY2UoNjY5NCkKZzEgPC0gc3RfZ2VvbWV0cnkobXVuaWNpcGlvc19zZikKZzFfcGFuY3J1ZG8gPC0gc3RfZ2VvbWV0cnkocGFuY3J1ZG9fc2YpICMtIGRlam8gc29sbyBsYSBnZW9tZXRyaWEKCiMtIGRpc3RhbmNpYXNfYV9QYW5jcnVkbyA8LSBtYXBwbHkoc3RfZGlzdGFuY2UsIGcxLCBnMV9wYW5jcnVkbykgJT4lIGFzLnZlY3RvcikgIy0gY29uIFItYmFzZQpkaXN0YW5jaWFzX2FfUGFuY3J1ZG8gPC0gbWFwMihnMSwgZzFfcGFuY3J1ZG8sIHN0X2Rpc3RhbmNlKSAlPiUgYXNfdmVjdG9yKCkgICAgICAgICAjLSBjb24gcHVycnIKZGlzdGFuY2lhc19hX1BhbmNydWRvIDwtIGFzLmRhdGEuZnJhbWUoZGlzdGFuY2lhc19hX1BhbmNydWRvKQpkZl9jb25fZGlzdGFuY2lhc19hX1BhbmNydWRvIDwtIGJpbmRfY29scyhtdW5pY2lwaW9zX3NmLCBkaXN0YW5jaWFzX2FfUGFuY3J1ZG8pICU+JSAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtdXRhdGUoS21zID0gZGlzdGFuY2lhc19hX1BhbmNydWRvICogMTAwKSAlPiUgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXJyYW5nZShLbXMpCmBgYCAKCkVsIHB1ZWJsbyBtw6FzIGNlcmNhbm8gYSBQYW5jcnVkbyBlcyBgciBkZl9jb25fZGlzdGFuY2lhc19hX1BhbmNydWRvJE5PTUJSRV9BQ1RVQUxbMl1gIHF1ZSBlc3TDoSBhIGByIGRmX2Nvbl9kaXN0YW5jaWFzX2FfUGFuY3J1ZG8kS21zWzJdYCwgZXNvIHNpLCBlbiBsaW5lYSByZWN0YS4gUG9yIGxhIGNhcnJldGVyYSBoYXkgdW4gcG9jbyBtw6FzLCBzZWfDum4gR29vZ2xlIE1hcHMgNywxIGtpbMOzbWV0cm9zLiBBIHZlciBzaSBwb2RlbW9zIGNhbGN1bGFybG8gbHVlZ28gY29uIFIgdXNhbmRvIE9TTS4KCkVsIHB1ZWJsbyBtw6FzIGxlamFubyBhIFBhbmNydWRvLCBzaSBjb25zaWRlcmFtb3Mgc8OzbG8gbGEgcGVuw61uc3VsYSBlcyBNdXjDrWEgZW4gbGEgcHJvdmluY2lhIGRlIEEgQ29ydcOxYSwgcXVlIGVzdMOhIGEgODUxLDY1IGtpbMOzbWV0cm9zIChlbiBsaW5lYSByZWN0YSkuIEVuIGNvY2hlLCBzZWfDum4gR29vZ2xlIE1hcHMsIDkyNCBLbXMuCgo8YnI+CgoKIyMjIE11bmljaXBpb3MgcG9yIGxvcyBxdWUgcGFzYSBlbCByw61vIEVicm8KCgpQdWVzIGVzbywgcXVlIG1lIGFwZXRlY2Ugc2FiZXIgcG9yIGRvbmRlIHBhc2EgZWwgRWJybyB5IGVsIFBhbmNydWRvLgoKMS4gUHJpbWVybyBjYXJnbyBnZW9tZXRyw61hcyBkZSBsb3MgbXVuaWNpcGlvcyBlc3Bhw7FvbGVzLiBMYXMgbGluZGVzIG11bmljaXBhbGVzIHByb3ZpZW5lbiBkZWwgcGFxdWV0ZSBbYExBVTJib3VuZGFyaWVzNHNwYWluYF0oaHR0cHM6Ly9naXRodWIuY29tL3BlcmV6cDQ0L0xBVTJib3VuZGFyaWVzNHNwYWluKQoKYGBge3J9Cm11bmlfeHggPC0gbXVuaWNpcGlvc18yMDE3ICU+JSBzdF9zZXRfZ2VvbWV0cnkoTlVMTCkKbXVuaSAgICA8LSBtdW5pY2lwaW9zXzIwMTcKYGBgCgoKVmFtb3MgYSBxdWVkYXJub3Mgc29sbyBjb24gbGEgRXNwYcOxYSBwZW5pbnN1bGFyOgoKYGBge3J9CiN6eiA8LSBtdW5pX3h4ICU+JSBncm91cF9ieShJTkVDb2RDQ0FBLCBOb21icmVDQ0FBKSAlPiUgY291bnQoKQptdW5pX2JvcnJhciA8LSBjKCJJbGxlcyBCYWxlYXJzIiwgIkNhbmFyaWFzIikKbXVuaSA8LSBtdW5pICU+JSBmaWx0ZXIoIShOb21icmVDQ0FBICVpbiUgbXVuaV9ib3JyYXIpKQpgYGAKCgoyLiBBaG9yYSBsb3MgZGF0b3MgZGUgcsOtb3MgUGFuY3J1ZG8sIEppbG9jYSwgSmFsw7NuIHkgRWJybwoKCmBgYHtyLCBldmFsID0gRkFMU0UsIGVjaG8gPSBGQUxTRX0KcmlvcyA8LSBzdF9yZWFkKGhlcmU6OmhlcmUoImRhdG9zIiwgIm1hcGFzIiwgIlJpb3MiLCAiTV9SaW9zX3YyLnNocCIpKQpjb2Rfcmlvc19ndWVub3MgPC0gYyg5MTM3NTcsIDkxMzY4NywgOTEzNjg1LCA5MTM2MTkpCm5vbWJyZV9yaW9zX2d1ZW5vcyA8LSBjKCJQYW5jcnVkbyIsICJKaWxvY2EiLCAiSmFsw7NuIiwgIkVicm8iKQpyaW9zIDwtIHJpb3MgJT4lIGZpbHRlcihDb2RfVW5pICVpbiUgY29kX3Jpb3NfZ3Vlbm9zKQpjbGFzcyhyaW9zKQojc2F2ZVJEUyhyaW9zLCBoZXJlOjpoZXJlKCJkYXRvcyIsICJtYXBhcyIsICJSaW9zIiwgInJpb19lYnJvLnJkcyIpKQpgYGAKCgpgYGB7cn0KcmlvcyA8LSByZWFkX3JkcyhoZXJlOjpoZXJlKCJkYXRvcyIsICAibWFwYXMiLCAiUmlvcyIsICJyaW9fZWJyby5yZHMiKSkKYGBgCgpWYW1vcyBhIHBvbmVyIHRvZG9zIGxvcyB0cmFtb3MgZGUgbG9zIDQgcsOtb3MganVudG9zLiBDb24gYHN1bW1hcmlzZSgpYAoKYGBge3J9CnJpb3NfeHggPC0gcmlvcyAlPiUgc3Rfc2V0X2dlb21ldHJ5KE5VTEwpCnJpb3NfeCA8LSByaW9zICU+JSBzZWxlY3QoTm9tX1JpbywgTG9uZ19SaW9fbSwgZ2VvbWV0cnkpICU+JSBncm91cF9ieShOb21fUmlvKSAlPiUgc3VtbWFyaXplKCkKYGBgCgoKSGFnYW1vcyB5YSB1biBtYXBhIGRlIGxvcyBsaW5kZXMgbXVuaWNpcGFsZXMsIGxvcyByw61vcyB5IHZveSBhIHNpdHVhciBQYW5jcnVkbyB5IFZhbGVuY2lhOgoKYGBge3IsIGVjaG8gPSBGQUxTRX0KcHVlYmxvcyAgPC0gZGF0YS5mcmFtZShub21icmUgPSBjKCJQYW5jcnVkbyIsICJWYWxlbmNpYSIpLCBsb25naXR1ZGUgPSBjKC0xLjAyODc4MSwgLTAuMzc1NjU3MiksIGxhdGl0dWRlID0gYyg0MC43NjE3NSwgMzkuNDc1MzQpICkgIy0gRVRSUzg5CmBgYAoKCgpgYGB7cn0KdGhlbWVfc2V0KGNvd3Bsb3Q6OnRoZW1lX21hcCgpKQpwIDwtIGdncGxvdChtdW5pKSArIAogICAgIGdlb21fc2YoY29sb3IgPSAiZ3JleSIsIGZpbGwgPSAid2hpdGUiLCBsd2QgPSAwLjAxKSArCiAgICAgZ2VvbV9zZihkYXRhID0gcmlvc194LCBjb2xvciA9ICJsaWdodGJsdWUiLCBsd2QgPSAxLjYpICsKICAgICBnZW9tX3BvaW50KGRhdGEgPSBwdWVibG9zLCBhZXMoeCA9IGxvbmdpdHVkZSwgeSA9IGxhdGl0dWRlKSwgc2l6ZSA9IDMsIGNvbG9yID0gImRhcmtyZWQiKSArCiAgICAgZ2VvbV90ZXh0KGRhdGEgID0gcHVlYmxvcywgYWVzKHggPSBsb25naXR1ZGUsIHkgPSBsYXRpdHVkZSwgbGFiZWwgPSBub21icmUpLCBjb2xvciA9ICJkYXJrcmVkIiwKICAgICAgICAgICAgICAgICAgICBmb250ZmFjZSA9ICJib2xkIiwgY2hlY2tfb3ZlcmxhcCA9IFRSVUUsIHNpemUgPSAyLjUpICsKICAgICBsYWJzKHRpdGxlID0gIlItTWFwYSBjb24gNCByw61vcyBnw7xlbm9zIiwKICAgICAgICAgIGNhcHRpb24gPSAiRGF0b3MgcHJvdmVuaWVudGVzIGRlbCBwYXF1ZXRlIExBVTJib3VuZGFyaWVzNHNwYWluIikgKyAgCiAgICAgICAgICB0aGVtZShwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGwgPSAiYWxpY2VibHVlIikpCnAKYGBgCgpWZW5nYSwgY2FsY3VsYW1vcyBsYXMgaW50ZXJzZWNjaW9uZXMgY29uIGBzZjo6c3RfaW50ZXJzZWN0cygpYCBwZXJvIGFudGVzIHRlbmVtb3MgcXVlIHBvbmVyIGxvcyAyIGRmcyBlbiBlbCBtaXNtbyBDUlM6CgpgYGB7ciwgZXZhbCA9IFRSVUV9Cm11bmlfYSA8LSBtdW5pICU+JSBzdF90cmFuc2Zvcm0oY3JzID0gc3RfY3JzKHJpb3NfeCkpCnp6X2EgPC0gc3RfaW50ZXJzZWN0cyhtdW5pX2EsIHJpb3NfeCwgc3BhcnNlID0gRkFMU0UsIHByZXBhcmVkID0gVFJVRSkKYGBgCgpBaG9yYSB5YSBzaSBzZWxlY2Npb25hbW9zIGFxdWVsbG9zIG11bmljaXBpb3MgcG9yIGxvcyBxdWUgcGFzYSBlbCBFQnJvLiBMbyBoYWdvIGNvbiBSLWJhc2U6CgpgYGB7ciwgZXZhbCA9IFRSVUV9Cm11bmlfZWJybyA8LSBtdW5pX2FbenpfYSxdCmBgYAoKClBhcmVjZSBxdWUgaGF5IGByIG5yb3cobXVuaV9lYnJvKWAgbXVuaWNpcGlvcyBwb3IgbG9xIGV1IHBhc2EgZWwgRWJyby4gQSB2ZXIgc2kgbm9zIHNhbGUgZWwgbWFwYQoKYGBge3J9CnAgKyBnZW9tX3NmKGRhdGEgPSBtdW5pX2Vicm8pICsKICAgIGdlb21fc2YoZGF0YSA9IHJpb3NfeCwgY29sb3IgPSAibGlnaHRibHVlIiwgbHdkID0gMC4yNSkKYGBgCgpObywgbm8gdm95IGEgb2J0ZW5lciBsb3MgbXVuaWNpcGlvcyBwb3IgbG9zIHF1ZSBwYXNhIGVsIHJpbyBQYW5jcnVkby4gRXNvIGVzdMOhIE1BTCEhISEKCjxicj4KICAKLS0tLS0tLS0tLS0tLQogIAo8YnI+CgoKIyBCaWJsaW8KCgotIFtJbnRyb2R1Y3Rpb24gdG8gR0lTIHdpdGggUl0oaHR0cHM6Ly93d3cuamVzc2VzYWRsZXIuY29tL3Bvc3QvZ2lzLXdpdGgtci1pbnRyby8/dXRtX2NvbnRlbnQ9YnVmZmVyMDcyYWUmdXRtX21lZGl1bT1zb2NpYWwmdXRtX3NvdXJjZT10d2l0dGVyLmNvbSZ1dG1fY2FtcGFpZ249YnVmZmVyKSBkZSBKZXNzZSBTYWRsZXIsIHNlZ8O6biBbTWlsZXMgTWNCYWluXShodHRwczovL3R3aXR0ZXIuY29tL01pbGVzTWNCYWluKSB1bmEgZGUgbGFzIG1lam9yZXMgaW50cm9kdWNjaW9uZXMgYSBHSVMgZW4gUi4KCi0gW0dlb2NvbXB1dGF0aW9uIHdpdGggUl0oaHR0cHM6Ly9nZW9jb21wci5yb2JpbmxvdmVsYWNlLm5ldC8pLiBBIGJvb2tkb3duIG9uIGdlb2dyYXBoaWMgZGF0YSBhbmFseXNpcywgdmlzdWFsaXphdGlvbiBhbmQgbW9kZWxpbmcgd2l0aCBSLgoKLSBbVmnDsWV0YXMgZGVsIHBhcXVldGUgYHNmYF0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3NmLykKCgotIFtDb29yZGluYXRlIFJlZmVyZW5jZSBTeXN0ZW1zXShodHRwczovL2RhdGFjYXJwZW50cnkub3JnL29yZ2FuaXphdGlvbi1nZW9zcGF0aWFsLzAzLWNycy9pbmRleC5odG1sKS4gVHV0b3JpYWwgZGUgRGF0YSBDYXJwZW50cnkgc29icmUgQ1JTLgoKCi0gW0ludHJvZHVjdGlvbiB0byBHZW9zcGF0aWFsIENvbmNlcHRzXShodHRwczovL2RhdGFjYXJwZW50cnkub3JnL29yZ2FuaXphdGlvbi1nZW9zcGF0aWFsLykgZGUgRGF0YSBDYXJwZW50cnkuCgoKLSBVbmEgc2VyaWUgZGUgcG9zdHMgc29icmU6RHJhd2luZyBiZWF1dGlmdWwgbWFwcyBwcm9ncmFtbWF0aWNhbGx5IHdpdGggUiwgc2YgYW5kIGdncGxvdDIuIFtCYXNpY3NdKGh0dHBzOi8vd3d3LnItc3BhdGlhbC5vcmcvL3IvMjAxOC8xMC8yNS9nZ3Bsb3QyLXNmLmh0bWwpLFtMYXllcnNdKGh0dHBzOi8vd3d3LnItc3BhdGlhbC5vcmcvL3IvMjAxOC8xMC8yNS9nZ3Bsb3QyLXNmLTIuaHRtbCkgeSBbTGF5b3V0c10oaHR0cHM6Ly93d3cuci1zcGF0aWFsLm9yZy8vci8yMDE4LzEwLzI1L2dncGxvdDItc2YtMy5odG1sKQoKLSBVbiBlamVtcGxvIGVuIGVsIHF1ZSBzZSB2YSBjb25zdHJ1eWVuZG8gbGEgW1dpbmtlbCB0cmlwZWwgcHJvamVjdGlvbl0oaHR0cHM6Ly93aWxrZWxhYi5vcmcvcHJhY3RpY2FsZ2cvYXJ0aWNsZXMvV2lua2VsX3RyaXBlbC5odG1sKQoKLSBVbiBbcG9zdF0oaHR0cHM6Ly93d3cuamxhLWRhdGEubmV0L2VuZy9sZWFmbGV0LWluLXItdGlwcy1hbmQtdHJpY2tzL2luZGV4Lmh0bWwpIHNvYnJlIGxhIHV0aWxpemFjacOzbiBkZSBsZWFmbGV0CgotIFBhcXVldGUgW2BvcGVucm91dGVzZXJ2aWNlYF0oaHR0cHM6Ly9naXNjaWVuY2UuZ2l0aHViLmlvL29wZW5yb3V0ZXNlcnZpY2Utci9hcnRpY2xlcy9vcGVucm91dGVzZXJ2aWNlLmh0bWwpIHBhcmEgY2FsY3VsYXIgcnV0YXMgY29uIFtvcGVucm91dGVzZXJ2aWNlXShodHRwczovL29wZW5yb3V0ZXNlcnZpY2Uub3JnLykuCgo8YnI+PGJyPgoK