1. Introducción
La clasificación de imágenes es objeto de estudio y aplicación en
múltiples áreas del conocimiento. En el caso más simple, se busca tomar
una decisión con base en la información que contiene una imagen [1]. En
este reporte se aborda el ejercicio de clasificar imágenes de sujetos
con lentes/gafas utilizando técnicas de aprendizaje estadístico
supervisadas.
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).
Se pueden encontrar personas con lentes y personas sin lentes. Dentro de
las imágenes de personas con lentes se hallan 6 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 es revisado y se eliminan imágenes que se
consideran podrían pertubar el aprendizaje del modelo, en éstas se
hallaban personas con lentes a medio construir, dos rostros en una misma
imagen y otras con detalles similares. Finalmente el conjunto de datos
queda con 454 imágenes de sujetos con lentes y 618 de sujetos sin
lentes. También, se eligió esta cantidad de datos por la capacidad y
velocidad de procesamiento que se tiene al alcance.
2.1. Procesamiento
Las imágenes se pueden vectorizar. Es decir, vista la imagen como una
matriz, sus columnas se ubican consecutivamente una debajo de la otra
hasta obtener un vector. Luego, los vectores 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
Se puede evidenciar a continuación una figura de un hombre sin gafas
de una de las imágenes del modelo.
#Podemos mostrarla con el siguiente código
display(img,method='raster',all=TRUE)

Igualmente, se muestra una figura de un hombre con gafas en una de
las imágenes del modelo.
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)

Ya que todas las figuras fueron creadas por computadora, los lentes
se ubicarán casi en la misma posición, por esa razón se delimitaron las
imágenes.
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)

Inclusive, se podría cortar solamente el septo nasal que es donde se
marcan más los lentes para diferenciar las personas que usan o no gafas.
Como se muestra a continuación:
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)
}






