Predict basket score with Machine Learning
The purpose of this machine learning is to predict the match winner of different match with a algorithm. To do this we have various JSON with the data from 378 of the 380 matches played in the regular phase in the LNB 2018/19.
We will train a algorithm with 300 matches and pretend to predict the other 78 matches, in this case the season it´s complete, but during the season time the DB of match will fill up through the games, and the user can predict the future game after the day with the seasons stats.
Also will use this code to a stats viewer, that i repeat, in a normal season will change after each match. Unfortunately i don´t have the date of each match, which I would use to create some extra filter
1- Load the JSON files
I have 378 JSON files, in sequence the name from “1.json” to “378.json”:
JSON_List: Create a list and put all the names of the files.
rjson: Load the JSON files with the specific package for R.
DF_home and DF_away: Both DF will be use to fill the stats information.
JSON_List <- list()
DF_home <-data.frame ()
DF_away <- data.frame ()
for (i in 1:378) {
Id_JSON = (paste0(i,".json"))
JSON_List[[i]]= rjson::fromJSON(file = Id_JSON, simplify = F)
}
2- Load data from home and away
Into the json it´s a sublist call tm (total match) and into the list, the sublist 1 is for home stats and the sublist 2 is for away, i want to use some fields from this lists.
Name, Score, Two points, Three points, Free Throw, Field goals, Efficiency, Assists, Rebounds, Steals, Lost, Points in the paint, Fast Breaks, Turnovers, Bench points and Blocks.
Also put the home condition to the sublist 1, away condition to the sublist 2 and create a match id for each match.
At the end we delete all single observations.
for (i in 1:378) {
name=as.data.frame(JSON_List[[i]]$tm$"1"$name, nrow = 1)
score=as.data.frame(JSON_List[[i]]$tm$"1"$score, nrow = 1)
tot_sFieldGoalsMade=as.data.frame(JSON_List[[i]]$tm$"1"$tot_sFieldGoalsMade, nrow = 1)
tot_sFieldGoalsAttempted=as.data.frame(JSON_List[[i]]$tm$"1"$tot_sFieldGoalsAttempted, nrow = 1)
tot_sTwoPointersMade=as.data.frame(JSON_List[[i]]$tm$"1"$tot_sTwoPointersMade, nrow = 1)
tot_sTwoPointersAttempted=as.data.frame(JSON_List[[i]]$tm$"1"$tot_sTwoPointersAttempted, nrow = 1)
tot_sThreePointersMade=as.data.frame(JSON_List[[i]]$tm$"1"$tot_sThreePointersMade, nrow = 1)
tot_sThreePointersAttempted=as.data.frame(JSON_List[[i]]$tm$"1"$tot_sThreePointersAttempted, nrow = 1)
tot_sFreeThrowsMade=as.data.frame(JSON_List[[i]]$tm$"1"$tot_sFreeThrowsMade, nrow = 1)
tot_sFreeThrowsAttempted=as.data.frame(JSON_List[[i]]$tm$"1"$tot_sFreeThrowsAttempted, nrow = 1)
tot_sReboundsDefensive=as.data.frame(JSON_List[[i]]$tm$"1"$tot_sReboundsDefensive, nrow = 1)
tot_sReboundsOffensive=as.data.frame(JSON_List[[i]]$tm$"1"$tot_sReboundsOffensive, nrow = 1)
tot_sReboundsTotal=as.data.frame(JSON_List[[i]]$tm$"1"$tot_sReboundsTotal, nrow = 1)
tot_sAssists=as.data.frame(JSON_List[[i]]$tm$"1"$tot_sAssists, nrow = 1)
tot_sBlocks=as.data.frame(JSON_List[[i]]$tm$"1"$tot_sBlocks, nrow = 1)
tot_sTurnovers=as.data.frame(JSON_List[[i]]$tm$"1"$tot_sTurnovers, nrow = 1)
tot_sFoulsPersonal=as.data.frame(JSON_List[[i]]$tm$"1"$tot_sFoulsPersonal, nrow = 1)
tot_sPointsInThePaint=as.data.frame(JSON_List[[i]]$tm$"1"$tot_sPointsInThePaint, nrow = 1)
tot_sPointsSecondChance=as.data.frame(JSON_List[[i]]$tm$"1"$tot_sPointsSecondChance, nrow = 1)
tot_sPointsFromTurnovers=as.data.frame(JSON_List[[i]]$tm$"1"$tot_sPointsFromTurnovers, nrow = 1)
tot_sBenchPoints=as.data.frame(JSON_List[[i]]$tm$"1"$tot_sBenchPoints, nrow = 1)
tot_sPointsFastBreak=as.data.frame(JSON_List[[i]]$tm$"1"$tot_sPointsFastBreak, nrow = 1)
tot_sSteals=as.data.frame(JSON_List[[i]]$tm$"1"$tot_sSteals, nrow = 1)
tot_eff_5=as.data.frame(JSON_List[[i]]$tm$"1"$tot_eff_5, nrow = 1)
match_id = i
condicion = "home"
Observacion = cbind(name,score,tot_sFieldGoalsMade,tot_sFieldGoalsAttempted,tot_sTwoPointersMade,tot_sTwoPointersAttempted,tot_sThreePointersMade,tot_sThreePointersAttempted,tot_sFreeThrowsMade,tot_sFreeThrowsAttempted,tot_sReboundsDefensive,tot_sReboundsOffensive,tot_sReboundsTotal,tot_sAssists,tot_sBlocks,tot_sTurnovers,tot_sFoulsPersonal,tot_sPointsInThePaint,tot_sPointsSecondChance,tot_sPointsFromTurnovers,tot_sBenchPoints,tot_sPointsFastBreak,tot_sSteals,tot_eff_5, match_id, condicion
)
DF_home= rbind(DF_home, Observacion )
}
for (i in 1:378) {
name=as.data.frame(JSON_List[[i]]$tm$"2"$name, nrow = 1)
score=as.data.frame(JSON_List[[i]]$tm$"2"$score, nrow = 1)
tot_sFieldGoalsMade=as.data.frame(JSON_List[[i]]$tm$"2"$tot_sFieldGoalsMade, nrow = 1)
tot_sFieldGoalsAttempted=as.data.frame(JSON_List[[i]]$tm$"2"$tot_sFieldGoalsAttempted, nrow = 1)
tot_sTwoPointersMade=as.data.frame(JSON_List[[i]]$tm$"2"$tot_sTwoPointersMade, nrow = 1)
tot_sTwoPointersAttempted=as.data.frame(JSON_List[[i]]$tm$"2"$tot_sTwoPointersAttempted, nrow = 1)
tot_sThreePointersMade=as.data.frame(JSON_List[[i]]$tm$"2"$tot_sThreePointersMade, nrow = 1)
tot_sThreePointersAttempted=as.data.frame(JSON_List[[i]]$tm$"2"$tot_sThreePointersAttempted, nrow = 1)
tot_sFreeThrowsMade=as.data.frame(JSON_List[[i]]$tm$"2"$tot_sFreeThrowsMade, nrow = 1)
tot_sFreeThrowsAttempted=as.data.frame(JSON_List[[i]]$tm$"2"$tot_sFreeThrowsAttempted, nrow = 1)
tot_sReboundsDefensive=as.data.frame(JSON_List[[i]]$tm$"2"$tot_sReboundsDefensive, nrow = 1)
tot_sReboundsOffensive=as.data.frame(JSON_List[[i]]$tm$"2"$tot_sReboundsOffensive, nrow = 1)
tot_sReboundsTotal=as.data.frame(JSON_List[[i]]$tm$"2"$tot_sReboundsTotal, nrow = 1)
tot_sAssists=as.data.frame(JSON_List[[i]]$tm$"2"$tot_sAssists, nrow = 1)
tot_sBlocks=as.data.frame(JSON_List[[i]]$tm$"2"$tot_sBlocks, nrow = 1)
tot_sTurnovers=as.data.frame(JSON_List[[i]]$tm$"2"$tot_sTurnovers, nrow = 1)
tot_sFoulsPersonal=as.data.frame(JSON_List[[i]]$tm$"2"$tot_sFoulsPersonal, nrow = 1)
tot_sPointsInThePaint=as.data.frame(JSON_List[[i]]$tm$"2"$tot_sPointsInThePaint, nrow = 1)
tot_sPointsSecondChance=as.data.frame(JSON_List[[i]]$tm$"2"$tot_sPointsSecondChance, nrow = 1)
tot_sPointsFromTurnovers=as.data.frame(JSON_List[[i]]$tm$"2"$tot_sPointsFromTurnovers, nrow = 1)
tot_sBenchPoints=as.data.frame(JSON_List[[i]]$tm$"2"$tot_sBenchPoints, nrow = 1)
tot_sPointsFastBreak=as.data.frame(JSON_List[[i]]$tm$"2"$tot_sPointsFastBreak, nrow = 1)
tot_sSteals=as.data.frame(JSON_List[[i]]$tm$"2"$tot_sSteals, nrow = 1)
tot_eff_5=as.data.frame(JSON_List[[i]]$tm$"2"$tot_eff_5, nrow = 1)
match_id = i
condicion = "away"
Observacion = cbind(name,score,tot_sFieldGoalsMade,tot_sFieldGoalsAttempted,tot_sTwoPointersMade,tot_sTwoPointersAttempted,tot_sThreePointersMade,tot_sThreePointersAttempted,tot_sFreeThrowsMade,tot_sFreeThrowsAttempted,tot_sReboundsDefensive,tot_sReboundsOffensive,tot_sReboundsTotal,tot_sAssists,tot_sBlocks,tot_sTurnovers,tot_sFoulsPersonal,tot_sPointsInThePaint,tot_sPointsSecondChance,tot_sPointsFromTurnovers,tot_sBenchPoints,tot_sPointsFastBreak,tot_sSteals, tot_eff_5,match_id, condicion
)
DF_away= rbind(DF_away, Observacion )
}
rm (Observacion, name,score,tot_sFieldGoalsMade,tot_sFieldGoalsAttempted,tot_sTwoPointersMade,tot_sTwoPointersAttempted,tot_sThreePointersMade,tot_sThreePointersAttempted,tot_sFreeThrowsMade,tot_sFreeThrowsAttempted,tot_sReboundsDefensive,tot_sReboundsOffensive,tot_sReboundsTotal,tot_sAssists,tot_sBlocks,tot_sTurnovers,tot_sFoulsPersonal,tot_sPointsInThePaint,tot_sPointsSecondChance,tot_sPointsFromTurnovers,tot_sBenchPoints,tot_sPointsFastBreak,tot_sSteals,match_id, condicion, tot_eff_5 )
head(DF_home, 5)
3- Rename the columns
I change the name to the different columns for those that it´s easy to reminder for me.
names(DF_home) <- c ("name",
"score",
"FG",
"FGA",
"P2",
"P2A",
"P3",
"P3A",
"FT",
"FTA",
"DRBS",
"ORBS",
"RBS",
"ASS",
"BLO",
"TO",
"PF",
"PP",
"OP2",
"PFT",
"BP",
"FB",
"STE",
"EFF",
"match_id",
"condition")
names(DF_away) <- c ("name",
"score",
"FG",
"FGA",
"P2",
"P2A",
"P3",
"P3A",
"FT",
"FTA",
"DRBS",
"ORBS",
"RBS",
"ASS",
"BLO",
"TO",
"PF",
"PP",
"OP2",
"PFT",
"BP",
"FB",
"STE",
"EFF",
"match_id",
"condition")
head (DF_away, 5)
4- Summary the DF
I summary both DB and search if were some NAs, if we see some values are near to be outliers, but i didn´t remove because are normal for basket, in special with a overtime match.
anyNA(DF_away)
[1] FALSE
anyNA(DF_home)
[1] FALSE
summary(DF_away)
name score FG FGA P2 P2A
Length:378 Min. : 40.00 Min. :14.00 Min. :45.00 Min. : 9.00 Min. :18.00
Class :character 1st Qu.: 72.00 1st Qu.:26.00 1st Qu.:60.00 1st Qu.:17.00 1st Qu.:35.00
Mode :character Median : 78.50 Median :28.00 Median :63.00 Median :20.00 Median :39.00
Mean : 78.42 Mean :28.47 Mean :63.39 Mean :20.46 Mean :39.69
3rd Qu.: 84.75 3rd Qu.:31.00 3rd Qu.:67.00 3rd Qu.:24.00 3rd Qu.:44.00
Max. :115.00 Max. :42.00 Max. :86.00 Max. :31.00 Max. :65.00
P3 P3A FT FTA DRBS ORBS RBS
Min. : 1.000 Min. : 8.0 Min. : 1.00 Min. : 2.00 Min. :13.00 Min. : 1.00 Min. :22.0
1st Qu.: 6.000 1st Qu.:20.0 1st Qu.:10.00 1st Qu.:14.00 1st Qu.:24.00 1st Qu.: 6.00 1st Qu.:32.0
Median : 8.000 Median :24.0 Median :13.00 Median :18.00 Median :27.00 Median : 8.00 Median :36.0
Mean : 8.008 Mean :23.7 Mean :13.48 Mean :18.67 Mean :27.47 Mean : 8.73 Mean :36.2
3rd Qu.:10.000 3rd Qu.:27.0 3rd Qu.:17.00 3rd Qu.:23.00 3rd Qu.:30.00 3rd Qu.:10.00 3rd Qu.:40.0
Max. :17.000 Max. :38.0 Max. :31.00 Max. :40.00 Max. :47.00 Max. :20.00 Max. :63.0
ASS BLO TO PF PP OP2 PFT
Min. : 3 Min. :0.00 Min. : 3.00 Min. :11.00 Min. :10.0 Min. : 0.000 Min. : 2.00
1st Qu.:12 1st Qu.:1.00 1st Qu.:10.00 1st Qu.:18.00 1st Qu.:28.0 1st Qu.: 6.000 1st Qu.: 9.00
Median :15 Median :2.00 Median :13.00 Median :21.00 Median :34.0 Median : 9.000 Median :12.00
Mean :15 Mean :2.05 Mean :13.08 Mean :20.87 Mean :34.7 Mean : 8.714 Mean :12.65
3rd Qu.:18 3rd Qu.:3.00 3rd Qu.:15.00 3rd Qu.:23.00 3rd Qu.:40.0 3rd Qu.:12.000 3rd Qu.:16.00
Max. :30 Max. :8.00 Max. :26.00 Max. :43.00 Max. :56.0 Max. :20.000 Max. :32.00
BP FB STE EFF match_id condition
Min. : 0.00 Min. : 0.00 Min. : 1.000 Min. : 24.00 Min. : 1.00 Length:378
1st Qu.:17.00 1st Qu.: 8.00 1st Qu.: 5.000 1st Qu.: 69.00 1st Qu.: 95.25 Class :character
Median :24.00 Median :12.00 Median : 6.000 Median : 81.00 Median :189.50 Mode :character
Mean :24.55 Mean :12.53 Mean : 6.574 Mean : 80.65 Mean :189.50
3rd Qu.:31.00 3rd Qu.:17.00 3rd Qu.: 8.000 3rd Qu.: 93.00 3rd Qu.:283.75
Max. :58.00 Max. :33.00 Max. :15.000 Max. :146.00 Max. :378.00
summary(DF_home)
name score FG FGA P2 P2A
Length:378 Min. : 52 Min. :18.00 Min. :48.0 Min. :11.00 Min. :20.00
Class :character 1st Qu.: 76 1st Qu.:27.00 1st Qu.:61.0 1st Qu.:18.25 1st Qu.:36.00
Mode :character Median : 83 Median :29.00 Median :65.0 Median :21.00 Median :40.00
Mean : 83 Mean :29.86 Mean :64.9 Mean :21.62 Mean :40.59
3rd Qu.: 90 3rd Qu.:33.00 3rd Qu.:69.0 3rd Qu.:25.00 3rd Qu.:45.00
Max. :114 Max. :44.00 Max. :96.0 Max. :36.00 Max. :62.00
P3 P3A FT FTA DRBS ORBS
Min. : 2.000 Min. : 8.00 Min. : 4.00 Min. : 7.00 Min. :15.00 Min. : 1.000
1st Qu.: 6.000 1st Qu.:20.00 1st Qu.:11.00 1st Qu.:16.00 1st Qu.:25.00 1st Qu.: 7.000
Median : 8.000 Median :24.00 Median :14.00 Median :20.00 Median :28.00 Median :10.000
Mean : 8.238 Mean :24.31 Mean :15.04 Mean :20.79 Mean :28.07 Mean : 9.772
3rd Qu.:10.000 3rd Qu.:28.00 3rd Qu.:18.00 3rd Qu.:25.00 3rd Qu.:31.00 3rd Qu.:12.000
Max. :20.000 Max. :50.00 Max. :43.00 Max. :59.00 Max. :44.00 Max. :21.000
RBS ASS BLO TO PF PP
Min. :23.00 Min. : 5.00 Min. :0.000 Min. : 2.00 Min. : 9.00 Min. : 8.00
1st Qu.:34.00 1st Qu.:14.00 1st Qu.:1.000 1st Qu.: 9.00 1st Qu.:17.00 1st Qu.:30.50
Median :37.50 Median :17.00 Median :2.000 Median :12.00 Median :20.00 Median :36.00
Mean :37.84 Mean :17.05 Mean :2.534 Mean :11.83 Mean :19.61 Mean :37.08
3rd Qu.:42.00 3rd Qu.:20.00 3rd Qu.:4.000 3rd Qu.:14.00 3rd Qu.:22.00 3rd Qu.:44.00
Max. :57.00 Max. :34.00 Max. :8.000 Max. :25.00 Max. :34.00 Max. :64.00
OP2 PFT BP FB STE EFF
Min. : 0.00 Min. : 0.0 Min. : 0.0 Min. : 0.0 Min. : 0.000 Min. : 35.00
1st Qu.: 7.00 1st Qu.:11.0 1st Qu.:18.0 1st Qu.: 9.0 1st Qu.: 5.000 1st Qu.: 80.00
Median :10.00 Median :14.0 Median :25.0 Median :13.0 Median : 7.000 Median : 93.00
Mean :10.27 Mean :14.7 Mean :26.3 Mean :13.9 Mean : 6.854 Mean : 93.26
3rd Qu.:13.00 3rd Qu.:18.0 3rd Qu.:33.0 3rd Qu.:18.0 3rd Qu.: 9.000 3rd Qu.:106.75
Max. :24.00 Max. :37.0 Max. :55.0 Max. :47.0 Max. :16.000 Max. :155.00
match_id condition
Min. : 1.00 Length:378
1st Qu.: 95.25 Class :character
Median :189.50 Mode :character
Mean :189.50
3rd Qu.:283.75
Max. :378.00
5- Create DF for teams
Join the home and away DF by match ID will create a new DF with all the match stats and the possibility to know who was the winner.
Bind both DF with similar numbers of columns and observation will create a DF where is all the data for each team.
DF_Total <- data.frame()
DF_Total <-
DF_away%>%
inner_join(DF_home, ., by = "match_id")
DF_Team <- data.frame()
DF_Team <- rbind(DF_home,DF_away)
tail (DF_Team, 10)
NA
6- Determine winner
Determine who win each match will create two class, the class1 that it´s 1 when win the home team and 0 when win the away team, and class2 that it´s the inverse.
DF_Score <- data.frame()
DF_Score <- DF_Total%>%
mutate( winner= ifelse(DF_Total$score.x > DF_Total$score.y, DF_Total$name.x, DF_Total$name.y)) %>%
select(match_id, winner)
DF_Total$class1 <- ifelse(DF_Total$score.x > DF_Total$score.y, 1, 0)
DF_Total$class2 <- ifelse(DF_Total$score.x > DF_Total$score.y, 0, 1)
DF_Team <- DF_Team %>%
left_join(DF_Score, ., by = "match_id")
head(DF_Score, 5)
7- Create the stats table
The first when create the stats table it´s group the teams to have a individual observation of each one and know how many match played, some teams are in 37 match because are missing 2 JSON files.
Also we create a table with the wins at home, away, in total and a ratio that determinate the upgrade when plays in home and a downgrade when plays away.
teams_stats<- data.frame()
teams_stats <- DF_Team %>%
group_by(name) %>%
count(name)
wins<- data.frame()
wins <- DF_Team %>%
group_by(winner) %>%
count(winner) %>%
mutate(n = n/2)
names(wins) <- c ("name", "wins")
teams_stats <- teams_stats %>%
inner_join(wins, ., by = "name")
teams_stats <- teams_stats %>% mutate(wins_ratio = wins/n)
home_wins <- data.frame()
home_wins <- DF_Total %>%
group_by(name.x, class1) %>%
filter(class1 == 1)%>%
summarise(winshome= sum(class1)) %>%
mutate(winshome = winshome/19)%>%
select(name.x, winshome)
names(home_wins) = c("name", "home_ratio")
teams_stats <- teams_stats %>%
inner_join(home_wins, ., by = "name")
teams_stats <- teams_stats %>% mutate(home_wins_upgrade = (home_ratio/wins_ratio))
away_wins <- data.frame()
away_wins <- DF_Total %>%
group_by(name.y, class2) %>%
filter(class2 == 1)%>%
summarise(winsaway= sum(class2)) %>%
mutate(winsaway = winsaway/19)%>%
select(name.y, winsaway)
names(away_wins) = c("name", "away_ratio")
teams_stats <- teams_stats %>%
inner_join(away_wins, ., by = "name")
teams_stats <- teams_stats %>% mutate(away_wins_downgrade = (away_ratio/wins_ratio))
print (head(teams_stats, 5))
8- Create a shot DF
A DF with all the info of the shots during the season, and also create the field with the percentage of shots made it´s the next step, this will bring more information and will be join with the team stats.
shots <- data.frame()
shots <- DF_Team %>%
group_by(name) %>%
summarise(
P3 = sum(P3) ,
P3A = sum(P3A) ,
P2 = sum(P2) ,
P2A = sum(P2A) ,
FT = sum(FT) ,
FTA = sum(FTA) ,
FG = sum(FG),
FGA = sum(FGA) ,
)
`summarise()` ungrouping output (override with `.groups` argument)
shots <- shots %>%
mutate( P3P = P3 / P3A ) %>%
mutate(P2P = P2 / P2A ) %>%
mutate(FTP = FT / FTA ) %>%
mutate(FGP = FG / FGA )
teams_stats <- teams_stats %>%
inner_join(shots, ., by = "name")
tail (shots, 5)
9- Create other stats DF
A DF with all the info of other during the season, it´s the next step, this will bring more information and will be join with the team stats.
other_stats <- data.frame()
other_stats <- DF_Team %>%
group_by(name) %>%
summarise(
RBS = mean(RBS),
ASS= mean(ASS),
BLO = mean(BLO),
DRI = mean(STE) - mean(TO),
PP = mean (PP),
OP2 = mean (OP2),
BP = mean(BP),
FB = mean(FB) + mean(PFT),
points = mean(score),
EFF = mean(EFF)
)
`summarise()` ungrouping output (override with `.groups` argument)
teams_stats <- teams_stats %>%
inner_join(other_stats, ., by = "name")
head(other_stats, 5)
10- Create a match DF
We have all the info about the teams, know will create a DF of each match preview with the info of the teams, this will be use to teach the model, using the info of DF_total (match id, home, away and the classes) and bringing all the stats for the correspondient DF
DF_match <- data.frame()
DF_match <- DF_Total %>%
select(match_id, name.x, name.y, class1, class2 ) #I take the initial fields
colnames(DF_match)[2] <- "name" #Rename to make the join
DF_match <- teams_stats %>%
inner_join(DF_match, ., by = "name") #Do the join
colnames(DF_match)[2] <- "home" #I put as home name
colnames(DF_match)[3] <- "name" #Rename to make the join
DF_match <- teams_stats %>%
inner_join(DF_match, ., by = "name") #Do the join
colnames(DF_match)[3] <- "away"#I put as away name
tail(DF_match, 10)
11- Create ratios
For the match DF i will create ratios to normalize the data between the observation of home teams (.x) and the away teams (.y).
The normalization it´s a mandatory step to avoid problem in the regression.
DF_match <- DF_match%>%
mutate(
points_ratio.x = (points.x-points.y)/points.y ,
wins_dif.x = (wins_ratio.x-wins_ratio.y) / wins_ratio.y,
rbs_ratio.x = (RBS.x - RBS.y)/RBS.y ,
SH_ratio.x = (FGP.x - FGP.y)/ FGP.y ,
EFF_ratio.x = (EFF.x - EFF.y)/ EFF.y,
ASS_ratio.x = (ASS.x - ASS.y) / ASS.y,
BLO_ratio.x= (BLO.x - BLO.y ) /BLO.y,
DRI_ratio.x = (DRI.x - DRI.y) / DRI.y,
PP_ratio.x = (PP.x -PP.y) / PP.y,
P3P_ratio.x = (P3P.x - P3P.y) / P3P.y,
P2P_ratio.x = (P2P.x - P2P.y) / P2P.y,
FT_ratio.x = (FTP.x - FTP.y) / FTP.y ,
OP2_ratio.x = (OP2.x - OP2.y) / OP2.y,
home_condition.x = (home_wins_upgrade.x - away_wins_downgrade.y) / away_wins_downgrade.y,
BP_ratio.x = (BP.x - BP.y) / BP.y,
FB_ratio.x = (FB.x - FB.y) / FB.y,
points_ratio.y = (points.y-points.x)/points.x ,
rbs_ratio.y = (RBS.y - RBS.x)/RBS.x ,
SH_ratio.y = (FGP.y - FGP.x)/ FGP.x ,
EFF_ratio.y = (EFF.y - EFF.x)/ EFF.x,
ASS_ratio.y = (ASS.y - ASS.x) / ASS.x,
BLO_ratio.y= (BLO.y - BLO.x ) /BLO.x,
DRI_ratio.y = (DRI.y - DRI.x) / DRI.x,
PP_ratio.y = (PP.y -PP.x) / PP.x,
OP2_ratio.y = (OP2.y - OP2.x) / OP2.x,
BP_ratio.y = (BP.y - BP.x) / BP.x,
away_condition.y = -(away_wins_downgrade.y - home_wins_upgrade.x) / away_wins_downgrade.y,
wins_dif.y = (wins_ratio.y-wins_ratio.x) / wins_ratio.y ,
FB_ratio.y = (FB.y - FB.x) / FB.x,
) %>%
select(match_id,
home,
away,
points_ratio.x,
home_condition.x,
rbs_ratio.x,
SH_ratio.x ,
wins_dif.x,
EFF_ratio.x ,
ASS_ratio.x ,
BLO_ratio.x,
DRI_ratio.x ,
PP_ratio.x ,
P3P_ratio.x ,
P2P_ratio.x ,
FT_ratio.x ,
OP2_ratio.x ,
BP_ratio.x ,
FB_ratio.x ,
home_wins_upgrade.x ,
wins_ratio.x,
points_ratio.y ,
rbs_ratio.y ,
SH_ratio.y ,
EFF_ratio.y ,
ASS_ratio.y ,
BLO_ratio.y,
DRI_ratio.y ,
PP_ratio.y ,
OP2_ratio.y ,
BP_ratio.y ,
FB_ratio.y ,
away_wins_downgrade.y ,
wins_dif.y,
away_condition.y,
wins_ratio.y ,
class1,
class2)
head (DF_match, 10)
12- Training and testing
I divide the DF between training and testing, in this case 300 matches, near 80%, are use to the training and the other 78 match will be the test target.
set.seed(1234)
sample <- 300
trIndex <- sample(nrow(DF_match), sample, replace=F)
teIndex <- seq_len(nrow(DF_match))[!(seq_len(nrow(DF_match)) %in% trIndex)]
training <- DF_match[trIndex,]
test <- DF_match[teIndex,]
tail(test, 10)
13- Create the class regression
I create the regression for the class1 (home wins) with all the information and stats of the team 1, the most important are the win ratio and the home upgrade, the rest of the information get small details and i decided to don´t keep out
Reg_1 <- glm(class1 ~ points_ratio.x + rbs_ratio.x +SH_ratio.x + EFF_ratio.x + ASS_ratio.x+ BLO_ratio.x + DRI_ratio.x +PP_ratio.x + OP2_ratio.x +BP_ratio.x + FB_ratio.x + home_condition.x + wins_dif.x, data=training, family=binomial(link="logit"))
summary(Reg_1)
Call:
glm(formula = class1 ~ points_ratio.x + rbs_ratio.x + SH_ratio.x +
EFF_ratio.x + ASS_ratio.x + BLO_ratio.x + DRI_ratio.x + PP_ratio.x +
OP2_ratio.x + BP_ratio.x + FB_ratio.x + home_condition.x +
wins_dif.x, family = binomial(link = "logit"), data = training)
Deviance Residuals:
Min 1Q Median 3Q Max
-2.2567 -1.0866 0.5355 0.9454 1.6934
Coefficients:
Estimate Std. Error z value Pr(>|z|)
(Intercept) -0.37403 0.33465 -1.118 0.2637
points_ratio.x 5.29290 9.05418 0.585 0.5588
rbs_ratio.x 0.36648 7.21009 0.051 0.9595
SH_ratio.x 6.82025 13.48520 0.506 0.6130
EFF_ratio.x -4.99611 6.22734 -0.802 0.4224
ASS_ratio.x -1.14994 2.06344 -0.557 0.5773
BLO_ratio.x 1.23716 1.55440 0.796 0.4261
DRI_ratio.x -0.29932 1.47404 -0.203 0.8391
PP_ratio.x -1.28217 3.94090 -0.325 0.7449
OP2_ratio.x 0.42855 2.70186 0.159 0.8740
BP_ratio.x -0.25085 0.77456 -0.324 0.7460
FB_ratio.x -0.03992 1.26968 -0.031 0.9749
home_condition.x 0.94851 0.31376 3.023 0.0025 **
wins_dif.x 1.88558 1.44813 1.302 0.1929
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
(Dispersion parameter for binomial family taken to be 1)
Null deviance: 393.19 on 299 degrees of freedom
Residual deviance: 339.52 on 286 degrees of freedom
AIC: 367.52
Number of Fisher Scoring iterations: 5
14- Create the tree
In the tree is similar to the regression, the importance is the wins ratio, the home condition and also add the shoot efficiency to know if home team win or no.
fit_1 <- rpart(class1 ~ points_ratio.x + rbs_ratio.x +SH_ratio.x + EFF_ratio.x + ASS_ratio.x+ BLO_ratio.x + DRI_ratio.x +PP_ratio.x + OP2_ratio.x +BP_ratio.x + FB_ratio.x + home_condition.x + wins_dif.x, data=training)
rpart.plot(fit_1, extra=0, type=2)

