drawing

Seasons 1950-2017





Setup Stage


Loading necessary packages.

library(dplyr)
library(tidyr)
library(ggplot2)
library(RColorBrewer)
library(plotly)
library(reshape2)
library(kableExtra)
library(wordcloud2)


Create additional functions

# Mode average
getmode <- function(v) {
   uniqv <- unique(v)
   uniqv[which.max(tabulate(match(v, uniqv)))]
}


Load the dataset


Dataset taken from: https://www.kaggle.com/drgilermo/nba-players-stats/version/2

If you need to see the glossary and the data tidying process, please visit the first part here.

Loading datasets…

NBA <- read.csv("NBA_TidySet.csv")[,-c(1)]
NBA_Scaled <- read.csv("NBA_Scaled_TidySet.csv")
NBA$Pos <- factor(NBA$Pos, levels = c("C", "PF", "SF", "SG", "PG"))
NBA_Scaled$Pos <- factor(NBA_Scaled$Pos, levels = c("C", "PF", "SF", "SG", "PG"))
PosColorCode <- c("C"="#FF0000", "PF"="#FFA500", "SF"="#DDDD00" ,"SG"="#0000FF", "PG"="#32CD32")


Displaying main table

NBA




POINTS


Points, commonly perceived as the most substantial indicator to value a player’s competence to compete in the NBA. After all, the team that has recorded the most points at the end of a game is declared that game’s winner. So, let the analyzing begin…

Points can be aggregated from three different grounds:

  1. Field Goals (FG): for each FG successfully made from within the three-point line, the player scores 2 points.
  2. Three-Points Field Goal (3P): for each FG successfully made from beyond the three-point line, the player scores 3 points.
  3. Free-Throw (FT): for each FT successfully made, the player scores 1 point.



Annual Team Points per Game


I will start from a wider scope down into the smaller ones. So let’s start with total points accumulated per team year-by-year.

Since the dataset is a player statistics, not the team, it’s not so easy to get the data that I want, so I came up with these following method:

  1. Calculate total points collected each year from all players.
  2. Calculate number of teams participated in NBA competition each year.
  3. Calculate number of games each team has to play each year.
  4. Calculate total NBA games in each year by multiplying the number of games and number of teams participated then divide it by two (each game two teams compete with each other).
  5. Calculate average team score in each game by dividing total points NBA collected each year by the total number of games each year.

And to make the plot more meaningful, why not let it tell where those points generated from?

So here we go..


Team_Scores <- NBA %>%
    group_by(Year) %>%
    summarise(Total_PTS = sum(PTS),
            Total_2FG = sum(X2P)*2,
            Total_3FG = sum(X3P, na.rm = T)*3,
            Total_FT = sum(FT),
            nTeam = n_distinct(Tm),
            nGames = max(G),
            Total_G = round((nGames*nTeam)/2, 0),
            FG2 = round((Total_2FG/Total_G)/2, 2),
            FG3 = round((Total_3FG/Total_G)/2, 2),
            FT = round((Total_FT/Total_G)/2, 2),
            avg_Tm_Scores = round((Total_PTS/Total_G)/2, 2)) %>%
    gather(Parameter, Count, FG2:FT, -c(Year:Total_G, avg_Tm_Scores))
Team_Scores %>%
    group_by(Year, Parameter) %>%
    ggplot(aes(Year, Count, fill=forcats::fct_rev(Parameter))) + 
    geom_bar(stat="identity") +
    geom_hline(yintercept = mean(Team_Scores$avg_Tm_Scores), col = "blue", alpha = 0.5) +
    ggtitle("Annual Team Scores per Game") +
    guides(fill=guide_legend(title="")) +
    xlab("Year") +
    ylab("Score per Game") +
    scale_fill_manual(name="", values=c("#FF5555", "#008500", "#0055FF")) +
    scale_x_continuous(breaks = seq(1950, 2020, 10)) +
    theme(legend.position="bottom")


  • Average team score per game across the NBA history is 101.55.
  • The lowest average team score is 72.26 in the 1950-1951 season.
  • The highest average team score is 116.75 in the 1960-1961 season.
  • 2-points FG is accounted for 70% of all points collected, averaging 72.86 per game.
  • 3-points FG is accounted for 9.9% of all points collected, averaging 7.22 per game, And it’s 22.5% of points collected in this decade.
  • Free throws are accounted for 20.1% of points, averaging 21.46 per game.



Annual Points per Game by Position


Points per game (PPG) is a standard gauge used to measure a player’s ability to scores points. It is calculated by dividing the total number of points by the number of games.

Here I wanted to compare annual PPG in each position among themselves and to average. We do that first by making a yearly table with all the positions spread and filled with their respective annual PPG and then add the annual average column.


MeanPPG <- NBA %>%
    group_by(Year) %>%
    summarise(Average = round(sum(PTS)/sum(G), 2))
AvgPosPPGbyYear <- NBA %>%
    group_by(Year, Pos) %>%
    summarise(PPG = round(sum(PTS)/sum(G), 1)) %>%
    dcast(Year ~ Pos) %>%
    bind_cols(MeanPPG[,2])
AvgPosPPGbyYear %>%
    kable(escape = FALSE, align='c', caption = "Points per Game by Position") %>%
    kable_styling("striped", full_width = T) %>%
    column_spec(1, bold = T) %>%
    scroll_box(width = "100%", height = "300px")
Points per Game by Position
Year C PF SF SG PG Average
1950 9.2 8.1 7.7 7.5 8.3 8.02
1951 12.4 8.4 7.7 8.2 9.6 8.82
1952 11.0 9.1 9.0 8.3 9.1 9.21
1953 11.4 8.8 7.8 9.1 8.6 9.03
1954 13.6 7.7 6.7 8.7 8.3 8.67
1955 12.4 10.6 10.0 8.7 9.8 10.23
1956 13.7 9.2 10.0 9.2 9.2 10.25
1957 12.0 11.6 9.3 10.2 9.2 10.49
1958 12.3 12.3 12.0 10.0 9.5 11.23
1959 10.5 12.0 13.3 10.3 10.1 11.27
1960 12.8 12.3 14.3 11.2 10.2 12.17
1961 12.1 12.4 13.2 12.1 12.3 12.42
1962 14.8 11.2 14.2 12.0 11.3 12.67
1963 12.6 11.6 12.3 11.8 11.6 12.02
1964 12.1 12.3 11.9 10.9 11.0 11.65
1965 12.1 11.7 10.8 11.4 11.8 11.52
1966 11.8 11.8 11.3 12.5 13.6 12.14
1967 10.6 10.5 13.8 12.8 13.0 12.18
1968 11.1 11.9 12.7 12.7 12.5 12.19
1969 12.0 10.9 11.4 12.9 13.2 12.01
1970 12.0 11.1 11.8 11.9 14.3 12.17
1971 11.4 9.9 11.9 11.3 13.5 11.50
1972 11.4 9.9 12.3 10.6 13.2 11.38
1973 9.8 11.0 10.9 11.5 12.4 11.07
1974 9.5 10.1 11.3 11.7 11.6 10.83
1975 9.3 10.2 10.1 10.4 11.8 10.28
1976 9.4 9.9 10.1 11.3 11.3 10.38
1977 10.0 10.8 10.4 11.6 9.3 10.45
1978 11.2 10.0 11.4 12.9 9.4 11.04
1979 10.5 10.8 11.8 11.5 10.9 11.13
1980 10.7 10.2 12.3 12.5 9.4 11.06
1981 10.3 10.1 12.0 12.2 9.6 10.84
1982 10.2 9.4 11.3 11.5 10.0 10.50
1983 9.4 10.6 11.6 10.6 10.4 10.55
1984 10.0 9.6 12.5 11.6 9.3 10.61
1985 9.1 10.5 12.3 11.9 10.1 10.78
1986 9.4 9.6 12.9 11.0 10.1 10.57
1987 8.8 10.2 12.2 12.0 10.2 10.72
1988 8.9 10.1 12.4 12.3 9.3 10.56
1989 8.2 9.8 13.1 12.5 9.9 10.62
1990 8.8 10.1 12.3 12.3 9.3 10.55
1991 8.3 9.4 12.3 12.1 10.6 10.49
1992 8.1 10.3 11.8 11.9 9.8 10.40
1993 8.4 10.5 10.8 12.5 9.6 10.38
1994 8.9 9.3 10.6 11.6 9.4 9.95
1995 9.0 9.1 11.0 11.5 9.5 9.96
1996 8.4 9.2 10.9 11.1 9.9 9.87
1997 7.7 9.4 10.7 11.1 9.5 9.68
1998 8.4 9.0 10.6 10.5 8.7 9.45
1999 7.7 8.4 10.0 9.9 8.4 8.86
2000 7.9 9.2 10.9 11.2 8.5 9.49
2001 7.0 9.7 10.2 11.7 8.6 9.37
2002 7.2 9.3 10.7 10.9 9.3 9.44
2003 6.8 10.3 10.3 10.8 9.3 9.40
2004 6.7 9.7 9.2 11.4 9.3 9.24
2005 6.9 10.3 10.9 10.0 10.1 9.57
2006 6.5 10.0 10.5 10.9 10.2 9.58
2007 8.4 8.3 11.2 11.3 9.6 9.67
2008 8.4 9.0 11.6 11.0 9.4 9.84
2009 8.4 10.0 10.6 10.9 10.0 9.97
2010 8.6 10.2 10.0 11.0 10.1 9.97
2011 7.9 10.2 9.9 10.7 10.1 9.75
2012 7.6 9.1 9.0 10.2 9.9 9.19
2013 8.1 9.6 8.8 10.0 10.5 9.38
2014 8.1 10.2 9.3 9.9 11.1 9.73
2015 8.7 8.9 8.7 10.2 10.7 9.48
2016 9.0 8.6 9.8 10.5 10.7 9.72
2017 9.4 8.9 10.1 10.3 11.2 9.98


With line plot, it will be easy to see which position is hot or cold in each year by using average line as a threshold.

PosPPG <- AvgPosPPGbyYear %>%
    plot_ly(x = ~Year, opacity=0.5) %>%
    add_lines(y = ~C, name = "C", mode = 'lines', line=list(color='#FF0000')) %>%
    add_lines(y = ~PF, name = "PF", mode = 'lines', line=list(color='#FFA500')) %>%
    add_lines(y = ~SF, name = "SF", mode = 'lines', line=list(color='#DDDD00')) %>%
    add_lines(y = ~SG, name = "SG", mode = 'lines', line=list(color='#0000FF')) %>%
    add_lines(y = ~PG, name = "PG", mode = 'lines', line=list(color='#32CD32')) %>%
    add_lines(y = ~Average, name = "Average", mode = 'marker', opacity=1) %>%
    layout(legend = list(orientation = 'h'),
           title = 'Annual Points per Game by Position',
           yaxis = list(title = "Points per Game"),
           xaxis = list(title = ""))
#PosPPGChart <- api_create(PosPPG, filename = "PosPPG")
#PosPPGChart
PosPPG

click image for interactive plotly graph


Now, this may be looked like a messy spaghetti plot. This is where the plot_ly come to the rescue when you click on compare data on hover and then go hover your pointer along the lines horizontally. Now the spaghetti with the right sauce may satisfy your appetite.



Position Compared for Accumulated Points by Decade

Now I’d like to see total points distribution to each position. But instead of the yearly accumulation I want to see by decades instead. This way it looks more pleasant to eyes while I learn to make grouped stacked bar plot.

NBA %>%
    mutate(Decade = as.factor(ifelse(Year %in% 1950:1959, "1950-59",
                               ifelse(Year %in% 1960:1969, "1960-69",
                                      ifelse(Year %in% 1970:1979, "1970-79",
                                             ifelse(Year %in% 1980:1989, "1980-89",
                                                    ifelse(Year %in% 1990:1999, "1990-99",
                                                           ifelse(Year %in% 2000:2010, "2000-09",
                                                                  "2010+")))))))) %>%
    group_by(Pos, Decade) %>%
    summarise(PTS = sum(PTS)) %>%
    ggplot(aes(Pos, PTS, fill=forcats::fct_rev(Decade))) + 
    geom_bar(stat="identity") +
    ggtitle("Points Distribution by Position") +
    guides(fill=guide_legend(title="Decades")) +
    xlab("Position") +
    ylab("Points") +
    scale_fill_brewer(palette="Set2")



All-Time Points per Game Leaders


Many of us NBA fans probably already know that Michael Jordan, followed closely by Wilt Chamberlain, are in the top leaderboard of All-Time Points per Game. What I’m intrigued to see is how their annual PPG rise and fall shaped like throughout their career. Here we gonna see top 10 all-time leaders in PPG get compared.

First, I need to create the leaderboard table as a base for generating the plot. This probably the most celebrated table that you can find easily on the web. Even so, I refrain myself to see it until mine done, then I take a look at the official table to make sure I did not make any mistake.

AllTimePPG <- NBA %>%
    group_by(Player) %>%
    summarise(Pos = getmode(Pos),
              Team = getmode(Tm),
              ActiveYears = paste(getmode(YearStart), "-", getmode(YearEnd)),
              Games = sum(G),
              Points = sum(PTS),
              PPG = round(Points/Games, 2)) %>%
    filter(Games >= 400 | Points > 10000) %>%
    arrange(desc(PPG)) %>%
    head(n=20) %>%
    mutate(Rank = dense_rank(desc(PPG))) %>%
    select(Rank, everything())
AllTimePPG %>%
    mutate(Pos = cell_spec(Pos, color = "white", align = "c", 
                    background = factor(Pos, c("C", "PF", "SF", "SG", "PG"), 
                                        PosColorCode))) %>%
    kable(escape = FALSE, caption = "All-Time Points per Game Leaders") %>%
    kable_styling("striped", full_width = T) %>%
    column_spec(2, bold = T) %>%
    column_spec(1, bold = T, color = "yellow", background = "#FF0000") %>%
    column_spec(8, bold = T, color = "white", background = "#777777") %>%
    scroll_box(width = "100%", height = "300px")
All-Time Points per Game Leaders
Rank Player Pos Team ActiveYears Games Points PPG
1 Michael Jordan SG CHI 1985 - 2003 1072 32292 30.12
2 Wilt Chamberlain C LAL 1960 - 1973 1045 31419 30.07
3 Elgin Baylor SF LAL 1959 - 1972 846 23149 27.36
4 Kevin Durant SF OKC 2008 - 2018 703 19121 27.20
5 LeBron James SF CLE 2004 - 2018 1061 28787 27.13
6 Jerry West PG LAL 1961 - 1974 932 25192 27.03
7 Allen Iverson SG PHI 1997 - 2010 914 24368 26.66
8 Bob Pettit PF STL 1955 - 1965 792 20880 26.36
9 George Gervin SG SAS 1973 - 1986 791 20708 26.18
10 Oscar Robertson PG CIN 1961 - 1974 1040 26710 25.68
11 Karl Malone PF UTA 1986 - 2004 1476 36928 25.02
12 Kobe Bryant SG LAL 1997 - 2016 1346 33643 24.99
13 Dominique Wilkins SF ATL 1983 - 1999 1074 26668 24.83
14 Carmelo Anthony SF DEN 2004 - 2018 976 24156 24.75
15 Kareem Abdul-Jabbar C LAL 1970 - 1989 1560 38387 24.61
16 Larry Bird SF BOS 1980 - 1992 897 21791 24.29
17 Adrian Dantley SF UTA 1977 - 1991 955 23177 24.27
18 Pete Maravich SG NOJ 1971 - 1980 658 15948 24.24
19 Shaquille O'Neal C LAL 1993 - 2011 1207 28596 23.69
20 Dwyane Wade SG MIA 2004 - 2018 915 21317 23.30


Generating plot…

PPGAll <- NBA %>%
    filter(Player %in% head(AllTimePPG$Player, 10)) %>%
    group_by(Age, Player) %>%
    summarise(Games = sum(G),
              Points = sum(PTS),
              PPG = round(Points/Games, 2)) %>%
    ggplot() +
    geom_line(aes(Age, PPG, color=Player), alpha = 1) +
    ggtitle("All-Time Top Scorers' Chronological PPG") +
    theme(legend.position="bottom")
#ggplotly(PPGAll, session="knitr", kwargs=list(filename="PPGall_knitr", fileopt="overwrite"))
PPGall

click image for interactive plotly graph


Again, add your favored sauce to your spaghetti by click on “Compare data on hover” or double click desired player on the legend to isolate one trace.

Now let’s see PPG leader in each position.

TopPPGPos <- AllTimePPG %>%
    group_by(Pos) %>%
    summarise(Player = Player[which.max(PPG)],
              Team = Team[which.max(PPG)],
              ActiveYears = ActiveYears[which.max(PPG)],
              Games = Games[which.max(PPG)],
              PPG = PPG[which.max(PPG)])

Best Points per Game by Position

C


Wilt Chamberlain

30.07
  • Team: LAL
  • Games: 1045
  • Years: 1960 - 1973

PF


Bob Pettit

26.36
  • Team: STL
  • Games: 792
  • Years: 1955 - 1965

SF


Elgin Baylor

27.36
  • Team: LAL
  • Games: 846
  • Years: 1959 - 1972

SG


Michael Jordan

30.12
  • Team: CHI
  • Games: 1072
  • Years: 1985 - 2003

PG


Jerry West

27.03
  • Team: LAL
  • Games: 932
  • Years: 1961 - 1974





Annual Points per Game Leaders


Still exploring Points per Game, we now see annual leaders and then plot it for our eyes delight.

TopScorer <- NBA %>%
    group_by(Year) %>%
    summarise(Player = as.character(Player[which.max(PpG)]),
              Team = Tm[which.max(PpG)],
              Pos = Pos[which.max(PpG)],
              Age = Age[which.max(PpG)],
              Games = G[which.max(PpG)],
              Shooting = TS.[which.max(PpG)],
              Top_PPG = max(round(PpG, 2))) %>%
    arrange(desc(Year))
TopScorer %>%
    mutate(Pos = cell_spec(Pos, color = "white", align = "c", 
                    background = factor(Pos, c("C", "PF", "SF", "SG", "PG"), 
                                        PosColorCode))) %>%
    kable(escape = FALSE, caption = "Annual Points per Game Leaders") %>%
    kable_styling("striped", full_width = T) %>%
    column_spec(1, bold = T, color = "yellow", background = "#FF0000") %>%
    column_spec(2, bold = T) %>%
    column_spec(8, bold = T, color = "white", background = "#777777") %>%
    scroll_box(width = "100%", height = "300px")