Finalmente, se evidenció que habían diferencias entre los histogramas
de personas con o sin gafas, ya que se aprecia que hay un perfil
diferente entre la personas que tienen gafas y las que no. Para ello, se
utilizó la intensidad de luz que está en una escala de 0 a 1, donde el 0
representa la tonalidad más oscura de la imagen y el 1 la más clara. Es
decir, si en la fotografía existen más píxeles en la parte oscura,
entonces la frecuencia allí representada en el histograma será
mayor.
Para las imágenes que no tienen lentes, la frecuencia está hacia el
lado derecho, es decir, que la imagen tiene más intensidad de luz. Para
las imágenes que tienen lentes oscuros, la frecuencia está hacia el lado
izquierdo, es decir, que la imagen tiene más píxeles oscuros. Y
finalmente, para las imágenes que tienen lentes transparentes, la
frecuencia tiene más variabilidad pero aún así se puede notar que son
diferentes a las demás.
3. Modelo
Con la intención de buscar una forma para poder comparar los vectores
se crea una matriz con datos de intensidad, en donde, se establecen los
cuartiles del histograma para obtener un perfil de comparación. Y
además, con apoyo de una función cíclica, la cual comienza en 1 hasta el
tamaño de la lista, se crea una matriz(representa las fotos con gafas)
que finalmente se almacena en un archivo csv.
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 muestran los valores divididos en rangos con
respecto a su nivel de intensidad de luz, para así tener más resolución
en los resultados.
#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
LS0tDQp0aXRsZTogIkNsYXNpZmljYWNpw7NuIGRlIGltw6FnZW5lcyBhIHRyYXbDqXMgZGUgbGEgaWRlbnRpZmljYWNpw7NuIGRlIHJvc3Ryb3MgY29uIGxlbnRlcyBvIHNpbiBsZW50ZXMiDQphdXRob3I6ICJBbWlsZGVyIFN0ZXdpbiBPc3BpbmEgVG9iw7NuLCBOaWNvbGFzIFBlcmV6IFZhc3F1ZXosIEpvaG4gU3RpdmVuIE1lamlhIExvcGVyYS4iDQpkYXRlOiAiMjIvMTEvMjAyMyINCm91dHB1dDogDQogIGh0bWxfZG9jdW1lbnQ6DQogICAgdGhlbWU6IHNwYWNlbGFiDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgY29kZV9kb3dubG9hZDogeWVzDQogICAgZGZfcHJpbnQ6IHBhZ2VkDQogICAgdG9jOiB0cnVlDQogICAgdG9jX2Zsb2F0OiANCiAgICAgIGNvbGFwc2U6IGZhbHNlDQogICAgICANCiAgICAgIA0KLS0tIA0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkNCmBgYA0KDQoNCiMjIDEuIEludHJvZHVjY2nDs24NCg0KTGEgY2xhc2lmaWNhY2nDs24gZGUgaW3DoWdlbmVzIGVzIG9iamV0byBkZSBlc3R1ZGlvIHkgYXBsaWNhY2nDs24gZW4gbcO6bHRpcGxlcyDDoXJlYXMgZGVsIGNvbm9jaW1pZW50by4gRW4gZWwgY2FzbyBtw6FzIHNpbXBsZSwgc2UgYnVzY2EgdG9tYXIgdW5hIGRlY2lzacOzbiBjb24gYmFzZSBlbiBsYSBpbmZvcm1hY2nDs24gcXVlIGNvbnRpZW5lIHVuYSBpbWFnZW4gWzFdLiBFbiBlc3RlIHJlcG9ydGUgc2UgYWJvcmRhIGVsIGVqZXJjaWNpbyBkZSBjbGFzaWZpY2FyIGltw6FnZW5lcyBkZSBzdWpldG9zIGNvbiBsZW50ZXMvZ2FmYXMgdXRpbGl6YW5kbyB0w6ljbmljYXMgZGUgYXByZW5kaXphamUgZXN0YWTDrXN0aWNvIHN1cGVydmlzYWRhcy4NCg0KDQojIyAyLiBEYXRvcw0KDQpFbCBjb25qdW50byBkZSBkYXRvcyBzZSBvYnR1dm8gZGVsIGN1cnNvIFQ4MS04NTU6IFtBcHBsaWNhdGlvbnMgb2YgRGVlcCBMZWFybmluZ10oaHR0cHM6Ly9zaXRlcy53dXN0bC5lZHUvamVmZmhlYXRvbi90ODEtNTU4LykgZGlyaWdpZG8gcG9yIGVsIHByb2Zlc29yIFtKZWZmIEhlYXRvbl0oaHR0cHM6Ly9naXRodWIuY29tL2plZmZoZWF0b24pLCBlbiBsYSBXYXNoaW5ndG9uIFVuaXZlcnNpdHkgaW4gU3QuIExvdWlzIChXVVNUTCkuIExvcyBkYXRvcywgZXN0w6FuIGFsb2phZG9zIGVuIHVuIHJldG8gZGUgW0thZ2dsZV0oaHR0cHM6Ly93d3cua2FnZ2xlLmNvbS9qZWZmaGVhdG9uL2dsYXNzZXMtb3Itbm8tZ2xhc3NlcykgZGVsIGHDsW8gMjAyMCwgZW4gZG9uZGUgc2UgZW5jdWVudHJhbiA1MDAwIGltw6FnZW5lcyBwcm9kdWNpZGFzIGFydGlmaWNpYWxtZW50ZSBhIHRyYXbDqXMgZGUgdW5hIHJlZCBuZXVyb25hbCBhZHZlcnNhIGdlbmVyYXRpdmEgKEdBTikuIFNlIHB1ZWRlbiBlbmNvbnRyYXIgcGVyc29uYXMgY29uIGxlbnRlcyB5IHBlcnNvbmFzIHNpbiBsZW50ZXMuIERlbnRybyBkZSBsYXMgaW3DoWdlbmVzIGRlIHBlcnNvbmFzIGNvbiBsZW50ZXMgc2UgaGFsbGFuIDYgdGlwb3MgZGUgbGVudGVzLiBMYSBmaWd1cmEgMSBwcmVzZW50YSB1bmEgbXVlc3RyYSBkZSBsb3MgdGlwb3MgZGUgbGVudGVzIHF1ZSBzZSBwdWVkZW4gZW5jb250cmFyIGVuIGVsIGNvbmp1bnRvIGRlIGRhdG9zOg0KDQoNCiFbRmlndXJhIDEuIFRpcG9zIGRlIGxlbnRlc10oaHR0cHM6Ly9kYXRhLmhlYXRvbnJlc2VhcmNoLmNvbS9pbWFnZXMvd3VzdGwva2FnZ2xlL2thZ2dsZS1mYWNlcy1nbGFzc2VzLTIucG5nKQ0KDQpFbCBjb25qdW50byBkZSBkYXRvcyBlcyByZXZpc2FkbyB5IHNlIGVsaW1pbmFuIGltw6FnZW5lcyBxdWUgc2UgY29uc2lkZXJhbiBwb2Ryw61hbiBwZXJ0dWJhciBlbCBhcHJlbmRpemFqZSBkZWwgbW9kZWxvLCBlbiDDqXN0YXMgc2UgaGFsbGFiYW4gcGVyc29uYXMgY29uIGxlbnRlcyBhIG1lZGlvIGNvbnN0cnVpciwgZG9zIHJvc3Ryb3MgZW4gdW5hIG1pc21hIGltYWdlbiB5IG90cmFzIGNvbiBkZXRhbGxlcyBzaW1pbGFyZXMuIEZpbmFsbWVudGUgZWwgY29uanVudG8gZGUgZGF0b3MgcXVlZGEgY29uIDQ1NCBpbcOhZ2VuZXMgZGUgc3VqZXRvcyBjb24gbGVudGVzIHkgNjE4IGRlIHN1amV0b3Mgc2luIGxlbnRlcy4gVGFtYmnDqW4sIHNlIGVsaWdpw7MgZXN0YSBjYW50aWRhZCBkZSBkYXRvcyBwb3IgbGEgY2FwYWNpZGFkIHkgdmVsb2NpZGFkIGRlIHByb2Nlc2FtaWVudG8gcXVlIHNlIHRpZW5lIGFsIGFsY2FuY2UuDQoNCg0KIyMjIDIuMS4gUHJvY2VzYW1pZW50byANCg0KTGFzIGltw6FnZW5lcyBzZSBwdWVkZW4gdmVjdG9yaXphci4gRXMgZGVjaXIsIHZpc3RhIGxhIGltYWdlbiBjb21vIHVuYSBtYXRyaXosIHN1cyBjb2x1bW5hcyBzZSB1YmljYW4gY29uc2VjdXRpdmFtZW50ZSB1bmEgZGViYWpvIGRlIGxhIG90cmEgaGFzdGEgb2J0ZW5lciB1biB2ZWN0b3IuIEx1ZWdvLCBsb3MgdmVjdG9yZXMgc2Vyw6FuIGxhcyBmaWxhcyBkZSB1bmEgbWF0cml6IGRlIGRhdG9zIGNvbiBsYSBpbmZvcm1hY2nDs24gZGUgaW50ZW5zaWRhZGVzIHBhcmEgY2FkYSBww614ZWwgZW4gbGFzIHJlc3BlY3RpdmFzIGltw6FnZW5lcy4NCg0KUGFyYSBkZXNjcmliaXIgdW5hIGZvcm1hIGRlIGNvbXBhcmFyIGxvcyB2ZWN0b3JlcywgdXNhcmVtb3MgdW5hIG11ZXN0cmEgZGUgNiBzdWpldG9zICgzIGNvbiBnYWZhcyB5IDMgc2luIGdhZmFzKS4gTGFzIGZpZ3VyYXMgc2Vyw6FuIGFuYWxpemFkYXMgY29uIGVsIHBhcXVldGUgW0VCSW1hZ2VdKGh0dHBzOi8vYmlvY29uZHVjdG9yLm9yZy9wYWNrYWdlcy9yZWxlYXNlL2Jpb2MvaHRtbC9FQkltYWdlLmh0bWwpDQoNCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgcmVzdWx0cyA9ICdoaWRlJywgZWNobz1GIH0NCiNpbnN0YWxhciBwYXF1ZXRlcyB5IGxpYnJlcsOtYXMgbmVjZXNhcmlhcw0KDQojaWYgKCFyZXF1aXJlKCJCaW9jTWFuYWdlciIsIHF1aWV0bHkgPSBUUlVFKSkNCiMgIGluc3RhbGwucGFja2FnZXMoIkJpb2NNYW5hZ2VyIikNCg0KI0Jpb2NNYW5hZ2VyOjppbnN0YWxsKCJFQkltYWdlIikNCg0KI2luc3RhbGwucGFja2FnZXMoInBscyIpDQojaW5zdGFsbC5wYWNrYWdlcygicGl4bWFwIikNCiNpbnN0YWxsLnBhY2thZ2VzKCJ5YXJkc3RpY2siKQ0KDQoNCmxpYnJhcnkoRUJJbWFnZSkNCmxpYnJhcnkocGxzKQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShwaXhtYXApDQpsaWJyYXJ5KGRhdGEudGFibGUpDQpsaWJyYXJ5KHJlYWR4bCkNCmxpYnJhcnkoeWFyZHN0aWNrKQ0KDQoNCmBgYA0KDQoNCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgcmVzdWx0cyA9ICdoaWRlJywgZWNobz1GIH0NCiNFbnRyYXIgYWwgZGlyZWN0b3JpbyBiYXNlDQoNCg0KDQpzZXR3ZCgiLi9Nb2RlbG9zIikNCg0KDQoNCiNBYnJpciBsYXMgaW3DoWdlbmVzIGRlbCBtb2RlbG8NCmxpc3QuZmlsZXMoKS0+bGlzdGENCg0KI1ZlciBsYXMgZmlndXJhcyBjaXRhZGFzDQoNCmBgYA0KDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIHJlc3VsdHMgPSAnaGlkZScsIGVjaG89RiB9DQojTHVlZ28sIGFicmlyZW1vcyB1bmEgZGUgZXNhcyBmaWd1cmFzDQoNCg0Kc2V0d2QoIi4vTW9kZWxvcyIpDQoNCg0KaW1hZ2VuYW1lIDwtbGlzdGFbMV0NCmltZyA9IHJlYWRJbWFnZShpbWFnZW5hbWUpDQppbWFnZW5hbWUgPC1saXN0YVsyXQ0KaW1nMiA9IHJlYWRJbWFnZShpbWFnZW5hbWUpDQpgYGANClNlIHB1ZWRlIGV2aWRlbmNpYXIgYSBjb250aW51YWNpw7NuIHVuYSBmaWd1cmEgZGUgdW4gaG9tYnJlIHNpbiBnYWZhcyBkZSB1bmEgZGUgbGFzIGltw6FnZW5lcyBkZWwgbW9kZWxvLg0KDQpgYGB7ciAgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSAsIGVjaG89VCxmaWcud2lkdGg9IDIsZmlnLmhlaWdodD0gMixmaWcuYWxpZ249J2NlbnRlcid9DQojUG9kZW1vcyBtb3N0cmFybGEgY29uIGVsIHNpZ3VpZW50ZSBjw7NkaWdvDQpkaXNwbGF5KGltZyxtZXRob2Q9J3Jhc3RlcicsYWxsPVRSVUUpDQpgYGANCg0KSWd1YWxtZW50ZSwgc2UgbXVlc3RyYSB1bmEgZmlndXJhIGRlIHVuIGhvbWJyZSBjb24gZ2FmYXMgZW4gdW5hIGRlIGxhcyBpbcOhZ2VuZXMgZGVsIG1vZGVsby4NCg0KYGBge3IgIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UgLCBlY2hvPVQsZmlnLndpZHRoPSAyLGZpZy5oZWlnaHQ9IDIsZmlnLmFsaWduPSdjZW50ZXInIH0NCmRpc3BsYXkoaW1nMixtZXRob2Q9J3Jhc3RlcicsYWxsPVRSVUUpDQpgYGANCg0KUGFyYSBmYWNpbGl0YXIgbGEgdmlzdWFsaXphY2nDs24geSB2ZWN0b3JpemFjacOzbiwgc2UgcHJvY2VkZSBhIGNvbnZlcnRpciBsYXMgaW3DoWdlbmVzIGVuIHVuYSBlc2NhbGEgZGUgZ3Jpc2VzIHBhcmEgbm8gdHJhYmFqYXIgY29uIGVsIG1vZGVsbyBkZSBjb2xvciBDTVlLKiAobW9kZWxvIGRlIGNvbG9yIHN1c3RyYWN0aXZvIHF1ZSBzZSB1dGlsaXphIGVuIGxhIGltcHJlc2nDs24gZW4gY29sb3JlcywgZXMgbGEgdmVyc2nDs24gbW9kZXJuYSB5IG3DoXMgcHJlY2lzYSBkZWwgYW50aWd1byBtb2RlbG8gdHJhZGljaW9uYWwgZGUgY29sb3JhY2nDs24pLg0KDQpQb3IgZW5kZSwgbGFzIGltw6FnZW5lcyBwcmVzZW50YWRhcyBhaG9yYSBzZSB2YW4gYSB2aXN1YWxpemFyIGVuIGVzcGVjdHJvIGRlIGdyaXNlcy4NCg0KYGBge3IgIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UgLCBlY2hvPVQsZmlnLndpZHRoPSAyLGZpZy5oZWlnaHQ9IDIsZmlnLmFsaWduPSdjZW50ZXInIH0NCiNFc2NhbGEgZGUgZ3Jpc2VzLg0KDQppbWdfZzwtY2hhbm5lbChpbWcsImdyYXkiKQ0KaW1nX2cyPC1jaGFubmVsKGltZzIsImdyYXkiKQ0KDQpkaXNwbGF5KGltZ19nLG1ldGhvZD0ncmFzdGVyJyxhbGw9VFJVRSkNCmRpc3BsYXkoaW1nX2cyLG1ldGhvZD0ncmFzdGVyJyxhbGw9VFJVRSkNCmBgYA0KDQpZYSBxdWUgdG9kYXMgbGFzIGZpZ3VyYXMgZnVlcm9uIGNyZWFkYXMgcG9yIGNvbXB1dGFkb3JhLCBsb3MgbGVudGVzIHNlIHViaWNhcsOhbiBjYXNpIGVuIGxhIG1pc21hIHBvc2ljacOzbiwgcG9yIGVzYSByYXrDs24gc2UgZGVsaW1pdGFyb24gbGFzIGltw6FnZW5lcy4NCg0KYGBge3IgIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UgLCBlY2hvPVQsIGZpZy53aWR0aD0gMyxmaWcuaGVpZ2h0PSAzLGZpZy5hbGlnbj0nY2VudGVyJyB9DQppbWdfY3JvcCA9IGltZ19nWzE1MDo5MDAsMzYwOjY1MF0NCmRpc3BsYXkoaW1nX2Nyb3AsbWV0aG9kPSdyYXN0ZXInLGFsbD1UUlVFKQ0KDQppbWdfY3JvcDIgPSBpbWdfZzJbMTUwOjkwMCwzNjA6NjUwXQ0KZGlzcGxheShpbWdfY3JvcDIsbWV0aG9kPSdyYXN0ZXInLGFsbD1UUlVFKQ0KYGBgDQoNCkluY2x1c2l2ZSwgc2UgcG9kcsOtYSBjb3J0YXIgc29sYW1lbnRlIGVsIHNlcHRvIG5hc2FsIHF1ZSBlcyBkb25kZSBzZSBtYXJjYW4gbcOhcyBsb3MgbGVudGVzIHBhcmEgZGlmZXJlbmNpYXIgbGFzIHBlcnNvbmFzIHF1ZSB1c2FuIG8gbm8gZ2FmYXMuIENvbW8gc2UgbXVlc3RyYSBhIGNvbnRpbnVhY2nDs246DQoNCmBgYHtyICBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFICwgZWNobz1ULCBmaWcud2lkdGg9IDMsZmlnLmhlaWdodD0gMyxmaWcuYWxpZ249J2NlbnRlcicgfQ0KaW1nX2Nyb3AgPSBpbWdfZ1s0NDA6NTkwLDQ0MDo1ODBdDQpkaXNwbGF5KGltZ19jcm9wLG1ldGhvZD0ncmFzdGVyJyxhbGw9VFJVRSkNCg0KaW1nX2Nyb3AyID0gaW1nX2cyWzQ0MDo1OTAsNDQwOjU4MF0NCmRpc3BsYXkoaW1nX2Nyb3AyLG1ldGhvZD0ncmFzdGVyJyxhbGw9VFJVRSkNCmBgYA0KDQpWYW1vcyBhIHByZXNlbnRhciB1bm9zIGhpc3RvZ3JhbWFzIGNvbiBlbCBmaW4gZGUgdmVyIHBvciBtZWRpbyBkZSBzdXMgZnJlY3VlbmNpYXMgc2kgaGF5IGRpZmVyZW5jaWFzIGVudHJlIGltw6FnZW5lcyBjb24geSBzaW4gZ2FmYXMuIEVuIGxvcyBzaWd1aWVudGVzIGhpc3RvZ3JhbWFzIHNlIGJ1c2NhIG9ic2VydmFyIGxhIGNhbnRpZGFkIGRlIHDDrXhlbGVzICgyMS4yOTEgcMOteGVsZXMpIHZlcnN1cyBsYSBpbnRlbnNpZGFkICgwIGEgMSkuDQoNCmBgYHtyICBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFICwgZWNobz1UIH0NCg0Kc2V0d2QoIi4vTW9kZWxvcyIpDQoNCg0KZm9yIChpIGluIDE6Nil7DQppbWFnZW5hbWUgPC1saXN0YVtpXQ0KDQppbWcgPSByZWFkSW1hZ2UoaW1hZ2VuYW1lKQ0KDQppbWdfZzwtY2hhbm5lbChpbWcsImdyYXkiKQ0KDQppbWdfY3JvcCA9IGltZ19nWzQ0MDo1OTAsNDQwOjU4MF0NCnBhcihtZnJvdz1jKDEsMikpDQpkaXNwbGF5KGltZ19jcm9wKjIsbWV0aG9kPSdyYXN0ZXInLGFsbD1UUlVFKQ0KDQpoaXN0KGltZ19jcm9wLGJyZWFrcz0xMDApDQp9DQoNCmBgYA0KDQpGaW5hbG1lbnRlLCBzZSBldmlkZW5jacOzIHF1ZSBoYWLDrWFuIGRpZmVyZW5jaWFzIGVudHJlIGxvcyBoaXN0b2dyYW1hcyBkZSBwZXJzb25hcyBjb24gbyBzaW4gZ2FmYXMsIHlhIHF1ZSBzZSBhcHJlY2lhIHF1ZSBoYXkgdW4gcGVyZmlsIGRpZmVyZW50ZSBlbnRyZSBsYSBwZXJzb25hcyBxdWUgdGllbmVuIGdhZmFzIHkgbGFzIHF1ZSBuby4gUGFyYSBlbGxvLCBzZSB1dGlsaXrDsyBsYSBpbnRlbnNpZGFkIGRlIGx1eiBxdWUgZXN0w6EgZW4gdW5hIGVzY2FsYSBkZSAwIGEgMSwgZG9uZGUgZWwgMCByZXByZXNlbnRhIGxhIHRvbmFsaWRhZCBtw6FzIG9zY3VyYSBkZSBsYSBpbWFnZW4geSBlbCAxIGxhIG3DoXMgY2xhcmEuIEVzIGRlY2lyLCBzaSBlbiBsYSBmb3RvZ3JhZsOtYSBleGlzdGVuIG3DoXMgcMOteGVsZXMgZW4gbGEgcGFydGUgb3NjdXJhLCBlbnRvbmNlcyBsYSBmcmVjdWVuY2lhIGFsbMOtIHJlcHJlc2VudGFkYSBlbiBlbCBoaXN0b2dyYW1hIHNlcsOhIG1heW9yLg0KDQpQYXJhIGxhcyBpbcOhZ2VuZXMgcXVlIG5vIHRpZW5lbiBsZW50ZXMsIGxhIGZyZWN1ZW5jaWEgZXN0w6EgaGFjaWEgZWwgbGFkbyBkZXJlY2hvLCBlcyBkZWNpciwgcXVlIGxhIGltYWdlbiB0aWVuZSBtw6FzIGludGVuc2lkYWQgZGUgbHV6LiBQYXJhIGxhcyBpbcOhZ2VuZXMgcXVlIHRpZW5lbiBsZW50ZXMgb3NjdXJvcywgbGEgZnJlY3VlbmNpYSBlc3TDoSBoYWNpYSBlbCBsYWRvIGl6cXVpZXJkbywgZXMgZGVjaXIsIHF1ZSBsYSBpbWFnZW4gdGllbmUgbcOhcyBww614ZWxlcyBvc2N1cm9zLiBZIGZpbmFsbWVudGUsIHBhcmEgbGFzIGltw6FnZW5lcyBxdWUgdGllbmVuIGxlbnRlcyB0cmFuc3BhcmVudGVzLCBsYSBmcmVjdWVuY2lhIHRpZW5lIG3DoXMgdmFyaWFiaWxpZGFkIHBlcm8gYcO6biBhc8OtIHNlIHB1ZWRlIG5vdGFyIHF1ZSBzb24gZGlmZXJlbnRlcyBhIGxhcyBkZW3DoXMuDQoNCiMjIDMuIE1vZGVsbw0KDQpDb24gbGEgaW50ZW5jacOzbiBkZSBidXNjYXIgdW5hIGZvcm1hIHBhcmEgcG9kZXIgY29tcGFyYXIgbG9zIHZlY3RvcmVzIHNlIGNyZWEgdW5hIG1hdHJpeiBjb24gZGF0b3MgZGUgaW50ZW5zaWRhZCwgZW4gZG9uZGUsIHNlIGVzdGFibGVjZW4gbG9zIGN1YXJ0aWxlcyBkZWwgaGlzdG9ncmFtYSBwYXJhIG9idGVuZXIgdW4gcGVyZmlsIGRlIGNvbXBhcmFjacOzbi4gWSBhZGVtw6FzLCBjb24gYXBveW8gZGUgdW5hIGZ1bmNpw7NuIGPDrWNsaWNhLCBsYSBjdWFsIGNvbWllbnphIGVuIDEgaGFzdGEgZWwgdGFtYcOxbyBkZSBsYSBsaXN0YSwgc2UgY3JlYSB1bmEgbWF0cml6KHJlcHJlc2VudGEgbGFzIGZvdG9zIGNvbiBnYWZhcykgcXVlIGZpbmFsbWVudGUgc2UgYWxtYWNlbmEgZW4gdW4gYXJjaGl2byBjc3YuDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIHJlc3VsdHMgPSAnaGlkZScsIGVjaG89Rn0NCiNFbnRyYXIgYWwgZGlyZWN0b3JpbyBiYXNlDQoNCg0KIyANCiMgI0FicmlyIGxhcyBpbcOhZ2VuZXMgZGVsIG1vZGVsbw0KIyBsaXN0LmZpbGVzKCktPmxpc3RhDQoNCiNWYW1vcyBhIGNyZWFyIHVuYSBjb2x1bW5hIGNvbiBsb3MgY3VhcnRpbGVzIG5lY2VzYXJpb3MgKGJhc2FkbyBlbiBlbCBjb25jZXB0byBkZSBoaXN0b2dyYW1hKQ0KDQojIHBhc3RlMChzZXEoMCwwLjk5NSwwLjAwNSksIi0iLHNlcSgwLjAwNSwxLDAuMDA1KSktPmN1YXJ0aWxlcw0KIyANCiMgZGF0YS5mcmFtZSh2YWx1ZXM9Y3VhcnRpbGVzKS0+Z2FmYXMNCiMgDQojIGZvciAoaSBpbiAxOmxlbmd0aChsaXN0YSkpew0KIyBpbWFnZW5hbWUgPC1saXN0YVtpXQ0KIyANCiMgaW1nID0gcmVhZEltYWdlKGltYWdlbmFtZSkNCiMgDQojIGltZ19nPC1jaGFubmVsKGltZywiZ3JheSIpDQojIA0KIyBpbWdfY3JvcCA9IGltZ19nWzQ0MDo1OTAsNDQwOjU4MF0NCiMgaGlzdChpbWdfY3JvcCxicmVha3M9c2VxKDAsMSwwLjAwNSkpLT5kYWRvcw0KIyBkYXRhLmZyYW1lKGdhZmFzLGRhZG9zJGNvdW50cyktPmdhZmFzDQojIA0KIyB9DQojIGNvbG5hbWVzKGdhZmFzKTwtYygidmFsdWVzIixsaXN0YSkNCg0KI2d1YXJkYW5kbyBlc3RlIHJlc3VsdGFkbw0KDQoNCmBgYA0KDQpTZSByZWFsaXphIGVsIG1pc21vIHByb2Nlc28gYW50ZXJpb3IgcGFyYSBlbCBjYXNvIGRlIGZvdG9zIHNpbiBnYWZhcywgcXVlIGlndWFsbWVudGUgZGljaGEgbWF0cml6IGdlbmVyYWRhIHNlIGFsbWFjZW5hIGVuIG90cm8gYXJjaGl2byBjc3YuDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCByZXN1bHRzID0gJ2hpZGUnLCBlY2hvPUZ9DQoNCiNFbnRyYXIgYWwgZGlyZWN0b3JpbyBiYXNlDQoNCg0KDQojIA0KIyAjQWJyaXIgbGFzIGltw6FnZW5lcyBkZWwgbW9kZWxvDQojIGxpc3QuZmlsZXMoKS0+bGlzdGENCiMgDQojICNWYW1vcyBhIGNyZWFyIHVuYSBjb2x1bW5hIGNvbiBsb3MgY3VhcnRpbGVzIG5lY2VzYXJpb3MgKGJhc2FkbyBlbiBlbCBjb25jZXB0byBkZSBoaXN0b2dyYW1hKQ0KIyANCiMgcGFzdGUwKHNlcSgwLDAuOTk1LDAuMDA1KSwiLSIsc2VxKDAuMDA1LDEsMC4wMDUpKS0+Y3VhcnRpbGVzDQojIA0KIyBkYXRhLmZyYW1lKHZhbHVlcz1jdWFydGlsZXMpLT5TZ2FmYXMNCiMgDQojIGZvciAoaSBpbiAxOmxlbmd0aChsaXN0YSkpew0KIyBpbWFnZW5hbWUgPC1saXN0YVtpXQ0KIyANCiMgaW1nID0gcmVhZEltYWdlKGltYWdlbmFtZSkNCiMgDQojIGltZ19nPC1jaGFubmVsKGltZywiZ3JheSIpDQojIA0KIyBpbWdfY3JvcCA9IGltZ19nWzQ0MDo1OTAsNDQwOjU4MF0NCiMgaGlzdChpbWdfY3JvcCxicmVha3M9c2VxKDAsMSwwLjAwNSkpLT5kYWRvcw0KIyBkYXRhLmZyYW1lKFNnYWZhcyxkYWRvcyRjb3VudHMpLT5TZ2FmYXMNCiMgDQojIH0NCiMgY29sbmFtZXMoU2dhZmFzKTwtYygidmFsdWVzIixsaXN0YSkNCiMgDQojICNndWFyZGFuZG8gZXN0ZSByZXN1bHRhZG8NCiMgDQoNCmBgYA0KDQpFbiBlc3RlIG1vZGVsbywgZ2VuZXJhbW9zIDIwMCB2YWxvcmVzIG51bcOpcmljb3MgcGFyYSBjYWRhIGltYWdlbiByZWNvcnRhZGEsIGRvbmRlIGRpY2hvcyB2YWxvcmVzIHJlcHJlc2VudGFuIGxhIGNhbnRpZGFkIGRlIHDDrXhlbGVzLg0KDQpBIGNvbnRpbnVhY2nDs24gc2UgbXVlc3RyYW4gbG9zIHZhbG9yZXMgZGl2aWRpZG9zIGVuIHJhbmdvcyBjb24gcmVzcGVjdG8gYSBzdSBuaXZlbCBkZSBpbnRlbnNpZGFkIGRlIGx1eiwgcGFyYSBhc8OtIHRlbmVyIG3DoXMgcmVzb2x1Y2nDs24gZW4gbG9zIHJlc3VsdGFkb3MuDQpgYGB7ciwgZmlnLmRpbSA9IGMoMTgsIDYpLCBkcGk9NjAwLCBtZXNzYWdlPUZBTFNFfQ0KI0VudHJhciBhbCBkaXJlY3RvcmlvIGJhc2UNCg0KDQojTGVlciBsb3MgYXJjaGl2b3MNCg0KcmVhZC5jc3YoIkZvdG9zQ29uR2FmYXMuY3N2IiktPmdhZmFzDQoNCiNnYWZhcyA8LSByZWFkLmNzdigiQzovVXNlcnMvTGVub3ZvL0Rvd25sb2Fkcy9Gb3Rvc0NvbkdhZmFzLmNzdiIpDQoNCnJlYWQuY3N2KCJGb3Rvc1NpbkdhZmFzLmNzdiIpLT5TZ2FmYXMNCg0KI1NnYWZhcyA8LSByZWFkLmNzdigiQzovVXNlcnMvTGVub3ZvL0Rvd25sb2Fkcy9Gb3Rvc1NpbkdhZmFzLmNzdiIpDQoNCiNWYW1vcyBhIHZlciByw6FwaWRhbWVudGUgbG9zIGFyY2hpdm9zOg0KDQpoZWFkKGdhZmFzLDUpDQoNCmhlYWQoU2dhZmFzLDUpDQpgYGANCg0KTHVlZ28sIGxhIGluZm9ybWFjacOzbiBxdWUgc2UgdGllbmUgdGFudG8gcGFyYSBsYXMgaW3DoWdlbmVzIHF1ZSB0aWVuZW4gZ2FmYXMgY29tbyBsYXMgcXVlIG5vLCBsYSBhZGVjdWFtb3Mgc2Vnw7puIGxvIHJlcXVlcmlkby4gRGVzcHXDqXMgZGUgZXN0ZSBwcm9jZXNvIHNlIGFncnVwYSBsYSBpbmZvcm1hY2nDs24gIGRlIGFtYm9zIGdydXBvcyB5IHNlIGNyZWEgdW4gYXJjaGl2byBjc3YgY29uIGxvcyBkYXRvcyBkZSBlbnRyZW5hbWllbnRvLg0KYGBge3IsIGZpZy5kaW0gPSBjKDE4LCA2KSwgZHBpPTYwMCwgbWVzc2FnZT1GQUxTRX0NCiNvcmRlbmFyIHRhYmxhcywgY29sdW1uYXMsIGNhbWJpYXIgbm9tYnJlcw0KDQpnYWZhc1ssMl0tPnJvd25hbWVzKGdhZmFzKQ0KZ2FmYXNbLC1jKDE6MildLT5nYWZhcw0KdChnYWZhcyktPmdhZmFzDQoNCg0KU2dhZmFzWywyXS0+cm93bmFtZXMoU2dhZmFzKQ0KU2dhZmFzWywtYygxOjIpXS0+U2dhZmFzDQp0KFNnYWZhcyktPlNnYWZhcw0KDQojSnVudGFuZG8gbGEgaW5mb3JtYWNpw7NuDQoNCnRyYWluPC1kYXRhLmZyYW1lKHJiaW5kKGdhZmFzLFNnYWZhcyksZ3J1cG89YyhyZXAoIkNvbl9HYWZhcyIsbnJvdyhnYWZhcykpLHJlcCgiU2luX0dhZmFzIixucm93KFNnYWZhcykpKSkNCmNvbG5hbWVzKHRyYWluKTwtYyhjb2xuYW1lcyhnYWZhcyksImdydXBvIikNCg0KI3dyaXRlLmNzdih0cmFpbiwidHJhaW5fMS5jc3YiKQ0KYGBgDQoNCkFob3JhLCBzZSByZWFsaXphIHVuIGdyw6FmaWNvIHBhcmEgYW5hbGl6YXIgbGEgZnJlY3VlbmNpYSBjb24gcmVzcGVjdG8gYSBsYSBpbnRlbnNpZGFkIGRlIGx1ei4NCmBgYHtyLCBmaWcuZGltID0gYygxOCwgNiksIGRwaT02MDAsIG1lc3NhZ2U9RkFMU0V9DQojcmV2aXNhbmRvIGxvcyBoaXN0b2dyYW1hcw0KDQoNCnRyYWluICU+JSBwaXZvdF9sb25nZXIoIWdydXBvLG5hbWVzX3RvPSJyYW5nZSIsdmFsdWVzX3RvPSJ2YWx1ZSIpLT5kYXRvcw0KDQoNCmdncGxvdChkYXRvcywgYWVzKHg9cmFuZ2UsIHk9dmFsdWUsIGNvbG9yPWdydXBvKSkgKw0KICBnZW9tX2JveHBsb3QoKSsgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgdmp1c3QgPSAwLjUsIGhqdXN0PTEsc2l6ZT04KSkNCmBgYA0KRW4gZGljaG8gZ3LDoWZpY28sIHNlIHZlIHJlcHJlc2VudGFkbyBwb3IgbWVkaW8gZGVsIGJveHBsb3Qgcm9qbyBsYSBpbmZvcm1hY2nDs24gcXVlIHBvc2VlbiBsYXMgaW3DoWdlbmVzIGNvbiBnYWZhcyB5IHBvciBtZWRpbyBkZWwgYm94cGxvdCBhenVsLCBsYSBpbmZvcm1hY2nDs24gcXVlIHBvc2VlbiBsYXMgaW3DoWdlbmVzIHNpbiBnYWZhcywgcXVlIHNlIGVuY3VlbnRyYW4gZW50cmUgdW4gcmFuZ28gZGUgaW50ZW5zaWRhZCBkZSBsdXooZGUgMCBhIDEpLCBkb25kZSBlbiBlZmVjdG8gc2Ugb2JzZXJ2w7MsIHF1ZSBlbiBlbCBib3hwbG90IHF1ZSByZXByZXNlbnRhbiBsYXMgaW3DoWdlbmVzIGNvbiBnYWZhcyhyb2pvcykgc2UgZW5jdWVudHJhbiBlbnRyZSByYW5nbyBkZSBvcGFjaWRhZChsYWRvIGl6cXVpZXJkbyBkZSBsYSBpbWFnZW4pLCBsbyBjdWFsIGV2aWRlbmNpYSBxdWUgcG9zZWUgdW4gcGVyZmlsIGRpZmVyZW50ZSBxdWUgZWwgZGUgbGFzIGltw6FnZW5lcyBzaW4gZ2FmYXMoYXp1bCkuIFBhcmEgZWwgZW50cmVuYW1pZW50byBkZWwgbW9kZWxvIHNlIHRvbWFyw6FuIGxvcyBkYXRvcyBkZWwgbGFkbyBpenF1aWVyZG8gcGFyYSBkaWZlcmVuY2lhciBsYXMgaW3DoWdlbmVzIGNvbiB5IHNpbiBnYWZhcywgZGFkbyBxdWUgYSBsYSBkZXJlY2hhIGxhIGRpZmVyZW5jaWEgZW50cmUgbGFzIGltw6FnZW5lcyBlcyBwb2NhIHkgdGVuZGVyw61hIGEgY29udGFtaW5hciBlbCBtb2RlbG8uDQoNCmBgYHtyLCBmaWcuZGltID0gYygxOCwgNiksIGRwaT02MDAsIG1lc3NhZ2U9RkFMU0UsIGluY2x1ZGU9RkFMU0V9DQpwbmcoIkJveHBsb3RfZ2VuZXJhbC5wbmciLHdpZHRoPTEyMDAwLCBoZWlnaHQ9NTAwMCxyZXM9NjAwKQ0KZ2dwbG90KGRhdG9zLCBhZXMoeD1yYW5nZSwgeT12YWx1ZSwgY29sb3I9Z3J1cG8pKSArDQogIGdlb21fYm94cGxvdCgpKyB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCB2anVzdCA9IDAuNSwgaGp1c3Q9MSxzaXplPTgpKQ0KZGV2Lm9mZigpDQpgYGANCg0KQ29uIGVsIGZpbiBkZSByZWR1Y2lyIGxhIGRpbWVuc2lvbmFsaWRhZCBzZSB1dGlsaXphIGVsIEFuw6FsaXNpcyBkZSBDb21wb25lbnRlcyBQcmluY2lwYWxlcyAoQUNQKSwgcGFyYSBhc8OtIHJlcHJlc2VudGFyIGxhIGluZm9ybWFjacOzbiBvcmlnaW5hbCBwZXJvIGVuIHVuIGVzcGFjaW8gZGUgZGltZW5zacOzbiBtZW5vcihsaW1pdGFuZG8gbGEgcMOpcmRpZGEgZGUgaW5mb3JtYWNpw7NuKS4NCg0KRGUgZXN0YSBmb3JtYSwgc2UgZWxhYm9yYSBlbCBtb2RlbG8gbWVkaWFudGUgcmVncmVzacOzbiBkZSBjb21wb25lbnRlcyBwcmluY2lwYWxlcywgZG9uZGUgZWwgZ3J1cG8gcmVwcmVzZW50YSBsYSB2YXJpYWJsZSBwcmVkaWN0b3JhIHkgbGEgZGF0YSBhIGxhIHZhcmlhYmxlIGRlIHJlc3B1ZXN0YSBhIG1vZGVsYXIsIGFzw60gbWlzbW8sIHNlIGNyZcOzIHVuYSB2YXJpYWJsZSBkb25kZSBlbCBuw7ptZXJvIDEgcmVwcmVzZW50YSBsYXMgaW3DoWdlbmVzIGNvbiBnYWZhcyB5IDAgbGFzIGltw6FnZW5lcyBzaW4gZ2FmYXMuIElndWFsbWVudGUgdG9kbyBlc3RlIHByb2NlZGltaWVudG8gc2UgZ3VhcmTDsyBlbiB1biBhcmNoaXZvIGNzdi4NCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSAsIGVjaG89VH0NCg0KI0VqZWN1dGFuZG8gZWwgQUNQDQoNCg0KbXV0YXRlKHRyYWluLGdydXBvPWlmZWxzZShncnVwbz09IkNvbl9HYWZhcyIsMSwwKSktPnRyYWluMg0KDQojY3JlYWNpw7NuIGRlbCBtb2RlbG8NCnBjcihncnVwb34uLGRhdGE9dHJhaW4yWyxjKDE6NjAsMjAxKV0sc2NhbGU9VFJVRSx2YWxpZGF0aW9uPSJDViIpLT5tb2RlbA0KDQojUHJ1ZWJhIHJlZHVuZGFudGUgDQpwcmVkaWN0KG1vZGVsLHRyYWluMlssLTIwMV0sbmNvbXA9MTApLT5wcm9iDQoNCiNTaSBzZSBkZXNlYSwgc2UgcHVlZGVuIGp1bnRhciBsb3MgZGF0b3MgY3JlYWRvcyBjb24gbG9zIHZhbG9yZXMgcmVhbGVzIChjb2x1bW5hIGdydXBvIGRlIGxhIHRhYmxhIHRyYWluKQ0KIGNiaW5kKHByb2IsdHJhaW4yWywyMDFdKS0+cHJvYg0KDQogI3dyaXRlLmNzdihwcm9iLCJkYXRvc19nZW5lcmFkb3MuY3N2IikNCiANCmBgYA0KDQoNCkRlc3B1w6lzIGRlIGNyZWFyIGVsIG1vZGVsbywgcGVyY2liaW1vcyBhbGd1bm9zIGNhc29zIGRvbmRlIGxhIHByZWRpY2Npw7NuIGVyYSBtdXkgYmFqYSBlbiBmb3RvcyBjb24gZ2FmYXMgKGRlYmlkbyBhIG1hcmNvcyBkZSBsYXMgZ2FmYXMgbXV5IGZpbm9zLCBwbGF0ZWFkb3MgbyB0cmFuc3BhcmVudGVzKSBvIG11eSBhbHRhIGVuIGZvdG9zIHNpbiBnYWZhcyAoZGViaWRvIGEgcGVyc29uYXMgY29uIHVuYSB0ZXogbcOhcyBvc2N1cmEpLCBjb21vIHNlIHB1ZWRlIGFwcmVjaWFyIGVuIGVsIHNpZ3VpZW50ZSBncsOhZmljby4NCg0KDQoNCmBgYHtyICBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFICwgZWNobz1ULGZpZy53aWR0aD0gMixmaWcuaGVpZ2h0PSAyLGZpZy5hbGlnbj0nY2VudGVyJ30NCg0Kc2V0d2QoIi4vU2luIGdhZmFzX3RyYWluaW5nIikNCg0KDQoNCiNBYnJpciBsYXMgaW3DoWdlbmVzIGRlbCBtb2RlbG8NCmxpc3QuZmlsZXMoKS0+bGlzdGENCg0KaTwtMTI3DQppbWFnZW5hbWUgPC1saXN0YVtpXQ0KDQppbWcgPSByZWFkSW1hZ2UoaW1hZ2VuYW1lKQ0KDQppbWdfZzwtY2hhbm5lbChpbWcsImdyYXkiKQ0KDQppbWdfY3JvcCA9IGltZ19nWzQ0MDo1OTAsNDQwOjU4MF0NCmRpc3BsYXkoaW1nX2csbWV0aG9kPSdyYXN0ZXInKQ0KDQpzZXR3ZCgiLi4vQ29uIGdhZmFzX3RyYWluaW5nIikNCg0KDQojQWJyaXIgbGFzIGltw6FnZW5lcyBkZWwgbW9kZWxvDQpsaXN0LmZpbGVzKCktPmxpc3RhDQoNCmk8LTIxOQ0KaW1hZ2VuYW1lIDwtbGlzdGFbaV0NCg0KaW1nID0gcmVhZEltYWdlKGltYWdlbmFtZSkNCg0KaW1nX2c8LWNoYW5uZWwoaW1nLCJncmF5IikNCg0KaW1nX2Nyb3AgPSBpbWdfZ1s0NDA6NTkwLDQ0MDo1ODBdDQpkaXNwbGF5KGltZ19nLG1ldGhvZD0ncmFzdGVyJykNCg0KDQoNCmBgYA0KDQpBIGNvbnRpbnVhY2nDs24sIHNlIHByZXNlbnRhIHVuIGRpYWdyYW1hIGRlIHZpb2zDrW4sIGVuIGRvbmRlIHNlIHJlcHJlc2VudGEgbGEgZGVuc2lkYWQgZGUgbXVlc3RyYXMgcXVlIGNsYXNpZmljYSBlbCBtb2RlbG8gZGUgZW50cmVuYW1pZW50byBwYXJhIGxvcyBncnVwb3MgMCAoY29uIGdhZmFzKSB5IDEgKHNpbiBnYWZhcykuIEVzIGbDoWNpbCBkZXN0YWNhciBxdWUgbGEgZ3JhbiBtYXlvcsOtYSBkZSBsYXMgb2JzZXJ2YWNpb25lcyBzaW4gZ2FmYXMgc2UgZW5jdWVudHJhbiBwb3IgZGViYWpvIGRlIDAuNSwgcHJvcG9yY2lvbsOhbmRvbGUgbGEgZm9ybWEgImFuY2hhIiBjYXJhY3RlcsOtc3RpY2EgeSBlbCBncnVwbyBjb24gZ2FmYXMgdGllbmUgdW5hIGZvcm1hIG3DoXMgYWxhcmdhZGEgeSBhbHRhLCBvY3VwYW5kbyB1biByYW5nbyBkZSB2YWxvcmVzIG1heW9yLg0KDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UgLCBlY2hvPVQsZmlnLndpZHRoPSAzLGZpZy5oZWlnaHQ9IDMsZmlnLmFsaWduPSdjZW50ZXInfQ0Kc2V0d2QoIi4uL1RlcmNlcmEgZW50cmVnYSIpDQpyZWFkLmNzdigiZGF0b3NfZ2VuZXJhZG9zLmNzdiIpLT5kYXRvcw0KDQoNCg0KI0VuIGVsIHNpZ3VpZW50ZSBncsOhZmljbywgc2UgbXVlc3RyYSBjb21vIDAsIGFxdWVsbGFzIGltw6FnZW5lcyBxdWUgbm8gZGViZXLDrWFuIHRlbmVyIGdhZmFzIHkgY29tbyAxLCBhcXVlbGxhcyBxdWUgZGViZXLDrWFuIG1vc3RyYXIgZ2FmYXMuIA0KZ2dwbG90KGRhdG9zLGFlcyh4PWFzLmZhY3RvcihYLjEpLHk9cHJvYikpK2dlb21fdmlvbGluKCkNCmBgYA0KDQpVbmEgbWFuZXJhIGRlIGNvbXByb2JhciBsYXMgZGlmZXJlbmNpYXMgZW50cmUgbG9zIGdydXBvcywgZXMgcmVhbGl6YXIgdW5hIHBydWViYSB0IHBhcmEgbGEgZGlmZXJlbmNpYSBkZSBtZWRpYXMuDQoNClBydWViYSBkZSBoaXDDs3Rlc2lzOg0KDQokSF8wJD0kXG11XzEtXG11XzI9MCQgIHZzICRIXzEkPSRcbXVfMSAtIFxtdV8yIFxuZXEgMCQNCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UgLCBlY2hvPVR9DQojUGFyYSBjb21wcm9iYXIgbG9zIGRhdG9zIHkgbGFzIGRpZmVyZW5jaWFzIHNpZ25pZmljYXRpdmFzLCBwb2RlbW9zIGVqZWN1dGFyIHVuIGFuw6FsaXNpcyBkZSBjb21wYXJhY2nDs24gZGUgcHJvbWVkaW9zIChwcnVlYmEgdCkNCnQudGVzdChkYXRvc1t3aGljaChkYXRvcyRYLjE9PTEpLDJdLGRhdG9zW3doaWNoKGRhdG9zJFguMT09MCksMl0pDQoNCmBgYA0KDQpEZSBhY3VlcmRvIGFsIHJlc3VsdGFkbywgbnVlc3RyYSBzb3NwZWNoYSBlcyBjaWVydGEgZGUgcXVlIGxhcyBtZWRpYXMgZGUgbG9zIHZhbG9yZXMgcGFyYSBhbWJvcyBncnVwb3Mgc29uIGRpZmVyZW50ZXMgeSBlc3RvIHNlIGNvbnRyYXN0YSBjb24gZWwgcC12YWx1ZSBkZSAkMi4yZV57LTE2fSQgYXJyb2phZG8gcG9yIGxhIHBydWViYSwgbG8gY3VhbCBub3MgcGVybWl0ZSByZWNoYXphciBsYSBoaXDDs3Rlc2lzIG51bGEuDQoNCg0KIyMgNC4gVmFsaWRhY2nDs24NCg0KRGVsIG1vZGVsbyBwcmVkaWN0aXZvIHRyYWJhamFkbyBoYXN0YSBhaG9yYSwgc2kgYmllbiBlcyBjaWVydG8gcXVlIHNlIGNhcmFjdGVyaXphIHBvciB0ZW5lciB1bmEgbMOzZ2ljYSBhdmFuemFkYSwgcmVxdWllcmUgZGUgdMOpcm1pbm9zIGVzcGVjw61maWNvcy4gRW4gZXN0ZSBjYXNvLCBlbCBlbnRyZW5hbWllbnRvIHNlIHJlYWxpesOzIGNvbiBpbcOhZ2VuZXMgZnJvbnRhbGVzIChzdHJhaWdodCksIGVudG9uY2VzIGxhIHZhbGlkYWNpw7NuIGRlYmUgaGFjZXJzZSBjb24gZWwgbWlzbW8gdGlwbyBkZSBpbcOhZ2VuZXMuIEVzIGRlY2lyLCBlbCBtb2RlbG8gcHVlZGUgcHJlZGVjaXIgY29uIHVuIGNpZXJ0byBncmFkbyBkZSBlcnJvciwgc2kgc2UgbGUgZXZhbMO6YSBjb24gaW3DoWdlbmVzIHNpbWlsYXJlcyBhIGxhcyBxdWUgc2UgdXNhcm9uIGVuIGVudHJlbmFtaWVudG8uDQoNCkRlbCBjb25qdW50byBkZSB2YWxpZGFjacOzbiBvYnRlbmlkbyBkZSBbVUNJIE1hY2hpbmUgTGVhcm5pbmcgUmVwb3NpdG9yeV0oaHR0cHM6Ly9hcmNoaXZlLmljcy51Y2kuZWR1L21sL2RhdGFzZXRzL0NNVStGYWNlK0ltYWdlcykgdGVuZW1vcyA0MjQgZm90b3MgcXVlIGN1bXBsZW4gY29uIGVzdGEgY2FyYWN0ZXLDrXN0aWNhLCBzZSBhbmFsaXphcsOhbiB1c2FuZG8gbGEgbGlicmVyw61hIFtwaXhtYXBdKGh0dHBzOi8vd3d3LnJkb2N1bWVudGF0aW9uLm9yZy9wYWNrYWdlcy9waXhtYXAvdmVyc2lvbnMvMC40LTEyL3RvcGljcy9wbm0pDQoNCg0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCByZXN1bHRzID0gJ2hpZGUnLCBlY2hvPUZ9DQoNCg0KDQojIA0KIyBsaXN0LmZpbGVzKCktPmxpc3RhDQojIA0KIyAjQ29udmVydGlyIHRvZG8gcGFyYSBwbmcNCiMgZm9yIChpIGluIDE6bGVuZ3RoKGxpc3RhKSl7DQojIHJlYWQucG5tKGxpc3RhW2ldKS0+YQ0KIyBwbmcocGFzdGUwKGxpc3RhW2ldLCIucG5nIikpDQojIHBsb3QoYSkNCiMgZGV2Lm9mZigpDQojIH0NCg0KYGBgDQoNCg0KTGFzIGltw6FnZW5lcyBzZWxlY2Npb25hZGFzIHNlIGNvbnZpcnRpZXJvbiB0b2RhcyBkZSBmb3JtYXRvIHBnbSBhIHBuZy4gVW5hIGltYWdlbiBkZSBlc3RlIGNvbmp1bnRvIGVzOg0KDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UgLCBlY2hvPVQsZmlnLndpZHRoPSAyLGZpZy5oZWlnaHQ9IDIsZmlnLmFsaWduPSdjZW50ZXInfQ0KDQoNCnNldHdkKCIuL1Rlc3RfUE5HIikNCg0KDQojQWJyaXIgbGFzIGltw6FnZW5lcyBkZWwgbW9kZWxvDQpsaXN0LmZpbGVzKCktPmxpc3RhDQoNCmk8LTQNCmltYWdlbmFtZSA8LWxpc3RhW2ldDQoNCmltZyA9IHJlYWRJbWFnZShpbWFnZW5hbWUpDQoNCmltZ19nPC1jaGFubmVsKGltZywiZ3JheSIpDQoNCg0KZGlzcGxheShpbWdfZyxtZXRob2Q9J3Jhc3RlcicpDQoNCmBgYA0KDQoNCg0KVW5hIHZleiBxdWUgc2UgdGllbmVuIHRvZGFzIGxhcyBpbcOhZ2VuZXMgZW4gZm9ybWF0byBwbmcsIHJlcGV0aXJlbW9zIGxvcyBwYXNvcyBpbmljaWFsZXMgcGFyYSBjYWxjdWxhciBsb3MgaGlzdG9ncmFtYXMgZGVsIGdydXBvIGRlIHZhbGlkYWNpw7NuLg0KDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIHJlc3VsdHMgPSAnaGlkZScsIGVjaG89Rn0NCiMgDQojIA0KIyAjQWJyaXIgbGFzIGltw6FnZW5lcyBkZWwgbW9kZWxvDQojIGxpc3QuZmlsZXMoKS0+bGlzdGENCiMgDQojICNWYW1vcyBhIGNyZWFyIHVuYSBjb2x1bW5hIGNvbiBsb3MgY3VhcnRpbGVzIG5lY2VzYXJpb3MgDQojIA0KIyBwYXN0ZTAoc2VxKDAsMC45OTUsMC4wMDUpLCItIixzZXEoMC4wMDUsMSwwLjAwNSkpLT5jdWFydGlsZXMNCiMgDQojIGRhdGEuZnJhbWUodmFsdWVzPWN1YXJ0aWxlcyktPnRlc3QNCiMgDQojIGZvciAoaSBpbiAxOmxlbmd0aChsaXN0YSkpew0KIyBpbWFnZW5hbWUgPC1saXN0YVtpXQ0KIyANCiMgaW1nID0gcmVhZEltYWdlKGltYWdlbmFtZSkNCiMgDQojIGltZ19nPC1jaGFubmVsKGltZywiZ3JheSIpDQojIA0KIyBpbWdfY3JvcCA9IGltZ19nWzI0NToyNjUsMjAwOjI1MF0NCiMgaGlzdChpbWdfY3JvcCxicmVha3M9c2VxKDAsMSwwLjAwNSkpLT5kYWRvcw0KIyBkYXRhLmZyYW1lKHRlc3QsZGFkb3MkY291bnRzKS0+dGVzdA0KIyANCiMgfQ0KIyBjb2xuYW1lcyh0ZXN0KTwtYygidmFsdWVzIixsaXN0YSkNCg0KI0d1YXJkYW5kbyBlc3RlIHJlc3VsdGFkbw0KDQoNCg0KDQpgYGANCg0KRmluYWxtZW50ZSBzZSBwcnVlYmEgeSBzZSByZXZpc2FuIGxvcyByZXN1bHRhZG9zIGRlbCBtb2RlbG8gZWxhYm9yYWRvLg0KDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIHJlc3VsdHMgPSAnaGlkZScsIGVjaG89RiB9DQoNCg0KDQojIA0KIyByZWFkLmNzdigidHJhaW5fMS5jc3YiKS0+dHJhaW4NCiMgDQojIA0KIyANCiMgdHJhaW5bLDFdLT5yb3duYW1lcyh0cmFpbikNCiMgdHJhaW5bLC0xXS0+dHJhaW4NCiMgDQojIG11dGF0ZSh0cmFpbixncnVwbz1pZmVsc2UoZ3J1cG89PSJDb25fR2FmYXMiLDEsMCkpLT50cmFpbjINCiMgDQojIA0KIyANCiMgI2NyZWFjacOzbiBkZWwgbW9kZWxvDQojIHBjcihncnVwb34uLGRhdGE9dHJhaW4yWyxjKDE6NjAsMjAxKV0sc2NhbGU9VFJVRSx2YWxpZGF0aW9uPSJDViIpLT5tb2RlbA0KIyANCiMgI1BydWViYSBjb24gZ3J1cG8gdGVzdA0KIyByZWFkLmNzdigidGVzdC5jc3YiKS0+dGVzdA0KIyB0ZXN0WywyXS0+cm93bmFtZXModGVzdCkgDQojIHRlc3RbLC1jKDE6MildLT50ZXN0DQojIHQodGVzdCktPnRlc3QNCiMgDQojIHByZWRpY3QobW9kZWwsbmV3ZGF0YT10ZXN0WywxOjYwXSxuY29tcD0xMCktPnByb2INCg0KI1NpIHNlIGRlc2VhLCBzZSBwdWVkZW4ganVudGFyIGxvcyBkYXRvcyBjcmVhZG9zIGNvbiBsb3MgdmFsb3JlcyByZWFsZXMgKGNvbHVtbmEgZ3J1cG8gZGUgbGEgdGFibGEgdHJhaW4pDQogIyB3cml0ZS5jc3YocHJvYiwicmVzdWx0YWRvcy5jc3YiKQ0KIA0KDQpgYGANClBhcmEgdmVyIGVsIHBvcmNlbnRhamUgZGUgYWNpZXJ0b3MsIHNlIGFuYWxpemFuIGxvcyB2YWxvcmVzIHJlYWxlcyB2cyBwcmVkaWNob3MgcG9yIGVsIG1vZGVsbywgZXN0byBzZSBoYWNlIGVuIGVsIGFyY2hpdm8gcmVzdWx0YWRvcy5jc3YgeSByZXN1bHRhZG8gKDEpLmNzdiBnZW5lcmFkb3MgYW50ZXJpb21lbnRlLiBFbiBlbCBzaWd1aWVudGUgZ3LDoWZpY28sIHNlIG9ic2VydmFuIHZhbG9yZXMgZGUgZXN0aW1hY2nDs24gbcOhcyBhbHRvcyBlbiBsYXMgZm90b3MgY29uIGdhZmFzLCBjb21vIGVyYSBkZSBlc3BlcmFyc2UgZW4gY29uY29yZGFuY2lhIHBvciBlbCByZXN1bHRhZG8gdmlzdG8gYW50ZXJpb21lbnRlIGVuIGVsIGdyw6FmaWNvIGRlIHZpb2zDrW4uIFNlIG9ic2VydmFuIGFsZ3Vub3MgdmFsb3JlcyBhdMOtcGljb3MuIA0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFICwgZWNobz1ULGZpZy53aWR0aD0gMyxmaWcuaGVpZ2h0PSAzLGZpZy5hbGlnbj0nY2VudGVyJ30NCg0KDQpyZWFkLmNzdigicmVzdWx0YWRvcy5jc3YiKS0+ZGF0b3MNCg0KI2RhdG9zIDwtIHJlYWQuY3N2KCJDOi9Vc2Vycy9MZW5vdm8vRG93bmxvYWRzL3Jlc3VsdGFkb3MuY3N2IikNCg0KcmJpbmQobXV0YXRlKGRhdG9zW2RhdG9zJFggJWxpa2UlICJvcGVuIixdLGdydXBvPSJTaW5fZ2FmYXMiKSwNCiAgICAgIG11dGF0ZShkYXRvc1tkYXRvcyRYICVsaWtlJSAic3VuZ2xhIixdLGdydXBvPSJDb25fZ2FmYXMiKSktPmRhdG9zMg0KDQoNCmdncGxvdChkYXRvczIsYWVzKHg9Z3J1cG8seT1ncnVwby4xMC5jb21wcykpK2dlb21fYm94cGxvdCgpK3NjYWxlX3lfbG9nMTAoKQ0KYGBgDQpDb24gbGEgaW50ZW5jacOzbiBkZSB2aXN1YWxpemFyIGVsIGRlc2VtcGXDsW8gZGUgZXN0ZSBhbGdvcml0bW8ocXVlIHNlIGVtcGxlYSBlbiBhcHJlbmRpemFqZSBzdXBlcnZpc2FkbykgcHJvY2VkZW1vcyBhIHJlYWxpemFyIHVuYSBtYXRyaXogZGUgY29uZnVzacOzbiwgbGEgY3VhbCBlcyB1bmEgaGVycmFtaWVudGEgbXV5IMO6dGlsIHBhcmEgdmFsb3JhciBxdcOpIHRhbiBidWVubyBlcyB1biBtb2RlbG8gZGUgY2xhc2lmaWNhY2nDs24gYmFzYWRvIGVuIGFwcmVuZGl6YWplIGF1dG9tw6F0aWNvLiBFbiBwYXJ0aWN1bGFyLCBzaXJ2ZSBwYXJhIG1vc3RyYXIgZGUgZm9ybWEgZXhwbMOtY2l0YSBjdcOhbmRvIHVuYSBjbGFzZSBlcyBjb25mdW5kaWRhIGNvbiBvdHJhLCBsbyBjdWFsIG5vcyBwZXJtaXRlIHRyYWJhamFyIGRlIGZvcm1hIHNlcGFyYWRhIGNvbiBkaXN0aW50b3MgdGlwb3MgZGUgZXJyb3IuDQoNCmBgYHtyICBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFICwgZWNobz1ULGZpZy53aWR0aD0gMyxmaWcuaGVpZ2h0PSAzLGZpZy5hbGlnbj0nY2VudGVyJ30NCiNkYXRvc213IDwtIHJlYWRfZXhjZWwoIkM6L1VzZXJzL0xlbm92by9Eb3dubG9hZHMvcmVzdWx0YWRvcyAoMSkueGxzeCIpDQoNCg0KDQpkYXRvc213IDwtIHJlYWR4bDo6cmVhZF9leGNlbCgicmVzdWx0YWRvcygxKS54bHN4IikgICANCg0KZGF0b3NtdyRUZcOzcmljbyA8LSBhcy5mYWN0b3IoZGF0b3NtdyRUZcOzcmljbykNCmRhdG9zbXckRXN0aW1hZG8gPC0gYXMuZmFjdG9yKGRhdG9zbXckRXN0aW1hZG8pDQoNCmNtIDwtIGNvbmZfbWF0KGRhdG9zbXcsIEVzdGltYWRvLCBUZcOzcmljbykNCg0KYXV0b3Bsb3QoY20sIHR5cGUgPSAiaGVhdG1hcCIpICsNCiAgc2NhbGVfZmlsbF9ncmFkaWVudChsb3cgPSAicGluayIsIGhpZ2ggPSAiY3lhbiIpDQoNCg0KYGBgDQpMb3MgdmFsb3JlcyBkZSBsYSBkaWFnb25hbCBwcmluY2lwYWwgYT0yMDkoOTguNTglKSB5IGQ9MTczKDgxLjYlKSBjb3JyZXNwb25kZW4gdGFudG8gYSBsb3MgdmFsb3JlcyBlc3RpbWFkb3MgZGUgZm9ybWEgY29ycmVjdGEgcG9yIGVsIG1vZGVsbywgY29tbyBhIGxvcyB2ZXJkYWRlcm9zIHBvc2l0aXZvcyBkID0gMTczKGltw6FnZW5lcyBjb24gbGVudGVzKSwgeSBhIGxvcyB2ZXJkYWRlcm9zIG5lZ2F0aXZvcyBhPSAyMDkoaW3DoWdlbmVzIHNpbiBsZW50ZXMpLg0KDQpMYSBvdHJhIGRpYWdvbmFsLCBwb3IgdGFudG8sIHJlcHJlc2VudGEgbG9zIGNhc29zIGVuIGxvcyBxdWUgZWwgbW9kZWxvIHNlIGhhIGVxdWl2b2NhZG8gKGM9MzkoMTgsNCUpIGZhbHNvcyBuZWdhdGl2b3MsIGI9MygxLjQyJSkgZmFsc29zIHBvc2l0aXZvcykuDQoNCkx1ZWdvIGRlIGhhYmVyIGFuYWxpemFkbyBsYSBtYXRyaXggZGUgY29uZnVzacOzbiwgc2UgcHJvY2VkZSBhIGNhbGN1bGFyIGxhIGV4YWN0aXR1ZCwgbGEgcHJlY2lzacOzbiwgbGEgc2Vuc2liaWxpZGFkIHkgbGEgZXNwZWNpZmljaWRhZC4NCg0KLSBFeGFjdGl0dWQNCkxhIGV4YWN0aXR1ZCAobyDCq2FjY3VyYWN5wqspIHJlcHJlc2VudGEgZWwgcG9yY2VudGFqZSBkZSBwcmVkaWNjaW9uZXMgY29ycmVjdGFzIGZyZW50ZSBhbCB0b3RhbC4gUG9yIHRhbnRvLCBlcyBlbCBjb2NpZW50ZSBlbnRyZSBsb3MgY2Fzb3MgYmllbiBjbGFzaWZpY2Fkb3MgcG9yIGVsIG1vZGVsbyAodmVyZGFkZXJvcyBwb3NpdGl2b3MgeSB2ZXJkYWRlcm9zIG5lZ2F0aXZvcywgZXMgZGVjaXIsIGxvcyB2YWxvcmVzIGVuIGxhIGRpYWdvbmFsIGRlIGxhIG1hdHJpeiBkZSBjb25mdXNpw7NuKSwgeSBsYSBzdW1hIGRlIHRvZG9zIGxvcyBjYXNvcy4NCg0KKDIwOSsxNzMpLygyMDkrMTczKzM5KzMpPTM4Mi80MjQ9IDAuOTAwOTQzNCoxMDAlID0gOTAlDQoNCkVsIHZhbG9yIG9idGVuaWRvIHBhcmEgbGEgZXhhY3RpdHVkIGVuIGVzdGUgbW9kZWxvIGVzIGRlbCA5MCUuDQoNCi0gUHJlY2lzacOzbg0KTGEgcHJlY2lzacOzbiwgKG/igJxwcmVjaXNpb27igJ0pIHNlIHJlZmllcmUgYSBsbyBjZXJjYSBxdWUgZXN0w6EgZWwgcmVzdWx0YWRvIGRlIHVuYSBwcmVkaWNjacOzbiBkZWwgdmFsb3IgdmVyZGFkZXJvLiBQb3IgdGFudG8sIGVzIGVsIGNvY2llbnRlIGVudHJlIGxvcyBjYXNvcyBwb3NpdGl2b3MgYmllbiBjbGFzaWZpY2Fkb3MgcG9yIGVsIG1vZGVsbyB5IGVsIHRvdGFsIGRlIHByZWRpY2Npb25lcyBwb3NpdGl2YXMuDQoNCigxNzMpLygxNzMrMyk9IDAuOTgyOTU0NSoxMDAlID0gOTguMyUNCg0KRWwgdmFsb3Igb2J0ZW5pZG8gcGFyYSBlc3RlIG1vZGVsbyBlcyBkZSB1biA5OC4zJS4gUG9yIHRhbnRvLCBudWVzdHJvIG1vZGVsbyBlcyBtw6FzIHByZWNpc28gcXVlIGV4YWN0by4NCg0KLSBTZW5zaWJpbGlkYWQNCkxhIHNlbnNpYmlsaWRhZCAobyByZWNhbGwpIHJlcHJlc2VudGEgbGEgdGFzYSBkZSB2ZXJkYWRlcm9zIHBvc2l0aXZvcyAoVHJ1ZSBQb3NpdGl2ZSBSYXRlKSDDsyBUUC4gRXMgbGEgcHJvcG9yY2nDs24gZW50cmUgbG9zIGNhc29zIHBvc2l0aXZvcyBiaWVuIGNsYXNpZmljYWRvcyBwb3IgZWwgbW9kZWxvLCByZXNwZWN0byBhbCB0b3RhbCBkZSBwb3NpdGl2b3MsIGVsIGN1YWwgZXMsIGxhIGhhYmlsaWRhZCBkZWwgbW9kZWxvIGRlIGRlY3RldGFyIGxvcyBjYXNvcyByZWxldmFudGVzLg0KDQoxNzMvKDM5KzE3MykgPSAwLjgxNjAzNzcqMTAwJSA9IDgxLjYlDQoNClVuIDgxLjYlIGVzIGNsYXJhbWVudGUgdW4gdmFsb3IgbXV5IGJ1ZW5vIHBhcmEgdW5hIG3DqXRyaWNhLiBQb2RlbW9zIGRlY2lyIHF1ZSBudWVzdHJvIGFsZ29yaXRtbyBkZSBjbGFzaWZpY2FjacOzbiBlcyBzZW5zaWJsZSwgZXMgZGVjaXIsIG5vIHNlIGxlIGVzY2FwYW4gbXVjaG9zIHBvc2l0aXZvcy4NCg0KLSBFc3BlY2lmaWNpZGFkDQpMYSBlc3BlY2lmaWNpZGFkLCBwb3Igc3UgcGFydGUsIGVzIGxhIHRhc2EgZGUgdmVyZGFkZXJvcyBuZWdhdGl2b3MsICjigJx0cnVlIG5lZ2F0aXZlIHJhdGXigJ0pbyBUTi4gRXMgbGEgcHJvcG9yY2nDs24gZW50cmUgbG9zIGNhc29zIG5lZ2F0aXZvcyBiaWVuIGNsYXNpZmljYWRvcyBwb3IgZWwgbW9kZWxvLCByZXNwZWN0byBhbCB0b3RhbCBkZSBuZWdhdGl2b3MuDQoNCjIwOS8oMjA5KzMpID0gMC45ODU4NDkxKjEwMCUgPSA5OC42JQ0KDQpFbiBlc3RlIGNhc28sIGxhIGVzcGVjaWZpY2lkYWQgdGllbmUgdW4gdmFsb3IgbXV5IGJ1ZW5vLiBFc3RvIHNpZ25pZmljYSBxdWUgc3UgY2FwYWNpZGFkIGRlIGRpc2NyaW1pbmFyIGxvcyBjYXNvcyBuZWdhdGl2b3MgZXMgbXV5IGJ1ZW5hLiBFcyBkZWNpciwgZXMgZGlmw61jaWwgb2J0ZW5lciBmYWxzb3MgcG9zaXRpdm9zLg0KDQotIENvbmNsdXNpw7NuDQpDb21vIHNlIHB1ZG8gb2JzZXJ2YXIsIHBhcmEgY2FkYSBtw6l0cmljYSBzZSBvYnR1dmllcm9uIHZhbG9yZXMgYWx0b3MsIGxvIGN1YWwgaW5kaWNhIHF1ZSBlbCBtb2RlbG8gdGllbmUgYWx0YSBwcmVjaXNpw7NuIHkgZXhhY3RpdHVkLCB5IGFkZW3DoXMgZGUgZWxsbyB0aWVuZSB1bmEgYWx0YSBzZW5zaWJpbGlkYWQodGllbmUgYWx0byBwb3JjZW50YWplIGVuIGRldGVjdGFyIGNhc29zIHBvc2l0aXZvcykgeSB1bmEgYWx0YSBlc3BlY2lmaWNpZGFkKHRpZW5lIGFsdG8gcG9yY2VudGFqZSBlbiBkZXRlY3RhciBjYXNvcyBuZWdhdGl2b3MpLg0KDQpOdWV2YW1lbnRlLCBjb21vIHNlIGhpem8gcGFyYSBlbCBjb25qdW50byBkZSBlbnRyZW5hbWllbnRvLCBzZSByZWFsaXphIHVuYSBwcnVlYmEgdCBwYXJhIGxhIGRpZmVyZW5jaWEgZGUgbWVkaWFzIGVudHJlIGxvcyBncnVwb3MuDQoNClBydWViYSBkZSBoaXDDs3Rlc2lzOg0KDQokSF8wJD0kXG11XzEtXG11XzI9MCQgIHZzICRIXzEkPSRcbXVfMSAtIFxtdV8yIFxuZXEgMCQNCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSAsIGVjaG89VH0NCiNQYXJhIGNvbXByb2JhciBsb3MgZGF0b3MgeSBsYXMgZGlmZXJlbmNpYXMgc2lnbmlmaWNhdGl2YXMsIHBvZGVtb3MgZWplY3V0YXIgdW4gYW7DoWxpc2lzIGRlIGNvbXBhcmFjacOzbiBkZSBtZWRpYXMgKHBydWViYSB0KQ0KdC50ZXN0KGRhdG9zMlt3aGljaChkYXRvczIkZ3J1cG89PSJDb25fZ2FmYXMiKSwyXSxkYXRvc1t3aGljaChkYXRvczIkZ3J1cG89PSJTaW5fZ2FmYXMiKSwyXSkNCg0KDQpgYGANCkRlIGFjdWVyZG8gYWwgcmVzdWx0YWRvLCBudWVzdHJhIHNvc3BlY2hhIGVzIGNpZXJ0YSBkZSBxdWUgbGFzIG1lZGlhcyBkZSBsb3MgdmFsb3JlcyBwYXJhIGFtYm9zIGdydXBvcyBzb24gZGlmZXJlbnRlcyB5IGVzdG8gc2UgY29udHJhc3RhIGNvbiBlbCBwLXZhbHVlIGRlIDAuMDAwMTY1NSBhcnJvamFkbyBwb3IgbGEgcHJ1ZWJhLCBsbyBjdWFsIG5vcyBwZXJtaXRlIHJlY2hhemFyIGxhIGhpcMOzdGVzaXMgbnVsYS4NCg0KDQoNCg0KDQojIyA1LiBJbnRlcnJvZ2FudGVzIA0KDQoNCiMjIyA1LjEuIMK/UXXDqSBhZmVjdGEgbGEgY2FwYWNpZGFkIGRlbCBtb2RlbG8gZW4gZWwgY29uanVudG8gZGUgdmFsaWRhY2nDs24/DQoNCi0gTGEgdWJpY2FjacOzbiBkZSBsb3Mgcm9zdHJvcy4gUHJlZmVyaWJsZW1lbnRlIHNlIGRlc2VhIHF1ZSB0b2RhcyBsYXMgaW3DoWdlbmVzIHNlYW4gY2VudHJhZGFzIGVuIGRvbmRlIHNlIGRlbm90ZSBlbCBzZXB0byBuYXNhbC4gSW3DoWdlbmVzIGRlIHBlcmZpbCBvIGNvbiBlbCBtZW50w7NuIGxldmFudGFkbyBubyBzb24gYXB0YXMgcGFyYSBlc3RlIG1vZGVsby4NCg0KLSBMYSBlc3RhZGFyaXphY2nDs24gZGUgbGFzIGltw6FnZW5lcy4gRXhpc3RlbiBmb3RvcyBleHRyYcOxYXMgZW4gZWwgY29uanVudG8gZGUgdmFsaWRhY2nDs24sIGltw6FnZW5lcyBkb2JsZXMgw7MgY29uIHJvc3Ryb3MgYSBtZWRpYXMsIGVzdG8gZ2VuZXJhIHJ1aWRvIHkgZW50b3JwZWNlIGxhIGNhcGFjaWRhZCBkZSBjbGFzaWZpY2FyIGNvcnJlY3RhbWVudGUuDQoNCi0gTGEgdGV6IGRlIGxhcyBwZXJzb25hcyBkZSBsYXMgZm90b2dyYWbDrWFzLiBQdWVkZSBsbGVnYXIgYSBhbHRlcmFyIGVsIG1vZGVsbyBkZWJpZG8gYSAgbGEgY2FudGlkYWQgZGUgcMOteGVsZXMgKFlhIHNlYW4gbWFzIG9zY3Vyb3MgbyBtw6FzIGNsYXJvcykgZGViaWRvIGEgcXVlIG5vIHBvZHLDrWEgZGlzdGluZ3VpciBzaSBsYSBwZXJzb25hIGxsZXZhIGdhZmFzIG8gbm8uDQoNCi0gRXJyb3JlcyBodW1hbm9zLiBFbCBtYWwgcHJvY2VkaW1pZW50byBhIGxhIGhvcmEgZGUgZXNjb2dlciB1bmEgaW1hZ2VuIHkgYWdyZWdhcmxhIGVuIGxhIGNsYXNpZmljYWNpw7NuIGluY29ycmVjdGEuDQoNCi0gRm9ybWF0byBkZSBpbcOhZ2VuZXMuIExhIHRyYW5zZm9ybWFjacOzbiBkZSBpbcOhZ2VuZXMgZGUgcGdtIGEgcG5nIHBhcmEgZWwgZGVzYXJyb2xsbyBkZWwgbW9kZWxvLg0KDQoNCg0KIyMjIDUuMi4gwr9IYXkgYWxndW5hIGNhcmFjdGVyw61zdGljYSBkZSBsYXMgaW3DoWdlbmVzIHF1ZSBtZWpvcmUgbGEgY2FwYWNpZGFkIGRlIHJlc3B1ZXN0YT8NCg0KDQotIENvbW8gc2UgZGlqbyBhbnRlcmlvcm1lbnRlLCBxdWUgdG9kYXMgbGFzIGltw6FnZW5lcyBzZWFuIGZyb250YWxlcy4NCg0KLSBMYSBuaXRpZGV6IGRlIGxhcyBpbcOhZ2VuZXMuDQoNCi0gUGFyYSBlc3RlIG1vZGVsbywgbGFzIHBlcnNvbmFzIGNvbiB0ZXogY2xhcmEgZGViZXLDrWFuIHVzYXIgbGVudGVzIG9zY3Vyb3MgcGFyYSBxdWUgw6lzdGUgcHVlZGEgaWRlbnRpZmljYXIgcXVlIHPDrSBwb3NlZW4gbGVudGVzLg0KDQoNCiMjIDYuIEVubGFjZSBkZSBpbnRlcsOpcw0KDQpVc3RlZCBwdWVkZSByZXZpc2FyIGVsIGPDs2RpZ28geSBsb3MgZGF0b3MgdXNhZG9zIGVuIGVzdGUgcHJveWVjdG8gZW4gbnVlc3RybyByZXBvc2l0b3JpbyBlbiBbR2l0aHViXSgpDQoNCiMjIDcuIFJlZmVyZW5jaWFzDQoNClsxXSBodHRwczovL3d3dy51bS5lcy9nZW9ncmFmL3NpZ211ci90ZW1hcmlvaHRtbC9ub2RlNzQuaHRtbCAgDQpbMl0gaHR0cHM6Ly9lbXByZXNhcy5ibG9ndGhpbmtiaWcuY29tL2NvbW8taW50ZXJwcmV0YXItbGEtbWF0cml6LWRlLWNvbmZ1c2lvbi1lamVtcGxvLXByYWN0aWNvLw0KWzNdIGh0dHBzOi8vYXJjaGl2ZS5pY3MudWNpLmVkdS9kYXRhc2V0LzEyNC9jbXUrZmFjZStpbWFnZXMNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0K