Introdução

O conjunto de dados de jogadores de futebol do jogo fifa 24 desenvolvido pela famosa empresa EA SPORTS é uma coleção abrangente de informações sobre jogadores de futebol de todo o mundo. Este conjunto de dados oferece uma riqueza de atributos relacionados a cada jogador, tornando-o um recurso valioso para diversas análises e insights sobre o mundo do futebol, tanto para entusiastas de jogos quanto para entusiastas de esportes do mundo real.

What’s the purpose/ Qual o nosso objetivo???

O objetivo é encontrar padrões de comportamento nos jogadores brasileiros do fifa 24 da EA SPORTS e encontrar o melhor modelo que possa fazer uma boa predição do salário em dolár deles.

Quais variáveis foram escolhidas???

No banco original tinha 41 variáveis e 5682 linhas que correspondem aos diferentes jogadores mas resolvemos fazer a análise com apenas 223 jogadores brasileiros do fifa 24 e as 29 variáveis que seguem:

Height: The height of the player in centimeters.
Weight: The weight of the player in kilograms.
Age: The age of the player.
Ball Control: Player’s skill in controlling the ball.
Dribbling: Player’s dribbling ability.
Slide Tackle: Player’s ability to perform slide tackles.
Stand Tackle: Player’s ability to perform standing tackles.
Aggression: Player’s aggression level.
Reactions: Player’s reaction time.
Attacking Position: Player’s positioning for attacking plays.
Interceptions: Player’s skill in intercepting passes.
Vision: Player’s vision on the field.
Crossing: Player’s ability to deliver crosses.
Short Pass: Player’s short passing accuracy.
Long Pass: Player’s ability in long passing.
Acceleration: Player’s acceleration on the field.
Stamina: Player’s stamina level.
Strength: Player’s physical strength.
Sprint Speed: Player’s speed in sprints.
Agility: Player’s agility in maneuvering.
Jumping: Player’s jumping ability.
Heading: Player’s heading skills.
Shot Power: Player’s power in shooting.
Finishing: Player’s finishing skills.
Long Shots: Player’s ability to make long-range shots.
Curve: Player’s ability to curve the ball.
Penalties: Player’s penalty-taking skills.
Volleys: Player’s volleying skills.
Value: The estimated value of the player in dollars.

Análise Exploratória

Pacotes utilizados

pacman::p_load("tidyverse", "readxl", "glmnet", "psych", "caret", "readxl", "DT", "knitr", "GGally", "ggplot2", "embed", "umap", "ggcorrplot2", "sqldf", "factoextra","Hmisc")


Overview do banco de dados

ourdata = read_excel("fifa_24_brazil.xlsx")
ourdata = data.frame(ourdata)
DT::datatable(ourdata)


Estrutura do banco de dados

str(ourdata)
## 'data.frame':    223 obs. of  29 variables:
##  $ height       : num  172 177 182 172 186 183 188 185 186 183 ...
##  $ weight       : num  60 73 70 69 80 75 80 78 74 76 ...
##  $ age          : num  34 34 30 23 31 31 29 27 24 23 ...
##  $ ball_control : num  66 65 73 71 66 73 19 69 60 62 ...
##  $ dribbling    : num  70 66 72 72 64 74 13 66 45 52 ...
##  $ slide_tackle : num  54 15 26 30 42 35 14 42 70 63 ...
##  $ stand_tackle : num  55 27 28 35 33 37 14 39 70 71 ...
##  $ aggression   : num  67 74 54 51 56 50 28 58 63 76 ...
##  $ reactions    : num  64 68 60 64 58 63 53 65 62 58 ...
##  $ att_position : num  72 70 67 58 72 71 4 72 28 48 ...
##  $ interceptions: num  56 20 24 31 39 41 7 32 66 66 ...
##  $ vision       : num  64 58 58 61 43 52 38 68 29 52 ...
##  $ crossing     : num  62 59 60 58 49 68 13 37 27 62 ...
##  $ short_pass   : num  63 63 68 66 58 50 17 63 63 65 ...
##  $ long_pass    : num  55 45 52 51 45 49 23 51 59 53 ...
##  $ acceleration : num  87 70 75 72 51 83 24 69 63 65 ...
##  $ stamina      : num  74 52 71 65 62 71 22 71 69 73 ...
##  $ strength     : num  69 71 64 53 71 70 58 74 73 71 ...
##  $ sprint_speed : num  90 71 74 70 67 80 22 73 55 59 ...
##  $ agility      : num  78 69 77 73 41 73 34 72 45 50 ...
##  $ jumping      : num  80 73 88 59 70 68 53 67 72 67 ...
##  $ heading      : num  53 66 59 40 71 46 10 65 68 62 ...
##  $ shot_power   : num  64 85 68 62 74 50 53 58 48 71 ...
##  $ finishing    : num  62 72 63 65 70 77 7 70 24 41 ...
##  $ long_shots   : num  64 68 62 63 60 73 5 62 21 56 ...
##  $ curve        : num  45 62 64 66 46 45 10 47 26 54 ...
##  $ penalties    : num  60 71 57 63 61 63 13 61 42 62 ...
##  $ volleys      : num  53 70 62 53 67 67 7 59 34 47 ...
##  $ value        : num  1000000 1000000 1000000 1000000 1000000 1000000 1100000 1100000 1100000 1100000 ...

Comentário: Todas as variáveis presentes no banco de dados são numéricas quantitativas contínuas


Detecção de missings data (dados faltantes) ???

missings_vector = sapply(ourdata, function(x) sum(is.na(ourdata)))

names(missings_vector) = names(ourdata)

missings_vector |> print()
##        height        weight           age  ball_control     dribbling 
##             0             0             0             0             0 
##  slide_tackle  stand_tackle    aggression     reactions  att_position 
##             0             0             0             0             0 
## interceptions        vision      crossing    short_pass     long_pass 
##             0             0             0             0             0 
##  acceleration       stamina      strength  sprint_speed       agility 
##             0             0             0             0             0 
##       jumping       heading    shot_power     finishing    long_shots 
##             0             0             0             0             0 
##         curve     penalties       volleys         value 
##             0             0             0             0

Comentário: Não tem presença de nenhum dado faltante no banco obtido.


Descritivas do banco de dados

standard_desviation = numeric(0)
variance = numeric(0)
min_var = numeric(0)
max_var = numeric(0)
mediana_var = numeric(0)
media_var = numeric(0)

for (i in (1:29)){
  
  standard_desviation = append(standard_desviation, sd(ourdata[,i]))
  variance = append(variance, var(ourdata[,i]))
  min_var = append(min_var, min(ourdata[,i]))
  max_var = append(max_var, max(ourdata[,i]))
  mediana_var = append(mediana_var, median(ourdata[,i]))
  media_var = append(media_var, mean(ourdata[,i]))}



Desvio_padrao = standard_desviation
Variancia = variance
Minimo = min_var
Maximo = max_var
Mediana = mediana_var
Media = media_var

banco_summary = rbind(Desvio_padrao, Variancia, Minimo, Maximo, Mediana, Media)

rownames(banco_summary) = c("Desvio_Padrão", "Variância", "Mínimo", "Máximo", "Mediana", "Média")
colnames(banco_summary) = names(ourdata)



knitr::kable(banco_summary)
height weight age ball_control dribbling slide_tackle stand_tackle aggression reactions att_position interceptions vision crossing short_pass long_pass acceleration stamina strength sprint_speed agility jumping heading shot_power finishing long_shots curve penalties volleys value
Desvio_Padrão 7.148488 7.062387 4.374788 14.97296 17.47348 22.77781 22.94537 16.95636 7.448882 19.32247 22.34332 13.63287 17.73532 13.04029 13.67454 14.72324 13.68716 12.43989 13.95006 14.60047 12.87711 16.86337 12.00169 19.37083 18.58055 17.95708 15.52879 17.70823 9.343031e+06
Variância 51.100877 49.877308 19.138771 224.18963 305.32243 518.82855 526.49020 287.51800 55.485840 373.35769 499.22385 185.85509 314.54143 170.04925 186.99293 216.77376 187.33830 154.75086 194.60405 213.17364 165.81994 284.37337 144.04052 375.22894 345.23694 322.45655 241.14342 313.58155 8.729222e+13
Mínimo 164.000000 60.000000 19.000000 11.00000 7.00000 8.00000 8.00000 20.00000 48.000000 4.00000 6.00000 15.00000 8.00000 14.00000 16.00000 15.00000 21.00000 30.00000 20.00000 21.00000 31.00000 9.00000 23.00000 4.00000 5.00000 8.00000 12.00000 6.00000 1.500000e+01
Máximo 197.000000 92.000000 39.000000 94.00000 95.00000 87.00000 87.00000 94.00000 88.000000 86.00000 86.00000 90.00000 84.00000 85.00000 83.00000 92.00000 94.00000 93.00000 94.00000 93.00000 91.00000 91.00000 88.00000 86.00000 83.00000 88.00000 91.00000 86.00000 9.950000e+07
Mediana 180.000000 75.000000 27.000000 70.00000 68.00000 59.00000 62.00000 63.00000 69.000000 61.00000 60.00000 61.00000 62.00000 68.00000 62.00000 71.00000 71.00000 69.00000 71.00000 69.00000 68.00000 62.00000 65.00000 55.00000 60.00000 57.00000 57.00000 53.00000 2.100000e+06
Média 180.318386 75.278027 27.565022 65.65919 62.36323 50.25112 52.17937 60.00448 68.269058 56.14350 51.51570 59.09865 56.88789 65.43946 59.25561 68.66368 68.67265 67.86099 68.65919 66.91928 66.31390 57.97758 63.99552 52.17937 54.61883 54.05381 54.97309 49.67265 5.174574e+06


