Documento disponível em: https://rpubs.com/JoaoLRPT/carseats_eda

Trabalho da disciplina de Ciclo de Vida e Introdução à Linguagem R

Lista de packages usados: 

1. Indicação da lista de bibliotecas usadas no início do trabalho e a justificativa para uso

# Lista de pacotes usados:
library(ISLR) # contem o dataset CarSeats, utilizado no trabalho
library(data.table) # contem o formato das tabelas, utilizado no trabalho
library(psych) # para fazer o teste de correlação
library(ggplot2) # para o mapa de calor (heatmap), scatterplots, histogramas e gráfico de barras.
library(tidyverse) # para o histograma sobreposto
library(grid) # para o histograma sobreposto
library(gridExtra) # para o histograma sobreposto

Justificativa de uso:
ISLR: por conter o dataset utilizado aqui;
data.table: tenho grande familiaridade com essa forma de armazenar tabelas. Em meu trabalho, em geral, utilizávamos dplyr. Porém, constatamos certa lentidão em alguns projetos, ao passo que estávamos trabalhando com um conjunto de dados muito grande. Se tornou hábito limpar a memória, com rm() e gc(), ao longo do script, para não haver estouro de memória. Pesquisando, verifiquei que o data.table era até 10x mais rápido do que as outras opções, como dplyr, sqldf e o base do R;
psych: biblioteca conhecida pelo corr.test, que realiza o teste de correlação, determinando quais colunas apresentam correlação significativa;
ggplot2: biblioteca conhecida por sua variedade de gráficos. Sua function ggplot() foi utilizada na construção dos seguintes gráficos: heatmap (mapa de calor), scatterplots (gráfico de pontos), histogramas e barplots (gráfico de barras).
As seguintes bibliotecas foram utilizadas para facilitar a construção dos histogramas sobrepostos:
tidyverse: é importante o analista de dados sempre se atentar aos conflitos entre pacotes, quando existem métodos com mesmas nomenclatura (homônimos), conforme warning após o library(tidyvere). Note uma série de métodos homônimos entre o tidyverse versus ggplot2, dplyr, lubridate e purr.
grid.
gridExtra.

ETL (Extração - Transformação - Carga):

2. Indicação da fonte dos dados

# Informacoes do dataset usado
?Carseats

No code chunk acima, podemos verificar que se trata de um conjunto de dados simulados contendo vendas de assentos infantis de carro em 400 diferentes lojas.

3. Apresentar o metadado (dicionário de dados) do dataset

No ?Carseats, acima, conseguimos extrair, dentre outras informações, os seguintes metadados:
Temos uma tabela de dados contendo 400 observações e as seguintes 11 variáveis:
Sales: Vendas unitárias (em milhares) da loja;
CompPrice: Preço cobrado pelo concorrente de cada loja;
Income: Nível de renda da comunidade (em milhares de dólares);
Advertising: Orçamento de publicidade de cada loja (em milhares de dólares);
Population: População na região (em milhares);
Price: Preço que a empresa cobra pelos assentos infantis de carro;
ShelveLoc: Um fator com níveis Ruim, Bom e Médio que indica a qualidade da localização das estantes das cadeirinhas em cada loja;
Age: Idade média da população local;
Education: Nível de educação de cada localidade;
Urban: Um fator com níveis Não e Sim para indicar se a loja está localizada em uma localidade urbana (Yes) ou rural (no);
US: Um fator com níveis Não e Sim para indicar se a loja está nos USA (Yes) ou não (No).

4. Escrever o código para a importação do dataset

# Carregar o dataset
data("Carseats")
setDT(Carseats)
# Estrutura do banco de dados
str(Carseats) 
Classes ‘data.table’ and 'data.frame':  400 obs. of  11 variables:
 $ Sales      : num  9.5 11.22 10.06 7.4 4.15 ...
 $ CompPrice  : num  138 111 113 117 141 124 115 136 132 132 ...
 $ Income     : num  73 48 35 100 64 113 105 81 110 113 ...
 $ Advertising: num  11 16 10 4 3 13 0 15 0 0 ...
 $ Population : num  276 260 269 466 340 501 45 425 108 131 ...
 $ Price      : num  120 83 80 97 128 72 108 120 124 124 ...
 $ ShelveLoc  : Factor w/ 3 levels "Bad","Good","Medium": 1 2 3 3 1 1 3 2 3 3 ...
 $ Age        : num  42 65 59 55 38 78 71 67 76 76 ...
 $ Education  : num  17 10 12 14 13 16 15 10 10 17 ...
 $ Urban      : Factor w/ 2 levels "No","Yes": 2 2 2 2 2 1 2 2 1 1 ...
 $ US         : Factor w/ 2 levels "No","Yes": 2 2 2 2 1 2 1 2 1 2 ...
 - attr(*, ".internal.selfref")=<externalptr> 

Análise Exploratória de Dados básica:

5. Destacar os valores máximos das 20 primeiras linhas

# avaliando as 20 primeiras linhas
Carseats[1:20,]
# detectando os campos numericos:
campos_numericos = names(Filter(is.numeric, Carseats))
campos_numericos
[1] "Sales"       "CompPrice"   "Income"      "Advertising" "Population"  "Price"      
[7] "Age"         "Education"  
# função para devolver os valores máximos de cada coluna, removendo os NA
colMax <- function(df) sapply(df, max, na.rm = TRUE)
# valores máximos das 20 primeiras linhas desses campos numéricos
colMax(Carseats[1:20, names(Carseats) %in% campos_numericos, with=FALSE])
      Sales   CompPrice      Income Advertising  Population       Price         Age   Education 
      13.91      149.00      117.00       16.00      503.00      144.00       78.00       18.00 

6. Destacar os valores mínimos das 20 últimas linhas

# função para devolver os valores mínimos de cada coluna, removendo os NA
colMin <- function(df) sapply(df, min, na.rm = TRUE)
# valores mínimos das 20 últimas linhas desses campos numéricos
colMin(Carseats[(nrow(Carseats)-19):nrow(Carseats), names(Carseats) %in% campos_numericos, with=FALSE])
      Sales   CompPrice      Income Advertising  Population       Price         Age   Education 
        3.9        98.0        23.0         0.0        17.0        68.0        28.0        10.0 

7. Listar os nomes das colunas

names(Carseats)
 [1] "Sales"       "CompPrice"   "Income"      "Advertising" "Population"  "Price"      
 [7] "ShelveLoc"   "Age"         "Education"   "Urban"       "US"         

8. Verificar a dimensão do dataset (total de linhas e colunas)

cat("Número de linhas: ", nrow(mtcars), "\n", sep="")
Número de linhas: 32
cat("Número de colunas: ", ncol(mtcars), "\n", sep="")
Número de colunas: 11

9. Contar o total de amostras por uma das variáveis categóricas tanto ordinal ou nominal

table(Carseats$Urban, useNA="always")

  No  Yes <NA> 
 118  282    0 

10. Apresentar a estatística básica para o dataset – summary()

summary(Carseats)
     Sales          CompPrice       Income        Advertising       Population   
 Min.   : 0.000   Min.   : 77   Min.   : 21.00   Min.   : 0.000   Min.   : 10.0  
 1st Qu.: 5.390   1st Qu.:115   1st Qu.: 42.75   1st Qu.: 0.000   1st Qu.:139.0  
 Median : 7.490   Median :125   Median : 69.00   Median : 5.000   Median :272.0  
 Mean   : 7.496   Mean   :125   Mean   : 68.66   Mean   : 6.635   Mean   :264.8  
 3rd Qu.: 9.320   3rd Qu.:135   3rd Qu.: 91.00   3rd Qu.:12.000   3rd Qu.:398.5  
 Max.   :16.270   Max.   :175   Max.   :120.00   Max.   :29.000   Max.   :509.0  
     Price        ShelveLoc        Age          Education    Urban       US     
 Min.   : 24.0   Bad   : 96   Min.   :25.00   Min.   :10.0   No :118   No :142  
 1st Qu.:100.0   Good  : 85   1st Qu.:39.75   1st Qu.:12.0   Yes:282   Yes:258  
 Median :117.0   Medium:219   Median :54.50   Median :14.0                      
 Mean   :115.8                Mean   :53.32   Mean   :13.9                      
 3rd Qu.:131.0                3rd Qu.:66.00   3rd Qu.:16.0                      
 Max.   :191.0                Max.   :80.00   Max.   :18.0                      

É possível analisar o range (amplitude), concentração (através dos quartis) e média de cada campo quantitativo e as categorias presentes nos campos qualitativos. Note a ausência de dados faltantes (NA) no dataset. Para confirmar essa ausência de NA’s, é possível rodar o seguinte script:

sum(is.na(Carseats))
[1] 0

11. Realizar a análise de correlação via linha de comando

# análise de correlação entre todos os campos numéricos
carseats_numericos = Carseats[,names(Carseats) %in% campos_numericos, with=FALSE]
round(cor(carseats_numericos),4)
              Sales CompPrice  Income Advertising Population   Price     Age Education
Sales        1.0000    0.0641  0.1520      0.2695     0.0505 -0.4450 -0.2318   -0.0520
CompPrice    0.0641    1.0000 -0.0807     -0.0242    -0.0947  0.5848 -0.1002    0.0252
Income       0.1520   -0.0807  1.0000      0.0590    -0.0079 -0.0567 -0.0047   -0.0569
Advertising  0.2695   -0.0242  0.0590      1.0000     0.2657  0.0445 -0.0046   -0.0336
Population   0.0505   -0.0947 -0.0079      0.2657     1.0000 -0.0121 -0.0427   -0.1064
Price       -0.4450    0.5848 -0.0567      0.0445    -0.0121  1.0000 -0.1022    0.0117
Age         -0.2318   -0.1002 -0.0047     -0.0046    -0.0427 -0.1022  1.0000    0.0065
Education   -0.0520    0.0252 -0.0569     -0.0336    -0.1064  0.0117  0.0065    1.0000

1ª) r = 0.5848 - Note que a maior correlação ocorre entre preço e preço da concorrência (Price vs CompPrice, com correlação = 0.5848), o que é bastante lógico, ao passo que os preços devem seguir um padrão por região, logo, preços entre concorrentes tendem a serem relacionados.

