Required packages

This section loads the required packages

# Loads required packages
library(dplyr)
library(readr)
library(stringr)
library(tidyr)
library(forecast)
library(Hmisc)

Executive Summary

In this assignment, we applied what we learnt during the semester on data wrangling and data pre-processing techniques. Two data sets were merged using the following key pre-processing steps:

1.Data (Part 1 of 2): The two data sets used for this assignment is related to the NBA (National Basketball Association). The NBA is an American men’s professional basketball league. It’s made up of 30 teams. It is one of the top men’s professional basketball league in the world.

They were both collected from the basketball reference site: [https://www.basketball-reference.com/] which holds all types of basketball related statistics.

Before the data sets could be merged, there’s was a few de-duplication and manipulation steps. In the detailed sections below, I’ve broken the data sections into two parts. The first part is the initial inspection and the second part was where i merged the two data sets.

2.Understand: In this section, I inspected the attributes and also looked at a few samples. I found that there were some illegal characters that needed to be stripped out. There was also some character->numeric and character-factors conversion completed here,In particular an ordered factor around player position.

3.De-duplication & Scan I: In this section, I’ve introduced a step where i de-duplicated both data sets. It is very common in the NBA that a player can be traded during the playing season. Hence the players stats will have multiple records if a player played for multiple teams, This is the same for the contracts data sets as two teams could be paying a salary. For this assignment, I’ve consolidated the players stats to be one total and also assigned it to his latest team since i wanted to use the whole season as a comparison and not in between the season.

4.Tidy & Manipulate Data I: In this section, I checked the tidiness of the data sets and found that the contracts data set had to be converted from a wide format to a long format. I used the gather function here to do the conversion. The players statistics data set was already tidy after the de-duplication and cleansing of the data in previous steps.

5.Data (Part 1 of 2): After some de-duplication and manipulation, I finally merged my two data sets in this section.

6.Tidy & Manipulate Data II: In this section, I created two new fields. player efficiency and salary per game. The formula for efficiency can be found here: [https://en.wikipedia.org/wiki/Efficiency_(basketball)] and is used across most basketball league to measure a players’ basketball efficiency using counting statistics. Salary per game was a field i was interested to see how much a player earns on average for the season.

7.Scan II - Outliers: In this section, I explored the numeric data to detect outliers and we were able to remove the outliers using a capping technique that we applied to the salary and player efficiency data.

8.Transform:

Transformed the data using a few techniques to normalised the salary data. In this section, I tried a few tehcniquesnamely Log10, Square Root and Box-Cox methods normalised the salary data. Both Log10 and Box-Cox techniques were the best at achieiving this.

Data (Part 1 of 2)

This is the detailed data section where the data set was initially looked at. The first dataset relates to NBA players salary starting from the 2019-2020 Season. The second dataset is related to NBA player total statistics for the 2019-2020 Season.

Data - Data Set 1: NBA Player Contracts 2019-2020.

Data Set 1: NBA Player Contracts 2019-2020

https://www.basketball-reference.com/contracts/players.html

The following variables are found in the dataset and defined as follows:

  • Rk - Rank of salary sorted by the 2019-2020 NBA Season.
  • Player - The player’s Name.
  • Tm - The team the players plays for.
  • 2019-20 - The Player salary’s for the 2019-2020 NBA Season.
  • 2020-21 - The Player salary’s for the 2020-2021 NBA Season.
  • 2021-22 - The Player salary’s for the 2021-2022 NBA Season.
  • 2022-23 - The Player salary’s for the 2022-2023 NBA Season.
  • 2023-24 - The Player salary’s for the 2023-2024 NBA Season.
  • 2024-25 - The Player salary’s for the 2024-2025 NBA Season.
  • Signed Using - The type of contract the player signed up for.
  • Guaranteed - The amount of a player’s remaining salary that is guaranteed.
## Load data section for data set 1: NBA Player Contracts 2019-2020 

# Create variable to hold the column names.
player_contract_header <- c("Rk","Player","Tm","2019-20","2020-21","2021-22","2022-23","2023-24","2024-25","Signed Using","Guaranteed") 

# Loading in the data via the "read_csv" command.
player_contract <- read_csv("2019_20_NBA_Player_Contracts.csv",skip=2, col_names=player_contract_header)
Parsed with column specification:
cols(
  Rk = col_double(),
  Player = col_character(),
  Tm = col_character(),
  `2019-20` = col_character(),
  `2020-21` = col_character(),
  `2021-22` = col_character(),
  `2022-23` = col_character(),
  `2023-24` = col_character(),
  `2024-25` = col_character(),
  `Signed Using` = col_character(),
  Guaranteed = col_character()
)
# Getting the dimensions of the dataset.
dim(player_contract)
[1] 568  11
# Having a peek of the dataset.
head(player_contract)

Data set 1 contains 568 observations(Rows) and 11 variables(Columns).

Data - Dataset 2: NBA Player Total Statistics for the 2019-2020 NBA Season.

Data Set 2: NBA Player Total Statistics for the 2019-2020 NBA Season.

https://www.basketball-reference.com/leagues/NBA_2020_totals.html

The following variables are found in the dataset and defined as follows:

  • Rk - Rank of salary sorted by the player’s last name in ascending order.
  • Player - The player’s Name.
  • Pos - The player’s basketball position that they play on the basketball court.
  • Age - The age of the player as of the 1st of February of the season.
  • Tm - The team the players plays for.
  • G - The number of games the player played in the Season.
  • GS - The number of games the player started in the Season.
  • MP - The amount of time the player played measured in minutes.
  • FG - The number of field goals baskets made.
  • FGA - The number of field goals baskets attempted.
  • FG% - The percentage of a player’s made field goal.
  • 3P - The number of 3 pointers field goals baskets made.
  • 3PA - The number of 3 pointers field goals baskets attempted.
  • 3P% - The percentage of a player’s 3 pointer made field goal.
  • 2P - The number of 2 point field goals baskets made.
  • 2PA - The number of 2 point field goals baskets attempted.
  • 2P% - The percentage of a player’s 2 point made field goal.
  • eFG% - The effective field goal percentage, based on the formula: (FG + 0.5 * 3P) / FGA
  • FT - The number of free throws made.
  • FTA - The number of free throws attempted.
  • FT% - The percentage of a player’s 2 point made field goal.
  • ORB - The number of offensive Rebounds.
  • DRB - The number of defensive Rebounds.
  • TRB - The number of total Rebounds.
  • AST - The number of assists.
  • STL - The number of steals.
  • BLK - The number of blocks.
  • TOV - The number of turnovers.
  • PF - The number of personal fouls.
  • PTS - The number of points.
## Load data section for data set 2: NBA Player Total Statistics for the 2019-2020 NBA Season.

# Loading in the data via the "read_csv" command.
player_stats <- read_csv("2019_20_NBA_Player_Stats_Totals.csv")
Parsed with column specification:
cols(
  .default = col_double(),
  Player = col_character(),
  Pos = col_character(),
  Tm = col_character()
)
See spec(...) for full column specifications.
# Getting the dimensions of the dataset.
dim(player_stats)
[1] 651  30
# Having a peek of the dataset.
head(player_stats)

Data set 2 contains 651 observations(Rows) and 30 variables(Columns).

We are unable to merge the 2 datasets in this section as there’s a number of conversion and tidy steps we need to do before we can merge the datasets. The mergining of the dataset will be defined in part 2 of the data section.

Understand

In this section, we seek to understand the 2 data sets and apply any conversions to the attributes of the data.

Understand - Data Set 1: NBA Player Contracts 2019-2020.

# Checking the structure of the data set
str(player_contract)
tibble [568 x 11] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
 $ Rk          : num [1:568] 1 2 3 4 5 6 7 8 9 10 ...
 $ Player      : chr [1:568] "Stephen Curry\\curryst01" "Chris Paul\\paulch01" "Russell Westbrook\\westbru01" "John Wall\\walljo01" ...
 $ Tm          : chr [1:568] "GSW" "OKC" "HOU" "WAS" ...
 $ 2019-20     : chr [1:568] "$40,231,758" "$38,506,482" "$38,178,000" "$37,800,000" ...
 $ 2020-21     : chr [1:568] "$43,006,362" "$41,358,814" "$41,006,000" "$40,824,000" ...
 $ 2021-22     : chr [1:568] "$45,780,966" "$44,211,146" "$43,848,000" "$43,848,000" ...
 $ 2022-23     : chr [1:568] NA NA "$46,662,000" "$46,872,000" ...
 $ 2023-24     : chr [1:568] NA NA NA NA ...
 $ 2024-25     : chr [1:568] NA NA NA NA ...
 $ Signed Using: chr [1:568] "Bird Rights" "Bird Rights" "Bird Rights" "Bird Rights" ...
 $ Guaranteed  : chr [1:568] "$129,019,086" "$79,865,296" "$123,032,000" "$122,472,000" ...
 - attr(*, "spec")=
  .. cols(
  ..   Rk = col_double(),
  ..   Player = col_character(),
  ..   Tm = col_character(),
  ..   `2019-20` = col_character(),
  ..   `2020-21` = col_character(),
  ..   `2021-22` = col_character(),
  ..   `2022-23` = col_character(),
  ..   `2023-24` = col_character(),
  ..   `2024-25` = col_character(),
  ..   `Signed Using` = col_character(),
  ..   Guaranteed = col_character()
  .. )

It appears that after a sneak peek of the dataset, we need to apply some conversions to the dataset, stripping of certain characters and also converting character fields to numeric fields. We will also rename the fields in the section to make them more readable.

# Stripping out illegal characters
# Cleaning up the "$" and "\" symbols from the salary columns.
# str_replace(player_contract,"\\$","")
pattern = c("\\$|\\,")
player_contract <- player_contract %>%
  mutate_all(function(x) gsub(pattern,"",x))

# Split out the player field into the two new fields: Player Name and Player ID
player_contract <- player_contract %>% separate(Player, c("Player_Name","Player_ID"), sep="\\\\")

# Renaming the columns
player_contract <- player_contract %>% rename(Salary_Rank = Rk)
player_contract <- player_contract %>% rename(Team = Tm)
player_contract <- player_contract %>% rename("Salary_2019-20" = "2019-20")
player_contract <- player_contract %>% rename("Salary_2020-21" = "2020-21")
player_contract <- player_contract %>% rename("Salary_2021-22" = "2021-22")
player_contract <- player_contract %>% rename("Salary_2022-23" = "2022-23")
player_contract <- player_contract %>% rename("Salary_2023-24" = "2023-24")
player_contract$`Salary_2019-20` <- as.numeric(player_contract$`Salary_2019-20`)
player_contract$`Salary_2020-21` <- as.numeric(player_contract$`Salary_2020-21`)
player_contract$`Salary_2021-22` <- as.numeric(player_contract$`Salary_2021-22`)
player_contract$`Salary_2022-23` <- as.numeric(player_contract$`Salary_2022-23`)
player_contract$`Salary_2023-24` <- as.numeric(player_contract$`Salary_2023-24`)

# Keep required columns only for the 2019-2020 Season
player_contract <- player_contract %>% 
                   select(Player_Name,Player_ID,Team,"Salary_2019-20","Salary_2020-21")

# Checking the structure of the data set after the conversions
str(player_contract)
tibble [568 x 5] (S3: tbl_df/tbl/data.frame)
 $ Player_Name   : chr [1:568] "Stephen Curry" "Chris Paul" "Russell Westbrook" "John Wall" ...
 $ Player_ID     : chr [1:568] "curryst01" "paulch01" "westbru01" "walljo01" ...
 $ Team          : chr [1:568] "GSW" "OKC" "HOU" "WAS" ...
 $ Salary_2019-20: num [1:568] 40231758 38506482 38178000 37800000 37800000 ...
 $ Salary_2020-21: num [1:568] 43006362 41358814 41006000 40824000 40824000 ...

The salary column has been converted to numeric as expected and also column names are more readable.

Understand - Dataset 2: NBA Player Total Statistics for the 2019-2020 NBA Season.

# Checking the structure of the data set
str(player_stats)
tibble [651 x 30] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
 $ Rk    : num [1:651] 1 2 3 4 5 6 7 8 9 10 ...
 $ Player: chr [1:651] "Steven Adams\\adamsst01" "Bam Adebayo\\adebaba01" "LaMarcus Aldridge\\aldrila01" "Kyle Alexander\\alexaky01" ...
 $ Pos   : chr [1:651] "C" "PF" "C" "PF" ...
 $ Age   : num [1:651] 26 22 34 23 21 24 21 27 29 26 ...
 $ Tm    : chr [1:651] "OKC" "MIA" "SAS" "MIA" ...
 $ G     : num [1:651] 63 72 53 2 47 38 70 10 18 10 ...
 $ GS    : num [1:651] 63 72 53 0 1 0 64 0 2 1 ...
 $ MP    : num [1:651] 1680 2417 1754 13 591 ...
 $ FG    : num [1:651] 283 440 391 1 98 117 302 19 25 10 ...
 $ FGA   : num [1:651] 478 790 793 2 266 251 465 44 86 38 ...
 $ FG%   : num [1:651] 0.592 0.557 0.493 0.5 0.368 0.466 0.649 0.432 0.291 0.263 ...
 $ 3P    : num [1:651] 1 2 61 0 46 57 0 5 9 6 ...
 $ 3PA   : num [1:651] 3 14 157 0 133 141 6 16 36 29 ...
 $ 3P%   : num [1:651] 0.333 0.143 0.389 NA 0.346 0.404 0 0.313 0.25 0.207 ...
 $ 2P    : num [1:651] 282 438 330 1 52 60 302 14 16 4 ...
 $ 2PA   : num [1:651] 475 776 636 2 133 110 459 28 50 9 ...
 $ 2P%   : num [1:651] 0.594 0.564 0.519 0.5 0.391 0.545 0.658 0.5 0.32 0.444 ...
 $ eFG%  : num [1:651] 0.593 0.558 0.532 0.5 0.455 0.58 0.649 0.489 0.343 0.342 ...
 $ FT    : num [1:651] 117 264 158 0 25 39 171 7 19 2 ...
 $ FTA   : num [1:651] 201 382 191 0 37 45 270 11 29 4 ...
 $ FT%   : num [1:651] 0.582 0.691 0.827 NA 0.676 0.867 0.633 0.636 0.655 0.5 ...
 $ ORB   : num [1:651] 207 176 103 2 9 8 216 2 24 1 ...
 $ DRB   : num [1:651] 376 559 289 1 75 77 455 7 63 20 ...
 $ TRB   : num [1:651] 583 735 392 3 84 85 671 9 87 21 ...
 $ AST   : num [1:651] 146 368 129 0 89 52 110 21 21 8 ...
 $ STL   : num [1:651] 51 82 36 0 17 10 40 5 18 0 ...
 $ BLK   : num [1:651] 67 93 87 0 8 2 92 2 8 6 ...
 $ TOV   : num [1:651] 94 204 74 1 54 33 77 8 17 4 ...
 $ PF    : num [1:651] 122 182 128 1 57 53 162 7 27 13 ...
 $ PTS   : num [1:651] 684 1146 1001 2 267 ...
 - attr(*, "spec")=
  .. cols(
  ..   Rk = col_double(),
  ..   Player = col_character(),
  ..   Pos = col_character(),
  ..   Age = col_double(),
  ..   Tm = col_character(),
  ..   G = col_double(),
  ..   GS = col_double(),
  ..   MP = col_double(),
  ..   FG = col_double(),
  ..   FGA = col_double(),
  ..   `FG%` = col_double(),
  ..   `3P` = col_double(),
  ..   `3PA` = col_double(),
  ..   `3P%` = col_double(),
  ..   `2P` = col_double(),
  ..   `2PA` = col_double(),
  ..   `2P%` = col_double(),
  ..   `eFG%` = col_double(),
  ..   FT = col_double(),
  ..   FTA = col_double(),
  ..   `FT%` = col_double(),
  ..   ORB = col_double(),
  ..   DRB = col_double(),
  ..   TRB = col_double(),
  ..   AST = col_double(),
  ..   STL = col_double(),
  ..   BLK = col_double(),
  ..   TOV = col_double(),
  ..   PF = col_double(),
  ..   PTS = col_double()
  .. )
# Stripping out illegal characters
# Cleaning up the "$" and "\" symbols from the salary columns.
# str_replace(player_contract,"\\$","")
pattern = c("\\$|\\,")
player_stats <- player_stats %>%
  mutate_all(function(x) gsub(pattern,"",x))

# Split out the player field into the two new fields: Player Name and Player ID
player_stats <- player_stats %>% separate(Player, c("Player_Name","Player_ID"), sep="\\\\")

# Renaming the columns
player_stats <- player_stats %>% rename(Player_Rank = Rk) 
player_stats <- player_stats %>% rename(Position = Pos)
player_stats <- player_stats %>% rename(Team = Tm)
player_stats <- player_stats %>% rename(Games_played = G)
player_stats <- player_stats %>% rename(Games_started = GS)
player_stats <- player_stats %>% rename(minutes_played = MP)
player_stats <- player_stats %>% rename(field_goals = FG)
player_stats <- player_stats %>% rename(field_goals_attempted = FGA)
player_stats <- player_stats %>% rename(three_pointers = "3P")
player_stats <- player_stats %>% rename(three_pointers_attempted = "3PA")
player_stats <- player_stats %>% rename(two_pointers = "2P")
player_stats <- player_stats %>% rename(two_pointers_attempted = "2PA")
player_stats <- player_stats %>% rename(free_throws = FT)
player_stats <- player_stats %>% rename(free_throws_attempted = FTA)
player_stats <- player_stats %>% rename(offensive_rebounds = ORB)
player_stats <- player_stats %>% rename(defensive_rebounds = DRB)
player_stats <- player_stats %>% rename(total_rebounds = TRB)
player_stats <- player_stats %>% rename(assists = AST)
player_stats <- player_stats %>% rename(steals = STL)
player_stats <- player_stats %>% rename(blocks = BLK)
player_stats <- player_stats %>% rename(turnovers = TOV)
player_stats <- player_stats %>% rename(personal_fouls = PF)
player_stats <- player_stats %>% rename(points = PTS)

# Keep required columns only for the 2019-2020 Season
player_stats <- player_stats %>% 
                   select(Player_Rank, Player_Name,Player_ID,Position,Team,Games_played,
                          Games_started,minutes_played,field_goals,field_goals_attempted,
                          three_pointers,three_pointers_attempted,two_pointers,
                          two_pointers_attempted,free_throws,free_throws_attempted,
                          offensive_rebounds,defensive_rebounds,total_rebounds,
                          assists,steals,blocks,turnovers,personal_fouls,points)

# Converting various character fields to numeric fields
player_stats$Games_played <- as.numeric(player_stats$Games_played)
# player_stats[,5:24] <- as.numeric(player_stats[,5:24])

player_stats[6:25] <- lapply(player_stats[6:25], as.numeric)

# Converting the position field to be an order factor field
player_stats$Position <- factor(player_stats$Position, 
                                levels= c("PG", "SG", "SF", "PF", "C", "Other"), 
                                labels = c("PG", "SG", "SF", "PF", "C", "Other"), 
                                ordered = TRUE)

# Converting the rank field to be an order factor field
player_stats$Player_Rank <- as.factor(player_stats$Player_Rank)

# Checking the structure of the data set
str(player_stats)
tibble [651 x 25] (S3: tbl_df/tbl/data.frame)
 $ Player_Rank             : Factor w/ 651 levels "1","10","100",..: 1 112 223 334 445 556 619 630 641 2 ...
 $ Player_Name             : chr [1:651] "Steven Adams" "Bam Adebayo" "LaMarcus Aldridge" "Kyle Alexander" ...
 $ Player_ID               : chr [1:651] "adamsst01" "adebaba01" "aldrila01" "alexaky01" ...
 $ Position                : Ord.factor w/ 6 levels "PG"<"SG"<"SF"<..: 5 4 5 4 2 2 5 2 4 3 ...
 $ Team                    : chr [1:651] "OKC" "MIA" "SAS" "MIA" ...
 $ Games_played            : num [1:651] 63 72 53 2 47 38 70 10 18 10 ...
 $ Games_started           : num [1:651] 63 72 53 0 1 0 64 0 2 1 ...
 $ minutes_played          : num [1:651] 1680 2417 1754 13 591 ...
 $ field_goals             : num [1:651] 283 440 391 1 98 117 302 19 25 10 ...
 $ field_goals_attempted   : num [1:651] 478 790 793 2 266 251 465 44 86 38 ...
 $ three_pointers          : num [1:651] 1 2 61 0 46 57 0 5 9 6 ...
 $ three_pointers_attempted: num [1:651] 3 14 157 0 133 141 6 16 36 29 ...
 $ two_pointers            : num [1:651] 282 438 330 1 52 60 302 14 16 4 ...
 $ two_pointers_attempted  : num [1:651] 475 776 636 2 133 110 459 28 50 9 ...
 $ free_throws             : num [1:651] 117 264 158 0 25 39 171 7 19 2 ...
 $ free_throws_attempted   : num [1:651] 201 382 191 0 37 45 270 11 29 4 ...
 $ offensive_rebounds      : num [1:651] 207 176 103 2 9 8 216 2 24 1 ...
 $ defensive_rebounds      : num [1:651] 376 559 289 1 75 77 455 7 63 20 ...
 $ total_rebounds          : num [1:651] 583 735 392 3 84 85 671 9 87 21 ...
 $ assists                 : num [1:651] 146 368 129 0 89 52 110 21 21 8 ...
 $ steals                  : num [1:651] 51 82 36 0 17 10 40 5 18 0 ...
 $ blocks                  : num [1:651] 67 93 87 0 8 2 92 2 8 6 ...
 $ turnovers               : num [1:651] 94 204 74 1 54 33 77 8 17 4 ...
 $ personal_fouls          : num [1:651] 122 182 128 1 57 53 162 7 27 13 ...
 $ points                  : num [1:651] 684 1146 1001 2 267 ...

De-duplication & Scan I

In this section, we seek to de-duplicate the data in the two data sets and also scan for missing and/or special values and also any obvious errors.

De-duplication & Scan I - Data Set 1: NBA Player Contracts 2019-2020.

# De-duplication Process

# Sample a record to understand the duplicate i.e Pau Gasol
sum(duplicated(player_contract$Player_Name))
[1] 42
# Which rows are duplicates?
which(duplicated(player_contract$Player_Name))
 [1] 290 292 296 348 369 441 446 447 470 471 474 475 476 478 481 485 487 488 490 504 505 507 512 514
[25] 516 522 523 526 528 529 534 535 536 537 542 544 545 549 551 553 556 563
# Let's pick on Pau Gasol
which(player_contract$Player_Name=="Pau Gasol")
[1] 198 290
# Get both his records to see why it's duplicated
player_contract %>% filter(player_contract$Player_Name=="Pau Gasol")
# Separate out the duplicate players
tbl_player_name <- table(player_contract$Player_Name)
df_dup_player_name <- player_contract[player_contract$Player_Name %in% names(tbl_player_name)[tbl_player_name>1],]

# Consolidate the duplicate records by summing up the salary
stage_salary <- df_dup_player_name %>% group_by(Player_ID)  %>%
  summarise(`Salary_2019-20` = sum(`Salary_2019-20`),
            `Salary_2020-21` = sum(`Salary_2020-21`)) %>% ungroup()

# Create a dataset to obtain the player's last team that he played for
stage_lastrecord <- df_dup_player_name %>%
  group_by(Player_Name) %>%
  slice(n()) %>%
  ungroup()

# Refine the selected Columns
lastrecord_select <- select(stage_lastrecord, Player_Name,Player_ID,Team)

# Finalised the duplicate records with the consolidated slary and final team that the player played for:
df_dup_player_name_final <- stage_salary %>% inner_join(lastrecord_select, by="Player_ID") %>% select(Player_Name,Player_ID,Team,`Salary_2019-20`,`Salary_2020-21`)


# Separate out the non-duplicate players
df_non_dup_player_name <- player_contract[player_contract$Player_Name %in% names(tbl_player_name)[tbl_player_name<2],]
df_non_dup_player_name <- df_non_dup_player_name %>% select(Player_Name,Player_ID,Team,`Salary_2019-20`,`Salary_2020-21`)
# Combining the two datasets 
player_contract_final <- union(df_dup_player_name_final,df_non_dup_player_name)

# Sort the data set by highest salary
player_contract_final <- player_contract_final %>% arrange(.,desc(`Salary_2019-20`))

# Add a "Ranking" field to the salary. We will name it Salary_Rank
player_contract_final <- tibble::rowid_to_column(player_contract_final, "Salary_Rank")

# Convert the rank field to be a factor
player_contract_final$Salary_Rank <- as.factor(player_contract_final$Salary_Rank)

# Assign the updated dataset to the original one
player_contract <- player_contract_final

# Check for duplicates
sum(duplicated(player_contract$Player_Name))
[1] 0
sum(duplicated(player_contract$Player_ID))
[1] 0
# Check the structure
str(player_contract)
tibble [526 x 6] (S3: tbl_df/tbl/data.frame)
 $ Salary_Rank   : Factor w/ 526 levels "1","2","3","4",..: 1 2 3 4 5 6 7 8 9 10 ...
 $ Player_Name   : chr [1:526] "Stephen Curry" "Chris Paul" "Russell Westbrook" "John Wall" ...
 $ Player_ID     : chr [1:526] "curryst01" "paulch01" "westbru01" "walljo01" ...
 $ Team          : chr [1:526] "GSW" "OKC" "HOU" "WAS" ...
 $ Salary_2019-20: num [1:526] 40231758 38506482 38178000 37800000 37800000 ...
 $ Salary_2020-21: num [1:526] 43006362 41358814 41006000 40824000 40824000 ...

In this section i checked for any missing values (NA’s), infinite or NaN values.

# Check for NA's
sapply(player_contract, function(x) sum(is.na(x)))
   Salary_Rank    Player_Name      Player_ID           Team Salary_2019-20 Salary_2020-21 
             0              0              0              0              0            193 
# Check for infinites
sapply(player_contract, function(x) sum(is.infinite(x)))
   Salary_Rank    Player_Name      Player_ID           Team Salary_2019-20 Salary_2020-21 
             0              0              0              0              0              0 
# Check for NaN
sapply(player_contract, function(x) sum(is.nan(x)))
   Salary_Rank    Player_Name      Player_ID           Team Salary_2019-20 Salary_2020-21 
             0              0              0              0              0              0 

Looks like we found some NA’s in the salary data for 2020-2021. Since the data is numeric, taking the average salary is an option that will be used to impute into the NA’s


# Imputes the mean for the missing values
player_contract$`Salary_2020-21` <- impute(player_contract$`Salary_2020-21`, fun=mean(player_contract$`Salary_2020-21`, na.rm = TRUE)) 

# Check missing value after imputation
sapply(player_contract, function(x) sum(is.na(x)))
   Salary_Rank    Player_Name      Player_ID           Team Salary_2019-20 Salary_2020-21 
             0              0              0              0              0              0 
colSums(is.na(player_contract))
   Salary_Rank    Player_Name      Player_ID           Team Salary_2019-20 Salary_2020-21 
             0              0              0              0              0              0 

De-duplication & Scan I - Dataset 2: NBA Player Total Statistics for the 2019-2020 NBA Season.

# De-duplication Process

# Sample a record to understand the duplicate i.e Trevor Ariza
sum(duplicated(player_stats$Player_Name))
[1] 122
# Which row numbers has the duplicates?
which(duplicated(player_stats$Player_Name))
  [1]  20  21  33  34  38  39  43  44  47  48  51  52  64  65  94  95  97  98 102 103 109 110 117 118
 [25] 124 125 128 129 132 133 144 145 147 148 150 151 155 156 161 162 167 168 174 175 185 186 194 195
 [49] 198 199 201 202 218 219 236 237 246 247 254 255 260 261 274 275 278 279 287 288 317 318 328 329
 [73] 333 334 347 348 352 353 372 373 413 414 415 435 436 438 439 452 453 460 461 482 483 518 519 521
 [97] 522 532 533 562 563 566 567 578 579 580 590 591 595 596 600 601 606 607 613 614 621 622 629 630
[121] 636 637
# Let see which row is Trevor Ariza
which(player_stats$Player_Name=="Trevor Ariza")
[1] 19 20 21
# Have a look at the full records from Trevor Ariza
player_stats %>% filter(player_stats$Player_Name=="Trevor Ariza")

# Separate out the duplicate players
tbl_player_stats_name <- table(player_stats$Player_Name)
df_dup_player_stats_name <- player_stats[player_stats$Player_Name %in% names(tbl_player_stats_name)[tbl_player_stats_name>1],]

# Remove the rows where the team equals "TOT". This row is redundant
stage_player_stats <- df_dup_player_stats_name %>% filter(df_dup_player_stats_name$Team != "TOT")

# Consolidate the duplicate records by summing up the counting stats
stage_player_stats2 <- stage_player_stats  %>% group_by(Player_ID)  %>% summarise(
                    `Games_played` = sum(`Games_played`),
                    `Games_started` = sum(`Games_started`),
                    `minutes_played` = sum(`minutes_played`),
                    `field_goals` = sum(`field_goals`),
                    `field_goals_attempted` = sum(`field_goals_attempted`),
                    `two_pointers` = sum(`two_pointers`),
                    `two_pointers_attempted` = sum(`two_pointers_attempted`),
                    `three_pointers` = sum(`three_pointers`),
                    `three_pointers_attempted` = sum(`three_pointers_attempted`),
                    `free_throws` = sum(`free_throws`),
                    `free_throws_attempted` = sum(`free_throws_attempted`),
                    `offensive_rebounds` = sum(`offensive_rebounds`),
                    `defensive_rebounds` = sum(`defensive_rebounds`),
                    `total_rebounds` = sum(`total_rebounds`),
                    `assists` = sum(`assists`),
                    `steals` = sum(`steals`),
                    `blocks` = sum(`blocks`),
                    `turnovers` = sum(`turnovers`),
                    `personal_fouls` = sum(`personal_fouls`),
                    `points` = sum(`points`)
            ) %>% ungroup()
`summarise()` ungrouping output (override with `.groups` argument)
# Get the last row for each player as we need to get their last position team. We do this by using the slice function
last_team_player_stats <- stage_player_stats %>%
  group_by(Player_Name) %>%
  slice(n()) %>%
  ungroup()

# Keeping only the required columns
last_team_player_stats <- last_team_player_stats %>% select(Player_Name,Player_ID,Position, Team)

# Joining the the two stage datasets to complete the de-duplication process.
stage_player_stats3 <- last_team_player_stats %>% inner_join(stage_player_stats2, by="Player_ID") 
df_dup_player_stats_name <- stage_player_stats3

# Separate out the non-duplicate players
df_non_dup_player_stats_name <- player_stats[player_stats$Player_Name %in% names(tbl_player_stats_name)[tbl_player_stats_name<2],]

# Removing the plyer-rank column
df_non_dup_player_stats_name <- df_non_dup_player_stats_name %>% select(-Player_Rank)

# Check to see if the there's any duplicates in the non-duplicate players stats dataset. Expected results should be zero
sum(duplicated(df_non_dup_player_stats_name$Player_Name))
[1] 0
sum(duplicated(df_non_dup_player_stats_name$Player_ID))
[1] 0
# Combining the two datasets 
player_stats_final <- union(df_dup_player_stats_name,df_non_dup_player_stats_name)

# Check to see if the there's any duplicates in the combined players stats dataset. Expected results should be zero
sum(duplicated(player_stats_final$Player_Name))
[1] 0
sum(duplicated(player_stats_final$Player_ID))
[1] 0

Check for any NA’s and Special Values

# Check for NA's
sapply(player_stats_final, function(x) sum(is.na(x)))
             Player_Name                Player_ID                 Position                     Team 
                       0                        0                        0                        0 
            Games_played            Games_started           minutes_played              field_goals 
                       0                        0                        0                        0 
   field_goals_attempted             two_pointers   two_pointers_attempted           three_pointers 
                       0                        0                        0                        0 
three_pointers_attempted              free_throws    free_throws_attempted       offensive_rebounds 
                       0                        0                        0                        0 
      defensive_rebounds           total_rebounds                  assists                   steals 
                       0                        0                        0                        0 
                  blocks                turnovers           personal_fouls                   points 
                       0                        0                        0                        0 
# Check for infinites
sapply(player_stats_final, function(x) sum(is.infinite(x)))
             Player_Name                Player_ID                 Position                     Team 
                       0                        0                        0                        0 
            Games_played            Games_started           minutes_played              field_goals 
                       0                        0                        0                        0 
   field_goals_attempted             two_pointers   two_pointers_attempted           three_pointers 
                       0                        0                        0                        0 
three_pointers_attempted              free_throws    free_throws_attempted       offensive_rebounds 
                       0                        0                        0                        0 
      defensive_rebounds           total_rebounds                  assists                   steals 
                       0                        0                        0                        0 
                  blocks                turnovers           personal_fouls                   points 
                       0                        0                        0                        0 
# Check for NaN
sapply(player_stats_final, function(x) sum(is.nan(x)))
             Player_Name                Player_ID                 Position                     Team 
                       0                        0                        0                        0 
            Games_played            Games_started           minutes_played              field_goals 
                       0                        0                        0                        0 
   field_goals_attempted             two_pointers   two_pointers_attempted           three_pointers 
                       0                        0                        0                        0 
three_pointers_attempted              free_throws    free_throws_attempted       offensive_rebounds 
                       0                        0                        0                        0 
      defensive_rebounds           total_rebounds                  assists                   steals 
                       0                        0                        0                        0 
                  blocks                turnovers           personal_fouls                   points 
                       0                        0                        0                        0 

There a no missing values(NA’s), infinite or NaN values in the players stats data set

Tidy & Manipulate Data I

Tidy & Manipulate Data I - Data Set 1: NBA Player Contracts 2019-2020.

The contracts data set was found to break the following tidy principle:

  • Each observation has it’s own row

The contracts data set is untidy as each observation for a salary does not form it’s own row and the salary years are across the columns.To tidy the dataset up, we will use the gather function from the tidyverse to change the data from a wide format to a long format.

#Tidying the contracts data set by using the gather() function
player_contract_final <- player_contract_final %>% gather(Salary_Year,Salary,5:6)

# Updating the value to reflect the year
player_contract_final$Salary_Year <- player_contract_final$Salary_Year %>% str_replace("Salary_2019-20", "2019-2020")
player_contract_final$Salary_Year <- player_contract_final$Salary_Year %>% str_replace("Salary_2020-21", "2020-2021")

Tidy & Manipulate Data I - Dataset 2: NBA Player Total Statistics for the 2019-2020 NBA Season.

The players stats dataset was tidy.

It conforms to these standards: * Each variable must have its own column. * Each observation must have its own row. * Each value must have its own cell.

Data (Part 2)

We can now merge the 2 datasets after the de-duplication process. The new merged data set will be player performance

# Filtering the contract data as we only want the current season.
player_contract_filter <- player_contract_final %>% filter(player_contract_final$Salary_Year=="2019-2020")

# Creating the merged dataset named player_performance.
# using an inner join as there a some players that have a contract but no stats, and also there's a stats for players that have been paid but have not been captured by the contracts dataset.
player_performance <- player_stats_final %>% inner_join(player_contract_filter, by="Player_ID")

# Removing the duplication columns and cleaning up the column names
player_performance <- player_performance %>% select(-Team.y,-Player_Name.y,-Salary_Year)
player_performance <- player_performance %>% rename(Player_Name = Player_Name.x)
player_performance <- player_performance %>% rename(Team = Team.x)

# Check for any dups after the merged
sum(duplicated(player_performance$Player_Name))
[1] 0
sum(duplicated(player_performance$Player_ID))
[1] 0

Tidy & Manipulate Data II

In this section, after the data sets were merged, I created the two new fields: player_efficiency & Salary_per_game

# Creating the 2 new fields using mutate
player_performance_new <- player_performance %>% mutate(.,
                                                        player_efficiency = (points+
                                                                            total_rebounds+
                                                                            assists+
                                                                            steals+
                                                                            blocks-
                                                                            (field_goals_attempted-field_goals)-
                                                                            (free_throws_attempted-free_throws)-
                                                                            turnovers)/Games_played
                                                        ,Salary_per_game= Salary/Games_played
                                                                            )
# Sorting the Dataset
player_performance_new <- player_performance_new %>% arrange(.,desc(player_efficiency))

# Selected the required columns
player_performance_new <- player_performance_new %>% select(Player_Name,Player_ID,Position,Team,Games_played,Games_started,minutes_played,
                                  Salary_Rank,Salary,player_efficiency,Salary_per_game)
# The new merged dataset
head(player_performance_new)
str(player_performance_new)
tibble [479 x 11] (S3: tbl_df/tbl/data.frame)
 $ Player_Name      : chr [1:479] "Giannis Antetokounmpo" "James Harden" "Luka Don?i?" "Karl-Anthony Towns" ...
 $ Player_ID        : chr [1:479] "antetgi01" "hardeja01" "doncilu01" "townska01" ...
 $ Position         : Ord.factor w/ 6 levels "PG"<"SG"<"SF"<..: 4 2 1 5 4 1 1 5 3 1 ...
 $ Team             : chr [1:479] "MIL" "HOU" "DAL" "MIN" ...
 $ Games_played     : num [1:479] 63 68 61 35 62 67 66 73 57 20 ...
 $ Games_started    : num [1:479] 63 68 61 35 62 67 66 73 57 20 ...
 $ minutes_played   : num [1:479] 1917 2483 2047 1187 2131 ...
 $ Salary_Rank      : Factor w/ 526 levels "1","2","3","4",..: 42 5 158 33 35 6 21 38 14 17 ...
 $ Salary           : num [1:479] 25842697 37800000 7683360 27250000 27093019 ...
 $ player_efficiency: num [1:479] 34.6 32.6 30.8 30.6 29.8 ...
 $ Salary_per_game  : num [1:479] 410202 555882 125957 778571 436984 ...

Scan II

In this section,I scanned the numeric data set for any outliers.

# Check for Outliers using boxplots
df_outliers <- player_performance_new %>% select_if(is.numeric)
for (i in 1:6) {
  boxplot(df_outliers[c(i)], xlab = colnames(df_outliers)[i])
}

NA

After running the boxplots to dettect outliers, It appears that the salary field and newly created player efficiency field has a few outliers that we need to address.The salary per game field is based of salary so in the next section i’ll just look at capping salary.

# Cap Function
cap <- function(x){
    quantiles <- quantile( x, c(.05, 0.25, 0.75, .95 ) )
    x[ x < quantiles[2] - 1.5*IQR(x) ] <- quantiles[1]
    x[ x > quantiles[3] + 1.5*IQR(x) ] <- quantiles[4]
    x
}

# Apply the capping
Salary <- player_performance_new$Salary %>% cap()
player_efficiency <- player_performance_new$player_efficiency %>% cap()

# Let's Recreate the boxplots to ensure that's it's all fixed
par(mfrow = c(2,2))
boxplot(Salary, xlab = "Salary ($)")
boxplot(player_efficiency, xlab = "Player Efficiency Score")

As you can see in the boxplots after the capping that it did a good job of removing all but one outlier. Looking into this lone outlier, it was steph curry who got injured 5 games into the season. For those 5 games, he earned $8,046,351.6 (8mil) per game for the 2019-2020. How’s that for a pay day.

Transform

In this section, We used to the salary field to do the transformation. We will use a combination of logarithms, square root and boxcox techniques to apply and then we will choose the best technique to use from the transformation.

# plot the histogram first
hist(player_performance_new$Salary)

The standard histogram has a right skewed effect to it.

# Using the square root technique
salary_square <- sqrt(player_performance_new$Salary)
hist(salary_square)

# Using the logarithm10 technique
Log <- log10(player_performance_new$Salary)
hist(Log)

# Using the Box-Cox technique
boxcox_hist <- BoxCox(player_performance_new$Salary, lambda = "auto")
hist(boxcox_hist)

Both Log10 and Box-Cox methods normalises the salary data.


LS0tDQp0aXRsZTogIk1BVEgyMzQ5IERhdGEgV3JhbmdsaW5nIg0KYXV0aG9yOiAiRGFuaWVsIFRyYW4gTmd1eWVuIChzOTcwNzY1NSkiDQpzdWJ0aXRsZTogQXNzaWdubWVudCAyDQpvdXRwdXQ6DQogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQNCiAgcGRmX2RvY3VtZW50OiBkZWZhdWx0DQogIGh0bWxfZG9jdW1lbnQ6DQogICAgZGZfcHJpbnQ6IHBhZ2VkDQotLS0NCg0KIyMgUmVxdWlyZWQgcGFja2FnZXMgDQoNClRoaXMgc2VjdGlvbiBsb2FkcyB0aGUgcmVxdWlyZWQgcGFja2FnZXMgDQoNCmBgYHtyfQ0KIyBMb2FkcyByZXF1aXJlZCBwYWNrYWdlcw0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkocmVhZHIpDQpsaWJyYXJ5KHN0cmluZ3IpDQpsaWJyYXJ5KHRpZHlyKQ0KbGlicmFyeShmb3JlY2FzdCkNCmxpYnJhcnkoSG1pc2MpDQpgYGANCg0KIyMgRXhlY3V0aXZlIFN1bW1hcnkgDQoNCkluIHRoaXMgYXNzaWdubWVudCwgd2UgYXBwbGllZCB3aGF0IHdlIGxlYXJudCBkdXJpbmcgdGhlIHNlbWVzdGVyIG9uIGRhdGEgd3JhbmdsaW5nIGFuZCBkYXRhIHByZS1wcm9jZXNzaW5nIHRlY2huaXF1ZXMuIFR3byBkYXRhIHNldHMgd2VyZSBtZXJnZWQgdXNpbmcgdGhlIGZvbGxvd2luZyBrZXkgcHJlLXByb2Nlc3Npbmcgc3RlcHM6DQoNCjEuRGF0YSAoUGFydCAxIG9mIDIpOg0KVGhlIHR3byBkYXRhIHNldHMgdXNlZCBmb3IgdGhpcyBhc3NpZ25tZW50IGlzIHJlbGF0ZWQgdG8gdGhlIE5CQSAoTmF0aW9uYWwgQmFza2V0YmFsbCBBc3NvY2lhdGlvbikuIFRoZSBOQkEgaXMgYW4gQW1lcmljYW4gbWVuJ3MgcHJvZmVzc2lvbmFsIGJhc2tldGJhbGwgbGVhZ3VlLiBJdCdzIG1hZGUgdXAgb2YgMzAgdGVhbXMuIEl0IGlzIG9uZSBvZiB0aGUgdG9wIG1lbidzIHByb2Zlc3Npb25hbCBiYXNrZXRiYWxsIGxlYWd1ZSBpbiB0aGUgd29ybGQuDQoNClRoZXkgd2VyZSBib3RoIGNvbGxlY3RlZCBmcm9tIHRoZSBiYXNrZXRiYWxsIHJlZmVyZW5jZSBzaXRlOiBbaHR0cHM6Ly93d3cuYmFza2V0YmFsbC1yZWZlcmVuY2UuY29tL10gd2hpY2ggaG9sZHMgYWxsIHR5cGVzIG9mIGJhc2tldGJhbGwgcmVsYXRlZCBzdGF0aXN0aWNzLg0KDQpCZWZvcmUgdGhlIGRhdGEgc2V0cyBjb3VsZCBiZSBtZXJnZWQsIHRoZXJlJ3Mgd2FzIGEgZmV3IGRlLWR1cGxpY2F0aW9uIGFuZCBtYW5pcHVsYXRpb24gc3RlcHMuIEluIHRoZSBkZXRhaWxlZCBzZWN0aW9ucyBiZWxvdywgSSd2ZSBicm9rZW4gdGhlIGRhdGEgc2VjdGlvbnMgaW50byB0d28gcGFydHMuIFRoZSBmaXJzdCBwYXJ0IGlzIHRoZSBpbml0aWFsIGluc3BlY3Rpb24gYW5kIHRoZSBzZWNvbmQgcGFydCB3YXMgd2hlcmUgaSBtZXJnZWQgdGhlIHR3byBkYXRhIHNldHMuIA0KDQoyLlVuZGVyc3RhbmQ6DQpJbiB0aGlzIHNlY3Rpb24sIEkgaW5zcGVjdGVkIHRoZSBhdHRyaWJ1dGVzIGFuZCBhbHNvIGxvb2tlZCBhdCBhIGZldyBzYW1wbGVzLiBJIGZvdW5kIHRoYXQgdGhlcmUgd2VyZSBzb21lIGlsbGVnYWwgY2hhcmFjdGVycyB0aGF0IG5lZWRlZCB0byBiZSBzdHJpcHBlZCBvdXQuIFRoZXJlIHdhcyBhbHNvIHNvbWUgY2hhcmFjdGVyLT5udW1lcmljIGFuZCBjaGFyYWN0ZXItZmFjdG9ycyBjb252ZXJzaW9uIGNvbXBsZXRlZCBoZXJlLEluIHBhcnRpY3VsYXIgYW4gb3JkZXJlZCBmYWN0b3IgYXJvdW5kIHBsYXllciBwb3NpdGlvbi4NCg0KMy5EZS1kdXBsaWNhdGlvbiAmIFNjYW4gSToNCkluIHRoaXMgc2VjdGlvbiwgSSd2ZSBpbnRyb2R1Y2VkIGEgc3RlcCB3aGVyZSBpIGRlLWR1cGxpY2F0ZWQgYm90aCBkYXRhIHNldHMuDQpJdCBpcyB2ZXJ5IGNvbW1vbiBpbiB0aGUgTkJBIHRoYXQgYSBwbGF5ZXIgY2FuIGJlIHRyYWRlZCBkdXJpbmcgdGhlIHBsYXlpbmcgc2Vhc29uLiBIZW5jZSB0aGUgcGxheWVycyBzdGF0cyB3aWxsIGhhdmUgbXVsdGlwbGUgcmVjb3JkcyBpZiBhIHBsYXllciBwbGF5ZWQgZm9yIG11bHRpcGxlIHRlYW1zLCBUaGlzIGlzIHRoZSBzYW1lIGZvciB0aGUgY29udHJhY3RzIGRhdGEgc2V0cyBhcyB0d28gdGVhbXMgY291bGQgYmUgcGF5aW5nIGEgc2FsYXJ5LiBGb3IgdGhpcyBhc3NpZ25tZW50LCBJJ3ZlIGNvbnNvbGlkYXRlZCB0aGUgcGxheWVycyBzdGF0cyB0byBiZSBvbmUgdG90YWwgYW5kIGFsc28gYXNzaWduZWQgaXQgdG8gaGlzIGxhdGVzdCB0ZWFtIHNpbmNlIGkgd2FudGVkIHRvIHVzZSB0aGUgd2hvbGUgc2Vhc29uIGFzIGEgY29tcGFyaXNvbiBhbmQgbm90IGluIGJldHdlZW4gdGhlIHNlYXNvbi4gDQoNCjQuVGlkeSAmIE1hbmlwdWxhdGUgRGF0YSBJOg0KSW4gdGhpcyBzZWN0aW9uLCBJIGNoZWNrZWQgdGhlIHRpZGluZXNzIG9mIHRoZSBkYXRhIHNldHMgYW5kIGZvdW5kIHRoYXQgdGhlIGNvbnRyYWN0cyBkYXRhIHNldCBoYWQgdG8gYmUgY29udmVydGVkIGZyb20gYSB3aWRlIGZvcm1hdCB0byBhIGxvbmcgZm9ybWF0LiBJIHVzZWQgdGhlIGdhdGhlciBmdW5jdGlvbiBoZXJlIHRvIGRvIHRoZSBjb252ZXJzaW9uLg0KVGhlIHBsYXllcnMgc3RhdGlzdGljcyBkYXRhIHNldCB3YXMgYWxyZWFkeSB0aWR5IGFmdGVyIHRoZSBkZS1kdXBsaWNhdGlvbiBhbmQgY2xlYW5zaW5nIG9mIHRoZSBkYXRhIGluIHByZXZpb3VzIHN0ZXBzLg0KDQo1LkRhdGEgKFBhcnQgMSBvZiAyKToNCkFmdGVyIHNvbWUgZGUtZHVwbGljYXRpb24gYW5kIG1hbmlwdWxhdGlvbiwgSSBmaW5hbGx5IG1lcmdlZCBteSB0d28gZGF0YSBzZXRzIGluIHRoaXMgc2VjdGlvbi4NCg0KNi5UaWR5ICYgTWFuaXB1bGF0ZSBEYXRhIElJOg0KSW4gdGhpcyBzZWN0aW9uLCBJIGNyZWF0ZWQgdHdvIG5ldyBmaWVsZHMuIHBsYXllciBlZmZpY2llbmN5IGFuZCBzYWxhcnkgcGVyIGdhbWUuDQpUaGUgZm9ybXVsYSBmb3IgZWZmaWNpZW5jeSBjYW4gYmUgZm91bmQgaGVyZTogW2h0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0VmZmljaWVuY3lfKGJhc2tldGJhbGwpXSBhbmQgaXMgdXNlZCBhY3Jvc3MgbW9zdCBiYXNrZXRiYWxsIGxlYWd1ZSB0byBtZWFzdXJlIGEgcGxheWVycycgYmFza2V0YmFsbCBlZmZpY2llbmN5IHVzaW5nIGNvdW50aW5nIHN0YXRpc3RpY3MuIA0KU2FsYXJ5IHBlciBnYW1lIHdhcyBhIGZpZWxkIGkgd2FzIGludGVyZXN0ZWQgdG8gc2VlIGhvdyBtdWNoIGEgcGxheWVyIGVhcm5zIG9uIGF2ZXJhZ2UgZm9yIHRoZSBzZWFzb24uDQoNCjcuU2NhbiBJSSAtIE91dGxpZXJzOiANCkluIHRoaXMgc2VjdGlvbiwgSSBleHBsb3JlZCB0aGUgbnVtZXJpYyBkYXRhIHRvIGRldGVjdCBvdXRsaWVycyBhbmQgd2Ugd2VyZSBhYmxlIHRvIHJlbW92ZSB0aGUgb3V0bGllcnMgdXNpbmcgYSBjYXBwaW5nIHRlY2huaXF1ZSB0aGF0IHdlIGFwcGxpZWQgdG8gdGhlIHNhbGFyeSBhbmQgcGxheWVyIGVmZmljaWVuY3kgZGF0YS4NCg0KOC5UcmFuc2Zvcm06DQoNClRyYW5zZm9ybWVkIHRoZSBkYXRhIHVzaW5nIGEgZmV3IHRlY2huaXF1ZXMgdG8gbm9ybWFsaXNlZCB0aGUgc2FsYXJ5IGRhdGEuDQpJbiB0aGlzIHNlY3Rpb24sIEkgdHJpZWQgYSBmZXcgdGVoY25pcXVlc25hbWVseSBMb2cxMCwgU3F1YXJlIFJvb3QgYW5kIEJveC1Db3ggbWV0aG9kcyBub3JtYWxpc2VkIHRoZSBzYWxhcnkgZGF0YS4NCkJvdGggTG9nMTAgYW5kIEJveC1Db3ggdGVjaG5pcXVlcyB3ZXJlIHRoZSBiZXN0IGF0IGFjaGllaXZpbmcgdGhpcy4gIA0KDQpcbmV3cGFnZQ0KIyMgRGF0YSAoUGFydCAxIG9mIDIpDQoNClRoaXMgaXMgdGhlIGRldGFpbGVkIGRhdGEgc2VjdGlvbiB3aGVyZSB0aGUgZGF0YSBzZXQgd2FzIGluaXRpYWxseSBsb29rZWQgYXQuDQpUaGUgZmlyc3QgZGF0YXNldCByZWxhdGVzIHRvIE5CQSBwbGF5ZXJzIHNhbGFyeSBzdGFydGluZyBmcm9tIHRoZSAyMDE5LTIwMjAgU2Vhc29uLg0KVGhlIHNlY29uZCBkYXRhc2V0IGlzIHJlbGF0ZWQgdG8gTkJBIHBsYXllciB0b3RhbCBzdGF0aXN0aWNzIGZvciB0aGUgMjAxOS0yMDIwIFNlYXNvbi4NCg0KIyMjIERhdGEgLSBEYXRhIFNldCAxOiBOQkEgUGxheWVyIENvbnRyYWN0cyAyMDE5LTIwMjAuIA0KDQpEYXRhIFNldCAxOiBOQkEgUGxheWVyIENvbnRyYWN0cyAyMDE5LTIwMjAgDQoNCmh0dHBzOi8vd3d3LmJhc2tldGJhbGwtcmVmZXJlbmNlLmNvbS9jb250cmFjdHMvcGxheWVycy5odG1sDQoNClRoZSBmb2xsb3dpbmcgdmFyaWFibGVzIGFyZSBmb3VuZCBpbiB0aGUgZGF0YXNldCBhbmQgZGVmaW5lZCBhcyBmb2xsb3dzOiANCg0KKiBSayAtIFJhbmsgb2Ygc2FsYXJ5IHNvcnRlZCBieSB0aGUgMjAxOS0yMDIwIE5CQSBTZWFzb24uDQoqIFBsYXllciAtIFRoZSBwbGF5ZXIncyBOYW1lLg0KKiBUbSAtIFRoZSB0ZWFtIHRoZSBwbGF5ZXJzIHBsYXlzIGZvci4JDQoqIDIwMTktMjAJLSBUaGUgUGxheWVyIHNhbGFyeSdzIGZvciB0aGUgMjAxOS0yMDIwIE5CQSBTZWFzb24uDQoqIDIwMjAtMjEJLSBUaGUgUGxheWVyIHNhbGFyeSdzIGZvciB0aGUgMjAyMC0yMDIxIE5CQSBTZWFzb24uCQ0KKiAyMDIxLTIyCS0gVGhlIFBsYXllciBzYWxhcnkncyBmb3IgdGhlIDIwMjEtMjAyMiBOQkEgU2Vhc29uLg0KKiAyMDIyLTIzCS0gVGhlIFBsYXllciBzYWxhcnkncyBmb3IgdGhlIDIwMjItMjAyMyBOQkEgU2Vhc29uLg0KKiAyMDIzLTI0CS0gVGhlIFBsYXllciBzYWxhcnkncyBmb3IgdGhlIDIwMjMtMjAyNCBOQkEgU2Vhc29uLg0KKiAyMDI0LTI1CS0gVGhlIFBsYXllciBzYWxhcnkncyBmb3IgdGhlIDIwMjQtMjAyNSBOQkEgU2Vhc29uLg0KKiBTaWduZWQgVXNpbmcgLSAJVGhlIHR5cGUgb2YgY29udHJhY3QgdGhlIHBsYXllciBzaWduZWQgdXAgZm9yLg0KKiBHdWFyYW50ZWVkIC0gVGhlIGFtb3VudCBvZiBhIHBsYXllcidzIHJlbWFpbmluZyBzYWxhcnkgdGhhdCBpcyBndWFyYW50ZWVkLg0KDQpgYGB7cn0NCiMjIExvYWQgZGF0YSBzZWN0aW9uIGZvciBkYXRhIHNldCAxOiBOQkEgUGxheWVyIENvbnRyYWN0cyAyMDE5LTIwMjAgDQoNCiMgQ3JlYXRlIHZhcmlhYmxlIHRvIGhvbGQgdGhlIGNvbHVtbiBuYW1lcy4NCnBsYXllcl9jb250cmFjdF9oZWFkZXIgPC0gYygiUmsiLCJQbGF5ZXIiLCJUbSIsIjIwMTktMjAiLCIyMDIwLTIxIiwiMjAyMS0yMiIsIjIwMjItMjMiLCIyMDIzLTI0IiwiMjAyNC0yNSIsIlNpZ25lZCBVc2luZyIsIkd1YXJhbnRlZWQiKSANCg0KIyBMb2FkaW5nIGluIHRoZSBkYXRhIHZpYSB0aGUgInJlYWRfY3N2IiBjb21tYW5kLg0KcGxheWVyX2NvbnRyYWN0IDwtIHJlYWRfY3N2KCIyMDE5XzIwX05CQV9QbGF5ZXJfQ29udHJhY3RzLmNzdiIsc2tpcD0yLCBjb2xfbmFtZXM9cGxheWVyX2NvbnRyYWN0X2hlYWRlcikNCg0KIyBHZXR0aW5nIHRoZSBkaW1lbnNpb25zIG9mIHRoZSBkYXRhc2V0Lg0KZGltKHBsYXllcl9jb250cmFjdCkNCg0KIyBIYXZpbmcgYSBwZWVrIG9mIHRoZSBkYXRhc2V0Lg0KaGVhZChwbGF5ZXJfY29udHJhY3QpDQpgYGANCkRhdGEgc2V0IDEgY29udGFpbnMgNTY4IG9ic2VydmF0aW9ucyhSb3dzKSBhbmQgMTEgdmFyaWFibGVzKENvbHVtbnMpLg0KDQojIyMgRGF0YSAtIERhdGFzZXQgMjogTkJBIFBsYXllciBUb3RhbCBTdGF0aXN0aWNzIGZvciB0aGUgMjAxOS0yMDIwIE5CQSBTZWFzb24uDQoNCkRhdGEgU2V0IDI6IE5CQSBQbGF5ZXIgVG90YWwgU3RhdGlzdGljcyBmb3IgdGhlIDIwMTktMjAyMCBOQkEgU2Vhc29uLg0KDQpodHRwczovL3d3dy5iYXNrZXRiYWxsLXJlZmVyZW5jZS5jb20vbGVhZ3Vlcy9OQkFfMjAyMF90b3RhbHMuaHRtbA0KDQpUaGUgZm9sbG93aW5nIHZhcmlhYmxlcyBhcmUgZm91bmQgaW4gdGhlIGRhdGFzZXQgYW5kIGRlZmluZWQgYXMgZm9sbG93czogDQoNCiogUmsgLSBSYW5rIG9mIHNhbGFyeSBzb3J0ZWQgYnkgdGhlIHBsYXllcidzIGxhc3QgbmFtZSBpbiBhc2NlbmRpbmcgb3JkZXIuDQoqIFBsYXllciAtIFRoZSBwbGF5ZXIncyBOYW1lLg0KKiBQb3MJLSBUaGUgcGxheWVyJ3MgYmFza2V0YmFsbCBwb3NpdGlvbiB0aGF0IHRoZXkgcGxheSBvbiB0aGUgYmFza2V0YmFsbCBjb3VydC4NCiogQWdlCS0gVGhlIGFnZSBvZiB0aGUgcGxheWVyIGFzIG9mIHRoZSAxc3Qgb2YgRmVicnVhcnkgb2YgdGhlIHNlYXNvbi4NCiogVG0gLSBUaGUgdGVhbSB0aGUgcGxheWVycyBwbGF5cyBmb3IuCQkNCiogRwktIFRoZSBudW1iZXIgb2YgZ2FtZXMgdGhlIHBsYXllciBwbGF5ZWQgaW4gdGhlIFNlYXNvbi4NCiogR1MgLSBUaGUgbnVtYmVyIG9mIGdhbWVzIHRoZSBwbGF5ZXIgc3RhcnRlZCBpbiB0aGUgU2Vhc29uLgkNCiogTVAgLSBUaGUgYW1vdW50IG9mIHRpbWUgdGhlIHBsYXllciBwbGF5ZWQgbWVhc3VyZWQgaW4gbWludXRlcy4JDQoqIEZHIC0gVGhlIG51bWJlciBvZiBmaWVsZCBnb2FscyBiYXNrZXRzIG1hZGUuDQoqIEZHQSAtIFRoZSBudW1iZXIgb2YgZmllbGQgZ29hbHMgYmFza2V0cyBhdHRlbXB0ZWQuCQ0KKiBGRyUJLSBUaGUgcGVyY2VudGFnZSBvZiBhIHBsYXllcidzIG1hZGUgZmllbGQgZ29hbC4NCiogM1AgLSBUaGUgbnVtYmVyIG9mIDMgcG9pbnRlcnMgZmllbGQgZ29hbHMgYmFza2V0cyBtYWRlLgkNCiogM1BBCS0gVGhlIG51bWJlciBvZiAzIHBvaW50ZXJzIGZpZWxkIGdvYWxzIGJhc2tldHMgYXR0ZW1wdGVkLg0KKiAzUCUgLSBUaGUgcGVyY2VudGFnZSBvZiBhIHBsYXllcidzIDMgcG9pbnRlciBtYWRlIGZpZWxkIGdvYWwuCQ0KKiAyUCAtIFRoZSBudW1iZXIgb2YgMiBwb2ludCBmaWVsZCBnb2FscyBiYXNrZXRzIG1hZGUuCQ0KKiAyUEEgLSBUaGUgbnVtYmVyIG9mIDIgcG9pbnQgZmllbGQgZ29hbHMgYmFza2V0cyBhdHRlbXB0ZWQuCQ0KKiAyUCUJLSBUaGUgcGVyY2VudGFnZSBvZiBhIHBsYXllcidzIDIgcG9pbnQgbWFkZSBmaWVsZCBnb2FsLiANCiogZUZHJSAtIFRoZSBlZmZlY3RpdmUgZmllbGQgZ29hbCBwZXJjZW50YWdlLCBiYXNlZCBvbiB0aGUgZm9ybXVsYTogKEZHICsgMC41ICogM1ApIC8gRkdBCQ0KKiBGVCAtIFRoZSBudW1iZXIgb2YgZnJlZSB0aHJvd3MgbWFkZS4JDQoqIEZUQQktIFRoZSBudW1iZXIgb2YgZnJlZSB0aHJvd3MgYXR0ZW1wdGVkLgkNCiogRlQlCS0gVGhlIHBlcmNlbnRhZ2Ugb2YgYSBwbGF5ZXIncyAyIHBvaW50IG1hZGUgZmllbGQgZ29hbC4gDQoqIE9SQiAtIFRoZSBudW1iZXIgb2Ygb2ZmZW5zaXZlIFJlYm91bmRzLg0KKiBEUkIJLSBUaGUgbnVtYmVyIG9mIGRlZmVuc2l2ZSBSZWJvdW5kcy4NCiogVFJCIC0gVGhlIG51bWJlciBvZiB0b3RhbCBSZWJvdW5kcy4JDQoqIEFTVAktIFRoZSBudW1iZXIgb2YgYXNzaXN0cy4NCiogU1RMIC0gVGhlIG51bWJlciBvZiBzdGVhbHMuCQ0KKiBCTEsgLSBUaGUgbnVtYmVyIG9mIGJsb2Nrcy4JDQoqIFRPViAtIFRoZSBudW1iZXIgb2YgdHVybm92ZXJzLgkNCiogUEYgLSBUaGUgbnVtYmVyIG9mIHBlcnNvbmFsIGZvdWxzLgkNCiogUFRTIC0gVGhlIG51bWJlciBvZiBwb2ludHMuCQ0KDQpgYGB7cn0NCiMjIExvYWQgZGF0YSBzZWN0aW9uIGZvciBkYXRhIHNldCAyOiBOQkEgUGxheWVyIFRvdGFsIFN0YXRpc3RpY3MgZm9yIHRoZSAyMDE5LTIwMjAgTkJBIFNlYXNvbi4NCg0KIyBMb2FkaW5nIGluIHRoZSBkYXRhIHZpYSB0aGUgInJlYWRfY3N2IiBjb21tYW5kLg0KcGxheWVyX3N0YXRzIDwtIHJlYWRfY3N2KCIyMDE5XzIwX05CQV9QbGF5ZXJfU3RhdHNfVG90YWxzLmNzdiIpDQoNCiMgR2V0dGluZyB0aGUgZGltZW5zaW9ucyBvZiB0aGUgZGF0YXNldC4NCmRpbShwbGF5ZXJfc3RhdHMpDQoNCiMgSGF2aW5nIGEgcGVlayBvZiB0aGUgZGF0YXNldC4NCmhlYWQocGxheWVyX3N0YXRzKQ0KYGBgDQpEYXRhIHNldCAyIGNvbnRhaW5zIDY1MSBvYnNlcnZhdGlvbnMoUm93cykgYW5kIDMwIHZhcmlhYmxlcyhDb2x1bW5zKS4NCg0KV2UgYXJlIHVuYWJsZSB0byBtZXJnZSB0aGUgMiBkYXRhc2V0cyBpbiB0aGlzIHNlY3Rpb24gYXMgdGhlcmUncyBhIG51bWJlciBvZiBjb252ZXJzaW9uIGFuZCB0aWR5IHN0ZXBzIHdlIG5lZWQgdG8gZG8gYmVmb3JlIHdlIGNhbiBtZXJnZSB0aGUgZGF0YXNldHMuIFRoZSBtZXJnaW5pbmcgb2YgdGhlIGRhdGFzZXQgd2lsbCBiZSBkZWZpbmVkIGluIHBhcnQgMiBvZiB0aGUgZGF0YSBzZWN0aW9uLg0KDQpcbmV3cGFnZQ0KIyMgVW5kZXJzdGFuZCANCg0KSW4gdGhpcyBzZWN0aW9uLCB3ZSBzZWVrIHRvIHVuZGVyc3RhbmQgdGhlIDIgZGF0YSBzZXRzIGFuZCBhcHBseSBhbnkgY29udmVyc2lvbnMgdG8gdGhlIGF0dHJpYnV0ZXMgb2YgdGhlIGRhdGEuDQoNCiMjIyBVbmRlcnN0YW5kIC0gRGF0YSBTZXQgMTogTkJBIFBsYXllciBDb250cmFjdHMgMjAxOS0yMDIwLiANCg0KYGBge3J9DQojIENoZWNraW5nIHRoZSBzdHJ1Y3R1cmUgb2YgdGhlIGRhdGEgc2V0DQpzdHIocGxheWVyX2NvbnRyYWN0KQ0KYGBgDQpJdCBhcHBlYXJzIHRoYXQgYWZ0ZXIgYSBzbmVhayBwZWVrIG9mIHRoZSBkYXRhc2V0LCB3ZSBuZWVkIHRvIGFwcGx5IHNvbWUgY29udmVyc2lvbnMgdG8gdGhlIGRhdGFzZXQsIHN0cmlwcGluZyBvZiBjZXJ0YWluIGNoYXJhY3RlcnMgYW5kIGFsc28gY29udmVydGluZyBjaGFyYWN0ZXIgZmllbGRzIHRvIG51bWVyaWMgZmllbGRzLiBXZSB3aWxsIGFsc28gcmVuYW1lIHRoZSBmaWVsZHMgaW4gdGhlIHNlY3Rpb24gdG8gbWFrZSB0aGVtIG1vcmUgcmVhZGFibGUuDQoNCmBgYHtyfQ0KIyBTdHJpcHBpbmcgb3V0IGlsbGVnYWwgY2hhcmFjdGVycw0KIyBDbGVhbmluZyB1cCB0aGUgIiQiIGFuZCAiXCIgc3ltYm9scyBmcm9tIHRoZSBzYWxhcnkgY29sdW1ucy4NCiMgc3RyX3JlcGxhY2UocGxheWVyX2NvbnRyYWN0LCJcXCQiLCIiKQ0KcGF0dGVybiA9IGMoIlxcJHxcXCwiKQ0KcGxheWVyX2NvbnRyYWN0IDwtIHBsYXllcl9jb250cmFjdCAlPiUNCiAgbXV0YXRlX2FsbChmdW5jdGlvbih4KSBnc3ViKHBhdHRlcm4sIiIseCkpDQoNCiMgU3BsaXQgb3V0IHRoZSBwbGF5ZXIgZmllbGQgaW50byB0aGUgdHdvIG5ldyBmaWVsZHM6IFBsYXllciBOYW1lIGFuZCBQbGF5ZXIgSUQNCnBsYXllcl9jb250cmFjdCA8LSBwbGF5ZXJfY29udHJhY3QgJT4lIHNlcGFyYXRlKFBsYXllciwgYygiUGxheWVyX05hbWUiLCJQbGF5ZXJfSUQiKSwgc2VwPSJcXFxcIikNCg0KIyBSZW5hbWluZyB0aGUgY29sdW1ucw0KcGxheWVyX2NvbnRyYWN0IDwtIHBsYXllcl9jb250cmFjdCAlPiUgcmVuYW1lKFNhbGFyeV9SYW5rID0gUmspDQpwbGF5ZXJfY29udHJhY3QgPC0gcGxheWVyX2NvbnRyYWN0ICU+JSByZW5hbWUoVGVhbSA9IFRtKQ0KcGxheWVyX2NvbnRyYWN0IDwtIHBsYXllcl9jb250cmFjdCAlPiUgcmVuYW1lKCJTYWxhcnlfMjAxOS0yMCIgPSAiMjAxOS0yMCIpDQpwbGF5ZXJfY29udHJhY3QgPC0gcGxheWVyX2NvbnRyYWN0ICU+JSByZW5hbWUoIlNhbGFyeV8yMDIwLTIxIiA9ICIyMDIwLTIxIikNCnBsYXllcl9jb250cmFjdCA8LSBwbGF5ZXJfY29udHJhY3QgJT4lIHJlbmFtZSgiU2FsYXJ5XzIwMjEtMjIiID0gIjIwMjEtMjIiKQ0KcGxheWVyX2NvbnRyYWN0IDwtIHBsYXllcl9jb250cmFjdCAlPiUgcmVuYW1lKCJTYWxhcnlfMjAyMi0yMyIgPSAiMjAyMi0yMyIpDQpwbGF5ZXJfY29udHJhY3QgPC0gcGxheWVyX2NvbnRyYWN0ICU+JSByZW5hbWUoIlNhbGFyeV8yMDIzLTI0IiA9ICIyMDIzLTI0IikNCnBsYXllcl9jb250cmFjdCRgU2FsYXJ5XzIwMTktMjBgIDwtIGFzLm51bWVyaWMocGxheWVyX2NvbnRyYWN0JGBTYWxhcnlfMjAxOS0yMGApDQpwbGF5ZXJfY29udHJhY3QkYFNhbGFyeV8yMDIwLTIxYCA8LSBhcy5udW1lcmljKHBsYXllcl9jb250cmFjdCRgU2FsYXJ5XzIwMjAtMjFgKQ0KcGxheWVyX2NvbnRyYWN0JGBTYWxhcnlfMjAyMS0yMmAgPC0gYXMubnVtZXJpYyhwbGF5ZXJfY29udHJhY3QkYFNhbGFyeV8yMDIxLTIyYCkNCnBsYXllcl9jb250cmFjdCRgU2FsYXJ5XzIwMjItMjNgIDwtIGFzLm51bWVyaWMocGxheWVyX2NvbnRyYWN0JGBTYWxhcnlfMjAyMi0yM2ApDQpwbGF5ZXJfY29udHJhY3QkYFNhbGFyeV8yMDIzLTI0YCA8LSBhcy5udW1lcmljKHBsYXllcl9jb250cmFjdCRgU2FsYXJ5XzIwMjMtMjRgKQ0KDQojIEtlZXAgcmVxdWlyZWQgY29sdW1ucyBvbmx5IGZvciB0aGUgMjAxOS0yMDIwIFNlYXNvbg0KcGxheWVyX2NvbnRyYWN0IDwtIHBsYXllcl9jb250cmFjdCAlPiUgDQogICAgICAgICAgICAgICAgICAgc2VsZWN0KFBsYXllcl9OYW1lLFBsYXllcl9JRCxUZWFtLCJTYWxhcnlfMjAxOS0yMCIsIlNhbGFyeV8yMDIwLTIxIikNCg0KIyBDaGVja2luZyB0aGUgc3RydWN0dXJlIG9mIHRoZSBkYXRhIHNldCBhZnRlciB0aGUgY29udmVyc2lvbnMNCnN0cihwbGF5ZXJfY29udHJhY3QpDQoNCmBgYA0KVGhlIHNhbGFyeSBjb2x1bW4gaGFzIGJlZW4gY29udmVydGVkIHRvIG51bWVyaWMgYXMgZXhwZWN0ZWQgYW5kIGFsc28gY29sdW1uIG5hbWVzIGFyZSBtb3JlIHJlYWRhYmxlLg0KDQojIyMgVW5kZXJzdGFuZCAtIERhdGFzZXQgMjogTkJBIFBsYXllciBUb3RhbCBTdGF0aXN0aWNzIGZvciB0aGUgMjAxOS0yMDIwIE5CQSBTZWFzb24uDQoNCmBgYHtyfQ0KIyBDaGVja2luZyB0aGUgc3RydWN0dXJlIG9mIHRoZSBkYXRhIHNldA0Kc3RyKHBsYXllcl9zdGF0cykNCmBgYA0KDQpgYGB7cn0NCiMgU3RyaXBwaW5nIG91dCBpbGxlZ2FsIGNoYXJhY3RlcnMNCiMgQ2xlYW5pbmcgdXAgdGhlICIkIiBhbmQgIlwiIHN5bWJvbHMgZnJvbSB0aGUgc2FsYXJ5IGNvbHVtbnMuDQojIHN0cl9yZXBsYWNlKHBsYXllcl9jb250cmFjdCwiXFwkIiwiIikNCnBhdHRlcm4gPSBjKCJcXCR8XFwsIikNCnBsYXllcl9zdGF0cyA8LSBwbGF5ZXJfc3RhdHMgJT4lDQogIG11dGF0ZV9hbGwoZnVuY3Rpb24oeCkgZ3N1YihwYXR0ZXJuLCIiLHgpKQ0KDQojIFNwbGl0IG91dCB0aGUgcGxheWVyIGZpZWxkIGludG8gdGhlIHR3byBuZXcgZmllbGRzOiBQbGF5ZXIgTmFtZSBhbmQgUGxheWVyIElEDQpwbGF5ZXJfc3RhdHMgPC0gcGxheWVyX3N0YXRzICU+JSBzZXBhcmF0ZShQbGF5ZXIsIGMoIlBsYXllcl9OYW1lIiwiUGxheWVyX0lEIiksIHNlcD0iXFxcXCIpDQoNCiMgUmVuYW1pbmcgdGhlIGNvbHVtbnMNCnBsYXllcl9zdGF0cyA8LSBwbGF5ZXJfc3RhdHMgJT4lIHJlbmFtZShQbGF5ZXJfUmFuayA9IFJrKSANCnBsYXllcl9zdGF0cyA8LSBwbGF5ZXJfc3RhdHMgJT4lIHJlbmFtZShQb3NpdGlvbiA9IFBvcykNCnBsYXllcl9zdGF0cyA8LSBwbGF5ZXJfc3RhdHMgJT4lIHJlbmFtZShUZWFtID0gVG0pDQpwbGF5ZXJfc3RhdHMgPC0gcGxheWVyX3N0YXRzICU+JSByZW5hbWUoR2FtZXNfcGxheWVkID0gRykNCnBsYXllcl9zdGF0cyA8LSBwbGF5ZXJfc3RhdHMgJT4lIHJlbmFtZShHYW1lc19zdGFydGVkID0gR1MpDQpwbGF5ZXJfc3RhdHMgPC0gcGxheWVyX3N0YXRzICU+JSByZW5hbWUobWludXRlc19wbGF5ZWQgPSBNUCkNCnBsYXllcl9zdGF0cyA8LSBwbGF5ZXJfc3RhdHMgJT4lIHJlbmFtZShmaWVsZF9nb2FscyA9IEZHKQ0KcGxheWVyX3N0YXRzIDwtIHBsYXllcl9zdGF0cyAlPiUgcmVuYW1lKGZpZWxkX2dvYWxzX2F0dGVtcHRlZCA9IEZHQSkNCnBsYXllcl9zdGF0cyA8LSBwbGF5ZXJfc3RhdHMgJT4lIHJlbmFtZSh0aHJlZV9wb2ludGVycyA9ICIzUCIpDQpwbGF5ZXJfc3RhdHMgPC0gcGxheWVyX3N0YXRzICU+JSByZW5hbWUodGhyZWVfcG9pbnRlcnNfYXR0ZW1wdGVkID0gIjNQQSIpDQpwbGF5ZXJfc3RhdHMgPC0gcGxheWVyX3N0YXRzICU+JSByZW5hbWUodHdvX3BvaW50ZXJzID0gIjJQIikNCnBsYXllcl9zdGF0cyA8LSBwbGF5ZXJfc3RhdHMgJT4lIHJlbmFtZSh0d29fcG9pbnRlcnNfYXR0ZW1wdGVkID0gIjJQQSIpDQpwbGF5ZXJfc3RhdHMgPC0gcGxheWVyX3N0YXRzICU+JSByZW5hbWUoZnJlZV90aHJvd3MgPSBGVCkNCnBsYXllcl9zdGF0cyA8LSBwbGF5ZXJfc3RhdHMgJT4lIHJlbmFtZShmcmVlX3Rocm93c19hdHRlbXB0ZWQgPSBGVEEpDQpwbGF5ZXJfc3RhdHMgPC0gcGxheWVyX3N0YXRzICU+JSByZW5hbWUob2ZmZW5zaXZlX3JlYm91bmRzID0gT1JCKQ0KcGxheWVyX3N0YXRzIDwtIHBsYXllcl9zdGF0cyAlPiUgcmVuYW1lKGRlZmVuc2l2ZV9yZWJvdW5kcyA9IERSQikNCnBsYXllcl9zdGF0cyA8LSBwbGF5ZXJfc3RhdHMgJT4lIHJlbmFtZSh0b3RhbF9yZWJvdW5kcyA9IFRSQikNCnBsYXllcl9zdGF0cyA8LSBwbGF5ZXJfc3RhdHMgJT4lIHJlbmFtZShhc3Npc3RzID0gQVNUKQ0KcGxheWVyX3N0YXRzIDwtIHBsYXllcl9zdGF0cyAlPiUgcmVuYW1lKHN0ZWFscyA9IFNUTCkNCnBsYXllcl9zdGF0cyA8LSBwbGF5ZXJfc3RhdHMgJT4lIHJlbmFtZShibG9ja3MgPSBCTEspDQpwbGF5ZXJfc3RhdHMgPC0gcGxheWVyX3N0YXRzICU+JSByZW5hbWUodHVybm92ZXJzID0gVE9WKQ0KcGxheWVyX3N0YXRzIDwtIHBsYXllcl9zdGF0cyAlPiUgcmVuYW1lKHBlcnNvbmFsX2ZvdWxzID0gUEYpDQpwbGF5ZXJfc3RhdHMgPC0gcGxheWVyX3N0YXRzICU+JSByZW5hbWUocG9pbnRzID0gUFRTKQ0KDQojIEtlZXAgcmVxdWlyZWQgY29sdW1ucyBvbmx5IGZvciB0aGUgMjAxOS0yMDIwIFNlYXNvbg0KcGxheWVyX3N0YXRzIDwtIHBsYXllcl9zdGF0cyAlPiUgDQogICAgICAgICAgICAgICAgICAgc2VsZWN0KFBsYXllcl9SYW5rLCBQbGF5ZXJfTmFtZSxQbGF5ZXJfSUQsUG9zaXRpb24sVGVhbSxHYW1lc19wbGF5ZWQsDQogICAgICAgICAgICAgICAgICAgICAgICAgIEdhbWVzX3N0YXJ0ZWQsbWludXRlc19wbGF5ZWQsZmllbGRfZ29hbHMsZmllbGRfZ29hbHNfYXR0ZW1wdGVkLA0KICAgICAgICAgICAgICAgICAgICAgICAgICB0aHJlZV9wb2ludGVycyx0aHJlZV9wb2ludGVyc19hdHRlbXB0ZWQsdHdvX3BvaW50ZXJzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICB0d29fcG9pbnRlcnNfYXR0ZW1wdGVkLGZyZWVfdGhyb3dzLGZyZWVfdGhyb3dzX2F0dGVtcHRlZCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgb2ZmZW5zaXZlX3JlYm91bmRzLGRlZmVuc2l2ZV9yZWJvdW5kcyx0b3RhbF9yZWJvdW5kcywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgYXNzaXN0cyxzdGVhbHMsYmxvY2tzLHR1cm5vdmVycyxwZXJzb25hbF9mb3Vscyxwb2ludHMpDQoNCiMgQ29udmVydGluZyB2YXJpb3VzIGNoYXJhY3RlciBmaWVsZHMgdG8gbnVtZXJpYyBmaWVsZHMNCnBsYXllcl9zdGF0cyRHYW1lc19wbGF5ZWQgPC0gYXMubnVtZXJpYyhwbGF5ZXJfc3RhdHMkR2FtZXNfcGxheWVkKQ0KIyBwbGF5ZXJfc3RhdHNbLDU6MjRdIDwtIGFzLm51bWVyaWMocGxheWVyX3N0YXRzWyw1OjI0XSkNCg0KcGxheWVyX3N0YXRzWzY6MjVdIDwtIGxhcHBseShwbGF5ZXJfc3RhdHNbNjoyNV0sIGFzLm51bWVyaWMpDQoNCiMgQ29udmVydGluZyB0aGUgcG9zaXRpb24gZmllbGQgdG8gYmUgYW4gb3JkZXIgZmFjdG9yIGZpZWxkDQpwbGF5ZXJfc3RhdHMkUG9zaXRpb24gPC0gZmFjdG9yKHBsYXllcl9zdGF0cyRQb3NpdGlvbiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxldmVscz0gYygiUEciLCAiU0ciLCAiU0YiLCAiUEYiLCAiQyIsICJPdGhlciIpLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygiUEciLCAiU0ciLCAiU0YiLCAiUEYiLCAiQyIsICJPdGhlciIpLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb3JkZXJlZCA9IFRSVUUpDQoNCiMgQ29udmVydGluZyB0aGUgcmFuayBmaWVsZCB0byBiZSBhbiBvcmRlciBmYWN0b3IgZmllbGQNCnBsYXllcl9zdGF0cyRQbGF5ZXJfUmFuayA8LSBhcy5mYWN0b3IocGxheWVyX3N0YXRzJFBsYXllcl9SYW5rKQ0KDQojIENoZWNraW5nIHRoZSBzdHJ1Y3R1cmUgb2YgdGhlIGRhdGEgc2V0DQpzdHIocGxheWVyX3N0YXRzKQ0KYGBgDQpcbmV3cGFnZQ0KIyMJRGUtZHVwbGljYXRpb24gJiBTY2FuIEkgDQoNCkluIHRoaXMgc2VjdGlvbiwgd2Ugc2VlayB0byBkZS1kdXBsaWNhdGUgdGhlIGRhdGEgaW4gdGhlIHR3byBkYXRhIHNldHMgYW5kIGFsc28gc2NhbiBmb3IgbWlzc2luZyBhbmQvb3Igc3BlY2lhbCB2YWx1ZXMgYW5kIGFsc28gYW55IG9idmlvdXMgZXJyb3JzLg0KDQojIyMgRGUtZHVwbGljYXRpb24gJiBTY2FuIEkgIC0gRGF0YSBTZXQgMTogTkJBIFBsYXllciBDb250cmFjdHMgMjAxOS0yMDIwLiANCg0KYGBge3J9DQojIERlLWR1cGxpY2F0aW9uIFByb2Nlc3MNCg0KIyBTYW1wbGUgYSByZWNvcmQgdG8gdW5kZXJzdGFuZCB0aGUgZHVwbGljYXRlIGkuZSBQYXUgR2Fzb2wNCnN1bShkdXBsaWNhdGVkKHBsYXllcl9jb250cmFjdCRQbGF5ZXJfTmFtZSkpDQoNCiMgV2hpY2ggcm93cyBhcmUgZHVwbGljYXRlcz8NCndoaWNoKGR1cGxpY2F0ZWQocGxheWVyX2NvbnRyYWN0JFBsYXllcl9OYW1lKSkNCg0KIyBMZXQncyBwaWNrIG9uIFBhdSBHYXNvbA0Kd2hpY2gocGxheWVyX2NvbnRyYWN0JFBsYXllcl9OYW1lPT0iUGF1IEdhc29sIikNCg0KIyBHZXQgYm90aCBoaXMgcmVjb3JkcyB0byBzZWUgd2h5IGl0J3MgZHVwbGljYXRlZA0KcGxheWVyX2NvbnRyYWN0ICU+JSBmaWx0ZXIocGxheWVyX2NvbnRyYWN0JFBsYXllcl9OYW1lPT0iUGF1IEdhc29sIikNCmBgYA0KDQpgYGB7cn0NCiMgU2VwYXJhdGUgb3V0IHRoZSBkdXBsaWNhdGUgcGxheWVycw0KdGJsX3BsYXllcl9uYW1lIDwtIHRhYmxlKHBsYXllcl9jb250cmFjdCRQbGF5ZXJfTmFtZSkNCmRmX2R1cF9wbGF5ZXJfbmFtZSA8LSBwbGF5ZXJfY29udHJhY3RbcGxheWVyX2NvbnRyYWN0JFBsYXllcl9OYW1lICVpbiUgbmFtZXModGJsX3BsYXllcl9uYW1lKVt0YmxfcGxheWVyX25hbWU+MV0sXQ0KDQojIENvbnNvbGlkYXRlIHRoZSBkdXBsaWNhdGUgcmVjb3JkcyBieSBzdW1taW5nIHVwIHRoZSBzYWxhcnkNCnN0YWdlX3NhbGFyeSA8LSBkZl9kdXBfcGxheWVyX25hbWUgJT4lIGdyb3VwX2J5KFBsYXllcl9JRCkgICU+JQ0KICBzdW1tYXJpc2UoYFNhbGFyeV8yMDE5LTIwYCA9IHN1bShgU2FsYXJ5XzIwMTktMjBgKSwNCiAgICAgICAgICAgIGBTYWxhcnlfMjAyMC0yMWAgPSBzdW0oYFNhbGFyeV8yMDIwLTIxYCkpICU+JSB1bmdyb3VwKCkNCg0KIyBDcmVhdGUgYSBkYXRhc2V0IHRvIG9idGFpbiB0aGUgcGxheWVyJ3MgbGFzdCB0ZWFtIHRoYXQgaGUgcGxheWVkIGZvcg0Kc3RhZ2VfbGFzdHJlY29yZCA8LSBkZl9kdXBfcGxheWVyX25hbWUgJT4lDQogIGdyb3VwX2J5KFBsYXllcl9OYW1lKSAlPiUNCiAgc2xpY2UobigpKSAlPiUNCiAgdW5ncm91cCgpDQoNCiMgUmVmaW5lIHRoZSBzZWxlY3RlZCBDb2x1bW5zDQpsYXN0cmVjb3JkX3NlbGVjdCA8LSBzZWxlY3Qoc3RhZ2VfbGFzdHJlY29yZCwgUGxheWVyX05hbWUsUGxheWVyX0lELFRlYW0pDQoNCiMgRmluYWxpc2VkIHRoZSBkdXBsaWNhdGUgcmVjb3JkcyB3aXRoIHRoZSBjb25zb2xpZGF0ZWQgc2xhcnkgYW5kIGZpbmFsIHRlYW0gdGhhdCB0aGUgcGxheWVyIHBsYXllZCBmb3I6DQpkZl9kdXBfcGxheWVyX25hbWVfZmluYWwgPC0gc3RhZ2Vfc2FsYXJ5ICU+JSBpbm5lcl9qb2luKGxhc3RyZWNvcmRfc2VsZWN0LCBieT0iUGxheWVyX0lEIikgJT4lIHNlbGVjdChQbGF5ZXJfTmFtZSxQbGF5ZXJfSUQsVGVhbSxgU2FsYXJ5XzIwMTktMjBgLGBTYWxhcnlfMjAyMC0yMWApDQoNCg0KIyBTZXBhcmF0ZSBvdXQgdGhlIG5vbi1kdXBsaWNhdGUgcGxheWVycw0KZGZfbm9uX2R1cF9wbGF5ZXJfbmFtZSA8LSBwbGF5ZXJfY29udHJhY3RbcGxheWVyX2NvbnRyYWN0JFBsYXllcl9OYW1lICVpbiUgbmFtZXModGJsX3BsYXllcl9uYW1lKVt0YmxfcGxheWVyX25hbWU8Ml0sXQ0KZGZfbm9uX2R1cF9wbGF5ZXJfbmFtZSA8LSBkZl9ub25fZHVwX3BsYXllcl9uYW1lICU+JSBzZWxlY3QoUGxheWVyX05hbWUsUGxheWVyX0lELFRlYW0sYFNhbGFyeV8yMDE5LTIwYCxgU2FsYXJ5XzIwMjAtMjFgKQ0KDQpgYGANCg0KYGBge3J9DQojIENvbWJpbmluZyB0aGUgdHdvIGRhdGFzZXRzIA0KcGxheWVyX2NvbnRyYWN0X2ZpbmFsIDwtIHVuaW9uKGRmX2R1cF9wbGF5ZXJfbmFtZV9maW5hbCxkZl9ub25fZHVwX3BsYXllcl9uYW1lKQ0KDQojIFNvcnQgdGhlIGRhdGEgc2V0IGJ5IGhpZ2hlc3Qgc2FsYXJ5DQpwbGF5ZXJfY29udHJhY3RfZmluYWwgPC0gcGxheWVyX2NvbnRyYWN0X2ZpbmFsICU+JSBhcnJhbmdlKC4sZGVzYyhgU2FsYXJ5XzIwMTktMjBgKSkNCg0KIyBBZGQgYSAiUmFua2luZyIgZmllbGQgdG8gdGhlIHNhbGFyeS4gV2Ugd2lsbCBuYW1lIGl0IFNhbGFyeV9SYW5rDQpwbGF5ZXJfY29udHJhY3RfZmluYWwgPC0gdGliYmxlOjpyb3dpZF90b19jb2x1bW4ocGxheWVyX2NvbnRyYWN0X2ZpbmFsLCAiU2FsYXJ5X1JhbmsiKQ0KDQojIENvbnZlcnQgdGhlIHJhbmsgZmllbGQgdG8gYmUgYSBmYWN0b3INCnBsYXllcl9jb250cmFjdF9maW5hbCRTYWxhcnlfUmFuayA8LSBhcy5mYWN0b3IocGxheWVyX2NvbnRyYWN0X2ZpbmFsJFNhbGFyeV9SYW5rKQ0KDQojIEFzc2lnbiB0aGUgdXBkYXRlZCBkYXRhc2V0IHRvIHRoZSBvcmlnaW5hbCBvbmUNCnBsYXllcl9jb250cmFjdCA8LSBwbGF5ZXJfY29udHJhY3RfZmluYWwNCg0KIyBDaGVjayBmb3IgZHVwbGljYXRlcw0Kc3VtKGR1cGxpY2F0ZWQocGxheWVyX2NvbnRyYWN0JFBsYXllcl9OYW1lKSkNCnN1bShkdXBsaWNhdGVkKHBsYXllcl9jb250cmFjdCRQbGF5ZXJfSUQpKQ0KDQojIENoZWNrIHRoZSBzdHJ1Y3R1cmUNCnN0cihwbGF5ZXJfY29udHJhY3QpDQoNCmBgYA0KDQpJbiB0aGlzIHNlY3Rpb24gaSBjaGVja2VkIGZvciBhbnkgbWlzc2luZyB2YWx1ZXMgKE5BJ3MpLCBpbmZpbml0ZSBvciBOYU4gdmFsdWVzLg0KDQpgYGB7cn0NCiMgQ2hlY2sgZm9yIE5BJ3MNCnNhcHBseShwbGF5ZXJfY29udHJhY3QsIGZ1bmN0aW9uKHgpIHN1bShpcy5uYSh4KSkpDQoNCiMgQ2hlY2sgZm9yIGluZmluaXRlcw0Kc2FwcGx5KHBsYXllcl9jb250cmFjdCwgZnVuY3Rpb24oeCkgc3VtKGlzLmluZmluaXRlKHgpKSkNCg0KIyBDaGVjayBmb3IgTmFODQpzYXBwbHkocGxheWVyX2NvbnRyYWN0LCBmdW5jdGlvbih4KSBzdW0oaXMubmFuKHgpKSkNCmBgYA0KDQpMb29rcyBsaWtlIHdlIGZvdW5kIHNvbWUgTkEncyBpbiB0aGUgc2FsYXJ5IGRhdGEgZm9yIDIwMjAtMjAyMS4NClNpbmNlIHRoZSBkYXRhIGlzIG51bWVyaWMsIHRha2luZyB0aGUgYXZlcmFnZSBzYWxhcnkgaXMgYW4gb3B0aW9uIHRoYXQgd2lsbCBiZSB1c2VkIHRvIGltcHV0ZSBpbnRvIHRoZSBOQSdzDQpgYGB7cn0NCg0KIyBJbXB1dGVzIHRoZSBtZWFuIGZvciB0aGUgbWlzc2luZyB2YWx1ZXMNCnBsYXllcl9jb250cmFjdCRgU2FsYXJ5XzIwMjAtMjFgIDwtIGltcHV0ZShwbGF5ZXJfY29udHJhY3QkYFNhbGFyeV8yMDIwLTIxYCwgZnVuPW1lYW4ocGxheWVyX2NvbnRyYWN0JGBTYWxhcnlfMjAyMC0yMWAsIG5hLnJtID0gVFJVRSkpIA0KDQojIENoZWNrIG1pc3NpbmcgdmFsdWUgYWZ0ZXIgaW1wdXRhdGlvbg0Kc2FwcGx5KHBsYXllcl9jb250cmFjdCwgZnVuY3Rpb24oeCkgc3VtKGlzLm5hKHgpKSkNCmNvbFN1bXMoaXMubmEocGxheWVyX2NvbnRyYWN0KSkNCmBgYA0KDQojIyMgRGUtZHVwbGljYXRpb24gJiBTY2FuIEkgIC0gRGF0YXNldCAyOiBOQkEgUGxheWVyIFRvdGFsIFN0YXRpc3RpY3MgZm9yIHRoZSAyMDE5LTIwMjAgTkJBIFNlYXNvbi4NCmBgYHtyfQ0KIyBEZS1kdXBsaWNhdGlvbiBQcm9jZXNzDQoNCiMgU2FtcGxlIGEgcmVjb3JkIHRvIHVuZGVyc3RhbmQgdGhlIGR1cGxpY2F0ZSBpLmUgVHJldm9yIEFyaXphDQpzdW0oZHVwbGljYXRlZChwbGF5ZXJfc3RhdHMkUGxheWVyX05hbWUpKQ0KDQojIFdoaWNoIHJvdyBudW1iZXJzIGhhcyB0aGUgZHVwbGljYXRlcz8NCndoaWNoKGR1cGxpY2F0ZWQocGxheWVyX3N0YXRzJFBsYXllcl9OYW1lKSkNCg0KIyBMZXQgc2VlIHdoaWNoIHJvdyBpcyBUcmV2b3IgQXJpemENCndoaWNoKHBsYXllcl9zdGF0cyRQbGF5ZXJfTmFtZT09IlRyZXZvciBBcml6YSIpDQoNCiMgSGF2ZSBhIGxvb2sgYXQgdGhlIGZ1bGwgcmVjb3JkcyBmcm9tIFRyZXZvciBBcml6YQ0KcGxheWVyX3N0YXRzICU+JSBmaWx0ZXIocGxheWVyX3N0YXRzJFBsYXllcl9OYW1lPT0iVHJldm9yIEFyaXphIikNCg0KIyBTZXBhcmF0ZSBvdXQgdGhlIGR1cGxpY2F0ZSBwbGF5ZXJzDQp0YmxfcGxheWVyX3N0YXRzX25hbWUgPC0gdGFibGUocGxheWVyX3N0YXRzJFBsYXllcl9OYW1lKQ0KZGZfZHVwX3BsYXllcl9zdGF0c19uYW1lIDwtIHBsYXllcl9zdGF0c1twbGF5ZXJfc3RhdHMkUGxheWVyX05hbWUgJWluJSBuYW1lcyh0YmxfcGxheWVyX3N0YXRzX25hbWUpW3RibF9wbGF5ZXJfc3RhdHNfbmFtZT4xXSxdDQoNCiMgUmVtb3ZlIHRoZSByb3dzIHdoZXJlIHRoZSB0ZWFtIGVxdWFscyAiVE9UIi4gVGhpcyByb3cgaXMgcmVkdW5kYW50DQpzdGFnZV9wbGF5ZXJfc3RhdHMgPC0gZGZfZHVwX3BsYXllcl9zdGF0c19uYW1lICU+JSBmaWx0ZXIoZGZfZHVwX3BsYXllcl9zdGF0c19uYW1lJFRlYW0gIT0gIlRPVCIpDQoNCiMgQ29uc29saWRhdGUgdGhlIGR1cGxpY2F0ZSByZWNvcmRzIGJ5IHN1bW1pbmcgdXAgdGhlIGNvdW50aW5nIHN0YXRzDQpzdGFnZV9wbGF5ZXJfc3RhdHMyIDwtIHN0YWdlX3BsYXllcl9zdGF0cyAgJT4lIGdyb3VwX2J5KFBsYXllcl9JRCkgICU+JSBzdW1tYXJpc2UoDQogICAgICAgICAgICAgICAgICAgIGBHYW1lc19wbGF5ZWRgID0gc3VtKGBHYW1lc19wbGF5ZWRgKSwNCiAgICAgICAgICAgICAgICAgICAgYEdhbWVzX3N0YXJ0ZWRgID0gc3VtKGBHYW1lc19zdGFydGVkYCksDQogICAgICAgICAgICAgICAgICAgIGBtaW51dGVzX3BsYXllZGAgPSBzdW0oYG1pbnV0ZXNfcGxheWVkYCksDQogICAgICAgICAgICAgICAgICAgIGBmaWVsZF9nb2Fsc2AgPSBzdW0oYGZpZWxkX2dvYWxzYCksDQogICAgICAgICAgICAgICAgICAgIGBmaWVsZF9nb2Fsc19hdHRlbXB0ZWRgID0gc3VtKGBmaWVsZF9nb2Fsc19hdHRlbXB0ZWRgKSwNCiAgICAgICAgICAgICAgICAgICAgYHR3b19wb2ludGVyc2AgPSBzdW0oYHR3b19wb2ludGVyc2ApLA0KICAgICAgICAgICAgICAgICAgICBgdHdvX3BvaW50ZXJzX2F0dGVtcHRlZGAgPSBzdW0oYHR3b19wb2ludGVyc19hdHRlbXB0ZWRgKSwNCiAgICAgICAgICAgICAgICAgICAgYHRocmVlX3BvaW50ZXJzYCA9IHN1bShgdGhyZWVfcG9pbnRlcnNgKSwNCiAgICAgICAgICAgICAgICAgICAgYHRocmVlX3BvaW50ZXJzX2F0dGVtcHRlZGAgPSBzdW0oYHRocmVlX3BvaW50ZXJzX2F0dGVtcHRlZGApLA0KICAgICAgICAgICAgICAgICAgICBgZnJlZV90aHJvd3NgID0gc3VtKGBmcmVlX3Rocm93c2ApLA0KICAgICAgICAgICAgICAgICAgICBgZnJlZV90aHJvd3NfYXR0ZW1wdGVkYCA9IHN1bShgZnJlZV90aHJvd3NfYXR0ZW1wdGVkYCksDQogICAgICAgICAgICAgICAgICAgIGBvZmZlbnNpdmVfcmVib3VuZHNgID0gc3VtKGBvZmZlbnNpdmVfcmVib3VuZHNgKSwNCiAgICAgICAgICAgICAgICAgICAgYGRlZmVuc2l2ZV9yZWJvdW5kc2AgPSBzdW0oYGRlZmVuc2l2ZV9yZWJvdW5kc2ApLA0KICAgICAgICAgICAgICAgICAgICBgdG90YWxfcmVib3VuZHNgID0gc3VtKGB0b3RhbF9yZWJvdW5kc2ApLA0KICAgICAgICAgICAgICAgICAgICBgYXNzaXN0c2AgPSBzdW0oYGFzc2lzdHNgKSwNCiAgICAgICAgICAgICAgICAgICAgYHN0ZWFsc2AgPSBzdW0oYHN0ZWFsc2ApLA0KICAgICAgICAgICAgICAgICAgICBgYmxvY2tzYCA9IHN1bShgYmxvY2tzYCksDQogICAgICAgICAgICAgICAgICAgIGB0dXJub3ZlcnNgID0gc3VtKGB0dXJub3ZlcnNgKSwNCiAgICAgICAgICAgICAgICAgICAgYHBlcnNvbmFsX2ZvdWxzYCA9IHN1bShgcGVyc29uYWxfZm91bHNgKSwNCiAgICAgICAgICAgICAgICAgICAgYHBvaW50c2AgPSBzdW0oYHBvaW50c2ApDQogICAgICAgICAgICApICU+JSB1bmdyb3VwKCkNCg0KIyBHZXQgdGhlIGxhc3Qgcm93IGZvciBlYWNoIHBsYXllciBhcyB3ZSBuZWVkIHRvIGdldCB0aGVpciBsYXN0IHBvc2l0aW9uIHRlYW0uIFdlIGRvIHRoaXMgYnkgdXNpbmcgdGhlIHNsaWNlIGZ1bmN0aW9uDQpsYXN0X3RlYW1fcGxheWVyX3N0YXRzIDwtIHN0YWdlX3BsYXllcl9zdGF0cyAlPiUNCiAgZ3JvdXBfYnkoUGxheWVyX05hbWUpICU+JQ0KICBzbGljZShuKCkpICU+JQ0KICB1bmdyb3VwKCkNCg0KIyBLZWVwaW5nIG9ubHkgdGhlIHJlcXVpcmVkIGNvbHVtbnMNCmxhc3RfdGVhbV9wbGF5ZXJfc3RhdHMgPC0gbGFzdF90ZWFtX3BsYXllcl9zdGF0cyAlPiUgc2VsZWN0KFBsYXllcl9OYW1lLFBsYXllcl9JRCxQb3NpdGlvbiwgVGVhbSkNCg0KIyBKb2luaW5nIHRoZSB0aGUgdHdvIHN0YWdlIGRhdGFzZXRzIHRvIGNvbXBsZXRlIHRoZSBkZS1kdXBsaWNhdGlvbiBwcm9jZXNzLg0Kc3RhZ2VfcGxheWVyX3N0YXRzMyA8LSBsYXN0X3RlYW1fcGxheWVyX3N0YXRzICU+JSBpbm5lcl9qb2luKHN0YWdlX3BsYXllcl9zdGF0czIsIGJ5PSJQbGF5ZXJfSUQiKSANCmRmX2R1cF9wbGF5ZXJfc3RhdHNfbmFtZSA8LSBzdGFnZV9wbGF5ZXJfc3RhdHMzDQoNCiMgU2VwYXJhdGUgb3V0IHRoZSBub24tZHVwbGljYXRlIHBsYXllcnMNCmRmX25vbl9kdXBfcGxheWVyX3N0YXRzX25hbWUgPC0gcGxheWVyX3N0YXRzW3BsYXllcl9zdGF0cyRQbGF5ZXJfTmFtZSAlaW4lIG5hbWVzKHRibF9wbGF5ZXJfc3RhdHNfbmFtZSlbdGJsX3BsYXllcl9zdGF0c19uYW1lPDJdLF0NCg0KIyBSZW1vdmluZyB0aGUgcGx5ZXItcmFuayBjb2x1bW4NCmRmX25vbl9kdXBfcGxheWVyX3N0YXRzX25hbWUgPC0gZGZfbm9uX2R1cF9wbGF5ZXJfc3RhdHNfbmFtZSAlPiUgc2VsZWN0KC1QbGF5ZXJfUmFuaykNCg0KIyBDaGVjayB0byBzZWUgaWYgdGhlIHRoZXJlJ3MgYW55IGR1cGxpY2F0ZXMgaW4gdGhlIG5vbi1kdXBsaWNhdGUgcGxheWVycyBzdGF0cyBkYXRhc2V0LiBFeHBlY3RlZCByZXN1bHRzIHNob3VsZCBiZSB6ZXJvDQpzdW0oZHVwbGljYXRlZChkZl9ub25fZHVwX3BsYXllcl9zdGF0c19uYW1lJFBsYXllcl9OYW1lKSkNCnN1bShkdXBsaWNhdGVkKGRmX25vbl9kdXBfcGxheWVyX3N0YXRzX25hbWUkUGxheWVyX0lEKSkNCg0KIyBDb21iaW5pbmcgdGhlIHR3byBkYXRhc2V0cyANCnBsYXllcl9zdGF0c19maW5hbCA8LSB1bmlvbihkZl9kdXBfcGxheWVyX3N0YXRzX25hbWUsZGZfbm9uX2R1cF9wbGF5ZXJfc3RhdHNfbmFtZSkNCg0KIyBDaGVjayB0byBzZWUgaWYgdGhlIHRoZXJlJ3MgYW55IGR1cGxpY2F0ZXMgaW4gdGhlIGNvbWJpbmVkIHBsYXllcnMgc3RhdHMgZGF0YXNldC4gRXhwZWN0ZWQgcmVzdWx0cyBzaG91bGQgYmUgemVybw0Kc3VtKGR1cGxpY2F0ZWQocGxheWVyX3N0YXRzX2ZpbmFsJFBsYXllcl9OYW1lKSkNCnN1bShkdXBsaWNhdGVkKHBsYXllcl9zdGF0c19maW5hbCRQbGF5ZXJfSUQpKQ0KYGBgDQpDaGVjayBmb3IgYW55IE5BJ3MgYW5kIFNwZWNpYWwgVmFsdWVzDQoNCmBgYHtyfQ0KIyBDaGVjayBmb3IgTkEncw0Kc2FwcGx5KHBsYXllcl9zdGF0c19maW5hbCwgZnVuY3Rpb24oeCkgc3VtKGlzLm5hKHgpKSkNCg0KIyBDaGVjayBmb3IgaW5maW5pdGVzDQpzYXBwbHkocGxheWVyX3N0YXRzX2ZpbmFsLCBmdW5jdGlvbih4KSBzdW0oaXMuaW5maW5pdGUoeCkpKQ0KDQojIENoZWNrIGZvciBOYU4NCnNhcHBseShwbGF5ZXJfc3RhdHNfZmluYWwsIGZ1bmN0aW9uKHgpIHN1bShpcy5uYW4oeCkpKQ0KDQpgYGANClRoZXJlIGEgbm8gbWlzc2luZyB2YWx1ZXMoTkEncyksIGluZmluaXRlIG9yIE5hTiB2YWx1ZXMgaW4gdGhlIHBsYXllcnMgc3RhdHMgZGF0YSBzZXQNCg0KXG5ld3BhZ2UNCiMjCVRpZHkgJiBNYW5pcHVsYXRlIERhdGEgSSANCg0KIyMjIFRpZHkgJiBNYW5pcHVsYXRlIERhdGEgSSAtIERhdGEgU2V0IDE6IE5CQSBQbGF5ZXIgQ29udHJhY3RzIDIwMTktMjAyMC4gDQoNClRoZSBjb250cmFjdHMgZGF0YSBzZXQgd2FzIGZvdW5kIHRvIGJyZWFrIHRoZSBmb2xsb3dpbmcgdGlkeSBwcmluY2lwbGU6DQoNCiogRWFjaCBvYnNlcnZhdGlvbiBoYXMgaXTigJlzIG93biByb3cgDQoNClRoZSBjb250cmFjdHMgZGF0YSBzZXQgaXMgdW50aWR5IGFzIGVhY2ggb2JzZXJ2YXRpb24gZm9yIGEgc2FsYXJ5IGRvZXMgbm90IGZvcm0gaXQncyBvd24gcm93IGFuZCB0aGUgc2FsYXJ5IHllYXJzIGFyZSBhY3Jvc3MgdGhlIGNvbHVtbnMuVG8gdGlkeSB0aGUgZGF0YXNldCB1cCwgd2Ugd2lsbCB1c2UgdGhlIGdhdGhlciBmdW5jdGlvbiBmcm9tIHRoZSB0aWR5dmVyc2UgdG8gY2hhbmdlIHRoZSBkYXRhIGZyb20gYSB3aWRlIGZvcm1hdCB0byBhIGxvbmcgZm9ybWF0Lg0KDQpgYGB7cn0NCiNUaWR5aW5nIHRoZSBjb250cmFjdHMgZGF0YSBzZXQgYnkgdXNpbmcgdGhlIGdhdGhlcigpIGZ1bmN0aW9uDQpwbGF5ZXJfY29udHJhY3RfZmluYWwgPC0gcGxheWVyX2NvbnRyYWN0X2ZpbmFsICU+JSBnYXRoZXIoU2FsYXJ5X1llYXIsU2FsYXJ5LDU6NikNCg0KIyBVcGRhdGluZyB0aGUgdmFsdWUgdG8gcmVmbGVjdCB0aGUgeWVhcg0KcGxheWVyX2NvbnRyYWN0X2ZpbmFsJFNhbGFyeV9ZZWFyIDwtIHBsYXllcl9jb250cmFjdF9maW5hbCRTYWxhcnlfWWVhciAlPiUgc3RyX3JlcGxhY2UoIlNhbGFyeV8yMDE5LTIwIiwgIjIwMTktMjAyMCIpDQpwbGF5ZXJfY29udHJhY3RfZmluYWwkU2FsYXJ5X1llYXIgPC0gcGxheWVyX2NvbnRyYWN0X2ZpbmFsJFNhbGFyeV9ZZWFyICU+JSBzdHJfcmVwbGFjZSgiU2FsYXJ5XzIwMjAtMjEiLCAiMjAyMC0yMDIxIikNCg0KYGBgDQoNCiMjIyBUaWR5ICYgTWFuaXB1bGF0ZSBEYXRhIEkgLSBEYXRhc2V0IDI6IE5CQSBQbGF5ZXIgVG90YWwgU3RhdGlzdGljcyBmb3IgdGhlIDIwMTktMjAyMCBOQkEgU2Vhc29uLg0KDQpUaGUgcGxheWVycyBzdGF0cyBkYXRhc2V0IHdhcyB0aWR5Lg0KDQpJdCBjb25mb3JtcyB0byB0aGVzZSBzdGFuZGFyZHM6DQoqIEVhY2ggdmFyaWFibGUgbXVzdCBoYXZlIGl0cyBvd24gY29sdW1uLg0KKiBFYWNoIG9ic2VydmF0aW9uIG11c3QgaGF2ZSBpdHMgb3duIHJvdy4NCiogRWFjaCB2YWx1ZSBtdXN0IGhhdmUgaXRzIG93biBjZWxsLg0KDQpcbmV3cGFnZQ0KIyMgRGF0YSAoUGFydCAyKQ0KDQpXZSBjYW4gbm93IG1lcmdlIHRoZSAyIGRhdGFzZXRzIGFmdGVyIHRoZSBkZS1kdXBsaWNhdGlvbiBwcm9jZXNzLiBUaGUgbmV3IG1lcmdlZCBkYXRhIHNldCB3aWxsIGJlIHBsYXllciBwZXJmb3JtYW5jZQ0KYGBge3J9DQojIEZpbHRlcmluZyB0aGUgY29udHJhY3QgZGF0YSBhcyB3ZSBvbmx5IHdhbnQgdGhlIGN1cnJlbnQgc2Vhc29uLg0KcGxheWVyX2NvbnRyYWN0X2ZpbHRlciA8LSBwbGF5ZXJfY29udHJhY3RfZmluYWwgJT4lIGZpbHRlcihwbGF5ZXJfY29udHJhY3RfZmluYWwkU2FsYXJ5X1llYXI9PSIyMDE5LTIwMjAiKQ0KDQojIENyZWF0aW5nIHRoZSBtZXJnZWQgZGF0YXNldCBuYW1lZCBwbGF5ZXJfcGVyZm9ybWFuY2UuDQojIHVzaW5nIGFuIGlubmVyIGpvaW4gYXMgdGhlcmUgYSBzb21lIHBsYXllcnMgdGhhdCBoYXZlIGEgY29udHJhY3QgYnV0IG5vIHN0YXRzLCBhbmQgYWxzbyB0aGVyZSdzIGEgc3RhdHMgZm9yIHBsYXllcnMgdGhhdCBoYXZlIGJlZW4gcGFpZCBidXQgaGF2ZSBub3QgYmVlbiBjYXB0dXJlZCBieSB0aGUgY29udHJhY3RzIGRhdGFzZXQuDQpwbGF5ZXJfcGVyZm9ybWFuY2UgPC0gcGxheWVyX3N0YXRzX2ZpbmFsICU+JSBpbm5lcl9qb2luKHBsYXllcl9jb250cmFjdF9maWx0ZXIsIGJ5PSJQbGF5ZXJfSUQiKQ0KDQojIFJlbW92aW5nIHRoZSBkdXBsaWNhdGlvbiBjb2x1bW5zIGFuZCBjbGVhbmluZyB1cCB0aGUgY29sdW1uIG5hbWVzDQpwbGF5ZXJfcGVyZm9ybWFuY2UgPC0gcGxheWVyX3BlcmZvcm1hbmNlICU+JSBzZWxlY3QoLVRlYW0ueSwtUGxheWVyX05hbWUueSwtU2FsYXJ5X1llYXIpDQpwbGF5ZXJfcGVyZm9ybWFuY2UgPC0gcGxheWVyX3BlcmZvcm1hbmNlICU+JSByZW5hbWUoUGxheWVyX05hbWUgPSBQbGF5ZXJfTmFtZS54KQ0KcGxheWVyX3BlcmZvcm1hbmNlIDwtIHBsYXllcl9wZXJmb3JtYW5jZSAlPiUgcmVuYW1lKFRlYW0gPSBUZWFtLngpDQoNCiMgQ2hlY2sgZm9yIGFueSBkdXBzIGFmdGVyIHRoZSBtZXJnZWQNCnN1bShkdXBsaWNhdGVkKHBsYXllcl9wZXJmb3JtYW5jZSRQbGF5ZXJfTmFtZSkpDQpzdW0oZHVwbGljYXRlZChwbGF5ZXJfcGVyZm9ybWFuY2UkUGxheWVyX0lEKSkNCmBgYA0KXG5ld3BhZ2UNCiMjCVRpZHkgJiBNYW5pcHVsYXRlIERhdGEgSUkgDQoNCkluIHRoaXMgc2VjdGlvbiwgYWZ0ZXIgdGhlIGRhdGEgc2V0cyB3ZXJlIG1lcmdlZCwgSSBjcmVhdGVkIHRoZSB0d28gbmV3IGZpZWxkczogcGxheWVyX2VmZmljaWVuY3kgJiBTYWxhcnlfcGVyX2dhbWUNCg0KYGBge3J9DQojIENyZWF0aW5nIHRoZSAyIG5ldyBmaWVsZHMgdXNpbmcgbXV0YXRlDQpwbGF5ZXJfcGVyZm9ybWFuY2VfbmV3IDwtIHBsYXllcl9wZXJmb3JtYW5jZSAlPiUgbXV0YXRlKC4sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBsYXllcl9lZmZpY2llbmN5ID0gKHBvaW50cysNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0b3RhbF9yZWJvdW5kcysNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhc3Npc3RzKw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0ZWFscysNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBibG9ja3MtDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKGZpZWxkX2dvYWxzX2F0dGVtcHRlZC1maWVsZF9nb2FscyktDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKGZyZWVfdGhyb3dzX2F0dGVtcHRlZC1mcmVlX3Rocm93cyktDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHVybm92ZXJzKS9HYW1lc19wbGF5ZWQNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLFNhbGFyeV9wZXJfZ2FtZT0gU2FsYXJ5L0dhbWVzX3BsYXllZA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkNCiMgU29ydGluZyB0aGUgRGF0YXNldA0KcGxheWVyX3BlcmZvcm1hbmNlX25ldyA8LSBwbGF5ZXJfcGVyZm9ybWFuY2VfbmV3ICU+JSBhcnJhbmdlKC4sZGVzYyhwbGF5ZXJfZWZmaWNpZW5jeSkpDQoNCiMgU2VsZWN0ZWQgdGhlIHJlcXVpcmVkIGNvbHVtbnMNCnBsYXllcl9wZXJmb3JtYW5jZV9uZXcgPC0gcGxheWVyX3BlcmZvcm1hbmNlX25ldyAlPiUgc2VsZWN0KFBsYXllcl9OYW1lLFBsYXllcl9JRCxQb3NpdGlvbixUZWFtLEdhbWVzX3BsYXllZCxHYW1lc19zdGFydGVkLG1pbnV0ZXNfcGxheWVkLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFNhbGFyeV9SYW5rLFNhbGFyeSxwbGF5ZXJfZWZmaWNpZW5jeSxTYWxhcnlfcGVyX2dhbWUpDQoNCmBgYA0KDQoNCmBgYHtyfQ0KIyBUaGUgbmV3IG1lcmdlZCBkYXRhc2V0DQpoZWFkKHBsYXllcl9wZXJmb3JtYW5jZV9uZXcpDQpzdHIocGxheWVyX3BlcmZvcm1hbmNlX25ldykNCmBgYA0KDQpcbmV3cGFnZQ0KIyMJU2NhbiBJSQ0KDQpJbiB0aGlzIHNlY3Rpb24sSSBzY2FubmVkIHRoZSBudW1lcmljIGRhdGEgc2V0IGZvciBhbnkgb3V0bGllcnMuIA0KDQpgYGB7cn0NCiMgQ2hlY2sgZm9yIE91dGxpZXJzIHVzaW5nIGJveHBsb3RzDQpkZl9vdXRsaWVycyA8LSBwbGF5ZXJfcGVyZm9ybWFuY2VfbmV3ICU+JSBzZWxlY3RfaWYoaXMubnVtZXJpYykNCmZvciAoaSBpbiAxOjYpIHsNCiAgYm94cGxvdChkZl9vdXRsaWVyc1tjKGkpXSwgeGxhYiA9IGNvbG5hbWVzKGRmX291dGxpZXJzKVtpXSkNCn0NCg0KYGBgDQpBZnRlciBydW5uaW5nIHRoZSBib3hwbG90cyB0byBkZXR0ZWN0IG91dGxpZXJzLCBJdCBhcHBlYXJzIHRoYXQgdGhlIHNhbGFyeSBmaWVsZCBhbmQgbmV3bHkgY3JlYXRlZCBwbGF5ZXIgZWZmaWNpZW5jeSBmaWVsZCBoYXMgYSBmZXcgb3V0bGllcnMgdGhhdCB3ZSBuZWVkIHRvIGFkZHJlc3MuVGhlIHNhbGFyeSBwZXIgZ2FtZSBmaWVsZCBpcyBiYXNlZCBvZiBzYWxhcnkgc28gaW4gdGhlIG5leHQgc2VjdGlvbiBpJ2xsIGp1c3QgbG9vayBhdCBjYXBwaW5nIHNhbGFyeS4NCg0KYGBge3J9DQojIENhcCBGdW5jdGlvbg0KY2FwIDwtIGZ1bmN0aW9uKHgpew0KICAgIHF1YW50aWxlcyA8LSBxdWFudGlsZSggeCwgYyguMDUsIDAuMjUsIDAuNzUsIC45NSApICkNCiAgICB4WyB4IDwgcXVhbnRpbGVzWzJdIC0gMS41KklRUih4KSBdIDwtIHF1YW50aWxlc1sxXQ0KICAgIHhbIHggPiBxdWFudGlsZXNbM10gKyAxLjUqSVFSKHgpIF0gPC0gcXVhbnRpbGVzWzRdDQogICAgeA0KfQ0KDQojIEFwcGx5IHRoZSBjYXBwaW5nDQpTYWxhcnkgPC0gcGxheWVyX3BlcmZvcm1hbmNlX25ldyRTYWxhcnkgJT4lIGNhcCgpDQpwbGF5ZXJfZWZmaWNpZW5jeSA8LSBwbGF5ZXJfcGVyZm9ybWFuY2VfbmV3JHBsYXllcl9lZmZpY2llbmN5ICU+JSBjYXAoKQ0KDQojIExldCdzIFJlY3JlYXRlIHRoZSBib3hwbG90cyB0byBlbnN1cmUgdGhhdCdzIGl0J3MgYWxsIGZpeGVkDQpwYXIobWZyb3cgPSBjKDIsMikpDQpib3hwbG90KFNhbGFyeSwgeGxhYiA9ICJTYWxhcnkgKCQpIikNCmJveHBsb3QocGxheWVyX2VmZmljaWVuY3ksIHhsYWIgPSAiUGxheWVyIEVmZmljaWVuY3kgU2NvcmUiKQ0KYGBgDQpBcyB5b3UgY2FuIHNlZSBpbiB0aGUgYm94cGxvdHMgYWZ0ZXIgdGhlIGNhcHBpbmcgdGhhdCBpdCBkaWQgYSBnb29kIGpvYiBvZiByZW1vdmluZyBhbGwgYnV0IG9uZSBvdXRsaWVyLg0KTG9va2luZyBpbnRvIHRoaXMgbG9uZSBvdXRsaWVyLCBpdCB3YXMgc3RlcGggY3Vycnkgd2hvIGdvdCBpbmp1cmVkIDUgZ2FtZXMgaW50byB0aGUgc2Vhc29uLiBGb3IgdGhvc2UgNSBnYW1lcywgaGUgZWFybmVkICQ4LDA0NiwzNTEuNiAoOG1pbCkgcGVyIGdhbWUgZm9yIHRoZSAyMDE5LTIwMjAuIEhvdydzIHRoYXQgZm9yIGEgcGF5IGRheS4NCg0KXG5ld3BhZ2UNCiMjCVRyYW5zZm9ybSANCg0KSW4gdGhpcyBzZWN0aW9uLCBXZSB1c2VkIHRvIHRoZSBzYWxhcnkgZmllbGQgdG8gZG8gdGhlIHRyYW5zZm9ybWF0aW9uLg0KV2Ugd2lsbCB1c2UgYSBjb21iaW5hdGlvbiBvZiBsb2dhcml0aG1zLCBzcXVhcmUgcm9vdCBhbmQgYm94Y294IHRlY2huaXF1ZXMgdG8gYXBwbHkgYW5kIHRoZW4gd2Ugd2lsbCBjaG9vc2UgdGhlIGJlc3QgdGVjaG5pcXVlIHRvIHVzZSBmcm9tIHRoZSB0cmFuc2Zvcm1hdGlvbi4NCg0KYGBge3J9DQojIHBsb3QgdGhlIGhpc3RvZ3JhbSBmaXJzdA0KaGlzdChwbGF5ZXJfcGVyZm9ybWFuY2VfbmV3JFNhbGFyeSkNCmBgYA0KDQpUaGUgc3RhbmRhcmQgaGlzdG9ncmFtIGhhcyBhIHJpZ2h0IHNrZXdlZCBlZmZlY3QgdG8gaXQuDQoNCmBgYHtyfQ0KIyBVc2luZyB0aGUgc3F1YXJlIHJvb3QgdGVjaG5pcXVlDQpzYWxhcnlfc3F1YXJlIDwtIHNxcnQocGxheWVyX3BlcmZvcm1hbmNlX25ldyRTYWxhcnkpDQpoaXN0KHNhbGFyeV9zcXVhcmUpDQpgYGANCg0KYGBge3J9DQojIFVzaW5nIHRoZSBsb2dhcml0aG0xMCB0ZWNobmlxdWUNCkxvZyA8LSBsb2cxMChwbGF5ZXJfcGVyZm9ybWFuY2VfbmV3JFNhbGFyeSkNCmhpc3QoTG9nKQ0KYGBgDQoNCg0KYGBge3J9DQojIFVzaW5nIHRoZSBCb3gtQ294IHRlY2huaXF1ZQ0KYm94Y294X2hpc3QgPC0gQm94Q294KHBsYXllcl9wZXJmb3JtYW5jZV9uZXckU2FsYXJ5LCBsYW1iZGEgPSAiYXV0byIpDQpoaXN0KGJveGNveF9oaXN0KQ0KYGBgDQpCb3RoIExvZzEwIGFuZCBCb3gtQ294IG1ldGhvZHMgbm9ybWFsaXNlcyB0aGUgc2FsYXJ5IGRhdGEuDQoNCjxicj4=