Histogramas

for (i in (1:29)){

  hist(ourdata[, i], col = "blue", xlab = names(ourdata)[i],  main = paste0("Histograma da variável", " ",  names(ourdata)[i]))

}

Comentário: Parece que nenhuma variável está seguindo uma distribuição normal. Fora as variáveis Altura e Tempo de reação do jogador que apresentaram uma distribuição um pouco menos assimétrica, as outras variáveis, apresentaram uma distribuição fortemente assimétrica.


Boxplots

boxplot(ourdata, col = "blue", main = "Boxplot de todas as variáveis do banco" )

Boxplots - ZOOM1

boxplot(ourdata, col = "blue", main = "Boxplot de todas as variáveis do banco -  ZOOM1" , ylim = c(0, 200))

Boxplots - ZOOM2

boxplot(ourdata, col = "blue", main = "Boxplot de todas as variáveis do banco - ZOOM2" , ylim = c(0, 90))


ggpairs(ourdata, aes(alpha=0.8), lower=list(continuous="points"),
        upper=list(continuous="blank"),
        axisLabels="none", switch="both")

ggcorrplot::ggcorrplot(cor(ourdata),
                       hc.order = TRUE,
                       type = "lower",
                       lab = TRUE,
                       lab_size = 1.5) 

Comentário:Podemos reparar que obtivemos fortes correlações lineares entre algumas variáveis como por exmplo as variáveis Habilidade do jogador para driblar e Habilidade do jogador a controlar a bola (0,93). Porém, tem várias variáveis que parecem não ter uma relação linear como por exemplo as variáveis Agilidade do jogador nas manobras e o Nível de agressão do jogador (0).

Redução de Dimensionalidade

Antes de ter aplicado o método de redução de dimensionalidade UMAP, as variáveis têm sido padronizadas

# Semente definida 
set.seed(00316695)
umap_fit <- ourdata %>%
  select(where(is.numeric)) %>%
  scale() %>% 
  umap()



umap_df <- umap_fit$layout %>%
  as.data.frame() %>% 
  round(5)
 


DT::datatable(umap_df)

Interpretação: Inicialmente tinhamos 29 variáveis mas o método UMAP retornou duas variáveis que são combinações das variáveis originais. É importante salientar que as variáveis foram padronizadas antes de aplicar o método UMAP.

Clusterização

fviz_nbclust(umap_df, kmeans,method = "silhouette", linecolor = "blue")

Interpretação: Olhando o gráfico acima parece que seria melhor agrupar os dados em exatamente 3 (três) clusters (grupos). A localização da curva (joelho/cotovelo) na plotagem é geralmente considerada como um indicador do número apropriado de agrupamentos. A função R fviz_nbclust() parece fornecer uma solução conveniente para estimar o número ideal de clusters.


Visualização dos dados clusterizados::

set.seed(00316695)

dataKmeans <- kmeans(umap_df,3)

clusterized <- fviz_cluster(dataKmeans,
 data = umap_df, 
 geom = "point",
 stand = FALSE,
 title = "kMEANS — CLUSTERING",
 frame.type = "convex")

clusterized

my_clusters= (data.frame(Clusters = 1:3, dataKmeans$centers))  
names(my_clusters) = c("Clusters", "Variável1", "Variável2")
knitr::kable((my_clusters) %>% dplyr::arrange(my_clusters[2]))
Clusters Variável1 Variável2
3 3 -8.566504 -14.248161
2 2 -1.669596 2.091563
1 1 3.482893 -0.520113

Interpretação a partir dos centróides dos clusters:

O gráfico mostra que os grupos 1 e 2 são bem próximos e os dois juntos são bem distantes do grupo 3. Por outro lado, parece que o grupo 2 contém mais jogadores brasileiros do fifa24 do que os dois outros grupos. Olhando as características dos centróides dos 3 (três) clusters (grupos) acima podemos dizer que em média o grupo 1 possui características acima da média considerando apenas a variável1 e o grupo 2 é aquele que apresentou características acima da média considerando apenas a variável2. Infelizmente a dimensão do banco original foi bem reduzida, mas seria mais interessante olhar a formação dos clusters em função de todas as variáveis íniciais

E se não tivéssemos reduzido a dimensão do banco de dados, o que aconteceria com os clusters???

Vamos olhar juntos o que acontecer…..

# Semente definida 
set.seed(00316695)
fviz_nbclust(ourdata, kmeans,method = "silhouette", linecolor = "red")

set.seed(00316695)

dataKmeans_entire <- kmeans(ourdata,3)

clusterized_entire <- fviz_cluster(dataKmeans_entire,
 data = ourdata, 
 geom = "point",
 stand = FALSE,
 title = "kMEANS — CLUSTERING",
 frame.type = "convex")

clusterized_entire

my_clusters_entire= (data.frame(Clusters = 1:3, dataKmeans_entire$centers))  

knitr::kable(my_clusters_entire) 
Clusters height weight age ball_control dribbling slide_tackle stand_tackle aggression reactions att_position interceptions vision crossing short_pass long_pass acceleration stamina strength sprint_speed agility jumping heading shot_power finishing long_shots curve penalties volleys value
1 175.0000 68.00000 31.00000 94.00000 95.00000 29.00000 32.00000 63.00000 88.00000 86.00000 37.00000 90.00000 83.00000 85.00000 81.00000 88.00000 79.00000 52.00000 86.00000 93.00000 62.00000 63.00000 79.00000 83.00000 81.00000 88.00000 91.00000 86.00000 99500000
2 179.3871 73.70968 26.45161 74.70968 71.09677 54.35484 56.77419 67.96774 78.25806 64.19355 56.32258 67.54839 64.00000 73.83871 65.70968 77.19355 77.61290 70.06452 76.58065 73.61290 70.64516 64.38710 69.51613 59.64516 61.77419 64.25806 59.32258 57.48387 19774194
3 180.4974 75.57068 27.72775 64.04188 60.77487 49.69634 51.53927 58.69634 66.54450 54.68063 50.81152 57.56544 55.59686 63.97382 58.09424 67.17801 67.16754 67.58639 67.28272 65.69634 65.63351 56.91099 63.02094 50.80628 53.31937 52.21990 54.07853 48.21466 2311152

Interpretação: Temos agora todas as características importantes dos 3 clusters. Com base nessas informações pode se tomar decisões ou desenvolver algum sistema de recomendação no jogo. Por exemplo, novos contratos podem ser oferecidos para alguns jogadores do cluster 1 que aparentemente possuem uma boa finalização e uma boa visão do jogo, habilidades que podem ser muito valiosas numa partida de football.

Forecasting

Pacotes utilizados

library(tidyverse)
library(caret)
require(WVPlots)
library(gridExtra)
library(grid)
library(ggridges)
library(ggthemes)
theme_set(theme_minimal())

library(iml)


library(breakDown)

library(rpart) #arvore de decisao
library(rpart.plot) # arvore de decisao

library(plotROC) # plotar a curva roc
library(pROC)

Divisão dos dados do banco em \(80\)% e \(20\)%

set.seed(00316695)
idx <- createDataPartition(ourdata$value, 
                           p = 0.8, ## proporcao de dados do banco de treinamento
                           list = FALSE, 
                           times = 1)

ourdata_train <- ourdata[ idx,]
ourdata_test  <- ourdata[-idx,]

Árvores de Decisão

fit_control <- trainControl(method = "repeatedcv",
                           number = 5,
                           repeats = 1)

set.seed(00316695)
arvore_model <- train(value ~ ., 
                  data = ourdata_train, 
                  method = "rpart", # árvore de decisao 
                  preProcess = c("scale", "center"),
                  trControl = fit_control)
arvore_model
## CART 
## 
## 179 samples
##  28 predictor
## 
## Pre-processing: scaled (28), centered (28) 
## Resampling: Cross-Validated (5 fold, repeated 1 times) 
## Summary of sample sizes: 143, 142, 144, 143, 144 
## Resampling results across tuning parameters:
## 
##   cp         RMSE     Rsquared   MAE    
##   0.0233678  6750566  0.5115469  3558647
##   0.1017104  7475209  0.4253208  4035609
##   0.4388383  8225123  0.3045293  4559974
## 
## RMSE was used to select the optimal model using the smallest value.
## The final value used for the model was cp = 0.0233678.
plot(arvore_model)

rpart.plot(arvore_model$finalModel)

Salários preditos

test_predict <- predict(arvore_model, ourdata_test)

print(test_predict)
##        3        4        9       11       15       17       27       31 
##  2642832  2642832  2642832  2642832  2642832  2642832  2642832  2642832 
##       32       33       34       38       49       61       66       70 
##  2642832  2642832  2642832  2642832  2642832 12252424  2642832 12252424 
##       72       73       89       95       96       98      104      110 
## 12252424 12252424  2642832  2642832  2642832  2642832  2642832  2642832 
##      113      117      121      125      127      136      139      143 
##  2642832  2642832  2642832 12252424 34812500  2642832 12252424 12252424 
##      148      159      163      168      171      172      175      177 
## 12252424  2642832  2642832  2642832  2642832  2642832  2642832  2642832 
##      187      194      198      217 
##  2642832  2642832  2642832  2642832
#library(ModelMetrics)
rmse_arvore_decision = RMSE(test_predict, ourdata_test$value)

Result: Obtivemos um RMSE de \(5025686\)


Será que podemos encontrar um modelo com uma melhor performance???

Random Forest

modelLookup("rf")
##   model parameter                         label forReg forClass probModel
## 1    rf      mtry #Randomly Selected Predictors   TRUE     TRUE      TRUE
fit_control_rf <- trainControl(method = "repeatedcv",
                           number = 5,
                           repeats = 1)

