drawing

Seasons 1950-2017




Introduction


This in an exploratory analysis of the NBA Seasons 1950-2017. If you are familiar with NBA statistics terminology, don’t care about how the data being processed, or just want to go straight to the fun, please skip this boring part and proceed to the next part.


About Me and the Project

It was the NBA who first introduced me into the broad world of statistics in an entertaining manner back in my childhood. Since then I have always been captivated by statistics and data.

Despite my interest in statistics coupled with intermittently making amateurish statistical data analysis out of anything that piqued my interest for pleasure, I never really had the proper training nor consider take the subject as a career until recently. It was not long ago, without prior knowledge in the area, I started to teach myself coding in Data Science obsessively by dive into MOOC (Massive Open Online Course) courses religiously in my spare time.

Being absent from following NBA regularly for a long time elevated my excitement to explore, analyze and gain insight from the data. Thus, this dataset is perfect for my first attempt at doing a self-directed project, as it is convenience for me to dig deeper into learning Data Science while reaping the joy of producing it.

I expect to use this opportunity to gain more experience by challenging myself to implement my new skill while expanding it by exploring various methods of data wrangling and visualization, then communicating the findings efficiently in an aesthetic manner. In this way, I can expect to shape my skills through practical learning.

The objective is to present NBA seasons statistical facts through compelling visualization and tables. I do not make any predictive analysis here (will get there later).

This presentation is designed for anyone who interested in this subjects without the need of prior technical knowledge, hence the simplification and references, it also intended to draw expertise in the subject to give feedback for my improvement.

I hope this may be useful as a reference for understanding the NBA statistics, NBA seasons history, or as a code reference for R learners


The Dataset

The dataset contains aggregate individual statistics for 67 NBA seasons from 1950 to 2017. The data was scraped from Basketball-reference and is outdated. Nevertheless, the codes are designed to facilitate update once new data emerge. . For simplicity, I took only the basic stats (FG, PTS, etc.), and dismissed the advanced stats (VORP, PER, OBPM, USG%, etc.).

Since the dataset provides regular season statistics and basic player biodata only, I need to point out the dataset limitation:

  • No NBA champion, playoffs or All-Star is present in the dataset.
  • There’s no win-lose nor score information.
  • No team’s stats given.
  • Data about title or awards (e.g.: MVP contest winners, rookie of the year, All-Star player, etc.) is not provided in the dataset.
  • The player’s stats are presented as a whole season, so it’s not possible to extract information about in-game records.
  • No regional division information is given.




Glossary



More about Positions

Historically, only three positions were recognized (two guards, two forwards, and one center) based on where they played on the court: Guards generally played outside and away from the hoop and forwards played outside and near the baseline, with the center usually positioned in the key. During the 1980s, as team strategy evolved after the three-point field goal and the three-point arc were added to the basketball court, more specialized roles developed, resulting in the five position designations used today. (source)

In here, for simplicity and esthetical purpose, I arbitrarily framed up all the players into the standard five position used today and then color-coded them. This is essensial because I tend to make good use of this for comparison frequently.

For more information about each position, click on the position icon below.

  • : Center
  • : Power Forward
  • : Small Forward
  • : Shooting Guard
  • : Point Guard




Data Preparation Stage


Loading necessary packages.

library(dplyr)
library(tidyr)
library(measurements)
library(sqldf)
library(kableExtra)


Create additional functions

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

convertHeight <- function(x) {
    x <- as.character(x)
    split <- strsplit(x, "-")
    feet <- as.numeric(split[[1]][1])
    inch <- as.numeric(split[[1]][2])
    round(conv_unit(feet, "ft", "cm") + conv_unit(inch, "inch", "cm"),0)
}


Load the dataset


Download and extracting the files.

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

fileURL <- "https://www.kaggle.com/drgilermo/nba-players-stats/downloads/nba-players-stats.zip/2"
filename <- "NBASeason1950-2017.zip"

# Checking if archieve already exists.
if (!file.exists(filename)){
  download.file(fileURL, filename, method="curl")
}  

# Checking if folder exists
if (!file.exists("NBA Season Dataset")) { 
  unzip(filename)
}

Download date: 28 July 2018


Load the data.

NBA <- read.csv("NBA Season Dataset/Seasons_Stats.csv")[, c(2:9, 11:20, 32:53)]
PlayerData <- read.csv("NBA Season Dataset/player_data.csv")[, c(1:3, 5:6)]


Tidying data


Clean up data.

# Remove NA rows
NBA <- NBA %>% filter(!is.na(Year), !is.na(Player))
# Remove Team = TOT (which indicates total, when player played in more than 1 team in a season)
NBA <- NBA[NBA$Tm != "TOT",]
# Remove of "*" which indicates a player is a member of NBA Hall of Fame
NBA$Player <- gsub("\\*$", "", NBA$Player)
# Fix player data
PlayerData[2143, 4] = as.factor("6-2")
PlayerData[2143, 5] = 190
NBA[21304, 3] = "SG"


Renaming some columns of the dataset

colnames(PlayerData) <- c("Player", "YearStart", "YearEnd", "Height-feet", "Weight-lbs")


Merging data

