A continuación se muestra un estudio utilizando regresiones logísticas simples y múltiples para predecir una variable categórtica.

El Estudio pretende encontrar un modelo de regresión logística para predecir si un alumno del curso de Matemáticas puede aprobar o no dicho curso utiliando las notas obtenidas del primer, segundo parcial y examen final como variables explicatorias.

Se mostaran seis modelos, en los primeros tres se pretende predecir si el alumno gana o no la clase con una regresión logística simple, asignando una probabilidad en función de la nota del primer parcial, segundo parcial y el examen final.

Despues se mostrará una regresión logística múltiple, para asignar un valor de probabilidad al par de notas formado por las notas del (primer parcial -segundo parcial), (segundo parcial - examen final) y por último (primer parcial - examen final).

Es importante recalcar que estamos asumiendo que todos los examenes tanto parciales como final, tiene la misma ponderación en el curso:

#Cargamos librerias
library(dplyr)
library(ggplot2)

#Cargamos DataSet.
data_set<- read.csv('Research2filePreng.csv')
#View(data_set)

Primero Limparemos y Ajustaremos la Data

#Personalizamos el data frame
data_set<-data_set %>%
  select(Curso02, Seccion03, Carrera04, Fecha06, Ciclo07, 
    Genero10, PrimerParcial11, SegundoParcial13, ExamenFinal15, NotaFinal17) %>%
  filter(Ciclo07 != "NA") #Eliminamos Filas con NA
#Cambio de Nombres de Columna
colnames(data_set)<-c("Curso", "Sec", "Carrera", "Smst", "Año", "Genero", "EP1",
                      "EP2", "EF", "NF") 

El dataset quedaría de esta manera

head(data_set)

En General Cuantos Alumnos Hombres y Mujeres Han Habido:

gen_summary<- data_set %>%
  select(Genero)%>%
  group_by(Genero) %>%
  summarise(Total=sum(Genero))
gen_summary$Genero[1]="Masculino"
gen_summary$Genero[2]="Femenino"
gen_summary

Graficando tenemos:

gen_summary %>%
  ggplot(aes(x=Genero, y=Total)) +
  geom_bar(stat = "identity", fill=c("pink", "light blue"))

Agregamos columna si el alumno aprobo o no (variable para clasificación)

data_set$Aprob<-ifelse((data_set$NF >= 61), yes=1, no=0)

Ahora extraemos la data del curso de matematica que nos interesa:

#Matemática 1
mate1<-data_set %>%
  filter(Curso == "Matematica I") %>%
  filter(Curso != "NA")

Separaremos la data en 70% para entrenamiento y 30% para pruebas:

#Subsets de datos para entrenamiento y pruebas Matematica 1
train_mate1<-mate1[c(1:round(0.7*nrow(mate1))),]
test_mate1<-mate1[c(round(0.7*nrow(mate1)) + 1: nrow(mate1)),]
test_mate1<- test_mate1 %>%
  filter(Sec != "NA")

Modelo 1: Influencia en la Nota del Primer Parcial en Aprobar el Curso:

#Modelo para Matematica 1:
mod1<-glm(data=train_mate1,  formula = Aprob ~ EP1, family = binomial(link="logit"))
#Agregando el Modelo:
xv<-seq(min(train_mate1$EP1), max(train_mate1$EP1), 0.01)
yv<-predict(object=mod1, list(EP1=xv), type="response")
demo_plot1<-data.frame(xv, yv)
names(demo_plot1)<-c("Nota", "Probabilidad")
logitplot_1<-ggplot(data=demo_plot1, aes(x=Nota, y=Probabilidad)) + geom_line()
plotm1 <- train_mate1 %>%
  arrange(EP1) %>%
  ggplot(aes(x=EP1, y=Aprob)) +
  geom_point() + geom_line(data=demo_plot1, aes(x=Nota, y=Probabilidad), color="blue") +
  labs(x = "Nota Parcial 1") +
  labs(y = "Probabilidad")+
  ggtitle("Probabilidad de Ganar el Curso en función de la Nota de Parcial 1") +
  theme_minimal()
#Graficando tenemos.
plotm1

A continuación se muestra la informaación mas importante del modelo 1:
  • Los coeficientes son:
  • El intercepto del modelo es -4.4468757.
  • El coeficiente del modelo es 0.0765029.
  • Podemos decir que ambos coeficientes tienen significacncia en el modelo ya que el valor P de ambos es 210^{-16}.

La ecución del modelo es:

\[ P(\small{EP1})=\frac{1}{1+e^{-[(0.0765)-4.447*(EP1)]}}\]

A continuación mostratemos una predicciones en base al modelo 1 asumiendo que si la probabilidad es mayor a 0.5, entonces el alumno aprobara el curso, el frame de datos esta formado por la nota del primer parcial, la probabilidad obtenida por el modelo, si el alumno aprobo o no el curso y la predicción obtenida con el modelo si la probabilidad es mayor a 0.5:
prediccion1<-predict(object=mod1, newdata=test_mate1, type="response")
resultado1<-data.frame(test_mate1$EP1, (as.data.frame(prediccion1))$prediccion1)
resultado1$Aprobo<- test_mate1$Aprob #Agregamos columna del resultado
names(resultado1)<-c("Nota", "Probabilidad", "Aprobo Clase")
resultado1$Prediccion<- ifelse((resultado1$Probabilidad >= 0.5), yes=1, no=0)
resultado1 %>% arrange(Nota)

Calculando efectividad:

a<-0
i<-1
for(i in (1:nrow(resultado1))){
  if(resultado1$`Aprobo Clase`[[i]] == resultado1$Prediccion[[i]]){
    a<-a+1
  } else {
    a<-a
  }
}
ef1<-a/nrow(resultado1)

El modelo 1 tiene un 0.7625418 de efectividad.

Modelo 2: Influencia en la Nota del Segundo Parcial en Aprobar el Curso

#Modelo para Matematica 1:
mod2<-glm(data=train_mate1,  formula = Aprob ~ EP2, family = binomial(link="logit"))
#Agregando el Modelo:
xv<-seq(min(train_mate1$EP2), max(train_mate1$EP2), 0.01)
yv<-predict(object=mod2, list(EP2=xv), type="response")
demo_plot2<-data.frame(xv, yv)
names(demo_plot2)<-c("Nota", "Probabilidad")
logitplot_2<-ggplot(data=demo_plot2, aes(x=Nota, y=Probabilidad)) + geom_line()
plotm2 <- train_mate1 %>%
  arrange(EP2) %>%
  ggplot(aes(x=EP2, y=Aprob)) +
  geom_point() + geom_line(data=demo_plot2, aes(x=Nota, y=Probabilidad), color="red") +
  labs(x = "Nota Parcial 2") +
  labs(y = "Probabilidad")+
  ggtitle("Probabilidad de Ganar el Curso en función de la Nota de Parcial 2") +
  theme_minimal()
#Graficando tenemos.
plotm2

A continuación se muestra la informaación mas importante del modelo 2:
  • Los Parametros del modelo son:
  • El intercepto del modelo es -5.613524.
  • El coeficiente del modelo es 0.1059263.
  • Podemos decir que ambos parametros tienen significacncia en el modelo ya que el valor P de ambos es 210^{-16}.

La ecución del modelo es:

\[ P(\small{EP2})=\frac{1}{1+e^{-[(0.1059)-5.6135*(EP2)]}}\]

##### A continuación mostratemos una predicciones en base al modelo 2 asumiendo que si la probabilidad es mayor a 0.5, entonces el alumno aprobara el curso, el frame de datos esta formado por la nota del primer segundo, la probabilidad obtenida por el modelo, si el alumno aprobo o no el curso y la predicción obtenida con el modelo si la probabilidad es mayor a 0.5:
prediccion2<-predict(object=mod2, newdata=test_mate1, type="response")
resultado2<-data.frame(test_mate1$EP2, (as.data.frame(prediccion2))$prediccion2)
resultado2$Aprobo<- test_mate1$Aprob #Agregamos columna del resultado
names(resultado2)<-c("Nota", "Probabilidad", "Aprobo Clase")
resultado2$Prediccion<- ifelse((resultado2$Probabilidad >= 0.5), yes=1, no=0)
resultado2 %>% arrange(Nota) ## Ordenamos el arreglo

Calculando efectividad:

a<-0
i<-1
for(i in (1:nrow(resultado2))){
  if(resultado2$`Aprobo Clase`[[i]] == resultado2$Prediccion[[i]]){
    a<-a+1
  } else {
    a<-a
  }
}
ef2<-a/nrow(resultado2)

El modelo 2 tiene un 0.8595318 de efectividad.

Modelo 3: Influencia en la Nota del Examen Final en Aprobar el Curso

#Modelo para Matematica 1:
mod3<-glm(data=train_mate1,  formula = Aprob ~ EF , family = binomial(link="logit"))
#Agregando el Modelo:
xv<-seq(min(train_mate1$EF), max(train_mate1$EF), 0.01)
yv<-predict(object=mod3, list(EF=xv), type="response")
demo_plot3<-data.frame(xv, yv)
names(demo_plot3)<-c("Nota", "Probabilidad")
logitplot_3<-ggplot(data=demo_plot3, aes(x=Nota, y=Probabilidad)) + geom_line()

Graficando Tenemos:

plotm3 <- train_mate1 %>%
  arrange(EF) %>%
  ggplot(aes(x=EF, y=Aprob)) +
  geom_point() + geom_line(data=demo_plot3, aes(x=Nota, y=Probabilidad), color="green") +
  labs(x = "Nota Examen Final") +
  labs(y = "Probabilidad")+
  ggtitle("Probabilidad de Ganar el Curso en función de la Nota del Examen Final") +
  theme_minimal()
plotm3

A continuación se muestra la informaación mas importante del modelo 3:
  • Los Parametros del modelo son:
  • El intercepto del modelo es -3.9102457.
  • El coeficiente del modelo es 0.0933747.
  • Podemos decir que ambos parametros tienen significacncia en el modelo ya que el valor P de ambos es 210^{-16}.

La ecución del modelo es:

\[ P(\small{EP2})=\frac{1}{1+e^{-[(0.0934)-3.9102*(EP2)]}}\] ##### ##### A continuación mostratemos una predicciones en base al modelo 3 asumiendo que si la probabilidad es mayor a 0.5, entonces el alumno aprobara el curso, el frame de datos esta formado por la nota del examen final, la probabilidad obtenida por el modelo, si el alumno aprobo o no el curso y la predicción obtenida con el modelo si la probabilidad es mayor a 0.5:

prediccion3<-predict(object=mod3, newdata=test_mate1, type="response")
resultado3<-data.frame(test_mate1$EF, (as.data.frame(prediccion3))$prediccion3)
resultado3$Aprobo<- test_mate1$Aprob #Agregamos columna del resultado
names(resultado3)<-c("Nota", "Probabilidad", "Aprobo Clase")
resultado3$Prediccion<- ifelse((resultado3$Probabilidad >= 0.5), yes=1, no=0)
resultado3 %>% arrange(Nota) ## Orgenamos el arreglo

Calculando efectividad:

a<-0
i<-1
for(i in (1:nrow(resultado3))){
  if(resultado3$`Aprobo Clase`[[i]] == resultado3$Prediccion[[i]]){
    a<-a+1
  } else {
    a<-a
  }
}
ef3<-a/nrow(resultado3)

El modelo 3 tiene un 0.8528428 de efectividad.

Gráficas de Probabilidad:

xv<-seq(0, 100, 0.01)
yv1<-predict(object=mod1, list(EP1=xv), type="response")
yv2<-predict(object=mod2, list(EP2=xv), type="response")
yv3<-predict(object=mod3, list(EF=xv), type="response")
demo_plot1<-data.frame(xv, yv1)
demo_plot2<-data.frame(xv, yv2)
demo_plot3<-data.frame(xv, yv3)
names(demo_plot1)<-c("Nota", "Probabilidad")
names(demo_plot2)<-c("Nota", "Probabilidad")
names(demo_plot3)<-c("Nota", "Probabilidad")
 test_mate1 %>% 
  ggplot(aes(x=EF, y=Aprob)) +
  geom_blank() + geom_line(data=demo_plot1, aes(x=Nota, y=Probabilidad,  fill=Probabilidad), color="blue")+
  geom_line(data=demo_plot2, aes(x=Nota, y=Probabilidad), color="red") +
  geom_line(data=demo_plot3, aes(x=Nota, y=Probabilidad), color="green") +
  labs(x = "Nota Examen (0-100)") +
  labs(y = "Probabilidad" )+
  ggtitle("Probabilidad de Ganar el Curso en función de la Nota obtenida") +
  scale_x_continuous(breaks=seq(0,100,by=10)) +
  theme_minimal()
Ignoring unknown aesthetics: fill

En la grafica anterior se muestan la regresión logistica simple de cada modelo obtenido para compararlos, donde la azul es la probabilidad de aprobar el curso en función de la nota del primer parcial, la roja es la probabilidad de ganar el curso en función de la nota del segundo parcial, y la verde es la probabilidad de ganar el curso en función de la nota del examen final.

Ahora mostraremos tres modelos de regresión logistica múltiple, utilizando combinaciones de dos examens para encontrar la probabilidad de que un alumno apreuebe/repruebe la clase.

Modelo 4: Influencia del Primer y Segundo Parcial:

library(plotly)
#Modelo Regresión Logistica Multiple, Examen Parcial 1 y Examen Parcial 2:
mod4<-glm(data=train_mate1,  formula = Aprob ~ EP1 + EP2 , family = binomial(link="logit"))
#Definiendo Dominio:
x4<-seq(0, 100, 1)
y4<-seq(0, 100, 1)
domain<-expand.grid(x4,y4)
names(domain)<-c("EP1", "EP2")
z4<-predict(mod4, domain, type="response")
d<-data.frame(z4)
z4<-d$z4
data_xy=data.frame(domain$EP1, domain$EP2, z4)

Graficando Tenemos:

#plot_ly(z = z4, type = "surface", )
p <- plot_ly(x =domain$EP1, y = domain$EP2, z = z4, type = "heatmap", xaxis=domain$EP1, yaxis=domain$EP2,
  colorbar = list(title = "Probabilidad")) %>%
  layout(title = 'Probabilidad de Ganar el Curso vrs Parcial 1 y Parcial 2', xaxis = list(title = 'Nota Parcial 1') , yaxis = list(title = 'Nota Parcial 2'))
p
A continuación se muestra la informaación mas importante del modelo 4:
  • Los Parametros del modelo son:
  • El intercepto del modelo es -8.6398793.
  • El coeficiente del modelo es 0.0582498.
  • El coeficiente del modelo es 0.099308.
  • Podemos decir que ambos parametros tienen significacncia en el modelo ya que el valor P de ambos es 210^{-16}.

La ecución del modelo es:

\[ P(\small{EP1, EP2})=\frac{1}{1+e^{-[-8.6398+0.0582*(EP1)+0.09931*(EP2)]}}\] #####A continuación mostratemos una predicciones en base al modelo 4 asumiendo que si la probabilidad es mayor a 0.5, entonces el alumno aprobara el curso, el frame de datos esta formado por la nota del primer parcial, la nota del segundo parcial, la probabilidad obtenida por el modelo, si el alumno aprobo o no el curso y la predicción obtenida con el modelo si la probabilidad es mayor a 0.5:

prediccion4<-predict(object=mod4, newdata=test_mate1, type="response")
resultado4<-data.frame(test_mate1$EP1, test_mate1$EP2, (as.data.frame(prediccion4))$prediccion4)
resultado4$Aprobo<- test_mate1$Aprob #Agregamos columna del resultado
names(resultado4)<-c("Nota EP1", "Nota EP2", "Probabilidad", "Aprobo Clase")
resultado4$Prediccion<- ifelse((resultado4$Probabilidad >= 0.5), yes=1, no=0)
resultado4 %>% arrange(`Nota EP1`) ## Orgenamos el arreglo

Calculando efectividad:

b<-0
i<-1
for(i in (1:nrow(resultado4))){
  if(resultado4$`Aprobo Clase`[[i]] == resultado4$Prediccion[[i]]){
    b<-b+1
  } else {
    b<-b
  }
}
ef4<-b/nrow(resultado4)

El modelo 4 tiene un 0.8528428 de efectividad.

Modelo 5: Influencia del Primer Parcial y Examen Final:

#Modelo para Matematica 1:
mod5<-glm(data=train_mate1,  formula = Aprob ~ EP1 + EF , family = binomial(link="logit"))
#Agregando el Modelo:
domain<-expand.grid(x4,y4)
x4<-seq(0, 100, 1)
y4<-seq(0, 100, 1)
names(domain)<-c("EP1", "EF")
z4<-predict(mod5, domain, type="response")
d<-data.frame(z4)
z4<-d$z4
data_xy=data.frame(domain$EP1, domain$EF, z4)

Graficando tenemos:

#plot_ly(z = z4, type = "surface", )
p <- plot_ly(x =domain$EP1, y = domain$EF, z = z4, type = "heatmap", xaxis=domain$EP1, yaxis=domain$EF,
  colorbar = list(title = "Probabilidad")) %>%
  layout(title = 'Probabilidad de Ganar el Curso vrs Parcial 1 y Final', xaxis = list(title = 'Nota Parcial 1') , yaxis = list(title = 'Nota Examen Final'))
p
A continuación se muestra la informaación mas importante del modelo 5:
  • Los Parametros del modelo son:
  • El intercepto del modelo es -7.9954419.
  • El coeficiente del modelo es 0.0682128.
  • El coeficiente del modelo es 0.0936558.
  • Podemos decir que ambos parametros tienen significacncia en el modelo ya que el valor P de ambos es \(2*10^{-16}\).

La ecución del modelo es:

\[ P(\small{EP1, EF})=\frac{1}{1+e^{-[-7.9954+0.06821*(EP1)+0.0935*(EF)]}}\] ##### A continuación mostratemos una predicciones en base al modelo 5 asumiendo que si la probabilidad es mayor a 0.5, entonces el alumno aprobara el curso, el frame de datos esta formado por la nota del primer parcial, la nota del examen final, la probabilidad obtenida por el modelo, si el alumno aprobo o no el curso y la predicción obtenida con el modelo si la probabilidad es mayor a 0.5::

prediccion5<-predict(object=mod5, newdata=test_mate1, type="response")
resultado5<-data.frame(test_mate1$EP1, test_mate1$EF, (as.data.frame(prediccion5))$prediccion5)
resultado5$Aprobo<- test_mate1$Aprob #Agregamos columna del resultado
names(resultado5)<-c("Nota EP1", "Nota EF", "Probabilidad", "Aprobo Clase")
resultado5$Prediccion<- ifelse((resultado5$Probabilidad >= 0.5), yes=1, no=0)
resultado5 %>% arrange(`Nota EP1`) ## Orgenamos el arreglo

Calculando efectividad:

a<-0
i<-1
for(i in (1:nrow(resultado5))){
  if(resultado5$`Aprobo Clase`[[i]] == resultado5$Prediccion[[i]]){
    a<-a+1
  } else {
    a<-a
  }
}
ef5<-a/nrow(resultado5)

El modelo 5 tiene un 0.9565217 de efectividad.

Modelo 6: Influencia del Segundo Parcial y Examen Final:

#Modelo para Matematica 1:
mod6<-glm(data=train_mate1,  formula = Aprob ~ EP2 + EF , family = binomial(link="logit"))
#Agregando el Modelo:
domain<-expand.grid(x4,y4)
x4<-seq(0, 100, 1)
y4<-seq(0, 100, 1)
names(domain)<-c("EP2", "EF")
z4<-predict(mod6, domain, type="response")
d<-data.frame(z4)
z4<-d$z4
data_xy=data.frame(domain$EP2, domain$EF, z4)

Graficando Tenemos:

#plot_ly(z = z4, type = "surface", )
p <- plot_ly(x =domain$EP2, y = domain$EF, z = z4, type = "heatmap", xaxis=domain$EP2, yaxis=domain$EF,
  colorbar = list(title = "Probabilidad")) %>%
  layout(title = 'Probabilidad de Ganar el Curso vrs Parcial 2 y Final', xaxis = list(title = 'Nota Parcial 2') , yaxis = list(title = 'Nota Examen Final'))
p
A continuación se muestra la informaación mas importante del modelo 6:
  • Los Parametros del modelo son:
  • El intercepto del modelo es -9.8516948.
  • El coeficiente del modelo es 0.1098379.
  • El coeficiente del modelo es 0.0928167.
  • Podemos decir que ambos parametros tienen significacncia en el modelo ya que el valor P de ambos es \(2*10^{-16}\).

La ecución del modelo es:

\[ P(\small{EP2, EF})=\frac{1}{1+e^{-[-9.8516+0.1098*(EP2)+0.09282*(EF)]}}\] #####A continuación mostratemos una predicciones en base al modelo 4 asumiendo que si la probabilidad es mayor a 0.5, entonces el alumno aprobara el curso, el frame de datos esta formado por la nota del segundo parcial, la nota del examen final, la probabilidad obtenida por el modelo, si el alumno aprobo o no el curso y la predicción obtenida con el modelo si la probabilidad es mayor a 0.5::

