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 <- 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