2ª) r = -0.4450 - Como a segunda maior correlação, temos a correlação entre preço e vendas (Price vs Sales, com correlação = -0.4450). Essa relação também é esperada, ao passo que quando os preços caem a tendência é de que as vendas aumentem.

3ª) r = 0.2695 - Como a terceira maior correlação, temos vendas vs publicidade (Sales vs Advertising, com correlação = 0.2695). Isso reflete o impacto positivo do orçamento gasto com campanhas publicitárias sobre o total de vendas de cadeiras infantis para carros.

4ª) r = 0.2657 - Correlação grande ocorre entre população e publicidade (Population vs Advertising). Ao passo que a população aumenta, o investimento em publicidade também tende a seguir essa tendência.

5ª) r = -0.2318 - É interessante destacar a correlação entre vendas e idade (Sales vs Age). Naturalmente, uma população mais velha tende a ter menos filhos pequenos, o que leva a tendência de reduzir as compras de cadeiras infantis para veículos automotivos. Essa relação está presente com r = -0.2318 de correlação entre vendas e idade.

# solicitando a análise de correlação, em termos de p-valor:
analise_correlacao = corr.test(x = carseats_numericos, # Dados
                 use = "complete", # Como vamos lidar com missings
                 method = "pearson") # tipo de correlação

Analisando p-valores dessas correlações:

round(analise_correlacao$p,4)
             Sales CompPrice Income Advertising Population  Price    Age Education
Sales       0.0000    1.0000 0.0531      0.0000     1.0000 0.0000 0.0001    1.0000
CompPrice   0.2009    0.0000 1.0000      1.0000     1.0000 0.0000 0.9024    1.0000
Income      0.0023    0.1073 0.0000      1.0000     1.0000 1.0000 1.0000    1.0000
Advertising 0.0000    0.6294 0.2391      0.0000     0.0000 1.0000 1.0000    1.0000
Population  0.3140    0.0584 0.8752      0.0000     0.0000 1.0000 1.0000    0.7353
Price       0.0000    0.0000 0.2579      0.3743     0.8087 0.0000 0.8632    1.0000
Age         0.0000    0.0451 0.9258      0.9276     0.3948 0.0411 0.0000    1.0000
Education   0.2999    0.6154 0.2566      0.5029     0.0334 0.8148 0.8971    0.0000

A tabela abaixo apresenta os p-valores (p) através de asteriscos, onde:  

p < 0.001 retornará três asteriscos,

0.001 < p < 0.01 retornará dois asteriscos,  

0.01 < p < 0.05 retornará um asterisco:

ifelse(analise_correlacao$p <.001, "***", ifelse(analise_correlacao$p < 0.01, "**", ifelse(analise_correlacao$p <0.05, "*", "")))
            Sales CompPrice Income Advertising Population Price Age   Education
Sales       "***" ""        ""     "***"       ""         "***" "***" ""       
CompPrice   ""    "***"     ""     ""          ""         "***" ""    ""       
Income      "**"  ""        "***"  ""          ""         ""    ""    ""       
Advertising "***" ""        ""     "***"       "***"      ""    ""    ""       
Population  ""    ""        ""     "***"       "***"      ""    ""    ""       
Price       "***" "***"     ""     ""          ""         "***" ""    ""       
Age         "***" "*"       ""     ""          ""         "*"   "***" ""       
Education   ""    ""        ""     ""          "*"        ""    ""    "***"    

Note as 5 maiores correlações destacadas, e explicadas anteriormente, como obtendo p-valores <0.001. Com uma correlação quase borderline, mas ainda assim, significante a 5%, note preço versus idade, significativo a 5%, mas bem próximo da borda (p-valor = 0.0411). Com r = -0.1022, populações mais velhas tendem a pagar menos por cadeiras infantis para carro. Isso levanta uma hipótese de causa seguindo os princípios da lei da oferta x demanda, onde população envelhecendo tende a comprar menos cadeiras infantis por terem menos filhos pequenos, o que infla os estoques, o que obriga a baixarem os preços.

Análise Exploratória de Dados com gráficos

Análise de Correlação:

12. Criar um gráfico heatmap a partir das variáveis usadas na correlação

#Mapa de calor (heat map) Iris
#library(ggplot2) # para o mapa de calor (heatmap)
#library(reshape2) # reshape2::melt foi substituído por data.table::melt

dat <- carseats_numericos
cor <- data.table::melt(cor(dat, use="p"))
Aviso: Um matrix foi passado para o genérico melt em data.table, que vai tentar redirecionar para o método relevante no pacote reshape2; favor notar que reshape2 foi substituído e não está mais em desenvolvimento ativo, e que esse redirecionamento agora é obsoleto. Para continuar usando métodos melt de reshape2 enquanto ambos os pacotes estão anexados, p.ex. melt.list, você pode prefixar com o namespace, p.ex. reshape2::melt(cor(dat, use = "p")). Na próxima versão, este aviso se tornará um erro.
head(cor)
heat <- ggplot(data=cor, aes(x=Var1, y=Var2, fill=value)) 
heat + geom_tile() + labs(x = "", y = "") + scale_fill_gradient2(limits=c(-1, 1))

Destaque para correlações negativas fortes entre vendas e preço (Sales vs Price) e correlações positivas fortes entre preço e preço da concorrência (Price vs CompPrice). Tais situações já foram analisadas anteriormente. O mapa de calor acima apresenta um bom apelo visual, tornando a interpretação mais rápida.

13. Criar um scatterplot para o par de variáveis com maior correlação

# scatterplot Price vs CompPrice
# Gráfico de dispersão - scatterplot
plot(carseats_numericos$Price,carseats_numericos$CompPrice, xlab="Preço", ylab="Preço dos Concorrentes")

Conforme já visualizado na análise de correlação, note a correlação positiva entre preço dos assentos infantis versus preço da concorrência, o que indica que os preços entre concorrentes locais são correlacionados.

14. Realizar a análise bivariada por meio de scatterplots para exibir a distribuição dos dados entre as principais variáveis categóricas. Utilize cores e altere o tamanho dos pontos para facilitar a interpretação

# Vetor de cores com base nas classes de ShelveLoc (Qualidade da localização das estantes)
cores <- c("Bad" = "red", "Medium" = "yellow", "Good" = "green")
cores_pontos <- scale_colour_manual(name="ShelveLoc", values=cores)

#Alterando o tamanho dos pontos
ggplot(Carseats, aes(x=Price, y=Sales, shape = ShelveLoc)) + 
  geom_point(size = 2, alpha= I(0.8), aes(colour = ShelveLoc)) +
  cores_pontos +  
  xlab("Preço por assento (US$)") +  
  ylab("Vendas (milhares de unidades)") 

Logo, é perceptível a relação de preços grandes e vendas baixas com estantes mal localizadas. É possível que a qualidade da localização da estante esteja impactando nas vendas, e não apenas o preço unitário.

Análise de Distribuição:

15. Realizar a análise univariada com um histograma para uma variável numérica

# Criar o histograma com intervalos de 0,5
hist(Carseats$Sales, breaks = seq(min(Carseats$Sales), 
     max(Carseats$Sales) + 0.5, by = 0.5), xlim = c(0, max(Carseats$Sales)+3), main = "", 
     xlab = "Vendas (milhares de unidades)", ylab = "Frequência", col = "lightblue")

Note a concentração da quantia de vendas da loja bastante presente entre 5 e 10 mil unidades.

16. Apresentar em apenas um gráfico vários histogramas para as variáveis numéricas

Apresentando gráficos de maneira única, para visualização de todos os campos numéricos:

# Histogramas de todas variáveis numéricas
par(mfrow = c(2,3))
hist(carseats_numericos$Sales, xlab="Vendas", main="", col="blue")
hist(carseats_numericos$CompPrice, xlab="Preço da Concorrência", main="", col="lightblue")
hist(carseats_numericos$Income, xlab="Renda comunitária (US$ milhares)", main="", col="blue")
hist(carseats_numericos$Advertising, xlab="Orçamento publicitário (US$ milhares)", main="", col="lightblue")
hist(carseats_numericos$Population, xlab="População (milhares)", main="", col="blue")
hist(carseats_numericos$Price, xlab="Preço do assento infantil", main="", col="lightblue")

hist(carseats_numericos$Age, xlab="Idade média populacional", main="", col="blue")
hist(carseats_numericos$Education, xlab="Nível educacional", main="", col="lightblue")

Porém, é possível utilizar histogramas sobrepostos para comparação de campos numéricos entre grupos, como é feito abaixo:

# Será que Lojas Urbanas vendem mais ou menos do que Lojas Rurais? 
# Além disso, Lojas Urbanas contêm preços maiores ou menores do que Lojas Rurais?
# Será que Lojas Americanas vendem mais ou menos do que Lojas Não Americanas?
# Além disso, quais apresentam preços maiores?
# Será que Lojas com Estantes bem localizadas vendem mais? E elas têm preços maiores?
#Histogramas
#library(tidyverse)
#library(grid)
#library(gridExtra)
h1 <- Carseats %>% 
  ggplot(aes(Sales)) + geom_histogram(aes(fill=Urban), binwidth=1, col="black") +
  geom_vline(aes(xintercept=mean(Sales)), linetype="dashed", color="black") + labs(x="Vendas (milhares de unidades)", y="Frequência") +
  theme(legend.position="none")
h2 <- Carseats %>% 
  ggplot(aes(Price)) + geom_histogram(aes(fill=Urban), binwidth=10, col="black") +
  geom_vline(aes(xintercept=mean(Price)), linetype="dashed", color="black") + labs(x="Preço (US$)", y="Frequência") +
  theme(legend.position="right")
grid.arrange(h1,h2, nrow=1, top=textGrob(""))

h1 <- Carseats %>% 
  ggplot(aes(Sales)) + geom_histogram(aes(fill=US), binwidth=1, col="black") +
  geom_vline(aes(xintercept=mean(Sales)), linetype="dashed", color="black") + labs(x="Vendas (milhares de unidades)", y="Frequência") +
  theme(legend.position="none")
h2 <- Carseats %>% 
  ggplot(aes(Price)) + geom_histogram(aes(fill=US), binwidth=10, col="black") +
  geom_vline(aes(xintercept=mean(Price)), linetype="dashed", color="black") + labs(x="Preço (US$)", y="Frequência") +
  theme(legend.position="right")