NBA <- sqldf("SELECT * FROM NBA JOIN PlayerData ON NBA.Player = PlayerData.Player 
      WHERE NBA.Year >= PlayerData.YearStart AND NBA.Year <= PlayerData.YearEnd")


Fixing Position

In here, for simplicity and esthetical purpose, I arbitrarily framed up all the players into the standard five position used today and then color-coded them:

  • ā€œC-Fā€ in original dataset becomes ā€œCā€
  • ā€œF-Cā€ in original dataset becomes ā€œPFā€
  • ā€œFā€ in original dataset becomes ā€œPFā€
  • ā€œF-Gā€ in original dataset becomes ā€œSFā€
  • ā€œGā€ in original dataset becomes ā€œSGā€
  • ā€œG-Fā€ in original dataset becomes ā€œSFā€
NBA$Pos[NBA$Pos == "C-F"] <- "C"
NBA$Pos[NBA$Pos == "F-C"] <- "PF"
NBA$Pos[NBA$Pos == "F"] <- "PF"
NBA$Pos[NBA$Pos == "F-G"] <- "SF"
NBA$Pos[NBA$Pos == "G"] <- "SG"
NBA$Pos[NBA$Pos == "G-F"] <- "SF"
NBA$Pos <- factor(NBA$Pos, levels = c("C", "PF", "SF", "SG", "PG"))
PosColorCode <- c("C"="#FF0000", "PF"="#FFA500", "SF"="#DDDD00" ,"SG"="#0000FF", "PG"="#32CD32")


Create new variables:

    Player info dataset

  • Height: Convert unit from feet to cm
  • Weight: Convert unit from lbs to kg
  • BMI: Calculate Body Mass Index
  • Born: Year of birth
  • ORpG: (Offensive Rebounds per Game): Average offensive rebounds a player made in a game. (ORB/G)
  • DRpG: (Defensive Rebounds per Game): Average defensive rebounds a player made in a game. (DRB/G)
  • RpG (Rebounds per Game): Average rebounds a player made in a game. (TRB/G)
  • ApG (Assists per Game): Average assists a player made in a game. (AST/G)
  • SPG (Steals per Game): Average rebounds a player made in a game. (STL/G)
  • BPG (Blocks Per Game): Average rebounds a player made in a game. (BLK/G)
  • TPG (Turnovers Per Game): Average turnovers a player made in a game. (TOV/G)
  • PpG (Points per Game): Average points a player made in a game. (PTS/G)
  • Position: Same like Pos, but prettified


NBA <- NBA %>%
    rowwise() %>%
    mutate(Height = convertHeight(`Height-feet`),
           Weight = round(conv_unit(`Weight-lbs`, "lbs", "kg"),0),
           BMI = round(Weight / (Height / 100)^2, 2),
           Born = Year - Age,
           ORpG = ORB / G,
           DRpG = DRB / G,
           RpG = TRB / G,
           ApG = AST / G,
           SpG = STL / G,
           BpG = BLK / G,
           TpG = TOV / G,
           PpG = PTS / G,
           Position = cell_spec(Pos,
                            color = "white",
                            align = "c",
                            background = factor(Pos, c("C", "PF", "SF", "SG", "PG"),
                                                PosColorCode)))


Arrange the table.

NBA <- NBA %>%
    select(Year:MP, YearStart:BMI, "Born", FG:PTS, TS.:TOV., ORpG:PpG, "Position", -c("Height-feet", "Weight-lbs"))


Create duplicate table with normalized stats.

NBA_Scaled <- NBA %>% mutate_at(vars(c(G:MP, FG:PpG)), scale)

NBA_pM <- NBA %>%
    mutate_at(vars(c(FG:PTS)), function(x){ x/NBA$MP}) %>%
    rename_at(vars(c(FG:PTS)), ~paste0(.,"_pM"))

NBA_Scaled <- cbind(NBA_Scaled, NBA_pM[,c(15:36)])


Displaying raw tidy data table

NBA


  • Number of rows: 22345
  • Number of columns: 56
  • Number of players: 3889
  • Number of teams: 68
  • File Size: 7275.5 Kb



Write tidy dataset for later use…

write.csv(NBA, file = "NBA_TidySet.csv")
write.csv(NBA_Scaled, file = "NBA_Scaled_TidySet.csv")




End of Session


LS0tDQp0aXRsZTogIlZpc3VhbGl6aW5nIE5CQSBTZWFzb25zOiBJbnRyb2R1Y3Rpb24gJiBQcmVwYXJhdGlvbiINCmF1dGhvcjogIk51bm5vIE51Z3JvaG8iDQpvdXRwdXQ6DQogIGh0bWxfbm90ZWJvb2s6DQogICAgY3NzOiBzdHlsZS5jc3MNCiAgICB0aGVtZTogcGFwZXINCiAgaHRtbF9kb2N1bWVudDoNCiAgICBkZl9wcmludDogcGFnZWQNCi0tLQ0KDQo8YnIvPg0KPGJyLz4NCg0KPGRpdiBjbGFzcz0iaGVhZGVyIj4NCg0KPGNlbnRlcj48aW1nIHNyYz0iaW1hZ2VzL05CQUxvZ29UcmFuc3AucG5nIiBhbHQ9ImRyYXdpbmciIHdpZHRoPSIyMDBweCIgaGVpZ3RoPSIxMDBweCIvPg0KDQoqKlNlYXNvbnMgMTk1MC0yMDE3KioNCg0KPGRpdiBjbGFzcz0iZHJvcGRvd24iPg0KDQo8YnV0dG9uIGNsYXNzPSJkcm9wYnRuIj5JbnRyb2R1Y3Rpb24gJiBQcmVwYXJhdGlvbjwvYnV0dG9uPg0KDQo8ZGl2IGNsYXNzPSJkcm9wZG93bi1jb250ZW50Ij4NCg0KPGEgaHJlZj0iaHR0cHM6Ly9ycHVicy5jb20vbmluamF6emxlL05CQVNlYXNvbnMxIj5QYXJ0IDE6IFBsYXllcnM8L2E+DQoNCjxhIGhyZWY9Imh0dHBzOi8vcnB1YnMuY29tL25pbmphenpsZS9OQkFTZWFzb25zMiI+UGFydCAyOiBQb2ludHM8L2E+DQoNCjxhIGhyZWY9Imh0dHBzOi8vcnB1YnMuY29tL25pbmphenpsZS9OQkFTZWFzb25zMiI+UGFydCAzOiBTaG9vdGluZ3MgKEZHIGFuZCBGVCk8L2E+DQoNCjxhIGhyZWY9Imh0dHBzOi8vcnB1YnMuY29tL25pbmphenpsZS9OQkFTZWFzb25zNCI+UGFydCA0OiBTaG9vdGluZ3MgKDMtUG9pbnRzIGFuZCBNaXhlZCk8L2E+DQoNCjxhIGhyZWY9Imh0dHBzOi8vcnB1YnMuY29tL25pbmphenpsZS9OQkFTZWFzb25zNSI+UGFydCA1OiBTa2lsbHM8L2E+DQoNCjxhIGhyZWY9Imh0dHBzOi8vcnB1YnMuY29tL25pbmphenpsZS9OQkFTZWFzb25zNiI+UGFydCA2OiBSb2xlczwvYT4NCg0KPC9kaXY+DQoNCjwvZGl2Pg0KDQo8L2NlbnRlcj4NCg0KPC9kaXY+DQoNCjxici8+DQo8YnIvPg0KDQotLS0NCg0KIyBJbnRyb2R1Y3Rpb24NCg0KPGJyLz4NCg0KVGhpcyBpbiBhbiBbZXhwbG9yYXRvcnkgYW5hbHlzaXNdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0V4cGxvcmF0b3J5X2RhdGFfYW5hbHlzaXMpIG9mIHRoZSBOQkEgU2Vhc29ucyAxOTUwLTIwMTcuIElmIHlvdSBhcmUgZmFtaWxpYXIgd2l0aCBOQkEgc3RhdGlzdGljcyB0ZXJtaW5vbG9neSwgZG9uJ3QgY2FyZSBhYm91dCBob3cgdGhlIGRhdGEgYmVpbmcgcHJvY2Vzc2VkLCBvciBqdXN0IHdhbnQgdG8gZ28gc3RyYWlnaHQgdG8gdGhlIGZ1biwgcGxlYXNlIHNraXAgdGhpcyBib3JpbmcgcGFydCBhbmQgcHJvY2VlZCB0byB0aGUgW25leHQgcGFydF0oaHR0cHM6Ly9ycHVicy5jb20vbmluamF6emxlL05CQVNlYXNvbnMxKS4gDQoNCjxici8+DQoNCiMjIyMgQWJvdXQgTWUgYW5kIHRoZSBQcm9qZWN0DQoNCkl0IHdhcyB0aGUgTkJBIHdobyBmaXJzdCBpbnRyb2R1Y2VkIG1lIGludG8gdGhlIGJyb2FkIHdvcmxkIG9mIHN0YXRpc3RpY3MgaW4gYW4gZW50ZXJ0YWluaW5nIG1hbm5lciBiYWNrIGluIG15IGNoaWxkaG9vZC4gU2luY2UgdGhlbiBJIGhhdmUgYWx3YXlzIGJlZW4gY2FwdGl2YXRlZCBieSBzdGF0aXN0aWNzIGFuZCBkYXRhLg0KDQpEZXNwaXRlIG15IGludGVyZXN0IGluIHN0YXRpc3RpY3MgY291cGxlZCB3aXRoIGludGVybWl0dGVudGx5IG1ha2luZyBhbWF0ZXVyaXNoIHN0YXRpc3RpY2FsIGRhdGEgYW5hbHlzaXMgb3V0IG9mIGFueXRoaW5nIHRoYXQgcGlxdWVkIG15IGludGVyZXN0IGZvciBwbGVhc3VyZSwgSSBuZXZlciByZWFsbHkgaGFkIHRoZSBwcm9wZXIgdHJhaW5pbmcgbm9yIGNvbnNpZGVyIHRha2UgdGhlIHN1YmplY3QgYXMgYSBjYXJlZXIgdW50aWwgcmVjZW50bHkuIEl0IHdhcyBub3QgbG9uZyBhZ28sIHdpdGhvdXQgcHJpb3Iga25vd2xlZGdlIGluIHRoZSBhcmVhLCAgSSBzdGFydGVkIHRvIHRlYWNoIG15c2VsZiBjb2RpbmcgaW4gRGF0YSBTY2llbmNlIG9ic2Vzc2l2ZWx5IGJ5IGRpdmUgaW50byBNT09DIChNYXNzaXZlIE9wZW4gT25saW5lIENvdXJzZSkgY291cnNlcyByZWxpZ2lvdXNseSBpbiBteSBzcGFyZSB0aW1lLg0KDQpCZWluZyBhYnNlbnQgZnJvbSBmb2xsb3dpbmcgTkJBIHJlZ3VsYXJseSBmb3IgYSBsb25nIHRpbWUgZWxldmF0ZWQgbXkgZXhjaXRlbWVudCB0byBleHBsb3JlLCBhbmFseXplIGFuZCBnYWluIGluc2lnaHQgZnJvbSB0aGUgZGF0YS4gVGh1cywgdGhpcyBkYXRhc2V0IGlzIHBlcmZlY3QgZm9yIG15IGZpcnN0IGF0dGVtcHQgYXQgZG9pbmcgYSBzZWxmLWRpcmVjdGVkIHByb2plY3QsIGFzIGl0IGlzIGNvbnZlbmllbmNlIGZvciBtZSB0byBkaWcgZGVlcGVyIGludG8gbGVhcm5pbmcgRGF0YSBTY2llbmNlIHdoaWxlIHJlYXBpbmcgdGhlIGpveSBvZiBwcm9kdWNpbmcgaXQuDQoNCkkgZXhwZWN0IHRvIHVzZSB0aGlzIG9wcG9ydHVuaXR5IHRvIGdhaW4gbW9yZSBleHBlcmllbmNlIGJ5IGNoYWxsZW5naW5nIG15c2VsZiB0byBpbXBsZW1lbnQgbXkgbmV3IHNraWxsIHdoaWxlIGV4cGFuZGluZyBpdCBieSBleHBsb3JpbmcgdmFyaW91cyBtZXRob2RzIG9mIGRhdGEgd3JhbmdsaW5nIGFuZCB2aXN1YWxpemF0aW9uLCB0aGVuIGNvbW11bmljYXRpbmcgdGhlIGZpbmRpbmdzIGVmZmljaWVudGx5IGluIGFuIGFlc3RoZXRpYyBtYW5uZXIuIEluIHRoaXMgd2F5LCBJIGNhbiBleHBlY3QgdG8gc2hhcGUgbXkgc2tpbGxzIHRocm91Z2ggcHJhY3RpY2FsIGxlYXJuaW5nLg0KDQpUaGUgb2JqZWN0aXZlIGlzIHRvIHByZXNlbnQgTkJBIHNlYXNvbnMgc3RhdGlzdGljYWwgZmFjdHMgdGhyb3VnaCBjb21wZWxsaW5nIHZpc3VhbGl6YXRpb24gYW5kIHRhYmxlcy4gSSBkbyBub3QgbWFrZSBhbnkgcHJlZGljdGl2ZSBhbmFseXNpcyBoZXJlICh3aWxsIGdldCB0aGVyZSBsYXRlcikuDQoNClRoaXMgcHJlc2VudGF0aW9uIGlzIGRlc2lnbmVkIGZvciBhbnlvbmUgd2hvIGludGVyZXN0ZWQgaW4gdGhpcyBzdWJqZWN0cyB3aXRob3V0IHRoZSBuZWVkIG9mIHByaW9yIHRlY2huaWNhbCBrbm93bGVkZ2UsIGhlbmNlIHRoZSBzaW1wbGlmaWNhdGlvbiBhbmQgcmVmZXJlbmNlcywgaXQgYWxzbyBpbnRlbmRlZCB0byBkcmF3IGV4cGVydGlzZSBpbiB0aGUgc3ViamVjdCB0byBnaXZlIGZlZWRiYWNrIGZvciBteSBpbXByb3ZlbWVudC4NCg0KSSBob3BlIHRoaXMgbWF5IGJlIHVzZWZ1bCBhcyBhIHJlZmVyZW5jZSBmb3IgdW5kZXJzdGFuZGluZyB0aGUgTkJBIHN0YXRpc3RpY3MsIE5CQSBzZWFzb25zIGhpc3RvcnksIG9yIGFzIGEgY29kZSByZWZlcmVuY2UgZm9yIFIgbGVhcm5lcnMNCg0KPGJyLz4NCg0KIyMjIyBUaGUgRGF0YXNldA0KDQpUaGUgW2RhdGFzZXRdKGh0dHBzOi8vd3d3LmthZ2dsZS5jb20vZHJnaWxlcm1vL25iYS1wbGF5ZXJzLXN0YXRzL3ZlcnNpb24vMikgY29udGFpbnMgYWdncmVnYXRlIGluZGl2aWR1YWwgc3RhdGlzdGljcyBmb3IgNjcgTkJBIHNlYXNvbnMgZnJvbSAxOTUwIHRvIDIwMTcuIFRoZSBkYXRhIHdhcyBzY3JhcGVkIGZyb20gQmFza2V0YmFsbC1yZWZlcmVuY2UgYW5kIGlzIF9fb3V0ZGF0ZWRfXy4gTmV2ZXJ0aGVsZXNzLCB0aGUgY29kZXMgYXJlIGRlc2lnbmVkIHRvIGZhY2lsaXRhdGUgdXBkYXRlIG9uY2UgbmV3IGRhdGEgZW1lcmdlLg0KLg0KRm9yIHNpbXBsaWNpdHksIEkgdG9vayBvbmx5IHRoZSBiYXNpYyBzdGF0cyAoYEZHYCwgYFBUU2AsIGV0Yy4pLCBhbmQgZGlzbWlzc2VkIHRoZSBhZHZhbmNlZCBzdGF0cyAoYFZPUlBgLCBgUEVSYCwgYE9CUE1gLCBgVVNHJWAsIGV0Yy4pLg0KDQpTaW5jZSB0aGUgZGF0YXNldCBwcm92aWRlcyByZWd1bGFyIHNlYXNvbiBzdGF0aXN0aWNzIGFuZCBiYXNpYyBwbGF5ZXIgYmlvZGF0YSBvbmx5LCBJIG5lZWQgdG8gcG9pbnQgb3V0IHRoZSBkYXRhc2V0IGxpbWl0YXRpb246DQoNCiogTm8gTkJBIGNoYW1waW9uLCBwbGF5b2ZmcyBvciBBbGwtU3RhciBpcyBwcmVzZW50IGluIHRoZSBkYXRhc2V0Lg0KKiBUaGVyZSdzIG5vIHdpbi1sb3NlIG5vciBzY29yZSBpbmZvcm1hdGlvbi4NCiogTm8gdGVhbSdzIHN0YXRzIGdpdmVuLg0KKiBEYXRhIGFib3V0IHRpdGxlIG9yIGF3YXJkcyAoZS5nLjogTVZQIGNvbnRlc3Qgd2lubmVycywgcm9va2llIG9mIHRoZSB5ZWFyLCBBbGwtU3RhciBwbGF5ZXIsIGV0Yy4pIGlzIG5vdCBwcm92aWRlZCBpbiB0aGUgZGF0YXNldC4NCiogVGhlIHBsYXllcidzIHN0YXRzIGFyZSBwcmVzZW50ZWQgYXMgYSB3aG9sZSBzZWFzb24sIHNvIGl0J3Mgbm90IHBvc3NpYmxlIHRvIGV4dHJhY3QgaW5mb3JtYXRpb24gYWJvdXQgaW4tZ2FtZSByZWNvcmRzLg0KKiBObyByZWdpb25hbCBkaXZpc2lvbiBpbmZvcm1hdGlvbiBpcyBnaXZlbi4NCg0KDQo8YnIvPg0KDQotLS0NCg0KPGJyLz4NCg0KIyBHbG9zc2FyeQ0KDQo8YnIvPg0KDQoqIGBZZWFyYDogIFNpbmNlIHRoZSBOQkEgc2Vhc29uIGlzIHNwbGl0IG92ZXIgdHdvIGNhbGVuZGFyIHllYXJzLCB0aGUgeWVhciBnaXZlbiBpcyB0aGUgbGFzdCB5ZWFyIGZvciB0aGF0IHNlYXNvbi4gU28sIHRoZSB5ZWFyIGZvciB0aGUgMTk5OS0wMCBzZWFzb24gd291bGQgYmUgMjAwMC4NCiogYFBsYXllcmA6IEEgcGxheWVyJ3MgbmFtZS4NCiogYFBvc2A6IFBvc2l0aW9uIChDZW50ZXIsIFBvd2VyIEZvcndhcmQsIFNtYWxsIEZvcndhcmQsIFNob290aW5nIEd1YXJkLCBQb2ludCBHdWFyZCkNCiogYEFnZWA6IFBsYXllcidzIGFnZSBvbiBGZWJydWFyeSAxc3Qgb2YgdGhlIGdpdmVuIHNlYXNvbi4NCiogYFRtYDogVGhlIHRlYW0sIGluIHdoaWNoIHBsYXllciByb3N0ZXJlZCBpbiBhIGdpdmVuIHNlYXNvbi4gQSBwbGF5ZXIgY2FuIGhhdmUgbW9yZSB0aGFuIGEgdGVhbSBpbiBhIHNlYXNvbiBkdWUgdG8gdGhlIGZyZXF1ZW50IHRyYWRlLiBGb3IgZnVydGhlciBpbmZvcm1hdGlvbiBhYm91dCB0ZWFtIGFiYnJldmlhdGlvbnMgYW5kIGhpc3RvcnkgdmlzaXQgW3RoaXMgbGluay5dKGh0dHA6Ly93d3cuc2hycHNwb3J0cy5jb20vbmJhL2V4cGxhaW4uaHRtKQ0KKiBgR2AgKCoqR2FtZXMqKik6IE51bWJlciBvZiBnYW1lcyBwbGF5ZWQgaW4gYSBzZWFzb24NCiogYEdTYCAoKipHYW1lIFN0YXJ0ZWQqKik6IE51bWJlciBvZiBnYW1lcyBwbGF5ZWQgaW4gYSBzZWFzb24gYXMgYSBTdGFydGluZyBMaW5ldXBzIChhdmFpbGFibGUgc2luY2UgdGhlIDE5ODIgc2Vhc29uKQ0KKiBgTVBgICgqKk1pbnV0ZXMgUGxheWVkKiopOiBUb3RhbCBtaW51dGVzIG9mIHBsYXkgaW4gYSBzZWFzb24gKGF2YWlsYWJsZSBzaW5jZSB0aGUgMTk1MS01MiBzZWFzb24pDQoNCg0KKiBgQm9ybmA6IEEgeWVhciB0aGF0IGEgcGxheWVyIGJvcm4uDQoqIGBZZWFyU3RhcnRgOiBNaW51dGVzIG9mIHBsYXllZCBpbiBhIHNlYXNvbi4NCiogYFllYXJFbmRgOiBNaW51dGVzIG9mIHBsYXllZCBpbiBhIHNlYXNvbi4NCiogYEhlaWdodGA6IE1pbnV0ZXMgb2YgcGxheWVkIGluIGEgc2Vhc29uLg0KKiBgV2VpZ2h0YDogTWludXRlcyBvZiBwbGF5ZWQgaW4gYSBzZWFzb24uDQoqIGBCTUlgICgqKkJvZHkgTWFzcyBJbmRleCoqKTogTWludXRlcyBvZiBwbGF5ZWQgaW4gYSBzZWFzb24uDQoNCg0KKiBgRkdgICgqKkZpZWxkIEdvYWxzKiopOiBbRmllbGQgR29hbHNdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0ZpZWxkX2dvYWxfKGJhc2tldGJhbGwpKSAoaW5jbHVkZXMgYm90aCAyLXBvaW50IGZpZWxkIGdvYWxzIGFuZCAzLXBvaW50IGZpZWxkIGdvYWxzKQ0KKiBgRkdBYCAoKipGaWVsZCBHb2FsIEF0dGVtcHRzKiopOiBbRmllbGQgR29hbCBBdHRlbXB0c10oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvRmllbGRfZ29hbF8oYmFza2V0YmFsbCkpIChpbmNsdWRlcyBib3RoIDItcG9pbnQgZmllbGQgZ29hbCBhdHRlbXB0cyBhbmQgMy1wb2ludCBmaWVsZCBnb2FsIGF0dGVtcHRzKQ0KKiBgRkcuYCAoKipGaWVsZCBHb2FsIFBlcmNlbnRhZ2UqKik6IFtGaWVsZCBHb2FsIFBlcmNlbnRhZ2VdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0ZpZWxkX2dvYWxfcGVyY2VudGFnZSkNCiogYFgzUGAgKCoqMy1Qb2ludCBGaWVsZCBHb2FscyoqKTogWzMtUG9pbnQgRmllbGQgR29hbHNdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL1RocmVlLXBvaW50X2ZpZWxkX2dvYWwpIChhdmFpbGFibGUgc2luY2UgdGhlIDE5NzktODAgc2Vhc29uIGluIHRoZSBOQkEpDQoqIGBYM1BBYCAoKiozLVBvaW50IEZpZWxkIEdvYWwgQXR0ZW1wdHMqKik6IFszLVBvaW50IEZpZWxkIEdvYWwgQXR0ZW1wdHNdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL1RocmVlLXBvaW50X2ZpZWxkX2dvYWwpIChhdmFpbGFibGUgc2luY2UgdGhlIDE5NzktODAgc2Vhc29uIGluIHRoZSBOQkEpDQoqIGBYM1AuYCAoKiozLVBvaW50IEZpZWxkIEdvYWwgUGVyY2VudGFnZSoqKTogWzMtUG9pbnQgRmllbGQgR29hbCBQZXJjZW50YWdlXShodHRwczovL3d3dy5zcG9ydGluZ2NoYXJ0cy5jb20vZGljdGlvbmFyeS9uYmEvdGhyZWUtcG9pbnQtZmllbGQtZ29hbC1wZXJjZW50YWdlLmFzcHgpIChhdmFpbGFibGUgc2luY2UgdGhlIDE5NzktODAgc2Vhc29uIGluIHRoZSBOQkEpDQoqIGBYMlBgICgqKjItUG9pbnQgRmllbGQgR29hbHMqKik6IEZpZWxkIEdvYWxzIG1pbnVzIDMtUG9pbnQgRmllbGQgR29hbHMNCiogYFgyUEFgICgqKjItUG9pbnQgRmllbGQgR29hbCBBdHRlbXB0cyoqKTogRmllbGQgR29hbCBBdHRlbXB0cyBtaW51cyAzLVBvaW50IEZpZWxkIEdvYWwgQXR0ZW1wdHMNCiogYFgyUC5gICgqKjItUG9pbnQgRmllbGQgR29hbCBQZXJjZW50YWdlKiopOiAyLVBvaW50IEZpZWxkIEdvYWwgUGVyY2VudGFnZTsgdGhlIGZvcm11bGEgaXMgMlAgLyAyUEENCiogYGVGRy5gICgqKkVmZmVjdGl2ZSBGaWVsZCBHb2FsIFBlcmNlbnRhZ2UqKik6IFRoaXMgc3RhdGlzdGljIGFkanVzdHMgZm9yIHRoZSBmYWN0IHRoYXQgYSAzLXBvaW50IGZpZWxkIGdvYWwgaXMgd29ydGggb25lIG1vcmUgcG9pbnQgdGhhbiBhIDItcG9pbnQgZmllbGQgZ29hbC4gRm9yIGV4YW1wbGUsIHN1cHBvc2UgUGxheWVyIEEgZ29lcyA0IGZvciAxMCB3aXRoIDIgdGhyZWVzLCB3aGlsZSBQbGF5ZXIgQiBnb2VzIDUgZm9yIDEwIHdpdGggMCB0aHJlZXMuIEVhY2ggcGxheWVyIHdvdWxkIGhhdmUgMTAgcG9pbnRzIGZyb20gZmllbGQgZ29hbHMsIGFuZCB0aHVzIHdvdWxkIGhhdmUgdGhlIHNhbWUgZWZmZWN0aXZlIGZpZWxkIGdvYWwgcGVyY2VudGFnZSAoNTAlKS4gVGhlIGZvcm11bGEgaXMgJFxmcmFje0ZHICsgMC41ICogM1B9e0ZHQX0kDQoqIGBGVGAgKCoqRnJlZSBUaHJvd3MqKik6IFtGcmVlIFRocm93c10oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvRnJlZV90aHJvdykNCiogYEZUQWAgKCoqRnJlZSBUaHJvdyBBdHRlbXB0cyoqKTogW0ZyZWUgVGhyb3cgQXR0ZW1wdHNdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0ZyZWVfdGhyb3cpDQoqIGBGVC5gICgqKkZyZWUgVGhyb3cgUGVyY2VudGFnZSoqKTogRnJlZSBUaHJvdyBQZXJjZW50YWdlOyB0aGUgZm9ybXVsYSBpcyBGVCAvIEZUQS4NCg0KDQoqIGBPUkJgICgqKk9mZmVuc2l2ZSBSZWJvdW5kcyoqKTogW09mZmVuc2l2ZSBSZWJvdW5kc10oaHR0cHM6Ly93d3cuc3BvcnRpbmdjaGFydHMuY29tL2RpY3Rpb25hcnkvbmJhL29mZmVuc2l2ZS1yZWJvdW5kcy5hc3B4KSAoYXZhaWxhYmxlIHNpbmNlIHRoZSAxOTczLTc0IHNlYXNvbiBpbiB0aGUgTkJBKQ0KKiBgRFJCYCAoKipEZWZlbnNpdmUgUmVib3VuZHMqKik6IFtEZWZlbnNpdmUgUmVib3VuZHNdKGh0dHBzOi8vd3d3LnNwb3J0aW5nY2hhcnRzLmNvbS9kaWN0aW9uYXJ5L25iYS9kZWZlbnNpdmUtcmVib3VuZC5hc3B4KSAoYXZhaWxhYmxlIHNpbmNlIHRoZSAxOTczLTc0IHNlYXNvbiBpbiB0aGUgTkJBKQ0KKiBgVFJCYCAoKipUb3RhbCBSZWJvdW5kcyoqKTogW1RvdGFsIFJlYm91bmRzXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9SZWJvdW5kXyhiYXNrZXRiYWxsKSkgKGF2YWlsYWJsZSBzaW5jZSB0aGUgMTk1MC01MSBzZWFzb24pIA0KKiBgQVNUYCAoKipBc3Npc3RzKiopOiBbQXNzaXN0c10oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvQXNzaXN0XyhiYXNrZXRiYWxsKSkNCiogYFNUTGAgKCoqU3RlYWxzKiopOiBbU3RlYWxzXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9TdGVhbF8oYmFza2V0YmFsbCkpIChhdmFpbGFibGUgc2luY2UgdGhlIDE5NzMtNzQgc2Vhc29uIGluIHRoZSBOQkEpDQoqIGBCTEtgICgqKkJsb2NrcyoqKTogW0Jsb2Nrc10oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvQmxvY2tfKGJhc2tldGJhbGwpKSAoYXZhaWxhYmxlIHNpbmNlIHRoZSAxOTczLTc0IHNlYXNvbiBpbiB0aGUgTkJBKQ0KKiBgVE9WYCAoKipUdXJub3ZlcnMqKik6IFtUdXJub3ZlcnNdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL1R1cm5vdmVyXyhiYXNrZXRiYWxsKSkgKGF2YWlsYWJsZSBzaW5jZSB0aGUgMTk3Ny03OCBzZWFzb24gaW4gdGhlIE5CQSkNCiogYFBGYCAoKipQZXJzb25hbCBGb3VscyoqKTogW1BlcnNvbmFsIEZvdWxzXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9QZXJzb25hbF9mb3VsXyhiYXNrZXRiYWxsKSkNCiogYFBUU2AgKCoqUG9pbnRzKiopOiBbUG9pbnRzXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9Qb2ludF8oYmFza2V0YmFsbCkpDQoNCg0KKiBgVFMuYCAoKipUcnVlIFNob290aW5nICUqKik6IE1lYXN1cmUgb2Ygc2hvb3RpbmcgZWZmaWNpZW5jeSB0aGF0IHRha2VzIGludG8gYWNjb3VudCBmaWVsZCBnb2FscywgMy1wb2ludCBmaWVsZCBnb2FscywgYW5kIGZyZWUgdGhyb3dzLiBFcXVhdGlvbjogJFxmcmFje1BUU317MlRTQX0kDQoqIGBYM1BBcmAgKCoqMy1Qb2ludCBBdHRlbXAgUmF0ZSoqKTogTWVhc3VyZSBvZiB3aGF0ICUgb2YgYSBwbGF5ZXIncyBzaG90cyBjb21lIGZyb20gbG9uZy1kaXN0YW5jZSwgYW5vdGhlciBnb29kIGdhdWdlIG9mIGhvdyB0aGV5J3JlIHV0aWxpemVkIG9mZmVuc2l2ZWx5Lg0KKiBgRlRyYCAoKipGcmVlIFRocm93IFJhdGUqKik6IFRoZSByYXRpbyBvZiBmcmVlIHRocm93cyBhdHRlbXB0ZWQgcGVyIGZpZWxkIGdvYWwgYXR0ZW1wdGVkLg0KKiBgT1JCLmAgKCoqT2ZmZW5zaXZlIFJlYm91bmQgUGVyY2VudGFnZSoqKTogRXN0aW1hdGUgb2YgdGhlIHBlcmNlbnRhZ2Ugb2YgYXZhaWxhYmxlIG9mZmVuc2l2ZSByZWJvdW5kcyBhIHBsYXllciBncmFiYmVkIHdoaWxlIGhlIHdhcyBvbiB0aGUgZmxvb3IuDQoqIGBEUkIuYCAoKipEZWZlbnNpdmUgUmVib3VuZCBQZXJjZW50YWdlKiopOiBFc3RpbWF0ZSBvZiB0aGUgcGVyY2VudGFnZSBvZiBhdmFpbGFibGUgZGVmZW5zaXZlIHJlYm91bmRzIGEgcGxheWVyIGdyYWJiZWQgd2hpbGUgaGUgd2FzIG9uIHRoZSBmbG9vci4NCiogYFRSQi5gICgqKlRvdGFsIFJlYm91bmQgUGVyY2VudGFnZSoqKTogRXN0aW1hdGUgb2YgdGhlIHBlcmNlbnRhZ2Ugb2YgYXZhaWxhYmxlIHJlYm91bmRzIGEgcGxheWVyIGdyYWJiZWQgd2hpbGUgaGUgd2FzIG9uIHRoZSBmbG9vci4NCiogYEFTVC5gICgqKkFzc2lzdCBQZXJjZW50YWdlKiopOiBFc3RpbWF0ZSBvZiB0aGUgcGVyY2VudGFnZSBvZiB0ZWFtbWF0ZSBmaWVsZCBnb2FscyBhIHBsYXllciBhc3Npc3RlZCB3aGlsZSBoZSB3YXMgb24gdGhlIGZsb29yLg0KKiBgU1RMLmAgKCoqU3RlYWwgUGVyY2VudGFnZSoqKTogRXN0aW1hdGUgb2YgdGhlIHBlcmNlbnRhZ2Ugb2Ygb3Bwb25lbnQgcG9zc2Vzc2lvbnMgdGhhdCBlbmQgd2l0aCBhIHN0ZWFsIGJ5IHRoZSBwbGF5ZXIgd2hpbGUgaGUgd2FzIG9uIHRoZSBmbG9vci4NCiogYEJMSy5gICgqKkJsb2NrIFBlcmNlbnRhZ2UqKik6IEVzdGltYXRlIG9mIHRoZSBwZXJjZW50YWdlIG9mIG9wcG9uZW50IHR3by1wb2ludCBmaWVsZCBnb2FsIGF0dGVtcHRzIGJsb2NrZWQgYnkgdGhlIHBsYXllciB3aGlsZSBoZSB3YXMgb24gdGhlIGZsb29yLg0KKiBgVE9WLmAgKCoqVHVybm92ZXIgUGVyY2VudGFnZSoqKTogRXN0aW1hdGVzIHRoZSBudW1iZXIgb2YgdHVybm92ZXJzIGEgcGxheWVyIGNvbW1pdHMgcGVyIDEwMCBwb3NzZXNzaW9ucy4NCg0KDQo8YnIvPg0KDQojIyMgTW9yZSBhYm91dCBQb3NpdGlvbnMNCg0KSGlzdG9yaWNhbGx5LCBvbmx5IHRocmVlIHBvc2l0aW9ucyB3ZXJlIHJlY29nbml6ZWQgKHR3byBndWFyZHMsIHR3byBmb3J3YXJkcywgYW5kIG9uZSBjZW50ZXIpIGJhc2VkIG9uIHdoZXJlIHRoZXkgcGxheWVkIG9uIHRoZSBjb3VydDogR3VhcmRzIGdlbmVyYWxseSBwbGF5ZWQgb3V0c2lkZSBhbmQgYXdheSBmcm9tIHRoZSBob29wIGFuZCBmb3J3YXJkcyBwbGF5ZWQgb3V0c2lkZSBhbmQgbmVhciB0aGUgYmFzZWxpbmUsIHdpdGggdGhlIGNlbnRlciB1c3VhbGx5IHBvc2l0aW9uZWQgaW4gdGhlIGtleS4gRHVyaW5nIHRoZSAxOTgwcywgYXMgdGVhbSBzdHJhdGVneSBldm9sdmVkIGFmdGVyIHRoZSB0aHJlZS1wb2ludCBmaWVsZCBnb2FsIGFuZCB0aGUgdGhyZWUtcG9pbnQgYXJjIHdlcmUgYWRkZWQgdG8gdGhlIGJhc2tldGJhbGwgY291cnQsIG1vcmUgc3BlY2lhbGl6ZWQgcm9sZXMgZGV2ZWxvcGVkLCByZXN1bHRpbmcgaW4gdGhlIGZpdmUgcG9zaXRpb24gZGVzaWduYXRpb25zIHVzZWQgdG9kYXkuIChbc291cmNlXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9CYXNrZXRiYWxsX3Bvc2l0aW9ucykpDQoNCkluIGhlcmUsIGZvciBzaW1wbGljaXR5IGFuZCBlc3RoZXRpY2FsIHB1cnBvc2UsIEkgYXJiaXRyYXJpbHkgZnJhbWVkIHVwIGFsbCB0aGUgcGxheWVycyBpbnRvIHRoZSBzdGFuZGFyZCBmaXZlIHBvc2l0aW9uIHVzZWQgdG9kYXkgYW5kIHRoZW4gY29sb3ItY29kZWQgdGhlbS4gVGhpcyBpcyBlc3NlbnNpYWwgYmVjYXVzZSBJIHRlbmQgdG8gbWFrZSBnb29kIHVzZSBvZiB0aGlzIGZvciBjb21wYXJpc29uIGZyZXF1ZW50bHkuDQoNCkZvciBtb3JlIGluZm9ybWF0aW9uIGFib3V0IGVhY2ggcG9zaXRpb24sIGNsaWNrIG9uIHRoZSBwb3NpdGlvbiBpY29uIGJlbG93Lg0KDQo8ZGl2IGNsYXNzPSJQb3MiPg0KDQo8dWw+DQoNCjxsaT48YSBocmVmPSJodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9DZW50ZXJfKGJhc2tldGJhbGwpIiB0YXJnZXQ9Il9ibGFuayI+PGJ1dHRvbiBpZD0iQyI+PC9idXR0b24+PC9hPjogQ2VudGVyPC9saT4NCg0KPGxpPjxhIGhyZWY9Imh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL1Bvd2VyX2ZvcndhcmRfKGJhc2tldGJhbGwpIiB0YXJnZXQ9Il9ibGFuayI+PGJ1dHRvbiBpZD0iUEYiPjwvYnV0dG9uPjwvYT46IFBvd2VyIEZvcndhcmQ8L2xpPg0KDQo8bGk+PGEgaHJlZj0iaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvU21hbGxfZm9yd2FyZCIgdGFyZ2V0PSJfYmxhbmsiPjxidXR0b24gaWQ9IlNGIj48L2J1dHRvbj48L2E+OiBTbWFsbCBGb3J3YXJkPC9saT4NCg0KPGxpPjxhIGhyZWY9Imh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL1Nob290aW5nX2d1YXJkIiB0YXJnZXQ9Il9ibGFuayI+PGJ1dHRvbiBpZD0iU0ciPjwvYnV0dG9uPjwvYT46IFNob290aW5nIEd1YXJkPC9saT4NCg0KPGxpPjxhIGhyZWY9Imh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL1BvaW50X2d1YXJkIiB0YXJnZXQ9Il9ibGFuayI+PGJ1dHRvbiBpZD0iUEciPjwvYnV0dG9uPjwvYT46IFBvaW50IEd1YXJkPC9saT4NCg0KPC91bD4NCg0KPC9kaXY+DQoNCjxici8+DQoNCi0tLQ0KDQo8YnIvPg0KDQojIERhdGEgUHJlcGFyYXRpb24gU3RhZ2UNCg0KPGJyLz4NCg0KTG9hZGluZyBuZWNlc3NhcnkgcGFja2FnZXMuDQoNCmBgYHtyIHNldHVwLCBtZXNzYWdlPUZBTFNFfQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkodGlkeXIpDQpsaWJyYXJ5KG1lYXN1cmVtZW50cykNCmxpYnJhcnkoc3FsZGYpDQpsaWJyYXJ5KGthYmxlRXh0cmEpDQpgYGANCg0KPGJyLz4NCg0KQ3JlYXRlIGFkZGl0aW9uYWwgZnVuY3Rpb25zDQoNCmBgYHtyfQ0KIyBNb2RlIGF2ZXJhZ2UNCmdldG1vZGUgPC0gZnVuY3Rpb24odikgew0KICAgdW5pcXYgPC0gdW5pcXVlKHYpDQogICB1bmlxdlt3aGljaC5tYXgodGFidWxhdGUobWF0Y2godiwgdW5pcXYpKSldDQp9DQoNCmNvbnZlcnRIZWlnaHQgPC0gZnVuY3Rpb24oeCkgew0KICAgIHggPC0gYXMuY2hhcmFjdGVyKHgpDQogICAgc3BsaXQgPC0gc3Ryc3BsaXQoeCwgIi0iKQ0KICAgIGZlZXQgPC0gYXMubnVtZXJpYyhzcGxpdFtbMV1dWzFdKQ0KICAgIGluY2ggPC0gYXMubnVtZXJpYyhzcGxpdFtbMV1dWzJdKQ0KICAgIHJvdW5kKGNvbnZfdW5pdChmZWV0LCAiZnQiLCAiY20iKSArIGNvbnZfdW5pdChpbmNoLCAiaW5jaCIsICJjbSIpLDApDQp9DQpgYGANCg0KPGJyLz4NCg0KIyMjIyBMb2FkIHRoZSBkYXRhc2V0DQoNCjxici8+DQoNCkRvd25sb2FkIGFuZCBleHRyYWN0aW5nIHRoZSBmaWxlcy4NCg0KRGF0YXNldCBmcm9tOiBodHRwczovL3d3dy5rYWdnbGUuY29tL2RyZ2lsZXJtby9uYmEtcGxheWVycy1zdGF0cy92ZXJzaW9uLzINCg0KYGBge3J9DQpmaWxlVVJMIDwtICJodHRwczovL3d3dy5rYWdnbGUuY29tL2RyZ2lsZXJtby9uYmEtcGxheWVycy1zdGF0cy9kb3dubG9hZHMvbmJhLXBsYXllcnMtc3RhdHMuemlwLzIiDQpmaWxlbmFtZSA8LSAiTkJBU2Vhc29uMTk1MC0yMDE3LnppcCINCg0KIyBDaGVja2luZyBpZiBhcmNoaWV2ZSBhbHJlYWR5IGV4aXN0cy4NCmlmICghZmlsZS5leGlzdHMoZmlsZW5hbWUpKXsNCiAgZG93bmxvYWQuZmlsZShmaWxlVVJMLCBmaWxlbmFtZSwgbWV0aG9kPSJjdXJsIikNCn0gIA0KDQojIENoZWNraW5nIGlmIGZvbGRlciBleGlzdHMNCmlmICghZmlsZS5leGlzdHMoIk5CQSBTZWFzb24gRGF0YXNldCIpKSB7IA0KICB1bnppcChmaWxlbmFtZSkNCn0NCmBgYA0KDQoqKkRvd25sb2FkIGRhdGU6IDI4IEp1bHkgMjAxOCoqDQoNCjxici8+DQoNCkxvYWQgdGhlIGRhdGEuDQoNCmBgYHtyfQ0KTkJBIDwtIHJlYWQuY3N2KCJOQkEgU2Vhc29uIERhdGFzZXQvU2Vhc29uc19TdGF0cy5jc3YiKVssIGMoMjo5LCAxMToyMCwgMzI6NTMpXQ0KUGxheWVyRGF0YSA8LSByZWFkLmNzdigiTkJBIFNlYXNvbiBEYXRhc2V0L3BsYXllcl9kYXRhLmNzdiIpWywgYygxOjMsIDU6NildDQpgYGANCg0KPGJyLz4NCg0KIyMjIyBUaWR5aW5nIGRhdGENCg0KPGJyLz4NCg0KQ2xlYW4gdXAgZGF0YS4NCg0KYGBge3J9DQojIFJlbW92ZSBOQSByb3dzDQpOQkEgPC0gTkJBICU+JSBmaWx0ZXIoIWlzLm5hKFllYXIpLCAhaXMubmEoUGxheWVyKSkNCiMgUmVtb3ZlIFRlYW0gPSBUT1QgKHdoaWNoIGluZGljYXRlcyB0b3RhbCwgd2hlbiBwbGF5ZXIgcGxheWVkIGluIG1vcmUgdGhhbiAxIHRlYW0gaW4gYSBzZWFzb24pDQpOQkEgPC0gTkJBW05CQSRUbSAhPSAiVE9UIixdDQojIFJlbW92ZSBvZiAiKiIgd2hpY2ggaW5kaWNhdGVzIGEgcGxheWVyIGlzIGEgbWVtYmVyIG9mIE5CQSBIYWxsIG9mIEZhbWUNCk5CQSRQbGF5ZXIgPC0gZ3N1YigiXFwqJCIsICIiLCBOQkEkUGxheWVyKQ0KIyBGaXggcGxheWVyIGRhdGENClBsYXllckRhdGFbMjE0MywgNF0gPSBhcy5mYWN0b3IoIjYtMiIpDQpQbGF5ZXJEYXRhWzIxNDMsIDVdID0gMTkwDQpOQkFbMjEzMDQsIDNdID0gIlNHIg0KDQpgYGANCg0KPGJyLz4NCg0KUmVuYW1pbmcgc29tZSBjb2x1bW5zIG9mIHRoZSBkYXRhc2V0DQoNCmBgYHtyfQ0KY29sbmFtZXMoUGxheWVyRGF0YSkgPC0gYygiUGxheWVyIiwgIlllYXJTdGFydCIsICJZZWFyRW5kIiwgIkhlaWdodC1mZWV0IiwgIldlaWdodC1sYnMiKQ0KYGBgDQoNCjxici8+DQoNCk1lcmdpbmcgZGF0YQ0KDQpgYGB7cn0NCk5CQSA8LSBzcWxkZigiU0VMRUNUICogRlJPTSBOQkEgSk9JTiBQbGF5ZXJEYXRhIE9OIE5CQS5QbGF5ZXIgPSBQbGF5ZXJEYXRhLlBsYXllciANCiAgICAgIFdIRVJFIE5CQS5ZZWFyID49IFBsYXllckRhdGEuWWVhclN0YXJ0IEFORCBOQkEuWWVhciA8PSBQbGF5ZXJEYXRhLlllYXJFbmQiKQ0KYGBgDQoNCjxici8+DQoNCkZpeGluZyBQb3NpdGlvbg0KDQpJbiBoZXJlLCBmb3Igc2ltcGxpY2l0eSBhbmQgZXN0aGV0aWNhbCBwdXJwb3NlLCBJIGFyYml0cmFyaWx5IGZyYW1lZCB1cCBhbGwgdGhlIHBsYXllcnMgaW50byB0aGUgc3RhbmRhcmQgZml2ZSBwb3NpdGlvbiB1c2VkIHRvZGF5IGFuZCB0aGVuIGNvbG9yLWNvZGVkIHRoZW06DQoNCiogKioiQy1GIioqIGluIG9yaWdpbmFsIGRhdGFzZXQgYmVjb21lcyAqKiJDIioqDQoqICoqIkYtQyIqKiBpbiBvcmlnaW5hbCBkYXRhc2V0IGJlY29tZXMgKioiUEYiKioNCiogKioiRiIqKiBpbiBvcmlnaW5hbCBkYXRhc2V0IGJlY29tZXMgKioiUEYiKioNCiogKioiRi1HIioqIGluIG9yaWdpbmFsIGRhdGFzZXQgYmVjb21lcyAqKiJTRiIqKg0KKiAqKiJHIioqIGluIG9yaWdpbmFsIGRhdGFzZXQgYmVjb21lcyAqKiJTRyIqKg0KKiAqKiJHLUYiKiogaW4gb3JpZ2luYWwgZGF0YXNldCBiZWNvbWVzICoqIlNGIioqDQoNCmBgYHtyfQ0KTkJBJFBvc1tOQkEkUG9zID09ICJDLUYiXSA8LSAiQyINCk5CQSRQb3NbTkJBJFBvcyA9PSAiRi1DIl0gPC0gIlBGIg0KTkJBJFBvc1tOQkEkUG9zID09ICJGIl0gPC0gIlBGIg0KTkJBJFBvc1tOQkEkUG9zID09ICJGLUciXSA8LSAiU0YiDQpOQkEkUG9zW05CQSRQb3MgPT0gIkciXSA8LSAiU0ciDQpOQkEkUG9zW05CQSRQb3MgPT0gIkctRiJdIDwtICJTRiINCk5CQSRQb3MgPC0gZmFjdG9yKE5CQSRQb3MsIGxldmVscyA9IGMoIkMiLCAiUEYiLCAiU0YiLCAiU0ciLCAiUEciKSkNClBvc0NvbG9yQ29kZSA8LSBjKCJDIj0iI0ZGMDAwMCIsICJQRiI9IiNGRkE1MDAiLCAiU0YiPSIjRERERDAwIiAsIlNHIj0iIzAwMDBGRiIsICJQRyI9IiMzMkNEMzIiKQ0KYGBgDQoNCjxici8+DQoNCkNyZWF0ZSBuZXcgdmFyaWFibGVzOg0KDQo8dWw+UGxheWVyIGluZm8gZGF0YXNldA0KDQo8bGk+YEhlaWdodGA6IENvbnZlcnQgdW5pdCBmcm9tICpmZWV0KiB0byAqY20qPC9saT4NCjxsaT5gV2VpZ2h0YDogQ29udmVydCB1bml0IGZyb20gKmxicyogdG8gKmtnKjwvbGk+DQo8bGk+YEJNSWA6IENhbGN1bGF0ZSBCb2R5IE1hc3MgSW5kZXg8L2xpPg0KPGxpPmBCb3JuYDogWWVhciBvZiBiaXJ0aDwvbGk+DQo8bGk+YE9ScEdgOiAoKipPZmZlbnNpdmUgUmVib3VuZHMgcGVyIEdhbWUqKik6IEF2ZXJhZ2Ugb2ZmZW5zaXZlIHJlYm91bmRzIGEgcGxheWVyIG1hZGUgaW4gYSBnYW1lLiAoT1JCL0cpPC9saT4NCjxsaT5gRFJwR2A6ICgqKkRlZmVuc2l2ZSBSZWJvdW5kcyBwZXIgR2FtZSoqKTogQXZlcmFnZSBkZWZlbnNpdmUgcmVib3VuZHMgYSBwbGF5ZXIgbWFkZSBpbiBhIGdhbWUuIChEUkIvRyk8L2xpPg0KPGxpPmBScEdgICgqKlJlYm91bmRzIHBlciBHYW1lKiopOiBBdmVyYWdlIHJlYm91bmRzIGEgcGxheWVyIG1hZGUgaW4gYSBnYW1lLiAoVFJCL0cpPC9saT4NCjxsaT5gQXBHYCAoKipBc3Npc3RzIHBlciBHYW1lKiopOiBBdmVyYWdlIGFzc2lzdHMgYSBwbGF5ZXIgbWFkZSBpbiBhIGdhbWUuIChBU1QvRyk8L2xpPg0KPGxpPmBTUEdgICgqKlN0ZWFscyBwZXIgR2FtZSoqKTogQXZlcmFnZSByZWJvdW5kcyBhIHBsYXllciBtYWRlIGluIGEgZ2FtZS4gKFNUTC9HKTwvbGk+DQo8bGk+YEJQR2AgKCoqQmxvY2tzIFBlciBHYW1lKiopOiBBdmVyYWdlIHJlYm91bmRzIGEgcGxheWVyIG1hZGUgaW4gYSBnYW1lLiAoQkxLL0cpPC9saT4NCjxsaT5gVFBHYCAoKipUdXJub3ZlcnMgUGVyIEdhbWUqKik6IEF2ZXJhZ2UgdHVybm92ZXJzIGEgcGxheWVyIG1hZGUgaW4gYSBnYW1lLiAoVE9WL0cpPC9saT4NCjxsaT5gUHBHYCAoKipQb2ludHMgcGVyIEdhbWUqKik6IEF2ZXJhZ2UgcG9pbnRzIGEgcGxheWVyIG1hZGUgaW4gYSBnYW1lLiAoUFRTL0cpPC9saT4NCjxsaT5gUG9zaXRpb25gOiBTYW1lIGxpa2UgYFBvc2AsIGJ1dCBwcmV0dGlmaWVkPC9saT4NCg0KPC91bD4NCg0KPGJyLz4NCg0KYGBge3J9DQpOQkEgPC0gTkJBICU+JQ0KICAgIHJvd3dpc2UoKSAlPiUNCiAgICBtdXRhdGUoSGVpZ2h0ID0gY29udmVydEhlaWdodChgSGVpZ2h0LWZlZXRgKSwNCiAgICAgICAgICAgV2VpZ2h0ID0gcm91bmQoY29udl91bml0KGBXZWlnaHQtbGJzYCwgImxicyIsICJrZyIpLDApLA0KICAgICAgICAgICBCTUkgPSByb3VuZChXZWlnaHQgLyAoSGVpZ2h0IC8gMTAwKV4yLCAyKSwNCiAgICAgICAgICAgQm9ybiA9IFllYXIgLSBBZ2UsDQogICAgICAgICAgIE9ScEcgPSBPUkIgLyBHLA0KICAgICAgICAgICBEUnBHID0gRFJCIC8gRywNCiAgICAgICAgICAgUnBHID0gVFJCIC8gRywNCiAgICAgICAgICAgQXBHID0gQVNUIC8gRywNCiAgICAgICAgICAgU3BHID0gU1RMIC8gRywNCiAgICAgICAgICAgQnBHID0gQkxLIC8gRywNCiAgICAgICAgICAgVHBHID0gVE9WIC8gRywNCiAgICAgICAgICAgUHBHID0gUFRTIC8gRywNCiAgICAgICAgICAgUG9zaXRpb24gPSBjZWxsX3NwZWMoUG9zLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yID0gIndoaXRlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBhbGlnbiA9ICJjIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kID0gZmFjdG9yKFBvcywgYygiQyIsICJQRiIsICJTRiIsICJTRyIsICJQRyIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgUG9zQ29sb3JDb2RlKSkpDQoNCmBgYA0KDQo8YnIvPg0KDQpBcnJhbmdlIHRoZSB0YWJsZS4NCmBgYHtyfQ0KTkJBIDwtIE5CQSAlPiUNCiAgICBzZWxlY3QoWWVhcjpNUCwgWWVhclN0YXJ0OkJNSSwgIkJvcm4iLCBGRzpQVFMsIFRTLjpUT1YuLCBPUnBHOlBwRywgIlBvc2l0aW9uIiwgLWMoIkhlaWdodC1mZWV0IiwgIldlaWdodC1sYnMiKSkNCmBgYA0KDQo8YnIvPg0KDQpDcmVhdGUgZHVwbGljYXRlIHRhYmxlIHdpdGggbm9ybWFsaXplZCBzdGF0cy4NCmBgYHtyfQ0KTkJBX1NjYWxlZCA8LSBOQkEgJT4lIG11dGF0ZV9hdCh2YXJzKGMoRzpNUCwgRkc6UHBHKSksIHNjYWxlKQ0KDQpOQkFfcE0gPC0gTkJBICU+JQ0KICAgIG11dGF0ZV9hdCh2YXJzKGMoRkc6UFRTKSksIGZ1bmN0aW9uKHgpeyB4L05CQSRNUH0pICU+JQ0KICAgIHJlbmFtZV9hdCh2YXJzKGMoRkc6UFRTKSksIH5wYXN0ZTAoLiwiX3BNIikpDQoNCk5CQV9TY2FsZWQgPC0gY2JpbmQoTkJBX1NjYWxlZCwgTkJBX3BNWyxjKDE1OjM2KV0pDQpgYGANCg0KPGJyLz4NCg0KKipEaXNwbGF5aW5nIHJhdyB0aWR5IGRhdGEgdGFibGUqKg0KDQo8ZGl2IGNsYXNzPSdtYWludGFibGUnPg0KDQpgYGB7cn0NCk5CQQ0KYGBgDQoNCjxici8+DQoNCjxkaXYgY2xhc3M9IkZhY3QiPg0KDQo8dWwgY2xhc3M9IkN1c3RvbUxpc3QiPg0KDQo8bGk+TnVtYmVyIG9mIHJvd3M6IGByIG5yb3coTkJBKWA8L2xpPg0KPGxpPk51bWJlciBvZiBjb2x1bW5zOiBgciBuY29sKE5CQSlgPC9saT4NCjxsaT5OdW1iZXIgb2YgcGxheWVyczogYHIgbl9kaXN0aW5jdChOQkEkUGxheWVyKWA8L2xpPg0KPGxpPk51bWJlciBvZiB0ZWFtczogYHIgbl9kaXN0aW5jdChOQkEkVG0pYDwvbGk+DQo8bGk+RmlsZSBTaXplOiBgciBmb3JtYXQob2JqZWN0LnNpemUoTkJBKSwgdW5pdHMgPSAiS2IiKWA8L2xpPg0KDQo8L3VsPg0KDQo8L2Rpdj4NCg0KDQo8YnIvPg0KPGJyLz4NCg0KV3JpdGUgdGlkeSBkYXRhc2V0IGZvciBsYXRlciB1c2UuLi4NCmBgYHtyfQ0Kd3JpdGUuY3N2KE5CQSwgZmlsZSA9ICJOQkFfVGlkeVNldC5jc3YiKQ0Kd3JpdGUuY3N2KE5CQV9TY2FsZWQsIGZpbGUgPSAiTkJBX1NjYWxlZF9UaWR5U2V0LmNzdiIpDQpgYGANCg0KPC9kaXY+DQoNCjxici8+DQo8YnIvPg0KDQotLS0NCg0KPGRpdiBjbGFzcz0icm93Ij4NCg0KIDxkaXYgY2xhc3M9ImNvbHVtbiBsZWZ0Iiwgc3R5bGU9ImJhY2tncm91bmQtaW1hZ2U6IG5vbmU7IHdpZHRoOiAyMCU7IGJvcmRlcjogbm9uZTsiPg0KIDxhIGhyZWY9IiMiIHRhcmdldD0iX2JsYW5rIj4NCg0KIDwvYT4NCiA8L2Rpdj4NCiANCiA8ZGl2IGNsYXNzPSJjb2x1bW4gbWlkZGxlIj4NCg0KRW5kIG9mIFNlc3Npb24NCg0KICA8L2Rpdj4NCiAgDQogIDxkaXYgY2xhc3M9ImNvbHVtbiByaWdodCI+DQogIDxhIGhyZWY9Imh0dHBzOi8vcnB1YnMuY29tL25pbmphenpsZS9OQkFTZWFzb25zMSIgdGFyZ2V0PSJfYmxhbmsiPg0KPGJ1dHRvbiBjbGFzcz0icmlnaHRidG4iPjwvYnV0dG9uPg0KICA8L2E+DQogIDwvZGl2Pg0KICANCjwvZGl2Pg0KDQotLS0=