prediccion6<-predict(object=mod6, newdata=test_mate1, type="response")
resultado6<-data.frame(test_mate1$EP2, test_mate1$EF, (as.data.frame(prediccion6))$prediccion6)
resultado6$Aprobo<- test_mate1$Aprob #Agregamos columna del resultado
names(resultado6)<-c("Nota EP1", "Nota EF", "Probabilidad", "Aprobo Clase")
resultado6$Prediccion<- ifelse((resultado6$Probabilidad >= 0.5), yes=1, no=0)
resultado6 %>% arrange(`Nota EP1`) ## Orgenamos el arreglo

Calculando efectividad:

a<-0
i<-1
for(i in (1:nrow(resultado6))){
  if(resultado6$`Aprobo Clase`[[i]] == resultado6$Prediccion[[i]]){
    a<-a+1
  } else {
    a<-a
  }
}
ef6<-a/nrow(resultado5)

El modelo 1 tiene un 0.9698997 de efectividad.

Conclisiones:

De los modelos mostrados anteriormente, podemos notar que en general todos los examens tiene una influencia importante en aprobar el curso de matematica o no, sin embargo, es interesante ver que la eficiencia de los modelos que involucran al examen final son superiores, por tanto es vital que los alumnos le pongan mas empeño al examen final, según nuestro estudio.