Annual Points per Game Leaders
Year Player Team Pos Age Games Shooting Top_PPG
2017 Russell Westbrook OKC PG 28 81 0.554 31.58
2016 Stephen Curry GSW PG 27 79 0.669 30.06
2015 Russell Westbrook OKC PG 26 67 0.536 28.15
2014 Kevin Durant OKC SF 25 81 0.635 32.01
2013 Carmelo Anthony NYK PF 28 67 0.560 28.66
2012 Kevin Durant OKC SF 23 66 0.610 28.03
2011 Kevin Durant OKC SF 22 78 0.589 27.71
2010 Kevin Durant OKC SF 21 82 0.607 30.15
2009 Dwyane Wade MIA SG 27 79 0.574 30.20
2008 LeBron James CLE SF 23 75 0.568 30.00
2007 Kobe Bryant LAL SG 28 77 0.580 31.56
2006 Kobe Bryant LAL SG 27 80 0.559 35.40
2005 Allen Iverson PHI PG 29 75 0.532 30.69
2004 Tracy McGrady ORL SG 24 67 0.526 28.03
2003 Tracy McGrady ORL SG 23 75 0.564 32.09
2002 Allen Iverson PHI SG 26 60 0.489 31.38
2001 Allen Iverson PHI SG 25 71 0.518 31.08
2000 Shaquille O'Neal LAL C 27 79 0.578 29.67
1999 Allen Iverson PHI SG 23 48 0.508 26.75
1998 Michael Jordan CHI SG 34 82 0.533 28.74
1997 Michael Jordan CHI SG 33 82 0.567 29.65
1996 Michael Jordan CHI SG 32 82 0.582 30.38
1995 Shaquille O'Neal ORL C 22 79 0.588 29.30
1994 David Robinson SAS C 28 80 0.577 29.79
1993 Michael Jordan CHI SG 29 78 0.564 32.58
1992 Michael Jordan CHI SG 28 80 0.579 30.05
1991 Michael Jordan CHI SG 27 82 0.605 31.46
1990 Michael Jordan CHI SG 26 82 0.606 33.57
1989 Michael Jordan CHI SG 25 81 0.614 32.51
1988 Michael Jordan CHI SG 24 82 0.603 34.98
1987 Michael Jordan CHI SG 23 82 0.562 37.09
1986 Dominique Wilkins ATL SF 26 78 0.536 30.33
1985 Bernard King NYK SF 28 55 0.585 32.89
1984 Adrian Dantley UTA SF 27 79 0.652 30.61
1983 Adrian Dantley UTA SF 26 22 0.661 30.73
1982 George Gervin SAS SG 29 79 0.562 32.29
1981 Adrian Dantley UTA SF 24 80 0.622 30.65
1980 George Gervin SAS SG 27 78 0.587 33.14
1979 George Gervin SAS SF 26 80 0.591 29.56
1978 John Williamson NJN SG 26 33 0.509 29.48
1977 Pete Maravich NOJ SG 29 73 0.492 31.14
1976 Bob McAdoo BUF C 24 78 0.542 31.12
1975 Bob McAdoo BUF C 23 82 0.569 34.52
1974 Bob McAdoo BUF C 22 74 0.594 30.55
1973 Tiny Archibald KCO PG 24 80 0.555 33.99
1972 Kareem Abdul-Jabbar MIL C 24 81 0.603 34.84
1971 Kareem Abdul-Jabbar MIL C 23 82 0.606 31.66
1970 Jerry West LAL PG 31 74 0.572 31.20
1969 Elvin Hayes SDR C 23 82 0.483 28.38
1968 Oscar Robertson CIN PG 29 65 0.588 29.17
1967 Rick Barry SFW SF 22 78 0.531 35.58
1966 Wilt Chamberlain PHI C 29 79 0.547 33.53
1965 Wilt Chamberlain SFW C 28 38 0.495 38.95
1964 Wilt Chamberlain SFW C 27 80 0.537 36.85
1963 Wilt Chamberlain SFW C 26 80 0.550 44.83
1962 Wilt Chamberlain PHW C 25 80 0.536 50.36
1961 Wilt Chamberlain PHW C 24 79 0.519 38.39
1960 Wilt Chamberlain PHW C 23 72 0.493 37.60
1959 Bob Pettit STL PF 26 72 0.519 29.24
1958 George Yardley DET SF 29 72 0.505 27.79
1957 Paul Arizin PHW SF 28 71 0.515 25.59
1956 Bob Pettit STL C 23 72 0.502 25.68
1955 Neil Johnston PHW C 25 72 0.536 22.65
1954 Neil Johnston PHW C 24 72 0.531 24.43
1953 Neil Johnston PHW C 23 70 0.534 22.34
1952 Paul Arizin PHW SF 23 66 0.546 25.36
1951 George Mikan MNL C 26 68 0.509 28.41
1950 George Mikan MNL C 25 68 0.487 27.43


Let’s now generate the plot with the same color-coded position with additional NBA average line.

PPGmean <- NBA %>%
    group_by(Year) %>%
    summarise(meanPPG = mean(sum(PTS)/sum(G), na.rm = T))
TS <- TopScorer %>%
    ggplot() +
    geom_bar(aes(Year, Top_PPG, fill = Pos, text = paste("Player:", Player)), stat = "identity") +
    geom_line(aes(Year, meanPPG, linetype = "Average line"), data = PPGmean, color = "black") +
    ggtitle("Top Scorer by Year") +
    scale_x_continuous(breaks = seq(1950, 2020, 10)) +
    scale_fill_manual("Pos", values = PosColorCode) +
    ylab("Top Points per Game") +
    theme(legend.position='none')
TSplotly <- ggplotly(TS, session="knitr", kwargs=list(filename="TS_knitr", fileopt="overwrite"))
api_create(TSplotly, filename = "AnnualTS")
Found a grid already named: 'AnnualTS Grid'. Since fileopt='overwrite', I'll try to update it
Found a plot already named: 'AnnualTS'. Since fileopt='overwrite', I'll try to update it
AnnualTS

click image for interactive plotly graph

You can hover over the bars to see the details. We can clearly see the pattern which shows Wilt Chamberlain and Michael Jordan era, followed by a short Durrant era.


  • Highest PPG leader in a season: 50.36 by Wilt Chamberlain in 1962
  • Lowest PPG leader in a season: 22.34 by Neil Johnston in 1953
  • Average PPG leader all season: 31.24, compares to overall average PPG NBA: 10.41
  • Most seasons leading the league in PPG: 10 by Michael Jordan



Points per 36 Minutes


The concept of per-minute statistics is a basic building block of statistical analysis in basketball. While looking at per-game averages gives us some information about the productivity of a player, per-minute averages tell us a great deal more about how well, or poorly, that player is actually performing. (source)

As we know full-length of an NBA game is 48 minutes, divided by 4 quarters so it’s 12 minutes each quarter. So why 36? Well, as the argument goes, the NBA average for a starter is about 36 minutes (they play 3 quarters out of 4 quarters), and 36 is the more realistic way to measure stats as not many players play 40+ minutes, let alone 48.

PTS36 <- NBA %>%
    group_by(Player) %>%
    summarise(Pos = getmode(Pos),
              ActiveYears = paste(getmode(YearStart), "-", getmode(YearEnd)),
              Games = sum(G),
              MP = sum(MP),
              Points = sum(PTS),
              MPG = round(MP/Games, 2),
              PPG = round(Points/Games, 2),
              PPM36 = round(PPG/MPG * 36, 2)) %>%
    filter(Games > 100) %>%
    arrange(desc(PPM36)) %>%
    mutate(Rank = dense_rank(desc(PPM36))) %>%
    select(Rank, everything(), -c(MP, Points)) %>%
    head(n=20)
PTS36 %>%
    mutate(Pos = cell_spec(Pos,
                            color = "white",
                            align = "c",
                            background = factor(Pos, c("C", "PF", "SF", "SG", "PG"),
                                                PosColorCode))) %>%
    kable(escape = FALSE, caption = "Points per 36 Minutes") %>%
    kable_styling("striped", full_width = T) %>%
    column_spec(2, bold = T) %>%
    column_spec(1, bold = T, color = "yellow", background = "#FF0000") %>%
    column_spec(8, bold = T, color = "white", background = "#777777") %>%
    scroll_box(width = "100%", height = "300px")
Points per 36 Minutes
Rank Player Pos ActiveYears Games MPG PPG PPM36
1 Michael Jordan SG 1985 - 2003 1072 38.26 30.12 28.34
2 George Gervin SG 1973 - 1986 791 33.55 26.18 28.09
3 Kevin Durant SF 2008 - 2018 703 37.38 27.20 26.20
4 Freeman Williams SG 1979 - 1986 323 20.45 14.67 25.82
5 John Drew SF 1975 - 1985 739 29.54 20.69 25.21
6 Dominique Wilkins SF 1983 - 1999 1074 35.49 24.83 25.19
7 LeBron James SF 2004 - 2018 1061 38.90 27.13 25.11
8 Kobe Bryant SG 1997 - 2016 1346 36.13 24.99 24.90
9 David Thompson SG 1976 - 1984 509 32.03 22.13 24.87
10 Jerry West PG 1961 - 1974 932 39.24 27.03 24.80
11 Carmelo Anthony SF 2004 - 2018 976 36.20 24.75 24.61
11 Elgin Baylor SF 1959 - 1972 846 40.03 27.36 24.61
12 Shaquille O'Neal C 1993 - 2011 1207 34.73 23.69 24.56
13 Bob Pettit PF 1955 - 1965 792 38.75 26.36 24.49
14 Adrian Dantley SF 1977 - 1991 955 35.76 24.27 24.43
15 Walter Davis SG 1978 - 1992 1033 27.94 18.90 24.35
16 Karl Malone PF 1986 - 2004 1476 37.16 25.02 24.24
17 Alex English SF 1977 - 1991 1193 31.91 21.47 24.22
18 Kareem Abdul-Jabbar C 1970 - 1989 1560 36.82 24.61 24.06
19 Bernard King SF 1978 - 1993 874 33.66 22.49 24.05


For the plot, I’d like to see the discrepancy between points per 36 minutes (PTS36) and points per game (PPG), sorted by PTS36 rank.

PTS36 %>%
    mutate(Player = reorder(Player, desc(PPM36), FUN=median)) %>%
    ggplot(aes(group = 1)) +
    geom_segment(aes(x=Player, xend=Player, y=PPM36, yend=PPG), color="black") +
    geom_point(aes(Player, PPM36, color="#FF5800"), size=5) +
    geom_point(aes(Player, PPG, color="#009dff"), size=3, shape=18) +
    theme(axis.text.x = element_text(angle = 90, hjust = 1)) +
    ggtitle("Points per 36 Minutes vs. Points per Game") +
    xlab("") +
    ylab("PTS36 vs. PPG") +
    scale_color_manual(name="", values=c("#009dff", "#FF5800"),
                       labels=c("Points/Game", "Points/36 minutes"),
                       guide = guide_legend(reverse=TRUE)) +
    theme(legend.position="bottom")


This simply shows how well they scores and how much chance their coach give them to play in a game. If the blue diamond is inside the orange circle, it means their minutes per game (MPG) is around 36 minutes, if it falls below the circle then their MPG is lower than 36 and vice versa.



Most Points Career


Now we about to see the most points accumulated by a player throughout his career in the NBA.

PTSMost <- NBA %>%
    group_by(Player) %>%
    filter(n_distinct(Born) == 1) %>%
    summarise(Pos = getmode(Position),
              ActiveYears = paste(getmode(YearStart), "-", getmode(YearEnd)),
              YearsActive = n_distinct(Year),
              Games = sum(G),
              MP = sum(MP),
              MPG = round(MP/Games, 2),
              Points = as.numeric(sum(PTS)),
              PPG = round(Points/Games, 2)) %>%
    filter(Games > 100) %>%
    arrange(desc(Points)) %>%
    head(n=20) %>%
    mutate(Rank = rank(desc(Points))) %>%
    select(Rank, everything(), -c(MP))
PTSMost %>%
    kable(escape = FALSE, caption = "Most Points Career") %>%
    kable_styling("striped", full_width = T) %>%
    column_spec(2, bold = T) %>%
    column_spec(1, bold = T, color = "yellow", background = "#FF0000") %>%
    column_spec(8, bold = T, color = "white", background = "#777777") %>%
    scroll_box(width = "100%", height = "300px")
Most Points Career
Rank Player Pos ActiveYears YearsActive Games MPG Points PPG
1 Kareem Abdul-Jabbar C 1970 - 1989 20 1560 36.82 38387 24.61
2 Karl Malone PF 1986 - 2004 19 1476 37.16 36928 25.02
3 Kobe Bryant SG 1997 - 2016 20 1346 36.13 33643 24.99
4 Michael Jordan SG 1985 - 2003 15 1072 38.26 32292 30.12
5 Wilt Chamberlain C 1960 - 1973 14 1045 45.80 31419 30.07
6 Dirk Nowitzki PF 1999 - 2018 19 1394 34.92 30260 21.71
7 LeBron James SF 2004 - 2018 14 1061 38.90 28787 27.13
8 Shaquille O'Neal C 1993 - 2011 19 1207 34.73 28596 23.69
9 Moses Malone C 1975 - 1995 19 1329 33.91 27409 20.62
10 Elvin Hayes PF 1969 - 1984 16 1303 38.37 27313 20.96
11 Hakeem Olajuwon C 1985 - 2002 18 1238 35.72 26946 21.77
12 Oscar Robertson PG 1961 - 1974 14 1040 42.20 26710 25.68
13 Dominique Wilkins SF 1983 - 1999 15 1074 35.49 26668 24.83
14 Tim Duncan C 1998 - 2016 19 1392 34.03 26496 19.03
15 Paul Pierce SF 1999 - 2017 19 1343 34.16 26397 19.66
16 John Havlicek SF 1963 - 1978 16 1270 36.59 26395 20.78
17 Kevin Garnett PF 1996 - 2016 21 1462 34.49 26071 17.83
18 Alex English SF 1977 - 1991 15 1193 31.91 25613 21.47
19 Reggie Miller SG 1988 - 2005 18 1389 34.28 25279 18.20
20 Jerry West PG 1961 - 1974 14 932 39.24 25192 27.03


Generating plot…

PTSMost %>%
    head(10) %>%
    arrange(Points) %>%
    mutate(Player=factor(Player, Player)) %>%
    ggplot(aes(Player, Points)) +
    geom_segment(aes(x=Player, xend=Player, y=0, yend=Points),
                 color="gray11", linetype = "dotted", size=1, alpha=0.6) +
    geom_point(color="orangered1", size=4) +
    geom_text(aes(label=Points), hjust=0.5, vjust=-1.5, size=3) +
    ggtitle("Most Points Career") +
    coord_flip() +
    xlab("") +
    ylab("Points")



Finding the BIGGEST Top Scorers in the League


Now that we have seen the best scorers in the league in several categories, I’d like to proceed with finding the biggest name in scoring by combining all category from the previous leaderboards:

  1. Top 20 All-Time Points per Game Leaders
  2. Annual Points per Game Leaders
  3. Top 20 Points per 36 Minutes Leaders
  4. Top 20 Most Points Leaders

My arbitrary method to measure their prominence as a scorer by using tokens as a valuation unit described as follows:

  • Rank 1 earn 10 tokens
  • Rank 2 earn 7 tokens
  • Rank 3 earn 4 tokens
  • Rank 4 to 10 earn 2 tokens
  • Rank 11 to 20 earn 1 token
  • Each annual PPG leader earn 1 token
  • PPG must not below 18.0

Then we can rank them based on the total tokens they earn.

So let’s get started…

# Step 1: Calculating tokens for All-Time Points per Game leaderboard:
PTSValuation1 <- AllTimePPG %>%
    group_by(Player) %>%
    summarise(n = (ifelse(Rank == 1, 10, ifelse(Rank == 2, 7,
                        ifelse(Rank == 3, 4, ifelse(Rank%in%c(4:10), 2, 1)))))) %>%
    mutate(Player = as.character(Player)) %>%
    arrange(desc(n))
# Step 2: Calculating tokens for All-Time Points per Game leaderboard:
PTSValuation2 <- TopScorer %>% count(Player) %>% arrange(desc(n))
# Step 3: Calculating tokens for Points per 36 Minutes leaderboard:
PTSValuation3 <- PTS36 %>%
    group_by(Player) %>%
    filter(PPG > 16) %>%
    summarise(n = (ifelse(Rank == 1, 10, ifelse(Rank == 2, 7,
                        ifelse(Rank == 3, 4, ifelse(Rank%in%c(4:10), 2, 1)))))) %>%
    mutate(Player = as.character(Player)) %>%
    arrange(desc(n))
# Step 4: Calculating tokens for Most Points in career leaderboard:
PTSValuation4 <- PTSMost %>%
    group_by(Player) %>%
    summarise(n = (ifelse(Rank == 1, 10, ifelse(Rank == 2, 7, 
                    ifelse(Rank == 3, 4, ifelse(Rank%in%c(4:10), 2, 1)))))) %>%
    mutate(Player = as.character(Player)) %>%
    arrange(desc(n))
# Step 5: Merge the dataframes and calculating total tokens:
AllScorers <- data.frame(Player = c(PTSValuation1$Player,
                                    PTSValuation2$Player,
                                    PTSValuation3$Player,
                                    PTSValuation4$Player),
                         Tokens = c(PTSValuation1$n,
                                    PTSValuation2$n,
                                    PTSValuation3$n,
                                    PTSValuation4$n)) %>%
    group_by(Player) %>%
    summarise(Total = sum(Tokens)) %>%
    arrange(desc(Total))
# Step 6: Display the final table:
AllScorers %>%
    group_by(Player) %>%
    mutate(Pos = getmode(NBA$Position[NBA$Player %in% Player]),
           PPG = round(sum(NBA$PTS[NBA$Player %in% Player])/sum(NBA$G[NBA$Player %in% Player]), 2),
           Tokens = paste(strrep("|", Total))) %>%
    select(-Total,everything()) %>%
    filter(PPG > 16) %>%
    kable(escape = FALSE, caption = "BIGGEST Top Scorers") %>%
    kable_styling(bootstrap_options = "striped", full_width = F, font_size = 11) %>%
    column_spec(1, bold = T) %>%
    column_spec(4, bold = T, color = "gold") %>%
    column_spec(5, bold = T, color = "white", background = "#777777", width = "8px") %>%
    scroll_box(width = "60%", height = "500px") %>%
    kable_styling(position = "float_left")
BIGGEST Top Scorers
Player Pos PPG Tokens Total
Michael Jordan SG 30.12 |||||||||||||||||||||||||||||||| 32
Wilt Chamberlain C 30.07 |||||||||||||||| 16
Kareem Abdul-Jabbar C 24.61 |||||||||||||| 14
George Gervin SG 26.18 |||||||||||| 12
Kevin Durant SF 27.20 |||||||||| 10
Karl Malone PF 25.02 ||||||||| 9
Kobe Bryant SG 24.99 ||||||||| 9
LeBron James SF 27.13 ||||||| 7
Allen Iverson SG 26.66 |||||| 6
Jerry West PG 27.03 |||||| 6
Shaquille O'Neal C 23.69 |||||| 6
Adrian Dantley SF 24.27 ||||| 5
Bob Pettit PF 26.36 ||||| 5
Dominique Wilkins SF 24.83 ||||| 5
Elgin Baylor SF 27.36 ||||| 5
Oscar Robertson PG 25.68 |||| 4
Bob McAdoo C 22.05 ||| 3
Carmelo Anthony SF 24.75 ||| 3
Elvin Hayes PF 20.96 ||| 3
Neil Johnston C 19.42 ||| 3
Alex English SF 21.47 || 2
Bernard King SF 22.49 || 2
David Thompson SG 22.13 || 2
Dirk Nowitzki PF 21.71 || 2
Dwyane Wade SG 23.30 || 2
George Mikan C 22.32 || 2
John Drew SF 20.69 || 2
Moses Malone C 20.62 || 2
Paul Arizin SF 22.81 || 2
Pete Maravich SG 24.24 || 2
Russell Westbrook PG 22.69 || 2
Tracy McGrady SG 19.60 || 2
David Robinson C 21.06 | 1
George Yardley SF 19.20 | 1
Hakeem Olajuwon C 21.77 | 1
John Havlicek SF 20.78 | 1
John Williamson SG 20.15 | 1
Kevin Garnett PF 17.83 | 1
Larry Bird SF 24.29 | 1
Paul Pierce SF 19.66 | 1
Reggie Miller SG 18.20 | 1
Rick Barry SF 23.17 | 1
Stephen Curry PG 22.80 | 1
Tim Duncan C 19.03 | 1
Tiny Archibald PG 18.81 | 1
Walter Davis SG 18.90 | 1


