1. Introducción
La clasificación de imágenes representa un área de estudio y
aplicación diversa en múltiples disciplinas. En su forma más básica,
implica tomar decisiones basadas en la información contenida en una
imagen. Este informe se centra en el desafío de clasificar imágenes de
individuos usando gafas mediante técnicas de aprendizaje estadístico
supervisado.
2. Datos
El conjunto de datos se obtuvo del curso T81-855: Applications of Deep
Learning dirigido por el profesor Jeff Heaton, en la Washington
University in St. Louis (WUSTL). Los datos, están alojados en un reto de
Kaggle
del año 2020, en donde se encuentran 5000 imágenes producidas
artificialmente a través de una red neuronal adversa generativa (GAN).
El conjunto incluye personas con y sin gafas, y dentro de las imágenes
de personas con gafas, se distinguen seis tipos de lentes. La figura 1
presenta una muestra de los tipos de lentes que se pueden encontrar en
el conjunto de datos:
Figura 1. Tipos de lentes
El conjunto de datos fue revisado y se eliminaron imágenes que
podrían afectar el aprendizaje del modelo, como personas con lentes a
medio construir o dos rostros en una misma imagen. El conjunto final
consta de 454 imágenes con lentes y 618 sin lentes, seleccionadas para
adaptarse a la capacidad y velocidad de procesamiento disponibles.
2.1. Procesamiento
Las imágenes se pueden vectorizar. Estos vectores generados a partir
de las imagenes serán las filas de una matriz de datos con la
información de intensidades para cada píxel en las respectivas
imágenes.
Para describir una forma de comparar los vectores, usaremos una
muestra de 6 sujetos (3 con gafas y 3 sin gafas). Las figuras serán
analizadas con el paquete EBImage
A continuación del modelo se toma una figura de un hombre sin
gafas.
#Podemos mostrarla con el siguiente código
display(img,method='raster',all=TRUE)

Igualmente, se muestra una figura de un hombre con gafas.
display(img2,method='raster',all=TRUE)

Para facilitar la visualización y vectorización, se procede a
convertir las imágenes en una escala de grises para no trabajar con el
modelo de color CMYK* (modelo de color sustractivo que se utiliza en la
impresión en colores, es la versión moderna y más precisa del antiguo
modelo tradicional de coloración).
Por ende, las imágenes presentadas ahora se van a visualizar en
espectro de grises.
#Escala de grises.
img_g<-channel(img,"gray")
img_g2<-channel(img2,"gray")
display(img_g,method='raster',all=TRUE)

display(img_g2,method='raster',all=TRUE)

Las imágenes fueron delimitadas seleccionando regiones específicas.
Las coordenadas utilizadas para el recorte fueron [150:900,
360:650].
img_crop = img_g[150:900,360:650]
display(img_crop,method='raster',all=TRUE)

img_crop2 = img_g2[150:900,360:650]
display(img_crop2,method='raster',all=TRUE)

Las imágenes fueron recortadas focalizando únicamente la nariz, ya
que el area logra ser distintivo y diferenciador para los sujetos que
usan lentes. Las coordenadas utilizadas para el recorte fueron [440:590,
440:580].
img_crop = img_g[440:590,440:580]
display(img_crop,method='raster',all=TRUE)

img_crop2 = img_g2[440:590,440:580]
display(img_crop2,method='raster',all=TRUE)

Vamos a presentar unos histogramas con el fin de ver por medio de sus
frecuencias si hay diferencias entre imágenes con y sin gafas. En los
siguientes histogramas se busca observar la cantidad de píxeles (21.291
píxeles) versus la intensidad (0 a 1).
setwd("./Modelos")
for (i in 1:6){
imagename <-lista[i]
img = readImage(imagename)
img_g<-channel(img,"gray")
img_crop = img_g[440:590,440:580]
par(mfrow=c(1,2))
display(img_crop*2,method='raster',all=TRUE)
hist(img_crop,breaks=100)
}






En resumen, se identificaron diferencias significativas entre los
histogramas de personas con y sin gafas. La variación en la intensidad
de luz, representada en una escala de 0 a 1, revela perfiles
distintivos. En las imágenes sin gafas, la frecuencia se encuentra hacia
el lado derecho, indicando mayor intensidad de luz.
Por el contrario, en las imágenes con lentes oscuros, la frecuencia
se sitúa hacia el lado izquierdo, indicando más píxeles oscuros. En el
caso de las imágenes con lentes transparentes, se observa una
variabilidad en la frecuencia, pero aún así se diferencian de las otras
categorías.
3. Modelo
Se implementó un código en R que busca comparar vectores de
intensidad mediante la creación de una matriz de datos. Esta matriz se
construye utilizando cuartiles obtenidos a partir de histogramas de
imágenes. El proceso incluye la lectura de imágenes, conversión a escala
de grises, recorte de regiones específicas, y cálculo de histogramas
para extraer los cuartiles. La información resultante se organiza en una
matriz, representando perfiles de comparación
Se realiza el mismo proceso anterior para el caso de fotos sin gafas,
que igualmente dicha matriz generada se almacena en otro archivo
csv.
En este modelo, generamos 200 valores numéricos para cada imagen
recortada, donde dichos valores representan la cantidad de píxeles.
A continuación, se presentan los valores divididos en rangos para las
imágenes con y sin gafas, respectivamente.
#Entrar al directorio base
#Leer los archivos
read.csv("FotosConGafas.csv")->gafas
#gafas <- read.csv("C:/Users/Lenovo/Downloads/FotosConGafas.csv")
read.csv("FotosSinGafas.csv")->Sgafas
#Sgafas <- read.csv("C:/Users/Lenovo/Downloads/FotosSinGafas.csv")
#Vamos a ver rápidamente los archivos:
head(gafas,5)
head(Sgafas,5)
Luego, la información que se tiene tanto para las imágenes que tienen
gafas como las que no, la adecuamos según lo requerido. Después de este
proceso se agrupa la información de ambos grupos y se crea un archivo
csv con los datos de entrenamiento.
#ordenar tablas, columnas, cambiar nombres
gafas[,2]->rownames(gafas)
gafas[,-c(1:2)]->gafas
t(gafas)->gafas
Sgafas[,2]->rownames(Sgafas)
Sgafas[,-c(1:2)]->Sgafas
t(Sgafas)->Sgafas
#Juntando la información
train<-data.frame(rbind(gafas,Sgafas),grupo=c(rep("Con_Gafas",nrow(gafas)),rep("Sin_Gafas",nrow(Sgafas))))
colnames(train)<-c(colnames(gafas),"grupo")
#write.csv(train,"train_1.csv")
Ahora, se realiza un gráfico para analizar la frecuencia con respecto
a la intensidad de luz.
#revisando los histogramas
train %>% pivot_longer(!grupo,names_to="range",values_to="value")->datos
ggplot(datos, aes(x=range, y=value, color=grupo)) +
geom_boxplot()+ theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1,size=8))
En dicho gráfico, se ve representado por medio del boxplot rojo la
información que poseen las imágenes con gafas y por medio del boxplot
azul, la información que poseen las imágenes sin gafas, que se
encuentran entre un rango de intensidad de luz(de 0 a 1), donde en
efecto se observó, que en el boxplot que representan las imágenes con
gafas(rojos) se encuentran entre rango de opacidad(lado izquierdo de la
imagen), lo cual evidencia que posee un perfil diferente que el de las
imágenes sin gafas(azul). Para el entrenamiento del modelo se tomarán
los datos del lado izquierdo para diferenciar las imágenes con y sin
gafas, dado que a la derecha la diferencia entre las imágenes es poca y
tendería a contaminar el modelo.
Con el fin de reducir la dimensionalidad se utiliza el Análisis de
Componentes Principales (ACP), para así representar la información
original pero en un espacio de dimensión menor(limitando la pérdida de
información).
De esta forma, se elabora el modelo mediante regresión de componentes
principales, donde el grupo representa la variable predictora y la data
a la variable de respuesta a modelar, así mismo, se creó una variable
donde el número 1 representa las imágenes con gafas y 0 las imágenes sin
gafas. Igualmente todo este procedimiento se guardó en un archivo
csv.
#Ejecutando el ACP
mutate(train,grupo=ifelse(grupo=="Con_Gafas",1,0))->train2
#creación del modelo
pcr(grupo~.,data=train2[,c(1:60,201)],scale=TRUE,validation="CV")->model
#Prueba redundante
predict(model,train2[,-201],ncomp=10)->prob
#Si se desea, se pueden juntar los datos creados con los valores reales (columna grupo de la tabla train)
cbind(prob,train2[,201])->prob
#write.csv(prob,"datos_generados.csv")
Después de crear el modelo, percibimos algunos casos donde la
predicción era muy baja en fotos con gafas (debido a marcos de las gafas
muy finos, plateados o transparentes) o muy alta en fotos sin gafas
(debido a personas con una tez más oscura), como se puede apreciar en el
siguiente gráfico.
setwd("./Sin gafas_training")
#Abrir las imágenes del modelo
list.files()->lista
i<-127
imagename <-lista[i]
img = readImage(imagename)
img_g<-channel(img,"gray")
img_crop = img_g[440:590,440:580]
display(img_g,method='raster')

setwd("../Con gafas_training")
#Abrir las imágenes del modelo
list.files()->lista
i<-219
imagename <-lista[i]
img = readImage(imagename)
img_g<-channel(img,"gray")
img_crop = img_g[440:590,440:580]
display(img_g,method='raster')

A continuación, se presenta un diagrama de violín, en donde se
representa la densidad de muestras que clasifica el modelo de
entrenamiento para los grupos 0 (con gafas) y 1 (sin gafas). Es fácil
destacar que la gran mayoría de las observaciones sin gafas se
encuentran por debajo de 0.5, proporcionándole la forma “ancha”
característica y el grupo con gafas tiene una forma más alargada y alta,
ocupando un rango de valores mayor.
setwd("../Tercera entrega")
read.csv("datos_generados.csv")->datos
#En el siguiente gráfico, se muestra como 0, aquellas imágenes que no deberían tener gafas y como 1, aquellas que deberían mostrar gafas.
ggplot(datos,aes(x=as.factor(X.1),y=prob))+geom_violin()

Una manera de comprobar las diferencias entre los grupos, es realizar
una prueba t para la diferencia de medias.
Prueba de hipótesis:
\(H_0\)=\(\mu_1-\mu_2=0\) vs \(H_1\)=\(\mu_1 -
\mu_2 \neq 0\)
#Para comprobar los datos y las diferencias significativas, podemos ejecutar un análisis de comparación de promedios (prueba t)
t.test(datos[which(datos$X.1==1),2],datos[which(datos$X.1==0),2])
##
## Welch Two Sample t-test
##
## data: datos[which(datos$X.1 == 1), 2] and datos[which(datos$X.1 == 0), 2]
## t = 29.103, df = 557.26, p-value < 2.2e-16
## alternative hypothesis: true difference in means is not equal to 0
## 95 percent confidence interval:
## 0.4641408 0.5313271
## sample estimates:
## mean of x mean of y
## 0.7104474 0.2127134
De acuerdo al resultado, nuestra sospecha es cierta de que las medias
de los valores para ambos grupos son diferentes y esto se contrasta con
el p-value de \(2.2e^{-16}\) arrojado
por la prueba, lo cual nos permite rechazar la hipótesis nula.
4. Validación
Del modelo predictivo trabajado hasta ahora, si bien es cierto que se
caracteriza por tener una lógica avanzada, requiere de términos
específicos. En este caso, el entrenamiento se realizó con imágenes
frontales (straight), entonces la validación debe hacerse con el mismo
tipo de imágenes. Es decir, el modelo puede predecir con un cierto grado
de error, si se le evalúa con imágenes similares a las que se usaron en
entrenamiento.
Del conjunto de validación obtenido de UCI
Machine Learning Repository tenemos 424 fotos que cumplen con esta
característica, se analizarán usando la librería pixmap
Las imágenes seleccionadas se convirtieron todas de formato pgm a
png. Una imagen de este conjunto es:
setwd("./Test_PNG")
#Abrir las imágenes del modelo
list.files()->lista
i<-4
imagename <-lista[i]
img = readImage(imagename)
img_g<-channel(img,"gray")
display(img_g,method='raster')