set.seed(00316695)
rf_model <- train(value ~ ., 
                  data = ourdata_train, 
                  method = "rf", 
                  preProcess = c("scale", "center"),
                  trControl = fit_control_rf,
                  verbose = FALSE)
rf_model
## Random Forest 
## 
## 179 samples
##  28 predictor
## 
## Pre-processing: scaled (28), centered (28) 
## Resampling: Cross-Validated (5 fold, repeated 1 times) 
## Summary of sample sizes: 143, 142, 144, 143, 144 
## Resampling results across tuning parameters:
## 
##   mtry  RMSE     Rsquared   MAE    
##    2    5615815  0.7567667  2738425
##   15    5351718  0.7395533  2423744
##   28    5569078  0.6971769  2510609
## 
## RMSE was used to select the optimal model using the smallest value.
## The final value used for the model was mtry = 15.

The Features Importance

rf_model_imp <- varImp(rf_model, scale = TRUE)
p1 <- rf_model_imp$importance %>%
  as.data.frame() %>%
  rownames_to_column() %>%
  ggplot(aes(x = reorder(rowname, Overall), y = Overall)) +
    geom_bar(stat = "identity", fill = "red", alpha = 0.8) +
    coord_flip()

p1

Interpretação: É importante salientar que a variável que foi mais importante é Tempo de reação do jogador o que pode até fazer sentido já que numa partida decisiva, reagir mais rápido e de maneira eficaz pode ser muito valioso para o time e pode ter uma consequência positiva no salário do jogador

Salários preditos

test_predict_rf <- predict(rf_model, ourdata_test)
print(test_predict_rf)
##          3          4          9         11         15         17         27 
##  1141597.6  1020352.0   832637.0  2948855.6  1627349.6   918078.8  3390879.7 
##         31         32         33         34         38         49         61 
##  1079918.2  1730325.8  1644538.3  1383555.9  1670762.6  2902915.0  9161806.2 
##         66         70         72         73         89         95         96 
##   442020.7 10749789.6 16943161.8 15346139.7  2223928.0  2228586.4  2047435.8 
##         98        104        110        113        117        121        125 
##  4255261.1  2866743.4  3152151.7  5401559.3  3217054.1   115147.9 20525448.5 
##        127        136        139        143        148        159        163 
## 20182166.6  6489255.1  9099577.4 12867433.4 28734111.7  3489754.9  4204215.5 
##        168        171        172        175        177        187        194 
##   136322.9  4342810.8 14679415.4  5051358.8   285087.2   589829.9   349033.0 
##        198        217 
##   523596.1  1931167.7
#library(ModelMetrics)
rmse_randomforest = RMSE(test_predict_rf, ourdata_test$value)

DECISÃO FINAL

knitr::kable(data.frame(rmse_arvore_decision, rmse_randomforest))
rmse_arvore_decision rmse_randomforest
5025686 2944535

Fica óbvio que o modelo que teve uma boa performance em predizer os salários dos jogadores brasileiros do fifa 24 foi o modelo de random forest.

Conclusão

Em virtude de todas as análises feitas anteriormente podemos dizer que conseguimos estabelecer um certo padrão de comportamento nos jogadores do jogo fifa 24 e que um modelo de random forest com uma validação cruzada repetida, com 5 folds e uma repetição única pode ser melhor que uma árvore de decisão com a mesma configuração. A variável a mais importante encontrada com o algorithmo de random forest foi o Tempo de reação do jogador. </ center>