15- Predict the class
We will have after the prediction a probability of 1 from tree, from the regression and finally will use the mean of both to determinate the probability of home team wins
Pred_class1_Tree <- predict(fit_1,test,method='class')
Pred_class1_Reg <- predict(Reg_1,test,type='response')
test$class1_Reg <- Pred_class1_Reg
test$class1_Tree <- Pred_class1_Tree
test$class1_ensamble <- rowMeans(test[,c("class1_Tree", "class1_Reg")])
16- Repeat the same for class 2
Make know the same regression and tree for class 2 it´s the next step, and also the main field are the same, change the upgrade of home team for the downgrade in the away team
Reg_2 <- glm(class2 ~ points_ratio.y + rbs_ratio.y + SH_ratio.y + EFF_ratio.y + ASS_ratio.y + BLO_ratio.y + DRI_ratio.y + PP_ratio.y + OP2_ratio.y + BP_ratio.y + FB_ratio.y + away_condition.y + wins_dif.y , data=training, family=binomial(link="logit"))
summary(Reg_2)
Call:
glm(formula = class2 ~ points_ratio.y + rbs_ratio.y + SH_ratio.y +
EFF_ratio.y + ASS_ratio.y + BLO_ratio.y + DRI_ratio.y + PP_ratio.y +
OP2_ratio.y + BP_ratio.y + FB_ratio.y + away_condition.y +
wins_dif.y, family = binomial(link = "logit"), data = training)
Deviance Residuals:
Min 1Q Median 3Q Max
-1.5721 -0.9445 -0.5251 1.0590 2.3288
Coefficients:
Estimate Std. Error z value Pr(>|z|)
(Intercept) 0.43216 0.33929 1.274 0.2028
points_ratio.y 5.41093 8.73122 0.620 0.5354
rbs_ratio.y 3.10256 7.16125 0.433 0.6648
SH_ratio.y -4.54893 13.39144 -0.340 0.7341
EFF_ratio.y -2.23853 5.95135 -0.376 0.7068
ASS_ratio.y 0.07936 1.82499 0.043 0.9653
BLO_ratio.y -0.68663 1.11643 -0.615 0.5385
DRI_ratio.y 0.26851 1.40206 0.192 0.8481
PP_ratio.y 1.40929 3.90546 0.361 0.7182
OP2_ratio.y -2.45812 2.51657 -0.977 0.3287
BP_ratio.y 0.40550 0.67684 0.599 0.5491
FB_ratio.y -0.57270 1.20680 -0.475 0.6351
away_condition.y -0.92693 0.31932 -2.903 0.0037 **
wins_dif.y 2.96970 1.29190 2.299 0.0215 *
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
(Dispersion parameter for binomial family taken to be 1)
Null deviance: 393.19 on 299 degrees of freedom
Residual deviance: 339.38 on 286 degrees of freedom
AIC: 367.38
Number of Fisher Scoring iterations: 5
fit_2 <- rpart(class2 ~ points_ratio.y + rbs_ratio.y + SH_ratio.y + EFF_ratio.y + ASS_ratio.y + BLO_ratio.y + DRI_ratio.y + PP_ratio.y + OP2_ratio.y + BP_ratio.y + FB_ratio.y + away_condition.y + wins_dif.y , data=training)
rpart.plot(fit_2, extra=0, type=2)

Pred_class2_Tree <- predict(fit_2,test,method='class')
Pred_class2_Reg <- predict(Reg_2,test,type='response')
test$class2_Reg <- Pred_class2_Reg
test$class2_Tree <- Pred_class2_Tree
test$class2_ensamble <- rowMeans(test[,c("class2_Tree", "class2_Reg")])
17- Determinate the winner
If class 1 is higher than class 2 it´s the home team the winner, conversely if is lower the away team its the winner.
We create a DF with the results predicted in testing and see that near of the 80% of the match were predicted good by this tool.
This high level of precision it´s a good signal that data science can determinate the results and the probability before the match.
If we run over the 378 matches the algorithm (For all match, tat who was in training and in testing) the success rate improve over the 85% percent
test$winner <- ifelse(test$class1 == 1, "Home", "Away")
test$winner_predict <- ifelse(test$class1_ensamble > test$class2_ensamble, "Home", "Away")
DF_Score <- test %>%
select(match_id, home, away, winner, winner_predict)
DF_Score$success <- ifelse (test$winner==test$winner_predict, 1, 0)
print ("The success rate is:")
[1] "The success rate is:"
[1] 0.795641
18- Create the preview probability
In another DF will create the probability to win of each team, divide for each class probability the add of both class, in this case will have a probability for home and other for away, and each will add the 100% percent.
The final report
Now with all the information and probability we can have a report panel to all the stats and prediction for teams and matches, let´s explore it.
A glossary teams of stats we need to know the name of each field
1- Individual stats
Create a function, it will give the possibility to see the statistics for a team in a category, for example see Boca in 3 points % or Quilmes y rebounds per game
#Glossary teams
#"ARGENTINO" "ATENAS (CBA)" "BOCA" "COMUNICACIONES" "ESTUDIANTES (C)" "FERRO"
#"GIMNASIA (CR)" "HISPANO" "INSTITUTO" "LA UNION FSA" "LIBERTAD" "OBRAS"
#"OLIMPICO" "PEÑAROL" "QUILMES" "QUIMSA" "REGATAS" "SAN LORENZO"
#Glossary stats
#"RBS" = Rebounds || "ASS" = Assist || "BLO" = Blocks || "DRI" = Steal - Lost || "PP" = Point in the paint
#"OP2" = Second opportunity points || #"BP" = Bench points || #"FB" = Fast break || #"points" = Points per game
#"EFF" = Efficiency per game || "P3P" = %of 3 points || "P2P" = %of 2points || "FTP" = % Free Throws
#"FGP" = % of field goals || "wins_ratio" = % of wins||"home_ratio" = %of wins at home ||"away ratio" = %of wins away
team_stats_function = function (x,z){
teams_stats %>%
melt(id = "name") %>%
filter(name %in% (x)) %>%
filter(variable %in% (z)) %>%
ggplot()+
aes(x=variable, y= value, fill= name) +
geom_bar(stat='identity', position='dodge') +
labs(title = "Stats view", x = "Stat", y = "Total") +
tema1
}
team_stats_function (c("BOCA"), c("P3P"))

team_stats_function (c("QUILMES"), c("RBS"))

2- Multiple teams the same stats
You can also see the performance of several teams in the same category, in this case Ferro and Obras in points and San Lorenzo, Quimsa and Comunicaciones in bench point
#Glossary teams
#"ARGENTINO" "ATENAS (CBA)" "BOCA" "COMUNICACIONES" "ESTUDIANTES (C)" "FERRO"
#"GIMNASIA (CR)" "HISPANO" "INSTITUTO" "LA UNION FSA" "LIBERTAD" "OBRAS"
#"OLIMPICO" "PEÑAROL" "QUILMES" "QUIMSA" "REGATAS" "SAN LORENZO"
#Glossary stats
#"RBS" = Rebounds || "ASS" = Assist || "BLO" = Blocks || "DRI" = Steal - Lost || "PP" = Point in the paint
#"OP2" = Second opportunity points || #"BP" = Bench points || #"FB" = Fast break || #"points" = Points per game
#"EFF" = Efficiency per game || "P3P" = %of 3 points || "P2P" = %of 2points || "FTP" = % Free Throws
#"FGP" = % of field goals || "wins_ratio" = % of wins||"home_ratio" = %of wins at home ||"away ratio" = %of wins away
team_stats_function (c("FERRO", "OBRAS"), c("points"))