grid.arrange(h1,h2, nrow=1, top=textGrob(""))

h1 <- Carseats %>% 
  ggplot(aes(Sales)) + geom_histogram(aes(fill=ShelveLoc), binwidth=1, col="black") +
  geom_vline(aes(xintercept=mean(Sales)), linetype="dashed", color="black") + labs(x="Vendas (milhares de unidades)", y="Frequência") +
  theme(legend.position="none")
h2 <- Carseats %>% 
  ggplot(aes(Price)) + geom_histogram(aes(fill=ShelveLoc), binwidth=10, col="black") +
  geom_vline(aes(xintercept=mean(Price)), linetype="dashed", color="black") + labs(x="Preço (US$)", y="Frequência") +
  theme(legend.position="right")
grid.arrange(h1,h2, nrow=1, top=textGrob(""))

Urban: Há bastante similaridade nas vendas e preços de lojas urbanas e rurais.
US: Já na análise da nacionalidade das lojas, é possível notar um volume de vendas um pouco maior para as lojas americanas do que para as não americanas. Mas a diferença parece sutil.
Shelveloc: Na mesma linha do scatterplot anteriormente visualizado, nos histogramas é possível notar um comportamento visível no aumento de vendas ao passo que a localização do produto nas estantes fiquei melhor.

17. Verificar com boxplots a presença de possíveis outliers

# Boxplots de todas variáveis numéricas
par(mfrow = c(2,3))
boxplot(carseats_numericos$Sales, xlab="Vendas", main="", col="blue")
boxplot(carseats_numericos$CompPrice, xlab="Preço da Concorrência", main="", col="lightblue")
boxplot(carseats_numericos$Income, xlab="Renda comunitária (US$ milhares)", main="", col="blue")
boxplot(carseats_numericos$Advertising, xlab="Orçamento publicitário (US$ milhares)", main="", col="lightblue")
boxplot(carseats_numericos$Population, xlab="População (milhares)", main="", col="blue")
boxplot(carseats_numericos$Price, xlab="Preço do assento infantil", main="", col="lightblue")

boxplot(carseats_numericos$Age, xlab="Idade média populacional", main="", col="blue")
boxplot(carseats_numericos$Education, xlab="Nível educacional", main="", col="lightblue")

Outliers encontrados:

Vendas: dois outliers superiores, representando duas lojas com mais de 15 mil vendas;
Preço da Concorrência: um outlier superior e um outlier inferior, representando, respectivamente, uma loja com concorrente cobrando um valor muito alto na cadeira infantil para carro (mais do que US$ 160) e uma loja com concorrente cobrando um valor muito baixo no item (menos de US$ 80).
Preço: dois outliers superiores, cobrando mais de US$ 160 e três outliers inferiores, cobrando menos de U$ 60 no produto.

Gráficos de classificação:

18. Criar um gráfico de barras ou colunas

#Gráfico de barras de vendas dentro e fora dos US
ggplot(Carseats, aes(x = US)) + 
  geom_bar() + 
  theme_classic() +
  ggtitle("US") +
  xlab("US") + 
  ylab("Frequency") + 
  geom_text(stat= "count",aes(label = ..count..), vjust = -1) + coord_cartesian(ylim = c(0, max(table(Carseats$US)*1.1))) 

Note a predominância de lojas americanas na amostra.

Gráficos de partes ou setores:

19. Criar um gráfico de pizza ou setores para uma variável categórica com porcentagens

pie(table(Carseats$Urban), 
    labels = paste(names(table(Carseats$Urban)), " - ",
                   table(Carseats$Urban)," (", 100*table(Carseats$Urban)/sum(table(Carseats$Urban)), "%)", sep=""), col=c("black", "white"), main = "Loja urbana")

É possível constatar que 70.5% (282 lojas) da amostra é referente a lojas situadas na região urbana da cidade, em contraponto aos 29.5% (118 lojas) situados na região rural.

Análise dos Dados:

20. Interpretar os gráficos gerados
Interpretação efetuada ao longo do documento.