“Dado é informação e informação bem análisada é poder”
LS0tDQp0aXRsZTogIlNlbWluw6FyaW8yIC0gIE1hY2hpbmUgTGVhcm5pbmciDQphdXRob3I6ICIgS3DDqGTDqSBEamlkam9obyBSb2RuZWwgSmVhbi1QYXRlcm5lIERvc3NhIg0KZGF0ZTogIjE5IGRlIGZldmVyZWlybyBkZSAyMDI0Ig0Kb3V0cHV0OiANCiAgICBodG1sX2RvY3VtZW50Og0KICAgICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KICAgICAgY29kZV9mb2xkaW5nOiBzaG93DQogICAgICB0aGVtZTogZmxhdGx5DQotLS0NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpDQpgYGANCg0KDQoNCg0KIyB7LnRhYnNldCAudGFic2V0LWZhZGV9DQoNCg0KIyMgSW50cm9kdcOnw6NvIA0KDQoNCg0KPiA8c3BhbiBzdHlsZT0iY29sb3I6YmxhY2s7Zm9udC1zaXplOjIwcHg7Ij4NCk8gY29uanVudG8gZGUgZGFkb3MgZGUgam9nYWRvcmVzIGRlIGZ1dGVib2wgZG8gam9nbyBmaWZhIDI0IGRlc2Vudm9sdmlkbyBwZWxhIGZhbW9zYSBlbXByZXNhIDxzcGFuIHN0eWxlPSJjb2xvcjpibHVlO2ZvbnQtc2l6ZToyMHB4OyI+DQoqKkVBIFNQT1JUUyoqPC9zcGFuPiDDqSB1bWEgY29sZcOnw6NvIGFicmFuZ2VudGUgZGUgaW5mb3JtYcOnw7VlcyBzb2JyZSBqb2dhZG9yZXMgZGUgZnV0ZWJvbCBkZSB0b2RvIG8gbXVuZG8uIEVzdGUgY29uanVudG8gZGUgZGFkb3Mgb2ZlcmVjZSB1bWEgcmlxdWV6YSBkZSBhdHJpYnV0b3MgcmVsYWNpb25hZG9zIGEgY2FkYSBqb2dhZG9yLCB0b3JuYW5kby1vIHVtIHJlY3Vyc28gdmFsaW9zbyBwYXJhIGRpdmVyc2FzIGFuw6FsaXNlcyBlIGluc2lnaHRzIHNvYnJlIG8gbXVuZG8gZG8gZnV0ZWJvbCwgdGFudG8gcGFyYSBlbnR1c2lhc3RhcyBkZSBqb2dvcyBxdWFudG8gcGFyYSBlbnR1c2lhc3RhcyBkZSBlc3BvcnRlcyBkbyBtdW5kbyByZWFsLg0KPC9zcGFuPg0KDQoNCj4gPHNwYW4gc3R5bGU9ImNvbG9yOmJsdWU7Zm9udC1zaXplOjI1cHg7Ij4NCioqV2hhdCdzIHRoZSBwdXJwb3NlLyBRdWFsIG8gbm9zc28gb2JqZXRpdm8/Pz8qKjwvc3Bhbj4gPGJyPg0KDQo+IDxzcGFuIHN0eWxlPSJjb2xvcjpibGFjaztmb250LXNpemU6MjBweDsiPiBPIG9iamV0aXZvIMOpIGVuY29udHJhciBwYWRyw7VlcyBkZSBjb21wb3J0YW1lbnRvICBub3Mgam9nYWRvcmVzIGJyYXNpbGVpcm9zIGRvIGZpZmEgMjQgZGE8L3NwYW4+ICA8c3BhbiBzdHlsZT0iY29sb3I6Ymx1ZTtmb250LXNpemU6MjBweDsiPg0KKipFQSBTUE9SVFMqKjwvc3Bhbj4gPHNwYW4gc3R5bGU9ImNvbG9yOmJsYWNrO2ZvbnQtc2l6ZToyMHB4OyI+IGUgZW5jb250cmFyIG8gbWVsaG9yIG1vZGVsbyBxdWUgcG9zc2EgZmF6ZXIgdW1hIGJvYSBwcmVkacOnw6NvIGRvIHNhbMOhcmlvIGVtIGRvbMOhciBkZWxlcy4gPC9zcGFuPg0KDQo+IDxzcGFuIHN0eWxlPSJjb2xvcjpibHVlO2ZvbnQtc2l6ZToyNXB4OyI+DQoqKlF1YWlzIHZhcmnDoXZlaXMgZm9yYW0gZXNjb2xoaWRhcz8/PyoqIDwvc3Bhbj4gIDxicj4NCg0KPiA8c3BhbiBzdHlsZT0iY29sb3I6YmxhY2s7Zm9udC1zaXplOjIwcHg7Ij4NCk5vIGJhbmNvIG9yaWdpbmFsIHRpbmhhICoqNDEqKiB2YXJpw6F2ZWlzIGUgKio1NjgyKiogbGluaGFzIHF1ZSBjb3JyZXNwb25kZW0gYW9zIGRpZmVyZW50ZXMgam9nYWRvcmVzIG1hcyByZXNvbHZlbW9zIGZhemVyIGEgYW7DoWxpc2UgY29tIGFwZW5hcyAqKjIyMyoqICoqam9nYWRvcmVzIGJyYXNpbGVpcm9zKiogZG8gZmlmYSAyNCBlIGFzICoqMjkqKiB2YXJpw6F2ZWlzIHF1ZSBzZWd1ZW06IDwvc3Bhbj4NCg0KPiBIZWlnaHQ6IFRoZSBoZWlnaHQgb2YgdGhlIHBsYXllciBpbiBjZW50aW1ldGVycy48YnI+DQo+IFdlaWdodDogVGhlIHdlaWdodCBvZiB0aGUgcGxheWVyIGluIGtpbG9ncmFtcy48YnI+DQo+IEFnZTogVGhlIGFnZSBvZiB0aGUgcGxheWVyLjxicj4NCj4gQmFsbCBDb250cm9sOiBQbGF5ZXIncyBza2lsbCBpbiBjb250cm9sbGluZyB0aGUgYmFsbC48YnI+DQo+IERyaWJibGluZzogUGxheWVyJ3MgZHJpYmJsaW5nIGFiaWxpdHkuPGJyPg0KPiBTbGlkZSBUYWNrbGU6IFBsYXllcidzIGFiaWxpdHkgdG8gcGVyZm9ybSBzbGlkZSB0YWNrbGVzLjxicj4NCj4gU3RhbmQgVGFja2xlOiBQbGF5ZXIncyBhYmlsaXR5IHRvIHBlcmZvcm0gc3RhbmRpbmcgdGFja2xlcy48YnI+DQo+IEFnZ3Jlc3Npb246IFBsYXllcidzIGFnZ3Jlc3Npb24gbGV2ZWwuPGJyPg0KPiBSZWFjdGlvbnM6IFBsYXllcidzIHJlYWN0aW9uIHRpbWUuPGJyPg0KPiBBdHRhY2tpbmcgUG9zaXRpb246IFBsYXllcidzIHBvc2l0aW9uaW5nIGZvciBhdHRhY2tpbmcgcGxheXMuPGJyPg0KPiBJbnRlcmNlcHRpb25zOiBQbGF5ZXIncyBza2lsbCBpbiBpbnRlcmNlcHRpbmcgcGFzc2VzLjxicj4NCj4gVmlzaW9uOiBQbGF5ZXIncyB2aXNpb24gb24gdGhlIGZpZWxkLjxicj4NCj4gQ3Jvc3Npbmc6IFBsYXllcidzIGFiaWxpdHkgdG8gZGVsaXZlciBjcm9zc2VzLjxicj4NCj4gU2hvcnQgUGFzczogUGxheWVyJ3Mgc2hvcnQgcGFzc2luZyBhY2N1cmFjeS48YnI+DQo+IExvbmcgUGFzczogUGxheWVyJ3MgYWJpbGl0eSBpbiBsb25nIHBhc3NpbmcuPGJyPg0KPiBBY2NlbGVyYXRpb246IFBsYXllcidzIGFjY2VsZXJhdGlvbiBvbiB0aGUgZmllbGQuPGJyPg0KPiBTdGFtaW5hOiBQbGF5ZXIncyBzdGFtaW5hIGxldmVsLjxicj4NCj4gU3RyZW5ndGg6IFBsYXllcidzIHBoeXNpY2FsIHN0cmVuZ3RoLjxicj4NCj4gU3ByaW50IFNwZWVkOiBQbGF5ZXIncyBzcGVlZCBpbiBzcHJpbnRzLjxicj4NCj4gQWdpbGl0eTogUGxheWVyJ3MgYWdpbGl0eSBpbiBtYW5ldXZlcmluZy48YnI+DQo+IEp1bXBpbmc6IFBsYXllcidzIGp1bXBpbmcgYWJpbGl0eS48YnI+DQo+IEhlYWRpbmc6IFBsYXllcidzIGhlYWRpbmcgc2tpbGxzLjxicj4NCj4gU2hvdCBQb3dlcjogUGxheWVyJ3MgcG93ZXIgaW4gc2hvb3RpbmcuPGJyPg0KPiBGaW5pc2hpbmc6IFBsYXllcidzIGZpbmlzaGluZyBza2lsbHMuPGJyPg0KPiBMb25nIFNob3RzOiBQbGF5ZXIncyBhYmlsaXR5IHRvIG1ha2UgbG9uZy1yYW5nZSBzaG90cy48YnI+DQo+IEN1cnZlOiBQbGF5ZXIncyBhYmlsaXR5IHRvIGN1cnZlIHRoZSBiYWxsLjxicj4NCj4gUGVuYWx0aWVzOiBQbGF5ZXIncyBwZW5hbHR5LXRha2luZyBza2lsbHMuPGJyPg0KPiBWb2xsZXlzOiBQbGF5ZXIncyB2b2xsZXlpbmcgc2tpbGxzLjxicj4NCj4gVmFsdWU6IFRoZSBlc3RpbWF0ZWQgdmFsdWUgb2YgdGhlIHBsYXllciBpbiBkb2xsYXJzLjxicj4NCjwvc3Bhbj4NCg0KDQoNCiMjIEFuw6FsaXNlIEV4cGxvcmF0w7NyaWEgIA0KDQoNCj4gPHNwYW4gc3R5bGU9ImNvbG9yOmJsdWU7Zm9udC1zaXplOjI1cHg7Ij4NCioqUGFjb3RlcyB1dGlsaXphZG9zKiogPC9zcGFuPiANCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpwYWNtYW46OnBfbG9hZCgidGlkeXZlcnNlIiwgInJlYWR4bCIsICJnbG1uZXQiLCAicHN5Y2giLCAiY2FyZXQiLCAicmVhZHhsIiwgIkRUIiwgImtuaXRyIiwgIkdHYWxseSIsICJnZ3Bsb3QyIiwgImVtYmVkIiwgInVtYXAiLCAiZ2djb3JycGxvdDIiLCAic3FsZGYiLCAiZmFjdG9leHRyYSIsIkhtaXNjIikNCg0KYGBgDQoNCjxicj4NCg0KPiA8c3BhbiBzdHlsZT0iY29sb3I6Ymx1ZTtmb250LXNpemU6MjVweDsiPg0KKipPdmVydmlldyBkbyBiYW5jbyBkZSBkYWRvcyoqIDwvc3Bhbj4gDQoNCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQoNCg0Kb3VyZGF0YSA9IHJlYWRfZXhjZWwoImZpZmFfMjRfYnJhemlsLnhsc3giKQ0Kb3VyZGF0YSA9IGRhdGEuZnJhbWUob3VyZGF0YSkNCkRUOjpkYXRhdGFibGUob3VyZGF0YSkNCmBgYA0KDQo8YnI+DQoNCj4gPHNwYW4gc3R5bGU9ImNvbG9yOmJsdWU7Zm9udC1zaXplOjI1cHg7Ij4NCioqRXN0cnV0dXJhIGRvIGJhbmNvIGRlIGRhZG9zKiogPC9zcGFuPiANCg0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnN0cihvdXJkYXRhKQ0KYGBgDQoNCg0KDQo+IDxzcGFuIHN0eWxlPSJjb2xvcjpyZWQ7Zm9udC1zaXplOjI1cHg7Ij4gKipDb21lbnTDoXJpbyoqOjwvc3Bhbj48c3BhbiBzdHlsZT0iY29sb3I6YmxhY2s7Zm9udC1zaXplOjI1cHg7Ij4NCioqVG9kYXMgYXMgdmFyacOhdmVpcyBwcmVzZW50ZXMgbm8gYmFuY28gZGUgZGFkb3Mgc8OjbyBudW3DqXJpY2FzIHF1YW50aXRhdGl2YXMgY29udMOtbnVhcyoqIDwvc3Bhbj4gDQoNCjxicj4NCg0KPiA8c3BhbiBzdHlsZT0iY29sb3I6Ymx1ZTtmb250LXNpemU6MjVweDsiPiAqKkRldGVjw6fDo28gZGUgbWlzc2luZ3MgZGF0YSAoZGFkb3MgZmFsdGFudGVzKSA/Pz8qKiA8L3NwYW4+IA0KDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KbWlzc2luZ3NfdmVjdG9yID0gc2FwcGx5KG91cmRhdGEsIGZ1bmN0aW9uKHgpIHN1bShpcy5uYShvdXJkYXRhKSkpDQoNCm5hbWVzKG1pc3NpbmdzX3ZlY3RvcikgPSBuYW1lcyhvdXJkYXRhKQ0KDQptaXNzaW5nc192ZWN0b3IgfD4gcHJpbnQoKQ0KYGBgDQoNCg0KDQo+IDxzcGFuIHN0eWxlPSJjb2xvcjpyZWQ7Zm9udC1zaXplOjI1cHg7Ij4gKipDb21lbnTDoXJpbyoqOjwvc3Bhbj48c3BhbiBzdHlsZT0iY29sb3I6YmxhY2s7Zm9udC1zaXplOjI1cHg7Ij4NCioqTsOjbyB0ZW0gcHJlc2Vuw6dhIGRlIG5lbmh1bSBkYWRvIGZhbHRhbnRlIG5vIGJhbmNvIG9idGlkby4qKiA8L3NwYW4+IA0KDQoNCjxicj4NCg0KPiA8c3BhbiBzdHlsZT0iY29sb3I6Ymx1ZTtmb250LXNpemU6MjVweDsiPiAqKkRlc2NyaXRpdmFzIGRvIGJhbmNvIGRlIGRhZG9zKiogPC9zcGFuPiANCg0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnN0YW5kYXJkX2Rlc3ZpYXRpb24gPSBudW1lcmljKDApDQp2YXJpYW5jZSA9IG51bWVyaWMoMCkNCm1pbl92YXIgPSBudW1lcmljKDApDQptYXhfdmFyID0gbnVtZXJpYygwKQ0KbWVkaWFuYV92YXIgPSBudW1lcmljKDApDQptZWRpYV92YXIgPSBudW1lcmljKDApDQoNCmZvciAoaSBpbiAoMToyOSkpew0KICANCiAgc3RhbmRhcmRfZGVzdmlhdGlvbiA9IGFwcGVuZChzdGFuZGFyZF9kZXN2aWF0aW9uLCBzZChvdXJkYXRhWyxpXSkpDQogIHZhcmlhbmNlID0gYXBwZW5kKHZhcmlhbmNlLCB2YXIob3VyZGF0YVssaV0pKQ0KICBtaW5fdmFyID0gYXBwZW5kKG1pbl92YXIsIG1pbihvdXJkYXRhWyxpXSkpDQogIG1heF92YXIgPSBhcHBlbmQobWF4X3ZhciwgbWF4KG91cmRhdGFbLGldKSkNCiAgbWVkaWFuYV92YXIgPSBhcHBlbmQobWVkaWFuYV92YXIsIG1lZGlhbihvdXJkYXRhWyxpXSkpDQogIG1lZGlhX3ZhciA9IGFwcGVuZChtZWRpYV92YXIsIG1lYW4ob3VyZGF0YVssaV0pKX0NCg0KDQoNCkRlc3Zpb19wYWRyYW8gPSBzdGFuZGFyZF9kZXN2aWF0aW9uDQpWYXJpYW5jaWEgPSB2YXJpYW5jZQ0KTWluaW1vID0gbWluX3Zhcg0KTWF4aW1vID0gbWF4X3Zhcg0KTWVkaWFuYSA9IG1lZGlhbmFfdmFyDQpNZWRpYSA9IG1lZGlhX3Zhcg0KDQpiYW5jb19zdW1tYXJ5ID0gcmJpbmQoRGVzdmlvX3BhZHJhbywgVmFyaWFuY2lhLCBNaW5pbW8sIE1heGltbywgTWVkaWFuYSwgTWVkaWEpDQoNCnJvd25hbWVzKGJhbmNvX3N1bW1hcnkpID0gYygiRGVzdmlvX1BhZHLDo28iLCAiVmFyacOibmNpYSIsICJNw61uaW1vIiwgIk3DoXhpbW8iLCAiTWVkaWFuYSIsICJNw6lkaWEiKQ0KY29sbmFtZXMoYmFuY29fc3VtbWFyeSkgPSBuYW1lcyhvdXJkYXRhKQ0KDQoNCg0Ka25pdHI6OmthYmxlKGJhbmNvX3N1bW1hcnkpDQpgYGANCg0KPGJyPg0KDQo+IDxzcGFuIHN0eWxlPSJjb2xvcjpibHVlO2ZvbnQtc2l6ZToyNXB4OyI+ICoqSGlzdG9ncmFtYXMqKiA8L3NwYW4+IA0KDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KDQpmb3IgKGkgaW4gKDE6MjkpKXsNCg0KICBoaXN0KG91cmRhdGFbLCBpXSwgY29sID0gImJsdWUiLCB4bGFiID0gbmFtZXMob3VyZGF0YSlbaV0sICBtYWluID0gcGFzdGUwKCJIaXN0b2dyYW1hIGRhIHZhcmnDoXZlbCIsICIgIiwgIG5hbWVzKG91cmRhdGEpW2ldKSkNCg0KfQ0KDQoNCmBgYA0KDQoNCj4gPHNwYW4gc3R5bGU9ImNvbG9yOnJlZDtmb250LXNpemU6MjVweDsiPiAqKkNvbWVudMOhcmlvKio6PC9zcGFuPjxzcGFuIHN0eWxlPSJjb2xvcjpibGFjaztmb250LXNpemU6MjVweDsiPg0KKipQYXJlY2UgcXVlIG5lbmh1bWEgdmFyacOhdmVsIGVzdMOhIHNlZ3VpbmRvIHVtYSBkaXN0cmlidWnDp8OjbyBub3JtYWwuIEZvcmEgYXMgdmFyacOhdmVpcyoqIDxzcGFuIHN0eWxlPSJjb2xvcjpibHVlO2ZvbnQtc2l6ZToyNXB4OyI+KipBbHR1cmEqKjwvc3Bhbj4gKiplKiogPHNwYW4gc3R5bGU9ImNvbG9yOmJsdWU7Zm9udC1zaXplOjI1cHg7Ij4qKlRlbXBvIGRlIHJlYcOnw6NvIGRvIGpvZ2Fkb3IqKjwvc3Bhbj4gKipxdWUgYXByZXNlbnRhcmFtIHVtYSBkaXN0cmlidWnDp8OjbyB1bSBwb3VjbyBtZW5vcyBhc3NpbcOpdHJpY2EsIGFzIG91dHJhcyB2YXJpw6F2ZWlzLCBhcHJlc2VudGFyYW0gdW1hIGRpc3RyaWJ1acOnw6NvIGZvcnRlbWVudGUgYXNzaW3DqXRyaWNhLioqIDwvc3Bhbj4gDQoNCg0KPGJyPg0KDQoNCg0KPiA8c3BhbiBzdHlsZT0iY29sb3I6Ymx1ZTtmb250LXNpemU6MjVweDsiPiAqKkJveHBsb3RzKiogPC9zcGFuPiANCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQoNCmJveHBsb3Qob3VyZGF0YSwgY29sID0gImJsdWUiLCBtYWluID0gIkJveHBsb3QgZGUgdG9kYXMgYXMgdmFyacOhdmVpcyBkbyBiYW5jbyIgKQ0KYGBgDQoNCg0KDQo+IDxzcGFuIHN0eWxlPSJjb2xvcjpibHVlO2ZvbnQtc2l6ZToyNXB4OyI+ICoqQm94cGxvdHMgLSBaT09NMSoqIDwvc3Bhbj4gDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KDQpib3hwbG90KG91cmRhdGEsIGNvbCA9ICJibHVlIiwgbWFpbiA9ICJCb3hwbG90IGRlIHRvZGFzIGFzIHZhcmnDoXZlaXMgZG8gYmFuY28gLSAgWk9PTTEiICwgeWxpbSA9IGMoMCwgMjAwKSkNCmBgYA0KDQoNCj4gPHNwYW4gc3R5bGU9ImNvbG9yOmJsdWU7Zm9udC1zaXplOjI1cHg7Ij4gKipCb3hwbG90cyAtIFpPT00yKiogPC9zcGFuPiANCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQoNCmJveHBsb3Qob3VyZGF0YSwgY29sID0gImJsdWUiLCBtYWluID0gIkJveHBsb3QgZGUgdG9kYXMgYXMgdmFyacOhdmVpcyBkbyBiYW5jbyAtIFpPT00yIiAsIHlsaW0gPSBjKDAsIDkwKSkNCmBgYA0KDQo8YnI+DQoNCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpnZ3BhaXJzKG91cmRhdGEsIGFlcyhhbHBoYT0wLjgpLCBsb3dlcj1saXN0KGNvbnRpbnVvdXM9InBvaW50cyIpLA0KICAgICAgICB1cHBlcj1saXN0KGNvbnRpbnVvdXM9ImJsYW5rIiksDQogICAgICAgIGF4aXNMYWJlbHM9Im5vbmUiLCBzd2l0Y2g9ImJvdGgiKQ0KYGBgDQoNCg0KDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KZ2djb3JycGxvdDo6Z2djb3JycGxvdChjb3Iob3VyZGF0YSksDQogICAgICAgICAgICAgICAgICAgICAgIGhjLm9yZGVyID0gVFJVRSwNCiAgICAgICAgICAgICAgICAgICAgICAgdHlwZSA9ICJsb3dlciIsDQogICAgICAgICAgICAgICAgICAgICAgIGxhYiA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgICAgIGxhYl9zaXplID0gMS41KSANCmBgYA0KDQoNCj4gPHNwYW4gc3R5bGU9ImNvbG9yOnJlZDtmb250LXNpemU6MjVweDsiPiAqKkNvbWVudMOhcmlvKio6PC9zcGFuPjxzcGFuIHN0eWxlPSJjb2xvcjpibGFjaztmb250LXNpemU6MjVweDsiPioqUG9kZW1vcyByZXBhcmFyIHF1ZSBvYnRpdmVtb3MgZm9ydGVzIGNvcnJlbGHDp8O1ZXMgbGluZWFyZXMgIGVudHJlIGFsZ3VtYXMgdmFyacOhdmVpcyBjb21vIHBvciBleG1wbG8gYXMgdmFyacOhdmVpcyoqPC9zcGFuPiA8c3BhbiBzdHlsZT0iY29sb3I6Ymx1ZTtmb250LXNpemU6MjVweDsiPioqSGFiaWxpZGFkZSBkbyBqb2dhZG9yIHBhcmEgZHJpYmxhcioqPC9zcGFuPiA8c3BhbiBzdHlsZT0iY29sb3I6YmxhY2s7Zm9udC1zaXplOjI1cHg7Ij4qKmUqKjwvc3Bhbj4gPHNwYW4gc3R5bGU9ImNvbG9yOmJsdWU7Zm9udC1zaXplOjI1cHg7Ij4qKkhhYmlsaWRhZGUgZG8gam9nYWRvciBhIGNvbnRyb2xhciBhIGJvbGEqKjwvc3Bhbj4gPHNwYW4gc3R5bGU9ImNvbG9yOmJsdWU7Zm9udC1zaXplOjI1cHg7Ij4qKigwLDkzKSoqLjwvc3Bhbj4gPHNwYW4gc3R5bGU9ImNvbG9yOmJsYWNrO2ZvbnQtc2l6ZToyNXB4OyI+KipQb3LDqW0sIHRlbSB2w6FyaWFzIHZhcmnDoXZlaXMgcXVlIHBhcmVjZW0gbsOjbyB0ZXIgdW1hIHJlbGHDp8OjbyBsaW5lYXIgY29tbyBwb3IgZXhlbXBsbyBhcyB2YXJpw6F2ZWlzKio8L3NwYW4+ICA8c3BhbiBzdHlsZT0iY29sb3I6Ymx1ZTtmb250LXNpemU6MjVweDsiPioqQWdpbGlkYWRlIGRvIGpvZ2Fkb3IgbmFzIG1hbm9icmFzKio8L3NwYW4+IDxzcGFuIHN0eWxlPSJjb2xvcjpibGFjaztmb250LXNpemU6MjVweDsiPiAqKmUgbyoqPC9zcGFuPiA8c3BhbiBzdHlsZT0iY29sb3I6Ymx1ZTtmb250LXNpemU6MjVweDsiPioqTsOtdmVsIGRlIGFncmVzc8OjbyBkbyBqb2dhZG9yKio8L3NwYW4+IDxzcGFuIHN0eWxlPSJjb2xvcjpibHVlO2ZvbnQtc2l6ZToyNXB4OyI+KiooMCkqKi48L3NwYW4+DQoNCg0KIyMgUmVkdcOnw6NvIGRlIERpbWVuc2lvbmFsaWRhZGUNCg0KDQo+IDxzcGFuIHN0eWxlPSJjb2xvcjpibHVlO2ZvbnQtc2l6ZToyNXB4OyI+IEFudGVzIGRlIHRlciBhcGxpY2FkbyBvIG3DqXRvZG8gZGUgcmVkdcOnw6NvIGRlIGRpbWVuc2lvbmFsaWRhZGUgVU1BUCwgYXMgdmFyacOhdmVpcyB0w6ptIHNpZG8gcGFkcm9uaXphZGFzIDwvc3Bhbj4gDQoNCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQojIFNlbWVudGUgZGVmaW5pZGEgDQpzZXQuc2VlZCgwMDMxNjY5NSkNCnVtYXBfZml0IDwtIG91cmRhdGEgJT4lDQogIHNlbGVjdCh3aGVyZShpcy5udW1lcmljKSkgJT4lDQogIHNjYWxlKCkgJT4lIA0KICB1bWFwKCkNCg0KDQoNCnVtYXBfZGYgPC0gdW1hcF9maXQkbGF5b3V0ICU+JQ0KICBhcy5kYXRhLmZyYW1lKCkgJT4lIA0KICByb3VuZCg1KQ0KIA0KDQoNCkRUOjpkYXRhdGFibGUodW1hcF9kZikNCmBgYA0KDQoNCg0KPiA8c3BhbiBzdHlsZT0iY29sb3I6cmVkO2ZvbnQtc2l6ZToyNXB4OyI+ICoqSW50ZXJwcmV0YcOnw6NvKio6PC9zcGFuPjxzcGFuIHN0eWxlPSJjb2xvcjpibGFjaztmb250LXNpemU6MjVweDsiPg0KKipJbmljaWFsbWVudGUgdGluaGFtb3MgMjkgdmFyacOhdmVpcyBtYXMgbyBtw6l0b2RvIFVNQVAgcmV0b3Jub3UgZHVhcyB2YXJpw6F2ZWlzIHF1ZSBzw6NvIGNvbWJpbmHDp8O1ZXMgZGFzIHZhcmnDoXZlaXMgb3JpZ2luYWlzLiDDiSBpbXBvcnRhbnRlIHNhbGllbnRhciBxdWUgYXMgdmFyacOhdmVpcyBmb3JhbSBwYWRyb25pemFkYXMgYW50ZXMgZGUgYXBsaWNhciBvIG3DqXRvZG8gVU1BUC4qKjwvc3Bhbj4gDQoNCg0KIyMgQ2x1c3Rlcml6YcOnw6NvDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KZnZpel9uYmNsdXN0KHVtYXBfZGYsIGttZWFucyxtZXRob2QgPSAic2lsaG91ZXR0ZSIsIGxpbmVjb2xvciA9ICJibHVlIikNCmBgYA0KDQoNCj4gPHNwYW4gc3R5bGU9ImNvbG9yOnJlZDtmb250LXNpemU6MjVweDsiPiAqKkludGVycHJldGHDp8OjbyoqOjwvc3Bhbj48c3BhbiBzdHlsZT0iY29sb3I6YmxhY2s7Zm9udC1zaXplOjI1cHg7Ij4NCioqT2xoYW5kbyBvIGdyw6FmaWNvIGFjaW1hIHBhcmVjZSBxdWUgc2VyaWEgbWVsaG9yIGFncnVwYXIgb3MgZGFkb3MgZW0gZXhhdGFtZW50ZSAzICh0csOqcykgY2x1c3RlcnMgKGdydXBvcykuIEEgbG9jYWxpemHDp8OjbyBkYSBjdXJ2YSAoam9lbGhvL2NvdG92ZWxvKSBuYSBwbG90YWdlbSDDqSBnZXJhbG1lbnRlIGNvbnNpZGVyYWRhIGNvbW8gdW0gaW5kaWNhZG9yIGRvIG7Dum1lcm8gYXByb3ByaWFkbyBkZSBhZ3J1cGFtZW50b3MuIEEgZnVuw6fDo28qKjwvc3Bhbj4gPHNwYW4gc3R5bGU9ImNvbG9yOnJlZDtmb250LXNpemU6MjVweDsiPioqUiBmdml6X25iY2x1c3QoKSoqPC9zcGFuPiA8c3BhbiBzdHlsZT0iY29sb3I6YmxhY2s7Zm9udC1zaXplOjI1cHg7Ij4qKnBhcmVjZSBmb3JuZWNlciB1bWEgc29sdcOnw6NvIGNvbnZlbmllbnRlIHBhcmEgZXN0aW1hciBvIG7Dum1lcm8gaWRlYWwgZGUgY2x1c3RlcnMuKio8L3NwYW4+DQoNCg0KPGJyPg0KDQo+IDxzcGFuIHN0eWxlPSJjb2xvcjpibHVlO2ZvbnQtc2l6ZToyNXB4OyI+ICoqVmlzdWFsaXphw6fDo28gZG9zIGRhZG9zIGNsdXN0ZXJpemFkb3M6Kio6PC9zcGFuPg0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnNldC5zZWVkKDAwMzE2Njk1KQ0KDQpkYXRhS21lYW5zIDwtIGttZWFucyh1bWFwX2RmLDMpDQoNCmNsdXN0ZXJpemVkIDwtIGZ2aXpfY2x1c3RlcihkYXRhS21lYW5zLA0KIGRhdGEgPSB1bWFwX2RmLCANCiBnZW9tID0gInBvaW50IiwNCiBzdGFuZCA9IEZBTFNFLA0KIHRpdGxlID0gImtNRUFOUyDigJQgQ0xVU1RFUklORyIsDQogZnJhbWUudHlwZSA9ICJjb252ZXgiKQ0KDQpjbHVzdGVyaXplZA0KDQoNCm15X2NsdXN0ZXJzPSAoZGF0YS5mcmFtZShDbHVzdGVycyA9IDE6MywgZGF0YUttZWFucyRjZW50ZXJzKSkgIA0KbmFtZXMobXlfY2x1c3RlcnMpID0gYygiQ2x1c3RlcnMiLCAiVmFyacOhdmVsMSIsICJWYXJpw6F2ZWwyIikNCmtuaXRyOjprYWJsZSgobXlfY2x1c3RlcnMpICU+JSBkcGx5cjo6YXJyYW5nZShteV9jbHVzdGVyc1syXSkpDQpgYGANCg0KDQo+IDxzcGFuIHN0eWxlPSJjb2xvcjpyZWQ7Zm9udC1zaXplOjI1cHg7Ij4gKipJbnRlcnByZXRhw6fDo28gYSBwYXJ0aXIgZG9zIGNlbnRyw7NpZGVzIGRvcyBjbHVzdGVyczoqKjwvc3Bhbj4gPGJyPiANCg0KPiA8c3BhbiBzdHlsZT0iY29sb3I6YmxhY2s7Zm9udC1zaXplOjI1cHg7Ij4qKk8gZ3LDoWZpY28gbW9zdHJhIHF1ZSBvcyBncnVwb3MgMSBlIDIgc8OjbyBiZW0gcHLDs3hpbW9zIGUgb3MgZG9pcyBqdW50b3Mgc8OjbyBiZW0gZGlzdGFudGVzIGRvIGdydXBvIDMuIFBvciBvdXRybyBsYWRvLCBwYXJlY2UgcXVlIG8gZ3J1cG8gMiBjb250w6ltIG1haXMgam9nYWRvcmVzIGJyYXNpbGVpcm9zIGRvIGZpZmEyNCBkbyBxdWUgb3MgZG9pcyBvdXRyb3MgZ3J1cG9zLiBPbGhhbmRvIGFzIGNhcmFjdGVyw61zdGljYXMgZG9zIGNlbnRyw7NpZGVzIGRvcyAzICh0csOqcykgY2x1c3RlcnMgKGdydXBvcykgYWNpbWEgcG9kZW1vcyBkaXplciBxdWUgZW0gbcOpZGlhIG8gZ3J1cG8gMSBwb3NzdWkgY2FyYWN0ZXLDrXN0aWNhcyBhY2ltYSBkYSBtw6lkaWEgY29uc2lkZXJhbmRvIGFwZW5hcyBhIHZhcmnDoXZlbDEgZSBvIGdydXBvIDIgw6kgYXF1ZWxlIHF1ZSBhcHJlc2VudG91IGNhcmFjdGVyw61zdGljYXMgYWNpbWEgZGEgbcOpZGlhIGNvbnNpZGVyYW5kbyBhcGVuYXMgYSB2YXJpw6F2ZWwyLg0KSW5mZWxpem1lbnRlIGEgZGltZW5zw6NvIGRvIGJhbmNvIG9yaWdpbmFsIGZvaSBiZW0gcmVkdXppZGEsIG1hcyBzZXJpYSBtYWlzIGludGVyZXNzYW50ZSBvbGhhciBhIGZvcm1hw6fDo28gZG9zIGNsdXN0ZXJzIGVtIGZ1bsOnw6NvIGRlIHRvZGFzIGFzIHZhcmnDoXZlaXMgw61uaWNpYWlzKio8L3NwYW4+DQoNCg0KDQoNCj4gPHNwYW4gc3R5bGU9ImNvbG9yOmJsdWU7Zm9udC1zaXplOjI1cHg7Ij4gKipFIHNlIG7Do28gdGl2w6lzc2Vtb3MgcmVkdXppZG8gYSBkaW1lbnPDo28gZG8gYmFuY28gZGUgZGFkb3MsIG8gcXVlIGFjb250ZWNlcmlhIGNvbSBvcyBjbHVzdGVycz8/PyoqPC9zcGFuPg0KDQoNCj4gPHNwYW4gc3R5bGU9ImNvbG9yOnJlZDtmb250LXNpemU6MjVweDsiPiAqKlZhbW9zIG9saGFyIGp1bnRvcyBvIHF1ZSBhY29udGVjZXIuLi4uLioqPC9zcGFuPg0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCg0KIyBTZW1lbnRlIGRlZmluaWRhIA0Kc2V0LnNlZWQoMDAzMTY2OTUpDQpmdml6X25iY2x1c3Qob3VyZGF0YSwga21lYW5zLG1ldGhvZCA9ICJzaWxob3VldHRlIiwgbGluZWNvbG9yID0gInJlZCIpDQpgYGANCg0KDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0Kc2V0LnNlZWQoMDAzMTY2OTUpDQoNCmRhdGFLbWVhbnNfZW50aXJlIDwtIGttZWFucyhvdXJkYXRhLDMpDQoNCmNsdXN0ZXJpemVkX2VudGlyZSA8LSBmdml6X2NsdXN0ZXIoZGF0YUttZWFuc19lbnRpcmUsDQogZGF0YSA9IG91cmRhdGEsIA0KIGdlb20gPSAicG9pbnQiLA0KIHN0YW5kID0gRkFMU0UsDQogdGl0bGUgPSAia01FQU5TIOKAlCBDTFVTVEVSSU5HIiwNCiBmcmFtZS50eXBlID0gImNvbnZleCIpDQoNCmNsdXN0ZXJpemVkX2VudGlyZQ0KYGBgDQoNCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpteV9jbHVzdGVyc19lbnRpcmU9IChkYXRhLmZyYW1lKENsdXN0ZXJzID0gMTozLCBkYXRhS21lYW5zX2VudGlyZSRjZW50ZXJzKSkgIA0KDQprbml0cjo6a2FibGUobXlfY2x1c3RlcnNfZW50aXJlKSANCmBgYA0KDQoNCj4gPHNwYW4gc3R5bGU9ImNvbG9yOnJlZDtmb250LXNpemU6MjVweDsiPiAqKkludGVycHJldGHDp8OjbyoqOjwvc3Bhbj48c3BhbiBzdHlsZT0iY29sb3I6YmxhY2s7Zm9udC1zaXplOjI1cHg7Ij4NCioqVGVtb3MgYWdvcmEgdG9kYXMgYXMgY2FyYWN0ZXLDrXN0aWNhcyBpbXBvcnRhbnRlcyBkb3MgMyBjbHVzdGVycy4gQ29tIGJhc2UgbmVzc2FzIGluZm9ybWHDp8O1ZXMgcG9kZSBzZSB0b21hciBkZWNpc8O1ZXMgb3UgZGVzZW52b2x2ZXIgYWxndW0gc2lzdGVtYSBkZSByZWNvbWVuZGHDp8OjbyBubyBqb2dvLiBQb3IgZXhlbXBsbywgbm92b3MgY29udHJhdG9zIHBvZGVtIHNlciBvZmVyZWNpZG9zIHBhcmEgYWxndW5zIGpvZ2Fkb3JlcyBkbyBjbHVzdGVyIDEgcXVlIGFwYXJlbnRlbWVudGUgcG9zc3VlbSB1bWEgYm9hIGZpbmFsaXphw6fDo28gZSB1bWEgYm9hIHZpc8OjbyBkbyBqb2dvLCBoYWJpbGlkYWRlcyBxdWUgcG9kZW0gc2VyIG11aXRvIHZhbGlvc2FzIG51bWEgcGFydGlkYSBkZSBmb290YmFsbC4qKjwvc3Bhbj4NCg0KDQoNCiMjIEZvcmVjYXN0aW5nDQoNCj4gPHNwYW4gc3R5bGU9ImNvbG9yOmJsdWU7Zm9udC1zaXplOjI1cHg7Ij4NCioqUGFjb3RlcyB1dGlsaXphZG9zKiogPC9zcGFuPiANCg0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCg0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KGNhcmV0KQ0KcmVxdWlyZShXVlBsb3RzKQ0KbGlicmFyeShncmlkRXh0cmEpDQpsaWJyYXJ5KGdyaWQpDQpsaWJyYXJ5KGdncmlkZ2VzKQ0KbGlicmFyeShnZ3RoZW1lcykNCnRoZW1lX3NldCh0aGVtZV9taW5pbWFsKCkpDQoNCmxpYnJhcnkoaW1sKQ0KDQoNCmxpYnJhcnkoYnJlYWtEb3duKQ0KDQpsaWJyYXJ5KHJwYXJ0KSAjYXJ2b3JlIGRlIGRlY2lzYW8NCmxpYnJhcnkocnBhcnQucGxvdCkgIyBhcnZvcmUgZGUgZGVjaXNhbw0KDQpsaWJyYXJ5KHBsb3RST0MpICMgcGxvdGFyIGEgY3VydmEgcm9jDQpsaWJyYXJ5KHBST0MpDQpgYGANCg0KDQo+IDxzcGFuIHN0eWxlPSJjb2xvcjpibHVlO2ZvbnQtc2l6ZToyNXB4OyI+DQoqKkRpdmlzw6NvIGRvcyBkYWRvcyBkbyBiYW5jbyBlbSAkODAkJSBlICQyMCQlKiogPC9zcGFuPiANCg0KDQpgYGB7cn0NCnNldC5zZWVkKDAwMzE2Njk1KQ0KaWR4IDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24ob3VyZGF0YSR2YWx1ZSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICBwID0gMC44LCAjIyBwcm9wb3JjYW8gZGUgZGFkb3MgZG8gYmFuY28gZGUgdHJlaW5hbWVudG8NCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGxpc3QgPSBGQUxTRSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICB0aW1lcyA9IDEpDQoNCm91cmRhdGFfdHJhaW4gPC0gb3VyZGF0YVsgaWR4LF0NCm91cmRhdGFfdGVzdCAgPC0gb3VyZGF0YVstaWR4LF0NCmBgYA0KDQo+IDxzcGFuIHN0eWxlPSJjb2xvcjpibHVlO2ZvbnQtc2l6ZToyNXB4OyI+DQoqKsOBcnZvcmVzIGRlIERlY2lzw6NvKiogPC9zcGFuPiANCg0KDQpgYGB7ciwgd2FybmluZz0gRkFMU0V9DQoNCmZpdF9jb250cm9sIDwtIHRyYWluQ29udHJvbChtZXRob2QgPSAicmVwZWF0ZWRjdiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICBudW1iZXIgPSA1LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVwZWF0cyA9IDEpDQoNCnNldC5zZWVkKDAwMzE2Njk1KQ0KYXJ2b3JlX21vZGVsIDwtIHRyYWluKHZhbHVlIH4gLiwgDQogICAgICAgICAgICAgICAgICBkYXRhID0gb3VyZGF0YV90cmFpbiwgDQogICAgICAgICAgICAgICAgICBtZXRob2QgPSAicnBhcnQiLCAjIMOhcnZvcmUgZGUgZGVjaXNhbyANCiAgICAgICAgICAgICAgICAgIHByZVByb2Nlc3MgPSBjKCJzY2FsZSIsICJjZW50ZXIiKSwNCiAgICAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IGZpdF9jb250cm9sKQ0KYXJ2b3JlX21vZGVsDQoNCg0KcGxvdChhcnZvcmVfbW9kZWwpDQoNCnJwYXJ0LnBsb3QoYXJ2b3JlX21vZGVsJGZpbmFsTW9kZWwpDQpgYGANCg0KDQo+IDxzcGFuIHN0eWxlPSJjb2xvcjpibHVlO2ZvbnQtc2l6ZToyNXB4OyI+DQoqKlNhbMOhcmlvcyBwcmVkaXRvcyoqIDwvc3Bhbj4gDQoNCg0KYGBge3IsIHdhcm5pbmc9IEZBTFNFfQ0KDQp0ZXN0X3ByZWRpY3QgPC0gcHJlZGljdChhcnZvcmVfbW9kZWwsIG91cmRhdGFfdGVzdCkNCg0KcHJpbnQodGVzdF9wcmVkaWN0KQ0KYGBgDQoNCg0KDQpgYGB7ciwgd2FybmluZz0gRkFMU0V9DQojbGlicmFyeShNb2RlbE1ldHJpY3MpDQpybXNlX2Fydm9yZV9kZWNpc2lvbiA9IFJNU0UodGVzdF9wcmVkaWN0LCBvdXJkYXRhX3Rlc3QkdmFsdWUpDQpgYGANCg0KPiA8c3BhbiBzdHlsZT0iY29sb3I6cmVkO2ZvbnQtc2l6ZToyNXB4OyI+ICoqUmVzdWx0Kio6PC9zcGFuPiA8c3BhbiBzdHlsZT0iY29sb3I6YmxhY2s7Zm9udC1zaXplOjI1cHg7Ij4gT2J0aXZlbW9zIHVtICoqUk1TRSoqIGRlICQ1MDI1Njg2JCA8L3NwYW4+DQoNCjxicj4NCg0KPiA8c3BhbiBzdHlsZT0iY29sb3I6cmVkO2ZvbnQtc2l6ZTozMHB4OyI+ICoqU2Vyw6EgcXVlIHBvZGVtb3MgZW5jb250cmFyIHVtIG1vZGVsbyBjb20gdW1hIG1lbGhvciBwZXJmb3JtYW5jZT8/PyoqPC9zcGFuPg0KDQoNCg0KPiA8c3BhbiBzdHlsZT0iY29sb3I6Ymx1ZTtmb250LXNpemU6MjVweDsiPg0KKipSYW5kb20gRm9yZXN0KiogPC9zcGFuPiANCg0KDQpgYGB7cn0NCm1vZGVsTG9va3VwKCJyZiIpDQoNCmZpdF9jb250cm9sX3JmIDwtIHRyYWluQ29udHJvbChtZXRob2QgPSAicmVwZWF0ZWRjdiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICBudW1iZXIgPSA1LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVwZWF0cyA9IDEpDQoNCnNldC5zZWVkKDAwMzE2Njk1KQ0KcmZfbW9kZWwgPC0gdHJhaW4odmFsdWUgfiAuLCANCiAgICAgICAgICAgICAgICAgIGRhdGEgPSBvdXJkYXRhX3RyYWluLCANCiAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJyZiIsIA0KICAgICAgICAgICAgICAgICAgcHJlUHJvY2VzcyA9IGMoInNjYWxlIiwgImNlbnRlciIpLA0KICAgICAgICAgICAgICAgICAgdHJDb250cm9sID0gZml0X2NvbnRyb2xfcmYsDQogICAgICAgICAgICAgICAgICB2ZXJib3NlID0gRkFMU0UpDQpyZl9tb2RlbA0KYGBgDQoNCg0KDQo+IDxzcGFuIHN0eWxlPSJjb2xvcjpibHVlO2ZvbnQtc2l6ZToyNXB4OyI+DQoqKlRoZSBGZWF0dXJlcyBJbXBvcnRhbmNlKiogPC9zcGFuPiANCg0KYGBge3J9DQpyZl9tb2RlbF9pbXAgPC0gdmFySW1wKHJmX21vZGVsLCBzY2FsZSA9IFRSVUUpDQpwMSA8LSByZl9tb2RlbF9pbXAkaW1wb3J0YW5jZSAlPiUNCiAgYXMuZGF0YS5mcmFtZSgpICU+JQ0KICByb3duYW1lc190b19jb2x1bW4oKSAlPiUNCiAgZ2dwbG90KGFlcyh4ID0gcmVvcmRlcihyb3duYW1lLCBPdmVyYWxsKSwgeSA9IE92ZXJhbGwpKSArDQogICAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIGZpbGwgPSAicmVkIiwgYWxwaGEgPSAwLjgpICsNCiAgICBjb29yZF9mbGlwKCkNCg0KcDENCmBgYA0KDQoNCj4gPHNwYW4gc3R5bGU9ImNvbG9yOnJlZDtmb250LXNpemU6MjVweDsiPiAqKkludGVycHJldGHDp8OjbyoqOjwvc3Bhbj48c3BhbiBzdHlsZT0iY29sb3I6YmxhY2s7Zm9udC1zaXplOjI1cHg7Ij4NCioqw4kgaW1wb3J0YW50ZSBzYWxpZW50YXIgcXVlIGEgdmFyacOhdmVsIHF1ZSBmb2kgbWFpcyBpbXBvcnRhbnRlIMOpIFRlbXBvIGRlIHJlYcOnw6NvIGRvIGpvZ2Fkb3IgbyBxdWUgcG9kZSBhdMOpIGZhemVyIHNlbnRpZG8gasOhIHF1ZSBudW1hIHBhcnRpZGEgZGVjaXNpdmEsIHJlYWdpciBtYWlzIHLDoXBpZG8gZSBkZSBtYW5laXJhIGVmaWNheiBwb2RlIHNlciBtdWl0byB2YWxpb3NvIHBhcmEgbyB0aW1lIGUgcG9kZSB0ZXIgdW1hIGNvbnNlcXXDqm5jaWEgcG9zaXRpdmEgbm8gc2Fsw6FyaW8gZG8gam9nYWRvcioqPC9zcGFuPg0KDQoNCj4gPHNwYW4gc3R5bGU9ImNvbG9yOmJsdWU7Zm9udC1zaXplOjI1cHg7Ij4NCioqU2Fsw6FyaW9zIHByZWRpdG9zKiogPC9zcGFuPiANCg0KDQpgYGB7ciwgd2FybmluZz0gRkFMU0V9DQoNCnRlc3RfcHJlZGljdF9yZiA8LSBwcmVkaWN0KHJmX21vZGVsLCBvdXJkYXRhX3Rlc3QpDQpwcmludCh0ZXN0X3ByZWRpY3RfcmYpDQpgYGANCg0KDQpgYGB7ciwgd2FybmluZz0gRkFMU0V9DQojbGlicmFyeShNb2RlbE1ldHJpY3MpDQpybXNlX3JhbmRvbWZvcmVzdCA9IFJNU0UodGVzdF9wcmVkaWN0X3JmLCBvdXJkYXRhX3Rlc3QkdmFsdWUpDQpgYGANCg0KDQo+IDxzcGFuIHN0eWxlPSJjb2xvcjpibHVlO2ZvbnQtc2l6ZToyNXB4OyI+DQoqKkRFQ0lTw4NPIEZJTkFMKiogPC9zcGFuPiANCg0KDQpgYGB7ciwgd2FybmluZz0gRkFMU0V9DQoNCmtuaXRyOjprYWJsZShkYXRhLmZyYW1lKHJtc2VfYXJ2b3JlX2RlY2lzaW9uLCBybXNlX3JhbmRvbWZvcmVzdCkpDQoNCmBgYA0KDQoNCj4gPHNwYW4gc3R5bGU9ImNvbG9yOmJsdWU7Zm9udC1zaXplOjI1cHg7Ij4NCioqRmljYSDDs2J2aW8gcXVlIG8gbW9kZWxvIHF1ZSB0ZXZlIHVtYSBib2EgcGVyZm9ybWFuY2UgZW0gcHJlZGl6ZXIgb3Mgc2Fsw6FyaW9zIGRvcyBqb2dhZG9yZXMgYnJhc2lsZWlyb3MgZG8gZmlmYSAyNCBmb2kgbyBtb2RlbG8gZGUgcmFuZG9tIGZvcmVzdC4qKiA8L3NwYW4+IA0KDQoNCg0KIyMgQ29uY2x1c8Ojbw0KDQo+IDxjZW50ZXI+IDxzcGFuIHN0eWxlPSJjb2xvcjpibGFjaztmb250LXNpemU6MjBweDsiPg0KKipFbSB2aXJ0dWRlIGRlIHRvZGFzIGFzIGFuw6FsaXNlcyBmZWl0YXMgYW50ZXJpb3JtZW50ZSBwb2RlbW9zIGRpemVyIHF1ZSBjb25zZWd1aW1vcyBlc3RhYmVsZWNlciB1bSBjZXJ0byBwYWRyw6NvIGRlIGNvbXBvcnRhbWVudG8gbm9zIGpvZ2Fkb3JlcyBkbyBqb2dvIGZpZmEgMjQgZSBxdWUgdW0gbW9kZWxvIGRlIHJhbmRvbSBmb3Jlc3QgY29tIHVtYSB2YWxpZGHDp8OjbyAgY3J1emFkYSByZXBldGlkYSwgY29tIDUgZm9sZHMgZSB1bWEgcmVwZXRpw6fDo28gw7puaWNhIHBvZGUgc2VyIG1lbGhvciBxdWUgdW1hIMOhcnZvcmUgZGUgZGVjaXPDo28gY29tIGEgbWVzbWEgY29uZmlndXJhw6fDo28uIEEgdmFyacOhdmVsIGEgbWFpcyBpbXBvcnRhbnRlIGVuY29udHJhZGEgY29tIG8gYWxnb3JpdGhtbyBkZSByYW5kb20gZm9yZXN0IGZvaSBvIFRlbXBvIGRlIHJlYcOnw6NvIGRvIGpvZ2Fkb3IuKiogPC9zcGFuPiA8LyBjZW50ZXI+IA0KDQo8YnI+DQoNCiA8Y2VudGVyPiA8c3BhbiBzdHlsZT0iY29sb3I6Ymx1ZTtmb250LXNpemU6IDIwcHg7Ij4gKioiRGFkbyDDqSBpbmZvcm1hw6fDo28gZSBpbmZvcm1hw6fDo28gYmVtIGFuw6FsaXNhZGEgw6kgcG9kZXIiKio8L3NwYW4+IDxjZW50ZXI+IA0KDQoNCg0KIyMgUmVmZXLDqm5jaWFzDQoNCj4gaHR0cHM6Ly9zbW9sc2tpLmdpdGh1Yi5pby9saXZyb2F2YW5jYWRvL2FuYWxpc2UtZGUtY2x1c3RlcnMuaHRtbA0KDQo+IGh0dHBzOi8vd3d3LnRtd3Iub3JnL2RpbWVuc2lvbmFsaXR5Lmh0bWwNCg0KPiBPIGxpbmsgcGFyYSBiYWl4YXIgbyBiYW5jbyBkZSBkYWRvcyBwYXJhIHBvc3PDrXZlbCByZXByb2R1dGliaWxpZGFkZTogaHR0cHM6Ly93d3cua2FnZ2xlLmNvbS9kYXRhc2V0cy9yZWhhbmRsMjMvZmlmYS0yNC1wbGF5ZXItc3RhdHMtZGF0YXNldC9kYXRhDQo=