Based on this calculation, Michael Jordan remains the all-time king of the scorer in the NBA. Let’s see their names from the biggest name to smaller ones…

AllScorers %>%
    wordcloud2(size=0.5, color='random-light', backgroundColor="black")



SuperScorers FG-3P-FT Ratio


Now let’s take the best eight top scorers (who has 7 or more tokens based on our previous valuation), named them SuperScorers, then find out where all those points they made generated from.

Bear in mind that in the NBA, the three-point line was introduced in the 1979–80 season. And our three out of eight SuperScorers (Chamberlain, Gervin, and Abdul-Jabbar) are been active in the field since before then. With that being said, let us now see where their points accumulated from.


Here’s the table…

SuperScorers <- NBA %>%
    filter(Player %in% head(AllScorers$Player, 8)) %>%
    group_by(Player) %>%
    summarise(Pos = getmode(Position),
              ActiveYears = paste(getmode(YearStart), "-", getmode(YearEnd)),
              YearsActive = n_distinct(Year),
              Games = sum(G),
              FGx2 = sum(X2P) * 2,
              FGx3 = sum(X3P, na.rm = T) * 3,
              FT = sum(FT),
              PTS = sum(PTS),
              PPG = round(PTS/Games, 2)) %>%
    arrange(desc(PPG))
SuperScorers %>%
    kable(escape = FALSE, caption = "SuperScorers 2Pts-3Pts-FT Ratio") %>%
    kable_styling("striped", full_width = T) %>%
    column_spec(1, bold = T)
SuperScorers 2Pts-3Pts-FT Ratio
Player Pos ActiveYears YearsActive Games FGx2 FGx3 FT PTS PPG
Michael Jordan SG 1985 - 2003 15 1072 23222 1743 7327 32292 30.12
Wilt Chamberlain C 1960 - 1973 14 1045 25362 0 6057 31419 30.07
Kevin Durant SF 2008 - 2018 10 703 10406 3780 4935 19121 27.20
LeBron James SF 2004 - 2018 14 1061 17912 4401 6474 28787 27.13
George Gervin SG 1973 - 1986 10 791 15936 231 4541 20708 26.18
Karl Malone PF 1986 - 2004 19 1476 26886 255 9787 36928 25.02
Kobe Bryant SG 1997 - 2016 20 1346 19784 5481 8378 33643 24.99
Kareem Abdul-Jabbar C 1970 - 1989 20 1560 31672 3 6712 38387 24.61


And let’s see the plot…

SuperScorers %>%
    gather(Parameter, Points, -c(Player:Games, PTS:PPG)) %>%
    select(-c(Pos, Games, PTS)) %>%
    mutate(Player = factor(Player, levels = c("Kareem Abdul-Jabbar", "Kobe Bryant", "Karl Malone", "George Gervin", "LeBron James", "Kevin Durant", "Wilt Chamberlain", "Michael Jordan"))) %>%
    ggplot(aes(Player, Points, fill=forcats::fct_rev(Parameter))) +
    geom_bar( stat="identity", position="fill") +
    ggtitle("Points Breakdown Among SuperScorers") +
    coord_flip() +
    xlab("") +
    ylab("Shooting") +
    guides(fill = guide_legend(reverse = TRUE)) +
    theme(legend.position="bottom") +
    theme(legend.title=element_blank())



Scoring efficiency among the SuperScorers


Let’s take a step further by comparing their shooting capability to each other and to the NBA average with radar plot.

Since unavailability of the data (in this case three-points data) will mess up the plot, I exclude those three old-timers from our list so we proceed with 5 players.

The variables used for comparison:

  • X2Pm: normalized 2 points Field Goal made per minute
  • X2Pa: normalized 2 points Field Goal attempt per minute
  • X2P.: normalized 2 points Field Goal percentage
  • X3Pm: normalized 3 points Field Goal made per minute
  • X3Pa: normalized 3 points Field Goal attempt per minute
  • X3P.: normalized 3 points Field Goal percentage
  • FTm: normalized Free Throw made per minute
  • FTa: normalized Free Throw attempt per minute
  • FT.: normalized Free Throw percentage
  • TS.: normalized true shooting percentage
  • eFG.: normalized effective field goal percentage
RadarScore <- NBA_Scaled %>%
    group_by(Player) %>%
    summarise(X2Pm = mean(X2P_pM),
              X2Pa = mean(X2PA_pM),
              X2P. = mean(X2P., na.rm=T),
              X3Pm = mean(X3P_pM),
              X3Pa = mean(X3PA_pM),
              X3P. = mean(X3P., na.rm=T),
              FTm = mean(FT_pM),
              FTa = mean(FTA_pM),
              FT. = mean(FT., na.rm=T),
              TS. = mean(TS., na.rm=T),
              eFG. = mean(eFG., na.rm=T)) %>%
    filter(Player %in% SuperScorers$Player) %>%
    select(-Player)
radarPPG <- plot_ly(type = 'scatterpolar',
        fill = 'toself',
        mode = 'lines') %>%
    add_trace(r = as.numeric(as.vector(RadarScore[7,])),
              theta = as.character(as.vector(colnames(RadarScore))),
              name = 'Michael Jordan') %>%
    add_trace(r = as.numeric(as.vector(RadarScore[4,])),
              theta = as.character(as.vector(colnames(RadarScore))),
              name = 'Kevin Durant') %>%
    add_trace(r = as.numeric(as.vector(RadarScore[6,])),
              theta = as.character(as.vector(colnames(RadarScore))),
              name = 'LeBron James') %>%
    add_trace(r = as.numeric(as.vector(RadarScore[3,])),
              theta = as.character(as.vector(colnames(RadarScore))),
              name = 'Karl Malone') %>%
    add_trace(r = as.numeric(as.vector(RadarScore[5,])),
              theta = as.character(as.vector(colnames(RadarScore))),
              name = 'Kobe Bryant') %>%
    layout(polar = list(radialaxis = list(visible = T,
                                          range = c(-1.5, 1.5))))
api_create(radarPPG, filename = "radarPPG")
Found a grid already named: 'radarPPG Grid'. Since fileopt='overwrite', I'll try to update it
Found a plot already named: 'radarPPG'. Since fileopt='overwrite', I'll try to update it


radarPPG

click image for interactive plotly graph

How to interpret the plot:

  • Click on the ‘player legend’ in the upper right of the graph to select/unselect each position, double-click to isolate the trace. You can compare them in any way as you desire.
  • “0” in the scale is the average of each variable, anything below “0” tells us that the player performance in that particular category is below average, and vice versa.


  • Michael Jordan leads the group in both X2Pm and X2Pa category.
  • Kevin Durant leads the group in all three-points category (X3Pm, X3Pa, X3P.), FTm, FT., TS., and eFG.. He trails in X2PA category.
  • LeBron James leads the group in X2P. category. He trails in FTm category.
  • Karl Malone leads the group in FTa category. He trails in all three-points (X3Pm, X3Pa, X3P.) and FT. category, since he’s the only post player in the group.
  • Kobe Bryant trails in X2Pm, X2P., FTa, TS., eFG. category, which made him the most ineffective scorer in this Superscorer group.




End of Session