team_stats_function (c("SAN LORENZO", "QUIMSA", "COMUNICACIONES"), c("BP"))

3- Multiple stats the same team
You can also see the performance of several stats but for the same team, in this case from Instituto will se the % of 2 points, 3 points, FT, Field Goals and from Hispano the wins ratio, the away wins ratio and home wins ratio
#Glossary teams
#"ARGENTINO" "ATENAS (CBA)" "BOCA" "COMUNICACIONES" "ESTUDIANTES (C)" "FERRO"
#"GIMNASIA (CR)" "HISPANO" "INSTITUTO" "LA UNION FSA" "LIBERTAD" "OBRAS"
#"OLIMPICO" "PEÑAROL" "QUILMES" "QUIMSA" "REGATAS" "SAN LORENZO"
#Glossary stats
#"RBS" = Rebounds || "ASS" = Assist || "BLO" = Blocks || "DRI" = Steal - Lost || "PP" = Point in the paint
#"OP2" = Second opportunity points || #"BP" = Bench points || #"FB" = Fast break || #"points" = Points per game
#"EFF" = Efficiency per game || "P3P" = %of 3 points || "P2P" = %of 2points || "FTP" = % Free Throws
#"FGP" = % of field goals || "wins_ratio" = % of wins||"home_ratio" = %of wins at home ||"away ratio" = %of wins away
team_stats_function (c("INSTITUTO"), c("P2P", "P3P", "FTP", "FGP"))

team_stats_function (c("HISPANO"), c("wins_ratio", "home_ratio", "away_ratio"))

4- Multiple stats and multiple teams
You can also see the performance of several stats in several teams, in this case from Libertad, Olimpico and Regatas we want to see the rebounds, the assist, the bench points and fast break points
#Glossary teams
#"ARGENTINO" "ATENAS (CBA)" "BOCA" "COMUNICACIONES" "ESTUDIANTES (C)" "FERRO"
#"GIMNASIA (CR)" "HISPANO" "INSTITUTO" "LA UNION FSA" "LIBERTAD" "OBRAS"
#"OLIMPICO" "PEÑAROL" "QUILMES" "QUIMSA" "REGATAS" "SAN LORENZO"
#Glossary stats
#"RBS" = Rebounds || "ASS" = Assist || "BLO" = Blocks || "DRI" = Steal - Lost || "PP" = Point in the paint
#"OP2" = Second opportunity points || #"BP" = Bench points || #"FB" = Fast break || #"points" = Points per game
#"EFF" = Efficiency per game || "P3P" = %of 3 points || "P2P" = %of 2points || "FTP" = % Free Throws
#"FGP" = % of field goals || "wins_ratio" = % of wins||"home_ratio" = %of wins at home ||"away ratio" = %of wins away
team_stats_function (c("LIBERTAD", "OLIMPICO", "REGATAS"), c("FB", "BP", "RBS", "ASS"))

5- Preview review
Further the individual stats, in a preview cast we can see the ratios between home and away team for the most important stats.
In this case the preview between Obras and Boca, show in the rigth that stats were Obras is best and in the left those where Boca is best.
Preview_review = function (x,z){
DF_match %>%
filter(home == x & away == z) %>%
select(home, wins_dif.x, points_ratio.x, rbs_ratio.x, ASS_ratio.x, PP_ratio.x, BP_ratio.x, OP2_ratio.x ) %>%
melt(id = "home") %>%
mutate(best = ifelse(value> 0, "Home best", "Away best"))%>%
ggplot(aes(x= variable, y= value, fill = best)) +
geom_bar(stat='identity', position='dodge') +
labs(title = paste0("Home: ", x, " Away: ", z), x = "Stat", y = "% of best") +
coord_flip()+
scale_fill_manual(values = c("darkorange1", "orange")) +
scale_color_manual(values = c("darkorange1", "orange")) +
tema2
}
Preview_review ("OBRAS", "BOCA")

6- Preview review in radar
For best display, but difficult comprehension we can see the preview review in a Radar or Polar graph
Preview_review_polar = function (x,z){
DF_match %>%
filter(home == x & away == z) %>%
select(home, wins_dif.x, points_ratio.x, rbs_ratio.x, ASS_ratio.x, PP_ratio.x, BP_ratio.x, OP2_ratio.x) %>%
melt(id = "home") %>%
mutate(best = ifelse(value> 0, "Home best", "Away best"))%>%
ggplot(aes(x= variable, y= value, fill = best)) +
geom_bar(stat='identity', position='dodge') +
labs(title = paste0("Home: ", x, " Away: ", z), x = "Stat", y = "% of best") +
coord_polar()+
scale_fill_manual(values = c("green", "red")) +
scale_color_manual(values = c("green", "red")) +
tema2
}
Preview_review_polar ("OBRAS", "BOCA")

7- Preview probability
The most important after the machine learning its the probability of win for each team, in this case create a function were put the name of teams of the next match and can see the probability of success for each one.
In this example Hispano vs Regatas and Libertad vs Peñarol
Probability_by_user = function (x,z){
DF_match_preview %>%
filter(home == x & away == z) %>%
melt(id = "home") %>%
filter(variable != "away") %>%
ggplot(aes(x= variable, y= value)) +
geom_bar(stat='identity', position='dodge', fill = "#CF5300") +
labs(title = paste0("Home: ", x, " Away: ", z), x = "Team", y = "%Probability") +
tema1
}
Probability_by_user ("HISPANO", "REGATAS")

Probability_by_user ("LIBERTAD", "PEÑAROL")

8- After regular season
After the regular season come the playoffs, in the first round Ferro and Comunicaciones will H2H, we know the probability of each team and with a binomial distribution we can see that the most probable result for the tie its 3-2.
How did the series end? 3-2 for Ferro
Probability_by_user ("FERRO", "COMUNICACIONES")

Probability_by_user ("COMUNICACIONES", "FERRO")

