Visão rápida
- O
ggplot2
foi criado por Hadley Wickham, baseado no livro Grammar of Graphics, de Leland Wilkinson.
- A ideia é pensar em um “gráfico” como uma estrutura racional, composta por camadas.
- Não precisa ter todas as camadas, mas três são obrigatórias:
- data: o conjunto de dados deve ser uma
tibble
(ou, pelo menos, um dataframe
);
- aesthetics: mapeamentos estéticos, como selecionamento das variáveis em questão;
- geometries: são as formas do gráfico (pontos, boxplot, histograma, etc)
- Não veremos todos os gráficos, apenas alguns poucos;
- Apenas tangenciaremos o pacote !!
Queira comunicar, não “lacrar”!
Antes de começar
- Vamos carregar a biblioteca do
tidyverse
, não apenas a do ggplot
.
- Isso se deve ao fato de sempre precisarmos manipular os dados antes de plotar algum gráfico.
O código abaixo carrega o pacote:
Registered S3 methods overwritten by 'dbplyr':
method from
print.tbl_lazy
print.tbl_sql
-- Attaching packages ---------------------------------- tidyverse 1.3.1 --
v ggplot2 3.3.5 v purrr 0.3.4
v tibble 3.1.3 v dplyr 1.0.7
v tidyr 1.1.3 v stringr 1.4.0
v readr 2.0.0 v forcats 0.5.1
-- Conflicts ------------------------------------- tidyverse_conflicts() --
x dplyr::filter() masks stats::filter()
x dplyr::lag() masks stats::lag()
- Depois disso, usaremos a função
read_csv()
para acessar o conjunto de dados.
- Usaremos o conjunto de dados
milhas_por_galao.csv
, bem como o conjunto de dados notas_disciplinas.csv
, ambos disponíveis em nosso repositório: Dados.csv.
- O primeiro conjunto de dados, nomearemos por
dados_mpg
e o segundo por dados_notas
# Lendo o conjunto `milhas_por_galao.csv`
dados_mpg <- read_csv("dados/milhas_por_galao.csv")
Rows: 234 Columns: 11
-- Column specification ---------------------------------------------------
Delimiter: ","
chr (6): fabricante, modelo, transmissao, tracao, combustivel, classe
dbl (5): cilindrada, ano, cilindros, cidade, rodovia
i Use `spec()` to retrieve the full column specification for this data.
i Specify the column types or set `show_col_types = FALSE` to quiet this message.
# Lendo o conjunto de dados `notas_disciplina.csv`
dados_notas <- read_csv("dados/notas_disciplina.csv")
Rows: 20 Columns: 12
-- Column specification ---------------------------------------------------
Delimiter: ","
chr (1): Curso
dbl (11): 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020
i Use `spec()` to retrieve the full column specification for this data.
i Specify the column types or set `show_col_types = FALSE` to quiet this message.
- O dataset
dados_mpg
ficaria assim:
- Já o dataset
dados_notas
, assim:
- veja que esse conjunto de dados não está na forma
tidy
.
Sintax do ggplot
ggplot(dados, mapping = aes(x = ..., y = ..., outras = ...)) +
geom_funcao(opções)
Ou, usando o pipe:
dados %>%
ggplot(mapping = aes(x = ..., y = ..., outras = ...)) +
geom_funcao(opções)
- Note que usamos o sinal “
+
” para adicionar as camadas do ggplot
.
- Para ficar mais explícito, às vezes usaremos assim:
dados %>%
ggplot() +
aes(x = ..., y = ..., outras = ...) +
geom_funcao(opções)
1. Gráficos de Pontos
- Também são chamados de “gráfico de dispersão” (scatter plot)
- Relaciona variáveis numéricas.
- Vamos relacionar a quantidade de cilindradas com a quantidade de milhas por galão em rodovia.
- ou seja, vamos relacionar as seguintes variáveis numéricas em nosso dataset:
rodovia
e cilindrada
.
- estamos usando o dataset
dados_mpg
.
- Nesse exemplo, vamos usar as camadas, passo a passo (mesmo não precisando)
1º Camada: Data (dados)
- Observe que se colocarmos apenas a camada dos “dados”, não aparece (quase) nada
- carrega-se apenas o conjunto de dados
2º Camada: Aesthetics (estética)
- Acrescentando a camada estética geral (
aes()
), já aparecem os valores do eixo \(x\) e os valores do eixo \(y\).
- também é costume colocar essa camada dentro da camada
ggplot()
, assim: ggplot(data = ..., aes(x = ..., y = ...))
.
ggplot(data = dados_mpg) +
aes(x = rodovia, y = cilindrada)
3º Camada: Geometries (geometria)
- Vamos agora acrescentar a camada da geometria dos dados
ggplot(data = dados_mpg) +
aes(x = cilindrada, y = rodovia) +
geom_point()
- Veja com com essas três camadas, já temos um conjunto de informações básicas sobre o que desejamos comunicar.
- as outras camadas, deixam o gráfico mais “bonito”.
- Também podemos acrescentar argumentos convenientes na camada
Geometries
, como size
(que altera o tamanho dos pontos)
- Apenas por simplicidade, vamos omitir o nome “date”
ggplot(dados_mpg) +
aes(x = cilindrada, y = rodovia) +
geom_point(size = 2)
4º Camada: Facets (facetas)
- Ao acrescentar a camada “facets”, particularizamos alguma característica desejável
- no exemplo, estamos facetando pelo ano, ou seja, separando os dados por ano.
- usaremos a função
facet_grid()
ggplot(data = dados_mpg) +
aes(x = cilindrada, y = rodovia) +
geom_point(size = 2) +
facet_grid(ano~.)
- Veja que usamos o argumento
ano~.
- isso faz com que a variável
ano
fique na “horizontal”
- Se mudarmos a posição desse argumento, ou seja,
.~ano
, fica assim:
ggplot(data = dados_mpg) +
aes(x = cilindrada, y = rodovia) +
geom_point(size = 2) +
facet_grid(.~ano)
- Ainda podemos mudar a cor dos pontos, de acordo com a classe dos carros.
- para isso usaremos o argumento
color = classe
- devemos acrescentar o argumento
color
dentro de um aes()
(local), pois relaciona à mudanças na variável do dataset (ou seja, ele mapeará as características estéticas em cada parte da variável);
- se a mudança é apenas numérica, não precisa do
aes()
ggplot(data = dados_mpg) +
aes(x = cilindrada, y = rodovia) +
geom_point(size = 2, aes(color = classe)) +
facet_grid(.~ano)
- Se não tivéssemos acrescentado o
aes()
ficaria assim (com erro):
ggplot(data = dados_mpg) +
aes(x = cilindrada, y = rodovia) +
geom_point(size = 2, color = classe) +
facet_grid(.~ano)
5º Camada: Statistics (estatísticas)
- Pode ser a média, mediana, uma regra de regressão, etc.
- para esse exemplo, usaremos uma curva que melhor se ajusta nos conjuntos de pontos.
- para isso, usaremos a função
geom_smooth()
ggplot(data = dados_mpg) +
aes(x = cilindrada, y = rodovia) +
geom_point(size = 2, aes(color = classe)) +
facet_grid(.~ano) +
geom_smooth()
`geom_smooth()` using method = 'loess' and formula 'y ~ x'
- Se quisermos uma reta que melhor se adeque aos pontos, devemos acrescentar o argumento `
method = lm
:
ggplot(data = dados_mpg) +
aes(x = cilindrada, y = rodovia) +
geom_point(size = 2, aes(color = classe)) +
facet_grid(.~ano) +
geom_smooth(method = lm)
`geom_smooth()` using formula 'y ~ x'
- Caso seja conveniente retirarmos o “sombreamento” (intervalo de confiança) da curva (inclusive a reta), usamos o argumento
se = FALSE
.
ggplot(data = dados_mpg) +
aes(x = cilindrada, y = rodovia) +
geom_point(size = 2, aes(color = classe)) +
facet_grid(.~ano) +
geom_smooth(method = lm, se = FALSE)
`geom_smooth()` using formula 'y ~ x'
- E, se quisermos mudar a cor dessa reta de regressão, podemos fazer dentro dessa camada:
ggplot(data = dados_mpg) +
aes(x = cilindrada, y = rodovia) +
geom_point(size = 2, aes(color = classe)) +
facet_grid(.~ano) +
geom_smooth(method = lm, se = FALSE, color = "red")
`geom_smooth()` using formula 'y ~ x'
6º Camada: Coordinates (coordenadas)
- Podemos usar coordenadas cartesianas ou polares;
- no nosso exemplo, estamos usando coordenadas polares.
- Também é possível delimitar os eixos coordenados.
- em nosso caso, usaremos as funções
xlim
e ylim
dentro da função coord_cartesian()
ggplot(data = dados_mpg) +
aes(x = cilindrada, y = rodovia) +
geom_point(size = 2, aes(color = classe)) +
facet_grid(.~ano) +
geom_smooth(method = lm, se = FALSE, color = "red") +
coord_cartesian(xlim = c(0, 8), ylim = c(10, 45))
`geom_smooth()` using formula 'y ~ x'
7º Camada: Theme
- Podemos colocar um tema que melhor se adeque ao aspecto de nosso texto
- no nosso caso, vamos usar o tema
theme_bw
ggplot(data = dados_mpg) +
aes(x = cilindrada, y = rodovia) +
geom_point(size = 2, aes(color = classe)) +
facet_grid(.~ano) +
geom_smooth(method = lm, se = FALSE, color = "red") +
theme_bw()
`geom_smooth()` using formula 'y ~ x'
- Também podemos modificar o título e os nomes dos eixos com a função
labs()
ggplot(data = dados_mpg) +
aes(x = cilindrada, y = rodovia) +
geom_point(size = 2, aes(color = classe)) +
facet_grid(.~ano) +
geom_smooth(method = lm, se = FALSE, color = "red") +
theme_bw() +
labs(
title = "Correlação entre variáveis numéricas",
subtitle = "Cilindrada vs Milhas por Galão",
x = "Cilindrada",
y = "Milhas por Galão de Combustível"
)
`geom_smooth()` using formula 'y ~ x'
2. Gráficos de linha
- Usaremos para esse gráfico o dataset
notas_disciplina.csv
.
- Precisamos deixá-lo na forma `tidy``, pivotando as colunas (já vimos como faz isso)
- também renomearemos uma das variáveis
- chamaremos esses dados modificados por
notas_tidy
notas_tidy <- dados_notas %>%
pivot_longer(
!Curso,
names_to = "ano",
values_to = "notas"
) %>%
rename(curso = Curso)
notas_tidy
- Para criarmos o gráfico de linhas, vamos usar as médias do conjunto de dados ao longo dos anos.
- Agora, vamos sumarizar as médias das turmas por ano:
p <- notas_tidy %>%
group_by(ano) %>%
summarise(
media_notas = round(mean(notas), 1)
)
p
p %>%
ggplot(aes(x = ano, y = media_notas, group = 1)) +
geom_line(color = "#BF616A", size = 1)
- Note que:
- usamos o comando
group = 1
no aes()
geral.
- escolhemos uma cor vermelha específica (com suporte em html)
- O código completo, já com o tema, fica
notas_tidy %>%
group_by(ano) %>%
summarise(
media_notas = round(mean(notas), 1)
) %>%
ggplot(aes(x = ano, y = media_notas, group = 1)) +
geom_line(color = "#BF616A", size = 1) +
labs(
title = "Gráfico de Linhas das Notas",
x = "",
y = "Média das Notas"
) +
theme_minimal()
- Veja que o “
+
” só é colocado nas camadas do ggolot.
- um erro comum é trocar o
+
pelo %>%
(pipe)
3. Gráfico de Colunas
- Para o gráfico de colunas, vamos usar o
dados_mpg
e relacionarmos quantos carros usam os diferentes combustíveis (p
= premium; r
= regular; e
= ethanol; d
= diesel; g
= gás).
dados_mpg %>%
count(combustivel)
- Assim, usaremos a função
geom_col()
para criarmos o gráfico de colunas, relacionando o combustível com sua frequência absoluta n
dados_mpg %>%
count(combustivel) %>%
ggplot(aes(x = combustivel, y = n)) +
geom_col()
- Se quisermos preencher cada coluna com uma cor de acordo com o tipo de combustível, devemos acrescentar no
aes()
um argumento para tal fato.
- o argumento
color
vai cobrir a fronteira dos gráficos;
- o argumento
fill
vai preencher o interior dos gráficos.
dados_mpg %>%
count(combustivel) %>%
ggplot(aes(x = combustivel, y = n , color = combustivel)) +
geom_col()
dados_mpg %>%
count(combustivel) %>%
ggplot(aes(x = combustivel, y = n , fill = combustivel)) +
geom_col()
- Há um conjunto de cores (viridis) que ajuda na visualização de pessoas com dificuldade de visão ou daltonismo
- particularmente, uso a variação
d
(default)
dados_mpg %>%
count(combustivel) %>%
ggplot(aes(x = combustivel, y = n , fill = combustivel)) +
geom_col() +
scale_fill_viridis_d()
- Uma maneira rápida de reordenarmos as colunas de forma crescente é usarmos a função
fct_reorder()
, do pacote forcats
(incluso no tidyverse
).
dados_mpg %>%
count(combustivel) %>%
ggplot(aes(x = fct_reorder(combustivel, n), y = n, fill = combustivel)) +
geom_col() +
scale_fill_viridis_d()
- Para a ordenação de forma decrescente, podemos usar a função
desc()
, ou simplesmente colocar o sinal “-
” antes da variável numérica que estamos ordenando.
dados_mpg %>%
count(combustivel) %>%
ggplot(aes(x = fct_reorder(combustivel, -n), y = n, fill = combustivel)) +
geom_col() +
scale_fill_viridis_d()
- Melhorando o gráfico, temos:
dados_mpg %>%
count(combustivel) %>%
ggplot(aes(x = fct_reorder(combustivel, -n), y = n, fill = combustivel)) +
geom_col() +
scale_fill_viridis_d() +
labs(
title = "Comparando os tipos de combustíveis",
subtitle = "(`p` = premium; `r` = regular; `e` = ethanol; `d` = diesel; `g` = gás)",
x = "",
y = ""
)
3.1 Gráfico de Barras
- Mesma ideia do anterior
- Geralmente usado quando o nome da variável é grande
- o pacote
scales
será usado para exibir um dos eixos com notação de porcentagem.
- esse pacote faz muitas outras coisas, mas só usaremos essa nesse exemplo.
- se você não tiver instalado, faça-o com o comando
install.packages("scales")
.
Attaching package: ‘scales’
The following object is masked from ‘package:purrr’:
discard
The following object is masked from ‘package:readr’:
col_factor
- Usamos a função
label_percent()
, dentro da função scale_x_continuous()
, no argumnento label
.
- também podemos delimitar o eixo \(x\) por aqui!
- o termo
accuracy
diz quantas casas decimais desejamos, caso seja necessário.
dados_mpg %>%
count(classe) %>%
mutate(prop = n / sum(n)) %>%
ggplot() +
aes(x = prop, y = fct_reorder(classe, prop), fill = classe) +
geom_col() +
scale_fill_viridis_d() +
scale_x_continuous(
labels = label_percent(accuracy = 1),
limits = c(0, .3)
) +
labs(
title = "Porcentagem das Classes dos Carros",
x = "",
y = ""
) +
theme_minimal()
4. Histograma
- Um histograma é uma representação gráfica da distribuição de uma variável numérica.
- note que aceita apenas variáveis numéricas de entrada.
- A variável é dividida em várias classes (intervalor)
- o número de observações por classes é representado pela altura da barra.
- Usaremos o dataset
notas_tidy
para exemplificação:
notas_tidy %>%
ggplot() +
aes(x = notas) +
geom_histogram(fill = "blue", color = "black") +
labs(
title = "Histograma das Notas",
x = "Notas",
y = ""
) +
theme_minimal()
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
- Podemos alterar o tamanho das classes usando o argumento
binwidth
notas_tidy %>%
ggplot() +
aes(x = notas) +
geom_histogram(fill = "blue", color = "black", binwidth = 1) +
labs(
title = "Histograma das Notas",
x = "Notas",
y = ""
) +
theme_minimal()
5. Gráfico de Densidade
- Podemos interpretar como uma
suavização
do Histograma.
notas_tidy %>%
ggplot() +
aes(x = notas) +
geom_density(fill = "#404080")
- Podemos comparar por curso
notas_tidy %>%
ggplot() +
aes(x = notas) +
geom_density(aes(fill = curso), alpha = 0.3) +
scale_fill_viridis_d()
- Ainda separando por “facetas” pelos cursos
notas_tidy %>%
ggplot() +
aes(x = notas) +
geom_density(aes(fill = curso), alpha = 0.3) +
facet_grid(curso~.) +
scale_fill_viridis_d()
- Ou “facetando” ao longo dos anos
notas_tidy %>%
ggplot() +
aes(x = notas) +
geom_density(aes(fill = curso), alpha = 0.3) +
facet_grid(ano~.) +
scale_fill_viridis_d()
- Este último, por conter muita informação, não ficou tão clara nossa comunicação.
- Para contornar isso, podemos usar o pacote
ggridges
5.1 Ridgeline (Joyplot)
- Carregue (se preciso, instale-o antes) o pacote
ggridges
# install.packages("ggridges")
library(ggridges)
- Usaremos a função
geom_density_ridges()
notas_tidy %>%
ggplot() +
aes(x = notas, y = ano, fill = curso) +
geom_density_ridges(alpha = 0.5) +
scale_fill_viridis_d()
Picking joint bandwidth of 0.568
- Podemos filtrar por um curso específico, por exemplo, Química.
notas_tidy %>%
filter(curso == "Qui") %>%
ggplot() +
aes(x = notas, y = ano, fill = curso) +
geom_density_ridges(alpha = 0.5) +
scale_fill_viridis_d()
Picking joint bandwidth of 0.61
6. Boxplot
- Já vimos muito sobre boxplot em nossa disciplina (inclusive artigos que explicam os pormenores)
- Para exemplificar, continuaremos com o dataset
notas_tidy
.
- inicialmente, vamos comparar, ao logo dos anos, os boxplots dos cursos
notas_tidy %>%
ggplot() +
aes(x = ano, y= notas, fill = curso) +
geom_boxplot() +
scale_fill_viridis_d()
- Agora, vamos comparar apenas as notas ao longo dos anos.
- se quisermos omitir as legendas, usamos
show.legend = FALSE
notas_tidy %>%
ggplot() +
aes(x = ano, y= notas) +
geom_boxplot(aes(fill = ano), show.legend = FALSE) +
scale_fill_viridis_d()
LS0tCnRpdGxlOiAiQWxnbyAobXVpdG8gcsOhcGlkbykgc29icmUgbyBnZ3Bsb3QyIgphdXRob3I6ICLDjWNhcm8gVmlkYWwgRnJlaXJlIgpkYXRlOiAiRXN0YXQgKENGUC9VRlJCKSIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OiB5ZXMKICAgIGhpZ2hsaWdodDogdGFuZ28KICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB5ZXMKICAgIGRmX3ByaW50OiBwYWdlZAotLS0KCmBgYHtyIHNldHVwLCBlY2hvPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoCiAgbWVzc2FnZSA9IEZBTFNFLAogIHdhcm5pbmcgPSBGQUxTRSwKICBjb2xsYXBzZSA9IFRSVUUKKQpgYGAKCgotLS0KCiMjIFZpc8OjbyByw6FwaWRhCgotIE8gYGdncGxvdDJgIGZvaSBjcmlhZG8gcG9yIEhhZGxleSBXaWNraGFtLCBiYXNlYWRvIG5vIGxpdnJvICpHcmFtbWFyIG9mIEdyYXBoaWNzKiwgZGUgTGVsYW5kIFdpbGtpbnNvbi4KLSBBIGlkZWlhIMOpIHBlbnNhciBlbSB1bSAiZ3LDoWZpY28iIGNvbW8gdW1hIGVzdHJ1dHVyYSByYWNpb25hbCwgY29tcG9zdGEgcG9yICoqY2FtYWRhcyoqLgoKYGBge3IgY2FtYWRhcywgZWNobz1GQUxTRX0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoImltZy9ncmFtbWFyLW9mLWdyYXBoaWNzLnBuZyIpCmBgYAoKLSBOw6NvIHByZWNpc2EgdGVyIHRvZGFzIGFzIGNhbWFkYXMsIG1hcyB0csOqcyBzw6NvICoqb2JyaWdhdMOzcmlhcyoqOgogICsgKipkYXRhKio6IG8gY29uanVudG8gZGUgZGFkb3MgZGV2ZSBzZXIgdW1hIGB0aWJibGVgIChvdSwgcGVsbyBtZW5vcywgdW0gYGRhdGFmcmFtZWApOwogICsgKiphZXN0aGV0aWNzKio6IG1hcGVhbWVudG9zIGVzdMOpdGljb3MsIGNvbW8gc2VsZWNpb25hbWVudG8gZGFzIHZhcmnDoXZlaXMgZW0gcXVlc3TDo287CiAgKyAqKmdlb21ldHJpZXMqKjogc8OjbyBhcyBmb3JtYXMgZG8gZ3LDoWZpY28gKHBvbnRvcywgYm94cGxvdCwgaGlzdG9ncmFtYSwgZXRjKQoKLSBOw6NvIHZlcmVtb3MgdG9kb3Mgb3MgZ3LDoWZpY29zLCBhcGVuYXMgYWxndW5zIHBvdWNvczsKLSBBcGVuYXMgdGFuZ2VuY2lhcmVtb3MgbyBwYWNvdGUgISEKCiMjIFF1ZWlyYSBjb211bmljYXIsIG7Do28gImxhY3JhciIhCgohW10oaHR0cHM6Ly9zdGF0NTQ1LmNvbS9pbWcvbGVzcy1pcy1tb3JlLWRhcmtob3JzZS1hbmFseXRpY3MuZ2lmKQoKIyMgQW50ZXMgZGUgY29tZcOnYXIKCi0gVmFtb3MgY2FycmVnYXIgYSBiaWJsaW90ZWNhIGRvIGB0aWR5dmVyc2VgLCBuw6NvIGFwZW5hcyBhIGRvIGBnZ3Bsb3RgLgotIElzc28gc2UgZGV2ZSBhbyBmYXRvIGRlIHNlbXByZSBwcmVjaXNhcm1vcyBtYW5pcHVsYXIgb3MgZGFkb3MgYW50ZXMgZGUgcGxvdGFyIGFsZ3VtIGdyw6FmaWNvLgoKTyBjw7NkaWdvIGFiYWl4byBjYXJyZWdhIG8gcGFjb3RlOgoKYGBge3IgdGlkeXZlcnNlfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKYGBgCgotIERlcG9pcyBkaXNzbywgdXNhcmVtb3MgYSBmdW7Dp8OjbyBgcmVhZF9jc3YoKWAgcGFyYSBhY2Vzc2FyIG8gY29uanVudG8gZGUgZGFkb3MuCi0gVXNhcmVtb3MgbyBjb25qdW50byBkZSBkYWRvcyBgbWlsaGFzX3Bvcl9nYWxhby5jc3ZgLCBiZW0gY29tbyBvIGNvbmp1bnRvIGRlIGRhZG9zIGBub3Rhc19kaXNjaXBsaW5hcy5jc3ZgLCBhbWJvcyBkaXNwb27DrXZlaXMgZW0gbm9zc28gcmVwb3NpdMOzcmlvOiBbRGFkb3MuY3N2XShodHRwczovL2dpdGh1Yi5jb20vaWNhcm8tZnJlaXJlL2RhZG9zX2NzdikuCiAgKyBPIHByaW1laXJvIGNvbmp1bnRvIGRlIGRhZG9zLCBub21lYXJlbW9zIHBvciBgZGFkb3NfbXBnYCBlIG8gc2VndW5kbyBwb3IgYGRhZG9zX25vdGFzYAoKYGBge3IgbGVuZG8tZGFkb3N9CiMgTGVuZG8gbyBjb25qdW50byBgbWlsaGFzX3Bvcl9nYWxhby5jc3ZgCmRhZG9zX21wZyA8LSByZWFkX2NzdigiZGFkb3MvbWlsaGFzX3Bvcl9nYWxhby5jc3YiKQoKIyBMZW5kbyBvIGNvbmp1bnRvIGRlIGRhZG9zIGBub3Rhc19kaXNjaXBsaW5hLmNzdmAKZGFkb3Nfbm90YXMgPC0gcmVhZF9jc3YoImRhZG9zL25vdGFzX2Rpc2NpcGxpbmEuY3N2IikKYGBgCgotIE8gKmRhdGFzZXQqIGBkYWRvc19tcGdgIGZpY2FyaWEgYXNzaW06CgpgYGB7cn0KZGFkb3NfbXBnCmBgYAoKLSBKw6EgbyAqZGF0YXNldCogYGRhZG9zX25vdGFzYCwgYXNzaW06CiAgKyB2ZWphIHF1ZSBlc3NlIGNvbmp1bnRvIGRlIGRhZG9zIG7Do28gZXN0w6EgbmEgZm9ybWEgYHRpZHlgLiAKICAKYGBge3J9CmRhZG9zX25vdGFzCmBgYAoKIyMgU2ludGF4IGRvIGdncGxvdAoKYGBge3IsIGV2YWw9RkFMU0V9CmdncGxvdChkYWRvcywgbWFwcGluZyA9IGFlcyh4ID0gLi4uLCB5ID0gLi4uLCBvdXRyYXMgPSAuLi4pKSArCiAgZ2VvbV9mdW5jYW8ob3DDp8O1ZXMpCmBgYAoKT3UsIHVzYW5kbyBvICpwaXBlKjoKCmBgYHtyLCBldmFsPUZBTFNFfQpkYWRvcyAlPiUgCiAgZ2dwbG90KG1hcHBpbmcgPSBhZXMoeCA9IC4uLiwgeSA9IC4uLiwgb3V0cmFzID0gLi4uKSkgKwogIGdlb21fZnVuY2FvKG9ww6fDtWVzKQpgYGAKCi0gTm90ZSBxdWUgdXNhbW9zIG8gc2luYWwgImArYCIgcGFyYSAqYWRpY2lvbmFyKiBhcyBjYW1hZGFzIGRvIGBnZ3Bsb3RgLgotIFBhcmEgZmljYXIgbWFpcyBleHBsw61jaXRvLCDDoHMgdmV6ZXMgdXNhcmVtb3MgYXNzaW06CgpgYGB7ciwgZXZhbD1GQUxTRX0KZGFkb3MgJT4lIAogIGdncGxvdCgpICsKICBhZXMoeCA9IC4uLiwgeSA9IC4uLiwgb3V0cmFzID0gLi4uKSArCiAgZ2VvbV9mdW5jYW8ob3DDp8O1ZXMpCmBgYAoKIyMgMS4gR3LDoWZpY29zIGRlIFBvbnRvcwoKLSBUYW1iw6ltIHPDo28gY2hhbWFkb3MgZGUgImdyw6FmaWNvIGRlIGRpc3BlcnPDo28iICgqc2NhdHRlciBwbG90KikKLSBSZWxhY2lvbmEgdmFyacOhdmVpcyBudW3DqXJpY2FzLgotIFZhbW9zIHJlbGFjaW9uYXIgYSBxdWFudGlkYWRlIGRlIGNpbGluZHJhZGFzIGNvbSBhIHF1YW50aWRhZGUgZGUgbWlsaGFzIHBvciBnYWzDo28gZW0gcm9kb3ZpYS4KICArIG91IHNlamEsIHZhbW9zIHJlbGFjaW9uYXIgYXMgc2VndWludGVzIHZhcmnDoXZlaXMgbnVtw6lyaWNhcyBlbSBub3NzbyAqZGF0YXNldCo6IGByb2RvdmlhYCBlIGBjaWxpbmRyYWRhYC4KICArIGVzdGFtb3MgdXNhbmRvIG8gKmRhdGFzZXQqIGBkYWRvc19tcGdgLgotIE5lc3NlIGV4ZW1wbG8sIHZhbW9zIHVzYXIgYXMgY2FtYWRhcywgcGFzc28gYSBwYXNzbyAobWVzbW8gbsOjbyBwcmVjaXNhbmRvKQoKIyMjIDHCuiBDYW1hZGE6ICpEYXRhKiAoZGFkb3MpCgpgYGB7cn0KZ2dwbG90KGRhdGEgPSBkYWRvc19tcGcpCmBgYAoKLSBPYnNlcnZlIHF1ZSBzZSBjb2xvY2FybW9zIGFwZW5hcyBhIGNhbWFkYSBkb3MgImRhZG9zIiwgbsOjbyBhcGFyZWNlIChxdWFzZSkgbmFkYQogICsgY2FycmVnYS1zZSBhcGVuYXMgbyBjb25qdW50byBkZSBkYWRvcwoKIyMjIDLCuiBDYW1hZGE6ICpBZXN0aGV0aWNzKiAoZXN0w6l0aWNhKQoKLSBBY3Jlc2NlbnRhbmRvIGEgY2FtYWRhIGVzdMOpdGljYSBnZXJhbCAoYGFlcygpYCksIGrDoSBhcGFyZWNlbSBvcyB2YWxvcmVzIGRvIGVpeG8gJHgkIGUgb3MgdmFsb3JlcyBkbyBlaXhvICR5JC4KICArIHRhbWLDqW0gw6kgY29zdHVtZSBjb2xvY2FyIGVzc2EgY2FtYWRhIGRlbnRybyBkYSBjYW1hZGEgYGdncGxvdCgpYCwgYXNzaW06IGBnZ3Bsb3QoZGF0YSA9IC4uLiwgYWVzKHggPSAuLi4sIHkgPSAuLi4pKWAuCiAgCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IGRhZG9zX21wZykgKwogIGFlcyh4ID0gcm9kb3ZpYSwgeSA9IGNpbGluZHJhZGEpCmBgYAojIyMgM8K6IENhbWFkYTogKkdlb21ldHJpZXMqIChnZW9tZXRyaWEpCgotIFZhbW9zIGFnb3JhIGFjcmVzY2VudGFyIGEgY2FtYWRhIGRhIGdlb21ldHJpYSBkb3MgZGFkb3MKCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IGRhZG9zX21wZykgKwogIGFlcyh4ID0gY2lsaW5kcmFkYSwgeSA9IHJvZG92aWEpICsKICBnZW9tX3BvaW50KCkKYGBgCi0gVmVqYSBjb20gY29tIGVzc2FzIHRyw6pzIGNhbWFkYXMsIGrDoSB0ZW1vcyB1bSBjb25qdW50byBkZSBpbmZvcm1hw6fDtWVzIGLDoXNpY2FzIHNvYnJlIG8gcXVlIGRlc2VqYW1vcyBjb211bmljYXIuCiAgKyBhcyBvdXRyYXMgY2FtYWRhcywgZGVpeGFtIG8gZ3LDoWZpY28gbWFpcyAiYm9uaXRvIi4KLSBUYW1iw6ltIHBvZGVtb3MgYWNyZXNjZW50YXIgYXJndW1lbnRvcyBjb252ZW5pZW50ZXMgbmEgY2FtYWRhIGBHZW9tZXRyaWVzYCwgY29tbyBgc2l6ZWAgKHF1ZSBhbHRlcmEgbyB0YW1hbmhvIGRvcyBwb250b3MpCi0gQXBlbmFzIHBvciBzaW1wbGljaWRhZGUsIHZhbW9zIG9taXRpciBvIG5vbWUgImRhdGUiCgpgYGB7cn0KZ2dwbG90KGRhZG9zX21wZykgKwogIGFlcyh4ID0gY2lsaW5kcmFkYSwgeSA9IHJvZG92aWEpICsKICBnZW9tX3BvaW50KHNpemUgPSAyKQpgYGAKCiMjIyA0wrogQ2FtYWRhOiAqRmFjZXRzKiAoZmFjZXRhcykKCi0gQW8gYWNyZXNjZW50YXIgYSBjYW1hZGEgImZhY2V0cyIsIHBhcnRpY3VsYXJpemFtb3MgYWxndW1hIGNhcmFjdGVyw61zdGljYSBkZXNlasOhdmVsCiAgKyBubyBleGVtcGxvLCBlc3RhbW9zIGZhY2V0YW5kbyBwZWxvIGFubywgb3Ugc2VqYSwgc2VwYXJhbmRvIG9zIGRhZG9zIHBvciBhbm8uCiAgKyB1c2FyZW1vcyBhIGZ1bsOnw6NvIGBmYWNldF9ncmlkKClgCgpgYGB7cn0KZ2dwbG90KGRhdGEgPSBkYWRvc19tcGcpICsKICBhZXMoeCA9IGNpbGluZHJhZGEsIHkgPSByb2RvdmlhKSArCiAgZ2VvbV9wb2ludChzaXplID0gMikgKwogIGZhY2V0X2dyaWQoYW5vfi4pCmBgYAotIFZlamEgcXVlICB1c2Ftb3MgbyBhcmd1bWVudG8gYGFub34uYAogICsgaXNzbyBmYXogY29tIHF1ZSBhIHZhcmnDoXZlbCBgYW5vYCBmaXF1ZSBuYSAiaG9yaXpvbnRhbCIKICArIFNlIG11ZGFybW9zIGEgcG9zacOnw6NvIGRlc3NlIGFyZ3VtZW50bywgb3Ugc2VqYSwgYC5+YW5vYCwgZmljYSBhc3NpbToKCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IGRhZG9zX21wZykgKwogIGFlcyh4ID0gY2lsaW5kcmFkYSwgeSA9IHJvZG92aWEpICsKICBnZW9tX3BvaW50KHNpemUgPSAyKSArCiAgZmFjZXRfZ3JpZCgufmFubykKYGBgCi0gQWluZGEgcG9kZW1vcyBtdWRhciBhIGNvciBkb3MgcG9udG9zLCBkZSBhY29yZG8gY29tIGEgY2xhc3NlIGRvcyBjYXJyb3MuCiAgKyBwYXJhIGlzc28gdXNhcmVtb3MgbyBhcmd1bWVudG8gYGNvbG9yID0gY2xhc3NlYAogICsgZGV2ZW1vcyBhY3Jlc2NlbnRhciBvIGFyZ3VtZW50byBgY29sb3JgIGRlbnRybyBkZSB1bSBgYWVzKClgIChsb2NhbCksIHBvaXMgcmVsYWNpb25hIMOgIG11ZGFuw6dhcyBuYSAqKnZhcmnDoXZlbCoqIGRvIGRhdGFzZXQgKG91IHNlamEsIGVsZSBtYXBlYXLDoSBhcyBjYXJhY3RlcsOtc3RpY2FzIGVzdMOpdGljYXMgZW0gY2FkYSBwYXJ0ZSBkYSB2YXJpw6F2ZWwpOwogICsgc2UgYSBtdWRhbsOnYSDDqSBhcGVuYXMgbnVtw6lyaWNhLCBuw6NvIHByZWNpc2EgZG8gYGFlcygpYAogIApgYGB7cn0KZ2dwbG90KGRhdGEgPSBkYWRvc19tcGcpICsKICBhZXMoeCA9IGNpbGluZHJhZGEsIHkgPSByb2RvdmlhKSArCiAgZ2VvbV9wb2ludChzaXplID0gMiwgYWVzKGNvbG9yID0gY2xhc3NlKSkgKwogIGZhY2V0X2dyaWQoLn5hbm8pCmBgYAotIFNlIG7Do28gdGl2w6lzc2Vtb3MgYWNyZXNjZW50YWRvIG8gYGFlcygpYCBmaWNhcmlhIGFzc2ltIChjb20gZXJybyk6CgpgYGB7ciwgZXZhbD1GQUxTRX0KZ2dwbG90KGRhdGEgPSBkYWRvc19tcGcpICsKICBhZXMoeCA9IGNpbGluZHJhZGEsIHkgPSByb2RvdmlhKSArCiAgZ2VvbV9wb2ludChzaXplID0gMiwgY29sb3IgPSBjbGFzc2UpICsKICBmYWNldF9ncmlkKC5+YW5vKQpgYGAKCiMjIyA1wrogQ2FtYWRhOiAqU3RhdGlzdGljcyogKGVzdGF0w61zdGljYXMpCgotIFBvZGUgc2VyIGEgbcOpZGlhLCBtZWRpYW5hLCB1bWEgcmVncmEgZGUgcmVncmVzc8OjbywgZXRjLgotIHBhcmEgZXNzZSBleGVtcGxvLCB1c2FyZW1vcyB1bWEgY3VydmEgcXVlIG1lbGhvciBzZSBhanVzdGEgbm9zIGNvbmp1bnRvcyBkZSBwb250b3MuCiAgKyBwYXJhIGlzc28sIHVzYXJlbW9zIGEgZnVuw6fDo28gYGdlb21fc21vb3RoKClgCiAgCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IGRhZG9zX21wZykgKwogIGFlcyh4ID0gY2lsaW5kcmFkYSwgeSA9IHJvZG92aWEpICsKICBnZW9tX3BvaW50KHNpemUgPSAyLCBhZXMoY29sb3IgPSBjbGFzc2UpKSArCiAgZmFjZXRfZ3JpZCgufmFubykgKwogIGdlb21fc21vb3RoKCkKYGBgCgotIFNlIHF1aXNlcm1vcyB1bWEgcmV0YSBxdWUgbWVsaG9yIHNlIGFkZXF1ZSBhb3MgcG9udG9zLCBkZXZlbW9zIGFjcmVzY2VudGFyIG8gYXJndW1lbnRvIGBgbWV0aG9kID0gbG1gOgoKYGBge3J9CmdncGxvdChkYXRhID0gZGFkb3NfbXBnKSArCiAgYWVzKHggPSBjaWxpbmRyYWRhLCB5ID0gcm9kb3ZpYSkgKwogIGdlb21fcG9pbnQoc2l6ZSA9IDIsIGFlcyhjb2xvciA9IGNsYXNzZSkpICsKICBmYWNldF9ncmlkKC5+YW5vKSArCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gbG0pCmBgYAoKLSBDYXNvIHNlamEgY29udmVuaWVudGUgcmV0aXJhcm1vcyBvICJzb21icmVhbWVudG8iIChpbnRlcnZhbG8gZGUgY29uZmlhbsOnYSkgZGEgY3VydmEgKGluY2x1c2l2ZSBhIHJldGEpLCB1c2Ftb3MgbyBhcmd1bWVudG8gYHNlID0gRkFMU0VgLgoKYGBge3J9CmdncGxvdChkYXRhID0gZGFkb3NfbXBnKSArCiAgYWVzKHggPSBjaWxpbmRyYWRhLCB5ID0gcm9kb3ZpYSkgKwogIGdlb21fcG9pbnQoc2l6ZSA9IDIsIGFlcyhjb2xvciA9IGNsYXNzZSkpICsKICBmYWNldF9ncmlkKC5+YW5vKSArCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gbG0sIHNlID0gRkFMU0UpCmBgYAoKLSBFLCBzZSBxdWlzZXJtb3MgbXVkYXIgYSBjb3IgZGVzc2EgcmV0YSBkZSByZWdyZXNzw6NvLCBwb2RlbW9zIGZhemVyIGRlbnRybyBkZXNzYSBjYW1hZGE6CgpgYGB7cn0KZ2dwbG90KGRhdGEgPSBkYWRvc19tcGcpICsKICBhZXMoeCA9IGNpbGluZHJhZGEsIHkgPSByb2RvdmlhKSArCiAgZ2VvbV9wb2ludChzaXplID0gMiwgYWVzKGNvbG9yID0gY2xhc3NlKSkgKwogIGZhY2V0X2dyaWQoLn5hbm8pICsKICBnZW9tX3Ntb290aChtZXRob2QgPSBsbSwgc2UgPSBGQUxTRSwgY29sb3IgPSAicmVkIikKYGBgCgojIyMgNsK6IENhbWFkYTogKkNvb3JkaW5hdGVzKiAoY29vcmRlbmFkYXMpCgotIFBvZGVtb3MgdXNhciBjb29yZGVuYWRhcyBjYXJ0ZXNpYW5hcyBvdSBwb2xhcmVzOwogICsgbm8gbm9zc28gZXhlbXBsbywgZXN0YW1vcyB1c2FuZG8gY29vcmRlbmFkYXMgcG9sYXJlcy4KLSBUYW1iw6ltIMOpIHBvc3PDrXZlbCBkZWxpbWl0YXIgb3MgZWl4b3MgY29vcmRlbmFkb3MuCiAgKyBlbSBub3NzbyBjYXNvLCB1c2FyZW1vcyBhcyBmdW7Dp8O1ZXMgYHhsaW1gIGUgYHlsaW1gIGRlbnRybyBkYSBmdW7Dp8OjbyBgY29vcmRfY2FydGVzaWFuKClgCgpgYGB7cn0KZ2dwbG90KGRhdGEgPSBkYWRvc19tcGcpICsKICBhZXMoeCA9IGNpbGluZHJhZGEsIHkgPSByb2RvdmlhKSArCiAgZ2VvbV9wb2ludChzaXplID0gMiwgYWVzKGNvbG9yID0gY2xhc3NlKSkgKwogIGZhY2V0X2dyaWQoLn5hbm8pICsKICBnZW9tX3Ntb290aChtZXRob2QgPSBsbSwgc2UgPSBGQUxTRSwgY29sb3IgPSAicmVkIikgKwogIGNvb3JkX2NhcnRlc2lhbih4bGltID0gYygwLCA4KSwgeWxpbSA9IGMoMTAsIDQ1KSkKYGBgCgojIyMgN8K6IENhbWFkYTogKlRoZW1lKgoKLSBQb2RlbW9zIGNvbG9jYXIgdW0gdGVtYSBxdWUgbWVsaG9yIHNlIGFkZXF1ZSBhbyBhc3BlY3RvIGRlIG5vc3NvIHRleHRvCiAgKyBubyBub3NzbyBjYXNvLCB2YW1vcyB1c2FyIG8gdGVtYSBgdGhlbWVfYndgCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IGRhZG9zX21wZykgKwogIGFlcyh4ID0gY2lsaW5kcmFkYSwgeSA9IHJvZG92aWEpICsKICBnZW9tX3BvaW50KHNpemUgPSAyLCBhZXMoY29sb3IgPSBjbGFzc2UpKSArCiAgZmFjZXRfZ3JpZCgufmFubykgKwogIGdlb21fc21vb3RoKG1ldGhvZCA9IGxtLCBzZSA9IEZBTFNFLCBjb2xvciA9ICJyZWQiKSArCiAgdGhlbWVfYncoKQpgYGAKCi0gVGFtYsOpbSBwb2RlbW9zIG1vZGlmaWNhciBvIHTDrXR1bG8gZSBvcyBub21lcyBkb3MgZWl4b3MgY29tIGEgZnVuw6fDo28gYGxhYnMoKWAKCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IGRhZG9zX21wZykgKwogIGFlcyh4ID0gY2lsaW5kcmFkYSwgeSA9IHJvZG92aWEpICsKICBnZW9tX3BvaW50KHNpemUgPSAyLCBhZXMoY29sb3IgPSBjbGFzc2UpKSArCiAgZmFjZXRfZ3JpZCgufmFubykgKwogIGdlb21fc21vb3RoKG1ldGhvZCA9IGxtLCBzZSA9IEZBTFNFLCBjb2xvciA9ICJyZWQiKSArCiAgdGhlbWVfYncoKSArCiAgbGFicygKICAgIHRpdGxlID0gIkNvcnJlbGHDp8OjbyBlbnRyZSB2YXJpw6F2ZWlzIG51bcOpcmljYXMiLAogICAgc3VidGl0bGUgPSAiQ2lsaW5kcmFkYSB2cyBNaWxoYXMgcG9yIEdhbMOjbyIsCiAgICB4ID0gIkNpbGluZHJhZGEiLAogICAgeSA9ICJNaWxoYXMgcG9yIEdhbMOjbyBkZSBDb21idXN0w612ZWwiCiAgKQpgYGAKCiMjIDIuIEdyw6FmaWNvcyBkZSBsaW5oYQoKLSBVc2FyZW1vcyBwYXJhIGVzc2UgZ3LDoWZpY28gbyAqZGF0YXNldCogYG5vdGFzX2Rpc2NpcGxpbmEuY3N2YC4KLSBQcmVjaXNhbW9zIGRlaXjDoS1sbyBuYSBmb3JtYSBgdGlkeWBgLCBwaXZvdGFuZG8gYXMgY29sdW5hcyAoasOhIHZpbW9zIGNvbW8gZmF6IGlzc28pCiAgKyB0YW1iw6ltIHJlbm9tZWFyZW1vcyB1bWEgZGFzIHZhcmnDoXZlaXMKICArIGNoYW1hcmVtb3MgZXNzZXMgZGFkb3MgbW9kaWZpY2Fkb3MgcG9yIGBub3Rhc190aWR5YAoKYGBge3J9Cm5vdGFzX3RpZHkgPC0gZGFkb3Nfbm90YXMgJT4lIAogIHBpdm90X2xvbmdlcigKICAgICFDdXJzbywKICAgIG5hbWVzX3RvID0gImFubyIsCiAgICB2YWx1ZXNfdG8gPSAibm90YXMiCiAgKSAlPiUgCiAgcmVuYW1lKGN1cnNvID0gQ3Vyc28pCgpub3Rhc190aWR5CmBgYAoKLSBQYXJhIGNyaWFybW9zIG8gZ3LDoWZpY28gZGUgbGluaGFzLCB2YW1vcyB1c2FyIGFzIG3DqWRpYXMgZG8gY29uanVudG8gZGUgZGFkb3MgYW8gbG9uZ28gZG9zIGFub3MuCi0gQWdvcmEsIHZhbW9zIHN1bWFyaXphciBhcyBtw6lkaWFzIGRhcyB0dXJtYXMgcG9yIGFubzoKICArIGNoYW1hcmVtb3MgZGUgYHBgCgpgYGB7cn0KcCA8LSBub3Rhc190aWR5ICU+JSAKICBncm91cF9ieShhbm8pICU+JSAKICBzdW1tYXJpc2UoCiAgICBtZWRpYV9ub3RhcyA9IHJvdW5kKG1lYW4obm90YXMpLCAxKQopCgpwCmBgYAoKYGBge3J9CiBwICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBhbm8sIHkgPSBtZWRpYV9ub3RhcywgZ3JvdXAgPSAxKSkgKwogIGdlb21fbGluZShjb2xvciA9ICIjQkY2MTZBIiwgc2l6ZSA9IDEpCmBgYAotIE5vdGUgcXVlOgogICsgdXNhbW9zIG8gY29tYW5kbyBgZ3JvdXAgPSAxYCBubyBgYWVzKClgIGdlcmFsLgogICsgZXNjb2xoZW1vcyB1bWEgY29yIHZlcm1lbGhhIGVzcGVjw61maWNhIChjb20gc3Vwb3J0ZSBlbSBodG1sKQotIE8gY8OzZGlnbyBjb21wbGV0bywgasOhIGNvbSBvIHRlbWEsIGZpY2EKCmBgYHtyfQogbm90YXNfdGlkeSAlPiUgCiAgZ3JvdXBfYnkoYW5vKSAlPiUgCiAgc3VtbWFyaXNlKAogICAgbWVkaWFfbm90YXMgPSByb3VuZChtZWFuKG5vdGFzKSwgMSkKICApICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBhbm8sIHkgPSBtZWRpYV9ub3RhcywgZ3JvdXAgPSAxKSkgKwogIGdlb21fbGluZShjb2xvciA9ICIjQkY2MTZBIiwgc2l6ZSA9IDEpICsKICBsYWJzKAogICAgdGl0bGUgPSAiR3LDoWZpY28gZGUgTGluaGFzIGRhcyBOb3RhcyIsCiAgICB4ID0gIiIsCiAgICB5ID0gIk3DqWRpYSBkYXMgTm90YXMiCiAgKSArCiAgdGhlbWVfbWluaW1hbCgpCmBgYAotIFZlamEgcXVlIG8gImArYCIgc8OzIMOpIGNvbG9jYWRvIG5hcyAqKmNhbWFkYXMqKiBkbyBnZ29sb3QuCiAgKyB1bSBlcnJvIGNvbXVtIMOpIHRyb2NhciBvIGArYCBwZWxvIGAgJT4lIGAgKHBpcGUpCgojIyAzLiBHcsOhZmljbyBkZSBDb2x1bmFzCgotIFBhcmEgbyBncsOhZmljbyBkZSBjb2x1bmFzLCB2YW1vcyB1c2FyIG8gYGRhZG9zX21wZ2AgZSByZWxhY2lvbmFybW9zIHF1YW50b3MgY2Fycm9zIHVzYW0gb3MgZGlmZXJlbnRlcyBjb21idXN0w612ZWlzIChgcGAgPSAqcHJlbWl1bSo7IGByYCA9ICpyZWd1bGFyKjsgYGVgID0gKmV0aGFub2wqOyBgZGAgPSAqZGllc2VsKjsgYGdgID0gKmfDoXMqKS4KCmBgYHtyfQpkYWRvc19tcGcgJT4lIAogIGNvdW50KGNvbWJ1c3RpdmVsKQpgYGAKCi0gQXNzaW0sIHVzYXJlbW9zIGEgZnVuw6fDo28gYGdlb21fY29sKClgIHBhcmEgY3JpYXJtb3MgbyBncsOhZmljbyBkZSBjb2x1bmFzLCByZWxhY2lvbmFuZG8gbyBjb21idXN0w612ZWwgY29tIHN1YSBmcmVxdcOqbmNpYSBhYnNvbHV0YSBgbmAKCmBgYHtyfQpkYWRvc19tcGcgJT4lIAogIGNvdW50KGNvbWJ1c3RpdmVsKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBjb21idXN0aXZlbCwgeSA9IG4pKSArCiAgZ2VvbV9jb2woKQpgYGAKLSBTZSBxdWlzZXJtb3MgcHJlZW5jaGVyIGNhZGEgY29sdW5hIGNvbSB1bWEgY29yIGRlIGFjb3JkbyBjb20gbyB0aXBvIGRlIGNvbWJ1c3TDrXZlbCwgZGV2ZW1vcyBhY3Jlc2NlbnRhciBubyBgYWVzKClgIHVtIGFyZ3VtZW50byBwYXJhIHRhbCBmYXRvLgogICsgbyBhcmd1bWVudG8gYGNvbG9yYCB2YWkgY29icmlyIGEgZnJvbnRlaXJhIGRvcyBncsOhZmljb3M7CiAgKyBvIGFyZ3VtZW50byBgZmlsbGAgdmFpIHByZWVuY2hlciBvIGludGVyaW9yIGRvcyBncsOhZmljb3MuCgpgYGB7cn0KZGFkb3NfbXBnICU+JSAKICBjb3VudChjb21idXN0aXZlbCkgJT4lCiAgZ2dwbG90KGFlcyh4ID0gY29tYnVzdGl2ZWwsIHkgPSBuICwgY29sb3IgPSBjb21idXN0aXZlbCkpICsKICBnZW9tX2NvbCgpCmBgYAoKYGBge3J9CmRhZG9zX21wZyAlPiUgCiAgY291bnQoY29tYnVzdGl2ZWwpICU+JQogIGdncGxvdChhZXMoeCA9IGNvbWJ1c3RpdmVsLCB5ID0gbiAsIGZpbGwgPSBjb21idXN0aXZlbCkpICsKICBnZW9tX2NvbCgpCmBgYAotIEjDoSB1bSBjb25qdW50byBkZSBjb3JlcyAoW3ZpcmlkaXNdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy92aXJpZGlzL3ZpZ25ldHRlcy9pbnRyby10by12aXJpZGlzLmh0bWwpKSBxdWUgYWp1ZGEgbmEgdmlzdWFsaXphw6fDo28gZGUgcGVzc29hcyBjb20gZGlmaWN1bGRhZGUgZGUgdmlzw6NvIG91IGRhbHRvbmlzbW8KICArIHBhcnRpY3VsYXJtZW50ZSwgdXNvIGEgdmFyaWHDp8OjbyBgZGAgKCpkZWZhdWx0KikKCmBgYHtyfQpkYWRvc19tcGcgJT4lIAogIGNvdW50KGNvbWJ1c3RpdmVsKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBjb21idXN0aXZlbCwgeSA9IG4gLCBmaWxsID0gY29tYnVzdGl2ZWwpKSArCiAgZ2VvbV9jb2woKSArCiAgc2NhbGVfZmlsbF92aXJpZGlzX2QoKQpgYGAKLSBVbWEgbWFuZWlyYSByw6FwaWRhIGRlIHJlb3JkZW5hcm1vcyBhcyBjb2x1bmFzIGRlIGZvcm1hIGNyZXNjZW50ZSDDqSB1c2FybW9zIGEgZnVuw6fDo28gYGZjdF9yZW9yZGVyKClgLCBkbyBwYWNvdGUgW2Bmb3JjYXRzYF0oaHR0cHM6Ly9mb3JjYXRzLnRpZHl2ZXJzZS5vcmcvKSAoaW5jbHVzbyBubyBgdGlkeXZlcnNlYCkuCgpgYGB7cn0KZGFkb3NfbXBnICU+JSAKICBjb3VudChjb21idXN0aXZlbCkgJT4lIAogIGdncGxvdChhZXMoeCA9IGZjdF9yZW9yZGVyKGNvbWJ1c3RpdmVsLCBuKSwgeSA9IG4sIGZpbGwgPSBjb21idXN0aXZlbCkpICsKICBnZW9tX2NvbCgpICsKICBzY2FsZV9maWxsX3ZpcmlkaXNfZCgpCmBgYAotIFBhcmEgYSBvcmRlbmHDp8OjbyBkZSBmb3JtYSBkZWNyZXNjZW50ZSwgcG9kZW1vcyB1c2FyIGEgZnVuw6fDo28gYGRlc2MoKWAsIG91IHNpbXBsZXNtZW50ZSBjb2xvY2FyIG8gc2luYWwgImAtYCIgYW50ZXMgZGEgdmFyacOhdmVsIG51bcOpcmljYSBxdWUgZXN0YW1vcyBvcmRlbmFuZG8uCgpgYGB7cn0KZGFkb3NfbXBnICU+JSAKICBjb3VudChjb21idXN0aXZlbCkgJT4lIAogIGdncGxvdChhZXMoeCA9IGZjdF9yZW9yZGVyKGNvbWJ1c3RpdmVsLCAtbiksIHkgPSBuLCBmaWxsID0gY29tYnVzdGl2ZWwpKSArCiAgZ2VvbV9jb2woKSArCiAgc2NhbGVfZmlsbF92aXJpZGlzX2QoKQpgYGAKLSBNZWxob3JhbmRvIG8gZ3LDoWZpY28sIHRlbW9zOgoKYGBge3J9CmRhZG9zX21wZyAlPiUgCiAgY291bnQoY29tYnVzdGl2ZWwpICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBmY3RfcmVvcmRlcihjb21idXN0aXZlbCwgLW4pLCB5ID0gbiwgZmlsbCA9IGNvbWJ1c3RpdmVsKSkgKwogIGdlb21fY29sKCkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19kKCkgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJDb21wYXJhbmRvIG9zIHRpcG9zIGRlIGNvbWJ1c3TDrXZlaXMiLAogICAgc3VidGl0bGUgPSAiKGBwYCA9IHByZW1pdW07IGByYCA9IHJlZ3VsYXI7IGBlYCA9IGV0aGFub2w7IGBkYCA9IGRpZXNlbDsgYGdgID0gZ8OhcykiLAogICAgeCA9ICIiLAogICAgeSA9ICIiCikKYGBgCgoKIyMjIDMuMSBHcsOhZmljbyBkZSBCYXJyYXMKCi0gTWVzbWEgaWRlaWEgZG8gYW50ZXJpb3IKLSBHZXJhbG1lbnRlIHVzYWRvIHF1YW5kbyBvIG5vbWUgZGEgdmFyacOhdmVsIMOpIGdyYW5kZQotIG8gcGFjb3RlIGBzY2FsZXNgIHNlcsOhIHVzYWRvIHBhcmEgZXhpYmlyIHVtIGRvcyBlaXhvcyBjb20gbm90YcOnw6NvIGRlIHBvcmNlbnRhZ2VtLgogICsgZXNzZSBwYWNvdGUgZmF6IG11aXRhcyBvdXRyYXMgY29pc2FzLCBtYXMgc8OzIHVzYXJlbW9zIGVzc2EgbmVzc2UgZXhlbXBsby4KICArIHNlIHZvY8OqIG7Do28gdGl2ZXIgaW5zdGFsYWRvLCBmYcOnYS1vIGNvbSBvIGNvbWFuZG8gYGluc3RhbGwucGFja2FnZXMoInNjYWxlcyIpYC4KCmBgYHtyfQpsaWJyYXJ5KHNjYWxlcykKYGBgCgotIFVzYW1vcyBhIGZ1bsOnw6NvIGBsYWJlbF9wZXJjZW50KClgLCBkZW50cm8gZGEgZnVuw6fDo28gYHNjYWxlX3hfY29udGludW91cygpYCwgbm8gYXJndW1uZW50byBgbGFiZWxgLgogICsgdGFtYsOpbSBwb2RlbW9zIGRlbGltaXRhciBvIGVpeG8gJHgkIHBvciBhcXVpIQogICsgbyB0ZXJtbyBgYWNjdXJhY3lgIGRpeiBxdWFudGFzIGNhc2FzIGRlY2ltYWlzIGRlc2VqYW1vcywgY2FzbyBzZWphIG5lY2Vzc8OhcmlvLgoKYGBge3J9CmRhZG9zX21wZyAlPiUgCiAgY291bnQoY2xhc3NlKSAlPiUgCiAgbXV0YXRlKHByb3AgPSBuIC8gc3VtKG4pKSAlPiUgCiAgZ2dwbG90KCkgKwogIGFlcyh4ID0gcHJvcCwgeSA9IGZjdF9yZW9yZGVyKGNsYXNzZSwgcHJvcCksIGZpbGwgPSBjbGFzc2UpICsKICBnZW9tX2NvbCgpICsKICBzY2FsZV9maWxsX3ZpcmlkaXNfZCgpICsKICBzY2FsZV94X2NvbnRpbnVvdXMoCiAgICBsYWJlbHMgPSBsYWJlbF9wZXJjZW50KGFjY3VyYWN5ID0gMSksIAogICAgbGltaXRzID0gYygwLCAuMykKICApICsKICBsYWJzKAogICAgdGl0bGUgPSAiUG9yY2VudGFnZW0gZGFzIENsYXNzZXMgZG9zIENhcnJvcyIsCiAgICB4ID0gIiIsCiAgICB5ID0gIiIKICApICsKICB0aGVtZV9taW5pbWFsKCkKYGBgCgojIyA0LiBIaXN0b2dyYW1hCgotIFVtIGhpc3RvZ3JhbWEgw6kgdW1hIHJlcHJlc2VudGHDp8OjbyBncsOhZmljYSBkYSBkaXN0cmlidWnDp8OjbyBkZSB1bWEgdmFyacOhdmVsIG51bcOpcmljYS4gCiAgKyBub3RlIHF1ZSBhY2VpdGEgYXBlbmFzIHZhcmnDoXZlaXMgbnVtw6lyaWNhcyBkZSBlbnRyYWRhLiAKLSBBIHZhcmnDoXZlbCDDqSBkaXZpZGlkYSBlbSB2w6FyaWFzIGNsYXNzZXMgKGludGVydmFsb3IpCiAgKyBvIG7Dum1lcm8gZGUgb2JzZXJ2YcOnw7VlcyBwb3IgY2xhc3NlcyDDqSByZXByZXNlbnRhZG8gcGVsYSBhbHR1cmEgZGEgYmFycmEuCi0gVXNhcmVtb3MgbyAqZGF0YXNldCogYG5vdGFzX3RpZHlgIHBhcmEgZXhlbXBsaWZpY2HDp8OjbzoKCmBgYHtyfQpub3Rhc190aWR5ICU+JSAKICBnZ3Bsb3QoKSArCiAgYWVzKHggPSBub3RhcykgKwogIGdlb21faGlzdG9ncmFtKGZpbGwgPSAiYmx1ZSIsIGNvbG9yID0gImJsYWNrIikgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJIaXN0b2dyYW1hIGRhcyBOb3RhcyIsCiAgICB4ID0gIk5vdGFzIiwKICAgIHkgPSAiIgogICkgKwogIHRoZW1lX21pbmltYWwoKQpgYGAKCi0gUG9kZW1vcyBhbHRlcmFyIG8gdGFtYW5obyBkYXMgY2xhc3NlcyB1c2FuZG8gbyBhcmd1bWVudG8gYGJpbndpZHRoYAoKYGBge3J9Cm5vdGFzX3RpZHkgJT4lIAogIGdncGxvdCgpICsKICBhZXMoeCA9IG5vdGFzKSArCiAgZ2VvbV9oaXN0b2dyYW0oZmlsbCA9ICJibHVlIiwgY29sb3IgPSAiYmxhY2siLCBiaW53aWR0aCA9IDEpICsKICBsYWJzKAogICAgdGl0bGUgPSAiSGlzdG9ncmFtYSBkYXMgTm90YXMiLAogICAgeCA9ICJOb3RhcyIsCiAgICB5ID0gIiIKICApICsKICB0aGVtZV9taW5pbWFsKCkKYGBgCgojIyA1LiBHcsOhZmljbyBkZSBEZW5zaWRhZGUKCi0gUG9kZW1vcyBpbnRlcnByZXRhciBjb21vIHVtYSBgc3Vhdml6YcOnw6NvYCBkbyBIaXN0b2dyYW1hLgoKYGBge3J9Cm5vdGFzX3RpZHkgJT4lIAogIGdncGxvdCgpICsKICBhZXMoeCA9IG5vdGFzKSArCiAgZ2VvbV9kZW5zaXR5KGZpbGwgPSAiIzQwNDA4MCIpCmBgYAotIFBvZGVtb3MgY29tcGFyYXIgcG9yIGN1cnNvCgpgYGB7cn0Kbm90YXNfdGlkeSAlPiUgCiAgZ2dwbG90KCkgKwogIGFlcyh4ID0gbm90YXMpICsKICBnZW9tX2RlbnNpdHkoYWVzKGZpbGwgPSBjdXJzbyksIGFscGhhID0gMC4zKSArCiAgc2NhbGVfZmlsbF92aXJpZGlzX2QoKQpgYGAKLSBBaW5kYSBzZXBhcmFuZG8gcG9yICJmYWNldGFzIiBwZWxvcyBjdXJzb3MKCmBgYHtyfQpub3Rhc190aWR5ICU+JSAKICBnZ3Bsb3QoKSArCiAgYWVzKHggPSBub3RhcykgKwogIGdlb21fZGVuc2l0eShhZXMoZmlsbCA9IGN1cnNvKSwgYWxwaGEgPSAwLjMpICsKICBmYWNldF9ncmlkKGN1cnNvfi4pICsKICBzY2FsZV9maWxsX3ZpcmlkaXNfZCgpCmBgYAotIE91ICJmYWNldGFuZG8iIGFvIGxvbmdvIGRvcyBhbm9zCgpgYGB7cn0Kbm90YXNfdGlkeSAlPiUgCiAgZ2dwbG90KCkgKwogIGFlcyh4ID0gbm90YXMpICsKICBnZW9tX2RlbnNpdHkoYWVzKGZpbGwgPSBjdXJzbyksIGFscGhhID0gMC4zKSArCiAgZmFjZXRfZ3JpZChhbm9+LikgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19kKCkKYGBgCi0gRXN0ZSDDumx0aW1vLCBwb3IgY29udGVyIG11aXRhIGluZm9ybWHDp8OjbywgbsOjbyBmaWNvdSB0w6NvIGNsYXJhIG5vc3NhIGNvbXVuaWNhw6fDo28uCiAgKyBQYXJhIGNvbnRvcm5hciBpc3NvLCBwb2RlbW9zIHVzYXIgbyBwYWNvdGUgW2BnZ3JpZGdlc2BdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9nZ3JpZGdlcy92aWduZXR0ZXMvaW50cm9kdWN0aW9uLmh0bWwpCgojIyMgNS4xIFJpZGdlbGluZSAoSm95cGxvdCkKCi0gQ2FycmVndWUgKHNlIHByZWNpc28sIGluc3RhbGUtbyBhbnRlcykgbyBwYWNvdGUgYGdncmlkZ2VzYAoKYGBge3J9CiMgaW5zdGFsbC5wYWNrYWdlcygiZ2dyaWRnZXMiKQpsaWJyYXJ5KGdncmlkZ2VzKQpgYGAKCi0gVXNhcmVtb3MgYSBmdW7Dp8OjbyBgZ2VvbV9kZW5zaXR5X3JpZGdlcygpYAoKYGBge3J9Cm5vdGFzX3RpZHkgJT4lIAogIGdncGxvdCgpICsKICBhZXMoeCA9IG5vdGFzLCB5ID0gYW5vLCBmaWxsID0gY3Vyc28pICsKICBnZW9tX2RlbnNpdHlfcmlkZ2VzKGFscGhhID0gMC41KSArCiAgc2NhbGVfZmlsbF92aXJpZGlzX2QoKQpgYGAKCi0gUG9kZW1vcyBmaWx0cmFyIHBvciB1bSBjdXJzbyBlc3BlY8OtZmljbywgcG9yIGV4ZW1wbG8sIFF1w61taWNhLgoKYGBge3J9Cm5vdGFzX3RpZHkgJT4lIAogIGZpbHRlcihjdXJzbyA9PSAiUXVpIikgJT4lIAogIGdncGxvdCgpICsKICBhZXMoeCA9IG5vdGFzLCB5ID0gYW5vLCBmaWxsID0gY3Vyc28pICsKICBnZW9tX2RlbnNpdHlfcmlkZ2VzKGFscGhhID0gMC41KSArCiAgc2NhbGVfZmlsbF92aXJpZGlzX2QoKQpgYGAKCiMjIDYuIEJveHBsb3QKCi0gSsOhIHZpbW9zIG11aXRvIHNvYnJlIGJveHBsb3QgZW0gbm9zc2EgZGlzY2lwbGluYSAoaW5jbHVzaXZlIGFydGlnb3MgcXVlIGV4cGxpY2FtIG9zIHBvcm1lbm9yZXMpCi0gUGFyYSBleGVtcGxpZmljYXIsIGNvbnRpbnVhcmVtb3MgY29tIG8gKmRhdGFzZXQqIGBub3Rhc190aWR5YC4KICArIGluaWNpYWxtZW50ZSwgdmFtb3MgY29tcGFyYXIsIGFvIGxvZ28gZG9zIGFub3MsIG9zIGJveHBsb3RzIGRvcyBjdXJzb3MKICAKYGBge3J9Cm5vdGFzX3RpZHkgJT4lIAogIGdncGxvdCgpICsKICBhZXMoeCA9IGFubywgeT0gbm90YXMsIGZpbGwgPSBjdXJzbykgKwogIGdlb21fYm94cGxvdCgpICsKICBzY2FsZV9maWxsX3ZpcmlkaXNfZCgpCmBgYAotIEFnb3JhLCB2YW1vcyBjb21wYXJhciBhcGVuYXMgYXMgbm90YXMgYW8gbG9uZ28gZG9zIGFub3MuCiAgKyBzZSBxdWlzZXJtb3Mgb21pdGlyIGFzIGxlZ2VuZGFzLCB1c2Ftb3MgYHNob3cubGVnZW5kID0gRkFMU0VgCgpgYGB7cn0Kbm90YXNfdGlkeSAlPiUgCiAgZ2dwbG90KCkgKwogIGFlcyh4ID0gYW5vLCB5PSBub3RhcykgKwogIGdlb21fYm94cGxvdChhZXMoZmlsbCA9IGFubyksIHNob3cubGVnZW5kID0gRkFMU0UpICsKICBzY2FsZV9maWxsX3ZpcmlkaXNfZCgpCmBgYAojIyBFeHRyYTogcmV0YSB2ZXJ0aWNhbCBlIGFub3Rhw6fDtWVzCgotIENvbnNpZGVyZSBhIGRlbnNpZGFkZSBkYXMgbm90YXMgZG8gY3Vyc286CgpgYGB7cn0Kbm90YXNfdGlkeSAlPiUgCiAgZ2dwbG90KCkgKwogIGFlcyh4ID0gbm90YXMpICsKICBnZW9tX2RlbnNpdHkoZmlsbCA9ICJsaWdodGJsdWUiKSArCiAgbGFicygKICAgIHRpdGxlID0gIkRpc3RyaWJ1acOnw6NvIGRhcyBOb3RhcyIsCiAgICBzdWJ0aXRsZSA9ICIoVGFudG8gZGUgTWF0ZW3DoXRpY2EsIHF1YW50byBkZSBRdcOtbWljYSkiLAogICAgeCA9ICJOb3RhcyIsCiAgICB5ID0gIkRpc3RyaWJ1acOnw6NvIgogICkgKwogIHRoZW1lX21pbmltYWwoKQpgYGAKLSBDb21vIHJlcHJlc2VudGFyLCBwb3IgZXhlbXBsbywgYSBtw6lkaWEgZGVzc2FzIG5vdGFzIGNvbSB1bWEgcmV0YSB2ZXJ0aWNhbD8KCmBgYHtyfQpub3Rhc190aWR5ICU+JSAKICBnZ3Bsb3QoKSArCiAgYWVzKHggPSBub3RhcykgKwogIGdlb21fZGVuc2l0eShmaWxsID0gImxpZ2h0Ymx1ZSIsIGFscGhhID0gMC42KSArCiAgbGFicygKICAgIHRpdGxlID0gIkRpc3RyaWJ1acOnw6NvIGRhcyBOb3RhcyIsCiAgICBzdWJ0aXRsZSA9ICIoVGFudG8gZGUgTWF0ZW3DoXRpY2EsIHF1YW50byBkZSBRdcOtbWljYSkiLAogICAgeCA9ICJOb3RhcyIsCiAgICB5ID0gIkRpc3RyaWJ1acOnw6NvIgogICkgKwogIHRoZW1lX21pbmltYWwoKSArCiAgZ2VvbV92bGluZShhZXMoeGludGVyY2VwdCA9IG1lYW4obm90YXMpKSwgY29sb3IgPSAicmVkIikgCmBgYAotIEUgc2UgcXVpc2VybW9zIGNvbG9jYXIgbyBub21lICJNw6lkaWEiIG5lc3NhIHJldGE/CgpgYGB7cn0Kbm90YXNfdGlkeSAlPiUgCiAgZ2dwbG90KCkgKwogIGFlcyh4ID0gbm90YXMpICsKICBnZW9tX2RlbnNpdHkoZmlsbCA9ICJsaWdodGJsdWUiLCBhbHBoYSA9IDAuNikgKwogIGdlb21fdmxpbmUoYWVzKHhpbnRlcmNlcHQgPSBtZWFuKG5vdGFzKSksIGNvbG9yID0gInJlZCIpICsKICBsYWJzKAogICAgdGl0bGUgPSAiRGlzdHJpYnVpw6fDo28gZGFzIE5vdGFzIiwKICAgIHN1YnRpdGxlID0gIihUYW50byBkZSBNYXRlbcOhdGljYSwgcXVhbnRvIGRlIFF1w61taWNhKSIsCiAgICB4ID0gIk5vdGFzIiwKICAgIHkgPSAiRGlzdHJpYnVpw6fDo28iCiAgKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICBhbm5vdGF0ZSgidGV4dCIsIHggPSA2LCB5ID0gMC4wNSwgbGFiZWwgPSAiTcOpZGlhIiwgY29sb3IgPSAicmVkIikKYGBgCgo=