LS0tDQp0aXRsZTogIlByb3llY3RvIEVjb25vbWV0cu1hIDEiDQphdXRob3I6IFByZW5nIEJpYmENCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQojIyMjIEEgY29udGludWFjafNuIHNlIG11ZXN0cmEgdW4gZXN0dWRpbyB1dGlsaXphbmRvIHJlZ3Jlc2lvbmVzIGxvZ+1zdGljYXMgc2ltcGxlcyB5IG36bHRpcGxlcyBwYXJhIHByZWRlY2lyIHVuYSB2YXJpYWJsZSBjYXRlZ/NydGljYS4NCg0KIyMjIyBFbCBFc3R1ZGlvIHByZXRlbmRlIGVuY29udHJhciB1biBtb2RlbG8gZGUgcmVncmVzafNuIGxvZ+1zdGljYSBwYXJhIHByZWRlY2lyIHNpIHVuIGFsdW1ubyBkZWwgY3Vyc28gZGUgTWF0ZW3hdGljYXMgcHVlZGUgYXByb2JhciBvIG5vIGRpY2hvIGN1cnNvIHV0aWxpYW5kbyBsYXMgbm90YXMgb2J0ZW5pZGFzIGRlbCBwcmltZXIsIHNlZ3VuZG8gcGFyY2lhbCB5IGV4YW1lbiBmaW5hbCBjb21vIHZhcmlhYmxlcyBleHBsaWNhdG9yaWFzLg0KDQojIyMjU2UgbW9zdGFyYW4gc2VpcyBtb2RlbG9zLCBlbiBsb3MgcHJpbWVyb3MgdHJlcyBzZSBwcmV0ZW5kZSBwcmVkZWNpciBzaSBlbCBhbHVtbm8gZ2FuYSBvIG5vIGxhIGNsYXNlIGNvbiB1bmEgcmVncmVzafNuIGxvZ+1zdGljYSBzaW1wbGUsIGFzaWduYW5kbyB1bmEgcHJvYmFiaWxpZGFkIGVuIGZ1bmNp824gZGUgbGEgbm90YSBkZWwgcHJpbWVyIHBhcmNpYWwsIHNlZ3VuZG8gcGFyY2lhbCB5IGVsIGV4YW1lbiBmaW5hbC4NCg0KIyMjI0Rlc3B1ZXMgc2UgbW9zdHJhcuEgdW5hIHJlZ3Jlc2nzbiBsb2ftc3RpY2EgbfpsdGlwbGUsIHBhcmEgYXNpZ25hciB1biB2YWxvciBkZSBwcm9iYWJpbGlkYWQgYWwgcGFyIGRlIG5vdGFzIGZvcm1hZG8gcG9yIGxhcyBub3RhcyBkZWwgKHByaW1lciBwYXJjaWFsIC1zZWd1bmRvIHBhcmNpYWwpLCAoc2VndW5kbyBwYXJjaWFsIC0gZXhhbWVuIGZpbmFsKSB5IHBvciD6bHRpbW8gKHByaW1lciBwYXJjaWFsIC0gZXhhbWVuIGZpbmFsKS4NCg0KIyMjIyBFcyBpbXBvcnRhbnRlIHJlY2FsY2FyIHF1ZSBlc3RhbW9zIGFzdW1pZW5kbyBxdWUgdG9kb3MgbG9zIGV4YW1lbmVzIHRhbnRvIHBhcmNpYWxlcyBjb21vIGZpbmFsLCB0aWVuZSBsYSBtaXNtYSBwb25kZXJhY2nzbiBlbiBlbCBjdXJzbzoNCg0KDQpgYGB7cn0NCiNDYXJnYW1vcyBsaWJyZXJpYXMNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KGdncGxvdDIpDQoNCiNDYXJnYW1vcyBEYXRhU2V0Lg0KZGF0YV9zZXQ8LSByZWFkLmNzdignUmVzZWFyY2gyZmlsZVByZW5nLmNzdicpDQojVmlldyhkYXRhX3NldCkNCmBgYA0KDQojIyMjIFByaW1lcm8gTGltcGFyZW1vcyB5IEFqdXN0YXJlbW9zIGxhIERhdGENCmBgYHtyfQ0KI1BlcnNvbmFsaXphbW9zIGVsIGRhdGEgZnJhbWUNCmRhdGFfc2V0PC1kYXRhX3NldCAlPiUNCiAgc2VsZWN0KEN1cnNvMDIsIFNlY2Npb24wMywgQ2FycmVyYTA0LCBGZWNoYTA2LCBDaWNsbzA3LCANCiAgICBHZW5lcm8xMCwgUHJpbWVyUGFyY2lhbDExLCBTZWd1bmRvUGFyY2lhbDEzLCBFeGFtZW5GaW5hbDE1LCBOb3RhRmluYWwxNykgJT4lDQogIGZpbHRlcihDaWNsbzA3ICE9ICJOQSIpICNFbGltaW5hbW9zIEZpbGFzIGNvbiBOQQ0KDQojQ2FtYmlvIGRlIE5vbWJyZXMgZGUgQ29sdW1uYQ0KY29sbmFtZXMoZGF0YV9zZXQpPC1jKCJDdXJzbyIsICJTZWMiLCAiQ2FycmVyYSIsICJTbXN0IiwgIkHxbyIsICJHZW5lcm8iLCAiRVAxIiwNCiAgICAgICAgICAgICAgICAgICAgICAiRVAyIiwgIkVGIiwgIk5GIikgDQpgYGANCiMjIyMgRWwgZGF0YXNldCBxdWVkYXLtYSBkZSBlc3RhIG1hbmVyYQ0KYGBge3J9DQpoZWFkKGRhdGFfc2V0KQ0KYGBgDQojIyMjIEVuIEdlbmVyYWwgQ3VhbnRvcyBBbHVtbm9zIEhvbWJyZXMgeSBNdWplcmVzIEhhbiBIYWJpZG86DQpgYGB7cn0NCmdlbl9zdW1tYXJ5PC0gZGF0YV9zZXQgJT4lDQogIHNlbGVjdChHZW5lcm8pJT4lDQogIGdyb3VwX2J5KEdlbmVybykgJT4lDQogIHN1bW1hcmlzZShUb3RhbD1zdW0oR2VuZXJvKSkNCmdlbl9zdW1tYXJ5JEdlbmVyb1sxXT0iTWFzY3VsaW5vIg0KZ2VuX3N1bW1hcnkkR2VuZXJvWzJdPSJGZW1lbmlubyINCmdlbl9zdW1tYXJ5DQpgYGANCiMjIyMgR3JhZmljYW5kbyB0ZW5lbW9zOg0KYGBge3J9DQpnZW5fc3VtbWFyeSAlPiUNCiAgZ2dwbG90KGFlcyh4PUdlbmVybywgeT1Ub3RhbCkpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIGZpbGw9YygicGluayIsICJsaWdodCBibHVlIikpDQpgYGANCg0KIyMjIyBBZ3JlZ2Ftb3MgY29sdW1uYSBzaSBlbCBhbHVtbm8gYXByb2JvIG8gbm8gKHZhcmlhYmxlIHBhcmEgY2xhc2lmaWNhY2nzbikNCmBgYHtyfQ0KZGF0YV9zZXQkQXByb2I8LWlmZWxzZSgoZGF0YV9zZXQkTkYgPj0gNjEpLCB5ZXM9MSwgbm89MCkNCmBgYA0KDQojIyMjIEFob3JhIGV4dHJhZW1vcyBsYSBkYXRhIGRlbCBjdXJzbyBkZSBtYXRlbWF0aWNhIHF1ZSBub3MgaW50ZXJlc2E6DQpgYGB7cn0NCiNNYXRlbeF0aWNhIDENCm1hdGUxPC1kYXRhX3NldCAlPiUNCiAgZmlsdGVyKEN1cnNvID09ICJNYXRlbWF0aWNhIEkiKSAlPiUNCiAgZmlsdGVyKEN1cnNvICE9ICJOQSIpDQpgYGANCg0KIyMjIFNlcGFyYXJlbW9zIGxhIGRhdGEgZW4gNzAlIHBhcmEgZW50cmVuYW1pZW50byB5IDMwJSBwYXJhIHBydWViYXM6DQpgYGB7cn0NCiNTdWJzZXRzIGRlIGRhdG9zIHBhcmEgZW50cmVuYW1pZW50byB5IHBydWViYXMgTWF0ZW1hdGljYSAxDQp0cmFpbl9tYXRlMTwtbWF0ZTFbYygxOnJvdW5kKDAuNypucm93KG1hdGUxKSkpLF0NCnRlc3RfbWF0ZTE8LW1hdGUxW2Mocm91bmQoMC43Km5yb3cobWF0ZTEpKSArIDE6IG5yb3cobWF0ZTEpKSxdDQp0ZXN0X21hdGUxPC0gdGVzdF9tYXRlMSAlPiUNCiAgZmlsdGVyKFNlYyAhPSAiTkEiKQ0KYGBgDQoNCiMjI01vZGVsbyAxOiBJbmZsdWVuY2lhIGVuIGxhIE5vdGEgZGVsIFByaW1lciBQYXJjaWFsIGVuIEFwcm9iYXIgZWwgQ3Vyc286DQpgYGB7ciwgZWNobz1UUlVFfQ0KI01vZGVsbyBwYXJhIE1hdGVtYXRpY2EgMToNCm1vZDE8LWdsbShkYXRhPXRyYWluX21hdGUxLCAgZm9ybXVsYSA9IEFwcm9iIH4gRVAxLCBmYW1pbHkgPSBiaW5vbWlhbChsaW5rPSJsb2dpdCIpKQ0KDQojQWdyZWdhbmRvIGVsIE1vZGVsbzoNCnh2PC1zZXEobWluKHRyYWluX21hdGUxJEVQMSksIG1heCh0cmFpbl9tYXRlMSRFUDEpLCAwLjAxKQ0KeXY8LXByZWRpY3Qob2JqZWN0PW1vZDEsIGxpc3QoRVAxPXh2KSwgdHlwZT0icmVzcG9uc2UiKQ0KZGVtb19wbG90MTwtZGF0YS5mcmFtZSh4diwgeXYpDQpuYW1lcyhkZW1vX3Bsb3QxKTwtYygiTm90YSIsICJQcm9iYWJpbGlkYWQiKQ0KbG9naXRwbG90XzE8LWdncGxvdChkYXRhPWRlbW9fcGxvdDEsIGFlcyh4PU5vdGEsIHk9UHJvYmFiaWxpZGFkKSkgKyBnZW9tX2xpbmUoKQ0KDQpwbG90bTEgPC0gdHJhaW5fbWF0ZTEgJT4lDQogIGFycmFuZ2UoRVAxKSAlPiUNCiAgZ2dwbG90KGFlcyh4PUVQMSwgeT1BcHJvYikpICsNCiAgZ2VvbV9wb2ludCgpICsgZ2VvbV9saW5lKGRhdGE9ZGVtb19wbG90MSwgYWVzKHg9Tm90YSwgeT1Qcm9iYWJpbGlkYWQpLCBjb2xvcj0iYmx1ZSIpICsNCiAgbGFicyh4ID0gIk5vdGEgUGFyY2lhbCAxIikgKw0KICBsYWJzKHkgPSAiUHJvYmFiaWxpZGFkIikrDQogIGdndGl0bGUoIlByb2JhYmlsaWRhZCBkZSBHYW5hciBlbCBDdXJzbyBlbiBmdW5jafNuIGRlIGxhIE5vdGEgZGUgUGFyY2lhbCAxIikgKw0KICB0aGVtZV9taW5pbWFsKCkNCg0KI0dyYWZpY2FuZG8gdGVuZW1vcy4NCnBsb3RtMQ0KYGBgDQojIyMjIyBBIGNvbnRpbnVhY2nzbiBzZSBtdWVzdHJhIGxhIGluZm9ybWFhY2nzbiBtYXMgaW1wb3J0YW50ZSBkZWwgbW9kZWxvIDE6DQoqIExvcyBjb2VmaWNpZW50ZXMgc29uOg0KICArIEVsIGludGVyY2VwdG8gZGVsIG1vZGVsbyBlcyAqKmByIGNvZWYobW9kMSlbWzFdXWAqKi4NCiAgKyBFbCBjb2VmaWNpZW50ZSBkZWwgbW9kZWxvIGVzICoqYHIgY29lZihtb2QxKVtbMl1dYCoqLg0KKiBQb2RlbW9zIGRlY2lyIHF1ZSBhbWJvcyBjb2VmaWNpZW50ZXMgdGllbmVuIHNpZ25pZmljYWNuY2lhIGVuIGVsIG1vZGVsbyB5YSBxdWUgZWwgdmFsb3IgUCBkZSBhbWJvcyBlcyAqKmByIDJlLTE2YCoqLg0KDQojIyMjIExhIGVjdWNp824gZGVsIG1vZGVsbyBlczoNCiQkIFAoXHNtYWxse0VQMX0pPVxmcmFjezF9ezErZV57LVsoMC4wNzY1KS00LjQ0NyooRVAxKV19fSQkIA0KDQojIyMjIyBBIGNvbnRpbnVhY2nzbiBtb3N0cmF0ZW1vcyB1bmEgcHJlZGljY2lvbmVzIGVuIGJhc2UgYWwgbW9kZWxvIDEgYXN1bWllbmRvIHF1ZSBzaSBsYSBwcm9iYWJpbGlkYWQgZXMgbWF5b3IgYSAqKjAuNSoqLCBlbnRvbmNlcyBlbCBhbHVtbm8gYXByb2JhcmEgZWwgY3Vyc28sIGVsIGZyYW1lIGRlIGRhdG9zIGVzdGEgZm9ybWFkbyBwb3IgbGEgbm90YSBkZWwgcHJpbWVyIHBhcmNpYWwsIGxhIHByb2JhYmlsaWRhZCBvYnRlbmlkYSBwb3IgZWwgbW9kZWxvLCBzaSBlbCBhbHVtbm8gYXByb2JvIG8gbm8gZWwgY3Vyc28geSBsYSBwcmVkaWNjafNuIG9idGVuaWRhIGNvbiBlbCBtb2RlbG8gc2kgbGEgcHJvYmFiaWxpZGFkIGVzIG1heW9yIGEgMC41Og0KYGBge3J9DQpwcmVkaWNjaW9uMTwtcHJlZGljdChvYmplY3Q9bW9kMSwgbmV3ZGF0YT10ZXN0X21hdGUxLCB0eXBlPSJyZXNwb25zZSIpDQpyZXN1bHRhZG8xPC1kYXRhLmZyYW1lKHRlc3RfbWF0ZTEkRVAxLCAoYXMuZGF0YS5mcmFtZShwcmVkaWNjaW9uMSkpJHByZWRpY2Npb24xKQ0KcmVzdWx0YWRvMSRBcHJvYm88LSB0ZXN0X21hdGUxJEFwcm9iICNBZ3JlZ2Ftb3MgY29sdW1uYSBkZWwgcmVzdWx0YWRvDQpuYW1lcyhyZXN1bHRhZG8xKTwtYygiTm90YSIsICJQcm9iYWJpbGlkYWQiLCAiQXByb2JvIENsYXNlIikNCnJlc3VsdGFkbzEkUHJlZGljY2lvbjwtIGlmZWxzZSgocmVzdWx0YWRvMSRQcm9iYWJpbGlkYWQgPj0gMC41KSwgeWVzPTEsIG5vPTApDQpyZXN1bHRhZG8xICU+JSBhcnJhbmdlKE5vdGEpDQpgYGANCiMjIyNDYWxjdWxhbmRvIGVmZWN0aXZpZGFkOg0KYGBge3J9DQphPC0wDQppPC0xDQoNCmZvcihpIGluICgxOm5yb3cocmVzdWx0YWRvMSkpKXsNCiAgaWYocmVzdWx0YWRvMSRgQXByb2JvIENsYXNlYFtbaV1dID09IHJlc3VsdGFkbzEkUHJlZGljY2lvbltbaV1dKXsNCiAgICBhPC1hKzENCiAgfSBlbHNlIHsNCiAgICBhPC1hDQogIH0NCn0NCmVmMTwtYS9ucm93KHJlc3VsdGFkbzEpDQpgYGANCiMjIyMgRWwgbW9kZWxvIDEgdGllbmUgdW4gKipgciBlZjFgKiogZGUgZWZlY3RpdmlkYWQuDQoNCg0KIyMjTW9kZWxvIDI6IEluZmx1ZW5jaWEgZW4gbGEgTm90YSBkZWwgU2VndW5kbyBQYXJjaWFsIGVuIEFwcm9iYXIgZWwgQ3Vyc28gDQpgYGB7cn0NCiNNb2RlbG8gcGFyYSBNYXRlbWF0aWNhIDE6DQptb2QyPC1nbG0oZGF0YT10cmFpbl9tYXRlMSwgIGZvcm11bGEgPSBBcHJvYiB+IEVQMiwgZmFtaWx5ID0gYmlub21pYWwobGluaz0ibG9naXQiKSkNCg0KI0FncmVnYW5kbyBlbCBNb2RlbG86DQp4djwtc2VxKG1pbih0cmFpbl9tYXRlMSRFUDIpLCBtYXgodHJhaW5fbWF0ZTEkRVAyKSwgMC4wMSkNCnl2PC1wcmVkaWN0KG9iamVjdD1tb2QyLCBsaXN0KEVQMj14diksIHR5cGU9InJlc3BvbnNlIikNCmRlbW9fcGxvdDI8LWRhdGEuZnJhbWUoeHYsIHl2KQ0KbmFtZXMoZGVtb19wbG90Mik8LWMoIk5vdGEiLCAiUHJvYmFiaWxpZGFkIikNCmxvZ2l0cGxvdF8yPC1nZ3Bsb3QoZGF0YT1kZW1vX3Bsb3QyLCBhZXMoeD1Ob3RhLCB5PVByb2JhYmlsaWRhZCkpICsgZ2VvbV9saW5lKCkNCg0KcGxvdG0yIDwtIHRyYWluX21hdGUxICU+JQ0KICBhcnJhbmdlKEVQMikgJT4lDQogIGdncGxvdChhZXMoeD1FUDIsIHk9QXByb2IpKSArDQogIGdlb21fcG9pbnQoKSArIGdlb21fbGluZShkYXRhPWRlbW9fcGxvdDIsIGFlcyh4PU5vdGEsIHk9UHJvYmFiaWxpZGFkKSwgY29sb3I9InJlZCIpICsNCiAgbGFicyh4ID0gIk5vdGEgUGFyY2lhbCAyIikgKw0KICBsYWJzKHkgPSAiUHJvYmFiaWxpZGFkIikrDQogIGdndGl0bGUoIlByb2JhYmlsaWRhZCBkZSBHYW5hciBlbCBDdXJzbyBlbiBmdW5jafNuIGRlIGxhIE5vdGEgZGUgUGFyY2lhbCAyIikgKw0KICB0aGVtZV9taW5pbWFsKCkNCg0KI0dyYWZpY2FuZG8gdGVuZW1vcy4NCnBsb3RtMg0KYGBgDQojIyMjIyBBIGNvbnRpbnVhY2nzbiBzZSBtdWVzdHJhIGxhIGluZm9ybWFhY2nzbiBtYXMgaW1wb3J0YW50ZSBkZWwgbW9kZWxvIDI6DQoqIExvcyBQYXJhbWV0cm9zIGRlbCBtb2RlbG8gc29uOg0KICArIEVsIGludGVyY2VwdG8gZGVsIG1vZGVsbyBlcyAqKmByIGNvZWYobW9kMilbWzFdXWAqKi4NCiAgKyBFbCBjb2VmaWNpZW50ZSBkZWwgbW9kZWxvIGVzICoqYHIgY29lZihtb2QyKVtbMl1dYCoqLg0KKiBQb2RlbW9zIGRlY2lyIHF1ZSBhbWJvcyBwYXJhbWV0cm9zIHRpZW5lbiBzaWduaWZpY2FjbmNpYSBlbiBlbCBtb2RlbG8geWEgcXVlIGVsIHZhbG9yIFAgZGUgYW1ib3MgZXMgKipgciAyZS0xNmAqKi4NCg0KIyMjIyBMYSBlY3VjafNuIGRlbCBtb2RlbG8gZXM6DQokJCBQKFxzbWFsbHtFUDJ9KT1cZnJhY3sxfXsxK2Veey1bKDAuMTA1OSktNS42MTM1KihFUDIpXX19JCQgDQoNCiMjIyMjICMjIyMjIEEgY29udGludWFjafNuIG1vc3RyYXRlbW9zIHVuYSBwcmVkaWNjaW9uZXMgZW4gYmFzZSBhbCBtb2RlbG8gMiBhc3VtaWVuZG8gcXVlIHNpIGxhIHByb2JhYmlsaWRhZCBlcyBtYXlvciBhICoqMC41KiosIGVudG9uY2VzIGVsIGFsdW1ubyBhcHJvYmFyYSBlbCBjdXJzbywgZWwgZnJhbWUgZGUgZGF0b3MgZXN0YSBmb3JtYWRvIHBvciBsYSBub3RhIGRlbCBwcmltZXIgc2VndW5kbywgbGEgcHJvYmFiaWxpZGFkIG9idGVuaWRhIHBvciBlbCBtb2RlbG8sIHNpIGVsIGFsdW1ubyBhcHJvYm8gbyBubyBlbCBjdXJzbyB5IGxhIHByZWRpY2Np824gb2J0ZW5pZGEgY29uIGVsIG1vZGVsbyBzaSBsYSBwcm9iYWJpbGlkYWQgZXMgbWF5b3IgYSAwLjU6DQpgYGB7cn0NCnByZWRpY2Npb24yPC1wcmVkaWN0KG9iamVjdD1tb2QyLCBuZXdkYXRhPXRlc3RfbWF0ZTEsIHR5cGU9InJlc3BvbnNlIikNCnJlc3VsdGFkbzI8LWRhdGEuZnJhbWUodGVzdF9tYXRlMSRFUDIsIChhcy5kYXRhLmZyYW1lKHByZWRpY2Npb24yKSkkcHJlZGljY2lvbjIpDQpyZXN1bHRhZG8yJEFwcm9ibzwtIHRlc3RfbWF0ZTEkQXByb2IgI0FncmVnYW1vcyBjb2x1bW5hIGRlbCByZXN1bHRhZG8NCm5hbWVzKHJlc3VsdGFkbzIpPC1jKCJOb3RhIiwgIlByb2JhYmlsaWRhZCIsICJBcHJvYm8gQ2xhc2UiKQ0KcmVzdWx0YWRvMiRQcmVkaWNjaW9uPC0gaWZlbHNlKChyZXN1bHRhZG8yJFByb2JhYmlsaWRhZCA+PSAwLjUpLCB5ZXM9MSwgbm89MCkNCnJlc3VsdGFkbzIgJT4lIGFycmFuZ2UoTm90YSkgIyMgT3JkZW5hbW9zIGVsIGFycmVnbG8NCmBgYA0KIyMjI0NhbGN1bGFuZG8gZWZlY3RpdmlkYWQ6DQpgYGB7cn0NCmE8LTANCmk8LTENCg0KZm9yKGkgaW4gKDE6bnJvdyhyZXN1bHRhZG8yKSkpew0KICBpZihyZXN1bHRhZG8yJGBBcHJvYm8gQ2xhc2VgW1tpXV0gPT0gcmVzdWx0YWRvMiRQcmVkaWNjaW9uW1tpXV0pew0KICAgIGE8LWErMQ0KICB9IGVsc2Ugew0KICAgIGE8LWENCiAgfQ0KfQ0KZWYyPC1hL25yb3cocmVzdWx0YWRvMikNCmBgYA0KIyMjIyBFbCBtb2RlbG8gMiB0aWVuZSB1biAqKmByIGVmMmAqKiBkZSBlZmVjdGl2aWRhZC4NCg0KDQojIyNNb2RlbG8gMzogSW5mbHVlbmNpYSBlbiBsYSBOb3RhIGRlbCBFeGFtZW4gRmluYWwgZW4gQXByb2JhciBlbCBDdXJzbyANCmBgYHtyfQ0KI01vZGVsbyBwYXJhIE1hdGVtYXRpY2EgMToNCm1vZDM8LWdsbShkYXRhPXRyYWluX21hdGUxLCAgZm9ybXVsYSA9IEFwcm9iIH4gRUYgLCBmYW1pbHkgPSBiaW5vbWlhbChsaW5rPSJsb2dpdCIpKQ0KDQojQWdyZWdhbmRvIGVsIE1vZGVsbzoNCnh2PC1zZXEobWluKHRyYWluX21hdGUxJEVGKSwgbWF4KHRyYWluX21hdGUxJEVGKSwgMC4wMSkNCnl2PC1wcmVkaWN0KG9iamVjdD1tb2QzLCBsaXN0KEVGPXh2KSwgdHlwZT0icmVzcG9uc2UiKQ0KZGVtb19wbG90MzwtZGF0YS5mcmFtZSh4diwgeXYpDQpuYW1lcyhkZW1vX3Bsb3QzKTwtYygiTm90YSIsICJQcm9iYWJpbGlkYWQiKQ0KbG9naXRwbG90XzM8LWdncGxvdChkYXRhPWRlbW9fcGxvdDMsIGFlcyh4PU5vdGEsIHk9UHJvYmFiaWxpZGFkKSkgKyBnZW9tX2xpbmUoKQ0KYGBgDQoNCiMjIyNHcmFmaWNhbmRvIFRlbmVtb3M6DQpgYGB7ciB9DQpwbG90bTMgPC0gdHJhaW5fbWF0ZTEgJT4lDQogIGFycmFuZ2UoRUYpICU+JQ0KICBnZ3Bsb3QoYWVzKHg9RUYsIHk9QXByb2IpKSArDQogIGdlb21fcG9pbnQoKSArIGdlb21fbGluZShkYXRhPWRlbW9fcGxvdDMsIGFlcyh4PU5vdGEsIHk9UHJvYmFiaWxpZGFkKSwgY29sb3I9ImdyZWVuIikgKw0KICBsYWJzKHggPSAiTm90YSBFeGFtZW4gRmluYWwiKSArDQogIGxhYnMoeSA9ICJQcm9iYWJpbGlkYWQiKSsNCiAgZ2d0aXRsZSgiUHJvYmFiaWxpZGFkIGRlIEdhbmFyIGVsIEN1cnNvIGVuIGZ1bmNp824gZGUgbGEgTm90YSBkZWwgRXhhbWVuIEZpbmFsIikgKw0KICB0aGVtZV9taW5pbWFsKCkNCnBsb3RtMw0KYGBgDQojIyMjIyBBIGNvbnRpbnVhY2nzbiBzZSBtdWVzdHJhIGxhIGluZm9ybWFhY2nzbiBtYXMgaW1wb3J0YW50ZSBkZWwgbW9kZWxvIDM6DQoqIExvcyBQYXJhbWV0cm9zIGRlbCBtb2RlbG8gc29uOg0KICArIEVsIGludGVyY2VwdG8gZGVsIG1vZGVsbyBlcyAqKmByIGNvZWYobW9kMylbWzFdXWAqKi4NCiAgKyBFbCBjb2VmaWNpZW50ZSBkZWwgbW9kZWxvIGVzICoqYHIgY29lZihtb2QzKVtbMl1dYCoqLg0KKiBQb2RlbW9zIGRlY2lyIHF1ZSBhbWJvcyBwYXJhbWV0cm9zIHRpZW5lbiBzaWduaWZpY2FjbmNpYSBlbiBlbCBtb2RlbG8geWEgcXVlIGVsIHZhbG9yIFAgZGUgYW1ib3MgZXMgKipgciAyZS0xNmAqKi4NCg0KIyMjIyBMYSBlY3VjafNuIGRlbCBtb2RlbG8gZXM6DQokJCBQKFxzbWFsbHtFUDJ9KT1cZnJhY3sxfXsxK2Veey1bKDAuMDkzNCktMy45MTAyKihFUDIpXX19JCQNCiMjIyMjICMjIyMjIEEgY29udGludWFjafNuIG1vc3RyYXRlbW9zIHVuYSBwcmVkaWNjaW9uZXMgZW4gYmFzZSBhbCBtb2RlbG8gMyBhc3VtaWVuZG8gcXVlIHNpIGxhIHByb2JhYmlsaWRhZCBlcyBtYXlvciBhICoqMC41KiosIGVudG9uY2VzIGVsIGFsdW1ubyBhcHJvYmFyYSBlbCBjdXJzbywgZWwgZnJhbWUgZGUgZGF0b3MgZXN0YSBmb3JtYWRvIHBvciBsYSBub3RhIGRlbCBleGFtZW4gZmluYWwsIGxhIHByb2JhYmlsaWRhZCBvYnRlbmlkYSBwb3IgZWwgbW9kZWxvLCBzaSBlbCBhbHVtbm8gYXByb2JvIG8gbm8gZWwgY3Vyc28geSBsYSBwcmVkaWNjafNuIG9idGVuaWRhIGNvbiBlbCBtb2RlbG8gc2kgbGEgcHJvYmFiaWxpZGFkIGVzIG1heW9yIGEgMC41Og0KYGBge3J9DQpwcmVkaWNjaW9uMzwtcHJlZGljdChvYmplY3Q9bW9kMywgbmV3ZGF0YT10ZXN0X21hdGUxLCB0eXBlPSJyZXNwb25zZSIpDQpyZXN1bHRhZG8zPC1kYXRhLmZyYW1lKHRlc3RfbWF0ZTEkRUYsIChhcy5kYXRhLmZyYW1lKHByZWRpY2Npb24zKSkkcHJlZGljY2lvbjMpDQpyZXN1bHRhZG8zJEFwcm9ibzwtIHRlc3RfbWF0ZTEkQXByb2IgI0FncmVnYW1vcyBjb2x1bW5hIGRlbCByZXN1bHRhZG8NCm5hbWVzKHJlc3VsdGFkbzMpPC1jKCJOb3RhIiwgIlByb2JhYmlsaWRhZCIsICJBcHJvYm8gQ2xhc2UiKQ0KcmVzdWx0YWRvMyRQcmVkaWNjaW9uPC0gaWZlbHNlKChyZXN1bHRhZG8zJFByb2JhYmlsaWRhZCA+PSAwLjUpLCB5ZXM9MSwgbm89MCkNCnJlc3VsdGFkbzMgJT4lIGFycmFuZ2UoTm90YSkgIyMgT3JnZW5hbW9zIGVsIGFycmVnbG8NCmBgYA0KIyMjI0NhbGN1bGFuZG8gZWZlY3RpdmlkYWQ6DQpgYGB7cn0NCmE8LTANCmk8LTENCg0KZm9yKGkgaW4gKDE6bnJvdyhyZXN1bHRhZG8zKSkpew0KICBpZihyZXN1bHRhZG8zJGBBcHJvYm8gQ2xhc2VgW1tpXV0gPT0gcmVzdWx0YWRvMyRQcmVkaWNjaW9uW1tpXV0pew0KICAgIGE8LWErMQ0KICB9IGVsc2Ugew0KICAgIGE8LWENCiAgfQ0KfQ0KZWYzPC1hL25yb3cocmVzdWx0YWRvMykNCmBgYA0KIyMjIyBFbCBtb2RlbG8gMyB0aWVuZSB1biAqKmByIGVmM2AqKiBkZSBlZmVjdGl2aWRhZC4NCg0KDQoNCiMjI0dy4WZpY2FzIGRlIFByb2JhYmlsaWRhZDoNCmBgYHtyfQ0KDQp4djwtc2VxKDAsIDEwMCwgMC4wMSkNCnl2MTwtcHJlZGljdChvYmplY3Q9bW9kMSwgbGlzdChFUDE9eHYpLCB0eXBlPSJyZXNwb25zZSIpDQp5djI8LXByZWRpY3Qob2JqZWN0PW1vZDIsIGxpc3QoRVAyPXh2KSwgdHlwZT0icmVzcG9uc2UiKQ0KeXYzPC1wcmVkaWN0KG9iamVjdD1tb2QzLCBsaXN0KEVGPXh2KSwgdHlwZT0icmVzcG9uc2UiKQ0KDQpkZW1vX3Bsb3QxPC1kYXRhLmZyYW1lKHh2LCB5djEpDQpkZW1vX3Bsb3QyPC1kYXRhLmZyYW1lKHh2LCB5djIpDQpkZW1vX3Bsb3QzPC1kYXRhLmZyYW1lKHh2LCB5djMpDQoNCm5hbWVzKGRlbW9fcGxvdDEpPC1jKCJOb3RhIiwgIlByb2JhYmlsaWRhZCIpDQpuYW1lcyhkZW1vX3Bsb3QyKTwtYygiTm90YSIsICJQcm9iYWJpbGlkYWQiKQ0KbmFtZXMoZGVtb19wbG90Myk8LWMoIk5vdGEiLCAiUHJvYmFiaWxpZGFkIikNCmBgYA0KDQoNCmBgYHtyIGZpZy53aWR0aD02LCBmaWcuaGVpZ2h0PTV9DQogdGVzdF9tYXRlMSAlPiUgDQogIGdncGxvdChhZXMoeD1FRiwgeT1BcHJvYikpICsNCiAgZ2VvbV9ibGFuaygpICsgZ2VvbV9saW5lKGRhdGE9ZGVtb19wbG90MSwgYWVzKHg9Tm90YSwgeT1Qcm9iYWJpbGlkYWQpLCBjb2xvcj0iYmx1ZSIpKw0KICBnZW9tX2xpbmUoZGF0YT1kZW1vX3Bsb3QyLCBhZXMoeD1Ob3RhLCB5PVByb2JhYmlsaWRhZCksIGNvbG9yPSJyZWQiKSArDQogIGdlb21fbGluZShkYXRhPWRlbW9fcGxvdDMsIGFlcyh4PU5vdGEsIHk9UHJvYmFiaWxpZGFkKSwgY29sb3I9ImdyZWVuIikgKw0KICBsYWJzKHggPSAiTm90YSBFeGFtZW4gKDAtMTAwKSIpICsNCiAgbGFicyh5ID0gIlByb2JhYmlsaWRhZCIgKSsNCiAgZ2d0aXRsZSgiUHJvYmFiaWxpZGFkIGRlIEdhbmFyIGVsIEN1cnNvIGVuIGZ1bmNp824gZGUgbGEgTm90YSBvYnRlbmlkYSIpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcz1zZXEoMCwxMDAsYnk9MTApKSArDQogIHRoZW1lX21pbmltYWwoKQ0KYGBgDQojIyMjRW4gbGEgZ3JhZmljYSBhbnRlcmlvciBzZSBtdWVzdGFuIGxhIHJlZ3Jlc2nzbiBsb2dpc3RpY2Egc2ltcGxlIGRlIGNhZGEgbW9kZWxvIG9idGVuaWRvIHBhcmEgY29tcGFyYXJsb3MsIGRvbmRlIGxhIGF6dWwgZXMgbGEgcHJvYmFiaWxpZGFkIGRlIGFwcm9iYXIgZWwgY3Vyc28gZW4gZnVuY2nzbiBkZSBsYSBub3RhIGRlbCBwcmltZXIgcGFyY2lhbCwgbGEgcm9qYSBlcyBsYSBwcm9iYWJpbGlkYWQgZGUgZ2FuYXIgZWwgY3Vyc28gZW4gZnVuY2nzbiBkZSBsYSBub3RhIGRlbCBzZWd1bmRvIHBhcmNpYWwsIHkgbGEgdmVyZGUgZXMgbGEgcHJvYmFiaWxpZGFkIGRlIGdhbmFyIGVsIGN1cnNvIGVuIGZ1bmNp824gZGUgbGEgbm90YSBkZWwgZXhhbWVuIGZpbmFsLg0KDQoNCg0KIyMjICoqQWhvcmEgbW9zdHJhcmVtb3MgdHJlcyBtb2RlbG9zIGRlIHJlZ3Jlc2nzbiBsb2dpc3RpY2EgbfpsdGlwbGUsIHV0aWxpemFuZG8gY29tYmluYWNpb25lcyBkZSBkb3MgZXhhbWVucyBwYXJhIGVuY29udHJhciBsYSBwcm9iYWJpbGlkYWQgZGUgcXVlIHVuIGFsdW1ubyBhcHJldWViZS9yZXBydWViZSBsYSBjbGFzZS4qKg0KDQojIyNNb2RlbG8gNDogSW5mbHVlbmNpYSBkZWwgUHJpbWVyIHkgU2VndW5kbyBQYXJjaWFsOg0KYGBge3J9DQpsaWJyYXJ5KHBsb3RseSkNCiNNb2RlbG8gUmVncmVzafNuIExvZ2lzdGljYSBNdWx0aXBsZSwgRXhhbWVuIFBhcmNpYWwgMSB5IEV4YW1lbiBQYXJjaWFsIDI6DQptb2Q0PC1nbG0oZGF0YT10cmFpbl9tYXRlMSwgIGZvcm11bGEgPSBBcHJvYiB+IEVQMSArIEVQMiAsIGZhbWlseSA9IGJpbm9taWFsKGxpbms9ImxvZ2l0IikpDQoNCiNEZWZpbmllbmRvIERvbWluaW86DQp4NDwtc2VxKDAsIDEwMCwgMSkNCnk0PC1zZXEoMCwgMTAwLCAxKQ0KZG9tYWluPC1leHBhbmQuZ3JpZCh4NCx5NCkNCm5hbWVzKGRvbWFpbik8LWMoIkVQMSIsICJFUDIiKQ0KDQp6NDwtcHJlZGljdChtb2Q0LCBkb21haW4sIHR5cGU9InJlc3BvbnNlIikNCmQ8LWRhdGEuZnJhbWUoejQpDQp6NDwtZCR6NA0KZGF0YV94eT1kYXRhLmZyYW1lKGRvbWFpbiRFUDEsIGRvbWFpbiRFUDIsIHo0KQ0KYGBgDQoNCiMjIyNHcmFmaWNhbmRvIFRlbmVtb3M6DQpgYGB7ciBmaWcud2lkdGg9MTIsIGZpZy5oZWlnaHQ9N30NCiNwbG90X2x5KHogPSB6NCwgdHlwZSA9ICJzdXJmYWNlIiwgKQ0KcCA8LSBwbG90X2x5KHggPWRvbWFpbiRFUDEsIHkgPSBkb21haW4kRVAyLCB6ID0gejQsIHR5cGUgPSAiaGVhdG1hcCIsIHhheGlzPWRvbWFpbiRFUDEsIHlheGlzPWRvbWFpbiRFUDIsDQogIGNvbG9yYmFyID0gbGlzdCh0aXRsZSA9ICJQcm9iYWJpbGlkYWQiKSkgJT4lDQogIGxheW91dCh0aXRsZSA9ICdQcm9iYWJpbGlkYWQgZGUgR2FuYXIgZWwgQ3Vyc28gdnJzIFBhcmNpYWwgMSB5IFBhcmNpYWwgMicsIHhheGlzID0gbGlzdCh0aXRsZSA9ICdOb3RhIFBhcmNpYWwgMScpICwgeWF4aXMgPSBsaXN0KHRpdGxlID0gJ05vdGEgUGFyY2lhbCAyJykpDQpwDQpgYGANCiMjIyMjIEEgY29udGludWFjafNuIHNlIG11ZXN0cmEgbGEgaW5mb3JtYWFjafNuIG1hcyBpbXBvcnRhbnRlIGRlbCBtb2RlbG8gNDoNCiogTG9zIFBhcmFtZXRyb3MgZGVsIG1vZGVsbyBzb246DQogICsgRWwgaW50ZXJjZXB0byBkZWwgbW9kZWxvIGVzICoqYHIgY29lZihtb2Q0KVtbMV1dYCoqLg0KICArIEVsIGNvZWZpY2llbnRlIGRlbCBtb2RlbG8gZXMgKipgciBjb2VmKG1vZDQpW1syXV1gKiouDQogICsgRWwgY29lZmljaWVudGUgZGVsIG1vZGVsbyBlcyAqKmByIGNvZWYobW9kNClbWzNdXWAqKi4NCiogUG9kZW1vcyBkZWNpciBxdWUgYW1ib3MgcGFyYW1ldHJvcyB0aWVuZW4gc2lnbmlmaWNhY25jaWEgZW4gZWwgbW9kZWxvIHlhIHF1ZSBlbCB2YWxvciBQIGRlIGFtYm9zIGVzICoqYHIgMmUtMTZgKiouDQoNCiMjIyMgTGEgZWN1Y2nzbiBkZWwgbW9kZWxvIGVzOg0KJCQgUChcc21hbGx7RVAxLCBFUDJ9KT1cZnJhY3sxfXsxK2Veey1bLTguNjM5OCswLjA1ODIqKEVQMSkrMC4wOTkzMSooRVAyKV19fSQkDQojIyMjI0EgY29udGludWFjafNuIG1vc3RyYXRlbW9zIHVuYSBwcmVkaWNjaW9uZXMgZW4gYmFzZSBhbCBtb2RlbG8gNCBhc3VtaWVuZG8gcXVlIHNpIGxhIHByb2JhYmlsaWRhZCBlcyBtYXlvciBhICoqMC41KiosIGVudG9uY2VzIGVsIGFsdW1ubyBhcHJvYmFyYSBlbCBjdXJzbywgZWwgZnJhbWUgZGUgZGF0b3MgZXN0YSBmb3JtYWRvIHBvciBsYSBub3RhIGRlbCBwcmltZXIgcGFyY2lhbCwgbGEgbm90YSBkZWwgc2VndW5kbyBwYXJjaWFsLCBsYSBwcm9iYWJpbGlkYWQgb2J0ZW5pZGEgcG9yIGVsIG1vZGVsbywgc2kgZWwgYWx1bW5vIGFwcm9ibyBvIG5vIGVsIGN1cnNvIHkgbGEgcHJlZGljY2nzbiBvYnRlbmlkYSBjb24gZWwgbW9kZWxvIHNpIGxhIHByb2JhYmlsaWRhZCBlcyBtYXlvciBhIDAuNToNCmBgYHtyfQ0KcHJlZGljY2lvbjQ8LXByZWRpY3Qob2JqZWN0PW1vZDQsIG5ld2RhdGE9dGVzdF9tYXRlMSwgdHlwZT0icmVzcG9uc2UiKQ0KcmVzdWx0YWRvNDwtZGF0YS5mcmFtZSh0ZXN0X21hdGUxJEVQMSwgdGVzdF9tYXRlMSRFUDIsIChhcy5kYXRhLmZyYW1lKHByZWRpY2Npb240KSkkcHJlZGljY2lvbjQpDQpyZXN1bHRhZG80JEFwcm9ibzwtIHRlc3RfbWF0ZTEkQXByb2IgI0FncmVnYW1vcyBjb2x1bW5hIGRlbCByZXN1bHRhZG8NCm5hbWVzKHJlc3VsdGFkbzQpPC1jKCJOb3RhIEVQMSIsICJOb3RhIEVQMiIsICJQcm9iYWJpbGlkYWQiLCAiQXByb2JvIENsYXNlIikNCnJlc3VsdGFkbzQkUHJlZGljY2lvbjwtIGlmZWxzZSgocmVzdWx0YWRvNCRQcm9iYWJpbGlkYWQgPj0gMC41KSwgeWVzPTEsIG5vPTApDQpyZXN1bHRhZG80ICU+JSBhcnJhbmdlKGBOb3RhIEVQMWApICMjIE9yZ2VuYW1vcyBlbCBhcnJlZ2xvDQpgYGANCiMjIyNDYWxjdWxhbmRvIGVmZWN0aXZpZGFkOg0KYGBge3J9DQpiPC0wDQppPC0xDQoNCmZvcihpIGluICgxOm5yb3cocmVzdWx0YWRvNCkpKXsNCiAgaWYocmVzdWx0YWRvNCRgQXByb2JvIENsYXNlYFtbaV1dID09IHJlc3VsdGFkbzQkUHJlZGljY2lvbltbaV1dKXsNCiAgICBiPC1iKzENCiAgfSBlbHNlIHsNCiAgICBiPC1iDQogIH0NCn0NCmVmNDwtYi9ucm93KHJlc3VsdGFkbzQpDQpgYGANCiMjIyMgRWwgbW9kZWxvIDQgdGllbmUgdW4gKipgciBlZjRgKiogZGUgZWZlY3RpdmlkYWQuDQoNCg0KDQojIyNNb2RlbG8gNTogSW5mbHVlbmNpYSBkZWwgUHJpbWVyIFBhcmNpYWwgeSBFeGFtZW4gRmluYWw6DQpgYGB7cn0NCiNNb2RlbG8gcGFyYSBNYXRlbWF0aWNhIDE6DQptb2Q1PC1nbG0oZGF0YT10cmFpbl9tYXRlMSwgIGZvcm11bGEgPSBBcHJvYiB+IEVQMSArIEVGICwgZmFtaWx5ID0gYmlub21pYWwobGluaz0ibG9naXQiKSkNCg0KI0FncmVnYW5kbyBlbCBNb2RlbG86DQoNCmRvbWFpbjwtZXhwYW5kLmdyaWQoeDQseTQpDQp4NDwtc2VxKDAsIDEwMCwgMSkNCnk0PC1zZXEoMCwgMTAwLCAxKQ0KbmFtZXMoZG9tYWluKTwtYygiRVAxIiwgIkVGIikNCg0KejQ8LXByZWRpY3QobW9kNSwgZG9tYWluLCB0eXBlPSJyZXNwb25zZSIpDQpkPC1kYXRhLmZyYW1lKHo0KQ0KejQ8LWQkejQNCmRhdGFfeHk9ZGF0YS5mcmFtZShkb21haW4kRVAxLCBkb21haW4kRUYsIHo0KQ0KYGBgDQoNCiMjIyMgR3JhZmljYW5kbyB0ZW5lbW9zOg0KYGBge3IgZmlnLndpZHRoPTEyLCBmaWcuaGVpZ2h0PTd9DQojcGxvdF9seSh6ID0gejQsIHR5cGUgPSAic3VyZmFjZSIsICkNCnAgPC0gcGxvdF9seSh4ID1kb21haW4kRVAxLCB5ID0gZG9tYWluJEVGLCB6ID0gejQsIHR5cGUgPSAiaGVhdG1hcCIsIHhheGlzPWRvbWFpbiRFUDEsIHlheGlzPWRvbWFpbiRFRiwNCiAgY29sb3JiYXIgPSBsaXN0KHRpdGxlID0gIlByb2JhYmlsaWRhZCIpKSAlPiUNCiAgbGF5b3V0KHRpdGxlID0gJ1Byb2JhYmlsaWRhZCBkZSBHYW5hciBlbCBDdXJzbyB2cnMgUGFyY2lhbCAxIHkgRmluYWwnLCB4YXhpcyA9IGxpc3QodGl0bGUgPSAnTm90YSBQYXJjaWFsIDEnKSAsIHlheGlzID0gbGlzdCh0aXRsZSA9ICdOb3RhIEV4YW1lbiBGaW5hbCcpKQ0KcA0KYGBgDQojIyMjIyBBIGNvbnRpbnVhY2nzbiBzZSBtdWVzdHJhIGxhIGluZm9ybWFhY2nzbiBtYXMgaW1wb3J0YW50ZSBkZWwgbW9kZWxvIDU6DQoqIExvcyBQYXJhbWV0cm9zIGRlbCBtb2RlbG8gc29uOg0KICArIEVsIGludGVyY2VwdG8gZGVsIG1vZGVsbyBlcyAqKmByIGNvZWYobW9kNSlbWzFdXWAqKi4NCiAgKyBFbCBjb2VmaWNpZW50ZSBkZWwgbW9kZWxvIGVzICoqYHIgY29lZihtb2Q1KVtbMl1dYCoqLg0KICArIEVsIGNvZWZpY2llbnRlIGRlbCBtb2RlbG8gZXMgKipgciBjb2VmKG1vZDUpW1szXV1gKiouDQoqIFBvZGVtb3MgZGVjaXIgcXVlIGFtYm9zIHBhcmFtZXRyb3MgdGllbmVuIHNpZ25pZmljYWNuY2lhIGVuIGVsIG1vZGVsbyB5YSBxdWUgZWwgdmFsb3IgUCBkZSBhbWJvcyBlcyAqKiQyKjEwXnstMTZ9JCoqLg0KDQojIyMjIExhIGVjdWNp824gZGVsIG1vZGVsbyBlczoNCiQkIFAoXHNtYWxse0VQMSwgRUZ9KT1cZnJhY3sxfXsxK2Veey1bLTcuOTk1NCswLjA2ODIxKihFUDEpKzAuMDkzNSooRUYpXX19JCQNCiMjIyMjIEEgY29udGludWFjafNuIG1vc3RyYXRlbW9zIHVuYSBwcmVkaWNjaW9uZXMgZW4gYmFzZSBhbCBtb2RlbG8gNSBhc3VtaWVuZG8gcXVlIHNpIGxhIHByb2JhYmlsaWRhZCBlcyBtYXlvciBhICoqMC41KiosIGVudG9uY2VzIGVsIGFsdW1ubyBhcHJvYmFyYSBlbCBjdXJzbywgZWwgZnJhbWUgZGUgZGF0b3MgZXN0YSBmb3JtYWRvIHBvciBsYSBub3RhIGRlbCBwcmltZXIgcGFyY2lhbCwgbGEgbm90YSBkZWwgZXhhbWVuIGZpbmFsLCBsYSBwcm9iYWJpbGlkYWQgb2J0ZW5pZGEgcG9yIGVsIG1vZGVsbywgc2kgZWwgYWx1bW5vIGFwcm9ibyBvIG5vIGVsIGN1cnNvIHkgbGEgcHJlZGljY2nzbiBvYnRlbmlkYSBjb24gZWwgbW9kZWxvIHNpIGxhIHByb2JhYmlsaWRhZCBlcyBtYXlvciBhIDAuNTo6DQpgYGB7cn0NCnByZWRpY2Npb241PC1wcmVkaWN0KG9iamVjdD1tb2Q1LCBuZXdkYXRhPXRlc3RfbWF0ZTEsIHR5cGU9InJlc3BvbnNlIikNCnJlc3VsdGFkbzU8LWRhdGEuZnJhbWUodGVzdF9tYXRlMSRFUDEsIHRlc3RfbWF0ZTEkRUYsIChhcy5kYXRhLmZyYW1lKHByZWRpY2Npb241KSkkcHJlZGljY2lvbjUpDQpyZXN1bHRhZG81JEFwcm9ibzwtIHRlc3RfbWF0ZTEkQXByb2IgI0FncmVnYW1vcyBjb2x1bW5hIGRlbCByZXN1bHRhZG8NCm5hbWVzKHJlc3VsdGFkbzUpPC1jKCJOb3RhIEVQMSIsICJOb3RhIEVGIiwgIlByb2JhYmlsaWRhZCIsICJBcHJvYm8gQ2xhc2UiKQ0KcmVzdWx0YWRvNSRQcmVkaWNjaW9uPC0gaWZlbHNlKChyZXN1bHRhZG81JFByb2JhYmlsaWRhZCA+PSAwLjUpLCB5ZXM9MSwgbm89MCkNCnJlc3VsdGFkbzUgJT4lIGFycmFuZ2UoYE5vdGEgRVAxYCkgIyMgT3JnZW5hbW9zIGVsIGFycmVnbG8NCmBgYA0KIyMjI0NhbGN1bGFuZG8gZWZlY3RpdmlkYWQ6DQpgYGB7cn0NCmE8LTANCmk8LTENCg0KZm9yKGkgaW4gKDE6bnJvdyhyZXN1bHRhZG81KSkpew0KICBpZihyZXN1bHRhZG81JGBBcHJvYm8gQ2xhc2VgW1tpXV0gPT0gcmVzdWx0YWRvNSRQcmVkaWNjaW9uW1tpXV0pew0KICAgIGE8LWErMQ0KICB9IGVsc2Ugew0KICAgIGE8LWENCiAgfQ0KfQ0KZWY1PC1hL25yb3cocmVzdWx0YWRvNSkNCmBgYA0KIyMjIyBFbCBtb2RlbG8gNSB0aWVuZSB1biAqKmByIGVmNWAqKiBkZSBlZmVjdGl2aWRhZC4NCg0KDQojIyNNb2RlbG8gNjogSW5mbHVlbmNpYSBkZWwgU2VndW5kbyBQYXJjaWFsIHkgRXhhbWVuIEZpbmFsOg0KYGBge3J9DQojTW9kZWxvIHBhcmEgTWF0ZW1hdGljYSAxOg0KbW9kNjwtZ2xtKGRhdGE9dHJhaW5fbWF0ZTEsICBmb3JtdWxhID0gQXByb2IgfiBFUDIgKyBFRiAsIGZhbWlseSA9IGJpbm9taWFsKGxpbms9ImxvZ2l0IikpDQoNCiNBZ3JlZ2FuZG8gZWwgTW9kZWxvOg0KDQpkb21haW48LWV4cGFuZC5ncmlkKHg0LHk0KQ0KeDQ8LXNlcSgwLCAxMDAsIDEpDQp5NDwtc2VxKDAsIDEwMCwgMSkNCm5hbWVzKGRvbWFpbik8LWMoIkVQMiIsICJFRiIpDQoNCno0PC1wcmVkaWN0KG1vZDYsIGRvbWFpbiwgdHlwZT0icmVzcG9uc2UiKQ0KZDwtZGF0YS5mcmFtZSh6NCkNCno0PC1kJHo0DQpkYXRhX3h5PWRhdGEuZnJhbWUoZG9tYWluJEVQMiwgZG9tYWluJEVGLCB6NCkNCmBgYA0KIyMjI0dyYWZpY2FuZG8gVGVuZW1vczoNCmBgYHtyIGZpZy53aWR0aD0xMiwgZmlnLmhlaWdodD03fQ0KI3Bsb3RfbHkoeiA9IHo0LCB0eXBlID0gInN1cmZhY2UiLCApDQpwIDwtIHBsb3RfbHkoeCA9ZG9tYWluJEVQMiwgeSA9IGRvbWFpbiRFRiwgeiA9IHo0LCB0eXBlID0gImhlYXRtYXAiLCB4YXhpcz1kb21haW4kRVAyLCB5YXhpcz1kb21haW4kRUYsDQogIGNvbG9yYmFyID0gbGlzdCh0aXRsZSA9ICJQcm9iYWJpbGlkYWQiKSkgJT4lDQogIGxheW91dCh0aXRsZSA9ICdQcm9iYWJpbGlkYWQgZGUgR2FuYXIgZWwgQ3Vyc28gdnJzIFBhcmNpYWwgMiB5IEZpbmFsJywgeGF4aXMgPSBsaXN0KHRpdGxlID0gJ05vdGEgUGFyY2lhbCAyJykgLCB5YXhpcyA9IGxpc3QodGl0bGUgPSAnTm90YSBFeGFtZW4gRmluYWwnKSkNCnANCmBgYA0KIyMjIyMgQSBjb250aW51YWNp824gc2UgbXVlc3RyYSBsYSBpbmZvcm1hYWNp824gbWFzIGltcG9ydGFudGUgZGVsIG1vZGVsbyA2Og0KKiBMb3MgUGFyYW1ldHJvcyBkZWwgbW9kZWxvIHNvbjoNCiAgKyBFbCBpbnRlcmNlcHRvIGRlbCBtb2RlbG8gZXMgKipgciBjb2VmKG1vZDYpW1sxXV1gKiouDQogICsgRWwgY29lZmljaWVudGUgZGVsIG1vZGVsbyBlcyAqKmByIGNvZWYobW9kNilbWzJdXWAqKi4NCiAgKyBFbCBjb2VmaWNpZW50ZSBkZWwgbW9kZWxvIGVzICoqYHIgY29lZihtb2Q2KVtbM11dYCoqLg0KKiBQb2RlbW9zIGRlY2lyIHF1ZSBhbWJvcyBwYXJhbWV0cm9zIHRpZW5lbiBzaWduaWZpY2FjbmNpYSBlbiBlbCBtb2RlbG8geWEgcXVlIGVsIHZhbG9yIFAgZGUgYW1ib3MgZXMgKiokMioxMF57LTE2fSQqKi4NCg0KIyMjIyBMYSBlY3VjafNuIGRlbCBtb2RlbG8gZXM6DQokJCBQKFxzbWFsbHtFUDIsIEVGfSk9XGZyYWN7MX17MStlXnstWy05Ljg1MTYrMC4xMDk4KihFUDIpKzAuMDkyODIqKEVGKV19fSQkDQojIyMjI0EgY29udGludWFjafNuIG1vc3RyYXRlbW9zIHVuYSBwcmVkaWNjaW9uZXMgZW4gYmFzZSBhbCBtb2RlbG8gNCBhc3VtaWVuZG8gcXVlIHNpIGxhIHByb2JhYmlsaWRhZCBlcyBtYXlvciBhICoqMC41KiosIGVudG9uY2VzIGVsIGFsdW1ubyBhcHJvYmFyYSBlbCBjdXJzbywgZWwgZnJhbWUgZGUgZGF0b3MgZXN0YSBmb3JtYWRvIHBvciBsYSBub3RhIGRlbCBzZWd1bmRvIHBhcmNpYWwsIGxhIG5vdGEgZGVsIGV4YW1lbiBmaW5hbCwgbGEgcHJvYmFiaWxpZGFkIG9idGVuaWRhIHBvciBlbCBtb2RlbG8sIHNpIGVsIGFsdW1ubyBhcHJvYm8gbyBubyBlbCBjdXJzbyB5IGxhIHByZWRpY2Np824gb2J0ZW5pZGEgY29uIGVsIG1vZGVsbyBzaSBsYSBwcm9iYWJpbGlkYWQgZXMgbWF5b3IgYSAwLjU6Og0KYGBge3J9DQpwcmVkaWNjaW9uNjwtcHJlZGljdChvYmplY3Q9bW9kNiwgbmV3ZGF0YT10ZXN0X21hdGUxLCB0eXBlPSJyZXNwb25zZSIpDQpyZXN1bHRhZG82PC1kYXRhLmZyYW1lKHRlc3RfbWF0ZTEkRVAyLCB0ZXN0X21hdGUxJEVGLCAoYXMuZGF0YS5mcmFtZShwcmVkaWNjaW9uNikpJHByZWRpY2Npb242KQ0KcmVzdWx0YWRvNiRBcHJvYm88LSB0ZXN0X21hdGUxJEFwcm9iICNBZ3JlZ2Ftb3MgY29sdW1uYSBkZWwgcmVzdWx0YWRvDQpuYW1lcyhyZXN1bHRhZG82KTwtYygiTm90YSBFUDEiLCAiTm90YSBFRiIsICJQcm9iYWJpbGlkYWQiLCAiQXByb2JvIENsYXNlIikNCnJlc3VsdGFkbzYkUHJlZGljY2lvbjwtIGlmZWxzZSgocmVzdWx0YWRvNiRQcm9iYWJpbGlkYWQgPj0gMC41KSwgeWVzPTEsIG5vPTApDQpyZXN1bHRhZG82ICU+JSBhcnJhbmdlKGBOb3RhIEVQMWApICMjIE9yZ2VuYW1vcyBlbCBhcnJlZ2xvDQpgYGANCiMjIyNDYWxjdWxhbmRvIGVmZWN0aXZpZGFkOg0KYGBge3J9DQphPC0wDQppPC0xDQoNCmZvcihpIGluICgxOm5yb3cocmVzdWx0YWRvNikpKXsNCiAgaWYocmVzdWx0YWRvNiRgQXByb2JvIENsYXNlYFtbaV1dID09IHJlc3VsdGFkbzYkUHJlZGljY2lvbltbaV1dKXsNCiAgICBhPC1hKzENCiAgfSBlbHNlIHsNCiAgICBhPC1hDQogIH0NCn0NCmVmNjwtYS9ucm93KHJlc3VsdGFkbzUpDQpgYGANCiMjIyMgRWwgbW9kZWxvIDEgdGllbmUgdW4gKipgciBlZjZgKiogZGUgZWZlY3RpdmlkYWQuDQoNCiMjI0NvbmNsaXNpb25lczoNCiMjIyMgRGUgbG9zIG1vZGVsb3MgbW9zdHJhZG9zIGFudGVyaW9ybWVudGUsIHBvZGVtb3Mgbm90YXIgcXVlIGVuIGdlbmVyYWwgdG9kb3MgbG9zIGV4YW1lbnMgdGllbmUgdW5hIGluZmx1ZW5jaWEgaW1wb3J0YW50ZSBlbiBhcHJvYmFyIGVsIGN1cnNvIGRlIG1hdGVtYXRpY2EgbyBubywgc2luIGVtYmFyZ28sIGVzIGludGVyZXNhbnRlIHZlciBxdWUgbGEgZWZpY2llbmNpYSBkZSBsb3MgbW9kZWxvcyBxdWUgaW52b2x1Y3JhbiBhbCBleGFtZW4gZmluYWwgc29uIHN1cGVyaW9yZXMsIHBvciB0YW50byBlcyB2aXRhbCBxdWUgbG9zIGFsdW1ub3MgbGUgcG9uZ2FuIG1hcyBlbXBl8W8gYWwgZXhhbWVuIGZpbmFsLCBzZWf6biBudWVzdHJvIGVzdHVkaW8uDQo=