Una vez que se tienen todas las imágenes en formato png, repetiremos
los pasos iniciales para calcular los histogramas del grupo de
validación.
Finalmente se prueba y se revisan los resultados del modelo
elaborado.
Para ver el porcentaje de aciertos, se analizan los valores reales vs
predichos por el modelo, esto se hace en el archivo resultados.csv y
resultado (1).csv generados anteriomente. En el siguiente gráfico, se
observan valores de estimación más altos en las fotos con gafas, como
era de esperarse en concordancia por el resultado visto anteriomente en
el gráfico de violín. Se observan algunos valores atípicos.
read.csv("resultados.csv")->datos
#datos <- read.csv("C:/Users/Lenovo/Downloads/resultados.csv")
rbind(mutate(datos[datos$X %like% "open",],grupo="Sin_gafas"),
mutate(datos[datos$X %like% "sungla",],grupo="Con_gafas"))->datos2
ggplot(datos2,aes(x=grupo,y=grupo.10.comps))+geom_boxplot()+scale_y_log10()
Con la intención de visualizar el desempeño de este algoritmo(que se
emplea en aprendizaje supervisado) procedemos a realizar una matriz de
confusión, la cual es una herramienta muy útil para valorar qué tan
bueno es un modelo de clasificación basado en aprendizaje automático. En
particular, sirve para mostrar de forma explícita cuándo una clase es
confundida con otra, lo cual nos permite trabajar de forma separada con
distintos tipos de error.
#datosmw <- read_excel("C:/Users/Lenovo/Downloads/resultados (1).xlsx")
datosmw <- readxl::read_excel("resultados(1).xlsx")
datosmw$Teórico <- as.factor(datosmw$Teórico)
datosmw$Estimado <- as.factor(datosmw$Estimado)
cm <- conf_mat(datosmw, Estimado, Teórico)
autoplot(cm, type = "heatmap") +
scale_fill_gradient(low = "pink", high = "cyan")
Los valores de la diagonal principal a=209(98.58%) y d=173(81.6%)
corresponden tanto a los valores estimados de forma correcta por el
modelo, como a los verdaderos positivos d = 173(imágenes con lentes), y
a los verdaderos negativos a= 209(imágenes sin lentes).
La otra diagonal, por tanto, representa los casos en los que el
modelo se ha equivocado (c=39(18,4%) falsos negativos, b=3(1.42%) falsos
positivos).
Luego de haber analizado la matrix de confusión, se procede a
calcular la exactitud, la precisión, la sensibilidad y la
especificidad.
- Exactitud La exactitud (o «accuracy«) representa el porcentaje de
predicciones correctas frente al total. Por tanto, es el cociente entre
los casos bien clasificados por el modelo (verdaderos positivos y
verdaderos negativos, es decir, los valores en la diagonal de la matriz
de confusión), y la suma de todos los casos.
(209+173)/(209+173+39+3)=382/424= 0.9009434*100% = 90%
El valor obtenido para la exactitud en este modelo es del 90%.
- Precisión La precisión, (o“precision”) se refiere a lo cerca que
está el resultado de una predicción del valor verdadero. Por tanto, es
el cociente entre los casos positivos bien clasificados por el modelo y
el total de predicciones positivas.
(173)/(173+3)= 0.9829545*100% = 98.3%
El valor obtenido para este modelo es de un 98.3%. Por tanto, nuestro
modelo es más preciso que exacto.
- Sensibilidad La sensibilidad (o recall) representa la tasa de
verdaderos positivos (True Positive Rate) ó TP. Es la proporción entre
los casos positivos bien clasificados por el modelo, respecto al total
de positivos, el cual es, la habilidad del modelo de dectetar los casos
relevantes.
173/(39+173) = 0.8160377*100% = 81.6%
Un 81.6% es claramente un valor muy bueno para una métrica. Podemos
decir que nuestro algoritmo de clasificación es sensible, es decir, no
se le escapan muchos positivos.
- Especificidad La especificidad, por su parte, es la tasa de
verdaderos negativos, (“true negative rate”)o TN. Es la proporción entre
los casos negativos bien clasificados por el modelo, respecto al total
de negativos.
209/(209+3) = 0.9858491*100% = 98.6%
En este caso, la especificidad tiene un valor muy bueno. Esto
significa que su capacidad de discriminar los casos negativos es muy
buena. Es decir, es difícil obtener falsos positivos.
- Conclusión Como se pudo observar, para cada métrica se obtuvieron
valores altos, lo cual indica que el modelo tiene alta precisión y
exactitud, y además de ello tiene una alta sensibilidad(tiene alto
porcentaje en detectar casos positivos) y una alta especificidad(tiene
alto porcentaje en detectar casos negativos).
Nuevamente, como se hizo para el conjunto de entrenamiento, se
realiza una prueba t para la diferencia de medias entre los grupos.
Prueba de hipótesis:
\(H_0\)=\(\mu_1-\mu_2=0\) vs \(H_1\)=\(\mu_1 -
\mu_2 \neq 0\)
#Para comprobar los datos y las diferencias significativas, podemos ejecutar un análisis de comparación de medias (prueba t)
t.test(datos2[which(datos2$grupo=="Con_gafas"),2],datos[which(datos2$grupo=="Sin_gafas"),2])
##
## Welch Two Sample t-test
##
## data: datos2[which(datos2$grupo == "Con_gafas"), 2] and datos[which(datos2$grupo == "Sin_gafas"), 2]
## t = 3.8015, df = 412.13, p-value = 0.0001655
## alternative hypothesis: true difference in means is not equal to 0
## 95 percent confidence interval:
## 1.349349 4.239047
## sample estimates:
## mean of x mean of y
## 6.759925 3.965727
De acuerdo al resultado, nuestra sospecha es cierta de que las medias
de los valores para ambos grupos son diferentes y esto se contrasta con
el p-value de 0.0001655 arrojado por la prueba, lo cual nos permite
rechazar la hipótesis nula.
5. Interrogantes
5.1. ¿Qué afecta la capacidad del modelo en el conjunto de
validación?
La ubicación de los rostros. Preferiblemente se desea que todas
las imágenes sean centradas en donde se denote el septo nasal. Imágenes
de perfil o con el mentón levantado no son aptas para este
modelo.
La estadarización de las imágenes. Existen fotos extrañas en el
conjunto de validación, imágenes dobles ó con rostros a medias, esto
genera ruido y entorpece la capacidad de clasificar
correctamente.
La tez de las personas de las fotografías. Puede llegar a alterar
el modelo debido a la cantidad de píxeles (Ya sean mas oscuros o más
claros) debido a que no podría distinguir si la persona lleva gafas o
no.
Errores humanos. El mal procedimiento a la hora de escoger una
imagen y agregarla en la clasificación incorrecta.
Formato de imágenes. La transformación de imágenes de pgm a png
para el desarrollo del modelo.
5.2. ¿Hay alguna característica de las imágenes que mejore la
capacidad de respuesta?
Como se dijo anteriormente, que todas las imágenes sean
frontales.
La nitidez de las imágenes.
Para este modelo, las personas con tez clara deberían usar lentes
oscuros para que éste pueda identificar que sí poseen lentes.
6. Enlace de interés
Usted puede revisar el código y los datos usados en este proyecto en
nuestro repositorio en Github
LS0tDQp0aXRsZTogIkNsYXNpZmljYWNpw7NuIGRlIGltw6FnZW5lcyBhIHRyYXbDqXMgZGUgbGEgaWRlbnRpZmljYWNpw7NuIGRlIHJvc3Ryb3MgY29uIGxlbnRlcyBvIHNpbiBsZW50ZXMiDQphdXRob3I6ICJBbWlsZGVyIFN0ZXdpbiBPc3BpbmEgVG9iw7NuLCBOaWNvbGFzIFBlcmV6IFZhc3F1ZXosIEpvaG4gU3RpdmVuIE1lamlhIExvcGVyYS4iDQpkYXRlOiAiMjIvMTEvMjAyMyINCm91dHB1dDogDQogIGh0bWxfZG9jdW1lbnQ6DQogICAgdGhlbWU6IHNwYWNlbGFiDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgY29kZV9kb3dubG9hZDogeWVzDQogICAgZGZfcHJpbnQ6IHBhZ2VkDQogICAgdG9jOiB0cnVlDQogICAgdG9jX2Zsb2F0OiANCiAgICAgIGNvbGFwc2U6IGZhbHNlDQogICAgICANCiAgICAgIA0KLS0tIA0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkNCmBgYA0KDQoNCiMjIDEuIEludHJvZHVjY2nDs24NCg0KTGEgY2xhc2lmaWNhY2nDs24gZGUgaW3DoWdlbmVzIHJlcHJlc2VudGEgdW4gw6FyZWEgZGUgZXN0dWRpbyB5IGFwbGljYWNpw7NuIGRpdmVyc2EgZW4gbcO6bHRpcGxlcyBkaXNjaXBsaW5hcy4gRW4gc3UgZm9ybWEgbcOhcyBiw6FzaWNhLCBpbXBsaWNhIHRvbWFyIGRlY2lzaW9uZXMgYmFzYWRhcyBlbiBsYSBpbmZvcm1hY2nDs24gY29udGVuaWRhIGVuIHVuYSBpbWFnZW4uIEVzdGUgaW5mb3JtZSBzZSBjZW50cmEgZW4gZWwgZGVzYWbDrW8gZGUgY2xhc2lmaWNhciBpbcOhZ2VuZXMgZGUgaW5kaXZpZHVvcyB1c2FuZG8gZ2FmYXMgbWVkaWFudGUgdMOpY25pY2FzIGRlIGFwcmVuZGl6YWplIGVzdGFkw61zdGljbyBzdXBlcnZpc2Fkby4NCg0KDQojIyAyLiBEYXRvcw0KDQpFbCBjb25qdW50byBkZSBkYXRvcyBzZSBvYnR1dm8gZGVsIGN1cnNvIFQ4MS04NTU6IFtBcHBsaWNhdGlvbnMgb2YgRGVlcCBMZWFybmluZ10oaHR0cHM6Ly9zaXRlcy53dXN0bC5lZHUvamVmZmhlYXRvbi90ODEtNTU4LykgZGlyaWdpZG8gcG9yIGVsIHByb2Zlc29yIFtKZWZmIEhlYXRvbl0oaHR0cHM6Ly9naXRodWIuY29tL2plZmZoZWF0b24pLCBlbiBsYSBXYXNoaW5ndG9uIFVuaXZlcnNpdHkgaW4gU3QuIExvdWlzIChXVVNUTCkuIExvcyBkYXRvcywgZXN0w6FuIGFsb2phZG9zIGVuIHVuIHJldG8gZGUgW0thZ2dsZV0oaHR0cHM6Ly93d3cua2FnZ2xlLmNvbS9qZWZmaGVhdG9uL2dsYXNzZXMtb3Itbm8tZ2xhc3NlcykgZGVsIGHDsW8gMjAyMCwgZW4gZG9uZGUgc2UgZW5jdWVudHJhbiA1MDAwIGltw6FnZW5lcyBwcm9kdWNpZGFzIGFydGlmaWNpYWxtZW50ZSBhIHRyYXbDqXMgZGUgdW5hIHJlZCBuZXVyb25hbCBhZHZlcnNhIGdlbmVyYXRpdmEgKEdBTikuIEVsIGNvbmp1bnRvIGluY2x1eWUgcGVyc29uYXMgY29uIHkgc2luIGdhZmFzLCB5IGRlbnRybyBkZSBsYXMgaW3DoWdlbmVzIGRlIHBlcnNvbmFzIGNvbiBnYWZhcywgc2UgZGlzdGluZ3VlbiBzZWlzIHRpcG9zIGRlIGxlbnRlcy4gTGEgZmlndXJhIDEgcHJlc2VudGEgdW5hIG11ZXN0cmEgZGUgbG9zIHRpcG9zIGRlIGxlbnRlcyBxdWUgc2UgcHVlZGVuIGVuY29udHJhciBlbiBlbCBjb25qdW50byBkZSBkYXRvczoNCg0KDQohW0ZpZ3VyYSAxLiBUaXBvcyBkZSBsZW50ZXNdKGh0dHBzOi8vZGF0YS5oZWF0b25yZXNlYXJjaC5jb20vaW1hZ2VzL3d1c3RsL2thZ2dsZS9rYWdnbGUtZmFjZXMtZ2xhc3Nlcy0yLnBuZykNCg0KRWwgY29uanVudG8gZGUgZGF0b3MgZnVlIHJldmlzYWRvIHkgc2UgZWxpbWluYXJvbiBpbcOhZ2VuZXMgcXVlIHBvZHLDrWFuIGFmZWN0YXIgZWwgYXByZW5kaXphamUgZGVsIG1vZGVsbywgY29tbyBwZXJzb25hcyBjb24gbGVudGVzIGEgbWVkaW8gY29uc3RydWlyIG8gZG9zIHJvc3Ryb3MgZW4gdW5hIG1pc21hIGltYWdlbi4gRWwgY29uanVudG8gZmluYWwgY29uc3RhIGRlIDQ1NCBpbcOhZ2VuZXMgY29uIGxlbnRlcyB5IDYxOCBzaW4gbGVudGVzLCBzZWxlY2Npb25hZGFzIHBhcmEgYWRhcHRhcnNlIGEgbGEgY2FwYWNpZGFkIHkgdmVsb2NpZGFkIGRlIHByb2Nlc2FtaWVudG8gZGlzcG9uaWJsZXMuDQoNCg0KIyMjIDIuMS4gUHJvY2VzYW1pZW50byANCg0KTGFzIGltw6FnZW5lcyBzZSBwdWVkZW4gdmVjdG9yaXphci4gRXN0b3MgdmVjdG9yZXMgZ2VuZXJhZG9zIGEgcGFydGlyIGRlIGxhcyBpbWFnZW5lcyBzZXLDoW4gbGFzIGZpbGFzIGRlIHVuYSBtYXRyaXogZGUgZGF0b3MgY29uIGxhIGluZm9ybWFjacOzbiBkZSBpbnRlbnNpZGFkZXMgcGFyYSBjYWRhIHDDrXhlbCBlbiBsYXMgcmVzcGVjdGl2YXMgaW3DoWdlbmVzLg0KDQpQYXJhIGRlc2NyaWJpciB1bmEgZm9ybWEgZGUgY29tcGFyYXIgbG9zIHZlY3RvcmVzLCB1c2FyZW1vcyB1bmEgbXVlc3RyYSBkZSA2IHN1amV0b3MgKDMgY29uIGdhZmFzIHkgMyBzaW4gZ2FmYXMpLiBMYXMgZmlndXJhcyBzZXLDoW4gYW5hbGl6YWRhcyBjb24gZWwgcGFxdWV0ZSBbRUJJbWFnZV0oaHR0cHM6Ly9iaW9jb25kdWN0b3Iub3JnL3BhY2thZ2VzL3JlbGVhc2UvYmlvYy9odG1sL0VCSW1hZ2UuaHRtbCkNCg0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCByZXN1bHRzID0gJ2hpZGUnLCBlY2hvPUYgfQ0KI2luc3RhbGFyIHBhcXVldGVzIHkgbGlicmVyw61hcyBuZWNlc2FyaWFzDQoNCiNpZiAoIXJlcXVpcmUoIkJpb2NNYW5hZ2VyIiwgcXVpZXRseSA9IFRSVUUpKQ0KIyAgaW5zdGFsbC5wYWNrYWdlcygiQmlvY01hbmFnZXIiKQ0KDQojQmlvY01hbmFnZXI6Omluc3RhbGwoIkVCSW1hZ2UiKQ0KDQojaW5zdGFsbC5wYWNrYWdlcygicGxzIikNCiNpbnN0YWxsLnBhY2thZ2VzKCJwaXhtYXAiKQ0KI2luc3RhbGwucGFja2FnZXMoInlhcmRzdGljayIpDQoNCg0KbGlicmFyeShFQkltYWdlKQ0KbGlicmFyeShwbHMpDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KHBpeG1hcCkNCmxpYnJhcnkoZGF0YS50YWJsZSkNCmxpYnJhcnkocmVhZHhsKQ0KbGlicmFyeSh5YXJkc3RpY2spDQoNCg0KYGBgDQoNCg0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCByZXN1bHRzID0gJ2hpZGUnLCBlY2hvPUYgfQ0KI0VudHJhciBhbCBkaXJlY3RvcmlvIGJhc2UNCg0KDQoNCnNldHdkKCIuL01vZGVsb3MiKQ0KDQoNCg0KI0FicmlyIGxhcyBpbcOhZ2VuZXMgZGVsIG1vZGVsbw0KbGlzdC5maWxlcygpLT5saXN0YQ0KDQojVmVyIGxhcyBmaWd1cmFzIGNpdGFkYXMNCg0KYGBgDQoNCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgcmVzdWx0cyA9ICdoaWRlJywgZWNobz1GIH0NCiNMdWVnbywgYWJyaXJlbW9zIHVuYSBkZSBlc2FzIGZpZ3VyYXMNCg0KDQpzZXR3ZCgiLi9Nb2RlbG9zIikNCg0KDQppbWFnZW5hbWUgPC1saXN0YVsxXQ0KaW1nID0gcmVhZEltYWdlKGltYWdlbmFtZSkNCmltYWdlbmFtZSA8LWxpc3RhWzJdDQppbWcyID0gcmVhZEltYWdlKGltYWdlbmFtZSkNCmBgYA0KQSBjb250aW51YWNpw7NuIGRlbCBtb2RlbG8gc2UgdG9tYSB1bmEgZmlndXJhIGRlIHVuIGhvbWJyZSBzaW4gZ2FmYXMuDQoNCmBgYHtyICBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFICwgZWNobz1ULGZpZy53aWR0aD0gMixmaWcuaGVpZ2h0PSAyLGZpZy5hbGlnbj0nY2VudGVyJ30NCiNQb2RlbW9zIG1vc3RyYXJsYSBjb24gZWwgc2lndWllbnRlIGPDs2RpZ28NCmRpc3BsYXkoaW1nLG1ldGhvZD0ncmFzdGVyJyxhbGw9VFJVRSkNCmBgYA0KDQpJZ3VhbG1lbnRlLCBzZSBtdWVzdHJhIHVuYSBmaWd1cmEgZGUgdW4gaG9tYnJlIGNvbiBnYWZhcy4NCg0KYGBge3IgIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UgLCBlY2hvPVQsZmlnLndpZHRoPSAyLGZpZy5oZWlnaHQ9IDIsZmlnLmFsaWduPSdjZW50ZXInIH0NCmRpc3BsYXkoaW1nMixtZXRob2Q9J3Jhc3RlcicsYWxsPVRSVUUpDQpgYGANCg0KUGFyYSBmYWNpbGl0YXIgbGEgdmlzdWFsaXphY2nDs24geSB2ZWN0b3JpemFjacOzbiwgc2UgcHJvY2VkZSBhIGNvbnZlcnRpciBsYXMgaW3DoWdlbmVzIGVuIHVuYSBlc2NhbGEgZGUgZ3Jpc2VzIHBhcmEgbm8gdHJhYmFqYXIgY29uIGVsIG1vZGVsbyBkZSBjb2xvciBDTVlLKiAobW9kZWxvIGRlIGNvbG9yIHN1c3RyYWN0aXZvIHF1ZSBzZSB1dGlsaXphIGVuIGxhIGltcHJlc2nDs24gZW4gY29sb3JlcywgZXMgbGEgdmVyc2nDs24gbW9kZXJuYSB5IG3DoXMgcHJlY2lzYSBkZWwgYW50aWd1byBtb2RlbG8gdHJhZGljaW9uYWwgZGUgY29sb3JhY2nDs24pLg0KDQpQb3IgZW5kZSwgbGFzIGltw6FnZW5lcyBwcmVzZW50YWRhcyBhaG9yYSBzZSB2YW4gYSB2aXN1YWxpemFyIGVuIGVzcGVjdHJvIGRlIGdyaXNlcy4NCg0KYGBge3IgIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UgLCBlY2hvPVQsZmlnLndpZHRoPSAyLGZpZy5oZWlnaHQ9IDIsZmlnLmFsaWduPSdjZW50ZXInIH0NCiNFc2NhbGEgZGUgZ3Jpc2VzLg0KDQppbWdfZzwtY2hhbm5lbChpbWcsImdyYXkiKQ0KaW1nX2cyPC1jaGFubmVsKGltZzIsImdyYXkiKQ0KDQpkaXNwbGF5KGltZ19nLG1ldGhvZD0ncmFzdGVyJyxhbGw9VFJVRSkNCmRpc3BsYXkoaW1nX2cyLG1ldGhvZD0ncmFzdGVyJyxhbGw9VFJVRSkNCmBgYA0KDQpMYXMgaW3DoWdlbmVzIGZ1ZXJvbiBkZWxpbWl0YWRhcyBzZWxlY2Npb25hbmRvIHJlZ2lvbmVzIGVzcGVjw61maWNhcy4gTGFzIGNvb3JkZW5hZGFzIHV0aWxpemFkYXMgcGFyYSBlbCByZWNvcnRlIGZ1ZXJvbiBbMTUwOjkwMCwgMzYwOjY1MF0uDQoNCmBgYHtyICBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFICwgZWNobz1ULCBmaWcud2lkdGg9IDMsZmlnLmhlaWdodD0gMyxmaWcuYWxpZ249J2NlbnRlcicgfQ0KaW1nX2Nyb3AgPSBpbWdfZ1sxNTA6OTAwLDM2MDo2NTBdDQpkaXNwbGF5KGltZ19jcm9wLG1ldGhvZD0ncmFzdGVyJyxhbGw9VFJVRSkNCg0KaW1nX2Nyb3AyID0gaW1nX2cyWzE1MDo5MDAsMzYwOjY1MF0NCmRpc3BsYXkoaW1nX2Nyb3AyLG1ldGhvZD0ncmFzdGVyJyxhbGw9VFJVRSkNCmBgYA0KDQpMYXMgaW3DoWdlbmVzIGZ1ZXJvbiByZWNvcnRhZGFzIGZvY2FsaXphbmRvIMO6bmljYW1lbnRlIGxhIG5hcml6LCB5YSBxdWUgZWwgYXJlYSBsb2dyYSBzZXIgZGlzdGludGl2byB5IGRpZmVyZW5jaWFkb3IgcGFyYSBsb3Mgc3VqZXRvcyBxdWUgdXNhbiBsZW50ZXMuIExhcyBjb29yZGVuYWRhcyB1dGlsaXphZGFzIHBhcmEgZWwgcmVjb3J0ZSBmdWVyb24gWzQ0MDo1OTAsIDQ0MDo1ODBdLg0KDQpgYGB7ciAgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSAsIGVjaG89VCwgZmlnLndpZHRoPSAzLGZpZy5oZWlnaHQ9IDMsZmlnLmFsaWduPSdjZW50ZXInIH0NCmltZ19jcm9wID0gaW1nX2dbNDQwOjU5MCw0NDA6NTgwXQ0KZGlzcGxheShpbWdfY3JvcCxtZXRob2Q9J3Jhc3RlcicsYWxsPVRSVUUpDQoNCmltZ19jcm9wMiA9IGltZ19nMls0NDA6NTkwLDQ0MDo1ODBdDQpkaXNwbGF5KGltZ19jcm9wMixtZXRob2Q9J3Jhc3RlcicsYWxsPVRSVUUpDQpgYGANCg0KVmFtb3MgYSBwcmVzZW50YXIgdW5vcyBoaXN0b2dyYW1hcyBjb24gZWwgZmluIGRlIHZlciBwb3IgbWVkaW8gZGUgc3VzIGZyZWN1ZW5jaWFzIHNpIGhheSBkaWZlcmVuY2lhcyBlbnRyZSBpbcOhZ2VuZXMgY29uIHkgc2luIGdhZmFzLiBFbiBsb3Mgc2lndWllbnRlcyBoaXN0b2dyYW1hcyBzZSBidXNjYSBvYnNlcnZhciBsYSBjYW50aWRhZCBkZSBww614ZWxlcyAoMjEuMjkxIHDDrXhlbGVzKSB2ZXJzdXMgbGEgaW50ZW5zaWRhZCAoMCBhIDEpLg0KDQpgYGB7ciAgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSAsIGVjaG89VCB9DQoNCnNldHdkKCIuL01vZGVsb3MiKQ0KDQoNCmZvciAoaSBpbiAxOjYpew0KaW1hZ2VuYW1lIDwtbGlzdGFbaV0NCg0KaW1nID0gcmVhZEltYWdlKGltYWdlbmFtZSkNCg0KaW1nX2c8LWNoYW5uZWwoaW1nLCJncmF5IikNCg0KaW1nX2Nyb3AgPSBpbWdfZ1s0NDA6NTkwLDQ0MDo1ODBdDQpwYXIobWZyb3c9YygxLDIpKQ0KZGlzcGxheShpbWdfY3JvcCoyLG1ldGhvZD0ncmFzdGVyJyxhbGw9VFJVRSkNCg0KaGlzdChpbWdfY3JvcCxicmVha3M9MTAwKQ0KfQ0KDQpgYGANCg0KRW4gcmVzdW1lbiwgc2UgaWRlbnRpZmljYXJvbiBkaWZlcmVuY2lhcyBzaWduaWZpY2F0aXZhcyBlbnRyZSBsb3MgaGlzdG9ncmFtYXMgZGUgcGVyc29uYXMgY29uIHkgc2luIGdhZmFzLiBMYSB2YXJpYWNpw7NuIGVuIGxhIGludGVuc2lkYWQgZGUgbHV6LCByZXByZXNlbnRhZGEgZW4gdW5hIGVzY2FsYSBkZSAwIGEgMSwgcmV2ZWxhIHBlcmZpbGVzIGRpc3RpbnRpdm9zLiBFbiBsYXMgaW3DoWdlbmVzIHNpbiBnYWZhcywgbGEgZnJlY3VlbmNpYSBzZSBlbmN1ZW50cmEgaGFjaWEgZWwgbGFkbyBkZXJlY2hvLCBpbmRpY2FuZG8gbWF5b3IgaW50ZW5zaWRhZCBkZSBsdXouIA0KDQpQb3IgZWwgY29udHJhcmlvLCBlbiBsYXMgaW3DoWdlbmVzIGNvbiBsZW50ZXMgb3NjdXJvcywgbGEgZnJlY3VlbmNpYSBzZSBzaXTDumEgaGFjaWEgZWwgbGFkbyBpenF1aWVyZG8sIGluZGljYW5kbyBtw6FzIHDDrXhlbGVzIG9zY3Vyb3MuIEVuIGVsIGNhc28gZGUgbGFzIGltw6FnZW5lcyBjb24gbGVudGVzIHRyYW5zcGFyZW50ZXMsIHNlIG9ic2VydmEgdW5hIHZhcmlhYmlsaWRhZCBlbiBsYSBmcmVjdWVuY2lhLCBwZXJvIGHDum4gYXPDrSBzZSBkaWZlcmVuY2lhbiBkZSBsYXMgb3RyYXMgY2F0ZWdvcsOtYXMuDQoNCiMjIDMuIE1vZGVsbw0KDQpTZSBpbXBsZW1lbnTDsyB1biBjw7NkaWdvIGVuIFIgcXVlIGJ1c2NhIGNvbXBhcmFyIHZlY3RvcmVzIGRlIGludGVuc2lkYWQgbWVkaWFudGUgbGEgY3JlYWNpw7NuIGRlIHVuYSBtYXRyaXogZGUgZGF0b3MuIEVzdGEgbWF0cml6IHNlIGNvbnN0cnV5ZSB1dGlsaXphbmRvIGN1YXJ0aWxlcyBvYnRlbmlkb3MgYSBwYXJ0aXIgZGUgaGlzdG9ncmFtYXMgZGUgaW3DoWdlbmVzLiBFbCBwcm9jZXNvIGluY2x1eWUgbGEgbGVjdHVyYSBkZSBpbcOhZ2VuZXMsIGNvbnZlcnNpw7NuIGEgZXNjYWxhIGRlIGdyaXNlcywgcmVjb3J0ZSBkZSByZWdpb25lcyBlc3BlY8OtZmljYXMsIHkgY8OhbGN1bG8gZGUgaGlzdG9ncmFtYXMgcGFyYSBleHRyYWVyIGxvcyBjdWFydGlsZXMuIExhIGluZm9ybWFjacOzbiByZXN1bHRhbnRlIHNlIG9yZ2FuaXphIGVuIHVuYSBtYXRyaXosIHJlcHJlc2VudGFuZG8gcGVyZmlsZXMgZGUgY29tcGFyYWNpw7NuDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIHJlc3VsdHMgPSAnaGlkZScsIGVjaG89Rn0NCiNFbnRyYXIgYWwgZGlyZWN0b3JpbyBiYXNlDQoNCg0KIyANCiMgI0FicmlyIGxhcyBpbcOhZ2VuZXMgZGVsIG1vZGVsbw0KIyBsaXN0LmZpbGVzKCktPmxpc3RhDQoNCiNWYW1vcyBhIGNyZWFyIHVuYSBjb2x1bW5hIGNvbiBsb3MgY3VhcnRpbGVzIG5lY2VzYXJpb3MgKGJhc2FkbyBlbiBlbCBjb25jZXB0byBkZSBoaXN0b2dyYW1hKQ0KDQojIHBhc3RlMChzZXEoMCwwLjk5NSwwLjAwNSksIi0iLHNlcSgwLjAwNSwxLDAuMDA1KSktPmN1YXJ0aWxlcw0KIyANCiMgZGF0YS5mcmFtZSh2YWx1ZXM9Y3VhcnRpbGVzKS0+Z2FmYXMNCiMgDQojIGZvciAoaSBpbiAxOmxlbmd0aChsaXN0YSkpew0KIyBpbWFnZW5hbWUgPC1saXN0YVtpXQ0KIyANCiMgaW1nID0gcmVhZEltYWdlKGltYWdlbmFtZSkNCiMgDQojIGltZ19nPC1jaGFubmVsKGltZywiZ3JheSIpDQojIA0KIyBpbWdfY3JvcCA9IGltZ19nWzQ0MDo1OTAsNDQwOjU4MF0NCiMgaGlzdChpbWdfY3JvcCxicmVha3M9c2VxKDAsMSwwLjAwNSkpLT5kYWRvcw0KIyBkYXRhLmZyYW1lKGdhZmFzLGRhZG9zJGNvdW50cyktPmdhZmFzDQojIA0KIyB9DQojIGNvbG5hbWVzKGdhZmFzKTwtYygidmFsdWVzIixsaXN0YSkNCg0KI2d1YXJkYW5kbyBlc3RlIHJlc3VsdGFkbw0KDQoNCmBgYA0KDQpTZSByZWFsaXphIGVsIG1pc21vIHByb2Nlc28gYW50ZXJpb3IgcGFyYSBlbCBjYXNvIGRlIGZvdG9zIHNpbiBnYWZhcywgcXVlIGlndWFsbWVudGUgZGljaGEgbWF0cml6IGdlbmVyYWRhIHNlIGFsbWFjZW5hIGVuIG90cm8gYXJjaGl2byBjc3YuDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCByZXN1bHRzID0gJ2hpZGUnLCBlY2hvPUZ9DQoNCiNFbnRyYXIgYWwgZGlyZWN0b3JpbyBiYXNlDQoNCg0KDQojIA0KIyAjQWJyaXIgbGFzIGltw6FnZW5lcyBkZWwgbW9kZWxvDQojIGxpc3QuZmlsZXMoKS0+bGlzdGENCiMgDQojICNWYW1vcyBhIGNyZWFyIHVuYSBjb2x1bW5hIGNvbiBsb3MgY3VhcnRpbGVzIG5lY2VzYXJpb3MgKGJhc2FkbyBlbiBlbCBjb25jZXB0byBkZSBoaXN0b2dyYW1hKQ0KIyANCiMgcGFzdGUwKHNlcSgwLDAuOTk1LDAuMDA1KSwiLSIsc2VxKDAuMDA1LDEsMC4wMDUpKS0+Y3VhcnRpbGVzDQojIA0KIyBkYXRhLmZyYW1lKHZhbHVlcz1jdWFydGlsZXMpLT5TZ2FmYXMNCiMgDQojIGZvciAoaSBpbiAxOmxlbmd0aChsaXN0YSkpew0KIyBpbWFnZW5hbWUgPC1saXN0YVtpXQ0KIyANCiMgaW1nID0gcmVhZEltYWdlKGltYWdlbmFtZSkNCiMgDQojIGltZ19nPC1jaGFubmVsKGltZywiZ3JheSIpDQojIA0KIyBpbWdfY3JvcCA9IGltZ19nWzQ0MDo1OTAsNDQwOjU4MF0NCiMgaGlzdChpbWdfY3JvcCxicmVha3M9c2VxKDAsMSwwLjAwNSkpLT5kYWRvcw0KIyBkYXRhLmZyYW1lKFNnYWZhcyxkYWRvcyRjb3VudHMpLT5TZ2FmYXMNCiMgDQojIH0NCiMgY29sbmFtZXMoU2dhZmFzKTwtYygidmFsdWVzIixsaXN0YSkNCiMgDQojICNndWFyZGFuZG8gZXN0ZSByZXN1bHRhZG8NCiMgDQoNCmBgYA0KDQpFbiBlc3RlIG1vZGVsbywgZ2VuZXJhbW9zIDIwMCB2YWxvcmVzIG51bcOpcmljb3MgcGFyYSBjYWRhIGltYWdlbiByZWNvcnRhZGEsIGRvbmRlIGRpY2hvcyB2YWxvcmVzIHJlcHJlc2VudGFuIGxhIGNhbnRpZGFkIGRlIHDDrXhlbGVzLg0KDQpBIGNvbnRpbnVhY2nDs24sIHNlIHByZXNlbnRhbiBsb3MgdmFsb3JlcyBkaXZpZGlkb3MgZW4gcmFuZ29zIHBhcmEgbGFzIGltw6FnZW5lcyBjb24geSBzaW4gZ2FmYXMsIHJlc3BlY3RpdmFtZW50ZS4NCmBgYHtyLCBmaWcuZGltID0gYygxOCwgNiksIGRwaT02MDAsIG1lc3NhZ2U9RkFMU0V9DQojRW50cmFyIGFsIGRpcmVjdG9yaW8gYmFzZQ0KDQoNCiNMZWVyIGxvcyBhcmNoaXZvcw0KDQpyZWFkLmNzdigiRm90b3NDb25HYWZhcy5jc3YiKS0+Z2FmYXMNCg0KI2dhZmFzIDwtIHJlYWQuY3N2KCJDOi9Vc2Vycy9MZW5vdm8vRG93bmxvYWRzL0ZvdG9zQ29uR2FmYXMuY3N2IikNCg0KcmVhZC5jc3YoIkZvdG9zU2luR2FmYXMuY3N2IiktPlNnYWZhcw0KDQojU2dhZmFzIDwtIHJlYWQuY3N2KCJDOi9Vc2Vycy9MZW5vdm8vRG93bmxvYWRzL0ZvdG9zU2luR2FmYXMuY3N2IikNCg0KI1ZhbW9zIGEgdmVyIHLDoXBpZGFtZW50ZSBsb3MgYXJjaGl2b3M6DQoNCmhlYWQoZ2FmYXMsNSkNCg0KaGVhZChTZ2FmYXMsNSkNCmBgYA0KDQpMdWVnbywgbGEgaW5mb3JtYWNpw7NuIHF1ZSBzZSB0aWVuZSB0YW50byBwYXJhIGxhcyBpbcOhZ2VuZXMgcXVlIHRpZW5lbiBnYWZhcyBjb21vIGxhcyBxdWUgbm8sIGxhIGFkZWN1YW1vcyBzZWfDum4gbG8gcmVxdWVyaWRvLiBEZXNwdcOpcyBkZSBlc3RlIHByb2Nlc28gc2UgYWdydXBhIGxhIGluZm9ybWFjacOzbiAgZGUgYW1ib3MgZ3J1cG9zIHkgc2UgY3JlYSB1biBhcmNoaXZvIGNzdiBjb24gbG9zIGRhdG9zIGRlIGVudHJlbmFtaWVudG8uDQpgYGB7ciwgZmlnLmRpbSA9IGMoMTgsIDYpLCBkcGk9NjAwLCBtZXNzYWdlPUZBTFNFfQ0KI29yZGVuYXIgdGFibGFzLCBjb2x1bW5hcywgY2FtYmlhciBub21icmVzDQoNCmdhZmFzWywyXS0+cm93bmFtZXMoZ2FmYXMpDQpnYWZhc1ssLWMoMToyKV0tPmdhZmFzDQp0KGdhZmFzKS0+Z2FmYXMNCg0KDQpTZ2FmYXNbLDJdLT5yb3duYW1lcyhTZ2FmYXMpDQpTZ2FmYXNbLC1jKDE6MildLT5TZ2FmYXMNCnQoU2dhZmFzKS0+U2dhZmFzDQoNCiNKdW50YW5kbyBsYSBpbmZvcm1hY2nDs24NCg0KdHJhaW48LWRhdGEuZnJhbWUocmJpbmQoZ2FmYXMsU2dhZmFzKSxncnVwbz1jKHJlcCgiQ29uX0dhZmFzIixucm93KGdhZmFzKSkscmVwKCJTaW5fR2FmYXMiLG5yb3coU2dhZmFzKSkpKQ0KY29sbmFtZXModHJhaW4pPC1jKGNvbG5hbWVzKGdhZmFzKSwiZ3J1cG8iKQ0KDQojd3JpdGUuY3N2KHRyYWluLCJ0cmFpbl8xLmNzdiIpDQpgYGANCg0KQWhvcmEsIHNlIHJlYWxpemEgdW4gZ3LDoWZpY28gcGFyYSBhbmFsaXphciBsYSBmcmVjdWVuY2lhIGNvbiByZXNwZWN0byBhIGxhIGludGVuc2lkYWQgZGUgbHV6Lg0KYGBge3IsIGZpZy5kaW0gPSBjKDE4LCA2KSwgZHBpPTYwMCwgbWVzc2FnZT1GQUxTRX0NCiNyZXZpc2FuZG8gbG9zIGhpc3RvZ3JhbWFzDQoNCg0KdHJhaW4gJT4lIHBpdm90X2xvbmdlcighZ3J1cG8sbmFtZXNfdG89InJhbmdlIix2YWx1ZXNfdG89InZhbHVlIiktPmRhdG9zDQoNCg0KZ2dwbG90KGRhdG9zLCBhZXMoeD1yYW5nZSwgeT12YWx1ZSwgY29sb3I9Z3J1cG8pKSArDQogIGdlb21fYm94cGxvdCgpKyB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCB2anVzdCA9IDAuNSwgaGp1c3Q9MSxzaXplPTgpKQ0KYGBgDQpFbiBkaWNobyBncsOhZmljbywgc2UgdmUgcmVwcmVzZW50YWRvIHBvciBtZWRpbyBkZWwgYm94cGxvdCByb2pvIGxhIGluZm9ybWFjacOzbiBxdWUgcG9zZWVuIGxhcyBpbcOhZ2VuZXMgY29uIGdhZmFzIHkgcG9yIG1lZGlvIGRlbCBib3hwbG90IGF6dWwsIGxhIGluZm9ybWFjacOzbiBxdWUgcG9zZWVuIGxhcyBpbcOhZ2VuZXMgc2luIGdhZmFzLCBxdWUgc2UgZW5jdWVudHJhbiBlbnRyZSB1biByYW5nbyBkZSBpbnRlbnNpZGFkIGRlIGx1eihkZSAwIGEgMSksIGRvbmRlIGVuIGVmZWN0byBzZSBvYnNlcnbDsywgcXVlIGVuIGVsIGJveHBsb3QgcXVlIHJlcHJlc2VudGFuIGxhcyBpbcOhZ2VuZXMgY29uIGdhZmFzKHJvam9zKSBzZSBlbmN1ZW50cmFuIGVudHJlIHJhbmdvIGRlIG9wYWNpZGFkKGxhZG8gaXpxdWllcmRvIGRlIGxhIGltYWdlbiksIGxvIGN1YWwgZXZpZGVuY2lhIHF1ZSBwb3NlZSB1biBwZXJmaWwgZGlmZXJlbnRlIHF1ZSBlbCBkZSBsYXMgaW3DoWdlbmVzIHNpbiBnYWZhcyhhenVsKS4gUGFyYSBlbCBlbnRyZW5hbWllbnRvIGRlbCBtb2RlbG8gc2UgdG9tYXLDoW4gbG9zIGRhdG9zIGRlbCBsYWRvIGl6cXVpZXJkbyBwYXJhIGRpZmVyZW5jaWFyIGxhcyBpbcOhZ2VuZXMgY29uIHkgc2luIGdhZmFzLCBkYWRvIHF1ZSBhIGxhIGRlcmVjaGEgbGEgZGlmZXJlbmNpYSBlbnRyZSBsYXMgaW3DoWdlbmVzIGVzIHBvY2EgeSB0ZW5kZXLDrWEgYSBjb250YW1pbmFyIGVsIG1vZGVsby4NCg0KYGBge3IsIGZpZy5kaW0gPSBjKDE4LCA2KSwgZHBpPTYwMCwgbWVzc2FnZT1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCnBuZygiQm94cGxvdF9nZW5lcmFsLnBuZyIsd2lkdGg9MTIwMDAsIGhlaWdodD01MDAwLHJlcz02MDApDQpnZ3Bsb3QoZGF0b3MsIGFlcyh4PXJhbmdlLCB5PXZhbHVlLCBjb2xvcj1ncnVwbykpICsNCiAgZ2VvbV9ib3hwbG90KCkrIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIHZqdXN0ID0gMC41LCBoanVzdD0xLHNpemU9OCkpDQpkZXYub2ZmKCkNCmBgYA0KDQpDb24gZWwgZmluIGRlIHJlZHVjaXIgbGEgZGltZW5zaW9uYWxpZGFkIHNlIHV0aWxpemEgZWwgQW7DoWxpc2lzIGRlIENvbXBvbmVudGVzIFByaW5jaXBhbGVzIChBQ1ApLCBwYXJhIGFzw60gcmVwcmVzZW50YXIgbGEgaW5mb3JtYWNpw7NuIG9yaWdpbmFsIHBlcm8gZW4gdW4gZXNwYWNpbyBkZSBkaW1lbnNpw7NuIG1lbm9yKGxpbWl0YW5kbyBsYSBww6lyZGlkYSBkZSBpbmZvcm1hY2nDs24pLg0KDQpEZSBlc3RhIGZvcm1hLCBzZSBlbGFib3JhIGVsIG1vZGVsbyBtZWRpYW50ZSByZWdyZXNpw7NuIGRlIGNvbXBvbmVudGVzIHByaW5jaXBhbGVzLCBkb25kZSBlbCBncnVwbyByZXByZXNlbnRhIGxhIHZhcmlhYmxlIHByZWRpY3RvcmEgeSBsYSBkYXRhIGEgbGEgdmFyaWFibGUgZGUgcmVzcHVlc3RhIGEgbW9kZWxhciwgYXPDrSBtaXNtbywgc2UgY3Jlw7MgdW5hIHZhcmlhYmxlIGRvbmRlIGVsIG7Dum1lcm8gMSByZXByZXNlbnRhIGxhcyBpbcOhZ2VuZXMgY29uIGdhZmFzIHkgMCBsYXMgaW3DoWdlbmVzIHNpbiBnYWZhcy4gSWd1YWxtZW50ZSB0b2RvIGVzdGUgcHJvY2VkaW1pZW50byBzZSBndWFyZMOzIGVuIHVuIGFyY2hpdm8gY3N2Lg0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFICwgZWNobz1UfQ0KDQojRWplY3V0YW5kbyBlbCBBQ1ANCg0KDQptdXRhdGUodHJhaW4sZ3J1cG89aWZlbHNlKGdydXBvPT0iQ29uX0dhZmFzIiwxLDApKS0+dHJhaW4yDQoNCiNjcmVhY2nDs24gZGVsIG1vZGVsbw0KcGNyKGdydXBvfi4sZGF0YT10cmFpbjJbLGMoMTo2MCwyMDEpXSxzY2FsZT1UUlVFLHZhbGlkYXRpb249IkNWIiktPm1vZGVsDQoNCiNQcnVlYmEgcmVkdW5kYW50ZSANCnByZWRpY3QobW9kZWwsdHJhaW4yWywtMjAxXSxuY29tcD0xMCktPnByb2INCg0KI1NpIHNlIGRlc2VhLCBzZSBwdWVkZW4ganVudGFyIGxvcyBkYXRvcyBjcmVhZG9zIGNvbiBsb3MgdmFsb3JlcyByZWFsZXMgKGNvbHVtbmEgZ3J1cG8gZGUgbGEgdGFibGEgdHJhaW4pDQogY2JpbmQocHJvYix0cmFpbjJbLDIwMV0pLT5wcm9iDQoNCiAjd3JpdGUuY3N2KHByb2IsImRhdG9zX2dlbmVyYWRvcy5jc3YiKQ0KIA0KYGBgDQoNCg0KRGVzcHXDqXMgZGUgY3JlYXIgZWwgbW9kZWxvLCBwZXJjaWJpbW9zIGFsZ3Vub3MgY2Fzb3MgZG9uZGUgbGEgcHJlZGljY2nDs24gZXJhIG11eSBiYWphIGVuIGZvdG9zIGNvbiBnYWZhcyAoZGViaWRvIGEgbWFyY29zIGRlIGxhcyBnYWZhcyBtdXkgZmlub3MsIHBsYXRlYWRvcyBvIHRyYW5zcGFyZW50ZXMpIG8gbXV5IGFsdGEgZW4gZm90b3Mgc2luIGdhZmFzIChkZWJpZG8gYSBwZXJzb25hcyBjb24gdW5hIHRleiBtw6FzIG9zY3VyYSksIGNvbW8gc2UgcHVlZGUgYXByZWNpYXIgZW4gZWwgc2lndWllbnRlIGdyw6FmaWNvLg0KDQoNCg0KYGBge3IgIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UgLCBlY2hvPVQsZmlnLndpZHRoPSAyLGZpZy5oZWlnaHQ9IDIsZmlnLmFsaWduPSdjZW50ZXInfQ0KDQpzZXR3ZCgiLi9TaW4gZ2FmYXNfdHJhaW5pbmciKQ0KDQoNCg0KI0FicmlyIGxhcyBpbcOhZ2VuZXMgZGVsIG1vZGVsbw0KbGlzdC5maWxlcygpLT5saXN0YQ0KDQppPC0xMjcNCmltYWdlbmFtZSA8LWxpc3RhW2ldDQoNCmltZyA9IHJlYWRJbWFnZShpbWFnZW5hbWUpDQoNCmltZ19nPC1jaGFubmVsKGltZywiZ3JheSIpDQoNCmltZ19jcm9wID0gaW1nX2dbNDQwOjU5MCw0NDA6NTgwXQ0KZGlzcGxheShpbWdfZyxtZXRob2Q9J3Jhc3RlcicpDQoNCnNldHdkKCIuLi9Db24gZ2FmYXNfdHJhaW5pbmciKQ0KDQoNCiNBYnJpciBsYXMgaW3DoWdlbmVzIGRlbCBtb2RlbG8NCmxpc3QuZmlsZXMoKS0+bGlzdGENCg0KaTwtMjE5DQppbWFnZW5hbWUgPC1saXN0YVtpXQ0KDQppbWcgPSByZWFkSW1hZ2UoaW1hZ2VuYW1lKQ0KDQppbWdfZzwtY2hhbm5lbChpbWcsImdyYXkiKQ0KDQppbWdfY3JvcCA9IGltZ19nWzQ0MDo1OTAsNDQwOjU4MF0NCmRpc3BsYXkoaW1nX2csbWV0aG9kPSdyYXN0ZXInKQ0KDQoNCg0KYGBgDQoNCkEgY29udGludWFjacOzbiwgc2UgcHJlc2VudGEgdW4gZGlhZ3JhbWEgZGUgdmlvbMOtbiwgZW4gZG9uZGUgc2UgcmVwcmVzZW50YSBsYSBkZW5zaWRhZCBkZSBtdWVzdHJhcyBxdWUgY2xhc2lmaWNhIGVsIG1vZGVsbyBkZSBlbnRyZW5hbWllbnRvIHBhcmEgbG9zIGdydXBvcyAwIChjb24gZ2FmYXMpIHkgMSAoc2luIGdhZmFzKS4gRXMgZsOhY2lsIGRlc3RhY2FyIHF1ZSBsYSBncmFuIG1heW9yw61hIGRlIGxhcyBvYnNlcnZhY2lvbmVzIHNpbiBnYWZhcyBzZSBlbmN1ZW50cmFuIHBvciBkZWJham8gZGUgMC41LCBwcm9wb3JjaW9uw6FuZG9sZSBsYSBmb3JtYSAiYW5jaGEiIGNhcmFjdGVyw61zdGljYSB5IGVsIGdydXBvIGNvbiBnYWZhcyB0aWVuZSB1bmEgZm9ybWEgbcOhcyBhbGFyZ2FkYSB5IGFsdGEsIG9jdXBhbmRvIHVuIHJhbmdvIGRlIHZhbG9yZXMgbWF5b3IuDQoNCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSAsIGVjaG89VCxmaWcud2lkdGg9IDMsZmlnLmhlaWdodD0gMyxmaWcuYWxpZ249J2NlbnRlcid9DQpzZXR3ZCgiLi4vVGVyY2VyYSBlbnRyZWdhIikNCnJlYWQuY3N2KCJkYXRvc19nZW5lcmFkb3MuY3N2IiktPmRhdG9zDQoNCg0KDQojRW4gZWwgc2lndWllbnRlIGdyw6FmaWNvLCBzZSBtdWVzdHJhIGNvbW8gMCwgYXF1ZWxsYXMgaW3DoWdlbmVzIHF1ZSBubyBkZWJlcsOtYW4gdGVuZXIgZ2FmYXMgeSBjb21vIDEsIGFxdWVsbGFzIHF1ZSBkZWJlcsOtYW4gbW9zdHJhciBnYWZhcy4gDQpnZ3Bsb3QoZGF0b3MsYWVzKHg9YXMuZmFjdG9yKFguMSkseT1wcm9iKSkrZ2VvbV92aW9saW4oKQ0KYGBgDQoNClVuYSBtYW5lcmEgZGUgY29tcHJvYmFyIGxhcyBkaWZlcmVuY2lhcyBlbnRyZSBsb3MgZ3J1cG9zLCBlcyByZWFsaXphciB1bmEgcHJ1ZWJhIHQgcGFyYSBsYSBkaWZlcmVuY2lhIGRlIG1lZGlhcy4NCg0KUHJ1ZWJhIGRlIGhpcMOzdGVzaXM6DQoNCiRIXzAkPSRcbXVfMS1cbXVfMj0wJCAgdnMgJEhfMSQ9JFxtdV8xIC0gXG11XzIgXG5lcSAwJA0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSAsIGVjaG89VH0NCiNQYXJhIGNvbXByb2JhciBsb3MgZGF0b3MgeSBsYXMgZGlmZXJlbmNpYXMgc2lnbmlmaWNhdGl2YXMsIHBvZGVtb3MgZWplY3V0YXIgdW4gYW7DoWxpc2lzIGRlIGNvbXBhcmFjacOzbiBkZSBwcm9tZWRpb3MgKHBydWViYSB0KQ0KdC50ZXN0KGRhdG9zW3doaWNoKGRhdG9zJFguMT09MSksMl0sZGF0b3Nbd2hpY2goZGF0b3MkWC4xPT0wKSwyXSkNCg0KYGBgDQoNCkRlIGFjdWVyZG8gYWwgcmVzdWx0YWRvLCBudWVzdHJhIHNvc3BlY2hhIGVzIGNpZXJ0YSBkZSBxdWUgbGFzIG1lZGlhcyBkZSBsb3MgdmFsb3JlcyBwYXJhIGFtYm9zIGdydXBvcyBzb24gZGlmZXJlbnRlcyB5IGVzdG8gc2UgY29udHJhc3RhIGNvbiBlbCBwLXZhbHVlIGRlICQyLjJlXnstMTZ9JCBhcnJvamFkbyBwb3IgbGEgcHJ1ZWJhLCBsbyBjdWFsIG5vcyBwZXJtaXRlIHJlY2hhemFyIGxhIGhpcMOzdGVzaXMgbnVsYS4NCg0KDQojIyA0LiBWYWxpZGFjacOzbg0KDQpEZWwgbW9kZWxvIHByZWRpY3Rpdm8gdHJhYmFqYWRvIGhhc3RhIGFob3JhLCBzaSBiaWVuIGVzIGNpZXJ0byBxdWUgc2UgY2FyYWN0ZXJpemEgcG9yIHRlbmVyIHVuYSBsw7NnaWNhIGF2YW56YWRhLCByZXF1aWVyZSBkZSB0w6lybWlub3MgZXNwZWPDrWZpY29zLiBFbiBlc3RlIGNhc28sIGVsIGVudHJlbmFtaWVudG8gc2UgcmVhbGl6w7MgY29uIGltw6FnZW5lcyBmcm9udGFsZXMgKHN0cmFpZ2h0KSwgZW50b25jZXMgbGEgdmFsaWRhY2nDs24gZGViZSBoYWNlcnNlIGNvbiBlbCBtaXNtbyB0aXBvIGRlIGltw6FnZW5lcy4gRXMgZGVjaXIsIGVsIG1vZGVsbyBwdWVkZSBwcmVkZWNpciBjb24gdW4gY2llcnRvIGdyYWRvIGRlIGVycm9yLCBzaSBzZSBsZSBldmFsw7phIGNvbiBpbcOhZ2VuZXMgc2ltaWxhcmVzIGEgbGFzIHF1ZSBzZSB1c2Fyb24gZW4gZW50cmVuYW1pZW50by4NCg0KRGVsIGNvbmp1bnRvIGRlIHZhbGlkYWNpw7NuIG9idGVuaWRvIGRlIFtVQ0kgTWFjaGluZSBMZWFybmluZyBSZXBvc2l0b3J5XShodHRwczovL2FyY2hpdmUuaWNzLnVjaS5lZHUvbWwvZGF0YXNldHMvQ01VK0ZhY2UrSW1hZ2VzKSB0ZW5lbW9zIDQyNCBmb3RvcyBxdWUgY3VtcGxlbiBjb24gZXN0YSBjYXJhY3RlcsOtc3RpY2EsIHNlIGFuYWxpemFyw6FuIHVzYW5kbyBsYSBsaWJyZXLDrWEgW3BpeG1hcF0oaHR0cHM6Ly93d3cucmRvY3VtZW50YXRpb24ub3JnL3BhY2thZ2VzL3BpeG1hcC92ZXJzaW9ucy8wLjQtMTIvdG9waWNzL3BubSkNCg0KDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIHJlc3VsdHMgPSAnaGlkZScsIGVjaG89Rn0NCg0KDQoNCiMgDQojIGxpc3QuZmlsZXMoKS0+bGlzdGENCiMgDQojICNDb252ZXJ0aXIgdG9kbyBwYXJhIHBuZw0KIyBmb3IgKGkgaW4gMTpsZW5ndGgobGlzdGEpKXsNCiMgcmVhZC5wbm0obGlzdGFbaV0pLT5hDQojIHBuZyhwYXN0ZTAobGlzdGFbaV0sIi5wbmciKSkNCiMgcGxvdChhKQ0KIyBkZXYub2ZmKCkNCiMgfQ0KDQpgYGANCg0KDQpMYXMgaW3DoWdlbmVzIHNlbGVjY2lvbmFkYXMgc2UgY29udmlydGllcm9uIHRvZGFzIGRlIGZvcm1hdG8gcGdtIGEgcG5nLiBVbmEgaW1hZ2VuIGRlIGVzdGUgY29uanVudG8gZXM6DQoNCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSAsIGVjaG89VCxmaWcud2lkdGg9IDIsZmlnLmhlaWdodD0gMixmaWcuYWxpZ249J2NlbnRlcid9DQoNCg0Kc2V0d2QoIi4vVGVzdF9QTkciKQ0KDQoNCiNBYnJpciBsYXMgaW3DoWdlbmVzIGRlbCBtb2RlbG8NCmxpc3QuZmlsZXMoKS0+bGlzdGENCg0KaTwtNA0KaW1hZ2VuYW1lIDwtbGlzdGFbaV0NCg0KaW1nID0gcmVhZEltYWdlKGltYWdlbmFtZSkNCg0KaW1nX2c8LWNoYW5uZWwoaW1nLCJncmF5IikNCg0KDQpkaXNwbGF5KGltZ19nLG1ldGhvZD0ncmFzdGVyJykNCg0KYGBgDQoNCg0KDQpVbmEgdmV6IHF1ZSBzZSB0aWVuZW4gdG9kYXMgbGFzIGltw6FnZW5lcyBlbiBmb3JtYXRvIHBuZywgcmVwZXRpcmVtb3MgbG9zIHBhc29zIGluaWNpYWxlcyBwYXJhIGNhbGN1bGFyIGxvcyBoaXN0b2dyYW1hcyBkZWwgZ3J1cG8gZGUgdmFsaWRhY2nDs24uDQoNCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgcmVzdWx0cyA9ICdoaWRlJywgZWNobz1GfQ0KIyANCiMgDQojICNBYnJpciBsYXMgaW3DoWdlbmVzIGRlbCBtb2RlbG8NCiMgbGlzdC5maWxlcygpLT5saXN0YQ0KIyANCiMgI1ZhbW9zIGEgY3JlYXIgdW5hIGNvbHVtbmEgY29uIGxvcyBjdWFydGlsZXMgbmVjZXNhcmlvcyANCiMgDQojIHBhc3RlMChzZXEoMCwwLjk5NSwwLjAwNSksIi0iLHNlcSgwLjAwNSwxLDAuMDA1KSktPmN1YXJ0aWxlcw0KIyANCiMgZGF0YS5mcmFtZSh2YWx1ZXM9Y3VhcnRpbGVzKS0+dGVzdA0KIyANCiMgZm9yIChpIGluIDE6bGVuZ3RoKGxpc3RhKSl7DQojIGltYWdlbmFtZSA8LWxpc3RhW2ldDQojIA0KIyBpbWcgPSByZWFkSW1hZ2UoaW1hZ2VuYW1lKQ0KIyANCiMgaW1nX2c8LWNoYW5uZWwoaW1nLCJncmF5IikNCiMgDQojIGltZ19jcm9wID0gaW1nX2dbMjQ1OjI2NSwyMDA6MjUwXQ0KIyBoaXN0KGltZ19jcm9wLGJyZWFrcz1zZXEoMCwxLDAuMDA1KSktPmRhZG9zDQojIGRhdGEuZnJhbWUodGVzdCxkYWRvcyRjb3VudHMpLT50ZXN0DQojIA0KIyB9DQojIGNvbG5hbWVzKHRlc3QpPC1jKCJ2YWx1ZXMiLGxpc3RhKQ0KDQojR3VhcmRhbmRvIGVzdGUgcmVzdWx0YWRvDQoNCg0KDQoNCmBgYA0KDQpGaW5hbG1lbnRlIHNlIHBydWViYSB5IHNlIHJldmlzYW4gbG9zIHJlc3VsdGFkb3MgZGVsIG1vZGVsbyBlbGFib3JhZG8uDQoNCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgcmVzdWx0cyA9ICdoaWRlJywgZWNobz1GIH0NCg0KDQoNCiMgDQojIHJlYWQuY3N2KCJ0cmFpbl8xLmNzdiIpLT50cmFpbg0KIyANCiMgDQojIA0KIyB0cmFpblssMV0tPnJvd25hbWVzKHRyYWluKQ0KIyB0cmFpblssLTFdLT50cmFpbg0KIyANCiMgbXV0YXRlKHRyYWluLGdydXBvPWlmZWxzZShncnVwbz09IkNvbl9HYWZhcyIsMSwwKSktPnRyYWluMg0KIyANCiMgDQojIA0KIyAjY3JlYWNpw7NuIGRlbCBtb2RlbG8NCiMgcGNyKGdydXBvfi4sZGF0YT10cmFpbjJbLGMoMTo2MCwyMDEpXSxzY2FsZT1UUlVFLHZhbGlkYXRpb249IkNWIiktPm1vZGVsDQojIA0KIyAjUHJ1ZWJhIGNvbiBncnVwbyB0ZXN0DQojIHJlYWQuY3N2KCJ0ZXN0LmNzdiIpLT50ZXN0DQojIHRlc3RbLDJdLT5yb3duYW1lcyh0ZXN0KSANCiMgdGVzdFssLWMoMToyKV0tPnRlc3QNCiMgdCh0ZXN0KS0+dGVzdA0KIyANCiMgcHJlZGljdChtb2RlbCxuZXdkYXRhPXRlc3RbLDE6NjBdLG5jb21wPTEwKS0+cHJvYg0KDQojU2kgc2UgZGVzZWEsIHNlIHB1ZWRlbiBqdW50YXIgbG9zIGRhdG9zIGNyZWFkb3MgY29uIGxvcyB2YWxvcmVzIHJlYWxlcyAoY29sdW1uYSBncnVwbyBkZSBsYSB0YWJsYSB0cmFpbikNCiAjIHdyaXRlLmNzdihwcm9iLCJyZXN1bHRhZG9zLmNzdiIpDQogDQoNCmBgYA0KUGFyYSB2ZXIgZWwgcG9yY2VudGFqZSBkZSBhY2llcnRvcywgc2UgYW5hbGl6YW4gbG9zIHZhbG9yZXMgcmVhbGVzIHZzIHByZWRpY2hvcyBwb3IgZWwgbW9kZWxvLCBlc3RvIHNlIGhhY2UgZW4gZWwgYXJjaGl2byByZXN1bHRhZG9zLmNzdiB5IHJlc3VsdGFkbyAoMSkuY3N2IGdlbmVyYWRvcyBhbnRlcmlvbWVudGUuIEVuIGVsIHNpZ3VpZW50ZSBncsOhZmljbywgc2Ugb2JzZXJ2YW4gdmFsb3JlcyBkZSBlc3RpbWFjacOzbiBtw6FzIGFsdG9zIGVuIGxhcyBmb3RvcyBjb24gZ2FmYXMsIGNvbW8gZXJhIGRlIGVzcGVyYXJzZSBlbiBjb25jb3JkYW5jaWEgcG9yIGVsIHJlc3VsdGFkbyB2aXN0byBhbnRlcmlvbWVudGUgZW4gZWwgZ3LDoWZpY28gZGUgdmlvbMOtbi4gU2Ugb2JzZXJ2YW4gYWxndW5vcyB2YWxvcmVzIGF0w61waWNvcy4gDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UgLCBlY2hvPVQsZmlnLndpZHRoPSAzLGZpZy5oZWlnaHQ9IDMsZmlnLmFsaWduPSdjZW50ZXInfQ0KDQoNCnJlYWQuY3N2KCJyZXN1bHRhZG9zLmNzdiIpLT5kYXRvcw0KDQojZGF0b3MgPC0gcmVhZC5jc3YoIkM6L1VzZXJzL0xlbm92by9Eb3dubG9hZHMvcmVzdWx0YWRvcy5jc3YiKQ0KDQpyYmluZChtdXRhdGUoZGF0b3NbZGF0b3MkWCAlbGlrZSUgIm9wZW4iLF0sZ3J1cG89IlNpbl9nYWZhcyIpLA0KICAgICAgbXV0YXRlKGRhdG9zW2RhdG9zJFggJWxpa2UlICJzdW5nbGEiLF0sZ3J1cG89IkNvbl9nYWZhcyIpKS0+ZGF0b3MyDQoNCg0KZ2dwbG90KGRhdG9zMixhZXMoeD1ncnVwbyx5PWdydXBvLjEwLmNvbXBzKSkrZ2VvbV9ib3hwbG90KCkrc2NhbGVfeV9sb2cxMCgpDQpgYGANCkNvbiBsYSBpbnRlbmNpw7NuIGRlIHZpc3VhbGl6YXIgZWwgZGVzZW1wZcOxbyBkZSBlc3RlIGFsZ29yaXRtbyhxdWUgc2UgZW1wbGVhIGVuIGFwcmVuZGl6YWplIHN1cGVydmlzYWRvKSBwcm9jZWRlbW9zIGEgcmVhbGl6YXIgdW5hIG1hdHJpeiBkZSBjb25mdXNpw7NuLCBsYSBjdWFsIGVzIHVuYSBoZXJyYW1pZW50YSBtdXkgw7p0aWwgcGFyYSB2YWxvcmFyIHF1w6kgdGFuIGJ1ZW5vIGVzIHVuIG1vZGVsbyBkZSBjbGFzaWZpY2FjacOzbiBiYXNhZG8gZW4gYXByZW5kaXphamUgYXV0b23DoXRpY28uIEVuIHBhcnRpY3VsYXIsIHNpcnZlIHBhcmEgbW9zdHJhciBkZSBmb3JtYSBleHBsw61jaXRhIGN1w6FuZG8gdW5hIGNsYXNlIGVzIGNvbmZ1bmRpZGEgY29uIG90cmEsIGxvIGN1YWwgbm9zIHBlcm1pdGUgdHJhYmFqYXIgZGUgZm9ybWEgc2VwYXJhZGEgY29uIGRpc3RpbnRvcyB0aXBvcyBkZSBlcnJvci4NCg0KYGBge3IgIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UgLCBlY2hvPVQsZmlnLndpZHRoPSAzLGZpZy5oZWlnaHQ9IDMsZmlnLmFsaWduPSdjZW50ZXInfQ0KI2RhdG9zbXcgPC0gcmVhZF9leGNlbCgiQzovVXNlcnMvTGVub3ZvL0Rvd25sb2Fkcy9yZXN1bHRhZG9zICgxKS54bHN4IikNCg0KDQoNCmRhdG9zbXcgPC0gcmVhZHhsOjpyZWFkX2V4Y2VsKCJyZXN1bHRhZG9zKDEpLnhsc3giKSAgIA0KDQpkYXRvc213JFRlw7NyaWNvIDwtIGFzLmZhY3RvcihkYXRvc213JFRlw7NyaWNvKQ0KZGF0b3NtdyRFc3RpbWFkbyA8LSBhcy5mYWN0b3IoZGF0b3NtdyRFc3RpbWFkbykNCg0KY20gPC0gY29uZl9tYXQoZGF0b3NtdywgRXN0aW1hZG8sIFRlw7NyaWNvKQ0KDQphdXRvcGxvdChjbSwgdHlwZSA9ICJoZWF0bWFwIikgKw0KICBzY2FsZV9maWxsX2dyYWRpZW50KGxvdyA9ICJwaW5rIiwgaGlnaCA9ICJjeWFuIikNCg0KDQpgYGANCkxvcyB2YWxvcmVzIGRlIGxhIGRpYWdvbmFsIHByaW5jaXBhbCBhPTIwOSg5OC41OCUpIHkgZD0xNzMoODEuNiUpIGNvcnJlc3BvbmRlbiB0YW50byBhIGxvcyB2YWxvcmVzIGVzdGltYWRvcyBkZSBmb3JtYSBjb3JyZWN0YSBwb3IgZWwgbW9kZWxvLCBjb21vIGEgbG9zIHZlcmRhZGVyb3MgcG9zaXRpdm9zIGQgPSAxNzMoaW3DoWdlbmVzIGNvbiBsZW50ZXMpLCB5IGEgbG9zIHZlcmRhZGVyb3MgbmVnYXRpdm9zIGE9IDIwOShpbcOhZ2VuZXMgc2luIGxlbnRlcykuDQoNCkxhIG90cmEgZGlhZ29uYWwsIHBvciB0YW50bywgcmVwcmVzZW50YSBsb3MgY2Fzb3MgZW4gbG9zIHF1ZSBlbCBtb2RlbG8gc2UgaGEgZXF1aXZvY2FkbyAoYz0zOSgxOCw0JSkgZmFsc29zIG5lZ2F0aXZvcywgYj0zKDEuNDIlKSBmYWxzb3MgcG9zaXRpdm9zKS4NCg0KTHVlZ28gZGUgaGFiZXIgYW5hbGl6YWRvIGxhIG1hdHJpeCBkZSBjb25mdXNpw7NuLCBzZSBwcm9jZWRlIGEgY2FsY3VsYXIgbGEgZXhhY3RpdHVkLCBsYSBwcmVjaXNpw7NuLCBsYSBzZW5zaWJpbGlkYWQgeSBsYSBlc3BlY2lmaWNpZGFkLg0KDQotIEV4YWN0aXR1ZA0KTGEgZXhhY3RpdHVkIChvIMKrYWNjdXJhY3nCqykgcmVwcmVzZW50YSBlbCBwb3JjZW50YWplIGRlIHByZWRpY2Npb25lcyBjb3JyZWN0YXMgZnJlbnRlIGFsIHRvdGFsLiBQb3IgdGFudG8sIGVzIGVsIGNvY2llbnRlIGVudHJlIGxvcyBjYXNvcyBiaWVuIGNsYXNpZmljYWRvcyBwb3IgZWwgbW9kZWxvICh2ZXJkYWRlcm9zIHBvc2l0aXZvcyB5IHZlcmRhZGVyb3MgbmVnYXRpdm9zLCBlcyBkZWNpciwgbG9zIHZhbG9yZXMgZW4gbGEgZGlhZ29uYWwgZGUgbGEgbWF0cml6IGRlIGNvbmZ1c2nDs24pLCB5IGxhIHN1bWEgZGUgdG9kb3MgbG9zIGNhc29zLg0KDQooMjA5KzE3MykvKDIwOSsxNzMrMzkrMyk9MzgyLzQyND0gMC45MDA5NDM0KjEwMCUgPSA5MCUNCg0KRWwgdmFsb3Igb2J0ZW5pZG8gcGFyYSBsYSBleGFjdGl0dWQgZW4gZXN0ZSBtb2RlbG8gZXMgZGVsIDkwJS4NCg0KLSBQcmVjaXNpw7NuDQpMYSBwcmVjaXNpw7NuLCAob+KAnHByZWNpc2lvbuKAnSkgc2UgcmVmaWVyZSBhIGxvIGNlcmNhIHF1ZSBlc3TDoSBlbCByZXN1bHRhZG8gZGUgdW5hIHByZWRpY2Npw7NuIGRlbCB2YWxvciB2ZXJkYWRlcm8uIFBvciB0YW50bywgZXMgZWwgY29jaWVudGUgZW50cmUgbG9zIGNhc29zIHBvc2l0aXZvcyBiaWVuIGNsYXNpZmljYWRvcyBwb3IgZWwgbW9kZWxvIHkgZWwgdG90YWwgZGUgcHJlZGljY2lvbmVzIHBvc2l0aXZhcy4NCg0KKDE3MykvKDE3MyszKT0gMC45ODI5NTQ1KjEwMCUgPSA5OC4zJQ0KDQpFbCB2YWxvciBvYnRlbmlkbyBwYXJhIGVzdGUgbW9kZWxvIGVzIGRlIHVuIDk4LjMlLiBQb3IgdGFudG8sIG51ZXN0cm8gbW9kZWxvIGVzIG3DoXMgcHJlY2lzbyBxdWUgZXhhY3RvLg0KDQotIFNlbnNpYmlsaWRhZA0KTGEgc2Vuc2liaWxpZGFkIChvIHJlY2FsbCkgcmVwcmVzZW50YSBsYSB0YXNhIGRlIHZlcmRhZGVyb3MgcG9zaXRpdm9zIChUcnVlIFBvc2l0aXZlIFJhdGUpIMOzIFRQLiBFcyBsYSBwcm9wb3JjacOzbiBlbnRyZSBsb3MgY2Fzb3MgcG9zaXRpdm9zIGJpZW4gY2xhc2lmaWNhZG9zIHBvciBlbCBtb2RlbG8sIHJlc3BlY3RvIGFsIHRvdGFsIGRlIHBvc2l0aXZvcywgZWwgY3VhbCBlcywgbGEgaGFiaWxpZGFkIGRlbCBtb2RlbG8gZGUgZGVjdGV0YXIgbG9zIGNhc29zIHJlbGV2YW50ZXMuDQoNCjE3My8oMzkrMTczKSA9IDAuODE2MDM3NyoxMDAlID0gODEuNiUNCg0KVW4gODEuNiUgZXMgY2xhcmFtZW50ZSB1biB2YWxvciBtdXkgYnVlbm8gcGFyYSB1bmEgbcOpdHJpY2EuIFBvZGVtb3MgZGVjaXIgcXVlIG51ZXN0cm8gYWxnb3JpdG1vIGRlIGNsYXNpZmljYWNpw7NuIGVzIHNlbnNpYmxlLCBlcyBkZWNpciwgbm8gc2UgbGUgZXNjYXBhbiBtdWNob3MgcG9zaXRpdm9zLg0KDQotIEVzcGVjaWZpY2lkYWQNCkxhIGVzcGVjaWZpY2lkYWQsIHBvciBzdSBwYXJ0ZSwgZXMgbGEgdGFzYSBkZSB2ZXJkYWRlcm9zIG5lZ2F0aXZvcywgKOKAnHRydWUgbmVnYXRpdmUgcmF0ZeKAnSlvIFROLiBFcyBsYSBwcm9wb3JjacOzbiBlbnRyZSBsb3MgY2Fzb3MgbmVnYXRpdm9zIGJpZW4gY2xhc2lmaWNhZG9zIHBvciBlbCBtb2RlbG8sIHJlc3BlY3RvIGFsIHRvdGFsIGRlIG5lZ2F0aXZvcy4NCg0KMjA5LygyMDkrMykgPSAwLjk4NTg0OTEqMTAwJSA9IDk4LjYlDQoNCkVuIGVzdGUgY2FzbywgbGEgZXNwZWNpZmljaWRhZCB0aWVuZSB1biB2YWxvciBtdXkgYnVlbm8uIEVzdG8gc2lnbmlmaWNhIHF1ZSBzdSBjYXBhY2lkYWQgZGUgZGlzY3JpbWluYXIgbG9zIGNhc29zIG5lZ2F0aXZvcyBlcyBtdXkgYnVlbmEuIEVzIGRlY2lyLCBlcyBkaWbDrWNpbCBvYnRlbmVyIGZhbHNvcyBwb3NpdGl2b3MuDQoNCi0gQ29uY2x1c2nDs24NCkNvbW8gc2UgcHVkbyBvYnNlcnZhciwgcGFyYSBjYWRhIG3DqXRyaWNhIHNlIG9idHV2aWVyb24gdmFsb3JlcyBhbHRvcywgbG8gY3VhbCBpbmRpY2EgcXVlIGVsIG1vZGVsbyB0aWVuZSBhbHRhIHByZWNpc2nDs24geSBleGFjdGl0dWQsIHkgYWRlbcOhcyBkZSBlbGxvIHRpZW5lIHVuYSBhbHRhIHNlbnNpYmlsaWRhZCh0aWVuZSBhbHRvIHBvcmNlbnRhamUgZW4gZGV0ZWN0YXIgY2Fzb3MgcG9zaXRpdm9zKSB5IHVuYSBhbHRhIGVzcGVjaWZpY2lkYWQodGllbmUgYWx0byBwb3JjZW50YWplIGVuIGRldGVjdGFyIGNhc29zIG5lZ2F0aXZvcykuDQoNCk51ZXZhbWVudGUsIGNvbW8gc2UgaGl6byBwYXJhIGVsIGNvbmp1bnRvIGRlIGVudHJlbmFtaWVudG8sIHNlIHJlYWxpemEgdW5hIHBydWViYSB0IHBhcmEgbGEgZGlmZXJlbmNpYSBkZSBtZWRpYXMgZW50cmUgbG9zIGdydXBvcy4NCg0KUHJ1ZWJhIGRlIGhpcMOzdGVzaXM6DQoNCiRIXzAkPSRcbXVfMS1cbXVfMj0wJCAgdnMgJEhfMSQ9JFxtdV8xIC0gXG11XzIgXG5lcSAwJA0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFICwgZWNobz1UfQ0KI1BhcmEgY29tcHJvYmFyIGxvcyBkYXRvcyB5IGxhcyBkaWZlcmVuY2lhcyBzaWduaWZpY2F0aXZhcywgcG9kZW1vcyBlamVjdXRhciB1biBhbsOhbGlzaXMgZGUgY29tcGFyYWNpw7NuIGRlIG1lZGlhcyAocHJ1ZWJhIHQpDQp0LnRlc3QoZGF0b3MyW3doaWNoKGRhdG9zMiRncnVwbz09IkNvbl9nYWZhcyIpLDJdLGRhdG9zW3doaWNoKGRhdG9zMiRncnVwbz09IlNpbl9nYWZhcyIpLDJdKQ0KDQoNCmBgYA0KRGUgYWN1ZXJkbyBhbCByZXN1bHRhZG8sIG51ZXN0cmEgc29zcGVjaGEgZXMgY2llcnRhIGRlIHF1ZSBsYXMgbWVkaWFzIGRlIGxvcyB2YWxvcmVzIHBhcmEgYW1ib3MgZ3J1cG9zIHNvbiBkaWZlcmVudGVzIHkgZXN0byBzZSBjb250cmFzdGEgY29uIGVsIHAtdmFsdWUgZGUgMC4wMDAxNjU1IGFycm9qYWRvIHBvciBsYSBwcnVlYmEsIGxvIGN1YWwgbm9zIHBlcm1pdGUgcmVjaGF6YXIgbGEgaGlww7N0ZXNpcyBudWxhLg0KDQoNCg0KDQoNCiMjIDUuIEludGVycm9nYW50ZXMgDQoNCg0KIyMjIDUuMS4gwr9RdcOpIGFmZWN0YSBsYSBjYXBhY2lkYWQgZGVsIG1vZGVsbyBlbiBlbCBjb25qdW50byBkZSB2YWxpZGFjacOzbj8NCg0KLSBMYSB1YmljYWNpw7NuIGRlIGxvcyByb3N0cm9zLiBQcmVmZXJpYmxlbWVudGUgc2UgZGVzZWEgcXVlIHRvZGFzIGxhcyBpbcOhZ2VuZXMgc2VhbiBjZW50cmFkYXMgZW4gZG9uZGUgc2UgZGVub3RlIGVsIHNlcHRvIG5hc2FsLiBJbcOhZ2VuZXMgZGUgcGVyZmlsIG8gY29uIGVsIG1lbnTDs24gbGV2YW50YWRvIG5vIHNvbiBhcHRhcyBwYXJhIGVzdGUgbW9kZWxvLg0KDQotIExhIGVzdGFkYXJpemFjacOzbiBkZSBsYXMgaW3DoWdlbmVzLiBFeGlzdGVuIGZvdG9zIGV4dHJhw7FhcyBlbiBlbCBjb25qdW50byBkZSB2YWxpZGFjacOzbiwgaW3DoWdlbmVzIGRvYmxlcyDDsyBjb24gcm9zdHJvcyBhIG1lZGlhcywgZXN0byBnZW5lcmEgcnVpZG8geSBlbnRvcnBlY2UgbGEgY2FwYWNpZGFkIGRlIGNsYXNpZmljYXIgY29ycmVjdGFtZW50ZS4NCg0KLSBMYSB0ZXogZGUgbGFzIHBlcnNvbmFzIGRlIGxhcyBmb3RvZ3JhZsOtYXMuIFB1ZWRlIGxsZWdhciBhIGFsdGVyYXIgZWwgbW9kZWxvIGRlYmlkbyBhICBsYSBjYW50aWRhZCBkZSBww614ZWxlcyAoWWEgc2VhbiBtYXMgb3NjdXJvcyBvIG3DoXMgY2xhcm9zKSBkZWJpZG8gYSBxdWUgbm8gcG9kcsOtYSBkaXN0aW5ndWlyIHNpIGxhIHBlcnNvbmEgbGxldmEgZ2FmYXMgbyBuby4NCg0KLSBFcnJvcmVzIGh1bWFub3MuIEVsIG1hbCBwcm9jZWRpbWllbnRvIGEgbGEgaG9yYSBkZSBlc2NvZ2VyIHVuYSBpbWFnZW4geSBhZ3JlZ2FybGEgZW4gbGEgY2xhc2lmaWNhY2nDs24gaW5jb3JyZWN0YS4NCg0KLSBGb3JtYXRvIGRlIGltw6FnZW5lcy4gTGEgdHJhbnNmb3JtYWNpw7NuIGRlIGltw6FnZW5lcyBkZSBwZ20gYSBwbmcgcGFyYSBlbCBkZXNhcnJvbGxvIGRlbCBtb2RlbG8uDQoNCg0KDQojIyMgNS4yLiDCv0hheSBhbGd1bmEgY2FyYWN0ZXLDrXN0aWNhIGRlIGxhcyBpbcOhZ2VuZXMgcXVlIG1lam9yZSBsYSBjYXBhY2lkYWQgZGUgcmVzcHVlc3RhPw0KDQoNCi0gQ29tbyBzZSBkaWpvIGFudGVyaW9ybWVudGUsIHF1ZSB0b2RhcyBsYXMgaW3DoWdlbmVzIHNlYW4gZnJvbnRhbGVzLg0KDQotIExhIG5pdGlkZXogZGUgbGFzIGltw6FnZW5lcy4NCg0KLSBQYXJhIGVzdGUgbW9kZWxvLCBsYXMgcGVyc29uYXMgY29uIHRleiBjbGFyYSBkZWJlcsOtYW4gdXNhciBsZW50ZXMgb3NjdXJvcyBwYXJhIHF1ZSDDqXN0ZSBwdWVkYSBpZGVudGlmaWNhciBxdWUgc8OtIHBvc2VlbiBsZW50ZXMuDQoNCg0KIyMgNi4gRW5sYWNlIGRlIGludGVyw6lzDQoNClVzdGVkIHB1ZWRlIHJldmlzYXIgZWwgY8OzZGlnbyB5IGxvcyBkYXRvcyB1c2Fkb3MgZW4gZXN0ZSBwcm95ZWN0byBlbiBudWVzdHJvIHJlcG9zaXRvcmlvIGVuIFtHaXRodWJdKGh0dHBzOi8vZ2l0aHViLmNvbS9BbWlsZGVyTy9UZXJjZXJhLWVudHJlZ2EtRnVuZGFtZW50b3MtZGUtQW5hbGl0aWNhKQ0KDQojIyA3LiBSZWZlcmVuY2lhcw0KDQpbMV0gaHR0cHM6Ly93d3cudW0uZXMvZ2VvZ3JhZi9zaWdtdXIvdGVtYXJpb2h0bWwvbm9kZTc0Lmh0bWwgIA0KWzJdIGh0dHBzOi8vZW1wcmVzYXMuYmxvZ3RoaW5rYmlnLmNvbS9jb21vLWludGVycHJldGFyLWxhLW1hdHJpei1kZS1jb25mdXNpb24tZWplbXBsby1wcmFjdGljbw0KWzNdIGh0dHBzOi8vYXJjaGl2ZS5pY3MudWNpLmVkdS9kYXRhc2V0LzEyNC9jbXUrZmFjZStpbWFnZXMNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0K