LS0tDQp0aXRsZTogJ1Zpc3VhbGl6aW5nIE5CQSBTZWFzb25zIFBhcnQgMjogUG9pbnRzJw0KYXV0aG9yOiAiTnVubm8gTnVncm9obyINCm91dHB1dDoNCiAgaHRtbF9ub3RlYm9vazoNCiAgICBjc3M6IHN0eWxlLmNzcw0KICAgIHRoZW1lOiBwYXBlcg0KICAgIGNvZGVfZm9sZGluZzogaGlkZQ0KICBodG1sX2RvY3VtZW50Og0KICAgIGNzczogc3R5bGUuY3NzDQogICAgdGhlbWU6IHBhcGVyDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQotLS0NCg0KPGJyLz4NCjxici8+DQoNCjxkaXYgY2xhc3M9ImhlYWRlciI+DQoNCjxjZW50ZXI+PGltZyBzcmM9ImltYWdlcy9OQkFMb2dvVHJhbnNwLnBuZyIgYWx0PSJkcmF3aW5nIiB3aWR0aD0iMjAwcHgiIGhlaWd0aD0iMTAwcHgiLz4NCg0KKipTZWFzb25zIDE5NTAtMjAxNyoqDQoNCjxkaXYgY2xhc3M9ImRyb3Bkb3duIj4NCg0KPGJ1dHRvbiBjbGFzcz0iZHJvcGJ0biI+UGFydCAyOiBQb2ludHM8L2J1dHRvbj4NCg0KPGRpdiBjbGFzcz0iZHJvcGRvd24tY29udGVudCI+DQoNCjxhIGhyZWY9Imh0dHBzOi8vcnB1YnMuY29tL25pbmphenpsZS9OQkFTZWFzb25zMCI+SW50cm9kdWN0aW9uICYgUHJlcGFyYXRpb248L2E+DQoNCjxhIGhyZWY9Imh0dHBzOi8vcnB1YnMuY29tL25pbmphenpsZS9OQkFTZWFzb25zMSI+UGFydCAxOiBQbGF5ZXJzPC9hPg0KDQo8YSBocmVmPSJodHRwczovL3JwdWJzLmNvbS9uaW5qYXp6bGUvTkJBU2Vhc29uczMiPlBhcnQgMzogU2hvb3RpbmdzIChGRyBhbmQgRlQpPC9hPg0KDQo8YSBocmVmPSJodHRwczovL3JwdWJzLmNvbS9uaW5qYXp6bGUvTkJBU2Vhc29uczQiPlBhcnQgNDogU2hvb3RpbmdzICgzLVBvaW50cyBhbmQgTWl4ZWQpPC9hPg0KDQo8YSBocmVmPSJodHRwczovL3JwdWJzLmNvbS9uaW5qYXp6bGUvTkJBU2Vhc29uczUiPlBhcnQgNTogU2tpbGxzPC9hPg0KDQo8YSBocmVmPSJodHRwczovL3JwdWJzLmNvbS9uaW5qYXp6bGUvTkJBU2Vhc29uczYiPlBhcnQgNjogUm9sZXM8L2E+DQoNCjwvZGl2Pg0KDQo8L2Rpdj4NCg0KPC9jZW50ZXI+DQoNCjwvZGl2Pg0KDQo8YnIvPg0KPGJyLz4NCg0KLS0tDQoNCjxici8+DQoNCiMgU2V0dXAgU3RhZ2UNCg0KPGJyLz4NCg0KTG9hZGluZyBuZWNlc3NhcnkgcGFja2FnZXMuDQoNCmBgYHtyIHNldHVwLCBtZXNzYWdlPUZBTFNFfQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkodGlkeXIpDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KFJDb2xvckJyZXdlcikNCmxpYnJhcnkocGxvdGx5KQ0KbGlicmFyeShyZXNoYXBlMikNCmxpYnJhcnkoa2FibGVFeHRyYSkNCmxpYnJhcnkod29yZGNsb3VkMikNCmBgYA0KDQo8YnIvPg0KDQpDcmVhdGUgYWRkaXRpb25hbCBmdW5jdGlvbnMNCg0KYGBge3J9DQojIE1vZGUgYXZlcmFnZQ0KZ2V0bW9kZSA8LSBmdW5jdGlvbih2KSB7DQogICB1bmlxdiA8LSB1bmlxdWUodikNCiAgIHVuaXF2W3doaWNoLm1heCh0YWJ1bGF0ZShtYXRjaCh2LCB1bmlxdikpKV0NCn0NCmBgYA0KDQo8YnIvPg0KDQojIyMjIExvYWQgdGhlIGRhdGFzZXQNCg0KPGJyLz4NCg0KRGF0YXNldCB0YWtlbiBmcm9tOiBodHRwczovL3d3dy5rYWdnbGUuY29tL2RyZ2lsZXJtby9uYmEtcGxheWVycy1zdGF0cy92ZXJzaW9uLzINCg0KSWYgeW91IG5lZWQgdG8gc2VlIHRoZSBnbG9zc2FyeSBhbmQgdGhlIGRhdGEgdGlkeWluZyBwcm9jZXNzLCBwbGVhc2UgdmlzaXQgdGhlIGZpcnN0IHBhcnQgW2hlcmVdKGh0dHBzOi8vcnB1YnMuY29tL25pbmphenpsZS9OQkFTZWFzb25zMCkuDQoNCkxvYWRpbmcgZGF0YXNldHMuLi4NCg0KYGBge3J9DQpOQkEgPC0gcmVhZC5jc3YoIk5CQV9UaWR5U2V0LmNzdiIpWywtYygxKV0NCk5CQV9TY2FsZWQgPC0gcmVhZC5jc3YoIk5CQV9TY2FsZWRfVGlkeVNldC5jc3YiKQ0KTkJBJFBvcyA8LSBmYWN0b3IoTkJBJFBvcywgbGV2ZWxzID0gYygiQyIsICJQRiIsICJTRiIsICJTRyIsICJQRyIpKQ0KTkJBX1NjYWxlZCRQb3MgPC0gZmFjdG9yKE5CQV9TY2FsZWQkUG9zLCBsZXZlbHMgPSBjKCJDIiwgIlBGIiwgIlNGIiwgIlNHIiwgIlBHIikpDQpQb3NDb2xvckNvZGUgPC0gYygiQyI9IiNGRjAwMDAiLCAiUEYiPSIjRkZBNTAwIiwgIlNGIj0iI0REREQwMCIgLCJTRyI9IiMwMDAwRkYiLCAiUEciPSIjMzJDRDMyIikNCmBgYA0KDQo8YnIvPg0KDQoqKkRpc3BsYXlpbmcgbWFpbiB0YWJsZSoqDQoNCjxkaXYgY2xhc3M9J21haW50YWJsZSc+DQoNCmBgYHtyfQ0KTkJBDQpgYGANCjwvZGl2Pg0KDQo8YnIvPg0KDQotLS0NCg0KPGJyLz4NCg0KIyBQT0lOVFMNCg0KPGJyLz4NCg0KUG9pbnRzLCBjb21tb25seSBwZXJjZWl2ZWQgYXMgdGhlIG1vc3Qgc3Vic3RhbnRpYWwgaW5kaWNhdG9yIHRvIHZhbHVlIGEgcGxheWVyJ3MgY29tcGV0ZW5jZSB0byBjb21wZXRlIGluIHRoZSBOQkEuIEFmdGVyIGFsbCwgdGhlIHRlYW0gdGhhdCBoYXMgcmVjb3JkZWQgdGhlIG1vc3QgcG9pbnRzIGF0IHRoZSBlbmQgb2YgYSBnYW1lIGlzIGRlY2xhcmVkIHRoYXQgZ2FtZSdzIHdpbm5lci4gU28sIGxldCB0aGUgYW5hbHl6aW5nIGJlZ2luLi4uIA0KDQpQb2ludHMgY2FuIGJlIGFnZ3JlZ2F0ZWQgZnJvbSB0aHJlZSBkaWZmZXJlbnQgZ3JvdW5kczoNCg0KMS4gKipGaWVsZCBHb2FscyAoRkcpKio6IGZvciBlYWNoIEZHIHN1Y2Nlc3NmdWxseSBtYWRlIGZyb20gd2l0aGluIHRoZSB0aHJlZS1wb2ludCBsaW5lLCB0aGUgcGxheWVyIHNjb3JlcyAyIHBvaW50cy4NCjIuICoqVGhyZWUtUG9pbnRzIEZpZWxkIEdvYWwgKDNQKSoqOiBmb3IgZWFjaCBGRyBzdWNjZXNzZnVsbHkgbWFkZSBmcm9tIGJleW9uZCB0aGUgdGhyZWUtcG9pbnQgbGluZSwgdGhlIHBsYXllciBzY29yZXMgMyBwb2ludHMuDQozLiAqKkZyZWUtVGhyb3cgKEZUKSoqOiBmb3IgZWFjaCBGVCBzdWNjZXNzZnVsbHkgbWFkZSwgdGhlIHBsYXllciBzY29yZXMgMSBwb2ludC4NCg0KPGJyLz4NCjxici8+DQoNCiMjIyBBbm51YWwgVGVhbSBQb2ludHMgcGVyIEdhbWUgDQoNCjxkaXYgY2xhc3M9IkJveCI+DQoNCjxici8+DQoNCkkgd2lsbCBzdGFydCBmcm9tIGEgd2lkZXIgc2NvcGUgZG93biBpbnRvIHRoZSBzbWFsbGVyIG9uZXMuIFNvIGxldCdzIHN0YXJ0IHdpdGggdG90YWwgcG9pbnRzIGFjY3VtdWxhdGVkIHBlciB0ZWFtIHllYXItYnkteWVhci4NCg0KU2luY2UgdGhlIGRhdGFzZXQgaXMgYSBwbGF5ZXIgc3RhdGlzdGljcywgbm90IHRoZSB0ZWFtLCBpdCdzIG5vdCBzbyBlYXN5IHRvIGdldCB0aGUgZGF0YSB0aGF0IEkgd2FudCwgc28gSSBjYW1lIHVwIHdpdGggdGhlc2UgZm9sbG93aW5nIG1ldGhvZDoNCg0KMS4gQ2FsY3VsYXRlICoqdG90YWwgcG9pbnRzKiogY29sbGVjdGVkIGVhY2ggeWVhciBmcm9tIGFsbCBwbGF5ZXJzLg0KMi4gQ2FsY3VsYXRlICoqbnVtYmVyIG9mIHRlYW1zKiogcGFydGljaXBhdGVkIGluIE5CQSBjb21wZXRpdGlvbiBlYWNoIHllYXIuDQozLiBDYWxjdWxhdGUgKipudW1iZXIgb2YgZ2FtZXMqKiBlYWNoIHRlYW0gaGFzIHRvIHBsYXkgZWFjaCB5ZWFyLg0KNC4gQ2FsY3VsYXRlICoqdG90YWwgTkJBIGdhbWVzKiogaW4gZWFjaCB5ZWFyIGJ5IG11bHRpcGx5aW5nIHRoZSBudW1iZXIgb2YgZ2FtZXMgYW5kIG51bWJlciBvZiB0ZWFtcyBwYXJ0aWNpcGF0ZWQgdGhlbiBkaXZpZGUgaXQgYnkgdHdvIChlYWNoIGdhbWUgdHdvIHRlYW1zIGNvbXBldGUgd2l0aCBlYWNoIG90aGVyKS4NCjUuIENhbGN1bGF0ZSAqKmF2ZXJhZ2UgdGVhbSBzY29yZSoqIGluIGVhY2ggZ2FtZSBieSBkaXZpZGluZyB0b3RhbCBwb2ludHMgTkJBIGNvbGxlY3RlZCBlYWNoIHllYXIgYnkgdGhlIHRvdGFsIG51bWJlciBvZiBnYW1lcyBlYWNoIHllYXIuDQoNCkFuZCB0byBtYWtlIHRoZSBwbG90IG1vcmUgbWVhbmluZ2Z1bCwgd2h5IG5vdCBsZXQgaXQgdGVsbCB3aGVyZSB0aG9zZSBwb2ludHMgZ2VuZXJhdGVkIGZyb20/DQoNClNvIGhlcmUgd2UgZ28uLg0KDQoNCg0KPGJyLz4NCg0KYGBge3IgZmlnLndpZHRoPTksIGZpZy5oZWlnaHQ9NX0NClRlYW1fU2NvcmVzIDwtIE5CQSAlPiUNCiAgICBncm91cF9ieShZZWFyKSAlPiUNCiAgICBzdW1tYXJpc2UoVG90YWxfUFRTID0gc3VtKFBUUyksDQogICAgICAgICAgICBUb3RhbF8yRkcgPSBzdW0oWDJQKSoyLA0KICAgICAgICAgICAgVG90YWxfM0ZHID0gc3VtKFgzUCwgbmEucm0gPSBUKSozLA0KICAgICAgICAgICAgVG90YWxfRlQgPSBzdW0oRlQpLA0KICAgICAgICAgICAgblRlYW0gPSBuX2Rpc3RpbmN0KFRtKSwNCiAgICAgICAgICAgIG5HYW1lcyA9IG1heChHKSwNCiAgICAgICAgICAgIFRvdGFsX0cgPSByb3VuZCgobkdhbWVzKm5UZWFtKS8yLCAwKSwNCiAgICAgICAgICAgIEZHMiA9IHJvdW5kKChUb3RhbF8yRkcvVG90YWxfRykvMiwgMiksDQogICAgICAgICAgICBGRzMgPSByb3VuZCgoVG90YWxfM0ZHL1RvdGFsX0cpLzIsIDIpLA0KICAgICAgICAgICAgRlQgPSByb3VuZCgoVG90YWxfRlQvVG90YWxfRykvMiwgMiksDQogICAgICAgICAgICBhdmdfVG1fU2NvcmVzID0gcm91bmQoKFRvdGFsX1BUUy9Ub3RhbF9HKS8yLCAyKSkgJT4lDQogICAgZ2F0aGVyKFBhcmFtZXRlciwgQ291bnQsIEZHMjpGVCwgLWMoWWVhcjpUb3RhbF9HLCBhdmdfVG1fU2NvcmVzKSkNCg0KVGVhbV9TY29yZXMgJT4lDQogICAgZ3JvdXBfYnkoWWVhciwgUGFyYW1ldGVyKSAlPiUNCiAgICBnZ3Bsb3QoYWVzKFllYXIsIENvdW50LCBmaWxsPWZvcmNhdHM6OmZjdF9yZXYoUGFyYW1ldGVyKSkpICsgDQogICAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiKSArDQogICAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gbWVhbihUZWFtX1Njb3JlcyRhdmdfVG1fU2NvcmVzKSwgY29sID0gImJsdWUiLCBhbHBoYSA9IDAuNSkgKw0KICAgIGdndGl0bGUoIkFubnVhbCBUZWFtIFNjb3JlcyBwZXIgR2FtZSIpICsNCiAgICBndWlkZXMoZmlsbD1ndWlkZV9sZWdlbmQodGl0bGU9IiIpKSArDQogICAgeGxhYigiWWVhciIpICsNCiAgICB5bGFiKCJTY29yZSBwZXIgR2FtZSIpICsNCiAgICBzY2FsZV9maWxsX21hbnVhbChuYW1lPSIiLCB2YWx1ZXM9YygiI0ZGNTU1NSIsICIjMDA4NTAwIiwgIiMwMDU1RkYiKSkgKw0KICAgIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoMTk1MCwgMjAyMCwgMTApKSArDQogICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJib3R0b20iKQ0KYGBgDQoNCjxici8+DQoNCjxkaXYgY2xhc3M9IkZhY3QiPg0KDQo8dWwgY2xhc3M9IkN1c3RvbUxpc3QiPg0KDQo8bGk+QXZlcmFnZSB0ZWFtIHNjb3JlIHBlciBnYW1lIGFjcm9zcyB0aGUgTkJBIGhpc3RvcnkgaXMgYHIgcm91bmQobWVhbihUZWFtX1Njb3JlcyRhdmdfVG1fU2NvcmVzKSwgMilgLjwvbGk+DQo8bGk+VGhlIGxvd2VzdCBhdmVyYWdlIHRlYW0gc2NvcmUgaXMgYHIgbWluKFRlYW1fU2NvcmVzJGF2Z19UbV9TY29yZXMpYCBpbiB0aGUgYHIgVGVhbV9TY29yZXMkWWVhclt3aGljaC5taW4oVGVhbV9TY29yZXMkYXZnX1RtX1Njb3JlcyldLTFgLWByIFRlYW1fU2NvcmVzJFllYXJbd2hpY2gubWluKFRlYW1fU2NvcmVzJGF2Z19UbV9TY29yZXMpXWAgc2Vhc29uLjwvbGk+DQo8bGk+VGhlIGhpZ2hlc3QgYXZlcmFnZSB0ZWFtIHNjb3JlIGlzIGByIG1heChUZWFtX1Njb3JlcyRhdmdfVG1fU2NvcmVzKWAgaW4gdGhlIGByIFRlYW1fU2NvcmVzJFllYXJbd2hpY2gubWF4KFRlYW1fU2NvcmVzJGF2Z19UbV9TY29yZXMpXS0xYC1gciBUZWFtX1Njb3JlcyRZZWFyW3doaWNoLm1heChUZWFtX1Njb3JlcyRhdmdfVG1fU2NvcmVzKV1gIHNlYXNvbi48L2xpPg0KPGxpPjItcG9pbnRzIEZHIGlzIGFjY291bnRlZCBmb3IgYHIgcm91bmQoc3VtKFRlYW1fU2NvcmVzJFRvdGFsXzJGRykvc3VtKFRlYW1fU2NvcmVzJFRvdGFsX1BUUykgKiAxMDAsIDEpYCUgb2YgYWxsIHBvaW50cyBjb2xsZWN0ZWQsIGF2ZXJhZ2luZyBgciByb3VuZChtZWFuKFRlYW1fU2NvcmVzJENvdW50W1RlYW1fU2NvcmVzJFBhcmFtZXRlcj09IkZHMiJdKSwgMilgIHBlciBnYW1lLjwvbGk+DQo8bGk+My1wb2ludHMgRkcgaXMgYWNjb3VudGVkIGZvciBgciByb3VuZChzdW0oVGVhbV9TY29yZXMkVG90YWxfM0ZHKS9zdW0oVGVhbV9TY29yZXMkVG90YWxfUFRTKSAqIDEwMCwgMSlgJSBvZiBhbGwgcG9pbnRzIGNvbGxlY3RlZCwgYXZlcmFnaW5nIGByIHJvdW5kKG1lYW4oVGVhbV9TY29yZXMkQ291bnRbVGVhbV9TY29yZXMkUGFyYW1ldGVyPT0iRkczIl0pLCAyKWAgcGVyIGdhbWUsIEFuZCBpdCdzIGByIHJvdW5kKHN1bShUZWFtX1Njb3JlcyRUb3RhbF8zRkdbVGVhbV9TY29yZXMkWWVhciA+PSAyMDEwXSkvc3VtKFRlYW1fU2NvcmVzJFRvdGFsX1BUU1tUZWFtX1Njb3JlcyRZZWFyID49IDIwMTBdKSAqIDEwMCwgMSlgJSBvZiBwb2ludHMgY29sbGVjdGVkIGluIHRoaXMgZGVjYWRlLjwvbGk+DQo8bGk+RnJlZSB0aHJvd3MgYXJlIGFjY291bnRlZCBmb3IgYHIgcm91bmQoc3VtKFRlYW1fU2NvcmVzJFRvdGFsX0ZUKS9zdW0oVGVhbV9TY29yZXMkVG90YWxfUFRTKSAqIDEwMCwgMSlgJSBvZiBwb2ludHMsIGF2ZXJhZ2luZyBgciByb3VuZChtZWFuKFRlYW1fU2NvcmVzJENvdW50W1RlYW1fU2NvcmVzJFBhcmFtZXRlcj09IkZUIl0pLCAyKWAgcGVyIGdhbWUuPC9saT4NCg0KPC91bD4NCg0KPC9kaXY+DQoNCjwvZGl2Pg0KDQo8YnIvPg0KPGJyLz4NCg0KIyMjIEFubnVhbCBQb2ludHMgcGVyIEdhbWUgYnkgUG9zaXRpb24NCg0KPGRpdiBjbGFzcz0iQm94Ij4NCg0KPGJyLz4NCg0KUG9pbnRzIHBlciBnYW1lIChQUEcpIGlzIGEgc3RhbmRhcmQgZ2F1Z2UgdXNlZCB0byBtZWFzdXJlIGEgcGxheWVyJ3MgYWJpbGl0eSB0byBzY29yZXMgcG9pbnRzLiBJdCBpcyBjYWxjdWxhdGVkIGJ5IGRpdmlkaW5nIHRoZSB0b3RhbCBudW1iZXIgb2YgcG9pbnRzIGJ5IHRoZSBudW1iZXIgb2YgZ2FtZXMuDQoNCkhlcmUgSSB3YW50ZWQgdG8gY29tcGFyZSBhbm51YWwgUFBHIGluIGVhY2ggcG9zaXRpb24gYW1vbmcgdGhlbXNlbHZlcyBhbmQgdG8gYXZlcmFnZS4gV2UgZG8gdGhhdCBmaXJzdCBieSBtYWtpbmcgYSB5ZWFybHkgdGFibGUgd2l0aCBhbGwgdGhlIHBvc2l0aW9ucyBzcHJlYWQgYW5kIGZpbGxlZCB3aXRoIHRoZWlyIHJlc3BlY3RpdmUgYW5udWFsIFBQRyBhbmQgdGhlbiBhZGQgdGhlIGFubnVhbCBhdmVyYWdlIGNvbHVtbi4NCg0KPGJyLz4NCg0KPGRpdiBjbGFzcz0idGFibGUiPg0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFfQ0KTWVhblBQRyA8LSBOQkEgJT4lDQogICAgZ3JvdXBfYnkoWWVhcikgJT4lDQogICAgc3VtbWFyaXNlKEF2ZXJhZ2UgPSByb3VuZChzdW0oUFRTKS9zdW0oRyksIDIpKQ0KDQpBdmdQb3NQUEdieVllYXIgPC0gTkJBICU+JQ0KICAgIGdyb3VwX2J5KFllYXIsIFBvcykgJT4lDQogICAgc3VtbWFyaXNlKFBQRyA9IHJvdW5kKHN1bShQVFMpL3N1bShHKSwgMSkpICU+JQ0KICAgIGRjYXN0KFllYXIgfiBQb3MpICU+JQ0KICAgIGJpbmRfY29scyhNZWFuUFBHWywyXSkNCg0KQXZnUG9zUFBHYnlZZWFyICU+JQ0KICAgIGthYmxlKGVzY2FwZSA9IEZBTFNFLCBhbGlnbj0nYycsIGNhcHRpb24gPSAiUG9pbnRzIHBlciBHYW1lIGJ5IFBvc2l0aW9uIikgJT4lDQogICAga2FibGVfc3R5bGluZygic3RyaXBlZCIsIGZ1bGxfd2lkdGggPSBUKSAlPiUNCiAgICBjb2x1bW5fc3BlYygxLCBib2xkID0gVCkgJT4lDQogICAgc2Nyb2xsX2JveCh3aWR0aCA9ICIxMDAlIiwgaGVpZ2h0ID0gIjMwMHB4IikNCmBgYA0KPC9kaXY+DQoNCjxici8+DQoNCldpdGggbGluZSBwbG90LCBpdCB3aWxsIGJlIGVhc3kgdG8gc2VlIHdoaWNoIHBvc2l0aW9uIGlzIGhvdCBvciBjb2xkIGluIGVhY2ggeWVhciBieSB1c2luZyBhdmVyYWdlIGxpbmUgYXMgYSB0aHJlc2hvbGQuIA0KDQpgYGB7ciBmaWcud2lkdGggPSA5LCBmaWcuaGVpZ2h0PTV9DQpQb3NQUEcgPC0gQXZnUG9zUFBHYnlZZWFyICU+JQ0KICAgIHBsb3RfbHkoeCA9IH5ZZWFyLCBvcGFjaXR5PTAuNSkgJT4lDQogICAgYWRkX2xpbmVzKHkgPSB+QywgbmFtZSA9ICJDIiwgbW9kZSA9ICdsaW5lcycsIGxpbmU9bGlzdChjb2xvcj0nI0ZGMDAwMCcpKSAlPiUNCiAgICBhZGRfbGluZXMoeSA9IH5QRiwgbmFtZSA9ICJQRiIsIG1vZGUgPSAnbGluZXMnLCBsaW5lPWxpc3QoY29sb3I9JyNGRkE1MDAnKSkgJT4lDQogICAgYWRkX2xpbmVzKHkgPSB+U0YsIG5hbWUgPSAiU0YiLCBtb2RlID0gJ2xpbmVzJywgbGluZT1saXN0KGNvbG9yPScjRERERDAwJykpICU+JQ0KICAgIGFkZF9saW5lcyh5ID0gflNHLCBuYW1lID0gIlNHIiwgbW9kZSA9ICdsaW5lcycsIGxpbmU9bGlzdChjb2xvcj0nIzAwMDBGRicpKSAlPiUNCiAgICBhZGRfbGluZXMoeSA9IH5QRywgbmFtZSA9ICJQRyIsIG1vZGUgPSAnbGluZXMnLCBsaW5lPWxpc3QoY29sb3I9JyMzMkNEMzInKSkgJT4lDQogICAgYWRkX2xpbmVzKHkgPSB+QXZlcmFnZSwgbmFtZSA9ICJBdmVyYWdlIiwgbW9kZSA9ICdtYXJrZXInLCBvcGFjaXR5PTEpICU+JQ0KICAgIGxheW91dChsZWdlbmQgPSBsaXN0KG9yaWVudGF0aW9uID0gJ2gnKSwNCiAgICAgICAgICAgdGl0bGUgPSAnQW5udWFsIFBvaW50cyBwZXIgR2FtZSBieSBQb3NpdGlvbicsDQogICAgICAgICAgIHlheGlzID0gbGlzdCh0aXRsZSA9ICJQb2ludHMgcGVyIEdhbWUiKSwNCiAgICAgICAgICAgeGF4aXMgPSBsaXN0KHRpdGxlID0gIiIpKQ0KDQojUG9zUFBHQ2hhcnQgPC0gYXBpX2NyZWF0ZShQb3NQUEcsIGZpbGVuYW1lID0gIlBvc1BQRyIpDQojUG9zUFBHQ2hhcnQNCmBgYA0KDQo8ZGl2PjxhIGhyZWY9Imh0dHBzOi8vcGxvdC5seS9+TnVuTm9EZUNhYnVuaWMvMTIvP3NoYXJlX2tleT16VVVVd0Y3RnhTTXBMYm1jNWxXN3E0IiB0YXJnZXQ9Il9ibGFuayIgdGl0bGU9IlBvc1BQRyIgc3R5bGU9ImRpc3BsYXk6IGJsb2NrOyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48aW1nIHNyYz0iaHR0cHM6Ly9wbG90Lmx5L35OdW5Ob0RlQ2FidW5pYy8xMi5wbmc/c2hhcmVfa2V5PXpVVVV3RjdGeFNNcExibWM1bFc3cTQiIGFsdD0iUG9zUFBHIiBzdHlsZT0ibWF4LXdpZHRoOiAxMDAlO3dpZHRoOiA4ODBweDsiICB3aWR0aD0iODgwIiBvbmVycm9yPSJ0aGlzLm9uZXJyb3I9bnVsbDt0aGlzLnNyYz0naHR0cHM6Ly9wbG90Lmx5LzQwNC5wbmcnOyIgLz48L2E+PHNjcmlwdCBkYXRhLXBsb3RseT0iTnVuTm9EZUNhYnVuaWM6MTIiIHNoYXJla2V5LXBsb3RseT0ielVVVXdGN0Z4U01wTGJtYzVsVzdxNCIgc3JjPSJodHRwczovL3Bsb3QubHkvZW1iZWQuanMiIGFzeW5jPjwvc2NyaXB0PjwvZGl2Pg0KDQoqY2xpY2sgaW1hZ2UgZm9yIGludGVyYWN0aXZlIHBsb3RseSBncmFwaCoNCg0KPGJyLz4NCg0KTm93LCB0aGlzIG1heSBiZSBsb29rZWQgbGlrZSBhIG1lc3N5IHNwYWdoZXR0aSBwbG90LiBUaGlzIGlzIHdoZXJlIHRoZSBgcGxvdF9seWAgY29tZSB0byB0aGUgcmVzY3VlIHdoZW4geW91IGNsaWNrIG9uICpjb21wYXJlIGRhdGEgb24gaG92ZXIqIGFuZCB0aGVuIGdvIGhvdmVyIHlvdXIgcG9pbnRlciBhbG9uZyB0aGUgbGluZXMgaG9yaXpvbnRhbGx5LiBOb3cgdGhlIHNwYWdoZXR0aSB3aXRoIHRoZSByaWdodCBzYXVjZSBtYXkgc2F0aXNmeSB5b3VyIGFwcGV0aXRlLiANCg0KPC9kaXY+DQoNCjxici8+DQo8YnIvPg0KDQojIyMgUG9zaXRpb24gQ29tcGFyZWQgZm9yIEFjY3VtdWxhdGVkIFBvaW50cyBieSBEZWNhZGUgDQoNCjxkaXYgY2xhc3M9J0JveCcpDQoNCjxici8+DQoNCk5vdyBJJ2QgbGlrZSB0byBzZWUgdG90YWwgcG9pbnRzIGRpc3RyaWJ1dGlvbiB0byBlYWNoIHBvc2l0aW9uLiBCdXQgaW5zdGVhZCBvZiB0aGUgeWVhcmx5IGFjY3VtdWxhdGlvbiBJIHdhbnQgdG8gc2VlIGJ5IGRlY2FkZXMgaW5zdGVhZC4gVGhpcyB3YXkgaXQgbG9va3MgbW9yZSBwbGVhc2FudCB0byBleWVzIHdoaWxlIEkgbGVhcm4gdG8gbWFrZSBncm91cGVkIHN0YWNrZWQgYmFyIHBsb3QuDQoNCmBgYHtyIGZpZy53aWR0aCA9IDksIGZpZy5oZWlnaHQ9NX0NCk5CQSAlPiUNCiAgICBtdXRhdGUoRGVjYWRlID0gYXMuZmFjdG9yKGlmZWxzZShZZWFyICVpbiUgMTk1MDoxOTU5LCAiMTk1MC01OSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKFllYXIgJWluJSAxOTYwOjE5NjksICIxOTYwLTY5IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKFllYXIgJWluJSAxOTcwOjE5NzksICIxOTcwLTc5IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShZZWFyICVpbiUgMTk4MDoxOTg5LCAiMTk4MC04OSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKFllYXIgJWluJSAxOTkwOjE5OTksICIxOTkwLTk5IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKFllYXIgJWluJSAyMDAwOjIwMTAsICIyMDAwLTA5IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICIyMDEwKyIpKSkpKSkpKSAlPiUNCiAgICBncm91cF9ieShQb3MsIERlY2FkZSkgJT4lDQogICAgc3VtbWFyaXNlKFBUUyA9IHN1bShQVFMpKSAlPiUNCiAgICBnZ3Bsb3QoYWVzKFBvcywgUFRTLCBmaWxsPWZvcmNhdHM6OmZjdF9yZXYoRGVjYWRlKSkpICsgDQogICAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiKSArDQogICAgZ2d0aXRsZSgiUG9pbnRzIERpc3RyaWJ1dGlvbiBieSBQb3NpdGlvbiIpICsNCiAgICBndWlkZXMoZmlsbD1ndWlkZV9sZWdlbmQodGl0bGU9IkRlY2FkZXMiKSkgKw0KICAgIHhsYWIoIlBvc2l0aW9uIikgKw0KICAgIHlsYWIoIlBvaW50cyIpICsNCiAgICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlPSJTZXQyIikNCmBgYA0KPC9kaXY+DQoNCjxici8+DQo8YnIvPg0KDQojIyMgQWxsLVRpbWUgUG9pbnRzIHBlciBHYW1lIExlYWRlcnMNCg0KPGRpdiBjbGFzcz0iQm94Ij4NCg0KPGJyLz4NCg0KTWFueSBvZiB1cyBOQkEgZmFucyBwcm9iYWJseSBhbHJlYWR5IGtub3cgdGhhdCBNaWNoYWVsIEpvcmRhbiwgZm9sbG93ZWQgY2xvc2VseSBieSBXaWx0IENoYW1iZXJsYWluLCBhcmUgaW4gdGhlIHRvcCBsZWFkZXJib2FyZCBvZiBBbGwtVGltZSBQb2ludHMgcGVyIEdhbWUuIFdoYXQgSSdtIGludHJpZ3VlZCB0byBzZWUgaXMgaG93IHRoZWlyIGFubnVhbCBQUEcgcmlzZSBhbmQgZmFsbCBzaGFwZWQgbGlrZSB0aHJvdWdob3V0IHRoZWlyIGNhcmVlci4gSGVyZSB3ZSBnb25uYSBzZWUgdG9wIDEwIGFsbC10aW1lIGxlYWRlcnMgaW4gUFBHIGdldCBjb21wYXJlZC4NCg0KRmlyc3QsIEkgbmVlZCB0byBjcmVhdGUgdGhlIGxlYWRlcmJvYXJkIHRhYmxlIGFzIGEgYmFzZSBmb3IgZ2VuZXJhdGluZyB0aGUgcGxvdC4gVGhpcyBwcm9iYWJseSB0aGUgbW9zdCBjZWxlYnJhdGVkIHRhYmxlIHRoYXQgeW91IGNhbiBmaW5kIGVhc2lseSBvbiB0aGUgd2ViLiBFdmVuIHNvLCBJIHJlZnJhaW4gbXlzZWxmIHRvIHNlZSBpdCB1bnRpbCBtaW5lIGRvbmUsIHRoZW4gSSB0YWtlIGEgbG9vayBhdCB0aGUgb2ZmaWNpYWwgdGFibGUgdG8gbWFrZSBzdXJlIEkgZGlkIG5vdCBtYWtlIGFueSBtaXN0YWtlLiANCg0KPGRpdiBjbGFzcz0idGFibGUiPg0KDQpgYGB7ciBmaWcud2lkdGggPSA5LCBmaWcuY2FwPSJBbGwifQ0KQWxsVGltZVBQRyA8LSBOQkEgJT4lDQogICAgZ3JvdXBfYnkoUGxheWVyKSAlPiUNCiAgICBzdW1tYXJpc2UoUG9zID0gZ2V0bW9kZShQb3MpLA0KICAgICAgICAgICAgICBUZWFtID0gZ2V0bW9kZShUbSksDQogICAgICAgICAgICAgIEFjdGl2ZVllYXJzID0gcGFzdGUoZ2V0bW9kZShZZWFyU3RhcnQpLCAiLSIsIGdldG1vZGUoWWVhckVuZCkpLA0KICAgICAgICAgICAgICBHYW1lcyA9IHN1bShHKSwNCiAgICAgICAgICAgICAgUG9pbnRzID0gc3VtKFBUUyksDQogICAgICAgICAgICAgIFBQRyA9IHJvdW5kKFBvaW50cy9HYW1lcywgMikpICU+JQ0KICAgIGZpbHRlcihHYW1lcyA+PSA0MDAgfCBQb2ludHMgPiAxMDAwMCkgJT4lDQogICAgYXJyYW5nZShkZXNjKFBQRykpICU+JQ0KICAgIGhlYWQobj0yMCkgJT4lDQogICAgbXV0YXRlKFJhbmsgPSBkZW5zZV9yYW5rKGRlc2MoUFBHKSkpICU+JQ0KICAgIHNlbGVjdChSYW5rLCBldmVyeXRoaW5nKCkpDQoNCkFsbFRpbWVQUEcgJT4lDQogICAgbXV0YXRlKFBvcyA9IGNlbGxfc3BlYyhQb3MsIGNvbG9yID0gIndoaXRlIiwgYWxpZ24gPSAiYyIsIA0KICAgICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kID0gZmFjdG9yKFBvcywgYygiQyIsICJQRiIsICJTRiIsICJTRyIsICJQRyIpLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBQb3NDb2xvckNvZGUpKSkgJT4lDQogICAga2FibGUoZXNjYXBlID0gRkFMU0UsIGNhcHRpb24gPSAiQWxsLVRpbWUgUG9pbnRzIHBlciBHYW1lIExlYWRlcnMiKSAlPiUNCiAgICBrYWJsZV9zdHlsaW5nKCJzdHJpcGVkIiwgZnVsbF93aWR0aCA9IFQpICU+JQ0KICAgIGNvbHVtbl9zcGVjKDIsIGJvbGQgPSBUKSAlPiUNCiAgICBjb2x1bW5fc3BlYygxLCBib2xkID0gVCwgY29sb3IgPSAieWVsbG93IiwgYmFja2dyb3VuZCA9ICIjRkYwMDAwIikgJT4lDQogICAgY29sdW1uX3NwZWMoOCwgYm9sZCA9IFQsIGNvbG9yID0gIndoaXRlIiwgYmFja2dyb3VuZCA9ICIjNzc3Nzc3IikgJT4lDQogICAgc2Nyb2xsX2JveCh3aWR0aCA9ICIxMDAlIiwgaGVpZ2h0ID0gIjMwMHB4IikNCmBgYA0KPC9kaXY+DQoNCjxici8+DQoNCkdlbmVyYXRpbmcgcGxvdC4uLg0KDQpgYGB7ciBmaWcud2lkdGggPSA5LCBmaWcuaGVpZ2h0PTUsIHBsb3RseT1UUlVFfQ0KUFBHQWxsIDwtIE5CQSAlPiUNCiAgICBmaWx0ZXIoUGxheWVyICVpbiUgaGVhZChBbGxUaW1lUFBHJFBsYXllciwgMTApKSAlPiUNCiAgICBncm91cF9ieShBZ2UsIFBsYXllcikgJT4lDQogICAgc3VtbWFyaXNlKEdhbWVzID0gc3VtKEcpLA0KICAgICAgICAgICAgICBQb2ludHMgPSBzdW0oUFRTKSwNCiAgICAgICAgICAgICAgUFBHID0gcm91bmQoUG9pbnRzL0dhbWVzLCAyKSkgJT4lDQogICAgZ2dwbG90KCkgKw0KICAgIGdlb21fbGluZShhZXMoQWdlLCBQUEcsIGNvbG9yPVBsYXllciksIGFscGhhID0gMSkgKw0KICAgIGdndGl0bGUoIkFsbC1UaW1lIFRvcCBTY29yZXJzJyBDaHJvbm9sb2dpY2FsIFBQRyIpICsNCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb249ImJvdHRvbSIpDQoNCiNnZ3Bsb3RseShQUEdBbGwsIHNlc3Npb249ImtuaXRyIiwga3dhcmdzPWxpc3QoZmlsZW5hbWU9IlBQR2FsbF9rbml0ciIsIGZpbGVvcHQ9Im92ZXJ3cml0ZSIpKQ0KYGBgDQoNCjxkaXY+PGEgaHJlZj0iaHR0cHM6Ly9wbG90Lmx5L35OdW5Ob0RlQ2FidW5pYy8xNC8/c2hhcmVfa2V5PTlQOUh6ak1PRVlOeEN6cVlhcnJSWEciIHRhcmdldD0iX2JsYW5rIiB0aXRsZT0iUFBHYWxsIiBzdHlsZT0iZGlzcGxheTogYmxvY2s7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxpbWcgc3JjPSJodHRwczovL3Bsb3QubHkvfk51bk5vRGVDYWJ1bmljLzE0LnBuZz9zaGFyZV9rZXk9OVA5SHpqTU9FWU54Q3pxWWFyclJYRyIgYWx0PSJQUEdhbGwiIHN0eWxlPSJtYXgtd2lkdGg6IDEwMCU7d2lkdGg6IDg4MHB4OyIgIHdpZHRoPSI4ODAiIG9uZXJyb3I9InRoaXMub25lcnJvcj1udWxsO3RoaXMuc3JjPSdodHRwczovL3Bsb3QubHkvNDA0LnBuZyc7IiAvPjwvYT48c2NyaXB0IGRhdGEtcGxvdGx5PSJOdW5Ob0RlQ2FidW5pYzoxNCIgc2hhcmVrZXktcGxvdGx5PSI5UDlIempNT0VZTnhDenFZYXJyUlhHIiBzcmM9Imh0dHBzOi8vcGxvdC5seS9lbWJlZC5qcyIgYXN5bmM+PC9zY3JpcHQ+PC9kaXY+DQoNCipjbGljayBpbWFnZSBmb3IgaW50ZXJhY3RpdmUgcGxvdGx5IGdyYXBoKg0KDQo8YnIvPg0KDQpBZ2FpbiwgYWRkIHlvdXIgZmF2b3JlZCBzYXVjZSB0byB5b3VyIHNwYWdoZXR0aSBieSBjbGljayBvbiAqIkNvbXBhcmUgZGF0YSBvbiBob3ZlciIqIG9yIGRvdWJsZSBjbGljayBkZXNpcmVkIHBsYXllciBvbiB0aGUgbGVnZW5kIHRvIGlzb2xhdGUgb25lIHRyYWNlLiANCg0KTm93IGxldCdzIHNlZSBQUEcgbGVhZGVyIGluIGVhY2ggcG9zaXRpb24uDQoNCmBgYHtyfQ0KVG9wUFBHUG9zIDwtIEFsbFRpbWVQUEcgJT4lDQogICAgZ3JvdXBfYnkoUG9zKSAlPiUNCiAgICBzdW1tYXJpc2UoUGxheWVyID0gUGxheWVyW3doaWNoLm1heChQUEcpXSwNCiAgICAgICAgICAgICAgVGVhbSA9IFRlYW1bd2hpY2gubWF4KFBQRyldLA0KICAgICAgICAgICAgICBBY3RpdmVZZWFycyA9IEFjdGl2ZVllYXJzW3doaWNoLm1heChQUEcpXSwNCiAgICAgICAgICAgICAgR2FtZXMgPSBHYW1lc1t3aGljaC5tYXgoUFBHKV0sDQogICAgICAgICAgICAgIFBQRyA9IFBQR1t3aGljaC5tYXgoUFBHKV0pDQpgYGANCg0KDQoNCjxkaXYgaWQ9IlRvcEJ5UG9zIiBjbGFzcz0iY2xlYXIiPg0KPGgxPkJlc3QgUG9pbnRzIHBlciBHYW1lIGJ5IFBvc2l0aW9uPC9oMT4gIA0KPGRpdiBjbGFzcz0iVG9wUGxheWVyIj4NCjxoMz48c3BhbiBzdHlsZT0iIGNvbG9yOiB3aGl0ZTtib3JkZXItcmFkaXVzOiA0cHg7IHBhZGRpbmctcmlnaHQ6IDRweDsgcGFkZGluZy1sZWZ0OiA0cHg7IGJhY2tncm91bmQtY29sb3I6ICNGRjAwMDA7dGV4dC1hbGlnbjogYzsiID5DPC9zcGFuPjxici8+PHNwYW4+PGltZyBjbGFzcz0icGhvdG8iIHNyYz0iaHR0cHM6Ly9hay1zdGF0aWMuY21zLm5iYS5jb20vd3AtY29udGVudC91cGxvYWRzL2hlYWRzaG90cy9uYmEvbGF0ZXN0LzI2MHgxOTAvNzYzNzUucG5nIiBhbHQ9IiI+PC9pbWc+PC9zcGFuPjwvaDM+DQo8ZGl2IGNsYXNzPSJQTmFtZSI+PGg0Pjxici8+YHIgVG9wUFBHUG9zJFBsYXllclsxXWA8L2g0PjwvZGl2Pg0KPGRpdiBjbGFzcz0iUGN0IiBocmVmPSIiPmByIFRvcFBQR1BvcyRQUEdbMV1gPC9kaXY+ICANCjx1bD4NCjxsaT48Yj5UZWFtOiA8L2I+YHIgVG9wUFBHUG9zJFRlYW1bMV1gPC9saT4NCjxsaT48Yj5HYW1lczogPC9iPmByIFRvcFBQR1BvcyRHYW1lc1sxXWA8L2xpPg0KPGxpPjxiPlllYXJzOiA8L2I+YHIgVG9wUFBHUG9zJEFjdGl2ZVllYXJzWzFdYDwvbGk+CQkJDQo8L3VsPiANCjwvZGl2Pg0KDQo8ZGl2IGNsYXNzPSJUb3BQbGF5ZXIiPg0KPGgzPjxzcGFuIHN0eWxlPSIgY29sb3I6IHdoaXRlO2JvcmRlci1yYWRpdXM6IDRweDsgcGFkZGluZy1yaWdodDogNHB4OyBwYWRkaW5nLWxlZnQ6IDRweDsgYmFja2dyb3VuZC1jb2xvcjogI0ZGQTUwMDt0ZXh0LWFsaWduOiBjOyIgPlBGPC9zcGFuPjxici8+PHNwYW4+PGltZyBjbGFzcz0icGhvdG8iIHNyYz0iaHR0cHM6Ly9pbWFnZS5pYmIuY28vakRxSHRWL1BsYXllci1Cb2ItUGV0dGl0LnBuZyIgYWx0PSIiPjwvaW1nPjwvc3Bhbj48L2gzPg0KPGRpdiBjbGFzcz0iUE5hbWUiPjxoND48YnIvPmByIFRvcFBQR1BvcyRQbGF5ZXJbMl1gPC9oND48L2Rpdj4NCjxkaXYgY2xhc3M9IlBjdCIgaHJlZj0iIj5gciBUb3BQUEdQb3MkUFBHWzJdYDwvZGl2PiAgICAgICAgIA0KPHVsPg0KPGxpPjxiPlRlYW06IDwvYj5gciBUb3BQUEdQb3MkVGVhbVsyXWA8L2xpPg0KPGxpPjxiPkdhbWVzOiA8L2I+YHIgVG9wUFBHUG9zJEdhbWVzWzJdYDwvbGk+DQo8bGk+PGI+WWVhcnM6IDwvYj5gciBUb3BQUEdQb3MkQWN0aXZlWWVhcnNbMl1gPC9saT4JCQkNCjwvdWw+IA0KPC9kaXY+DQoNCjxkaXYgY2xhc3M9IlRvcFBsYXllciI+DQo8aDM+PHNwYW4gc3R5bGU9IiBjb2xvcjogd2hpdGU7Ym9yZGVyLXJhZGl1czogNHB4OyBwYWRkaW5nLXJpZ2h0OiA0cHg7IHBhZGRpbmctbGVmdDogNHB4OyBiYWNrZ3JvdW5kLWNvbG9yOiAjRERERDAwO3RleHQtYWxpZ246IGM7IiA+U0Y8L3NwYW4+PGJyLz48c3Bhbj48aW1nIGNsYXNzPSJwaG90byIgc3JjPSJodHRwczovL2FrLXN0YXRpYy5jbXMubmJhLmNvbS93cC1jb250ZW50L3VwbG9hZHMvaGVhZHNob3RzL25iYS9sYXRlc3QvMjYweDE5MC83NjEyNy5wbmciIGFsdD0iIj48L2ltZz48L3NwYW4+PC9oMz4NCjxkaXYgY2xhc3M9IlBOYW1lIj48aDQ+PGJyLz5gciBUb3BQUEdQb3MkUGxheWVyWzNdYDwvaDQ+PC9kaXY+DQo8ZGl2IGNsYXNzPSJQY3QiIGhyZWY9IiI+YHIgVG9wUFBHUG9zJFBQR1szXWA8L2Rpdj4gICAgICAgICANCjx1bD4NCjxsaT48Yj5UZWFtOiA8L2I+YHIgVG9wUFBHUG9zJFRlYW1bM11gPC9saT4NCjxsaT48Yj5HYW1lczogPC9iPmByIFRvcFBQR1BvcyRHYW1lc1szXWA8L2xpPg0KPGxpPjxiPlllYXJzOiA8L2I+YHIgVG9wUFBHUG9zJEFjdGl2ZVllYXJzWzNdYDwvbGk+CQkNCjwvdWw+IA0KPC9kaXY+DQoNCjxkaXYgY2xhc3M9IlRvcFBsYXllciIgaWQ9IlRvcEFscGhhIj4NCjxoMz48c3BhbiBzdHlsZT0iIGNvbG9yOiB3aGl0ZTtib3JkZXItcmFkaXVzOiA0cHg7IHBhZGRpbmctcmlnaHQ6IDRweDsgcGFkZGluZy1sZWZ0OiA0cHg7IGJhY2tncm91bmQtY29sb3I6ICMwMDAwRkY7dGV4dC1hbGlnbjogYzsiID5TRzwvc3Bhbj48YnIvPjxzcGFuPjxpbWcgY2xhc3M9InBob3RvIiBzcmM9Imh0dHBzOi8vYWstc3RhdGljLmNtcy5uYmEuY29tL3dwLWNvbnRlbnQvdXBsb2Fkcy9oZWFkc2hvdHMvbmJhL2xhdGVzdC8yNjB4MTkwLzg5My5wbmciIGFsdD0iIj48L2ltZz48L3NwYW4+PC9oMz4NCg0KPGRpdiBjbGFzcz0iUE5hbWUiPjxoND48YnIvPmByIFRvcFBQR1BvcyRQbGF5ZXJbNF1gPC9oND48L2Rpdj4NCjxkaXYgY2xhc3M9IlBjdCIgaHJlZj0iIj5gciBUb3BQUEdQb3MkUFBHWzRdYDwvZGl2PiAgICAgICAgIA0KPHVsPg0KPGxpPjxiPlRlYW06IDwvYj5gciBUb3BQUEdQb3MkVGVhbVs0XWA8L2xpPg0KPGxpPjxiPkdhbWVzOiA8L2I+YHIgVG9wUFBHUG9zJEdhbWVzWzRdYDwvbGk+DQo8bGk+PGI+WWVhcnM6IDwvYj5gciBUb3BQUEdQb3MkQWN0aXZlWWVhcnNbNF1gPC9saT4JCQkNCjwvdWw+IA0KPC9kaXY+DQoNCjxkaXYgY2xhc3M9IlRvcFBsYXllciI+DQo8aDM+PHNwYW4gc3R5bGU9IiBjb2xvcjogd2hpdGU7Ym9yZGVyLXJhZGl1czogNHB4OyBwYWRkaW5nLXJpZ2h0OiA0cHg7IHBhZGRpbmctbGVmdDogNHB4OyBiYWNrZ3JvdW5kLWNvbG9yOiAjMzJDRDMyO3RleHQtYWxpZ246IGM7IiA+UEc8L3NwYW4+PGJyLz48c3Bhbj48aW1nIGNsYXNzPSJwaG90byIgc3JjPSJodHRwczovL2ltYWdlLmliYi5jby9oQnd3bnEvUGxheWVyLUplcnJ5LVdlc3QucG5nIiBhbHQ9IiI+PC9pbWc+PC9zcGFuPjwvaDM+DQo8ZGl2IGNsYXNzPSJQTmFtZSI+PGg0Pjxici8+YHIgVG9wUFBHUG9zJFBsYXllcls1XWA8L2g0PjwvZGl2Pg0KPGRpdiBjbGFzcz0iUGN0IiBocmVmPSIiPmByIFRvcFBQR1BvcyRQUEdbNV1gPC9kaXY+ICAgICAgICAgDQo8dWw+DQo8bGk+PGI+VGVhbTogPC9iPmByIFRvcFBQR1BvcyRUZWFtWzVdYDwvbGk+DQo8bGk+PGI+R2FtZXM6IDwvYj5gciBUb3BQUEdQb3MkR2FtZXNbNV1gPC9saT4NCjxsaT48Yj5ZZWFyczogPC9iPmByIFRvcFBQR1BvcyRBY3RpdmVZZWFyc1s1XWA8L2xpPgkJDQo8L3VsPiANCjwvZGl2Pg0KDQo8L2Rpdj4NCg0KPGJyLz4NCjxici8+DQoNCjwvZGl2Pg0KPC9kaXY+DQoNCjxici8+DQo8YnIvPg0KDQojIyMgQW5udWFsIFBvaW50cyBwZXIgR2FtZSBMZWFkZXJzDQoNCjxkaXYgY2xhc3M9IkJveCI+DQoNCjxici8+DQoNClN0aWxsIGV4cGxvcmluZyBQb2ludHMgcGVyIEdhbWUsIHdlIG5vdyBzZWUgYW5udWFsIGxlYWRlcnMgYW5kIHRoZW4gcGxvdCBpdCBmb3Igb3VyIGV5ZXMgZGVsaWdodC4NCg0KPGRpdiBjbGFzcz0idGFibGUiPg0KDQpgYGB7cn0NClRvcFNjb3JlciA8LSBOQkEgJT4lDQogICAgZ3JvdXBfYnkoWWVhcikgJT4lDQogICAgc3VtbWFyaXNlKFBsYXllciA9IGFzLmNoYXJhY3RlcihQbGF5ZXJbd2hpY2gubWF4KFBwRyldKSwNCiAgICAgICAgICAgICAgVGVhbSA9IFRtW3doaWNoLm1heChQcEcpXSwNCiAgICAgICAgICAgICAgUG9zID0gUG9zW3doaWNoLm1heChQcEcpXSwNCiAgICAgICAgICAgICAgQWdlID0gQWdlW3doaWNoLm1heChQcEcpXSwNCiAgICAgICAgICAgICAgR2FtZXMgPSBHW3doaWNoLm1heChQcEcpXSwNCiAgICAgICAgICAgICAgU2hvb3RpbmcgPSBUUy5bd2hpY2gubWF4KFBwRyldLA0KICAgICAgICAgICAgICBUb3BfUFBHID0gbWF4KHJvdW5kKFBwRywgMikpKSAlPiUNCiAgICBhcnJhbmdlKGRlc2MoWWVhcikpDQoNClRvcFNjb3JlciAlPiUNCiAgICBtdXRhdGUoUG9zID0gY2VsbF9zcGVjKFBvcywgY29sb3IgPSAid2hpdGUiLCBhbGlnbiA9ICJjIiwgDQogICAgICAgICAgICAgICAgICAgIGJhY2tncm91bmQgPSBmYWN0b3IoUG9zLCBjKCJDIiwgIlBGIiwgIlNGIiwgIlNHIiwgIlBHIiksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFBvc0NvbG9yQ29kZSkpKSAlPiUNCiAgICBrYWJsZShlc2NhcGUgPSBGQUxTRSwgY2FwdGlvbiA9ICJBbm51YWwgUG9pbnRzIHBlciBHYW1lIExlYWRlcnMiKSAlPiUNCiAgICBrYWJsZV9zdHlsaW5nKCJzdHJpcGVkIiwgZnVsbF93aWR0aCA9IFQpICU+JQ0KICAgIGNvbHVtbl9zcGVjKDEsIGJvbGQgPSBULCBjb2xvciA9ICJ5ZWxsb3ciLCBiYWNrZ3JvdW5kID0gIiNGRjAwMDAiKSAlPiUNCiAgICBjb2x1bW5fc3BlYygyLCBib2xkID0gVCkgJT4lDQogICAgY29sdW1uX3NwZWMoOCwgYm9sZCA9IFQsIGNvbG9yID0gIndoaXRlIiwgYmFja2dyb3VuZCA9ICIjNzc3Nzc3IikgJT4lDQogICAgc2Nyb2xsX2JveCh3aWR0aCA9ICIxMDAlIiwgaGVpZ2h0ID0gIjMwMHB4IikNCmBgYA0KPC9kaXY+DQoNCjxici8+DQoNCkxldCdzIG5vdyBnZW5lcmF0ZSB0aGUgcGxvdCB3aXRoIHRoZSBzYW1lIGNvbG9yLWNvZGVkIHBvc2l0aW9uIHdpdGggYWRkaXRpb25hbCBOQkEgYXZlcmFnZSBsaW5lLg0KDQpgYGB7ciB3YXJuaW5nID0gRkFMU0UsIGZpZy53aWR0aCA9IDksIGZpZy5oZWlnaHQgPSA1LCBwbG90bHk9VFJVRX0NClBQR21lYW4gPC0gTkJBICU+JQ0KICAgIGdyb3VwX2J5KFllYXIpICU+JQ0KICAgIHN1bW1hcmlzZShtZWFuUFBHID0gbWVhbihzdW0oUFRTKS9zdW0oRyksIG5hLnJtID0gVCkpDQoNClRTIDwtIFRvcFNjb3JlciAlPiUNCiAgICBnZ3Bsb3QoKSArDQogICAgZ2VvbV9iYXIoYWVzKFllYXIsIFRvcF9QUEcsIGZpbGwgPSBQb3MsIHRleHQgPSBwYXN0ZSgiUGxheWVyOiIsIFBsYXllcikpLCBzdGF0ID0gImlkZW50aXR5IikgKw0KICAgIGdlb21fbGluZShhZXMoWWVhciwgbWVhblBQRywgbGluZXR5cGUgPSAiQXZlcmFnZSBsaW5lIiksIGRhdGEgPSBQUEdtZWFuLCBjb2xvciA9ICJibGFjayIpICsNCiAgICBnZ3RpdGxlKCJUb3AgU2NvcmVyIGJ5IFllYXIiKSArDQogICAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgxOTUwLCAyMDIwLCAxMCkpICsNCiAgICBzY2FsZV9maWxsX21hbnVhbCgiUG9zIiwgdmFsdWVzID0gUG9zQ29sb3JDb2RlKSArDQogICAgeWxhYigiVG9wIFBvaW50cyBwZXIgR2FtZSIpICsNCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb249J25vbmUnKQ0KDQpUU3Bsb3RseSA8LSBnZ3Bsb3RseShUUywgc2Vzc2lvbj0ia25pdHIiLCBrd2FyZ3M9bGlzdChmaWxlbmFtZT0iVFNfa25pdHIiLCBmaWxlb3B0PSJvdmVyd3JpdGUiKSkNCmFwaV9jcmVhdGUoVFNwbG90bHksIGZpbGVuYW1lID0gIkFubnVhbFRTIikNCmBgYA0KDQo8ZGl2PjxhIGhyZWY9Imh0dHBzOi8vcGxvdC5seS9+TnVuTm9EZUNhYnVuaWMvMjAvP3NoYXJlX2tleT1sbzU0VmdKTU8wdGVrYnZvZ3hyRmViIiB0YXJnZXQ9Il9ibGFuayIgdGl0bGU9IkFubnVhbFRTIiBzdHlsZT0iZGlzcGxheTogYmxvY2s7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxpbWcgc3JjPSJodHRwczovL3Bsb3QubHkvfk51bk5vRGVDYWJ1bmljLzIwLnBuZz9zaGFyZV9rZXk9bG81NFZnSk1PMHRla2J2b2d4ckZlYiIgYWx0PSJBbm51YWxUUyIgc3R5bGU9Im1heC13aWR0aDogMTAwJTt3aWR0aDogODgwcHg7IiAgd2lkdGg9Ijg4MCIgb25lcnJvcj0idGhpcy5vbmVycm9yPW51bGw7dGhpcy5zcmM9J2h0dHBzOi8vcGxvdC5seS80MDQucG5nJzsiIC8+PC9hPjxzY3JpcHQgZGF0YS1wbG90bHk9Ik51bk5vRGVDYWJ1bmljOjIwIiBzaGFyZWtleS1wbG90bHk9ImxvNTRWZ0pNTzB0ZWtidm9neHJGZWIiIHNyYz0iaHR0cHM6Ly9wbG90Lmx5L2VtYmVkLmpzIiBhc3luYz48L3NjcmlwdD48L2Rpdj4NCg0KDQoqY2xpY2sgaW1hZ2UgZm9yIGludGVyYWN0aXZlIHBsb3RseSBncmFwaCoNCg0KDQpZb3UgY2FuIGhvdmVyIG92ZXIgdGhlIGJhcnMgdG8gc2VlIHRoZSBkZXRhaWxzLiBXZSBjYW4gY2xlYXJseSBzZWUgdGhlIHBhdHRlcm4gd2hpY2ggc2hvd3MgV2lsdCBDaGFtYmVybGFpbiBhbmQgTWljaGFlbCBKb3JkYW4gZXJhLCBmb2xsb3dlZCBieSBhIHNob3J0IER1cnJhbnQgZXJhLg0KDQo8YnIvPg0KDQo8ZGl2IGNsYXNzPSJGYWN0Ij4NCg0KPHVsIGNsYXNzPSJDdXN0b21MaXN0Ij4NCg0KPGxpPkhpZ2hlc3QgUFBHIGxlYWRlciBpbiBhIHNlYXNvbjogYHIgVG9wU2NvcmVyJFRvcF9QUEdbd2hpY2gubWF4KFRvcFNjb3JlciRUb3BfUFBHKV1gIGJ5IGByIFRvcFNjb3JlciRQbGF5ZXJbd2hpY2gubWF4KFRvcFNjb3JlciRUb3BfUFBHKV1gIGluIGByIFRvcFNjb3JlciRZZWFyW3doaWNoLm1heChUb3BTY29yZXIkVG9wX1BQRyldYDwvbGk+DQo8bGk+TG93ZXN0IFBQRyBsZWFkZXIgaW4gYSBzZWFzb246IGByIFRvcFNjb3JlciRUb3BfUFBHW3doaWNoLm1pbihUb3BTY29yZXIkVG9wX1BQRyldYCBieSBgciBUb3BTY29yZXIkUGxheWVyW3doaWNoLm1pbihUb3BTY29yZXIkVG9wX1BQRyldYCBpbiBgciBUb3BTY29yZXIkWWVhclt3aGljaC5taW4oVG9wU2NvcmVyJFRvcF9QUEcpXWA8L2xpPg0KPGxpPkF2ZXJhZ2UgUFBHIGxlYWRlciBhbGwgc2Vhc29uOiBgciByb3VuZChtZWFuKFRvcFNjb3JlciRUb3BfUFBHKSwgMilgLCBjb21wYXJlcyB0byBvdmVyYWxsIGF2ZXJhZ2UgUFBHIE5CQTogYHIgcm91bmQobWVhbihQUEdtZWFuJG1lYW5QUEcpLCAyKWA8L2xpPg0KPGxpPk1vc3Qgc2Vhc29ucyBsZWFkaW5nIHRoZSBsZWFndWUgaW4gUFBHOiBgciBtYXgodGFibGUoVG9wU2NvcmVyJFBsYXllcikpYCBieSBgciBuYW1lcyh0YWJsZShUb3BTY29yZXIkUGxheWVyKSlbYXMudmVjdG9yKHRhYmxlKFRvcFNjb3JlciRQbGF5ZXIpKT09bWF4KHRhYmxlKFRvcFNjb3JlciRQbGF5ZXIpKV1gPC9saT4NCg0KPC91bD4NCg0KPC9kaXY+DQoNCjwvZGl2Pg0KDQoNCjxici8+DQo8YnIvPg0KDQojIyMgUG9pbnRzIHBlciAzNiBNaW51dGVzDQoNCjxkaXYgY2xhc3M9IkJveCI+DQoNCjxici8+DQoNClRoZSBjb25jZXB0IG9mIHBlci1taW51dGUgc3RhdGlzdGljcyBpcyBhIGJhc2ljIGJ1aWxkaW5nIGJsb2NrIG9mIHN0YXRpc3RpY2FsIGFuYWx5c2lzIGluIGJhc2tldGJhbGwuIFdoaWxlIGxvb2tpbmcgYXQgcGVyLWdhbWUgYXZlcmFnZXMgZ2l2ZXMgdXMgc29tZSBpbmZvcm1hdGlvbiBhYm91dCB0aGUgcHJvZHVjdGl2aXR5IG9mIGEgcGxheWVyLCBwZXItbWludXRlIGF2ZXJhZ2VzIHRlbGwgdXMgYSBncmVhdCBkZWFsIG1vcmUgYWJvdXQgaG93IHdlbGwsIG9yIHBvb3JseSwgdGhhdCBwbGF5ZXIgaXMgYWN0dWFsbHkgcGVyZm9ybWluZy4gKFtzb3VyY2VdKGh0dHBzOi8vd3d3LnNwb3J0aW5nY2hhcnRzLmNvbS9kaWN0aW9uYXJ5L25iYS9wZXItbWludXRlLXN0YXRpc3RpY3MuYXNweCkpDQoNCkFzIHdlIGtub3cgZnVsbC1sZW5ndGggb2YgYW4gTkJBIGdhbWUgaXMgNDggbWludXRlcywgZGl2aWRlZCBieSA0IHF1YXJ0ZXJzIHNvIGl0J3MgMTIgbWludXRlcyBlYWNoIHF1YXJ0ZXIuIFNvIHdoeSAzNj8gV2VsbCwgYXMgdGhlIGFyZ3VtZW50IGdvZXMsIHRoZSBOQkEgYXZlcmFnZSBmb3IgYSBzdGFydGVyIGlzIGFib3V0IDM2IG1pbnV0ZXMgKHRoZXkgcGxheSAzIHF1YXJ0ZXJzIG91dCBvZiA0IHF1YXJ0ZXJzKSwgYW5kIDM2IGlzIHRoZSBtb3JlIHJlYWxpc3RpYyB3YXkgdG8gbWVhc3VyZSBzdGF0cyBhcyBub3QgbWFueSBwbGF5ZXJzIHBsYXkgNDArIG1pbnV0ZXMsIGxldCBhbG9uZSA0OC4NCg0KPGRpdiBjbGFzcz0idGFibGUiPg0KDQpgYGB7cn0NClBUUzM2IDwtIE5CQSAlPiUNCiAgICBncm91cF9ieShQbGF5ZXIpICU+JQ0KICAgIHN1bW1hcmlzZShQb3MgPSBnZXRtb2RlKFBvcyksDQogICAgICAgICAgICAgIEFjdGl2ZVllYXJzID0gcGFzdGUoZ2V0bW9kZShZZWFyU3RhcnQpLCAiLSIsIGdldG1vZGUoWWVhckVuZCkpLA0KICAgICAgICAgICAgICBHYW1lcyA9IHN1bShHKSwNCiAgICAgICAgICAgICAgTVAgPSBzdW0oTVApLA0KICAgICAgICAgICAgICBQb2ludHMgPSBzdW0oUFRTKSwNCiAgICAgICAgICAgICAgTVBHID0gcm91bmQoTVAvR2FtZXMsIDIpLA0KICAgICAgICAgICAgICBQUEcgPSByb3VuZChQb2ludHMvR2FtZXMsIDIpLA0KICAgICAgICAgICAgICBQUE0zNiA9IHJvdW5kKFBQRy9NUEcgKiAzNiwgMikpICU+JQ0KICAgIGZpbHRlcihHYW1lcyA+IDEwMCkgJT4lDQogICAgYXJyYW5nZShkZXNjKFBQTTM2KSkgJT4lDQogICAgbXV0YXRlKFJhbmsgPSBkZW5zZV9yYW5rKGRlc2MoUFBNMzYpKSkgJT4lDQogICAgc2VsZWN0KFJhbmssIGV2ZXJ5dGhpbmcoKSwgLWMoTVAsIFBvaW50cykpICU+JQ0KICAgIGhlYWQobj0yMCkNCg0KUFRTMzYgJT4lDQogICAgbXV0YXRlKFBvcyA9IGNlbGxfc3BlYyhQb3MsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3IgPSAid2hpdGUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFsaWduID0gImMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJhY2tncm91bmQgPSBmYWN0b3IoUG9zLCBjKCJDIiwgIlBGIiwgIlNGIiwgIlNHIiwgIlBHIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBQb3NDb2xvckNvZGUpKSkgJT4lDQogICAga2FibGUoZXNjYXBlID0gRkFMU0UsIGNhcHRpb24gPSAiUG9pbnRzIHBlciAzNiBNaW51dGVzIikgJT4lDQogICAga2FibGVfc3R5bGluZygic3RyaXBlZCIsIGZ1bGxfd2lkdGggPSBUKSAlPiUNCiAgICBjb2x1bW5fc3BlYygyLCBib2xkID0gVCkgJT4lDQogICAgY29sdW1uX3NwZWMoMSwgYm9sZCA9IFQsIGNvbG9yID0gInllbGxvdyIsIGJhY2tncm91bmQgPSAiI0ZGMDAwMCIpICU+JQ0KICAgIGNvbHVtbl9zcGVjKDgsIGJvbGQgPSBULCBjb2xvciA9ICJ3aGl0ZSIsIGJhY2tncm91bmQgPSAiIzc3Nzc3NyIpICU+JQ0KICAgIHNjcm9sbF9ib3god2lkdGggPSAiMTAwJSIsIGhlaWdodCA9ICIzMDBweCIpDQpgYGANCjwvZGl2Pg0KDQo8YnIvPg0KDQpGb3IgdGhlIHBsb3QsIEknZCBsaWtlIHRvIHNlZSB0aGUgZGlzY3JlcGFuY3kgYmV0d2VlbiBwb2ludHMgcGVyIDM2IG1pbnV0ZXMgKFBUUzM2KSBhbmQgcG9pbnRzIHBlciBnYW1lIChQUEcpLCBzb3J0ZWQgYnkgUFRTMzYgcmFuay4NCg0KYGBge3IgZmlnLndpZHRoPTksIGZpZy5oZWlnaHQ9NX0NClBUUzM2ICU+JQ0KICAgIG11dGF0ZShQbGF5ZXIgPSByZW9yZGVyKFBsYXllciwgZGVzYyhQUE0zNiksIEZVTj1tZWRpYW4pKSAlPiUNCiAgICBnZ3Bsb3QoYWVzKGdyb3VwID0gMSkpICsNCiAgICBnZW9tX3NlZ21lbnQoYWVzKHg9UGxheWVyLCB4ZW5kPVBsYXllciwgeT1QUE0zNiwgeWVuZD1QUEcpLCBjb2xvcj0iYmxhY2siKSArDQogICAgZ2VvbV9wb2ludChhZXMoUGxheWVyLCBQUE0zNiwgY29sb3I9IiNGRjU4MDAiKSwgc2l6ZT01KSArDQogICAgZ2VvbV9wb2ludChhZXMoUGxheWVyLCBQUEcsIGNvbG9yPSIjMDA5ZGZmIiksIHNpemU9Mywgc2hhcGU9MTgpICsNCiAgICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDEpKSArDQogICAgZ2d0aXRsZSgiUG9pbnRzIHBlciAzNiBNaW51dGVzIHZzLiBQb2ludHMgcGVyIEdhbWUiKSArDQogICAgeGxhYigiIikgKw0KICAgIHlsYWIoIlBUUzM2IHZzLiBQUEciKSArDQogICAgc2NhbGVfY29sb3JfbWFudWFsKG5hbWU9IiIsIHZhbHVlcz1jKCIjMDA5ZGZmIiwgIiNGRjU4MDAiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxzPWMoIlBvaW50cy9HYW1lIiwgIlBvaW50cy8zNiBtaW51dGVzIiksDQogICAgICAgICAgICAgICAgICAgICAgIGd1aWRlID0gZ3VpZGVfbGVnZW5kKHJldmVyc2U9VFJVRSkpICsNCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb249ImJvdHRvbSIpDQpgYGANCg0KPGJyLz4NCg0KVGhpcyBzaW1wbHkgc2hvd3MgaG93IHdlbGwgdGhleSBzY29yZXMgYW5kIGhvdyBtdWNoIGNoYW5jZSB0aGVpciBjb2FjaCBnaXZlIHRoZW0gdG8gcGxheSBpbiBhIGdhbWUuIElmIHRoZSBibHVlIGRpYW1vbmQgaXMgaW5zaWRlIHRoZSBvcmFuZ2UgY2lyY2xlLCBpdCBtZWFucyB0aGVpciBtaW51dGVzIHBlciBnYW1lIChNUEcpIGlzIGFyb3VuZCAzNiBtaW51dGVzLCBpZiBpdCBmYWxscyBiZWxvdyB0aGUgY2lyY2xlIHRoZW4gdGhlaXIgTVBHIGlzIGxvd2VyIHRoYW4gMzYgYW5kIHZpY2UgdmVyc2EuIA0KDQo8L2Rpdj4NCg0KDQo8YnIvPg0KPGJyLz4NCg0KIyMjIE1vc3QgUG9pbnRzIENhcmVlcg0KDQo8ZGl2IGNsYXNzPSJCb3giPg0KDQo8YnIvPg0KDQpOb3cgd2UgYWJvdXQgdG8gc2VlIHRoZSBtb3N0IHBvaW50cyBhY2N1bXVsYXRlZCBieSBhIHBsYXllciB0aHJvdWdob3V0IGhpcyBjYXJlZXIgaW4gdGhlIE5CQS4gDQoNCjxkaXYgY2xhc3M9InRhYmxlIj4NCg0KYGBge3J9DQpQVFNNb3N0IDwtIE5CQSAlPiUNCiAgICBncm91cF9ieShQbGF5ZXIpICU+JQ0KICAgIGZpbHRlcihuX2Rpc3RpbmN0KEJvcm4pID09IDEpICU+JQ0KICAgIHN1bW1hcmlzZShQb3MgPSBnZXRtb2RlKFBvc2l0aW9uKSwNCiAgICAgICAgICAgICAgQWN0aXZlWWVhcnMgPSBwYXN0ZShnZXRtb2RlKFllYXJTdGFydCksICItIiwgZ2V0bW9kZShZZWFyRW5kKSksDQogICAgICAgICAgICAgIFllYXJzQWN0aXZlID0gbl9kaXN0aW5jdChZZWFyKSwNCiAgICAgICAgICAgICAgR2FtZXMgPSBzdW0oRyksDQogICAgICAgICAgICAgIE1QID0gc3VtKE1QKSwNCiAgICAgICAgICAgICAgTVBHID0gcm91bmQoTVAvR2FtZXMsIDIpLA0KICAgICAgICAgICAgICBQb2ludHMgPSBhcy5udW1lcmljKHN1bShQVFMpKSwNCiAgICAgICAgICAgICAgUFBHID0gcm91bmQoUG9pbnRzL0dhbWVzLCAyKSkgJT4lDQogICAgZmlsdGVyKEdhbWVzID4gMTAwKSAlPiUNCiAgICBhcnJhbmdlKGRlc2MoUG9pbnRzKSkgJT4lDQogICAgaGVhZChuPTIwKSAlPiUNCiAgICBtdXRhdGUoUmFuayA9IHJhbmsoZGVzYyhQb2ludHMpKSkgJT4lDQogICAgc2VsZWN0KFJhbmssIGV2ZXJ5dGhpbmcoKSwgLWMoTVApKQ0KDQoNClBUU01vc3QgJT4lDQogICAga2FibGUoZXNjYXBlID0gRkFMU0UsIGNhcHRpb24gPSAiTW9zdCBQb2ludHMgQ2FyZWVyIikgJT4lDQogICAga2FibGVfc3R5bGluZygic3RyaXBlZCIsIGZ1bGxfd2lkdGggPSBUKSAlPiUNCiAgICBjb2x1bW5fc3BlYygyLCBib2xkID0gVCkgJT4lDQogICAgY29sdW1uX3NwZWMoMSwgYm9sZCA9IFQsIGNvbG9yID0gInllbGxvdyIsIGJhY2tncm91bmQgPSAiI0ZGMDAwMCIpICU+JQ0KICAgIGNvbHVtbl9zcGVjKDgsIGJvbGQgPSBULCBjb2xvciA9ICJ3aGl0ZSIsIGJhY2tncm91bmQgPSAiIzc3Nzc3NyIpICU+JQ0KICAgIHNjcm9sbF9ib3god2lkdGggPSAiMTAwJSIsIGhlaWdodCA9ICIzMDBweCIpDQpgYGANCjwvZGl2Pg0KDQo8YnIvPg0KDQpHZW5lcmF0aW5nIHBsb3QuLi4NCg0KYGBge3IgZmlnLndpZHRoID0gOSwgZmlnLmhlaWdodD01fQ0KUFRTTW9zdCAlPiUNCiAgICBoZWFkKDEwKSAlPiUNCiAgICBhcnJhbmdlKFBvaW50cykgJT4lDQogICAgbXV0YXRlKFBsYXllcj1mYWN0b3IoUGxheWVyLCBQbGF5ZXIpKSAlPiUNCiAgICBnZ3Bsb3QoYWVzKFBsYXllciwgUG9pbnRzKSkgKw0KICAgIGdlb21fc2VnbWVudChhZXMoeD1QbGF5ZXIsIHhlbmQ9UGxheWVyLCB5PTAsIHllbmQ9UG9pbnRzKSwNCiAgICAgICAgICAgICAgICAgY29sb3I9ImdyYXkxMSIsIGxpbmV0eXBlID0gImRvdHRlZCIsIHNpemU9MSwgYWxwaGE9MC42KSArDQogICAgZ2VvbV9wb2ludChjb2xvcj0ib3JhbmdlcmVkMSIsIHNpemU9NCkgKw0KICAgIGdlb21fdGV4dChhZXMobGFiZWw9UG9pbnRzKSwgaGp1c3Q9MC41LCB2anVzdD0tMS41LCBzaXplPTMpICsNCiAgICBnZ3RpdGxlKCJNb3N0IFBvaW50cyBDYXJlZXIiKSArDQogICAgY29vcmRfZmxpcCgpICsNCiAgICB4bGFiKCIiKSArDQogICAgeWxhYigiUG9pbnRzIikNCmBgYA0KDQo8L2Rpdj4NCg0KPGJyLz4NCjxici8+DQoNCiMjIyBGaW5kaW5nIHRoZSBCSUdHRVNUIFRvcCBTY29yZXJzIGluIHRoZSBMZWFndWUNCg0KPGRpdiBjbGFzcz0iQm94Ij4NCg0KPGJyLz4NCg0KTm93IHRoYXQgd2UgaGF2ZSBzZWVuIHRoZSBiZXN0IHNjb3JlcnMgaW4gdGhlIGxlYWd1ZSBpbiBzZXZlcmFsIGNhdGVnb3JpZXMsIEknZCBsaWtlIHRvIHByb2NlZWQgd2l0aCBmaW5kaW5nIHRoZSBiaWdnZXN0IG5hbWUgaW4gc2NvcmluZyBieSBjb21iaW5pbmcgYWxsIGNhdGVnb3J5IGZyb20gdGhlIHByZXZpb3VzIGxlYWRlcmJvYXJkczoNCg0KMS4gVG9wIDIwIEFsbC1UaW1lIFBvaW50cyBwZXIgR2FtZSBMZWFkZXJzDQoyLiBBbm51YWwgUG9pbnRzIHBlciBHYW1lIExlYWRlcnMNCjMuIFRvcCAyMCBQb2ludHMgcGVyIDM2IE1pbnV0ZXMgTGVhZGVycw0KNC4gVG9wIDIwIE1vc3QgUG9pbnRzIExlYWRlcnMNCg0KTXkgYXJiaXRyYXJ5IG1ldGhvZCB0byBtZWFzdXJlIHRoZWlyIHByb21pbmVuY2UgYXMgYSBzY29yZXIgYnkgdXNpbmcgdG9rZW5zIGFzIGEgdmFsdWF0aW9uIHVuaXQgZGVzY3JpYmVkIGFzIGZvbGxvd3M6DQoNCiogUmFuayAxIGVhcm4gMTAgdG9rZW5zDQoqIFJhbmsgMiBlYXJuIDcgdG9rZW5zDQoqIFJhbmsgMyBlYXJuIDQgdG9rZW5zDQoqIFJhbmsgNCB0byAxMCBlYXJuIDIgdG9rZW5zDQoqIFJhbmsgMTEgdG8gMjAgZWFybiAxIHRva2VuDQoqIEVhY2ggYW5udWFsIFBQRyBsZWFkZXIgZWFybiAxIHRva2VuDQoqIFBQRyBtdXN0IG5vdCBiZWxvdyAxOC4wDQogICAgDQpUaGVuIHdlIGNhbiByYW5rIHRoZW0gYmFzZWQgb24gdGhlIHRvdGFsIHRva2VucyB0aGV5IGVhcm4uDQoNClNvIGxldCdzIGdldCBzdGFydGVkLi4uDQoNCjxkaXYgY2xhc3M9InRhYmxlIj4NCmBgYHtyfQ0KIyBTdGVwIDE6IENhbGN1bGF0aW5nIHRva2VucyBmb3IgQWxsLVRpbWUgUG9pbnRzIHBlciBHYW1lIGxlYWRlcmJvYXJkOg0KUFRTVmFsdWF0aW9uMSA8LSBBbGxUaW1lUFBHICU+JQ0KICAgIGdyb3VwX2J5KFBsYXllcikgJT4lDQogICAgc3VtbWFyaXNlKG4gPSAoaWZlbHNlKFJhbmsgPT0gMSwgMTAsIGlmZWxzZShSYW5rID09IDIsIDcsDQogICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UoUmFuayA9PSAzLCA0LCBpZmVsc2UoUmFuayVpbiVjKDQ6MTApLCAyLCAxKSkpKSkpICU+JQ0KICAgIG11dGF0ZShQbGF5ZXIgPSBhcy5jaGFyYWN0ZXIoUGxheWVyKSkgJT4lDQogICAgYXJyYW5nZShkZXNjKG4pKQ0KDQojIFN0ZXAgMjogQ2FsY3VsYXRpbmcgdG9rZW5zIGZvciBBbGwtVGltZSBQb2ludHMgcGVyIEdhbWUgbGVhZGVyYm9hcmQ6DQpQVFNWYWx1YXRpb24yIDwtIFRvcFNjb3JlciAlPiUgY291bnQoUGxheWVyKSAlPiUgYXJyYW5nZShkZXNjKG4pKQ0KDQojIFN0ZXAgMzogQ2FsY3VsYXRpbmcgdG9rZW5zIGZvciBQb2ludHMgcGVyIDM2IE1pbnV0ZXMgbGVhZGVyYm9hcmQ6DQpQVFNWYWx1YXRpb24zIDwtIFBUUzM2ICU+JQ0KICAgIGdyb3VwX2J5KFBsYXllcikgJT4lDQogICAgZmlsdGVyKFBQRyA+IDE4KSAlPiUNCiAgICBzdW1tYXJpc2UobiA9IChpZmVsc2UoUmFuayA9PSAxLCAxMCwgaWZlbHNlKFJhbmsgPT0gMiwgNywNCiAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShSYW5rID09IDMsIDQsIGlmZWxzZShSYW5rJWluJWMoNDoxMCksIDIsIDEpKSkpKSkgJT4lDQogICAgbXV0YXRlKFBsYXllciA9IGFzLmNoYXJhY3RlcihQbGF5ZXIpKSAlPiUNCiAgICBhcnJhbmdlKGRlc2MobikpDQoNCiMgU3RlcCA0OiBDYWxjdWxhdGluZyB0b2tlbnMgZm9yIE1vc3QgUG9pbnRzIGluIGNhcmVlciBsZWFkZXJib2FyZDoNClBUU1ZhbHVhdGlvbjQgPC0gUFRTTW9zdCAlPiUNCiAgICBncm91cF9ieShQbGF5ZXIpICU+JQ0KICAgIHN1bW1hcmlzZShuID0gKGlmZWxzZShSYW5rID09IDEsIDEwLCBpZmVsc2UoUmFuayA9PSAyLCA3LCANCiAgICAgICAgICAgICAgICAgICAgaWZlbHNlKFJhbmsgPT0gMywgNCwgaWZlbHNlKFJhbmslaW4lYyg0OjEwKSwgMiwgMSkpKSkpKSAlPiUNCiAgICBtdXRhdGUoUGxheWVyID0gYXMuY2hhcmFjdGVyKFBsYXllcikpICU+JQ0KICAgIGFycmFuZ2UoZGVzYyhuKSkNCg0KIyBTdGVwIDU6IE1lcmdlIHRoZSBkYXRhZnJhbWVzIGFuZCBjYWxjdWxhdGluZyB0b3RhbCB0b2tlbnM6DQpBbGxTY29yZXJzIDwtIGRhdGEuZnJhbWUoUGxheWVyID0gYyhQVFNWYWx1YXRpb24xJFBsYXllciwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFBUU1ZhbHVhdGlvbjIkUGxheWVyLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgUFRTVmFsdWF0aW9uMyRQbGF5ZXIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBQVFNWYWx1YXRpb240JFBsYXllciksDQogICAgICAgICAgICAgICAgICAgICAgICAgVG9rZW5zID0gYyhQVFNWYWx1YXRpb24xJG4sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBQVFNWYWx1YXRpb24yJG4sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBQVFNWYWx1YXRpb24zJG4sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBQVFNWYWx1YXRpb240JG4pKSAlPiUNCiAgICBncm91cF9ieShQbGF5ZXIpICU+JQ0KICAgIHN1bW1hcmlzZShUb3RhbCA9IHN1bShUb2tlbnMpKSAlPiUNCiAgICBhcnJhbmdlKGRlc2MoVG90YWwpKQ0KDQojIFN0ZXAgNjogRGlzcGxheSB0aGUgZmluYWwgdGFibGU6DQpBbGxTY29yZXJzICU+JQ0KICAgIGdyb3VwX2J5KFBsYXllcikgJT4lDQogICAgbXV0YXRlKFBvcyA9IGdldG1vZGUoTkJBJFBvc2l0aW9uW05CQSRQbGF5ZXIgJWluJSBQbGF5ZXJdKSwNCiAgICAgICAgICAgUFBHID0gcm91bmQoc3VtKE5CQSRQVFNbTkJBJFBsYXllciAlaW4lIFBsYXllcl0pL3N1bShOQkEkR1tOQkEkUGxheWVyICVpbiUgUGxheWVyXSksIDIpLA0KICAgICAgICAgICBUb2tlbnMgPSBwYXN0ZShzdHJyZXAoInwiLCBUb3RhbCkpKSAlPiUNCiAgICBzZWxlY3QoLVRvdGFsLGV2ZXJ5dGhpbmcoKSkgJT4lDQogICAgZmlsdGVyKFBQRyA+IDE2KSAlPiUNCiAgICBrYWJsZShlc2NhcGUgPSBGQUxTRSwgY2FwdGlvbiA9ICJCSUdHRVNUIFRvcCBTY29yZXJzIikgJT4lDQogICAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9ICJzdHJpcGVkIiwgZnVsbF93aWR0aCA9IEYsIGZvbnRfc2l6ZSA9IDExKSAlPiUNCiAgICBjb2x1bW5fc3BlYygxLCBib2xkID0gVCkgJT4lDQogICAgY29sdW1uX3NwZWMoNCwgYm9sZCA9IFQsIGNvbG9yID0gImdvbGQiKSAlPiUNCiAgICBjb2x1bW5fc3BlYyg1LCBib2xkID0gVCwgY29sb3IgPSAid2hpdGUiLCBiYWNrZ3JvdW5kID0gIiM3Nzc3NzciLCB3aWR0aCA9ICI4cHgiKSAlPiUNCiAgICBzY3JvbGxfYm94KHdpZHRoID0gIjYwJSIsIGhlaWdodCA9ICI1MDBweCIpICU+JQ0KICAgIGthYmxlX3N0eWxpbmcocG9zaXRpb24gPSAiZmxvYXRfbGVmdCIpDQpgYGANCg0KPGltZyBpZD0iTUoiIHNyYz0iaHR0cHM6Ly9pbWFnZS5pYmIuY28vYjVROTcwL01KRmx5LVNoYWRvdy5wbmciLz4NCg0KPC9kaXY+DQoNCjxici8+DQoNCkJhc2VkIG9uIHRoaXMgY2FsY3VsYXRpb24sIE1pY2hhZWwgSm9yZGFuIHJlbWFpbnMgdGhlIGFsbC10aW1lIGtpbmcgb2YgdGhlIHNjb3JlciBpbiB0aGUgTkJBLiBMZXQncyBzZWUgdGhlaXIgbmFtZXMgZnJvbSB0aGUgYmlnZ2VzdCBuYW1lIHRvIHNtYWxsZXIgb25lcy4uLg0KDQpgYGB7ciBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD00fQ0KQWxsU2NvcmVycyAlPiUNCiAgICB3b3JkY2xvdWQyKHNpemU9MC41LCBjb2xvcj0ncmFuZG9tLWxpZ2h0JywgYmFja2dyb3VuZENvbG9yPSJibGFjayIpDQpgYGANCg0KPC9kaXY+DQoNCg0KPGJyLz4NCjxici8+DQoNCiMjIyBTdXBlclNjb3JlcnMgRkctM1AtRlQgUmF0aW8NCg0KPGRpdiBjbGFzcz0iQm94Ij4NCg0KPGJyLz4NCg0KTm93IGxldCdzIHRha2UgdGhlIGJlc3QgZWlnaHQgdG9wIHNjb3JlcnMgKHdobyBoYXMgNyBvciBtb3JlIHRva2VucyBiYXNlZCBvbiBvdXIgcHJldmlvdXMgdmFsdWF0aW9uKSwgbmFtZWQgdGhlbSBgU3VwZXJTY29yZXJzYCwgdGhlbiBmaW5kIG91dCB3aGVyZSBhbGwgdGhvc2UgcG9pbnRzIHRoZXkgbWFkZSBnZW5lcmF0ZWQgZnJvbS4NCg0KQmVhciBpbiBtaW5kIHRoYXQgaW4gdGhlIE5CQSwgdGhlIHRocmVlLXBvaW50IGxpbmUgd2FzIGludHJvZHVjZWQgaW4gdGhlIDE5NznigJM4MCBzZWFzb24uIEFuZCBvdXIgdGhyZWUgb3V0IG9mIGVpZ2h0IGBTdXBlclNjb3JlcnNgIChDaGFtYmVybGFpbiwgR2VydmluLCBhbmQgQWJkdWwtSmFiYmFyKSBhcmUgYmVlbiBhY3RpdmUgaW4gdGhlIGZpZWxkIHNpbmNlIGJlZm9yZSB0aGVuLiBXaXRoIHRoYXQgYmVpbmcgc2FpZCwgbGV0IHVzIG5vdyBzZWUgd2hlcmUgdGhlaXIgcG9pbnRzIGFjY3VtdWxhdGVkIGZyb20uDQoNCjxici8+DQoNCkhlcmUncyB0aGUgdGFibGUuLi4NCg0KPGRpdiBjbGFzcz0idGFibGUiPg0KDQpgYGB7cn0NClN1cGVyU2NvcmVycyA8LSBOQkEgJT4lDQogICAgZmlsdGVyKFBsYXllciAlaW4lIGhlYWQoQWxsU2NvcmVycyRQbGF5ZXIsIDgpKSAlPiUNCiAgICBncm91cF9ieShQbGF5ZXIpICU+JQ0KICAgIHN1bW1hcmlzZShQb3MgPSBnZXRtb2RlKFBvc2l0aW9uKSwNCiAgICAgICAgICAgICAgQWN0aXZlWWVhcnMgPSBwYXN0ZShnZXRtb2RlKFllYXJTdGFydCksICItIiwgZ2V0bW9kZShZZWFyRW5kKSksDQogICAgICAgICAgICAgIFllYXJzQWN0aXZlID0gbl9kaXN0aW5jdChZZWFyKSwNCiAgICAgICAgICAgICAgR2FtZXMgPSBzdW0oRyksDQogICAgICAgICAgICAgIEZHeDIgPSBzdW0oWDJQKSAqIDIsDQogICAgICAgICAgICAgIEZHeDMgPSBzdW0oWDNQLCBuYS5ybSA9IFQpICogMywNCiAgICAgICAgICAgICAgRlQgPSBzdW0oRlQpLA0KICAgICAgICAgICAgICBQVFMgPSBzdW0oUFRTKSwNCiAgICAgICAgICAgICAgUFBHID0gcm91bmQoUFRTL0dhbWVzLCAyKSkgJT4lDQogICAgYXJyYW5nZShkZXNjKFBQRykpDQoNClN1cGVyU2NvcmVycyAlPiUNCiAgICBrYWJsZShlc2NhcGUgPSBGQUxTRSwgY2FwdGlvbiA9ICJTdXBlclNjb3JlcnMgMlB0cy0zUHRzLUZUIFJhdGlvIikgJT4lDQogICAga2FibGVfc3R5bGluZygic3RyaXBlZCIsIGZ1bGxfd2lkdGggPSBUKSAlPiUNCiAgICBjb2x1bW5fc3BlYygxLCBib2xkID0gVCkNCmBgYA0KPC9kaXY+DQoNCjxici8+DQoNCkFuZCBsZXQncyBzZWUgdGhlIHBsb3QuLi4NCg0KYGBge3IgZmlnLndpZHRoID0gOX0NClN1cGVyU2NvcmVycyAlPiUNCiAgICBnYXRoZXIoUGFyYW1ldGVyLCBQb2ludHMsIC1jKFBsYXllcjpHYW1lcywgUFRTOlBQRykpICU+JQ0KICAgIHNlbGVjdCgtYyhQb3MsIEdhbWVzLCBQVFMpKSAlPiUNCiAgICBtdXRhdGUoUGxheWVyID0gZmFjdG9yKFBsYXllciwgbGV2ZWxzID0gYygiS2FyZWVtIEFiZHVsLUphYmJhciIsICJLb2JlIEJyeWFudCIsICJLYXJsIE1hbG9uZSIsICJHZW9yZ2UgR2VydmluIiwgIkxlQnJvbiBKYW1lcyIsICJLZXZpbiBEdXJhbnQiLCAiV2lsdCBDaGFtYmVybGFpbiIsICJNaWNoYWVsIEpvcmRhbiIpKSkgJT4lDQogICAgZ2dwbG90KGFlcyhQbGF5ZXIsIFBvaW50cywgZmlsbD1mb3JjYXRzOjpmY3RfcmV2KFBhcmFtZXRlcikpKSArDQogICAgZ2VvbV9iYXIoIHN0YXQ9ImlkZW50aXR5IiwgcG9zaXRpb249ImZpbGwiKSArDQogICAgZ2d0aXRsZSgiUG9pbnRzIEJyZWFrZG93biBBbW9uZyBTdXBlclNjb3JlcnMiKSArDQogICAgY29vcmRfZmxpcCgpICsNCiAgICB4bGFiKCIiKSArDQogICAgeWxhYigiU2hvb3RpbmciKSArDQogICAgZ3VpZGVzKGZpbGwgPSBndWlkZV9sZWdlbmQocmV2ZXJzZSA9IFRSVUUpKSArDQogICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJib3R0b20iKSArDQogICAgdGhlbWUobGVnZW5kLnRpdGxlPWVsZW1lbnRfYmxhbmsoKSkNCmBgYA0KDQo8L2Rpdj4NCg0KPGJyLz4NCjxici8+DQoNCiMjIyBTY29yaW5nIGVmZmljaWVuY3kgYW1vbmcgdGhlIFN1cGVyU2NvcmVycw0KDQo8ZGl2IGNsYXNzPSJCb3giPg0KDQo8YnIvPg0KDQpMZXQncyB0YWtlIGEgc3RlcCBmdXJ0aGVyIGJ5IGNvbXBhcmluZyB0aGVpciBzaG9vdGluZyBjYXBhYmlsaXR5IHRvIGVhY2ggb3RoZXIgYW5kIHRvIHRoZSBOQkEgYXZlcmFnZSB3aXRoIHJhZGFyIHBsb3QuDQoNClNpbmNlIHVuYXZhaWxhYmlsaXR5IG9mIHRoZSBkYXRhIChpbiB0aGlzIGNhc2UgdGhyZWUtcG9pbnRzIGRhdGEpIHdpbGwgbWVzcyB1cCB0aGUgcGxvdCwgSSBleGNsdWRlIHRob3NlIHRocmVlIG9sZC10aW1lcnMgZnJvbSBvdXIgbGlzdCBzbyB3ZSBwcm9jZWVkIHdpdGggNSBwbGF5ZXJzLg0KDQpUaGUgdmFyaWFibGVzIHVzZWQgZm9yIGNvbXBhcmlzb246DQoNCiogYFgyUG1gOiBub3JtYWxpemVkIDIgcG9pbnRzIEZpZWxkIEdvYWwgbWFkZSBwZXIgbWludXRlDQoqIGBYMlBhYDogbm9ybWFsaXplZCAyIHBvaW50cyBGaWVsZCBHb2FsIGF0dGVtcHQgcGVyIG1pbnV0ZQ0KKiBgWDJQLmA6IG5vcm1hbGl6ZWQgMiBwb2ludHMgRmllbGQgR29hbCBwZXJjZW50YWdlDQoqIGBYM1BtYDogbm9ybWFsaXplZCAzIHBvaW50cyBGaWVsZCBHb2FsIG1hZGUgcGVyIG1pbnV0ZQ0KKiBgWDNQYWA6IG5vcm1hbGl6ZWQgMyBwb2ludHMgRmllbGQgR29hbCBhdHRlbXB0IHBlciBtaW51dGUNCiogYFgzUC5gOiBub3JtYWxpemVkIDMgcG9pbnRzIEZpZWxkIEdvYWwgcGVyY2VudGFnZQ0KKiBgRlRtYDogbm9ybWFsaXplZCBGcmVlIFRocm93IG1hZGUgcGVyIG1pbnV0ZQ0KKiBgRlRhYDogbm9ybWFsaXplZCBGcmVlIFRocm93IGF0dGVtcHQgcGVyIG1pbnV0ZQ0KKiBgRlQuYDogbm9ybWFsaXplZCBGcmVlIFRocm93IHBlcmNlbnRhZ2UNCiogYFRTLmA6IG5vcm1hbGl6ZWQgdHJ1ZSBzaG9vdGluZyBwZXJjZW50YWdlDQoqIGBlRkcuYDogbm9ybWFsaXplZCBlZmZlY3RpdmUgZmllbGQgZ29hbCBwZXJjZW50YWdlDQoNCg0KYGBge3IgZmlnLndpZHRoPTh9DQpSYWRhclNjb3JlIDwtIE5CQV9TY2FsZWQgJT4lDQogICAgZ3JvdXBfYnkoUGxheWVyKSAlPiUNCiAgICBzdW1tYXJpc2UoWDJQbSA9IG1lYW4oWDJQX3BNKSwNCiAgICAgICAgICAgICAgWDJQYSA9IG1lYW4oWDJQQV9wTSksDQogICAgICAgICAgICAgIFgyUC4gPSBtZWFuKFgyUC4sIG5hLnJtPVQpLA0KICAgICAgICAgICAgICBYM1BtID0gbWVhbihYM1BfcE0pLA0KICAgICAgICAgICAgICBYM1BhID0gbWVhbihYM1BBX3BNKSwNCiAgICAgICAgICAgICAgWDNQLiA9IG1lYW4oWDNQLiwgbmEucm09VCksDQogICAgICAgICAgICAgIEZUbSA9IG1lYW4oRlRfcE0pLA0KICAgICAgICAgICAgICBGVGEgPSBtZWFuKEZUQV9wTSksDQogICAgICAgICAgICAgIEZULiA9IG1lYW4oRlQuLCBuYS5ybT1UKSwNCiAgICAgICAgICAgICAgVFMuID0gbWVhbihUUy4sIG5hLnJtPVQpLA0KICAgICAgICAgICAgICBlRkcuID0gbWVhbihlRkcuLCBuYS5ybT1UKSkgJT4lDQogICAgZmlsdGVyKFBsYXllciAlaW4lIFN1cGVyU2NvcmVycyRQbGF5ZXIpICU+JQ0KICAgIHNlbGVjdCgtUGxheWVyKQ0KDQpyYWRhclBQRyA8LSBwbG90X2x5KHR5cGUgPSAnc2NhdHRlcnBvbGFyJywNCiAgICAgICAgZmlsbCA9ICd0b3NlbGYnLA0KICAgICAgICBtb2RlID0gJ2xpbmVzJykgJT4lDQogICAgYWRkX3RyYWNlKHIgPSBhcy5udW1lcmljKGFzLnZlY3RvcihSYWRhclNjb3JlWzcsXSkpLA0KICAgICAgICAgICAgICB0aGV0YSA9IGFzLmNoYXJhY3Rlcihhcy52ZWN0b3IoY29sbmFtZXMoUmFkYXJTY29yZSkpKSwNCiAgICAgICAgICAgICAgbmFtZSA9ICdNaWNoYWVsIEpvcmRhbicpICU+JQ0KICAgIGFkZF90cmFjZShyID0gYXMubnVtZXJpYyhhcy52ZWN0b3IoUmFkYXJTY29yZVs0LF0pKSwNCiAgICAgICAgICAgICAgdGhldGEgPSBhcy5jaGFyYWN0ZXIoYXMudmVjdG9yKGNvbG5hbWVzKFJhZGFyU2NvcmUpKSksDQogICAgICAgICAgICAgIG5hbWUgPSAnS2V2aW4gRHVyYW50JykgJT4lDQogICAgYWRkX3RyYWNlKHIgPSBhcy5udW1lcmljKGFzLnZlY3RvcihSYWRhclNjb3JlWzYsXSkpLA0KICAgICAgICAgICAgICB0aGV0YSA9IGFzLmNoYXJhY3Rlcihhcy52ZWN0b3IoY29sbmFtZXMoUmFkYXJTY29yZSkpKSwNCiAgICAgICAgICAgICAgbmFtZSA9ICdMZUJyb24gSmFtZXMnKSAlPiUNCiAgICBhZGRfdHJhY2UociA9IGFzLm51bWVyaWMoYXMudmVjdG9yKFJhZGFyU2NvcmVbMyxdKSksDQogICAgICAgICAgICAgIHRoZXRhID0gYXMuY2hhcmFjdGVyKGFzLnZlY3Rvcihjb2xuYW1lcyhSYWRhclNjb3JlKSkpLA0KICAgICAgICAgICAgICBuYW1lID0gJ0thcmwgTWFsb25lJykgJT4lDQogICAgYWRkX3RyYWNlKHIgPSBhcy5udW1lcmljKGFzLnZlY3RvcihSYWRhclNjb3JlWzUsXSkpLA0KICAgICAgICAgICAgICB0aGV0YSA9IGFzLmNoYXJhY3Rlcihhcy52ZWN0b3IoY29sbmFtZXMoUmFkYXJTY29yZSkpKSwNCiAgICAgICAgICAgICAgbmFtZSA9ICdLb2JlIEJyeWFudCcpICU+JQ0KICAgIGxheW91dChwb2xhciA9IGxpc3QocmFkaWFsYXhpcyA9IGxpc3QodmlzaWJsZSA9IFQsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByYW5nZSA9IGMoLTEuNSwgMS41KSkpKQ0KDQphcGlfY3JlYXRlKHJhZGFyUFBHLCBmaWxlbmFtZSA9ICJyYWRhclBQRyIpDQpgYGANCg0KPGJyLz4NCg0KPGRpdj48YSBocmVmPSJodHRwczovL3Bsb3QubHkvfk51bk5vRGVDYWJ1bmljLzE2Lz9zaGFyZV9rZXk9MmpMUmFFNE1sWGticUprWXVhTDdCdiIgdGFyZ2V0PSJfYmxhbmsiIHRpdGxlPSJyYWRhclBQRyIgc3R5bGU9ImRpc3BsYXk6IGJsb2NrOyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48aW1nIHNyYz0iaHR0cHM6Ly9wbG90Lmx5L35OdW5Ob0RlQ2FidW5pYy8xNi5wbmc/c2hhcmVfa2V5PTJqTFJhRTRNbFhrYnFKa1l1YUw3QnYiIGFsdD0icmFkYXJQUEciIHN0eWxlPSJtYXgtd2lkdGg6IDEwMCU7d2lkdGg6IDYwMHB4OyIgIHdpZHRoPSI2MDAiIG9uZXJyb3I9InRoaXMub25lcnJvcj1udWxsO3RoaXMuc3JjPSdodHRwczovL3Bsb3QubHkvNDA0LnBuZyc7IiAvPjwvYT48c2NyaXB0IGRhdGEtcGxvdGx5PSJOdW5Ob0RlQ2FidW5pYzoxNiIgc2hhcmVrZXktcGxvdGx5PSIyakxSYUU0TWxYa2JxSmtZdWFMN0J2IiBzcmM9Imh0dHBzOi8vcGxvdC5seS9lbWJlZC5qcyIgYXN5bmM+PC9zY3JpcHQ+PC9kaXY+DQoNCipjbGljayBpbWFnZSBmb3IgaW50ZXJhY3RpdmUgcGxvdGx5IGdyYXBoKg0KDQoqKkhvdyB0byBpbnRlcnByZXQgdGhlIHBsb3Q6KioNCg0KKiBDbGljayBvbiB0aGUgJ3BsYXllciBsZWdlbmQnIGluIHRoZSB1cHBlciByaWdodCBvZiB0aGUgZ3JhcGggdG8gc2VsZWN0L3Vuc2VsZWN0IGVhY2ggcG9zaXRpb24sIGRvdWJsZS1jbGljayB0byBpc29sYXRlIHRoZSB0cmFjZS4gWW91IGNhbiBjb21wYXJlIHRoZW0gaW4gYW55IHdheSBhcyB5b3UgZGVzaXJlLg0KKiAiMCIgaW4gdGhlIHNjYWxlIGlzIHRoZSBhdmVyYWdlIG9mIGVhY2ggdmFyaWFibGUsIGFueXRoaW5nIGJlbG93ICIwIiB0ZWxscyB1cyB0aGF0IHRoZSBwbGF5ZXIgcGVyZm9ybWFuY2UgaW4gdGhhdCBwYXJ0aWN1bGFyIGNhdGVnb3J5IGlzIGJlbG93IGF2ZXJhZ2UsIGFuZCB2aWNlIHZlcnNhLg0KDQo8YnIvPg0KDQo8ZGl2IGNsYXNzPSJGYWN0Ij4NCg0KPHVsIGNsYXNzPSJDdXN0b21MaXN0Ij4NCg0KPGxpPioqTWljaGFlbCBKb3JkYW4qKiBsZWFkcyB0aGUgZ3JvdXAgaW4gYm90aCBgWDJQbWAgYW5kIGBYMlBhYCBjYXRlZ29yeS48L2xpPg0KPGxpPioqS2V2aW4gRHVyYW50KiogbGVhZHMgdGhlIGdyb3VwIGluIGFsbCB0aHJlZS1wb2ludHMgY2F0ZWdvcnkgKGBYM1BtYCwgYFgzUGFgLCBgWDNQLmApLCBgRlRtYCwgYEZULmAsIGBUUy5gLCBhbmQgYGVGRy5gLiBIZSB0cmFpbHMgaW4gYFgyUEFgIGNhdGVnb3J5LjwvbGk+DQo8bGk+KipMZUJyb24gSmFtZXMqKiBsZWFkcyB0aGUgZ3JvdXAgaW4gYFgyUC5gIGNhdGVnb3J5LiBIZSB0cmFpbHMgaW4gYEZUbWAgY2F0ZWdvcnkuPC9saT4NCjxsaT4qKkthcmwgTWFsb25lKiogbGVhZHMgdGhlIGdyb3VwIGluIGBGVGFgIGNhdGVnb3J5LiBIZSB0cmFpbHMgaW4gYWxsIHRocmVlLXBvaW50cyAoYFgzUG1gLCBgWDNQYWAsIGBYM1AuYCkgYW5kIGBGVC5gIGNhdGVnb3J5LCBzaW5jZSBoZSdzIHRoZSBvbmx5IHBvc3QgcGxheWVyIGluIHRoZSBncm91cC48L2xpPg0KPGxpPioqS29iZSBCcnlhbnQqKiB0cmFpbHMgaW4gYFgyUG1gLCBgWDJQLmAsIGBGVGFgLCBgVFMuYCwgYGVGRy5gIGNhdGVnb3J5LCB3aGljaCBtYWRlIGhpbSB0aGUgbW9zdCBpbmVmZmVjdGl2ZSBzY29yZXIgaW4gdGhpcyBTdXBlcnNjb3JlciBncm91cC48L2xpPg0KDQo8L3VsPg0KDQo8L2Rpdj4NCg0KPC9kaXY+DQoNCjxici8+DQo8YnIvPg0KDQotLS0NCg0KPGRpdiBjbGFzcz0icm93Ij4NCg0KIDxkaXYgY2xhc3M9ImNvbHVtbiBsZWZ0Ij4NCiA8YSBocmVmPSJodHRwczovL3JwdWJzLmNvbS9uaW5qYXp6bGUvTkJBU2Vhc29uczEiIHRhcmdldD0iX2JsYW5rIj4NCjxidXR0b24gY2xhc3M9ImxlZnRidG4iPjwvYnV0dG9uPg0KIDwvYT4NCiA8L2Rpdj4NCiANCiA8ZGl2IGNsYXNzPSJjb2x1bW4gbWlkZGxlIj4NCg0KRW5kIG9mIFNlc3Npb24NCg0KICA8L2Rpdj4NCiAgDQogIDxkaXYgY2xhc3M9ImNvbHVtbiByaWdodCI+DQogIDxhIGhyZWY9Imh0dHBzOi8vcnB1YnMuY29tL25pbmphenpsZS9OQkFTZWFzb25zMyIgdGFyZ2V0PSJfYmxhbmsiPg0KPGJ1dHRvbiBjbGFzcz0icmlnaHRidG4iPjwvYnV0dG9uPg0KICA8L2E+DQogIDwvZGl2Pg0KICANCjwvZGl2Pg0KDQotLS0=