LS0tDQp0aXRsZTogIkFuw6FsaXNlIGV4cGxvcmF0w7NyaWEgZG8gZGF0YXNldCBDYXJzZWF0cyINCmF1dGhvcjogIkpvw6NvIEx1Y2FzIGRhIFJvc2EgUGVyaW4gVGVsbGVzIg0KZGF0ZTogIjAxLzAzLzIwMjUiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sgDQotLS0NCg0KKipEb2N1bWVudG8gZGlzcG9uw612ZWwgZW06IDxodHRwczovL3JwdWJzLmNvbS9Kb2FvTFJQVC9jYXJzZWF0c19lZGE+KioNCg0KIyMgVHJhYmFsaG8gZGEgZGlzY2lwbGluYSBkZSBDaWNsbyBkZSBWaWRhIGUgSW50cm9kdcOnw6NvIMOgIExpbmd1YWdlbSBSDQoNCiMjIyBMaXN0YSBkZSBwYWNrYWdlcyB1c2Fkb3M6XCANCg0KKioxLiBJbmRpY2HDp8OjbyBkYSBsaXN0YSBkZSBiaWJsaW90ZWNhcyB1c2FkYXMgbm8gaW7DrWNpbyBkbyB0cmFiYWxobyBlIGEganVzdGlmaWNhdGl2YSBwYXJhIHVzbyoqDQoNCmBgYHtyfQ0KIyBMaXN0YSBkZSBwYWNvdGVzIHVzYWRvczoNCmxpYnJhcnkoSVNMUikgIyBjb250ZW0gbyBkYXRhc2V0IENhclNlYXRzLCB1dGlsaXphZG8gbm8gdHJhYmFsaG8NCmxpYnJhcnkoZGF0YS50YWJsZSkgIyBjb250ZW0gbyBmb3JtYXRvIGRhcyB0YWJlbGFzLCB1dGlsaXphZG8gbm8gdHJhYmFsaG8NCmxpYnJhcnkocHN5Y2gpICMgcGFyYSBmYXplciBvIHRlc3RlIGRlIGNvcnJlbGHDp8Ojbw0KbGlicmFyeShnZ3Bsb3QyKSAjIHBhcmEgbyBtYXBhIGRlIGNhbG9yIChoZWF0bWFwKSwgc2NhdHRlcnBsb3RzLCBoaXN0b2dyYW1hcyBlIGdyw6FmaWNvIGRlIGJhcnJhcy4NCmxpYnJhcnkodGlkeXZlcnNlKSAjIHBhcmEgbyBoaXN0b2dyYW1hIHNvYnJlcG9zdG8NCmxpYnJhcnkoZ3JpZCkgIyBwYXJhIG8gaGlzdG9ncmFtYSBzb2JyZXBvc3RvDQpsaWJyYXJ5KGdyaWRFeHRyYSkgIyBwYXJhIG8gaGlzdG9ncmFtYSBzb2JyZXBvc3RvDQoNCmBgYA0KSnVzdGlmaWNhdGl2YSBkZSB1c286XA0KKipJU0xSKio6IHBvciBjb250ZXIgbyBkYXRhc2V0IHV0aWxpemFkbyBhcXVpO1wNCioqZGF0YS50YWJsZSoqOiB0ZW5obyBncmFuZGUgZmFtaWxpYXJpZGFkZSBjb20gZXNzYSBmb3JtYSBkZSBhcm1hemVuYXIgdGFiZWxhcy4gRW0gbWV1IHRyYWJhbGhvLCBlbSBnZXJhbCwgdXRpbGl6w6F2YW1vcyBkcGx5ci4gUG9yw6ltLCBjb25zdGF0YW1vcyBjZXJ0YSBsZW50aWTDo28gZW0gYWxndW5zIHByb2pldG9zLCBhbyBwYXNzbyBxdWUgZXN0w6F2YW1vcyB0cmFiYWxoYW5kbyBjb20gdW0gY29uanVudG8gZGUgZGFkb3MgbXVpdG8gZ3JhbmRlLiBTZSB0b3Jub3UgaMOhYml0byBsaW1wYXIgYSBtZW3Ds3JpYSwgY29tIHJtKCkgZSBnYygpLCBhbyBsb25nbyBkbyBzY3JpcHQsIHBhcmEgbsOjbyBoYXZlciBlc3RvdXJvIGRlIG1lbcOzcmlhLiBQZXNxdWlzYW5kbywgdmVyaWZpcXVlaSBxdWUgbyBkYXRhLnRhYmxlIGVyYSBhdMOpIDEweCBtYWlzIHLDoXBpZG8gZG8gcXVlIGFzIG91dHJhcyBvcMOnw7VlcywgY29tbyBkcGx5ciwgc3FsZGYgZSBvIGJhc2UgZG8gUjtcDQoqKnBzeWNoKio6IGJpYmxpb3RlY2EgY29uaGVjaWRhIHBlbG8gY29yci50ZXN0LCBxdWUgcmVhbGl6YSBvIHRlc3RlIGRlIGNvcnJlbGHDp8OjbywgZGV0ZXJtaW5hbmRvIHF1YWlzIGNvbHVuYXMgYXByZXNlbnRhbSBjb3JyZWxhw6fDo28gc2lnbmlmaWNhdGl2YTtcDQoqKmdncGxvdDIqKjogYmlibGlvdGVjYSBjb25oZWNpZGEgcG9yIHN1YSB2YXJpZWRhZGUgZGUgZ3LDoWZpY29zLiBTdWEgZnVuY3Rpb24gZ2dwbG90KCkgZm9pIHV0aWxpemFkYSBuYSBjb25zdHJ1w6fDo28gZG9zIHNlZ3VpbnRlcyBncsOhZmljb3M6IGhlYXRtYXAgKG1hcGEgZGUgY2Fsb3IpLCBzY2F0dGVycGxvdHMgKGdyw6FmaWNvIGRlIHBvbnRvcyksIGhpc3RvZ3JhbWFzIGUgYmFycGxvdHMgKGdyw6FmaWNvIGRlIGJhcnJhcykuXA0KQXMgc2VndWludGVzIGJpYmxpb3RlY2FzIGZvcmFtIHV0aWxpemFkYXMgcGFyYSBmYWNpbGl0YXIgYSBjb25zdHJ1w6fDo28gZG9zIGhpc3RvZ3JhbWFzIHNvYnJlcG9zdG9zOlwNCioqdGlkeXZlcnNlKio6IMOpIGltcG9ydGFudGUgbyBhbmFsaXN0YSBkZSBkYWRvcyBzZW1wcmUgc2UgYXRlbnRhciBhb3MgY29uZmxpdG9zIGVudHJlIHBhY290ZXMsIHF1YW5kbyBleGlzdGVtIG3DqXRvZG9zIGNvbSBtZXNtYXMgbm9tZW5jbGF0dXJhIChob23DtG5pbW9zKSwgY29uZm9ybWUgd2FybmluZyBhcMOzcyBvIGxpYnJhcnkodGlkeXZlcmUpLiBOb3RlIHVtYSBzw6lyaWUgZGUgbcOpdG9kb3MgaG9tw7RuaW1vcyBlbnRyZSBvIHRpZHl2ZXJzZSB2ZXJzdXMgZ2dwbG90MiwgZHBseXIsIGx1YnJpZGF0ZSBlIHB1cnIuXA0KKipncmlkKiouXA0KKipncmlkRXh0cmEqKi5cDQoNCiMjIyBFVEwgKEV4dHJhw6fDo28gLSBUcmFuc2Zvcm1hw6fDo28gLSBDYXJnYSk6XA0KDQoqKjIuIEluZGljYcOnw6NvIGRhIGZvbnRlIGRvcyBkYWRvcyoqDQoNCmBgYHtyfQ0KIyBJbmZvcm1hY29lcyBkbyBkYXRhc2V0IHVzYWRvDQo/Q2Fyc2VhdHMNCmBgYA0KTm8gY29kZSBjaHVuayBhY2ltYSwgcG9kZW1vcyB2ZXJpZmljYXIgcXVlIHNlIHRyYXRhIGRlIHVtIGNvbmp1bnRvIGRlIGRhZG9zIHNpbXVsYWRvcyBjb250ZW5kbyB2ZW5kYXMgZGUgYXNzZW50b3MgaW5mYW50aXMgZGUgY2Fycm8gZW0gNDAwIGRpZmVyZW50ZXMgbG9qYXMuIFwNCg0KDQoqKjMuIEFwcmVzZW50YXIgbyBtZXRhZGFkbyAoZGljaW9uw6FyaW8gZGUgZGFkb3MpIGRvIGRhdGFzZXQqKg0KDQpObyA/Q2Fyc2VhdHMsIGFjaW1hLCBjb25zZWd1aW1vcyBleHRyYWlyLCBkZW50cmUgb3V0cmFzIGluZm9ybWHDp8O1ZXMsIG9zIHNlZ3VpbnRlcyBtZXRhZGFkb3M6XA0KVGVtb3MgdW1hIHRhYmVsYSBkZSBkYWRvcyBjb250ZW5kbyA0MDAgb2JzZXJ2YcOnw7VlcyBlIGFzIHNlZ3VpbnRlcyAxMSB2YXJpw6F2ZWlzOlwNCioqU2FsZXMqKjogVmVuZGFzIHVuaXTDoXJpYXMgKGVtIG1pbGhhcmVzKSBkYSBsb2phO1wNCioqQ29tcFByaWNlKio6IFByZcOnbyBjb2JyYWRvIHBlbG8gY29uY29ycmVudGUgZGUgY2FkYSBsb2phO1wNCioqSW5jb21lKio6IE7DrXZlbCBkZSByZW5kYSBkYSBjb211bmlkYWRlIChlbSBtaWxoYXJlcyBkZSBkw7NsYXJlcyk7XA0KKipBZHZlcnRpc2luZyoqOiBPcsOnYW1lbnRvIGRlIHB1YmxpY2lkYWRlIGRlIGNhZGEgbG9qYSAoZW0gbWlsaGFyZXMgZGUgZMOzbGFyZXMpO1wNCioqUG9wdWxhdGlvbioqOiBQb3B1bGHDp8OjbyBuYSByZWdpw6NvIChlbSBtaWxoYXJlcyk7XA0KKipQcmljZSoqOiBQcmXDp28gcXVlIGEgZW1wcmVzYSBjb2JyYSBwZWxvcyBhc3NlbnRvcyBpbmZhbnRpcyBkZSBjYXJybztcDQoqKlNoZWx2ZUxvYyoqOiBVbSBmYXRvciBjb20gbsOtdmVpcyBSdWltLCBCb20gZSBNw6lkaW8gcXVlIGluZGljYSBhIHF1YWxpZGFkZSBkYSBsb2NhbGl6YcOnw6NvIGRhcyBlc3RhbnRlcyBkYXMgY2FkZWlyaW5oYXMgZW0gY2FkYSBsb2phO1wNCioqQWdlKio6IElkYWRlIG3DqWRpYSBkYSBwb3B1bGHDp8OjbyBsb2NhbDtcDQoqKkVkdWNhdGlvbioqOiBOw612ZWwgZGUgZWR1Y2HDp8OjbyBkZSBjYWRhIGxvY2FsaWRhZGU7XA0KKipVcmJhbioqOiBVbSBmYXRvciBjb20gbsOtdmVpcyBOw6NvIGUgU2ltIHBhcmEgaW5kaWNhciBzZSBhIGxvamEgZXN0w6EgbG9jYWxpemFkYSBlbSB1bWEgbG9jYWxpZGFkZSB1cmJhbmEgKFllcykgb3UgcnVyYWwgKG5vKTtcDQoqKlVTKio6IFVtIGZhdG9yIGNvbSBuw612ZWlzIE7Do28gZSBTaW0gcGFyYSBpbmRpY2FyIHNlIGEgbG9qYSBlc3TDoSBub3MgVVNBIChZZXMpIG91IG7Do28gKE5vKS5cDQoNCioqNC4gRXNjcmV2ZXIgbyBjw7NkaWdvIHBhcmEgYSBpbXBvcnRhw6fDo28gZG8gZGF0YXNldCAqKg0KDQoNCmBgYHtyfQ0KIyBDYXJyZWdhciBvIGRhdGFzZXQNCmRhdGEoIkNhcnNlYXRzIikNCnNldERUKENhcnNlYXRzKQ0KIyBFc3RydXR1cmEgZG8gYmFuY28gZGUgZGFkb3MNCnN0cihDYXJzZWF0cykgDQpgYGANCg0KIyMjIEFuw6FsaXNlIEV4cGxvcmF0w7NyaWEgZGUgRGFkb3MgYsOhc2ljYToNCg0KKio1LiBEZXN0YWNhciBvcyB2YWxvcmVzIG3DoXhpbW9zIGRhcyAyMCBwcmltZWlyYXMgbGluaGFzKioNCg0KYGBge3J9DQojIGF2YWxpYW5kbyBhcyAyMCBwcmltZWlyYXMgbGluaGFzDQpDYXJzZWF0c1sxOjIwLF0NCmBgYA0KYGBge3J9DQojIGRldGVjdGFuZG8gb3MgY2FtcG9zIG51bWVyaWNvczoNCmNhbXBvc19udW1lcmljb3MgPSBuYW1lcyhGaWx0ZXIoaXMubnVtZXJpYywgQ2Fyc2VhdHMpKQ0KY2FtcG9zX251bWVyaWNvcw0KYGBgDQpgYGB7cn0NCiMgZnVuw6fDo28gcGFyYSBkZXZvbHZlciBvcyB2YWxvcmVzIG3DoXhpbW9zIGRlIGNhZGEgY29sdW5hLCByZW1vdmVuZG8gb3MgTkENCmNvbE1heCA8LSBmdW5jdGlvbihkZikgc2FwcGx5KGRmLCBtYXgsIG5hLnJtID0gVFJVRSkNCiMgdmFsb3JlcyBtw6F4aW1vcyBkYXMgMjAgcHJpbWVpcmFzIGxpbmhhcyBkZXNzZXMgY2FtcG9zIG51bcOpcmljb3MNCmNvbE1heChDYXJzZWF0c1sxOjIwLCBuYW1lcyhDYXJzZWF0cykgJWluJSBjYW1wb3NfbnVtZXJpY29zLCB3aXRoPUZBTFNFXSkNCg0KYGBgDQoNCioqNi4gRGVzdGFjYXIgb3MgdmFsb3JlcyBtw61uaW1vcyBkYXMgMjAgw7psdGltYXMgbGluaGFzKioNCg0KYGBge3J9DQojIGZ1bsOnw6NvIHBhcmEgZGV2b2x2ZXIgb3MgdmFsb3JlcyBtw61uaW1vcyBkZSBjYWRhIGNvbHVuYSwgcmVtb3ZlbmRvIG9zIE5BDQpjb2xNaW4gPC0gZnVuY3Rpb24oZGYpIHNhcHBseShkZiwgbWluLCBuYS5ybSA9IFRSVUUpDQojIHZhbG9yZXMgbcOtbmltb3MgZGFzIDIwIMO6bHRpbWFzIGxpbmhhcyBkZXNzZXMgY2FtcG9zIG51bcOpcmljb3MNCmNvbE1pbihDYXJzZWF0c1sobnJvdyhDYXJzZWF0cyktMTkpOm5yb3coQ2Fyc2VhdHMpLCBuYW1lcyhDYXJzZWF0cykgJWluJSBjYW1wb3NfbnVtZXJpY29zLCB3aXRoPUZBTFNFXSkNCmBgYA0KKio3LiBMaXN0YXIgb3Mgbm9tZXMgZGFzIGNvbHVuYXMqKg0KDQpgYGB7cn0NCm5hbWVzKENhcnNlYXRzKQ0KYGBgDQoqKjguIFZlcmlmaWNhciBhIGRpbWVuc8OjbyBkbyBkYXRhc2V0ICh0b3RhbCBkZSBsaW5oYXMgZSBjb2x1bmFzKSoqDQoNCmBgYHtyfQ0KY2F0KCJOw7ptZXJvIGRlIGxpbmhhczogIiwgbnJvdyhtdGNhcnMpLCAiXG4iLCBzZXA9IiIpDQpjYXQoIk7Dum1lcm8gZGUgY29sdW5hczogIiwgbmNvbChtdGNhcnMpLCAiXG4iLCBzZXA9IiIpDQpgYGANCg0KKio5LiBDb250YXIgbyB0b3RhbCBkZSBhbW9zdHJhcyBwb3IgdW1hIGRhcyB2YXJpw6F2ZWlzIGNhdGVnw7NyaWNhcyB0YW50byBvcmRpbmFsIG91IG5vbWluYWwqKg0KDQpgYGB7cn0NCnRhYmxlKENhcnNlYXRzJFVyYmFuLCB1c2VOQT0iYWx3YXlzIikNCmBgYA0KDQoqKjEwLiBBcHJlc2VudGFyIGEgZXN0YXTDrXN0aWNhIGLDoXNpY2EgcGFyYSBvIGRhdGFzZXQg4oCTIHN1bW1hcnkoKSoqDQoNCmBgYHtyfQ0Kc3VtbWFyeShDYXJzZWF0cykNCmBgYA0Kw4kgcG9zc8OtdmVsIGFuYWxpc2FyIG8gcmFuZ2UgKGFtcGxpdHVkZSksIGNvbmNlbnRyYcOnw6NvIChhdHJhdsOpcyBkb3MgcXVhcnRpcykgZSBtw6lkaWEgZGUgY2FkYSBjYW1wbyBxdWFudGl0YXRpdm8gZSBhcyBjYXRlZ29yaWFzIHByZXNlbnRlcyBub3MgY2FtcG9zIHF1YWxpdGF0aXZvcy4gTm90ZSBhIGF1c8OqbmNpYSBkZSBkYWRvcyBmYWx0YW50ZXMgKE5BKSBubyBkYXRhc2V0LiBQYXJhIGNvbmZpcm1hciBlc3NhIGF1c8OqbmNpYSBkZSBOQSdzLCDDqSBwb3Nzw612ZWwgcm9kYXIgbyBzZWd1aW50ZSBzY3JpcHQ6XA0KDQpgYGB7cn0NCnN1bShpcy5uYShDYXJzZWF0cykpDQpgYGANCg0KDQoNCioqMTEuIFJlYWxpemFyIGEgYW7DoWxpc2UgZGUgY29ycmVsYcOnw6NvIHZpYSBsaW5oYSBkZSBjb21hbmRvKioNCg0KYGBge3J9DQojIGFuw6FsaXNlIGRlIGNvcnJlbGHDp8OjbyBlbnRyZSB0b2RvcyBvcyBjYW1wb3MgbnVtw6lyaWNvcw0KY2Fyc2VhdHNfbnVtZXJpY29zID0gQ2Fyc2VhdHNbLG5hbWVzKENhcnNlYXRzKSAlaW4lIGNhbXBvc19udW1lcmljb3MsIHdpdGg9RkFMU0VdDQpyb3VuZChjb3IoY2Fyc2VhdHNfbnVtZXJpY29zKSw0KQ0KYGBgDQoqKjHCqikgciA9IDAuNTg0OCoqIC0gTm90ZSBxdWUgYSBtYWlvciBjb3JyZWxhw6fDo28gb2NvcnJlIGVudHJlIHByZcOnbyBlIHByZcOnbyBkYSBjb25jb3Jyw6puY2lhIChQcmljZSB2cyBDb21wUHJpY2UsIGNvbSBjb3JyZWxhw6fDo28gPSAwLjU4NDgpLCBvIHF1ZSDDqSBiYXN0YW50ZSBsw7NnaWNvLCBhbyBwYXNzbyBxdWUgb3MgcHJlw6dvcyBkZXZlbSBzZWd1aXIgdW0gcGFkcsOjbyBwb3IgcmVnacOjbywgbG9nbywgcHJlw6dvcyBlbnRyZSBjb25jb3JyZW50ZXMgdGVuZGVtIGEgc2VyZW0gcmVsYWNpb25hZG9zLiBcDQoNCioqMsKqKSByID0gLTAuNDQ1MCoqIC0gQ29tbyBhIHNlZ3VuZGEgbWFpb3IgY29ycmVsYcOnw6NvLCB0ZW1vcyBhIGNvcnJlbGHDp8OjbyBlbnRyZSBwcmXDp28gZSB2ZW5kYXMgKFByaWNlIHZzIFNhbGVzLCBjb20gY29ycmVsYcOnw6NvID0gLTAuNDQ1MCkuIEVzc2EgcmVsYcOnw6NvIHRhbWLDqW0gw6kgZXNwZXJhZGEsIGFvIHBhc3NvIHF1ZSBxdWFuZG8gb3MgcHJlw6dvcyBjYWVtIGEgdGVuZMOqbmNpYSDDqSBkZSBxdWUgYXMgdmVuZGFzIGF1bWVudGVtLiBcDQoNCioqM8KqKSByID0gMC4yNjk1KiogLSBDb21vIGEgdGVyY2VpcmEgbWFpb3IgY29ycmVsYcOnw6NvLCB0ZW1vcyB2ZW5kYXMgdnMgcHVibGljaWRhZGUgKFNhbGVzIHZzIEFkdmVydGlzaW5nLCBjb20gY29ycmVsYcOnw6NvID0gMC4yNjk1KS4gSXNzbyByZWZsZXRlIG8gaW1wYWN0byBwb3NpdGl2byBkbyBvcsOnYW1lbnRvIGdhc3RvIGNvbSBjYW1wYW5oYXMgcHVibGljaXTDoXJpYXMgc29icmUgbyB0b3RhbCBkZSB2ZW5kYXMgZGUgY2FkZWlyYXMgaW5mYW50aXMgcGFyYSBjYXJyb3MuIFwNCg0KKio0wqopIHIgPSAwLjI2NTcqKiAtIENvcnJlbGHDp8OjbyBncmFuZGUgb2NvcnJlIGVudHJlIHBvcHVsYcOnw6NvIGUgcHVibGljaWRhZGUgKFBvcHVsYXRpb24gdnMgQWR2ZXJ0aXNpbmcpLiBBbyBwYXNzbyBxdWUgYSBwb3B1bGHDp8OjbyBhdW1lbnRhLCBvIGludmVzdGltZW50byBlbSBwdWJsaWNpZGFkZSB0YW1iw6ltIHRlbmRlIGEgc2VndWlyIGVzc2EgdGVuZMOqbmNpYS4NCg0KKio1wqopIHIgPSAtMC4yMzE4KiogLSDDiSBpbnRlcmVzc2FudGUgZGVzdGFjYXIgYSBjb3JyZWxhw6fDo28gZW50cmUgdmVuZGFzIGUgaWRhZGUgKFNhbGVzIHZzIEFnZSkuIE5hdHVyYWxtZW50ZSwgdW1hIHBvcHVsYcOnw6NvIG1haXMgdmVsaGEgdGVuZGUgYSB0ZXIgbWVub3MgZmlsaG9zIHBlcXVlbm9zLCBvIHF1ZSBsZXZhIGEgdGVuZMOqbmNpYSBkZSByZWR1emlyIGFzIGNvbXByYXMgZGUgY2FkZWlyYXMgaW5mYW50aXMgcGFyYSB2ZcOtY3Vsb3MgYXV0b21vdGl2b3MuIEVzc2EgcmVsYcOnw6NvIGVzdMOhIHByZXNlbnRlIGNvbSByID0gLTAuMjMxOCBkZSBjb3JyZWxhw6fDo28gZW50cmUgdmVuZGFzIGUgaWRhZGUuIFwNCg0KDQpgYGB7cn0NCiMgc29saWNpdGFuZG8gYSBhbsOhbGlzZSBkZSBjb3JyZWxhw6fDo28sIGVtIHRlcm1vcyBkZSBwLXZhbG9yOg0KYW5hbGlzZV9jb3JyZWxhY2FvID0gY29yci50ZXN0KHggPSBjYXJzZWF0c19udW1lcmljb3MsICMgRGFkb3MNCiAgICAgICAgICAgICAgICAgdXNlID0gImNvbXBsZXRlIiwgIyBDb21vIHZhbW9zIGxpZGFyIGNvbSBtaXNzaW5ncw0KICAgICAgICAgICAgICAgICBtZXRob2QgPSAicGVhcnNvbiIpICMgdGlwbyBkZSBjb3JyZWxhw6fDo28NCmBgYA0KDQpBbmFsaXNhbmRvIHAtdmFsb3JlcyBkZXNzYXMgY29ycmVsYcOnw7VlczoNCg0KYGBge3J9DQpyb3VuZChhbmFsaXNlX2NvcnJlbGFjYW8kcCw0KQ0KYGBgDQoNCkEgdGFiZWxhIGFiYWl4byBhcHJlc2VudGEgb3MgcC12YWxvcmVzIChwKSBhdHJhdsOpcyBkZSBhc3RlcmlzY29zLCBvbmRlOiBcIA0KDQpwIDwgMC4wMDEgcmV0b3JuYXLDoSB0csOqcyBhc3RlcmlzY29zLCBcDQoNCjAuMDAxIDwgcCA8IDAuMDEgcmV0b3JuYXLDoSBkb2lzIGFzdGVyaXNjb3MsIFwgDQoNCjAuMDEgPCBwIDwgMC4wNSByZXRvcm5hcsOhIHVtIGFzdGVyaXNjbzogXA0KDQoNCmBgYHtyfQ0KaWZlbHNlKGFuYWxpc2VfY29ycmVsYWNhbyRwIDwuMDAxLCAiKioqIiwgaWZlbHNlKGFuYWxpc2VfY29ycmVsYWNhbyRwIDwgMC4wMSwgIioqIiwgaWZlbHNlKGFuYWxpc2VfY29ycmVsYWNhbyRwIDwwLjA1LCAiKiIsICIiKSkpDQpgYGANCk5vdGUgYXMgNSBtYWlvcmVzIGNvcnJlbGHDp8O1ZXMgZGVzdGFjYWRhcywgZSBleHBsaWNhZGFzIGFudGVyaW9ybWVudGUsIGNvbW8gb2J0ZW5kbyBwLXZhbG9yZXMgPDAuMDAxLiBDb20gdW1hIGNvcnJlbGHDp8OjbyBxdWFzZSBib3JkZXJsaW5lLCBtYXMgYWluZGEgYXNzaW0sIHNpZ25pZmljYW50ZSBhIDUlLCBub3RlIHByZcOnbyB2ZXJzdXMgaWRhZGUsIHNpZ25pZmljYXRpdm8gYSA1JSwgbWFzIGJlbSBwcsOzeGltbyBkYSBib3JkYSAocC12YWxvciA9IDAuMDQxMSkuIENvbSByID0gLTAuMTAyMiwgcG9wdWxhw6fDtWVzIG1haXMgdmVsaGFzIHRlbmRlbSBhIHBhZ2FyIG1lbm9zIHBvciBjYWRlaXJhcyBpbmZhbnRpcyBwYXJhIGNhcnJvLiBJc3NvIGxldmFudGEgdW1hIGhpcMOzdGVzZSBkZSBjYXVzYSBzZWd1aW5kbyBvcyBwcmluY8OtcGlvcyBkYSBsZWkgZGEgb2ZlcnRhIHggZGVtYW5kYSwgb25kZSBwb3B1bGHDp8OjbyBlbnZlbGhlY2VuZG8gdGVuZGUgYSBjb21wcmFyIG1lbm9zIGNhZGVpcmFzIGluZmFudGlzIHBvciB0ZXJlbSBtZW5vcyBmaWxob3MgcGVxdWVub3MsIG8gcXVlIGluZmxhIG9zIGVzdG9xdWVzLCBvIHF1ZSBvYnJpZ2EgYSBiYWl4YXJlbSBvcyBwcmXDp29zLg0KDQoNCiMjIyBBbsOhbGlzZSBFeHBsb3JhdMOzcmlhIGRlIERhZG9zIGNvbSBncsOhZmljb3MNCg0KKipBbsOhbGlzZSBkZSBDb3JyZWxhw6fDo286KioNCg0KKioxMi4gQ3JpYXIgdW0gZ3LDoWZpY28gaGVhdG1hcCBhIHBhcnRpciBkYXMgdmFyacOhdmVpcyB1c2FkYXMgbmEgY29ycmVsYcOnw6NvKioNCg0KYGBge3J9DQojTWFwYSBkZSBjYWxvciAoaGVhdCBtYXApIElyaXMNCiNsaWJyYXJ5KGdncGxvdDIpICMgcGFyYSBvIG1hcGEgZGUgY2Fsb3IgKGhlYXRtYXApDQojbGlicmFyeShyZXNoYXBlMikgIyByZXNoYXBlMjo6bWVsdCBmb2kgc3Vic3RpdHXDrWRvIHBvciBkYXRhLnRhYmxlOjptZWx0DQoNCmRhdCA8LSBjYXJzZWF0c19udW1lcmljb3MNCmNvciA8LSBkYXRhLnRhYmxlOjptZWx0KGNvcihkYXQsIHVzZT0icCIpKQ0KaGVhZChjb3IpDQpoZWF0IDwtIGdncGxvdChkYXRhPWNvciwgYWVzKHg9VmFyMSwgeT1WYXIyLCBmaWxsPXZhbHVlKSkgDQpoZWF0ICsgZ2VvbV90aWxlKCkgKyBsYWJzKHggPSAiIiwgeSA9ICIiKSArIHNjYWxlX2ZpbGxfZ3JhZGllbnQyKGxpbWl0cz1jKC0xLCAxKSkNCg0KYGBgDQoNCkRlc3RhcXVlIHBhcmEgY29ycmVsYcOnw7VlcyBuZWdhdGl2YXMgZm9ydGVzIGVudHJlIHZlbmRhcyBlIHByZcOnbyAoU2FsZXMgdnMgUHJpY2UpIGUgY29ycmVsYcOnw7VlcyBwb3NpdGl2YXMgZm9ydGVzIGVudHJlIHByZcOnbyBlIHByZcOnbyBkYSBjb25jb3Jyw6puY2lhIChQcmljZSB2cyBDb21wUHJpY2UpLiBUYWlzIHNpdHVhw6fDtWVzIGrDoSBmb3JhbSBhbmFsaXNhZGFzIGFudGVyaW9ybWVudGUuIE8gbWFwYSBkZSBjYWxvciBhY2ltYSBhcHJlc2VudGEgdW0gYm9tIGFwZWxvIHZpc3VhbCwgdG9ybmFuZG8gYSBpbnRlcnByZXRhw6fDo28gbWFpcyByw6FwaWRhLlwNCg0KDQoqKjEzLiBDcmlhciB1bSBzY2F0dGVycGxvdCBwYXJhIG8gcGFyIGRlIHZhcmnDoXZlaXMgY29tIG1haW9yIGNvcnJlbGHDp8OjbyoqXA0KDQpgYGB7cn0NCiMgc2NhdHRlcnBsb3QgUHJpY2UgdnMgQ29tcFByaWNlDQojIEdyw6FmaWNvIGRlIGRpc3BlcnPDo28gLSBzY2F0dGVycGxvdA0KcGxvdChjYXJzZWF0c19udW1lcmljb3MkUHJpY2UsY2Fyc2VhdHNfbnVtZXJpY29zJENvbXBQcmljZSwgeGxhYj0iUHJlw6dvIiwgeWxhYj0iUHJlw6dvIGRvcyBDb25jb3JyZW50ZXMiKQ0KYGBgDQpDb25mb3JtZSBqw6EgdmlzdWFsaXphZG8gbmEgYW7DoWxpc2UgZGUgY29ycmVsYcOnw6NvLCBub3RlIGEgY29ycmVsYcOnw6NvIHBvc2l0aXZhIGVudHJlIHByZcOnbyBkb3MgYXNzZW50b3MgaW5mYW50aXMgdmVyc3VzIHByZcOnbyBkYSBjb25jb3Jyw6puY2lhLCBvIHF1ZSBpbmRpY2EgcXVlIG9zIHByZcOnb3MgZW50cmUgY29uY29ycmVudGVzIGxvY2FpcyBzw6NvIGNvcnJlbGFjaW9uYWRvcy5cDQoNCg0KKioxNC4gUmVhbGl6YXIgYSBhbsOhbGlzZSBiaXZhcmlhZGEgcG9yIG1laW8gZGUgc2NhdHRlcnBsb3RzIHBhcmEgZXhpYmlyIGEgZGlzdHJpYnVpw6fDo28gZG9zIGRhZG9zIGVudHJlIGFzIHByaW5jaXBhaXMgdmFyacOhdmVpcyBjYXRlZ8OzcmljYXMuIFV0aWxpemUgY29yZXMgZSBhbHRlcmUgbyB0YW1hbmhvIGRvcyBwb250b3MgcGFyYSBmYWNpbGl0YXIgYSBpbnRlcnByZXRhw6fDo28qKlwNCg0KYGBge3J9DQojIFZldG9yIGRlIGNvcmVzIGNvbSBiYXNlIG5hcyBjbGFzc2VzIGRlIFNoZWx2ZUxvYyAoUXVhbGlkYWRlIGRhIGxvY2FsaXphw6fDo28gZGFzIGVzdGFudGVzKQ0KY29yZXMgPC0gYygiQmFkIiA9ICJyZWQiLCAiTWVkaXVtIiA9ICJ5ZWxsb3ciLCAiR29vZCIgPSAiZ3JlZW4iKQ0KY29yZXNfcG9udG9zIDwtIHNjYWxlX2NvbG91cl9tYW51YWwobmFtZT0iU2hlbHZlTG9jIiwgdmFsdWVzPWNvcmVzKQ0KDQojQWx0ZXJhbmRvIG8gdGFtYW5obyBkb3MgcG9udG9zDQpnZ3Bsb3QoQ2Fyc2VhdHMsIGFlcyh4PVByaWNlLCB5PVNhbGVzLCBzaGFwZSA9IFNoZWx2ZUxvYykpICsgDQogIGdlb21fcG9pbnQoc2l6ZSA9IDIsIGFscGhhPSBJKDAuOCksIGFlcyhjb2xvdXIgPSBTaGVsdmVMb2MpKSArDQogIGNvcmVzX3BvbnRvcyArICANCiAgeGxhYigiUHJlw6dvIHBvciBhc3NlbnRvIChVUyQpIikgKyAgDQogIHlsYWIoIlZlbmRhcyAobWlsaGFyZXMgZGUgdW5pZGFkZXMpIikgDQoNCmBgYA0KDQpMb2dvLCDDqSBwZXJjZXB0w612ZWwgYSByZWxhw6fDo28gZGUgcHJlw6dvcyBncmFuZGVzIGUgdmVuZGFzIGJhaXhhcyBjb20gZXN0YW50ZXMgbWFsIGxvY2FsaXphZGFzLiDDiSBwb3Nzw612ZWwgcXVlIGEgcXVhbGlkYWRlIGRhIGxvY2FsaXphw6fDo28gZGEgZXN0YW50ZSBlc3RlamEgaW1wYWN0YW5kbyBuYXMgdmVuZGFzLCBlIG7Do28gYXBlbmFzIG8gcHJlw6dvIHVuaXTDoXJpby5cDQoNCg0KIyMjIEFuw6FsaXNlIGRlIERpc3RyaWJ1acOnw6NvOg0KDQoqKjE1LiBSZWFsaXphciBhIGFuw6FsaXNlIHVuaXZhcmlhZGEgY29tIHVtIGhpc3RvZ3JhbWEgcGFyYSB1bWEgdmFyacOhdmVsIG51bcOpcmljYSoqXA0KDQpgYGB7cn0NCiMgQ3JpYXIgbyBoaXN0b2dyYW1hIGNvbSBpbnRlcnZhbG9zIGRlIDAsNQ0KaGlzdChDYXJzZWF0cyRTYWxlcywgYnJlYWtzID0gc2VxKG1pbihDYXJzZWF0cyRTYWxlcyksIA0KICAgICBtYXgoQ2Fyc2VhdHMkU2FsZXMpICsgMC41LCBieSA9IDAuNSksIHhsaW0gPSBjKDAsIG1heChDYXJzZWF0cyRTYWxlcykrMyksIG1haW4gPSAiIiwgDQogICAgIHhsYWIgPSAiVmVuZGFzIChtaWxoYXJlcyBkZSB1bmlkYWRlcykiLCB5bGFiID0gIkZyZXF1w6puY2lhIiwgY29sID0gImxpZ2h0Ymx1ZSIpDQoNCmBgYA0KTm90ZSBhIGNvbmNlbnRyYcOnw6NvIGRhIHF1YW50aWEgZGUgdmVuZGFzIGRhIGxvamEgYmFzdGFudGUgcHJlc2VudGUgZW50cmUgNSBlIDEwIG1pbCB1bmlkYWRlcy5cDQoNCioqMTYuIEFwcmVzZW50YXIgZW0gYXBlbmFzIHVtIGdyw6FmaWNvIHbDoXJpb3MgaGlzdG9ncmFtYXMgcGFyYSBhcyB2YXJpw6F2ZWlzIG51bcOpcmljYXMqKlwNCg0KQXByZXNlbnRhbmRvIGdyw6FmaWNvcyBkZSBtYW5laXJhIMO6bmljYSwgcGFyYSB2aXN1YWxpemHDp8OjbyBkZSB0b2RvcyBvcyBjYW1wb3MgbnVtw6lyaWNvczpcDQoNCmBgYHtyfQ0KIyBIaXN0b2dyYW1hcyBkZSB0b2RhcyB2YXJpw6F2ZWlzIG51bcOpcmljYXMNCnBhcihtZnJvdyA9IGMoMiwzKSkNCmhpc3QoY2Fyc2VhdHNfbnVtZXJpY29zJFNhbGVzLCB4bGFiPSJWZW5kYXMiLCBtYWluPSIiLCBjb2w9ImJsdWUiKQ0KaGlzdChjYXJzZWF0c19udW1lcmljb3MkQ29tcFByaWNlLCB4bGFiPSJQcmXDp28gZGEgQ29uY29ycsOqbmNpYSIsIG1haW49IiIsIGNvbD0ibGlnaHRibHVlIikNCmhpc3QoY2Fyc2VhdHNfbnVtZXJpY29zJEluY29tZSwgeGxhYj0iUmVuZGEgY29tdW5pdMOhcmlhIChVUyQgbWlsaGFyZXMpIiwgbWFpbj0iIiwgY29sPSJibHVlIikNCmhpc3QoY2Fyc2VhdHNfbnVtZXJpY29zJEFkdmVydGlzaW5nLCB4bGFiPSJPcsOnYW1lbnRvIHB1YmxpY2l0w6FyaW8gKFVTJCBtaWxoYXJlcykiLCBtYWluPSIiLCBjb2w9ImxpZ2h0Ymx1ZSIpDQpoaXN0KGNhcnNlYXRzX251bWVyaWNvcyRQb3B1bGF0aW9uLCB4bGFiPSJQb3B1bGHDp8OjbyAobWlsaGFyZXMpIiwgbWFpbj0iIiwgY29sPSJibHVlIikNCmhpc3QoY2Fyc2VhdHNfbnVtZXJpY29zJFByaWNlLCB4bGFiPSJQcmXDp28gZG8gYXNzZW50byBpbmZhbnRpbCIsIG1haW49IiIsIGNvbD0ibGlnaHRibHVlIikNCmhpc3QoY2Fyc2VhdHNfbnVtZXJpY29zJEFnZSwgeGxhYj0iSWRhZGUgbcOpZGlhIHBvcHVsYWNpb25hbCIsIG1haW49IiIsIGNvbD0iYmx1ZSIpDQpoaXN0KGNhcnNlYXRzX251bWVyaWNvcyRFZHVjYXRpb24sIHhsYWI9Ik7DrXZlbCBlZHVjYWNpb25hbCIsIG1haW49IiIsIGNvbD0ibGlnaHRibHVlIikNCmBgYA0KUG9yw6ltLCDDqSBwb3Nzw612ZWwgdXRpbGl6YXIgaGlzdG9ncmFtYXMgc29icmVwb3N0b3MgcGFyYSBjb21wYXJhw6fDo28gZGUgY2FtcG9zIG51bcOpcmljb3MgZW50cmUgZ3J1cG9zLCBjb21vIMOpIGZlaXRvIGFiYWl4bzpcDQoNCmBgYHtyfQ0KIyBTZXLDoSBxdWUgTG9qYXMgVXJiYW5hcyB2ZW5kZW0gbWFpcyBvdSBtZW5vcyBkbyBxdWUgTG9qYXMgUnVyYWlzPyANCiMgQWzDqW0gZGlzc28sIExvamFzIFVyYmFuYXMgY29udMOqbSBwcmXDp29zIG1haW9yZXMgb3UgbWVub3JlcyBkbyBxdWUgTG9qYXMgUnVyYWlzPw0KIyBTZXLDoSBxdWUgTG9qYXMgQW1lcmljYW5hcyB2ZW5kZW0gbWFpcyBvdSBtZW5vcyBkbyBxdWUgTG9qYXMgTsOjbyBBbWVyaWNhbmFzPw0KIyBBbMOpbSBkaXNzbywgcXVhaXMgYXByZXNlbnRhbSBwcmXDp29zIG1haW9yZXM/DQojIFNlcsOhIHF1ZSBMb2phcyBjb20gRXN0YW50ZXMgYmVtIGxvY2FsaXphZGFzIHZlbmRlbSBtYWlzPyBFIGVsYXMgdMOqbSBwcmXDp29zIG1haW9yZXM/DQojSGlzdG9ncmFtYXMNCiNsaWJyYXJ5KHRpZHl2ZXJzZSkNCiNsaWJyYXJ5KGdyaWQpDQojbGlicmFyeShncmlkRXh0cmEpDQpoMSA8LSBDYXJzZWF0cyAlPiUgDQogIGdncGxvdChhZXMoU2FsZXMpKSArIGdlb21faGlzdG9ncmFtKGFlcyhmaWxsPVVyYmFuKSwgYmlud2lkdGg9MSwgY29sPSJibGFjayIpICsNCiAgZ2VvbV92bGluZShhZXMoeGludGVyY2VwdD1tZWFuKFNhbGVzKSksIGxpbmV0eXBlPSJkYXNoZWQiLCBjb2xvcj0iYmxhY2siKSArIGxhYnMoeD0iVmVuZGFzIChtaWxoYXJlcyBkZSB1bmlkYWRlcykiLCB5PSJGcmVxdcOqbmNpYSIpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIikNCmgyIDwtIENhcnNlYXRzICU+JSANCiAgZ2dwbG90KGFlcyhQcmljZSkpICsgZ2VvbV9oaXN0b2dyYW0oYWVzKGZpbGw9VXJiYW4pLCBiaW53aWR0aD0xMCwgY29sPSJibGFjayIpICsNCiAgZ2VvbV92bGluZShhZXMoeGludGVyY2VwdD1tZWFuKFByaWNlKSksIGxpbmV0eXBlPSJkYXNoZWQiLCBjb2xvcj0iYmxhY2siKSArIGxhYnMoeD0iUHJlw6dvIChVUyQpIiwgeT0iRnJlcXXDqm5jaWEiKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0icmlnaHQiKQ0KZ3JpZC5hcnJhbmdlKGgxLGgyLCBucm93PTEsIHRvcD10ZXh0R3JvYigiIikpDQpgYGANCg0KYGBge3J9DQpoMSA8LSBDYXJzZWF0cyAlPiUgDQogIGdncGxvdChhZXMoU2FsZXMpKSArIGdlb21faGlzdG9ncmFtKGFlcyhmaWxsPVVTKSwgYmlud2lkdGg9MSwgY29sPSJibGFjayIpICsNCiAgZ2VvbV92bGluZShhZXMoeGludGVyY2VwdD1tZWFuKFNhbGVzKSksIGxpbmV0eXBlPSJkYXNoZWQiLCBjb2xvcj0iYmxhY2siKSArIGxhYnMoeD0iVmVuZGFzIChtaWxoYXJlcyBkZSB1bmlkYWRlcykiLCB5PSJGcmVxdcOqbmNpYSIpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIikNCmgyIDwtIENhcnNlYXRzICU+JSANCiAgZ2dwbG90KGFlcyhQcmljZSkpICsgZ2VvbV9oaXN0b2dyYW0oYWVzKGZpbGw9VVMpLCBiaW53aWR0aD0xMCwgY29sPSJibGFjayIpICsNCiAgZ2VvbV92bGluZShhZXMoeGludGVyY2VwdD1tZWFuKFByaWNlKSksIGxpbmV0eXBlPSJkYXNoZWQiLCBjb2xvcj0iYmxhY2siKSArIGxhYnMoeD0iUHJlw6dvIChVUyQpIiwgeT0iRnJlcXXDqm5jaWEiKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0icmlnaHQiKQ0KZ3JpZC5hcnJhbmdlKGgxLGgyLCBucm93PTEsIHRvcD10ZXh0R3JvYigiIikpDQpgYGANCg0KYGBge3J9DQpoMSA8LSBDYXJzZWF0cyAlPiUgDQogIGdncGxvdChhZXMoU2FsZXMpKSArIGdlb21faGlzdG9ncmFtKGFlcyhmaWxsPVNoZWx2ZUxvYyksIGJpbndpZHRoPTEsIGNvbD0iYmxhY2siKSArDQogIGdlb21fdmxpbmUoYWVzKHhpbnRlcmNlcHQ9bWVhbihTYWxlcykpLCBsaW5ldHlwZT0iZGFzaGVkIiwgY29sb3I9ImJsYWNrIikgKyBsYWJzKHg9IlZlbmRhcyAobWlsaGFyZXMgZGUgdW5pZGFkZXMpIiwgeT0iRnJlcXXDqm5jaWEiKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIpDQpoMiA8LSBDYXJzZWF0cyAlPiUgDQogIGdncGxvdChhZXMoUHJpY2UpKSArIGdlb21faGlzdG9ncmFtKGFlcyhmaWxsPVNoZWx2ZUxvYyksIGJpbndpZHRoPTEwLCBjb2w9ImJsYWNrIikgKw0KICBnZW9tX3ZsaW5lKGFlcyh4aW50ZXJjZXB0PW1lYW4oUHJpY2UpKSwgbGluZXR5cGU9ImRhc2hlZCIsIGNvbG9yPSJibGFjayIpICsgbGFicyh4PSJQcmXDp28gKFVTJCkiLCB5PSJGcmVxdcOqbmNpYSIpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJyaWdodCIpDQpncmlkLmFycmFuZ2UoaDEsaDIsIG5yb3c9MSwgdG9wPXRleHRHcm9iKCIiKSkNCmBgYA0KKipVcmJhbioqOiBIw6EgYmFzdGFudGUgc2ltaWxhcmlkYWRlIG5hcyB2ZW5kYXMgZSBwcmXDp29zIGRlIGxvamFzIHVyYmFuYXMgZSBydXJhaXMuXA0KKipVUyoqOiBKw6EgbmEgYW7DoWxpc2UgZGEgbmFjaW9uYWxpZGFkZSBkYXMgbG9qYXMsIMOpIHBvc3PDrXZlbCBub3RhciB1bSB2b2x1bWUgZGUgdmVuZGFzIHVtIHBvdWNvIG1haW9yIHBhcmEgYXMgbG9qYXMgYW1lcmljYW5hcyBkbyBxdWUgcGFyYSBhcyBuw6NvIGFtZXJpY2FuYXMuIE1hcyBhIGRpZmVyZW7Dp2EgcGFyZWNlIHN1dGlsLiBcDQoqKlNoZWx2ZWxvYyoqOiBOYSBtZXNtYSBsaW5oYSBkbyBzY2F0dGVycGxvdCBhbnRlcmlvcm1lbnRlIHZpc3VhbGl6YWRvLCBub3MgaGlzdG9ncmFtYXMgw6kgcG9zc8OtdmVsIG5vdGFyIHVtIGNvbXBvcnRhbWVudG8gdmlzw612ZWwgbm8gYXVtZW50byBkZSB2ZW5kYXMgYW8gcGFzc28gcXVlIGEgbG9jYWxpemHDp8OjbyBkbyBwcm9kdXRvIG5hcyBlc3RhbnRlcyBmaXF1ZWkgbWVsaG9yLiBcDQoNCioqMTcuIFZlcmlmaWNhciBjb20gYm94cGxvdHMgYSBwcmVzZW7Dp2EgZGUgcG9zc8OtdmVpcyBvdXRsaWVycyoqXA0KDQpgYGB7cn0NCiMgQm94cGxvdHMgZGUgdG9kYXMgdmFyacOhdmVpcyBudW3DqXJpY2FzDQpwYXIobWZyb3cgPSBjKDIsMykpDQpib3hwbG90KGNhcnNlYXRzX251bWVyaWNvcyRTYWxlcywgeGxhYj0iVmVuZGFzIiwgbWFpbj0iIiwgY29sPSJibHVlIikNCmJveHBsb3QoY2Fyc2VhdHNfbnVtZXJpY29zJENvbXBQcmljZSwgeGxhYj0iUHJlw6dvIGRhIENvbmNvcnLDqm5jaWEiLCBtYWluPSIiLCBjb2w9ImxpZ2h0Ymx1ZSIpDQpib3hwbG90KGNhcnNlYXRzX251bWVyaWNvcyRJbmNvbWUsIHhsYWI9IlJlbmRhIGNvbXVuaXTDoXJpYSAoVVMkIG1pbGhhcmVzKSIsIG1haW49IiIsIGNvbD0iYmx1ZSIpDQpib3hwbG90KGNhcnNlYXRzX251bWVyaWNvcyRBZHZlcnRpc2luZywgeGxhYj0iT3LDp2FtZW50byBwdWJsaWNpdMOhcmlvIChVUyQgbWlsaGFyZXMpIiwgbWFpbj0iIiwgY29sPSJsaWdodGJsdWUiKQ0KYm94cGxvdChjYXJzZWF0c19udW1lcmljb3MkUG9wdWxhdGlvbiwgeGxhYj0iUG9wdWxhw6fDo28gKG1pbGhhcmVzKSIsIG1haW49IiIsIGNvbD0iYmx1ZSIpDQpib3hwbG90KGNhcnNlYXRzX251bWVyaWNvcyRQcmljZSwgeGxhYj0iUHJlw6dvIGRvIGFzc2VudG8gaW5mYW50aWwiLCBtYWluPSIiLCBjb2w9ImxpZ2h0Ymx1ZSIpDQpib3hwbG90KGNhcnNlYXRzX251bWVyaWNvcyRBZ2UsIHhsYWI9IklkYWRlIG3DqWRpYSBwb3B1bGFjaW9uYWwiLCBtYWluPSIiLCBjb2w9ImJsdWUiKQ0KYm94cGxvdChjYXJzZWF0c19udW1lcmljb3MkRWR1Y2F0aW9uLCB4bGFiPSJOw612ZWwgZWR1Y2FjaW9uYWwiLCBtYWluPSIiLCBjb2w9ImxpZ2h0Ymx1ZSIpDQpgYGANCk91dGxpZXJzIGVuY29udHJhZG9zOlwNCg0KKipWZW5kYXMqKjogZG9pcyBvdXRsaWVycyBzdXBlcmlvcmVzLCByZXByZXNlbnRhbmRvIGR1YXMgbG9qYXMgY29tIG1haXMgZGUgMTUgbWlsIHZlbmRhcztcDQoqKlByZcOnbyBkYSBDb25jb3Jyw6puY2lhKio6IHVtIG91dGxpZXIgc3VwZXJpb3IgZSB1bSBvdXRsaWVyIGluZmVyaW9yLCByZXByZXNlbnRhbmRvLCByZXNwZWN0aXZhbWVudGUsIHVtYSBsb2phIGNvbSBjb25jb3JyZW50ZSBjb2JyYW5kbyB1bSB2YWxvciBtdWl0byBhbHRvIG5hIGNhZGVpcmEgaW5mYW50aWwgcGFyYSBjYXJybyAobWFpcyBkbyBxdWUgVVMkIDE2MCkgZSB1bWEgbG9qYSBjb20gY29uY29ycmVudGUgY29icmFuZG8gdW0gdmFsb3IgbXVpdG8gYmFpeG8gbm8gaXRlbSAobWVub3MgZGUgVVMkIDgwKS5cDQoqKlByZcOnbyoqOiBkb2lzIG91dGxpZXJzIHN1cGVyaW9yZXMsIGNvYnJhbmRvIG1haXMgZGUgVVMkIDE2MCBlIHRyw6pzIG91dGxpZXJzIGluZmVyaW9yZXMsIGNvYnJhbmRvIG1lbm9zIGRlIFUkIDYwIG5vIHByb2R1dG8uIFwNCg0KDQojIyMgR3LDoWZpY29zIGRlIGNsYXNzaWZpY2HDp8OjbzoNCg0KKioxOC4gQ3JpYXIgdW0gZ3LDoWZpY28gZGUgYmFycmFzIG91IGNvbHVuYXMqKlwNCg0KYGBge3J9DQojR3LDoWZpY28gZGUgYmFycmFzIGRlIHZlbmRhcyBkZW50cm8gZSBmb3JhIGRvcyBVUw0KZ2dwbG90KENhcnNlYXRzLCBhZXMoeCA9IFVTKSkgKyANCiAgZ2VvbV9iYXIoKSArIA0KICB0aGVtZV9jbGFzc2ljKCkgKw0KICBnZ3RpdGxlKCJVUyIpICsNCiAgeGxhYigiVVMiKSArIA0KICB5bGFiKCJGcmVxdWVuY3kiKSArIA0KICBnZW9tX3RleHQoc3RhdD0gImNvdW50IixhZXMobGFiZWwgPSAuLmNvdW50Li4pLCB2anVzdCA9IC0xKSArIGNvb3JkX2NhcnRlc2lhbih5bGltID0gYygwLCBtYXgodGFibGUoQ2Fyc2VhdHMkVVMpKjEuMSkpKSANCg0KYGBgDQpOb3RlIGEgcHJlZG9taW7Dom5jaWEgZGUgbG9qYXMgYW1lcmljYW5hcyBuYSBhbW9zdHJhLiBcDQoNCiMjIyBHcsOhZmljb3MgZGUgcGFydGVzIG91IHNldG9yZXM6DQoNCioqMTkuIENyaWFyIHVtIGdyw6FmaWNvIGRlIHBpenphIG91IHNldG9yZXMgcGFyYSB1bWEgdmFyacOhdmVsIGNhdGVnw7NyaWNhIGNvbSBwb3JjZW50YWdlbnMqKlwNCg0KYGBge3J9DQpwaWUodGFibGUoQ2Fyc2VhdHMkVXJiYW4pLCANCiAgICBsYWJlbHMgPSBwYXN0ZShuYW1lcyh0YWJsZShDYXJzZWF0cyRVcmJhbikpLCAiIC0gIiwNCiAgICAgICAgICAgICAgICAgICB0YWJsZShDYXJzZWF0cyRVcmJhbiksIiAoIiwgMTAwKnRhYmxlKENhcnNlYXRzJFVyYmFuKS9zdW0odGFibGUoQ2Fyc2VhdHMkVXJiYW4pKSwgIiUpIiwgc2VwPSIiKSwgY29sPWMoImJsYWNrIiwgIndoaXRlIiksIG1haW4gPSAiTG9qYSB1cmJhbmEiKQ0KYGBgDQrDiSBwb3Nzw612ZWwgY29uc3RhdGFyIHF1ZSA3MC41JSAoMjgyIGxvamFzKSBkYSBhbW9zdHJhIMOpIHJlZmVyZW50ZSBhIGxvamFzIHNpdHVhZGFzIG5hIHJlZ2nDo28gdXJiYW5hIGRhIGNpZGFkZSwgZW0gY29udHJhcG9udG8gYW9zIDI5LjUlICgxMTggbG9qYXMpIHNpdHVhZG9zIG5hIHJlZ2nDo28gcnVyYWwuXA0KDQojIyMgQW7DoWxpc2UgZG9zIERhZG9zOg0KDQoqKjIwLiBJbnRlcnByZXRhciBvcyBncsOhZmljb3MgZ2VyYWRvcyoqXA0KSW50ZXJwcmV0YcOnw6NvIGVmZXR1YWRhIGFvIGxvbmdvIGRvIGRvY3VtZW50by4gXA0K