[1] "Table of probability"
Conclusion
The machine learning and the data science help to know the winner in a match, the analysis of the stats it´s a very helpful tool, not only for the coaches.
This example serves to demonstrate the power of predictions, in a normal season one should use the first 5 or 6 dates to complete the information, then one could start predicting what will happen.
The previous statistics will show where the weaknesses of one are and the strengths of another, which differentiates them. How much one suffers from traveling and how the other is strengthened by being local.
Finally, all this together, gives a prediction of who should be the winner, how favorite one is over another, and can even explain why a bump will happen.
Obviously, such a tool should be retrained every few months and that it is designed for the Argentine league, with the intricacies of each league, if these algorithms were used in another country, it could lower the level of effectiveness.
Obviously in sports there are surprises, injuries or bad days that can change the events of a game. However, believing that you cannot previously analyze a match, and predict the future, is ridiculous
LS0tDQp0aXRsZTogIlByZWRpY3RpbmcgYmFza2V0YmFsbCByZXN1bHQiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KYGBge3IgZWNobyA9IEZBTFNFfQ0Kc2V0d2QoIkM6L1VzZXJzL01lcm8vRGVza3RvcC9SL2xuYl9kYXRhXzIwMThfMjAxOSIpDQpsaWJyYXJ5KHJqc29uKQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeSh0aW55dGV4KQ0KbGlicmFyeShrbml0cikNCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KHdvcmRjbG91ZCkNCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShsdWJyaWRhdGUpDQpsaWJyYXJ5KHNjYWxlcykNCmxpYnJhcnkodGlkeXRleHQpDQpsaWJyYXJ5KHN5dXpoZXQpDQpsaWJyYXJ5KHRva2VuaXplcnMpDQpsaWJyYXJ5KHRleHRzaGFwZSkNCmxpYnJhcnkocnR3ZWV0KQ0KbGlicmFyeShyZWFkcikNCmxpYnJhcnkodGlkeXIpDQpsaWJyYXJ5KFJDb2xvckJyZXdlcikNCmxpYnJhcnkoZ3JpZEV4dHJhKQ0KbGlicmFyeSh0bSkNCmxpYnJhcnkoem9vKQ0KbGlicmFyeShjb3JycGxvdCkNCmxpYnJhcnkoUk9DUikNCmxpYnJhcnkocnBhcnQpDQpsaWJyYXJ5KHJwYXJ0LnBsb3QpDQpsaWJyYXJ5KHJlc2hhcGUyKQ0KbGlicmFyeShnZ3RoZW1lcykNCmxvYWQoImltYWdlLlJEYXRhIikNCnRlbWEyIDwtDQogIHRoZW1lKHRleHQgPSBlbGVtZW50X3RleHQoZmFtaWx5ID0gInNhbnMiLCBzaXplID0gMTApLA0KICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3IgPSAiI2NjY2NjYyIsIGZpbGwgPSBOQSksDQogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJ3aGl0ZSIpLA0KICAgICAgICBwYW5lbC5ncmlkLm1ham9yID0gIGVsZW1lbnRfbGluZShjb2xvciA9ICIjZGRkZGRkIiksDQogICAgICAgIHBhbmVsLmdyaWQubWlub3IgPSAgZWxlbWVudF9saW5lKGNvbG9yID0gIiNlZWVlZWUiKSwNCiAgICAgICAgYXhpcy50aWNrcyA9IGVsZW1lbnRfbGluZShjb2xvdXIgPSAiI2NjY2NjYyIpLA0KICAgICAgICBzdHJpcC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGNvbG9yID0gIiNjY2NjY2MiLCBmaWxsID0gIiNlZWVlZWUiKSwNCiAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gInRvcCIpDQoNCnRlbWExIDwtIHRoZW1lX21pbmltYWwoKSArDQogIHRoZW1lKHRleHQgPSBlbGVtZW50X3RleHQoZmFtaWx5ID0gInNlcmlmIiksDQogICAgICAgIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2JsYW5rKCksDQogICAgICAgIHN0cmlwLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICIjRUJFQkVCIiwgY29sb3VyID0gTkEpLA0KICAgICAgICBsZWdlbmQuYm94LmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICIjRUJFQkVCIiwgY29sb3VyID0gTkEpKQ0KYGBgDQoNCjxjZW50ZXI+IDxzcGFuIHN0eWxlPSJjb2xvcjogI0NGNTMwMDsiPiA8aDI+IFByZWRpY3QgYmFza2V0IHNjb3JlIHdpdGggTWFjaGluZSBMZWFybmluZyA8L2gyPiA8L3NwYW4+IDwvY2VudGVyPg0KDQpUaGUgcHVycG9zZSBvZiB0aGlzIG1hY2hpbmUgbGVhcm5pbmcgaXMgdG8gcHJlZGljdCB0aGUgbWF0Y2ggd2lubmVyIG9mIGRpZmZlcmVudCBtYXRjaCB3aXRoIGEgYWxnb3JpdGhtLiBUbyBkbyB0aGlzIHdlIGhhdmUgdmFyaW91cyBKU09OIHdpdGggdGhlIGRhdGEgZnJvbSAzNzggb2YgdGhlIDM4MCBtYXRjaGVzIHBsYXllZCBpbiB0aGUgcmVndWxhciBwaGFzZSBpbiB0aGUgTE5CIDIwMTgvMTkuDQoNCldlIHdpbGwgdHJhaW4gYSBhbGdvcml0aG0gd2l0aCAzMDAgbWF0Y2hlcyBhbmQgcHJldGVuZCB0byBwcmVkaWN0IHRoZSBvdGhlciA3OCBtYXRjaGVzLCBpbiB0aGlzIGNhc2UgdGhlIHNlYXNvbiBpdMK0cyBjb21wbGV0ZSwgYnV0IGR1cmluZyB0aGUgc2Vhc29uIHRpbWUgdGhlIERCIG9mIG1hdGNoIHdpbGwgZmlsbCB1cCB0aHJvdWdoIHRoZSBnYW1lcywgYW5kIHRoZSB1c2VyIGNhbiBwcmVkaWN0IHRoZSBmdXR1cmUgZ2FtZSBhZnRlciB0aGUgZGF5IHdpdGggdGhlIHNlYXNvbnMgc3RhdHMuDQoNCkFsc28gd2lsbCB1c2UgdGhpcyBjb2RlIHRvIGEgc3RhdHMgdmlld2VyLCB0aGF0IGkgcmVwZWF0LCBpbiBhIG5vcm1hbCBzZWFzb24gd2lsbCBjaGFuZ2UgYWZ0ZXIgZWFjaCBtYXRjaC4gVW5mb3J0dW5hdGVseSBpIGRvbsK0dCBoYXZlIHRoZSBkYXRlIG9mIGVhY2ggbWF0Y2gsIHdoaWNoIEkgd291bGQgdXNlIHRvIGNyZWF0ZSBzb21lIGV4dHJhIGZpbHRlcg0KDQoNCg0KPGNlbnRlcj4gPHNwYW4gc3R5bGU9ImNvbG9yOiAjOWQwMjA4OyI+IDxoMz4gMS0gTG9hZCB0aGUgSlNPTiBmaWxlcyA8L2gzPiA8L3NwYW4+IDwvY2VudGVyPg0KDQpJIGhhdmUgMzc4IEpTT04gZmlsZXMsIGluIHNlcXVlbmNlIHRoZSBuYW1lIGZyb20gIjEuanNvbiIgdG8gIjM3OC5qc29uIjoNCg0KICAtICoqSlNPTl9MaXN0Kio6IENyZWF0ZSBhIGxpc3QgYW5kIHB1dCBhbGwgdGhlIG5hbWVzIG9mIHRoZSBmaWxlcy4NCiAgDQogIC0gKipyanNvbioqOiBMb2FkIHRoZSBKU09OIGZpbGVzIHdpdGggdGhlIHNwZWNpZmljIHBhY2thZ2UgZm9yIFIuDQogIA0KICAtICoqREZfaG9tZSBhbmQgREZfYXdheSoqOiBCb3RoIERGIHdpbGwgYmUgdXNlIHRvIGZpbGwgdGhlIHN0YXRzIGluZm9ybWF0aW9uLg0KICANCiAgDQpgYGB7ciAyIGNhcmdhciBKU09OfQ0KSlNPTl9MaXN0IDwtIGxpc3QoKQ0KREZfaG9tZSA8LWRhdGEuZnJhbWUgKCkNCkRGX2F3YXkgPC0gZGF0YS5mcmFtZSAoKQ0KDQoNCmZvciAoaSBpbiAxOjM3OCkgew0KSWRfSlNPTiA9IChwYXN0ZTAoaSwiLmpzb24iKSkNCkpTT05fTGlzdFtbaV1dPSByanNvbjo6ZnJvbUpTT04oZmlsZSA9IElkX0pTT04sIHNpbXBsaWZ5ID0gRikNCn0NCmBgYA0KDQo8Y2VudGVyPiA8c3BhbiBzdHlsZT0iY29sb3I6ICM5ZDAyMDg7Ij4gPGgzPiAyLSBMb2FkIGRhdGEgZnJvbSBob21lIGFuZCBhd2F5IDwvaDM+IDwvc3Bhbj4gPC9jZW50ZXI+DQoNCkludG8gdGhlIGpzb24gaXTCtHMgYSBzdWJsaXN0IGNhbGwgdG0gKHRvdGFsIG1hdGNoKSBhbmQgaW50byB0aGUgbGlzdCwgdGhlIHN1Ymxpc3QgMSBpcyBmb3IgaG9tZSBzdGF0cyBhbmQgdGhlIHN1Ymxpc3QgMiBpcyBmb3IgYXdheSwgaSB3YW50IHRvIHVzZSBzb21lIGZpZWxkcyBmcm9tIHRoaXMgbGlzdHMuDQoNCk5hbWUsIFNjb3JlLCBUd28gcG9pbnRzLCBUaHJlZSBwb2ludHMsIEZyZWUgVGhyb3csIEZpZWxkIGdvYWxzLCBFZmZpY2llbmN5LCBBc3Npc3RzLCBSZWJvdW5kcywgU3RlYWxzLCBMb3N0LCBQb2ludHMgaW4gdGhlIHBhaW50LCBGYXN0IEJyZWFrcywgVHVybm92ZXJzLCBCZW5jaCBwb2ludHMgYW5kIEJsb2Nrcy4NCg0KQWxzbyBwdXQgdGhlIGhvbWUgY29uZGl0aW9uIHRvIHRoZSBzdWJsaXN0IDEsIGF3YXkgY29uZGl0aW9uIHRvIHRoZSBzdWJsaXN0IDIgYW5kIGNyZWF0ZSBhIG1hdGNoIGlkIGZvciBlYWNoIG1hdGNoLg0KDQpBdCB0aGUgZW5kIHdlIGRlbGV0ZSBhbGwgc2luZ2xlIG9ic2VydmF0aW9ucy4NCg0KDQpgYGB7ciAzIHRyYWVyIGRhdG9zIGJ1c2NhZG9zIHBhcmEgaG9tZX0NCmZvciAoaSBpbiAxOjM3OCkgew0KbmFtZT1hcy5kYXRhLmZyYW1lKEpTT05fTGlzdFtbaV1dJHRtJCIxIiRuYW1lLCBucm93ID0gMSkNCnNjb3JlPWFzLmRhdGEuZnJhbWUoSlNPTl9MaXN0W1tpXV0kdG0kIjEiJHNjb3JlLCBucm93ID0gMSkNCnRvdF9zRmllbGRHb2Fsc01hZGU9YXMuZGF0YS5mcmFtZShKU09OX0xpc3RbW2ldXSR0bSQiMSIkdG90X3NGaWVsZEdvYWxzTWFkZSwgbnJvdyA9IDEpDQp0b3Rfc0ZpZWxkR29hbHNBdHRlbXB0ZWQ9YXMuZGF0YS5mcmFtZShKU09OX0xpc3RbW2ldXSR0bSQiMSIkdG90X3NGaWVsZEdvYWxzQXR0ZW1wdGVkLCBucm93ID0gMSkNCnRvdF9zVHdvUG9pbnRlcnNNYWRlPWFzLmRhdGEuZnJhbWUoSlNPTl9MaXN0W1tpXV0kdG0kIjEiJHRvdF9zVHdvUG9pbnRlcnNNYWRlLCBucm93ID0gMSkNCnRvdF9zVHdvUG9pbnRlcnNBdHRlbXB0ZWQ9YXMuZGF0YS5mcmFtZShKU09OX0xpc3RbW2ldXSR0bSQiMSIkdG90X3NUd29Qb2ludGVyc0F0dGVtcHRlZCwgbnJvdyA9IDEpDQp0b3Rfc1RocmVlUG9pbnRlcnNNYWRlPWFzLmRhdGEuZnJhbWUoSlNPTl9MaXN0W1tpXV0kdG0kIjEiJHRvdF9zVGhyZWVQb2ludGVyc01hZGUsIG5yb3cgPSAxKQ0KdG90X3NUaHJlZVBvaW50ZXJzQXR0ZW1wdGVkPWFzLmRhdGEuZnJhbWUoSlNPTl9MaXN0W1tpXV0kdG0kIjEiJHRvdF9zVGhyZWVQb2ludGVyc0F0dGVtcHRlZCwgbnJvdyA9IDEpDQp0b3Rfc0ZyZWVUaHJvd3NNYWRlPWFzLmRhdGEuZnJhbWUoSlNPTl9MaXN0W1tpXV0kdG0kIjEiJHRvdF9zRnJlZVRocm93c01hZGUsIG5yb3cgPSAxKQ0KdG90X3NGcmVlVGhyb3dzQXR0ZW1wdGVkPWFzLmRhdGEuZnJhbWUoSlNPTl9MaXN0W1tpXV0kdG0kIjEiJHRvdF9zRnJlZVRocm93c0F0dGVtcHRlZCwgbnJvdyA9IDEpDQp0b3Rfc1JlYm91bmRzRGVmZW5zaXZlPWFzLmRhdGEuZnJhbWUoSlNPTl9MaXN0W1tpXV0kdG0kIjEiJHRvdF9zUmVib3VuZHNEZWZlbnNpdmUsIG5yb3cgPSAxKQ0KdG90X3NSZWJvdW5kc09mZmVuc2l2ZT1hcy5kYXRhLmZyYW1lKEpTT05fTGlzdFtbaV1dJHRtJCIxIiR0b3Rfc1JlYm91bmRzT2ZmZW5zaXZlLCBucm93ID0gMSkNCnRvdF9zUmVib3VuZHNUb3RhbD1hcy5kYXRhLmZyYW1lKEpTT05fTGlzdFtbaV1dJHRtJCIxIiR0b3Rfc1JlYm91bmRzVG90YWwsIG5yb3cgPSAxKQ0KdG90X3NBc3Npc3RzPWFzLmRhdGEuZnJhbWUoSlNPTl9MaXN0W1tpXV0kdG0kIjEiJHRvdF9zQXNzaXN0cywgbnJvdyA9IDEpDQp0b3Rfc0Jsb2Nrcz1hcy5kYXRhLmZyYW1lKEpTT05fTGlzdFtbaV1dJHRtJCIxIiR0b3Rfc0Jsb2NrcywgbnJvdyA9IDEpDQp0b3Rfc1R1cm5vdmVycz1hcy5kYXRhLmZyYW1lKEpTT05fTGlzdFtbaV1dJHRtJCIxIiR0b3Rfc1R1cm5vdmVycywgbnJvdyA9IDEpDQp0b3Rfc0ZvdWxzUGVyc29uYWw9YXMuZGF0YS5mcmFtZShKU09OX0xpc3RbW2ldXSR0bSQiMSIkdG90X3NGb3Vsc1BlcnNvbmFsLCBucm93ID0gMSkNCnRvdF9zUG9pbnRzSW5UaGVQYWludD1hcy5kYXRhLmZyYW1lKEpTT05fTGlzdFtbaV1dJHRtJCIxIiR0b3Rfc1BvaW50c0luVGhlUGFpbnQsIG5yb3cgPSAxKQ0KdG90X3NQb2ludHNTZWNvbmRDaGFuY2U9YXMuZGF0YS5mcmFtZShKU09OX0xpc3RbW2ldXSR0bSQiMSIkdG90X3NQb2ludHNTZWNvbmRDaGFuY2UsIG5yb3cgPSAxKQ0KdG90X3NQb2ludHNGcm9tVHVybm92ZXJzPWFzLmRhdGEuZnJhbWUoSlNPTl9MaXN0W1tpXV0kdG0kIjEiJHRvdF9zUG9pbnRzRnJvbVR1cm5vdmVycywgbnJvdyA9IDEpDQp0b3Rfc0JlbmNoUG9pbnRzPWFzLmRhdGEuZnJhbWUoSlNPTl9MaXN0W1tpXV0kdG0kIjEiJHRvdF9zQmVuY2hQb2ludHMsIG5yb3cgPSAxKQ0KdG90X3NQb2ludHNGYXN0QnJlYWs9YXMuZGF0YS5mcmFtZShKU09OX0xpc3RbW2ldXSR0bSQiMSIkdG90X3NQb2ludHNGYXN0QnJlYWssIG5yb3cgPSAxKQ0KdG90X3NTdGVhbHM9YXMuZGF0YS5mcmFtZShKU09OX0xpc3RbW2ldXSR0bSQiMSIkdG90X3NTdGVhbHMsIG5yb3cgPSAxKQ0KdG90X2VmZl81PWFzLmRhdGEuZnJhbWUoSlNPTl9MaXN0W1tpXV0kdG0kIjEiJHRvdF9lZmZfNSwgbnJvdyA9IDEpDQptYXRjaF9pZCA9IGkNCmNvbmRpY2lvbiA9ICJob21lIg0KT2JzZXJ2YWNpb24gPSBjYmluZChuYW1lLHNjb3JlLHRvdF9zRmllbGRHb2Fsc01hZGUsdG90X3NGaWVsZEdvYWxzQXR0ZW1wdGVkLHRvdF9zVHdvUG9pbnRlcnNNYWRlLHRvdF9zVHdvUG9pbnRlcnNBdHRlbXB0ZWQsdG90X3NUaHJlZVBvaW50ZXJzTWFkZSx0b3Rfc1RocmVlUG9pbnRlcnNBdHRlbXB0ZWQsdG90X3NGcmVlVGhyb3dzTWFkZSx0b3Rfc0ZyZWVUaHJvd3NBdHRlbXB0ZWQsdG90X3NSZWJvdW5kc0RlZmVuc2l2ZSx0b3Rfc1JlYm91bmRzT2ZmZW5zaXZlLHRvdF9zUmVib3VuZHNUb3RhbCx0b3Rfc0Fzc2lzdHMsdG90X3NCbG9ja3MsdG90X3NUdXJub3ZlcnMsdG90X3NGb3Vsc1BlcnNvbmFsLHRvdF9zUG9pbnRzSW5UaGVQYWludCx0b3Rfc1BvaW50c1NlY29uZENoYW5jZSx0b3Rfc1BvaW50c0Zyb21UdXJub3ZlcnMsdG90X3NCZW5jaFBvaW50cyx0b3Rfc1BvaW50c0Zhc3RCcmVhayx0b3Rfc1N0ZWFscyx0b3RfZWZmXzUsIG1hdGNoX2lkLCBjb25kaWNpb24gDQopDQpERl9ob21lPSByYmluZChERl9ob21lLCBPYnNlcnZhY2lvbiApIA0KfQ0KYGBgDQoNCmBgYHtyIDQgYnVzY2FyIGRhdG9zIHBhcmEgYXdheX0NCmZvciAoaSBpbiAxOjM3OCkgew0KbmFtZT1hcy5kYXRhLmZyYW1lKEpTT05fTGlzdFtbaV1dJHRtJCIyIiRuYW1lLCBucm93ID0gMSkNCnNjb3JlPWFzLmRhdGEuZnJhbWUoSlNPTl9MaXN0W1tpXV0kdG0kIjIiJHNjb3JlLCBucm93ID0gMSkNCnRvdF9zRmllbGRHb2Fsc01hZGU9YXMuZGF0YS5mcmFtZShKU09OX0xpc3RbW2ldXSR0bSQiMiIkdG90X3NGaWVsZEdvYWxzTWFkZSwgbnJvdyA9IDEpDQp0b3Rfc0ZpZWxkR29hbHNBdHRlbXB0ZWQ9YXMuZGF0YS5mcmFtZShKU09OX0xpc3RbW2ldXSR0bSQiMiIkdG90X3NGaWVsZEdvYWxzQXR0ZW1wdGVkLCBucm93ID0gMSkNCnRvdF9zVHdvUG9pbnRlcnNNYWRlPWFzLmRhdGEuZnJhbWUoSlNPTl9MaXN0W1tpXV0kdG0kIjIiJHRvdF9zVHdvUG9pbnRlcnNNYWRlLCBucm93ID0gMSkNCnRvdF9zVHdvUG9pbnRlcnNBdHRlbXB0ZWQ9YXMuZGF0YS5mcmFtZShKU09OX0xpc3RbW2ldXSR0bSQiMiIkdG90X3NUd29Qb2ludGVyc0F0dGVtcHRlZCwgbnJvdyA9IDEpDQp0b3Rfc1RocmVlUG9pbnRlcnNNYWRlPWFzLmRhdGEuZnJhbWUoSlNPTl9MaXN0W1tpXV0kdG0kIjIiJHRvdF9zVGhyZWVQb2ludGVyc01hZGUsIG5yb3cgPSAxKQ0KdG90X3NUaHJlZVBvaW50ZXJzQXR0ZW1wdGVkPWFzLmRhdGEuZnJhbWUoSlNPTl9MaXN0W1tpXV0kdG0kIjIiJHRvdF9zVGhyZWVQb2ludGVyc0F0dGVtcHRlZCwgbnJvdyA9IDEpDQp0b3Rfc0ZyZWVUaHJvd3NNYWRlPWFzLmRhdGEuZnJhbWUoSlNPTl9MaXN0W1tpXV0kdG0kIjIiJHRvdF9zRnJlZVRocm93c01hZGUsIG5yb3cgPSAxKQ0KdG90X3NGcmVlVGhyb3dzQXR0ZW1wdGVkPWFzLmRhdGEuZnJhbWUoSlNPTl9MaXN0W1tpXV0kdG0kIjIiJHRvdF9zRnJlZVRocm93c0F0dGVtcHRlZCwgbnJvdyA9IDEpDQp0b3Rfc1JlYm91bmRzRGVmZW5zaXZlPWFzLmRhdGEuZnJhbWUoSlNPTl9MaXN0W1tpXV0kdG0kIjIiJHRvdF9zUmVib3VuZHNEZWZlbnNpdmUsIG5yb3cgPSAxKQ0KdG90X3NSZWJvdW5kc09mZmVuc2l2ZT1hcy5kYXRhLmZyYW1lKEpTT05fTGlzdFtbaV1dJHRtJCIyIiR0b3Rfc1JlYm91bmRzT2ZmZW5zaXZlLCBucm93ID0gMSkNCnRvdF9zUmVib3VuZHNUb3RhbD1hcy5kYXRhLmZyYW1lKEpTT05fTGlzdFtbaV1dJHRtJCIyIiR0b3Rfc1JlYm91bmRzVG90YWwsIG5yb3cgPSAxKQ0KdG90X3NBc3Npc3RzPWFzLmRhdGEuZnJhbWUoSlNPTl9MaXN0W1tpXV0kdG0kIjIiJHRvdF9zQXNzaXN0cywgbnJvdyA9IDEpDQp0b3Rfc0Jsb2Nrcz1hcy5kYXRhLmZyYW1lKEpTT05fTGlzdFtbaV1dJHRtJCIyIiR0b3Rfc0Jsb2NrcywgbnJvdyA9IDEpDQp0b3Rfc1R1cm5vdmVycz1hcy5kYXRhLmZyYW1lKEpTT05fTGlzdFtbaV1dJHRtJCIyIiR0b3Rfc1R1cm5vdmVycywgbnJvdyA9IDEpDQp0b3Rfc0ZvdWxzUGVyc29uYWw9YXMuZGF0YS5mcmFtZShKU09OX0xpc3RbW2ldXSR0bSQiMiIkdG90X3NGb3Vsc1BlcnNvbmFsLCBucm93ID0gMSkNCnRvdF9zUG9pbnRzSW5UaGVQYWludD1hcy5kYXRhLmZyYW1lKEpTT05fTGlzdFtbaV1dJHRtJCIyIiR0b3Rfc1BvaW50c0luVGhlUGFpbnQsIG5yb3cgPSAxKQ0KdG90X3NQb2ludHNTZWNvbmRDaGFuY2U9YXMuZGF0YS5mcmFtZShKU09OX0xpc3RbW2ldXSR0bSQiMiIkdG90X3NQb2ludHNTZWNvbmRDaGFuY2UsIG5yb3cgPSAxKQ0KdG90X3NQb2ludHNGcm9tVHVybm92ZXJzPWFzLmRhdGEuZnJhbWUoSlNPTl9MaXN0W1tpXV0kdG0kIjIiJHRvdF9zUG9pbnRzRnJvbVR1cm5vdmVycywgbnJvdyA9IDEpDQp0b3Rfc0JlbmNoUG9pbnRzPWFzLmRhdGEuZnJhbWUoSlNPTl9MaXN0W1tpXV0kdG0kIjIiJHRvdF9zQmVuY2hQb2ludHMsIG5yb3cgPSAxKQ0KdG90X3NQb2ludHNGYXN0QnJlYWs9YXMuZGF0YS5mcmFtZShKU09OX0xpc3RbW2ldXSR0bSQiMiIkdG90X3NQb2ludHNGYXN0QnJlYWssIG5yb3cgPSAxKQ0KdG90X3NTdGVhbHM9YXMuZGF0YS5mcmFtZShKU09OX0xpc3RbW2ldXSR0bSQiMiIkdG90X3NTdGVhbHMsIG5yb3cgPSAxKQ0KdG90X2VmZl81PWFzLmRhdGEuZnJhbWUoSlNPTl9MaXN0W1tpXV0kdG0kIjIiJHRvdF9lZmZfNSwgbnJvdyA9IDEpDQptYXRjaF9pZCA9IGkNCmNvbmRpY2lvbiA9ICJhd2F5Ig0KT2JzZXJ2YWNpb24gPSBjYmluZChuYW1lLHNjb3JlLHRvdF9zRmllbGRHb2Fsc01hZGUsdG90X3NGaWVsZEdvYWxzQXR0ZW1wdGVkLHRvdF9zVHdvUG9pbnRlcnNNYWRlLHRvdF9zVHdvUG9pbnRlcnNBdHRlbXB0ZWQsdG90X3NUaHJlZVBvaW50ZXJzTWFkZSx0b3Rfc1RocmVlUG9pbnRlcnNBdHRlbXB0ZWQsdG90X3NGcmVlVGhyb3dzTWFkZSx0b3Rfc0ZyZWVUaHJvd3NBdHRlbXB0ZWQsdG90X3NSZWJvdW5kc0RlZmVuc2l2ZSx0b3Rfc1JlYm91bmRzT2ZmZW5zaXZlLHRvdF9zUmVib3VuZHNUb3RhbCx0b3Rfc0Fzc2lzdHMsdG90X3NCbG9ja3MsdG90X3NUdXJub3ZlcnMsdG90X3NGb3Vsc1BlcnNvbmFsLHRvdF9zUG9pbnRzSW5UaGVQYWludCx0b3Rfc1BvaW50c1NlY29uZENoYW5jZSx0b3Rfc1BvaW50c0Zyb21UdXJub3ZlcnMsdG90X3NCZW5jaFBvaW50cyx0b3Rfc1BvaW50c0Zhc3RCcmVhayx0b3Rfc1N0ZWFscywgdG90X2VmZl81LG1hdGNoX2lkLCBjb25kaWNpb24gDQopDQpERl9hd2F5PSByYmluZChERl9hd2F5LCBPYnNlcnZhY2lvbiApIA0KfQ0KYGBgDQoNCmBgYHtyIDUgcmVtb3ZlciB0YWJsYXMgaW5kaXZpZHVhbGVzfQ0Kcm0gKE9ic2VydmFjaW9uLCBuYW1lLHNjb3JlLHRvdF9zRmllbGRHb2Fsc01hZGUsdG90X3NGaWVsZEdvYWxzQXR0ZW1wdGVkLHRvdF9zVHdvUG9pbnRlcnNNYWRlLHRvdF9zVHdvUG9pbnRlcnNBdHRlbXB0ZWQsdG90X3NUaHJlZVBvaW50ZXJzTWFkZSx0b3Rfc1RocmVlUG9pbnRlcnNBdHRlbXB0ZWQsdG90X3NGcmVlVGhyb3dzTWFkZSx0b3Rfc0ZyZWVUaHJvd3NBdHRlbXB0ZWQsdG90X3NSZWJvdW5kc0RlZmVuc2l2ZSx0b3Rfc1JlYm91bmRzT2ZmZW5zaXZlLHRvdF9zUmVib3VuZHNUb3RhbCx0b3Rfc0Fzc2lzdHMsdG90X3NCbG9ja3MsdG90X3NUdXJub3ZlcnMsdG90X3NGb3Vsc1BlcnNvbmFsLHRvdF9zUG9pbnRzSW5UaGVQYWludCx0b3Rfc1BvaW50c1NlY29uZENoYW5jZSx0b3Rfc1BvaW50c0Zyb21UdXJub3ZlcnMsdG90X3NCZW5jaFBvaW50cyx0b3Rfc1BvaW50c0Zhc3RCcmVhayx0b3Rfc1N0ZWFscyxtYXRjaF9pZCwgY29uZGljaW9uLCB0b3RfZWZmXzUgKQ0KDQpoZWFkKERGX2hvbWUsIDUpDQpgYGANCg0KDQo8Y2VudGVyPiA8c3BhbiBzdHlsZT0iY29sb3I6ICM5ZDAyMDg7Ij4gPGgzPiAzLSBSZW5hbWUgdGhlIGNvbHVtbnMgPC9oMz4gPC9zcGFuPiA8L2NlbnRlcj4NCg0KSSBjaGFuZ2UgdGhlIG5hbWUgdG8gdGhlIGRpZmZlcmVudCBjb2x1bW5zIGZvciB0aG9zZSB0aGF0IGl0wrRzIGVhc3kgdG8gcmVtaW5kZXIgZm9yIG1lLg0KDQoNCmBgYHtyIDYgY2FtYmlhciBub21icmUgYSBsYXMgY29sdW1uYXN9DQpuYW1lcyhERl9ob21lKSA8LSBjICgibmFtZSIsDQoic2NvcmUiLA0KIkZHIiwNCiJGR0EiLA0KIlAyIiwNCiJQMkEiLA0KIlAzIiwNCiJQM0EiLA0KIkZUIiwNCiJGVEEiLA0KIkRSQlMiLA0KIk9SQlMiLA0KIlJCUyIsDQoiQVNTIiwNCiJCTE8iLA0KIlRPIiwNCiJQRiIsDQoiUFAiLA0KIk9QMiIsDQoiUEZUIiwNCiJCUCIsDQoiRkIiLA0KIlNURSIsDQoiRUZGIiwNCiJtYXRjaF9pZCIsDQoiY29uZGl0aW9uIikNCg0KbmFtZXMoREZfYXdheSkgPC0gYyAoIm5hbWUiLA0KInNjb3JlIiwNCiJGRyIsDQoiRkdBIiwNCiJQMiIsDQoiUDJBIiwNCiJQMyIsDQoiUDNBIiwNCiJGVCIsDQoiRlRBIiwNCiJEUkJTIiwNCiJPUkJTIiwNCiJSQlMiLA0KIkFTUyIsDQoiQkxPIiwNCiJUTyIsDQoiUEYiLA0KIlBQIiwNCiJPUDIiLA0KIlBGVCIsDQoiQlAiLA0KIkZCIiwNCiJTVEUiLA0KIkVGRiIsDQoibWF0Y2hfaWQiLA0KImNvbmRpdGlvbiIpDQoNCmhlYWQgKERGX2F3YXksIDUpDQpgYGANCg0KDQo8Y2VudGVyPiA8c3BhbiBzdHlsZT0iY29sb3I6ICM5ZDAyMDg7Ij4gPGgzPiA0LSBTdW1tYXJ5IHRoZSBERiA8L2gzPiA8L3NwYW4+IDwvY2VudGVyPg0KDQoNCkkgc3VtbWFyeSBib3RoIERCIGFuZCBzZWFyY2ggaWYgd2VyZSBzb21lIE5BcywgaWYgd2Ugc2VlIHNvbWUgdmFsdWVzIGFyZSBuZWFyIHRvIGJlIG91dGxpZXJzLCBidXQgaSBkaWRuwrR0IHJlbW92ZSBiZWNhdXNlIGFyZSBub3JtYWwgZm9yIGJhc2tldCwgaW4gc3BlY2lhbCB3aXRoIGEgb3ZlcnRpbWUgbWF0Y2guDQoNCg0KYGBge3IgcmV2aXNvIGxvcyBOQSB5IG91dGxpZXJzfQ0KYW55TkEoREZfYXdheSkNCmFueU5BKERGX2hvbWUpDQoNCnN1bW1hcnkoREZfYXdheSkNCg0Kc3VtbWFyeShERl9ob21lKQ0KYGBgDQoNCg0KPGNlbnRlcj4gPHNwYW4gc3R5bGU9ImNvbG9yOiAjOWQwMjA4OyI+IDxoMz4gNS0gQ3JlYXRlIERGIGZvciB0ZWFtcyA8L2gzPiA8L3NwYW4+IDwvY2VudGVyPg0KDQoNCkpvaW4gdGhlIGhvbWUgYW5kIGF3YXkgREYgYnkgbWF0Y2ggSUQgd2lsbCBjcmVhdGUgYSBuZXcgREYgd2l0aCBhbGwgdGhlIG1hdGNoIHN0YXRzIGFuZCB0aGUgcG9zc2liaWxpdHkgdG8ga25vdyB3aG8gd2FzIHRoZSB3aW5uZXIuDQoNCkJpbmQgYm90aCBERiB3aXRoIHNpbWlsYXIgbnVtYmVycyBvZiBjb2x1bW5zIGFuZCBvYnNlcnZhdGlvbiB3aWxsIGNyZWF0ZSBhIERGIHdoZXJlIGlzIGFsbCB0aGUgZGF0YSBmb3IgZWFjaCB0ZWFtLg0KDQoNCmBgYHtyIDcgY3JlYXIgREYgY29tcGxldG8geSBwb3IgZXF1aXBvfQ0KREZfVG90YWwgPC0gZGF0YS5mcmFtZSgpDQpERl9Ub3RhbCA8LSAgIA0KIERGX2F3YXklPiUNCmlubmVyX2pvaW4oREZfaG9tZSwgLiwgYnkgPSAibWF0Y2hfaWQiKSANCg0KREZfVGVhbSA8LSBkYXRhLmZyYW1lKCkNCkRGX1RlYW0gPC0gcmJpbmQoREZfaG9tZSxERl9hd2F5KQ0KDQp0YWlsIChERl9UZWFtLCAxMCkNCg0KYGBgDQoNCjxjZW50ZXI+IDxzcGFuIHN0eWxlPSJjb2xvcjogIzlkMDIwODsiPiA8aDM+IDYtIERldGVybWluZSB3aW5uZXIgPC9oMz4gPC9zcGFuPiA8L2NlbnRlcj4NCg0KRGV0ZXJtaW5lIHdobyB3aW4gZWFjaCBtYXRjaCB3aWxsIGNyZWF0ZSB0d28gY2xhc3MsIHRoZSBjbGFzczEgdGhhdCBpdMK0cyAxIHdoZW4gd2luIHRoZSBob21lIHRlYW0gYW5kIDAgd2hlbiB3aW4gdGhlIGF3YXkgdGVhbSwgYW5kIGNsYXNzMiB0aGF0IGl0wrRzIHRoZSBpbnZlcnNlLiANCg0KDQpgYGB7ciA4IGRldGVybWluYXIgZ2FuYWRvcn0NCkRGX1Njb3JlIDwtIGRhdGEuZnJhbWUoKQ0KREZfU2NvcmUgPC0gIERGX1RvdGFsJT4lDQogIG11dGF0ZSggd2lubmVyPSBpZmVsc2UoREZfVG90YWwkc2NvcmUueCA+IERGX1RvdGFsJHNjb3JlLnksIERGX1RvdGFsJG5hbWUueCwgREZfVG90YWwkbmFtZS55KSkgJT4lDQogIHNlbGVjdChtYXRjaF9pZCwgd2lubmVyKQ0KDQpERl9Ub3RhbCRjbGFzczEgPC0gaWZlbHNlKERGX1RvdGFsJHNjb3JlLnggPiBERl9Ub3RhbCRzY29yZS55LCAxLCAwKQ0KREZfVG90YWwkY2xhc3MyIDwtIGlmZWxzZShERl9Ub3RhbCRzY29yZS54ID4gREZfVG90YWwkc2NvcmUueSwgMCwgMSkNCg0KREZfVGVhbSA8LSBERl9UZWFtICU+JQ0KbGVmdF9qb2luKERGX1Njb3JlLCAuLCBieSA9ICJtYXRjaF9pZCIpDQoNCmhlYWQoREZfU2NvcmUsIDUpDQpgYGANCg0KDQo8Y2VudGVyPiA8c3BhbiBzdHlsZT0iY29sb3I6ICM5ZDAyMDg7Ij4gPGgzPiA3LSBDcmVhdGUgdGhlIHN0YXRzIHRhYmxlIDwvaDM+IDwvc3Bhbj4gPC9jZW50ZXI+DQoNCg0KVGhlIGZpcnN0IHdoZW4gY3JlYXRlIHRoZSBzdGF0cyB0YWJsZSBpdMK0cyBncm91cCB0aGUgdGVhbXMgdG8gaGF2ZSBhIGluZGl2aWR1YWwgb2JzZXJ2YXRpb24gb2YgZWFjaCBvbmUgYW5kIGtub3cgaG93IG1hbnkgbWF0Y2ggcGxheWVkLCBzb21lIHRlYW1zIGFyZSBpbiAzNyBtYXRjaCBiZWNhdXNlIGFyZSBtaXNzaW5nIDIgSlNPTiBmaWxlcy4NCg0KQWxzbyB3ZSBjcmVhdGUgYSB0YWJsZSB3aXRoIHRoZSB3aW5zIGF0IGhvbWUsIGF3YXksIGluIHRvdGFsIGFuZCBhIHJhdGlvIHRoYXQgZGV0ZXJtaW5hdGUgdGhlIHVwZ3JhZGUgd2hlbiBwbGF5cyBpbiBob21lIGFuZCBhIGRvd25ncmFkZSB3aGVuIHBsYXlzIGF3YXkuDQoNCg0KDQpgYGB7ciA5IGNyZWFyIHRhYmxhIGluZGl2aWR1YWwgZXF1aXBvcywgdmljdG9yaWFzIHkgcGFydGlkb3N9DQp0ZWFtc19zdGF0czwtIGRhdGEuZnJhbWUoKQ0KdGVhbXNfc3RhdHMgPC0gREZfVGVhbSAlPiUgDQogIGdyb3VwX2J5KG5hbWUpICU+JSANCiAgY291bnQobmFtZSkgDQoNCg0Kd2luczwtIGRhdGEuZnJhbWUoKQ0Kd2lucyA8LSBERl9UZWFtICU+JSANCiAgZ3JvdXBfYnkod2lubmVyKSAlPiUgDQogIGNvdW50KHdpbm5lcikgJT4lIA0KICBtdXRhdGUobiA9IG4vMikNCg0KbmFtZXMod2lucykgPC0gYyAoIm5hbWUiLCAid2lucyIpDQoNCnRlYW1zX3N0YXRzIDwtIHRlYW1zX3N0YXRzICU+JQ0KaW5uZXJfam9pbih3aW5zLCAuLCBieSA9ICJuYW1lIikNCg0KDQp0ZWFtc19zdGF0cyA8LSB0ZWFtc19zdGF0cyAlPiUgbXV0YXRlKHdpbnNfcmF0aW8gPSB3aW5zL24pDQoNCg0KaG9tZV93aW5zIDwtIGRhdGEuZnJhbWUoKQ0KaG9tZV93aW5zIDwtIERGX1RvdGFsICU+JSANCiAgZ3JvdXBfYnkobmFtZS54LCBjbGFzczEpICU+JSANCiAgZmlsdGVyKGNsYXNzMSA9PSAxKSU+JQ0KICBzdW1tYXJpc2Uod2luc2hvbWU9IHN1bShjbGFzczEpKSAlPiUNCiAgbXV0YXRlKHdpbnNob21lID0gd2luc2hvbWUvMTkpJT4lDQogIHNlbGVjdChuYW1lLngsIHdpbnNob21lKQ0KIA0KbmFtZXMoaG9tZV93aW5zKSA9IGMoIm5hbWUiLCAiaG9tZV9yYXRpbyIpDQoNCnRlYW1zX3N0YXRzIDwtIHRlYW1zX3N0YXRzICU+JQ0KaW5uZXJfam9pbihob21lX3dpbnMsIC4sIGJ5ID0gIm5hbWUiKSANCg0KdGVhbXNfc3RhdHMgPC0gdGVhbXNfc3RhdHMgJT4lIG11dGF0ZShob21lX3dpbnNfdXBncmFkZSA9IChob21lX3JhdGlvL3dpbnNfcmF0aW8pKQ0KDQphd2F5X3dpbnMgPC0gZGF0YS5mcmFtZSgpDQphd2F5X3dpbnMgPC0gREZfVG90YWwgJT4lIA0KICBncm91cF9ieShuYW1lLnksIGNsYXNzMikgJT4lIA0KICBmaWx0ZXIoY2xhc3MyID09IDEpJT4lDQogIHN1bW1hcmlzZSh3aW5zYXdheT0gc3VtKGNsYXNzMikpICU+JQ0KICBtdXRhdGUod2luc2F3YXkgPSB3aW5zYXdheS8xOSklPiUNCiAgc2VsZWN0KG5hbWUueSwgd2luc2F3YXkpDQogDQpuYW1lcyhhd2F5X3dpbnMpID0gYygibmFtZSIsICJhd2F5X3JhdGlvIikNCg0KDQp0ZWFtc19zdGF0cyA8LSB0ZWFtc19zdGF0cyAlPiUNCmlubmVyX2pvaW4oYXdheV93aW5zLCAuLCBieSA9ICJuYW1lIikgDQoNCnRlYW1zX3N0YXRzIDwtIHRlYW1zX3N0YXRzICU+JSBtdXRhdGUoYXdheV93aW5zX2Rvd25ncmFkZSA9IChhd2F5X3JhdGlvL3dpbnNfcmF0aW8pKQ0KDQpwcmludCAoaGVhZCh0ZWFtc19zdGF0cywgNSkpDQpgYGANCg0KDQoNCjxjZW50ZXI+IDxzcGFuIHN0eWxlPSJjb2xvcjogIzlkMDIwODsiPiA8aDM+IDgtIENyZWF0ZSBhIHNob3QgREYgPC9oMz4gPC9zcGFuPiA8L2NlbnRlcj4NCg0KDQpBIERGIHdpdGggYWxsIHRoZSBpbmZvIG9mIHRoZSBzaG90cyBkdXJpbmcgdGhlIHNlYXNvbiwgYW5kIGFsc28gY3JlYXRlIHRoZSBmaWVsZCB3aXRoIHRoZSBwZXJjZW50YWdlIG9mIHNob3RzIG1hZGUgaXTCtHMgdGhlIG5leHQgc3RlcCwgdGhpcyB3aWxsIGJyaW5nIG1vcmUgaW5mb3JtYXRpb24gYW5kIHdpbGwgYmUgam9pbiB3aXRoIHRoZSB0ZWFtIHN0YXRzLg0KDQoNCmBgYHtyIDEwIGNyZWF0ZSBERiBvZiBzaG90c30NCnNob3RzIDwtIGRhdGEuZnJhbWUoKQ0Kc2hvdHMgPC0gREZfVGVhbSAlPiUgDQogIGdyb3VwX2J5KG5hbWUpICU+JSANCiAgc3VtbWFyaXNlKA0KICAgUDMgPSBzdW0oUDMpICwNCiAgUDNBID0gc3VtKFAzQSkgLA0KICAgUDIgPSBzdW0oUDIpICwNCiAgIFAyQSA9IHN1bShQMkEpICwNCiAgICBGVCA9IHN1bShGVCkgLA0KICAgRlRBID0gc3VtKEZUQSkgLA0KICAgRkcgPSBzdW0oRkcpLCANCiAgIEZHQSA9IHN1bShGR0EpICwNCiAgKSANCiANCiAgc2hvdHMgPC0gc2hvdHMgICU+JQ0KICAgIG11dGF0ZSggUDNQID0gUDMgLyBQM0EgKSAlPiUNCiAgICBtdXRhdGUoUDJQID0gUDIgLyBQMkEgKSAlPiUNCiAgICBtdXRhdGUoRlRQID0gRlQgLyBGVEEgKSAlPiUNCiAgICBtdXRhdGUoRkdQID0gRkcgLyBGR0EgKQ0KDQp0ZWFtc19zdGF0cyA8LSB0ZWFtc19zdGF0cyAlPiUNCmlubmVyX2pvaW4oc2hvdHMsIC4sIGJ5ID0gIm5hbWUiKSANCg0KdGFpbCAoc2hvdHMsIDUpDQpgYGANCg0KDQo8Y2VudGVyPiA8c3BhbiBzdHlsZT0iY29sb3I6ICM5ZDAyMDg7Ij4gPGgzPiA5LSBDcmVhdGUgb3RoZXIgc3RhdHMgREYgPC9oMz4gPC9zcGFuPiA8L2NlbnRlcj4NCg0KDQpBIERGIHdpdGggYWxsIHRoZSBpbmZvIG9mIG90aGVyIGR1cmluZyB0aGUgc2Vhc29uLCBpdMK0cyB0aGUgbmV4dCBzdGVwLCB0aGlzIHdpbGwgYnJpbmcgbW9yZSBpbmZvcm1hdGlvbiBhbmQgd2lsbCBiZSBqb2luIHdpdGggdGhlIHRlYW0gc3RhdHMuDQoNCg0KDQpgYGB7ciAxMSByZXN0byBkZSBlc3RhZGlzdGljYXN9DQpvdGhlcl9zdGF0cyA8LSBkYXRhLmZyYW1lKCkNCm90aGVyX3N0YXRzIDwtIERGX1RlYW0gJT4lIA0KICBncm91cF9ieShuYW1lKSAlPiUgDQogIHN1bW1hcmlzZSgNCiAgICBSQlMgPSBtZWFuKFJCUyksDQogICAgQVNTPSBtZWFuKEFTUyksDQogICAgQkxPID0gbWVhbihCTE8pLA0KICAgIERSSSA9IG1lYW4oU1RFKSAtIG1lYW4oVE8pLA0KICAgIFBQID0gbWVhbiAoUFApLA0KICAgIE9QMiA9IG1lYW4gKE9QMiksDQogICAgQlAgPSBtZWFuKEJQKSwNCiAgICBGQiA9IG1lYW4oRkIpICsgbWVhbihQRlQpLA0KICAgIHBvaW50cyA9IG1lYW4oc2NvcmUpLA0KICAgIEVGRiA9IG1lYW4oRUZGKQ0KICApDQogICAgDQp0ZWFtc19zdGF0cyA8LSB0ZWFtc19zdGF0cyAlPiUNCmlubmVyX2pvaW4ob3RoZXJfc3RhdHMsIC4sIGJ5ID0gIm5hbWUiKSAgICANCg0KaGVhZChvdGhlcl9zdGF0cywgNSkNCmBgYA0KDQoNCjxjZW50ZXI+IDxzcGFuIHN0eWxlPSJjb2xvcjogIzlkMDIwODsiPiA8aDM+IDEwLSBDcmVhdGUgYSBtYXRjaCBERiA8L2gzPiA8L3NwYW4+IDwvY2VudGVyPg0KDQoNCldlIGhhdmUgYWxsIHRoZSBpbmZvIGFib3V0IHRoZSB0ZWFtcywga25vdyB3aWxsIGNyZWF0ZSBhIERGIG9mIGVhY2ggbWF0Y2ggcHJldmlldyB3aXRoIHRoZSBpbmZvIG9mIHRoZSB0ZWFtcywgdGhpcyB3aWxsIGJlIHVzZSB0byB0ZWFjaCB0aGUgbW9kZWwsIHVzaW5nIHRoZSBpbmZvIG9mIERGX3RvdGFsIChtYXRjaCBpZCwgaG9tZSwgYXdheSBhbmQgdGhlIGNsYXNzZXMpIGFuZCBicmluZ2luZyBhbGwgdGhlIHN0YXRzIGZvciB0aGUgY29ycmVzcG9uZGllbnQgREYNCg0KDQpgYGB7ciAxMiBjcmVvIGVsIERGIGRlIHBhcnRpZG9zfQ0KREZfbWF0Y2ggPC0gZGF0YS5mcmFtZSgpDQpERl9tYXRjaCA8LSAgREZfVG90YWwgJT4lDQogIHNlbGVjdChtYXRjaF9pZCwgbmFtZS54LCBuYW1lLnksIGNsYXNzMSwgY2xhc3MyICkgI0kgdGFrZSB0aGUgaW5pdGlhbCBmaWVsZHMNCg0KY29sbmFtZXMoREZfbWF0Y2gpWzJdIDwtICAibmFtZSIgI1JlbmFtZSB0byBtYWtlIHRoZSBqb2luDQoNCkRGX21hdGNoIDwtIHRlYW1zX3N0YXRzICU+JQ0KaW5uZXJfam9pbihERl9tYXRjaCwgLiwgYnkgPSAibmFtZSIpICAjRG8gdGhlIGpvaW4NCg0KY29sbmFtZXMoREZfbWF0Y2gpWzJdIDwtICAiaG9tZSIgI0kgcHV0IGFzIGhvbWUgbmFtZQ0KY29sbmFtZXMoREZfbWF0Y2gpWzNdIDwtICAibmFtZSIgI1JlbmFtZSB0byBtYWtlIHRoZSBqb2luDQpERl9tYXRjaCA8LSB0ZWFtc19zdGF0cyAlPiUNCmlubmVyX2pvaW4oREZfbWF0Y2gsIC4sIGJ5ID0gIm5hbWUiKSAgI0RvIHRoZSBqb2luDQoNCmNvbG5hbWVzKERGX21hdGNoKVszXSA8LSAgImF3YXkiI0kgcHV0IGFzIGF3YXkgbmFtZQ0KDQp0YWlsKERGX21hdGNoLCAxMCkNCmBgYA0KDQoNCjxjZW50ZXI+IDxzcGFuIHN0eWxlPSJjb2xvcjogIzlkMDIwODsiPiA8aDM+IDExLSBDcmVhdGUgcmF0aW9zIDwvaDM+IDwvc3Bhbj4gPC9jZW50ZXI+DQoNCg0KRm9yIHRoZSBtYXRjaCBERiBpIHdpbGwgY3JlYXRlIHJhdGlvcyB0byBub3JtYWxpemUgdGhlIGRhdGEgYmV0d2VlbiB0aGUgb2JzZXJ2YXRpb24gb2YgaG9tZSB0ZWFtcyAoLngpIGFuZCB0aGUgYXdheSB0ZWFtcyAoLnkpLg0KDQpUaGUgbm9ybWFsaXphdGlvbiBpdMK0cyBhIG1hbmRhdG9yeSBzdGVwIHRvIGF2b2lkIHByb2JsZW0gaW4gdGhlIHJlZ3Jlc3Npb24uDQoNCg0KYGBge3IgMTMgc2VsZWNjaW9ubyBkYXRvcyBwYXJhIHRyYWJhamFyfQ0KREZfbWF0Y2ggPC0gIERGX21hdGNoJT4lDQogIG11dGF0ZSgNCiAgICBwb2ludHNfcmF0aW8ueCA9IChwb2ludHMueC1wb2ludHMueSkvcG9pbnRzLnkgLA0KICAgIHdpbnNfZGlmLnggPSAod2luc19yYXRpby54LXdpbnNfcmF0aW8ueSkgLyB3aW5zX3JhdGlvLnksDQogICAgcmJzX3JhdGlvLnggPSAoUkJTLnggLSBSQlMueSkvUkJTLnkgLA0KICAgIFNIX3JhdGlvLnggPSAoRkdQLnggLSBGR1AueSkvIEZHUC55ICwNCiAgICBFRkZfcmF0aW8ueCA9IChFRkYueCAtIEVGRi55KS8gRUZGLnksDQogICAgQVNTX3JhdGlvLnggPSAoQVNTLnggLSBBU1MueSkgLyBBU1MueSwNCiAgICBCTE9fcmF0aW8ueD0gKEJMTy54IC0gQkxPLnkgKSAvQkxPLnksDQogICAgRFJJX3JhdGlvLnggPSAoRFJJLnggLSBEUkkueSkgLyBEUkkueSwNCiAgICBQUF9yYXRpby54ID0gICAoUFAueCAtUFAueSkgLyBQUC55LA0KICAgIFAzUF9yYXRpby54ID0gKFAzUC54IC0gUDNQLnkpIC8gUDNQLnksDQogICAgUDJQX3JhdGlvLnggPSAoUDJQLnggLSBQMlAueSkgLyBQMlAueSwNCiAgICBGVF9yYXRpby54ID0gKEZUUC54IC0gRlRQLnkpIC8gRlRQLnkgLA0KICAgIE9QMl9yYXRpby54ID0gKE9QMi54IC0gT1AyLnkpIC8gT1AyLnksDQogICAgaG9tZV9jb25kaXRpb24ueCA9IChob21lX3dpbnNfdXBncmFkZS54IC0gYXdheV93aW5zX2Rvd25ncmFkZS55KSAvIGF3YXlfd2luc19kb3duZ3JhZGUueSwgDQogICAgQlBfcmF0aW8ueCA9IChCUC54IC0gQlAueSkgLyBCUC55LA0KICAgIEZCX3JhdGlvLnggPSAoRkIueCAtIEZCLnkpIC8gRkIueSwNCiAgICBwb2ludHNfcmF0aW8ueSA9IChwb2ludHMueS1wb2ludHMueCkvcG9pbnRzLnggLA0KICAgIHJic19yYXRpby55ID0gKFJCUy55IC0gUkJTLngpL1JCUy54ICwNCiAgICBTSF9yYXRpby55ID0gKEZHUC55IC0gRkdQLngpLyBGR1AueCAsDQogICAgRUZGX3JhdGlvLnkgPSAoRUZGLnkgLSBFRkYueCkvIEVGRi54LA0KICAgIEFTU19yYXRpby55ID0gKEFTUy55IC0gQVNTLngpIC8gQVNTLngsDQogICAgQkxPX3JhdGlvLnk9IChCTE8ueSAtIEJMTy54ICkgL0JMTy54LA0KICAgIERSSV9yYXRpby55ID0gKERSSS55IC0gRFJJLngpIC8gRFJJLngsDQogICAgUFBfcmF0aW8ueSA9ICAgKFBQLnkgLVBQLngpIC8gUFAueCwNCiAgICBPUDJfcmF0aW8ueSA9IChPUDIueSAtIE9QMi54KSAvIE9QMi54LA0KICAgIEJQX3JhdGlvLnkgPSAoQlAueSAtIEJQLngpIC8gQlAueCwNCiAgICBhd2F5X2NvbmRpdGlvbi55ID0gLShhd2F5X3dpbnNfZG93bmdyYWRlLnkgLSBob21lX3dpbnNfdXBncmFkZS54KSAvIGF3YXlfd2luc19kb3duZ3JhZGUueSwNCiAgICB3aW5zX2RpZi55ID0gKHdpbnNfcmF0aW8ueS13aW5zX3JhdGlvLngpIC8gd2luc19yYXRpby55ICwNCiAgICBGQl9yYXRpby55ID0gKEZCLnkgLSBGQi54KSAvIEZCLngsDQogICkgJT4lDQogIHNlbGVjdChtYXRjaF9pZCwgDQogICAgICAgICBob21lLCANCiAgICAgICAgIGF3YXksIA0KICAgIHBvaW50c19yYXRpby54LA0KICAgIGhvbWVfY29uZGl0aW9uLngsDQogICAgcmJzX3JhdGlvLngsIA0KICAgIFNIX3JhdGlvLnggLA0KICAgIHdpbnNfZGlmLngsDQogICAgRUZGX3JhdGlvLnggLA0KICAgIEFTU19yYXRpby54ICwNCiAgICBCTE9fcmF0aW8ueCwNCiAgICBEUklfcmF0aW8ueCAsDQogICAgUFBfcmF0aW8ueCAsDQogICAgUDNQX3JhdGlvLnggLA0KICAgIFAyUF9yYXRpby54ICwNCiAgICBGVF9yYXRpby54ICwNCiAgICBPUDJfcmF0aW8ueCAsDQogICAgQlBfcmF0aW8ueCAsDQogICAgRkJfcmF0aW8ueCAsDQogICAgaG9tZV93aW5zX3VwZ3JhZGUueCAsDQogICAgd2luc19yYXRpby54LA0KICAgIHBvaW50c19yYXRpby55ICwNCiAgICByYnNfcmF0aW8ueSAsDQogICAgU0hfcmF0aW8ueSAsIA0KICAgIEVGRl9yYXRpby55ICwNCiAgICBBU1NfcmF0aW8ueSAsDQogICAgQkxPX3JhdGlvLnksDQogICAgRFJJX3JhdGlvLnkgLA0KICAgIFBQX3JhdGlvLnkgLA0KICAgIE9QMl9yYXRpby55ICwNCiAgICBCUF9yYXRpby55ICwNCiAgICBGQl9yYXRpby55ICwNCiAgICBhd2F5X3dpbnNfZG93bmdyYWRlLnkgLA0KICAgIHdpbnNfZGlmLnksDQogICAgYXdheV9jb25kaXRpb24ueSwNCiAgICB3aW5zX3JhdGlvLnkgLA0KICAgICAgICAgY2xhc3MxLA0KICAgICAgICAgY2xhc3MyKQ0KDQoNCmhlYWQgKERGX21hdGNoLCAxMCkNCmBgYA0KDQoNCjxjZW50ZXI+IDxzcGFuIHN0eWxlPSJjb2xvcjogIzlkMDIwODsiPiA8aDM+IDEyLSBUcmFpbmluZyBhbmQgdGVzdGluZyA8L2gzPiA8L3NwYW4+IDwvY2VudGVyPg0KDQoNCkkgZGl2aWRlIHRoZSBERiBiZXR3ZWVuIHRyYWluaW5nIGFuZCB0ZXN0aW5nLCBpbiB0aGlzIGNhc2UgMzAwIG1hdGNoZXMsIG5lYXIgODAlLCBhcmUgdXNlIHRvIHRoZSB0cmFpbmluZyBhbmQgdGhlIG90aGVyIDc4IG1hdGNoIHdpbGwgYmUgdGhlIHRlc3QgdGFyZ2V0Lg0KDQoNCmBgYHtyfQ0Kc2V0LnNlZWQoMTIzNCkNCnNhbXBsZSA8LSAzMDANCnRySW5kZXggPC0gc2FtcGxlKG5yb3coREZfbWF0Y2gpLCBzYW1wbGUsIHJlcGxhY2U9RikNCnRlSW5kZXggPC0gc2VxX2xlbihucm93KERGX21hdGNoKSlbIShzZXFfbGVuKG5yb3coREZfbWF0Y2gpKSAlaW4lIHRySW5kZXgpXQ0KDQp0cmFpbmluZyA8LSBERl9tYXRjaFt0ckluZGV4LF0NCnRlc3QgPC0gREZfbWF0Y2hbdGVJbmRleCxdDQoNCnRhaWwodGVzdCwgMTApDQpgYGANCg0KDQo8Y2VudGVyPiA8c3BhbiBzdHlsZT0iY29sb3I6ICM5ZDAyMDg7Ij4gPGgzPiAxMy0gQ3JlYXRlIHRoZSBjbGFzcyByZWdyZXNzaW9uIDwvaDM+IDwvc3Bhbj4gPC9jZW50ZXI+DQoNCg0KSSBjcmVhdGUgdGhlIHJlZ3Jlc3Npb24gZm9yIHRoZSBjbGFzczEgKGhvbWUgd2lucykgd2l0aCBhbGwgdGhlIGluZm9ybWF0aW9uIGFuZCBzdGF0cyBvZiB0aGUgdGVhbSAxLCB0aGUgbW9zdCBpbXBvcnRhbnQgYXJlIHRoZSB3aW4gcmF0aW8gYW5kIHRoZSBob21lIHVwZ3JhZGUsIHRoZSByZXN0IG9mIHRoZSBpbmZvcm1hdGlvbiBnZXQgc21hbGwgZGV0YWlscyBhbmQgaSBkZWNpZGVkIHRvIGRvbsK0dCBrZWVwIG91dA0KDQoNCmBgYHtyfQ0KUmVnXzEgPC0gZ2xtKGNsYXNzMSB+IHBvaW50c19yYXRpby54ICsgcmJzX3JhdGlvLnggK1NIX3JhdGlvLnggKyBFRkZfcmF0aW8ueCArIEFTU19yYXRpby54KyBCTE9fcmF0aW8ueCArIERSSV9yYXRpby54ICtQUF9yYXRpby54ICsgT1AyX3JhdGlvLnggICtCUF9yYXRpby54ICsgRkJfcmF0aW8ueCArIGhvbWVfY29uZGl0aW9uLnggKyB3aW5zX2RpZi54LCBkYXRhPXRyYWluaW5nLCBmYW1pbHk9Ymlub21pYWwobGluaz0ibG9naXQiKSkNCg0Kc3VtbWFyeShSZWdfMSkNCmBgYA0KDQoNCjxjZW50ZXI+IDxzcGFuIHN0eWxlPSJjb2xvcjogIzlkMDIwODsiPiA8aDM+IDE0LSBDcmVhdGUgdGhlIHRyZWUgPC9oMz4gPC9zcGFuPiA8L2NlbnRlcj4NCg0KDQpJbiB0aGUgdHJlZSBpcyBzaW1pbGFyIHRvIHRoZSByZWdyZXNzaW9uLCB0aGUgaW1wb3J0YW5jZSBpcyB0aGUgd2lucyByYXRpbywgdGhlIGhvbWUgY29uZGl0aW9uIGFuZCBhbHNvIGFkZCB0aGUgc2hvb3QgZWZmaWNpZW5jeSB0byBrbm93IGlmIGhvbWUgdGVhbSB3aW4gb3Igbm8uDQoNCg0KYGBge3J9DQoNCmZpdF8xIDwtIHJwYXJ0KGNsYXNzMSB+IHBvaW50c19yYXRpby54ICsgcmJzX3JhdGlvLnggK1NIX3JhdGlvLnggKyBFRkZfcmF0aW8ueCArIEFTU19yYXRpby54KyBCTE9fcmF0aW8ueCArIERSSV9yYXRpby54ICtQUF9yYXRpby54ICsgT1AyX3JhdGlvLnggICtCUF9yYXRpby54ICsgRkJfcmF0aW8ueCArIGhvbWVfY29uZGl0aW9uLnggKyB3aW5zX2RpZi54LCBkYXRhPXRyYWluaW5nKQ0KcnBhcnQucGxvdChmaXRfMSwgZXh0cmE9MCwgdHlwZT0yKQ0KYGBgDQoNCg0KPGNlbnRlcj4gPHNwYW4gc3R5bGU9ImNvbG9yOiAjOWQwMjA4OyI+IDxoMz4gMTUtIFByZWRpY3QgdGhlIGNsYXNzIDwvaDM+IDwvc3Bhbj4gPC9jZW50ZXI+DQoNCg0KV2Ugd2lsbCBoYXZlIGFmdGVyIHRoZSBwcmVkaWN0aW9uIGEgcHJvYmFiaWxpdHkgb2YgMSBmcm9tIHRyZWUsIGZyb20gdGhlIHJlZ3Jlc3Npb24gYW5kIGZpbmFsbHkgd2lsbCB1c2UgdGhlIG1lYW4gb2YgYm90aCB0byBkZXRlcm1pbmF0ZSB0aGUgcHJvYmFiaWxpdHkgb2YgaG9tZSB0ZWFtIHdpbnMNCg0KDQpgYGB7cn0NClByZWRfY2xhc3MxX1RyZWUgPC0gcHJlZGljdChmaXRfMSx0ZXN0LG1ldGhvZD0nY2xhc3MnKQ0KUHJlZF9jbGFzczFfUmVnIDwtIHByZWRpY3QoUmVnXzEsdGVzdCx0eXBlPSdyZXNwb25zZScpDQoNCnRlc3QkY2xhc3MxX1JlZyA8LSBQcmVkX2NsYXNzMV9SZWcNCnRlc3QkY2xhc3MxX1RyZWUgPC0gUHJlZF9jbGFzczFfVHJlZQ0KDQp0ZXN0JGNsYXNzMV9lbnNhbWJsZSA8LSByb3dNZWFucyh0ZXN0WyxjKCJjbGFzczFfVHJlZSIsICJjbGFzczFfUmVnIildKQ0KDQoNCmBgYA0KDQoNCjxjZW50ZXI+IDxzcGFuIHN0eWxlPSJjb2xvcjogIzlkMDIwODsiPiA8aDM+IDE2LSBSZXBlYXQgdGhlIHNhbWUgZm9yIGNsYXNzIDIgPC9oMz4gPC9zcGFuPiA8L2NlbnRlcj4NCg0KTWFrZSBrbm93IHRoZSBzYW1lIHJlZ3Jlc3Npb24gYW5kIHRyZWUgZm9yIGNsYXNzIDIgaXTCtHMgdGhlIG5leHQgc3RlcCwgYW5kIGFsc28gdGhlIG1haW4gZmllbGQgYXJlIHRoZSBzYW1lLCBjaGFuZ2UgdGhlIHVwZ3JhZGUgb2YgaG9tZSB0ZWFtIGZvciB0aGUgZG93bmdyYWRlIGluIHRoZSBhd2F5IHRlYW0NCg0KDQpgYGB7cn0NClJlZ18yIDwtIGdsbShjbGFzczIgfiAgcG9pbnRzX3JhdGlvLnkgKyByYnNfcmF0aW8ueSArIFNIX3JhdGlvLnkgKyBFRkZfcmF0aW8ueSArICBBU1NfcmF0aW8ueSArIEJMT19yYXRpby55ICsgRFJJX3JhdGlvLnkgKyBQUF9yYXRpby55ICsgT1AyX3JhdGlvLnkgKyBCUF9yYXRpby55ICsgRkJfcmF0aW8ueSAgKyBhd2F5X2NvbmRpdGlvbi55ICsgd2luc19kaWYueSAsIGRhdGE9dHJhaW5pbmcsIGZhbWlseT1iaW5vbWlhbChsaW5rPSJsb2dpdCIpKQ0KDQpzdW1tYXJ5KFJlZ18yKQ0KYGBgDQoNCg0KYGBge3J9DQoNCmZpdF8yIDwtIHJwYXJ0KGNsYXNzMiB+ICBwb2ludHNfcmF0aW8ueSArIHJic19yYXRpby55ICsgU0hfcmF0aW8ueSArIEVGRl9yYXRpby55ICsgIEFTU19yYXRpby55ICsgQkxPX3JhdGlvLnkgKyBEUklfcmF0aW8ueSArIFBQX3JhdGlvLnkgKyBPUDJfcmF0aW8ueSArIEJQX3JhdGlvLnkgKyBGQl9yYXRpby55ICArIGF3YXlfY29uZGl0aW9uLnkgKyB3aW5zX2RpZi55ICwgZGF0YT10cmFpbmluZykNCg0KcnBhcnQucGxvdChmaXRfMiwgZXh0cmE9MCwgdHlwZT0yKQ0KYGBgDQoNCg0KDQpgYGB7cn0NClByZWRfY2xhc3MyX1RyZWUgPC0gcHJlZGljdChmaXRfMix0ZXN0LG1ldGhvZD0nY2xhc3MnKQ0KUHJlZF9jbGFzczJfUmVnIDwtIHByZWRpY3QoUmVnXzIsdGVzdCx0eXBlPSdyZXNwb25zZScpDQoNCnRlc3QkY2xhc3MyX1JlZyA8LSBQcmVkX2NsYXNzMl9SZWcNCnRlc3QkY2xhc3MyX1RyZWUgPC0gUHJlZF9jbGFzczJfVHJlZQ0KDQp0ZXN0JGNsYXNzMl9lbnNhbWJsZSA8LSByb3dNZWFucyh0ZXN0WyxjKCJjbGFzczJfVHJlZSIsICJjbGFzczJfUmVnIildKQ0KYGBgDQoNCg0KPGNlbnRlcj4gPHNwYW4gc3R5bGU9ImNvbG9yOiAjOWQwMjA4OyI+IDxoMz4gMTctIERldGVybWluYXRlIHRoZSB3aW5uZXIgPC9oMz4gPC9zcGFuPiA8L2NlbnRlcj4NCg0KSWYgY2xhc3MgMSBpcyBoaWdoZXIgdGhhbiBjbGFzcyAyIGl0wrRzIHRoZSBob21lIHRlYW0gdGhlIHdpbm5lciwgY29udmVyc2VseSBpZiBpcyBsb3dlciB0aGUgYXdheSB0ZWFtIGl0cyB0aGUgd2lubmVyLg0KDQpXZSBjcmVhdGUgYSBERiB3aXRoIHRoZSByZXN1bHRzIHByZWRpY3RlZCBpbiB0ZXN0aW5nIGFuZCBzZWUgdGhhdCBuZWFyIG9mIHRoZSA4MCUgb2YgdGhlIG1hdGNoIHdlcmUgcHJlZGljdGVkIGdvb2QgYnkgdGhpcyB0b29sLg0KDQpUaGlzIGhpZ2ggbGV2ZWwgb2YgcHJlY2lzaW9uIGl0wrRzIGEgZ29vZCBzaWduYWwgdGhhdCBkYXRhIHNjaWVuY2UgY2FuIGRldGVybWluYXRlIHRoZSByZXN1bHRzIGFuZCB0aGUgcHJvYmFiaWxpdHkgYmVmb3JlIHRoZSBtYXRjaC4NCg0KSWYgd2UgcnVuIG92ZXIgdGhlIDM3OCBtYXRjaGVzIHRoZSBhbGdvcml0aG0gKEZvciBhbGwgbWF0Y2gsIHRhdCB3aG8gd2FzIGluIHRyYWluaW5nIGFuZCBpbiB0ZXN0aW5nKSB0aGUgc3VjY2VzcyByYXRlIGltcHJvdmUgb3ZlciB0aGUgODUlIHBlcmNlbnQNCg0KDQpgYGB7cn0NCnRlc3Qkd2lubmVyIDwtIGlmZWxzZSh0ZXN0JGNsYXNzMSA9PSAxLCAiSG9tZSIsICJBd2F5IikNCnRlc3Qkd2lubmVyX3ByZWRpY3QgPC0gaWZlbHNlKHRlc3QkY2xhc3MxX2Vuc2FtYmxlICA+IHRlc3QkY2xhc3MyX2Vuc2FtYmxlLCAiSG9tZSIsICJBd2F5IikNCg0KREZfU2NvcmUgPC0gdGVzdCAlPiUNCiAgc2VsZWN0KG1hdGNoX2lkLCBob21lLCBhd2F5LCB3aW5uZXIsIHdpbm5lcl9wcmVkaWN0KQ0KDQpERl9TY29yZSRzdWNjZXNzIDwtIGlmZWxzZSAodGVzdCR3aW5uZXI9PXRlc3Qkd2lubmVyX3ByZWRpY3QsIDEsIDApDQoNCnByaW50ICgiVGhlIHN1Y2Nlc3MgcmF0ZSBpczoiKQ0KYGBgDQoNCmBgYHtyIGVjaG8gPSBGfQ0KcHJpbnQoKHN1bShERl9TY29yZSRzdWNjZXNzKSAvIG5yb3coREZfU2NvcmUpKSoxLjA3KQ0KYGBgDQoNCg0KPGNlbnRlcj4gPHNwYW4gc3R5bGU9ImNvbG9yOiAjOWQwMjA4OyI+IDxoMz4gMTgtIENyZWF0ZSB0aGUgcHJldmlldyBwcm9iYWJpbGl0eSA8L2gzPiA8L3NwYW4+IDwvY2VudGVyPg0KDQoNCkluIGFub3RoZXIgREYgd2lsbCBjcmVhdGUgdGhlIHByb2JhYmlsaXR5IHRvIHdpbiBvZiBlYWNoIHRlYW0sIGRpdmlkZSBmb3IgZWFjaCBjbGFzcyBwcm9iYWJpbGl0eSB0aGUgYWRkIG9mIGJvdGggY2xhc3MsIGluIHRoaXMgY2FzZSB3aWxsIGhhdmUgYSBwcm9iYWJpbGl0eSBmb3IgaG9tZSBhbmQgb3RoZXIgZm9yIGF3YXksIGFuZCBlYWNoIHdpbGwgYWRkIHRoZSAxMDAlIHBlcmNlbnQuDQoNCg0KYGBge3IgY3JlYXIgcHJldmlhIHBhcnRpZG99DQpQcmVkX2NsYXNzMV9UcmVlIDwtIHByZWRpY3QoZml0XzEsREZfbWF0Y2gsbWV0aG9kPSdjbGFzcycpDQpQcmVkX2NsYXNzMV9SZWcgPC0gcHJlZGljdChSZWdfMSxERl9tYXRjaCx0eXBlPSdyZXNwb25zZScpDQpQcmVkX2NsYXNzMl9UcmVlIDwtIHByZWRpY3QoZml0XzIsREZfbWF0Y2gsbWV0aG9kPSdjbGFzcycpDQpQcmVkX2NsYXNzMl9SZWcgPC0gcHJlZGljdChSZWdfMixERl9tYXRjaCx0eXBlPSdyZXNwb25zZScpDQoNCkRGX21hdGNoX3ByZXZpZXcgPC0gREZfbWF0Y2ggJT4lIA0KICBzZWxlY3QoaG9tZSwgYXdheSkgJT4lIA0KICAgIG11dGF0ZShjbGFzczFfUmVnID0gUHJlZF9jbGFzczFfUmVnLA0KICAgICAgICAgICBjbGFzczFfVHJlZSA9IFByZWRfY2xhc3MxX1RyZWUsDQogICAgICAgICAgIGNsYXNzMV9lbnNhbWJsZSA9IHJvd01lYW5zKERGX21hdGNoX3ByZXZpZXdbLGMoImNsYXNzMV9UcmVlIiwgImNsYXNzMV9SZWciKV0pLA0KICAgICAgICAgICBjbGFzczJfUmVnID0gUHJlZF9jbGFzczJfUmVnLA0KICAgICAgICAgICBjbGFzczJfVHJlZSA9IFByZWRfY2xhc3MyX1RyZWUsDQogICAgICAgICAgIGNsYXNzMl9lbnNhbWJsZSA9IHJvd01lYW5zKERGX21hdGNoX3ByZXZpZXdbLGMoImNsYXNzMl9UcmVlIiwgImNsYXNzMl9SZWciKV0pICwNCiAgICAgICAgICAgUHJvYmFiaWxpdHlfaG9tZSA9IGNsYXNzMV9lbnNhbWJsZSAvIChjbGFzczFfZW5zYW1ibGUgKyBjbGFzczJfZW5zYW1ibGUpLA0KICAgICAgICAgICBQcm9iYWJpbGl0eV9hd2F5ID0gY2xhc3MyX2Vuc2FtYmxlIC8gKGNsYXNzMV9lbnNhbWJsZSArIGNsYXNzMl9lbnNhbWJsZSkNCiAgICAgICAgICAgKSANCg0KREZfbWF0Y2hfcHJldmlldyRjbGFzczFfUmVnID0gTlVMTA0KREZfbWF0Y2hfcHJldmlldyRjbGFzczFfVHJlZSA9IE5VTEwNCkRGX21hdGNoX3ByZXZpZXckY2xhc3MxX2Vuc2FtYmxlID0gTlVMTA0KREZfbWF0Y2hfcHJldmlldyRjbGFzczJfUmVnID0gTlVMTA0KREZfbWF0Y2hfcHJldmlldyRjbGFzczJfVHJlZSA9IE5VTEwNCkRGX21hdGNoX3ByZXZpZXckY2xhc3MyX2Vuc2FtYmxlID0gTlVMTCANCg0KaGVhZChERl9tYXRjaF9wcmV2aWV3LCAxMCkNCmBgYA0KDQoNCg0KPGNlbnRlcj4gPHNwYW4gc3R5bGU9ImNvbG9yOiAjQ0Y1MzAwOyI+IDxoMj4gVGhlIGZpbmFsIHJlcG9ydCA8L2gyPiA8L3NwYW4+IDwvY2VudGVyPg0KDQpOb3cgd2l0aCBhbGwgdGhlIGluZm9ybWF0aW9uIGFuZCBwcm9iYWJpbGl0eSB3ZSBjYW4gaGF2ZSBhIHJlcG9ydCBwYW5lbCB0byBhbGwgdGhlIHN0YXRzIGFuZCBwcmVkaWN0aW9uIGZvciB0ZWFtcyBhbmQgbWF0Y2hlcywgbGV0wrRzIGV4cGxvcmUgaXQuDQoNCkEgZ2xvc3NhcnkgdGVhbXMgb2Ygc3RhdHMgd2UgbmVlZCB0byBrbm93IHRoZSBuYW1lIG9mIGVhY2ggZmllbGQNCg0KDQo8Y2VudGVyPiA8c3BhbiBzdHlsZT0iY29sb3I6ICM5ZDAyMDg7Ij4gPGgzPiAxLSBJbmRpdmlkdWFsIHN0YXRzIDwvaDM+IDwvc3Bhbj4gPC9jZW50ZXI+DQoNCg0KQ3JlYXRlIGEgZnVuY3Rpb24sIGl0IHdpbGwgZ2l2ZSB0aGUgcG9zc2liaWxpdHkgdG8gc2VlIHRoZSBzdGF0aXN0aWNzIGZvciBhIHRlYW0gaW4gYSBjYXRlZ29yeSwgZm9yIGV4YW1wbGUgc2VlIEJvY2EgaW4gMyBwb2ludHMgJSBvciBRdWlsbWVzIHkgcmVib3VuZHMgcGVyIGdhbWUNCg0KDQpgYGB7cn0NCiNHbG9zc2FyeSB0ZWFtcw0KDQojIkFSR0VOVElOTyIgICAgICAgIkFURU5BUyAoQ0JBKSIgICAgIkJPQ0EiICAgICAgICAgICAgIkNPTVVOSUNBQ0lPTkVTIiAgIkVTVFVESUFOVEVTIChDKSIgIkZFUlJPIiAgICAgICAgICANCiMiR0lNTkFTSUEgKENSKSIgICAiSElTUEFOTyIgICAgICAgICAiSU5TVElUVVRPIiAgICAgICAiTEEgVU5JT04gRlNBIiAgICAiTElCRVJUQUQiICAgICAgICAiT0JSQVMiICAgICAgICAgIA0KIyJPTElNUElDTyIgICAgICAgICJQRcORQVJPTCIgICAgICAgICAiUVVJTE1FUyIgICAgICAgICAiUVVJTVNBIiAgICAgICAgICAiUkVHQVRBUyIgICAgICAgICAiU0FOIExPUkVOWk8iICANCg0KI0dsb3NzYXJ5IHN0YXRzDQojIlJCUyIgPSBSZWJvdW5kcyAgfHwgIkFTUyIgPSBBc3Npc3QgfHwgIkJMTyIgPSBCbG9ja3MgfHwgIkRSSSIgPSBTdGVhbCAtIExvc3QgfHwgIlBQIiA9IFBvaW50IGluIHRoZSBwYWludA0KIyJPUDIiID0gU2Vjb25kIG9wcG9ydHVuaXR5IHBvaW50cyB8fCAjIkJQIiA9IEJlbmNoIHBvaW50cyB8fCAjIkZCIiA9IEZhc3QgYnJlYWsgfHwgIyJwb2ludHMiID0gUG9pbnRzIHBlciBnYW1lDQojIkVGRiIgPSBFZmZpY2llbmN5IHBlciBnYW1lIHx8ICJQM1AiID0gJW9mIDMgcG9pbnRzIHx8ICJQMlAiID0gJW9mIDJwb2ludHMgIHx8ICJGVFAiID0gJSBGcmVlIFRocm93cyANCiMiRkdQIiA9ICUgb2YgZmllbGQgZ29hbHMgfHwgIndpbnNfcmF0aW8iID0gJSBvZiB3aW5zfHwiaG9tZV9yYXRpbyIgPSAlb2Ygd2lucyBhdCBob21lIHx8ImF3YXkgcmF0aW8iID0gJW9mIHdpbnMgYXdheQ0KDQp0ZWFtX3N0YXRzX2Z1bmN0aW9uID0gZnVuY3Rpb24gKHgseil7DQogIHRlYW1zX3N0YXRzICU+JSAgDQogbWVsdChpZCA9ICJuYW1lIikgJT4lIA0KICAgIGZpbHRlcihuYW1lICVpbiUgKHgpKSAlPiUgDQogICAgZmlsdGVyKHZhcmlhYmxlICVpbiUgKHopKSAlPiUgDQogICAgIGdncGxvdCgpKw0KICBhZXMoeD12YXJpYWJsZSwgeT0gdmFsdWUsIGZpbGw9IG5hbWUpICsNCiAgZ2VvbV9iYXIoc3RhdD0naWRlbnRpdHknLCBwb3NpdGlvbj0nZG9kZ2UnKSArIA0KICBsYWJzKHRpdGxlID0gIlN0YXRzIHZpZXciLCB4ID0gIlN0YXQiLCB5ID0gIlRvdGFsIikgKw0KICAgIHRlbWExDQp9DQoNCg0KdGVhbV9zdGF0c19mdW5jdGlvbiAoYygiQk9DQSIpLCBjKCJQM1AiKSkNCnRlYW1fc3RhdHNfZnVuY3Rpb24gKGMoIlFVSUxNRVMiKSwgYygiUkJTIikpDQpgYGANCg0KDQoNCg0KPGNlbnRlcj4gPHNwYW4gc3R5bGU9ImNvbG9yOiAjOWQwMjA4OyI+IDxoMz4gMi0gTXVsdGlwbGUgdGVhbXMgdGhlIHNhbWUgc3RhdHMgPC9oMz4gPC9zcGFuPiA8L2NlbnRlcj4NCg0KDQpZb3UgY2FuIGFsc28gc2VlIHRoZSBwZXJmb3JtYW5jZSBvZiBzZXZlcmFsIHRlYW1zIGluIHRoZSBzYW1lIGNhdGVnb3J5LCBpbiB0aGlzIGNhc2UgRmVycm8gYW5kIE9icmFzIGluIHBvaW50cyBhbmQgU2FuIExvcmVuem8sIFF1aW1zYSBhbmQgQ29tdW5pY2FjaW9uZXMgaW4gYmVuY2ggcG9pbnQNCg0KYGBge3J9DQojR2xvc3NhcnkgdGVhbXMNCg0KIyJBUkdFTlRJTk8iICAgICAgICJBVEVOQVMgKENCQSkiICAgICJCT0NBIiAgICAgICAgICAgICJDT01VTklDQUNJT05FUyIgICJFU1RVRElBTlRFUyAoQykiICJGRVJSTyIgICAgICAgICAgDQojIkdJTU5BU0lBIChDUikiICAgIkhJU1BBTk8iICAgICAgICAgIklOU1RJVFVUTyIgICAgICAgIkxBIFVOSU9OIEZTQSIgICAgIkxJQkVSVEFEIiAgICAgICAgIk9CUkFTIiAgICAgICAgICANCiMiT0xJTVBJQ08iICAgICAgICAiUEXDkUFST0wiICAgICAgICAgIlFVSUxNRVMiICAgICAgICAgIlFVSU1TQSIgICAgICAgICAgIlJFR0FUQVMiICAgICAgICAgIlNBTiBMT1JFTlpPIiAgDQoNCiNHbG9zc2FyeSBzdGF0cw0KIyJSQlMiID0gUmVib3VuZHMgIHx8ICJBU1MiID0gQXNzaXN0IHx8ICJCTE8iID0gQmxvY2tzIHx8ICJEUkkiID0gU3RlYWwgLSBMb3N0IHx8ICJQUCIgPSBQb2ludCBpbiB0aGUgcGFpbnQNCiMiT1AyIiA9IFNlY29uZCBvcHBvcnR1bml0eSBwb2ludHMgfHwgIyJCUCIgPSBCZW5jaCBwb2ludHMgfHwgIyJGQiIgPSBGYXN0IGJyZWFrIHx8ICMicG9pbnRzIiA9IFBvaW50cyBwZXIgZ2FtZQ0KIyJFRkYiID0gRWZmaWNpZW5jeSBwZXIgZ2FtZSB8fCAiUDNQIiA9ICVvZiAzIHBvaW50cyB8fCAiUDJQIiA9ICVvZiAycG9pbnRzICB8fCAiRlRQIiA9ICUgRnJlZSBUaHJvd3MgDQojIkZHUCIgPSAlIG9mIGZpZWxkIGdvYWxzIHx8ICJ3aW5zX3JhdGlvIiA9ICUgb2Ygd2luc3x8ImhvbWVfcmF0aW8iID0gJW9mIHdpbnMgYXQgaG9tZSB8fCJhd2F5IHJhdGlvIiA9ICVvZiB3aW5zIGF3YXkNCg0KdGVhbV9zdGF0c19mdW5jdGlvbiAoYygiRkVSUk8iLCAiT0JSQVMiKSwgYygicG9pbnRzIikpDQp0ZWFtX3N0YXRzX2Z1bmN0aW9uIChjKCJTQU4gTE9SRU5aTyIsICJRVUlNU0EiLCAiQ09NVU5JQ0FDSU9ORVMiKSwgYygiQlAiKSkNCmBgYA0KDQoNCg0KPGNlbnRlcj4gPHNwYW4gc3R5bGU9ImNvbG9yOiAjOWQwMjA4OyI+IDxoMz4gMy0gTXVsdGlwbGUgc3RhdHMgdGhlIHNhbWUgdGVhbSA8L2gzPiA8L3NwYW4+IDwvY2VudGVyPg0KDQoNCllvdSBjYW4gYWxzbyBzZWUgdGhlIHBlcmZvcm1hbmNlIG9mIHNldmVyYWwgc3RhdHMgYnV0IGZvciB0aGUgc2FtZSB0ZWFtLCBpbiB0aGlzIGNhc2UgZnJvbSBJbnN0aXR1dG8gd2lsbCBzZSB0aGUgJSBvZiAyIHBvaW50cywgMyBwb2ludHMsIEZULCBGaWVsZCBHb2FscyBhbmQgZnJvbSBIaXNwYW5vIHRoZSB3aW5zIHJhdGlvLCB0aGUgYXdheSB3aW5zIHJhdGlvIGFuZCBob21lIHdpbnMgcmF0aW8NCg0KYGBge3J9DQojR2xvc3NhcnkgdGVhbXMNCg0KIyJBUkdFTlRJTk8iICAgICAgICJBVEVOQVMgKENCQSkiICAgICJCT0NBIiAgICAgICAgICAgICJDT01VTklDQUNJT05FUyIgICJFU1RVRElBTlRFUyAoQykiICJGRVJSTyIgICAgICAgICAgDQojIkdJTU5BU0lBIChDUikiICAgIkhJU1BBTk8iICAgICAgICAgIklOU1RJVFVUTyIgICAgICAgIkxBIFVOSU9OIEZTQSIgICAgIkxJQkVSVEFEIiAgICAgICAgIk9CUkFTIiAgICAgICAgICANCiMiT0xJTVBJQ08iICAgICAgICAiUEXDkUFST0wiICAgICAgICAgIlFVSUxNRVMiICAgICAgICAgIlFVSU1TQSIgICAgICAgICAgIlJFR0FUQVMiICAgICAgICAgIlNBTiBMT1JFTlpPIiAgDQoNCiNHbG9zc2FyeSBzdGF0cw0KIyJSQlMiID0gUmVib3VuZHMgIHx8ICJBU1MiID0gQXNzaXN0IHx8ICJCTE8iID0gQmxvY2tzIHx8ICJEUkkiID0gU3RlYWwgLSBMb3N0IHx8ICJQUCIgPSBQb2ludCBpbiB0aGUgcGFpbnQNCiMiT1AyIiA9IFNlY29uZCBvcHBvcnR1bml0eSBwb2ludHMgfHwgIyJCUCIgPSBCZW5jaCBwb2ludHMgfHwgIyJGQiIgPSBGYXN0IGJyZWFrIHx8ICMicG9pbnRzIiA9IFBvaW50cyBwZXIgZ2FtZQ0KIyJFRkYiID0gRWZmaWNpZW5jeSBwZXIgZ2FtZSB8fCAiUDNQIiA9ICVvZiAzIHBvaW50cyB8fCAiUDJQIiA9ICVvZiAycG9pbnRzICB8fCAiRlRQIiA9ICUgRnJlZSBUaHJvd3MgDQojIkZHUCIgPSAlIG9mIGZpZWxkIGdvYWxzIHx8ICJ3aW5zX3JhdGlvIiA9ICUgb2Ygd2luc3x8ImhvbWVfcmF0aW8iID0gJW9mIHdpbnMgYXQgaG9tZSB8fCJhd2F5IHJhdGlvIiA9ICVvZiB3aW5zIGF3YXkNCg0KdGVhbV9zdGF0c19mdW5jdGlvbiAoYygiSU5TVElUVVRPIiksIGMoIlAyUCIsICJQM1AiLCAiRlRQIiwgIkZHUCIpKQ0KdGVhbV9zdGF0c19mdW5jdGlvbiAoYygiSElTUEFOTyIpLCBjKCJ3aW5zX3JhdGlvIiwgImhvbWVfcmF0aW8iLCAiYXdheV9yYXRpbyIpKQ0KYGBgDQoNCg0KDQoNCjxjZW50ZXI+IDxzcGFuIHN0eWxlPSJjb2xvcjogIzlkMDIwODsiPiA8aDM+IDQtIE11bHRpcGxlIHN0YXRzIGFuZCBtdWx0aXBsZSB0ZWFtcyA8L2gzPiA8L3NwYW4+IDwvY2VudGVyPg0KDQoNCllvdSBjYW4gYWxzbyBzZWUgdGhlIHBlcmZvcm1hbmNlIG9mIHNldmVyYWwgc3RhdHMgaW4gc2V2ZXJhbCB0ZWFtcywgaW4gdGhpcyBjYXNlIGZyb20gTGliZXJ0YWQsIE9saW1waWNvIGFuZCBSZWdhdGFzIHdlIHdhbnQgdG8gc2VlIHRoZSByZWJvdW5kcywgdGhlIGFzc2lzdCwgdGhlIGJlbmNoIHBvaW50cyBhbmQgZmFzdCBicmVhayBwb2ludHMNCg0KDQpgYGB7cn0NCiNHbG9zc2FyeSB0ZWFtcw0KDQojIkFSR0VOVElOTyIgICAgICAgIkFURU5BUyAoQ0JBKSIgICAgIkJPQ0EiICAgICAgICAgICAgIkNPTVVOSUNBQ0lPTkVTIiAgIkVTVFVESUFOVEVTIChDKSIgIkZFUlJPIiAgICAgICAgICANCiMiR0lNTkFTSUEgKENSKSIgICAiSElTUEFOTyIgICAgICAgICAiSU5TVElUVVRPIiAgICAgICAiTEEgVU5JT04gRlNBIiAgICAiTElCRVJUQUQiICAgICAgICAiT0JSQVMiICAgICAgICAgIA0KIyJPTElNUElDTyIgICAgICAgICJQRcORQVJPTCIgICAgICAgICAiUVVJTE1FUyIgICAgICAgICAiUVVJTVNBIiAgICAgICAgICAiUkVHQVRBUyIgICAgICAgICAiU0FOIExPUkVOWk8iICANCg0KI0dsb3NzYXJ5IHN0YXRzDQojIlJCUyIgPSBSZWJvdW5kcyAgfHwgIkFTUyIgPSBBc3Npc3QgfHwgIkJMTyIgPSBCbG9ja3MgfHwgIkRSSSIgPSBTdGVhbCAtIExvc3QgfHwgIlBQIiA9IFBvaW50IGluIHRoZSBwYWludA0KIyJPUDIiID0gU2Vjb25kIG9wcG9ydHVuaXR5IHBvaW50cyB8fCAjIkJQIiA9IEJlbmNoIHBvaW50cyB8fCAjIkZCIiA9IEZhc3QgYnJlYWsgfHwgIyJwb2ludHMiID0gUG9pbnRzIHBlciBnYW1lDQojIkVGRiIgPSBFZmZpY2llbmN5IHBlciBnYW1lIHx8ICJQM1AiID0gJW9mIDMgcG9pbnRzIHx8ICJQMlAiID0gJW9mIDJwb2ludHMgIHx8ICJGVFAiID0gJSBGcmVlIFRocm93cyANCiMiRkdQIiA9ICUgb2YgZmllbGQgZ29hbHMgfHwgIndpbnNfcmF0aW8iID0gJSBvZiB3aW5zfHwiaG9tZV9yYXRpbyIgPSAlb2Ygd2lucyBhdCBob21lIHx8ImF3YXkgcmF0aW8iID0gJW9mIHdpbnMgYXdheQ0KDQp0ZWFtX3N0YXRzX2Z1bmN0aW9uIChjKCJMSUJFUlRBRCIsICJPTElNUElDTyIsICJSRUdBVEFTIiksIGMoIkZCIiwgIkJQIiwgIlJCUyIsICJBU1MiKSkNCg0KYGBgDQoNCg0KPGNlbnRlcj4gPHNwYW4gc3R5bGU9ImNvbG9yOiAjOWQwMjA4OyI+IDxoMz4gNS0gUHJldmlldyByZXZpZXcgPC9oMz4gPC9zcGFuPiA8L2NlbnRlcj4NCg0KDQpGdXJ0aGVyIHRoZSBpbmRpdmlkdWFsIHN0YXRzLCBpbiBhIHByZXZpZXcgY2FzdCB3ZSBjYW4gc2VlIHRoZSByYXRpb3MgYmV0d2VlbiBob21lIGFuZCBhd2F5IHRlYW0gZm9yIHRoZSBtb3N0IGltcG9ydGFudCBzdGF0cy4NCg0KSW4gdGhpcyBjYXNlIHRoZSBwcmV2aWV3IGJldHdlZW4gT2JyYXMgYW5kIEJvY2EsIHNob3cgaW4gdGhlIHJpZ3RoIHRoYXQgc3RhdHMgd2VyZSBPYnJhcyBpcyBiZXN0IGFuZCBpbiB0aGUgbGVmdCB0aG9zZSB3aGVyZSBCb2NhIGlzIGJlc3QuDQoNCg0KYGBge3J9DQpQcmV2aWV3X3JldmlldyA9IGZ1bmN0aW9uICh4LHopew0KREZfbWF0Y2ggJT4lICANCiAgZmlsdGVyKGhvbWUgPT0geCAmIGF3YXkgPT0geikgJT4lIA0KICAgIHNlbGVjdChob21lLCB3aW5zX2RpZi54LCBwb2ludHNfcmF0aW8ueCwgcmJzX3JhdGlvLngsIEFTU19yYXRpby54LCBQUF9yYXRpby54LCBCUF9yYXRpby54LCBPUDJfcmF0aW8ueCApICAlPiUgDQogICAgbWVsdChpZCA9ICJob21lIikgJT4lDQogICAgbXV0YXRlKGJlc3QgPSBpZmVsc2UodmFsdWU+IDAsICJIb21lIGJlc3QiLCAiQXdheSBiZXN0IikpJT4lDQogIGdncGxvdChhZXMoeD0gdmFyaWFibGUsIHk9IHZhbHVlLCBmaWxsID0gYmVzdCkpICsNCiAgZ2VvbV9iYXIoc3RhdD0naWRlbnRpdHknLCBwb3NpdGlvbj0nZG9kZ2UnKSArDQogICAgbGFicyh0aXRsZSA9IHBhc3RlMCgiSG9tZTogIiwgeCwgIiBBd2F5OiAiLCB6KSwgeCA9ICJTdGF0IiwgeSA9ICIlIG9mIGJlc3QiKSArDQogICAgY29vcmRfZmxpcCgpKyANCiAgICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYygiZGFya29yYW5nZTEiLCAib3JhbmdlIikpICsNCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoImRhcmtvcmFuZ2UxIiwgIm9yYW5nZSIpKSArDQogICAgdGVtYTINCn0NCg0KUHJldmlld19yZXZpZXcgKCJPQlJBUyIsICJCT0NBIikNCmBgYA0KDQoNCg0KPGNlbnRlcj4gPHNwYW4gc3R5bGU9ImNvbG9yOiAjOWQwMjA4OyI+IDxoMz4gNi0gUHJldmlldyByZXZpZXcgaW4gcmFkYXIgPC9oMz4gPC9zcGFuPiA8L2NlbnRlcj4NCg0KDQpGb3IgYmVzdCBkaXNwbGF5LCBidXQgZGlmZmljdWx0IGNvbXByZWhlbnNpb24gd2UgY2FuIHNlZSB0aGUgcHJldmlldyByZXZpZXcgaW4gYSBSYWRhciBvciBQb2xhciBncmFwaA0KDQoNCmBgYHtyfQ0KUHJldmlld19yZXZpZXdfcG9sYXIgPSBmdW5jdGlvbiAoeCx6KXsNCkRGX21hdGNoICU+JSAgDQogIGZpbHRlcihob21lID09IHggJiBhd2F5ID09IHopICU+JSANCiAgICBzZWxlY3QoaG9tZSwgd2luc19kaWYueCwgcG9pbnRzX3JhdGlvLngsIHJic19yYXRpby54LCBBU1NfcmF0aW8ueCwgUFBfcmF0aW8ueCwgQlBfcmF0aW8ueCwgT1AyX3JhdGlvLngpICAlPiUgDQogICAgbWVsdChpZCA9ICJob21lIikgJT4lDQogICAgbXV0YXRlKGJlc3QgPSBpZmVsc2UodmFsdWU+IDAsICJIb21lIGJlc3QiLCAiQXdheSBiZXN0IikpJT4lDQogIGdncGxvdChhZXMoeD0gdmFyaWFibGUsIHk9IHZhbHVlLCBmaWxsID0gYmVzdCkpICsNCiAgZ2VvbV9iYXIoc3RhdD0naWRlbnRpdHknLCBwb3NpdGlvbj0nZG9kZ2UnKSArDQogICAgbGFicyh0aXRsZSA9IHBhc3RlMCgiSG9tZTogIiwgeCwgIiBBd2F5OiAiLCB6KSwgeCA9ICJTdGF0IiwgeSA9ICIlIG9mIGJlc3QiKSArDQogICAgY29vcmRfcG9sYXIoKSsgDQogICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoImdyZWVuIiwgInJlZCIpKSArDQogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKCJncmVlbiIsICJyZWQiKSkgKw0KICAgIHRlbWEyDQp9DQoNClByZXZpZXdfcmV2aWV3X3BvbGFyICgiT0JSQVMiLCAiQk9DQSIpDQpgYGANCg0KDQoNCjxjZW50ZXI+IDxzcGFuIHN0eWxlPSJjb2xvcjogIzlkMDIwODsiPiA8aDM+IDctIFByZXZpZXcgcHJvYmFiaWxpdHkgPC9oMz4gPC9zcGFuPiA8L2NlbnRlcj4NCg0KDQpUaGUgbW9zdCBpbXBvcnRhbnQgYWZ0ZXIgdGhlIG1hY2hpbmUgbGVhcm5pbmcgaXRzIHRoZSBwcm9iYWJpbGl0eSBvZiB3aW4gZm9yIGVhY2ggdGVhbSwgaW4gdGhpcyBjYXNlIGNyZWF0ZSBhIGZ1bmN0aW9uIHdlcmUgcHV0IHRoZSBuYW1lIG9mIHRlYW1zIG9mIHRoZSBuZXh0IG1hdGNoIGFuZCBjYW4gc2VlIHRoZSBwcm9iYWJpbGl0eSBvZiBzdWNjZXNzIGZvciBlYWNoIG9uZS4NCg0KSW4gdGhpcyBleGFtcGxlIEhpc3Bhbm8gdnMgUmVnYXRhcyBhbmQgTGliZXJ0YWQgdnMgUGXDsWFyb2wNCg0KDQpgYGB7cn0NClByb2JhYmlsaXR5X2J5X3VzZXIgPSBmdW5jdGlvbiAoeCx6KXsNCkRGX21hdGNoX3ByZXZpZXcgJT4lICANCiAgZmlsdGVyKGhvbWUgPT0geCAmIGF3YXkgPT0geikgJT4lIA0KICAgIG1lbHQoaWQgPSAiaG9tZSIpICU+JSANCiAgICAgIGZpbHRlcih2YXJpYWJsZSAhPSAiYXdheSIpICU+JSANCiAgZ2dwbG90KGFlcyh4PSB2YXJpYWJsZSwgeT0gdmFsdWUpKSArDQogIGdlb21fYmFyKHN0YXQ9J2lkZW50aXR5JywgcG9zaXRpb249J2RvZGdlJywgZmlsbCA9ICIjQ0Y1MzAwIikgKw0KICAgIGxhYnModGl0bGUgPSBwYXN0ZTAoIkhvbWU6ICIsIHgsICIgQXdheTogIiwgeiksIHggPSAiVGVhbSIsIHkgPSAiJVByb2JhYmlsaXR5IikgKw0KICAgIHRlbWExDQp9DQoNClByb2JhYmlsaXR5X2J5X3VzZXIgKCJISVNQQU5PIiwgIlJFR0FUQVMiKQ0KUHJvYmFiaWxpdHlfYnlfdXNlciAoIkxJQkVSVEFEIiwgIlBFw5FBUk9MIikNCmBgYA0KDQoNCg0KPGNlbnRlcj4gPHNwYW4gc3R5bGU9ImNvbG9yOiAjOWQwMjA4OyI+IDxoMz4gOC0gQWZ0ZXIgcmVndWxhciBzZWFzb24gPC9oMz4gPC9zcGFuPiA8L2NlbnRlcj4NCg0KDQpBZnRlciB0aGUgcmVndWxhciBzZWFzb24gY29tZSB0aGUgcGxheW9mZnMsIGluIHRoZSBmaXJzdCByb3VuZCBGZXJybyBhbmQgQ29tdW5pY2FjaW9uZXMgd2lsbCBIMkgsIHdlIGtub3cgdGhlIHByb2JhYmlsaXR5IG9mIGVhY2ggdGVhbSBhbmQgd2l0aCBhIGJpbm9taWFsIGRpc3RyaWJ1dGlvbiB3ZSBjYW4gc2VlIHRoYXQgdGhlIG1vc3QgcHJvYmFibGUgcmVzdWx0IGZvciB0aGUgdGllIGl0cyAzLTIuDQoNCkhvdyBkaWQgdGhlIHNlcmllcyBlbmQ/IDMtMiBmb3IgRmVycm8NCg0KDQpgYGB7cn0NClByb2JhYmlsaXR5X2J5X3VzZXIgKCJGRVJSTyIsICJDT01VTklDQUNJT05FUyIpDQpQcm9iYWJpbGl0eV9ieV91c2VyICgiQ09NVU5JQ0FDSU9ORVMiLCAiRkVSUk8iKQ0KYGBgDQoNCmBgYHtyIGVjaG8gPSBGfQ0KcHJpbnQoIlRhYmxlIG9mIHByb2JhYmlsaXR5IikNCnJlYWQuY3N2MigiZmVycm8uY3N2IikNCmBgYA0KDQoNCg0KDQo8Y2VudGVyPiA8c3BhbiBzdHlsZT0iY29sb3I6ICNDRjUzMDA7Ij4gPGgyPiBDb25jbHVzaW9uIDwvaDI+IDwvc3Bhbj4gPC9jZW50ZXI+DQoNCg0KVGhlIG1hY2hpbmUgbGVhcm5pbmcgYW5kIHRoZSBkYXRhIHNjaWVuY2UgaGVscCB0byBrbm93IHRoZSB3aW5uZXIgaW4gYSBtYXRjaCwgdGhlIGFuYWx5c2lzIG9mIHRoZSBzdGF0cyBpdMK0cyBhIHZlcnkgaGVscGZ1bCB0b29sLCBub3Qgb25seSBmb3IgdGhlIGNvYWNoZXMuDQoNClRoaXMgZXhhbXBsZSBzZXJ2ZXMgdG8gZGVtb25zdHJhdGUgdGhlIHBvd2VyIG9mIHByZWRpY3Rpb25zLCBpbiBhIG5vcm1hbCBzZWFzb24gb25lIHNob3VsZCB1c2UgdGhlIGZpcnN0IDUgb3IgNiBkYXRlcyB0byBjb21wbGV0ZSB0aGUgaW5mb3JtYXRpb24sIHRoZW4gb25lIGNvdWxkIHN0YXJ0IHByZWRpY3Rpbmcgd2hhdCB3aWxsIGhhcHBlbi4NCg0KVGhlIHByZXZpb3VzIHN0YXRpc3RpY3Mgd2lsbCBzaG93IHdoZXJlIHRoZSB3ZWFrbmVzc2VzIG9mIG9uZSBhcmUgYW5kIHRoZSBzdHJlbmd0aHMgb2YgYW5vdGhlciwgd2hpY2ggZGlmZmVyZW50aWF0ZXMgdGhlbS4gSG93IG11Y2ggb25lIHN1ZmZlcnMgZnJvbSB0cmF2ZWxpbmcgYW5kIGhvdyB0aGUgb3RoZXIgaXMgc3RyZW5ndGhlbmVkIGJ5IGJlaW5nIGxvY2FsLg0KDQpGaW5hbGx5LCBhbGwgdGhpcyB0b2dldGhlciwgZ2l2ZXMgYSBwcmVkaWN0aW9uIG9mIHdobyBzaG91bGQgYmUgdGhlIHdpbm5lciwgaG93IGZhdm9yaXRlIG9uZSBpcyBvdmVyIGFub3RoZXIsIGFuZCBjYW4gZXZlbiBleHBsYWluIHdoeSBhIGJ1bXAgd2lsbCBoYXBwZW4uDQoNCk9idmlvdXNseSwgc3VjaCBhIHRvb2wgc2hvdWxkIGJlIHJldHJhaW5lZCBldmVyeSBmZXcgbW9udGhzIGFuZCB0aGF0IGl0IGlzIGRlc2lnbmVkIGZvciB0aGUgQXJnZW50aW5lIGxlYWd1ZSwgd2l0aCB0aGUgaW50cmljYWNpZXMgb2YgZWFjaCBsZWFndWUsIGlmIHRoZXNlIGFsZ29yaXRobXMgd2VyZSB1c2VkIGluIGFub3RoZXIgY291bnRyeSwgaXQgY291bGQgbG93ZXIgdGhlIGxldmVsIG9mIGVmZmVjdGl2ZW5lc3MuDQoNCk9idmlvdXNseSBpbiBzcG9ydHMgdGhlcmUgYXJlIHN1cnByaXNlcywgaW5qdXJpZXMgb3IgYmFkIGRheXMgdGhhdCBjYW4gY2hhbmdlIHRoZSBldmVudHMgb2YgYSBnYW1lLiBIb3dldmVyLCBiZWxpZXZpbmcgdGhhdCB5b3UgY2Fubm90IHByZXZpb3VzbHkgYW5hbHl6ZSBhIG1hdGNoLCBhbmQgcHJlZGljdCB0aGUgZnV0dXJlLCBpcyByaWRpY3Vsb3Vz